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 다중 triggerbuild-image.yml: OIDC 로그인, build-arg, 이미지 pushdeploy.yml: workflow_run trigger, 배포 후 검증 루프, 롤백
이 패턴들이 GitHub Actions의 기본 어휘(workflow, job, step, action, trigger, secret, matrix) 위에서 작성된다. 본 글이 그 어휘를 한 호흡으로 정리한다.
2 핵심 4계층
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 runnerubuntu-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 | 2× |
| 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=srcuses:로 호출하는 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
6.2 GitHub Secrets — 시크릿
- name: Run integration test
env:
AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}
run: pytest -m integrationGitHub 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
설치·빌드 결과를 캐싱해 워크플로 실행 시간을 단축한다.
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 자주 발생하는 오류 패턴
CORRECT:
latest나 main은 외부 변경에 영향받는다. 11-1편 Reproducible Build 원칙대로 명시적 버전 pinning.
CORRECT:
yml 파일은 git history에 영구 보존. 시크릿은 GitHub Secrets로만.
- 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, 부분 출력)는 노출될 수 있다.
strategy:
matrix:
python-version: ["3.11", "3.12"]
os: [ubuntu, windows]
# fail-fast 기본값 true — 한 job 실패하면 나머지 즉시 취소CORRECT:
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 관련 주제
선행 학습
- Python async/await – async 개념 (이 글과 무관하지만 시리즈)
- pytest 기초 – pr-check.yml의 pytest 명령 토대
- 환경변수와 dotenv – GitHub Secrets와 같은 시크릿 관리 철학
바로 이어 읽을 글 (Tier 2 다음 편)
- YAML 기초·문법·anchor — 작성 예정
- Python logging + structured log — 작성 예정
- Docker Compose 기초 — 작성 예정
MINERVA 시리즈 응용
- MINERVA CI/CD GitHub Actions (07-1) – 본 글의 모든 개념을 4개 워크플로로 통합
- MINERVA Config 운영 패턴 (11-1) – Reproducible Build의 OIDC + commit baking
- MINERVA 고급 테스트 패턴 (12-1) – pytest 마커가 워크플로 단계로 매핑