본문 바로가기
Docker

Docker 10회차 : 운영 관점 기초 (보안/환경변수/헬스체크/리소스/정리)

by 마틴블레이크 2026. 1. 1.
반응형

Docker 10회차 : 운영 관점 기초 (보안/환경변수/헬스체크/리소스/정리)

개발 환경에서 “잘 돌아가요”를 넘어, 운영 관점에서 “안전하게, 오래, 문제를 빨리 찾고, 깔끔하게 정리되는” Docker 사용법을 정리합니다. 초보자분도 따라 할 수 있도록 환경변수/시크릿, 헬스체크/재시작, 로그/리소스 모니터링, 이미지·컨테이너 정리를 실습 중심으로 구성하고, 마지막에는 바로 써먹는 로컬 운영 런북 v1을 산출물로 제공합니다.

목차

  1. 핵심 개념 한눈에 보기
  2. 환경변수와 시크릿: 최소한의 원칙
  3. 헬스체크와 재시작 정책
  4. 로그/리소스(메모리/CPU) 모니터링 기초
  5. 이미지/컨테이너 정리 전략
  6. 실습: 운영 감각 만들기
  7. 산출물: 로컬 운영 런북 v1
  8. 추가로 생각해볼 점
  9. 블로그 최적화 정보

핵심 포인트

  • 환경변수는 “설정”, 시크릿은 “비밀”입니다. 같은 방식으로 다루면 유출 위험이 커집니다.
  • 헬스체크는 “상태 확인”이고, 재시작 정책은 “죽었을 때 복구”입니다. 둘은 목적이 다릅니다.
  • 운영에서 중요한 것은 “문제 발생” 자체보다 얼마나 빨리 감지하고, 얼마나 빨리 원인을 좁히는지입니다.
  • 정리는 “디스크 확보”를 넘어 재현 가능한 환경 유지에 도움이 됩니다. 대신, 정리 명령은 파괴적이므로 순서를 정해야 합니다.

환경변수와 시크릿(최소한의 원칙)

운영에서 환경변수와 시크릿을 구분하는 이유는 단순합니다. 환경변수는 노출되어도 큰 문제가 없는 “설정값”을 담고, 시크릿은 노출되면 즉시 사고로 이어지는 “비밀값”을 담기 때문입니다. 초보자 시점에서는 “둘 다 값인데 왜 구분해요?”가 자연스럽지만, 운영에서는 이 구분이 습관이 되어야 합니다.

최소한의 원칙 5가지

  • 비밀은 코드에 쓰지 않습니다. 저장소에 한 번 올라가면 완전히 지우기 어렵습니다.
  • 비밀은 로그에 찍지 않습니다. 디버그 로그에 토큰/패스워드가 노출되는 일이 자주 발생합니다.
  • 비밀은 환경변수로도 조심합니다. 환경변수는 조회/덤프/스크린샷으로 노출되기 쉽습니다.
  • 권한은 최소로. “일단 전체 권한”은 나중에 반드시 빚이 됩니다.
  • .env는 편하지만 ‘비밀 보관소’가 아닙니다. 로컬/학습용으로 쓰되, 운영에서는 별도 시크릿 관리가 바람직합니다.

환경변수/시크릿/파일 마운트 비교

방식 적합한 값 장점 주의점
-e, env_file 포트, 모드, 기능 플래그 등 설정값 간단하고 어디서나 사용 가능 로그/덤프/공유 시 노출 위험, 값 범위 검증 필요
시크릿(권장: 오케스트레이션/외부 Vault) DB 비밀번호, API 토큰, 키 파일 접근 제어·회전·감사에 유리 환경 구성 필요(팀/인프라 정책과 함께 설계)
파일 마운트(-v) 인증서, 설정 파일, 키 파일(권한 제어 필요) 파일 단위로 관리, 앱이 파일을 기대할 때 편리 호스트 파일 권한/백업/유출 경로 관리가 중요

