1. Hangman 서비스
2. CI/CD와 Github Actions
3. Github Actions 사용- Python Application - Docker Image
Hangman이라는 웹 서비스를 Docker로 구동하는 것입니다.

Push를 통해 GitHub의 코드의 변경이 발생하면
그 Action이 Trigger가 되어서
Docker image를 빌드하고 Docker hub에 Push합니다.
Continuous delivery가 되는 환경이라면,
Production 서버에 명령을 보내
Docker hub의 최신 이미지를 내려 받아
컨테이너를 돌릴 수 있도록 합니다.
위 과정들을 자동화시킵니다.
동작하지 않는 미완성 Hangman 게임 프로그램
실행 방법
python3 -m flask run --host=0.0.0.0 --port=4000app.py
flask의 메인 함수가 들어있는 코드,
Commandline으로 받은 포트번호에 bind하고 요청을 기다림
test.py
app.py에 있는 코드의 유닛 테스트 로직을 담당,
CI/CD 구성시 자동으로 실행이 되게 구성
Container는 완전히 별개의 공간이기 때문에 Port번호를 연다고 해서 Container 밖에서 보이지 않습니다.

-> 포트 포워딩이 필요
포트 맵핑(포트 포워딩)
-p 옵션을 사용docker run -p host_port:container_port image_name-p 옵션이란?
호스트(host) 컴퓨터에서 컨테이너에서 리스닝하고 있는 포트로 접속할 수 있도록 설정하는 옵션
docker run -p 4000:4000 skqltldnjf77/hangman

FROM python:3.8-slim-buster
# docker inspect로 찾을 수 있는 사용자정보 메타데이터
LABEL Maintainer="skqltldnjf77@gmail.com"
COPY . /app
WORKDIR /app
# build시 실행
RUN pip3 install -r requirements.txt
# 포트번호를 4000쓴다고 알림
EXPOSE 4000
# container 실행시 실행
CMD ["python3", "-m", "flask", "run", "--host=0.0.0.0", "--port=4000"]
# 이미지 이름을 hangman이라 지정,
# 현재 경로의 dockerfile 기반 이미지 생성
docker build -t hangman .
# docker hub에 업로드를 위한 이미지 이름 변경
docker tag hangman:latest skqltldnjf77/hangman:latest
# 동작 확인
docker run -p 4000:4000 skqltldnjf77/hangman
# detach 명령어를 통해 백그라운드에서 돌아가게도 가능
docker run -p 4000:4000 -d skqltldnjf77/hangman

# docker hub에 업로드
docker push skqltldnjf77/hangman

https://labs.play-with-docker.com/ 에서 이미지를 실행해보겠습니다.

개발한 소프트웨어를 최종적으로 출시하기 위한 형태로 만드는 것
( 배포하기 이전에 패키지로 만드는 것)
개발이 끝나기 전부터 빌드를 진행하면 소프트웨어의 안정성이 증대됨
Software Engineering Practice의 하나
기본 원칙
코드 Repo는 하나만 유지 (Master)
( 코드를 수정하고 싶을 때, Master를 건드리지 말고
Branch를 만들어서 작업하고 Merge하도록 )
코드 변경은 자주, 조금씩하는 것이 좋음
테스트를 최대한 추가 (핵심)
빌드를 계속적으로 수행 (자동화)
성공한 빌드의 프로덕션 릴리스 (자동화)

새 코드의 커밋으로 인해 테스트가 실패하는 경우
대부분의 경우 빌드 실패시 빌드가 다시 성공할 때까지 코드 변경을 금지
빌드 실패는 모든 사람을 잡아두는 족쇄
빌드 실패시 가벼운 형태의 페널티 부여하는 방식도 좋음
Github에 Push, Merge 시점이 CI/CD를 실행하기 위한 절호의 순간입니다.
코드가 Main/Master 브랜치에 추가되는 순간 CI/CD를 트리거
이때, 테스트를 수행하고 최종적으로 Docker Image 등을 만들도록 하는 것이 가능
그래서 CI/CD는 Github에 구현하는 것이 가장 자연스러움
Github에서 이를 가능하게 해주는 것이 Actions라는 기능
Actions :
특정 이벤트(Merge, Commit)를 대상으로 조건을 만족하는 순간,
Workflow를 실행할 수 있고 다수의 Workflow를 세팅 가능
Workflow : CI/CD를 트리거하는 역할
Workflow는 트리거 이벤트가 발생하면 시작되는 일련의 동작들을 지칭
트리거 이벤트의 예시
Workflow를 위한 명령어들은 YAML 파일로 저장
Workflow는 하나 이상의 Job으로 이루어집니다.
Workflow ∋ Job ∋ Step(명령어 실행)

