📈

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

2023/10/16に公開

本記事は下記の記事の続きとなっています。

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

前編では GKE の提供されている監視/可視化の機能の概要とメトリクス面での Google Cloud Managed Service for Prometheus についてまとめました。

後編のこちらではログ面での Fluentbit可視化面での Grafana と Hubble UI についてまとめていきたいと思います。

Overview

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

  • Cloud Operations for GKE の機能により Fluentbit ベースのエージェントが自動デプロイされてコンテナ単位のログなどが Cloud Logging で確認できました
  • Grafana への GMP を経由した Cloud Monitoring のメトリクス集約Cloud Logging のプラグインを活用することでのログ集約が実現できました
  • Hubbble UI をデプロイすることで Namespace 単位のトラフィックフローの可視化が容易に実現できました

所感

前編と後編に分けて、GKE の監視/可視化周りを極力手間をかけずにという点にフォーカスして調べてみました。調べれば調べるほど、「そんな機能もあるの!?」と驚くことが多いとともに非常に面白かったです。

特に Cloud MonitoringCloud LoggingGrafana への集約がとてもシームレスにできるようになっていて、今回のテーマにぴったりな調査結果となっていました。

また、Observability の 3 本柱である Metrics / Log / Trace のうち Trace 部分も調べていきたいので次は OpenTelemetryCloud Trace などの分散トレーシングツールも記事にしたいと思います!興味ある方はぜひ Good してください!(笑)

キーワード

Fluentbit

GKE の Cloud Logging とのインテグレーションとして Cloud Operations for GKE という機能があり、こちらをオンしておくと GKE でのコンテナ単位でのログやシステムログを Cloud Logging が収集されます。収集されるログは基本 3 種類、さらに追加で 3 種類のようです。

  • 基本ログ
    • 監査ログ:管理アクティビティ、データアクセス etc
    • システムログ:特定の名前空間の Pod ログ etc
    • アプリケーションログ:ユーザーノード上のシステム以外のコンテナで生成されたログ
  • 追加ログ
    • API サーバーログ:kube-apiserver で生成されたログ
    • Scheduler ログ:kube-scheduler で生成されたログ
    • Controller Manager ログ:kube-controller-manager で生成されたログ

設定変更はコンソールから可能です。

