第一部分: Apollo簡介
隨着程序功能的日益復雜,程序的配置日益增多:各種功能的開關、參數的配置、服務器的地址……
對程序配置的期望值也越來越高:配置修改后實時生效,灰度發布,分環境、分集群管理配置,完善的權限、審核機制……
在這樣的大環境下,傳統的通過配置文件、數據庫等方式已經越來越無法滿足開發人員對配置管理的需求。
Apollo配置中心應運而生!
1、Apollo簡介
Apollo支持4個維度管理Key-Value格式的配置:
application (應用) environment (環境) cluster (集群) namespace (命名空間)
配置基本概念
配置是獨立於程序的只讀變量 配置獨立於程序的,同一個程序在不同的配置下有不同的行為 配置對於程序是只讀的,程序通過讀取配置來改變自己的行為,程序不應該去改變配置 配置伴隨應用的整個生命周期 配置貫穿於應用的整個生命周期,應用在啟動時通過讀取配置來初始化,在運行時根據配置調整行為 配置可以有多種加載方式 配置文件、環境變量、啟動參數、基於數據庫 配置需要治理 權限控制(由於配置能改變程序的行為,不正確的配置甚至能引起災難,所以對配置的修改必須有比較完善的權限控制) 不同環境、集群配置管理(同一份程序在不同的環境(開發,測試,生產)、不同的集群(如不同的數據中心)經常需要有不同的配置,所以需要有完善的環境、集群配置管理)
為什么需要Apollo
統一管理不同環境、不同集群的配置 Apollo提供了統一界面集中式管理不同環境(environment)、不同集群(cluster)、不同命名空間(namespace)的配置。 同一份代碼部署在不同的集群,可以有不同的配置,比如zookeeper的地址等 通過命名空間(namespace)可以很方便地支持多個不同應用共享同一份配置,同時還允許應用對共享的配置進行覆蓋 配置修改實時生效(熱發布) 用戶在Apollo修改完配置並發布后,客戶端能實時(1秒)接收到最新的配置,並通知到應用程序 版本發布管理 所有的配置發布都有版本管理,從來很方便的支持配置回滾 灰度發布 支持配置的灰度發布,比如點了發布后,只對部分應用實例生效,等觀察一段時間沒問題后再推給所有應用實例 權限管理、發布審核、操作審計 應用和配置的管理都有完善的權限管理機制 所有操作都有審計日志 客戶端配置信息監控 可以在界面上方便地看到配置在被哪些實例使用 提供Java和.Net原生客戶端 提供開放平台API 部署簡單
2、Apollo配置中心設計
基礎模型
用戶在配置中心對配置進行修改並發布 配置中心通知Apollo客戶端有配置更新 Apollo客戶端從配置中心拉取最新的配置、更新本地配置並通知到應用
架構模塊
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進程中
為什么需要eureka
由於config-server和admin-server會部署多個,哪個前端client(比如JAVA)和portal怎么找到對應的config和admin呢?Apollo配置中心是基於Spring Cloud開發的,我們引入eureka作為服務注冊及服務發現,
Client和Portal通過eureka獲取到對應config-server和admin-server的地址,然后直接於config-server或admin-server聯系
各模塊概要介紹
Config Service 提供配置獲取接口 提供配置更新推送接口(基於Http long polling) 服務端使用Spring DeferredResult實現異步化,從而大大增加長連接數量 目前使用的tomcat embed默認配置是最多10000個連接(可以調整),使用了4C8G的虛擬機實測可以支撐10000個連接,所以滿足需求(一個應用實例只會發起一個長連接)。 接口服務對象為Apollo客戶端 Admin Service 提供配置管理接口 提供配置修改、發布等接口 接口服務對象為Portal Meta Server Portal通過域名訪問Meta Server獲取Admin Service服務列表(IP+Port) Client通過域名訪問Meta Server獲取Config Service服務列表(IP+Port) Meta Server從Eureka獲取Config Service和Admin Service的服務信息,相當於是一個Eureka Client 增設一個Meta Server的角色主要是為了封裝服務發現的細節,對Portal和Client而言,永遠通過一個Http接口獲取Admin Service和Config Service的服務信息,而不需要關心背后實際的服務注冊和發現組件 Meta Server只是一個邏輯角色,在部署時和Config Service是在一個JVM進程中的,所以IP、端口和Config Service一致 Eureka 基於Eureka和Spring Cloud Netflix提供服務注冊和發現 Config Service和Admin Service會向Eureka注冊服務,並保持心跳 為了簡單起見,目前Eureka在部署時和Config Service是在一個JVM進程中的(通過Spring Cloud Netflix) Portal 提供Web界面供用戶管理配置 通過Meta Server獲取Admin Service服務列表(IP+Port),通過IP+Port訪問服務 在Portal側做load balance、錯誤重試 Client Apollo提供的客戶端程序,為應用提供配置獲取、實時更新等功能 通過Meta Server獲取Config Service服務列表(IP+Port),通過IP+Port訪問服務 在Client側做load balance、錯誤重試
服務端設計
配置發布后的實時推送設計
上圖簡要描述了配置發布的大致過程: 用戶在Portal操作配置發布 Portal調用Admin Service的接口操作發布 Admin Service發布配置后,發送ReleaseMessage給各個Config Service Config Service收到ReleaseMessage后,通知對應的客戶端
實現方式如下: Admin Service在配置發布后會往ReleaseMessage表插入一條消息記錄,消息內容就是配置發布的AppId+Cluster+Namespace,參見DatabaseMessageSender Config Service有一個線程會每秒掃描一次ReleaseMessage表,看看是否有新的消息記錄,參見ReleaseMessageScanner Config Service如果發現有新的消息記錄,那么就會通知到所有的消息監聽器(ReleaseMessageListener),如NotificationControllerV2,消息監聽器的注冊過程參見ConfigServiceAutoConfiguration NotificationControllerV2得到配置發布的AppId+Cluster+Namespace后,會通知對應的客戶端
Config Service通知客戶端的實現方式
那NotificationControllerV2在得知有配置發布后是如何通知到客戶端的呢? 實現方式如下: 客戶端會發起一個Http請求到Config Service的notifications/v2接口,也就是NotificationControllerV2,參見RemoteConfigLongPollService NotificationControllerV2不會立即返回結果,而是通過Spring DeferredResult把請求掛起 如果在60秒內沒有該客戶端關心的配置發布,那么會返回Http狀態碼304給客戶端 如果有該客戶端關心的配置發布,NotificationControllerV2會調用DeferredResult的setResult方法,傳入有配置變化的namespace信息,同時該請求會立即返回。客戶端從返回的結果中獲取到配置變化的namespace后,會立即請求Config Service獲取該namespace的最新配置。
客戶端設計
上圖簡要描述了Apollo客戶端的實現原理: 客戶端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送。(通過Http Long Polling實現) 客戶端還會定時從Apollo配置中心服務端拉取應用的最新配置。 這是一個fallback機制,為了防止推送機制失效導致配置不更新 客戶端定時拉取會上報本地版本,所以一般情況下,對於定時拉取的操作,服務端都會返回304 - Not Modified 定時頻率默認為每5分鍾拉取一次,客戶端也可以通過在運行時指定System Property: apollo.refreshInterval來覆蓋,單位為分鍾 客戶端從Apollo配置中心服務端獲取到應用的最新配置后,會保存在內存中 客戶端會把從服務端獲取到的配置在本地文件系統緩存一份 在遇到服務不可用,或網絡不通的時候,依然能從本地恢復配置 應用程序可以從Apollo客戶端獲取最新的配置、訂閱配置更新通知
總結:
推拉結合
保持長連接,配置實時推送
定期拉配置(fallback)
配置緩存在內存
本地緩存一份
應用程序
通過apollo客戶端獲取最新配置
訂閱配置更新通知
3 客戶端獲取配置
Application.yml
#啟動加載apollo apollo: bootstrap: enabled: true namespaces: application,datasource #攔截feign調用 后去request請求參數開啟 hystrix: command: default: execution: isolation: strategy: SEMAPHORE logging: config: classpath:logback-spring.xml
src/main/resources/META-INF/app.properties
# test app.id=100003
配置中心地址
有幾種傳遞metaserver地址的方式
1)啟動參數: -Ddev_meta=http://192.168.31.20 2)apollo-core.jar中添加apollo-env.properties
3)classpath中單獨放一份:apollo-env.properties (src/main/resources/apollo-env.properties)
dev.meta=http://192.168.31.20 fat.meta=http://172.16.6.190:8081 uat.meta=http://172.16.6.190:8083 pro.meta=http://apollo.glp168.com
運行環境Env的方式
1)啟動參數: -Denv=ENV 2)配置文件(推薦) Linux: /opt/settings/server.properties Windows: C:\opt\settings\server.properties 支持:DEV/FAT/UAT/PRO/LOCAL
3)環境變量 ENV
一個環境中的一個app,在不同的集群中可以有不同的配置
1)啟動參數: -Dapollo.cluster=app_cluster_v1 2)通過配置文件 Linux /opt/settings/server.properties windows C:\opt\settings\server.properties
本地緩存
Linux: /opt/data/{appid}/config-cache windows: C:\opt\data\{appid}\config-cache 注意應用需要有讀寫權限 文件名: {appId}-{cluster}-{namespace}:properties
案例:
[root@bss-core-df948cbb8-vv98w config-cache]# pwd
/opt/data/100004/config-cache
[root@bss-core-df948cbb8-vv98w config-cache]# ll
total 8
-rw-r--r-- 1 root root 1021 8月 12 10:49 100004+default+application.properties
-rw-r--r-- 1 root root 469 8月 12 10:49 100004+default+datasource.properties
Maven Dependency
<dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-client</artifactId> <version>1.1.0</version> </dependency>
API使用方式
獲取默認namespace的配置(application)
Config config = ConfigService.getAppConfig(); //config instance is singleton for each namespace and is never null String someKey = "someKeyFromDefaultNamespace"; String someDefaultValue = "someDefaultValueForTheKey"; String value = config.getProperty(someKey, someDefaultValue);
通過上述的config.getProperty可以獲取到someKey對應的實時最新的配置值。
另外,配置值從內存中獲取,所以不需要應用自己做緩存
監聽配置變化事件
Config config = ConfigService.getAppConfig(); //config instance is singleton for each namespace and is never null config.addChangeListener(new ConfigChangeListener() { @Override public void onChange(ConfigChangeEvent changeEvent) { System.out.println("Changes for namespace " + changeEvent.getNamespace()); for (String key : changeEvent.changedKeys()) { ConfigChange change = changeEvent.getChange(key); System.out.println(String.format("Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType())); } } });
獲取公共Namespace的配置
String somePublicNamespace = "CAT"; Config config = ConfigService.getConfig(somePublicNamespace); //config instance is singleton for each namespace and is never null String someKey = "someKeyFromPublicNamespace"; String someDefaultValue = "someDefaultValueForTheKey"; String value = config.getProperty(someKey, someDefaultValue);
第二部分:部署案例
Portal部署一套,用於管理Dev,FAT,UAT,PRO環境
MetaServer/ConfigService/AdminService 需要每個環境獨立部署,每個環境獨立數據庫DB
EurekaServer和MetaServer和ConfigService住在同一個JVM進程,AdminService使用另一個JVM進程
AdminService和ConfigService使用同一個數據庫,PortalService使用不同的數據庫
ApolloConfigDB: Eureka服務URL列表,各環境不同 update apolloconfigbetadb.ServerConfig set ServerConfig.Value="http://config-p.aa.com/eureka" where ServerConfig.Key="eureka.service.url"; ApolloPortalDB: 調整支持多個環境 select * from ServerConfig; update Serverconfig set Value="dev,fat,uat,pro" where Id=1;
在Kubernetes平台運行ConfigService和AdminService、PortalService (版本以:1.5.1)
1)部署ConfigService
第一步:制作鏡像
https://github.com/ctripcorp/apollo/releases/download/v1.5.1/apollo-configservice-1.5.1-github.zip 軟件包
startup.sh腳本更改
[root@master01 scripts]# cat startup.sh #!/bin/bash SERVICE_NAME=apollo-configservice ## Adjust log dir if necessary LOG_DIR=/opt/logs/apollo-config-server ## Adjust server port if necessary SERVER_PORT=8080 APOLLO_CONFIG_SERVICE_NAME=$(hostname -i) SERVER_URL="http://${APOLLO_CONFIG_SERVICE_NAME}:${SERVER_PORT}" ## Adjust memory settings if necessary #export JAVA_OPTS="-Xms6144m -Xmx6144m -Xss256k -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=384m -XX:NewSize=4096m -XX:MaxNewSize=4096m -XX:SurvivorRatio=8" ## Only uncomment the following when you are using server jvm #export JAVA_OPTS="$JAVA_OPTS -server -XX:-ReduceInitialCardMarks" ########### The following is the same for configservice, adminservice, portal ########### export JAVA_OPTS="$JAVA_OPTS -XX:ParallelGCThreads=4 -XX:MaxTenuringThreshold=9 -XX:+DisableExplicitGC -XX:+ScavengeBeforeFullGC -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+ExplicitGCInvokesConcurrent -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -Duser.timezone=Asia/Shanghai -Dclient.encoding.override=UTF-8 -Dfile.encoding=UTF-8 -Djava.security.egd=file:/dev/./urandom" export JAVA_OPTS="$JAVA_OPTS -Dserver.port=$SERVER_PORT -Dlogging.file=$LOG_DIR/$SERVICE_NAME.log -XX:HeapDumpPath=$LOG_DIR/HeapDumpOnOutOfMemoryError/" # Find Java if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then javaexe="$JAVA_HOME/bin/java" elif type -p java > /dev/null 2>&1; then javaexe=$(type -p java) elif [[ -x "/usr/bin/java" ]]; then javaexe="/usr/bin/java" else echo "Unable to find Java" exit 1 fi if [[ "$javaexe" ]]; then version=$("$javaexe" -version 2>&1 | awk -F '"' '/version/ {print $2}') version=$(echo "$version" | awk -F. '{printf("%03d%03d",$1,$2);}') # now version is of format 009003 (9.3.x) if [ $version -ge 011000 ]; then JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$LOG_DIR/gc.log:time,level,tags -Xlog:safepoint -Xlog:gc+heap=trace" elif [ $version -ge 010000 ]; then JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$LOG_DIR/gc.log:time,level,tags -Xlog:safepoint -Xlog:gc+heap=trace" elif [ $version -ge 009000 ]; then JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$LOG_DIR/gc.log:time,level,tags -Xlog:safepoint -Xlog:gc+heap=trace" else JAVA_OPTS="$JAVA_OPTS -XX:+UseParNewGC" JAVA_OPTS="$JAVA_OPTS -Xloggc:$LOG_DIR/gc.log -XX:+PrintGCDetails" JAVA_OPTS="$JAVA_OPTS -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=60 -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled -XX:CMSFullGCsBeforeCompaction=9 -XX:+CMSClassUnloadingEnabled -XX:+PrintGCDateStamps -XX:+PrintGCApplicationConcurrentTime -XX:+PrintHeapAtGC -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=5M" fi fi printf "$(date) ==== Starting ==== \n" cd `dirname $0`/.. chmod 755 $SERVICE_NAME".jar" ./$SERVICE_NAME".jar" start rc=$?; if [[ $rc != 0 ]]; then echo "$(date) Failed to start $SERVICE_NAME.jar, return code: $rc" exit $rc; fi tail -f /dev/null
Dockerfile文件
cat >Dockerfile <<-EOF FROM stanleyws/jre8:8u112 ENV VERSION 1.5.1 RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\ echo "Asia/Shanghai" > /etc/timezone ADD apollo-configservice-${VERSION}.jar /apollo-configservice/apollo-configservice.jar ADD config/ /apollo-configservice/config ADD scripts/ /apollo-configservice/scripts CMD ["/apollo-configservice/scripts/startup.sh"] EOF
build-command.sh
[root@master01 dockerfile]# cat build-command.sh docker build -t registry.cn-shanghai.aliyuncs.com/xhaihua/apollo-configservice:v1.5.1 . sleep 2 docker push registry.cn-shanghai.aliyuncs.com/xhaihua/apollo-configservice:v1.5.1
導入configservice數據庫,執行sql
(由於每個環境需要單獨一個數據庫)
創建數據庫: Create database ApolloConfigTestDB;
授權: grant INSERT,DELETE,UPDATE,SELECT on ApolloConfigTestDB.* to "apolloconfig"@"172.19.167.%" identified by "123456";
https://raw.githubusercontent.com/ctripcorp/apollo/1.5.1/scripts/db/migration/configdb/V1.0.0__initialization.sql 數據庫 (注意把創建數據庫的語句刪除,修改Use)
然后更改eureka地址: update apolloconfigbetadb.ServerConfig set ServerConfig.Value="http://config-p.aa.com/eureka" where ServerConfig.Key="eureka.service.url";
第二步: 編寫K8S文件
#configmap --- apiVersion: v1 kind: ConfigMap metadata: name: apollo-configservice-cm namespace: test data: application-github.properties: | # DataSource spring.datasource.url = jdbc:mysql://172.19.166.17:3306/ApolloConfigTestDB?characterEncoding=utf8 spring.datasource.username = apolloconfig spring.datasource.password = 123456 eureka.service.url = http://config-p.aa.com/eureka app.properties: | appId=100003171 --- kind: Ingress apiVersion: extensions/v1beta1 metadata: name: apollo-configservice namespace: test spec: rules: - host: config-p.aa.com http: paths: - path: / backend: serviceName: apollo-configservice servicePort: 8080 --- kind: Service apiVersion: v1 metadata: name: apollo-configservice namespace: test spec: ports: - protocol: TCP port: 8080 targetPort: 8080 nodePort: 30108 selector: app: apollo-configservice type: NodePort sessionAffinity: None ---
kind: Deployment apiVersion: apps/v1 metadata: name: apollo-configservice namespace: test labels: name: apollo-configservice spec: replicas: 1 selector: matchLabels: name: apollo-configservice template: metadata: labels: app: apollo-configservice name: apollo-configservice spec: nodeSelector: role: test imagePullSecrets: - name: aliyun-docker-secret volumes: - name: configmap-volume configMap: name: apollo-configservice-cm containers: - name: apollo-configservice image: registry.cn-shanghai.aliyuncs.com/xhaihua/apollo-configservice:v1.5.1 ports: - containerPort: 8080 protocol: TCP volumeMounts: - name: configmap-volume mountPath: /apollo-configservice/config terminationMessagePath: /dev/termination-log terminationMessagePolicy: File imagePullPolicy: IfNotPresent resources: requests: cpu: 400m memory: 1024Mi limits: cpu: 800m memory: 2Gi restartPolicy: Always terminationGracePeriodSeconds: 30 securityContext: runAsUser: 0 schedulerName: default-scheduler strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 revisionHistoryLimit: 5 progressDeadlineSeconds: 600
第二步: 部署adminiservice
1)鏡像制作
https://github.com/ctripcorp/apollo/releases/download/v1.5.1/apollo-adminservice-1.5.1-github.zip
startup.sh腳本更改
[root@master01 dockerfile]# cat scripts/startup.sh #!/bin/bash SERVICE_NAME=apollo-adminservice ## Adjust log dir if necessary LOG_DIR=/opt/logs/apollo-adminservice ## Adjust server port if necessary SERVER_PORT=8080 APOLLO_ADMIN_SERVICE_NAME=$(hostname -i) # SERVER_URL="http://localhost:${SERVER_PORT}" SERVER_URL="http://${APOLLO_ADMIN_SERVICE_NAME}:${SERVER_PORT}" ## Adjust memory settings if necessary #export JAVA_OPTS="-Xms2560m -Xmx2560m -Xss256k -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=384m -XX:NewSize=1536m -XX:MaxNewSize=1536m -XX:SurvivorRatio=8" ## Only uncomment the following when you are using server jvm #export JAVA_OPTS="$JAVA_OPTS -server -XX:-ReduceInitialCardMarks" ########### The following is the same for configservice, adminservice, portal ########### export JAVA_OPTS="$JAVA_OPTS -XX:ParallelGCThreads=4 -XX:MaxTenuringThreshold=9 -XX:+DisableExplicitGC -XX:+ScavengeBeforeFullGC -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+ExplicitGCInvokesConcurrent -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -Duser.timezone=Asia/Shanghai -Dclient.encoding.override=UTF-8 -Dfile.encoding=UTF-8 -Djava.security.egd=file:/dev/./urandom" export JAVA_OPTS="$JAVA_OPTS -Dserver.port=$SERVER_PORT -Dlogging.file=$LOG_DIR/$SERVICE_NAME.log -XX:HeapDumpPath=$LOG_DIR/HeapDumpOnOutOfMemoryError/" # Find Java if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then javaexe="$JAVA_HOME/bin/java" elif type -p java > /dev/null 2>&1; then javaexe=$(type -p java) elif [[ -x "/usr/bin/java" ]]; then javaexe="/usr/bin/java" else echo "Unable to find Java" exit 1 fi if [[ "$javaexe" ]]; then version=$("$javaexe" -version 2>&1 | awk -F '"' '/version/ {print $2}') version=$(echo "$version" | awk -F. '{printf("%03d%03d",$1,$2);}') # now version is of format 009003 (9.3.x) if [ $version -ge 011000 ]; then JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$LOG_DIR/gc.log:time,level,tags -Xlog:safepoint -Xlog:gc+heap=trace" elif [ $version -ge 010000 ]; then JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$LOG_DIR/gc.log:time,level,tags -Xlog:safepoint -Xlog:gc+heap=trace" elif [ $version -ge 009000 ]; then JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$LOG_DIR/gc.log:time,level,tags -Xlog:safepoint -Xlog:gc+heap=trace" else JAVA_OPTS="$JAVA_OPTS -XX:+UseParNewGC" JAVA_OPTS="$JAVA_OPTS -Xloggc:$LOG_DIR/gc.log -XX:+PrintGCDetails" JAVA_OPTS="$JAVA_OPTS -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=60 -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled -XX:CMSFullGCsBeforeCompaction=9 -XX:+CMSClassUnloadingEnabled -XX:+PrintGCDateStamps -XX:+PrintGCApplicationConcurrentTime -XX:+PrintHeapAtGC -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=5M" fi fi printf "$(date) ==== Starting ==== \n" cd `dirname $0`/.. chmod 755 $SERVICE_NAME".jar" ./$SERVICE_NAME".jar" start rc=$?; if [[ $rc != 0 ]]; then echo "$(date) Failed to start $SERVICE_NAME.jar, return code: $rc" exit $rc; fi tail -f /dev/null
Dockerfile文件
[root@master01 dockerfile]# cat Dockerfile FROM stanleyws/jre8:8u112 ENV VERSION 1.5.1 RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\ echo "Asia/Shanghai" > /etc/timezone ADD apollo-adminservice-${VERSION}.jar /apollo-adminservice/apollo-adminservice.jar ADD config/ /apollo-adminservice/config ADD scripts/ /apollo-adminservice/scripts CMD ["/apollo-adminservice/scripts/startup.sh"]
Build-command.sh
[root@master01 dockerfile]# cat build-command.sh #!/bin/bash docker build -t registry.cn-shanghai.aliyuncs.com/xhaihua/apollo-adminservice:v1.5.1 . sleep 2 docker push registry.cn-shanghai.aliyuncs.com/xhaihua/apollo-adminservice:v1.5.1
2)編寫k8s文件
apiVersion: v1 kind: ConfigMap metadata: name: apollo-adminservice-cm namespace: test data: application-github.properties: | # DataSource spring.datasource.url = jdbc:mysql://172.19.166.17:3306/ApolloConfigTestDB?characterEncoding=utf8 spring.datasource.username = apolloconfig spring.datasource.password = 123456 eureka.service.url = http://config-p.aa.com/eureka app.properties: | appId=100003172 --- kind: Deployment apiVersion: apps/v1 metadata: name: apollo-adminservice namespace: test labels: name: apollo-adminservice spec: replicas: 1 selector: matchLabels: name: apollo-adminservice template: metadata: labels: app: apollo-adminservice name: apollo-adminservice spec: nodeSelector: role: test imagePullSecrets: - name: aliyun-docker-secret volumes: - name: configmap-volume configMap: name: apollo-adminservice-cm containers: - name: apollo-adminservice image: registry.cn-shanghai.aliyuncs.com/xhaihua/apollo-adminservice:v1.5.1 ports: - containerPort: 8080 protocol: TCP volumeMounts: - name: configmap-volume mountPath: /apollo-adminservice/config terminationMessagePath: /dev/termination-log terminationMessagePolicy: File imagePullPolicy: IfNotPresent resources: requests: cpu: 400m memory: 1024Mi limits: cpu: 800m memory: 2Gi imagePullSecrets: - name: harbor restartPolicy: Always terminationGracePeriodSeconds: 30 securityContext: runAsUser: 0 schedulerName: default-scheduler strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 revisionHistoryLimit: 7 progressDeadlineSeconds: 600
第三步: 部署portal服務
1)鏡像制作
https://github.com/ctripcorp/apollo/releases/download/v1.5.1/apollo-portal-1.5.1-github.zip
startup.sh啟動文件
#!/bin/bash SERVICE_NAME=apollo-portal ## Adjust log dir if necessary LOG_DIR=/opt/logs/apollo-portal-server ## Adjust server port if necessary SERVER_PORT=8080 APOLLO_PORTAL_SERVICE_NAME=$(hostname -i) # SERVER_URL="http://localhost:$SERVER_PORT" SERVER_URL="http://${APOLLO_PORTAL_SERVICE_NAME}:${SERVER_PORT}" ## Adjust memory settings if necessary #export JAVA_OPTS="-Xms2560m -Xmx2560m -Xss256k -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=384m -XX:NewSize=1536m -XX:MaxNewSize=1536m -XX:SurvivorRatio=8" ## Only uncomment the following when you are using server jvm #export JAVA_OPTS="$JAVA_OPTS -server -XX:-ReduceInitialCardMarks" ########### The following is the same for configservice, adminservice, portal ########### export JAVA_OPTS="$JAVA_OPTS -XX:ParallelGCThreads=4 -XX:MaxTenuringThreshold=9 -XX:+DisableExplicitGC -XX:+ScavengeBeforeFullGC -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+ExplicitGCInvokesConcurrent -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -Duser.timezone=Asia/Shanghai -Dclient.encoding.override=UTF-8 -Dfile.encoding=UTF-8 -Djava.security.egd=file:/dev/./urandom" export JAVA_OPTS="$JAVA_OPTS -Dserver.port=$SERVER_PORT -Dlogging.file=$LOG_DIR/$SERVICE_NAME.log -XX:HeapDumpPath=$LOG_DIR/HeapDumpOnOutOfMemoryError/" # Find Java if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then javaexe="$JAVA_HOME/bin/java" elif type -p java > /dev/null 2>&1; then javaexe=$(type -p java) elif [[ -x "/usr/bin/java" ]]; then javaexe="/usr/bin/java" else echo "Unable to find Java" exit 1 fi if [[ "$javaexe" ]]; then version=$("$javaexe" -version 2>&1 | awk -F '"' '/version/ {print $2}') version=$(echo "$version" | awk -F. '{printf("%03d%03d",$1,$2);}') # now version is of format 009003 (9.3.x) if [ $version -ge 011000 ]; then JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$LOG_DIR/gc.log:time,level,tags -Xlog:safepoint -Xlog:gc+heap=trace" elif [ $version -ge 010000 ]; then JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$LOG_DIR/gc.log:time,level,tags -Xlog:safepoint -Xlog:gc+heap=trace" elif [ $version -ge 009000 ]; then JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$LOG_DIR/gc.log:time,level,tags -Xlog:safepoint -Xlog:gc+heap=trace" else JAVA_OPTS="$JAVA_OPTS -XX:+UseParNewGC" JAVA_OPTS="$JAVA_OPTS -Xloggc:$LOG_DIR/gc.log -XX:+PrintGCDetails" JAVA_OPTS="$JAVA_OPTS -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=60 -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled -XX:CMSFullGCsBeforeCompaction=9 -XX:+CMSClassUnloadingEnabled -XX:+PrintGCDateStamps -XX:+PrintGCApplicationConcurrentTime -XX:+PrintHeapAtGC -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=5M" fi fi printf "$(date) ==== Starting ==== \n" cd `dirname $0`/.. chmod 755 $SERVICE_NAME".jar" ./$SERVICE_NAME".jar" start rc=$?; if [[ $rc != 0 ]]; then echo "$(date) Failed to start $SERVICE_NAME.jar, return code: $rc" exit $rc; fi tail -f /dev/null
Dockerfile文件
FROM stanleyws/jre8:8u112 ENV VERSION 1.5.1 RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\ echo "Asia/Shanghai" > /etc/timezone ADD apollo-portal-${VERSION}.jar /apollo-portal/apollo-portal.jar ADD config/ /apollo-portal/config ADD scripts/ /apollo-portal/scripts CMD ["/apollo-portal/scripts/startup.sh"]
build-command.sh 構建鏡像
#!/bin/bash docker build -t registry.cn-shanghai.aliyuncs.com/xhaihua/apollo-portal:v1.5.1 . sleep 2 docker push registry.cn-shanghai.aliyuncs.com/xhaihua/apollo-portal:v1.5.1
導入數據
https://raw.githubusercontent.com/ctripcorp/apollo/1.5.1/scripts/db/migration/portaldb/V1.0.0__initialization.sql 修改Serverconfig (默認有dev環境,根據你部署了幾套configserver和adminserver,如果部署可測試環境、預發布環境、生產環境,那么就要修改該Serverconfig表) select * from ServerConfig; update Serverconfig set Value="dev,fat,uat,pro" where Id=1;
授權:
grant INSERT,DELETE,UPDATE,SELECT on ApolloPortalDB.* to "apolloportal"@"172.19.167.%" identified by "123456";
2)編寫k8s文件
apiVersion: v1 kind: ConfigMap metadata: name: apollo-portal-cm namespace: devops data: application-github.properties: | # DataSource spring.datasource.url = jdbc:mysql://172.19.166.17:3306/ApolloPortalDB?characterEncoding=utf8 spring.datasource.username = apolloportal spring.datasource.password = 123456 app.properties: | appId=100003173 apollo-env.properties: | fat.meta=http://config-p.distrii.com uat.meta=http://config-b.distrii.com
kind: Service apiVersion: v1 metadata: name: apollo-portal namespace: devops spec: ports: - protocol: TCP port: 8080 targetPort: 8080 selector: app: apollo-portal type: ClusterIP sessionAffinity: None --- kind: Ingress apiVersion: extensions/v1beta1 metadata: name: apollo-portal namespace: devops spec: rules: - host: portal.aa.com http: paths: - path: / backend: serviceName: apollo-portal servicePort: 8080
kind: Deployment apiVersion: apps/v1 metadata: name: apollo-portal namespace: devops labels: name: apollo-portal spec: replicas: 1 selector: matchLabels: name: apollo-portal template: metadata: labels: app: apollo-portal name: apollo-portal spec: nodeSelector: role: devops imagePullSecrets: - name: aliyun-docker-secret volumes: - name: configmap-volume configMap: name: apollo-portal-cm containers: - name: apollo-portal image: registry.cn-shanghai.aliyuncs.com/xhaihua/apollo-portal:v1.5.1 ports: - containerPort: 8080 protocol: TCP volumeMounts: - name: configmap-volume mountPath: /apollo-portal/config terminationMessagePath: /dev/termination-log terminationMessagePolicy: File imagePullPolicy: Always resources: requests: cpu: 400m memory: 512Mi limits: cpu: 800m restartPolicy: Always terminationGracePeriodSeconds: 30 securityContext: runAsUser: 0 schedulerName: default-scheduler strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 revisionHistoryLimit: 7 progressDeadlineSeconds: 600
第三部分: Apollo Portal界面操作
1)修改管理員apollo密碼 (或者新建用戶)
2)新建應用
一個應用可以多個集群,默認有一個default集群
集群分配權限
點擊右上方的集群名稱,然后出現集群配置的tab,點擊 授權
添加名稱空間namespace (Namespace分配權限)
新增配置
發布配置
發布回滾