主题
SkyWalking 8.8.x 外接 Elasticsearch 的 Helm 生产部署实践
SkyWalking 官方 Helm Chart 默认会在集群内拉起一套内嵌 Elasticsearch,适合体验和测试,但在生产环境中并不推荐:APM 链路数据写入量大、保留周期固定,通常要求接入已有的、经过容量规划的独立 ES 集群。本文基于一套真实生产环境的部署脚本(SkyWalking 8.8.1 + chart 4.2.0 + 外部 ES),完整讲解外接 ES 的 Helm 部署过程,覆盖部署脚本逐项参数、验证方法、UI 加 Basic Auth 的通用做法,以及索引清理、扩容、存储估算等生产建议。
适用对象:已有独立 Elasticsearch 集群(7.x),需要在 Kubernetes 上部署 SkyWalking 8.8.x 并复用该 ES 的运维工程师。
一、架构说明
整体架构分三层:
| 组件 | 作用 | 暴露方式 |
|---|---|---|
| OAP Server | 接收 Agent 上报的 Trace/Metrics/Log,聚合分析后写入外部 ES | NodePort(gRPC 11800、REST 12800) |
| UI(Rocketbot) | 查询 OAP REST 接口并展示拓扑、链路、指标 | NodePort(8080) |
| 外部 Elasticsearch | 持久化存储所有 APM 数据,由 OAP 自动创建索引模板和索引 | 集群外独立部署,不随 Chart 安装 |
调用链路:
业务 Pod(Java Agent)
│ gRPC :11800(通过 OAP NodePort 或集群内 Service)
▼
OAP Server(多副本,Kubernetes 集群内部通过 label 自发现组集群)
│ HTTP :9200(bulk 批量写入)
▼
外部 Elasticsearch 集群(3+ 节点)
▲
│ HTTP 查询
UI(Rocketbot)── 浏览器通过 NodePort 访问要点:
- Chart 中
elasticsearch.enabled=false关闭内嵌 ES,OAP 通过elasticsearch.address指向外部集群。 - OAP 多副本之间通过 Chart 默认的 Kubernetes 集群模式自动发现,无需 ZooKeeper/Nacos。
- Agent 接入地址使用 OAP 的 NodePort(或集群内 Service 地址),与 ES 无直接关系。
二、Helm 部署脚本
以下脚本已按生产实践参数化,ES 地址、账号、密码通过环境变量注入,避免明文入库。执行前请先 export 对应变量。
bash
#!/bin/bash
# helm-install-skywalking.sh
# 使用私有 Harbor chartrepo 中的 skywalking 4.2.0 Chart 安装 SkyWalking 8.8.1(外接 ES)
#
# 用法:
# export ES_PASSWORD='xxx'
# bash helm-install-skywalking.sh # 默认参数安装
# RELEASE_NAME=skywalking881 K8S_NAMESPACE=apm \
# bash helm-install-skywalking.sh # 自定义 Release 与命名空间
set -euo pipefail
# ── 可按需修改的参数 ──────────────────────────────────────────────
RELEASE_NAME="${RELEASE_NAME:-skywalking881}" # Helm Release 名
K8S_NAMESPACE="${K8S_NAMESPACE:-apm}" # K8s 命名空间
CHART_VERSION="4.2.0" # Chart 版本(内置 App 8.8.x)
CHART="chartrepo/rancher/skywalking" # 私有仓库中的 Chart 坐标
# 外部 ES(地址/账号可改默认值,密码强制从环境变量读取,不写死在脚本里)
ES_ADDRESS="${ES_ADDRESS:-192.168.10.11:9200,192.168.10.12:9200,192.168.10.13:9200}"
ES_USER="${ES_USER:-elastic}"
ES_PASSWORD="xxx"
# OAP
SW_NAMESPACE="${SW_NAMESPACE:-apm}" # SkyWalking 逻辑命名空间(ES 索引前缀隔离用)
OAP_IMAGE_REPO="harbor.example.com/base/skywalking/skywalking-oap-server"
OAP_IMAGE_TAG="8.8.1"
OAP_REPLICAS="3" # OAP 副本数,按上报量调整
OAP_GRPC_NODE_PORT="43808" # Agent gRPC 上报端口(对应 11800)
OAP_REST_NODE_PORT="42808" # OAP REST/GraphQL 端口(对应 12800)
OAP_JAVA_OPTS="-Xmx8g -Xms8g" # JVM 堆,建议不超过 limits.memory 的 2/3
# UI
UI_IMAGE_REPO="harbor.example.com/base/skywalking/skywalking-ui"
UI_IMAGE_TAG="8.8.1" # 与 OAP 版本保持一致
UI_NODE_PORT="41088"
UI_REPLICAS="2"
# initContainer(等待依赖就绪用的 busybox)
INIT_IMAGE="harbor.example.com/base/skywalking/busybox"
# ─────────────────────────────────────────────────────────────────
echo "============================================================"
echo " SkyWalking Helm 安装"
echo " Release : $RELEASE_NAME"
echo " Namespace: $K8S_NAMESPACE"
echo " Chart : $CHART $CHART_VERSION"
echo " SW_NAMESPACE: $SW_NAMESPACE"
echo "============================================================"
# ── 1. 确保 helm repo 已添加(私有 Harbor chartrepo,密码请自行替换)──
echo ""
echo "[1/3] 添加/更新 Helm repo..."
helm repo add chartrepo \
--username admin \
--password '<HARBOR密码>' \
https://harbor.example.com/chartrepo 2>/dev/null || true
helm repo update chartrepo
# ── 2. 确保 K8s namespace 存在 ───────────────────────────────────
echo ""
echo "[2/3] 确认 namespace $K8S_NAMESPACE ..."
kubectl get namespace "$K8S_NAMESPACE" &>/dev/null \
|| kubectl create namespace "$K8S_NAMESPACE"
# ── 3. Helm install / upgrade ────────────────────────────────────
echo ""
echo "[3/3] 执行 helm upgrade --install ..."
helm upgrade --install "$RELEASE_NAME" "$CHART" \
--version "$CHART_VERSION" \
--namespace "$K8S_NAMESPACE" \
--set elasticsearch.enabled=false \
--set elasticsearch.address="$ES_ADDRESS" \
--set elasticsearch.config.user="$ES_USER" \
--set elasticsearch.config.password="xxx" \
--set oap.storageType=elasticsearch \
--set oap.dynamicConfigEnabled=true \
--set "oap.env.SW_NAMESPACE=$SW_NAMESPACE" \
--set oap.env.SW_STORAGE_ES_INDEX_SHARDS_NUMBER="6" \
--set oap.env.SW_SERVICE_NAME_MAX_LENGTH="190" \
--set oap.image.repository="$OAP_IMAGE_REPO" \
--set oap.image.tag="$OAP_IMAGE_TAG" \
--set "oap.javaOpts=$OAP_JAVA_OPTS" \
--set oap.replicas="$OAP_REPLICAS" \
--set oap.resources.limits.cpu=4 \
--set oap.resources.limits.memory=12Gi \
--set oap.resources.requests.cpu=4 \
--set oap.resources.requests.memory=10Gi \
--set oap.service.type=NodePort \
--set oap.ports.grpcNodePort="$OAP_GRPC_NODE_PORT" \
--set oap.ports.restNodePort="$OAP_REST_NODE_PORT" \
--set ui.image.repository="$UI_IMAGE_REPO" \
--set ui.image.tag="$UI_IMAGE_TAG" \
--set ui.replicas="$UI_REPLICAS" \
--set ui.service.type=NodePort \
--set ui.service.nodePort="$UI_NODE_PORT" \
--set initContainer.image="$INIT_IMAGE"
echo ""
echo "============================================================"
echo " 安装完成"
echo " 查看状态: kubectl get pods -n $K8S_NAMESPACE"
echo "============================================================"三、关键参数逐项说明
3.1 外部 ES 接入
| 参数 | 说明 |
|---|---|
elasticsearch.enabled=false | 关闭 Chart 内嵌 ES 子 Chart,这是外接 ES 的开关 |
elasticsearch.address | 外部 ES 节点列表,逗号分隔,最终注入 OAP 环境变量 SW_STORAGE_ES_CLUSTER_NODES |
elasticsearch.config.user / password | ES 认证账号密码,对应 SW_ES_USER / SW_ES_PASSWORD;密码务必通过环境变量传入,不要写进脚本或提交到 Git |
oap.storageType=elasticsearch | 声明存储后端类型(对应 SW_STORAGE) |
oap.dynamicConfigEnabled=true | 开启动态配置,允许后续通过 ConfigMap/UI 动态调整部分 OAP 配置 |
注意:脚本中 SW_NAMESPACE 是 SkyWalking 的逻辑命名空间,会作为 ES 索引名前缀,用于在同一个 ES 集群内隔离多套 SkyWalking 环境(如 prod、cluster-a)。它与 K8s namespace 是两个概念,不要混淆。
3.2 ES 分片与服务名长度
| 环境变量 | 示例值 | 说明 |
|---|---|---|
SW_STORAGE_ES_INDEX_SHARDS_NUMBER | 6 | 新建索引的主分片数,一般取 ES 数据节点数的 1~2 倍。3 节点集群建议 3~6;写入量特别大的环境可按节点倍数上调(如 32),但分片过多会增加集群开销 |
SW_SERVICE_NAME_MAX_LENGTH | 190 | 服务名最大长度,默认 70。微服务名较长(含环境前缀、版本号)时容易超限被截断,建议调大 |
3.3 副本数、JVM 与资源限额
| 参数 | 示例值 | 说明 |
|---|---|---|
oap.replicas | 3 | OAP 副本数。中等规模(数百服务实例)3 副本起步;大规模可按上报量扩到 8~12 副本,多副本间通过 Kubernetes 集群模式自动分片聚合 |
oap.javaOpts | -Xmx8g -Xms8g | 堆内存固定为同一值避免动态伸缩抖动;堆建议为 limits.memory 的 1/2 ~ 2/3,剩余留给堆外缓存与操作系统 |
oap.resources.requests/limits | 4C / 10Gi~12Gi | requests 与 limits 接近可减少调度后资源争抢;OAP 是内存敏感型应用,limits 过低会被 OOMKill |
ui.replicas | 2 | UI 无状态,2 副本保证高可用即可 |
3.4 NodePort 端口规划
| 端口 | NodePort | 用途 |
|---|---|---|
| gRPC 11800 | 43808 | Agent 数据上报入口,Agent 侧配置 collector.backend_service=<任一节点IP>:43808 |
| REST 12800 | 42808 | GraphQL 查询接口,UI 内部走 Service 访问,此端口主要供第三方系统对接 |
| UI 8080 | 41088 | 浏览器访问入口:http://<任一节点IP>:41088 |
NodePort 一旦指定不要与其他服务冲突;生产环境建议改用 LoadBalancer 或 Ingress + 域名,NodePort 仅作内网快速接入方案。
四、关于 OAP 的 application.yml
OAP 容器的完整配置文件位于镜像内 /skywalking/config/application.yml,内容较长且绝大部分模块(各种 receiver、配置中心、exporter 等)使用默认值即可,本文不抄录原文。需要查阅或定制时参考:
- 容器内直接查看:
kubectl exec -it <oap-pod> -n apm -- cat /skywalking/config/application.yml - 官方文档对应版本的环境变量说明(所有配置项均支持
SW_XXX环境变量覆盖):https://skywalking.apache.org/docs/
Helm 部署场景下只需记住一个原则:不要直接改容器内文件,一律通过 --set oap.env.SW_XXX=... 注入环境变量覆盖默认值,这样配置随 Release 可追溯、可回滚。常用环境变量除上文已列出的之外还有:
| 环境变量 | 默认值 | 说明 |
|---|---|---|
SW_CORE_RECORD_DATA_TTL | 3(天) | Trace/Log 记录类数据保留天数 |
SW_CORE_METRICS_DATA_TTL | 7(天) | 指标类数据保留天数 |
SW_STORAGE_ES_BULK_ACTIONS | 5000 | 每积攒多少条记录触发一次 bulk 写入 |
SW_STORAGE_ES_FLUSH_INTERVAL | 15(秒) | bulk 强制刷盘间隔 |
SW_STORAGE_ES_INDEX_REPLICAS_NUMBER | 1 | 索引副本数,ES 集群可靠性要求高时保持 1 |
TTL 到期后由 OAP 的 DataKeeper 定时删除旧索引(enableDataKeeperExecutor 默认开启),这是索引生命周期清理的主要机制,详见第七部分。
五、部署验证
5.1 Pod 与 Service 状态
bash
kubectl get pods -n apm -o wide
kubectl get svc -n apm
# 期望:oap 全部 Running 且 READY 1/1,ui 全部 Running
# svc 中 oap 的 11800/12800、ui 的 8080 均已映射到指定 NodePort5.2 OAP 日志确认 ES 连通
bash
kubectl logs -n apm deploy/skywalking881-oap | grep -i -E "elasticsearch|error" | head -20
# 正常应看到 ES 客户端初始化、索引模板创建成功等日志
# 若出现 Connection refused / AuthenticationException,见第八部分故障速查同时可直接在 ES 上确认索引已创建:
bash
curl -u elastic:'<ES密码>' 'http://192.168.10.11:9200/_cat/indices/apm*?v'
# SW_NAMESPACE=apm 时索引名以 apm- 开头,如 apm-segment-20260728、apm-service_traffic-xxx5.3 UI 访问
浏览器打开 http://<任一节点IP>:41088,首次进入无数据属正常(尚无 Agent 上报)。
5.4 Agent 接入测试
以 Java Agent 为例,挑选一个测试服务接入:
bash
# JVM 启动参数追加
-javaagent:/opt/skywalking-agent/skywalking-agent.jar
-Dskywalking.agent.service_name=demo-service
-Dskywalking.collector.backend_service=192.168.10.11:43808发起几次请求后,在 UI 的「General Service」中能看到 demo-service 的拓扑、Trace 和指标,即全链路打通。
六、UI 增加 Basic Auth 的通用方法
SkyWalking UI 本身不带认证,直接暴露 NodePort 等于全公司可见。最简单的通用方案是:Rocketbot UI 镜像内置 Nginx,支持挂载 htpasswd 文件开启 HTTP Basic 认证。以下凭据均为示例值,请自行替换。
6.1 生成 htpasswd 文件
bash
# 安装工具:yum install -y httpd-tools 或 apt install -y apache2-utils
htpasswd -cb htpasswd swadmin 'SwAdmin@2026' # 示例账号密码,请自行替换
cat htpasswd
# 输出形如:swadmin:$apr1$0b7WBDsw$xcNHZL0hwf9dyUZ4aq0AN1没有 htpasswd 工具时可用 openssl 生成:openssl passwd -apr1 '你的密码'。
6.2 创建 Secret
bash
kubectl create secret generic skywalking-ui-basic-auth \
--from-file=htpasswd=xxx -n apm等价的 YAML 形式(data 为 htpasswd 文件内容的 base64):
yaml
apiVersion: v1
kind: Secret
metadata:
name: skywalking-ui-basic-auth
namespace: apm
type: Opaque
data:
# 内容为 "swadmin:$apr1$0b7WBDsw$xcNHZL0hwf9dyUZ4aq0AN1" 的 base64,示例值,请自行替换
htpasswd: xxx6.3 挂载到 UI Pod
修改 UI Deployment,将 Secret 挂载到镜像内 Nginx 约定的认证文件路径(Rocketbot UI 镜像为 /home/htpasswd):
yaml
spec:
template:
spec:
containers:
- name: ui
image: harbor.example.com/base/skywalking/skywalking-ui:8.8.1
env:
- name: SW_OAP_ADDRESS
value: http://skywalking881-oap:12800
volumeMounts:
- name: basic-auth
mountPath: /home/htpasswd
subPath: htpasswd
volumes:
- name: basic-auth
secret:
secretName: skywalking-ui-basic-auth
items:
- key: htpasswd
path: htpasswd更新后重新访问 UI,浏览器会弹出 Basic Auth 登录框。注意:该路径依赖 UI 镜像内置 Nginx 配置已开启 auth_basic,若使用的官方 apache/skywalking-ui 镜像未内置认证逻辑,可改用更通用的 Ingress 方案——在 UI Service 前挂 ingress-nginx,通过 nginx.ingress.kubernetes.io/auth-type: basic + auth-secret 注解实现同样的效果。
七、生产建议
7.1 ES 索引生命周期清理
SkyWalking 的索引按天滚动(dayStep=1),清理有两层机制,建议同时配置:
- OAP 内置 DataKeeper:由
SW_CORE_RECORD_DATA_TTL(记录类,默认 3 天)和SW_CORE_METRICS_DATA_TTL(指标类,默认 7 天)控制,到期自动删除旧索引。生产上按磁盘容量反推保留天数,不要盲目调大。 - 兜底清理:防止 OAP 清理任务异常导致索引堆积,可用 cron 定期兜底删除超期索引:
bash
# 删除 10 天前的 skywalking 索引(示例,请按实际 TTL 调整)
curl -u elastic:'<ES密码>' -XDELETE \
"http://192.168.10.11:9200/apm-*-$(date -d '10 days ago' +%Y%m%d)*"7.2 OAP 扩容
- 扩容信号:OAP Pod CPU 长期跑满、ES bulk 写入延迟增大、UI 指标出现分钟级空洞。
- 扩容方式:直接修改脚本中
OAP_REPLICAS重新执行helm upgrade,OAP 通过 Kubernetes 集群模式自动重新分配聚合分片,无需人工干预。 - 扩容后注意 ES 侧压力:OAP 副本数增加,bulk 并发相应增加,ES 写入线程池和磁盘 IO 需留有余量。
7.3 存储估算
经验公式(Java Agent 全采样场景):
每日 ES 数据量 ≈ 服务实例数 × 实例日均请求量 × 单 Trace 平均大小(约 2~5 KB)示例:200 个实例、每实例日均 100 万请求、单 Trace 3 KB,则每日约 600 GB,保留 3 天需约 1.8 TB 有效容量;叠加索引副本(replicas=1)和 ES 磁盘水位(建议低于 70%),3 节点集群每节点至少准备 1.5~2 TB SSD。采样率下调(如 10%)可线性降低存储。生产建议开启 Agent 采样或使用 traceSamplingPolicySettingsFile 精细化控制。
八、故障速查
| 现象 | 可能原因 | 排查与处理 |
|---|---|---|
| OAP 启动即 CrashLoopBackOff | ES 地址不可达或认证失败 | kubectl logs 查看 OAP 日志:Connection refused 检查 ES 地址/端口与网络策略;AuthenticationException 核对 ES_USER/ES_PASSWORD;ES 开启 TLS 时确认 SW_STORAGE_ES_HTTP_PROTOCOL=https |
| OAP Running 但 UI 无数据 | Agent 未上报 / SW_NAMESPACE 不一致 / 时间范围选错 | 确认 Agent backend_service 指向 OAP NodePort;curl http://ES:9200/_cat/indices/<namespace>*?v 看当天索引是否有文档写入;UI 右上角时间范围放宽到最近 1 小时 |
ES 写入拒绝(es_rejected_execution_exception) | ES 写入队列满,bulk 洪峰 | 调大 ES 写入线程池队列或加数据节点;OAP 侧降低 SW_STORAGE_ES_BULK_ACTIONS 批次或增加 OAP 副本分摊;检查 ES 磁盘是否触发 flood_stage 只读(PUT _all/_settings {"index.blocks.read_only_allow_delete": null} 解除) |
| UI 打开白屏或 502 | UI 无法连接 OAP Service | 确认 UI 容器内 SW_OAP_ADDRESS 指向正确的 OAP Service DNS 名与 12800 端口;kubectl exec 进 UI 容器 curl 该地址验证 |
| Agent 日志报 gRPC 连接超时 | NodePort 不通或端口冲突 | 节点上 nc -zv <节点IP> 43808 验证;检查节点 iptables/安全组是否放行 NodePort 段(默认 30000-32767,本例为高端口需确认 kube-proxy 端口范围已扩展) |
| 索引名前缀与预期不符 | SW_NAMESPACE 未生效 | 该变量同时影响集群与存储两处,修改后需重建(或手动删除)旧索引模板;多环境共用一个 ES 时务必为每套环境设置独立 SW_NAMESPACE |
| OAP 频繁 OOMKill | 堆设置过小或 limits 过低 | 按 3.3 节原则调整 oap.javaOpts 与 resources;关注 container_memory_working_set_bytes 持续逼近 limit 时及时扩容副本 |
| 挂载 htpasswd 后 UI 仍无认证弹窗 | 镜像 Nginx 未启用 auth_basic | 换用官方支持认证的 UI 镜像,或改用 ingress-nginx Basic Auth 注解方案(见 6.3) |