← Lakhan Samani | Digital Garden

Run server as systemd process & automate deployment with github actions

Docker and kubernetes are great for automating deployment, scaling, and management of containerized applications. But it comes with extra cost of resource management. Often it is found that this processes itself would require some resources to run and execute the servers. We can totally avoid this complexity by using systemd services. The name systemd adheres to the Unix convention of naming daemons by appending the letter d. It provides array of system components for linux operating system.

In this blog post we will see how we can deploy a NodeJS server on linux machine and automate the deployment with github actions. But it is not only limited to NodeJS ideally you can deploy any daemon/long running processes using systemd.

Let's get started!

Step 1: Create a HTTP server or any long running process.

For the demo purpose let's create a simple NodeJS server using Express.

  • Initialize node project npm init -y
  • Install dependencies npm install expres
  • Create index.js with following content
    1const express = require('express') 2const app = express() 3const port = 3000 4 5app.get('/', (req, res) => { 6 res.send('Hello World!') 7}) 8 9app.listen(port, () => { 10 console.log(`Example app listening at http://localhost:${port}`) 11})
  • Add start script in package.json, scripts section
    1 "start": "node index.js"
  • Run npm start from the terminal & verify if the app is running or not.

Step 2: Create a virtual linux machine

In this tutorial we are going to use Google Cloud and Ubuntu to create a VM instance. You can use any provider and linux distribution here. Just the installation and creation commands can change!

  • Setup Virtual Machine For Google cloud users following is the command to create vm instance.

Note: Replace the PROJECT_NAME, SERVICE_ACCOUNT with your google cloud project and service accounts. Also you can set different machine type based on your traffic and processing.

1gcloud beta compute --project=PROJECT_NAME instances create my-server --zone=us-central1-a --machine-type=e2-micro --subnet=default --network-tier=PREMIUM --maintenance-policy=MIGRATE --service-account=SERVICE_ACCOUNT --scopes=https://www.googleapis.com/auth/devstorage.read_only,https://www.googleapis.com/auth/logging.write,https://www.googleapis.com/auth/monitoring.write,https://www.googleapis.com/auth/servicecontrol,https://www.googleapis.com/auth/service.management.readonly,https://www.googleapis.com/auth/trace.append --tags=http-server,https-server --image=debian-10-buster-v20210512 --image-project=debian-cloud --boot-disk-size=10GB --boot-disk-type=pd-balanced --boot-disk-device-name=my-server --no-shielded-secure-boot --shielded-vtpm --shielded-integrity-monitoring --reservation-affinity=any
  • Enable requests on port 3000 for this instance

Note: for the demo purpose I am enabling port 3000 on all the instances in my Google account

1gcloud compute --project=PROJECT_NAME firewall-rules create port-3000 --direction=INGRESS --priority=1000 --network=default --action=ALLOW --rules=tcp:3000 --source-ranges=0.0.0.0/0
  • ssh into the newly created VM
1gcloud beta compute ssh --zone=us-central1-a "my-server" --project=PROJECT_NAME
1sudo apt-get update 2curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash - 3sudo apt-get install -y nodejs
  • Install wget for downloading source files
1sudo apt-get install -y wget
  • Install unzip for extracting source
1sudo apt-get install -y unzip

Step 3: Release the basic version and test the app

  • Download the zip from github
1wget https://github.com/lakhansamani/deployment-automation-demo/archive/refs/heads/main.zip
  • Unzip the source
1unzip main.zip
  • Install dependenceis
1mv deployment-automation-demo-main demo && cd demo 2npm install
  • Start node server
1npm start
  • Open the instance IP:3000 in your browser and you will see Hello World

Now when your ssh connection will end this service will stop as it is not running as daemon process. In order to achieve this we will create a systemd process for the same

Step 4: Create systemd configuration file

We will create demo.service file in our source code and use it with each deployment.

Note: Change service name, path to source and working directory.

demo.service

1[Unit] 2Description=demo 3 4[Service] 5Type=simple 6Restart=always 7RestartSec=5 8ExecStart=/usr/bin/node /home/YOUR_SSH_USERNAME/demo/index.js 9WorkingDirectory=/home/YOUR_SSH_USERNAME/demo/ 10 11[Install] 12WantedBy=multi-user.target

There are more configurations that you can set here. Example memory requirements and environment variables. For more info you can check manual.

Step 5: Create shell script to deploy the latest release

Using shell script we will be performing following tasks.

  • Download the source based on release tag
  • Install the node dependencies
  • Start/Restart the systemd service

We can put this file in the home folder of our VM and can execute it using github actions.

This script takes 1 input argument, i.e. release tag/number

Note: Please change github path, repo name while unzipping it and service in the file below

deploy.sh

1#!/bin/sh 2if [ "$1" == "" ]; then 3 echo "Please enter version" 4 exit 1 5fi 6 7echo "upgrading demo to v-${1}" 8 9# for private repos you can use wget with GithubToken in header 10# example --header "Authorization: token ${GITHUB_TOKEN}" 11 12wget https://github.com/lakhansamani/deployment-automation-demo/archive/refs/tags/${1}.zip -O demo.zip 13 14# unzip 15unzip demo.zip 16mkdir -p demo 17cp deployment-automation-demo-${1}/* demo/ 18rm -rf deployment-automation-demo-${1} demo.zip 19cd demo 20npm install 21 22# Copy service file, incase if there are any changes 23sudo cp demo.service /etc/systemd/system/demo.service 24# reload configurations incase if service file has changed 25sudo systemctl daemon-reload 26# restart the service 27sudo systemctl restart demo 28# start of VM restart 29sudo systemctl enable demo 30

Step 6: Create GitHub action

We want to run the shell script created in step 5 and restart system process on new the release.

  • Create .github/workflows folder in your source code
1mkdir -p .github/workflows
  • Create .github/workflows/main.yml to run action
1touch .github/workflows/main.yml
  • Add workflow to ssh into vm and run the deloy.sh script with correct release tag

Note: add ~/.ssh/google_compute_engine Private key in your github repository secret. Also add IP address as secret so that you don't expose it unnecessarily. Also change SSH_USERNAME as per your VM

1name: GitHub Actions Demo 2on: 3 release: 4 types: [published] 5jobs: 6 Explore-GitHub-Actions: 7 runs-on: ubuntu-latest 8 steps: 9 - name: Get the version 10 id: get_version 11 run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 12 - name: Debug version 13 id: Debug 14 run: echo ${{ steps.get_version.outputs.VERSION }} 15 - name: executing remote ssh commands using ssh key 16 uses: appleboy/ssh-action@master 17 with: 18 host: ${{ secrets.GCP_IP }} 19 username: SSH_USERNAME 20 key: ${{ secrets.GCP_PRIVATE_KEY }} 21 port: 22 22 script: /bin/sh deploy.sh ${{ steps.get_version.outputs.VERSION }} 23

Hurray! thats all we need. Now every time you create new release on github it will automatically deploy to you VM machine. You can also follow this process to deploy multiple services on single instance and achieve the micro service architecture here.

Here is a github repository that includes source for this demo.