1. 配置中心介绍

1.1. 配置管理方式

当前的微服务环境中,配置项都不是硬编码到代码中的,需要通过外部文件或者参数的方式来实现可动态调整的配置,目前主要有以下几种方式来管理:

  • 数据库:部分开发会将配置写在数据库中,定期读取和应用
  • 环境变量:这种是云原生环境中很常用的一种方式,通过在容器启动时对全局环境变量赋值的方式来完成
  • 命令行参数:通过Pod的args或者docker容器的CMD来实现启动参数调整
  • configmap:K8S集群中独有配置管理方案,参考:ConfigMap
  • 存储卷:将公共配置放在共享存储中,然后容器启动时挂载配置文件,不建议使用
  • 第三方配置中心:如 Apollo、XDiamond、Qconf等,这类配置中心操作根据方便

    1.2. Apollo

    Apollo(阿波罗)是携程框架部门研发的开源配置管理中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性。Apollo支持4个维度管理Key-Value格式的配置:Namespace(名称空间)、Cluster(集群)、Environment(环境)、Application(应用)。具体介绍参考:https://github.com/ctripcorp/apollo/wiki

    1.2.1. Apollo 特性

  • 统一管理不同环境(支持有限的环境关键词)、不同集群的配置

  • 配置修改实时生效
  • 版本发布管理
  • 灰度发布
  • 权限管理、发布审核、操作审计
  • 客户端配置信息监控
  • 提供Java和.Net原生客户端,且支持HTTP接口

    1.2.2. Apollo 基础模型

    用户发布配置到 Apollo 配置中心 —> 通知客户端配置发生变化 —> 客户端拉取配置
    basic-architecture.png

    1.2.3. 整体架构

  • 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进程中,因此必须要先部署 Config Service,否则其它服务交付会出错

basic-architecture.png
选择Eureka的原因:

  • 它提供了完整的Service Registry和Service Discovery实现
    • 首先是提供了完整的实现,并且也经受住了Netflix自己的生产环境考验,相对使用起来会比较省心。
  • 和Spring Cloud无缝集成
    • 我们的项目本身就使用了Spring Cloud和Spring Boot,同时Spring Cloud还有一套非常完善的开源代码来整合Eureka,所以使用起来非常方便。
    • 另外,Eureka还支持在我们应用自身的容器中启动,也就是说我们的应用启动完之后,既充当了Eureka的角色,同时也是服务的提供者。这样就极大的提高了服务的可用性。
    • 这一点是我们选择Eureka而不是zk、etcd等的主要原因,为了提高配置中心的可用性和降低部署复杂度,我们需要尽可能地减少外部依赖。
  • Open Source

    • 最后一点是开源,由于代码是开源的,所以非常便于我们了解它的实现原理和排查问题。

      1.2.4. 客户端实现方式

      client-architecture.png
  • 客户端和服务端保持了一个长连接,从而能第一时间获得配置更新的推送。

  • 客户端还会定时从Apollo配置中心服务端拉取应用的最新配置。
    • 这是一个fallback机制,为了防止推送机制失效导致配置不更新
    • 客户端定时拉取会上报本地版本,所以一般情况下,对于定时拉取的操作,服务端都会返回304 - Not Modified
    • 定时频率默认为每5分钟拉取一次,客户端也可以通过在运行时指定System Property: apollo.refreshInterval来覆盖,单位为分钟。
  • 客户端从Apollo配置中心服务端获取到应用的最新配置后,会保存在内存中
  • 客户端会把从服务端获取到的配置在本地文件系统缓存一份
    • 在遇到服务不可用,或网络不通的时候,依然能从本地恢复配置
  • 应用程序从Apollo客户端获取最新的配置、订阅配置更新通知

    1.2.5. 高可用场景

    | 场景 | 影响 | 降级 | 原因 | | —- | —- | —- | —- | | 某台config service下线 | 无影响 | | Config service无状态,客户端重连其它config service | | 所有config service下线 | 客户端无法读取最新配置,Portal无影响 | 客户端重启时,可以读取本地缓存配置文件 | | | 某台admin service下线 | 无影响 | | Admin service无状态,Portal重连其它admin service | | 所有admin service下线 | 客户端无影响,portal无法更新配置 | | | | 某台portal下线 | 无影响 | | Portal域名通过slb绑定多台服务器,重试后指向可用的服务器 | | 全部portal下线 | 客户端无影响,portal无法更新配置 | | | | 某个数据中心下线 | 无影响 | | 多数据中心部署,数据完全同步,Meta Server/Portal域名通过slb自动切换到其它存活的数据中心 |

