1. 웹서비스를 도커로 구동

  • hangman_web이라는 repo의 main에 코드가 머지될 때마다 다음을 수행
    • 테스트 수행
    • Docker Image 빌드
    • Docekr Image를 Docker Hub로 푸시
  • 위 과정을 Github repo에 Github Actions로 구현

스크린샷 2023-12-19 오후 1 40 25

2. Hangman 프로그램

  • flask를 사용하여 웹으로 노출
    • 포트 번호는 어디든 바인딩 가능하며, 실행할 때 지정
    • requirements.txt로 flask 관련 모듈 설치 필요
  • 실행 방법
    • python3 -m flask run --host=0.0.0.0 --port=4000


  • app.py
    • flask의 메인 함수가 있고, 커맨드라인으로 받은 포트에 바인드하고 요청이 들어오기를 기다림
  • reqirements.txt
    • pip3 install requirements.txt
    • flask 모듈들 설치
  • test.py
    • app.py에 있는 코드의 유닛 테스트 로직이 들어가 있음
    • CI/CD 구성 시 실행되게 구성할 예정

requirements.txt

Flask==2.3.2
Flask-HTTPAuth==4.5.0
Flask-Login==0.6.2
Flask-SQLAlchemy==3.0.3

app.py

from flask import Flask, session

app = Flask(__name__)

...

app.secret_key = "Python study"
if __name__ == "__main__":
    app.run()

프로그램 실행

https://labs.play-with-docker.com

git clone https://github.com/bokyung124/hangman_web.git
pip3 install requirements.txt
python3 -m flask run --host=0.0.0.0 --port=4000


스크린샷 2023-12-19 오후 1 56 35 open port

스크린샷 2023-12-19 오후 1 56 43

2. Hangman 서비스를 Docker Image로 구성

Docker 컨테이너 내부 프로세스와 호스트 프로세스 간의 통신

  • Docker 컨테이너로 포트 4000에 실행된 flask app이 있을 때
  • 이 앱을 호스트 운영체제에서 접근하려면?

  • Docker 컨테이너 내부 프로세스가 오픈한 포트 번호는 외부 프로세스에서는 안보임
  • 컨테이너 프로세스가 오픈한 포트 번호를 외부로 노출해주는 것이 포트맵핑
    • docker run 수행 시 -p 옵션 사용!
    • docker run -p 4000:4000 [image]

스크린샷 2023-12-19 오후 2 07 02


FROM python:3.8-slim-buster
LABEL Maintainer="leebk1124@gmail.com"
WORKDIR /app
COPY app.py ./
COPY requirements.txt ./
RUN pip3 install -r /app/requirements.txt
EXPOSE 4000    
# EXPOSE: 동작에는 영향을 미치지 않음!
CMD ["python3", "-m", "flask", "run", "--host=0.0.0.0", "--port=4000"]


  • 이미지 정보 확인: docker inspect [image]
  • background 실행: run에서 -d 옵션

3. CI/CD

소프트웨어 빌드

  • 개발한 소프트웨어를 최종적으로 출시하기 위한 형태로 만드는 것
    • 테스트가 빌드의 중요한 일부로 포함
    • 기대하는대로 코드가 돌아감을 보장
  • 참여 개발자들이 많을수록 더 중요함
  • 개발이 끝나기 전부터 빌드를 하면 소프트웨어의 안정성 증대
    • Continuous Integration

Continuous Integration

  • Software Engineering Practice의 하나
  • 기본 원칙
    • 코드 Repo는 하나만 유지 (Master)
    • 코드 변경을 최대한 자주 반영
      • Master와 branch 최대한 sink
        • 너무 차이가 나면 merge할 때 힘듦 & 코드 리뷰 어려움
    • 테스트를 최대한 추가
      • Test Coverage
        • 75% -> 우리가 갖고 있는 코드의 75%가 테스트로 커버됨
      • 최소 Test Coverage를 정하고 빌드하는 것이 일반적
      • 코드 변경을 push 할 때마다 자동으로 품질을 체크하는 것
    • 빌드를 계속적으로 수행 (자동화)
      • Commit Build vs. Nightly Build
    • 성공한 빌드의 프로덕션 릴리스 (자동화)
      • CD: Continous Delivery

