⚙️

GKE入門:Terraformによるクラスター構築からGrafanaでメトリクスを取得するまで

2023/12/29に公開

1. はじめに

この記事では、TerraformでAutopilotモードのGKEクラスターGoogle Cloud Managed Service for Prometheus(以降、GMPと表記)をデプロイし、Grafanaを利用してメトリクスを取得するまでの流れを紹介します。Terraformのコードとデプロイスクリプトを全て記載するので、この記事の流れに沿うことで、上記の構成をご自身の環境で再現することができます。また、掲題のゴールを達成するために必要な知識も簡単に説明します。ただし、全てを詳細に説明することはしません。あくまで、上記を達成するために必要な知識のロードマップを提供するのみです。

2. 前提知識

ここでは、この記事で必要となる前提知識を簡単に説明します。説明は簡略化して記述しているので、正確かつ詳細な情報は各節の末尾に記した公式ドキュメントをご参照ください。

2-1. Autopilotモード

GKEクラスターの運用モードにはAutopilotとStandardの2つがあります。基本的には、Autopilotモードを使用することが推奨されています。両者の違いを簡単に整理すると、前者はワーカーノードの管理をGoogleに任せるモードで、後者はワーカーノードの管理を自身で行うモードです。例えば、Autopilotモードでは、Googleがノードをセキュアな状態に維持し、ワークロードの負荷に応じてノードをスケールしてくれます。一方、Standardモードでは、デフォルトでは上記のことはやってくれません。もちろん、自身で設定して上記と同様のことを実現することは可能です。多くのことを自身で設定する分、ニーズに合わせた柔軟な設定が可能です。

今回はAutopilotモードのGKEクラスターを構築します。というのも、個人の学習用途でクラスターを立てるのであれば、こちらの方がコストが安くすむからです。Standardモードでは、クラスターを構成するCompute Engineインスタンスに対して料金がかかる一方で、AutopilotモードではPodが要求するリソース量に応じた料金がかかります。当然、Podはワーカーノード上で稼働するものなので、Podの要求リソース量に応じた料金の方が安いという訳です。

参考にした公式ドキュメント

2-2. 限定公開クラスター

通常のGKEクラスターとは異なり、限定公開クラスターではパブリックIPアドレスが付与されていないワーカーノードを利用します(下図)。つまり、ノードをインターネット接続から分離することができます。もちろん、所属するVPCネットワークは異なりますが、コントロールプレーンからノードへのアクセスは可能です。コントロールプレーンはGoogleが管理するプロジェクトのVPCネットワーク内のVirtual Machine上で稼働していますが、コントロールプレーンが属するVPCネットワークとデータプレーンが属するVPCネットワークとはVPC Network Peeringで接続されているからです。


限定公開クラスターのアーキテクチャ(公式ドキュメントより引用)

また、限定公開クラスターのコントロールプレーンにはパブリックエンドポイントとプライベートエンドポイントの2つが存在し、パブリックエンドポイントは必要に応じて無効化することができます。もちろん、セキュリティ的にはパブリックエンドポイントを無効化するのが最も安全です。ですが、今回はパブリックエンドポイントは無効化しません。というのも、プライベートエンドポイントを利用してkubectlコマンドでワークロードを操作するためには、追加で色々設定が必要になるからです。また、パブリックエンドポイントにアクセス可能なIPアドレスも制限しません。前述のことはコントロールプレーンの承認済みネットワークを指定することで実現できますが、記事の内容をシンプルにするためにもここではこの設定を省略します。

参考にした公式ドキュメント

2-3. Workload Identity

Workload IdentityはGKE上で稼働しているワークロードから他のGoogle Cloudサービスに安全にアクセスするための仕組みです。これにより、Kubernetes(以降、K8sと記載)のリソースであるServiceAccountとIAMサービスアカウントを結びつけることができます。つまり、K8sのServiceAccountが割り当てられたPodからGoogle CloudのAPIにアクセスする際、PodはK8sのServiceAccountに紐づけられたIAMサービスアカウントとして認証されるようになります。よって、Podごとに最小権限を付与することができ、よりセキュアにすることができます。

参考にした公式ドキュメント

2-4. Google Cloud Managed Service for Prometheus (GMP)

Prometheusは、コンテナ・K8sを利用したシステムのデファクトスタンダードになっているOSSの監視ツールです。そして、GMPはPrometheus serverの運用をGoogleがフルマネージしてくれるサービスです。

アーキテクチャの概要は下図のようになっています。GMPはCloud Monitoringでも使用されているMonarchというグローバルにスケールするデータストアに対して、メトリクスを書き込むインターフェイスとPromQLでメトリクスを読み込むインターフェイスが追加されたものです。


GMPのアーキテクチャ(公式ドキュメントより引用)

参考にした公式ドキュメント

