📈

GKE で手間をかけずに Let's Observability!-前編-

2023/10/11に公開

運用している GKE 上のアプリケーションでバグが発生すると、解析するのログをひたすら眺めるデバッグ大会が最近よく開催され、アプリケーションに詳しい人物がいるとログの範囲を絞っていき原因を発見することができるのですが、自分のように最近ジョインした人間からするとこの絞り込みすらままなりません。

これはデバッグの負担が偏る話でもありますし、バグの発生地点の切り分けがしづらいという点でも運用のあるべき姿ではないと思い Observability に注目したいと考えました!

今回は極力手間をかけずに仕組みを構築するという点にフォーカスしながら GKE の監視/可視化周りに関する手段について調査しました。

こちらの前編では概要と Google Cloud Managed Service for Prometheus を中心に、後編では Fluentbit, Grafana, Hubble UI について記載します。

GKE で手間をかけずに Let's Observability ! - 後編 -

Overview

GKE の監視/可視化の手法について検証

  • Google Cloud Managed Service for Prometheus + Fluentbit + Grafana + Hubble UI で極力手間をかけずに監視/可視化の仕組みを実現できました

  • Google Cloud Managed Service for PrometheusFluentbit は設定をオンにすることで自動でデプロイされます(Standard と Autopilot で多少異なります)

  • 可視化のための Prometheus UIGrafanaHubble UI の追加のデプロイも比較的簡単で、各種連携も労力少なくできました

  • Google Cloud Managed Service for Prometheus には複数のデータ収集方法があり、管理コストと収集できるメトリクスのトレードオフで選択可能となっていました

所感

監視/可視化の手法は様々な選択肢があると思いますが、初手は作り込みすぎずマネージドなものを中心に仕組みを構築できればと思い調べ始めましたが、思いの外多くの機能が用意されていて驚きました。 一方で公式ドキュメントは機能が追加されていったこともあり、異なる項目に関連事項が入ってたりして読みづらさも感じました。

今回のテーマに関連しているものがどこかに体系的にまとまっていたら嬉しいなという思いもあり、こちらと次の記事でまとめたいと思いました。どなたかの役に立てたなら幸いです!

キーワード

検証環境の準備

GKE のプロビジョニング

GKE は Standard でも Autopilot でも今回は関係ないので、Standard とします。ポイントとなるのが、 Observability に関連する 4 つのオプションです。

# GKE Standard クラスタを構築
gcloud container clusters create sample-cluster \
    --enable-dataplane-v2 \
    --enable-dataplane-v2-metrics \
    --enable-managed-prometheus \
    --dataplane-v2-observability-mode=INTERNAL_VPC_LB \
    --location asia-northeast1-a
  • --enable-dataplane-v2: GKE Dataplane V2 を有効化
  • --enable-managed-prometheus: Google Cloud Managed Service for Prometheus を有効化
  • --enable-dataplane-v2-metrics: GKE Dataplane V2 Metrics を有効化
  • --dataplane-v2-observability-mode=...:GKE Dataplane V2 Observability Tool を有効化

1. GKE Dataplane V2

GKE のネットワークに Cilium と eBPF を使用して実装されます。従来のパケットルーティングがこれらによって効率的に処理することができ、ネットワーキングに関してスケーラビリティやより高いセキュリティを獲得することができます。(参考)

コンソール画面ではこちらが有効化されます。余談ですが従来のネットワーキングには Calico が利用されており、この設定で Cilium が利用されることから Calico は無効化されてるみたいです。

2. Google Cloud Managed Service for Prometheus(GMP)

その名の通りでマネージドの Prometheus が利用可能です。4 つのモードが存在していて、それぞれで管理コストと収集できるメトリクスが異なってくるようです。(参考)

  • マネージドデータ収集
  • セルフデプロイのデータ収集
  • OpenTelemetry Collector
  • Ops エージェント

今回は手間をかけずにがポイントなのでマネージドデータ収集のモードを利用します。詳細は後述します。

コンソール画面ではこちらが有効化されます。

3. GKE Dataplane V2 Metrics

