资讯动态

基于Docker Compose部署SkyWalking:从架构选型到Java探针接入全攻略

发布时间:2026/10/14 15:52:38 来源:尧图企业网站定制
前阵子在帮团队搭一套应用性能监控APM系统目标很明确把线上接口变慢、依赖调用异常、服务拓扑看不清这几个老大难问题一次性解决。调研了一圈最终选了 SkyWalking。它的链路追踪、拓扑分析和告警能力都很能打而且支持 Java、Go、Node.js 等多种语言的探针最省心的是它部署方式足够灵活。这篇文章就围绕 docker compose 来部署 SkyWalking从编排文件怎么写、存储怎么选到 Java 服务怎么接入探针、上线后有哪些坑一条路讲完。读完你不仅能复现一套完整环境还能规避掉不少文档里没写明白的隐雷。1. 部署 SkyWalking 前先把架构和存储选型想清楚1.1 SkyWalking 由哪三部分拼起来很多第一次接触 SkyWalking 的人以为它就是一个能看调用链的 Web 面板其实它是由三个角色组成的系统OAP Server核心分析引擎负责接收探针上报的链路数据、指标数据做聚合分析然后把结果写入存储。它同时暴露两个端口gRPC 端口默认 11800 用于接收探针数据HTTP 端口默认 12800 用于 UI 查询数据。SkyWalking UI纯前端展示层加载.jar里的静态资源通过 HTTP 请求 OAP 接口展示服务拓扑、链路追踪、仪表盘、告警等。存储OAP 的分析结果最终要落到存储里可选方案包括 H2、Elasticsearch、MySQL、PostgreSQL 等。打个比方探针是每个应用里装的摄像头OAP 是监控机房里的录像存储和分析中心UI 则是值班室的监控屏幕。摄像头拍到的画面要经过分析中心处理后才能在大屏上看到完整的调用路径。这决定了部署时你不能只拉一个镜像跑起来就完事而是要把 OAP、UI、存储三者的关系用编排工具一次性定义清楚。1.2 为什么用 docker compose 而不是单容器很多人在本地验证时会直接docker run把 OAP 和 UI 分别跑起来。这个做法不是不行但问题在于每次启动要手动记端口、网络、环境变量如果加上 Elasticsearch你还需要额外管理存储容器的生命周期一旦换台机器所有配置等于重来。docker compose 的价值在于把多个容器之间的网络、依赖关系、环境变量统一写进一个 YAML 文件里一条docker compose up -d就能拉起整条链路。它足够轻很适合开发环境、中小团队内部监控甚至可以作为生产环境的最小可用方案。如果你的集群规模大到需要 K8s这套 compose 文件里的服务拆分逻辑也能直接平移过去只是把编排层换成 Deployment/Service 而已。我个人的习惯是凡是涉及多个互相依赖的容器一律先写 compose不写一堆 docker run 脚本。理由是 compose 文件本身就是最好的部署文档团队其他人看到 YAML 就明白谁依赖谁、哪个端口干嘛用的。1.3 存储选哪个H2、Elasticsearch 还是 MySQL这是部署前最容易忽略、后期最难改的决定。SkyWalking 的存储层默认 H2这在本地跑 Demo 毫无问题但 H2 是内嵌式数据库重启后数据容易丢失也不适合并发量稍大的场景。生产环境基本都会换掉。存储优势劣势适用场景H2零配置、随 OAP 启动数据易丢、性能弱本地体验、功能验证Elasticsearch查询快、支持复杂聚合、SkyWalking 深度优化内存占用高、组件多生产环境主流选择MySQL/PostgreSQL团队熟悉、运维成本低大数据量下查询性能下降中小规模生产如果只是想体验 SkyWalking 的能力先用默认 H2 跑通流程注意用docker compose down -v清空旧数据即可。如果是给线上环境用的我建议一步到位选 ElasticsearchSkyWalking 对 ES 的适配最完善拓扑、Trace 查询在数据量上来后的表现远好于 MySQL。不过千万别随便拉一个最新版 ES版本匹配要看 SkyWalking 官方支持矩阵。以 9.x 版本的 SkyWalking 来说适配最好的是 ES 7.x尤其 7.17 这条线很稳。2. docker-compose.yml 逐段拆解从镜像版本到健康检查2.1 镜像版本匹配是第一道门槛SkyWalking 的镜像 tag 很容易踩坑。你可能会在 Docker Hub 上看到apache/skywalking-oap-server和apache/skywalking-ui这两个镜像的 tag 规则基本一致但 OAP、UI、Agent 三者的版本必须保持兼容。如果 OAP 是 9.xUI 就必须用配套 9.x 的 tag探针也尽量选 9.x 版本。原因是 OAP 和 UI 之间的接口、OAP 和 Agent 之间的上报协议都会随版本升级发生变化混用不同大版本经常会出现探针在报数据、OAP 日志没报错、UI 却查不到服务的诡异现象。我在实践中都直接锁定版本比如统一用9.3.0后续再按计划升级。2.2 可以直接抄的 compose 文件下面这份配置是我整理过的基线版本OAP UI Elasticsearch 三件套存储选 ES 7.17版本锁定在 SkyWalking 9.3.0version: 3.8 services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:7.17.16 container_name: skywalking-es environment: - discovery.typesingle-node - bootstrap.memory_locktrue - ES_JAVA_OPTS-Xms512m -Xmx512m ulimits: memlock: soft: -1 hard: -1 volumes: - es-data:/usr/share/elasticsearch/data ports: - 9200:9200 healthcheck: test: [CMD-SHELL, wget -q -O - http://localhost:9200/_cluster/health /dev/null || exit 1] interval: 30s timeout: 10s retries: 5 start_period: 30s oap: image: apache/skywalking-oap-server:9.3.0 container_name: skywalking-oap depends_on: elasticsearch: condition: service_healthy environment: SW_STORAGE: elasticsearch SW_STORAGE_ES_REST_CLIENT_SERVER_URLS: http://elasticsearch:9200 SW_CORE_REST_PORT: 12800 SW_CORE_GRPC_PORT: 11800 ports: - 11800:11800 - 12800:12800 ui: image: apache/skywalking-ui:9.3.0 container_name: skywalking-ui depends_on: - oap environment: SW_OAP_ADDRESS: http://oap:12800 ports: - 8080:8080 volumes: es-data:这份文件有三个容易被忽视的设计点逐个说明。2.3 环境变量与健康检查为什么要这么写ES 的ES_JAVA_OPTS必须显式设置。不设的话ES 默认按机器内存的 50% 分堆本地开发机往往只有 8G 或 16GES 一上来就吃掉好几个 GOAP 又吃 1GDocker Desktop 直接卡死。我在开发机上给 ES 限制到 512M生产环境再按实际吞吐量调整到 8G 或 16G。ulimits的 memlock 设置是为了让 ES 锁定内存防止 swap 导致性能抖动。这个配置在 Linux 上效果明显macOS 的 Docker Desktop 也能兼容。healthcheck是 ES 容器的关键护栏。如果不加健康检查直接让 OAP 启动OAP 很可能会在 ES 还没初始化完毕时就尝试建索引触发一堆连接异常。虽然 OAP 有重试机制最终还是会自动恢复但日志里会满是报错新手一看就慌。加上depends_on的condition: service_healthyOAP 就只有等 ES 真正就绪后才启动日志干净时序也明确。关于健康检查命令有个小坑ES 官方镜像不一定带curl如果你用curl -f ...做检查可能在部分镜像里直接报 command not found导致容器被判定为 unhealthy。我用wget是因为它在 ES 镜像里可获得性更稳定如果你在自己定制的镜像里明确装了 curl换回 curl 也没问题。UI 的SW_OAP_ADDRESS必须写容器网络内的服务名也就是http://oap:12800而不是http://localhost:12800。因为 UI 容器在访问 OAP 时走的是 compose 自动创建的内部网络服务名oap才是 OAP 容器的地址。写成 localhost 的话UI 会尝试连接自己容器内部的 12800 端口结果必然是连接拒绝。2.4 网络与端口映射的规划思路compose 会自动为所有服务创建一个默认网络所以这里的服务名可以直接互相解析。端口映射遵循宿主机端口:容器内端口的规则ES映射9200:9200方便本机用 Kibana 或 curl 排查索引数据。OAP11800:11800必须暴露给业务应用中的探针上报12800:12800是 UI 查询接口。UI8080:8080是浏览器访问入口如果宿主机 8080 被占可以改成18080:8080。这里有一个经常被忽略的点探针上报不能用 12800。很多人在接入 Agent 时以为既然 UI 走 12800探针也走 12800结果数据完全上不来。探针走的是 gRPC永远是 11800。3. 启动之后的三道验证关卡别急着接业务3.1 第一次拉起环境的完整命令序列配置写好后我通常按这个流程启动并观察docker compose up -d docker compose ps docker compose logs -f elasticsearch docker compose logs -f oap第一次启动要拉镜像ES 镜像通常在 100M 到几百 M 不等OAP 和 UI 加一起也不算小耐心等一等。启动顺序上你会看到 ES 先进入 running但它的健康检查会有 start_period 的宽限期。OAP 会等 ES 健康后才开始初始化。3.2 OAP 日志里哪些字段代表真正就绪OAP 启动是个漫长的过程从初始化存储、创建索引模板到加载心跳机制通常需要 30 秒到两分钟取决于机器性能。观察日志时不要一看到 INFO 就以为成功了要等下面这几类关键内容启动模式相关信息比如OAP starts up in mode: ...。存储初始化完成日志里能看出使用的是 Elasticsearch。最后看到类似SkyWalking OAP Server started successfully的字段才代表 OAP 整体就绪。在 OAP 就绪前日志里出现个别连接重置、重试的 WARN 不用慌这是容器启动时序的常见过程。但如果过了好几分钟仍在反复报连接拒绝就要去查 ES 日志和网络配置了。3.3 UI 端最终验证OAP 就绪后浏览器访问http://localhost:8080应该能看到 SkyWalking 的仪表盘页面。左侧的 General Service 列表一开始是空的这正常因为还没有任何探针上报数据。如果 UI 页面打不开先docker compose logs ui看下 Nginx 是否正常启动再看端口映射是否冲突。如果页面打开是空白但 UI 容器日志没报错多数是浏览器兼容问题比如某些老版本浏览器对前端打包脚本兼容性不好我实测 Chromium 内核的浏览器最省心Firefox 也没问题IE 类内核则不要浪费时间。4. 把业务应用接进 SkyWalkingJava 探针接入的完整流程4.1 探针目录与基础参数存储和 UI 跑通之后才轮到真正有价值的部分——让业务应用把调用链数据吐上来。以最常用的 Java Agent 为例。先去下载与 OAP 版本对齐的 Java Agent 压缩包解压后的结构大概是这样的skywalking-agent/ ├── activations/ ├── bootstrap/ ├── config/agent.config ├── logs/ ├── optional-plugins/ ├── plugins/ └── skywalking-agent.jar启动 Java 服务时只需要增加一个-javaagent参数另外两个参数决定了你是谁和上报给谁java -javaagent:/path/to/skywalking-agent/skywalking-agent.jar \ -Dskywalking.agent.service_nameuser-service \ -Dskywalking.collector.backend_servicelocalhost:11800 \ -jar user-service.jar这里三个点必须说透skywalking.agent.service_name服务在 UI 里显示的名字建议与注册中心里的服务名保持一致后面做告警、排障时能对应上。skywalking.collector.backend_serviceOAP 的 gRPC 地址格式是ip:11800。如果是本地部署的 OAP写localhost:11800如果服务在 Docker 容器里要写 OAP 容器在 compose 网络里的服务名oap:11800。-javaagent参数必须放在-jar之前JVM 需要最先加载 agent。写反了的话agent 不会生效而且启动流程经常没有任何报错只是在 UI 里永远看不到你的服务。4.2 使用环境变量覆盖配置省去改文件的烦恼除了启动参数SkyWalking Agent 也支持用环境变量覆盖配置。这个方式在容器化部署时尤其顺手因为可以在 Dockerfile 或 compose 的 environment 里直接写SW_AGENT_NAMEuser-service SW_AGENT_COLLECTOR_BACKEND_SERVICESoap:11800环境变量是在 agent.config 之上的覆盖层优先级也更高。我在生产环境基本不用-D参数全部走环境变量这样镜像可以做成通用的服务名由编排平台在部署时注入不需要为每个服务单独 build 一个镜像。这里有一个容易忽略的细节环境变量名里的包名信息不要拼错。SW_AGENT_COLLECTOR_BACKEND_SERVICES对应的是collector.backend_service配置项少个 S、多个下划线都会导致配置不生效。由于 Agent 日志默认打印量不大这类问题很难定位我一般先用-D参数验证能通再改成环境变量。4.3 容器化服务接入的一种标准姿势假设你的业务服务也运行在 Docker 容器里并且和 SkyWalking 在同一个 compose 网络中推荐把 Agent 直接打进镜像。下面这个 Dockerfile 片段可以作为参考FROM openjdk:8-jre COPY user-service.jar /app/user-service.jar COPY skywalking-agent /app/skywalking-agent ENV SW_AGENT_NAMEuser-service ENV SW_AGENT_COLLECTOR_BACKEND_SERVICESoap:11800 CMD [java, -javaagent:/app/skywalking-agent/skywalking-agent.jar, -jar, /app/user-service.jar]关键点只有一个SW_AGENT_COLLECTOR_BACKEND_SERVICES的值要写oap:11800因为两个容器在同一个 compose 网络里服务名oap可以直接解析到 OAP 容器。如果写成localhost:11800探针会把数据上报到业务容器自己的 11800 端口必然失败。启动容器后访问几次业务接口等 30 秒到 1 分钟让数据异步上报再去 SkyWalking UI 的 General Service 看user-service是否出现。如果没出现优先查 Agent 日志位置在skywalking-agent/logs/目录下日志里通常会写明无法连接 Collector或类似信息。4.4 验证粒度链路数据到底准不准服务出现在列表里还不算完建议点进某个服务的端到端链路确认每个 Trace 的时间跨度、SQL 语句、HTTP 请求参数都能正常采集。我经常遇到的情况是服务名出来了但是 Trace 里只有入口没有下游调用的分段。这多半是插件没生效的体现。SkyWalking 对 Spring Cloud、Dubbo、gRPC、MySQL、Redis 等场景的插件是默认开启的但某些较新的库版本或自研框架需要手动启用 optional-plugins。遇到这种情况先查官方插件支持列表再去optional-plugins目录里把需要的插件包挪到plugins目录重启服务即可。5. 常见生产环境问题与我的排障清单5.1 版本兼容矩阵不想反复折腾就先定版本我在本地、测试、生产环境里分别遇到过不同版本的坑总结出一条最优先的规则在部署 SkyWalking 前先确定一套版本矩阵OAP、UI、Agent 尽量保持同一大版本。组件锁定版本备注SkyWalking OAP9.3.0作为核心基线SkyWalking UI9.3.0与 OAP 严格一致Java Agent9.3.0与 OAP 一致Elasticsearch7.17.169.x 的最佳搭档如果 Agent 版本太老、OAP 版本太新最常见的表现是探针在报数据、OAP 日志没有任何报错、但 UI 就是看不到任何服务。这种静默失败最让人头疼因为没有错误日志可供排查。所以不要小看版本一致性。5.2 内存资源控制Docker Desktop 和小机器的自救方案本地开发机跑完整套环境的资源消耗是肉眼可见的。ES 如果不管默认堆内存能吃掉机器一半内存OAP 再吃 1G 左右Docker Desktop 经常因此假死。我建议的资源配置如下ESES_JAVA_OPTS-Xms512m -Xmx512m本地验证足够。OAP默认就行如果项目里链路量很大可以显式设置SW_OAP_OPTS-Xms1g -Xmx1g。UI纯前端内存占用不高不用特意调。生产环境不要照抄这些值要按每秒请求量、Trace 条数、指标存留期来估算。ES 的堆内存跟 JVM 服务一样最大不建议超过物理内存的 50%预留内存给系统 cache 很关键。5.3 数据保留期与磁盘水位默认情况下 SkyWalking 会定期清理过期数据指标数据 TTL 默认 7 天记录数据 TTL 默认 3 天。这在很多业务场景下不够用尤其想回溯一个月前的链路时发现数据早就被清掉了。通过 OAP 的环境变量可以直接调整SW_CORE_RECORD_DATA_TTL: 7 SW_CORE_METRIC_DATA_TTL: 15但如果调整后 ES 磁盘压力变大你还要关注 ES 索引的清理策略。SkyWalking 每天生成按天索引的格式在 ES 里能看到类似sw_trace_20250517的索引。如果你打算自己写清理任务记得同时匹配sw_前缀的各类索引别只删了 trace把sw_metric_*、sw_log_*落下了。5.4 从容升级别再为数据迁移失眠版本升级是 APM 系统绕不开的运维动作。SkyWalking 数据模型在升级时不保证完全兼容尤其是 OAP 大版本跨越时老索引里的历史数据读不出来是常有的事。我现在的标准流程是升级前先备份 ES 索引快照到对象存储升级后至少保留旧索引 7 天确认新版本稳定后才清理。如果只是小版本升级比如 9.2.0 升 9.3.0通常直接替换镜像重启即可风险很小。另外再说一个很细的坑用docker compose down -v时会把es-data这个 volume 一并删掉本地重新搭环境很干净但如果线上误执行等于直接把所有历史监控数据洗没了。所以线上环境的 volume 定义一定要加备份机制或者至少把数据目录挂载到宿主机别裸用 volume。5.5 UI 界面翻车的三个隐蔽问题打开 UI 后如果首页能访问但图表数据为空我见过的高频原因有三个第一OAP 的存储地址配置不正确导致写入和查询连到了不同的 ES 集群。尤其当你通过SW_STORAGE_ES_REST_CLIENT_SERVER_URLS指向 ES 时要保证 OAP 容器能解析到这个地址不要写localhost:9200。第二UI 的SW_OAP_ADDRESS写成了http://localhost:12800。跟探针上报的坑一样容器内部访问 localhost 访问的是自己应该写http://oap:12800。第三浏览器访问的是 UI 端口但 UI 容器映射出去的端口配置改过而你在前端加载的资源路径里混淆了端口。比如访问 18080 被转发到 8080页面能打开但 UI 内置的 API 路径如果配置了绝对地址就可能出现局部请求失败的情况。这三个问题排查起来并不难按容器日志 → 环境变量 → 端口映射 → 浏览器开发者工具 Network 面板的顺序走大多数都能在几分钟内定位。5.6 探针接入时最容易出现的六类报错把接入过程中常见的报错整理成一份速查表方便你按图索骥现象常见原因解决方向UI 看不到服务Agent 版本与 OAP 不一致对齐版本查 Agent 日志服务出现但无调用链下游插件未生效检查 optional-plugins上报失败连接超时backend_service 写错改oap:11800Agent 日志乱码日志编码问题设置-Dfile.encodingUTF-8重启后数据消失用了默认 H2 存储换 Elasticsearch调用链分段缺失异步/线程池未做跨线程传递开启相关插件或手动传播有一次线上服务用的是自定义线程池处理请求SkyWalking 默认把跨线程的 Trace 上下文给断开导致链路断层。我当时的做法是把optional-plugins/apm-customize-enhance-plugin.jar放到 plugins 目录再配合自定义增强配置问题才彻底解决。这类问题依赖具体框架建议接入后一定要拿真实流量多压几次。SkyWalking 这套系统本质上解决的是当服务多到一定程度你再也无法靠肉眼判断一个请求到底经过了多少环节、每个环节消耗了多少时间的痛点。docker compose 的部署方式虽说不是唯一选择但它给了开发环境和中小团队一个极低的启动门槛。我自己的经验是第一次搭建时把存储选型、版本矩阵、数据保留期这三件事想清楚后面至少能少折腾一半。至于接入探针真正花时间的不是参数配置而是根据你使用的框架确认哪些插件要开、哪些链路值得埋点。先把这条链路跑通再逐步把告警、日志采集这些高级能力加上去你会发现 APM 系统会从一个监控工具变成真正能帮你定位线上问题的左膀右臂。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