大学小学期实践课的第四节课,我们安排的是Apollo软件部署实操。如果你没接触过这门课,前面三节分别是Linux基础命令、MySQL数据库使用、Git和Maven工程管理,到这节突然上了一个配置中心组件,很多同学第一反应是:为什么要单独装一个软件管配置?直接在配置文件里改不行吗?
这节实操课结束之后我才发现,Apollo这个选型其实非常贴合"小学期实践"的调性——它不像某些课程那样一句"已经部署好了,大家直接用"就带过,而是需要学生自己拉源码、配数据库、打包、启动三个Java服务,中间会遇到一堆真实部署才会碰到的问题。今天这篇就把整节课的完整过程、踩坑记录和给学生讲的核心原理梳理一遍,给准备上同样内容的朋友一个参考。
1. 课程背景:为什么小学期第四课要选Apollo来练部署
1.1 前三节课的铺设逻辑
很多小学期课程容易走两个极端:要么全是理论课,学生听三天PPT然后交一份报告;要么直接丢一个高大上的项目,学生照着文档复制粘贴,根本不知道自己在干什么。我们这门课的实际思路是:先让你有操作Linux、操作数据库、管理工程代码的底子,再拿一个真实可用的开源软件让你独立部署一遍,把前面三节课的知识全部串起来。
部署Apollo这件事,恰好能把前三节全部用上:你需要用Linux命令解压、创建目录、查看进程;需要连MySQL建库导数据;需要用Maven拉依赖打jar包;需要改配置文件然后用Git版本管理你的改动。如果前面三节课是散的点,这节就是一条把它们拧在一起的线。
1.2 Apollo配置中心到底解决什么问题
先跟完全没接触过的同学说一下Apollo是什么。假设你现在维护五个微服务,每个服务里都有一份application.yml,里面写着数据库地址、Redis地址、各种开关参数。哪天数据库地址变了,你得挨个改五个文件,然后挨个重新打包、重新部署、重启服务,一整套流程下来半小时起步。
Apollo做的事情,就是把所有配置集中到一个管理界面上,服务启动的时候从Apollo拉取配置,你在Apollo后台改一个配置项,运行中的服务能在几秒内自动拿到新值,不需要重新打包也不用重启。这种"配置集中管理+动态下发"的能力,是微服务架构里非常基础的一环。单纯讲概念学生很难有体感,但让他自己部署一遍,再写个demo验证配置实时生效,理解就完全不一样了。
2. 部署前必须理清的两个认知:三组件分工和两套数据库
2.1 Config Service、Admin Service、Portal三者的关系
Apollo整体拆成了三个Java服务,名字很容易混淆,但分工其实很清晰。
- Config Service:负责给业务系统的客户端提供配置读取、订阅推送功能。业务应用启动时会连它拿配置,配置变更时也是它把最新值推给客户端。它默认端口是8080。
- Admin Service:负责给管理后台提供配置管理、发布、回滚等功能。你可以理解成它是"干活的",管理后台的所有操作最终由它落到数据库里。默认端口8090。
- Portal:管理员操作的可视化网页,默认端口8070。它本身不直接操作业务配置,而是通过Admin Service完成操作。
刚开始部署时学生最容易搞混的就是这三个服务的职责,我通常会打一个比方:Portal是餐厅前台,Admin Service是后厨,Config Service是外卖窗口。顾客(业务应用)去外卖窗口取餐,前台负责下单,后厨负责做饭,三者不能互相替代。
2.2 两个数据库分别存什么
Apollo部署需要初始化两个MySQL数据库,这个也是初学者容易搞乱的地方。
- ApolloConfigDB:存的是配置本身,包括每个项目的配置项、发布历史、灰度规则等,Config Service和Admin Service都用这个库。
- ApolloPortalDB:存的是Portal后台的用户信息、项目列表、权限等门户数据,Portal服务使用这个库。
为什么拆成两个而不是一个?因为ConfigDB是运行时数据,业务依赖非常重,写操作频繁;PortalDB是管理端数据,相对独立。拆开之后,我们可以把ConfigDB做更细的读写分离和容灾,Portal的日常维护也不会影响配置读写链路。你在执行SQL脚本的时候一定分清哪个脚本进哪个库,装反了后面会非常痛苦。
3. 部署实操链路:从源码编译到三个服务全部启动
3.1 获取源码与工程结构
我们课程里让学生从官方仓库Clone了Apollo的源码工程,也可以用课程组准备的离线包,反正结构是一样的。Clone下来之后主目录下有几个子工程,重点看这三个:
- apollo-configservice
- apollo-adminservice
- apollo-portal
它们的共同父工程里管理着Maven依赖版本。我建议学生先不要改任何代码,先跑通官方默认配置,成功之后再尝试改端口、改数据库连接,这样排查问题的时候能分清楚是环境问题还是代码问题。
3.2 初始化数据库的具体步骤
用MySQL客户端连上数据库后,执行官方项目里的两个SQL脚本,一个叫apolloconfigdb.sql,一个叫apolloportaldb.sql。需要说明的是,不同版本脚本内容有差异,最好和源码版本保持一致,不要拿1.x的脚本配2.x的代码。
执行命令大概是这样的:
mysql -u root -p < apolloconfigdb.sql mysql -u root -p < apolloportaldb.sql执行完可以进MySQL看一眼:
SHOW DATABASES; USE ApolloConfigDB; SHOW TABLES;正常情况下能看到一堆表,比如APP、ITEM、RELEASE等,没有报错说明导入成功。这里有两个小坑:
- 建库时字符集需要是utf8mb4,否则后面如果存了Emoji之类的特殊字符会报错。
- 如果之前执行过脚本,想重新导入,建议先DROP DATABASE再重新执行,避免残留数据干扰。
3.3 打包编译与启动参数的调整
源码工程拿到并且数据库就绪后,进到Apollo工程根目录执行打包:
mvn clean package -DskipTests这一步会拉取大量依赖,耗时比较长,建议提前把Maven仓库配好国内镜像。打包完成后,三个子工程的target目录下会各自生成一个可执行的jar包,类似:
- apollo-configservice-2.x.x.jar
- apollo-adminservice-2.x.x.jar
- apollo-portal-2.x.x.jar
在没有改任何配置的情况下直接启动,默认连的是开发者本机的localhost数据库,密码为空。实际教学环境里MySQL一定有密码,所以必须改数据库连接配置。这个配置在三个服务各自的application.properties里,核心配置如下:
# Config Service 和 Admin Service 都需要改 spring.datasource.url=jdbc:mysql://localhost:3306/ApolloConfigDB?characterEncoding=utf8&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=你的密码 # Portal 改的是自己的库 spring.datasource.url=jdbc:mysql://localhost:3306/ApolloPortalDB?characterEncoding=utf8&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=你的密码这里如果MySQL是8.x版本,连接串里建议加上useSSL=false和allowPublicKeyRetrieval=true,不然启动时经常报Public Key Retrieval is not allowed,这个错我在几个学生机器上反复见到过。
3.4 按顺序启动服务并验证注册情况
配置改好之后按顺序启动。先启动Config Service:
java -Xms256m -Xmx512m -jar apollo-configservice-2.x.x.jar看到日志里出现Tomcat started on port 8080并且没有异常堆栈,说明Config Service起来了。然后启动Admin Service:
java -Xms256m -Xmx512m -jar apollo-adminservice-2.x.x.jar它的日志里会包含向注册中心注册自己的信息。最后启动Portal:
java -Xms256m -Xmx512m -jar apollo-portal-2.x.x.jar三个服务全部启动后,浏览器访问http://localhost:8080可以看到一个Eureka注册中心页面,正常的话应该列出CONFIG-SERVICE和ADMIN-SERVICE两个服务实例。这一步是整个部署过程中的核心验证点——如果Eureka页面能看到两个服务,说明前两个服务注册成功;如果只有Config Service或者两个都没有,那问题就出在注册链路或数据库连接上。
4. 实测中躲不开的几个坑与排查思路
4.1 服务起不来先查数据库连接日志
前面说的数据库连接问题是最常见的一类。学生启动服务时,经常出现进程起来了,服务自己也打印了启动日志,但几秒之后就报错退出。这种时候第一反应应该是去看最后几行异常信息,而不是重新启动一次。
我之前带学生排查过一个案例:某同学MySQL使用的是8.x版本,密码没问题,连接地址也没问题,但没加allowPublicKeyRetrieval=true,报错内容是Public Key Retrieval is not allowed。这个报错信息其实已经告诉你了——MySQL8默认使用caching_sha2_password认证,第一次连接时需要从服务端获取公钥来加密密码传输,连接参数里没允许这个行为就拒绝执行。加上参数就好了。
还有一种情况是数据库连接串里的serverTimezone没设置,Java驱动和MySQL时区不一致会直接抛异常。这类问题在教学中出现的频率比我预想的高很多,根源是很多同学习惯照抄别人的连接串,却不知道每个参数是干什么用的。
4.2 Eureka页面显示两个服务,但Portal登录后看不到环境
另一个高频坑是:Eureka页面一切正常,Config Service和Admin Service都注册上了,Portal也能登录进去,但界面上找不到"环境"选项,或者创建项目时无法选择环境。
这个问题的根子在Portal这个服务需要知道自己该往哪个环境发布配置。它读取的配置项是apollo.portal.envs和dev.meta:
apollo.portal.envs=dev dev.meta=http://localhost:8080第一行告诉Portal当前系统里有哪些环境,我们只准备了一个dev环境,所以就是dev。第二行告诉Portal,dev环境的元数据服务地址是Config Service的地址,也就是8080端口。这里有个特别容易写错的地方:dev.meta填的是Config Service的地址,不是Admin Service,也不是Portal自己。很多同学会想当然地填成8090或者8070,结果就是后台永远感知不到配置服务的存在。
如果这个配置没写,Portal会出现一种很迷惑的状态:能登录、能进后台,但是看不到任何环境按钮,项目创建流程没法完成。这个时候别急着检查容器网络,先看一眼Portal的配置文件里有没有这两行。
4.3 Eureka自我保护模式下的红色告警误判
还有一次遇到的情况很有意思:某个同学的Eureka页面是正常的黄色背景,上面有一行红色WARNING提示,大意是"自我保护模式已启用,某个服务实例可能已经崩溃"。这位同学以为自己部署失败了,紧张地截图发过来。
其实这是Eureka自我保护机制触发了。如果服务实例心跳不健康,Eureka宁可让旧实例待在注册表里,也不轻易把它踢出去,防止因网络抖动导致大面积下线。在单机部署的Apollo里,只有一个Config Service实例和一个Admin Service实例,没有其他副本做心跳备份,很容易进入这个状态。
遇到这种情况不要慌,重点看页面上的实例列表:只要CONFIG-SERVICE和ADMIN-SERVICE都在"UP"状态,就说明服务是正常的。如果确实是DOWN了,再去翻对应服务的日志,多半是配置变更或内存不足导致的,而不是自我保护机制本身的问题。
4.4 教学机的内存限制与启动参数调整
小学期教室里的电脑配置一般不会太好,每台机器还要同时开虚拟机、数据库、浏览器,如果三个Java服务都用默认参数启动,内存会很紧张。默认情况下JVM堆可能分配得比较大,学生机器直接卡死的情况我们都遇到过。
建议在课程开始时统一让大家用这个参数启动:
java -Xms128m -Xmx256m -jar apollo-configservice-2.x.x.jar java -Xms128m -Xmx256m -jar apollo-adminservice-2.x.x.jar java -Xms64m -Xmx128m -jar apollo-portal-2.x.x.jar把堆内存调小之后,三个服务加起来大约占400MB左右,教学机完全扛得住。不过堆内存太小会让GC频繁一些,在练习环境里完全够用,这是教学演示场景下非常实用的一个取舍。
5. 部署完成后的验证闭环:从管理后台到客户端热更新
5.1 管理后台创建项目并发布一条配置
部署不是终点,得真正用起来才算完成。登录Portal(http://localhost:8070),默认账号是apollo,密码是admin,不过不同版本初始账号可能不同,以官方文档为准。登进去后第一步是创建项目:
- 应用ID(AppId):建议取一个唯一值,比如demo001
- 应用名称:随便填
- 应用部门/负责人:填空或随便填都可以
创建完成以后进入配置管理界面,新增一个配置项。比如:
- Key:test.name
- Value:hello-apollo
填完点击发布。到这里你已经在Apollo里成功发布了一条配置到dev环境,这条配置现在对任何配置了AppId=demo001的客户端开放了。
5.2 写一个最小的Spring Boot客户端来拉取配置
为了让学生真实感受到配置下发链路,我们要求每人写一个极简的Spring Boot工程来连接Apollo。核心依赖和代码就几行,但信息密度很高。
先在pom.xml里引入客户端依赖:
<dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-client</artifactId> <version>2.x.x</version> </dependency>创建一个Spring Boot入口类,加上Apollo配置注解:
@SpringBootApplication @EnableApolloConfig public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }然后在application.properties里指定当前项目的AppId和Apollo地址:
app.id=demo001 apollo.meta=http://localhost:8080这里app.id必须和你在Portal里创建项目时填的完全一致,Apollo就是靠这个app.id去定位配置的。apollo.meta指向Config Service端口,不是Admin也不是Portal。接着写一个接口读取配置:
@RestController public class ConfigController { @Value("${test.name:default}") private String testName; @GetMapping("/test") public String test() { return testName; } }启动这个Spring Boot应用,访问http://localhost:8081/test,能看到返回hello-apollo,说明客户端从Apollo拉到了配置。
5.3 热更新演示:不重启应用的配置修改
验证热更新是整节课最直观的高潮环节。保持Spring Boot应用不重启,回到Portal后台,把test.name的值从hello-apollo改成hello-again,点发布,然后再次访问客户端的/test接口。
正常情况下,几秒钟后返回结果就变成了hello-again,全程不需要重启任何进程。这个体验对初次接触配置中心的同学来说非常震撼——原来改配置可以不用发版本。背后是客户端和Config Service之间维持了一条长连接,服务端配置变化时会主动推送变更事件给客户端,客户端收到事件后刷新Spring容器里的配置值。
这一环是整个实操课程里最值得花时间讲透的部分。前面所有部署、踩坑、排错,都是为了最后这一刻能顺滑地演示"配置发布即生效"的效果。
6. 这节实操课我的总结与建议
Apollo的部署实操放到小学期第四课,从难度上说刚好卡在一个微妙的位置——比单纯装软件难,比从零写框架简单,非常适合用来锻炼学生排查问题的基本功。我在带课过程中最深的一个体会是:大部分学生第一次部署失败都不是因为步骤写得不清楚,而是遇到报错后慌慌张张重来一遍,没有先理解报错信息在说什么。
建议准备上类似课程的同学,无论你是老师还是学生,都提前做三件事:
- 把数据库连接串里的每个参数弄清楚再动手,尤其是时区、SSL、认证方式这些容易被忽略的项,它们在现代MySQL版本里会频繁出来找麻烦。
- 理解三个服务加两个数据库的拓扑关系后再敲命令,不要照着文档一路启动,否则出了问题你连哪个服务挂了都判断不了。
- 把部署成功后的验证环节当成课程的一部分,每次都加一个客户端拉配置的demo,确保整个链路确实通了,而不是停留在"页面能打开"的表面成功。
最后说一个我实际带课时给学生的经验:部署类实操最容易出错的地方,往往不是大段代码逻辑,而是那些看起来最不起眼的配置文件单行参数。教大家在排查问题的时候,先看配置文件,再看数据库连接,最后才去看代码——按这个顺序排查,大部分问题都能在十分钟内定位。小学期的价值不在于把每一个工具都玩得精通,而在于建立"部署、验证、排错"这个完整闭环的直觉,这比记住任何一条具体命令都重要。