파이프라인이 하는 일 — CI·CD·GitOps
CI/CD(Continuous Integration / Continuous Delivery)는 코드 변경을 자동으로 빌드·테스트하고, 검증을 통과하면 배포까지 이어지게 하는 파이프라인이다. 손으로 빌드하고 서버에 접속해 배포하던 방식에서는 «내 PC에선 됐는데» 같은 사고가 끊이지 않는다. 파이프라인은 그 과정을 코드로 고정해, 누가 언제 돌려도 같은 결과가 나오게 한다.
CI — 머지 전에 깨진 코드를 거른다
CI는 푸시나 PR이 올라올 때마다 의존성 설치 → 린트 → 단위·통합 테스트 → 보안 점검 → 이미지 빌드를 수행한다. 메인 브랜치에는 테스트를 통과한 코드만 들어간다는 보장이 CI의 존재 이유다.
CD — 검증된 결과물을 환경에 반영한다
CD는 CI가 만든 이미지를 스테이징·운영 환경에 반영한다. 방식은 두 갈래다. CI 잡이 kubectl apply로 직접 밀어 넣는 명령형(push) 방식과, Git에 적힌 원하는 상태를 클러스터 안의 컨트롤러가 끌어와 맞추는 선언형(pull) GitOps 방식이다.
| 구분 | 명령형(CI가 push) | GitOps(컨트롤러가 pull) |
|---|---|---|
| 배포 방식 | CI가 kubectl·helm으로 직접 반영 | Argo CD 등이 Git 상태를 끌어와 반영 |
| 클러스터 자격 증명 | CI에 있어야 함 | CI에는 필요 없음 |
| 되돌리기 | 이전 이미지로 다시 배포 | 매니페스트 커밋을 되돌림 |
| 어긋남 감지 | 따로 만들어야 함 | Git과 클러스터를 계속 비교 |
GitOps에서는 «배포 = 매니페스트 PR 머지»가 된다. 누가 무엇을 언제 배포했는지가 Git 이력에 남고, 문제가 생기면 그 커밋을 되돌려 이전 상태로 돌아간다. 어느 쪽이든 핵심은 같다. 사람이 손으로 하던 단계를 하나씩 코드로 옮기는 것이다.
CI 워크플로 — 캐시·매트릭스·최소 권한
Node.js 서비스를 기준으로 한 GitHub Actions CI 단계다. 핵심은 의존성 캐시, 매트릭스 병렬 테스트, 최소 권한 선언, 그리고 같은 브랜치의 이전 실행을 취소하는 concurrency 네 가지다. 액션 판은 2026-10-30 기준 최신 메이저(checkout v7, setup-node v7)로 맞췄다.
name: CI
on:
push:
branches: [main]
pull_request:
# 같은 브랜치에 새 푸시가 오면 진행 중인 실행을 취소
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
# 하나라도 적으면 나머지 권한은 none 이 된다
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: [22.x, 24.x] # 지원 중인 LTS 두 줄을 동시에
steps:
- uses: actions/checkout@v7
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node-version }}
cache: 'npm' # package-lock.json 기준 캐시
- run: npm ci
- run: npm run lint
- run: npm test -- --coverage
- run: npm audit --audit-level=high # 높은 등급 취약점이면 실패cache: 'npm'은 잠금 파일이 바뀌지 않는 한 내려받은 패키지를 다시 쓰게 해 설치 단계를 줄인다. 줄어드는 폭은 의존성 크기와 캐시 적중률에 따라 다르므로, 바꾸기 전후 실행 시간을 같은 저장소에서 재서 확인한다. 매트릭스는 Node.js 22·24처럼 지원 기간이 남은 판을 나란히 돌려 «어느 판에서만 깨지는지»를 한 번에 보여 준다.
GitHub 문서 기준으로 permissions에 항목을 하나라도 적으면 적지 않은 권한은 모두 none이 된다. 워크플로 전체는 contents: read로 두고, OIDC나 패키지 푸시가 필요한 잡에서만 권한을 더한다. 시작 권한(조직·저장소 기본값)이 넓게 설정돼 있어도 이 선언으로 좁혀진다.
OIDC — 저장소에 클라우드 키를 두지 않는다
예전에는 AWS_ACCESS_KEY_ID 같은 장기 자격 증명을 GitHub 시크릿에 넣었다. 이 키가 새면 그대로 침해로 이어진다. GitHub 문서가 정리한 OIDC의 이점은 세 가지다.
- 클라우드 시크릿이 필요 없다 — 클라우드 자격 증명을 장기 GitHub 시크릿으로 복사해 둘 필요가 없다.
- 잡 하나만 쓰는 단기 토큰 — 클라우드가 그 잡에서만 유효한 짧은 토큰을 발급하고, 끝나면 만료된다.
- 세밀한 통제 — 어떤 워크플로가 어떤 역할을 쓸 수 있는지 클라우드 쪽 권한 도구로 정한다.
클라우드는 토큰의 sub 클레임(예: repo:my-org/app:environment:production)이 역할의 신뢰 조건과 맞는지 확인한다. 신뢰 조건을 저장소 전체(repo:my-org/*)처럼 넓게 열면 다른 저장소의 워크플로도 그 역할을 받을 수 있으므로, 저장소와 환경(또는 브랜치)까지 좁혀 적는다.
deploy-auth:
needs: test
runs-on: ubuntu-latest
environment: production # sub 클레임에 환경 이름이 들어감
permissions:
contents: read
id-token: write # OIDC 토큰 요청
steps:
- uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::123456789012:role/github-deploy
aws-region: ap-northeast-2
- run: aws sts get-caller-identity # 받은 역할 확인이미지 빌드와 출처 증명
공급망 쪽 보강의 핵심은 «이 이미지가 어느 저장소의 어느 워크플로에서 만들어졌는가»를 검증할 수 있게 하는 것이다. GitHub 문서는 컨테이너 이미지에 빌드 출처 증명(artifact attestation)을 붙이는 예로 actions/attest@v4를 쓰고, 필요한 권한을 id-token: write·contents: read·attestations: write·packages: write로 적는다.
build-and-attest:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
permissions:
contents: read
id-token: write
attestations: write
packages: write
steps:
- uses: actions/checkout@v7
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
id: push # 아래에서 digest 를 읽기 위한 id
uses: docker/build-push-action@v7
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Attest build provenance
uses: actions/attest@v4
with:
subject-name: ghcr.io/${{ github.repository }} # 태그 없이
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true문서 설명대로 subject-name은 태그 없는 전체 이미지 이름, subject-digest는 sha256:… 형식의 다이제스트이고, docker/build-push-action 단계에 id를 줘야 그 다이제스트를 읽을 수 있다. 예전 예제에서 흔히 보이던 실수가 이 id를 빠뜨려 다이제스트가 빈 값이 되는 것이다. SBOM(이미지에 든 패키지 목록)도 같은 액션의 sbom-path 입력으로 서명된 증명으로 붙일 수 있다.
배포하는 쪽에서는 GitHub CLI로 검증한다.
gh attestation verify oci://ghcr.io/my-org/app:TAG -R my-org/app
GitOps 배포와 카나리
GitOps 구조에서 CI 잡은 클러스터를 직접 건드리지 않는다. 대신 매니페스트 저장소의 이미지 태그만 바꾸고, 클러스터 안의 Argo CD가 그 변경을 끌어와 반영한다. 아래는 배포용 저장소의 이미지 태그를 새 커밋 SHA로 바꿔 커밋하는 예다.
bump-manifest:
needs: build-and-attest
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout deploy repo
uses: actions/checkout@v7
with:
repository: my-org/deploy-manifests
token: ${{ secrets.DEPLOY_REPO_TOKEN }} # 배포 저장소 쓰기만 되는 토큰
- name: Update image tag
run: |
cd overlays/production
kustomize edit set image \
app=ghcr.io/my-org/app:${{ github.sha }}
git config user.name "deploy-bot"
git config user.email "deploy-bot@users.noreply.github.com"
git commit -am "deploy: app ${{ github.sha }}"
git push여기서 쓰는 토큰은 배포 저장소 하나에만 쓰기 권한이 있게 좁힌다. 이 커밋이 올라가면 Argo CD가 차이를 감지하고, 정한 배포 전략에 따라 운영 클러스터에 새 판을 반영한다. Argo CD 설치와 Application 구성은 GitOps Argo CD 자동 배포 가이드에 따로 정리했다.
카나리 — 일부에게 먼저
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: app
spec:
strategy:
canary:
analysis: # 단계가 진행되는 동안 뒤에서 계속 판정
templates:
- templateName: success-rate
startingStep: 1
steps:
- setWeight: 10
- pause: { duration: 5m }
- setWeight: 50
- pause: { duration: 5m }
- setWeight: 100Argo Rollouts 문서 기준으로 카나리는 setWeight·pause 단계를 차례로 밟고, 분석(analysis)이 실패하면 롤아웃이 중단(abort)되어 트래픽이 안정판으로 돌아간다. 트래픽 라우팅(인그레스·서비스 메시) 없이 쓰면 가중치는 레플리카 수로 어림한다. 레플리카 10개에 10%면 카나리 1개·안정판 9개가 되는 식이라, 정밀한 비율이 필요하면 트래픽 관리 연동을 붙인다. 분석 템플릿에 넣을 성공률 지표는 실시간 성능 모니터링과 자동 스케일링의 오류율·지연 SLI 식을 그대로 쓸 수 있다.
재사용 워크플로와 파이프라인 지표
재사용 워크플로 — 공통 규칙을 한 곳에
저장소가 많아지면 보안 점검·출처 증명·배포 게이트 같은 공통 단계를 재사용 워크플로로 한 곳에 두고 각 저장소가 uses:로 부른다. GitHub 문서 기준 제약은 두 가지다. 호출 사슬은 최대 10단계(맨 위 호출 워크플로 포함)까지 이을 수 있고 고리 모양 호출은 안 된다. 그리고 사슬을 따라 내려가며 권한은 유지하거나 줄일 수만 있고 늘릴 수 없다. 그래서 재사용 워크플로 안에서 필요한 권한(예: id-token: write)은 부르는 쪽에서 먼저 줘야 한다.
jobs:
release:
uses: my-org/platform-workflows/.github/workflows/build-attest.yml@v3
permissions:
contents: read
id-token: write
attestations: write
packages: write
secrets: inherit공통 워크플로는 태그(@v3)나 커밋 SHA로 고정해 부른다. 브랜치 이름으로 부르면 공통 쪽 변경이 모든 저장소에 예고 없이 퍼진다. 고급 트리거·환경 보호 규칙은 GitHub Actions CI/CD 고급 워크플로우에서 더 다뤘다.
파이프라인이 잘 도는지 재는 법 — DORA 지표
파이프라인이 팀에 도움이 되는지는 DORA의 소프트웨어 전달 지표로 본다. DORA는 지금 지표를 다섯 개로 정리하고 처리량과 불안정성 두 묶음으로 나눈다.
| 묶음 | 지표 | 파이프라인에서 재는 곳 |
|---|---|---|
| 처리량 | 변경 리드 타임 | 커밋 시각 → 운영 반영 시각 |
| 처리량 | 배포 빈도 | 운영 반영 횟수(매니페스트 머지 수) |
| 처리량 | 실패한 배포 복구 시간 | 실패 감지 → 복구 배포 |
| 불안정성 | 변경 실패율 | 되돌리거나 고친 배포 비율 |
| 불안정성 | 배포 재작업률 | 계획에 없던 수정 배포 비율 |
따로 감시 프로그램을 돌릴 필요는 없다. 워크플로 실행 기록(시작·끝 시각, 결론)과 배포 저장소의 커밋 이력만으로 위 다섯 값을 셀 수 있다. 자주 걸리는 문제는 세 가지다. 잠금 파일이 자주 바뀌어 캐시가 안 맞는 것, OIDC 역할의 신뢰 조건이 실제 sub 값과 달라 인증이 실패하는 것, 카나리 분석 기준을 너무 빡빡하게 잡아 멀쩡한 배포가 중단되는 것이다.
정리 — 넓혀 가는 순서
처음부터 전부 갖출 필요는 없다. 사람이 손으로 하던 일을 하나씩 코드로 옮기면 된다.
| 순서 | 할 일 | 끝났다는 기준 |
|---|---|---|
| 1 | 푸시마다 린트·테스트(CI 게이트) | 메인 브랜치에 테스트 안 거친 커밋 0 |
| 2 | 최소 권한·concurrency·캐시 | 워크플로 맨 위에 permissions가 있음 |
| 3 | OIDC로 클라우드 인증 전환 | 저장소 시크릿에 장기 클라우드 키 0 |
| 4 | 이미지 빌드 + 출처 증명 | gh attestation verify 통과 |
| 5 | GitOps 배포 + 카나리 | 배포 = 매니페스트 머지 · 분석 실패 시 자동 중단 |
Docker 이미지 빌드부터 단계별로 따라 하려면 Docker + GitHub Actions CI/CD 가이드를 함께 본다. 가장 좋은 배포는 아무도 긴장하지 않는 배포다.
자주 묻는 것
GitHub Actions에서 OIDC를 쓰려면 무엇이 필요한가요?
OIDC 토큰을 요청하는 잡에 id-token: write 권한을 주고, 클라우드 쪽 역할의 신뢰 조건에 토큰의 sub 클레임(저장소·환경·브랜치)을 맞춰 적습니다. 그러면 저장소 시크릿에 장기 클라우드 키를 두지 않고 그 잡에서만 유효한 단기 토큰으로 인증합니다.
permissions를 적으면 나머지 권한은 어떻게 되나요?
GitHub 문서 기준으로 permissions에 항목을 하나라도 적으면 적지 않은 권한은 모두 none이 됩니다. 워크플로 전체는 contents: read로 두고, 필요한 잡에서만 id-token이나 packages 권한을 더하는 방식이 안전합니다.
빌드 출처 증명(attestation)은 어떻게 붙이고 확인하나요?
actions/attest 액션에 태그 없는 이미지 이름과 sha256 다이제스트를 넘기고 push-to-registry를 켭니다. 다이제스트는 docker/build-push-action 단계에 id를 줘서 읽습니다. 확인은 gh attestation verify 명령으로 합니다.
재사용 워크플로는 몇 단계까지 중첩할 수 있나요?
GitHub 문서 기준으로 맨 위 호출 워크플로를 포함해 최대 10단계까지 이을 수 있고, 고리 모양 호출은 안 됩니다. 권한은 사슬을 따라 유지하거나 줄일 수만 있으므로 필요한 권한은 부르는 쪽에서 먼저 줍니다.
참고 자료
- GitHub Docs. OpenID Connect — 클라우드 시크릿 불필요·잡 단위 단기 토큰·sub 클레임 신뢰 조건(2026-10-30 확인). docs.github.com
- GitHub Docs. Using artifact attestations — actions/attest@v4, 권한 4가지, subject-name·subject-digest·push-to-registry, sbom-path, gh attestation verify(2026-10-30 확인). docs.github.com
- GitHub Docs. Reuse workflows — 최대 10단계·고리 금지·권한은 유지 또는 축소만(2026-10-30 확인). docs.github.com
- GitHub Docs. Workflow syntax — permissions(적지 않은 권한은 none)·concurrency cancel-in-progress(2026-10-30 확인). docs.github.com
- GitHub 릴리스 — actions/checkout v7.0.1·actions/setup-node v7.1.0·docker/build-push-action v7.4.0·docker/login-action v4.7.0·aws-actions/configure-aws-credentials v6.3.0(2026-10-30 확인). github.com
- Argo Rollouts. Canary — setWeight·pause·analysis, 분석 실패 시 중단, 트래픽 라우팅 없을 때 레플리카 수로 어림(2026-10-30 확인). argo-rollouts.readthedocs.io
- DORA. DORA's software delivery performance metrics — 지표 5개·처리량/불안정성(2026-10-30 확인). dora.dev