Kubernetes is an open source system used to automate management of containerized applications. A containerized application, simply means an application that runs inside a container. The management part refers, among other things to:

  • Deployment of changes
  • Rollbacks
  • Load balancing and discoverability
  • Secret and configuration management
  • Container placement
  • Scaling applications

Kubernetes clusters

A Kubernetes cluster is a set of machines that are configured in a way that allows us to tell Kubernetes that we need to run our application without having to worry about exactly where it runs. A Kubernetes cluster consists of two things:

  • Control Plane
  • Nodes

The Control Plane refers to hosts that are responsible for managing the cluster, while the Nodes are the hosts where applications are run.

Kubernetes Nodes need to have a container management system (Docker, for example) as well as an agent used for communicating with the Control Plane. The name of this agent is Kubelet.

When we need to perform and operation on the Kubernetes cluster (For example, deploy an application), we tell the Control Plane that we want to deploy the application. The Control Plane will then decide in which Nodes it should run the application and communicate with those nodes to get the application to the desired state.

Creating a cluster

Creating a cluster from scratch it’s not a simple task. Instead, we are going to create a cluster in Google Cloud. This cluster might incur some costs (Less than 1 USD if we run it for one day), but we are going it to tear it down at the end of this article.

I assume that you have a Google Cloud account and gcloud cli set up. You can take a look at my introduction to Google Cloud CLI, if you need help setting it up.

We can use this command to verify we are connected to the right account:

1
gcloud auth list

To list all our Kubernetes clusters we can use this command:

1
gcloud container clusters list

If we don’t have any clusters running, the command won’t return any output.

Before we create a cluster we need to install kubectl, which is a command line utility to interact with our Kubernetes cluster.

In Linux, we can download it:

1
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"

And then install it:

1
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

To keep costs at a minimum, we are going to be creating a cluster with only 1 node:

1
gcloud container clusters create our-test-cluster --zone us-central1-a --num-nodes 1

After some time, our cluster will be ready. We can see the status of the cluster using gcloud:

1
2
3
gcloud container clusters list
NAME              LOCATION     MASTER_VERSION   MASTER_IP       MACHINE_TYPE  NODE_VERSION     NUM_NODES  STATUS
our-test-cluster  us-central1  1.20.10-gke.301  104.197.87.172  e2-medium     1.20.10-gke.301  9          RUNNING

Configuring kubectl

Now that we have our cluster running, we need to configure kubectl so it can manage this cluster. kubectl, has the concept of context, which basically means the cluster the tool will talk to if no other cluster is specified with the --cluster flag.

To get the current context:

1
kubectl config current-context

If a context hasn’t been set, we will recieve a message saying so. To set our context we can:

1
gcloud container clusters get-credentials our-test-cluster --zone us-central1-a

After doing that, the context will be set:

1
2
kubectl config current-context
gke_test-123456_us-central1_our-test-cluster

The information kubectl uses to connect to a cluster is stored under ~/.kube/config.

Deploying an application

Now that we have kubectl set up, we can use it to deploy applications in our cluster.

Before we deploy our application we need to get familiar with some concepts. First of all, I’ll assume you are familiar with what an image and a container are in the Docker world. On top of these concepts, Kubernetes introduces the concept of pod. A pod is a group of one or more containers with shared storage and network resources. These containers always run together in a single node.

Basically, a pod contains a group of application containers that always need to run together.

Pods are the unit of deployment in Kubernetes. We don’t deploy containers, we deploy pods. We can deploy a pod that consists of a single container with this command:

1
kubectl create deployment echo-server-deployment --image=ealen/echo-server --replicas=1

echo-server-deployment is the name of our deployment. ealen/echo-server is the name of the docker image we are deploying, --replicas=1 means that we only want one instance of this pod to run.

We can see our deployments:

1
2
3
kubectl get deployments
NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
echo-server-deployment   1/1     1            1           2m10s

The READY column tells us how many of our replicas are ready to be used by our end users. It follows the format <ready>/<desired>.

UP-TO-DATE tells us how many replicas have been updated to the latest desired state. Currently it shows that all our pods are UP-TO-DATE, but during a rolling deployment this number might be lower than our total replicas for some time.

AVAILABLE tells us how many replicas are available to our users.

We can also see our pods:

1
2
3
kubectl get pods
NAME                                     READY   STATUS    RESTARTS   AGE
echo-server-deployment-c4dd99ffc-2kmkh   1/1     Running   0          2m34s

