소개

목표 : 독자가 최소한이지만 기능적인 파이썬 애플리케이션용 CI/CD 파이프라인을 설정하고 PyPI에 자동 배포하도록 안내합니다.

개발 프로세스의 자동화는 현대 프로젝트에서 필수적이 되었습니다. 잘 설계된 CI/CD 파이프라인은 개발 주기 초기에 회귀를 감지하는 것뿐만 아니라 배포 프로세스를 완전히 자동화할 수 있습니다.

이 문서에서는 GitHub Actions를 사용하여 Python 애플리케이션에 대한 완전한 CI/CD 파이프라인을 구축하는 방법을 살펴보겠습니다. 여기서 CI는 지속적 통합, CD는 지속적 배포를 의미하며, PyPI에 배포하는 과정까지 다룹니다.

파이프라인 아키텍처

우리의 파이프라인은 두 개의 서로 다른 워크플로우로 구성됩니다 :

  1. CI 파이프라인각 푸시 및 pull request마다 실행됩니다

  2. CD 파이프라인: GitHub 릴리스 중에만 트리거됨

@startuml
!theme plain

package "GitHub 저장소" {
  [Source Code] as source
  [GitHub Actions] as actions
}

package "CI 파이프라인" {
  [Checkout] as checkout_ci
  [Setup Python] as python_ci
  [Install Dependencies] as deps_ci
  [Linting (Ruff)] as lint
  [Unit Tests] as tests
}

package "CD 파이프라인" {
  [Checkout] as checkout_cd
  [Setup Python] as python_cd
  [Build Package] as build
  [Publish to PyPI] as pypi
}

source --> actions : Push/PR
actions --> checkout_ci
checkout_ci --> python_ci
python_ci --> deps_ci
deps_ci --> lint
lint --> tests

source --> actions : Release
actions --> checkout_cd : On Release
checkout_cd --> python_cd
python_cd --> build
build --> pypi

@enduml

연속 통합(CI) 파이프라인 구성

CI 워크플로우 구조

CI 워크플로우는 코드의 각 기여를 검증하도록 설계되었습니다. 다음은 전체 구성 :

name: CI/CD Pipeline

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.x'

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install ruff pytest pytest-mock

    - name: Run Linting (Ruff)
      run: ruff check .

    - name: Run Tests (Pytest)
      run: pytest

CI 단계 분석

1. 트리거 (on)

----
----
on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
----

파이프라인이 다음에 따라 트리거됩니다: - 브랜치에 푸시할 때마다`main` - 각 pull request에 대해`main`

이 접근 방식은 메인 코드가 안정적으로 유지되도록 하고 모든 기여가 통합되기 전에 검증되도록 보장합니다.

==== 2. 실행 환경

----

runs-on: ubuntu-latest

Ubuntu Latest는 대부분의 Python 프로젝트에 성능, 비용, 호환성 측면에서 좋은 균형을 제공합니다.

==== 3. 코드 체크아웃

```yaml
----
----
- name: Checkout code
  uses: actions/checkout@v4
----

행동`checkout@v4`저장소의 소스 코드를 가져옵니다. v4 버전은 성능과 보안 개선을 제공합니다.

==== 4. 파이썬 구성

----

- name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x'

사용`'3.x'`자동으로 최신 안정 버전의 Python 3을 사용하게 하여 유지 관리를 간소화합니다.

==== 5. 의존성 설치

```yaml
----
----
- name: Install dependencies
  run: |
    python -m pip install --upgrade pip
    pip install -r requirements.txt
    pip install ruff pytest pytest-mock
----

이 단계: - pip을 최신 버전으로 업데이트합니다 - 프로젝트 의존성을 설치합니다. - 개발 도구(린팅 및 테스트)를 추가합니다.

==== 6. Ruff로 린팅

----

- name: Run Linting (Ruff) run: ruff check .

