따라하면서 만들어보는 Git Action CI/CD
ETC./Github

따라하면서 만들어보는 Git Action CI/CD

뉴비뉴 2020. 11. 28.

안녕하세요.

 

글또 5기를 시작하며 에서 다짐한 것 처럼 Github의 Git Action을 사용하여 CI/CD를 구현하는 방법을 포스팅해보았습니다.

회사에서는 Git Flow 방식으로 develop, release(stg), master(prod)로 구분하여 브랜치 별로 Push가 감지되면 도커 이미지를 빌드하고, AWS ECR에 빌드 된 이미지를 Push하는 방식으로 사용하였습니다.

 

경험했던 것들을 정리하여 글을 작성하려고하니 글의 내용이 길어졌고, 내용이 길어지면서 독자분들이 흥미를 잃을 수 있을 것이라고 판단되어 이 글은 간단하게 CI/CD에 대한 정의, 테스트 코드, Git Action을 구현해보는 편으로 간추려보았습니다.

목차

  • CI/CD란 무엇인가?
  • 테스트 코드란?
  • Git Action이란?
  • CD(지속적 배포) 구현

CI(지속적 통합)/CD(지속적 배포)란 무엇인가?

CI(지속적 통합)이란 개발자들이 애플리케이션에 적용한 변경 사항이 버그 테스트를 거쳐 리포지토리(예: GitHub 또는 컨테이너 레지스트리)에 자동으로 업로드되는 것을 뜻하며, CD(지속적 배포)이란 개발자의 변경 사항을 리포지토리에서 고객이 사용 가능한 프로덕션 환경까지 자동으로 릴리스하는 것을 의미합니다.

 

아래 그림을 보면서 단계 별로 설명을 드리도록 하겠습니다.

CI/CD Flow

1. 개발자 뚜벅이는 파이썬 코드를 작성했고, 테스트코드까지 작성하여 Github에 Pull Request를 하였습니다.

2. Github Action에서는 내가 정의한 yaml 파일에 적힌 내용으로 어떠한 작업을 수행합니다.

3. 선임개발자는 뚜벅이가 작성한 코드를 보고 Main 브랜치에 Merge 시킵니다.

4. DockerHub에 뚜벅이가 작성한 코드를 이미지 빌드하여 업로드합니다.

 

이러한 순서로 CI/CD가 진행 될 것이다. 라는 정도만 기억하시면 됩니다.

완벽하게 이해하려고 하실 필요는 없습니다.

 

다음으로 테스트 코드에 대해 간단하게 구현하고, 설명드리도록 하겠습니다.

테스트 코드란?

우리가 지속적인 통합(CI)를 하기 위해서는 작성한 코드가 정상적으로 동작하는지를 확인해야 합니다.

 

pytest 프레임 워크를 사용하면 소규모 테스트를 쉽게 작성할 수 있지만 애플리케이션 및 라이브러리에 대한 복잡한 기능 테스트를 지원하도록 확장됩니다. 테스트 코드는 능숙하게 작성하고 이 코드를 패러랠하게 돌리는 것은 이제 좋은 습관으로 인정받고 있습니다. 이 방법을 현명하게 사용하면 코드의 의도를 보다 명확히하는데 좋을 뿐 아니라, 아키텍처의 결합도를 낮출 수 있습니다.

 

간단한 테스트 코드를 작성해보겠습니다.

 

test_sample.py 파일을 생성해주시고, 안에 pytest 공식문서 예제인 간단한 테스트 코드를 사용해보겠습니다.

코드를 보면 항상 성공하는 케이스입니다. 여러분들이 사용하실 때는 실제 로직을 검증할 수 있는 테스트코드를 작성해야 합니다.

# content of test_sample.py
def inc(x):
    return x + 1


def test_answer():
    assert inc(4) == 5

작성한 테스트 코드를 실행하려면 커맨드 라인에 pytest를 입력하면 됩니다.

Git Action이란?

