Building a Delivery Tracking System: Jenkins meets Golang
1. Introduction
Real-time data tracking is essential for monitoring transportation of goods and services. I used Golang to build an application that simulates GPS movement, ingest the time-series data in QuestDB and there’s a REST API that exposes the real-time data and metrics. We will leverage Docker to integrate the backend, QuestDB as the time-series database and a data-source for Grafana visualization and the deployment process will be automated using Jenkins.
Tech Stack
Golang for Backend
QuestDB for Time-Series Database
Grafana for Data Visualization
Jenkins for CI/CD
Docker for Containerization
Prometheus for Scraping and Monitoring
Prerequisites
2 Ubuntu 22.04 Servers
Jenkins-Server
Application
Ensure port
8080
is open on Jenkins-ServerInstall Docker on both servers
2. Setting up Jenkins
The following are taken to setup the Jenkins server:
- SSH into the server and create a script called
jenkins.sh
and paste the following code:
#!/bin/bash
sudo apt update
sudo apt install openjdk-17-jre -y
sudo apt install openjdk-11-jdk -y
sudo wget -O /usr/share/keyrings/jenkins-keyring.asc \
https://pkg.jenkins.io/debian/jenkins.io-2023.key
echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
https://pkg.jenkins.io/debian binary/ | sudo tee \
/etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt update
sudo apt install jenkins -y
sudo systemctl start jenkins
sudo systemctl enable jenkins
- Make the script executable and run it:
sudo chmod +x jenkins.sh && ./jenkins.sh
- Jenkins comes with a default admin password. To find the password, run the following command:
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
To access Jenkins server, open your browser and paste
http:<jenkins_server_ip>:8080
.Paste the password and click on
Continue
.Install suggested plugins and create an admin user.
- Once Jenkins is installed, you can access it by visiting
http://<jenkins_server_ip>:8080
in your web browser.
Some plugins are required for Jenkins to function properly. To install them, click on
Manage Jenkins
in the left sidebar, then click onPlugins
.Click on
Available Plugins
and search for the following plugins:GitHub Integration Plugin
Blue Ocean Plugin
Docker Plugin
Click on each plugin, install and do not select
Restart Jenkins when installation is complete and no jobs are running
.
Security Best Practices
- Use secure communication protocol HTTPS/SSL
- Implement role-based access control (RBAC) for Jenkins users
- Implement IP whitelisting to restrict access to Jenkins server
3. Setting up Jenkins Pipeline
To create a Jenkins pipeline, follow these steps:
- On the Jenkins dashboard, click on
New Item
in the left sidebar.
- Enter a name for your pipeline, select
Pipeline
and click onOK
.
- Give the pipeline a description, select
Discard old builds
and choose2
the number of builds to keep ensuring server memory usage is optimized.
- Select
GitHub project
and paste the repository URL of the project.
https://github.com/ekedonald/stackup-testing.git
- Select
GitHub hook trigger for GITScm polling
, this needs to be enabled for Jenkins to automatically trigger the pipeline when changes are pushed to the repository.
In the pipeline configuration page, click on
Pipeline
and selectPipeline script from SCM
.Fill in the following fields:
SCM:
Git
Repository URL:
https://github.com/ekedonald/stackup-testing.git
Branch Specifier:
*/main
Script Path:
Jenkinsfile
- Click on
Save
to save the pipeline configuration.
4. Configure Webhook on GitHub
To configure the webhook on GitHub, follow these steps:
Go to the repository settings page on GitHub and click on
Webhooks
in the left sidebar.Click on
Add webhook
.Fill in the following fields and click on
Add webhook
:Payload URL:
http://<jenkins_server_ip>:8080/github-webhook/
Content type:
application/json
5. Configure Secrets and Secrets on Jenkins
To configure secrets and secrets on Jenkins, follow these steps:
On the Jenkins dashboard, click on
Manage Jenkins
andAdd Credentials
.On stores scoped to jenkins, click on
global
andAdd Credentials
.
Create a new credential for the
env
with the following details:Kind:
Secret file
File:
upload or drag the env file
ID:
env-file-secret
Create new credentials for the
private key
with the following details:Kind:
Username with password
ID:
ssh-pem-key
Username:
remote server username
, treat username as secretPrivate key:
upload or drag the private key
Create a new credential for the
remote-user
with the following details:Kind:
Secret text
Secret:
remote server username
ID:
remote-user
Create a new credential for the
remote-host
with the following details:Kind:
Secret text
Secret:
remote server password
ID:
remote-host
6. Configure your Jenkinsfile
In the github repository, create a file called Jenkinsfile
and paste the following code:
pipeline {
agent any
environment {
DOCKER_IMAGE = 'delivery-tracker'
DOCKER_TAG = "${BUILD_NUMBER}"
PEM_PATH = "/tmp/deploy-key-${BUILD_NUMBER}.pem"
TEMP_DIR = "/tmp/deployment-${BUILD_NUMBER}"
GIT_REPO = 'https://github.com/ekedonald/stackup-testing.git'
REMOTE_DIR = '/home/ubuntu/stackup-testing'
}
stages {
stage('Setup SSH') {
steps {
script {
withCredentials([
string(credentialsId: 'remote-user', variable: 'REMOTE_USER'),
string(credentialsId: 'remote-host', variable: 'REMOTE_HOST'),
sshUserPrivateKey(credentialsId: 'ssh-pem-key', keyFileVariable: 'SSH_KEY')
]) {
sh """
cp "\$SSH_KEY" ${PEM_PATH}
chmod 600 ${PEM_PATH}
ssh-keyscan -H \$REMOTE_HOST >> /tmp/known_hosts_${BUILD_NUMBER}
"""
}
}
}
}
stage('Create .env File') {
steps {
script {
withCredentials([file(credentialsId: 'env-file-secrets', variable: 'ENV_FILE')]) {
sh 'cp $ENV_FILE .env'
}
}
}
}
stage('Build Docker Image') {
steps {
script {
sh "docker build -t ${DOCKER_IMAGE}:${DOCKER_TAG} ."
sh "docker save ${DOCKER_IMAGE}:${DOCKER_TAG} > ${DOCKER_IMAGE}.tar"
}
}
}
stage('Transfer and Deploy') {
steps {
script {
withCredentials([
string(credentialsId: 'remote-user', variable: 'REMOTE_USER'),
string(credentialsId: 'remote-host', variable: 'REMOTE_HOST')
]) {
sh """
ssh -i ${PEM_PATH} -o UserKnownHostsFile=/tmp/known_hosts_${BUILD_NUMBER} \
\$REMOTE_USER@\$REMOTE_HOST '\
mkdir -p ${TEMP_DIR}'
scp -i ${PEM_PATH} -o UserKnownHostsFile=/tmp/known_hosts_${BUILD_NUMBER} \
${DOCKER_IMAGE}.tar \$REMOTE_USER@\$REMOTE_HOST:${TEMP_DIR}/
scp -i ${PEM_PATH} -o UserKnownHostsFile=/tmp/known_hosts_${BUILD_NUMBER} \
.env \$REMOTE_USER@\$REMOTE_HOST:${TEMP_DIR}/
ssh -i ${PEM_PATH} -o UserKnownHostsFile=/tmp/known_hosts_${BUILD_NUMBER} \
\$REMOTE_USER@\$REMOTE_HOST '\
git clone ${GIT_REPO} ${REMOTE_DIR} && \
cp ${TEMP_DIR}/.env ${REMOTE_DIR}/ && \
docker load < ${TEMP_DIR}/${DOCKER_IMAGE}.tar && \
rm -rf ${TEMP_DIR} && \
cd ${REMOTE_DIR} && \
sed -i "s|build: .|image: ${DOCKER_IMAGE}:${DOCKER_TAG}|g" compose.yaml && \
docker compose up -d'
"""
}
}
}
}
}
post {
always {
sh """
rm -f ${PEM_PATH}
rm -f /tmp/known_hosts_${BUILD_NUMBER}
rm -f ${DOCKER_IMAGE}.tar
rm -f .env
"""
cleanWs()
}
success {
echo 'Deployment completed successfully!'
}
failure {
echo 'Deployment failed!'
}
}
}
The pipeline above is configured to:
Build a Docker image for the application
delivery-tracker
Transfer the Docker image to the remote server
Deploy the application to the remote server
Clean up the temporary files and resources
7. Trigger the Jenkins Pipeline from GitHub
Update a file in your GitHub repository and it will automatically trigger the Jenkins pipeline.
Go to the Jenkins dashboard, click on the build number dropdown and select
Pipeline Overview
to view the build logs.
The pipeline build is successfully and you should be able to access the application at http://<remote_server_ip>:3000 to access Grafana.
Grafana will be integrated with questdb
as a datasource and we can analyze metrics such as Total Distance Covered
and identify Top Performing Drivers
.