Skip to content

GitHub Actions

아이디어에서 생산까지의 워크 플로우 자동화

GitHub Actions를 사용하면 이제 세계적 수준의 CI/CD를 사용하여 모든 소프트웨어 워크 플로를 쉽게 자동화 할 수 있습니다. GitHub에서 바로 코드를 빌드, 테스트 및 배포하세요. 원하는 방식으로 코드 검토, 분기 관리 및 문제 분류 작업을 수행하십시오.

Categories

Actions Marketplace

Cache 제거 방법

다음 페이지로 가서 제거하자.

https://github.com/<OWNER>/<REPO>/actions/caches

Workflows

GitHub Actions에서 가장 상위 개념인 워크플로우(Workflow, 작업 흐름)는 쉽게 말해 자동화해놓은 작업 과정이라고 볼 수 있습니다. 워크플로우는 코드 저장소 내에서 .github/workflows 폴더 아래에 위치한 YAML 파일로 설정하며, 하나의 코드 저장소에는 여러 개의 워크플로우, 즉 여러 개의 YAML 파일을 생성할 수 있습니다.

이 워크플로우 YAML 파일에는 크게 2가지를 정의해야하는데요. #on과 #jobs 속성이 있습니다.

name

워크 플로우 이름

name: CI

on:
  # ...

jobs:
  # ...

Environment variables

name: Greeting on variable day

on:
  workflow_dispatch

env:
  DAY_OF_WEEK: Monday

services

미리 작동해야 하는 Background Service

# ...

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:11
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: postgres
        ports: ['5432:5432']
        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
    steps:
      # ...

on

on 속성으로 워크플로우가 언제 실행되는지를 정의합니다.

특정 브랜치 선택 방법

main 브랜치로 푸시하거나, releases/로 시작하는 분기로 푸시할 때 실행:

on:
  push:
    branches:
      - 'main'
      - 'releases/**'

jobs:
  # ...(생략)...

푸시가 특정 파일에 영향을 미치는 경우에만 워크플로 실행

예를 들어 이 워크플로는 JavaScript 파일(.js)에 변경 내용을 푸시할 때 실행됩니다:

on:
  push:
    paths:
      - '**.js'

jobs:
  # ...(생략)...

그 밖에 paths-ignore 필터도 있다.

특정 브랜치의 특정 파일 선택

AND 조건으로 생각하면 된다.

on:
  push:
    branches:
      - 'releases/**'
    paths:
      - '**.js'

특정 태그의 푸시가 발생하는 경우에만 워크플로 실행

예를 들어 이 워크플로는 v1.으로 시작하는 태그를 푸시할 때 실행됩니다.

on:
  push:
    tags:
      - v1.**

그 밖에 tags-ignore 필터도 있다.

만약 major version 이 1 또는 2 인 경우를 필터링 하고 싶다면 v[12].[0-9]+.[0-9]+ 라고 하면 된다.

일반적인 semantic version 패턴은:

on:
  push:
    tags:
      - '[0-9]+.[0-9]+.[0-9]+'

cron 스케줄러

다른 예로, 매일 자정에 워크플로우를 실행하려면 다음과 같이 설정합니다.

on:
  schedule:
    - cron: "0 0 * * *"

jobs:
  # ...(생략)...

특정 워크플로우 종속

"tests" 워크플로우가 완료되었을 경우:

on:
  workflow_run:
    workflows: ["tests"]
    branches: [main]
    types:
      - completed

jobs:
  # ...(생략)...

Web 에서 수동 배포할 경우

"Create a new release" 버튼을 클릭하여 새로운 릴리즈 페이지를 생성하면 이벤트가 생성된다.

on:
  release:
    types: [published]

jobs:
  # ...(생략)...

전체 이벤트 트리거는 이 곳을 참조.

Filter pattern cheat sheet

You can use special characters in path, branch, and tag filters.

  • *: Matches zero or more characters, but does not match the / character. For example, Octo* matches Octocat.
  • **: Matches zero or more of any character.
  • ?: Matches zero or one of the preceding character.
  • +: Matches one or more of the preceding character.
  • [] Matches one character listed in the brackets or included in ranges. Ranges can only include a-z, A-Z, and 0-9. For example, the range[0-9a-z] matches any digit or lowercase letter. For example, [CB]at matches Cat or Bat and [1-2]00 matches 100 and 200.
  • !: At the start of a pattern makes it negate previous positive patterns. It has no special meaning if not the first character.

