콘텐츠로 이동

GitLab Ops Server Bootstrap

0. 전제

  • 운영 서버가 GitLab CI/CD + Release Storage + Polling Deploy 구조에 참여하기 전, CD runtime bootstrap 을 수행하는 절차를 정의함.
  • 운영 서버는 polling deploy 를 수행하기 전에 runtime directory, shared env, deploy lock, current-release-state, runtime script, service hook, systemd unit, logrotate 설정이 먼저 준비되어야 함.

bootstrap 흐름:

운영 서버 초기 준비
        ▼
root 실행 여부 확인
        ▼
service env 로드 (.ops/cd/env/.service.env)
        ▼
SERVICE_USER / SERVICE_GROUP / SERVICE_NAME 필수값 및 식별자 검증
        ▼
RELEASE_ENV 필수값 및 허용값 검증 (production / pre-production)
        ▼
runtime path 변수 정의
        ▼
service user/group 존재 확인
        ▼
source runtime script 확인 (.ops/cd/opt/bin/poll-release.sh)
        ▼
release storage env 확인 (.ops/cd/env/.release-storage.env)
        ▼
PM2 절대 경로 확인 (/usr/bin/pm2)
        ▼
필수 패키지 설치 및 실행 파일 확인
        ▼
기존 timer 중지 및 실행 중인 service 확인
        ▼