Actions 메뉴 선택
workflow 생성
yml 파일을 직접 생성 혹은 템플릿(CI Templates) 선택 후 수정
Github Actions를 사용해 Main 브랜치에 push, PR이 발생하는 경우
test.py를 실행하여 테스트가 동작할 수 있도록 해보겠습니다.
Python Application이라는 CI Template을 사용해보겠습니다.
( Python Application 상세 )
기본으로 pytest가 테스트 프레임웍으로 설치됨
Python code linting tool으로 flake8 설치
e.g.)
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics

일반적으로 pytest를 많이 사용하는 추세이지만,
이번 경우는 unittest로 진행합니다.
# 메인 메소드가 정해지지 않은 경우에도 사용
python3 -m unittest test.py
# 메인 메소드가 정해진 경우 사용 가능
python3 test.py
# 메인 메소드가 정해져있고 메인 메소드가 실행이될 때
# 테스트 코드가 돌아가므로 둘 다 사용해도 무방합니다.
Github Repo에서 Actions를 선택하고
CI Template으로 Python Application 선택합니다.
이제 이 python-app.yml을 편집할 것입니다.

yml (yaml) 파일 포맷
환경설정 파일로 많이 사용
간단한 작성 예시
https://velog.io/@skqltldnjf77/YML-or-YAML-%EA%B0%84%EB%8B%A8-%EC%9E%91%EC%84%B1%EB%B2%95
python-app.yml을 편집
name: Python application
# "on" : 트리거 이벤트 지정
# main브랜치에 push, PR가 생긴 경우 발생
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
# jobs : 실제 CI 프로세스가 스텝(name) 별로 기술되는 곳
jobs:
build:
runs-on: ubuntu-latest
# 명령어들이 모여있는 Steps
# 각 명령이 name별로 구분됨
steps:
- uses: actions/checkout@v4
# Python 3.10 설치
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
# 변경 사항 : 기존에 pytest가 있던 부분을 삭제
# flake8을 설치,
# requirements.txt가 있으면 설치
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 #pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
# flake 8를 두번 실행해서 문법, 코딩 스타일 체크
- 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
# 변경 사항 : 기존 pytest를 unittest로 변경
# run에 test.py가 동작할 수 있도록 새로운 명령을 부여
# test로 시작하는 모든 python 파일을 실행
- name: Test with unittest
run: |
python -m unittest discover -p 'test*.py'
이제 Github repo의 main branch에 push, PR이 발생하면 매번 테스트가 동작하게 될 것입니다.
이를 확인하기 위해 Readme.md를 수정해보겠습니다.
그 결과, PR 후 테스트가 자동으로 동작한 것을 확인할 수 있습니다.

Github에서는 Docker에 대한 정보를 가지고 있지 않으므로 Docker hub ID, PW와 같은 정보를 전달해주어야합니다.
docker login
docker build
docker push
=> 이 과정을 docker-image.yml에 기술

Github에 Settings를 선택해서
Security -> Secrets and variables -> Actions으로 접근 후,
New respository secret을 선택합니다.
여기서 Docker hub ID, PW를 저장해주면
YML 파일 안에서 ${{secrets.DOCKER_USER}}의 형태로 접근할 수 있게 됩니다.
ID, PW를 각각 작업해주어야합니다.

Github Repo의 Actions에서 New workflow를 생성해줍니다.
CI Template으로 Docker Image 선택합니다.
이제 이 docker-image.yml을 편집할 것입니다.
docker-image.yml 편집name: Docker Image CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
# 변경 사항 :
# name: docker login 추가
# name: Build the Docker image 수정
# name: docker push 추가
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
Docker Image 빌드 후 Docker hub로 Push까지 되는 것을 확인할 수 있습니다.
