MySQL Operator for Kubernetes の構築をさっくりと試してみた

# 概要
MySQL Operator for Kubernetes（以下MySQL Operator）というKubernetes上にMySQLの環境を手軽に用意できるOperatorを試してみましたのでまとめてみます。
（普段はインメモリのDBやSQLiteのようなファイルベースを使いがち）

## 本記事の対象
- Kubernetesを触り始めた方
- 簡単にMySQLを構築してみたい方
- MySQL Operatorを使ってみたい方

※本記事ではKubernetesの環境はご自身で準備いただくことを前提としております

# MySQL Operatorについて
OperatorというKubernetesの運用自動化に使われる拡張機能を利用して、Oracle社のMySQL開発チームにて開発がされたものとなります。
MySQL OperatorはMySQL InnoDB Clusterの管理に重点を置いております。
[参考リンク：KubernetesのOperatorについて](https://kubernetes.io/ja/docs/concepts/extend-kubernetes/operator/)
[開発元Github：MySQL Operator for Kubernetes](https://github.com/mysql/mysql-operator)

## MySQL InnoDB Clusterについて
複数台のMySQLServerでの高可用性やスケーリングなどの機能を提供するものになります。
最低3台のMySQL Serverと、アプリケーションとMySQL Server間のルーティングを提供するMySQL Routerで構成がされます。
[引用元](https://dev.mysql.com/doc/refman/8.0/en/mysql-innodb-cluster-introduction.html)

## MySQL Operatorのアーキテクチャ図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406495/701604aa-705b-3735-1931-37d25fafdd3d.png)
[画像引用元](https://dev.mysql.com/doc/mysql-operator/en/mysql-operator-introduction.html)

# 構築手順
それではここからMySQL Operatorの構築した手順について記載いたします。
基本的には公式ドキュメントを参考にしながら進めております。
[公式ドキュメント](https://dev.mysql.com/doc/mysql-operator/en/mysql-operator-introduction.html)

## 今回展開先に使用したKubernetes環境
Kubeadmで構築した1台のMasterと2台のWorkerで成る以下のようなKubernetesクラスタに対して展開を行いました。
一応以下記事に同じ構成を作成する手順をまとめております。
[よく使っているkubeadmを使ったKubernetes検証環境の構築](https://qiita.com/strawberryjam/items/f0ff7bef425f6a711b10)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406495/5d65082d-a6f9-693f-a26c-7a052860f248.png)


## MySQL Operatorデプロイ

### CRDのインストール
MySQL Operatorで使用されるカスタム リソース定義 (CRD) をインストールします
```shell
kubectl apply -f https://raw.githubusercontent.com/mysql/mysql-operator/trunk/deploy/deploy-crds.yaml

#出力
customresourcedefinition.apiextensions.k8s.io/innodbclusters.mysql.oracle.com created
customresourcedefinition.apiextensions.k8s.io/mysqlbackups.mysql.oracle.com created
customresourcedefinition.apiextensions.k8s.io/clusterkopfpeerings.zalando.org created
customresourcedefinition.apiextensions.k8s.io/kopfpeerings.zalando.org created
```
### MySQL Operator デプロイ
以下コマンドでMySQL Operatorのデプロイを実施します。
```shell
kubectl apply -f https://raw.githubusercontent.com/mysql/mysql-operator/trunk/deploy/deploy-operator.yaml

#出力
clusterrole.rbac.authorization.k8s.io/mysql-operator created
clusterrole.rbac.authorization.k8s.io/mysql-sidecar created
clusterrolebinding.rbac.authorization.k8s.io/mysql-operator-rolebinding created
clusterkopfpeering.zalando.org/mysql-operator created
namespace/mysql-operator created
serviceaccount/mysql-operator-sa created
deployment.apps/mysql-operator created
```

名前空間[mysql-operator]が自動で作成されデプロイが行われたため、実行に問題がないか確認。
```shell
kubectl get deployment mysql-operator --namespace mysql-operator

#出力
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
mysql-operator   1/1     1            1           25h
```

以上でMySQL Operatorの準備が完了となります。
ここから先はMySQL Operatorで管理するMySQL InnoDB Clusterを展開していきます。

## MySQL InnoDB Clusterデプロイ
### Secret作成
MySQLのrootユーザー用のSecretを作成しておきます。
※mysqlpwdとtestは任意の値に変更してください
```shell
kubectl create secret generic mysqlpwd \
        --from-literal=rootUser=root \
        --from-literal=rootHost=% \
        --from-literal=rootPassword="test"
#出力
secret/mysqlpwd created
```

### MySQL InnoDB Cluster yaml作成
MySQL InnoDB Clusterをデプロイするためのyamlを準備します。
以下ではMySQL Serverインスタンス3台とMySQL Routerインスタンス1台を定義しています。
※sercretNameは前述の手順で作成したSercretをご記載ください
```mycluster.yaml
apiVersion: mysql.oracle.com/v2
kind: InnoDBCluster
metadata:
  name: mycluster
spec:
  secretName: mysqlpwd
  tlsUseSelfSigned: true
  instances: 3
  router:
    instances: 1
```

### MySQL InnoDB Cluster デプロイ
作成したyamlでInnoDB Clusterのデプロイを行います。
```shell
kubectl apply -f mycluster.yaml

#出力
innodbcluster.mysql.oracle.com/mycluster created
```

以下コマンドでプロセスがすべてONLINEになるまで待機をします。
（少し時間がかかると聞いたため、ご飯でも食べてきます）
```
kubectl get innodbcluster --watch

#出力
NAME        STATUS    ONLINE   INSTANCES   ROUTERS   AGE
mycluster   PENDING   0        3           1         15s
```

### MySQL InnoDB Cluster デプロイ失敗
ここでしばらく待つ（ご飯から帰ってくる）とONLINEになっているはずが、、一向にならない。。
そこで生成されているPodのログをのぞいてみます。
```shell
kubectl describe pod mycluster-0

#出力抜粋
Warning  FailedScheduling  11s    default-scheduler  0/3 nodes are available: 3 pod has unbound immediate PersistentVolumeClaims. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.

Error    Logging           22s    kopf               Handler 'on_pod_create' failed temporarily: Sidecar of mycluster-0 is not yet configured
  Normal   Logging           22s    kopf               POD CREATED: pod=mycluster-0 ContainersReady=None Ready=None gate[configured]=None
```
中身を見るとWarningの中にPersistentVolumeClaimsがバインドされていない旨の記載があります。
ここでPersistentVolumeを作成していなかったことに気づきます。

## PersistentVolume の作成
MySQL Serverの展開にPersistentVolume（以下PV）が必要になるため、準備を進めます。
今回INSTANCEは3つあるため、小分けに3つのPVを用意していきます。

### PV作成用のyaml作成
今回はNFSのサーバーがありましたため、そちらを使用していきます。
※NFS等の外部ストレージの用意が急遽難しい場合はhostPathの指定でローカルのディレクトリで取り合えず試すことはできます。
※hostPathの指定は以下yaml内のコメントアウト部分です。nfsの記載と入れ替えでご利用ください。
```pv0001.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0001
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    server: ××.××.××.××
    path: /export/nfs/pv0001
  #hostPath:
  #  path: /data/pv0001
```

### PV作成
作成したyamlでPVを作成します。
```shell
kubectl apply -f pv0001.yaml

#出力
persistentvolume/pv0001 created
```

今回はPVを3つ用意してSTATUSがAvailableになっていることを確認します。
```
kubectl get pv

#出力
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv0001   50Gi       RWX            Recycle          Available                                   115m
pv0002   50Gi       RWX            Recycle          Available                                   115m
pv0003   50Gi       RWX            Recycle          Available                                   115m
```
これでPVの準備は完了となります。

### 補足
今回私はNFSを利用しましたが、それにあたりKubernetes Clusterに参加しているMaster及びWorkerに、
nfs-commonのインストールが必要になります。
同じ手順を追っていただきNFSを使ってみた方は各Kubernetes Nodeに以下のような形でインストールが必要な旨ご注意ください。
```shell
sudo apt-get install nfs-common
```


## MySQL InnoDB Clusterデプロイ リベンジ
それではPVの準備が出来たため、気を取り直してMySQL InnoDB Clusterのデプロイを行います。

### MySQL InnoDB Cluster yaml修正
今回PVの作成の際にaccessModesをReadWriteManyにしています。
そのため、前述で作成したmycluster.yamlでもReadWriteManyを指定する形に修正しておきます。
※datadirVolumeClaimTemplate以下を追記しております。
```mycluster.yaml
apiVersion: mysql.oracle.com/v2
kind: InnoDBCluster
metadata:
  name: mycluster
spec:
  secretName: mysqlpwd
  tlsUseSelfSigned: true
  instances: 3
  router:
    instances: 1
  datadirVolumeClaimTemplate:
    accessModes:
      - ReadWriteMany
```

### MySQL InnoDB Cluster 再デプロイ
修正したyamlにて再度デプロイを実施して、ONLINEになるまで待機します。
```shell
kubectl apply -f mycluster.yaml
kubectl get innodbcluster --watch
```

少し待つとONLINEが3になり全プロセスがONLINEになったことが確認できました。
```
NAME        STATUS   ONLINE   INSTANCES   ROUTERS   AGE
mycluster   ONLINE   3        3           1         112s
```

pvcも試しに見てみると無事にBoundになっており、それぞれバインドされていることが確認できます。
```shell
NAME                  STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
datadir-mycluster-0   Bound    pv0001   50Gi       RWX                           8m30s
datadir-mycluster-1   Bound    pv0002   50Gi       RWX                           8m30s
datadir-mycluster-2   Bound    pv0003   50Gi       RWX                           8m30s
```

# 構築した環境でMySQLを試してみる
せっかくなので構築した環境でMySQLを少し試してみます。

## MySQLへログイン
まずは作成したMySQL Serverへログインをしてみます。

### 接続先のIPアドレスを確認
MySQLにログインするにあたり、接続先のIPアドレスを確認していきます。
Kubernetesクラスタ内から接続できる入り口としてServiceが展開されているので、そのCLUSTER-IPを確認します。
```
kubectl get service mycluster

#出力
NAME        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                                                  AGE
mycluster   ClusterIP   10.102.215.34   <none>        3306/TCP,33060/TCP,6446/TCP,6448/TCP,6447/TCP,6449/TCP   13m
```

### ログイン実行
前述で確認したCLUSTER-IPを用いてログインを行います。
※mysqlコマンドでログインを試すため、Masterに一旦mysqlクライアントをインストールしております
```shell
mysql -h 10.102.215.34 -u root -p
```
上記コマンド実行後パスワードが求められるので、Sercret作成の際に使ったパスワードを入力すると、
無事にログインが完了してSQLコマンドを実行できるようになります。
```shell
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1159
Server version: 8.0.31 MySQL Community Server - GPL

Copyright (c) 2000, 2022, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>
```

## お試しで色々SQLを実行
とりあえずINSERTしたデータをSELECTできるところまで実行してみます。
```sql
-- DBの作成と指定
create database test_db;
use test_db;

-- テーブルの作成とデータの挿入
create table test_table(test_id int auto_increment not null primary key, test_text text not null);
insert into test_table (test_text) values('Hello MySQL Operator');

-- 挿入したデータの取得
select * from test_table;

-- 出力
+---------+----------------------+
| test_id | test_text            |
+---------+----------------------+
|       1 | Hello MySQL Operator |
+---------+----------------------+
1 row in set (0.00 sec)
```
無事にSQLコマンドが動作していることが確認できました。
以上で、MySQL Serverとして問題なく利用できることが確認できたかと思います。

## おまけ：現在のSQL Serverのhostnameについて
最後にhostnameの取得を行ってみます。
MySQLでは以下コマンドでhostnameを取得することができます。
```sql
select @@hostname;

-- 出力
+-------------+
| @@hostname  |
+-------------+
| mycluster-0 |
+-------------+
1 row in set (0.00 sec)
```
mycluster-0というhostnameが取得できました。
このhostnameをkubectl get podsの結果と照らし合わせると、現在Workerのubuntu-kube02上のSQL Serverに入っているんだなと確認ができました。
```shell
kubectl get pods -o wide

# 出力
NAME                                READY   STATUS      RESTARTS      AGE   IP            NODE            NOMINATED NODE   READINESS GATES
mycluster-0                         2/2     Running     0             36m   10.244.2.49   ubuntu-kube02   <none>           2/2
mycluster-1                         2/2     Running     0             36m   10.244.1.39   ubuntu-kube03   <none>           2/2
mycluster-2                         2/2     Running     0             36m   10.244.2.48   ubuntu-kube02   <none>           2/2
```


# 最後に
## 今回構築していて気になったこと
途中でPVを作ってPVCとバインドをしているのですが、その際にストレージクラスの指定をしていませんでした。
なにかデフォルトのなにかが勝手に使用されるのかなと思いましたが、「kubectl get sc」をうっても特に出力はされませんでした。
問題なく動きはしたものの、ストレージクラスはなくても動くもの？なのかというところが疑問のままになりました。
（他パターン等で試したり、色々調べてみます）

## 感想
今回はMySQL Operatorについて構築をしてみた内容についてまとめました。
Kubernetesは触り始めて間もないため、実際は記事に書いてあるよりも苦労したところもありました。
Kubernetesは今後も使う機会がありそうなため、もう少し土台はしっかり学んでおければと思います。