🚀 서론[주제 소개]: 서버 운영자에게 가장 두려운 것은 "내가 잠든 사이 서버가 죽는 것"입니다. 특히 마이크로서비스 환경(Docker)에서는 DB가 재시작되거나 네트워크가 깜빡일 때, 애플리케이션이 연결을 잃고 '좀비' 상태가 되는 일이 빈번합니다. 오늘은 Docker Compose 환경에서 컨테이너 상태를 스스로 진단하고 복구하는 '자가 치유(Self-Healing) 시스템' 구축법을 다룹니다.
[왜 작성하였는가?]: 단순히 도커를 띄우는 것을 넘어, "순서 상관없이, 장애가 발생해도 알아서 복구되는" 견고한 시스템을 만드는 실전 비법을 공유합니다. 이 글을 읽으면 더 이상 수동으로 restart를 누르지 않아도 됩니다.
🧠 [Docker 자가 치유]: 핵심 개념 파헤치기[Healthcheck (헬스체크)]: 컨테이너가 단순히 '켜져 있는지'가 아니라 '실제로 일을 할 수 있는지'를 주기적으로 검사하는 기능입니다.
왜 중요한가요?: 프로세스는 살아있지만 DB 연결이 끊겨 에러만 뱉는 '뇌사 상태'를 감지할 수 있는 유일한 방법입니다.
[Autoheal (오토힐)]: 헬스체크가 실패(Unhealthy)했다고 판단되면, 해당 컨테이너를 강제로 재시작(Restart) 시켜주는 '구급대원' 역할을 하는 보조 컨테이너입니다.
놓치기 쉬운 점: 도커 자체 기능(restart: always)은 프로세스가 죽어야만 작동합니다. 프로세스가 살아있는 '좀비 상태'에서는 오토힐이 필수입니다.
[App Retry Logic (재시도 로직)]: 애플리케이션 코드 내부에서 DB나 외부 서비스 연결이 실패했을 때, 포기하지 않고 연결될 때까지 기다리는 코드 패턴입니다.
실무 적용 시 고려사항: 인프라(Docker)가 재시작을 시켜줘도, 앱이 켜지는 찰나에 DB가 준비되지 않았다면 또 죽습니다. 이 로직은 그 '찰나'를 버티게 해주는 최후의 보루입니다.
📚 [Docker Compose]: 공식 가이드라인 & 권장 사항[공식 소스]: Docker Compose Specification - Healthcheck
[주요 권장 사항]:
테스트 명령의 구체화: 단순히 포트만 확인(ping)하지 말고, 실제 애플리케이션의 엔드포인트(curl /health)를 찔러봐야 합니다.
적절한 타임아웃 설정: 검사가 너무 오래 걸리면 시스템 전체가 느려지거나 오진단이 발생할 수 있습니다.
Start Period 활용: 애플리케이션이 처음 부팅되는 시간(예: 30초) 동안은 검사 실패를 카운트하지 않도록 유예 시간을 줘야 합니다.
안전 제일 원칙: 헬스체크 명령어 자체가 시스템 리소스를 많이 잡아먹지 않도록 가볍게 설계하세요.🛠️ [무중단 시스템]: 실무 적용 마스터 플랜[단계별 적용 시나리오]: 도커 설정부터 애플리케이션 코드 수정까지, 완벽한 자가 치유 환경을 구축합니다.[1단계: Docker Compose에 Healthcheck 심기]무엇을 하는가?: 내 앱이 건강한지 30초마다 스스로 진단하도록 설정합니다.
어떻게 하는가?: docker-compose.yml 파일에 healthcheck 섹션을 추가합니다.# docker-compose.yml
services:
my-app:
image: my-app:latest
# ... 기타 설정 ...
healthcheck:
# [핵심] 단순 포트 체크가 아닌, 웹 서버 응답(200 OK)을 확인
test: ["CMD-SHELL", "curl -f http://localhost:8000/docs || exit 1"]
interval: 30s # 30초마다 검사
timeout: 10s # 10초 내 응답 없으면 실패
retries: 3 # 3번 연속 실패 시 Unhealthy 판정
start_period: 40s # 부팅 시간 40초는 봐줌
labels:
- "autoheal=true" # 오토힐에게 "나를 감시해줘"라고 알림
팁: 이미지에 curl이 없다면 파이썬 스크립트(python -c ...)를 사용할 수 있습니다.
성공 점검: docker ps 입력 시 상태창에 (healthy) 또는 (starting) 문구가 표시되어야 합니다.[2단계: Autoheal(구급대원) 컨테이너 배치]무엇을 하는가?: Unhealthy 상태인 컨테이너를 발견하면 자동으로 재시작해주는 관리자를 둡니다.
어떻게 하는가?: docker-compose.yml에 autoheal 서비스를 추가합니다.# docker-compose.yml (services 섹션 하단에 추가)
autoheal:
image: willfarrell/autoheal
container_name: autoheal
restart: always
environment:
- AUTOHEAL_CONTAINER_LABEL=all # 라벨이 붙은 모든 컨테이너 감시
volumes:
- /var/run/docker.sock:/var/run/docker.sock # 도커 데몬 제어권 부여
실수 방지 팁: volumes 설정을 빼먹으면 오토힐이 도커 명령을 내릴 수 없어 작동하지 않습니다.[3단계: Python 코드에 '근성(Retry)' 심기]무엇을 하는가?: 앱이 켜질 때 DB가 없어도 죽지 않고 기다리도록 코드를 수정합니다. (가장 중요!)
어떻게 하는가?: FastAPI의 startup 이벤트에 무한 루프를 추가합니다.# Before: 연결 안 되면 에러 뱉고 앱이 켜져버림 (좀비화)
# app.redis = await aioredis.from_url(...)
# After: 연결될 때까지 무한 대기
import asyncio
from redis.exceptions import ConnectionError
@app.on_event("startup")
async def startup_event():
app.redis = aioredis.from_url(...)
while True:
try:
await app.redis.ping()
print("✅ Redis 연결 성공!")
break
except ConnectionError:
print("⏳ Redis 연결 실패... 3초 뒤 재시도")
await asyncio.sleep(3)
변화 요약: 기존에는 DB 연결 실패 시 불완전한 상태로 앱이 켜졌지만, 이제는 완전한 연결이 보장될 때까지 부팅을 완료하지 않습니다.📊 [자가 치유]: 생생한 성공 & 실패 사례 분석[성공 사례: 새벽 3시의 네트워크 단절]
배경: 클라우드 호스팅 업체의 점검으로 Redis 서버의 네트워크가 1분간 차단됨.
적용 전략: Autoheal + Healthcheck 조합 사용.
핵심 결과: 네트워크가 끊기자 앱 헬스체크 실패 -> 오토힐이 앱 재시작 -> 앱은 Retry 로직으로 Redis가 돌아올 때까지 대기 -> 네트워크 복구 즉시 자동 연결. 운영자 개입 0회.
성공 요인 분석: 인프라(Docker)와 코드(Python)가 서로의 약점을 완벽하게 보완함.
[실패 사례: "포트는 열렸는데..."]
배경: Redis 연결이 끊겼으나, 헬스체크를 단순히 "앱 포트(8000)가 열렸는가?"로만 설정함.
실패 요인: 앱은 Redis 연결 에러를 내부적으로 뱉고 있었지만, 웹 서버 포트 자체는 열려 있었음. 헬스체크는 계속 Healthy로 판정했고, 오토힐은 작동하지 않음.
얻은 교훈: 헬스체크는 반드시 '기능적 검사'여야 한다. 단순 포트 확인(nc -z)은 '뇌사 상태'인 앱을 걸러내지 못한다.
❓ 자주 묻는 질문(FAQ)Q1. Autoheal 컨테이너가 시스템 리소스를 많이 먹나요?
A1. 거의 먹지 않습니다. 매우 가벼운 이미지이며, 주기적으로 도커 상태만 확인하므로 무시해도 될 수준입니다.
Q2. Healthcheck 간격(interval)은 몇 초가 적당한가요?
A2. 서비스 중요도에 따라 다르지만, 보통 30초~60초가 적당합니다. 너무 짧으면(5초) 시스템 부하가 생길 수 있습니다.
Q3. curl 명령어가 없다는 에러가 나요.
A3. 사용하는 도커 이미지(Python-slim 등)에 curl이 설치되지 않아서 그렇습니다. python -c 코드를 사용하거나 Dockerfile에서 RUN apt-get install curl을 해야 합니다.
Q4. 앱 코드에 Retry 로직만 있으면 되지 않나요?
A4. Retry는 '부팅 시점'이나 '일시적 오류'는 잡지만, 앱이 메모리 누수로 멈추거나 완전히 뻗었을 때는 재시작을 못 시킵니다. 오토힐이 꼭 필요합니다.
Q5. depends_on 옵션과는 무엇이 다른가요?
A5. depends_on은 컨테이너 실행 순서만 정해줄 뿐, 실행 후 상태나 재시작은 관리하지 않습니다.
Q6. 로컬 개발 환경에서도 필요한가요?
A6. 필수는 아니지만, PC를 껐다 켜거나 도커를 재시작할 때 신경 쓸 필요가 없어져서 개발 효율이 매우 올라갑니다.
💡 [운영 노하우]: 실전 운영 팁 & 주의사항[팁 1: 헬스체크 전용 엔드포인트 만들기]: /health 같은 API를 따로 만들어서, DB 연결 상태와 메모리 상태 등을 체크하고 200 또는 500을 리턴하게 짜는 것이 가장 확실합니다.
[팁 2: Start Period의 중요성]: 앱이 무거워서 켜지는 데 1분이 걸리는데 start_period를 안 주면, 켜지기도 전에 오토힐이 계속 재시작을 시키는 무한 재부팅 루프에 빠질 수 있습니다.
[팁 3: 알림 연동]: 오토힐이 재시작을 시킬 때 슬랙(Slack)이나 이메일로 알림을 보내도록 설정하면, 시스템 불안정 징후를 미리 파악할 수 있습니다.
[팁 4: 좀비 프로세스 주의]: 앱 코드에서 에러 처리를 잘못해서 try-except로 모든 에러를 삼키고 넘어가면, 헬스체크도 속을 수 있습니다. 치명적 에러 시에는 확실히 앱이 죽거나 에러 응답을 보내야 합니다.🏁 결론 및 다음 단계[핵심 요약]: 완벽한 무중단 시스템은 "똑똑한 헬스체크(Docker)", "성실한 구급대원(Autoheal)", 그리고 "끈기 있는 애플리케이션(Retry Code)" 이 세 가지 박자가 맞아야 완성됩니다. 이 구성을 갖추면 순서 오류나 일시적 장애로부터 시스템이 100% 자동으로 복구됩니다.
[다음 단계 제안]: 지금 당장 운영 중인 docker-compose.yml을 열어 healthcheck가 없는 서비스가 있는지 확인하세요. 그리고 여러분의 앱 코드 startup 부분에 while True 재시도 로직이 있는지 점검해 보세요. 오늘 저녁부터는 두 발 뻗고 주무실 수 있습니다!
뭐더라..
메인컴보다 보조컴 서버가 빨리켜지면 보조컴의 서버 도커 재시작을 하기전까지 레디스 연결을 못함.
- 도커 헬스체크,오토힐로 조치.(코드바꾸기 귀찮아서) 메인컴 레디스 찔러봄 -> 레디스 살아있네 -> 재시작 안함 -> ㅅㅂ?
- 서버 코드 바꿈 연결 트라이익셉션 -> 와일 트라이익셉으로.
- 레디스 죽임 -> 설정간격마다 살아있는거 확인할때까지 재시작
- 작성하다 뭔가 이상해서 보강함 걍 fast api에서 health 체크가능한 경로 만들고 레디스, fastapi 둘다 살아있는지 체크하는걸로 변경
걍 다음 읽으셈
1. 왜 따로 하면 안 되나요? (반쪽짜리 검사)앱만 체크 (curl localhost:8000):
앱은 켜져 있는데, 레디스 선이 잘려서 아무런 데이터도 못 가져오는 상황입니다.
도커는 **"어? 앱 켜져 있네? 건강해!"**라고 착각합니다.
결과: 사용자는 500 에러만 계속 보는데, 도커는 아무 조치도 안 합니다.
레디스만 체크 (socket connect 6379):
레디스는 멀쩡한데, 앱이 메모리 누수로 멈췄습니다.
도커는 **"어? 레디스 켜져 있네? 건강해!"**라고 착각합니다.
결과: 사이트 접속이 안 되는데, 도커는 재시작을 안 해줍니다.2. 둘 다 체크하면? (완벽한 검사)앱 내부의 /health API를 통해 **"나(앱)도 멀쩡하고, 레디스랑 통신도 잘 돼!"**라는 걸 확인해야 진짜 건강한 겁니다.
[시나리오]
도커: "야 앱아, 너 건강하냐? (/health 호출)"
앱: "잠만, 나 숨 쉬는지 보고... OK. 레디스한테 핑 날려볼게... OK."
앱: "응! 나도 괜찮고 레디스도 연결돼! (200 OK)"
도커: "오케이, 합격."[하나라도 안 되면?]
앱: "으악! 나 레디스랑 연결 끊겼어! (503 Error)"
도커: "너 아프구나? 재시작!"
오토힐: (앱을 껐다 켬) -> (앱이 while 루프로 레디스 기다림) -> 복구 완료.