⚙️

k8sgpt + LocalAI で k8s クラスタ分析を試してみる

2024/08/12に公開

概要

k8sGPT は AI を利用して k8s クラスタの問題点やトラブルシュートを行うためのプロジェクトです。最初の commit が 2023 年 3 月と比較的歴史の浅いプロジェクトですが、2023 年 12 月に CNCF の sandbox プロジェクトに採用され、現時点で github start ~ 5k 程となっています。

https://k8sgpt.ai/

k8sGPT の特徴の一つとして ChatGPT や大手クラウドプロバイダーの AI 系サービスだけでなく、LocalAI や ollama 等のローカルで構築可能な LLM も利用できる点があります。これによりデータの機密性の観点からパブリックな AI サービスの利用が難しい場合や、ローカルで用意したモデルを使って分析を行いたいような場合に最適です。サポートしている AI は ドキュメントを参照
今回はローカルに構築した LocalAI と組み合わせて使ってみます。

環境構築

LocalAI の構築

LocalAI は docker や kubernetes 上で構築できますが、ここでは手軽さを重視して docker を使用します。

https://localai.io/docs/getting-started/models/

docker イメージは公式のものが用意されていますが、用途によっていくつかイメージタグが分かれています。分類としては大きく分けて以下のようになっています (詳細は https://localai.io/basics/container/ を参照)。

  • GPU を使うか CPU のみを使うか
  • 事前に設定済みのモデルを使用するか

今回は 設定済みのモデルを使用し、CPU のみ の設定で行うため、localai/localai:latest-aio-cpu のイメージタグを使用します。
docker-compose.yml の例が UsageGithub にあるのでこれを参考に作成。

docker-compose.yml
services:
  api:
    container_name: localai
    image: localai/localai:latest-aio-cpu
    ports:
      - 8080:8080
    environment:
      LOCALAI_API_KEY: test
    volumes:
      - ./models:/build/models:cached

LocalAI は様々な設定項目がありますがここでは基本的にデフォルト設定を使用します。
また、認証されたリクエストのみを受け付けるために LOCALAI_API_KEY に api kye を設定します。通常の api key と同様に推測されにくいランダムな文字列が推奨されますが、簡単のため test に指定。
docker-compose up -d でコンテナを起動すると https://localai.io/basics/container/#all-in-one-images に記載の各モデルのダウンロードが行われます。docker logs localai のコンテナログより進捗が確認できます。

ダウンロードが完了してログに LocalAI API is listening! Please connect to the endpoint for API documentation. endpoint=http://0.0.0.0:8080 が表示されるとリクエストを受け付けるようになるので、Try it out にある api を実行して動作を確認します。
今回は api key を設定しているので、ヘッダーに指定しないリクエストは拒否されます。

$ curl http://192.168.3.204:8080/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d '{ "model": "gpt-4", "messages": [{"role": "user", "content": "How are you doing?"}] }'

{"message":"Authorization header missing"}%

"Authorization: Bearer [api_key]" を指定するとリクエストが通ります。

$ curl http://192.168.3.204:8080/v1/chat/completions \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer test" \
    -d '{ "model": "gpt-4", "messages": [{"role": "user", "content": "How are you doing?"}] }'

{
  "created": 1723306961,
  "object": "chat.completion",
  "id": "fc893abe-6ae6-4ff8-856b-dfda6ed41138",
  "model": "gpt-4",
  "choices": [
    {
      "index": 0,
      "finish_reason": "stop",
      "message": {
        "role": "assistant",
        "content": "Hello! I'm doing well, thank you for asking. How about you? How can I assist you today?"
      }
    }
  ],
  "usage": {
    "prompt_tokens": 14,
    "completion_tokens": 24,
    "total_tokens": 38
  }
}

LocalAI 側のコンテナログでアクセスログを確認可能。 ip=192.168.3.30 からの通信が上記のリクエストに対応。

10:08AM INF Success ip=127.0.0.1 latency="71.495µs" method=GET status=200 url=/readyz
10:08AM WRN Client error ip=192.168.3.30 latency="77.404µs" method=POST status=401 url=/v1/chat/completions
10:08AM INF Trying to load the model 'b5869d55688a529c3738cb044e92c331' with the backend '[llama-cpp llama-ggml gpt4all llama-cpp-fallback rwkv piper stablediffusion whisper huggingface bert-embeddings /build/backend/python/openvoice/run.sh /build/backend/python/parler-tts/run.sh /build/backend/python/vllm/run.sh /build/backend/python/vall-e-x/run.sh /build/backend/python/transformers-musicgen/run.sh /build/backend/python/petals/run.sh /build/backend/python/bark/run.sh /build/backend/python/sentencetransformers/run.sh /build/backend/python/exllama2/run.sh /build/backend/python/sentencetransformers/run.sh /build/backend/python/coqui/run.sh /build/backend/python/transformers/run.sh /build/backend/python/rerankers/run.sh /build/backend/python/autogptq/run.sh /build/backend/python/exllama/run.sh /build/backend/python/mamba/run.sh /build/backend/python/diffusers/run.sh]'
10:08AM INF [llama-cpp] Attempting to load
10:08AM INF Loading model 'b5869d55688a529c3738cb044e92c331' with backend llama-cpp
WARNING: failed to read int from file: open /sys/class/drm/card0/device/numa_node: no such file or directory
WARNING: error parsing the pci address "virtio0"
10:08AM INF [llama-cpp] attempting to load with AVX2 variant
10:08AM INF [llama-cpp] Loads OK
10:09AM INF Success ip=192.168.3.30 latency=28.564574887s method=POST status=200 url=/v1/chat/completions

K8sGPT の構築

k8sGPT は CLI でコマンドを実行して解析する方法、または k8s クラスタに operator をデプロイして分析する方法がありますが、ここでは Operator を使用します。
Operator は helm でインストールできます。

https://docs.k8sgpt.ai/getting-started/in-cluster-operator/

helm repo add k8sgpt https://charts.k8sgpt.ai/
helm repo update
helm install k8sgpt k8sgpt/k8sgpt-operator -n k8sgpt-operator-system --create-namespace

次に kind: K8sGPT のカスタムリソースを作成します。これが backend provider と呼ばれる backend (ここでは LocalAI) と通信してクラスタ内の分析を行うリソースとなります。
先ほど作成した LocalAI の api key を secret に作成。

kubectl create secret generic k8sgpt-localai-secret --from-literal=api-key=test -n k8sgpt-operator-system

LocalAI を backend に指定するには、Github の例 に従ってマニフェストを用意します。変更が必要な箇所は以下。

properties Description Value
backend 通信先の backend provider 名称を指定。 localai
model 通信先 backend provider で分析に使用するモデルの名称を指定。LocalAI では text Generation のモデル名が gpt-4 になっているのでこれを指定。 gpt-4
baseUrl 通信先 backend provider のエンドポイントを指定 http://192.168.3.204:8080/v1
version K8sGPT のバージョンを指定。現時点では v0.3.40 が最新版 v0.3.40
k8sgpt-localai.yml
apiVersion: core.k8sgpt.ai/v1alpha1
kind: K8sGPT
metadata:
  name: k8sgpt-localai
  namespace: k8sgpt-operator-system
spec:
  ai:
    enabled: true
    model: gpt-4
    secret:
      name: k8sgpt-localai-secret
      key: api-key
    backend: localai
    baseUrl: http://192.168.3.204:8080/v1
  noCache: false
  version: v0.3.40

デプロイすると operator が作成を検知して k8sGPT pod が起動します。

$ k get pod
NAME                                                              READY   STATUS    RESTARTS   AGE
k8s-operator-k8sgpt-operator-controller-manager-69896dc68dmjg2j   2/2     Running   0          69s
k8sgpt-localai-67b4bd6497-jj8wl                                   1/1     Running   0          49s

分析

上記で使う準備ができたので、ドキュメント にある以下のマニフェストをデプロイしてエラーを発生させます。

apiVersion: v1
kind: Pod
metadata:
  name: broken-pod
  namespace: default
spec:
  containers:
    - name: broken-pod
      image: nginx:1.a.b.c  # イメージタグが不正なので pod が起動しない
      livenessProbe:
        httpGet:
          path: /
          port: 81
        initialDelaySeconds: 3
        periodSeconds: 3

デプロイ後に少し待つと分析が完了し、結果が results.core.k8sgpt.ai リソースとして作成されます。

$ k get results.core.k8sgpt.ai
NAME                                KIND          BACKEND
argocdargocdapplicationcontroller   StatefulSet   localai
backstagebackstagefront             Service       localai
backstagesamplenginxsvc             Service       localai
defaultbrokenpod                    Pod           localai

分析の詳細は yaml の spec から確認できます。分析結果としてはイメージ pull に失敗していること、その対処方法が提案されています。

$ k get results.core.k8sgpt.ai  defaultbrokenpod -o yaml
apiVersion: core.k8sgpt.ai/v1alpha1
kind: Result
metadata:
  creationTimestamp: "2024-08-11T10:25:45Z"
  generation: 1
  labels:
    k8sgpts.k8sgpt.ai/backend: localai
    k8sgpts.k8sgpt.ai/name: k8sgpt-localai
    k8sgpts.k8sgpt.ai/namespace: k8sgpt-operator-system
  name: defaultbrokenpod
  namespace: k8sgpt-operator-system
  resourceVersion: "8013182"
  uid: ae75161d-8d96-42e3-a80e-c931ccc96352
spec:
  backend: localai
  details: |-
    Error: The Kubernetes container is unable to pull the specified image "nginx:1.a.b.c" due to a network issue or image availability problem.
    Solution: 1) Check your internet connection. 2) Verify the image is available and tagged correctly. 3) Retry the image pull operation. If the problem persists, consider using a different image version.
  error:
  - text: Back-off pulling image "nginx:1.a.b.c
  kind: Pod
  name: default/broken-pod
  parentObject: ""
