DaemonSet を使用して GKE ノードを自動的にブートストラップする

このチュートリアルでは、DaemonSet を使用して Google Kubernetes Engine(GKE)クラスタのノードをカスタマイズする方法について説明します。DaemonSet によって、すべての(または選択された)ノードで Pod のコピーが実行されるようになります。新しいノードがクラスタに追加されると、DaemonSet から Pod も実行されます。

クラスタの初期化に使用するツールとシステムが、ワークロードの実行に使用するツールとシステムとは異なる場合は、環境の管理にかかる労力が増えます。たとえば、構成管理ツールを使用してクラスタノードを初期化する場合は、残りのワークロードが実行されるランタイム環境外の手順に依存します。DaemonSet を使用すると、GKE ノードの変更に使用するのと同じツールを使用してワークロードをオーケストレートできます。

このチュートリアルの目的は、システム管理者、システム エンジニア、インフラストラクチャ オペレーターが Kubernetes クラスタの初期化を合理化することです。

このページを読む前に、次のことを理解しておいてください。

このチュートリアルでは、Kubernetes の taint と toleration を使用して、アプリケーション ワークロードをスケジュールする前に、DaemonSet によってノードが構成されるようにする方法について説明します。

目標

このチュートリアルでは、次のことを行います。

  • GKE クラスタをプロビジョニングする。
  • ノード構成を適用する前に、ノードプールを taint してワークロードのスケジューリングを防止します。
  • ノードを構成して taint を削除する DaemonSet をデプロイする。
  • クラスタノードが構成され、taint が削除されていることを確認します。

費用

このドキュメントでは、課金対象である次の Google Cloudコンポーネントを使用します。

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。

新規の Google Cloud ユーザーは無料トライアルをご利用いただける場合があります。

このドキュメントに記載されているタスクの完了後、作成したリソースを削除すると、それ以上の請求は発生しません。詳細については、クリーンアップをご覧ください。

始める前に

  1. Google Cloud アカウントにログインします。 Google Cloudを初めて使用する場合は、 アカウントを作成して、実際のシナリオで Google プロダクトのパフォーマンスを評価してください。新規のお客様には、ワークロードの実行、テスト、デプロイができる無料クレジット $300 分も差し上げます。
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

  4. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  5. Verify that billing is enabled for your Google Cloud project.

特権 DaemonSet のセキュリティへの影響

DaemonSet(または任意の Pod)で securityContext: privileged: true 設定を使用すると、強力ですが、その Pod のコンテナ分離境界のほとんどが無効になるため、セキュリティに重大な影響を及ぼします。次のセキュリティ制限とリスクに注意する必要があります。

  • コンテナ エスケープまたはホストの侵害: 特権コンテナ アプリケーションまたはイメージ内の脆弱性により、ホストノードで root アクセスが直接取得される可能性があります。
  • 最小権限の原則に違反する: 特権モードでは、特定のタスクに必要な権限をはるかに超えるすべての機能が付与されます。この広範なアクセスにより、コンテナが侵害された場合に発生する可能性のある損害が増大します。
  • ノードの不安定化: 誤った sysctl 値や rm -rf /host/boot などのコマンドなど、偶発的または悪意のあるコマンドが特権コンテナ内で実行される可能性があります。このようなコマンドは、ホストノードのオペレーティング システムをクラッシュさせたり、破損させたりする可能性があります。
  • ラテラル ムーブメント: 特権付き DaemonSet を介して 1 つのノードを侵害すると、攻撃者は他のノード、Kubernetes コントロール プレーン、接続されたシステムを攻撃するための強力な足がかりを得ることができます。
  • データ漏洩: ホスト ファイル システム(/)への無制限のアクセスにより、認証情報、鍵、他の Pod に属するデータなど、ノードに保存されている機密データが漏洩する可能性があります(hostPath ボリュームを使用している場合)。
  • 攻撃対象領域の拡大: 特権モードでは、ホストカーネルのシステムコールと機能がコンテナ内からの潜在的なエクスプロイトにさらされます。

