Introduction

As the saying goes, "you are only as good as your tools", and this is especially true when it comes to software development and network automation.

Within this article will provide a beginners guide to 2 tools that should be in any network automators toolbox - Docker and Visual Studio Code.

  • Docker - A container platform that can be used to create lightweight isolated environments, allowing you to easily separate dependencies between projects.
  • Visual Studio Code (VS Code) - An extremely popular free open source IDE, that can reduce development time, due to inbuilt syntax checking, formatting and debugging features.

home-screenshot-win
Figure 1 - VS Code.[1]

Containers/Docker

Containerised applications are becoming more popular for a number of reasons like application packaging, standardisation, lightweight portability, and ease of use for testing/development.

Unlike a VM (virtual machine), which is a virtualized instance of an OS, a container is a logically isolated slice of an operating system, i.e. unlike a VM each container shares a kernel with other containers and other system libraries. This makes containers extremely lightweight and fast to run.

container_vs_vm
Figure 2 - Container vs VM Overview. [2]

There are multiple container platforms available, however in this article we will focus on Docker due to its simplicity and popularity.

There are 2 main components that you must first understand when it comes to Docker - images and containers. Docker images can be considered as nothing more than a template and Docker containers are the running instances created from those templates.[3]

For the purpose of this tutorial I suggest the usage and installation of Docker Desktop for Mac and Windows. To follow along with the examples clone the git repository like so:

$ git clone https://github.com/rickdonato/python-dev-with-docker-and-vscode

Within this repo a simple CLI python script is included that will generate some interface configuration. The other files within this repo will all become clear as we go through the various examples.

Dockerfile

An image is built when instructions defined in a Dockerfile are executed by docker.

This file is usually located under the same directory of your project, in this case under the python-development-with-docker-and-vscode directory. For this example, I have named it Docker.standalone

FROM python:3.8-rc-slim-stretch

LABEL version="1.0.0"
LABEL description="This an example python-based container"
LABEL maintainer="David Flores <@netpanda>"

WORKDIR /app

COPY . /app

RUN pip install -r requirements.txt

CMD ["/bin/bash"]

Let's dissect what these instructions mean:

  • FROM python:3.8-rc-slim-stretch: Specifies the parent image to be used to build this container. This is a python-slim and lightweight image downloaded from Docker Hub. If you want to know how the python image is built and how to use it see their documentation.
  • LABEL key=value: The 3 lines are specifying key value pairs of information used for the container metadata.
  • WORKDIR /app: The instruction sets the working directory for any RUN, CMD, ENTRYPOINT and ADD instructions.
  • COPY . /app: This instruction copies new files from &lt;src> and adds them to the filesystem of the container at the path &lt;dest>. In this case it will copy the python code, the data variable files and the templates.
  • RUN pip install -r requirements.txt: TheRUN instruction will execute any commands in a new layer on top of the current image and commit the results. The resulting committed image will be used for the next step in the Dockerfile. Since it will trigger a layering process you should use them wisely. In this case it will install the Python application requirements.
  • CMD [“/bin/bash”]: The main purpose of CMD is to define a command that is executed at runtime.

For more information on the available instructions take a look at Dockerfile Reference.

Dockerignore

Care must be taken on the COPY instruction, as you may not want to add ALL the files from the python application into the container. May due to the files not being required or due to security reasons. To exclude directories and files from being copied into your container create a file .dockerignore, defining which files and directories you want to exclude.

**/.git
**/.gitignore
**/.vscode
README.md

Docker Build

With the Dockefile created, an image now needs to be built. For that we use the docker build command.

Make sure you are at the same level as the Dockerfile location, although you can specify its location with command-line options.

$ docker build --tag intro-netautomation:standalone --file Dockerfile.standalone .
  • The --tag <name:tag> will specify the image name and optionally a tag.
  • The --file <dockerfile path> will specify the Dockerfile filename name.
  • The . at the end is to look for the Dockerfile in the current directory and its "context" (folders and files needed to build the image). Though we have manually specified the Dockerfile.standalone file, with the . we are also specifying the directory to copy the other project files and folders from.

The result will be a Docker container image named intro-netautomation with the tag standalone. To verify the images and general specifications you can run:

$ docker images
REPOSITORY                TAG                   IMAGE ID            CREATED             SIZE
intro-netautomation       standalone            1abe9d092bfe        10 minutes ago      159MB
...

Docker Run

Interactive Mode

Since this is an “interactive” container application, meaning that you need to connect to it to be able to run the CLI script, there are a couple of flags needed for the docker run command.

$ docker run --rm -it intro-netautomation:standalone

Let's dissect the flags and arguments passed:

  • The --rm option will automatically remove the container when it exits.
  • The -it flags are short for --interactive and --tty. So when then container is run it will take you straight inside of it .This is also known as interactive mode.
  • intro-netautomation:standalone - this states which image to create a running instance of.
  • The last argument is normally composed of the command and arguments to pass to the running container. In this case it is empty and thus using the default parameter defined in the CMD instruction, which is to run /bin/bash.

When executed it will start the container and present you within the bash shell. Since the python parent image is based on Debian you can run commands like pwd and ls.

Here is an example of docker run:

$ docker run --rm -it intro-netautomation:standalone
root@6924e6a68475:/app#
root@6924e6a68475:/app# ls -al
total 44
drwxr-xr-x 1 root root 4096 Feb  9 12:51 .
drwxr-xr-x 1 root root 4096 Feb  9 13:05 ..
drwxr-xr-x 2 root root 4096 Feb  9 12:37 .devcontainer
-rw-r--r-- 1 root root   49 Feb  9 12:37 .dockerignore
-rw-r--r-- 1 root root  173 Feb  9 12:37 Dockerfile
-rw-r--r-- 1 root root  262 Feb  9 12:46 Dockerfile.standalone
drwxr-xr-x 2 root root 4096 Feb  9 12:51 build
-rw-r--r-- 1 root root 1904 Feb  9 12:37 configurator.py
drwxr-xr-x 2 root root 4096 Feb  9 12:37 data
-rw-r--r-- 1 root root   14 Feb  9 12:37 requirements.txt
drwxr-xr-x 2 root root 4096 Feb  9 12:37 templates

You can see when running the ls -al no git files or folders are present, this is thanks to the .dockerignore file. What follows next is an example run of the CLI script.

root@6924e6a68475:/app# python configurator.py data/interfaces_vars.json  templates/cisco_interfaces.j2
Created data/conf.txt File! -->
!
interface Vlan177
  description Lan In-Band Network
  ip address 10.77.1.68 255.255.255.0
  load-interval 5
!
interface Management1
  description lab01 - Eth100/1/37
  ip address 10.17.17.177 255.255.255.0
  load-interval 5
  no shutdown
!
!
root@6924e6a68475:/app# exit
$

Detached Mode

Interactive Mode is useful for one-time applications or services that provide some sort of API or callback process to interact with, but that is not always the case.

In this example, since this is a CLI application, it will be best to have the container running in detached mode, i.e in the background. This is achieved by, again, running the docker run command with the parameters shown before, but with the addition of -d for detached. You can see more about this option within the following documentation.

$ docker run --name container01 -dit intro-netautomation:standalone
e53e4a8e582a753e71b33e7e1c1b7647a72f438260ba0a0cd72fdc3208900b21

This will create the container named container01 in detached mode. The output generated is its complete ID. You can see the container running and its general parameters with docker container ls:

$ docker container ls
CONTAINER ID        IMAGE                            COMMAND             CREATED             STATUS              PORTS               NAMES
e53e4a8e582a        intro-netautomation:standalone   "/bin/bash"         12 seconds ago      Up 11 seconds                           container01

Docker Exec

Docker exec. allows you to run a command in a running container. For example,

docker exec -it container01 /bin/bash, this instructs Docker to run (within interactive mode) the command /bin/bash on container01.

$ docker exec -it container01 /bin/bash
root@e53e4a8e582a:/app#
root@e53e4a8e582a:/app# exit
exit
$
$ docker container ls
CONTAINER ID        IMAGE                            COMMAND             CREATED             STATUS              PORTS               NAMES
e53e4a8e582a        intro-netautomation:standalone   "/bin/bash"         2 minutes ago       Up 2 minutes                            container01

This gives you the ability to have a running container and test it. With an editor like Visual Studio Code you can connect to the container and perform development tasks on it!. More on this later.

Docker Start/Stop

With this approach you can treat the container in a VM like manner. You can stop it and start it when you need it. This is done with the docker stop and docker start commands.

For example:

$ docker container ls -a
CONTAINER ID        IMAGE                            COMMAND             CREATED             STATUS              PORTS               NAMES
e53e4a8e582a        intro-netautomation:standalone   "/bin/bash"         4 minutes ago       Up 4 minutes                            container01
$ docker stop container01
container01
$ docker container ls -a
CONTAINER ID        IMAGE                            COMMAND             CREATED             STATUS                      PORTS               NAMES
e53e4a8e582a        intro-netautomation:standalone   "/bin/bash"         6 minutes ago       Exited (0) 15 seconds ago                       container01
$ docker start container01
container01
$ docker container ls -a
CONTAINER ID        IMAGE                            COMMAND             CREATED             STATUS              PORTS               NAMES
e53e4a8e582a        intro-netautomation:standalone   "/bin/bash"         6 minutes ago       Up 3 seconds                            container01

Is important to know that files added or modified inside the container are NOT GOING TO BE DESTROYED, you can test by adding a file on the container directory /app, stopping the container, and then starting it again. When you check the directory you will see the file still remains.

This makes the container a great lightweight jump server/bastion portable solution when using it with the likes of network simulation tools like GNS3 or EVE-NG.

You can find in this repository some dockerfiles with instructions to build a network-tool centric workstation : GitHub - davidban77/netautomator: Docker specs for a network automation focused workstation.

Docker Commit

The last feature/command I want to talk about in this tutorial is the docker commit command. It creates a new image from a container’s changes.

For example, let's say I already added a “test” file under the /app directory on container01 and want to create a new image named test.

$ docker commit -m "Adding a test file" container01 intro-netautomation:test
sha256:d9e4b1d7519922df673f9d11c672e36cf12fecf7e5aac2654000af25d165cf36
$
$ docker images
REPOSITORY                                                     TAG                   IMAGE ID            CREATED             SIZE
intro-netautomation                                            test                  38ab5295b57d        3 seconds ago       159MB
intro-netautomation                                            standalone            1abe9d092bfe        28 minutes ago      159MB

The new image is tagged intro-netautomation:test.

Let us create a new container from this image and check the contents of the /app directory.

docker run --name container02 -it intro-netautomation:test
root@624497b5980b:/app#
root@624497b5980b:/app# ls -al
total 44
drwxr-xr-x 1 root root 4096 Feb  9 13:22 .
drwxr-xr-x 1 root root 4096 Feb  9 13:23 ..
drwxr-xr-x 2 root root 4096 Feb  9 12:37 .devcontainer
-rw-r--r-- 1 root root   49 Feb  9 12:37 .dockerignore
-rw-r--r-- 1 root root  173 Feb  9 12:37 Dockerfile
-rw-r--r-- 1 root root  262 Feb  9 12:46 Dockerfile.standalone
drwxr-xr-x 2 root root 4096 Feb  9 12:51 build
-rw-r--r-- 1 root root 1904 Feb  9 12:37 configurator.py
drwxr-xr-x 2 root root 4096 Feb  9 12:37 data
-rw-r--r-- 1 root root   14 Feb  9 12:37 requirements.txt
drwxr-xr-x 2 root root 4096 Feb  9 12:37 templates
-rw-r--r-- 1 root root    0 Feb  9 13:22 test
root@624497b5980b:/app# exit

You can see the test file present at the end.

Coming Up

In the next part of this series we will dive into the integration of Docker with VS Code. See you there...

References


  1. "Visual Studio Code - Code Editing. Redefined." https://code.visualstudio.com/. Accessed 24 Feb. 2020. ↩︎

  2. "Container workflows at Pawsey: Introduction to Docker." https://pawseysc.github.io/container-workflows/01-docker-intro/index.html. Accessed 24 Feb. 2020. ↩︎

  3. "Docker Images vs Containers - All You Need To Know." 16 Oct. 2019, https://www.dockerjet.com/docker-images-vs-containers. Accessed 24 Feb. 2020. ↩︎