status:
  lifecycle: historical

k8sgpt pod は server mode で起動しているため、分析結果は grpc を使うことでクラスタ内外からも取得できます。
k8sgpt pod 作成時に svc も作成されるので CLUSTER-IP を確認。

$ k get svc
NAME                                                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
k8sgpt-localai                                            ClusterIP   10.109.241.92   <none>        8080/TCP   10m

クラスタ内から上記の ip に対して grpcurl を実行すると json 形式で結果を取得できます。内容は上記の result の spec.error[].text となっています。

$ grpcurl -plaintext -d '{"namespace": "default"}' 10.107.21.110:8080 schema.v1.ServerService/Analyze
{
  "status": "ProblemDetected",
  "problems": 1,
  "results": [
    {
      "kind": "Pod",
      "name": "default/broken-pod",
      "error": [
        {
          "text": "Back-off pulling image \"nginx:1.a.b.c\""
        }
      ]
    }
  ]
}

ドキュメントによると explain: true を指定すると詳細も合わせて取得できそうなのですが、これを指定すると何故か pod がエラーとなり CrashLoopBackOff で起動しなくなります。

$ grpcurl -plaintext -d '{"explain": true, "namespace": "default"}' 10.107.21.110:8080 schema.v1.ServerService/Analyze
ERROR:
  Code: Unavailable
  Message: error reading from server: EOF
