Previously, we have created a simple job pod using

kubectl run job1 --image=nginx

In this post, we will see how we can create a Pod with our own executable.

All the code for this chapter (and other chapters) is available at https://github.com/param108/kubernetes101

Simple Web Service

If you go to the github link above, you will see a directory 011 which has the code and yaml snippets for this chapter. The directory looks like this

011
└── firstpod
    ├── Dockerfile
    ├── Makefile
    ├── webpod.yml
    ├── Readme.md
    └── web
        ├── go.mod
        ├── go.sum
        ├── Makefile
        ├── Readme.md
        ├── web
        └── web.go

Prerequisites

  1. docker
  2. golang (version >= 1.12)

I will leave it to you to install the pre-requisites using google. (suffering builds character :-))

Setup

To setup, checkout the repository to your local machine and build the docker image.

git clone https://github.com/param108/kubernetes101.git
cd kubernetes101/011/firstpod
make docker

This will tell you if the pre-requisites are setup correctly.

Next we need to setup minikube so that we can push the docker image into Minikube’s private docker registry.

Minikube

Minikube does *not* share the same docker registry as your laptop. If you push the image to your laptop docker registry, it will NOT be available for use with Minikube.

To point to Minikube’s docker server to push your images you need to do the following.

eval $(minikube -p minikube docker-env)

This sets up the following environment variables.

export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/home/param/.minikube/certs"
export MINIKUBE_ACTIVE_DOCKERD="minikube"

These tell the docker client to use Minikube’s docker service instead of that on the local machine. This needs to be setup in each shell.

Build the image in Minikube’s docker

In the same shell where you ran the eval command build the docker image again.

make docker # from the kubernetes101/011/firstpod/ directory

Dockerfile description

The web application listens on a port 8080 and responds to /ping and returns <html>ok</html>

FROM ubuntu:18.04

ADD web/web /web

RUN apt-get update && apt-get install -y wget

RUN wget https://dl.google.com/go/go1.13.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.13.linux-amd64.tar.gz

EXPOSE 8080

CMD /web

This dockerfile uses a base ubuntu 18.04 image and adds our compiled executable web as the /web path in the image. It then installs wget in order to install golang. Finally the Expose command is only informative, you can treat it as a comment. It means that the port 8080 should be opened on the host machine. Again its only informative and doesnt really do anything.

The CMD line tells docker which application to run once everything is setup. In this case our web executable.

Pod spec

In order to run this application in Kubernetes we need to create a PodSpec.

A podspec tells Kubernetes what to run and how.

apiVersion: "v1"
kind: Pod
metadata:
  name: web
  labels:
    app: web
    version: v1
    role: backend
spec:
  containers:
  - name: web
    image: web:latest
    imagePullPolicy: Never
    command: ["/web"]
    args: []
    ports:
      - containerPort: 8080
        protocol: TCP
        hostPort: 8080

imagePullPolicy: This decides how often kubernetes will try and pull the image from the public dockerhub. As our image (which we built above) isnt on dockerhub, we Never want to pull it.
ports:
containerPort: The port on the container that needs to be expose. Should use the same value as the EXPOSE command in the dockerfile.
hostPort: which port on the host we would like to listen on. This allows forms a tunnel from the hostPort to the containerPort. Allows us to connect to the Pod from outside the internal network of kubernetes.
containers: As you can see, this is a list of maps. Each entry corresponds to a containter, so we can have multiple containers in this pod and all will be started together. In this post we only have 1, but in the next post will have more.

Creating a pod

Lets create the pod by applying the file webpod.yml. (Its there in the repository).

kubectl apply -f webpod.yml

Lets check if its created.

$ kubectl get pods
NAME   READY   STATUS    RESTARTS   AGE
web    1/1     Running   0          10s

Lets see if its working.

$ curl localhost:8080
curl: (7) Failed to connect to localhost port 8080: Connection refused

This should work no? Actually No! You assume that our laptop is the node on which kubernetes is running. Minikube is running the kubernetes cluster on the VM it created! Lets look at that pod’s details.

$ kubectl get pods/web -o yaml
...
...  
hostIP: 192.168.99.100
phase: Running
podIP: 172.17.0.2
podIPs:
  - ip: 172.17.0.2
...
...

The hostIP is 192.168.99.100. Lets try to hit that.

$ curl 192.168.99.100:8080/ping
<html>ok</html>$

It works!

Checking for life

As the pod is listening on a port inside kubernetes, it must have an ip address no? If we get the ip address we should be able to hit it no ?

Get the details of the pod using the below command. The output will be quite large, but we are only interested in the section I have shown below.

$ kubectl get pods/web -o yaml
...
...  
hostIP: 192.168.99.100
phase: Running
podIP: 172.17.0.2
podIPs:
  - ip: 172.17.0.2
...
...

(to understand more about any kubernetes api you can find it here.)
hostIP: Is the IP of the host of the node on which the pod is running. In our case, as this is minikube its running in the VM it created.
podIP and podIPs: tell us the IPs allocated to the pod. These IPs unfortunately local to the cluster and not accessible from outside, even from your laptop.

In the normal case, you would use a service to expose pods to the outside world.

In GCP you can create VPC Native clusters which make podIPs available as local ip addresses available to other internal ips in the VPC network.

Getting IN from INSIDE

If you have the correct permissions, you can get into the pod using kubectl!

kubectl exec -it pods/web /bin/bash

So whats going on here? Well we are asking kubernetes to run the command /bin/bash on our container and asking it to run on a tty -it making it interactive.

$ kubectl exec -it pods/web /bin/bash
root@web:/# ls /web
/web
root@web:/# 

What you see here is that kubernetes has provided us a shell inside the web container! This is similar to the docker exec command.

root@web:/# apt install -y curl
...
...
...
root@web:/# curl localhost:8080/ping
<html>ok</html>root@web:/# 
root@web:/#  

voila! Type exit to leave the shell. Don’t worry it wont kill the pod!.

The sequel

This is actually not much of a test. We are just trying to hit the containers localhost from the container itself. This obviously works. What we really want to do is to hit the url from outside the pod. Say from another pod.

Lets create a new pod with a different name.

apiVersion: "v1"
kind: Pod
metadata:
  name: web2
  labels:
    app: web
    version: v1
    role: backend
spec:
  containers:
  - name: web
    image: web:latest
    imagePullPolicy: Never
    command: ["/web"]
    args: []
    ports:
      - containerPort: 8080
        protocol: TCP
        hostPort: 8081

Edit the webpod.yml as above and apply it again. The 2 changes are

  1. metadata:name – name has to be unique
  2. spec:ports:hostPort – has to be unique otherwise pod will remaining in pending state until the Port is “free“.

Now lets get the ip address of pods/web and hit it from pods/web2 using the exec command. The ip address of pods/web is 172.17.0.2 from above.

root@web2:/# apt install -y curl
...
...
...                                                                                         
root@web2:/# curl 172.17.0.2:8080/ping                                                                
<html>ok</html>root@web2:/#                                                                           
root@web2:/#                                                                                          

Learnings

minikube has its own docker registry which you can talk to!

pods get an internal ip which is not *usually* accessible from outside the kubernetes cluster

kubectl exec can be used to get into running pods!

any kubernetes spec definition can be found here.

Conclusion

With this, we have created our first pod. It doesn’t do anything but its a first step anyway. In the next post we will create a complete web service with postgres db running in one pod and look at the limitations of the approach.