The characters *, [, and ! are special characters in YAML. If you start a pattern with *, [, or !, you must enclose the pattern in quotes. Also, if you use a flow sequence with a pattern containing [ and/or ], the pattern must be enclosed in quotes.

# Valid
paths:
  - '**/README.md'

# Invalid - creates a parse error that
# prevents your workflow from running.
paths:
  - **/README.md

# Valid
branches: [ main, 'release/v[0-9].[0-9]' ]

# Invalid - creates a parse error
branches: [ main, release/v[0-9].[0-9] ]

jobs

jobs 속성으로 워크플로우가 어떻게 실행되는지를 정의합니다.

GitHub Actions에서 작업(Job)이란 독립된 가상 머신(machine) 또는 컨테이너(container)에서 돌아가는 하나의 처리 단위를 의미합니다. 하나의 워크플로우는 적어도 하나 이상의 작업으로 구성됩니다. 그리고 모든 작업은 기본적으로 동시에 실행되며 필요 시 작업 간에 의존 관계를 설정하여 작업이 실행되는 순서를 제어할 수도 있습니다.

작업은 워크플로우 YAML 파일 내에서 jobs 속성을 사용하며 작업 식별자(ID)와 작업 세부 내용 간의 맵핑(mapping) 형태로 명시가 되는데요.

가장 널리 사용되는 우분투의 최신 실행 환경에서 해당 작업을 실행하고 싶다면 다음과 같이 설정합니다.

jobs:
  job1:
    runs-on: ubuntu-latest
    steps:
      # ...(생략)...

steps

정말 단순한 작업이 아닌 이상 하나의 작업은 일반적으로 여러 단계의 명령을 순차적으로 실행하는 경우가 많죠? 그래서 GitHub Actions에서는 각 작업(job)이 하나 이상의 단계(step)로 모델링이 되는데요.

작업 단계는 단순한 커맨드(command)나 스크립트(script)가 될 수도 있고 다음 섹션에서 자세히 설명할 액션(action)이라고 하는 좀 더 복잡한 명령일 수도 있습니다. 커맨드나 스크립트를 실행할 때는 run 속성을 사용하며, 액션을 사용할 때는 uses 속성을 사용합니다.

# ...(생략)...

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm install
      - run: npm test

워크플로우 파일 내에서 작업 단계를 명시해줄 때는 주의할 부분이 있는데요. YAML 문법에서 시퀀스(sequence) 타입을 사용하기 때문에 각 단계 앞에 반드시 -를 붙여줘야 합니다.

Actions

마지막으로 살펴볼 개념은 GitHub Actions의 꽃이라고 볼 수 있으며 서비스 이름에도 들어있는 바로 액션(Action)입니다. 액션은 GitHub Actions에서 빈번하게 필요한 반복 단계를 재사용하기 용이하도록 제공되는 일종의 작업 공유 메커니즘인데요. 이 액션은 하나의 코드 저장소 범위 내에서 여러 워크플로우 간에서 공유를 할 수 있을 뿐만 아니라, 공개 코드 저장소를 통해 액션을 공유하면 GitHub 상의 모든 코드 저장소에서 사용이 가능해집니다.

GitHub에서 제공하는 대표적인 공개 액션으로 바로 위 예제에서도 사용했던 체크 아웃 액션(actions/checkout)을 들 수 있는데요. 대부분의 CI/CD 작업은 코드 저장소로 부터 코드를 작업 실행 환경으로 내려받는 것으로 시작하므로 이 액션이 얼마나 범용적으로 사용될지는 굳이 말씀을 안 드려도 상상이 가시죠?

GitHub Marketplace 에서는 수많은 벤더(vendor)가 공개해놓은 다양한 액션을 쉽게 접할 수가 있는데요. 한 마디로 이 액션을 중심으로 하나의 큰 커뮤니티가 형성이 되고 더 많은 사용자와 벤더가 GitHub Actions으로 몰려드는 선순환이 일어나고 있습니다.

runs-on

Tag 에서만 실행되는 Workflow 연결

You can turn your CD workflow into action and run it as part of your CI with condition: .github/actiond/cd/action.yml:

name: CD
description: Run CD
runs:
  using: composite
  steps:
    - run: echo "Success!"
      shell: bash

CI:

name: CI

on:
  push:
    tags: v[1-9]+.[0-9]+.[0-9]+
  pull_request:
    branches: [develop, hotfix*]

jobs:
  sucess:
    name: Log success
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: echo "Success!"
      - name: Run CD
        if: github.event_name == 'push' && contains(github.event.ref, '/tags/')
        uses: ./.github/actions/cd

Have it as a separate job that is dependant on CI job using needs option

Converting it to action makes for better encapsulation IMO although requires some work.

자동 토큰 인증

원하는 곳에 ${{ secrets.GITHUB_TOKEN }} 를 사용하면 된다.

Service containers

name: Redis Service Example
on: push

jobs:
  # Label of the container job
  runner-job:
    # You must use a Linux environment when using service containers or container jobs
    runs-on: ubuntu-latest

    # Service containers to run with `runner-job`
    services:
      # Label used to access the service container
      redis:
        # Docker Hub image
        image: redis
        #
        ports:
          # Opens tcp port 6379 on the host and service container
          - 6379:6379

Tag 참조

#on에서 추가하고 싶다면:

on:
  push:
    tags:
      - 'v*'

조건에 추가하고 싶다면:

if: startsWith(github.event.ref, 'refs/tags/v')

Postgres Test

This is a good starting point for getting Python, Django, Postgres running as a service, pytest, black, and pip caching rolling with GitHub Actions.

python-django-postgres-ci.yml

name: CI

on: [push]

jobs:

  test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:11
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: postgres
        ports: ['5432:5432']
        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

    steps:
      - uses: actions/checkout@v1
        with:
          fetch-depth: 1

      - name: Set up Python 3.7
        uses: actions/setup-python@v1
        with:
          python-version: 3.7

      - uses: actions/cache@v1
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
          restore-keys: |
            ${{ runner.os }}-pip-
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          python -m pip install -r requirements.txt
        if: steps.cache.outputs.cache-hit != 'true'

      - name: Test with black
        run: |
          python -m black --check .
      - name: Test with pytest
        env:
          DATABASE_URL: 'postgres://postgres:postgres@localhost:${{ job.services.postgres.ports[5432] }}/postgres'
        run: |
          python -m pytest

z-docker-publish.yml

name: docker-publish

on:
  push:
    branches:
      - master

jobs:

  docker:
    name: Build and Publish Docker image
    runs-on: ubuntu-latest

    steps:
      - name: Git - Get Sources
        uses: actions/checkout@v1
        with:
          fetch-depth: 1

      - name: Docker - Build
        run: |
          docker build . --file .docker/Dockerfile --tag changeme-web
      - name: Docker - Login
        run: |
          echo ${{ secrets.GITHUB_TOKEN }} | docker login docker.pkg.github.com --username ${{ github.actor }} --password-stdin
      - name: Docker - Tag
        run: |
          docker tag changeme-web docker.pkg.github.com/${{ github.repository }}/changeme-web:latest
      - name: Docker - Push
        run: |
          docker push docker.pkg.github.com/${{ github.repository }}/changeme-web:latest

Docker Image Build and Push

Self Hosted Runners

local caching 을 통한 github action의 속도 향상

This action allows caching dependencies and build outputs to improve workflow execution time on self hosted machine.

github에서 git action을 활용하여 CI/CD pipeline을 돌리는 분들이 많을 겁니다. 빠른 빌드를 위해 cache를 많이 사용하는데, 처음에는 크게 문제가 없지만 서비스가 커지거나 브랜치가 많아지면 캐싱이 안 되고 매번 다시 빌드되어 속도가 느려지기 시작합니다. 깃헙 액션의 기본 캐싱은 10GB 크기 제한이 있기 때문입니다.

이 문제를 해결하기 위해 self-hosted runner에서 로컬에 캐싱할 수 있는 패키지를 개발했습니다.

      - name: Cache node dependencies  
        id: node-cache  
        uses: corca-ai/local-cache@v2  
        with:  
          path: node_modules  
          key: node-${{ hashFiles(‘yarn.lock’) }}  
          clean-key: node-  

github-hosted runner 대신 self-hosted runner를 이용하고, 기존 cache를 사용하던 부분에서 uses 부분만 corca-ai/local-cache로 변경하면 바로 사용 가능합니다. 캐시의 크기 제한이 사라지고, 기존 캐싱은 네트워크를 통해 캐시 파일을 불러오는 것에 반해 로컬 캐싱은 디스크에서 바로 로드하기 때문에 캐싱 속도가 획기적으로 향상됩니다. 저희는 5배 이상의 속도 향상을 체감하게 되었습니다.

See also

Favorite site

Articles