6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

GKE(Kubernetes)でTODO管理Linebot を作った話 【Flask, Python】

Last updated at Posted at 2022-04-06

はじめに

業務でKubernetesで運用しているシステムの開発と運用に携わってきました。0からKubernetesのシステム全体を構築する機会はなかったのでこれを機に簡単なFlaskを使ったLineBotをGKE上で構築しました。

個人制作における簡易的なLinebot作成の目的であれば、Cloud functionsAWS Lambdaなどサーバーレスな技術的を用いて迅速にデプロイできるため、Kubernetesを使うのは少しオーバースペックに思うかも知れません。
今回は、Kubernetes(on GKE)を使って0からシステムを構築し、Kubernetesを使ったアプリの全体像を把握したいこと、及び、Kubernetesの各オブジェクトへの理解を深めることを大きな目的としています。

とはいえKubernetesを採用するメリットもあり代表的には以下が挙げられます。

  • リクエスト数が増えた際にスケールアウトしやすいこと。
    -> Podの設定(replicas数など)を変更するだけで容易にスケールアウトが可能であること。リソースを増強する際いちいち新たなサーバーの構築作業に時間をかける必要がありません。
  • リクエスト数が少ないときに最小限のリソースを元にサービスを運用することが可能であること。
    -> 例えばサービスを立ち上げたばかりでユーザーのリクエスト数が少ないときは、Pod数を1台にするなどPod数を限りなく少なくして運用することができます。多くのパブリッククラウドにおいて、NodeやPodの使用数によって課金額が決まるため、システムの負荷に応じてリソースの調整が可能です。

本記事ではDeploymentServiceなど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 -> カスタムレコード -> カスタムレコードを管理から新規レコードの追加を行うことができます。
image.png

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アプリケーションにアクセスします。
後のdeploymentmetadataにより、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"]
web/default.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アプリのためDeploymentServiceが必要です。
Deploymentでは各コンテナが実行される最小単位であるPodの状態を定義します。
Serviceは、そのPodがクラスタ外、またクラスタ内のオブジェクトと通信するために必要なネットワーク設定を定義します。

ConfigMapSecretは、DBのユーザー名やパスワードを保持するために設置しました。ConfigMapは特に公開しても問題ないパラメータを保持しているのに対して、Secretでは機密度の高い情報を保持しています。
またPersistentVolumePersistentVolumeClaimを使って、Mysqlの永続化ボリュームをKubernetesクラスタ上へ割り当てを行います。
Kubernetesによって作成されたPodのデータはPodが削除されると消えてしまうため、再起動のたびにDBデータが初期化されます。
Podが削除された際にMysqlのデータが消えないように、PersistentVolumeのオブジェクトを使ってデータの永続化を行います。
本格的に本番環境に対してDBサーバーを構築する場合は、Cloud SQLなどのマネージドサービスを使うことが推奨されます。本システムではあくまで第一目的が学習であるため、上記のようなシステム構成としました。

Ingressの代わりにServiceLoadbalancerタイプを使用することで外部にアプリを公開することが可能ですが、
今回はSSL証明書の設定を管理するために設置しました。Ingressの中でManagedCertificateのオブジェクトを参照します。

Deployment

Deployment はPodとReplicaSetの宣言的なアップデート機能を提供します。Deploymentにおいて 理想的な状態を記述すると、Deploymentコントローラーは指定された頻度で現在の状態を理想的な状態に変更します。Deploymentを定義することによって、新しいReplicaSetを作成したり、既存のDeploymentを削除して新しいDeploymentで全てのリソースを適用できます。

DeploymentによりPodのあるべき状態を定義します。仮にPodが削除されたり、内部でエラーが発生した場合、Kubernetesは、Deploymentの記述を元にPodの再作成を行います。本システムにおいてはNginxFlaskMysqlコンテナをそれぞれを持つPodの状態を記述するために使用しています。

DeploymentPodの理想的な状態を定義する宣言的なオブジェクトであり、実態としてCPUやメモリリソースを消費するオブジェクトではありません。そのため図においてはNodeの外側からPodに対して矢印を向けています。

web/deployment.yaml
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

app/deployment.yaml
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

db/deployment.yaml
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のセットに対する負荷分散が可能です。

ServicePod間の通信制御のパラメータを記述します。DeploymentではPodは外部との通信を行うことができません。ServicePod間のネットワークを記述し外部から参照できる名前、もしくは、IPアドレス(LoadBalancerタイプの場合)を発行することで、Kubernetes外部と内部の間でのネットワークの橋渡しを行う役割を担っています。

ServiceはTypeにより識別する異なるネットワーク構成を提供します。
ClusterIPLoadBalancerNodePort が代表的です。
それぞれの違いは、Linkの記事にとてもわかり易くまとめられています。
ClusterIPは、クラスタ外からアクセスすることができないIPアドレスを発行します。一方、NodePortLoadBalancerServiceはクラスタ外からアクセスすることが可能です。NodePortLoadBalancerの違いは、
各ノードに個別でアクセスを行う場合はNodePortを利用し、各ノードへの振り分けをロードバランサが行う場合はLoadBalancerを利用します。

本システムにおいては、Nginxを外部からのリクエストを捌くためのリバースプロキシとして外部に公開したいこと、また、AppコンテナとDBコンテナは外部に公開する必要がないことから

  • Webコンテナ(Nginx) -> NodePort
  • Appコンテナ、DBコンテナ-> ClusterIP
    としました。
app/service.yaml
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

web/service.yaml
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

db/service.yaml
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-nginxPodに入り、app-servermetadataの名前解決ができることを確認できます。

$ 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!

オブジェクトに名前を付与する似た概念にLabelSelectorがありますがリソースをグループ化し、そのまとまったリソースを使う際に参照されるもののようです。ネットワークの名前解決の際には使われていないようでした。参考

Ingress

クラスター内のServiceに対する外部からのアクセス(主にHTTP)を管理するAPIオブジェクトです。Ingressは負荷分散、SSL終端、名前ベースの仮想ホスティングの機能を提供します。

Ingressは、外部からのアクセスを内部Serviceに転送する役割を担っています。本システムではGKEのGoogleマネージドSSL証明書を使用するManagedCertificateオブジェクトを作成し、SSL証明書を使用する機能をIngressで参照することで、mydomain.com サーバーのSSL暗号化を実現しています。

ingress/managed-cert-ingress.yaml
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

managed-cert.yaml
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により参照されます。
(この記事が参考になりました。)

db/configmap.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で管理しました。

db/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: pass-secret
type: Opaque
data:
  password: XXXXXXXXXXXXXX  # Base64でエンコードしたMysql Passwordの値

永続化ボリューム(PersistentVolume,PersistentVolumeClaim

ストレージを管理することはインスタンスを管理することとは全くの別物です。PersistentVolumeサブシステムは、ストレージが何から提供されているか、どのように消費されているかをユーザーと管理者から抽象化するAPIを提供します。これを実現するためのPersistentVolumePersistentVolumeClaimという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と合わせて使用する必要があるようです。
(この記事が参考になりました)

db/persistent-volume.yaml
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リソースの管理等も試していきたいです。

その他の参考文献

  1. docker-composeとKubernetesの記述方法を変換するツールとして、Komposeといった両者間のフォーマット変換ツールがあるようですが、細かい設定までは変換できなかったです。

6
4
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
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?