Delicious Kubernetes in 4 Steps
Tags: kubernetes, devops, tutorial, docker
Published: 2019-12-12 18:47:00

<!--
Canonical: https://thenewstack.io/a-step-by-step-guide-to-continuous-deployment-on-kubernetes/
As seen on: https://semaphoreci.com/blog/guide-continuous-deployment-kubernetes
-->

# Delicious Kubernetes in 4 Steps

A long time ago, in a job far, far away, I was tasked with switching our old-school LAMP stacks over to Kubernetes. My boss at the time, always starry-eyed for new technologies, announced the change should only take a few days—a bold statement considering we didn’t even have a grasp on how containers worked yet.

After reading the official docs and Googling around, I began to feel overwhelmed. There were too many new concepts to learn: there were the pods, the containers, and the replicas. To me, it seemed Kubernetes was reserved for a clique of sophisticated developers.

This post is what I would have liked to read at that time: a short, simple, no-nonsense guide on how the heck I go about deploying an application in Kubernetes.

<!-- TODO: a 3 step diagram: Dockerfile+docker build -> Binary file+docker push -> Manifest yml+kubectl apply -->

<!-- a detailed, step-by-step account of how to deploy an app to Kubernetes. By the end of this article, you’ll have a working Kubernetes deployment and continuous delivery workflow. -->
<!-- HIDDEN: Continuous Integration and Delivery, Getting ready, Fork, Testing -->

I’ve put all the files we’ll be needing below. Feel to fork and clone the repository.

{% github semaphoreci-demos/semaphore-demo-ruby-kubernetes %}

## Step 1: Get Your Cluster

Maybe I’m stating the obvious, but the first step is getting a Kubernetes cluster. Most cloud providers offer this service in one form or another, so shop around and see what fits your needs. The lowest-end machine and cluster size is enough to run our example app. I like starting from a three-node cluster, but you can get away with just one node.

