Docker
Docker Compose healthcheck를 믿고 배포했다가 늦게 발견한 문제
컨테이너가 healthy로 보였지만 실제 API는 준비되지 않았던 배포 경험을 healthcheck 기준과 함께 돌아봤다.
초록색 상태가 곧 정상은 아니었다
배포 후 docker compose ps에서 서비스가 healthy로 보여서 안심했다. 그런데 프론트엔드에서 첫 요청이 몇 번 실패했다. 컨테이너 상태만 보고 넘어간 것이 실수였다. healthcheck가 실제 사용자 요청과 다른 아주 얕은 확인만 하고 있었다.
당시 설정은 이런 식이었다.
services:
api:
image: my-api:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 10s
timeout: 3s
retries: 3
/health는 서버 프로세스가 떠 있는지만 확인했고, 데이터베이스 연결이나 마이그레이션 상태는 보지 않았다.
실제로 실패한 지점
API 서버는 시작됐지만 DB 커넥션 풀이 준비되기 전에 healthcheck가 통과했다. 바로 이어서 들어온 요청은 DB 조회에서 실패했다. 로그를 보니 컨테이너 시작 시각과 첫 에러 시각이 거의 붙어 있었다.
docker compose logs --since=10m api
docker compose ps
이때부터 healthy라는 단어를 너무 넓게 해석하지 않기로 했다.
healthcheck를 바꾼 기준
나는 상태 확인을 두 단계로 나눴다.
live: 프로세스가 살아 있는지 확인ready: 요청을 받아도 되는지 확인
Compose의 healthcheck에는 ready에 가까운 엔드포인트를 넣었다.
app.get('/ready', async (req, res) => {
try {
await db.query('select 1')
res.status(200).json({ ok: true })
} catch {
res.status(503).json({ ok: false })
}
})
그리고 설정도 /ready를 보도록 바꿨다.
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/ready"]
interval: 10s
timeout: 3s
retries: 5
start_period: 20s
start_period를 둔 이유는 앱이 뜨는 동안의 실패를 곧바로 unhealthy로 세지 않기 위해서다.
depends_on에 대한 착각
depends_on도 한 번 헷갈렸다. 서비스 시작 순서를 어느 정도 맞춰 줄 뿐, 애플리케이션이 완전히 준비됐다는 뜻은 아니다. 조건을 쓰더라도 각 서비스가 자기 준비 상태를 정확히 알려 줘야 한다.
depends_on:
db:
condition: service_healthy
이 설정만으로 API 내부 마이그레이션 완료까지 보장되는 것은 아니었다.
회고
이후 배포 체크는 docker compose ps에서 끝내지 않는다. 새 버전을 올린 뒤 실제 API 한두 개를 호출하고, 로그에 초기 실패가 없는지 확인한다. healthcheck는 좋은 안전장치지만, 무엇을 건강하다고 볼지 정하지 않으면 초록색 표시가 오히려 방심하게 만들 수 있다.