GitHub Actions

(github actions 관련 간단하게 발표한 내용입니다.)

1. About GitHub Actions

GitHub Actions란 무엇인가?

  • GitHub 플랫폼의 (CI/CD) 서비스

  • 프로젝트의 다양한 이벤트에 반응하여 자동화된 워크플로우를 실행할 수 있음

    • push / pull request 등 여러 event에 대응해 test, build, deploy 등 작업을 자동화

    CI/CD?


    https://www.redhat.com/ko/topics/devops/what-is-ci-cd

    • CI (Continuous Integration)
      • 변경 사항을 정기적으로 병합하고 자동으로 테스트하는 프로세스
      • 코드 품질을 유지하고 조기에 문제 발견
    • CD (Continuous Deployment)
      • 개발이 완료된 코드를 자동으로 배포하는 프로세스
      • 제품을 빠르게 배포하고 업데이트할 수 있음

GitHub Actions 이점

  1. 통합된 환경
    1. GitHub 플랫폼 내에서 바로 CI/CD 파이프라인 구성 가능 → 별도의 CI/CD 도구를 사용할 필요가 없음
    2. 일관된 환경을 제공하며, GitHub의 다른 기능들과도 쉽게 통합됨
  2. 유연성
    1. YAML로 구성된 워크플로우를 사용하여 다양한 작업을 수행 가능
  3. 확장성
    1. 여러 가지 공용 액션과 자체적으로 정의한 커스텀 액션을 사용하여 파이프라인을 확장할 수 있음
    2. 재사용 가능한 코드를 작성한 후에, 다른 프로젝트에서 쉽게 활용할 수 있음

GitHub Actions 왜 써야하죠?

  • 사용 예시

    • 프로젝트 CI/CD
      • Airflow 사용 시 여러분이 작성한 DAG 코드를 서버에도 배포해줘야 함 (GitHub Actions 외에도 sync 할 수 있는 다른 방식도 있으니 찾아보세용)
      • main branch에 merge하기 전에, pull request를 통해 코드 테스트 가능
    • 자동화(개인적인 용도)
  • 사용 이유

    • CI/CD 경험
      • 저도 작년 말, GitHub pages로 블로그를 만들기 위해 접하게 되었고 당시엔 CI/CD가 뭔지도 잘 몰랐습니다. 개념적으로는 알았어도 실제로 어떻게 하는지 아무것도 몰랐어요

      • 그런데 잘 몰라도 매번 프로젝트에 사용하니까 점점 익숙해졌고, 구현할 수 있는 수준이 점점 높아졌습니다.

      • 여러분이 접할 수 있는 가장 쉽고 간단한 CI/CD 구현 도구일거라고 생각합니다(아닐수도) 많이 써보세요!

    • script언어를 사용하는 경험
      • 대부분 ubuntu-latest 기반으로 코드를 작성할거라, script언어를 써보게 될 것
      • Data Engineer CLI 환경 많이 접해야 합니다. 익숙해지기 좋아요
      • 물론 실제 CLI와 다르지만 비슷하다는 의미입니다!

2. 기본 구조와 작동 방식

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# .github/workflows/github-actions-workflow-test.yaml

name: workflow name

**on:**
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

**jobs**:
  build:
    **runs-on: ubuntu-latest**
    ...

    **steps:**
    - uses: actions/checkout@v3
    
    - name: Set up Python
      uses: actions/setup-python@v3
      with:
        python-version: ...
        
    - name: Install Dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
                

워크플로우 workflow

  • 자동화된 절차 / .github/workflows 디렉토리에 있는 YAML 파일
  • 하나 이상의 job을 포함하며, GitHub 이벤트 또는 수동으로 trigger 될 수 있음

작업 job

  • 같은 runner에서 실행되는 일련의 단계
  • 각 작업은 워크플로우 파일 내의 runs-on 레이블로 지정된 인스턴스에서 실행됨

단계 step

  • job 내에서 명령을 실행하는 개별 작업으로, 스크립트 또는 Action을 사용할 수 있음
  • 동일한 job 내에서 순차적으로 하나씩 실행됨

