case-kの備忘録

日々の備忘録です。データ分析とクラウドに興味があります。

Google Kubernetes Engine プラクティス

Google Kubernetes Engineについて少し調べたので備忘録として残しておきます。

Code

github.com

概要

Google Kubernetes Engineとは

Kubernetesの機能に加えて下記をサポートしてるGCPのフルマネージドサービスです。Kubernetesの既存機能に加え、次の機能があります。

  • Kubernetesのバージョン管理:常に最新の状態になるようにします
  • GCPの他のサービスとの連携GCPの各種サービスとの連携・統合が容易
  • オートスケーリング:CPU使用率やカスタム指標に基づいて、Podやクラスタを自動でスケーリング可能

Kubernetesについては次の記事でまとめました。
www.case-k.jp
GKEについては次の記事を参考にさせていただきました。
Kubernetes Engine  |  Google Cloud
[Cloud OnAir] Dive to Google Kubernetes Engine 2018年8月2日 放送
DevOps 編(全 6 回) | はじめてみよう Google Cloud Platform Online Handson
[Cloud OnAir] Google Cloud で実践するマイクロサービスアーキテクチャ 2019年2月21日 放送

実践編

次の書籍の構成を参考にさせていただきました。書籍に沿って行い、フロントにNuxt.jsバックエンドはGO、プロキシサーバにNginx、データストアにMySQLといった構成です。
gihyo.jp

Cloud SDKをインストール

ローカルホストにCloud SDKをインストールしgcloudコマンドを使えるようにし、upddateします

$ gcloud components update

認証しGCPプロジェクトを操作できるようにします

$ gcloud auth login

プロジェクトを設定

$ gcloud config set project [project id ]

リーージョンは東京リージョンを選びます

$ gcloud config set compute/zone asia-northeast1-a
Updated property [compute/zone].

GKEクラスタ構築

GKEのクラスタを作ります。マシンタイプはコストが低い「n1-standard-1」でクラスタ内のノード数は3とします。

$ gcloud container clusters create casek --machine-type=n1-standard-1 --num-nodes=3

gcloudコマンドで作成したクラスタを制御できるようにするために、kubectlコマンドに認証情報を設定します。

$ gcloud container clusters get-credentials casek
Fetching cluster endpoint and auth data.
kubeconfig entry generated for casek.

kubectlコマンドでGCP上に構築したクラスタを制御できることを確認します。

$ kubectl get nodes
NAME                                   STATUS   ROLES    AGE   VERSION
gke-casek-default-pool-cfeda864-7mgk   Ready    <none>   10m   v1.14.10-gke.17
gke-casek-default-pool-cfeda864-gqv5   Ready    <none>   10m   v1.14.10-gke.17
gke-casek-default-pool-cfeda864-pxjx   Ready    <none>   10m   v1.14.10-gke.17

Kubernetesでストレージを確保するためのリソース

Kubernetesでストレージを確保するためのリソースとしてPersistentVolumeとPersistentVolumeClaimがあります。

PersistentVolumeとPersistentVolumeClaim

PersistentVolumeはストレージの実体で、PersistentVolumeClaimはPersistentVolumeに対して必要な容量を動的に確保するためのものです。
PersistentVolumeClaimのマニフェストファイルは次のようになります。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-example
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName
  resources:
    requests:
      storage:  4Gi
  • accessModes:Podからストレージへのマウントポリシーのこと。「ReadWriteOnceは」どこか1つのノードからR/Wマウントのみ許可されている。
  • storageClassName:StorageClassの名前。利用するストレージの種類を定義。resources.requests.storageでボリュームが必要な容量を指定する。
StorageClass

StorageClassはPersistentVolumeが確保するストレージの種類を定義するリソースとなります。GCPのストレージには「標準」と「SSD」が存在しています。次のマニフェストにはSSDを利用してストレージを作成しています。

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata: 
  name: ssd
  annotations:
    storageclass.kubernetes.io/is-default-class: "false"
  labels:
    kubernetes.io/cluster-service: "true"
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-ssd
  • provisioner:GCPの永久ストレージであるGCEPersistentDiskに対応したVolumePluginであるgce-pdを指定
  • type:SSDに対応したpd-ssdを指定
$ kubectl apply -f storage-class-ssd.yaml
storageclass.storage.k8s.io/ssd created
StatefulSet

StatefulSetはデータストアのように継続的にデータを永続化するステートフルなアプリケーションの管理に向いているリソースです。類似するものでDeploymentは永続的なデータをもつ必要がないステートレスなアプリケーションをデプロイするのに向いているリソースです。

