🥷

Multi Kubernetes Cluster間でレプリケーションするOperator作った

2023/04/21に公開

はじめに

普段からOperatorの実装や、コードリーディングが好きで四六時中考えたりしています。
そこで、最近気になっていたMulti Kubernetes Cluster間でレプリケーションするOperatorを実装しようと思い立ち、今回実装に踏み切りました。
なお、現在一応レプリケーションできるようになっていますが、まだ完成ではなく最低限の実装が完了した状態です。最後にロードマップも記載しますので、お付き合いください。

コードの場所

https://github.com/jnytnai0613/resource-replicator

動作例

上記githubリポジトリのREADMEに動画を貼っています。そちらをご確認ください。

機能紹介

  • 以下の作業を自動化します。
  1. PrimaryクラスタとSecondaryクラスタの検出
    • clusterdetectorリソースがOperatorにより自動作成され、クラスタの検出を行います。
  2. 以下のkuberndtes Resourceを作成する。
    • ConfigMap
    • Deployment
    • Service
    • Ingress
      • IngressでSSLを有効にすると、以下のリソースが自動的に作成されます。
        • Secret1: IngressのSSL終端に必要なCA証明書、サーバー証明書、サーバー証明書の秘密鍵が含まれる
        • Secret2: Ingressへのアクセスに必要なクライアント証明書と秘密鍵が含まれる
  3. レプリケーション用namespace自動作成/削除

レプリケーション

PrimaryクラスタとSecondaryクラスタの検出

Operatorデプロイと同時に以下のようにclusterdetectorリソースが自動作成されます。
この時、いずれかのSecondaryクラスタと通信ができない場合、Status列がUnknownとなり、REASON列に理由が出力されます。clusterdetectorリソースを用い、レプリケーションを行います。

kubectl -n resource-replicator-system get clusterdetectors.replicate.jnytnai0613.github.io -owide --show-labels
NAME                              CONTEXT      CLUSTER         USER                CLUSTERSTATUS   REASON   AGE   LABELS
v1252-cluster.kubernetes-admin3   secondary2   v1252-cluster   kubernetes-admin3   Running                  37h   app.kubernetes.io/role=secondary
v1262-cluster.kubernetes-admin2   secondary1   v1262-cluster   kubernetes-admin2   Running                  37h   app.kubernetes.io/role=secondary
v1270-cluster.kubernetes-admin1   primary      v1270-cluster   kubernetes-admin1   Running                  37h   app.kubernetes.io/role=primary

replicatorリソースの作成

以下のyamlファイルをapplyし、replicatorリソースを作成することで、レプリケーションを行うことが可能です。また、レプリケーション対象のnamespaceはreplicationNamespaceフィールドに、レプリケーション先のSecondaryクラスタはtargetClusterフィールドに記載します。
なお、今回はレプリケーションにServer-Side Applyを採用しています。Server-Side Applyを行うに当たって、以下のreplicatorリソースの定義にApplyconfigurationを埋め込み、通常の、Deploymentなどの定義を行う際と同様の書き方で定義できるようにしています。
また、replicatorリソースは様々なnamespaceを管理する関係上Clusterワイドなリソースとしています。

apiVersion: replicate.jnytnai0613.github.io/v1
kind: Replicator
metadata:
  labels:
    app.kubernetes.io/name: replicator
    app.kubernetes.io/instance: replicator-sample
    app.kubernetes.io/part-of: resource-replicator
    app.kubernetes.io/managed-by: kustomize
    app.kubernetes.io/created-by: resource-replicator
  name: replicator-sample
