TechLunch

The free lunch is over.

Kubernetes入門6: 初期化専用コンテナを起動する

一般的には、何らかのサービスを提供するアプリケーションをコンテナとして起動することが多いです。しかし、DBなどの初期化処理などを実行するコンテナを一度だけ起動したいということもあります。

その場合、KubernetesInit Containersが要件に適しているでしょう。Configure Pod Initializationを参考に試してみました。

準備

techlunch.hatenablog.com minikubeを用意しておきましょう。

init containerを含むPodを作成する

以下のPod設定ファイルで、1つのアプリケーションコンテナと1つのinit containerを持つPodを作成します。

# https://k8s.io/examples/pods/init-containers.yaml
apiVersion: v1
kind: Pod
metadata:
  name: init-demo
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 80
    volumeMounts:
    - name: workdir
      mountPath: /usr/share/nginx/html
  # These containers are run during pod initialization
  initContainers:
  - name: install
    image: busybox
    command:
    - wget
    - "-O"
    - "/work-dir/index.html"
    - http://kubernetes.io
    volumeMounts:
    - name: workdir
      mountPath: "/work-dir"
  dnsPolicy: Default
  volumes:
  - name: workdir
    emptyDir: {}

設定ファイルを見ると、init containerとアプリケーションコンテナはworkdirというボリュームを共有していることがわかります。 init containerは、アプリケーションコンテナが起動する前に、

wget -O /work-dir/index.html http://kubernetes.io

という処理を実行し、完了します。この際にnginxサーバのルートディレクトリにファイルを書き込んでいることがわかります。

それでは実行してみます。

$ kubectl create -f https://k8s.io/examples/pods/init-containers.yaml
$ kubectl get pods
NAME        READY     STATUS    RESTARTS   AGE
init-demo   1/1       Running   0          5m

次に、nginxによって提供されているpageがinit containerによって書き込まれたファイルであることを確認します。

$ kubectl exec -it init-demo -- /bin/bash
# apt update; apt install curl -y
# curl localhost
...

今回はnginxを例にしましたが、MySQLなどのDBに初期データを投入するといった使い方もできます。

コンテナとVMの関係

Dockerブログに、Are Containers Replacing Virtual Machines?という記事がポストされました。

記事ではVMとコンテナの関係や違いについて述べられています。本記事では一部抜粋して紹介します。

Point #1: Containers Are More Agile than VMs

https://i2.wp.com/blog.docker.com/wp-content/uploads/Blog.-Are-containers-..VM-Image-1-1024x435.png?ssl=1

左側がDockerを利用してコンテナをデプロイする時のアーキテクチャになります。コンテナ化されたアプリケーションはホストOSを共有していますので、アプリケーションの起動時間しかかかりません。一方、右側の図の通りVMごとにアプリケーションをデプロイする場合は、OSから起動するため多少時間がかかります。

また、一般的にアプリケーションは一つのDockerイメージとして作成し、そのイメージを元に各環境でDockerコンテナを起動します。そのため、自分のマシンでは動いたという問題が起こりにくくなります。*1

Point #3: Integrate Containers with Your Existing IT Processes

https://i2.wp.com/blog.docker.com/wp-content/uploads/Are-containers-..-vms-image-2-1024x759.png?ssl=1

ほとんどの企業はエンタープライズ用途に必須な監視、バックアップ、プロセス自動化といったツールを含んだVM環境を備えています。コンテナは図のように、VM上でも問題なく動かすことができるので、既存の資産を有効活用しつつ徐々にアプリケーションをコンテナ化していくといったことができます。

また、コンテナとVMの両方を管理するという点では、KubeVirtが役に立つかもしれません。

結局のところ、コンテナとVMは競合するものではなく共存するものなので、統合を楽に行うツールなどが今後出てくるかもしれません。

*1:実行する環境ごとに環境情報が異なるので、実際には環境依存で動かない場合もあります。

どのようなワークロードをコンテナ化すべきか

Container JournalのDeciding Which – and How Many – Workloads to Containerizeという記事が興味深かったので、簡単に紹介します。

記事では、

No matter how popular containers become, the reality is that they are going to coexist with virtual machines and bare-metal servers at most organizations. They won’t totally replace older forms of infrastructure.

という考えのもと、どのようなワークロードをコンテナ化すべきかについて述べています。

コンテナ化すべきものとそうでないもの

どのインフラをコンテナ化すべきか考える時に、次の質問を投げかけています。

Persistent data needs.

