0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Kubernetesのすべてがわかる

0
Last updated at Posted at 2026-06-08

Kubernetes Deployment と Service の基本

Kubernetes (K8s) は コンテナを自動で起動・管理するためのオーケストレーションツール です。

最も基本的な構成は以下のようになります。

Deployment
    ↓
   Pod
    ↓
Container
  • Container
    • Dockerコンテナそのもの
  • Pod
    • Kubernetesでコンテナを実行する最小単位(複数のアプリも持てる)
  • Deployment
    • Podを管理する仕組み

演習

flaskコンテナを3つ作ってHello World表示させるのを目標にしていくよ

必要なファイルとコマンド一覧

解説は下へ

  1. app.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello Kubernetes!"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)
  1. requirements.txt
flask
  1. Dockerfile
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY app.py .

CMD ["python", "app.py"]

4.Dockerイメージ作成

docker build -t flask-hello:v1 .

5.deployment.yaml

apiVersion: apps/v1
kind: Deployment

metadata:
  name: flask-hello

spec:
  replicas: 3

  selector:
    matchLabels:
      app: flask-hello

  template:
    metadata:
      labels:
        app: flask-hello

    spec:
      containers:
      - name: flask-hello
        image: flask-hello:v1
        imagePullPolicy: IfNotPresent

        ports:
        - containerPort: 5000

6.service.yaml

apiVersion: v1
kind: Service

metadata:
  name: flask-hello-service

spec:
  selector:
    app: flask-hello

  ports:
  - port: 80
    targetPort: 5000

  type: LoadBalancer

7.サービス作成・Deployment作成

kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

Deploymentとは

Deploymentは

  • Podの作成
  • Pod数の維持
  • 自動復旧(Self Healing)
  • ローリングアップデート

を行う管理機能です。

deployment.yaml

apiVersion: apps/v1
kind: Deployment

metadata:
  name: flask-hello

spec:
  replicas: 3

  selector:
    matchLabels:
      app: flask-hello

  template:
    metadata:
      labels:
        app: flask-hello

    spec:
      containers:
      - name: flask-hello
        image: flask-hello:v1
        imagePullPolicy: IfNotPresent

        ports:
        - containerPort: 5000

各項目の説明

apiVersion

apiVersion: apps/v1

Deployment機能を利用するAPIバージョンです。

kind

kind: Deployment

作成するリソースの種類です。

今回は Deployment を作成します。

metadata

metadata:
  name: flask-hello

Deploymentの名前です。

確認時に使用します。

kubectl get deployment

replicas

replicas: 3

同じPodを3個起動します。

Pod1
Pod2
Pod3

負荷分散や障害対策のために複数台起動します。

selector

selector:
  matchLabels:
    app: flask-hello

Deploymentが管理するPodを識別する条件です。

app=flask-hello

というラベルを持つPodを管理します。

template

template:

Deploymentが作成するPodのテンプレートです。

labels

labels:
  app: flask-hello

作成されるPodへラベルを付与します。

app=flask-hello

後でServiceがこのラベルを利用してPodを探します。

containers

containers:

Pod内で起動するコンテナ一覧です。

image

image: flask-hello:v1

利用するDockerイメージです。

例:

docker build -t flask-hello:v1 .

imagePullPolicy

imagePullPolicy: IfNotPresent

イメージ取得方法を指定します。

設定 説明
Always 毎回取得
IfNotPresent なければ取得
Never 絶対取得しない

containerPort

containerPort: 5000

アプリケーションが使用するポートです。

例えばFlaskなら

app.run(host="0.0.0.0", port=5000)

Deployment作成の流れ

kubectl apply -f deployment.yaml

実行すると

kubectl apply
        ↓
Deployment作成
        ↓
ReplicaSet作成
        ↓
Podを3個作成
        ↓
各Podでコンテナ起動

となります。

ReplicaSetとは

Deploymentの裏側で動く仕組みです。

Deployment
      ↓
 ReplicaSet
      ↓
    Pod

ReplicaSetが

replicas: 3

を維持します。

Pod確認

kubectl get pods

NAME                          READY   STATUS
flask-hello-xxxx             1/1     Running
flask-hello-yyyy             1/1     Running
flask-hello-zzzz             1/1     Running

Podが壊れたら?

kubectl delete pod flask-hello-xxxxx

すると

Pod削除
    ↓
Deployment検知
    ↓
ReplicaSet検知
    ↓
新Pod作成

されます。

これが Kubernetes の自己修復(Self Healing)機能です。

Serviceとは

Serviceは

  • Podの固定入口
  • 負荷分散
  • Service Discovery

を提供します。

ブラウザ
   ↓
Service
 ↓  ↓  ↓
Pod Pod Pod

なぜServiceが必要か

Deploymentだけだと

PodA
PodB
PodC

が作られます。

しかしPodには内部IPが付きます。

10.1.0.5
10.1.0.6
10.1.0.7

問題は

Pod再作成
    ↓
IP変更

が発生することです。

そのため

ブラウザ
   ↓
Podへ直接アクセス

は実用的ではありません。

service.yaml

