6-volume

卷: 将磁盘挂载到容器


介绍卷

  • 卷被定义为 Pod 的一部分,且和 Pod 拥有相同的生命周期。在重启容器之后,新容器可以识别前一个容器写入卷的所有文件。 另外,如果一个 Pod 包含有多个容器,那么这个卷可以同时被 Pod 内的所有容器使用。

  • 填充或装入卷的过程是在 Pod 内的容器启动之前执行的。

  • 卷被绑定到 Pod 的生命周期中,只有在 Pod 存在时才会存在,但是取决于卷的类型,某些类型下,即使在 Pod 和 卷消失之后,卷的文件也可能保持和原样,并可以挂载到新的卷中。

通过卷在容器之间共享数据

使用 emptyDir 卷

emptyDir卷提供一个空目录,供 pod 内的应用程序写入。emptyDir 卷的生命周期和 Pod 的生命周期相关联,所以当删除 Pod 时,卷的内容就会丢失。

  • 构建 fortune 镜像

Dockerfile 及容器入口文件

docker build -t bwangel/fortune .
docker push bwangel/fortune

emptyDir 卷是在承载 Pod 的工作节点的实际磁盘上创建的,因此其性能取决与节点磁盘的类型,我们也可以通知 k8s 在 tmfs 文件系统(存在内存而非磁盘)上创建 emptyDir。只需要修改卷的 medium 设置即可:

volumes:
    - name: html
      emptyDir:
        medium: Memory

通过端口转发访问 Pod

  • fortune-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: fortune
spec:
  containers:
  - image: bwangel/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
  volumes:
  - name: html
    emptyDir: {}

下面的命令创建了一个 fortune 的 Pod,并设置了本地端口8080 转发到 fortune Pod 的 80 端口

$ k create -f fortune-pod.yaml
$ k port-forward fortune 8080:8080
$ http localhost:8080
HTTP/1.1 200 OK
Accept-Ranges: bytes
Connection: keep-alive
Content-Length: 24
Content-Type: text/html
Date: Tue, 05 Jan 2021 00:38:47 GMT
ETag: "5ff3b512-18"
Last-Modified: Tue, 05 Jan 2021 00:38:42 GMT
Server: nginx/1.19.6

You now have Asian Flu.

通过 LoadBalancer 服务来访问 Pod

  • webview-svc.yaml
apiVersion: v1
kind: ReplicationController
metadata:
  name: webview
spec:
  replicas: 3
  template:
    metadata:
      name: webview-v1
      labels:
        app: webview
    spec:
      containers:
      - image: bwangel/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
      volumes:
      - name: html
        emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: webview
spec:
  type: LoadBalancer
  selector:
    app: webview
  ports:
    - port: 80
      targetPort: 80

下面的命令创建了一个名为 webview 的 Loadbalancer 服务和一个名为 webview 的 RC,我们可以通过 LoadBalancer 的外部IP 访问到 Pod。

ø> k create -f 6chapter/webview-svc.yaml
ø> k get svc webview
NAME      TYPE           CLUSTER-IP    EXTERNAL-IP    PORT(S)        AGE
webview   LoadBalancer   10.3.242.84   34.84.233.43   80:31870/TCP   9m21s
ø> http 34.84.233.43
HTTP/1.1 200 OK
Accept-Ranges: bytes
Connection: keep-alive
Content-Length: 51
Content-Type: text/html
Date: Tue, 05 Jan 2021 00:43:12 GMT
ETag: "5ff3b61c-33"
Last-Modified: Tue, 05 Jan 2021 00:43:08 GMT
Server: nginx/1.19.6

You'll feel much better once you've given up hope.

访问工作节点文件系统上的文件

hostPath 卷可以让 Pod 读取/写入 工作节点的文件系统。

从下面的例子中可以看到,系统的 fluentd-gke-jvbcc Pod 使用 hostPath 卷来读取工作节点上的 /var/log/var/lib/docker/containers 等目录。

