k8s (六) ConfigMap 和 Secret:配置应用程序
一、向容器传递命令行参数
1.1. 在 Docker 中定义命令与参数
容器中运行的完整指令由两部分组成:命令与参数。
ENTRYPOINT 与 CMD:
- ENTRYPOINT:定义容器启动时被调用的可执行程序。
- CMD:指定传递给 ENTRYPOINT 的参数。
尽管可以直接使用 CMD 指令指定镜像运行时想要执行的命令,正确的做法依旧是借助 ENTRYPOINT 指令,仅仅用 CMD 指定所需的默认参数。这样,镜像可以直接运行,无须添加任何参数:
docker run <image>
或者是添加一些参数,覆盖 Dockerfile 中任何由 CMD 指定的默认参数值:
docker run <image> <arguments>
shell 与 exec 形式的区别:
- shell 形式:如 ENTRYPOINT node app.js
- exec 形式:如 ENTRYPOINT [“node”, “app.js”]
两者的区别在于指定的命令是否是在 shell 中被调用。exec 形式的 ENTRYPOINT 指令是直接运行 node 进程;shell 形式的指令主进程(PID 1)是 shell 进程而非 node 进程,node 进程于 shell 中启动。shell 进程往往是多余的,因此通常可以直接采用 exec 形式的 ENTRYPOINT 指令。
实例:
luksa/fortune:args 镜像中的运行脚本:
#!/bin/bash
trap "exit" SIGINT
INTERVAL=$1
echo Configured to generate new fortune every $INTERVAL seconds
mkdir -p /var/htdocs
while :
do
echo $(date) Writing fortune to /var/htdocs/index.html
/usr/games/fortune > /var/htdocs/index.html
sleep $INTERVAL
done
luksa/fortune:args 的 Dockerfile 内容:
FROM ubuntu:latest
RUN apt-get update ; apt-get -y install fortune
ADD fortuneloop.sh /bin/fortuneloop.sh
ENTRYPOINT ["/bin/fortuneloop.sh"]
CMD ["10"]
测试:
docker run -it luksa/fortune:args # 默认的 10 秒钟
docker run -it luksa/fortune:args 15 # 传参修改为 15 秒钟
1.2. 在 Kubernetes 中覆盖命令和参数
在 Kubernetes 中定义容器时,镜像的 ENTRYPOINT 和 CMD 均可以被覆盖,仅需要在容器定义中设置 command 和 args 的值:
kind: Pod
spec:
containers:
- image: some/image
command: ["/bin/command"] # 对应 ENTRYPOINT
args: ["arg1", "arg2", "arg3"] # 对应 CMD,多参数值可以使用 "-" 数组的形式(字符串值无须用引号标记,数值需要)
注意:command 和 args 字段在 pod 创建后无法被修改。绝大多数情况下,只需要设置自定义参数。命令一般很少被覆盖,除非针对一些未定义 ENTRYPOINT 的通用镜像。
二、为容器设置环境变量
在容器定义中指定环境变量:
apiVersion: v1
kind: Pod
metadata:
name: fortune-env
spec:
containers:
- image: luksa/fortune:env
env:
- name: INTERVAL
value: "30"
在环境变量值中引用其他环境变量:
env:
- name: FIRST_VAR
value: "foo"
- name: SECOND_VAR
value: "$(FIRST_VAR)bar"
三、利用 ConfigMap 解耦配置
Kubernetes 允许将配置选项分离到单独的资源对象 ConfigMap 中,本质上就是一个键/值对映射,值可以是短字面量,也可以是完整的配置文件。应用无须直接读取 ConfigMap,甚至根本不需要知道其是否存在。映射的内容通过环境变量或者卷文件的形式传递给容器,而并非直接传递给容器。
3.1. 创建 ConfigMap
3.1.1. 使用指令创建 ConfigMap
kubectl create configmap fortune-config --from-literal=sleep-interval=25
创建了一个叫 fortune-config 的 ConfigMap,只包含一个条目。也可以添加多个条目:
kubectl create configmap myconfigmap --from-literal=foo=bar --from-literal=bar=baz --from-literal=one=two
注意:ConfigMap 中的键名必须是一个合法的 DNS 子域,仅包含数字字母、破折号、下划线以及圆点。
查看创建的 ConfigMap 的 YAML 格式的定义描述:
kubectl get configmap fortune-config -o yaml
3.1.2. 从文件内容创建 ConfigMap 目录
kubectl create configmap my-config --from-file=config-file.conf
默认使用文件名作为键名,也可以手动指定键名:
kubectl create configmap my-config --from-file=customkey=config file.conf
多次使用 —from-file 参数可增加多个文件条目。
3.1.3. 从文件夹创建 ConfigMap
kubectl create configmap my -config --from-file=/path/to/dir
为文件夹中的每个文件单独创建条目,仅限于那些文件名可作为合法 ConfigMap 键名的文件
3.1.4. 合并不同选项
kubectl create configmap my -config
--from-file=foo.json # 单独的文件
--from-file=bar=foobar.conf # 自定义键名条目下的文件
--from-file=config-opts/ # 完整的文件夹
--from-file=some=thing # 字面量
3.2. 给容器传递 ConfigMap 条目作为环境变量
apiVersion: v1
kind: Pod
metadata:
name: fortune-env-from-configmap
spec:
containers:
- image: luksa/fortune:env
env:
- name: INTERVAL # 环境变量名为 INTERVAL
valueFrom:
configMapKeyRef: # ConfigMap
name: fortune-config # ConfigMap 名称
key: sleep-interval # ConfigMap 对应键的值
... ...
在 pod 中引用不存在的 ConfigMap,如果没有设置 configMapKeyRef.optional: true 则容器会启动失败,如果之后创建了这个缺失的 ConfigMap,失败容器会自动启动,无须重新创建 pod
3.3. 一次性传递 ConfigMap 的所有条目作为环境变量
spec:
containers:
- image: some-image
envFrom:
- prefix: CONFIG_ # 所有环境变量的前缀,可以不设置
configMapRef:
name: my-config-map
... ...
3.4. 传递 ConfigMap 条目作为命令行参数
apiVersion: v1
kind: Pod
metadata:
name: fortune-args-from-configmap
spec:
containers:
- image: luksa/fortune:args
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
args: ["$(INTERVAL)"] # 在参数设置中引用环境变量
... ...
3.5. 使用 ConfigMap 卷将条目暴露为文件
3.5.1. 创建 ConfigMap
新建文件夹 configmap-files 并将 my-nginx-config.conf 和 sleep-interval 放置在文件夹下:
# 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
先删除之前创建的 ConfigMap:
kubectl delete configmap fortune-config
新建 ConfigMap:
kubectl create configmap fortune-config --from-file=configmap-files
查看:
kubectl get configmap fortune-config -o yaml
3.5.2. 在卷内使用 ConfigMap 的条目
# fortune-pod-configmap-volume.yaml
# 详见:https://github.com/luksa/kubernetes-in-action/blob/master/Chapter07/fortune-pod-configmap-volume.yaml
apiVersion: v1
kind: Pod
metadata:
name: fortune-configmap-volume
spec:
containers:
... ...
- image: nginx:alpine
name: web-server
volumeMounts:
... ...
- name: config
mountPath: /etc/nginx/conf.d # 挂载 configMap 卷至这个位置
readOnly: true
... ...
volumes:
... ...
- name: config
configMap:
name: fortune-config # 卷引用 fortune-config
创建:
kubectl create -f fortune-pod-configmap-volume.yaml
验证:
kubectl port-forward fortune-configmap-volume 8080:80 &
curl -H "Accept-Encoding: gzip" -I localhost:8080
输出:
... ...
Content-Encoding: gzip
注意:挂载某一文件夹会隐藏该文件夹中已存在的文件
3.5.3. 卷内暴露指定的 ConfigMap 条目
volumes:
- name: config
configMap:
name: fortune-config
items: # 选择包含在卷中的条目
- key: my-nginx-config.conf # 该键对应的条目被包含
path: gzip.conf # 条目的值被存储在该文件中
`
3.5.4. ConfigMap 独立条目作为文件被挂载且不隐藏文件夹中的其他文件
spec:
containers:
- image: some/image
volumeMounts:
- name: myvolume
mountPath: /etc/someconfig.conf # 挂载至某一文件而不是文件夹
subPath: myconfig.conf #仅挂载指定的条目 myconfig.conf 并非完整的卷
3.5.5. 为 configMap 卷中的文件设置权限
configMap 卷中所有文件的权限默认被设置为644 (-rw-r-r—)。可以通过卷规格定义中的 defaultMode 属性改变默认权限:
volumes:
- name: html
emptyDir: {}
- name: config
configMap:
name: fortune-config
defaultMode: 0660 # 设置所有文件的权限为 -rw-rw------
3.6. 更新应用配置且不重启应用程序
使用环境变量或者命令行参数作为配置源的弊端在于无法在进程运行时更新配置。将 ConfigMap 暴露为卷可以达到配置热更新的效果,无须重新创建 pod 或者重启容器。ConfigMap 被更新之后,卷中引用它的所有文件也会相应更新,进程发现文件被改变之后进行重载。Kubernetes 同样支待文件更新之后手动通知容器。
修改 ConfigMap:
kubectl edit configmap fortune-config
将 gzip on 改为 gzip off,ConfigMap 被更新不久之后会自动更新卷中的对应文件,查看:
kubectl exec fortune-configmap-volume -c web-server -- cat /etc/nginx/conf.d/my-nginx-config.conf
通知 Nginx 重载配置:
kubectl exec fortune-configmap-volume -c web-server -- nginx -s reload
四、使用 Secret 给容器传递敏感数据
Secret 的使用方法也与ConfigMap 相同,可以:
- 将 Secret 条目作为环境变量传递给容器
- 将 Secret 条目暴露为卷中的文件
Secret 只会存储在节点的内存中,永不写入物理存储,这样从节点上删除 Secret 时就不需要擦除磁盘
创建 Secret:
kubectl create secret generic fortune-https --from-file=https.key --from-file=https.cert --from-file=foo
https.key, https.cert, foo 文件内容见 链接
查看:
kubectl get secret fortune-https -o yaml
Secret 条目的内容会被以 Base64 格式编码,这种区别导致在处理 YAML 和 JSON 格式的 Secret 时会稍许有些麻烦,可以通过 stringData 字段设置条目的纯文本值。
通过 secret 卷将 Secret 暴露给容器之后,Secret 条目的值会被解码并以真实形式(纯文本或二进制)写入对应的文件。通过环境变量暴露 Secret 条目亦是如此。在这两种情况下,应用程序均无须主动解码,可直接读取文件内容或者查找环境变量。
修改 fortune-config ConfigMap 以开启 HTTPS:
kubectl edit configmap fortune-config
... ...
server {
listen 80;
listen 443 ssl;
server_name www.kubia-example.com;
ssl_certificate certs/https.cert; # /etc/nginx 的相对位置
ssl_certificate_key certs/https.key; # /etc/nginx 的相对位置
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
... ...
挂载 fortune-secret 至 pod:
# fortune-pod-https.yaml
apiVersion: v1
kind: Pod
metadata:
name: fortune-https
spec:
containers:
- image: luksa/fortune:env
name: html-generator
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
volumeMounts:
- name: html
mountPath: /var/htdocs
- image: nginx:alpine
name: web-server
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
- name: config
mountPath: /etc/nginx/conf.d
readOnly: true
- name: certs
mountPath: /etc/nginx/certs/ # 配置 Nginx 从 /etc/nginx/certs 中读取证书和密钥文件
readOnly: true
ports:
- containerPort: 80
- containerPort: 443
volumes:
- name: html
emptyDir: {}
- name: config
configMap:
name: fortune-config
items:
- key: my-nginx-config.conf
path: https.conf
- name: certs
secret:
secretName: fortune-https # secret 卷
kubectl create -f fortune-pod-https.yaml
测试:
kubectl port-forward fortune-https 8443:443 &
curl https://localhost:8443 -k