apiVersion: v1
kind: Service

metadata:
  name: flask-hello-service

spec:
  selector:
    app: flask-hello

  ports:
  - port: 80
    targetPort: 5000

  type: LoadBalancer

selector

selector:
  app: flask-hello

Serviceは

app=flask-hello

のラベルを持つPodを自動発見します。

port

port: 80

Service側ポート

targetPort

targetPort: 5000

Pod側ポート

通信イメージ

http://Service:80
        ↓
Flask:5000

type

type: LoadBalancer

Service公開方法です。

ClusterIP

type: ClusterIP

クラスター内部のみ通信可能

Pod ←→ Pod

NodePort

type: NodePort

ノードのポートを公開

NodeIP:30000~

でアクセス可能

LoadBalancer

type: LoadBalancer

クラウド環境向け

AWSなら

Service
    ↓
AWS ELB

が自動作成されます。

EKSでは通常これを利用します。

Service作成

kubectl apply -f service.yaml

流れ

Service作成
      ↓
selector確認
      ↓
app=flask-hello Pod検索
      ↓
Service登録

そのほかのコマンド

port-forward

ローカルPCから簡単にアクセスする方法

kubectl port-forward service/flask-hello-service 8080:80

アクセス

localhost:8080
      ↓
Service:80
      ↓
Pod:5000

スケールアウト

Pod数を増やす

kubectl scale deployment flask-hello --replicas=10
Pod 3台
   ↓
Pod 10台

Deployment確認

kubectl get deployments
項目 意味
READY 起動中Pod数
UP-TO-DATE 最新設定適用済Pod数
AVAILABLE 利用可能Pod数

Deployment詳細確認

kubectl describe deployment flask-hello

確認できる内容

  • replicas
  • image
  • selector
  • events
  • 更新履歴

YAML出力

kubectl get deployment flask-hello -o yaml

現在の設定を確認できます。

ローリングアップデート

例えば

image: flask-hello:v2

へ変更して

kubectl apply -f deployment.yaml

すると

旧Pod
 ↓
新Pod
 ↓
旧Pod削除

が順番に行われます。

サービス停止なしで更新できます。

再起動

kubectl rollout restart deployment flask-hello
Pod再作成
 ↓
新Pod起動
 ↓
旧Pod削除

ロールバック

履歴確認

kubectl rollout history deployment flask-hello

戻す

kubectl rollout undo deployment flask-hello

ログ確認

kubectl logs flask-hello-xxxxx

コンテナ標準出力を確認できます。

Pod内部へ入る

kubectl exec -it flask-hello-xxxxx -- sh

Ubuntu系なら

kubectl exec -it flask-hello-xxxxx -- bash

も利用可能です。

イベント確認

kubectl get events

エラー解析で非常によく使います。

ImagePullBackOff
CrashLoopBackOff
FailedScheduling

などの原因を確認できます。

リソース削除

Service削除

kubectl delete service flask-hello-service

Deployment削除

kubectl delete deployment flask-hello

内部的には

Deployment削除
      ↓
ReplicaSet削除
      ↓
Pod削除

となります。

Kubernetes全体像

                Browser
                   │
                   ▼
            Service(LoadBalancer)
                   │
          ┌────────┼────────┐
          ▼        ▼        ▼
        Pod1     Pod2     Pod3
          │        │        │
          ▼        ▼        ▼
    flask-hello flask-hello flask-hello
          │
          ▼
       Container

Deployment
      │
      ▼
 ReplicaSet
      │
      ▼
  Podを3台維持

この構成が Kubernetes の最も基本的な 「Deployment + Service」構成 であり、EKS・AKS・GKE などのマネージドKubernetesでも同じ考え方で動作します。

==============================================================================================================================================================================================

Pod

kind: Pod

Podとは

Kubernetesでコンテナを実行する最小単位です。

Dockerでは

docker run nginx

でコンテナを直接起動します。

一方Kubernetesでは

Pod
↓
Container

という構造になります。

つまり、

Docker
└─ Container

Kubernetes
└─ Pod
    └─ Container

です。

なぜPodが必要なのか

Kubernetesはコンテナだけでなく、

  • ネットワーク
  • ストレージ
  • 設定
  • ライフサイクル管理

もまとめて管理したいからです。

そのため

Container

ではなく

Pod

という概念を導入しています。

Podの中身

最も単純なPod

apiVersion: v1
kind: Pod

metadata:
  name: nginx-pod

spec:
  containers:
  - name: nginx
    image: nginx

構造

nginx-pod
└─ nginx Container

Podは複数コンテナを持てる

実は

1 Pod = 1 Container

ではありません。

kind: Pod

spec:
  containers:
  - name: nginx
    image: nginx

  - name: log-agent
    image: fluentd

構造

Pod
├─ nginx
└─ fluentd

例えば

Webサーバ
+
ログ収集コンテナ

を1つのPodに入れることがあります。

Pod内のコンテナは何を共有する?

同じPod内のコンテナは

ネットワーク共有

同じIPアドレスを持ちます。

Pod IP
 10.244.1.20

 nginx
 fluentd

