Mini Project 6 02

dev와 main 배포 레일로 구축하는 안전장치

dev와 main을 나누는 것이 단순한 Git 규칙처럼 보인다. 왜 파이프라인과 배포 환경까지 함께 나뉘어야 하는지 모호하다.

근거 · 교안 CI/CD 브랜치·파이프라인 파트

dev와 main 배포 레일로 구축하는 안전장치 대표 이미지

1장: Git 브랜치, 그 이상의 약속

솔라는 모니터에 떠 있는 Git 저장소의 브랜치 목록을 쳐다보았다. maindev. 너무나 익숙한 이름들이었다. 며칠 전, 프로젝트 문서를 훑다가 마주친 한 문장이 머릿속에서 떠나지 않았다.

“브랜치 전략은 main/dev로 간단히 도입하고, 추가 기능은 dev 브랜치에서 작업 후 main에 병합한다.”

‘간단히 도입하고.’ 솔라는 이 표현이 마음에 걸렸다. 마치 서랍에 칸을 나누는 것처럼, dev는 어질러도 되는 개발용 공간, main은 완성품만 두는 깔끔한 공간. 그냥 코드를 정리하는 규칙일 뿐인데, 사람들은 이걸 중요한 아키텍처 원칙처럼 이야기했다.

“언니, 이거 봐.”

솔라는 옆자리의 루나를 불렀다.

“우리 프로젝트의 dev 브랜치 설명에 ‘개발/검증용’이라고 쓰여 있잖아. 근데 이게 그렇게 특별한 의미가 있는 걸까? 그냥 개발자들이 작업하는 코드를 모아두는 곳, main은 사용자에게 나갈 안정적인 코드만 두는 곳. 단순히 용도에 따라 코드를 분리해서 관리하는 규칙 아니야?”

솔라의 말에는 ‘다들 너무 거창하게 생각하는 것 같아’라는 뉘앙스가 섞여 있었다. 루나는 솔라의 화면을 잠시 들여다보더니, 의자 등받이에 몸을 기댔다.

“음, 솔라 네 말대로 그게 전부라고 한번 생각해 보자. 그냥 코드 보관함을 두 개로 나눈 것뿐이라고.”

루나는 말을 이었다.

“자, 그럼 여기 dev 브랜치에 있는 최신 코드를 개발 서버에 배포해야 해. 그리고 main 브랜치의 코드는 실제 운영 서버에 올려야 하고. 만약 브랜치가 정말 단순한 코드 분리 규칙이라면, Git 명령어만으로 이 작업을 어떻게 할 수 있을까?”

갑작스러운 질문에 솔라는 잠시 생각에 잠겼다. Git만으로 배포하기. 아주 불가능한 시나리오는 아니었다.

“음… 개발 서버에 접속해서 git pull origin dev를 실행하고, 운영 서버에 접속해서 git pull origin main을 실행하면 되지 않을까? 물론 서버마다 접속해서 명령어를 직접 쳐야 하니까 좀 번거롭긴 하겠다.”

“맞아, 번거롭지. 그럼 한 가지만 더. 만약 네가 너무 바빠서 실수로 운영 서버에서 dev 브랜치를 pull하면 어떻게 될까?”

순간 솔라의 표정이 굳었다. 아직 검증되지 않은, 버그가 있을지도 모르는 코드가 실제 사용자들이 쓰는 서비스에 그대로 적용되는 상황. 생각만 해도 아찔했다.

“내가 아니라 다른 팀원이 실수할 수도 있고. 명령어 한 줄 차이로 큰 사고가 날 수 있겠네. 사람이 항상 완벽할 수는 없으니까.”

“바로 그거야.”

루나가 작게 고개를 끄덕였다.

“브랜치 이름은 단순한 꼬리표가 아니야. 약속에 가깝지. ‘이 dev 브랜치에 들어온 코드는 어떤 일이 있어도 개발 및 검증 환경으로만 간다’는 약속. 그리고 ‘이 main 브랜치에 들어온 코드는 아주 신중하고 통제된 절차를 거쳐서만 운영 환경에 도달할 수 있다’는 약속.”