3. いざ、構築!

ここでは、GKEクラスター・GMPをデプロイするTerraformコードとGrafanaをデプロイするスクリプトを紹介します。もし、自分でも構築してみたいけどTerraformとgcloud CLIの実行環境はどのようにしようかと悩まれた場合は、devcontainerで構築するTerraformとgcloud CLIの実行環境をご参考ください。

3-1. GKEクラスターとGMPの構築

まずは、限定公開のGKEクラスターとGMPを構築します。これらを構築するTerraformコードは以下の通りです。

locals {
  prefix  = "your_prefix"
  project = "your_project_id"
  region  = "your_region"
  zone    = "your_zone"

  k8s_ns_monitoring = "monitoring"
}
resource "google_compute_network" "main" {
  name                    = "${local.prefix}-vpc"
  auto_create_subnetworks = false
  routing_mode            = "REGIONAL"
  mtu                     = 1460
}
resource "google_compute_subnetwork" "main" {
  name                     = "${local.prefix}-subnet-app"
  region                   = local.region
  network                  = google_compute_network.main.id
  ip_cidr_range            = "10.0.0.0/8"
  private_ip_google_access = true
  secondary_ip_range {
    range_name    = "pods"
    ip_cidr_range = "192.168.0.0/16"
  }
  secondary_ip_range {
    range_name    = "services"
    ip_cidr_range = "192.169.0.0/16"
  }
}
resource "google_container_cluster" "main" {
  name = "${local.prefix}-cluster"

  location            = local.region
  project             = local.project
  deletion_protection = false

  network    = google_compute_network.main.name
  subnetwork = google_compute_subnetwork.main.name

  enable_autopilot = true
  allow_net_admin  = true
  cluster_autoscaling {
    auto_provisioning_defaults {
      oauth_scopes = ["https://www.googleapis.com/auth/cloud-platform"]
    }
  }

  addons_config {
    horizontal_pod_autoscaling {
      disabled = false
    }
    http_load_balancing {
      disabled = false
    }
  }

  networking_mode = "VPC_NATIVE"
  ip_allocation_policy {
    cluster_secondary_range_name  = google_compute_subnetwork.main.secondary_ip_range.0.range_name
    services_secondary_range_name = google_compute_subnetwork.main.secondary_ip_range.1.range_name
  }

  private_cluster_config {
    enable_private_nodes    = true
    enable_private_endpoint = false
    master_ipv4_cidr_block  = "192.170.0.0/28"
    master_global_access_config {
      enabled = true
    }
  }

  logging_config {
    enable_components = [
      "SYSTEM_COMPONENTS",
      "APISERVER",
      "CONTROLLER_MANAGER",
      "SCHEDULER",
      "WORKLOADS"
    ]
  }
  monitoring_config {
    enable_components = [
      "SYSTEM_COMPONENTS",
      "APISERVER",
      "SCHEDULER",
      "CONTROLLER_MANAGER",
      "STORAGE",
      "HPA",
      "POD",
      "DAEMONSET",
      "DEPLOYMENT",
      "STATEFULSET"
    ]
    managed_prometheus {
      enabled = true
    }
  }
}

3-2. 監視用のIAMサービスアカウントの作成

次に、メトリクスの読み取り権限を割り当てたIAMサービスアカウントを作成します。このサービスアカウントを作成するTerraformコードは以下の通りです。

resource "google_service_account" "monitor" {
  account_id   = "monitor"
  display_name = "monitor"
  project      = local.project
  description  = "Service account for collecting metrics from Cloud Monitoring"
}
resource "google_service_account_iam_binding" "iam_workloadIdentityUser" {
  service_account_id = google_service_account.monitor.name
  role               = "roles/iam.workloadIdentityUser"
  members = [
    "serviceAccount:${google_service_account.monitor.email}",
    "serviceAccount:${local.project}.svc.id.goog[${local.k8s_ns_monitoring}/default]"
  ]
}
resource "google_project_iam_binding" "monitoring_viewer" {
  project = local.project
  role    = "roles/monitoring.viewer"
  members = [
    "serviceAccount:${google_service_account.monitor.email}"
  ]
}

ここで作成したIAMサービスアカウントは、後でK8sクラスターのmonitoringというネームスペースに属するdefaultのK8s ServiceAccountに紐付けます。こうすることで、そのK8s ServiceAccountが紐づいたPodからのメトリクス読み取りを可能とします。

3-3. Prometheus frontend UIとGrafanaのデプロイ

続いて、構築したGKEクラスター上にPrometheus frontend UIとGrafanaをデプロイします。実はGrafanaだけではダメで、認証プロキシとしてPrometheus frontend UIが必要になります。というのも、Google Cloud APIsはOAuth2を使用した認証を必要としますが、GrafanaはOAuth2をサポートしていないからです。

