TL;DR

  • kamal app maintenance를 실행하면 kamal-proxy가 해당 Host의 요청을 가로채 503을 반환합니다. 종료는 kamal app live입니다.
  • 커스텀 503 페이지를 사용하려면 config/deploy.ymlerror_pages_path(예: public/proxy/) 아래 503.html을 두고 한 번 배포하여 프록시에 파일을 올려두셔야 합니다.
  • Kamal의 Health Check 경로를 /up처럼 전용 경로로 분리해야 의도한대로 동작합니다.

kamal2의 점검(maintenance) 모드

클라우드가 고도화되면서 블루/그린 배포 전략 등이 보편화되었고 점검 페이지를 보는 일은 많이 줄었습니다. 하지만 여전히 점검이 유용한 경우도 있습니다. kamal2에서는 네이티브하게 점검 모드를 지원합니다.

kamal2에서는 kamal-proxy가 프록시 레이어를 담당합니다. 점검(maintenance) 모드를 켜면 프록시가 앱 뒤쪽으로 전달하지 않고 자체적으로 503을 반환합니다.

# 점검 시작
$ kamal app maintenance

Kamal2의 기본 점검 페이지
Kamal2의 기본 점검 페이지

점검 페이지 하단에 안내 문구를 지정하는 것도 가능합니다.

# 점검 시작 (안내 문구 포함)
kamal app maintenance --message "오늘 02:00~03:00 DB 점검 중입니다"

커스텀 메시지를 사용한 점검 페이지
커스텀 메시지를 사용한 점검 페이지

점검이 끝나면 live 서브 커맨드로 점검 모드를 종료할 수 있습니다.

# 점검 종료
kamal app live

curl로도 테스트해볼 수 있습니다. Kamal2는 기본적으로 kamal-proxy를 사용해서 Host를 기반으로 라우팅을 하기 때문에, 실제 호스트를 사용해서 확인하거나 로컬 서버에서 테스트하는 경우 반드시 -H 옵션을 사용해 호스트를 지정해주어야합니다.

# 프록시(또는 ALB) 경유 테스트 시 Host 헤더를 명시
curl -i -H 'Host: app.kamal.local' https://100.40.10.10/

커스텀 503 페이지와 메시지 사용법

config/deploy.ymlerror_pages_path를 지정해 두시면, 배포 시 그 경로의 에러 페이지들이 프록시 호스트로 복사됩니다. 예를 들어 public/proxy/503.html을 만들어두면 maintenance 모드에서 해당 파일을 그대로 보여줍니다.

# config/deploy.yml
# public/proxy/503.html
error_pages_path: public/proxy/ 

중요: 커스텀 503을 처음 쓰실 때는 반드시 한 번 배포(kamal deploy)하여 파일을 프록시에 복사해야 합니다. maintenance 명령만으로는 새 HTML이 업로드되지 않습니다.

기본 템플릿은 --message로 넘긴 문구를 표시합니다. 직접 만든 503.html에서도 아래처럼 Go 템플릿 변수 {{ .Message }} 를 사용하시면 같은 문구를 노출할 수 있습니다.

<!-- public/proxy/503.html -->
<main>
  <h1>일시 점검 중입니다</h1>
  <p>
    {{ if .Message }}
      {{ .Message }}
    {{ else }}
      점검 중입니다. 잠시 후 다시 이용해 주세요.
    {{ end }}
  </p>
</main>

이제 다음 명령어로 커스텀 페이지가 적용되었는지 확인해봅니다.

$ kamal app maintenance --message "오늘 02:00~02:10 점검 중입니다"

점검 기능 활용

다운타임이 필요한 작업에서는 프록시 상태와 앱 컨테이너 상태를 구분해서 제어하는 것이 중요합니다. 반드시 maintenance가 먼저 의도한대로 동작하는지 확인후 점검 작업을 진행해주세요.

  1. 프록시를 먼저 maintenance로 전환
    kamal app maintenance --message "점검 중입니다"
  2. 필요 시 앱/워커 정지 (DB 마이그레이션 등)
    kamal app stop --roles=web   # 필요한 role만 선택적으로
  3. 작업 수행 후 앱 기동 또는 배포
    kamal app start --roles=web   # 또는 kamal deploy
  4. 라이브 복귀
    kamal app live

트러블슈팅: 점검을 걸었는데 루트에서 빈 응답(200)을 반환하는 경우

maintenance 기능을 처음 사용할 때, 루트(/) 라우트에서 빈 응답(status code: 200)을 내려주는 문제를 겪었습니다. 처음에는 로드 밸런서 레이어와 충돌이라고 생각했었는데, 다른 경로로 접근하는 경우 점검 페이지가 제대로 출력되는 상황이었습니다. 이는 deploy.yml 에서 설정한 proxy의 healthcheck 경로가 특별하게 처리되기 때문입니다.

실제로 문제가 있었던 상황에서는 healthcheck 경로가 /(루트)로 설정되어있습니다. 다음과 같이 healthcheck 경로를 분리하는 것을 권장합니다.

proxy:
  hosts:
    - blog.44bits.io
  healthcheck:
    path: /up
    interval: 3
    timeout: 3

마치며

점검 페이지를 구현하는 건 간단하면서도 까다로운 작업 중 하나입니다. 메인 서버에서 응답을 처리할 수 없기 때문에 이를 처리하기 위한 서버나 별도 레이어에서 처리하는 경우가 많습니다. 대규모 서비스에서는 별도로 점검 페이지 서빙을 위한 서버를 관리하거나 로드밸런서에서 응답을 가로채서 처리하는 경우가 많습니다.

Kamal에서 제공하는 점검(maintenance) 모드는 매우 실용적인 기능 중 하나입니다. 특히 kamal-proxy 레이어가 사실상 고정인 상황에서 이를 활용한 유용한 기능 중 하나입니다. Kamal을 사용중이라면 사용해보시기 바랍니다.