diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..8e0738069de2c47eb9f561115a308e4becff2305 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,45 @@ +# Example of basic pipeline triggered on commit to the main branch. We assume +# to use a minimalistic Alpine Linux Docker image. +--- +variables: + # PRODUCTION_IP: "<production-server-IP>" + APP_PAGE: "public/index.html" + CLONE_URL_SSH: "ssh://git@ssh.hesge.ch:${CI_PROJECT_ID}" + CLONE_URL_TOK: "https://gitlab-ci-token:${CI_JOB_TOKEN}@gitedu.hesge.ch" + +stages: + - test + - deploy + +# Commmon to all stages -- needed because the image's storage is not persistent across them +before_script: + # uncomment for debugging. Watch out! It *will* expose any secret passed from an unprotected variable. + # - export + - apk -U add git openssh-client rsync + - mkdir -p -m 0700 ~/.ssh + - touch ~/.ssh/known_hosts && chmod 644 ~/.ssh/known_hosts + - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" >> ~/.ssh/config' + - git config --global user.email "deployer@ci-cd.lab" + - git config --global user.name "The Deployer" + - eval $(ssh-agent -s) + - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - + - echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts + +unit_test1: + stage: test + script: + - echo "This job shall test that the version string in '${APP_PAGE}' is well formatted" + - git clone --depth=1 ${CLONE_URL_TOK}/${CI_PROJECT_PATH} + - '[[ $(sed -nr "/^\s*Version:\s+([[:digit:]]\.){2}[[:digit:]]\s*\$/p" ${CI_PROJECT_NAME}/${APP_PAGE} | wc -l) -eq 1 ]] + || { echo "[error] Version string badly formatted or not found"; exit 1; }' + +deploy_prod: + stage: deploy + environment: + name: production + url: http://${PRODUCTION_IP} + script: + - echo "Deploying from ${CI_REPOSITORY_URL}::${CI_COMMIT_REF_NAME}" + - rsync --rsync-path="sudo rsync" -Cavh ${CI_PROJECT_DIR}/public/ debian@${PRODUCTION_IP}:/var/www/html/ + only: + - main diff --git a/README.md b/README.md index 592d43412e23180791fffab94cc0f87f9d33f346..81151d4de0869e31f1671ab789cd2684918565b8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,124 @@ # Lab-GitLab-CI-CD -Simple app deployment exercise (DevOps) with GitLab CI/CD \ No newline at end of file +Simple app deployment exercise (DevOps) with GitLab CI/CD. + +## Pedagogical objectives ## + + * Become familiar with DevOps practices + * Learn how to automatically deploy things via GitLab CD/CD + +## Scenario ## + +A simple Web application composed of just one HTML page has to be +automatically deployed to a production Web server, whenever a new commit is +pushed to the `main` branch, but only if an app unit test passes. + +The production server is supposed to be already provisioned and installed. + +The Web app page `public/index.html` just shows an app version number. The app +unit test shall check that the above number is correctly formatted. + +The goal is to set up a simple GitLab C/CD pipeline to implement the scenario +above. + +### Workflow ### + +This is a conceptual description of the workflow that the GitLab pipeline +shall implement. Each phase below is prefixed by a 'prompt' specifying *who* +is playing: + +* M = Maintainer -- manual action +* G = GitLab -- automatic action + +1. M: increment the version number written in `public/index.html`. E.g..: + `x.y.z => x.y.z+1`. +1. M: commit and push. +1. G: trigger a pipeline, whose stages are: + 1. *test*: check the version number format. If OK, + 1. *deploy*: push the new app to the production server. + + +## Tasks ## + +In this lab you will perform a number of tasks and document your progress in a +lab report. Each task specifies one or more deliverables to be +produced. Collect all the deliverables in your lab report. + +**N.B.** Some tasks require interacting with your local machine's OS: any +related commands are supposed to be run into a terminal with the following +conventions about the *command line prompt*: + + * `#`: execution with super user's (root) privileges + * `$`: execution with normal user's privileges + * `lcl`: your local machine + * `ins`: your VM instance + + +### Task #1: GitLab CI/CD setup ### + +**Goal:** set up your GitLab CI/CD via its Web GUI. + +Our GitLab instance comes with a shared Docker-based CI/CD *runner*. + +Deploy [SSH keys for the Docker +executor](https://docs.gitlab.com/ee/ci/ssh_keys/). :bulb: All secrets should +be **protected** and possibly **masked**. The following variables have to be added: +`SSH_PRIVATE_KEY`, `SSH_KNOWN_HOSTS` and `PRODUCTION_IP`. + +Configure the CI/CD system as it follows in section **Settings > CI/CD > +General pipelines**: +- [ ] Public pipelines +- [x] Auto-cancel redundant pipelines +- [x] Skip outdated deployment jobs +- Git strategy: fetch +- Git shallow clone: 1 +- Timeout: 10m + + +### Task #2: CI/CD pipeline ### + +**Goal:** write a GitLab-style pipeline file `.gitlab-ci.yml`. + +For our simple DevOps scenario, the following structure suffices: + +``` yaml +--- +# Stages are job collections and run sequentially +stages: + - test + - deploy + +# Do here anything common to all jobs' scripts +before_script: + - # install needed packages in this runner + - # configure the git client on this runner + - # set up SSH keys via ssh-agent + - # launch the ssh-agent and feed it our key from var $SSH_PRIVATE_KEY + - echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts + +# Jobs run in parallel within the same stage, unless dependencies are expressed +unit_test1: + stage: test + script: + - # clone our repo locally + - # check the app version number and exit != 0 if it's wrong + +deploy_prod: + stage: deploy + # optional. The IP comes from a CI/CD variable + environment: + name: production + url: http://${PRODUCTION_IP} + script: + - echo "Deploying from ${CI_REPOSITORY_URL}::${CI_COMMIT_REF_NAME}" + - # sync "public/" to the target production server via SSH + # <condition> to trigger only on a given branch +``` + +### Task #3: test operation ### + +**Goal:** Test the pipeline by pushing a commit to the main branch. + +Verify that: + 1. The pipeline is correctly triggered on push/commit. + 1. The app is not deployed if the version number is broken. diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000000000000000000000000000000000000..2b73ac2e64a37317c18202129b6532769b477a0f --- /dev/null +++ b/public/index.html @@ -0,0 +1,36 @@ +<!doctype html> +<html> + <head> + <title>Simple CI/CD pipeline with GitLab</title> + <meta charset="utf-8" /> + <style type="text/css"> + .version { + font-family: monospace; + font-size: 50px; + margin: 100px auto; + width: 100%; + text-align: center; + } + .commit { + font-family: monospace; + font-size: 30px; + margin: 100px auto; + width: 100%; + text-align: center; + } + </style> + </head> + <body> + <h1>Simple CI/CD pipeline with GitLab</h1> + <p> + Please increase the version number below before merging. Never + touch the 'Id' string--it will be automatically populated by Git + checkout.</p> + <div class="version"> + Version: 2.3.5 + </div> + <div class="commit"> + Commit: $Id: 242e2db4c0bee7e7362e14e371d72189884d0d8a $ + </div> + </body> +</html>