Rook CephFSでの障害ドメインのカスタマイズ

はじめに

以前下記の記事でCephFS/Rookを実装しました.

tenzen.hatenablog.com

その際障害ドメインをホスト単位で設定していたのですが,Cpehには元々様々な種類の障害ドメインが用意されています.そのうち今回は3つのzoneを設定することで,図1のような3重冗長構成を作っていきたいと思います.

current_constitution
図1 : 今回の構成

また今回は下記のような配分でノード毎にストレージを配置しました. (しれっとMaster冗長構成になってますが,後日これについては記事にします.)

ノード名 役割 SSD容量 玉数 zone名
k8s-m0 control plane node 3TB 4 ars
k8s-m1 control plane node 3TB 4 ars
michiru worker node 6TB 8 yorumi
eriri worker node 3TB 4 sasaki
utaha worker node 3TB 4 sasaki

障害ドメインの種類

下記のCephドキュメントに記載されている通り12種類用意されています.

docs.ceph.com

  • osd (or device)
  • host
  • chassis
  • rack
  • row
  • pdu
  • pod
  • room
  • datacenter
  • zone
  • region
  • root

RookにおけるCephではこれらに対応するものとして以下のような9種類の障害ドメインが用意されています.

  • topology.kubernetes.io/region
  • topology.kubernetes.io/zone
  • topology.rook.io/datacenter
  • topology.rook.io/room
  • topology.rook.io/pod
  • topology.rook.io/pdu
  • topology.rook.io/row
  • topology.rook.io/rack
  • topology.rook.io/chassis

github.com

下記のようにこれらをk8s nodeにラベルとして与えることで,障害ドメインの指定をすることができます.

$ kubectl label node mynode topology.kubernetes.io/zone=zone1
$ kubectl label node mynode topology.rook.io/rack=rack1

注意点としては上記9種類はk8s version 1.17以降からしか対応しておらず,v1.16以前では下記二種類のみが対応しています.

障害ドメインのカスタム設定

この節では,前回作成したRookの掃除を行った後マニフェストをの変更部分の解説をし,実際にデプロイしていきたいと思います.

掃除

既にCephFS/Rookを構築済みの場合はリセットします. まず,下記のコマンドで削除します.

$ cd rook/cluster/examples/kubernetes/ceph/
$ kubectl delete -f csi/cephfs/storageclass.yaml
$ kubectl delete -f filesystem.yaml
$ kubectl delete -f toolbox.yaml
$ kubectl delete -f dashboard-external-https.yaml
$ kubectl delete -f cluster.yaml
$ kubectl delete -f operator.yaml
$ kubectl delete -f common.yaml

次にRookのディレクトリとデバイスマッピング情報を削除するために以下のコマンドを使用した全てのデバイスで実行します.(下記はsdaの例)

$ sudo dmsetup remove_all && sudo sgdisk -Z /dev/sda && sudo rm -vR /var/lib/rook*

マニフェストの書き換え

基本的に前回作ったCephFS/Rookマニフェストのままですが,少し変更する部分があります.まだ前回の変更を行ってない人はRook-Cephfs + Nvidia Driver vs てんぜん - てんぜんの生存日誌の手順を先に行ってください.

はじめに前回Discover Agentをworker Nodeに配置するため 「rook/cluster/examples/kubernetes/ceph/operator.yaml」で指定した,nodeAffinityをコメントアウトして全ノードにDiscover Agentを配置させます.この作業は各環境に合わせて使用するディスクが配置されているNodeにDiscover Agentが配置されるように記述してください.

...(省略)
        # (Optional) Rook Discover priority class name to set on the pod(s)
        # - name: DISCOVER_PRIORITY_CLASS_NAME
        #   value: "<PriorityClassName>"
        # (Optional) Discover Agent NodeAffinity.
-       - name: DISCOVER_AGENT_NODE_AFFINITY
-         value: "hardware-type=NVIDIAGPU"
        # Allow rook to create multiple file systems. Note: This is considered
        # an experimental feature in Ceph as described at
...(省略)

次に「rook/cluster/examples/kubernetes/ceph/cluster.yaml」を以下のように編集します.下記の編集内容は前回から追加したNodeとdeviceを追加しているので,各環境に合わせて変更してください.

...(省略)
# Cluster level list of directories to use for filestore-based OSD storage. If uncomment, this example would create an OSD under the dataDirHostPath.
    #directories:
    #- path: /var/lib/rook
# Individual nodes and their config can be specified as well, but 'useAllNodes' above must be set to false. Then, only the named
# nodes below will be used as storage resources.  Each node's 'name' field should match their 'kubernetes.io/hostname' label.
    nodes:
    - name: "michiru"
      devices:
      - name: "sda"
      - name: "sdb"
      - name: "sdc"
      - name: "sdd"
      - name: "sde"
      - name: "sdf"
      - name: "sdg"
      - name: "sdh"
    - name: "eriri"
      devices:
      - name: "sda"
      - name: "sdb"
      - name: "sdc"
      - name: "sdd"
    - name: "utaha"
      devices:
      - name: "sda"
      - name: "sdb"
      - name: "sdc"
      - name: "sdd"
    - name: "k8s-m0"
      devices:
      - name: "sda"
      - name: "sdc"
      - name: "sdd"
      - name: "sde"
    - name: "k8s-m1"
      devices:
      - name: "sda"
      - name: "sdc"
      - name: "sdd"
      - name: "sde"
...(省略)

最後に「rook/cluster/examples/kubernetes/ceph/filesystem.yaml」を以下のように編集して障害ドメインをhostからzoneへ変更します.

...(省略)
#################################################################################################################
# Create a filesystem with settings with replication enabled for a production environment.
# A minimum of 3 OSDs on different nodes are required in this example.
#  kubectl create -f filesystem.yaml
#################################################################################################################

apiVersion: ceph.rook.io/v1
kind: CephFilesystem
metadata:
  name: myfs
  namespace: rook-ceph
spec:
  # The metadata pool spec. Must use replication.
  metadataPool:
    replicated:
      size: 3
  # The list of data pool specs. Can use replication or erasure coding.
  dataPools:
-   - failureDomain: host
+   - failureDomain: zone
      replicated:
        size: 3
...(省略)

デプロイ

先ほど作成したマニフェストを使ってCephFS/Rookをデプロイする前に,各nodeにzoneの名前のラベルをつけていきます.また,今回使用しているk8s clusterはv1.16.3なので前述した通り2種類の障害ノードしか対応してないため下記のようにします.

$ kubectl label node k8s-m0 failure-domain.beta.kubernetes.io/zone=ars
$ kubectl label node k8s-m1 failure-domain.beta.kubernetes.io/zone=ars
$ kubectl label node michiru failure-domain.beta.kubernetes.io/zone=yorumi
$ kubectl label node utaha failure-domain.beta.kubernetes.io/zone=sasaki
$ kubectl label node eriri failure-domain.beta.kubernetes.io/zone=sasaki

以下のコマンドを実行すると各Nodeにラベルがつけられた様子が確認できます.

$ kubectl get nodes --show-labels
NAME      STATUS   ROLES    AGE    VERSION   LABELS
eriri     Ready    <none>   4d3h   v1.16.3   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,failure-domain.beta.kubernetes.io/zone=sasaki,gpu-type=RTX2080Ti,hardware-type=NVIDIAGPU,kubernetes.io/arch=amd64,kubernetes.io/hostname=eriri,kubernetes.io/os=linux
k8s-m0    Ready    master   4d3h   v1.16.3   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,failure-domain.beta.kubernetes.io/zone=ars,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-m0,kubernetes.io/os=linux,node-role.kubernetes.io/master=
k8s-m1    Ready    master   4d3h   v1.16.3   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,failure-domain.beta.kubernetes.io/zone=ars,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-m1,kubernetes.io/os=linux,node-role.kubernetes.io/master=
michiru   Ready    <none>   4d3h   v1.16.3   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,failure-domain.beta.kubernetes.io/zone=yorumi,gpu-type=GP100,hardware-type=NVIDIAGPU,kubernetes.io/arch=amd64,kubernetes.io/hostname=michiru,kubernetes.io/os=linux
utaha     Ready    <none>   4d3h   v1.16.3   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,failure-domain.beta.kubernetes.io/zone=sasaki,gpu-type=RTX2080Ti,hardware-type=NVIDIAGPU,kubernetes.io/arch=amd64,kubernetes.io/hostname=utaha,kubernetes.io/os=linux

各nodeの準備ができたので,デプロイしていきます.

