一. 背景

在生产环境中,一个app服务处于工作中的状态,此时如果需要临时替换某些配置信息,如数据库连接池大小,链接等,一般都需要在源码端修改配置信息,然后重新部署,可想而知,这样做会影响用户或者其他与其对接的app服务。有没有可以在不重启应用的情况下就可以修改配置信息呢?答案是肯定。
达到热更新的方案还是很多的,如果是java体系,spring-cloud已经为我们提供的一套方案,即spring-cloud-config,本文就不做介绍了。
spring-cloud-kubernetes是springcloud官方推出的开源项目,用于将Spring Cloud和Spring Boot应用运行在kubernetes环境,并且提供了通用的接口来调用kubernetes服务,最终是借用了kubernetes自己的服务发现功能,当configmap发生变动时可通知相关服务更新配置。

二. 实践

1.准备

1.1 应用源码

准备一个spring-boot应用,目录如下:
image.png

(1)添加maven依赖

通过maven创建名为springcloudk8sreloadconfigdemo的springboot工程,pom.xml内容如下,要注意的是新增了依赖spring-cloud-starter-kubernetes-config、spring-boot-actuator、spring-boot-actuator-autoconfigure,

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.2.7.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.ftlcloud</groupId>
  12. <artifactId>demo</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>demo</name>
  15. <description>Demo project for Spring Boot</description>
  16. <properties>
  17. <spring-boot.version>2.2.7.RELEASE</spring-boot.version>
  18. <spring-cloud.version>Hoxton.SR4</spring-cloud.version>
  19. <maven-checkstyle-plugin.failsOnError>false</maven-checkstyle-plugin.failsOnError>
  20. <maven-checkstyle-plugin.failsOnViolation>false</maven-checkstyle-plugin.failsOnViolation>
  21. <maven-checkstyle-plugin.includeTestSourceDirectory>false</maven-checkstyle-plugin.includeTestSourceDirectory>
  22. <maven-compiler-plugin.version>3.5</maven-compiler-plugin.version>
  23. <maven-deploy-plugin.version>2.8.2</maven-deploy-plugin.version>
  24. <maven-failsafe-plugin.version>2.18.1</maven-failsafe-plugin.version>
  25. <maven-surefire-plugin.version>2.21.0</maven-surefire-plugin.version>
  26. <fabric8.maven.plugin.version>3.5.37</fabric8.maven.plugin.version>
  27. <springcloud.kubernetes.version>1.0.1.RELEASE</springcloud.kubernetes.version>
  28. </properties>
  29. <dependencies>
  30. <dependency>
  31. <groupId>org.springframework.boot</groupId>
  32. <artifactId>spring-boot-starter-web</artifactId>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.springframework.boot</groupId>
  36. <artifactId>spring-boot-starter-actuator</artifactId>
  37. </dependency>
  38. <dependency>
  39. <groupId>org.springframework.boot</groupId>
  40. <artifactId>spring-boot-actuator-autoconfigure</artifactId>
  41. </dependency>
  42. <dependency>
  43. <groupId>org.springframework.cloud</groupId>
  44. <artifactId>spring-cloud-starter-kubernetes-config</artifactId>
  45. <version>${springcloud.kubernetes.version}</version>
  46. </dependency>
  47. <dependency>
  48. <groupId>org.projectlombok</groupId>
  49. <artifactId>lombok</artifactId>
  50. <optional>true</optional>
  51. </dependency>
  52. <dependency>
  53. <groupId>org.springframework.boot</groupId>
  54. <artifactId>spring-boot-starter-test</artifactId>
  55. <scope>test</scope>
  56. <exclusions>
  57. <exclusion>
  58. <groupId>org.junit.vintage</groupId>
  59. <artifactId>junit-vintage-engine</artifactId>
  60. </exclusion>
  61. </exclusions>
  62. </dependency>
  63. </dependencies>
  64. <dependencyManagement>
  65. <dependencies>
  66. <dependency>
  67. <groupId>org.springframework.cloud</groupId>
  68. <artifactId>spring-cloud-dependencies</artifactId>
  69. <version>${spring-cloud.version}</version>
  70. <type>pom</type>
  71. <scope>import</scope>
  72. </dependency>
  73. </dependencies>
  74. </dependencyManagement>
  75. <build>
  76. <plugins>
  77. <plugin>
  78. <groupId>org.springframework.boot</groupId>
  79. <artifactId>spring-boot-maven-plugin</artifactId>
  80. <version>${spring-boot.version}</version>
  81. <executions>
  82. <execution>
  83. <goals>
  84. <goal>repackage</goal>
  85. </goals>
  86. </execution>
  87. </executions>
  88. </plugin>
  89. <plugin>
  90. <!--skip deploy -->
  91. <groupId>org.apache.maven.plugins</groupId>
  92. <artifactId>maven-deploy-plugin</artifactId>
  93. <version>${maven-deploy-plugin.version}</version>
  94. <configuration>
  95. <skip>true</skip>
  96. </configuration>
  97. </plugin>
  98. <plugin>
  99. <groupId>org.apache.maven.plugins</groupId>
  100. <artifactId>maven-surefire-plugin</artifactId>
  101. <version>${maven-surefire-plugin.version}</version>
  102. <configuration>
  103. <skipTests>true</skipTests>
  104. <!-- Workaround for https://issues.apache.org/jira/browse/SUREFIRE-1588 -->
  105. <useSystemClassLoader>false</useSystemClassLoader>
  106. </configuration>
  107. </plugin>
  108. <plugin>
  109. <groupId>io.fabric8</groupId>
  110. <artifactId>fabric8-maven-plugin</artifactId>
  111. <version>${fabric8.maven.plugin.version}</version>
  112. <executions>
  113. <execution>
  114. <id>fmp</id>
  115. <goals>
  116. <goal>resource</goal>
  117. </goals>
  118. </execution>
  119. </executions>
  120. </plugin>
  121. </plugins>
  122. </build>
  123. <profiles>
  124. <profile>
  125. <id>kubernetes</id>
  126. <build>
  127. <plugins>
  128. <plugin>
  129. <groupId>io.fabric8</groupId>
  130. <artifactId>fabric8-maven-plugin</artifactId>
  131. <version>${fabric8.maven.plugin.version}</version>
  132. <executions>
  133. <execution>
  134. <id>fmp</id>
  135. <goals>
  136. <goal>resource</goal>
  137. <goal>build</goal>
  138. </goals>
  139. </execution>
  140. </executions>
  141. <configuration>
  142. <enricher>
  143. <config>
  144. <fmp-service>
  145. <type>NodePort</type>
  146. </fmp-service>
  147. </config>
  148. </enricher>
  149. </configuration>
  150. </plugin>
  151. </plugins>
  152. </build>
  153. </profile>
  154. </profiles>
  155. </project>

