- CI/CD 서버 스펙 변경 반영 (8vCPU/16GB) - 개발서버 DB 백업 문서화 (sam-db-backup.sh) - CI/CD 백업 경로/대상 업데이트 (/data/, codebridge 추가, root 크론) - MNG Jenkinsfile storage/logs 심링크 수정 반영 - 마이그레이션 정책 추가 (sam→API, codebridge→MNG) - MNG 배포 후 500 에러 트러블슈팅 사례 추가 - Laravel Scheduler 섹션 추가 (API+MNG, www-data) - deploy 전용 계정 전환 계획 문서 추가
462 lines
14 KiB
Markdown
462 lines
14 KiB
Markdown
# deploy 전용 계정 전환 계획
|
|
|
|
> **작성일**: 2026-03-19
|
|
> **상태**: 계획 수립
|
|
> **목적**: 개인 계정(hskwon, pro, kkk) 의존 제거 → 시스템 계정(deploy, root, www-data)으로 전환
|
|
> **대상 서버**: sam-prod, sam-cicd, sam-dev (3대)
|
|
|
|
---
|
|
|
|
## 1. 현황 요약
|
|
|
|
### 개인 계정 의존 항목 (20개)
|
|
|
|
| 서버 | 항목 | 현재 계정 | 전환 대상 |
|
|
|------|------|----------|----------|
|
|
| **prod** | PM2 프로세스 + startup 서비스 | hskwon | root |
|
|
| **prod** | Jenkinsfile DEPLOY_USER (api, mng, react, sales) | hskwon | deploy |
|
|
| **prod** | releases/current 심링크 소유 | hskwon | deploy |
|
|
| **prod** | /home/webservice/ 디렉토리 소유 | hskwon | root |
|
|
| **prod** | ecosystem.config.js 소유 | hskwon | root |
|
|
| **prod** | shared/.env 소유 (api, mng) | hskwon | deploy:webservice |
|
|
| **prod** | sales/.env (chmod 600) | hskwon | deploy:webservice (640) |
|
|
| **prod** | backups/ 디렉토리 | hskwon | root |
|
|
| **cicd** | /data/ 디렉토리 | hskwon:kkk | root:root |
|
|
| **cicd** | /data/backups/mysql/ 백업 파일 | hskwon:kkk | root:root |
|
|
| **cicd** | hskwon 빈 crontab 잔존 | hskwon | 삭제 |
|
|
| **dev** | Laravel scheduler (/etc/crontab) | hskwon | www-data |
|
|
| **dev** | Gitea cache 정리 (hskwon crontab) | hskwon | root (/etc/crontab) |
|
|
| **dev** | PM2 프로세스 + startup 서비스 | hskwon | root |
|
|
| **dev** | /home/webservice/ 디렉토리 소유 | hskwon | root |
|
|
| **dev** | sales_org/ 소유 | pro | root (또는 삭제) |
|
|
| **dev** | sam_backup_20260317.sql 임시파일 | pro | 삭제 |
|
|
| **모든 Jenkinsfile** | DEPLOY_USER = 'hskwon' | hskwon | deploy |
|
|
| **mng Jenkinsfile** | storage/logs mkdir (심링크 아님) | — | 심링크로 변경 |
|
|
| **Jenkins credential** | deploy-ssh-key → hskwon 키 | hskwon | deploy 키 |
|
|
|
|
---
|
|
|
|
## 2. 전환 계획
|
|
|
|
### Phase 0: deploy 계정 생성 (서비스 영향 없음)
|
|
|
|
> 기존 hskwon 방식이 그대로 동작하는 상태에서 새 계정만 준비
|
|
|
|
#### 0-1. 3대 서버에 deploy 계정 생성
|
|
|
|
```bash
|
|
# sam-prod
|
|
sudo useradd -r -m -s /bin/bash -G webservice -c 'CI/CD Deploy Account' deploy
|
|
sudo passwd -l deploy # 패스워드 로그인 차단 (SSH 키만 허용)
|
|
|
|
# sam-dev
|
|
sudo useradd -r -m -s /bin/bash -G develop -c 'CI/CD Deploy Account' deploy
|
|
sudo passwd -l deploy
|
|
|
|
# sam-cicd (직접 SSH 접속은 없지만 일관성)
|
|
sudo useradd -r -m -s /bin/bash -c 'CI/CD Deploy Account' deploy
|
|
sudo passwd -l deploy
|
|
```
|
|
|
|
#### 0-2. SSH 키 생성 (sam-cicd에서)
|
|
|
|
```bash
|
|
# Jenkins가 사용할 SSH 키 생성
|
|
sudo -u jenkins ssh-keygen -t ed25519 -f /var/lib/jenkins/.ssh/id_ed25519_deploy -N '' -C 'jenkins-deploy@sam-cicd'
|
|
```
|
|
|
|
#### 0-3. SSH 공개키 배포
|
|
|
|
```bash
|
|
# sam-prod
|
|
sudo mkdir -p /home/deploy/.ssh
|
|
sudo cp /var/lib/jenkins/.ssh/id_ed25519_deploy.pub /tmp/deploy_key.pub
|
|
# (scp로 전송 후)
|
|
sudo sh -c 'cat /tmp/deploy_key.pub >> /home/deploy/.ssh/authorized_keys'
|
|
sudo chown -R deploy:deploy /home/deploy/.ssh
|
|
sudo chmod 700 /home/deploy/.ssh
|
|
sudo chmod 600 /home/deploy/.ssh/authorized_keys
|
|
|
|
# sam-dev (동일)
|
|
```
|
|
|
|
#### 0-4. sudoers 설정 (sam-prod, sam-dev)
|
|
|
|
```bash
|
|
# /etc/sudoers.d/deploy
|
|
sudo visudo -f /etc/sudoers.d/deploy
|
|
```
|
|
|
|
```
|
|
# sam-prod용
|
|
deploy ALL=(ALL) NOPASSWD: /usr/sbin/service php8.4-fpm reload
|
|
deploy ALL=(ALL) NOPASSWD: /bin/systemctl reload php8.4-fpm
|
|
deploy ALL=(ALL) NOPASSWD: /bin/systemctl restart php8.4-fpm
|
|
deploy ALL=(ALL) NOPASSWD: /usr/bin/supervisorctl restart sam-queue-worker\:*
|
|
deploy ALL=(ALL) NOPASSWD: /bin/chown -R www-data\:webservice *
|
|
deploy ALL=(ALL) NOPASSWD: /bin/chmod -R 775 *
|
|
deploy ALL=(ALL) NOPASSWD: /bin/chmod 640 *
|
|
```
|
|
|
|
```
|
|
# sam-dev용
|
|
deploy ALL=(ALL) NOPASSWD: /bin/systemctl reload php8.4-fpm
|
|
deploy ALL=(ALL) NOPASSWD: /bin/chown -R www-data\:webservice *
|
|
deploy ALL=(ALL) NOPASSWD: /bin/chown -R www-data\:develop *
|
|
deploy ALL=(ALL) NOPASSWD: /bin/chmod -R 775 *
|
|
deploy ALL=(ALL) NOPASSWD: /bin/chmod 640 *
|
|
```
|
|
|
|
#### 0-5. SSH 연결 테스트
|
|
|
|
```bash
|
|
# sam-cicd에서 실행
|
|
sudo -u jenkins ssh -i /var/lib/jenkins/.ssh/id_ed25519_deploy deploy@211.117.60.189 'whoami && hostname'
|
|
sudo -u jenkins ssh -i /var/lib/jenkins/.ssh/id_ed25519_deploy deploy@114.203.209.83 'whoami && hostname'
|
|
```
|
|
|
|
#### 0-6. deploy를 webservice 그룹에 추가 확인
|
|
|
|
```bash
|
|
# sam-prod
|
|
id deploy
|
|
# → deploy groups: deploy webservice
|
|
|
|
# sam-dev
|
|
id deploy
|
|
# → deploy groups: deploy develop
|
|
```
|
|
|
|
**Phase 0 완료 기준**: deploy 계정으로 SSH + sudo 테스트 통과. 기존 서비스 영향 **없음**.
|
|
|
|
---
|
|
|
|
### Phase 1: Jenkins 배포 전환 (배포 중단 5분)
|
|
|
|
> **작업 시간**: 야간 또는 사용자 없는 시간
|
|
> **전제**: Phase 0 완료, deploy SSH 연결 확인됨
|
|
|
|
#### 1-1. Jenkins credential 추가
|
|
|
|
Jenkins 웹 UI → Credentials → Global:
|
|
- ID: `deploy-ssh-key-v2` (기존 유지한 채 새로 추가)
|
|
- 유형: SSH Username with private key
|
|
- Username: `deploy`
|
|
- Private Key: `/var/lib/jenkins/.ssh/id_ed25519_deploy`
|
|
|
|
#### 1-2. Jenkinsfile 수정 (4개 저장소)
|
|
|
|
**api/Jenkinsfile:**
|
|
```groovy
|
|
environment {
|
|
DEPLOY_USER = 'deploy' // hskwon → deploy
|
|
// ...
|
|
}
|
|
// sshagent: deploy-ssh-key → deploy-ssh-key-v2
|
|
```
|
|
|
|
**mng/Jenkinsfile:**
|
|
```groovy
|
|
environment {
|
|
DEPLOY_USER = 'deploy' // hskwon → deploy
|
|
}
|
|
// + storage/logs 심링크 수정 (500 에러 재발 방지)
|
|
// 변경 전: mkdir -p ... storage/logs && sudo chown ...
|
|
// 변경 후: rm -rf storage/logs && ln -sfn /home/webservice/mng/shared/storage/logs storage/logs
|
|
```
|
|
|
|
**react/Jenkinsfile, sales/Jenkinsfile:**
|
|
```groovy
|
|
environment {
|
|
DEPLOY_USER = 'deploy' // hskwon → deploy
|
|
}
|
|
```
|
|
|
|
#### 1-3. Jenkinsfile credential ID 변경
|
|
|
|
모든 Jenkinsfile에서:
|
|
```groovy
|
|
// 변경 전
|
|
sshagent(credentials: ['deploy-ssh-key']) {
|
|
|
|
// 변경 후
|
|
sshagent(credentials: ['deploy-ssh-key-v2']) {
|
|
```
|
|
|
|
#### 1-4. 테스트 배포
|
|
|
|
```
|
|
1. mng develop push → 개발서버 배포 확인
|
|
2. mng main push → 운영서버 배포 확인
|
|
3. api main push → Stage → 승인 → Production 확인
|
|
4. react develop push → 개발서버 확인
|
|
```
|
|
|
|
#### 1-5. 파일 소유권 정리 (sam-prod)
|
|
|
|
```bash
|
|
# /home/webservice 최상위
|
|
sudo chown root:webservice /home/webservice/
|
|
sudo chown root:webservice /home/webservice/ecosystem.config.js
|
|
|
|
# shared/.env 소유자 → deploy (Jenkins가 배포 시 접근 가능)
|
|
sudo chown deploy:webservice /home/webservice/api/shared/.env
|
|
sudo chown deploy:webservice /home/webservice/mng/shared/.env
|
|
sudo chmod 640 /home/webservice/api/shared/.env
|
|
sudo chmod 640 /home/webservice/mng/shared/.env
|
|
|
|
# sales .env (600 → 640)
|
|
sudo chown deploy:webservice /home/webservice/sales/.env
|
|
sudo chmod 640 /home/webservice/sales/.env
|
|
|
|
# api, mng, react 디렉토리 (releases/shared 상위)
|
|
for d in api api-stage mng react react-stage sales landing; do
|
|
sudo chown deploy:webservice /home/webservice/$d 2>/dev/null
|
|
done
|
|
|
|
# backups
|
|
sudo chown root:webservice /home/webservice/backups/
|
|
```
|
|
|
|
**Phase 1 완료 기준**: Jenkins → deploy 계정으로 4개 프로젝트 배포 성공. hskwon SSH 키 미사용.
|
|
|
|
---
|
|
|
|
### Phase 2: PM2 전환 (Next.js 다운타임 1~2분)
|
|
|
|
> **작업 시간**: 야간 필수 (sam.it.kr 일시 중단)
|
|
|
|
#### 2-1. sam-prod PM2 전환
|
|
|
|
```bash
|
|
# 1. 현재 PM2 정지
|
|
pm2 stop all
|
|
pm2 kill
|
|
|
|
# 2. 기존 PM2 서비스 비활성화
|
|
sudo systemctl stop pm2-hskwon
|
|
sudo systemctl disable pm2-hskwon
|
|
|
|
# 3. root PM2 서비스 생성
|
|
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u root --hp /root
|
|
# → /etc/systemd/system/pm2-root.service 생성
|
|
|
|
# 4. ecosystem.config.js로 시작
|
|
cd /home/webservice && sudo pm2 start ecosystem.config.js
|
|
sudo pm2 save
|
|
|
|
# 5. 서비스 확인
|
|
sudo pm2 status
|
|
curl -sI https://sam.it.kr | head -3
|
|
curl -sI https://stage.sam.it.kr | head -3
|
|
|
|
# 6. 이전 서비스 파일 삭제
|
|
sudo rm /etc/systemd/system/pm2-hskwon.service
|
|
sudo systemctl daemon-reload
|
|
```
|
|
|
|
#### 2-2. sam-dev PM2 전환
|
|
|
|
```bash
|
|
# 동일 절차
|
|
pm2 stop all && pm2 kill
|
|
sudo systemctl stop pm2-hskwon && sudo systemctl disable pm2-hskwon
|
|
sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u root --hp /root
|
|
cd /home/webservice && sudo pm2 start ecosystem.config.js
|
|
sudo pm2 save
|
|
sudo rm /etc/systemd/system/pm2-hskwon.service
|
|
sudo systemctl daemon-reload
|
|
|
|
# 확인
|
|
sudo pm2 status
|
|
```
|
|
|
|
**Phase 2 완료 기준**: PM2가 root로 실행. `pm2 status`에서 user=root 확인. 부팅 후 자동 복구 테스트 (선택).
|
|
|
|
---
|
|
|
|
### Phase 3: Cron/시스템 정리 (서비스 영향 없음)
|
|
|
|
#### 3-1. sam-dev: Laravel scheduler 전환
|
|
|
|
```bash
|
|
# /etc/crontab 수정
|
|
# 변경 전:
|
|
# * * * * * hskwon cd /home/webservice/api && php artisan schedule:run >> /dev/null 2>&1
|
|
# 변경 후:
|
|
* * * * * www-data cd /home/webservice/api && php artisan schedule:run >> /dev/null 2>&1
|
|
```
|
|
|
|
#### 3-2. sam-dev: Gitea cache 정리 → root crontab
|
|
|
|
```bash
|
|
# hskwon crontab에서 삭제
|
|
crontab -r # (이미 빈 crontab)
|
|
|
|
# /etc/crontab에 추가
|
|
# 0 4 * * 0 root find /var/lib/gitea/data/repo-archive -type f -mtime +7 -delete 2>/dev/null
|
|
# 0 4 * * 0 root find /var/lib/gitea/data/repo-archive -type d -empty -delete 2>/dev/null
|
|
```
|
|
|
|
#### 3-3. sam-cicd: 빈 crontab 삭제
|
|
|
|
```bash
|
|
crontab -r # hskwon 빈 crontab 삭제
|
|
```
|
|
|
|
#### 3-4. sam-cicd: /data/ 소유권 변경
|
|
|
|
```bash
|
|
sudo chown -R root:root /data/
|
|
sudo chown root:root /data/scripts/backup-db.sh
|
|
sudo chown root:root /data/scripts/.sam_backup.cnf
|
|
sudo chmod 600 /data/scripts/.sam_backup.cnf
|
|
```
|
|
|
|
#### 3-5. sam-dev: 디렉토리 소유권 정리
|
|
|
|
```bash
|
|
# /home/webservice 최상위
|
|
sudo chown root:develop /home/webservice/
|
|
|
|
# 임시 파일 삭제
|
|
sudo rm -f /home/webservice/sam_backup_20260317.sql
|
|
sudo rm -f /home/webservice/demo.tar.gz # 필요 여부 확인
|
|
|
|
# sales_org → 필요 여부 확인 후 삭제 또는 소유권 변경
|
|
sudo chown -R root:develop /home/webservice/sales_org/
|
|
```
|
|
|
|
**Phase 3 완료 기준**: 개인 계정 crontab 전부 비어 있음. /data/, /home/webservice/ root 소유.
|
|
|
|
---
|
|
|
|
### Phase 4: 검증 및 문서 업데이트
|
|
|
|
#### 4-1. 전체 서버 개인 계정 의존 재점검
|
|
|
|
```bash
|
|
# 3대 서버에서 실행
|
|
for u in hskwon pro kkk; do
|
|
echo "=== $u crontab ==="
|
|
sudo crontab -u $u -l 2>/dev/null || echo 'none'
|
|
done
|
|
|
|
# PM2 소유자 확인
|
|
pm2 status # user 컬럼 = root
|
|
|
|
# 서비스 상태
|
|
sudo systemctl status pm2-root
|
|
sudo systemctl status nginx php8.4-fpm mysql redis-server supervisor
|
|
```
|
|
|
|
#### 4-2. ops-manual 문서 업데이트
|
|
|
|
- `01-server-overview.md`: 사용자 목록에 deploy 추가, 디렉토리 소유권
|
|
- `05-deployment.md`: Jenkinsfile DEPLOY_USER=deploy 반영
|
|
- `09-security.md`: deploy 계정 설명, sudoers 설정
|
|
- `10-backup-recovery.md`: crontab 실행 주체 확인
|
|
|
|
#### 4-3. 이 계획 문서 → 완료 후 삭제
|
|
|
|
---
|
|
|
|
## 3. 롤백 계획
|
|
|
|
| Phase | 롤백 방법 | 소요 시간 |
|
|
|-------|----------|----------|
|
|
| Phase 0 | 계정 삭제 (`userdel deploy`) | 1분 |
|
|
| Phase 1 | Jenkinsfile DEPLOY_USER → hskwon 복원, credential 원복 | 5분 |
|
|
| Phase 2 | `sudo pm2 kill` → hskwon으로 PM2 재시작 | 2분 |
|
|
| Phase 3 | crontab, 소유권 원복 | 5분 |
|
|
|
|
**핵심**: Phase 1까지는 기존 hskwon 방식이 병행 가능하므로 즉시 롤백 가능.
|
|
|
|
---
|
|
|
|
## 4. 작업 일정 (제안)
|
|
|
|
| Phase | 작업 | 시간대 | 서비스 영향 |
|
|
|-------|------|--------|-----------|
|
|
| **0** | deploy 계정 생성 + SSH 키 + sudoers | 업무 시간 | 없음 |
|
|
| **1** | Jenkinsfile 수정 + 테스트 배포 | 업무 시간 (배포 조율) | 배포 불가 5분 |
|
|
| **2** | PM2 전환 | **야간** | Next.js 다운 1~2분 |
|
|
| **3** | Cron/소유권 정리 | 업무 시간 | 없음 |
|
|
| **4** | 검증 + 문서 | 업무 시간 | 없음 |
|
|
|
|
---
|
|
|
|
## 5. 함께 수정하는 항목 (Jenkinsfile 수정 시)
|
|
|
|
### MNG storage/logs 심링크 수정 (500 에러 재발 방지)
|
|
|
|
현재 Jenkinsfile:
|
|
```bash
|
|
mkdir -p bootstrap/cache storage/framework/{views,cache/data,sessions} storage/logs &&
|
|
sudo chown -R www-data:webservice storage/logs &&
|
|
```
|
|
|
|
수정 후:
|
|
```bash
|
|
mkdir -p bootstrap/cache storage/framework/{views,cache/data,sessions} &&
|
|
rm -rf storage/logs &&
|
|
ln -sfn /home/webservice/mng/shared/storage/logs storage/logs &&
|
|
```
|
|
|
|
> `migrate --force` 실행 시 deploy 계정으로 로그 파일이 생성되어도, shared/storage/logs는
|
|
> www-data:webservice 소유이므로 권한 문제 없음.
|
|
|
|
### API storage 권한도 동일 패턴 적용
|
|
|
|
현재 API Jenkinsfile에서 `sudo chown -R www-data:webservice storage bootstrap/cache`로 해결하고 있으나,
|
|
storage/logs도 shared 심링크로 통일하는 것이 더 안전.
|
|
|
|
---
|
|
|
|
## 6. 체크리스트
|
|
|
|
### Phase 0
|
|
- [ ] sam-prod에 deploy 계정 생성 + webservice 그룹
|
|
- [ ] sam-dev에 deploy 계정 생성 + develop 그룹
|
|
- [ ] sam-cicd에 deploy 계정 생성
|
|
- [ ] SSH 키 생성 (sam-cicd jenkins 사용자)
|
|
- [ ] SSH 공개키 → sam-prod, sam-dev authorized_keys
|
|
- [ ] sudoers 설정 (sam-prod, sam-dev)
|
|
- [ ] SSH 연결 테스트 통과
|
|
|
|
### Phase 1
|
|
- [ ] Jenkins credential 추가 (deploy-ssh-key-v2)
|
|
- [ ] api/Jenkinsfile DEPLOY_USER + credential 변경
|
|
- [ ] mng/Jenkinsfile DEPLOY_USER + credential + storage/logs 심링크 수정
|
|
- [ ] react/Jenkinsfile DEPLOY_USER + credential 변경
|
|
- [ ] sales/Jenkinsfile DEPLOY_USER + credential 변경
|
|
- [ ] mng 테스트 배포 (develop → 개발서버)
|
|
- [ ] mng 테스트 배포 (main → 운영서버)
|
|
- [ ] api 테스트 배포 (main → Stage → Production)
|
|
- [ ] react 테스트 배포 (develop → 개발서버)
|
|
- [ ] sam-prod 파일 소유권 정리
|
|
- [ ] .env 권한 640 확인 (api, mng, sales)
|
|
|
|
### Phase 2
|
|
- [ ] sam-prod PM2 → root 전환 (야간)
|
|
- [ ] sam.it.kr 접속 확인
|
|
- [ ] stage.sam.it.kr 접속 확인
|
|
- [ ] sam-dev PM2 → root 전환
|
|
- [ ] dev.codebridge-x.com 접속 확인
|
|
|
|
### Phase 3
|
|
- [ ] sam-dev scheduler → www-data
|
|
- [ ] sam-dev Gitea cache 정리 → root /etc/crontab
|
|
- [ ] sam-cicd hskwon 빈 crontab 삭제
|
|
- [ ] sam-cicd /data/ 소유권 → root
|
|
- [ ] sam-dev 임시 파일 삭제 + 소유권 정리
|
|
|
|
### Phase 4
|
|
- [ ] 전체 서버 개인 계정 의존 재점검 (0건 확인)
|
|
- [ ] ops-manual 문서 업데이트
|
|
- [ ] 이 계획 문서 삭제
|
|
|
|
---
|
|
|
|
**최종 업데이트**: 2026-03-19
|