Kubernetes Tutorial

Note: You can complete this tutorial with a partner if you have trouble downloading/installing Docker Desktop or any of the Kubernetes software.

Prerequisites

  1. Install the Kubernetes command-line tool, kubectl, so that you can communicate with Kubernetes clusters.
    1. Follow the instructions in the link to install the proper version depending on your operating system (Linux, MacOS, or Windows). You should use version v1.16.0, but if you can't run it, don't worry about it.
  2. Install Minikube, a tool that runs a single-node Kubernetes cluster in a virtual machine on your personal computer.
    1. Follow the instructions in the link to install Minikube depending on your operating system (Linux, MacOS, or Windows).
    2. Install VirtualBox as your hypervisor.
    3. Use the binary download or direct download instructions to install Minikube.

1. Build a Python web application

First, we will build a simple Python web application that returns "Hello world" in a few different languages when you visit the web page.

  1. Working off of your lecture25 branch, create a L25/ directory in your CS207 course repo.
  2. Please download the hello_world_server.py file from the Lecture 25 page on the course website and place it in your L25/ directory.
  3. If you are running Python 3, then you should be able to run the web application using python hello_world_server.py in your Terminal window.
  4. Visit http://localhost:8080/ in a web browser to make sure your application is up and running. You should get a web page that says Could not connect to database.. This is because our web server is trying to connect to a "database" to retrieve information about what should be displayed on the web page, but we have not started up our database server.
  5. Let's start up a database that our hello world server can connect to. This requires us to start up a second web server that will host our database. In this example, our database is really just a Python list.
    1. Please download the hello_world_db.py file from the Lecture 25 page on the course website and place it in your L25/ directory. Take a look at the .py file to see that the database is just a Python list.
    2. Open a second Terminal window and change directories into your L25/ directory.
    3. Run python hello_world_db.py in your Terminal window.
    4. You should see a message beginning with "Created HTTP server to run our database...", which means you have successfully started a web server with a "database".
  6. Now, go back to http://localhost:8080/ in a web browser. You should see a web page that says "Hello world!" or its translation in Spanish, French, German, Mandarin, or Hindi. If you reload the page several times, you should see the translation changes.
    1. Although this is a simple example, you are running a fully functioning web application using two individual web servers on your computer!

Below is a schematic of the web application you have built. The servers are running on top of your OS, using your native Python. Because you are running the servers locally on your laptop, you are restricted to the localhost IP address to connect the two servers. As a result, a user can technically access your database directly by sending a request to http://localhost:8081, which is not what you want.

  1. Shut down the web servers in your Terminal windows by pressing Control-C in both windows.

2. Dockerize your Python web application

Going forward in this tutorial will require that you have Docker Desktop downloaded on your machine. If you were unable to download Docker Destop for lecture 15, please complete the rest of this exercise with a partner. For your convenience, the instructions for downloading Docker Desktop are provided below.

Installing Docker Desktop

  1. Install Docker Desktop. Use one of the links below to download the proper Docker application depending on your operating system. You will need to make an account on dockerhub.
    1. For Mac users, follow this link- https://docs.docker.com/docker-for-mac/install/.
    2. For Windows users, follow this link- https://docs.docker.com/docker-for-windows/install/
      1. You will need to install Hyper-V to get Docker to work.
    3. For Linux users, follow this link- https://docs.docker.com/install/linux/docker-ce/ubuntu/
  2. Open a Terminal window and type docker run hello-world to make sure Docker is installed properly.

If you are working on a Windows machine, we are aware of some issues with downloading and installing Docker Desktop depending on your version of Windows. If you have any issue, please do not worry since this lecture exercise can be completed by working in pairs.

Building a Docker image

