GKE Sandbox を使用して信頼できないワークロードを実行する

このページでは、ポッド内のコンテナが不明なコードや信頼できないコードを実行したときに、GKE Sandbox を使用してノードのホストカーネルを保護する方法について説明します。たとえば、SaaS プロバイダなどのマルチテナント クラスタでは、ユーザーから送信された不明なコードが実行されることが少なくありません。

GKE Sandbox は、オープンソース プロジェクトの gVisor を使用します。このトピックでは gVisor の概要について説明します。詳細については、gVisor の公式ドキュメントをご覧ください。

概要

GKE Sandbox は、セキュリティに新たなレイヤを追加し、信頼できないコードからクラスタノードのホストカーネルを保護します。GKE Sandbox の仕組みについて説明する前に、潜在的なリスクの特徴について理解しておきましょう。これは、リスクを回避する際に役立ちます。

dockercontainerd などのコンテナ ランタイムは、コンテナのプロセスとノードのカーネルをある程度分離しています。ただし、コンテナ ランタイムはノードの特権ユーザーとして実行されることが多く、その場合、ホストカーネルに対するほとんどのシステムコールにアクセスできます。

潜在的な脅威

マルチテナント クラスタや、コンテナで信頼できないワークロードが実行されるクラスタは、他のクラスタよりも脆弱性が悪用されるリスクが高くなります。たとえば、SaaS プロバイダ、ウェブ ホスティング プロバイダ、ユーザーにコードのアップロードと実行を許可している組織では、攻撃を受ける可能性が高くなります。コンテナ ランタイムやホストカーネルの欠陥が悪用されると、コンテナ内で実行されるプロセスがコンテナをエスケープして、ノードのカーネルに影響を及ぼし、ノードが停止する可能性があります。

また、悪意のあるテナントが、このような欠陥を利用して他のテナントのメモリやディスクに侵入し、データを盗み出す可能性もあります。

信頼できないワークロードが、他の Google Cloud Platform サービスやクラスタ メタデータにアクセスする可能性もあります。

GKE Sandbox が脅威を軽減する仕組み

gVisor は、高レベルの権限を必要としない Linux カーネル API のユーザー空間を再実装したものです。containerd などのコンテナ ランタイムとともに、このユーザー空間カーネルはシステムコールの大半を再実装し、ホストカーネルに代わってサービスを提供します。これにより、ホストカーネルへの直接アクセスが制限されています。この仕組みの詳細については、gVisor アーキテクチャ ガイドをご覧ください。コンテナの観点から見ると、gVisor はほぼ透過的で、コンテナ化されたアプリケーションに変更を加える必要はありません。

ノードプールで GKE Sandbox を有効にすると、そのノードプール内のノードで実行中のポッドごとにサンドボックスが作成されます。サンドボックス化されたポッドを実行するノードは、他の GCP サービスやクラスタ メタデータにアクセスすることはできません。

各サンドボックスは、独自のユーザー空間カーネルを使用します。このため、必要な分離レベルとアプリケーションの特性に基づいて、コンテナをポッドにグループ化できます。

GKE Sandbox は、次のタイプのアプリケーションに特に適しています。サンドボックス化するアプリケーションについては、制限事項をご覧ください。

  • Rust、Java、Python、PHP、Node.js、Golang など、ランタイムを使用するサードパーティまたは信頼できないアプリケーション
  • ウェブサーバーのフロントエンド、キャッシュ、またはプロキシ
  • CPU を使用して外部メディアまたはデータを処理するアプリケーション
  • CPU を使用した機械学習ワークロード
  • CPU 使用量の多いアプリケーションやメモリを大量に消費するアプリケーション

その他のセキュリティに関する推奨事項

GKE Sandbox を使用する場合は、次の推奨事項も考慮してください。

  • ノードで vCPU が 1 つしか使用されていない場合は、Hyper-Threading を無効にして、Intel が発表した Microarchitectural Data Sampling(MDS)の脆弱性を回避することをおすすめします。詳細については、セキュリティ情報をご覧ください。

  • サンドボックス内で実行されるすべてのコンテナに対してリソース制限を指定することを強くおすすめします。これにより、不正なアプリケーションによるノードリソースの占有を回避し、ノード上で実行中の他のアプリケーションやシステム プロセスへの悪影響を防ぐことができます。

制限事項

GKE Sandbox は多くのアプリケーションで動作していますが、すべてのアプリケーションに対応しているわけではありません。このセクションでは、GKE Sandbox の現在の制限事項について詳しく説明します。

