Case study

cxdm CLI & DevOps

infra·94 commits·2025–2026

Architecture

pinch to zoom · drag to pan

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 부재 등). 운영을 안정화하려면 언어 자체를 바꿔야 했다.

pinch to zoom · drag to pan

목표는 세 가지로 좁혔다.

  1. 단일 진실원 + 단일 CLIservices.config.yml 한 곳에서 서비스 정의를 읽어 모든 명령이 동작하게.
  2. Cloud Run + Vercel 통합 추상화 — 양쪽 플랫폼의 배포·시크릿·워크플로우를 같은 인터페이스로.
  3. 신규 서비스 온보딩 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~12bash/Taskfile + GCP SA / Secret Manager 스크립트, GitHub CLI 기반 secrets 관리
2026.01단일 브랜치 setup·MCP DevOps 도구 도입으로 인터페이스 정돈
2026.03.27Python Click rewrite — 하루 안에 scaffold + 7개 command + install.sh
2026.04workflows create 옵션 확장(env/VPC/deploy_trigger), 범용 시크릿 업로드 워크플로우, cxdm env 통합

services.config.yml 단일 진실원

역할
service_nameGCP Secret 이름·Cloud Run 리비전·Vercel 프로젝트 별칭의 단일 base
platformcloud-run / vercel → workflow 템플릿 분기
envsdev/stage/prod 환경별 SA·시크릿 키·VPC 옵션
secretsSecret Manager에 동기화할 키 목록 (네이밍 검증/자동 수정)

services.config.yml을 읽는 한 곳을 통과시키니, 서비스 이름이 바뀌면 workflow·SA·시크릿 라벨이 같이 따라가는 구조가 됐다 (2025.11 airline-feeairline-fee-fe 리네이밍 시 한 곳만 고침). config 변경이 곧 인프라 변경이다.

pinch to zoom · drag to pan

어댑터 계층 — Click 명령은 도메인만 안다

commands/* 는 도메인 의도("env push dev")만 표현하고, 실제 부수효과는 어댑터에 위임한다.

  • core/github.pygh CLI를 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에 따라 분기한다.

platformupload 경로비고
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한 서비스 다중 환경 자동
최소 인스턴스 · VPC04-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.29GitHub CLI 기반 secrets 관리 + multiline 보존수동 web UI 입력 제거
2025.11.19Secret 네이밍 검증·자동 수정naming 사고 사전 차단
2025.12.03mcp-devops CLI 도입 → AI 에이전트가 동일 도구 호출운영 절차의 에이전트 위임 가능
2026.03.27Python Click rewrite — 모든 명령 단일 entrypoint로 통합사용자 문서 1벌
2026.04.21cxdm env 통합 명령 (secrets/vercel deprecation)API 표면 축소
pinch to zoom · drag to pan

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 syncGitHub ↔ GCP ↔ Vercel ↔ .env
AI 에이전트 위임mcp-devops 도구화로 절차 자체가 호출 가능한 함수

기술적·운영적으로 가져간 학습은 네 가지다.

  1. bash는 운영 안정성의 천장이 낮다. macOS zsh / Ubuntu bash / GitHub Actions runner 사이의 미세 차이가 매번 사고로 돌아왔다. Python Click + InquirerPy + Rich로 옮긴 순간 인자 파싱·에러 메시지·인터랙티브 UX가 한 번에 해결됐고, 9개월 누적 spec이 있었기에 rewrite는 하루로 닫혔다.
  2. 단일 진실원이 있으면 리네이밍이 두렵지 않다. airline-feeairline-fee-fe처럼 서비스명을 바꾸는 일이 services.config.yml 한 줄 수정으로 끝난다. workflow·SA·시크릿 라벨·CI dispatch 대상이 모두 같은 키를 본다.
  3. chicken-and-egg는 인자로 끊는다. 새 서비스를 main에 머지하기 전에는 workflow가 안 도는 모순을 --service-name 인자로 우회 — 동기화·검증을 작업 브랜치에서 가능하게 만들었다. CI를 "있는 그대로 쓰지 않고 우회로를 설계할 수 있다"는 게 큰 학습이었다.
  4. 운영 사고는 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 에이전트가 만든 워크트리가 자동으로 정리되게.