1.3. 部署架构图

生产实践中,开发环境、测试环境、生产环境放到三个不同的K8S集群中,三个环境中使用三套不同的 admin service、config service和数据库,共用同一个Protal前端。从而实现了分环境的配置中心。本次实验环境资源有限,采用不同的K8S名称空间区分不同的环境。
image.png

image.png


2. 准备环境

2.1. 停止dubbo微服务集群

持续集成章节中的Provider、Consumer、Monitor全部停止掉。

2.2. 拆分zookeeper集群

为了模拟生产、测试、开发三种环境,将zookeeper集群拆分为独立的三个节点。

  1. # hdss7-11,hdss7-12,hdss7-21 操作一致
  2. [root@hdss7-11 ~]# /opt/apps/zookeeper/bin/zkServer.sh stop
  3. [root@hdss7-11 ~]# rm -fr /data/zookeeper/data/* /data/zookeeper/logs/* # 删除历史数据
  4. [root@hdss7-11 ~]# sed -i '/^server/d' /opt/apps/zookeeper/conf/zoo.cfg # 删除集群配置
  5. [root@hdss7-11 ~]# /opt/apps/zookeeper/bin/zkServer.sh start
  6. [root@hdss7-11 ~]# /opt/apps/zookeeper/bin/zkServer.sh status # 启动后,所有节点都为 standalone 模式
  7. ZooKeeper JMX enabled by default
  8. Using config: /opt/apps/zookeeper/bin/../conf/zoo.cfg
  9. Mode: standalone

2.3. 创建名称空间

[root@hdss7-22 ~]# kubectl create namespace pro
[root@hdss7-22 ~]# kubectl create namespace fat
[root@hdss7-22 ~]# kubectl create namespace dev
[root@hdss7-22 ~]# kubectl create secret docker-registry harbor --docker-username=admin --docker-password='Harbor12345' --docker-server=http://harbor.od.com -n pro
[root@hdss7-22 ~]# kubectl create secret docker-registry harbor --docker-username=admin --docker-password='Harbor12345' --docker-server=http://harbor.od.com -n fat
[root@hdss7-22 ~]# kubectl create secret docker-registry harbor --docker-username=admin --docker-password='Harbor12345' --docker-server=http://harbor.od.com -n dev

3. 部署Config Service

3.1. 初始化数据库

3.1.1. 刷SQL脚本

# 数据库服务器: hdss7-150.host.com  MySQL 5.7
[root@hdss7-150 src]# wget -O config.sql https://raw.githubusercontent.com/ctripcorp/apollo/1.5.1/scripts/db/migration/configdb/V1.0.0__initialization.sql
# SQL脚本中的调整: 
# 1. 不同环境分库;
# 2. 不同环境中 ApolloConfigDB.ServerConfig 表中Eureka地址调整
[root@hdss7-150 src]# sed -i 's#http://localhost:8080/eureka/#http://config.od.com/eureka/#' config.sql
[root@hdss7-150 src]# sed 's/ApolloConfigDB/ApolloConfigFatDB/;s#http://config.od.com/eureka/#http://config-fat.od.com/eureka/#' config.sql > config-fat.sql
[root@hdss7-150 src]# sed 's/ApolloConfigDB/ApolloConfigDevDB/;s#http://config.od.com/eureka/#http://config-dev.od.com/eureka/#' config.sql > config-dev.sql

[root@hdss7-150 src]# mysql -uroot -p < config.sql 
[root@hdss7-150 src]# mysql -uroot -p < config-fat.sql 
[root@hdss7-150 src]# mysql -uroot -p < config-dev.sql

