Mini Project 6 06
자동 배포의 역설: 운영 책임 게이트로서의 수동 승인
자동 배포를 만들면서 왜 수동 승인 단계를 다시 넣는지 모순처럼 느껴진다.
근거 · 교안 CodePipeline/승인 흐름 파트
1장: 왜 자동화에 ‘수동 승인’이 필요할까?
솔라의 손가락이 모니터 위를 맴돌았다. AWS CodePipeline 콘솔 화면에 그려진 파이프라인의 흐름은 명쾌했다. 소스 코드가 변경되면, 빌드를 거쳐 컨테이너 이미지가 만들어지고, 테스트를 통과하면 배포 단계로 넘어간다. 물 흐르듯 자연스러운 자동화의 약속. 그런데 배포(Deploy) 단계에 추가된 ‘Manual Approval’이라는 주황색 블록이 그 흐름을 뚝 끊고 있었다.
“언니, 이거 좀 이상하지 않아?”
솔라는 옆에서 조용히 자신의 모니터를 보던 루나를 불렀다.
“자동 배포 파이프라인을 열심히 만들고 있는데, 마지막에 ‘수동 승인’ 단계를 넣으라니. 이건 그냥 사람이 버튼 누를 때까지 기다리는 거잖아. 그럼 자동화하는 의미가 없는 거 아니야? 모순 같아.”
솔라의 목소리에는 명백한 혼란이 묻어 있었다. 기껏 힘들여 구축한 자동화 시스템의 발목을 스스로 잡는 듯한 이 지시가 영 마음에 들지 않는 눈치였다. 모든 수동 개입을 없애고 사람의 실수를 원천 차단하는 것이 자동화의 목표라고 믿었기 때문이다.
루나는 솔라의 화면을 잠시 들여다보더니, 고개를 들어 다른 곳을 상상하듯 말했다.
“솔라, 아주 근사한 최첨단 로봇 셰프가 있는 레스토랑이 곧 개업한다고 상상해 봐. 주방 시스템은 완벽하게 자동화되어 있어서, 버튼 하나만 누르면 주문부터 요리 완성까지 순식간에 끝난대.”
“와, 멋지다. 영화에나 나올 법한 곳이네.”
“그치? 드디어 개업일이야. 로봇 셰프가 첫 번째 대표 메뉴를 레시피대로 완벽하게 만들어냈어. 자, 그럼 레스토랑 사장은 그 즉시 정문을 활짝 열고 손님들을 맞이해야 할까?”
루나의 질문에 솔라는 잠시 생각에 잠겼다. 질문의 의도가 뻔히 보였지만, 상상 속 레스토랑의 주인이 된 것처럼 진지하게 시뮬레이션을 돌려보았다.
“아니, 당연히 아니지.” 솔라가 고개를 저었다. “아무리 레시피가 완벽하고 로봇이 정확했어도, 사장이라면 먼저 음식을 직접 먹어봐야지. 혹시 소금이 너무 많이 들어가진 않았는지, 온도는 적당한지. 그리고 홀 서빙 직원들은 준비됐는지, 테이블 세팅은 완벽한지… 모든 게 손님 맞을 준비가 됐는지 최종 확인하고 문을 열어야지.”
“바로 그거야.”
루나가 솔라의 모니터에 떠 있는 ‘Manual Approval’ 블록을 가리켰다.
“방금 네가 말한 ‘최종 확인하고 문을 여는 것’. 저 블록이 바로 그 역할을 하는 거야. 자동화된 파이프라인이 코드를 서버에 올리는 것(배포)과, 그 새로운 기능을 실제 사용자에게 공개하는 것(서비스 오픈)은 다른 차원의 문제일 수 있어.”
루나의 말에 솔라의 눈이 동그래졌다. ‘배포’와 ‘오픈’. 늘 한 덩어리로 생각했던 두 단어가 분리되기 시작했다.
“자동화는 반복적이고 실수하기 쉬운 작업들, 예를 들면 소스 코드를 가져와서 빌드하고, 테스트하고, 서버 수십 대에 똑같이 복사하고 재시작하는 그런 과정들을 대신해 주는 거야. 지루하고 틀리기 쉬운 일을 기계에 맡겨서 사람은 더 중요한 판단에 집중할 수 있게 해주는 거지. 모든 판단을 없애는 게 아니라.”
솔라는 아, 하고 작은 탄성을 내뱉었다. 머릿속에 엉켜 있던 실타래가 풀리는 느낌이었다.
“그러니까… 자동화는 기계처럼 반복적인 실행을 책임지고, ‘지금 이 변경 사항을 모든 사용자에게 적용해도 정말 괜찮을까?’ 하는 최종 판단의 책임은 사람이 지도록, 그 순간을 만들어주는 거구나.”
솔라는 다시 CodePipeline 화면을 보았다. 아까는 흐름을 방해하는 장애물처럼 보였던 주황색 블록이, 이제는 전혀 다른 의미로 다가왔다. 그것은 자동화의 실패나 모순이 아니라, 프로덕션이라는 무대에 오르기 전 마지막으로 옷매무새를 가다듬고 심호흡을 하는 ‘분장실의 문’ 같은 것이었다.
“하나의 긴 컨베이어 벨트가 끝없이 돌아간다고 생각하기보다, 목적에 따라 잠시 멈추거나 다른 레일로 갈아탈 수 있는 기찻길이라고 보는 게 더 정확해. 이 파이프라인의 흐름을 필요에 따라 나눌 수 있다는 거지.”
“파이프라인을 분리해서 생각한다… 알 것 같아. 중요한 변경이 실제 사용자에게 영향을 주기 전에 마지막 확인을 하는 건 정말 중요하겠다.”
솔라는 고개를 끄덕이며 수긍했다. ‘수동 승인’은 더 이상 모순이 아니었다. 오히려 자동화의 가치를 더 높여주는 현명한 안전장치였다. 하지만 새로운 관점이 생기자, 또 다른 질문이 고개를 들었다.
“그런데 언니, 그럼 모든 배포에 이 ‘수동 승인’이 다 필요한가? 그냥 개발 서버에 오타 하나 고쳐서 올리는 것까지 이렇게 매번 승인을 받아야 한다면… 그건 좀 번거로울 것 같은데?“
2장: 개발과 프로덕션, 다른 책임 다른 파이프라인
루나는 솔라의 질문에 말없이 고개를 끄덕였다. 그리고는 자신의 모니터 화면에 있던 복잡한 콘솔 창을 닫고, 깨끗한 다이어그램 도구를 열었다. 잠시의 정적 속에서 루나의 마우스 커서가 움직여 두 개의 평행선을 길게 그었다. 마치 나란히 달리는 두 개의 철로 같았다. 위쪽 선로에는 dev, 아래쪽 선로에는 main이라고 짧게 적었다.
그림을 본 솔라는 무언가 떠오른 듯 어깨를 폈다. Git 브랜치 이름이었다. 단순한 그림이었지만, 솔라가 방금 던진 질문—모든 배포에 수동 승인이 필요한가—이 두 개의 길로 나뉘어 표현된 것만 같았다. 하나의 단일한 컨베이어 벨트가 아니라는 지난 대화의 끝이 시각적으로 펼쳐진 것이다.
“네 질문에 대한 답이 이 그림 안에 있어.”
루나가 먼저 dev라고 쓰인 위쪽 선로를 가리켰다.
“네가 말한 ‘개발 서버에 오타 하나 고치는’ 상황을 이 길에 올려보자. 개발자가 dev 브랜치에 코드 변경을 푸시하면, 어떤 일이 벌어질까?”
“음… dev 파이프라인이 시작되겠지. 소스 코드를 받고, 빌드하고, 테스트하고… 그리고 개발 서버에 바로 배포하겠지?”
솔라는 자신의 생각을 더듬으며 말했다. 그녀가 기대하는 이상적인 자동화의 모습이었다.
“맞아. 중간에 멈추는 것 없이, 끝까지 쭉 달리는 거야.”
루나는 커서로 dev 선로의 시작부터 끝까지를 부드럽게 이었다. 그 위에는 어떤 장애물도, 멈춤 신호도 없었다. 화살표는 곧장 [dev namespace 배포] 라는 종착역을 향해 뻗어 있었다.
“빠르고 좋네. 개발할 땐 이게 최고지. 코드를 고치자마자 바로 테스트 환경에서 확인해 볼 수 있으니까.”
솔라는 만족스러운 표정을 지었다. 그녀가 생각했던 ‘번거로움’이 없는, 시원하게 뚫린 고속도로였다. 모든 배포가 이래야 한다는 그녀의 초기 믿음이 이 dev 파이프라인에서는 정확히 들어맞았다.
“그럼 이번엔 아래쪽 길을 볼까?”
루나의 커서가 main 이라고 적힌 선로로 옮겨갔다.
“중요한 기능 개발이 끝나서, dev 브랜치의 변경 사항을 main 브랜치로 병합(merge)했어. 이제 진짜 사용자들이 쓸 서비스에 반영될 차례야. main 파이프라인이 시작되지.”
루나는 main 선로 위에서도 커서를 움직였다. 시작은 비슷했다. 소스 코드, 빌드, 이미지 생성. 하지만 [ECR Push] 라는 지점을 지나자, 커서는 더 나아가지 못하고 멈춰 섰다. 루나는 그 자리에 어제 솔라가 봤던 것과 똑같은 주황색의 ‘Manual Approval’ 블록을 그려 넣었다. prod namespace 배포라는 최종 목적지 바로 앞에 세워진 명백한 관문이었다.
“왜 이 길은 다르게 취급해야 할까? 두 길의 가장 큰 차이점이 뭘까?”
루나의 질문에 솔라는 잠시 생각에 잠겼다. 똑같은 코드 변경인데, 왜 어떤 길은 직행이고 어떤 길은 검문소를 통과해야 하는가.
”…이용하는 사람.”
마침내 솔라가 입을 열었다.
“개발 서버(dev 환경)는 개발팀인 우리만 쓰잖아. 뭔가 잘못돼도 우리끼리 ‘어, 이거 왜 이러지?’ 하고 고치면 그만이야. 하지만 프로덕션 서버(prod 환경)는… 진짜 고객들이 돈 내고 쓰는 서비스니까. 거기서 에러가 나면 회사 전체가 난리가 나겠지.”
그 순간, 솔라의 머릿속에서 두 개의 선로가 전혀 다른 무게감으로 다가왔다. dev 선로는 가벼운 연습용 카트 트랙이었고, main 선로는 수백 명의 승객을 태운 KTX가 달리는 실제 철로였다. 똑같은 엔진(코드)을 시험하더라도, 트랙의 성격에 따라 요구되는 안전 절차와 책임의 무게는 완전히 달랐다.
“바로 그거야. 환경마다 책임의 무게가 다르니까, 파이프라인의 규칙도 달라져야 하는 거야.”
루나가 덧붙였다.
“개발 환경에서는 속도가 중요해. 빠른 피드백을 통해 더 좋은 코드를 만들어내는 게 목적이지. 그래서 최대한 걸리는 것 없이 자동화하는 게 맞아. 하지만 프로덕션 환경의 최우선 가치는 속도가 아니라 안정성이야. 단 한 번의 실수가 큰 손실로 이어질 수 있으니까.”
솔라는 고개를 끄덕이며 루나가 그린 다이어그램을 다시 보았다. 처음에는 모든 배포가 똑같은 프로세스를 따라야 효율적이라고 생각했다. 하지만 이제는 그 생각이 얼마나 위험할 수 있는지 깨달았다. dev의 속도를 prod에 그대로 적용하는 것은, 안전 점검 없이 KTX를 최고 속력으로 출발시키는 것과 같았다. 반대로 prod의 신중함을 dev에 적용하는 것은, 동네 카트장에서조차 헬멧, 보호대, 안전 교육, 서약서까지 모두 요구하는 것과 같았다. 비효율적일 뿐이다.
“그렇구나… 환경별로 다른 정책을 적용하는 게 리스크 관리의 핵심이었네. 모든 배포를 똑같이 취급하는 게 아니라, 각 환경의 목적과 책임에 맞게 파이프라인을 설계해야 했던 거구나.”
‘수동 승인’이라는 번거로워 보였던 단계는, 프로덕션 환경의 안정성을 지키기 위한 최소한의, 그리고 가장 중요한 안전장치였다. 솔라는 이제 main 파이프라인에 있는 주황색 블록이 모순이 아니라, 오히려 깊은 고민 끝에 나온 합리적인 설계임을 이해할 수 있었다.
생각이 정리되자, 솔라는 한 걸음 더 나아갔다. 그녀의 시선이 main 파이프라인의 주황색 블록에 다시 꽂혔다.
“알겠어, 언니. 프로덕션 배포 전에는 반드시 멈춰서 최종 확인을 해야 한다는 건 이제 확실히 이해했어. 그런데… 이 ‘수동 승인’이라는 게 구체적으로 어떻게 동작하는 거야? 그냥 파이프라인이 잠시 멈춰있는 상태인 건가? 누군가 실수로라도 ‘승인’ 버튼을 누르면 바로 배포되는 거야? 이 중요한 책임을 누가, 어떻게 지게 되는 거지?“
3장: 수동 승인, 단순 지연이 아닌 ‘운영 책임 게이트’
솔라의 질문이 끝나자, 루나는 이전 장에서 그렸던 dev와 main 파이프라인 다이어그램을 다시 화면에 띄웠다. 하지만 무언가 달라져 있었다. 이전에는 main 파이프라인의 최종 목적지 앞에 덩그러니 놓여 있던 주황색 Manual Approval 블록 주위로 작은 아이콘들이 추가된 것이다. 블록 바로 뒤에는 종 모양의 알림 아이콘이, 그리고 그 아래에는 댓글처럼 보이는 URL 링크 아이콘과 함께 사람 모양 아이콘이 그려져 있었다.
그저 멈춰있는 장애물 같았던 블록이, 이제는 여러 장치와 연결된 복잡한 관문처럼 보였다. 솔라가 마지막에 던졌던 질문—이 중요한 책임을 누가, 어떻게 지는가—에 대한 답이 이미 그림 위에 준비된 듯했다. 솔라는 말없이 그 새로운 그림을 응시했다. 파이프라인이 멈춘다는 단순한 사실 너머에, 보이지 않는 어떤 절차가 작동하고 있음을 직감했다.
“네가 한 질문은 정말 중요해, 솔라. ‘누가’ 그리고 ‘어떻게’ 책임을 지는가. 만약 이 주황색 블록이 단순히 파이프라인을 멈춰놓고 아무나 ‘계속’ 버튼을 누를 수 있게 방치하는 기능이라면, 그건 자동화의 실패가 맞을 거야. 오히려 위험을 증가시키는 셈이지.”
루나는 그림 속의 Manual Approval 블록을 클릭하는 시늉을 했다.
“자, 가상으로 한번 겪어보자. 네가 개발한 중요한 기능이 dev 브랜치에서 모든 테스트를 마치고, 동료의 코드 리뷰까지 거쳐 main 브랜치로 막 병합됐어. main 파이프라인이 즉시 실행되고, 빌드와 이미지 생성을 거쳐 마침내 이 관문 앞에 도착했지. 파이프라인의 상태는 ‘In Progress’에서 ‘Waiting for approval’로 바뀌었어.”
루나의 설명에 솔라는 상상 속의 개발자가 되어 고개를 끄덕였다.
“그럼… 파이프라인은 멈춰있고, 나는 이제 기다리는 건가?”
“아니. 파이프라인은 행동을 시작해.”
루나는 다이어그램의 종 모양 아이콘을 톡 가리켰다.
“이게 AWS의 SNS, Simple Notification Service와 연결된 단계야. 파이프라인이 승인을 기다리는 상태가 되는 바로 그 순간, 미리 지정된 채널로 알림을 보내. 예를 들면, 운영팀의 공식 슬랙 채널이나 팀장님의 이메일로 말이야.”
루나는 모니터 한쪽에 작은 텍스트 창을 열어 메시지 예시를 타이핑했다.
[긴급: 프로덕션 배포 승인 요청]
프로젝트: Phoenix-API
변경 사항: 새로운 결제 모듈 추가 (PR #132)
요청자: 솔라 Kim
프로덕션 환경으로의 배포가 대기 중입니다.
아래 링크에서 변경 내역을 검토하고 승인해 주세요.
→ https://codepipeline.ap-northeast-2.amazonaws.com/.../approve
솔라는 화면에 나타난 메시지를 보고 눈을 크게 떴다. 이것은 막연한 기다림이 아니었다. 명확한 수신인에게, 무엇을 해야 하는지, 왜 해야 하는지에 대한 정보를 담아 보내는 공식적인 ‘요청’이었다.
“와… 그냥 멈춰있는 게 아니네. 책임자를 정확히 호출하는 거구나. ‘누군가 확인하겠지’가 아니라 ‘운영팀장님, 확인해 주세요’라고 콕 집어서 말하는 것처럼.”
“바로 그거야. 그리고 이 알림에서 가장 중요한 부분은 바로 저 Pull Request 링크야.”
루나는 메시지 속 (PR #132) 부분을 강조했다.
“우리 팀은 main 브랜치에 코드를 직접 푸시하는 걸 규칙으로 막아 놨어. 모든 변경은 반드시 PR, 즉 Pull Request를 통해서만 들어올 수 있지. 그 PR에는 어떤 코드가 왜 바뀌었고, 어떤 테스트를 거쳤는지, 동료 개발자는 어떤 의견을 줬는지가 모두 기록되어 있어. 그러니 승인자는 저 링크만 눌러봐도 이번 배포가 어떤 영향을 미칠지 충분한 맥락을 파악하고 결정을 내릴 수 있는 거지.”
그제야 솔라의 머릿속에 흩어져 있던 조각들이 하나의 그림으로 맞춰졌다. 브랜치 보호 규칙, PR 기반의 협업 문화, 자동화된 파이프라인, 그리고 알림 시스템과 연결된 수동 승인 단계까지. 이 모든 것이 서로 맞물려 돌아가는 하나의 정교한 시스템이었다.
‘Manual Approval’은 자동화의 흐름을 끊는 모순적인 단계가 아니었다. 오히려 인간의 최종적인 판단과 책임을 이 자동화 시스템 안으로 통합하는, 가장 중요한 연결점이었다. 누가, 언제, 무엇을 근거로 승인했는지 모든 기록이 남는, 명확한 ‘운영 책임 게이트’인 것이다.
“이건… 단순한 버튼이 아니었어. 배포에 대한 최종 서명을 하는 날인대였구나. 실수로 누를 수 있는 성질의 것이 아니네. 이 메시지를 받고, PR 내용을 확인하고, 승인 링크를 누르는 모든 행위가 ‘이 배포에 대한 책임은 내가 진다’는 선언이었던 거야.”
솔라는 이전과 완전히 다른 눈으로 파이프라인 다이어그램을 바라봤다. 주황색 블록은 더 이상 흐름을 막는 장애물이 아니라, 프로덕션이라는 성으로 들어가는 마지막 성문처럼 보였다. 그리고 그 성문을 여는 열쇠는 정해진 책임자에게만 주어진다.
생각을 정리한 솔라는 자신감이 붙은 목소리로 루나에게 말했다.
“언니, 그럼 나도 한번 이 게이트를 설계해 볼래.”
솔라는 루나의 다이어그램을 옆으로 치우고, 빈 캔버스를 열었다. 그리고는 망설임 없이 자신만의 파이프라인을 그리기 시작했다. dev와 main으로 나뉜 두 개의 레일, dev 파이프라인은 거침없이 끝까지 이어졌지만, main 파이프라인의 배포 단계 앞에는 어김없이 주황색 블록을 그려 넣었다.
“이건 우리가 새로 시작할 사내 통계 대시보드 프로젝트용 파이프라인이야. 사용자는 우리 직원들뿐이지만, 데이터가 틀리면 중요한 의사결정에 문제가 생길 수 있어.”
솔라는 주황색 블록 옆에 작은 메모를 달았다.
[Manual Approval] → 대상: 데이터팀 리더. 알림 채널: #data-ops 슬랙. 포함 정보: PR 링크.
그녀는 더 이상 ‘수동 승인’을 자동화의 모순이라 여기지 않았다. 오히려 그것을 가장 중요한 운영 원칙을 지키는 핵심 도구로 활용하고 있었다. 자동화는 인간의 판단을 없애는 것이 아니라, 가장 중요한 판단의 순간을 가장 명확하게 드러내고 그 책임의 무게를 올바르게 분배하는 것임을, 솔라는 이제 자신의 설계로 증명해 보이고 있었다.