LoginSignup
14

More than 3 years have passed since last update.

一足遅れて Kubernetes を学び始める - 06. workloads その2 -

Last updated at Posted at 2019-05-05

ストーリー

  1. 一足遅れて Kubernetes を学び始める - 01. 環境選択編 -
  2. 一足遅れて Kubernetes を学び始める - 02. Docker For Mac -
  3. 一足遅れて Kubernetes を学び始める - 03. Raspberry Pi -
  4. 一足遅れて Kubernetes を学び始める - 04. kubectl -
  5. 一足遅れて Kubernetes を学び始める - 05. workloads その1 -
  6. 一足遅れて Kubernetes を学び始める - 06. workloads その2 -
  7. 一足遅れて Kubernetes を学び始める - 07. workloads その3 -
  8. 一足遅れて Kubernetes を学び始める - 08. discovery&LB その1 -
  9. 一足遅れて Kubernetes を学び始める - 09. discovery&LB その2 -
  10. 一足遅れて Kubernetes を学び始める - 10. config&storage その1 -
  11. 一足遅れて Kubernetes を学び始める - 11. config&storage その2 -
  12. 一足遅れて Kubernetes を学び始める - 12. リソース制限 -
  13. 一足遅れて Kubernetes を学び始める - 13. ヘルスチェックとコンテナライフサイクル -
  14. 一足遅れて Kubernetes を学び始める - 14. スケジューリング -
  15. 一足遅れて Kubernetes を学び始める - 15. セキュリティ -
  16. 一足遅れて Kubernetes を学び始める - 16. コンポーネント -

前回

一足遅れて Kubernetes を学び始める - 05. workloads その1 -では、Pod,ReplicaSet,Deploymentの3つを学習しました。今回はDaemonSet,StatefulSet(一部)を学びます。

DaemonSet

ReplicaSetとほぼ同じ機能のリソース。
ReplicaSetとの違いは、各ノードに1つずつ配置するのがDaemonSet,バラバラなのがReplicaSet。
用途として、モニタリングツールやログ収集のPodに使う。

さっそく、試してみます。

sample-ds.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: sample-ds
spec:
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
          ports:
            - containerPort: 80
pi@raspi001:~/tmp $ k apply -f . --all --prune
daemonset.apps/sample-ds created
pi@raspi001:~/tmp $ k get all -o=wide
NAME                  READY   STATUS    RESTARTS   AGE   IP            NODE       NOMINATED NODE   READINESS GATES
pod/sample-ds-wxzbw   1/1     Running   0          60s   10.244.2.24   raspi003   <none>           <none>
pod/sample-ds-xjjtp   1/1     Running   0          60s   10.244.1.37   raspi002   <none>           <none>

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE    SELECTOR
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   6d1h   <none>

NAME                       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE   CONTAINERS        IMAGES       SELECTOR
daemonset.apps/sample-ds   2         2         2       2            2           <none>          60s   nginx-container   nginx:1.12   app=sample-app

ReplicaSetと大きく違いはありません。
また、各ノードに対してpodが作られていることがわかります。

Deploymentと似ているアップデート戦略があり、OnDeleteとRollingUpdate(デフォルト)があります。前者は、podを明示的に削除した(k delete)際に更新する戦略です。DaemonSetは、死活監視やログ収集に使うので、手動でのタイミングが効くOnDeleteが好まれます。後者は、Deploymentと同じ動きで、即時更新していく戦略です。

ReplicaSetと似ているようで、機能的にはDeploymentに近い感じですね。ReplicaSetはpodが削除されたら複製されますけど、アップデートされません。DaemonSetはpodが削除されたら複製するし、アップデートもされます。試してみます。

sample-ds.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: sample-ds
spec:
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.13
          ports:
            - containerPort: 80

nginxのバージョンを1.12から1.13に変更しました。

pi@raspi001:~/tmp $ k apply -f . --all --prune
daemonset.apps/sample-ds configured
pi@raspi001:~/tmp $ k get all -o=wide
NAME                  READY   STATUS              RESTARTS   AGE   IP            NODE       NOMINATED NODE   READINESS GATES
pod/sample-ds-sx4mv   0/1     ContainerCreating   0          5s    <none>        raspi003   <none>           <none>
pod/sample-ds-xjjtp   1/1     Running             0          12m   10.244.1.37   raspi002   <none>           <none>

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE    SELECTOR
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   6d2h   <none>

NAME                       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE   CONTAINERS        IMAGES       SELECTOR
daemonset.apps/sample-ds   2         2         1       1            1           <none>          12m   nginx-container   nginx:1.13   app=sample-app

applyしてみると、一台ずつupdateされています(containerCreating)。Deploymentと違うのは、最大pod数が1のために、一時的にpodが機能しなくなるタイミングが生まれます(超過分の設定不可)。

pi@raspi001:~/tmp $ k delete pod sample-ds-sx4mv
pod "sample-ds-sx4mv" deleted
pi@raspi001:~/tmp $ k get all -o=wide
NAME                  READY   STATUS              RESTARTS   AGE     IP            NODE       NOMINATED NODE   READINESS GATES
pod/sample-ds-hgvtv   0/1     ContainerCreating   0          6s      <none>        raspi003   <none>           <none>
pod/sample-ds-k8cfx   1/1     Running             0          4m38s   10.244.1.38   raspi002   <none>           <none>

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE    SELECTOR
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   6d2h   <none>

