K8s 集群使用 ConfigMap 优雅加载 Spring Boot 配置文件

文章目录

1、Spring Boot 加载配置介绍

我们知道 Spring Boot 工程默认的配置文件名称为 application.properties,SpringApplication 将从以下位置加载 application.properties 文件,并把它们添加到 Spring Environment 中:

  • 当前目录下的 /config 子目录
  • 当前目录
  • 一个 Classpath 下的 /config
  • Classpath 根路径

如果我们运行时想指定运行哪个环境的配置文件,可以有三种方式:

  1. 在工程 resources 文件夹下 application.properties 文件中配置 spring.profiles.active=dev 指定加载的环境类型
  2. 启动 jar 时,指定 --spring.profiles.active=prod 加载的环境类型
  3. 启动 jar 时,指定 --spring.config.location=target/application.properties加载配置文件位置

至于在工程中如何获取这些配置文件值,这里就不在描述了,这个不是本次演示的重点。

2、环境、软件准备

本次演示环境,我是在本机 MAC OS 上操作,以下是安装的软件及版本:

  • Docker: 17.09.0-ce
  • Java: 1.8.0_211
  • Spring-boot: 2.1.4
  • Oracle VirtualBox: 5.1.20 r114628 (Qt5.6.2)
  • Minikube: v0.28.2
  • Kubernetes: v1.10.0
  • Kubectl:
    • Client Version: v1.10.0
    • Server Version: v1.10.0

注意:这里 Kubernetes 集群搭建使用 Minikube 来完成,Minikube 启动的单节点 k8s Node 实例是需要运行在本机的 VM 虚拟机里面,所以需要提前安装好 VM,这里我选择 Oracle VirtualBox。k8s 运行底层使用 Docker 容器,所以本机需要安装好 Docker 环境,这里忽略 Docker、VirtualBox、Minikube、Kubectl 的安装过程,可以参考之前文章 Minikube & kubectl 升级并配置,这里结合代码着重介绍下在 K8s 集群中如何使用 ConfigMap 优雅加载 Spring Boot 工程配置文件。

3、Spring Boot 示例工程 Demo

首先我们使用 IDEA 创建一个 Spring Boot 项目,项目名为 demo,为了好演示加载不同配置文件展示效果,这里添加 swagger-ui 依赖,然后新建 Controller 类 DemoController,通过读取配置文件中的 key 值并返回,代码如下:

package com.example.demo.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/demo")
@PropertySource("classpath:mysql.properties")
@Api(tags = "DemoController", description = "测试读取不同资源文件")
public class DemoController {

    @Value("${env}")
    private String env;
    @Value("${msg}")
    private String msg;
    @Value("${mysql.hostname}")
    private String mysl_url;
    @Value("${mysql.port}")
    private String mysql_port;

    @ApiOperation(value = "获取配置文件变量")
    @RequestMapping(value = "", method = RequestMethod.GET)
    public Map<String, Object> getDemoKey() {
        Map<String, Object> map = new HashMap<>();
        try {
            map.put("env", env);
            map.put("msg", msg);
            map.put("mysql_url", mysl_url);
            map.put("mysql_port", mysql_port);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }
}

然后在 resources 目录下,分别新建不同环境的配置文件 application.properties,以及其他配置文件 mysql.properties 如下:

# application-dev.properties
env=dev
msg=this is dev env properteis.

# application-prod.properties
env=prod
msg=this is prod env properteis.

# application-test.properties
env=test
msg=this is test env properteis.

# mysql.properties
mysql.hostname=127.0.0.1
mysql.port=3306

同时在 application.properties 配置文件下指定加载的环境为 dev,毕竟本地开发,还是使用开发环境配置比较多。

# application.properties
spring.profiles.active=dev

其他代码文件这里不再贴出来了,源码已经上传到 Github 上 spring-k8s-configmap-demo 。接下来,我们本地启动一下,看下能否正确读取到 dev 环境配置文件吧!

$ mvn clean package
$ java -jar demo-0.0.1-SNAPSHOT.jar

启动完毕,本地浏览器访问 http://127.0.0.1:8080/swagger-ui.html 页面,测试一下 http://127.0.0.1:8080/demo 接口,可以看到正确加载到 application-dev.propertiesmysql.properties 配置文件内容。

OK, 工程启动没有问题,接下来我们来创建 Dockerfile 来构建工程镜像,方便后边部署到 K8s 集群中,新建 Dockerfile 如下:

$ vim Dockerfile
FROM huwanyang168/centos7_jdk8:v1.2

MAINTAINER huwanyang168 <huwanyang168@163.com>

ADD demo-0.0.1-SNAPSHOT.jar /opt

WORKDIR /opt

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "demo-0.0.1-SNAPSHOT.jar"]

