Skip to main content

Command Palette

Search for a command to run...

How to Deploy a Full Stack Webapp on Cloud using best DevOps Practices

Updated
How to Deploy a Full Stack Webapp on Cloud using best DevOps Practices
M

Developer...

The tutorial will include a Video chatting application with react and nodejs as frontend and backend respectively , throughout the tutorial I will explain how to deploy the application to civo going through unit and intigration testing , CI/CD pipeline , scanning through some tools like datree , Trivy and Kubescape and in the end I will show how to deploy the application manually and using a IAC tool like Terraform to civo cluster.

  1. The frontend and backend setup

  2. Creating yaml files to deploy to kubernetes

  3. Scanning and testing files through tools like datree and kubescape

  4. manual and automatic(automation through IAC) deployment of cluster

Do not forget to give a star to the project used for this tutorial : https://github.com/Mahesh-Kasabe/Lets-Meet-Devops

Now let's start by creating a project and the folder structure which will look something like this :

structure.png

1. The frontend and backend setup

The frontend part is created using the reactJS framework which includes a webpage which lets u enter a meeting id and code and after invoking the backend processes which is largely depends upon socketio and nodejs the application lets u connect with the other person.

CodeBases :

Backend Logic :

io.on("connection", (socket) => {
    socket.emit("me", socket.id);
    console.log(`User Connected with socket id ${socket.id}`);
    socket.on("join_room", (data) => {
        socket.join(data);
        console.log(`User with ID: ${socket.id} joined room: ${data}`);
      });
      socket.on("send_message", (data) => {
        socket.to(data.room).emit("receive_message", data);
      });
    socket.on("disconnect", () => {
        socket.broadcast.emit("callEnded")
    });
    socket.on("callUser", ({ userToCall, signalData, from, name }) => {
        socket.to(userToCall).emit("callUser", { signal: signalData, from, name });
    });
    socket.on("answerCall", (data) => {
        socket.to(data.to).emit("callAccepted", data.signal)
    });
});

Now that we have our backend and frontend setup together now let's write some unit test cases for them, just so that our production server would not go down after deploying the applications

The frontend testcase file : code

The backend testcase file : code

and after testing the web applications we will dockerize both the application in order to deploy to Kubernetes :

Frontend

FROM node:alpine as prod
Run mkdir -p /home/src/frontend
COPY . /home/src/frontend
WORKDIR /home/src/frontend
RUN yarn install
RUN yarn build
CMD ["yarn","serve","build"]

FROM node:alpine as test
Run mkdir -p /home/src/frontend
COPY . /home/src/frontend
WORKDIR /home/src/frontend
RUN yarn install
RUN yarn run test a --clearCache

Backend

From node:17-alpine as prod
Run mkdir -p /home/src/backend
COPY . /home/src/backend
WORKDIR /home/src/backend
RUN npm install
CMD ["npm","start"]

From node:17-alpine as test
Run mkdir -p /home/src/backend
COPY . /home/src/backend
WORKDIR /home/src/backend
RUN npm install
RUN npm run test

As of now we have created chunk of files to dockerize the application with production and test tag now it's time to automate the deployment of docker containers to dockerhub using github actions .

before creating the workflows we will need some token to secure our workflows , let's add your github token, docker username and password inside secrets as PAT , DOCKER_USERNAME , DOCKER_TOKEN

for that matter now we will create a .github/workflows/ folder inside our main project folder so we could write some workflows for our CI/CD pipeline , now once we have our folder set up now let's create a cd-frontend file which will eventually deploy our frontend docker container on dockerhub , the content inside our workflow will be like this ,

frontend.png

similarly the backend image will get deployed to dockerhub ..

backend.png

Now once we have our github actions pipeline setup now we can move on to deploying it to our kubernetes cluster

2. Creating YAML files to deploy to Kubernetes

The very first file we need to create in order to deploy our dockerize containers to Kubernetes is to create a deployment and service file for both the applications so we could download the applications to our clusters.

frontend deployment and service YAML

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: frontend
  name: frontend-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template: 
    metadata:
      labels:
        app: frontend
    spec:
      containers:
        - image: maheshkasbe/frontend:v0.0.3
          name: frontend-container
          ports:
            - containerPort: 3000
---------

apiVersion: v1
kind: Service
metadata:
 name: frontend-service
spec:
 type: NodePort
 selector:
   app: frontend
 ports:
 - port: 3000
   targetPort: 3000
   nodePort: 3000

backend deployment and service yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: backend
  name: backend-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backend
  template: 
    metadata:
      labels:
        app: backend
    spec:
      containers:
        - image: maheshkasbe/backend:v0.0.3
          name: backend-container
          ports:
            - containerPort: 3001