Github Action은 Github 저장소를 기반으로 소프트웨어 개발 Workflow를 자동화 할 수 있는 도구입니다. 간단하게 말하자면 Github에서 직접 제공하는 CI/CD 도구라고 할 수 있습니다. 

Workflow는 Github 저장소에서 발생하는 build, test, package, release, deploy 등 다양한 이벤트를 기반으로 직접 원하는 Workflow를 만들 수 있습니다.

Workflow는 Runners라고 불리는 Github에서 호스팅 하는 머신의 Linux, macOS, Windows 환경 그리고 컨테이너에서 실행할 수 있습니다. 간단히 정리하면 Workflow를 실행하기 위해 컨테이너가 생성되고, 그 안에서 우리가 정의한 작업들을 수행하고 없어집니다.

 

Git Action을 만들기 위해서는 아래 사진과 같이 .github/workflows 라는 폴더를 생성하겠습니다.

생성한 폴더 안에 apptest.yaml 파일을 생성해주었습니다. yaml이라는 파일을 처음보시는 분도 계실텐데 간단하게 설명하자면 JSON과 같이 데이터 교환 format 입니다.

yaml 파일을 작성하실 때 주의하실 점이 하나 있습니다. yaml 에서 indent는 스페이스바 2번 입니다.

파일 구조

Git Action yaml을 작성할 때 필요한 문법들을 설명해드리도록 하겠습니다.

On

on은 워크플로우를 트리거하는 Github 이벤트의 이름입니다.

아래와 같이 Push, PullRequest 트리거되면 Workflow가 동작하게 됩니다.

 

case1의 경우를 살펴보면 branche main에 PullRequest가 트리거되면 동작 한다는 의미입니다.

case2는 위에서 살펴봤던 release가 트리거 되면 동작하도록 설정되어있지만 types라는 조건이 붙어 조건에 속하는 경우에만 Workflow가 동작되도록 할 수 있습니다.

# case1
on:
  pull_request:
    branches:
      - main
      
on: [push, pull_request]

on: pull_request

# case2
on:
  release:
    types: [published, created, edited]

Job

job 워크플로우 실행은 하나 이상의 작업(job)으로 구성됩니다. 작업은 기본적으로 병렬로 실행됩니다.

case1 처럼 구현하면 2개의 작업이 병렬로 진행됩니다.

jobs:
  deploy:
    name: App Test # job name
    runs-on: ubuntu-latest # 각 작업은 실행으로 지정된 환경에서 실행됩니다.
    
# case1
jobs:
  my_first_job:
    name: My first job
  my_second_job:
    name: My second job

Steps

stpes 가장 중요한 부분입니다.

- Command 명령을 실행할 수 있습니다.

- Github Action Market Place에서 다른사람들이 만들어놓은 오픈소스를 가져와 사용할 수 있습니다.
- uses: 누군가가 만들어 놓은 것을 가져와 사용하는 것 입니다.

 

아래 예시의 steps별로 설명해드리도록 하겠습니다.

 

1. Checkout은 현재 상태의 소스코드를 가상의 컨테이너 안으로 Checkout해주는 역할을 합니다.

2. 다음으로 Setup-python은 가상의 컨테이너 안에 python이 돌아갈 수 있는 환경을 설치하는 것 입니다.

3. Install dependencies는 python의 의존성을 설치해주는 단계입니다.

4. 마지막으로 pytest은 커맨드 명령으로 pytest를 입력하면 테스트가 진행됩니다.

steps:
  - name: Checkout
    uses: actions/checkout@v2
  - name: Set up Python 3.7
    uses: actions/setup-python@v1
    with:
      python-version: 3.7
  - name: Install dependencies
    run: |
      python -m pip install --upgrade pip
      pip install -r requirements.txt
  - name: Test
    run: |
	  pytest

지금까지 간단하게 GitAction Workflow의 문법에 대해 알아봤습니다.

더 자세한 사용법을 알고 싶으신분들은 참고에 있는 GitAction 링크를 참고하시기 바랍니다.

 

위에서 작성한 것들을 정리하면 아래와 같습니다.

# apptest.yaml

on:
  pull_request:
    branches:
      - main

