콘텐츠로 이동

GitLab Ops Server Bootstrap Runbook (운영 기준)

0. 전제

  • 23-GitLab Ops Server Bootstrap 기준으로 정의된 bootstrap 구조를 운영 서버에 적용하는 실행 절차를 정의함.
  • bootstrap.sh 는 운영 서버를 polling deploy 가능한 runtime 상태로 구성하는 installer 임.
  • 운영자는 root shell 로 접속하지 않고, root 권한이 필요한 명령은 sudo 로 실행함.

bootstrap 전체 흐름:

운영 서버 초기 준비
        ▼
.ops/cd source 파일 확인
        ▼
source 권한 / 개행 / 문법 확인
        ▼
RELEASE_ENV 지정 후 bootstrap.sh 실행
        ▼
runtime directory 생성
        ▼
shared env 복사
        ▼
poll-release hook 동기화
        ▼
current-release-state.json / poll-release.log 초기화
        ▼
poll-release runtime script 설치
        ▼
systemd service/timer 생성
        ▼
logrotate config 생성
        ▼
systemd daemon-reload
        ▼
timer enable/restart
        ▼
bootstrap 결과 확인
        ▼
polling deploy 가능 상태 진입

운영 기준:

  • bootstrap source of truth 는 .ops/cd/bootstrap.sh
  • 운영 서버의 systemd unit 파일은 수동 수정하지 않음
  • systemd unit 변경이 필요하면 .ops/cd/bootstrap.sh 를 수정한 뒤 재실행함
  • RELEASE_ENV 허용값은 production, pre-production
  • bootstrap 은 runtime provisioning 만 수행함
  • 실제 release polling/deploy 동작은 poll-release.sh 가 수행함
  • GitLab CI/CD 의 release target branch 는 Release Yaml 파일 (.ops/ci/release-branch-versions/{CI_COMMIT_BRANCH}.yaml) 기준으로 확장 가능
  • 본 runbook 의 bootstrap 실행은 현재 bootstrap.sh 가 허용하는 RELEASE_ENV 값 기준으로 수행함.

1. Bootstrap 실행 전 source 확인

운영 서버에 .ops/cd 작업 디렉토리가 배치되어 있어야 함.

cd /home/{SERVICE_USER}/.ops/cd

source 구조 확인:

sudo ls -ld /home/{SERVICE_USER}/.ops
sudo ls -ld /home/{SERVICE_USER}/.ops/cd
sudo ls -ld /home/{SERVICE_USER}/.ops/cd/env
sudo ls -l /home/{SERVICE_USER}/.ops/cd/bootstrap.sh
sudo ls -l /home/{SERVICE_USER}/.ops/cd/opt/bin/poll-release.sh
sudo ls -l /home/{SERVICE_USER}/.ops/cd/env/.service.env
sudo ls -l /home/{SERVICE_USER}/.ops/cd/env/.release-storage.env
sudo ls -la /home/{SERVICE_USER}/.ops/cd/poll-release-hooks

필수 source 파일:

/home/{SERVICE_USER}/.ops/cd/bootstrap.sh
/home/{SERVICE_USER}/.ops/cd/opt/bin/poll-release.sh
/home/{SERVICE_USER}/.ops/cd/env/.service.env
/home/{SERVICE_USER}/.ops/cd/env/.release-storage.env

선택 source directory:

/home/{SERVICE_USER}/.ops/cd/poll-release-hooks/

정상 기준:

  • .ops/cd/bootstrap.sh 가 존재해야 함
  • .ops/cd/opt/bin/poll-release.sh 가 존재해야 함
  • .ops/cd/env/.service.env 가 존재해야 함
  • .ops/cd/env/.release-storage.env 가 존재해야 함
  • hook directory 가 있으면 bootstrap 시 shared hook 영역으로 동기화됨

2. Source 권한 정리

source directory 소유자를 정리함.

sudo chown -R {SERVICE_USER}:{SERVICE_GROUP} /home/{SERVICE_USER}/.ops

기본 권한을 정리함.

sudo find /home/{SERVICE_USER}/.ops -type d -exec chmod 755 {} \;
sudo find /home/{SERVICE_USER}/.ops -type f -exec chmod 644 {} \;

