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-start\sql 下面的兩個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.准備數據庫,如下:
項目中scripts\sql\下面有兩個腳本。
由於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("===\t " + 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的配置區分是:環境(DEV\PRO)->集群(默認是default)->namespace,和nacos的namespace、grop、以及環境有點區別。
4.灰度測試實際是可以先用配置對部分服務節點產生影響,之后測試沒問題可以合並到主分支對所有節點生效,或者有問題刪除灰度測試的配置。
5.Springboor應用client區分環境是通過apollo.meta區分,值是configservice的地址;區分集群通過配置apollo.cluster;區分namespace通過配置apollo.bootstrap.namespaces