Linux 성능 튜닝 트러블슈팅: top, iostat, vmstat로 문제 해결

Linux 성능 튜닝 트러블슈팅: top, iostat, vmstat로 문제 해결 홈서버와 사내 개발 서버를 운영하다가 어느 날 갑자기 API 응답이 10초 이상 지연되거나 SSH 접속조차 버벅거리며 멈추는 장애를 겪었습니다. 어디서 병목이 터졌는지 모를 때 무작정 서버를 재부팅하면 원인 규명을 영영 놓치게 됩니다. 터미널에 접속하자마자 top, vmstat, iostat 세 가지 명령어로 CPU 폭주인지, 메모리 스왑인지, 디스크 I/O 병목인지 3분 만에 진단해 내는 실전 모니터링 노하우를 정리했습니다. 1. Linux 성능 모니터링 도구 기본 사용법 1.1 top: 실시간 CPU/메모리 모니터링 top은 Linux 시스템의 실시간 상태를 확인하는 가장 기본적인 도구입니다. CPU 사용률, 메모리 사용량, 실행 중인 프로세스 목록 등을 1초 단위로 업데이트하여 보여줍니다. ...

2026년 4월 23일 · 4 분 · 757 단어 · Chanyeol

SpringBoot + GitHub Actions CI/CD 완벽 구성 & 문제 해결

SpringBoot + GitHub Actions CI/CD 완벽 구성 & 문제 해결 SpringBoot 애플리케이션에 GitHub Actions를 활용한 CI/CD 파이프라인을 구축하고, 실제 운영 환경에서 발생하는 문제를 해결하는 방법을 단계별로 설명합니다. 이 가이드는 JDK 설치부터 클라우드 배포까지 전체 워크플로우를 다루며, 실패 사례별 트러블슈팅 기법을 포함합니다. 튜토리얼: SpringBoot CI/CD 기본 구성 1. GitHub 리포지토리 연결 및 기본 Workflow 생성 SpringBoot 프로젝트를 GitHub에 업로드합니다. git init git add . git commit -m "Initial commit" git branch -M main git remote add origin https://github.com/<your-id>/<repo-name>.git git push -u origin main .github/workflows/springboot-ci-cd.yml 파일을 생성합니다. 2. 기본 Workflow YAML 구성 name: SpringBoot CI/CD on: push: branches: [ "main" ] pull_request: branches: [ "main" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up JDK 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' - name: Build with Gradle run: ./gradlew build - name: Build Docker image run: | docker build -t my-springboot-app:latest . docker tag my-springboot-app:latest \ $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/my-springboot-app:latest - name: Push to ECR run: | aws ecr get-login-password --region $AWS_REGION | \ docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/my-springboot-app:latest env: AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }} AWS_REGION: ${{ secrets.AWS_REGION }} 팁: Maven 사용 시 ./mvnw clean package로 변경합니다. Dockerfile이 프로젝트 루트에 위치해야 합니다. ...

2026년 4월 23일 · 3 분 · 498 단어 · Chanyeol

CodePipeline 실패 해결법: CI/CD 파이프라인 문제 분석부터 복구까지

AWS CodePipeline 실패 해결법: CI/CD 파이프라인 문제 분석부터 복구까지 AWS CodePipeline으로 GitHub 레포지토리와 EC2 배포 자동화를 처음 구축했을 때, 빌드 아티팩트가 S3 권한 문제로 누락되거나 CodeBuild 스크립트에서 환경 변수를 못 불러와 파이프라인이 멈추는 에러를 수없이 겪었습니다. AWS 콘솔의 에러 메시지는 너무 불친절해서 원인을 찾기 위해 CloudWatch 로그 그룹을 하나하나 뒤져야 했죠. 실무 파이프라인 운영 중 가장 빈번하게 발생하는 핵심 에러 유형과 즉각적인 해결법을 기록했습니다. 1. 튜토리얼: CodePipeline 기본 구성 및 모니터링 1.1 CodePipeline 구조 이해 CodePipeline은 Source → Build → Deploy 3단계로 구성됩니다. 각 단계는 독립적인 액션으로 관리되며, 실패 시 롤백이 가능합니다. 예를 들어, GitHub에서 코드를 가져온 후 CodeBuild로 빌드하고, CodeDeploy로 EC2에 배포하는 파이프라인을 구성할 수 있습니다. ...

2026년 4월 21일 · 3 분 · 539 단어 · Chanyeol
SSH PortProxy로 안전하게 원격 서비스 접근하기

SSH PortProxy로 안전하게 원격 서비스 접근하기

