有需要温习 Volume 的小伙伴移步: Docker容器数据卷
Volume 是 Pod 中能够被多个容器访问的共享目录。
Kubernetes 的 Volume 定义在 Pod 上, 它被一个 Pod 中的多个容器挂载到具体的文件目录下。
Volume 与 Pod 的生命周期相同, 但与容器的生命周期不相关, 当容器终止或重启时, Volume 中的数据也不会丢失。
要使用 volume, pod 需要指定 volume 的类型和内容(字段)和 映射到容器的位置(字段)。
Kubernetes 支持多种类型的 Volume,包括
- emptyDir
- hostPath
- gcePersistentDisk
- awsElasticBlockStore
- nfs
- iscsi
- flocker
- glusterfs
- rbd
- cephfs
- gitRepo
- secret
- persistentVolumeClaim
- downwardAPI
- azureFileVolume
- azureDisk
- vsphereVolume
- Quobyte
- PortworxVolume
- ScaleIO
在 k8s常用操作命令 中的 create 命令, 创建了一个 Nginx YAML, 内容如下:
[root@k8s-master ~]# vim nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
我们在这个 Deployment 为基础, 尝试声明一个 Volume
在 Kubernetes 中, Volume 是属于 Pod 对象的一部分。所以, 我们就需要修改这个 YAML 文件里的 template.spec 字段, 如下所示:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.8
ports:
- containerPort: 80
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: nginx-vol
volumes:
- name: nginx-vol
emptyDir: {}
① 可以看到, 我们在 Deployment 的 Pod 模板部分添加了一个 volumes 字段, 定义了这个 Pod 声明的所有 Volume。它的名字叫作 nginx-vol, 类型是 emptyDir。
② emptyDir 类型其实就等同于 Docker 的隐式 Volume 参数, 即: 不显式声明宿主机目录的 Volume。所以, Kubernetes 也会在宿主机上创建一个临时目录, 这个目录将来就会被绑定挂载到容器所声明的 Volume 目录上。
③ EmptyDir 类型的 volume 创建于 pod 被调度到某个宿主机上的时候, 而同一个 pod 内的容器都能读写 EmptyDir 中的同一个文件。
④ 一旦这个 pod 离开了这个宿主机, EmptyDir 中的数据就会被永久删除。
⑤ 所以目前 EmptyDir 类型的 volume 主要用作临时空间, 比如 Web 服务器写日志或者 tmp 文件需要的临时目录。
Pod 中的容器, 使用的是 volumeMounts 字段来声明自己要挂载哪个 Volume, 并通过 mountPath 字段来定义容器内的 Volume 目录, 比如:/usr/share/nginx/html
当然, Kubernetes 也提供了显式的 Volume 定义, 它叫作 hostPath。比如下面的这个 YAML 文件:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.8
ports:
- containerPort: 80
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: nginx-vol
volumes:
- name: nginx-vol
hostPath:
path: "/var/data"
这样, 容器 Volume 挂载的宿主机目录, 就变成了 /var/data。
使用 kubectl apply
指令, 更新这个 Deployment
[root@k8s-master yaml]# kubectl apply -f nginx-deployment.yaml
通过 kubectl get
指令,查看两个 Pod 被逐一更新的过程:
[root@k8s-master yaml]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-5f8c6846ff-492d7 1/1 Running 0 44m
nginx-deployment-5f8c6846ff-l2prp 1/1 Running 0 45m
nginx-deployment-9754ccbdf-5spwg 0/1 ContainerCreating 0 19s
从返回结果中, 我们可以看到, 新旧两个 Pod, 被交替创建、删除, 最后剩下的就是新版本的 Pod。
然后, 你可以使用 kubectl describe
查看一下最新的 Pod, 就会发现 Volume 的信息已经出现在了 Container 描述部分:
...
Containers:
nginx:
Container ID: docker://07b4f89248791c2aa47787e3da3cc94b48576cd173018356a6ec8db2b6041343
Image: nginx:1.8
...
Environment: <none>
Mounts:
/usr/share/nginx/html from nginx-vol (rw)
...
Volumes:
nginx-vol:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
在之前的笔记中, 有介绍过 volume 存放/共享容器中的数据, 在 k8s 中, 还存在几种特殊的 volume, 它们存在的意义不是为了存放/共享容器中的数据。这些特殊的 volume 的作用是为容器提供预先定义好的数据, 所以从容器的角度来看, 这些 volume 里的信息就是仿佛被 k8s 投射进容器的一样
目前为止, k8s 支持的特殊 volume 一共有四种:
- Secret
- ConfigMap
- Downward API
- ServiceAccountToken
讲加密数据存在 etcd 里面, 让 Pod 容器以挂载 Volume 方式进行访问
使用场景: 数据库的凭证
$ vim Secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm
$ kubectl apply -f Secret.yaml
secret/mysecret created
$ kubectl get secret
NAME TYPE DATA AGE
default-token-ch88s kubernetes.io/service-account-token 3 9d
mysecret Opaque 2 6s
$ vim secret-var.yaml
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: nginx
image: nginx
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: mysecret
key: username
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
mypod 1/1 Running 0 29s
$ kubectl exec -it mypod bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@mypod:/# echo $SECRET_USERNAME
admin
root@mypod:/# echo $SECRET_PASSWORD
1f2d1e2e67df
$ vim secret-vol.yaml
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: foo
mountPath: "/etc/foo"
readOnly: true
volumes:
- name: foo
secret:
secretName: mysecret
$ kubectl apply -f secret-vol.yaml
pod/mypod created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
mypod 1/1 Running 0 10s
$ kubectl exec -it mypod bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@mypod:/# ls /etc/fo
fonts/ foo/
root@mypod:/# ls /etc/fo
fonts/ foo/
root@mypod:/# ls /etc/foo/
password username
root@mypod:/# cat /etc/foo/password
1f2d1e2e67dfroot@mypod:/# cat /etc/foo/username
adminroot@mypod:/#
在之前的笔记中, 有介绍过 volume 存放/共享容器中的数据, 在 k8s 中, 还存在几种特殊的 volume, 它们存在的意义不是为了存放/共享容器中的数据。这些特殊的 volume 的作用是为容器提供预先定义好的数据, 所以从容器的角度来看, 这些 volume 里的信息就是仿佛被 k8s 投射进容器的一样
目前为止, k8s 支持的特殊 volume 一共有四种:
- Secret
- ConfigMap
- Downward API
- ServiceAccountToken
和 Secret 类似, ConfigMap 的作用是将不加密数据存储到 etcd 中, 让 Pod 以变量的形式或者 Volume 挂载到容器中.
场景: 配置文件
# 1. 准备 redis.properties 配置文件, 如下:
redis.host=127.0.0.1
redis.port=6379
redis.password=123456
# 2. 创建
$ kubectl create configmap redis-config --from-file=redis.properties
configmap/redis-config created
$ kubectl get cm
NAME DATA AGE
redis-config 1 5s
$ kubectl describe cm redis-config
Name: redis-config
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
redis.properties:
----
redis.host=127.0.0.1
redis.port=6379
redis.password=123456
Events: <none>
$ vim cm.yaml
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: busybox
image: busybox
command: [ "/bin/sh","-c","cat /etc/config/redis.properties" ]
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: redis-config
restartPolicy: Never
$ kubectl apply -f cm.yaml
pod/mypod created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
mypod 0/1 Completed 0 6s
$ kubectl logs mypod
redis.host=127.0.0.1
redis.port=6379
redis.password=123456
$ vim myconfig.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: myconfig
namespace: default
data:
special.level: info
special.type: hello
$ kubectl apply -f myconfig.yaml
configmap/myconfig created
$ kubectl get cm
NAME DATA AGE
myconfig 2 3s
redis-config 1 5m8s
$ vim config-var.yaml
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: busybox
image: busybox
command: [ "/bin/sh", "-c", "echo $(LEVEL) $(TYPE)" ]
env:
- name: LEVEL
valueFrom:
configMapKeyRef:
name: myconfig
key: special.level
- name: TYPE
valueFrom:
configMapKeyRef:
name: myconfig
key: special.type
restartPolicy: Never
$ kubectl apply -f config-var.yaml
pod/mypod created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
mypod 0/1 ContainerCreating 0 3s
$ kubectl logs mypod
info hello
作用: 让 Pod 里的容器能够直接获取到这个 Pod 对象本身的信息
例如:
$ vim downward-vol.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-downwardapi-volume
labels:
zone: us-est-coast
cluster: test-cluster1
rack: rack-22
spec:
containers:
- name: client-container
image: busybox
command: ["sh", "-c"]
args:
- while true; do
if [[ -e /etc/podinfo/labels ]]; then
echo -en '\n\n'; cat /etc/podinfo/labels; fi;
sleep 5;
done;
volumeMounts:
- name: podinfo
mountPath: /etc/podinfo
readOnly: false
volumes:
- name: podinfo
projected:
sources:
- downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
在这个 Yaml 文件中, 声明了一个 projected 类型的 volume, 数据源是 Downward API, 这个 Downward API column 声明了要暴露 Pod 的 metadata.labels 信息给容器
通过这样的声明, 当前 Pod 的 labels 字段的值, 就会被 k8s 自动挂载称为容器里面的 /etc/podinfo/labels 文件
当启动这个容器的时候, 会不断的打印出 /etc/podinfo/labels 里的内容, 可以通过 kubectl logs 查看
$ kubectl apply -f downward-vol.yaml
$ kubectl logs test-downwardapi-volume
到目前为止 Downward API 支持的字段如下:
1. 使用fieldRef可以声明使用:
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['<KEY>'] - 指定<KEY>的Label值
metadata.annotations['<KEY>'] - 指定<KEY>的Annotation值
metadata.labels - Pod的所有Label
metadata.annotations - Pod的所有Annotation
2. 使用resourceFieldRef可以声明使用:
容器的CPU limit
容器的CPU request
容器的memory limit
容器的memory request
需要注意的是, Downward API 能够获取到的信息, 一定是 Pod 里的容器进程启动之前就能够确定下来的, 如果你想获取容器进程的 Pid, 则通过 Downward API 获取不到
需要注意的是, 通过 Secret/ConfigMap 的方式挂载到容器里, 一旦其对应的 Etcd 里的数据被更新, 这些 volume 里的内容也会被更新, 但是存在一定的延迟, 如果以环境变量的方式挂载, 则不会自动更新