사전 커밋에서 배포까지
관통

커밋에서 배포까지

gabury1

손으로 만든 커밋 하나가 서버에서 도는 프로세스가 되기까지의 길입니다. 사람이 손을 대는 자리는 커밋하고 푸시하는 데까지입니다. 그 뒤로는 자동화가 빌드하고 테스트하고 배포합니다.

상세

시작은 로컬입니다. Git 문서는 커밋을 저장소에 변경을 기록하는 일이라고 적습니다. 커밋 명령은 인덱스의 현재 내용과 변경을 설명하는 메시지를 담은 새 커밋을 만듭니다. 새 커밋은 HEAD 의 직계 자식이 됩니다. 브랜치는 그 새 커밋을 가리키도록 갱신됩니다. 커밋할 내용은 여러 방식으로 지정합니다. 흔한 방식은 git add 로 인덱스에 변경을 미리 쌓아 두는 것입니다. 수정한 파일도 인덱스에 넣어야 합니다.

다음은 푸시입니다. 푸시는 원격 저장소의 참조를 갱신하면서 필요한 객체를 함께 보냅니다. 원격에 아직 없는 데이터만 보냅니다. 가장 단순한 꼴은 git push origin main 입니다. 로컬 main 브랜치를 origin 이라는 원격의 main 브랜치로 보냅니다.

여기까지가 사람의 손입니다. 그 다음부터가 자동화입니다. GitHub Actions 문서는 자신을 빌드·테스트·배포 파이프라인을 자동화하는 CI/CD 플랫폼이라고 적습니다. 워크플로는 저장소에 체크인된 YAML 파일로 정의됩니다. 저장소에서 이벤트가 일어나면 워크플로가 실행됩니다. 누군가 커밋을 푸시하는 것이 그런 이벤트입니다. 일정에 맞춰, REST API 호출로, 또는 수동으로 실행할 수도 있습니다.

파이프라인 안은 다시 나뉩니다. 워크플로 하나는 잡을 하나 이상 담습니다. 잡은 순차로도 병렬로도 돕니다. 잡 하나는 각자의 가상 머신 러너나 컨테이너 안에서 돕니다. 잡 안에는 스텝이 있습니다. 스텝은 직접 정의한 스크립트이거나 재사용 가능한 확장인 액션입니다. 러너는 워크플로를 돌리는 서버입니다. 러너 하나는 한 번에 잡 하나를 돌립니다.

GitLab 은 같은 것을 스테이지로 묶습니다. 파이프라인은 .gitlab-ci.yml 에 YAML 키워드로 설정합니다. 스테이지는 순서대로 돌고, 한 스테이지 안의 잡들은 병렬로 돕니다. 한 스테이지의 잡이 모두 성공하면 파이프라인이 다음 스테이지로 넘어갑니다. 어느 잡이 실패하면 다음 스테이지는 대개 실행되지 않고 파이프라인이 일찍 끝납니다. 작은 파이프라인은 빌드·테스트·배포 세 스테이지로 이뤄지기도 합니다.

끝은 배포입니다. GitHub 문서는 지속적 배포를 자동화로 소프트웨어 업데이트를 게시하고 배포하는 관행이라고 적습니다. 전형적인 과정에서 코드는 배포 전에 자동으로 빌드되고 테스트됩니다. 지속적 배포는 흔히 지속적 통합과 짝을 이룹니다.

경로

세 토막으로 나눠 봅니다. 로컬에서 파이프라인이 시작될 때까지, 파이프라인 안에서 결과물이 레지스트리에 올라갈 때까지, 그리고 배포 대상에서 트래픽을 받을 때까지입니다.

flowchart TD
    A[커밋] --> B[푸시]
    B --> C{이벤트}
    C -->|푸시| D[파이프라인 실행]
    C -->|풀 리퀘스트 병합| D
    C -->|일정| D
    C -->|수동| D

