콘텐츠로 이동

GitLab Ops Server Poll-Release

0. 전제

poll-release.sh 는 Release Storage 에 publish 된 desired release state 를 기준으로 운영 서버가 self-pull deploy 를 수행하는 runtime script 임.

poll-release 전체 흐름:

systemd timer 또는 수동 실행
        ▼
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} 로 실행함
  • poll-release.sh 는 root 로 직접 실행하지 않음
  • working directory 는 /var/www/{SERVICE_NAME} 이어야 함
  • Release Storage 는 MinIO/S3 compatible API 로 조회함
  • 운영 서버는 GitLab 에 직접 접근하지 않음
  • 운영 서버는 build 를 수행하지 않음
  • release package 는 CI/CD 단계에서 이미 실행 가능한 상태로 생성되어야 함
  • current symlink 만 mutable activate 지점으로 사용함
  • release directory 는 immutable release archive 로 취급함
  • 현재 스크립트는 별도 HTTP health check 와 자동 rollback 을 수행하지 않음

1. 목적

poll-release.sh 의 목적은 운영 서버의 현재 상태를 Release Storage 에 선언된 desired state 로 수렴시키는 것임.

GitLab CI/CD 는 release package 와 release metadata 를 publish 하고, 운영 서버는 해당 metadata 를 polling 하여 필요한 경우 self-pull deploy 를 수행함.

운영 기준:

  • GitLab 은 deploy executor 가 아님
  • GitLab 은 release state 를 선언함
  • 운영 서버는 desired state 를 polling 함
  • 운영 서버는 desired state 기준으로 직접 deploy 를 수행함
  • 동일 package_version 이 이미 적용된 경우 deploy 를 수행하지 않음
  • partially deployed 상태를 정상 상태로 간주하지 않음
  • current-release-state.json 은 성공적으로 activate 된 release 기준으로만 갱신함

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 path 변수:

DEPLOY_BASE=/var/www/{SERVICE_NAME}

RELEASES_PATH=$DEPLOY_BASE/releases
SHARED_PATH=$DEPLOY_BASE/shared
CURRENT_LINK=$DEPLOY_BASE/current

DOWNLOAD_PATH=$DEPLOY_BASE/downloads
TMP_PATH=$DEPLOY_BASE/tmp
LOG_PATH=$DEPLOY_BASE/logs

LOCK_FILE=$DEPLOY_BASE/deploy.lock
STATE_FILE=$SHARED_PATH/current-release-state.json
POLL_LOG_FILE=$LOG_PATH/poll-release.log

SERVICE_ENV_FILE=$SHARED_PATH/.service.env
RELEASE_STORAGE_ENV_FILE=$SHARED_PATH/.release-storage.env
ENV_TARGET_FILE=$SHARED_PATH/.env.{RELEASE_ENV}

POLL_RELEASE_HOOKS_PATH=$SHARED_PATH/poll-release-hooks
AFTER_PREPARE_RELEASE_HOOK=$POLL_RELEASE_HOOKS_PATH/01-after-prepare-release.sh

각 directory 역할:

  • current: 현재 활성 release 를 가리키는 symlink 임
  • releases: immutable release directory 를 보관함
  • downloads: release package 다운로드 임시 보관 영역임
  • tmp: desired state, tar list, extract, stage, temporary symlink 작업 영역임
  • logs: poll-release file log 저장 영역임
  • shared: release-independent env/state/hook 저장 영역임
  • deploy.lock: bootstrap 과 poll-release 실행을 직렬화하기 위한 lock file 임

3. systemd 실행 구조

poll-release 는 systemd service + timer 기반으로 실행함.

systemd service 기준:

User={SERVICE_USER}
Group={SERVICE_GROUP}
WorkingDirectory=/var/www/{SERVICE_NAME}
Environment=SERVICE_NAME={SERVICE_NAME}
Environment=SERVICE_USER={SERVICE_USER}
Environment=SERVICE_GROUP={SERVICE_GROUP}
Environment=DEPLOY_BASE=/var/www/{SERVICE_NAME}
Environment=RELEASE_ENV={RELEASE_ENV}
ExecStart=/opt/bin/poll-release.{SERVICE_NAME}.sh

timer 기준:

OnBootSec=1m
OnUnitActiveSec=1m
RandomizedDelaySec=15s
AccuracySec=5s
Persistent=true