简单说明一下,我们是基于 centos7_jdk8 环境,添加并启动编译后的 jar,最后构建镜像并 push 到镜像仓库(这里我推送到个人 Docker Hub 仓库)。

$ docker build -t huwanyang168/demo:0.0.1 -f Dockerfile .
$ docker push huwanyang168/demo:0.0.1

启动一下,也是妥妥没有问题的,这里就不在演示了。

4、K8s ConfigMap 加载工程配置文件

接下来,我们创建一个可以在 K8s 集群中运行该镜像的资源类型 yaml 文件,该文件主要包含 NamespaceConfigMapDeploymentService 四种资源类型,这里我使用两种方式加载配置文件,对比下二者的好处和弊端。

4.1、直接加载环境的配置文件

yaml 文件如下:

apiVersion: v1
kind: Namespace
metadata:
  name: wanyang3
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: demo-configmap
  namespace: wanyang3
data:
  application.properties: |
    env=local
    msg=this is local env properteis.
  mysql.properteis: |
    mysql.hostname=10.10.10.10
    mysql.port=3333  
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-hwy
  namespace: wanyang3
  labels:
    app: demo-hwy
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: demo-hwy
  template:
    metadata:
      labels:
        app: demo-hwy
    spec:
      containers:
      - name: demo
        image: huwanyang168/demo:0.0.1
        imagePullPolicy: IfNotPresent
        args: ["--spring.config.location=application.properties,mysql.properties"]
        ports:
        - containerPort: 8080
        volumeMounts:
        - name: demo-config
          mountPath: /opt/application.properties
          subPath: application.properties
        - name: demo-config
          mountPath: /opt/mysql.properties 
          subPath: mysql.properties   
      volumes:
      - name: demo-config
        configMap:
          name: demo-configmap
          items:
            - key: application.properties
              path: application.properties 
            - key: mysql.properties
              path: mysql.properties  
---
apiVersion: v1
kind: Service
metadata:
  name: demo-hwy
  namespace: wanyang3
  labels:
    app: demo-hwy
spec:
  type: NodePort 
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
      nodePort: 32123
  selector:
    app: demo-hwy     

说明一下:

  • 创建一个名称为 wanyang3 的 Namespace,下边其他资源部署在该命名空间下。
  • 创建一个 ConfigMap 来配置下边容器启动时需要使用到的对应环境的配置文件,一般会有多个环境配置,例如 devtestprod,这里部署需要使用到哪个环境的配置文件,就配置哪个。
  • 创建一个 Deployment 用来部署上边的 demo 镜像,开启 8080 端口并挂载 ConfigMap 指定的环境配置文件到指定位置。
  • 创建一个 Service 来代理上边的 Deployment 并使用 NodePort 方式对外暴露 32123端口来方便访问。

这里有个地方需要注意:就是 K8s 中 commandargs 和 Dockerfile 中的 ENTRYPOINTCMD 之间的关系,下边详细介绍一下二者的关系。

K8s 中 command、args 和 Dockerfile 中的 ENTRYPOINT、CMD 之间的关系

我们知道,K8s 中有 command、args 可以指定镜像启动命令和参数,而 Dockerfile 中 ENTRYPOINT、CMD 同样可以指定镜像启动命令和参数,在 K8s 中当用户同时写了 command 和 args 的时候,是可以覆盖 Dockerfile 中 ENTRYPOINT 的命令行和 CMD 参数,但对于一些其他情况,比如仅仅写了 command 或者 args 的时候,二者的覆盖关系又是怎样呢?我们参照 这里 获得完整的情况分类如下:

  • 如果 command 和 args 均没有配置,那么使用 Dockerfile 默认的配置。
  • 如果 command 配置,args 没有配置,那么 Dockerfile 默认的配置会被忽略而且仅仅执行 yaml 文件中的 command(不带任何参数)。
  • 如果 command 没有配置,args 配置了,那么 Dockerfile 默认配置的 ENTRYPOINT 的命令行会被执行,但是调用的参数是 yaml 中的 args。
  • 如果 command 和 args 都配置了,那么 Dockerfile 默认的配置被忽略,使用 yaml 的配置。

Image Entrypoint

Image Cmd

Container command

Container args

Command run

[/ep-1]

[foo bar]

[ep-1 foo bar]

[/ep-1]

[foo bar]

[/ep-2]

[ep-2]

[/ep-1]

[foo bar]

[zoo boo]

[ep-1 zoo boo]

[/ep-1]

[foo bar]

[/ep-2]

[zoo boo]

[ep-2 zoo boo]