커밋에서 작업 내용이 저장소의 새 커밋이 됩니다. 푸시에서 그 커밋이 원격 저장소의 참조가 됩니다. 파이프라인 트리거에서 그 푸시가 이벤트가 되고, 이벤트가 워크플로 실행을 부릅니다. 병합·일정·수동도 같은 자리로 들어옵니다. 풀 리퀘스트가 병합되면 그 풀 리퀘스트는 자동으로 닫힙니다. 병합을 트리거로 삼으려면 pull_request 의 closed 이벤트에 merged 값이 참인지 보는 조건을 붙입니다.

flowchart TD
    E[빌드] --> F[테스트]
    F -->|성공| G[아티팩트 · 컨테이너 이미지]
    F -->|실패| K[파이프라인 조기 종료]
    G --> H[레지스트리]

빌드에서 소스가 실행 가능한 결과물이 됩니다. Docker Build 는 클라이언트인 Buildx 와 서버인 BuildKit 으로 나뉩니다. 빌드 출력은 클라이언트로 돌아오거나 레지스트리 같은 곳으로 올라갑니다. 테스트에서 그 결과물이 검사를 통과한 결과물이 됩니다. GitLab 의 예시에서 테스트 스테이지의 잡들은 컴파일 잡이 성공했을 때만 돕니다.

아티팩트 만들기에서 잡이 만든 파일이 잡이 끝난 뒤에도 남는 것이 됩니다. 아티팩트는 워크플로 실행 중에 만들어진 파일 또는 파일 묶음입니다. 같은 워크플로의 다른 잡으로 파일을 넘길 때도 씁니다. 빌드한 바이너리나 빌드 로그가 그 자리에 들어갑니다.

컨테이너 이미지 만들기에서 빌드 출력이 이미지 참조를 가진 것이 됩니다. 이미지 참조는 [HOST[:PORT]/]NAMESPACE/REPOSITORY[:TAG] 로 이뤄집니다. HOST 를 빼면 Docker Hub 가 기본입니다. 태그를 빼면 latest 가 기본입니다. docker image tag 가 원본 이미지를 가리키는 새 태그를 만듭니다.

레지스트리에 올리기에서 로컬 이미지가 남이 받아갈 수 있는 것이 됩니다. docker image push 가 이미지를 레지스트리로 올립니다. OCI 배포 명세는 푸시를 블롭과 매니페스트를 레지스트리에 업로드하는 행위로 정의합니다. 다이제스트는 블롭 내용의 암호학적 해시로 만든 고유 식별자입니다. 태그는 매니페스트를 가리키는, 사람이 읽을 수 있는 포인터입니다. 한 매니페스트 다이제스트를 가리키는 태그는 없을 수도, 하나일 수도, 여럿일 수도 있습니다.

flowchart TD
    M[파드 템플릿 변경] --> N[롤아웃]
    N --> O[헬스체크]
    O -->|레디니스 실패| P[트래픽 제외]
    O -->|라이브니스 실패| Q[컨테이너 재시작]
    O -->|모두 준비| R[롤아웃 완료]
    R -.불안정하면.-> S[롤백]

배포에서 레지스트리의 이미지가 도는 프로세스가 됩니다. 쿠버네티스 디플로이먼트의 롤아웃은 파드 템플릿이 바뀔 때만 트리거됩니다. 템플릿의 레이블이나 컨테이너 이미지를 갱신하는 것이 그런 변경입니다. 스케일 조정 같은 다른 변경은 롤아웃을 트리거하지 않습니다. 롤아웃 중에는 새 레플리카셋을 만들어 늘리면서 예전 레플리카셋을 줄입니다.

헬스체크에서 도는 프로세스가 트래픽을 받는 프로세스가 됩니다. 프로브는 kubelet 이 컨테이너에 주기적으로 수행하는 진단입니다. 레디니스 프로브는 컨테이너가 언제 트래픽을 받을 준비가 됐는지 판정합니다. 실패하면 EndpointSlice 컨트롤러가 그 파드의 IP 주소를 해당 서비스들의 EndpointSlice 에서 뺍니다. 라이브니스 프로브는 컨테이너를 언제 재시작할지 판정합니다.

