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
Create user-service files (
index.js
andpackage.json
)Test locally:
# Install all required packages listed in package.json npm install # Start the Node.js application npm start
Verify: Visit
http://localhost:3000/user
Greeting Service Setup
Create greeting-service files (
index.js
andpackage.json
)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');
Test locally:
# Install all required packages listed in package.json npm install # Start the Node.js application npm start
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:
Go to your GitLab project
Navigate to Settings → CI/CD → Runners
Click "New project runner"
Configure runner settings:
Tags:
k3s-deploy
(or your preferred tag)Description:
K3s deployment runner
Maximum timeout:
3600
seconds (1 hour)
Click "Create runner"
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:
Description: Press Enter (uses default)
Tags: Press Enter (no tags needed)
Maintenance note: Press Enter (skip)
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:
Variable | Value | Type |
KUBE_CONFIG | Base64 kubeconfig output | Variable |
CI_REGISTRY_USER | GitLab username | Variable |
CI_REGISTRY_PASSWORD | Personal access token | Variable (Masked) |
CI_REGISTRY | registry.gitlab.com | Variable |
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
Go to GitLab: Navigate to your project → CI/CD → Pipelines
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
Pod not starting: Check logs with
kubectl logs -l app=service-name
Permission denied: Ensure gitlab-runner has proper permissions
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!
Subscribe to my newsletter
Read articles from Sachindu Malshan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