# 授权,实践中不同环境的库可以使用不同的用户名和密码
# 因为Pod连接MySQL是做了SNAT,所以需要使用Node IP授权,如果需要显示Pod地址,可以添加路由表
mysql> grant SELECT,INSERT,UPDATE,DELETE on ApolloConfigDB.* to apolloconfig@'10.4.7.%' identified by 'Centos.1992!@#' ;
mysql> grant SELECT,INSERT,UPDATE,DELETE on ApolloConfigFatDB.* to apolloconfig@'10.4.7.%' identified by 'Centos.1992!@#' ;
mysql> grant SELECT,INSERT,UPDATE,DELETE on ApolloConfigDevDB.* to apolloconfig@'10.4.7.%' identified by 'Centos.1992!@#' ;
mysql> show grants for apolloconfig@'10.4.7.%';
+--------------------------------------------------------------------------------------------+
| Grants for apolloconfig@10.4.7.%                                                           |
+--------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'apolloconfig'@'10.4.7.%'                                            |
| GRANT SELECT, INSERT, UPDATE, DELETE ON `ApolloConfigDB`.* TO 'apolloconfig'@'10.4.7.%'    |
| GRANT SELECT, INSERT, UPDATE, DELETE ON `ApolloConfigFatDB`.* TO 'apolloconfig'@'10.4.7.%' |
| GRANT SELECT, INSERT, UPDATE, DELETE ON `ApolloConfigDevDB`.* TO 'apolloconfig'@'10.4.7.%' |
+--------------------------------------------------------------------------------------------+

3.1.2. 配置DNS解析

[root@hdss7-11 ~]# vim /var/named/od.com.zone 
......
config             A    10.4.7.10
config-fat         A    10.4.7.10
config-dev         A    10.4.7.10
mysql              A    10.4.7.150
[root@hdss7-11 ~]# systemctl restart named

3.2. 制作Config Service镜像

# 下载页面: https://github.com/ctripcorp/apollo/releases/tag/v1.5.1
[root@hdss7-200 src]# unzip apollo-configservice-1.5.1-github.zip -d docker_files/apollo-configservice
[root@hdss7-200 src]# cd docker_files/apollo-configservice
[root@hdss7-200 apollo-configservice]# ls config/ -l # 以下配置采用 configmap 挂载
total 8
-rw-r--r-- 1 root root 289 Nov  9 23:21 application-github.properties # DB信息
-rw-r--r-- 1 root root  30 Apr 20  2017 app.properties # appid,jdkVersion信息
#!/bin/bash
# 修改 scripts/startup.sh; 其中内存参数需要根据情况调整,此处增加了一个APOLLO_CONFIG_SERVICE_NAME,没有通过环境变量传递
# https://github.com/ctripcorp/apollo/tree/1.5.1/scripts/apollo-on-kubernetes/apollo-config-server/scripts
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:+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
[root@hdss7-200 apollo-configservice]# vim Dockerfile
# jre:8u112 制作地址: https://www.yuque.com/duduniao/ww8pmw/gp8n04#JJ6SE
# config 通过configmap来挂载, *sources.jar为源码,scritps/shutdown.sh 不需要
FROM harbor.od.com/public/jre:8u112
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\
    echo "Asia/Shanghai" > /etc/timezone && mkdir -p /apollo-configservice/scripts
ADD apollo-configservice-1.5.1.jar /apollo-configservice/apollo-configservice.jar
ADD scripts/ /apollo-configservice/scripts
CMD ["/apollo-configservice/scripts/startup.sh"]
[root@hdss7-200 apollo-configservice]# docker build . -t harbor.od.com/infra/apollo-configservice:v1.5.1
[root@hdss7-200 apollo-configservice]# docker image push harbor.od.com/infra/apollo-configservice:v1.5.1

3.3. 资源配置清单

3.3.1. configmap

apiVersion: v1
kind: ConfigMap
metadata:
  name: apollo-configservice-cm
  namespace: pro