돌아오는 길도 있습니다. 디플로이먼트가 크래시 루프처럼 불안정할 때 롤백합니다. 기본값으로 롤아웃 이력이 시스템에 모두 보관되므로 언제든 롤백할 수 있습니다. 리비전 히스토리 한도를 고치면 보관 범위가 달라집니다.

경계

이 경로는 어디서 시작해 어디서 끝나는가가 사람마다 갈립니다. 두 자리에 선을 긋습니다.

끝을 정하는 자리

배포 명령이 받아들여진 시점이 끝인가. 아닙니다. 쿠버네티스는 디플로이먼트가 새 레플리카셋을 만들거나 늘리거나 줄이는 동안을 진행 중이라고 표시합니다. 완료는 세 조건이 모두 서야 붙습니다. 모든 레플리카가 지정한 최신 버전으로 갱신되어야 합니다. 모든 레플리카가 available 이어야 합니다. 예전 레플리카가 하나도 돌고 있지 않아야 합니다. kubectl rollout status 가 종료 코드 0 을 돌려주면 롤아웃이 완료된 것입니다. 명령을 낸 시점과 이 시점 사이는 경로 안입니다.

트래픽이 넘어간 시점은 또 한 칸 더 갑니다. 파드가 돌고 있어도 레디니스 프로브가 실패 상태를 돌려주면 EndpointSlice 컨트롤러가 그 파드의 IP 주소를 서비스의 EndpointSlice 에서 뺍니다. 준비되지 않은 컨테이너로는 트래픽이 가지 않습니다. 그래서 이 항목은 끝을 헬스체크까지로 잡습니다.

시작을 정하는 자리

커밋만 하고 푸시하지 않은 것도 이 경로 안인가. 아닙니다. 파이프라인은 이벤트가 부릅니다. 로컬 커밋은 저장소에 이벤트를 만들지 않습니다.

시작을 커밋으로 볼지 병합으로 볼지는 트리거를 어떻게 걸었느냐로 갈립니다. on: push 로 걸면 커밋이나 태그를 푸시할 때 워크플로가 돕니다. 기본 브랜치에 병합되지 않은 워크플로도 포함합니다. 병합을 시작으로 잡으려면 pull_request 의 closed 이벤트에 merged 값이 참인지 보는 조건을 겁니다. 두 방식 모두 이 경로의 정상적인 시작입니다.

관련 항목

로컬 단계를 이루는 구성 요소

커밋 · 인덱스 · HEAD · 브랜치 · git add · 푸시 · 원격 저장소 · origin · 참조 · 풀 리퀘스트 · 병합

파이프라인을 이루는 구성 요소

워크플로 · 이벤트 · 잡 · 스텝 · 액션 · 러너 · 파이프라인 · 스테이지 · .gitlab-ci.yml · 지속적 통합 · 지속적 배포 · 리포지터리 디스패치 웹훅

빌드가 남기는 결과물

아티팩트 · 빌드 로그 · 컨테이너 이미지 · 이미지 매니페스트 · 블롭 · 다이제스트 · 태그 · Buildx · BuildKit · upload-artifact · download-artifact

결과물을 올려 두는 저장소

레지스트리 · 리포지터리 · Docker Hub · 푸시 · 풀 · OCI 배포 명세 · 이미지 참조

배포를 이루는 쿠버네티스 리소스

디플로이먼트 · 파드 템플릿 · 롤아웃 · 레플리카셋 · 리비전 히스토리 · 롤백 · kubectl rollout status

트래픽 준비를 판정하는 헬스체크 구성 요소

프로브 · 레디니스 프로브 · 라이브니스 프로브 · 스타트업 프로브 · kubelet · EndpointSlice

이 경로를 실제로 구현하는 제품

Git · GitHub Actions · GitLab · Docker Build · Kubernetes

다른 이름: commit to deploy · 배포 파이프라인