(2)src\main\resources创建名为bootstrap.yml的文件

如下:

server:
  port: 8080

management:
  endpoint:
    restart:
      enabled: true
    health:
      enabled: true
    info:
      enabled: true

spring:
  application:
    name: ftl-cloud-app-demo
  profiles:
    active: staging
  cloud:
    kubernetes:
      reload:
        #自动更新配置的开关设置为打开
        enabled: true
        #更新配置信息的模式是主动拉取
        mode: polling
        #主动拉取的间隔时间是500毫秒
        period: 1000
      config:
        sources:
          - name: ${spring.application.name}
            namespace: import-staging

可见新增了配置项spring.cloud.kubernetes.reload和spring.cloud.kubernetes.config,前者用于开启自动更新配置,执行更新模式为500毫秒拉取一次,后者指定配置来源于kubernetes的哪个namespace下的哪个configmap。

(3)java源码

需要应用加载的信息如下

## kafka topic相关配置
kafka:
  topic:
    group-id: receiver-group
    topic-name:
      - topic1
      - topic2
      - topic3

KafkaTopicProperties.java :

@RefreshScope
@Data
@Component
@ConfigurationProperties("kafka.topic")
public class KafkaTopicProperties {

    private String groupId;
    private String[] topicName;

    public String getGroupId() {
        return groupId;
    }

    public void setGroupId(String groupId) {
        this.groupId = groupId;
    }

    public String[] getTopicName() {
        return topicName;
    }

    public void setTopicName(String[] topicName) {
        this.topicName = topicName;
    }
}

TestController.java


@RestController
public class TestController {

    @Autowired
    private KafkaTopicProperties properties;

    @GetMapping("/get")
    @ResponseBody
    public Object test(){
        return  properties.getTopicName();
    }

}

DemoApplication.java


@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

1.2 configmap创建

