[5/28] TIL - Docker 실습, CI/CD, Github Actions

Sangwon Jwa·2024년 5월 28일

데브코스 TIL

목록 보기
38/54
post-thumbnail

📖 학습 주제


  1. 실습1 - Hangman 서비스
  2. CI/CD
  3. Github Actions

✏️ 주요 메모 사항 소개


실습1 - Hangman 서비스

1. Hangman 프로그램 개요

이번 실습에서는 웹서비스를 Doker로 구동해보는 전체 과정을 다시한번 익히는 연습을 해보자. hangman_web이라는 repo의 main에 코드가 머지될 때마다 다음을 수행하도록 구성

  • 테스트 수행
  • Docker Image 빌드
    • 이를 위해 Dockerfile 부터 만들어볼 예정
  • Docker Image를 Docker Hub로 push

위 과정을 Github repo에 Github Actions으로 구현해보자

시작하기 앞서 우리가 올릴 프로그램은 hangman 웹서비스로, https://github.com/learndataeng/hangman_web 링크에서 clone을 하던, fork를 하던해서 자신의 repository에 복사해두자.

이 hangman 프로그램은 flask를 사용하여 웹으로 노출하고 있다. 포트번호는 어디든 바인딩 가능하며 실행할 때 지정할 것이고 추가로 flask 관련 모듈 설치가 필요하다 (requirements.txt)

python3 -m flask run --host=0.0.0.0 --port=4000 명령어로 실행할 수 있고, 이 경우 app.py를 기본으로 사용한다.

다음은 Hangman repository의 구조이다.

일단 이 프로그램을 play-with-docker에서 실행해보자. 먼저 깃 저장소를 clone하고, pip3 install -r requirements.txt를 통해 관련 라이브러리를 설치한 다음, python3 -m flask run 명령어로 실행해보자.

그 후, OPEN PORT버튼을 누른 뒤 우리가 설정한 4000을 입력하면 Hangman 웹의 페이지를 볼 수 있다.


2. Hangman 서비스 Docker Image로 빌드

한가지 짚고 넘어가야 하는 부분이 있다. 만약 Docker 컨테이너로 포트 4000에 실행된 Flask app이 있다고 가정할 때, 이 app을 호스트 운영체제에서 접근할 수 있을까? 답은 '접근할 수 없다' 이다.

이러한 이유는 컨테이너는 완전히 별개의 공간이기 때문에 그 안에서 포트번호 4000을 열었다고 해서, 컨테이너 바깥에서 보이지는 않기 때문이다. 이를 해결하기 위해 우리는 Docker 컨테이너 내부 프로세스가 오픈한 포트번호를 외부로 노출해주는 작업을 해주어야 한다. 이를 Port-Mapping 이라고 한다.

방법은 docker run 수행시 -p 옵션을 사용하는 것이다. (EX : docker run -p 4000:4000 이미지이름)

이제 Dockefile을 만든 뒤 Docker Image로 빌드를 해보자. 작성한 Dockerfile은 다음과 같다. Dockerfile은 clone 한 Hangman_web Repository안에 만들어 주자. (같은 디렉토리 안에 app.py와 같은 파일이 있어야함)

Dockerfile이 작성이 완료되었다면 docker build 명령어를 통해시 이미지를 만들어주자.

docker run -p 4000:4000 hjjwa1234/hangman 명령어를 사용해서 잘 작동하는지 확인해보자

  • -d 옵션을 사용하면 백그라운드에서 실행하도록 명령할 수도 있다.


3. Docker Hub에 이미지 올리기

docker push 명령어를 사용해서 만든 이미지를 Docker Hub에서 공유하고 웹브라우저에서 확인해보자.


4. 다른 Linux 서버에서 사용하기

play-with-docker 페이지에서 우리가 Docker Hub에 올린 이미지를 컨테이너로 실행시켜 보자. run 명령어를 실행 시 local에서 이미지가 없으면 자동으로 Docker Hub에서 이미지 파일을 찾기 때문에 자동적으로 pull 작업이 완료된다.

이후, OPEN PORT 버튼 누른 뒤 4000번 입력해주면 Hangman 페이지를 확인할 수 있다.


CI / CD

먼저 소프트웨어 빌드란 자신(혹은 팀)이 개발한 소프트웨어를 최종적으로 출시하기 위한 형태로 만드는 것을 말한다. 테스트가 빌드의 중요한 일부로 포함되고, 참여 개발자들이 많을수록 이는 더 중요하다.