실행 스크립트 권한을 설정함.

sudo chmod 755 /home/{SERVICE_USER}/.ops/cd/bootstrap.sh
sudo chmod 755 /home/{SERVICE_USER}/.ops/cd/opt/bin/poll-release.sh

hook 권한을 설정함.

if [ -d /home/{SERVICE_USER}/.ops/cd/poll-release-hooks ]; then
  sudo find /home/{SERVICE_USER}/.ops/cd/poll-release-hooks -type d -exec chmod 755 {} \;
  sudo find /home/{SERVICE_USER}/.ops/cd/poll-release-hooks -type f -exec chmod 755 {} \;
fi

민감 env 권한을 설정함.

sudo chmod 700 /home/{SERVICE_USER}/.ops/cd/env
sudo chmod 600 /home/{SERVICE_USER}/.ops/cd/env/.service.env
sudo chmod 600 /home/{SERVICE_USER}/.ops/cd/env/.release-storage.env

3. Source 개행 / 문법 확인

BOM / CRLF 를 제거함.

sudo sed -i '1s/^\xEF\xBB\xBF//' /home/{SERVICE_USER}/.ops/cd/bootstrap.sh
sudo sed -i '1s/^\xEF\xBB\xBF//' /home/{SERVICE_USER}/.ops/cd/opt/bin/poll-release.sh
sudo sed -i 's/\r$//' /home/{SERVICE_USER}/.ops/cd/bootstrap.sh
sudo sed -i 's/\r$//' /home/{SERVICE_USER}/.ops/cd/opt/bin/poll-release.sh

hook script 도 동일하게 정리함.

if [ -d /home/{SERVICE_USER}/.ops/cd/poll-release-hooks ]; then
  sudo find /home/{SERVICE_USER}/.ops/cd/poll-release-hooks -type f -name '*.sh' -exec sed -i '1s/^\xEF\xBB\xBF//' {} \;
  sudo find /home/{SERVICE_USER}/.ops/cd/poll-release-hooks -type f -name '*.sh' -exec sed -i 's/\r$//' {} \;
fi

문법을 확인함.

sudo bash -n /home/{SERVICE_USER}/.ops/cd/bootstrap.sh
sudo bash -n /home/{SERVICE_USER}/.ops/cd/opt/bin/poll-release.sh

hook 문법을 확인함.

if [ -d /home/{SERVICE_USER}/.ops/cd/poll-release-hooks ]; then
  sudo find /home/{SERVICE_USER}/.ops/cd/poll-release-hooks -type f -name '*.sh' -exec bash -n {} \;
fi

정상 기준:

bash -n 결과가 아무 출력 없이 종료되어야 함

4. Bootstrap 실행

production 환경 bootstrap:

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

pre-production 환경 bootstrap:

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

주의:

  • RELEASE_ENV 는 필수임
  • RELEASE_ENVproduction 또는 pre-production 이어야 함
  • bootstrap 은 지정한 RELEASE_ENV 기준으로 systemd service 환경변수를 생성함
  • 동일 서버에서 release env 를 변경하려면 해당 RELEASE_ENV 값으로 bootstrap 을 다시 실행함

5. Bootstrap 실행 로그 확인

bootstrap 실행 중 출력에서 다음 항목을 확인함.

Bootstrap start
Service Name
Service User
Service Group
Release Env
Deploy Base
Runtime Script
Service Unit
Timer Unit
PM2 Binary

정상 완료 로그:

Bootstrap completed.
Summary:
  Service Name
  Service User
  Service Group
  Release Env
  Deploy Base
  Runtime Script
  Service Unit
  Timer Unit

실패 시 우선 확인:

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

6. Runtime 결과 확인

bootstrap 완료 후 runtime directory 구조를 확인함.

/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

runtime script 확인:

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

정상 기준:

/var/www/{SERVICE_NAME}                  755 {SERVICE_USER}:{SERVICE_GROUP}
/var/www/{SERVICE_NAME}/downloads        750 {SERVICE_USER}:{SERVICE_GROUP}
/var/www/{SERVICE_NAME}/logs             750 {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}/tmp              750 {SERVICE_USER}:{SERVICE_GROUP}
/var/www/{SERVICE_NAME}/deploy.lock      600 {SERVICE_USER}:{SERVICE_GROUP}
/opt/bin/poll-release.{SERVICE_NAME}.sh  755 root:root