data:
  application-github.properties: |
    # DataSource
    spring.datasource.url = jdbc:mysql://mysql.od.com:3306/ApolloConfigDB?characterEncoding=utf8
    spring.datasource.username = apolloconfig
    spring.datasource.password = Centos.1992!@#
    eureka.service.url = http://config.od.com/eureka
  app.properties: |
    appId=100003171

3.3.2. deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: apollo-configservice
  namespace: pro
  labels: 
    name: apollo-configservice
spec:
  replicas: 1
  selector:
    matchLabels: 
      name: apollo-configservice
  template:
    metadata:
      labels: 
        app: apollo-configservice 
        name: apollo-configservice
    spec:
      volumes:
      - name: configmap-volume
        configMap:
          name: apollo-configservice-cm
      containers:
      - name: apollo-configservice
        image: harbor.od.com/infra/apollo-configservice:v1.5.1
        ports:
        - containerPort: 8080
          protocol: TCP
        volumeMounts:
        - name: configmap-volume
          mountPath: /apollo-configservice/config
      imagePullSecrets:
      - name: harbor
  strategy:
    type: RollingUpdate
    rollingUpdate: 
      maxUnavailable: 1
      maxSurge: 1
  revisionHistoryLimit: 10

3.3.3. service

kind: Service
apiVersion: v1
metadata: 
  name: apollo-configservice
  namespace: pro
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  selector: 
    app: apollo-configservice

3.3.4. ingress

kind: Ingress
apiVersion: extensions/v1beta1
metadata: 
  name: apollo-configservice
  namespace: pro
spec:
  rules:
  - host: config.od.com
    http:
      paths:
      - path: /
        backend: 
          serviceName: apollo-configservice
          servicePort: 80

3.3.5. 其它环境中的配置清单

以上是生产环境pro中的清单,其它环境参考修改。修改点:

  • Namespace 属性修改
  • configmap中 eureka.service.url修改成对应环境地址、数据库名
  • ingress中域名的修改

    3.4. 应用配置清单

    Pro、Dev和Fat环境应用资源清单方式一致,检查 info location 与 config 页面是否正常即可。
    [root@hdss7-22 ~]# kubectl apply -f http://k8s-yaml.od.com/pro/configservice/configmap.yaml
    [root@hdss7-22 ~]# kubectl apply -f http://k8s-yaml.od.com/pro/configservice/deployment.yaml
    [root@hdss7-22 ~]# kubectl apply -f http://k8s-yaml.od.com/pro/configservice/service.yaml
    [root@hdss7-22 ~]# kubectl apply -f http://k8s-yaml.od.com/pro/configservice/ingress.yaml
    
[root@hdss7-22 ~]# curl -s 172.7.22.5:8080/info # 访问 configservice pod返回信息
{"git":{"commit":{"time":{"seconds":1573275854,"nanos":0},"id":"c9eae54"},"branch":"1.5.1"}}

image.png


4. 部署Admin service

4.1. 制作adminservice 镜像

# 下载页面: https://github.com/ctripcorp/apollo/releases/tag/v1.5.1
[root@hdss7-200 src]# mkdir -p docker_files/apollo-adminservice
[root@hdss7-200 src]# unzip -q apollo-adminservice-1.5.1-github.zip -d docker_files/apollo-adminservice
[root@hdss7-200 src]# cd docker_files/apollo-adminservice
[root@hdss7-200 apollo-adminservice]# vim scripts/startup.sh 
#!/bin/bash
# 此处修改了 SERVER_PORT APOLLO_ADMIN_SERVICE_NAME
# 参考地址:https://github.com/ctripcorp/apollo/tree/master/scripts/apollo-on-kubernetes/apollo-admin-server/scripts
SERVICE_NAME=apollo-adminservice
## Adjust log dir if necessary
LOG_DIR=/opt/logs/apollo-admin-server
## 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:+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
[root@hdss7-200 apollo-adminservice]# vim Dockerfile 
# jre:8u112 制作地址: https://www.yuque.com/duduniao/ww8pmw/gp8n04#JJ6SE
FROM harbor.od.com/public/jre:8u112
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\
    echo "Asia/Shanghai" > /etc/timezone && mkdir -p /apollo-adminservice/scripts