spec:
  targetCluster:
    - v1252-cluster.kubernetes-admin3
    - v1262-cluster.kubernetes-admin2
  replicationNamespace: "test-ns"
  deploymentName: nginx
  deploymentSpec:
    replicas: 3
    strategy:
      type: RollingUpdate
      rollingUpdate:
        maxSurge: 30%
        maxUnavailable: 30%
    template:
      spec:
        initContainers:
          - name: init
            image: alpine
            command: 
              - sh
              - -c
              - |
                cat << EOT > /tmp/run-nginx.sh
                apt-get update
                apt-get install inotify-tools -y
                nginx
                EOT
                chmod 500 /tmp/run-nginx.sh
                cat << EOT > /tmp/auto-reload-nginx.sh
                oldcksum=\`cksum /etc/nginx/conf.d/default.conf\`
                inotifywait -e modify,move,create,delete -mr --timefmt '%Y/%m/%d %H:%M:%S' --format '%T' /etc/nginx/conf.d/ | \
                while read date time; do
                    newcksum=\`cksum /etc/nginx/conf.d/default.conf\`
                    if [ ${newcksum} != ${oldcksum} ]; then
                        echo "At \${time} on \${date}, config file update detected."
                        oldcksum=\${newcksum}
                        service nginx restart
                    fi
                done
                EOT
                chmod 500 /tmp/auto-reload-nginx.sh
            volumeMounts:
            - name: nginx-reload
              mountPath: "/tmp/"
        containers:
          - name: nginx
            image: nginx:latest
            command:
              - bash
              - -c
              - "/tmp/run-nginx.sh && /tmp/auto-reload-nginx.sh"
            volumeMounts:
            - name: conf
              mountPath: "/etc/nginx/conf.d/"
            - name: index
              mountPath: "/usr/share/nginx/html/"
            - name: nginx-reload
              mountPath: "/tmp/"
        volumes:
        - name: conf
          configMap:
            name: "nginx"
            items:
              - key: "default.conf"
                path: "default.conf"
        - name: index
          configMap:
            name: "nginx"
            items:
              - key: "mod-index.html"
                path: "mod-index.html"
        - name: nginx-reload
          emptyDir: {}
  configMapName: nginx
  configMapData:
    default.conf: |
      server {
            listen 80 default_server;
            listen [::]:80 default_server ipv6only=on;
            root /usr/share/nginx/html;
            index index.html index.htm mod-index.html;
          server_name localhost;
      }
    mod-index.html: |
      <!DOCTYPE html>
      <html>
      <head>
      <title>Yeahhhhhhh!! Welcome to nginx!!</title>
      <style>
      html { color-scheme: light dark; }
      body { width: 35em; margin: 0 auto;
      font-family: Tahoma, Verdana, Arial, sans-serif; }
      </style>
      </head>
      <body>
      <h1>Yeahhhhhhh!! Welcome to nginx!!</h1>
      <p>If you see this page, the nginx web server is successfully installed and
      working. Further configuration is required.</p>
      <p>For online documentation and support please refer to
      <a href="http://nginx.org/">nginx.org</a>.<br/>
      Commercial support is available at
      <a href="http://nginx.com/">nginx.com</a>.</p>
      <p><em>Thank you for using nginx.</em></p>
      </body>
      </html>
  serviceName: nginx
  ##############################################################################
  ## Selector is automatically assigned by the controller and is not required.
  ##############################################################################
  serviceSpec:
    type: ClusterIP
    ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  
  ingressName: nginx
  ingressSpec:
    rules:
    - host: nginx.example.com
      http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: nginx
              port:
                number: 80
  ingressSecureEnabled: true

上記yamlをデプロイすると、以下のようにPrimaryクラスタとSecondaryクラスタにリソースが作成され、レプリケーション完了とします。また、Primaryクラスタにリソース作成完了してから、Secondaryクラスタにレプリケーションされます。

$ kubectl -n test-ns get all,cm,secret,ing
NAME                        READY   STATUS    RESTARTS   AGE
pod/nginx-b76b5ccdf-57fzc   1/1     Running   0          11h
pod/nginx-b76b5ccdf-g57d6   1/1     Running   0          11h
pod/nginx-b76b5ccdf-jj5j5   1/1     Running   0          11h

NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/nginx   ClusterIP   10.108.190.77   <none>        80/TCP    11h

NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx   3/3     3            3           11h

NAME                              DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-b76b5ccdf   3         3         3       11h

NAME                         DATA   AGE
configmap/kube-root-ca.crt   1      11h
configmap/nginx              2      11h

NAME                TYPE     DATA   AGE
secret/ca-secret    Opaque   3      11h
secret/cli-secret   Opaque   2      11h

NAME                              CLASS   HOSTS               ADDRESS       PORTS     AGE
ingress.networking.k8s.io/nginx   nginx   nginx.example.com   192.168.9.1   80, 443   11h

今後のロードマップ

  • CLIでコンテキストの読み取り/削除、レプリケーション先リソースの追加を行う。
  • 「replicatorリソースの作成」で説明した以外のリソースのレプリケーションを許可する。その場合、ユーザーはCLIで任意のリソースを追加することができる。
  • 常時レプリケーションによるアウトプレイスアップグレードツールとして昇華させる。
  • ベストエフォート: OpenTelemetryによる分散トレーシングをサポートする。

さいごに

前述した通り、まだまだ実装は終わりではありません。
最終的には、アウトプレイスアップグレードツールとしての昇華が目標ですので、ロードマップを消化しつつ、
楽しんで実装していきたいと思います。

Discussion