配置管理的预备知识
在《持续交付》第二章的小结中,作者写道:配置管理是本书其他内容的基础。没有配置管理,根本谈不上持续集成、发布管理以及部署流水线。
可以看出,配置的管理是多么的重要。在小结中,作者使用以下几个问题来判断你的配置管理做得是否好:
- 是否仅依靠保存于版本控制系统的数据(除了生产数据),就可以从无到有重建生产系统?
- 是否可以将应用程序回滚到以前某个正确的状态?
- 是否确保在测试、试运行和正式上线时以同样的方式创建部署环境?
如果你的配置管理流程比较好的话,所有的问题都应该是肯定的。
经过笔者长时间的一线配置管理的经验,要想更好实践《持续交付》中的配置管理,笔者认为配置管理中心应该能做到:
- 配置项的定义与配置的存储的解耦。比如应用可能会从Nacos中读取,也可以从环境变量里读取。
- 配置项之间的相互引用:消除重复配置项。比如MySQL的连接字符串,可能同时在应用a的配置和应用b的配置中出现。
- 配置项版本化:配置项变更跟踪;
- 配置项的定义与配置的格式的解耦。比如同一个配置项在应用a使用的是XML格式,但是应用b使用的是YAML格式。
- 配置项的自动化校验:当用户引入一个错误的配置项时,我们应该可以知道。
- 配置项的作用域功能。
- 配置项分组功能:比如按环境对配置项进行分组。
- 配置项加密功能。
目前没有一个配置中心能实现以上所有的功能。但是,基于Ansible再配合其它工具,我们可以很低成本地实现以上大部分功能,虽然有些粗糙。
基于Ansible和Nacos的实践
本文以Ansible结合Nacos为例介绍如何更好的进行配置管理。
在本案例中,Ansible担任了配置中心的角色,实现配置项的定义,描述配置项与配置格式(XML、YAML、Properties)之间关系,描述配置项与配置存储(Nacos)之间的关系。Nacos纯粹只是一个存储配置的角色。
Ansible介绍
Ansible通常被认为是一款自动化运维工具。用户通过YAML文件描述机器或者应用的最终状态,Ansible负责实现该状态。它通过Python技术栈的Jinja2模板引擎实现了我们所需要的绝大部分配置管理功能。所以,基于它结合Jenkins这类自动化平台即可实现一个低成本的灵活的配置管理中心。
说回来,配置中心的实现的核心其实是文本模板引擎。
Nacos介绍
Nacos是阿里巴巴开源的一个动态服务发现、配置管理和服务管理平台。不过,它的配置管理功能离上文谈到的配置中心还差太远。但是至少作为配置存储的实现,还是可以的。
说回来,由于我们的配置管理是基于Ansible的,所以,按道理,我们是可以很容易切换配置中心的。
整体架构
通过Ansible的配置功能,我能实现大部分配置中心应该实现的功能。但是,我们的应用程序不会直接读取Ansible工程中的配置,而是从Nacos中读,所以,我们通过一个Ansible任务自动化将配置发布到Nacos中。最后,再结合Jenkins实现流水线。
Ansible工程的准备
实际工作中,我们会为每个环境创建一个Git工程项目。这个Git工程项目是一个典型的Ansible工程结构,如下:
.
├── Jenkinsfile.groovy
├── README.md
├── group_vars
│ ├── all
│ │ ├── 01.all.yaml
│ │ ├── 02.nacos-config.yaml
│ │ └── app-config
│ │ ├── demo.json.yaml
│ │ ├── application.yaml.yaml
│ │ ├── demo.properties.yaml
├── host_vars
├── hosts
└── playbook-nacos.yaml
如果我们要对比不同环境的配置,只需要通过Beyond compare这类工具就可以实现。就这样可以低成本的实现了一个大的配置管理平台应该有的功能。
以下是各个文件的介绍:
group_vars目录是Ansible的约定目录,用于存放组级别的配置变量及全局配置变量。我们在group_vars目录下建立一个all/app-config子目录,子目录中存储各种类型配置,是为了人类能更好查找到自己的配置。all/app-config子目录下,每一个YAML文件中只定义一个全局配置。这里实现了配置格式与配置项定义的解耦。这是整个方案中,最难理解的地方。
group_vars/all目录下的文件示例:
demo.json.yaml
domo_json_nacos_config: |
{
"message": "hello json"
}
application.yaml.yaml
application_yaml_nacos_config: |
---
server:
port: 8080
demo.properties.yaml
demo_properties_nacos_config: |
spring.datasource.url={{ global_db.url }}
注意,global_db.url是全局级别的配置项,它的值可以被多个配置文件引用。
02.nacos_config.yaml
此文件用于定义Nacos中的配置文件与我们的Ansible之间的关系。nacos_config
配置项列表中的每一项都是一个Nacos更新配置的REST API的一次请求。只不过,我们通过YAML实现声明式了。
nacos_config:
- dataId: demo.json
group: demoGroup
type: json
content: "{{ demo_json_nacos_config | from_json | to_nice_json(indent=2) }}"
tenant: "{{ nacos_namespace }}"
- dataId: application.yaml.yaml
group: demoGroup
type: yaml
content: "{{ application_yaml_nacos_config}}"
tenant: "{{ nacos_namespace }}"
- dataId: demo.properties
group: demoGroup
type: properties
content: "{{ demo_properties_nacos_config }}"
tenant: "{{ nacos_namespace }}"
Nacos提供了更新配置的REST API如下:
curl -X POST 'http://127.0.0.1:8848/nacos/v1/cs/configs' \
-d 'dataId=nacos.example&group=com.alibaba.nacos&content=contentTest'
我们现在的问题就是如何将nacos_config.yaml中的配置变成请求Nacos REST API。有两个方案:
- 方案一:在Jenkins pipeline中,使用groovy语言读取nacos_config.yaml文件,然后拼装REST请求。
- 方案二:写一个Ansible任务,它会读取nacos_config.yaml的变量,然后通过Ansible的uri模块发起REST请求。
我们选择了方案二。因为方案二能做到与Jenkins的解耦。将来,我们切换到其它的自动化平台,难度不会太大。而且方案二能让我们脱离自动化平台,在本地手工执行。
以下就是我们的Ansible任务的写法:
---
- name: "nacos config deploy"
uri:
url: "http://{{nacos_server_addr}}/nacos/v1/cs/configs"
method: POST
body_format: "raw"
return_content: yes
body:
tenant: "{{item.tenant | urlencode}}"
group: "{{item.group|urlencode}}"
dataId: "{{item.dataId | urlencode}}"
type: "{{item.type | urlencode}}"
content: "{{item.content | urlencode}}"
status_code: 200
headers:
content_type: "Content-Type:application/x-www-form-urlencoded; charset=UTF-8"
with_items: "{{nacos_config}}"
when: nacos_config is defined
no_log: "{{ansible_global_no_log | default(True)}}"
这里有一个Nacos的坑,1.x的版本下,它只支持urlencode的方式。如果配置文件太大,会请求失败。简单看了下Nacos2.x,REST API似乎没什么变化。
结合Jenkins的流水线
当Ansible工程准备好之后,剩下的问题就是如何自动化执行Ansible。这是Jenkins的强项,就很容易做到了。以下是核心代码:
container('ansible') {
withCredentials([file(credentialsId: "ansible-vault", variable: 'vaultFile'),
sshUserPrivateKey(credentialsId: "ssh-keyfile", keyFileVariable: 'keyfile')
]) {
sh """
ansible-playbook -i ./hosts \
--vault-password-file ${env.vaultFile} \
--extra-vars 'ansible_ssh_private_key_file=${keyfile}' \
${WORKSPACE}/playbook.yaml
"""
}
} //end container
至此,只要提交配置仓库代码,就可以自动化发布配置到Nacos中。
后记
以上实践,在我们项目组已经运行1年多,目前运行良好。除了基础设施,我们已经做到了《持续交付》中的:仅依靠保存于版本控制系统的数据,就能重建系统;能快速的回滚到某个正确的版本;在多个环境中以同样的方式部署环境。
本文虽然介绍的是基于Nacos,但是其思想也适用于行业中的其它的“配置中心”。希望能对你有帮助。如果你有什么疑问,可以留言。