jobs:
  deploy:
    name: App Test
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Set up Python 3.7
        uses: actions/setup-python@v1
        with:
          python-version: 3.7
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
      - name: Test
        run: |
          pytest

작성한 파일을 main branch에 Push하고, branch를 하나 만들어 PR를 보내보겠습니다.

feature/readme-reference라는 branch를 만들어 Push하였고, Github로 이동하여 PR을 보내보도록 하겠습니다.

<영상1>

사진에서 밑줄 친 부분을 보면 앞서 만들었던 App Test가 돌아가는 것을 확인할 수 있습니다. 하지만 <영상1>을 잘 보면 중간에 Merge pull request를 할 수 있게 초록색으로 변경되어 있는 것을 확인할 수 있습니다.

 

정리해보자면,

1. PR Create

2. Merge pull request(버튼 활성화)

3. Git Action 수행(버튼 비활성화) 같이 진행됩니다.

 

여기서 2번 동작의 경우에는 우리가 만든 Action이 다 끝나지도 않았고, 통과했는지 여부도 모르는데 Merge 버튼이 활성화 되었습니다.

해당 동작은 불필요하기에 없애주도록 하겠습니다.

 

github 웹 페이지 이동, 작업 할 레포로 이동합니다.

settings -> Branches -> Branch protection rules 이동 후 아래와 같이 설정해주면 됩니다.

 

Status checks found in the last week for this repository는 Git Action yaml파일 job 안에 정의한 Name을 선택해주시면 됩니다.

좋습니다, 설정 후 어떻게 바뀌었는지 확인해보겠습니다.

Git Action 테스트가 끝나기 전에는 Merge Pull Request가 비활성화 된 것을 확인할 수 있습니다.

CD(지속적 배포) 구현

지속적인 배포가 가능하려면 어떻게 해야 할까요?

 

도커 이미지를 빌드하고, AWS ECR이나 Azure ACR, Docker Hub에 Push한다고 가정하겠습니다. 하지만, 이미지를 Push 한다고해서 내가 원하는 서버 인스턴스에 알아서 이미지를 Pull 받아서 실행시키지는 않을 겁니다.

 

그렇다면 어떻게 배포를 해야할까요?

 

제가 경험해본 방법으로는 AWS CodeDeploy(코드 배포 자동화)가 있습니다.

 

Git Action에서 코드를 압축한 다음 AWS S3에 업로드 한 뒤 codedeploy-agent가 인스턴스 서버에 코드를 옮기는 방법입니다. 서비스 다운타임이 적다는 것과 오토스케일링을 손쉽게 설정 할 수 있다는 것과 yaml 파일로 배포가 되기 전에 환경을 설치하거나 배포가 되고나서 어떤 동작을 수행하라는 것을 간단하게 만들 수 있었습니다. 단점으로는 codedeploy-agent가 서버에 설치되어 메모리를 차지한다는 것과 버전관리를 해주는 것은 고맙지만 서버에 최근 배포 된 5개의 코드를 보관하면서 용량을 차지한다는 것 입니다.

 

이 글은 깊게 AWS의 서비스를 이용하여 배포를 자동화하는 것 까지는 들어가지 않을 것이기 때문에 Git Action에서 도커 이미지를 빌드하고, Docker Hub에 이미지를 Push하는 과정만 설명해보도록 하겠습니다.

Dockerfile

도커파일을 하나 만들어보겠습니다.

FROM python:3.7-slim

WORKDIR /app  # 작업 할 디렉터리를 지정
COPY requirements.txt .  # requirements를 /app 안으로 COPY

RUN pip install -r requirements.txt  # 의존성 설치

COPY . .  # Source Code를 /app 안으로 COPY

RUN pytest  # pytest 실행

`docker build -t action:latest .` 명령어로 이미지를 빌드하겠습니다.

Docker Build

도커 이미지가 정상적으로 빌드되었습니다. `docker images` 명령어로 생성 된 이미지를 확인 할 수 있습니다.

