持续集成CI&CD之配置管理最佳实践

Posted 老梅mqm

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了持续集成CI&CD之配置管理最佳实践相关的知识,希望对你有一定的参考价值。

上一章:​​持续集成CI&CD之工具快速搭建​

下一章:​​持续集成CI&CD之CI的完整版最佳实践​

选用阿里的nacos配置管理进行最佳实践

Nacos Config 配置方案设计

业务配置的一些场景

在实际的业务场景中应用和共享配置间的关系可能如下图所示:

持续集成CI&CD之配置管理最佳实践_html


  • 从单个应用的角度来看: 应用可能会有多套(develop/beta/product)发布环境,多套发布环境之间有不同的基础配置,例如数据库。
  • 从多个应用的角度来看:多个应用间可能会有一些共享通用的配置,比如多个应用之间共用一套zookeeper集群。

极简配置方案设计

在实际开发、运维过程中,如果一个或多个应用使用了多个配置文件,特别是共享的配置文件,很容易出现误解,搞混配置【需要区别多个配置文件的优先级】;建议设计2层配置文件即可,手动配置的全局共享配置只能有一个,并且不支持动态刷新;每一个应用除了有一个全局配置文件后,还应该默认有一个专属的配置(默认可以不存在),支持动态刷新;如下图

持续集成CI&CD之配置管理最佳实践_spring_02


配置的优化级别应该是:本地配置 < 全局共享配置 < 专属配置 不同的环境配置,应该使用空间进行隔离

实现案例

nacos配置的优先级

Spring Cloud Alibaba Nacos Config 目前提供了三种配置能力从 Nacos 拉取相关的配置。

  • A: 通过​​spring.cloud.nacos.config.shared-configs[n].data-id​​ 支持多个共享 Data Id 的配置
  • B: 通过​​spring.cloud.nacos.config.extension-configs[n].data-id​​ 的方式支持多个扩展 Data Id 的配置
  • C: 通过内部相关规则(应用名、应用名+ Profile )自动生成相关的 Data Id 配置

当三种方式共同使用时,他们的一个优先级关系是:A < B < C

nacos配置的使用

以spring-cloud为例

基于nacos配置优先级,以及设计方案的最简原则,建议spring-cloud项目使用以下bootstrap.yml文件配置

server:
  port: $CONTAINER_PORT:8080 #所有类似$CONTAINER_PORT:8080写法的 CONTAINER_PORT不能改动,默认值根据业务修改
  servlet:
    context-path: /   #工程访问路径业务自己定义
# https://github.com/spring-projects
spring:
  main:
    allow-bean-definition-overriding: true # 允许定义bean的覆盖
  application:
    name: $APP_NAME:talkweb-demo #默认值一般修改为与工程名或子模块名相同
  cloud:
    ###nacos配置###
    nacos:
      server-addr: $NACOS_URL:192.168.141.203:8848
      username: $NACOS_USR:nacos-usr
      password: $NACOS_PWD:nacos-pwd
      discovery:
        namespace: $NACOS_NAMESPACE:namespace-dev
        group: $NACOS_GROUP:DEFAULT_GROUP
      config:
        namespace: $NACOS_NAMESPACE:namespace-dev
        file-extension: properties
        extension-configs:
          # 全局配置不支持动态刷新
          - data-id: $GLOBAL_COMM:global-comm.properties
            group: $NACOS_GROUP:DEFAULT_GROUP

​全局配置文件​​:global-comm.properties,(手动设置data-id,全局的公共配置)

​空间ID​​:namespace-dev(设置空间ID时,建议手动输入可读的ID,不要系统自动生成)

​应用专属配置文件​​:$spring.application.name.properties,(系统默认设置的data-id,主要使用场景是,”单个服务调优“或者“在公共配置中会影响其他服务的特别的配置“)

为保证代码完整性、可阅读性,所有的代码配置都必须在本地的配置中体现,并设置默认值[$环境:默认值]。所以建议nacos中的配置文件以key=vue 的形式的属性文件存在,用于覆盖本地的默认值。

扩展

有些管理系统的配置的一些开关,可以结合nacos的配置实现,如下增加​​系统全局配置​