$ k logs k8sgpt-localai-67b4bd6497-qcmfq k8sgpt
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x261b43b]

goroutine 1 [running]:
github.com/k8sgpt-ai/k8sgpt/cmd/serve.init.func1(0xc0001db000?, {0x2f83ad6?, 0x4?, 0x2f83ada?})
        /workspace/cmd/serve/serve.go:152 +0x5db
github.com/spf13/cobra.(*Command).execute(0x5361b40, {0x53f9000, 0x0, 0x0})
        /go/pkg/mod/github.com/spf13/cobra@v1.8.1/command.go:989 +0xab1
github.com/spf13/cobra.(*Command).ExecuteC(0x535d920)
        /go/pkg/mod/github.com/spf13/cobra@v1.8.1/command.go:1117 +0x3ff
github.com/spf13/cobra.(*Command).Execute(...)
        /go/pkg/mod/github.com/spf13/cobra@v1.8.1/command.go:1041
github.com/k8sgpt-ai/k8sgpt/cmd.Execute({0x37e78ec?, 0x0?}, {0x37e78ed?, 0x51693c0?}, {0x37e78ee?, 0xc0000061c0?})
        /workspace/cmd/root.go:59 +0x91
main.main()
        /workspace/main.go:25 +0x3d

エラー内容を github で検索するといくつか issue や PR が見つかるので、こちらは近いうちに修正されるかもしれません。


このクラスタは backstage の記事を書いた際に使用したものを流用しているため argocd や backstage 関連のリソースがいくつかデプロイ済みとなっていますが、そちらに関しても問題が検出されているので見てみます。
argocd に関する問題では argocd-application-controller という StatefulSet が存在しない svc argocd-application-controller を使用しているという指摘になっています。

argocd
- apiVersion: core.k8sgpt.ai/v1alpha1
  kind: Result
  metadata:
    creationTimestamp: "2024-08-11T10:25:45Z"
    generation: 1
    labels:
      k8sgpts.k8sgpt.ai/backend: localai
      k8sgpts.k8sgpt.ai/name: k8sgpt-localai
      k8sgpts.k8sgpt.ai/namespace: k8sgpt-operator-system
    name: argocdargocdapplicationcontroller
    namespace: k8sgpt-operator-system
    resourceVersion: "8013179"
    uid: b5411baf-cb92-4f37-82a1-fefbe2a0a942
  spec:
    backend: localai
    details: |-
      Error: StatefulSet uses a non-existent service argocd/argocd-application-controller.
      Solution: 1. Check if the service name is correct. 2. Verify if the service is deployed. 3. Ensure the service is accessible within the cluster. 4. If the issue persists, recreate the StatefulSet with correct service details.
    error:
    - sensitive:
      - masked: fCJqMndQ
        unmasked: argocd
      - masked: MVQ5Yl5vLTg7d0d5LDJmUHRjQjhmMXxeOVpOM0k=
        unmasked: argocd-application-controller
      text: StatefulSet uses the service argocd/argocd-application-controller which
        does not exist.
    kind: StatefulSet
    name: argocd/argocd-application-controller
    parentObject: ""
  status:
    lifecycle: historical