ノードプールの構成

  • デフォルトのノードプールで GKE Sandbox を有効にすることはできません。
  • GKE Sandbox を使用する場合、クラスタに 2 つ以上のノードプールが必要です。 GKE Sandbox が無効になっているノードプールが少なくとも 1 つは必要です。 すべてのワークロードがサンドボックス化されていても、このノードプールには 1 つ以上のノードが必要です。

クラスタ メタデータへのアクセス

  • サンドボックス化されたポッドを実行するノードでは、ノードのオペレーティング システムのレベルでクラスタ メタデータにアクセスできません。
  • GKE Sandbox が有効になっているノードでは、通常のポッドを実行できます。ただし、デフォルトでは、こうした通常のポッドは GCP サービスやクラスタ メタデータにアクセスできません。ポッドに GCP サービスへのアクセスを許可するには、Workload Identity を使用します。

機能

デフォルトでは、コンテナは未処理のソケットを開けないため、悪質な攻撃を防ぐことができます。pingtcpdump など、特定のネットワーク関連ツールは、そのコアの機能の一部として未処理のソケットを作成します。未処理のソケットを有効にするには、NET_RAW の機能をコンテナのセキュリティ コンテキストに明示的に追加する必要があります。

spec:
  containers:
  - name: my-container
    securityContext:
      capabilities:
        add: ["NET_RAW"]

互換性のない機能

現在、GKE Sandbox は次の Kubernetes の機能と併用できません。

  • GPU や TPU などのアクセラレータ
  • Istio
  • ポッドまたはコンテナのレベルでの統計情報のモニタリング
  • hostpath ストレージ
  • CPU とメモリの制限は、保証されたポッドとバースト可能ポッドにのみ適用されますが、適用されるのは、ポッドで実行中のすべてのコンテナに CPU とメモリの制限が指定されている場合だけです。
  • hostNetworkhostPIDhostIPC など、ホストの名前空間を指定する PodSecurityPolicies を使用するポッド
  • 特権モードなどの PodSecurityPolicy 設定を使用するポッド
  • VolumeDevices
  • ポート転送
  • Seccomp、Apparmo または Selinux SysctlNoNewPrivileges双方向の MountPropagationFSGroupProcMount などの Linux カーネル セキュリティ モジュール

ワークロードの特性

ノードのカーネルにアクセスするために中間層を追加すると、パフォーマンスとのトレードオフが発生します。GKE Sandbox は、分離を必要とする大規模なマルチテナント クラスタで最大の効果を発揮します。GKE Sandbox でワークロードのテストを行う場合は、次のガイドラインに従ってください。

システムコール

オーバーヘッドの少ないシステムコールを大量に生成するワークロード(小規模の入出力オペレーションを頻繁に実行するワークロードなど)をサンドボックス内で実行すると、システム リソースの消費が増える可能性があります。その場合、より強力なノードを使用するか、クラスタにノードを追加する必要があります。

ハードウェアまたは仮想化への直接アクセス

ワークロードで次のいずれかが必要な場合、GKE Sandbox は適切ではない可能性があります。このサンドボックスでは、ノードのホストカーネルに直接アクセスできません。

  • ノードのハードウェアへの直接アクセス
  • カーネルレベルの仮想化機能
  • 特権コンテナ

GKE Sandbox を有効にする

GKE Sandbox は、新しいクラスタまたは既存のクラスタで有効にできます。

始める前に

このタスクの準備として、次の手順を行います。

  • Google Kubernetes Engine API が有効になっていることを確認します。
  • Enable Google Kubernetes Engine API を有効にする
  • Cloud SDK がインストール済みであることを確認します。
  • デフォルトのプロジェクト ID を設定します。
    gcloud config set project [PROJECT_ID]
  • ゾーンクラスタを使用する場合は、デフォルトのコンピューティング ゾーンを設定します。
    gcloud config set compute/zone [COMPUTE_ZONE]
  • リージョン クラスタを使用する場合は、デフォルトのコンピューティング リージョンを設定します。
    gcloud config set compute/region [COMPUTE_REGION]
  • gcloud を最新バージョンに更新します。
    gcloud components update
  • GKE Sandbox を使用するには、クラスタ マスターとノードに GKE v1.12.7-gke.17 以降または v1.13.5-gke.15 以降が必要です。
  • gcloud コマンドは、バージョン 243.0.0 以降が必要です。