![Kubernetes](https://thepracticaldev.s3.amazonaws.com/i/zinu8kv6926hfhiv0r6e.png)

After the cluster ready, download the [kubeconfig](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) file from your provider. Some let you download it directly from their web console, while others require a helper program. Check their documentation. We’ll need this file to connect to the cluster.

## Step 2: The Docker Image

We can run anything in Kubernetes—as long as it has been packaged with Docker.

![Docker](https://thepracticaldev.s3.amazonaws.com/i/fttrj1d11we4f6ul9ev8.png)

**So, what does Docker do?** Docker creates an isolated space, called a *container*, where application can run without interference. We can use Docker to put our applications in portable images that we can run anywhere without having to install libraries or dependencies.

To build a Docker image, we need the [docker CLI](https://docs.docker.com/engine/reference/commandline/cli/) and a Dockerfile like this:

```Dockerfile
FROM ruby:2.5

RUN apt-get update -qq && apt-get install -y build-essential

ENV APP_HOME /app
RUN mkdir $APP_HOME
WORKDIR $APP_HOME

ADD Gemfile* $APP_HOME/
RUN bundle install --without development test

ADD . $APP_HOME

EXPOSE 4567

CMD ["bundle", "exec", "rackup", "--host", "0.0.0.0", "-p", "4567"]
```

The file has the commands to build a ruby-based application image:

- Start from a pre-built ruby image.
- Install the build tools with apt-get.
- Copy Gemfile since it has all the dependencies.
- Install them with bundle.
- Copy the app source code.
- Define the listening port and the start command.

<!-- We’ll bake our production image in the Semaphore environment. However, if you wish to do a quick test on your machine, type: -->

Build it and run it using:

```bash
$ docker build . -t hello-ruby
$ docker run -p 4567:4567 hello-ruby
```

<!-- Our Docker image will include Ruby, the app code, and all its libraries. Take a look a the Dockerfile: -->

## Step 3: Put the Image in a Registry

Step 3 is about putting the image where Kubernetes can find it.

Docker images are stored in Docker registries. [Docker Hub](https://hub.docker.com/) is the default and provides unlimited free space for public repositories.

To upload an image to the registry:

- Connect to the Docker registry with `docker login`
- Tag the image with your Docker username: `docker tag`
- Push it to the registry: `docker push`

For example:

```bash
$ export DOCKER_USERNAME=<<YOUR USERNAME>>
$ export DOCKER_PASSWORD=<<YOUR_PASSWORD>>
$ docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
$ docker tag hello-ruby -t $DOCKER_USERNAME/hello-ruby
$ docker push $DOCKER_USERNAME/hello-ruby
```

## Step 4: Add a Pinch of Manifest and Mix

Automatic deployment is Kubernetes’ strong suit. All we need is to tell the cluster our final desired state and it will take care of the rest.

![Haxxor Skillz](https://thepracticaldev.s3.amazonaws.com/i/oxsu8520hod7mxrzgasv.jpg)

In Kubernetes we don’t manage containers directly. In truth, we work with pods. A pod is like a group of merry friends that always go together to the same places. Containers in a pod are guaranteed to run on the same node and have the same IP. They always start and stop in unison and, since they run on the same machine, they can share its resources.

<!--
The problem with pods is that they can start and stop at any time, and we can’t know for sure what IPs they’ll get assigned. To route HTTP traffic from our users we’ll also need a load-balancing service; it will be responsible for keeping track of the pods and forwarding incoming connections so, from the client point of view, there is always a single public IP.  Open the file located at deployment.yml. This is the manifest for deploying our app. It has two resources separated by three dashes. First, the deployment:
-->

To tell Kubernetes what we want we must write a manifest file. A minimal viable manifest looks like this:

```yaml
# deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-ruby
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hello-ruby
  template:
    metadata:
      labels:
        app: hello-ruby
    spec:
      containers:
        - name: hello-ruby
          image: $DOCKER_USERNAME/hello-ruby:latest
```

There are several interesting things here:

- `Labels`: resources can have a name and several labels, which are convenient to organize things.
- `Spec`: defines the desired final state and the template used to create the pods.
- `Replicas`: defines how many copies of the pod to create. We usually set this to the number of nodes in the cluster.

To complete the setup, we need a service. A service presents a fixed IP address to the world. We can use load balancer service to forward traffic to the pods:

```yaml
# service.yml
apiVersion: v1
kind: Service
metadata:
  name: hello-ruby-lb
spec:
  selector:
    app: hello-ruby
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 4567
```

Kubernetes matches up the `selector` with the `labels` to connect services and pods.

Now to apply both files and you should get the application running in a few minutes:

```bash
$ kubectl apply -f service.yml
$ kubectl apply -f deployment.yml
```

Check the cluster status with:

```bash
$ kubectl get deployment
NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
semaphore-demo-ruby-kubernetes   1/1     1            1           31s

$ kubectl get service
NAME                                TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes                          ClusterIP      10.120.0.1     <none>        443/TCP        5d20h
semaphore-demo-ruby-kubernetes-lb   LoadBalancer   10.120.8.161   <pending>     80:31603/TCP   36s
```

## The Secret Ingredient: CI/CD

Pssst... come near... I have to tell you a secret...

> You don’t have to do all this by hand. You can use Continuous Integration and Delivery to test and deploy on your behalf.

The demo project we cloned in the beginning comes with everything you need to get started with the Semaphore CI/CD platform.

![Semaphore CI/CD](https://thepracticaldev.s3.amazonaws.com/i/mfy6chn0la7xkyxf0tow.jpg)

To get started with a free account, go to [semaphoreci.com](https://semaphoreci.com) and sign up using your GitHub account.

### Tell Semaphore How to Connect to Kubernetes

Semaphore needs to know how to connect to your cluster. We can store sensitive data in Semaphore using [secrets].

Semaphore provides a secure mechanism to store sensitive information such as passwords, tokens, or keys.
In order to connect to your cluster, create a [secret] in the Semaphore website:

- On the left navigation bar, under Configuration, click on Secrets.
- Click on **Create New Secret**.
- The secret name is “do-k8s”.
- Upload the Kubeconfig file to `/home/semaphore/.kube/dok8s.yml`.
- Define any other environment variables needed to connect to your cloud.
- Click on the **Save Changes** button.

![Kubernetes Secret](https://thepracticaldev.s3.amazonaws.com/i/7ph1epu2fcvoip6o526s.png)

<!--
If you prefer to use the command line instead, install the [Semaphore CLI] and create the secret from the terminal:
```bash
$ sem secret create do-k8s \
   -f $HOME/.kube/config:/home/semaphore/.kube/dok8s.yml
```
```bash
$ sem secret create dockerhub \
   -e DOCKER_USERNAME=YOUR_USERNAME \
   -e DOCKER_PASSWORD=YOUR_PASSWORD
```
-->

Create second secret to store the `dockerhub` user and password:

![DockerHub Secert](https://thepracticaldev.s3.amazonaws.com/i/tsuxe0b0fiunjh6h13dv.png)

### Semaphore Pipelines

Semaphore uses the YAML Syntax to define what the pipelines do at each step.

The pipeline files are located in the `.semaphore` directory:

- `semaphore.yml`: tests the application.
- `docker-build.yml`: build the Docker image and push it to Docker Hub.
- `deploy-k8s.yml`: deploy the application.


Let’s examine `deploy-k8s.yml`. The basics for pipelines were discussed [in a previous post](https://dev.to/semaphore/continuous-integration-and-delivery-to-aws-kubernetes-12d6) so I’ll jump straight deployment job. The heart of the pipeline are *blocks* and *jobs*. We put our command in jobs, and our jobs in blocks.

The deploy block first imports the secrets we just created using the `secrets` property:

```yaml
blocks:
  - name: Deploy to Kubernetes
    task:
      secrets:
        - name: do-k8s
        - name: dockerhub
```

Then, we define the environment using the `env_vars` property. You may need to add more variables.

```yaml
      env_vars:
        - name: KUBECONFIG
          value: /home/semaphore/.kube/dok8s.yaml
```

We have only one job in the block. It clones the Git repository with the [checkout](https://docs.semaphoreci.com/article/54-toolbox-reference#checkout) script, then does the declarative deployment.

```yaml
      jobs:
      - name: Deploy
        commands:
          - checkout

          # <PUT CLOUD-SPECIFIC LOGIN COMMANDS HERE (aws, gcloud, doctl, etc.)>

          - envsubst < deployment.yml | tee deployment.yml
          - kubectl apply -f deployment.yml
```

If you need cloud-specific commands to login, add them before the first kubectl command.

### Push and Deploy

Semaphore now has all the information to make the deployment on our behalf.

Delete the deployment we did manually:

```bash
$ kubectl delete hello-ruby
$ kubectl delete hello-ruby-lb
```

Edit `deployment.yml` and set the number of `replicas` to the number of nodes in your cluster. I’ll use three replicas.

<!--
Add the project to Semaphore. You can use the sem CLI

```bash
$ cd semaphore-demo-ruby-kubernetes
$ sem init
```
-->
Add the project to Semaphore:

1. Go to https://semaphoreci.com
2. Click on the **+ (plus)** sign next to *Projects* to see a list of your repositories.
3. Use the *Choose* button next to `semaphore-demo-ruby-kubernetes`.

Finally, make a push:

```bash
$ git add .semaphore
$ git commit -m “first deployment”
$ git push origin master
```

And watch Semaphore go:

![Ready to Promote](https://thepracticaldev.s3.amazonaws.com/i/6ysps05byto2ht3uwpex.png)

When the “Docker Build” pipeline is complete, press on the **Promote** button to deploy:

![Deployment Complete](https://thepracticaldev.s3.amazonaws.com/i/pxdfbdgd1uizl5nsdi6i.png)

# Want More Kubernetes?

We have some cloud specific tutorials here:

{% link https://dev.to/semaphore/google-cloud-kubernetes-in-10-minutes-2enb %}
{% link https://dev.to/semaphore/continuous-integration-and-delivery-to-aws-kubernetes-12d6 %}

If you wish to learn more about how Semaphore can work Kubernetes and Docker check out these:

- [CI/CD for Microservices on DigitalOcean Kubernetes](https://semaphoreci.com/blog/cicd-microservices-digitalocean-kubernetes)
- [Working with Docker Images](https://docs.semaphoreci.com/article/78-working-with-docker-images)

Did you find the post useful? Hit those ❤️ and 🦄, follow me or leave a comment below.

Interested in CI/CD and Kubernetes? We’re working on a free ebook, [sign up](http://bit.ly/2KsBHI2), to receive it as soon as it’s published.

Thanks for reading!