솔라는 다시 dev 브랜치가 개발/검증용이라는 문구를 떠올렸다. 이전에는 그저 설명문처럼 보였던 단어들이 다르게 읽히기 시작했다. ‘검증’이라는 단어에 무게가 실렸다. 검증은 그냥 코드를 가져다 놓는 게 아니다. 코드를 실행하고, 테스트하고, 다른 시스템과 잘 동작하는지 확인하는 과정 전체를 포함한다. main에 담긴 코드가 안정적이어야 한다는 건, 그 코드가 놓일 환경 역시 최고 수준의 안정성을 보장해야 한다는 뜻이었다.

“아…”

솔라는 나지막한 탄성을 뱉었다.

“그러니까 devmain이라는 브랜치의 분리는, 단순히 코드 뭉치를 나누는 Git의 기능에서 끝나는 게 아니었구나. 각 브랜치에 담긴 코드가 앞으로 어떤 경로를 거쳐, 어떤 성격의 환경에 도착하게 될지를 결정하는 거대한 시스템의 출발점이었던 거야. 브랜치 이름 자체가 일종의 목적지 안내판이었네.”

‘간단히 도입한다’는 말의 가벼움이 사라지고, 그 자리에 묵직한 설계 의도가 보이기 시작했다. 이것은 코드를 정리하는 규칙을 넘어, 배포라는 여정 전체를 안전하게 만들기 위한 첫 번째 안전장치였다. 코드가 담기는 그릇을 나누는 순간, 그 그릇이 향할 길도 완전히 분리되는 것.

솔라의 시선은 이제 갈라진 두 개의 철로를 보고 있었다. 하나는 실험과 검증을 위한 선로, 다른 하나는 실제 승객을 태우고 달리는 안전한 운행 선로. 두 선로는 결코 서로 섞여서는 안 됐다.

“알겠어. 이제 브랜치를 나누는 게 왜 단순한 코드 관리가 아닌지 이해가 돼. 이건 목적에 따라 코드가 갈 길을 처음부터 분리하는, ‘목적 기반 분리’의 시작이었어.”

솔라는 스스로 얻은 깨달음을 정리하며 말했다. 하지만 이내 새로운 궁금증이 고개를 들었다.

“그렇다면 그 분리된 길, 언니가 말한 그 ‘약속’은 실제로 어떻게 지켜지는 거야? git pull이 아니라면, dev 브랜치의 코드를 ‘검증 환경’으로, main 브랜치의 코드를 ‘운영 환경’으로 안전하게 실어 나르는 진짜 ‘레일’은 대체 어떤 모습일까?“

2장: 안전한 개발의 시작: dev 검증 레일

솔라의 마지막 질문은 허공에 흩어지지 않았다. 루나는 말없이 자신의 모니터를 솔라 쪽으로 돌렸다. 화면에는 복잡해 보이는 다이어그램이 떠 있었다. Source, Build, Deploy라는 이름표가 붙은 상자들이 왼쪽에서 오른쪽으로 화살표로 연결되어 있었다. 각 상자 위에는 aivle-m-6th-t24/6-mini-project24라는 익숙한 저장소 이름과 함께 dev-pipeline이라는 제목이 선명했다.

“이게… 솔라 네가 물었던 ‘실제 레일’이야. dev 브랜치를 위한 전용 철로지.”

솔라는 다이어그램을 유심히 살폈다. Git 저장소에서 코드를 가져와(Source), 무언가를 만들고(Build), 어딘가에 배포하는(Deploy) 흐름. 마치 공장의 컨베이어 벨트 같았다.

“흐음, git pull보다는 훨씬 체계적으로 보이긴 하네. 근데 결국 이것도 순서대로 명령어를 실행해 주는 자동화 도구 같은 거 아냐? 개발 서버에 배포하는 과정을 그냥 보기 좋게 그려놓은 느낌인데. main 브랜치용 레일도 그냥 이 그림에서 목적지만 바꾼, 비슷한 모습이겠지?”

솔라의 눈에는 dev 환경과 prod 환경이 크기만 다른 비슷한 공간으로 보였다. 그렇다면 그곳으로 향하는 길 역시, 이름표만 다를 뿐 구조는 같아야 했다.

루나는 솔라의 말을 가만히 듣고 있다가, 솔라의 키보드를 가리켰다.

“백 마디 말보다 한번 보는 게 낫겠지. 네 코드 편집기에서 아무거나, 아주 사소한 거라도 좋으니 dev 브랜치의 코드를 수정하고 푸시해 볼래? 주석 한 줄 바꾸는 것만으로도 충분해.”

