1 - 設定のベストプラクティス

このドキュメントでは、ユーザーガイド、入門マニュアル、および例を通して紹介されている設定のベストプラクティスを中心に説明します。

このドキュメントは生ものです。このリストには載っていないが他の人に役立つかもしれない何かについて考えている場合、IssueまたはPRを遠慮なく作成してください。

一般的な設定のTips

  • 構成を定義する際には、最新の安定したAPIバージョンを指定してください。

  • 設定ファイルは、クラスターに反映される前にバージョン管理システムに保存されるべきです。これによって、必要に応じて設定変更を迅速にロールバックできます。また、クラスターの再作成や復元時にも役立ちます。

  • JSONではなくYAMLを使って設定ファイルを書いてください。これらのフォーマットはほとんどすべてのシナリオで互換的に使用できますが、YAMLはよりユーザーフレンドリーになる傾向があります。

  • 意味がある場合は常に、関連オブジェクトを単一ファイルにグループ化します。多くの場合、1つのファイルの方が管理が簡単です。例としてguestbook-all-in-one.yamlファイルを参照してください。

  • 多くのkubectlコマンドがディレクトリに対しても呼び出せることも覚えておきましょう。たとえば、設定ファイルのディレクトリで kubectl applyを呼び出すことができます。

  • 不必要にデフォルト値を指定しないでください。シンプルかつ最小限の設定のほうがエラーが発生しにくくなります。

  • よりよいイントロスペクションのために、オブジェクトの説明をアノテーションに入れましょう。

"真っ裸"のPod に対する ReplicaSet、Deployment、およびJob

  • 可能な限り、"真っ裸"のPod(ReplicaSetDeploymentにバインドされていないPod)は使わないでください。Nodeに障害が発生した場合、これらのPodは再スケジュールされません。

    明示的にrestartPolicy: Neverを使いたいシーンを除いて、DeploymentはPodを直接作成するよりもほとんど常に望ましい方法です。Deploymentには、希望する数のPodが常に使用可能であることを確認するためにReplicaSetを作成したり、Podを置き換えるための戦略(RollingUpdateなど)を指定したりできます。Jobのほうが適切な場合もあるかもしれません。

Service

  • 対応するバックエンドワークロード(DeploymentまたはReplicaSet)の前、およびそれにアクセスする必要があるワークロードの前にServiceを作成します。Kubernetesがコンテナを起動すると、コンテナ起動時に実行されていたすべてのServiceを指す環境変数が提供されます。たとえば、fooという名前のServiceが存在する場合、すべてのコンテナは初期環境で次の変数を取得します。

    FOO_SERVICE_HOST=<the host the Service is running on>
    FOO_SERVICE_PORT=<the port the Service is running on>
    

    これは順序付けの必要性を意味します - PodがアクセスしたいServicePod自身の前に作らなければならず、そうしないと環境変数は注入されません。DNSにはこの制限はありません。

  • (強くお勧めしますが)クラスターアドオンの1つの選択肢はDNSサーバーです。DNSサーバーは、新しいServiceについてKubernetes APIを監視し、それぞれに対して一連のDNSレコードを作成します。クラスタ全体でDNSが有効になっている場合は、すべてのPodが自動的にServicesの名前解決を行えるはずです。

  • どうしても必要な場合以外は、PodにhostPortを指定しないでください。PodをhostPortにバインドすると、Podがスケジュールできる場所の数を制限します、それぞれの<hostIPhostPortprotocol>の組み合わせはユニークでなければならないからです。hostIPprotocolを明示的に指定しないと、KubernetesはデフォルトのhostIPとして0.0.0.0を、デフォルトの protocolとしてTCPを使います。

    デバッグ目的でのみポートにアクセスする必要がある場合は、apiserver proxyまたはkubectl port-forwardを使用できます。

    ノード上でPodのポートを明示的に公開する必要がある場合は、hostPortに頼る前にNodePortの使用を検討してください。

  • hostPortの理由と同じくして、hostNetworkの使用はできるだけ避けてください。

  • kube-proxyのロードバランシングが不要な場合は、headless ServiceClusterIPNone)を使用してServiceを簡単に検出できます。

ラベルの使用

  • { app: myapp, tier: frontend, phase: test, deployment: v3 }のように、アプリケーションまたはデプロイメントの セマンティック属性 を識別するラベルを定義して使いましょう。これらのラベルを使用して、他のリソースに適切なポッドを選択できます。例えば、すべてのtier:frontendを持つPodを選択するServiceや、app:myappに属するすべてのphase:testコンポーネント、などです。このアプローチの例を知るには、ゲストブックアプリも合わせてご覧ください。

セレクターからリリース固有のラベルを省略することで、Serviceを複数のDeploymentにまたがるように作成できます。 Deploymentにより、ダウンタイムなしで実行中のサービスを簡単に更新できます。

オブジェクトの望ましい状態はDeploymentによって記述され、その仕様への変更が 適用 されると、Deploymentコントローラは制御された速度で実際の状態を望ましい状態に変更します。

  • デバッグ用にラベルを操作できます。Kubernetesコントローラー(ReplicaSetなど)とServiceはセレクターラベルを使用してPodとマッチするため、Podから関連ラベルを削除すると、コントローラーによって考慮されたり、Serviceによってトラフィックを処理されたりすることがなくなります。既存のPodのラベルを削除すると、そのコントローラーはその代わりに新しいPodを作成します。これは、「隔離」環境で以前の「ライブ」Podをデバッグするのに便利な方法です。対話的にラベルを削除または追加するには、kubectl labelを使います。

コンテナイメージ

imagePullPolicyとイメージのタグは、kubeletが特定のイメージをpullしようとしたときに作用します。

  • imagePullPolicy: IfNotPresent: ローカルでイメージが見つからない場合にのみイメージをpullします。

  • imagePullPolicy: Always: kubeletがコンテナを起動する度に、kubeletはコンテナイメージレジストリに問い合わせて、イメージのダイジェストの名前解決を行います。もし、kubeletが同じダイジェストのコンテナイメージをローカルにキャッシュしていたら、kubeletはそのキャッシュされたイメージを利用します。そうでなければ、kubeletは解決されたダイジェストのイメージをダウンロードし、そのイメージを利用してコンテナを起動します。

  • imagePullPolicy のタグが省略されていて、利用してるイメージのタグが:latestの場合や省略されている場合、Alwaysが適用されます。

  • imagePullPolicy のタグが省略されていて、利用してるイメージのタグはあるが:latestでない場合、IfNotPresentが適用されます。

  • imagePullPolicy: Never: 常にローカルでイメージを探そうとします。ない場合にもイメージはpullしません。

備考: コンテナが常に同じバージョンのイメージを使用するようにするためには、そのコンテナイメージのダイジェストを指定することができます。<image-name>:<tag><image-name>@<digest>で置き換えます(例:image@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2)。このダイジェストはイメージの特定のバージョンを一意に識別するため、ダイジェスト値を変更しない限り、Kubernetesによって更新されることはありません。
備考: どのバージョンのイメージが実行されているのかを追跡するのが難しく、適切にロールバックするのが難しいため、本番環境でコンテナをデプロイするときは :latestタグを使用しないでください。
備考: ベースイメージのプロバイダーのキャッシュセマンティクスにより、imagePullPolicy:Alwaysもより効率的になります。たとえば、Dockerでは、イメージがすでに存在する場合すべてのイメージレイヤーがキャッシュされ、イメージのダウンロードが不要であるため、pullが高速になります。

kubectlの使い方

2 - ConfigMap

ConfigMapは、 機密性のないデータをキーと値のペアで保存するために使用されるAPIオブジェクトです。Podは、環境変数、コマンドライン引数、またはボリューム内の設定ファイルとしてConfigMapを使用できます。

ConfigMapを使用すると、環境固有の設定をコンテナイメージから分離できるため、アプリケーションを簡単に移植できるようになります。

注意: ConfigMapは機密性や暗号化を提供しません。保存したいデータが機密情報である場合は、ConfigMapの代わりにSecretを使用するか、追加の(サードパーティー)ツールを使用してデータが非公開になるようにしてください。

動機

アプリケーションのコードとは別に設定データを設定するには、ConfigMapを使用します。

たとえば、アプリケーションを開発していて、(開発用時には)自分のコンピューター上と、(実際のトラフィックをハンドルするときは)クラウド上とで実行することを想像してみてください。あなたは、DATABASE_HOSTという名前の環境変数を使用するコードを書きます。ローカルでは、この変数をlocalhostに設定します。クラウド上では、データベースコンポーネントをクラスター内に公開するKubernetesのServiceを指すように設定します。

こうすることで、必要であればクラウド上で実行しているコンテナイメージを取得することで、ローカルでも完全に同じコードを使ってデバッグができるようになります。

ConfigMapは、大量のデータを保持するようには設計されていません。ConfigMapに保存されるデータは1MiBを超えることはできません。この制限を超える設定を保存する必要がある場合は、ボリュームのマウントを検討するか、別のデータベースまたはファイルサービスを使用することを検討してください。

ConfigMapオブジェクト

ConfigMapは、他のオブジェクトが使うための設定を保存できるAPIオブジェクトです。ほとんどのKubernetesオブジェクトにspecセクションがあるのとは違い、ConfigMapにはdataおよびbinaryDataフィールドがあります。これらのフィールドは、キーとバリューのペアを値として受け入れます。dataフィールドとbinaryDataフィールドはどちらもオプションです。dataフィールドはUTF-8バイトシーケンスを含むように設計されていますが、binaryDataフィールドはバイナリデータを含むように設計されています。

ConfigMapの名前は、有効なDNSのサブドメイン名でなければなりません。

dataまたはbinaryDataフィールドの各キーは、英数字、-_、または.で構成されている必要があります。dataに格納されているキーは、binaryDataフィールドのキーと重複することはできません。

v1.19以降、ConfigMapの定義にimmutableフィールドを追加して、イミュータブルなConfigMapを作成できます。

ConfigMapとPod

ConfigMapを参照して、ConfigMap内のデータを元にしてPod内のコンテナの設定をするPodのspecを書くことができます。このとき、PodとConfigMapは同じ名前空間内に存在する必要があります。

以下に、ConfigMapの例を示します。単一の値を持つキーと、Configuration形式のデータ片のような値を持つキーがあります。

apiVersion: v1
kind: ConfigMap
metadata:
  name: game-demo
data:
  # プロパティーに似たキー。各キーは単純な値にマッピングされている
  player_initial_lives: "3"
  ui_properties_file_name: "user-interface.properties"

  # ファイルに似たキー
  game.properties: |
    enemy.types=aliens,monsters
    player.maximum-lives=5    
  user-interface.properties: |
    color.good=purple
    color.bad=yellow
    allow.textmode=true    

ConfigMapを利用してPod内のコンテナを設定する方法には、次の4種類があります。

  1. コンテナ内のコマンドと引数
  2. 環境変数をコンテナに渡す
  3. 読み取り専用のボリューム内にファイルを追加し、アプリケーションがそのファイルを読み取る
  4. Kubernetes APIを使用してConfigMapを読み込むコードを書き、そのコードをPod内で実行する

これらのさまざまな方法は、利用するデータをモデル化するのに役立ちます。最初の3つの方法では、kubeletがPodのコンテナを起動する時にConfigMapのデータを使用します。

4番目の方法では、ConfigMapとそのデータを読み込むためのコードを自分自身で書く必要があります。しかし、Kubernetes APIを直接使用するため、アプリケーションはConfigMapがいつ変更されても更新イベントを受信でき、変更が発生したときにすぐに反応できます。この手法では、Kubernetes APIに直接アクセスすることで、別の名前空間にあるConfigMapにもアクセスできます。

以下に、Podを設定するためにgame-demoから値を使用するPodの例を示します。

apiVersion: v1
kind: Pod
metadata:
  name: configmap-demo-pod
spec:
  containers:
    - name: demo
      image: alpine
      command: ["sleep", "3600"]
      env:
        # 環境変数を定義します。
        - name: PLAYER_INITIAL_LIVES # ここではConfigMap内のキーの名前とは違い
                                     # 大文字が使われていることに着目してください。
          valueFrom:
            configMapKeyRef:
              name: game-demo           # この値を取得するConfigMap。
              key: player_initial_lives # 取得するキー。
        - name: UI_PROPERTIES_FILE_NAME
          valueFrom:
            configMapKeyRef:
              name: game-demo
              key: ui_properties_file_name
      volumeMounts:
      - name: config
        mountPath: "/config"
        readOnly: true
  volumes:
    # Podレベルでボリュームを設定し、Pod内のコンテナにマウントします。
    - name: config
      configMap:
        # マウントしたいConfigMapの名前を指定します。
        name: game-demo
        # ファイルとして作成するConfigMapのキーの配列
        items:
        - key: "game.properties"
          path: "game.properties"
        - key: "user-interface.properties"
          path: "user-interface.properties"

ConfigMapは1行のプロパティの値と複数行のファイルに似た形式の値を区別しません。問題となるのは、Podや他のオブジェクトによる値の使用方法です。

この例では、ボリュームを定義して、demoコンテナの内部で/configにマウントしています。これにより、ConfigMap内には4つのキーがあるにもかかわらず、2つのファイル/config/game.propertiesおよび/config/user-interface.propertiesだけが作成されます。