そのため

localhost

で通信できます。

nginx
↓ localhost:9000
fluentd

ストレージ共有

Volumeを共有できます。

Pod
├─ nginx
├─ fluentd
└─ shared-volume

kubectl applyしたときに起こること

例えば

kubectl apply -f pod.yaml

を実行すると

① kubectl

kubectl

Podを作ってください

というリクエストを送ります。

② API Server

API Server

が受け取ります。

③ etcd保存

Pod情報が保存されます。

etcd

Pod
 name=nginx-pod
 image=nginx

④ Scheduler

Schedulerが監視しています。

Pod発見
↓
どのNodeで動かす?

を判断します。

Node-1
CPU 50%

Node-2
CPU 10%

なら

Node-2

を選ぶことがあります。

⑤ kubelet

選ばれたNodeの

kubelet

Podを起動しろ

と指示を受けます。

⑥ containerd

containerdが

nginxイメージ取得

コンテナ起動

します。

⑦ Running

最終的に

kubectl get pods

nginx-pod Running

になります。

Podのライフサイクル

Pending

まだ起動していない

Scheduler待ち

ContainerCreating

コンテナ作成中

Image Pull中

など

Running

正常起動

Succeeded

正常終了

Jobでよく見る

Failed

異常終了

CrashLoopBackOff

起動

クラッシュ

再起動

クラッシュ

を繰り返している

PodのIP

Podには自動的にIPが付きます。

nginx-pod
↓
10.244.1.10

しかし

Pod削除
↓
再作成

すると

10.244.1.10
↓
10.244.1.32

に変わることがあります。

だからServiceが必要になります。

なぜ本番でPodを直接作らないのか

例えば

kind: Pod

だけで作ると

Pod死亡
↓
終わり

です。

再作成されません。

nginx-pod
↓
Node障害
↓
消滅

そのため本番では

kind: Deployment

を使います。

Deploymentなら

Pod死亡
↓
ReplicaSet検知
↓
新しいPod作成

となります。

PodとDeploymentの違い

Pod

Pod
└─ Container

特徴

単発
自己修復なし
スケールなし

Deployment

Deployment
└─ ReplicaSet
    ├─ Pod
    ├─ Pod
    └─ Pod

特徴

自己修復あり
スケール可能
Rolling Update可能

実務での使い分け

Podを直接使うケース

学習
検証
デバッグ
一時的な動作確認

kubectl run nginx --image=nginx

Deploymentを使うケース

Flask
FastAPI
Django
Node.js
Spring Boot
Nginx

などのWebアプリ

StatefulSetを使うケース

MySQL
PostgreSQL
Redis
MongoDB
Kafka

などのDB

まとめ

Pod
↓
Kubernetesでコンテナを動かす最小単位
kubectl apply
↓
API Server
↓
etcd
↓
Scheduler
↓
kubelet
↓
containerd
↓
Container起動
Pod
└─ Container

ですが、

本番では通常

Deployment
StatefulSet
DaemonSet

の配下でPodを管理します。

==============================================================================================================================================================================================

Kubernetesの kind とは

KubernetesのJSON/YAMLでは、

apiVersion: apps/v1
kind: Deployment

のように kind を指定します。

これは

Kubernetesに何を作るのか

を表しています。

Kubernetesの基本構造

例えば

kubectl apply -f deployment.json

すると、

kubectl
↓
API Server
↓
etcdへ保存
↓
対応するControllerが検知
↓
実際のリソース作成

となります。

Namespace

kind: Namespace

役割

Kubernetes内の論理的な区画を作る。

kind: Namespace
metadata:
  name: dev

apply時

Namespace作成
↓
etcd保存

終了。

Podは作られません。

イメージ

Cluster
  dev
  ├─ mysql
  ├─ flask
  └─ redis
  prod
  ├─ mysql
  ├─ flask
  └─ redis

Secret

kind: Secret

役割

パスワードやAPIキーを保存する。

kind: Secret
stringData:
  password: secret123

apply時

Secret作成
↓
etcd保存

終了。

Pod起動時

env:
- valueFrom:
    secretKeyRef:

で読み込まれる。

よく保存するもの

DBパスワード
APIキー
TLS証明書
JWT秘密鍵

ConfigMap

kind: ConfigMap

役割

設定ファイルを保存する。

kind: ConfigMap
data:
  app.conf: |
    port=8080

apply時

ConfigMap作成
↓
etcd保存

Pod起動時

volumeMounts

または

envFrom

で読み込まれる。

よく保存するもの

SQL
設定ファイル
ログ設定
アプリ設定

Pod

kind: Pod
kind: Pod

spec:
  containers:
  - name: nginx
    image: nginx

  - name: log-agent
    image: fluentd

役割

コンテナを起動する最小単位。

kind: Pod

apply時

Pod作成
↓
Scheduler
↓
Node決定
↓
kubelet
↓
containerd
↓
コンテナ起動

実際

Pod
└─ Container

注意

本番ではあまり直接作らない。ReplicaSet検知できないから

通常は

Deployment
StatefulSet
DaemonSet

を使う。

