8 minute read

이 블로그 포스트의 내용은 2022 Open Source Contributon Challenges 기간 워크샵 내용을 정리한 것입니다.

2022 OSSC 4th meetup note

Today’s Topic: Github Actions

terraform, pulumi 등 다른 CI/CD 를 위한 IaaS 를 위한 도구들도 있다.

Continuous Deployment

Continuous Deployment(지속적 배포): 소프트웨어의 기능이 자동화된 배포를 통해 자주 제공되는 것을 말한다. 이것은 수동 릴리스를 통해 언제든지 배포할 수 있는 소프트웨어를 제공하는 기능이다. 이는 자동화된 배포를 사용하는 연속적전달과는 대조된다. Martin Fowler 에 따르면, 지속적인 배포에는 지속적 전달이 필요하다.

Continuous Delivery

Continuous Delivery(지속적 전달): 소프트웨어를 릴리스 할 때, 수동으로 수행하지 않고도 언제든지 안정적으로 릴리스 할 수 있도록 한다. 더 빠른 속도와 빈도로 소프트웨어를 빌드, 테스트 및 릴리스하는 것을 목표로한다. 이는 프로덕션 환경의 애플리케이션에 대한 더 많은 증분 업데이트를 허용하여, 변경 사항을 제공하는 데 드는 비용, 시간 및 위협을 줄이는데 도움이 된다. 지속적 배포를 위해서는 간단하고 반복 가능한 배포 프로세스가 중요하다.

Continuous Integration

Continuous Integretion(지속적 통합)

모든 개발자의 작업 복사본을 하루에 여러 번 공유 메인라인에 병합하는 방식입니다. 변경을 시작할 때, 개발자는 작업할 현재 코드 기반의 복사본을 가져옵니다. 다른 개발자가 변경된 코드를 소스 코드 리포지토리에 제출하면, 이 복사본이 리포지토리 코드를 반영하지 않게 됩니다. 기존 코드 기반이 변경될 수 있을 뿐만 아니라, 종속성 및 잠재적 충돌을 생성하는 새 라이브러리 및 기타 리소스 뿐만 아니라 새 코드가 추가될 수 있습니다.

따라서 코드를 제출하기 전에, 먼저 기존 리포지토리의 변경사항을 반영하도록 코드를 업데이트해야합니다.

Workflow of CI

  • 로컬에서 테스트 실행
  • CI에서 코드 컴파일
  • CI에서 테스트 실행
  • CI에서 아티팩트 배포

CI 와 CD를 구분하는 것이 중요하다

깃허브 액션은 CI, CD 를 구분할 수 있는 도구이다.

이벤트 기반으로 액션이 발생하며, issue, PR 등 다양한 이벤트에 대해 동작을 실행할 수 있다.

수동으로도 워크플로우를 실행할 수 있다!

개발환경 설정하기

다음의 두 vscode 확장 프로그램을 설치합니다.

YAML - Visual Studio Marketplace

GitHub Actions - Visual Studio Marketplace

기본 워크 플로우 파일 생성 방법

  1. mkdir .github/
  2. mkdir .github/workflows
  3. touch main.yaml

워크플로우를 정의하는것 을 깃허브 액션이라고도한다.

액션에서 job이 생성되면,

job은 github 클라우드의 큐에 들어가고, 랜덤하게 가상머신을 잡아서 실행된다.

가상머신의 성능도 모두 제각각이라, 어떤 작업이 먼저 끝날지도 알 수 없다.

시간 종속성을 설정해 줄 수도 있다. 예를 들어, 빌드 가 반드시 끝난 후에 릴리스 를 해야하는 경우가 있을 수 있다.

워크플로우 파일 작성하기

기본 문법

name: 'My First GitHub Actions' # 워크플로우 이름을 지정

on: push # 어떤 이벤트에서 액션이 트리거 될지를 지정. 여러개의 이벤트를 지정할 수도 있다.

env: # 환경 변수를 지정. 각 환경 변수는 선언된 블록에서 스코프를 가진다. 상위 스코프에서 선언된 환경 변수는 하위 스코프로 전달된다.
  WORKFLOW_LEVEL: "This value comes from the WORKFLOW level"

