업데이트중
#1. 220916
Jenkins도 아니고 왜 Github Action을 사용하느냐??
** 수많은 CI 중 Github Action을 하는 이유는 몇가지가 있다.
- GitHub과 하나로 통일된 환경에서 CI 수행이 가능
- 중앙에서 관리하는 GitHub Actions Runner에 지속적으로 트러블슈팅하여 원활한 CI 환경 구성이 가능
- 프로젝트마다 개별 Runner를 통한 빌드 테스트가 가능
- GitHub Actions Runner 기동을 하기 위해 친숙한 문법의 YAML 파일로 간단하게 파이프라인 구성 가능
핵심적인 이유는 1이겠다. GitHub을 쓰면서 다른 CI를 쓸 바에.. 그냥 쓰겠다라는 말이다.
??? GitLab도 있는데 뭐야 왜 그거 안해????
사실 GitLab, GitHub Action 다 다르다.
그렇다면 GitLab과 GitHub Action은 뭔 차이가 있을까?
위 이미지만 보면 GitLab이 좋아보이는 것은 사실이다.
하지만 CI/CD는 무엇으로도 가능하다. GitHub Action도 가능하다.
무엇보다도 가볍고 개인 프로젝트하기에 편하다고 해서 쓴다.
어차피 한 번 써보면 다 비슷비슷할 것이라고 생각한다.
해보자
https://docs.github.com/en/actions/quickstart
위 링크로 시작하면 된다.
workflow를 만드는 yaml 파일을 파헤쳐보자
뭐 처음부터 모든 것을 알 수는 없겠지만 어떤 식인지 맛만보자
name: 'Link Checker: All English' # 워크플로우, workflow의 이름으로 => Github의 action에서 볼 수 있다.
#일반적으로 여기에다는 이 workflow가 왜 필요하고 하는 역할이 무엇인지, 서술한다.
# **What it does**: Renders the content of every page and check all internal links.
# **Why we have it**: To make sure all links connect correctly.
# **Who does it impact**: Docs content.
#on 키워드는 어떤 trigger로 이 workflow를 run하게 할지 정하는 것이다.
# 아래에서는 3가지가 있다.
# 1. workflow_dispatch가 일어날 때 => UI이용해서 run할 때 쓰는 것이다.
# 2. push가 일어날 때 (어떠한 commit이든 main에 push가 일어날 때) => 다른 특정 브랜치를 지정할 수도 있다.
# 3. pull_request가 일어날 때: => 즉, pr이 만들어지거나 업데이트될 때 실행된다.
on:
workflow_dispatch:
push:
branches:
- main
pull_request:
#permission 키워드는 token에 의해서 실행되냐 안되냐를 의미한다. => pr을 하자마자 실행할 필요는 없다.
permissions:
contents: read
# Needed for the 'trilom/file-changes-action' action
pull-requests: read
#concurrency는 workflow의 run 작업을 큐잉하여 이전 작업에 방해받지 않게 하는 것이다.
#아래 나온 group이란 것은 어떤 workflow에 대해서 concurrency를 설정할 지 정한다. 뒤에 있는 ||은 fallback을 정하는 값이다.
#cancel-in-progress란 같은 그룹에 있는 작업이 진행될 때는 cancel시키는 것을 말한다.
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
cancel-in-progress: true
#워크 플로에서 실행되는 모든 작업들을 모아놓은 것이다.
#이게 shell script라고 보면 된다. ★★★★★★★★★★
jobs:
#job을 ID와 함께 저장하는 것이다. 즉, 여기서는 job ID가 check-links라는 것을 의미한다. (jobs에 key로 저장된다)
check-links:
#runs-on이란 여기서는 self-hosted에서 실행하겠다는 말이다.
#runs-on에도 조건을 붙일 수 있는데 뒤의 []이다. 즉, github의 repository의 경로가 github/docs-internal일 때 실행된다는 것이다.
#만약 path가 틀리다면 ubuntu-latest로 실행된다. 얘는 GitHub에 의해서 실행된다.
runs-on: ${{ fromJSON('["ubuntu-latest", "self-hosted"]')[github.repository == 'github/docs-internal'] }}
#steps는 실행되는 순서를 의미한다. 각 job은 고유한 steps를 가진다.
steps:
#uses 키워드 작업을 하도록 하는데 actions/checkout 이라는 행동을 하게하는 것이다.
#그 작업은 runner에게 repo를 checkout하고 해당 위치에서 download를 하게 한다.
# checkout 하는 이유는 당연히 원본은 건들지 않고 작업하기 위함이다.
#대개 workflow에서 코드의 실행이 있거나 repo에 정의된 작업을 하기 위해서 checkout을 먼저 진행한다.
- name: Checkout
uses: actions/checkout@v3
#여기서는 역시 uses로 정의된 action을 실행한다. => Node.js 패키지를 설치하는 작업이다.
#이렇게 되면 runner에서 npm을 실행할 수 있게 된다.
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 16.13.x
cache: npm
#run은 runner에서 실행하게 하는 키워드이다.
#즉, npm ci가 runner에서 실행된다.
- name: Install
run: npm ci
# Creates file "$/files.json", among others
# uses키워드를 사용하여 trilon/file-changes-action을 또 실행하게 된다. => 뒤에 붙은 것 특정 버전의 파일만을 위한 것으로 SHA를 이용했다.
# 수정된 모든 파일들 모으는 작업을 한다.
- name: Gather files changed
uses: trilom/file-changes-action@a6ca26c14274c33b15e6499323aac178af06ad4b
with:
fileOutput: 'json'
# For verification
# 위에서 생긴 files.json을 workflow의 run's log에서 볼 수 있다.
# debugging할 때 쓴다.
- name: Show files changed
run: cat $HOME/files.json
#run 역시 runner에서 명령어를 실행하기 위함이다.
#여기서는 .script/rendered-content-link-checker.mjs 에서 실행하는 것이다.그리고 parameter를 전달해준다.
- name: Link check (warnings, changed files)
run: |
./script/rendered-content-link-checker.mjs \
--language en \
--max 100 \
--check-anchors \
--check-images \
--verbose \
--list $HOME/files.json
#여기도 마찬가지다. parameter가 다를 뿐
- name: Link check (critical, all files)
run: |
./script/rendered-content-link-checker.mjs \
--language en \
--exit \
--verbose \
--check-images \
--level critical
여기서도 솔직히 뭘 하는지는 알겠는데
개념들이 머리에 없어서 다 이해가지는 않는다.
아래에서 자주 쓰이는 개념을 알아보자
Workflows
워크플로 자체는 하나 이상의 작업을 실행하는 자동화 프로세스를 말한다.
다시 말해서 워크플로를 만들면 어떠한 자동화 작업을 진행할 수 있는 것이다.
이러한 워크플로는 Event로 인해 Tirgger되거나 수동으로 Trigger 된다.
Events
워크플로를 실행하는 레포지토리의 특정 동작을 말한다.
예를 들면 PR이나 Push가 있다.
더 나아것 REST API 로도 Trigger가 될 수 있다고 한다.
그러한 이벤트는 겁나 많다. (https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#branch_protection_rule)
필요한 Event는 위 링크에서 찾아보자
Jobs
워크플로우에 자동화 프로세스를 steps으로 정리한 것을 모아둔 것이다.
각 step들은 shell script로 구성되어 있어 실행된다. 아니면 미리 지정된 action이 실행된다.
**당연히 각 단계는 동일한 runner에서 실행되므로 이전의 결과에 대해 다음 step에서 사용이 가능하다.
기본적으로는 작업에는 종속성이 없다.
**하지만 필요하다면\ 다른 job과도 종속성을 구성할 수 있다.
서로 병렬로 실행되어 작업이 다른 작업이 끝날 떄까지 대기하고 완료되고 나서야 실행시킬 수 있다.
=> 종속성이 있다는 것이 의외로 장점이 될 때가 많다.
Actions
여기서 Action은 자주 반복되는 GitHub Actions 전용 프로그램을 말한다.
즉, 모든 Shell script 작성이 아니어도 미리 지정된 작업을 가져와서 실행할 수 있다.
위에서 나온 uses가 그 예이다.
Runners
runner는 실제로 위의 job을 실행하는 머신이다. 각 러너는 한 번에 하나의 작업을 실행할 수 있다.
만약 구성에 러너가 많이 필요해진다면 더 러너를 추가시킬 수 있다.
특히 Action에 대해서 더 설명해보려고 한다.
맨날 Shell script를 짜는 것이 지겨워졌는지
이제 사람들은 내가 유용하게 쓴 action을 공유하기 시작한다.
그래서 나는 action을 만들지 않고도 유용한 action을 사용할 수 있다.
Github Action에서 쓰는 action은 3가지로 정의된다.
1. 같은 repo에 있는 workflow 파일 안에
2. 어떤 public repo
3. docker hub에 올라와있는 도커 컨테이너 이미지
이러한 action들은 Gihub community에서 찾을 수 있다. ( https://github.com/marketplace?type=actions )
위에서 많이 쓴 checkout을 찾아볼까??
steps:
- name: Checkout
uses: actions/checkout@v3
음.. 여기있구나
예시도 여기있네?
여기서 with으로 넣은 parameter가 없으니 다 default 겠구나??
아하? 그럼 그냥 뭐 default 값이구나?
그럼 그냥 runner에다 branch 만들고 remote에 있는 모든 것들을 내려받으면 되겠다!! 라고 이해하면 된다.
일반적으로 uses 는" {소유자}/{저장소명}@{참조자} "이런식으로 쓰게 된다.
위에서는 actions이 소유자이고, checkout이 저장소이며 최신 버전은 v3이다.
그래서 actions/checkout@v3 라는 action이 실행될 수 있는 것이다.
**참고로 기본 default 값은 최신 상태를 가져오기 때문에 이전 커밋에 대한 작업을 위해서는 fetch-depth를 이용해야 한다.
게다가 다른 저장소의 코드도 받아올 수 있다.
repository의 parameter에 {소유자}/{저장소} 를 넣어주면 된다.
ref로 브랜치 이름이나 커밋 해시값 등을 추가해서 정확한 상태를 가져올 수 있다.
또 알아야할 것이 몇가지 있다.
Action 보다는 덜 중요하지만 그래도 중요하다.
바로 환경변수를 사용하는 것이다.
각 workflow마다 기본 환경변수가 포함될 것이다. 하지만 사용자 지정 환경변수가 필요할 때도 있는데 이 때
yaml 파일을 수정해서 사용할 수 있다.
예를 들자면
jobs:
example-job:
steps:
- name: Connect to PostgreSQL
run: node client.js
env:
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
여기서 POSTGRES_HOST 라는 변수를 선언과 값을 지정해줄 수 있다.,
포트도 이렇게 지정해줄 수 있다.
이러한 환경변수는 client.js 에 이미 포함되어 있을 것이며 client.js 에서 이러한 변수들을 쓸 수 있다.
즉, 자주 바뀌는 환경변수들을 js에 넣어서 만들면 직접 코드를 수정하지 않더라도 yaml 파일 수준에서 바로바로 바꿀 수 있다는 말과 같다.
다음은 스크립트 작업인데 특히 셸 스크립트 작업이다.
직접 커맨드를 하나씩 입력하여 실행할 수도 있고
미리 만들어놓은 shell script를 실행할 수도 있다.
jobs:
example-job:
steps:
- name: Run build script
run: ./.github/scripts/build.sh
shell: bash
다음은 작업 간의 데이터 공유라는 점이다.
동일한 러너에서 작업했다면 작업 결과가 남아있다.
즉, 그 결과를 다른 작업에서도 다른 step에서도 쓸 수 있다는 말이다.
**다른 job이라면 작업 조금 다르다. job끼리는 독립성을 가진다.
예를 들자면
output.log에 저장을 하게 되었는데
그러한 output.log를 아티팩트로 업로드하는 action을 수행한다.
jobs:
example-job:
name: Save output
steps:
- shell: bash
run: |
expr 1 + 1 > output.log
- name: Upload output file
uses: actions/upload-artifact@v3
with:
name: output-log-file
path: output.log
즉, 다른 workflow에서 다른 아티팩트를 다운로드하려면 아래 action을 수행하면 된다.
jobs:
example-job:
steps:
- name: Download a single artifact
uses: actions/download-artifact@v3
with:
name: output-log-file
name은 key로 저장되는데
저렇게 검색된 artifact를 다운로드 받을 수 있다.
다만 다운로드는 업로드가 이루어진 다음에나 이루어질 수 있어서
needs: upload-job-name 업로드 작업이 완료될 때까지 시작되지 않도록 다운로드 작업을 지정해야 한다.
즉, 종속성을 만들어야 한다.
종속성은 이렇게 만든다.
name: Dependency
on: push
jobs:
job1:
runs-on: ubuntu-latest
steps:
- run: echo ${{ github.job }}
job2:
runs-on: ubuntu-latest
steps:
- run: echo ${{ github.job }}
job3:
runs-on: ubuntu-latest
steps:
- run: echo ${{ github.job }}
이렇게만 선언하면 종속성을 하나도 가지지 않는다.
job끼리 그래서 need를 선언해준다.
아래 같이 쓰면 된다.
on: push
jobs:
job1:
runs-on: ubuntu-latest
steps:
- run: echo ${{ github.job }}
job2:
runs-on: ubuntu-latest
needs: job1
steps:
- run: echo ${{ github.job }}
job3:
runs-on: ubuntu-latest
needs: [job1, job2]
steps:
- run: echo ${{ github.job }}
그렇게되면 job1 - job2 - job3 순서대로 실행된다.
뭐 저렇게 해서 순서를 지정할 수도 있지만
작업 순서를 if 속성으로도 제어할 수 있다.
작업이 수행하려면 어떠한 조건을 만족시켜야만 한다는 조건을 넣을 수 있다.
아래처럼 if를 넣어서 제어가 가능하다.
작업 이름 중 echo_if 는 main branch push 작업이 일어났을 경우
skip_ci 는 커밋 메세지에 skip ci가 포함되지 않았을 경우에 실행된다는 뜻이다.
물론 여기서도 종속성이 필요하다면 제어가 가능하다.
on: push
jobs:
echo:
runs-on: ubuntu-latest
steps:
- run: echo 'Hello!'
echo_if:
runs-on: ubuntu-latest
if: github.ref_name == 'main'
steps:
- run: echo 'Hello, Main!'
skip_ci:
runs-on: ubuntu-latest
if: ${{ !contains(github.event.head_commit.message, 'skip ci') }}
steps:
- run: echo 'Hello, Skip CI!'
그렇다면.. job을 여러 머신에서 돌려보고 싶다면..? 어떨까?
os 별로 돌려보고 싶다면 어떻게 할까?
아래처럼 strategy 속성을 이용하면 된다.
matrix의 밑에 있는 os에 대해서 각각 실행될 것이다.
on: push
jobs:
date:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- ubuntu-latest
- windows-latest
steps:
- run: date
참고링크
https://docs.github.com/en/actions
'DevOps' 카테고리의 다른 글
GSLB, 트래픽이 높을 때 로드밸런싱하기 (0) | 2022.11.12 |
---|---|
PromQL과 MetricsQL 의 쿼리 최적화 방법(진행중) (0) | 2022.10.08 |
CMake 튜토리얼 - 2 (2) | 2022.09.20 |
CMake 튜토리얼 - 1 (0) | 2022.09.16 |
Continuos Integration / Continuous Delivery , CI / CD란? (0) | 2022.09.03 |