Service

kind: Service

役割

Podへの入口。

なぜ必要?

PodのIPは変わる。

10.1.1.1
↓
再起動
↓
10.1.1.8

Serviceを使うと

mysql-service

で固定アクセス可能。

apply時

Service作成
↓
CoreDNS登録
↓
Endpoint Controller監視

Pod起動後

Service
↓
Endpoint
↓
Pod

が紐付く。

Serviceの種類

ClusterIP
type: ClusterIP

内部通信専用。

NodePort
type: NodePort
NodeIP:30080

でアクセス。

LoadBalancer
type: LoadBalancer

AWSなら

ELB
ALB
NLB

が作られる。

Headless Service
clusterIP: None

StatefulSet向け。

Deployment

kind: Deployment

役割

ステートレスアプリを管理。

replicas: 2

apply時

Deployment作成
↓
ReplicaSet作成
↓
Pod作成

構造

Deployment
└─ ReplicaSet
    ├─ Pod
    └─ Pod

Podが死んだら

Pod死亡
↓
ReplicaSet検知
↓
再作成

よく使うもの

Flask
FastAPI
Nginx
Node.js
Spring Boot

StatefulSet

kind: StatefulSet

役割

状態を持つアプリを管理。

Deploymentとの違い

Deployment

app-random1
app-random2

StatefulSet

mysql-0
mysql-1

固定名。

apply時

StatefulSet作成
↓
Pod作成
↓
PVC作成
↓
Volume割当

主な用途

MySQL
PostgreSQL
MongoDB
Redis Cluster
Kafka

PersistentVolume (PV)

kind: PersistentVolume

役割

実際のストレージ。

apply時

PV作成

AWSなら

EBS
EFS
FSx

と紐付く。

PersistentVolumeClaim (PVC)

kind: PersistentVolumeClaim

役割

ストレージ要求。

storage: 10Gi

apply時

PVC作成
↓
PV検索
↓
自動接続

イメージ

Pod
↓
PVC
↓
PV
↓
EBS

Job

kind: Job

役割

一度だけ実行する。

DB移行
バックアップ
集計

apply時

Job作成
↓
Pod起動
↓
処理実行
↓
成功
↓
終了

CronJob

kind: CronJob

役割

定期実行。

schedule: "0 0 * * *"

apply時

CronJob登録

実行時

毎日0時
↓
Job作成
↓
Pod起動
↓
処理実行

Ingress

kind: Ingress

役割

HTTPルーティング。

example.com
↓
Flask Service

api.example.com
↓
API Service

apply時

Ingress作成
↓
Ingress Controller検知
↓
Nginx設定生成

DaemonSet

kind: DaemonSet

役割

全Nodeに1個ずつPodを配置。

apply時

Node数確認
↓
各NodeへPod配置

主な用途

Fluentd
Datadog Agent
Prometheus Node Exporter
CloudWatch Agent

実務で最もよく使うKind

Kind 用途
Namespace 環境分離
Secret パスワード
ConfigMap 設定ファイル
Service 通信入口
Deployment Webアプリ
StatefulSet DB
PVC 永続ストレージ
Ingress HTTP公開
Job 一回実行
CronJob 定期実行
DaemonSet 監視Agent

このあたりの kind を理解すると、EKSやGKEの実務案件の8〜9割程度のKubernetes構成は読めるようになります。

Controllerとの対応表

Kind Controller
Deployment Deployment Controller
ReplicaSet ReplicaSet Controller
StatefulSet StatefulSet Controller
DaemonSet DaemonSet Controller
Job Job Controller
CronJob CronJob Controller
Service Endpoint Controller
Namespace API Serverのみ
Secret API Serverのみ
ConfigMap API Serverのみ

Controllerは常にetcdを監視しており、

希望状態(Desired State)

現在状態(Current State)

の差分を埋め続けます。

これがKubernetesの基本思想である

宣言的管理(Declarative Management)

です。

StatefulSet と Deployment の違い

一言で言うと、

Deployment
→ ステートレスなアプリ向け

StatefulSet
→ 状態(データ)を持つアプリ向け

です。

ステートレスとステートフル

ステートレス

サーバがデータを持たない。

Flask
FastAPI
Nginx
Node.js
Spring Boot

例えば

Flask Pod-1

が消えても

Flask Pod-2

が同じ処理をできます。

ステートフル

サーバがデータを持つ。

MySQL
PostgreSQL
MongoDB
Redis
Kafka

例えば

mysql-0

に保存したデータは

mysql-1

にはありません。

そのため個体を識別する必要があります。

Deployment

構造

Deployment
└─ ReplicaSet
    ├─ Pod
    ├─ Pod
    └─ Pod

Pod名

Deploymentはランダムになります。

flask-74d5c8f7f-p9mqx
flask-74d5c8f7f-v42jp

再作成されると

flask-74d5c8f7f-ab123

になることもあります。

スケール

replicas: 3

なら

Pod × 3

を維持します。

Podが落ちたら

Pod死亡
↓
ReplicaSet検知
↓
新しいPod作成

Serviceとの相性