実際にリソースを見てみると StatefulSet は存在していますが、たしかに argocd-application-controller という svc はありません。
代わりに svc argocd-applicationset-controller が deployment argocd-applicationset-controller に関連づいています。

$ k get statefulsets.apps
NAME                            READY   AGE
argocd-application-controller   1/1     14d

$ k get svc
NAME                                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
argocd-applicationset-controller          ClusterIP   10.99.253.179    <none>        7000/TCP,8080/TCP            14d
argocd-dex-server                         ClusterIP   10.111.75.121    <none>        5556/TCP,5557/TCP,5558/TCP   14d
argocd-metrics                            ClusterIP   10.97.97.124     <none>        8082/TCP                     14d
argocd-notifications-controller-metrics   ClusterIP   10.111.78.10     <none>        9001/TCP                     14d
argocd-redis                              ClusterIP   10.111.104.228   <none>        6379/TCP                     14d
argocd-repo-server                        ClusterIP   10.96.70.22      <none>        8081/TCP,8084/TCP            14d
argocd-server                             ClusterIP   10.110.184.220   <none>        80/TCP,443/TCP               14d
argocd-server-metrics                     ClusterIP   10.102.135.119   <none>        8083/TCP                     14d

$ k describe svc argocd-applicationset-controller
Name:              argocd-applicationset-controller
Namespace:         argocd
Labels:            app.kubernetes.io/component=applicationset-controller
                   app.kubernetes.io/name=argocd-applicationset-controller
                   app.kubernetes.io/part-of=argocd
Annotations:       <none>
Selector:          app.kubernetes.io/name=argocd-applicationset-controller
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.99.253.179
IPs:               10.99.253.179
Port:              webhook  7000/TCP
TargetPort:        webhook/TCP
Endpoints:         10.244.1.20:7000
Port:              metrics  8080/TCP
TargetPort:        metrics/TCP
Endpoints:         10.244.1.20:8080
Session Affinity:  None
Events:            <none>

$ k get pod -o wide argocd-application-controller-0 argocd-applicationset-controller-8485455fd5-vhrj7
NAME                                                READY   STATUS    RESTARTS       AGE   IP            NODE     NOMINATED NODE   READINESS GATES
argocd-application-controller-0                     1/1     Running   1 (4d1h ago)   13d   10.244.1.24   k8s-w1   <none>           <none>
argocd-applicationset-controller-8485455fd5-vhrj7   1/1     Running   1 (4d1h ago)   14d   10.244.1.20   k8s-w1   <none>           <none>

ただ上記リソースは argocd のインストール手順に沿ってデプロイされたものであり、そもそも pod 側からは関連付いているサービスを確認することができないのでどうやって StatefulSet が argocd-application-controller を使用していることを検知したのかは疑問です。
生成 AI 系サービスでも内容が間違っていることはよくあることなので、上記の指摘もあまり妥当ではないと思われます。

Backend 関連の指摘では、svc backstage-front の宛先 pod が設定されていないという内容になっています。

backend
- apiVersion: core.k8sgpt.ai/v1alpha1
  kind: Result
  metadata:
    creationTimestamp: "2024-08-11T10:25:45Z"
    generation: 1
    labels:
      k8sgpts.k8sgpt.ai/backend: localai
      k8sgpts.k8sgpt.ai/name: k8sgpt-localai
      k8sgpts.k8sgpt.ai/namespace: k8sgpt-operator-system
    name: backstagebackstagefront
    namespace: k8sgpt-operator-system
    resourceVersion: "8013181"
    uid: b1d551d3-f078-4570-b82c-bc4ca92a57bb
  spec:
    backend: localai
    details: |-
      Error: The service with label app.kubernetes.io/name=backstage has no available endpoints.
      Solution: Check the service and endpoint configuration, ensure the deployment is running, and verify the service is correctly associated with the desired endpoints. If needed, update or recreate the service and endpoints accordingly.
    error:
    - sensitive:
      - masked: eVF1L3wsISNKUTBPVmJUWnkyLHN3fA==
        unmasked: app.kubernetes.io/name
      - masked: NmxoLDJ7UnRW
        unmasked: backstage
      text: Service has no endpoints, expected label app.kubernetes.io/name=backstage
    kind: Service
    name: backstage/backstage-front
    parentObject: ""
  status:
    lifecycle: historical

