这周刚带完一轮小学期实践课,轮到我负责的是Apollo部署实操。从课堂反馈来看,这次部署的翻车点比想象中集中,很多同学卡在同一个环节反复折腾。作为带课的人,我既不想让大家只对着文档复制命令,也不希望他们盯着报错日志干瞪眼,所以把这一整节课从选型到踩坑的完整路线写了出来。如果你正好在准备Apollo部署,或者要给一群刚开始接触配置中心的同学讲这堂课,这篇应该能帮你把最关键的路径和最容易踩的坑提前摸清。
先说清楚这篇讲什么:基于Apollo开源配置中心,在单机环境完成服务端部署,包括数据库初始化、三个核心服务的启动、Portal登录,以及最后用客户端验证配置拉取和热更新。整个流程以2.x版本为例,覆盖的是一家之言的实测经验,不是照着官方文档念一遍。
1. 课程定位与部署方案的选型逻辑
1.1 配置中心在技术栈里到底解决什么问题
讲部署之前,我一直坚持先回答一个问题:一个项目里,数据库地址、功能开关、限流阈值这些配置,平时到底放在哪里?
很多同学在小学期之前只接触过单体项目,配置都写在application.yml或application.properties里,改一个配置就要重新打包发布,要是有多台机器,还得一台一台手工同步,漏改一台就是事故。到了微服务架构下,服务数量上来了,环境也分出了开发、测试、生产,配置管理就成了很现实的痛点:散落在各处、没有版本概念、改了不能立即生效、没有权限控制。配置中心就是来解决这一组问题的,它把配置集中存储、提供版本管理、权限控制和审计日志,最核心的是支持热发布,改了配置之后客户端不用重启就能拿到新值。
Apollo在配置中心里属于设计非常经典的一类实现,社区一直很活跃,架构上把管理端、客户端、服务端拆得清清楚楚,拿来当教学案例再合适不过。尤其对于小学期这样时间紧张、目标是“让每个人都能动手跑通完整链路”的课程,Apollo的部署产物足够友好,模块划分又足够直观,能让学生在一天之内真正看到“配置从管理后台发布,再到客户端监听到变化”的完整过程。别小看这个闭环,很多人在生产环境用了很久配置中心,也只停留在“能连上、能改配置”的层面,课上把这个闭环走通,比讲十页PPT都管用。
1.2 为什么小学期选择单机部署方案
Apollo在真实生产环境里通常会做多实例集群部署,ConfigService和AdminService都可以横向扩展,Portal也可以做高可用。但小学期实践课,我最开始就否掉了集群方案。
原因很直接:课堂场景下,单机部署暴露出的问题已经足够典型了。三个服务要启动、两个数据库要初始化、端口要规划、日志要看明白,任何一个环节出错,都能让学生把配置中心的工作机制再理解一遍。如果一上来就搞三台机器或一堆容器,网络不通、负载均衡、注册中心互相发现,这些额外变量会彻底淹没课堂主线。单机部署不是生产环境的缩水版,而是把架构里的关键链路完整保留了下来:客户端访问ConfigService、Portal调用AdminService、服务注册到Eureka,这些关键交互一个都不会少,只是少了冗余和弹性。
带着学生先把单机链路跑通,再在总结环节讲清楚“生产环境加机器、加负载均衡、加多环境后,哪些组件会扩展、哪些端口会变化”,这比一开始就铺开一个庞大架构效率高很多。实践课的核心目标是建立体感,不是搭建一个生产级环境。
1.3 部署之前必须背下来的三个服务模块
正式开始敲命令之前,我先要求学生把Apollo服务端的三个模块关系搞清楚,这是整个部署过程中最重要的心智模型。
- ConfigService:负责向客户端提供配置读取和订阅推送,客户端启动时从它这里拉配置,配置变更时也是它实时通知客户端。同时,ConfigService内部内置了一个Eureka Server,用来做服务注册和发现。
- AdminService:负责配置管理操作,Portal后台的所有增删改查最终都是通过调用它来完成的,它直接读写配置数据库。
- Portal:就是那个网页管理后台,用户在这里管理项目、权限、配置项。Portal本身不直接操作配置库,而是通过AdminService的接口落到数据库。
这三个服务的调用关系可以简单理解为:浏览器访问Portal,Portal调用AdminService,AdminService读写ApolloConfigDB;业务系统作为客户端,启动时访问ConfigService,ConfigService读写ApolloConfigDB,同时在配置变更时通过长连接推送给客户端。而ConfigService和AdminService在启动后,都会注册到ConfigService内置的Eureka上,Portal通过Eureka找到AdminService的地址。
如果这套关系没理顺,后面部署出现的很多报错根本无从下手。比如Portal提示“没有可用Admin Service”,本质就是Portal在Eureka里没找到AdminService的注册信息;客户端一直拉不到配置,很可能就是meta地址没有指向ConfigService。带着这张关系图去操作,每一步都有落点。
2. 环境准备:别急着解压,先把地基打好
2.1 虚拟机与JDK版本选择
Apollo的三个服务都是Java编写的Spring Boot应用,部署环境建议直接用Linux虚拟机。我带的课上要求每人准备一台2核4G的虚拟机,内存至少2G,因为三个服务同时运行大概会占用800M到1G内存,再算上操作系统本身,2G内存是底线,再低就会频繁触发垃圾回收,日志里会出现各种超时问题。
JDK版本这里要格外注意。Apollo 2.x基于Spring Boot 2.x,官方要求JDK 8以上,但我实测下来JDK 8是最稳的选择。用JDK 11或17也能启动,但偶尔会遇到反射访问受限、模块系统提示之类的兼容性噪声,对课堂排查来说纯属添乱。如果虚拟机里还没有JDK,直接装OpenJDK 8就行:
sudo yum install -y java-1.8.0-openjdk装完用java -version确认一下版本号。有同学图省事装了最新的JDK 21,结果在启动阶段就报了奇怪的异常,换回JDK 8之后一切正常。这类环境问题,与其现场排查半天,不如一开始就统一版本。
2.2 MySQL准备与初始化脚本导入
Apollo需要MySQL作为元数据存储,5.7和8.0都可以,我这边统一用的8.0。这里有个核心概念必须先讲清楚:Apollo不是只用一个库,而是拆成了两个数据库。
- ApolloConfigDB:存放配置数据,包括项目、集群、命名空间、配置项、发布记录等。
- ApolloPortalDB:存放Portal后台自身的数据,包括用户、角色、权限、操作日志等。
把两个库分开是职责分离的设计:Portal库的权限体系是管理后台自己的事,配置库才是业务系统真正读取的数据。部署包里自带初始化SQL脚本,一般叫apolloconfigdb.sql和apolloportaldb.sql,导入方式很直接:
mysql -uroot -p < /path/to/apolloconfigdb.sql mysql -uroot -p < /path/to/apolloportaldb.sql脚本里面已经包含了建库语句,所以不需要手动先CREATE DATABASE。导入完成之后,可以顺手验证一下表是否生成:
mysql -uroot -p -e "use ApolloConfigDB; show tables;" mysql -uroot -p -e "use ApolloPortalDB; show tables;"ApolloConfigDB里能看到几十张表,ApolloPortalDB里的表数量相对少一些,重点检查PortalDB里有一张users表,里面预置了默认登录账号。这一步很多同学会忽略,直到Portal登录失败才发现是库没导全。
关于数据库连接参数,有一点强烈建议一开始就配好:连接串加上时区和编码参数。MySQL 8对时区比较敏感,不加serverTimezone参数,启动时经常报时区相关的异常;不指定UTF-8,后面如果配置项里有中文,可能一路乱码到客户端。标准写法是这样:
spring.datasource.url = jdbc:mysql://127.0.0.1:3306/ApolloConfigDB?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai spring.datasource.username = root spring.datasource.password = 你的密码2.3 部署包从哪里来:源码编译还是用现成构建产物
第一次部署Apollo的人很容易陷入一个误区:拿到源码包就开始Maven打包。源码包打完包依赖下载量很大,我第一次实习时在实验室网速环境下打了快一个小时,最后还因为某个依赖下载失败导致构建中断。带课之后我直接要求大家用官方发布页的编译产物,也就是Release里打好的zip包,解压之后里面已经是可运行的服务目录结构。
当然,有余力且对构建流程感兴趣的同学,第二周可以自己去拉源码跑一下Maven构建,理解一下多模块项目的打包产物是如何产生的,但课堂四课时的时间非常宝贵,没必要第一天就耗在构建上。拿到部署包之后,按文档要求确认版本,然后分别解压,你会看到configservice、adminservice、portal三个独立的目录。接下来的所有操作都围绕这三个目录展开。
3. 核心实操:Apollo三服务启动全流程
3.1 修改数据库连接:三个配置文件分别改哪里
这是整个部署过程中操作量最大、最容易出错的一步,我在课上专门留了20分钟让大家自己动手改完三个服务的配置文件。
首先要明确:ConfigService和AdminService连的是ApolloConfigDB,Portal连的是ApolloPortalDB。这个对应关系一旦搞反,后面会出现各种匪夷所思的问题。以2.x版本为例,三个服务解压出来的目录命名大概长这样:
- apollo-configservice-2.x.x
- apollo-adminservice-2.x.x
- apollo-portal-2.x.x
需要修改的配置文件分别在对应目录下的config文件夹里。ConfigService和AdminService的配置目录里一般有一个application.properties,把里面spring.datasource开头的几行改成你自己的数据库地址、用户名、密码。修改完后的效果类似:
spring.datasource.url = jdbc:mysql://127.0.0.1:3306/ApolloConfigDB?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai spring.datasource.username = root spring.datasource.password = 123456AdminService的配置除了服务名不同,数据库连接串指向的是同一个ApolloConfigDB,照抄就行。
Portal的application.properties里改成ApolloPortalDB:
spring.datasource.url = jdbc:mysql://127.0.0.1:3306/ApolloPortalDB?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai spring.datasource.username = root spring.datasource.password = 123456只改数据库连接还不够,Portal还要知道自己应该连接哪些环境,以及每个环境对应的ConfigService地址在哪里。这个信息在config目录下的apollo-env.properties里。单机部署场景只需要关心dev环境,确保文件里有这么一行:
dev.meta=http://127.0.0.1:8080这个配置的含义是:当客户端或Portal需要访问dev环境时,去http://127.0.0.1:8080找ConfigService。有些版本里它默认就存在,有些版本里没有,没有的话必须自己补上,否则后面在Portal里创建项目时,环境列表可能是空的。
这里给一个我在课堂上反复强调的经验:版本不同,配置文件名可能有差异,有的版本里叫application-github.properties,有的版本里还保留着demo配置。不要死记文件名,用命令到config目录下搜一下datasource关键字就行:
grep -r "datasource" /path/to/apollo-configservice/config/看到哪个文件里有数据库连接配置,就改哪个文件。这个方法比记文件名可靠得多。
3.2 启动ConfigService与AdminService
配置文件改完,进入启动环节。启动顺序有一定讲究,建议先启动ConfigService,因为它内置了Eureka,是服务注册中心,其他服务启动后需要向它注册。虽然同时启动大多数情况下也能工作,但为了排查方便,养成先启动注册中心的习惯没有坏处。
进入ConfigService目录执行启动脚本:
cd apollo-configservice-2.x.x ./scripts/startup.sh这个脚本默认会在后台启动进程,同时把日志写到logs目录下。第一次启动的同学最常犯的错就是启动完不看日志,发现等了一会儿端口没起来就开始怀疑人生。正确做法是启动之后立刻盯日志:
tail -f logs/configservice.log日志里出现类似Started ApolloConfigService或者Netty started的字样,就说明启动成功了。如果启动失败,日志里会直接抛出异常原因,比盲目猜要高效得多。
ConfigService起来之后,验证一下健康状态:
curl http://127.0.0.1:8080/healthz我印象里这个接口会返回一个简单的响应体,表示服务正常,具体返回内容不同版本略有差别,但请求成功本身就是信号。再验证一下Eureka是否正常工作:
curl http://127.0.0.1:8080/eureka/apps这个接口返回的JSON里会列出当前已经注册到Eureka的实例。刚启动ConfigService时至少能看到它自己注册的信息。
然后同样方式启动AdminService:
cd apollo-adminservice-2.x.x ./scripts/startup.shAdminService启动后会自动向ConfigService内置的Eureka注册。验证健康状态:
curl http://127.0.0.1:8090/healthz如果这一步能通,再回到Eureka的接口看一次,会发现注册列表里多出了AdminService的实例信息。这就在现场验证了服务注册链路。
3.3 启动Portal并完成首次登录
前面两个服务正常之后,最后启动Portal:
cd apollo-portal-2.x.x ./scripts/startup.shPortal默认端口是8070,启动成功后用浏览器访问:
http://你的虚拟机IP:8070浏览器访问和你本机访问不一样,注意防火墙要放行8070端口。默认登录账号是apollo,密码是admin。这里再强调一次,这个账号是写死在ApolloPortalDB初始化脚本里的,如果登录不了,90%的情况是PortalDB没有正确导入,或者Portal连接到了错误的数据库。
首次登录进入Portal,界面是干干净净的空白首页。这时候先别急着创建项目,先确认环境信息。进入管理员工具相关页面,或者直接看系统信息,确认dev环境已经注册。如果环境列表为空,就是你前面apollo-env.properties里的dev.meta没配或者没配对,回去改完重启Portal再看。
到这里,服务端部署已经完成。从浏览器到Portal,从Portal到AdminService,从AdminService到数据库,再到Eureka上两个服务的注册信息,整条管理链路已经通了。但这只完成了一半,部署的最终目的是让业务系统能用上配置中心,还需要做客户端验证。
4. 部署完必须做的一件事:用客户端验证配置拉取与热更新
4.1 在Portal中创建项目并发布配置
服务和后台都正常了,很多同学会以为到这里就大功告成。我给课堂设置的验收标准不是“Portal能登录”,而是“客户端能拉取配置,并且修改配置后客户端能实时收到新值”。
打开Portal,新建一个项目。填写AppId,比如用SampleApp,关联部门随便选,负责人等信息也随意。创建完成之后,进入项目的默认命名空间application,添加一个配置项,key写成greeting,value写成hello-apollo,然后点击发布。
发布在Apollo里是个很重要的动作,因为它会产生一条发布记录并触发变更通知。这里要留意一个小细节:编辑配置之后如果直接退出页面,不点击发布,配置是不会生效的。很多同学在这里漏掉发布操作,然后跑客户端拉不到新配置。
4.2 最小客户端接入
客户端验证我用的是最简单的方式,不用搞复杂的Spring Boot工程,一个Java类加一个依赖就够。新建一个Maven工程,引入apollo-client依赖,写一个main方法:
import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.ConfigService; public class ApolloDemo { public static void main(String[] args) throws Exception { Config config = ConfigService.getAppConfig(); String greeting = config.getProperty("greeting", "default-value"); System.out.println("启动时读取配置: " + greeting); while (true) { Thread.sleep(5000); System.out.println("最新配置: " + config.getProperty("greeting", "default-value")); } } }运行之前通过系统属性指定本次连接的是哪个应用、哪个环境:
-Dapp.id=SampleApp -Dapollo.meta=http://127.0.0.1:8080这里apollo.meta指定的是ConfigService的地址,如果这个参数不配或者配错,客户端就找不到配置服务,启动会一直报错。
运行之后,控制台会打印出hello-apollo。下一步回到Portal,把greeting的值改成hello-changed,再发布一次。客户端那边不需要重启,最多等待几秒钟,就会打印出最新配置: hello-changed。看到这一幕,才算是真正理解了Apollo热更新的含义。
Apollo实现热更新的核心机制是HTTP长轮询。客户端向ConfigService发起一个长连接请求,服务端有配置变更时立即返回,没有变更时就挂住这个请求,等一段时间后再发起下一次。加上客户端本地会缓存配置,即使ConfigService短暂不可用,业务系统也能继续用本地缓存跑,不会因为配置中心故障而立刻瘫痪。当然这些是后话,课堂上看到热更新那一刻,学生的反馈普遍是“原来配置中心是这么工作的”,这比任何讲解都直观。
5. 部署过程的坑与排查实录
5.1 常见问题速查表
这几天课堂加课后答疑下来,我把学生踩过的坑归了个类,整理成一张速查表,部署时照着排查能省很多时间。
| 症状 | 常见原因 | 排查方向 |
|---|---|---|
| Portal登录直接失败 | ApolloPortalDB没导入,或Portal连接库配错 | 检查PortalDB的users表里有没有apollo用户 |
| Portal能登录但环境列表为空 | apollo-env.properties缺少dev.meta配置 | 补上dev.meta后重启Portal |
| ConfigService启动后端口未监听 | JDK版本不对、端口被占用、数据库连接失败 | 看logs/configservice.log的首个异常堆栈 |
| AdminService启动失败 | 数据库连接串指向了PortalDB | 确认AdminService连的是ApolloConfigDB |
| Portal提示没有可用Admin Service | AdminService没启动,或没注册到Eureka | 检查AdminService日志,确认Eureka注册列表 |
| 客户端一直连不上配置 | apollo.meta地址错误,或ConfigService没启动 | 先用curl验证ConfigService的healthz接口 |
| 连接MySQL报时区异常 | 数据库连接串缺少serverTimezone参数 | 按文中的标准连接串修改后重启 |
| 多个服务启动后内存飙升 | 虚拟机内存不够 | 把虚拟机内存加到4G,关闭无关进程 |
| 页面能打开但样式错乱 | 浏览器缓存 | 强刷或用无痕模式重新登录 |
5.2 典型的启动失败日志分析与处理
挑三个出现频率最高的失败场景展开说说。
第一个是数据库连接失败。日志里会看到Access denied for user或者Communications link failure。前者说明账号密码不对,后者说明数据库地址或端口不对。出现这类问题,我的建议是先脱离Apollo单独验证MySQL连接,用命令行登录一下:
mysql -uroot -p -h127.0.0.1 -P3306如果命令行能连上,那问题大概率出在配置文件里的连接串;如果命令行连不上,那问题出在MySQL本身。这样一分层,排查范围立刻缩小。
第二个是Eureka注册相关的问题。AdminService日志里可能反复出现向注册中心注册失败的提示,或者Portal页面一直提示找不到AdminService。这个问题的常见原因是主机名解析异常。当服务之间通过主机名互相访问,而虚拟机里/etc/hosts又没有正确配置时,注册信息里记录的主机名就解析不了,导致Portal通过Eureka拿到的地址不可用。解决方法是确保/etc/hosts里把本机的主机名和IP映射好。这是Apollo部署里非常经典的一个坑,我们在课上至少有两个同学卡在这里。
第三个是端口被占用。8080是一个非常常用的端口,很多同学虚拟机里跑着别的东西,或者刚好有代理占用了8080。如果坚持不改端口,就把占用端口的进程找出来处理掉:
lsof -i:8080如果这个端口确实动不了,改ConfigService端口也可以,但要记得连带修改Portal的dev.meta,因为Portal要凭这个地址找到ConfigService。改一个端口,牵动一条链路,这也是为什么我建议大家部署期间保持默认端口。
5.3 几个避免踩坑的建议
课带下来,我发现大部分问题其实都是操作习惯问题。整理几条我反复强调的建议,提前看一遍能避开很多坑。
- 改完配置文件必须重启对应服务。这个看起来像废话,但实际操作中很多同学改完之后直接刷新浏览器,发现没变化,就开始怀疑前面所有步骤。改哪个服务的数据库配置,就重启哪个服务。
- 多开几个终端窗口:一个窗口用来启动和盯日志,一个窗口用来执行curl验证,一个窗口随时查文件。这样既不会漏掉启动报错,也不会改错目录。
- 三个服务都启动之后,不要急着关虚拟机。先按第四部分的流程做一遍客户端验证,确认热更新确实生效,再认为部署“真正完成”。
- 虚拟机在配置好JDK、MySQL、部署包之后,做一个快照。后面再折腾坏了,直接回滚,不用从头再来。小学期这种高强度推进的场景,快照是成本最低的保险。
- 密码里如果有特殊字符,比如
@、&之类的,在properties文件里要注意转义。这个坑比较隐蔽,之前有个同学密码里带了&,连接串被截断,MySQL一直连不上。
6. 课程完成之后还能怎么延伸
部署跑通、客户端验证通过,这堂课的硬指标就算达成了。但如果有同学还想再往深走一步,有几个方向可以自己练一练。
一个是把Apollo的Open API用起来,通过HTTP接口来创建项目、发布配置,这样可以模拟配置中心与CI/CD流程的对接。另一个方向是把Java客户端接进一个Spring Boot工程,配合@ApolloConfigChangeListener做配置变更监听,把热更新落到实际业务场景里。还有一条路是把单机改成集群,引入Nginx做负载均衡,让多个ConfigService实例一起对外提供服务。这三条路难度递增,但知识点都是从课上的基础链路延伸出来的。
从我个人的带课感受来说,配置中心这种基础设施类组件,最容易走偏的地方就是只讲概念不动手。Apollo这套部署流程能在几小时内走完,却能把配置存储、服务发现、管理端与业务端分离、长轮询推送这些关键机制全部带出来,作为实践课内容,性价比其实很高。部署过程中踩到的每一个坑,回头看都是理解架构的素材,这也是我会把这堂课一直保留在课程计划里的原因。