솔라는 의아했지만, 루나의 말대로 했다. 코드의 주석 한 줄을 바꾼 뒤, 익숙하게 명령어를 입력했다. git commit, 그리고 git push origin dev. 별다른 일은 일어나지 않았다. 적어도 솔라의 컴퓨터에서는 그랬다.

“자, 이제 다시 아까 그 화면을 봐.”

솔라가 루나의 모니터로 고개를 돌리는 순간, 나지막한 탄성을 내뱉었다. 조금 전까지 모두 회색이었던 다이어그램의 첫 번째 상자, Source가 파란색으로 바뀌며 ‘진행 중(In Progress)’이라는 글씨가 깜빡이고 있었다. 마치 솔라가 코드를 밀어 넣는 순간, 잠자고 있던 레일의 첫 번째 구간에 불이 켜진 것 같았다.

잠시 후, Source 단계가 녹색 체크 표시와 함께 ‘성공(Succeeded)’으로 바뀌더니, 바통을 이어받은 것처럼 Build 단계가 파란색으로 빛나기 시작했다. 거대한 기계가 조용히, 하지만 멈춤 없이 움직이는 모습이었다. 솔라는 키보드에 손도 대지 않았다. 그저 깃허브에 코드를 보냈을 뿐인데, 저 복잡한 시스템이 스스로 잠에서 깨어나 약속된 작업을 수행하고 있었다.

몇 분이 더 흐르자, 마지막 Deploy 단계까지 모두 녹색으로 바뀌었다.

“세상에… 내가 푸시하자마자 자동으로 시작됐어. 아무것도 누르지 않았는데.”

솔라는 다이어그램을 다시 보았다. 이제는 단순한 명령어의 나열로 보이지 않았다. 이것은 dev 브랜치에 코드가 도착하는 순간, 즉시 반응하여 변경 사항을 검증 가능한 상태로 만들어 세상에 내놓는 완전한 자동 시스템이었다. 개발자가 자신의 변경 사항이 다른 부분과 잘 어우러지는지, 혹은 망가뜨리지는 않았는지 거의 즉시 확인할 수 있도록 설계된 고속 레일.

“아… 알겠다. 이 dev 레일의 핵심은 속도와 자동화였어. 그냥 개발용 코드를 가져다 놓는 창고가 아니라, ‘이 코드 괜찮아?‘라고 물으면 ‘바로 확인해 줄게!’ 하고 즉시 답을 주는 거대한 검증 시스템이었던 거야.”

솔라는 깨달았다. dev 환경이 단지 main 환경의 축소판이 아니라는 것을. dev 환경의 진짜 가치는 그 환경에 도달하기까지의 과정, 즉 이 ‘자동 검증 레일’에 있었다. 개발자의 손을 떠난 코드가 어떤 인적 개입도 없이 스스로 빌드되고, 테스트되고, 배포되어 살아 움직이는 모습을 즉각적으로 보여주는 것. 그것이 이 레일이 존재하는 이유였다.

솔라의 머릿속에서 ‘목적 기반 분리’라는 개념이 한층 더 명확해졌다. dev 브랜치의 목적은 ‘신속한 개발과 검증’이고, 이 파이프라인은 그 목적을 달성하기 위해 완벽하게 맞춤 설계된 도구였다.

“그럼 dev 레일이 신속한 검증을 위한 자동 레일이라면… 진짜 사용자가 쓰는 main으로 가는 레일은 이것과 완전히 달라야겠네.”

솔라의 시선은 dev-pipeline 옆에 나란히 자리한 prod-pipeline이라는 이름으로 향했다.

“실수 하나가 큰 사고로 이어지는 운영 환경에, 이렇게 자동으로 코드를 밀어 넣을 수는 없잖아. 저쪽 레일에는 분명… 속도 대신 다른 무언가를 위한, 다른 종류의 안전장치가 있겠지?“

3장: 운영 안정성: main 통제 레일

솔라의 시선이 머물렀던 prod-pipeline이라는 이름 위로 마우스 커서가 올라갔다. dev 레일이 신속한 자동 검증을 위한 것이라면, 진짜 서비스로 향하는 main 레일은 뭔가 다를 것이라는 기대. 솔라는 주저 없이 prod-pipeline을 클릭했다.