これらのログの収集に利用されているのが Fluentbit になります。正確には下記のようです。(参考

  • GKE 1.17 以降:Fluentbit ベースの Logging エージェント
  • GKE 1.17 以前:Fluentd ベースの Logging エージェント

こちらの記事が Fluentbit でどのように GKE がログを収集しているかを調べていて勉強になりました。

ポイント① - 自動デプロイ -

これらのリソースは kube-system の Namespace に DeamonSet でデプロイされていることがわかります。 上述したように Cloud Operations for GKE の設定がしてあると GKE のプロビジョニングと同時に自動でデプロイされます。

$ kubectl get pod -n kube-system
NAME                                                       READY   STATUS     
fluentbit-gke-klxsh                                        2/2     Running     0          7d14h
fluentbit-gke-ndb9g                                        2/2     Running     0          13d

例えば、アプリケーションログであれば Cloud Logging で「Kubernetes コンテナ」からログを見たいコンテナを選択することで確認できます。ログクエリはこんな感じです。

resource.type="k8s_container"
resource.labels.cluster_name="sample-gke-cluster"
resource.labels.namespace_name="sample"

特段準備しなくてコンテナ単位のログが取得できるのは非常にありがたいですね。

ポイント② - 書き込み権限付与 -

これは私自身がハマったところではあるのですが、最初コンテナログが Cloud Logging で確認することができない事象が発生していました。

よくよくドキュメントを読んで見ると、ログの書き込みには GKE ノードのサービスアカウントにログ書き込み権限である roles/logging.logWriter が必要となるのでこのロールを付与することで解決できました。

ポイント③ - スループット向上 -

監視/可視化の仕組みを構築する上で一番怖いのは知らず知らずのうちにメトリクスやログが欠損していることです。以前の Cloud Operations for GKE のドキュメントのトラブルシューティングには下記のように記述されていたようです。(2022.06 時点)

GKE クラスタで Cloud Operations for GKE を使用していて、GKE クラスタから大量のログを書き込む場合、それらのログの多くが継続的に Cloud Logging に表示されないことがあります。ロギングの量が、サポートされている Cloud Operations for GKE のロギング スループットを超えている可能性があります。

Cloud Operations for GKE は現在、ノードあたり最大 100 KB/秒のロギング スループットをサポートしています。GKE クラスタのノードでこれより多くのロギング スループットが必要な場合は、独自の Fluentd をデプロイしてカスタマイズし、スループットを向上させることをおすすめします。

同様のドキュメントを参照してみると下記のように変更されています。

Cloud Operations for GKE は現在、ノードあたり最大 100 KB/秒のロギング スループットをサポートしています。GKE クラスタ内のいずれかのノードにこれより多くのロギング スループットが必要な場合は、ロギング エージェントのスループットを向上させることをおすすめします。

スループットが足りていない場合には独自の Fluentd をカスタマイズしてスループットを向上とあるところが、ロギングエージェントのスループットを向上と記載が変わっています。

gcloud container clusters create/update コマンドのオプションに --logging-variant=MAX_THROUGHPUT が用意されていてマネージドでスループットの向上が実現できるようです。(参考)

以前のトラブルシュートの記載についてはこちらの記事を参考にさせていただきました。

ポイント④ - カスタマイズ -

Fluentbit ベースのエージェントは下記の設定変更が必要な場面ではカスタマイズができるようです。

  • ログからの機密データの削除
  • STDOUT または STDERR に書き込まれない付加的なログの収集
  • パフォーマンスに関連する特定の設定の使用
  • カスタマイズしたログ形式

こちらの詳細な方法はドキュメントには別途調査したいと思います。

Grafana

メトリクスとログを集約する可視化ツールとしては Grafana を利用してみたいと思います。Google Cloud で利用を検討するとマーケットプレイスがあがると思いますが、今回はドキュメントに沿って一時的な Deployment を利用してみます。(参考)

ひとまず、デプロイしてみて port-forward でアクセスを確認してみます。

kubectl -n ${NAMESPACE_NAME} apply -f  https://raw.githubusercontent.com/GoogleCloudPlatform/prometheus-engine/beb779d32f4dd531a3faad9f2916617b8d9baefd/examples/grafana.yaml
kubectl -n ${NAMESPACE_NAME} port-forward svc/grafana 3000

ポイント① - メトリクス -

メトリクスの可視化に関しては GMP がデプロイされているので素直に Data Source に登録します。こちらは公式ドキュメントを参考にしています。

私の場合は認証がうまくいかなったので、追加で Basic auth をオンにして admin, admin を入力したらうまくいきました。

GKE Dataplane V2 Metrics によって取得できるようになった pod_flow_ingress_flows_count を試しに入力してみると想定通り取れることを確認できました。

また、前編で記載した図から Monarch と PromQL Query API の連携から Google Cloud の Free Metrics を取得できるようになります。こちらも試しに kubernetes_io:cantainer_uptime を入力してみたら想定通り取れることを確認できました。

これで GMP/Cloud Monitoring ⇒ Grafana によって Google Cloud の Free Metrics を含めて幅広いメトリクスを可視化できるようになりました!すごい!

ポイント② - ログ -

ログの可視化に関しては Fluentbit を連携させようと思っていたら便利なプラグインを発見しました。(参考)

Cloud Logging プラグインを利用するとなんと Grafana で Cloud Logging の可視化が可能になります。Add new connection で Logging で検索するとプラグインが出てくるのでインストールします、その後 Data Source に追加します。Data Source の方ではクレデンシャルキーを利用するかノードのサービスアカウントを利用するか選択できます。

ダッシュボードから要素を追加する際に Data Source を Google Cloud Logging と設定して、ログクエリをそのまま入力すると Cloud Logging で出力されるもの同様な情報を取得できました。

これで Fluentbit ⇒ Cloud Logging ⇒ Grafana によってコンテナ単位のログや Google Cloud の Free Logs も可視化できるようになりました!素晴らしい!

ポイント③ - 永続化 -

ここまでやってみて、一時的なテスト用の Deployment は永続されていないことに気づきました。なので PersistentVolumeClaim リソースを使って永続化したいと思います。(参考)

grafana.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: grafana-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: grafana
  name: grafana
spec:
  selector:
    matchLabels:
      app: grafana
  template:
    metadata:
      labels:
        app: grafana
    spec:
      securityContext:
        fsGroup: 472
        supplementalGroups:
          - 0
      containers:
        - name: grafana
          image: grafana/grafana:latest
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 3000
              name: http-grafana
              protocol: TCP
          readinessProbe:
            failureThreshold: 3
            httpGet:
              path: /robots.txt
              port: 3000
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 30
            successThreshold: 1
            timeoutSeconds: 2
          livenessProbe:
            failureThreshold: 3
            initialDelaySeconds: 30
            periodSeconds: 10
            successThreshold: 1
            tcpSocket:
              port: 3000
            timeoutSeconds: 1
          resources:
            requests:
              cpu: 250m
              memory: 750Mi
          volumeMounts:
            - mountPath: /var/lib/grafana
              name: grafana-pv
      volumes:
        - name: grafana-pv
          persistentVolumeClaim:
            claimName: grafana-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: grafana
spec:
  ports:
    - port: 3000
      protocol: TCP
      targetPort: http-grafana
  selector:
    app: grafana
  sessionAffinity: None
  type: LoadBalancer

PersistentVolumeClaim リソースは GKE のストレージに連動していて正常に作成できているとフェーズが Bound と表示されます。

こちらで Pod が入れ替わっても設定を残すことができました!

Hubble UI

最後にトラフィックフローの可視化可能な Hubble UI を試したいと思います。

Hubble UI は GKE Dataplane V2 が有効化され Observability Tools も有効化されていると Hubble UI のマニフェストファイルをデプロイして利用可能となります。(参考)

hubble-ui.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: hubble-ui
  namespace: kube-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: hubble-ui
  labels:
    app.kubernetes.io/part-of: cilium
rules:
  - apiGroups:
      - networking.k8s.io
    resources:
      - networkpolicies
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - componentstatuses
      - endpoints
      - namespaces
      - nodes
      - pods
      - services
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - apiextensions.k8s.io
    resources:
      - customresourcedefinitions
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - cilium.io
    resources:
      - "*"
    verbs:
      - get
      - list
      - watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: hubble-ui
  labels:
    app.kubernetes.io/part-of: cilium
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: hubble-ui
subjects:
  - kind: ServiceAccount
    name: hubble-ui
    namespace: kube-system
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: hubble-ui-nginx
  namespace: kube-system
data:
  nginx.conf: |
    server {
        listen       8081;
        # uncomment for IPv6
        # listen       [::]:8081;
        server_name  localhost;
        root /app;
        index index.html;
        client_max_body_size 1G;
        location / {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            # CORS
            add_header Access-Control-Allow-Methods "GET, POST, PUT, HEAD, DELETE, OPTIONS";
            add_header Access-Control-Allow-Origin *;
            add_header Access-Control-Max-Age 1728000;
            add_header Access-Control-Expose-Headers content-length,grpc-status,grpc-message;
            add_header Access-Control-Allow-Headers range,keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout;
            if ($request_method = OPTIONS) {
                return 204;
            }
            # /CORS
            location /api {
                proxy_http_version 1.1;
                proxy_pass_request_headers on;
                proxy_hide_header Access-Control-Allow-Origin;
                proxy_pass http://127.0.0.1:8090;
            }
            location / {
                # double `/index.html` is required here
                try_files $uri $uri/ /index.html /index.html;
            }
        }
    }
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: hubble-ui
  namespace: kube-system
  labels:
    k8s-app: hubble-ui
    app.kubernetes.io/name: hubble-ui
    app.kubernetes.io/part-of: cilium
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: hubble-ui
  template:
    metadata:
      labels:
        k8s-app: hubble-ui
        app.kubernetes.io/name: hubble-ui
        app.kubernetes.io/part-of: cilium
    spec:
      securityContext:
        fsGroup: 1000
        seccompProfile:
          type: RuntimeDefault
      serviceAccount: hubble-ui
      serviceAccountName: hubble-ui
      containers:
        - name: frontend
          image: quay.io/cilium/hubble-ui:v0.11.0
          ports:
            - name: http
              containerPort: 8081
          volumeMounts:
            - name: hubble-ui-nginx-conf
              mountPath: /etc/nginx/conf.d/default.conf
              subPath: nginx.conf
            - name: tmp-dir
              mountPath: /tmp
          terminationMessagePolicy: FallbackToLogsOnError
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsUser: 1000
            runAsGroup: 1000
            capabilities:
              drop:
                - all
        - name: backend
          image: quay.io/cilium/hubble-ui-backend:v0.11.0
          env:
            - name: EVENTS_SERVER_PORT
              value: "8090"
            - name: FLOWS_API_ADDR
              value: "hubble-relay.kube-system.svc:443"
            - name: TLS_TO_RELAY_ENABLED
              value: "true"
            - name: TLS_RELAY_SERVER_NAME
              value: relay.kube-system.svc.cluster.local
            - name: TLS_RELAY_CA_CERT_FILES
              value: /var/lib/hubble-ui/certs/hubble-relay-ca.crt
            - name: TLS_RELAY_CLIENT_CERT_FILE
              value: /var/lib/hubble-ui/certs/client.crt
            - name: TLS_RELAY_CLIENT_KEY_FILE
              value: /var/lib/hubble-ui/certs/client.key
          ports:
            - name: grpc
              containerPort: 8090
          volumeMounts:
            - name: hubble-ui-client-certs
              mountPath: /var/lib/hubble-ui/certs
              readOnly: true
          terminationMessagePolicy: FallbackToLogsOnError
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsUser: 1000
            runAsGroup: 1000
            capabilities:
              drop:
                - all
      volumes:
        - configMap:
            defaultMode: 420
            name: hubble-ui-nginx
          name: hubble-ui-nginx-conf
        - emptyDir: {}
          name: tmp-dir
        - name: hubble-ui-client-certs
          projected:
            # note: the leading zero means this number is in octal representation: do not remove it
          defaultMode: 0400
            sources:
              - secret:
                  name: hubble-relay-client-certs
                  items:
                    - key: ca.crt
                      path: hubble-relay-ca.crt
                    - key: tls.crt
                      path: client.crt
                    - key: tls.key
                      path: client.key
---
kind: Service
apiVersion: v1
metadata:
  name: hubble-ui
  namespace: kube-system
  labels:
    k8s-app: hubble-ui
    app.kubernetes.io/name: hubble-ui
    app.kubernetes.io/part-of: cilium
spec:
  type: ClusterIP
  selector:
    k8s-app: hubble-ui
  ports:
    - name: http
      port: 80
      targetPort: 8081

こちらで紹介したように Observability が有効化されることで下記のリソースがデプロイされて Pod のネットワークテレメトリーデータが収集されます。

$ 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

上記をデプロイした上で Port-forward してみると下記のような UI を確認できます。

Hubble UI の利用方法や Cilium や eBPF についてはこちらの記事が大変参考になりました。

まとめ

GKE で手間をかけずに監視/可視化の仕組みを手間なく構築するというテーマで、GMP に続いて Fluentbit, Grafana, Hubble UI について調べてみました。

Cloud Operations for GKE によって Fluentbit の自動デプロイおよび Cloud Logging への転送はほぼマネージドといってよいくらいに実施することがなくシームレスに利用ができました。

個人的には GMP/Cloud MonitoringCloud Logging のデータ Grafana への集約がこんなにも簡単にできることに驚きました!!

今後は今回記事で紹介した内容をもとに極力手間をかけずに監視/可視化の仕組みを商用環境で構築して実際に運用してみたいと思います。

参考記事

さいごに

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

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

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

https://twitter.com/pHaya72

Discussion