초보자에게 현실적인 운영 습관: “설정은 env, 비밀은 파일/시크릿”

  • 학습/로컬에서는 .env를 쓰되 반드시 .gitignore에 포함합니다.
  • 운영에서는 가능하면 시크릿 전용 저장소(예: Vault/클라우드 시크릿)를 쓰고, 컨테이너에는 “필요한 순간에만” 주입합니다.
  • 팀/프로젝트 규모가 작아도, 최소한 비밀은 별도 파일로 분리하고 권한을 제한하는 습관이 도움이 됩니다.

예시: 환경변수 주입(로컬)

# 1) 단일 실행에서 간단히 주입
docker run --rm -p 8080:80 \
  -e APP_MODE=prod \
  -e FEATURE_X=true \
  --name web nginx:alpine

# 2) env 파일로 주입(.env 같은 파일)

docker run --rm -p 8080:80 
--env-file ./.env 
--name web nginx:alpine

운영 관점에서는 값의 출처가 중요합니다. “어디서 누가 값을 바꿨는지”를 추적할 수 있어야 장애 대응이 쉬워집니다. 최소한 .env는 파일로 관리하고, 변경 이력을 남기는 방식을 권장합니다(단, 비밀값은 제외).


헬스체크와 재시작 정책

운영 안정성을 만들 때 가장 흔한 오해가 “재시작 정책만 걸면 안정적이다”입니다. 재시작 정책은 프로세스가 종료되었을 때 다시 올려주는 장치입니다. 반면 헬스체크는 프로세스는 살아있지만 서비스는 망가진 상태를 감지하는 장치입니다. 둘을 함께 써야 “죽으면 살아나고, 살아있어도 이상하면 표시되는” 구조가 됩니다.

헬스체크(Healthcheck) 기본

  • 무엇을 확인할지: 포트 오픈(최소), HTTP 200(권장), 내부 의존성까지 확인(상황에 따라)
  • 얼마나 자주: 너무 자주 하면 부하, 너무 드물면 감지 지연
  • 실패 기준: 일시 장애를 흡수할 “retries”가 필요
  • 타임아웃: 응답이 늦는 상황을 고려하여 timeout을 설정

재시작 정책(Restart Policy) 기본

정책 의미 추천 용도 주의점
no(기본) 죽어도 재시작하지 않음 테스트/단발성 작업 운영 서비스에는 보통 부적합
on-failure[:N] 비정상 종료 시에만 재시작 실패 시 자동 복구가 필요한 배치/서비스 정상 종료(0)에는 재시작하지 않음
always 항상 재시작(정상 종료 포함) 항상 떠 있어야 하는 서비스 의도적 중지 후에도 다시 켜질 수 있음
unless-stopped 수동으로 멈추기 전까지 재시작 로컬/개발용 운영에 가장 무난 중지/재기동 의도를 팀과 맞춰야 함

Compose로 함께 설정하는 예시