화면에 나타난 것은 그러나, 조금 전 보았던 dev-pipeline의 다이어그램과 거의 똑같은 그림이었다. Source, Build, Deploy. 왼쪽에서 오른쪽으로 흐르는 세 개의 상자와 화살표. 너무나 익숙한 구성에 솔라는 잠시 어깨에 힘이 빠지는 것을 느꼈다. 그녀는 두 개의 파이프라인 화면을 나란히 띄웠다. 차이점이라고는 각 상자 아래에 적힌 목적지 이름, EKS dev namespaceEKS prod namespace 정도뿐이었다.

“언니, 이거 봐.”

솔라가 약간은 실망한 목소리로 루나를 불렀다.

prod-pipelinedev 파이프라인이랑 구조가 완전히 똑같아. 그냥 목적지만 prod 환경으로 바꾼 거 아니야? 이러면 main 브랜치에 코드가 병합되는 순간, 저번처럼 자동으로 실제 서비스까지 배포된다는 뜻이잖아. 위험하지 않아?”

솔라의 말대로라면, dev 브랜치에 푸시하는 것과 main 브랜치에 병합하는 것의 차이는 단지 최종 목적지가 다를 뿐, 그 과정의 자동화 수준은 동일했다. 속도를 위해 설계된 레일을 그대로 가져와 목적지만 바꿔 단 셈이었다.

“그림만 보면 네 말이 맞아.”

루나는 솔라가 띄워놓은 두 개의 다이어그램을 잠시 보더니, prod-pipeline 쪽의 실행 이력(Execution history) 버튼을 가리켰다.

“하지만 레일의 진짜 모습은 설계도가 아니라, 기차가 지나간 흔적에 남아있지. dev 파이프라인과 prod 파이프라인의 실행 기록을 한번 비교해 볼래?”

솔라는 먼저 dev-pipeline의 실행 기록을 열었다. 며칠간의 기록이 빽빽했다. 모든 실행이 Succeeded 녹색 표시를 달고 있었고, 시작부터 끝까지 막힘없이 이어진 자동 실행의 흔적이 선명했다.

이어서 그녀는 prod-pipeline의 실행 기록을 열었다. 분위기가 사뭇 달랐다. 실행 횟수 자체가 dev에 비해 훨씬 적었고, 드문드문 Failed라는 빨간 글씨도 보였다. 하지만 솔라의 눈길을 사로잡은 것은 따로 있었다. 가장 최근의 실행 기록 하나가 ‘진행 중(In Progress)’ 상태로 멈춰 있었던 것이다. 자동화된 레일이라면 벌써 끝나고도 남았을 시간이었다.

“왜 멈춰있지?”

솔라는 홀린 듯 ‘진행 중’ 상태인 파이프라인의 상세 보기로 들어갔다. Source 단계는 녹색, Build 단계도 녹색. 코드를 가져와 빌드하는 것까지는 성공적으로 끝났다. 하지만 그 다음, Deploy 단계로 넘어가지 않고 중간에 새로운 상자 하나가 끼어 있었다.

ManualApproval

dev 파이프라인에서는 본 적 없는, 낯선 이름이었다. 그 단계는 파란색으로 깜빡이며, 마치 신호등의 빨간불처럼 전체 흐름을 가로막고 있었다. 그 아래에는 ‘검토(Review)’라는 버튼이 활성화되어 있었다.

“아…”

솔라는 그제야 깨달았다. 두 레일은 같지 않았다. 겉보기엔 비슷했지만, main 브랜치의 코드를 실어 나르는 이 레일에는 의도적으로 설치된 ‘정지 신호’가 있었다. dev 레일이 속도를 위해 논스톱으로 달리는 KTX였다면, main 레일은 중요한 교차로마다 일단 멈춰 서서 안전을 확인하는 화물 열차와 같았다.

“이건… 자동이 아니었어.”

솔라가 나지막이 중얼거렸다.

dev 레일이 개발자의 실수를 빠르게 ‘검증’하기 위한 자동 레일이라면, 이 main 레일은 운영 환경을 ‘보호’하기 위한 통제 레일이었구나. 속도가 아니라 통제가 목적인 거야. 그래서 일부러 중간에 브레이크를 걸어서, 사람이 마지막으로 확인하고 승인하지 않으면 절대 최종 목적지로 출발할 수 없게 막아놓은 거고.”

‘간단히 도입한다’는 말 뒤에 숨겨진 깊은 설계 의도가 마침내 그 모습을 온전히 드러냈다. devmain의 분리는 단순히 코드 저장소를 나누는 것을 넘어, 각각 ‘자동 검증 레일’과 ‘운영 통제 레일’이라는 성격이 판이하게 다른 두 개의 파이프라인으로 이어지는 거대한 아키텍처의 시작점이었다. Manual Approval 단계는 그 아키텍처의 핵심 안전장치였다.