NAME                       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE   CONTAINERS        IMAGES       SELECTOR
daemonset.apps/sample-ds   2         2         1       2            1           <none>          17m   nginx-container   nginx:1.13   app=sample-app

podを削除しても、セルフヒーリングで復活します。

StatefulSet

ステートレスなpodではなく、DBのようなステートフルなpod向けのリソース。
podを削除しても、データを永続的に保存する仕組みが存在。
動作自体は、replicaSetと似ている。

さっそく、試してみます。

sample-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: sample-statefulset
spec:
  serviceName: sample-statefulset
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
          ports:
            - containerPort: 80
          volumeMounts:
          - name: www
            mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1G

mountPathで指定したマウントしたいパスを、volumeClaimTemplatesでマウントしてくれます。 どこに?
Storageに関しては別で学習することにします。
ひとまず、applyします。

pi@raspi001:~/tmp $ k apply -f . --all --prune
daemonset.apps/sample-ds unchanged
statefulset.apps/sample-statefulset created
pi@raspi001:~/tmp $ k get pod
NAME                   READY   STATUS    RESTARTS   AGE
sample-ds-hgvtv        1/1     Running   0          54m
sample-ds-k8cfx        1/1     Running   0          58m
sample-statefulset-0   0/1     Pending   0          5m19s
pi@raspi001:~/tmp $ k describe pod sample-statefulset-0
...
Events:
  Type     Reason            Age                  From               Message
  ----     ------            ----                 ----               -------
  Warning  FailedScheduling  70s (x3 over 2m28s)  default-scheduler  pod has unbound immediate PersistentVolumeClaims (repeated 2 times)

おや、Pendingになってしまいました。 pod has unbound immediate PersistentVolumeClaims

PersistentVolumeとPersistentVolumeClaims

PersistentVolume(永続的ボリューム)は、名前の通りで、データを永続的に保存しておく場所のリソースです。
マネージドサービスを利用すると、デフォルトでPresistentVolumeが用意されているそうです。
私の環境は、マネージドサービスではなく、自作環境であるので、PresistentVolumeを用意する必要があります。

PersistentVolumeClaims(永続的ボリューム要求)は、これも名前の通りで、「PresistentVolumeを使わせて」というリソースです。
このリソースで、PresistentVolumeのnameを指定し、applyすることで、初めてマウントができます。
例えば、PodからPersistentVolumeClaimsの名前を指定してあげると、そのPodはClaimしたPersistentVolumeをマウントすることができます。
volumeClaimTemplatesというのは、「わざわざPersistentVolumeClaimsを定義しなくてもテンプレートに沿って書けばClaimsできるよ」というものです。

で、何が問題だったの?

pod has unbound immediate PersistentVolumeClaimsのとおりで、「PersistentVolumeの要求をしたけど、Volume割当できなかったよ」とのことです。

PersistentVolume(pv)があるのか確認してみます。

pi@raspi001:~/tmp $ k get pv
No resources found.

たしかにないです。PersistentVolumeを用意しないといけないのですが、どうしましょう。
解決手段として考えたのは3点です。

  1. GCPやAWS,Azureのサービスを使う
  2. LocalVolumeを使う
  3. NFSを使う

types-of-persistent-volumes

1は、書いておいてなんですが、却下です。理由は、せっかくraspberryPiで構築したのでクラウドサービスを利用したくないからです。

2は、Kubernetes: Local Volumeの検証の参考にして試したのですが、 記事にも書いてあるとおり「Local Volumeは他のPodから共有で利用することができない」ため、statefulsetがreplica:1でなければ動きません。それはそれで動くので学習になり良いのですが、せっかくならreplicaの制限なしにしたいです(ReadWriteManyにしたい)。

3は、もう一台raspberryPiを用意して、それをNFSと見立ててPersistentVolumeにしてみる方法です。

3を進めようと思います。

NFS導入

サーバ設定

NFS用の新たなraspberryPiを用意します。設定手順はこちらを参考にしました。
その後の続きは下記です。

NFSのホスト名はnfspiとします。

~ $ slogin pi@nfspi.local
pi@nfspi:~ $ sudo apt-get install nfs-kernel-server
pi@nfspi:~ $ sudo vim /etc/exports
/home/data/ 192.168.3.0/255.255.255.0(rw,sync,no_subtree_check,fsid=0)

意味としては、「指定範囲のIPアドレスからのマウントを許可する」。オプションは、こちらを参照。

host ip
iMac 192.168.3.3
raspi001(master) 192.168.3.32
raspi002(worker) 192.168.3.33
raspi003(worker) 192.168.3.34
nfspi(NFS) 192.168.3.35
pi@nfspi:~ $ sudo mkdir -p /home/data
pi@nfspi:~ $ sudo chmod 755 /home/data
pi@nfspi:~ $ sudo chown pi:pi /home/data
pi@nfspi:~ $ sudo /etc/init.d/nfs-kernel-server restart
pi@nfspi:~ $ service rpcbind status
pi@nfspi:~ $ systemctl status nfs-server.service

