Kubernetes 上に MongoDB レプリカセット を 構築する 方法

0 件のコメント

今回は「Kubernetes 上 に MongoDB の レプリカセット を 構築する 方法」についてまとめます。

概要

MongoDB 3台構成 の レプリカセット を構築します。 また、レプリカセット構築にあたっては「キーファイル(--keyFile)」「認証(--auth)」「実行ユーザー変更(runAsUser)」も含めた内容になっています。 最後の StatefulSet がいろいろな情報を含んでしまって濁ってしまっていますが…3種類を含んでいることを踏まえて読んでいただければ理解しやすいと思います。

前提

今回の検証は以下のような環境で実施しています。

  • Kubernetes v1.14.2
  • Docker v18.09.6

PersistentVolume

今回は3台構成なのでデータ保存先も3か所用意します。 環境の都合で保存先は同じ NFS になっています。

  1. マニュフェスト作成

    mongo-pv.yml

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: pv0001
      labels:
        type: nfs
    spec:
      capacity:
        storage: 3Gi
      storageClassName: slow
      accessModes: ["ReadWriteMany"]
      persistentVolumeReclaimPolicy: Retain
      nfs:
        server: 10.51.1.103
        path: /var/share/pv0001
    
    ---
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: pv0002
      labels:
        type: nfs
    spec:
      capacity:
        storage: 3Gi
      storageClassName: slow
      accessModes: ["ReadWriteMany"]
      persistentVolumeReclaimPolicy: Retain
      nfs:
        server: 10.51.1.103
        path: /var/share/pv0002
    
    ---
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: pv0003
      labels:
        type: nfs
    spec:
      capacity:
        storage: 3Gi
      storageClassName: slow
      accessModes: ["ReadWriteMany"]
      persistentVolumeReclaimPolicy: Retain
      nfs:
        server: 10.51.1.103
        path: /var/share/pv0003
    
  2. 適用

    kubectl apply -f mongo-pv.yml
    
  3. 確認

    kubectl get pv
    

Service

Podネットワーク中でサービス名を使った検索をするわけではないので、ClusterIP は不要になります。 必要なのはPodの名前からIPアドレスを引けるようにするため、ヘッドレスサービス を作成します。

  1. マニュフェスト作成

    mongo-svc.yml

    apiVersion: v1
    kind: Service
    metadata:
      name: mongo-svc
      labels:
        app: mongodb
    spec:
      ports:
        - port: 27017
          targetPort: 27017
      clusterIP: None               # Headless Service
      selector:
        app: mongodb                # match "StatefulSet spec.template.metadata.labels"
    

    HeadlessService は clusterIP に None を指定することで実現できます。

  2. 適用

    kubectl apply -f mongo-svc.yml
    
  3. 確認

    kubectl get svc
    

Secret

「ユーザー名、パスワード、キーファイル」を準備します。

  1. キーファイル作成

    openssl rand -base64 768 > keyfile
    

    MongoDBのキーファイルは「6~1024文字」なのですが…1024文字で生成すると少しデータが膨らむようでうまく動きませんでした。 そのため文字数は少な目で生成します。

  2. Secretリソース作成

    kubectl create secret generic mongo-secret \
      --from-literal=root_username=admin \
      --from-literal=root_password=Passw0rd \
      --from-file=./keyfile
    
  3. 確認

    kubectl get secrets
    

StatefulSet

