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
imported>Mbaoma No edit summary |
imported>Mbaoma No edit summary |
||
Line 6: | Line 6: | ||
* Run services on minikube via a YAML configuration file. | * Run services on minikube via a YAML configuration file. | ||
== 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. | |||
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: | |||
<syntaxhighlight lang="bash"> | |||
apiVersion: batch/v1 | |||
kind: Job | |||
metadata: | |||
name: pywpchksumbot | |||
spec: | |||
template: | |||
spec: | |||
containers: | |||
- name: pywpchksumbot | |||
image: <your_username>/<image_name>:<tag> | |||
imagePullPolicy: IfNotPresent | |||
resources: {} | |||
restartPolicy: Never | |||
backoffLimit: 4 | |||
</syntaxhighlight> | |||
* Run your YAML script with the following commands: | |||
<syntaxhighlight lang="bash"> | |||
$ minikube start | |||
$ kubectl create -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 | |||
</syntaxhighlight> | |||
* Delete all running instances with: | |||
<syntaxhighlight lang="bash"> | |||
$ kubectl delete job <job_name> | |||
job.batch "pywpchksumbot" deleted | |||
</syntaxhighlight> | |||
* Alternatively, instead of typing the job name, the YAML file can be used: | |||
<syntaxhighlight lang="bash"> | |||
$ kubectl delete -f <file-name>.yaml | |||
job.batch "pywpchksumbot" deleted | |||
</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]. | |||
* Create a new YAML file. Your YAML file should contain a configuration similar to the snippet below: | |||
<syntaxhighlight lang="bash"> | |||
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 | |||
</syntaxhighlight> | |||
* Run your YAML script with the following commands: | |||
<syntaxhighlight lang="bash"> | |||
$ 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> | |||
</syntaxhighlight> | |||
* You can change the schedule by editing the YAML file. Delete all running instances with: | |||
<syntaxhighlight lang="bash"> | |||
$ kubectl delete job <job_name> | |||
job.batch "pywpchksumbot" deleted | |||
</syntaxhighlight> | |||
* 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"> | |||
$ kubectl apply -f <file_name>.yaml | |||
cronjob.batch/cronpywpchksumbot configured | |||
$ kubectl describe cronjobs <cron_job> | |||
</syntaxhighlight> | |||
* Check for the schedule: | |||
<syntaxhighlight lang="bash"> | |||
$ kubectl describe cronjob <cron_job> | grep -i schedule | |||
Schedule: <value in schedule> | |||
Last Schedule Time: Day, Date Month Year Hour:Minute:Seconds +0000 | |||
</syntaxhighlight> | |||
* Delete all running instances: | |||
<syntaxhighlight lang="bash"> | |||
$ kubectl delete cronjobs --all | |||
cronjob.batch "cronpywpchksumbot" deleted | |||
</syntaxhighlight> | |||
'''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 [https://stripe.com/blog/operating-kubernetes 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 [[Kubernetes/Kubernetes Workshop/Build a service application on Kubernetes|Module 2]]. | |||
* Create a new YAML file using the editor of you choice: | |||
<syntaxhighlight lang="bash"> | |||
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 | |||
</syntaxhighlight> | |||
'''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: | |||
<syntaxhighlight lang="bash"> | |||
$ 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> | |||
</syntaxhighlight> | |||
'''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: | |||
<syntaxhighlight lang="bash"> | |||
kind: Service | |||
apiVersion: v1 | |||
metadata: | |||
name: ndeploy | |||
spec: | |||
selector: | |||
app: ndeploy | |||
type: NodePort | |||
ports: | |||
- protocol: TCP | |||
port: 80 | |||
targetPort: 80 | |||
</syntaxhighlight> | |||
* Run your YAML script with the following commands: | |||
<syntaxhighlight lang="bash"> | |||
$ 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 | |||
</syntaxhighlight> | |||
* Navigate to the URL with a browser or make a curl request on the machine you are running minikube on. | |||
* Delete all running instances: | |||
<syntaxhighlight lang="bash"> | |||
$ kubectl delete service <service_name> | |||
$ kubectl delete deployment <deployment_name> | |||
</syntaxhighlight> | |||
== 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. | |||
<syntaxhighlight lang="bash"> | |||
$ docker pull mariadb | |||
$ docker run --name=<container-name> -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -d mariadb | |||
3e8f39…………………………. | |||
</syntaxhighlight> | |||
* Inside the container, download the books which you would use in building your database: | |||
<syntaxhighlight lang="bash"> | |||
$ 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 | |||
</syntaxhighlight> | |||
* Run the database in a safe mode: | |||
<syntaxhighlight lang="bash"> | |||
$ mysqld_safe --skip-grant-tables & | |||
</syntaxhighlight> | |||
* In a new terminal, login as root user. Type in any random password at the prompt. | |||
<syntaxhighlight lang="bash"> | |||
$ mysql -u root -p | |||
Enter password: | |||
Welcome to the MariaDB monitor. Commands end with ; or \g. | |||
Your MariaDB connection id is 7 | |||
………………………………………………………. | |||
MariaDB [(none)]> | |||
</syntaxhighlight> | |||
* Create your database: | |||
<syntaxhighlight lang="bash"> | |||
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 | |||
</syntaxhighlight> | |||
* Test your access as user bookdb: | |||
<syntaxhighlight lang="bash"> | |||
$ 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 | |||
</syntaxhighlight> | |||
* Exit the container and save the Docker image: | |||
<syntaxhighlight lang="bash"> | |||
$ exit | |||
$ docker commit --change='CMD ["/usr/bin/mysqld_safe"]' --change='EXPOSE 3306' <containerid> bookdb | |||
</syntaxhighlight> | |||
* After the container runs to your satisfaction, kill the container: | |||
<syntaxhighlight lang="bash"> | |||
$ docker ps | |||
$ docker kill <containerid> | |||
</syntaxhighlight> | |||
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''': | |||
<syntaxhighlight lang="bash"> | |||
<?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(); | |||
?> | |||
</syntaxhighlight> | |||
'''Dockerfile''': | |||
<syntaxhighlight lang="bash"> | |||
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"] | |||
</syntaxhighlight> | |||
* Build your image: | |||
<syntaxhighlight lang="bash"> | |||
$ 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…………………………... | |||
</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. | |||
* Tag and push the images to Docker Hub: | |||
<syntaxhighlight lang="bash"> | |||
docker tag <imageid> <userid>/bookdb | |||
docker tag <imageid> <userid>/bookdbapp | |||
docker push <userid>/bookdb | |||
docker push <userid>/bookdbapp | |||
</syntaxhighlight> | |||
* Using a text editor of your choice, create the following scripts: | |||
'''bookdb.yml''' | |||
<syntaxhighlight lang="bash"> | |||
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 | |||
</syntaxhighlight> | |||
'''bookdbdeployment.yaml''': | |||
<syntaxhighlight lang="bash"> | |||
kind: Service | |||
apiVersion: v1 | |||
metadata: | |||
name: bookdbserver | |||
spec: | |||
selector: | |||
app: bookdb | |||
ports: | |||
- protocol: TCP | |||
port: 3306 | |||
targetPort: 3306 | |||
</syntaxhighlight> | |||
'''bookdbappdeployment.yaml''': | |||
<syntaxhighlight lang="bash"> | |||
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 | |||
</syntaxhighlight> | |||
'''bookdbappservice.yaml''': | |||
<syntaxhighlight lang="bash"> | |||
kind: Service | |||
apiVersion: v1 | |||
metadata: | |||
name: bookdbapp | |||
spec: | |||
selector: | |||
app: bookdbapp | |||
type: LoadBalancer | |||
ports: | |||
- protocol: TCP | |||
port: 80 | |||
targetPort: 80 | |||
</syntaxhighlight> | |||
* Run your service: | |||
<syntaxhighlight lang="bash"> | |||
$ 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 | |||
</syntaxhighlight> | |||
* Test your minikube server: | |||
<syntaxhighlight lang="bash"> | |||
$ minikube service bookdbapp --url | |||
$ curl <URL> | |||
</syntaxhighlight> | |||
'''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. | |||
* After the container runs to your satisfaction, kill the container: | |||
<syntaxhighlight lang="bash"> | |||
$ kubectl delete service bookdbapp bookdbserver | |||
$ kubectl delete deployment bookdbapp bookdb | |||
$ minikube stop | |||
</syntaxhighlight> | |||
<syntaxhighlight lang="bash"> | |||
</syntaxhighlight> | |||
== Next Module == | == Next Module == | ||
Module 4: 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 == |
Revision as of 18:23, 14 June 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
You can use the 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 YAML structured configuration file, allowing you to use source control to keep track of and document the various versions of services you build. You can work with the pywpchksumbot.py application, which you built in 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:
apiVersion: batch/v1
kind: Job
metadata:
name: pywpchksumbot
spec:
template:
spec:
containers:
- name: pywpchksumbot
image: <your_username>/<image_name>:<tag>
imagePullPolicy: IfNotPresent
resources: {}
restartPolicy: Never
backoffLimit: 4
- Run your YAML script with the following commands:
$ minikube start
$ kubectl create -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 you 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