Service
↓
負荷分散
↓
Pod1
Pod2
Pod3

が得意です。

StatefulSet

構造

StatefulSet
├─ mysql-0
├─ mysql-1
└─ mysql-2

Pod名

固定です。

mysql-0
mysql-1
mysql-2

再起動しても

mysql-0

mysql-0

のままです。

なぜ固定名が必要?

例えば

mysql-primary
mysql-replica

のような構成では

mysql-0

がPrimary

mysql-1

がReplica

として動作します。

名前が変わると困ります。

永続ストレージ

Deployment

Pod削除
↓
データ消える

ことが多いです。

StatefulSet

mysql-0
↓
PVC
↓
PV

を持ちます。

mysql-0
└─ mysql-data-mysql-0

mysql-1
└─ mysql-data-mysql-1

Podが再作成されても

mysql-0

mysql-data-mysql-0

を再利用します。

起動順序

Deployment

Pod1
Pod2
Pod3

を並列作成

StatefulSet

mysql-0
↓
起動完了
↓
mysql-1
↓
起動完了
↓
mysql-2

順番に作成

削除順序

Deployment

全部同時

StatefulSet

mysql-2
↓
mysql-1
↓
mysql-0

逆順で削除

DNS

Deployment と StatefulSet の DNS の違い

Kubernetesでは、Service が DNS 名を提供します。


Deployment の場合

Deploymentで作られるPodは、再作成されるたびに名前が変わります。

flask-6f4f9c8f7b-abcde
flask-6f4f9c8f7b-fghij

そのため、

flask-6f4f9c8f7b-abcde

のようなPod名を前提に通信してはいけません。

代わりに Service を作ります。

kind: Service
metadata:
  name: flask-service

するとDNSで

flask-service

へアクセスできます。

通信すると、

flask-service
 ↓
Pod1
Pod2
Pod3

のどれかにロードバランシングされます。


Deploymentのイメージ
Client
   ↓
flask-service
   ↓
 ┌─────┬─────┬─────┐
 ↓     ↓     ↓
Pod1  Pod2  Pod3

どのPodに接続されるかは分かりません。

つまり

flask-service

しか知らなくてよい仕組みです。


StatefulSet の場合

StatefulSetは

mysql-0
mysql-1
mysql-2

のようにPod名が固定されます。

再起動しても

mysql-0

は常に

mysql-0

です。


Headless Service

通常のService

clusterIP: 10.0.0.100

mysql-service

にアクセスするとロードバランシングされます。


Headless Service

clusterIP: None

にすると、

mysql-service

はロードバランサーを持ちません。

代わりに各PodのDNSが作られます。


kind: Service
metadata:
  name: mysql-service
spec:
  clusterIP: None
kind: StatefulSet
metadata:
  name: mysql

するとDNSは

mysql-0.mysql-service
mysql-1.mysql-service
mysql-2.mysql-service

になります。


実際の名前の構造

<Pod名>.<Service名>

なので

mysql-0.mysql-service

Pod名      = mysql-0
Service名  = mysql-service

です。

実際にはさらに

mysql-0.mysql-service.default.svc.cluster.local

という完全なDNS名になります。


なぜ StatefulSet で必要なのか

MySQLクラスタを考えます。

mysql-0 = Primary
mysql-1 = Replica
mysql-2 = Replica

アプリ側で

mysql-0.mysql-service

へ接続すれば、

常にPrimaryへ接続できます。


Deploymentだと

mysql-service

しかなく、

Pod1
Pod2
Pod3

のどれに接続されるか分からないため、

データベースには向きません。


まとめ

項目 Deployment StatefulSet
Pod名 毎回変わる 固定
DNS Serviceのみ PodごとのDNSあり
接続先 どのPodか不明 特定Podを指定可能
ロードバランシング あり Headless Serviceならなし
用途 Flask、API、Webサーバ MySQL、PostgreSQL、Kafka、Redis

覚え方は、

Deployment
→ 「同じPodを何個も並べる」

StatefulSet
→ 「1台1台に名前が付いている」

です。

そのため StatefulSet では

mysql-0.mysql-service

のような個別DNSが利用でき、MySQLやKafkaのような状態を持つアプリを運用できます。

YAML比較

Deployment

apiVersion: apps/v1
kind: Deployment

spec:
  replicas: 3

StatefulSet

apiVersion: apps/v1
kind: StatefulSet

spec:
  serviceName: mysql-service
  replicas: 3

StatefulSetにはDNS名を作るために

serviceName:

が必須です。

Kubernetes内部での違い

Deployment

kubectl apply
↓
Deployment作成
↓
Deployment Controller
↓
ReplicaSet作成
↓
Pod作成

StatefulSet

kubectl apply
↓
StatefulSet作成
↓
StatefulSet Controller
↓
PVC作成
↓
Pod作成

実務での使い分け

Deployment

Flask
FastAPI
Django
Node.js
Nginx
Apache

StatefulSet

MySQL
PostgreSQL
MongoDB
Redis Cluster
Kafka
Elasticsearch/OpenSearch
ZooKeeper

まとめ

