前言:
我们经常都需要为我们的应用程序配置一些特殊的数据,比如密钥、Token 、数据库连接地址或者其他私密的信息。你的应用可能会使用一些特定的配置文件进行配置,比如settings.py文件,或者我们可以在应用的业务逻辑中读取环境变量或者某些标志来处理配置信息。
当然,我们可以使用环境变量和统一的配置文件来解决这个问题,当我们想改变配置的时候,只需要更改环境变量或者配置文件就可以了,但是对于微服务来说的话,这也是比较麻烦的一件事情,Docker 允许我们在 Dockerfile 中指定环境变量,但是如果我们需要在不同的容器中引用相同的数据呢,如果我们的应用程序是运行在集群上的时候,对于配置主机的环境变量也是难以管理的了。接下来我们来写一个应用程序,最后用kubernetes来管理我们的配置信息。
1.编写应用
下面是我们简单定义的一个 WEB 服务,其中 TOKEN 和 LANGUAGE 是硬编码在程序代码中的,如下:(hardcode-app.py)
# -*- coding: utf-8 -*-
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/")
def index():
TOKEN = 'wakewake123'
LANGUAGE = 'Chinese'
return jsonify(token=TOKEN, lang=LANGUAGE)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
如果我们现在想要改变 TOKEN 或者 LANGUAGE 的话,我们就需要手动去修改上面的代码了,这就有可能会导致新的 BUG 或者安全漏洞之类的。
下面用环境变量代替上面的参数
使用环境变量还是比较容易的,大部分编程语言都有内置的方式去读取环境变量,我们这里是 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=wakewake123
$ export LANGUAGE=Chinese
另外我们也可以在启动服务的时候设置环境变量,比如,对于我们的这个flask应用,我们可以这样运行:
$ TOKEN=wakewake123 LANGUAGE=Chinese python3 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/,可以看到下面的输出信息:
证明环境变量设置生效了
3.使用 Docker 的环境变量
我们现在来使用容器运行我们的应用程序,我们就可以不依赖主机的环境变量了,每个容器都有自己的环境变量,所以保证容器的环境变量正确的配置就显得尤为重要了。幸运的是,Docker 可以非常轻松的构建带有环境变量的容器,在Dockerfile文件中,我们可以通过ENV指令来设置容器的环境变量。
FROM python:3.10.2
# 设置工作目录
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# 安装依赖
RUN pip install flask
# 添加应用
ADD . /usr/src/app
# 设置环境变量
ENV TOKEN wakewake123
ENV LANGUAGE Chinese
# 暴露端口
EXPOSE 5000
# 运行服务
CMD python read-env-app.py
将上面的文件保存为Dockerfile,放置在read-env-app.py文件同目录下面,然后我们可以构建镜像:
docker build -t wake/envtest .
构建成功后,我们可以使用上面的wake/envtest镜像启动一个容器:
docker run --name envtest --rm -p 5000:5000 -it wake/envtest
然后我们在浏览器中打开http://127.0.0.1:5000/可以看到下面的输出信息证明我们的环境配置成功了:
4.使用 Kubernetes 的环境变量
当我们开始使用Kubernetes的时候,情况又不太一样了,我们可能会在多个 Kubernetes Deployment 中使用相同的 Docker 镜像,我们也可能希望对 Deployment 进行 A/B 测试,对不同的 Deployment 设置不同的配置信息。
和上面的 Dockerfile 一样,我们可以在 Kubernetes Deployment 的YAML文件中指定环境变量,这样我们就可以在不同的 Deployment 中设置不同的环境变量:(read-env.yaml)
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: envtest
namespace: default
labels:
name: envtest
spec:
replicas: 1
selector:
matchLabels:
name: envtest # 添加这个标签以匹配模板中的标签
spec:
containers:
- name: envtest
image: cnych/envtest
ports:
- containerPort: 5000
env:
- name: TOKEN
value: "wakewake123"
- name: LANGUAGE
value: "Chinese"
注意上面的 POD 的 env 部分,我们可以在这里指定我们想要设置的环境变量,比如这里我设置了 TOKEN 和 LANGUAGE 两个环境变量!
5. 使用 Kubernetes 的 Secrets 和 ConfigMap 进行配置
Docker和Kubernetes环境变量的不足之处在于它们是和容器的部署相关的,如果我们想要更改它们的话,就得重新构建容器或者修改 Deployment,更麻烦的是,如果想将变量用于多个容器或 Deployment 的话,就必须将配置复制过去。
幸运的是,Kubernetes中提供了Secrets(用于比较私密的数据)和ConfigMap(用于非私密的数据)两种资源可以很好的解决我们上面的问题。
Secret 和 ConfigMap 之间最大的区别就是 Secret 的数据是用Base64编码混淆过的,不过以后可能还会有其他的差异,对于比较机密的数据(如API密钥)使用 Secret 是一个很好的做法,但是对于一些非私密的数据(比如数据目录)用 ConfigMap 来保存就很好。
我们将 TOKEN 保存为 Secret:
kubectl create secret generic token --from-literal=TOKEN=wakewake123
然后将 LANGUAGE 参数保存为 ConfigMap:
kubectl create configmap language --from-literal=LANGUAGE=Chinese
然后我们可以通过下面的命令查看创建的 Secret 和 ConfigMap:
现在,我们可以重新修改 Kubernetes Demployment 的 YAML 文件:(final-read-env.yaml)
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: envtest
namespace: default
labels:
name: envtest
spec:
replicas: 1
selector:
matchLabels:
name: envtest # 添加这个标签以匹配模板中的标签
template:
metadata:
labels:
name: envtest
spec:
containers:
- name: envtest
image: cnych/envtest
ports:
- containerPort: 5000
env:
- name: TOKEN
valueFrom:
secretKeyRef:
name: token
key: TOKEN
- name: LANGUAGE
valueFrom:
configMapKeyRef:
name: language
key: LANGUAGE
这里可以看到在K8S中,使用configmap和secret的pod已经拉起来了。
从里面访问,也可以拿到我们要的数据。将之前的硬编码 env 的值更改为从 secret 和 ConfigMap 中读取了。
6.更新 Secret 和 ConfigMap
使用 Kubernetes 来管理我们的环境变量后,意味这我们不必更改代码或者重新构建镜像来改变环境变量的值了。由于 POD 在启动的时候会缓存环境变量的值,所以如果我们要更改环境变量的值的话,需要以下两个步骤:
首先,更新 Secret 或者 ConfigMap:
kubectl create configmap language --from-literal=LANGUAGE=English -o yaml --dry-run | kubectl replace -f -
configmap "language" replaced
kubectl create secret generic token --from-literal=TOKEN=789wakewake -o yaml --dry-run | kubectl replace -f -
secret "token" replaced
然后重启一下pod,观察是否生效
重新访问本地服务,发现配置已经生效了。
评论区