セキュリティ リスクを回避するには、次のベスト プラクティスと緩和策に注意する必要があります。

  • 特権モードの使用を避ける: 最も安全な方法は、privileged: true 設定を完全に避けることです。
  • Linux 機能を使用する: 昇格された権限が必要な場合は、完全な権限ではなく、securityContext.capabilities.add フィールドで NET_ADMIN、SYS_ADMIN、SYS_MODULE などの特定の Linux 機能を付与できます。このアプローチは、最小権限の原則に従うものです。広範な権限を付与するよりも、このアプローチをおすすめします。
  • スコープを制限する: コンテナが侵害された場合に影響を抑えるため、専用の(汚染されている可能性のある)ノードプールでのみ特権 DaemonSet を実行します。
  • ポリシーを適用する: Policy Controller や Gatekeeper などのツールを使用して、特権コンテナのデプロイを制限、監査、または正当化するポリシーを作成します。
  • 信頼できるイメージをスキャンして使用する: Binary Authorization と厳格なイメージ スキャンを使用して、審査済みの信頼できるコンテナ イメージのみが権限昇格で実行されるようにします。
  • ホストマウントを最小限に抑える: 必要な特定のホストパスのみをマウントし、可能な限り readOnly: true を使用します。ルート ファイル システム全体(/)のマウントは避けてください。
  • 定期的な監査を実施する: privileged: true 設定で実行されているすべてのワークロードを定期的に確認します。

環境をブートストラップする

このセクションでは、次の操作を行います。

  1. 必要な Cloud APIs を有効にする。
  2. GKE クラスタ内のノードに対する制限された権限を持つサービス アカウントをプロビジョニングする。
  3. GKE クラスタを準備する。
  4. ユーザーにクラスタ管理者権限を付与する。

Cloud APIs を有効にする

  1. Cloud Shell を開きます。

    Cloud Shell を開く

  2. Google Cloud プロジェクトを選択します。

    gcloud config set project project-id
    

    project-id は、このチュートリアル用に作成または選択したGoogle Cloud プロジェクトの ID に置き換えます。

  3. Kubernetes Engine API を有効にします。

    gcloud services enable container.googleapis.com
    

GKE クラスタを管理するためのサービス アカウントのプロビジョニング

このセクションでは、クラスタ内のノードに関連付けられたサービス アカウントを作成します。このチュートリアルでは、GKE ノードでデフォルトのサービス アカウントの代わりにこのサービス アカウントを使用します。ベスト プラクティスとして、アプリケーションの実行に必要なロールとアクセス権のみをサービス アカウントに付与することをおすすめします。

サービス アカウントに必要なロールは次のとおりです。

  • モニタリング閲覧者のロール(roles/monitoring.viewer)。このロールは、モニタリング データに対する読み取り専用権限を付与します。
  • モニタリング指標の書き込みロール(roles/monitoring.metricWriter)。このロールでは、モニタリング データの書き込みが許可されます。
  • ログ書き込みロール(roles/logging.logWriter)。このロールは、ログを書き込む権限を付与します。

サービス アカウントをプロビジョニングするには、次の操作を行います。

  1. Cloud Shell で、サービス アカウント名を格納する環境変数を初期化します。

    GKE_SERVICE_ACCOUNT_NAME=ds-init-tutorial-gke
    
  2. サービス アカウントを作成します。

    gcloud iam service-accounts create "$GKE_SERVICE_ACCOUNT_NAME" \
      --display-name="$GKE_SERVICE_ACCOUNT_NAME"
    
  3. サービス アカウントのメール アカウント名を格納する環境変数を初期化します。

    GKE_SERVICE_ACCOUNT_EMAIL="$(gcloud iam service-accounts list \
        --format='value(email)' \
        --filter=displayName:"$GKE_SERVICE_ACCOUNT_NAME")"
    
  4. サービス アカウントに Identity and Access Management(IAM)のロールをバインドします。

    gcloud projects add-iam-policy-binding \
        "$(gcloud config get-value project 2> /dev/null)" \
        --member serviceAccount:"$GKE_SERVICE_ACCOUNT_EMAIL" \
        --role roles/monitoring.viewer
    gcloud projects add-iam-policy-binding \
        "$(gcloud config get-value project 2> /dev/null)" \
        --member serviceAccount:"$GKE_SERVICE_ACCOUNT_EMAIL" \
        --role roles/monitoring.metricWriter
    gcloud projects add-iam-policy-binding \
        "$(gcloud config get-value project 2> /dev/null)" \
        --member serviceAccount:"$GKE_SERVICE_ACCOUNT_EMAIL" \
        --role roles/logging.logWriter
    