データの永続化を必要としないstatelessなアプリケーションは、コンテナ化する第一候補となるでしょう。一方、永続化を必要とするアプリケーションのコンテナ化は複雑です。まずは、statelessなアプリケーションのコンテナ化で知見をためていくことを私はおすすめしています。

Workload isolation.

ベアメタル、コンテナ、VMでisolationのレベルは大きく異なるので、要件に従って適切な方法を取る必要があります。私の感覚では、自社が管理するマシン上で様々なアプリケーションを走らせる場合には、コンテナは適していると思います。

Scalability.

コンテナはVMと比べて起動速度も早いですし、スケーラビリティが求められるワークロードはコンテナ化に適していると言えます。

Updates.

迅速かつ継続的に更新が必要なアプリケーションの場合は、Dockerイメージとコンテナの恩恵を強く感じると思います。一方、ほとんど更新を必要としないレガシーなアプリケーションは現在の実行環境から、あえてコンテナ化しても期待する効果を得ることはできないかもしれません。

その他にも記事では、

How Much Should You Containerize?

などについて、述べていますので、興味のある方はご覧ください。

KubeVirtなどに関するPodCastが公開されました

blog.openshift.com

RedHatにより提供されているPodCastにて、KubeVirtやContainer Native Virtualization (CNV) について話されています。KubeVirtは本ブログでも簡単に紹介したとおり、VMをPodと同じレベルのリソースとして管理するためのKubernetes拡張機能です。

CNVは、コンテナ前提となりつつある世の中で、既存の資産であるVM上のワークロードをどのようにして、コンテナと共存していくかということに対する概念、考え方のように思えます。それを実現するための実装としてKubeVirtなどのOSSを開発しているようです。

VMのワークロードもコンテナ化すれば良いじゃないかと思われる方もいるかもしれませんが、OS依存のアプリケーションなど問題は山積みだと思いますので、Kubernetesへの緩やかな移行を促すためにも本プロジェクトは興味深く思っています。もちろんコンテナでは満たせない要件は、今まで通りVMを利用する必要がありますので、そういった意味でも大変重要なプロジェクトだと思います。

VM資産をお持ちで、Kubernetesへの移行を検討してる方は、このPodCastも聞いて見てください。

Kubevirt: VMを管理するためのKubernetesアドオン

コンテナと同じようにVMKubernetesで管理するためのアドオンであるKubevirtについて、Kubernetsブログで紹介されていたので、試してみました。

Kubevirtは、Custom Resource Definitions (CRDs)などを利用して, Kubernetesを拡張しており、Podと同じようにVMを管理することができます。

準備

techlunch.hatenablog.com

Minikubeをインストールしてる前提となるので、上の記事などを参考にインストールします。 私の環境は、VirtualBoxを利用しており、nested virtualizationが使えないため、次のコマンドでエミュレーションを有効化します。

$ kubectl create configmap -n kube-system kubevirt-config --from-literal debug.allowEmulation=true

KubeVirt関連コンポーネントをPodとして実行します。

$ export VERSION=v0.7.0-alpha.2
$ kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/$VERSION/kubevirt.yaml

次のコマンドでPodが立ち上がっていることが確認できます。

$ kubectl get pods --namespace=kube-system
NAME                                    READY     STATUS    RESTARTS   AGE
...
virt-api-7797f95869-9pvg7               1/1       Running   0          11m
virt-api-7797f95869-lhqn7               1/1       Running   0          11m
virt-controller-69cc6b4897-jnvl7        1/1       Running   0          11m
virt-controller-69cc6b4897-zq2qx        1/1       Running   0          11m
virt-handler-fjttr                      1/1       Running   0          11m

VMのデプロイ

次のコマンドを見ればわかる通り、Podと同じようにKubernetesの一リソースとして管理が可能です。

# VM作成
$ kubectl apply -f https://raw.githubusercontent.com/kubevirt/demo/master/manifests/vm.yaml

# 確認
$ kubectl get vms
NAME      CREATED AT
testvm    1m
$ kubectl get vms -o yaml
...

また、本記事では割愛しますが、virtctlをインストールすることで、Kubernetesで作成したVMにアクセスしたりすることが可能です。これは、Podとして作成したコンテナにDockerコマンドを利用してアクセスするのと同じように使うことができます。

今回はKubevirtをほんの少し触った程度で終わります。しかし、今後Kubernetesの導入を考えていく上でレガシーなVM資産との共存を考えていく必要が出てくることを考慮し、別の記事で詳細に触れたいと思っています。

Kubernetes入門5: Podを特定のノードに割り当てる

