天天看點

建立具有運作時可觀測性的 Kubernetes 叢集

作者:岱軍

翻譯自 Creating a Kubernetes Cluster with Runtime Observability 。

Kubernetes 是一個開源系統,在雲原生環境中被廣泛使用,用于提供在雲中部署和擴充容器化應用程式的方法。它觀察日志和名額的能力是衆所周知和有文檔記錄的,但其對應用程式跟蹤的可觀測性是新的。

以下是 Kubernetes 生态系統中最近活動的簡要概述:

  • 第一次讨論始于 2018 年 12 月,即第一個是關于實作 instrumentation 的 PR 。
  • KEP(Kubernetes 增強提案)于 2020 年 1 月建立,後來範圍限定為 API 伺服器(KEP 647 API Server Tracing),而 Kubelet 的新 KEP 于 2021 年 7 月提出(KEP 2831 Kubelet Tracing)。
  • etcd(Kubernetes 将其用作内部資料存儲)于 2020 年 11 月開始讨論跟蹤(此處),并于 2021 年 5 月合并了第一個版本。
  • containerd 和 CRI-O 是兩個用于 Kubernetes 的容器運作時接口,于 2021 年開始實作跟蹤(CRI-O 為 2021 年 4 月,containerd 為 2021 年 8 月)。
  • API 伺服器跟蹤在 v1.22(2021 年 8 月)中作為 alpha 版本釋出,在 v1.27(2023 年 4 月)中作為 beta 版本釋出。
  • Kubelet 跟蹤在 v1.25(2022 年 8 月)中作為 alpha 版本釋出,在 v1.27(2023 年 4 月)中作為 beta 版本釋出。

在調查 Kubernetes 跟蹤的目前狀态時,我們發現很少有文章記錄如何啟用它,比如 Kubernetes 部落格上關于 kubelet 可觀測性的文章。我們決定記錄我們的發現,并提供分步說明,在本地設定 Kubernetes 并檢查跟蹤。

您将學習如何将此 instrumentation 與 Kubernetes 一起使用,通過設定本地可觀測性環境,然後在啟用跟蹤的情況下執行 Kubernetes 的本地安裝,開始觀察其 API(kube-apiserver)、節點代理 (kubelet)和容器運作時(containerd)上的跟蹤。

首先,在本地計算機上安裝以下工具:

  • Docker:允許我們運作容器化環境的容器環境
  • k3d:一個使用 Docker 運作 k3s(輕量級 Kubernetes 發行版)的包裝器
  • kubectl:與叢集互動的 Kubernetes CLI

設定可觀測性堆棧以監視跟蹤

若要設定可觀測性堆棧,你将運作 OpenTelemetry(OTel) Collector ,該工具可從不同的應用接收遙測資料并将其發送到跟蹤後端。作為跟蹤後端,您将使用 Jaeger ,這是一個開源工具,可收集跟蹤并允許您查詢它們。

在您的計算機上,建立一個名為 kubetracing 的目錄并建立一個名為 otel-collector.yaml 的檔案,複制以下代碼片段的内容,并将其儲存在您喜歡的檔案夾中。

此檔案将配置 OpenTelemetry Collector 以接收 OpenTelemetry 格式的跟蹤并将其導出到 Jaeger 。

receivers:
  otlp:
    protocols:
      grpc:
      http:
processors:
  probabilistic_sampler:
    hash_seed: 22
    sampling_percentage: 100
  batch:
    timeout: 100ms
exporters:
  logging:
    logLevel: debug
  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [probabilistic_sampler, batch]
      exporters: [otlp/jaeger, logging]           

之後,在同一檔案夾中,建立一個 docker-compose.yaml 檔案,該檔案将有兩個容器,一個用于 Jaeger ,另一個用于 OpenTelemetry Collector。

services:
  jaeger:
    healthcheck:
      test:
        - CMD
        - wget
        - --spider
        - localhost:16686
      timeout: 3s
      interval: 1s
      retries: 60
    image: jaegertracing/all-in-one:latest
    restart: unless-stopped
    environment:
      - COLLECTOR_OTLP_ENABLED=true
    ports:
      - 16686:16686
  otel-collector:
    command:
      - --config
      - /otel-local-config.yaml
    depends_on:
      jaeger:
        condition: service_started
    image: otel/opentelemetry-collector:0.54.0
    ports:
      - 4317:4317
    volumes:
      - ./otel-collector.yaml:/otel-local-config.yaml           

現在,通過在 kubetracing 檔案夾中運作以下指令來啟動可觀測性環境:

docker compose up
           

這将啟動 Jaeger 和 OpenTelemetry Collector ,使他們能夠從其他應用程式接收跟蹤。

建立具有運作時可觀測性的 Kubernetes 叢集

設定可觀測性環境後,建立配置檔案以在 kube-apiserver 、 kubelet 和 containerd 中啟用 OpenTelemetry 跟蹤。

在 kubetracing 檔案夾中,建立一個名為 config 的子檔案夾,該子檔案夾将包含以下兩個檔案。

首先是 apiserver-tracing.yaml ,它包含 kube-apiserver 用于導出包含 Kubernetes API 執行資料跟蹤的跟蹤配置。在此配置中,将 API 設定為使用 samplingRatePerMillion 配置發送 100% 的跟蹤。将終端節點設定為 host.k3d.internal:4317 ,以允許由 k3d/k3s 建立的叢集調用計算機上的另一個 API。在這種情況下,OpenTelemetry Collector 通過 docker compose 部署到了端口 4317 。

apiVersion: apiserver.config.k8s.io/v1beta1
kind: TracingConfiguration
endpoint: host.k3d.internal:4317
samplingRatePerMillion: 1000000 # 100%           

第二個檔案是 kubelet-tracing.yaml,它為 kubelet 提供了額外的配置。在這裡,您将啟用功能标志 KubeletTracing ( Kubernetes 1.27 中的 beta 版功能,撰寫本文時的目前版本)并設定與 kube-apiserver 相同的跟蹤設定。

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
featureGates:
  KubeletTracing: true
tracing:
  endpoint: host.k3d.internal:4317
  samplingRatePerMillion: 1000000 # 100%           

傳回到 kubetracing 檔案夾,建立最後一個檔案 config.toml.tmpl,這是 k3s 用于配置 containerd 的模闆檔案。此檔案類似于 k3s 使用的預設配置,檔案末尾還有兩個部分,用于配置 containerd 以發送跟蹤。

version = 2
[plugins."io.containerd.internal.v1.opt"]
  path = "{{ .NodeConfig.Containerd.Opt }}"