spring:
  application:
    name: $APP_NAME:talkweb-demo #默认值一般修改为与工程名或子模块名相同
  cloud:
    ###nacos配置###
    nacos:
      server-addr: $NACOS_URL:192.168.141.203:8848
      username: $NACOS_USR:nacos-usr
      password: $NACOS_PWD:nacos-pwd
      discovery:
        namespace: $NACOS_NAMESPACE:namespace-dev
        group: $NACOS_GROUP:DEFAULT_GROUP
      config:
        namespace: $NACOS_NAMESPACE:namespace-dev
        file-extension: properties
        extension-configs:
          # 全局配置不支持动态刷新
          - data-id: $GLOBAL_COMM:global-comm.properties
            group: $NACOS_GROUP:DEFAULT_GROUP
          # 系统全局配置支持动态刷新[该配置由系统默认创建,不能人为操作]
          - data-id: $SYS_GLOBAL_CONFIG:sys-global-config.properties
            group: $NACOS_GROUP:DEFAULT_GROUP
            refresh: true

系统默认全局配置:sys-global-config.properties (手动设置data-id,支持动态刷新,业务系统自动生成,不能人为操作,主要用于管理后台配置的参数,eg:邮箱配置、短信配置、系统开关等等)

前端使用nacos配置

为了使一套代码能再不同的环境中,前端也需要使用配置中心,最好能与后端使用一套配置。 设计方案:前端工程(vue)在项目中使用公共的一个配置文件;部署时利用shell脚本,从nacos中获取配置后, 自动替换前端配置文件中的配置项。

以k8s部署为例

1.在docker编译时,将​​默认的配置文件​​​替换为​​带有占位符配置文件​​。(占位符,只是将具体值由nacos中key取代,eg: $key)

FROM nginx:stable
MAINTAINER Qiming Mei <meiqiming@talkweb.com.cn>

COPY dist/ /usr/share/nginx/html/
COPY init.js /usr/share/nginx/html/static/js/config.js
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
#CMD ["/bin/bash","-c", "/entrypoint.sh" ]
ENTRYPOINT ["/entrypoint.sh"]

2.利用启动entrypoint.sh配置文件中​​替换占位符​

#!/bin/bash
#配置文件路径,默认是static/js/config.js
__config_file="/usr/share/nginx/html/static/js/config.js"
if [ -n "$VUE_CONFIG_FILE_PATH" ]; then
  __config_file="/usr/share/nginx/html/$VUE_CONFIG_FILE_PATH"
fi;

if [  -f "/init-env/env.conf" ]; then
grep -v "^#" /init-env/env.conf |grep -v ^$  |while read LINE
do
  A=`echo $LINE |awk -F "=" print $1`
  B=`echo $LINE |awk -F "$A=" print $2`
  result=$(echo $A | grep "\\.")
  if [[ "$result" != "" ]]; then
    echo "#$A=\\"$B\\"" >>  /init-env/env1.conf
  elif [[ -z "$B" ]]; then
    echo "空行,不需转换舍弃";
  else
    B=$B//\\"/\\\\\\"; #处理特殊字符
    echo "$A=\\"$B/\\`/\\\\\\`\\"" >>  /init-env/env1.conf
  fi
done
sed -i s/\\r// /init-env/env1.conf
#sed -e s#\\&#\\\\&#g env.conf > env1.conf
eval "$(cat /init-env/env1.conf)"
rm -f /init-env/env1.conf
else
    echo "/init-env/env.conf文件不存在,注意需要引入外部环境变量哦!";
fi;

#备份配置文件,存在就不备份,不存在就备份
if [ ! -f  "$__config_file.env" ]; then
  cp $__config_file $__config_file.env
fi

# 变量替换方式直接会将$的变量替换掉
if [ -f "/init-env/env.conf" -o "$IS_ENV_REPLACE" = "true" ]; then
  #替换$中的环境变量
eval "cat <<EOF
$(<$__config_file.env)
EOF
" > $__config_file
  #删除环境变量文件
  rm -rf /init-env/env.conf
  #赋予可读权限
  chmod -R +r /usr/share/nginx/html
fi

nginx -g daemon off;

3.利用k8s的初始化容器下载nacos配置文件 ​​__XXX__​​为占位符,部署时会替换为具体的值

