콘텐츠로 이동

GitLab Ops Server Poll-Release Runbook (운영 기준)

0. 전제

poll-release 전체 흐름:

실행 요청
        ▼
runtime 환경 검증
        ▼
.env.{RELEASE_ENV} / .release-storage.env 로드
        ▼
deploy.lock 획득
        ▼
desired release state 조회
        ▼
metadata 검증
        ▼
현재 package_version 과 비교
        ▼
신규 release package 다운로드
        ▼
sha256 / tar safety 검증
        ▼
extract / stage / release promote
        ▼
after-prepare-release hook 실행
        ▼
current symlink activate
        ▼
release marker 작성
        ▼
PM2 delete/start/save
        ▼
current-release-state.json 갱신
        ▼
downloads / tmp / old releases cleanup
        ▼
최종 검증

운영 기준:

  • poll-release.sh{SERVICE_USER} 로 실행함
  • working directory 는 /var/www/{SERVICE_NAME} 이어야 함
  • Release Storage 는 MinIO/S3 compatible API 로 조회함
  • 운영 서버는 GitLab 에 직접 접근하지 않음
  • current symlink 만 mutable activate 지점으로 사용함
  • release directory 는 immutable release archive 로 취급함
  • GitLab CI/CD 의 release target branch 는 .ops/ci/release-branch-versions/{branch}.yaml 기준으로 확장 가능함.

1. 빠른 상태 확인

poll-release 상태를 먼저 확인:

sudo systemctl status release-poll.{SERVICE_NAME}.timer -n 100 --no-pager
sudo systemctl status release-poll.{SERVICE_NAME}.service -n 100 --no-pager
sudo systemctl list-timers | grep release-poll

최근 실행 로그 확인:

sudo journalctl -u release-poll.{SERVICE_NAME}.service -n 100 --no-pager
sudo tail -n 100 /var/www/{SERVICE_NAME}/logs/poll-release.log

현재 활성 release 확인:

sudo readlink -f /var/www/{SERVICE_NAME}/current
sudo jq -r '.package_version // .version // empty' /var/www/{SERVICE_NAME}/shared/current-release-state.json
sudo -u {SERVICE_USER} -H pm2 status {SERVICE_NAME}

정상 기준:

  • timer 상태가 active(waiting) 이어야 함
  • service 는 oneshot 실행 후 inactive(dead) 일 수 있음
  • current 는 releases/{PACKAGE_VERSION} 을 가리켜야 함
  • current-release-state.json 의 package_version 이 current release 와 일치해야 함
  • PM2 app 이 online 상태여야 함

2. Runtime 사전 확인

수동 실행 또는 장애 분석 전 runtime 구조를 확인:

/var/www/{SERVICE_NAME}/
├── current -> /var/www/{SERVICE_NAME}/releases/{PACKAGE_VERSION}
├── downloads/
├── logs/
│   └── poll-release.log
├── releases/
├── shared/
│   ├── poll-release-hooks/
│   │   └── 01-after-prepare-release.sh
│   ├── .env.pre-production
│   ├── .env.production
│   ├── .release-storage.env
│   ├── .service.env
│   └── current-release-state.json
├── tmp/
└── deploy.lock

runtime directory 확인:

sudo ls -ld /var/www/{SERVICE_NAME}
sudo ls -ld /var/www/{SERVICE_NAME}/downloads
sudo ls -ld /var/www/{SERVICE_NAME}/logs
sudo ls -l /var/www/{SERVICE_NAME}/logs/poll-release.log
sudo ls -ld /var/www/{SERVICE_NAME}/releases
sudo ls -ld /var/www/{SERVICE_NAME}/shared
sudo ls -ld /var/www/{SERVICE_NAME}/tmp
sudo ls -l /var/www/{SERVICE_NAME}/deploy.lock
sudo ls -l /opt/bin/poll-release.{SERVICE_NAME}.sh

shared directory 확인:

sudo ls -ld /var/www/{SERVICE_NAME}/shared/poll-release-hooks
sudo ls -l /var/www/{SERVICE_NAME}/shared/poll-release-hooks/01-after-prepare-release.sh
sudo ls -l /var/www/{SERVICE_NAME}/shared/.env.pre-production
sudo ls -l /var/www/{SERVICE_NAME}/shared/.env.production
sudo ls -l /var/www/{SERVICE_NAME}/shared/.release-storage.env
sudo ls -l /var/www/{SERVICE_NAME}/shared/.service.env
sudo ls -l /var/www/{SERVICE_NAME}/shared/current-release-state.json

3. systemd 자동 실행 확인

timer 기준으로 poll-release 자동 실행 여부를 확인:

sudo systemctl status release-poll.{SERVICE_NAME}.timer -n 100 --no-pager
sudo systemctl cat release-poll.{SERVICE_NAME}.timer --no-pager

service unit 확인:

sudo systemctl cat release-poll.{SERVICE_NAME}.service --no-pager

timer 재시작:

sudo systemctl restart release-poll.{SERVICE_NAME}.timer
sudo systemctl status release-poll.{SERVICE_NAME}.timer -n 100 --no-pager
sudo systemctl list-timers | grep release-poll

4. 수동 poll-release 실행

  • root 로 직접 실행하지 않음
  • /var/www/{SERVICE_NAME} 에서 실행함

자동 timer 를 잠시 중지:

sudo systemctl stop release-poll.{SERVICE_NAME}.timer