これは、Podの定義がvolumesセクションでitemsという配列を指定しているためです。もしitemsの配列を完全に省略すれば、ConfigMap内の各キーがキーと同じ名前のファイルになり、4つのファイルが作成されます。

ConfigMapを使う

ConfigMapは、データボリュームとしてマウントできます。ConfigMapは、Podへ直接公開せずにシステムの他の部品として使うこともできます。たとえば、ConfigMapには、システムの他の一部が設定のために使用するデータを保存できます。

ConfigMapの最も一般的な使い方では、同じ名前空間にあるPod内で実行されているコンテナに設定を構成します。ConfigMapを独立して使用することもできます。

たとえば、ConfigMapに基づいて動作を調整するアドオンオペレーターを見かけることがあるかもしれません。

ConfigMapをPodからファイルとして使う

ConfigMapをPod内のボリュームで使用するには、次のようにします。

  1. ConfigMapを作成するか、既存のConfigMapを使用します。複数のPodから同じConfigMapを参照することもできます。
  2. Podの定義を修正して、.spec.volumes[]以下にボリュームを追加します。ボリュームに任意の名前を付け、.spec.volumes[].configMap.nameフィールドにConfigMapオブジェクトへの参照を設定します。
  3. ConfigMapが必要な各コンテナに.spec.containers[].volumeMounts[]を追加します。.spec.containers[].volumeMounts[].readOnly = trueを指定して、.spec.containers[].volumeMounts[].mountPathには、ConfigMapのデータを表示したい未使用のディレクトリ名を指定します。
  4. イメージまたはコマンドラインを修正して、プログラムがそのディレクトリ内のファイルを読み込むように設定します。ConfigMapのdataマップ内の各キーが、mountPath以下のファイル名になります。

以下は、ボリューム内にConfigMapをマウントするPodの例です。

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    configMap:
      name: myconfigmap

使用したいそれぞれのConfigMapごとに、.spec.volumes内で参照する必要があります。

Pod内に複数のコンテナが存在する場合、各コンテナにそれぞれ別のvolumeMountsのブロックが必要ですが、.spec.volumesはConfigMapごとに1つしか必要ありません。

マウントしたConfigMapの自動的な更新

ボリューム内で現在使用中のConfigMapが更新されると、射影されたキーも最終的に(eventually)更新されます。kubeletは定期的な同期のたびにマウントされたConfigMapが新しいかどうか確認します。しかし、kubeletが現在のConfigMapの値を取得するときにはローカルキャッシュを使用します。キャッシュの種類は、KubeletConfiguration構造体の中のConfigMapAndSecretChangeDetectionStrategyフィールドで設定可能です。ConfigMapは、監視(デフォルト)、ttlベース、またはすべてのリクエストを直接APIサーバーへ単純にリダイレクトする方法のいずれかによって伝搬されます。その結果、ConfigMapが更新された瞬間から、新しいキーがPodに射影されるまでの遅延の合計は、最長でkubeletの同期期間+キャッシュの伝搬遅延になります。ここで、キャッシュの伝搬遅延は選択したキャッシュの種類に依存します(監視の伝搬遅延、キャッシュのttl、または0に等しくなります)。

環境変数として使用されるConfigMapは自動的に更新されないため、ポッドを再起動する必要があります。

イミュータブルなConfigMap

FEATURE STATE: Kubernetes v1.19 [beta]

Kubernetesのベータ版の機能である イミュータブルなSecretおよびConfigMap は、個別のSecretやConfigMapをイミュータブルに設定するオプションを提供します。ConfigMapを広範に使用している(少なくとも数万のConfigMapがPodにマウントされている)クラスターでは、データの変更を防ぐことにより、以下のような利点が得られます。

  • アプリケーションの停止を引き起こす可能性のある予想外の(または望まない)変更を防ぐことができる
  • ConfigMapをイミュータブルにマークして監視を停止することにより、kube-apiserverへの負荷を大幅に削減し、クラスターの性能が向上する

この機能は、ImmutableEmphemeralVolumesフィーチャーゲートによって管理されます。immutableフィールドをtrueに設定することで、イミュータブルなConfigMapを作成できます。次に例を示します。

apiVersion: v1
kind: ConfigMap
metadata:
  ...
data:
  ...
immutable: true

一度ConfigMapがイミュータブルに設定されると、この変更を元に戻したり、dataまたはbinaryDataフィールドのコンテンツを変更することはできません。ConfigMapの削除と再作成のみ可能です。既存のPodは削除されたConfigMapのマウントポイントを保持するため、こうしたPodは再作成することをおすすめします。

次の項目

3 - Secret

KubernetesのSecretはパスワード、OAuthトークン、SSHキーのような機密情報を保存し、管理できるようにします。 Secretに機密情報を保存することは、それらをPodの定義やコンテナイメージに直接記載するより、安全で柔軟です。 詳しくはSecretの設計文書を参照してください。

Secretはパスワード、トークン、キーのような小容量の機密データを含むオブジェクトです。 他の方法としては、そのような情報はPodの定義やイメージに含めることができます。 ユーザーはSecretを作ることができ、またシステムが作るSecretもあります。

Secretの概要

Secretを使うには、PodはSecretを参照することが必要です。 PodがSecretを使う方法は3種類あります。

Secretオブジェクトの名称は正当なDNSサブドメイン名である必要があります。 シークレットの構成ファイルを作成するときに、dataおよび/またはstringDataフィールドを指定できます。dataフィールドとstringDataフィールドはオプションです。 dataフィールドのすべてのキーの値は、base64でエンコードされた文字列である必要があります。 base64文字列への変換が望ましくない場合は、代わりにstringDataフィールドを指定することを選択できます。これは任意の文字列を値として受け入れます。

datastringDataのキーは、英数字、-_、または.で構成されている必要があります。 stringDataフィールドのすべてのキーと値のペアは、内部でdataフィールドにマージされます。 キーがdataフィールドとstringDataフィールドの両方に表示される場合、stringDataフィールドで指定された値が優先されます。

Secretの種類

Secretを作成するときは、Secrettypeフィールド、または特定の同等のkubectlコマンドラインフラグ(使用可能な場合)を使用して、その型を指定できます。 Secret型は、Secret dataのプログラムによる処理を容易にするために使用されます。

Kubernetesは、いくつかの一般的な使用シナリオに対応するいくつかの組み込み型を提供します。 これらの型は、実行される検証とKubernetesが課す制約の点で異なります。

Builtin Type Usage
Opaque arbitrary user-defined data
kubernetes.io/service-account-token service account token
kubernetes.io/dockercfg serialized ~/.dockercfg file
kubernetes.io/dockerconfigjson serialized ~/.docker/config.json file
kubernetes.io/basic-auth credentials for basic authentication
kubernetes.io/ssh-auth credentials for SSH authentication
kubernetes.io/tls data for a TLS client or server
bootstrap.kubernetes.io/token bootstrap token data

Secretオブジェクトのtype値として空でない文字列を割り当てることにより、独自のSecret型を定義して使用できます。空の文字列はOpaque型として扱われます。Kubernetesは型名に制約を課しません。ただし、組み込み型の1つを使用している場合は、その型に定義されているすべての要件を満たす必要があります。

Opaque secrets

Opaqueは、Secret構成ファイルから省略された場合のデフォルトのSecret型です。 kubectlを使用してSecretを作成する場合、genericサブコマンドを使用してOpaqueSecret型を示します。 たとえば、次のコマンドは、Opaque型の空のSecretを作成します。

kubectl create secret generic empty-secret
kubectl get secret empty-secret

出力は次のようになります。

NAME           TYPE     DATA   AGE
empty-secret   Opaque   0      2m6s

DATA列には、Secretに保存されているデータ項目の数が表示されます。 この場合、0は空のSecretを作成したことを意味します。

Service account token Secrets

kubernetes.io/service-account-token型のSecretは、サービスアカウントを識別するトークンを格納するために使用されます。 このSecret型を使用する場合は、kubernetes.io/service-account.nameアノテーションが既存のサービスアカウント名に設定されていることを確認する必要があります。Kubernetesコントローラーは、kubernetes.io/service-account.uidアノテーションや実際のトークンコンテンツに設定されたdataフィールドのtokenキーなど、他のいくつかのフィールドに入力します。

apiVersion: v1
kind: Secret
metadata:
  name: secret-sa-sample
  annotations:
    kubernetes.io/service-account.name: "sa-name"
type: kubernetes.io/service-account-token
data:
  # You can include additional key value pairs as you do with Opaque Secrets
  extra: YmFyCg==

Podを作成すると、Kubernetesはservice account Secretを自動的に作成し、このSecretを使用するようにPodを自動的に変更します。service account token Secretには、APIにアクセスするための資格情報が含まれています。

API証明の自動作成と使用は、必要に応じて無効にするか、上書きすることができます。 ただし、API Serverに安全にアクセスするだけの場合は、これが推奨されるワークフローです。

ServiceAccountの動作の詳細については、ServiceAccountのドキュメントを参照してください。 PodからServiceAccountを参照する方法については、PodautomountServiceAccountTokenフィールドとserviceAccountNameフィールドを確認することもできます。

Docker config Secrets

次のtype値のいずれかを使用して、イメージのDockerレジストリにアクセスするための資格情報を格納するSecretを作成できます。

  • kubernetes.io/dockercfg
  • kubernetes.io/dockerconfigjson

kubernetes.io/dockercfg型は、Dockerコマンドラインを構成するためのレガシー形式であるシリアル化された~/.dockercfgを保存するために予約されています。 このSecret型を使用する場合は、Secretのdataフィールドに.dockercfgキーが含まれていることを確認する必要があります。このキーの値は、base64形式でエンコードされた~/.dockercfgファイルの内容です。

kubernetes.io/dockerconfigjson型は、~/.dockercfgの新しいフォーマットである~/.docker/config.jsonファイルと同じフォーマットルールに従うシリアル化されたJSONを保存するために設計されています。 このSecret型を使用する場合、Secretオブジェクトのdataフィールドには.dockerconfigjsonキーが含まれている必要があります。このキーでは、~/.docker/config.jsonファイルのコンテンツがbase64でエンコードされた文字列として提供されます。

以下は、kubernetes.io/dockercfg型のSecretの例です。

apiVersion: v1
kind: Secret
  name: secret-dockercfg
type: kubernetes.io/dockercfg
 data:
  .dockercfg: |
        "<base64 encoded ~/.dockercfg file>"
備考: base64エンコーディングを実行したくない場合は、代わりにstringDataフィールドを使用することを選択できます。

マニフェストを使用してこれらの型のSecretを作成すると、APIserverは期待されるキーがdataフィールドに存在するかどうかを確認し、提供された値を有効なJSONとして解析できるかどうかを確認します。APIサーバーは、JSONが実際にDocker configファイルであるかどうかを検証しません。

Docker configファイルがない場合、またはkubectlを使用してDockerレジストリSecretを作成する場合は、次の操作を実行できます。

kubectl create secret docker-registry secret-tiger-docker \
  --docker-username=tiger \
  --docker-password=pass113 \
  --docker-email=tiger@acme.com

このコマンドは、kubernetes.io/dockerconfigjson型のSecretを作成します。 dataフィールドから.dockerconfigjsonコンテンツをダンプすると、その場で作成された有効なDocker configである次のJSONコンテンツを取得します。

{
  "auths": {
    "https://index.docker.io/v1/": {
      "username": "tiger",
      "password": "pass113",
      "email": "tiger@acme.com",
      "auth": "dGlnZXI6cGFzczExMw=="
    }
  }
}

Basic authentication Secret

kubernetes.io/basic-auth型は、Basic認証に必要な認証を保存するために提供されています。このSecret型を使用する場合、Secretのdataフィールドには次の2つのキーが含まれている必要があります。

  • username: 認証のためのユーザー名
  • password: 認証のためのパスワードかトークン

上記の2つのキーの両方の値は、base64でエンコードされた文字列です。もちろん、Secretの作成にstringDataを使用してクリアテキストコンテンツを提供することもできます。

次のYAMLは、Basic authentication Secretの設定例です。

apiVersion: v1
kind: Secret
metadata:
  name: secret-basic-auth
type: kubernetes.io/basic-auth
stringData:
  username: admin
  password: t0p-Secret

Basic認証Secret型は、ユーザーの便宜のためにのみ提供されています。Basic認証に使用される資格情報のOpaqueを作成できます。 ただし、組み込みのSecret型を使用すると、認証の形式を統一するのに役立ち、APIserverは必要なキーがSecret configurationで提供されているかどうかを確認します。

SSH authentication secrets

組み込みのタイプkubernetes.io/ssh-authは、SSH認証で使用されるデータを保存するために提供されています。このSecret型を使用する場合、使用するSSH認証としてdata(またはstringData)フィールドにssh-privatekeyキーと値のペアを指定する必要があります。

次のYAMLはSSH authentication Secretの設定例です:

apiVersion: v1
kind: Secret
metadata:
  name: secret-ssh-auth
type: kubernetes.io/ssh-auth
data:
  # the data is abbreviated in this example
  ssh-privatekey: |
          MIIEpQIBAAKCAQEAulqb/Y ...

SSH authentication Secret型は、ユーザーの便宜のためにのみ提供されています。 SSH認証に使用される資格情報のOpaqueを作成できます。 ただし、組み込みのSecret型を使用すると、認証の形式を統一するのに役立ち、APIserverは必要なキーがSecret configurationで提供されているかどうかを確認します。

TLS secrets

