An attempt to bring together various components including Jenkins for Continuous Integration (CI), Python for development, and Docker containers for consistency/quality/speed. The post will incrementally demonstrate how to configure a Jenkins pipeline to test a Docker container which contains Python code and associated automated tests. Note that this tutorial is a culmination of several “best practice” tutorials by the specific vendors of the technologies within.
Technology Ecosystem
The steps in this post are executed on a VM running the Ubuntu operating system (specifically, Ubuntu 16.04, 64-bit). Details related to the host machine are as follows:
Development Host
- Hostname: devbox.localhost
- OS: Ubuntu 16.04
- CPU: 2
- RAM: 4096 MB
- Network: Private Network
- IP: 10.11.13.15
Install Docker
As a first step, let’s install Docker on the Linux VM that will be hosting the full suite of applications:
At this point, Docker is installed and the current user (vagrant
if you’re using a Vagrant
VM) is part of the docker group. However, you must log out/log back into the VM before you
are able to run commands.
Create a “Hello World” Python Application
Now, let’s create a simple Python web application using Python Flask. Create a directory
named /opt/hello_world
and use virtualenv
to create an isolated Python working
environment (you may need to install virtualenv using pip
):
Next, add a few files/install Flask:
Create Dockerfile
Now that we have Docker installed and a Python application, let’s create the Dockerfile to build the Docker container which will host the application:
Create Docker Container and Test
Finally, now that we have the Flask Python files and corresponding Dockerfile, we can create the Docker image and from it, launch a container with the application:
If all goes well, you can now access the application using a browser at the following URL (assuming the IP address of your VM matches the above configuration):
http://10.11.13.15:4040/
You should see a web page displayed with “Hello World!” and a Hostname. If you wanted to run the container in the background (detached), you could use the following commands to launch/kill the application:
Adding Python Tests
Now we’ll add the pytest framework and add a simple unit test to the application.
To run the unit tests, run the following command:
Installing Jenkins
Now that we have Docker installed and a demo/test Python Flask application with a basic unit test, we will install and configure Jenkins so we can develop a CI/CD pipeline for the code base.
Access the Jenkins site via a browser at the following URL (again, assuming your IP address matches that of the IP of the test VM used above):
http://10.11.13.15:8080/
Follow the steps in the browser to initialize the Jenkins instance and configure the administrative user for the application.
Creating a Jenkins CI Job
We can now create a Jenkins CI job for the Jenkins application to run. The following steps will be performed within the same browser session as previously logged into.
First, create a “New Job”. Use the name “Hello World App” and select “Pipeline” as the project type.
For the new project being created, enter the following details:
- Build Triggers: Specify “Poll SCM” as the option, and enter the schedule
* * * * *
to have Jenkins poll the file system for changes every minute (configure this to your liking). - Pipeline: Add the following code, which triggers the various steps in the pipeline:
As a note, before you run a build, you will likely need to remove the __pycache__
directory from
the hello_world
directory to prevent access permission issues. Ideally, this would be handled
differently (and as stated previously, even more ideally, using a real git endpoint is desired over
a local file system approach), but for the purposes of expedience for this tutorial, the immediate
recommendation is to remove all directories using a command like the following when in the
hello_world
directory:
find . -name __pycache__ -type d -exec rm -rf {} \;
Running the CI Job and Inspecting Docker
Now that we have our Jenkins project set up, we can trigger a build and inspect the output. Within the Jenkins UI, navigate to your project and trigger a build by selecting the “Build Now” option. You should see all 3 steps for the build executed/green. If you see any red/errors, select the Build and inspect the “Console Output” to see what went wrong.
Once the build succeeds, you can inspect the output by running the docker image commands on the command line:
Note that in the above output, the image name includes the build number - you could also get fancier and include a date/timestamp for builds, or something different altogether. In order to fully close the loop on this testing, we should spin up a Docker container using the above built image and test that it works as we expect:
Once you run the above, you should be able to navigate to the URL previously visited to see the “Hello World!” message as previously seen:
http://10.11.13.15:4040
Congratulations, you’ve successfully built your CI/CD pipeline!
Room for Improvement
There are obviously MANY improvements to make to the above. Again, this is a basics tutorial that
is not DRY in code (Jenkins pipeline script repeating variables), not optimized for usability
(__pycache__
directory issue), and in general lacks the finesse that one should expect for a
production-grade implementation. Please use the above to build your basic understanding of how Docker,
Python, and Jenkins CI all integrate for a CI pipeline to produce Docker-based Python applications.
Credit
The above tutorial was pieced together with some information from the following sites/resources: