Cloud Dev 1st CI AI - 100-hours-a-week/KTB4-1st-wiki GitHub Wiki

v1 CI ์›Œํฌํ”Œ๋กœ์šฐ ๊ตฌํ˜„ - AI

2๋‹จ๊ณ„. CI(์ง€์†์  ํ†ตํ•ฉ) ํŒŒ์ดํ”„๋ผ์ธ ์„ค๊ณ„์—์„œ ์ •ํ•œ CI Trigger๋ฅผ KTB4_1st_AI ๋ ˆํฌ์— ์‹ค์ œ๋กœ ๊ตฌํ˜„ํ•œ ๋‚ด์šฉ์ด๋‹ค.

  • ๋Œ€์ƒ ๋ ˆํฌ: KTB4_1st_AI (FastAPI)
  • ์›Œํฌํ”Œ๋กœ์šฐ ํŒŒ์ผ: ai-pr-check.yml, ai-docker-publish.yml

์›Œํฌํ”Œ๋กœ์šฐ ๊ตฌ์„ฑ

ํŒŒ์ผ ํŠธ๋ฆฌ๊ฑฐ ์‹คํ–‰ ํ•ญ๋ชฉ
ai-pr-check.yml feat โ†’ dev, hotfix โ†’ main PR Ruff ์ •์  ๋ถ„์„, ๋นŒ๋“œ ๊ฒ€์ฆ(์•ฑ ์ž„ํฌํŠธ), Docker Image ๋นŒ๋“œ(push ์—†์Œ)
ai-docker-publish.yml dev push(merge) Docker Image ๋นŒ๋“œ + GHCR push (latest, dev-<short SHA>)
ai-docker-publish.yml release ํƒœ๊ทธ(v*.*.*) push Docker Image ๋นŒ๋“œ + GHCR push (Semantic Version)

1) ai-pr-check.yml - PR ๊ฒ€์ฆ

name: AI CI - PR Check

# feat -> dev PR, hotfix -> main PR: ์ •์  ๋ถ„์„, (๋‹จ์œ„ ํ…Œ์ŠคํŠธ - ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์—†์–ด ํ˜„์žฌ ์ œ์™ธ), ๋นŒ๋“œ ๊ฒ€์ฆ, Docker Image ๋นŒ๋“œ(push ์—†์Œ)
on:
  pull_request:
    branches:
      - dev
      - main

# ๋™์ผ PR์— ์—ฐ์† Push ๋ฐœ์ƒ ์‹œ ์ง„ํ–‰ ์ค‘์ด๋˜ ์ด์ „ ์‹คํ–‰ ์ž๋™ ์ทจ์†Œ
concurrency:
  group: ai-pr-check-${{ github.event.pull_request.number }}
  cancel-in-progress: true

jobs:
  verify:
    name: ์ •์  ๋ถ„์„ ๋ฐ ๋นŒ๋“œ ๊ฒ€์ฆ
    runs-on: ubuntu-latest
    steps:
      - name: Checkout # ์†Œ์Šค ์ฝ”๋“œ ๋‹ค์šด๋กœ๋“œ
        uses: actions/checkout@v7

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.13.13"
          cache: pip

      - name: ์˜์กด์„ฑ ์„ค์น˜
        run: pip install --no-cache-dir -r requirements.txt ruff

      - name: ์ •์  ๋ถ„์„ (Ruff Lint)
        run: ruff check .

      - name: ์ •์  ๋ถ„์„ (Ruff Format)
        run: ruff format --check .

      # ๋‹จ์œ„ ํ…Œ์ŠคํŠธ - ํ˜„์žฌ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์—†์–ด ์ƒ๋žต

      - name: ๋นŒ๋“œ ๊ฒ€์ฆ (์•ฑ ์ž„ํฌํŠธ)
        run: python -c "from main import app"

  docker-build:
    name: Docker Image ๋นŒ๋“œ (๊ฒ€์ฆ์šฉ, push ์—†์Œ)
    needs: verify
    # ARM64 ์ „์šฉ GitHub Hosted ๋Ÿฌ๋„ˆ ์‚ฌ์šฉ
    runs-on: ubuntu-24.04-arm
    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Docker Image ๋นŒ๋“œ (push ์—†์Œ)
        uses: docker/build-push-action@v7
        with:
          context: . # ๋นŒ๋“œ ์ปจํ…์ŠคํŠธ ๊ฒฝ๋กœ
          push: false
          tags: ktb4-1st-ai:pr-${{ github.event.pull_request.number }}

์„ธ๋ถ€ ๊ฒฐ์ • ์‚ฌํ•ญ

  • ์ •์  ๋ถ„์„: Ruff
    • ํ˜‘์—…-๊ฐ€์ด๋“œ-v1์—์„œ FastAPI ํŒŒํŠธ์˜ CI ํ’ˆ์งˆ ๊ฒŒ์ดํŠธ ๋„๊ตฌ๋กœ ์ง€์ •๋จ. ๋ณ„๋„ ์„ค์ • ํŒŒ์ผ(pyproject.toml ๋“ฑ)์€ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๊ณ  Ruff ๊ธฐ๋ณธ ๋ฃฐ์…‹์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•œ๋‹ค.
    • ์„ค๊ณ„ ๋ฌธ์„œ(2๋‹จ๊ณ„)์—์„œ ์–ธ๊ธ‰ํ•œ "import ์ •๋ฆฌ"๋Š” ๊ธฐ๋ณธ ๋ฃฐ์…‹์—๋Š” ํฌํ•จ๋˜์ง€ ์•Š์ง€๋งŒ, ํŒ€์ด ์ •์‹์œผ๋กœ ํ•ฉ์˜ํ•˜๊ธฐ ์ „๊นŒ์ง€๋Š” ์ž„์˜๋กœ ํ”„๋กœ์ ํŠธ ์„ค์ • ํŒŒ์ผ์„ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๊ธฐ๋กœ ํ–ˆ๋‹ค.
  • Python ๋ฒ„์ „ ๊ณ ์ •(3.13.13)
  • ๋นŒ๋“œ ๋‹จ๊ณ„๋ฅผ ์•ฑ ์ž„ํฌํŠธ๋กœ ๋Œ€์ฒด
    • Python์€ ์ปดํŒŒ์ผ ๋‹จ๊ณ„๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— python -c "from main import app"์œผ๋กœ FastAPI ์•ฑ์ด ๋ฌธ๋ฒ•/์ž„ํฌํŠธ ์˜ค๋ฅ˜ ์—†์ด ๋กœ๋“œ๋˜๋Š”์ง€ ํ™•์ธํ•ด ๊ฐ™์€ ๋ชฉ์ (๋นŒ๋“œ ์‹คํŒจ ์กฐ๊ธฐ ๋ฐœ๊ฒฌ)์„ ๋‹ฌ์„ฑํ•œ๋‹ค.
  • ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ œ์™ธ
    • ํ˜„์žฌ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์—†์–ด ์ƒ๋žต
  • PR ๋‹จ๊ณ„ Docker ๋นŒ๋“œ๋Š” push ์•ˆ ํ•จ
    • PR์€ merge๊ฐ€ ๊ฑฐ์ ˆ๋˜์–ด ์ฝ”๋“œ๊ฐ€ ๋ฐ”๋€” ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ๋นŒ๋“œ ์„ฑ๊ณต ์—ฌ๋ถ€๋งŒ ํ™•์ธํ•˜๊ณ  ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์—๋Š” ์˜ฌ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค.

2) ai-docker-publish.yml - ์ด๋ฏธ์ง€ ๋นŒ๋“œ/ํ‘ธ์‹œ

name: AI Docker Image Publish

# dev push(merge): Docker Image ๋นŒ๋“œ ๋ฐ GHCR(100-hours-a-week ์กฐ์ง ์†Œ์† ํŒจํ‚ค์ง€) Push
#   - latest(๋กœ์ปฌ ํ…Œ์ŠคํŠธ์šฉ ํฌ์ธํ„ฐ), dev-<short SHA>(๋ถˆ๋ณ€ ์ถ”์ ์šฉ) ํƒœ๊ทธ
# release tag(v*.*.*) push: ๋ฐฐํฌ์šฉ Docker Image ๋นŒ๋“œ ๋ฐ GHCR Push
#   - main์œผ๋กœ dev/hotfix๊ฐ€ merge๋œ ๋’ค, ํ•ด๋‹น ์ปค๋ฐ‹์— Semantic Version ํƒœ๊ทธ(์˜ˆ: v1.0.1)๋ฅผ pushํ•˜๋ฉด ํŠธ๋ฆฌ๊ฑฐ๋จ
#   - Semantic Version ํƒœ๊ทธ๋งŒ ์ด๋ฏธ์ง€ ํƒœ๊ทธ๋กœ ์‚ฌ์šฉ
on:
  push:
    branches:
      - dev
    tags:
      - "v*.*.*"

# ๋™์ผ ๋ธŒ๋žœ์น˜/ํƒœ๊ทธ์— ์—ฐ์† Push ๋ฐœ์ƒ ์‹œ ์ง„ํ–‰ ์ค‘์ด๋˜ ์ด์ „ ์‹คํ–‰ ์ž๋™ ์ทจ์†Œ
concurrency:
  group: ai-docker-publish-${{ github.ref }} # dev์— pushํ•˜๋ฉด refs/heads/dev, ํƒœ๊ทธ pushํ•˜๋ฉด refs/tags/v1.0.1
  cancel-in-progress: true

env:
  REGISTRY: ghcr.io
  # github.repository๋Š” "100-hours-a-week/KTB4_1st_AI" ํ˜•ํƒœ(docker/metadata-action์ด images ๊ฐ’์„ ์ž๋™์œผ๋กœ ์†Œ๋ฌธ์ž ๋ณ€ํ™˜)
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    name: Docker Image ๋นŒ๋“œ ๋ฐ GHCR Push
    # ARM64 ์ „์šฉ GitHub Hosted ๋Ÿฌ๋„ˆ ์‚ฌ์šฉ
    runs-on: ubuntu-24.04-arm
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout
        uses: actions/checkout@v7

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v4

      - name: Login to GHCR
        uses: docker/login-action@v4
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GHCR_TOKEN }}

      - name: Docker ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ(ํƒœ๊ทธ/๋ผ๋ฒจ) ์ƒ์„ฑ
        id: meta
        uses: docker/metadata-action@v6
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          # dev          : latest(๋กœ์ปฌ ํ…Œ์ŠคํŠธ์šฉ ํฌ์ธํ„ฐ), dev-<short SHA>(๋ถˆ๋ณ€ ์ถ”์ ์šฉ)
          # release tag  : push๋œ ํƒœ๊ทธ(v1.0.1)์—์„œ v๋ฅผ ๋—€ Semantic Version์„ ๊ทธ๋Œ€๋กœ ํƒœ๊ทธ๋กœ ์‚ฌ์šฉ
          tags: |
            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/dev' }}
            type=sha,format=short,prefix=dev-,enable=${{ github.ref == 'refs/heads/dev' }}
            type=semver,pattern={{version}}
          labels: |
            org.opencontainers.image.authors=${{ github.actor }}

      - name: Docker Image ๋นŒ๋“œ ๋ฐ Push
        id: build-push
        run: |
          set -e

          # ํƒœ๊ทธ ๋ณ€ํ™˜
          TAG_ARGS=()
          while IFS= read -r tag; do
            [ -z "$tag" ] && continue
            TAG_ARGS+=(--tag "$tag")
          done <<< "${{ steps.meta.outputs.tags }}"

          # ๋ผ๋ฒจ ๋ณ€ํ™˜
          LABEL_ARGS=()
          while IFS= read -r label; do
            [ -z "$label" ] && continue
            LABEL_ARGS+=(--label "$label")
          done <<< "${{ steps.meta.outputs.labels }}"

          # ์ด๋ฏธ์ง€ ๋นŒ๋“œ + GHCR push
          docker buildx build \
            --push \
            "${TAG_ARGS[@]}" \
            "${LABEL_ARGS[@]}" \
            .