**러프**은 초고속 파이썬 린터로, Rust로 작성되었습니다. 이는 여러 도구(Flake8, Black, isort)의 기능을 하나의 성능 좋은 도구로 결합합니다.

==== 7. 테스트 실행

```yaml
----
----
- name: Run Tests (Pytest)
  run: pytest
----

Pytest는 테스트 스위트 전체를 실행하여 변경 사항이 회귀를 일으키지 않도록 보장합니다.

== 배포 파이프라인 구성(CD)

=== CD 워크플로우 구조

CD 워크플로우는 GitHub 릴리스에서만 트리거되고 PyPI에 게시를 자동화합니다:

[source,yaml]
----
name: Publish to PyPI

on:
  release:
    types:
      - published

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.x'

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install setuptools wheel twine

    - name: Build and publish
      env:
        TWINE_USERNAME: __token__
        TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
      run: |
        python setup.py sdist bdist_wheel
        twine upload dist/*
----

=== CD 단계 분석

==== 트리거 출시

----

on: release: types: - published

CD 파이프라인은 GitHub 릴리스가 게시될 때만 트리거됩니다. 이 접근 방식은 배포에 대한 정확한 제어를 보장합니다.

==== 2. 빌드 도구 설치

```yaml
----
----
pip install setuptools wheel twine
----

- **setuptools**: 파이썬 패키징 도구 - **바퀴**: 현대적인 Python 배포 형식 - **끈**PyPI에 업로드하는 보안 도구

==== 3. 인증 구성

----

env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}

인증은 GitHub 비밀에 저장된 PyPI API 토큰을 사용하며, 이는 기존 자격 증명보다 더 안전합니다.

==== 4. 빌드 및 배포