---
apiVersion: apps/v1  
kind: Deployment
metadata:
  name: __DOMAIN_NAME__
  namespace: __NAME_SPACE__
spec:
  selector:
    matchLabels:
      app: __DOMAIN_NAME__
  replicas: __REPLICAS_NUM__ 
  template: 
    metadata:
      labels:
        app: __DOMAIN_NAME__
    spec:
      initContainers:
      - name: init-env-sidecar
        image: busybox:latest
        command: [ "sh", "-c"]
        args:
        - set -ex;
          CONFIG_FILE=$CONFIG_FILE:-"vue-comm.properties";
          SYS_GLOBAL_CONFIG=$SYS_GLOBAL_CONFIG:-"sys-global-config.properties";
          wget --post-data="username=$NACOS_USR&password=$NACOS_PWD" -S "$NACOS_URL/nacos/v1/auth/users/login" -O login-token;
          access_token=$(grep  -Eo "accessToken":"([^"]*)" login-token |awk -F \\":\\" print $2);
          access_token=$access_token/\\"/;
          rm -f /init-env/env.conf;
          rm -f /init-env/env-sys.conf;
          wget "$NACOS_URL/nacos/v1/cs/configs?dataId=$CONFIG_FILE&group=$NACOS_GROUP&tenant=$NACOS_NAMESPACE&accessToken=$access_token" -O  /init-env/env.conf;
          wget "$NACOS_URL/nacos/v1/cs/configs?dataId=$SYS_GLOBAL_CONFIG&group=$NACOS_GROUP&tenant=$NACOS_NAMESPACE&accessToken=$access_token" -O  /init-env/env-sys.conf || return 0;
          if [  $? -eq 0 -a -f "/init-env/env-sys.conf" ]; then
            echo -e  "\\n" >>  /init-env/env.conf;
            cat /init-env/env-sys.conf >> /init-env/env.conf;
          fi
        env: #环境变量设置
        - name: NACOS_NAMESPACE
          value: __NACOS_NAMESPACE__
        - name: NACOS_GROUP
          value: __NACOS_GROUP__
        - name: SYS_GLOBAL_CONFIG
          value: __SYS_GLOBAL_CONFIG__
        - name: CONFIG_FILE
          value: __CONFIG_FILE__
        - name: NACOS_URL
          value: __NACOS_URL__
        envFrom:
        - secretRef:
            name: __NACOS_AUTH__
        volumeMounts:
        - name: init-env
          mountPath: /init-env/
      containers:
      - name: __DOMAIN_NAME__
        image: __DOCKER_IMAGE__
        imagePullPolicy: IfNotPresent #本地存在就不到远程拉取镜像
        env: #环境变量设置
        - name: TZ
          value: Asia/Shanghai
        - name: DOMAIN_NAME
          value: __DOMAIN_NAME__.__NAME_SPACE__
        resources: #资源限制
          requests:
            memory: "128Mi"
            cpu: "100m" #最低需要 0.1个cpu
          limits:
            memory: "__LIMIT_MEMORY__Mi"
            cpu: "1000m"
        ports:
        - containerPort: 80
        readinessProbe: #就绪探针
        #  httpGet:
        #    path: /index.html
        #    port: 80
          tcpSocket:
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 15
          timeoutSeconds: 5
        livenessProbe: #健康检查
        #  httpGet:
        #    path: /index.html
        #    port: 80
          tcpSocket:
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 15
          timeoutSeconds: 5
        volumeMounts:
        - name: time-config
          mountPath: /etc/localtime
          readOnly: true
        - name: init-env
          mountPath: /init-env/
      imagePullSecrets:
      - name: __DOCKER_REGISTRY_SECRET__
      nodeSelector:
        isDev: "true"
      volumes:
      - name: time-config
        hostPath:
          path: /etc/localtime
      - name: init-env
        emptyDir: 


以上是关于持续集成CI&CD之配置管理最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

持续集成CI/CD之CD的完整版最佳实践

新Jenkins实践-第1章 开篇-为什么要做CI/CD?

CI/CD与Docker

CICD - 持续集成与持续交付

CI与CD之Docker上安装Jenkins

CI与CD之Docker上安装Jenkins