正しく設定されたか、iMacから確認してみます。

~ $ mkdir share
~ $ sudo mount_nfs -P nfspi.local:/home/data ./share/
~ $ sudo umount share

OK

クライアント設定

各ノードに対して下記を実行します。

pi@raspi001:~ $ sudo apt-get install nfs-common

nfs-client導入

raspberryPi環境では、真っ白な状態なので、一からPersistentVolumeを用意する必要があります。それにはVolumeとなるStorageの型を用意する必要もあるのですが、Storage Classesを見る限り、NFS用の型は標準で存在しません。そこで、nfs-clientを使ってNFS用のStorageClassを作成します。

pi@raspi001:~ $ git clone https://github.com/kubernetes-incubator/external-storage.git && cd cd external-storage/nfs-client/
pi@raspi001:~/external-storage/nfs-client $ NS=$(kubectl config get-contexts|grep -e "^\*" |awk '{print $5}')
pi@raspi001:~/external-storage/nfs-client $ NAMESPACE=${NS:-default}
pi@raspi001:~/external-storage/nfs-client $ sed -i'' "s/namespace:.*/namespace: $NAMESPACE/g" ./deploy/rbac.yaml
pi@raspi001:~/external-storage/nfs-client $ k apply -f deploy/rbac.yaml

rbac.yamlにあるnamespaceを現在動かしている環境のnamespaceに置換して、applyしています。

pi@raspi001:~/external-storage/nfs-client $ k apply -f deploy/deployment-arm.yaml
pi@raspi001:~/external-storage/nfs-client $ k apply -f deploy/class.yaml

deployment-arm.yamlでは、NFSサーバのIPアドレス(192.168.3.35)とマウントパス(/home/data)を設定しました。
class.yamlが、今回欲していたNFSのstorageClass(managed-nfs-storage)になります。

※ raspberryPiのイメージはRaspbianを使っているので、arm用のdeployment-arm.yamlを使います。Wiki
これに随分とハマってしまいました...

pi@raspi001:~/external-storage/nfs-client $ k apply -f deploy/test-claim.yaml -f deploy/test-pod.yaml

試しにマウント先にファイルが作成できているのかテストしています。確認します。

nfspiに移動

pi@nfspi:~ $ ls /home/data

あれば成功です。あれば、下記で片付けます。

pi@raspi001:~/external-storage/nfs-client $ k delete -f deploy/test-pod.yaml -f deploy/test-claim.yaml

statefulsetをリトライ

以上で、StorageClassを用意できました。よって後は、PersistentVolume作って、PersistentVolumeClaim作って...となる予定でした。
しかし、nfs-clientには、dynamic provisioningという機能が備わっており、PersistentVolumeを作らなくても、PersistentVolumeClaimするだけで良くなります。この件については、storageを学習する際に書きます。

raspi001に移動して、sample-statefulset.yamlをもう一度applyします。
(storageClassName: managed-nfs-storageを追加, ReadWriteOnce→ReadWriteManyに変更)

sample-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: sample-statefulset
spec:
  serviceName: sample-statefulset
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
          ports:
            - containerPort: 80
          volumeMounts:
          - name: www
            mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes:
      - ReadWriteMany
      storageClassName: managed-nfs-storage
      resources:
        requests:
          storage: 1Gi
pi@raspi001:~/tmp $ k apply -f sample-statefulset.yaml

nfapiに移動して、あるか確認。

pi@nfspi:~ $ ls -la /home/data
total 20
drwxrwxrwx 5 pi     pi      4096 May  5 17:18 .
drwxr-xr-x 4 root   root    4096 May  4 15:50 ..
drwxrwxrwx 2 nobody nogroup 4096 May  5 17:17 default-www-sample-statefulset-0-pvc-5911505b-6f51-11e9-bb47-b827eb8ccd80
drwxrwxrwx 2 nobody nogroup 4096 May  5 17:18 default-www-sample-statefulset-1-pvc-5f2fd68e-6f51-11e9-bb47-b827eb8ccd80
drwxrwxrwx 2 nobody nogroup 4096 May  5 17:18 default-www-sample-statefulset-2-pvc-69bee568-6f51-11e9-bb47-b827eb8ccd80

ありました! マウントできています!

お片付け

--pruneでも良いのですが、下記のほうが使いやすかったです。

pi@raspi001:~/tmp $ k delete -f sample-ds.yaml -f sample-statefulset.yaml
pi@raspi001:~/tmp $ k delete pvc www-sample-statefulset-{0,1,2}

k get pvk get pvcを試して頂き、今回作ったリソースがありましたら削除お願いします。

おわりに

StatefulSetを使える状態にするまでに記事が大きくなってしまいました。次回に詳しく学んでいこうと思います。笑
あと、nfs-clientを見て思ったのが、kubernetesのパッケージマネージャであるhelmを導入した方が、遥かに便利だと思いつつ、手動設定しました。。。
次回は、こちらです。

Register as a new user and use Qiita more conveniently

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