[DEV] 11주차. Docker & K8s (2)
1. 웹서비스를 도커로 구동
- hangman_web이라는 repo의 main에 코드가 머지될 때마다 다음을 수행
- 테스트 수행
- Docker Image 빌드
- Docekr Image를 Docker Hub로 푸시
- 위 과정을 Github repo에 Github Actions로 구현
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
open port
2. Hangman 서비스를 Docker Image로 구성
Docker 컨테이너 내부 프로세스와 호스트 프로세스 간의 통신
- Docker 컨테이너로 포트 4000에 실행된 flask app이 있을 때
-
이 앱을 호스트 운영체제에서 접근하려면?
- Docker 컨테이너 내부 프로세스가 오픈한 포트 번호는 외부 프로세스에서는 안보임
- 컨테이너 프로세스가 오픈한 포트 번호를 외부로 노출해주는 것이 포트맵핑
- docker run 수행 시
-p
옵션 사용! docker run -p 4000:4000 [image]
- docker run 수행 시
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할 때 힘듦 & 코드 리뷰 어려움
- Master와 branch 최대한 sink
- 테스트를 최대한 추가
- Test Coverage
- 75% -> 우리가 갖고 있는 코드의 75%가 테스트로 커버됨
- 최소 Test Coverage를 정하고 빌드하는 것이 일반적
- 코드 변경을 push 할 때마다 자동으로 품질을 체크하는 것
- Test Coverage
- 빌드를 계속적으로 수행 (자동화)
- Commit Build vs. Nightly Build
- 성공한 빌드의 프로덕션 릴리스 (자동화)
- CD: Continous Delivery
빌드 실패
- 새 코드의 커밋으로 인해 테스트가 실패하는 경우
- 많은 회사들이 빌드 실패 시 빌드가 다시 성공할 때까지 코드 변경 금지
- 즉 빌드 실패는 모든 사람들을 잡아두는 족쇄
- 어느정도 조직이 커지면 빌드만 전담하는 엔지니어가 생김
- 업무 중 하나는 빌드 실패 시 누가 주범인지 알아내는 것
- 빌드 실패 시 가벼운 형태로 패널티 부여
- push하기 전 로컬 환경에서 테스트가 성공하는지 돌려보는 습관을 갖는 것이 중요!
CI/CD
- 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됨
전체 플로우
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 생성
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'
github actions 실행중
빌드 끝
flake8 테스트, unittest 모두 통과
7. Github Actions - Dockerizaton 추가
- Docker Image Template 사용
Docker 관련 스텝
- docker login
- Docker hub ID와 PW를 읽어와야 하는데, 이를 Github 내에 저장
secrets.DOCKER_USER
secrets.DOCKER_PASSWORD
- Docker hub ID와 PW를 읽어와야 하는데, 이를 Github 내에 저장
- docker build
- docker push
- 위 과정을 .github/workflows/docker-image.yml에 기술
Docker Hub 정보 보관
[Repo] - [Settings] - [Security] - [Secrets and variables] - [Actions]
- YML 파일 안에서는 아래로 접근 가능
- $
- $
New repository secret 선택
변수 등록
docker-image.yml
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
push 완료