Github Actions 를 이용한 CI/CD 파이프라인 구축하기
28 Apr 2021개요
Github Actions 는 Github 에서 제공하는 소프트웨어 개발 Workflow 를 자동화하는 도구입니다.
CI, CD 파이프라인은 빌드/배포 과정 동안 수행해야 할 테스크가 정의된 것입니다.
Github Actions 를 통해 CI/CD 파이프라인을 Workflow 에 정의하여 자동화하는 과정을 살펴보겠습니다.
사용 도구
Github Actions 를 이용한 CI/CD 파이프라인 구축하기 위해서 다음과 같은 도구들을 사용합니다.
- Github Repository
- Docker 이미지 (app 포함)
- GCE (Google Compute Engine) 인스턴스
- GCR (Google Container Registry)
- Slack Repository
GCE 인스턴스 생성 및 Docker 이미지 관리에 대해서는 아래링크를 참조바랍니다.
GCE 위에 모놀리식 스프링부트 실행시키기
간단한 Workflow 만들기
Github actions 에서 제공하는 Workflow 는 Github 저장소에 저장된 소스코드를 이용한
Build, Test, Release, Deploy 등 다양한 이벤트를 자동화할 수 있습니다.
Workflow 는 Runners 라 불리는 Github 에서 호스팅된 환경(Linux, macOS, Windows 등)에서 실행됩니다.
(사용자가 직접 호스팅하는 환경에서도 구동이 가능합니다. self hosted runner)
또한 다른 사람들이 제작한 Workflow 가 Github Marketplace 에 공유되어 있습니다.
Github 을 통해 공식인증된 Workflow 를 사용할 수도 있고, 커스텀된 Workflow 를 제작할 수도 있습니다.
간단한 커스텀 Workflow 를 만들어보겠습니다.
Workflow 만드는 방법은 간단합니다.
우선 자신의 프로젝트가 저장되어 있는 Github Repository 에 .yml 파일을 하나 생성합니다.
위치는 프로젝트 폴더의 최상위에 .github/workflow/*.yml 로 위치시킵니다.
위 방법처럼 직접 파일을 생성하지 않아도 됩니다.
Github 에서 제공하는 Actions UI 를 이용해 보겠습니다.
Github Repository 탭에 Actions 메뉴로 들어갑니다.
왼쪽에 New workflow 를 눌러 새로운 workflow 를 만들 수 있습니다.
set up a workflow yourself 를 눌러 커스텀 workflow 파일을 생성합니다.
파일 내용은 다음을 복사합니다.
name: Java Gradle Build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
# "master" 체크아웃
- name: Checkout
uses: actions/checkout@v2
# Java + Gradle 기반 앱 테스트 및 빌드
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Source Code Test And Build
run: |
chmod +x gradlew
./gradlew build
위 명령어를 하나하나 살펴보겠습니다.
-
name
workflow 의 이름을 명시합니다.name: Java Gradle Build
-
on
workflow 의 이벤트 조건을 명시합니다.
push또는pull request동작시 작동하게 하거나cron문법을 이용해 시간을 설정할 수도 있습니다.on: schedule: - cron: '*/10 * * * *' push: branches: [ master ] pull_request: branches: [ master ]또한
paths를 이용하여 특정 패턴에 해당하는 파일이 변경되었을 때 트리거가 되도록할 수 있습니다.on: push: branches: [ master, dev ] paths: - "**.java" - "**.js" paths-ignore: - "doc/**" - "**.md"
-
jobs
job 은 기본 실행 단위입니다.
job 들은 모두 병렬적으로 실행됩니다.jobs: some-job-id: ... some-job-id-2: ...-
runs-on
해당 job 을 가상 실행할 컴퓨팅 환경인runner를 명시합니다.
ubuntu-latest, macos-latest, windows-latest 등으로 실행 가능합니다.jobs: some-job-1: runs-on: ubuntu-latest
strategy
여러 환경에서의 실행을 위해 build matrix 를 설정합니다.
각기 다른 환경들을 명시하여 같은 job 을 동시에 실행할 수 있습니다.jobs: some-job-1: strategy: matrix: node-version: [10.x, 12.x]
steps
job 내부 실행 과정을 명시합니다.- Github 저장소 코드 체크아웃
- Github Marketplace 에서 import 한 actions 실행
- shell 명령어 실행
- 도커 이미지 빌드/배포
- aws/gcp 등의 인프라에 서비스 배포 등
각 step 은 job 의 컴퓨팅 자원에서 독립적인 프로세스로 동작하며,
여러가지 명령들을 순차적으로 실행합니다.
(또한 runner 의 파일 시스템에 접근할 수 있습니다)jobs: some-job-1: runs-on: ubuntu-latest steps: # Github 저장소에 저장된 소스코드를 체크아웃 - name: Checkout uses: actions/checkout@v2 # Java + Gradle 기반 앱 테스트 및 빌드 - name: Set up JDK 1.8 uses: actions/setup-java@v1 with: java-version: 1.8 - name: Source Code Test And Build run: | chmod +x gradlew ./gradlew build위 workflow 는 Github 에 저장된 소스코드를 체크아웃하고 gradlew 를 통해 build 작업이 순차적으로 실행됩니다.
steps.name
step 의 이름을 명시합니다.
steps.uses
해당 스텝에서 사용할 action 을 명시합니다.
Github Marketplace 에 많은 action 들이 있습니다.
-
steps.run
runner 의 shell 을 이용하여 명시된 명령어 나열을 실행합니다.jobs: some-job-1: steps: - name: My First Step run: | ## 명령어를 여러 줄 사용하기 위해서는 파이프(|) 를 입력합니다. npm install npm test npm build
-
이외에 Github Actions 문법은 공식문서를 통해 확인할 수 있습니다.
Workflow syntax for GitHub Actions
이제 복사한 내용을 commit 하면 자동으로 Action workflow 가 실행됩니다.
Actions UI 에서 workflow 실행 과정이 모니터링 됩니다.
CI/CD 파이프라인 Workflow
기본적인 Github Actions 내용을 숙지했다면 CI/CD 자동화 파이프라인을 구축해보겠습니다.
Workflow 는 다음과 같습니다.
name: Build-Deploy
on:
push:
branches: [ master ]
paths-ignore:
- "**.md"
pull_request:
branches: [ master ]
paths-ignore:
- "**.md"
env:
GITHUB_TOKEN: $
SLACK_WEBHOOK_URL: $
jobs:
build:
runs-on: ubuntu-latest
steps:
# "master" 체크아웃
- name: Checkout
uses: actions/checkout@v2
# Java + Gradle 기반 앱 테스트 및 빌드
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Source Code Test And Build
run: |
chmod +x gradlew
./gradlew build
# Gcloud CLI 세팅
- name: Set up Gcloud
uses: google-github-actions/setup-gcloud@master
with:
version: '290.0.1'
service_account_key: $
project_id: $
export_default_credentials: true
# GCR 연결 위한 인증 작업 실행
- name: Set Auth GCR
run: gcloud --quiet auth configure-docker
# GCR에서 이전 버전 참고하여 다음 버전 만든 후, 이미지 빌드 및 푸쉬
- name: Build Docker Image And Delivery To GCR
run: |
IMAGE=gcr.io/$/$
INPUT=$(gcloud container images list-tags --format='get(tags)' $IMAGE)
LATEST_TAG=$(echo ${INPUT[0]} | awk -F ' ' '{print $1}' | awk -F ';' '{print $1}')
ADD=0.01
VERSION=$(echo "${LATEST_TAG} $ADD" | awk '{print $1 + $2}')
NEW_VERSION=$(printf "%.2g\n" "${VERSION}")
docker build --tag $IMAGE:${NEW_VERSION} .
docker push $IMAGE:${NEW_VERSION}
docker tag $IMAGE:${NEW_VERSION} $IMAGE:latest
docker push $IMAGE:latest
# 작업 결과 슬랙 전송
- name: Result to Slack
uses: 8398a7/action-slack@v3
with:
status: $
fields: repo,message,commit,author,action,eventName,ref,workflow,job,took
author_name: MSA Build Result
if: always()
deploy:
needs: [ build ]
runs-on: ubuntu-latest
steps:
# SSH 접속을 통한 직접 배포
- name: Deploy to GCE
uses: appleboy/ssh-action@master
with:
host: $
username: $
key: $
passphrase: $
script: |
CONTAINER_NAME=hello-container
IMAGE=gcr.io/$/$
sudo gcloud --quiet auth configure-docker
sudo docker ps -q --filter "name=$CONTAINER_NAME" | grep -q . && sudo docker stop $CONTAINER_NAME
sudo docker system prune -a -f
sudo docker run -d --name "$CONTAINER_NAME" --rm -p $:$ $IMAGE:latest
# 작업 결과 슬랙 전송
- name: Result to Slack
uses: 8398a7/action-slack@v3
with:
status: $
fields: repo,message,commit,author,action,eventName,ref,workflow,job,took
author_name: MSA Deploy Result
if: always()
위 명령어 하나하나 살펴보겠습니다.
우선 트리거 조건을 명시하는 on 부분입니다.
paths-ignore 를 통해 commit 시 무시되는 파일 확장자를 명시합니다.
on:
push:
branches: [ master ]
paths-ignore:
- "**.md"
pull_request:
branches: [ master ]
paths-ignore:
- "**.md"
env 는 workflow 내부에서 사용되는 환경변수입니다.
아래 action 에서 사용될 각 변수들에 값을 넣어줍니다.
$ 문법을 통해 Github Secret 에서 설정한 시크릿을 가져올 수 있습니다.
GITHUB_TOKEN, SLACK_WEBHOOK_URL 변수들에 대한 설명은 아래에서 다루겠습니다.
env:
GITHUB_TOKEN: $
SLACK_WEBHOOK_URL: $
runner 환경은 리눅스 ubuntu 를 사용하여 실행되게 합니다.
runs-on: ubuntu-latest
Github 저장소에서 소스코드를 체크아웃 합니다.
체크아웃된 코드는 runner 의 가상 실행 환경에 위치됩니다.
# "master" 체크아웃
- name: Checkout
uses: actions/checkout@v2
해당 프로젝트는 Java 프로젝트를 기반으로 진행되므로 JDK 1.8 환경을 runner 에 세팅해줍니다.
체크아웃 한 코드를 gradlew 를 통해 Test 및 Build 작업을 실행합니다.
# Java + Gradle 기반 앱 테스트 및 빌드
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Source Code Test And Build
run: |
chmod +x gradlew
./gradlew build
GCP 를 제어하기 위한 Gcloud CLI 를 세팅합니다.
service_account_key 와 project_id 는 GCP 인증을 위해 GCP 서비스 계정에서 받아와 설정해줘야 하는 정보들입니다.
Secret 설정은 아래에서 자세하게 다루겠습니다.
# Gcloud CLI 세팅
- name: Set up Gcloud
uses: google-github-actions/setup-gcloud@master
with:
version: '290.0.1'
service_account_key: $
project_id: $
export_default_credentials: true
GCR (Google Container Registry) 은 GCP 에서 제공하는 컨테이너 이미지 레지스트리(저장소) 입니다.
Docker 이미지를 빌드하여 저장하기 위해 GCR 을 사용합니다.
아래 명령어는 GCR 연결을 위한 선 인증 작업을 실행합니다.
run 명령으로 실행되는 gcloud 사용을 위해 위에서 Gcloud CLI 세팅작업이 선결되어야 합니다.
# GCR 연결 위한 인증 작업 실행
- name: Set Auth GCR
run: gcloud --quiet auth configure-docker
shell 명령을 사용하여 GCR 에 도커 이미지를 push 하는 과정입니다.
GCR 에 저장되어 있는 최근 이미지의 버전을 얻어와 버전업한 후 도커 이미지를 빌드하여 저장소로 푸시하는 과정이 이뤄집니다.
# GCR에서 이전 버전 참고하여 다음 버전 만든 후, 이미지 빌드 및 푸쉬
- name: Build Docker Image And Delivery To GCR
run: |
IMAGE=gcr.io/$/$
INPUT=$(gcloud container images list-tags --format='get(tags)' $IMAGE)
LATEST_TAG=$(echo ${INPUT[0]} | awk -F ' ' '{print $1}' | awk -F ';' '{print $1}')
ADD=0.01
VERSION=$(echo "${LATEST_TAG} $ADD" | awk '{print $1 + $2}')
NEW_VERSION=$(printf "%.2g\n" "${VERSION}")
docker build --tag $IMAGE:${NEW_VERSION} .
docker push $IMAGE:${NEW_VERSION}
docker tag $IMAGE:${NEW_VERSION} $IMAGE:latest
docker push $IMAGE:latest
Slack 채널로 해당 작업 결과를 전송합니다.
# 작업 결과 슬랙 전송
- name: Result to Slack
uses: 8398a7/action-slack@v3
with:
status: $
fields: repo,message,commit,author,action,eventName,ref,workflow,job,took
author_name: MSA Build Result
if: always()
GCE (Google Compute Engine) 로 SSH 접속하여 도커 이미지를 pull 하여 컨테이너에 어플리케이션이 포함된 이미지를 실행합니다.
script 부분은 SSH 접속후 해당 컨테이너에서 실행할 스크립트를 명시합니다.
컨테이너 내부에서 GCR 접속을 위해 gcloud --quiet auth configure-docker 명령을 통해 접근 권한을 얻습니다.
기존 컨테이너를 정지/삭제한 뒤 버전업된 새로운 이미지를 받아와 실행시킵니다.
# SSH 접속을 통한 직접 배포
- name: Deploy to GCE
uses: appleboy/ssh-action@master
with:
host: $
username: $
key: $
passphrase: $
script: |
CONTAINER_NAME=hello-container
IMAGE=gcr.io/$/$
sudo gcloud --quiet auth configure-docker
sudo docker ps -q --filter "name=$CONTAINER_NAME" | grep -q . && sudo docker stop $CONTAINER_NAME
sudo docker system prune -a -f
sudo docker run -d --name "$CONTAINER_NAME" --rm -p $:$ $IMAGE:latest
위 내용대로 workflow 설정파일을 생성합니다.
이제 내부 환경변수에 들어갈 Secret 값들을 설정해보겠습니다.
Github Secret 설정
Github 에서는 프로젝트 내에서 사용되는 민감한 정보(가령 보안과 관련된)들을 안전하게 관리해줍니다.
우선 Github Repository 에 Setting - 왼쪽 메뉴 중 Secrets 에 들어갑니다.
오른쪽 상단의 New repository secret 을 눌러 새로운 secret 을 생성합니다.
Name 부분에 secret 변수명을 입력하고, Value 부분에 secret 변수의 값을 입력합니다.
위 예시와 같이 생성된 secret 은 다음과 같은 형태로 사용됩니다.
$ { { secrets.MY_SECRET_VALUE } }
이제 위 workflow 에서 사용된 secret 을 하나하나 살펴보겠습니다.
-
GCP_PROJECT_ID
GCP 프로젝트 생성 당시 발급된 고유 ID 를 등록합니다.Secret Name Secret Value GCP_PROJECT_IDmindful-folio-309712
GCP PROJECT ID
-
GCE_INSTANCE_NAME
GCE 인스턴스 이름을 등록합니다.Secret Name Secret Value GCE_INSTANCE_NAMEinstance-test-2
GCE INSTANCE NAME
-
GCE_INSTANCE_ZONE
GCE 인스턴스 영역을 등록합니다.Secret Name Secret Value GCE_INSTANCE_ZONEasia-northeast3-a
GCE INSTANCE ZONE
-
GCP_SA_KEY
GCP 프로젝트의 서비스 계정(Service Account) 를 등록합니다.
외부에서 GCP 연결을 위해gcloud cli를 이용합니다.
이때 서비스 계정을 통해 인증 과정을 거치게 됩니다.우선 왼쪽 메뉴의
IAM 및 관리자-서비스 계정으로 들어갑니다.
GCP SA자신의 GCE 인스턴스 아이디에 해당하는 항목의 맨 오른쪽 점 세개
작업부분을 누르고키 관리를 선택합니다.
GCP SA왼쪽
키 추가-새 키 만들기-JSON을 선택한 뒤 -만들기를 눌러 SA 키를 생성합니다.
GCP SA이때, 자동으로 다운로드 되는 파일 내용을
base64인코딩을 한 후secret에 등록합니다.
인코딩 변환 방법은 다음과 같습니다.- 인코딩 변환 사이트 : https://www.base64decode.org/
- 윈도우 명령 프롬프트 :
certutil -encode [sa key 경로] [인코딩 결과 경로] - 리눅스 :
base64 [sa key 경로] > [인코딩 결과 경로] - macOS :
base64 -i [sa key 경로] -o [인코딩 결과 경로]
Secret Name Secret Value GCP_SA_KEY[ base64 인코딩된 SA KEY ]
-
REPOSITORY_NAME
해당 프로젝트의 Github Repository 저장소 이름을 등록합니다.Secret Name Secret Value REPOSITORY_NAMEmsa-helloworld
-
CONTAINER_PORT
Docker 컨테이너에서 EXPOSE 될 포트를 지정합니다.
기본 어플리케이션 작동 테스트가 목적이므로8080값을 등록합니다.Secret Name Secret Value CONTAINER_PORT8080
-
SSH_HOST
GCE 인스턴스에 SSH 접속을 위한 값입니다.
GCE 인스턴스 메인화면에 적혀져있는외부 IP를 등록합니다.Secret Name Secret Value SSH_HOST34.64.154.29
-
SSH_USERNAME
GCP 프로젝트를 생성한 자신의 구글 ID 를 등록합니다.Secret Name Secret Value SSH_USERNAMEnzzi.dev
-
SSH_KEY
GCE 인스턴스 SSH 접속 역시 인증과정에 필요한 KEY 등록이 필요합니다.우선 터미널 또는 명령 프롬프트에 다음을 입력합니다.
` $ ssh-keygen -t rsa -f [KEY 경로] -C “[유저 아이디@gmail.com]” `
예시
ssh-keygen -t rsa -f ~/.ssh/rsa-gcp-key -C "nzzi.dev@gmail.com"비밀번호(passphrase) 등록 또한 해줍니다.
이때 생성된
private key를secret으로 등록합니다.Secret Name Secret Value SSH_KEY[ SSH Private KEY ]여기서 끝이 아닙니다.
생성된public key를 gcp 메타데이터에 등록해줘야 합니다.우선 GCP 왼쪽 메뉴에서 메타데이터 탭으로 들어가줍니다.
GCP 메타데이터 메뉴SSH 키탭을 선택해주고수정-항목 추가를 선택해줍니다.
위에서 생성한public key내용을 복사합니다.
GCP SSH 공개키 등록 예시다른 플랫폼에서의 접속방법은 다음을 참고바랍니다.
GCE 인스턴스 컨테이너에 접속하기
-
SSH_PASSPHRASE
위 비밀번호 등록 과정에서 입력한passphrase를 등록합니다.Secret Name Secret Value SSH_PASSPHRASE[ SSH PASSPHRASE ]
-
SLACK_WEBHOOK_URL
SLACK 에 빌드 결과를 알림하기 위한 알림 URL 을 등록합니다.우선 슬랙에 가입을 하고 워크스페이스를 생성해줍니다.
(기존 워크스페이스도 상관없습니다)결과를 전송받을 채널을 지정한 후 (기본 채널도 상관없습니다)
Slack API 를 관리하는 곳으로 이동합니다.Create New App버튼을 눌러줍니다.
SLACK Create New AppFrom scratch선택한 후 적절한 App 이름을 적고 workspace 를 선택해줍니다.
SLACK Create New AppIncoming Webhooks을 선택하면 웹훅 URL 이 생성됩니다.
SLACK Incoming Webhooks우측 상단에 토글 버튼을
On으로 바꿔주고 아래에Webhook URL복사해준 뒤,
Add New Webhook to Workspace를 눌러 워크스페이스에 웹훅을 추가해줍니다.
SLACK Add New Webhooks복사한
Webhook URL은secret에 등록해줍니다.Secret Name Secret Value SLACK_WEBHOOK_URLhttps://hooks.slack.com/services/ASDASDASD/ZXCZXCZXC/QWEQWEQWE
Docker file
Spring boot 로 제작한 자바 어플리케이션을 실행시키는 간단한 도커 이미지 입니다.
FROM java:8
VOLUME /helloworldVolume
EXPOSE 8080
ARG JAR_FILE=build/libs/helloworld-0.0.1-SNAPSHOT.jar
ADD ${JAR_FILE} app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
해당 파일을 Github 저장소 최상위에 위치시킵니다.
동작 확인
commit 한 내용들을 저장소에 push 할 경우,
workflow 의 on 트리거 조건에 detection 되어 workflow 가 자동 실행됩니다.