$ cd rook/cluster/examples/kubernetes/ceph/
$ kubectl apply -f common.yaml
$ kubectl apply -f operator.yaml
$ kubectl apply -f cluster.yaml
$ kubectl apply -f toolbox.yaml
$ kubectl apply -f dashboard-external-https.yaml
$ kubectl apply -f csi/cephfs/storageclass.yaml
$ kubectl apply -f filesystem.yaml

初めてデプロイする人は10分から15分経つと以下のようなPodが起動するはずです.OSDはデバイス1つにつき1つ起動するので,足りない場合は不具合が出ているので,手順を最初から見直してください.

$ kubectl get pods -n rook-ceph
NAME                                                READY   STATUS      RESTARTS   AGE
csi-cephfsplugin-f2tpr                              3/3     Running     0          3d19h
csi-cephfsplugin-k8bnk                              3/3     Running     0          3d19h
csi-cephfsplugin-mcpdg                              3/3     Running     0          3d19h
csi-cephfsplugin-provisioner-565ffd64f5-r7gx8       4/4     Running     0          3d19h
csi-cephfsplugin-provisioner-565ffd64f5-twxqw       4/4     Running     0          3d19h
csi-cephfsplugin-smrmg                              3/3     Running     0          3d19h
csi-cephfsplugin-zbdqf                              3/3     Running     0          3d19h
csi-rbdplugin-9wrbq                                 3/3     Running     0          3d19h
csi-rbdplugin-bv94s                                 3/3     Running     0          3d19h
csi-rbdplugin-kwh26                                 3/3     Running     0          3d19h
csi-rbdplugin-provisioner-7bb78d6c66-9sd9f          5/5     Running     0          3d19h
csi-rbdplugin-provisioner-7bb78d6c66-rgdt4          5/5     Running     0          3d19h
csi-rbdplugin-q9xp4                                 3/3     Running     0          3d19h
csi-rbdplugin-wgjkl                                 3/3     Running     0          3d19h
rook-ceph-crashcollector-eriri-69fd49d544-8tx6h     1/1     Running     0          3d18h
rook-ceph-crashcollector-k8s-m0-776ff955dc-t9hnx    1/1     Running     0          3d19h
rook-ceph-crashcollector-k8s-m1-6c84b65678-g9g6k    1/1     Running     0          3d19h
rook-ceph-crashcollector-michiru-69cffc7954-p9v4x   1/1     Running     0          3d18h
rook-ceph-crashcollector-utaha-674c87b758-dgcc4     1/1     Running     0          3d19h
rook-ceph-mds-myfs-a-8c4fb886d-j5d6b                1/1     Running     0          3d18h
rook-ceph-mds-myfs-b-5689b8c86c-sscbb               1/1     Running     0          3d18h
rook-ceph-mgr-a-75bf7c6b6-rbvnt                     1/1     Running     0          3d19h
rook-ceph-mon-a-74cf787c65-snvkk                    1/1     Running     0          3d19h
rook-ceph-mon-b-7657b6644b-wvb27                    1/1     Running     0          3d19h
rook-ceph-mon-c-f86b77989-gg82c                     1/1     Running     0          3d19h
rook-ceph-operator-6d74795f75-xp7w7                 1/1     Running     0          3d19h
rook-ceph-osd-0-68d8565f65-6ptjz                    1/1     Running     0          3d19h
rook-ceph-osd-1-b786547b6-n9r9d                     1/1     Running     0          3d19h
rook-ceph-osd-10-6668495c74-8f5f5                   1/1     Running     0          3d19h
rook-ceph-osd-11-7b8d98b8b5-4qt9q                   1/1     Running     0          3d19h
rook-ceph-osd-12-8d44dc47d-pw2rh                    1/1     Running     0          3d19h
rook-ceph-osd-13-d76d65586-rk8bf                    1/1     Running     0          3d19h
rook-ceph-osd-14-85776b8545-5p964                   1/1     Running     0          3d19h
rook-ceph-osd-15-754546cb69-kpb2p                   1/1     Running     0          3d19h
rook-ceph-osd-16-5c65c8d784-qhw4g                   1/1     Running     0          3d19h
rook-ceph-osd-17-7d5dfd84c7-5htv6                   1/1     Running     0          3d19h
rook-ceph-osd-18-5b4c8fdc55-xb8rw                   1/1     Running     0          3d19h
rook-ceph-osd-19-5594dfd4c8-84dxk                   1/1     Running     0          3d19h
rook-ceph-osd-2-5ff6bf78db-vg8td                    1/1     Running     0          3d19h
rook-ceph-osd-20-59c9755658-8khms                   1/1     Running     0          3d19h
rook-ceph-osd-21-5c8b597fb9-g5r4j                   1/1     Running     0          3d19h
rook-ceph-osd-22-7c9df6cd44-c4h2n                   1/1     Running     0          3d19h
rook-ceph-osd-23-5777bfb7-tblz5                     1/1     Running     0          3d19h
rook-ceph-osd-3-794b4dc876-2ntx5                    1/1     Running     0          3d19h
rook-ceph-osd-4-dfb4697bb-pvhdd                     1/1     Running     0          3d19h
rook-ceph-osd-5-689f9b9dd8-cw4s6                    1/1     Running     0          3d19h
rook-ceph-osd-6-5c956b4c77-h5b8c                    1/1     Running     0          3d19h
rook-ceph-osd-7-64ff8fd48-jsjsv                     1/1     Running     0          3d19h
rook-ceph-osd-8-6557dc8788-784rh                    1/1     Running     0          3d19h
rook-ceph-osd-9-66777dd9d9-9lshx                    1/1     Running     0          3d19h
rook-ceph-osd-prepare-eriri-cdjsg                   0/1     Completed   0          3d18h
rook-ceph-osd-prepare-k8s-m0-v6hdj                  0/1     Completed   0          3d18h
rook-ceph-osd-prepare-k8s-m1-psm85                  0/1     Completed   0          3d18h
rook-ceph-osd-prepare-michiru-f5l2m                 0/1     Completed   0          3d18h
rook-ceph-osd-prepare-utaha-wll2j                   0/1     Completed   0          3d18h
rook-ceph-tools-7f96779fb9-gq6nq                    1/1     Running     0          3d19h
rook-discover-h7hgr                                 1/1     Running     0          3d19h
rook-discover-mrvjd                                 1/1     Running     0          3d19h
rook-discover-rqjtj                                 1/1     Running     0          3d19h
rook-discover-tlzbc                                 1/1     Running     0          3d19h
rook-discover-wdl49                                 1/1     Running     0          3d19h

おまけ

この節ではcephコマンドを使ってSDSの構成を確認した後,軽いベンチマーるをやっていきたいと思います.

構成の確認

toolboxでcephコマンドを実行してみると以下のようにzoneがしっかり認識されていることがわかります.

$ kubectl -n rook-ceph exec -it $(kubectl -n rook-ceph get pod -l "app=rook-ceph-tools" -o jsonpath='{.items[0].metadata.name}') bash
[root@rook-ceph-tools-7f96779fb9-gq6nq /]# ceph status
  cluster:
    id:     072e88e6-9d39-43cb-8f23-fdca16ba58ad
    health: HEALTH_OK
 
  services:
    mon: 3 daemons, quorum a,b,c (age 3d)
    mgr: a(active, since 3d)
    mds: myfs:1 {0=myfs-b=up:active} 1 up:standby-replay
    osd: 24 osds: 24 up (since 3d), 24 in (since 3d)
 
  data:
    pools:   2 pools, 16 pgs
    objects: 15.14k objects, 56 GiB
    usage:   192 GiB used, 16 TiB / 17 TiB avail
    pgs:     16 active+clean
 
  io:
    client:   115 MiB/s rd, 170 B/s wr, 45 op/s rd, 0 op/s wr
