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

 

 

 

新增配置

 

 发布配置

 

 发布回滚

 

posted @ 2020-08-12 13:43  个人成长之路  阅读(1954)  评论(0编辑  收藏  举报