Kubernetesは、TLSに通常使用される証明書とそれに関連付けられたキーを保存するための組み込みのSecret型kubernetes.io/tlsを提供します。このデータは、主にIngressリソースのTLS terminationで使用されますが、他のリソースで使用されることも、ワークロードによって直接使用されることもあります。 このSecret型を使用する場合、APIサーバーは各キーの値を実際には検証しませんが、tls.keyおよびtls.crtキーをSecret configurationのdata(またはstringData)フィールドに指定する必要があります。

次のYAMLはTLS Secretの設定例です:

apiVersion: v1
kind: Secret
metadata:
  name: secret-tls
type: kubernetes.io/tls
data:
  # the data is abbreviated in this example
  tls.crt: |
        MIIC2DCCAcCgAwIBAgIBATANBgkqh ...
  tls.key: |
        MIIEpgIBAAKCAQEA7yn3bRHQ5FHMQ ...

TLS Secret型は、ユーザーの便宜のために提供されています。 TLSサーバーやクライアントに使用される資格情報のOpaqueを作成できます。ただし、組み込みのSecret型を使用すると、プロジェクトでSecret形式の一貫性を確保できます。APIserverは、必要なキーがSecret configurationで提供されているかどうかを確認します。

kubectlを使用してTLS Secretを作成する場合、次の例に示すようにtlsサブコマンドを使用できます。

kubectl create secret tls my-tls-secret \
  --cert=path/to/cert/file \
  --key=path/to/key/file

公開鍵と秘密鍵のペアは、事前に存在している必要があります。--certの公開鍵証明書は.PEMエンコード(Base64エンコードDER形式)であり、--keyの指定された秘密鍵と一致する必要があります。 秘密鍵は、一般にPEM秘密鍵形式と呼ばれる暗号化されていない形式である必要があります。どちらの場合も、PEMの最初と最後の行(たとえば、-------- BEGIN CERTIFICATE ------------ END CERTIFICATE ----)は含まれていません

Bootstrap token Secrets

Bootstrap token Secretは、Secretのtypebootstrap.kubernetes.io/tokenに明示的に指定することで作成できます。このタイプのSecretは、ノードのブートストラッププロセス中に使用されるトークン用に設計されています。よく知られているConfigMapに署名するために使用されるトークンを格納します。

Bootstrap toke Secretは通常、kube-systemnamespaceで作成されbootstrap-token-<token-id>の形式で名前が付けられます。ここで<token-id>はトークンIDの6文字の文字列です。

Kubernetesマニフェストとして、Bootstrap token Secretは次のようになります。

apiVersion: v1
kind: Secret
metadata:
  name: bootstrap-token-5emitj
  namespace: kube-system
type: bootstrap.kubernetes.io/token
data:
  auth-extra-groups: c3lzdGVtOmJvb3RzdHJhcHBlcnM6a3ViZWFkbTpkZWZhdWx0LW5vZGUtdG9rZW4=
  expiration: MjAyMC0wOS0xM1QwNDozOToxMFo=
  token-id: NWVtaXRq
  token-secret: a3E0Z2lodnN6emduMXAwcg==
  usage-bootstrap-authentication: dHJ1ZQ==
  usage-bootstrap-signing: dHJ1ZQ==

Bootstrap type Secretには、dataで指定された次のキーがあります。

  • token_id:トークン識別子としてのランダムな6文字の文字列。必須。
  • token-secret:実際のtoken secretとしてのランダムな16文字の文字列。必須。
  • description:トークンの使用目的を説明する人間が読める文字列。オプション。
  • expiration:トークンの有効期限を指定するRFC3339を使用した絶対UTC時間。オプション。
  • usage-bootstrap-<usage>:Bootstrap tokenの追加の使用法を示すブールフラグ。
  • auth-extra-groupssystem:bootstrappersグループに加えて認証されるグループ名のコンマ区切りのリスト。

上記のYAMLは、値がすべてbase64でエンコードされた文字列であるため、分かりづらく見えるかもしれません。実際には、次のYAMLを使用して同一のSecretを作成できます。

apiVersion: v1
kind: Secret
metadata:
  # Note how the Secret is named
  name: bootstrap-token-5emitj
  # A bootstrap token Secret usually resides in the kube-system namespace
  namespace: kube-system
type: bootstrap.kubernetes.io/token
stringData:
  auth-extra-groups: "system:bootstrappers:kubeadm:default-node-token"
  expiration: "2020-09-13T04:39:10Z"
  # This token ID is used in the name
  token-id: "5emitj"
  token-secret: "kq4gihvszzgn1p0r"
  # This token can be used for authentication
  usage-bootstrap-authentication: "true"
  # and it can be used for signing
  usage-bootstrap-signing: "true"

Secretの作成

Secretを作成するには、いくつかのオプションがあります。

Secretの編集

既存のSecretは次のコマンドで編集することができます。

kubectl edit secrets mysecret

デフォルトに設定されたエディターが開かれ、dataフィールドのBase64でエンコードされたSecretの値を編集することができます。

# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm
kind: Secret
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: { ... }
  creationTimestamp: 2016-01-22T18:41:56Z
  name: mysecret
  namespace: default
  resourceVersion: "164619"
  uid: cfee02d6-c137-11e5-8d73-42010af00002
type: Opaque

Secretの使用

Podの中のコンテナがSecretを使うために、データボリュームとしてマウントしたり、環境変数として値を参照できるようにできます。 Secretは直接Podが参照できるようにはされず、システムの別の部分に使われることもあります。 例えば、Secretはあなたに代わってシステムの他の部分が外部のシステムとやりとりするために使う機密情報を保持することもあります。

SecretをファイルとしてPodから利用する

PodのボリュームとしてSecretを使うには、

  1. Secretを作成するか既存のものを使用します。複数のPodが同一のSecretを参照することができます。
  2. ボリュームを追加するため、Podの定義の.spec.volumes[]以下を書き換えます。ボリュームに命名し、.spec.volumes[].secret.secretNameフィールドはSecretオブジェクトの名称と同一にします。
  3. Secretを必要とするそれぞれのコンテナに.spec.containers[].volumeMounts[]を追加します。.spec.containers[].volumeMounts[].readOnly = trueを指定して.spec.containers[].volumeMounts[].mountPathをSecretをマウントする未使用のディレクトリ名にします。
  4. イメージやコマンドラインを変更し、プログラムがそのディレクトリを参照するようにします。連想配列dataのキーはmountPath以下のファイル名になります。

これはSecretをボリュームとしてマウントするPodの例です。

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret

使用したいSecretはそれぞれ.spec.volumesの中で参照されている必要があります。

Podに複数のコンテナがある場合、それぞれのコンテナがvolumeMountsブロックを必要としますが、.spec.volumesはSecret1つあたり1つで十分です。

多くのファイルを一つのSecretにまとめることも、多くのSecretを使うことも、便利な方を採ることができます。

Secretのキーの特定のパスへの割り当て

Secretのキーが割り当てられるパスを制御することができます。 それぞれのキーがターゲットとするパスは.spec.volumes[].secret.itemsフィールドによって指定できます。

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      items:
      - key: username
        path: my-group/my-username

次のような挙動をします。

  • username/etc/foo/usernameの代わりに/etc/foo/my-group/my-usernameの元に格納されます。
  • passwordは現れません。

.spec.volumes[].secret.itemsが使われるときは、itemsの中で指定されたキーのみが現れます。 Secretの中の全てのキーを使用したい場合は、itemsフィールドに全て列挙する必要があります。 列挙されたキーは対応するSecretに存在する必要があり、そうでなければボリュームは生成されません。

Secretファイルのパーミッション

単一のSecretキーに対して、ファイルアクセスパーミッションビットを指定することができます。 パーミッションを指定しない場合、デフォルトで0644が使われます。 Secretボリューム全体のデフォルトモードを指定し、必要に応じてキー単位で上書きすることもできます。

例えば、次のようにしてデフォルトモードを指定できます。

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      defaultMode: 0400

Secretは/etc/fooにマウントされ、Secretボリュームが生成する全てのファイルはパーミッション0400に設定されます。

JSONの仕様は8進数の記述に対応していないため、パーミッション0400を示す値として256を使用することに注意が必要です。 Podの定義にJSONではなくYAMLを使う場合は、パーミッションを指定するためにより自然な8進表記を使うことができます。

kubectl execを使ってPodに入るときは、期待したファイルモードを知るためにシンボリックリンクを辿る必要があることに注意してください。

例として、PodのSecretのファイルモードを確認します。

kubectl exec mypod -it sh

cd /etc/foo
ls -l

出力は次のようになります。

total 0
lrwxrwxrwx 1 root root 15 May 18 00:18 password -> ..data/password
lrwxrwxrwx 1 root root 15 May 18 00:18 username -> ..data/username

正しいファイルモードを知るためにシンボリックリンクを辿ります。

cd /etc/foo/..data
ls -l

出力は次のようになります。

total 8
-r-------- 1 root root 12 May 18 00:18 password
-r-------- 1 root root  5 May 18 00:18 username

前の例のようにマッピングを使い、ファイルごとに異なるパーミッションを指定することができます。

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      items:
      - key: username
        path: my-group/my-username
        mode: 0777

この例では、ファイル/etc/foo/my-group/my-usernameのパーミッションは0777になります。 JSONを使う場合は、JSONの制約により10進表記の511と記述する必要があります。

後で参照する場合、このパーミッションの値は10進表記で表示されることがあることに注意してください。

Secretの値のボリュームによる利用

Secretのボリュームがマウントされたコンテナからは、Secretのキーはファイル名として、Secretの値はBase64デコードされ、それらのファイルに格納されます。 上記の例のコンテナの中でコマンドを実行した結果を示します。

ls /etc/foo/

出力は次のようになります。

username
password
cat /etc/foo/username

出力は次のようになります。

admin
cat /etc/foo/password

出力は次のようになります。

1f2d1e2e67df

コンテナ内のプログラムはファイルからSecretの内容を読み取る責務を持ちます。

マウントされたSecretの自動更新

ボリュームとして使用されているSecretが更新されると、やがて割り当てられたキーも同様に更新されます。 kubeletは定期的な同期のたびにマウントされたSecretが新しいかどうかを確認します。 しかしながら、kubeletはSecretの現在の値の取得にローカルキャッシュを使用します。 このキャッシュはKubeletConfiguration struct内のConfigMapAndSecretChangeDetectionStrategyフィールドによって設定可能です。 Secretはwatch(デフォルト)、TTLベース、単に全てのリクエストをAPIサーバーへリダイレクトすることのいずれかによって伝搬します。 結果として、Secretが更新された時点からPodに新しいキーが反映されるまでの遅延時間の合計は、kubeletの同期間隔 + キャッシュの伝搬遅延となります。 キャッシュの遅延は、キャッシュの種別により、それぞれwatchの伝搬遅延、キャッシュのTTL、0になります。

備考: SecretをsubPathを指定してボリュームにマウントしているコンテナには、Secretの更新が反映されません。

Secretを環境変数として使用する

SecretをPodの環境変数として使用するには、

  1. Secretを作成するか既存のものを使います。複数のPodが同一のSecretを参照することができます。
  2. Podの定義を変更し、Secretを使用したいコンテナごとにSecretのキーと割り当てたい環境変数を指定します。Secretキーを利用する環境変数はenv[].valueFrom.secretKeyRefにSecretの名前とキーを指定すべきです。
  3. イメージまたはコマンドライン(もしくはその両方)を変更し、プログラムが指定した環境変数を参照するようにします。

Secretを環境変数で参照するPodの例を示します。

apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: mycontainer
    image: redis
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: username
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password
  restartPolicy: Never

環境変数からのSecretの値の利用

Secretを環境変数として利用するコンテナの内部では、Secretのキーは一般の環境変数名として現れ、値はBase64デコードされた状態で保持されます。

上記の例のコンテナの内部でコマンドを実行した結果の例を示します。

echo $SECRET_USERNAME

出力は次のようになります。

admin
echo $SECRET_PASSWORD

出力は次のようになります。

1f2d1e2e67df

Immutable Secrets

FEATURE STATE: Kubernetes v1.19 [beta]

Kubernetesベータ機能ImmutableSecrets and ConfigMapsは、個々のSecretsとConfigMapsをimutableとして設定するオプションを提供します。Secret(少なくとも数万の、SecretからPodへの一意のマウント)を広範囲に使用するクラスターの場合、データの変更を防ぐことには次の利点があります。

  • アプリケーションの停止を引き起こす可能性のある偶発的な(または不要な)更新からユーザーを保護します
  • imutableとしてマークされたSecretのウォッチを閉じることで、kube-apiserverの負荷を大幅に削減することができ、クラスターのパフォーマンスを向上させます。

この機能は、ImmutableEphemeralVolumesfeature gateによって制御されます。これは、v1.19以降デフォルトで有効になっています。immutableフィールドをtrueに設定することで、imutableのSecretを作成できます。例えば、

apiVersion: v1
kind: Secret
metadata:
  ...
data:
  ...
immutable: true
備考: SecretまたはConfigMapがimutableとしてマークされると、この変更を元に戻したり、dataフィールドの内容を変更したりすることはできません。Secretを削除して再作成することしかできません。 既存のPodは、削除されたSecretへのマウントポイントを維持します。これらのPodを再作成することをお勧めします。

imagePullSecretsを使用する