러너 runner

  • 워크플로우를 실행하는 서버 machine
  • GitHub가 제공하는 runner (ubuntu-latest 등)을 사용하거나, 직접 machine을 구성하고 self-hosted-runner를 사용할 수도 있음

3. 사용 예시

1) 간단한 CI - lint & test

목표

  • 코드 lint & test
    • linting?
      • 프로그래밍에서 소스 코드를 분석하여 오류, 버그, 스타일 오류, 의심스러운 구조와 같은 문제점을 찾아내는 과정

상세 기능

  • main branchpush / pull request가 일어날 경우, 코드를 lint (수정 및 여러가지 오류 체크) 및 test 하기

workflow code

  • code

    https://github.com/EstateTrend-DevCourse/EstateTrend/blob/main/.github/workflows/django.yml

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    name: Django CI
    
    **on:**
      push:
        branches: [ "main" ]
      pull_request:
        branches: [ "main" ]
    
    jobs:
      build:
        **runs-on: ubuntu-latest**
        strategy:
          max-parallel: 4
          matrix:
            python-version: [3.12]
    
        **steps:**
        - uses: actions/checkout@v3
    
        - name: Set up Python ${{ matrix.python-version }}
          uses: actions/setup-python@v3
          with:
            python-version: ${{ matrix.python-version }}
    
        - name: Install Dependencies
          run: |
            python -m pip install --upgrade pip
            pip install -r requirements.txt        
    
        - name: Run Tests
          run: |
            python manage.py test        
    

2) CD 자동화

목표

  • **Airflow DAG파일**을 서버 환경으로 자동 배포

상세 기능

  • main branch로 push event가 일어날 경우, DAG 코드를 개발 서버로 sync
  • 서버를 self-hosted runner로 사용해 코드를 자동으로 서버에 받은 후, DAG 파일 위치로 copy하기
    • 서버환경과 연결하는 방법
      1. self-hosted-runner 사용
      2. GCP등 cloud sdk를 설치 후 VM에 script(git pull) 전송하여 실행
      3. 또 다른 방법이 있을수도?

workflow 코드

  • code

    https://github.com/zizzic/airflow_repo/blob/main/.github/workflows/sync_dag_code.yml

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    
    name: CI/CD Workflow
    
    on:
        push:
            branches: develop
        pull_request:
            branches: develop
    
    permissions:
        contents: read
        pull-requests: read
    
    jobs:
        path-filter:
            runs-on: ubuntu-latest
            outputs:
                dags-changed: ${{ steps.filter.outputs.dags }}
                docker-changed: ${{ steps.filter.outputs.docker }}
                plugin-created: ${{ steps.filter.outputs.plugin}}
            steps:
                - uses: actions/checkout@v4
    
                - name: Path Filter
                  id: filter
                  uses: dorny/paths-filter@v3
                  with:
                      filters: |
                          dags:
                            - 'dags/**/*.py'
                            - 'tests/dags/**/*.py' 
                          docker:
                            - 'docker-compose.yaml'
                            - 'Dockerfile'
                          plugin:
                            - 'plugins/***'                      
    
        build:
            needs: path-filter
            if: (needs.path-filter.outputs.dags-changed == 'true'|| needs.path-filter.outputs.plugin-created == 'true') && github.event_name == 'pull_request'
            runs-on: ubuntu-latest
            steps:
                - uses: actions/checkout@v4
    
                - name: Setup Python
                  uses: actions/setup-python@v5
                  with:
                      python-version: "3.11"
    
                - name: Set Environment Variables
                  run: |
                      echo "PYTHON_VERSION=3.11" >> $GITHUB_ENV
                      echo "AIRFLOW_VERSION=2.7.2" >> $GITHUB_ENV                  
    
                - name: Install dependencies
                  run: |
                      CONSTRAINT_URL="<https://raw.githubusercontent.com/apache/airflow/constraints-${AIRFLOW_VERSION}/constraints-${PYTHON_VERSION}.txt>"
                      echo "Installing Apache Airflow version ${AIRFLOW_VERSION} with constraints from ${CONSTRAINT_URL}"
                      pip install "apache-airflow[amazon,mysql]==${AIRFLOW_VERSION}" --constraint "${CONSTRAINT_URL}"
                      pip install pytest                  
    
                - name: Install Pylint
                  run: pip install pylint
                - name: Run Pylint
                  env:
                      PYTHONPATH: plugins
                  run: pylint --output-format=colorized --disable=missing-docstring,invalid-name,W,C0301,C0411 $(find dags/ tests/dags/ plugins/ -name "*.py") || true
                - name: Test DAG integrity
                  run: pytest tests/
    
        deploy:
            needs: path-filter
            if: github.event_name == 'push'
            runs-on: self-hosted
            steps:
                - name: Checkout code
                  uses: actions/checkout@v4
    
                - name: Extract repository name
                  run: echo "REPO_NAME=$(echo $GITHUB_REPOSITORY | cut -d '/' -f 2)" >> $GITHUB_ENV
    
                - name: Sync specific files
                  env:
                      DEPLOY_DIRECTORY: /home/zizzicteam2/${{ env.REPO_NAME }}
                  run: |
                      rsync -av --include='dags/***' --include='plugins/***' --include='docker-compose.yaml' --include='Dockerfile' --exclude='*' ./ $DEPLOY_DIRECTORY                  
                - name: Restart Docker Services
                  if: ${{ needs.path-filter.outputs.docker-changed == 'true' || needs.path-filter.outputs.plugin-created == 'true'}}
                  env:
                      DEPLOY_DIRECTORY: /home/zizzicteam2/${{ env.REPO_NAME }}
                  run: |
                      cd $DEPLOY_DIRECTORY
    
                      docker compose down
                      docker compose up -d                  
    

