DevOps: Canary Deployment

Github Actions CI/CD 파이프라인 구축하기

by 공음

이론적 배경


1. 왜 'DevOps'인가: 전통적인 IT 환경의 한계

22년 11월 30일, chatGPT가 출시된지 3년이 조금 넘었습니다.

3년동안 생성형 AI는 우리의 생활을 송두리째 바꿔버렸고, IT 종사자들은 트렌드를 분석하며 시류에 편승하려고 노력하는 하루하루를 보내고 있습니다.


프롬프트 엔지니어링이 도래한 이유로 개발자들의 생산성은 극대화되어 사용자 피드백을 즉각적으로 디버깅해 냅니다. 하지만 여기엔 치명적인 한계가 있습니다. 인프라 배포가 개발 속도를 따라가지 못하는 것이죠.


그래서 기존에는 아무리 빨리 개발을 해내어도 매달 말에 모아서 배포를 진행했기 때문에 사용자 경험(UX)의 퀄리티가 떨어질 수 밖에 없습니다.

이러한 한계를 극복하고 고객에게 새로운 가치를 신속하게 전달하기 위해 등장한 것이 바로 DevOps입니다.


2. CI/CD 파이프라인: 자동화의 구체적 실현

DevOps의 철학을 기술적으로 구현한 핵심 실천 도구가 바로 CI/CD 파이프라인입니다.

Continuous Integration(CI, 지속적 통합): 개발자들이 수정한 코드를 중앙 저장소에 빈번하게 병합하고, 자동으로 빌드 및 테스트를 실행하여 '통합 지옥'을 예방합니다.

Continuous Deployment(CD, 지속적 제공/배포): 검증된 코드가 운영 환경까지 100% 자동화되어 릴리스되는 궁극의 단계입니다.

실전에서는 GitHub ActionsJenkins 같은 플랫폼을 활용하여 최종 배포까지의 흐름을 관리합니다.

그리고 보안적인 배포가 되려면 기능의 Unit Test하는 CI 단계에서 SBOM을 고려한 설계를 진행한 후, CD를 연계하는 것이 최적이라고 할 수 있습니다.


3. 현대적 인프라의 완성: GitOps와 무중단 배포

최근에는 쿠버네티스(K8s) 환경에 최적화된 GitOps 패러다임이 주목받고 있습니다.

GitOps: Git 저장소(Repo)를 인프라 설정의 '단일 진실 공급원'으로 삼아, Argo CD와 같은 에이전트가 클러스터 상태를 자동으로 동기화하고 복구(Self-healing)합니다.

안전한 배포 전략: 사용자 체감 중단 시간을 최소화하기 위해 롤링(Rolling), 카나리(Canary), 블루/그린(Blue/Green)과 같은 고도의 무중단 배포 기법을 적용합니다.


4. Canary Deployment Strategy

배포를 하는 방식에는 다양한 방법이 존재합니다.

그중 Canary 방식은 구 버전의 서버와 새 버전의 서버들을 구성하고 일부 트래픽을 새 버전으로 분산하여 오류 여부를 판단합니다. 만약 오류가 없다면 점차적으로 새 버전의 서버들로 전환하여 마무리합니다.

Blue-Green으로 구 버전과 새 버전을 관리합니다.


Gemini_Generated_Image_z2thyzz2thyzz2th.png




디렉토리 구조


.github/workflows/deploy.yaml: GitHub Actions를 이용한 자동 배포 파이프라인 설정 파일입니다. 코드 푸시 시 빌드 및 배포 트리거를 정의합니다.


nginx/: 웹 서버 및 리버스 프록시 역할을 하는 Nginx 관련 설정

default.conf: Nginx의 가상 호스트 및 라우팅 규칙(카나리 배포를 위한 트래픽 분산 등) 파일

Dockerfile: 커스텀 설정이 반영된 Nginx 컨테이너를 생성하기 위한 빌드 파일


deploy.sh: 로컬 환경이나 서버에서 배포 과정을 자동화하기 위해 작성된 쉘 스크립트

Dockerfile: 메인 애플리케이션(Node.js)을 컨테이너화하기 위한 빌드 파일

README.md: 프로젝트의 개요, 설치 방법, 실행 방법 등을 기록한 문서 파일

server.js: 실제 서비스 로직이 포함됩니다.


배포 흐름을 이미지화 하면 다음과 같습니다.

스크린샷 2026-03-31 오후 4.43.35.png




Step 1: Github Actions Runners Setting (Self-Hosted)


1. 작업할 디렉토리에서 Terminal을 켠다.

2. 도커 컨테이너 Run & Exec