솔라의 시선은 다시 ‘검토’ 버튼에 머물렀다. 이 버튼 하나가 아찔한 배포 사고를 막아주는 마지막 파수꾼처럼 느껴졌다. 하지만 동시에 새로운 의문이 피어올랐다.

“그렇다면 이 ‘운영 통제’라는 건, 그냥 이 버튼 하나가 전부일까? 이 버튼을 누르는 사람은 어떤 근거로 ‘안전하다’고 판단하는 거지? 만약 실수로 누르면? 이 버튼을 누르기 전, 우리가 따라야 하는 또 다른 규칙이나 약속이 이 레일 어딘가에 숨겨져 있는 거 아닐까?“

4장: 배포의 파수꾼: 수동 승인과 PR 흐름

솔라의 시선은 prod-pipeline 화면의 한 지점에 고정되어 있었다. 파란색으로 깜빡이는 ManualApproval 단계, 그리고 그 아래에 활성화된 ‘검토(Review)’ 버튼. 마우스 커서를 가져가자 손가락 모양으로 바뀌었지만, 감히 클릭할 엄두가 나지 않았다. 이 버튼 하나에 실제 운영 서비스의 운명이 달려있다고 생각하니, 단순한 UI 요소가 아니라 거대한 수문의 잠금장치처럼 느껴졌다.

하지만 동시에 의구심이 들었다. 이렇게 중요한 결정이 고작 버튼 클릭 한 번에 좌우된다는 게 어딘가 허술하게 느껴졌다. 자동화된 dev 레일의 깔끔함과 비교되어서일까. 수동으로 개입하는 이 과정이 오히려 실수를 유발하는 병목 지점처럼 보이기도 했다.

“언니, 이 ManualApproval 단계 말이야.”

솔라는 마침내 입을 열었다.

“운영 환경을 보호하기 위한 통제 레일이라는 건 알겠어. 그런데 결국 사람이 버튼을 누르는 거잖아. 만약 피곤한 오후에 실수로 누르면? 아니면, ‘괜찮겠지’ 하고 무심코 누르면? 모든 자동화의 끝에 이런 수동 절차 하나만 덜렁 있는 건, 좀 불안한 안전장치 아닐까?”

루나는 대답 대신, 솔라의 책상에 있던 포스트잇 한 장을 가져왔다. 그리고는 모니터 화면의 ManualApproval 상자 위로 그것을 조심스럽게 붙여 가려버렸다. ‘검토’ 버튼도, 파랗게 깜빡이던 신호도 더는 보이지 않았다. 이제 prod-pipelineBuild 단계에서 Deploy 단계로 바로 이어지는, dev-pipeline과 똑같은 모습이 되었다.

“좋아. 그럼 그 불안한 안전장치를 잠시 치워보자.”

루나가 차분한 목소리로 말했다.

“그리고 상상해 보는 거야. 어떤 개발자가 실수로 치명적인 버그가 담긴 코드를 dev에서 검증하고, 그걸 그대로 main 브랜치에 병합했다고. 이 ManualApproval이 없는 레일 위에서, 그 코드는 이제 어디로, 어떻게 흘러갈까?”

솔라는 잠시 숨을 삼켰다. 포스트잇으로 가려진 레일의 빈 공간이 아찔한 절벽처럼 느껴졌다. 그녀는 루나의 말에 따라 머릿속으로 최악의 시나리오를 그리기 시작했다.

main 브랜치에 코드가 병합되는 순간, prod-pipeline은 자동으로 움직이기 시작한다. Source 단계가 녹색으로 바뀌고, 곧이어 Build 단계도 성공적으로 완료된다. 그리고… 멈추는 곳이 없다. 잠시 후, Deploy 단계까지 거침없이 녹색 불을 켠다. 실제 사용자들이 접속해 있는 운영 서버에, 방금 막 병합된 버그 코드가 그대로 배포된 것이다.

”…서버가 다운될 수도 있고, 사용자 데이터가 엉망이 될 수도 있겠지. 고객센터는 불만 전화로 마비되고, 우리는 원인을 찾기 위해 밤을 새워야 할 거야. 모든 게 정상으로 돌아올 때까지 서비스는 중단될 거고.”

솔라는 상상만으로도 피곤해지는 얼굴로 말했다.