We can see the name of the pod starts with the name of our deployment. In this case, READY means how many containers of our pod are available to our users. Since our pod has only one container, it only shows 1/1.

Let’s scale our deployment so we are running two pods instead of just one:

1
kubectl scale deployment/echo-server-deployment --replicas=2

If we check our deployment, we will see that we now have 2 replicas:

1
2
3
kubectl get deployments
NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
echo-server-deployment   2/2     2            2           9m53s

If we inspect the pods, we’ll see that both of them are running in the same node because we only have one:

1
2
3
4
kubectl get pods -o wide
NAME                                     READY   STATUS    RESTARTS   AGE     IP          NODE                                              NOMINATED NODE   READINESS GATES
echo-server-deployment-c4dd99ffc-q52bj   1/1     Running   0          3m40s   10.0.0.12   gke-our-test-cluster-default-pool-ad01764f-trn6   <none>           <none>
echo-server-deployment-c4dd99ffc-s8g6t   1/1     Running   0          13m     10.0.0.10   gke-our-test-cluster-default-pool-ad01764f-trn6   <none>           <none>

If we describe the deployment we can see the change we made in the events:

1
2
3
4
5
6
7
8
9
10
kubectl describe deployment/echo-server-deployment

...

Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  17m   deployment-controller  Scaled up replica set echo-server-deployment-c4dd99ffc to 1
  Normal  ScalingReplicaSet  8m5s  deployment-controller  Scaled up replica set echo-server-deployment-c4dd99ffc to 2

We successfully scaled our application.

Troubleshooting

Kubernetes provides a set of tools that can be used to diagnose problems if they arise. Let’s look at some of them.

If we want to see all the pods in our cluster we can use:

1
kubectl describe pods

This will output a lot of information if we have a lot of pods in a cluster. If we only care about a single pod, we can use:

1
kubectl describe pods/<pod id>

The output of the describe command contains a lot of useful information. Some of it is the id of the node where the pod is running, information about the containers and their state, and the last events that have happened in the pod. If there are issues starting containers, or passing health checks, these will be visible in the events.

Another way to troubleshoot problems with a pod is to look at the logs:

1
kubectl logs <pod id>

If this is not enough, we can get a shell to a container:

1
kubectl exec -it <pod id> -- sh

If our pod has more than one container, we need to specify which container we want to connect to:

1
kubectl exec -it <pod id> -c <container name> -- sh

Services

When we deploy an application in Kubernetes, all pods are given a private IP address that allows them to communicate with other pods in the same cluster.

The problem with is that pods can come and go, so it’s hard to keep track of the IPs we need to call to reach a service. We need something like a load balancer where pods can register so it’s easy to find a specific service. This is what services do.

The simplest service type is the ClusterIP (which is the default). This creates an IP address in the private network and acts as a load balancer that will redirect requests to that IP to the desired pods.

By default, there is a service called kubernetes:

1
2
3
kubectl get services
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.3.240.1   <none>        443/TCP   31m

This service is used by pods to find the control plane host. To expose our own service, we can use the following command:

1
kubectl expose deployment/echo-server-deployment --port 80

We can now see our service:

1
2
3
4
kubectl get services
NAME                     TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
echo-server-deployment   ClusterIP   10.3.248.92   <none>        80/TCP    34s
kubernetes               ClusterIP   10.3.240.1    <none>        443/TCP   6m29s

Since the IP assigned to the service is internal to the cluster (10.3.248.92), we need to get in the cluster to test that it’s doing what we want. We can do this by getting a shell into a pod:

1
kubectl exec -it <pod id> -- sh

From inside the pod, we can use wget to make a request:

1
wget 10.3.248.92?query=test -O output

If we then check the pod logs, we are going to see that requests are balanced between both pods:

1
kubectl logs <pod id>

Cleaning up

Now that we are done playing around, we can destroy our cluster so we are not charged for it anymore:

1
gcloud container clusters delete our-test-cluster --zone us-central1-a

Conclusion

In this article we scratched the surface of what can be done with Kubernetes. We created a cluster and deployed some pods using a docker image. We also learned some debugging tools that will be used to diagnose problems in our future clusters.

[ architecture  docker  gcp  ]
Instrumenting an Istio Cluster With Jaeger Tracing
Kubernetes ConfigMaps
Kubernetes networking with Istio
Managing Kubernetes Objects With Yaml Configurations
GKE - Insufficient regional quota to satisfy request: resource "IN_USE_ADDRESSES"