개발이 끝나기 전부터 빌드를 하면 소프트웨어의 안정성이 증대한다는 장점이 있다. 이 장점을 살려서 코드를 고칠때마다 테스트를 진행하고, 기대한대로 코드가 잘 돌아가는지 확인하는 작업을 Continuous Integration이라고 한다

  • Continuous Integration의 기본 원칙
    • 코드 Repo는 하나만 유지 (Master)
    • 코드변경을 최대한 자주 반영
    • 테스트를 최대한 추가
      • Test Coverage
    • 빌드를 계속적으로 수행 (자동화)
      • Commi Build vs Nightly Build
    • 성공한 빌드의 프로덕션 릴리스 (자동화)
      • CD (Continuous Delivey)

이런 방식으로 진행할 때 빌드가 실패했다는 것은 무슨 의미일까? 보통 새 코드의 커밋으로 인해 테스트가 실패하는 경우가 있다.

  • 많은 회사들은 빌드 실패 시 빌드가 다시 성공할때까지 코드 변경을 금지한다.
    • 즉, 빌드 실패는 모든 사람들을 잡아두는 족쇄
    • 그래서 어느정도 조직이 커지면 빌드만 전담하는 엔지니어가 있다

Git이나 Github와 같은 버전관리 툴을 사용하여 협업을 하는 경우 PushMerge 하는 시점이 CI/CD를 실행하기 위한 절호의 순간이다. 코드가 메인/마스터나 브랜치에 추가되는 순간 CI/CD를 트리거하는 방식으로 적용할 수 있다.

  • 이를 특정 메인/마스터나 특정 브랜치만 대상으로 하도록 설정 가능
  • 이 때 테스트를 수행하고 최종적으로 Docker Image등을 만들도록 하는 것이 가능
  • 그래서 CI/CD는 Github에 구현하는 것이 가장 자연스럽다.

그래서 Github에서는 이를 Actions라는 기능을 통해 Workflow라는 이름으로 구현 가능하다.


Github Actions

Github Actions란 CI/CD를 Github 위에서 구현하기 위한 서비스로, 코드 테스트, 빌드, 배포 자동화 기능을 제공한다. 이를 Workflow라 부르며 아래 컴포넌트로 구성된다.

  • Events
  • Jobs
  • Actions
  • Runner
    • Github hosted runners
    • Self hosted runners

Workflow

Workflow는 트리거 이벤트가 발생하면 시작되는 일련의 동작들을 지칭한다. 트리거 이벤트의 예들을 보자면 다음과 같은 것이 있다.

  1. 코드 커밋 (main과 같은 특정 브랜치를 대상으로만 제한 가능)
  2. PR 생성
  3. 다른 Workflow의 성공적인 실행
  • Workflow를 위한 명령어들은 YAML 파일로 저장한다
    • 명령어들로는 환경설정과 scripts 실행들이 대표적
  • Workflow는 Job들로 나눠지며 각 Job은 일련의 스텝을 수행
    • 각 스텝은 하나 혹은 그 이상의 명령어를 실행
      • 이 명령어는 actions라고 부르는 명령어들의 집합이 될 수도 있음
    • 각 스텝은 윈도우나 리눅스 서버 위에서 runner에 의해 실행
      • 이걸 Docker Image에서 수행하는 것이 서비스 배포 과정에 따라 더 일반적이기도 함

Github의 Repository를 보면 Actions 메뉴가 있는데, 여기서 workflow를 생성할 수 있다. yml 파일을 직접 생성하던가, 혹은 템플릿(CI Templates)을 선택 후 수정하는 방법이 있다. (Python Application 혹은 Docker Image)


Github Actions - 테스트 추가하기

앞서 우리가 작성했던 hangman 코드에 Github Actions 기능을 이용해서 테스트를 추가해보자.

  • Github Actions를 통해 main 브랜치에 pushPR이 있는 경우 test.py를 실행할 예정
    • 하나의 repo에 대해 다수의 workflow들이 존재 가능
    • 모두 .github/workflows/ 밑에 yml 파일 형태로 존재

우리가 이번 실습에서 사용해볼 CI Templat은 Python Application이라는 템플릿이다.

기본으로 pytest를 테스트 프레임워크로 설치하여 (우리는 unittest로 작성되어 있음) 테스트 코드를 실행하고, 이외에도 Python code linting tool으로 falke8을 설치하여 문법 에러와 코딩 스타일을 체크할 예정이다.

  • flake8 : 파이썬 코드에서 에러나 코딩 스타일 등에서 이슈를 체크해주는 툴. (이런 툴을 Linting tool이라 부름)

 
테스트 코드는 다음과 같다. (test.py)

"""Unit test cases for hangman game."""
import unittest
import app as hangman