“자동화는 실수를 용납하지 않으니까. 좋은 쪽으로든, 나쁜 쪽으로든. 이 버튼은 그 맹목적인 자동화의 흐름을 멈춰 세우고, 사람에게 생각할 시간을 주는 마지막 브레이크였구나.”

이제 솔라는 이 수동 절차가 번거로운 과정이 아니라, 반드시 필요한 안전장치임을 받아들였다. 하지만 원래의 질문이 다른 모습으로 되돌아왔다.

“알겠어. 멈추는 건 중요해. 그런데 이 브레이크를 다시 ‘푸는’ 근거는 뭐야? 승인 버튼을 누르는 사람은 도대체 뭘 보고 ‘이제 안전하다. 출발시켜도 좋다’고 판단하는 거지? 그 판단의 근거가 없다면, 결국 실수로 누르는 것과 다를 바 없잖아.”

그녀의 질문에 루나는 파이프라인 화면을 내리고, 대신 익숙한 깃허브 저장소 페이지를 열었다. 그리고 최근 main 브랜치에 병합된 풀 리퀘스트(Pull Request) 목록 중 하나를 클릭했다.

화면에는 dev 브랜치의 변경 사항을 main으로 합치기 위해 생성된 토론장이 펼쳐져 있었다. 코드 변경 내역 아래에는 다른 팀원들의 코드 리뷰 댓글과 승인 표시(Approved)가 달려 있었다. 오른쪽에는 자동화된 테스트가 모두 통과했음을 알리는 녹색 체크 표시들이 나열되어 있었다.

“파이프라인의 ManualApproval 버튼은 결코 혼자 있지 않아.”

루나가 풀 리퀘스트 화면을 가리키며 말했다.

“그 버튼을 누르는 사람은 자신의 감을 믿는 게 아니야. 바로 이 기록들을 신뢰하는 거지. ‘이 코드는 최소 두 명 이상의 동료가 검토하고 동의했습니다.’, ‘이 코드는 우리가 약속한 모든 자동 테스트를 통과했습니다.’, ‘이 변경으로 인해 발생할 수 있는 문제에 대해 충분히 논의했습니다.’ 이 모든 약속들이 지켜졌음을 확인하고, 그에 대한 최종 책임을 지는 행위가 바로 ‘승인’ 버튼을 누르는 거야.”

솔라의 눈이 커졌다. 그녀는 이제야 두 개의 다른 시스템이 어떻게 하나의 안전망으로 연결되는지 깨달았다. ManualApproval 버튼은 배포 파이프라인이라는 레일 위에 놓인 정지 신호등이었고, 풀 리퀘스트(PR) 검토 과정은 그 신호등을 안전하게 통과하기 위한 운전 면허 시험과도 같았다.

“아…! 그러니까 main으로 가는 레일을 타기 전에, 풀 리퀘스트라는 별도의 검문소에서 동료들의 검토와 자동화된 테스트라는 철저한 심사를 먼저 통과해야 했던 거구나. 파이프라인의 수동 승인은 그 모든 절차가 제대로 이행되었음을 마지막으로 확인하는 도장에 불과했던 거네.”

자동화의 속도만으로는 잡을 수 없는 논리적 오류나 비즈니스 영향도에 대한 판단. 기계가 아닌 동료 개발자의 경험과 지혜를 통해 걸러지는 문제들. 이 모든 것이 ‘인적 개입’이라는 이름의 촘촘한 안전망을 이루고 있었다.

솔라는 이 모든 장치들이 결국 서로를 어떻게 보완하며 작동하는지 생각하기 시작했다. devmain이라는 목적 기반의 분리, 속도를 위한 ‘자동 검증 레일’과 통제를 위한 ‘운영 통제 레일’의 분리, 그리고 마지막으로 자동화의 맹점을 보완하는 ‘인적 개입 안전망’까지.

“그럼 이 모든 게 합쳐져서 하나의 거대한 시스템을 이루는 거네. 브랜치 분리, 두 개의 다른 레일, 그리고 이 인적 안전망까지… 이 조각들을 다 모으면, 우리가 만들고자 했던 전체 ‘안전장치 아키텍처’의 청사진이 그려지는 걸까?“

5장: 안전한 배포 아키텍처: 분리된 레일의 시너지