ø> kubectl describe pod fluentd-gke-jvbcc --namespace kube-system
Name:                 fluentd-gke-jvbcc
...
Volumes:
  varrun:
    Type:          HostPath (bare host directory volume)
    Path:          /var/run/google-fluentd
    HostPathType:
  varlog:
    Type:          HostPath (bare host directory volume)
    Path:          /var/log
    HostPathType:
  varlibdockercontainers:
    Type:          HostPath (bare host directory volume)
    Path:          /var/lib/docker/containers
    HostPathType:
  input-config-volume:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      fluentd-gke-input-config-v1.2.9
    Optional:  false
  config-volume:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      fluentd-gke-config-v1.2.9
    Optional:  false
  fluentd-gke-token-299pv:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  fluentd-gke-token-299pv
    Optional:    false
...

使用持久化存储

# 创建 GCE 持久磁盘存储
gcloud compute disks create --size=10Gib --zone=asia-northeast1-a mongodb

# 下面的命令可以查看 Pod 被调度到了哪一个节点上
ø> k get pod -o wide gitrepo-volume-pod
NAME                 READY   STATUS    RESTARTS   AGE   IP         NODE                                   NOMINATED NODE   READINESS GATES
gitrepo-volume-pod   1/1     Running   0          31h   10.0.0.7   gke-kubia-default-pool-83589229-zfw4   <none>           <none>
  • mongodb-pod-gcepd.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mongodb
spec:
  volumes:
  - name: mongodb-data
    # 声明卷的类型和它使用的文件系统
    gcePersistentDisk:
      pdName: mongodb
      fsType: ext4
  containers:
  - image: mongo
    name: mongodb
    # 根据卷的名字将卷挂载到容器上
    volumeMounts:
    - name: mongodb-data
      mountPath: /data/db
    ports:
    - containerPort: 27017
      protocol: TCP
  • 操作实录
# 创建 mongodb Pod
ø> k create -f 6chapter/mongodb-pod-gcepd.yaml
pod/mongodb created
# 查看 Pod 状态,已经成功运行了
ø> k get pods mongodb
NAME                 READY   STATUS    RESTARTS   AGE
mongodb              1/1     Running   0          60s
# 登录 Mongodb 数据库
ø> k exec -it mongodb -- mongo
> use mystore
switched to db mystore
# 向数据库中写入数据
> db.foo.insert({'name': 'foo'})
WriteResult({ "nInserted" : 1 })
> db.foo.find()
{ "_id" : ObjectId("5ff64ad47af44ac9535a9abe"), "name" : "foo" }
>
bye
# 删除 Pod 并重建
ø> k delete pod mongodb
pod "mongodb" deleted
ø> k create -f 6chapter/mongodb-pod-gcepd.yaml
pod/mongodb created
# 重新登录 MongoDB 进行查找,发现之前创建的记录还在
ø> k exec -it mongodb -- mongo
> use mystore
switched to db mystore
> db.foo.find()
{ "_id" : ObjectId("5ff64ad47af44ac9535a9abe"), "name" : "foo" }
>
bye

从底层技术解耦 Pod

持久卷和持久卷声明的工作模式

Persistent Volume(PV),持久卷,管理员声明的一种和存储系统绑定的资源。

持久卷不属于任何命名空间,它和节点一样,是集群层面的资源。

Persitent Volume Claim(PVC),持久卷声明,开发人员声明的一种持久卷资源请求,K8S 将会根据 PVC 中的声明,分配合适的持久卷资源和 PVC 绑定起来。

PVC 状态说明:

  • RWO – ReadWriteOnce 仅允许单个节点的挂载读写
  • ROX – ReadOnlyMany 允许多个节点的挂载只读
  • RWX – ReadWriteMany 允许多个节点挂载读写这个卷

注意: RWO, ROX, RWX 涉及可以同时使用卷的 工作节点 的数量而非 Pod 的数量。

PVC 只能在特定命名空间中创建,且只能被这个命名空间的 Pod 使用。

它们的工作模式:

创建持久卷

  • mongodb-pv-gcepd.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mongodb-pv