docker run -d --name local-ubuntu-runner -v /var/run/docker.sock:/var/run/docker.sock ubuntu sleep infinity

docker exec -it local-ubuntu-runner bash


*참고*

Daemon: run -d 옵션 / sleep infinity

Volume Mount: 컨테이너 내부에서 호스트 머신의 도커 엔진에 직접 접근하고 이를 제어하기 위함

docker.sock: 도커 내부 네트워크에서 각 컨테이너를 호출하기 위해 사용할 소켓


3. Install Requisitements Tools

apt update

apt install -y curl tar git docker.io sudo libicu-dev

chmod 666 /var/run/docker.sock


4. User changing

useradd -m runner

su - runner

bash


5. Github의 'Settings > Runners > Linux > ARM64(Mac OS)' 실행

스크린샷 2026-03-31 오후 3.27.21.png


Step 2: Type Code


server.js

서버 Request에 따라 현재 서버 버전을 Response하는 간단한 서버 코드

스크린샷 2026-03-31 오후 4.00.25.png

Dockerfile (for server.js)

스크린샷 2026-03-31 오후 5.11.18.png

default.conf

80 포트로 서버 Listen을 하며,

Container 내부에서 'backend'에게 리버스 프록시하는 nginx.conf 값

스크린샷 2026-03-31 오후 5.08.33.png

Dockerfile (for nginx)

'backend'는 app-blue:8080 으로 지칭

스크린샷 2026-03-31 오후 5.12.28.png



Step 3: Docker Network 구축


1. 도커 네트워크 생성

docker network create canary-net


2. server.js로 실행될 이미지 및 컨테이너 생성

docker build -t my-canary-app .

docker run -d --name app-blue --network canary-net -e PORT=8080 -e COLOR="BLUE" my-canary-app


3. nginx로 실행될 이미지 및 컨테이너 생성

docker build -t my-nginx .

docker run -d --name nginx-proxy --network canary-net -p 80:80 my-nginx



Step 4: deploy.yaml


Github Repo에서 main 브랜치로 Push 이벤트를 탐지하면,

self-hosted Runner에서 deploy.sh를 명령하도록 하는 Pipeline입니다.

스크린샷 2026-03-31 오후 5.13.37.png


Step 5: deploy.sh


기존 버전인 Blue를 띄워두고 Repo에 특정 브랜치(main)에 push 이벤트가 감지되면 Runner Container는 일정 비율을 Green으로 전환합니다.

스크린샷 2026-03-31 오후 5.09.55.png
스크린샷 2026-03-31 오후 5.10.32.png


최종 컨테이너 목록


저는 Docker Desktop을 활용해 Container들을 확인했습니다.

스크린샷 2026-03-31 오후 4.59.00.png




토큰


설계한 내용이 잘 작동하는지 알아보겠습니다.

이제 Docker에 올린 Runner Container가 Git Push를 감지하기 위해 Repository에 접근해야합니다.

이때 private Repo에 접근하기 위해 엑세스 토큰과 감지할 Repo 주소를 활용하여 연동할 것입니다.

활용하는 방법은 다음 단계인 '테스트' 단계에서 보여드리겠습니다.


경로: Github 계정 > Developer Settings > Personal Access Token > Token(Classic)

이때 scope에서 repo와 workflow에 대한 권한을 부여하고 생성합니다.

스크린샷 2026-03-31 오후 6.53.19.png



테스트


1. git 커밋

git init

git add .

git commit -m "commit message"


옵션. branch name 변경

(만약 git status 했는데 master라면)

git branch -M main


2. Token 연결 및 git push

git remote add origin https://{my_token}@{my_repo_git}

git push -u origin main


*수정*

(만약 remote 주소 설정을 잘못했다면 remote set-url 활용)

git remote set-url origin https://{my_token}@{my_repo_git}


만약 테스트 에러가 발생한다면

My_Repo > Actions에서 CI 오류 로그를 확인할 수 있습니다.

스크린샷 2026-03-31 오후 9.05.57.png




결론


Claude Code와 같은 도구로 여러 AI Agent를 이용해 수많은 서비스가 쏟아지고 있습니다.

하지만 아무리 잘 기획한 서비스라고 해도 버전 업그레이드를 피할 수는 없습니다.


유저 피드백, 버그, 보안 트러블 등 다양한 문제로 새로운 버전을 출시하더라도

인프라 측면에서 배포 환경을 안전하고 빠르게 구축할 수 없다면 도태될 수 밖에 없는 세상이 도래했습니다.


CI/CD 파이프라인 도구는 빠르게 변모하는 시장에서 내 서비스를 환경으로 인도할 것입니다.

작가의 이전글나만의 파도를 잡다