とても残念なことに MongoDB の公式イメージを素のまま使おうとすると認証回りの初期化処理がうまく動きませんでした。。 仕方ないので、公式イメージのコマンドに対してシェルスクリプトを挟むことで回避するようにしています。 ただ…結果としてマニュフェストが大きくなってしまってます。。 実運用する場合はきちんとDockerイメージ作って運用したほうがよさそうです。

  1. マニュフェスト作成

    mongo-sts.yml

    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: mongo-sts
      namespace: default
      labels:
        app: mongodb
    spec:
      selector:
        matchLabels:
          app: mongodb
      serviceName: mongo-svc        # match "Service metadata.name"
      replicas: 3
      template:
        metadata:
          name: mongodb
          namespace: default
          labels:
            app: mongodb
            ver: "0.0.1"
            env: staging
        spec:
          terminationGracePeriodSeconds: 10
          containers:
            - name: mongodb
              image: mongo:4.0
              ports:
                - containerPort: 27017
              command:
                - "/bin/sh"
                - "-c"
              args:
                - |
                  INIT_FLAG_FILE=/data/db/init-completed
                  INIT_LOG_FILE=/data/db/init-mongodb.log
    
                  start_mongod_as_daemon() {
                  echo 
                  echo "> start mongod ..."
                  echo 
                  mongod \
                    --fork \
                    --logpath ${INIT_LOG_FILE} \
                    --quiet \
                    --bind_ip 127.0.0.1 \
                    --smallfiles;
                  }
    
                  create_user() {
                  echo 
                  echo "> create user ..."
                  echo 
                  mongo "${MONGO_INITDB_DATABASE}" <<-EOS
                    db.createUser({
                      user: "${MONGO_INITDB_ROOT_USERNAME}",
                      pwd: "${MONGO_INITDB_ROOT_PASSWORD}",
                      roles: [{ role: "root", db: "${MONGO_INITDB_DATABASE}" }]
                    })
                  EOS
                  }
    
                  create_initialized_flag() {
                  echo 
                  echo "> create initialized flag file ..."
                  echo 
                  cat <<-EOF > "${INIT_FLAG_FILE}"
                  [$(date +%Y-%m-%dT%H:%M:%S.%3N)] Initialize scripts is finished.
                  EOF
                  }
    
                  stop_mongod() {
                  echo 
                  echo "> stop mongod ..."
                  echo 
                  mongod --shutdown;
                  }
    
                  
                  start_mongod() {
                  mongod \
                    --auth \
                    --clusterAuthMode "keyFile" \
                    --keyFile "/home/mongodb/keyfile" \
                    --replSet "rs0" \
                    --bind_ip_all \
                    --smallfiles;
                  }
                  
                  
                  if [ ! -e ${INIT_FLAG_FILE} ]; then
                  echo 
                  echo "--- Initialize MongoDB --------------"
                  echo 
                  start_mongod_as_daemon
                  create_user
                  create_initialized_flag
                  fi
    
                  echo
                  echo "--- Start MongoDB -------------------"
                  echo
                  start_mongod
    
              env:
                - name: "MONGO_INITDB_ROOT_USERNAME"
                  valueFrom:
                    secretKeyRef:
                      name: mongo-secret
                      key: root_username
                - name: "MONGO_INITDB_ROOT_PASSWORD"
                  valueFrom:
                    secretKeyRef:
                      name: mongo-secret
                      key: root_password
                - name: "MONGO_INITDB_DATABASE"
                  value: "admin"
              volumeMounts:
                - name: mongo-secret-keyfile
                  mountPath: /etc/mongo/secrets
                  readOnly: true
                - name: volatile-volume
                  mountPath: /home/mongodb
                - name: nfs-storage
                  mountPath: /data/db
              securityContext:
                runAsUser: 999
                runAsGroup: 999
          initContainers:
            - image: centos:7
              name: mongo-init
              volumeMounts:
                - name: mongo-secret-keyfile
                  mountPath: /etc/mongo/secret
                - name: volatile-volume
                  mountPath: /home/mongodb
              command:
                - "/bin/sh"
                - "-c"
              args:
                - |
                  cp /etc/mongo/secret/keyfile /home/mongodb/keyfile
                  chown 999:999 /home/mongodb/keyfile
                  chmod 700 /home/mongodb/keyfile
                  exit
          securityContext:
            fsGroup: 999
          volumes:
            - name: mongo-secret-keyfile
              secret:
                secretName: mongo-secret
                items:
                  - key: keyfile
                    path: keyfile
                    mode: 384
            - name: volatile-volume
              emptyDir: {}
      volumeClaimTemplates:
        - metadata:
            name: nfs-storage
          spec:
            storageClassName: slow
            accessModes:
              - ReadWriteMany
            resources:
              requests:
                storage: 3Gi
    

    L.12
    serviceName は 作成済みの HeadlessService 名 と同じ名前を指定してあげます。 ここでサービス名が異なっているとサービス利用してPod名を解決できません(=DNSにレコードが登録されない)。

    L.34-102
    DBの初期化で利用するユーザー名、パスワードは 作成済の Secretリソース から取得します。 こうすることで、機微な情報をリポジトリにコミットせずにすみます。 MongoDB公式の初期化処理がいまいちなので…結局このあたりの処理は自前実装しています。

    L.128, L.146, L.154
    keyfile の パーミッションは実行ユーザーのみに限定しないと MongoDB が起動しません。 これを実現するためには、 mode に 10進数 か 8進数 でパーミッションを指定し、fsGroupmongodb (UID=999) を指定します。 本来であればこの設定(modefsGroup)のみでうまく動いてほしいのですが… Secrets と ConfigMap はマウントする際、強制的に「root 所有の 読み取り専用、フルアクセス」になってしまいます。 そのため、 initContainersemptyDir の組み合わせ技で中間処理を挟んで適切な所有者とアクセス権になるよう修正します。

    L.161, 162, 163, 166
    volumeClaimTemplates は作成した PersistentVolume にあわせて指定します。 一致しなければいけないのは storageClassNameaccessModesstorage は PersistentVolume より小さい値であればOKです。

  2. 適用

    kubectl apply -f mongo-sts.yml.yml
    
  3. 確認

    kubectl get sts,po
    

初期設定

前述までの実装ができていれば MongoDB サーバー が 3台 立ち上がっているはずです。 まだ単独で動作しているだけの状態なので、レプリカセットとしての初期化を行っていきます。

  1. いずれかのPodに入る

    [root@k8s-master ~]# kubectl exec -it mongo-sts-0 -- sh
    
  2. MongoDBに接続

    # mongo mongo-sts-0.mongo-svc
    
  3. adminデータベースにログイン

    > use admin
    > db.auth("admin", "Passw0rd")
    
  4. レプリカセットを初期化

    > config = {
       _id : "rs0",
       members: [
          { _id: 0, host: "mongo-sts-0.mongo-svc:27017" },
          { _id: 1, host: "mongo-sts-1.mongo-svc:27017" },
          { _id: 2, host: "mongo-sts-2.mongo-svc:27017" },
       ]
    }
    > rs.initiate(config)
    

動作確認

動作確認は MongoDB クラスタに接続して以下のようなコマンドを実行することで状態確認してみます。

> rs.status()

今回は「Kubernetes 上 に MongoDB の レプリカセット を 構築する 方法」についてまとめました。 参考になったでしょうか? 本記事がお役に立っていると嬉しいです!!

最後に… このブログに興味を持っていただけた方は、 ぜひ 「Facebookページ に いいね!」または 「Twitter の フォロー」 お願いします!!