MySQL

まずはMySQLをデプロイします。

Masterをデプロイ
# mysql-master.yaml
apiVersion: v1
kind: Service
metadata: 
  name: mysql-master
  labels:
    app: mysql-master
spec:
  ports:
  - port: 3306
    name: mysql
  clusterIP: None
  selector:
    app: mysql-master
---
apiVersion: apps/v1
kind: StatefulSet
metadata: 
  name: mysql-master
  labels: 
    app: mysql-master
spec:
  serviceName: "mysql-master"
  selector:
    matchLabels:
      app: mysql-master
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql-master
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: mysql
        image: gihyodocker/tododb:latest
        imagePullPolicy: Always
        args:
        - "--ignore-db-dir=lost+found"
        ports:
        - containerPort: 3306
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "gihyo"
        - name: MYSQL_DATABASE
          value: "tododb"
        - name: MYSQL_USER
          value: "gihyo"
        - name: MYSQL_PASSWORD
          value: "gihyo"
        - name: MYSQL_MASTER
          value: "true"
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
  volumeClaimTemplates:
  - metadata:
      name: mysql-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: ssd
      resources:
        requests:
         storage: 4Gi
  • volumeClaimTemplates:PersistentVolumeClaimをPodごとに自動生成するテンプレート

Serviceはクラスタ内で「mysql-master」で名前解決できるよう定義しておきます。
マニフェストができたらデプロイします。

$ kubectl apply -f mysql-master.yaml
service/mysql-master unchanged
statefulset.apps/mysql-master created
Slaveをデプロイ

次にSlaveのStatefuleSetのマニフェストファイルを定義していきます。マニフェストはMasterとほとんど同じです。Serviceはクラスタ内で「mysql-slave」で名前解決できるよう定義しておきます。レプリケーションは2つとします。

apiVersion: v1
kind: Service
metadata: 
  name: mysql-slave
  labels:
    app: mysql-slave
spec:
  ports:
  - port: 3306
    name: mysql
  clusterIP: None
  selector:
    app: mysql-slave
---
apiVersion: apps/v1
kind: StatefulSet
metadata: 
  name: mysql-slave
  labels: 
    app: mysql-slave
spec:
  serviceName: "mysql-slave"
  selector:
    matchLabels:
      app: mysql-slave
  replicas: 2 # master1 slave 2
  updateStrategy: 
    type: OnDelete
  template:
    metadata:
      labels:
        app: mysql-slave
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: mysql
        image: gihyodocker/tododb:latest
        imagePullPolicy: Always
        args:
        - "--ignore-db-dir=lost+found"
        ports:
        - containerPort: 3306
        env: # 変更
        - name: MySQL_MASTER_HOST
          value: "mysql-master"
        - name: MYSQL_ROOT_PASSWORD
          value: "gihyo"
        - name: MYSQL_DATABASE
          value: "tododb"
        - name: MYSQL_REPL_USER
          value: "gihyo"
        - name: MYSQL_REPL_PASSWORD
          value: "gihyo"
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
  volumeClaimTemplates:
  - metadata:
      name: mysql-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: ssd
      resources:
        requests:
         storage: 4Gi
  • updateStrategy:StatefulSetの更新戦略

RollingUpdate:Podの自動更新

updateStrategy:
  type: RollingUpdate
  partition: 3

OnDelete:更新するには手動でPodを削除。削除すると手動で新しいPodを作成する。
Kubernetes道場 13日目 - StatefulSet / DaemonSetについて - Toku's Blog

マニフェストファイルをデプロイし、動作を確認します。

$ kubectl apply -f mysql-slave.yaml
service/mysql-slave created
statefulset.apps/mysql-slave created

$ kubectl get pod
NAME             READY   STATUS    RESTARTS   AGE
mysql-master-0   1/1     Running   0          38m
mysql-slave-0    1/1     Running   1          9m5s
mysql-slave-1    1/1     Running   1          8m20s

$ kubectl exec -it mysql-slave-0 bash mysql -u root -pgihyo tododb -e "SHOW TABLES;"
root@mysql-slave-0:/# mysql -u root -pgihyo tododb

バックエンド

MySQLにデータを取りにいくAPIを作ります。プロキシサーバとしてNginxを介してAPIを実行します。ServiceではAPIで名前解決できるように「todoapi」を選択します。DeploimentではNginxコンテナとAPIコンテナを1つのPodとしてデプロイします。

apiVersion: v1
kind: Service
metadata:
  name: todoapi
  labels:
    app: todoapi
