Kubernetesのオブジェクトについて ~マニフェストを添えて~
記事の目的
kubernetes(以下k8s)を触り始めるにあたり、Podなどの概念を理解していなかったので、リソース操作の基本的なオブジェクトの概要、マニフェストの書き方についてまとめておく。
オブジェクトについて
k8sのクラスターを表現するためのエンティティ。
k8sはオブジェクトで定義した状態を、コントロールプレーンが常に現在の状態の差分を埋め、理想の状態に管理します。
オブジェクトの操作方法は基本的にはkubectlコマンドを通して、マニフェストに記載したオブジェクトの状態を操作することが可能です。
オブジェクトでは以下のような状態を管理することができます。下の例はあくまでk8sでできることの一部であることに注意してください。
- どのようなコンテナ化されたアプリケーションをデプロイするか
- デプロイしたアプリのヒーリング機能
- アプリのアップデート戦略
次の章からこれらの機能について具体的にどのオブジェクトを操作することで実現できるのかをまとめていきます。
なお、オブジェクトの説明については各章で説明の記載をしていますが、kubectl explain <object>で確認することが可能です。
たとえば、Podのspecフィールドにどのような項目を指定できるかなどはkubectl explain pods.specで確認することができます。
Pod
Podはk8sで管理できる最小のデプロイ可能な単位。
Podは1つ以上のコンテナを持ち、ストレージ、ネットワークのリソースを共有。
また、コンテナには実行方法に関するルールが指定可能です。
Pod内のコンテナは、コントロールプレーンによって、クラスター内の同じホスト上に自動的に配置、スケジュールされます。
基本的にはPodは直接デプロイされるものではありません。代わりにDeploymentやJobなどのワークロードリソースと呼ばれるものを使用してPodの作成を行います。
Podを直接デプロイする場合のマニフェストは以下の通り。
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.18-alpine
    ports:
    - containerPort: 80
| 項目 | 説明 | 
|---|---|
| apiVersion | オブジェクトが定義されているバージョン | 
| kind | オブジェトを表す文字列 | 
| metadata | オブジェクトのメタデータ | 
| metadata.name | ネームスペース内で自身を表す一意な文字列 | 
| spec | Podのふるまいを定義する | 
| spec.containers | Podで動作するコンテナの一覧。少なくとも1つの要素を持つ | 
| spec.containers[].name | DNS_LABELとしてのPod内でユニークな名前 | 
| spec.containers[].image | Dockerイメージ名 | 
| spec.containers[].ports | コンテナから公開するポート | 
| spec.containers[].ports[].containerPort | PodのIPアドレスで公開するポート番号 | 
ReplicaSet
Podを指定した個数で維持することを目的としたオブジェクト。
Podの個数維持はk8sのセルフヒーリング機能で実現しています。
通常、DeploymentというReplicaSetにPodのアップデート機能を追加したオブジェクトを介してPodの個数維持を定義するため、アップデート機能を必要としない限りはReplicaSetを直接使うことは基本的にはないとおもいます。
ReplicaSetを作成するマニフェストは以下の通りです。
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: replicaset-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: replicaset-nginx
  template:
    metadata:
      labels:
        app: replicaset-nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.18-alpine
        ports:
        - containerPort: 80
| 項目 | 説明 | 
|---|---|
| apiVersion | オブジェクトが定義されているバージョン | 
| kind | オブジェトを表す文字列 | 
| metadata | オブジェクトのメタデータ | 
| metadata.name | ネームスペース内で自身を表す一意な文字列 | 
| spec | ReplicaSetのふるまいを定義する | 
| spec.replicas | 希望したいPod数 | 
| spec.selector | ReplicaSetでスケールさせたいPodに一致させるためのクエリ。replica countと一致する必要がある | 
| spec.selector.matchLabels | スケールさせたいPodが持つラベルを指定。複数指定可能で、それらはAND条件 | 
| spec.template | Pod章のspec配下の説明と同じ | 
Deployment
DeploymentはReplicaSetの機能に、Podのアップデート機能を追加したもの。
Deploymentを利用することで、ReplicaSetを作成したり、既存のDeploymentを新しいDeploymentにアップデートすることが可能になります。
アップデートではイメージなどの更新が走った際に、更新ルールにのっとってPodが順に切り替わっていきます。
試しにdeploymentをデプロイしたマニフェストのイメージなどのバージョンを変更して再度applyしなおすと、Podが切り替わっていく様子がkubectl get podsを通して確認できます。
アップデートの方法はいくつかの方法が存在し、マニフェストのspec.strategyで指定可能です。
Deploymentのマニフェストは以下の通りです。
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-nginx
  labels:
    name: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: deployment-nginx
  template:
    metadata:
      labels:
        app: deployment-nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.21-alpine
        ports:
        - containerPort: 80