빌드 실패

  • 새 코드의 커밋으로 인해 테스트가 실패하는 경우
  • 많은 회사들이 빌드 실패 시 빌드가 다시 성공할 때까지 코드 변경 금지
    • 즉 빌드 실패는 모든 사람들을 잡아두는 족쇄
    • 어느정도 조직이 커지면 빌드만 전담하는 엔지니어가 생김
      • 업무 중 하나는 빌드 실패 시 누가 주범인지 알아내는 것
    • 빌드 실패 시 가벼운 형태로 패널티 부여
  • push하기 전 로컬 환경에서 테스트가 성공하는지 돌려보는 습관을 갖는 것이 중요!

CI/CD

스크린샷 2023-12-20 오전 8 53 40

  • DevOps: 프로세스 생성, 배포 과정 자동화, 모니터링
  • 소프트웨어 배포에 점점 Docker Image가 쓰이고 있고, 소프트웨어 환경은 K8s와 같은 Container Orchestration 서비스가 사용되고 있음

4. Git

  • 분산환경을 지원하는 소스 버전 컨트롤 시스템
    • CVS, SVN은 항상 서버에 연결되어 있다는 전제 하에 사용 가능 (중앙개발)
  • CSV, SVN에 비해 현저하게 빠르나 사용법은 더 복잡함

  • 장점
    • 다수의 개발자가 공동 개발
    • 코드 리뷰 가능
    • 코드 백업
    • 과거의 코드로 롤백 가능 (버전 간 이동 가능)
    • 코드뿐만 아니라 모든 텍스트 파일에 사용 가능

Github

  • Git Repo 호스팅/클라우드 서비스
    • 대부분의 회사들이 Git을 직접 설치하기 보다는 Github tkdyd
  • 웹기반 인터페이스도 제공
  • 자신이 만든 레포들이 모두 public일 경우 사용이 무료
  • 다양한 툴을 제공하여 생태계 마련
    • Copilot
    • CI/CD 등의 다양한 연동을 위한 Workflows 제공
    • Wikis, Issues 기능 등

Git 관련 용어

  • Repo: Git으로 관리되는 소프트웨어 프로젝트
  • Master/Main: 한 Repo에서 기본이 되는 메인 코드
    • Git은 master, Github에서는 이제 main
  • Branch
    • 자신의 Repo에서 새로운 기능 개발 등을 위해 master 혹은 다른 branch로부터 만든 코드 작업본
    • 작업 후 나중에 원본 branch와 다시 병합하려는 목적으로 만들어짐
  • Clone: 다른 계정에 존재하는 repo로부터 새로운 local repository를 만드는 것
  • Commit (Check-in): 내가 만든 코드 변경을 branch의 local repository에 반영하는 것


  • 작업은 항상 내 컴퓨터의 Local Repo에서 일어나며 Pull과 Push를 통해 서버 상의 Remote Repo와 연결

  • Pull: Master와 같은 Remote Repo로부터 마지막 Pull 이후 변경된 것을 다시 가져오는 작업, 즉 Master 혹은 branch와 sink하는 것
  • Push: 작업 중인 Local Repo에서 Remote Repo로 변경사항들을 복사하는 것
  • Merge: Pull이나 Push했을 경우 두 branch간의 충돌을 해결하는 과정
    • 많은 경우 자동으로 해결되나, 몇몇 경우에는 손으로 직접 충돌을 해결해야 함

Main Branch

  • 특정한 기능을 구현하기 위해 만들어진 mainline의 복사본
  • 구현과 테스트가 이루어진 후에 main branch와 merge됨

스크린샷 2023-12-20 오전 9 36 53


전체 플로우

스크린샷 2023-12-20 오전 9 37 43

CI/CD에서 Github

Push나 Merge 시점이 CI/CD를 실행하기 위한 절호의 순간

  • 코드가 main 브랜치에 추가되는 순간 CI/CD를 트리거
    • 이를 main이나 특정 브랜치만 대상으로 하도록 설정 가능
    • 이 때 테스트를 수행하고 최종적으로 Docker Image 등을 만들도록 하는 것이 가능
    • 그래서 CI/CD는 Github에 구현하는 것이 가장 자연스러움
  • 이를 Actions라는 기능을 통해 Workflow라는 이름으로 구현 가능

5. Github Actions

  • CI/CD를 Github 위에서 구현하기 위한 서비스
    • 코드 테스트, 빌드, 배포 자동화 기능 제공
  • Workflow라 아래 부르며 아래 컴포넌트로 구성
    • Events
    • Jobs
    • Actions
    • Runner
      • Github hosted runners (유료)
      • Self hosted runners (무료)