```yaml
----
----
run: |
  python setup.py sdist bdist_wheel
  twine upload dist/*
----

- `sdist`: 소스 배포본을 생성합니다. - `bdist_wheel`: 휠(바이너리 배포)를 생성합니다 - `twine upload`PyPI에 배포를 게시합니다

== 파이썬 패키지 구성

=== setup.py의 구조

파이프라인이 작동하려면 프로젝트에 파일을 포함해야 합니다.`setup.py`:

[source,python]
----
from setuptools import setup, find_packages

with open("README.adoc", "r", encoding="utf-8") as fh:
    long_description = fh.read()

setup(
    name="playlist-downloader",
    version="1.0.0",
    author="Votre Nom",
    author_email="[email protected]",
    description="CLI tool for managing YouTube playlists",
    long_description=long_description,
    long_description_content_type="text/plain",
    url="https://github.com/cheroliv/playlist-downloader",
    packages=find_packages(),
    classifiers=[
        "Development Status :: 4 - Beta",
        "Intended Audience :: Developers",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
        "Programming Language :: Python :: 3",
        "Programming Language :: Python :: 3.8",
        "Programming Language :: Python :: 3.9",
        "Programming Language :: Python :: 3.10",
        "Programming Language :: Python :: 3.11",
    ],
    python_requires=">=3.8",
    install_requires=[
        "typer>=0.9.0",
        "yt-dlp>=2023.1.6",
        "google-api-python-client>=2.70.0",
        "google-auth-oauthlib>=0.7.1",
        "pymonad>=2.4.0",
        "pyyaml>=6.0",
    ],
    entry_points={
        "console_scripts": [
            "playlist-downloader=cli:app",
        ],
    },
)
----

=== setup.py의 핵심 포인트

1. **메타데이터**: 이름, 버전, 작가, 설명
2. **의존성**필요한 패키지 목록
3. **진입점**: 노출된 CLI 명령어
4. **분류기**: PyPI용 메타데이터

== GitHub Secrets를 사용한 보안

=== PyPI 토큰 설정

1. **PyPI에서 API 토큰 생성**:

- PyPI에 로그인하세요 - 계정 설정 > API 토큰으로 이동하세요 - 적절한 권한으로 새 토큰을 생성하세요

1. **GitHub에 비밀을 추가**:

- 리포지토리 설정 > 비밀 및 변수 > 작업 - 새로운 비밀을 이름을 지정하여 생성하세요`PYPI_API_TOKEN` - PyPI 토큰을 붙여넣으세요

[plantuml, secrets-flow, svg]
----
@startuml
!theme plain

actor Developer as dev
participant "GitHub 저장소" as repo
participant "GitHub 액션" as actions
participant PyPI

dev -> repo : Configure PYPI_API_TOKEN secret
repo -> actions : Trigger CD pipeline on release
actions -> actions : Access secret securely
actions -> PyPI : Authenticate with token
PyPI -> PyPI : Validate and publish package

note over actions, PyPI
  Token never exposed in logs
  Automatic rotation possible
end note

@enduml
----

== 전체 배포 워크플로

=== 배포 시퀀스

[plantuml, deployment-sequence, svg]
----
@startuml
!theme plain

actor Developer as dev
participant "로컬 Git" as git
participant "GitHub" as github
participant "GitHub Actions" as actions
participant PyPI
participant "최종 사용자" as users

dev -> git : git tag v1.0.0
dev -> git : git push origin v1.0.0
git -> github : Push tag
dev -> github : Create release from tag
github -> actions : Trigger CD pipeline
actions -> actions : Checkout code
actions -> actions : Setup Python environment
actions -> actions : Install build tools
actions -> actions : Build distributions (sdist + wheel)
actions -> PyPI : Upload to PyPI with token
PyPI -> PyPI : Validate and publish
users -> PyPI : pip install playlist-downloader

note over dev, github
  Release creation can be automated
  or done manually through GitHub UI
end note

@enduml
----

=== 파이프라인 상태

[plantuml, pipeline-states, svg]
----
@startuml
!theme plain

[*] --> Idle

Idle --> CI_Running : Push/PR created
CI_Running --> CI_Success : All checks pass
CI_Running --> CI_Failed : Linting/Tests fail
CI_Success --> Idle : Merge completed
CI_Failed --> Idle : Fix and retry

Idle --> CD_Running : Release published
CD_Running --> CD_Success : Package published
CD_Running --> CD_Failed : Build/Upload error
CD_Success --> Idle : Package available on PyPI
CD_Failed --> Idle : Fix and retry release

note on link #red : Blocks merge
note on link #green : Allows deployment

@enduml
----

== 모범 사례 및 최적화

=== 1. 버전 관리

의미 있는 Git 태그를 사용하세요 :

----

git tag -a v1.2.3 -m "Release version 1.2.3" git push origin v1.2.3

=== 2. 매트릭스 테스트

여러 Python 버전에서 테스트하기 :

```yaml
----
----
strategy:
  matrix:
    python-version: [3.8, 3.9, "3.10", "3.11"]
----

=== 3. 의존성 캐시

캐시로 빌드를 가속화하세요:

----

- name: Cache pip dependencies uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}

=== 4. 배포 환경

GitHub 환경을 사용하여 제어된 배포를 수행하세요 :