| 項目 | 説明 | 
|---|---|
| apiVersion | オブジェクトが定義されているバージョン | 
| kind | オブジェトを表す文字列 | 
| metadata | オブジェクトのメタデータ | 
| metadata.name | ネームスペース内で自身を表す一意な文字列 | 
| metadata.labesl | オブジェクトに付与するラベル | 
| spec | Deploymentのふるまいを定義する | 
| spec.replicas | 希望したいPod数 | 
| spec.selector | Podに付与されたラベルを指定。これと一致するPodを持つReplicaSetをDeploymentで管理する | 
| spec.selector.matchLabels | スケールさせたいPodが持つラベルを指定。複数指定可能で、それらはAND条件 | 
| spec.template | Pod章のspec配下の説明と同じ | 
Service
Podの集合に対して、ネットワークサービスとして公開するためのオブジェクト。
PodはそれぞれにIPアドレスが割り当てられますが、状況によってはPodは削除され再作成されます。
このとき、再作成されたPodのIPアドレスはもともとのPodのIPアドレスは異なってきます。
ServiceはこのPodの特定のために、セレクター機能を使うことで解決します。下のマニフェスト例ではspec.selectorでapp: deployment-nginxのラベルを持つPodに対してservice-nginxという名前でアクセス可能になります。
また、Pod以外への通信も抽象化可能です。この場合、セレクターによる指定ではなくEndpointsオブジェクトを明示的に組み合わせることでk8s以外のコンポーネントへの通信も抽象化できます。
Serviceのマニフェストは以下の通りです。
apiVersion: v1
kind: Service
metadata:
  name: service-nginx
spec:
  selector:
    app: deployment-nginx
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 80
| 項目 | 説明 | 
|---|---|
| apiVersion | オブジェクトが定義されているバージョン | 
| kind | オブジェトを表す文字列 | 
| metadata | オブジェクトのメタデータ | 
| metadata.name | ネームスペース内で自身を表す一意な文字列 | 
| spec | Serviceのふるまいを定義する | 
| spec.selector | Podに付与されたラベルを指定。これと一致するPodを持つPodに対してServiceが通信を転送する | 
| spec.ports | サービスによって展開されるポートのリスト | 
| spec.ports[].protocol | Serviceで扱うプロトコル | 
| spec.ports[].port | Serviceによって公開したいPod側のポート | 
| spec.ports[].targetPort | portへの通信で、外部からアクセスする際に使用する外部のポート | 
ConfigMap
ConfigMapは他のオブジェクトが参照するためのデータを保存するためのオブジェクト。
dataセクション配下にkey-valueの形式でデータを定義することで、PodからConfigMapのmetadata.nameをもとにボリュームにマウントすることが可能になります。
また、ConfigMapを使ってデータをPodに読ませることで、ConfigMapで変更した内容を自動的にPodに知らせることができ、Podの再起動なしにデータの更新が可能となります。
では実際にマニフェストを作成し、PodにConfigMapを使って設定ファイルなどを読み込ませていきます。
今回はnginxの設定ファイル、htmlファイルをConfigMapで管理し、nginxのPodにマウントさせます。
apiVersion: v1
kind: ConfigMap
metadata:
  name: configmap-sample