솔라의 책상 위에는, 지난 며칠간의 여정을 요약한 한 장의 그림이 펼쳐져 있었다. 그녀가 직접 그린, 삐뚤빼뚤하지만 핵심이 담긴 도식이었다. 왼쪽에는 devmain이라는 두 개의 Git 브랜치가, 오른쪽에는 dev-namespaceprod-namespace라는 최종 목적지가 그려져 있었다. 그리고 그 사이를 잇는 두 갈래의 길. 하나는 멈춤 없이 달리는 직선의 ‘자동 검증 레일’이었고, 다른 하나는 중간에 ‘PR 검토’와 ‘수동 승인’이라는 정거장이 그려진 ‘운영 통제 레일’이었다.

그녀는 지금까지 알아낸 개념들을—‘목적 기반 분리’, ‘자동 검증 레일’, ‘운영 통제 레일’, ‘인적 개입 안전망’—각각의 위치에 포스트잇으로 붙여두었다. 조각난 퍼즐들을 모두 찾아 제자리에 끼워 맞춘 기분이었다. 하지만 그림을 완성하고 나니, 오히려 풀리지 않는 의문이 더 선명해졌다. 이 모든 장치들은 결국 어떤 하나의 큰 그림을 그리고 있는 걸까? 각자 맡은 역할은 알겠지만, 이들이 함께 작동하며 만들어내는 시너지는 무엇일까.

“언니, 내가 그린 이 아키텍처 그림 좀 봐.”

솔라가 그림을 들고 루나에게 다가갔다.

“브랜치 분리, 두 개의 다른 레일, 그리고 인적 안전망까지. 우리가 이야기한 안전장치들을 전부 그려 넣었어. 그런데 이렇게 보니까… 그냥 절차가 두 배로 복잡해진 것 같아. 개발자 입장에서는 dev 브랜치에 코드를 보내고, 또 PR을 만들어서 main에 보내야 하잖아. 각 레일이 서로를 어떻게 보완하는지는 알겠는데, 이게 하나의 통합된 시스템으로서는 어떻게 작동한다는 건지 잘 와닿지가 않아.”

솔라의 그림은 정확했지만, 각 요소들이 서로 단절된 채 각자의 역할만 수행하는 것처럼 보였다. 두 개의 철로가 나란히 놓여있을 뿐, 서로 교차하거나 신호를 주고받는 유기적인 관계는 보이지 않았다.

루나는 솔라의 그림을 잠시 들여다보더니, 펜을 하나 집어 들었다. 그림 위에 무언가를 그리려는 대신, 그녀는 빈 공간에 아주 작은 글씨로 이렇게 썼다.

신규 기능: ‘좋아요’ 버튼 추가

“자, 이 새로운 기능이 우리 시스템에 들어온다고 상상해 보자.”

루나가 말했다.

“솔라 네가 이 그림 위에서, 이 작은 기능의 코드가 여행하는 경로를 직접 한번 따라가 볼래? 개발자가 첫 코드를 작성하는 순간부터, 실제 사용자가 ‘좋아요’ 버튼을 누를 수 있게 되기까지. 모든 안전장치가 어떻게 작동하는지 하나씩 짚어가면서.”

솔라는 잠시 생각에 잠기더니, 루나의 펜을 건네받아 ‘좋아요’ 버튼이라는 가상의 코드를 상징하는 작은 점을 찍었다. 그녀의 펜 끝이 복잡한 아키텍처 위에서 긴 여정을 시작했다.

“첫째, 개발자는 dev 브랜치에서 새로운 feature/like-button 브랜치를 만들어서 코드를 작성하고, 작업이 끝나면 dev 브랜치에 병합해. 그러면…”

솔라의 펜이 dev 브랜치에서 ‘자동 검증 레일’로 이동했다.

”…자동으로 dev-pipeline이 동작해서, 빌드하고, 테스트하고, dev-namespace에 배포하겠지. 그럼 팀원들은 다 같이 개발 환경에 접속해서 ‘좋아요’ 버튼이 다른 기능과 충돌 없이 잘 작동하는지 바로 확인할 수 있어. 여기서 1차 검증이 끝나는 거야. 이게 ‘자동 검증 레일’의 역할.”

펜 끝이 dev-namespace에 도착하자, 솔라는 잠시 멈췄다. 그리고는 다시 펜을 들어 dev 브랜치에서 main 브랜치를 향하는 화살표, 즉 ‘PR(풀 리퀘스트)’ 경로로 옮겨갔다.

