개발 트러블슈팅 완전 정복 | 웹개발·백엔드·데브옵스 실무 에러 해결법. Docker, GitHub Actions, TypeScript, MySQL, Redis 등 실전 경험 기반의 개발 블로그
목요일
Kubernetes CrashLoopBackOff 완벽 해결 가이드
증상: kubectl get pods에서 STATUS가 CrashLoopBackOff로 표시되며, 컨테이너가 시작과 종료를 반복합니다.
원인:
1. 애플리케이션 코드 버그 또는 크래시
2. 환경 변수 누락
3. 필요한 파일 또는 리소스 미존재
4. 헬스체크 실패
5. 메모리/CPU 부족
6. 이미지 문제
해결 방법:
1. 로그 확인: kubectl logs 파드이름 --previous
2. 파드 상세 정보 확인: kubectl describe pod 파드이름
3. 환경 변수 검증: deployment spec에서 환경 변수 설정 확인
4. 헬스체크 설정 검토: initialDelaySeconds를 충분히 설정 (예: 30초)
5. 리소스 요청/제한 확인: requests와 limits 설정 검토, OOMKilled 확인
6. 이미지 및 진입점 확인: Dockerfile의 CMD가 정확한지 확인
7. 볼륨 마운트 확인: 필요한 경로가 정확히 마운트되었는지 확인
디버그 팁: kubectl debug 파드이름 -it --image=busybox로 컨테이너 환경 직접 확인
정리: CrashLoopBackOff는 로그와 describe 명령으로 원인 파악이 가능하며, 대부분 설정 문제입니다.
Docker 멀티스테이지 빌드 캐시 최적화하기
🔍 검색 키워드: Docker 멀티스테이지 빌드, Dockerfile 캐시 성능, Docker 레이어 캐싱, 빌드 시간 단축
Docker 멀티스테이지 빌드 캐시 최적화하기
증상
Dockerfile로 이미지를 빌드할 때마다 다음과 같은 문제가 발생한다:
$ docker build -t myapp:latest .
Step 3/10 : RUN npm install # 매번 30초 이상 소요
Step 4/10 : RUN npm run build # 매번 60초 이상 소요
...
real 2m 15s
코드는 조금만 바뀌었는데도 빌드 시간이 계속 길어진다. CI/CD 파이프라인에서 빌드만 몇 분씩 걸려서 배포 속도가 느리다. 특히 npm, pip 같은 의존성 설치 단계가 매번 전부 다시 실행된다.
원인
Docker는 각 RUN, COPY, ADD 단계를 하나의 레이어로 취급하고, 파일이 변경되면 그 이후의 모든 레이어를 다시 빌드한다. 예를 들어:
FROM node:18
COPY . /app # 레이어1: 모든 파일 복사
WORKDIR /app
RUN npm install # 레이어2: 의존성 설치 (레이어1 변경되면 캐시 무효)
RUN npm run build # 레이어3: 빌드 (레이어2 변경되면 캐시 무효)
코드를 한 줄만 바꿔서 COPY . /app을 실행하면 npm install, npm run build가 모두 다시 실행된다. 즉, 캐시를 제대로 활용하지 못한다.
멀티스테이지 빌드를 사용하면서도 각 스테이지 간 의존성이 꼬여 있으면 캐시 효율이 떨어진다.
해결방법
1. 의존성 파일을 먼저 복사 (핵심!)
FROM node:18 AS builder
WORKDIR /app
# package.json, package-lock.json만 먼저 복사
COPY package*.json ./
# 의존성 설치 (코드 변경 시 캐시 유지)
RUN npm ci
# 이제 코드 복사
COPY . .
# 빌드
RUN npm run build
# 런타임 스테이지
FROM node:18-alpine
WORKDIR /app
# 의존성만 복사 (빌드 아티팩트 불필요)
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
캐시 효과:
package.json불변 → npm install 캐시 유지- 코드만 변경 → COPY . . 부터만 재실행
- 빌드 시간 30초 → 5초
2. Docker Buildkit으로 고급 캐싱 활용
// syntax=docker/dockerfile:1.4
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
// --mount=type=cache로 npm cache 디렉토리 보존
RUN --mount=type=cache,target=/root/.npm \
npm ci --prefer-offline
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
빌드 명령:
DOCKER_BUILDKIT=1 docker build -t myapp:latest .
효과:
- npm cache가 빌드 간에 유지되어 더 빠름
- 오프라인 설치 최적화
3. 불필요한 파일 제외 (.dockerignore)
.git
.gitignore
node_modules
npm-debug.log
.env
.env.local
dist
build
coverage
.DS_Store
README.md
효과:
- COPY . . 시 변경되지 않은 파일이 많으면 해시 계산이 더 오래 걸림
- .dockerignore로 제외하면 캐시 재계산 범위 축소
4. 여러 스테이지에서 캐시 공유
// syntax=docker/dockerfile:1.4
FROM node:18 AS dependencies
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci
// 빌드 스테이지1
FROM dependencies AS builder1
COPY . .
RUN npm run build
// 빌드 스테이지2 (테스트)
FROM dependencies AS tester
COPY . .
RUN npm test
// 최종 스테이지
FROM node:18-alpine
WORKDIR /app
COPY --from=builder1 /app/dist ./dist
COPY --from=dependencies /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]
5. 번들 사이즈 최소화 (캐시 영향 없지만 이미지 크기 감소)
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
// 크기 최소화: dependencies만 복사, devDependencies 제외
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
// 본번 의존성만 설치 (devDependencies 없음)
COPY package*.json ./
RUN npm ci --omit=dev
EXPOSE 3000
CMD ["node", "dist/index.js"]
정리표
| 문제 | 원인 | 해결법 |
|---|---|---|
| npm install 매번 재실행 | COPY . . 후 RUN npm install | package.json만 먼저 복사 |
| 캐시가 가끔만 작동 | 불필요 파일 포함 시 해시 변경 | .dockerignore 작성 |
| 빌드 시간 여전히 길다 | npm cache 미보존 | Buildkit + --mount=cache 활용 |
| 이미지 크기 커짐 | devDependencies 포함 | npm ci --omit=dev |
| 멀티스테이지 간 캐시 미공유 | 각 스테이지가 독립적 | dependencies AS 공용 스테이지 생성 |
TIP: docker build --progress=plain으로 상세 로그 확인, docker image history myapp:latest로 각 레이어 크기 확인 가능. 캐시 강제 무효화는 docker build --no-cache 또는 COPY . . --chown=node:node 같은 타임스탐프 변경 명령으로.
금요일
Docker 포트 충돌 & 컨테이너 연결 에러 완전 정복
ì ì´ ìë¬ê° ì꾸 ëì¤ë
ë커를 ì°ë¤ ë³´ë©´ ìì¤í구 ì´ ë ê°ì§ë¥¼ 겪ëë¤.
- 컨í ì´ë ëì°ë ¤ëë° í¬í¸ê° ì´ë¯¸ ì ì ëë¤ê³ ë§í
- 컨í ì´ëë ì¤í ì¤ì¸ë° ì±ë¼ë¦¬ íµì ì´ ì ë¨
ë ë¤ "ì?"를 ì´í´íë©´ í´ê²°ì 5ë¶ì´ë¤. ëª¨ë¥´ê³ --forceë ì¬ììë§ ë°ë³µíë©´ í루 ë 린ë¤.
1. í¬í¸ ì¶©ë ìë¬
ìë¬ ë©ìì§ í¨í´
Error response from daemon: driver failed programming external connectivity on endpoint myapp
(xxx): Bind for 0.0.0.0:8080 failed: port is already allocated
Error starting userland proxy: listen tcp4 0.0.0.0:3306: bind: address already in use
ìì¸ 3ê°ì§
| ìì¸ | ë¹ë | ì¤ëª |
|---|---|---|
| ì´ì 컨í ì´ëê° ì£½ì§ ìê³ í¬í¸ ì ì ì¤ | â â â | docker stop ì íê³ ê·¸ë¥ í°ë¯¸ë ë«ì ê²½ì° |
| í¸ì¤í¸ íë¡ì¸ì¤(MySQL, Nginx ë±)ê° ê°ì í¬í¸ ì¬ì© | â â â | ë¡ì»¬ì MySQL ê¹ë ¤ìëë° 3306 ì°ë ¤ í ë |
| ì´ì 컨í ì´ëê° exited ìíë¡ í¬í¸ íë© | â ââ | docker psì ì ë³´ì´ì§ë§ docker ps -aì ë³´ì |
[ì´ë³´] ë¨ê³ë³ í´ê²°ë²
1ë¨ê³: ì´ë¤ íë¡ì¸ì¤ê° í¬í¸ ì°ëì§ íì¸
# macOS / Linux
lsof -i :8080
# Windows (PowerShell)
netstat -ano | findstr :8080
2ë¨ê³: ë커 컨í ì´ë íì¸
# ì¤í ì¤ì¸ 컨í
ì´ëë§
docker ps
# ì¤ë¨ë ê² í¬í¨ ì ì²´
docker ps -a
# í¹ì í¬í¸ ì°ë 컨í
ì´ë 찾기
docker ps --filter "publish=8080"
3ë¨ê³: ì ì ì¤ì¸ 컨í ì´ë ì 리
# í¹ì 컨í
ì´ë ì¤ì§
docker stop <container_id>
# ì¤ì§ + ìì
docker rm -f <container_id>
# exited ìí 컨í
ì´ë ì¼ê´ ì 리
docker container prune
[ì¤ê¸] í¬í¸ 매í ì ëµ
ê°ì í¬í¸ë¥¼ ì¨ì¼ íë ìë¹ì¤ê° ì¬ë¬ ê°ë¼ë©´, í¸ì¤í¸ í¬í¸ë¥¼ ë¤ë¥´ê² 매ííë¤.
# í¸ì¤í¸ 8081 â 컨í
ì´ë ë´ë¶ 8080
docker run -p 8081:8080 myapp
# ì¬ë¬ í¬í¸ ëì 매í
docker run -p 8080:8080 -p 443:443 myapp
docker-compose.ymlììë:
services:
app:
image: myapp
ports:
- "8081:8080" # í¸ì¤í¸:컨í
ì´ë
db:
image: mysql:8
ports:
- "3307:3306" # ë¡ì»¬ MySQLê³¼ ì¶©ë ë°©ì§
[ê³ ê¸] ëì í¬í¸ í ë¹
í ì¤í¸ íê²½ìì í¬í¸ ì¶©ëì ìì² ì°¨ë¨íë ë°©ë² â í¸ì¤í¸ í¬í¸ë¥¼ ëì»¤ê° ììì ë¹ì´ìë ê±° ì¡ê² íë¤.
# í¸ì¤í¸ í¬í¸ 미ì§ì â ìì í ë¹
docker run -p 0:8080 myapp
# í ë¹ë í¬í¸ íì¸
docker port <container_id> 8080
# ì¶ë ¥ ì: 0.0.0.0:49152
Node.jsìì íê²½ë³ìë¡ í¬í¸ ë°ì ì°ë í¨í´:
// app.js
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
2. 컨í ì´ë ê° íµì ì ëë ìë¬
ìë¬ ë©ìì§ í¨í´
Error: connect ECONNREFUSED 127.0.0.1:3306
getaddrinfo ENOTFOUND db
컨í
ì´ë Aìì 컨í
ì´ë Bì localhostë¡ ì ìíë ¤ í´ì ì기ë 문ì ë¤. 컨í
ì´ëë¼ë¦¬ localhostë ê³µì íì§ ìëë¤. ê°ì ë
립ë ë¤í¸ìí¬ ë¤ìì¤íì´ì¤ë¥¼ ê°ì§ë¤.
ìì¸ê³¼ í´ê²°ë² ì²´í¬ë¦¬ì¤í¸
| ìí© | ì못ë ì ê·¼ | ì¬ë°ë¥¸ ì ê·¼ |
|---|---|---|
| 컨í ì´ë A â B ì ì | localhost:3306 | 컨í ì´ëëª ëë ìë¹ì¤ëª |
| docker run ë¨ë ì¤í | ë¤í¸ìí¬ ë¯¸ì§ì | --network íëê·¸ë¡ ê°ì ë¤í¸ìí¬ ì¬ì© |
| docker-compose ì¬ì© | ë³ë network ì ì | ê°ì compose íì¼ ë´ë©´ ìë ì°ê²° |
[ì´ë³´] docker-composeë¡ ì»¨í ì´ë ì°ê²°
docker-compose를 ì°ë©´ ê°ì íì¼ ìì ìë¹ì¤ë¼ë¦¬ë ìë¹ì¤ëª ì¼ë¡ ë°ë¡ íµì ëë¤. ë¤í¸ìí¬ ì¤ì ë°ë¡ ì í´ë ëë¤.
# docker-compose.yml
services:
app:
image: node:20
environment:
DB_HOST: db # "localhost" ìëê³ ìë¹ì¤ëª
"db"
DB_PORT: 3306
depends_on:
- db
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: mydb
Node.jsìì DB ì°ê²°:
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: process.env.DB_HOST || 'db', // ìë¹ì¤ëª
port: process.env.DB_PORT || 3306,
user: 'root',
password: 'secret',
database: 'mydb'
});
Python (SQLAlchemy):
import os
from sqlalchemy import create_engine
DATABASE_URL = (
f"mysql+pymysql://root:secret@"
f"{os.getenv('DB_HOST', 'db')}:"
f"{os.getenv('DB_PORT', '3306')}/mydb"
)
engine = create_engine(DATABASE_URL)
[ì¤ê¸] docker runì¼ë¡ ìë ë¤í¸ìí¬ ì°ê²°
docker-compose ìì´ ì»¨í ì´ë ì¬ë¬ ê° ì°ê²°í ë:
# 1. ê³µì© ë¤í¸ìí¬ ìì±
docker network create mynet
# 2. DB 컨í
ì´ë를 í´ë¹ ë¤í¸ìí¬ì ë¶ì¬ ì¤í
docker run -d \
--name mydb \
--network mynet \
-e MYSQL_ROOT_PASSWORD=secret \
mysql:8
# 3. ì± ì»¨í
ì´ëë ê°ì ë¤í¸ìí¬ë¡ ì¤í
docker run -d \
--name myapp \
--network mynet \
-e DB_HOST=mydb \
-p 8080:8080 \
myapp:latest
Java (Spring Boot) application.yml:
spring:
datasource:
url: jdbc:mysql://${DB_HOST:mydb}:${DB_PORT:3306}/mydb
username: root
password: secret
[ê³ ê¸] ë¤í¸ìí¬ ëë²ê¹
# 컨í
ì´ëê° ì´ë¤ ë¤í¸ìí¬ì ë¶ì´ìë
docker inspect myapp | grep -A 20 "Networks"
# í¹ì ë¤í¸ìí¬ì ì°ê²°ë 컨í
ì´ë 목ë¡
docker network inspect mynet
# 컨í
ì´ë ììì ì§ì í í
ì¤í¸
docker exec -it myapp ping mydb
# 컨í
ì´ë ììì í¬í¸ ì´ë ¤ìë íì¸
docker exec -it myapp nc -zv mydb 3306
# ìì debug 컨í
ì´ëë¡ ë¤í¸ìí¬ ì§ë¨
docker run --rm --network mynet nicolaka/netshoot nmap -p 3306 mydb
Nginx ì¤ì ìì upstreamì 컨í ì´ëëª ì¼ë¡:
# nginx.conf
upstream backend {
server app:8080; # 컨í
ì´ë/ìë¹ì¤ëª
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
í¸ë¬ë¸ìí ì²´í¬ë¦¬ì¤í¸
í¬í¸ ì¶©ë ë°ì ì
| ì²´í¬ í목 | ëª ë ¹ì´ |
|---|---|
| ì¤í ì¤ì¸ 컨í ì´ë íì¸ | docker ps |
| ì¤ë¨ í¬í¨ ì ì²´ íì¸ | docker ps -a |
| í¸ì¤í¸ í¬í¸ ì ì íì¸ | lsof -i :PORT (mac/linux) |
| 문ì 컨í ì´ë ê°ì ìì | docker rm -f CONTAINER_ID |
| ë¶íìí 컨í ì´ë ì¼ê´ ì 리 | docker container prune |
| í¬í¸ ë¤ë¥´ê² ì¬ë§¤í | docker run -p HOST:CONTAINER |
컨í ì´ë ì°ê²° ì ë ë
| ì²´í¬ í목 | ëª ë ¹ì´ |
|---|---|
| ê°ì ë¤í¸ìí¬ì¸ì§ íì¸ | docker network inspect NETWORK |
| 컨í ì´ëëª /ìë¹ì¤ëª ì¼ë¡ ì ìíëì§ íì¸ | localhost â ìë¹ì¤ëª
|
| 컨í ì´ë ë´ë¶ìì í í ì¤í¸ | docker exec -it APP ping DB |
| í¬í¸ ì´ë ¤ìë íì¸ | docker exec -it APP nc -zv DB PORT |
| 컨í ì´ë ë¡ê·¸ íì¸ | docker logs CONTAINER_ID |
| ë¤í¸ìí¬ ì¬ìì± í ì¬ì°ê²° | docker network create + --network |
ì주 íë ì¤ì ìì½
1. docker stop ëì í°ë¯¸ëë§ ë«ëë¤
â 컨í
ì´ëë ì´ìì í¬í¸ ê³ì ì ì . docker stop ëë docker-compose down ìµê´í.
2. 컨í
ì´ë ììì localhostë¡ ë¤ë¥¸ 컨í
ì´ë ì ê·¼
â ê° ì»¨í
ì´ëë ë
립 ë¤í¸ìí¬. ìë¹ì¤ëª
ì´ë 컨í
ì´ëëª
ì¼ë¡ ì ê·¼.
3. exited 컨í
ì´ëê° í¬í¸ ì¡ê³ ìë ì¤ ëª¨ë¦
â docker ps -aë¡ exited í¬í¨í´ì íì íì¸.
4. depends_onë§ ë¯¿ê³ DB ì¤ë¹ëë¤ê³ ì°©ê°
â depends_onì 컨í
ì´ë ìì ììë§ ë³´ì¥. DBê° ì¤ì ë¡ ready ìíì¸ì§ë ë³ê°. healthcheckë wait ë¡ì§ íì.
# healthcheckë¡ DB ì¤ë¹ íì¸
services:
db:
image: mysql:8
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 10s
retries: 5
app:
depends_on:
db:
condition: service_healthy # DB healthy íì¸ í ìì
ë커 ê´ë ¨ ìë¬ë ëë¶ë¶ ì´ ë ê°ì§ìì ì¨ë¤. í¬í¸ ì¶©ëì´ë©´ docker ps -aë¶í°, ì°ê²° ì ëë©´ ë¤í¸ìí¬ì í¸ì¤í¸ëª
ë¶í° íì¸íë©´ 길ì ìì§ ìëë¤.