CI/CD Practice 03

CodePipeline, GitHub 저장소 소스로 연결하기: 흐릿한 관계를 명확하게

S3 zip 업로드에서 GitHub 저장소로 source를 바꾸면 편해지는 것은 알겠지만, 토큰, 연결, push, pipeline trigger가 어떤 관계인지 흐릿하다.

근거 · CI/CD 교안 p36-p55

CodePipeline, GitHub 저장소 소스로 연결하기: 흐릿한 관계를 명확하게 대표 이미지

1장: CodePipeline은 GitHub에 어떻게 접속하나요?

솔라의 모니터에는 AWS CodePipeline 설정 화면과 GitHub의 ‘Personal Access Token’ 생성 화면, 두 개의 탭이 나란히 떠 있었다. 이전 프로젝트에서는 소스 코드를 S3 버킷에 zip 파일로 올려서 파이프라인을 실행했었다. 간편했지만, 코드를 수정할 때마다 압축해서 올리는 과정이 번거로웠다. 그래서 이번에는 더 ‘제대로’ 해보고 싶었다. 바로 GitHub 저장소를 파이프라인의 소스 단계에 직접 연결하는 것이다.

분명 더 편하고 자동화된 방식일 거라 기대했는데, 솔라는 미간을 찌푸렸다. 안내 문서에는 ‘GitHub 연결을 생성하라’고 되어 있고, 또 다른 한편에서는 ‘GitHub에서 repo 권한을 가진 PAT(Personal Access Token)를 발급받으라’고 쓰여 있었다. 두 작업 모두 GitHub와 관련이 있는데, 왜 별도로 해야 하는지 알 수 없었다.

“언니, 나 이것 좀 봐줘. 이상해.”

솔라가 부르는 소리에 옆에서 조용히 책을 읽던 루나가 다가왔다.

“CodePipeline에서 GitHub를 소스로 쓰고 싶은데, ‘연결’이라는 걸 만들라고 해서 만들었어. 그럼 내 GitHub 계정이랑 연결된 거 아냐? 그런데 또 GitHub에 가서 토큰이라는 걸 따로 만들어서 등록하래. 그냥 계정끼리 연결하는 거면 알아서 다 해주면 되는 거 아니야? 꼭 이중 잠금장치 같아.”

솔라의 말에는 ‘편해지려고 했는데 왜 더 복잡하게 느껴지지?’ 하는 답답함이 묻어났다. 그냥 화면에 나오는 대로 따라 하면 되지만, 왜 그래야 하는지 이해되지 않는 찝찝함이 남았다.

루나는 솔라의 화면을 잠시 들여다보더니, 빈 종이를 가져와 두 개의 큰 네모를 그렸다. 하나에는 ‘AWS CodePipeline’, 다른 하나에는 ‘GitHub’라고 적었다.

“솔라, 좋은 지적이야. 이건 두 회사가 서로 다른 건물에 있다고 상상하면 쉬워.”

루나는 말을 이었다.

“AWS와 GitHub는 완전히 다른 서비스지. 서로의 내부 사정을 알 수도 없고, 마음대로 드나들어서도 안 돼. 솔라 네가 두 서비스 모두의 계정을 가지고 있을 뿐이야.”

“그건 당연하지. 그래서 ‘연결’하는 거 아니었어? 우리 서로 아는 사이니까 문 열어주자, 하고.”

솔라가 받아쳤다. 그녀의 생각에 ‘연결’은 두 서비스가 악수하고 서로를 신뢰하기로 약속하는 과정이었다.

“거의 비슷해. 하지만 그냥 ‘나 솔라인데 문 열어줘’라고 말하는 것만으로는 부족해.”

루나는 ‘AWS CodePipeline’ 네모에서 ‘GitHub’ 네모로 향하는 화살표를 그렸다.

“CodePipeline이 네 GitHub 저장소에 있는 코드를 가지러 가야 해. 그러려면 먼저 GitHub의 문을 두드려야겠지. 이때 CodePipeline은 ‘솔라 님의 부탁을 받고 왔습니다’라고 말할 거야. 여기까지가 네가 AWS 콘솔에서 ‘연결’을 생성하는 행위의 의미야. ‘내가 이제부터 저쪽 GitHub에 말을 걸 거야’라고 선언하는 거지.”

루나는 잠시 말을 멈추고 솔라가 이해했는지 살폈다.

“그럼 GitHub는 뭐라고 대답할까? ‘솔라 님을 어떻게 믿죠? 그리고 당신이 우리 건물에 들어와서 정확히 뭘 할 수 있는지 증명해 보세요’라고 하지 않을까?”