[plugins."io.containerd.grpc.v1.cri"]
  stream_server_address = "127.0.0.1"
  stream_server_port = "10010"
  enable_selinux = {{ .NodeConfig.SELinux }}
  enable_unprivileged_ports = {{ .EnableUnprivileged }}
  enable_unprivileged_icmp = {{ .EnableUnprivileged }}
{{- if .DisableCgroup}}
  disable_cgroup = true
{{end}}
{{- if .IsRunningInUserNS }}
  disable_apparmor = true
  restrict_oom_score_adj = true
{{end}}
{{- if .NodeConfig.AgentConfig.PauseImage }}
  sandbox_image = "{{ .NodeConfig.AgentConfig.PauseImage }}"
{{end}}
{{- if .NodeConfig.AgentConfig.Snapshotter }}
[plugins."io.containerd.grpc.v1.cri".containerd]
  snapshotter = "{{ .NodeConfig.AgentConfig.Snapshotter }}"
  disable_snapshot_annotations = {{ if eq .NodeConfig.AgentConfig.Snapshotter "stargz" }}false{{else}}true{{end}}
{{ if eq .NodeConfig.AgentConfig.Snapshotter "stargz" }}
{{ if .NodeConfig.AgentConfig.ImageServiceSocket }}
[plugins."io.containerd.snapshotter.v1.stargz"]
cri_keychain_image_service_path = "{{ .NodeConfig.AgentConfig.ImageServiceSocket }}"
[plugins."io.containerd.snapshotter.v1.stargz".cri_keychain]
enable_keychain = true
{{end}}
{{ if .PrivateRegistryConfig }}
{{ if .PrivateRegistryConfig.Mirrors }}
[plugins."io.containerd.snapshotter.v1.stargz".registry.mirrors]{{end}}
{{range $k, $v := .PrivateRegistryConfig.Mirrors }}
[plugins."io.containerd.snapshotter.v1.stargz".registry.mirrors."{{$k}}"]
  endpoint = [{{range $i, $j := $v.Endpoints}}{{if $i}}, {{end}}{{printf "%q" .}}{{end}}]
{{if $v.Rewrites}}
  [plugins."io.containerd.snapshotter.v1.stargz".registry.mirrors."{{$k}}".rewrite]
{{range $pattern, $replace := $v.Rewrites}}
    "{{$pattern}}" = "{{$replace}}"
{{end}}
{{end}}
{{end}}
{{range $k, $v := .PrivateRegistryConfig.Configs }}
{{ if $v.Auth }}
[plugins."io.containerd.snapshotter.v1.stargz".registry.configs."{{$k}}".auth]
  {{ if $v.Auth.Username }}username = {{ printf "%q" $v.Auth.Username }}{{end}}
  {{ if $v.Auth.Password }}password = {{ printf "%q" $v.Auth.Password }}{{end}}
  {{ if $v.Auth.Auth }}auth = {{ printf "%q" $v.Auth.Auth }}{{end}}
  {{ if $v.Auth.IdentityToken }}identitytoken = {{ printf "%q" $v.Auth.IdentityToken }}{{end}}
{{end}}
{{ if $v.TLS }}
[plugins."io.containerd.snapshotter.v1.stargz".registry.configs."{{$k}}".tls]
  {{ if $v.TLS.CAFile }}ca_file = "{{ $v.TLS.CAFile }}"{{end}}
  {{ if $v.TLS.CertFile }}cert_file = "{{ $v.TLS.CertFile }}"{{end}}
  {{ if $v.TLS.KeyFile }}key_file = "{{ $v.TLS.KeyFile }}"{{end}}
  {{ if $v.TLS.InsecureSkipVerify }}insecure_skip_verify = true{{end}}
{{end}}
{{end}}
{{end}}
{{end}}
{{end}}
{{- if not .NodeConfig.NoFlannel }}
[plugins."io.containerd.grpc.v1.cri".cni]
  bin_dir = "{{ .NodeConfig.AgentConfig.CNIBinDir }}"
  conf_dir = "{{ .NodeConfig.AgentConfig.CNIConfDir }}"
{{end}}
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
  runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
  SystemdCgroup = {{ .SystemdCgroup }}
{{ if .PrivateRegistryConfig }}
{{ if .PrivateRegistryConfig.Mirrors }}
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]{{end}}
{{range $k, $v := .PrivateRegistryConfig.Mirrors }}
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."{{$k}}"]
  endpoint = [{{range $i, $j := $v.Endpoints}}{{if $i}}, {{end}}{{printf "%q" .}}{{end}}]
{{if $v.Rewrites}}
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors."{{$k}}".rewrite]
{{range $pattern, $replace := $v.Rewrites}}
    "{{$pattern}}" = "{{$replace}}"
{{end}}
{{end}}
{{end}}
{{range $k, $v := .PrivateRegistryConfig.Configs }}
{{ if $v.Auth }}
[plugins."io.containerd.grpc.v1.cri".registry.configs."{{$k}}".auth]
  {{ if $v.Auth.Username }}username = {{ printf "%q" $v.Auth.Username }}{{end}}
  {{ if $v.Auth.Password }}password = {{ printf "%q" $v.Auth.Password }}{{end}}
  {{ if $v.Auth.Auth }}auth = {{ printf "%q" $v.Auth.Auth }}{{end}}
  {{ if $v.Auth.IdentityToken }}identitytoken = {{ printf "%q" $v.Auth.IdentityToken }}{{end}}
{{end}}
{{ if $v.TLS }}
[plugins."io.containerd.grpc.v1.cri".registry.configs."{{$k}}".tls]
  {{ if $v.TLS.CAFile }}ca_file = "{{ $v.TLS.CAFile }}"{{end}}
  {{ if $v.TLS.CertFile }}cert_file = "{{ $v.TLS.CertFile }}"{{end}}
  {{ if $v.TLS.KeyFile }}key_file = "{{ $v.TLS.KeyFile }}"{{end}}
  {{ if $v.TLS.InsecureSkipVerify }}insecure_skip_verify = true{{end}}
{{end}}
{{end}}
{{end}}
{{range $k, $v := .ExtraRuntimes}}
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes."{{$k}}"]
  runtime_type = "{{$v.RuntimeType}}"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes."{{$k}}".options]
  BinaryName = "{{$v.BinaryName}}"
{{end}}
[plugins."io.containerd.tracing.processor.v1.otlp"]
  endpoint = "host.k3d.internal:4317"
  protocol = "grpc"
  insecure = true
[plugins."io.containerd.internal.v1.tracing"]
  sampling_ratio = 1.0
  service_name = "containerd"           

建立這些檔案後,在 kubetracing 檔案夾中打開一個終端并運作 k3d 以建立叢集。在運作此指令之前,請替換 kubetracing 檔案夾的整個路徑的 [CURRENT_PATH] 占位符。您可以通過在該檔案夾的終端中運作 echo $PWD 指令來檢索它。

k3d cluster create tracingcluster \
  --image=rancher/k3s:v1.27.1-k3s1 \
  --volume '[CURRENT_PATH]/config.toml.tmpl:/var/lib/rancher/k3s/agent/etc/containerd/config.toml.tmpl@server:*' \
  --volume '[CURRENT_PATH]/config:/etc/kube-tracing@server:*' \
  --k3s-arg '--kube-apiserver-arg=tracing-config-file=/etc/kube-tracing/apiserver-tracing.yaml@server:*' \
  --k3s-arg '--kube-apiserver-arg=feature-gates=APIServerTracing=true@server:*' \
  --k3s-arg '--kubelet-arg=config=/etc/kube-tracing/kubelet-tracing.yaml@server:*'           