項目 Deployment StatefulSet
用途 Webアプリ DB
Pod名 ランダム 固定
ストレージ 任意 ほぼ必須
DNS Service経由 個別DNSあり
起動順序 並列 順番
削除順序 並列 逆順
データ保持 弱い 強い
主な用途 Flask, FastAPI, Nginx MySQL, PostgreSQL, Kafka

PVCとの関係

StatefulSetはPodごとに専用PVCを持ちます。

mysql-0
└─ PVC:mysql-data-mysql-0

mysql-1
└─ PVC:mysql-data-mysql-1

そのため

mysql-0削除
↓
mysql-0再作成

されても

mysql-data-mysql-0

が再利用されます。

結果として

Podは消えても
データは消えない

というDB向けの仕組みになっています。

実践

MysqlPod2つ、そのMysqlPodからデータを取得してブラウザに表示するFlaskPod2つを作っていきます。

Kubernetes構成

MySQL Pod × 2
Flask Pod × 2
Flask → MySQL Service → MySQL Pod

1. ファイル構成

k8s-mysql-flask/
├─ app.py
├─ Dockerfile
├─ requirements.txt
├─ namespace.json
├─ mysql-secret.json
├─ mysql-init-configmap.json
├─ mysql-service.json
├─ mysql-statefulset.json
├─ flask-service.json
└─ flask-deployment.json

2. Flaskアプリ

app.py

from flask import Flask
import mysql.connector
import os

app = Flask(__name__)

MYSQL_HOST = os.environ.get("MYSQL_HOST", "mysql-service")
MYSQL_USER = os.environ.get("MYSQL_USER", "root")
MYSQL_PASSWORD = os.environ.get("MYSQL_PASSWORD", "password")
MYSQL_DATABASE = os.environ.get("MYSQL_DATABASE", "sampledb")

@app.route("/")
def index():
    conn = mysql.connector.connect(
        host=MYSQL_HOST,
        user=MYSQL_USER,
        password=MYSQL_PASSWORD,
        database=MYSQL_DATABASE
    )

    cursor = conn.cursor()
    cursor.execute("SELECT id, name FROM users")
    rows = cursor.fetchall()

    cursor.close()
    conn.close()

    html = "<h1>Users from MySQL</h1><ul>"
    for row in rows:
        html += f"<li>{row[0]} : {row[1]}</li>"
    html += "</ul>"

    return html

@app.route("/health")
def health():
    return "OK"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

requirements.txt

flask
mysql-connector-python

Dockerfile

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .

CMD ["python", "app.py"]

3. Dockerイメージ作成

docker build -t flask-mysql-app:v1 .

4. Kubernetes JSONファイル

namespace.json

{
  "apiVersion": "v1",
  "kind": "Namespace",
  "metadata": {
    "name": "mysql-flask-demo"
  }
}

mysql-secret.json

{
  "apiVersion": "v1",
  "kind": "Secret",
  "metadata": {
    "name": "mysql-secret",
    "namespace": "mysql-flask-demo"
  },
  "type": "Opaque",
  "stringData": {
    "MYSQL_ROOT_PASSWORD": "password"
  }
}

mysql-statefulset.jsonの下記で使用される

"env": [
  {
    "name": "MYSQL_ROOT_PASSWORD",
    "valueFrom": {
      "secretKeyRef": {
        "name": "mysql-secret",
        "key": "MYSQL_ROOT_PASSWORD"
      }
    }
  }
],

mysql-init-configmap.json

{
  "apiVersion": "v1",
  "kind": "ConfigMap",
  "metadata": {
    "name": "mysql-init-config",
    "namespace": "mysql-flask-demo"
  },
  "data": {
    "init.sql": "CREATE DATABASE IF NOT EXISTS sampledb;\nUSE sampledb;\nCREATE TABLE IF NOT EXISTS users (\n  id INT AUTO_INCREMENT PRIMARY KEY,\n  name VARCHAR(255) NOT NULL\n);\nINSERT INTO users (name) VALUES ('Taro'), ('Hanako'), ('Jiro');\n"
  }
}

mysql-statefulset.jsonの下記で使用される

"volumes": [
  {
    "name": "mysql-init",
    "configMap": {
      "name": "mysql-init-config"
    }
  }
]

mysql-service.json

{
  "apiVersion": "v1",
  "kind": "Service",
  "metadata": {
    "name": "mysql-service",
    "namespace": "mysql-flask-demo"
  },
  "spec": {
    "clusterIP": "None",
    "selector": {
      "app": "mysql"
    },
    "ports": [
      {
        "name": "mysql",
        "port": 3306,
        "targetPort": 3306
      }
    ]
  }
}

mysql-statefulset.jsonの下記で使用される