“둘째, 개발 환경에서 기능이 안정적이라고 판단되면, dev의 변경사항을 main으로 합쳐달라는 PR을 생성해. 여기서부터는 ‘인적 개입 안전망’이 작동하기 시작하지. 다른 동료들이 코드를 리뷰하고, 비즈니스 로직에 문제는 없는지, 잠재적인 위험은 없는지 꼼꼼히 따져보고 승인해 줄 거야.”

솔라의 펜은 ‘PR 검토’라는 정거장을 지나 마침내 main 브랜치에 닿았다. 코드가 병합되는 순간, 그녀의 펜은 곧바로 ‘운영 통제 레일’ 위로 올라탔다.

“셋째, main 브랜치에 코드가 들어오면, prod-pipeline이 자동으로 시작돼. 하지만 dev 때와는 달라. 빌드 후에 ‘수동 승인’ 단계에서 딱 멈추지. 레일에 빨간 불이 켜진 것처럼. 이게 바로 ‘운영 통제 레일’의 핵심이야.”

마지막 단계였다. 솔라의 펜은 ‘수동 승인’이라는 마지막 관문 앞에 멈춰 섰다.

“그리고 넷째, 배포 책임자가 PR에 모든 동료의 검토와 테스트 통과 기록이 남아있는 걸 최종 확인하고, 이 ‘수동 승인’ 버튼을 눌러. 그러면 비로소 배포가 재개되고, ‘좋아요’ 버튼이 실제 사용자들이 쓰는 prod-namespace에 안전하게 도착하는 거야.”

펜 끝이 마침내 최종 목적지인 prod-namespace에 닿는 순간, 솔라는 숨을 내쉬었다. 직접 가상의 시나리오를 따라 그림 위를 여행하고 나니, 전에는 보이지 않던 것들이 보이기 시작했다. 이것은 단순히 두 개의 분리된 레일이 아니었다.

”…알겠다.”

솔라의 목소리에 깨달음이 묻어났다.

“이건 그냥 병렬로 놓인 두 개의 길이 아니었어. dev 레일에서 1차 검증을 통과하지 못한 코드는 애초에 PR조차 만들 수 없고, PR이라는 인적 안전망을 통과하지 못한 코드는 main 레일 위에 올라탈 자격조차 없어. 그리고 main 레일 위에 올라탔다고 해도, 최종 수동 승인이라는 마지막 관문을 넘지 못하면 절대 운영 환경에 닿을 수 없는 거야. 모든 단계가 서로를 전제로 하고, 다음 단계의 안전을 보장하는 연쇄적인 안전장치였어.”

솔라는 처음 이 여정을 시작하게 했던, 문서의 한 구절을 떠올렸다.

“브랜치 전략은 main/dev로 간단히 도입하고, 추가 기능은 dev 브랜치에서 작업 후 main에 병합한다.”

전에는 이 문장이 모든 복잡성을 무시한, 위험할 정도로 ‘간단한’ 설명이라고 생각했다. 하지만 이제는 그 의미가 완전히 다르게 읽혔다. ‘간단히 도입한다’는 말은 아키텍처가 단순하다는 뜻이 아니었다. 개발자가 따라야 할 규칙은 ‘dev에서 작업하고 main에 병합한다’는 단순한 흐름이지만, 그 단순한 약속의 이면에는 이 모든 다중 레일 안전 아키텍처가 촘촘하게 받쳐주고 있다는 선언이었던 것이다. 복잡성은 시스템이 책임지고, 개발자는 개발에만 집중할 수 있도록 설계된 것.

솔라는 자신이 그렸던 도식의 빈 공간에, 새로운 제목을 적어 넣었다.

‘다중 레일 배포 안전 아키텍처’

그리고 그 아래에, 원래 문장을 자신만의 언어로 다시 고쳐 썼다.

‘브랜치 분리는 단순히 코드를 나누는 규칙이 아니라, 신속한 검증과 안정적인 운영이라는 두 가지 목적을 모두 달성하기 위한 다중 레일 아키텍처의 시작점이다. 각 레일과 안전망은 서로 유기적으로 연결되어, 코드 변경이 운영 환경에 도달하기까지의 전 과정을 통제하고 보호한다.’

더 이상 ‘간단히 도입한다’는 말에 흔들리지 않았다. 그 말 뒤에 숨겨진 정교한 설계와 깊은 고민을, 이제는 자신의 눈으로 직접 보고 이해했기 때문이었다.