Kubernetes: MySQL Operatorの動作検証

# 概要
　Operatorは、CoreOS社(2018年現在はRedHat社)が、2016年に管理者のもつアプリケーションのナレッジを、Kubernetesの拡張機能を活用しソフトウェアとしてプログラムするものとして、発表されました。Kubernetesでは、セルフヒーリングなどインフラストラクチャーの運用を自律化させるフレームワークが備わっています。このフレームワークを使ったOperatorを開発することでアプリケーションの運用を支援します。
　今回は、2018/7にv0.2.0が公開されたOracle社が提供する[MySQL-Operator](https://github.com/oracle/mysql-operator)の動作検証を行います。

# 検証環境
- Kubernetes v1.10.0
- minikube v0.28.2
- Helm v2.9.1

# 事前準備
　MySQL-Operator はHelmをつかってデプロイするため、Helmのセットアップを行います。

```
$ helm init
...

$ kubectl get pod -n kube-system
NAME                                        READY     STATUS              RESTARTS   AGE
...
tiller-deploy-f9b8476d-79g5k                1/1       Running             0          6m
```
これにより、Kubernetesの`kube-system`ネームスペースにTaillerのPodがデプロイされます。

# Operatorのデプロイ
　初めに公式のGitHubレポジトリから[MySQL-Operator](https://github.com/oracle/mysql-operator)をダウンロードします。

```
$ git clone https://github.com/oracle/mysql-operator.git
Cloning into 'mysql-operator'...
...

$ cd mysql-operator
```
次に、MySQL-Operatorをデプロイします。デプロイ先のデフォルトは`mysql-operator`ネームスペースになります。

```
$ kubectl create namespace mysql-operator
namespace "mysql-operator" created

$ helm install --name mysql-operator mysql-operator
NAME:   mysql-operator
LAST DEPLOYED: Sun Aug 12 19:40:33 2018
NAMESPACE: kube-system
STATUS: DEPLOYED

RESOURCES:
==> v1/ServiceAccount
NAME            SECRETS  AGE
mysql-operator  1        0s
mysql-agent     1        0s

==> v1beta1/CustomResourceDefinition
NAME                                   AGE
mysqlclusters.mysql.oracle.com         0s
mysqlbackups.mysql.oracle.com          0s
mysqlrestores.mysql.oracle.com         0s
mysqlbackupschedules.mysql.oracle.com  0s

==> v1beta1/ClusterRole
mysql-operator  0s
mysql-agent     0s

==> v1beta1/ClusterRoleBinding
NAME            AGE
mysql-agent     0s
mysql-operator  0s

==> v1beta1/Deployment
NAME            DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
mysql-operator  1        1        1           0          0s

==> v1/Pod(related)
NAME                             READY  STATUS   RESTARTS  AGE
mysql-operator-56cb675b7f-jr56r  0/1    Pending  0         0s


NOTES:
Thanks for installing the MySQL Operator.

Check if the operator is running with

kubectl -n mysql-operator get po

$ kubectl get pod -n mysql-operator
NAME                              READY     STATUS    RESTARTS   AGE
mysql-operator-56cb675b7f-jr56r   1/1       Running   0          3m
```
mysql-operator-XXXXXというPodがデプロイされていればデプロイ成功です。
MySQL-Operatorでは、次の4つのCRD(CustomResourceDefinition)がデプロイされます。

- mysqlclusters.mysql.oracle.com  
MySQLのクラスタをコントロール (kind: Cluster)
- mysqlbackups.mysql.oracle.com  
データのバックアップをコントロール (kind:Backup )
- mysqlrestores.mysql.oracle.com  
データのリストアをコントロール (kind: Restore)
- mysqlbackupschedules.mysql.oracle.com  
  定期実行のバックアップスケジュールをコントロール (kind: BackupSchedule)

Operatorでは、こららのCRDを使いMySQLを操作していきます。

# シンプルな構成のMySQLのデプロイ
　今回の検証では、`default`ネームスペースにMySQLをデプロイします。
まずは、`default`ネームスペースにServiceAccountとRoleBindingを設定します。以下のような`serviceaccount.yaml `ファイルを作成します。

```serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: mysql-agent
  namespace: default
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: mysql-agent
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: mysql-agent
subjects:
- kind: ServiceAccount
  name: mysql-agent
  namespace: default
```
定義したServiceAccountとRoleBindingをkubectlを使ってデプロイします。

```
$ kubectl create -f serviceaccount.yaml
serviceaccount "mysql-agent" created
rolebinding "mysql-agent" created
```
続いて、シンプルなMySQLのクラスタ構成を作るための定義を、以下の`cluster.yaml`ファイルに作成します。

```cluster.yaml
apiVersion: mysql.oracle.com/v1alpha1
kind: Cluster
metadata:
  name: my-app-db
  namespace: default
```
定義したClusterをkubectlを使ってデプロイします。

```
$ kubectl create -f cluster.yaml
mysqlcluster "my-app-db" created

$ kubectl get pod
NAME          READY     STATUS    RESTARTS   AGE
my-app-db-0   2/2       Running   0          17m
my-app-db-1   2/2       Running   0          2m
my-app-db-2   2/2       Running   0          1m

$ kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
my-app-db    ClusterIP   None         <none>        3306/TCP   19m
...
```
デプロイが成功すると、StatefulSetにて3つのPodが生成されます。また、Service(svc)には、各Podの名前をDNSへ登録するためのヘッドレスサービス`my-app-d`が登録されています。

続いて、デプロイしたMySQLへアクセスしてみます。
MySQL Operatorのデフォルトでは、Rootパスワードは自動生成されているため、MySQLのRootパスワードを取得します。

```
$ kubectl get secret my-app-db-root-password -o jsonpath="{.data.password}" | base64 --decode
<Password>
```
取得したRootパスワードを使って、MySQLのテーブルにアクセスします。
アクセスには、mysqlコマンドを利用します。

```
$ kubectl run mysql-client --image=mysql:5.7 -it --rm --restart=Never -- mysql -h my-app-db -uroot -p<Password> -e 'SELECT 1'
mysql: [Warning] Using a password on the command line interface can be insecure.
+---+
| 1 |
+---+
| 1 |
+---+
```

デプロイしたMySQLを削除します。

```
$ kubectl delete -f cluster.yaml
mysqlcluster "my-app-db" deleted
```

# Rootパスワード変更+PersistentVolume構成のMySQLのデプロイ
　次に、MySQLの以下の点をカスタマイズしデプロイします。

- MySQLのRootパスワードを任意に変更
- MySQLのデプロイ数を2つに変更
- Multi-MasterモードのMySQLに変更
- MySQLのデータ格納ディレクトリをPersistentVolumeを使用

まず、MySQLのRootパスワードを格納するSecretを作成します。

```
$ kubectl create secret generic mysql-root-user-secret --from-literal=password=hogehoge
secret "mysql-root-user-secret" created
```

次に、MySQLのYAMLを`cluster-pv.yaml`に作成します。

```cluster-pv.yaml
apiVersion: mysql.oracle.com/v1alpha1
kind: Cluster
metadata:
  name: my-app-db
spec:
  rootPasswordSecret:
    name: mysql-root-user-secret
  members: 2
  multiMaster: true
  volumeClaimTemplate:
    metadata:
      name: data
    spec:
      storageClassName: standard
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi
```

`spec. rootPasswordSecret`に、先ほど作成したSecretを指定します。この指定により、MySQLのRootパスワードが任意に指定できます。
`spec. members`には、MySQLのデプロイ数を指定します。ここではデプロイ数を2を設定しています。
`spec.multiMaster`にTrueを指定することで、Multi-MasterモードのMySQLとなります。
`spec.volumeClaimTemplate`には、MySQLのデータ格納ディレクトリの格納先となるPersistentVolumeを指定します。この`volumeClaimTemplate`は、StatefulSetでPodが生成される際に、自動でPersistentVolumeClaimとPersistentVolumeを作成します。今回の検証では、minikubeを利用しているため、あらかじめminikubeに設定されているStorageClassのstandardを`storageClassName`に指定します。

作成した`cluster-pv.yaml`をデプロイします。

```
$ kubectl create -f cluster-pv.yaml
mysqlcluster "my-app-db" created
```
デプロイしたMySQLを確認します。

```
$ kubectl get pod
NAME          READY     STATUS    RESTARTS   AGE
my-app-db-0   2/2       Running   0          1m
my-app-db-1   1/2       Running   0          44s

$ kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
...
my-app-db    ClusterIP   None         <none>        3306/TCP   1m

$ kubectl get pvc
NAME               STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
data-my-app-db-0   Bound     pvc-d536b611-a04c-11e8-9566-080027ae26d3   1Gi        RWO            standard       2m
data-my-app-db-1   Bound     pvc-f115036f-a04c-11e8-9566-080027ae26d3   1Gi        RWO            standard       1m

$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                      STORAGECLASS   REASON    AGE
pvc-d536b611-a04c-11e8-9566-080027ae26d3   1Gi        RWO            Delete           Bound     default/data-my-app-db-0   standard                 2m
pvc-f115036f-a04c-11e8-9566-080027ae26d3   1Gi        RWO            Delete           Bound     default/data-my-app-db-1   standard                 1m
```

MySQLのPodが2つと、データ格納のPersistentVolumeClaimとPersistentVolumeがデプロイされているのが確認できます。
さらに、kubectl describeコマンドでPodを確認してみます。

```
$ kubectl describe pod my-app-db-0
Name:           my-app-db-0
Namespace:      default
...
    Mounts:
      /var/lib/mysql from data (rw)
      /var/lib/mysql-agent from mysqlbackupvolume (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from mysql-agent-token-9ztcv (ro)
...
Volumes:
  data:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  data-my-app-db-0
    ReadOnly:   false
  mysqlbackupvolume:
    Type:    EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:  
  mysql-agent-token-9ztcv:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  mysql-agent-token-9ztcv
    Optional:    false
```

`my-app-db-0`のPod内の`/var/lib/mysql`が、自動生成されたPersistentVolumeClaimの`data-my-app-db-0`を使ってPersistentVolumeをマウントしているのがわかります。
　以上で。Rootパスワード変更+PersistentVolume構成のMySQLがデプロイされました。

# クリーンアップ
 最初に、デプロイしたMySQLと自動生成されたPersistentVolumeClaim、PersistentVolumeを削除します。

```
$ kubectl delete -f cluster-pv.yaml
$ kubectl delete pvc --all
$ kubectl delete pv --all
```
次に、ServiceAccountとRoleBindingを削除します。

```
$ kubectl delete -f serviceaccount.yaml
```

最後に、MySQL Operatorと`mysql-operator`ネームスペースを削除します。

```
$ helm delete mysql-operator
$ kubectl delete ns mysql-operator
```

# 感想
　今回の動作検証では、MySQLのデプロイメントを行いました。MySQL Operatorにより、複雑なYAMLを作成しなくても容易にMySQLのクラスタ構成をデプロイすることが可能です。Operatorを使わず、クラスタ構成のMySQLのYAMLを作成しようとすると、インフラストラクチャーのナレッジとMySQLのナレッジの両方を身につける必要があります。しかし、MySQLが今すぐ欲しい、MySQLを利用するアプリケーションの開発に専念したい等、これらのナレッジをもった管理者が側にいれば良いのになぁと思うケースもあるでしょう。このような場合に、MySQL Operatorを使うと有効かと思います。
　このように、Operatorは管理者のナレッジを実現し、アプリケーションの運用を支援するソフトウェアになります。またOperatorのユーザや開発者が多くなりあらゆる運用のナレッジがOperatorに実装されるようになれば、これまで組織や管理者ごとにバラバラであった運用ナレッジを、世界中で共有することができるかも知れません。
　残念な点としては、MySQL Operatorはv0.2.0(2018/8現在)と開発がスタートしたばかりのため、以下の制限があるようです。今後の開発に期待します。

- Maser-Slave構成が不完全  
MySQLのPodのデプロイは行われますが、Slaveの設定ができておらず、Master-Slave間のデータ同期が出来ません。
- Backup/Restoreで指定できるバックアップ先のストレージはAmazon S3のみ  
今回検証で作成した`cluster-pv.yaml`に`spec. backupVolumeClaimTemplate`を追加することで、Backup/Restoreで利用するためのPersistentVolume, PersistentVolumeClaimは自動生成できます([参照](rs.md#create-a-cluster-with-a-persistent-data-volume-and-a-persistent-volume-to-use-for-backupsrestore))。しかし、CRDのBackup/Restoreではサポートされていません。

# 参考情報
- [Oracle MySQL Operator](https://github.com/oracle/mysql-operator)
- [MySQL Operator Tutorial](https://github.com/oracle/mysql-operator/blob/master/docs/tutorial.md)