runtime directory 로 이동:

cd /var/www/{SERVICE_NAME}

poll-release 를 서비스 사용자로 실행:

sudo -u {SERVICE_USER} -H env \
  SERVICE_NAME={SERVICE_NAME} \
  SERVICE_USER={SERVICE_USER} \
  SERVICE_GROUP={SERVICE_GROUP} \
  DEPLOY_BASE=/var/www/{SERVICE_NAME} \
  RELEASE_ENV={RELEASE_ENV} \
  /opt/bin/poll-release.{SERVICE_NAME}.sh

timer 를 재개:

sudo systemctl start release-poll.{SERVICE_NAME}.timer
sudo systemctl status release-poll.{SERVICE_NAME}.timer -n 100 --no-pager
sudo systemctl list-timers | grep release-poll

5. 실행 중 로그 확인

systemd journal 확인:

sudo journalctl -u release-poll.{SERVICE_NAME}.service -f

SyslogIdentifier 기준 확인:

sudo journalctl -t release-poll.{SERVICE_NAME} -f

file log 확인:

sudo tail -F /var/www/{SERVICE_NAME}/logs/poll-release.log

6. 정상 배포 결과 확인

current symlink 확인:

sudo readlink -f /var/www/{SERVICE_NAME}/current

release marker 확인:

PACKAGE_VERSION="$(sudo jq -r '.package_version // .version // empty' /var/www/{SERVICE_NAME}/shared/current-release-state.json)"
test -n "$PACKAGE_VERSION"
sudo ls -l /var/www/{SERVICE_NAME}/current/"$PACKAGE_VERSION"

state 확인:

sudo jq . /var/www/{SERVICE_NAME}/shared/current-release-state.json

PM2 확인:

sudo -u {SERVICE_USER} -H pm2 status {SERVICE_NAME}
sudo -u {SERVICE_USER} -H pm2 describe {SERVICE_NAME}

7. 수동 복구 기준

  • 현재 스크립트는 자동 rollback 을 수행하지 않음.
  • 수동 rollback 후 current-release-state.json 은 자동으로 되돌아가지 않음
  • 필요 시 이전 release metadata 기준으로 state 를 수동 복구해야 함

현재 current 확인:

sudo readlink -f /var/www/{SERVICE_NAME}/current

release 목록 확인:

sudo ls -dt /var/www/{SERVICE_NAME}/releases/*

복구 대상 release 선택:

ROLLBACK_RELEASE=/var/www/{SERVICE_NAME}/releases/{PREVIOUS_PACKAGE_VERSION}

current symlink 수동 교체:

sudo rm -f /var/www/{SERVICE_NAME}/tmp/current.rollback.link
sudo ln -s "$ROLLBACK_RELEASE" /var/www/{SERVICE_NAME}/tmp/current.rollback.link
sudo mv -Tf /var/www/{SERVICE_NAME}/tmp/current.rollback.link /var/www/{SERVICE_NAME}/current

PM2 env 기준:

production     -> production
pre-production -> pre_production

PM2 재시작:

sudo -u {SERVICE_USER} -H env SERVICE_NAME={SERVICE_NAME} /usr/bin/pm2 delete {SERVICE_NAME} || true
sudo -u {SERVICE_USER} -H env SERVICE_NAME={SERVICE_NAME} /usr/bin/pm2 start /var/www/{SERVICE_NAME}/current/ecosystem.config.cjs --env {PM2_ENV} --update-env
sudo -u {SERVICE_USER} -H /usr/bin/pm2 save --force
sudo -u {SERVICE_USER} -H /usr/bin/pm2 status {SERVICE_NAME}

주의:

  • 수동 rollback 후 Release Storage 의 current/{RELEASE_ENV}.json 이 여전히 최신 package_version 을 가리키고 있으면, 다음 polling cycle 에서 다시 최신 desired state 로 배포될 수 있음.
  • rollback 을 유지하려면 Release Storage current metadata 를 이전 release metadata 로 재지정하거나, 운영 서버의 timer 를 중지한 상태에서 원인 분석을 수행함.

8. Cleanup 확인

poll-release 성공 후 downloads 와 tmp 는 비워짐.

sudo find /var/www/{SERVICE_NAME}/downloads -mindepth 1 -maxdepth 1 -print
sudo find /var/www/{SERVICE_NAME}/tmp -mindepth 1 -maxdepth 1 -print

release retention 확인:

sudo ls -dt /var/www/{SERVICE_NAME}/releases/*

9. 보안 기준

운영 기준:

  • 운영 서버는 Release Storage read-only 권한만 사용함
  • 운영 서버는 GitLab 직접 접근 없이 Release Storage 만 조회함
  • Release Storage credential 은 /var/www/{SERVICE_NAME}/shared/.release-storage.env 에서 관리함
  • shared env 파일은 Git 저장소에 포함하지 않음
  • shared env 파일은 shell script 로 source 되므로 신뢰된 파일만 배치함
  • shared env 파일 권한은 600 기준으로 관리함
  • poll-release runtime 은 {SERVICE_USER} 로 실행함
  • root 권한이 필요한 수동 명령은 sudo 로 실행함
  • package archive 는 extract 전 tar path safety 검사를 수행함
  • package sha256 이 일치하지 않으면 activate 하지 않음
  • cleanup 은 지정된 downloads/tmp 경로 하위만 삭제함