[root@rook-ceph-tools-7f96779fb9-gq6nq /]#
[root@rook-ceph-tools-7f96779fb9-gq6nq /]# ceph osd status
+----+---------+-------+-------+--------+---------+--------+---------+-----------+
| id |   host  |  used | avail | wr ops | wr data | rd ops | rd data |   state   |
+----+---------+-------+-------+--------+---------+--------+---------+-----------+
| 0  |  eriri  | 15.0G |  449G |    0   |     0   |    0   |     0   | exists,up |
| 1  |  k8s-m1 | 1025M |  929G |    0   |     0   |    0   |     0   | exists,up |
| 2  |  k8s-m0 | 8171M |  923G |    0   |     0   |    0   |     0   | exists,up |
| 3  |  utaha  | 1030M |  463G |    0   |     0   |    1   |    90   | exists,up |
| 4  | michiru | 1025M |  463G |    0   |     0   |    0   |     0   | exists,up |
| 5  |  eriri  | 7943M |  457G |    0   |     0   |    6   |  16.0M  | exists,up |
| 6  |  utaha  | 15.2G |  915G |    0   |     0   |    0   |     0   | exists,up |
| 7  |  k8s-m1 | 8299M |  456G |    0   |     0   |    0   |  1024k  | exists,up |
| 8  |  k8s-m0 | 1025M |  463G |    0   |     0   |    0   |     0   | exists,up |
| 9  | michiru | 21.9G |  956G |    0   |     0   |    3   |  9011k  | exists,up |
| 10 |  eriri  | 8135M |  923G |    0   |     0   |    0   |     0   | exists,up |
| 11 |  utaha  | 1036M |  929G |    0   |     0   |    0   |     0   | exists,up |
| 12 |  k8s-m1 | 8231M |  922G |    0   |     0   |    0   |     0   | exists,up |
| 13 |  k8s-m0 | 8211M |  456G |    0   |     0   |    0   |     0   | exists,up |
| 14 | michiru | 14.8G |  963G |    0   |     0   |    0   |     0   | exists,up |
| 15 |  eriri  | 14.9G |  916G |    0   |     0   |    0   |   819k  | exists,up |
| 16 |  utaha  | 1029M |  463G |    0   |     0   |    0   |     0   | exists,up |
| 17 |  k8s-m1 | 8121M |  457G |    0   |     0   |    1   |  3276k  | exists,up |
| 18 |  k8s-m0 | 21.8G |  909G |    0   |     0   |    1   |  2457k  | exists,up |
| 19 | michiru | 1025M |  463G |    0   |     0   |    0   |     0   | exists,up |
| 20 | michiru | 8353M |  456G |    0   |     0   |    0   |     0   | exists,up |
| 21 | michiru | 14.9G |  963G |    0   |     0   |    2   |  3072k  | exists,up |
| 22 | michiru | 1030M |  976G |    0   |     0   |    0   |     0   | exists,up |
| 23 | michiru | 1033M |  463G |    0   |     0   |    0   |     0   | exists,up |
+----+---------+-------+-------+--------+---------+--------+---------+-----------+
[root@rook-ceph-tools-7f96779fb9-gq6nq /]#
[root@rook-ceph-tools-7f96779fb9-gq6nq /]# ceph osd tree
ID  CLASS WEIGHT   TYPE NAME            STATUS REWEIGHT PRI-AFF 
 -1       16.54297 root default                                 
-10        5.45312     zone ars                                 
-13        2.72656         host k8s-m0                          
  2   ssd  0.90919             osd.2        up  1.00000 1.00000 
  8   ssd  0.45409             osd.8        up  1.00000 1.00000 
 13   ssd  0.45409             osd.13       up  1.00000 1.00000 
 18   ssd  0.90919             osd.18       up  1.00000 1.00000 
 -9        2.72656         host k8s-m1                          
  1   ssd  0.90919             osd.1        up  1.00000 1.00000 
  7   ssd  0.45409             osd.7        up  1.00000 1.00000 
 12   ssd  0.90919             osd.12       up  1.00000 1.00000 
 17   ssd  0.45409             osd.17       up  1.00000 1.00000 
 -4        5.45312     zone sasaki                              
 -3        2.72656         host eriri                           
  0   ssd  0.45409             osd.0        up  1.00000 1.00000 
  5   ssd  0.45409             osd.5        up  1.00000 1.00000 
 10   ssd  0.90919             osd.10       up  1.00000 1.00000 
 15   ssd  0.90919             osd.15       up  1.00000 1.00000 
 -7        2.72656         host utaha                           
  3   ssd  0.45409             osd.3        up  1.00000 1.00000 
  6   ssd  0.90919             osd.6        up  1.00000 1.00000 
 11   ssd  0.90919             osd.11       up  1.00000 1.00000 
 16   ssd  0.45409             osd.16       up  1.00000 1.00000 
-16        5.63672     zone yorumi                              
-15        5.63672         host michiru                         
  4   ssd  0.45409             osd.4        up  1.00000 1.00000 
  9   ssd  0.95509             osd.9        up  1.00000 1.00000 
 14   ssd  0.95509             osd.14       up  1.00000 1.00000 
 19   ssd  0.45409             osd.19       up  1.00000 1.00000 
 20   ssd  0.45409             osd.20       up  1.00000 1.00000 
 21   ssd  0.95509             osd.21       up  1.00000 1.00000 
 22   ssd  0.95509             osd.22       up  1.00000 1.00000 
 23   ssd  0.45409             osd.23       up  1.00000 1.00000 

ベンチマーク

5台のノードを跨いだSDSになっているので,適当な感じでベンチマークをとってみます.

まずはじめに,以下のマニフェスト「bench.yaml」で適当なubuntu16.04のstatefulsetを作成します.

apiVersion: v1
kind: Service
metadata:
  name: bench-svc
spec:
  clusterIP: None
  selector:
    app: bench
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: bench
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bench
  serviceName: bench-svc
  template:
    metadata:
      labels:
        app: bench
    spec:
      containers:
      - image: ubuntu:16.04
        tty: true
        name: bench
        volumeMounts:
        - name: data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteMany"]
      resources:
        requests:
          storage: 800Gi
      storageClassName: csi-cephfs

本来はDockerfileを使って準備をするべきですが今回は簡易的に行うため,デプロイして必要なソフトをインストールします.

$ kubectl apply -f bench.yaml
$
$ kubectl exec -it bench-0 /bin/bash
root@bench-0:/# apt-get update
root@bench-0:/# apt-get install -y fio

IOPSやスループットや応答速度などSDSの評価では様々な評価基準が存在しますが,今回はQiitaのfioを使ってストレージの性能を計測してみた - Qiitaを参考にSequential Writeでスループットを測っていきます.その他の性能評価に興味がある方は記事を参考にやってみてください.