jobs: # 어떤 일을 수행할지를 지정. 여러개의 일을 수행할 수도 있다.
  first-job: # 일을 정의
    name: 'First Job' # 일의 이름

    runs-on: ubuntu-latest # 어떤 운영체제에서 실행될지를 지정. # 여러개의 운영체제를 지정할 수도 있다.

    steps: # 일의 실행 단계를 지정.
    - name: Say Hello World 1 # 단계의 이름을 지정
      shell: bash # 단계의 명령 실행 환경을 지정
      run: | # 단계에서 실행할 명령을 지정. 여러개의 명령을 실행할 수도 있다.
        echo "Hello World from step 1"

    - name: Say Hello World 2
      shell: pwsh
      run: |
        echo "Hello World from step 2"

런타임에서 환경 변수 선언

name: 'My First GitHub Actions'

on: push

env:
  PRESET_VALUE: "This is the preset value"

jobs:
  first-job:
    name: 'First Job'

    runs-on: ubuntu-latest

    steps:
    - name: Set environment variable 1
      shell: bash
      run: | # github 환경변수를 런타임에 추가한다.
        echo "STEPSET_VALUE_1='This is the value 1 set in the step'" >> $GITHUB_ENV

		- name: Check environment variables
      shell: bash
      run: | # github 문법을 사용해 환경변수를 사용할 수 있다.
        echo "preset value: $"
        echo "stepset value 1: $"

참조가능한 값 내보내기

name: 'My First GitHub Actions'

on: push

jobs:
  first-job:
    name: 'First Job'

    runs-on: ubuntu-latest

    steps:
    - name: Set output value
      id: first # 참조 가능하도록 id를 지정해준다.
      shell: bash
      run: | # 내보낼 값을 설정한다.
        first_value="This is the value for the first output"

        echo "::set-output name=first_value::$first_value"

    - name: Check output value
      shell: bash
      run: | # id를 통해 내보내진 값을 참조할 수 있다.
        echo "first value: $"

echo "::add-mask::$first_value" 을 값을 내보내기 전에 추가하면,

중요 정보가 안보이게 마스킹을 할 수도 있다. → job이 실행중일 때는 참조 가능하나, 외부로 노출될 때(로그에 찍힐 때 등)에는 ***로 가려진다.

Github Secrets 사용하기

Github의 리포지토리 세팅에서, secrets 에 값을 추가하고, 이를 참조하는 것으로 비밀 정보를 사용할 수 있다.

name: 'My First GitHub Actions'

on: push

jobs:
  first-job:
    name: 'First Job'

    runs-on: ubuntu-latest

    steps:
    - name: Show secret
      shell: bash
      run: | # github secrets의 값을 참조한다.
        echo $

    - name: Check secret
      shell: pwsh # 파워쉘을 사용한다.
      run: | # 이와 같이 조건문을 추가할 수도 있다. 
        if ("$" -eq "World") {
            echo "YES!"
        } else {
            echo "NO!"
        }

Matrix 사용하기

Matrix를 사용하여, 같은 작업을 코드를 중복하지 않고 여러버전의 언어, 혹은 여러 운영체제의 가상머신에서 실행할 수 있다. 다음의 job에서는 3개의 운영체제와 2개의 nodejs 런타임 버전에서 총 6번 실행된다.

name: 'My First GitHub Actions'

on: push

jobs:
  first-job:
    name: 'First Job'

    strategy: # 매트릭스 전략을 선언한다.
      matrix: # 매트릭스를 정의한다. 하나 이상의 원소를 갖는 배열을 정의한다.
        os: [ 'windows-latest', 'macos-latest', 'ubuntu-latest' ]
				nodejs: [ '14.x', '16.x' ]

    runs-on: $ # 매트릭스에서 정의된 운영체제의 값을 참조한다.

    steps:
    - name: Setup node.js
      uses: actions/setup-node@v3
      with:
        node-version: $

    - name: Check node.js version from $
      shell: bash
      run: |
        echo $(node --version) # 설치된 nodejs의 버전을 확인한다.

작업에 조건문 달기