“아…! 그래서 증표가 필요한 거구나.”

솔라의 눈이 동그래졌다.

“맞아. 그 증표가 바로 Personal Access Token, PAT야.”

루나는 펜으로 ‘GitHub’ 네모 안에 작은 열쇠 모양을 그렸다.

“솔라 네가 GitHub에 가서 토큰을 만든다는 건, GitHub에게 ‘이 특별한 열쇠(토큰)를 가지고 오는 외부인(CodePipeline)에게는 내 특정 방(저장소)에 들어와서 물건(코드)을 볼 수 있는 권한(repo scope)을 주세요’라고 미리 허락을 받아두는 것과 같아. 토큰은 비밀번호처럼 모든 권한을 주는 게 아니라, 딱 명시된 권한만 가진 일회성 열쇠 같은 거야.”

이제 솔라의 머릿속에서 흩어져 있던 조각들이 맞춰지기 시작했다. CodePipeline의 ‘연결’ 설정 화면에 토큰을 입력하는 칸이 왜 있었는지, GitHub에서 토큰을 만들 때 왜 ‘repo’, ‘admin’처럼 복잡한 권한을 선택해야 했는지 명확해졌다.

요소역할 비유실제 기능
AWS-GitHub 연결”제가 GitHub에 말을 걸겠습니다”라는 선언CodePipeline과 GitHub 간의 통신 채널 설정
GitHub PAT”이 열쇠를 가진 사람은 믿어주세요”라는 인증서 겸 열쇠CodePipeline이 GitHub API를 호출할 때 사용하는 인증 자격증명
repo 권한”열쇠로 저장소(방)를 보고 읽을 수만 있습니다”라는 규칙토큰이 가진 권한의 범위 (코드 읽기, 웹훅 설정 등)

솔라는 고개를 끄덕이며 자신의 언어로 정리했다.

“알았어! ‘연결’은 CodePipeline과 GitHub 사이에 다리를 놓겠다는 선언이고, PAT는 그 다리를 건널 때 보여줘야 하는 통행증 같은 거네. 그냥 ‘나 솔라야’라고 외치는 게 아니라, GitHub가 알아볼 수 있는 공식적인 ‘열쇠’를 쥐여주는 거구나. CodePipeline은 그냥 내 심부름꾼일 뿐이고, 내가 심부름꾼 손에 뭘 들려 보낼지 직접 정하는 거였어.”

이제 ‘연결’과 ‘토큰’은 더 이상 중복되거나 번거로운 절차가 아니었다. 각자 명확한 역할과 책임이 있는, 꼭 필요한 보안 단계로 보였다. 솔라는 뿌듯한 표정으로 GitHub에서 새로 발급받은 토큰을 복사해 CodePipeline 연결 설정에 붙여 넣었다. ‘연결 성공’ 메시지가 화면에 떴다.

흐릿했던 관계가 명확해지는 순간이었다. 하지만 솔라의 머릿속에는 곧바로 새로운 질문이 떠올랐다.

“좋아, 이제 CodePipeline이 내 GitHub 저장소에 들어올 수 있는 공식적인 열쇠를 가졌어. 그런데… 심부름꾼이 언제 우리 집에 와야 하는지는 어떻게 알지? 내가 코드를 새로 고칠 때마다 알려줘야 하나? 아니면 계속 문 앞에서 기다리고 있나?“

2장: 내 Git push가 GitHub에 미치는 영향은 무엇인가요?

CodePipeline과 GitHub 사이의 안전한 통로가 열리자, 솔라는 자신감이 붙었다. 이제 드디어 자신의 코드를 파이프라인에 태울 차례였다. 이전처럼 zip 파일을 만들어 S3에 올리는 대신, 더 ‘현대적인’ 방식으로 말이다.

솔라의 컴퓨터에는 test-website-repo라는 이름의 폴더가 있었고, 그 안에는 간단한 index.html 파일이 들어있었다. 터미널 창을 열고, 그녀는 안내 문서에서 봤던 일련의 명령어들을 쳐다보았다.

git init
git add .
git commit -m "first commit"
git remote add origin <GitHub_저장소_URL>
git push -u origin main

“으… 복잡해.”

솔라는 무심코 혼잣말을 내뱉었다. S3에 올릴 때는 그냥 파일을 압축해서 웹 브라우저에서 드래그 앤 드롭하면 끝이었는데, 이건 마치 여러 단계의 암호를 통과하는 기분이었다.

