11 KiB
k-skill-proxy 배포 가이드 (gpu01 + cron + Docker)
k-skill-proxy 프로덕션은 이제 Google Cloud Run이 아니라 gpu01의 Docker 컨테이너로 운영한다. GitHub Actions의 main push/merge 자동 배포는 프로덕션 배포 경로가 아니다.
이 문서는 gpu01의 cron 기반 자동 배포, 시크릿/env 배치, 헬스체크, 로그, 롤백, 그리고 기존 GCP 리소스 정리 절차를 정리한다. 일반 contributor는 읽지 않아도 되며, 프록시 운영 maintainer가 gpu01 운영·복구·인계를 할 때 기준 문서로 사용한다.
운영 사실
| 항목 | 값 |
|---|---|
| 프로덕션 호스트 | gpu01 |
| 공개 도메인 | https://k-skill-proxy.nomadamas.org |
| 런타임 | Docker container |
| 컨테이너 이름 | k-skill-proxy |
| 후보 컨테이너 이름 | k-skill-proxy-candidate |
| 이미지 태그 | k-skill-proxy:<git-sha> |
| 컨테이너 이미지 정의 | packages/k-skill-proxy/Dockerfile |
| Docker build context | repo root (/opt/k-skill/current 기준) |
| 배포 스크립트 | scripts/deploy-k-skill-proxy-gpu01.sh |
| 권장 repo checkout | /opt/k-skill/current |
| 비밀 env 파일 | /etc/k-skill-proxy/secrets.env (0600 또는 더 엄격) |
| 배포 대상 config | /etc/k-skill-proxy/deploy.env (0640 또는 더 엄격) |
| 로그 디렉터리 | /var/log/k-skill-proxy |
| 상태 디렉터리 | /var/lib/k-skill-proxy |
| 프로덕션 포트 | host 127.0.0.1:4020 → container 8080 |
| 후보 smoke 포트 | host 127.0.0.1:4021 → container 8080 |
| 배포 트리거 | gpu01 cron이 host-configured deploy SHA/ref만 배포 |
배포 의미론
mainmerge 자체는 프로덕션을 바꾸지 않는다.- gpu01 cron은
/etc/k-skill-proxy/deploy.env에 명시된KSKILL_PROXY_DEPLOY_SHA또는KSKILL_PROXY_DEPLOY_REF만 배포한다. - 배포 대상이 설정되지 않았으면 스크립트는 fail-closed로 종료한다.
- 스크립트는 절대
origin/main을 기본값으로 삼지 않는다. - 권장 초기 컷오버 방식은
KSKILL_PROXY_DEPLOY_SHA=<full commit sha>를 고정하는 것이다. - git 기반 승격이 더 편하면
KSKILL_PROXY_DEPLOY_REF=production같은 전용 production branch/ref를 쓰되, 그 ref를 갱신하는 행위가 곧 프로덕션 승격이다.
배포 흐름
cron이 실행하는 한 번의 배포는 다음 순서로만 성공한다.
flock으로 중복 실행을 막는다./etc/k-skill-proxy/deploy.env와/etc/k-skill-proxy/secrets.env를 검증한다.- repo root에서
git fetch --prune origin을 실행한다. - 명시된 deploy SHA/ref를 commit SHA로 해석한다.
- 현재 배포된 SHA와 같으면 아무 것도 바꾸지 않고 종료한다.
- 기존 serving state를
/var/lib/k-skill-proxy/rollback-state.env에 저장한다. - checkout을 resolved SHA로 detach/force 이동한 뒤 repo root build context로 이미지를 빌드한다.
- 후보 컨테이너를
127.0.0.1:4021에서 띄운다. - 후보 컨테이너의 local
/health가ok: true이고 필수 upstream 설정 boolean이 true인지 확인한다. - 기존 프로덕션 컨테이너를 교체해
127.0.0.1:4020에 새 컨테이너를 띄운다. https://k-skill-proxy.nomadamas.org/healthpublic smoke를 수행한다.- 대표 read-only public route smoke를 수행한다.
- 모든 public smoke가 통과한 뒤에만
/var/lib/k-skill-proxy/deployed-sha를 갱신한다.
public smoke 실패는 경고가 아니라 배포 실패다. 상태 파일을 갱신하지 않고, 가능한 경우 이전 컨테이너/image/port/routing state로 rollback을 시도한다.
1회성 gpu01 셋업
sudo install -d -m 0755 /opt/k-skill
sudo install -d -m 0750 /etc/k-skill-proxy
sudo install -d -m 0750 /var/lib/k-skill-proxy
sudo install -d -m 0750 /var/log/k-skill-proxy
# repo checkout. 운영자가 실제 deploy user 권한에 맞춰 owner를 조정한다.
sudo git clone https://github.com/NomaDamas/k-skill.git /opt/k-skill/current
필수 도구:
gitdockercurlpython3flock(util-linux)
reverse proxy는 https://k-skill-proxy.nomadamas.org를 gpu01의 127.0.0.1:4020으로 전달해야 한다. DNS/TLS/reverse proxy 설정은 repo 밖의 호스트 설정이므로 컷오버 때 public /health로 반드시 검증한다.
배포 대상 설정
/etc/k-skill-proxy/deploy.env 예시:
# 가장 안전한 초기 컷오버: 정확한 commit SHA 고정
KSKILL_PROXY_DEPLOY_SHA=0123456789abcdef0123456789abcdef01234567
# 또는 전용 production ref를 의도적으로 운용할 때만 사용
# KSKILL_PROXY_DEPLOY_REF=production
KSKILL_PROXY_REPO_DIR=/opt/k-skill/current
KSKILL_PROXY_CONTAINER_NAME=k-skill-proxy
KSKILL_PROXY_IMAGE_NAME=k-skill-proxy
KSKILL_PROXY_HOST_PORT=4020
KSKILL_PROXY_CANDIDATE_PORT=4021
KSKILL_PROXY_CONTAINER_PORT=8080
KSKILL_PROXY_PUBLIC_BASE_URL=https://k-skill-proxy.nomadamas.org
KSKILL_PROXY_STATE_DIR=/var/lib/k-skill-proxy
KSKILL_PROXY_LOG_DIR=/var/log/k-skill-proxy
둘 다 설정되어 있으면 KSKILL_PROXY_DEPLOY_SHA가 우선한다. 둘 다 없으면 배포하지 않는다.
프로덕션 승격 절차
pinned SHA 방식
dev→mainmerge 후 검증할 commit SHA를 정한다.- gpu01에서
/etc/k-skill-proxy/deploy.env의KSKILL_PROXY_DEPLOY_SHA를 해당 full SHA로 바꾼다. - cron이 다음 주기에 배포하거나, 운영자가 스크립트를 수동 실행한다.
production branch 방식
production같은 전용 branch/ref를 운영한다.- maintainer가 의도적으로 해당 ref를 fast-forward 또는 갱신한다.
- gpu01 cron은
KSKILL_PROXY_DEPLOY_REF=production만 해석해서 배포한다.
이 방식을 써도 main merge 단독으로는 배포되지 않는다.
시크릿과 runtime env
/etc/k-skill-proxy/secrets.env는 repo에 넣지 않는다. root 또는 전용 deploy user만 읽을 수 있게 0600 또는 더 엄격하게 둔다.
필수/운영 env 예시:
AIR_KOREA_OPEN_API_KEY=...
KMA_OPEN_API_KEY=...
SEOUL_OPEN_API_KEY=...
HRFCO_OPEN_API_KEY=...
OPINET_API_KEY=...
DATA_GO_KR_API_KEY=...
DATA4LIBRARY_AUTH_KEY=...
FOODSAFETYKOREA_API_KEY=...
KAKAO_REST_API_KEY=...
KEDU_INFO_KEY=...
KRX_API_KEY=...
KOSIS_API_KEY=...
NAVER_SEARCH_CLIENT_ID=...
NAVER_SEARCH_CLIENT_SECRET=...
LAW_OC=...
# 선택
LAW_REFERER=
LAW_USER_AGENT=
# runtime knobs
KSKILL_PROXY_HOST=0.0.0.0
KSKILL_PROXY_NAME=k-skill-proxy
KSKILL_PROXY_CACHE_TTL_MS=300000
KSKILL_PROXY_RATE_LIMIT_WINDOW_MS=60000
KSKILL_PROXY_RATE_LIMIT_MAX=60
PORT=8080
Docker access 보안 경계
Docker daemon, Docker socket, docker group, sudo docker, 컨테이너 inspect/start 권한은 모두 프로덕션 시크릿 접근 권한과 동일하게 취급한다. 컨테이너 env, bind mount, 로그, 대체 컨테이너 실행을 통해 upstream API key를 읽을 수 있기 때문이다.
- 편의상 일반 사용자를
dockergroup에 추가하지 않는다. - cron user는 프로덕션 운영자로 간주하고 의도적으로 선택한다.
- 배포 스크립트는
set -x를 쓰지 않고 env 값을 로그에 출력하지 않는다. - Docker socket을 외부에 노출하지 않는다.
cron 설정
crontab 예시:
*/10 * * * * flock -n /var/lock/k-skill-proxy-deploy.lock /opt/k-skill/current/scripts/deploy-k-skill-proxy-gpu01.sh >> /var/log/k-skill-proxy/deploy.log 2>&1
cron은 production deploy trigger지만, 배포 대상은 항상 /etc/k-skill-proxy/deploy.env의 explicit SHA/ref다. cron 주기가 돌아도 대상 SHA가 바뀌지 않았으면 스크립트는 skip한다.
수동 배포/점검
/opt/k-skill/current/scripts/deploy-k-skill-proxy-gpu01.sh
curl -fsS https://k-skill-proxy.nomadamas.org/health
로그:
tail -f /var/log/k-skill-proxy/deploy.log
docker logs --tail=200 k-skill-proxy
smoke test 기준
local health
curl -fsS http://127.0.0.1:4021/health
candidate 단계에서는 후보 포트 4021, production 단계에서는 4020을 확인한다. /health JSON은 ok: true여야 하며, upstreams의 *Configured boolean이 true인지 확인한다.
public health
curl -fsS https://k-skill-proxy.nomadamas.org/health
public health 실패는 배포 실패다. deployed-state를 갱신하지 않는다.
대표 route smoke
기본 대표 route는 health만으로 잡히지 않는 public routing/API path 회귀를 확인하기 위한 read-only 요청이다. 운영 환경에서 upstream quota/availability를 고려해 route를 조정할 수 있다.
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/fine-dust/report' \
--data-urlencode 'stationName=종로구'
upstream 자체 장애가 확인된 경우에만 운영자가 로그에 사유를 남기고 route smoke를 별도 처리한다. public /health는 항상 필수다.
rollback
배포 스크립트는 production switch 전에 /var/lib/k-skill-proxy/rollback-state.env에 다음 값을 저장한다.
- timestamp
- previous deployed SHA
- previous image tag 또는 image ID
- previous container name
- previous container ID
- previous host port → container port mapping
- previous reverse-proxy upstream target
- previous routing state
- previous health status
수동 rollback 기본 절차:
sudo -E /opt/k-skill/current/scripts/deploy-k-skill-proxy-gpu01.sh --rollback
curl -fsS https://k-skill-proxy.nomadamas.org/health
문제가 이미지가 아니라 reverse proxy/DNS/TLS인 경우에는 저장된 routing state에 따라 이전 upstream target 또는 proxy 설정을 복원하고 proxy를 reload한다. rollback은 이전 이미지를 띄우는 것만으로 충분하다고 가정하지 않는다.
문제 해결
| 증상 | 확인할 것 |
|---|---|
no deploy target configured |
/etc/k-skill-proxy/deploy.env에 KSKILL_PROXY_DEPLOY_SHA 또는 KSKILL_PROXY_DEPLOY_REF가 있는지 확인 |
health의 *Configured가 false |
/etc/k-skill-proxy/secrets.env에 해당 upstream key가 있는지 확인 |
| Docker build 실패 | repo root에서 -f packages/k-skill-proxy/Dockerfile .로 빌드되는지 확인 |
| public health 실패 | DNS, TLS, reverse proxy upstream, firewall, container port mapping 확인 |
| cron은 도는데 배포가 안 됨 | deployed SHA와 resolved SHA가 같은지, lock이 오래 잡혀 있는지, 로그 권한 확인 |
| rollback 후에도 public 장애 | reverse proxy/routing state가 이전 target으로 복원됐는지 확인 |
기존 GCP/Cloud Run 정리
GitHub Actions의 Cloud Run 자동 배포는 즉시 비활성화한다. 다만 Cloud Run, Artifact Registry, WIF, Secret Manager 리소스는 gpu01 안정화 기간 동안 legacy rollback/비교용으로 잠시 남길 수 있다.
정리 순서:
- gpu01 public
/health와 대표 route smoke가 안정적으로 통과하는지 확인한다. - GitHub에 push-to-main 배포 workflow가 남아 있지 않은지 확인한다.
- rollback window가 끝나면 Cloud Run traffic, Artifact Registry image, WIF provider/service account, Secret Manager secret을 수동 정리한다.
- 정리 기록은 운영 로그나 PR 본문에 남긴다.