在管理多个 Kubernetes 集群的可观测性技术栈时,一个反复出现的痛点是配置的漂移与不一致性。SkyWalking 的告警规则、Elasticsearch 的索引生命周期策略(ILM)、乃至 OAP Server 的核心配置文件,在开发、测试和生产环境中往往通过手动修改或临时脚本来调整,这导致了审计困难、变更不可追溯,以及在环境重建时无法精确复现。真实项目中,这种手动运维模式是不可持续的,尤其是在追求平台稳定性和研发效率的团队中。
我们的目标是建立一个完全声明式的管理模型:任何对可观测性平台的变更,无论是部署、升级还是配置调整,都必须通过 Git 提交发起,经由 Pull Request 进行评审,并由自动化系统(GitOps 控制器)同步到目标集群。GitHub 将成为唯一的可信源(Single Source of Truth)。
graph TD
subgraph GitHub Repository
A[Developer] -- Pushes Change --> B(PR: Update alarm-settings.yml)
B -- Merged --> C(main branch)
end
subgraph Kubernetes Cluster
D[ArgoCD Controller] -- Watches --> C
D -- Detects Change --> E{Sync Operation}
E -- Applies --> F[ConfigMap: skywalking-oap-config]
F -- Volume Mount --> G[SkyWalking OAP Pods]
E -- Applies --> H[Elasticsearch CR]
H -- Managed by --> I[ECK Operator]
I -- Configures --> J[Elasticsearch Cluster]
E -- Applies --> K[Deployment: my-web-api]
K -- Injects Agent --> L[API Pods with SkyWalking Agent]
end
L -- Send Traces --> G
G -- Store Data --> J
style A fill:#f9f,stroke:#333,stroke-width:2px
style C fill:#ccf,stroke:#333,stroke-width:2px
这个流程的核心是将原本动态、易变的运维操作,转化为静态、可版本化的代码。技术选型决策如下:
- 容器编排: Kubernetes,作为云原生事实标准。
- GitOps 控制器: ArgoCD。其 Pull 模式和对 K8s 原生清单的良好支持,使其成为实现声明式同步的理想选择。
- Elasticsearch 管理: Elastic Cloud on Kubernetes (ECK) Operator。相比于使用 Helm Chart,Operator 提供了更深度的生命周期管理,能够处理复杂的有状态应用升级、配置变更和存储管理,这对于生产环境的 ES 集群至关重要。
- SkyWalking 部署: SkyWalking Kubernetes Operator。它将 SkyWalking 的组件(OAP, UI, Satellite)抽象为 CRD,简化了部署配置。
第一步:构建 Git 仓库结构
一个清晰的 Git 仓库结构是 GitOps 实践的基石。我们将采用 monorepo 的方式,集中管理所有环境的配置。
# a-gitops-observability-repo/
#
├── apps/ # ArgoCD Application 定义
│ ├── dev/
│ │ ├── my-web-api.yaml
│ │ └── observability.yaml
│ └── staging/
│ ├── my-web-api.yaml
│ └── observability.yaml
│
├── infrastructure/ # 基础设施组件 (Operators, etc.)
│ ├── argocd/ # ArgoCD 自身安装清单
│ ├── eck-operator/ # ECK Operator 安装清单
│ └── skywalking-operator/ # SkyWalking Operator 安装清单
│
└── tenants/ # 各环境的具体配置清单
├── base/ # 所有环境共享的基础配置
│ ├── skywalking/
│ │ ├── oap-server.yaml
│ │ └── ui.yaml
│ └── web-api/
│ ├── deployment.yaml
│ └── service.yaml
│
└── overlays/ # 特定环境的覆盖配置 (Kustomize)
├── dev/
│ ├── elasticsearch.yaml
│ ├── skywalking-configs.yaml
│ └── web-api-replicas.yaml
└── staging/
├── elasticsearch.yaml
├── skywalking-configs.yaml
└── web-api-replicas.yaml
这种结构利用 Kustomize 来分离基础配置(base)和环境特定配置(overlays),避免了大量重复的 YAML。ArgoCD 的 Application 将会指向这些 overlays 目录。
第二步:声明式管理 Elasticsearch 集群
首先,我们需要在集群中安装 ECK Operator。这通常是一次性的引导操作。安装完成后,我们就可以通过 Elasticsearch 自定义资源(CR)来声明我们需要的集群。
在 tenants/overlays/dev/elasticsearch.yaml 中,我们定义一个用于开发环境的 ES 集群。这里的坑在于,必须同时声明式地管理其索引生命周期策略(ILM),否则索引会无限增长,耗尽存储。
# tenants/overlays/dev/elasticsearch.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: es-ilm-policy-skywalking
namespace: observability
labels:
"common.k8s.elastic.co/object-type": "ilm-policy" # ECK Operator 会识别这个标签
data:
policy.json: |
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_size": "25gb",
"max_age": "7d"
},
"set_priority": {
"priority": 100
}
}
},
"delete": {
"min_age": "30d",
"actions": {
"delete": {
"delete_searchable_snapshot": true
}
}
}
}
}
}
---
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
name: skywalking-es
namespace: observability
spec:
version: "8.9.2"
nodeSets:
- name: master-data-ingest
count: 3
# 生产环境中,应将 master, data, ingest 节点角色分离到不同的 NodeSets
# For a real production setup, separate master, data, and ingest roles into different NodeSets.
config:
node.store.allow_mmap: false
podTemplate:
spec:
containers:
- name: elasticsearch
resources:
requests:
memory: "4Gi"
cpu: "1"
limits:
memory: "4Gi"
cpu: "2"
# 生产环境强烈建议使用反亲和性,将 Pod 分散到不同物理节点
# For production, use anti-affinity to spread pods across different physical nodes.
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
elasticsearch.k8s.elastic.co/cluster-name: "skywalking-es"
topologyKey: "kubernetes.io/hostname"
# 挂载 ILM 策略
# ECK Operator will automatically detect the ConfigMap with the special label
# and configure Elasticsearch to use it. No explicit mounting is needed in the CR.
# The presence of the labeled ConfigMap is sufficient.
http:
tls:
selfSignedCertificate:
disabled: true # 简化示例,生产环境必须启用 TLS
auth: {} # 禁用认证以简化,生产环境必须启用
通过 common.k8s.elastic.co/object-type: "ilm-policy" 这个标签,ECK Operator会自动将 ConfigMap 中的 JSON 内容配置为 ES 的 ILM 策略。当我们需要调整索引保留时间时,只需修改 policy.json 并提交到 Git,ArgoCD 会自动更新 ConfigMap,ECK Operator 随之更新 ES 内部的策略,整个过程无需手动干预。
第三步:声明式管理 SkyWalking 及其核心配置
部署 SkyWalking Operator 之后,我们可以定义 OAPServer 和 UI 资源。最大的挑战在于如何声明式地管理 OAP Server 的配置文件,特别是 alarm-settings.yml。一个常见的错误是尝试通过修改 Operator 的代码或构建自定义的 Docker 镜像来注入配置,这违背了 GitOps 的原则。
正确的做法是,将配置文件作为 ConfigMap 进行管理,并通过 volumeMounts 挂载到 OAP Pod 中,覆盖默认的配置。
文件 tenants/overlays/dev/skywalking-configs.yaml:
# tenants/overlays/dev/skywalking-configs.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: skywalking-oap-config
namespace: observability
data:
# 覆盖 OAP Server 的核心配置文件
# 注意:这里的配置需要与 SkyWalking 版本兼容
# The key must match the filename expected by SkyWalking OAP Server
alarm-settings.yml: |
rules:
# Rule unique name, must be ended with `_rule`.
service_resp_time_rule:
metrics-name: service_sla
op: ">"
threshold: 1000 # in milliseconds
period: 10
count: 3
silence-period: 5
message: Response time of service {name} is more than 1000ms in 3 minutes of last 10 minutes.
service_slow_endpoint_rule:
metrics-name: endpoint_avg
op: ">"
threshold: 1000
period: 10
count: 2
silence-period: 2
message: Response time of endpoint {name} is more than 1000ms in 2 minutes of last 10 minutes.
webhooks:
# - url: http://your-webhook-host/alarm
# secret: your-secret
文件 tenants/base/skywalking/oap-server.yaml:
# tenants/base/skywalking/oap-server.yaml
apiVersion: oap.skywalking.apache.org/v1alpha1
kind: OAPServer
metadata:
name: skywalking-oap
namespace: observability
spec:
version: "9.5.0"
instances: 2
# 这里的配置将覆盖 OAP 容器的启动参数
config:
- name: SW_STORAGE
value: "elasticsearch" # 使用 ES 作为存储
- name: SW_STORAGE_ES_CLUSTER_NODES
# 这是与 ECK 创建的 ES Service 的通信地址
value: "skywalking-es-es-http.observability.svc:9200"
- name: SW_HEALTH_CHECKER
value: "default"
- name: SW_TELEMETRY
value: "prometheus" # 暴露 Prometheus 指标
- name: SW_CORE_LOG_LEVEL
value: "INFO"
# ILM 相关配置,确保 SkyWalking 创建的索引能匹配上我们定义的 ILM Policy
- name: SW_STORAGE_ES_INDEX_SHARDS_NUMBER
value: "2"
- name: SW_STORAGE_ES_SUPER_DATASET_SHARDS_NUMBER
value: "2"
- name: SW_STORAGE_ES_REPLICA_NUMBER
value: "1"
# IMPORTANT: SkyWalking will create indices with a `-yyyyMMdd` suffix.
# The ILM policy must be applied to these indices via an index template.
# The ECK ILM management is for indices it creates, for SkyWalking, we need to
# instruct SkyWalking itself on how to behave.
# SkyWalking 9+ has built-in support for ILM.
- name: SW_STORAGE_ES_DAY_STEP
value: "1" # Rollover daily
- name: SW_STORAGE_ES_LOG_TTL
value: "30" # Log data TTL in days
- name: SW_STORAGE_ES_RECORD_TTL
value: "7" # Record data TTL in days
# 关键部分:挂载包含告警规则的 ConfigMap
# This is the core of declarative configuration management.
extraVolumeMounts:
- name: oap-config-volume
mountPath: /skywalking/config/alarm-settings.yml
subPath: alarm-settings.yml # 精确挂载文件,而不是整个目录
readOnly: true
extraVolumes:
- name: oap-config-volume
configMap:
name: skywalking-oap-config # 对应上面创建的 ConfigMap
items:
- key: alarm-settings.yml
path: alarm-settings.yml
现在,当告警阈值需要从 1000ms 调整为 800ms 时,工程师只需提交一个 PR 来修改 skywalking-configs.yaml。一旦合并,ArgoCD 会更新 ConfigMap,随后 Kubernetes 会触发对 OAP Server StatefulSet 的滚动更新,新的 Pod 启动后将加载新的告警规则。整个过程透明、可审计。
第四步:为 Web API 自动注入 SkyWalking Agent
要观察一个应用,必须为其注入探针。SkyWalking Operator 提供了自动注入的能力,这比手动修改 Dockerfile 或启动脚本要优雅得多。我们只需要在被监控应用的 Deployment 上添加特定的注解。
文件 tenants/base/web-api/deployment.yaml:
# tenants/base/web-api/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-web-api
namespace: default
labels:
app: my-web-api
spec:
replicas: 1 # 副本数将在 overlay 中被覆盖
selector:
matchLabels:
app: my-web-api
template:
metadata:
labels:
app: my-web-api
# 关键注解,触发 SkyWalking Operator 的注入逻辑
annotations:
# 启用自动注入
swck-injection: "true"
# SkyWalking Agent 的配置
agent.skywalking.apache.org/agent-name: "my-web-api"
agent.skywalking.apache.org/collector-backend-services: "skywalking-oap.observability.svc:11800"
agent.skywalking.apache.org/logging-level: "INFO"
spec:
containers:
- name: app
# 一个标准的 Java Web API 镜像
image: your-repo/my-java-web-api:1.0.0
ports:
- containerPort: 8080
resources:
limits:
cpu: "500m"
memory: "1Gi"
requests:
cpu: "250m"
memory: "512Mi"
# 启动命令中不需要包含 -javaagent 参数,Operator 会自动添加
# The command should NOT include the -javaagent parameter.
# The operator's admission webhook will patch the pod spec to add it.
# The JAVA_TOOL_OPTIONS environment variable will be set.
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
当这个 Deployment 被应用到集群时,SkyWalking Operator 的 MutatingAdmissionWebhook 会拦截 Pod 的创建请求,动态修改其定义,添加一个 initContainer 来下载 SkyWalking agent,并设置 JAVA_TOOL_OPTIONS 环境变量来加载 agent。这一切对应用开发者是透明的。
最终的 GitOps 工作流
至此,我们已经将整个可观测性技术栈及其配置纳入了 GitOps 的管理范畴。一个典型的变更流程如下:
- 场景: 生产环境的
my-web-api服务需要将响应时间告警阈值从 1000ms 调整为 800ms。 - 操作: 工程师在本地克隆
a-gitops-observability-repo仓库,创建一个新分支。 - 修改: 修改
tenants/overlays/staging/skywalking-configs.yaml文件中的threshold值为800。 - 提交与PR: 提交修改,并向
main分支发起一个 Pull Request,在描述中说明变更原因。 - 评审与合并: SRE 团队或相关负责人评审该 PR。确认无误后,合并到
main分支。 - 自动同步: ArgoCD 监测到
main分支的变更,自动将更新后的ConfigMap同步到staging集群。 - 生效: SkyWalking OAP Pod 滚动更新,加载新的告警规则。
- 推广到生产: 验证无误后,通过 cherry-pick 或合并分支等方式将此变更应用到
tenants/overlays/production/目录,重复上述流程。
方案的局限性与未来展望
此方案实现了彻底的声明式管理,但并非没有权衡。每次修改 ConfigMap 都会导致 SkyWalking OAP Server 的 Pod 重启,对于大规模、高流量的实例,这可能会导致短暂的监控数据接收中断。虽然滚动更新可以缓解这个问题,但并非零中断。SkyWalking 提供了 Dynamic Configuration API,可以在运行时更新配置而无需重启,但将其与 GitOps 模型结合会更复杂,可能需要一个中间控制器来监听 Git 变更并调用 API,这破坏了 K8s 原生资源模型的简洁性。
其次,对于 Elasticsearch 的深度性能调优,例如分片策略、JVM 配置等,虽然也可以通过 ECK Operator 的 CRD 进行声明式管理,但这需要运维人员对 ES 有非常深入的理解。本方案解决了配置管理的一致性和可追溯性问题,但并未降低技术栈本身的复杂性。
未来的迭代方向可能包括:
- 探索一种混合模式,即大部分静态配置通过 GitOps 管理,而少数需要频繁动态调整的配置(如告警规则的启用/禁用)通过一个与 Git 同步的“配置服务”暴露 API 来实现,寻找声明式模型与运行时灵活性之间的平衡点。
- 将 ES 的容量规划与成本管理也纳入 GitOps 流程,例如通过自定义的控制器,根据 Git 中定义的 SLO 目标和历史用量数据,自动调整 ES NodeSet 的
count和storage大小,实现真正的 FinOps。