```yaml
----
----
jobs:
  deploy:
    environment: production
    runs-on: ubuntu-latest
----

== 유스케이스 및 아키텍처

=== 사용 사례 다이어그램

[plantuml, use-cases, svg]
----
@startuml
!theme plain

left to right direction

actor "개발자" as dev
actor "깃허브 액션" as ga
actor "최종 사용자" as user

package "CI/CD 시스템" {
  usecase "링팅 실행" as lint
  usecase "테스트 실행" as test
  usecase "빌드 패키지" as build
  usecase "PyPI에 게시" as publish
  usecase "릴리스 만들기" as release
}

dev --> lint : Pushes code
dev --> test : Pushes code
dev --> release : Creates release
ga --> build : On release trigger
ga --> publish : After successful build
user --> publish : Downloads package

lint .> test : triggers
test .> build : on success
build .> publish : on success

@enduml
----

=== 배포 아키텍처

[plantuml, deployment-architecture, svg]
----
@startuml
!theme plain

cloud "깃헙" {
  [Source Repository]
  [GitHub Actions]
  [Secrets Store]
}

cloud "PyPI" {
  [Package Registry]
  [Distribution Files]
}

node "CI/CD 파이프라인" {
  [Linting]
  [Testing]
  [Building]
  [Publishing]
}

[Source Repository] --> [GitHub Actions] : Triggers
[GitHub Actions] --> [Linting]
[Linting] --> [Testing]
[Testing] --> [Building]
[Building] --> [Publishing]
[Publishing] --> [Package Registry] : Uploads
[Secrets Store] --> [Publishing] : Provides token

note as N1
  Secure token-based
  authentication
end note

[Secrets Store] .. N1

@enduml
----

== 감시와 디버깅

=== 로그 및 모니터링

GitHub Actions은 각 단계에 대한 자세한 로그를 제공합니다. 디버그하려면 :

1. **로그를 조사해 주세요**각각의 step
2. **디버그를 활성화하세요**와 함께`ACTIONS_STEP_DEBUG`
3. **아티팩트를 사용하세요**빌드 파일을 저장하기 위해

----

- name: Upload build artifacts uses: actions/upload-artifact@v3 if: failure() with: name: build-logs path: build/

=== 알림

슬랙 또는 이메일 알림을 추가하세요 :

```yaml
----
----
- name: Notify on failure
  if: failure()
  uses: 8398a7/action-slack@v3
  with:
    status: ${{ job.status }}
    webhook_url: ${{ secrets.SLACK_WEBHOOK }}
----

== 결론

GitHub Actions를 사용해 견고한 CI/CD 파이프라인을 구축하면 개발 경험이 근본적으로 바뀝니다. 린팅, 테스트, 배포를 자동화하면 :

- **오류를 줄이세요**생산 중 - **주기를 가속화하세요**개발 - **신뢰를 개선하세요**당신의 릴리스에서 - **협업을 원활하게 하세요**팀으로

이 파이프라인은 linting 도구, 테스트 프레임워크, 또는 배포 대상을 조정하여 다양한 유형의 Python 프로젝트에 맞출 수 있습니다.

이러한 워크플로우의 초기 설정 투자는 배포 과정에서의 시간 절감과 수작업 오류 감소 덕분에 빠르게 회수됩니다.

== 보충 자료

- https://docs.github.com/en/actions[GitHub Actions 문서] - https://packaging.python.org/[Python 패키징 가이드] - https://docs.pytest.org/[문서 Pytest] - https://docs.astral.sh/ruff/[Ruff 문서] - https://twine.readthedocs.io/[Twine 문서]

✅ 기능적인 파이프라인 도달! 이제 간단한 CI/CD 파이프라인이 있어 GitHub Actions에서 직접 테스트를 자동화하고 Python 패키지를 PyPI에 게시할 수 있습니다.

그러나 이 파이프라인은 의도적으로 최소한으로 유지됩니다. 아직 전문적인 맥락에서 필수적인 일부 측면을 다루지 않습니다 :

파이썬 멀티버전 테스트,

자동 보안 분석,

Test PyPI를 통한 점진적 배포,

파이프라인 모니터링 및 메트릭,

버전 관리 자동화 및 현대적인 관례 통합 (pyproject.toml).

다음 섹션에서는 한 단계 더 나아갈 것입니다. 이 기본 파이프라인을 실제 산업용 배포 파이프라인, 즉 강력하고 안전한 형태로 변환하는 방법을 배우게 될 것입니다. 이렇게 변환된 파이프라인은 프로덕션 Python 프로젝트에 바로 사용할 준비가 될 것입니다.
----

관련 기사