ADD apollo-adminservice-1.5.1.jar /apollo-adminservice/apollo-adminservice.jar
ADD scripts/startup.sh /apollo-adminservice/scripts
CMD ["/apollo-adminservice/scripts/startup.sh"]
[root@hdss7-200 apollo-adminservice]# docker image build . -t harbor.od.com/infra/apollo-adminservice:v1.5.1
[root@hdss7-200 apollo-adminservice]# docker image push harbor.od.com/infra/apollo-adminservice:v1.5.1

4.2. 资源配置清单

4.2.1. configmap

# appId 与 configservice、portal 不一致
apiVersion: v1
kind: ConfigMap
metadata:
  name: apollo-adminservice-cm
  namespace: pro
data:
  application-github.properties: |
    spring.datasource.url = jdbc:mysql://mysql.od.com:3306/ApolloConfigDB?characterEncoding=utf8
    spring.datasource.username = apolloconfig
    spring.datasource.password = Centos.1992!@#
    eureka.service.url = http://config.od.com/eureka
  app.properties: |
    appId=100003172

4.2.2. deployment

# admimservice 不对外提供统一接入口,其它模块通过 Eureka 查询到具体admin service地址
kind: Deployment
apiVersion: apps/v1
metadata:
  name: apollo-adminservice
  namespace: pro
  labels: 
    name: apollo-adminservice
spec:
  replicas: 1
  selector:
    matchLabels: 
      name: apollo-adminservice
  template:
    metadata:
      labels: 
        app: apollo-adminservice 
        name: apollo-adminservice
    spec:
      volumes:
      - name: configmap-volume
        configMap:
          name: apollo-adminservice-cm
      containers:
      - name: apollo-adminservice
        image: harbor.od.com/infra/apollo-adminservice:v1.5.1
        ports:
        - containerPort: 8080
          protocol: TCP
        volumeMounts:
        - name: configmap-volume
          mountPath: /apollo-adminservice/config
      imagePullSecrets:
      - name: harbor
  strategy:
    type: RollingUpdate
    rollingUpdate: 
      maxUnavailable: 1
      maxSurge: 1
  revisionHistoryLimit: 10
  progressDeadlineSeconds: 600

4.2.3. 其它环境中的配置清单

以上是生产环境pro中的清单,其它环境参考修改。修改点:

  • Namespace 属性修改
  • configmap中 eureka.service.url修改成对应环境地址

    4.3. 交付adminservice

    [root@hdss7-22 ~]# kubectl apply -f http://k8s-yaml.od.com/pro/adminservice/configmap.yaml
    [root@hdss7-22 ~]# kubectl apply -f http://k8s-yaml.od.com/pro/adminservice/deployment.yaml
    
[root@hdss7-22 ~]# kubectl get pod -n pro -o wide -l name=apollo-adminservice
NAME                                  READY   STATUS    RESTARTS   AGE    IP           NODE                NOMINATED NODE   READINESS GATES
apollo-adminservice-bffbf595c-hhcbq   1/1     Running   0          6m4s   172.7.22.5   hdss7-22.host.com   <none>           <none>
[root@hdss7-22 ~]# curl -s 172.7.22.5:8080/info
{"git":{"commit":{"time":{"seconds":1573275854,"nanos":0},"id":"c9eae54"},"branch":"1.5.1"}}

image.png
image.png


5. 部署Portal

5.1. 初始化数据库

[root@hdss7-150 src]# wget -O portal.sql https://raw.githubusercontent.com/ctripcorp/apollo/1.5.1/scripts/db/migration/portaldb/V1.0.0__initialization.sql

mysql> source /opt/src/portal.sql ;
grant SELECT,INSERT,UPDATE,DELETE on ApolloPortalDB.* to apolloportal@'10.4.7.%' identified by 'Centos.1992!@#' ;

5.2. 制作portal镜像

# 下载页面: https://github.com/ctripcorp/apollo/releases/tag/v1.5.1
[root@hdss7-200 src]# mkdir -p docker_files/apollo-portal
[root@hdss7-200 src]# unzip -q apollo-portal-1.5.1-github.zip -d docker_files/apollo-portal
[root@hdss7-200 src]# cd docker_files/apollo-portal
[root@hdss7-200 apollo-portal]# ls config/ -l
total 12
-rw-r--r-- 1 root root 234 Nov  9 23:21 apollo-env.properties
-rw-r--r-- 1 root root 218 Nov  9 23:21 application-github.properties
-rw-r--r-- 1 root root  30 Apr 20  2017 app.properties
[root@hdss7-200 apollo-portal]# cat config/apollo-env.properties # 不同环境配置
local.meta=http://localhost:8080               # 本地环境
dev.meta=http://fill-in-dev-meta-server:8080   # 开发环境:Development environment
fat.meta=http://fill-in-fat-meta-server:8080   # 测试环境:Feature Acceptance Test environment
uat.meta=http://fill-in-uat-meta-server:8080   # 预生产环境:User Acceptance Test environment
lpt.meta=${lpt_meta}
pro.meta=http://fill-in-pro-meta-server:8080   # 生产环境:Production environment
# 目前支持的变量有: LOCAL, DEV, FWS, FAT, UAT, LPT, PRO, TOOLS, UNKNOWN
# reference: https://github.com/ctripcorp/apollo/blob/master/apollo-core/src/main/java/com/ctrip/framework/apollo/core/enums/Env.java
[root@hdss7-200 apollo-portal]# vim scripts/startup.sh
#!/bin/bash
# 此处修改了 SERVER_PORT APOLLO_ADMIN_SERVICE_NAME
# 参考地址:https://github.com/ctripcorp/apollo/blob/1.5.1/scripts/apollo-on-kubernetes/apollo-portal-server/scripts/startup-kubernetes.sh
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:+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
[root@hdss7-200 apollo-portal]# vim Dockerfile 
# jre:8u112 制作地址: https://www.yuque.com/duduniao/ww8pmw/gp8n04#JJ6SE
# config 通过configmap来挂载, *sources.jar为源码,scritps/shutdown.sh 不需要
FROM harbor.od.com/public/jre:8u112
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\
    echo "Asia/Shanghai" > /etc/timezone && mkdir -p /apollo-portal/scripts
ADD apollo-portal-1.5.1.jar /apollo-portal/apollo-portal.jar
ADD scripts/startup.sh /apollo-portal/scripts
CMD ["/apollo-portal/scripts/startup.sh"]
[root@hdss7-200 apollo-portal]# docker image build . -t harbor.od.com/infra/apollo-portal:v1.5.1
[root@hdss7-200 apollo-portal]# docker image push harbor.od.com/infra/apollo-portal:v1.5.1

5.3. 资源配置清单

5.3.1. configmap

apiVersion: v1
kind: ConfigMap
metadata:
  name: apollo-portal-cm
  namespace: infra
data:
  application-github.properties: |
    spring.datasource.url = jdbc:mysql://mysql.od.com:3306/ApolloPortalDB?characterEncoding=utf8
    spring.datasource.username = apolloportal
    spring.datasource.password = Centos.1992!@#
  app.properties: |
    appId=100003173
  apollo-env.properties: |
    pro.meta=http://config.od.com
    fat.meta=http://config-fat.od.com
    dev.meta=http://config-dev.od.com

5.3.2. deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: apollo-portal
  namespace: infra
  labels: 
    name: apollo-portal
spec:
  # 当前的负载均衡方式(IPVS-nq) 不支持多台 portal,否则会出现会话异常
  replicas: 1
  selector:
    matchLabels: 
      name: apollo-portal
  template:
    metadata:
      labels: 
        app: apollo-portal 
        name: apollo-portal
    spec:
      volumes:
      - name: configmap-volume
        configMap:
          name: apollo-portal-cm
      containers:
      - name: apollo-portal
        image: harbor.od.com/infra/apollo-portal:v1.5.1
        ports:
        - containerPort: 8080
          protocol: TCP
        volumeMounts:
        - name: configmap-volume
          mountPath: /apollo-portal/config
      imagePullSecrets:
      - name: harbor
  strategy:
    type: RollingUpdate
    rollingUpdate: 
      maxUnavailable: 1
      maxSurge: 1
  revisionHistoryLimit: 10

