Fly.io에서 무중단 배포(Seamless Deployments)
Fly.io에서 무중단 배포는 마법이 아니라, 제대로 작동하는 헬스 체크 덕분이에요. Fly.io 앱은 개별 VM에서 실행되며, 새 VM이 살아 있음을 증명하기 전에는 트래픽을 라우팅하지 않아요. 이 문서에서는 헬스 체크를 제대로 설정해 앱을 망가뜨리거나 운에 기대지 않고 무중단 배포를 하는 방법을 설명해 드릴게요.
출처: 문서
본문
개요
**무중단 배포는 마법이 아니에요. 그저 제대로 동작하는 헬스 체크일 뿐이에요. Fly.io 앱은 개별 VM에서 실행되며, 새 VM이 살아 있음을 입증하기 전까지는 트래픽 라우팅을 시작하지 않아요. 여기서 망가뜨리지 않으면서, 혹은 행운에 의존하지 않으면서 무중단 배포를 얻는 방법을 알려드릴게요.**배포는 헬스 체크의 품질만큼만 좋아요
헬스 체크가 작동하면 Fly.io 배포는 마법처럼 보여요. 새 Machine이 부팅되고, 옛 것들은 내려가며, 사용자는 아무것도 알아차리지 못해요. 하지만 백그라운드에서는 Fly Proxy가 Machine들을 지켜보며, 헬스 체크를 통과한 Machine에만 트래픽을 보내요.
헬스 체크는 플랫폼에 앱이 살아 있음을 알리는 방법이에요. 헬스 체크가 없거나 망가져 있으면, 어둠 속을 비행하는 셈이에요.
fly.toml에서 보통 http_service.checks 아래에 헬스 체크를 정의해요.(http_service 단축키를 사용하지 않는다면 대안인 services.http_checks도 있어요) HTTP 기반 앱의 경우요. 이 체크는 간단해요: / 같은 경로에 요청을 보내 200 OK 응답을 기대하고, 리다이렉트나 타임아웃이 나오면 실패해요. 앱이 HTTP를 HTTPS로 리다이렉트한다면 X-Forwarded-Proto = "https" 같은 헤더를 추가해 오탐(false failure)을 피하세요.
가장 흔한 헬스 체크 실패 모드는 쉽게 넘어지기 쉬워요:
- 200 응답 대신 301/302를 받는 경우
- 콜드 스타트 조건에서 느리게 로드되는 경로를 때리는 경우
- 부팅 후
grace_period로 체크를 지연하는 것을 잊는 경우
전형적인 HTTP 헬스 체크는 이렇게 생겼어요:
[http_service.checks]
interval = "15s"
timeout = "2s"
grace_period = "10s"
method = "GET"
path = "/healthz"
headers = { X-Forwarded-Proto = "https" }
HTTP를 서빙하지 않는 앱이나 더 가벼운 체크를 원하는 앱에는 TCP 헬스 체크도 있어요. 이 체크는 구성한 포트에 소켓을 열기만 시도하고, 연결이 수락되면 Machine이 정상이라고 판단해요.
fly.toml의 [checks] 섹션에 정의되는 최상위 헬스 체크는 트래픽 제어가 아니라 관측성을 위한 것이에요. 라우팅에는 영향을 주지 않아요. Machine이 이 체크에 실패해도 로테이션에서 빠지지 않아요. 따라서 내부 모니터링과 알림에 이상적이며, 특히 백그라운드 워커나 공개를 대상으로 하지 않는 서비스에서 뭔가 잘못됐는지 알고 싶지만 Fly가 자동으로 트래픽을 다시 라우팅할 필요는 없을 때 좋아요.
배포에는 또 다른 레이어인 machine_checks가 있어요. 이것은 트래픽 라우팅에 관한 것이 아니라, 잘못된 배포가 실제로 배포되기 전에 잡는 것에 관한 것이에요. machine_check는 임시 Machine을 부팅하고 FLY_TEST_MACHINE_IP를 설정한 뒤 원하는 대로 찔러볼 수 있게 해 줘요. 통합 테스트를 실행하거나, 시뮬레이션 트래픽을 수행하거나, 데이터베이스 접근을 확인하거나, 구성을 검증할 수 있어요. 체크가 실패하면 배포가 중단돼요. 이는 새 릴리스의 카나리 테스트에 완벽해요.
예제:
[services.machine_checks]
command = ["/bin/bash", "-c", "curl -f http://$FLY_TEST_MACHINE_IP:8080/healthz"]
interval = "30s"
timeout = "5s"
참고: machine_checks는 새 이미지가 다른 곳에 배포되기 전에 임시 Machine에서 실행돼요. 격리되어 있으므로 실행 중인 앱에는 영향을 주지 않아요. 하지만 망가진 코드를 프로덕션에 밀어 넣는 것을 막아줄 수 있어요.
Machine이 헬스 체크에 실패하면 Fly Proxy가 로테이션에서 빼요. 그 Machine은 계속 실행되고, 로깅되고, 디버깅될 수 있지만, 사용자에게는 보이지 않아요.
배포 전략: 나만의 트레이드오프 선택하기
헬스 체크와 최소 두 대의 Machine을 함께 사용하면 다운타임을 피할 수 있어요. 새 Machine이 실행될 때까지 플랫폼은 정상 Machine을 종료하지 않아요.
Fly.io는 fly.toml의 [deploy] 섹션에서 몇 가지 배포 전략을 지원해요:
- 롤링(Rolling, 기본): Machine을 한 번에 하나씩 교체해요.
max_unavailable로 한 번에 몇 대까지 내릴지 제어해요. (예:1또는25%) - 즉시(Immediate): 배를 불태워요. 모든 Machine이 한 번에 업데이트되고, 헬스 체크도 없이 최선을 기대해요.
- 카나리(Canary): Machine 하나로 시작해요. 정상이면 롤링으로 계속해요. 연결된 볼륨과는 함께 사용할 수 없어요.
- 블루그린(Bluegreen): 새 Machine을 옛 것들 옆에서 부팅해요. 모든 새 Machine이 체크를 통과한 후에만 트래픽을 전환해요. 가장 빠르고 안전하지만, 역시 연결된 볼륨은 사용할 수 없어요.
모든 배포에는 스모크 체크도 포함돼요. Fly는 Machine이 시작된 후 약 10초 동안 지켜봐요. 반복해서 충돌하면 배포가 실패해요.
이미지가 크거나 부팅이 느리다면 wait_timeout을 설정하는 것을 잊지 마세요. Machine이 시작되기 전에 타임아웃에 걸리기 쉬워요.
release_command로 일회성 작업 실행하기
앱이 실제로 배포되기 전에 데이터베이스 마이그레이션 같은 스크립트 또는 다른 일회성 준비 작업을 실행해야 할 때가 있어요. fly.toml의 release_command가 정확히 그 역할을 해줘요. 새 임시 Machine을 띄우고 명령을 실행한 뒤 종료하고 파괴해요. 배포된 Machine에는 합류하지 않아요.
이것은 배포 시도당 한 번, 빌드된 이미지와 앱의 환경을 사용해 실행돼요. 볼륨이 연결되지 않으므로 영구 상태가 필요한 작업에는 적합하지 않아요.
release_command가 실패하면 배포가 진행되지 않아요. 이는 보통 원하는 동작이에요. 마이그레이션이 안 됐다면 그 지점에서 멈추는 게 낫기 때문이에요.
ENTRYPOINT와 CMD가 어떻게 처리되는지 포함한 더 자세한 내용은 fly.toml 참조에 있어요.
무중단은 공동 책임이에요
Fly.io 플랫폼은 Machine을 정상으로 유지하고 트래픽을 흐르게 해요. 하지만 고칠 수 없는 것이 하나 있어요: 배포 중 애플리케이션 비호환성이에요.
데이터베이스 스키마를 바꾸는 경우를 생각해 보세요. release_command에서 마이그레이션을 실행한 뒤 Machine들을 하나씩 업데이트해요. 옛 앱 코드가 새 스키마를 처리할 수 없다면, 옛 Machine이 계속 실행되는 동안 사용자는 오류를 보게 돼요. 블루그린 배포도 마찬가지예요. 새 Machine들은 정상이지만, 전부 활성화되기 전까지는 트래픽이 전환되지 않아요.
이것을 고치는 것은 여러분의 몫이에요. 잠시 공존할 수 있도록 스키마와 앱 변경을 설계해야 해요. 보통 이것을 의미해요:
- 옛 스키마와 새 스키마 모두에서 작동하는 앱 코드
- 컬럼이나 테이블을 추가하지만, 나중까지 드롭하거나 이름을 바꾸지 않는 마이그레이션
모든 프레임워크에 이에 대한 지침이 있어요. 읽어보고 따르세요. 그러면 헬스 체크, 배포 전략, 플랫폼이 제 역할을 할 수 있어요.
다운타임 없이 배포하는 것은 자동이 아니에요. 하지만 헬스 체크의 약간의 도움과 앱 로직에 대한 약간의 주의만 있으면 충분히 가능해요.