《Kubernetes进阶实战》第八章《配置容器应用:ConfigMap和Secret》

版权声明:秉承开源精神,博主博文可以随机转载,但请注明出处! https://blog.csdn.net/zisefeizhu/article/details/86556212

我们经常都需要为我们的应用程序配置一些特殊的数据,比如密钥、Token 、数据库连接地址或者其他私密的信息。你的应用可能会使用一些特定的配置文件进行配置,比如settings.py文件,或者我们可以在应用的业务逻辑中读取环境变量或者某些标志来处理配置信息。

当然你可以直接将这些应用配置信息直接硬编码到你的应用程序中去,对于一个小型的应用,这或许是可以接受的,但是,对于一个相对较大的应用程序或者微服务的话,硬编码就会变得难以管理了。比如你现在有10个微服务,都连接了数据库A,如果现在需要更改数据库A的连接地址的话,就需要修改10个地方,显然这是难以忍受的。

当然,我们可以使用环境变量和统一的配置文件来解决这个问题,当我们想改变配置的时候,只需要更改环境变量或者配置文件就可以了,但是对于微服务来说的话,这也是比较麻烦的一件事情,Docker 允许我们在 Dockerfile 中指定环境变量,但是如果我们需要在不同的容器中引用相同的数据呢,如果我们的应用程序是运行在集群上的时候,对于配置主机的环境变量也是难以管理的了。接下来我们来写一个应用程序,最后用kubernetes来管理我们的配置信息。

本文涉及到的所有代码都位于此Gist中:https://gist.github.com/cnych/d40756ce6e03035551b6a023135a78d9

1. 编写应用