원하는 조건에서만 작업을 실행하도록 조건을 설정할 수 있다.

name: 'My First GitHub Actions'

on: [ 'push', 'pull_request' ] # 2개의 이벤트에서 작업이 실행되도록 설정한다.

jobs:
  first-job:
    name: 'First Job'
    if: github.event_name == 'push' # 이벤트가 push일 때만 작업을 실행한다.

    strategy:
      matrix:
        os: [ 'windows-latest', 'macos-latest', 'ubuntu-latest' ]

    runs-on: $

    steps:
    - name: Say Hello World from $
      shell: bash
      run: |
        echo "Hello World on $"

  second-job:
    name: 'Second Job'
    if: github.event_name == 'pull_request' # 이벤트가 pull_request일 때만 작업을 실행한다.

    strategy:
      matrix:
        os: [ 'windows-latest', 'macos-latest', 'ubuntu-latest' ]

    runs-on: $

    steps:
    - name: Say Hello World from $
      shell: bash
      run: |
        echo "Hello World on $"

스텝에 조건문 달기

원하는 조건에서만 스텝을 실행하도록 조건을 설정할 수 있다.

name: 'My First GitHub Actions'

on: push

jobs:
  first-job:
    name: 'First Job'

    strategy:
      matrix:
        os: [ 'windows-latest', 'macos-latest', 'ubuntu-latest' ]

    runs-on: $

    steps:
    - name: Say Hello World from $ 1
      shell: bash
      run: |
        echo "Hello World on $ 1"

    - name: Say Hello World from $ 2
      if: matrix.os == 'ubuntu-latest' # os가 ubuntu-latest일 때만 스텝을 실행한다.
      shell: bash
      run: |
        echo "Hello World on $ 2"

작업 체이닝

작업이 특정 순서로 실행되도록 설정해줄 수 있다.

name: 'My First GitHub Actions'

on:
  push:
    branches:
    - main

jobs:
  first-job:
    name: 'First Job'

    runs-on: ubuntu-latest

    steps:
    - name: Say Hello World from first job
      shell: bash
      run: |
        echo "Hello World from first job"

  second-job:
    name: 'Second Job'
    needs: first-job # second-job은 반드시 first-job이 실행된 후에만 실행된다.

    runs-on: ubuntu-latest

    steps:
    - name: Say Hello World from second job
      shell: bash
      run: |
        echo "Hello World from second job"

이벤트 추가하기

name: 'My First GitHub Actions'

on: # push이고, branch가 main일때, push이고, v로 시작하는 태그일때, pull_request이고, branch가 main일때 실행된다.
  push:
    branches:
    - main
    tags:
    - 'v*'
  pull_request:
    branches:
    - main

jobs:
  build:
    name: Build Apps

    runs-on: ubuntu-latest

    steps:
    - name: Checkout the repo
      uses: actions/checkout@v2 # 재사용 가능한 오픈소스 워크플로우를 사용한다.

    - name: Setup .NET SDK
      uses: actions/setup-dotnet@v1
      with: # 재사용 가능한 워크플로우에 필요한 값을 넘겨준다.
        dotnet-version: '6.x'

    - name: Restore NuGet packages
      shell: bash
      run: |
        dotnet restore ./api

    - name: Build solution
      shell: bash
      run: |
        dotnet build ./api -c Release

    - name: Create FunctionApp artifact
      shell: bash
      run: |
        dotnet publish ./api -c Release -o ./api/bin/published

    - name: Upload FunctionApp artifact
      uses: actions/upload-artifact@v2
      with:
        name: apiapp
        path: api/bin/published

  release:
    name: Release Apps
    needs: build

    runs-on: ubuntu-latest

    steps:
    - name: Download FunctionApp artifact
      uses: actions/download-artifact@v2
      with:
        name: apiapp
        path: artifacts/api

    - name: Zip FunctionApp artifact
      shell: bash
      run: |
        pushd artifacts/api
        zip -qq -r apiapp.zip .
        popd

        mv artifacts/api/apiapp.zip artifacts/apiapp.zip

    - name: Release FunctionApp artifact to GitHub
      uses: "marvinpinto/action-automatic-releases@latest"
      with:
        repo_token: "$"
        prerelease: false
        files: |
          artifacts/apiapp.zip