root@bench-0:/# fio -filename=/data/test -direct=1 -rw=write -bs=32m -size=2G -numjobs=16 -runtime=10 -group_reporting -name=file1
file1: (g=0): rw=write, bs=32M-32M/32M-32M/32M-32M, ioengine=sync, iodepth=1
...
fio-2.2.10
Starting 16 processes
file1: Laying out IO file(s) (1 file(s) / 2048MB)
Jobs: 9 (f=9): [W(1),_(1),W(1),_(1),W(2),_(1),E(1),W(1),_(1),W(1),_(1),W(1),_(1),W(2)] [16.4% done] [0KB/160.0MB/0KB /s] [0/5/0 iops] [eta 01m:01sJobs: 4 (f=4): [_(2),W(1),_(1),W(1),_(3),W(1),_(6),W(1)] [9.3% done] [0KB/160.0MB/0KB /s] [0/5/0 iops] [eta 02m:07s]                               
file1: (groupid=0, jobs=16): err= 0: pid=1712: Wed Mar 25 22:26:57 2020
  write: io=983040KB, bw=76460KB/s, iops=2, runt= 12857msec
    clat (msec): min=2947, max=11596, avg=6210.40, stdev=2299.15
     lat (msec): min=2952, max=11601, avg=6216.23, stdev=2299.87
    clat percentiles (msec):
     |  1.00th=[ 2933],  5.00th=[ 3621], 10.00th=[ 3621], 20.00th=[ 4113],
     | 30.00th=[ 4621], 40.00th=[ 5145], 50.00th=[ 5538], 60.00th=[ 5997],
     | 70.00th=[ 7570], 80.00th=[ 8356], 90.00th=[ 9110], 95.00th=[10683],
     | 99.00th=[11600], 99.50th=[11600], 99.90th=[11600], 99.95th=[11600],
     | 99.99th=[11600]
    bw (KB  /s): min= 2824, max=11100, per=7.81%, avg=5969.67, stdev=2089.59
    lat (msec) : >=2000=100.00%
  cpu          : usr=0.09%, sys=0.05%, ctx=375, majf=0, minf=173
  IO depths    : 1=100.0%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0%
     submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     complete  : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     issued    : total=r=0/w=30/d=0, short=r=0/w=0/d=0, drop=r=0/w=0/d=0
     latency   : target=0, window=0, percentile=100.00%, depth=1

Run status group 0 (all jobs):
  WRITE: io=983040KB, aggrb=76459KB/s, minb=76459KB/s, maxb=76459KB/s, mint=12857msec, maxt=12857msec

「WRITE: io=983040KB, aggrb=76459KB/s, minb=76459KB/s, maxb=76459KB/s, mint=12857msec, maxt=12857msec」と出ているので,Sequential Writeのスループットは76.46MB/sくらいです.Node間のネットワークが1Gbpsなので仕方がないとは言えあまりにひどい結果にネットワークの強化を決意しました. ちなみに,/home/のベンチマークも取ってみました.

root@bench-0:/# fio -filename=/home/test -direct=1 -rw=write -bs=32m -size=2G -numjobs=16 -runtime=10 -group_reporting -name=file1
file1: (g=0): rw=write, bs=32M-32M/32M-32M/32M-32M, ioengine=sync, iodepth=1
...
fio-2.2.10
Starting 16 processes
file1: Laying out IO file(s) (1 file(s) / 2048MB)
Jobs: 16 (f=16): [W(16)] [100.0% done] [0KB/2208MB/0KB /s] [0/69/0 iops] [eta 00m:00s]
file1: (groupid=0, jobs=16): err= 0: pid=1813: Wed Mar 25 22:27:38 2020
  write: io=22432MB, bw=2192.4MB/s, iops=68, runt= 10232msec
    clat (msec): min=15, max=233, avg=227.52, stdev=19.18
     lat (msec): min=19, max=235, avg=231.00, stdev=18.75
    clat percentiles (msec):
     |  1.00th=[  113],  5.00th=[  229], 10.00th=[  229], 20.00th=[  229],
     | 30.00th=[  231], 40.00th=[  231], 50.00th=[  231], 60.00th=[  231],
     | 70.00th=[  231], 80.00th=[  231], 90.00th=[  231], 95.00th=[  233],
     | 99.00th=[  233], 99.50th=[  233], 99.90th=[  233], 99.95th=[  233],
     | 99.99th=[  233]
    bw (KB  /s): min=140034, max=196215, per=6.34%, avg=142331.60, stdev=8037.97
    lat (msec) : 20=0.14%, 50=0.29%, 100=0.57%, 250=99.00%
  cpu          : usr=1.52%, sys=2.66%, ctx=1490, majf=0, minf=172
  IO depths    : 1=100.0%, 2=0.0%, 4=0.0%, 8=0.0%, 16=0.0%, 32=0.0%, >=64=0.0%
     submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     complete  : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
     issued    : total=r=0/w=701/d=0, short=r=0/w=0/d=0, drop=r=0/w=0/d=0
     latency   : target=0, window=0, percentile=100.00%, depth=1

Run status group 0 (all jobs):
  WRITE: io=22432MB, aggrb=2192.4MB/s, minb=2192.4MB/s, maxb=2192.4MB/s, mint=10232msec, maxt=10232msec

「WRITE: io=22432MB, aggrb=2192.4MB/s, minb=2192.4MB/s, maxb=2192.4MB/s, mint=10232msec, maxt=10232msec」となりました.ネットワークを経由してないので流石に速いですね.

補足事項

Rookを設定した後にNodeを追加するとデフォルトではRookの最新バージョン(Master)がインストールされるため,Nodeごとにバージョンが異なってしまいます.バージョンが異なると鍵交換ができなくなってしまうようで正常に起動することができないので注意してください.私は「rook/cluster/examples/kubernetes/ceph/operator.yaml」を下記のように書き換えてrookのバージョンを決めうちしています.

#################################################################################################################
# The deployment for the rook operator
# Contains the common settings for most Kubernetes deployments.
# For example, to create the rook-ceph cluster:
#   kubectl create -f common.yaml
#   kubectl create -f operator.yaml
#   kubectl create -f cluster.yaml
#
# Also see other operator sample files for variations of operator.yaml:
# - operator-openshift.yaml: Common settings for running in OpenShift
#################################################################################################################
# OLM: BEGIN OPERATOR DEPLOYMENT
apiVersion: apps/v1
kind: Deployment
metadata:
  name: rook-ceph-operator
  namespace: rook-ceph
  labels:
    operator: rook
    storage-backend: ceph
spec:
  selector:
    matchLabels:
      app: rook-ceph-operator
  replicas: 1
  template:
    metadata:
      labels:
        app: rook-ceph-operator
    spec:
      serviceAccountName: rook-ceph-system
      containers:
      - name: rook-ceph-operator
-       image: rook/ceph:master
+       image: rook/ceph:v1.2.2
        args: ["ceph", "operator"]

...(省略)

おわりに

前回作成したCephFS/Rookで障害ドメインを変更してより柔軟な構成を作成しました.またどこかでお会いしましょう.

kube-vipを用いたHA Kubernetes Clusterの構築

f:id:tenzen_hgst:20200609030552p:plain

はじめに

去年の記事でシングルMaster構成のKubernetes(以下K8s) Clusterを作成しました. tenzen.hatenablog.com

シングルマスター構成は細かい事を気にせず構築できて便利なのですが,実際にサービスとして稼働させる際はMasterの冗長化も行いたいところです.

そこで今回はHigh Availability(HA)構成のK8s Clusterを構築し, その上にDeepLearning環境を構築していきます. Qiitaやブログをみると,KeepAlivedとHAProxyをK8s Cluster外の別ノードで構築し,Masterの役割をロードバランシングしている例が拝見されます.しかし今回は以下のドキュメントに準拠しつつ,kube-vip(Kube-vip)を用いてMasterを冗長化していきます.

kubernetes.io

ここでK8sの役割名についてですが,公式ドキュメントに準拠し,本節以降HA構成をとったK8s Clusterにおいてetcdやapi-serverなどが載っているNodeをcontrol plane node,それ以外をnodeと呼びます.

また,以前の記事「Kubernetes(k8s) v1.16とNvidia-Docker2を用いたマルチノードDeepLearning環境の構築 - てんぜんの生存日誌」や「Rook-Cephfs + Nvidia Driver vs てんぜん - てんぜんの生存日誌」と 同じ作業部分も記述しているので,わかっているorもうやっている人が読みとばせるように一つ目の記事と同じ部分は節名に「(以前と同様#1)」,二つ目の記事と同じ部分は説明に「(以前と同様#2)」と記述しているので活用してください.

※別Nodeでetcd Clusterを構築する構成もありますが,本記事ではetcdをcontrol plane nodeに載せた構成を構築します.

Kubernetes Clusterの構築

今回構築するK8s環境の物理構成は以下の通りになります.本節以降はNode名が頻繁に出てきますので,適宜参照してください. また,今回は記事執筆時点で最新版のK8s,v1.18.3を使用します.

ホスト名 役割 CPU メモリ GPU OS
k8s-m0 control plane + node intel Core i5 9600K 64GB(Non ECC) なし Ubuntu 16.04LTS(linux-image-4.15.0-72-generic)
k8s-m1 control plane + node intel Core i5 9600K 64GB(Non ECC) なし Ubuntu 16.04LTS(linux-image-4.15.0-72-generic)
michiru control plane + node intelXeon Gold 6230 ×2 192GB(ECC Registerd) Quadro GP100 ×4 Ubuntu 16.04LTS(linux-image-4.15.0-72-generic)
utaha node intel Core i9 9900K 128GB(Non ECC) RTX2080Ti×2 Ubuntu 16.04LTS(linux-image-4.15.0-72-generic)
eriri node intel Core i9 9900K 128GB(Non ECC) RTX2080Ti×2 Ubuntu 16.04LTS(linux-image-4.15.0-72-generic)

Step01[全Node共通事項]準備

この章では各種ソフトウェアをインストールする前の準備を行います. 明記しない限り全てのnode,control plane nodeで同じ設定を行ってください.

linux カーネルバージョンの変更(以前と同様#2)

以下の記事で説明した経緯でlinux kernelのバージョンをあげます.(ubuntu 18.04など使用する場合は関係ありません.) tenzen.hatenablog.com

まず現在のKernel Imageを調べておきます.

$ sudo uname -a
Linux tenzen 4.4.0-170-generic #199-Ubuntu SMP Thu Nov 14 01:45:04 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

次に現在使用できるKernel Imageを探してきます.

$ sudo apt-cache search linux-image | grep image-4 | grep generic
(省略)
linux-image-4.15.0-54-generic - Signed kernel image generic
linux-image-4.15.0-55-generic - Signed kernel image generic
linux-image-4.15.0-58-generic - Signed kernel image generic
linux-image-4.15.0-60-generic - Signed kernel image generic
linux-image-4.15.0-62-generic - Signed kernel image generic
linux-image-4.15.0-64-generic - Signed kernel image generic
linux-image-4.15.0-65-generic - Signed kernel image generic
linux-image-4.15.0-66-generic - Signed kernel image generic
linux-image-4.15.0-69-generic - Signed kernel image generic
linux-image-4.15.0-70-generic - Signed kernel image generic
linux-image-4.15.0-72-generic - Signed kernel image generic
linux-image-4.15.0-74-generic - Signed kernel image generic
linux-image-4.15.0-76-generic - Signed kernel image generic
linux-image-4.15.0-88-generic - Signed kernel image generic
linux-image-4.15.0-91-generic - Signed kernel image generic
linux-image-4.15.0-96-generic - Signed kernel image generic
linux-image-4.15.0-99-generic - Signed kernel image generic
linux-image-4.4.0-101-generic - Linux kernel image for version 4.4.0 on 64 bit x86 SMP
linux-image-4.4.0-103-generic - Linux kernel image for version 4.4.0 on 64 bit x86 SMP
...(省略)

今回はlinux-image-4.15.0-72-genericを使用していきます

$ sudo apt-get install -y linux-image-4.15.0-72-generic
$ reboot

再起動すると一番新しいKernel Imageを選択するようになっているので切り替わっていると思います.

$ sudo uname -a
Linux k8s-m 4.15.0-72-generic #81~16.04.1-Ubuntu SMP Tue Nov 26 16:34:21 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

スワップ機能のオフ(以前と同様#1)

K8sは,スワップ機能に対応していないためスワップ機能をオフにする必要があります.

そこで以下のコマンドを実行してスワップ機能のオンオフを確認してください.

$ cat /etc/fstab

# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point>   <type>  <options>       <dump>  <pass>
# / was on /dev/sda2 during installation
UUID=hoge hoge hoge /               ext4    errors=remount-ro 0       1
# /boot/efi was on /dev/sda1 during installation
UUID=hoge hoge  /boot/efi       vfat    umask=0077      0       1
swap was on /dev/sda3 during installation
#UUID=hoge hoge hoge none            swap    sw              0       0

上記のように「swap was on ~」の部分がコメントアウトされていない場合は,スワップ機能がオンになっているためコメントアウトしてスワップ機能をオフにしてください.その後以下のコマンドを実行してスワップがオフになっているか確認してください.

$ swapon -s

何か表示された場合は以下のコマンドを実行してスワップを削除します.

$ swapoff -a

ポートの解放

次に,kubernetesが使用するポートを開放していきます.

開放しなければならないポートはKubernetesの公式インストールガイドにも書いてあります.

kubernetes.io kubernetes.io

今回はufwを用いてポート制御を行うためufwをオンにし,確認を行ってください.

$ sudo ufw enable
$ sudo ufw status
Status: active

次に,Kubernetes公式インストールガイドにしたがって下記のポートを開けていきます.

また今回はflannelを用いて内部ネットワークを構築するため,以下のように他のポートも開けていきます.他にもkube-vipで通過うポートなど以下ポート番号表にあるポートを解放します.

kubernetes.io

Node ポート番号
control plane node 53, 6443-6444, 2379-2380, 8285, 8472, 9153, 10000, 10250-10252
node 53, 8285, 8472, 9153, 10250, 30000-32767

controls plane node

$ sudo ufw allow 53 &&\
sudo ufw allow 6443-6444 &&\
sudo ufw allow 2379:2380/tcp &&\
sudo ufw allow 8285 &&\
sudo ufw allow 8472 &&\
sudo ufw allow 9153 &&\
sudo ufw allow 10000 &&\
sudo ufw allow 10250:10252/tcp

node

$ sudo ufw allow 53 &&\
sudo ufw allow 6444 &&\
sudo ufw allow 8285 &&\
sudo ufw allow 8472 &&\
sudo ufw allow 9153 &&\
sudo ufw allow 10000 &&\
sudo ufw allow 10250 &&\
sudo ufw allow 30000:32767/tcp

name serverの変更(以前と同様)

最後にdnsmasqが悪さをしてgithubのissuesのようにKubernetesのCoreDNSを壊したり, github.com

コンテナ内から通信できなくなったりするためname serverを変更します.

まず,以下のコマンドを実行して設定変更の必要があるかどうかを確認します.

$ cat /etc/resolv.conf 
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 127.0.1.1

上記のようにnameserverが「127.0.1.1」になっている場合は設定変更の必要があります.それ以外の場合は以下の作業を行わなくても大丈夫なはずです.

まず下記のコマンドでNetworkManager.confを表示させ,「dns=dnsmasq」の部分がコメントアウトになっているか確認してください. コメントアウトされていない場合は「dns=dnsmasq」の部分をコメントアウトしてください.

その後ネットワークマネージャを再起動し,resolve.confを再確認してnameserverが変わっていることを確認してください.その際127.0.1.1の設定が残っている場合コメントアウトするか,削除してください.

$ cat /etc/NetworkManager/NetworkManager.conf 
[main]
plugins=ifupdown,keyfile,ofono
dns=dnsmasq

[ifupdown]
managed=false
$ sudo systemctl restart network-manager
$ cat /etc/resolv.conf 
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver xxx.xxx.xxx.xxx

以上で準備は完了です.

Step02[全Node共通事項]:ソフトウェアのインストール

この章では各種ソフトウェアのインストールを行います. 明記しない限り全てのcontrol plane node,nodeで同じ設定を行ってください.

Nvidia Driver(以前と同様#2)

今回は最新のNvidia Driverの中でも比較的安定しているnvidia-440をインストールしました.

Nvidia Driverはcontrol plane nodeにのみインストールしてください.(control plane nodeにもGPUを積んでnodeとして使用する場合はcontrol plane nodeにもインストールしてください.)

まずはじめにNvidia DriverのインストーラLinux Headerを見てインストールするものを判断しているため,使用中のLinux Imageに該当するLinux Headerをインストールします.

$ sudo apt-get install -y linux-headers-4.15.0-72-generic
$ reboot

再起動後「/usr/src/」にlinux-headers-4.15.0-72-genericがあることを確認します.これがないとNvidia Driverを正常にインストールすることができませんのでよく確認しておいてください.

$ ls /usr/src
linux-headers-4.15.0-72-generic
...(省略)

Nvidia公式サイトから自分が使用中のGPUなどを選択してインストーラをダウンロードてきます.今回は440番を使用しました.

ダウンロードできたらダウンロードしてきたファイルの権限を変更後ディスプレイマネージャを切ってCUIモードでログインし直してください.(UbuntuならCtr+Alt+F1)

$ mv /home/tenzen/Downloads/NVIDIA-Linux-x86_64-440.44.run /home/tenzen
$ chmod 777 NVIDIA-Linux-x86_64-440.44.run
$ sudo service lightdm stop

CUIモードでログインできたらドライバをインストールしていきます.インストーラを起動すると青い画面になって色々聞かれるので使用しているホストに合わせて答えていってください.

$ sudo NVIDIA-Linux-x86_64-440.44.run

Docker(以前と同様#1)

次にDocker18.06.3をインストールします. 以下のコマンドを順番に実行していけばインストールできます. 途中,「sudo apt-cache policy docker-ce」コマンドの出力結果に「docker-ce=18.06.3~ce~3-0~ubuntu」があることを確認してください.

$ sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo apt-key fingerprint 0EBFCD88
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
$ sudo apt-get update
$ sudo apt-cache policy docker-ce
$ sudo apt-get install -y docker-ce=18.06.3~ce~3-0~ubuntu

ここで,指定バージョンのdockerがインストールできたかの確認をしてPC起動時にdockerが起動するよう設定しておきます.

$ docker -v
Docker version 18.06.3-ce, build d7080c1
$ service docker start && service docker status

Nvidia-Docker2

さらにNvidia-Docker2のインストールをしていきます.

Nvidia-Docker2はcontrol plane nodeにのみインストールしてください.(control plane nodeにもGPUを積んでいる場合はcontrol plane nodeにもインストールしてください.)

よく似た名前でNvidia-Dockerと言うものがありますが,別物なので間違ってインストールしないように気をつけてください. また,docker v19.03以降からdockerがgpuにネイティブ対応したためNvidia-Docker2ではなくnvidia-container-toolkitを使用するようになりました. しかしながら公式ドキュメント GitHub - NVIDIA/k8s-device-plugin: NVIDIA device plugin for Kubernetes にあるように記事執筆時点でnvidia-container-toolkitには対応しておらずNvidia-docker2を使用するようにアナウンスされているので注意してください.

Note that you need to install the nvidia-docker2 package and not the nvidia-container-toolkit. This is because the new --gpus options hasn't reached kubernetes yet.

以下のコマンドを順番に実行していきます.

$ curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
$ curl -s -L https://nvidia.github.io/nvidia-docker/ubuntu16.04/amd64/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
$ sudo apt-get update
$ sudo apt-get install -y nvidia-docker2
$ sudo pkill -SIGHUP dockerd

これでNvidia-docker2のインストールは完了しました.

インストールが完了したら,nvidia-docker2のバージョン確認とdockerのランタイムをnvidia-docker2のものへ変更します.

まず,以下のコマンドでnvidia-docker2のバージョンを確認し,nvidia-dockerではなくnvidia-docker2がインストールできているか確認します.

$ sudo nvidia-docker version
NVIDIA Docker: 2.2.2
Client:
 Version:           18.06.3-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        d7080c1
 Built:             Wed Feb 20 02:27:18 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.06.3-ce
  API version:      1.38 (minimum version 1.12)
  Go version:       go1.10.3
  Git commit:       d7080c1
  Built:            Wed Feb 20 02:26:20 2019
  OS/Arch:          linux/amd64
  Experimental:     false

次にdockerのランタイムをnvidiaのものへ変更をしていきます.

下記のコマンドを実行してnvidiaランタイムをダウンロード後,/etc/docker/daemon.jsonnvidiaランタイムに変更します.

$ sudo apt-get install nvidia-container-runtime
$ sudo tee /etc/docker/daemon.json <<EOF
{
    "default-runtime": "nvidia",
    "runtimes": {
        "nvidia": {
            "path": "/usr/bin/nvidia-container-runtime",
            "runtimeArgs": []
        }
    }
}
EOF
$ sudo pkill -SIGHUP dockerd
$ sudo systemctl daemon-reload
$ sudo systemctl restart docker

最後にnvidiaのランタイムが正しく読み込まれているかの確認を行います. 下記コマンドを実行後,GPUの情報が表示されれば正しく読み込まれています.

$ sudo docker run --rm nvidia/cuda nvidia-smi
Sun Jun  7 20:08:01 2020       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.44       Driver Version: 440.44       CUDA Version: 10.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Quadro GP100        Off  | 00000000:18:00.0 Off |                  Off |
| 45%   62C    P0    96W / 235W |    959MiB / 16276MiB |    100%      Default |
+-------------------------------+----------------------+----------------------+
|   1  Quadro GP100        Off  | 00000000:3B:00.0 Off |                  Off |
| 46%   64C    P0   163W / 235W |    643MiB / 16278MiB |    100%      Default |
+-------------------------------+----------------------+----------------------+
|   2  Quadro GP100        Off  | 00000000:5E:00.0 Off |                  Off |
| 49%   68C    P0   197W / 235W |    643MiB / 16278MiB |    100%      Default |
+-------------------------------+----------------------+----------------------+
|   3  Quadro GP100        Off  | 00000000:86:00.0 Off |                  Off |
| 48%   66C    P0   171W / 235W |    643MiB / 16278MiB |     96%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+

Kubernetes(以前と同様#1)

最後にKubernetesをインストールしていきます.何もツールを用いずインストールするを行うのは非常に難易度が高いため,今回はKubernetes公式もオススメしているkubeadmを用いてインストールを行っていきます.

kubernetes.io

以下のコマンドを実行してGoogleリポジトリの登録を行い,その後パッケージのインストールを行います.

$ sudo apt-get update
$ sudo apt-get update && sudo apt-get install -y apt-transport-https
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
$ echo deb http://apt.kubernetes.io/ kubernetes-xenial main | sudo tee -a /etc/apt/sources.list.d/kubernetes.list > /dev/null
$ sudo apt-get update
$ sudo apt-get install -y kubectl kubelet kubeadm

Step03:1台目のcontrol plane nodeのセットアップ

はじめにで述べたようにkube-vipを用いてHA構成の設定をした後, Step01でスワップ機能をオフにしましたが,念のためにもう一度オフにしておきます. その後,kubeadmを用いてセットアップしていきます. また,一台目のcontrol plane nodeとそれ以外のcontrol plane nodeでは設定が少し違う事に注意してください.

kube-vip

まずはじめにkube-vipについて軽く紹介した後に,実際の構築を行なっていきたいと思います.

kube-vipとは

kube-vipとは,以下アーキテクチャ図のようにK8s clusterのcontrol plane node内にデプロイされる仮想IPの管理とLoad Balancingの機能を兼ね備えた静的Podの事です.旧来のHA構成においてKeepAlivedが行なっていた仮想IPの管理と,HAProxyが行なっていたLoadBalancingを一つにまとめたようなコンポーネントになっております.

architecture_kube-vip
kube-vipのアーキテクチャ(引用元:https://kube-vip.io/architecture/)

kube-vipの設定

まず以下のようなマニフェストを「config.yaml」を「/etc/kube-vip/」に作成してください. 注意点として以下の設定はcontrol plane node3台を想定したマニフェストとなっているためそれ以外の構成の場合は適宜変更してください.(control plane nodeは3台以上用意しないと障害耐性がない.)

remotePeers:
- id: ${ID_NOT_LOCAL_NODE}
  address: ${IP_ADDR_NOT_LOCAL_NODE}
  port: 10000
- id: ${ID_NOT_LOCAL_NODE}
  address: ${IP_ADDR_NOT_LOCAL_NODE}
  port: 10000
localPeer:
  ${ID_LOCAL_NODE}
  address: ${IP_ADDR_LOCAL_NODE}
  port: 10000
vip: ${VI_IP_ADDR}
gratuitousARP: true
singleNode: false
startAsLeader: true
interface: ${NETWORK_INTERFACE_NAME}
loadBalancers:
- name: Kubernetes Control Plane
  type: tcp
  port: 6443
  bindToVip: true
  backends:
  - port: 6444
    address: ${IP_ADDR_CONTROL_PLANE_NODE}
  - port: 6444
    address: ${IP_ADDR_CONTROL_PLANE_NODE}
  - port: 6444
    address: ${IP_ADDR_CONTROL_PLANE_NODE}

また,以下マニフェスト内で「${hogehoge}」になっている部分は以下の表を参考に使用する環境に合わせて設定してください.

変数名 説明
ID_NOT_LOCAL_NODE control plane nodeのうち1台目以外のID*1
ID_NOT_LOCAL_NODE control plane nodeのうち1台目のID*1
IP_ADDR_NOT_LOCAL_NODE control plane nodeのうち1台目以外のIPアドレス
IP_ADDR_LOCAL_NODE control plane nodeのうち1台目以外のIPアドレス
VI_IP_ADDR kube-apiサーバのエンドポイントとして使用する仮想IPアドレス*2
NETWORK_INTERFACE_NAME 使用するネットワークインターフェース名(例:eno0)
P_ADDR_CONTROL_PLANE_NODE control plane nodeのIPアドレス

1 一意であればなんでもOK 2 必ず使用していないIPアドレスを使用しなければならない.

マニフェストが完成したら以下のdocker コマンドを実行して設定を反映させます.

$ docker run -it --rm plndr/kube-vip:0.1.1 /kube-vip sample manifest \
    | sed "s|plndr/kube-vip:'|plndr/kube-vip:0.1.1'|" \
    | sudo tee /etc/kubernetes/manifests/kube-vip.yaml

以下のような出力がえられると成功です.

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  name: kube-vip
  namespace: kube-system
spec:
  containers:
  - command:
    - /kube-vip
    - start
    - -c
    - /vip.yaml
    image: 'plndr/kube-vip:0.1.1'
    name: kube-vip
    resources: {}
    securityContext:
      capabilities:
        add:
        - NET_ADMIN
        - SYS_TIME
    volumeMounts:
    - mountPath: /vip.yaml
      name: config
  hostNetwork: true
  volumes:
  - hostPath:
      path: /etc/kube-vip/config.yaml
    name: config
status: {}

kubeadmでのセットアップ

「sudo kubeadm init」コマンドのオプション「----pod-network-cidr」は,クラスタ内ネットワークの構築にFlannelを使用する場合の設定です. 他の方法で構築する場合は以下のKubernetes公式ページを参考に設定してください.

kubernetes.io

また,「--control-plane-endpoint」はkube-vipの${VI_IP_ADDR}を入力する事でkube-apiサーバのエンドポイントを設定し,「--upload-certs」はcontrol plane node間で自動的に証明書の交換を行なってくれるオプション(以前はこのオプションがなく手動で証明書交換を行なっていた.)で,「--apiserver-bind-port」はkube-apiサーバ用ポートです. ここで,kubeadmのドキュメント kubeadm init - Kubernetes にあるように,アップロードされたcontrol plane nodeの証明書は有効期限が2時間しかありませんので注意してください.

$ sudo kubeadm init --control-plane-endpoint ${VI_IP_ADDR}:6443 --pod-network-cidr=10.244.0.0/16 --upload-certs --apiserver-bind-port 6444

W0608 17:35:35.559622   19592 version.go:102] could not fetch a Kubernetes version from the internet: unable to get URL "https://dl.k8s.io/release/stable-1.txt": Get https://storage.googleapis.com/kubernetes-release/release/stable-1.txt: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
W0608 17:35:35.559725   19592 version.go:103] falling back to the local client version: v1.18.3
W0608 17:35:35.559949   19592 configset.go:202] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io]
[init] Using Kubernetes version: v1.18.3
[preflight] Running pre-flight checks
        [WARNING IsDockerSystemdCheck]: detected "cgroupfs" as the Docker cgroup driver. The recommended driver is "systemd". Please follow the guide at https://kubernetes.io/docs/setup/cri/
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [k8s-m0 kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-ca" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Generating "etcd/ca" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [k8s-m0 localhost] and IPs [xxx.xxx.xxx.xxx 127.0.0.1 ::1]
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [k8s-m0 localhost] and IPs [xxx.xxx.xxx.xxx 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "sa" key and public key
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address
[kubeconfig] Writing "admin.conf" kubeconfig file
[endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address
[kubeconfig] Writing "kubelet.conf" kubeconfig file
[endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
W0608 17:35:37.730317   19592 manifests.go:225] the default kube-apiserver authorization-mode is "Node,RBAC"; using "Node,RBAC"
[control-plane] Creating static Pod manifest for "kube-scheduler"
W0608 17:35:37.730883   19592 manifests.go:225] the default kube-apiserver authorization-mode is "Node,RBAC"; using "Node,RBAC"
[etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests"
[wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s
[apiclient] All control plane components are healthy after 21.578061 seconds
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.18" in namespace kube-system with the configuration for the kubelets in the cluster
[upload-certs] Storing the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace
[upload-certs] Using certificate key:
ffa4187d479b4543ef2543e458cc5aa791c37b9159defa35e4d463518a871e15
[mark-control-plane] Marking the node k8s-m0 as control-plane by adding the label "node-role.kubernetes.io/master=''"
[mark-control-plane] Marking the node k8s-m0 as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[bootstrap-token] Using token: iri98c.vvgys2r619z676d5
[bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to get nodes
[bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of the control-plane node running the following command on each as root:

  kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehoge \
    --discovery-token-ca-cert-hash sha256:hogehoge \
    --control-plane --certificate-key hogehoge

Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehoge \
    --discovery-token-ca-cert-hash sha256:hogehoge

下の方に「Your Kubernetes control-plane has initialized successfully!」と表示されれば成功です. control plane nodeを冗長化する際に「kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehoge \ --discovery-token-ca-cert-hash sha256:hogehoge \ --control-plane --certificate-key hogehoge」を,nodeを追加する際に「kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehoge \ --discovery-token-ca-cert-hash sha256:hogehoge」を使用するのでどこかに保存しておいてください.このtokenの有効期限は24時間のため有効期限が切れた場合は以下のコマンドで生成済みのtokenの有無を確認の上新規発行を行ってください.

$ sudo kubeadm token list
[sudo] michiru のパスワード: 
TOKEN                     TTL         EXPIRES                     USAGES                   DESCRIPTION                                                EXTRA GROUPS
hogehoge   24h         2020-06-09T17:36:00+09:00   authentication,signing   The default bootstrap token generated by 'kubeadm init'.   system:bootstrappers:kubeadm:default-node-token
$ sudo kubeadm token create --print-join-command
kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehogehoge     --discovery-token-ca-cert-hash sha256:hogehogehoge

ここで設定ミスをして,もう一度「sudo kubeadm init」コマンドを実行する場合「sudo kubeadm reset」を実行してkubeadmの設定をリセットしてから再度「sudo kubeadm init」コマンドを実行してください.

次に,kubectlで使用する認証ファイルの準備を行うため、以下のコマンドを実行してください.

$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

CNIの設定

以下のコマンドでflannelを用いて構築していきます.

kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml 

podsecuritypolicy.policy/psp.flannel.unprivileged created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds-amd64 created
daemonset.apps/kube-flannel-ds-arm64 created
daemonset.apps/kube-flannel-ds-arm created
daemonset.apps/kube-flannel-ds-ppc64le created
daemonset.apps/kube-flannel-ds-s390x created

念の為に正しく設定できているか確認しておきます.

$ kubectl get po -n kube-system
NAME                                   READY   STATUS    RESTARTS   AGE
coredns-66bff467f8-lnp56               1/1     Running   2          9h
etcd-k8s-m0                            1/1     Running   0          9h
kube-apiserver-k8s-m0                  1/1     Running   0          9h
kube-controller-manager-k8s-m0         1/1     Running   1          9h
kube-flannel-ds-amd64-bddvt            1/1     Running   1          9h
kube-proxy-7pqtt                       1/1     Running   0          9h
kube-scheduler-k8s-m0                  1/1     Running   1          9h
kube-vip-k8s-m0                        1/1     Running   0          9h

ここで,すべてのサービスのSTATUSがRunningになっていることを確認してください. 特にcorednsが正しく起動できていない場合,Step01の設定が正しく行えていないことが考えられるためよく確認してください

Step04:2台目以降のcontrol plane nodeのセットアップ

2台目以降control plane nodeの何台追加しても同じ設定になります. kube-vipとkubeadmの実行順序だけ気をつけてください.

kubeadmでのセットアップ

一台目のcontrol plane node構築時に出力されたコマンドを実行するだけです.

$ kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehoge \
    --discovery-token-ca-cert-hash sha256:hogehoge \
    --control-plane --certificate-key hogehoge

[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[preflight] Running pre-flight checks before initializing the new control plane instance
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'
[download-certs] Downloading the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace
[certs] Using certificateDir folder "/etc/kubernetes/pki"
[certs] Generating "etcd/peer" certificate and key
[certs] etcd/peer serving cert is signed for DNS names [k8s-m1 localhost] and IPs [xxx.xxx.xxx.xxx 127.0.0.1 ::1]
[certs] Generating "apiserver-etcd-client" certificate and key
[certs] Generating "etcd/server" certificate and key
[certs] etcd/server serving cert is signed for DNS names [k8s-m1 localhost] and IPs [xxx.xxx.xxx.xxx 127.0.0.1 ::1]
[certs] Generating "etcd/healthcheck-client" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [k8s-m1 kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx]
[certs] Generating "apiserver-kubelet-client" certificate and key
[certs] Generating "front-proxy-client" certificate and key
[certs] Valid certificates and keys now exist in "/etc/kubernetes/pki"
[certs] Using the existing "sa" key
[kubeconfig] Generating kubeconfig files
[kubeconfig] Using kubeconfig folder "/etc/kubernetes"
[kubeconfig] Writing "admin.conf" kubeconfig file
[kubeconfig] Writing "controller-manager.conf" kubeconfig file
[kubeconfig] Writing "scheduler.conf" kubeconfig file
[control-plane] Using manifest folder "/etc/kubernetes/manifests"
[control-plane] Creating static Pod manifest for "kube-apiserver"
W0608 17:41:14.871995   10536 manifests.go:225] the default kube-apiserver authorization-mode is "Node,RBAC"; using "Node,RBAC"
[control-plane] Creating static Pod manifest for "kube-controller-manager"
W0608 17:41:14.876431   10536 manifests.go:225] the default kube-apiserver authorization-mode is "Node,RBAC"; using "Node,RBAC"
[control-plane] Creating static Pod manifest for "kube-scheduler"
W0608 17:41:14.877076   10536 manifests.go:225] the default kube-apiserver authorization-mode is "Node,RBAC"; using "Node,RBAC"
[check-etcd] Checking that the etcd cluster is healthy
[kubelet-start] Downloading configuration for the kubelet from the "kubelet-config-1.18" ConfigMap in the kube-system namespace
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...
[etcd] Announced new etcd member joining to the existing etcd cluster
[etcd] Creating static Pod manifest for "etcd"
[etcd] Waiting for the new etcd member to join the cluster. This can take up to 40s
{"level":"warn","ts":"2020-06-08T17:41:31.138+0900","caller":"clientv3/retry_interceptor.go:61","msg":"retrying of unary invoker failed","target":"passthrough:///https://xxx.xxx.xxx.xxx:2379","attempt":0,"error":"rpc error: code = DeadlineExceeded desc = context deadline exceeded"}
[upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[mark-control-plane] Marking the node k8s-m1 as control-plane by adding the label "node-role.kubernetes.io/master=''"
[mark-control-plane] Marking the node k8s-m1 as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule]

This node has joined the cluster and a new control plane instance was created:

* Certificate signing request was sent to apiserver and approval was received.
* The Kubelet was informed of the new secure connection details.
* Control plane (master) label and taint were applied to the new node.
* The Kubernetes control plane instances scaled up.
* A new etcd member was added to the local/stacked etcd cluster.

To start administering your cluster from this node, you need to run the following as a regular user:

        mkdir -p $HOME/.kube
        sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
        sudo chown $(id -u):$(id -g) $HOME/.kube/config

Run 'kubectl get nodes' to see this node join the cluster.

続いて以下のコマンドを実行し,kube-configファイルを作成します.

$ mkdir -p $HOME/.kube &&\
   sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config &&\
   sudo chown $(id -u):$(id -g) $HOME/.kube/config

kube-vip

ドキュメント Load Balancing a Kubernetes Cluster (Control-Plane) にあるように,2台目以降のcontrol plane nodeをセットアップする際はK8s clusterに参加させるより前にkube-vip静的Pod用マニフェストを準備しては行けません.

2台目以降でも使用するkube-vip静的Pod用yamlファイルは1台目とほとんど変わりませんが,少し変更箇所もあるので以下のように「/etc/kube-vip/config.yaml」を作成してください.

remotePeers:
- id: ${ID_NOT_LOCAL_NODE}
  address: ${IP_ADDR_NOT_LOCAL_NODE}
  port: 10000
- id: ${ID_NOT_LOCAL_NODE}
  address: ${IP_ADDR_NOT_LOCAL_NODE}
  port: 10000
localPeer:
  ${ID_LOCAL_NODE}
  address: ${IP_ADDR_LOCAL_NODE}
  port: 10000
vip: ${VI_IP_ADDR}
gratuitousARP: true
singleNode: false
startAsLeader: false
interface: ${NETWORK_INTERFACE_NAME}
loadBalancers:
- name: Kubernetes Control Plane
  type: tcp
  port: 6443
  bindToVip: true
  backends:
  - port: 6444
    address: ${IP_ADDR_CONTROL_PLANE_NODE}
  - port: 6444
    address: ${IP_ADDR_CONTROL_PLANE_NODE}
  - port: 6444
    address: ${IP_ADDR_CONTROL_PLANE_NODE}

1台目のyamlファイルとの変更点は以下の通りです. また,「LOACL_NODE」がついているものはインストールするホストにしたがって変更してください.

...(省略)
vip: ${VI_IP_ADDR}
gratuitousARP: true
singleNode: false
- startAsLeader: true
+ startAsLeader: false
interface: ${NETWORK_INTERFACE_NAME}
...(省略)

Step05:nodeのセットアップ

nodeの方も,念のためにもう一度スワップ機能をオフにしておきます. その後一台目のcotrol planeで発行されて保存したtokenを入力します. またnodeは何台目であっても設定方法が同じです.

$ kubeadm join xxx.xxx.xxx.xxx:6443 --token hogehoge \
>     --discovery-token-ca-cert-hash sha256:hogehoge
W0608 17:52:22.187551    3076 join.go:346] [preflight] WARNING: JoinControlPane.controlPlane settings will be ignored when control-plane flag is not set.
[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
[kubelet-start] Downloading configuration for the kubelet from the "kubelet-config-1.18" ConfigMap in the kube-system namespace
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.

「Run 'kubectl get nodes' on the control-plane to see this node join the cluster.」と最後に表示されればさきほど作成したK8s Clusterへの追加が成功しています.

Step06:最終確認

最後に,追加したnodeがK8s Clusterに追加されたか確認を行うためcontrol plane nodeもしかはkube-configを保持するローカルマシンで以下のコマンドを実行してください.

$ kubectl get nodes
NAME      STATUS   ROLES    AGE   VERSION
eriri     Ready    <none>   8h    v1.18.3
k8s-m0    Ready    master   8h    v1.18.3
k8s-m1    Ready    master   8h    v1.18.3
michiru   Ready    master   8h    v1.18.3
utaha     Ready    <none>   8h    v1.18.3

K8s Cluster上でのDeepLearning環境の構築

この章では,先ほど構築したK8s Cluster上に機械学習環境の構築を行っていきます.

NVIDIA-device-plugin-for-Kubernetesの導入

NVIDIA-device-plugin-for-Kubernetesのdaemonsetをデプロイします. 今回は記事執筆時点で最新のbeta6を使用します.

github.com

$ kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/1.0.0-beta6/nvidia-device-plugin.yml

daemonset.apps/nvidia-device-plugin-daemonset created

次にNVIDIA-device-plugin-for-Kubernetesデーモンセットが正常にデプロイできているかどうか確認します.「nvidia-device-plugin-daemonset-xxxxx」のSTATUSがRUNNINGになっていれば正常にデプロイできています.

セットアップが完了したので,テスト用Podをデプロイして確認します.デプロイするPodの中身は自分でマニフェストファイルを書くか,以下の内容で「sample.yaml」ファイルを作成してください.

piVersion: v1
kind: Pod
metadata:
  name: gpu-pod
spec:
  containers:
    - name: cuda-container
      image: nvidia/cuda:10.0-devel
      tty: true
      resources:
        limits:
          nvidia.com/gpu: 1 # requesting 1 GPUs

マニフェストが完成したら以下のコマンドを実行してgpu-podをデプロイし,デプロイしたPodのStatusがRunnningになっているかどうか確認します.

$ kubectl apply -f sample.yaml
pod/gpu-pod created
$ kubectl get pods --all-namespaces -o wide
NAMESPACE     NAME                                   READY   STATUS    RESTARTS   AGE   IP               NODE    NOMINATED NODE   READINESS GATES
default       gpu-pod                                1/1     Running   0          10s   10.244.1.5       Utaha   <none>           <none>
(省略)

以上で全て完了です.

確認

全ての工程を正しく行えた場合以下のようなPodがkube-system namespaceにデプロイされているはずです.

$ kubectl get po -n kube-system
NAME                                   READY   STATUS    RESTARTS   AGE
coredns-66bff467f8-lnp56               1/1     Running   2          9h
coredns-66bff467f8-tpcdf               1/1     Running   0          9h
etcd-k8s-m0                            1/1     Running   0          9h
etcd-k8s-m1                            1/1     Running   0          9h
etcd-michiru                           1/1     Running   0          9h
kube-apiserver-k8s-m0                  1/1     Running   0          9h
kube-apiserver-k8s-m1                  1/1     Running   0          9h
kube-apiserver-michiru                 1/1     Running   0          9h
kube-controller-manager-k8s-m0         1/1     Running   1          9h
kube-controller-manager-k8s-m1         1/1     Running   0          9h
kube-controller-manager-michiru        1/1     Running   0          9h
kube-flannel-ds-amd64-bddvt            1/1     Running   1          9h
kube-flannel-ds-amd64-cbgx8            1/1     Running   2          9h
kube-flannel-ds-amd64-h6bcv            1/1     Running   2          9h
kube-flannel-ds-amd64-pdjxh            1/1     Running   0          9h
kube-flannel-ds-amd64-xlbjj            1/1     Running   3          9h
kube-proxy-7pqtt                       1/1     Running   0          9h
kube-proxy-b66mj                       1/1     Running   0          9h
kube-proxy-ht7dd                       1/1     Running   0          9h
kube-proxy-klgk4                       1/1     Running   0          9h
kube-proxy-tdzkb                       1/1     Running   0          9h
kube-scheduler-k8s-m0                  1/1     Running   1          9h
kube-scheduler-k8s-m1                  1/1     Running   0          9h
kube-scheduler-michiru                 1/1     Running   0          9h
kube-vip-k8s-m0                        1/1     Running   0          9h
kube-vip-k8s-m1                        1/1     Running   0          9h
kube-vip-michiru                       1/1     Running   0          9h
nvidia-device-plugin-daemonset-245zk   1/1     Running   0          7h16m
nvidia-device-plugin-daemonset-6dcxx   1/1     Running   0          7h16m
nvidia-device-plugin-daemonset-hgkwt   1/1     Running   0          7h16m
nvidia-device-plugin-daemonset-sf7nl   1/1     Running   0          7h16m
nvidia-device-plugin-daemonset-twnsb   1/1     Running   0          7h16m

出力結果を見てみるとetcd,kube-apiserver,kube-controller,kube-schedulerがしっかり冗長構成になっていることがわかります.この記事ではやりませんが,実際にどれか一つのマシンを落としてもきちんとK8s clusterは稼働し続けていることが確認できると思います.

おわりに

今回はkube-vipを用いてcontrol planeの冗長化を行いました. ドキュメント Creating a single control-plane cluster with kubeadm - Kubernetes

By default, your cluster will not schedule Pods on the control-plane node for security reasons.

にあるように プロダクションで使用することを考えるとセキュリティ上の問題からcontrol planeとnodeは分離するべきですが台数を用意することができなかったため共存させる形になりました.誰かの参考になれば幸いです.