1탄에서 쿠버네티스 개념을 정리했다면, 이번 2탄은 직접 FastAPI 앱을 Minikube 위에 올려보는 실습입니다.
- 실습 목표
- WSL2(Ubuntu 24.04) 안에 Minikube 클러스터 만들기
- FastAPI 앱을 Docker 이미지로 빌드해서 Minikube에 배포
- Service로 외부에서 접속해보기
- Pod를 일부러 죽여보고, 쿠버네티스의 Self-Healing 확인하기
0. 실습 환경
- Host OS: Windows 11
- WSL2: Ubuntu 24.04
- Minikube: v1.37.0 (linux/amd64)
- Docker: docker.io 패키지
- kubectl: v1.34.2 (클라이언트)
실습은 WSL2 Ubuntu 터미널 안에서만 진행한다고 생각하면 편합니다.
1. Minikube & Docker & kubectl 설치
1-1. Minikube 설치
# Minikube 바이너리 다운로드
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
# 실행 권한 부여
chmod +x minikube-linux-amd64
# /usr/local/bin 으로 이동 (PATH에 포함된 위치)
sudo mv minikube-linux-amd64 /usr/local/bin/minikube
# 버전 확인
minikube version
minikube version: v1.37.0
commit: 65318f4cfff9c12cc87ec9eb8f4cdd57b25047f3
1-2. Docker 설치 (없다면)
Minikube가 컨테이너를 띄우려면 컨테이너 런타임(Docker 등) 이 필요합니다.
Docker가 없다면 먼저 설치해줍니다.
sudo apt update
sudo apt install -y docker.io
# Docker 서비스 실행 & 부팅 시 자동 시작
sudo systemctl enable --now docker
# 현재 유저를 docker 그룹에 추가
sudo usermod -aG docker $USER
# 현재 셸에 바로 반영
newgrp docker
그리고 확인
docker version
docker run --rm hello-world
1-3. kubectl 설치
쿠버네티스 클러스터와 대화하는 CLI 도구입니다.
# 최신 stable 버전 다운로드
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/
kubectl version --client
출력 예시
Client Version: v1.34.2
Kustomize Version: v5.7.1
1-4. Minikube 클러스터 시작
minikube start
설치가 될때까지 기다립니다.
이후 아래 명령어
kubectl get nodes
예를 들어 이런 식으로 나오면 클러스터 준비 완료입니다.
NAME STATUS ROLES AGE VERSION
minikube Ready control-plane 2m v1.32.0
여기까지 환경 설정 완료 입니다.
2. 실습 디렉토리 구조 만들기
이번 실습에서 사용할 디렉토리 구조는 이렇게 됩니다.
fastapi-k8s
├── main.py
├── requirements.txt
├── Dockerfile
├── deployment.yaml
├── service.yaml # ClusterIP Service
└── service_nodeport.yaml # NodePort Service (옵션)
WSL2 Ubuntu에서
mkdir fastapi-k8s
cd fastapi-k8s
이 디렉토리 안에서 나머지 파일들을 만들어 줍니다.
3. FastAPI 앱 작성 (main.py, requirements.txt)
3-1. main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Hello, Kubernetes!!"}
@app.get("/health")
def health():
return {"status": "ok"}
- / 엔드포인트: 간단한 인사 메시지
- /health 엔드포인트: 헬스체크용(쿠버네티스 readiness/liveness probe에서 사용 예정)
3-2. requirements.txt
fastapi==0.104.0
uvicorn==0.24.0
FastAPI + uvicorn만 최소로 넣은 심플한 requirements입니다.
4. Dockerfile 작성 (FastAPI 앱 이미지 만들기)
Dockerfile
FROM python:3.11-slim
# 작업 디렉토리
WORKDIR /app
# 의존성 먼저 복사 & 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 애플리케이션 코드 복사
COPY main.py .
# 컨테이너 내부에서 열어줄 포트
EXPOSE 8000
# FastAPI 앱 실행 (main.py 안의 app 객체)
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
- main:app → main.py 안의 app = FastAPI() 를 가리키는 표현
- 컨테이너 내부 포트는 8000으로 통일
5. Minikube Docker 환경으로 이미지 빌드
Minikube가 자체 Docker 데몬을 쓰는 경우가 많아서,
이미지를 Minikube가 보는 Docker에다 빌드해주는 게 편합니다.
5-1. Minikube Docker 환경 사용 설정
eval $(minikube docker-env)
이걸 실행하면 현재 셸에서 docker 명령이 Minikube 안쪽 Docker를 바라보게 됩니다.
docker images
를 찍으면 Minikube가 사용하는 이미지 목록이 보일 거예요.
5-2. FastAPI 이미지 빌드
Deployment에서 쓸 이미지 태그와 맞춰주기 위해 v1 태그로 빌드합니다.
docker build -t fastapi-k8s:v1 .
빌드 후 다시 확인
docker images | grep fastapi-k8s
예시
fastapi-k8s v1 123abc... 1 minute ago 120MB
6. Deployment 작성 및 배포
이제 이 이미지를 쿠버네티스에 올리는 Deployment를 만듭니다.
6-1. deployment.yaml 작성
apiVersion: apps/v1
kind: Deployment
metadata:
name: fastapi-k8s
labels:
app: fastapi-k8s
spec:
replicas: 2
selector:
matchLabels:
app: fastapi-k8s
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
template:
metadata:
labels:
app: fastapi-k8s
spec:
containers:
- name: fastapi
image: fastapi-k8s:v1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8000
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 3
periodSeconds: 5
timeoutSeconds: 1
failureThreshold: 3
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
failureThreshold: 3
env:
- name: APP_ENV
value: "prod"
포인트만 짚어보면
- replicas: 2 → Pod를 항상 2개 유지
- image: fastapi-k8s:v1 → 아까 빌드한 이미지 태그와 일치해야 함
- readinessProbe, livenessProbe
- /health 엔드포인트로 앱 상태 체크
- 준비/생존 여부에 따라 트래픽 전달 여부를 조절
6-2. Deployment 배포
kubectl apply -f deployment.yaml
상태 확인
kubectl get pods -w
예시
NAME READY STATUS RESTARTS AGE
fastapi-k8s-7d6c7bdc65-abcde 1/1 Running 0 20s
fastapi-k8s-7d6c7bdc65-fghij 1/1 Running 0 18s
로그도 한 번 찍어볼 수 있습니다.
kubectl logs fastapi-k8s-7d6c7bdc65-abcde
7. Service 작성 – ClusterIP & NodePort 두 가지 버전
이제 클러스터 안에 떠 있는 FastAPI Pod에 접속 가능한 엔드포인트(Service) 를 달아줄 차례입니다.
7-1. ClusterIP Service (service.yaml)
apiVersion: v1
kind: Service
metadata:
name: fastapi-k8s
labels:
app: fastapi-k8s
spec:
type: ClusterIP
selector:
app: fastapi-k8s
ports:
- name: http
port: 80
targetPort: 8000
- type: ClusterIP → 클러스터 내부에서만 접근 가능한 기본 타입
- port: 80 → Service가 노출하는 포트
- targetPort: 8000 → Pod 내부 컨테이너 포트
배포
kubectl apply -f service.yaml
kubectl get svc
출력 예
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
fastapi-k8s ClusterIP 10.103.226.43 <none> 80/TCP 5s
7-2. NodePort Service (service_nodeport.yaml, 옵션)
Minikube에서는 NodePort Service를 만들고,
minikube service 명령으로 쉽게 브라우저까지 열 수 있습니다.
apiVersion: v1
kind: Service
metadata:
name: fastapi-k8s
labels:
app: fastapi-k8s
spec:
type: NodePort
selector:
app: fastapi-k8s
ports:
- name: http
port: 80
targetPort: 8000
nodePort: 30080
- type: NodePort → 클러스터 외부(호스트)에서 접속 가능한 포트를 하나 열어줌
- nodePort: 30080 → 호스트(WSL 기준 minikube VM)의 30080 포트로 접근 가능
배포
kubectl apply -f service_nodeport.yaml
kubectl get svc
NodePort 타입으로 바뀐 것을 확인할 수 있습니다.
8. Minikube에서 FastAPI 앱 접속하기
접속 방법은 크게 두 가지로 소개하겠습니다.
8-1. 방법 1: port-forward (ClusterIP + 로컬 포트 포워딩)
ClusterIP Service + kubectl port-forward 조합입니다.
kubectl port-forward service/fastapi-k8s 8080:80
- 로컬(WSL) 8080 → Service 80 → Pod 8000 으로 트래픽이 전달됩니다.
다른 터미널에서
curl http://localhost:8080/
# → {"message": "Hello, Kubernetes!!"}
curl http://localhost:8080/health
# → {"status": "ok"}
8-2. 방법 2: minikube service (NodePort + 자동 URL 열기)
NodePort Service를 사용 중이라면
minikube service fastapi-k8s --url
이 URL로 접속하면 동일하게 FastAPI 앱에 도달합니다.
- http://127.0.0.1:30080/
- http://127.0.0.1:30080/health
둘 다 잘 뜨는지 확인해보면 OK.
📌 Service Type 비교: ClusterIP vs NodePort
ClusterIP (기본)
- 클러스터 내부에서만 접근
- 서비스에 고정 IP 할당
- 서비스 간 통신 용도 (백엔드 ↔ 데이터베이스 등)
- 외부로 직접 노출되지 않음
NodePort
- 모든 Node의 특정 Port가 오픈됨
- 클러스터 외부에서 직접 접근 가능
- NodeIP:NodePort 형태로 접속
- 주로 개발·테스트용 혹은 간단한 외부 노출 용도
9. Self-Healing 실습: Pod를 죽여보자 😈
쿠버네티스의 Self-Healing을 체감해보기 위해,
일부러 Pod를 삭제해보는 실험을 해봅니다.
준비: 터미널 3개
- 터미널 A: 서비스 배포 터미널
- 터미널 B: Pod 상태를 실시간으로 보기
kubectl get pods -w
- 터미널 C: Pod를 삭제 명령을 입력할 터미널
9-1. Pod 삭제
터미널 C에서 현재 Pod 이름을 하나 골라서
kubectl delete pod fastapi-k8s-7d6c7bdc65-abcde
(실제 이름은 kubectl get pods 결과에 맞춰 입력)
9-2. 무슨 일이 일어나는지 관찰하기
- 터미널 B (kubectl get pods -w)에서는
- 삭제한 Pod가 Terminating → 사라지고
- 곧바로 새로운 Pod가 Pending → ContainerCreating → Running 으로 올라오는 걸 볼 수 있습니다.
- 터미널 A or 브라우저에서 계속 / 혹은 /health에 요청을 보내보면,
- 순간적으로 응답이 살짝 흔들릴 수는 있지만,
- 곧 정상 응답으로 복구되는 걸 확인할 수 있습니다.
이게 바로 Deployment에서 선언한 replicas: 2를 맞추기 위해
쿠버네티스가 원하는 상태(2개) 와 현재 상태(1개) 를 비교해서
자동으로 새 Pod를 띄우는 Self-Healing 동작입니다.
'IT > [클라우드]' 카테고리의 다른 글
| [Kubernetes] ConfigMap & Secret으로 FastAPI 설정 분리하기 (4) (0) | 2025.11.23 |
|---|---|
| [Kubernetes] Rolling Update & Rollback으로 FastAPI 무중단 배포하기 (3) (0) | 2025.11.23 |
| [Kubernetes] AI·빅데이터 직무를 위한 쿠버네티스 개념 이해 (1) (0) | 2025.11.22 |
| [AWS EC2] EC2 인스턴스 볼륨 용량 늘리기 (0) | 2024.06.04 |
| [AWS EC2] 포트 개방 기본 (맨날 헷갈려서 메모) (0) | 2024.05.02 |