这里我们使用将 ConfigMap 配置的对应环境配置文件挂载到容器指定位置(跟 jar 包在同一目录),然后通过 1、Spring Boot 加载配置介绍 中的第三种方式,它会在当前目录自动查找指定的配置文件,从而达到启动服务时能够加载正确的配置文件的目的。这种方式好处就是,我们构建时可以不包含不同环境的配置文件(当然打包含进去也是没问题的,会覆盖),这样 jar 包就是一个纯净的不带任何配置文件的应用,该 jar 包在任何环境均可使用,只需要启动时加载包含了对应环境的配置文件的 ConfigMap 即可,其次就是如果仅仅是配置文件需要修改,那么可以在不需要重新构建镜像的情况下,直接修改 ConfigMap 即可。坏处就是每次配置 ConfigMap 时要将对应环境的配置文件配置到 yaml 文件中,稍显复杂。

最后,在集群内部署一下该 yaml,部署成功后,通过访问 http://<k8s_cluster_ip>:32123 地址,查看下是否能够正确读取到配置吧,测试没有问题。

4.2、配置要加载的环境属性

yaml 文件如下:

apiVersion: v1
kind: Namespace
metadata:
  name: wanyang3
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: demo-configmap-1
  namespace: wanyang3
data:
  DEPLOYMENT_ENV: test
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-hwy-1
  namespace: wanyang3
  labels:
    app: demo-hwy-1
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: demo-hwy-1
  template:
    metadata:
      labels:
        app: demo-hwy-1
    spec:
      containers:
      - name: demo
        image: huwanyang168/demo:0.0.1
        imagePullPolicy: IfNotPresent
        args: ["--spring.profiles.active=$(DEPLOYMENT_ENV_KEY)"]
        ports:
        - containerPort: 8080
        env:
        - name: DEPLOYMENT_ENV_KEY
          valueFrom:
            configMapKeyRef:
              name: demo-configmap-1
              key: DEPLOYMENT_ENV
---
apiVersion: v1
kind: Service
metadata:
  name: demo-hwy-1
  namespace: wanyang3
  labels:
    app: demo-hwy-1
spec:
  type: NodePort 
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
      nodePort: 32124
  selector:
    app: demo-hwy-1     

说明一下:

  • 创建一个名称为 wanyang3 的 Namespace,下边其他资源部署在该命名空间下。
  • 创建一个 ConfigMap 用来配置一下 DEPLOYMENT_ENV: test 的 Key-Value 值,主要用来为下边启动容器时指定激活那个环境的配置,一般分为 devtestprod 等环境配置。
  • 创建一个 Deployment 用来部署上边的 demo 镜像,开启 8080 端口并指定加载 ConfigMap指定的环境配置。
  • 创建一个 Service 来代理上边的 Deployment 并使用 NodePort 方式对外暴露 32124 端口来方便访问。

这里有个地方需要注意:就是 Deploymentcommond 命令中使用 ConfigMap 定义的环境变量方式。

Deployment 在 commond 命令中使用 ConfigMap 定义的环境变量

我们可以使用该方式从 ConfigMap中获取指定的 Key 值,并设置为 env 环境变量的形式,可参考 这里 查看使用示例。

env:
- name: DEPLOYMENT_ENV_KEY
  valueFrom:
    configMapKeyRef:
      name: demo-configmap-1
      key: DEPLOYMENT_ENV

然后就可以在 command 或者 args 命令时,直接通过 $(DEPLOYMENT_ENV_KEY) 方式获取 env 的值啦!

为什么要强调这点呢,因为在 4.1、直接加载环境的配置文件 中我们通过挂载 volume 的方式将 ConfigMap 中的文件或者值挂载到容器指定位置,这里我们使用 Deployment 在 commond 命令中使用 ConfigMap 定义的环境变量,通过这种方式将要激活的环境属性传递到启动参数中,这样在启动容器时,就可以动态加载指定的环境配置文件啦(这里使用 1、Spring Boot 加载配置介绍 中的第二种方式)。对比上边那种方式,好处就是部署时不需要每次将对应环境的配置文件写到 ConfigMap 中,而是简单的指定激活的环境属性即可(前提是构建时包含所有环境的配置文件),非常的方便。坏处就是如果配置文件改变,每次都得重新构建镜像,重新走一遍部署流程。

最后,在集群内部署一下该 yaml,部署成功后,通过访问 http://<k8s_cluster_ip>:32124 地址,查看下是否能够正确读取到配置吧,测试妥妥没有问题的。

当然,除了上边两种方式外,我们也可以直接在 Dockerfile 中指定激活的环境配置文件,这样的话,我们部署到不同环境时,需要分别构建镜像,这样 K8s 部署时就可以不需要指定 ConfigMap 了,个人认为此方式对应迭代不频繁的项目可以采用,毕竟不需要重复构建不同环境配置文件的镜像,但是对于迭代频繁的项目,建议采用 ConfigMap 方式,这样我们就可以避免重复构建不同环境的镜像啦,一个镜像搞定所有环境!

参考资料

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券