GitLab CI/CD: Architecting, Deploying, and Optimizing Pipelines

Continuous Deployment with GitLab

Job Create Secret and Deploy to K8S Dev Environment

In this guide, we’ll walk through creating a Kubernetes Secret and deploying application manifests to the development namespace using GitLab CI/CD. You’ll learn how to integrate secret creation into your pipeline and verify a successful deploy.

1. Verify the Development Namespace

First, confirm the development namespace is clean:

kubectl -n development get all

No resources found in development namespace.

Note

Always ensure you’re targeting the correct namespace before deploying to avoid unintended changes.


2. Initial CI Job Definition

Here’s a basic k8s_dev_deploy job from .gitlab-ci.yml:

k8s_dev_deploy:
  stage: dev-deploy
  image: alpine:3.7
  dependencies: []
  before_script:
    - wget https://storage.googleapis.com/kubernetes-release/release/$(wget -q -O - https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
    - chmod +x ./kubectl && mv ./kubectl /usr/bin/kubectl
    - apk add --no-cache gettext
    - envsubst -V
  script:
    - export KUBECONFIG=$DEV_KUBE_CONFIG
    - kubectl version -o yaml
    - kubectl config get-contexts
    - kubectl get nodes
    - export INGRESS_IP=$(kubectl -n ingress-nginx get service ingress-nginx-controller -o jsonpath="{.status.loadBalancer.ingress[0].ip}")
    - for manifest in kubernetes/manifest/*.yaml; do
        envsubst < $manifest | kubectl apply -f -
      done

Warning

At this stage, deployment will fail because the application requires a mongo-db-creds secret that doesn’t exist yet.


3. Required Manifests

Define the Kubernetes resources for your application. Below are two essential manifest examples:

Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: solar-system
  namespace: ${NAMESPACE}
  annotations:
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
spec:
  rules:
    - host: solar-system-${NAMESPACE}.${INGRESS_IP}.nip.io
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: solar-system
                port:
                  number: 3000
  tls:
    - hosts:
        - solar-system-${NAMESPACE}.${INGRESS_IP}.nip.io
      secretName: ingress-local-tls

Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: solar-system
  namespace: ${NAMESPACE}
spec:
  replicas: ${REPLICAS}
  selector:
    matchLabels:
      app: solar-system
  template:
    metadata:
      labels:
        app: solar-system
    spec:
      containers:
        - name: solar-system
          image: ${K8S_IMAGE}
          imagePullPolicy: Always
          ports:
            - name: http
              containerPort: 3000
      envFrom:
        - secretRef:
            name: mongo-db-creds
ResourcePurpose
IngressRoute HTTP/TLS traffic to the service
DeploymentManage pods, replicas, and rolling updates

The mongo-db-creds secret must include:

  • MONGO_URI
  • MONGO_USERNAME
  • MONGO_PASSWORD

4. Create Secret and Update CI Job

Incorporate secret creation into your pipeline before applying manifests:

k8s_dev_deploy:
  stage: dev-deploy
  image: alpine:3.7
  dependencies: []
  before_script:
    - wget https://storage.googleapis.com/kubernetes-release/release/$(wget -q -O - https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
    - chmod +x ./kubectl && mv ./kubectl /usr/bin/kubectl
    - apk add --no-cache gettext
    - envsubst -V
  script:
    - export KUBECONFIG=$DEV_KUBE_CONFIG
    - INGRESS_IP=$(kubectl -n ingress-nginx get service ingress-nginx-controller -o jsonpath="{.status.loadBalancer.ingress[0].ip}")
    - echo "Ingress IP: $INGRESS_IP"
    - kubectl -n $NAMESPACE create secret generic mongo-db-creds \
        --from-literal=MONGO_URI="$MONGO_URI" \
        --from-literal=MONGO_USERNAME="$MONGO_USERNAME" \
        --from-literal=MONGO_PASSWORD="$MONGO_PASSWORD" \
        --dry-run=client -o yaml | kubectl apply -f -
    - for manifest in kubernetes/manifest/*.yaml; do
        envsubst < $manifest | kubectl apply -f -
      done
    - kubectl -n $NAMESPACE get all,secret,ingress

Note

Using --dry-run=client -o yaml ensures idempotent secret creation.


5. Pipeline Configuration

Define the overall GitLab pipeline, stages, and variables:

workflow:
  name: Solar System NodeJS Pipeline

stages:
  - test
  - containerization
  - dev-deploy

variables:
  DOCKER_USERNAME: siddharth67
  IMAGE_VERSION: $CI_PIPELINE_ID
  K8S_IMAGE: $DOCKER_USERNAME/solar-system:$IMAGE_VERSION
  MONGO_URI: 'mongodb+srv://supercluster.d83ji.mongodb.net/superData'
  MONGO_USERNAME: superuser
  MONGO_PASSWORD: $M_DB_PASSWORD
VariableDescription
DOCKER_USERNAMEDocker Hub username
IMAGE_VERSIONImage tag (CI pipeline ID)
K8S_IMAGEFull image name for Kubernetes pull
MONGO_*MongoDB connection credentials

Include a containerization job if needed:

docker_build:
  stage: containerization
  image: docker:24.0.5
  services:
    - docker:24.0.5-dind
  script:
    - docker load -i image/solar-system-image:$IMAGE_VERSION.tar
    - docker login --username=$DOCKER_USERNAME --password=$DOCKER_PASSWORD
    - docker push $K8S_IMAGE

Once pushed, GitLab will visualize the pipeline:

The image shows a GitLab CI/CD pipeline interface for a NodeJS project named "Solar System," displaying completed jobs for containerization and deployment stages.


6. Inspect the Dev-Deploy Logs

Your dev-deploy job logs should look like this:

$ envsubst -V
envsubst (GNU gettext-runtime) 0.19.8.1
$ export KUBECONFIG=$DEV_KUBE_CONFIG
$ kubectl version -o yaml
# ClientVersion and ServerVersion details
$ kubectl get nodes
# Lists nodes
$ export INGRESS_IP=139.48.208.48
$ kubectl -n $NAMESPACE create secret generic mongo-db-creds \
    --from-literal=MONGO_URI=$MONGO_URI \
    --from-literal=MONGO_USERNAME=$MONGO_USERNAME \
    --from-literal=MONGO_PASSWORD=$MONGO_PASSWORD \
    --dry-run=client -o yaml | kubectl apply -f -
secret/mongo-db-creds created
$ for manifest in kubernetes/manifest/*.yaml; do
    envsubst < $manifest | kubectl apply -f -
  done
deployment.apps/solar-system created
ingress.networking.k8s.io/solar-system created
service/solar-system created
$ kubectl -n $NAMESPACE get all,secret,ingress
# Pods, services, deployment, secret, and ingress listed

7. Verify Deployment Locally

Check all resources in development:

kubectl -n development get all
NAME                                   READY   STATUS    RESTARTS   AGE
pod/solar-system-86fc65474-5klmr       1/1     Running   0          3m10s
pod/solar-system-86fc65474-fcfpq       1/1     Running   0          3m10s

NAME                   TYPE        CLUSTER-IP      PORT(S)          AGE
service/solar-system   NodePort    10.102.111.40   3000:30654/TCP   3m5s

NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/solar-system 2/2     2            2           3m10s

NAME                                        DESIRED   CURRENT   READY   AGE
replicaset.apps/solar-system-86f65474      2         2         2       3m10s

Retrieve the Ingress host:

kubectl -n development get ingress
NAME           HOSTS                                               ADDRESS         PORTS   AGE
solar-system   solar-system-development.139.84.208.48.nip.io       139.84.208.48   80,443  3m33s

Open the application in your browser (accept the self-signed certificate):

http://solar-system-development.139.84.208.48.nip.io

The image shows a digital representation of the solar system with planets orbiting the sun, accompanied by a user interface for exploring the planets.

Search for a planet by number (e.g., 3 for Earth):

The image shows a webpage about the solar system, featuring an illustration of Earth and a description of the planet. There is a search interface for exploring planets.


8. Next Steps

To improve reliability, integrate health checks into your pipeline:

  • GET /live{"status":"live"}
  • GET /ready{"status":"ready"}, HTTP 200

These endpoints can be tested with tools like k6 or Postman CLI.


Watch Video

Watch video content

Previous
Job Replace Placeholders Tokens