k8s data storage

Keywords: Linux MySQL Kubernetes AWS Docker

How Kubernetes manages storage resources.

First we will learn about Volume and how Kubernetes provides storage for containers in clusters through Volume; then we will practice several commonly used Volume types and understand their respective application scenarios; finally, we will discuss how Kubernetes separates cluster administrators from cluster users through Persistent Volume and Persistent Volume Claim, and practice Vo. Static and dynamic supply of lume.

Volume

In this section, we discuss Kubernetes'storage model Volume and learn how to map various persistent storage to containers.

We often say that containers and pods are short-lived.
The implication is that their life cycles may be short and they will be destroyed and created frequently. When the container is destroyed, the data stored in the file system inside the container will be cleared.

To persist the container's data, you can use Kubernetes Volume.

Volume's life cycle is independent of containers. Containers in Pod may be destroyed and rebuilt, but Volume will be retained.

In essence, Kubernetes Volume is a directory, similar to Docker Volume. When Volume is mount ed to Pod, all containers in Pod can access the Volume. Kubernetes Volume also supports a variety of backend types, including emptyDir, hostPath, GCE Persistent Disk, AWS Elastic Block Store, NFS, Ceph, etc. The complete list is available for reference. https://kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes

Volume provides an abstraction of various backend s. When using Volume to read and write data, the container does not need to care whether the data is stored in the file system of the local node or on the cloud hard disk. For it, all types of Volume are just a directory.

We'll start with the simplest emptyDir, Kubernetes Volume.

emptyDir

EmptyDir is the most basic Volume type. As its name indicates, an emptyDir Volume is an empty directory on the Host.

emptyDir Volume is persistent for containers, but not for Pods. When a Pod is deleted from a node, the Volume content is also deleted. But if only the container was destroyed and Pod was still there, Volume would not be affected.

That is to say, the life cycle of emptyDir Volume is the same as that of Pod.

All containers in Pod can share Volume, and they can specify their mount paths. Following is an example to practice emptyDir. The configuration file is as follows:

apiVersion: v1
kind: Pod
metadata:
  name: producer-consumer
spec:
  containers:
  - image: busybox
    name: producer
    volumeMounts:
    - mountPath: /producer_dir
      name: shared-volume
    args:
    - /bin/sh
    - -c
    - echo "hello world"> /producer_dir/hello ; sleep 30000

  - image: busybox
    name: consumer
    volumeMounts:
    - mountPath: /consumer_dir
      name: shared-volume
    args:
    - /bin/sh
    - -c
    - cat /consumer_dir/hello ; sleep 30000

  volumes:
  - name: shared-volume
    emptyDir: {}

Here we simulate a producer-consumer scenario. Pod has two containers, producer and consumer, which share a Volume. Producer is responsible for writing data to Volume, while consumer reads data from Volume.

The bottom volume of the file defines a Volume shared-volume of emptyDir type.

(2) The producer container will share-volume mount to the / producer_dir directory.

(3) The producer writes the data to the file hello through echo.

(4) The consumer container will share-volume mount to the / consumer_dir directory.

consumer reads data from the file hello through cat.

Execute the following command to create a Pod:

# kubectl apply -f emptyDir.yaml
pod/producer-consumer created

# kubectl get pod
NAME                READY   STATUS    RESTARTS   AGE
producer-consumer   2/2     Running   0          87s

# kubectl logs producer-consumer consumer 
hello world

kubectl logs show that the container consumer successfully reads the data written by producer, verifying that the two containers share emptyDir Volume.

Because emptyDir is a directory in the Docker Host filesystem, its effect is equivalent to executing docker run-v/producer_dir and docker run-v/consumer_dir. Looking at the container configuration details through docker inspect, we found that both containers mount ed the same directory:

 {
                "Type": "bind",
                "Source": "/var/lib/kubelet/pods/188767a3-cf18-4bf9-a89b-b0c4cf4124bb/volumes/kubernetes.io~empty-dir/shared-volume",
                "Destination": "/consumer_dir",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }

"Mounts": [
            {
                "Type": "bind",
                "Source": "/var/lib/kubelet/pods/188767a3-cf18-4bf9-a89b-b0c4cf4124bb/volumes/kubernetes.io~empty-dir/shared-volume",
                "Destination": "/producer_dir",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            },

Here/var/lib/kubelet/pods/3e6100eb-a97a-11e7-8f72-0800274451ad/volumes/kubernetes.io~empty-dir/shared-volume is the real path of emptyDir on Host.

EmptyDir is a temporary directory created on Host. Its advantage is that it can easily provide shared storage for containers in Pod without additional configuration. But it doesn't have persistence, and if Pod doesn't exist, emptyDir doesn't exist. Based on this feature, emptyDir is particularly suited for situations where containers in Pod need temporary shared storage space, such as the previous producer-consumer use case.

hostPath Volume.

The function of hostPath Volume is to mount the existing directory in the Docker Host file system to the container of Pod. Most applications do not use hostPath Volume because it actually increases the coupling between Pod and nodes, limiting the use of Pod. However, applications that need access to Kubernetes or Docker internal data (configuration files and binary libraries) need to use hostPath.

For example, kube-apiserver and kube-controller-manager are such applications, through which

kubectl edit --namespace=kube-system pod kube-apiserver-k8s-master
To see the configuration of kube-apiserver Pod, the following is the relevant section of Volume:

   volumeMounts:
    - mountPath: /etc/ssl/certs
      name: ca-certs
      readOnly: true
    - mountPath: /etc/pki
      name: etc-pki
      readOnly: true
    - mountPath: /etc/kubernetes/pki
      name: k8s-certs
      readOnly: true

  volumes:
  - hostPath:
      path: /etc/ssl/certs
      type: DirectoryOrCreate
    name: ca-certs
  - hostPath:
      path: /etc/pki
      type: DirectoryOrCreate
    name: etc-pki
  - hostPath:
      path: /etc/kubernetes/pki
      type: DirectoryOrCreate
    name: k8s-certs

Three hostPath volume k8s, certs and PKI are defined, which correspond to the Host directory / etc/kubernetes, / etc/ssl/certs and / etc/pki, respectively.

If the Pod is destroyed, the directory corresponding to the hostpath will also be preserved. From this point of view, the persistence of hostpath is better than emptyDir. But once Host crashes, hostPath can't be accessed.

A truly persistent Volume.

External Storage Provider

If Kubernetes is deployed on public clouds such as AWS, GCE, Azure, you can directly use the cloud hard disk as Volume. Here is an example of AWS Elastic Block Store:

To use ESB volume in Pod, you must first create it in AWS and then refer to it through volume-id. The use of other cloud hard drives can be referred to the official documents of various public cloud vendors.

Kubernetes Volume can also use mainstream distributed storage, such as Ceph, GlusterFS, etc. the following is an example of Ceph:

Ceph's / some/path/in/side/cephfs directory is mount ed to the container path / test-ceph.

Compared with emptyDir and hostPath, the biggest feature of these Volume types is that they do not rely on Kubernetes. Volume's underlying infrastructure is managed by an independent storage system, separated from the Kubernetes cluster. After data is persisted, even the entire Kubernetes crash will not be compromised.

Of course, storage systems such as operations and maintenance are usually not a simple task, especially when there are high requirements for reliability, high availability and scalability.

Volume provides a very good data persistence solution, but there are still shortcomings in manageability. In the next section, we'll learn about storage solutions with more executive rationality: Persistent Volume & Persistent Volume Claim.

PV & PVC

Volume provides a very good data persistence solution, but there are still shortcomings in manageability.

Take the previous AWS EBS example. To use Volume, Pod must know the following information beforehand:

  • Currently Volume comes from AWS EBS.
  • EBS Volume has been created beforehand and knows the exact volume-id.

Pod is usually maintained by the application developer, while Volume is usually maintained by the administrator of the storage system. Developers need to get the above information:

  • Or ask the administrator.
  • Either they are administrators themselves.

This leads to a management problem: the responsibilities of application developers and system administrators are coupled. It is acceptable if the system is small or for a development environment. However, when the cluster size becomes larger, especially for the generating environment, considering efficiency and security, this has become a problem that must be solved.

Kubernetes's solution is Persistent Volume and Persistent Volume Claim.

Persistent volume (PV) is a piece of storage space in the external storage system, which is created and maintained by the administrator. Like Volume, PV is persistent and lifecycle independent of Pod.

Persistent volume claim (PVC) is a claim for PV. PVC is usually created and maintained by ordinary users. When storage resources need to be allocated to Pod, users can create a PVC that specifies the size of storage resources and access modes (such as read-only), and Kubernetes will find and provide PVs that meet the requirements.

With Persistent Volume Claim, users only need to tell Kubernetes what kind of storage resources they need, without having to care about where the real space is allocated and how to access the underlying details. The underlying information of the Storage Provider is handled by the administrator, who should only care about the details of creating Persistent Volume.

Kubernetes supports a variety of Persistent Volumes, such as AWS EBS, Ceph, NFS, etc. For a complete list, see https://kubernetes.io/docs/concepts/storage/persistent-volumes/#types-of-persistent-volumes

NFS PersistentVolume

Practice PV and PVC through NFS.

As a preparation, we have built an NFS server on the k8s-master node with the directory / nfsdata:

[root@k8s-master ~]# showmount -e
Export list for k8s-master:
/nfsdata 192.168.168.0/24

Next, create a PV mypv1 configuration file nfs-pv1.yml as follows:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: mypv1
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: nfs
  nfs:
    path: /nfsdata/pv1
    server: 192.168.168.10

(1) Capacity specifies that the capacity of PV is 1G.

(2) accessModes specifies the access mode as ReadWriteOnce, and supports the following access modes:
ReadWriteOnce-PV can mount to a single node in read-write mode.
ReadOnlyMany-PV can mount to multiple nodes in read-only mode.
ReadWriteMany-PV can mount to multiple nodes in read-write mode.

(3) The persistent Volume Reclaim Policy specifies that when the PV recovery strategy is Recycle, the supporting strategies are:
Retain - Manual recycling by administrators is required.
Recycle --- Clear the data in PV, the effect is equivalent to executing rm-rf/the volume/*.
Delete - Delete the corresponding storage resources on Storage Provider, such as AWS EBS, GCE PD, Azure Disk, OpenStack Cinder Volume, etc.

Storage ClassName specifies that the class of PV is nfs. It is equivalent to setting up a classification for PV. PVC can specify the PV of the corresponding class to apply for.

(5) Specify the corresponding directory of PV on the NFS server.

Create mypv1:

[root@k8s-master ~]# kubectl apply -f nfs-pv1.yml 
persistentvolume/mypv1 created
[root@k8s-master ~]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
mypv1   1Gi        RWO            Recycle          Available           nfs                     7s

STATUS is Available, indicating that mypv1 is ready and can be applied for by PVC.

Next, create PVC mypvc1. The configuration file nfs-pvc1.yml is as follows:

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: mypvc1
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs

PVC is very simple, just specify the PV capacity, access mode and class.

Create mypvc1:

[root@k8s-master ~]# kubectl apply -f nfs-pvc1.yml 
persistentvolumeclaim/mypvc1 created
[root@k8s-master ~]# kubectl get pvc
NAME     STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mypvc1   Bound    mypv1    1Gi        RWO            nfs            6s
[root@k8s-master ~]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM            STORAGECLASS   REASON   AGE
mypv1   1Gi        RWO            Recycle          Bound    default/mypvc1   nfs                     2m18s
[root@k8s-master ~]# 

From the output of kubectl get pvc and kubectl get pv, we can see that mypvc1 has Bound to mypv1, and the application is successful.

Next you can use storage in Pod. The Pod configuration file pod1.yml is as follows:

kind: Pod
apiVersion: v1
metadata:
  name: mypod1
spec:
  containers:
    - name: mypod1
      image: busybox
      args:
      - /bin/sh
      - -c
      - sleep 30000
      volumeMounts:
      - mountPath: "/mydata"
        name: mydata
  volumes:
    - name: mydata
      persistentVolumeClaim:
        claimName: mypvc1

Similar to the normal Volume format, in volumes, you specify the Volume to be applied for using mypvc1 through persistent Volume Claim.

Create mypod1:

[root@k8s-master ~]# kubectl apply -f pod1.yml
pod/mypod1 created

[root@k8s-master ~]# kubectl get pod -o wide     
NAME     READY   STATUS    RESTARTS   AGE     IP           NODE        NOMINATED NODE   READINESS GATES
mypod1   1/1     Running   0          3m19s   10.244.1.2   k8s-node1   <none>           <none>

Verify that PV is available:

[root@k8s-master ~]# kubectl exec mypod1 touch /mydata/hello
[root@k8s-master ~]# ll /nfsdata/pv1/
total 0
-rw-r--r-- 1 root root 0 Oct 12 16:36 hello

As you can see, the file / mydata/hello created in Pod has indeed been saved in the NFS server directory / nfsdata/pv1.

If you no longer need to use PV, you can remove PVC and recycle PV.

How does MySQL use PV and PVC?

This section demonstrates how to provide persistent storage for MySQL databases by:

Create PV and PVC.

Deploy MySQL.

Add data to MySQL.

Kubernetes automatically migrates MySQL to other nodes to simulate node downtime.

Verify data consistency.

First, create PV and PVC with the following configuration:

mysql-pv.yml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-pv
spec:
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 1Gi
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs
  nfs:
    path: /nfsdata/mysql-pv
    server: 192.168.77.10

mysql-pvc.yml

kind: PersistentVolumeClaim
apiVersion: v1
metadata: 
  name: mysql-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs

Create mysql-pv and mysql-pvc:

Next, deploy MySQL with the following configuration files:

apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  ports:
  - port: 3306
  selector:
    app: mysql

---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: mysql:5.6
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pvc  

PVC mysql-pvc Bound PV mysql-pv will be mount ed to MySQL data directory var/lib/mysql.

MySQL is deployed to k8s-node 2, and the following Service mysql is accessed through the client:

# kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -ppassword 

Update the database:

[root@k8s-master ~]# kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -ppassword
If you don't see a command prompt, try pressing enter.

mysql> use mysql 
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> create table my_id(id int(4));
Query OK, 0 rows affected (0.09 sec)

mysql> insert into my_id values(111);
Query OK, 1 row affected (0.00 sec)

mysql> select * from my_id;
+------+
| id   |
+------+
|  111 |
+------+
1 row in set (0.00 sec)

Switch to database mysql.

(2) Create the database table my_id.

(3) Insert a piece of data.

(4) Verify that the data has been written.

Turn k8s-node 2 off to simulate node downtime.

# systemctl poweroff 

Over time, Kubernetes migrated MySQL to k8s-node 1.

[root@k8s-master ~]# kubectl get pod
NAME                     READY   STATUS        RESTARTS   AGE
mysql-84bdf65dd5-bjz8b   1/1     Terminating   0          22m
mysql-84bdf65dd5-ddlhc   1/1     Running       0          34s

Verify data consistency:

[root@k8s-master ~]# kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -ppassword           If you don't see a command prompt, try pressing enter.

mysql> use mysql
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

mysql> select * from my_id;
+------+
| id   |
+------+
|  111 |
+------+
1 row in set (0.01 sec)

mysql> quit

MySQL service is restored and the data is intact.

Volumes of emptyDir and hostPath types are convenient, but not durable. Kubernetes supports Volumes of various external storage systems.

PV and PVC separate the responsibilities of administrators and ordinary users, and are more suitable for production environment.

Posted by mbeals on Mon, 14 Oct 2019 06:14:07 -0700