이제 도커 이미지를 업로드 할 곳이 필요합니다. Docker Hub 회원가입하고 레포지토리를 만드는 과정은 간단하기 때문에 설명은 생략하고 Git Action yaml 파일을 수정해주도록 하겠습니다.

 

Docker Hub Login

Git Action 안에서 Docker hub에 로그인해야 도커 이미지를 Push 할 수 있습니다.

사용하는 방법은 아래와 같습니다.

jobs:
  login:
    runs-on: ubuntu-latest
    steps:
      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

 

여기서 ${{ secrets.DOCKERHUB_USERNAME }} 안에 보시면 secrets는 Github 레포지토리에서 생성하는 secrets을 가져와 사용하는 것 입니다. Github settings -> Secrets로 이동해 USERNAME과 TOKEN을 입력해주도록 하겠습니다.

Dockerhub Token 생성방법은 링크로 이동하여 확인하시기 바랍니다.

Github Secrets 등록

다음으로 Docker hub에 action 레포지토리를 생성해주도록 하겠습니다.

위 사진에서 Docker commands를 보시면 2044smile는 계정명이고, action은 레포지토리 이름입니다.

tagname은 v<major>.<minor>.<patch> 이런식으로 버전관리를 해주시면 됩니다. 이 글에서는 간단하게 latest로 관리하겠습니다.

 

다시 yaml 파일로 돌아와서 Secrets을 등록해주었고, Docker login은 정상적으로 잘 동작할 것 입니다. 다음으로 해야 할 일은 도커 이미지를 빌드하고, 도커 허브에 Push하는 일만 남았습니다.

 

우리는 앞서 `docker build action:latest .` 로 이미지를 빌드 했었습니다. 하지만 해당 이미지를 그대로 도커 허브 레포지토리에 Push하게 된다면 경로가 맞지 않아 Push를 하지 못합니다. 그래서 `docker tag action:latest 2044smile/action:latest` 명령어로 도커 이미지 이름을 수정 해야 합니다.

 

마지막으로 docker push 명령어로 `docker push 2044smile/action:latest` 이미지를 도커 허브 레포지토리에 Push하면 됩니다.

on:
  pull_request:
    branches:
      - main

jobs:
  deploy:
    name: App Test
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Set up Python 3.7
        uses: actions/setup-python@v1
        with:
          python-version: 3.7
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
      - name: Test
        run: |
          pytest
      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      - name: build and release to DockerHub
        env:  # 환경변수로 값을 지정하여 사용할 수 있습니다.
          REPO: 2044smile
          LAYER_NAME: action
        run: |
          docker build -t $LAYER_NAME  .
          docker tag $LAYER_NAME:latest $REPO/$LAYER_NAME:latest
          docker push $REPO/$LAYER_NAME:latest

파일을 수정하고, main 브랜치에 PullRequest를 보내보면 Git Action이 동작하면서 생성해두었던 Docker Hub 이미지가 올라갑니다.

아래 사진을 보시면 이미지를 빌드하다보니 시간이 좀 더 오래 걸린 것을 확인할 수 있습니다.

마지막으로 Docker Hub에 이미지가 잘 올라갔는지 확인해보도록 하겠습니다.

Docker Hub

마무리

테스트코드로 코드를 검증하고, 도커 이미지를 새롭게 빌드하여 이미지 저장소에다가 Push하는 CI/CD를 구현하였습니다.

글을 작성할 때 Github, Docker에 대한 설명을 생략하였기 때문에 Git과 Docker를 사용해보지 않으신분들에게는 이해하기 어렵겠다는 생각을하여 만들어보면서 이해하는 것이 가장 좋은 방법이라고 생각하기에 제목을 '따라하면서 만들어보는' 이라는 컨셉으로 글을 작성해보았습니다. 추가설명이 필요하거나 조언을 주실 부분이 있으시면 편하게 댓글로 피드백주시면 감사하겠습니다.

참고

 

 

 

 

'ETC. > Github' 카테고리의 다른 글

Github 기본 브랜치 설정  (1) 2020.01.28
Git flow 방법론  (0) 2020.01.28

댓글

💲 추천 글