《K8s 实战》——ConfigMap 和 Secret

一、向容器传递参数或环境变量

1. 在 Docker 中定义命令与参数

  • 容器运行的完整指令由两部分组成:命令 ENTRYPOINT 与参数 CMD
    • ENTRYPOINT 定义容器启动时被调用的可执行程序
    • CMD 指定传递给 ENTRYPOINT 的参数
  • 上述两种指令均支持以下两种形式:
    • shell 形式:如 ENTRYPOINT node app.js
    • exec 形式:如 ENTRYPOINT ["node", "app.js"]
  • 两者的区别在于指定的命令是否是在 shell 中被调用:
1
2
3
4
5
6
7
8
9
10
11
12
# shell形式:在shell中执行node进程
$ docker exec -it 383a ps x
PID TTY STAT TIME COMMAND
1 ? Ss 0:00 /bin/sh -c node app.js
8 ? Sl 0:00 node app.js
14 pts/0 Rs+ 0:00 ps x

# exec形式:直接运行node进程
$ docker exec -it a435 ps x
PID TTY STAT TIME COMMAND
1 ? Ssl 0:00 node app.js
12 pts/0 Rs+ 0:00 ps x
  • 虽然可以直接使用 CMD 指令指定镜像运行时想要执行的命令,但还是建议借助 ENTRYPOINT 指令,仅仅用 CMD 指定所需的默认参数
1
2
3
4
5
6
7
8
$ cat Dockerfile
FROM ubuntu:latest
ADD test.sh /bin/test.sh # test.sh每`$1`秒输出一行文本
ENTRYPOINT ["/bin/test.sh"]
CMD ["10"]
$ docker build -t lb/test:args .
$ docker run -it lb/test:args
$ docker run -it lb/test:args 15 # 传递参数

2. 向容器传递命令行参数

  • 在 K8s 中定义容器时,镜像的 ENTRYPOINTCMD 均可以被覆盖。仅需要在容器定义中设置属性 commandargs 的值
    • 绝大多数情况下,只需要设置 argscommand 一般很少被覆盖,除非针对一些未定义 ENTRYPOINT 的通用镜像,如 busybox
    • commandargs 字段在 Pod 创建后无法被修改
1
2
3
4
5
6
7
8
9
kind: Pod
spec:
containers:
- image: some/image
command: ["/bin/command"] # 对应ENTRYPOINT,一般情况不覆盖
args: ["arg1", "arg2"] # 对应CMD
#args:
#- foo # 字符串无需引号标记
#- "15" # 数值需要

3. 为容器设置环境变量

  • K8s 可为 Pod 中的每个容器指定环境变量,环境变量列表无法在 Pod 创建后被修改
1
2
3
4
5
6
7
8
9
kind: Pod
spec:
containers:
- image: some/image
env: # 指定环境变量
- name: FOO
value: "foo"
- name: BAR
value: "$(FOO)bar" # 引入其他环境变量(command和args属性值也可以借此引用环境变量)
  • 在每个容器中,K8s 会自动暴露相同命名空间下每个 service 对应的环境变量

二、ConfigMap

1. 介绍

  • K8s 允许将配置选项分离到单独的资源对象 ConfigMap 中。其本质为键值对映射,值可以为字面量配置文件
  • 应用无需直接读取 ConfigMap,其内容可以通过环境变量卷文件的形式传递给容器

2. 创建 ConfigMap

  • ConfigMap 的键名需仅包含数字、字母、破折号、下划线、圆点,圆点可首位。键名不合法则不会映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cat cm.yaml
apiVersion: v1
kind: ConfigMap
data:
sleep-interval: "25" # 配置条目
metadata:
name: my-config

# 可指定字面量或配置文件
$ kubectl create configmap my-config \
--from-file=foo.json \ # 文件
--from-file=bar=foobar.conf \ # 文件自定义键名
--from-file=config-opts/ \ # 文件夹
--from-literal=some=thing # 字面量