imagePullSecretsフィールドは同一のネームスペース内のSecretの参照のリストです。 kubeletにDockerやその他のイメージレジストリのパスワードを渡すために、imagePullSecretsにそれを含むSecretを指定することができます。 kubeletはこの情報をPodのためにプライベートイメージをpullするために使います。 imagePullSecretsの詳細はPodSpec APIを参照してください。

imagePullSecretを手動で指定する

ImagePullSecretsの指定の方法はコンテナイメージのドキュメントに記載されています。

imagePullSecretsが自動的にアタッチされるようにする

imagePullSecretsを手動で作成し、サービスアカウントから参照することができます。 サービスアカウントが指定されるまたはデフォルトでサービスアカウントが設定されたPodは、サービスアカウントが持つimagePullSecretsフィールドを得ます。 詳細な手順の説明はサービスアカウントへのImagePullSecretsの追加を参照してください。

手動で作成されたSecretの自動的なマウント

手動で作成されたSecret(例えばGitHubアカウントへのアクセスに使うトークンを含む)はサービスアカウントを基に自動的にアタッチすることができます。 詳細な説明はPodPresetを使ったPodへの情報の注入を参照してください。

詳細

制限事項

Secretボリュームは指定されたオブジェクト参照が実際に存在するSecretオブジェクトを指していることを保証するため検証されます。 そのため、Secretはそれを必要とするPodよりも先に作成する必要があります。

Secretリソースはnamespaceに属します。 Secretは同一のnamespaceに属するPodからのみ参照することができます。

各Secretは1MiBの容量制限があります。 これはAPIサーバーやkubeletのメモリーを枯渇するような非常に大きなSecretを作成することを避けるためです。 しかしながら、小さなSecretを多数作成することも同様にメモリーを枯渇させます。 Secretに起因するメモリー使用量をより網羅的に制限することは、将来計画されています。

kubeletがPodに対してSecretを使用するとき、APIサーバーから取得されたSecretのみをサポートします。 これにはkubectlを利用して、またはレプリケーションコントローラーによって間接的に作成されたPodが含まれます。 kubeletの--manifest-urlフラグ、--configフラグ、またはREST APIにより生成されたPodは含まれません (これらはPodを生成するための一般的な方法ではありません)。

環境変数として使われるSecretは任意と指定されていない限り、それを使用するPodよりも先に作成される必要があります。 存在しないSecretへの参照はPodの起動を妨げます。

Secretに存在しないキーへの参照(secretKeyRefフィールド)はPodの起動を妨げます。

SecretをenvFromフィールドによって環境変数へ設定する場合、環境変数の名称として不適切なキーは飛ばされます。 Podは起動することを認められます。 このとき、reasonがInvalidVariableNamesであるイベントが発生し、メッセージに飛ばされたキーのリストが含まれます。 この例では、Podは2つの不適切なキー1badkey2alsobadを含むdefault/mysecretを参照しています。

kubectl get events

出力は次のようになります。

LASTSEEN   FIRSTSEEN   COUNT     NAME            KIND      SUBOBJECT                         TYPE      REASON
0s         0s          1         dapi-test-pod   Pod                                         Warning   InvalidEnvironmentVariableNames   kubelet, 127.0.0.1      Keys [1badkey, 2alsobad] from the EnvFrom secret default/mysecret were skipped since they are considered invalid environment variable names.

SecretとPodの相互作用

Kubernetes APIがコールされてPodが生成されるとき、参照するSecretの存在は確認されません。 Podがスケジューリングされると、kubeletはSecretの値を取得しようとします。 Secretが存在しない、または一時的にAPIサーバーへの接続が途絶えたことにより取得できない場合、kubeletは定期的にリトライします。 kubeletはPodがまだ起動できない理由に関するイベントを報告します。 Secretが取得されると、kubeletはそのボリュームを作成しマウントします。 Podのボリュームが全てマウントされるまでは、Podのコンテナは起動することはありません。

ユースケース

ユースケース: コンテナの環境変数として

Secretの作成

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  USER_NAME: YWRtaW4=
  PASSWORD: MWYyZDFlMmU2N2Rm
kubectl apply -f mysecret.yaml

envFromを使ってSecretの全てのデータをコンテナの環境変数として定義します。 SecretのキーはPod内の環境変数の名称になります。

apiVersion: v1
kind: Pod
metadata:
  name: secret-test-pod
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh", "-c", "env" ]
      envFrom:
      - secretRef:
          name: mysecret
  restartPolicy: Never

ユースケース: SSH鍵を持つPod

SSH鍵を含むSecretを作成します。

kubectl create secret generic ssh-key-secret --from-file=ssh-privatekey=/path/to/.ssh/id_rsa --from-file=ssh-publickey=/path/to/.ssh/id_rsa.pub

出力は次のようになります。

secret "ssh-key-secret" created

SSH鍵を含むsecretGeneratorフィールドを持つkustomization.yamlを作成することもできます。

注意: 自身のSSH鍵を送る前に慎重に検討してください。クラスターの他のユーザーがSecretにアクセスできる可能性があります。 Kubernetesクラスターを共有しているユーザー全員がアクセスできるようにサービスアカウントを使用し、ユーザーが安全でない状態になったらアカウントを無効化することができます。

SSH鍵のSecretを参照し、ボリュームとして使用するPodを作成することができます。

apiVersion: v1
kind: Pod
metadata:
  name: secret-test-pod
  labels:
    name: secret-test
spec:
  volumes:
  - name: secret-volume
    secret:
      secretName: ssh-key-secret
  containers:
  - name: ssh-test-container
    image: mySshImage
    volumeMounts:
    - name: secret-volume
      readOnly: true
      mountPath: "/etc/secret-volume"

コンテナのコマンドを実行するときは、下記のパスにて鍵が利用可能です。

/etc/secret-volume/ssh-publickey
/etc/secret-volume/ssh-privatekey

コンテナーはSecretのデータをSSH接続を確立するために使用することができます。

ユースケース: 本番、テスト用の認証情報を持つPod

あるPodは本番の認証情報のSecretを使用し、別のPodはテスト環境の認証情報のSecretを使用する例を示します。

secretGeneratorフィールドを持つkustomization.yamlを作成するか、kubectl create secretを実行します。

kubectl create secret generic prod-db-secret --from-literal=username=produser --from-literal=password=Y4nys7f11

出力は次のようになります。

secret "prod-db-secret" created
kubectl create secret generic test-db-secret --from-literal=username=testuser --from-literal=password=iluvtests

出力は次のようになります。

secret "test-db-secret" created
備考:

