这kubernetes中,这类Volume不是为了存放数据,也不是用来做数据交换,而是为容器提供预先定义好的数据。所以从容器角度来看,这类Volume就像是被投射进容器一样。

到目前为止,kubernetes支持4种这类Volume:
(1)、Secret
(2)、ConfigMap
(3)、DownloadAPI
(4)、ServiceAccountToken

3.1.1、Secret

Secret的作用是把Pod想要访问的加密数据存放到Etcd中,然后可以在Pod容器中通过挂载的方式访问Secret里保存的信息。

一旦Secret被创建,就可以通过下面三种方式使用它:
(1)在创建Pod时,通过为Pod指定Service Account来自动使用该Secret。
(2)通过挂载该Secret到Pod来使用它。
(3)在Docker镜像下载时使用,通过指定Pod的spc.ImagePullSecrets来引用它。

例如,我们定义下面这个Pod:

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: pod-volume-test
  5. labels:
  6. app: pod-volume-test
  7. spec:
  8. containers:
  9. - name: volume-secret-test
  10. image: busybox
  11. command:
  12. - "/bin/sh"
  13. - "-c"
  14. - "sleep 3600"
  15. volumeMounts:
  16. - name: my-secret
  17. mountPath: "/project-volume"
  18. readOnly: true
  19. volumes:
  20. - name: my-secret
  21. projected:
  22. sources:
  23. - secret:
  24. name: my-secret-volume

从上面的yaml文件中可以看到定义了一个简单的容器volume-secret-test,它里面挂载了一个my-secret的volume,这个volume是project类型,而这个数据来源是叫user和password得secret对象。

这里的secret对象,可以直接通过文件生成,也可以通过定义YAML文件的方式来创建。
(1)、通过文件生成

  1. cat ./username.txt
  2. admin
  3. $ cat ./password.txt
  4. c1oudc0w!
  5. $ kubectl create secret generic user --from-file=./username.txt
  6. $ kubectl create secret generic pass --from-file=./password.txt

(2)、通过YAML方式生成

  1. apiVersion: v1
  2. kind: Secret
  3. metadata:
  4. name: my-secret-volume
  5. type: Opaque
  6. data:
  7. user: cm9vdA==
  8. password: UEBzc1cwcmQ=

其中user和password的值是需要经过base64转码,如下:

  1. [root@master ~]# echo -n "root" | base64
  2. cm9vdA==
  3. [root@master ~]# echo -n "P@ssW0rd" | base64
  4. UEBzc1cwcmQ=

这个转码未经过加密,在实际生产中是需要添加一个secret的插件,用来做加密操作。
然后我们来创建这个两个pod:

  1. # kubectl apply -f creat-volume-secret.yaml
  2. # kubectl apply -f pod-volume-test.yaml

然后通过如下命令查看创建的secret:
image.png

然后通过kubectl exec进去容器查看是否挂载上:

  1. [root@master volume]# kubectl exec -it pod-volume-test -- /bin/sh
  2. / # cd project-volume/
  3. /project-volume # ls
  4. password user
  5. /project-volume # cat password
  6. P@ssW0rd
  7. /project-volume # cat user
  8. root

通过这种方式挂载的Secret,如果某个数据被更新,这些Volume里的内容不会被更新,如果要更新,我们需要重新apply一下或者删除重建。

比如,我们修改password的值:

  1. apiVersion: v1
  2. kind: Secret
  3. metadata:
  4. name: my-secret-volume
  5. type: Opaque
  6. data:
  7. user: cm9vdA==
  8. password: cGFzc3dvcmQxMjM0NTY=

然后重新执行一下:

  1. # kubectl apply -f creat-volume-secret.yaml

然后我们进入容器查看password变化(大概等了2分钟):

  1. [root@master volume]# kubectl exec -it pod-volume-test -- /bin/sh
  2. / # cd project-volume/
  3. /project-volume # cat password
  4. password123456

说明: 每个单独的Secret大小不能超过1MB,Kubernetes不鼓励创建大的Secret,因为如果使用大的Secret,则将大量占用API Server和kubelet的内存。当然,创建许多小的Secret也能耗尽APIServer和kubelet的内存。

