# 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.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752694698239/23cf9422-6125-4f71-bcf8-a99d12c37f6c.gif align="center")

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
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752865369385/1a85994a-9b76-4b88-88e9-3cdb52d6fb14.jpeg align="center")

Learning areas:

* Set up K3s on your server
    
* Containerize Node.js applications with Docker
    
* Deploy services to Kubernetes
    
* Automate deployments with GitLab CI/CD
    

<mark>GitLab Repository</mark>: [https://gitlab.com/sachindu\_personal/KubeGreet.git](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.

```bash
# 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:**

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

### Uninstall K3s (if needed)

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

---

## ✅ Step 2: Building the Microservices Application

### Project Structure

Create the following directory structure:

```bash
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:**
    
    ```bash
    # 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:
    
    ```javascript
    // 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:**
    
    ```bash
    # 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)

```bash
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:**

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

**Greeting Service:**

```bash
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

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

---

## ✅ Step 4: Setting up GitLab Repository

### Initialize Git Repository

```bash
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 &gt; Access Token &gt; 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:**

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

**Verify deployment:**

```bash
kubectl get pods
```

**Expected output:**

```bash
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

```bash
# 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**

```bash
# 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:**

```bash
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**

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752692791015/26523466-f588-47c5-a0f4-efec6029b683.png align="center")

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

**Step 4: Configure kubectl Access**

```bash
# 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**

```bash
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**

```bash
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**

```bash
# 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**

```bash
# 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:

```bash
# 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:**

```bash
# 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:**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1752693923275/f62d6d9d-e998-4552-95bb-24b4e02b7e21.png align="center")

---

## ⚠️ 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

```bash
# 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!**
