▶︎ 개요
FastAPI 파이썬 웹 프레임워크를 사용하여 간단한 프로젝트를 진행해보자.
/
, /db
두 개의 라우팅을 만들거고 /db
라우팅은 접근 시 바로 db에 접근하도록 구현할 것이다.
그리고 파드가 비정상적으로 종료되어 데이터가 유실되는 것을 방지하기 위해 NFS Volume을 사용하여 마스터 노드 서버의 /mnt/shared/fastapi-db
경로를 db 컨테이너의 /var/lib/mysql
경로와 마운트 시켜 데이터 유실을 방지할 것이다.
또 프로젝트 소스코드 변경이 컨테이너에서도 실시간으로 이루어지게 하기 위해 /mnt/shared/fastapi-app
경로를 fastapi 컨테이너의 /apps
경로와 마운트해보자.
‣ 프로젝트 구조
$ tree .
.
|-- mysql-deployment.yaml
|-- mysql-volume.yaml
|-- was-deployment.yaml
|-- was-secret.yaml
`-- was-volume.yaml
‣ 이미지 빌드
main.py
from fastapi import FastAPI
from dataclasses import dataclass
import pymysql
import os
def get_env() -> tuple[str | int]:
return (
os.getenv('MYSQL_HOST'),
os.getenv('MYSQL_PORT'),
os.getenv('MYSQL_USER'),
os.getenv('MYSQL_PASSWORD'),
os.getenv('MYSQL_DATABASE'),
)
@dataclass
class Data():
host: str
port: int
user: str
password: str
database: str
data : Data = Data(*get_env())
def connect_db() -> pymysql.Connection:
con = pymysql.connect(
host=data.host,
port=int(data.port),
user=data.user,
password=data.password,
database=data.database,
charset='utf8'
)
return con
app = FastAPI()
@app.get('/')
def say_hello() -> dict[str,str]:
return {"Message": "안녕하세요"}
@app.get('/db')
def connect() -> None | dict[str,str]:
try:
con : pymysql.Connection = connect_db()
cursor = con.cursor()
query = "SELECT * FROM testTBL;"
cursor.execute(query)
result = cursor.fetchone()
print(result)
return {"Message": "DB connection complete!}
except Exception as e:
print(e)
return {"Message": "DB connection failed!"}
Dockerfile
FROM python:3.10-slim-buster
WORKDIR /usr/src/app
COPY ./requirements/*.txt ./requirements.txt
RUN pip install --upgrade pip && pip install -r ./requirements.txt
COPY ./ ./
EXPOSE 8080
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
먼저 로컬에서 작업한 프로젝트를 도커 이미지화 시켜 docker hub에 업로드 시킬거다.
$ docker buildx build --platform linux/amd64 --push --tag yshrim12/was .
▶︎ 사전 준비
- K8s 클러스터
- nfs-server
K8s 클러스터 환경이 구축되어 있어야 하고 마스터 노드에 nfs-utils
패키지가 설치되어 있어야 한다.
MountPoint 생성
$ mkdir /mnt/shared/fastapi-app
$ mkdir /mnt/shared/fastapi-db
/etc/exportfs
# 아래 내용 추가
/mnt/shared 192.168.56.30/24(rw,sync,no_root_squash,no_subtree_check)
exportfs 적용
$ exportfs -a
nfs-server 재시작
$ systemctl restart nfs-server
▶︎ Manifest 작성
이미지 배포가 완료되었다면 오브젝트 스펙을 작성해보자.
‣ MySQL
• PV
mysql-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: db-storage
labels:
type: db-nfs
spec:
capacity:
storage: 10Gi
accessModes: ["ReadWriteMany"]
nfs:
server: "192.168.56.30"
path: "/mnt/shared/fastapi-db"
spec.capacity
: 이 PV의 용량은 10 기가바이트(GiB)입니다.spec.accessModes
: ["ReadWriteMany"]: 이 PV는 여러 개의 Pod에서 동시에 읽고 쓸 수 있는 'ReadWriteMany' 액세스 모드를 지원합니다.spec.nfs
: PV가 NFS(Network File System) 프로토콜을 사용하는 것을 나타냅니다.spec.nfs.server
: "192.168.56.30": NFS 서버의 IP 주소는 "192.168.56.30"입니다.spec.nfs.path
: "/mnt/shared/wtt-db-volume": NFS 서버의 공유 디렉토리 경로는 "/mnt/shared/fastapi-db"입니다.
• PVC
mysql-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-claim
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 4Gi
storageClassName: ""
selector:
matchLabels:
type: db-nfs
spec.accessModes
: ["ReadWriteMany"]: 이 PVC는 여러 개의 Pod에서 동시에 읽고 쓸 수 있는 'ReadWriteMany' 액세스 모드를 요청합니다.spec.resources
: PVC에서 요청하는 리소스를 정의합니다.spec.resources.requests
: 이 PVC가 요청하는 스토리지 용량은 4 기가바이트(GiB)입니다.spec.storageClassName
: "": 이 PVC는 특정 storage class를 지정하지 않습니다.spec.selector
: PVC가 특정 PersistentVolume과 매칭되어야 할 때 선택적으로 사용됩니다.spec.selector.matchLabels
: 이 PVC는 type: db-nfs 라벨을 가진 PersistentVolume과 매칭되어야 합니다. 즉, type: db-nfs 라벨을 가진 PersistentVolume과 연결될 것을 요청합니다.
• Deployment
mysql-deployment.yaml
#================
# MySQL Service
#================
apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
ports:
- name: db-port
port: 3306
targetPort: 3306
selector:
app: mysql
type: ClusterIP
---
#================
# MySQL Deployment
#================
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-pod
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: docker.io/mysql:latest
ports:
- containerPort: 3306
protocol: TCP
volumeMounts:
- name: db-volume
mountPath: /var/lib/mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: secret
- name: MYSQL_DATABASE
value: testDB
volumes:
- name: db-volume
persistentVolumeClaim:
claimName: db-claim
spec.template.spec.containers.volumeMounts
: 컨테이너에 마운트할 볼륨을 정의합니다. 여기서는 이름이 "db-volume"인 - PersistentVolumeClaim을 "/var/lib/mysql" 경로에 마운트합니다.spec.template.spec.containers.env
: 컨테이너에서 사용할 환경 변수를 정의합니다. 여기서는 MySQL 루트 비밀번호와 데이터베이스 이름을 설정합니다.spec.template.spec.volumes
: Pod에 연결할 볼륨을 정의합니다.spec.template.spec.volumes.name
: db-volume: 이 볼륨의 이름은 "db-volume"입니다.spec.template.spec.volumes.persistentVolumeClaim
: 이 볼륨은 PVC인 "db-claim"과 연결됩니다.
‣ WAS
• PV
was-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: was-storage
labels:
type: was-nfs
spec:
capacity:
storage: 10Gi
accessModes: ["ReadWriteMany"]
nfs:
server: "192.168.56.30"
path: "/mnt/shared/fastapi-app"
spec.capacity
: 이 PV의 용량은 10 기가바이트(GiB)입니다.spec.accessModes
: ["ReadWriteMany"]: 이 PV는 여러 개의 Pod에서 동시에 읽고 쓸 수 있는 'ReadWriteMany' 액세스 모드를 지원합니다.spec.nfs
: PV가 NFS(Network File System) 프로토콜을 사용하는 것을 나타냅니다.spec.nfs.server
: "192.168.56.30": NFS 서버의 IP 주소는 "192.168.56.30"입니다.spec.nfs.path
: "/mnt/shared/wtt-app": NFS 서버의 공유 디렉토리 경로는 "/mnt/shared/fastapi-app"입니다.
• PVC
was-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: was-claim
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 4Gi
storageClassName: ""
selector:
matchLabels:
type: was-nfs
spec.accessModes
: ["ReadWriteMany"]: 이 PVC는 여러 개의 Pod에서 동시에 읽고 쓸 수 있는 'ReadWriteMany' 액세스 모드를 요청합니다.spec.resources
: PVC에서 요청하는 리소스를 정의합니다.spec.resources.requests
: 이 PVC가 요청하는 스토리지 용량은 4 기가바이트(GiB)입니다.spec.storageClassName
: "": 이 PVC는 특정 storage class를 지정하지 않습니다.spec.selector
: PVC가 특정 PersistentVolume과 매칭되어야 할 때 선택적으로 사용됩니다.spec.selector.matchLabels
: 이 PVC는 type: was-nfs 라벨을 가진 PersistentVolume과 매칭되어야 합니다. 즉, type: was-nfs 라벨을 가진 PersistentVolume과 연결될 것을 요청합니다.
• WAS Deployment
was-deployment.yaml
#================
# WAS Service
#================
apiVersion: v1
kind: Service
metadata:
name: was-service
spec:
ports:
- port: 8080
targetPort: 8080
nodePort: 30002
selector:
app: fastapi
type: NodePort
---
#================
# WAS Deployment
#================
apiVersion: apps/v1
kind: Deployment
metadata:
name: was-fastapi
spec:
replicas: 1
selector:
matchLabels:
app: fastapi
template:
metadata:
labels:
app: fastapi
spec:
containers:
- name: fastapi
image: docker.io/yshrim12/was:latest
resources:
limits:
cpu: "0.5"
memory: 1Gi
ports:
- containerPort: 8080
volumeMounts:
- name: was-volume
mountPath: /apps
env:
- name: MYSQL_HOST
valueFrom:
secretKeyRef:
name: was-secret
key: MYSQL_HOST
- name: MYSQL_PORT
valueFrom:
secretKeyRef:
name: was-secret
key: MYSQL_PORT
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: was-secret
key: MYSQL_USER
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: was-secret
key: MYSQL_PASSWORD
- name: MYSQL_DATABASE
valueFrom:
secretKeyRef:
name: was-secret
key: MYSQL_DATABASE
volumes:
- name: was-volume
persistentVolumeClaim:
claimName: was-claim
spec.containers.resources
: 컨테이너가 사용할 수 있는 CPU 및 메모리 리소스의 제한을 정의합니다.spec.containers.ports
: 컨테이너가 노출할 포트를 정의합니다. 여기서는 8080번 포트를 사용합니다.spec.containers.volumeMounts
: 컨테이너에 마운트할 볼륨을 정의합니다. 여기서는 이름이 "was-volume"인 PersistentVolumeClaim을 "/apps" 경로에 마운트합니다.spec.containers.env
: 컨테이너에서 사용할 환경 변수를 정의합니다. 여기서는 MySQL 호스트, 포트, 사용자 이름, 암호, 데이터베이스 이름을 시크릿에서 가져옵니다.spec.volumes
: Pod에 연결할 볼륨을 정의합니다.spec.volumes.name
: was-volume: 이 볼륨의 이름은 "was-volume"입니다.spec.volumes.persistentVolumeClaim
: 이 볼륨은 PVC인 "was-claim"과 연결됩니다.
‣ 배포하기
$ kubectl apply -f .
service/mysql-service created
deployment.apps/mysql-pod created
persistentvolume/db-storage created
persistentvolumeclaim/db-claim created
service/was-service created
deployment.apps/was-fastapi created
secret/was-secret created
persistentvolume/was-storage created
persistentvolumeclaim/was-claim created