即application.yml内容,本文以kafka主题简单配置为例,如下:
## kafka topic相关配置
kafka:
  topic:
    group-id: receiver-group
    topic-name:
      - topic1
      - topic2
      - topic3

(1)通过rancher创建configmap

如果k8s通过rancher管理的话,可以通过rancher创建此configmap.
首先,点击资源-配置映射如下:
image.png
添加一个配置映射,名称就取我们的demo名称ftl-cloud-app-demo,注意选择命名空间。
image.png
点击保存即可创建成功一个名称为ftl-cloud-app-demo的configmap

(2)通过kubectl创建configmap

1)在kubernetes环境新建名为ftl-cloud-app-demo.yml的文件,内容如下:

apiVersion: v1
data:
  application.yml: |-
    ## kafka topic相关配置
    kafka:
      topic:
        group-id: receiver-group
        topic-name:
          - topic1
          - topic2
          - topic3
kind: ConfigMap
metadata:
  name: ftl-cloud-app-demo
  namespace: import-staging

保存后执行:

kubectl apply -f ftl-cloud-app-demo.yml

即可生成名称为ftl-cloud-app-demo的configmap。

2.授权

2.1 角色的创建

在命令行执行:

kubectl create role role-configmap-reader --verb=get,list,watch --resource=pods,configmaps --dry-run -o yaml > role-configmap-reader.yml

执行后,会生成role-configmap-reader.yml的文件,编辑使之如下:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: import-staging
  name: role-configmap-reader
rules:
  - apiGroups: [""]
    resources: ["pods","configmaps"]
    verbs: ["get", "watch", "list"]

保存,执行

kubectl apply -f role-configmap-reader.yml

即可创建一个拥有”get”, “watch”, “list” -> “pods”,”configmaps”权限的角色

2.2 创建ServiceAccount

在命令行执行:

kubectl create serviceaccount qianxunke  -o yaml > user-qianxunke.yml

在当前目录下会生成user-qianxunke.yml的文件,我们需要编辑它,改变默认命名空间(default),如下:
image.png
修改好命名空间后,保存执行

kubectl apply -f user-qianxunke.yml

2.3 绑定Role和ServiceAccount

kubectl create rolebinding rolebinding-pods-configmap-reade- --role=pods-reader --user=qianxunke --dry-run -o yaml > rolebinding-pods-configmap-reader.yaml

执行成功会在当前目录生成rolebinding-pods-configmap-reader.yaml,编辑其,使之如下:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: rolebinding-pods-configmap-reader
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: role-configmap-reader
subjects:
  - kind: ServiceAccount
    name: qianxunke
    namespace: import-staging

保存,执行:

kubectl apply -f rolebinding-pods-configmap-reader.yaml

3.验证

3.1项目部署

这个根据自己的环境,基本步骤就是:编译镜像,推送镜像到镜像仓库,在k8s中创建模版,执行。
这里我们创建一个无状态的deployment来部署:
内容如下:

apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: ftl-cloud-app-demo
spec:
  selector:
    matchLabels:
      app: ftl-cloud-app-demo
  replicas: 2 # tells deployment to run 2 pods matching the template
  template:
    metadata:
      labels:
        app: ftl-cloud-app-demo
    spec:
      containers:
      - name: ftl-cloud-app-demo
        image: 填写镜像地址
        ports:
        - containerPort: 8080
      serviceAccount: qianxunke
      serviceAccountName: qianxunke

注意:serviceAccount,serviceAccountName的值为上文授权的用户名
保存以上内容在ftl-cloud-app-demo-deployment.yml,然后执行

kubectl apply -f ./ftl-cloud-app-demo-deployment.yml

不出意外,程序运行正常。
如果结合rancher部署应用,只需修改已有的deployment,在相应位置添加serviceAccount,serviceAccountName即可。

3.2 验证

在postman输入:
http://自己的域名或IP/get
输入如下:
image.png
修改configmap

kubectl edit configmap ftl-cloud-app-demo

image.png再在postman点击send,即可马得到如下结果:
image.png

4.补充说明

之前的bootstrap.yml中和同步配置相关的参数,如下图红框所示:
image.png
polling是定时拉取的模式,间隔时间太大会影响实时性,太小又导致请求过于密集,所以spring-cloud-kubernetes框架还给出了另一种模式:事件通知,对应的值是event;
设置事件通知模式的步骤:先将mode的值从polling改为event,再将period参数注释掉(该参数只在mode等于polling时有效),修改后如下:
image.png