Apollo(阿波羅) 分布式配置中心


 

第一部分: 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  軟件包

https://raw.githubusercontent.com/ctripcorp/apollo/1.5.1/scripts/db/migration/configdb/V1.0.0__initialization.sql  數據庫

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

https://raw.githubusercontent.com/ctripcorp/apollo/1.5.1/scripts/db/migration/portaldb/V1.0.0__initialization.sql

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

https://raw.githubusercontent.com/ctripcorp/apollo/1.5.1/scripts/db/migration/portaldb/V1.0.0__initialization.sql

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分配權限)

 

 

 

新增配置

 

 發布配置

 

 發布回滾

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM