Skip to content

Lab 5 - Deploying ApplicationsπŸ”—

Lab 5.1 Set up GitHub Repository
Lab 5.2 Build a Sample Web App in a Docker Container
Lab 5.3 Build a CI/CD Pipeline

5.1 Set up GitHub RepositoryπŸ”—

ObjectivesπŸ”—

    Part 1: Join the GitHub Classroom
    Part 2: Initialize the Local Repository
    Part 3: Set up the Remote Repository

InstructionsπŸ”—

Part 1: Join the GitHub ClassroomπŸ”—

  1. Follow the GitHub assignment link posted in Canvas. If you did not complete the setup in Lab 2.2 you will need to authorize GitHub Classroom to link with your account and select your name from the list of students.

  2. Accept the assignment and refresh the page until it says your repository has been created. Click the link to proceed to the repository.

    Important

    GitHub Classroom is currently experiencing a bug where you might receive a Repository Access Issue error after accepting the assignment. If this happens, check the e-mail connected to your GitHub account for an invitation e-mail from GitHub. Use the View Invitation link in the e-mail to see the link to accept the invitation and proceed to the repository.

  3. Select the Code dropdown and copy the HTTPS URL. Store it somewhere for use in a later step.

    Tip

    If you've configured your VM to use SSH to connect to GitHub instead of HTTPS, click on the Code dropdown, select the SSH tab, and copy the URL from there instead.

Part 2: Initialize the Local RepositoryπŸ”—

You will be creating a local repository from a folder on the DEVASC VM. This folder already contains some files, so you will need to initialize a repository in the folder, pull down the files from the remote GitHub Classroom repository, then add the local files to the local repository to combine the remote and local files into a single repository.

Important

If you did not complete Lab 2, or you have reset your VM since then, you'll need to update Git to the newest version as well as configure your name, e-mail address, and default branch name, before beginning this lab.

