目的
DockerやKubernetes(k8s)のおかげで様々なソフトウェアを気軽に試す、あるいは開発で利用できるようになりましたが、本番利用を考えると多くの考慮点を解決しなければいけません。その1つがNFRs(Non-Functional Requirements: 非機能要件)です。例えば可用性の観点では、スケールアウトしにくいRDBはk8sとは相性が悪いです。
有名なMySQLはレプリケーションに対応していますが、マスタが死んだときのスレーブの自動昇格をどうやって実現するかの考慮が必要ですし、昇格までの時間はサービス停止になります。また、Oracle公式のMySQL Clusterはk8sへの対応が明確に表明されてないと思っています。
そこで、今回はそこをよしなにしてくれるVitessをk8sで動かしてみます。
VitessはYouTubeが開発したMySQLのクラスタリングツールです。最近CNCFにプロジェクトがホストされたので、それなりに由緒正しいものになりました。
検証は以下の環境で行いました。
- IBM Cloud Private 2.1.0.2 (Kubernetes 1.9.1)
手順
Helm版について
Chartは以下サイトで公開されていますが、残念ながらまだalphaクオリティとのことです。なので今回は公式サイトの手動の手順に従って検証します。
etcd-operatorのインストール
先にetcd-operatorをデプロイしておきます。今回はnamespaceをdefaultからteruに変えます。ServiceAccountはdefaultではなくetcd-operatorという名称で新規作成し、ClusterRoleを割り当てます。RoleではなくClusterRoleにする理由は、最初のetcd-operatorはCRD(Custom Resource Definition)というものを自動作成しますが、それにはRoleではなくClusterRoleが必要になるため、のようです。このように必要最低限の権限を付与するのがk8sの本番運用では考慮が必要になります(これが大変です)。
ServiceAccountを作成します。
$ kubectl create sa etcd-operator -n teru
ClusterRoleとClusterRoleBindingを作成します。
$ git clone https://github.com/coreos/etcd-operator.git
$ cd etcd-operator
$ export SA_NAME=etcd-operator
$ export ROLE_NAME=etcd-operator
$ export ROLE_BINDING_NAME=etcd-operator
$ export NAMESPACE=teru
$ sed -e "s/<ROLE_NAME>/${ROLE_NAME}/g" example/rbac/cluster-role-template.yaml | kubectl create -f - -n ${NAMESPACE}
$ sed -e "s/<ROLE_NAME>/${ROLE_NAME}/g" -e "s/<ROLE_BINDING_NAME>/${ROLE_BINDING_NAME}/g" -e "s/<NAMESPACE>/${NAMESPACE}/g" example/rbac/cluster-role-binding-template.yaml -e "s/ name: default/ name: ${SA_NAME}/" | kubectl create -f - -n ${NAMESPACE}
Deploymentを作成します。
$ sed -e "s/^ spec:/ spec:\n serviceAccountName: ${SA_NAME}/" example/deployment.yaml | kubectl create -f - -n ${NAMESPACE}
ログにエラーが出てないか確認します。
$ kubectl get pod -n teru | grep etcd
etcd-operator-65f6cd5964-l98p6 1/1 Running 0 6m
$ kubectl logs etcd-operator-65f6cd5964-l98p6 -n teru
time="2018-03-28T17:40:25Z" level=info msg="etcd-operator Version: 0.9.1"
time="2018-03-28T17:40:25Z" level=info msg="Git SHA: 16f0e1b3"
time="2018-03-28T17:40:25Z" level=info msg="Go Version: go1.9.4"
time="2018-03-28T17:40:25Z" level=info msg="Go OS/Arch: linux/amd64"
time="2018-03-28T17:40:25Z" level=info msg="Event(v1.ObjectReference{Kind:\"Endpoints\", Namespace:\"teru\", Name:\"etcd-operator\", UID:\"1938312d-32af-11e8-a731-000c293da360\", APIVersion:\"v1\", ResourceVersion:\"603114\", FieldPath:\"\"}): type: 'Normal' reason: 'LeaderElection' etcd-operator-65f6cd5964-l98p6 became leader"
Goのインストール
Vitessの各種ツール類を作成するためにGoをインストールします。
$ cd
$ wget https://dl.google.com/go/go1.10.linux-amd64.tar.gz
$ tar zxvf go1.10.linux-amd64.tar.gz
$ sudo cp -r go /usr/local/
環境変数に次のものを追加します。
export GOPATH=${HOME}/go
export PATH=${PATH}:/usr/local/go/bin:${GOPATH}/bin
vtctlclientのビルドとインストール
以下のコマンドでvtctlclientのビルドとインストールをします。成功すれば${GOPATH}/bin/vtctlclient
が出来ます。
$ go get vitess.io/vitess/go/cmd/vtctlclient
$ ls ${GOPATH}/bin/vtctlclient
/home/teru/go/bin/vtctlclient
バックアップの設定
configure.shを実行します。ここでは主にバックアップをどこに保管するかを指定します。GCS(Google Cloud Storage)かNFSが選べます。今回はNFSにしますが、NFSはPod内にマウントされている必要があります。ここでは/mntにしておきます。
$ cd ${GOPATH}/src/vitess.io/vitess/examples/kubernetes
$ ./configure.sh
Vitess Docker image (leave empty for default) []:
Backup Storage (file, gcs) [gcs]: file
Root directory for backups (usually an NFS mount): /mnt
NOTE: You must add your NFS mount to the vtctld-controller-template
and vttablet-pod-template as described in the Kubernetes docs:
http://kubernetes.io/v1.0/docs/user-guide/volumes.html#nfs
NOTEの通り、2つのファイルを修正し、NFS領域をマウントする設定をテンプレートファイルに追記します。
(略)
volumeMounts
- name: backup # 追加
mountPath: /mnt # 追加
(略)
volumes:
- name: backup # 追加
persistentVolumeClaim: # 追加
claimName: vitess-backup # 追加
(略)
volumeMounts
- name: backup # 追加
mountPath: /mnt # 追加
(略)
volumeMounts
- name: backup # 追加
mountPath: /mnt # 追加
(略)
persistentVolumeClaim: # 追加
claimName: vitess-backup # 追加
PersistentVolumeClaimを作っておきます。私は以前の記事でNFSの動的プロビジョニングのためのStorageClassを作っておいたので、それを使います。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: vitess-backup
annotations:
volume.kubernetes.io/storage-class: "nfs"
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 8Gi
$ kubectl create -f vitess-pvc.yaml
etcdの起動
環境変数VITESS_NAMEにnamespaceを、CELLSにセル名をセットし、etcdを実行します。
$ export VITESS_NAME=teru
$ export CELLS=vitess
$ ./etcd-up.sh
Creating etcd service for 'global' cell...
etcdcluster "etcd-global" created
Creating etcd service for 'vittes' cell...
etcdcluster "etcd-vittes" created
Podの状態を確認します。しばらく待つとRunningになります。
$ kubectl get pods -n teru | grep etcd
etcd-global-2z5mpjjqmt 1/1 Running 0 11s
etcd-global-4kzbnlkqwf 1/1 Running 0 27s
etcd-global-r66b9nsqrq 1/1 Running 0 36s
etcd-operator-65f6cd5964-ff24g 1/1 Running 0 1h
etcd-vitess-5wxv9g7tv8 1/1 Running 0 35s
etcd-vitess-ltfflh2gxq 1/1 Running 0 27s
etcd-vitess-nbxfjqsjxd 1/1 Running 0 11s
vtlctldの起動
$ ./vtctld-up.sh
Creating vtctld ClusterIP service...
service "vtctld" created
Creating vtctld replicationcontroller...
replicationcontroller "vtctld" created
persistentvolumeclaim "vitess-backup" created
To access vtctld web UI, start kubectl proxy in another terminal:
kubectl proxy --port=8001
Then visit http://localhost:8001/api/v1/proxy/namespaces/teru/services/vtctld:web/
言われたとおりにkubectl proxy --port=8001
してURLにアクセスしてみましょう。まだ何も定義がありませんが、Vitessのコントロールパネルにアクセスできます。
トポロジーの設定
トポロジーを設定します。
./kvtctl.sh AddCellInfo -server_address http://etcd-${CELLS}-client:2379 ${CELLS}
vttabletの起動
vttabletを起動します。vttabletとは、mysqldと同じPodで動くものだそうです。環境変数KEYSPACEに任意の値をセットしておきます。また、起動するPod数を調整します。以下の例では、全体のPod数を3、そのうち1つをReadOnlyにしています。デフォルトでは5個のPodが起動しそのうち2つがReadOnlyなのですが、私の検証機ではスペック不足で全て起動できませんでしたので、数を減らしました。
$ export KEYSPACE=teru_keyspace
$ export TABLETS_PER_SHARD=3
$ export RDONLY_COUNT=1
$ ./vttablet-up.sh
Creating teru_keyspace.shard-0 pods in cell vitess...
Creating pod for tablet vitess-0000000100...
pod "vttablet-100" created
Creating pod for tablet vitess-0000000101...
pod "vttablet-101" created
Creating pod for tablet vitess-0000000102...
pod "vttablet-102" created
先ほどのコントロールパネルを見ると、teru_keyspaceで1つのシャードができています。
Podの状態を表示します。全てRunningになっています。
$ kubectl get pods -n teru | grep vtt
vttablet-100 2/2 Running 1 59s
vttablet-101 2/2 Running 1 59s
vttablet-102 2/2 Running 1 59s
Tabletの状態を表示します。
$ ./kvtctl.sh ListAllTablets ${CELLS}
Starting port forwarding to vtctld...
vitess-0000000100 teru_keyspace 0 replica 10.1.45.220:15002 10.1.45.220:3306 []
vitess-0000000101 teru_keyspace 0 replica 10.1.225.43:15002 10.1.225.43:3306 []
vitess-0000000102 teru_keyspace 0 rdonly 10.1.45.221:15002 10.1.45.221:3306 []
Replicaが2つ、ReadOnlyが1つできています。Masterはまだなくてよいです。
データベースの初期化
データベースを初期化します。
$ ./kvtctl.sh InitShardMaster -force ${KEYSPACE}/0 ${CELLS}-0000000100
Starting port forwarding to vtctld...
W0404 03:09:31.694664 23743 main.go:58] W0403 18:09:31.688983 reparent.go:181] master-elect tablet vitess-0000000100 is not the shard master, proceeding anyway as -force was used
W0404 03:09:31.696059 23743 main.go:58] W0403 18:09:31.689397 reparent.go:187] master-elect tablet vitess-0000000100 is not a master in the shard, proceeding anyway as -force was used
今回は初回なのでまだマスターが存在しない状態です。そのため、InitShardMaster -forceで一番目のTabletであるvitess-0000000100を強制的にマスターにしています。改めてTabletの状態を見ると、masterが出現しました。
$ ./kvtctl.sh ListAllTablets ${CELLS}
Starting port forwarding to vtctld...
vitess-0000000100 teru_keyspace 0 master 10.1.45.220:15002 10.1.45.220:3306 []
vitess-0000000101 teru_keyspace 0 replica 10.1.225.43:15002 10.1.225.43:3306 []
vitess-0000000102 teru_keyspace 0 rdonly 10.1.45.221:15002 10.1.45.221:3306 []
テーブルの作成
それではテーブルを作成します。ソースツリー付属のテストテーブルを使います。
$ cat create_test_table.sql
CREATE TABLE messages (
page BIGINT(20) UNSIGNED,
time_created_ns BIGINT(20) UNSIGNED,
message VARCHAR(10000),
PRIMARY KEY (page, time_created_ns)
) ENGINE=InnoDB
$ ./kvtctl.sh ApplySchema -sql "$(cat create_test_table.sql)" ${KEYSPACE}
Starting port forwarding to vtctld...
次のコマンドで各tabletにテーブル定義がレプリケーションされていることがわかります。
$ ./kvtctl.sh GetSchema ${CELLS}-0000000100
$ ./kvtctl.sh GetSchema ${CELLS}-0000000101
$ ./kvtctl.sh GetSchema ${CELLS}-0000000102
(例)
Starting port forwarding to vtctld...
{
"database_schema": "CREATE DATABASE /*!32312 IF NOT EXISTS*/ {{.DatabaseName}} /*!40100 DEFAULT CHARACTER SET utf8 */",
"table_definitions": [
{
"name": "messages",
"schema": "CREATE TABLE `messages` (\n `page` bigint(20) unsigned NOT NULL,\n `time_created_ns` bigint(20) unsigned NOT NULL,\n `message` varchar(10000) DEFAULT NULL,\n PRIMARY KEY (`page`,`time_created_ns`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8",
"columns": [
"page",
"time_created_ns",
"message"
],
"primary_key_columns": [
"page",
"time_created_ns"
],
"type": "BASE TABLE",
"data_length": "16384",
"row_count": "0"
}
],
"version": "5b2e5dbcb5766b6c69fe55c81b6ea805"
}
データのバックアップ
ここで最初のバックアップをとります。バックアップはReadOnlyノードの1つを指定して取得します。なぜReadOnlyノードを指定するかというと、バックアップの間はレプリケーションが一時的に停止するので、静止点が取れるのだそうです。
$ ./kvtctl.sh Backup ${CELLS}-0000000102
Starting port forwarding to vtctld...
正しくとれたか確認してみます。
$ ./kvtctl.sh ListBackups ${KEYSPACE}/0
Starting port forwarding to vtctld...
2018-04-03.181103.vitess-0000000102
このバックアップがあると、今後ノードを足したときに自動的にこれを使ってリストアし、マスタからの差分をレプリケーションしてくれるのだそうです。便利ですね。
Routing Schemaの初期化
説明を読んでもよくわかりませんでしたが、何かの初期化のようです。
$ ./kvtctl.sh RebuildVSchemaGraph
Starting port forwarding to vtctld...
vtgateの起動
構成作業の最後ですが、クライアントから生きてるMySQLにルーティングするためのvtgateを起動します。デフォルトではレプリカが3つ起動しますが私の検証環境はリソース不足なので1個にします。
$ export VTGATE_REPLICAS=1
$ ./vtgate-up.sh
Creating vtgate service in cell vitess...
service "vtgate-vitess" created
Creating vtgate replicationcontroller in cell vitess...
replicationcontroller "vtgate-vitess" created
$ kubectl get pods -n teru | grep vt
vtctld-rcdtg 1/1 Running 0 2h
vtgate-vitess-4cb6w 1/1 Running 0 1m
vttablet-100 2/2 Running 1 39m
vttablet-101 2/2 Running 1 39m
vttablet-102 2/2 Running 1 39m
以上でセットアップは完了です。今後、実際に動かしていろいろ検証したいと思います。