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で障害ドメインを変更してより柔軟な構成を作成しました.またどこかでお会いしましょう.

Golangを用いたマニフェスト(Kubernetes)生成ツール

注意事項

本記事ではgolangで実装したコマンドラインツールの紹介をしますが,私が構築したKubernetes環境に最適化(スペックの選択等)させているため,そのままでは使用することはできません.使用される際は自己責任でコードを書き換えて使用してください.

はじめに

本ブログではKubernetes(以下k8s) Clusterの整備に関する備忘録をまとめてきました.(最近更新できてない分は少しづつ更新していきます.)

現在はおおよそ図1のような構成でJupyterlabを提供することにより,機械学習環境をk8sを用いて構築しています.

fig1:around jupyterlab in k8s cluster
図1:k8s clusterのJupyterlab周辺

しかしながら,k8sを何も知らない人にとってはマニフェストの作成が使用への大きな壁になっていました.また,マニフェストのサンプルを配布した場合でも,スペックの異なるノードをまとめて運用しているためコンテナのスペックを選択する際に各ノードのスペックを知っておく必要があり,よりマニフェストの作成が複雑になっていました.

そこで今回,以下図2の赤枠部分を自動化するコマンドラインツールcksctlをgolangで実装してみました.使用方法などはGitLabのReadmeに書いてあるので,どんな感じで設計したのかをまとめておきたいと思います.

gitlab.com

fig2:pipline
図2:パイプライン

前提条件

図2のフローはGitOpsという思想に基づいたものになっています.GitOpsをざっくりいうと設定ファイル(k8sでいう所のマニフェスト)やプログラムファイルをGitで管理し,CIやCDをGitを起点に行おうというものです.図2をみて貰えばわかりますが,k8sを使用する場合,CIツールとCDツールを分離して従来のようなCIツールに権限が集中する状況を避けます.また,マニフェストを人間が管理し,kubectlコマンドを用いてアプリケーションのデプロイを行っているとマニフェストの管理が杜撰になり,デプロイミスも増えてしまいます.そこで,マニフェストをGit管理することでマニフェストの変更履歴を残し,CDツールでデプロイすることにより,オペレーションエラーを極力減らす事ができます.

ツールの選択

今回の環境では,アプリケーションの開発を行うわけではないため,CIはあまり使用しません.そのためCIを行うのは,Dockerfileのpushによるdocker buildのみになります.

CIツールにはGitLab CI,CDツールにはArgoCDを採用します.

環境

今回アプリケーションの開発も実際にアプリケーションを動作させる環境もDockerを用いて作成しました.これにより,開発環境と実行環境を揃える事ができ,さらにアプリケーションを使用する際もdocker runするだけで動作させる事ができます.

開発環境

FROM golang:1.13.10
LABEL maintainer="CVLAB.KubernetesService:tenzen"

ENV USER_NAME tenzen
ENV UID 1000
RUN useradd -m -u ${UID} ${USER_NAME}

ENV ARGO_CLI_VERSION v1.5.5

ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
ENV GOPATH /home/${USER_NAME}/app

WORKDIR ${GOPATH}/src
RUN go get -u gopkg.in/src-d/go-git.v4/... \
 && go get gopkg.in/yaml.v3 \
 && curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/${ARGO_CLI_VERSION}/argocd-linux-amd64 \
 && chmod +x /usr/local/bin/argocd

実行環境

アプリケーションの実行環境では,開発環境でビルドした後のバイナリファイルのみを実行環境に持ってくるマルチステージビルドを行う事で,イメージ容量の軽減を実現しています.実際に開発環境のイメージは1GBほどありますが,アプリケーション環境は20MBほどにおさまっています.

FROM golang:1.13.10 as builder
LABEL maintainer="CVLAB.KubernetesService:tenzen"

ENV USER_NAME builder
ENV UID 1000
RUN useradd -m -u ${UID} ${USER_NAME}

ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
ENV GOPATH /home/${USER_NAME}/app

WORKDIR ${GOPATH}/src
RUN go get -u gopkg.in/src-d/go-git.v4/... \
 && go get gopkg.in/yaml.v3 \
 && chown -R ${UID}:${UID} .
COPY ["builder", "."]