spec:
  selector:
    app: todoapi
  ports:
    - name: http
      port: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: todoapi
  labels:
    app: todoapi
spec:
  replicas: 2
  selector:
    matchLabels:
      app: todoapi
  template:
    metadata:
      labels:
        app: todoapi
    spec:
      containers:
      - name: nginx
        image: gihyodocker/nginx:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 80
        env:
        - name: WORKER_PROCESSES
          value: "2"
        - name: WORKER_CONNECTIONS
          value: "1024"
        - name: LOG_STDOUT
          value: "true"
        - name: BACKEND_HOST
          value: "localhost:8080"
      - name: api
        image: gihyodocker/todoapi:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
        env:
        - name: TODO_BIND
          value: ":8080"
        - name: TODO_MASTER_URL
          value: "gihyo:gihyo@tcp(mysql-master:3306)/tododb?parseTime=true"
        - name: TODO_SLAVE_URL
          value: "gihyo:gihyo@tcp(mysql-slave:3306)/tododb?parseTime=true"

マニフェストファイルをデプロイします、

$ kubectl apply -f todo-api.yaml
service/todoapi unchanged
deployment.apps/todoapi created

フロントエンド

NginxコンテナとWEBコンテナを1つのPodとしてデプロイします。Serviceで疎通できるコンテナ(todoweb)を指定します。Deployment対象はNginxコンテナとWEBコンテナとなります。Nginxコンテナの環境変数「BACKEND_HOST」は同じPod内のWEBコンテナとなるので「localhost:3000」を選びます。WEBコンテナの「TODO_API_URL」にはtodoapiのService名をURLにします。

# todo-web.yaml
apiVersion: v1
kind: Service
metadata:
  name: todoweb
  labels:
    app: todoweb
spec:
  selector:
    app: todoweb
  ports:
    - name: http
      port: 80
  type: NodePort

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: todoweb
  labels:
    name: todoweb
spec:
  replicas: 2
  selector:
    matchLabels:
      app: todoweb
  template:
    metadata:
      labels:
        app: todoweb
    spec:
      volumes:
      - name: assets
        emptyDir: {}
      containers:
      - name: nginx
        image: gihyodocker/nginx-nuxt:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 80
        env:
        - name: WORKER_PROCESSES
          value: "2"
        - name: WORKER_CONNECTIONS
          value: "1024"
        - name: LOG_STDOUT
          value: "true"
        - name: BCKEND_HOST
          value: "localhost:3000"
        volumeMounts:
        - mountPath: /var/www/_nuxt
          name: assets
      - name: web
        image: gihyodocker/todoweb:latest
        imagePullPolicy: Always
        lifecycle:
          postStart:
            exec:
              command:
              - cp
              - -R
              - /todoweb/.nuxt/dist
              - /
        ports:
        - containerPort: 3000
        env:
        - name: TODO_API_URL
          value: http://todoapi
        volumeMounts:
        - mountPath: /dist
          name: assets

マニフェストファイルをデプロイします。

$ kubectl apply -f todo-web.yaml
service/todoweb unchanged
deployment.apps/todoweb created

Ingressでインターネットに公開

Ingressを利用してWEBアプリケーションをインターネットに公開します。GCPを利用しているため、Cloud Load Balancingが利用されます。GCPを使う場合、Serviceを外部公開する上でLoadBalancer Serviceを利用する方法もありますが、HTTPレベルでServiceの制御をするようなケースではあまり向いていないようです。こちらのハンズオンではLoadBalancer Serviceを利用する方法で行っていました。
DevOps 編(全 6 回) | はじめてみよう Google Cloud Platform Online Handson

# ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress
spec:
  rules:
  - http:
      paths:
      - path: /*
        backend:
          serviceName: todoweb
          servicePort: 80
$ kubectl apply -f ingress.yaml
ingress.extensions/ingress created
$  kubectl get pod
NAME                       READY   STATUS    RESTARTS   AGE
mysql-master-0             1/1     Running   0          25h
mysql-slave-0              1/1     Running   1          25h
mysql-slave-1              1/1     Running   1          24h
todoapi-5467bf5d79-79vgl   2/2     Running   0          24h
todoapi-5467bf5d79-9pjrl   2/2     Running   0          24h
todoweb-6df88ccc69-887b6   2/2     Running   0          32s
todoweb-6df88ccc69-qk7sz   2/2     Running   0          24s

所感

運用面などはまだまだですが基礎的な部分を少しは理解できてきたので、以前作った株価のアプリケーションをベースにイメージを作り、GKE上にあげてみたいと思います。