今回は「Kubernetes 上 に MongoDB の レプリカセット を 構築する 方法」についてまとめます。
概要
MongoDB 3台構成 の レプリカセット を構築します。
また、レプリカセット構築にあたっては「キーファイル(--keyFile
)」「認証(--auth
)」「実行ユーザー変更(runAsUser
)」も含めた内容になっています。
最後の StatefulSet がいろいろな情報を含んでしまって濁ってしまっていますが…3種類を含んでいることを踏まえて読んでいただければ理解しやすいと思います。
前提
今回の検証は以下のような環境で実施しています。
- Kubernetes v1.14.2
- Docker v18.09.6
PersistentVolume
今回は3台構成なのでデータ保存先も3か所用意します。 環境の都合で保存先は同じ NFS になっています。
-
マニュフェスト作成
mongo-pv.yml
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849apiVersion: 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
-
適用
1kubectl apply -f mongo-pv.yml
-
確認
1kubectl get pv
Service
Podネットワーク中でサービス名を使った検索をするわけではないので、ClusterIP は不要になります。 必要なのはPodの名前からIPアドレスを引けるようにするため、ヘッドレスサービス を作成します。
-
マニュフェスト作成
mongo-svc.yml
12345678910111213apiVersion: 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 を指定することで実現できます。
-
適用
1kubectl apply -f mongo-svc.yml
-
確認
1kubectl get svc
Secret
「ユーザー名、パスワード、キーファイル」を準備します。
-
キーファイル作成
1openssl rand -base64 768 > keyfile
MongoDBのキーファイルは「6~1024文字」なのですが…1024文字で生成すると少しデータが膨らむようでうまく動きませんでした。 そのため文字数は少な目で生成します。
-
Secretリソース作成
1234kubectl create secret generic mongo-secret \
--from-literal=root_username=admin \
--from-literal=root_password=Passw0rd \
--from-
file
=.
/keyfile
-
確認
1kubectl get secrets
StatefulSet
とても残念なことに MongoDB の公式イメージを素のまま使おうとすると認証回りの初期化処理がうまく動きませんでした。。 仕方ないので、公式イメージのコマンドに対してシェルスクリプトを挟むことで回避するようにしています。 ただ…結果としてマニュフェストが大きくなってしまってます。。 実運用する場合はきちんとDockerイメージ作って運用したほうがよさそうです。
-
マニュフェスト作成
mongo-sts.yml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166apiVersion: 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進数 でパーミッションを指定し、fsGroup
にmongodb (UID=999)
を指定します。 本来であればこの設定(mode
とfsGroup
)のみでうまく動いてほしいのですが… Secrets と ConfigMap はマウントする際、強制的に「root 所有の 読み取り専用、フルアクセス」になってしまいます。 そのため、initContainers
とemptyDir
の組み合わせ技で中間処理を挟んで適切な所有者とアクセス権になるよう修正します。L.161, 162, 163, 166
volumeClaimTemplates
は作成した PersistentVolume にあわせて指定します。 一致しなければいけないのはstorageClassName
、accessModes
。storage
は PersistentVolume より小さい値であればOKです。 -
適用
1kubectl apply -f mongo-sts.yml.yml
-
確認
1kubectl get sts,po
初期設定
前述までの実装ができていれば MongoDB サーバー が 3台 立ち上がっているはずです。 まだ単独で動作しているだけの状態なので、レプリカセットとしての初期化を行っていきます。
-
いずれかのPodに入る
1[root@k8s-master ~]# kubectl exec -it mongo-sts-0 -- sh
-
MongoDBに接続
1# mongo mongo-sts-0.mongo-svc
-
adminデータベースにログイン
12> use admin
> db.auth(
"admin"
,
"Passw0rd"
)
-
レプリカセットを初期化
123456789> 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 クラスタに接続して以下のようなコマンドを実行することで状態確認してみます。
1 | > rs.status() |
今回は「Kubernetes 上 に MongoDB の レプリカセット を 構築する 方法」についてまとめました。 参考になったでしょうか? 本記事がお役に立っていると嬉しいです!!
最後に… このブログに興味を持っていただけた方は、 ぜひ 「Facebookページ に いいね!」または 「Twitter の フォロー」 お願いします!!