Posts簡単なPythonアプリをKubernetesにデプロイする
概要
Pythonのみで完結する簡単な3層アプリを構築し、kubernetesにデプロイする。
StreamlitとFlaskサーバーを連携させ、DBへTodoメモを保存させる。
構成
- フロントエンド: Streamlit
- バックエンド: Flask
- データベース: PostgreSQL
フォルダ配置
.
├── frontend
│ ├── Dockerfile
│ ├── app.py
│ └── requirements.txt
├── backend
│ ├── Dockerfile
│ ├── app.py
│ └── requirements.txt
└── k8s
├── backend.yaml
├── db.yaml
└── frontend.yaml
アプリケーションコード
フロントエンド
streamlitを実行するdockerfileを作成。
Dockerfile
FROM python:3.9
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
requirements.txt
streamlit
requests
アプリケーションコードは以下。
Todoを記入し、ボタンを押すとリストに登録されるだけの簡単なアプリを作成。
app.py
import streamlit as st
import requests
import os
API_URL = os.getenv("API_URL", "http://backend:5000")
st.title("✍Todo App")
new_todo = st.text_input("新しいTodoを追加")
if st.button("追加"):
if new_todo:
response = requests.post(f"{API_URL}/todos", json={"text": new_todo})
if response.status_code == 200:
st.success("Todoを追加しました!")
st.rerun()
else:
st.error("エラーが発生しました")
st.subheader("登録済みのTodo")
todos = requests.get(f"{API_URL}/todos").json()
for todo in todos:
col1, col2 = st.columns([4, 1])
col1.write(todo["text"])
if col2.button("削除", key=todo["id"]):
requests.delete(f"{API_URL}/todos/{todo['id']}")
st.rerun()
バックエンド
フロントエンドとほぼ同様の手順。
パッケージの中身と、実行コマンドのみ変更している。
Dockerfile
FROM python:3.9
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
requirements.txt
Flask
Flask-SQLAlchemy
Flask-Cors
psycopg2-binary
アプリケーションコードは以下。
/todos
パスにGET, POSTメソッドを追加し、todoメモの取得と登録ができるようにした。
また、/todos/<int:todo_id>
パスにDELETEメソッドを割り当てることで、idを指定してメモを削除できるようにした。
app.py
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
import os
app = Flask(__name__)
CORS(app)
# 環境変数からDBの設定を取得
DB_USER = os.getenv("POSTGRES_USER", "user")
DB_PASSWORD = os.getenv("POSTGRES_PASSWORD", "password")
DB_NAME = os.getenv("POSTGRES_DB", "tododb")
DB_HOST = os.getenv("POSTGRES_HOST", "db")
app.config["SQLALCHEMY_DATABASE_URI"] = (
f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}/{DB_NAME}"
)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.String(200), nullable=False)
@app.route("/todos", methods=["GET"])
def get_todos():
todos = Todo.query.all()
return jsonify([{"id": todo.id, "text": todo.text} for todo in todos])
@app.route("/todos", methods=["POST"])
def add_todo():
data = request.json
new_todo = Todo(text=data["text"])
db.session.add(new_todo)
db.session.commit()
return jsonify({"message": "Todo added!"})
@app.route("/todos/<int:todo_id>", methods=["DELETE"])
def delete_todo(todo_id):
todo = Todo.query.get(todo_id)
if todo:
db.session.delete(todo)
db.session.commit()
return jsonify({"message": "Todo deleted!"})
return jsonify({"error": "Todo not found"}), 404
if __name__ == "__main__":
with app.app_context():
db.create_all()
app.run(host="0.0.0.0", port=5000)
kubernetesでのデプロイ
マニフェストファイルの作成
k8s/
以下に、マニフェストファイルを作成。
frontend.yaml
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
type: LoadBalancer
ports:
- port: 8501
selector:
app: frontend
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: frontend:v0.0.0
imagePullPolicy: Never
env:
- name: API_URL
value: "http://backend:5000"
ports:
- containerPort: 8501
backend.yaml
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
ports:
- port: 5000
selector:
app: backend
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 2
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: backend:v0.0.0
imagePullPolicy: Never
env:
- name: POSTGRES_HOST
value: "db"
ports:
- containerPort: 5000
db.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Mi
---
apiVersion: v1
kind: Service
metadata:
name: db
spec:
ports:
- port: 5432
selector:
app: db
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: db
spec:
replicas: 1
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: db
image: postgres:15
env:
- name: POSTGRES_USER
value: "user"
- name: POSTGRES_PASSWORD
value: "password"
- name: POSTGRES_DB
value: "tododb"
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
デプロイ手順
minikubeを起動。
minikube start --memory=4096 --cpus=2
以下コマンドで、minikube内のDockerレジストリに、コンテナイメージをビルド。
eval $(minikube docker-env)
docker build -t backend:v0.0.0 ./backend
docker build -t frontend:v0.0.0 ./frontend
todo-app
という名前空間を切って、マニフェストをデプロイ。
kubectl create namespace todo-app
kubectl apply -f k8s/ -n todo-app
暫く待つとコンテナが起動し、以下のコマンドでステータスを確認できる。
kubectl get pod,svc -n todo-app
NAME READY STATUS RESTARTS AGE
pod/backend-75db745896-2mn6x 1/1 Running 1 (26s ago) 29s
pod/backend-75db745896-brhwg 1/1 Running 1 (25s ago) 29s
pod/db-5566db44cc-mh2vx 1/1 Running 0 29s
pod/frontend-6776dd978-9bpqg 1/1 Running 0 29s
pod/frontend-6776dd978-hvlx7 1/1 Running 0 29s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/backend ClusterIP 10.106.69.10 <none> 5000/TCP 29s
service/db ClusterIP 10.109.7.131 <none> 5432/TCP 29s
service/frontend LoadBalancer 10.103.12.36 <pending> 8501:31661/TCP 29s
ブラウザから操作
以下で、frontendのサービスをローカルの8501ポートに転送する。
kubectl port-forward svc/frontend 8501:8501 -n todo-app
以下のようにアプリ画面が表示され、Todoを登録、確認、削除できるようになる。
削除
以下でnamespaceごとアプリを削除する
kubectl delete namespace todo-app
まとめ
簡易な3層構造(streamlit, flask, db)のアプリを作成した。
kubernetesでのデプロイサンプルを作成した。