5.3.3. service

apiVersion: v1
kind: Service
metadata: 
  name: apollo-portal
  namespace: infra
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  selector: 
    app: apollo-portal

5.3.4. ingress

apiVersion: extensions/v1beta1
kind: Ingress
metadata: 
  name: apollo-portal
  namespace: infra
spec:
  rules:
  - host: portal.od.com
    http:
      paths:
      - path: /
        backend: 
          serviceName: apollo-portal
          servicePort: 80

5.3.5. DNS配置

[root@hdss7-11 ~]# vim /var/named/od.com.zone
......
portal             A    10.4.7.10
[root@hdss7-11 ~]# systemctl restart named
[root@hdss7-11 ~]# dig -t A portal.od.com @10.4.7.11 +short
10.4.7.10

5.4. 交付Portal

[root@hdss7-21 ~]# kubectl apply -f http://k8s-yaml.od.com/devops/apollo/portal/configmap.yaml
[root@hdss7-21 ~]# kubectl apply -f http://k8s-yaml.od.com/devops/apollo/portal/deployment.yaml
[root@hdss7-21 ~]# kubectl apply -f http://k8s-yaml.od.com/devops/apollo/portal/service.yaml
[root@hdss7-21 ~]# kubectl apply -f http://k8s-yaml.od.com/devops/apollo/portal/ingress.yaml

image.png
image.png


6. Apollo Portal使用

6.1. 创建多环境

image.png
image.png
image.png

6.2. 创建部门

mysql> select id,`key`,value from ApolloPortalDB.ServerConfig; # key 是保留字,需要用`key`才能查询
+----+-------------------------------------+-------------------------------------------------------------------------------------------+
| id | key                                 | value                                                                                     |
+----+-------------------------------------+-------------------------------------------------------------------------------------------+
|  1 | apollo.portal.envs                  | pro,fat,dev                                                                               |
|  2 | organizations                       | [{"orgId":"TEST1","orgName":"样例部门1"},{"orgId":"TEST2","orgName":"样例部门2"}]         |
|  3 | superAdmin                          | apollo                                                                                    |
|  4 | api.readTimeout                     | 10000                                                                                     |
|  5 | consumer.token.salt                 | someSalt                                                                                  |
|  6 | admin.createPrivateNamespace.switch | true                                                                                      |
|  7 | configView.memberOnly.envs          | pro                                                                                       |
+----+-------------------------------------+-------------------------------------------------------------------------------------------+

通过【系统参数】入口,可修改系统参数,参数表为 ApolloPortalDB.ServerConfig,常修改部门信息。
image.png

6.3. 创建项目

6.3.1. 创建管理用户

image.png

6.3.2. Dubbo微服务提供者

image.png
image.png
image.png

6.3.3. Dubbo微服务消费者

image.png
image.png

6.4. 多环境中使用Apollo

6.4.1. 编译和打包镜像

image.png
image.png

6.4.2. 应用提供者到K8S集群

# 注意:C_OPTS 环境变量为configservice地址,不同环境中namespace不一致
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dubbo-demo-service
  namespace: pro
  labels: 
    name: dubbo-demo-service
    tier: provider
spec:
  replicas: 1
  selector:
    matchLabels: 
      name: dubbo-demo-service
  template:
    metadata:
      labels: 
        app: dubbo-demo-service
        name: dubbo-demo-service
    spec:
      containers:
      - name: dubbo-demo-service
        image: harbor.od.com/app/dubbo-demo-service:apollo_20200205_1342
        env:
        - name: JAR_BALL
          value: dubbo-server.jar
        - name: C_OPTS
          value: -Denv=pro -Dapollo.meta=http://config.od.com
      imagePullSecrets:
      - name: harbor
  strategy:
    type: RollingUpdate
    rollingUpdate: 
      maxUnavailable: 1
      maxSurge: 1
  revisionHistoryLimit: 10
  progressDeadlineSeconds: 600
