에이전트가 운영 환경을 망친 것이 아닙니다. 당신의 파이프라인이 망친 것입니다.
요약
AI 에이전트 도입 시 운영 환경의 안정성을 보장하기 위해 머지(Merge)와 릴리스(Release)를 분리하는 전략을 제안합니다. 지속적 배포의 위험을 줄이기 위해 피처 플래그나 명시적인 승격 단계를 통해 결합을 해제해야 합니다.
핵심 포인트
- 머지 이벤트와 라이브 상태를 동일시하지 말고 분리할 것
- 피처 플래그는 도구가 아닌 결합 해제를 위한 수단임
- 자동화된 타이머 기반의 프로덕션 승격을 지양할 것
- 릴리스 후 실제 트래픽 처리 및 엔드포인트 체크를 통한 검증 필요
서류상으로는 꽤 합리적으로 보이는 파이프라인을 상상해 보세요. 에이전트(Agent)가 풀 리퀘스트(Pull Request)를 생성합니다. CI(지속적 통합)가 메인(main) 브랜치로의 머지(Merge)를 검증합니다: 린트(Lint), 테스트(Test), 빌드(Build)가 모두 통과되어야 하며, 그렇지 않으면 반영되지 않습니다. 스케줄링된 작업(Scheduled job)은 스테이징(Staging)에 있는 것을 주기적으로 운영(Production) 환경으로 바로 승격시킵니다. 기능별 개별 승인은 없습니다. 그저 타이머에 맞춰 일괄 전환될 뿐입니다.
그러한 파이프라인은 언젠가 반드시 당신의 일주일을 망쳐놓을 것입니다.
에이전트가 악의적인 행동을 했기 때문이 아닙니다.
지속적 인도 (Continuous delivery)는 모든 머지 (merge)가 릴리스 가능 (releasable) 상태임을 의미합니다. 그것이 모든 머지가 릴리스 (released) 되었음을 의미하지는 않습니다. 지속적 배포 (Continuous deployment)는 이 두 가지를 동일한 동작으로 통합하며, 이는 빠르게 움직임으로써 상속받는 기본값이 아니라 당신이 의도적으로 내린 선택입니다.
만약 당신의 파이프라인이 "머지됨"과 "라이브 상태"를 동의어로 취급한다면, 당신은 스스로 결정하기도 전에 지속적 배포를 선택한 셈입니다. 해결책은 파이프라인의 속도를 늦추는 것이 아닙니다. 머지 이벤트와 "고객이 이를 볼 수 있음" 이벤트가 더 이상 동일한 버튼이 되지 않도록 두 결정을 분리하는 것입니다.
피처 플래그 (Feature flags)는 그러한 분리를 구현하는 인기 있는 방법 중 하나이며, 이미 사용 중이라면 좋습니다. 계속 사용하세요. 하지만 플래그는 메커니즘일 뿐, 목적이 아닙니다. 저는 피처 플래그 시스템을 직접 운영하지 않지만, 여전히 동일한 분리를 달성하고 있습니다. 중요한 것은 디커플링 (decoupling, 결합 해제)이지, 그것을 달성하기 위해 사용하는 특정 도구가 아닙니다.
그것이 구체적으로 어떤 모습인지 명확하지 않을 수 있으니 설명하자면: 피처 플래그는 가장 단순한 형태로 볼 때, 불리언 (boolean) 값과 if 문 하나입니다.
if (flags.newInvoiceExport) {
renderNewExport()
} else {
...
시작하기 위해 가격 페이지가 있는 벤더 (vendor)가 필요하지는 않습니다. 설정 값 (config value), 환경 변수 (environment variable), 설정 테이블의 컬럼 (column) 등 이 모든 것이 첫날의 시작점이 될 수 있습니다. 단순한 버전이 당신의 발목을 잡는 단계가 되었을 때 LaunchDarkly나 Unleash 같은 도구로 업그레이드하세요. 그 전에는 필요 없습니다.
머지 이벤트뿐만 아니라 릴리스 이벤트에도 게이트를 설치하세요
저의 설정은 다음과 같습니다: PR은 main으로 머지되지만, main이 라이브 상태인 것은 아닙니다. 별도의 릴리스 단계가 main을 production 브랜치로 승격(promote)시키며, 이 승격은 제가 명시적으로 진행(go)을 명령한 후에만 일어납니다. 타이머에 의해 프로덕션으로 푸시되는 것은 아무것도 없으며, 서로 관련 없는 변경 사항들이 모여 있다고 해서 자동으로 푸시되는 것도 없습니다.
일단 그 프로모션 (promotion)이 시작되면, Vercel이 "빌드 성공 (build succeeded)"이라고 말한다고 해서 끝나는 것이 아닙니다. 릴리스 프로세스 (release process)는 Vercel이 새로운 빌드가 실제로 트래픽을 처리하고 있음을 확인될 때까지 기다린 다음, 프로덕션 (production) 자체에 대해 자동화된 체크 (automated check)를 실행합니다. 즉, 반드시 작동해야 하는 몇 가지 엔드포인트 (endpoints)를 호출하여 정상 작동을 확인하고, 그제서야 릴리스가 완료되었다고 선언합니다. 만약 그 체크가 실패하면, 제가 알게 됩니다. 사용자가 알게 되는 것이 아닙니다.
그 이후에는 두 번째의 더 느린 계층이 있습니다. 이번 릴리스에서 변경된 특정 사항들을 수동으로 검토하는 단계입니다. 자동화된 체크는 서버가 응답한다는 것은 알려줄 수 있지만, 기능 (feature)이 의도한 대로 동작하는지는 알려줄 수 없기 때문입니다.
이 중 그 어떤 것도 어디에 플래그 (flag)를 설정할 필요를 요구하지 않습니다. 게이트 (gate)는 "이 코드가 스스로를 숨기는 방법을 아는가"가 아닙니다. 게이트는 "이 코드가 실제 사용자에게 도달하기 전에 사람, 그다음 기계, 그리고 다시 사람이 모두 '아니오'라고 말할 기회를 갖는가"입니다. 피처 플래그 (Feature flags)는 그 위에 코드 내부의 네 번째 계층을 추가해 줄 뿐입니다. 있으면 좋지만, 핵심적인 지지 구조 (load-bearing part)는 아닙니다.
자동화된 체크는 한발 앞선 모니터링 (monitoring)입니다
저는 이 산업의 사고 대응 (incident response) 및 관측성 (observability) 분야에서 수년 동안 시간을 보냈으며, 이에 대해 전체 강연을 할 정도로 경험을 쌓았습니다. 결코 유행을 타지 않는 교훈은 이것입니다. 릴리스 후의 자동화된 체크는 발생 가능한 모든 잘못된 배포 (bad deploy)를 잡아내기 위해 존재하는 것이 아닙니다. 잘못된 배포를 느리게 발견하는 상황을 방지하기 위해 존재하는 것입니다.
사용자가 당신의 버그를 마주하는 것 또한 모니터링입니다. 다만 그것은 최악의 형태일 뿐입니다. 대시보드 (dashboard) 대신 고객 지원 티켓 (support ticket)을 통해 알게 되며, 티켓이 도착했을 때쯤이면 이미 누군가에게는 한참 동안이나 엉망인 오후를 보낸 뒤이기 때문입니다.
실제로 중요한 몇 가지 사항을 점검하는 배포 후 체크 (post-deploy check)가 대시보드 (dashboards), 알림 (alerts), 트레이싱 (tracing) 등 당신이 이미 구축해 놓은 진정한 관측성 (observability)을 대체할 수는 없습니다. 그것은 당신이 무언가를 망가뜨렸을 가능성이 가장 높은 순간, 즉 무언가를 변경한 직후를 겨냥하여 동일한 본능을 압축해 놓은 버전일 뿐입니다.
차선책: 빠르게 되돌리고, 스스로를 막다른 길로 몰아넣지 마세요
릴리스 게이트 (release gate)는 "준비가 되지 않은" 경우를 처리합니다. 하지만 프로모션 (promotion)이 완료되어 실제 사용자들이 이를 보게 되었고, 실제 트래픽 하에서만 나타나는 버그가 발견되는 "내가 틀렸던" 경우는 처리하지 못합니다. 이를 위해서는 나머지 절반, 즉 빠르게 되돌릴 수 있는 능력(revert fast)이 필요하며, 이는 대부분 하나의 지루한 규칙으로 귀결됩니다.
이전 버전의 코드가 견딜 수 없는 데이터베이스 마이그레이션 (database migration)을 배포하지 마세요.
이것이 확장/축소 (expand/contract) 패턴입니다. 이 일을 오랫동안 해왔다면, 당신은 대략 3년마다 새로운 이름으로 이 패턴을 다시 배웠을 것입니다. 새 컬럼을 Nullable로 추가합니다. 데이터를 채웁니다 (Backfill). 이전 컬럼과 새 컬럼 모두에 기록합니다. 새 컬럼을 신뢰할 수 있게 되면 거기서 읽어옵니다. 오직 그 후에, 다음 릴리스에서 이전 컬럼을 삭제합니다. 이 시퀀스의 모든 단계는 롤백 (roll back)하기에 안전합니다. 왜냐하면 앱의 이전 버전은 아직 존재하지 않는 것에 절대 의존하지 않기 때문입니다.
이러한 규율을 건너뛰면 당신의 되돌리기 (revert) 버튼은 장식품이 되어버립니다. 코드는 약 4초 만에 git revert 할 수 있습니다. 하지만 마이그레이션이 이미 실행되어 이전 코드가 예상하는 컬럼을 이미 삭제해 버렸다면, 코드를 되돌리는 것은 단지 더 화려한 방식으로 서비스 장애를 겪는 방법이 될 뿐입니다.
이것은 우연이 아니라, 사고 대응의 기본 (incident response 101)이기도 합니다. 효과적인 사고 대응 (incident response)은 회의 없이 즉시 실행할 수 있도록 미리 합의된 소수의 조치들, 즉 되돌리기 (revert), 롤백 (roll back), 담당자 호출 (page someone) 등 상황에 맞는 조치들을 갖추고 운영됩니다. 롤백 계획 (rollback plan)을 만들어야 할 최악의 타이밍은 바로 그 계획이 해결해야 할 사고가 발생한 순간입니다. 만약 되돌리기를 하기 위해 엔지니어가 세 개의 상호 의존적인 마이그레이션 (migration) 중 어떤 것이 안전하게 취소 가능한지 실시간으로 판단해야 한다면, 당신에게는 롤백 계획이 없는 것입니다. 당신에게 있는 것은 단지 논의 주제일 뿐이며, 논의 주제는 장애를 해결하지 못합니다.
누군가는 여전히 "진행"이라고 말할 책임이 있어야 합니다
위에서 언급한 자동화 중 그 어떤 것도 배포 (release)에 대한 책임 소재를 바꾸지는 않습니다. 다만 그 사람이 무엇을 가지고 책임을 지는지를 바꿀 뿐입니다.
저는 이전에도 블로그가 아닌 무대 위에서, 서비스의 라이프사이클 (the lifecycle of a service)이 무언가가 프로덕션 (production)에 도달하는 순간 끝나는 것이 아니며, 소유권 (ownership) 또한 배포되는 즉시 증발하지 않는다는 점에 대해 이야기한 적이 있습니다. 누군가가 빌드 (build)를 프로덕션으로 승격시키기로 결정하는 데 책임을 지는 것과 마찬가지로, 누군가는 프로덕션에 있는 서비스에 대한 소유권을 가집니다. 만약 특정 배포 (deploy)에 대해
그 인간을 루프(loop)에서 제외하거나, 에이전트(agent)를 쥐여주어 그들을 더 빠르고 더 많게 만든다면, 당신이 건너뛰었던 규율(discipline)은 더 이상 선택 사항이 아니게 됩니다. 그것은 결코 선택 사항이었던 적이 없습니다. 그저 목요일 오후 4시 58분에 누군가가 내린 판단(judgment call)으로 간신히 가려져 있었을 뿐입니다.
에이전트가 이 문제를 만든 것이 아닙니다. 에이전트는 단지 그 문제를 숨겨왔던 마찰(friction)을 제거했을 뿐입니다.
지난번과 마찬가지로: 머지(merge) 측 가드레일(guardrails)이 어떻게 작동하는지에 대한 증거를 원하신다면, 여기를 확인하세요. 이번 글은 그 나머지 절반입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기