新しいクラスタの場合

GKE Sandbox を有効にするには、ノードプールを構成します。デフォルトのノードプール(クラスタの作成時に作成される最初のノードプール)は GKE Sandbox を使用できません。クラスタの作成時に GKE Sandbox を有効にするには、クラスタを作成するときに 2 つ目のノードプールを追加する必要があります。

Console

クラスタを表示するには、GCP Console で Google Kubernetes Engine のメニューに移動します。

  1. GCP Console で Google Kubernetes Engine のメニューに移動します。

    Google Kubernetes Engine のメニューに移動

  2. [クラスタを作成] をクリックします。

  3. [標準クラスタ] テンプレートを選択するか、ワークロードに適切なテンプレートを選択します。

  4. 推奨(省略可能): Stackdriver Logging と Stackdriver Monitoring を有効にして、gVisor メッセージをログに記録します。

  5. [ノードプールを追加] をクリックします。

  6. 要件に合わせてノードプールを構成します。ノードプールで [その他のノードプール オプション] をクリックします。次の設定を構成します。

    • ノード バージョンは、v1.12.6-gke.8 以降を選択します。
    • ノードイメージは、containerd(cos_containerd)が含まれている Container-Optimized OS(ベータ版)を選択します。
    • [Sandbox with gVisor(ベータ版)を有効にする] を有効にします。
    • ノードプール内のノードが複数の vCPU を使用している場合は、[ラベルを追加] をクリックします。キーに cloud.google.com/gke-smt-disabled を設定し、値に true を設定します。次に、セキュリティ情報 にある手順に従って、ハイパースレッディングを無効にします。

    必要に応じて、他のノードプール設定を構成します。

  7. ノードプールの設定を保存し、クラスタの構成を続けます。

gcloud

デフォルトのノードプールで GKE Sandbox を有効にすることはできません。また、新しいクラスタを作成するときに、gcloud コマンドを実行します。代わりに、通常どおりにクラスタを作成します。--enable-stackdriver-kubernetes フラグを追加して、Stackdriver Logging と Stackdriver Monitoring を有効にします。この操作は省略可能ですが、行うことをおすすめします。gVisor メッセージがログに記録されます。

次に、--sandbox フラグを type=gvisor に設定して、gcloud beta container node-pools create コマンドを実行します。角かっこで囲まれた値は独自の値に置き換えます。ただし、ノードのバージョンは v1.12.6-gke.8 以降にする必要があります。

gcloud beta container node-pools create [NODE_POOL_NAME] \
  --cluster=[CLUSTER_NAME] \
  --node-version=[NODE_VERSION] \
  --image-type=cos_containerd \
  --sandbox type=gvisor \
  --enable-autoupgrade

ノードの作成中、ワークロードがノードにスケジュールされる前に、gvisor RuntimeClass がインスタンス化されます。次のコマンドを使用すると、gvisor RuntimeClass の存在を確認できます。

kubectl get runtimeclasses
NAME     AGE
gvisor   19s

既存クラスタの場合

既存のクラスタで GKE Sandbox を有効にするには、新しいノードプールを追加し、そのノードプールの機能を有効にするか、デフォルト以外の既存のノードプールを変更します。

Console

  1. GCP Console で Google Kubernetes Engine のメニューに移動します。

    Google Kubernetes Engine のメニューに移動

  2. クラスタの編集ボタン(鉛筆の形をしたボタン)をクリックします。

  3. 必要に応じて、ノードプールを追加 をクリックして、追加のノードプールを追加します。 既存のノードプールを編集する場合は、ノードプールの編集ボタンをクリックします。デフォルトのノードプールで Sandbox with gVisor(ベータ版)を有効にしないでください。

  4. [Sandbox with gVisor(ベータ版)] を有効にして、[完了] をクリックします。

  5. 必要に応じて、クラスタの構成をさらに変更し、[保存] をクリックします。

gcloud

GKE Sandbox を有効にする新しいノードプールを作成するには、次のようなコマンドを使用します。

gcloud beta container node-pools create [NODE_POOL_NAME] \
  --cluster=[CLUSTER_NAME] \
  --image-type=cos_containerd \
  --sandbox type=gvisor \
  --enable-autoupgrade

既存のノードプールで GKE Sandbox を有効にするには、次のようなコマンドを使用します。デフォルトのノードプールで --sandbox type=gvisor を有効にしないでください。

 gcloud beta container node-pools update [NODE_POOL_NAME] \
  --sandbox type=gvisor