spec:
  capacity:
    storage: 10Gi
  # 声明持久卷的访问模式,单节点读写和多节点只读
  accessModes:
  - ReadWriteOnce
  - ReadOnlyMany
  # pvc 删除后,保留 pv
  persistentVolumeReclaimPolicy: Retain
  gcePersistentDisk:
    pdName: mongodb
    fsType: ext4
ø> k create -f 6chapter/mongodb-pv-gcepd.yaml
persistentvolume/mongodb-pv created
# 查看持久卷,可以看到持久卷的状态是 Available
ø> k get pv
NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
mongodb-pv   10Gi       RWO,ROX        Retain           Available                                   3s

创建持久卷声明

  • mongodb-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongodb-pvc
spec:
  # 声明需要的 pv 的特征,存储是1Gi,访问模式是单节点读写
  resources:
    requests:
      storage: 1Gi
  accessModes:
  - ReadWriteOnce
  storageClassName: ""
# 创建 pvc
ø> k create -f 6chapter/mongodb-pvc.yaml
persistentvolumeclaim/mongodb-pvc created
ø> k get pvc
NAME          STATUS   VOLUME       CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mongodb-pvc   Bound    mongodb-pv   10Gi       RWO,ROX                       3s
# 查看pv,可以看到它的状态已经变成了 Bound,且被绑定到了 default/mongodb-pvc 上
ø> k get pv
NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                 STORAGECLASS   REASON   AGE
mongodb-pv   10Gi       RWO,ROX        Retain           Bound    default/mongodb-pvc                           3m51s

在 Pod 中使用 持久卷声明

  • mongodb-pod-pvc.yaml
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
  # 将刚刚创建的 PVC 声明为一种卷
  volumes:
  - name: mongodb-data
    persistentVolumeClaim:
      claimName: mongodb-pvc
# 创建 Pod
ø> k create -f 6chapter/mongodb-pod-pvc.yaml
pod/mongodb created
# 登录 mongodb,查询我们之前创建的数据
ø> k exec -it mongodb -- mongo
> use mystore
switched to db mystore
# 可以看到之前创建的数据仍然存在
> db.foo.find()
{ "_id" : ObjectId("5ff64ad47af44ac9535a9abe"), "name" : "foo" }
>
bye

删除持久卷声明

ø> k delete pod mongodb
pod "mongodb" deleted
2598 (lazyubuntu) ~/Github/k8s-in-actions (master)
ø> k delete pvc mongodb-pvc
persistentvolumeclaim "mongodb-pvc" deleted
ø> k get pv
NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM                 STORAGECLASS   REASON   AGE
mongodb-pv   10Gi       RWO,ROX        Retain           Released   default/mongodb-pvc                           12m

当我们删除 PVC 之后,可以看到 PV 没有变成可用的状态,而是变成了 Released 状态。这是因为我们在创建 PV 时声明它的 RECLAIM POLICYRetain 。此时需要系统管理员来确定 PV 中的数据是否还可用,手动回收卷并更改其状态,然后这个PV才能够被重新使用。

持久卷的动态配置

StorageClass 是一种集群资源,它绑定了一个 制备程序 ,可以根据 PVC 的申请,来动态地创建 PV 并绑定到实际的存储上。

  • 持久卷动态配置的完整图示

  • k8s 中有个默认的存储类 (StorageClass) standard,如果 PVC 声明中没有指定存储类,那就使用这个默认的存储类来创建 PV 。
ø> k get sc standard -o yaml
allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  annotations:
    # 这个注解声明它是默认的存储类
    storageclass.kubernetes.io/is-default-class: "true"
  creationTimestamp: "2021-01-03T16:26:12Z"
  labels:
    addonmanager.kubernetes.io/mode: EnsureExists
  name: standard
  resourceVersion: "263"
  selfLink: /apis/storage.k8s.io/v1/storageclasses/standard
  uid: 278b918a-466e-454b-bab4-f25833314feb
parameters:
  type: pd-standard
provisioner: kubernetes.io/gce-pd  # 这是它所配置的制备程序
reclaimPolicy: Delete  # 声明创建的 PV 的使用策略
volumeBindingMode: Immediate
2020年10月11日 / 14:47