以下はPrometheus frontend UIとGrafanaのデプロイするスクリプトです。公式ドキュメントのStandalone Prometheus frontend UIGrafanaを参考に、スクリプトとして書き起こしました。

#!/bin/bash

PROJECT_ID=your_project_id
NAMESPACE_NAME=monitoring

# Create a namespace for monitoring
kubectl create ns ${NAMESPACE_NAME}

# Wait for the namespace to be created
timeout=300
start_time=$(date +%s)
echo -n "Waiting for namespace '${NAMESPACE_NAME}' to be created"
while true; do
    current_time=$(date +%s)
    elapsed_time=$(( current_time - start_time ))
    if [ $elapsed_time -ge $timeout ]; then
        echo "Timeout reached. Namespace '${NAMESPACE_NAME}' not created in 5 minutes."
        exit 1
    fi

    echo -n "."

    if kubectl get namespace ${NAMESPACE_NAME} &> /dev/null; then
        echo "Namespace '${NAMESPACE_NAME}' successfully created."
        break
    fi

    sleep 1
done

# Connect the default service account in the namespace to the monitoring service account
kubectl annotate serviceaccount \
  --namespace ${NAMESPACE_NAME} \
  default \
  iam.gke.io/gcp-service-account=monitor@${PROJECT_ID}.iam.gserviceaccount.com

# Deploy the Prometheus UI
# [ref] https://cloud.google.com/stackdriver/docs/managed-prometheus/query#ui-prometheus
curl https://raw.githubusercontent.com/GoogleCloudPlatform/prometheus-engine/v0.7.4/examples/frontend.yaml |
sed 's/\$PROJECT_ID/primordial-will-407619/' |
kubectl apply -n ${NAMESPACE_NAME} -f -

# Deploy the Grafana
# [ref] https://cloud.google.com/stackdriver/docs/managed-prometheus/query#grafana-deploy
kubectl -n ${NAMESPACE_NAME} apply -f  https://raw.githubusercontent.com/GoogleCloudPlatform/prometheus-engine/beb779d32f4dd531a3faad9f2916617b8d9baefd/examples/grafana.yaml

# Wait for the frontend service to become available
START_TIME=$(date +%s)
echo -n "Waiting for pods to become available"
while true; do
    CURRENT_TIME=$(date +%s)
    ELAPSED_TIME=$((CURRENT_TIME - START_TIME))
    if [ "${ELAPSED_TIME}" -ge 300 ]; then
        echo "Timeout: Pod did not become available within 5 minutes."
        exit 1
    fi

    echo -n "."

    AVAILABLE_PROMETHEUS_PODS=$(kubectl get deployment frontend -n ${NAMESPACE_NAME} -o jsonpath="{.status.availableReplicas}")
    AVAILABLE_GRAFANA_PODS=$(kubectl get deployment grafana -n ${NAMESPACE_NAME} -o jsonpath="{.status.availableReplicas}")
    if [ "${AVAILABLE_PROMETHEUS_PODS}" -ge 1 ] 2>/dev/null && [ "${AVAILABLE_GRAFANA_PODS}" -ge 1 ] 2>/dev/null; then
        echo "One or more pods for both Promethus and Grafana are available."
        break
    fi

    sleep 1
done

# Port-forward the Grafana to your local machine
kubectl -n ${NAMESPACE_NAME} port-forward svc/grafana 3000

上記のスクリプトでは、以下5つのことを順番に行ないます。

  1. monitoringという名前のネームスペースを作成する。
  2. monitoringネームスペース内のdefaultのK8s ServiceAccountを前節で作成した監視用IAMサービスアカウントと紐付ける。
  3. Prometheus frontend UIをデプロイする。
  4. Grafanaをデプロイする。
  5. Grafanaサービスをlocalhostの3000番ポートにポートフォワーディングする。

上記のスクリプトの中で実際にクラスターに適用しているマニフェストについては、以下をご確認ください。

3-4. Grafanaからメトリクスが取得できることを確認

最後に、Grafanaからメトリクスを取得できることを確認します。

まず、http://localhost:3000にアクセスしてGrafanaを開き、user:passwordをadmin:adminとしてログインします。そして、Prometheus UIを認証プロキシとして使用するよう公式ドキュメントを参考にしてデータソースを構成します。最後に、Exploreを開いてupのようなクエリを実行し、下図のようにメトリクスが取れていることが確認できたら、目的達成です🎉

4. まとめ

この記事では、GKEクラスターとGoogle Cloud Managed Service for Prometheusをデプロイし、Grafanaでメトリクスを取得するまでの流れを説明しました。そして、これらの手順をTerraformとシェルスクリプトとして書き起こし、誰でも再現できるようにしました。これからGKEやGMPに入門する方にとって何かの助けになれば幸いです!

Discussion