--------

apiVersion: v1
kind: Service
metadata:
 name: backend-service
 labels:
   app: backend
spec:
 type: ClusterIP
 selector:
   app: backend
 ports:
 - port: 3001
   targetPort: 3001

**For one click argocd deployment for our project with self heal and automatic sync property **

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: app-backend
  namespace: argocd
spec:
  project: default
  source:
    repoURL: 'https://github.com/Mahesh-Kasabe/Lets-Meet-Devops/'
    path: Deployments/K8s/backend
    targetRevision: HEAD
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: app
  syncPolicy:
    syncOptions:
      - CreateNamespace=true      # to create the namaspace if not exsists
    automated:
      prune: true
      selfHeal: true
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: app-frontend
  namespace: argocd
spec:
  project: default
  source:
    repoURL: 'https://github.com/Mahesh-Kasabe/Lets-Meet-Devops/'
    path: Deployments/K8s/frontend
    targetRevision: HEAD
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: app
  syncPolicy:
    syncOptions:
      - CreateNamespace=true      # to create the namaspace if not exsists
    automated:
      prune: true
      selfHeal: trueapiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: app-backend
  namespace: argocd
spec:
  project: default
  source:
    repoURL: 'https://github.com/Mahesh-Kasabe/Lets-Meet-Devops/'
    path: Deployments/K8s/backend
    targetRevision: HEAD
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: app
  syncPolicy:
    syncOptions:
      - CreateNamespace=true      # to create the namaspace if not exsists
    automated:
      prune: true
      selfHeal: true
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: app-frontend
  namespace: argocd
spec:
  project: default
  source:
    repoURL: 'https://github.com/Mahesh-Kasabe/Lets-Meet-Devops/'
    path: Deployments/K8s/frontend
    targetRevision: HEAD
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: app
  syncPolicy:
    syncOptions:
      - CreateNamespace=true      # to create the namaspace if not exsists
    automated:
      prune: true
      selfHeal: true

Now let's Create a docker-compose file just to run both the applications on local-system or any virtual system instances like EC2 (AWS)

docker-compose yaml

version: '3'
networks:
  lets-meet:
services:
  backend:
    image: docker.io/maheshkasbe/backend:v0.0.4
    container_name: backend
    ports:
      - "3001:3001"
    networks:
      - lets-meet
  frontend:
    depends_on:
      - backend
    image: docker.io/maheshkasbe/frontend:v0.0.4
    container_name: frontend
    ports:
      - "80:3000"
    networks:
      - lets-meet

3. Setting up datreeio, kubescape in order to scan yaml files for vulnerabilities , security and hygeine

Now in order to validate, scan and secure our argocd-test.yaml file we need to have our argocd account token , once we have our argocd account token we can start by creating **datree-test.yaml ** , by adding the following argument we can easily scan our argocd file .

argocd.png

argocd2.png

```plaintext name: Datree-policy-Checks

on: push: branches: [ master ] pull_request: branches: [ master ]

env: DATREE_TOKEN: ${{ secrets.DATREE_TOKEN }}

jobs: ArgoCDPolicyCheck: runs-on: ubuntu-latest

steps: - name: Checkout uses: actions/checkout@v2

- name: Run Datree Policy Check uses: datreeio/action-datree@main with: path: 'Deployments/K8s/argocd.yaml' cliArguments: '--only-k8s-files -p Argo' ``` Now we need to scan all the deployment files with kubescape , ( Kubescape is the Open Source Kubernetes Security Platform - from development to production, configuration to runtime )

The workflow file will be something like this

name: K8s Manifest Sec checks Kubescape

on:
  pull_request:
    branches: [ master ]
  push:
    branches: [ master ]
