はじめに
業務でKubernetesで運用しているシステムの開発と運用に携わってきました。0からKubernetesのシステム全体を構築する機会はなかったのでこれを機に簡単なFlaskを使ったLineBotをGKE上で構築しました。
個人制作における簡易的なLinebot作成の目的であれば、Cloud functionsやAWS Lambdaなどサーバーレスな技術的を用いて迅速にデプロイできるため、Kubernetesを使うのは少しオーバースペックに思うかも知れません。
今回は、Kubernetes(on GKE)を使って0からシステムを構築し、Kubernetesを使ったアプリの全体像を把握したいこと、及び、Kubernetesの各オブジェクトへの理解を深めることを大きな目的としています。
とはいえKubernetesを採用するメリットもあり代表的には以下が挙げられます。
-
リクエスト数が増えた際にスケールアウトしやすいこと。
-> Podの設定(replicas数など)を変更するだけで容易にスケールアウトが可能であること。リソースを増強する際いちいち新たなサーバーの構築作業に時間をかける必要がありません。 - リクエスト数が少ないときに最小限のリソースを元にサービスを運用することが可能であること。
-> 例えばサービスを立ち上げたばかりでユーザーのリクエスト数が少ないときは、Pod数を1台にするなどPod数を限りなく少なくして運用することができます。多くのパブリッククラウドにおいて、NodeやPodの使用数によって課金額が決まるため、システムの負荷に応じてリソースの調整が可能です。
本記事ではDeployment
やService
などKubernetesでデプロイする際に使うであろう一通りのオブジェクトを使用して、TODO管理用LineBotを作成したシステムの紹介します。
アプリのロジックは簡易的なのでKubernetesの記述方法にフォーカスしてキャッチアップしやすい内容であると思います。
加えてdocker-compose
を使ってローカルでアプリケーション立ち上げることができるようにしました。
docker-compose
を使ったコンテナ連携の方法とKubernetesの方法の違いを手を動かしながら比較することができます。1
全体のソースコード
secret.yaml
などの一部のファイルを除き、全体のソースコードを公開しているので、適宜参照してください。
使用した技術とバージョン一覧
Tech | Version |
---|---|
Python | 3.9.7 |
Flask | 1.1.4 |
Kubernetes | 1.23.5 |
Docker | 20.10.12 |
Nginx | 1.21.6 |
Mysql | 5.7 |
Kubernetes(k8s)とは
Kubernetes (K8s)は、デプロイやスケーリングを自動化したり、コンテナ化されたアプリケーションを管理したりするための、オープンソースのシステムです。
近年、Infrastructure as Code(IaC)と総称されるインフラの設定をコードで管理する技術が数多く導入されています。宣言的な記述を元にサーバースペックなどのインフラパラメータをコードで記述することで冪等性を考慮でき、インフラ構築の再現性を高めることができます。加えて、保守にかかるコストを削減することができ、属人化を避けることにも繋がることがメリットとして挙げられ数多くのプロダクトで導入が進んでいます。
その中でもk8sはコンテナオーケストレーションのプロダクトとしてデファクトスタンダードとなっているCNCFの代表的なOSSの一つです。
GKEとは
Google Kubernetes Engine(GKE)は、Google のインフラストラクチャを使用して、コンテナ化されたアプリケーションのデプロイ、管理、スケーリングを行うマネージド環境を提供します。GKE 環境は複数のマシン(具体的には、Compute Engine インスタンス)で構成され、これらのマシンがグループ化されてクラスタを形成します。
GCPの提供するKubernetes のマネージドサービスです。本システムではGKE上で各Kubernetesオブジェクトを作成しアプリケーションをデプロイします。
TODO管理LineBotの要件
- テキストを送るとそのタスクを含めたタスクの一覧を教えてくれます。
- 改行区切りで複数のタスクを同時に入力可能であり、既存のTODOタスク名と同じテキストを送信することで、TODOタスクからそのタスクを消去できます。
- 特定の文字列を送信することでタスクの一斉削除も可能です。
トークサンプル
使い方としては、Lineの友達追加をした後、トーク画面でTODOの追加と削除ができます。
システム構成図
Appサーバー、Webサーバー、DBサーバーをそれぞれ別のPod
(コンテナ)上で立ち上げる3階層システムを構築しました。
Service
間の通信においてはmetadata
を用いてKubernetes内部で名前解決を行います。
GCPのVPCネットワークにより、グローバルIP静的アドレスを予約し、またそれ用の独自ドメインを取得しておきます。
それらをドメイン登録事業者内のDNS設定でAレコードを使ったDNS設定により、紐付けをします。
リクエストは、ドメイン
-> グローバルIPアドレス(Ingress)
-> Service(linebot-nginx)
を経てコンテナが起動されているPodにたどり着き、コンテナで処理されます。
使用した GCPのサービス は以下のとおりです。
- Google Kubernetes Engine(Node はGoogle Compute Engine上で稼働する)
- Google Cloud Load Balancing
- Google Domain
- Google VPC network
- Google Artifact Registry
ディレクトリ構成
$ tree
.
├── LICENSE
├── README.md
├── __pycache__
│ └── app.cpython-38.pyc
├── app <-------------------- Flask Application 関連のディレクトリ
│ ├── Dockerfile
│ ├── __pycache__
│ │ ├── app.cpython-39.pyc
│ │ └── wsgi.cpython-39.pyc
│ ├── app.py
│ ├── application.log
│ ├── deployment.yaml
│ ├── development.json
│ ├── requirements.txt
│ ├── service.yaml
│ └── uwsgi.ini
├── architecture_linebot.drawio.svg
├── db <-------------------- Mysql を使ったDBサーバーの設定を記述
│ ├── configmap.yaml
│ ├── deployment.yaml
│ ├── initdb.d
│ │ └── init.sql
│ ├── persistent-volume.yaml
│ ├── secret.yaml
│ └── service.yaml
├── docker-compose.yaml <-------------- ローカル実行用docker-compose.yaml
├── ingress <-------------- SSL証明書、Ingress設定を記述
│ ├── managed-cert-ingress.yaml
│ └── managed-cert.yaml
└── web <-------------------- Nginx を使ったWebサーバーの設定を記述
├── Dockerfile
├── default.conf
├── deployment.yaml
└── service.yaml
ローカル環境での立ち上げ
docker-compose
コマンドによりApp、Web、DBのコンテナをローカル環境で立ち上げることができます。
$ pwd
/path/to/dir/linebot/
// App,Web,DBの3つのアプリケーションを起動
$ docker-compose up
事前準備
GKEへアプリをデプロイする以外に設定や作成することを以下に列挙します。
LINE Developers へのアカウント登録、アクセスキーの取得、疎通確認
アクセストークンとシークレットトークンを取得します。自分の場合は、app/development.json内の環境変数で値を保持します。
Line Developersへの登録とLinebotのCallBackの実装に関しては本記事では触れていません。Githubの実装もしくは、他の方の記事をご参照ください。
公開ドメインに対するSSL証明書の取得
GoogleマネージドSSL証明書の使用を参考にIngress
オブジェクトを用いたGoogleマネージドSSL証明書を構築しました。
静的外部グローバルIPアドレスの予約
gcloud compute addresses create linebot-ingress --global --ip-version IPV4
linebot-ingress
には、好きなIPアドレスの名前を入力します。ここで作成するアドレスはリージョンタイプでなくグローバルタイプを指定する必要があります。
これは後のIngress
オブジェクト内のglobal-static-ip-nameで指定するのでメモしておいてください。
一般公開向けドメインの取得
公開用ドメインの取得をします。筆者はGoogle Domainを使用しています。取得したドメイン名も後のIngress
オブジェクト内で指定します。
取得したドメインのDNS設定
取得したドメインと予約済静的アドレスの紐付けを行うため、DNSの設定を修正します。
今回はドメインに対してIPアドレスを紐つけることなので、Aレコードを新規で追加します。
Google Domain であれば、Google Domain
-> DNS
-> カスタムレコード
-> カスタムレコードを管理
から新規レコードの追加を行うことができます。
TTLはデフォルトの1時間に設定しました。
GKEクラスタへのデプロイの流れ
GCP Artifact Registry の作成
↓
GCP クラスタの作成とNodeの作成
↓
Dockerfileのビルドと作成されたイメージをGCP Artifact Registryへ保存
↓
Deployment, Service など各k8sオブジェクトのデプロイ
↓
動作確認(+GCPクラスタの削除)
GCP Artifact Registry の作成
PROJECT_ID
にGCPのプロジェクトIDを指定して、GCP Artifact Registry を作成します。
Artifact RegistryはBuildして作成したDocker Imageの保存場所です。後にDeployment
オブジェクトでArtifact Registry のイメージを指定します。
$ gcloud artifacts repositories create linebot-repo \
--project={PROJECT_ID} \
--repository-format=docker \
--location=asia-northeast1 \
--description="Docker repository"
GCP クラスタの作成とNodeの作成
gcloud container clusters create linebot-gke --num-nodes 3 --zone asia-northeast1-a
ここではlinebot-gke
というクラスタを作成しました。
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-linebot-gke-xxxxxxxxxxxxxxxxxxxxxxxxxx Ready <none> 2d v1.21.9-gke.1002
gke-linebot-gke-yyyyyyyyyyyyyyyyyyyyyyyyyy Ready <none> 2d v1.21.9-gke.1002
gke-linebot-gke-zzzzzzzzzzzzzzzzzzzzzzzzzz Ready <none> 2d v1.21.9-gke.1002
Dockerfileのビルドと作成されたイメージをGCP Artifact Registryへ保存
Dockerfile
uwsgiを使ってFlaskアプリケーションをDockerコンテナ内で立ち上げます。コンテナのポートに5000番を使用します。
Nginx からはhttp://app-server:5000
でFlaskアプリケーションにアクセスします。
後のdeployment
のmetadata
により、Kubernetes内部でapp-server
の名前解決を行い、Nginx<=>uwsgi(Flask)間の通信を行います。
app/Dockerfile
FROM python:3.9.7
WORKDIR /app
COPY . /app
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
ENV FLASK_APP=app
ENV FLASK_DEBUG=1
EXPOSE 5000
CMD ["uwsgi","--ini","/app/uwsgi.ini"]
Nginxの初期状態の設定を./default.conf
のファイル内に記述します。
location /
のlocation
ディレクティブによりhttp通信(Port80を経由した)の /
配下のすべてのアクセスに対してFlaskへリクエストを渡しています。
web/Dockerfile
FROM nginx:latest
COPY ./default.conf /etc/nginx/conf.d/
CMD ["nginx", "-g", "daemon off;", "-c", "/etc/nginx/nginx.conf"]
server {
listen 80;
location / {
proxy_pass http://app-server:5000;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
ビルドとイメージのPush
次のコマンドでDockerfileのビルドとArtifact Registryへ保存します。ビルドはGCP上で行われます。
// Build image and deploy
$ pwd
/path/to/dir/linebot/
// Flask App image build and push
$ gcloud builds submit \
--tag {PROJECT_ID}/linebot-repo/app-server:1.0.0 ./app/
// Nginx container image build and push
$ gcloud builds submit \
--tag {PROJECT_ID}/linebot-repo/linebot-nginx:1.0.0 ./web/
各Kubernetes オブジェクトを作成
次に各アプリケーションのKubernetes オブジェクトを作成していきます。
今回使用したk8sのオブジェクトは以下のとおりです。
Object | Explanation |
---|---|
Deployment | Pod の各リソースの値を定義する |
Service | Kubernetes クラスタ内外のネットワークを定義する |
ClusterIP(Service) | Service の種類の一つ。直接外部と通信を行わない |
NodePort(Service) | Service の種類の一つ。直接外部と通信が可能 |
Ingress | SSL 終端やロードバランシングの機能を持つオブジェクト |
Pod | Kubernetes 実行時の最小単位 |
ConfigMap | 環境変数などのパラメータを保持する |
Secret | 環境変数などの機密性の高いパラメータを保持する |
ManagedCertificate | SSL 証明書の管理を行う |
PersistentVolume | Kubernetes クラスタ上に Volume マウントの割当を行う |
PersistentVolumeClaim | PersistentVolume の実際に必要とするリソースを記述するを |
また本システムにおいて各アプリケーションのmetadata
を以下としています。
- App(Flask):
app-server
- Web(Nginx):
linebot-nginx
- DB(Mysql):
mysql-db
まずApp、Web、DBアプリのためDeployment
とService
が必要です。
Deployment
では各コンテナが実行される最小単位であるPod
の状態を定義します。
Service
は、そのPod
がクラスタ外、またクラスタ内のオブジェクトと通信するために必要なネットワーク設定を定義します。
ConfigMap
とSecret
は、DBのユーザー名やパスワードを保持するために設置しました。ConfigMap
は特に公開しても問題ないパラメータを保持しているのに対して、Secret
では機密度の高い情報を保持
しています。
またPersistentVolume
とPersistentVolumeClaim
を使って、Mysqlの永続化ボリュームをKubernetesクラスタ上へ割り当てを行います。
Kubernetesによって作成されたPod
のデータはPod
が削除されると消えてしまうため、再起動のたびにDBデータが初期化されます。
Pod
が削除された際にMysqlのデータが消えないように、PersistentVolume
のオブジェクトを使ってデータの永続化を行います。
本格的に本番環境に対してDBサーバーを構築する場合は、Cloud SQLなどのマネージドサービスを使うことが推奨されます。本システムではあくまで第一目的が学習であるため、上記のようなシステム構成としました。
Ingress
の代わりにService
のLoadbalancer
タイプを使用することで外部にアプリを公開することが可能ですが、
今回はSSL証明書の設定を管理するために設置しました。Ingress
の中でManagedCertificate
のオブジェクトを参照します。
Deployment
Deployment はPodとReplicaSetの宣言的なアップデート機能を提供します。Deploymentにおいて 理想的な状態を記述すると、Deploymentコントローラーは指定された頻度で現在の状態を理想的な状態に変更します。Deploymentを定義することによって、新しいReplicaSetを作成したり、既存のDeploymentを削除して新しいDeploymentで全てのリソースを適用できます。
Deployment
によりPod
のあるべき状態を定義します。仮にPod
が削除されたり、内部でエラーが発生した場合、Kubernetesは、Deployment
の記述を元にPod
の再作成を行います。本システムにおいてはNginx
、Flask
、Mysql
コンテナをそれぞれを持つPod
の状態を記述するために使用しています。
Deployment
はPod
の理想的な状態を定義する宣言的なオブジェクトであり、実態としてCPUやメモリリソースを消費するオブジェクトではありません。そのため図においてはNode
の外側からPod
に対して矢印を向けています。
apiVersion: apps/v1
kind: Deployment
metadata:
name: linebot-nginx
spec:
selector:
matchLabels:
app: linebot-nginx
replicas: 1
template:
metadata:
labels:
app: linebot-nginx
spec:
containers:
- name: linebot-nginx
image: asia-northeast1-docker.pkg.dev/gcp-compute-engine-343613/linebot-repo/linebot-nginx:1.0.0
imagePullPolicy: Always
ports:
- containerPort: 80
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-server
spec:
selector:
matchLabels:
app: app-server
replicas: 1
template:
metadata:
labels:
app: app-server
spec:
containers:
- name: app-server
image: asia-northeast1-docker.pkg.dev/gcp-compute-engine-343613/linebot-repo/app-server:1.0.0
imagePullPolicy: Always
ports:
- containerPort: 5000
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-db
spec:
selector:
matchLabels:
app: mysql-db
strategy:
type: Recreate
template:
metadata:
labels:
app: mysql-db
spec:
containers:
- image: mysql:5.7
name: mysql
resources:
env:
- name: MYSQL_ROOT_PASSWORD
value: password
- name: MYSQL_DATABASE
value: linebot
- name: MYSQL_USER
value: kota1110
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: pass-secret
key: password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
- name: init-sql-configmap
mountPath: /docker-entrypoint-initdb.d
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pv-claim
readOnly: false
- name: init-sql-configmap
configMap:
name: init-db-sql
items:
- key: init.sql
path: init.sql
Service
Podの集合で実行されているアプリケーションをネットワークサービスとして公開する抽象的な方法です。
Kubernetesではなじみのないサービスディスカバリーのメカニズムを使用するためにユーザーがアプリケーションの修正をする必要はありません。 KubernetesはPodにそれぞれのIPアドレス割り振りや、Podのセットに対する単一のDNS名を提供したり、それらのPodのセットに対する負荷分散が可能です。
Service
はPod
間の通信制御のパラメータを記述します。Deployment
ではPod
は外部との通信を行うことができません。Service
でPod
間のネットワークを記述し外部から参照できる名前、もしくは、IPアドレス(LoadBalancer
タイプの場合)を発行することで、Kubernetes外部と内部の間でのネットワークの橋渡しを行う役割を担っています。
Service
はTypeにより識別する異なるネットワーク構成を提供します。
ClusterIP
、LoadBalancer
、NodePort
が代表的です。
それぞれの違いは、Linkの記事にとてもわかり易くまとめられています。
ClusterIP
は、クラスタ外からアクセスすることができないIPアドレスを発行します。一方、NodePort
とLoadBalancer
のService
はクラスタ外からアクセスすることが可能です。NodePort
とLoadBalancer
の違いは、
各ノードに個別でアクセスを行う場合はNodePort
を利用し、各ノードへの振り分けをロードバランサが行う場合はLoadBalancer
を利用します。
本システムにおいては、Nginxを外部からのリクエストを捌くためのリバースプロキシとして外部に公開したいこと、また、AppコンテナとDBコンテナは外部に公開する必要がないことから
- Webコンテナ(
Nginx
) ->NodePort
- Appコンテナ、DBコンテナ->
ClusterIP
としました。
apiVersion: v1
kind: Service
metadata:
name: app-server
labels:
app: app-server
spec:
type: ClusterIP
ports:
- name: http-port
protocol: TCP
port: 5000
targetPort: 5000
selector:
app: app-server
apiVersion: v1
kind: Service
metadata:
name: linebot-nginx
labels:
app: linebot-nginx
spec:
type: NodePort
ports:
- name: http-port
protocol: TCP
port: 80
targetPort: 80
nodePort: 30082
selector:
app: linebot-nginx
apiVersion: v1
kind: Service
metadata:
name: mysql-db
spec:
type: ClusterIP
ports:
- name: mysql
port: 3306
targetPort: 3306
protocol: TCP
selector:
app: mysql-db
web/service.yaml
のPortの意味するところは以下のとおりです。
port: 80 -> 外部からのアクセスを受け取るコンテナ内のアプリケーションのPort番号
targetPort: 80 -> 転送先のPort番号
nodePort: 30082 -> Podが割り当てられた外部からアクセスされるNodeのPort番号
また後ほど各オブジェクトをデプロイしPod
を立ち上げた後、
linebot-nginx
のPod
に入り、app-server
のmetadata
の名前解決ができることを確認できます。
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
app-server1-xxxxxxxxx 1/1 Running 0 23m
linebot-nginx-yyyyyyyyy 1/1 Running 0 23m
mysql-db-zzzzzzzzz 1/1 Running 0 165m
$ kubectl exec -it linebot-nginx-yyyyyyyyy -- bash
root@linebot-nginx-yyyyyyyyy:/# curl "http://app-server:5000"
hello world!
オブジェクトに名前を付与する似た概念にLabel
とSelector
がありますがリソースをグループ化し、そのまとまったリソースを使う際に参照されるもののようです。ネットワークの名前解決の際には使われていないようでした。参考
Ingress
クラスター内のServiceに対する外部からのアクセス(主にHTTP)を管理するAPIオブジェクトです。Ingressは負荷分散、SSL終端、名前ベースの仮想ホスティングの機能を提供します。
Ingress
は、外部からのアクセスを内部Service
に転送する役割を担っています。本システムではGKEのGoogleマネージドSSL証明書を使用するManagedCertificate
オブジェクトを作成し、SSL証明書を使用する機能をIngress
で参照することで、mydomain.com
サーバーのSSL暗号化を実現しています。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: managed-cert-ingress
annotations:
kubernetes.io/ingress.global-static-ip-name: linebot-ingress # 作成した静的アドレス名
networking.gke.io/managed-certificates: managed-cert
kubernetes.io/ingress.class: "gce"
spec:
defaultBackend:
service:
name: linebot-nginx # バックエンドのService名。ここでは Webアプリケーションのlinebot-nginx を指定する。
port:
number: 80
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
name: managed-cert
spec:
domains:
- mydomain.com # 取得したドメイン名
Pod
Pod
は、Kubernetesアプリケーションの基本的な実行単位です。これは、作成またはデプロイするKubernetesオブジェクトモデルの中で最小かつ最も単純な単位です。Pod
は、クラスターで実行されているプロセスを表します。
各アプリケーションコンテナはPod
内で作成されます。
Deployment
で記述されたリソースの分だけ、Node
上に実際のリソースが割り当てられます。基本的には、1コンテナ1Podでデプロイします。サイドカーコンテナを使うことで1Podに2コンテナ以上デプロイすることも可能です。
今回、Pod
の定義はDeployment
オブジェクトで管理したためPod
自体のオブジェクトは作成しませんでした。
ConfigMap
ConfigMapは、 機密性のないデータをキーと値のペアで保存するために使用されるAPIオブジェクトです。Podは、環境変数、コマンドライン引数、またはボリューム内の設定ファイルとしてConfigMapを使用できます。ConfigMapを使用すると、環境固有の設定をコンテナイメージから分離できるため、アプリケーションを簡単に移植できるようになります。
アプリケーション固有のパラメータを管理するのに役に立つオブジェクトがConfigMap
です。生テキストのままテキストを保持するので機密性の高いデータを保存するには適しませんがオプションなどの設定値を保存できるオブジェクトです。
本システムではMysql
コンテナ立ち上げ時に実行したいSQLを参照するために使用しました。
metadata: init-db-sql
にてdb/deployment.yaml
により参照されます。
(この記事が参考になりました。)
apiVersion: v1
kind: ConfigMap
metadata:
name: init-db-sql
data:
init.sql: |
use linebot;
CREATE TABLE if not exists `testTable`(
`id` int(11),
`name` varchar(30)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
CREATE TABLE if not exists `itemTable`(
`user_id` varchar(50),
`item` varchar(50),
`todo_flg` int(11),
PRIMARY KEY (`user_id`, `item`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
Secret
KubernetesのSecretはパスワード、OAuthトークン、SSHキーのような機密情報を保存し、管理できるようにします。 Secretに機密情報を保存することは、それらをPodの定義やコンテナイメージに直接記載するより、安全で柔軟です。 詳しくはSecretの設計文書を参照してください。Secretはパスワード、トークン、キーのような小容量の機密データを含むオブジェクトです。 他の方法としては、そのような情報はPodの定義やイメージに含めることができます。 ユーザーはSecretを作ることができ、またシステムが作るSecretもあります。
Base64
などでエンコードを行いパスワードなどの機密情報を保持します。あくまでBase64
でエンコードしたテキストを保持するだけで、復号すると元のテキストを参照できます。そのため、暗号化のためにはkubesecなど別の機能を連携する必要があります。
パスワードなどの機密性の高い設定値を生データで保持せず扱えることがメリットです。
本システムではMysql
のパスワードをSecret
で管理しました。
apiVersion: v1
kind: Secret
metadata:
name: pass-secret
type: Opaque
data:
password: XXXXXXXXXXXXXX # Base64でエンコードしたMysql Passwordの値
永続化ボリューム(PersistentVolume,PersistentVolumeClaim
ストレージを管理することはインスタンスを管理することとは全くの別物です。
PersistentVolume
サブシステムは、ストレージが何から提供されているか、どのように消費されているかをユーザーと管理者から抽象化するAPIを提供します。これを実現するためのPersistentVolume
とPersistentVolumeClaim
という2つの新しいAPIリソースを紹介します。
PersistentVolume
PersistentVolume (PV)はストレージクラスを使って管理者もしくは動的にプロビジョニングされるクラスターのストレージの一部です。これはNodeと同じようにクラスターリソースの一部です。PVはVolumeのようなボリュームプラグインですが、PVを使う個別のPodとは独立したライフサイクルを持っています。このAPIオブジェクトはNFS、iSCSIやクラウドプロバイダー固有のストレージシステムの実装の詳細を捕捉します。
PersistentVolumeClaim
PersistentVolumeClaim(PVC)はユーザーによって要求されるストレージです。これはPodと似ています。PodはNodeリソースを消費し、PVCはPVリソースを消費します。Podは特定レベルのCPUとメモリーリソースを要求することができます。クレームは特定のサイズやアクセスモード(例えば、ReadWriteOnce、ReadOnlyMany、ReadWriteManyにマウントできます。詳しくはアクセスモードを参照してください)を要求することができます。
PersistentVolume
は、Kubernetesクラスタ上に割り当てられるボリュームを定義します。Kubernetesクラスタが削除されるとPersistentVolume
も削除されますが、Podが再起動されてもVolume自体は残るので、Pod
の再起動のたびにMysqlのデータが消えてしまうことはありません。
PersistentVolumeClaim
で要求するリソースを定義します。PersistentVolume
と合わせて使用する必要があるようです。
(この記事が参考になりました)
kind: PersistentVolume
apiVersion: v1
metadata:
name: mysql-pv-volume # PVの名前
labels:
type: local
spec:
storageClassName: manual # PVCと一致させる必要がある
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany # 複数Node読み書き可
hostPath:
path: /var/lib/data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
spec:
# storageClassName=manualのPVを探してマウントする
storageClassName: manual
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi # PVが持っている容量のうち5GBを使用する
各k8sオブジェクトのデプロイ
# Build & Run containers
kubectl apply \
-f app/deployment.yaml \
-f app/service.yaml \
-f web/deployment.yaml \
-f web/service.yaml \
-f db/deployment.yaml \
-f db/service.yaml \
-f db/secret.yaml \
-f db/configmap.yaml \
-f db/persistent-volume.yaml \
-f ingress/managed-cert.yaml \
-f ingress/managed-cert-ingress.yaml
GCPクラスタの削除
# Make sure delete cluster not to be billed!
$ gcloud container clusters delete linebot-gke --zone asia-northeast1
おわりに
GKEで稼働するコンテナベースの簡易なFlaskアプリケーションを作成しました。
TODO管理Botとしては、あくまで個人使用を目的として考えていますが、
GKE上で構築された3層構造のアプリケーションは、LineBot以外にも他展開ができるかと思います。
- k8s上にAPIサーバーとフロントエンドのアプリケーションを作成し、GKEベースのWebアプリケーションを作成する。
- TODO管理のみならず、他のAPIと連携したLineBot等
GKEでシステムを運用するにはサーバーレスアプリケーションに比べてKubernetesの学習コストと運用費用がかかります。
スケール前提の個人開発のアプリケーション、もしくは中規模以上のチームで運用するシステムにおいてはKubernetesを導入するメリットもあるかと。
次はKubernetesの最適なリソース管理の方法を模索したり、Terraformを利用したGCPリソースの管理等も試していきたいです。
その他の参考文献
- Kubernetes公式ドキュメント
- Kubernetes道場(各k8sオブジェクトが非常にわかりやすくまとめられています。)