“언니, 이것 좀 봐. 결국엔 내 index.html 파일 하나 GitHub에 올리는 건데, 왜 이렇게 절차가 많아? git push라는 게 S3 업로드랑 뭐가 다른 거지? 그냥 더 복잡한 업로드 버튼 같은데.”

솔라의 투덜거림에 루나가 다가와 모니터를 들여다보았다. 화면에는 검은 터미널 창과 실행되기를 기다리는 명령어들이 떠 있었다.

“정말 그럴까, 솔라? S3에 파일을 올리는 것과 본질적으로 같을까?”

루나는 책상 위 메모 패드를 가져와 두 개의 칸으로 나눴다. 왼쪽에는 ‘S3에 zip 파일 업로드’, 오른쪽에는 ‘GitHub에 push’라고 적었다.

“네가 이전에 했던 방식을 한번 떠올려보자. S3로 작업할 때, 코드를 바꾸면 어떻게 했지?”

“음… index.html 파일을 수정하고, 폴더 전체를 my-website-v2.zip 같은 이름으로 압축해서 S3 버킷에 올렸지.”

솔라가 말하는 동안 루나는 왼쪽 칸에 내용을 요약해 적었다.

  1. 로컬에서 코드 수정
  2. 새 버전의 zip 파일 생성 (예: my-website-v2.zip)
  3. S3 콘솔에 업로드

“좋아. 여기서 중요한 질문. S3는 my-website-v2.zip 파일이 v1에서 정확히 무엇이 바뀌었는지 알고 있을까?”

“아니. 그냥 이름이 다른 새 파일일 뿐이지. 이전 파일이랑은 아무 관계도 없어.”

“맞아. S3의 관점에서 보면, 그저 저장 공간에 또 하나의 독립적인 객체가 추가된 것뿐이야.”

루나는 ‘GitHub에 push’라고 적힌 오른쪽 칸을 가리켰다.

“자, 이제 이걸 해보자. 대신 각 명령어가 무슨 일을 하는지 생각하면서.”

솔라는 다시 터미널로 돌아가 명령어를 하나씩 입력하기 시작했다. git init, git add . 까지는 순조로웠다. git commit 차례가 되자 루나가 솔라를 멈춰 세웠다.

“잠깐. 그 명령어는 무슨 의미지?”

“커밋? 그냥 저장하는 거 아냐? ‘first commit’이라는 메시지를 남기라고 하던데.”

“‘메시지를 남긴다’는 게 핵심이야. 단순 저장이 아니라, ‘어떤 변경사항이 있었는지’에 대한 공식적인 기록, 즉 버전의 이정표를 만드는 행위야.”

솔라는 git commit -m "첫 웹사이트 커밋" 이라고 입력하고 엔터를 쳤다. 파일이 어딘가로 전송된 것은 아니었다. 자신의 컴퓨터 안에서 ‘첫 웹사이트 커밋’이라는 이름표를 단 버전이 하나 생성되었을 뿐이다.

마지막으로 git push 명령을 실행하자, 몇 줄의 메시지와 함께 모든 과정이 끝났다.

“이제 GitHub 저장소 페이지를 새로고침 해봐.”

솔라가 페이지를 새로고침하자, 비어있던 저장소에 index.html 파일이 나타났다.

“올라갔네! 봐봐, 결국 파일 업로드 맞잖아.”

솔라가 의기양양하게 말하는 순간, 루나가 마우스 커서를 다른 곳으로 움직였다. 파일 목록 위, < > Code 탭 옆에 있는 Commits 라는 링크였다.

“여기를 눌러볼래?”

솔라가 Commits를 클릭하자, 새로운 화면이 나타났다. 그곳에는 방금 솔라가 입력했던 “첫 웹사이트 커밋”이라는 메시지와 함께 고유한 코드(커밋 해시), 작성자, 시간이 기록되어 있었다.

그 순간 솔라는 깨달았다. 메모장의 두 칸이 머릿속에서 선명하게 대비되었다.

S3에 zip 파일 업로드GitHub에 push
독립적인 파일 덩어리를 올림변경 이력을 가진 버전을 올림
S3는 버전 간의 관계를 모름GitHub는 각 버전(커밋)의 연결고리를 모두 알고 있음
창고에 새 상자를 그냥 쌓는 것도서관의 장부에 새 기록을 등재하고 책을 서가에 꽂는 것

“아…! 다르네. 완전히 달라.”

솔라가 나지막이 말했다.

“S3에 파일을 올리는 건 그냥 창고에 이름표만 붙인 상자를 던져두는 거였어. 이전 상자와 무슨 관계인지, 안에 뭐가 어떻게 바뀌었는지 아무도 모르지. 그런데 git push는… 내 컴퓨터에서 만든 ‘버전 기록’ 자체를 GitHub라는 중앙 저장소에 그대로 옮겨서 업데이트하는 거구나. 파일 하나가 아니라, 프로젝트의 역사를 통째로 동기화하는 거였어.”