$\*=!のような特殊文字はシェルに解釈されるので、エスケープする必要があります。 ほとんどのシェルではパスワードをエスケープする最も簡単な方法はシングルクォート(')で囲むことです。 例えば、実際のパスワードがS!B\*d$zDsb=だとすると、実行すべきコマンドは下記のようになります。

kubectl create secret generic dev-db-secret --from-literal=username=devuser --from-literal=password='S!B\*d$zDsb='

--from-fileによってファイルを指定する場合は、そのパスワードに含まれる特殊文字をエスケープする必要はありません。

Podを作成します。

cat <<EOF > pod.yaml
apiVersion: v1
kind: List
items:
- kind: Pod
  apiVersion: v1
  metadata:
    name: prod-db-client-pod
    labels:
      name: prod-db-client
  spec:
    volumes:
    - name: secret-volume
      secret:
        secretName: prod-db-secret
    containers:
    - name: db-client-container
      image: myClientImage
      volumeMounts:
      - name: secret-volume
        readOnly: true
        mountPath: "/etc/secret-volume"
- kind: Pod
  apiVersion: v1
  metadata:
    name: test-db-client-pod
    labels:
      name: test-db-client
  spec:
    volumes:
    - name: secret-volume
      secret:
        secretName: test-db-secret
    containers:
    - name: db-client-container
      image: myClientImage
      volumeMounts:
      - name: secret-volume
        readOnly: true
        mountPath: "/etc/secret-volume"
EOF

同じkustomization.yamlにPodを追記します。

cat <<EOF >> kustomization.yaml
resources:
- pod.yaml
EOF

下記のコマンドを実行して、APIサーバーにこれらのオブジェクト群を適用します。

kubectl apply -k .

両方のコンテナはそれぞれのファイルシステムに下記に示すファイルを持ちます。ファイルの値はそれぞれのコンテナの環境ごとに異なります。

/etc/secret-volume/username
/etc/secret-volume/password

2つのPodの仕様の差分は1つのフィールドのみである点に留意してください。 これは共通のPodテンプレートから異なる能力を持つPodを作成することを容易にします。

2つのサービスアカウントを使用すると、ベースのPod仕様をさらに単純にすることができます。

  1. prod-userprod-db-secret
  2. test-usertest-db-secret

簡略化されたPod仕様は次のようになります。

apiVersion: v1
kind: Pod
metadata:
  name: prod-db-client-pod
  labels:
    name: prod-db-client
spec:
  serviceAccount: prod-db-client
  containers:
  - name: db-client-container
    image: myClientImage

ユースケース: Secretボリューム内のdotfile

キーをドットから始めることで、データを「隠す」ことができます。 このキーはdotfileまたは「隠し」ファイルを示します。例えば、次のSecretはsecret-volumeボリュームにマウントされます。

apiVersion: v1
kind: Secret
metadata:
  name: dotfile-secret
data:
  .secret-file: dmFsdWUtMg0KDQo=
---
apiVersion: v1
kind: Pod
metadata:
  name: secret-dotfiles-pod
spec:
  volumes:
  - name: secret-volume
    secret:
      secretName: dotfile-secret
  containers:
  - name: dotfile-test-container
    image: k8s.gcr.io/busybox
    command:
    - ls
    - "-l"
    - "/etc/secret-volume"
    volumeMounts:
    - name: secret-volume
      readOnly: true
      mountPath: "/etc/secret-volume"

このボリュームは.secret-fileという単一のファイルを含み、dotfile-test-containerはこのファイルを/etc/secret-volume/.secret-fileのパスに持ちます。

備考: ドットから始まるファイルはls -lの出力では隠されるため、ディレクトリの内容を参照するときにはls -laを使わなければなりません。

ユースケース: Podの中の単一コンテナのみが参照できるSecret

HTTPリクエストを扱い、複雑なビジネスロジックを処理し、メッセージにHMACによる認証コードを付与する必要のあるプログラムを考えます。 複雑なアプリケーションロジックを持つため、サーバーにリモートのファイルを読み出せる未知の脆弱性がある可能性があり、この脆弱性は攻撃者に秘密鍵を晒してしまいます。

このプログラムは2つのコンテナに含まれる2つのプロセスへと分割することができます。 フロントエンドのコンテナはユーザーとのやりとりやビジネスロジックを扱い、秘密鍵を参照することはできません。 署名コンテナは秘密鍵を参照することができて、単にフロントエンドからの署名リクエストに応答します。例えば、localhostの通信によって行います。

この分割する手法によって、攻撃者はアプリケーションサーバーを騙して任意の処理を実行させる必要があるため、ファイルの内容を読み出すより困難になります。

ベストプラクティス

Secret APIを使用するクライアント

Secret APIとやりとりするアプリケーションをデプロイするときには、RBACのような認可ポリシーを使用して、アクセスを制限すべきです。 Secretは様々な種類の重要な値を保持することが多く、サービスアカウントのトークンのようにKubernetes内部や、外部のシステムで昇格できるものも多くあります。個々のアプリケーションが、Secretの能力について推論することができたとしても、同じネームスペースの別のアプリケーションがその推定を覆すこともあります。

これらの理由により、ネームスペース内のSecretに対するwatchlistリクエストはかなり強力な能力であり、避けるべきです。Secretのリストを取得することはクライアントにネームスペース内の全てのSecretの値を調べさせることを認めるからです。クラスター内の全てのSecretに対するwatchlist権限は最も特権的な、システムレベルのコンポーネントに限って認めるべきです。

Secret APIへのアクセスが必要なアプリケーションは、必要なSecretに対するgetリクエストを発行すべきです。管理者は全てのSecretに対するアクセスは制限しつつ、アプリケーションが必要とする個々のインスタンスに対するアクセス許可を与えることができます。

getリクエストの繰り返しに対するパフォーマンスを向上するために、クライアントはSecretを参照するリソースを設計し、それをwatchして、参照が変更されたときにSecretを再度リクエストすることができます。加えて、個々のリソースをwatchすることのできる"bulk watch" APIが提案されており、将来のKubernetesリリースにて利用可能になる可能性があります。

セキュリティ特性

保護

Secretはそれを使用するPodとは独立に作成されるので、Podを作ったり、参照したり、編集したりするワークフローにおいてSecretが晒されるリスクは軽減されています。 システムは、可能であればSecretの内容をディスクに書き込まないような、Secretについて追加の考慮も行っています。

Secretはノード上のPodが必要とした場合のみ送られます。 kubeletはSecretがディスクストレージに書き込まれないよう、tmpfsに保存します。 Secretを必要とするPodが削除されると、kubeletはSecretのローカルコピーも同様に削除します。

同一のノードにいくつかのPodに対する複数のSecretが存在することもあります。 しかし、コンテナから参照できるのはPodが要求したSecretのみです。 そのため、あるPodが他のPodのためのSecretにアクセスすることはできません。

Podに複数のコンテナが含まれることもあります。しかし、Podの各コンテナはコンテナ内からSecretを参照するためにvolumeMountsによってSecretボリュームを要求する必要があります。 これはPodレベルでのセキュリティ分離を実装するのに便利です。

ほとんどのKubernetesディストリビューションにおいては、ユーザーとAPIサーバー間やAPIサーバーからkubelet間の通信はSSL/TLSで保護されています。 そのような経路で伝送される場合、Secretは保護されています。

FEATURE STATE: Kubernetes v1.13 [beta]

保存データの暗号化を有効にして、Secretがetcdに平文で保存されないようにすることができます。

リスク

  • APIサーバーでは、機密情報はetcdに保存されます。 そのため、
    • 管理者はクラスターデータの保存データの暗号化を有効にすべきです(v1.13以降が必要)。
    • 管理者はetcdへのアクセスを管理ユーザに限定すべきです。
    • 管理者はetcdで使用していたディスクを使用しなくなったときにはそれをワイプするか完全消去したくなるでしょう。
    • クラスターの中でetcdが動いている場合、管理者はetcdのピアツーピア通信がSSL/TLSを利用していることを確認すべきです。
  • Secretをマニフェストファイル(JSONまたはYAML)を介して設定する場合、それはBase64エンコードされた機密情報を含んでいるので、ファイルを共有したりソースリポジトリに入れることは秘密が侵害されることを意味します。Base64エンコーディングは暗号化手段では なく 、平文と同様であると判断すべきです。
  • アプリケーションはボリュームからSecretの値を読み取った後も、その値を保護する必要があります。例えば意図せずログに出力する、信用できない相手に送信するようなことがないようにです。
  • Secretを利用するPodを作成できるユーザーはSecretの値を見ることができます。たとえAPIサーバーのポリシーがユーザーにSecretの読み取りを許可していなくても、ユーザーはSecretを晒すPodを実行することができます。
  • 現在、任意のノードでルート権限を持つ人は誰でも、kubeletに偽装することで 任意の SecretをAPIサーバーから読み取ることができます。 単一のノードのルート権限を不正に取得された場合の影響を抑えるため、実際に必要としているノードに対してのみSecretを送る機能が計画されています。

次の項目

4 - コンテナのリソース管理

Podを指定する際に、コンテナが必要とする各リソースの量をオプションで指定することができます。 指定する最も一般的なリソースはCPUとメモリ(RAM)ですが、他にもあります。

Pod内のコンテナのリソース要求を指定すると、スケジューラはこの情報を使用して、どのNodeにPodを配置するかを決定します。コンテナに制限ソースを指定すると、kubeletはその制限を適用し、実行中のコンテナが設定した制限を超えてリソースを使用することができないようにします。また、kubeletは、少なくともそのシステムリソースのうち、要求の量を、そのコンテナが使用するために特別に確保します。

要求と制限

Podが動作しているNodeに利用可能なリソースが十分にある場合、そのリソースの要求が指定するよりも多くのリソースをコンテナが使用することが許可されます ただし、コンテナはそのリソースの制限を超えて使用することはできません。

たとえば、コンテナに256MiBのメモリー要求を設定し、そのコンテナが8GiBのメモリーを持つNodeにスケジュールされたPod内に存在し、他のPodが存在しない場合、コンテナはより多くのRAMを使用しようとする可能性があります。

そのコンテナに4GiBのメモリー制限を設定すると、kubelet(およびコンテナランタイム) が制限を適用します。ランタイムは、コンテナーが設定済みのリソース制限を超えて使用するのを防ぎます。例えば、コンテナ内のプロセスが、許容量を超えるメモリを消費しようとすると、システムカーネルは、メモリ不足(OOM)エラーで、割り当てを試みたプロセスを終了します。

制限は、違反が検出されるとシステムが介入するように事後的に、またはコンテナーが制限を超えないようにシステムが防ぐように強制的に、実装できます。 異なるランタイムは、同じ制限を実装するために異なる方法をとることができます。

備考: コンテナが自身のメモリー制限を指定しているが、メモリー要求を指定していない場合、Kubernetesは制限に一致するメモリー要求を自動的に割り当てます。同様に、コンテナが自身のCPU制限を指定しているが、CPU要求を指定していない場合、Kubernetesは制限に一致するCPU要求を自動的に割り当てます。

リソースタイプ

CPUメモリーはいずれもリソースタイプです。リソースタイプには基本単位があります。 CPUは計算処理を表し、Kubernetes CPUsの単位で指定されます。 メモリはバイト単位で指定されます。 Kubernetes v1.14以降を使用している場合は、huge pageリソースを指定することができます。 Huge PageはLinux固有の機能であり、Nodeのカーネルはデフォルトのページサイズよりもはるかに大きいメモリブロックを割り当てます。

たとえば、デフォルトのページサイズが4KiBのシステムでは、hugepages-2Mi: 80Miという制限を指定できます。 コンテナが40を超える2MiBの巨大ページ(合計80 MiB)を割り当てようとすると、その割り当ては失敗します。

備考: hugepages-*リソースをオーバーコミットすることはできません。 これはmemorycpuリソースとは異なります。

CPUとメモリーは、まとめてコンピュートリソースまたは単にリソースと呼ばれます。 コンピューティングリソースは、要求され、割り当てられ、消費され得る測定可能な量です。 それらはAPI resourcesとは異なります。 PodやServicesなどのAPIリソースは、Kubernetes APIサーバーを介して読み取りおよび変更できるオブジェクトです。

Podとコンテナのリソース要求と制限

Podの各コンテナは、次の1つ以上を指定できます。

  • spec.containers[].resources.limits.cpu
  • spec.containers[].resources.limits.memory
  • spec.containers[].resources.limits.hugepages-<size>
  • spec.containers[].resources.requests.cpu
  • spec.containers[].resources.requests.memory
  • spec.containers[].resources.requests.hugepages-<size>

要求と制限はそれぞれのコンテナでのみ指定できますが、このPodリソースの要求と制限の関係性について理解すると便利です。 特定のリソースタイプのPodリソース要求/制限は、Pod内の各コンテナに対するそのタイプのリソース要求/制限の合計です。

Kubernetesにおけるリソースの単位

CPUの意味

CPUリソースの制限と要求は、cpu単位で測定されます。 Kuberenetesにおける1つのCPUは、クラウドプロバイダーの1 vCPU/コアおよびベアメタルのインテルプロセッサーの1 ハイパースレッドに相当します。

要求を少数で指定することもできます。 spec.containers[].resources.requests.cpu0.5のコンテナは、1CPUを要求するコンテナの半分のCPUが保証されます。 0.1という表現は100mという表現と同等であり、100ミリCPUと読み替えることができます。 100ミリコアという表現も、同じことを意味しています。 0.1のような小数点のある要求はAPIによって100mに変換され、1mより細かい精度は許可されません。 このため、100mの形式が推奨されます。

CPUは常に相対量としてではなく、絶対量として要求されます。 0.1は、シングルコア、デュアルコア、あるいは48コアマシンのどのCPUに対してでも、同一の量を要求します。

メモリーの意味

メモリーの制限と要求はバイト単位で測定されます。 E、P、T、G、M、Kのいずれかのサフィックスを使用して、メモリーを整数または固定小数点数として表すことができます。 また、Ei、Pi、Ti、Gi、Mi、Kiのような2の累乗の値を使用することもできます。 たとえば、以下はほぼ同じ値を表しています。

128974848, 129e6, 129M, 123Mi

例を見てみましょう。 次のPodには2つのコンテナがあります。 各コンテナには、0.25cpuおよび64MiB(226バイト)のメモリー要求と、0.5cpuおよび128MiBのメモリー制限があります Podには0.5cpuと128MiBのメモリー要求があり、1cpuと256MiBのメモリ制限があると言えます。

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: app
    image: images.my-company.example/app:v4
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: log-aggregator
    image: images.my-company.example/log-aggregator:v6
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

リソース要求を含むPodがどのようにスケジュールされるか

Podを作成すると、KubernetesスケジューラーはPodを実行するNodeを選択します。 各Nodeには、リソースタイプごとに最大容量があります。それは、Podに提供できるCPUとメモリの量です。 スケジューラーは、リソースタイプごとに、スケジュールされたコンテナのリソース要求の合計がNodeの容量より少ないことを確認します。 Node上の実際のメモリーまたはCPUリソースの使用率は非常に低いですが、容量チェックが失敗した場合、スケジューラーはNodeにPodを配置しないことに注意してください。 これにより、例えば日々のリソース要求のピーク時など、リソース利用が増加したときに、Nodeのリソース不足から保護されます。

リソース制限のあるPodがどのように実行されるか

kubeletがPodのコンテナを開始すると、CPUとメモリーの制限がコンテナランタイムに渡されます。

Dockerを使用する場合:

  • spec.containers[].resources.requests.cpuは、潜在的に小数であるコア値に変換され、1024倍されます。 docker runコマンドの--cpu-sharesフラグの値は、この数値と2のいずれか大きい方が用いられます。

  • spec.containers[].resources.limits.cpuはミリコアの値に変換され、100倍されます。 結果の値は、コンテナが100ミリ秒ごとに使用できるCPU時間の合計です。 コンテナは、この間隔の間、CPU時間の占有率を超えて使用することはできません。

    備考: デフォルトのクォータ期間は100ミリ秒です。 CPUクォータの最小分解能は1ミリ秒です。
  • spec.containers[].resources.limits.memoryは整数に変換され、docker runコマンドの--memoryフラグの値として使用されます。

コンテナがメモリー制限を超過すると、終了する場合があります。 コンテナが再起動可能である場合、kubeletは他のタイプのランタイム障害と同様にコンテナを再起動します。

コンテナがメモリー要求を超過すると、Nodeのメモリーが不足するたびにそのPodが排出される可能性があります。

コンテナは、長時間にわたってCPU制限を超えることが許可される場合と許可されない場合があります。 ただし、CPUの使用量が多すぎるために、コンテナが強制終了されることはありません。

コンテナをスケジュールできないか、リソース制限が原因で強制終了されているかどうかを確認するには、トラブルシューティングのセクションを参照してください。

コンピュートリソースとメモリーリソースの使用量を監視する

Podのリソース使用量は、Podのステータスの一部として報告されます。

オプションの監視ツールがクラスターにおいて利用可能な場合、Podのリソース使用量はメトリクスAPIから直接、もしくは監視ツールから取得できます。

ローカルのエフェメラルストレージ

FEATURE STATE: Kubernetes v1.10 [beta]

Nodeには、ローカルに接続された書き込み可能なデバイス、または場合によってはRAMによってサポートされるローカルのエフェメラルストレージがあります。 "エフェメラル"とは、耐久性について長期的な保証がないことを意味します。

Podは、スクラッチ領域、キャッシュ、ログ用にエフェメラルなローカルストレージを使用しています。 kubeletは、ローカルのエフェメラルストレージを使用して、Podにスクラッチ領域を提供し、emptyDir ボリュームをコンテナにマウントできます。

また、kubeletはこの種類のストレージを使用して、Nodeレベルのコンテナログ、コンテナイメージ、実行中のコンテナの書き込み可能なレイヤーを保持します。

注意: Nodeに障害が発生すると、そのエフェメラルストレージ内のデータが失われる可能性があります。 アプリケーションは、ローカルのエフェメラルストレージにパフォーマンスのサービス品質保証(ディスクのIOPSなど)を期待することはできません。

ベータ版の機能として、Kubernetesでは、Podが消費するローカルのエフェメラルストレージの量を追跡、予約、制限することができます。

ローカルエフェメラルストレージの設定

Kubernetesは、Node上のローカルエフェメラルストレージを構成する2つの方法をサポートしています。

この構成では、さまざまな種類のローカルのエフェメラルデータ(emptyDirボリュームや、書き込み可能なレイヤー、コンテナイメージ、ログなど)をすべて1つのファイルシステムに配置します。 kubeletを構成する最も効果的な方法は、このファイルシステムをKubernetes(kubelet)データ専用にすることです。

kubeletはNodeレベルのコンテナログも書き込み、これらをエフェメラルなローカルストレージと同様に扱います。

kubeletは、設定されたログディレクトリ(デフォルトでは/var/log)内のファイルにログを書き出し、ローカルに保存された他のデータのベースディレクトリ(デフォルトでは/var/lib/kubelet)を持ちます。

通常、/var/lib/kubelet/var/logはどちらもシステムルートファイルシステムにあり、kubeletはそのレイアウトを考慮して設計されています。

Nodeには、Kubernetesに使用されていない他のファイルシステムを好きなだけ持つことができます。

Node上にファイルシステムがありますが、このファイルシステムは、ログやemptyDirボリュームなど、実行中のPodの一時的なデータに使用されます。 このファイルシステムは、例えばKubernetesに関連しないシステムログなどの他のデータに使用することができ、ルートファイルシステムとすることさえ可能です。

また、kubeletはノードレベルのコンテナログを最初のファイルシステムに書き込み、これらをエフェメラルなローカルストレージと同様に扱います。

また、別の論理ストレージデバイスでバックアップされた別のファイルシステムを使用することもできます。 この設定では、コンテナイメージレイヤーと書き込み可能なレイヤーを配置するようにkubeletに指示するディレクトリは、この2番目のファイルシステム上にあります。

最初のファイルシステムは、コンテナイメージレイヤーや書き込み可能なレイヤーを保持していません。

Nodeには、Kubernetesに使用されていない他のファイルシステムを好きなだけ持つことができます。

kubeletは、ローカルストレージの使用量を測定できます。 これは、以下の条件で提供されます。

  • LocalStorageCapacityIsolationフィーチャーゲートが有効になっています。(デフォルトでオンになっています。)
  • そして、ローカルのエフェメラルストレージ用にサポートされている構成の1つを使用してNodeをセットアップします。

別の構成を使用している場合、kubeletはローカルのエフェメラルストレージにリソース制限を適用しません。

備考: kubeletは、tmpfsのemptyDirボリュームをローカルのエフェメラルストレージとしてではなく、コンテナメモリーとして追跡します。

ローカルのエフェメラルストレージの要求と制限設定

ローカルのエフェメラルストレージを管理するためには ephemeral-storage パラメーターを利用することができます。 Podの各コンテナは、次の1つ以上を指定できます。

  • spec.containers[].resources.limits.ephemeral-storage
  • spec.containers[].resources.requests.ephemeral-storage

ephemeral-storageの制限と要求はバイト単位で記します。 ストレージは、次のいずれかの接尾辞を使用して、通常の整数または固定小数点数として表すことができます。 E、P、T、G、M、K。Ei、Pi、Ti、Gi、Mi、Kiの2のべき乗を使用することもできます。 たとえば、以下はほぼ同じ値を表しています。

128974848, 129e6, 129M, 123Mi

次の例では、Podに2つのコンテナがあります。 各コンテナには、2GiBのローカルのエフェメラルストレージ要求があります。 各コンテナには、4GiBのローカルのエフェメラルストレージ制限があります。 したがって、Podには4GiBのローカルのエフェメラルストレージの要求と、8GiBのローカルのエフェメラルストレージ制限があります。

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: app
    image: images.my-company.example/app:v4
    resources:
      requests:
        ephemeral-storage: "2Gi"
      limits:
        ephemeral-storage: "4Gi"
  - name: log-aggregator
    image: images.my-company.example/log-aggregator:v6
    resources:
      requests:
        ephemeral-storage: "2Gi"
      limits:
        ephemeral-storage: "4Gi"

エフェメラルストレージを要求するPodのスケジュール方法

Podを作成すると、KubernetesスケジューラーはPodを実行するNodeを選択します。 各Nodeには、Podに提供できるローカルのエフェメラルストレージの上限があります。 詳細については、Node割り当て可能を参照してください。

スケジューラーは、スケジュールされたコンテナのリソース要求の合計がNodeの容量より少なくなるようにします。

エフェメラルストレージの消費管理

kubeletがローカルのエフェメラルストレージをリソースとして管理している場合、kubeletはストレージの使用量を測定します

  • tmpfsemptyDirボリュームを除くemptyDirボリューム
  • Nodeレベルのログを保持するディレクトリ
  • 書き込み可能なコンテナレイヤー

Podが許可するよりも多くのエフェメラルストレージを使用している場合、kubeletはPodの排出をトリガーするシグナルを設定します。

コンテナレベルの分離の場合、コンテナの書き込み可能なレイヤーとログ使用量がストレージの制限を超えると、kubeletはPodに排出のマークを付けます。

Podレベルの分離の場合、kubeletはPod内のコンテナの制限を合計し、Podの全体的なストレージ制限を計算します。 このケースでは、すべてのコンテナからのローカルのエフェメラルストレージの使用量とPodのemptyDirボリュームの合計がPod全体のストレージ制限を超過する場合、 kubeletはPodをまた排出対象としてマークします。

注意:

kubeletがローカルのエフェメラルストレージを測定していない場合、ローカルストレージの制限を超えるPodは、ローカルストレージのリソース制限に違反しても排出されません。

ただし、書き込み可能なコンテナレイヤー、Nodeレベルのログ、またはemptyDirボリュームのファイルシステムスペースが少なくなると、Nodeはローカルストレージが不足していると汚染taintsし、この汚染は、汚染を特に許容しないPodの排出をトリガーします。

ローカルのエフェメラルストレージについては、サポートされている設定をご覧ください。

kubeletはPodストレージの使用状況を測定するさまざまな方法をサポートしています

kubeletは、emptyDirボリューム、コンテナログディレクトリ、書き込み可能なコンテナレイヤーをスキャンする定期的なスケジュールチェックを実行します。

スキャンは、使用されているスペースの量を測定します。

備考:

このモードでは、kubeletは削除されたファイルのために、開いているファイルディスクリプタを追跡しません。

あなた(またはコンテナ)がemptyDirボリューム内にファイルを作成した後、何かがそのファイルを開き、そのファイルが開かれたままの状態でファイルを削除した場合、削除されたファイルのinodeはそのファイルを閉じるまで残りますが、kubeletはそのスペースを使用中として分類しません。

FEATURE STATE: Kubernetes v1.15 [alpha]

プロジェクトクォータは、ファイルシステム上のストレージ使用量を管理するためのオペレーティングシステムレベルの機能です。 Kubernetesでは、プロジェクトクォータを有効にしてストレージの使用状況を監視することができます。 ノード上のemptyDirボリュームをバックアップしているファイルシステムがプロジェクトクォータをサポートしていることを確認してください。 例えば、XFSやext4fsはプロジェクトクォータを提供しています。

備考: プロジェクトクォータはストレージの使用状況を監視しますが、制限を強制するものではありません。

Kubernetesでは、1048576から始まるプロジェクトIDを使用します。 使用するプロジェクトIDは/etc/projects/etc/projidに登録されます。 この範囲のプロジェクトIDをシステム上で別の目的で使用する場合は、それらのプロジェクトIDを/etc/projects/etc/projidに登録し、 Kubernetesが使用しないようにする必要があります。

クォータはディレクトリスキャンよりも高速で正確です。 ディレクトリがプロジェクトに割り当てられると、ディレクトリ配下に作成されたファイルはすべてそのプロジェクト内に作成され、カーネルはそのプロジェクト内のファイルによって使用されているブロックの数を追跡するだけです。 ファイルが作成されて削除されても、開いているファイルディスクリプタがあれば、スペースを消費し続けます。 クォータトラッキングはそのスペースを正確に記録しますが、ディレクトリスキャンは削除されたファイルが使用するストレージを見落としてしまいます。

プロジェクトクォータを使用する場合は、次のことを行う必要があります。

  • kubelet設定で、LocalocalStorpactionCapactionIsolationFSQuotaMonitoring=trueフィーチャーゲートを有効にします。

  • ルートファイルシステム(またはオプションのランタイムファイルシステム))がプロジェクトクォータを有効にしていることを確認してください。 すべてのXFSファイルシステムはプロジェクトクォータをサポートしています。 ext4ファイルシステムでは、ファイルシステムがマウントされていない間は、プロジェクトクォータ追跡機能を有効にする必要があります。

    # ext4の場合、/dev/block-deviceがマウントされていません
    sudo tune2fs -O project -Q prjquota /dev/block-device
    
  • ルートファイルシステム(またはオプションのランタイムファイルシステム)がプロジェクトクォータを有効にしてマウントされていることを確認してください。 XFSとext4fsの両方で、マウントオプションはprjquotaという名前になっています。