GKE クラスタを準備する

このセクションでは、GKE クラスタを起動し、権限を付与して、クラスタ構成を終了します。

このチュートリアルのコンセプトのデモを行うには、汎用ノードの数が比較的少ない小規模なクラスタで十分です。1 つのノードプール(デフォルトのノードプール)があるクラスタを作成します。

  • Cloud Shell で、リージョン GKE クラスタを作成して起動します。

    gcloud container clusters create ds-init-tutorial \
        --enable-ip-alias \
        --machine-type=n1-standard-2 \
        --metadata disable-legacy-endpoints=true \
        --node-labels=app=default-init \
        --node-locations us-central1-a,us-central1-b,us-central1-c \
        --no-enable-basic-auth \
        --no-issue-client-certificate \
        --num-nodes=1 \
        --location us-central1 \
        --service-account="$GKE_SERVICE_ACCOUNT_EMAIL"
    

DaemonSet を使用してノード構成を適用する

このセクションでは、ノードプールに taint を適用して、構成が完了する前にワークロードがノードで実行されないようにします。次に、次の処理を行う DaemonSet をデプロイします。

  1. taint の toleration を使用して、taint が追加されたノードに Pod をスケジュールします。
  2. 特権 init コンテナを実行します。このコンテナは、まず sysctl を使用してノード構成を適用し、次に kubectl を使用してノードから taint を削除します。taint を削除すると、ノードでワークロードをスケジュール設定できるようになります。
  3. アイドル状態を維持し、リソースを消費しない一時停止コンテナのスケジュールを設定して実行し、DaemonSet が構成に使用される Pod を再スケジュールしないようにします。

