Deploy Simple Microservices App on K3s with GitLab CI/CD

Introduction

Micro-services architecture is everywhere in modern software development, but getting started with Kubernetes can feel overwhelming. That's where K3s comes in - a lightweight Kubernetes distribution that's perfect for learning and development.

In this tutorial, I build and deploy a simple two-service application:

  • User Service: Returns user information

  • Greeting Service: Calls the user service and creates personalized greetings

Learning areas:

  • Set up K3s on your server

  • Containerize Node.js applications with Docker

  • Deploy services to Kubernetes

  • Automate deployments with GitLab CI/CD

GitLab Repository: https://gitlab.com/sachindu_personal/KubeGreet.git


✅ Step 1: Prerequisites and K3s Installation

Install K3s on Your Server

K3s is a lightweight Kubernetes distribution perfect for development and production environments.

# Install K3s
curl -sfL https://get.k3s.io | sh -

# Check node status (takes ~30 seconds)
sudo k3s kubectl get node

# Create kubectl symlink for easier access
sudo ln -s /usr/local/bin/k3s /usr/local/bin/kubectl

Expected output:

NAME          STATUS   ROLES                  AGE   VERSION
thinkcentre   Ready    control-plane,master   93s   v1.32.6+k3s1

Uninstall K3s (if needed)

/usr/local/bin/k3s-uninstall.sh

✅ Step 2: Building the Microservices Application

Project Structure

Create the following directory structure:

KubeGreet/
├── user-service/
│   ├── index.js
│   ├── package.json
|   ├── package-lock.json
│   ├── Dockerfile
│   └── k8s/
│       └── user-service-deployment.yaml
├── greeting-service/
│   ├── index.js
│   ├── package.json
|   ├── package-lock.json
│   ├── Dockerfile
│   └── k8s/
│       └── greeting-service-deployment.yaml
├── README.md
├──.gitignore
└──.gitlab-ci.yml

User Service Setup

  1. Create user-service files (index.js and package.json)

  2. Test locally:

     # Install all required packages listed in package.json
     npm install
    
     # Start the Node.js application
     npm start
    
  3. Verify: Visit http://localhost:3000/user

Greeting Service Setup

  1. Create greeting-service files (index.js and package.json)

  2. Important: For local testing, modify the service URL:

     // Change from:const response = await axios.get('http://user-service:3000/user');// To:const response = await axios.get('http://localhost:3000/user');
    
  3. Test locally:

     # Install all required packages listed in package.json
     npm install
    
     # Start the Node.js application
     npm start
    
  4. Verify: Visit http://localhost:3001/greet


✅ Step 3: Containerizing with Docker

Install Docker (if not installed)

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Verify installation
docker run hello-world

Build and Test Docker Images

User Service:

cd user-service
docker build -t username/user-service:latest .
docker run -d -p 3000:3000 --name testUserservice username/user-service:latest

Greeting Service:

cd greeting-service
docker build -t username/greeting-service:latest .
docker run -d -p 3001:3001 --name testGreetingservice username/greeting-service:latest

Push to Docker Hub

docker push username/user-service:latest
docker push username/greeting-service:latest

✅ Step 4: Setting up GitLab Repository

Initialize Git Repository

git init
git remote add origin https://gitlab.com/your-username/KubeGreet.git

# Create .gitignore
echo "node_modules/" >> .gitignore

git add .
git commit -m "initial commit"
git push --set-upstream origin main

GitLab Access Token

Create a personal access token for authentication:

  • Path: Preferences > Access Token > Personal Access Token

✅ Step 5: Creating Kubernetes Manifests

📌 What is K3s and Why Use It?

K3s is a lightweight, easy-to-install Kubernetes distribution designed for development, edge computing, and IoT environments. It is simpler and consumes fewer resources compared to full Kubernetes, making it perfect for local testing and learning DevOps workflows.

📌 What is a Manifest File?

A Kubernetes manifest file is a YAML file that defines your Kubernetes resources, such as deployments, services, and config maps. It tells Kubernetes what to deploy and how to configure it.

For example, a deployment manifest specifies:

  • Which Docker image to use

  • Number of replicas

  • Ports to expose

Manual Deployment to K3s on Localhost

Create deployment files:

  • user-service/k8s/user-service-deployment.yaml

  • greeting-service/k8s/greeting-service-deployment.yaml

Apply manifests:

kubectl apply -f user-service/k8s/user-service-deployment.yaml
kubectl apply -f greeting-service/k8s/greeting-service-deployment.yaml

Verify deployment:

kubectl get pods

Expected output:

NAME                                READY   STATUS    RESTARTS   AGE
greeting-service-576bbcfd76-zk2dg   1/1     Running   0          5m
user-service-6d5c9c4f79-r2fxl       1/1     Running   0          5m10s