# docker-compose.yml (학습용 예시)
version: "3.8"
services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    restart: unless-stopped
    environment:
      - APP_MODE=prod
    healthcheck:
      # 일부러 잘못된 경로로 시작해 'unhealthy'를 관찰해봅니다(실습에서 수정).
      test: ["CMD-SHELL", "wget -q -O - http://localhost/health || exit 1"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 10s

이 설정에서 중요한 관찰 포인트는 2가지입니다. (1) 컨테이너가 실행 중이어도 헬스체크가 실패하면 상태가 unhealthy로 바뀝니다. (2) restart는 “프로세스가 종료”되어야 작동합니다. 즉, 헬스체크 실패만으로는 컨테이너가 자동으로 재시작되지 않을 수 있습니다(로컬 환경에서는 특히). 운영에서는 오케스트레이션 정책(예: Kubernetes 등)과 결합해 “unhealthy → 재배치/재기동”을 설계합니다.


로그/리소스(메모리/CPU) 모니터링 기초

운영에서 가장 중요한 능력은 “현재 상태를 눈으로 확인”하는 것입니다. 로컬에서도 최소한 아래 정도는 습관처럼 할 수 있어야 장애 대응이 빨라집니다.

로그 확인: 먼저 범위를 줄이고, 그 다음 디테일

  • 최근 로그만: --tail로 범위를 줄입니다.
  • 실시간 추적: -f로 따라가며 증상이 재현되는지 봅니다.
  • 컨테이너 단위: “어느 컨테이너에서 문제가 생겼는지”를 먼저 확정합니다.
# 컨테이너 목록/상태
docker ps

# 최근 200줄만 확인
docker logs --tail 200 web

# 실시간으로 따라가기
docker logs -f web

리소스 확인: docker stats로 “누가 많이 먹는지”부터

초보자일수록 CPU/메모리 문제를 “앱이 느려요”로만 느끼기 쉽습니다. 하지만 운영에서는 “어떤 컨테이너가, 어떤 자원을, 얼마나, 얼마나 오래” 쓰는지부터 확인합니다.

# 실시간 리소스 사용량(종료: Ctrl + C)
docker stats

# 특정 컨테이너만
docker stats web

로그 폭주 방지(기초): 로그 로테이션 개념만이라도 잡기

로컬에서도 로그가 쌓이면 디스크를 빠르게 잠식할 수 있습니다. 기본 드라이버(json-file)를 쓴다면, “무한히 쌓이지 않게” 최소한의 로테이션 설정을 고려할 수 있습니다(환경에 따라 적용 방식이 다를 수 있습니다).

# 예시) Docker 데몬 설정(daemon.json) - 환경에 따라 위치/적용 방식이 다릅니다.
# 핵심은 "max-size, max-file"로 로그 파일이 무한히 커지지 않게 하는 것입니다.
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

이미지/컨테이너 정리 전략

Docker를 쓰다 보면 디스크가 갑자기 부족해지는 경우가 많습니다. 원인은 보통 아래 중 하나입니다.

  • 빌드하면서 쌓인 미사용 이미지(특히 태그 없는 dangling 이미지)
  • 종료됐지만 남아 있는 컨테이너
  • 데이터가 쌓인 볼륨(DB 실습을 반복할수록 누적)
  • 빌드 캐시/레이어

정리 전 “현황 확인”이 먼저입니다

# 디스크 사용 현황 요약(이미지/컨테이너/볼륨/빌드 캐시)
docker system df

# 종료된 컨테이너 포함 전체 목록

docker ps -a

# 볼륨 목록

docker volume ls

정리 명령 요약(자주 쓰는 것만)

명령 무엇을 지우나 언제 쓰나 주의점
docker container prune 중지된 컨테이너 실습 후 찌꺼기 정리 필요한 중지 컨테이너까지 삭제될 수 있음
docker image prune dangling 이미지 빌드/풀 반복 후 보통 안전하지만, 태그 관리가 엉키면 영향 가능
docker image prune -a 미사용 이미지(더 공격적) 디스크 확보가 급할 때 다시 pull/build 해야 해서 시간 증가
docker volume prune 미사용 볼륨 DB 실습 반복 후 데이터 삭제 가능성, 꼭 확인 필요
docker system prune --volumes 전반 정리(아주 공격적) 마지막 수단 지워도 되는지 확신 없으면 사용 금지

실습

실습은 “명령을 입력해봤다”로 끝내지 말고, 관찰 포인트를 확인하는 것이 중요합니다. 아래 실습은 로컬에서 재현 가능한 형태로 구성했습니다.

실습 1) 헬스체크 추가 후 비정상 상황에서 상태 변화 확인 + 재시작 확인

  1. 아래 Compose 파일로 컨테이너를 올립니다.
  2. docker ps에서 STATUS가 시간이 지나며 (healthy) 또는 (unhealthy)로 바뀌는지 관찰합니다.
  3. 그 다음, 헬스체크 경로를 정상 경로로 바꾸고 재배포하여 상태가 회복되는지 확인합니다.
  4. 마지막으로, 컨테이너를 강제로 종료시켜 재시작 정책이 작동하는지 확인합니다.
# 1) (처음) 일부러 실패하는 헬스체크로 실행
# docker-compose.yml의 healthcheck가 /health를 체크하도록 되어 있다면 그대로 사용
docker compose up -d

# 2) 상태 관찰
docker ps
docker inspect --format='{{.State.Health.Status}}' $(docker ps -q --filter "name=web")

