ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SkyWalking Docker Compose部署全攻略:链路追踪避坑实践

SkyWalking Docker Compose部署全攻略:链路追踪避坑实践

最近整理微服务链路追踪的时候,把SkyWalking用Docker Compose完整落地了一遍。整个过程踩了不少坑,从镜像版本选错导致OAP起不来,到探针数据死活不上报,最后都一一排查清楚。这篇就把整个部署过程、配置细节、避坑经验一次性讲透,给准备用Compose部署SkyWalking的同学一份能直接照做的参考。

这套方案适合谁呢?如果你正在维护微服务架构,接口变慢时说不清是哪个环节耗时长,或者调用链路上报错后查不到完整上下文,那SkyWalking就是你要的东西。用Docker Compose部署是因为它足够轻量、环境一致性好、迁移方便,适合中小团队快速搭建一套可用的APM平台。

1. 部署前必须搞清楚的架构组成与选型逻辑

1.1 SkyWalking到底由哪几个部分组成

SkyWalking是一个应用性能监控系统,核心功能包括分布式链路追踪、服务拓扑图展示、服务/实例/端点三个维度的性能指标分析,以及告警能力。整套系统从部署角度看,主要由三个部分组成:OAP Server、UI、存储后端。

OAP Server是整个系统的数据处理中枢,负责接收探针上报的Trace和Metric数据,完成聚合分析后写入存储,同时对UI提供查询接口。UI是Web控制台,渲染服务拓扑、追踪列表、性能指标图表,所有的数据都通过HTTP请求从OAP获取。存储后端默认是H2,生产环境一般换成ES或BanyanDB,后面我会细说存储选型。

数据流向是这样一个链路:应用内嵌的Agent采集调用链数据,通过gRPC协议上报给OAP Server,OAP处理完写入存储,UI查询时从OAP拿结果。明白这个链路对排查问题至关重要——数据不上报时,你得判断是Agent没采集到、上报网络不通、OAP没处理,还是存储写入失败。

1.2 为什么选择Docker Compose而不是裸机或K8s

部署SkyWalking无外乎三种方式:裸机直接跑、Docker Compose编排、K8s Helm编排。三者的取舍点在于团队规模和基础设施现状。

裸机部署最直接,下载tar包、配置JVM参数、启动脚本,一套流程下来也能跑,但环境不一致是个隐患。今天在一台机器上调通了,明天换台机器目录结构乱了、端口被占了,又得重新排查。而且升级版本时要手动替换二进制,回滚也不方便,一旦涉及多台机器就更是灾难。

K8s部署功能最强大,弹性伸缩、故障自愈都能实现,但前提是你已经有K8s集群。如果没有集群,为了一个监控系统去维护一套K8s,运维成本直接翻倍,这明显不划算。

Docker Compose刚好卡在中间:一条docker compose up -d就能拉起整套环境,配置都在一个yaml文件里,版本升级时改个镜像tag再重启就行。对于测试环境、中小团队,甚至生产环境流量不高的场景,这个方案都足够稳。Compose的文件即代码、环境一致性好、团队协作时交接成本低的特性,让它成为性价比最高的选择。

1.3 服务发现与健康检查的隐藏价值

Compose部署时,OAP和UI在同一个自定义网络内,UI通过服务名oap访问OAP,不用关心IP变化。这点看似不起眼,但实际体验非常好——容器重启后IP变了,配置完全不用改,服务名解析自动搞定。

健康检查方面,我在OAP容器里加了探活机制。OAP提供了/health接口,通过wget定时检测,如果连续几次失败,Compose会标记容器不健康,配合restart: unless-stopped策略,能实现基本的故障自愈。这个细节运维时能省不少事,后面配置文件里会给出具体写法。

2. 镜像版本与存储方案的选择

2.1 版本匹配是第一个大坑

先强调一个原则:OAP和UI的镜像版本必须保持一致。比如都用9.7.0,不要OAP用9.7.0、UI用9.6.0,跨版本接口可能有差异,UI上会出现接口报错或数据加载异常。

更关键的坑在于SkyWalking版本与存储后端的兼容性。SkyWalking和ES之间有严格的版本对应关系,不匹配时OAP启动会报Can not extract aggregation之类的错误,甚至直接起不来。我整理了一份当前稳定版本的兼容选择参考:

SkyWalking版本可用存储后端推荐方案
8.xES 6.x / 7.x,H2小规模用H2,生产用ES 7.x
9.xES 7.x / 8.x,H2,BanyanDB新项目优先BanyanDB,习惯ES用7.x
10.x(新版)ES 8.x,BanyanDB存储分离场景直接上BanyanDB