We will now put our "Hello world" python web application into 2 individual Docker containers.

  1. First, we must create a network on which our Docker containers can communicate with each other.
    1. Run docker network create helloNetwork.
    2. Run docker network ls. You should see helloNetwork in the list.
  2. Let's create a Docker container to run our database server now.
    1. Open a new Terminal window and change directories to the L25/ directory.
    2. Please download the Dockerfile_db file from the Lecture 25 page on the course website and place it in your L25/ directory.
    3. Run docker build -t cs207-lecture25:db -f Dockerfile_db . to build a Docker image for our database server.
      1. If you look at the build log, you will see that this Docker image also installs numpy just for the container. This is useful in the situation that different parts of our application rely on different versions of numpy or some other package. This encapsulation and isolation actually allows for more efficient development of individual components of an application because the back-end of the components can be changed and upgraded independently as long as the communication lines (API calls between the components remain consistent.
      2. The -t flag tells the build command to tag your Docker image with a specific name. Check out this link for an in-depth explanation of Docker tags.
      3. The -f flag tells the build command the name of the Dockerfile to use to build your image.
    4. Run docker image ls to see that we created a new Docker image with the tag db.
    5. Run docker run --name db -d --network helloNetwork cs207-lecture25:db in your terminal window.
      1. The --name flag allows us to name the container.
      2. The -d flag tells Docker to run this container in the background.
      3. The --network flag tells Docker which network we want the container to be connected to. Isolating containers to a specific network allows us to provide singular communication lines between different parts of an application and can prevent unwanted breaches. In this case, the container we just started is our database server and we do not want people on the outside to have access to it. Try going to http://localhost:8081/ to see if you can access the database server (hint: you should get a connection error).
    6. Check that our container is connected to the helloNetwork we created by running docker network inspect helloNetwork. You should see a dictionary with your Docker container.
  3. Let's create a Docker container for our front-end web server now.
    1. Please download the Dockerfile_server file from the Lecture 25 page on the course website and place it in your L25/ directory.
    2. Run docker build -t cs207-lecture25:server -f Dockerfile_server .
    3. Run docker image ls to see that we created a new Docker image with the tag server.
    4. Run docker run --name webServer -d -p 8080:8080 -e DB_URL=http://db:8081 --network helloNetwork cs207-lecture25:server.
      1. The -e flag allows us to specify an environment variable for our Docker container. In this case, we specify the URL through which our front-end server should communicate with our database server on the private helloNetwork network. If you look at Dockerfile_server file, you will see that it defines the DB_URL and passes it to the Python call to hello_world_server.py.
      2. By using the -p 8080:8080 flag, we expose port 8080 to the outside so our web page can be accessed.
  4. If visit http://localhost:8080/, you will see that our "Hello world" application is working again. If you refresh the page a few times, you should see "Hello world!" in different languages.

Below is a schematic of your Dockerized web application. The two servers are running in individual Docker containers on top of your OS. Since we created a separate network for our containers to communicate on, you can no longer directly access your database by sending a request to http://localhost:8081. This is the type of encapsulation you would like for your database because it generally stores sensitive information.

  1. Clean-up steps:
    1. Stop your containers with docker stop $(docker container ls -q). This may take a few seconds.
    2. Delete your containers with docker rm $(docker ps -aq). Beware, this will stop all containers you have running on your computer, so if you have containers running for other classes, you will have to remove the containers using the container IDs.
    3. Use docker rmi to delete your images, if you would like to.

3. Deploy your container to a Kubernetes Cluster

Prerequites

You will need to have installed kubectl and Minikube according to the instructions above.
This part of the Kubernetes tutorial was adapted from https://medium.com/@yzhong.cs/getting-started-with-kubernetes-and-docker-with-minikube-b413d4deeb92.

  1. Run minikube start in your Terminal window.

    1. You should see several lines beginning with emojis. The last line should say Done! kubectl is now configured to use "minikube".
    2. You now have a virtual cluster running on your machine/computer. You can think of this cluster as running a virtual machine on your personal computer.
  2. Kubernetes organizes Docker containers into Pods. Docker containers in the same pod share CPU allocation and memory. Typically, you would want multiple Docker containers in the same pod because they must interact to achieve some process, such as dealing with reads and writes to a database. Some info on why a pod may have multiple containers.

    1. To create a pod, we must also create a Deployment, which is basically a set of rules for how much CPU and memory a pod should have access to and different labels/names for a pod. Additionally, a deployment specifies what should happen to a pod if it stops running.
    2. Deployments can be configured from the command-line, but this becomes difficult when you have many parameters to specify. Therefore, users typically generate a YAML file specifying the details of their deployment, which is what we will do as well. It is convention to name the deployment object the same as the YAML file.
      1. Please download the hello_world_db_deployment.yaml file from the Lecture 25 page on the course website and place it in your L25/ directory.
      2. The name of our Deployment is hello-world-db-deployment.
      3. We only want one pod for our deployment, as indicated by replicas: 1.
      4. The selector defines how the Deployment finds which Pod(s) to manage. In this case, we simply select a label that is defined in the Pod template. That’s what the two books-app fields are for.
      5. We specify the Docker image and version we want to use- bhavenp/cs207-lecture25:db. The imagePullPolicy is set to Always since we want to pull the Docker image from Docker Hub whenever we create a new Pod. I already pushed our database server Docker image to a repository on Docker hub, making it easier for us to deploy the Docker image with our web application using Kubernetes.
      6. You can see that we expose port 8081 for the Pod.
    3. We can create a deployment for our Docker image using kubectl create -f hello_world_db_deployment.yaml.
    4. You should get a message saying "deployment.apps/hello-world created".
  3. You can now use the kubectl get deployments command to see that your deployment is available, meaning it is ready to receive HTTP requests. Use kubectl get pods command to see that pods are running.

Now, the Pod is running our database server running on our Kubernetes cluster, however this server is not exposed to the outside world because it is encapsulated within the cluster. Check this by trying to connect to http://localhost:8081/. You should get a connection error. We need to now create a deployment for our front-end web server, so that we can access our web application.

  1. Let's create a deployment for our front-end server.

    1. Please download the hello_world_server_deployment.yaml file from the Lecture 25 page on the course website and place it in your L25/ directory.
    2. Remember that we need to have our front-end server connect to our database server, like we accomplished when we were just running the Docker containers on our local machines. To accomplish this, we need to know the internal IP address that Kubernetes has assigned to the Pod running our database server. This IP address can only be used from within the Kubernetes cluster. We can get this IP by running kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP.
    3. Open up the hello_world_server_deployment.yaml file and make sure the IP you get for the hello-world-db-deployment is the same IP address specified in the value field for the DB_URL. If it is the same, then you're good to go. If it is different, then please change it.
      1. You will also see that we specify port 8080 as a communication port for the Pod.
    4. Now, we can run kubectl create -f hello_world_server_deployment.yaml to create a deployment for our front-end web server.
    5. Use kubectl get deployments and kubectl get pods to see that your pods are running.
  2. Now that we have both parts of our application running, try visiting http://localhost:8080/. Can you access the web application? The answer should be no because although we have exposed ports for our Pods, they are not accessible to the outside world. So far, we can only communicate with the Pods running our application if we are within the Kubernetes cluster (again, this is for isolation purposes). Let's check this out from within the cluster.

    1. Run kubectl get pods -o=custom-columns=NAME:.metadata.name,IP:.status.podIP and copy the IP address for the hello-world-server-deployment.
    2. Run minikube ssh in your Terminal window. You should see "minikube" written in you Terminal window.
    3. Run curl :8080/ several times. You should get responses for "Hello world!" in different languages, demonstrating that the application is working.
    4. Run exit to exit the minikube Terminal.
  3. To be able to access our web application from outisde, we need to create a Kubernetes Service to make the hello-world container accessible from outside the Kubernetes virtual network.

    1. Use kubectl expose deployment hello-world-server-deployment --type=LoadBalancer --port=8080 to allow your container to receive HTTP request from outside.
    2. You should get a message that says "service/hello-world exposed".
  4. You can view the status of your sercice by using the kubectl get services command.

    1. Notice that the EXTERNAL-IP for our service is pending. If we were running our Kubernetes cluster using a cloud provider, such as AWS, we would get an actual IP address to access our service from anywhere.
    2. Since we are running Minikube, you can access your service by using the minikube service hello-world-server-deployment. This should automatically open a web page with our Hello world! page. Reload this page a few times to see the different "Hello world!" translations.
  5. Congratulations! You have deployed a web application using Kubernetes!

Below is a schematic of your Dockerized web application on a Kubernetes (K8s) cluster. The two servers are running in individual Docker containers within the Kubernetes cluster, which is emulated by a VM in your case. Generally, a Kubernetes cluster would exists across serveral machines on a cloud provider like AWS. The K8s cluster takes care of generating a network for us and provides individual IP addresses for each of our Pods, reducing our work. By creating a K8s service for your web application, you provide outside users an entry point to use your application while protecting your database.

  1. You can clean up the resources and cluster using:
    1. kubectl delete service hello-world-server-deployment
    2. kubectl delete deployment hello-world-server-deployment
    3. kubectl delete deployment hello-world-db-deployment
    4. minikube stop
    5. minikube delete