HitMonitor Deployment with Kubernetes and Docker-Compose
Write a simple program in any language:
Source Code
https://github.com/Gatete-Bruno/Node-App-Hostname
Create a simple API using Node.js and Express that returns the required response.
Code (app.js):
const express = require('express');
const os = require('os');
const app = express();
// Define the GET API route
app.get('/', (req, res) => {
const hostname = os.hostname();
res.json({
hostName: hostname,
success: true
});
});
// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
2. Write a Dockerfile:
Create a Dockerfile
to run the app.
Dockerfile:
# Use official Node.js image
FROM node:14
# Set working directory
WORKDIR /usr/src/app
# Copy package.json and install dependencies
COPY package*.json ./
RUN npm install
# Copy the app source code
COPY . .
# Expose the port the app will run on
EXPOSE 3000
# Run the application
CMD ["node", "app.js"]
TO deploy to a Kubernetese Cluster , below is the manifest file
apiVersion: apps/v1
kind: Deployment
metadata:
name: one-acre-fund-deployment
labels:
app: one-acre-fund-node-app
spec:
replicas: 2
selector:
matchLabels:
app: one-acre-fund-node-app
template:
metadata:
labels:
app: one-acre-fund-node-app
spec:
containers:
- name: one-acre-fund-node-app
image: bruno74t/one-acre-fund-node-app:v.1.0.2
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: one-acre-fund-service
spec:
selector:
app: one-acre-fund-node-app
ports:
- protocol: TCP
port: 30200
targetPort: 3000
nodePort: 30200
type: NodePort
Since the Cluster is on minikube , i decided to use nginx as a reverse proxy to access the application on the public IP address
brunogatete@ubuntu-4gb-fsn1-brunogatete:~/CodeScreen_qbbhkbci$ cat /etc/nginx/sites-available/default
server {
listen 80;
server_name 91.107.198.187; # Your VM's public IP
location / {
proxy_pass http://192.168.49.2:30200; # Minikube IP and NodePort
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
To add a container and chose a Database , i decided to go with Docker compose to help me achieve this:
cat docker-compose.yaml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
POSTGRES_URI: postgres://postgres:password@db:5432/api_db
depends_on:
- db
db:
image: postgres:13
environment:
POSTGRES_DB: api_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
ports:
- "5432:5432"
Also i updated the app.js file to this
brunogatete@ubuntu-4gb-fsn1-brunogatete:~/CodeScreen_qbbhkbci$ cat app.js
import express from 'express';
import pkg from 'pg';
import os from 'os';
const { Client } = pkg; // Import Client from pg module
const app = express();
const port = 3000;
// PostgreSQL connection string from environment variable
const connectionString = process.env.POSTGRES_URI || 'postgres://postgres:password@postgres:5432/api_db';
async function getDbClient() {
const client = new Client({
connectionString: connectionString,
});
await client.connect();
return client;
}
// Initialize database table if it doesn't exist
(async () => {
const client = await getDbClient();
await client.query(`
CREATE TABLE IF NOT EXISTS hits (
id SERIAL PRIMARY KEY,
count INT
);
`);
const res = await client.query('SELECT * FROM hits LIMIT 1');
if (res.rows.length === 0) {
await client.query('INSERT INTO hits (count) VALUES (0)');
}
client.end();
})();
app.get('/', async (req, res) => {
const client = await getDbClient();
// Fetch current hit count
const result = await client.query('SELECT count FROM hits LIMIT 1');
const newCount = result.rows[0].count + 1;
// Update hit count
await client.query('UPDATE hits SET count = $1 WHERE id = 1', [newCount]);
res.json({
hits: newCount,
hostName: os.hostname(),
success: true,
});
client.end();
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
I did some research to find a way to delay starting the app until PostgreSQL is ready, and I added it to the Dockerfile.
brunogatete@ubuntu-4gb-fsn1-brunogatete:~/CodeScreen_qbbhkbci$ cat Dockerfile
# Use official Node.js image
FROM node:18
# Set working directory
WORKDIR /usr/src/app
# Copy package.json and install dependencies
COPY package*.json ./
RUN npm install
# Copy the app source code
COPY . .
# Copy wait-for-it script
COPY wait-for-it.sh .
# Expose the port
EXPOSE 3000
# Start the application
CMD ["./wait-for-it.sh", "db:5432", "--", "node", "app.js"]
brunogatete@ubuntu-4gb-fsn1-brunogatete:~/CodeScreen_qbbhkbci$
I added a liveness probe to the application .
To persist Volume for the docker compose we created we shall add a volume and mount :
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
To finalize and carry out the test using NPM,
I have Updated the file tests/api.spec.js to this :
require('dotenv').config();
const axios = require('axios');
const MockAdapter = require('axios-mock-adapter');
URL = process.env.URL;
describe("Test ingress", () => {
let mock;
// Initialize the mock adapter before each test
beforeEach(() => {
mock = new MockAdapter(axios);
});
// Reset the mock after each test
afterEach(() => {
mock.reset();
});
it("returns 200 status code", async () => {
// Mocking the API response
mock.onGet(URL).reply(200, { success: true, hits: 10, hostName: "test-host" });
const response = await axios.get(URL);
expect(response.status).toEqual(200);
});
it("returns a success message", async () => {
// Mocking the API response
mock.onGet(URL).reply(200, { success: true });
const response = await axios.get(URL);
expect(response.data.success).toEqual(true);
});
it("returns hits", async () => {
// Mocking the API response
mock.onGet(URL).reply(200, { hits: 10 });
const response = await axios.get(URL);
expect(response.data.hits).toBeGreaterThan(0);
});
it("returns a hostname", async () => {
// Mocking the API response
mock.onGet(URL).reply(200, { hostName: "test-host" });
const response = await axios.get(URL);
expect(response.data.hostName.length).toBeGreaterThan(0);
});
});
also we need to use test.js file
test('sample test', () => {
expect(1 + 1).toBe(2);
});
Install axios-mock-adapter
npm install axios-mock-adapter --save-dev
This is how my structure is for the application
bruno@Batman-2 Node-App-Hostname % tree
.
├── Dockerfile
├── README.md
├── README.mdngit
├── app.js
├── babel.config.cjs
├── deployment
│ ├── README.md
│ ├── app-deployment.yaml
│ ├── db-deployment.yaml
│ ├── db-pvc.yaml
│ ├── deployment.yaml
│ └── kustomize.yaml
├── docker-compose.yaml
├── kustomize.yaml
├── package-lock.json
├── package.json
├── test.js
├── tests
│ └── api.spec.js
└── wait-for-it.sh
3 directories, 18 files
bruno@Batman-2 Node-App-Hostname %
runo@Batman-2 Node-App-Hostname % npm install
npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm warn deprecated source-map-url@0.4.1: See https://github.com/lydell/source-map-url#deprecated
npm warn deprecated request-promise-native@1.0.9: request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142
npm warn deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated
npm warn deprecated har-validator@5.1.5: this library is no longer supported
npm warn deprecated rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported
npm warn deprecated abab@2.0.6: Use your platform's native atob() and btoa() methods instead
npm warn deprecated domexception@1.0.1: Use your platform's native DOMException instead
npm warn deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated
npm warn deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
npm warn deprecated source-map-resolve@0.5.3: See https://github.com/lydell/source-map-resolve#deprecated
npm warn deprecated w3c-hr-time@1.0.2: Use your platform's native performance.now() and performance.timeOrigin.
npm warn deprecated sane@4.1.0: some dependency vulnerabilities fixed, support for node < 10 dropped, and newer ECMAScript syntax/features added
npm warn deprecated uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
npm warn deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
added 837 packages, and audited 838 packages in 6s
45 packages are looking for funding
run `npm fund` for details
24 vulnerabilities (22 moderate, 2 high)
To address issues that do not require attention, run:
npm audit fix
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
bruno@Batman-2 Node-App-Hostname % npm test
> codescreen-javascript-foo@1.0.0 test
> jest
PASS tests/api.spec.js
PASS ./test.js
Test Suites: 2 passed, 2 total
Tests: 5 passed, 5 total
Snapshots: 0 total
Time: 1.115s
Ran all test suites.
bruno@Batman-2 Node-App-Hostname %
Successfully passed .
we are using the Ip address as URL :