git push는 단순 파일 복사가 아니었다. 그것은 GitHub 저장소를 프로젝트의 유일한 ‘진실의 원천(Single Source of Truth)‘으로 인정하고, 그곳의 상태를 공식적으로 ‘새로운 버전’으로 업데이트하는 중대한 행위였다.

이제 막 자신의 손으로 프로젝트의 공식적인 첫 번째 버전을 만들어낸 솔라의 머릿속에, 이전 장에서 풀리지 않았던 질문이 새로운 형태로 떠올랐다.

“좋아. 이제 내 GitHub 저장소는 공식적으로 ‘새 버전’이 됐어. 그런데 이걸로 끝인가? CodePipeline은 이 사실을 어떻게 알지? 내가 방금 GitHub에 새 역사를 썼다는 걸, 누가 알려주는 거야?“

3장: GitHub 변경이 CodePipeline을 어떻게 깨우고 아티팩트를 만들까요?

솔라의 모니터에는 이제 막 첫 번째 커밋이 기록된 GitHub 저장소 페이지와, 그 저장소를 소스로 지정한 CodePipeline 대시보드가 나란히 떠 있었다. 모든 설정은 성공적으로 끝났다. 인증된 통로가 만들어졌고, 그 통로를 통해 가져올 첫 번째 버전의 코드도 준비되었다. 그런데 두 화면은 서로 아무런 관련이 없는 것처럼 조용했다. 파이프라인의 ‘Source’ 단계는 여전히 ‘아직 실행되지 않음’ 상태였다.

“음…”

솔라는 팔짱을 끼고 화면을 노려보았다. 이전 장에서 git push가 프로젝트의 공식적인 새 버전을 만드는 행위라는 것은 명확히 이해했다. 하지만 그게 전부인 걸까? GitHub에 새 역사가 쓰였다는 사실을 CodePipeline은 언제쯤 알아챌까? 1분마다 한 번씩 확인하러 오나? 아니면 5분에 한 번? 솔라는 자기도 모르게 CodePipeline 화면의 새로고침 버튼 위로 마우스 커서를 가져갔다.

“어떻게 생각해? CodePipeline이 지금 뭘 하고 있을까?”

솔라의 혼잣말 같은 질문에 루나가 다가와 화면을 들여다봤다.

“글쎄, 네 생각은 어때?”

“음… 내 심부름꾼(CodePipeline)이 내가 맡긴 열쇠(PAT)를 가지고 있으니까… 주기적으로 우리 집(GitHub)에 와서 ‘새로 바뀐 거 없나요?’ 하고 물어보는 거 아닐까? 마치 순찰 도는 경비원처럼.”

솔라의 대답은 그럴듯했다. 자동화 시스템이라면 정해진 시간마다 상태를 확인하는 방식이 흔하기 때문이다. 이것이 바로 ‘폴링(Polling)’ 방식이다.

“그것도 하나의 방법이지.” 루나는 잠시 생각하더니 다른 제안을 했다. “실험을 하나 해보자. index.html 파일에 아주 작은 거라도 좋으니, 뭔가 하나 수정하고 다시 커밋해서 push 해보는 거야. 대신 이번에는 터미널에서 push 명령을 실행하는 순간, CodePipeline 화면에서 눈을 떼지 마.”

솔라는 고개를 끄덕이고 index.html 파일을 열어 제목 태그의 내용을 살짝 바꿨다. 그리고 다시 터미널을 열어 익숙한 명령어들을 입력했다.

git add .
git commit -m "Update page title"

마지막 git push 명령어를 입력할 차례. 솔라는 심호흡 한번 하고, 한쪽 눈은 터미널에, 다른 한쪽 눈은 CodePipeline 대시보드에 고정했다. 그리고 엔터 키를 눌렀다.

터미널에 git push가 완료되었다는 메시지가 뜨기 무섭게, 거짓말처럼 CodePipeline 화면이 바뀌었다. 새로고침 버튼을 누르지도 않았는데, 회색이었던 ‘Source’ 단계가 파란색 ‘진행 중(In Progress)’ 상태로 순식간에 활성화된 것이다.

“와! 바로 되네!”

솔라가 놀라서 외쳤다.

“내가 새로고침을 누르기도 전에 시작됐어. 순찰을 도는 게 아니었네. 이건… 마치 누가 알려준 것 같아!”