拡張リソース

拡張リソースはkubernetes.ioドメインの外で完全に修飾されたリソース名です。 これにより、クラスタオペレータはKubernetesに組み込まれていないリソースをアドバタイズし、ユーザはそれを利用することができるようになります。

拡張リソースを使用するためには、2つのステップが必要です。 第一に、クラスタオペレーターは拡張リソースをアドバタイズする必要があります。 第二に、ユーザーはPodで拡張リソースを要求する必要があります。

拡張リソースの管理

Nodeレベルの拡張リソース

Nodeレベルの拡張リソースはNodeに関連付けられています。

デバイスプラグイン管理のリソース

各Nodeにデバイスプラグインで管理されているリソースをアドバタイズする方法については、デバイスプラグインを参照してください。

その他のリソース

新しいNodeレベルの拡張リソースをアドバタイズするには、クラスタオペレータはAPIサーバにPATCHHTTPリクエストを送信し、クラスタ内のNodeのstatus.capacityに利用可能な量を指定します。 この操作の後、ノードのstatus.capacityには新しいリソースが含まれます。 status.allocatableフィールドは、kubeletによって非同期的に新しいリソースで自動的に更新されます。 スケジューラはPodの適合性を評価する際にNodeのstatus.allocatable値を使用するため、Nodeの容量に新しいリソースを追加してから、そのNodeでリソースのスケジューリングを要求する最初のPodが現れるまでには、短い遅延が生じる可能性があることに注意してください。

例:

以下は、curlを使用して、Masterがk8s-masterであるNodek8s-node-1で5つのexample.com/fooリソースを示すHTTPリクエストを作成する方法を示す例です。

curl --header "Content-Type: application/json-patch+json" \
--request PATCH \
--data '[{"op": "add", "path": "/status/capacity/example.com~1foo", "value": "5"}]' \
http://k8s-master:8080/api/v1/nodes/k8s-node-1/status
備考: 上記のリクエストでは、~1はパッチパス内の文字/のエンコーディングです。 JSON-Patchの操作パス値は、JSON-Pointerとして解釈されます。 詳細については、IETF RFC 6901, section 3を参照してください。

クラスターレベルの拡張リソース

クラスターレベルの拡張リソースはノードに関連付けられていません。 これらは通常、リソース消費とリソースクォータを処理するスケジューラー拡張機能によって管理されます。

スケジューラーポリシー構成では。スケジューラー拡張機能によって扱われる拡張リソースを指定できます。

例:

次のスケジューラーポリシーの構成は、クラスターレベルの拡張リソース"example.com/foo"がスケジューラー拡張機能によって処理されることを示しています。

  • スケジューラーは、Podが"example.com/foo"を要求した場合にのみ、Podをスケジューラー拡張機能に送信します。
  • ignoredBySchedulerフィールドは、スケジューラがそのPodFitsResources述語で"example.com/foo"リソースをチェックしないことを指定します。
{
  "kind": "Policy",
  "apiVersion": "v1",
  "extenders": [
    {
      "urlPrefix":"<extender-endpoint>",
      "bindVerb": "bind",
      "managedResources": [
        {
          "name": "example.com/foo",
          "ignoredByScheduler": true
        }
      ]
    }
  ]
}

拡張リソースの消費

ユーザーは、CPUやメモリのようにPodのスペックで拡張されたリソースを消費できます。 利用可能な量以上のリソースが同時にPodに割り当てられないように、スケジューラーがリソースアカウンティングを行います。

APIサーバーは、拡張リソースの量を整数の値で制限します。 有効な数量の例は、33000m3Kiです。 無効な数量の例は、0.51500mです。

備考: 拡張リソースは不透明な整数リソースを置き換えます。 ユーザーは、予約済みのkubernetes.io以外のドメイン名プレフィックスを使用できます。

Podで拡張リソースを消費するには、コンテナ名のspec.containers[].resources.limitsマップにキーとしてリソース名を含めます。

備考: 拡張リソースはオーバーコミットできないので、コンテナスペックに要求と制限の両方が存在する場合は等しくなければなりません。

Podは、CPU、メモリ、拡張リソースを含むすべてのリソース要求が満たされた場合にのみスケジュールされます。 リソース要求が満たされない限り、PodはPENDING状態のままです。

例:

下のPodはCPUを2つ、"example.com/foo"(拡張リソース)を1つ要求しています。

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - name: my-container
    image: myimage
    resources:
      requests:
        cpu: 2
        example.com/foo: 1
      limits:
        example.com/foo: 1

トラブルシューティング

failedSchedulingイベントメッセージが表示され、Podが保留中になる

スケジューラーがPodが収容されるNodeを見つけられない場合、場所が見つかるまでPodはスケジュールされないままになります。 スケジューラーがPodの場所を見つけられないたびに、次のようなイベントが生成されます。

kubectl describe pod frontend | grep -A 3 Events
Events:
  FirstSeen LastSeen   Count  From          Subobject   PathReason      Message
  36s   5s     6      {scheduler }              FailedScheduling  Failed for reason PodExceedsFreeCPU and possibly others

前述の例では、"frontend"という名前のPodは、Node上のCPUリソースが不足しているためにスケジューリングに失敗しています。 同様のエラーメッセージは、メモリー不足による失敗を示唆することもあります(PodExceedsFreeMemory)。 一般的に、このタイプのメッセージでPodが保留されている場合は、いくつか試すべきことがあります。

  • クラスタにNodeを追加します。
  • 不要なポッドを終了して、保留中のPodのためのスペースを空けます。
  • PodがすべてのNodeよりも大きくないことを確認してください。 例えば、すべてのNodeがcpu: 1の容量を持っている場合、cpu: 1.1を要求するPodは決してスケジューリングされません。

Nodeの容量や割り当て量はkubectl describe nodesコマンドで調べることができる。 例えば、以下のようになる。

kubectl describe nodes e2e-test-node-pool-4lw4
Name:            e2e-test-node-pool-4lw4
[ ... lines removed for clarity ...]
Capacity:
 cpu:                               2
 memory:                            7679792Ki
 pods:                              110
Allocatable:
 cpu:                               1800m
 memory:                            7474992Ki
 pods:                              110