如果你的业务已经有ES集群,直接复用是合理的。但要注意版本:ES 8.x和7.x的HTTP接口有差异,SkyWalking需要对应版本才能正确建索引和写入数据。如果没有现成ES,我个人建议用BanyanDB,它是SkyWalking社区自研的存储,原生适配,少了很多兼容性折腾。

2.2 存储方案对比:H2、ES、BanyanDB怎么选

H2是默认的内嵌数据库,零依赖、启动快,适合本地开发调试。但它不适合生产:数据量上来后查询明显变慢,容器删掉数据就没了,也没有水平扩展能力。我第一次部署时图省事直接用H2跑了两天,UI上图表加载越来越慢,换了ES之后才恢复正常。

ES是传统的主流选择,查询能力强、生态成熟。但它的资源消耗不小,单独跑一个ES容器要吃1G以上内存,加上OAP本身的内存占用,一台2G内存的机器根本跑不动。而且ES的索引生命周期管理需要额外配置,不然索引文件无限增长,磁盘迟早被撑爆。

BanyanDB是专用时序数据库,在链路数据和指标数据的写入、查询上做了针对性优化,部署比ES轻,还支持水平扩展。Compose部署时直接多起一个banyandb容器就行,配置也简单。我目前在用的就是OAP加BanyanDB的组合,稳定性和性能都满意。

给个结论:测试环境用H2没问题,生产环境没有ES的话就选BanyanDB,已经有ES且版本匹配就继续用ES,没什么好纠结的。

2.3 镜像tag选择的具体建议

镜像仓库用的是Apache官方镜像apache/skywalking-oap-server和apache/skywalking-ui。选tag时不要用latest,一定要锁定具体版本号。latest标签会在官方推送新版本后静默变化,哪天重新拉镜像,运行环境就跟之前不一样了,这在生产环境是隐患。

确定版本的思路是:先看你使用的Agent版本,Agent和OAP之间也有兼容性约定。比如你在用SkyWalking Java Agent 9.x,那OAP选9.x最稳。整体原则是Agent小版本可以略高于OAP,但不要跨大版本,否则Agent上报的数据格式OAP可能解析不了。

3. 编写docker-compose.yml时那些容易出错的细节

3.1 一份可直接落地的Compose配置

下面是我整理后稳定运行的配置,存储用了BanyanDB。这个配置结合了健康检查、日志限制、网络配置,都是实际踩坑后加进去的。

version: '3.8' services: banyandb: image: apache/skywalking-banyandb:0.6.0 container_name: banyandb restart: unless-stopped networks: - skywalking ports: - "17912:17912" volumes: - banyandb-data:/root healthcheck: test: ["CMD", "wget", "--spider", "http://127.0.0.1:17912/healthcheck"] interval: 30s timeout: 5s retries: 3 start_period: 20s oap: image: apache/skywalking-oap-server:9.7.0 container_name: skywalking-oap restart: unless-stopped depends_on: banyandb: condition: service_healthy networks: - skywalking ports: - "11800:11800" - "12800:12800" environment: SW_STORAGE: banyandb SW_STORAGE_BANYANDB_HOST: banyandb SW_STORAGE_BANYANDB_PORT: "17912" SW_TELEMETRY: prometheus TZ: Asia/Shanghai SW_OAP_OPTS: "-Xms512m -Xmx512m" healthcheck: test: ["CMD", "wget", "--spider", "http://127.0.0.1:12800/health"] interval: 30s timeout: 5s retries: 3 start_period: 30s ui: image: apache/skywalking-ui:9.7.0 container_name: skywalking-ui restart: unless-stopped depends_on: oap: condition: service_healthy networks: - skywalking ports: - "8080:8080" environment: SW_OAP_ADDRESS: http://oap:12800 TZ: Asia/Shanghai networks: skywalking: driver: bridge volumes: banyandb-data:

这份配置里有两个值得注意的地方。

第一,OAP的默认JVM堆内存是按宿主机内存比例计算的,容器里容易因为识别到宿主机内存较大而把堆设得过高。我显式指定了SW_OAP_OPTS为512m,实测跑中小规模微服务够用。根据项目规模调整,起步512m,流量大了加到1g或2g。

第二,UI通过环境变量SW_OAP_ADDRESS连接OAP,用的是http://oap:12800而不是localhost。这个必须写成服务名,依赖Docker内置DNS解析,容器重建后IP变了依然能正确连上。

