본문 바로가기

IT/[클라우드]

[Kubernetes] Minikube로 FastAPI 앱 배포하기 실습 (2)

728x90
반응형

1탄에서 쿠버네티스 개념을 정리했다면, 이번 2탄은 직접 FastAPI 앱을 Minikube 위에 올려보는 실습입니다.

  • 실습 목표
    1. WSL2(Ubuntu 24.04) 안에 Minikube 클러스터 만들기
    2. FastAPI 앱을 Docker 이미지로 빌드해서 Minikube에 배포
    3. Service로 외부에서 접속해보기
    4. 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:appmain.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"}
WSL 쪽 브라우저(혹은 Windows 브라우저에서 http://localhost:8080)로 열어봐도 됩니다.

8-2. 방법 2: minikube service (NodePort + 자동 URL 열기)

NodePort Service를 사용 중이라면

minikube service fastapi-k8s --url

 

http://127.0.0.1:30080

이 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개

  1. 터미널 A: 서비스 배포 터미널
  2. 터미널 B: Pod 상태를 실시간으로 보기
    kubectl get pods -w


     
  3. 터미널 C: Pod를 삭제 명령을 입력할 터미널

9-1. Pod 삭제

터미널 C에서 현재 Pod 이름을 하나 골라서

kubectl delete pod fastapi-k8s-7d6c7bdc65-abcde

(실제 이름은 kubectl get pods 결과에 맞춰 입력)

9-2. 무슨 일이 일어나는지 관찰하기

  1. 터미널 B (kubectl get pods -w)에서는
    • 삭제한 Pod가 Terminating → 사라지고
    • 곧바로 새로운 Pod가 PendingContainerCreatingRunning 으로 올라오는 걸 볼 수 있습니다.
  2. 터미널 A or 브라우저에서 계속 / 혹은 /health에 요청을 보내보면,
    • 순간적으로 응답이 살짝 흔들릴 수는 있지만,
    • 곧 정상 응답으로 복구되는 걸 확인할 수 있습니다.

이게 바로 Deployment에서 선언한 replicas: 2를 맞추기 위해
쿠버네티스가 원하는 상태(2개)현재 상태(1개) 를 비교해서
자동으로 새 Pod를 띄우는 Self-Healing 동작입니다.

반응형