使用 Grafana Tanka 在 Kubernetes 上部署 Grafana Tempo
【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo
本文以 Grafana Tempo 官方部署指南为骨架,结合当前仓库operations/jsonnet/microservices中的 Jsonnet 库源码,系统讲解如何借助 Grafana Tanka 与 jsonnet-bundler 在 Kubernetes 上快速拉起一套 Tempo 微服务集群(含 MinIO 对象存储、可选 metrics-generator 与 KEDA 自动扩缩)。读者完成后将掌握从初始化 Tanka 环境、生成 Kubernetes 清单到tk apply上线并验证的完整实战流程,同时理解微服务 Jsonnet 库中各组件副本数、资源、存储类等关键参数背后的默认值与源码依据。
概述:Tanka + Jsonnet 部署方式与适用场景
Tanka 是 Grafana Labs 开源的 Kubernetes 配置管理工具,它基于 Jsonnet 语言,允许你用可编程、可复用的方式描述集群资源,再渲染为 Kubernetes YAML 清单并通过tk apply下发到集群。相比手写大量 YAML,Jsonnet 天然支持变量、继承与函数抽象,非常适合维护 Tempo 这类组件众多(distributor、querier、query-frontend、metrics-generator、block-builder、live-store、backend-scheduler、backend-worker、memcached 等)的微服务部署。
当前仓库在 operations/jsonnet/microservices 目录下维护了一套完整的微服务 Jsonnet 库,其入口 tempo.libsonnet 通过(import 'common.libsonnet') + (import 'configmap.libsonnet') + ...的方式把各组件(distributor、generator、frontend、querier、block-builder、live-store、backend-scheduler、backend-worker、vulture、memcached、memberlist 等)的 libsonnet 文件组合成一个整体,并额外引入 autoscaling.libsonnet 提供基于 KEDA 的水平自动扩缩能力。
需要特别说明的是:本文的部署方式面向开发集群或沙箱环境,官方文档明确指出该配置不适合直接用于生产环境,但它是学习 Tempo 各组件协作关系的绝佳途径。在生产环境,建议使用云厂商托管的对象存储(如 S3、GCS、Azure Blob)以避免自行运维对象存储的开销。
前置条件
开始部署前,请确认以下资源:
- 一个 Kubernetes 集群:官方默认配置至少需要40 个 CPU 和 46GB 内存。如果只是小规模的写入或查询量,可以使用远小于此规格的配置(后文会介绍如何下调组件资源)。
kubectl:版本需与集群的 Kubernetes API 版本匹配。- 本地可用的
tk(Tanka)与jb(jsonnet-bundler)命令行工具。
配置 Kubernetes 并安装 Tanka
1. 创建工作目录
mkdir tempo cd tempo2. 创建 Kubernetes 命名空间
kubectl create namespace tempo示例中命名空间为tempo,你可以替换为任意名称;后续所有kubectl命令与 Tanka 环境都会使用该命名空间。
3. 安装工具链
- 安装 Grafana Tanka(参考 Tanka 官方安装文档)。如果你的机器上已有 Go 环境,也可以在非 GOPATH、非 go.mod 项目的目录下直接执行:
go install github.com/grafana/tanka/cmd/tk@latest go install github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb@latest - 安装
jsonnet-bundler(jb),它是 Jsonnet 的依赖管理器,用于拉取k.libsonnet、Tempo 库和 Memcached 库。
设置 Tanka 环境
Tanka 依赖当前 Kubernetes 上下文(context)来生成面向正确集群的清单。
检查当前上下文是否正确:
kubectl config current-context初始化 Tanka 环境。
tk init --k8s=false表示不生成 Kubernetes 相关的默认配置(因为稍后通过tk env set显式指定),然后添加名为environments/tempo的环境,并将该环境绑定到当前上下文对应的集群与tempo命名空间:tk init --k8s=false tk env add environments/tempo tk env set environments/tempo \ --namespace=tempo \ --server-from-context=$(kubectl config current-context)
这一步完成后,environments/tempo目录下会生成spec.json(环境规格)与后续我们要写入的main.jsonnet。
安装 Jsonnet 库
需要安装三类库:k.libsonnet(Kubernetes API 的 Jsonnet 封装)、Tempo 微服务库及其依赖、Memcached 库。
安装
k.libsonnet。将K8S_VERSION设为与你集群主版本一致的次版本(例如1.32):mkdir -p lib export K8S_VERSION=1.32 jb install github.com/jsonnet-libs/k8s-libsonnet/${K8S_VERSION}@main cat <<EOF > lib/k.libsonnet import 'github.com/jsonnet-libs/k8s-libsonnet/${K8S_VERSION}/main.libsonnet' EOF安装 Tempo Jsonnet 库及其依赖(即本仓库 operations/jsonnet/microservices 对应发布的模块):
jb install github.com/grafana/tempo/operations/jsonnet/microservices@main安装 Memcached 库及其依赖:
jb install github.com/grafana/jsonnet-libs/memcached@master
安装完成后,jsonnetfile.json与jsonnetfile.lock.json会记录这些依赖及其锁定版本,保证环境可复现。
部署 MinIO 对象存储
MinIO 是开源的 Amazon S3 兼容对象存储,可免费在 Kubernetes 上运行。本文用它充当 Tempo 的 trace 后端存储,无论底层是何种云平台或本地环境,这一步骤都保持一致。
创建
minio.yaml,内容如下。根据你的 Kubernetes 平台,可能需要修改storageClassName——例如 GKE 可能不支持local-path,但支持standard等其他名称:apiVersion: v1 kind: PersistentVolumeClaim metadata: # This name uniquely identifies the PVC. Will be used in deployment below. name: minio-pv-claim labels: app: minio-storage-claim spec: # Read more about access modes here: http://kubernetes.io/docs/user-guide/persistent-volumes/#access-modes accessModes: - ReadWriteOnce storageClassName: local-path resources: # This is the request for storage. Should be available in the cluster. requests: storage: 50Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: minio spec: selector: matchLabels: app: minio strategy: type: Recreate template: metadata: labels: # Label is used as selector in the service. app: minio spec: # Refer to the PVC created earlier volumes: - name: storage persistentVolumeClaim: # Name of the PVC created earlier claimName: minio-pv-claim initContainers: - name: create-buckets image: busybox:1.28 command: - 'sh' - '-c' - 'mkdir -p /storage/tempo-data' volumeMounts: - name: storage # must match the volume name, above mountPath: '/storage' containers: - name: minio # Pulls the default Minio image from Docker Hub image: minio/minio:latest args: - server - /storage - --console-address - ':9001' env: # MinIO root credentials - name: MINIO_ROOT_USER value: 'minio' - name: MINIO_ROOT_PASSWORD value: 'minio123' ports: - containerPort: 9000 - containerPort: 9001 volumeMounts: - name: storage # must match the volume name, above mountPath: '/storage' --- apiVersion: v1 kind: Service metadata: name: minio spec: type: ClusterIP ports: - port: 9000 targetPort: 9000 protocol: TCP name: api - port: 9001 targetPort: 9001 protocol: TCP name: console selector: app: minio该清单包含三部分:
- PVC:申请 50Gi 存储,
accessModes为ReadWriteOnce; - Deployment:initContainer 预先在
/storage下创建tempo-data目录作为 bucket 根路径;MinIO 容器以server /storage启动,并暴露 9000(S3 API)与 9001(Web 控制台)两个端口;默认凭据为minio/minio123; - Service:ClusterIP 类型,将 9000/9001 端口映射为
api与console。
- PVC:申请 50Gi 存储,
应用该清单:
kubectl apply --namespace tempo -f minio.yaml验证 bucket 已创建。没有该 bucket,Tempo 将无法存储任何数据:
kubectl port-forward --namespace tempo service/minio 9001:9001然后用浏览器访问
http://localhost:9001,使用minio/minio123登录,确认 Buckets 页面中存在tempo-data。编写
environments/tempo/main.jsonnet,将 Tempo 集群指向 MinIO,并配置各组件副本数、接收器端口、metrics-generator 与 Kafka 等参数:cat <<EOF > environments/tempo/main.jsonnet // The jsonnet file used to generate the Kubernetes manifests. local tempo = import 'microservices/tempo.libsonnet'; local k = import 'ksonnet-util/kausal.libsonnet'; local container = k.core.v1.container; local containerPort = k.core.v1.containerPort; tempo { _images+:: { tempo: 'grafana/tempo:3.0.0', }, tempo_distributor_container+:: container.withPorts([ containerPort.new('jaeger-grpc', 14250), containerPort.new('otlp-grpc', 4317), ]), _config+:: { namespace: 'tempo', query_frontend+: { replicas: 2, }, querier+: { replicas: 3, }, block_builder+: { replicas: 2, }, live_store+: { replicas: 2, pvc_size: '10Gi', pvc_storage_class: 'standard', }, backend_scheduler+: { pvc_size: '1Gi', pvc_storage_class: 'standard', }, backend_worker+: { replicas: 1, }, distributor+: { replicas: 3, receivers: { jaeger: { protocols: { grpc: { endpoint: '0.0.0.0:14250', }, }, }, otlp: { protocols: { grpc: { endpoint: '0.0.0.0:4317', }, }, }, }, }, metrics_generator+: { replicas: 1, ephemeral_storage_request_size: '10Gi', ephemeral_storage_limit_size: '11Gi', pvc_size: '10Gi', pvc_storage_class: 'standard', }, memcached+: { replicas: 3, }, bucket: 'tempo-data', backend: 's3', }, tempo_config+:: { storage+: { trace+: { s3: { bucket: $._config.bucket, access_key: 'minio', secret_key: 'minio123', endpoint: 'minio:9000', insecure: true, }, }, }, ingest+: { kafka+: { address: 'kafka:9092', topic: 'tempo-ingest', }, }, block_builder+: { consume_cycle_duration: '30s', }, metrics_generator+: { processor: { span_metrics: {}, service_graphs: {}, }, registry+: { external_labels: { source: 'tempo', }, }, }, overrides+: { defaults+: { metrics_generator+: { processors: ['service-graphs', 'span-metrics'], }, }, }, }, } EOF下面对该配置做逐段拆解:
_images+:::覆盖 Tempo 镜像版本。仓库 config.libsonnet 中默认定义了tempo: 'grafana/tempo:3.0.0',并将同一个镜像复用到 distributor、querier、query-frontend、metrics-generator、block-builder、live-store、backend-scheduler、backend-worker 等所有 Tempo 组件;这里显式指定版本可保证可复现性。tempo_distributor_container+:::为 distributor 容器追加jaeger-grpc(14250)与otlp-grpc(4317)两个端口,与下方receivers的监听端点一一对应。_config+:::覆盖各组件的副本数与存储配置。query_frontend、querier、block_builder、live_store、backend_worker、distributor、metrics_generator、memcached的默认值均定义在 config.libsonnet 中(例如query_frontend.replicas默认 1、querier.replicas默认 2、distributor.replicas默认 1、memcached.replicas默认 3),这里通过+:合并运算符逐项覆盖。注意live_store、backend_scheduler、metrics_generator的pvc_size与pvc_storage_class在源码中是必填项(源码中写为error 'Must specify a live-store pvc size'等),因此必须在_config中给出。tempo_config+:::这是写入 ConfigMap 的实际 Tempo 配置:storage.trace.s3:连接 MinIO,endpoint: 'minio:9000'指向 MinIO Service,insecure: true表示走 HTTP(MinIO 未启用 TLS);ingest.kafka:当前部署模式要求 Kafka 兼容系统(如 Kafka 或 Redpanda),address: 'kafka:9092'为示例地址,部署前需根据你的 Kafka 实例修改;block_builder.consume_cycle_duration:block-builder 消费 Kafka 分区并落盘的周期;metrics_generator.processor:启用span_metrics与service_graphs两个处理器,并通过overrides.defaults.metrics_generator.processors在租户级别默认开启。
官方文档特别提醒:该配置依赖 Kafka 兼容系统,你需要在部署 Tempo 之前先部署 Kafka(或 Redpanda),并将
ingest.kafka.address改为指向你的 Kafka 实例。
可选:启用 metrics-generator
前面的配置已经启用了 metrics 生成能力,但你还需要指定生成指标的去向。如果希望将指标远程写入 Prometheus 兼容实例(如 Grafana Cloud Metrics 或 Mimir),请在tempo_config的metrics_generator段中加入下面的 remote_write 配置块(示例假设需要 basic auth,如果不需要则删除basic_auth段):
storage+: { remote_write: [ { url: 'https://<urlForPrometheusCompatibleStore>/api/v1/write', send_exemplars: true, basic_auth: { username: '<username>', password: '<password>', }, } ], },Grafana Cloud Metrics 实例的端点等细节可通过 Grafana Cloud Portal 获取。需要留意:启用 metrics 生成并远程写入 Grafana Cloud Metrics 会产生额外的活跃序列(active series),可能影响账单计费;metrics-generator 的完整能力说明可参考仓库内 modules/generator 相关实现。
可选:启用 KEDA 自动扩缩
Tempo 的微服务 Jsonnet 库内置了基于 KEDA 的水平自动扩缩支持,适用于 distributor、metrics-generator、backend-worker、live-store 与 block-builder 组件。所有 KEDA scaler默认关闭(enabled: false),你可以通过_config.<component>.keda逐组件独立开启。开启前请确保集群已安装 KEDA operator 与 CRD。
以下示例一次性开启所有受支持的 KEDA scaler。将autoscaling_prometheus_url设为你的 Prometheus 兼容后端地址;如果后端是多租户系统(如 Grafana Mimir),还需设置autoscaling_prometheus_tenant为你的租户 ID,KEDA 会在每次抓取请求中附带X-Scope-OrgID头:
_config+:: { autoscaling_prometheus_url: 'http://prometheus-operated.monitoring.svc.cluster.local:9090', // autoscaling_prometheus_tenant: 'my-tenant', // Required for multi-tenant backends (e.g. Grafana Mimir) distributor+: { keda+: { enabled: true, min_replicas: 2, max_replicas: 200, target_cpu: '330m', }, }, metrics_generator+: { keda+: { enabled: true, min_replicas: 1, max_replicas: 200, target_cpu: '500m', // query: '', // Optional: a PromQL query for a Prometheus trigger. When set, it replaces the CPU trigger. }, }, backend_worker+: { keda+: { enabled: true, min_replicas: 3, max_replicas: 200, threshold: 200, }, }, live_store+: { keda+: { enabled: true, min_replicas: 1, max_replicas: 200, // window_seconds: 1800, // retention window; >= complete_block_timeout + query_backend_after // bytes_per_replica: 16800000000, // ~16 GiB at 10 MB/s per pod over 30m }, }, block_builder+: { keda+: { enabled: true, // scaling controls how block-builder replicas track live-store: // 'rollout-operator' (default): rollout-operator mirrors live-store zone-a replicas // directly to block-builder. Faster on both scale-up and scale-down. // Requires live_store.keda.enabled=true. rollout_operator_replica_template_access_enabled // is set automatically. // 'keda': a dedicated KEDA ScaledObject uses a kubernetes-workload trigger counting // live-store zone-a pods. Works with or without live-store KEDA. Reaction time is // bounded by KEDA's polling cycle and stabilization windows. // scaling: 'rollout-operator', }, }, },结合仓库源码 autoscaling.libsonnet 可以理解各组件实际使用的 KEDA 触发器类型:
- distributor:使用
cpu类型触发器,metricType: 'AverageValue',以target_cpu(默认330m,源码注释表明该值约对应每 pod 10MB/s 的写入吞吐)作为平均 CPU 目标。开启后 distributor Deployment 的spec.replicas会被移除,改由 ScaledObject 管理。 - metrics-generator:默认同样基于 CPU(
target_cpu默认500m)。若设置_config.metrics_generator.keda.query为一段 PromQL,则改用 Prometheus 触发器(metricType: 'Value',阈值固定为 1),此时查询结果必须精确等于期望的 pod 数量。这一机制常用于将副本数限制在 live-store 分区数以内——因为 metrics-generator 从 live-store 分区读取数据,超过分区数的 pod 没有可归属的分区,只会空转。 - backend-worker:基于 Prometheus 查询
tempodb_compaction_outstanding_blocks(默认查询使用avg_over_time计算 backend-scheduler 侧积压 block 数除以 backend-worker 副本数),以threshold(默认 200)为每个 pod 的平均积压阈值;开启时同样要求设置autoscaling_prometheus_url。 - live-store:默认 Prometheus 查询为
sum(rate(tempo_distributor_kafka_write_bytes_total{...}[window_seconds])) * window_seconds,即从 distributor(Kafka 生产者)侧度量窗口期内预期持有的总字节数;bytes_per_replica(默认10MB/s × window_seconds,约 16GiB/30 分钟)作为每个副本的字节目标,metricType: 'AverageValue'使期望副本数 = 总字节 / 单副本字节。scale_down_stabilization_window_seconds默认 35 分钟(与 live-store drain 窗口一致),避免短暂负载低谷触发缩容。 - block-builder:扩缩独立配置于
_config.block_builder.keda。默认策略scaling: 'rollout-operator'利用 rollout-operator 将 live-store zone-a 的副本数直接镜像给 block-builder,scale-up/scale-down 都最快(block-builder 在 ReplicaTemplate 变化时立即响应,并且会保持存活直到 live-store drain 窗口结束,避免丢失在途分区数据再写入后端);该策略要求live_store.keda.enabled=true,Tempo 会自动设置rollout_operator_replica_template_access_enabled。另一种策略scaling: 'keda'使用独立的 KEDA ScaledObject(kubernetes-workload触发器)统计 live-store zone-a pod 数量,不依赖 live-store KEDA 是否开启,但由于要经过 KEDA 轮询周期与稳定窗口,反应更慢:scale-up 要等新 zone-a pod 运行并被计数,scale-down 要等 zone-a pod 完全终止。block-builder KEDA 默认使用激进的扩缩窗口来尽量缩短延迟。当 block-builder KEDA 激活时,Tempo 会把_config.block_builder.partitions_per_instance(默认1)注入 block-builder 配置,保证分区分配计算正确。
可选:降低组件资源要求
如果写入与查询量较小,可以下调各组件分配的 CPU 与内存。例如调整 block-builder 的资源配额:
- 打开
environments/tempo/main.jsonnet; - 为对应组件(此处为 block-builder)新增配置块:
tempo_block_builder_container+:: { resources+: { limits+: { cpu: '3', memory: '5Gi', }, requests+: { cpu: '200m', memory: '2Gi', }, }, }, - 保存文件。
+:是 Jsonnet 的合并运算符,它会在库默认资源值(见 config.libsonnet,block-builder 默认 requests 为 500m CPU / 1Gi 内存,limits 为 1 CPU / 2Gi 内存)基础上逐字段覆盖。官方文档提醒:降低资源配额可能影响整体性能,需要结合实际负载权衡。
使用 Tanka 部署 Tempo
生成并应用全部 Kubernetes 清单:
tk apply environments/tempo/main.jsonnet如果想在下发前先查看生成的 YAML,也可以先用
tk show environments/tempo/main.jsonnet预览,或使用tk diff environments/tempo/main.jsonnet对比集群现状,这与仓库 operations/jsonnet/microservices/README.md 中推荐的tk diff/tk apply工作流一致。故障排查提示:如果部署后 live-store 没有正常启动,可能与持久化存储选择的存储类有关。此时可在
_config(注意不是tempo_config)段中添加合适的存储类,例如将 fast 存储类改为 standard:live_store+: { pvc_storage_class: 'standard', },从源码 live-store.libsonnet 可以看到,live-store 的 PVC 使用
$._config.live_store.pvc_storage_class作为storageClassName,且accessModes固定为ReadWriteOnce,因此存储类必须能被你的集群支持并满足单节点读写要求。
验证与下一步
部署成功后,Tempo 实例将通过 distributor Service(distributor.tempo.svc.cluster.local)接收两种已配置的 trace 协议:
- OTLP gRPC:端口
4317 - Jaeger gRPC:端口
14250
查询入口为query-frontend.tempo.svc.cluster.local的3200端口。
接下来需要验证部署是否正常工作,可以使用官方提供的测试应用:参考 Validate Kubernetes deployment using a test application 的步骤,向上述端口推送示例 trace 并通过查询接口检索,确认数据端到端可用。此外,仓库中的示例配置 test/environments/default/main.jsonnet 展示了一套贴近实际的最小化配置(使用 k3d、local-path存储类、单副本 memcached 等),可作为本地快速验证的参考范本。
【免费下载链接】tempoGrafana Tempo is a high volume, minimal dependency distributed tracing backend.项目地址: https://gitcode.com/GitHub_Trending/tempo1/tempo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考