class HangmanTestCase(unittest.TestCase):

    # def setUp(self):
    #

    # checkCorrectAnswer(correctLetters, secretWord)
    def test_checkCorrectAnswer(self):
        answer = hangman.checkCorrectAnswer("baon", "baboon")
        self.assertTrue(answer)

    def test_checkWrongAnswer(self):
        answer = hangman.checkWrongAnswer("zebrio", "zebra")
        self.assertTrue(answer)

    def test_1(self):
        answer = hangman.checkCorrectAnswer("bazn", "baboon")
        self.assertFalse(answer)

    def test_2(self):
        answer = hangman.checkCorrectAnswer("", " ")
        self.assertFalse(answer)

    def test_3(self):
        answer = hangman.checkCorrectAnswer("ZEBRA", "zebra")
        self.assertFalse(answer)


if __name__ == "__main__":
    unittest.main()

이제 자신의 Github의 hangman_web 레포지토리로 이동해서 Actions 탭에 들어가보자.

Configure 버튼을 누르게 되면 .github/workflows 아래의 python-app.yml 파일을 수정할 수 있는 에디터를 볼 수 있다.

기본 내용에서 살짝 다르게 변형해주자. 변경한 내용을 좀 해석해보면 다음과 같다.

# workflow 이름
name: Python application

# 트리거 이벤트 (main 브랜치를 대상으로 push와 pull_request 이벤트 발생 시 workflow 작동)
on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

permissions:
  contents: read

# workflow 내용
jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python 3.10
      uses: actions/setup-python@v3
      with:
        python-version: "3.10"
    
    # 라이브러리 설치    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install flake8
        if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
    - name: Lint with flake8
      run: |
        # stop the build if there are Python syntax errors or undefined names
        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
    - name: Test with unittest
      
      # 현재 디렉토리에서 서브 디렉토리를 다 뒤져서 test로 시작하는 모든 파일을 실행해라
      run: |
        python -m unittest discover -p 'test*.py'

commit changes 버튼을 누르면 .github/workflows 폴더 안에 만든 python-app.yml 파일이 생성된 것을 볼 수 있다.

이 workflow를 테스트 해보기 위해 readme.md 파일을 수정해서 main branch에 push 해보자. push를 하면 Actions 탭에서 Workflow가 실행된 것을 볼 수 있다.

안에 들어가서 자세한 정보를 보게 되면 flake8을 이용해서 문법 검사를 진행하고 unittst 탭에서 test.py의 테스트 5개가 성공적으로 완료된 것을 확인할 수 있다.


Github Actions - Dockerization 추가

이제는 Hangman Github repo에서 Girhub Actions를 통해 Docker Image를 빌드하고 푸시하는 것을 구현해보자. 템플릿은 Docker Image라는 템플릿을 이용하자.

Docker 관련 스텝들을 살펴보면

  • docker login
    • 이 때 Docker hub ID와 PASSWORD를 읽어와야함. 하드코딩하지 않고 Github내에 저장해 두자
      • secrets.DOCKER_USER
      • secrets.DOCKER_PASSWORD
  • docker build
  • docker push

위의 과정을 .github/workflows/docker-image.yml파일에 기술하면 된다. (steps 밑에 name)

 

먼저 Docker Hub ID와 PASSWORD를 Github내에 저장해보자. Setting 탭에서 Security 항목의 Secrets and variables를 선택하면 ID와 PASSWORD를 변수로 저장해 놓을 수 있다. YML 파일 안에서는 아래로 접근할 수 있다.

  • ${{secrets.DOCKER_USER}}
  • ${{secrets.DOCKER_PASSWORD}}

변수명과 값을 입력한 뒤 생성해주자


이제 Docker Image 템플릿을 이용해서 새로운 docker-image.yml 이라는 Workflow를 만들 차례이다.

Configure을 누른 뒤 파일 내용을 다음과 같이 변경해주자.

name: Docker Image CI

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:

  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: docker login
      env: 
        DOCKER_USER: ${{secrets.DOCKER_USER}}
        DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}}
      run: |
        docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
    - name: Build the Docker image
      run: docker build --tag ${{secrets.DOCKER_USER}}/hangman:latest .
    - name: docker push
      run: docker push ${{secrets.DOCKER_USER}}/hangman:latest

이 Workflow yml파일을 만드는 것도 메인 브랜치에 push를 하는 것이기 때문에 commit changes를 누르면 만든 두가지의 workflow가 작동하는 것을 볼 수 있다.

Docker Hub에서도 정상적으로 push가 이루어졌는 지를 확인할 수 있다.

정말 제대로 완료가 되었는지를 알아보기 위해 이를 터미널에서 다시한번 확인해보자.

0개의 댓글