runtime directory 생성 (/var/www/{SERVICE_NAME}/*)
        ▼
기본 ownership / 권한 고정
        ▼
deploy.lock 준비 및 bootstrap lock 획득
        ▼
shared env 파일 복사 (.service.env / .release-storage.env)
        ▼
poll-release hook 동기화
        ▼
current-release-state.json 초기화
        ▼
poll-release.log 초기화
        ▼
current symlink 상태 확인
        ▼
poll-release runtime script 설치 (/opt/bin/poll-release.{SERVICE_NAME}.sh)
        ▼
systemd service/timer 생성 (/etc/systemd/system)
        ▼
logrotate 설정 생성 (/etc/logrotate.d/release-poll.{SERVICE_NAME})
        ▼
SELinux context restore (restorecon)
        ▼
systemd daemon-reload
        ▼
systemd timer enable/restart
        ▼
bootstrap 결과 검증
        ▼
polling deploy 가능 상태 진입

운영 기준:

  • bootstrap.sh 는 운영 서버 최초 설치 entrypoint 로 사용함
  • bootstrap.sh 는 runtime provisioning 만 수행함
  • bootstrap source of truth 는 .ops/cd/bootstrap.sh 이며, 운영 서버의 systemd unit 파일을 수동 수정하지 않음
  • GitLab CI/CD 의 release target branch 는 .ops/ci/release-branch-versions/{branch}.yaml 기준으로 확장 가능함.

1. Project Operations Structure

  • 본 프로젝트는 GitLab CI/CD 와 운영 서버 polling deploy 구조를 분리하여 관리함.

각 Project 의 .ops 디렉토리는 아래 두 영역으로 구성됨.

.ops/
├── cd/
└── ci/

설명:

  • cd

    • 운영 서버 runtime/deploy layer 관리
    • 운영 서버 최초 bootstrap installer 관리
    • polling deploy runtime script 관리
    • 서비스별 poll-release hook 관리
    • Release Storage 접속 설정 관리
  • ci

    • GitLab pipeline orchestration 관리
    • build/package/release-state publish 관리
    • 환경 branch 별 release version 선언 관리

2. CD Runtime Overlay

  • .ops/cd 는 운영 서버 runtime 을 구성하기 위한 원본(source of truth) 역할을 수행함.
  • .ops/cd/opt/bin/poll-release.sh 는 운영 서버에 아래 경로로 설치됨.
/opt/bin/poll-release.{SERVICE_NAME}.sh
  • .ops/cd/env 의 env 파일은 bootstrap 시 운영 runtime 의 shared 영역으로 복사됨.
.ops/cd/env/.service.env
.ops/cd/env/.release-storage.env
        ▼
/var/www/{SERVICE_NAME}/shared/.service.env
/var/www/{SERVICE_NAME}/shared/.release-storage.env

.ops/cd/poll-release-hooks 의 hook 파일은 bootstrap 시 운영 runtime 의 shared hook 영역으로 동기화됨.

.ops/cd/poll-release-hooks/
        ▼
/var/www/{SERVICE_NAME}/shared/poll-release-hooks/

운영 기준:

  • .ops/cd/bootstrap.sh 는 설치 entrypoint 임
  • .ops/cd/opt/bin/poll-release.sh 는 runtime script source 임
  • .ops/cd/poll-release-hooks 는 서비스별 release 검증/후처리 hook source 임
  • 운영 서버에서 생성된 systemd unit, logrotate config, runtime script 는 bootstrap 결과물임

3. Project Repository 구조

확정 구조:

Project root
├── .ops/
│   ├── cd/
│   │   ├── env/
│   │   │   ├── .release-storage.env
│   │   │   └── .service.env
│   │   ├── opt/
│   │   │   └── bin/
│   │   │       └── poll-release.sh
│   │   ├── poll-release-hooks/
│   │   │   └── 01-after-prepare-release.sh
│   │   └── bootstrap.sh
│   └── ci/
│       ├── release-branch-versions/
│       │   ├── pre-production.yaml
│       │   ├── production.yaml
│       │   └── {release-target-branch}.yaml
│       ├── scripts/
│       │   ├── 01-detect-version-change.sh
│       │   ├── 02-build.sh
│       │   ├── 03-package.sh
│       │   └── 04-update-release.sh
│       └── set-release-state-pipeline.yaml
└── .gitlab-ci.yml

4. CD env 파일 구조

  • .ops/cd/env 의 env 파일은 bootstrap source env 파일임
  • bootstrap 실행 시 .service.env.release-storage.env 는 운영 runtime 의 shared 영역으로 복사됨
  • runtime shared env 파일은 {SERVICE_USER}:{SERVICE_GROUP} 소유, 600 권한으로 관리함

4.1 .service.env

경로:

.ops/cd/env/.service.env

변수:

  • SERVICE_USER: poll-release runtime 실행 사용자이며 runtime 파일 소유자 기준으로 사용함
  • SERVICE_GROUP: poll-release runtime 실행 group이며 runtime 파일 소유 group 기준으로 사용함
  • SERVICE_NAME: /var/www/{SERVICE_NAME} runtime root, systemd unit name, PM2 app name 구성에 사용함
SERVICE_USER={SERVICE_USER}
SERVICE_GROUP={SERVICE_GROUP}
SERVICE_NAME={SERVICE_NAME}

허용 문자 기준:

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

4.2 .release-storage.env

  • MinIO/S3 호환 Release Storage 접속 정보 정의
  • release state metadata 조회 위치 계산 기준 제공
  • release package 다운로드 기준 제공
  • MinIO 에서 IAM User 생성 후 해당 계정의 Access Key 를 생성하면 만들어지는 정보를 그대로 사용.
  • AWS S3 등 Cloud Service 에서 제공하는 S3 제품과 호환됨.

경로:

.ops/cd/env/.release-storage.env
MINIO_ENDPOINT=https://release.{ReleaseStorageUrl}
MINIO_REGION=ap-northeast-2
MINIO_ACCESS_KEY={MINIO_ACCESS_KEY}
MINIO_SECRET_KEY={MINIO_SECRET_KEY}
MINIO_BUCKET={PROJECT_NAME}
MINIO_SERVICE_PREFIX={SERVICE_PREFIX}/{SERVICE_NAME}

5. 운영 서버 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 root 는 /var/www/{SERVICE_NAME} 기준으로 통일함
  • shared 는 mutable runtime state/env 영역임
  • releases 는 immutable release archive 영역임
  • downloads 는 release package download 임시 보관 영역임
  • tmp 는 desired state, extract, stage 등 작업 영역임
  • logs 는 poll-release file log 영역임
  • deploy.lock 은 bootstrap 과 poll-release 동시 실행을 직렬화함
  • current 는 활성 release directory 를 가리키는 symlink 임

권한 기준:

/var/www/{SERVICE_NAME}                  755 {SERVICE_USER}:{SERVICE_GROUP}
/var/www/{SERVICE_NAME}/releases         755 {SERVICE_USER}:{SERVICE_GROUP}
/var/www/{SERVICE_NAME}/shared           700 {SERVICE_USER}:{SERVICE_GROUP}
/var/www/{SERVICE_NAME}/downloads        750 {SERVICE_USER}:{SERVICE_GROUP}
/var/www/{SERVICE_NAME}/tmp              750 {SERVICE_USER}:{SERVICE_GROUP}
/var/www/{SERVICE_NAME}/logs             750 {SERVICE_USER}:{SERVICE_GROUP}
/var/www/{SERVICE_NAME}/deploy.lock      600 {SERVICE_USER}:{SERVICE_GROUP}

shared/.service.env                      600 {SERVICE_USER}:{SERVICE_GROUP}
shared/.release-storage.env              600 {SERVICE_USER}:{SERVICE_GROUP}
shared/current-release-state.json        600 {SERVICE_USER}:{SERVICE_GROUP}
logs/poll-release.log                    600 {SERVICE_USER}:{SERVICE_GROUP}

6. bootstrap.sh 역할

  • bootstrap.sh 는 운영 서버 최초 설치 entrypoint 임.
  • 운영 서버를 polling deploy 가능한 상태로 만드는 installer 역할을 수행함.
  • bootstrap 단계는 provisioning/runtime preparation 에 집중함.

상세 흐름:

1.  root 실행 여부 확인
2.  .service.env 로드
3.  SERVICE_USER / SERVICE_GROUP / SERVICE_NAME 필수값 확인
4.  SERVICE_USER / SERVICE_GROUP / SERVICE_NAME 식별자 검증
5.  RELEASE_ENV 필수값 및 허용값 확인
6.  runtime path 변수 정의
7.  service user 존재 확인
8.  service group 존재 확인
9.  source poll-release.sh 파일 확인
10. PM2 절대 경로 확인
11. .release-storage.env source 파일 확인
12. 필수 패키지 설치
13. 필수 실행 파일 확인
14. 기존 timer 중지
15. 실행 중인 release-poll service 확인
16. runtime directory 생성
17. deploy.lock 준비
18. bootstrap lock 획득
19. shared env 파일 복사
20. poll-release hook 동기화
21. current-release-state.json 초기화
22. poll-release.log 초기화
23. current symlink 상태 확인
24. poll-release runtime script 설치
25. systemd service/timer 생성
26. logrotate config 생성
27. SELinux context restore
28. systemd daemon-reload
29. timer enable
30. timer restart
31. bootstrap 결과 검증

주의:

  • bootstrap 은 release-poll.{SERVICE_NAME}.timer 를 중지한 뒤 runtime 파일과 unit 을 재생성함
  • release-poll.{SERVICE_NAME}.service 가 실행 중이면 bootstrap 을 중단함
  • bootstrap 은 systemd unit 을 항상 source of truth 기준으로 재생성함
  • 운영 서버에서 unit 파일을 수동 수정하지 않음
  • runtime script 실행 사용자는 systemd service 의 User={SERVICE_USER} 기준임
  • SELinux context restore 는 restorecon 명령이 존재하는 환경에서 수행하며, restorecon 이 없으면 skip 함

7. Runtime Script 설치 구조

설치 규칙:

source    : .ops/cd/opt/bin/poll-release.sh
installed : /opt/bin/poll-release.{SERVICE_NAME}.sh
owner     : root:root
mode      : 755

8. Poll-release Hook 설치 구조

  • poll-release hook 은 서비스별 release 검증/후처리 확장 지점임.
  • hook source directory 가 없으면 bootstrap 은 hook 동기화를 skip 함

설치 규칙:

source    : .ops/cd/poll-release-hooks/
installed : /var/www/{SERVICE_NAME}/shared/poll-release-hooks/
owner     : {SERVICE_USER}:{SERVICE_GROUP}
mode      : 700

8.1 01-after-prepare-release.sh

  • release package extract 및 release path 준비가 완료된 뒤 실행되는 서비스별 검증 hook 임
  • current symlink 변경 전에 새 release directory 가 서비스 실행 가능한 상태인지 검증함
  • hook 실패 시 release 활성화와 PM2 reload 는 진행되지 않으며 기존 current 상태가 유지됨
  • hook 내부 검증 항목은 C++, PHP, Node.js, Node.js + Express, Next.js 등 각 서비스 runtime 특성에 따라 다르게 작성함

9. systemd Unit 정의

  • 운영 서버 polling deploy 는 systemd timer 가 주기적으로 service 를 실행하는 구조임.

구성 파일:

/etc/systemd/system/release-poll.{SERVICE_NAME}.service
/etc/systemd/system/release-poll.{SERVICE_NAME}.timer

9.1 release-poll.{SERVICE_NAME}.service

  • service 는 timer 에 의해 주기 실행되며 resident daemon 으로 동작하지 않음
[Unit]
Description=Release polling deploy service ({SERVICE_NAME})
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
TimeoutStartSec=300

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}

StandardOutput=journal
StandardError=journal
SyslogIdentifier=release-poll.{SERVICE_NAME}

PrivateTmp=true
NoNewPrivileges=true

ExecStart=/opt/bin/poll-release.{SERVICE_NAME}.sh

9.2 release-poll.{SERVICE_NAME}.timer

  • RandomizedDelaySec=15s 를 사용하여 다중 서버 polling 시점을 분산함
  • Persistent=true 를 사용하여 timer missed event 를 보정함
  • timer enable/start 대상은 service 가 아니라 timer 임
[Unit]
Description=Run Release polling deploy periodically ({SERVICE_NAME})

[Timer]
OnBootSec=1m
OnUnitActiveSec=1m

RandomizedDelaySec=15s
AccuracySec=5s
Persistent=true

Unit=release-poll.{SERVICE_NAME}.service

[Install]
WantedBy=timers.target

10. Shared 환경 파일 정책

  • shared 환경 파일은 운영 runtime 에서 poll-release 와 application 실행에 필요한 mutable env 영역임.
  • bootstrap.sh 는 shared 환경 파일 존재 여부를 검사하지 않음
  • poll-release.{SERVICE_NAME}.sh 내부에서 runtime validation 시 검사 수행
  • 실행 환경에 대응하는 .env.{RELEASE_ENV} 파일이 없으면 polling cycle 을 수행하지 않음

10.1 bootstrap 이 관리하는 파일

경로:

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

설치 규칙:

source    : .ops/cd/env/
installed : /var/www/{SERVICE_NAME}/shared/
owner     : {SERVICE_USER}:{SERVICE_GROUP}
mode      : 디렉토리 : 700, 파일 : 600

10.2 운영 서버에서 별도로 관리하는 파일

  • Bootstrap 완료 직후 운영 서버는 polling deploy 수행을 위해 shared 디렉토리에 실행 환경별 application env 파일을 수동 배치해야 함.
/var/www/{SERVICE_NAME}/shared/.env.{RELEASE_ENV}
source    : 운영 서버별 application env 파일 또는 별도 env template
installed : /var/www/{SERVICE_NAME}/shared/
owner     : {SERVICE_USER}:{SERVICE_GROUP}
mode      : 디렉토리 : 700, 파일 : 600

10.2.1 .env.{RELEASE_ENV}

용도:

  • application runtime 환경변수
  • PM2 실행 시 application 에 전달되는 환경변수
  • 실행 환경별 application 설정 분리

운영 기준:

  • .env.{RELEASE_ENV} 파일은 poll-release runtime 이 실행 환경별로 source 하는 application env 파일임
  • 파일명은 systemd service 의 Environment=RELEASE_ENV 값을 기준으로 결정됨
  • .env.{RELEASE_ENV} 파일이 없으면 poll-release cycle 은 skip 됨
  • .env.{RELEASE_ENV} 안에서 RELEASE_ENV 를 선언하는 경우, systemd service 의 Environment=RELEASE_ENV 값과 반드시 일치해야 함
  • 서비스 runtime 종류에 따라 필요한 application env 값은 다르게 정의함

예시:

# =============================================================================
# Application Runtime
# =============================================================================
NODE_ENV=production

10.3 권한 명령어

sudo chown {SERVICE_USER}:{SERVICE_GROUP} /var/www/{SERVICE_NAME}/shared/{Files}
sudo chmod 700 /var/www/{SERVICE_NAME}/shared
sudo chmod 600 /var/www/{SERVICE_NAME}/shared/{Files}

11. Release Storage 설정 정책

  • Release Storage 는 GitLab CI 가 publish 한 desired release state 와 release package 를 운영 서버가 polling 하기 위한 MinIO/S3 호환 저장소임.
  • Release Storage 접속 정보는 .ops/cd/env/.release-storage.env 에 정의함.
  • bootstrap 실행 시 .release-storage.env 는 운영 runtime 의 shared 영역으로 복사됨.
  • poll-release runtime 은 /var/www/{SERVICE_NAME}/shared/.release-storage.env 를 source 하여 desired release state 와 release package 를 조회함.

관련 설정 파일:

.ops/cd/env/.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

운영 기준:

  • MINIO_ENDPOINT 는 MinIO/S3 compatible API endpoint 임
  • MINIO_REGION 은 S3 client region 값임
  • MINIO_ACCESS_KEYMINIO_SECRET_KEY 는 Release Storage 접근용 credential 임
  • MINIO_BUCKET 은 release state metadata 와 release package 가 저장되는 bucket 임
  • MINIO_SERVICE_PREFIX 는 bucket 내부에서 서비스별 release object 를 구분하기 위한 prefix 임
  • MINIO_SERVICE_PREFIX 는 Release Storage object prefix 전체값이며 {SERVICE_PREFIX}/{SERVICE_NAME} 형식을 사용함
  • GitLab 문서 변수 SERVICE_PREFIXfrontend, backend, infra 같은 단독 분류값임
  • 예: SERVICE_PREFIX=frontend, SERVICE_NAME=example.com, MINIO_SERVICE_PREFIX=frontend/example.com

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

Release Storage endpoint 예시:

MINIO_ENDPOINT=https://release.{ReleaseStorageUrl}

중요:

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

12. Logrotate 정의

bootstrap.sh 는 polling runtime log file rotation 을 위해 아래 파일을 생성함.

/etc/logrotate.d/release-poll.{SERVICE_NAME}

대상 로그:

/var/www/{SERVICE_NAME}/logs/*.log

logrotate 정책:

daily
rotate 14
compress
delaycompress
missingok
notifempty
copytruncate
create 600 {SERVICE_USER} {SERVICE_GROUP}

운영 기준:

  • polling runtime log 는 /var/www/{SERVICE_NAME}/logs/poll-release.log 기준 사용
  • runtime log file 은 600 권한 기준 사용
  • poll-release runtime 은 oneshot script 이므로 reopen signal 대신 copytruncate 사용함
  • logrotate config 는 Git 관리 대상이 아니라 bootstrap generated runtime artifact 임

13. 필수 패키지 설치 및 실행 파일 확인

  • bootstrap.sh 는 필수 패키지를 설치한 뒤 실행 파일 존재 여부를 확인함.

설치 패키지:

sudo dnf install -y curl jq tar gzip coreutils util-linux rsync logrotate awscli

for cmd in curl jq tar sha256sum systemctl logrotate flock rsync aws; do
  command -v "$cmd" >/dev/null 2>&1 || {
    echo "missing: $cmd"
    exit 1
  }
done

추가 확인 대상:

sudo ls -l /usr/bin/pm2

운영 기준:

  • PM2 는 /usr/bin/pm2 기준으로 사용함
  • aws 명령은 MinIO/S3 호환 Release Storage 에서 desired state 와 package 를 가져오기 위해 필요함
  • flock 은 bootstrap 과 poll-release 동시 실행 방지를 위해 필요함

14. Bootstrap 실행 전 준비

  • 운영 서버에 .ops/cd 작업 디렉토리를 배치함.
cd /home/{SERVICE_USER}/.ops/cd

권한 정리:

SERVICE_USER={SERVICE_USER}
SERVICE_GROUP={SERVICE_GROUP}

SERVICE_USER_HOME="/home/$SERVICE_USER"
OPERATIONS_PATH="$SERVICE_USER_HOME/.ops"
CD_PATH="$OPERATIONS_PATH/cd"

BOOTSTRAP_SCRIPT="$CD_PATH/bootstrap.sh"
POLL_RELEASE_SCRIPT="$CD_PATH/opt/bin/poll-release.sh"
HOOKS_PATH="$CD_PATH/poll-release-hooks"
ENV_PATH="$CD_PATH/env"
SERVICE_ENV_FILE="$ENV_PATH/.service.env"
RELEASE_STORAGE_ENV_FILE="$ENV_PATH/.release-storage.env"

# 기본 경로 확인
sudo test -d "$OPERATIONS_PATH"
sudo test -d "$CD_PATH"
sudo test -f "$BOOTSTRAP_SCRIPT"
sudo test -f "$POLL_RELEASE_SCRIPT"
sudo test -d "$ENV_PATH"
sudo test -f "$SERVICE_ENV_FILE"
sudo test -f "$RELEASE_STORAGE_ENV_FILE"

# 소유자 정리
sudo chown -R "$SERVICE_USER":"$SERVICE_GROUP" "$OPERATIONS_PATH"

# 기본 권한 정리
sudo find "$OPERATIONS_PATH" -type d -exec chmod 755 {} \;
sudo find "$OPERATIONS_PATH" -type f -exec chmod 644 {} \;

# 실행 스크립트 권한
sudo chmod 755 "$BOOTSTRAP_SCRIPT"
sudo chmod 755 "$POLL_RELEASE_SCRIPT"

if [ -d "$HOOKS_PATH" ]; then
  sudo find "$HOOKS_PATH" -type d -exec chmod 755 {} \;
  sudo find "$HOOKS_PATH" -type f -exec chmod 755 {} \;
fi

# 민감 env 권한
sudo chmod 700 "$ENV_PATH"
sudo chmod 600 "$SERVICE_ENV_FILE"
sudo chmod 600 "$RELEASE_STORAGE_ENV_FILE"

# BOM/CRLF 제거
sudo sed -i '1s/^\xEF\xBB\xBF//' "$BOOTSTRAP_SCRIPT"
sudo sed -i '1s/^\xEF\xBB\xBF//' "$POLL_RELEASE_SCRIPT"
sudo sed -i 's/\r$//' "$BOOTSTRAP_SCRIPT"
sudo sed -i 's/\r$//' "$POLL_RELEASE_SCRIPT"

if [ -d "$HOOKS_PATH" ]; then
  sudo find "$HOOKS_PATH" -type f -name '*.sh' -exec sed -i '1s/^\xEF\xBB\xBF//' {} \;
  sudo find "$HOOKS_PATH" -type f -name '*.sh' -exec sed -i 's/\r$//' {} \;
fi

# sed -i 이후 소유자/권한 재확정
sudo chown -R "$SERVICE_USER":"$SERVICE_GROUP" "$OPERATIONS_PATH"

sudo find "$OPERATIONS_PATH" -type d -exec chmod 755 {} \;
sudo find "$OPERATIONS_PATH" -type f -exec chmod 644 {} \;

sudo chmod 755 "$BOOTSTRAP_SCRIPT"
sudo chmod 755 "$POLL_RELEASE_SCRIPT"

if [ -d "$HOOKS_PATH" ]; then
  sudo find "$HOOKS_PATH" -type d -exec chmod 755 {} \;
  sudo find "$HOOKS_PATH" -type f -exec chmod 755 {} \;
fi

sudo chmod 700 "$ENV_PATH"
sudo chmod 600 "$SERVICE_ENV_FILE"
sudo chmod 600 "$RELEASE_STORAGE_ENV_FILE"

# 문법 확인
sudo -u "$SERVICE_USER" bash -n "$BOOTSTRAP_SCRIPT"
sudo -u "$SERVICE_USER" bash -n "$POLL_RELEASE_SCRIPT"

if [ -d "$HOOKS_PATH" ]; then
  sudo -u "$SERVICE_USER" find "$HOOKS_PATH" -type f -name '*.sh' -exec bash -n {} \;
fi

15. Bootstrap 실행

  • 운영 서버 최초 구성 시 bootstrap.sh 를 실행하여 runtime bootstrap 을 수행함.

production 기준:

cd /home/{SERVICE_USER}/.ops/cd
sudo RELEASE_ENV=production bash /home/{SERVICE_USER}/.ops/cd/bootstrap.sh

pre-production 기준:

cd /home/{SERVICE_USER}/.ops/cd
sudo RELEASE_ENV=pre-production bash /home/{SERVICE_USER}/.ops/cd/bootstrap.sh

주의:

  • RELEASE_ENV 는 필수임
  • bootstrap 은 실행된 RELEASE_ENV 기준으로 systemd service 환경변수를 생성함
  • 동일 서버에서 다른 release env 로 재구성할 경우 bootstrap 을 해당 RELEASE_ENV 값으로 다시 실행해야 함

16. Bootstrap 결과 확인

16.1 Runtime Script / systemd Unit 확인

# runtime script
sudo ls -l /opt/bin/poll-release.{SERVICE_NAME}.sh

# systemd unit
sudo ls -l /etc/systemd/system/release-poll.{SERVICE_NAME}.service
sudo ls -l /etc/systemd/system/release-poll.{SERVICE_NAME}.timer

예상 결과:

# runtime script
-rwxr-xr-x root root /opt/bin/poll-release.{SERVICE_NAME}.sh

# systemd units
-rw-r--r-- root root /etc/systemd/system/release-poll.{SERVICE_NAME}.service
-rw-r--r-- root root /etc/systemd/system/release-poll.{SERVICE_NAME}.timer

16.2 Runtime Directory 확인

# runtime directory
sudo ls -ld /var/www/{SERVICE_NAME}

# releases
sudo ls -ld /var/www/{SERVICE_NAME}/releases

# shared
sudo ls -ld /var/www/{SERVICE_NAME}/shared

# downloads
sudo ls -ld /var/www/{SERVICE_NAME}/downloads

# tmp
sudo ls -ld /var/www/{SERVICE_NAME}/tmp

# logs
sudo ls -ld /var/www/{SERVICE_NAME}/logs

예상 결과:

drwxr-xr-x {SERVICE_USER} {SERVICE_GROUP} /var/www/{SERVICE_NAME}
drwxr-xr-x {SERVICE_USER} {SERVICE_GROUP} /var/www/{SERVICE_NAME}/releases
drwx------ {SERVICE_USER} {SERVICE_GROUP} /var/www/{SERVICE_NAME}/shared
drwxr-x--- {SERVICE_USER} {SERVICE_GROUP} /var/www/{SERVICE_NAME}/downloads
drwxr-x--- {SERVICE_USER} {SERVICE_GROUP} /var/www/{SERVICE_NAME}/tmp
drwxr-x--- {SERVICE_USER} {SERVICE_GROUP} /var/www/{SERVICE_NAME}/logs

16.3 Shared 파일 확인

sudo ls -la /var/www/{SERVICE_NAME}/shared

예상 결과:

-rw------- {SERVICE_USER} {SERVICE_GROUP} .service.env
-rw------- {SERVICE_USER} {SERVICE_GROUP} .release-storage.env
-rw------- {SERVICE_USER} {SERVICE_GROUP} current-release-state.json
drwx------ {SERVICE_USER} {SERVICE_GROUP} poll-release-hooks

16.4 Poll-release Hook 확인

sudo ls -la /var/www/{SERVICE_NAME}/shared/poll-release-hooks

예상 결과:

-rwx------ {SERVICE_USER} {SERVICE_GROUP} 01-after-prepare-release.sh

16.5 deploy.lock 확인

sudo ls -l /var/www/{SERVICE_NAME}/deploy.lock

예상 결과:

-rw------- {SERVICE_USER} {SERVICE_GROUP} /var/www/{SERVICE_NAME}/deploy.lock

16.6 Polling Log 확인

sudo ls -l /var/www/{SERVICE_NAME}/logs/poll-release.log

예상 결과:

-rw------- {SERVICE_USER} {SERVICE_GROUP} /var/www/{SERVICE_NAME}/logs/poll-release.log

16.7 Logrotate 확인

sudo ls -l /etc/logrotate.d/release-poll.{SERVICE_NAME}
sudo logrotate -d /etc/logrotate.d/release-poll.{SERVICE_NAME}

예상 결과:

-rw-r--r-- root root /etc/logrotate.d/release-poll.{SERVICE_NAME}

16.8 systemd service 환경 확인

sudo systemctl cat release-poll.{SERVICE_NAME}.service --no-pager | grep -E 'WorkingDirectory=|Environment=|ExecStart='

확인 대상:

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

RELEASE_ENV 는 bootstrap 실행 시 지정한 값과 일치해야 함.

16.9 systemd timer 등록 및 상태 확인

  • polling service 는 Type=oneshot 기준이므로 polling 종료 후 inactive (dead) 상태가 정상일 수 있음.
# timer 등록 확인
sudo systemctl list-timers | grep release-poll

# timer 상태 확인
sudo systemctl status release-poll.{SERVICE_NAME}.timer -n 100 --no-pager

# service 상태 확인
sudo systemctl status release-poll.{SERVICE_NAME}.service -n 100 --no-pager

예상 결과:

# timer 등록 확인
release-poll.{SERVICE_NAME}.timer loaded active waiting

# service 등록 확인
release-poll.{SERVICE_NAME}.service loaded inactive dead

# timer 상태
Loaded: loaded
Active: active (waiting)

# service 상태
Loaded: loaded
Active: inactive (dead)

16.10 Journal / Polling Log 조회

# unit 기준 조회
sudo journalctl -u release-poll.{SERVICE_NAME}.service -n 100 --no-pager

# SyslogIdentifier 기준 조회
sudo journalctl -t release-poll.{SERVICE_NAME} -n 100 --no-pager

# file log 기준 조회
sudo tail -n 100 /var/www/{SERVICE_NAME}/logs/poll-release.log

실시간 확인:

sudo journalctl -u release-poll.{SERVICE_NAME}.service -f
sudo tail -F /var/www/{SERVICE_NAME}/logs/poll-release.log

17. Bootstrap 이후 수동 poll-release 검증

  • bootstrap 이후 timer 를 잠시 중지하고 poll-release runtime 을 수동 실행하여 검증할 수 있음.
sudo systemctl stop release-poll.{SERVICE_NAME}.timer

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

18. 장애 대응 기준

18.1 bootstrap 실행 계정 오류

증상:

bootstrap.sh must be run as root

대응:

sudo RELEASE_ENV=production bash /home/{SERVICE_USER}/.ops/cd/bootstrap.sh

18.2 RELEASE_ENV 누락

증상:

Missing RELEASE_ENV

대응:

sudo RELEASE_ENV=production bash /home/{SERVICE_USER}/.ops/cd/bootstrap.sh
sudo RELEASE_ENV=pre-production bash /home/{SERVICE_USER}/.ops/cd/bootstrap.sh

18.3 release-poll service 실행 중 bootstrap 시도

증상:

Release service is currently running

대응:

sudo systemctl status release-poll.{SERVICE_NAME}.service -n 100 --no-pager
sudo journalctl -u release-poll.{SERVICE_NAME}.service -n 100 --no-pager

현재 polling cycle 이 종료된 뒤 bootstrap 을 재실행함.

증상:

/var/www/{SERVICE_NAME}/current exists but is not a symlink

대응:

sudo ls -la /var/www/{SERVICE_NAME}/current

current 는 release directory 를 가리키는 symlink 여야 함. 일반 directory/file 로 존재하면 bootstrap 이 중단됨.

18.5 PM2 누락

증상:

Missing pm2 executable: /usr/bin/pm2

대응:

sudo npm install -g pm2
sudo which pm2
sudo ls -l /usr/bin/pm2