リソースを見ると確かに Endpoints が設定されていないので、こちらは妥当な指摘であることが確認できます。

$ k describe svc backstage-front
Name:              backstage-front
Namespace:         backstage
Labels:            <none>
Annotations:       <none>
Selector:          app.kubernetes.io/name=backstage
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.110.88.138
IPs:               10.110.88.138
Port:              <unset>  80/TCP
TargetPort:        7007/TCP
Endpoints:         <none>
Session Affinity:  None
Events:            <none>

実際に分析を行っているのは backend provider の AI サービス(ここでは LocalAI) なので分析内容はモデルによる要因も大きいかと思われますが、単にエラーとなっているリソースを検出するだけでなく上記のような設定に不備があるリソースなども検出できるようになっています。
k8sGPT の分析に対応しているリソースは Analyzer と呼ばれており、Github にサポートしている Analyze のリストがあります。

operator の architecture

かなり簡潔な図ですが operator の architecture は以下のようになっています。


operator のアーキテクチャ図。ドキュメント より引用

図中の K8sGPT deployment が K8sGPT pod に対応しており、これが backend provider に指定した外部 LLM と通信してリソースの分析を行います。
そして k8sGPT operator が K8sGPT pod と通信し、分析結果を元に result カスタムリソースとして結果を公開するような仕組みになっています。

operator pod のログを見ると Creating new client for 10.108.64.236:8080 という行がありますが、これが通信対象の k8sGPT pod 内の k8sgpt コンテナの ip アドレスに対応しています。
k8sGPT pod の分析結果を元に argocdargocdapplicationcontroller などの指摘項目を result として作成していることが確認できます。
また、一度 result が作成された後も Reconciling を実行し、指摘項目が解消されたかどうか定期的に確認しています。

operator pod のログ
2024-08-11T10:19:02Z    INFO    Starting workers        {"controller": "k8sgpt", "controllerGroup": "core.k8sgpt.ai", "controllerKind": "K8sGPT", "worker count": 1}
Finished Reconciling k8sGPT
Finished Reconciling k8sGPT
Creating new client for 10.108.64.236:8080
Connection established between 10.108.64.236:8080 and localhost with time out of 1 seconds.
Remote Address : 10.108.64.236:8080
K8sGPT address: 10.108.64.236:8080
Created result argocdargocdapplicationcontroller
Created result backstagesamplenginxsvc
Created result backstagebackstagefront
Created result defaultbrokenpod
Finished Reconciling k8sGPT

Creating new client for 10.108.64.236:8080
Connection established between 10.108.64.236:8080 and localhost with time out of 1 seconds.
Remote Address : 10.108.64.236:8080
K8sGPT address: 10.108.64.236:8080
Checking if argocdargocdapplicationcontroller is still relevant
Checking if backstagesamplenginxsvc is still relevant
Checking if backstagebackstagefront is still relevant
Checking if defaultbrokenpod is still relevant
Finished Reconciling k8sGPT

指摘箇所を修正した場合は特に解消された等のログは出力されませんが、Checking if [resource] is still relevant がなくなるので判断できます。

operator pod のログ
# defaultbrokenpod を修正したとき

Creating new client for 10.108.64.236:8080
Connection established between 10.108.64.236:8080 and localhost with time out of 1 seconds.
Remote Address : 10.108.64.236:8080
K8sGPT address: 10.108.64.236:8080
Checking if backstagebackstagefront is still relevant
Checking if argocdargocdapplicationcontroller is still relevant
Checking if backstagesamplenginxsvc is still relevant
Finished Reconciling k8sGPT

おわりに

k8sGPT は比較的歴史の浅いプロジェクトということもあって、ドキュメントがけっこう簡潔だったり設定項目がまだ少ないという面もありますが、近年の LLM ブームに相まって今後の機能拡張が期待されます。
コンセプトの一つに Codified SRE knowledge knows what to search for とあるように、知識や経験が必要となる k8s のトラブルシュートやトリアージ、脆弱性評価といった SRE に関連する作業を AI を使って実行できるというのが大きなメリットであると言えます。
ちなみに CNCF でのブログもあります。

https://www.cncf.io/blog/2024/07/11/now-what-kubernetes-troubleshooting-with-ai/

Discussion