ノードの作成中、ワークロードがノードにスケジュールされる前に、gvisor RuntimeClass がインスタンス化されます。次のコマンドを使用すると、gvisor RuntimeClass の存在を確認できます。

kubectl get runtimeclasses
NAME     AGE
gvisor   19s

省略可: Stackdriver Logging と Stackdriver Monitoring を有効にする

クラスタで Stackdriver Logging と Stackdriver Monitoring を有効にして、gVisor メッセージをログに記録します。この操作は省略可能ですが、行うことをおすすめします。これらの機能を既存のクラスタで有効にするには、Google Cloud Platform Console を使用する必要があります。

  1. GCP Console で Google Kubernetes Engine のメニューに移動します。

    Google Kubernetes Engine のメニューに移動

  2. クラスタの編集ボタン(鉛筆の形をしたボタン)をクリックします。

  3. Stackdriver Logging と Stackdriver Monitoring を有効にします。

  4. 必要に応じて、クラスタの構成をさらに変更し、[保存] をクリックします。

GKE Sandbox の使用

サンドボックスでのアプリケーションの実行

GKE Sandbox を有効にしたノードで Deployment を強制的に実行するには、Deployment のマニフェストに従って spec.template.spec.runtimeClassNamegvisor に設定します。

# httpd.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpd
  labels:
    app: httpd
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpd
  template:
    metadata:
      labels:
        app: httpd
    spec:
      runtimeClassName: gvisor
      containers:
      - name: httpd
        image: httpd

Deployment を作成するには、kubectl create コマンドを実行します。

kubectl create -f httpd.yaml

GKE Sandbox が有効になっているノードプールのノードにポッドがデプロイされます。これを確認するには、kubectl get pods コマンドを使用して、ポッドがデプロイされているノードを探します。

kubectl get pods

NAME                    READY   STATUS    RESTARTS   AGE
httpd-db5899bc9-dk7lk   1/1     Running   0          24s

出力でポッドの名前を探し、次のコマンドを実行して RuntimeClass の値を確認します。

kubectl get pods [NAME-OF-POD] -o jsonpath='{.spec.runtimeClassName}'

gvisor

または、各ポッドの RuntimeClass をリスト表示して、gvisor に設定されているものを探します。

kubectl get pods -o jsonpath=$'{range .items[*]}{.metadata.name}: {.spec.runtimeClassName}\n{end}'

[NAME-OF-POD]: gvisor

ポッドがサンドボックス内で実行されていることを確認する方法は、サンドボックス内のデータに依存しないため、信頼性が高い方法となります。サンドボックス内から報告されたものは信頼できません。問題が存在していることや、悪意のある可能性があります。

通常のポッドとサンドボックス ポッドの実行

ノードプールで GKE Sandbox を有効にした後、Node Taints と許容値でサンドボックスを使用するのではなく、信頼できるアプリケーションをノードで実行できます。 これらのポッドは、「通常のポッド」といわれ、サンドボックスのポッドと区別されます。

通常のポッドは、サンドボックス化されたポッドと同様に、他の GCP サービスやクラスタ メタデータにアクセスできません。この設定は、ノードの構成時に有効になります。通常のポッドやサンドボックス化されたポッドが GCP サービスにアクセスする必要がある場合は、Workload Identity を使用します。

GKE Sandbox は、サンドボックス化されたポッドを実行できるノードに次のラベルと taint を追加します。

labels:
  sandbox.gke.io: gvisor
taints:
- effect: NoSchedule
  key: sandbox.gke.io
  value: gvisor

GKE Sandbox は、ポッド マニフェストのノード アフィニティと許容値に加えて、次のノード アフィニティと許容値をすべてのポッドに適用し、RuntimeClassgvisor に設定します。

affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: sandbox.gke.io/runtime
          operator: In
          values:
          - gvisor
tolerations:
  - effect: NoSchedule
    key: sandbox.gke.io/runtime
    operator: Equal
    value: gvisor

GKE Sandbox が有効なノードで通常ポッドのスケジュールを設定するには、上記のノード アフィニティと許容値を Pod マニフェストに手動で適用します。

  • GKE Sandbox が有効なノードでポッドが実行可能な場合は、許容値を追加します。
  • GKE Sandbox が有効なノードでポッドのスケジュールを設定する必要がある場合は、上記のノード アフィニティと許容値の両方を追加します。