수동으로 워크플로우 실행하기

수동으로 워크플로우를 입력과 함께 실행할 수 있다.

name: 'Manual Release'

on:
  workflow_dispatch: # 수동 워크플로우 실행을 정의한다.
    inputs: # 어떤 입력을 받을 것인지 정의한다.
      title:
        type: string
        required: true
        description: Enter the release title

jobs:
  build:
    name: Build Apps

    runs-on: ubuntu-latest

    steps:
    - name: Checkout the repo
      uses: actions/checkout@v2

    - name: Setup .NET SDK
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: '6.x'

    - name: Restore NuGet packages
      shell: bash
      run: |
        dotnet restore ./api

    - name: Build solution
      shell: bash
      run: |
        dotnet build ./api -c Release

    - name: Create FunctionApp artifact
      shell: bash
      run: |
        dotnet publish ./api -c Release -o ./api/bin/published

    - name: Upload FunctionApp artifact
      uses: actions/upload-artifact@v2
      with:
        name: apiapp
        path: api/bin/published

  release:
    name: Release Apps
    needs: build

    runs-on: ubuntu-latest

    steps:
    - name: Download FunctionApp artifact
      uses: actions/download-artifact@v2
      with:
        name: apiapp
        path: artifacts/api

    - name: Zip FunctionApp artifact
      shell: bash
      run: |
        pushd artifacts/api
        zip -qq -r apiapp.zip .
        popd

        mv artifacts/api/apiapp.zip artifacts/apiapp.zip

    - name: Release FunctionApp artifact to GitHub
      uses: "marvinpinto/action-automatic-releases@latest"
      with:
        repo_token: "$"
        prerelease: false
        title: $ # 워크플로우 실행시 입력받은 값을 참조한다.
        files: |
          artifacts/apiapp.zip

재사용 가능한 로컬 워크플로우 호출하기

재사용 가능한 워크플로우를 분리하고, 원할 때 호출하여 사용할 수 있다.

name: 'My First GitHub Actions'

on:
  push:
    branches:
    - main
    tags:
    - 'v*'
  pull_request:
    branches:
    - main

jobs:
  call_build:
    uses: ./.github/workflows/ci.yaml # 로컬 워크플로우 호출
    with:
      artifact_name: apiapp

  call_release:
    uses: ./.github/worlfows/cd.yaml # 로컬 워크플로우 호출
    needs: call_build
    with:
      title: latest
      artifact_name: apiapp
    secrets: inherit # 호출하는 워크플로우의 secrets를 호출된 워크플로우로 상속한다.

다중 단계 배포

여러 단계를 거치는 배포 작업을 환경 변수를 구분하고, 워크플로우를 호출하는 것으로 구성할 수 있다.

name: 'My First GitHub Actions'

on:
  push:
    branches:
    - main
    tags:
    - 'v*'
  pull_request:
    branches:
    - main

jobs:
  call_build:
    uses: ./.github/workflows/ci.yaml
    with:
      artifact_name: apiapp

  call_release_dev: # 개발 릴리스 워크플로우는 빌드 워크플로우가 먼저 호출된뒤에 호출된다.
    uses: ./.github/worlfows/cd.yaml
    needs: call_build
    with:
      title: latest
      artifact_name: apiapp
      env: DEV # 환경변수를 입력으로 받아 개발 단계와 제품 단계를 구분한다.
    secrets: inherit

  call_release_prod: # 제품 릴리스 워크플로우는 개발 릴리스 워크플로우가 먼저 호출된뒤에 호출된다.
    uses: ./.github/worlfows/cd.yaml
    needs: call_release_dev
    with:
      title: latest
      artifact_name: apiapp
      env: PROD
    secrets: inherit

HW

멘토님 실습 리포지토리에서 step 4, 5 실습해보고 레포에 올리고 링크 남기기

https://github.com/devkimchi/github-actions-from-scratch

참고

Continuous delivery - Wikipedia

Continuous integration - Wikipedia

Continuous deployment - Wikipedia