튜토리얼 개요 SSH PortProxy는 방화벽 제약이 있는 환경에서도 안전하게 원격 서비스에 접근할 수 있는 강력한 기술입니다. 기존 SSH 터널링과 유사하지만, 로컬 포트 포워딩을 더욱 직관적으로 구성할 수 있으며, 별도의 터널링 소프트웨어 없이도 작동합니다. 예를 들어 개발 서버가 외부 접속을 차단하고 있더라도, SSH 게이트웨이 서버를 경유하여 로컬 머신에서 localhost:8080으로 접근하면 원격 서버의 웹 서비스에 연결할 수 있습니다. 이는 데이터베이스 접근이나 내부 관리 콘솔 접속 시 특히 유용합니다. ...

2026년 4월 20일 · 4 분 · 640 단어 · Chanyeol

Chrome DevTools로 잡는 JavaScript 메모리 누수

개요 JavaScript 메모리 누수는 웹 애플리케이션의 성능을 저하시키는 주요 원인 중 하나입니다. 특히 SPA(Single Page Application)에서 페이지 전환 시 메모리 사용량이 지속적으로 증가하거나, DOM 요소를 제거했음에도 불구하고 참조가 유지되는 문제가 빈번히 발생합니다. 이 가이드에서는 Chrome DevTools를 활용해 메모리 누수를 진단하고 해결하는 체계적인 방법을 단계별로 설명합니다. 실제 사례를 바탕으로 한 코드 예제와 검증 방법을 포함해, 개발자들이 즉시 적용할 수 있는 실용적인 솔루션을 제공합니다. 문제 상황: 메모리 누수의 대표적 증상 1. 페이지 전환 시 메모리 사용량 지속 증가 SPA에서 라우터가 페이지 전환을 처리할 때, 이전 페이지의 리소스가 제대로 해제되지 않으면 메모리 사용량이 선형적으로 증가합니다. 예를 들어 React 애플리케이션에서 useEffect 정리 함수를 누락하거나, Vue에서 beforeUnmount 생명주기에서 이벤트 리스너를 제거하지 않는 경우 발생합니다. ...

2026년 4월 18일 · 4 분 · 846 단어 · Chanyeol

사내 VM IP 관리 시스템 만들기 - React 대시보드 + Docker Compose 배포 (4편)

React 대시보드 대시보드는 탭 2개로 구성된다. VM 목록 — 전체 VM 현황, 상태별 필터, ONLINE/OFFLINE/UNKNOWN 배지 이벤트 로그 — IP 충돌, IP 변경, 오프라인 이벤트 타임라인 WebSocket이 아닌 30초 폴링으로 구현했다. VM 상태가 초 단위로 바뀌지 않고, 운영 대시보드 특성상 약간의 지연은 허용된다. 심플하게 가는 게 낫다고 판단했다. 30초 폴링 훅 // usePolling.js import { useEffect, useRef } from 'react'; export function usePolling(callback, intervalMs = 30_000) { const callbackRef = useRef(callback); useEffect(() => { callbackRef.current = callback; }); useEffect(() => { callbackRef.current(); // 마운트 시 즉시 1회 실행 const id = setInterval(() => callbackRef.current(), intervalMs); return () => clearInterval(id); }, [intervalMs]); } // App.jsx const [vms, setVms] = useState([]); const [loading, setLoading] = useState(true); usePolling(async () => { const data = await fetch('/api/vms').then(r => r.json()); setVms(data); setLoading(false); }, 30_000); callbackRef를 쓰는 이유는 setInterval 클로저가 최초 등록 시점의 callback을 계속 참조하는 문제를 피하기 위해서다. useRef로 항상 최신 콜백을 가리키도록 한다. ...

2026년 4월 15일 · 5 분 · 958 단어 · Chanyeol

사내 VM IP 관리 시스템 만들기 - Spring Boot Heartbeat 처리 + 이상 감지 + Slack 알림 (3편)

heartbeat 처리 흐름 에이전트가 30초마다 POST /api/heartbeat를 호출한다. 서버는 MAC 주소를 기준으로 VM을 식별하고 상태를 갱신한다. heartbeat 수신 │ ▼ MAC 주소로 VM 조회 │ ├─ DB에 없음 → 신규 등록 └─ DB에 있음 → 상태 갱신 (hostname, last_seen_at) │ ▼ 이상 감지 실행 │ ├─ IP 변경 감지 ├─ IP 충돌 감지 └─ IP 대역 이탈 감지 HeartbeatService @Service @RequiredArgsConstructor @Transactional public class HeartbeatService { private final VmRepository vmRepo; private final AnomalyDetectorService anomalyDetector; public void process(HeartbeatRequest req) { Vm vm = vmRepo.findByMacAddress(req.getMacAddress()) .orElseGet(() -> vmRepo.save(Vm.create( req.getMacAddress(), req.getHostname(), req.getAgentVersion() ))); vm.heartbeat(req.getHostname(), req.getAgentVersion()); anomalyDetector.detectAndUpdate(vm, req.getNetworkInterfaces()); } } Vm.create()는 신규 VM을 UNKNOWN 상태로 생성한다. vm.heartbeat()는 lastSeenAt을 현재 시각으로 갱신하고 상태를 ONLINE으로 바꾼다. ...