์„ธ๋ถ€ ๊ฒฐ์ • ์‚ฌํ•ญ

  • dev ํƒœ๊ทธ: latest(๋กœ์ปฌ ํ…Œ์ŠคํŠธ์šฉ ํฌ์ธํ„ฐ), dev-<short SHA>(๋ถˆ๋ณ€ ์ถ”์ ์šฉ). ์„ค๊ณ„ ๋ฌธ์„œ(2๋‹จ๊ณ„) ๊ทธ๋Œ€๋กœ ์œ ์ง€.
  • main ๋ฐฐํฌ ๋ฒ„์ „ = Git tag ๊ธฐ๋ฐ˜ Semantic Version (์„ค๊ณ„ ๋ฌธ์„œ ๋Œ€๋น„ ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„, ์•„๋ž˜ "์„ค๊ณ„ ๋ฌธ์„œ์™€ ๋‹ฌ๋ผ์ง„ ์ " ์ฐธ๊ณ )
    • docker/metadata-action์˜ type=semver,pattern={{version}}์ด push๋œ ํƒœ๊ทธ(์˜ˆ: v1.0.1)์—์„œ v ์ ‘๋‘์–ด๋ฅผ ๋—€ 1.0.1์„ ์ž๋™์œผ๋กœ ์ด๋ฏธ์ง€ ํƒœ๊ทธ๋กœ ์ถ”์ถœํ•œ๋‹ค.
    • v ์ ‘๋‘์–ด๋ฅผ ๋–ผ๋Š” ์ด์œ : Git/GitHub ์ชฝ์€ ๋ฒ„์ „ ํƒœ๊ทธ์— v๋ฅผ ๋ถ™์ด๋Š” ๊ฒŒ ๊ด€๋ก€(v1.0.1)์ธ ๋ฐ˜๋ฉด, Docker ์ด๋ฏธ์ง€ ํƒœ๊ทธ๋Š” ์ ‘๋‘์–ด ์—†์ด ์ˆซ์ž๋งŒ ์“ฐ๋Š” ๊ฒŒ ๊ด€๋ก€(์˜ˆ: python:3.13.13)๋ผ, ๋‘ ์ƒํƒœ๊ณ„์˜ ์ฐจ์ด๋ฅผ metadata-action์ด ์ž๋™์œผ๋กœ ๋งž์ถฐ์ค€๋‹ค.
    • main์œผ๋กœ ๋ณ‘ํ•ฉ๋งŒ ํ•ด์„œ๋Š” ์ด๋ฏธ์ง€๊ฐ€ ๋งŒ๋“ค์–ด์ง€์ง€ ์•Š๋Š”๋‹ค. ๋ฐ˜๋“œ์‹œ ํƒœ๊ทธ๋ฅผ pushํ•ด์•ผ ํŠธ๋ฆฌ๊ฑฐ๋œ๋‹ค.
  • ๋ฆด๋ฆฌ์ฆˆ ์ ˆ์ฐจ (dev/hotfix โ†’ main PR merge ์ดํ›„)
    1. GitHub ์ €์žฅ์†Œ โ†’ Releases โ†’ Draft a new release
    2. Target: main, ์ƒˆ ํƒœ๊ทธ๋ฅผ v1.0.1 ํ˜•์‹์œผ๋กœ ์ž…๋ ฅ ํ›„ ์ƒ์„ฑ
    3. Publish release ํด๋ฆญ โ†’ ํƒœ๊ทธ๊ฐ€ push๋˜์–ด ์›Œํฌํ”Œ๋กœ์šฐ๊ฐ€ ์ž๋™ ์‹คํ–‰๋จ
    • CLI๋กœ git tag/git push ํ•ด๋„ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•˜์ง€๋งŒ, GitHub Release ์ชฝ์ด CLI ์—†์ด ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋๋‚˜๊ณ  ๋ฆด๋ฆฌ์ฆˆ ๋…ธํŠธ๋„ ํ•จ๊ป˜ ๋‚จ์•„ ์ด ๋ฐฉ๋ฒ•์„ ๊ถŒ์žฅํ•œ๋‹ค.
    • ์–ด๋–ค ์ž๋ฆฌ(MAJOR/MINOR/PATCH)๋ฅผ ์˜ฌ๋ฆด์ง€๋Š” ์„ค๊ณ„ ๋ฌธ์„œ์˜ ๊ธฐ์กด ๊ทœ์น™์„ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ฅธ๋‹ค: hotfix๋Š” ํ•ญ์ƒ PATCH, ์ •๊ธฐ ๋ฐฐํฌ๋Š” ๊ทธ ์ฃผ ๋ณ€๊ฒฝ ์ค‘ ์˜ํ–ฅ๋„๊ฐ€ ๊ฐ€์žฅ ํฐ ๋“ฑ๊ธ‰.
  • ๋ผ๋ฒจ ์ž‘์„ฑ์ž ์ •๋ณด: github.event.head_commit.author.name ๋Œ€์‹  github.actor๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. Annotated tag๋ฅผ pushํ•˜๋Š” ์ด๋ฒคํŠธ์—์„œ๋Š” head_commit์ด ๋น„์–ด ์žˆ์„ ์ˆ˜ ์žˆ์–ด(GitHub ์›นํ›… ํŠน์„ฑ), ํ•ญ์ƒ ๊ฐ’์ด ์ฑ„์›Œ์ง€๋Š” github.actor(ํƒœ๊ทธ๋ฅผ pushํ•œ ์‚ฌ๋žŒ)๋กœ ๋Œ€์ฒดํ–ˆ๋‹ค.

