GitHub Actions 워크플로 기초

trigger·job·step·action·secret·matrix — MINERVA 07-1 CI/CD의 토대

MINERVA 07-1편이 4개 워크플로(pr-check·integration·build-image·deploy)로 CI/CD를 묶었다. 본 글은 그 토대인 GitHub Actions의 핵심 개념을 정리한다. workflow·job·step 계층, trigger 종류, action 사용, GitHub Secrets·환경변수, matrix build, job 의존성·캐싱·OIDC를 한 호흡으로 다룬다.

Engineering
저자

Kwangmin Kim

공개

2026년 05월 06일

1 왜 GitHub Actions를 알아야 하는가

MINERVA 07-1편 GitHub Actions CI/CD는 4개 워크플로(pr-check·integration·build-image·deploy)로 빌드·테스트·배포를 자동화한다. 이 글들이 다루는 패턴은:

  • pr-check.yml: trigger 분기, action 활용, pytest 마커 통합
  • integration.yml: schedule cron + main push 다중 trigger
  • build-image.yml: OIDC 로그인, build-arg, 이미지 push
  • deploy.yml: workflow_run trigger, 배포 후 검증 루프, 롤백

이 패턴들이 GitHub Actions의 기본 어휘(workflow, job, step, action, trigger, secret, matrix) 위에서 작성된다. 본 글이 그 어휘를 한 호흡으로 정리한다.

2 핵심 4계층

정의: GitHub Actions

GitHub 저장소에 .github/workflows/*.yml 파일로 정의하는 이벤트 기반 자동화 엔진.

  • workflow: yml 파일 1개 = 1 워크플로
  • job: 워크플로 안의 실행 단위. 별도 runner에서 격리 실행
  • step: job 안의 단계. 같은 runner에서 순차 실행
  • action: step에서 호출하는 재사용 가능 도구 (예: actions/checkout)
# .github/workflows/example.yml
name: Example                        # workflow 이름

on: [push, pull_request]             # trigger

jobs:
  test:                              # job 1
    runs-on: ubuntu-22.04            # 어느 runner에서 실행
    steps:
      - uses: actions/checkout@v4    # step 1 — action 호출
      - run: echo "Hello"            # step 2 — shell 명령

  build:                             # job 2 (test와 병렬)
    runs-on: ubuntu-22.04
    needs: test                      # test 완료 후 실행
    steps:
      - run: echo "Building"

같은 workflow의 여러 job은 별도 runner에서 격리 실행된다. 한 job 안의 여러 step은 같은 runner에서 순차 실행되어 작업 디렉토리·환경변수가 공유된다.

3 trigger — on:

on:
  # 1. push — 브랜치에 push될 때
  push:
    branches: [main, develop]
    paths:                           # 특정 경로 변경 시만
      - "src/**"
      - "tests/**"

  # 2. pull_request — PR이 열리거나 갱신될 때
  pull_request:
    branches: [main]

  # 3. schedule — cron 스케줄
  schedule:
    - cron: "0 17 * * *"             # UTC 매일 17:00 (KST 02:00)

  # 4. workflow_dispatch — 수동 trigger (GitHub UI에서 실행 버튼)
  workflow_dispatch:
    inputs:
      environment:
        type: choice
        options: [dev, staging, prod]

  # 5. workflow_run — 다른 워크플로 완료 시
  workflow_run:
    workflows: ["Build & Push Image"]
    types: [completed]

  # 6. release — 릴리스 생성 시
  release:
    types: [published]

[MINERVA 07-1편 4개 워크플로]가 위 trigger들을 조합한다.

워크플로 trigger 조합 의도
pr-check push + pull_request 매 PR/커밋 검증
integration schedule + push: main + workflow_dispatch 일 1회 + main merge 즉시 + 수동
build-image push: main merge 시 이미지 생성
deploy workflow_run (build 완료 후) 빌드 성공 시만 배포

trigger를 분리하면 변경 빈도가 다른 작업이 서로 간섭하지 않는다 — PR 검증이 빠른 사이 일간 통합 테스트는 별도로 돌고, 배포는 빌드 성공 후에만 일어난다.

4 runner — 실행 환경

jobs:
  build:
    runs-on: ubuntu-22.04            # GitHub-hosted Linux
    # runs-on: ubuntu-latest         # 권장 안 함 — 미래 버전 변경 시 회귀
    # runs-on: windows-2022          # Windows
    # runs-on: macos-13              # macOS (비싸다)
    # runs-on: [self-hosted, gpu]    # self-hosted runner

ubuntu-latest 대신 명시적 버전(ubuntu-22.04)을 권장한다 — latest는 GitHub이 새 버전으로 갱신 시 워크플로 동작이 미세 변하는 회귀 위험이 있다. MINERVA 11-1편 Reproducible Build의 base image digest pinning과 같은 원칙.

GitHub-hosted runner의 무료 분 (퍼블릭 저장소 무제한, private은 월 한도):

플랜 Linux Windows macOS
Free 2,000분/월 4× = 8,000분 분량 10× = 20,000분 분량
OS 분당 비용 (분 단위)
Linux 1× (가장 저렴)
Windows
macOS 10×

대부분의 CI는 Linux로 충분하다. macOS는 iOS 빌드 같은 특수 목적에만.

5 action — 재사용 가능 도구

steps:
  # 1. action 호출 (uses)
  - uses: actions/checkout@v4        # 저장소 clone

  - uses: actions/setup-python@v5
    with:                            # action 매개변수
      python-version: "3.11.10"
      cache: pip

  # 2. shell 명령 (run)
  - name: Install dependencies       # step 이름 (선택, 로그용)
    run: |
      pip install --no-cache-dir -r requirements.txt
      pytest --cov=src

uses:로 호출하는 action은 GitHub Marketplace 또는 Docker 컨테이너로 정의된 재사용 도구다. 자주 쓰는 official action:

action 역할
actions/checkout@v4 git clone (거의 모든 워크플로의 첫 step)
actions/setup-python@v5 Python 설치 + pip 캐시
actions/setup-node@v4 Node.js 설치 + npm 캐시
actions/cache@v4 임의 디렉토리 캐싱
actions/upload-artifact@v4 빌드 산출물 업로드
actions/download-artifact@v4 다른 job에서 산출물 다운로드
azure/login@v2 Azure 인증 (OIDC 지원)
docker/build-push-action@v5 Docker 이미지 빌드+push

@v4는 major 버전 pinning. @abc1234... 같은 commit SHA pinning이 가장 안전하지만 가독성이 떨어진다. major 버전 pinning이 안전성·가독성 균형점.

6 환경변수와 secret

6.1 환경변수 — env

jobs:
  test:
    runs-on: ubuntu-22.04
    env:                             # job 전체에 적용
      LOG_LEVEL: DEBUG
      PYTHONPATH: ./src
    steps:
      - run: echo "$LOG_LEVEL"

      - name: Step-level env
        env:                         # step에만 적용
          TEMP_VAR: hello
        run: echo "$TEMP_VAR"

6.2 GitHub Secrets — 시크릿

- name: Run integration test
  env:
    AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}
  run: pytest -m integration

GitHub Secrets는:

  • 저장소·organization·environment 단위 설정
  • 워크플로 로그에 출력 시 자동 마스킹 (***)
  • pull request from fork에는 노출 안 됨 (보안 기본값)
  • 암호화되어 저장되며 워크플로 실행 시점에만 해독

설정: GitHub 저장소 → Settings → Secrets and variables → Actions → New repository secret.

Tier 1 환경변수 글에서 다룬 시크릿 관리 원칙(코드·이미지·git에 절대 commit 금지)이 GitHub Secrets로 자연스럽게 연결된다.

6.3 문자열 보간 — ${{ ... }}

GitHub Actions는 yml 안에서 ${{ ... }} 문법으로 변수·함수·context를 표현한다.

- name: Use context
  run: |
    echo "Branch: ${{ github.ref_name }}"
    echo "Commit: ${{ github.sha }}"
    echo "Actor: ${{ github.actor }}"

- name: Conditional
  if: ${{ github.event_name == 'pull_request' }}
  run: echo "PR 시점만 실행"

자주 쓰는 context:

context 의미
github.ref_name 브랜치 이름 (main, feature/x)
github.sha 전체 commit hash
github.event_name trigger 이벤트 (push, pull_request)
github.actor 워크플로 실행한 사용자
secrets.X GitHub Secret 값
env.X 환경변수
inputs.X workflow_dispatch 입력값

7 job 의존성 — needs

jobs:
  test:
    runs-on: ubuntu-22.04
    steps: [...]

  build:
    needs: test                      # test 성공 후만 실행
    runs-on: ubuntu-22.04
    steps: [...]

  deploy:
    needs: [test, build]             # 둘 다 성공 후
    runs-on: ubuntu-22.04
    steps: [...]

기본은 모든 job 병렬 실행. needs로 순서 강제. 한 job이 실패하면 그를 needs하는 job은 skip된다.

7.1 job 간 데이터 전달 — outputs

jobs:
  prepare:
    runs-on: ubuntu-22.04
    outputs:
      version: ${{ steps.meta.outputs.version }}
    steps:
      - id: meta
        run: echo "version=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT

  build:
    needs: prepare
    runs-on: ubuntu-22.04
    steps:
      - run: echo "Build version ${{ needs.prepare.outputs.version }}"

$GITHUB_OUTPUT은 step 단위 출력값을 다른 step·job이 읽을 수 있는 파일이다. 빌드 메타데이터(commit hash, build date)를 추출해 다음 job으로 전달할 때 사용한다 — MINERVA 07-1 build-image.yml 패턴.

8 matrix — 한 job 여러 변형

여러 Python 버전·OS에서 같은 테스트를 돌리고 싶다면 matrix로 자동 펼친다.

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        python-version: ["3.11", "3.12"]
        os: [ubuntu-22.04, windows-2022]
        # 2 × 2 = 4개 job 자동 생성
      fail-fast: false               # 한 변형 실패해도 나머지 계속 실행
    steps:
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - run: pytest

특정 조합만 제외하려면 exclude:

strategy:
  matrix:
    python-version: ["3.11", "3.12"]
    os: [ubuntu-22.04, windows-2022, macos-13]
    exclude:
      - os: macos-13
        python-version: "3.11"       # 이 조합은 빼기

MINERVA처럼 단일 Python 버전·단일 OS 운영이면 matrix 불필요. 라이브러리 개발(여러 버전 지원)에는 필수.

9 캐싱 — actions/cache

설치·빌드 결과를 캐싱해 워크플로 실행 시간을 단축한다.

- uses: actions/setup-python@v5
  with:
    python-version: "3.11.10"
    cache: pip                       # pip 캐시 자동 처리

setup-python이 가장 흔한 케이스를 자동 처리하지만, 더 세밀한 제어는 actions/cache를 직접 쓴다.

- name: Cache Poetry venv
  uses: actions/cache@v4
  with:
    path: ~/.cache/pypoetry/virtualenvs
    key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }}
    restore-keys: |
      ${{ runner.os }}-poetry-

key가 일치하면 캐시 hit, 그렇지 않으면 restore-keys에서 prefix 일치하는 가장 최근 캐시 사용. hashFiles('poetry.lock')이 lock 변경 시 자동으로 새 캐시 생성.

캐싱 효과: setup-python의 pip 캐시만으로도 의존성 설치 시간이 30초 → 5초 수준으로 줄어든다. GitHub Actions 무료 분도 절약.

10 OIDC — 시크릿 없이 클라우드 로그인

전통적 패턴: Azure 자격증명을 GitHub Secret에 저장 → 워크플로에서 사용. 개선된 패턴: GitHub의 OIDC 토큰으로 단기 인증.

permissions:
  id-token: write                    # OIDC 토큰 발급 권한
  contents: read

steps:
  - uses: azure/login@v2
    with:
      client-id: ${{ secrets.AZURE_CLIENT_ID }}      # service principal ID (시크릿 아님)
      tenant-id: ${{ secrets.AZURE_TENANT_ID }}
      subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

OIDC가 다른 점: client_secret을 저장하지 않는다. GitHub이 OIDC 토큰을 발급하고 Azure가 이를 federated credential로 인증. 토큰은 워크플로 실행 동안만 유효.

설정에 Azure AD App + Federated Credential 등록이 필요해 초기 설정 비용이 있지만, 시크릿 rotation이 필요 없는 운영 안전성을 얻는다. 자세한 절차는 MINERVA 07-1편 참조.

11 워크플로 구조 — 추천 디렉토리

.github/
├── workflows/
│   ├── pr-check.yml              # PR 시점 빠른 검증
│   ├── integration.yml           # 일 1회 통합
│   ├── build-image.yml           # main merge 시 이미지
│   ├── deploy.yml                # 배포
│   └── release.yml               # 릴리스 자동화
└── actions/                      # 자체 composite action (선택)
    └── setup-project/
        └── action.yml

워크플로마다 yml 분리 → 변경 폭발 반경 줄임 + GitHub Actions UI에서 워크플로별 실행 이력이 깔끔.

12 자주 발생하는 오류 패턴

WRONG:

runs-on: ubuntu-latest               # 새 버전 갱신 시 회귀 위험
- uses: actions/checkout@main        # 새 commit이 들어오면 즉시 영향

CORRECT:

runs-on: ubuntu-22.04                # 명시적 버전
- uses: actions/checkout@v4          # major 버전 pinning

latestmain은 외부 변경에 영향받는다. 11-1편 Reproducible Build 원칙대로 명시적 버전 pinning.

WRONG:

env:
  AZURE_OPENAI_API_KEY: "sk-abc123..."   # yml에 hardcode

CORRECT:

env:
  AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}

yml 파일은 git history에 영구 보존. 시크릿은 GitHub Secrets로만.

WRONG:

- run: |
    echo "Key: ${{ secrets.AZURE_OPENAI_API_KEY }}"   # 로그에 출력 (마스킹은 되지만 위험)
    echo "$SECRET" | xxd                                # 변환된 형태로 노출 가능

CORRECT:

- run: |
    pytest -m integration                              # 시크릿을 명령에 직접 넣지 말 것
  env:
    AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}

시크릿을 환경변수로만 전달하고 로그에 출력하지 않는다. GitHub의 마스킹은 정확한 문자열만 처리하므로 변환된 형태(base64, 부분 출력)는 노출될 수 있다.

WRONG:

strategy:
  matrix:
    python-version: ["3.11", "3.12"]
    os: [ubuntu, windows]
  # fail-fast 기본값 true — 한 job 실패하면 나머지 즉시 취소

CORRECT:

strategy:
  matrix:
    ...
  fail-fast: false                  # 모든 변형 결과를 끝까지 보고 싶을 때

matrix의 기본 fail-fast: true는 첫 실패에서 나머지를 취소한다. 라이브러리 개발에서 “어느 버전에서 실패하는가”를 모두 보고 싶다면 false로.

13 정리

개념 의미
workflow / job / step / action 4계층 구조 — yml 1개 = 1 워크플로
trigger (on:) push·pull_request·schedule·workflow_dispatch·workflow_run·release
runner ubuntu-22.04 명시 권장. matrix로 여러 변형
action uses: org/name@v4로 재사용. major 버전 pinning
env / secrets ${{ secrets.X }} 보간, yml에 hardcode 금지
context ${{ github.sha }}, ${{ github.ref_name }}
needs / outputs job 의존성 + 데이터 전달
matrix 한 job을 여러 변형으로 자동 펼침
캐시 setup-python의 cache 또는 actions/cache
OIDC 클라이언트 시크릿 저장 없이 클라우드 로그인

14 응용 분야

MINERVA 07-1 워크플로 본 글 절
pr-check.yml trigger (push+pull_request), action(checkout/setup-python), env+secrets, 캐시
integration.yml trigger (schedule+workflow_dispatch+push), if 조건부 (snapshot 주 1회)
build-image.yml OIDC, build-arg + outputs, docker action
deploy.yml workflow_run trigger, /health/build 검증 step, 자동 롤백 step

15 관련 주제

선행 학습

바로 이어 읽을 글 (Tier 2 다음 편)

  • YAML 기초·문법·anchor — 작성 예정
  • Python logging + structured log — 작성 예정
  • Docker Compose 기초 — 작성 예정

MINERVA 시리즈 응용

Subscribe

Enjoy this blog? Get notified of new posts by email: