secret用来保存小片敏感数据的k8s资源,例如密码,token,或者秘钥。这类数据当然也可以存放在Pod或者镜像中,但是放在Secret中是为了更方便的控制如何使用数据,并减少暴露的风险。
用户可以创建自己的secret,系统也会有自己的secret。
Pod需要先引用才能使用某个secret
Pod有2种方式来使用secret:
作为volume的一个域被一个或多个容器挂载
在拉取镜像的时候被kubelet引用。
內建的Secrets:
由ServiceAccount创建的API证书附加的秘钥
k8s自动生成的用来访问apiserver的Secret,所有Pod会默认使用这个Secret与apiserver通信
创建自己的Secret:
方式1:使用kubectl create secret命令
方式2:yaml文件创建Secret
假如某个Pod要访问数据库,需要用户名密码,分别存放在2个文件中:username.txt,password.txt
例子
# echo -n 'admin' > ./username.txt
# echo -n '1f2d1e2e67df' > ./password.txt
kubectl create secret指令将用户名密码写到secret中,并在apiserver创建Secret
# kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt
secret "db-user-pass" created
查看创建结果
# kubectl get secrets
NAME TYPE DATA AGE
db-user-pass Opaque 2 51s
注:
opaque:英[əʊˈpeɪk] 美[oʊˈpeɪk] 模糊
# kubectl describe secrets/db-user-pass
Name: db-user-pass
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
password.txt: 12 bytes
username.txt: 5 bytes
get或describe指令都不会展示secret的实际内容,这是出于对数据的保护的考虑,如果想查看实际内容使用命令:
# kubectl get secret db-user-pass -o json
创建一个secret.yaml文件,内容用base64编码
# echo -n 'admin' | base64
YWRtaW4=
# echo -n '1f2d1e2e67df' | base64
MWYyZDFlMmU2N2Rm
yaml文件内容
# vim secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm
创建
# kubectl create -f ./secret.yaml
secret "mysecret" created
解析Secret中内容
# kubectl get secret mysecret -o yaml
apiVersion: v1
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm
kind: Secret
metadata:
creationTimestamp: 2016-01-22T18:41:56Z
name: mysecret
namespace: default
resourceVersion: "164619"
selfLink: /api/v1/namespaces/default/secrets/mysecret
uid: cfee02d6-c137-11e5-8d73-42010af00002
type: Opaque
base64解码:
# echo 'MWYyZDFlMmU2N2Rm' | base64 --decode
1f2d1e2e67df
secret可以作为数据卷挂载或者作为环境变量暴露给Pod中的容器使用,也可以被系统中的其他资源使用。比如可以用secret导入与外部系统交互需要的证书文件等。
创建一个Secret,多个Pod可以引用同一个Secret
修改Pod的定义,在spec.volumes[]加一个volume,给这个volume起个名字,spec.volumes[].secret.secretName记录的是要引用的Secret名字
在每个需要使用Secret的容器中添加一项spec.containers[].volumeMounts[],指定spec.containers[].volumeMounts[].readOnly = true,spec.containers[].volumeMounts[].mountPath要指向一个未被使用的系统路径。
修改镜像或者命令行使系统可以找到上一步指定的路径。此时Secret中data字段的每一个key都是指定路径下面的一个文件名
一个Pod中引用Secret的列子:
vim pod_use_secret.yaml
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis:3.2.9
volumeMounts:
- name: foo
mountPath: "/etc/foo"
readOnly: true
volumes:
- name: foo
secret:
secretName: mysecret
每一个被引用的Secret都要在spec.volumes中定义
如果Pod中的多个容器都要引用这个Secret那么每一个容器定义中都要指定自己的volumeMounts,但是Pod定义中声明一次spec.volumes就好了。
可以控制secret key被映射到容器内的路径,利用spec.volumes[].secret.items来修改被映射的具体路径
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
readOnly: true
volumes:
- name: foo
secret:
secretName: mysecret
items:
- key: username
path: my-group/my-username
发生了什么呢?
username被映射到了文件/etc/foo/my-group/my-username而不是/etc/foo/username
而password没有被使用,这种方式每个key的调用需要单独用key像username一样调用
可以指定secret文件的权限,类似linux系统文件权限,如果不指定默认权限是0644,等同于linux文件的-rw-r–r--权限
设置默认权限位
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
volumes:
- name: foo
secret:
secretName: mysecret
defaultMode: 256
上述文件表示将secret挂载到容器的/etc/foo路径,每一个key衍生出的文件,权限位都将是0400
这里用十进制数256表示0400,可以使用八进制0400
同理可以单独指定某个key的权限
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo
mountPath: "/etc/foo"
volumes:
- name: foo
secret:
secretName: mysecret
items:
- key: username
path: my-group/my-username
mode: 511
从volume中读取secret的值
以文件的形式挂载到容器中的secret,他们的值已经是经过base64解码的了,可以直接读出来使用。
# ls /etc/foo/
username
password
# cat /etc/foo/username
admin
# cat /etc/foo/password
1f2d1e2e67df
被挂载的secret内容自动更新
也就是如果修改一个Secret的内容,那么挂载了该Secret的容器中也将会取到更新后的值,但是这个时间间隔是由kubelet的同步时间决定的。最长的时间将是一个同步周期加上缓存生命周期(period+ttl)
特例:以subPath(https://kubernetes.io/docs/concepts/storage/volumes/#using-subpath)形式挂载到容器中的secret将不会自动更新
以环境变量的形式使用Secret
创建一个Secret,多个Pod可以引用同一个Secret
修改pod的定义,定义环境变量并使用env[].valueFrom.secretKeyRef指定secret和相应的key
修改镜像或命令行,让它们可以读到环境变量
变量名:admin(secretkey(mysecret–>username=admin))
apiVersion: v1
kind: Pod
metadata:
name: secret-env-pod
spec:
containers:
- name: mycontainer
image: redis
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: mysecret
key: username
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password
restartPolicy: Never
容器中读取环境变量,已经是base64解码后的值了:
# echo $SECRET_USERNAME
admin
# echo $SECRET_PASSWORD
1f2d1e2e67df
使用imagePullSecrets
创建一个专门用来访问镜像仓库的secret,当创建Pod的时候由kubelet访问镜像仓库并拉取镜像,具体描述文档在 这里
设置自动导入的imagePullSecrets
可以手动创建一个,然后在serviceAccount中引用它。所有经过这个serviceAccount创建的Pod都会默认使用关联的imagePullSecrets来拉取镜像,参考文档
自动挂载手动创建的Secret
参考文档:https://kubernetes.io/docs/tasks/inject-data-application/podpreset/
限制
需要被挂载到Pod中的secret需要提前创建,否则会导致Pod创建失败
secret是有命名空间属性的,只有在相同namespace的Pod才能引用它
单个Secret容量限制的1Mb,这么做是为了防止创建超大的Secret导致apiserver或kubelet的内存耗尽。但是创建过多的小容量secret同样也会耗尽内存,这个问题在将来可能会有方案解决
kubelet只支持由API server创建出来的Pod中引用secret,使用特殊方式创建出来的Pod是不支持引用secret的,比如通过kubelet的–manifest-url参数创建的pod,或者–config参数创建的,或者REST API创建的。
通过secretKeyRef引用一个不存在你secret key会导致pod创建失败
用例
Pod中的ssh keys
创建一个包含ssh keys的secret
kubectl create secret generic ssh-key-secret --from-file=ssh-privatekey=/path/to/.ssh/id_rsa --from-file=ssh-publickey=/path/to/.ssh/id_rsa.pub
创建一个Pod,其中的容器可以用volume的形式使用ssh keys
kind: Pod
apiVersion: v1
metadata:
name: secret-test-pod
labels:
name: secret-test
spec:
volumes:
- name: secret-volume
secret:
secretName: ssh-key-secret
containers:
- name: ssh-test-container
image: mySshImage
volumeMounts:
- name: secret-volume
readOnly: true
mountPath: "/etc/secret-volume"
Pod中区分生产和测试证书
创建2种不同的证书,分别用在生产和测试环境
# kubectl create secret generic prod-db-secret --from-literal=username=produser --from-literal=password=Y4nys7f11
secret "prod-db-secret" created
# kubectl create secret generic test-db-secret --from-literal=username=testuser --from-literal=password=iluvtests
secret "test-db-secret" created
再创建2个不同的Pod
apiVersion: v1
kind: List
items:
- kind: Pod
apiVersion: v1
metadata:
name: prod-db-client-pod
labels:
name: prod-db-client
spec:
volumes:
- name: secret-volume
secret:
secretName: prod-db-secret
containers:
- name: db-client-container
image: myClientImage
volumeMounts:
- name: secret-volume
readOnly: true
mountPath: "/etc/secret-volume"
---
kind: Pod
apiVersion: v1
metadata:
name: test-db-client-pod
labels:
name: test-db-client
spec:
volumes:
- name: secret-volume
secret:
secretName: test-db-secret
containers:
- name: db-client-container
image: myClientImage
volumeMounts:
- name: secret-volume
readOnly: true
mountPath: "/etc/secret-volume"
两个容器中都会有下列的文件
/etc/secret-volume/username
/etc/secret-volume/password
以“.”开头的key可以产生隐藏文件
kind: Secret
apiVersion: v1
metadata:
name: dotfile-secret
data:
.secret-file: dmFsdWUtMg0KDQo=
---
kind: Pod
apiVersion: v1
metadata:
name: secret-dotfiles-pod
spec:
volumes:
- name: secret-volume
secret:
secretName: dotfile-secret
containers:
- name: dotfile-test-container
image: k8s.gcr.io/busybox
command:
- ls
- "-l"
- "/etc/secret-volume"
volumeMounts:
- name: secret-volume
readOnly: true
mountPath: "/etc/secret-volume"
会在挂载目录下产生一个隐藏文件,/etc/secret-volume/.secret-file
Secret
作用是帮你把 Pod 想要访问的加密数据,存放到 Etcd 中。然后,就可以通过在 Pod 的容器里挂载 Volume 的方式,访问到这些 Secret 里保存的信息了。
Secret 典型的使用场景:
存放数据库的 Credential 信息
例子:
# cat test-projected-volume.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-projected-volume
spec:
containers:
- name: test-secret-volume
image: busybox
args:
- sleep
- "86400"
volumeMounts:
- name: mysql-cred
mountPath: "/projected-volume"
readOnly: true
volumes:
- name: mysql-cred
projected:
sources:
- secret:
name: user
- secret:
name: pass
定义了一个容器,它声明挂载的 Volume是 projected 类型 , 并不是常见的 emptyDir 或者 hostPath 类型,
而这个 Volume 的数据来源(sources),则是名为 user 和 pass 的 Secret 对象,分别对应的是数据库的用户名和密码。
这里用到的数据库的用户名、密码,正是以 Secret 对象的方式交给 Kubernetes 保存的。
方法1. 使用 kubectl create secret 指令创建Secret对象
# cat ./username.txt
admin
# cat ./password.txt
c1oudc0w!
# kubectl create secret generic user --from-file=./username.txt
# kubectl create secret generic pass --from-file=./password.txt
username.txt 和 password.txt 文件里,存放的就是用户名和密码;而 user 和 pass,则是为 Secret 对象指定的名字。
查看Secret 对象:
# kubectl get secrets
NAME TYPE DATA AGE
user Opaque 1 51s
pass Opaque 1 51s
方法2. 通过编写 YAML 文件的方式来创建这个 Secret 对象
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
user: YWRtaW4=
pass: MWYyZDFlMmU2N2Rm
Secret 对象要求这些数据必须是经过 Base64 转码的,以免出现明文密码的安全隐患。
转码操作:
# echo -n 'admin' | base64
YWRtaW4=
# echo -n '1f2d1e2e67df' | base64
MWYyZDFlMmU2N2Rm
注意:像这样创建的 Secret 对象,它里面的内容仅仅是经过了转码,并没有被加密。生产环境中,需要在 Kubernetes 中开启 Secret 的加密插件,增强数据的安全性。
用yaml方式创建的secret调用方法如下:
# cat test-projected-volume.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-projected-volume1
spec:
containers:
- name: test-secret-volume1
image: busybox
args:
- sleep
- "86400"
volumeMounts:
- name: mysql-cred
mountPath: "/projected-volume"
readOnly: true
volumes:
- name: mysql-cred
secret:
secretName: mysecret
创建这个 Pod:
# kubectl create -f test-projected-volume.yaml
验证这些 Secret 对象是不是已经在容器里了:
# kubectl exec -it test-projected-volume -- /bin/sh
注意:
报错:上面这条命令会报错如下
# kubectl exec -it test-projected-volume /bin/sh
error: unable to upgrade connection: Forbidden (user=system:anonymous, verb=create, resource=nodes, subresource=proxy)
解决:绑定一个cluster-admin的权限
# kubectl create clusterrolebinding system:anonymous --clusterrole=cluster-admin --user=system:anonymous
clusterrolebinding.rbac.authorization.k8s.io/system:anonymous created
# ls /projected-volume/
user
pass
# cat /projected-volume/user
root
# cat /projected-volume/pass
1f2d1e2e67df
结果中看到,保存在 Etcd 里的用户名和密码信息,已经以文件的形式出现在了容器的 Volume 目录里。
而这个文件的名字,就是 kubectl create secret 指定的 Key,或者说是 Secret 对象的 data 字段指定的 Key。
同步更新:
通过挂载方式进入到容器里的 Secret,一旦其对应的 Etcd 里的数据被更新,这些 Volume 里的文件内容,同样也会被更新,kubelet 组件在定时维护这些 Volume。
1. 生成新的密码数据:
# echo -n '111111' | base64
MTExMTEx
2 . 修改数据:
# cat mysecret.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
user: YWRtaW4=
pass: MTExMTEx
3. 更新数据:
# kubectl apply -f mysecret.yaml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
secret/mysecret configured
4. 查看对应pod里的数据是否更新:
# kubectl exec -it test-projected-volume1 /bin/sh
# cat projected-volume/pass
111111
注:这个更新可能会有一定的延时。所以在编写应用程序时,在发起数据库连接的代码处写好重试和超时的逻辑,绝对是个好习惯。
查看secret具体的值:
# kubectl get secret mysecret -o json
{
"apiVersion": "v1",
"data": {
"pass": "MTExMTEx",
"user": "YWRtaW4="
},
"kind": "Secret",
"metadata": {
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"data\":{\"pass\":\"MTExMTEx\",\"user\":\"YWRtaW4=\"},\"kind\":\"Secret\",\"metadata\":{\"annotations\":{},\"name\":\"mysecret\",\"namespace\":\"default\"},\"type\":\"Opaque\"}\n"
},
"creationTimestamp": "2019-01-21T07:31:05Z",
"name": "mysecret",
"namespace": "default",
"resourceVersion": "125857",
"selfLink": "/api/v1/namespaces/default/secrets/mysecret",
"uid": "82e20780-1d4e-11e9-baa8-000c29f01606"
},
"type": "Opaque"
}
ConfigMap与 Secret 类似,用来存储配置文件的kubernetes资源对象,所有的配置内容都存储在etcd中。
ConfigMap 保存的是不需要加密的、应用所需的配置信息。
ConfigMap 的用法几乎与 Secret 完全相同:可以使用 kubectl create configmap 从文件或者目录创建 ConfigMap,也可以直接编写 ConfigMap 对象的 YAML 文件。
创建ConfigMap的方式有4种:
方式1:通过直接在命令行中指定configmap参数创建,即–from-literal
方式2:通过指定文件创建,即将一个配置文件创建为一个ConfigMap,–from-file=<文件>
方式3:通过指定目录创建,即将一个目录下的所有配置文件创建为一个ConfigMap,–from-file=<目录>
方式4:事先写好标准的configmap的yaml文件,然后kubectl create -f 创建
创建命令:
[root@master yaml]# kubectl create configmap test-config1 --from-literal=db.host=10.5.10.116 --from-literal=db.port='3306'
configmap/test-config1 created
结果如下面的data内容所示:
[root@master yaml]# kubectl get configmap test-config1 -o yaml
apiVersion: v1
data:
db.host: 10.5.10.116
db.port: "3306"
kind: ConfigMap
metadata:
creationTimestamp: "2019-02-14T08:22:34Z"
name: test-config1
namespace: default
resourceVersion: "7587"
selfLink: /api/v1/namespaces/default/configmaps/test-config1
uid: adfff64c-3031-11e9-abbe-000c290a5b8b
编辑配置文件app.properties内容如下:
[root@master yaml]# cat app.properties
property.1 = value-1
property.2 = value-2
property.3 = value-3
property.4 = value-4
[mysqld]
!include /home/wing/mysql/etc/mysqld.cnf
port = 3306
socket = /home/wing/mysql/tmp/mysql.sock
pid-file = /wing/mysql/mysql/var/mysql.pid
basedir = /home/mysql/mysql
datadir = /wing/mysql/mysql/var
创建(可以有多个–from-file):
# kubectl create configmap test-config2 --from-file=./app.properties
结果如下面data内容所示:
[root@master yaml]# kubectl get configmap test-config2 -o yaml
apiVersion: v1
data:
app.properties: |
property.1 = value-1
property.2 = value-2
property.3 = value-3
property.4 = value-4
[mysqld]
!include /home/wing/mysql/etc/mysqld.cnf
port = 3306
socket = /home/wing/mysql/tmp/mysql.sock
pid-file = /wing/mysql/mysql/var/mysql.pid
basedir = /home/mysql/mysql
datadir = /wing/mysql/mysql/var
kind: ConfigMap
metadata:
creationTimestamp: "2019-02-14T08:29:33Z"
name: test-config2
namespace: default
resourceVersion: "8176"
selfLink: /api/v1/namespaces/default/configmaps/test-config2
uid: a8237769-3032-11e9-abbe-000c290a5b8b
通过指定文件创建时,configmap会创建一个key/value对,key是文件名,value是文件内容。
如不想configmap中的key为默认的文件名,可以在创建时指定key名字:
# kubectl create configmap game-config-3 --from-file==
configs 目录下的config-1和config-2内容如下所示:
[root@master yaml]# tail configs/config-1
aaa
bbb
c=d
[root@master yaml]# tail configs/config-2
eee
fff
h=k
创建:
# kubectl create configmap test-config3 --from-file=./configs
结果下面data内容所示:
[root@master yaml]# kubectl get configmap test-config3 -o yaml
apiVersion: v1
data:
config-1: |
aaa
bbb
c=d
config-2: |
eee
fff
h=k
kind: ConfigMap
metadata:
creationTimestamp: "2019-02-14T08:37:05Z"
name: test-config3
namespace: default
resourceVersion: "8808"
selfLink: /api/v1/namespaces/default/configmaps/test-config3
uid: b55ffbeb-3033-11e9-abbe-000c290a5b8b
指定目录创建时,configmap内容中的各个文件会创建一个key/value对,key是文件名,value是文件内容。
假如目录中还包含子目录:
在上一步的configs目录下创建子目录subconfigs,并在subconfigs下面创建两个配置文件,指定目录configs创建名为test-config4的configmap:
kubectl create configmap test-config4 --from-file=./configs
结果发现和上面没有子目录时一样,说明指定目录时只会识别其中的文件,忽略子目录
yaml文件内容如下: 注意其中一个key的value有多行内容时的写法
[root@master yaml]# cat configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config4
namespace: default
data:
cache_host: memcached-gcxt
cache_port: "11211"
cache_prefix: gcxt
my.cnf: |
[mysqld]
log-bin = mysql-bin
haha = hehe
创建:
[root@master yaml]# kubectl apply -f configmap.yaml
configmap/test-config4 created
结果如下面data内容所示:
[root@master yaml]# kubectl get configmap test-config4 -o yaml
apiVersion: v1
data:
cache_host: memcached-gcxt
cache_port: "11211"
cache_prefix: gcxt
my.cnf: |
[mysqld]
log-bin = mysql-bin
haha = hehe
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"cache_host":"memcached-gcxt","cache_port":"11211","cache_prefix":"gcxt","my.cnf":"[mysqld]\nlog-bin = mysql-bin\nhaha = hehe\n"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test-config4","namespace":"default"}}
creationTimestamp: "2019-02-14T08:46:57Z"
name: test-config4
namespace: default
resourceVersion: "9639"
selfLink: /api/v1/namespaces/default/configmaps/test-config4
uid: 163fbe1e-3035-11e9-abbe-000c290a5b8b
查看configmap的详细信息:
# kubectl describe configmap
使用ConfigMap有三种方式,一种是通过环境变量的方式,直接传递pod,另一种是通过在pod的命令行下运行的方式,第三种是使用volume的方式挂载入到pod内
示例ConfigMap文件:
apiVersion: v1
kind: ConfigMap
metadata:
name: special-config
namespace: default
data:
special.how: very
special.type: charm
(1) 使用valueFrom、configMapKeyRef、name、key指定要用的key:
[root@master yaml]# cat testpod.yaml
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: daocloud.io/library/nginx
env:
- name: SPECIAL_LEVEL_KEY //这里是容器里设置的新变量的名字
valueFrom:
configMapKeyRef:
name: special-config //这里是来源于哪个configMap
key: special.how //configMap里的key
- name: SPECIAL_TYPE_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: special.type
restartPolicy: Never
测试:
[root@master yaml]# kubectl exec -it dapi-test-pod /bin/bash
root@dapi-test-pod:/# echo $SPECIAL_TYPE_KEY
charm
(2) 通过envFrom、configMapRef、name使得configmap中的所有key/value对儿 都自动变成环境变量:
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: daocloud.io/library/nginx
envFrom:
- configMapRef:
name: special-config
restartPolicy: Never
这样容器里的变量名称直接使用configMap里的key名:
[root@master yaml]# kubectl exec -it dapi-test-pod /bin/bash
root@dapi-test-pod:/# env
HOSTNAME=dapi-test-pod
NJS_VERSION=1.15.8.0.2.7-1~stretch
NGINX_VERSION=1.15.8-1~stretch
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_PORT=tcp://10.96.0.1:443
PWD=/
special.how=very
HOME=/root
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
TERM=xterm
SHLVL=1
KUBERNETES_SERVICE_PORT=443
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
special.type=charm
KUBERNETES_SERVICE_HOST=10.96.0.1
在容器内执行命令时引用,需要先设置为环境变量,之后可以通过$(VAR_NAME)设置容器启动命令的启动参数:
注:
这个容器在执行完成之后因为没有运行的bash和tty,所以会退出,pod会处于completed状态
可以用共享volume的方式查看最终的结果
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: k8s.gcr.io/busybox
command: [ "/bin/sh", "-c", "echo (SPECIAL_LEVEL_KEY) (SPECIAL_TYPE_KEY)" ]
env:
- name: SPECIAL_LEVEL_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: SPECIAL_LEVEL
- name: SPECIAL_TYPE_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: SPECIAL_TYPE
restartPolicy: Never
下面这个例子可以看到结果:
# cat testpod.yaml
apiVersion: v1
kind: Pod
metadata:
name: testpod1
spec:
containers:
- name: test-container
image: daocloud.io/library/nginx:1.7.9
volumeMounts:
- name: shared-data
mountPath: /pod-data
- name: test-container1
image: daocloud.io/library/nginx:1.7.9
volumeMounts:
- name: shared-data
mountPath: /data
command: [ "touch" ]
args: [ "/data/a.txt" ]
volumes:
- name: shared-data
hostPath:
path: /data
(1) 把1.4中test-config4所有key/value挂载进来:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-configmap
spec:
replicas: 1
template:
metadata:
labels:
app: nginx-configmap
spec:
containers:
- name: nginx-configmap
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: config-volume4
mountPath: /tmp/config4
volumes:
- name: config-volume4
configMap:
name: test-config4
进入容器中/tmp/config4查看:
[root@master yaml]# kubectl exec -it nginx-configmap-7447bf77d6-svj2t /bin/bash
root@nginx-configmap-7447bf77d6-svj2t:/# ls /tmp/config4/
cache_host cache_port cache_prefix my.cnf
root@nginx-configmap-7447bf77d6-svj2t:/# cat /tmp/config4/cache_host
memcached-gcxt
可以看到,在config4文件夹下以每一个key为文件名value为值创建了多个文件。
(2) 假如不想以key名作为配置文件名可以引入items 字段,在其中逐个指定要用相对路径path替换的key:
volumes:
- name: config-volume4
configMap:
name: test-config4
items:
- key: my.cnf //原来的key名
path: mysql-key
- key: cache_host //原来的key名
path: cache-host
备注:
删除configmap后原pod不受影响;然后再删除pod后,重启的pod的events会报找不到cofigmap的volume;
pod起来后再通过kubectl edit configmap …修改configmap,过一会pod内部的配置也会刷新。
在容器内部修改挂进去的配置文件后,过一会内容会再次被刷新为原始configmap内容
(3) 还可以为以configmap挂载进的volume添加subPath字段:
volumeMounts:
- name: config-volume5
mountPath: /tmp/my
subPath: my.cnf
- name: config-volume5
mountPath: /tmp/host
subPath: cache_host
- name: config-volume5
mountPath: /tmp/port
subPath: cache_port
- name: config-volume5
mountPath: /tmp/prefix
subPath: cache_prefix
volumes:
- name: config-volume5
configMap:
name: test-config4
注意在容器中的形式与(2)中的不同,(2)中是个链接,链到…data/。
备注:
删除configmap后原pod不受影响;然后再删除pod后,重启的pod的events会报找不到cofigmap的volume。
pod起来后再通过kubectl edit configmap …修改configmap,pod内部的配置也会自动刷新。
在容器内部修改挂进去的配置文件后,内容可以持久保存,除非杀掉再重启pod才会刷回原始configmap的内容。
subPath必须要与configmap中的key同名。
mountPath如/tmp/prefix:
<1>当/tmp/prefix不存在时(备注:此时/tmp/prefix和/tmp/prefix/无异),会自动创建prefix文件并把value写进去;
<2>当/tmp/prefix存在且是个文件时,里面内容会被configmap覆盖;
<3>当/tmp/prefix存在且是文件夹时,无论写/tmp/prefix还是/tmp/prefix/都会报错。
例:使用configmap替换nginx的配置文件
# cat configpod.yaml
apiVersion: v1
kind: Pod
metadata:
name: confignginx
labels:
app: nginx
spec:
containers:
- name: confignginx
image: daocloud.io/library/nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: configvolume
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
volumes:
- name: configvolume
configMap:
name: configmaptest
更新 ConfigMap 后
1、使用该 ConfigMap 挂载的 Env 不会同步更新
ENV 是在容器启动的时候注入的,启动之后 kubernetes 就不会再改变环境变量的值,且同一个 namespace 中的 pod 的环境变量是不断累加的。
2、使用该 ConfigMap 挂载的 Volume 中的数据需要一段时间(实测大概10秒)才能同步更新
注意:使用subPath的方式挂载的volume是不能自动更新的
为了更新容器中使用 ConfigMap 挂载的配置,可以通过滚动更新 pod 的方式来强制重新挂载 ConfigMap,也可以在更新了 ConfigMap 后,先将副本数设置为 0,然后再扩容。
用于在容器中获取 POD 的基本信息,kubernetes原生支持
Downward API提供了两种方式用于将 POD 的信息注入到容器内部:
环境变量:
用于单个变量,可以将 POD 信息和容器信息直接注入容器内部。
Volume挂载:
将 POD 信息生成为文件,直接挂载到容器内部中去。
通过Downward API来将 POD 的 IP、名称以及所对应的 namespace 注入到容器的环境变量中去,然后在容器中打印全部的环境变量来进行验证
使用fieldRef获取 POD 的基本信息
# cat test-env-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-env-pod
namespace: kube-system
spec:
containers:
- name: test-env-pod
image: daocloud.io/library/nginx:1.13.0-alpine
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
注意: POD 的 name 和 namespace 属于元数据,是在 POD 创建之前就已经定下来了的,所以使用 metata 获取就可以了,但是对于 POD 的 IP 则不一样,因为POD IP 是不固定的,POD 重建了就变了,它属于状态数据,所以使用 status 去获取:
所有基本信息可以使用下面的方式去查看(describe方式看不出来):
# kubectl get pod first-pod -o yaml
创建上面的 POD:
# kubectl create -f test-env-pod.yaml
pod "test-env-pod" created
POD 创建成功后,查看:
[root@master yaml]# kubectl exec -it test-env-pod /bin/bash -n kube-system
root@test-env-pod:/# env | grep POD
POD_IP=172.30.19.24
POD_NAME=test-env-pod
POD_NAMESPACE=kube-system
通过Downward API将 POD 的 Label、Annotation 等信息通过 Volume 挂载到容器的某个文件中去,然后在容器中打印出该文件的值来验证。
# test-volume-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-volume-pod
namespace: kube-system
labels:
k8s-app: test-volume
node-env: test
annotations:
build: test
own: qikqiak
spec:
containers:
- name: test-volume-pod-container
image: daocloud.io/library/nginx:1.13.0-alpine
volumeMounts:
- name: podinfo
mountPath: /etc/podinfo
volumes:
- name: podinfo
downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- path: "annotations"
fieldRef:
fieldPath: metadata.annotations
将元数据 labels 和 annotaions 以文件的形式挂载到了/etc/podinfo目录下,创建上面的 POD :
# kubectl create -f test-volume-pod.yaml
pod "test-volume-pod" create
在实际应用中,如果你的应用有获取 POD 的基本信息的需求,就可以利用Downward API来获取基本信息,然后编写一个启动脚本或者利用initContainer将 POD 的信息注入到容器中去,然后在自己的应用中就可以正常的处理相关逻辑了。
目前 Downward API 支持的字段:
spec.nodeName - 宿主机名字
status.hostIP - 宿主机 IP
metadata.name - Pod 的名字
metadata.namespace - Pod 的 Namespace
status.podIP - Pod 的 IP
spec.serviceAccountName - Pod 的 Service Account 的名字
metadata.uid - Pod 的 UID
metadata.labels[’’] - 指定 的 Label 值
metadata.annotations[’’] - 指定 的 Annotation 值
metadata.labels - Pod 的所有 Label
metadata.annotations - Pod 的所有 Annotation
容器的 CPU limit
容器的 CPU request
容器的 memory limit
容器的 memory request
上面这个列表的内容,随着 Kubernetes 项目的发展肯定还会不断增加。所以这里列出来的信息仅供参考,在使用 Downward API 时,还是要记得去查阅一下官方文档。
注意:Downward API 能够获取到的信息,一定是 Pod 里的容器进程启动之前就能够确定下来的信息。而如果你想要获取 Pod 容器运行后才会出现的信息,比如,容器进程的 PID,那就肯定不能使用 Downward API 了,而应该考虑在 Pod 里定义一个 sidecar 容器。
Secret、ConfigMap,以及 Downward API 这三种 Projected Volume 定义的信息,大多还可以通过环境变量的方式出现在容器里。但是,通过环境变量获取这些信息的方式,不具备自动更新的能力。一般情况下,建议使用 Volume 文件的方式获取这些信息。