workflow

  • 트리거 이벤트가 발생하면 시작되는 일련의 동작들
    • 일련의 동작: Jobs에 기술
  • 트리거 이벤트의 예
    • 코드 커밋 (main, dev와 같은 특정 브랜치를 대상으로만 제한 가능)
    • PR 생성
    • 다른 Workflow의 성공적인 실행 (chaining)
  • Workflow를 위한 명령어들을 YAML 파일로 저장
    • 명령어들로는 환경 설정과 scripts 실행 등이 대표적
  • Workflow는 Job들로 나눠지며, 각 Job은 일련의 스텝을 수행
    • 각 스텝은 하나 이상의 명령어 실행
      • 이 명령어는 actions라고 부르는 명령어들의 집합이 될 수 있음
    • 각 스텝은 윈도우나 리눅스 서버 위에서 runner에 의해 실행
      • 이것을 Docker Image에서 수행하는 것이 서비스 배포 과정에 따라 더 일반적이기도 함

Github에서

  • Repo 단위로 적용
  • 적용하려는 repo에서 Actions 메뉴 선택
  • Workflow todtjd
  • yml 파일 직접 생성 혹은 템플릿 (CI Templates) 선택 후 수정
    • Python Application / Docker Image

6. Test 추가

사용해 볼 CI Template

  • Python Application
    • CI할 때 기본이 되는 코드들을 최대한 채워준 템플릿
  • 테스트 코드 실행 이외에서 flake8을 사용해서 코딩 스타일을 체크해 볼 예정
  • 기본으로 pytest를 테스트 프레임워크로 설치
    • 우리는 unittest로 작성되어 있음
  • Python coding linting tool로 flake8을 설치하고, 문법 에러와 코딩 스타일 체크

파이썬 코드 스타일 체크 - flake8

  • 파이썬 코드에서 에러나 코딩 스타일 등에서 이슈를 체크해주는 툴
    • Linting tool이라고 부름 (언어 별로 존재)
  • ex) flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
    • 한 줄의 최대 길이는 127글자, 넘으면 Error
    • 최대 10까지는 봐주고, 그것보다 더 복잡하면 Error


import random

def lower(a)         # 콜론 빠짐!
    return a.lower()

lower("aBC")
flake8 smaple.py

# sample.py:3:12: E999 SyntaxError: invalid syntax

테스트 코드

  • unittest 사용
    • 요즘은 pytest를 많이 쓰는 듯
python3 -m unittest test.py
# 혹은 python3 test.py


  • test.py
"""Unit test cases for hangman game."""
import unittest
import app as hangman
# app.py를 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 actions 생성

스크린샷 2023-12-20 오전 10 13 08 python application 선택


workflow yml 파일 저장 후 README 파일 수정 (push를 위해)

# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: Python application

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

permissions:
  contents: read

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - 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 pytest
      run: |
        python -m unittest discover -p 'test.py'


스크린샷 2023-12-20 오전 11 09 55 github actions 실행중

스크린샷 2023-12-20 오전 11 11 08 빌드 끝

스크린샷 2023-12-20 오전 11 12 04 flake8 테스트, unittest 모두 통과

7. Github Actions - Dockerizaton 추가

  • Docker Image Template 사용

Docker 관련 스텝

  • docker login
    • Docker hub ID와 PW를 읽어와야 하는데, 이를 Github 내에 저장
      • secrets.DOCKER_USER
      • secrets.DOCKER_PASSWORD
  • docker build
  • docker push


  • 위 과정을 .github/workflows/docker-image.yml에 기술

Docker Hub 정보 보관

[Repo] - [Settings] - [Security] - [Secrets and variables] - [Actions]


  • YML 파일 안에서는 아래로 접근 가능
    • $
    • $


스크린샷 2023-12-20 오전 11 45 54

스크린샷 2023-12-20 오전 11 46 22 New repository secret 선택

스크린샷 2023-12-20 오전 11 46 42

스크린샷 2023-12-20 오전 11 47 15 변수 등록

docker-image.yml

스크린샷 2023-12-20 오전 11 40 05 docker image 선택


name: Docker Image CI

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

jobs:

  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: docker login
      env:
        DOCKER_USER: $
        DOCKER_PASSWORD: $
      run:
        docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
    - name: Build the Docker Image
      run: docker build --tag $/hangman:latest
    - name: docker push
      run: docker push $/hangman:latest


스크린샷 2023-12-20 오후 12 01 27 push 완료