{
  "apiVersion": "apps/v1",
  "kind": "StatefulSet",
  "metadata": {
    "name": "mysql",
    "namespace": "mysql-flask-demo"
  },
  "spec": {
    "serviceName": "mysql-service",
    "replicas": 2,

mysql-statefulset.json

{
  "apiVersion": "apps/v1",
  "kind": "StatefulSet",
  "metadata": {
    "name": "mysql",
    "namespace": "mysql-flask-demo"
  },
  "spec": {
    "serviceName": "mysql-service",
    "replicas": 2,
    "selector": {
      "matchLabels": {
        "app": "mysql"
      }
    },
    "template": {
      "metadata": {
        "labels": {
          "app": "mysql"
        }
      },
      "spec": {
        "containers": [
          {
            "name": "mysql",
            "image": "mysql:8.0",
            "ports": [
              {
                "containerPort": 3306,
                "name": "mysql"
              }
            ],
            "env": [
              {
                "name": "MYSQL_ROOT_PASSWORD",
                "valueFrom": {
                  "secretKeyRef": {
                    "name": "mysql-secret",
                    "key": "MYSQL_ROOT_PASSWORD"
                  }
                }
              }
            ],
            "volumeMounts": [
              {
                "name": "mysql-data",
                "mountPath": "/var/lib/mysql"
              },
              {
                "name": "mysql-init",
                "mountPath": "/docker-entrypoint-initdb.d"
              }
            ]
          }
        ],
        "volumes": [
          {
            "name": "mysql-init",
            "configMap": {
              "name": "mysql-init-config"
            }
          }
        ]
      }
    },
    "volumeClaimTemplates": [
      {
        "metadata": {
          "name": "mysql-data"
        },
        "spec": {
          "accessModes": ["ReadWriteOnce"],
          "resources": {
            "requests": {
              "storage": "1Gi"
            }
          }
        }
      }
    ]
  }
}
{
  "apiVersion": "apps/v1", // StatefulSetは apps/v1 API を使う
  "kind": "StatefulSet", // StatefulSetリソースを作成する
  "metadata": {
    "name": "mysql", // StatefulSetの名前を mysql にする
    "namespace": "mysql-flask-demo" // mysql-flask-demo Namespace内に作成する
  },
  "spec": {
    "serviceName": "mysql-service", // StatefulSetが使うHeadless Service名
    "replicas": 2, // MySQL Podを2個起動する
    "selector": {
      "matchLabels": {
        "app": "mysql" // app=mysql のPodをこのStatefulSetの管理対象にする
      }
    },
    "template": {
      "metadata": {
        "labels": {
          "app": "mysql" // 作成されるPodに app=mysql ラベルを付ける
        }
      },
      "spec": {
        "containers": [
          {
            "name": "mysql", // コンテナ名を mysql にする
            "image": "mysql:8.0", // MySQL 8.0 のDockerイメージを使う
            "ports": [
              {
                "containerPort": 3306, // コンテナ内の3306番ポートを使う
                "name": "mysql" // ポート名を mysql にする
              }
            ],
            "env": [
              {
                "name": "MYSQL_ROOT_PASSWORD", // MySQL rootパスワード用の環境変数
                "valueFrom": {
                  "secretKeyRef": {
                    "name": "mysql-secret", // mysql-secret というSecretを参照する
                    "key": "MYSQL_ROOT_PASSWORD" // Secret内の MYSQL_ROOT_PASSWORD を使う
                  }
                }
              }
            ],
            "volumeMounts": [
              {
                "name": "mysql-data", // mysql-data ボリュームを使う
                "mountPath": "/var/lib/mysql" // MySQLのデータ保存場所にマウントする
              },
              {
                "name": "mysql-init", // mysql-init ボリュームを使う
                "mountPath": "/docker-entrypoint-initdb.d" // 初期SQL自動実行用ディレクトリにマウントする
              }
            ]
          }
        ],
        "volumes": [
          {
            "name": "mysql-init", // mysql-init というボリュームを定義する
            "configMap": {
              "name": "mysql-init-config" // mysql-init-config ConfigMapをファイルとして使う
            }
          }
        ]
      }
    },
    "volumeClaimTemplates": [
      {
        "metadata": {
          "name": "mysql-data" // Podごとの永続ボリューム名を mysql-data にする
        },
        "spec": {
          "accessModes": ["ReadWriteOnce"], // 1つのNodeから読み書き可能なディスクにする
          "resources": {
            "requests": {
              "storage": "1Gi" // Podごとに1Giの永続ストレージを要求する
            }
          }
        }
      }
    ]
  }
}

flask-service.json

{
  "apiVersion": "v1",
  "kind": "Service",
  "metadata": {
    "name": "flask-service",
    "namespace": "mysql-flask-demo"
  },
  "spec": {
    "type": "NodePort",
    "selector": {
      "app": "flask"
    },
    "ports": [
      {
        "port": 80,
        "targetPort": 5000,
        "nodePort": 30080
      }
    ]
  }
}
{
  "apiVersion": "v1", // Serviceは Kubernetes Core API の v1 を使う
  "kind": "Service", // Serviceリソースを作成する
  "metadata": {
    "name": "flask-service", // Service名を flask-service にする
    "namespace": "mysql-flask-demo" // mysql-flask-demo Namespace内に作成する
  },
  "spec": {
    "type": "NodePort", // クラスタ外部から NodeIP:nodePort でアクセスできるServiceにする
    "selector": {
      "app": "flask" // app=flask ラベルを持つPodへ通信を流す
    },
    "ports": [
      {
        "port": 80, // Service自身が受け付けるポート番号
        "targetPort": 5000, // 転送先のFlask Pod内ポート番号
        "nodePort": 30080 // 外部からアクセスするためのNode側ポート番号
      }
    ]
  }
}

ブラウザ / curl

NodeIP:30080

flask-service:80

Flask Pod:5000

flask-deployment.json

{
  "apiVersion": "apps/v1",
  "kind": "Deployment",
  "metadata": {
    "name": "flask-deployment",
    "namespace": "mysql-flask-demo"
  },
  "spec": {
    "replicas": 2,
    "selector": {
      "matchLabels": {
        "app": "flask"
      }
    },
    "template": {
      "metadata": {
        "labels": {
          "app": "flask"
        }
      },
      "spec": {
        "containers": [
          {
            "name": "flask",
            "image": "flask-mysql-app:v1",
            "imagePullPolicy": "IfNotPresent",
            "ports": [
              {
                "containerPort": 5000
              }
            ],
            "env": [
              {
                "name": "MYSQL_HOST",
                "value": "mysql-service"
              },
              {
                "name": "MYSQL_USER",
                "value": "root"
              },
              {
                "name": "MYSQL_PASSWORD",
                "valueFrom": {
                  "secretKeyRef": {
                    "name": "mysql-secret",
                    "key": "MYSQL_ROOT_PASSWORD"
                  }
                }
              },
              {
                "name": "MYSQL_DATABASE",
                "value": "sampledb"
              }
            ]
          }
        ]
      }
    }
  }
}
{
  "apiVersion": "apps/v1", // Deploymentは apps/v1 API を使う
  "kind": "Deployment", // Deploymentリソースを作成する
  "metadata": {
    "name": "flask-deployment", // Deployment名を flask-deployment にする
    "namespace": "mysql-flask-demo" // mysql-flask-demo Namespace内に作成する
  },
  "spec": {
    "replicas": 2, // Flask Podを2個起動する
    "selector": {
      "matchLabels": {
        "app": "flask" // app=flask のPodをこのDeploymentの管理対象にする
      }
    },
    "template": {
      "metadata": {
        "labels": {
          "app": "flask" // 作成されるPodに app=flask ラベルを付ける
        }
      },
      "spec": {
        "containers": [
          {
            "name": "flask", // コンテナ名を flask にする
            "image": "flask-mysql-app:v1", // 使用するDockerイメージ
            "imagePullPolicy": "IfNotPresent", // ローカルにイメージがあればそれを使う
            "ports": [
              {
                "containerPort": 5000 // Flaskアプリが待ち受けるコンテナ内ポート
              }
            ],
            "env": [
              {
                "name": "MYSQL_HOST", // MySQL接続先ホスト名
                "value": "mysql-service" // Kubernetes Service名でMySQLへ接続する
              },
              {
                "name": "MYSQL_USER", // MySQL接続ユーザー名
                "value": "root" // rootユーザーで接続する
              },
              {
                "name": "MYSQL_PASSWORD", // MySQL接続パスワード
                "valueFrom": {
                  "secretKeyRef": {
                    "name": "mysql-secret", // mysql-secret Secretを参照する
                    "key": "MYSQL_ROOT_PASSWORD" // Secret内のMYSQL_ROOT_PASSWORDを使う
                  }
                }
              },
              {
                "name": "MYSQL_DATABASE", // 接続するデータベース名
                "value": "sampledb" // sampledbへ接続する
              }
            ]
          }
        ]
      }
    }
  }
}

5. kubectlコマンド

リソース作成

kubectl apply -f namespace.json
kubectl apply -f mysql-secret.json
kubectl apply -f mysql-init-configmap.json
kubectl apply -f mysql-service.json
kubectl apply -f mysql-statefulset.json
kubectl apply -f flask-service.json
kubectl apply -f flask-deployment.json

状態確認

kubectl get pods -n mysql-flask-demo
kubectl get svc -n mysql-flask-demo
kubectl get statefulset -n mysql-flask-demo
kubectl get deployment -n mysql-flask-demo
kubectl get pvc -n mysql-flask-demo

Flaskへアクセス

Minikube

minikube service flask-service -n mysql-flask-demo

または

curl http://localhost:30080

NodePort環境

http://<NodeIP>:30080

ログ確認

kubectl logs -n mysql-flask-demo deployment/flask-deployment
kubectl logs -n mysql-flask-demo mysql-0
kubectl logs -n mysql-flask-demo mysql-1

MySQLへ接続

kubectl exec -it -n mysql-flask-demo mysql-0 -- mysql -uroot -ppassword

SQL実行

USE sampledb;
SELECT * FROM users;

削除

kubectl delete namespace mysql-flask-demo

注意

この構成では

mysql-0
mysql-1

がそれぞれ独立したMySQLです。

レプリケーションは構成していないため、

mysql-0にINSERT
↓
mysql-1には反映されない

状態になります。

実運用では通常、

MySQL Primary
↓
MySQL Replica
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?