GKE Dataplane V2 Observability が Preview になったことで利用可能となった機能です。この機能では下記のトラフィック情報を提供してくれます。(参考

  • トラフィック フロー: GKE が Pod と Service の間のフローを処理する方法に関する分析情報。
  • ネットワーク ポリシーの適用: GKE が Kubernetes ネットワーク ポリシーを適用する方法に関する情報。

これらの情報は GMP や Cloud Monitoring でメトリクスを確認することができます。デフォルトでは下記のメトリクスを収集可能です。

4. GKE Dataplane V2 Observability Tool

こちらも Metrics 同様に GKE Dataplane V2 Observability が Preiview になったことで利用可能となった機能です。

利用方法は後述しますが、GKE のプロビジョニングと同時に下記の Hubble に関連するリソースがデプロイされます。(参考)

$ kubectl get po -n kube-system | grep hubble 
NAME                                 READY   STATUS      RESTARTS   AGE
hubble-generate-certs-init-lgslx     0/1     Completed   0          7d14h
hubble-relay-66859b686c-dz8m5        2/2     Running     0          7d14h

これらのリソースに加えて、Hubble-UI コンポーネントをデプロイするとトラフィックフローを簡単に可視化することができます!

サンプルアプリのデプロイ

前回のGKE Gateway Controller でサービスネットワーキング で利用した Nginx のアプリを利用します。

加えて、Fast API で実装した簡易アプリもデプロイしておきます。

Dockerfile
FROM python:3
USER root

RUN mkdir /src
RUN mkdir /src/app
COPY ./main.py /src/app/main.py
COPY ./requirements.txt /src/requirements.txt

RUN pip3 install --upgrade pip
RUN pip3 install -r /src/requirements.txt

WORKDIR /src/app

ENTRYPOINT ["uvicorn", "main:app", "--host", "0.0.0.0", "--reload"]
main.py
main.py
from fastapi import FastAPI, status
import logging

logger = logging.getLogger('uvicorn')
app = FastAPI()

@app.get("/", status_code=status.HTTP_200_OK)
async def root():
        logger.info('info-test')
        return {"message": "Hello World"}

@app.get("/fastapi/", status_code=status.HTTP_200_OK)
async def test():
        logger.info('info-test')
        logger.warning('warning-test')
        return {"message": "Hello World FastAPI"}
requirements.txt
fastapi
uvicorn
service-fastapi.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: fastapi-latest
  namespace: sample
  labels:
    apps: fastapi-latest
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fastapi-latest
  template:
    metadata:
      labels:
        app: fastapi-latest
    spec:
      containers:
      - name: fastapi-latest
        image: asia-northeast1-docker.pkg.dev/sample-project/sample/fastapi:latest
        imagePullPolicy: Always
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: fastapi-latest
  namespace: sample
  annotations:
    cloud.google.com/app-protocols: '{"my-http-port":"HTTP"}'
    cloud.google.com/neg: '{"exposed_ports": {"80":{}}}'
spec:
  selector:
    app: fastapi-latest
  type: ClusterIP
  ports:
  - name: my-http-port
    port: 80
    targetPort: 8000
httproute-fastapi.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
  name: fastapi-httproute
  namespace: sample
spec:
  parentRefs:
  - kind: Gateway
    name: sample-gateway
    namespace: sample
  # hostnames:
  # - "*"
  rules:
  - matches:
    - path:
        value: /fastapi/
    backendRefs:
    - name: fastapi-latest
      port: 80

Google Cloud Managed Service for Promethus(GMP)

マネージドのデータ収集モード(マネージドコレクション)の特徴は下記です。(公式ドキュメント抜粋)

  • すべての Kubernetes 環境で Google が推奨するアプローチです。
  • GKE UI、gcloud CLI、kubectl CLI、または Terraform を使用してデプロイします。
  • スクレイピングとルールは、軽量のカスタム リソース(CR)を使用して構成されます。
  • 簡単に使用できるフルマネージド環境が必要な場合に適しています。
  • prometheus-operator 構成ファイルから簡単に移行できます。
  • 現在の Prometheus のユースケースのほとんどに対応しています。

ということでより手間をかけずにという点においては良さそうです。マネージドコレクションでは Prometheus ベースのコレクタを Deamonset として実行して同一ノード上のデータを収集してくれます。

マネージドコレクションではノードのサービスアカウントの権限で Cloud Monitor に転送してくれるため Monitoring Metric Writer ロールの付与が必要です。

また、データについては追加料金なしに 24 ヶ月間保管されるようです。ありがたいですね。

ポイント① - 自動デプロイリソース -

--enable-managed-prometheus 有効化すると GKE プロビジョニング時に gmp-system という Namespace に下記のリソースがデプロイされています。(参考)

$ kubectl get po -n gmp-system
NAME                              READY   STATUS    RESTARTS      AGE
alertmanager-0                    2/2     Running   0             43m
collector-8rz8j                   2/2     Running   0             41m
collector-qqcrv                   2/2     Running   0             41m
collector-wrlnv                   2/2     Running   0             41m
gmp-operator-859cf8894c-rls2k     1/1     Running   0             43m
rule-evaluator-7c9848c877-wkgt5   2/2     Running   1 (41m ago)   41m
  • gmp-operator: Deployment : Google Cloud Managed Service for Prometheus の Kubernetes オペレーター
  • rule-evaluator: Deployment : アラートルールと記録ルールの構成と実行に使用
  • collector: DaemonSet : 各コレクタと同じノードで実行されている Pod からの指標のみを取得し、コレクションを水平にスケーリング
  • alertmanager: StatefulSet : トリガーされたアラートを推奨通知チャネルに送信するように構成

こちらの図をみるとマネージドコレクションは Cloud Monitoring の Ingest API を経由して Monarch に集約されて PromQL Query API や Cloud monitoring Query API で取得できるようになっていますね。 Google Cloud のメトリクスも Monarch に集約されていることから Prometheus でも取得できると!勉強になりました。

ポイント② - Prometheus UI -

この GMP ですがデプロイされたリソースと合わせて PodMonitoring カスタムリソースなるものを使用して、コンテナのデータを収集をするようです。

$ kubectl get podmonitoring -A
NAMESPACE     NAME                                      AGE
kube-system   advanced-datapath-observability-metrics   7d16h

私の Standard 環境では Kube-system にインストールされていることを確認しました。実際にメトリクスを収集できているかを確認するために Prometheus UI をデプロイします。(Cloud Monitoring でも確認できますが、次の記事にまとめる Grafana での認証プロキシとしても必要となるためデプロイします)

マニフェストはこちらです。名称あたりを適当に変更して port-forward でアクセスします。

prometheus-ui.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus-ui
spec:
  replicas: 2
  selector:
    matchLabels:
      app: prometheus-ui
  template:
    metadata:
      labels:
        app: prometheus-ui
    spec:
      automountServiceAccountToken: true
      nodeSelector:
        kubernetes.io/os: linux
        kubernetes.io/arch: amd64
      containers:
      - name: prometheus-ui
        image: gke.gcr.io/prometheus-engine/frontend:v0.7.0-gke.0
        args:
        - "--web.listen-address=:9090"
        - "--query.project-id=$PROJECT_ID"
        ports:
        - name: web
          containerPort: 9090
        readinessProbe:
          httpGet:
            path: /-/ready
            port: web
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - all
          privileged: false
          runAsGroup: 1000
          runAsNonRoot: true
          runAsUser: 1000
        livenessProbe:
          httpGet:
            path: /-/healthy
            port: web
---
apiVersion: v1
kind: Service
metadata:
  name: prometheus-ui
spec:
  clusterIP: None
  selector:
    app: prometheus-ui
  ports:
  - name: web
    port: 9090
$kubectl apply -f prometheus-ui.yaml
$kubectl port-forward -n gmp-system svc/prometheus-ui 9090

GKE Dataplane V2 Metrics で取得可能になった pod_flow_ingress_flows_count で検索してみると、 job="advanced-datapath-observability-metrics", location="asia-northeast1-a", namespace="kube-system" という表記があり、デプロイされている kube-systemadvanced-datapath-observability-metrics という PodMonitoring リソースから取得できていることがわかります!!

今回の場合は、kube-systemPodMontoring リソースが各 Namespace から Prometheus にメトリクスを転送しているようで、pod_flow_ingress_flows_count{namespace_name="sample"} で検索すると、sample に存在する Pod のメトリクスも確認できました。

また、Cloud Monitoring でも同様のメトリクスを確認することができます。

ポイント③ - ClusterPodMonitoring -

上記は Standard クラスタの話で、 Autopilot クラスタでは GMP を有効化しただけではメトリクスを取得することができません。下記の ClusterPodMonitoring リソースをデプロイする必要があるようです。(参考)

clusterpodmonitoring.yaml
apiVersion: monitoring.googleapis.com/v1
kind: ClusterPodMonitoring
metadata:
  name: advanced-datapath-observability-metrics
spec:
  selector:
    matchLabels:
      k8s-app: cilium
  endpoints:
  - port: flowmetrics
    interval: 60s
    metricRelabeling:
    # only keep denormalized pod flow metrics
    - sourceLabels: [__name__]
      regex: 'pod_flow_(ingress|egress)_flows_count'
      action: keep
    # extract pod name
    - sourceLabels: [__name__, destination]
      regex: 'pod_flow_ingress_flows_count;([a-zA-Z0-9-\.]+)/([a-zA-Z0-9-\.]+)'
      replacement: '${2}'
      targetLabel: pod_name
      action: replace
    - sourceLabels: [__name__, source]
      regex: 'pod_flow_egress_flows_count;([a-zA-Z0-9-\.]+)/([a-zA-Z0-9-\.]+)'
      replacement: '${2}'
      targetLabel: pod_name
      action: replace
    # extract workload name by removing 2 last "-XXX" parts
    - sourceLabels: [pod_name]
      regex: '([a-zA-Z0-9-\.]+)((-[a-zA-Z0-9\.]+){2})'
      replacement: '${1}'
      targetLabel: workload_name
      action: replace
    - sourceLabels: [pod_name]
      regex: '([a-zA-Z0-9\.]+)((-[a-zA-Z0-9\.]+){1})'
      replacement: '${1}'
      targetLabel: workload_name
      action: replace
    # extract pod namespace
    - sourceLabels: [__name__, destination]
      regex: 'pod_flow_ingress_flows_count;([a-zA-Z0-9-\.]+)/([a-zA-Z0-9-\.]+)'
      replacement: '${1}'
      targetLabel: namespace_name
      action: replace
    - sourceLabels: [__name__, source]
      regex: 'pod_flow_egress_flows_count;([a-zA-Z0-9-\.]+)/([a-zA-Z0-9-\.]+)'
      replacement: '${1}'
      targetLabel: namespace_name
      action: replace
    # extract remote workload name
    - sourceLabels: [__name__, source]
      regex: 'pod_flow_ingress_flows_count;([a-zA-Z0-9-\.]+)/([a-zA-Z0-9-\.]+)'
      replacement: '${2}'
      targetLabel: remote_workload
      action: replace
    - sourceLabels: [__name__, destination]
      regex: 'pod_flow_egress_flows_count;([a-zA-Z0-9-\.]+)/([a-zA-Z0-9-\.]+)'
      replacement: '${2}'
      targetLabel: remote_workload
      action: replace
    # extract remote workload namespace
    - sourceLabels: [__name__, source]
      regex: 'pod_flow_ingress_flows_count;([a-zA-Z0-9-\.]+)/([a-zA-Z0-9-\.]+)'
      replacement: '${1}'
      targetLabel: remote_namespace
      action: replace
    - sourceLabels: [__name__, destination]
      regex: 'pod_flow_egress_flows_count;([a-zA-Z0-9-\.]+)/([a-zA-Z0-9-\.]+)'
      replacement: '${1}'
      targetLabel: remote_namespace
      action: replace
    # default remote workload class to "pod"
    - replacement: 'pod'
      targetLabel: remote_class
      action: replace
    # extract remote workload class from reserved identity
    - sourceLabels: [__name__, source]
      regex: 'pod_flow_ingress_flows_count;reserved:([^/]*)'
      replacement: '${1}'
      targetLabel: remote_class
      action: replace
    - sourceLabels: [__name__, destination]
      regex: 'pod_flow_egress_flows_count;reserved:([^/]*)'
      replacement: '${1}'
      targetLabel: remote_class
      action: replace
  targetLabels:
    metadata: []

こちらの ClusterPodMonitoring リソースと Collector が連携してメトリクスが取得可能になります。

まとめ

GKE で手間をかけずに監視/可視化の仕組みを手間なく構築するというテーマで、まずは Google Cloud Managed Service for Prometheus について調べてみました。

マネージドな Promethus ということで GKE のプロビジョニングに設定をオンにするだけで必要最低限のリソースがデプロイされて、すぐに利用可能な状態となっていました。

可視化では Cloud Monitoring か Promethus UI をデプロイするかということで、こちらも使い慣れているものを選択可能という点も良かったです。

参考記事

さいごに

AWS と Google Cloud で構築したデータ基盤の開発・運用に携わっているデータエンジニアです。5 年くらい携わっていて、この業務がきっかけで Google Cloud が好きになりました。

現在は React のフロントエンジニアとして修行中です。

X では Google Cloud 関連の情報を発信をしています。

https://twitter.com/pHaya72

Discussion