[root@hdss7-22 ~]# kubectl apply -f http://k8s-yaml.od.com/pro/dubbo-demo-service/deployment.yaml
# 检查是否连接到apollo和配置是否正确,通过看日志来实现
[root@hdss7-22 ~]# kubectl get pod -n pro
NAME                                    READY   STATUS    RESTARTS   AGE
apollo-adminservice-bffbf595c-g88wz     1/1     Running   0          116m
apollo-configservice-5bf6df98df-2b458   1/1     Running   0          116m
dubbo-demo-service-7f6d79b894-fhgqb     1/1     Running   0          5m1s
[root@hdss7-22 ~]# kubectl logs dubbo-demo-service-7f6d79b894-fhgqb -n pro | grep -Eo 'config(-...)?\.od\.com|zk.\.od.com'|sort|uniq
config.od.com
zk1.od.com

image.png

6.4.3. 应用消费者到K8S集群

# 当前的deployment是在前一版本基础上改了镜像和增加环境变量,以及名称空间
# 前一版本参考 https://www.yuque.com/duduniao/ww8pmw/gp8n04#CYcLG
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dubbo-demo-consumer
  namespace: pro
  labels: 
    name: dubbo-demo-consumer
spec:
  replicas: 1
  selector:
    matchLabels: 
      name: dubbo-demo-consumer
  template:
    metadata:
      labels: 
        app: dubbo-demo-consumer
        name: dubbo-demo-consumer
    spec:
      containers:
      - name: dubbo-demo-consumer
        image: harbor.od.com/app/dubbo-demo-consumer:apollo_20200203_1346
        env:
        - name: JAR_BALL
          value: dubbo-client.jar
        - name: C_OPTS
          value: -Denv=pro -Dapollo.meta=http://config.od.com
      imagePullSecrets:
      - name: harbor
  strategy:
    type: RollingUpdate
    rollingUpdate: 
      maxUnavailable: 1
      maxSurge: 1
  revisionHistoryLimit: 10
  progressDeadlineSeconds: 600
# 当前的service是在前一版本基础上改了名称空间
# 前一版本参考 https://www.yuque.com/duduniao/ww8pmw/gp8n04#CYcLG
apiVersion: v1
kind: Service
metadata: 
  name: dubbo-demo-consumer
  namespace: pro
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  selector: 
    app: dubbo-demo-consumer
# 当前的ingress是在前一版本基础上改了名称空间
# 前一版本参考 https://www.yuque.com/duduniao/ww8pmw/gp8n04#CYcLG 
apiVersion: extensions/v1beta1
kind: Ingress
metadata: 
  name: dubbo-demo-consumer
  namespace: pro
spec:
  rules:
  - host: demo.od.com
    http:
      paths:
      - path: /
        backend: 
          serviceName: dubbo-demo-consumer
          servicePort: 80
# 三种环境中DNS配置
[root@hdss7-11 ~]# vim /var/named/od.com.zone 
......
config             A    10.4.7.10
config-fat         A    10.4.7.10
config-dev         A    10.4.7.10
[root@hdss7-11 ~]# systemctl restart named
[root@hdss7-22 ~]# kubectl apply -f http://k8s-yaml.od.com/pro/dubbo-demo-consumer/deployment.yaml
[root@hdss7-22 ~]# kubectl apply -f http://k8s-yaml.od.com/pro/dubbo-demo-consumer/service.yaml
[root@hdss7-22 ~]# kubectl apply -f http://k8s-yaml.od.com/pro/dubbo-demo-consumer/ingress.yaml
[root@hdss7-22 ~]# kubectl get pod -n pro -l name=dubbo-demo-consumer
NAME                                 READY   STATUS    RESTARTS   AGE
dubbo-demo-consumer-6cb7dfd8-h8ldl   1/1     Running   0          112s
[root@hdss7-22 ~]# kubectl logs dubbo-demo-consumer-6cb7dfd8-h8ldl -n pro | grep -oE 'config(-...)?\.od\.com|zk.\.od.com'|sort|uniq
config.od.com
zk1.od.com

image.png
image.png

6.4.4. 其它环境部署方式

不同的环境使用相同的镜像,区别在于名称空间不同、Pod环境变量C_OPTS的configservice域名不同


image.png