Apollo(阿波罗)是携程框架部门研发的开源配置管理中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性。
官网:https://github.com/ctripcorp/apollo/wiki
码云地址:https://gitee.com/nobodyiam/apollo
关于架构以及设计参考码云地址。
========基于Quick Start安装包构建======
1.apollo服务器搭建
1.下载Quick Start安装包(补充,通过这种方式不支持增加环境,只有DEV环境)
通过网盘链接https://pan.baidu.com/s/1Ieelw6y3adECgktO0ea0Gg下载,提取码: 9wwe
下载到本地后,在本地解压apollo-quick-start.zip
2.解压后安装
(1)Apollo服务端共需要两个数据库:ApolloPortalDB和ApolloConfigDB,只需要导入数据库即可。
apollo-quick-startsql 下面的两个SQL脚本:
(2)配置数据库连接信息:修改demo.sh(从该脚本也可以看出只支持dev环境)
Apollo服务端需要知道如何连接到你前面创建的数据库,所以需要编辑demo.sh,修改ApolloPortalDB和ApolloConfigDB相关的数据库连接串信息。
补充:由于启动失败我还修改了eureka的启动时间:
(3)启动eureka服务,启动eureka服务7001端口。
(4)启动apollo服务
$ ./demo.sh start
启动后日志如下:
liqiang@root MINGW64 ~/Desktop/apollo-quick-start $ ./demo.sh start Windows new JAVA_HOME is: /c/PROGRA~1/Java8/JDK18~1.0_1 ==== starting service ==== Service logging file is ./service/apollo-service.log Started [14708] Waiting for config service startup................................. Config service started. You may visit http://localhost:8080 for service status now! Waiting for admin service startup........ Admin service started ==== starting portal ==== Portal logging file is ./portal/apollo-portal.log rm: cannot remove './portal/apollo-portal.jar': Device or resource busy ln: failed to create hard link './portal/apollo-portal.jar': File exists Started [13524] Waiting for portal startup...................... Portal started. You can visit http://localhost:8070 now!
(5)访问8070端口后登陆,用户名apollo,密码admin后登录
(6)访问8080查看eureka服务:
2.Springcloud项目中集成apollo
1.apollo中创建项目并发布个配置文件
1.新建项目
2.如下新增一个key
3.点击发布按钮进行发布
4.查看列表如下:
1.IDEA新建项目获取上面配置
1.新建项目
2.修改pom
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud</artifactId> <groupId>cn.qz.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-config-apollo-3366</artifactId> <dependencies> <!--apollo--> <dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-client</artifactId> <version>1.1.0</version> </dependency> <!--引入自己抽取的工具包--> <dependency> <groupId>cn.qz.cloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--监控--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--热部署--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
3.修改yml
server:
port: 3366
app:
# 与apollo的admin管理界面添加的 appId 一致
id: cloud-config-apollo
apollo:
# meta的url
meta: http://localhost:8080
bootstrap:
enabled: true
eagerLoad:
enabled: true
4.主启动类:
package cn.qz.cloud; import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @Author: qlq * @Description * @Date: 12:41 2020/10/31 */ @SpringBootApplication @EnableApolloConfig public class ApolloConfigMain3366 { public static void main(String[] args) { SpringApplication.run(ApolloConfigMain3366.class, args); } }
5.测试类:
package cn.qz.cloud.controller; import cn.qz.cloud.utils.JSONResultUtil; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @Author: qlq * @Description * @Date: 12:43 2020/10/31 */ @RestController @RequestMapping("/config/apollo") public class ConfigController { @Value("${config.info}") private String configInfo; @GetMapping("/getConfigInfo") public JSONResultUtil<String> getConfigInfo() { return JSONResultUtil.successWithData(configInfo); } }
6.测试:
liqiang@root MINGW64 /e/IDEAWorkSpace/cloud (master) $ curl http://localhost:3366//config/apollo/getConfigInfo % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 81 0 81 0 0 1306 0 --:--:-- --:--:-- --:--:-- 2612{"success":true,"code":"200","msg":"","data":"apollo config info!!! version = 1"}
7.apollo修改后测试: 将version修改为2
liqiang@root MINGW64 /e/IDEAWorkSpace/cloud (master) $ curl http://localhost:3366//config/apollo/getConfigInfo % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 81 0 81 0 0 1306 0 --:--:-- --:--:-- --:--:-- 2612{"success":true,"code":"200","msg":"","data":"apollo config info!!! version = 2"}
补充:直接下载的quick start 包不支持多环境,所以只能从8080的dev环境获取信息。
=====分布式部署方式====
上面通过快速入门大概有了个了解。下面详细分析下。
1.简介
Apollo分为客户端和服务端。
服务端基于Springboot和Springcloud开发。打包后可以直接运行,不需要额外tomcat等容器。
Java客户端不依赖任何框架,能够运行于所有Java运行时环境,同时Spring/Springboot环境也有较好的支持。
1.架构图如下:
从下往上看:
Config Service提供配置的读取、推送等功能,服务对象是Apollo客户端
Admin Service提供配置的修改、发布等功能,服务对象是Apollo Portal(管理界面)
Config Service和Admin Service都是多实例、无状态部署,所以需要将自己注册到Eureka中并保持心跳
在Eureka之上我们架了一层Meta Server用于封装Eureka的服务发现接口
Client通过域名访问Meta Server获取Config Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Client侧会做load balance、错误重试
Portal通过域名访问Meta Server获取Admin Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Portal侧会做load balance、错误重试
为了简化部署,我们实际上会把Config Service、Eureka和Meta Server三个逻辑角色部署在同一个JVM进程中
1.四个核心模块及其主要功能
ConfigService:提供配置获取接口、提供配置推送接口、服务于Apollo客户端
AdminService:提供配置管理接口、提供配置修改发布接口、服务于管理界面Portal
Client:为应用获取配置,支持实时更新、通过MetaServer获取ConfigService的服务列表、使用客户端软负载SLB方式调用ConfigService
Portal:配置管理界面、通过MetaServer获取AdminService的服务列表、使用客户端软负载SLB方式调用AdminService
2.三个辅助服务发现模块
Eureka:用于服务发现和注册、Config/AdminService注册实例并定期报心跳、和ConfigService在一起部署
MetaServer:Portal通过域名访问MetaServer获取AdminService的地址列表、Client通过域名访问MetaServer获取ConfigService的地址列表、相当于一个Eureka Proxy逻辑角色,和ConfigService在一起部署
NginxLB:和域名系统配合,协助Portal访问MetaServer获取AdminService地址列表、和域名系统配合,协助Client访问MetaServer获取ConfigService地址列表、和域名系统配合,协助用户访问Portal进行配置管理
2.克隆代码之后执行
代码从码云克隆之后,查看项目如下:
用到的三个主要项目:apollo-configservice、apollo-adminservice、apollo-portal。也可以自己从这三个项目入手查看源码。
apollo-configservice 中包含了eurekaServer,主要是为客户端使用。
apollo-adminservice 注册到上面的eurekaServer中。主要为portal管理界面服务
apollo-portal中,从上面eurekaServer中根据服务名称获取到adminService进行服务。
这里演示两个环境。dev和prod环境。总共支持的环境如下:
public enum Env{ LOCAL, DEV, FWS, FAT, UAT, LPT, PRO, TOOLS, UNKNOWN; public static Env fromString(String env) { Env environment = EnvUtils.transformEnv(env); Preconditions.checkArgument(environment != UNKNOWN, String.format("Env %s is invalid", env)); return environment; } }
1.准备数据库,如下:
项目中scriptssql下面有两个脚本。
由于apollo多环境部署的模式是一个portal,每个环境对应一个adminservice和一个configservice。所以准备三个库。
到mysql执行后会创建两个数据库:ApolloConfigDB和ApolloPortalDB
另外加一个数据库:ApolloConfigDB1 (数据和上面ApolloConfigDB一样,作为prod环境的数据库)
注意:修改ApolloConfigDB1 数据库中,serverconfig表中eureka.service.url为http://localhost:8081/eureka/
2.启动服务
前提,下面启动均需要修改启动参数,修改方式点击Edit Configurations:
1.启动两个configservice服务
(1)修改vm,第一次配置如下:
-Dapollo_profile=github -Dserver.port=8081 -Dspring.datasource.url=jdbc:mysql://localhost:3306/apolloconfigdb1?characterEncoding=utf8&serverTimezone=UTC -Dspring.datasource.username=root -Dspring.datasource.password=123456 -Deureka.client.service-url.default-zone=http://localhost:8081/eureka/
(2)第二次如下:
-Dapollo_profile=github -Dserver.port=8080 -Dspring.datasource.url=jdbc:mysql://localhost:3306/apolloconfigdb?characterEncoding=utf8&serverTimezone=UTC -Dspring.datasource.username=root -Dspring.datasource.password=123456 -Deureka.client.service-url.default-zone=http://localhost:8080/eureka/
2.启动两个adminservice
(1)第一次vm参数如下:
-Dapollo_profile=github -Dserver.port=8090 -Dspring.datasource.url=jdbc:mysql://localhost:3306/apolloconfigdb?characterEncoding=utf8&serverTimezone=UTC -Dspring.datasource.username=root -Dspring.datasource.password=123456 -Deureka.client.service-url.default-zone=http://localhost:8080/eureka/
(2)第二个参数
-Dapollo_profile=github -Dserver.port=8090 -Dspring.datasource.url=jdbc:mysql://localhost:3306/apolloconfigdb?characterEncoding=utf8&serverTimezone=UTC -Dspring.datasource.username=root -Dspring.datasource.password=123456 -Deureka.client.service-url.default-zone=http://localhost:8080/eureka/
3.查看Eureka服务
8081服务:
8080服务:
4.启动portal服务
(1) vm参数为:
-Dapollo_profile=github,auth -Ddev_meta=http://localhost:8080/ -Dpro_meta=http://localhost:8081/ -Dserver.port=8070 -Dspring.datasource.url=jdbc:mysql://localhost:3306/ApolloPortalDB?characterEncoding=utf8&serverTimezone=UTC -Dspring.datasource.username=root -Dspring.datasource.password=123456
(2)修改数据库apolloportaldb的表serverconfig的apollo.portal.envs的值为dev,pro
5.启动apollo之后访问8070端口创建项目
6.查看由两个环境
7.dev环境和prod环境发布一个key
(1)dev
(2)pro环境
补充:.了解到不同你给的环境需要不同的configservice和adminservice。比如再增加一个环境beta需要再增加一个configservice和一个adminservice。
通过跟代码简单了解下如何如何区分环境。以点击PRO环境接口为例子。大致流程为:获取环境变量Env-》获取到基路径adminService地址-》拼接地址进行访问获取所需信息。也就验证了上面adminService是为portal服务的。
(1)访问dcontroller是http://localhost:8070/apps/cloud-config-apollo/envs/PRO/clusters/default/namespaces
(2)后端接口NamespaceController.findNamespaces方法,方法内部调用NamespaceService.findNamespaceBOs()方法,调用namespaceAPI.findNamespaceByCluster()方法
(3)namespaceAPI.findNamespaceByCluster方法如下:
public List<NamespaceDTO> findNamespaceByCluster(String appId, Env env, String clusterName) { NamespaceDTO[] namespaceDTOs = restTemplate.get(env, "apps/{appId}/clusters/{clusterName}/namespaces", NamespaceDTO[].class, appId, clusterName); return Arrays.asList(namespaceDTOs); }
(4)get方法如下:
public <T> T get(Env env, String path, Class<T> responseType, Object... urlVariables) throws RestClientException { return execute(HttpMethod.GET, env, path, null, responseType, urlVariables); }
(5)execute
private <T> T execute(HttpMethod method, Env env, String path, Object request, Class<T> responseType, Object... uriVariables) { if (path.startsWith("/")) { path = path.substring(1); } String uri = uriTemplateHandler.expand(path, uriVariables).getPath(); Transaction ct = Tracer.newTransaction("AdminAPI", uri); ct.addData("Env", env); List<ServiceDTO> services = getAdminServices(env, ct);
(6)getAdminServices
private List<ServiceDTO> getAdminServices(Env env, Transaction ct) { List<ServiceDTO> services = adminServiceAddressLocator.getServiceList(env); if (CollectionUtils.isEmpty(services)) { ServiceException e = new ServiceException(String.format("No available admin server." + " Maybe because of meta server down or all admin server down. " + "Meta server address: %s", portalMetaDomainService.getDomain(env))); ct.setStatus(e); ct.complete(); throw e; } return services; }
(7) getServiceList
public List<ServiceDTO> getServiceList(Env env) { List<ServiceDTO> services = cache.get(env); if (CollectionUtils.isEmpty(services)) { return Collections.emptyList(); } List<ServiceDTO> randomConfigServices = Lists.newArrayList(services); Collections.shuffle(randomConfigServices); return randomConfigServices; }
跟断点返回的信息:
(8)根据环境获取到adminservice地址之后拼接URL进行访问:
private String parseHost(ServiceDTO serviceAddress) { return serviceAddress.getHomepageUrl() + "/"; }
8.springboot项目中获取配置
这里用cloud-config-apollo-3366服务进行测试。通过测试也明白了apollo区分不同的环境是通过meta地址来区分,也就是apollo中configService的地址。
(1)bootstrap.yml如下:
server: port: 3366 app: # 与apollo的admin管理界面添加的 appId 一致 id: cloud-config-apollo apollo: # meta的url meta: http://localhost:8080 bootstrap: enabled: true # 从 namespace 中获取配置, 多个以逗号隔开, namespace的配置非properties格式的需要加后缀名 # namespaces: application,gateway,redis eagerLoad: enabled: true
测试:
liqiang@root MINGW64 /e/IDEAWorkSpace $ curl http://localhost:3366/config/apollo/getConfigInfo % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 63 0 63 0 0 1000 0 --:--:-- --:--:-- --:--:-- 3937{"success":true,"code":"200","msg":"","data":"dev version = 1"}
(2)修改meta的地址为8081的pro环境的meta地址:
server: port: 3366 app: # 与apollo的admin管理界面添加的 appId 一致 id: cloud-config-apollo apollo: # meta的url meta: http://localhost:8081 bootstrap: enabled: true # 从 namespace 中获取配置, 多个以逗号隔开, namespace的配置非properties格式的需要加后缀名 # namespaces: application,gateway,redis eagerLoad: enabled: true
测试:
liqiang@root MINGW64 /e/IDEAWorkSpace $ curl http://localhost:3366/config/apollo/getConfigInfo % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 63 0 63 0 0 52 0 --:--:-- 0:00:01 --:--:-- 53{"success":true,"code":"200","msg":"","data":"pro version = 1"}
9.补充namespace的用法
(1)创建namespace
(2)为mynamespace的pro环境创建配置信息并发布:
(3)修改yml中的namespace测试
server: port: 3366 app: # 与apollo的admin管理界面添加的 appId 一致 id: cloud-config-apollo apollo: # meta的url(用于区分apollo中不同的环境) meta: http://localhost:8081 bootstrap: enabled: true # 从 namespace 中获取配置, 多个以逗号隔开, namespace的配置非properties格式的需要加后缀名 namespaces: mynamespace eagerLoad: enabled: true
测试:
liqiang@root MINGW64 /e/IDEAWorkSpace $ curl http://localhost:3366/config/apollo/getConfigInfo % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 88 0 88 0 0 104 0 --:--:-- --:--:-- --:--:-- 106{"success":true,"code":"200","msg":"","data":"pro version = 1! namespace = mynamespace"}
10.补充集群的用法
(1)创建集群
(2)新建配置
(3)yml中指定集群
server: port: 3366 app: # 与apollo的admin管理界面添加的 appId 一致 id: cloud-config-apollo apollo: # meta的url(用于区分apollo中不同的环境) meta: http://localhost:8081 cluster: cluster1 bootstrap: enabled: true # 从 namespace 中获取配置, 多个以逗号隔开, namespace的配置非properties格式的需要加后缀名 # namespaces: mynamespace eagerLoad: enabled: true
测试:
liqiang@root MINGW64 /e/IDEAWorkSpace $ curl http://localhost:3366/config/apollo/getConfigInfo % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 72 0 72 0 0 461 0 --:--:-- --:--:-- --:--:-- 576{"success":true,"code":"200","msg":"","data":"cluster1 pro version = 1"}
10.springboot以配置bean的方式注入属性
(1)apollo新增配置:
(2)pom加入如下配置:
<!--使用刷新配置注解--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-context</artifactId> </dependency>
(3)新增配置类ApolloConfig,按前缀进行注入
package cn.qz.cloud.config; import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.stereotype.Component; /** * @Author: qlq * @Description * @Date: 22:55 2020/10/31 */ @Component @ConfigurationProperties(prefix = "es") @Getter @Setter @RefreshScope public class ApolloConfig { private String host; }
(4)增加ConfigRefresh允许自动更新
package cn.qz.cloud.config; import com.ctrip.framework.apollo.model.ConfigChangeEvent; import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.context.scope.refresh.RefreshScope; import org.springframework.stereotype.Component; /** * bean配置的方式自动刷新 * * @Author: qlq * @Description * @Date: 23:08 2020/10/31 */ @Component public class ConfigRefresh { @Autowired private RefreshScope refreshScope; @ApolloConfigChangeListener public void onChange(ConfigChangeEvent event) { for (String key : event.changedKeys()) { System.out.println("=== " + key); if (key.startsWith("es")) { refreshScope.refresh("apolloConfig"); break; } } } }
11. 补充:灰度发布的用法。灰度发布实际就是相当于先对部分节点进行测试,然后更新更新到所有节点或者删除灰度数据
(1)点击灰度之后点击新增灰度配置:es.host,值为 127.0.0.1:9200灰度测试
(2)新增灰度规则:实际就是对哪些节点有效
(3)灰度发布之后测试:取的是灰度的值
liqiang@root MINGW64 /e/IDEAWorkSpace $ curl http://localhost:3366/config/apollo/getEsHost % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 74 0 74 0 0 53 0 --:--:-- 0:00:01 --:--:-- 53{"success":true,"code":"200","msg":"","data":"127.0.0.1:9200灰度测试"}
(4)灰度发布同步到主版本:选择全量发布
(5)发布成功之后会和主版本保持一致
(6)修改信息为如下:
测试:
liqiang@root MINGW64 /e/IDEAWorkSpace $ curl http://localhost:3366/config/apollo/getEsHost % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 83 0 83 0 0 1765 0 --:--:-- --:--:-- --:--:-- 5187{"success":true,"code":"200","msg":"","data":"127.0.0.1:9200灰度测试222222222"}
(7)放弃灰度:直接点击 放弃灰度 后确认
测试:
liqiang@root MINGW64 /e/IDEAWorkSpace $ curl http://localhost:3366/config/apollo/getEsHost % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 74 0 74 0 0 2387 0 --:--:-- --:--:-- --:--:-- 4933{"success":true,"code":"200","msg":"","data":"127.0.0.1:9200灰度测试"}
12.补充:apollo构建高可用
apollo构建高可用,实际上是通过对configservice和adminservice进行集群。类似于springcloud的应用进行集群部署。
总结:
1.apollo四大核心组件:
configservice: 主要作用是提供eureka服务器以及为client服务
adminservice:注册到上面的eureka服务器,主要是为portal管理界面提供服务
portal:主要是为界面提供服务,从eureka服务器中获取adminservice调用对应接口。(不同的环境对应不同的conigservice)
client:从configservice获取配置信息
2.新建一个环境的时候一般是增加configservice和adminservice两个服务,当然要新建数据库。实现不同环境直接的数据隔离。
3.apollo的配置区分是:环境(DEVPRO)->集群(默认是default)->namespace,和nacos的namespace、grop、以及环境有点区别。
4.灰度测试实际是可以先用配置对部分服务节点产生影响,之后测试没问题可以合并到主分支对所有节点生效,或者有问题删除灰度测试的配置。
5.Springboor应用client区分环境是通过apollo.meta区分,值是configservice的地址;区分集群通过配置apollo.cluster;区分namespace通过配置apollo.bootstrap.namespaces