Learning Outcomes
- define continuous integration (CI)
- define continuous delivery (CD)
- define continuous deployment(CD)
- distinguish between development, testing (or staging), and production environments
- use webhooks to deploy a simple web application
Resources
- Continuous Integration & Continuous Deployment: CI/CD (video)
- What is Continuous Integration
- slides
- narated slides (video)
Lab
Video walkthrough of this lab.
Set up a SSH Key for GitLab
Start the Google Cloud Shell.
Generate a SSH key:
ssh-keygen -t ed25519 -C "YOUR EMAIL ADDRESS HERE"Display the public key:
cat .ssh/id_ed25519.pubCopy the output into your clipboard. Log in to GitLab. Go to your settings and then to the “SSH Keys” page. Paste your public key into the “Key” field. Click on the “Add key” button.
Create a Deployment Script for an Application
Create myapp/deploy.sh with the following contents:
#!/bin/sh
cd $HOME
wget -O $3.tar.gz https://gitlab.com/api/v4/projects/$1/repository/archive?private_token=$2
tar zxf $3.tar.gz
cp -R *-master-*/* $3
rm -r $3.tar.gz *-master-*
cd $3
npm install
pm2 reload wwwMake sure myapp/deploy.sh has the execution bit set:
chmod +x ~/myapp/deploy.shCreate a View for the Webhook
Create myapp/views/webhook.ejs with the following contents:
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
    <p>Webhook</p>
  </body>
</html>This page will never be seen, but Gitlab still needs a valid response to know that the webhook succeeded.
Create a Route for the Webhook
Create myapp/routes/webhook.js with the following contents:
var express = require('express');
var router = express.Router();
const { spawn } = require('child_process');
/* POST home page. */
router.post('/', function(req, res, next) {
    console.log( req.body );
    if ( process.env.WEBHOOK_TOKEN == req.headers['x-gitlab-token'] ) {
        const projectID = req.body['project']['id']
        const pathParts = req.body['project']['path_with_namespace'].split('/');
        const projectSlug = pathParts[1];
        const scriptPath = process.env.HOME +'/' + projectSlug + '/deploy.sh'
        const subprocess = spawn(
            scriptPath,
            [
                projectID,
                process.env.GITLAB_PRIVATE_TOKEN,
                projectSlug
            ],
            {
                detached: true,
                stdio: 'ignore'
        });
        subprocess.unref();
    } else {
        var forbiddenError = new Error( 'invalid or missing x-gitlab-token header');
        forbiddenError.status = 403;
        throw forbiddenError;
    }
    res.render('webhook', { title: 'Express: webhook POST' });
});
module.exports = router;Link the Webhook Route in the application
Require the webhook route in myapp/app.js:
var webhookRouter = require('./routes/webhook');Link the webhook route to the webhook path:
app.use('/webhook', webhookRouter);Create a .gitignore File
Create myapp/.gitignore with the following contents:
node_modulesSet Up a GitLab Project
In GitLab, go to “Projects | Your projects”. Click on the “New project” button. Choose “Create blank project”. For “Project name”, enter “myapp”. Click on the “Create project” button. Follow the instructions to “Push an existing folder”.
Go to the project settings page. Go to the “Webhooks” page. For the URL, enter http://NNN.NNN.NNN.NNN:8080/webhook. For “Secret Token”, enter a random sequence of letters and numbers. For the list of triggers, make sure only “Push events” is checked. Make sure “Enable SSL verification” is unchecked.
Go to your GitLab profile settings page. Go to the “Access Tokens” page. For “Name”, enter “deploy”. For “Scopes”, make sure “read_api” and “read_repository” are checked. Click on the “Create personal access token” button. Record the generated token value. THIS IS THE ONLY TIME YOU CAN SEE IT.
Deploy Application by Hand to Bootstrap
SSH into the virtual machine:
gcloud compute ssh lab1 --zone YOUR_ZONE_GOES_HEREInstall wget:
sudo apt install wgetMake a note of project ID for your GitLab project. (It is located just below the project name on the project overview page). Retrieve the latest project archive (fill in your actual values for YOUR_PROJECT_ID and YOUR_ACCESS_TOKEN):
wget -O myapp.tar.gz https://gitlab.com/api/v4/projects/YOUR_PROJECT_ID/repository/archive?private_token=YOUR_ACCESS_TOKENUnpack the archive:
tar zxf myapp.tar.gzWe are going to need to re-start the application with the correct environment variables set so we will stop and delete the current application:
pm2 stop www
pm2 delete wwwCopy the latest version of the application into the myapp directory:
cp -R *-master-*/* myappRemove the archive and the unpacked directory:
rm -r myapp.tar.gz *-master-*Change to application bin directory:
cd myapp/binRe-start the application with the correct environment variables (substituting your actual values for YOUR_ACCESS_TOKEN and YOUR_WEBHOOK_SECRET):
GITLAB_PRIVATE_TOKEN=YOUR_ACCESS_TOKEN WEBHOOK_TOKEN=YOUR_WEBHOOK_SECRET PORT=8080 DEBUG=myapp:* pm2 start wwwView the streaming logs for the application:
pm2 logsVerify that the Webhook Works
In a new tab, browse to the application using the external IP address for the virtual machine (http://NNN.NNN.NNN.NNN:8080).
Back in the Google Cloud Shell environment, change the title property in myapp\routes\index.js. Stage, commit, and push the changes to the repository. Verify that the change has be automatically deployed to http://NNN.NNN.NNN.NNN:8080 by refreshing the page.
Assignment
- Add a new route to the application, /guestbook.
- In the router, create a global array variable that maintains the list of vistors.
- For a GETrequest, display the contents of the array, one per line. Also, display a form where the user can enter their name and submit the form (as aPOSTrequest).
- For a POSTrequest, add the new name to the array and re-display the array.
- Stage, commit, and push your changes.
- Confirm that the changes are properly deployed to the virtual machine.