综上,我们可以通过Secret保管其他系统的敏感信息(比如数据库的用户名和密码),并以Mount的方式将Secret挂载到Container中,然后通过访问目录中文件的方式获取该敏感信息。当Pod被API Server创建时,API Server不会校验该Pod引用的Secret是否存在。一旦这个Pod被调度,则kubelet将试着获取Secret的值。如果Secret不存在或暂时无法连接到API Server,则kubelet按一定的时间间隔定期重试获取该Secret,并发送一个Event来解释Pod没有启动的原因。一旦Secret被Pod获取,则kubelet将创建并挂载包含Secret的Volume。只有所有Volume都挂载成功,Pod中的Container才会被启动。在kubelet启动Pod中的Container后,Container中和Secret相关的Volume将不会被改变,即使Secret本身被修改。为了使用更新后的Secret,必须删除旧Pod,并重新创建一个新Pod。

3.1.2、ConfigMap

ConfigMap和Serect类似,不同之处在于ConfigMap保存的数据信息是不需要加密的,比如一些应用的配置信息,其他的用法和Secret一样。同样,我们可以使用两种方式来创建ConfigMap:

  • 通过命令行方式,也就是kubectl create configmap;
  • 通过YAML文件方式;

(1)、通过命令方式创建

如果我们不知道ConfigMap的命令方式,可以使用kubectl create configmap -h查看使用方法,如下:

  1. Examples:
  2. # Create a new configmap named my-config based on folder bar
  3. kubectl create configmap my-config --from-file=path/to/bar
  4. # Create a new configmap named my-config with specified keys instead of file basenames on disk
  5. kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt
  6. # Create a new configmap named my-config with key1=config1 and key2=config2
  7. kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2
  8. # Create a new configmap named my-config from the key=value pairs in the file
  9. kubectl create configmap my-config --from-file=path/to/bar
  10. # Create a new configmap named my-config from an env file
  11. kubectl create configmap my-config --from-env-file=path/to/bar.env

从上面可以看出,创建ConfigMap可以从给定一个目录来创建。例如,我们定义了如下一些配置文件:

  1. [root@master volume]# cd configmap-daemo/
  2. [root@master configmap-daemo]# ll
  3. total 8
  4. -rw-r--r-- 1 root root 25 Sep 6 17:07 mysqld.conf
  5. -rw-r--r-- 1 root root 25 Sep 6 17:07 redis.conf
  6. [root@master configmap-daemo]# cat mysqld.conf
  7. host=127.0.0.1
  8. port=3306
  9. [root@master configmap-daemo]# cat redis.conf
  10. host=127.0.0.1
  11. port=6379

然后使用一下命令来进行创建:

  1. # kubectl create configmap my-configmap --from-file=configmap-daemo/

然后通过一下命令查看创建完的configmap:

  1. [root@master volume]# kubectl get configmap
  2. NAME DATA AGE
  3. my-configmap 2 19s
  4. [root@master volume]# kubectl describe configmap my-configmap
  5. Name: my-configmap
  6. Namespace: default
  7. Labels: <none>
  8. Annotations: <none>
  9. Data
  10. ====
  11. mysqld.conf:
  12. ----
  13. host=127.0.0.1
  14. port=3306
  15. redis.conf:
  16. ----
  17. host=127.0.0.1
  18. port=6379
  19. Events: <none>

我们可以看到两个key对应的是文件的名字,value对应的是文件的内容。如果要看键值的话可以通过如下命令查看:

  1. [root@master volume]# kubectl get configmap my-configmap -o yaml
  2. apiVersion: v1
  3. data:
  4. mysqld.conf: |
  5. host=127.0.0.1
  6. port=3306
  7. redis.conf: |
  8. host=127.0.0.1
  9. port=6379
  10. kind: ConfigMap
  11. metadata:
  12. creationTimestamp: "2019-09-06T09:10:49Z"
  13. name: my-configmap
  14. namespace: default
  15. resourceVersion: "252070"
  16. selfLink: /api/v1/namespaces/default/configmaps/my-configmap
  17. uid: cd8d3752-c1c4-48e0-b523-bafa20f1ba1f

当然,我们还可以通过文件来创建一个configmap,比如我们定义一个如下的配置文件:

  1. [root@master configmap-daemo]# cat nginx.conf
  2. user nobody;
  3. worker_processes 1;
  4. error_log logs/error.log;
  5. error_log logs/error.log notice;
  6. error_log logs/error.log info;
  7. pid logs/nginx.pid;
  8. events {
  9. worker_connections 1024;
  10. }
  11. http {
  12. include mime.types;
  13. default_type application/octet-stream;
  14. log_format main '$remote_addr - $remote_user [$time_local] "$request" '
  15. '$status $body_bytes_sent "$http_referer" '
  16. '"$http_user_agent" "$http_x_forwarded_for"';
  17. access_log logs/access.log main;
  18. sendfile on;
  19. tcp_nopush on;
  20. keepalive_timeout 65;
  21. gzip on;
  22. server {
  23. listen 80;
  24. server_name localhost;
  25. location / {
  26. root html;
  27. index index.html index.htm;
  28. }
  29. error_page 500 502 503 504 /50x.html;
  30. location = /50x.html {
  31. root html;
  32. }
  33. }
  34. }

