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

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

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

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

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

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

目標

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

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

費用

このドキュメントでは、課金対象である次の 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_ADMINSYS_ADMINSYS_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. まず sysctl を使用してノード構成を適用し、次に kubectl を使用してノードから taint を削除する、特権 init コンテナを実行します。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
    

    このマニフェストは、DaemonSet にノードから Taint を削除する権限を付与する ServiceAccount、ClusterRole、ClusterRoleBinding を作成します。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 の初期コンテナのログを調べて、その出力を確認します。

    kubectl logs POD_NAME -c configure-and-untaint
    

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

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

  2. テイストが削除されたことを確認します。

    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 を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

次のステップ