Useful Commands

# Scale deployment
kubectl scale deployment user-service --replicas=3

# Restart service
kubectl rollout restart deployment user-service

# Delete pod
kubectl delete pod <pod-name>

✅ Step 6: Automated CI/CD Pipeline Setup

Step 1: Install GitLab Runner

# Install GitLab Runner
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
sudo apt-get update
sudo apt-get install gitlab-runner

# Verify installation
gitlab-runner --version

Step 2: Register GitLab Runner

Get Project Runner Token:

  1. Go to your GitLab project

  2. Navigate to Settings → CI/CD → Runners

  3. Click "New project runner"

  4. Configure runner settings:

    • Tags: k3s-deploy (or your preferred tag)

    • Description: K3s deployment runner

    • Maximum timeout: 3600 seconds (1 hour)

  5. Click "Create runner"

  6. Copy the registration token

Register the Runner:

sudo gitlab-runner register \
  --url https://gitlab.com \
  --token <project-runner-token> \
  --executor shell

During registration, you'll be prompted:

  1. Description: Press Enter (uses default)

  2. Tags: Press Enter (no tags needed)

  3. Maintenance note: Press Enter (skip)

  4. Executor: Type shell

Step 3: Start GitLab Runner Service

sudo gitlab-runner start
sudo systemctl enable gitlab-runner
sudo systemctl status gitlab-runner

Shared runner should stay on the running state, otherwise Docker image will not be created.

Step 4: Configure kubectl Access

# Set up kubectl for current user
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config

# Test kubectl
kubectl get nodes

Step 5: Get Base64 Kubeconfig

cat ~/.kube/config | base64 -w 0

Copy the output for GitLab CI/CD variables.

Step 6: Configure GitLab CI/CD Variables

Go to Settings → CI/CD → Variables and add:

VariableValueType
KUBE_CONFIGBase64 kubeconfig outputVariable
CI_REGISTRY_USERGitLab usernameVariable
CI_REGISTRY_PASSWORDPersonal access tokenVariable (Masked)
CI_REGISTRYregistry.gitlab.comVariable

Step 7: Create Docker Registry Secret

kubectl create secret docker-registry gitlab-registry-secret \
  --docker-server=registry.gitlab.com \
  --docker-username=your-username \
  --docker-password=your-token \
  --docker-email=your-email@gmail.com

Step 8: Fix Runner Permissions

# Add gitlab-runner to sudo group
sudo usermod -a -G sudo gitlab-runner

# Fix K3s kubeconfig permissions
sudo chmod 644 /etc/rancher/k3s/k3s.yaml

# Alternative: Copy config for gitlab-runner
sudo mkdir -p /home/gitlab-runner/.kube
sudo cp /etc/rancher/k3s/k3s.yaml /home/gitlab-runner/.kube/config
sudo chown gitlab-runner:gitlab-runner /home/gitlab-runner/.kube/config
sudo chmod 600 /home/gitlab-runner/.kube/config

Step 9: Test Deployment

# Test manual deployment
kubectl apply -f user-service/k8s/user-service-deployment.yaml
kubectl get pods -l app=user-service

# Test the service
curl http://localhost:3000/greet

Step 10: Run CI/CD Pipeline

Now let's test the complete CI/CD pipeline by making a commit and verifying the automated deployment.

Trigger the Pipeline

Make a small change to test the automation:

# Commit and push
git add .
git commit -m "Test CI/CD pipeline"
git push origin main

Monitor Pipeline Execution

  1. Go to GitLab: Navigate to your project → CI/CD → Pipelines

  2. Check Pipeline Status: You should see a running pipeline with stages:

    • Build: Docker images are built and pushed

    • Deploy: Services are deployed to K3s cluster

Verify Successful Deployment

Check Pipeline Success:

# Check if pods are running
kubectl get pods

# Expected output:
NAME                                READY   STATUS    RESTARTS   AGE
greeting-service-576bbcfd76-zk2dg   1/1     Running   0          2m
user-service-6d5c9c4f79-r2fxl       1/1     Running   0          2m

Test the Application: Visit in your browser: http://<server-ip>:30081/greet

Expected Output:


⚠️ Common Errors and Troubleshooting

  1. Pod not starting: Check logs with kubectl logs -l app=service-name

  2. Permission denied: Ensure gitlab-runner has proper permissions

  3. Registry pull errors: Verify registry secret is created correctly

Useful Commands

# Check pod status
kubectl describe pod -l app=user-service

# View logs
kubectl logs -l app=user-service

# Check services
kubectl get svc

💬 Tried this flow or need any step explained better? Drop a comment below – I welcome your feedback and let’s grow together as DevOps learners!

0
Subscribe to my newsletter

Read articles from Sachindu Malshan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sachindu Malshan
Sachindu Malshan