Lab 2: File storage with Kubernetes¶
This lab demonstrates the use of cloud based file storage with Kubernetes. It uses the IBM Cloud File Storage which is persistent, fast, and flexible network-attached, NFS-based File Storage capacity ranging from 25 GB to 12,000 GB capacity with up to 48,000 IOPS. The IBM Cloud File Storage provides data across all worker nodes within a single availability zone.
Following topics are covered in this exercise:
- Claim a classic file storage volume.
- Make the volumes available in the
Guestbook
application. - Copy media files such as images into the volume using the Kubernetes CLI.
- Use the
Guestbook
application to view the images. - Claim back the storage resources and clean up.
Prereqs¶
Follow the prereqs if you haven't already.
Claim file storage volume¶
Review the storage classes for file storage. In addition to the standard set of storage classes, custom storage classes can be defined to meet the storage requirements.
kubectl get storageclasses
Expected output:
$ kubectl get storageclasses
default ibm.io/ibmc-file Delete Immediate false 27m
ibmc-file-bronze ibm.io/ibmc-file Delete Immediate false 27m
ibmc-file-bronze-gid ibm.io/ibmc-file Delete Immediate false 27m
ibmc-file-custom ibm.io/ibmc-file Delete Immediate false 27m
ibmc-file-gold (default) ibm.io/ibmc-file Delete Immediate false 27m
ibmc-file-gold-gid ibm.io/ibmc-file Delete Immediate false 27m
ibmc-file-retain-bronze ibm.io/ibmc-file Retain Immediate false 27m
ibmc-file-retain-custom ibm.io/ibmc-file Retain Immediate false 27m
ibmc-file-retain-gold ibm.io/ibmc-file Retain Immediate false 27m
ibmc-file-retain-silver ibm.io/ibmc-file Retain Immediate false 27m
ibmc-file-silver ibm.io/ibmc-file Delete Immediate false 27m
ibmc-file-silver-gid ibm.io/ibmc-file Delete Immediate false 27m
IKS comes with storage class definitions for file storage. This lab uses the storage class ibm-file-silver
. Note that the default class is ibmc-file-gold
is allocated if storgage class is not expliciity definded.
kubectl describe storageclass ibmc-file-silver
Expected output:
$ kubectl describe storageclass ibmc-file-silver
Name: ibmc-file-silver
IsDefaultClass: No
Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{},"labels":{"kubernetes.io/cluster-service":"true"},"name":"ibmc-file-silver"},"parameters":{"billingType":"hourly","classVersion":"2","iopsPerGB":"4","sizeRange":"[20-12000]Gi","type":"Endurance"},"provisioner":"ibm.io/ibmc-file","reclaimPolicy":"Delete"}
Provisioner: ibm.io/ibmc-file
Parameters: billingType=hourly,classVersion=2,iopsPerGB=4,sizeRange=[20-12000]Gi,type=Endurance
AllowVolumeExpansion: <unset>
MountOptions: <none>
ReclaimPolicy: Delete
VolumeBindingMode: Immediate
Events: <none>
File sliver has an IOPS of 4GB and a max capacity of 12TB.
Claim a file storage volume¶
IBM Cloud File Storage provides fast access to your data for a cluster running in a single available zone. For higher availability, use a storage option that is designed for geographically distributed data.
Review the yaml for the file storage PersistentVolumeClaim
. When we create this PersistentVolumeClaim
, it automatically creates it within an availability zone where the worker nodes are located.
cd guestbook-config/storage/lab2
cat pvc-file-silver.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: guestbook-pvc
labels:
billingType: hourly
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 20Gi
storageClassName: ibmc-file-silver
Create the PVC
kubectl apply -f pvc-file-silver.yaml
Expected output:
$ kubectl create -f pvc-file-silver.yaml
persistentvolumeclaim/guestbook-filesilver-pvc created
Verify the PVC claim is created with the status Bound
. This may take a minute or two.
kubectl get pvc guestbook-filesilver-pvc
Expected output:
$ kubectl get pvc guestbook-filesilver-pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
guestbook-filesilver-pvc Bound pvc-a7cb12ed-b52b-4342-966a-eceaf24e42a9 20Gi RWX ibmc-file-silver 2m
Details associated with the pv
. Use the pv
name from the previous command output.
kubectl get pv [pv name]
Expected output:
$ kubectl get pv pvc-a7cb12ed-b52b-4342-966a-eceaf24e42a9
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-a7cb12ed-b52b-4342-966a-eceaf24e42a9 20Gi RWX Delete Bound default/guestbook-filesilver-pvc ibmc-file-silver 90s
Use the volume in the Guestbook application¶
Change to the guestbook application source directory and review the html files images.html
and index.html
. images.html
has the code to display the images stored in the file storage.
cd $HOME/guestbook-nodejs/src
cat client/images.html
cat client/index.html
Run the commands listed below to build the guestbook image and copy into the docker hub registry: (Skip this step if you have already completed lab 1.)
cd $HOME/guestbook-nodejs/src
docker build -t $DOCKERUSER/guestbook-nodejs:storage .
docker login -u $DOCKERUSER
docker push $DOCKERUSER/guestbook-nodejs:storage
Review the deployment yaml file guestbook-deplopyment.yaml
prior to deploying the application into the cluster.
cd $HOME/guestbook-config/storage/lab2
cat guestbook-deployment.yaml
Replace the first part of the image
name with your docker hub user id.
The section spec.volumes
references the file
volume PVC. The section spec.containers.volumeMounts
has the mount path to store images in the volumes.
apiVersion: apps/v1
kind: Deployment
metadata:
name: guestbook-v1
...
spec:
containers:
- name: guestbook
image: rojanjose/guestbook-nodejs:storage
imagePullPolicy: Always
ports:
- name: http-server
containerPort: 3000
volumeMounts:
- name: guestbook-file-volume
mountPath: /app/public/images
volumes:
- name: guestbook-file-volume
persistentVolumeClaim:
claimName: guestbook-filesilver-pvc
Deploy the Guestbook application.
kubectl create -f guestbook-deployment.yaml
kubectl create -f guestbook-service.yaml
Verify the Guestbook application is runing.
kubectl get all
Expected output:
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/guestbook-v1-5bd76b568f-cdhr5 1/1 Running 0 13s
pod/guestbook-v1-5bd76b568f-w6h6h 1/1 Running 0 13s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/guestbook LoadBalancer 172.21.238.40 150.238.30.150 3000:31986/TCP 6s
service/kubernetes ClusterIP 172.21.0.1 <none> 443/TCP 9d
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/guestbook-v1 2/2 2 2 13s
NAME DESIRED CURRENT READY AGE
replicaset.apps/guestbook-v1-5bd76b568f 2 2 2 13s
Check the mount path inside the pod container. Get the pod listing.
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
guestbook-v1-5bd76b568f-cdhr5 1/1 Running 0 78s
guestbook-v1-5bd76b568f-w6h6h 1/1 Running 0 78s
Set these variables for each of your pod names:
export POD1=[FIRST POD NAME]
export POD2=[SECOND POD NAME]
Log into any one of the pod. Use one of the pod names from the previous command output.
kubectl exec -it $POD1 -- bash
Run the commands ls -al; ls -al images; df -ah
to view the volume and files. Review the mount for the new volume. Note that the images folder is empty.
$ kubectl exec -it $POD1 -- bash
root@guestbook-v1-5bd76b568f-cdhr5:/home/node/app# ls -alt
total 252
drwxr-xr-x 1 root root 4096 Nov 13 03:17 client
drwxr-xr-x 1 root root 4096 Nov 13 03:15 .
drwxr-xr-x 439 root root 16384 Nov 13 03:15 node_modules
-rw-r--r-- 1 root root 176643 Nov 13 03:15 package-lock.json
drwxr-xr-x 1 node node 4096 Nov 13 03:15 ..
-rw-r--r-- 1 root root 830 Nov 11 23:20 package.json
drwxr-xr-x 3 root root 4096 Nov 10 23:04 common
drwxr-xr-x 3 root root 4096 Nov 10 23:04 server
-rw-r--r-- 1 root root 12 Oct 29 21:00 .dockerignore
-rw-r--r-- 1 root root 288 Oct 29 21:00 .editorconfig
-rw-r--r-- 1 root root 8 Oct 29 21:00 .eslintignore
-rw-r--r-- 1 root root 27 Oct 29 21:00 .eslintrc
-rw-r--r-- 1 root root 151 Oct 29 21:00 .gitignore
-rw-r--r-- 1 root root 30 Oct 29 21:00 .yo-rc.json
-rw-r--r-- 1 root root 105 Oct 29 21:00 Dockerfile
root@guestbook-v1-5bd76b568f-cdhr5:/home/node/app# ls -alt client/images
total 8
drwxr-xr-x 1 root root 4096 Nov 13 03:17 ..
drwxr-xr-x 2 nobody 4294967294 4096 Nov 13 02:02 .
root@guestbook-v1-5bd76b568f-cdhr5:/home/node/app# df -h
Filesystem Size Used Avail Use% Mounted on
overlay 98G 4.9G 89G 6% /
tmpfs 64M 0 64M 0% /dev
tmpfs 7.9G 0 7.9G 0% /sys/fs/cgroup
shm 64M 0 64M 0% /dev/shm
/dev/mapper/docker_data 98G 4.9G 89G 6% /etc/hosts
tmpfs 7.9G 16K 7.9G 1% /run/secrets/kubernetes.io/serviceaccount
fsf-dal1003d-fz.adn.networklayer.com:/IBM02SEV2058850_2177/data01 20G 0 20G 0% /home/node/app/client/images
tmpfs 7.9G 0 7.9G 0% /proc/acpi
tmpfs 7.9G 0 7.9G 0% /proc/scsi
tmpfs 7.9G 0 7.9G 0% /sys/firmware
root@guestbook-v1-5bd76b568f-cdhr5:/home/node/app# exit
Note the filesystem fsf-dal1003d-fz.adn.networklayer.com:/IBM02SEV2058850_2177/data01
is mounted on path /home/node/app/client/images
.
Find the URL for the guestbook application by joining the worker node external IP and service node port.
HOSTNAME=`kubectl get nodes -ojsonpath='{.items[0].metadata.labels.ibm-cloud\.kubernetes\.io\/external-ip}'`
SERVICEPORT=`kubectl get svc guestbook -o=jsonpath='{.spec.ports[0].nodePort}'`
echo "http://$HOSTNAME:$SERVICEPORT"
Verify that the images are missing by viewing the data from the Guestbook application. Click on the hyperlink labled images
at the bottom of the guestbook home page. The images.html page shows images with broken links.
Load the file storage with images¶
Run the kubectl cp
command to move the images into the mounted volume.
cd $HOME/guestbook-config/storage/lab2
kubectl cp images $POD1:/home/node/app/client/
Refresh the page images.html
page in the guestbook application to view the uploaded images.
Shared storage across pods¶
Login into the other pod $POD2
to verify the volume mount.
kubectl exec -it $POD2 -- bash
root@guestbook-v1-5bd76b568f-w6h6h:/home/node/app# ls -alt /home/node/app/client/images
total 160
-rw-r--r-- 1 501 staff 56191 Nov 13 03:44 gb3.jpg
drwxr-xr-x 2 nobody 4294967294 4096 Nov 13 03:44 .
-rw-r--r-- 1 501 staff 21505 Nov 13 03:44 gb2.jpg
-rw-r--r-- 1 501 staff 58286 Nov 13 03:44 gb1.jpg
drwxr-xr-x 1 root root 4096 Nov 13 03:17 ..
root@guestbook-v1-5bd76b568f-w6h6h:/home/node/app# df -h
Filesystem Size Used Avail Use% Mounted on
overlay 98G 4.2G 89G 5% /
tmpfs 64M 0 64M 0% /dev
tmpfs 7.9G 0 7.9G 0% /sys/fs/cgroup
shm 64M 0 64M 0% /dev/shm
/dev/mapper/docker_data 98G 4.2G 89G 5% /etc/hosts
tmpfs 7.9G 16K 7.9G 1% /run/secrets/kubernetes.io/serviceaccount
fsf-dal1003d-fz.adn.networklayer.com:/IBM02SEV2058850_2177/data01 20G 128K 20G 1% /home/node/app/client/images
tmpfs 7.9G 0 7.9G 0% /proc/acpi
tmpfs 7.9G 0 7.9G 0% /proc/scsi
tmpfs 7.9G 0 7.9G 0% /sys/firmware
root@guestbook-v1-5bd76b568f-w6h6h:/home/node/app# exit
Note that the volume and the data are available on all the pods running the Guestbook application.
IBM Cloud File Storage is a NFS-based file storage that is available across all worker nodes within a single availability zone. If you are running a cluster with multiple nodes (within a single AZ) you can run the following commands to prove that your data is available across different nodes:
kubectl get pods -o wide
kubectl get nodes
Expected output:
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
guestbook-v1-6fb8b86876-n9jtz 1/1 Running 0 39h 172.30.224.70 10.38.216.205 <none> <none>
guestbook-v1-6fb8b86876-njwcz 1/1 Running 0 39h 172.30.169.144 10.38.216.238 <none> <none>
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
10.38.216.205 Ready <none> 4d5h v1.18.10+IKS
10.38.216.238 Ready <none> 4d5h v1.18.10+IKS
To extend our table from Lab 1 we now have:
Storage Type | Persisted at which level | Example Uses |
---|---|---|
Container local storage | Container | ephermal state |
Secondary Storage (EmptyDir) | Pod | Checkpoint a long computation process |
Primary Storage (HostPath) | Node | Running cAdvisor in a container |
IBM Cloud File Storage (NFS) | Availabilty Zone | Applications running in a single availabilty zone |
Data is available to all nodes within the availability zone where the file storage exists, but the accessMode
parameter on the PersistentVolumeClaim
determines if multiple pods are able to mount a volume specificed by a PVC. The possible values for this parameter are:
- ReadWriteMany: The PVC can be mounted by multiple pods. All pods can read from and write to the volume.
- ReadOnlyMany: The PVC can be mounted by multiple pods. All pods have read-only access.
- ReadWriteOnce: The PVC can be mounted by one pod only. This pod can read from and write to the volume.
[Optional exercises]¶
Another way to see that the data is persisted at the availability zone level, you can:
- Back up data.
- Delete pods to confirm that it does not impact the data used by the application.
- Delete the Kubernetes cluster.
- Create a new cluster and reuse the volume.
Clean up¶
List all the PVCs and PVs
kubectl get pvc
kubectl get pv
Delete all the pods using the PVC. Delete the PVC
kubectl delete pvc guestbook-pvc
persistentvolumeclaim "guestbook-pvc" deleted
List PV to ensure that it is removed as well. Cancel the physical storage volume from the cloud account. (Note: requires admin permissions?)
ibmcloud sl file volume-list --columns id --columns notes | grep pvc-6362f614-258e-48ee-a596-62bb4629cd75
183223942 {"plugin":"ibm-file-plugin-7b9db9c79f-86x8w","region":"us-south","cluster":"bugql3nd088jsp8iiagg","type":"Endurance","ns":"default","pvc":"guestbook-pvc","pv":"pvc-6362f614-258e-48ee-a596-62bb4629cd75","storageclass":"ibmc-file-silver","reclaim":"Delete"}
ibmcloud sl file volume-cancel 183223942
This will cancel the file volume: 183223942 and cannot be undone. Continue?> yes
Failed to cancel file volume: 183223942.
No billing item is found to cancel.