You are browsing a read-only backup copy of Wikitech. The live site can be found at wikitech.wikimedia.org

Kubernetes/Kubernetes Workshop/Setting up Infrastructure as Code (IaC) in Kubernetes: Difference between revisions

From Wikitech-static
Jump to navigation Jump to search
imported>Mbaoma
No edit summary
imported>Slavina Stefanova
(→‎Step 1 - Creating Jobs with YAML: Add comments to YAML config)
 
Line 8: Line 8:
== Step 1 - Creating Jobs with YAML ==
== Step 1 - Creating Jobs with YAML ==


You can use the [https://kubernetes.io/search/?q=minikube Kubernetes] (k8s) Command Line (CLI) to run your jobs or services and reconfigure specific parameters (replicas at least), which is convenient for testing quick changes. Still, it is easy to lose control over the executed changes. k8s offers the option to put all parameters in a [https://en.wikipedia.org/wiki/YAML YAML] structured configuration file, allowing you to use source control to keep track of and document the various versions of services you build.
Up to this point, we have done the following to run an application on a Kubernetes cluster:
You can work with the ''pywpchksumbot.py'' application, which you built in [[Kubernetes/Kubernetes Workshop/Set up a batch application on Kubernetes|Module 1]]. In this module, you will write out a YAML configuration file to run the ''pywpchksumbot'' application as a single job.


* Create a new YAML file. Your YAML file should continue a configuration similar to the snippet below:
# Packaged it as a container
# Wrapped the container in a Pod
# Deployed it via the Kubernetes Command Line Interface (CLI)


<syntaxhighlight lang="bash">
While using the command line is convenient for learning, experimenting and troubleshooting, the preferred way of doing things is to declare the desired state of your application in a ''manifest file''. Manifest files are written in [[:en:YAML|YAML]]<nowiki/>and describe what an application ''should'' look like. It defines things like which image to use, how many replicas to run, how to perform updates, and more. Behind the scenes, Kubernetes continuously monitors your cluster and compares its ''actual'' state to the ''desired'' state in the manifest. If a discrepancy is found, Kubernetes takes care of reconciling the situation.
 
This declarative model is simple and powerful: you just tell Kubernetes ''what'' you want, and Kubernetes takes care of the ''how.'' This approach is less error-prone and lends itself to version control and reproducible deployments. It is also self-documenting. 
 
In this section, we will work with the ''pywpchksumbot.py'' application from [[Kubernetes/Kubernetes Workshop/Set up a batch application on Kubernetes|Module 1]]. We start by creating a YAML configuration to run the ''pywpchksumbot'' application as a single job:
 
* Create the following YAML config file and save it as  <code><filename>.yaml</code>
 
<syntaxhighlight lang="yaml">
apiVersion: batch/v1
apiVersion: batch/v1
kind: Job
kind: Job
metadata:
metadata:
  name: pywpchksumbot
  name: pywpchksumbot           <== Job name
spec:
spec:
  template:
  template:                     <== Pod template definition
   spec:
   spec:
     containers:
     containers:
Line 27: Line 36:
       resources: {}
       resources: {}
     restartPolicy: Never
     restartPolicy: Never
  backoffLimit: 4
  backoffLimit: 4               <== Number of retries before considering a Job as failed
</syntaxhighlight>
</syntaxhighlight>


Line 34: Line 43:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ minikube start
$ minikube start
$ kubectl create -f <file-name>.yaml
 
$ kubectl apply -f <file-name>.yaml
job.batch/pywpchksumbot created
job.batch/pywpchksumbot created
$ kubectl get pods
$ kubectl get pods
NAME                  READY  STATUS            RESTARTS        AGE
NAME                  READY  STATUS            RESTARTS        AGE
pywpchksumbot-qsr86    0/1    Completed          0              23s
pywpchksumbot-qsr86    0/1    Completed          0              23s
$ kubectl get jobs
$ kubectl get jobs
NAME            COMPLETIONS  DURATION  AGE
NAME            COMPLETIONS  DURATION  AGE
Line 58: Line 70:
</syntaxhighlight>
</syntaxhighlight>


== Step 2 - Cronjobs ==


A [https://en.wikipedia.org/wiki/Cron cronjob] is a job scheduler on Unix-like operating systems. You will work with a cronjob that runs a program repeatedly with a syntax similar to [https://en.wikipedia.org/wiki/Cron crond].
==Step 2 - Cronjobs==
 
A [[:en:Cron|cronjob]] is a job scheduler on Unix-like operating systems. You will work with a cronjob that runs a program repeatedly with a syntax similar to [[:en:Cron|crond]].  


* Create a new YAML file. Your YAML file should contain a configuration similar to the snippet below:
*Create a new YAML file. Your YAML file should contain a configuration similar to the snippet below:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="yaml">
apiVersion: batch/v1
apiVersion: batch/v1
kind: CronJob
kind: CronJob
Line 80: Line 93:
           imagePullPolicy: IfNotPresent
           imagePullPolicy: IfNotPresent
         restartPolicy: OnFailure
         restartPolicy: OnFailure
</syntaxhighlight>
</syntaxhighlight>  


* Run your YAML script with the following commands:
*Run your YAML script with the following commands:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ kubectl create -f <file_name>.yaml
$ kubectl create -f <file_name>.yaml
cronjob.batch/cronpywpchksumbot created
cronjob.batch/cronpywpchksumbot created
$ kubectl get pods
$ kubectl get pods
$ kubectl get cronjobs
$ kubectl get cronjobs
NAME                SCHEDULE        SUSPEND  ACTIVE  LAST SCHEDULE  AGE
NAME                SCHEDULE        SUSPEND  ACTIVE  LAST SCHEDULE  AGE
cronpywpchksumbot  */5 * * * *    False      0        <none>          88s
cronpywpchksumbot  */5 * * * *    False      0        <none>          88s
$ kubectl describe cronjobs <cronjob_name>
$ kubectl describe cronjobs <cronjob_name>
</syntaxhighlight>
</syntaxhighlight>  


* You can change the schedule by editing the YAML file. Delete all running instances with:
*You can change the schedule by editing the YAML file. Delete all running instances with:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ kubectl delete job <job_name>
$ kubectl delete job <job_name>
job.batch "pywpchksumbot" deleted
job.batch "pywpchksumbot" deleted
</syntaxhighlight>
</syntaxhighlight>  


* In the line for schedule, you can replace the current value with any time description of your choice. Re-run the script:
*In the line for schedule, you can replace the current value with any time description of your choice. Re-run the script:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ kubectl apply -f <file_name>.yaml
$ kubectl apply -f <file_name>.yaml
cronjob.batch/cronpywpchksumbot configured
cronjob.batch/cronpywpchksumbot configured
$ kubectl describe cronjobs <cron_job>
$ kubectl describe cronjobs <cron_job>
</syntaxhighlight>
</syntaxhighlight>  


* Check for the schedule:
*Check for the schedule:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
Line 115: Line 132:
Schedule:                      <value in schedule>
Schedule:                      <value in schedule>
Last Schedule Time:  Day, Date Month Year Hour:Minute:Seconds +0000
Last Schedule Time:  Day, Date Month Year Hour:Minute:Seconds +0000
</syntaxhighlight>
</syntaxhighlight>  


* Delete all running instances:
*Delete all running instances:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
Line 124: Line 141:
</syntaxhighlight>
</syntaxhighlight>


'''Note''':
'''Note''':  


* Cronjobs with names longer than '''52''' characters silently fail to schedule jobs.
*Cronjobs with names longer than '''52''' characters silently fail to schedule jobs.
* Pods would sometimes get stuck in the ''Pending state'' forever.
*Pods would sometimes get stuck in the ''Pending state'' forever.
* The scheduler would crash every 3 hours.
*The scheduler would crash every 3 hours.
* Flannel’s ''hostgw'' backend did not replace outdated route table entries.
*Flannel’s ''hostgw'' backend did not replace outdated route table entries.
* You can study [https://stripe.com/blog/operating-kubernetes this] article to understand how Stripe makes use of Cronjobs.
*You can study [https://stripe.com/blog/operating-kubernetes this] article to understand how Stripe makes use of Cronjobs.


== Step 3 - Deploying a Simple Web Server ==
==Step 3 - Deploying a Simple Web Server==


In this section, you will deploy a web server using a YAML configuration file. The Docker image is that of the apache container you created in [[Kubernetes/Kubernetes Workshop/Build a service application on Kubernetes|Module 2]].
In this section, you will deploy a web server using a YAML configuration file. The Docker image is that of the apache container you created in [[Kubernetes/Kubernetes Workshop/Build a service application on Kubernetes|Module 2]].  


* Create a new YAML file using the editor of you choice:
*Create a new YAML file using the editor of your choice:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="yaml">
apiVersion: apps/v1
apiVersion: apps/v1
kind: Deployment
kind: Deployment
Line 163: Line 180:
</syntaxhighlight>
</syntaxhighlight>


'''Note''':  
'''Note''':


* Add a label app to the deployment and the resulting pods (the template stanza).
*Add a label app to the Deployment and the resulting Pods (the template stanza).
* Set the replicas to 1.
*Set the replicas to 1.
* Always pull the image from DockerHub, whether it is present in your local Docker or containerd context. Do this to ensure you have the right image. However, pulling images from Docker Hub will consume bandwidth every time you start a new pod.
*Always pull the image from DockerHub, whether it is present in your local Docker or containerd context. Do this to ensure you have the right image. However, pulling images from Docker Hub will consume bandwidth every time you start a new Pod.  


* Run your YAML script with the following commands:
*Run your YAML script with the following commands:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ kubectl create -f <file_name>.yaml
$ kubectl create -f <file_name>.yaml
deployment.apps/ndeploy created
deployment.apps/ndeploy created
$ kubectl get pods
$ kubectl get pods
$ kubectl get deployments
$ kubectl get deployments
NAME      READY  UP-TO-DATE  AVAILABLE  AGE
NAME      READY  UP-TO-DATE  AVAILABLE  AGE
<name>  1/1    1            1          8s
<name>  1/1    1            1          8s
$ kubectl describe deployment <deployment_name>
$ kubectl describe deployment <deployment_name>
</syntaxhighlight>
</syntaxhighlight>


'''Note''': The value of <code> <deployment_name> </code> should be the same as the value in the previous YAML file.
'''Note''': The value of <code> <deployment_name> </code> should be the same as the value in the previous YAML file.  


* Create a new YAML file, using your favorite edit, to define your service(s). Define the necessary service(s). The service will expose the pod in the deployment above and will use the ''app'' label in the deployment and match it via the selector in the service description:
*Create a new YAML file, using your favorite edit, to define your Service(s). Define the necessary Service(s). The Service will expose the Pod in the Deployment above and will use the ''app'' label in the Deployment and match it via the selector in the Service description:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="yaml">
kind: Service
kind: Service
apiVersion: v1
apiVersion: v1
Line 198: Line 218:
   port: 80
   port: 80
   targetPort: 80
   targetPort: 80
</syntaxhighlight>
</syntaxhighlight>  
   
   
* Run your YAML script with the following commands:
*Run your YAML script with the following commands:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ kubectl create -f <filename>.yaml
$ kubectl create -f <filename>.yaml
service/ndeploy created
service/ndeploy created
$ kubectl get services
$ kubectl get services
NAME        TYPE          CLUSTER-IP      EXTERNAL-IP  PORT(S)        AGE
NAME        TYPE          CLUSTER-IP      EXTERNAL-IP  PORT(S)        AGE
ndeploy      NodePort  10.103.127.158      <pending>    80:30818/TCP  3m3s
ndeploy      NodePort  10.103.127.158      <pending>    80:30818/TCP  3m3s
$ kubectl describe service <service_name>
$ kubectl describe service <service_name>
$ minikube service <service_name> --url
$ minikube service <service_name> --url
http://192.168.49.2:30818
http://192.168.49.2:30818
</syntaxhighlight>
</syntaxhighlight>  
* Navigate to the URL with a browser or make a curl request on the machine you are running minikube on.
*Navigate to the URL with a browser or make a curl request on the machine you are running minikube on.
* Delete all running instances:
*Delete all running instances:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ kubectl delete service <service_name>
$ kubectl delete service <service_name>
Line 218: Line 241:
</syntaxhighlight>
</syntaxhighlight>


== Hands-on Demo: Deploying a Production Web Server ==
==Hands-on Demo: Deploying a Production Web Server==


In this section, you will run a program that randomly selects and prints a book from a MySQL database (you can see it as a book recommendation server). This service will require a database server, two web servers, and a load balancer to make requests between them.
In this section, you will run a program that randomly selects and prints a book from a MySQL database (you can see it as a book recommendation server). This service will require a database server, two web servers, and a load balancer to make requests between them.
Line 225: Line 248:
The outlined steps below will help you setup and deploy a production ready web server:
The outlined steps below will help you setup and deploy a production ready web server:


'''Note''': In this case, we won’t be using a Dockerfile, but will create the container manually.
'''Note''': In this case, we won’t be using a Dockerfile, but will create the container manually.  
* Pull and run the MariaDB Docker container and build the database.  
*Pull and run the MariaDB Docker container and build the database.
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ docker pull mariadb
$ docker pull mariadb
$ docker run --name=<container-name> -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -d mariadb
$ docker run --name=<container-name> -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -d mariadb
3e8f39………………………….
3e8f39………………………….
</syntaxhighlight>
</syntaxhighlight>  


* Inside the container, download the books which you would use in building your database:
*Inside the container, download the books which you would use in building your database:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ docker ps -a
$ docker ps -a
Line 243: Line 266:
                                 Dload  Upload  Total  Spent    Left  Speed
                                 Dload  Upload  Total  Spent    Left  Speed
100 12151  100 12151    0    0  62264      0 --:--:-- --:--:-- --:--:-- 62634
100 12151  100 12151    0    0  62264      0 --:--:-- --:--:-- --:--:-- 62634
</syntaxhighlight>
</syntaxhighlight>  


* Run the database in a safe mode:
*Run the database in a safe mode:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ mysqld_safe --skip-grant-tables &
$ mysqld_safe --skip-grant-tables &
</syntaxhighlight>
</syntaxhighlight>  


* In a new terminal, login as root user. Type in any random password at the prompt.
*In a new terminal, login as root user. Type in any random password at the prompt.
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ mysql -u root -p
$ mysql -u root -p
Line 258: Line 281:
……………………………………………………….
……………………………………………………….
MariaDB [(none)]>
MariaDB [(none)]>
</syntaxhighlight>
</syntaxhighlight>  


* Create your database:
*Create your database:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
MariaDB [books]> create database books;
MariaDB [books]> create database books;
Line 280: Line 303:
MariaDB [books]> exit
MariaDB [books]> exit
Bye
Bye
</syntaxhighlight>
</syntaxhighlight>  


* Test your access as user bookdb:
*Test your access as user bookdb:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ mysql -u bookdb -pbookdbpass books
$ mysql -u bookdb -pbookdbpass books
Line 296: Line 319:
$ exit
$ exit
Bye
Bye
</syntaxhighlight>
</syntaxhighlight>  


* Exit the container and save the Docker image:
*Exit the container and save the Docker image:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ exit
$ exit
$ docker commit --change='CMD ["/usr/bin/mysqld_safe"]' --change='EXPOSE 3306' <containerid> bookdb
$ docker commit --change='CMD ["/usr/bin/mysqld_safe"]' --change='EXPOSE 3306' <containerid> bookdb
</syntaxhighlight>
</syntaxhighlight>  


* After the container runs to your satisfaction, kill the container:
*After the container runs to your satisfaction, kill the container:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ docker ps
$ docker ps
Line 310: Line 333:
</syntaxhighlight>
</syntaxhighlight>


To create a web server that will access the database, you will create the following:
To create a web server that will access the database, you will create the following:  
* A .php file.
*A .php file.
* A Dockerfile to create the web server docker image.
*A Dockerfile to create the web server docker image.
* A bookdbdeployment.yml file to run the mariadb database.
*A bookdbdeployment.yml file to run the mariadb database.
* A bookdbservice.yml file to make MariaDB accessible under the name needed for the app - bookdbserver (internally in k8s this works by registering the service name with cluster local DNS service).
*A bookdbservice.yml file to make MariaDB accessible under the name needed for the app - bookdbserver (internally in k8s this works by registering the service name with cluster local DNS service).
* A bookdbapp.yml deployment file
*A bookdbapp.yml deployment file
* A bookdbapp.yml service file
*A bookdbapp.yml service file


'''index.php file''':
'''index.php file''':
Line 350: Line 373:


'''Dockerfile''':
'''Dockerfile''':
<syntaxhighlight lang="bash">
<syntaxhighlight lang="dockerfile">
FROM ubuntu
FROM ubuntu
ENV DEBIAN_FRONTEND=noninteractive
ENV DEBIAN_FRONTEND=noninteractive
Line 358: Line 381:
EXPOSE 80
EXPOSE 80
CMD ["apachectl","-DFOREGROUND"]
CMD ["apachectl","-DFOREGROUND"]
</syntaxhighlight>
</syntaxhighlight>  


* Build your image:
*Build your image:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ docker build . --tag=bookdbapp
$ docker build . --tag=bookdbapp
Line 367: Line 390:
</syntaxhighlight>
</syntaxhighlight>


You should now be able to access the application via URL or the IP that your container runs on (run docker inspect bridge to confirm). You should now be able to run these images on minikube and add a second web server and a load-balancing service.  
You should now be able to access the application via URL or the IP that your container runs on (run docker inspect bridge to confirm). You should now be able to run these images on minikube and add a second web server and a load-balancing service.


* Tag and push the images to Docker Hub:
*Tag and push the images to Docker Hub:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
docker tag <imageid> <userid>/bookdb
docker tag <imageid> <userid>/bookdb
Line 375: Line 398:
docker push <userid>/bookdb
docker push <userid>/bookdb
docker push <userid>/bookdbapp
docker push <userid>/bookdbapp
</syntaxhighlight>
</syntaxhighlight>  


* Using a text editor of your choice, create the following scripts:
*Using a text editor of your choice, create the following scripts:


'''bookdb.yml'''
'''bookdb.yml'''
<syntaxhighlight lang="bash">
<syntaxhighlight lang="yaml">
apiVersion: apps/v1
apiVersion: apps/v1
kind: Deployment
kind: Deployment
Line 406: Line 429:


'''bookdbdeployment.yaml''':
'''bookdbdeployment.yaml''':
<syntaxhighlight lang="bash">
<syntaxhighlight lang="yaml">
kind: Service
kind: Service
apiVersion: v1
apiVersion: v1
Line 421: Line 444:


'''bookdbappdeployment.yaml''':
'''bookdbappdeployment.yaml''':
<syntaxhighlight lang="bash">
<syntaxhighlight lang="yaml">
apiVersion: apps/v1
apiVersion: apps/v1
kind: Deployment
kind: Deployment
Line 447: Line 470:


'''bookdbappservice.yaml''':
'''bookdbappservice.yaml''':
<syntaxhighlight lang="bash">
<syntaxhighlight lang="yaml">
kind: Service
kind: Service
apiVersion: v1
apiVersion: v1
Line 460: Line 483:
   port: 80
   port: 80
   targetPort: 80
   targetPort: 80
</syntaxhighlight>
</syntaxhighlight>  


* Run your service:
*Run your Service:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ minikube start
$ minikube start
Line 471: Line 494:
$ kubectl create -f bookdbappdeployment.yaml
$ kubectl create -f bookdbappdeployment.yaml
$ kubectl create -f bookdbappservice.yaml
$ kubectl create -f bookdbappservice.yaml
</syntaxhighlight>
</syntaxhighlight>  


* Test your minikube server:
*Test your minikube server:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ minikube service bookdbapp --url
$ minikube service bookdbapp --url
Line 479: Line 502:
</syntaxhighlight>
</syntaxhighlight>


'''Note''': Add <code>index.php</code> to the URL if you are getting the apache setup page.
'''Note''': Add <code>index.php</code> to the URL if you are getting the apache setup page.  


* Now that you have some applications running, you can check out the status of the deployments and services via the minikube dashboard.
*Now that you have some applications running, you can check out the status of the Deployments and Services via the minikube dashboard.
* After the container runs to your satisfaction, kill the container:
*After the container runs to your satisfaction, kill the container:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
$ kubectl delete service bookdbapp bookdbserver
$ kubectl delete service bookdbapp bookdbserver
Line 492: Line 515:
</syntaxhighlight>
</syntaxhighlight>


== Next Module ==
==Next Module==
Module 4: [[Kubernetes/Kubernetes Workshop/Autoscaling in Kubernetes|Autoscaling in Kubernetes]]  
Module 4: [[Kubernetes/Kubernetes Workshop/Autoscaling in Kubernetes|Autoscaling in Kubernetes]]  


== <div style="text-align: right; direction: ltr; margin-left: 1em;"> Previous Module ==
==<div style="text-align: right; direction: ltr; margin-left: 1em;"> Previous Module ==
<div style="text-align: right; direction: ltr; margin-left: 1em;"> Module 2: [[Kubernetes/Kubernetes Workshop/Build a service application on Kubernetes|Build a service application on Kubernetes]]
<div style="text-align: right; direction: ltr; margin-left: 1em;"> Module 2: [[Kubernetes/Kubernetes Workshop/Build a service application on Kubernetes|Build a service application on Kubernetes]]
<div>
<div>


[[Category:Kubernetes]] [[Category: How-To]]
[[Category:Kubernetes]]  
[[Category: How-To]]

Latest revision as of 14:37, 26 July 2022

Overview

At the end of this module, you should be able to:

  • Use a YAML formatted configuration file to run batch applications and services.
  • Setup a complex service of web servers, databases, and networking.
  • Run services on minikube via a YAML configuration file.

Step 1 - Creating Jobs with YAML

Up to this point, we have done the following to run an application on a Kubernetes cluster:

  1. Packaged it as a container
  2. Wrapped the container in a Pod
  3. Deployed it via the Kubernetes Command Line Interface (CLI)

While using the command line is convenient for learning, experimenting and troubleshooting, the preferred way of doing things is to declare the desired state of your application in a manifest file. Manifest files are written in YAMLand describe what an application should look like. It defines things like which image to use, how many replicas to run, how to perform updates, and more. Behind the scenes, Kubernetes continuously monitors your cluster and compares its actual state to the desired state in the manifest. If a discrepancy is found, Kubernetes takes care of reconciling the situation.

This declarative model is simple and powerful: you just tell Kubernetes what you want, and Kubernetes takes care of the how. This approach is less error-prone and lends itself to version control and reproducible deployments. It is also self-documenting.

In this section, we will work with the pywpchksumbot.py application from Module 1. We start by creating a YAML configuration to run the pywpchksumbot application as a single job:

  • Create the following YAML config file and save it as <filename>.yaml
apiVersion: batch/v1
kind: Job
metadata:
 name: pywpchksumbot            <== Job name
spec:
 template:                      <== Pod template definition
   spec:
     containers:
     - name: pywpchksumbot
       image: <your_username>/<image_name>:<tag>
       imagePullPolicy: IfNotPresent
       resources: {}
     restartPolicy: Never
 backoffLimit: 4                <== Number of retries before considering a Job as failed
  • Run your YAML script with the following commands:
$ minikube start

$ kubectl apply -f <file-name>.yaml
job.batch/pywpchksumbot created

$ kubectl get pods
NAME                   READY   STATUS             RESTARTS        AGE
pywpchksumbot-qsr86    0/1     Completed          0               23s

$ kubectl get jobs
NAME            COMPLETIONS   DURATION   AGE
pywpchksumbot   1/1           11s        76s
  • Delete all running instances with:
$ kubectl delete job <job_name>
job.batch "pywpchksumbot" deleted
  • Alternatively, instead of typing the job name, the YAML file can be used:
$ kubectl delete -f <file-name>.yaml
job.batch "pywpchksumbot" deleted


Step 2 - Cronjobs

A cronjob is a job scheduler on Unix-like operating systems. You will work with a cronjob that runs a program repeatedly with a syntax similar to crond.

  • Create a new YAML file. Your YAML file should contain a configuration similar to the snippet below:
apiVersion: batch/v1
kind: CronJob
metadata:
 name: cronpywpchksumbot
spec:
 schedule: "*/5 * * * *"
 jobTemplate:
   spec:
     template:
       spec:
         containers:
         - name: pywpchksumbot
           image: <your_username>/<image_name>:<tag>
           imagePullPolicy: IfNotPresent
         restartPolicy: OnFailure
  • Run your YAML script with the following commands:
$ kubectl create -f <file_name>.yaml
cronjob.batch/cronpywpchksumbot created

$ kubectl get pods

$ kubectl get cronjobs
NAME                SCHEDULE        SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cronpywpchksumbot   */5 * * * *    False       0        <none>          88s

$ kubectl describe cronjobs <cronjob_name>
  • You can change the schedule by editing the YAML file. Delete all running instances with:
$ kubectl delete job <job_name>
job.batch "pywpchksumbot" deleted
  • In the line for schedule, you can replace the current value with any time description of your choice. Re-run the script:
$ kubectl apply -f <file_name>.yaml
cronjob.batch/cronpywpchksumbot configured

$ kubectl describe cronjobs <cron_job>
  • Check for the schedule:
$ kubectl describe cronjob <cron_job> | grep -i schedule
Schedule:                      <value in schedule>
Last Schedule Time:  Day, Date Month Year Hour:Minute:Seconds +0000
  • Delete all running instances:
$ kubectl delete cronjobs --all
cronjob.batch "cronpywpchksumbot" deleted

Note:

  • Cronjobs with names longer than 52 characters silently fail to schedule jobs.
  • Pods would sometimes get stuck in the Pending state forever.
  • The scheduler would crash every 3 hours.
  • Flannel’s hostgw backend did not replace outdated route table entries.
  • You can study this article to understand how Stripe makes use of Cronjobs.

Step 3 - Deploying a Simple Web Server

In this section, you will deploy a web server using a YAML configuration file. The Docker image is that of the apache container you created in Module 2.

  • Create a new YAML file using the editor of your choice:
apiVersion: apps/v1
kind: Deployment
metadata:
 name: ndeploy
 labels:
   app: ndeploy
spec:
 replicas: 1
 strategy:
   type: RollingUpdate
 selector:
   matchLabels:
     app: ndeploy
 template:
   metadata:
     labels:
       app: ndeploy
   spec:
     containers:
      - name: ndeploy
        image: <your_username>/<image_name>:<tag>
        imagePullPolicy: Always

Note:

  • Add a label app to the Deployment and the resulting Pods (the template stanza).
  • Set the replicas to 1.
  • Always pull the image from DockerHub, whether it is present in your local Docker or containerd context. Do this to ensure you have the right image. However, pulling images from Docker Hub will consume bandwidth every time you start a new Pod.
  • Run your YAML script with the following commands:
$ kubectl create -f <file_name>.yaml
deployment.apps/ndeploy created

$ kubectl get pods

$ kubectl get deployments
NAME      READY   UP-TO-DATE   AVAILABLE   AGE
<name>   1/1     1            1           8s

$ kubectl describe deployment <deployment_name>

Note: The value of <deployment_name> should be the same as the value in the previous YAML file.

  • Create a new YAML file, using your favorite edit, to define your Service(s). Define the necessary Service(s). The Service will expose the Pod in the Deployment above and will use the app label in the Deployment and match it via the selector in the Service description:
kind: Service
apiVersion: v1
metadata:
 name: ndeploy
spec:
 selector:
     app: ndeploy
 type: NodePort
 ports:
 - protocol: TCP
   port: 80
   targetPort: 80
  • Run your YAML script with the following commands:
$ kubectl create -f <filename>.yaml
service/ndeploy created

$ kubectl get services
NAME         TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
ndeploy      NodePort   10.103.127.158       <pending>     80:30818/TCP   3m3s

$ kubectl describe service <service_name>

$ minikube service <service_name> --url
http://192.168.49.2:30818
  • Navigate to the URL with a browser or make a curl request on the machine you are running minikube on.
  • Delete all running instances:
$ kubectl delete service <service_name>
$ kubectl delete deployment <deployment_name>

Hands-on Demo: Deploying a Production Web Server

In this section, you will run a program that randomly selects and prints a book from a MySQL database (you can see it as a book recommendation server). This service will require a database server, two web servers, and a load balancer to make requests between them. There is an ongoing discussion around using k8s to host databases or stateful services. Still, for a non-production workload such as your demo application, k8s will work just fine.

The outlined steps below will help you setup and deploy a production ready web server:

Note: In this case, we won’t be using a Dockerfile, but will create the container manually.

  • Pull and run the MariaDB Docker container and build the database.
$ docker pull mariadb
$ docker run --name=<container-name> -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -d mariadb
3e8f39………………………….
  • Inside the container, download the books which you would use in building your database:
$ docker ps -a
$ docker exec -it <containerid> /bin/bash
$ apt-get update
$ apt-get install curl 
$ curl -o /var/lib/mysql/books.csv https://gist.githubusercontent.com/jaidevd/23aef12e9bf56c618c41/raw/c05e98672b8d52fa0cb94aad80f75eb78342e5d4/books.csv
 % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 12151  100 12151    0     0  62264      0 --:--:-- --:--:-- --:--:-- 62634
  • Run the database in a safe mode:
$ mysqld_safe --skip-grant-tables &
  • In a new terminal, login as root user. Type in any random password at the prompt.
$ mysql -u root -p
Enter password:
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 7
……………………………………………………….
MariaDB [(none)]>
  • Create your database:
MariaDB [books]> create database books;
Query OK, 1 row affected

MariaDB [books]> use books;
Database changed

MariaDB [books]> create table books ( title varchar(80), author varchar(80), genre varchar(30), pages int, publisher varchar(60) );
Query OK, 0 rows affected

MariaDB [books]> load data infile '/var/lib/mysql/books.csv' ignore into table books fields terminated by ',' enclosed by '"' lines terminated by '\n' ignore 1 rows;
Query OK, 211 rows affected, 1 warning (0.009 sec)
Records: 211  Deleted: 0  Skipped: 0  Warnings: 1
MariaDB [books]> create user 'bookdb'@'%' identified by 'bookdbpass';
Query OK, 0 rows affected
MariaDB [books]> grant all privileges on books.* to 'bookdb'@'%';
Query OK, 0 rows affected
MariaDB [books]> exit
Bye
  • Test your access as user bookdb:
$ mysql -u bookdb -pbookdbpass books
MariaDB [books]>
$ select count(*) from books;
+----------+
| count(*) |
+----------+
|      211 |
+----------+
1 row in set (0.008 sec)
$ select * from books;
$ exit
Bye
  • Exit the container and save the Docker image:
$ exit
$ docker commit --change='CMD ["/usr/bin/mysqld_safe"]' --change='EXPOSE 3306' <containerid> bookdb
  • After the container runs to your satisfaction, kill the container:
$ docker ps
$ docker kill <containerid>

To create a web server that will access the database, you will create the following:

  • A .php file.
  • A Dockerfile to create the web server docker image.
  • A bookdbdeployment.yml file to run the mariadb database.
  • A bookdbservice.yml file to make MariaDB accessible under the name needed for the app - bookdbserver (internally in k8s this works by registering the service name with cluster local DNS service).
  • A bookdbapp.yml deployment file
  • A bookdbapp.yml service file

index.php file:

<?php
$conn = new mysqli("bookdbserver", "bookdb", "bookdbpass", "books");
// Check connection
if ($conn->connect_error) {
 die("Connection failed: " . $conn->connect_error);
}
$sql = "SELECT count(*) AS total FROM books";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
 $count = $result->fetch_assoc();
 echo $count['total'];
 $offset = rand(1,$count['total']-1);
 $sql2 = "SELECT title, author from books LIMIT ".$offset.",1";
 $result2 = $conn->query($sql2);
 if ($result2->num_rows > 0) {
   echo "<HTML><BODY><TABLE>";
   // output data of each row
   while($row = $result2->fetch_assoc()) {
     echo "<TR><TD>".$row['title']."</TD><TD>".$row['author']."</TD></TR>";
   }
   echo "</TABLE></BODY></HTML>";
 }
} else {
 echo "0 results";
}
$conn->close();
?>

Dockerfile:

FROM ubuntu
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get install -y apache2 php php-mysql libapache2-mod-php
COPY index.php /var/www/html
EXPOSE 80
CMD ["apachectl","-DFOREGROUND"]
  • Build your image:
$ docker build . --tag=bookdbapp
$ docker run -p 80:80 --link <docker-container-name-from-step-1>:bookdpapp bookdpapp
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.3…………………………...

You should now be able to access the application via URL or the IP that your container runs on (run docker inspect bridge to confirm). You should now be able to run these images on minikube and add a second web server and a load-balancing service.

  • Tag and push the images to Docker Hub:
docker tag <imageid> <userid>/bookdb
docker tag <imageid> <userid>/bookdbapp
docker push <userid>/bookdb
docker push <userid>/bookdbapp
  • Using a text editor of your choice, create the following scripts:

bookdb.yml

apiVersion: apps/v1
kind: Deployment
metadata:
 name: bookdb
 labels:
   app: bookdb
spec:
 replicas: 1
 strategy:
   type: RollingUpdate
 selector:
   matchLabels:
     app: bookdb
 template:
   metadata:
     labels:
       app: bookdb
   spec:
     containers:
      - name: bookdb
        image: <userid>/bookdb:latest
        imagePullPolicy: Always

bookdbdeployment.yaml:

kind: Service
apiVersion: v1
metadata:
 name: bookdbserver
spec:
 selector:
   app: bookdb
 ports:
 - protocol: TCP
   port: 3306
   targetPort: 3306

bookdbappdeployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
 name: bookdbapp
 labels:
   app: bookdbapp
spec:
 replicas: 2
 strategy:
   type: RollingUpdate
 selector:
   matchLabels:
     app: bookdbapp
 template:
   metadata:
     labels:
       app: bookdbapp
   spec:
     containers:
       - name: bookdbapp
         image: <userid>/bookdbapp:latest
         imagePullPolicy: Always

bookdbappservice.yaml:

kind: Service
apiVersion: v1
metadata:
 name: bookdbapp
spec:
 selector:
   app: bookdbapp
 type: LoadBalancer
 ports:
 - protocol: TCP
   port: 80
   targetPort: 80
  • Run your Service:
$ minikube start
$ kubectl create -f bookdbdeployment.yaml
service/bookdbserver created
$ kubectl create -f bookdbservice.yaml
deployment.apps/bookdbapp created
$ kubectl create -f bookdbappdeployment.yaml
$ kubectl create -f bookdbappservice.yaml
  • Test your minikube server:
$ minikube service bookdbapp --url
$ curl <URL>

Note: Add index.php to the URL if you are getting the apache setup page.

  • Now that you have some applications running, you can check out the status of the Deployments and Services via the minikube dashboard.
  • After the container runs to your satisfaction, kill the container:
$ kubectl delete service bookdbapp bookdbserver
$ kubectl delete deployment bookdbapp bookdb
$ minikube stop

Next Module

Module 4: Autoscaling in Kubernetes

Previous Module

Module 2: Build a service application on Kubernetes