GKE で手間をかけずに Let's オブザーバビリティ ! - 後編 -
本記事は下記の記事の続きとなっています。
GKE で手間をかけずに Let's オブザーバビリティ! -前編-
前編では 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 Monitoring と Cloud Logging の Grafana への集約がとてもシームレスにできるようになっていて、今回のテーマにぴったりな調査結果となっていました。
また、 オブザーバビリティの 3 本柱である Metrics / Log / Trace のうち Trace 部分も調べていきたいので次は OpenTelemetry や Cloud 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
で生成されたログ
- API サーバーログ:
設定変更はコンソールから可能です。
これらのログの収集に利用されているのが 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
こちらで紹介したようにオブザーバビリティ機能が有効化されることで下記のリソースがデプロイされて 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 Monitoring と Cloud Logging のデータ Grafana への集約がこんなにも簡単にできることに驚きました!!
今後は今回記事で紹介した内容をもとに極力手間をかけずに監視/可視化の仕組みを商用環境で構築して実際に運用してみたいと思います。
参考記事
- GKE Dataplane V2 observability が Preview になりました
- GKEでCloud Loggingにログを取り込んでいたら気づかぬ間にログが欠損していた件
- GKE はどのようにログを収集しているのか
さいごに
AWS と Google Cloud で構築したデータ基盤の開発・運用に携わっているデータエンジニアです。5 年くらい携わっていて、この業務がきっかけで Google Cloud が好きになりました。
現在は React のフロントエンジニアとして修行中です。
X では Google Cloud 関連の情報を発信をしています。
Discussion