Kubernetes/Kubernetes Workshop/Step 3

From Wikitech

Step 3 Outcomes

  • Use a configuration file formatted in YAML to run batch applications and services
  • Run a more complicated service composed of webserver and database and networking.

At the end you should known how to run services on minikube via a YAML configfile.

Step 3: Infrastructure as Code in K8s - YAML

So far we used the k8s command line to run our jobs or services and reconfigure certain parameters (replicas at least). That is convenient to test out quick changes, but it is easy to lose control over the changes that were executed. K8s offers the option to put all parameters in a YAML structured config file, which gives us the opportunity to use source control to keep track of and document the various versions.

Hands-on: run the wpchksumbot via YAML

Let’s start with pywpchksumbot:

A YAML file for running a single job:

pywpchksumbot.yaml:

apiVersion: batch/v1
kind: Job
metadata:
 name: pywpchksumbot
spec:
 template:
   spec:
     containers:
     - name: pywpchksumbot
       image: <userid>/pywpchksumbot
       imagePullPolicy: IfNotPresent
       resources: {}
     restartPolicy: Never
 backoffLimit: 4

Run by:  

  • kubectl create -f pywpchksumbot.yaml
  • kubectl get pods
  • kubectl get jobs
  • To clean up:
    • kubectl delete job pywpchksumbot

Hands-on: cronjob wpchksumbot

There is also the type cronjob that will run a program repeatedly with a syntax similar to crond.

cron_mywpchksumbot.yaml:

apiVersion: batch/v1beta1
kind: CronJob
metadata:
 name: cronpywpchksumbot
spec:
 schedule: "*/5 * * * *"
 jobTemplate:
   spec:
     template:
       spec:
         containers:
         - name: pywpchksumbot
           image: <userid>/pywpchksumbot
           imagePullPolicy: IfNotPresent
         restartPolicy: OnFailure

Run by:  

  • kubectl create -f cron_mywpchksumbotpywpchksumbot.yaml
  • kubectl get pods
  • kubectl get cronjobs
  • kubectl describe cronjobs cronpywpchksumbot

To change the schedule, edit the file and apply:

  • sed -r -i 's?/5?/10?' cron_mywpchksumbot.yaml.yaml or your editor of choice
  • kubectl apply -f cron_mywpchksumbot.yaml
  • kubectl describe cronjobs cronpywpchksumbot
    • Check for the schedule kubectl describe cronjob cronpywpchksumbot | grep -i schedule
  • To clean up:
    • kubectl delete cronjobs --all

Read: https://stripe.com/blog/operating-kubernetes about use of cronjobs (scale, bugs, etc) at Stripe. Sample bugs (all fixed):

  • 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 didn’t replace outdated route table entries

Hands-on: a simple web server

There is also the type Deployment  that will run a server.

simpleapachedeployment.yaml:

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

Run by:  

  • kubectl create -f simpleapachedeployment.yaml
  • kubectl get pods
  • kubectl get deployments
  • kubectl describe deployment simpleapache

And define the necessary service. Just as with the command line option the service will expose the pod in the deployment above. It will use the “app” label in the deployment and match it via the selector in the service description.

simpleapacheservice.yaml:

kind: Service
apiVersion: v1
metadata:
 name: simpleapache
spec:
 selector:
   app: simpleapache
 type: LoadBalancer
 ports:
 - protocol: TCP
   port: 80
   targetPort: 80

Test by:

  • kubectl create -f simpleapacheservice.yaml
  • kubectl get services
  • kubectl describe service simpleapache
  • minikube service simpleapache --url
  • Go to the URL with a browser or curl on the machine you are running minikube on
  • To clean up:
    • kubectl delete service simpleapache
    • kubectl delete deployment simpleapache

Hands-on: a load balanced, database backed service

On to a more complicated service: index.php from below is a program that randomly selects a book from a mysql database and prints its - something like a book recommendation server. We will set up a database server and 2 web servers and loadbalance requests between them.

You might have heard that k8s is not good for databases or in general stateful services. That is true, even though the discussion is ongoing (link) but for a non-production workload such as this it will be ok.