2026년 4월 14일 · 5 분 · 988 단어 · Chanyeol

사내 VM IP 관리 시스템 만들기 - Node.js 에이전트 → Go 에이전트 전환 (2편)

처음엔 Node.js로 만들었다 에이전트를 처음 만들 때 익숙한 Node.js를 썼다. 로직 자체는 단순하다. 30초마다 POST /api/heartbeat 호출 호스트명, MAC 주소, IP 주소를 payload에 담아 전송 const axios = require('axios'); const os = require('os'); const SERVER_URL = process.env.IPAM_SERVER_URL; const INTERVAL_MS = 30_000; function getNics() { const interfaces = os.networkInterfaces(); const result = []; for (const [name, addrs] of Object.entries(interfaces)) { for (const addr of addrs) { if (addr.family === 'IPv4' && !addr.internal) { result.push({ interfaceName: name, macAddress: addr.mac, ipAddress: addr.address, }); } } } return result; } async function sendHeartbeat() { try { await axios.post(`${SERVER_URL}/api/heartbeat`, { hostname: os.hostname(), networkInterfaces: getNics(), }); } catch (e) { console.error('[heartbeat] 실패:', e.message); } } sendHeartbeat(); setInterval(sendHeartbeat, INTERVAL_MS); 동작은 잘 했다. 문제는 배포였다. ...

2026년 4월 13일 · 4 분 · 731 단어 · Chanyeol

사내 VM IP 관리 시스템 만들기 - 기획 배경 + 전체 아키텍처 + DB 스키마 (1편)

왜 만들게 됐나 VM이 늘어나면서 IP 관리가 슬슬 문제가 됐다. 스프레드시트에 IP 할당 현황을 수동으로 관리하는데, 담당자마다 업데이트 타이밍이 달라서 실제 상태와 항상 일치하지 않음 VM을 삭제하면서 IP 해제를 빠뜨리면, 나중에 같은 IP를 다른 VM에 할당해서 충돌이 발생 VM이 죽어도 스프레드시트에 살아있는 것처럼 남아있어서 “이 서버 살아있어요?” 같은 질문이 자주 옴 특정 VM의 IP 변동 이력을 알고 싶어도 추적 방법이 없음 그래서 만들었다. VM에 경량 에이전트를 설치해서 주기적으로 heartbeat를 전송 서버에서 heartbeat를 분석해서 IP 충돌, IP 변경, 오프라인 등 이상 감지 이상 발생 시 Slack 알림 웹 UI에서 전체 VM 현황과 이벤트 로그 조회 기술 스택 영역 기술 선택 이유 에이전트 Go 1.22 단일 바이너리, 런타임 불필요, 크기 소형 백엔드 Spring Boot 3 + JPA 팀 표준 스택 DB PostgreSQL 16 이력 테이블 + 집계 쿼리 캐시/dedup Redis 7 Slack 알림 중복 방지 프론트엔드 React 18 + Vite 30초 폴링 대시보드 인프라 Docker Compose 4개 서비스 일괄 관리 에이전트는 처음에 Node.js로 만들었다가 Go로 전환했다. 이유는 2편에서 다룬다. ...

2026년 4월 12일 · 4 분 · 759 단어 · Chanyeol

사내 인사정보 관리 시스템 만들기 - Docker Compose 멀티 서비스 배포 + 트러블슈팅 (3편)

Docker Compose 멀티 서비스 구성 세 서비스가 올바른 순서로 떠야 한다. db (PostgreSQL) → server (Express + Prisma) → client (React + nginx) server가 DB보다 먼저 뜨면 Prisma 연결이 실패하고, client가 server보다 먼저 뜨면 API 프록시가 동작하지 않는다. docker-compose.yml services: db: image: postgres:16-alpine container_name: insa-db environment: POSTGRES_DB: insa_db POSTGRES_USER: insa POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - ./data/postgres:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U insa -d insa_db"] interval: 5s timeout: 5s retries: 10 server: build: ./server container_name: insa-server ports: - "4000:4000" env_file: - ./server/.env environment: DATABASE_URL: postgresql://insa:${DB_PASSWORD}@db:5432/insa_db depends_on: db: condition: service_healthy healthcheck: test: ["CMD", "node", "-e", "require('http').get('http://localhost:4000/health', r => process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))"] interval: 5s timeout: 5s retries: 10 restart: unless-stopped client: build: ./client container_name: insa-client ports: - "3002:80" depends_on: server: condition: service_healthy restart: unless-stopped depends_on에 condition: service_healthy를 쓰면 단순히 컨테이너가 “시작됐는지"가 아니라 “준비됐는지"를 확인하고 다음 서비스를 시작한다. ...

2026년 4월 11일 · 5 분 · 862 단어 · Chanyeol