CI/CD의 기본 개념부터 브랜치 전략, GitHub Actions·Jenkins 파이프라인, 빌드 캐싱, OIDC 기반 시크릿 관리, 공급망 보안(SBOM·이미지 서명), Blue-Green·Canary 점진적 배포, GitOps(ArgoCD)까지 — AI Agent 서비스를 안전하고 빠르게 릴리스하는 전체 흐름을 정리합니다.
CI/CD의 기본 개념부터 브랜치 전략, GitHub Actions·Jenkins 파이프라인, 빌드 캐싱, OIDC 기반 시크릿 관리, 공급망 보안(SBOM·이미지 서명), Blue-Green·Canary 점진적 배포, GitOps(ArgoCD)까지 — AI Agent 서비스를 안전하고 빠르게 릴리스하는 전체 흐름을 정리합니다. 이 가이드는 개념을 나열하기보다, 실제 프로젝트에서 판단해야 하는 순서대로 내용을 따라갈 수 있게 구성했습니다.
핵심 관점
인프라 / 운영
설치 명령을 외우기보다 트래픽, 런타임, 관측, 장애 대응이 어떤 순서로 이어지는지 파악합니다.
CI/CD 파이프라인 설계빌드 속도 최적화 & 캐싱OIDC 시크릿리스 인증 & 공급망 보안점진적 배포 & GitOps
구조 다이어그램
글로 읽은 내용을 머릿속에 오래 남기려면 먼저 흐름을 그림으로 잡는 편이 좋습니다. 아래 두 그림은 CI/CD를 학습할 때 계속 되돌아볼 수 있는 기준 지도입니다.
학습 흐름
다이어그램 렌더링 중…
아키텍처 관점
다이어그램 렌더링 중…
CI/CD란 무엇인가
CI/CD를 처음 펼칠 때는 세부 명령보다 큰 그림이 먼저입니다. 이 섹션에서는 앞으로 배울 개념들이 어떤 문제를 풀기 위해 등장했는지부터 잡아봅니다.
CI(Continuous Integration)는 여러 개발자가 작업한 코드를 자주 병합하고, 병합할 때마다 자동으로 빌드·테스트해 통합 문제를 조기에 발견하는 관행입니다. CD는 그 뒤를 잇는 두 단계로 나뉘는데, "언제든 배포할 수 있는 상태로 아티팩트를 준비"하는 Continuous Delivery와, 사람의 승인 없이 "실제로 운영까지 자동 배포"하는 Continuous Deployment는 이름은 비슷해도 자동화 범위가 다릅니다.
다이어그램 렌더링 중…
용어
자동화 범위
사람의 개입
CI (Continuous Integration)
커밋마다 빌드 + 자동 테스트
병합 여부는 리뷰어가 결정
Continuous Delivery
CI + 배포 아티팩트를 항상 준비된 상태로 유지
운영 배포 버튼은 사람이 누름
Continuous Deployment
Delivery + 테스트 통과 시 운영까지 자동 배포
개입 없음 (실패 시에만 알림)
파이프라인 설계
여기서는 파이프라인 설계을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
AI Agent 서비스는 일반 웹 서비스보다 모델 응답 품질, RAG 검색 품질, 토큰 비용, 응답 지연이 함께 변합니다. 그래서 CI/CD는 단순 배포 자동화가 아니라 코드 품질, 모델 호출 안정성, 프롬프트 회귀, 성능 스모크 테스트를 함께 확인하는 운영 루프가 되어야 합니다.
다이어그램 렌더링 중…
단계
목표
대표 검증
Install
의존성 고정과 재현성 확보
npm ci, uv sync, lockfile 확인
Build
프론트/백엔드 빌드 실패 조기 감지
npm run build, Docker build
Test
API와 Agent 핵심 흐름 검증
unit test, API contract, RAG sample eval
Security
시크릿 누출과 취약 이미지 차단
secret scan, npm audit, image scan
Deploy
스테이징 확인 후 운영 반영
smoke test, health check, rollback point
브랜치 전략 & 트리거
여기서는 브랜치 전략 & 트리거을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
브랜치 전략은 곧 "언제 CI가 돌고 언제 배포가 일어나는가"를 결정합니다. 오래 살아남는 브랜치가 많을수록 병합 시점에 충돌과 통합 문제가 커지기 때문에, 대부분의 팀은 기능 브랜치를 하루이틀 안에 짧게 살고 죽게 만드는 Trunk-Based Development로 수렴하고 있습니다.
다이어그램 렌더링 중…
전략
특징
적합한 경우
Trunk-Based
기능 브랜치가 하루이틀 안에 main으로 병합, feature flag로 미완성 기능 숨김
빠른 배포 주기, CI/CD가 성숙한 대부분의 팀 (권장)
GitHub Flow
main + 기능 브랜치, PR 머지 즉시 배포
단순한 웹 서비스, 소규모 팀
GitFlow
main/develop/release/hotfix 등 여러 장기 브랜치
정해진 릴리스 주기가 있는 패키지 소프트웨어 (요즘은 과함)
GitHub Actions 기본 워크플로
여기서는 GitHub Actions 기본 워크플로을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
PR에서는 빌드와 테스트만 수행하고, main 브랜치에 병합될 때 Docker 이미지를 만들고 배포합니다. AI Agent 프로젝트는 모델 API 키, DB URL, 벡터 DB 접속 정보가 필요하므로 GitHub Secrets에 분리해 저장합니다.
여기서는 Jenkins Pipeline 방식을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
Jenkins는 사내망, 전용 빌드 서버, 폐쇄망 배포, 승인 단계가 필요한 조직에서 여전히 강력합니다. Jenkinsfile을 저장소에 함께 두면 빌드 절차를 코드로 관리할 수 있고, Credentials에 모델 API 키와 레지스트리 토큰을 안전하게 보관할 수 있습니다.
JenkinsfileGROOVY
pipeline { agent any environment { NODE_VERSION = '22' IMAGE_NAME = 'registry.example.com/aidevops-agent' IMAGE_TAG = "${env.GIT_COMMIT}" } options { timestamps() disableConcurrentBuilds() buildDiscarder(logRotator(numToKeepStr: '20')) } stages { stage('Checkout') { steps { checkout scm } } stage('Install') { steps { sh 'node --version' sh 'npm ci' } } stage('Build') { steps { sh 'npm run build' } } stage('Test') { steps { sh 'npm test -- --runInBand || true' sh 'bash scripts/ci-smoke.sh' } } stage('Docker Build') { steps { sh 'docker build -t $IMAGE_NAME:$IMAGE_TAG -t $IMAGE_NAME:latest .' } } stage('Docker Push') { when { branch 'main' } steps { withCredentials([usernamePassword( credentialsId: 'container-registry', usernameVariable: 'REGISTRY_USER', passwordVariable: 'REGISTRY_PASSWORD' )]) { sh 'echo $REGISTRY_PASSWORD | docker login registry.example.com -u $REGISTRY_USER --password-stdin' sh 'docker push $IMAGE_NAME:$IMAGE_TAG' sh 'docker push $IMAGE_NAME:latest' } } } stage('Deploy Staging') { when { branch 'main' } steps { sh 'ssh deploy@staging "cd /opt/aidevops-agent && docker compose pull && docker compose up -d"' sh 'BASE_URL=https://staging.example.com bash scripts/ci-smoke.sh' } } stage('Approve Production') { when { branch 'main' } steps { input message: 'Deploy to production?', ok: 'Deploy' } } stage('Deploy Production') { when { branch 'main' } steps { sh 'ssh deploy@prod "cd /opt/aidevops-agent && docker compose pull && docker compose up -d"' sh 'BASE_URL=https://www.aidevops.kr bash scripts/ci-smoke.sh' } } } post { failure { echo 'Pipeline failed. Check build logs and rollback if production deploy started.' } always { sh 'docker image prune -f || true' } }}
구성 요소
권장 방식
주의점
Agent
Docker 사용 가능 Jenkins node
Docker socket 권한과 빌드 격리 확인
Credentials
Jenkins Credentials Binding
API 키를 로그에 출력하지 않기
Stages
Install, Build, Test, Image, Deploy
stage별 실패 지점을 명확히 분리
Approval
input step으로 운영 배포 승인
스테이징 smoke test 이후 승인
Rollback
이전 이미지 태그 또는 kubectl rollout undo
배포 전 현재 태그 기록
캐싱 & 빌드 속도 최적화
여기서는 캐싱 & 빌드 속도 최적화을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
CI 파이프라인이 느려지면 개발자는 리뷰를 기다리다 다른 일을 하게 되고, 그만큼 컨텍스트 전환 비용이 커집니다. 의존성 설치와 Docker 레이어를 캐싱하고 테스트를 병렬로 나눠 돌리는 것만으로도 5~10분 걸리던 파이프라인을 1~2분대로 줄일 수 있는 경우가 많습니다.
다이어그램 렌더링 중…
.github/workflows/ci.yml (캐싱 + 매트릭스 병렬 테스트)YAML
jobs: test: strategy: matrix: shard: [1, 2, 3, 4] # 테스트를 4개로 쪼개 동시에 실행 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 cache: npm # package-lock.json 해시 기준 자동 캐싱 - run: npm ci - run: npm test -- --shard=${{ matrix.shard }}/4
캐시 범위
문제
해결
너무 넓은 키 (예: 브랜치명만)
의존성이 바뀌어도 오래된 캐시를 계속 사용 — 미묘한 버그 유발
캐시 키에 lockfile 해시를 반드시 포함
너무 좁은 키 (예: 커밋 SHA)
커밋마다 캐시가 달라져 사실상 캐시 재사용이 안 됨
lockfile·OS·언어 버전처럼 실제로 캐시 유효성에 영향을 주는 값만 키에 포함
Docker 이미지 빌드와 배포
여기서는 Docker 이미지 빌드와 배포을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
운영 배포는 소스 코드를 서버에서 직접 빌드하기보다, CI에서 검증된 Docker 이미지를 레지스트리에 올리고 서버는 해당 태그를 pull하도록 구성하는 편이 안정적입니다. 태그는 커밋 SHA를 사용하면 어떤 코드가 배포됐는지 추적하기 쉽습니다.
여기서는 시크릿 관리 & OIDC 연동을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
AWS_ACCESS_KEY_ID 같은 장기 액세스 키를 GitHub Secrets에 넣어두면, 그 키가 유출됐을 때 만료시키기 전까지 계속 악용될 수 있습니다. OIDC(OpenID Connect) 연동을 쓰면 CI 작업이 실행되는 그 순간에만 유효한 단기 자격 증명을 클라우드 provider로부터 직접 발급받기 때문에, 애초에 GitHub Secrets에 저장해둘 클라우드 키 자체가 없어집니다.
다이어그램 렌더링 중…
.github/workflows/deploy.ymlYAML
permissions: id-token: write # OIDC 토큰 발급에 필요 contents: readjobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/github-actions-deployer aws-region: ap-northeast-2 # AWS_ACCESS_KEY_ID/SECRET 없이 여기서 임시 자격 증명이 발급됨 - run: aws ecs update-service --cluster prod --service api --force-new-deployment
방식
유출 시 위험
만료
장기 액세스 키
만료 전까지 무제한 악용 가능
수동으로 로테이션하지 않으면 영구
OIDC 단기 토큰
이미 만료된 뒤라 재사용 불가능한 경우가 대부분
보통 15분~1시간
AI Agent 품질 게이트
여기서는 AI Agent 품질 게이트을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
AI 서비스는 빌드가 성공해도 답변 품질이 나빠질 수 있습니다. 최소한 고정 질문 세트, RAG 검색 샘플, 응답 시간 기준, 비용 기준을 CI에서 확인하세요.
여기서는 공급망 보안 — SBOM & 이미지 서명을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
이미지 자체에 취약점이 없어도, 빌드 과정에서 의존성이 몰래 변조되거나 검증되지 않은 이미지가 그대로 배포되는 공급망 공격은 막을 수 없습니다. SBOM(Software Bill of Materials)으로 이미지에 어떤 패키지가 정확히 어떤 버전으로 들어있는지 기록해두고, 이미지에 서명을 남겨 배포 직전에 "이 이미지가 정말 우리 CI가 만든 것인지"를 검증하는 것이 공급망 보안의 핵심입니다.
# 배포 서버/CD 파이프라인에서 배포 직전에 서명 검증cosign verify \ --certificate-identity-regexp "https://github.com/myorg/myrepo/.*" \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ ghcr.io/myorg/myapp@sha256:abc123...# 검증 실패 시 exit code가 0이 아니므로 CD 스크립트에서 바로 배포를 중단할 수 있음
도구/개념
역할
SBOM
이미지 안의 모든 패키지·버전 목록 — 새 CVE가 발표됐을 때 영향받는 이미지를 즉시 검색 가능
cosign sign (keyless)
GitHub OIDC 신원으로 이미지에 서명 — 개인 키를 별도로 관리할 필요 없음
cosign verify
서명이 신뢰할 수 있는 CI 파이프라인에서 만들어졌는지 배포 전에 확인
점진적 배포 전략 (Blue-Green·Canary)
여기서는 점진적 배포 전략 (Blue-Green·Canary)을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
새 버전을 한 번에 100% 트래픽으로 전환하면, 문제가 있을 때 모든 사용자가 동시에 영향을 받습니다. Blue-Green과 Canary는 모두 "새 버전을 일부에게만 먼저 노출하고 문제가 없으면 점진적으로 넓힌다"는 같은 목표를 다른 방식으로 구현합니다.
다이어그램 렌더링 중…
전략
전환 방식
장점
단점
Blue-Green
검증된 신규 환경으로 트래픽을 한 번에 전환
롤백이 즉시 가능 (트래픽을 원래대로 되돌리기만 하면 됨)
두 배의 인프라 리소스가 순간적으로 필요
Canary
일부 트래픽만 신규 버전으로 보내고 점진적으로 비중 확대
실제 트래픽으로 소규모 검증, 문제 시 영향 범위가 작음
두 버전이 공존하는 동안 호환성(DB 스키마 등) 관리 필요
Rolling (참고)
docker/kubernetes 가이드에서 다룬 방식 — 인스턴스를 하나씩 순차 교체
추가 리소스 없이 적용 가능
전환 중 신규/구버전이 동시에 트래픽을 받음
배포와 롤백
여기서는 배포와 롤백을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
운영 배포는 무조건 빠른 것보다 되돌릴 수 있는 것이 중요합니다. 배포 전 현재 이미지 태그를 기록하고, 새 이미지 health check가 실패하면 즉시 이전 태그로 되돌립니다.
scripts/deploy.shBASH
#!/usr/bin/env bashset -euo pipefailAPP_DIR="/opt/aidevops-agent"IMAGE="ghcr.io/org/aidevops-agent:${GITHUB_SHA:-latest}"ssh "$DEPLOY_USER@$DEPLOY_HOST" <<EOF set -euo pipefail cd "$APP_DIR" docker compose ps docker compose pull docker compose up -d sleep 10 curl -fsS http://localhost:3000/healthEOF# Kubernetes를 사용한다면:# kubectl set image deployment/aidevops-agent app=$IMAGE# kubectl rollout status deployment/aidevops-agent# kubectl rollout undo deployment/aidevops-agent
IaC 파이프라인 — Terraform & Ansible
여기서는 IaC 파이프라인 — Terraform & Ansible을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
CI/CD가 애플리케이션 코드를 빌드·배포한다면, Terraform과 Ansible은 그 애플리케이션이 올라갈 인프라 자체를 코드로 관리합니다. Terraform은 "인프라가 어떤 상태여야 하는지"를 선언해 VPC·EKS·RDS 같은 리소스를 프로비저닝하는 도구이고, Ansible은 이미 존재하는 서버에 패키지 설치·설정 파일 배포 같은 구성을 SSH로 맞추는 도구입니다 — 둘 다 별도의 파이프라인 스테이지로 CI/CD에 자연스럽게 편입됩니다.
여기서는 GitOps (ArgoCD)을 실제 코드와 함께 확인합니다. 예제를 그대로 따라 하기보다, 입력과 출력, 그리고 바뀌기 쉬운 부분이 어디인지 보면서 읽어보세요.
지금까지 다룬 방식은 모두 CI 파이프라인이 클러스터에 직접 접근해 kubectl apply나 docker compose up을 실행하는 "Push 기반" 배포입니다. GitOps는 반대로, Git 저장소의 매니페스트를 "이 클러스터가 도달해야 할 상태"로 선언해두면 클러스터 안에서 실행되는 에이전트(ArgoCD, Flux)가 그 상태와 실제 클러스터를 지속적으로 비교해 스스로 맞춰가는 "Pull 기반" 방식입니다.
다이어그램 렌더링 중…
argocd-application.yamlYAML
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: myapp namespace: argocdspec: project: default source: repoURL: https://github.com/myorg/myapp-manifests.git targetRevision: main path: overlays/production destination: server: https://kubernetes.default.svc namespace: production syncPolicy: automated: prune: true # Git에서 삭제된 리소스는 클러스터에서도 자동 삭제 selfHeal: true # 누군가 kubectl로 수동 변경해도 Git 상태로 자동 복원