Deploying our first application

Oct 30, 2024 min read

This article is part of a series of articles:

1. Our application (CI Part)

This is a discord bot creating a quiz. It’s an application written in golang.

The code is available here

Note: This application only connects to APIs, and therefore doesn’t need to receive requests from outside. There is therefore no need to create an ingress access from the internet to our cluster, which simplifies security management.


Application compilation is quite simple:

go mod tidy 
go build .

Which translates to the following job in our CI/CD configuration file, the .gitlab-ci.yml:

#.gitlab-ci.yml
buildgo:
  stage: build
  image: golang
  script:
    - go mod tidy
    - go build .
  cache:
    policy: push
    paths:
      - quizzbot

Each CI job runs in a different docker container, and therefore the cache allows saving files and sharing them between different containers, like here quizzbot, the compiled binary of our application.

We package our application in a docker image, which will be our deliverable:

#.gitlab-ci.yml
builddocker:
  stage: publish
  services:
    - name: docker:dind
      command: ["dockerd", "--host=tcp://0.0.0.0:2375"]
      alias: 'docker'
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD"
  script:
    - docker build -t $CI_REGISTRY/discordquizbot .
    - docker push $CI_REGISTRY/discordquizbot
  cache:
    policy: pull
    paths:
      - quizzbot

(Small disclaimer: the method presented here is old, but I know, I haven’t had too much time to polish this part)

Note: All this part is executed on gitlab.com shared runners, because there is no advantage to using our own resources for this.

2. Terraform deployment (CD Part)


Thing to know: I am a big advocate of terraform, so I will use it, and maybe a bit more than reason.

I will for example use directly the terraform provider for kubernetes where it would have been “prettier” to use helm configurations then execute them with the terraform helm provider, leaving the possibility for anyone to choose to deploy via terraform, or not.

That being said, let’s return to our deployment:

In this part, we declare variables not declared in our gitlab projects, or their parent group (see the chapter Preparing application deployment)

#.gitlab-ci.yml
variables:
  TF_VAR_MINIKUBE_HOST: ${MINIKUBE_HOST}
  TF_VAR_CI_PROJECT_ID: ${CI_PROJECT_ID}
  TF_VAR_CI_REGISTRY: ${CI_REGISTRY}

Here the TF_VAR_ prefix is a way to pass variables to terraform.

For example, the TF_VAR_CI_REGISTRY variable will appear as var.CI_REGISTRY in our terraform code.

On our gitlab group, we added the variable

TF_VAR_KUBECONFIG_MKS8

which contains the path of our configuration file in a format readable by kubectl (see the chapter Preparing application deployment)

Let’s start by running the terraform init command to initialize the execution folder then terraform plan which will validate by connecting to kubernetes that everything is in place for our deployment, without triggering it.

#.gitlab-ci.yml
terraform-plan:
  stage: build
  image:
    name: hashicorp/terraform:latest
    entrypoint:
      - '/usr/bin/env'
      - 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
  tags:
    - minikube
  only:
    - main
  script:
    - cd terraform
    - apk update && apk add envsubst
    - cat backend.tf.tpl | envsubst > backend.tf
    - terraform init -input=false
    - terraform plan -input=false -out=plan.cache

the terraform plan will execute on our kubernetes cluster thanks to this:

tags:
  - minikube

minikube is the tag I gave to my gitlab runner when creating it (see the chapter Preparing application deployment)

Everything will execute in the terraform folder of this repo and terraform will use all *.tf files present in this folder

I’ll let you study terraform documentation. We will especially be interested in how to deploy in our cluster.

To get to the essentials, we will configure the terraform provider (driver) for kubernetes, in the provider.tf file

# provider.tf
provider "kubernetes" {
  config_path    = var.KUBECONFIG_MKS8
}

(This is where we pass the path of our kubernetes configuration file to terraform)

Then, we instantiate our application via a module call in the main.tf file:

module "french" {
  source          = "./app_instance"
  MINIKUBE_NAMESPACE        = data.kubernetes_namespace.botspace.id
  CI_REGISTRY               = var.CI_REGISTRY
  DISCORD_TOKEN             = var.DISCORD_TOKEN
}

This module executes what is in the terraform/app_instance folder

and the terraform/app_instance/application.tf file contains our application deployment code:

resource "kubernetes_deployment" "discordbot" {
  metadata {
    name = "discordbot"
    namespace = var.MINIKUBE_NAMESPACE
    labels = {

    }
  }

  spec {
    replicas = 1

    selector {
      match_labels = {
        app = "discordbot"
      }
    }

    template {
      metadata {
        labels = {
          app = "discordbot"
        }
      }

      spec {
        container {
          image = "${var.CI_REGISTRY}/discordquizbot"
          name  = "discordquizbot"

          env {
            name  = "DISCORD_TOKEN"
            value = "${var.DISCORD_TOKEN}"
          }


        }
      }
    }
  }
}

To understand, I refer you to the documentation of the kubernetes_deployment object on the official site

Once the terraform plan is validated, our gitlab pipeline will execute the terraform apply which will apply the modifications.

Here’s what our gitlab pipeline looks like:

And our container will be started on our kubernetes cluster:


There you go! I hope this series of articles will have pleased you, and that you will take as much pleasure as I had doing it to set up my kubernetes cluster!

-|