# 3) (수정) docker-compose.yml에서 헬스체크 경로를 "/"로 바꿉니다.
# test: ["CMD-SHELL", "wget -q -O - http://localhost/ || exit 1"]
docker compose up -d

# 4) 재시작 정책 확인(강제로 종료)
docker kill web

# 5) 다시 살아나는지 확인
docker ps
docker logs --tail 50 web
  • 관찰 포인트: 헬스체크 실패는 “서비스 이상 신호”를 주지만, 로컬 Docker에서는 그 자체로 자동 재시작을 보장하지 않을 수 있습니다.
  • 관찰 포인트: 재시작 정책은 “프로세스 종료”에 반응하므로, docker kill 후 재기동 여부를 확인하면 동작이 명확해집니다.

실습 2) 리소스 제한을 걸고 동작 확인(가능한 범위)

리소스 제한을 이해하는 가장 쉬운 방법은 “제한을 걸어보고, 제한을 넘으면 어떤 일이 벌어지는지”를 보는 것입니다. 아래 예시는 메모리 제한을 걸어 OOM(Out Of Memory) 상황을 관찰하는 형태입니다.

# 1) 메모리/CPU 제한을 건 상태로 스트레스 컨테이너 실행
# (이미지: polinux/stress) - 환경에 따라 pull 시간이 걸릴 수 있습니다.
docker run --rm --name stress-test \
  --memory=128m --cpus=0.5 \
  polinux/stress \
  --vm 1 --vm-bytes 256M --timeout 20s

# 2) 동시에 리소스 관찰(다른 터미널에서)
docker stats
  • 관찰 포인트: 메모리를 제한했는데 더 크게 쓰려고 하면 프로세스가 강제 종료(OOM Kill)될 수 있습니다. 이때 컨테이너는 “갑자기 죽은 것처럼” 보일 수 있습니다.
  • 관찰 포인트: CPU 제한은 “최대치”를 제한하는 성격이므로, 처리량이 줄어드는 형태로 체감될 수 있습니다.
  • : 데스크톱(Windows/Mac) 환경에서는 커널 메시지 확인 방식이 리눅스와 다를 수 있습니다. 초보자 단계에서는 “컨테이너가 종료되었다/느려졌다”와 “stats에서 제한 근처까지 도달했다” 정도만 확인해도 충분합니다.

실습 3) 미사용 이미지/컨테이너/볼륨 정리 시나리오

  1. 먼저 디스크 현황을 확인합니다.
  2. 중지된 컨테이너를 정리합니다.
  3. dangling 이미지(태그 없는 이미지)를 정리합니다.
  4. 필요한 경우에만 볼륨/전체 정리를 진행합니다.
# 1) 현황 확인
docker system df
docker ps -a
docker image ls
docker volume ls

# 2) 중지 컨테이너 정리
docker container prune

# 3) dangling 이미지 정리(보통 상대적으로 안전)
docker image prune

# 4) 볼륨 정리(데이터 삭제 위험 -> 정말 필요할 때만)
docker volume prune

# 5) 마지막 수단(권장 X): 전체 정리 + 볼륨 포함
# docker system prune --volumes

산출물: “로컬 운영 런북 v1” (시작/중지/장애/백업/정리 절차)

아래 런북은 로컬에서 Docker로 서비스를 돌릴 때, “어떻게 시작하고, 어떻게 멈추고, 문제가 생기면 어디부터 보고, 최소한의 백업과 정리는 어떻게 할지”를 정리한 템플릿입니다. 팀/프로젝트에 맞게 서비스명만 바꿔도 재사용할 수 있도록 작성했습니다.

0) 기본 규칙(항상 지키기)

  • 명령 실행 전 대상 컨테이너/프로젝트가 맞는지 먼저 확인합니다: docker ps, docker compose ps.
  • 비밀값(API 토큰/비밀번호)은 터미널 히스토리/로그에 남기지 않습니다.
  • 정리(prune)는 파괴적입니다. 현황 확인 → 작은 정리 → 큰 정리 순서로 진행합니다.