์„ค๊ณ„ ๋ฌธ์„œ(2๋‹จ๊ณ„ CI ํŒŒ์ดํ”„๋ผ์ธ ์„ค๊ณ„)์™€ ๋‹ฌ๋ผ์ง„ ์ 

ํ•ญ๋ชฉ ์„ค๊ณ„ ๋ฌธ์„œ(2๋‹จ๊ณ„) ์‹ค์ œ ๊ตฌํ˜„ ์‚ฌ์œ 
๋‹จ์œ„ ํ…Œ์ŠคํŠธ (PR ๋‹จ๊ณ„) ํฌํ•จ ์ œ์™ธ ํ˜„์žฌ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์—†์Œ
dev โ†’ main PR ์ „์ฒด ํ…Œ์ŠคํŠธ ์›Œํฌํ”Œ๋กœ์šฐ ํฌํ•จ ๋ฏธ๊ตฌํ˜„ ํ˜„์žฌ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์—†์Œ
main ๋ฐฐํฌ ๋ฒ„์ „ ๊ด€๋ฆฌ ๋ช…์‹œ ์•ˆ ๋จ(Semantic Version ํƒœ๊ทธ ์‚ฌ์šฉ๋งŒ ๊ธฐ์ˆ ) Git tag(v*.*.*) push ๊ธฐ๋ฐ˜, GitHub Release๋กœ ์ƒ์„ฑ ๋ฒ„์ „์„ ์ฝ”๋“œ ํŒŒ์ผ(package.json๋ฅ˜)๋กœ ๊ด€๋ฆฌํ•˜๋ฉด ๋งค ๋ฐฐํฌ๋งˆ๋‹ค ๊ทธ ํŒŒ์ผ์„ ๊ณ ์น˜๋Š” PR์ด ๋ณ„๋„๋กœ ํ•„์š”ํ•จ. Git tag๋ฅผ ๋‹จ์ผ ์ง„์‹ค ๊ณต๊ธ‰์›์œผ๋กœ ์‚ผ์œผ๋ฉด ๊ทธ ์ ˆ์ฐจ๊ฐ€ ์—†์–ด์ง€๊ณ , "ํƒœ๊ทธ push = ๋ฐฐํฌ ํŠธ๋ฆฌ๊ฑฐ"๋กœ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ถ„๋ฆฌ๋œ๋‹ค.
main push(merge) ํŠธ๋ฆฌ๊ฑฐ main push(merge) ์‹œ ์ด๋ฏธ์ง€ ๋นŒ๋“œ release ํƒœ๊ทธ(v*.*.*) push ์‹œ ์ด๋ฏธ์ง€ ๋นŒ๋“œ ์œ„ ๋ฒ„์ „ ๊ด€๋ฆฌ ๋ฐฉ์‹ ๋ณ€๊ฒฝ์— ๋”ฐ๋ฅธ ํŠธ๋ฆฌ๊ฑฐ ๋ณ€๊ฒฝ. main์— ๋ณ‘ํ•ฉํ•˜๋Š” ์‹œ์ ๊ณผ, ์‹ค์ œ๋กœ ๋ฐฐํฌ์šฉ ์ด๋ฏธ์ง€๋ฅผ ๋งŒ๋“œ๋Š” ์‹œ์ (ํƒœ๊ทธ๋ฅผ ๋ถ™์ด๋Š” ์‹œ์ )์„ ์˜๋„์ ์œผ๋กœ ๋ถ„๋ฆฌํ–ˆ๋‹ค.

์ฐธ๊ณ 

  • Cloud-Design-2nd - 2๋‹จ๊ณ„ CI ํŒŒ์ดํ”„๋ผ์ธ ์„ค๊ณ„ (Trigger, CI Stages, ์‹คํŒจ ์ฒ˜๋ฆฌ ์ •์ฑ… ๋“ฑ ๊ทผ๊ฑฐ)
  • Cloud-Dev-v1-Dockerfile - AI Dockerfile ๊ตฌ์„ฑ
โš ๏ธ **GitHub.com Fallback** โš ๏ธ