운영 기준:

  • polling 은 systemd timer 기반으로 수행함
  • service 는 Type=oneshot 형태임
  • polling script 는 resident daemon 으로 동작하지 않음
  • timer enable/start 대상은 service 가 아니라 timer 임
  • 다중 서버 환경에서는 RandomizedDelaySec 로 polling 시점을 분산함

4. Runtime 환경 변수

필수 환경 변수:

SERVICE_NAME
SERVICE_USER
SERVICE_GROUP
DEPLOY_BASE
RELEASE_ENV

검증 기준:

  • SERVICE_NAME 은 비어 있으면 안 됨
  • SERVICE_USER 는 비어 있으면 안 됨
  • SERVICE_GROUP 은 비어 있으면 안 됨
  • DEPLOY_BASE 는 비어 있으면 안 됨
  • RELEASE_ENV 는 비어 있으면 안 됨
  • SERVICE_NAME, SERVICE_USER, SERVICE_GROUP 은 식별자 허용 문자 기준을 통과해야 함

허용 문자 기준:

A-Z a-z 0-9 . _ -

RELEASE_ENV 허용값:

production
pre-production

PM2 env 매핑:

production     -> production
pre-production -> pre_production

운영 기준:

  • systemd unit 의 RELEASE_ENV 값과 /var/www/{SERVICE_NAME}/shared/.env.{RELEASE_ENV} 내부의 RELEASE_ENV 값은 일치해야 함
  • .env.{RELEASE_ENV} 파일이 없으면 polling cycle 을 skip 함
  • .env.{RELEASE_ENV} 로드 후 RELEASE_ENV 값이 요청값과 다르면 실패함
  • GitLab CI/CD 의 release target branch 는 .ops/ci/release-branch-versions/{branch}.yaml 기준으로 확장 가능함.

5. Release Storage 정책

운영 서버는 Release Storage 를 MinIO/S3 compatible API 로 조회함.

Release Storage env 파일:

/var/www/{SERVICE_NAME}/shared/.release-storage.env

필수 변수:

MINIO_ENDPOINT
MINIO_REGION
MINIO_ACCESS_KEY
MINIO_SECRET_KEY
MINIO_BUCKET
MINIO_SERVICE_PREFIX

desired release state object:

s3://{MINIO_BUCKET}/{SERVICE_PREFIX}/{SERVICE_NAME}/{RELEASE_ENV}/current/{RELEASE_ENV}.json

예시:

s3://{MINIO_BUCKET}/{SERVICE_PREFIX}/{SERVICE_NAME}/production/current/production.json
s3://{MINIO_BUCKET}/{SERVICE_PREFIX}/{SERVICE_NAME}/pre-production/current/pre-production.json

조회 방식:

AWS_ACCESS_KEY_ID="$MINIO_ACCESS_KEY" \
AWS_SECRET_ACCESS_KEY="$MINIO_SECRET_KEY" \
AWS_DEFAULT_REGION="$MINIO_REGION" \
AWS_EC2_METADATA_DISABLED=true \
aws --endpoint-url "$MINIO_ENDPOINT" \
  s3 cp \
  "s3://$MINIO_BUCKET/$RELEASE_STATE_OBJECT" \
  "$DESIRED_STATE_FILE"

운영 기준:

  • MINIO_ENDPOINT 는 API endpoint 이며, desired release state object path 자체에 포함하지 않음
  • 운영 서버는 public HTTP URL 이 아니라 MinIO/S3 compatible API 를 통해 desired release state 를 조회함

6. Release Metadata 기준

poll-release 는 desired state JSON 에서 다음 필드를 읽음.

주요 필드:

target_env
project_name
service_prefix
service_name
declared_version

package_version
package_name
package_path
package_url
package_sha256
package_size_bytes

release_storage.endpoint
release_storage.bucket

metadata_path
current_metadata_path

commit_sha
commit_short_sha
pipeline_id

필수 검증:

target_env 비어 있지 않아야 함
target_env == RELEASE_ENV
project_name 비어 있지 않아야 함
project_name == PROJECT_NAME == MINIO_BUCKET
service_prefix 비어 있지 않아야 함
service_prefix == SERVICE_PREFIX
service_name 비어 있지 않아야 함
service_name == SERVICE_NAME
package_version 비어 있지 않아야 함
package_version path-safe 식별자여야 함
package_sha256 비어 있지 않아야 함
package_sha256 64자리 hex 값이어야 함
package_path 또는 package_url 중 하나는 Release Storage object path 로 제공되어야 함