USER ${USER_NAME}
RUN go build -o cksctl main.go


FROM alpine:3.9.6

ENV BUILDER_NAME builder
ENV USER_NAME user
ENV UID 1000
RUN adduser -D -g '' --uid ${UID} ${USER_NAME}

ENV BUILD_DIR /home/${BUILDER_NAME}/app/src
ENV HOME_DIR /home/${USER_NAME}/app

WORKDIR ${HOME_DIR}
COPY --from=builder ["${BUILD_DIR}/cksctl", "."]
COPY --from=builder ["${BUILD_DIR}/YamlTemplates", "YamlTemplates"]

RUN apk update \
 && apk add curl \
 && curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v1.5.5/argocd-linux-amd64 \
 && chmod +x /usr/local/bin/argocd \
 && mkdir cksSettingFiles \
 && chown -R ${UID}:${UID} .

USER ${USER_NAME}
ENTRYPOINT [ "./cksctl" ]

実装

Jupyterlabをデプロイするまでの大きな流れとしては,

  1. GitLab CIでdockerイメージの作成
  2. cksctlでマニフェストを作成して,Gitに保存
  3. ArgoCDでk8s上にデプロイする.

となっています.

fig3:role of cksctl
図3:cksctlの役割

GitLab CI

1のGitLab CIで使用するgitlab-ci.ymlはサンプルを参考に以下のようなものを作成しました.

docker-build-master:
  image: docker:latest
  stage: build
  services:
    - docker:dind
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  script:
    - docker build --pull -t "$CI_REGISTRY_IMAGE" .
    - docker push "$CI_REGISTRY_IMAGE"
  only:
    - master

この設定ファイルを使用することによりDockerfileをpushすると自動的にコンテナイメージのビルドが始まります.

cksctl

2においてはユーザに入力された要求スペック情報などを元にHelm Chartのvalues.yamlを作成し,HelmChartとともにGit リポジトリにプッシュします. 例えばjupyterlabのvalues.yamlは以下のようになっており,golangのtext/templateを用いて値を書き込みます.

uname: {{ .uname }}
jupyter_pass: {{ .jupyter_pass }}
image_name: {{ .image_name }}
hardware_type: {{ .hardware_type }}
image_secret: {{ .image_secret }}
gpu_flag: {{ .gpu_flag }}
gpu_type: {{ .gpu_type }}
gpu_num: {{ .gpu_num }}
cpu_num: {{ .cpu_num }}
memory_capacities: {{ .memory_capacities }}
created_day: {{ .created_day }}

また,一度入力した情報を設定ファイルとして保存し,使用できるようにもなっています. 設定ファイルは以下のようになっており,values.yamlと同様にgolangのtext/templateを用いて書き込みます.

settings: 
- name: {{ .cks_proj_name }}
  metadata:
  - git:
    - uname: {{ .account_name }}
      token: {{ .access_token }}
      project: {{ .project_name }}
      type: {{ .git_type }}
    k8s:
    - basic_info:
      - uname: {{ .uname }}
        jupyter_pass: {{ .jupyter_pass }}
        image: {{ .image_name }}
        secret: {{ .image_secret }}
      spec:
      - gpu:
        - flag: true
          hardware: {{ .hardware_type }}
{{- if eq .gpu_flag "true" }}
          type: {{ .gpu_type }}
          num: {{ .gpu_num }}  
{{- end }}
        cpu:
        - num: {{ .cpu_num }}
        memory:
        - capacities: {{ .memory_capacities }}
        cephfs:
        - capacities: {{ .cephfs_capacities }}

ArgoCD

3では,ArgoCDがHelmChartを用いてjupyterをデプロイします. HelmChartを使用しているので,HelmChartを準備し,values.yamlgolangのtext/templateにのとった形式に変更すれば簡単に対応アプリケーションを 増やせるようになっています.

改善点

現状Gitのマスターブランチにしかpushできないので,別ブランチにもpushできるようにしていきたいと考えています.

最後に

今回初めてgolangを使ったのですが,はじめはJavaの知識が邪魔をして構造体やインターフェースを理解する事が大変でした.普段全くアプリケーション開発などしないため相当ひどいコードになっていると思うので,コードを見て発狂する可能性のある方は見ないことをお勧めします.