Open Lab 2 and complete steps 1, 2, and the first 3 tasks of step 3 of the Initializing Git instructions before continuing.

  1. Inside the DEVASC VM, open a terminal and change to the ~/labs/devnet-src/sample-app directory.

    devasc@labvm:~$ cd labs/devnet-src/sample-app/
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  2. View the listing of all files currently in the sample-app directory.

    devasc@labvm:~/labs/devnet-src/sample-app$ ls -la
    total 20
    drwxr-xr-x 4 devasc devasc 4096 Jun 1 2020 .
    drwxr-xr-x 15 devasc devasc 4096 Feb 12 21:41 ..
    -rw-r--r-- 1 devasc devasc 42 Apr 23 2020 sample_app.py
    -rw-r--r-- 1 devasc devasc 0 Apr 23 2020 sample-app.sh
    drwxr-xr-x 2 devasc devasc 4096 Jun 1 2020 static
    drwxr-xr-x 2 devasc devasc 4096 Jun 1 2020 templates
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  3. Initialize this directory as a Git repository (don't miss the . at the end of the command!).

    devasc@labvm:~/labs/devnet-src/sample-app$ git init .
    Initialized empty Git repository in /home/devasc/labs/devnet-src/sample-app/.git/
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Part 3: Set up the Remote RepositoryπŸ”—

  1. Since we initialized our own local repository, rather than cloning an existing remote repository, we need to tell Git the location of the remote repository to synchronize with. Configure the location of the remote repository using the git remote add command. Use the URL to the GitHub Classroom repository you copied at the end of Part 1, not the one in the command below.

    Tip

    If you've configured your VM to use SSH to connect to GitHub, use the SSH URL that you copied in step 1, rather than the HTTPS link.

    devasc@labvm:~/labs/devnet-src/sample-app$ git remote add origin https://github.com/Ontario-Tech-NITS/lab-5-application-deployment-<username>.git
    

    Important

    Make sure you use your GitHub URL that you copied in Part 1, not the one in the command example! Also, if you're using SSH, your URL will start with git@github.com rather than https://.

  2. Verify that the remote is properly configured with the git remote --verbose command.

    devasc@labvm:~/labs/devnet-src/sample-app$ git remote --verbose
    origin https://github.com/Ontario-Tech-NITS/lab-5-application-deployment-<username>.git (fetch)
    origin https://github.com/Ontario-Tech-NITS/lab-5-application-deployment-<username>.git (push)
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  3. Pull down the files from the remote repository into the local repository. Remember to use your Personal Access Token as the password.

    Tip

    If you've configured your VM to use SSH to connect to GitHub instead of HTTPS, you will not be asked for a username and password, although you may be asked for a passphrase for your SSH key, if you configured one.

    devasc@labvm:~/labs/devnet-src/sample-app$ git pull origin main
    Username for 'https://github.com': username
    Password for 'https://username@github.com': Personal Access Token
    remote: Enumerating objects: 14, done.
    remote: Counting objects: 100% (14/14), done.
    remote: Compressing objects: 100% (11/11), done.
    remote: Total 14 (delta 1), reused 6 (delta 0), pack-reused 0 (from 0)
    Unpacking objects: 100% (14/14), 5.01 KiB | 5.01 MiB/s, done.
    From github.com:Ontario-Tech-NITS/lab-5-application-deployment-<username>
    * branch            main       -> FETCH_HEAD
    * [new branch]      main       -> origin/main
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  4. Use git status to verify all the local files are currently untracked.

    devasc@labvm:~/labs/devnet-src/sample-app$ git status
    On branch main
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
            sample-app.sh
            sample_app.py
            static/
            templates/
    
    nothing added to commit but untracked files present (use "git add" to track)
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  5. Add the files from the local working folder to the staging area (don't miss the . at the end of the command!).

    devasc@labvm:~/labs/devnet-src/sample-app$ git add .
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  6. Commit these new files to the local repository. Use the commit message Initializing the repository.

    devasc@labvm:~/labs/devnet-src/sample-app$ git commit -m "Initializing the repository"
    [main d832df6] Initializing the repository
     4 files changed, 11 insertions(+)
     create mode 100644 sample-app.sh
     create mode 100644 sample_app.py
     create mode 100644 static/style.css
     create mode 100644 templates/index.html
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  7. Use git status again to verify that all files have been committed to the local repository and that you are working on a clean tree.

    devasc@labvm:~/labs/devnet-src/sample-app$ git status
    On branch main
    nothing to commit, working tree clean
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  8. Push the local repository to the remote repository on GitHub, adding the files from the DEVASC VM to the existing repository. Remember to use your Personal Access Token when prompted for your password.

    Tip

    If you've configured your VM to use SSH to connect to GitHub instead of HTTPS, you will not be asked for a username and password, although you may be asked for a passphrase for your SSH key, if you configured one.

    devasc@labvm:~/labs/devnet-src/sample-app$ git push origin main
    Username for 'https://github.com': username
    Password for 'https://username@github.com': Personal Access Token
    Enumerating objects: 9, done.
    Counting objects: 100% (9/9), done.
    Delta compression using up to 2 threads
    Compressing objects: 100% (3/3), done.
    Writing objects: 100% (8/8), 770 bytes | 770.00 KiB/s, done.
    Total 8 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
    To github.com:Ontario-Tech-NITS/lab-5-application-deployment-<username>.git
       49f5c26..c638a4b  main -> main
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  9. Using a web browser, open the remote repository on GitHub and verify that you see the newly tracked files. Your local and remote repositories are now synchronized, and you are ready to complete the rest of the lab, committing your changes as you go (when instructed).

    Screenshot of GitHub Repository at the end of Lab 5.1

    Note

    The Autograding Tests will say "failing" at this point because you haven't finished the lab. This is normal.

5.2 Build a Sample Web App in a Docker ContainerπŸ”—

ObjectivesπŸ”—

    Part 1: Create a Simple Bash Script
    Part 2: Create a Sample Web App
    Part 3: Configure the Web App to Use Website Files
    Part 4: Create a Bash Script to Build and Run a Docker Container
    Part 5: Build, Run, and Verify the Docker Container

BackgroundπŸ”—

In this lab, you will review basic bash scripting techniques because bash scripting is a prerequisite for the rest of the lab. You will then build and modify a Python script for a simple web application. Next, you will create a bash script to automate the process for creating a Dockerfile, building the Docker container, and running the Docker container. Finally, you will use Docker commands to investigate the intricacies of the Docker container instance.

InstructionsπŸ”—

Part 1: Create a Simple Bash ScriptπŸ”—

Bash knowledge is crucial for working with continuous integration, continuous deployment, containers, and with your development environment. Bash scripts help programmers automate a variety of tasks in one script file. In this part, you will briefly review how to create a bash script. Later in the lab, you will use a bash script to automate the creation of a web app inside of a Docker container.

Step 1: Create an empty bash script fileπŸ”—

  1. In the ~/labs/devnet-src/sample-app folder, add a new file called user-input.sh.

    devasc@labvm:~/labs/devnet-src/sample-app$ touch user-input.sh
    devasc@labvm:~/labs/devnet-src/sample-app$
    

    Note

    The touch command creates an empty file with the specified name, or modifies the timestamps of an existing file.

Step 2: Open the file in the nano text editorπŸ”—

  1. Use the nano command to open the nano text editor. (You could also try vim if you're feeling brave)

    devasc@labvm:~/labs/devnet-src/sample-app$ nano user-input.sh
    

Step 3: Add the shebang to the top of the scriptπŸ”—

From here you can enter commands for your bash script. Use the arrow keys to navigate in nano. Notice the commands at the bottom for managing the file. The carat symbol (^) indicates that you use the Ctrl or Cmd key on your keyboard. For example, to exit nano, type Ctrl+X.

  1. Add the shebang, which tells the system that this file includes commands that need to be run in the bash shell.

    Shell script
    #!/bin/bash
    

    Note

    You could use a graphical text editor or open the file with VS Code. However, you should be familiar with command-line text editors like nano and vim.

Step 4: Add simple bash commands to the scriptπŸ”—

  1. Enter some simple bash commands for your script. The following commands will ask the user for a name, set the name to a variable called userName, and display a string of text with the user's name.

    Shell script
    echo -n "Enter Your Name: "
    read userName
    echo "Your name is $userName."
    

Step 5: Exit nano and save your scriptπŸ”—

  1. Press Ctrl+X, then Y, then Enter to exit nano and save your script.

Step 6: Run your script from the command lineπŸ”—

  1. You can run your script directly from the command line using the following command:

    devasc@labvm:~/labs/devnet-src/sample-app$ bash user-input.sh
    Enter Your Name: Josh
    Your name is Josh.
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Step 7: Change the mode of the script to an executable file for all usersπŸ”—

  1. Change the mode of the script to an executable using the chmod command. Set the options to a+x to make the script executable (x) by all users (a). After using chmod, notice permissions have been modified for users, groups, and others to include x (executable).

    devasc@labvm:~/labs/devnet-src/sample-app$ ls -l user-input.sh
    -rw-rw-r-- 1 devasc devasc 84 Jun  7 16:43 user-input.sh
    devasc@labvm:~/labs/devnet-src/sample-app$ chmod a+x user-input.sh
    devasc@labvm:~/labs/devnet-src/sample-app$ ls -l user-input.sh
    -rwxrwxr-x 1 devasc devasc 84 Jun  7 16:43 user-input.sh
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Step 8: Rename the file to remove the .sh extensionπŸ”—

  1. You can rename the file to remove the extension so that users do not have to add .sh to the command to execute the script.

    devasc@labvm:~/labs/devnet-src/sample-app$ mv user-input.sh user-input
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Step 9: Execute the script from the command lineπŸ”—

  1. Now the script can be run from the command line without the bash command or the .sh extension. To run this bash script without the source command, you must preface the script with "./".

    devasc@labvm:~/labs/devnet-src/sample-app$ ./user-input
    Enter Your Name: Bob
    Your name is Bob.
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Step 10: Commit the changes to the repositoryπŸ”—

  1. Add the user-input file to the staging area then commit the changes to the local repository using the message Created a simple bash script.

    devasc@labvm:~/labs/devnet-src/sample-app$ git add user-input
    devasc@labvm:~/labs/devnet-src/sample-app$ git commit -m "Created a simple bash script"
    [main 431f101] Created a simple bash script
     1 file changed, 4 insertions(+)
     create mode 100755 user-input
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Part 2: Create a Sample Web AppπŸ”—

Before we can launch an application in a Docker container, we first need to have the app. In this part, you will create a very simple Python script that will display the IP address of the client when the client visits the web page.

Step 1: Install Flask and open a port on the DEVASC VM firewallπŸ”—

Web application developers using Python typically leverage a framework. A framework is a code library to make it easier for developers to create reliable, scalable, and maintainable web applications. Flask is a web application framework written in Python. Other examples of python web frameworks include Tornado and Pyramid.

You will use this framework to create the sample web app. Flask receives requests and then provides a response to the user in the web app. This is useful for dynamic web applications because it allows user interaction and dynamic content. What makes your sample web app dynamic is that it will be displaying the IP address of the client.

Note

Understanding Flask functions, methods, and libraries are beyond the scope of this course. It is used in this lab to show how quickly you can get a web application up and running. If you want to learn more, search the internet for more information and tutorials on the Flask framework.

  1. Open a terminal window and install flask.

    devasc@labvm:~/labs/devnet-src/sample-app$ pip3 install flask
    Requirement already satisfied: flask in /home/devasc/.local/lib/python3.8/site-packages (1.1.2)
    Requirement already satisfied: Jinja2>=2.10.1 in /home/devasc/.local/lib/python3.8/site-packages (from flask) (2.11.2)
    Requirement already satisfied: click>=5.1 in /home/devasc/.local/lib/python3.8/site-packages (from flask) (7.1.2)
    Requirement already satisfied: itsdangerous>=0.24 in /home/devasc/.local/lib/python3.8/site-packages (from flask) (1.1.0)
    Requirement already satisfied: Werkzeug>=0.15 in /home/devasc/.local/lib/python3.8/site-packages (from flask) (1.0.1)
    Requirement already satisfied: MarkupSafe>=0.23 in /home/devasc/.local/lib/python3.8/site-packages (from Jinja2>=2.10.1->flask) (1.1.1)
    devasc@labvm:~/labs/devnet-src/sample-app$
    

    Note

    Flask may already be installed on your VM, in which case you'll receive Requirement already satisfied messages as in the above output. This is fine.

Step 2: Open the sample_app.py fileπŸ”—

  1. Open the sample_app.py file located in the ~/labs/devnet-src/sample-app directory. You can do this inside VS Code or you can use a command-line text editor like nano or vim.

    Important

    MAKE SURE YOU OPEN THE PYTHON FILE (.py) NOT THE SCRIPT (.sh)

Step 3: Import methods from flaskπŸ”—

  1. Add the following commands to import the required methods from the flask library.

    Python
    from flask import Flask
    from flask import request
    

Step 4: Create an instance of the Flask classπŸ”—

  1. Leave a blank line and create an instance of the Flask class named sample. Be sure to use two underscores (_) before and after the word "name".

    Python
    sample = Flask(__name__)
    

Step 5: Define a method to display the client IP addressπŸ”—

  1. Next, leave another blank line and configure Flask so that when a user visits the default page (root directory, represented by "/"), it displays a message with the IP address of the client.

    Python
    @sample.route("/")
    def main():
        return f"You are calling me from {request.remote_addr}\n"
    

    Note

    Notice the @sample.route("/") Flask statement. Frameworks such as Flask use a routing technique (.route) to refer to an application URL (this not to be confused with network routing). Here the "/" (root directory) is bound to the main() function. So, when the user goes to http://localhost:8080/ (root directory) URL, the main() function is executed and the output of the return statement will be displayed in the browser.

Step 6: Configure the app to run locallyπŸ”—

  1. Finally, leave another blank line and configure Flask to run the app locally at http://0.0.0.0:8080, which is also http://localhost:8080. Be sure to use two underscores before and after "name", and before and after "main". This should not be part of the main() function you created in the previous step, so ensure the if statement is not indented.

    Python
    if __name__ == "__main__":
        sample.run(host="0.0.0.0", port=8080)
    

Step 7: Save and run your sample web appπŸ”—

  1. Save your script and run it from the command line. You should see the following output which indicates that your sample-app server is running. If you do not see the following output or if you receive an error message, check your sample_app.py script carefully.

    Note

    The CLI will freeze after this output since it is running the script in the foreground. This is normal and expected.

    devasc@labvm:~/labs/devnet-src/sample-app$ python3 sample_app.py
    * Serving Flask app "sample-app" (lazy loading)
    * Environment: production
      WARNING: This is a development server. Do not use it in a production deployment.
      Use a production WSGI server instead.
    * Debug mode: off
    * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
    

Step 8: Verify the server is runningπŸ”—

You can verify the server is running in one of two ways.

  1. Open the Chromium web browser and enter 0.0.0.0:8080 in the URL field. You should get the following output:

    You are calling me from 127.0.0.1
    

    If you receive an HTTP 400 Bad Request response, check your sample_app.py script carefully.

  2. Open a new terminal window and use the command-line URL tool (cURL) to verify the server's response.

    Important

    DO NOT CLOSE THE TERMINAL THAT IS RUNNING THE PYTHON SCRIPT.

    devasc@labvm:~/labs/devnet-src/sample-app$ curl http://0.0.0.0:8080
    You are calling me from 127.0.0.1
    devasc@labvm:~/labs/devnet-src/sample-app$
    

    Note

    If you return to the terminal window where the script is running, you can see the GET requests appearing in real time.

Step 9: Stop the serverπŸ”—

  1. Return to the terminal window where the server is running and press Ctrl+C to stop the server.

Step 10: Commit the changes to the repositoryπŸ”—

  1. Add the sample_app.py file to the staging area then commit the changes to the local repository using the message Created sample_app.py.

    devasc@labvm:~/labs/devnet-src/sample-app$ git add sample_app.py
    devasc@labvm:~/labs/devnet-src/sample-app$ git commit -m "Created sample_app.py"
    [main 7f70a67] Created sample_app.py
     1 file changed, 11 insertions(+)
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Part 3: Configure the Web App to use Website FilesπŸ”—

In this part, build out the sample web app to include an index.html page and style.css specification. The index.html is typically the first page loaded in a client's web browser when visiting your website. The style.css is a style sheet used to customize the look of the web page.

Step 1: Explore the directories that will be used by the web appπŸ”—

  1. The directories templates and static are already in the sample-app directory. Open the index.html and style.css to view their contents. If you are familiar with HTML and CSS, feel free to customize these directories and files as much as you like. However, be sure you keep the You are calling me from {{request.remote_addr}} Python code in the index.html file as this is the dynamic aspect of the sample web app.

    devasc@labvm:~/labs/devnet-src/sample-app$ cat templates/index.html
    <html>
    <head>
        <title>Sample app</title>
        <link rel="stylesheet" href="/static/style.css" />
    </head>
    <body>
        <h1>You are calling me from {{request.remote_addr}}</h1>
    </body>
    </html>
    devasc@labvm:~/labs/devnet-src/sample-app$ cat static/style.css
    body {background: lightsteelblue;}
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Step 2: Update the Python code for the sample web appπŸ”—

Now that you have explored the basic website files, you need to update the sample_app.py file so that it renders the index.html file instead of just returning data.

Generating HTML content using Python code can be cumbersome, especially when using conditional statements or repeating structures. The HTML file can be rendered in Flask automatically using the render_template() function. This requires importing the render_template() function from the flask library and editing the return function.

  1. Make the highlighted edits to your script.

    Python
    from flask import Flask
    from flask import request
    from flask import render_template
    
    sample = Flask(__name__)
    
    @sample.route("/")
    def main():
        return render_template("index.html")
    
    if __name__ == "__main__":
        sample.run(host="0.0.0.0", port=8080)
    

Step 3: Save and run your scriptπŸ”—

  1. Save and run your sample_app.py script. You should get output similar to the following:

    devasc@labvm:~/labs/devnet-src/sample-app$ python3 sample_app.py
     * Serving Flask app "sample-app" (lazy loading)
     * Environment: production
       WARNING: This is a development server. Do not use it in a production deployment.
       Use a production WSGI server instead.
     * Debug mode: off
     * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
    

    Tip

    If you get a traceback output and an error with a message similar to OSError: [Errno 98] Address already in use, then you did not shut down your previous server in Part 2 Step 9. Return to the terminal window where that server is running and press Ctrl+C to end the server process, then re-run your script.

Step 4: Verify your program is runningπŸ”—

Again, you can verify your program is running in one of two ways.

  1. Open the Chromium web browser and enter 0.0.0.0:8080 in the URL field. You should get the same output as before. However, your background will be light steel blue and the text will be formatted as H1.

    A screenshot showing the Chromium browser with the output "You are calling me from 127.0.0.1

  2. Open another terminal window and use the curl command to verify the server's response. This is where you will see the result of the HTML code rendered automatically using the render_template() function. In this case, you will get all the HTML content. However, the dynamic Python code will be replaced with the value for {{request.remote_addr}}.

    devasc@labvm:~/labs/devnet-src/sample-app$ curl http://0.0.0.0:8080
    <html>
    <head>
        <title>Sample app</title>
        <link rel="stylesheet" href="/static/style.css" />
    </head>
    <body>
        <h1>You are calling me from 127.0.0.1</h1>
    </body>
    </html>
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Step 5: Stop the serverπŸ”—

  1. Return to the terminal window where the server is running and press Ctrl+C to stop the server.

Step 6: Commit the changes to the repositoryπŸ”—

  1. Add the updated sample_app.py file to the staging area then commit the changes to the local repository using the message Updated sample_app.py to use HTML files.

    devasc@labvm:~/labs/devnet-src/sample-app$ git add sample_app.py
    devasc@labvm:~/labs/devnet-src/sample-app$ git commit -m "Updated sample_app.py to use HTML files"
    [main 8358720] Updated sample_app.py to use HTML files
     1 file changed, 2 insertions(+), 1 deletion(-)
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Part 4: Create a Bash Script to Build and Run a Docker ContainerπŸ”—

An application can be deployed on a bare metal server (physical server dedicated to a single-tenant environment) or in a virtual machine, like you just did in the previous part. It can also be deployed in a containerized solution like Docker. In this part, you will create a bash script and add commands to it that complete the following tasks to build and run a Docker container:

  • Create temporary directories to store the website files.
  • Copy the website directories and sample_app.py to the temporary directory.
  • Build a Dockerfile.
  • Build the Docker container.
  • Start the container and verify it is running.

Note

In this lab you are using a bash script to create the Dockerfile, rather than creating the Dockerfile manually. The is because this bash script will be used in the next part of the lab to instruct Jenkins to automatically build the Docker container on demand for automated testing and deployment. This bash script will create the necessary files and folders then create and launch the Docker container. There are many other ways you could accomplish this; this is just one option.

Step 1: Create temporary directories to store the website filesπŸ”—

  1. Open the sample-app.sh bash script file in the ~/labs/devnet-src/sample-app directory. Add the 'shebang' and the commands to create a directory structure with tempdir as the parent folder.

    Important

    NOTE THAT THIS IS THE BASH SCRIPT (.sh) NOT THE PYTHON FILE (.py).

    Shell script
    #!/bin/bash
    
    mkdir tempdir
    mkdir tempdir/templates
    mkdir tempdir/static
    

Step 2: Copy the website directories and sample_app.py to the temporary directoryπŸ”—

  1. In the sample-app.sh file, leave a blank line then add the commands to copy the website directory and script to tempdir.

    Important

    Don't miss the periods at the end of each line!

    Shell script
    cp sample_app.py tempdir/.
    cp -r templates/* tempdir/templates/.
    cp -r static/* tempdir/static/.
    

Step 3: Create a DockerfileπŸ”—

In this step, you enter the necessary bash echo commands to the sample-app.sh file to create a Dockerfile in the tempdir directory. This Dockerfile will be used to build the container.

Important

Use a capital "D" for all instances of "Dockerfile", as in the examples.

  1. You need Python running in the container, so leave a blank line then add the Docker FROM command to install Python in the container.

    Shell script
    echo "FROM python" > tempdir/Dockerfile
    
  2. Your sample_app.py script needs Flask, so add the Docker RUN command to install Flask in the container. Note the double angle brackets (>>), which will append the output to the end of the Dockerfile rather than overwrite it.

    Shell script
    echo "RUN pip install flask" >> tempdir/Dockerfile
    
  3. Your container will need the website folders and the sample_app.py script to run the app, so add the Docker COPY commands to add them to a directory in the Docker container. In this example, you will create /home/myapp as the parent directory inside the Docker container. Besides copying the sample_app.py file to the Dockerfile, you will also be copying the index.html file from the templates directory and the style.css file from the static directory.

    Shell script
    echo "COPY ./static /home/myapp/static/" >> tempdir/Dockerfile
    echo "COPY ./templates /home/myapp/templates/" >> tempdir/Dockerfile
    echo "COPY sample_app.py /home/myapp/" >> tempdir/Dockerfile
    
  4. Use the Docker EXPOSE command to expose port 8080 for use by the webserver.

    Shell script
    echo "EXPOSE 8080" >> tempdir/Dockerfile
    
  5. Finally, add the Docker CMD command to execute the Python script.

    Shell script
    echo "CMD python3 /home/myapp/sample_app.py" >> tempdir/Dockerfile
    
  6. Leave a blank line and add the commands to the sample-app.sh file to switch to the tempdir directory and build the Docker container. The docker build command -t option allows you to specify the name of the container and the trailing period (.) indicates that you want the container built in the current directory.

    Shell script
    cd tempdir
    docker build -t sampleapp .
    
  7. Leave a blank line and add the docker run command to the sample-app.sh file to start the container.

    Shell script
    docker run -t -d -p 8080:8080 --name samplerunning sampleapp
    

    Note

    The docker run options indicate the following:

    • -t specifies that you want a terminal created for the container so the you can access it at the command line.

    • -d indicates that you want the container to run in the background and print the container ID when executing the docker ps -a command.

    • -p specifies that you want to publish the container's internal port to the host. The first 8080 references the port for the app running in the docker container (our sampleapp). The second 8080 tells docker to use this port on the local host. These values do not have to be the same. For example, to map an internal port 80 to external port 800 you could use 80:800.

    • --name specifies what you want to call the instance of the container (samplerunning). The instance name can be anything you want.

    • The last paramter is the container image that the instance will be based on (sampleapp). The image name needs to match the container name you specified in the docker build command (sampleapp).

  8. Leave another blank line and add the docker ps -a command to display all currently running Docker containers. This command will be the last one executed by the bash script.

    Shell script
    docker ps -a
    

Step 4: Save your bash scriptπŸ”—

  1. Save and close the sample-app.sh file.

Part 5: Build, Run, and Verify the Docker ContainerπŸ”—

In this part, you will execute the bash script you just created, which will make the directories, copy over the files, create a Dockerfile, build the Docker container, run an instance of the Docker container, and display output from the docker ps -a command showing details of the container currently running. Then you will investigate the Docker container, stop the container from running, and remove the container.

Important

Be sure you stopped any other web server processes you may still have running from the previous parts of this lab.

Step 1: Update Docker and RuncπŸ”—

Before you can run the bash script you just wrote you must update Docker and its dependency, runc.

  1. In a terminal window, use the following command to update the information about the latest versions of available packages.

    devasc@labvm:~/labs/devnet-src/sample-app$ sudo apt-get update
    Get:1 http://security.ubuntu.com/ubuntu focal-security InRelease [128 kB]
    Hit:2 http://ppa.launchpad.net/git-core/ppa/ubuntu focal InRelease             
    Hit:3 http://archive.ubuntu.com/ubuntu focal InRelease                         
    Get:4 http://archive.ubuntu.com/ubuntu focal-updates InRelease [128 kB]
    Get:5 http://security.ubuntu.com/ubuntu focal-security/main amd64 Packages [3,564 kB]
    Get:6 http://archive.ubuntu.com/ubuntu focal-backports InRelease [128 kB]
    Get:7 http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages [3,957 kB]
    Get:8 http://security.ubuntu.com/ubuntu focal-security/main i386 Packages [881 kB]
    Get:9 http://archive.ubuntu.com/ubuntu focal-updates/main i386 Packages [1,114 kB]        
    Fetched 9,900 kB in 2s (4,708 kB/s)                          
    Reading package lists... Done
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  2. Install the latest updated versions of Docker and runc. Note that the package name for Docker is docker.io, not docker. The package called docker is an entirely different application. Don't miss the runc at the end of the command.

    devasc@labvm:~/labs/devnet-src/sample-app$ sudo apt-get install -y docker.io runc
    Reading package lists... Done
    Building dependency tree       
    Reading state information... Done
    The following additional packages will be installed:
      libseccomp2
    Suggested packages:
      aufs-tools btrfs-progs debootstrap docker-buildx docker-compose-v2 docker-doc rinse zfs-fuse | zfsutils
    The following packages will be upgraded:
      docker.io libseccomp2 runc
    3 upgraded, 0 newly installed, 0 to remove and 670 not upgraded.
    
      <output omitted for brevity>
    
    Processing triggers for man-db (2.9.1-1) ...
    Processing triggers for libc-bin (2.31-0ubuntu9) ...
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  3. Verify the versions of Docker and runc. They should be greater than or equal to the version numbers shown in the sample output below

    devasc@labvm:~/labs/devnet-src/sample-app$ docker -v
    Docker version 26.1.3, build 26.1.3-0ubuntu1~20.04.1
    devasc@labvm:~/labs/devnet-src/sample-app$ runc -v
    runc version 1.1.12-0ubuntu2~20.04.1
    spec: 1.0.2-dev
    go: go1.21.1
    libseccomp: 2.5.1
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Step 2: Execute the Bash scriptπŸ”—

  1. Execute the Bash script from the command line. You should see output similar to the following. After creating the tempdir directories, the script executes the commands to build the Docker container. Notice that Step 7/7 in the output executes the sample_app.py that creates the web server. Also, notice the container ID. Yours will be different than the one shown in the output, and you will see this in the Docker command prompt later in the lab.

    Note

    You may see some warnings while the script is executing. They should not affect the function of the app.

    devasc@labvm:~/labs/devnet-src/sample-app$ bash ./sample-app.sh
    DEPRECATED: The legacy builder is deprecated and will be removed in a future release.
                Install the buildx component to build images with BuildKit:
                https://docs.docker.com/go/buildx/
    
    Sending build context to Docker daemon  6.144kB
    Step 1/7 : FROM python
    latest: Pulling from library/python
    866771c43bf5: Pull complete 
    ed881fbf1b07: Pull complete 
    9da421ddeb65: Pull complete 
    a793e3c6bce8: Pull complete 
    36a6d998ca16: Pull complete 
    ab1b8c92e120: Pull complete 
    6052218b0e44: Pull complete 
    Digest: sha256:61346539f7b26521a230e72c11da5ebd872924745074b19736e7d65ba748c366
    Status: Downloaded newer image for python:latest
     ---> d8f278e9e42a
    Step 2/7 : RUN pip install flask
     ---> Running in 735c9c328c96
    Collecting flask
      Downloading flask-3.1.3-py3-none-any.whl.metadata (3.2 kB)
    Collecting blinker>=1.9.0 (from flask)
      Downloading blinker-1.9.0-py3-none-any.whl.metadata (1.6 kB)
    Collecting click>=8.1.3 (from flask)
      Downloading click-8.3.1-py3-none-any.whl.metadata (2.6 kB)
    Collecting itsdangerous>=2.2.0 (from flask)
      Downloading itsdangerous-2.2.0-py3-none-any.whl.metadata (1.9 kB)
    Collecting jinja2>=3.1.2 (from flask)
      Downloading jinja2-3.1.6-py3-none-any.whl.metadata (2.9 kB)
    Collecting markupsafe>=2.1.1 (from flask)
      Downloading markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (2.7 kB)
    Collecting werkzeug>=3.1.0 (from flask)
      Downloading werkzeug-3.1.6-py3-none-any.whl.metadata (4.0 kB)
    Downloading flask-3.1.3-py3-none-any.whl (103 kB)
    Downloading blinker-1.9.0-py3-none-any.whl (8.5 kB)
    Downloading click-8.3.1-py3-none-any.whl (108 kB)
    Downloading itsdangerous-2.2.0-py3-none-any.whl (16 kB)
    Downloading jinja2-3.1.6-py3-none-any.whl (134 kB)
    Downloading markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (23 kB)
    Downloading werkzeug-3.1.6-py3-none-any.whl (225 kB)
    Installing collected packages: markupsafe, itsdangerous, click, blinker, werkzeug, jinja2, flask
    
    Successfully installed blinker-1.9.0 click-8.3.1 flask-3.1.3 itsdangerous-2.2.0 jinja2-3.1.6 markupsafe-3.0.3 werkzeug-3.1.6
    WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning.
    
    [notice] A new release of pip is available: 25.3 -> 26.0.1
    [notice] To update, run: pip install --upgrade pip
     ---> Removed intermediate container 735c9c328c96
     ---> 9bbfe4f350b7
    Step 3/7 : COPY ./static /home/myapp/static/
     ---> a97b003ffb64
    Step 4/7 : COPY ./templates /home/myapp/templates/
     ---> 7f4d76b9aacd
    Step 5/7 : COPY sample_app.py /home/myapp/
     ---> 22edb219b811
    Step 6/7 : EXPOSE 8080
     ---> Running in 7d53e2bd202a
     ---> Removed intermediate container 7d53e2bd202a
     ---> d5f95163bf37
    Step 7/7 : CMD python3 /home/myapp/sample_app.py
     ---> Running in c80bfe065ba3
     ---> Removed intermediate container c80bfe065ba3
     ---> 003328deaec5
    Successfully built 003328deaec5
    Successfully tagged sampleapp:latest
    0d1adb17159e9a7ee0efb4b0fd84e5cc88738e10564f6d1675b830eb8afa2bee
    CONTAINER ID   IMAGE       COMMAND                  CREATED        STATUS                  PORTS                                       NAMES
    0d1adb17159e   sampleapp   "/bin/sh -c 'python3…"   1 second ago   Up Less than a second   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   samplerunning
    devasc@labvm:~/labs/devnet-src/sample-app$
    

    Note

    If at any point you need to remove a docker container (for example, if there is an error in your code), you can do so with the command docker rm <container_name>. You may need to stop the container first, with the command docker stop <container_name>.

Step 3: Investigate the running Docker container and the web appπŸ”—

  1. The creation of the tempdir directories is not shown in the output for the script. You could add echo commands to print out messages when they are successfully created. You can also verify they are there with the ls command. Remember, this directory has the files and folders used to build the container and launch the web app. It is not the container that was built.

    devasc@labvm:~/labs/devnet-src/sample-app$ ls tempdir/
    Dockerfile sample_app.py static templates
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  2. Notice the Dockerfile created by your bash script. Open this file to see how it looks in its final form without the echo commands.

    devasc@labvm:~/labs/devnet-src/sample-app$ cat tempdir/Dockerfile
    FROM python
    RUN pip install flask
    COPY ./static /home/myapp/static/
    COPY ./templates /home/myapp/templates/
    COPY sample_app.py /home/myapp/
    EXPOSE 8080
    CMD python3 /home/myapp/sample_app.py
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  3. The output for the docker ps -a command may be hard to read depending on the width of your terminal display. You can redirect it to a text file where you can view it better without word wrapping. Do that now.

    devasc@labvm:~/labs/devnet-src/sample-app$ docker ps -a >> running.txt
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  4. The Docker container creates its own IP address from a private network address space. Verify the web app is running and reporting the IP address. In Chromium, open http://localhost:8080. You should see the message You are calling me from 172.17.0.1 formatted as H1 on a light steel blue background (notice this is 172.17.0.1 and not 127.0.0.1 as in previous examples).

    Important

    DO NOT PROCEED FURTHER UNTIL THIS WORKS.

    Screenshot of the Chromium app showing the webpage hosted at localhost:8080

  5. By default, Docker uses the 172.17.0.0/16 IPv4 subnet for container networking (this subnet can be changed if necessary). Use the command ip address to display the IP addresses used by your instance of the DEVASC VM. You should see the loopback address 127.0.0.1 that the web app used earlier in the lab and the new Docker interface with the IP address 172.17.0.1.

    devasc@labvm:~/labs/devnet-src/sample-app$ ip address show dev lo
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
           valid_lft forever preferred_lft forever
        inet6 ::1/128 scope host
           valid_lft forever preferred_lft forever
    devasc@labvm:~/labs/devnet-src/sample-app$ ip address show dev docker0
    4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
        link/ether 02:42:c2:d1:8a:2d brd ff:ff:ff:ff:ff:ff
        inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
           valid_lft forever preferred_lft forever
        inet6 fe80::42:c2ff:fed1:8a2d/64 scope link
           valid_lft forever preferred_lft forever
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Step 4: Access and explore the running containerπŸ”—

Remember that a Docker container is a way of encapsulating everything you need to run your application so that it can easily be deployed in a variety of environments, not just in your DEVASC VM.

  1. To access the running container, enter the docker exec -it command specifying the name of the running container (samplerunning) and that you want a bash shell (/bin/bash). The -i option specifies that you want it to be interactive and the -t option specifies that you want terminal access. The prompt changes to root@containerID. Your container ID will be different than the one shown below. Notice the container ID matches the ID shown in the output from docker ps -a.

    devasc@labvm:~/labs/devnet-src/sample-app$ docker exec -it samplerunning /bin/bash
    root@0d1adb17159e:/#
    
  2. You are now connected as root on the command line of your samplerunning Docker container. From here, you can use Linux commands to explore the Docker container. Enter ls to see the directory structure at the root level.

    root@0d1adb17159e:/# ls
    bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
    root@0d1adb17159e:/#
    
  3. Recall that in your bash script, you added commands in the Dockerfile that copied your app directories and files to the home/myapp directory inside the Docker container. Enter the ls command again for that folder to see your sample_app.py script and directories. To get a better understanding of what is included in your Docker container, you may wish to use the ls command to examine other directories such as /etc and /bin.

    root@0d1adb17159e:/# ls home/myapp/
    sample_app.py static templates
    root@0d1adb17159e:/#
    
  4. Exit the Docker container to return to the DEVASC VM command line.

    root@0d1adb17159e:/# exit
    exit
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Step 5: Stop and remove the Docker containerπŸ”—

  1. You can stop the Docker container with the docker stop command specifying the name of the running container. It will take a few seconds to clean up and cache the container. You can see that it still exists by entering the docker ps -a command. However, if you refresh the web page for http://localhost:8080, you will see the web app is no longer running.

    devasc@labvm:~/labs/devnet-src/sample-app$ docker stop samplerunning
    samplerunning
    devasc@labvm:~/labs/devnet-src/sample-app$ docker ps -a
    CONTAINER ID   IMAGE       COMMAND                  CREATED          STATUS                       PORTS     NAMES
    0d1adb17159e   sampleapp   "/bin/sh -c 'python3…"   16 minutes ago   Exited (137) 4 seconds ago             samplerunning
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  2. You can restart a stopped container with the docker start command. The container will immediately spin up.

    devasc@labvm:~/labs/devnet-src/sample-app$ docker start samplerunning
    samplerunning
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  3. To permanently remove the container, first stop it and then remove it with the docker rm command. You can always rebuild it again executing the sample-app program. Use the docker ps -a command to verify the container has been removed.

    devasc@labvm:~/labs/devnet-src/sample-app$ docker stop samplerunning
    samplerunning
    devasc@labvm:~/labs/devnet-src/sample-app$ docker rm samplerunning
    samplerunning
    devasc@labvm:~/labs/devnet-src/sample-app$ docker ps -a
    CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Step 6: Commit these changes to the repositoryπŸ”—

  1. Use git status to see which files have been added and modified since the last commit.

    devasc@labvm:~/labs/devnet-src/sample-app$ git status
    On branch main
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git restore <file>..." to discard changes in working directory)
            modified: sample-app.sh
    
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
            running.txt
            tempdir/
    
    no changes added to commit (use "git add" and/or "git commit -a")
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  2. Add all the files and folders to the staging area with git add . (don't miss the "." at the end). Verify all files have now been staged with git status.

    devasc@labvm:~/labs/devnet-src/sample-app$ git add .
    devasc@labvm:~/labs/devnet-src/sample-app$ git status
    On branch main
    Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
            new file: running.txt
            modified: sample-app.sh
            new file: tempdir/Dockerfile
            new file: tempdir/sample_app.py
            new file: tempdir/static/style.css
            new file: tempdir/templates/index.html
    
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  3. Commit the changes to the local repository using the message Created Dockerfile.

    devasc@labvm:~/labs/devnet-src/sample-app$ git commit -m "Created Dockerfile"
    [main aa3f42d] Created Dockerfile
     6 files changed, 56 insertions(+)
     create mode 100644 running.txt
     create mode 100644 tempdir/Dockerfile
     create mode 100644 tempdir/sample_app.py
     create mode 100644 tempdir/static/style.css
     create mode 100644 tempdir/templates/index.html
    devasc@labvm:~/labs/devnet-src/sample-app$
    

5.3 Build a CI/CD PipelineπŸ”—

ObjectivesπŸ”—

    Part 1: Modify the Sample App and Push Changes to Git
    Part 2: Download and Run the Jenkins Docker Image
    Part 3: Configure Jenkins
    Part 4: Use Jenkins to Run a Build of Your App
    Part 5: Use Jenkins to Test a Build
    Part 6: Create a Pipeline in Jenkins

Background / ScenarioπŸ”—

In this lab, you will install a Docker container that includes the latest version of Jenkins. You will configure Jenkins and then use Jenkins to download and run your Sample App program. Next, you will create a testing job inside Jenkins that will verify your Sample App program successfully runs each time you build it. Finally, you will integrate your Sample App and testing job into a Continuous Integration/Continuous Development pipeline that will verify your Sample App is ready to be deployed each time you change the code.

InstructionsπŸ”—

Part 1: Modify the Sample App and Push Changes to GitπŸ”—

In Part 2, you will install a Jenkins Docker image that will use port 8080. Recall that your sample-app files are also specifying port 8080. The Flask server and Jenkins server cannot both use 8080 at the same time.

In this part, you will change the port number used by the sample-app files, run the sample-app again to verify it works on the new port, and then push your changes to your GitHub repository.

Step 1: Open the sample-app filesπŸ”—

Make sure you are still in the ~/labs/devnet-src/sample-app directory as these are the files that are associated with your GitHub repository. Open both sample_app.py and sample-app.sh for editing.

Step 2: Edit the sample-app filesπŸ”—

  1. In sample_app.py, change the one instance of port 8080 to 5050 as shown below.

    Python
    from flask import Flask
    from flask import request
    from flask import render_template
    
    sample = Flask(__name__)
    
    @sample.route("/")
    def main():
     return render_template("index.html")
    
    if __name__ == "__main__":
      sample.run(host="0.0.0.0", port=5050)
    
  2. In sample-app.sh, change the three instances of port 8080 to 5050 as shown below.

    #!/bin/bash
    
    mkdir tempdir
    mkdir tempdir/templates
    mkdir tempdir/static
    
    cp sample_app.py tempdir/.
    cp -r templates/* tempdir/templates/.
    cp -r static/* tempdir/static/.
    
    echo "FROM python" >> tempdir/Dockerfile
    echo "RUN pip install flask" >> tempdir/Dockerfile
    echo "COPY ./static /home/myapp/static/" >> tempdir/Dockerfile
    echo "COPY ./templates /home/myapp/templates/" >> tempdir/Dockerfile
    echo "COPY sample_app.py /home/myapp/" >> tempdir/Dockerfile
    echo "EXPOSE 5050" >> tempdir/Dockerfile
    echo "CMD python3 /home/myapp/sample_app.py" >> tempdir/Dockerfile
    
    cd tempdir
    docker build -t sampleapp .
    
    docker run -t -d -p 5050:5050 --name samplerunning sampleapp
    
    docker ps -a
    

Step 3: Build and verify the sample-appπŸ”—

  1. Enter the bash command to build your app using the new port 5050.

    devasc@labvm:~/labs/devnet-src/sample-app$ bash ./sample-app.sh 
    Sending build context to Docker daemon  6.144kB
    Step 1/7 : FROM python
     ---> d8f278e9e42a
    Step 2/7 : RUN pip install flask
     ---> Using cache
     ---> 9bbfe4f350b7
    Step 3/7 : COPY ./static /home/myapp/static/
     ---> Using cache
     ---> a97b003ffb64
    Step 4/7 : COPY ./templates /home/myapp/templates/
     ---> Using cache
     ---> 7f4d76b9aacd
    Step 5/7 : COPY sample_app.py /home/myapp/
     ---> 82bab0a44d53
    Step 6/7 : EXPOSE 5050
     ---> Running in 80a695244045
     ---> Removed intermediate container 80a695244045
     ---> a506392523eb
    Step 7/7 : CMD python3 /home/myapp/sample_app.py
     ---> Running in 462534633c42
     ---> Removed intermediate container 462534633c42
     ---> c44773869790
    Successfully built c44773869790
    Successfully tagged sampleapp:latest
    f68433915e7ab74cbfeafce2a223fcf5665afc76e35442ea42e2aa8b5e4a6fb2
    CONTAINER ID   IMAGE       COMMAND                  CREATED                  STATUS                  PORTS                                       NAMES
    f68433915e7a   sampleapp   "/bin/sh -c 'python3…"   Less than a second ago   Up Less than a second   0.0.0.0:5050->5050/tcp, :::5050->5050/tcp   samplerunning
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  2. Open a Chromium browser tab and navigate to http://localhost:5050. You should see the message You are calling me from 172.17.0.1.

    Screenshot of the Chromium browser showing the app running on port 5050

  3. Stop the docker container with docker stop samplerunning.

    devasc@labvm:~/labs/devnet-src/sample-app$ docker stop samplerunning 
    samplerunning
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  4. Remove the docker container with docker rm samplerunning.

    devasc@labvm:~/labs/devnet-src/sample-app$ docker rm samplerunning 
    samplerunning
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Step 4: Push your changes to GitHubπŸ”—

  1. Now you are ready to commit your changes to your Git repository. Enter the following commands.

    devasc@labvm:~/labs/devnet-src/sample-app$ git add .
    devasc@labvm:~/labs/devnet-src/sample-app$ git status
    On branch master
    Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
            modified:   sample-app.sh
            modified:   sample_app.py
            modified:   tempdir/Dockerfile
            modified:   tempdir/sample_app.py
    
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  2. Commit the changes to the local repository using the message Changed port from 8080 to 5050.

    devasc@labvm:~/labs/devnet-src/sample-app$ git commit -m "Changed port from 8080 to 5050"
    [main 4184ce6] Changed port from 8080 to 5050
     4 files changed, 5 insertions(+), 5 deletions(-)
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  3. In an upcoming step, you will be configuring Jenkins to pull a copy of your code from GitHub, so let's push these changes to GitHub now. Remember to use your Personal Access Token when asked for your GitHub password.

    devasc@labvm:~/labs/devnet-src/sample-app$ git push origin main
    Username for 'https://github.com': <username>
    Password for 'https://<username>@github.com': Personal Access Token
    Enumerating objects: 24, done.
    Counting objects: 100% (24/24), done.
    Delta compression using up to 2 threads
    Compressing objects: 100% (21/21), done.
    Writing objects: 100% (21/21), 2.73 KiB | 2.73 MiB/s, done.
    Total 21 (delta 8), reused 0 (delta 0), pack-reused 0 (from 0)
    remote: Resolving deltas: 100% (8/8), done.
    To github.com:Ontario-Tech-NITS/lab-5-application-deployment-<username>.git
       c638a4b..4184ce6  main -> main
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  4. In your browser, return to the repository on github.com and verify your changes are present, along with the corresponding commit messages.

    Screenshot of a github.com repository with the changes and commit messages present

    Tip

    As mentioned before, the Autograding Tests will show failing since you haven't completed the lab yet. They only show passing if all of the tests are successful. You may also get an e-mail alert from GitHub that your tests are failing. You can safely ignore that until the lab is complete.

Part 2: Download and Run the Jenkins Docker ImageπŸ”—

In this part, you will download the Jenkins Docker image. You will then start an instance of the image and verify that the Jenkins server is running.

Step 1: Download the Jenkins Docker imageπŸ”—

The Jenkins Docker image is stored here: https://hub.docker.com/r/jenkins/jenkins. At the time of the writing of this lab, that site specifies that you use the command docker pull jenkins/jenkins to download the latest Jenkins container.

  1. Download the Jenkins Docker container from Docker Hub. You should get output similar to the following (your output may vary slightly):

    devasc@labvm:~/labs/devnet-src/sample-app$ docker pull jenkins/jenkins
    Using default tag: latest
    latest: Pulling from jenkins/jenkins
    53c88f1dfeb7: Pull complete 
    0a00411d4b37: Pull complete 
    701f6e686815: Pull complete 
    9be4edd19174: Pull complete 
    c8dc22cab1b3: Pull complete 
    a2a1d17a551e: Pull complete 
    d25669f3d454: Pull complete 
    e060a11317c3: Pull complete 
    49d42eeda86a: Pull complete 
    7065cd6dcc99: Pull complete 
    b16d777bb084: Pull complete 
    f07b36c014af: Pull complete 
    Digest: sha256:02b23d7053a0849e7c9c275ad1fb5d1308713189e644707300145dd270c2037b
    Status: Downloaded newer image for jenkins/jenkins:latest
    docker.io/jenkins/jenkins:latest
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Step 2: Start the Jenkins Docker containerπŸ”—

  1. Enter the following command on one line (no line breaks). This command will start the Jenkins Docker container and then allow Docker commands to be executed inside your Jenkins server.

    devasc@labvm:~/labs/devnet-src/sample-app$ docker run --rm -u root -p 8080:8080 -v jenkins-data:/var/jenkins_home -v $(which docker):/usr/bin/docker -v /var/run/docker.sock:/var/run/docker.sock -v "$HOME":/home --name jenkins_server jenkins/jenkins
    

    The options used in this docker run command are as follows:

    • --rm This option automatically removes the Docker container when you stop running it.

    • -u This option specifies the user. You want this Docker container to run as root so that all Docker commands entered inside the Jenkins server are allowed.

    • -p This option specifies the port the Jenkins server will run on locally.

    • -v These options bind mount volumes needed for Jenkins and Docker.

      • The first -v specifies where Jenkins data will be stored (/var/jenkins_home).
      • The second and third -v specifies where to find the Docker executable and socket so that you can run Docker inside the Docker container that is running the Jenkins server (nested Docker containers).
      • The last -v specifies the PATH variable for the home directory.
    • --name This is the name that the new Docker container will use.

    • jenkins/jenkins This is the name of the Docker image that will be used to build the container. It's the image we pulled down from Docker Hub in the previous step.

Step 3: Verify the Jenkins server is runningπŸ”—

  1. Watch for the following output on the screen (it may not be the very last line of the output, so look carefully):

    INFO hudson.lifecycle.Lifecycle#onReady: Jenkins is fully up and running
    
  2. The Jenkins server should now be running. Copy the admin password that displays in the output (your password will be different than the one shown below).

    Important

    Don't use Ctrl+C to try to copy the password or you may kill your Jenkins Docker container.

    <output omitted>
    *************************************************************
    *************************************************************
    *************************************************************
    
    Jenkins initial setup is required. An admin user has been created and a password generated.
    Please use the following password to proceed to installation:
    
    77dc402e31324c1b917f230af7bfebf2
    
    This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
    
    *************************************************************
    *************************************************************
    *************************************************************
    <output omitted>
    2020-05-12 16:34:29.608+0000 [id=19] INFO hudson.WebAppMain$3#run: Jenkins is fully up and running
    

    Important

    Do not enter any commands in this server window. If you accidentally stop the Jenkins server, you will need to re-enter the docker run command from Step 2 above.

    Note

    If you lose the password or you need to restart the Jenkins sever, you can always retrieve the password by accessing the command line of Jenkins Docker container. Create a second terminal window and enter the following commands so that you do not stop the Jenkins server. You don't need to do this unless you can't find the password.

    devasc@labvm:~/labs/devnet-src/sample-app$ docker exec -it jenkins_server /bin/bash
    root@19d2a847a54e:/# cat /var/jenkins_home/secrets/initialAdminPassword
    77dc402e31324c1b917f230af7bfebf2
    root@19d2a847a54e:/# exit
    exit
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Step 4: Investigate the levels of abstraction currently running on your computerπŸ”—

  1. The following ASCII diagram shows the levels of abstraction in this Docker-inside-Docker (DinD) implementation. This level of complexity is not unusual in today's networks and cloud infrastructures.

    +----------------------------------------+
    |    Your Computer's Operating System    |
    |  +----------------------------------+  |
    |  |            DEVASC VM             |  |
    |  |  +----------------------------+  |  |
    |  |  |       Docker container     |  |  |
    |  |  |  +----------------------+  |  |  |
    |  |  |  |    Jenkins server    |  |  |  |
    |  |  |  |  +----------------+  |  |  |  |
    |  |  |  |  |Docker container|  |  |  |  |
    |  |  |  |  +----------------+  |  |  |  |
    |  |  |  +----------------------+  |  |  |
    |  |  +----------------------------+  |  |
    |  +----------------------------------+  |
    +----------------------------------------+
    

Part 3: Configure JenkinsπŸ”—

In this Part, you will complete the initial configuration of the Jenkins server.

Step 1: Update ChromiumπŸ”—

  1. Before using the Jenkins web interface, you will need to update the Chromium web browser to a newer version using Ubuntu Snap. Close all open Chromium browser windows and open a second terminal window, then refresh the Chromium snap to install the latest version.

    devasc@labvm:~/labs/devnet-src/sample-app$ sudo snap refresh chromium
    chromium 145.0.7632.116 from Canonicalβœ“ refreshed
    devasc@labvm:~/labs/devnet-src/sample-app$
    

    Note

    It will likely take a few minutes to complete the update. Wait until you are returned to the command prompt and you see βœ“ refreshed before continuing to the next step.

Step 2: Open a web browser tabπŸ”—

  1. Open Chromium and navigate to http://localhost:8080/, then login with your copied admin password.

    Screenshot of the initial login page of Jenkins

  1. Click Install suggested plugins and wait for Jenkins to download and install the plugins. In the terminal window, you will see log messages as the installation proceeds. Be sure that you do not close this terminal window. You can open another terminal window for access to the command line if necessary.

    Screenshot of the Install Suggested Plugins window

Step 4: Skip creating a new admin userπŸ”—

  1. After the installation finishes, you are presented with the Create First Admin User window. For now, click Skip and continue as admin at the bottom.

    Screenshot of the Create First Admin User window

Step 5: Create an instance configurationπŸ”—

  1. In the Instance Configuration window, do not change anything. Click Save and Finish at the bottom.

    Screenshot of the Instance Configuration window

Step 6: Start using JenkinsπŸ”—

  1. In the next window, click Start using Jenkins. You should now be on the main dashboard with a Welcome to Jenkins! message.

    Screenshot showing the Jenkins is ready message

Part 4: Use Jenkins to Run a Build of Your AppπŸ”—

The fundamental unit of Jenkins is the job (also known as a project). You can create jobs that do a variety of tasks including the following:

  • Retrieve code from a source code management repository such as GitHub.

  • Build an application using a script or build tool.

  • Package an application and run it on a server.

In this part, you will create a simple Jenkins job that retrieves the latest version of your sample-app from GitHub and runs the build script. In Jenkins, you can then test your app and add it to a development pipeline.

Step 1: Create a new jobπŸ”—

  1. Click the Create a job link directly below the Welcome to Jenkins! message. Alternatively, you can click New Item in the menu on the left.

    Screenshot showing the Create a Job link

  2. In the Enter an item name field, fill in the name BuildAppJob. Click Freestyle project as the job type and click OK at the bottom of the screen.

    Screenshot showing the Name field

    Note

    The acronym SCM in the description of the Freestyle project stands for Source Code Management, which is a classification of software that is responsible for tracking and controlling changes in software (e.g., Git)

Step 2: Configure the Jenkins BuildAppJobπŸ”—

You are now in the configuration window where you can enter details about your job. The tabs across the left side are just shortcuts to the sections below. Click through the tabs to explore the options you can configure. For this simple job, you only need to add a few configuration details.

  1. Click the General tab, add a description for your job. For example, My first Jenkins job.

  2. Click the Source Code Management tab and choose the Git radio button. In the Repository URL field, add your GitHub repository link for the sample-app taking care to enter your case-sensitive username. Be sure to add the .git extension at the end of your URL. Use the HTTP URL, even if you have SSH configured. For example:

    Screenshot showing the SCM field in the New Item Configuration

    Note

    You will receive an error message that the connection has failed. This is because you have not selected the credentials yet.

    Note

    It is also possible to connect to your GitHub repository using SSH, but it's several extra steps and is outside the scope of this lab.

  3. For Credentials, click the Add button and choose Global.

  4. In the Add Credentials dialog box, select the Username with password option, then click Next.

    Screenshot of the Credential Type dialog box

  5. Fill in your GitHub username in the Username field and your Personal Access Token in the password field, then click Create.

  6. In the dropdown for Credentials where it currently says None, choose the credentials you just configured.

  7. After you have added the correct URL and credentials, Jenkins tests access to the repository.You should have no error messages. If you do, verify your URL and credentials. You will need to add them again as there is no way at this point to delete the ones you previously entered.

  8. Under Branches to build, change */master to */main.

    Screenshot of the Branches to build part of the configuration

  9. On the left of the BuildAppJob configuration window, click the Build Steps tab.

  10. Click the From Add build step button, and choose Execute shell from the dropdown.

    Screenshot of the Add Build Steps part of the configurations dialog

  11. In the Command field, enter the command you use to run the build for sample-app.sh script.

    bash ./sample-app.sh
    
  12. Click the Save button. You are returned to the Jenkins dashboard with the BuildAppJob selected.

Step 3: Have Jenkins build the appπŸ”—

  1. On the left side, click Build Now to start the job. Jenkins will download your Git repository and execute the build command bash ./sample-app.sh. Your build should succeed because you have not changed anything in the code (green checkmark). Your build number should be #1 unless you have built the app multiple times.

    Screenshot of successful build #1.

Step 4: View the console outputπŸ”—

  1. On the left, click the dropdown error next to your successful build and select Console Output.

    Screenshot showing the location of the Console Output menu item

  2. You should see output similar to the following. Notice the success messages at the bottom as well as the output from the docker ps -a command. Two docker containers are running: one for your sample-app running on local port 5050 and one for Jenkins on local port 8080.

    Started by user admin
    Running as SYSTEM
    Building in workspace /var/jenkins_home/workspace/BuildAppJob
    The recommended git tool is: NONE
    using credential e3276386-7924-45f2-8558-4a8410522a2a
    Cloning the remote Git repository
    Cloning repository https://github.com/Ontario-Tech-NITS/lab-5-application-deployment-<username>.git
     > git init /var/jenkins_home/workspace/BuildAppJob # timeout=10
    Fetching upstream changes from https://github.com/Ontario-Tech-NITS/lab-5-application-deployment-<username>.git
     > git --version # timeout=10
     > git --version # 'git version 2.47.3'
    using GIT_ASKPASS to set credentials 
     > git fetch --tags --force --progress -- https://github.com/Ontario-Tech-NITS/lab-5-application-deployment-<username>.git +refs/heads/*:refs/remotes/origin/* # timeout=10
     > git config remote.origin.url https://github.com/Ontario-Tech-NITS/lab-5-application-deployment-<username>.git # timeout=10
     > git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
    Avoid second fetch
     > git rev-parse refs/remotes/origin/main^{commit} # timeout=10
    Checking out Revision 4184ce60df8d8aa62aba63f3bc197a5632fe8810 (refs/remotes/origin/main)
     > git config core.sparsecheckout # timeout=10
     > git checkout -f 4184ce60df8d8aa62aba63f3bc197a5632fe8810 # timeout=10
    Commit message: "Changed port from 8080 to 5050"
    First time build. Skipping changelog.
    [BuildAppJob] $ /bin/sh -xe /tmp/jenkins13901989557087737154.sh
    + bash ./sample-app.sh
    Sending build context to Docker daemon  6.144kB
    
    Step 1/7 : FROM python
     ---> d8f278e9e42a
    Step 2/7 : RUN pip install flask
     ---> Using cache
     ---> 9bbfe4f350b7
    Step 3/7 : COPY ./static /home/myapp/static/
     ---> 09232a2a3cda
    Step 4/7 : COPY ./templates /home/myapp/templates/
     ---> ada98e832a47
    Step 5/7 : COPY sample_app.py /home/myapp/
     ---> 58adca85488e
    Step 6/7 : EXPOSE 5050
     ---> Running in 06ef56509712
     ---> Removed intermediate container 06ef56509712
     ---> 8069b1a5d983
    Step 7/7 : CMD python3 /home/myapp/sample_app.py
     ---> Running in c668d8511d89
     ---> Removed intermediate container c668d8511d89
     ---> 38b62db16e65
    Successfully built 38b62db16e65
    Successfully tagged sampleapp:latest
    cb9dda0d66d8579cac778933512b3bbff5d79788ace6ce21160a49f2cd465180
    CONTAINER ID   IMAGE             COMMAND                  CREATED          STATUS                  PORTS                                                  NAMES
    cb9dda0d66d8   sampleapp         "/bin/sh -c 'python3…"   1 second ago     Up Less than a second   0.0.0.0:5050->5050/tcp, :::5050->5050/tcp              samplerunning
    bc86f30b8896   jenkins/jenkins   "/usr/bin/tini -- /u…"   53 minutes ago   Up 52 minutes           0.0.0.0:8080->8080/tcp, :::8080->8080/tcp, 50000/tcp   jenkins_server
    Finished: SUCCESS
    

    Note

    If you see errors about a duplicate container name, you need to stop and remove the existing samplerunning docker container from a terminal window (docker stop samplerunning then docker rm samplerunning) and try again.

Step 5: Save the Console OutputπŸ”—

In this step you will save a copy of the console output as a text file to be committed to your Git repository for grading.

  1. In the top right side of the output window, click View as plain text.

  2. Right-click in the new page that loads and choose Save as…

  3. In the Save File dialog box that opens, navigate to /home/devasc/labs/devnet-src/sample-app.

  4. In the Name field at the top of the dialog box, name the file BuildAppJob.txt (CASE SENSITIVE!) then click Save.

    Screenshot of the Save File dialog box

  5. Click the back button on the browser to return to the Jenkins console.

Step 6: Open another web browser tab and verify sample app is runningπŸ”—

  1. Open a new browser tab and type in the local address, localhost:5050. You should see the content of your index.html displayed in light steel blue background color with You are calling me from 172.17.0.1 displayed in an H1 tag.

    Screenshot of the index.html website open in Chromium

Part 5: Use Jenkins to Test a BuildπŸ”—

In this part, you will create a second job that tests the build to ensure that it is working properly.

  1. You first need to stop and remove the samplerunning docker container. Open a new terminal window (don't use the one running Jenkins) and enter the following commands:

    devasc@labvm:~/labs/devnet-src/sample-app$ docker stop samplerunning
    samplerunning
    devasc@labvm:~/labs/devnet-src/sample-app$ docker rm samplerunning
    samplerunning
    

Step 1: Start a new job for testing your sample-appπŸ”—

  1. Return to the Jenkins web browser tab and click the Jenkins link in the top left corner to return to the main dashboard.

  2. Click the New Item link to create a new job.

    Screenshot showing the Jenkins dashboard

  3. In the Enter an item name field, fill in the name TestAppJob.

  4. Click Freestyle project as the job type.

  5. Scroll to the bottom and click OK.

    Screenshot showing the Jenkins New Item form for TestAppJob

Step 2: Configure the Jenkins TestAppJobπŸ”—

  1. Add a description for your job. For example, My first Jenkins test.

  2. Leave Source Code Management set to None.

  3. Click the Triggers tab and check the box, Build after other projects are built. For Projects to watch, fill in the name BuildAppJob.

    Screenshot of the Triggers section of the TestAppJob

Step 3: Write the test script that should run after a stable build of the BuildAppJobπŸ”—

  1. Click the Build Steps tab.

  2. Click Add build step and choose Execute shell.

    Screenshot of the Build Steps options

  3. Enter the following script. The if command should be all on one line, ending with ; then.

    This command will grep the output returned from the curl command to see if You are calling me from 172.17.0.1 is returned. If true, the script exits with a code of 0, which means that there are no errors in the BuildAppJob build. If false, the script exits with a code of 1 which means the BuildAppJob failed.

    if curl http://172.17.0.1:5050/ | grep "You are calling me from 172.17.0.1"; then
       exit 0
    else
       exit 1
    fi
    
  4. Click Save and then the Jenkins link on the left side to return to the Jenkins dashboard.

Step 4: Have Jenkins run the BuildAppJob job againπŸ”—

  1. Refresh the web page with the refresh button for your browser.

  2. You should now see your two jobs listed in the table. For the BuildAppJob job, click the build button on the far right (a green triangle ).

    Screenshot of the Jenkins dashboard showing the new TestAppJob

Step 5: Verify both jobs completedπŸ”—

  1. If all goes well, you should see the timestamp for the Last Success column update for both BuildAppJob and TestAppJob. This means your code for both jobs ran without error. But you can also verify this for yourself.

    A screenshot showing both the build and test jobs completed successfully

    Note

    If the timestamps do not update refresh the webpage.

  2. Click TestAppJob in the table on the Dashboard.

    Screenshot showing where to click on TestAppJob in the Jenkins Dashboard

  3. On the next page, under Permalinks, click the link for your last build, and then click Console Output in the menu on the left. You should see output similar to the following:

    Started by upstream project "BuildAppJob" build number 3
    originally caused by:
     Started by user admin
    Running as SYSTEM
    Building in workspace /var/jenkins_home/workspace/TestAppJob
    [TestAppJob] $ /bin/sh -xe /tmp/jenkins4597402301012103841.sh
    + + grep You are calling me from 172.17.0.1
    curl http://172.17.0.1:5050/
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    
      0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
    100   177  100   177    0     0  42304      0 --:--:-- --:--:-- --:--:-- 44250
        <h1>You are calling me from 172.17.0.1</h1>
    + exit 0
    Finished: SUCCESS
    
  4. It is not necessary to verify your sample app is running because the TestAppJob already did this for you. However, you can open a new browser tab for 172.17.0.1:5050 to see that it is indeed running.

Step 6: Save the Console OutputπŸ”—

In this step you will save a copy of the console output as a text file to be committed to your Git repository for grading.

  1. On the right side of the console window, click View as plain text.

  2. Right-click in the new page that loads and choose Save as…

  3. In the Save File dialog box that opens, navigate to /home/devasc/labs/devnet-src/sample-app if you are not already there.

  4. In the Name field at the top of the dialog box, name the file TestAppJob.txt (CASE SENSITIVE!) then click Save.

    Note

    The filename is different from the previous time you saved the output.

    A screenshot of the Save File dialog box

  5. Click the back button on the browser to return to the Jenkins console.

Part 6: Create a Pipeline in JenkinsπŸ”—

Although you can currently run your two jobs by simply clicking the Build Now button for the BuildAppJob, software development projects are typically much more complex. These projects can benefit greatly from automating builds for continuous integration of code changes and continuously creating development builds that are ready to deploy. This is the essence of CI/CD.

A pipeline can be automated to run based on a variety of triggers including periodically, based on GitHub repository changes, or from a script run remotely. However, in this part you will script a pipeline in Jenkins to run your two apps whenever you click the pipeline Build Now button.

Step 1: Create a Pipeline jobπŸ”—

  1. Click the Jenkins link in the top left, and then click New Item.

  2. In the Enter an item name field, type SamplePipeline.

  3. Select Pipeline as the job type.

  4. Scroll to the bottom and click OK.

    Screenshot showing the Jenkins New Item form for SamplePipeline

Step 2: Configure the SamplePipeline jobπŸ”—

  1. Investigate the Triggers section of the configuration page. Notice that there are a number of different ways to trigger a build. For the SamplePipeline job, you will trigger it manually (don't select any options).

  2. In the Pipeline section, add the following script.

    node {
        stage('Preparation') {
            catchError(buildResult: 'SUCCESS') {
                sh 'docker stop samplerunning'
                sh 'docker rm samplerunning'
            }
        }
        stage('Build') {
            build 'BuildAppJob'
        }
        stage('Results') {
            build 'TestAppJob'
        }
    }
    

    This script does the following:

    • It creates a single node build as opposed to a distributed or multi-node. Distributed or multi-node configurations are for larger pipelines than the one you are building in this lab and are beyond the scope of this course.

    • In the Preparation stage, the SamplePipeline will first make sure that any previous instances of the samplerunning docker container are stopped and removed. But if there is not yet a running container then these two commands would give you errors. Therefore, you use the catchError function to catch any errors and return a "SUCCESS" value to progress the script instead. This will ensure the pipeline continues to the next stage in the event that the samplerunning container is not already created.

    • In the Build stage, the SamplePipeline will build your BuildAppJob.

    • In the Results stage, the SamplePipeline will build your TestAppJob.

    Note

    This pipeline script follows the conventions of the Apache Groovy programming language. You can learn more about this syntax here: https://groovy-lang.org/syntax.html

  3. Click Save and you will be returned to the Jenkins dashboard for the SamplePipeline job.

Step 3: Run the SamplePipelineπŸ”—

  1. On the left, click Build Now to run the SamplePipeline job. Wait for the pipeline to complete. If you coded your Pipeline script without error, you should eventually see a green checkmark next to the build. If not, click Configure on the left to return to the SamplePipeline configuration and check your Pipeline script.

    A screenshot showing the successful build of the SamplePipeline

Step 4: Verify the SamplePipeline outputπŸ”—

  1. Click the SamplePipeline link at the top of the page, then click the latest build link under Permalinks.

  2. Click Console Output. You should see output similar to the following:

    Started by user admin
    [Pipeline] Start of Pipeline
    [Pipeline] node
    Running on Jenkins in /var/jenkins_home/workspace/SamplePipeline
    [Pipeline] {
    [Pipeline] stage
    [Pipeline] { (Preparation)
    [Pipeline] catchError
    [Pipeline] {
    [Pipeline] sh
    + docker stop samplerunning
    samplerunning
    [Pipeline] sh
    + docker rm samplerunning
    samplerunning
    [Pipeline] }
    [Pipeline] // catchError
    [Pipeline] }
    [Pipeline] // stage
    [Pipeline] stage
    [Pipeline] { (Build)
    [Pipeline] build (Building BuildAppJob)
    Scheduling project: BuildAppJob
    Starting building: BuildAppJob #5
    Build BuildAppJob #5 completed: SUCCESS
    [Pipeline] }
    [Pipeline] // stage
    [Pipeline] stage
    [Pipeline] { (Results)
    [Pipeline] build (Building TestAppJob)
    Scheduling project: TestAppJob
    Starting building: TestAppJob #2
    Build TestAppJob #2 completed: SUCCESS
    [Pipeline] }
    [Pipeline] // stage
    [Pipeline] }
    [Pipeline] // node
    [Pipeline] End of Pipeline
    Finished: SUCCESS
    

    Note

    You could also configure Jenkins with GitHub integration so that a pipeline can be triggered any time commits are pushed to GitHub. You can't set this up during this lab because you don't have access to the administrator settings of your repository.

Step 5: Save the Console OutputπŸ”—

In this step you will save a copy of the console output as a text file to be committed to your Git repository for grading.

  1. On the right side of the console window, click View as plain text.

  2. Right-click in the new page that loads and choose Save as…

  3. In the Save File dialog box that opens, navigate to /home/devasc/labs/devnet-src/sample-app if you are not already there.

  4. In the Name field at the top of the dialog box, name the file Pipeline.txt (CASE SENSITIVE!) then click Save

    Note

    The file name is different from the previous time you saved the output.

    A screenshot of the Save File dialog box for the file Pipeline.txt

Step 6: Stop the Docker containersπŸ”—

  1. Close the web browser and return to the terminal window where Jenkins is running. Press Ctrl+C to stop the running server.

  2. Use docker ps -a to see all running containers. If the samplerunning container is still running (STATUS is Up), use docker stop samplerunning to stop the container.

    devasc@labvm:~/labs/devnet-src/sample-app$ docker ps -a
    CONTAINER ID   IMAGE       COMMAND                  CREATED         STATUS         PORTS                                       NAMES
    f057e09e8e3a   sampleapp   "/bin/sh -c 'python3…"   7 minutes ago   Up 7 minutes   0.0.0.0:5050->5050/tcp, :::5050->5050/tcp   samplerunning
    devasc@labvm:~/labs/devnet-src/sample-app$ docker stop samplerunning
    samplerunning
    devasc@labvm:~/labs/devnet-src/sample-app$
    

Step 7: Push your changes to GitHubπŸ”—

  1. Now you are ready to commit your final changes to your Git repository. Enter the following commands to add your files to the staging area and verify they are ready to commit (make sure you see the three console outputs you saved earlier):

    devasc@labvm:~/labs/devnet-src/sample-app$ git add .
    devasc@labvm:~/labs/devnet-src/sample-app$ git status
    On branch main
    Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
            new file: BuildAppJob.txt
            new file: Pipeline.txt
            new file: TestAppJob.txt
    
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  2. Commit the changes to the local repository using the message Lab Complete!.

    devasc@labvm:~/labs/devnet-src/sample-app$ git commit -m "Lab Complete!"
    [main c041ac5] Lab Complete!
     3 files changed, 111 insertions(+)
     create mode 100644 BuildAppJob.txt
     create mode 100644 Pipeline.txt
     create mode 100644 TestAppJob.txt
    
  3. Push all the changes you made in the local repository back to the GitHub repository to complete the lab. Remember to use your Personal Access Token when asked for your GitHub password.

    Tip

    If you’ve configured your VM to use SSH to connect to GitHub instead of HTTPS, you will not be asked for a username and password, although you may be asked for a passphrase for your SSH key, if you configured one.

    devasc@labvm:~/labs/devnet-src/sample-app$ git push origin main
    Username for 'https://github.com': username
    Password for 'https://username@github.com': Personal Access Token
    Enumerating objects: 6, done.
    Counting objects: 100% (6/6), done.
    Delta compression using up to 2 threads
    Compressing objects: 100% (5/5), done.
    Writing objects: 100% (5/5), 2.35 KiB | 2.35 MiB/s, done.
    Total 5 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
    remote: Resolving deltas: 100% (1/1), completed with 1 local object.
    To github.com:Ontario-Tech-NITS/lab-5-application-deployment-<username>.git
       4184ce6..c041ac5  main -> main
    devasc@labvm:~/labs/devnet-src/sample-app$
    
  4. In your browser, return to the repository on github.com and verify all your changes are present, along with the corresponding commit messages. Wait a minute or so for the autograder tests to complete and ensure that it says passing in green.

    Note

    Since the autograder has to build your Docker containers, it will take longer than usual to complete the grading. Please refresh the page until it either passes or fails.

    Screenshot of successful autograder tests