선택 검증:

project_name 이 있으면 release_storage.bucket 또는 MINIO_BUCKET 과 일치해야 함
service_prefix 가 있으면 MINIO_SERVICE_PREFIX 에서 마지막 `/{SERVICE_NAME}` 을 제외한 값과 일치해야 함
release_storage.bucket 이 있으면 MINIO_BUCKET 과 일치해야 함

path safety 기준:

package_version 허용 문자: A-Z a-z 0-9 . _ -
package_name 은 basename 이어야 함
package_path 는 relative storage path 여야 함
package_url 은 http/https URL 이면 실패함

운영 기준:

  • package_version 은 immutable deploy identifier 로 사용함
  • declared_version 은 semantic version / release label 용도로 사용함
  • package_path 는 운영 서버가 다운로드할 Release Storage object path 임
  • package_path 를 package download 기준으로 우선 사용함
  • package_sha256 은 실제 package tarball 의 SHA256 hash 값임
  • 운영 서버는 metadata 내부의 package_sha256 값을 기준으로 다운로드한 package 를 검증함
  • package_sha256_path 는 checksum file 의 Release Storage object path metadata 임
  • package_sha256_path 는 감사, 추적, 수동 검증 용도로 유지함
  • package_sha256package_sha256_path 는 의미가 다름
  • package_url 은 legacy / fallback metadata 로 취급함
  • 현재 poll-release.sh 기준 package_urlhttp:// 또는 https:// 로 시작하면 실패함
  • 운영 서버는 public HTTP URL 이 아니라 MinIO/S3 compatible API 로 package 를 다운로드함
  • source.gitlab_package_registry 는 CI/CD source metadata 이며, 운영 서버 deploy download 기준은 Release Storage 임
  • 운영 서버는 source.gitlab_package_registry 값을 deploy download 에 사용하지 않음
  • metadata 의 service_prefix 는 GitLab 문서 변수 SERVICE_PREFIX 단독값임
  • MINIO_SERVICE_PREFIX 는 Release Storage object prefix 전체값이며 {SERVICE_PREFIX}/{SERVICE_NAME} 조합값임

7. Desired State / Current State

운영 서버는 마지막으로 성공적으로 적용한 release state 를 local state 로 유지함.

local state file:

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

비교 기준:

CURRENT_VERSION="$(jq -r '.package_version // .version // empty' "$STATE_FILE" 2>/dev/null || true)"

동작 기준:

desired package_version 이 비어 있으면 polling cycle skip
current package_version == desired package_version 이면 polling cycle skip
current package_version != desired package_version 이면 deploy 진행

운영 기준:

  • current-release-state.json 은 마지막 성공 deploy 의 desired state JSON 을 복사한 파일임
  • 동일 package_version 은 재배포하지 않음
  • current-release-state.json{} 상태이면 최초 deploy 대상으로 판단함
  • local state 는 PM2 reload/start 성공 이후에만 갱신함

8. Deploy Lock 기준

poll-release 는 deploy 중복 실행을 방지하기 위해 flock 기반 lock 을 사용함.

lock file:

/var/www/{SERVICE_NAME}/deploy.lock

동작:

exec 9>"$LOCK_FILE"

if ! flock -n 9; then
  skip "Another bootstrap/poll-release process is already running. skipped."
fi

운영 기준:

  • 동일 서비스의 bootstrap 과 poll-release 는 동시에 실행되면 안 됨
  • 동일 서비스의 poll-release cycle 은 동시에 실행되면 안 됨
  • lock 획득 실패는 장애가 아니라 현재 polling cycle skip 으로 처리함
  • lock 은 process lifetime 동안 유지됨
  • timer interval 보다 deploy 시간이 길어도 중복 deploy 를 방지함

9. Package Download / Verify 기준

package download path:

/var/www/{SERVICE_NAME}/downloads/{PACKAGE_NAME}

temporary download path:

/var/www/{SERVICE_NAME}/tmp/{PACKAGE_NAME}.tmp

package object:

s3://{MINIO_BUCKET}/{PACKAGE_DOWNLOAD_OBJECT}

동작 기준:

1. downloads/{PACKAGE_NAME} 이 이미 존재하는지 확인함
2. 기존 package 가 있으면 sha256 을 계산함
3. 기존 package sha256 이 expected sha256 과 같으면 download 를 skip 함
4. 기존 package sha256 이 다르면 기존 package 를 삭제하고 다시 다운로드함
5. tmp file 로 package 를 다운로드함
6. 다운로드 파일이 비어 있으면 실패함
7. tmp file sha256 을 검증함
8. sha256 이 일치하면 downloads/{PACKAGE_NAME} 으로 mv 함
9. package file 을 600 권한으로 설정함

sha256 기준:

expected sha256 = desired state 의 package_sha256
actual sha256   = sha256sum 으로 계산한 tarball hash

운영 기준:

  • package 다운로드는 MinIO/S3 compatible API 로 수행함
  • public HTTP download 는 현재 스크립트 기준으로 허용하지 않음
  • sha256 검증 실패 시 deploy 를 중단함
  • sha256 검증 실패 시 current symlink 와 current-release-state 는 변경하지 않음
  • 다운로드된 package file 은 deploy 완료 후 cleanup 단계에서 삭제됨

10. Tar Archive Safety / Extract 기준

extract path:

/var/www/{SERVICE_NAME}/tmp/extract.{PACKAGE_VERSION}

tar list file:

/var/www/{SERVICE_NAME}/tmp/package-tar-list.{PACKAGE_VERSION}.txt

검증 기준:

tar -tzf 가능해야 함
archive 내부 path 가 absolute path 이면 실패함
archive 내부 path 에 .. path traversal 이 있으면 실패함
extract 결과가 비어 있으면 실패함

unsafe path 조건:

^/
(^|/)\.\.(/|$)

운영 기준:

  • tar archive 는 gzip tarball 기준임
  • archive safety 검증 실패 시 deploy 를 중단함
  • extract 실패 시 current symlink 와 current-release-state 는 변경하지 않음
  • 운영 서버는 extract 이후 build 를 수행하지 않음

11. Stage / Promote 기준

release path:

/var/www/{SERVICE_NAME}/releases/{PACKAGE_VERSION}

stage path:

/var/www/{SERVICE_NAME}/tmp/stage.{PACKAGE_VERSION}

동작 기준:

1. RELEASE_PATH 가 이미 존재하는지 확인함
2. RELEASE_PATH 가 존재하고 non-empty directory 이면 재사용함
3. RELEASE_PATH 가 존재하지만 유효한 non-empty directory 가 아니면 실패함
4. RELEASE_PATH 가 없으면 STAGE_PATH 를 새로 생성함
5. extract path 내용을 stage path 로 rsync 함
6. stage path 가 비어 있으면 실패함
7. stage path 권한을 u+rwX,g+rX,o-rwx 로 정리함
8. stage path 를 release path 로 mv 함

운영 기준:

  • release path 는 immutable release archive 로 취급함
  • 동일 package_version 의 release path 가 이미 정상 존재하면 재사용함
  • stage path 에서 준비 완료 후 release path 로 promote 함
  • release path 로 직접 extract 하지 않음
  • stage 준비 실패 시 current symlink 와 current-release-state 는 변경하지 않음

12. After-prepare-release Hook 기준

hook path:

/var/www/{SERVICE_NAME}/shared/poll-release-hooks/01-after-prepare-release.sh

실행 시점:

release package download 완료
sha256 검증 완료
tar safety 검증 완료
extract 완료
stage prepare 완료
release path 준비 완료
current symlink 변경 전

hook 에 전달되는 환경변수:

SERVICE_NAME
SERVICE_USER
SERVICE_GROUP
DEPLOY_BASE
RELEASE_ENV
PACKAGE_VERSION
RELEASE_PATH
CURRENT_LINK

운영 기준:

  • hook 은 서비스별 release 검증/후처리 확장 지점임
  • hook 이 executable 이면 실행함
  • hook 이 없거나 executable 이 아니면 skip 함
  • hook 실패 시 poll-release 는 실패함
  • hook 실패 시 current symlink 는 변경하지 않음
  • hook 내부 검증 항목은 서비스 runtime 특성에 따라 다르게 작성함
  • C++, PHP, Node.js, Node.js + Express, Next.js 등 서비스별 실행 가능성 검증은 hook 에서 수행하는 것을 기준으로 함

current link:

/var/www/{SERVICE_NAME}/current

temporary symlink:

/var/www/{SERVICE_NAME}/tmp/current.{PACKAGE_VERSION}.link

동작 기준:

1. release path 존재 여부 확인
2. release path non-empty 여부 확인
3. current 가 존재하면서 symlink 가 아니면 실패
4. 기존 current target 을 기록함
5. tmp 영역에 새 symlink 생성
6. mv -Tf 로 current symlink 를 atomic swap 함
7. current target 이 release path 와 일치하는지 검증함

activate 방식:

ln -s "$RELEASE_PATH" "$NEW_CURRENT_LINK"
mv -Tf "$NEW_CURRENT_LINK" "$CURRENT_LINK"

운영 기준:

  • ln -sfn 단독 사용이 아니라 temporary symlink + mv -Tf 기반으로 activate 함
  • current symlink 교체는 atomic rename 기반으로 수행함
  • current 가 일반 file/directory 이면 실패함
  • activate 실패 시 current-release-state 는 갱신하지 않음
  • 현재 스크립트는 activate 이후 PM2 실패 시 자동 rollback 을 수행하지 않음

14. Release Marker 기준

marker file:

/var/www/{SERVICE_NAME}/current/{PACKAGE_VERSION}

동작 기준:

1. current link 가 symlink 인지 확인함
2. current target 이 directory 인지 확인함
3. current/{PACKAGE_VERSION} 파일을 touch 함
4. marker file 권한을 600 으로 설정함

운영 기준:

  • release marker 는 해당 current target 이 어떤 package_version 으로 activate 되었는지 확인하기 위한 파일임
  • marker 작성 실패 시 poll-release 는 실패할 수 있음
  • marker 파일은 최종 검증 단계에서 확인 대상임

15. PM2 Delete / Start 기준

PM2 app name:

{SERVICE_NAME}

PM2 binary:

/usr/bin/pm2

PM2 ecosystem file:

/var/www/{SERVICE_NAME}/current/ecosystem.config.cjs

PM2 env:

production     -> production
pre-production -> pre_production

동작 기준:

1. 기존 PM2 app 을 delete 함
2. delete 대상이 없어도 실패로 보지 않음
3. ecosystem.config.cjs 기준으로 PM2 start 수행함
4. --env {PM2_ENV} 를 전달함
5. --update-env 를 사용함
6. pm2 save --force 를 수행함
7. pm2 status 를 출력함

실행 형태:

SERVICE_NAME="$SERVICE_NAME" \
/usr/bin/pm2 start "$CURRENT_LINK/ecosystem.config.cjs" \
  --env "$PM2_ENV" \
  --update-env

운영 기준:

  • 현재 스크립트는 ecosystem.config.cjs 만 지원함
  • 현재 스크립트는 ecosystem.config.js fallback 을 수행하지 않음
  • PM2 start 실패 시 poll-release 는 실패함
  • PM2 start 실패 시 current-release-state 는 갱신하지 않음
  • 현재 스크립트는 PM2 start 실패 시 자동 rollback 을 수행하지 않음
  • framework-specific 실행 가능성 검증은 after-prepare hook 에서 수행함

16. Current Release State 갱신 기준

state file:

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

temporary state file:

/var/www/{SERVICE_NAME}/tmp/current-release-state.{PACKAGE_VERSION}.tmp.json

갱신 방식:

1. desired state file 존재 여부 확인
2. desired state JSON parse 가능 여부 확인
3. desired state file 을 tmp state file 로 복사
4. tmp state file 을 current-release-state.json 으로 mv
5. current-release-state.json 권한을 600 으로 설정

운영 기준:

  • current-release-state 는 desired state JSON 원본을 복사하여 저장함
  • current-release-state 는 PM2 reload/start 성공 이후에만 갱신함
  • metadata 검증 실패 시 갱신하지 않음
  • package download 실패 시 갱신하지 않음
  • sha256 검증 실패 시 갱신하지 않음
  • extract/stage 실패 시 갱신하지 않음
  • hook 실패 시 갱신하지 않음
  • activate 실패 시 갱신하지 않음
  • PM2 start 실패 시 갱신하지 않음

17. Cleanup 기준

기본 retention:

KEEP_RELEASES=3

환경변수 override:

KEEP_RELEASES={integer >= 1}

release cleanup 기준:

1. KEEP_RELEASES 값이 정수인지 확인함
2. KEEP_RELEASES 값이 1 이상인지 확인함
3. current target 을 readlink -f 로 확인함
4. current target 이 releases path 내부인지 검증함
5. releases 하위 directory 를 sort -r 기준으로 순회함
6. current target 은 항상 keep 함
7. current target 외 release directory 는 keep count 기준으로 보존/삭제함

downloads cleanup 기준:

/var/www/{SERVICE_NAME}/downloads 하위 항목 전체 삭제

tmp cleanup 기준:

/var/www/{SERVICE_NAME}/tmp 하위 항목 전체 삭제

운영 기준:

  • current target 은 삭제하지 않음
  • cleanup 대상 release path 가 releases path 밖이면 실패함
  • downloads path 가 $DEPLOY_BASE/downloads 가 아니면 실패함
  • tmp path 가 $DEPLOY_BASE/tmp 가 아니면 실패함
  • previous rollback target 을 별도로 보호하지 않음
  • 현재 스크립트는 local rollback 기능이 없으므로 rollback target retention 도 별도 수행하지 않음
  • cleanup 은 deploy 성공 이후 수행함

18. Skip / Failure 처리 기준

skip 조건:

.env.{RELEASE_ENV} 파일이 아직 준비되지 않음
poll-release lock 획득 실패
desired state 의 package_version 이 비어 있음
current-release-state.json 의 package_version 과 desired package_version 이 동일함

skip 결과:

current symlink 변경 없음
current-release-state.json 변경 없음
PM2 reload/start 없음
cleanup 없음
exit 0

activate 전 실패 시 영향:

current symlink 변경 없음
current-release-state.json 갱신 없음
PM2 reload/start 없음

activate 후 PM2 이전 실패 시 영향:

current symlink 는 새 release 를 가리킬 수 있음
current-release-state.json 갱신 없음
PM2 reload/start 안 됨
수동 확인 필요

PM2 실패 시 영향:

current symlink 는 새 release 를 가리킬 수 있음
current-release-state.json 갱신 없음
PM2 app start 실패 상태 가능
자동 rollback 없음
수동 복구 필요 가능

운영 기준:

  • metadata download 실패 시 current release 유지
  • metadata validation 실패 시 current release 유지
  • package download 실패 시 current release 유지
  • sha256 검증 실패 시 current release 유지
  • tar safety 검증 실패 시 current release 유지
  • extract 실패 시 current release 유지
  • stage prepare 실패 시 current release 유지
  • after-prepare hook 실패 시 current release 유지
  • current symlink activate 실패 시 current-release-state 갱신 금지
  • PM2 start 실패 시 current-release-state 갱신 금지
  • 현재 스크립트는 자동 health verify 를 수행하지 않음
  • 현재 스크립트는 자동 local rollback 을 수행하지 않음

19. 운영 확인 절차

빠른 상태 확인:

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}

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

20. 수동 실행 절차

timer 중지:

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

runtime directory 이동:

cd /var/www/{SERVICE_NAME}

수동 실행:

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

운영 기준:

  • 수동 실행 시에도 root 로 실행하지 않음
  • 수동 실행 시에도 sudo -u {SERVICE_USER} -H env ... 형태로 실행함
  • cd /var/www/{SERVICE_NAME} 이후 실행해야 함
  • RELEASE_ENV 는 bootstrap 으로 생성된 systemd service 환경과 동일한 값을 사용함

21. 수동 복구 기준

현재 스크립트는 자동 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}

22. 보안 기준

운영 기준:

  • 운영 서버는 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 경로 하위만 삭제함

23. 결론

poll-release.sh 는 Release Storage 의 desired state 를 기준으로 운영 서버가 self-pull deploy 를 수행하는 runtime script 임.

운영 서버는 GitLab 에 직접 접근하지 않고, Release Storage 에 publish 된 current metadata 를 MinIO/S3 compatible API 로 조회함.

신규 package_version 이 감지되면 release package 를 다운로드하고, sha256 검증, tar safety 검증, extract, stage, release promote, after-prepare hook, current symlink activate, PM2 delete/start/save, current-release-state 갱신, cleanup, 최종 검증 순서로 동작함.

본 구조는 immutable release archive 와 current symlink 기반 activate 를 전제로 함.

현재 스크립트는 PM2 process manager 기준이며, ecosystem.config.cjs 를 사용함.

현재 스크립트는 별도 HTTP health verify 와 local rollback 을 수행하지 않음.