7. Shared 파일 확인

shared directory 내용을 확인함.

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

개별 파일 확인:

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/.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

application env 파일은 bootstrap 이 생성하지 않음. 운영 서버에서 별도로 배치함.

sudo ls -l /var/www/{SERVICE_NAME}/shared/.env.{RELEASE_ENV}

# 현재 기본 운영 env 예시
sudo ls -l /var/www/{SERVICE_NAME}/shared/.env.production
sudo ls -l /var/www/{SERVICE_NAME}/shared/.env.pre-production

정상 기준:

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}
shared/poll-release-hooks                700 {SERVICE_USER}:{SERVICE_GROUP}
shared/poll-release-hooks/*.sh           700 {SERVICE_USER}:{SERVICE_GROUP}

8. systemd Unit 확인

service unit 확인:

sudo ls -l /etc/systemd/system/release-poll.{SERVICE_NAME}.service
sudo systemctl cat release-poll.{SERVICE_NAME}.service --no-pager

timer unit 확인:

sudo ls -l /etc/systemd/system/release-poll.{SERVICE_NAME}.timer
sudo systemctl cat release-poll.{SERVICE_NAME}.timer --no-pager

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
Unit=release-poll.{SERVICE_NAME}.service

9. systemd Timer 상태 확인

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

정상 기준:

release-poll.{SERVICE_NAME}.timer loaded active waiting
release-poll.{SERVICE_NAME}.service loaded inactive dead

주의:

  • service 는 Type=oneshot 이므로 실행 완료 후 inactive (dead) 상태가 정상일 수 있음
  • 자동 실행 대상은 service 가 아니라 timer 임

10. Logrotate 확인

logrotate config 를 확인함.

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

debug 모드로 확인함.

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

확인 대상:

/var/www/{SERVICE_NAME}/logs/*.log
daily
rotate 14
compress
delaycompress
missingok
notifempty
copytruncate
create 600 {SERVICE_USER} {SERVICE_GROUP}

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

bootstrap 후 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

주의:


12. 로그 확인

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

13. 장애 대응 기준

13.1 root 권한 없이 bootstrap 실행

증상:

bootstrap.sh must be run as root

대응:

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

13.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

13.3 RELEASE_ENV 값 오류

증상:

Invalid RELEASE_ENV
Allowed values: production, pre-production

대응:

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

13.4 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 이 중단됨
필요 시 수동 백업 후 current 경로를 정리함

13.6 PM2 누락

증상:

Missing pm2 executable: /usr/bin/pm2

대응:

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

13.7 필수 실행 파일 누락

증상:

Missing executable

대응:

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

그 후 bootstrap 을 재실행함.

13.8 .env.{RELEASE_ENV} 누락

증상:

.env.{RELEASE_ENV} file not found
poll-release skipped

확인:

sudo ls -l /var/www/{SERVICE_NAME}/shared/.env.{RELEASE_ENV}

대응:

sudo cp /path/to/.env.{RELEASE_ENV} /var/www/{SERVICE_NAME}/shared/.env.{RELEASE_ENV}
sudo chown {SERVICE_USER}:{SERVICE_GROUP} /var/www/{SERVICE_NAME}/shared/.env.{RELEASE_ENV}
sudo chmod 600 /var/www/{SERVICE_NAME}/shared/.env.{RELEASE_ENV}

그 후 poll-release 를 다시 수동 실행하거나 timer 를 재개함.


14. 보안 기준

운영 기준:

  • .release-storage.env 는 민감정보 파일로 취급함
  • .release-storage.env600 권한 기준으로 관리함
  • .service.env600 권한 기준으로 관리함
  • shared directory 는 700 권한 기준으로 관리함
  • bootstrap source env 파일은 Git 저장소에 실제 credential 을 포함하지 않음
  • 운영 서버의 runtime shared env 파일은 신뢰된 운영자만 수정함
  • systemd unit 은 bootstrap 결과물이며 수동 수정하지 않음
  • root 권한이 필요한 수동 명령은 sudo 로 실행함