読者です 読者をやめる 読者になる 読者になる

そんな今日この頃の技術ネタ

本家側に書くほどでもない小ネタ用

kubernetes学習その2 最低限文化的なサービスを立ち上げてみる

引き続きkubernetesの使い方について学習。

前回作ったminikubeの環境で実際にyamlファイルを用いた操作を行ってみる。

blue1st-tech.hateblo.jp


最低限把握しておくこと

作業の前にザックリと用語と概念を把握。

  • Node: 実際にコンテナが動いているマシンのこと。kubectl get nodeで一覧を見ることができる。
  • Pod: 同一のNode上で動かすコンテナ群。スケールする場合このPod単位での操作になるので、機能を実現する最低単位で分割していくと良さそう。
  • Service: それぞれのNode/Podで動作しているコンテナ同士や外部との通信を司る概念。
  • Resource: PodとかServiceとかいった概念をk8sではこう呼ぶらしい。
  • Manifestfile: Resourceの設定ファイルで、yamlやjsonなどの形式で記述できる。

今のところの大雑把な理解でいえば、Podがdocker-compose.yamlでいうところのservicesおよびvolumes項、Serviceがnetworks項にあたりそう。

この業界では「インスタンス上で動く何かしらの機能」を指して「サービス」と言ったりする(現にこの記事のタイトルもdocker-composeは前者の意味で使ってる)わけだけど、それと混同しないように注意したい。

PodのManifestfile

ひとまず単純に

  • HTTPサーバとしてnginxを起動
  • busyboxを別に起動し、そのディレクトリをマウント

といった構成を考えてみる。

pod.yamlとして以下のようなYAMLファイルを記述する。

apiVersion: v1
kind: Pod
metadata:
  name: http
  labels:
    app: http-app
spec:
  containers:
    - name: nginx
      image: nginx:latest
      ports:
        - name: http-port
          containerPort: 80
      volumeMounts:
        - name: docroot
          mountPath: /usr/share/nginx/html
    - name: html
      image: busybox
      command:
        - tail
        - -f
        - /dev/null
      volumeMounts:
        - name: docroot
          mountPath: /opt/html
      lifecycle:
        postStart:
          exec:
            command:
              - /bin/sh
              - -c
              - echo "<html><body>Hello k8s!</body></html>" > /opt/html/index.html
  volumes:
    - name: docroot
      emptyDir: {}

kind項にResourceの種類、metadataで名前やラベルなどの操作のための付加情報、spec内に実際的な記述を書き込んでいく。 このあたりのフォーマットは他のResourceでも同様なので目を慣らしておくと良い。


spec.containers項に実際に起動すべきコンテナ関連の情報を記述する。docker-composeを扱ったことがあればなんとなくニュアンスは分かるところだが、

  1. ポートなりボリュームなりはそれぞれ名前を付けて、それを用いて抽象的な概念として扱っていく。
  2. (僕の調べ方が足りないのなければ)volume-from的な他のコンテナのボリュームをそのままマウントする記述はできなそう。今回使用したemptyDir(Pod内のみの共通ボリューム)やhostDir、その他もろもろのストレージサービスを用いる記述でもって対処する必要がある。
  3. lifecycleという要素で起動時(postStart)や終了時(preStop)に任意のコマンドを実行できる。便利。
  4. docker-composeなんかだとボリュームをマウントするだけのコンテナはずっと起動しておく必要は無いのだけど、k8s的にはコンテナが死んでしまうとエラー扱いになってしまうため、今回でいうところのbusyboxはtail -f /dev/nullにより永続化している。(もしかしたらもっと良い対処があるのかも。)

4についてはJobというResourceを使って、単に「共有のボリュームにファイルを置く」だけのバッチとして扱うのが良いかもしれない、と書き上げた後で思った。

といった部分に慣れる必要がある。


そんなこんなで、

  • コンテナ起動時にhtmlファイルを作成
  • それをドキュメントルートしてマウント

といった動作を実現している。

ServiceのManifestfile

Services | Kubernetes

先のPodはあくまでNode上で起動しているもののため、実際にWebページとして見えるようにするにはServiceという概念を用いる必要がある。

service.yamlとして以下のような記述を行った。

kind: Service
apiVersion: v1
metadata:
  name: http-service
  labels:
    app: http-app
spec:
  selector:
    app: http-app
  ports:
    - port: 8080
      targetPort: http-port
  externalIPs:
    - 192.168.99.100

spec.selector項にはpodの方でラベルとして設定したものを記述。また、対象となるコンテナのポートを名前で指定する。最後のexternalIPsは・・・ひとまずminikube ipで拾えるIPをそのまま入力したものを使ってるが、もっと良い方法があるかもしれない。

起動

以上で記述に問題がなければファイルを指定して起動する。

$ kubectl create -f pod.yaml -f service.yaml
pod "http" created
service "http-service" created

-lオプションでラベルを指定して状態を確認できる。

$ kubectl get po,svc -l app=http-app
NAME      READY     STATUS    RESTARTS   AGE
po/http   2/2       Running   0          4m

NAME               CLUSTER-IP   EXTERNAL-IP      PORT(S)    AGE
svc/http-service   10.0.0.48    192.168.99.100   8080/TCP   2m

ひとまずPodでちゃんとコンテナが2つ起動されてること、ServiceでIPやポートが割当てられたことがなんとなく分かるだろう。 他にもlogsコマンドやdescribeコマンドなんかもあるが今回は割愛。

動作確認

Serviceで記述したように192.168.99.100の8080ポートに接続してみると・・・

$ curl 192.168.99.100:8080
<html><body>Hello k8s!</body></html>

意図通りのレスポンスが返ってくることが確認できる。

後片付け

$ kubectl delete -f pod.yaml -f service.yaml
pod "http" deleted
service "http-service" deleted



以上のようにしてひとまずk8s上でコンテナを立ち上げるところまでのやり方は把握できた。

実際に触ってみるとdocker-composeとは必ずしも機能が一対一で対応しているわけではないので、なまじあちらに慣れていると最初は戸惑うところであるが、実際にサービスとして運用することを考えると合理的な設計であるように思える。

とかくラベルや名前を通して管理するという作法が徹底されていて、例えば同じselectorで選択可能なpodを増やせば簡単にスケールできるし、また逆に適切にラベルを貼ることでBlue/Greenデプロイを実現することもできるのが面白い。(このあたりは次の機会にでも触れようかと思う。)

Kubernetes Cookbook: Building Cloud Native Applications

Kubernetes Cookbook: Building Cloud Native Applications