data:
  nginx.config: |
    pid /var/run/nginx.pid;
    events {
        worker_connections 3;
    }
    http {
        server {
            root /usr/share/nginx/html;
        }
    }
  nginx.html: |
    <!DOCTYPE html>
    <html>
      <head>
        <title>Welcome to nginx!</title>
      </head>
      <body>
        <h1>Nginx Home Page for ConfigMap!</h1>
        <p>This page is nginx page for sample of ConfigMap.</p>
        <p>My Profile</p>
        <p><a href="https://github.com/tamago0224">My Github</a>
        <p><a href="https://twitter.com/tamago_0224">My Twitter</a>
      </body>
    </html>
---
apiVersion: v1
kind: Pod
metadata:
  name: configmap-sample-pod
  labels:
    app: configmap-sample-pod
spec:
  containers:
    - name: nginx-pod
      image: nginx:1.23
      volumeMounts:
        - name: nginx-config
          mountPath: "/etc/nginx"
          readOnly: true
        - name: nginx-data
          mountPath: "/usr/share/nginx/html"
          readOnly: true
  volumes:
    - name: nginx-config
      configMap:
        name: configmap-sample
        items:
          - key: "nginx.config"
            path: "nginx.conf"
    - name: nginx-data
      configMap:
        name: configmap-sample
        items:
          - key: "nginx.html"
            path: "index.html"
---
apiVersion: v1
kind: Service
metadata:
  name: configmap-service-nginx
spec:
  selector:
    app: configmap-sample-pod
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 80
上記マニフェストには3つのyamlドキュメントが含まれています。
1つ目はConfigMapのドキュメントで、ここではnginxが読み込む設定を定義しています。
2つ目はPodのドキュメントで、ConfigMapで定義した設定をPodに読み込ませるためのnginxのPodの定義を記載しています。
3つ目はServiceのドキュメントで、こちらは2つ目作成したPodをデバック用のコンテナからcurlでnginxの応答を確認するために、Serviceオブジェクトを使ってPod外からアクセスするためのものです。
今回の目的としてはConfigMapを使ってPodとどう連携させるのかが中心となるので、1つ目、2つ目のドキュメントについて確認していきます。
まずはConfigMapの設定項目について
| 項目 | 説明 | 
|---|---|
| apiVersion | オブジェクトが定義されているバージョン | 
| kind | オブジェクトを表す文字列 | 
| metadata | オブジェクトのメタデータ | 
| metadata.name | ネームスペース内で自身を表す一意な文字列。Podの volumesセクションで指定する際に使われる | 
| data | データをkey-valueで指定するセクション。valueにnon-UTF8な者がある場合は binaryDataを使うこと | 
次にPodの設定項目について。PodはPodでも扱ったため、そこで取り扱わなかった部分について記載する
| 項目 | 説明 | 
|---|---|
| spec.containers.volumeMounts | コンテナへマウントするための設定を記載する項目 | 
| spec.containers.volumeMounts.name | 一致するボリュームの名前 | 
| spec.containers.volumeMounts.mountPath | ボリュームをマウントするコンテナ内のパス | 
| spec.containers.volumeMounts.readOnly | マウントしたボリュームがread-onlyかどうか | 
| spec.volumes | Podからアクセス可能な名前付きのボリュームを定義する項目 | 
| spec.volumes.name | Pod内で一意なDNS_LABELの形式を持った名前 | 
| spec.volumes.configMap | コンテナに展開するボリュームタイプ。今回はConfigMap | 
| spec.volumes.configMap.name | Podで参照するための名前 | 
| spec.volumes.configMap.items | ConfigMapで定義されているファイルを個別に追加するための項目 | 
| spec.volumes.configMap.items[].key | ConfigMapで定義したkey | 
| spec.volumes.configMap.items[].path | keyのvalueの内容を展開するパス先 | 
Secret
Secretは少量の機密情報を含んだオブジェクトです。
Secretを用いることで、アプリケーションコードに機密情報を含める必要がなくなります。
Secretの使い方はConfigMapとほぼ同じで、Secretのオブジェクトをマニフェストなどで定義し、PodからコンテナにSecretで定義した情報をマウントします。
ConfigMapと異なるところは主に2点ほどあると思います。
- 
typeを指定することで、Secretオブジェクトのシナリオを想定
- 
dataで指定するkey-valueのvalueはbase64でエンコードされたもの
dataについてはbase64でエンコードできない場合はstringDataを代わりに使うなどすることができます。
実際のマニフェストを用意してデプロイしてみます。
上の説明でもあった1. のtypeについては一般的なシナリオ、デフォルトでもあるOpaqueを使います。
また、base64エンコードして記載しているのでわかりにくいですが、今回利用する値はConfigMapで指定したものと同じものになります。
apiVersion: v1
kind: Secret
metadata:
  name: secret-sample