Kubernetesでは、スケジューラがPodをどのノードで実行するかを判断します。PodをGPUSSDが搭載された特定のノードで実行したい場合は、nodeSelectorなどを利用することで、実現できます。

KubernetesドキュメントのAssign Pods to Nodesを参考にしながら、試してみました。

準備

可能なら複数台のノードを用意できると良いです。Kubernetesのインストールはkubeadmを利用した方法が簡単です。

ノードにLabelを追加する

KubernetesクラスタにJoin済のノード一覧を表示します。

$ kubectl get nodes
NAME      STATUS    ROLES     AGE       VERSION
master     Ready     master    8d        v1.11.2
worker1    Ready     <none>    8d        v1.11.2
worker2    Ready     <none>    8d        v1.11.2

私のクラスタ環境は1台のmaster、2台のworkerで構成されていることがわかります。

次に、worker1にdisktype=ssdというlabelを付与します。

$ kubectl label nodes worker1 disktype=ssd
$ kubectl get nodes --show-labels
...

これで、worker1にはPodを割り当てる時のヒントになるlabelが付与されました。

Podを特定のノード上で動かす

次の設定ファイルでPodを作成します。

# https://k8s.io/examples/pods/pod-nginx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd

設定ファイルのnodeSelectorの項目に、disktype: ssdという設定を記述することで、このlabelを持つノードにPodがスケジューリングされます。

Podを作成し、どのノードで実行されているか確認すると、worker1でPodが動いていることがわかります。

$ kubectl create -f https://k8s.io/examples/pods/pod-nginx.yaml
$ kubectl get pods --output=wide
...

該当するlabelを持つノードが存在しない場合

Podの設定で、nodeSelectorを指定してる際に、それに該当するノードが存在しない場合の挙動はどうなるか試してみました。

まず、先ほどworker1に付与したlabelを削除します。

$ kubectl label nodes worker1 disktype-

Podを実行して、状態を確認します。

$ kubectl create -f https://k8s.io/examples/pods/pod-nginx.yaml
$ kubectl describe pods nginx
Events:
  Type     Reason            Age               From               Message
  ----     ------            ----              ----               -------
  Warning  FailedScheduling  4s (x5 over 13s)  default-scheduler  0/3 nodes are available: 3 node(s) didn't match node selector.

すると、nodeSelectorにマッチするノードが存在しないということでPodがどのノードでも実行されていないことがわかります。

以上より、ノードにlabelという情報を付与することで、ユーザがPodを任意のノードに割り当てることができます。GPUSSDを利用するアプリケーションをデプロイしたい方は、ぜひ試してみてください。

Kubernetes入門4: Liveness and Readiness Probesを使ってみる

Kubernetes上で実行されるアプリケーションコンテナの状態を確認する仕組みとして、Liveness and Readiness Probesがあります。それぞれ、

  • Liveness probe
    • 失敗するとコンテナを再起動する
  • Readiness probe
    • 失敗するとコンテナにトラフィックを流さなくなる(コンテナの再起動はしない)

という特徴があります。

今回は、Kubernetes TasksのConfigure Liveness and Readiness Probesを試してみました。

Liveness Probeの例がいくつか載っていますが、この記事では「Linuxコマンド」のみ実行しています。

Linuxコマンドで状態確認

設定ファイルは次の通りです。

# https://k8s.io/examples/pods/probe/exec-liveness.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-exec
spec:
  containers:
  - name: liveness
    image: k8s.gcr.io/busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5

この設定ファイルでは、コンテナ一つのPodが定義されており、そのコンテナは起動後に、touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; というコマンドを実行しています。このコマンドでは、指定ディレクトリにファイルを作成し、30秒後に削除しています。

一方、liveness probeの設定をみると、5秒ごとに cat /tmp/healthy コマンドを実行し、ファイルの存在確認を行っています。そのため、30秒後にはコマンドが失敗し、コンテナの再起動を試みることになるはずです。

Podを実行。

$ kubectl create -f https://k8s.io/examples/pods/probe/exec-liveness.yaml

30秒後にPodの状態確認。

$ kubectl describe pod liveness-exec
Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
<snip>
  Warning  Unhealthy  49s (x3 over 59s)  kubelet, test_node    Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
  Normal   Killing    18s                kubelet, test_node    Killing container with id docker://liveness:Container failed liveness probe.. Container will be killed and recreated.
<snip>

Liveness probeにより、コンテナの異常を検知し、コンテナ再起動を行っていることがわかります。

Readiness probeについても、元のドキュメントyamlファイルが載っていますので、興味がある方はご参照ください。