📚 엔지니어 · 세션 · 운영 · 배포 · 거버넌스 (수정중) · L03

프로덕션 배포 (수정중)

작성일자 2026-06-21  ·  수정일자 2026-06-21  ·  강의차수 L03  ·  예상소요 30분

목표 이 강의가 끝나면 학습자가 prod 박스에 자기 계정으로 표준 절차(SSH → git checkout → migrate → systemctl restart → 로그 확인)대로 재배포할 수 있고, 왜 공유 ubuntu 가 아니라 개인 계정인지(추적) 를 설명할 수 있다.

1. 무엇을 푸는 문제인가 — "전원 배포 가능 + 누가 했는지 추적"

우리는 2026-05-27부터 서버 2대 체제다.

  • dev 박스 (43.200.38.93) — 모든 dev 작업
  • prod 박스 (52.79.132.206, standarda-prod) — 신규 prod 전용

prod 을 별도 호스트로 뺀 이유는 dev migration·리소스 경합이 prod 데이터를 깨는 사고를 구조적으로 막기 위해서다(infrastructure/prod-server-host.md 기준; 팀 wiki).

여기서 운영 요구가 하나 나왔다.

"prod 릴리즈를 전원이 할 수 있게 하고 싶다. 단, 누가 어떤 버전을 언제 올렸는지 추적되게. 그리고 공유 ubuntu 말고 각자 본인 계정으로."

이 강의는 그 요구를 어떻게 설계·구현했는지(2026-06 세션)와, 그래서 너희가 prod 에 배포할 때 실제로 어떻게 하면 되는지(절차서 infrastructure/prod-release.md)를 같이 본다.


2. 왜 공유 ubuntu 가 아니라 개인 계정인가 — 추적의 문제

원래 prod 박스에는 사람 계정이 ubuntu 하나뿐이었다. 모두가 popup-chris-prod.pem 키로 ubuntu 에 들어가 배포했다.

문제는 명확하다 — ubuntu 로 한 모든 행위는 "ubuntu 가 했다"로만 남는다. golden 을 누가 어제 재배포했는지, 누가 마이그레이션을 돌렸는지 개인 단위로 구분이 안 된다.

해법은 두 갈래였다.

  • 막기 — prod 배포를 특정인/CI 로만 제한 (전원 배포 요구와 배치)
  • 추적하되 풀어주기 — 전원에게 권한을 주되 개인 계정으로 하게 해서, 모든 행위가 개인 단위로 로그에 남게 한다 ✅

우리는 후자를 택했다. dev 박스처럼 풀 sudo 를 주되(편하게 일하라고), 공유 ubuntu 가 아니라 개인 Linux 계정으로 접속하게 한 것이 핵심이다. 그러면 풀 권한이어도 "누가" 가 항상 남는다.

핵심 개념: 권한을 좁혀서가 아니라, "행위 주체를 개인으로 만들어서" 추적을 얻는다. 신뢰 팀에서는 차단보다 이 방식이 마찰이 적다. (L09 wiki 거버넌스의 "직접 쓰기 + 사후 추적"과 같은 철학)


3. 셋업된 것 — prod 개인 계정 모델 (dev 미러, 단 차이 있음)

prod 박스를 dev 박스와 같은 모델로 맞췄다(infrastructure/sudo-policy.md 2026-06-20 prod 확대 항목).

  • 개인 계정 6명: gun·eunseo·hyeonjin·sooa·ygchoi·hykim (devteam 그룹)
  • 풀 NOPASSWD:ALL sudo — 배포에 필요한 건 다 됨 (서비스 재시작, DB, 등)
  • postgres 개인 superuser 역할 — psql 을 본인 신분으로 (peer auth)
  • prod 프로젝트 디렉토리(/home/ubuntu/<proj>/)를 devteam setgid 공유 → 누가 git pull 해도 소유권 충돌 없음
  • 차이: prod 은 dev 와 달리 docker 그룹이 없다(미설치)

접속은 이렇게 바뀐다(popup-chris-prod.pem 공유 키 → 본인 계정):

ssh <본인계정>@52.79.132.206        # 예: ssh gun@52.79.132.206

