K8s 储存资源
基本定义
Pod 内的进程共享计算资源,但不包括磁盘。Kubernetes 通过定义存储卷来满足磁盘共享的需求,常用于扩展容器的存储空间并为其提供持久存储能力。
存储卷分为临时卷、本地卷和网络卷。临时卷和本地卷都位于工作节点,一旦 Pod 被调度到其他工作节点,这些类型的存储卷将无法访问。因此,临时卷和本地卷通常用于数据缓存,而持久化的数据需要放置于持久卷上。
卷是 Pod 的一个组成部分,因此像容器一样在 Pod 的配置中进行定义。它们不是独立的 Kubernetes 对象,不能单独创建或删除。Pod 中的所有容器都可以使用卷,但必须先将其挂载到容器中。
有多种可用的卷可以使用,单个容器可以同时使用不同类型的多个卷。可用的卷类型如下:
-
emptyDir:用于存储临时数据的简单空目录。
-
hostPath:用于将工作节点文件系统中的目录挂载到 Pod 中。
-
gitRepo:通过检出 Git 仓库的内容来初始化的卷。
-
nfs:挂载到 Pod 中的 NFS 共享卷。
-
gcePersistentDisk、awsElasticBlockStore、azureDisk:用于挂载云服务提供的特定存储类型。
-
ConfigMap、Secret、DownwardAPI:用于将 Kubernetes 部分资源和集群信息公开给 Pod 的特殊类型卷。
-
PersistentVolumeClaim:一种使用预配置或动态配置的持久存储类型。
-
cinder、Cephas、iscsi、flocker、glusterfs、quobyte、rbd、flexVolume、vsphere-Volume、photoPersistentDisk:用于挂载其他类型的网络存储。
emptyDir 卷
emptyDir 卷是一个空目录,Pod 内的程序可以写入所需的任何文件。当删除 Pod 时,卷的内容会丢失。它主要用于在 Pod 中运行的容器之间临时共享文件。
使用 emptyDir 卷
下面使用 Nginx 作为服务器和 fortune
命令来生成 HTML 内容。fortune
命令每次运行都会输出一个随机引用。我们可以创建一个脚本,每 10 秒运行一次,并将其储存在 index.html
文件中:
[root@server4-master ~]$ vi fortune-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: fortune
spec:
containers:
- image: luksa/fortune
name: html-generator
volumeMounts:
- name: html
mountPath: /var/htdocs
- image: nginx:alpine
name: web-server
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
emptyDir: {}
[root@server4-master ~]$ kubectl create -f fortune-pod.yaml
pod/fortune created
第一个容器 html-generator
用于将生成的 HTML 文件保存到 /var/htdocs
目录中,并将卷 html
挂载到该目录。
第二个容器 web-server
用于运行 Web 服务,并将卷 html
挂载到 /usr/share/nginx/html
目录中,设置为只读权限。
最后,我们定义了一个名为 html
的独立 emptyDir 卷,供上述容器挂载使用。
查看 Pod 状态
为了测试访问,我们可以直接设置端口转发来访问 Pod:
[root@server4-master ~]$ kubectl port-forward fortune 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
在新终端中,通过 curl
命令来访问:
[root@server4-master ~]$ curl 127.0.0.1:8080
Few things are harder to put up with than the annoyance of a good example.
-- "Mark Twain, Pudd'nhead Wilson's Calendar"
指定储存介质
可以将 emptyDir 卷创建在 tmpfs,也就是内存中。空间受限于内存大小,但性能非常好。需要同时设置 sizeLimit 来限制使用的空间大小。
下面创建一个 Pod 进行测试,包含 Tomcat 和 Busybox 两个容器。Tomcat 向挂载的卷中写入日志,而 Busybox 读取日志内容:
[root@server4-master ~]$ vi empty-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: volume-pod
namespace: default
spec:
containers:
- name: tomcat
image: tomcat
ports:
- containerPort: 8080
volumeMounts:
- name: app-logs
mountPath: /usr/local/tomcat/logs
- name: busybox
image: busybox
command: ["sh", "-c", "tail -f /logs/catalina*.log"]
volumeMounts:
- name: app-logs
mountPath: /logs
volumes:
- name: app-logs
emptyDir:
medium: Memory
[root@server4-master ~]$ kubectl create -f empty-pod.yaml
pod/volume-pod created
通过以下命令查看日志:
[root@server4-master ~]$ kubectl logs volume-pod -c busybox
14-Mar-2022 03:49:09.120 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.io.tmpdir=/usr/local/tomcat/temp
14-Mar-2022 03:49:09.128 INFO [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent Loaded Apache Tomcat Native library [1.2.31] using APR version [1.7.0].
gitRepo 卷
gitRepo 卷基本上也是一个 emptyDir 卷,通过在 Pod 启动时克隆 Git 仓库来填充数据。创建完成后,gitRepo 卷不会随着远程 Git 仓库的更新而更新。如果使用 ReplicationSet 进行管理,在删除 Pod 后会创建新的 Pod,再次触发 git clone 可以获取最新的文件。
一个典型的应用场景是存放网站的静态 HTML 文件,并创建一个包含 Web 服务器容器和 gitRepo 卷的 Pod。当 Pod 创建时,会拉取网站的最新版本并启动 Web 服务。不过,每次有新版本时需要删除 Pod 才能更新。
使用 gitRepo 卷
下面是一个使用 Nginx 容器和 gitRepo 卷的示例:
[root@k8s-master ~]$ vi gitrepo-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: gitrepo-volume-pod
spec:
containers:
- image: nginx:alpine
name: web-server
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
gitRepo:
repository: https://github.com/luksa/kubia-wesite-example.git
revision: master
directory: .
[root@k8s-master ~]$ kubectl create -f gitrepo-pod.yaml
pod/gitrepo-volume-pod created
在上述示例中,gitRepo 卷需要指定 revision
来指定 master 分支,并将仓库克隆到根目录。也可以指定其他文件夹名称。
私有仓库
gitRepo 卷无法直接克隆私有 Git 仓库,例如需要账号密码验证的 GitLab。如果需要添加支持,可以在 Pod 中添加额外的 sidecar 容器,例如 gitsync sidecar,用于对主容器进行操作,以同步仓库的版本等。
需要注意的是,gitRepo 卷现已被官方废弃,官方建议使用初始化容器将仓库中的数据复制到 emptyDir 储存卷上。
ConfigMap 资源
应用配置的关键在于能够在多个环境中区分配置选项,将配置从应用程序源码中分离。Kubernetes 允许将配置选项分离到单独的资源对象 ConfigMap 中。ConfigMap 本质上是一个键值对映射,值可以是字符串,也可以是完整的配置文件。示例图如下所示:
应用程序无需直接读取 ConfigMap,而是通过环境变量或卷文件的形式将映射的内容传递给容器。在命令行参数的定义中,可以使用变量语法引用环境变量,从而将 ConfigMap 的条目作为命令行参数传递给进程。
Pod 通过名称引用 ConfigMap,因此可以在多个环境中使用相同的 Pod 定义来描述,并根据不同的环境使用不同的配置值。
指定容器环境变量
如果 Docker 镜像有自定义参数可以配置,可以按照下面的 Dockerfile 进行设置:
#!/bin/bash
trap "exit" SIGINT
echo Fortune sleep every $INTERVAL seconds
mkdir -p /bar/htdocs
while :
do
echo $(date) Writing fortune to /var/htdocs/index.html
/usr/games/fortune > /var/htdocs/index.html
sleep $INTERVAL
done
想要在 Kubernetes 中设置自定义参数,可以在 spec.containers.env
中声明:
spec:
containers:
- image: fortune
env:
- name: INTERVAL
value: "30"
name: html-generator
也可以使用 $(VAR)
语法在环境变量值中引用其他环境变量:
spec:
containers:
- image: fortune
env:
- name: FIRST_VAR
value: "foobar"
- name: SECOND_VAR
value: "$(FIRST_VAR)2000"
创建 ConfigMap
可以直接通过命令行来创建一个最简单的 ConfigMap:
[root@server4-master ~]$ kubectl create configmap fortune-config --from-literal=sleep-interval=5
configmap/fortune-config created
这条指令创建了名为 fortune-config
的 ConfigMap,仅包含单映射条目 sleep-interval=5
。可以通过添加多个 --from-literal
参数创建包含多条目的 ConfigMap:
[root@server4-master ~]$ kubectl create configmap fortune-config --from-literal=one=1 --from-literal=two=11
通过观察 YAML 格式的定义描述,可以自定义配置文件,然后通过 create -f
来创建 ConfigMap。
可以直接从硬盘中读取文件,并将文件内容单独存储为 ConfigMap 中的值。例如,将当前目录下的 my.config
文件内容存为键名为 devconfig
的值:
[root@k8s-master 2]$ kubectl create configmap my-config --from-file=devconfig=my.config
通过多次使用 --from-file
参数可以增加多个文件条目。另外,还可以使用 --from-file
指定目录,kubectl
会为文件夹中的每个文件单独创建条目,键名为文件名:
[root@server4-master ~]$ kubectl create configmap my-config-dir --from-file=k8s
configmap/my-config-dir created
ConfigMap 可以混合使用多种类型的配置,例如一个 my-config
同时包含键值对和文件:
[root@server4-master ~]$ kubectl create configmap my-config --from-file=foo.json --from-file=bar=foobar.conf --from-file=config-opts/ --from-literal=some=thing
传递 ConfigMap
将值传递给 Pod 中的容器有三种方式。如果引用的 ConfigMap 不存在,容器会启动失败。也可以设置 optional: true
对引用进行可选设置。
-
通过环境变量传递键值
需要在
spec.containers.env.valueFrom
字段中指定:spec: containers: - image: fortune env: - name: INTERVAL valueFrom: configMapKeyRef: name: fortune-config key: sleep-interval
这里传递了一个环境变量
INTERVAL
,值取自fortune-config
中键sleep-interval
的值,然后由容器内的进程读取。 -
传递整个 ConfigMap 中的键值
在
spec.containers
中加入envFrom
字段来传递整个 ConfigMap 中的键值对:spec: containers: - image: fortune envFrom: - prefix: CONFIG_ configMapRef: name: my-cofig-map
上面设置了所有导入的环境变量包含前缀
CONFIG_
,若不设置前缀,环境变量的名称与 ConfigMap 中的键名相同。若键名不合法时不会自动转换。 -
传递 ConfigMap 条目作为命令行参数
在字段
spec.containers.args
中无法直接引用 ConfigMap 的条目,但可以利用 ConfigMap 条目初始化某个环境变量,然后再在参数字段中引用该变量:spec: containers: - image: fortune env: - name: INTERVAL valueFrom: configMapKeyRef: name: fortune-config key: sleep-interval args: ["$(INTERVAL)"]
使用 ConfigMap 卷
由于 ConfigMap 中可以包含完整的配置文件内容,想要将其暴露给容器时可以借助 ConfigMap 卷。ConfigMap 卷会将 ConfigMap 中的每个条目暴露为一个文件,运行在容器中的进程通过读取文件内容获得对应的条目值。
例如,将目录中的 Nginx 配置文件和 interval
文件一起创建名为 fortune-config
的 ConfigMap:
[root@server4-master configmap-files]$ echo "25" > interval
[root@server4-master configmap-files]$ vi advertise-task.iot.com.conf
server {
listen 80;
server_name advertise-task.iot.com;
location / {
proxy_pass http://advertise-task.iot.com.dev;
}
}
[root@server4-master configmap-files]$ kubectl create configmap fortune-config --from-file=../configmap-files/
configmap/fortune-config created
[root@server4-master ~]$ kubectl get configmaps fortune-config -o yaml
apiVersion: v1
data:
interval: |
25
advertise-task.iot.com.conf: |
server {
listen 80;
server_name advertise-task.iot.com;
location / {
proxy_pass http://advertise-task.iot.com.dev;
}
}
kind: ConfigMap
metadata:
creationTimestamp: "2022-03-15T14:01:15Z"
name: fortune-config
namespace: default
resourceVersion: "580367"
uid: 9a4c9cb4-c2d2-424c-9d57-ac3fe1851260
将 ConfigMap 卷内的文件挂载到 /etc/nginx/conf.d/
目录下:
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
也可以单独指定需要挂载的 ConfigMap 卷内文件。这里将 ConfigMap 卷内的配置文件 advertise-task.iot.com.conf
重命名为 at.conf
并挂载到指定目录下:
spec:
volumes:
- name: config
configMap:
name: fortune-config
items:
- key: advertise-task.iot.com.conf
path: at.conf
使用上述方法挂载卷时,容器内原有的同名目录会被隐藏,被挂载的目录会替代之。如果想要单独挂载文件而不是目录,需要在 spec.containers.volumeMounts
下面加入 subPath
字段:
spec:
containers:
- image: nginx:alpine
name: web-server
volumeMounts:
- name: config
mountPath: /etc/nginx/conf.d/advertise-task.iot.com.conf
subPath: advertise-task.iot.com.conf
readOnly: true
ConfigMap 卷中所有文件的默认权限是 644,可以在 spec.volumes.configMap
中加入 defaultMode
来改变默认权限:
spec:
volumes:
- name: config
configMap:
name: fortune-config
defaultMod: "6600"
将 ConfigMap 作为卷挂载可以实现配置的热更新效果,无需重启或重建 Pod。在修改了 ConfigMap 的配置后,可以通过 kubectl exec
命令来手动载入更新的配置:
[root@server4-master ~]$ kubectl edit configmap fortune-config
[root@server4-master ~]$ kubectl exec fortune -c web-server -- nginx -s reload
但如果挂载的单个文件,ConfigMap 更新后对应的文件不会被更新。
Secret 资源
Secret 的结构和使用方法与 ConfigMap 相同,也是键值对的映射,主要用来保存敏感信息,例如账号和证书。
Kubernetes 通过将 Secret 分发到 Pod 所在的节点来保障安全性,Secret 只会存储在节点的内存中,从而无法被窃取。
默认令牌
在 Kubernetes 安装完成后,会生成一个以 default-token 开头的默认 Secret,它会挂载到所有容器中:
[root@server4-master ~]$ kubectl get secrets
NAME TYPE DATA AGE
default-token-vqjsw kubernetes.io/service-account-token 3 132d
[root@server4-master ~]$ kubectl describe secrets default-token-vqjsw
Name: default-token-vqjsw
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name: default
kubernetes.io/service-account.uid: ae0b90b8-1323-42a2-990b-a18d4bfb7da8
Type: kubernetes.io/service-account-token
Data
====
ca.crt: 1099 bytes
namespace: 7 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IlZDd
默认的 Secret 包含三个条目:ca.crt、namespace 和 token,其中包含了访问 API 服务器所需的安全信息。
可以在 Pod 信息的 Mount 栏中看到 Secret 卷挂载的位置:
[root@server4-master ~]$ kubectl describe po dnsutils
Containers:
dnsutils:
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-5ghsj (ro)
默认的挂载行为可以通过在 Pod 定义中设置 automountServiceAccountToken
字段为 false
来关闭。
创建 Secret
使用命令行创建一个名为 ngx-https 的 Secret,用于存储 Nginx 所需的私钥和证书:
[root@server4-master ~]$ kubectl create secret generic ngx-https --from-file=https.cert --from-file=https.key --from-file=foo
Secret 条目的内容会以 Base64 格式编码,而 ConfigMap 则直接以纯文本形式显示。因此,Secret 还可以用于存储最大为 1MB 的二进制数据。
通过 stringData
字段设置条目的纯文本值,该值不会被编码为 Base64:
[root@server4-master ~]$ vi secret-test.yaml
apiVersion: v1
kind: Secret
stringData:
foo: ymfyc
data:
https.cert: Ls-3lcc
https.key: Lsv9elx
[root@server4-master ~]$ kubectl create -f secret-test.yaml
注意,stringData
字段只可写入。
使用 Secret
将 Secret 通过 Secret 卷暴露给容器后,Secret 条目的值将解码为实际形式(纯文本或二进制),并写入相应的文件中。同样,通过环境变量暴露 Secret 条目也是如此。在这两种情况下,应用程序无需手动解码,可以直接读取文件内容或查找环境变量:
spec:
containers:
volumeMounts:
- name: certs
mountPath: /etc/nginx/certs/
readOnly: true
volumes:
- name: certs
secret:
secretame: ngx-https
与 ConfigMap 卷相同,Secret 卷同样支持使用 defaultModes
属性指定卷中文件的默认权限。
Secret 条目也可以暴露为环境变量:
spec:
containers:
env:
- name: INTERVAL
valueFrom:
secretKeyRef:
name: ngx-https
key: foo
可以创建一个类型为 docker-registry
的 Secret,名称为 dockerhubsecret
,其中包含 Docker 镜像仓库证书,并在拉取镜像时进行引用:
[root@k8s-master 2]$ kubectl create secret docker-registry dockerhubsecret \
--docker-username=myusername --docker-password=mypasswd \
--docker-email=my@email.com
[root@k8s-master 2]$ vi dockerhub.yaml
apiVersion: v1
kind: Pod
metadata:
name: privae-pod
spec:
imagePullSecrets:
- name: dockerhubsecret
containers:
- image: assassing/av:v1
name: main
底层持久化储存
在 Kubernetes 中,底层持久化存储是一个重要的组件,用于存储应用程序的持久化数据,并确保数据的安全性和持久性。
hostPath 卷
hostPath 卷用于指向节点文件系统中的特定文件或目录。由于文件存储在特定节点的文件系统中,因此当 Pod 被重新调度到另一个节点时,可能无法访问数据。在 Kubernetes 的系统级服务 Pod 中,通常使用 hostPath 卷来访问节点的日志、配置文件或 CA 证书,但不推荐将其用于存储数据:
volumes:
- name: "hostpath"
hostPath:
path: "/data"
此外,还可以使用 type
参数来指定卷的类型:
DirectoryOrCreate
:如果指定的路径不存在,则自动创建一个权限为 0755 的空目录,所有者为 kubelet。Directory
:必须存在的目录路径。FileOrCreate
:如果指定的路径不存在,则自动创建一个权限为 0644 的空文件,所有者为 kubelet。File
:必须存在的文件路径。Socket
:必须存在的 Socket 文件路径。CharDevice
:必须存在的字符设备文件路径。BlockDevice
:必须存在的块设备文件路径。
根据实际需求选择合适的 type
参数来配置 hostPath 卷。请注意,使用 hostPath 卷时需要谨慎,确保数据的可靠性和安全性。
GCE 持久储存
如果集群运行在 Google Kubernetes Engine 中,你可以选择使用 GCE 持久化磁盘作为底层存储机制。首先,在同一区域的 Kubernetes 集群中创建一个 GCE 持久化磁盘,例如在 “europe-west” 区域,然后创建一个带有 GCE 持久化磁盘卷的 Pod:
apiVersion: v1
kind: Pod
metadata:
name: mongodb
spec:
containers:
- image: mongo
name: mongodb
volumeMounts:
- name: mongodb-data
mountPath: /data.db
ports:
- containerPort: 27017
protocol: TCP
volumes:
- name: mongodb-data
gcePersistentDisk:
pdName: mongodb
fsType: ext4
定义名称和文件系统类型为 ext4,接着向 mongodb 写入数据:
[root@k8s-master ~]kubectl exec -it mongodb mongo
[root@mongodb ~]use mystore
[root@mongodb ~]db.foo.insert({name:'foo'})
[root@mongodb ~]db.foo.find()
重建 Pod 后读取上一个 Pod 保存的数据:
[root@k8s-master ~]kubectl delete pod mongodb
[root@k8s-master ~]kubectl create -f mongodb.yaml
[root@k8s-master ~]kubectl exec -it mongodb mongo
[root@mongodb ~]use mystore
[root@mongodb ~]db.foo.find()
如果数据仍然存在,则说明持久化成功。
其他持久化储存卷
根据不同的基础设施,可以选择使用不同类型的持久化存储卷。例如,在 Amazon 上可以使用 awsElasticBlockStore 卷,在 Microsoft Azure 上可以使用 azureFile 或 azureDisk 卷。下面是一个使用 AWS 的示例:
apiVersion: v1
kind: Pod
metadata:
name: mongodb
spec:
volumes:
- name: mongodb-data
awsElasticBlockStore:
volumeID: mongodb
fsType: ext4
...
对于 NFS 共享,只需要指定 NFS 服务器的 IP 地址和共享路径即可:
...
spec:
volumes:
- name: mongodb-data
nfs:
server: 1.2.3.4
path: /some/path
...
要了解每种卷类型所需的属性设置,可以查询 Kubernetes API 文档,或使用 kubectl explain
命令。
需要注意的是,将这些基础设施类型放在 Pod 的配置中意味着该 Pod 的设置与特定集群强耦合,这样无法在另一个 Pod 中重复使用相同的设置。因此,在设计和配置持久化存储时需要谨慎考虑。
持久卷和持久卷声明
在集群中为了使应用正常请求储存资源,同时避免处理基础设施细节,引入了两个新资源,分别是持久卷(PersistentVolume,PV)和持久卷声明(PersistentVolumeClaim,PVC)。
当集群用户需要在其 pod 中使用持久化储存时,首先创建持久卷声明清单,指定所需最低容量要求和访问模式,由 API 服务器分配持久卷并绑定到持久卷声明中。
持久卷声明可以当做 pod 中的一个卷来使用,其他用户不能使用相同的持久卷,除非先通过删除持久卷声明释放。
下图展示了 PV 和 PVC 的关系:
下面用 NFS 文件系统做示例。
安装 NFS 文件系统
在所有节点上进行安装 NFS 套件:
[root@server4-master ~]$ yum install nfs-utils rpcbind -y
[root@server4-master ~]$ systemctl enable --now nfs
Created symlink from /etc/systemd/system/multi-user.target.wants/nfs-server.service to /usr/lib/systemd/system/nfs-server.service.
在 NFS 服务器上启动服务:
[root@server4-master ~]$ systemctl enable --now rpcbind
配置服务器上的共享目录:
[root@server4-master ~]$ mkdir /srv/pv
[root@server4-master ~]$ chown nfsnobody:nfsnobody /srv/pv
[root@server4-master ~]$ chmod 755 /srv/pv
[root@server4-master ~]$ echo -e "/srv/pv *(rw,no_root_squash,sync)">/etc/exports
[root@server4-master ~]$ exportfs -r
[root@server4-master ~]$ exportfs
/srv/pv <world>
[root@server4-master ~]$ showmount -e 192.168.2.204
Export list for 192.168.2.204:
/srv/pv *
修改最大同时连接用户数:
[root@server4-master ~]$ echo "options sunrpc tcp_slot_table_entries=128" >> /etc/modprobe.d/sunrpc.conf
[root@server4-master ~]$ echo "options sunrpc tcp_max_slot_table_entries=128" >> /etc/modprobe.d/sunrpc.conf
[root@server4-master ~]$ sysctl -w sunrpc.tcp_slot_table_entries=128
sunrpc.tcp_slot_table_entries = 128
创建新的挂载点:
[root@server4-master ~]$ mkdir -p /srv/pv/pv001 /srv/pv/pv002
[root@server4-master ~]$ echo -e "/srv/pv/pv001 *(rw,no_root_squash,sync)">>/etc/exports
[root@server4-master ~]$ echo -e "/srv/pv/pv002 *(rw,no_root_squash,sync)">>/etc/exports
[root@server4-master ~]$ exportfs -r
[root@server4-master ~]$ systemctl restart rpcbind && systemctl restart nfs
[root@server4-master ~]$ showmount -e 192.168.2.204
Export list for 192.168.2.204:
/srv/pv/pv002 *
/srv/pv/pv001 *
/srv/pv *
创建持久卷
配置一个 1 GB 大小的持久卷,供 MongoDB 使用:
[root@server4-master ~]$ vi mongodb-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: mongodb-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
nfs:
path: /srv/pv
server: 192.168.2.204
[root@server4-master ~]$ kubectl create -f mongodb-pv.yaml
persistentvolume/mongodb-pv created
[root@server4-master ~]$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mongodb-pv 1Gi RWO,ROX Retain Available 4s
持久卷不属于任何命名空间,它和节点一样是集群层面的资源。
创建持久卷声明
如果 Pod 需要使用之前创建的持久卷,需要创建一个持久卷声明:
[root@server4-master ~]$ vi mongodb-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongodb-pvc
spec:
resources:
requests:
storage: 1Gi
accessModes:
- ReadWriteOnce
storageClassName: ""
[root@server4-master ~]$ kubectl create -f mongodb-pvc.yaml
persistentvolumeclaim/mongodb-pvc created
[root@server4-master ~]$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mongodb-pvc Bound mongodb-pv 1Gi RWO,ROX 4s
通过查看 PVC(持久卷声明)状态,可以确认 PVC 已经与相应的 PV(持久卷)绑定。其中访问模式的简写含义如下:
-
RWO(ReadWriteOnce):仅允许单个节点挂载读写。
-
ROX(ReadOnlyMany):允许多个节点挂载只读。
-
RWX(ReadWriteMany):允许多个节点挂载读写。
这里的节点指的是 Kubernetes 节点,而不是 Pod 的数量。
再次查看 PV 的状态:
[root@server4-master ~]$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mongodb-pv 1Gi RWO,ROX Retain Bound default/mongodb-pvc 12m
可以看到它已经被绑定到 PVC 的声明中。其中 CLAIM 中的 default 表示默认命名空间。虽然持久卷属于整个集群,但持久卷声明只能在特定的命名空间内创建。因此,持久卷和持久卷声明只能由同一命名空间内的 Pod 创建和使用。还可以使用选择器来为 PV 应用标签选择器。
使用持久卷声明
在 Pod 中使用持久卷时,需要在 Pod 的卷中引用 PVC 的名称:
[root@server4-master ~]$ vi mongodb-pod.yaml
apiVersion: v1
kind: Service
metadata:
name: mongodb
spec:
type: NodePort
ports:
- port: 27017
targetPort: 27017
nodePort: 30000
selector:
app: kubia
---
apiVersion: v1
kind: Pod
metadata:
name: mongodb
labels:
app: mongo
spec:
containers:
- image: mongo
name: mongodb
volumeMounts:
- name: mongodb-data
mountPath: /data/db
ports:
- containerPort: 27017
protocol: TCP
volumes:
- name: mongodb-data
persistentVolumeClaim:
claimName: mongodb-pvc
[root@server4-master ~]$ kubectl create -f mongodb-pod.yaml
pod/mongodb created
请注意,虽然 PV 是全局资源,但 PVC 属于特定命名空间,只有同一命名空间内的 Pod 才能调用。
删除持久卷
在持久卷正在被使用时,不能直接删除它,需要先删除使用该持久卷的 Pod 和 PVC。持久卷的空间释放处理机制有三种:
-
Retain(保留)
删除 PV 后,根据设置的释放规则(Retain),硬盘中的文件仍然存在。重新创建 PV、PVC 和 Pod 后,文件内容和上一次运行时一样。使用 Retain 手动回收策略只能通过删除和重建持久卷来恢复可用状态。
-
Recycle(回收)
删除 PVC 后,会删除卷的内容,并使卷可用于再次声明。
-
Delete(删除)
删除底层存储。
动态化持久卷
在 Kubernetes 中,可以通过创建持久卷配置并定义一个或多个 StorageClass 对象,实现每次通过持久卷声明请求时自动创建一个新的持久卷。获取动态持久卷的步骤如下图所示:
不同的后端存储需要不同的置备程序(provisioner)。以 NFS 文件系统为例,首先需要部署 nfs-client:
[root@k8s-master html]$ echo "/root/3/html *(rw,sync,no_root_squash)" >> /etc/exports
[root@k8s-master ~]$ vi nfs-dp.yaml
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: nfs-client-provisioner
spec:
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccount: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: jmgao1983/nfs-client-provisioner
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: mynfs
- name: NFS_SERVER
value: 192.168.2.113
- name: NFS_PATH
value: /srv/pv
volumes:
- name: nfs-client-root
nfs:
server: 192.168.2.113
path: /srv/pv
[root@k8s-master ~]$ kubectl create -f nfs-dp.yaml
deployment.extensions/nfs-provisioner created
[root@k8s-master ~]$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nfs-provisioner 1/1 1 1 29s
然后创建 StorageClass 资源:
[root@k8s-master ~]$ vi mongodb-pv-sc.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: mynfs
[root@k8s-master ~]$ kubectl create -f mongodb-pv-sc.yaml
storageclass.storage.k8s.io/fast created
[root@k8s-master ~]$ kubectl get sc
NAME PROVISIONER AGE
fast mynfs 9s
创建一个 Pod 引用 StorageClass:
[root@k8s-master ~]$ vi nginx.yaml
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx1"
replicas: 2
template:
metadata:
labels:
app: nginx1
spec:
containers:
- name: nginx1
image: nginx:latest
volumeMounts:
- mountPath: "/mnt"
name: test
volumeClaimTemplates:
- metadata:
name: test
annotations:
volume.beta.kubernetes.io/storage-class: "fast"
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
"nginx.yaml" [New] 28L, 556C written
[root@k8s-master ~]$ kubectl create -f nginx.yaml
statefulset.apps/web created
创建 ServiceAccount 和角色:
[root@k8s-master ~]$ vi serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
[root@k8s-master ~]$ kubectl create -f serviceaccount.yaml
serviceaccount/nfs-client-provisioner created
[root@k8s-master ~]$ kubectl get sa
NAME SECRETS AGE
default 1 3d14h
nfs-client-provisioner 1 16s
[root@k8s-master ~]$ vi clusterrole.yaml
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["services", "endpoints"]
verbs: ["get"]
- apiGroups: ["extensions"]
resources: ["podsecuritypolicies"]
resourceNames: ["nfs-provisioner"]
verbs: ["use"]
"clusterrole.yaml" [New] 24L, 735C written
[root@k8s-master ~]$ vi clusterrolebinding.yaml
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-provisioner-runner
apiGroup: rbac.authorization.k8s.io
[root@k8s-master ~]$ kubectl create -f clusterrole.yaml -f clusterrolebinding.yaml
clusterrole.rbac.authorization.k8s.io/nfs-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-provisioner created
最后,验证是否会自动创建新的 PV:
[root@k8s-master ~]$ kubectl get pv |grep web
pvc-d2423554-6b6e-4c65-9209-e739abe7c653 1Gi RWO Delete Bound default/test-web-0 fast 2m28s
pvc-d430fa25-d4e4-4971-961f-80be40b8d9dc 1Gi RWO Delete Bound default/test-web-1 fast 2m12s
[root@k8s-master ~]$ kubectl get pvc |grep web
test-web-0 Bound pvc-d2423554-6b6e-4c65-9209-e739abe7c653 1Gi RWO fast 13m
test-web-1 Bound pvc-d430fa25-d4e4-4971-961f-80be40b8d9dc 1Gi RWO fast 2m36s
[root@k8s-master ~]$ kubectl get storageclass
NAME PROVISIONER AGE
fast mynfs 19m
[root@k8s-master ~]$ kubectl get pod |grep web
web-0 1/1 Running 0 8m43s
web-1 1/1 Running 0 3m29s
[root@k8s-master ~]$ kubectl scale statefulset web --replicas=3
statefulset.apps/web scaled
[root@k8s-master ~]$ kubectl get pod |grep web
web-0 1/1 Running 0 9m32s
web-1 1/1 Running 0 4m18s
web-2 0/1 ContainerCreating 0 4s
[root@k8s-master ~]$ ll /srv/pv/
total 0
drwxrwxrwx 2 root root 6 Jul 22 17:34 default-test-web-0-pvc-d2423554-6b6e-4c65-9209-e739abe7c653
drwxrwxrwx 2 root root 6 Jul 22 17:35 default-test-web-1-pvc-d430fa25-d4e4-4971-961f-80be40b8d9dc
drwxrwxrwx 2 root root 6 Jul 22 17:39 default-test-web-2-pvc-ffc2b763-2dd8-4a5b-a565-86dd416eba36