Tutorial: How to orchestrate stateless apps in Kubernetes?
Tags: kubernetes, tutorial, showdev, beginners
Published: 2019-09-09 16:17:04

Hello Kubernauts! Welcome to the "Kubernetes in a nutshell" blog series :-)

This is the first part that will cover native Kubernetes primitives for managing stateless applications. One of the most common use cases for Kubernetes is to orchestrate and operate stateless services. In Kubernetes, you need a `Pod` (or a *group* of `Pod`s in most cases) to represent a service or application - but there is more to it! We will go beyond a basic `Pod` and get explore other high level components namely `ReplicaSet`s and `Deployment`s.

As always, the code is [available on GitHub](https://github.com/abhirockzz/kubernetes-in-a-nutshell)

![](https://media.giphy.com/media/l0IyjiXOXTX6Yemsg/giphy.gif)

You will need a Kubernetes cluster to begin with. This could be a simple, single-node local cluster using [`minikube`](https://kubernetes.io/docs/setup/learning-environment/minikube/), [`Docker for Mac`](https://blog.docker.com/2018/01/docker-mac-kubernetes/) etc. or a managed Kubernetes service from [Azure (AKS)](https://docs.microsoft.com/azure/aks/?WT.mc_id=devto-blog-abhishgu), [Google](https://cloud.google.com/kubernetes-engine/), [AWS](https://aws.amazon.com/eks/) etc. To access your Kubernetes cluster, you will need [`kubectl`](https://kubernetes.io/docs/reference/kubectl/overview/), which is pretty [easy to install](https://kubernetes.io/docs/tasks/tools/install-kubectl/).

e.g. to install `kubectl` for Mac, all you need is

    curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl && \
    chmod +x ./kubectl && \
    sudo mv ./kubectl /usr/local/bin/kubectl

In case you already have the [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest&WT.mc_id=devto-blog-abhishgu) installed, all you need to do is [`az acs kubernetes install-cli`](https://docs.microsoft.com/cli/azure/acs/kubernetes?view=azure-cli-latest&WT.mc_id=devto-blog-abhishgu#az-acs-kubernetes-install-cli).

> If you are interested in learning Kubernetes and Containers using [Azure](https://azure.microsoft.com/services/kubernetes-service/?WT.mc_id=devto-blog-abhishgu), simply [create a **free** account](https://azure.microsoft.com/en-us/free/?WT.mc_id=devto-blog-abhishgu) and get going! A good starting point is to use the [quickstarts, tutorials and code samples](https://docs.microsoft.com/azure/aks/?WT.mc_id=devto-blog-abhishgu) in the documentation to familiarize yourself with the service. I also highly recommend checking out the [50 days Kubernetes Learning Path](https://azure.microsoft.com/resources/kubernetes-learning-path/?WT.mc_id=devto-blog-abhishgu). Advanced users might want to refer to [Kubernetes best practices](https://docs.microsoft.com/azure/aks/best-practices?WT.mc_id=devto-blog-abhishgu) or the watch some of the [videos](https://azure.microsoft.com/resources/videos/index/?services=kubernetes-service&WT.mc_id=devto-blog-abhishgu) for demos, top features and technical sessions.

Let's start off by understanding the concept of a `Pod`.

## `Pod`

A `Pod` is the smallest possible abstraction in Kubernetes and it can have one or more containers running within it. These containers share resources (storage, volume) and can communicate with each other over `localhost`.

Create a simple `Pod` using the YAML file below. 

> `Pod` is just a Kubernetes resource or object. The YAML file is something that describes its desired state along with some basic information - it is also referred to as a `manifest`, `spec` (shorthand for specification) or `definition`.

{% gist https://gist.github.com/abhirockzz/8235c7b933f05e6d56b047e4bd0b293d %}

As a part of the `Pod` spec, we convey our intent to run [`nginx`](https://www.nginx.com/) in Kubernetes and use the `spec.containers.image` to point to its container image on [DockerHub](https://hub.docker.com/_/nginx). 

Use the [`kubectl apply`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#apply) command to submit the `Pod` information to Kubernetes.

> To keep things simple, the YAML file is being referenced directly from the [GitHub repo](https://github.com/abhirockzz/kubernetes-in-a-nutshell), but you can also download the file to your local machine and use it in the same way.

    $ kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/stateless-apps/kin-stateless-pod.yaml
    
    pod/kin-stateless-1 created
    
    $ kubectl get pods
    
    NAME                               READY   STATUS    RESTARTS   AGE
    kin-stateless-1                    1/1     Running   0          10s

This should work as expected. Now, let’s delete the `Pod` and see what happens. For this, we will need to use `kubectl delete pod <pod_name>`

    $ kubectl delete pod kin-stateless-1
    pod "kin-stateless-1" deleted
    
    $ kubectl get pods
    No resources found.


![](https://media.giphy.com/media/VbvjcQp6cYYYE/giphy.gif)

For serious applications, you have to take care of the following aspects:

- **High availability and resiliency** — Ideally, your application should be robust enough to self-heal and remain available in face of failure e.g. `Pod` deletion due to node failure, etc.
- **Scalability** — What if a single instance of your app (`Pod`) does not suffice? Wouldn’t you want to run replicas/multiple instances?

Once you have multiple application instances running across the cluster, you will need to think about:

- **Scale** — Can you count on the underlying platform to handle horizontal scaling automatically?
- **Accessing your application** — How do clients (internal or external) reach your application and how is the traffic regulated across multiple instances (`Pod`s)?
- **Upgrades** — How can you handle application updates in a non-disruptive manner i.e. without downtime?

Enough about problems. Let’s look into some possible solutions!

## Pod Controllers

Although it is possible to create `Pod`s directly, it makes sense to use higher-level components that Kubernetes provides on top of `Pod`s in order to solve the above mentioned problems. In simple words, these components (also called `Controllers`) can create and manage a group of `Pod`s.

The following controllers work in the context of `Pod`s and stateless apps:

- **ReplicaSet**
- **Deployment**
- **ReplicationController**

> There are other `Pod` controllers like `StatefulSet`, `Job`, `DaemonSet` etc. but they are not relevant to stateless apps, hence not discussed here

## ReplicaSet

A `ReplicaSet` can be used to ensure that a fixed number of replicas/instances of your application (`Pod`) are always available. It identifies the group of `Pod`s that it needs to manage with the help of (user-defined) selector and orchestrates them (creates or deletes) to maintain the desired instance count.

Here is what a common `ReplicaSet` spec looks like

{% gist https://gist.github.com/abhirockzz/c3e5f23677cfcc14270978eae74084a5 %}

Let's create the `ReplicaSet`


    $ kubectl apply -f  https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/stateless-apps/kin-stateless-replicaset.yaml
    
    replicaset.apps/kin-stateless-rs created

    $ kubectl get replicasets
    
    NAME               DESIRED   CURRENT   READY   AGE
    kin-stateless-rs   2         2         2       1m11s

    $ kubectl get pods --selector=app=kin-stateless-rs
    
    NAME                     READY   STATUS    RESTARTS   AGE
    kin-stateless-rs-zn4p2   1/1     Running   0          13s
    kin-stateless-rs-zxp5d   1/1     Running   0          13s

Our `ReplicaSet` object (named `kin-stateless-rs`) was created along with two `Pod`s (notice that the names of the `Pod`s contain a random alphanumeric string e.g. `zn4p2`)

This was as per what we had supplied in the YAML (spec):

- `spec.replicas` was set to `two`
- `selector.matchLabels` was set to `app: kin-stateless-rs` and matched the `.spec.template.metadata.labels` field in the `Pod` specification. 

> [Labels are simple key-value pairs](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) which can be added to objects (such as a `Pod` in this case).

We used `--selector` in the `kubectl get` command to filter the `Pod`s based on their labels which in this case was `app=kin-stateless-rs`.

Try deleting one of the `Pod`s (just like you did in the previous case) 

> Please note that the `Pod` name will be different in your case, so make sure you use the right one.

    $ kubectl delete pod kin-stateless-rs-zxp5d
    
    pod "kin-stateless-rs-zxp5d" deleted

    $ kubectl get pods -l=app=kin-stateless-rs
    
    NAME                     READY   STATUS    RESTARTS   AGE
    kin-stateless-rs-nghgk   1/1     Running   0          9s
    kin-stateless-rs-zn4p2   1/1     Running   0          5m

We still have two `Pod`s! This is because a new `Pod` (highlighted) was created to satisfy the replica count (two) of the `ReplicaSet`.

To scale your application horizontally, all you need to do is update the `spec.replicas` field in the manifest file and submit it again.


> As an exercise, try scaling it up to five replicas and then going back to three.

So far so good! But this does not solve all the problems. One of them is handling application updates — specifically, in a way that does not require downtime. Kubernetes provides another component which works on top of `ReplicaSet`s to handle this and more.

## Deployment

A `Deployment` is an abstraction which manages a `ReplicaSet` — recall from the previous section, that a `ReplicaSet` manages a group of Pods. In addition to elastic scalability, `Deployment`s provide other useful features that allow you to manage updates, rollback to a previous state, pause and resume the deployment process, etc. Let’s explore these.

A Kubernetes `Deployment` borrows the following features from its underlying `ReplicaSet`:

- **Resiliency** — If a Pod crashes, it is automatically restarted, thanks to the `ReplicaSet`. The only exception is when you set the `restartPolicy` in the `Pod` specification to `Never`.
- **Scaling** — This is also taken care of by the underlying `ReplicaSet` object.


This what a typical `Deployment` spec looks like

{% gist https://gist.github.com/abhirockzz/fdf95d8afee3d03cb7b0790e45975c8b %}

Create the `Deployment` and see which Kubernetes objects get created

    $ kubectl apply -f  https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/stateless-apps/kin-stateless-deployment.yaml
    deployment.apps/kin-stateless-depl created
    
    $ kubectl get deployment kin-stateless-dp
    NAME               READY   UP-TO-DATE   AVAILABLE   AGE
    kin-stateless-dp   2/2     2            2           10
    
    $ kubectl get replicasets
    NAME                         DESIRED   CURRENT   READY   AGE
    kin-stateless-dp-8f9b4d456   2         2         2       12
    
    $ kubectl get pods -l=app=kin-stateless-dp
    NAME                               READY   STATUS    RESTARTS   AGE
    kin-stateless-dp-8f9b4d456-csskb   1/1     Running   0          14s
    kin-stateless-dp-8f9b4d456-hhrj7   1/1     Running   0          14s

`Deployment` (`kin-stateless-dp`) got created along with the `ReplicaSet` and (two) `Pod`s as specified in the `spec.replicas` field. Great! Now, let’s peek into the `Pod` to see which `nginx` version we’re using — please note that the `Pod` name will be different in your case, so make sure you use the right one

    $ kubectl exec kin-stateless-dp-8f9b4d456-csskb -- nginx -v
    nginx version: nginx/1.17.3

This is because the `latest` tag of the `nginx` image was picked up from [DockerHub](https://hub.docker.com/_/nginx) which happens to be `v1.17.3` at the time of writing.

> **What's `kubectl exec`?** In simple words, it allows you to execute a command in specific container within a `Pod`. In this case, our `Pod` has a single container, so we don't need to specify one


### Update a `Deployment`

You can trigger an update to an existing `Deployment` by modifying the template section of the `Pod` spec — a common example being updating to a newer version (label) of a container image. You can specify it using `spec.strategy.type` of the `Deployment` manifest and valid options are - `Rolling` update and `Recreate`.

**Rolling update**

Rolling updates ensure that you don’t incur application downtime during the update process — this is because the update happens one `Pod` at a time. There is a point in time where both the previous and current versions of the application co-exist. The old `Pod`s are deleted once the update is complete, but there will a phase where the total number of `Pod`s in your Deployment will be more than the specified `replicas` count. 

It is possible to further tune this behavior using the `maxSurge` and `maxUnavailable` settings.

- `spec.strategy.rollingUpdate.maxSurge` — maximum no. of `Pod`s which can be created in addition to the specified replica count
- `spec.strategy.rollingUpdate.maxUnavailable` — defines the maximum no. of `Pod`s which are not available

**Recreate**

This is quite straightforward — the old set of Pods are deleted before the new versions are rolled out. You could have achieved the same results using `ReplicaSet`s by first deleting the old one and then creating a new one with the updated spec (e.g. new docker image etc.)

Let's try and update the application by specifying an explicit Docker image tag — in this case, we'll use `1.16.0`. This means that once we update our app, this version should reflect when we introspect our `Pod`.

Download the `Deployment` manifest above, update it to change `spec.containers.image` from `nginx` to `nginx:1.16.0` and submit it to the cluster - this will trigger an update

    $ kubectl apply -f deployment.yaml
    deployment.apps/kin-stateless-dp configured
    
    $ kubectl get pods -l=app=kin-stateless-dp
    NAME                                READY   STATUS    RESTARTS   AGE
    kin-stateless-dp-5b66475bd4-gvt4z   1/1     Running   0          49s
    kin-stateless-dp-5b66475bd4-tvfgl   1/1     Running   0          61s

You should now see a new set of `Pod`s (notice the names). To confirm the update:

    $ kubectl exec kin-stateless-dp-5b66475bd4-gvt4z -- nginx -v
    nginx version: nginx/1.16.0

> Please note that the `Pod` name will be different in your case, so make sure you use the right one

### Rollback

If things don't go as expected with the current `Deployment`, you can revert back to the previous version in case the new one is not working as expected. This is possible since Kubernetes stores the rollout history of a `Deployment` in the form of revisions.

![](https://media.giphy.com/media/dbtDDSvWErdf2/giphy.gif)

To check the history for the `Deployment`:

    $ kubectl rollout history deployment/kin-stateless-dp
    
    deployment.extensions/kin-stateless-dp
    
    REVISION  CHANGE-CAUSE
    1         <none>
    2         <none>

Notice that there are two revisions, with `2` being the latest one. We can roll back to the previous one using `kubectl rollout undo`

    $ kubectl rollout undo deployment kin-stateless-dp
    deployment.extensions/kin-stateless-dp rolled back
    
    $ kubectl get pods -l=app=kin-stateless-dp
    NAME                                READY   STATUS        RESTARTS   AGE
    kin-stateless-dp-5b66475bd4-gvt4z   0/1     Terminating   0          10m
    kin-stateless-dp-5b66475bd4-tvfgl   1/1     Terminating   0          10m
    kin-stateless-dp-8f9b4d456-d4v97    1/1     Running       0          14s
    kin-stateless-dp-8f9b4d456-mq7sb    1/1     Running       0          7s

Notice the intermediate state where Kubernetes was busy terminating the `Pod`s of the old `Deployment` while making sure that new `Pod`s are created in response to the rollback request.

If you check the `nginx` version again, you will see that the app has indeed been rolled back to `1.17.3`.

    $ kubectl exec kin-stateless-dp-8f9b4d456-d4v97 -- nginx -v
    nginx version: nginx/1.17.3

![](https://media.giphy.com/media/120jXUxrHF5QJ2/giphy.gif)

### Pause and Resume

It is also possible to pause a `Deployment` rollout and resume it back after applying changes to it (during the paused state).

## `ReplicationController`

A `ReplicationController` is similar to a `Deployment` or `ReplicaSet`. However, it is *not* a recommended approach for stateless app orchestration since a `Deployment` offers a richer set of capabilities (as described in the previous section). You can read more about them in the [Kubernetes documentation](https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/).


### References

Check out Kubernetes documentation for the API details of the resources we discussed in this post i.e. `Pod`, `ReplicaSet` and `Deployment`

- [Pod API reference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.15/#pod-v1-core)
- [ReplicaSet API reference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.15/#replicaset-v1-apps)
- [Deployment API reference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.15/#deployment-v1-apps)

**Stay tuned for more in the next part of the series!**

I really hope you enjoyed and learned something from this article! Please like and follow if you did. Happy to get feedback via [@abhi_tweeter](https://twitter.com/abhi_tweeter) or just drop a comment.