[toc]
oss-configserver
概述
oss-configserver
是在 Spring Cloud Config
基础上进行定制开发, 是集成了权限控制, 配置修改主动通知等功能, 一个简单完整, 运维友好的配置中心解决方案. 此方案主要以spring cloud
项目为主, 同时兼容非spring cloud
情形, 详情请参考 Spring Cloud Config 文档.
此项目意在通过一个配置中心集群, 在几乎不需要运维介入的情况下, 管理多个项目.
功能特点
- 权限控制 集成了
spring security
, 以项目为粒度进行安全校验, 保证配置安全. - 运维友好 此架构几乎不需要运维. 用户名为项目名, 密码为开发自主配置加密后的密码. 任何修改开发自助完成. 仅在需要解密时, 可以登录管理员账户进行解密.
- 主动通知 在配置变更后, 可以通过
Spring cloud bus
主动通知客户端, 触发配置更新动作. 当前支持gitlab
和gogs
. 根据项目名称, 可以实现单一通知
,前缀通知
和全部通知
三种通知模式. - 开箱即用 简单配置以后, 通过
mvn
命令, 直接打包成docker
镜像, 即可在docker
环境下部署.
快速使用
主要以下步骤:
- 启动
eureka
服务发现 - 启动
Spring cloud bus
要用到的rabbit MQ
消息总线服务 - 启动
gitlab
git 仓库服务 - 在
gitlab
创建配置项目 - 配置并启动
home1-oss configserver
服务端 - 启动
config server
客户端, 并测试
下面我们就一步一步来完成.
此使用手册主要使用
home1-oss
里面相应项目来示例. 大家也可以使用其它替代品. 如果已经有服务, 可以略过对应启动过程.
请自行先安装好
docker
和docker compose
, 并申请和配置docker
镜像加速(参考阿里docker镜像:https://cr.console.aliyun.com/#/accelerator).
启动 eureka
服务
本文使用 home1-oss
下面的 oss-eureka
. 启动步骤:
- 将
oss-eureka
clone 到本地某个目录 :git clone https://github.com/home1-oss/oss-eureka.git /path/to/store
- cd 到对应目录里面
- 执行
docker-compose up
命令来启动eureka
服务
启动 rabit mq
服务
本文使用 home1-oss
下面的 docker-cloudbus
. 启动步骤:
同 oss-eureka
启动过程, docker-cloudbus
地址为: https://github.com/home1-oss/docker-cloudbus.git
启动 gitlab
git 仓库服务
本文使用 home1-oss
下面的 docker-gitlab
. 启动步骤:
同 oss-eureka
启动过程, 有两个文件夹, 进入gitlab
文件夹, docker-compose up 命令启动即可. docker-cloudbus
地址为: https://github.com/home1-oss/docker-gitlab.git
涉及到更新,
gitlab
这个过程会相对较慢, 如果已经有gitlab
, 可以用现成的.
在 gitlab
创建配置项目
- 打开 gitlab
http://localhost:10080/
, 使用user
/user_pass
登录. home1-oss
group 应该已经创建好.- 在
home1-oss
group下, 创建my-config-test-config
项目. - 在
my-config-test-config
项目下, 创建application.yml
文件, 并添加以下内容:
spring:
application.name: 'my-config-test'
cloud.config.password: 'my-config-test' # !!! 此处最好存放加密后内容, 其他人尽管看到, 也不知道密码. 详见本文档 加密 相关内容
message: hello, home1-oss configserver!
spring.rabbitmq: # mq 相关配置, 后面会进一步描述
host: cloudbus.local
port: 5672
username: user
password: user_pass
- 在项目中进入
settings
-Repository
菜单,在deploy key
里面, 会有一个名字为[email protected]_xxx
的deployKey, 点击enable
将其激活(每个项目需要单独激活). 这样 configerserver 就有权限访问到项目数据了. 如果使用已有 gitlab, 那么默认不会有这个[email protected]_xxx
deploy key. 手工添加 deploy Key 见后面.
配置项目必须以
-config
结尾, 这是oss-configserver
的强制要求.
在 gitlab 中添加 deploy key
- 获取 configserver deploy key, 执行以下命令
curl http://localhost:8888/config/deployPublicKey
- 在 gitlab 中以管理员身份点击
admin area
那个小扳手的图标. 进入管理员界面. - 点开齿轮状设置菜单, 选择
Deploy keys
子菜单. - 点击
New Deploy Key
将刚才第一步获得的 deployKey 添加上即可. 最后去对应项目中, 将 deploy key 设置为可用状态.
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDJexpGshox4d2mRhYIjOjxlAmcF9k9fKzlr2ylKS32LwMrVeKY+XyV06YvX0FE0uwj3DSp2Vai2e8kEylRDhQmuV1ZjjA08P9/j9SacFuzY8TfncdUwsQ3wxmBjmlpQoODUad7v0ld0r1AfttqbfGJr8L5gPzxvoA96K+6PkYyzUwbStJiW0ruNEVOb5LgN/v90LWMorwXj2Y/fu+i5OWp+iCTrQ6ltC6xQ/f3MyRMbfUxW3cXNp9UkdVkFDJ4Le/5poim5yPi6d2vjG8z7h5hM7M+H7q72hVoH9Rx0yzp55jOSRMXDGU138pK6HQFU/mCw9yaT0OwGK5IdvaX+ryd [email protected]
配置并启动 home1-oss configserver
服务端
- 将项目克隆到本地:
https://github.com/home1-oss/oss-configserver.git
- 将以下加入到
hosts
文件中.
127.0.0.1 cloudbus.local
127.0.0.1 oss-eureka.local
127.0.0.1 gitlab.local
- (如果完全按照本文档默认步骤操作, 则这几个选项不用修改). 修改
src/main/resources/application.yml
的eureka.instance
,spring.rabbitmq
和spring.cloud.config.server.git.uri
对应节点. - 启动
home1-oss configserver
. cd 到对应目录里面, 执行:mvn spring-boot:run
启动 config server
客户端, 并测试
- 创建客户端 maven 项目, 在
pom
里面 parent 和 dependencies 配置如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.woo5.test</groupId>
<artifactId>config-server-client-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<start-class>cn.woo5.test.config_server_client_test.ConfigServerClientApp</start-class>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 创建主类, 添加 controller 功能.
package cn.woo5.test.config_server_client_test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RefreshScope
@RestController
@SpringBootApplication
public class ConfigServerClientApp {
@Value("${message}")
private String message;
@RequestMapping("/message")
String getMessage() {
return this.message;
}
public static void main(String[] args) {
SpringApplication.run(ConfigServerClientApp.class, args);
}
}
- 创建客户端启动配置文件 (src/main/resources/bootstrap.yml)
spring.application.name: my-config-test # 我们在 gitlab 上创建的项目为 my-config-test-config , 这里取 "-config" 前面的部分
spring.cloud.config.uri: http://localhost:8888/config # /config 是 oss-configserver 统一路径前缀
spring.cloud.config.username: my-config-test # username 必须和applicationName一致
spring.cloud.config.password: my-config-test
spring.cloud.config.label: master
spring.cloud.config.profile: development.env
management.security.enabled: false
- 启动客户端
mvn spring-boot:run
- 访问并获得结果 访问
http://localhost:8080/message
结果将会是:
hello, home1-oss configserver!
至此, 完成 config server 基本使用.
如果 configer server 服务端报错:
Caused by: com.jcraft.jsch.JSchException: timeout: socket is not established
. 这应该是本机 ssh 链接 gitlab 过慢导致. 可以在 application.yml 中配置spring.cloud.config.server.jgit.timeout
把时间设置长一些. 单位秒.
直接访问
http://my-config-test:my-config-test@localhost:8888/config/my-config-test/development.env
可以得到 gitlab 上面的配置信息. 在访问时, 最好一直带着用户名密码, 因 config-server 在集群环境下,session
授权不共享.
配置加密解密
在 gitlab 中创建的配置项目是他人可见的, 我们可以将密码等敏感字段加密.
加密
我们将密码以密文形式来存放.
获取密文 命令行输入:
curl http://localhost:8888/config/encrypt -X POST -d 'my-config-test'
我们会获得以下返回字串:
AQB4hVdKfdq5m3/OUAot6cHsm0aBFnZ84MKBYoxplYKmyprJ0wmAHhjrYsytm1ItDR3Gtem6FLeqhkipRKPg2J+2dkmvcSWNi2qWz9dZ/fdPDnAdtI8g+mVwbrBn0y1wrwQyGMFlrW93biZJlNInSDtBJSX0FshPcv/p4E/p9RCw8IbuizI7d8O+Tr4CP2w21EUiQPDUQRB8BY0k3vqCULzOLvRTqnibgCcPsTk8+pZdYYNtCjuSbxcfrcogq2c1rrTKwbfWF4FjluKLTLfiobYNIkhASmagKq71LxpumJ5PwHR5FC1sEmv/mZsnNy09h36JYwF5zGbjog+Yu8wbVjosCsQWWg1gOliV8kkJ0BujELG956EtrTV+bm7m7AYzThA=
把密文前面加上{cipher}
字串以后, 加上单引号, gitlab 内容更新为:
spring:
application.name: 'my-config-test'
cloud.config.password: '{cipher}AQB4hVdKfdq5m3/OUAot6cHsm0aBFnZ84MKBYoxplYKmyprJ0wmAHhjrYsytm1ItDR3Gtem6FLeqhkipRKPg2J+2dkmvcSWNi2qWz9dZ/fdPDnAdtI8g+mVwbrBn0y1wrwQyGMFlrW93biZJlNInSDtBJSX0FshPcv/p4E/p9RCw8IbuizI7d8O+Tr4CP2w21EUiQPDUQRB8BY0k3vqCULzOLvRTqnibgCcPsTk8+pZdYYNtCjuSbxcfrcogq2c1rrTKwbfWF4FjluKLTLfiobYNIkhASmagKq71LxpumJ5PwHR5FC1sEmv/mZsnNy09h36JYwF5zGbjog+Yu8wbVjosCsQWWg1gOliV8kkJ0BujELG956EtrTV+bm7m7AYzThA='
message: hello, home1-oss configserver!
spring.rabbitmq: # mq 相关配置, 后面会进一步描述
host: cloudbus.local
port: 5672
username: user
password: user_pass
直接访问 http://my-config-test:my-config-test@localhost:8888/config/my-config-test/development.env 可以得到 gitlab 上面的配置信息. 密码已经被解密.
这样, 我们重启客户端, 同样可以用配置的 'my-config-test' 密码来访问项目.
注: 加密内容在
yml
文件中要加单引号, 在properties
文件中不能加单引号, 要不然内容不会被解密.
解密
解密需要管理员权限. 默认管理员用户名为
admin
密码速记生成并打印在日志中. 可以在application.yml
中设置默认密码.
使用 curl http://ADMIN_NAME:ADMIN_PASS@localhost:8888/config/decrypt -X POST -d 'data to decrypt'
解密. 把刚才加密的内容解密, 示例如下:
wanghaodembp:~ wanghao$ curl http://admin:fb8ff2bf-eb79-4c96-a88f-3ca6ec702ec6@localhost:8888/config/decrypt -X POST -d 'AQB4hVdKfdq5m3/OUAot6cHsm0aBFnZ84MKBYoxplYKmyprJ0wmAHhjrYsytm1ItDR3Gtem6FLeqhkipRKPg2J+2dkmvcSWNi2qWz9dZ/fdPDnAdtI8g+mVwbrBn0y1wrwQyGMFlrW93biZJlNInSDtBJSX0FshPcv/p4E/p9RCw8IbuizI7d8O+Tr4CP2w21EUiQPDUQRB8BY0k3vqCULzOLvRTqnibgCcPsTk8+pZdYYNtCjuSbxcfrcogq2c1rrTKwbfWF4FjluKLTLfiobYNIkhASmagKq71LxpumJ5PwHR5FC1sEmv/mZsnNy09h36JYwF5zGbjog+Yu8wbVjosCsQWWg1gOliV8kkJ0BujELG956EtrTV+bm7m7AYzThA='
my-config-testwanghaodembp:~ wanghao$
注意: 上面这次示例中, fb8ff2bf-eb79-4c96-a88f-3ca6ec702ec6 是我本次运行时生成的随机密码.
配置通用配置(父配置)
我们在配置不同项目的时候, 往往很多配置是可以提取出来共用的. oss-configserver
支持此功能.
父配置中一个Key对应的内容会被子配置中相同Key对应内容覆盖.
oss-configserver
支持任意层级的继承. 但是注意,
- 限制继承层级以保持效率.
- 避免循环继承.
- 继承关系项目最好要有统一前缀, 方便变更父项目, 子项目得到通知. 详见本文档: 主动通知 部分.
如果父配置设置了密码, 则子配置要继承需要提供密码才能访问(父文件中密码设置 key: spring.cloud.config.password. 子文件配置父密码Key: spring.cloud.config.parent-config.password).
通用配置示例
我们现在为my-config-test-config
配置项目增加一个父配置项目.
- 在 gitlab 中创建
my-config-test-common-config
项目, 新建application.yml
文件. 文件中添加:
message: this is father mesage
message2: this is the message2 of father
- 修改
my-config-test-config
的 application.yml, 增加两项:
spring.cloud.config.parent-config.enabled: true
spring.cloud.config.parent-config.application: my-config-test-common
最终 my-config-test-config
的 application.yml 变为:
spring:
application.name: 'my-config-test'
cloud.config:
password: '{cipher}AQB4hVdKfdq5m3/OUAot6cHsm0aBFnZ84MKBYoxplYKmyprJ0wmAHhjrYsytm1ItDR3Gtem6FLeqhkipRKPg2J+2dkmvcSWNi2qWz9dZ/fdPDnAdtI8g+mVwbrBn0y1wrwQyGMFlrW93biZJlNInSDtBJSX0FshPcv/p4E/p9RCw8IbuizI7d8O+Tr4CP2w21EUiQPDUQRB8BY0k3vqCULzOLvRTqnibgCcPsTk8+pZdYYNtCjuSbxcfrcogq2c1rrTKwbfWF4FjluKLTLfiobYNIkhASmagKq71LxpumJ5PwHR5FC1sEmv/mZsnNy09h36JYwF5zGbjog+Yu8wbVjosCsQWWg1gOliV8kkJ0BujELG956EtrTV+bm7m7AYzThA='
parent-config:
enabled: true
application: my-config-test-common
message: hello, home1-oss configserver!
- 现在访问 http://my-config-test:my-config-test@localhost:8888/config/my-config-test/development.env 时, 会发现已经多了一条"message2: this is the message2 of father", 而父项目message也在, 不过在装配成 yml 或 properties 时会被子 message 覆盖.
- 此时, 向客户端发送刷新配置请求
curl -X POST http://localhost:8080/refresh
客户端将会获得最新配置信息. 原因是我们再Controller上面加了@RefreshScope
注解, 并在pom里面引入了spring-boot-starter-actuator
. 这能使客户端在不重启的情况下, 实现配置动态刷新.
通用配置测试环境和线上环境分离
有一些通用的配置在测试环境和线上环境是不一样(例如公共通信秘钥等信息), 而且不想让测试环境看到. oss-config-server 支持通过添加 oss-config-server 启动参数, 在线上环境访问线上 git Repo.
使用步骤
- 线上启动 oss-config-server 时添加启动参数
- 在客户端配置 parent 信息时名字中添加参数, 这样访问测试环境 oss-config-server 获取 parent 信息时访问的是不带参数的 Repo 仓库, 访问线上环境 oss-config-server 获取 parent 信息时访问的是带有参数的 Repo 仓库.
配置示例
- 线上 oss-config-server 启动时添加如下参数(参数名字可以自定义, 这里以prodArg为例. 测试环境不要加此参数):
-DprodArg=production-
- 客户端配置:
...
...
spring.cloud.config.parent-config.enabled: true
spring.cloud.config.parent-config.application: my-config-test-{prodArg}common
...
这样在测试环境会访问 my-config-test-common 这个父Repo, 而在线上环境将会访问 my-config-test-production-common 这个Repo.
主动通知
Spring cloud config
默认通过 Spring cloud bus
实现了修改配置以后的主动通知. 工作步骤如下:
- 修改配置项目中的配置文件, 并
push
到 gitlab - gitlab 配置项目中的 webhook 监听到
push
发生以后, 会POST
一个请求到 configserver 服务端. - configserver 服务端接收到请求以后, 会通过
Spring cloud bus
(MQ) 向队列中发送一个消息. 所有监听这个队列的客户端都会收到这个消息, 并主动去拉取最新配置并刷新. 实现配置推送到 git 仓库以后, 所有客户端都可以主动更新.
我们实现了两种推送规则:
gitlab
上配置项目名字以home1-oss-common
开头, 并且-config
结尾内容发生变更, 将消息通知到 config-server 后, configer server 会通过 cloud bus 推送所有, 要求所有项目服务主动更新配置.- 以
-config
结尾的项目, 会推送给相同前缀项目相关服务, 前缀是指: 第一个连接线-
前面的字符. 例如:xxx-config
, 会推送给 applicationName 以xxx
开头的所有项目相关服务. 所以在取项目名字时要注意这一点.
现在支持 gitlab & gogs 两种 git 仓库的 web hook.
主动通知配置示例
因客户端已经加了spring-cloud-starter-bus-amqp
依赖, 服务端也已经加了spring-cloud-starter-stream-rabbit
依赖. 并且已经配置好. 所以客户端和服务端之间的 Spring cloud bus
连接已经建好. 我们下面只需要配置 gitlab
的 webhook, 让发生变动时, gitlab 主动通知 config server即可.
- 配置 gitlab 的 webhook
在 gitlab 项目中 setting
- integration
菜单下 URL 中输入 http://xxx.xxx.xxx.xxx:8888/config/monitor
并点击 添加按钮. (xxx.xxx.xxx.xxx 是机器IP, 不能用localhost)
注意:
- 如果用的是 docker 中的 gitlab, 在添加 webhook 时, 一定要用你电脑的IP, 因为 localhost 代表的是 gitlab 所在 docker 内部.
- 节点是
/config/monitor
, 因为我们在所有访问 config server 相关请求前面都加了 /config 这一层.
- 添加完 webhook 以后, 可以点击
Test
, 看下是否报错(电脑慢的时候, gitlab 会给一个500, 这个看下日志正常即可). - 把
my-config-test-config
clone 到本地, 修改其中 message, 提交并 push 到 gitlab. 稍等片刻, 刷新http://localhost:8080/message
, 发现结果已经主动变成修改以后的了.
spring cloud bus 客户端 Camden.SR5 版本有个 bug, 会在 MQ 中创建
SpringCloudBusInput
和SpringcloudBusOutput
两个队列, 会把消息推到 output 里面, 而只监听 input, 导致无法主动刷新. 尽量不要使用.
参考链接: https://github.com/spring-cloud/spring-cloud-bus/issues/55
其他
euraka 相关
前面我们都直接通过 IP 调用. 其实完全可以通过服务发现方式. 这里就不再赘述.
docker 相关
docker 脚本都已经准备好. 通过 docker-compose up
命令, 可以直接打包docker运行.
已知 bug
启动以后会发现日志里面会出现下面所示错误. 这是一个 Spring 的 bug. 不过不影响正常使用.
https://github.com/spring-cloud/spring-cloud-netflix/issues/1055
2017-08-22T18:01:38,394 �[1;31mERROR�[m �[32mc.n.d.TimedSupervisorTask [AsyncResolver-bootstrap-0] task supervisor rejected the task�[m
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@76f0322b rejected from java.util.concurrent.ThreadPoolExecutor@7837d285[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047) ~[?:1.8.0_101]
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823) ~[?:1.8.0_101]
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369) ~[?:1.8.0_101]
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112) ~[?:1.8.0_101]
at com.netflix.discovery.TimedSupervisorTask.run(TimedSupervisorTask.java:62) [eureka-client-1.4.11.jar:1.4.11]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_101]
at java.util.concurrent.FutureTask.run(FutureTask.java:266) [?:1.8.0_101]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [?:1.8.0_101]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [?:1.8.0_101]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [?:1.8.0_101]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [?:1.8.0_101]
at java.lang.Thread.run(Thread.java:745) [?:1.8.0_101]