3) Issue 및 project 등 연계

목표

  • 알고리즘 문제 풀이 코드 복습을 위한 GitHub Issue 생성

상세 기능

  • 백준허브(https://github.com/BaekjoonHub/BaekjoonHub)가 내 repo에 코드를 올려주면(문제 설명 + 풀이 코드)
    1. 문제 이름을 title로 하는 Issue 생성
    2. 본문에 풀이 코드 링크
    3. 문제 분류를 label으로 추가해서 분류별 복습 가능
    4. 복습해서 다시 푼 경우 해당 Issue에 댓글로 추가하기 (중복 title Issue 새로 발행 X)

workflow 코드

  • code

    https://github.com/srlee056/algorithm-study/blob/main/.github/workflows/commit_to_issue.yaml

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    
    name: Convert Commit to Issue
    on:
        push:
            branches:
                - main
            paths:
                - '백준/**'
                - '프로그래머스/**'
        workflow_dispatch:
    
    jobs:
        create_issue:
            runs-on: ubuntu-latest
            steps:
                - name: Checkout repository
                  uses: actions/checkout@v4
                  with:
                      fetch-depth: 0 # 모든 커밋을 가져오기 위해
                - name: Create Issue for Commit
                  env:
                      GITHUB_TOKEN: ${{ secrets.ACC_TKN }}
                  run: |
                      git log ${{ github.event.before }}..${{ github.event.after }} --format='%H' | while read commit_hash; do
                        COMMIT_MESSAGE=$(git log -1 --format=%B $commit_hash)
                        COMMIT_TITLE=$(echo "$COMMIT_MESSAGE" | head -n 1)
                        COMMIT_BODY=$(echo "$COMMIT_MESSAGE" | awk '/^$/ {seen_blank=1; next} seen_blank {print}')
    
                        echo "Commit Title: $COMMIT_TITLE"
                        echo "Commit Body: $COMMIT_BODY"
                        echo "Files changed in this commit: $COMMITTED_FILES"
    
                        problem_title=$(echo "$COMMIT_TITLE" | awk -F 'Title: |,' '{print $2}')
    
                        if [[ -z "$problem_title" ]]; then
                            echo "This commit is not by algorithm solving."
                        elif [[ $COMMIT_TITLE == [* ]]; then
                            echo "문제: $problem_title"
                            echo "Creating issue for commit starting with ["
    
                            # remove double qoute using tr -d '"'
                            COMMITTED_FILES=$(git diff-tree --no-commit-id --name-only -r $commit_hash | tr -d '"')
                            echo $COMMITTED_FILES
    
                            # Initializing an empty string for the links
                            COMMITTED_FILES_LINKS=""
    
                            # Looping through each committed file to create a Markdown link
                            for file in $COMMITTED_FILES; do
                                decoded_file=$(printf "%b" "$file")
                                # Replace this with your GitHub repository details
                                file_url="<https://github.com/$GITHUB_REPOSITORY/blob/main/$decoded_file>"
                                COMMITTED_FILES_LINKS+="- [$decoded_file]($file_url)\\n"
                            done
    
                            for file in $COMMITTED_FILES; do
                                # Check if the file is a README.md in the specific path
                                decoded_file=$(printf "%b" "$file")
                                if [[ "$decoded_file" == 백준/*/*README.md ]]; then
                                    # Assuming $decoded_file holds the path to the README.md file
                                    README_CONTENT=$(gh api repos/:owner/:repo/contents/$decoded_file --jq '.content' | base64 --decode)
                                    echo $README_CONTENT
                                    # Now you can process README_CONTENT with awk or other tools as needed
                                    LABELS=$(echo $README_CONTENT | awk '{
                                        match($0, /### 분류 ([^#]+) ###/, arr);
                                        if (arr[1] != "") print arr[1];
                                    }')
    
                                    echo $LABELS
                                    # Convert labels into format suitable for `gh issue create` command
    
                                    GH_LABELS=$(echo $LABELS | tr ',' '\\n' | sed 's/ /-/g' | sed 's/^-//;s/-$//' | awk '{printf "--label \\"%s\\" ", $0}')
    
                                    echo $GH_LABELS
                                    break # Assuming only one README.md is relevant per commit
                                fi
                            done
    
                            # Use gh issue list and grep to find an issue by title
                            existing_issue=$(gh issue list --repo "$GITHUB_REPOSITORY" | grep -w "$problem_title" | awk '{print $1}')
    
                            if [[ ! -z "$existing_issue" ]]; then
                                echo "Issue already exists with title: $problem_title, Issue Number: $existing_issue"
                                # Create a comment on the existing issue
                                comment_body=$(echo -e "A new commit has been made that references this issue.\\n\\n**Committed Files:**\\n$COMMITTED_FILES_LINKS")
                                gh issue comment $existing_issue --body "$comment_body" --repo "$GITHUB_REPOSITORY"
                            else
                                # 먼저 현재 리포지토리의 모든 라벨을 가져옵니다.
                                existing_labels=$(gh label list --repo "$GITHUB_REPOSITORY" | cut -f1)
    
                                # 라벨이 존재하는지 확인하고, 없으면 생성합니다.
                                for label in $(echo $LABELS | tr ',' '\\n' | sed 's/ /-/g' | sed 's/^-//;s/-$//'); do
                                    # echo 명령을 사용하여 현재 처리 중인 라벨을 출력할 수 있습니다.
                                    echo "Processing label: $label"
    
                                    # 현재 라벨이 이미 존재하는 라벨 목록에 있는지 확인합니다.
                                    if ! grep -q "^$label$" <<< "$existing_labels"; then
                                        echo "Creating label: $label"
                                        gh label create "$label" --force --description "Automatically created label" --repo "$GITHUB_REPOSITORY"
                                    else
                                        echo "Label already exists: $label"
                                    fi
                                done
    
                                # No existing issue found, proceed to create a new issue
                                issue_body=$(echo -e "This issue was automatically generated from a commit.\\n\\n**Committed Files:**\\n$COMMITTED_FILES_LINKS")
                                issue_response=$(gh issue create --title "$problem_title" --body "$issue_body" --repo "$GITHUB_REPOSITORY" --assignee @me $GH_LABELS --label "auto-generated")
                                echo "New issue created: $issue_response"
                            fi
                        fi
                      done                  
    

4) Airflow CI/CD

같이 프로젝트 진행했던 장태수 님의 글입니다.

링크 참고해서 따라해보시면 좋을 것 같아서 가져왔어요!

Airflow Dag CI/CD 구축기 (1)

Licensed under CC BY-NC-SA 4.0
마지막 수정: May 10, 2024 06:18 UTC
Hugo로 만듦
JimmyStack 테마 사용 중