2022/12/06

[小〜中規模向け]GKEにTiDBをデプロイする

gketidbk8smysql

概要

MySQL互換のNewSQLであるTiDBをGKEにデプロイしてみました。

今回は、そのデプロイ方法を備忘録として記載します。

注意

弊社のサービスはまだまだ規模が小さいため、省コスト(開発環境や小規模向けアプリのため)の設定です。具体的にはtidbの構成要素である、pd / tikv / tidb などのプロセスを同一のノードにホストするような設定にしています。

GKEのclusterはregionalなクラスターを作成して、アプリとDBは同一ノードにデプロイするようにしています。
これによって、各zoneに1台、計3台のノードにごっちゃにデプロイするようにしています。

大規模の場合は、公式サイトでやっているようにtidbの pd / tikv / tidb 専用にノードプールを作成する方が良いでしょう。

※ノードの1台を強制削除しても問題ないかはまだ未確認です。 時間ができたら試してみようと思います。(あとバックアップスケジュールとかも時間ができたら・・)

参考

基本的には公式サイトのGKE向けのドキュメントを参考にしています。

デプロイ手順

node poolの設定

# まずは各ノードにtagを付与します。
# pd=true/tikv=true/tidb=true というタグを付与します。後で、pd=trueとなっているノードにpdのpodをデプロイする設定に使います。
% gcloud container node-pools update "your-node-pool-name" \
  --project="your-project-id" \
  --region="your-region" \
  --cluster="your-cluster-name" \
  --node-labels=pd=true,tikv=true,tidb=true

# node-taintsは削除しておきます。全てのpodがノードにデプロイできるようにするためです。
% gcloud container node-pools update "your-node-pool-name" \
  --project="your-project-id" \
  --region="your-region" \
  --cluster="your-cluster-name" \
  --node-taints=""

# 設定が終わったら、以下のコマンドで、ちゃんとtagやtaintsが設定されていることを確認します。
% kubectl get node
% kubectl describe node <node-name-here>

storageclass

次に、clusterで利用するdiskの種類を定義します。 SSDを利用します。

nodelallocnoatimemountOptionsを指定する必要があるようなので、そのようにしました。

# 定義ファイルを作成
% cat <<'EOF' > storageclass.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: pd-custom
provisioner: kubernetes.io/gce-pd
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
  type: pd-ssd
mountOptions:
  - nodelalloc,noatime
EOF

# デプロイ
$ kubectl apply -f storageclass.yaml

# 確認(pd-customという名前のレコードが出てくればOK / そのほかにもデフォルトで作成されているpremium-rwoやstandard-rwoなども一緒に出てきます。)
$ kubectl get storageclass
NAME                     PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
pd-custom                kubernetes.io/gce-pd    Delete          WaitForFirstConsumer   true                   XX
premium-rwo              pd.csi.storage.gke.io   Delete          WaitForFirstConsumer   true                   XX
standard                 kubernetes.io/gce-pd    Delete          Immediate              true                   XX
standard-rwo (default)   pd.csi.storage.gke.io   Delete          WaitForFirstConsumer   true                   XX

operator

CRDs

次に、tidb operatorのためのCustom Resource Definitionsをデプロイします。

% wget https://raw.githubusercontent.com/pingcap/tidb-operator/v1.3.9/manifests/crd.yaml -O crd.yaml
% kubectl create -f crd.yaml
customresourcedefinition.apiextensions.k8s.io/backupschedules.pingcap.com created
customresourcedefinition.apiextensions.k8s.io/backups.pingcap.com created
customresourcedefinition.apiextensions.k8s.io/dmclusters.pingcap.com created
customresourcedefinition.apiextensions.k8s.io/restores.pingcap.com created
customresourcedefinition.apiextensions.k8s.io/tidbclusterautoscalers.pingcap.com created
customresourcedefinition.apiextensions.k8s.io/tidbclusters.pingcap.com created
customresourcedefinition.apiextensions.k8s.io/tidbinitializers.pingcap.com created
customresourcedefinition.apiextensions.k8s.io/tidbmonitors.pingcap.com created
customresourcedefinition.apiextensions.k8s.io/tidbngmonitorings.pingcap.com created

helm3 add pingcap repo

# confirm helm version
% helm version
version.BuildInfo{Version:"v3.10.2", GitCommit:"50f003e5ee8704ec937a756c646870227d7c8b58", GitTreeState:"clean", GoVersion:"go1.19.3"}

# add pingcap repo for helm3
% helm repo add pingcap https://charts.pingcap.org/

namespace

tidb-operator用のnamespaceを作成します。

# 定義ファイルを作成
% cat <<'EOF' > namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: tidb-admin
EOF

# デプロイ
% kubectl create -f namespace.yaml
namespace/tidb-admin created

tidb-operator

helm3を使って、tidb-operatorをデプロイします。

# install tidb-operator with helm3
# for more version information, check [github releases](https://github.com/pingcap/tidb-operator/releases) in advance.
% helm install --namespace tidb-admin tidb-operator pingcap/tidb-operator --version v1.3.9
NAME: tidb-operator
LAST DEPLOYED: Thu Dec  1 15:56:21 2022
NAMESPACE: tidb-admin
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Make sure tidb-operator components are running:

    kubectl get pods --namespace tidb-admin -l app.kubernetes.io/instance=tidb-operator