然后启动如下命令创建一个nginx的configmap:

  1. # kubectl create configmap nginx-configmap --from-file=nginx.conf

然后查看创建后的信息:

  1. [root@master configmap-daemo]# kubectl get configmap nginx-configmap -o yaml
  2. apiVersion: v1
  3. data:
  4. nginx.conf: |
  5. user nobody;
  6. worker_processes 1;
  7. error_log logs/error.log;
  8. error_log logs/error.log notice;
  9. error_log logs/error.log info;
  10. pid logs/nginx.pid;
  11. events {
  12. worker_connections 1024;
  13. }
  14. http {
  15. include mime.types;
  16. default_type application/octet-stream;
  17. log_format main '$remote_addr - $remote_user [$time_local] "$request" '
  18. '$status $body_bytes_sent "$http_referer" '
  19. '"$http_user_agent" "$http_x_forwarded_for"';
  20. access_log logs/access.log main;
  21. sendfile on;
  22. tcp_nopush on;
  23. keepalive_timeout 65;
  24. gzip on;
  25. server {
  26. listen 80;
  27. server_name localhost;
  28. location / {
  29. root html;
  30. index index.html index.htm;
  31. }
  32. error_page 500 502 503 504 /50x.html;
  33. location = /50x.html {
  34. root html;
  35. }
  36. }
  37. }
  38. kind: ConfigMap
  39. metadata:
  40. creationTimestamp: "2019-09-06T09:25:42Z"
  41. name: nginx-configmap
  42. namespace: default
  43. resourceVersion: "253356"
  44. selfLink: /api/v1/namespaces/default/configmaps/nginx-configmap
  45. uid: c4804bb0-dec3-461e-8fc4-0b0bb516815c

注:在一条命令中—from-file可以指定多次。
另外,通过帮助文档我们可以看到我们还可以直接使用字符串进行创建,通过—from-literal参数传递配置信息,同样的,这个参数可以使用多次,格式如下:

  1. [root@master configmap-daemo]# kubectl create configmap my-cm-daemo --from-literal=db.host=localhost --from-literal=db.port=3306
  2. configmap/my-cm-daemo created
  3. [root@master configmap-daemo]# kubectl get configmap my-cm-daemo -o yaml
  4. apiVersion: v1
  5. data:
  6. db.host: localhosy
  7. db.port: "3306"
  8. kind: ConfigMap
  9. metadata:
  10. creationTimestamp: "2019-09-06T09:29:49Z"
  11. name: my-cm-daemo
  12. namespace: default
  13. resourceVersion: "253713"
  14. selfLink: /api/v1/namespaces/default/configmaps/my-cm-daemo
  15. uid: ee8823e7-f2b3-46bf-ba6c-2f32303622c4

(2)、通过YAML文件创建

通过YAML文件创建就比较简单,我们可以参考上面输出的yaml信息,比如定义如下一个YAML文件:

  1. [root@master configmap-daemo]# cat my-cm-daemo2.yaml
  2. apiVersion: v1
  3. kind: ConfigMap
  4. metadata:
  5. name: my-cm-daemon2
  6. labels:
  7. app: cm-daemon
  8. data:
  9. redis.conf: |
  10. host=127.0.0.1
  11. port=6379