“정확해.”

루나는 기다렸다는 듯이, 이전 장에서 그렸던 그림 옆에 새로운 흐름을 그리기 시작했다.

“우리가 1장에서 만든 ‘연결’은 단순히 CodePipeline이 GitHub에 들어갈 수 있는 통로를 열어준 것만이 아니야. 그 과정에서 CodePipeline은 GitHub 저장소 설정에 몰래 ‘초인종’을 하나 설치해 둬.”

루나는 ‘GitHub’ 네모 박스에 작은 벨 모양을 그리고, 그 벨에서 ‘CodePipeline’으로 향하는 화살표를 추가했다.

“이 초인종의 기술적인 이름이 ‘웹훅(Webhook)‘이야. 네가 git push를 해서 저장소에 새로운 커밋이 생기면, 그게 ‘이벤트’가 돼. GitHub는 이 이벤트를 감지하고, 등록된 초인종, 즉 웹훅 주소로 ‘방금 main 브랜치에 새 커밋이 도착했어!’라는 신호를 즉시 보내주는 거지.”

솔라의 눈이 반짝였다. 주기적으로 확인하는 비효율적인 방식이 아니었다. 변경이 발생했을 때만 정확하게 호출되는, 훨씬 세련된 방식이었다.

잘못된 생각 (Polling)실제 동작 (Webhook)
CodePipeline이 주기적으로 GitHub를 확인하러 간다. (능동적)GitHub가 변경이 생기면 CodePipeline에게 알려준다. (수동적)
“뭐 바뀐 거 없어요?” 묻고 다니는 방식”띵동! 새 소식 왔어요!” 초인종 누르는 방식
변경이 없어도 계속 불필요한 확인 발생변경이 있을 때만 정확히 1번 통신 발생
변경 감지에 최대 몇 분의 지연 시간 발생 가능거의 실시간으로 즉시 반응

“알겠다! 그러니까 CodePipeline은 가만히 듣고만 있다가, GitHub가 초인종을 눌러주면 그때서야 움직이는 거구나. 그럼 움직여서 뭘 하는 거야? ‘진행 중’이라고 뜨고 나서는…”

“신호를 받은 CodePipeline은 이제 자신의 역할을 시작해. 먼저, 가지고 있던 열쇠(PAT)를 사용해서 GitHub 저장소에 접속해. 그리고 방금 도착했다고 알려온 바로 그 ‘Update page title’ 커밋 버전의 코드 전체를 깔끔하게 다운로드하는 거야. 마지막으로 그걸 하나의 압축 파일, 즉 **소스 아티팩트(Source Artifact)**로 만들어서 파이프라인의 다음 단계로 전달하는 거지.”

이제 모든 조각이 맞춰졌다. git push가 ‘버전 업데이트’라는 이벤트를 만들고, ‘연결’에 포함된 웹훅이 그 이벤트를 CodePipeline에 알리고, CodePipeline은 ‘PAT’로 인증하여 코드를 가져와 ‘아티팩트’로 포장한다. 흐릿했던 관계들이 하나의 명확한 워크플로우로 완성되는 순간이었다.

솔라는 책상 위의 빈 노트를 가져와 펜을 들었다. 그리고 방금 머릿속에서 완성된 그림을 자신의 손으로 직접 그려보기 시작했다.

  1. 나 (개발자 PC) git pushGitHub 저장소
    • (주석: ‘버전’의 역사를 업데이트!)
  2. GitHub 저장소 Push Event 발생! → 웹훅 호출 (초인종 누르기)
  3. 웹훅 신호 → AWS CodePipeline ‘연결’ 이 신호 수신
  4. CodePipeline “아, 일할 시간이군!”
    • (주석: PAT ‘열쇠’ 사용) → GitHub 저장소 로 가서 최신 커밋 코드 다운로드
  5. CodePipeline 다운로드한 코드를 SourceArtifact.zip 으로 포장
  6. Source Artifact다음 단계 (Build / Deploy) 로 전달

자신이 그린 다이어그램을 보며 솔라가 만족스러운 미소를 지었다.

“이제 흐릿하지 않아. 누가 누구에게 언제, 어떻게, 무엇을 전달하는지 명확해졌어. 이 설계도면, 다음 프로젝트에 그대로 쓸 수 있겠다.”

더 이상 ‘연결’은 귀찮은 절차가 아니었고, ‘push’는 단순 업로드가 아니었다. 모든 것이 자동화된 배포 파이프라인을 위한 정교한 약속이자 효율적인 소통 방식임을, 솔라는 이제 자신의 언어로 설명할 수 있게 되었다.