たとえば、次のマニフェストでは、サンドボックスでのアプリケーションの実行で使用したマニフェストを変更します。runtimeClass を削除して上記の taint と許容値の両方を追加し、サンドボックス化したポッドを含むノードで通常のポッドとして実行します。

# httpd-no-sandbox.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpd-no-sandbox
  labels:
    app: httpd
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpd
  template:
    metadata:
      labels:
        app: httpd
    spec:
      containers:
      - name: httpd
        image: httpd
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: sandbox.gke.io/runtime
                operator: In
                values:
                - gvisor
      tolerations:
        - effect: NoSchedule
          key: sandbox.gke.io/runtime
          operator: Equal
          value: gvisor

まず、サンドボックスで Deployment が実行されていないことを確認します。

kubectl get pods -o jsonpath=$'{range .items[*]}{.metadata.name}: {.spec.runtimeClassName}\n{end}'

httpd-db5899bc9-dk7lk: gvisor
httpd-no-sandbox-5bf87996c6-cfmmd:

前に作成した httpd Deployment はサンドボックス内で実行されます。これは、runtimeClass が gvisor のためです。httpd-no-sandbox Deployment の runtimeClass には値が設定されていないため、サンドボックスでは実行されません。

次に、GKE Sandbox を使用するノードで、サンドボックス化されていない Deployment が実行されていることを確認します。

kubectl get pod -o jsonpath=$'{range .items[*]}{.metadata.name}: {.spec.nodeName}\n{end}'

ノードプールの名前は、nodeName の値に埋め込まれます。GKE Sandbox が有効なノードプール内のノードで、ポッドが実行されていることを確認します。

メタデータの保護の確認

サンドボックスポッドを実行できるノードからメタデータが保護されていることを確認するには、次のようにテストを行います。

  1. kubectl apply -f を使用して、次のマニフェストから、サンドボックス化された Deployment を作成します。curlコマンドを含む fedora イメージを使用します。ポッドは /bin/sleep コマンドを実行して、Deployment が 10,000 秒間実行されるようにします。

    # sandbox-metadata-test.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: fedora
      labels:
        app: fedora
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: fedora
      template:
        metadata:
          labels:
            app: fedora
        spec:
          runtimeClassName: gvisor
          containers:
          - name: fedora
            image: fedora
            command: ["/bin/sleep","10000"]
    
  2. kubectl get pods を使用してポッドの名前を取得し、kubectl exec を使用して、ポッドにインタラクティブに接続します。

    kubectl exec -it [POD-NAME] /bin/sh
    

    /bin/sh セッションで、ポッド内のコンテナに接続されます。

  3. インタラクティブ セッションで、クラスタ メタデータを返す URL にアクセスします。

    curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/kube-env" -H "Metadata-Flavor: Google"
    

    パケットがサイレントで破棄されるため、コマンドが停止し、最終的にタイムアウトになります。

  4. Ctrl + C を押して curl コマンドを終了し、exit を入力してポッドから切断します。

  5. YAML マニフェストから RuntimeClass を削除し、kubectl apply -f [FILENAME] を使用してポッドを再デプロイします。サンドボックス化されたポッドが終了し、GKE Sandbox のないノードで再作成されます。

  6. 新しいポッド名を取得し、kubectl exec を使用してポッドに接続します。curl コマンドを再度実行します。今回は、結果が返されます。この出力例は省略されています。

    ALLOCATE_NODE_CIDRS: "true"
    API_SERVER_TEST_LOG_LEVEL: --v=3
    AUTOSCALER_ENV_VARS: kube_reserved=cpu=60m,memory=960Mi,ephemeral-storage=41Gi;...
    ...
    

    exit と入力して、ポッドから切断します。

  7. デプロイを削除します。

    kubectl delete deployment fedora
    

GKE Sandbox を無効にする

現在、ノードプールを更新して GKE Sandbox を無効にすることはできません。 既存のノードプールで GKE Sandbox を無効にするには、以下のいずれかを行います。

  • サンドボックス化されたポッドを削除します。GKE Sandbox を無効にすると、GKE Sandbox が有効になっているノードがない場合、通常のポッドとして実行されます。次に、GKE Sandbox が有効になっていたノードプールを削除します。あるいは
  • ノードプールのサイズをゼロにしますあるいは
  • RuntimeClassName の値を指定せずにポッドを再作成します。

次のステップ

このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...

Kubernetes Engine のドキュメント