cxdm CLI & DevOps
Architecture
Stack
- Python 3.11+
- Click 8
- Rich 13
- InquirerPy
- PyYAML / Ruamel.yaml
- uv
External integrations
- GitHub API (gh CLI)
- GCP Secret Manager + Cloud Run
- Vercel (npx vercel)
- pnpm / uv
Highlights
- Cloud Run + Vercel unified ops
- GitHub Actions workflow generator
- 9-step GCP SA automation
문제정의
CX DM 모노레포는 항공·호텔·CS·챗봇 도메인이 한 저장소에 모여 빠르게 분기·통합되는 구조다. 초기에는 서비스별로 bash 스크립트를 손으로 복사해 배포·환경변수·워크플로우를 관리했지만, 서비스가 3개를 넘어가는 순간 다음 비대칭이 누적됐다.
| 한계 | 영향 |
|---|---|
| 새 서비스 추가 시 2~3시간 수작업 (Cloud Run · Secret · workflow · SA) | 실험 비용이 높아 서비스 분리를 미루게 됨 |
| 서비스마다 Secret Manager 시크릿 (예약·정책·키별 분리, 총 48개) | 권한·rotate·감사 추적이 분산 |
| Cloud Run·Vercel 배포 절차가 따로 | 동일 명령으로 dev/stage/prod를 다룰 수 없음 |
| GitHub Actions workflow를 서비스마다 복붙 | reusable 변경 시 9곳을 수정 |
bash + Taskfile 묶음 (2025.08~12) 으로 한 차례 정리했지만, 인자 파싱·에러 핸들링·인터랙티브 프롬프트가 부재해 운영자가 매번 README를 다시 읽어야 했다. 더 큰 문제는 macOS / WSL / Ubuntu Runner 사이의 zsh·bash 차이로 같은 스크립트가 환경마다 깨졌다는 점이다 (zsh read 문법, mapfile 부재 등). 운영을 안정화하려면 언어 자체를 바꿔야 했다.
목표는 세 가지로 좁혔다.
- 단일 진실원 + 단일 CLI —
services.config.yml한 곳에서 서비스 정의를 읽어 모든 명령이 동작하게. - Cloud Run + Vercel 통합 추상화 — 양쪽 플랫폼의 배포·시크릿·워크플로우를 같은 인터페이스로.
- 신규 서비스 온보딩 1자릿수 분 — 새 서비스를 5분 안에 배포 가능한 상태로 끌어올린다.
구현
bash/Taskfile → Python Click 재작성 (2026-03-27)
3월 27일 하루에 scaffold → core 유틸 → UI 유틸 → doctor → Wave 2 명령어 (services·dev·secrets·vercel·workflows·gcp·project) → bootstrap install.sh → README 까지 한 번에 푸시했다. 8개월간 누적된 bash 노하우(시크릿 네이밍·SA 권한·workflow 입력)가 spec으로 굳어 있었기에 재작성이 그만큼 빠르게 닫혔다.
| 시기 | 주요 단계 |
|---|---|
| 2025.08~12 | bash/Taskfile + GCP SA / Secret Manager 스크립트, GitHub CLI 기반 secrets 관리 |
| 2026.01 | 단일 브랜치 setup·MCP DevOps 도구 도입으로 인터페이스 정돈 |
| 2026.03.27 | Python Click rewrite — 하루 안에 scaffold + 7개 command + install.sh |
| 2026.04 | workflows create 옵션 확장(env/VPC/deploy_trigger), 범용 시크릿 업로드 워크플로우, cxdm env 통합 |
services.config.yml 단일 진실원
| 키 | 역할 |
|---|---|
service_name | GCP Secret 이름·Cloud Run 리비전·Vercel 프로젝트 별칭의 단일 base |
platform | cloud-run / vercel → workflow 템플릿 분기 |
envs | dev/stage/prod 환경별 SA·시크릿 키·VPC 옵션 |
secrets | Secret Manager에 동기화할 키 목록 (네이밍 검증/자동 수정) |
services.config.yml을 읽는 한 곳을 통과시키니, 서비스 이름이 바뀌면 workflow·SA·시크릿 라벨이 같이 따라가는 구조가 됐다 (2025.11 airline-fee → airline-fee-fe 리네이밍 시 한 곳만 고침). config 변경이 곧 인프라 변경이다.
어댑터 계층 — Click 명령은 도메인만 안다
commands/* 는 도메인 의도("env push dev")만 표현하고, 실제 부수효과는 어댑터에 위임한다.
core/github.py—ghCLI를 subprocess로 감싸 multiline 시크릿·workflow dispatch·검증 처리core/shell.py— gcloud·vercel·pnpm·uv 호출. 에러는Result형태로 반환해 호출부가console로 렌더ui/interactive.py— InquirerPy 기반 환경 선택 / 서비스 다중 선택 프롬프트ui/console.py— Rich 기반 panel·table·spinner
도메인 명령과 어댑터를 분리하니 gh CLI 인터페이스 변경 · Vercel CLI 옵션 변경이 한 곳 수정으로 끝났다.
Cloud Run + Vercel 통합 — cxdm env
가장 자주 쓰는 명령이자, 가장 까다로운 통합 지점이었다. 같은 cxdm env push <service> <env>가 platform에 따라 분기한다.
| platform | upload 경로 | 비고 |
|---|---|---|
cloud-run | .env.<env> → 프로젝트당 1 Secret (auto-label) | 2025-10-28 다중 시크릿 구조에서 단일 시크릿+라벨로 개편 |
vercel | .env.<env> → vercel env add (npx) | multiline 개행 보존 처리 (10-29) |
| 둘 다 | GitHub Actions secrets 동기화 | service_name 직접 전달로 신규 서비스 chicken-and-egg 해결 (04-09) |
운영 중에 발견된 chicken-and-egg — workflow가 main에 머지되어야 trigger되는데, 신규 서비스는 main에 들어가기 전이라 동기화 불가 — 는 --service-name 인자로 끊었다. workflows가 현재 브랜치를 dispatch 대상으로 쓰도록 바꿔 (11-03) 작업 브랜치에서 검증 가능해졌다.
Workflow 생성기 + Reusable Workflow
cxdm workflows create <service> 가 services.config.yml 을 읽어 deploy·sync 워크플로우 YAML을 생성한다. 실제 빌드·배포 로직은 공통 reusable workflow 1벌에 모여 있고, 서비스별 파일은 uses: ...@main + inputs: 뿐. workflow 코드 80% 감소는 이 구조에서 나왔다.
옵션은 운영 요구에 따라 점진 확장됐다.
| 옵션 | 도입 | 효과 |
|---|---|---|
env 선택 (dev/stage/prod) | 04-08 | 한 서비스 다중 환경 자동 |
| 최소 인스턴스 · VPC | 04-08 | 콜드스타트·내부망 옵션 표준화 |
deploy_trigger (path/tag/both) | 04-14 | 톡톡 챗봇 같은 태그 배포 + 경로 변경 자동 배포 혼용 |
9-step GCP SA automation — cxdm gcp
새 서비스에 필요한 SA 권한 9단계 — 생성·키 발급·Secret Manager roles/secretmanager.admin·Cloud Run roles/run.admin·Artifact Registry writer·etc — 를 한 명령으로 묶었다. 10-29 사고("SA가 Secret 읽기만 가능하고 admin 없어 신규 secret 생성 실패") 가 admin 권한 부여 단계로 영구 기록됐다.
출시
big-bang 없이 bash → Click 점진 대체로 갔다. 8개월간 운영자(주로 본인) 가 매일 쓰던 절차였기에, 명령 단위로 cxdm에 흡수하고 deprecation 처리하면서 신뢰를 쌓았다.
| 시기 | 변경 | 운영 효과 |
|---|---|---|
| 2025.10.22~30 | .env ↔ Secret Manager 양방향, 프로젝트당 1 secret · auto-label · 동적 설정 | Secret 수가 처음 줄기 시작 (48 → ~20) |
| 2025.10.29 | GitHub CLI 기반 secrets 관리 + multiline 보존 | 수동 web UI 입력 제거 |
| 2025.11.19 | Secret 네이밍 검증·자동 수정 | naming 사고 사전 차단 |
| 2025.12.03 | mcp-devops CLI 도입 → AI 에이전트가 동일 도구 호출 | 운영 절차의 에이전트 위임 가능 |
| 2026.03.27 | Python Click rewrite — 모든 명령 단일 entrypoint로 통합 | 사용자 문서 1벌 |
| 2026.04.21 | cxdm env 통합 명령 (secrets/vercel deprecation) | API 표면 축소 |
install.sh 한 줄(uv tool install)로 팀원 PC에 배포되므로, rewrite 전후로도 동일한 명령 표면을 유지했다 — 사용자 입장에서 "cxdm이 더 빨라지고 에러 메시지가 친절해졌다" 정도의 체감으로 흡수됐다.
결과학습
| 항목 | 결과 |
|---|---|
| 통합 운영 서비스 | 9개 (Cloud Run 8 + Vercel 1) |
| 신규 서비스 온보딩 | 2~3시간 → 5분 (96% 단축) |
| Secret Manager 시크릿 수 | 48 → 12 (75% 감소) |
| GitHub Actions workflow 코드 | 80% 감소 (서비스별 → reusable 308 lines) |
| 양방향 secrets sync | GitHub ↔ GCP ↔ Vercel ↔ .env |
| AI 에이전트 위임 | mcp-devops 도구화로 절차 자체가 호출 가능한 함수 |
기술적·운영적으로 가져간 학습은 네 가지다.
- bash는 운영 안정성의 천장이 낮다. macOS zsh / Ubuntu bash / GitHub Actions runner 사이의 미세 차이가 매번 사고로 돌아왔다. Python Click + InquirerPy + Rich로 옮긴 순간 인자 파싱·에러 메시지·인터랙티브 UX가 한 번에 해결됐고, 9개월 누적 spec이 있었기에 rewrite는 하루로 닫혔다.
- 단일 진실원이 있으면 리네이밍이 두렵지 않다.
airline-fee→airline-fee-fe처럼 서비스명을 바꾸는 일이services.config.yml한 줄 수정으로 끝난다. workflow·SA·시크릿 라벨·CI dispatch 대상이 모두 같은 키를 본다. - chicken-and-egg는 인자로 끊는다. 새 서비스를 main에 머지하기 전에는 workflow가 안 도는 모순을
--service-name인자로 우회 — 동기화·검증을 작업 브랜치에서 가능하게 만들었다. CI를 "있는 그대로 쓰지 않고 우회로를 설계할 수 있다"는 게 큰 학습이었다. - 운영 사고는 SA 권한 단계와 네이밍 검증으로 기록된다. 10-29의 "admin 권한 없어 신규 secret 생성 실패"가 9-step SA setup에, secret naming 사고가 11-19의 검증·자동 수정 단계에 영구 기록됐다. CLI는 "사고 후 코드에 박는 메모"의 가장 견고한 형태였다.
Next
- MCP DevOps 도구 확장 —
mcp-devops로 cxdm 명령을 AI 에이전트가 호출 가능한 함수로 노출했다. workflows 트리거·secret diff·SA 상태 조회를 에이전트 위임으로 끌어올려, 신규 서비스 추가를 자연어 → 검증된 절차 실행으로 바꾼다. - deploy_trigger 모드 운영 데이터화 —
path/tag/both옵션이 어떤 서비스에 어떤 모드가 적합한지 운영 로그로 측정해, 신규 서비스 추가 시 모드를 자동 추천한다. - Pepe PR 워크트리 cleanup watcher 일반화 — 03-24 도입한 워크트리 cleanup을 cxdm 표준 명령으로 끌어들여, AI 에이전트가 만든 워크트리가 자동으로 정리되게.