⚠️ 이번 셋업의 가장 중요한 함정 — "본인 키만". 키를 등록할 때 dev 박스의 ~/.ssh/authorized_keys 를 그대로 복제하려 했는데, 확인해 보니 gun·eunseo·hyeonjin 의 dev authorized_keys 는 17개 키가 든 공유 묶음(셋 다 내용 동일)이었다. 여러 사람 키가 다 들어있다. 이걸 prod 에 그대로 복제하면 "누구나 gun 으로 로그인" 가능 → 추적이 깨진다. (gun 으로 로그인했는데 사실 다른 사람일 수 있음) 그래서 키 주석으로 본인 키 1개만 골라 등록했다 (gun→gun@popupstudio.ai, eunseo→esmaeng@…, hyeonjin→jacob@…). 교훈: "dev 키 그대로"가 목표가 아니라 "개인 추적"이 목표다. 요구의 글자가 아니라 의도를 보고, 추적을 깨는 복제는 하지 않는다.


4. 표준 릴리즈 절차 — 이렇게 배포한다

이미 prod 에 올라가 있는 프로젝트를 새 버전으로 재배포하는 일상 절차다. 전체는 infrastructure/prod-release.md에 있고, 여기선 흐름과 왜를 짚는다.

# 0. 본인 계정으로 접속 (공유 ubuntu 아님!)
ssh gun@52.79.132.206

# 1. 대상 서비스 확인
systemctl list-units '*-prod.service' '*celery*' --no-pager

# 2. 최신 코드 (tag 권장 — /release 스킬로 만든 버전)
cd /home/ubuntu/<proj>/src
git fetch --all --tags && git checkout <tag>      # 예: v1.4.0
git log -1 --oneline

# 3. 의존성 (requirements 바뀐 경우)
source /home/ubuntu/<proj>/bin/activate
pip install -r requirements.txt

# 4. 마이그레이션 + 정적파일 — prod 모드로
cd /home/ubuntu/<proj>/src
<PROJ>_PRODUCTION=True python manage.py migrate --noinput
<PROJ>_PRODUCTION=True python manage.py collectstatic --noinput

# 5. 서비스 재시작 (sudo)
sudo systemctl restart <proj>-prod.service
# celery 쓰면: sudo systemctl restart <proj>-celery.service <proj>-celery-beat.service

# 6. 확인
sudo systemctl status <proj>-prod.service --no-pager
journalctl -u <proj>-prod.service -n 50 --no-pager
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:80yy/    # 200 기대

짚을 점:

  • <PROJ>_PRODUCTION=True — 프로젝트별 대문자 prefix 환경변수가 prod settings(settings/production.py, .env.production, DB <proj>_prod)를 켠다. 이게 빠지면 dev 설정으로 돈다. (M1 L01 settings 분리와 연결)
  • prod DB 는 <proj>_prod — migrate 대상이 prod 데이터다. 스키마 변경이 크면 백업 확인.
  • 포트 80yy 는 infrastructure/server-environment.md 포트 표에서 확인. 외부는 https://<proj>.popupstudio.ai.
  • 롤백: git checkout <직전-tag> → sudo systemctl restart <proj>-prod.service. (마이그레이션 되돌리기는 데이터 손실 주의)

5. 추적은 실제로 어디에 남나

본인 계정으로 했다면 별도 노력 없이 4곳에 남는다.

추적 정보 어디에
누가 접속했나 /var/log/auth.log — Accepted publickey for gun
누가 서비스 재시작/sudo 했나 auth.log / journald (실행 유저 기록)
어떤 버전(코드)을 올렸나 git log 의 commit/tag (+ 작성자)
DB 작업 postgres 개인 role (peer auth)

→ "golden 을 어제 누가 v1.4.0 으로 올렸나?" 가 개인 단위로 답이 된다.

반대로 공유 ubuntu 로 하면 이 추적이 전부 "ubuntu"로 뭉개진다. 그래서 규칙은 하나다 — prod 배포는 반드시 본인 계정으로.


6. 릴리즈는 그 프로젝트 세션에서 실행한다

여기까지가 "어떻게 배포하나"였다면, 이건 "어디서(어느 Claude 세션에서) 배포하나"다 — 실제로 사고가 날 뻔했던 지점이다.

