From 8bed33fcba4f0682756c9b0afdb291e55a34fa27 Mon Sep 17 00:00:00 2001 From: Rojan Jose Date: Wed, 11 Nov 2020 12:53:11 -0500 Subject: [PATCH 1/3] lab1 instructions --- workshop/Lab0/README.md | 7 +- workshop/Lab1/README.md | 218 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 216 insertions(+), 9 deletions(-) diff --git a/workshop/Lab0/README.md b/workshop/Lab0/README.md index ed0d7f1..f142ea6 100644 --- a/workshop/Lab0/README.md +++ b/workshop/Lab0/README.md @@ -1,9 +1,12 @@ # Pre-work - ## 1. Setup Kubernetes environment Run through the instructions listed [here](https://github.com/IBM/kube101/tree/master/workshop/Lab0) -## 2. Download or clone the repo +## 2. Cloud shell login. + +## 3. Docker hub account. + + diff --git a/workshop/Lab1/README.md b/workshop/Lab1/README.md index 7921ac0..1fb173f 100644 --- a/workshop/Lab1/README.md +++ b/workshop/Lab1/README.md @@ -1,13 +1,217 @@ -# Lab 1. Container storage and Kubernetes +# Lab 1. Non-pesistent storage with Kubernetes -Expolore how local storage works on the pods. -Mount it on Docker. +Storing data in containers or worker nodes are considered as the [non-persistent](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#local-ephemeral-storage) forms of data storage. +In this lab, we will explore storage options on the IBM Kubernetes worker nodes. Follow this [lab](https://github.com/remkohdev/docker101/tree/master/workshop/lab-3) is you are interested in learning more about container based storage. -The application is the [Guestbook App](https://github.com/IBM/guestbook), which is a sample multi-tier web application. +The lab covers the following topics: +- Create and claim persistent volumes based on the [primary]() and secondary storage available on the worker nodes. +- Make the volumes available in the `Guestbook` application. +- Use the volumes to stroage application cache and debug information. +- Access the data from the guestbook container using the Kubernetes CLI. +- Claim back the storage resources and clean up. -## Scenario 1: Deploy the application using `kubectl` + +The primary storage maps to the volume type `hostPath` and the secondary storage maps to `emptyDir`. Learn more about Kubernetes volume types [here](https://kubernetes.io/docs/concepts/storage/volumes/). + +## Reserve Persistent Volumes + +From the cloud shell prompt, run the following commands to get the guestbook application and the kubernetes configuration needed for the storage labs. ```bash -git clone https://github.com/IBM/workshop-template -cd workshop-template +cd $HOME +git clone --branch fs https://github.com/IBM/guestbook-nodejs.git +git clone --branch storage https://github.com/rojanjose/guestbook-config.git +cd $HOME/guestbook-config/storage +``` + +Let's start with reserving the Persistent volume from the primary storage. +Review the yaml file `pv-hostpath.yaml`. Note the values set for `type`, `storageClassName` and `hostPath`. + +```console +apiVersion: v1 +kind: PersistentVolume +metadata: + name: guestbook-primary-pv + labels: + type: local +spec: + storageClassName: manual + capacity: + storage: 10Gi + accessModes: + - ReadWriteOnce + hostPath: + path: "/mnt/data" +``` + +Create the persistent volume as shown in the comamand below. +``` +kubectl create -f pv-hostpath.yaml +persistentvolume/guestbook-primary-pv created + +kubectl get pv +NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE +guestbook-primary-pv 10Gi RWO Retain Available manual 13s +``` + +PVC yaml: +``` +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: guestbook-local-pvc +spec: + storageClassName: manual + accessModes: + - ReadWriteMany + resources: + requests: + storage: 3Gi +``` + +Create PVC: + +``` +kubectl create -f guestbook-local-pvc.yaml +persistentvolumeclaim/guestbook-local-pvc created +❯ kubectl get pvc +NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE +guestbook-local-pvc Bound guestbook-local-pv 10Gi RWX manual 6s +``` + + +## Guestbook application using storage + +The application is the [Guestbook App](https://github.com/IBM/guestbook-nodejs), which is a sample multi-tier web application. + + +Build the image +Misc: (building v3 image) +``` +docker build -t rojanjose/guestbook:v31 . +docker push rojanjose/guestbook:v31 +https://hub.docker.com/repository/docker/rojanjose/guestbook/tags?page=1 +``` + + +Main.go file: +Line 186 +``` +//Setip data file + f, err := os.OpenFile("data/datafile.txt", os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666) + +``` + +Deployment yaml: +``` +apiVersion: apps/v1 +kind: Deployment +... + spec: + containers: + - name: guestbook + image: rojanjose/guestbook:v31 + ports: + - name: http-server + containerPort: 3000 + volumeMounts: + - name: guestbook-data-volume + mountPath: /app/data + volumes: + - name: guestbook-data-volume + persistentVolumeClaim: + claimName: guestbook-local-pvc ``` + +Deploy Guestbook application: + +``` +❯ kubectl create -f guestbook-deployment.yaml +deployment.apps/guestbook-v1 created +❯ kubectl get pods +NAME READY STATUS RESTARTS AGE +guestbook-v1-6f55cb54c5-jb89d 1/1 Running 0 14s +❯ kubectl create -f guestbook-service.yaml +service/guestbook created +``` + +Load data: + +![Guestbook images](images/guestbook-local-data.png) + + +Log into the pod: + +``` +kubectl exec -it guestbook-v1-6f55cb54c5-jb89d -- busybox sh + + +BusyBox v1.21.1 (Ubuntu 1:1.21.0-1ubuntu1) built-in shell (ash) +Enter 'help' for a list of built-in commands. + +/app # cat data/datafile.txt +2020/11/04 05:14:29 Logging guestbook data: +2020/11/04 05:20:14 One +2020/11/04 05:20:17 Two +2020/11/04 05:20:20 Three + +/app # df -ah +Filesystem Size Used Available Use% Mounted on +overlay 97.9G 1.4G 91.5G 2% / +proc 0 0 0 0% /proc +tmpfs 64.0M 0 64.0M 0% /dev +devpts 0 0 0 0% /dev/pts +cgroup 0 0 0 0% /sys/fs/cgroup/perf_event +... + +/dev/xvda2 24.2G 2.2G 20.8G 9% /app/data +... +tmpfs 7.8G 0 7.8G 0% /sys/firmware +``` + +Kill the pod: + +``` +kubectl get pods +NAME READY STATUS RESTARTS AGE +guestbook-v1-6f55cb54c5-jb89d 1/1 Running 0 12m +❯ +❯ +❯ kubectl delete pod guestbook-v1-6f55cb54c5-jb89d +pod "guestbook-v1-6f55cb54c5-jb89d" deleted +❯ kubectl get pods +NAME READY STATUS RESTARTS AGE +guestbook-v1-6f55cb54c5-gctwt 1/1 Running 0 11s +``` + +![Guestbook images](images/guestbook-local-data-deleted.png) + +Enter data: +![Guestbook images](images/guestbook-local-data-reload.png) + + +``` +kubectl exec -it guestbook-v1-6f55cb54c5-gctwt -- busybox sh + + +BusyBox v1.21.1 (Ubuntu 1:1.21.0-1ubuntu1) built-in shell (ash) +Enter 'help' for a list of built-in commands. + +/app # ls -alt +total 8972 +drwxr-xr-x 1 root root 4096 Nov 4 05:27 . +drwxr-xr-x 1 root root 4096 Nov 4 05:27 .. +drwxr-xr-x 2 root root 4096 Nov 4 05:14 data +drwxr-xr-x 1 root root 4096 Nov 4 05:12 public +-rwxr-xr-x 1 root root 9167339 Nov 4 05:12 guestbook +/app # cat data/datafile.txt +2020/11/04 05:14:29 Logging guestbook data: +2020/11/04 05:20:14 One +2020/11/04 05:20:17 Two +2020/11/04 05:20:20 Three +2020/11/04 05:27:20 Logging guestbook data: +2020/11/04 05:32:04 Four +2020/11/04 05:32:07 Five +2020/11/04 05:32:08 Six +``` + From 303c1383d9748de80b8e6ebc3bbcf7145ac34e15 Mon Sep 17 00:00:00 2001 From: Rojan Jose Date: Wed, 11 Nov 2020 22:52:43 -0500 Subject: [PATCH 2/3] lab1 instructions --- workshop/Lab1/README.md | 238 ++++++++++++------ .../images/lab1-guestbook-data-deleted.png | Bin 0 -> 109631 bytes .../images/lab1-guestbook-data-reload.png | Bin 0 -> 185941 bytes .../Lab1/images/lab1-guestbook-entries.png | Bin 0 -> 202510 bytes 4 files changed, 163 insertions(+), 75 deletions(-) create mode 100644 workshop/Lab1/images/lab1-guestbook-data-deleted.png create mode 100644 workshop/Lab1/images/lab1-guestbook-data-reload.png create mode 100644 workshop/Lab1/images/lab1-guestbook-entries.png diff --git a/workshop/Lab1/README.md b/workshop/Lab1/README.md index 1fb173f..3c3d6bf 100644 --- a/workshop/Lab1/README.md +++ b/workshop/Lab1/README.md @@ -54,6 +54,7 @@ NAME CAPACITY ACCESS MODES RECLAIM POL guestbook-primary-pv 10Gi RWO Retain Available manual 13s ``` +Next PVC yaml: ``` apiVersion: v1 @@ -82,136 +83,223 @@ guestbook-local-pvc Bound guestbook-local-pv 10Gi ## Guestbook application using storage -The application is the [Guestbook App](https://github.com/IBM/guestbook-nodejs), which is a sample multi-tier web application. +The application is the [Guestbook App](https://github.com/IBM/guestbook-nodejs), which is a simple multi-tier web application built using the loopback framework. +Change to the guestbook application source directory: -Build the image -Misc: (building v3 image) ``` -docker build -t rojanjose/guestbook:v31 . -docker push rojanjose/guestbook:v31 -https://hub.docker.com/repository/docker/rojanjose/guestbook/tags?page=1 +cd $HOME/guestbook-nodejs/src ``` +Review the source `common/models/entry.js`. The application uses storage allocated using `hostPath` to store data cache in the file `data/cache.txt`. The file `logs/debug.txt` records debug messages and is provisioned via the `emptyDir` storage type. +```source +module.exports = function(Entry) { + + Entry.greet = function(msg, cb) { + + // console.log("testing " + msg); + fs.appendFile('logs/debug.txt', "Recevied message: "+ msg +"\n", function (err) { + if (err) throw err; + console.log('Debug stagement printed'); + }); + + fs.appendFile('data/cache.txt', msg+"\n", function (err) { + if (err) throw err; + console.log('Saved in cache!'); + }); + +... +``` + +Run the commands listed below to build the guestbook image and copy into docker hub registry: -Main.go file: -Line 186 ``` -//Setip data file - f, err := os.OpenFile("data/datafile.txt", os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666) - +cd $HOME/guestbook-nodejs/src +docker build -t $DOCKERUSER/guestbook-nodejs:storage . +export DOCKERUSER=rojanjose +docker login -u DOCKERUSER +docker push $DOCKERUSER/guestbook-nodejs:storage ``` -Deployment yaml: +Review the deployment yaml file `guestbook-deplopyment.yaml` prior to deploying the application into the cluster. + +``` +cd $HOME/storage/lab1 +cat guestbook-deplopyment.yaml +``` + +The section `spec.volumes` lists `hostPath` and `emptyDir` volumes. The section `spec.containers.volumeMounts` lists the mount paths that the application uses to write in the volumes. + ``` apiVersion: apps/v1 kind: Deployment -... +metadata: + name: guestbook-v1 + labels: + app: guestbook + ... spec: containers: - name: guestbook - image: rojanjose/guestbook:v31 + image: rojanjose/guestbook-nodejs:storage + imagePullPolicy: Always ports: - name: http-server containerPort: 3000 volumeMounts: - - name: guestbook-data-volume - mountPath: /app/data + - name: guestbook-primary-volume + mountPath: /home/node/app/data + - name: guestbook-secondary-volume + mountPath: /home/node/app/logs volumes: - - name: guestbook-data-volume + - name: guestbook-primary-volume persistentVolumeClaim: - claimName: guestbook-local-pvc + claimName: guestbook-primary-pvc + - name: guestbook-secondary-volume + emptyDir: {} + + +... ``` Deploy Guestbook application: ``` -❯ kubectl create -f guestbook-deployment.yaml +kubectl create -f guestbook-deployment.yaml deployment.apps/guestbook-v1 created -❯ kubectl get pods + +kubectl get pods NAME READY STATUS RESTARTS AGE guestbook-v1-6f55cb54c5-jb89d 1/1 Running 0 14s -❯ kubectl create -f guestbook-service.yaml + +kubectl create -f guestbook-service.yaml service/guestbook created ``` -Load data: - -![Guestbook images](images/guestbook-local-data.png) - - -Log into the pod: +Find the URL for the guestbook application by joining the worker node external IP and service node port. ``` -kubectl exec -it guestbook-v1-6f55cb54c5-jb89d -- busybox sh +HOSTNAME=`ibmcloud ks workers --cluster $CLUSTERNAME | grep Ready | head -n 1 | awk '{print $2}'` +SERVICEPORT=`kubectl get svc guestbook -o=jsonpath='{.spec.ports[0].nodePort}'` +echo "http://$HOSTNAME:$SERVICEPORT" +``` +Open the URL in a browser and create guest book entries. -BusyBox v1.21.1 (Ubuntu 1:1.21.0-1ubuntu1) built-in shell (ash) -Enter 'help' for a list of built-in commands. +![Guestbook entries](images/lab1-guestbook-entries.png) -/app # cat data/datafile.txt -2020/11/04 05:14:29 Logging guestbook data: -2020/11/04 05:20:14 One -2020/11/04 05:20:17 Two -2020/11/04 05:20:20 Three +Log into the pod: -/app # df -ah -Filesystem Size Used Available Use% Mounted on -overlay 97.9G 1.4G 91.5G 2% / -proc 0 0 0 0% /proc -tmpfs 64.0M 0 64.0M 0% /dev -devpts 0 0 0 0% /dev/pts -cgroup 0 0 0 0% /sys/fs/cgroup/perf_event -... +``` +kubectl exec -it guestbook-v1-6f55cb54c5-jb89d bash + +kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead. + +root@guestbook-v1-6f55cb54c5-jb89d:/home/node/app# ls -al +total 256 +drwxr-xr-x 1 root root 4096 Nov 11 23:40 . +drwxr-xr-x 1 node node 4096 Nov 11 23:20 .. +-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 +drwxr-xr-x 2 root root 4096 Nov 11 03:40 client +drwxr-xr-x 3 root root 4096 Nov 10 23:04 common +drwxr-xr-x 2 root root 4096 Nov 11 23:16 data +drwxrwxrwx 2 root root 4096 Nov 11 23:44 logs +drwxr-xr-x 439 root root 16384 Nov 11 23:20 node_modules +-rw-r--r-- 1 root root 176643 Nov 11 23:20 package-lock.json +-rw-r--r-- 1 root root 830 Nov 11 23:20 package.json +drwxr-xr-x 3 root root 4096 Nov 10 23:04 server + +root@guestbook-v1-6f55cb54c5-jb89d:/home/node/app# cat data/cache.txt +Hello Kubernetes! +Hola Kubernetes! +Zdravstvuyte Kubernetes! +Nǐn hǎo Kubernetes! +Goedendag Kubernetes! + +root@guestbook-v1-6f55cb54c5-jb89d:/home/node/app# cat logs/debug.txt +Recevied message: Hello Kubernetes! +Recevied message: Hola Kubernetes! +Recevied message: Zdravstvuyte Kubernetes! +Recevied message: Nǐn hǎo Kubernetes! +Recevied message: Goedendag Kubernetes! + + +root@guestbook-v1-6f55cb54c5-jb89d:/home/node/app# df -h +Filesystem Size Used Avail Use% Mounted on +overlay 98G 3.5G 90G 4% / +tmpfs 64M 0 64M 0% /dev +tmpfs 7.9G 0 7.9G 0% /sys/fs/cgroup +/dev/mapper/docker_data 98G 3.5G 90G 4% /etc/hosts +shm 64M 0 64M 0% /dev/shm +/dev/xvda2 25G 3.6G 20G 16% /home/node/app/data +tmpfs 7.9G 16K 7.9G 1% /run/secrets/kubernetes.io/serviceaccount +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 -/dev/xvda2 24.2G 2.2G 20.8G 9% /app/data -... -tmpfs 7.8G 0 7.8G 0% /sys/firmware ``` -Kill the pod: +Kill the pod to see the impact of deleting the pod on data. ``` kubectl get pods NAME READY STATUS RESTARTS AGE guestbook-v1-6f55cb54c5-jb89d 1/1 Running 0 12m -❯ -❯ -❯ kubectl delete pod guestbook-v1-6f55cb54c5-jb89d + +kubectl delete pod guestbook-v1-6f55cb54c5-jb89d pod "guestbook-v1-6f55cb54c5-jb89d" deleted -❯ kubectl get pods + +kubectl get pods NAME READY STATUS RESTARTS AGE -guestbook-v1-6f55cb54c5-gctwt 1/1 Running 0 11s +guestbook-v1-5cbc445dc9-sx58j 1/1 Running 0 86s ``` -![Guestbook images](images/guestbook-local-data-deleted.png) +![Guestbook delete data](images/lab1-guestbook-data-deleted.png) -Enter data: -![Guestbook images](images/guestbook-local-data-reload.png) +Enter new data: +![Guestbook reload](images/lab1-guestbook-data-reload.png) +Log into the pod to view the state of the data. ``` -kubectl exec -it guestbook-v1-6f55cb54c5-gctwt -- busybox sh +kubectl get pods +NAME READY STATUS RESTARTS AGE +guestbook-v1-5cbc445dc9-sx58j 1/1 Running 0 86s + +kubectl exec -it guestbook-v1-5cbc445dc9-sx58j bash +kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead. + +root@guestbook-v1-5cbc445dc9-sx58j:/home/node/app# cat data/cache.txt +Hello Kubernetes! +Hola Kubernetes! +Zdravstvuyte Kubernetes! +Nǐn hǎo Kubernetes! +Goedendag Kubernetes! +Bye Kubernetes! +Aloha Kubernetes! +Ciao Kubernetes! +Sayonara Kubernetes! + +root@guestbook-v1-5cbc445dc9-sx58j:/home/node/app# cat logs/debug.txt +Recevied message: Bye Kubernetes! +Recevied message: Aloha Kubernetes! +Recevied message: Ciao Kubernetes! +Recevied message: Sayonara Kubernetes! +root@guestbook-v1-5cbc445dc9-sx58j:/home/node/app# +``` +This shows that the storage type `emptyDir` loose data on a pod restart where as `hostPath` data lives until the worker node or cluster is deleted. -BusyBox v1.21.1 (Ubuntu 1:1.21.0-1ubuntu1) built-in shell (ash) -Enter 'help' for a list of built-in commands. -/app # ls -alt -total 8972 -drwxr-xr-x 1 root root 4096 Nov 4 05:27 . -drwxr-xr-x 1 root root 4096 Nov 4 05:27 .. -drwxr-xr-x 2 root root 4096 Nov 4 05:14 data -drwxr-xr-x 1 root root 4096 Nov 4 05:12 public --rwxr-xr-x 1 root root 9167339 Nov 4 05:12 guestbook -/app # cat data/datafile.txt -2020/11/04 05:14:29 Logging guestbook data: -2020/11/04 05:20:14 One -2020/11/04 05:20:17 Two -2020/11/04 05:20:20 Three -2020/11/04 05:27:20 Logging guestbook data: -2020/11/04 05:32:04 Four -2020/11/04 05:32:07 Five -2020/11/04 05:32:08 Six -``` +## Clean up +``` +cd $HOME/guestbook-config/storage/lab1 +kubectl delete -f . +``` \ No newline at end of file diff --git a/workshop/Lab1/images/lab1-guestbook-data-deleted.png b/workshop/Lab1/images/lab1-guestbook-data-deleted.png new file mode 100644 index 0000000000000000000000000000000000000000..6f24cd7244d4e85444e7fc03d5a0b183b8675198 GIT binary patch literal 109631 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