3.2 端口配置到底哪些要映射出来

端口是所有新手最容易看懵的地方。OAP容器里有几个端口:11800、12347、12800,UI容器是8080。并不是所有端口都要映射到宿主机。

11800是gRPC端口,Agent通过它上报数据,必须映射出来,而且宿主机端口建议保持11800不变。12800是HTTP端口,UI查询数据用的,也要映射。12347是OAP内部通信用的,用默认的Compose网络就行,不用映射到宿主机。

UI容器的8080端口映射到宿主机的8080,访问http://服务器IP:8080就是SkyWalking控制台。如果8080被占用,改成"18080:8080"避免冲突。

一个容易踩的坑:如果你用防火墙,记得放行11800和8080端口。外部服务器上部署时,curl localhost:8080有响应但浏览器打不开,十有八九是防火墙只放行了HTTP端口没放行8080。

3.3 数据持久化与版本升级的注意事项

BanyanDB的数据目录通过volume挂载为banyandb-data,存在Docker管理的卷里。这样即使容器删了重建,数据还在。ES方案的话数据持久化在ES自己的volume里,H2方案则建议挂载/tmp目录到宿主机,否则容器一删监控数据全部消失。

升级版本时有个稳妥的顺序:先备份数据卷(docker run --rm -v banyandb-data:/data -v $(pwd):/backup alpine tar czf /backup/banyandb-backup.tar.gz /data),再停掉旧容器,然后修改镜像tag重新docker compose up -d。如果升级后发现UI异常,可以快速回滚到旧版本。

还有一个细节:depends_on配合condition: service_healthy要确保Compose版本支持。老版本Compose只支持depends_on的启动顺序控制,不支持等待健康检查,需要升级到较新的Compose版本才行。这也是为什么我在配置里给BanyanDB和OAP都加了健康检查,只有存储端健康了OAP才会启动,避免OAP起来后连不上存储一直报错重启。

4. 探针接入与数据上报验证

4.1 Java Agent接入的正确姿势

SkyWalking的探针接入,Java服务最常用的是Java Agent方式。下载对应版本的skywalking-agent压缩包并解压,然后在JVM启动参数里加两行:

-javaagent:/opt/skywalking-agent/skywalking-agent.jar -Dskywalking.agent.service_name=your-service-name -Dskywalking.collector.backend_service=oap-server-ip:11800

service_name就是这个服务在SkyWalking UI里显示的名字,一个服务对应一个名字,不要多个实例共用不同名字,不然UI上拓扑图会拆出多个服务节点。backend_service就是OAP的11800端口地址,注意是gRPC端口不是12800,这里写错数据就传不上来。

如果应用和OAP在同一个Docker网络里,backend_service可以直接写服务名;如果应用在宿主机上、OAP在容器里,就写宿主机IP。很多初学同学把backend_service写成localhost:11800,结果应用不在OAP容器里,数据自然上报不了。

4.2 容器化应用如何把探针装进去

对于Docker化部署的Java应用,我习惯用挂载方式注入Agent,不修改原始镜像。这样对应用镜像零侵入,Agent版本升级也只是换个挂载目录,不用重新构建应用镜像。

services: your-app: image: your-app-image:latest volumes: - /opt/skywalking-agent:/opt/skywalking-agent environment: JAVA_OPTS: "-javaagent:/opt/skywalking-agent/skywalking-agent.jar -Dskywalking.agent.service_name=your-app -Dskywalking.collector.backend_service=oap:11800"

注意Agent版本和OAP版本保持兼容,比如OAP是9.7.0,Agent也用9.x系列。跨大版本时老版本Agent上报的数据格式可能不兼容,如果UI没有任何数据而日志里没有明显报错,优先怀疑版本匹配问题。

如果你使用的是Spring Boot打包的fat jar,-javaagent参数的写法不变。如果你是Spring Boot 3.x项目,确认JDK版本与Agent支持范围匹配,新版Agent对JDK 17/21支持很好。

4.3 UI验证数据是否真正上报成功

部署完成后打开http://服务器IP:8080,进入UI后先看左侧菜单的服务列表。刚接完探针时不要着急,Agent启动到数据上报完成并不是实时的,通常有几十秒到几分钟的延迟。点开General Service页面,如果几秒内出现你配置的service_name,说明链路基本打通了。

更细致的验证方式是查看追踪页面,检索某个具体接口的Trace数据。能看到完整的调用链分段、每段耗时和状态码,就说明链路追踪完全正常。我遇到过的情况是服务列表能看到服务,但追踪页面查不到数据,后来发现是采样率配置导致部分请求没有被采集,agent.sample_rate默认是负数表示全量采样,如果你改成了比如3,那只有三分之一的请求会被上报,测试时尽量保持全量采样。

5. 踩过的坑:常见问题与排查实录

5.1 UI打不开或页面空白

UI服务本身的端口映射没做对,是最常见的乌龙。检查docker compose ps看UI容器状态,再用curl -I http://localhost:8080在宿主机上验证端口通不通。如果宿主机通但外部访问不了,检查防火墙安全组规则。

还有一个隐蔽问题:UI容器启动时连接OAP失败不会导致UI进程退出,页面会一直转圈或报接口错误。这时看UI容器日志,如果出现connect to oap:12800 failed,八成是OAP还没健康或网络隔离了。用docker exec -it skywalking-ui ping oap验证一下同网络内的连通性。

5.2 探针数据上报失败

这是排查耗时最长的问题。首先确认应用日志里Agent有没有启动,正常启动会输出Plugin provider ...之类的一堆加载日志。如果没有输出,说明-javaagent没生效,检查参数位置对不对——JVM参数必须在-jar之前。

确认Agent启动了,再看collector.backend_service地址。记住一个关键点:从应用容器内部ping OAP的11800端口,而不是从宿主机ping。你可以在应用容器里执行telnet oap 11800,如果不通,很可能是应用和OAP不在同一个Docker网络里。

版本不兼容是另一个高频原因。Agent版本过老时,UI日志里可能没有明显报错,但数据就是不来。直接把日志级别调到DEBUG看OAP端是否收到请求,这是最准确的判断方式。

5.3 系统资源占用与性能调优

OAP和ES都是吃内存大户,只开默认配置跑,2G内存的小机器会很紧张。我的调优思路是:如果非ES方案,把BanyanDB的内存控制在512m以内,OAP堆内存512m起步,给UI留256m,整个系统占用控制在1.5G左右。

如果流量不大却感觉OAP CPU偏高,检查是否开了太多不必要的采样插件。默认Agent会加载所有插件,比较耗资源的插件可以在/opt/skywalking-agent/optional-plugins里按需选择。还有一点,只要探针一直在采集,数据就会源源不断打到OAP,如果你开发环境不想采集,直接在JVM参数里去掉-javaagent就行,不要同时开两个采集端。

5.4 排查速查表

现象可能原因排查命令
UI无法访问端口映射或防火墙docker compose ps,curl -I localhost:8080
UI页面接口报错UI与OAP版本不一致检查镜像tag,确认都是9.7.0
服务列表为空Agent未上报或版本不兼容看应用日志,确认Agent加载
数据上报延迟采样率配置或网络延迟检查agent.sample_rate,telnet oap 11800
OAP启动失败存储连接失败或内存不足docker logs skywalking-oap看报错
容器频繁重启资源限制或健康检查失败docker inspect skywalking-oap看退出码

5.5 一个现实排查案例

有次我发现某新接的服务在UI上看不到,排查过程是这样的:先看应用日志,Agent加载正常,没有异常;进入容器telnet OAP的11800端口,通,说明网络没问题;再看OAP日志,发现有receive message error字样,点开详情是unknown proto。这就定位到了Agent和OAP版本跨了大版本,Agent上报的数据格式OAP不识别。把Agent升级到与OAP相同的大版本后,数据立刻正常了。这个案例说明日志里一般有线索,只是不会直接告诉你“版本不兼容”这几个字,需要看具体报错内容来推断。

6. 最后的经验分享

部署SkyWalking这个事,做一次不复杂,做好却要关注不少细节点。以上这些内容都是实际操作后得出的经验,版本匹配、健康检查、数据持久化、网络连通这些关键点,提前注意了能省去大量排障时间。

我个人建议的顺序是:先用H2存储把整套环境跑通,验证探针数据正常,然后再根据生产需求切换存储方案。直接一步到位上ES或BanyanDB反而容易因为配置问题排障半天。另外,探针接入建议先在开发环境跑一个服务验证链路,确认UI能看到数据和拓扑图之后,再批量接入其他服务,这样做不仅风险小,后续推广时心里也有底。好的监控系统不是部署完就结束了,持续观察数据质量、根据业务调整采样率,才是让它真正发挥价值的部分。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进