[ ... lines removed for clarity ...]
Non-terminated Pods:        (5 in total)
  Namespace    Name                                  CPU Requests  CPU Limits  Memory Requests  Memory Limits
  ---------    ----                                  ------------  ----------  ---------------  -------------
  kube-system  fluentd-gcp-v1.38-28bv1               100m (5%)     0 (0%)      200Mi (2%)       200Mi (2%)
  kube-system  kube-dns-3297075139-61lj3             260m (13%)    0 (0%)      100Mi (1%)       170Mi (2%)
  kube-system  kube-proxy-e2e-test-...               100m (5%)     0 (0%)      0 (0%)           0 (0%)
  kube-system  monitoring-influxdb-grafana-v4-z1m12  200m (10%)    200m (10%)  600Mi (8%)       600Mi (8%)
  kube-system  node-problem-detector-v0.1-fj7m3      20m (1%)      200m (10%)  20Mi (0%)        100Mi (1%)
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  CPU Requests    CPU Limits    Memory Requests    Memory Limits
  ------------    ----------    ---------------    -------------
  680m (34%)      400m (20%)    920Mi (12%)        1070Mi (14%)

前述の出力では、Podが1120m以上のCPUや6.23Gi以上のメモリーを要求した場合、そのPodはNodeに収まらないことがわかります。

Podsセクションを見れば、どのPodがNode上でスペースを占有しているかがわかります。

システムデーモンが利用可能なリソースの一部を使用しているため、Podに利用可能なリソースの量はNodeの容量よりも少なくなっています。 allocatableフィールドNodeStatusは、Podに利用可能なリソースの量を与えます。 詳細については、ノード割り当て可能なリソースを参照してください。

リソースクォータ機能は、消費できるリソースの総量を制限するように設定することができます。 名前空間と組み合わせて使用すると、1つのチームがすべてのリソースを占有するのを防ぐことができます。

コンテナが終了した

コンテナはリソース不足のため、終了する可能性があります。 コンテナがリソース制限に達したために強制終了されているかどうかを確認するには、対象のPodでkubectl describe podを呼び出します。

kubectl describe pod simmemleak-hra99
Name:                           simmemleak-hra99
Namespace:                      default
Image(s):                       saadali/simmemleak
Node:                           kubernetes-node-tf0f/10.240.216.66
Labels:                         name=simmemleak
Status:                         Running
Reason:
Message:
IP:                             10.244.2.75
Replication Controllers:        simmemleak (1/1 replicas created)
Containers:
  simmemleak:
    Image:  saadali/simmemleak
    Limits:
      cpu:                      100m
      memory:                   50Mi
    State:                      Running
      Started:                  Tue, 07 Jul 2015 12:54:41 -0700
    Last Termination State:     Terminated
      Exit Code:                1
      Started:                  Fri, 07 Jul 2015 12:54:30 -0700
      Finished:                 Fri, 07 Jul 2015 12:54:33 -0700
    Ready:                      False
    Restart Count:              5
Conditions:
  Type      Status
  Ready     False
Events:
  FirstSeen                         LastSeen                         Count  From                              SubobjectPath                       Reason      Message
  Tue, 07 Jul 2015 12:53:51 -0700   Tue, 07 Jul 2015 12:53:51 -0700  1      {scheduler }                                                          scheduled   Successfully assigned simmemleak-hra99 to kubernetes-node-tf0f
  Tue, 07 Jul 2015 12:53:51 -0700   Tue, 07 Jul 2015 12:53:51 -0700  1      {kubelet kubernetes-node-tf0f}    implicitly required container POD   pulled      Pod container image "k8s.gcr.io/pause:0.8.0" already present on machine
  Tue, 07 Jul 2015 12:53:51 -0700   Tue, 07 Jul 2015 12:53:51 -0700  1      {kubelet kubernetes-node-tf0f}    implicitly required container POD   created     Created with docker id 6a41280f516d
  Tue, 07 Jul 2015 12:53:51 -0700   Tue, 07 Jul 2015 12:53:51 -0700  1      {kubelet kubernetes-node-tf0f}    implicitly required container POD   started     Started with docker id 6a41280f516d
  Tue, 07 Jul 2015 12:53:51 -0700   Tue, 07 Jul 2015 12:53:51 -0700  1      {kubelet kubernetes-node-tf0f}    spec.containers{simmemleak}         created     Created with docker id 87348f12526a

上記の例では、Restart Count:5はPodのsimmemleakコンテナが終了して、5回再起動したことを示しています。

-o go-template=...オプションを指定して、kubectl get podを呼び出し、以前に終了したコンテナのステータスを取得できます。