下面是我们简单定义的一个 WEB 服务,其中 TOKEN 和 LANGUAGE 是硬编码在程序代码中的,如下:(hardcode-app.py

# -*- coding: utf-8 -*-
from flask import Flask, jsonify
app = Flask(__name__)


@app.route("/")
def index():
    TOKEN = 'abcdefg123456'
    LANGUAGE = 'English'
    return jsonify(token=TOKEN, lang=LANGUAGE)


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

如果我们现在想要改变 TOKEN 或者 LANGUAGE 的话,我们就需要手动去修改上面的代码了,这就有可能会导致新的 BUG 或者安全漏洞之类的。

下面我们来用环境变量代替上面的参数吧。

2. 使用环境变量

使用环境变量还是比较容易的,大部分编程语言都有内置的方式去读取环境变量,我们这里是 python,直接使用os包下面的 getenv 方法即可获取:(read-env-app.py

# -*- coding: utf-8 -*-
import os
from flask import Flask, jsonify
app = Flask(__name__)


@app.route("/")
def index():
    TOKEN = os.getenv('TOKEN', '')
    LANGUAGE = os.getenv('LANGUAGE', '')
    return jsonify(token=TOKEN, lang=LANGUAGE)


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

现在我们就可以通过设置环境变量而不是直接去修改源码来改变我们的配置了。在 Unix 系统(MacOS和Linux)下面,我们可以通过在终端中执行下面的命令来设置环境变量:

$ export TOKEN=abcdefg0000
$ export LANGUAGE=English

对于 Windows 系统,我们就通过可以通过cmd命令进入终端,执行下面的命令来设置环境变量:

setx TOKEN "abcdefg0000"
setx LANGUAGE "English"

另外我们也可以在启动服务的时候设置环境变量,比如,对于我们的这个flask应用,我们可以这样运行:

$ TOKEN=abcdefg0000 LANGUAGE=English python read-env-app.py
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [10/Feb/2018 15:15:14] "GET / HTTP/1.1" 200 -

然后我们浏览器中打开地址http://127.0.0.1:5000/,可以看到下面的输出信息:

{
    "lang": "English",
    "token": "abcdefg0000"
}

证明我们的环境变量设置生效了……

3. 使用 Docker 的环境变量

我们现在来使用容器运行我们的应用程序,我们就可以不依赖主机的环境变量了,每个容器都有自己的环境变量,所以保证容器的环境变量正确的配置就显得尤为重要了。幸运的是,Docker 可以非常轻松的构建带有环境变量的容器,在Dockerfile文件中,我们可以通过ENV指令来设置容器的环境变量。

FROM python:3.6.4

# 设置工作目录
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# 安装依赖
RUN pip install flask
# 添加应用
ADD . /usr/src/app

# 设置环境变量
ENV TOKEN abcdefg0000
ENV LANGUAGE English

# 暴露端口
EXPOSE 5000
# 运行服务
CMD python read-env-app.py

将上面的文件保存为Dockerfile,放置在read-env-app.py文件同目录下面,然后我们可以构建镜像:

$ docker build -t cnych/envtest .

构建成功后,我们可以使用上面的cnych/envtest镜像启动一个容器:

$ docker run --name envtest --rm -p 5000:5000 -it cnych/envtest
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

同样的我们可以通过标记-e来覆盖容器的环境变量:

$ docker run --name envtest --rm -e TOKEN=abcdefg88888 -e LANGUAGE=English -p 5000:5000 -it cnych/envtest
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

然后我们在浏览器中打开http://127.0.0.1:5000/可以看到下面的输出信息证明我们的环境配置成功了:

{
    "lang": "English",
    "token": "abcdefg88888"
}

向容器传递参数

Docker Kubernetes 描述
ENTRYPOINT command 容器中的可执行文件
CMD args 需要传递给可执行文件的参数

如果需要向容器传递参数,可以在Yaml文件中通过command和args或者环境变量的方式实现。

kind: Pod
spec:
  containers:
  - image: docker.io/nginx
    command: ["/bin/command"]
    args: ["arg1", "arg2", "arg3"]
    env:
    - name: INTERVAL
      value: "30"
    - name: FIRST_VAR
      value: "foo"
    - name: SECOND_VAR
      value: "$(FIRST_VAR)bar"

可以看到,我们可以利用env标签向容器中传递环境变量,环境变量还可以相互引用。这种方式的问题在于配置文件和部署是绑定的,那么对于同样的应用,测试环境的参数和生产环境是不一样的,这样就要求写两个部署文件,管理起来不是很方便。

什么是ConfigMap

上面提到的例子,利用ConfigMap可以解耦部署与配置的关系,对于同一个应用部署文件,可以利用valueFrom字段引用一个在测试环境和生产环境都有的ConfigMap(当然配置内容不相同,只是名字相同),就可以降低环境管理和部署的复杂度。

ConfigMap有三种用法:

  • 生成为容器内的环境变量
  • 设置容器启动命令的参数
  • 挂载为容器内部的文件或目录

ConfigMap的缺点

  • ConfigMap必须在Pod之前创建
  • ConfigMap属于某个NameSpace,只有处于相同NameSpace的Pod才可以应用它
  • ConfigMap中的配额管理还未实现
  • 如果是volume的形式挂载到容器内部,只能挂载到某个目录下,该目录下原有的文件会被覆盖掉
  • 静态Pod不能用ConfigMap

ConfigMap的创建

$ kubectl create configmap <map-name> --from-literal=<parameter-name>=<parameter-value>
$ kubectl create configmap <map-name> --from-literal=<parameter1>=<parameter1-value> --from-literal=<parameter2>=<parameter2-value> --from-literal=<parameter3>=<parameter3-value>
$ kubectl create configmap <map-name> --from-file=<file-path>
$ kubectl apply -f <configmap-file.yaml>
# 还可以从一个文件夹创建configmap
$ kubectl create configmap <map-name> --from-file=/path/to/dir

Yaml 的声明方式

apiVersion: v1
data:
  my-nginx-config.conf: |
    server {
      listen              80;
      server_name         www.kubia-example.com;

      gzip on;
      gzip_types text/plain application/xml;

      location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
      }
    }
  sleep-interval: |
    25
kind: ConfigMap     

ConfigMap的调用

环境变量的方式

apiVersion: v1
kind: Pod
metadata:
  name: env-configmap
spec:
  containers:
  - image: nginx
    env:
    - name: INTERVAL
      valueFrom:
        configMapKeyRef:
          name: <map-name>
          key: sleep-interval

如果引用了一个不存在的ConfigMap,则创建Pod时会报错,直到能够正常读取ConfigMap后,Pod会自动创建。

一次传递所有的环境变量

spec:
  containers:
  - image: nginx
    envFrom:
    - prefix: CONFIG_
      configMapRef:
        name: <map-name>

命令行参数的方式

apiVersion: v1
kind: Pod
metadata:
  name: env-configmap
spec:
  containers:
  - image: nginx
    env:
    - name: INTERVAL
      valueFrom:
        configMapKeyRef:
          name: <map-name>
          key: sleep-interval
    args: ["$(INTERVAL)"]

以配置文件的方式

apiVersion: v1
kind: Pod
metadata:
  name: nginx-test
spec:
  containers:
  - image: nginx
    name: web-server
    volumeMounts:
    - name: config
      mountPath: /etc/nginx/conf.d
      readOnly: true
  volumes:
  - name: config
    configMap:
      name: <map-name>

将Configmap挂载为一个文件夹后,原来在镜像中的文件夹里的内容就看不到,这是什么原理?这是因为原来文件夹下的内容无法进入,所以显示不出来。为了避免这种挂载方式影响应用的正常运行,可以将configmap挂载为一个配置文件。

spec:
  containers:
  - image: nginx
    volumeMounts:
    - name: config
      mountPath: /etc/someconfig.conf
      subPath: myconfig.conf

Configmap的更新

$ kubectl edit configmap <map-name>

confgimap更新后,如果是以文件夹方式挂载的,会自动将挂载的Volume更新。如果是以文件形式挂载的,则不会自动更新。
但是对多数情况的应用来说,配置文件更新后,最简单的办法就是重启Pod(杀掉再重新拉起)。如果是以文件夹形式挂载的,可以通过在容器内重启应用的方式实现配置文件更新生效。即便是重启容器内的应用,也要注意configmap的更新和容器内挂载文件的更新不是同步的,可能会有延时,因此一定要确保容器内的配置也已经更新为最新版本后再重新加载应用。

什么是Secret

Secret与ConfigMap类似,但是用来存储敏感信息。在Master节点上,secret以非加密的形式存储(意味着我们要对master严加管理)。从Kubernetes1.7之后,etcd以加密的形式保存secret。secret的大小被限制为1MB。当Secret挂载到Pod上时,是以tmpfs的形式挂载,即这些内容都是保存在节点的内存中,而不是写入磁盘,通过这种方式来确保信息的安全性。

Kubernetes helps keep your Secrets safe by making sure each Secret is only distributed to the nodes that run the pods that need access to the Secret. Also, on the nodes themselves, Secrets are always stored in memory and never written to physical storage, which would require wiping the disks after deleting the Secrets from them.

每个Kubernetes集群都有一个默认的secrets

创建和调用的过程与configmap大同小异,这里就不再赘述了。

参考:http://www.cnblogs.com/cocowool/p/kubernetes_configmap_secret.html

猜你喜欢

转载自blog.csdn.net/zisefeizhu/article/details/86556212