19
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

KubernetesのStatefulSetを疑ってみたが濡れ衣だった

Last updated at Posted at 2018-12-03

記事を読む前に

  • StatefulSetにデータ破損につながる仕様上の問題がないか疑って検証しました
  • タイトルにある通り、それ自体は濡れ衣でした
  • StatefulSetの挙動についてまとめに記載してあります。そこだけ読んでもOKです
    • ここからまとめの間は延々と疑った内容と検証の内容です

前置き

  • StatefulSetなるものが目につきました。こいつを使うと冗長構成のDBがKubernetes上に組めるそうです。
  • なので調査してみました

まずは調査

オートヒーリングの何が怪しいか

  • 障害時、Podが起動状態のまま、同一Podをもう一台起動することがあります。(以降これを多重起動問題と呼びます)
    • ここにReplicaSetでのNode障害時の挙動が解説されてます
      • STATUSがUnknownになるとPod立て直しが起きます
    • kubeletプロセスだけ落ちたりした場合、Pod(Docker上のコンテナ)は動いているにも関わらず、PodのSTATUSがUnknownになりPodの立て直しが起きてしまいます
      • kubelet障害についてはここのおまけに書きました
      • DockerのContainerdがハングしてコンテナの状態が取得できないという障害も起きたことがあります
    • DBで多重起動問題が起きると、 同一ボリュームをマウントするDBが2台立つことになります
      • やばそう:thinking:

懸念している多重起動問題を図で解説するとこんな感じです

  1. 通常時は master は kubeletから状態報告を受け取っています
    image.png

  2. kubelt障害等で報告が途絶えると、masterはPodの状態を判定できません。実はPodが生きていたとしても知るすべはありません
    image.png

  3. STATUS Unknown判定されると別NodeにPodを立て直します
    image.png

  4. 同一ボリュームをマウントするPodが2台できあがります。アプリによってはこれでデータ破損しかねません
    image.png

何を確かめればオートヒーリングを信用できるか

  • 3.の多重起動問題は実はStatefulSetでは起きない

検証してみる

環境構築

root@master:~# kubectl get nodes
NAME STATUS ROLES AGE VERSION
master Ready master 26h v1.12.3
node1 Ready 25h v1.12.3
node2 Ready 25h v1.12.3
node3 Ready 25h v1.12.3
node4 Ready 25h v1.12.3
node5 Ready 24h v1.12.3
```

/opt/nfs1 10.0.0.0/24(rw,sync,no_subtree_check,no_root_squash)
/opt/nfs2 10.0.0.0/24(rw,sync,no_subtree_check,no_root_squash)
/opt/nfs3 10.0.0.0/24(rw,sync,no_subtree_check,no_root_squash)
```

StatefulSet作成

  1. PV作成

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: pv1
    spec:
      capacity:
        storage: 11Gi
      volumeMode: Filesystem
      accessModes:
        - ReadWriteOnce
      persistentVolumeReclaimPolicy: Recycle
      storageClassName: slow
      mountOptions:
        - hard
      nfs:
        server: storage
        path: /opt/nfs1
    
    • pv2(path: /opt/nfs2), pv3(path: /opt/nfs3)も同様に作成します
  2. kubectl create -f https://k8s.io/examples/application/mysql/mysql-configmap.yaml

  3. kubectl create -f https://k8s.io/examples/application/mysql/mysql-services.yaml

  4. kubectl create -f https://k8s.io/examples/application/mysql/mysql-statefulset.yaml

  5. kubectl get pods -l app=mysql --watchで作成待機

    • mysql-0のStateがInit:0/2Init:1/2Running となった後にmysql-1が作成され始めます
      • mysql-2も同様にmysql-1Runningになってから作られます
    • すべて完了するのに10分ぐらいかかります
  6. 動作確認

    • mysqlコンテナを立ててmysql-0.mysqlに対してSQL実行します

    • データを流す

      kubectl run mysql-client --image=mysql:5.7 -i --rm --restart=Never --\
        mysql -h mysql-0.mysql <<EOF
      CREATE DATABASE test;
      CREATE TABLE test.messages (message VARCHAR(250));
      INSERT INTO test.messages VALUES ('hello');
      EOF
      
    • 流したデータを読み取る

      root@master:~# kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never --\
        mysql -h mysql-read -e "SELECT * FROM test.messages"
      If you don't see a command prompt, try pressing enter.
      +---------+
      | message |
      +---------+
      | hello   |
      +---------+
      pod "mysql-client" deleted
      
      • sqlで流した 'hello'が確かに読み取れてます

多重起動させてみる

  1. Podの配置を確認

root@master:~# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
mysql-0 2/2 Running 0 121m 10.44.0.1 node4
mysql-1 2/2 Running 0 119m 10.42.0.1 node3
mysql-2 2/2 Running 0 117m 10.35.0.1 node5
```

  1. mysql-0のいるnode4でkubeletを落とす

    root@node4:~# systemctl stop kubelet
    root@node4:~# systemctl status kubelet
    ● kubelet.service - kubelet: The Kubernetes Node Agent
       Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; vendor preset:
      Drop-In: /etc/systemd/system/kubelet.service.d
               └─10-kubeadm.conf
       Active: inactive (dead) since Mon 2018-12-03 05:04:57 UTC; 5s ago 
    ・・・
    
  2. 待つ

    • デフォルトだとだいたい5分程でSTATUS Unknownになります
  3. 待った結果

root@master:~# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
mysql-0 2/2 Unknown 0 142m 10.44.0.1 node4
mysql-1 2/2 Running 0 140m 10.42.0.1 node3
mysql-2 2/2 Running 0 138m 10.35.0.1 node5
```
* STATUS UnknownのままPodが立つ気配はありません
* どうやら濡れ衣だったようです:sweat_smile:

仕様を確認してみる

  • Issueがありました
    • StatefulSetではPodがSTATUS UnknownになってもPodを立て直さないよ
    • kubectl delete pods <pod> --grace-period=0 --forceでUnknownのPodを強制的に消すとPodを立て直すよ
    • これはStatefulSetの仕様なので、自動で立て直ししてほしい場合はDeploymentとか別のやつを使うといいよ

まとめ

  • 濡れ衣でした!StatefulSet、疑ってすみません:bow_tone1:
  • StatefulSetはNode障害等が起きても、管理者がコマンドを打つまではPodの立て直しはしません
    • 言い換えると、Node障害からの復旧は自動化されていません
    • その代わり多重起動でデータが破損したりすることは防がれています
    • 復旧が自動化されていない点を考えると、StatefulSetは高可用性のためというよりは、障害復旧手順の簡易化と負荷分散がメリットのように思えます
      • mysqlの例だと、読み取りをmysql-1,mysql-2に分散できることです
      • スレーブをマスターに昇格する仕組みを作れば高可用性(ダウンタイム短縮)もいけるかもしれませんが、k8sサービスの切り替えとかいろいろ大変そうです
  • Storageを使う場合で多重起動が起きると困る場合は、StatefulSetを使いましょう
  • 自動でPod立て直ししてほしい場合はDeployment等StatefulSet以外を検討しましょう
  • さすがGoogle。ちゃんと考えられてます
  • (見切り発車せずに先に仕様確認しとけばよかった)
19
9
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?