Let’s build the database server and its docker image first:

  • Start by running an ubuntu image
    • docker run -it ubuntu /bin/bash
  • Inside this container install mariadb, edit config and start mysql:
    • apt-get update
    • apt-get -y install mariadb-server
    • apt-get -y install vim
      • change /etc/mysql/mariadb.conf.d/50-server.cnf 127.0.0.1 -> 0.0.0.0
    • /usr/bin/mysqld_safe
  • Create a table, load the data and create a user. To do so, in another window access the same docker container to create the tables
    • docker ps
    • docker exec -it <containerid> /bin/bash
  • Inside this container:
    • Download the text file with the books
    • apt-get install curl curl -o /var/lib/mysql/books.csv  https://gist.githubusercontent.com/jaidevd/23aef12e9bf56c618c41/raw/c05e98672b8d52fa0cb94aad80f75eb78342e5d4/books.csv
    • mysql -u root mysql
    • create database books;
    • use books;
    • create table books ( title varchar(80), author varchar(80), genre varchar(30), pages int, publisher varchar(60) );
    • load data infile '/var/lib/mysql/books.csv' ignore into table books fields terminated by ',' enclosed by '"' lines terminated by '\n' ignore 1 rows;
    • create user 'bookdb'@'%' identified by 'bookdbpass';
    • grant all privileges on books.* to 'bookdb'@'%';
    • exit
  • Test access
    • mysql -u bookdb -pbookdbpass books
    • select count(*) from books;
    • exit
  • If everything is fine save the docker image:
    • docker commit --change='CMD ["/usr/bin/mysqld_safe"]' --change='EXPOSE 3306' <containerid> bookdb
  • And kill the container:
  • docker ps
  • docker kill <containerid>

Test the database server by building a docker image with a mariadb client on it and connect over the network

  • docker run --name=dbserver -p 3306:3306 bookdb
  • docker run -it --link dbserver:bookdbserver ubuntu /bin/bash
    • apt-get update
    • apt-get install mariadb-client
    • mysql -h bookdbserver -u bookdb -p books
      • Password is bookdbpass
      • select * from books;
  • To use the IP address of the database container rather than the name dbserver which is setup with the --link option, get the ip address of the container from  the docker default network which is called “bridge”
    • docker inspect bridge
      • Find the IP address of the container in the output

Once this works we can create the web server that will access the database. Here is the mentioned PHP program that we can use:

index.php:

<?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();
?>

And a Dockerfile to create the web server docker image:

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"]

To build:

  • docker build . --tag=bookdbapp

And to run the container with:

  • docker run -p80:80 --link dbserver:bookdbserver bookdbapp

We should now be able to access the application as on URL http://172.17.0.3 (or the IP that it really runs on - see docker inspect bridge to check). Try your browser or curl on the machine that you are running on.

We should now be able to run these images on minikube and add a second web server and a load-balancing service. Upload the images to dockerhub first:

  • Create 2 new repositories: bookdb and bookapp
  • Tag the images accordingly and push
    • docker tag <imageid> <userid>/bookdb
    • docker tag <imageid> <userid>/bookdbapp
    • docker push <userid>/bookdb
    • docker push <userid>/bookdbapp

Ready for running on minikube.

We will need 4 YAML files:

  • bookdb deployment - will run the mariadb database
  • bookdb service - will make mariadb accessible under the name needed for the app “bookdbserver”
    • Internally in k8s this work by registering the service name with cluster local DNS service
  • bookdbapp deployment
  • bookdbapp service

bookdbdeployment.yaml:

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  

bookdbservice.yaml - note the name:

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

bookdbappdeployment.yaml - note 2 replicas:

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

And start on minikube:

  • kubectl create -f bookdbdeployment.yaml
    • kubectl get deployments
    • kubectl get pods
  • kubectl create -f bookdbservice.yaml
    • kubectl get services
  • kubectl create -f bookdbappdeployment.yaml
    • kubectl get deployments
    • kubectl get pods
  • kubectl create -f bookdbappservice.yaml
    • kubectl get services

And to test:

  • minikube service bookdbapp --url
    • curl  or browser to the URL
    • add index.php to the URL if you are getting the Apache setup page

Now that we have some applications running, check out the status of the deployments and services via: minikube dashboard

At the end, clean up the environment by:

  • kubectl delete service bookdbapp bookdbserver
  • kubectl delete deployment bookdbapp bookdb
  • minikube stop