data:
  nginx.config: cGlkIC92YXIvcnVuL25naW54LnBpZDsKCmV2ZW50cyB7CiAgICB3b3JrZXJfY29ubmVjdGlvbnMgMzsKfQoKaHR0cCB7CiAgICBzZXJ2ZXIgewogICAgICAgIHJvb3QgL3Vzci9zaGFyZS9uZ2lueC9odG1sOwogICAgfQp9Cg==
  nginx.html: PCFET0NUWVBFIGh0bWw+CjxodG1sPgogICAgPGhlYWQ+CiAgICAgICAgPHRpdGxlPldlbGNvbWUgdG8gbmdpbnghPC90aXRsZT4KICAgIDwvaGVhZD4KICAgIDxib2R5PgogICAgICAgIDxoMT5OZ2lueCBIb21lIFBhZ2UgZm9yIENvbmZpZ01hcCE8L2gxPgogICAgICAgIDxwPlRoaXMgcGFnZSBpcyBuZ2lueCBwYWdlIGZvciBzYW1wbGUgb2YgQ29uZmlnTWFwLjwvcD4KICAgICAgICA8cD5NeSBQcm9maWxlPC9wPgogICAgICAgIDxwPjxhIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS90YW1hZ28wMjI0Ij5NeSBHaXRodWI8L2E+PC9wPgogICAgICAgIDxwPjxhIGhyZWY9Imh0dHBzOi8vdHdpdHRlci5jb20vdGFtYWdvXzAyMjQiPk15IFR3aXR0ZXI8L2E+PC9wPgogICAgPC9ib2R5Pgo8L2h0bWw+Cg==
---
apiVersion: v1
kind: Pod
metadata:
  name: secret-sample-pod
  labels:
    app: secret-sample-pod
spec:
  containers:
    - name: nginx-pod
      image: nginx:1.23
      volumeMounts:
        - name: nginx-config
          mountPath: "/etc/nginx"
          readOnly: true
        - name: nginx-data
          mountPath: "/usr/share/nginx/html"
          readOnly: true
  volumes:
    - name: nginx-config
      secret:
        secretName: secret-sample
        items:
          - key: nginx.config
            path: nginx.conf
    - name: nginx-data
      secret:
        secretName: secret-sample
        items:
          - key: nginx.html
            path: index.html
---
apiVersion: v1
kind: Service
metadata:
  name: secret-service-nginx
spec:
  selector:
    app: secret-sample-pod
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 80
ConfigMapとほぼほぼ同じなので、異なる項目のみ抽出して表にまとめます。
| 項目 | 説明 | 
|---|---|
| spec.volumes[].secret | コンテナに展開するボリュームタイプ。今回はSecret | 
| spec.volumes[].secret.secretName | Podにマウントさせたい同じnamespace内に存在するSecretの名前 | 
そのほかはConfigMapで説明した内容と同じです。
参考資料
- https://kubernetes.io/ja/docs/concepts/workloads/pods/
- https://kubernetes.io/ja/docs/concepts/workloads/controllers/replicaset/
- https://kubernetes.io/ja/docs/concepts/workloads/controllers/deployment/
- https://kubernetes.io/ja/docs/concepts/services-networking/service/
- https://kubernetes.io/ja/docs/concepts/configuration/configmap/
- https://kubernetes.io/ja/docs/concepts/configuration/secret/

Discussion