BACKEND

자동화된 테스트·배포 파이프라인 — Actions·OIDC·GitOps

junetapa 2026. 10. 30 업데이트 15 min read

코드를 푸시하면 테스트가 돌고, 통과하면 배포까지 이어지는 파이프라인을 처음부터 끝까지 정리한다. GitHub Actions로 검증과 이미지 빌드를 하고, OIDC 단기 토큰으로 클라우드에 인증하고, 빌드 출처 증명을 붙이고, GitOps로 배포하는 순서다. 예제의 액션 판과 문법은 2026-10-30 기준 GitHub·Argo 공식 문서로 다시 맞췄다.

파이프라인이 하는 일 — 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)로 맞췄다.

.github/workflows/ci.yml
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처럼 지원 기간이 남은 판을 나란히 돌려 «어느 판에서만 깨지는지»를 한 번에 보여 준다.

NOTE

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/*)처럼 넓게 열면 다른 저장소의 워크플로도 그 역할을 받을 수 있으므로, 저장소와 환경(또는 브랜치)까지 좁혀 적는다.

job: deploy-auth
  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로 적는다.

job: build-and-attest
  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로 검증한다.

bash
gh attestation verify oci://ghcr.io/my-org/app:TAG -R my-org/app

GitOps 배포와 카나리

GitOps 구조에서 CI 잡은 클러스터를 직접 건드리지 않는다. 대신 매니페스트 저장소의 이미지 태그만 바꾸고, 클러스터 안의 Argo CD가 그 변경을 끌어와 반영한다. 아래는 배포용 저장소의 이미지 태그를 새 커밋 SHA로 바꿔 커밋하는 예다.

job: bump-manifest (GitOps)
  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 자동 배포 가이드에 따로 정리했다.

카나리 — 일부에게 먼저

Argo Rollouts: 카나리
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: 100

Argo Rollouts 문서 기준으로 카나리는 setWeight·pause 단계를 차례로 밟고, 분석(analysis)이 실패하면 롤아웃이 중단(abort)되어 트래픽이 안정판으로 돌아간다. 트래픽 라우팅(인그레스·서비스 메시) 없이 쓰면 가중치는 레플리카 수로 어림한다. 레플리카 10개에 10%면 카나리 1개·안정판 9개가 되는 식이라, 정밀한 비율이 필요하면 트래픽 관리 연동을 붙인다. 분석 템플릿에 넣을 성공률 지표는 실시간 성능 모니터링과 자동 스케일링의 오류율·지연 SLI 식을 그대로 쓸 수 있다.

재사용 워크플로와 파이프라인 지표

재사용 워크플로 — 공통 규칙을 한 곳에

저장소가 많아지면 보안 점검·출처 증명·배포 게이트 같은 공통 단계를 재사용 워크플로로 한 곳에 두고 각 저장소가 uses:로 부른다. GitHub 문서 기준 제약은 두 가지다. 호출 사슬은 최대 10단계(맨 위 호출 워크플로 포함)까지 이을 수 있고 고리 모양 호출은 안 된다. 그리고 사슬을 따라 내려가며 권한은 유지하거나 줄일 수만 있고 늘릴 수 없다. 그래서 재사용 워크플로 안에서 필요한 권한(예: id-token: write)은 부르는 쪽에서 먼저 줘야 한다.

.github/workflows/release.yml (부르는 쪽)
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가 있음
3OIDC로 클라우드 인증 전환저장소 시크릿에 장기 클라우드 키 0
4이미지 빌드 + 출처 증명gh attestation verify 통과
5GitOps 배포 + 카나리배포 = 매니페스트 머지 · 분석 실패 시 자동 중단

Docker 이미지 빌드부터 단계별로 따라 하려면 Docker + GitHub Actions CI/CD 가이드를 함께 본다. 가장 좋은 배포는 아무도 긴장하지 않는 배포다.

자동화된 테스트·배포 파이프라인 대표 그림 — 왼쪽에 제목 글자가 있고, 오른쪽에 코드 블록이 검사 관문과 봉인 도장을 차례로 지나 컨테이너 상자가 되어 클러스터로 옮겨지는 컨베이어 장면
자동화된 테스트·배포 파이프라인 — Actions·OIDC·GitOps.

자주 묻는 것

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단계까지 이을 수 있고, 고리 모양 호출은 안 됩니다. 권한은 사슬을 따라 유지하거나 줄일 수만 있으므로 필요한 권한은 부르는 쪽에서 먼저 줍니다.

참고 자료

  1. GitHub Docs. OpenID Connect — 클라우드 시크릿 불필요·잡 단위 단기 토큰·sub 클레임 신뢰 조건(2026-10-30 확인). docs.github.com
  2. 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
  3. GitHub Docs. Reuse workflows — 최대 10단계·고리 금지·권한은 유지 또는 축소만(2026-10-30 확인). docs.github.com
  4. GitHub Docs. Workflow syntax — permissions(적지 않은 권한은 none)·concurrency cancel-in-progress(2026-10-30 확인). docs.github.com
  5. 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
  6. Argo Rollouts. Canary — setWeight·pause·analysis, 분석 실패 시 중단, 트래픽 라우팅 없을 때 레플리카 수로 어림(2026-10-30 확인). argo-rollouts.readthedocs.io
  7. DORA. DORA's software delivery performance metrics — 지표 5개·처리량/불안정성(2026-10-30 확인). dora.dev
CI/CD GitHub Actions Docker DevOps 자동화 백엔드
junetapa(박준철)
개발자가 아니라 쓰는 사람. IT 현장 27년의 기준으로 AI 도구를 고르고 기록한다.
Twitter Facebook URL 복사