/release 스킬, CLAUDE.md, 그리고 prod 설정(서비스명·경로·박스 IP)은 전부 프로젝트 스코프다. 중요한 건, standarda-template 으로 만든 프로젝트는 저마다 자기 /release 스킬 사본을 갖고 있고, 그 사본들이 시간이 지나며 서로 drift(어긋남) 한다는 점이다. Claude 세션은 자기가 루트를 둔 프로젝트(cwd)의 그 사본만 로드한다 — popax 세션은 popax 의 /release 와 prod 정보만 알고, knowclaw 의 것은 모른다. (스킬이 프로젝트 vs 글로벌로 갈리는 배경은 L11 참고.)

그래서 A 프로젝트 세션에서 B 프로젝트를 배포하면, B 의 최신 절차가 아니라 그 세션에 로드된 A 의(또는 stale 한) 스킬·경로·서비스명을 쓰게 된다.

실제 예: popax 세션에서 knowclaw 를 배포하려 한다고 하자. 그 세션에 로드된 /release 는 popax 의 사본이라 popax 경로·서비스명(popax-prod.service 등)을 박고 있고, 게다가 한동안 prod 박스 이전(2-box 전환)을 모른 채 stale 해 있기도 했다. knowclaw 는 자기만의 /release 사본·CLAUDE.md·prod 설정이 따로 있는데, popax 세션에서 끌어다 쓰면 엉뚱한 경로·서비스로 배포가 조용히 빗나간다.

규칙은 하나다 — 배포는 그 프로젝트 디렉토리에 루트를 둔 세션에서, 그 프로젝트의 /release 스킬로 한다.

  • popax 재배포 → /home/ubuntu/popax/src 세션의 /release
  • knowclaw 재배포 → /home/ubuntu/knowclaw/src 세션의 /release (popax 세션의 /release 가 아니라)

같은 함정의 사촌이 "이식은 복붙이 아니다" — 박스 간이든 프로젝트 간이든, 다른 곳의 가정(경로·서비스명·외부 의존성)을 그대로 가져오면 조용히 깨진다. "최신 스킬·prod 정보는 그 프로젝트 세션에만 로드된다"를 기본값으로 두라.


오늘 정리 + 다음

  • 정리: prod(52.79.132.206)은 dev 와 분리된 별도 박스다. 전원이 배포할 수 있게 하되 개인 Linux 계정으로 접속하게 해서(공유 ubuntu 아님) 풀 권한이어도 누가·언제·어떤 버전이 auth.log·git·postgres 에 개인 단위로 남는다. 표준 절차는 본인 계정 SSH → git checkout <tag> → <PROJ>_PRODUCTION=True migrate/collectstatic → sudo systemctl restart <proj>-prod.service → status/journalctl/curl 확인.
  • 흔한 함정: ① 공유 ubuntu 로 배포 — 추적이 사라진다. 항상 본인 계정. ② <PROJ>_PRODUCTION=True 누락 — dev 설정·dev DB 로 돌아버린다. ③ dev/prod 박스 혼동 — 이 절차는 prod 박스 전용(dev 는 /dev 스킬). ④ 자동 배포가 걸린 프로젝트(popax)는 수동 배포와 충돌할 수 있다. ⑤ 다른 프로젝트 세션에서 release — 스킬·경로·서비스명이 stale/wrong 일 수 있다. 배포는 그 프로젝트 디렉토리에 루트를 둔 세션의 release 스킬로 한다(§6).
  • 다음 시간: prod 최초 셋업(신규 prod 올리기 — DB 분리·gunicorn systemd·Apache vhost·certbot·Route53)은 infrastructure/prod-server-setup.md 기반으로 별도 강의에서. 이번 건 "이미 있는 prod 의 재배포"였다.
  • 자습 권장: 팀 wiki 의 infrastructure/prod-release.md(절차 원문)·infrastructure/prod-server-host.md(호스트·개인 계정 접속)·infrastructure/sudo-policy.md(권한 배경)을 읽어 본다. 그리고 "내가 맡은 프로젝트의 prod 서비스 이름·포트·DB 이름이 뭔지" 직접 systemctl list-units '*-prod.service' 로 찾아 본다.
이 강의를 학습하셨나요?