kubectl get pod -o go-template='{{range.status.containerStatuses}}{{"Container Name: "}}{{.name}}{{"\r\nLastState: "}}{{.lastState}}{{end}}'  simmemleak-hra99
Container Name: simmemleak
LastState: map[terminated:map[exitCode:137 reason:OOM Killed startedAt:2015-07-07T20:58:43Z finishedAt:2015-07-07T20:58:43Z containerID:docker://0e4095bba1feccdfe7ef9fb6ebffe972b4b14285d5acdec6f0d3ae8a22fad8b2]]

reason:OOM Killedが原因でコンテナが終了したことがわかります。OOMはメモリー不足を表します。

次の項目

5 - kubeconfigファイルを使用してクラスターアクセスを組織する

kubeconfigを使用すると、クラスターに、ユーザー、名前空間、認証の仕組みに関する情報を組織できます。kubectlコマンドラインツールはkubeconfigファイルを使用してクラスターを選択するために必要な情報を見つけ、クラスターのAPIサーバーと通信します。

備考: クラスターへのアクセスを設定するために使われるファイルはkubeconfigファイルと呼ばれます。これは設定ファイルを指すために使われる一般的な方法です。kubeconfigという名前を持つファイルが存在するという意味ではありません。

デフォルトでは、kubectl$HOME/.kubeディレクトリ内にあるconfigという名前のファイルを探します。KUBECONFIG環境変数を設定するか、--kubeconfigフラグで指定することで、別のkubeconfigファイルを指定することもできます。

kubeconfigファイルの作成と指定に関するステップバイステップの手順を知りたいときは、複数のクラスターへのアクセスを設定するを参照してください。

複数のクラスター、ユーザ、認証の仕組みのサポート

複数のクラスターを持っていて、ユーザーやコンポーネントがさまざまな方法で認証を行う次のような状況を考えてみます。

  • 実行中のkubeletが証明書を使用して認証を行う可能性がある。
  • ユーザーがトークンを使用して認証を行う可能性がある。
  • 管理者が個別のユーザに提供する複数の証明書を持っている可能性がある。

kubeconfigファイルを使用すると、クラスター、ユーザー、名前空間を組織化することができます。また、contextを定義することで、複数のクラスターや名前空間を素早く簡単に切り替えられます。

Context

kubeconfigファイルのcontext要素は、アクセスパラメーターを使いやすい名前でグループ化するために使われます。各contextは3つのパラメータ、cluster、namespace、userを持ちます。デフォルトでは、kubectlコマンドラインツールはクラスターとの通信にcurrent contextのパラメーターを使用します。

current contextを選択するには、以下のコマンドを使用します。

kubectl config use-context

KUBECONFIG環境変数

KUBECONFIG環境変数には、kubeconfigファイルのリストを指定できます。LinuxとMacでは、リストはコロン区切りです。Windowsでは、セミコロン区切りです。KUBECONFIG環境変数は必須ではありません。KUBECONFIG環境変数が存在しない場合は、kubectlはデフォルトのkubeconfigファイルである$HOME/.kube/configを使用します。

KUBECONFIG環境変数が存在する場合は、kubectlKUBECONFIG環境変数にリストされているファイルをマージした結果を有効な設定として使用します。

kubeconfigファイルのマージ

設定ファイルを確認するには、以下のコマンドを実行します。

kubectl config view

上で説明したように、出力は1つのkubeconfigファイルから作られる場合も、複数のkubeconfigファイルをマージした結果となる場合もあります。

kubectlがkubeconfigファイルをマージするときに使用するルールを以下に示します。

  1. もし--kubeconfigフラグが設定されていた場合、指定したファイルだけが使用されます。マージは行いません。このフラグに指定できるのは1つのファイルだけです。

    そうでない場合、KUBECONFIG環境変数が設定されていた場合には、それをマージするべきファイルのリストとして使用します。KUBECONFIG環境変数にリストされたファイルのマージは、次のようなルールに従って行われます。

    • 空のファイルを無視する。
    • デシリアライズできない内容のファイルに対してエラーを出す。
    • 特定の値やmapのキーを設定する最初のファイルが勝つ。
    • 値やmapのキーは決して変更しない。 例: 最初のファイルが指定したcurrent-contextを保持する。 例: 2つのファイルがred-userを指定した場合、1つ目のファイルのred-userだけを使用する。もし2つ目のファイルのred-user以下に競合しないエントリーがあったとしても、それらは破棄する。

    KUBECONFIG環境変数を設定する例については、KUBECONFIG環境変数を設定するを参照してください。

    それ以外の場合は、デフォルトのkubeconfigファイル$HOME/.kube/configをマージせずに使用します。

  2. 以下のチェーンで最初に見つかったものをもとにして、使用するcontextを決定する。

    1. --contextコマンドラインフラグが存在すれば、それを使用する。
    2. マージしたkubeconrfigファイルからcurrent-contextを使用する。

    この時点では、空のcontextも許容されます。

  3. クラスターとユーザーを決定する。この時点では、contextである場合もそうでない場合もあります。以下のチェーンで最初に見つかったものをもとにして、クラスターとユーザーを決定します。この手順はユーザーとクラスターについてそれぞれ1回ずつ、合わせて2回実行されます。

    1. もし存在すれば、コマンドラインフラグ--userまたは--clusterを使用する。
    2. もしcontextが空でなければ、contextからユーザーまたはクラスターを取得する。

    この時点では、ユーザーとクラスターは空である可能性があります。

  4. 使用する実際のクラスター情報を決定する。この時点では、クラスター情報は存在しない可能性があります。以下のチェーンで最初に見つかったものをもとにして、クラスター情報の各パーツをそれぞれを構築します。

    1. もし存在すれば、--server--certificate-authority--insecure-skip-tls-verifyコマンドラインフラグを使用する。
    2. もしマージしたkubeconfigファイルにクラスター情報の属性が存在すれば、それを使用する。
    3. もしサーバーの場所が存在しなければ、マージは失敗する。
  5. 使用する実際のユーザー情報を決定する。クラスター情報の場合と同じルールを使用して、ユーザー情報を構築します。ただし、ユーザーごとに許可される認証方法は1つだけです。

    1. もし存在すれば、--client-certificate--client-key--username--password--tokenコマンドラインフラグを使用する。
    2. マージしたkubeconfigファイルのuserフィールドを使用する。
    3. もし2つの競合する方法が存在する場合、マージは失敗する。
  6. もし何らかの情報がまだ不足していれば、デフォルトの値を使用し、認証情報については場合によってはプロンプトを表示する。

ファイルリファレンス

kubeconfigファイル内のファイルとパスのリファレンスは、kubeconfigファイルの位置からの相対パスで指定します。コマンドライン上のファイルのリファレンスは、現在のワーキングディレクトリからの相対パスです。$HOME/.kube/config内では、相対パスは相対のまま、絶対パスは絶対のまま保存されます。

次の項目

6 - Podの優先度とプリエンプション

FEATURE STATE: Kubernetes v1.14 [stable]

Podpriority(優先度)を持つことができます。 優先度は他のPodに対する相対的なPodの重要度を示します。 もしPodをスケジューリングできないときには、スケジューラーはそのPodをスケジューリングできるようにするため、優先度の低いPodをプリエンプトする(追い出す)ことを試みます。

警告:

クラスターの全てのユーザーが信用されていない場合、悪意のあるユーザーが可能な範囲で最も高い優先度のPodを作成することが可能です。これは他のPodが追い出されたりスケジューリングできない状態を招きます。 管理者はResourceQuotaを使用して、ユーザーがPodを高い優先度で作成することを防ぐことができます。

詳細はデフォルトで優先度クラスの消費を制限する を参照してください。

優先度とプリエンプションを使う方法

優先度とプリエンプションを使うには、

  1. 1つまたは複数のPriorityClassを追加します

  2. 追加したPriorityClassをpriorityClassNameに設定したPodを作成します。 もちろんPodを直接作る必要はありません。 一般的にはpriorityClassNameをDeploymentのようなコレクションオブジェクトのPodテンプレートに追加します。

これらの手順のより詳しい情報については、この先を読み進めてください。

備考: Kubernetesには最初から既に2つのPriorityClassが設定された状態になっています。 system-cluster-criticalsystem-node-criticalです。 これらは汎用のクラスであり、重要なコンポーネントが常に最初にスケジュールされることを保証するために使われます。

PriorityClass

PriorityClassはnamespaceによらないオブジェクトで、優先度クラスの名称から優先度を表す整数値への対応を定義します。 PriorityClassオブジェクトのメタデータのnameフィールドにて名称を指定します。 値はvalueフィールドで指定し、必須です。 値が大きいほど、高い優先度を示します。 PriorityClassオブジェクトの名称はDNSサブドメイン名として適切であり、かつsystem-から始まってはいけません。

PriorityClassオブジェクトは10億以下の任意の32ビットの整数値を持つことができます。 それよりも大きな値は通常はプリエンプトや追い出すべきではない重要なシステム用のPodのために予約されています。 クラスターの管理者は割り当てたい優先度に対して、PriorityClassオブジェクトを1つずつ作成すべきです。

PriorityClassは任意でフィールドglobalDefaultdescriptionを設定可能です。 globalDefaultフィールドはpriorityClassNameが指定されないPodはこのPriorityClassを使うべきであることを示します。globalDefaultがtrueに設定されたPriorityClassはシステムで一つのみ存在可能です。globalDefaultが設定されたPriorityClassが存在しない場合は、priorityClassNameが設定されていないPodの優先度は0に設定されます。

descriptionフィールドは任意の文字列です。クラスターの利用者に対して、PriorityClassをどのような時に使うべきか示すことを意図しています。

PodPriorityと既存のクラスターに関する注意

  • もし既存のクラスターをこの機能がない状態でアップグレードすると、既存のPodの優先度は実質的に0になります。

  • globalDefaulttrueに設定されたPriorityClassを追加しても、既存のPodの優先度は変わりません。PriorityClassのそのような値は、PriorityClassが追加された以後に作成されたPodのみに適用されます。

  • PriorityClassを削除した場合、削除されたPriorityClassの名前を使用する既存のPodは変更されませんが、削除されたPriorityClassの名前を使うPodをそれ以上作成することはできなくなります。

PriorityClassの例

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "この優先度クラスはXYZサービスのPodに対してのみ使用すべきです。"

非プリエンプトのPriorityClass

FEATURE STATE: Kubernetes v1.19 [beta]

PreemptionPolicy: Neverと設定されたPodは、スケジューリングのキューにおいて他の優先度の低いPodよりも優先されますが、他のPodをプリエンプトすることはありません。 スケジューリングされるのを待つ非プリエンプトのPodは、リソースが十分に利用可能になるまでスケジューリングキューに残ります。 非プリエンプトのPodは、他のPodと同様に、スケジューラーのバックオフの対象になります。これは、スケジューラーがPodをスケジューリングしようと試みたものの失敗した場合、低い頻度で再試行するようにして、より優先度の低いPodが先にスケジューリングされることを許します。

非プリエンプトのPodは、他の優先度の高いPodにプリエンプトされる可能性はあります。

PreemptionPolicyはデフォルトではPreemptLowerPriorityに設定されており、これが設定されているPodは優先度の低いPodをプリエンプトすることを許容します。これは既存のデフォルトの挙動です。 PreemptionPolicyNeverに設定すると、これが設定されたPodはプリエンプトを行わないようになります。

ユースケースの例として、データサイエンスの処理を挙げます。 ユーザーは他の処理よりも優先度を高くしたいジョブを追加できますが、そのとき既存の実行中のPodの処理結果をプリエンプトによって破棄させたくはありません。 PreemptionPolicy: Neverが設定された優先度の高いジョブは、他の既にキューイングされたPodよりも先に、クラスターのリソースが「自然に」開放されたときにスケジューリングされます。

非プリエンプトのPriorityClassの例

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority-nonpreempting
value: 1000000
preemptionPolicy: Never
globalDefault: false
description: "この優先度クラスは他のPodをプリエンプトさせません。"

Podの優先度

一つ以上のPriorityClassがあれば、仕様にPriorityClassを指定したPodを作成することができるようになります。優先度のアドミッションコントローラーはpriorityClassNameフィールドを使用し、優先度の整数値を設定します。PriorityClassが見つからない場合、そのPodの作成は拒否されます。

下記のYAMLは上記の例で作成したPriorityClassを使用するPodの設定の例を示します。優先度のアドミッションコントローラーは仕様を確認し、このPodの優先度は1000000であると設定します。

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  priorityClassName: high-priority

スケジューリング順序におけるPodの優先度の効果

Podの優先度が有効な場合、スケジューラーは待機状態のPodをそれらの優先度順に並べ、スケジューリングキューにおいてより優先度の低いPodよりも前に来るようにします。その結果、その条件を満たしたときには優先度の高いPodは優先度の低いPodより早くスケジューリングされます。優先度の高いPodがスケジューリングできない場合は、スケジューラーは他の優先度の低いPodのスケジューリングも試みます。

プリエンプション

Podが作成されると、スケジューリング待ちのキューに入り待機状態になります。スケジューラーはキューからPodを取り出し、ノードへのスケジューリングを試みます。Podに指定された条件を全て満たすノードが見つからない場合は、待機状態のPodのためにプリエンプションロジックが発動します。待機状態のPodをPと呼ぶことにしましょう。プリエンプションロジックはPよりも優先度の低いPodを一つ以上追い出せばPをスケジューリングできるようになるノードを探します。そのようなノードがあれば、優先度の低いPodはノードから追い出されます。Podが追い出された後に、Pはノードへスケジューリング可能になります。

ユーザーへ開示される情報

Pod PがノードNのPodをプリエンプトした場合、ノードNの名称がPのステータスのnominatedNodeNameフィールドに設定されます。このフィールドはスケジューラーがPod Pのために予約しているリソースの追跡を助け、ユーザーにクラスターにおけるプリエンプトに関する情報を与えます。

Pod Pは必ずしも「指名したノード」へスケジューリングされないことに注意してください。Podがプリエンプトされると、そのPodは終了までの猶予期間を得ます。スケジューラーがPodの終了を待つ間に他のノードが利用可能になると、スケジューラーは他のノードをPod Pのスケジューリング先にします。この結果、PodのnominatedNodeNamenodeNameは必ずしも一致しません。また、スケジューラーがノードNのPodをプリエンプトさせた後に、Pod Pよりも優先度の高いPodが来た場合、スケジューラーはノードNをその新しい優先度の高いPodへ与えます。このような場合は、スケジューラーはPod PのnominatedNodeNameを消去します。これによって、スケジューラーはPod Pが他のノードのPodをプリエンプトさせられるようにします。

プリエンプトの制限

プリエンプトされるPodの正常終了

Podがプリエンプトされると、猶予期間が与えられます。

Podは作業を完了し、終了するために十分な時間が与えられます。仮にそうでない場合、強制終了されます。この猶予期間によって、スケジューラーがPodをプリエンプトした時刻と、待機状態のPod Pがノード Nにスケジュール可能になるまでの時刻の間に間が開きます。この間、スケジューラーは他の待機状態のPodをスケジュールしようと試みます。プリエンプトされたPodが終了したら、スケジューラーは待ち行列にあるPodをスケジューリングしようと試みます。そのため、Podがプリエンプトされる時刻と、Pがスケジュールされた時刻には間が開くことが一般的です。この間を最小にするには、優先度の低いPodの猶予期間を0または小さい値にする方法があります。

PodDisruptionBudgetは対応するが、保証されない

PodDisruptionBudget (PDB)は、アプリケーションのオーナーが冗長化されたアプリケーションのPodが意図的に中断される数の上限を設定できるようにするものです。KubernetesはPodをプリエンプトする際にPDBに対応しますが、PDBはベストエフォートで考慮します。スケジューラーはプリエンプトさせたとしてもPDBに違反しないPodを探します。そのようなPodが見つからない場合でもプリエンプションは実行され、PDBに反しますが優先度の低いPodが追い出されます。

優先度の低いPodにおけるPod間のアフィニティ

次の条件が真の場合のみ、ノードはプリエンプションの候補に入ります。 「待機状態のPodよりも優先度の低いPodをノードから全て追い出したら、待機状態のPodをノードへスケジュールできるか」

備考: プリエンプションは必ずしも優先度の低いPodを全て追い出しません。 優先度の低いPodを全て追い出さなくても待機状態のPodがスケジューリングできる場合、一部のPodのみ追い出されます。 このような場合であったとしても、上記の条件は真である必要があります。偽であれば、そのノードはプリエンプションの対象とはされません。

待機状態のPodが、優先度の低いPodとの間でPod間のアフィニティを持つ場合、Pod間のアフィニティはそれらの優先度の低いPodがなければ満たされません。この場合、スケジューラーはノードのどのPodもプリエンプトしようとはせず、代わりに他のノードを探します。スケジューラーは適切なノードを探せる場合と探せない場合があります。この場合、待機状態のPodがスケジューリングされる保証はありません。

この問題に対して推奨される解決策は、優先度が同一または高いPodに対してのみPod間のアフィニティを作成することです。

複数ノードに対するプリエンプション

Pod PがノードNにスケジューリングできるよう、ノードNがプリエンプションの対象となったとします。 他のノードのPodがプリエンプトされた場合のみPが実行可能になることもあります。下記に例を示します。

  • Pod PをノードNに配置することを検討します。
  • Pod QはノードNと同じゾーンにある別のノードで実行中です。
  • Pod Pはゾーンに対するQへのアンチアフィニティを持ちます (topologyKey: topology.kubernetes.io/zone)。
  • Pod Pと、ゾーン内の他のPodに対しては他のアンチアフィニティはない状態です。
  • Pod PをノードNへスケジューリングするには、Pod Qをプリエンプトすることが考えられますが、スケジューラーは複数ノードにわたるプリエンプションは行いません。そのため、Pod PはノードNへはスケジューリングできないとみなされます。

Pod Qがそのノードから追い出されると、Podアンチアフィニティに違反しなくなるので、Pod PはノードNへスケジューリング可能になります。

複数ノードに対するプリエンプションに関しては、十分な需要があり、合理的な性能を持つアルゴリズムを見つけられた場合に、追加することを検討する可能性があります。

トラブルシューティング

Podの優先度とプリエンプションは望まない副作用をもたらす可能性があります。 いくつかの起こりうる問題と、その対策について示します。

Podが不必要にプリエンプトされる

プリエンプションは、リソースが不足している場合に優先度の高い待機状態のPodのためにクラスターの既存のPodを追い出します。 誤って高い優先度をPodに割り当てると、意図しない高い優先度のPodはクラスター内でプリエンプションを引き起こす可能性があります。Podの優先度はPodの仕様のpriorityClassNameフィールドにて指定されます。優先度を示す整数値へと変換された後、podSpecpriorityへ設定されます。

この問題に対処するには、PodのpriorityClassNameをより低い優先度に変更するか、このフィールドを未設定にすることができます。priorityClassNameが未設定の場合、デフォルトでは優先度は0とされます。

Podがプリエンプトされたとき、プリエンプトされたPodのイベントが記録されます。 プリエンプションはPodに必要なリソースがクラスターにない場合のみ起こるべきです。 このような場合、プリエンプションはプリエンプトされるPodよりも待機状態のPodの優先度が高い場合のみ発生します。 プリエンプションは待機状態のPodがない場合や待機状態のPodがプリエンプト対象のPod以下の優先度を持つ場合には決して発生しません。そのような状況でプリエンプションが発生した場合、問題を報告してください。

Podはプリエンプトされたが、プリエンプトさせたPodがスケジューリングされない

Podがプリエンプトされると、それらのPodが要求した猶予期間が与えられます。そのデフォルトは30秒です。 Podがその期間内に終了しない場合、強制終了されます。プリエンプトされたPodがなくなれば、プリエンプトさせたPodはスケジューリング可能です。

プリエンプトさせたPodがプリエンプトされたPodの終了を待っている間に、より優先度の高いPodが同じノードに対して作成されることもあります。この場合、スケジューラーはプリエンプトさせたPodの代わりに優先度の高いPodをスケジューリングします。

これは予期された挙動です。優先度の高いPodは優先度の低いPodに取って代わります。

優先度の高いPodが優先度の低いPodより先にプリエンプトされる

スケジューラーは待機状態のPodが実行可能なノードを探します。ノードが見つからない場合、スケジューラーは任意のノードから優先度の低いPodを追い出し、待機状態のPodのためのリソースを確保しようとします。 仮に優先度の低いPodが動いているノードが待機状態のPodを動かすために適切ではない場合、スケジューラーは他のノードで動いているPodと比べると、優先度の高いPodが動いているノードをプリエンプションの対象に選ぶことがあります。この場合もプリエンプトされるPodはプリエンプトを起こしたPodよりも優先度が低い必要があります。

複数のノードがプリエンプションの対象にできる場合、スケジューラーは優先度が最も低いPodのあるノードを選ぼうとします。しかし、そのようなPodがPodDisruptionBudgetを持っており、プリエンプトするとPDBに反する場合はスケジューラーは優先度の高いPodのあるノードを選ぶこともあります。

複数のノードがプリエンプションの対象として利用可能で、上記の状況に当てはまらない場合、スケジューラーは優先度の最も低いノードを選択します。

Podの優先度とQoSの相互作用

Podの優先度とQoSクラスは直交する機能で、わずかに相互作用がありますが、デフォルトではQoSクラスによる優先度の設定の制約はありません。スケジューラーのプリエンプションのロジックはプリエンプションの対象を決めるときにQoSクラスは考慮しません。 プリエンプションはPodの優先度を考慮し、優先度が最も低いものを候補とします。より優先度の高いPodは優先度の低いPodを追い出すだけではプリエンプトを起こしたPodのスケジューリングに不十分な場合と、PodDisruptionBudgetにより優先度の低いPodが保護されている場合のみ対象になります。

QoSとPodの優先度の両方を考慮するコンポーネントはリソース不足によりkubeletがPodを追い出すのみです。 kubeletは追い出すPodの順位付けを次の順で行います。枯渇したリソースを要求以上に使用しているか、優先度、枯渇したリソースの消費量の複数のPodの要求に対する相対値。 詳細はエンドユーザーのPodの追い出しを参照してください。

kubeletによるリソース不足時のPodの追い出しでは、リソースの消費が要求を超えないPodは追い出されません。優先度の低いPodのリソースの利用量がその要求を超えていなければ、追い出されることはありません。より優先度が高く、要求を超えてリソースを使用しているPodが追い出されます。

次の項目