6.2 通过卷在容器之间共享数据
尽管一个卷即使被单个容器使用也可能很有用,但是我们首先要关注它是如何用于在一个pod的多个容器之间共享数据的。
6.2.1 使用emptyDir卷
最简单的卷类型是emptyDir卷,所以作为第一个例子让我们看看如何在pod中定义卷。顾名思义,卷从一个空目录开始,运行在pod内的应用程序可以写入它需要的任何文件。因为卷的生存周期与pod的生存周期相关联,所以当删除pod时,卷的内容就会丢失。
一个emptyDir卷对于在同一个pod中运行的容器之间共享文件特别有用。但是它也可以被单个容器用于将数据临时写入磁盘,例如在大型数据集上执行排序操作时,没有那么多内存可供使用。数据也可以写入容器的文件系统本身(还记得容器的顶层读写层吗?),但是这两者之间存在着细微的差别。容器的文件系统甚至可能是不可写的(我们将在书的末尾讨论这个问题),所以写到挂载的卷可能是唯一的选择。
在pod中使用emptyDir卷
让我们重新回顾一下前面的例子,其中web服务器、内容代理和日志转换器共享两个卷,但让我们简化一下,现在将构建一个仅有web服务器容器内容代理和单独HTML的卷的pod。
我们将使用Nginx作为Web服务器和UNIX fortune命令来生成HTML内容,fortune命令每次运行时都会输出一个随机引用,可以创建一个脚本每10秒调用一次执行,并将其输出存储在index.html中,在Docker Hub上可以找到一个现成的Nginx镜像,但是需要自己创建fortune镜像,或者使用笔者已经构建并推送到Docker Hub luksa/Fortune下的镜像。如果你希望了解如何构建Docker镜像,请参考下面的注解(构建fortune容器镜像)。
构建fortune容器镜像
这里描述如何创建镜像,创建一个名为fortune的新目录,然后在其中创建一个具有以下内容的fortuneloop.sh的shell脚本:
#!/bin/bash
trap "exit" SIGINT
mkdir /var/htdocs
while:
do
echo $(date) Writing fortune to /var/htdocs/index.html
/usr/games/fortune > /var/htdocs/index.html
sleep 10
done
然后,在同一个目录中,创建一个名为Dockerfile的文件,其中包含以下内容:
FROM ubuntu:lastest
RUN apt-get update; apt-get -y install fortune
ADD fortuneloop.sh /bin/fortuneloop.sh
ENTRYPOINT /bin/fortuneloop.sh
该镜像基于ubuntu:latest镜像,默认情况下不包括fortune二进制文件。这就是为什么在Dockerfile的第二行中,需要使用apt-get安装它的原因。之后,可以向镜像的/bin文件夹中添加fortuneloop.sh脚本。在Dockerfile的最后一行中,指定镜像启动时执行fortuneloop.sh脚本。
准备好这两个文件之后,使用以下两个命令(用自己的Docker Hub用户ID替换luksa)构建镜像并上传到Docker Hub:
$ docker build -t luksa/fortune .
$ docker push luksa/fortune
创建pod
现在有两个镜像需要运行在pod上,是时候创建pod的manifest了,创建一个名为fortune-pod.yaml的文件,其内容包含在下面的代码清单中。
代码清单6.1 一个pod中有两个共用同一个卷的容器 :fortune-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: fortune
spec:
containers:
- image: luksa/fortune #第一个容器
name: html-generator
volumeMounts:
- name: html
mountPath: /var/htdocs #名字为html的卷挂载在容器的/var/htdocs中
- image: nginx:alpine #第二个容器
name: web-server
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html #相同的卷挂载在 /usr/share/nginx/html 上,设置为只读
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html #名为html的单独emptyDir卷,挂载在上面两个容器
emptyDir: {}
pod包含两个容器和一个挂载在两个容器中的共用的卷,但在不同的路径上。当html-generator容器启动时,它每10秒启动一次fortune命令输出到 /var/htdocs/index.html
文件。因为卷是在 /var/htdocs
上挂载的,所以index.html文件被写入卷中,而不是容器的顶层。一旦web-server容器启动,它就开始为 /usr/share/nginx/html
目录中的任意HTML文件提供服务(这是Nginx服务的默认服务文件目录)。因为我们将卷挂载在那个确切的位置,Nginx将为运行fortune循环的容器输出的index.html文件提供服务。最终的效果是,一个客户端向pod上的80端口发送一个HTTP请求,将接收当前的fortune消息作为响应。
查看pod状态
为了查看fortune消息,需要启用对pod的访问,可以尝试将端口从本地机器转发到pod来实现:
$ kubectl port-forward fortune 8080:80
注意 作为练习,还可以通过服务来访问该pod,而不是单纯使用端口转发。
现在可以通过本地计算机的8080端口来访问Nginx服务器。通过执行curl命令:
$ curl http://localhost:8080
如果等待几秒发送另一个请求,则应该会接收到另一条信息。通过组合两个容器,就创建了一个简单的应用,通过这个应用可以观察到卷是如何将两个容器组合在一起,并分别增强它们各自的功能的。
指定用于EMPTYDIR的介质
作为卷来使用的emptyDir,是在承载pod的工作节点的实际磁盘上创建的,因此其性能取决于节点的磁盘类型。但我们可以通知Kubernetes在tmfs文件系统(存在内存而非硬盘)上创建emptyDir。因此,将emptyDir的medium设置为Memory:
volumes:
- name: cache-volume
emptyDir:
medium: Memory #emptyDir的文件将会存储在内存中
emptyDir卷是最简单的卷类型,但是其他类型的卷都是在它的基础上构建的,在创建空目录后,它们会用数据填充它。有一种称作gitRepo的卷类型,我们将在下面进行介绍。
6.2.2 使用Git 仓库作为存储卷(这部分官方已经废弃,不学)
6.3 访问工作节点文件系统上的文件
大多数pod应该忽略它们的主机节点,因此它们不应该访问节点文件系统上的任何文件。但是某些系统级别的pod(切记,这些通常由DaemonSet管理)确实需要读取节点的文件或使用节点文件系统来访问节点设备。Kubernetes通过hostPath卷实现了这一点。
6.3.1 介绍hostPath卷
hostPath卷指向节点文件系统上的特定文件或目录(请参见图6.4)。在同一个节点上运行并在其hostPath卷中使用相同路径的pod可以看到相同的文件。
hostPath卷将工作节点上的文件或目录挂载到容器的文件系统中.
hostPath卷是我们介绍的第一种类型的持久性存储,因为emptyDir卷的内容都会在pod被删除时被删除,而hostPath卷的内容则不会被删除。如果删除了一个pod,并且下一个pod使用了指向主机上相同路径的hostPath卷,则新pod将会发现上一个pod留下的数据,但前提是必须将其调度到与第一个pod相同的节点上。
如果你正在考虑使用hostPath卷作为存储数据库数据的目录,请重新考虑。因为卷的内容存储在特定节点的文件系统中,所以当数据库pod被重新安排在另一个节点时,会找不到数据。这解释了为什么对常规pod使用hostPath卷不是一个好主意,因为这会使pod对预定规划的节点很敏感。
6.3.2 检查使用hostPath卷的系统pod
让我们看看如何正确地使用hostPath卷。我们先看一下是否有系统层面的pod已经在使用这种类型的卷,而不是直接创建一个新pod。你可能还记得前面一章中,有几个这样的pod正在kube-system命名空间中运行,再次列出它们:
$ kubectl get pods --namespace kube-system
选择第一个并查看其使用的卷大小(在下面的代码清单中显示)。
代码清单6.3 使用hostPath卷访问节点日志的pod
$ k describe po etcd-minikube -n kube-system
...
Volumes:
etcd-certs:
Type: HostPath (bare host directory volume)
Path: /var/lib/minikube/certs/etcd
HostPathType: DirectoryOrCreate
etcd-data:
Type: HostPath (bare host directory volume)
Path: /var/lib/minikube/etcd
HostPathType: DirectoryOrCreate
pod使用两个hostPath卷来访问节点的 /var/lib/minikube/certs/etcd
和 /var/lib/minikube/etcd
目录。也许你认为在第一次尝试时就找到一个在使用hostPath卷的pod很幸运,但实际上并不是这样(至少在GKE不是)。检查其他文件,将能看到大多数情况下都使用这种类型的卷来访问节点的日志文件、kubeconfig(Kubernetes配置文件)或CA证书。
如果检查其他pod,则会看到其中没有一个使用hostPath卷来存储自己的数据,都是使用这种卷来访问节点的数据。但是,正如我们在本章后面将看到的,hostPath卷通常用于尝试单节点集群中的持久化存储,譬如Minikube创建的集群。继续阅读,我们将了解即使在多节点集群中也应该使用哪些类型的卷来正确地存储持久化数据。
提示 请记住仅当需要在节点上读取或写入系统文件时才使用hostPath,切勿使用它们来持久化跨pod的数据。