查看结果信息:

  1. [root@master configmap-daemo]# kubectl apply -f my-cm-daemo2.yaml
  2. configmap/my-cm-daemon2 created
  3. [root@master configmap-daemo]# kubectl describe configmap my-cm-daemon2
  4. Name: my-cm-daemon2
  5. Namespace: default
  6. Labels: app=cm-daemon
  7. Annotations: kubectl.kubernetes.io/last-applied-configuration:
  8. {"apiVersion":"v1","data":{"redis.conf":"host=127.0.0.1\nport=6379\n"},"kind":"ConfigMap","metadata":{"annotations":{},"labels":{"app":"cm...
  9. Data
  10. ====
  11. redis.conf:
  12. ----
  13. host=127.0.0.1
  14. port=6379
  15. Events: <none>

(3)、使用ConfigMap

ConfigMap中的配置数据可以通过如下方式进行使用:

  • 设置环境变量值
  • 在容器里设置命令行参数
  • 在数据卷中创建config文件

1、通过设置环境变量值来使用ConfigMap
定义如下YAML文件:

  1. [root@master configmap-daemo]# cat env-configmap.yaml
  2. apiVersion: v1
  3. kind: Pod
  4. metadata:
  5. name: env-configmap
  6. labels:
  7. app: env-configmap-mysql
  8. spec:
  9. containers:
  10. - name: test-configmap
  11. image: busybox
  12. command:
  13. - "/bin/sh"
  14. - "-c"
  15. - "env"
  16. env:
  17. - name: DB_HOST
  18. valueFrom:
  19. configMapKeyRef:
  20. name: my-cm-daemo
  21. key: db.host
  22. - name: DB_PORT
  23. valueFrom:
  24. configMapKeyRef:
  25. name: my-cm-daemo
  26. key: db.port
  27. envFrom:
  28. - configMapRef:
  29. name: my-cm-daemo

查看配置结果:

  1. [root@master configmap-daemo]# kubectl apply -f env-configmap.yaml
  2. pod/env-configmap created
  3. [root@master configmap-daemo]# kubectl logs env-configmap
  4. KUBERNETES_PORT=tcp://10.68.0.1:443
  5. KUBERNETES_SERVICE_PORT=443
  6. HOSTNAME=env-configmap
  7. DB_PORT=3306
  8. DB_HOST=localhost
  9. ......

我们可以看到我们期望得到的变量信息已经从ConfigMap中得到了。

2、在容器中设置命令行参数来获取ConfigMap配置信息
定义YAML问价如下:

  1. [root@master configmap-daemo]# cat command-configmap.yaml
  2. apiVersion: v1
  3. kind: Pod
  4. metadata:
  5. name: command-configmap
  6. spec:
  7. containers:
  8. - name: command-configmap-test
  9. image: busybox
  10. command:
  11. - "/bin/sh"
  12. - "-c"
  13. - "echo $(DB_HOST) $(DB_PORT)"
  14. env:
  15. - name: DB_HOST
  16. valueFrom:
  17. configMapKeyRef:
  18. name: my-cm-daemo
  19. key: db.host
  20. - name: DB_PORT
  21. valueFrom:
  22. configMapKeyRef:
  23. name: my-cm-daemo
  24. key: db.port

查看结果

  1. [root@master configmap-daemo]# kubectl logs command-configmap
  2. localhosy 3306

3、在数据卷中使用ConfigMap
定义YAML文件如下:

  1. [root@master configmap-daemo]# cat volume-configmap.yaml
  2. apiVersion: v1
  3. kind: Pod
  4. metadata:
  5. name: volume-configmap-test
  6. spec:
  7. containers:
  8. - name: volume-configmap-test
  9. image: busybox
  10. command: [ "/bin/sh", "-c", "cat /etc/config/redis.conf" ]
  11. volumeMounts:
  12. - name: config-volume
  13. mountPath: /etc/config
  14. volumes:
  15. - name: config-volume
  16. configMap:
  17. name: my-configmap

运行Pod,查看结果

  1. [root@master configmap-daemo]# kubectl logs volume-configmap-test
  2. host=127.0.0.1
  3. port=6379

我们也可以在ConfigMap值被映射的数据卷里去控制路径,如下Pod定义:

  1. [root@master configmap-daemo]# cat volume-path-configmap.yaml
  2. apiVersion: v1
  3. kind: Pod
  4. metadata:
  5. name: volume-path-configmap
  6. spec:
  7. containers:
  8. - name: volume-path-configmap-test
  9. image: busybox
  10. command: [ "/bin/sh","-c","cat /etc/config/path/to/msyqld.conf" ]
  11. volumeMounts:
  12. - name: config-volume
  13. mountPath: /etc/config
  14. volumes:
  15. - name: config-volume
  16. configMap:
  17. name: my-configmap
  18. items:
  19. - key: mysqld.conf
  20. path: path/to/msyqld.conf

然后运行Pod,查看执行结果:

  1. [root@master configmap-daemo]# kubectl logs volume-path-configmap
  2. host=127.0.0.1
  3. port=3306

另外需要注意的是,当ConfigMap以数据卷的形式挂载进Pod的时,这时更新ConfigMap(或删掉重建ConfigMap),Pod内挂载的配置信息会热更新。这时可以增加一些监测配置文件变更的脚本,然后reload对应服务。

3.1.3、DownloadAPI

让这个Pod里的容器可以直接获取这个Pod API对象本身的信息。
比如:

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: test-downwardapi-volume
  5. labels:
  6. zone: us-est-coast
  7. cluster: test-cluster1
  8. rack: rack-22
  9. spec:
  10. containers:
  11. - name: client-container
  12. image: k8s.gcr.io/busybox
  13. command: ["sh", "-c"]
  14. args:
  15. - while true; do
  16. if [[ -e /etc/podinfo/labels ]]; then
  17. echo -en '\n\n'; cat /etc/podinfo/labels; fi;
  18. sleep 5;
  19. done;
  20. volumeMounts:
  21. - name: podinfo
  22. mountPath: /etc/podinfo
  23. readOnly: false
  24. volumes:
  25. - name: podinfo
  26. projected:
  27. sources:
  28. - downwardAPI:
  29. items:
  30. - path: "labels"
  31. fieldRef:
  32. fieldPath: metadata.labels

通过这样的声明方式,就可以把Pod的labels字段值挂载成kubernetes容器中/etc/podinfo/labels文件。

当前DownloadAPI支持的字段如下:

  1. 1. 使用 fieldRef 可以声明使用:
  2. spec.nodeName - 宿主机名字 通过env
  3. status.hostIP - 宿主机 IP 通过env
  4. metadata.name - Pod 的名字
  5. metadata.namespace - Pod Namespace
  6. status.podIP - Pod IP 通过env
  7. spec.serviceAccountName - Pod Service Account 的名字 通过env
  8. metadata.uid - Pod UID
  9. metadata.labels['<KEY>'] - 指定 <KEY> Label
  10. metadata.annotations['<KEY>'] - 指定 <KEY> Annotation
  11. metadata.labels - Pod 的所有 Label
  12. metadata.annotations - Pod 的所有 Annotation
  13. 2. 使用 resourceFieldRef 可以声明使用:
  14. 容器的 CPU limit
  15. 容器的 CPU request
  16. 容器的 memory limit
  17. 容器的 memory request

注意:DownloadAPI能够获取到的信息一定是Pod里的容器进程启动之前就能够确定下来的信息。

3.1.4、ServiceAccountToken

ServiceAccountToken是kubernetes内置的一种”服务账户”,它是Kubernetes进行权限分配的对象。ServiceAccount的 授权信息和文件实际上是保存在Secret对象中,它是一个特殊的Secret对象。任何运行在Kubernetes集群上的应用,都必须使用ServiceAccountToken里保存的授权信息,也就是Token,这样才能合法的访问API Server。

目前,Kubernetes已经提供了一个默认的”服务账户”(Default Service Account),任何一个运行在Kubernetes里的Pod,都可以无显示声明的挂载这个ServiceAccount并使用它。

我们随意找一个Pod查看其详细信息,就可以看到一个default-token-xxxx的Volume自动挂载到容器的/var/run/secrets/kubernetes.io/serviceaccount目录上。如下:

  1. [root@master k8s]# kubectl describe pod nginx-deployment-6f655f5d99-q4fhk
  2. ......
  3. Containers:
  4. nginx:
  5. .......
  6. Mounts:
  7. /var/run/secrets/kubernetes.io/serviceaccount from default-token-gmdbb (ro)
  8. Conditions:
  9. ......
  10. Volumes:
  11. default-token-gmdbb:
  12. Type: Secret (a volume populated by a Secret)
  13. SecretName: default-token-gmdbb
  14. Optional: false
  15. QoS Class: BestEffort

我们可以看到这个Volume是Secret类型,正式默认Service Account对应的ServiceAccountToken,所以说,Kubernetes在每个Pod创建的时候,自动为其添加默认的ServiceAccountToken的定义,然后自动为每个容器加上对应的VolumeMounts字段,这个过程对应用户来说是透明的,一旦这个Pod创建完成,容器里的应用可以直接从这个默认的ServiceAccountToken挂载的目录里访问到授权信息和文件,这个路径在Kubernetes里是固定的。

我们进入Pod会看到挂载的目录下的文件信息如下:

  1. [root@master k8s]# kubectl exec -it nginx-deployment-6f655f5d99-q4fhk -- /bin/sh
  2. # ls /var/run/secrets/kubernetes.io/serviceaccount
  3. ca.crt namespace token
  4. #

所以,应用程序只要加载这些授权文件,就可以直接访问Kubernetes API了,而且,如果是直接使用Kubernetes官方的包(k8s.io/client-go),是可以直接加载这些授权文件的。

这种把Kubernetes的配置信息以容器的方式运行在集群中,然后使用ServiceAccountToken方式自动授权,被称为”InClusterConfig”。