3. 传递 ConfigMap 作为环境变量

  • 启动 Pod 时若无 ConfigMap,Pod 会正常调度,但容器启动失败。创建 ConfigMap 后,失败容器会重启。可设置 configMapKeyRef.optional=true,这样即使 ConfigMap 不存在,容器也能启动
1
2
3
4
5
6
7
8
9
10
11
kind: Pod
spec:
containers:
- image: some/image
env:
- name: INTERVAL
valueFrom: # 使用ConfigMap中的key初始化
configMapKeyRef:
name: my-config
key: sleep-interval
#optional: true
  • 将所有条目暴露为环境变量(如果不是合法的环境变量名称,K8s 不会自动转换键名,如 CONFIG_FOO-BAR
1
2
3
4
5
6
7
spec:
containers:
- image: some/image
envFrom: # 传递所有
- prefix: CONFIG_ # 为所有环境变量设置前缀(可选)
configMapKeyRef:
name: my-config

4. 传递 ConfigMap 作为命令行参数

  • 使用 ConfigMap 初始化某个环境变量,然后在参数字段中引用该环境变量
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod
spec:
containers:
- image: some/image
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: my-config
key: sleep-interval
args: ["$(INTERVAL)"] # 参数中引用环境变量

5. 传递 ConfigMap 作为配置文件

  • ConfigMap 卷会将每个条目暴露成一个文件,运行在容器中的进程可通过读取文件内容获取相应的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$ cat test-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: fortune-config
data:
my-nginx-config.conf: |
server {
listen 80;
gzip on;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
sleep-interval: "25"
---
apiVersion: v1
kind: Pod
metadata:
name: test-configmap
spec:
containers:
- image: nginx:alpine
name: web-server
volumeMounts:
- name: config
mountPath: /etc/nginx/conf.d/ # 挂载到文件夹会隐藏该文件夹中已存在的文件
readOnly: true
volumes:
- name: config
configMap:
name: fortune-config

$ kubectl exec test-configmap -c web-server -- ls /etc/nginx/conf.d
my-nginx-config.conf
sleep-interval

$ kubectl port-forward test-configmap 8080:80 &
$ curl -H "Accept-Encoding: gzip" -I localhost:8080
Content-Encoding: gzip
  • 暴露指定的 ConfigMap 条目
1
2
3
4
5
6
7
8
9
spec:
volumes:
- name: config
configMap:
name: fortune-config
items: # 只暴露指定条目
- key: my-nginx-config.conf
path: test.conf # 条目的值被存储到该文件
# 此时`/etc/nginx/conf.d/`中只有test.conf
  • 挂载时如何不影响文件夹中的其他文件?
    • subPath 可用作挂载卷中的某个独立文件或文件夹,无需挂载整个卷
    • 这种文件挂载方式会有文件更新的缺陷:如果挂载的是容器中的单个文件而不是完整的卷,ConfigMap 更新后对应的文件不会被更新
1
2
3
4
5
6
7
spec:
containers:
- image: some/image
volumeMounts:
- name: config
mountPath: /etc/someconfig.conf # 挂载到某个文件
subPath: myconfig.conf # 仅挂载条目myconfig.conf

  • 为 ConfigMap 卷中的文件设置权限
1
2
3
4
5
volumes:
- name: config
configMap:
name: fortune-config
defaultMode: "6600" # 默认644

6. 更新配置且不重启应用程序

  • 更新 ConfigMap 后,卷中引用它的文件也会相应更新,进程发现文件改变后进行重载。K8s 也支持文件更新后手动通知容器
1
2
3
$ kubectl edit configmap fortune-config
$ kubectl exec test-configmap -c web-server -- cat /etc/nginx/conf.d/my-nginx-config.conf
$ kubectl exec test-configmap -c web-server -- nginx -s reload
  • ConfigMap 卷中的文件是 ..data 文件夹中文件的符号链接。ConfigMap 更新后,所有文件会被一次性更新:K8s 会创建一个文件夹写入所有文件,最终将符号 ..data 链接转为该文件夹
1
2
3
4
5
$ kubectl exec -it test-configmap -- ls -lA /etc/nginx/conf.d
drwxr-xr-x 2 root root 4096 May 13 08:21 ..2023_05_13_08_21_54.937580497
lrwxrwxrwx 1 root root 31 May 13 08:21 ..data -> ..2023_05_13_08_21_54.937580497
lrwxrwxrwx 1 root root 27 May 13 08:21 my-nginx-config.conf -> ..data/my-nginx-config.conf
lrwxrwxrwx 1 root root 21 May 13 08:21 sleep-interval -> ..data/sleep-interval

三、Secret

  • 与 ConfigMap 类似。区别:①K8s 仅将 Secret 分发到需要访问 Secret 的 Pod 所在的机器节点;②只存在于节点的内存中,不写入物理存储;③etcd 以加密形式存储 Secret

1. 默认令牌 Secret

  • 默认被挂载到所有容器(可通过设置 Pod 定义中或 Pod 使用的服务账户中的 automountServiceAccountToken=false 来关闭该默认行为)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ kubectl get secrets
NAME TYPE DATA AGE
default-token-zns7b kubernetes.io/service-account-token 3 2d
$ kubectl describe secrets
...
Data # 包含从Pod内部安全访问API服务器所需的全部信息
====
ca.crt: 570 bytes
namespace: 7 bytes
token: eyJhbGciO...
$ kubectl describe pod
...
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-zns7b
$ kubectl exec mypod ls /var/run/secrets/kubernetes.io/serviceaccount/
ca.crt
namespace
token

2. 创建 Secret

1
2
3
4
5
6
7
8
9
10
11
12
$ openssl genrsa -out https.key 2048
$ openssl req -new -x509 -key https.key -out https.cert -days 3650 -subj /CN=www.example.com
$ echo bar > foo

$ kubectl create secret generic fortune-https --from-file=https.key --from-file=https.cert --from-file=foo
$ kubectl get secret fortune-https -o yaml
apiVersion: v1
kind: Secret
data:
foo: YmFyCg==
https.cert: LS0tLS...
https.key: LS0tLS...
  • Secret 条目的内容会被 Base64 编码。Secret 条目可涵盖二进制数据,Base64 编码可将二进制数据转为纯文本(但 Secret 大小限于 1MB)
  • K8s 允许通过 stringData 字段设置条目的纯文本值
1
2
3
4
5
6
apiVersion: v1
kind: Secret
stringData: # 该字段只写,查看时不会显示,而是被编码后展示在data字段下
foo: bar
data:
https.cert: LS0tLS...

3. 使用 Secret

  • 将 Secret 卷暴露给容器后,条目的值会被自动解码并以真实形式写入对应文件或环境变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
$ cat test-secret.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-secret
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/
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

$ curl https://localhost:8443 -k -v
issuer: CN=www.example.com

# Secret卷采用内存文件系统挂载,不会写入磁盘
$ kubectl exec test-secret -c web-server -- mount | grep certs
tmpfs on /etc/nginx/certs type tmpfs (ro,relatime)
  • 暴露 Secret 为环境变量(不推荐,该方式可能无意中暴露 Secret 信息)
1
2
3
4
5
6
env:
- name: FOO_SECRET
valueFrom:
secretKeyRef:
name: fortune-https
key: foo

4. 镜像拉取 Secret

  • 从私有镜像仓库拉取镜像时,K8s 需拥有拉取镜像所需的证书,这时可以通过 Secret 来传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 创建包含Docker镜像仓库证书的Secret
$ kubectl create secret docker-registry mydockerhubsecret \
--docker-username=myusername --docker-password=mypassword --docker-email=myemail

# 引用该Secret
$ cat private-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: private-pod
spec:
imagePullSecrets:
- name: mydockerhubsecret
containers:
- image: username/private:tag
name: test
  • 可添加 Secret 到 ServiceAccount 使所有 Pod 都能自动添加上该 Secret