# check the operator pods are running
% kubectl get pods --namespace tidb-admin -l app.kubernetes.io/instance=tidb-operator
NAME                                       READY   STATUS    RESTARTS   AGE
tidb-controller-manager-xxxxxxxxxx-xxxxx   1/1     Running   0          XX
tidb-scheduler-xxxxxxxxxx-xxxxx            2/2     Running   0          XX

tidb cluster

namespace

tidb-cluster用のnamespaceを作成します。

# 定義ファイルを作成
% cat <<'EOF' > namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: tidb-cluster
EOF

# デプロイ
% kubectl create -f namespace.yaml
namespace/tidb-cluster created

tidb cluster

tidb-clusterをデプロイします。

# 定義ファイルの作成
# https://raw.githubusercontent.com/pingcap/tidb-operator/master/examples/gcp/tidb-cluster.yaml -O tidb-cluster.yaml を参考にしました。
% cat <<'EOF' > tidb-cluster.yaml
apiVersion: pingcap.com/v1alpha1
kind: TidbCluster
metadata:
  name: tidb
  namespace: tidb-cluster
spec:
  version: v6.1.0
  timezone: UTC
  configUpdateStrategy: RollingUpdate
  pvReclaimPolicy: Retain
  enableDynamicConfiguration: true
  schedulerName: default-scheduler
  topologySpreadConstraints:
  - topologyKey: topology.kubernetes.io/zone
  helper:
    image: alpine:3.16.0
  pd:
    baseImage: pingcap/pd
    maxFailoverCount: 0
    replicas: 3
    requests:
      storage: "2Gi"
    config: |
      [dashboard]
        internal-proxy = true
      [replication]
        location-labels = ["topology.kubernetes.io/zone", "kubernetes.io/hostname"]
        max-replicas = 3
    nodeSelector:
      pd: "true"                               # `pd=true`タグがついたnodeにデプロイされます。
    affinity:
      podAntiAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
            - key: app.kubernetes.io/component
              operator: In
              values:
              - pd
          topologyKey: kubernetes.io/hostname  # レプリカが同一node内にデプロイされないようにしています。
  tikv:
    baseImage: pingcap/tikv
    maxFailoverCount: 0
    replicas: 3
    requests:
      storage: "10Gi"
    annotations:
      tidb.pingcap.com/sysctl-init: "true"
    podSecurityContext:
      sysctls:
      - name: net.core.somaxconn
        value: "32768"
    config: {}
    nodeSelector:
      tikv: "true"                             # `tikv=true`タグがついたnodeにデプロイされます。
    affinity:
      podAntiAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
            - key: app.kubernetes.io/component
              operator: In
              values:
              - tikv
          topologyKey: kubernetes.io/hostname  # レプリカが同一node内にデプロイされないようにしています。
  tidb:
    baseImage: pingcap/tidb
    maxFailoverCount: 0
    replicas: 2
    service:
      type: NodePort
    config: |
      [performance]
        tcp-keep-alive = true
    annotations:
      tidb.pingcap.com/sysctl-init: "true"
    podSecurityContext:
      sysctls:
      - name: net.ipv4.tcp_keepalive_time
        value: "300"
      - name: net.ipv4.tcp_keepalive_intvl
        value: "75"
      - name: net.core.somaxconn
        value: "32768"
    separateSlowLog: true
    nodeSelector:
      tidb: "true"                             # `tidb=true`タグがついたnodeにデプロイされます。
    affinity:
      podAntiAffinity:
        requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchExpressions:
            - key: app.kubernetes.io/component
              operator: In
              values:
              - tidb
          topologyKey: kubernetes.io/hostname  # レプリカが同一node内にデプロイされないようにしています。
EOF

# デプロイ
% kubectl apply -f tidb-cluster.yaml

# 確認(pod)
% kubectl get pods -n tidb-cluster           
NAME                              READY   STATUS    RESTARTS   AGE
tidb-discovery-xxxxxxxxxx-xxxxx   1/1     Running   0          5m18s
tidb-pd-0                         1/1     Running   0          5m18s
tidb-pd-1                         1/1     Running   0          2m44s
tidb-pd-2                         1/1     Running   0          2m44s
tidb-tidb-0                       2/2     Running   0          48s
tidb-tidb-1                       2/2     Running   0          47s
tidb-tikv-0                       1/1     Running   0          108s
tidb-tikv-1                       1/1     Running   0          108s
tidb-tikv-2                       1/1     Running   0          108s

# 確認(service)
% kubectl get svc -n tidb-cluster
NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                          AGE
tidb-discovery   ClusterIP   XXX.XX.XXX.XXX   <none>        10261/TCP,10262/TCP              12m
tidb-pd          ClusterIP   XXX.XX.XXX.XX    <none>        2379/TCP                         12m
tidb-pd-peer     ClusterIP   None             <none>        2380/TCP,2379/TCP                12m
tidb-tidb        NodePort    XXX.XX.XXX.XX    <none>        4000:30225/TCP,10080:30395/TCP   7m54s
tidb-tidb-peer   ClusterIP   None             <none>        10080/TCP                        7m54s
tidb-tikv-peer   ClusterIP   None             <none>        20160/TCP                        8m54s

動作確認

最後に、kubectl port-forwardを使ってローカルのmysqlクライアントを使ってアクセスできるか確認しました。

# tidb serviceにport-forwardします。
% kubectl port-forward svc/tidb-tidb 4000:4000 -n tidb-cluster

# 別プロセスのターミナルを開いて、アクセスできることを確認しました。
$ mysql -h 127.0.0.1 -P 4000 -u root
mysql> /* you can run any query now! */
mysql> 

以上になります。