このチュートリアルでは、構成の例として vm.max_map_count=262144 カーネル パラメータを適用します。

  1. デフォルトのノードプールに taint を適用します。

    gcloud container node-pools update default-pool \
      --cluster=ds-init-tutorial \
      --node-taints=node.config.status/stage=configuring:NoSchedule \
      --region=us-central1
    

    この taint を使用すると、DaemonSet Pod など、この taint を許容する Pod のみがこのノードプールでスケジュールされます。

  2. テイストが適用されていることを確認します。

    kubectl describe nodes -l cloud.google.com/gke-nodepool=default-pool | grep Taints
    

    ノードのステータスに node.config.status/stage=configuring:NoSchedule が表示されます。

  3. 次のマニフェストを auto-untaint-daemonset.yaml として保存します。

    # WARNING: This DaemonSet runs as privileged, which has significant
    # security implications. Only use this on clusters where you have
    # strict controls over what is deployed.
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: node-config-sa
      namespace: default
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: node-patcher-role
    rules:
    - apiGroups: [""]
      resources: ["nodes"]
      # Permissions needed to read and remove a taint from the node.
      verbs: ["get", "patch", "update"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: node-config-binding
    subjects:
    - kind: ServiceAccount
      name: node-config-sa
      namespace: default
    roleRef:
      kind: ClusterRole
      name: node-patcher-role
      apiGroup: rbac.authorization.k8s.io
    ---
    apiVersion: apps/v1
    kind: DaemonSet
    metadata:
      name: auto-untaint-daemonset
      labels:
        app: auto-untaint-configurator
    spec:
      selector:
        matchLabels:
          app: auto-untaint-configurator
      updateStrategy:
        type: RollingUpdate
      template:
        metadata:
          labels:
            app: auto-untaint-configurator
        spec:
          serviceAccountName: node-config-sa
          hostPID: true
          # Toleration now matches the taint on your node.
          tolerations:
          - key: "node.config.status/stage"
            operator: "Equal"
            value: "configuring"
            effect: "NoSchedule"
          volumes:
          - name: host-root-fs
            hostPath:
              path: /
          initContainers:
          - name: configure-and-untaint
            image: ubuntu:22.04 # Using a standard container image.
            securityContext:
              privileged: true # Required for chroot and sysctl.
            env:
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            volumeMounts:
            - name: host-root-fs
              mountPath: /host
            command: ["/bin/bash", "-c"]
            args:
            - |
              # Using explicit error checking for each critical command.
    
              # Define the configuration and taint details.
              SYSCTL_PARAM="vm.max_map_count"
              SYSCTL_VALUE="262144"
              TAINT_KEY="node.config.status/stage"
    
              echo "Running configuration on node: ${NODE_NAME}"
    
              # 1. APPLY CONFIGURATION
              echo "--> Applying ${SYSCTL_PARAM}=${SYSCTL_VALUE}..."
              if ! chroot /host sysctl -w "${SYSCTL_PARAM}=${SYSCTL_VALUE}"; then
                echo "ERROR: Failed to apply sysctl parameter." >&2
                exit 1
              fi
              echo "--> Configuration applied successfully."
    
              # 2. UNTAINT THE NODE
              # This command removes the taint from the node this Pod is running on.
              echo "--> Untainting node ${NODE_NAME} by removing taint ${TAINT_KEY}..."
              if ! /host/home/kubernetes/bin/kubectl taint node "${NODE_NAME}" "${TAINT_KEY}:NoSchedule-"; then
                echo "ERROR: Failed to untaint the node." >&2
                exit 1
              fi
              echo "--> Node has been untainted and is now schedulable."
          # The main container is minimal; it just keeps the Pod running.
          containers:
          - name: pause-container
            image: registry.k8s.io/pause:3.9
    

    このマニフェストは、ServiceAccount、ClusterRole、ClusterRoleBinding を作成して、ノードから taint を削除する DaemonSet 権限を付与します。DaemonSet は、configuring:NoSchedule テイントを許容する各ノードに Pod をデプロイします。この Pod は、sysctl 構成(vm.max_map_count=262144)を適用してノードのテイストを削除する特権 init コンテナを実行します。これにより、ノードをスケジュール可能にします。一時停止コンテナが起動し、Pod の実行が維持されます。

    init コンテナは特権モードで実行されるため、セキュリティ上の影響があります。詳細については、特権 DaemonSet のトレードオフとセキュリティ制限をご覧ください。

  4. 次のようにマニフェストを適用します。

    kubectl apply -f auto-untaint-daemonset.yaml
    
  5. DaemonSet Pod が作成されていることを確認し、Running 状態になるまで待ちます。

    kubectl get pods -l app=auto-untaint-configurator -o wide
    

    Running 状態は、init コンテナが正常に完了したことを示します。Pod 名をメモしておきます。この名前は、次のセクションで初期化を確認するために使用します。

初期化手順を検証する

ノードの構成が完了したら、ログを確認して結果を確認できます。

  1. いずれかの Pod の init コンテナのログを確認して、出力を確認します。

    kubectl logs POD_NAME -c configure-and-untaint
    

    POD_NAME は、Pod の名前に置き換えます。

    構成とノードの taint 解除が成功したことを示す出力が表示されます。

  2. taint が削除されたことを確認します。

    kubectl describe nodes -l cloud.google.com/gke-nodepool=default-pool | grep Taints
    

    ノードのステータスに Taints: <none> が表示されるか、キー node.config.status/stage を持つ taint が表示されます。

クリーンアップ

このチュートリアルで使用したリソースについて Google Cloud アカウントに課金されないようにするには、このチュートリアル用に作成したプロジェクトを削除します。このチュートリアル専用のプロジェクトを作成した場合は、完全に削除できます。既存のプロジェクトを使用したが、削除したくない場合は、次の手順でプロジェクトをクリーンアップします。

プロジェクトをクリーンアップする

プロジェクトを削除せずにクリーンアップするには、このチュートリアルで作成したリソースを削除する必要があります。

  1. Cloud Shell で、GKE クラスタを削除します。

    gcloud container clusters delete ds-init-tutorial --quiet --region us-central1
    
  2. サービス アカウントを削除します。

    gcloud iam service-accounts delete "$GKE_SERVICE_ACCOUNT_EMAIL" --quiet
    

プロジェクトを削除する

課金を停止する最も簡単な方法は、チュートリアル用に作成したプロジェクトを削除することです。

  1. Google Cloud コンソールで [リソースの管理] ページに移動します。

    [リソースの管理] に移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

次のステップ