DEV Community

Cover image for Delicious Kubernetes in 4 Steps
Tomas Fernandez for Semaphore

Posted on • Originally published at thenewstack.io

Delicious Kubernetes in 4 Steps

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.

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

GitHub logo semaphoreci-demos / semaphore-demo-ruby-kubernetes

A Semaphore demo CI/CD pipeline for Kubernetes.

Semaphore CI/CD demo for Kubernetes

Build Status

This is an example application and CI/CD pipeline showing how to build, test and deploy a microservice to Kubernetes using Semaphore 2.0.

Ingredients:

  • Ruby Sinatra as web framework
  • RSpec for tests
  • Packaged in a Docker container
  • Container pushed to Docker Hub registry
  • Deployed to Kubernetes

CI/CD on Semaphore

If you're new to Semaphore, feel free to fork this repository and use it to create a project.

The CI/CD pipeline is defined in .semaphore directory and looks like this:

CI/CD pipeline on Semaphore

Local application setup

To run the microservice:

bundle install --path vendor/bundle
bundle exec rackup

To run tests:

bundle exec rspec

To build and run Docker container:

docker build -t semaphore-demo-ruby-kubernetes
docker run -p 80:4567 semaphore-demo-ruby-kubernetes
curl localhost
> hello world :))

Additional documentation

License

Copyright (c) 2022 Rendered Text

Distributed under the MIT License…

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

After the cluster ready, download the 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

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 and a Dockerfile like this:

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"]
Enter fullscreen mode Exit fullscreen mode

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.

Build it and run it using:

$ docker build . -t hello-ruby
$ docker run -p 4567:4567 hello-ruby
Enter fullscreen mode Exit fullscreen mode

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 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:

$ 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
Enter fullscreen mode Exit fullscreen mode

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

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.

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

# 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
Enter fullscreen mode Exit fullscreen mode

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:

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

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:

$ kubectl apply -f service.yml
$ kubectl apply -f deployment.yml
Enter fullscreen mode Exit fullscreen mode

Check the cluster status with:

$ 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
Enter fullscreen mode Exit fullscreen mode

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

To get started with a free account, go to 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

Create second secret to store the dockerhub user and password:

DockerHub Secert

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 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:

blocks:
  - name: Deploy to Kubernetes
    task:
      secrets:
        - name: do-k8s
        - name: dockerhub
Enter fullscreen mode Exit fullscreen mode

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

      env_vars:
        - name: KUBECONFIG
          value: /home/semaphore/.kube/dok8s.yaml
Enter fullscreen mode Exit fullscreen mode

We have only one job in the block. It clones the Git repository with the checkout script, then does the declarative deployment.

      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
Enter fullscreen mode Exit fullscreen mode

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:

$ kubectl delete hello-ruby
$ kubectl delete hello-ruby-lb
Enter fullscreen mode Exit fullscreen mode

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:

  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:

$ git add .semaphore
$ git commit -m “first deployment”
$ git push origin master
Enter fullscreen mode Exit fullscreen mode

And watch Semaphore go:

Ready to Promote

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

Deployment Complete

Want More Kubernetes?

We have some cloud specific tutorials here:

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

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, to receive it as soon as it’s published.

Thanks for reading!

Top comments (0)