1) 시작 절차(Start)

  1. 환경변수/설정 파일(.env 등) 준비 여부 확인
  2. 컨테이너 기동
  3. 헬스/로그 확인
# (Compose 기준)
docker compose up -d

# 상태 확인
docker compose ps
docker ps

# 로그 빠르게 확인(최근 100줄)
docker compose logs --tail 100

# 헬스 상태(헬스체크가 설정된 경우)
docker inspect --format='{{.State.Health.Status}}' $(docker ps -q)

2) 중지 절차(Stop)

  1. 서비스 중지(정상 종료)
  2. 필요 시 볼륨/데이터 보존 여부 판단
  3. 중지 후 잔여 컨테이너 확인
# (Compose 기준) 정상 중지
docker compose down

# 중지 후 확인
docker ps -a

3) 장애 대응 절차(Incident)

장애 대응의 핵심은 “원인 추측”이 아니라 범위를 좁히는 질문 순서입니다.

순서 질문 확인 명령 판단
1 컨테이너가 살아있나? docker ps 없으면 기동 실패/종료/재시작 루프 가능
2 왜 죽었나/무슨 에러인가? docker logs --tail 200 <name> 즉시 원인이 드러나는 경우가 많음
3 헬스 상태는 어떤가? docker inspect unhealthy면 의존성/엔드포인트 확인
4 리소스가 부족한가? docker stats OOM/CPU 포화/디스크 부족 의심
5 설정이 바뀌었나? docker inspect, 설정 파일 diff 변경 이력과 함께 원인 좁히기

4) 백업 절차(Backup)

로컬 환경에서도 최소한 “데이터가 어디에 있는지(볼륨/바인드 마운트)”를 알아야 백업을 할 수 있습니다. 운영에서 데이터는 보통 볼륨에 있으므로, 백업은 “볼륨 → 파일” 형태가 됩니다.

# 1) 어떤 볼륨을 쓰는지 확인(컨테이너 상세)
docker inspect <container_name>

# 2) (예시) 볼륨을 tar로 백업하는 패턴
# my_data_volume을 /backup에 tar로 저장
docker run --rm \
  -v my_data_volume:/data:ro \
  -v $(pwd):/backup \
  alpine \
  sh -c "tar -czf /backup/my_data_volume_backup.tar.gz -C /data ."
  • DB는 “파일 백업”이 항상 안전하지 않을 수 있습니다. 가능한 경우 DB가 제공하는 dump 명령(예: mysqldump, pg_dump)을 우선 고려합니다.
  • 백업 파일은 “언제/무엇을/어디서” 백업했는지 이름에 남기면 복구가 쉬워집니다(예: 날짜, 프로젝트명).

5) 정리 절차(Cleanup)

  1. 현황 확인: docker system df
  2. 안전한 정리부터: 중지 컨테이너 정리 → dangling 이미지 정리
  3. 데이터 정리는 마지막: 볼륨 prune은 “데이터 삭제” 가능성을 항상 전제
docker system df
docker container prune
docker image prune

# 데이터가 필요 없는 게 확실할 때만
# docker volume prune

추가로 생각해볼 점

  • 헬스체크의 “정답”은 없습니다. 너무 얕으면 장애를 못 잡고, 너무 깊으면 의존성 장애까지 모두 내 탓처럼 보일 수 있습니다. 팀의 장애 패턴에 맞춰 조정하는 것이 현실적입니다.
  • 리소스 제한은 “안전장치”이자 “성능 상한”입니다. 제한을 걸면 안정성은 올라가지만, 피크 트래픽에서 성능이 더 빨리 떨어질 수 있습니다.
  • 로그는 적을수록 좋은 것이 아니라, ‘쓸모 있는’ 것이 좋습니다. 장애 시 필요한 정보(요청 ID, 에러 코드, 외부 의존성 상태)를 남기는 설계가 중요합니다.
  • 정리 자동화는 단계적으로. 처음부터 system prune --volumes 같은 강한 자동화를 걸기보다, “보고 판단하는 정리”부터 습관을 들인 뒤 자동화를 고민하는 편이 안전합니다.

 

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

반응형