此指令将建立一個版本 v1.17.1 的 Kubernetes 叢集,并在計算機上的三個 docker 容器中進行設定。如果現在運作指令 kubectl cluster-info ,您将看到以下輸出:

Kubernetes control plane is running at https://0.0.0.0:60503
CoreDNS is running at https://0.0.0.0:60503/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
Metrics-server is running at https://0.0.0.0:60503/api/v1/namespaces/kube-system/services/https:metrics-server:https/proxy           

回到可觀測性環境的日志,你應該看到 OpenTelemetry Collector 中釋出了一些内部 Kubernetes 操作,如下所示:

Span #90
    Trace ID       : 03a7bf9008d54f02bcd4f14aa5438202
    Parent ID      :
    ID             : d7a10873192f7066
    Name           : KubernetesAPI
    Kind           : SPAN_KIND_SERVER
    Start time     : 2023-05-18 01:51:44.954563708 +0000 UTC
    End time       : 2023-05-18 01:51:44.957555323 +0000 UTC
    Status code    : STATUS_CODE_UNSET
    Status message :
Attributes:
     -> net.transport: STRING(ip_tcp)
     -> net.peer.ip: STRING(127.0.0.1)
     -> net.peer.port: INT(54678)
     -> net.host.ip: STRING(127.0.0.1)
     -> net.host.port: INT(6443)
     -> http.target: STRING(/api/v1/namespaces/kube-system/pods/helm-install-traefik-crd-8w4wd)
     -> http.server_name: STRING(KubernetesAPI)
     -> http.user_agent: STRING(k3s/v1.27.1+k3s1 (linux/amd64) kubernetes/bc5b42c)
     -> http.scheme: STRING(https)
     -> http.host: STRING(127.0.0.1:6443)
     -> http.flavor: STRING(2)
     -> http.method: STRING(GET)
     -> http.wrote_bytes: INT(4724)
     -> http.status_code: INT(200)
           

測試群集運作時

通過設定可觀測性和 Kubernetes 叢集,您現在可以針對 Kubernetes 觸發指令,并在 Jaeger 中檢視這些操作的痕迹。

打開浏覽器,然後導航到位于 http://localhost:16686/search 的 Jaeger UI。您将看到 apiserver 、 containerd 和 kubelet 服務正在釋出跟蹤:

建立具有運作時可觀測性的 Kubernetes 叢集

選擇 apiserver 并單擊“查找跟蹤”。在這裡,您可以看到來自 Kubernetes 控制平面的跟蹤:

建立具有運作時可觀測性的 Kubernetes 叢集

讓我們用 kubectl 對 Kubernetes 運作一個示例指令,就像運作 echo 一樣:

$ kubectl run -it --rm --restart=Never --image=alpine echo-command -- echo hi
# Output
# If you don't see a command prompt, try pressing enter.
# warning: couldn't attach to pod/echo-command, falling back to streaming logs: unable to upgrade connection: container echo-command not found in pod echo-command_default
# Hi
# pod "echo-command" deleted           

現在,再次打開 Jaeger,選擇 kubelet 服務,操作 syncPod ,并添加标簽 k8s.pod=default/echo-command ,您應該能夠看到與此 pod 相關的 span :

建立具有運作時可觀測性的 Kubernetes 叢集

展開一條跟蹤,您将看到建立此 Pod 的操作:

建立具有運作時可觀測性的 Kubernetes 叢集

總結

即使在 beta 版中,kubelet 和 apiserver 的跟蹤也可以幫助開發人員了解 Kubernetes 中發生的事情并開始調試問題。

這對于建立自定義任務的開發人員很有幫助,例如更新内部資源以向 Kubernetes 添加更多功能的 Kubernetes Operator 。

作為一個專注于在可觀測性領域建構開源工具的團隊,幫助整個 OpenTelemetry 社群的機會對我們來說很重要。這就是為什麼我們正在研究尋找從核心 Kubernetes 引擎收集跟蹤的新方法。由于 Kubernetes 公開了目前的可觀測性級别,我們希望釋出我們的發現,以幫助其他有興趣了解 Kubernetes 引擎中分布式跟蹤目前狀态的人。Daniel Dias 和 Sebastian Choren 正在開發 Tracetest ,這是一個開源工具,允許您使用 OpenTelemetry 開發和測試分布式系統。它适用于任何 OTel 相容系統,并允許建立基于跟蹤的測試。在 https://github.com/kubeshop/tracetest 檢視。

本文中使用的示例源代碼和設定說明可從 Tracetest 倉庫獲得。

參考

  • Kubernetes 系統元件的跟蹤
  • 在 ContainerD 上跟蹤
  • Kubernetes:監控資源的工具
  • 開始使用 OTel Collector
  • 通過 OpenTelemetry 提高 Kubernetes 容器運作時的可觀測性

繼續閱讀