15.3 DevOps — 파이프라인과 관측으로 배포 위험을 통제한다
사람이 손으로 빌드·테스트·배포하면 시스템은 사람의 실패율로 배포된다. DevOps는 개발(변경을 원함)과 운영(안정을 원함) 사이의 벽을 자동화로 허무는 접근이고, 그 구현이 CI/CD 파이프라인·IaC·관찰 가능성 스택이다. 이 문서는 이 셋을 별개 도구가 아니라 검증을 자동화하고, 노출의 폭발 반경을 통제하고, 노출 이후를 관측하는 하나의 피드백 시스템으로 설계한다.
학습 목표
- CI/CD 파이프라인의 각 단계가 사는 증거와 그 순서(싼 증거 먼저, fail-fast)의 근거를 설명한다.
- 배포 전략(rolling·blue-green·canary)을 폭발 반경·롤백 속도·자원 비용으로 선택한다.
- IaC의 선언적 모델과 상태·드리프트·멱등성을 근거로 인프라 변경을 판단한다.
- 메트릭·로그·트레이스 세 신호의 비용과 답할 수 있는 질문의 범위를 구분하고 관측 스택을 설계한다.
배경: 개발과 운영 사이의 벽
전통적 조직은 개발팀과 운영팀을 나눴다. 개발팀은 변경을 만들어 "던지고", 운영팀은 그것을 받아 배포·운영했다. 두 팀의 인센티브는 정반대다. 개발팀은 변경(기능)으로 평가받고, 운영팀은 안정성으로 평가받는다. 변경은 안정성의 최대 위협이므로, 이 구조는 갈등을 내장한다. 운영팀은 배포를 늦추고 절차를 늘려 방어하고, 개발팀은 그 절차를 우회하려 한다. 14.3에서 본 악순환 — 큰 배포가 큰 장애를, 큰 장애가 더 무거운 절차를 부르는 — 이 이 벽에서 자란다.
DevOps는 이 벽을 없애는 문화이자 실천이다. 핵심은 도구가 아니라 책임의 통합이다. 변경을 만든 팀이 그 변경의 운영까지 책임지면("you build it, you run it"), 안정성이 개발의 제약이 아니라 개발의 일부가 된다. 그러나 이 통합이 실효를 내려면 배포가 값싸고 안전하고 가역적이어야 한다. 그것을 만드는 것이 자동화다. 주문 확정 API 장애에서 설정·의존성 변경이 검증 없이 전체에 도달했다는 것은, 바로 이 자동화된 검증·노출 통제가 없었다는 뜻이다. 이 문서는 그 자동화를 인트로의 검증·노출·관측 계약으로 설계한다.
핵심 개념
CI: 통합을 자주, 자동으로 검증한다
지속적 통합(Continuous Integration)은 도구 이름이 아니라 실천이다. 원래 정의는 모든 개발자가 mainline에 자주(적어도 하루 한 번) 통합하고, 각 통합을 자동 빌드·테스트로 검증하는 것이다. 15.1의 통합 빈도, 15.2의 배치 크기와 정확히 같은 변수다. CI 서버(Jenkins, GitHub Actions 등)는 이 실천을 집행하는 수단일 뿐, 서버를 설치했다고 CI를 하는 것이 아니다. 개발자가 여전히 몇 주치 브랜치를 몰아 머지하면 통합은 자주 일어나지 않는다.
CI 파이프라인의 단계 순서는 14.1의 원칙 — 싼 증거를 먼저 산다 — 을 그대로 따른다.
커밋 push
│
▼
[정적 검사] 린트·타입 체크·포맷 ← 수초. 가장 싸고 빠른 실패
│
▼
[빌드] 컴파일·아티팩트 생성 ← 수십 초
│
▼
[단위 테스트] 격리된 로직 검증 ← 수십 초~수분
│
▼
[통합 테스트] 실제 의존성과 검증 ← 수분. 비싸고 느림
│
▼
[아티팩트 발행] 불변 이미지·패키지 ← 이후 단계가 재사용이 순서의 논리는 fail-fast다. 린트 오류를 통합 테스트까지 간 뒤 발견하는 것은 낭비다. 싸고 빠른 검사를 앞에 두어, 흔한 실패를 몇 초 안에 되돌린다. 순서를 정하는 기준은 (검출하는 결함의 흔함 × 검사 비용)이다. 흔한 결함을 싸게 잡는 검사가 앞에 온다.
두 가지 속성이 CI를 신뢰할 수 있게 만든다. 재현 가능한 빌드(같은 입력이 같은 아티팩트를 만듦 — 의존성 버전 고정, 15.1의 lockfile)와 아티팩트 불변성(한 번 만든 아티팩트를 이후 모든 환경이 그대로 재사용, 환경마다 다시 빌드하지 않음)이다. 후자가 "build once, deploy many"의 근거다. 스테이징에서 검증한 아티팩트와 프로덕션에 배포하는 아티팩트가 비트 단위로 같아야, 스테이징의 검증이 프로덕션에 대한 증거가 된다.
CD: 배포를 반복 가능·가역적으로 만든다
지속적 전달(Continuous Delivery)은 mainline의 모든 변경이 언제든 배포 가능한 상태로 유지되는 것이고, 지속적 배포(Continuous Deployment)는 검증을 통과한 변경을 자동으로 운영까지 내보내는 것이다. 둘의 차이는 마지막 "배포" 버튼을 사람이 누르느냐 자동이냐다. 어느 쪽이든 목표는 같다. 배포를 드물고 두려운 이벤트에서 일상적이고 지루한 작업으로 바꾸는 것이다.
여기서 14.3에서 본 배포와 릴리스의 분리가 핵심이 된다. 배포(deploy)는 코드를 운영 환경에 올리는 것이고, 릴리스(release)는 그 기능을 사용자에게 노출하는 것이다. 기능 플래그로 이 둘을 분리하면, 15.1의 trunk-based가 요구했던 "미완성 코드를 mainline에 두기"가 안전해진다. 코드는 매일 배포되지만 기능은 플래그 뒤에 숨어 있다가, 준비되면 코드 배포 없이 플래그만 켜서 노출된다. 롤백도 배포 되돌림이 아니라 플래그 끄기로, 초 단위에 폭발 반경을 0으로 만든다.
아티팩트는 환경을 거치며 **승격(promotion)**된다. 같은 이미지가 dev → staging → production으로 이동하며 각 환경에서 검증되고, 재빌드 없이 다음 환경으로 넘어간다. 환경 차이는 코드가 아니라 설정(환경 변수·시크릿)으로만 주입한다. 이것이 Twelve-Factor App의 config 분리 원칙이며, "스테이징에서는 됐는데 프로덕션에서 깨진다"의 상당 부분이 이 원칙 위반(환경마다 다른 아티팩트·다른 빌드)에서 온다.
배포 전략: 폭발 반경을 제어한다
배포는 구 버전에서 신 버전으로의 전환이고, 전략은 그 전환을 어떻게 하느냐다. 14.3에서 카나리의 예산 소진 계산(전체 배포는 43분에 월 예산 소진, 1% 카나리는 같은 결함을 예산 1.4%로 차단)을 다뤘다. 여기서는 각 전략의 구현 메커니즘과 트레이드오프를 본다.
| 전략 | 방식 | 폭발 반경 | 롤백 속도 | 자원 비용 | 주의점 |
|---|---|---|---|---|---|
| Recreate | 구 버전 내리고 신 버전 올림 | 100% + 다운타임 | 느림(재배포) | 1배 | 다운타임 허용 시에만 |
| Rolling | 인스턴스를 조금씩 교체 | 부분(교체 비율) | 중간 | 1배 | 두 버전이 잠시 공존 |
| Blue-green | 신 버전 전체를 별도로 띄우고 트래픽 전환 | 전환 순간 100% | 매우 빠름(트래픽 되돌림) | 2배 | 두 배 자원, DB 공유 문제 |
| Canary | 소수 트래픽에 신 버전 노출 후 점진 확대 | 노출 비율만큼 | 빠름 | 1배+α | SLI 자동 판정 필요 |
전략 선택은 취향이 아니라 폭발 반경·롤백 속도·자원 비용의 트레이드오프다. blue-green은 롤백이 트래픽 되돌림이라 가장 빠르지만 두 배 자원이 들고, 두 환경이 같은 DB를 공유하면 스키마 호환성 문제가 남는다. canary는 자원 효율적이고 폭발 반경을 노출 비율로 정밀 제어하지만, "카나리가 정상인가"를 SLI로 자동 판정하지 못하면 사람의 눈과 반응 속도에 묶인다. 배포 방식은 에러 버짓 정책의 집행 수단으로 선택한다(14.3).
이 모든 전략이 전제하는 것이 하나 있다. 두 버전이 잠시 공존해도 안전해야 한다. rolling·canary·blue-green 모두 전환 중 구·신 버전이 동시에 같은 데이터·스키마를 다룬다. 특히 데이터베이스 스키마 변경에서 이 전제가 깨진다. 컬럼을 삭제하는 마이그레이션을 코드 배포와 동시에 하면, 롤백 시 구 코드가 없는 컬럼을 참조해 깨진다. 해법은 expand-contract(parallel change) 패턴이다.
1. Expand : 새 컬럼을 추가만 한다(구 코드도 신 코드도 동작)
2. Migrate: 코드를 새 컬럼을 쓰도록 배포하고 데이터를 채운다
3. Contract: 구 컬럼을 참조하는 코드가 모두 사라진 뒤 컬럼을 제거한다각 단계가 이전 버전과 호환되므로 언제든 롤백 가능하다. 스키마 변경과 코드 배포를 원자적으로 묶으려는 시도가 롤백 불가능을 만들고, 그것을 풀어 여러 호환 단계로 나누는 것이 15.1의 "작은 배치"를 데이터 계층에 적용한 것이다. 무중단 배포의 상당 부분이 애플리케이션 배포 전략이 아니라 이 스키마 진화 규율에 달렸다.
IaC: 인프라를 선언하고 버전 관리한다
인프라를 콘솔에서 손으로 만들면, 그 인프라의 진짜 상태는 아무 데도 기록되지 않는다. 누가 무엇을 왜 바꿨는지 알 수 없고, 같은 환경을 재현할 수 없으며, 스테이징과 프로덕션이 미묘하게 달라진다. IaC(Infrastructure as Code)는 인프라를 코드로 선언해 15.1의 버전 관리 아래로 가져온다. 인프라 변경도 커밋·리뷰·이력·롤백의 대상이 된다.
명령형과 선언형의 구분이 핵심이다.
- 명령형(imperative): "이 명령들을 실행하라"(셸 스크립트,
apt install나열). 문제는 현재 상태에 의존한다는 것이다. 이미 설치돼 있으면? 부분 실패로 절반만 됐으면? 스크립트를 두 번 돌리면? - 선언형(declarative): "최종 상태가 이래야 한다"(Terraform, Kubernetes 매니페스트). 도구가 현재 상태와 원하는 상태(desired state)의 차이를 계산해 필요한 변경만 적용한다(reconciliation).
선언형이 IaC의 주류인 이유는 멱등성(idempotency) 때문이다. 같은 선언을 몇 번 적용해도 결과가 같다. 이미 원하는 상태면 아무것도 하지 않고, 다르면 차이만 좁힌다. 이 성질이 챕터 10의 재시도·수렴 개념과 같은 뿌리이며, 부분 실패 후 다시 적용해도 안전하게 만든다.
선언형 IaC의 핵심 개념은 **상태(state)**와 **드리프트(drift)**다. 도구는 자신이 만든 리소스의 상태를 어딘가에 기록한다(Terraform의 state 파일 등). 이 상태가 "코드가 선언한 것"과 "실제 인프라"를 잇는다. 문제는 누군가 콘솔에서 수동으로 인프라를 바꿀 때다. 그러면 실제 인프라가 코드·상태와 어긋나는 드리프트가 생긴다. 드리프트는 IaC의 신뢰를 무너뜨린다. 코드가 더 이상 진실이 아니게 되기 때문이다. 그래서 성숙한 운영은 수동 변경을 금지하고 모든 변경을 코드로만 하며, 드리프트를 탐지해 경보한다.
이 원리를 극단으로 밀면 GitOps다. Git 저장소가 인프라의 유일한 진실(single source of truth)이고, 에이전트가 클러스터의 실제 상태를 Git의 선언과 지속적으로 일치시킨다. 인프라 변경 = Git 커밋이므로, 15.1의 리뷰·이력·롤백이 인프라에 그대로 적용된다. 함께 오는 것이 **불변 인프라(immutable infrastructure)**다. 서버를 패치·수정(mutable)하는 대신, 새 이미지로 교체(immutable)한다. 실행 중인 서버를 손대지 않으므로 드리프트가 원천적으로 줄고, "이 서버는 왜 다른가"라는 눈송이(snowflake) 서버 문제가 사라진다. 배포 전략의 blue-green·rolling이 이 불변 교체 위에서 자연스럽게 성립한다.
관찰 가능성: 세 신호로 미지의 질문에 답한다
배포 이후 시스템이 실제로 어떻게 동작하는지를 아는 것이 인트로의 마지막 계약, 관측이다. 모니터링과 관찰 가능성(observability)의 구분이 중요하다. 모니터링은 미리 정한 질문에 답한다(CPU가 임계를 넘었나, 에러율이 올랐나). 관찰 가능성은 미리 정하지 못한 질문에 답할 수 있는 성질이다(왜 이 특정 사용자군의 이 엔드포인트만 어제부터 느린가). 분산 시스템에서는 장애의 형태를 미리 다 알 수 없으므로, 새로운 질문을 사후에 던질 수 있는 능력이 필요하다.
이 능력을 만드는 세 신호가 있고, 각각 비용과 답할 수 있는 질문의 범위가 다르다.
| 신호 | 형태 | 답하는 질문 | 비용 구조 | 카디널리티 |
|---|---|---|---|---|
| 메트릭(metric) | 시간에 따른 집계 수치 | "얼마나?" — 전체 추세, 비율 | 싸다(집계, 고정 시계열) | 낮음(라벨 조합이 시계열 수를 폭발시킴) |
| 로그(log) | 개별 이벤트의 기록 | "무슨 일이?" — 특정 사건의 상세 | 비싸다(이벤트마다 저장) | 높음(임의 필드) |
| 트레이스(trace) | 요청의 서비스 간 경로 | "어디서?" — 지연·실패의 위치와 인과 | 중간(보통 샘플링) | 높음(요청 단위) |
세 신호는 경쟁이 아니라 역할 분담이다. 메트릭은 14.3의 SLI가 놓이는 층으로, 싸게 집계되어 "지금 서비스가 목표를 지키는가"를 판정하고 burn rate 경보를 울린다. 그러나 집계라 개별 사건의 원인은 알려주지 않는다. 로그는 특정 사건의 상세를 담지만, 분산 시스템에서 한 요청이 여러 서비스의 로그에 흩어지면 재구성이 어렵다. 트레이스는 이 간극을 메운다. 하나의 요청에 추적 ID를 부여해 서비스 A→B→C를 지나는 경로 전체와 각 구간의 시간을 이어, 챕터 0의 주문 확정 API에서 "p99가 어느 구간에서 9초가 됐나"를 챕터 9의 네트워크 경로 진단처럼 요청 단위로 보여준다.
관측 설계의 실질적 제약은 카디널리티(cardinality) 비용이다. 메트릭에 라벨(사용자 ID, 요청 경로 등)을 붙이면 라벨 값의 조합마다 별도 시계열이 생긴다. 높은 카디널리티 라벨(사용자 ID 같은)을 메트릭에 붙이면 시계열 수가 폭발해 저장·질의 비용이 감당 불가능해진다. 그래서 "누가"에 해당하는 고유 식별자는 메트릭이 아니라 로그·트레이스에 담는다. 세 신호의 분담은 이 비용 구조에서 나온다. 집계로 족한 질문은 메트릭(싸다), 개별 식별이 필요한 질문은 로그·트레이스(비싸지만 카디널리티 무제한, 대신 샘플링으로 비용 관리)에 둔다. OpenTelemetry는 이 세 신호의 수집·전파를 벤더 중립 표준으로 통일해, 계측을 특정 백엔드에 묶이지 않게 한다.
실무 관점
CI가 느리면 팀이 우회한다
파이프라인이 30분씩 걸리면 개발자는 CI를 기다리지 않고 여러 변경을 몰아서 밀거나, 검증 전에 머지하거나, 로컬에서만 확인하고 넘어간다. 느린 CI는 통합 빈도를 낮춰 15.2의 큰 배치 문제를 되살린다. 그래서 CI 속도는 편의가 아니라 전달 경로의 건강 조건이다. 대응: 단계 병렬화, 변경된 부분만 테스트, 느린 통합·E2E는 커밋마다가 아니라 별도 주기로, 캐시 활용. "CI가 느려서 다들 우회한다"는 도구 문제가 아니라 통합 빈도를 죽이는 구조적 위험이다.
flaky 파이프라인은 신뢰를 죽인다
14.1에서 본 flaky 테스트는 CI에서 특히 파괴적이다. 통과해야 할 빌드가 무작위로 실패하면, 팀은 "재실행"을 반사적으로 누르게 되고, 그 순간 CI는 게이트이기를 그친다. 진짜 실패도 "또 flaky겠지"로 무시된다. 경보 채널의 신뢰 하락(14.3)과 같은 메커니즘이다. flaky 빌드는 격리·수정하거나 제거하며, 재실행이 관행이 되기 전에 다뤄야 한다.
관측을 "나중에"로 미루면 장애 때 눈이 없다
계측은 장애가 나기 전에 있어야 쓸모가 있다. 장애 한복판에서 "이 지표를 그때 수집했더라면"은 이미 늦다. 그러나 관측의 함정은 반대 방향에도 있다. 대시보드 100개와 경보 500개는 관측이 아니라 소음이다. 14.3의 원칙대로, 경보는 SLI 기반 소수로 두어 사용자 피해가 있을 때만 울리고, 원인 지표(CPU, 큐 길이)는 경보가 아니라 진단용 대시보드·트레이스로 옮긴다. 관측의 질은 신호의 양이 아니라 "새로운 질문에 답할 수 있는가"로 판단한다.
IaC 드리프트는 조용히 쌓인다
"급해서 콘솔에서 한 번만" 바꾼 것이 코드와 인프라의 어긋남을 만들고, 다음 IaC 적용 때 그 수동 변경이 조용히 되돌려지거나(사고) 예상 못한 충돌을 낸다. 드리프트는 IaC를 도입한 조직에서 가장 흔한 실패다. 예방은 문화와 자동화 둘 다다. 수동 변경 권한을 제한하고(사람이 콘솔에서 바꿀 수 없게), 드리프트를 정기 탐지해 경보하며, 긴급 수동 변경이 불가피하면 사후에 코드로 반영하는 절차를 둔다. 코드가 진실이라는 계약은 예외를 허용하는 순간 무너진다.
로그 비용은 성공할 때 폭발한다
트래픽이 늘면 로그량이 비례해 늘고, 모든 것을 무기한 저장하면 관측 비용이 인프라 비용을 넘어서기도 한다. 대응은 신호별 비용 구조에 맞춘 정책이다. 로그는 레벨·샘플링·보존 기간으로 통제하고, 고빈도 저가치 이벤트는 메트릭으로 집계하며, 트레이스는 샘플링(모든 요청이 아니라 일부, 또는 느린·실패한 요청 우선)한다. "모든 것을 로그로 남긴다"는 관측이 아니라 비용 폭탄이며, 무엇을 어느 신호로 어느 해상도로 남길지가 관측 설계다.
더 깊이: 공급망 보안과 파이프라인의 신뢰
CI/CD 파이프라인은 코드를 운영에 밀어 넣는 통로이므로, 파이프라인 자체가 공격 대상이 된다. 빌드 시스템이 오염되면 검증을 통과한 아티팩트에 악성 코드가 섞일 수 있고(SolarWinds 사고가 이 유형이다), 이는 소스 코드 리뷰로는 잡히지 않는다. 여기서 15.1의 커밋 서명과 챕터 3의 디지털 서명이 아티팩트 수준으로 확장된다.
- 아티팩트 서명·검증: 빌드가 산출물에 서명하고, 배포가 서명을 검증해 "이 아티팩트가 우리 파이프라인에서 나왔음"을 증명한다(Sigstore 등).
- 출처 증명(provenance): 이 아티팩트가 어떤 소스·어떤 빌드 단계에서 나왔는지의 검증 가능한 기록(SLSA 프레임워크).
- SBOM(소프트웨어 자재 명세서): 아티팩트에 포함된 모든 의존성의 목록. 새 취약점이 공개됐을 때 "우리가 그 버전을 쓰는가"를 즉시 답하게 한다.
- 재현 가능한 빌드: 같은 소스가 비트 단위로 같은 아티팩트를 만들면, 제3자가 독립적으로 빌드해 아티팩트가 소스와 일치하는지 검증할 수 있다.
이것은 인트로의 계약 연쇄를 공급망 무결성으로 끌어올린 것이다. 기록 계약(무결성·진위)이 소스뿐 아니라 빌드 산출물까지 이어져야, 검증을 통과한 것이 실제로 운영에 도달한 것과 같음을 보장할 수 있다. 다만 이 방어들은 검증을 강제할 때만 실효가 있다. 서명하되 검증하지 않으면, SBOM을 만들되 취약점과 대조하지 않으면, 그것은 장식이다.
정리
- DevOps는 개발과 운영 사이의 벽을 자동화로 허무는 접근이고, 그 벽에서 자라는 악순환을 배포를 값싸고 안전하고 가역적으로 만들어 끊는다.
- CI는 자주·자동 통합 검증이며, 파이프라인은 싼 증거를 먼저 두는 fail-fast 순서로 구성한다. 재현 가능한 빌드와 불변 아티팩트가 "한 번 빌드해 여러 환경에 배포"를 가능하게 한다.
- CD는 배포를 일상으로 만들고, 배포와 릴리스를 기능 플래그로 분리한다. 배포 전략(recreate·rolling·blue-green·canary)은 폭발 반경·롤백 속도·자원 비용의 트레이드오프이며 에러 버짓 정책의 집행 수단이다. 무중단 배포는 expand-contract 스키마 진화 규율에 달렸다.
- IaC는 인프라를 선언형 코드로 버전 관리한다. 멱등성·상태·드리프트가 핵심이고, 드리프트(수동 변경)가 "코드가 진실"이라는 계약을 무너뜨린다. GitOps와 불변 인프라가 이 원리의 극단이다.
- 관찰 가능성은 미지의 질문에 답할 수 있는 성질이다. 메트릭(집계·싸다·SLI)·로그(이벤트·비싸다)·트레이스(경로·인과)는 카디널리티 비용에 따라 역할을 나눈다. 관측의 질은 신호의 양이 아니라 새 질문에 답하는 능력으로 판단한다.
확인 문제
- 한 팀의 CI 파이프라인이 [정적 검사 → 빌드 → 단위 테스트 → 통합 테스트]인데, 최근 "통합 테스트가 자꾸 깨져서" 통합 테스트를 파이프라인 맨 앞으로 옮기자는 제안이 나왔다. 이 제안의 문제를 파이프라인 단계 순서의 원칙으로 반박하고, 통합 테스트가 자주 깨지는 진짜 원인의 후보를 제시하라.
- 어떤 서비스가 blue-green 배포를 쓴다. 신 버전이 사용자 테이블에
NOT NULL컬럼을 추가하는 마이그레이션을 포함해 배포됐는데, 배포 직후 구 버전으로 롤백하자 구 버전이 대량 오류를 냈다. 무슨 일이 일어났는지 설명하고, 같은 스키마 변경을 롤백 가능하게 만드는 절차를 제시하라. - 운영 중 "특정 지역 사용자의 결제 요청만 어제 오후부터 간헐적으로 느리다"는 문의가 들어왔다. 팀에는 서비스별 CPU·에러율 메트릭 대시보드는 있지만 분산 트레이싱이 없다. 이 관측 스택이 이 질문에 답하기 어려운 이유를 세 신호의 역할로 설명하고, 무엇을 보강해야 하는지 제시하라.
- 한 팀이 관측 비용을 줄이려 "모든 로그를 30일 보관하고, 모든 요청을 트레이싱하며, 사용자 ID를 모든 메트릭의 라벨로 붙여 세밀하게 본다"고 결정했다. 이 결정에서 비용이 폭발할 지점 세 곳을 카디널리티·신호별 비용 구조로 지적하고, 각각을 어떻게 재설계할지 제시하라.
정답과 해설
- 파이프라인 단계 순서의 원칙은 (검출하는 결함의 흔함 × 검사 비용)이 낮은 것, 즉 싸고 빠르게 흔한 실패를 잡는 검사를 앞에 두는 fail-fast다. 통합 테스트는 가장 느리고 비싸므로 맨 앞에 두면, 린트 오류 하나를 잡는 데도 매번 느린 통합 테스트를 먼저 돌려 피드백이 극적으로 느려진다. "자주 깨진다"는 이유로 앞에 옮기는 것은 순서를 거꾸로 뒤집는다. 통합 테스트가 자주 깨지는 진짜 원인 후보: (a) flaky — 외부 의존성·타이밍·공유 상태에 의존해 무작위로 실패한다(격리·수정 대상이지 순서 이동 대상이 아니다). (b) 환경 불안정 — 테스트 환경의 의존 서비스가 불안정하다. (c) 실제 결함을 늦게 잡음 — 앞단계(단위 테스트)가 부실해 통합 단계에서야 결함이 처음 드러난다면, 통합 테스트를 옮길 게 아니라 앞단계를 보강해야 한다. 어느 경우든 처방은 순서 변경이 아니라 원인 제거다.
- blue-green은 신·구 버전이 같은 데이터베이스를 공유한다. 신 버전이
NOT NULL컬럼을 추가하면 그 컬럼은 신 버전만 채운다. 롤백해 구 버전으로 돌아가면, 구 버전은 그 컬럼의 존재를 모르므로 INSERT 시 값을 주지 않고,NOT NULL제약에 걸려 대량 오류가 난다. 원인은 스키마 변경이 이전 버전과 호환되지 않는데도 코드 배포와 원자적으로 묶여, 롤백이 불가능해진 것이다. 롤백 가능하게 만드는 expand-contract 절차: (1) Expand — 컬럼을NULL허용(또는 기본값 포함)으로 추가만 한다. 구·신 코드 모두 동작한다. (2) Migrate — 신 코드를 그 컬럼을 채우도록 배포하고, 기존 행의 데이터를 백필한다. 이 시점에도 구 코드로 롤백 가능(구 코드는 컬럼을 무시). (3) Contract — 컬럼을 참조하지 않는 구 버전이 완전히 사라지고 데이터가 다 채워진 뒤에야, 필요하다면NOT NULL제약을 별도 마이그레이션으로 추가한다. 각 단계가 앞뒤 버전과 호환되므로 언제든 롤백 가능하다. - 이 질문("특정 지역의 결제 요청만 간헐적으로 느림")은 집계가 아니라 어디서·왜를 요구한다. 메트릭은 "얼마나"에 답하지만 집계라 특정 지역·특정 요청의 개별 경로를 보여주지 못한다 — CPU·에러율 대시보드로는 "결제 서비스 CPU 정상"까지만 보이고 그 요청이 어느 하위 구간에서 느려지는지 알 수 없다. 로그가 있어도 한 결제 요청이 여러 서비스(결제·재고·외부 PG)의 로그에 흩어져, 추적 ID 없이는 하나의 느린 요청을 재구성하기 어렵다. 트레이스가 없으므로 "이 요청이 A→B→C 중 어느 구간에서, 왜 느린가"라는 인과·위치 질문에 답할 도구가 없다. 보강: 분산 트레이싱을 도입해(OpenTelemetry로 추적 ID를 전파) 요청 경로와 구간별 지연을 잇고, 느린·실패 요청을 우선 샘플링한다. 그러면 "특정 지역 요청이 외부 PG 호출 구간에서 간헐적 지연"처럼 위치를 좁힐 수 있다. 지역·사용자군 같은 고카디널리티 속성은 메트릭 라벨이 아니라 트레이스·로그의 속성으로 담아 질의한다.
- 세 폭발 지점: (a) 사용자 ID를 메트릭 라벨로 — 가장 심각. 메트릭은 라벨 조합마다 별도 시계열이 생기는데, 사용자 ID는 카디널리티가 사용자 수만큼이라 시계열이 폭발해 저장·질의 비용이 감당 불가능해진다. 재설계: 사용자 단위 식별은 메트릭이 아니라 로그·트레이스의 속성으로 옮기고, 메트릭 라벨은 저카디널리티(지역·엔드포인트·상태 코드)로 제한한다. (b) 모든 요청 트레이싱 — 트레이스는 요청 단위라 100% 수집은 고트래픽에서 비싸다. 재설계: 샘플링(예: 일반 요청 1%, 느린·실패 요청은 100%)으로 비용 대비 진단 가치를 유지한다. (c) 모든 로그 30일 보관 — 로그는 이벤트마다 저장이라 트래픽에 비례해 비용이 늘고, 고빈도 저가치 로그가 대부분을 차지한다. 재설계: 로그 레벨·샘플링으로 볼륨을 줄이고, 고빈도 이벤트는 메트릭으로 집계하며, 보존 기간을 가치에 따라 차등(감사 로그는 길게, 디버그 로그는 짧게)한다. 공통 원칙은 질문의 성격(집계 vs 개별 식별)에 신호를 맞추고, 각 신호의 비용 구조에 맞는 해상도로 남기는 것이다.
참고 자료
- Martin Fowler, Continuous Integration와 ContinuousDelivery — CI/CD가 도구가 아니라 실천임을, 그 원 정의로 확인한다.
- Jez Humble, David Farley, Continuous Delivery (Addison-Wesley, 2010) — 배포 파이프라인, 아티팩트 승격, build-once 원칙의 기준 문헌이다.
- The Twelve-Factor App — 설정 분리, 빌드-릴리스-실행 단계 구분 등 배포 가능한 애플리케이션의 원칙이다.
- Liz Fong-Jones, Charity Majors 외, Observability Engineering (O'Reilly, 2022) — 모니터링과 관찰 가능성의 구분, 세 신호와 카디널리티 비용을 다룬다.
- OpenTelemetry Documentation — 메트릭·로그·트레이스 수집과 전파의 벤더 중립 표준 명세다.
- Danilo Sato, Zero Downtime Deployments and Parallel Change (expand-contract) — 롤백 가능한 스키마 진화 패턴의 출처다.
- SLSA (Supply-chain Levels for Software Artifacts) — 아티팩트 출처 증명과 공급망 무결성 수준의 프레임워크다.