jobs:
  kubescape:

    runs-on: ubuntu-latest
    environment: testing

    steps:
    - uses: actions/checkout@v3

    - name: 🧪 Install Kubescape
      run: 'curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash'

    - name: 🧪 Kubescape version
      run: kubescape version

    - name: Check the backend
      run: |
        cd Deployments/K8s
        kubescape scan -v backend/*.yaml

    - name: Check the frontend
      run: |
        cd Deployments/K8s/
        kubescape scan -v frontend/*.yaml

Here the kubescape scan file will be scanning all the Deployments file particularly written for frontend and backend deployment clusters

4. Now Setting up Trivy and CircleCi for Unit and Integration testing of Docker containers

For best practices we will include Trivy Vulnerability scanner which is nothing butit is the default scanner of choice for DevOps and security teams across many popular projects and companies. Users benefit from regular, quality contributions and innovative feature requests. Aqua Trivy is the default scanner for GitLab’s Container Scanning functionality, Artifact Hub and Harbor. Aqua Trivy is also a RedHat certified scanner.

Here we will add another yaml file so we could scan our backend and frontend docker containers for vulnerabilities

name: ImageScan [Aqua Trivy]

on:
  pull_request:
    branches: [ master ]
  push:
    branches: [ master ]


permissions:
  contents: read

jobs:
  build:
    permissions:
      contents: read # for actions/checkout to fetch code
      security-events: write # for github/codeql-action/upload-sarif to upload SARIF results

    name: Build
    runs-on: "ubuntu-18.04"
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Build an image from Dockerfile
        run: |
          cd src/backend
          docker build -t docker.io/my-organization/my-appb:${{ github.sha }} .
          cd ../frontend
          docker build -t docker.io/my-organization/my-appf:${{ github.sha }} .

      - name: Run Trivy vulnerability scanner(backend)
        uses: aquasecurity/trivy-action@cb606dfdb0d2b3698ace62192088ef4f5360b24f
        with:
          image-ref: 'docker.io/my-organization/my-appb:${{ github.sha }}'
          format: 'template'
          template: '@/contrib/sarif.tpl'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL'

      - name: Run Trivy vulnerability scanner(frontend)
        uses: aquasecurity/trivy-action@cb606dfdb0d2b3698ace62192088ef4f5360b24f
        with:
          image-ref: 'docker.io/my-organization/my-appf:${{ github.sha }}'
          format: 'template'
          template: '@/contrib/sarif.tpl'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL'

      - name: Upload Trivy scan results to GitHub Security tab
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: 'trivy-results.sarif'

once we are done our folder structure should look something like this

folder.png

CircleCI is the software delivery engine for teams who want to ship software faster, using code that’s reusable and easy to maintain.You can login using your github account in order to use your github projects directly

in order to create a circle ci pipeline we need to add .circleci folder inside our main project folder so we could easily write our artefacts and deploy them, now let's create a config.yaml file so we could configure our pipeline

project.png

Once the pipeline get's executed we get our interface on circleci like this

circlci.png

5. Adding dependabot for security ,version and vulnerability scanning throughout the code

# Basic dependabot.yml file
# REF: https://docs.github.com/en/code-security/supply-chain-security/keeping-your-actions-up-to-date-with-dependabot

version: 2
updates:
  # Enable version updates for GitHub Actions
  - package-ecosystem: "github-actions"
    # Look for `.github/workflows` in the `root` directory
    directory: "/"
    # Check for updates once a week
    schedule:
      interval: "daily"

The above file is about depebdabot which daily scans the entire repository for vulnurabilty scanning and keep your packages uptodat on daily basis , which eventually automatically pull new request on you github project on weekly

6. Manual and automatic(automation through IAC) deployment on Civo cluster

for civo

terraform {
  required_providers {
    civo = {
      source = "civo/civo"
      version = "0.10.3"
    }
  }
}

provider "civo" {
  token = "token"
}

resource "civo_kubernetes_cluster" "my-cluster" {
    name = "sammy"
    region = "LON1"
    applications = "Argo-cd"
    num_target_nodes = 3
    target_nodes_size = "g3.k3s.xsmall"
}

Deployments/IAC/CIVO/main.tf

Now at the end we want to deploy our terraform IAC structure to cloud for that matter we will scan and check format of our terraform IAC using github actions as follows

name: Continuous-Staging (Terraform)

on:
  push:
    branches:
    - "master"
  pull_request:

permissions:
  contents: read

jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest

    # Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest
    defaults:
      run:
        shell: bash

    steps:
    # Checkout the repository to the GitHub Actions runner
    - name: Checkout
      uses: actions/checkout@v3

    # Install the latest version of Terraform CLI and configure the Terraform CLI configuration file with a Terraform Cloud user API token
    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v2
      with:
        cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}

    # Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc.
    - name: Terraform Init 
      run: |
        cd Deployments/IAC/AWS
        terraform init
    # Checks that all Terraform configuration files adhere to a canonical format
    - name: Terraform Format
      run: |
        cd Deployments/IAC/AWS
        terraform fmt -check

Finally we are done with our project , thanks for reading folks !

A

Hi Mahesh the table of content and the idea seems amazing I know how to build the application so would you recommend learning these tools you used before following this blog like Kubescape, Daitree etc. and why Civo I mean isn't AWS generally used in most tutorials

More from this blog

Mahesh

12 posts