DevEnv Workshop - Lab 02¶
You are now going to practice using some of the most common docker commands to perform operations on a docker host - that is, a VM that can build and run containers.
Tools and applications are nowadays also distributed as containers, making them more portable and reducing the time it takes to get them running. The goal of this task is for you to become comfortable at consuming such applications and, later on, even build a container of your own.
Task 1 - Using Docker Commands¶
Step 1¶
In a terminal on the lab VM, confirm your docker version and test that the daemon is running. The docker command connects to the local docker daemon running on the lab VM.
ntc devenv-01 ~/lab $ docker -v
Docker version 19.03.12, build 48a66213fe
ntc devenv-01 ~/lab $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
The
docker pscommand is the short version of thedocker container lscommand and shows a list of all the running (and optionally stopped) containers.
Step 2¶
Run the command docker run ubuntu echo Hello World!. It will download and run the command echo Hello World! inside of a container image called ubuntu.
ntc devenv-01 ~/lab $ docker run ubuntu echo Hello World!
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
3ff22d22a855: Pull complete
e7cb79d19722: Pull complete
323d0d660b6a: Pull complete
b7f616834fd0: Pull complete
Digest: sha256:5d1d5407f353843ecf8b16524bc5565aa332e9e6a1297c73a92d3e754b8a636d
Status: Downloaded newer image for ubuntu:latest
Hello World!
By default, if you don't explicitly specify a registry address, the docker command will connect to
https://hub.docker.com/, a public repository of containers. In the example above, it downloads this container.
Step 3¶
The ubuntu image is now stored locally in docker's image repository on disk. Use the docker image ls command to view its contents.
ntc devenv-01 ~/lab $ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 1e4467b07108 13 days ago 73.9MB
You can see container image names, sizes, and their tag (or version).
Step 4¶
Containers can contain any programs, be it short-lived shell commands or long-lived services. You ran an echo command in the bash shell, so after it printed the message the container finished its execution. Run docker ps -a to view a list of all (running and stopped) containers.
ntc devenv-01 ~/lab $ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
73cd9fe02d18 ubuntu "echo Hello World!" 6 minutes ago Exited (0) 6 minutes ago stupefied_gagarin
docker psonly shows running containers by default, of which now you have none. Try it!
Step 5¶
Containers that finished their execution are still there, as you may want to inspect them, do operations with their filesystems, or restart them. They take up space on disk though, so if you are sure you don't need them any more, you can use the docker container prune command to clean up.
ntc devenv-01 ~/lab $ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
73cd9fe02d187da994310f933fc2d3c99b572cca685f148ff79dc6c7f421b5cc
Total reclaimed space: 0B
ntc devenv-01 ~/lab $ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Step 6¶
When you want to run multiple commands in a container, it is common to start a shell (similar to how you start in a terminal on a remote host). In order to interact with this shell from the command line, you need to tell docker to start an interactive terminal -it. Optionally, you can provide the --rm flag to tell docker to delete (or prune) the container once it finishes running.
Run the docker run -it --rm ubuntu bash command. When you are done with the shell, type exit to return to the docker host.
ntc devenv-01 ~/lab $ docker run -it --rm ubuntu bash
root@0a83b24e5ec4:/#
root@0a83b24e5ec4:/#
root@0a83b24e5ec4:/# echo Hello World!
Hello World!
root@0a83b24e5ec4:/# exit
exit
ntc devenv-01 ~/lab $
Step 7¶
If you want the container to keep running in the background, you can provide the -d or detach flag. Optionally, you can give your running container a name (it must be unique!).
Run the docker run -itd --name test ubuntu bash command now.
ntc devenv-01 ~/lab $ docker run -itd --name test ubuntu bash
cd68687c0816b9fb2eb07f27d2351c60e4bc8e9caaeb2cc6531a984b43c64f84
ntc devenv-01 ~/lab $
The result is a unique id of the running container and the docker command returns you to your shell, while the container is still executing in the background. This is commonly done with containers that provide services which are not interactive.
Step 8¶
Run the docker ps command. You will now see your test container in the running state.
ntc devenv-01 ~/lab $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cd68687c0816 ubuntu "bash" 2 seconds ago Up 1 second test
Step 9¶
To execute commands in an already running container, use the docker exec command. You may run one-shot commands or interactive (-it) sessions as you can see below for bash.
ntc devenv-01 ~/lab $ docker exec test echo Hello again!
Hello again!
ntc devenv-01 ~/lab $ docker exec -it test bash
root@cd68687c0816:/# #This is a new shell
root@cd68687c0816:/# ps a
PID TTY STAT TIME COMMAND
1 pts/0 Ss+ 0:00 bash
63 pts/1 Ss 0:00 bash
71 pts/1 R+ 0:00 ps a
root@cd68687c0816:/# exit
exit
ntc devenv-01 ~/lab $
In this case you know the name of your container, but if you didn't, you can find out the name or the ID from the
docker psoutput.The very first command you start a container with will always be PID 1. This is the first process and, when it ends, the container execution ends as well. Your second bash shell has a PID of 63 and will stop when you type
exit
Step 10¶
You can copy files from and to a container using the docker cp command. Copy the webserver/index.html file into the test container and then check it is there.
ntc devenv-01 ~/lab $ docker cp webserver/index.html test:/index.html
ntc devenv-01 ~/lab $ docker exec test cat /index.html
<h1>Hello there!</h1>
ntc devenv-01 ~/lab $
Step 11¶
If you want to stop a running container, you may use the docker stop command. Run the commands as shown below.
ntc devenv-01 ~/lab $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cd68687c0816 ubuntu "bash" 21 minutes ago Up 21 minutes test
ntc devenv-01 ~/lab $ docker stop test
test
ntc devenv-01 ~/lab $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Step 12¶
You can start a stopped container again. Run the docker start test command, then look at the output of docker ps.
ntc devenv-01 ~/lab $ docker start test
test
ntc devenv-01 ~/lab $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cd68687c0816 ubuntu "bash" 23 minutes ago Up 1 second test
Docker still has the full configuration and state of the container as it was not deleted.
Step 13¶
Since this is the same container as earlier, try to get the contents of the /index.html file.
Step 14¶
Now stop and delete your test container by running the commands shown below.
tc devenv-01 ~/lab $ docker stop test
test
ntc devenv-01 ~/lab $ docker rm test
test
ntc devenv-01 ~/lab $ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Your container named
testis now gone forever. Any changes from theubuntuimage, such as copied files, are also lost. Containers as ephemeral by design and you need explicit actions to preserve state.
Step 15¶
Start a new ubuntu container named test and check its filesystem. As expected, the /index.html is not there anymore, since you started the container from the base unmodified image.
ntc devenv-01 ~/lab $ docker run -it --rm ubuntu bash
root@98a1f4a0b5d7:/# cat /index.html
cat: /index.html: No such file or directory
root@98a1f4a0b5d7:/# exit
exit
ntc devenv-01 ~/lab $
Task 2 - Containerize a Python Web Server¶
One common usage for docker containers is to package applications for distribution and portability. Here, you will use a different container image called python to run your simple webserver from within a container and serve the index.html page.
Step 1¶
Retrieve version (or tag) 3.8-slim of the python container image using the docker pull python:3.8-slim command.
ntc devenv-01 ~/lab $ docker pull python:3.8-slim
3.8-slim: Pulling from library/python
bf5952930446: Pull complete
385bb58d08e6: Pull complete
ab14b629693d: Pull complete
7a5d07f2fd13: Pull complete
56745e40505a: Pull complete
Digest: sha256:f7edd1bb431a224e7f4f3e23cbb22738e82f4895a6d28f86294ce006177360c3
Status: Downloaded newer image for python:3.8-slim
docker.io/library/python:3.8-slim
Step 2¶
Test start a new container using this image as show below. Notice you get dropped into a python 3.8 interpreter by default.
ntc devenv-01 ~/lab $ docker run -it python:3.8-slim
Python 3.8.5 (default, Aug 4 2020, 16:24:08)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> exit()
ntc devenv-01 ~/lab $
Step 3¶
Change to the /home/ntc/lab folder. Check that you have the index.html file in the /home/ntc/lab/webserver folder.
ntc devenv-01 ~/lab $ cd /home/ntc/lab/
ntc devenv-01 ~/lab $ cat webserver/index.html
<h1>Hello there!</h1>
Step 4¶
Run the following command to start a containerized python webserver and then confirm it is running.
ntc devenv-01 ~/lab $ docker run -d --rm --name pyweb -v $PWD/webserver:/webserver -p8888:8000 python:3.8-slim python3 -m http.server --directory /webserver/
53f1bc49d06df7f0bedbd08e49ff348dd32f93ea01dde87046bff4797f337b8b
ntc devenv-01 ~/lab $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
53f1bc49d06d python:3.8-slim "python3 -m http.ser…" 3 seconds ago Up 1 second 0.0.0.0:8888->8000/tcp pyweb
Let's disect the command:
-dstart container in detached mode (run it as a service)--rmdelete the container once it stops--name pywebgive it the namepyweb-v $PWD/webserver:/webservermount the localwebserverfolder contents in the container at/webserver-p8888:8000create port forwarding to publish the container internal port 8000 to the docker host's port 8888python:3.8-slimthe container imagepython3 -m http.server --directory /webserver/the command to start a python web server
In your web browser of choice, open the following website: http://YOUR_VM_IP:8888 replacing your VM's IP as appropriate. You should now see a "Hello there!" message on the page.
Step 5¶
Open the /home/ntc/lab/webserver/index.html file in your editor and change the message to "Hello from Docker!". Save the file and refresh your web browser http://YOUR_VM_IP:8888 page.
Since this file is mounted into the container, the change will be instantly reflected and you should see the new message displayed.
Congratulations, you have containerized your first application!
Task 3 - Create a Network Development Container Image¶
You will now get started on the path of creating your own container images pre-loaded with libraried and tools that you often use. Here, you will build upon the python docker container image to create a netdev container that has ansible and a few other network automation libraries (netmiko, napalm, textfsm) installed.
Step 1¶
Create the /home/ntc/lab/netdev folder and change into it.
ntc devenv-01 ~/lab $ mkdir -p /home/ntc/lab/netdev
ntc devenv-01 ~/lab $ cd /home/ntc/lab/netdev
ntc devenv-01 ~/lab/netdev $
Step 2¶
Create a new file in the netdev folder named Dockerfile. It should have the following contents:
ntc devenv-01 ~/lab/netdev $ cat Dockerfile
FROM python:3.8-slim
LABEL maintainer="Yours Truly" description="Python based tooling" version="0.1"
RUN pip3 install --no-cache-dir ansible netmiko napalm textfsm
CMD ["/bin/bash"]
Step 3¶
Run the command docker build -t netdev:0.1 . to build a new container image named netdev and tag it with version 0.1. Mind the dot at the end of the command!
You will now see the build process that executes the commands as specified in the Dockerfile and produces a container image at the end if successful.
ntc devenv-01 ~/lab/netdev $ docker build -t netdev:0.1 .
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM python:3.8-slim
---> 07ea617545cd
Step 2/4 : LABEL maintainer="Yours Truly" description="Python based tooling" version="0.1"
---> Running in 99a9ca79ed31
Removing intermediate container 99a9ca79ed31
---> 1d0a17bf2d07
Step 3/4 : RUN pip3 install --no-cache-dir ansible netmiko napalm textfsm
---> Running in dcecc818fb84
Collecting ansible
Downloading ansible-2.9.11.tar.gz (14.2 MB)
Collecting netmiko
Downloading netmiko-3.2.0-py2.py3-none-any.whl (157 kB)
Collecting napalm
Downloading napalm-3.1.0-py2.py3-none-any.whl (229 kB)
Collecting textfsm
Downloading textfsm-1.1.0-py2.py3-none-any.whl (37 kB)
Collecting jinja2
Downloading Jinja2-2.11.2-py2.py3-none-any.whl (125 kB)
Collecting PyYAML
Downloading PyYAML-5.3.1.tar.gz (269 kB)
Collecting cryptography
Downloading cryptography-3.0-cp35-abi3-manylinux2010_x86_64.whl (2.7 MB)
Requirement already satisfied: setuptools>=38.4.0 in /usr/local/lib/python3.8/site-packages (from netmiko) (49.2.1)
Collecting scp>=0.13.2
Downloading scp-0.13.2-py2.py3-none-any.whl (9.5 kB)
Collecting paramiko>=2.4.3
Downloading paramiko-2.7.1-py2.py3-none-any.whl (206 kB)
Collecting pyserial
Downloading pyserial-3.4-py2.py3-none-any.whl (193 kB)
Collecting junos-eznc>=2.2.1
Downloading junos-eznc-2.5.1.tar.gz (154 kB)
Collecting future
Downloading future-0.18.2.tar.gz (829 kB)
Collecting cffi>=1.11.3
Downloading cffi-1.14.1-cp38-cp38-manylinux1_x86_64.whl (409 kB)
Collecting ciscoconfparse
Downloading ciscoconfparse-1.5.19-py3-none-any.whl (93 kB)
Collecting netaddr
Downloading netaddr-0.8.0-py2.py3-none-any.whl (1.9 MB)
Collecting pyeapi>=0.8.2
Downloading pyeapi-0.8.3.tar.gz (137 kB)
Collecting lxml>=4.3.0
Downloading lxml-4.5.2-cp38-cp38-manylinux1_x86_64.whl (5.4 MB)
Collecting requests>=2.7.0
Downloading requests-2.24.0-py2.py3-none-any.whl (61 kB)
Collecting six
Downloading six-1.15.0-py2.py3-none-any.whl (10 kB)
Collecting MarkupSafe>=0.23
Downloading MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl (32 kB)
Collecting pynacl>=1.0.1
Downloading PyNaCl-1.4.0-cp35-abi3-manylinux1_x86_64.whl (961 kB)
Collecting bcrypt>=3.1.3
Downloading bcrypt-3.1.7-cp34-abi3-manylinux1_x86_64.whl (56 kB)
Collecting ncclient>=0.6.3
Downloading ncclient-0.6.8.tar.gz (117 kB)
Collecting yamlordereddictloader
Downloading yamlordereddictloader-0.4.0.tar.gz (3.3 kB)
Collecting pyparsing
Downloading pyparsing-2.4.7-py2.py3-none-any.whl (67 kB)
Collecting transitions
Downloading transitions-0.8.2-py2.py3-none-any.whl (76 kB)
Collecting ntc_templates
Downloading ntc_templates-1.5.0-py3-none-any.whl (254 kB)
Collecting pycparser
Downloading pycparser-2.20-py2.py3-none-any.whl (112 kB)
Collecting colorama
Downloading colorama-0.4.3-py2.py3-none-any.whl (15 kB)
Collecting dnspython
Downloading dnspython-2.0.0-py3-none-any.whl (208 kB)
Collecting passlib
Downloading passlib-1.7.2-py2.py3-none-any.whl (507 kB)
Collecting chardet<4,>=3.0.2
Downloading chardet-3.0.4-py2.py3-none-any.whl (133 kB)
Collecting idna<3,>=2.5
Downloading idna-2.10-py2.py3-none-any.whl (58 kB)
Collecting certifi>=2017.4.17
Downloading certifi-2020.6.20-py2.py3-none-any.whl (156 kB)
Collecting urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1
Downloading urllib3-1.25.10-py2.py3-none-any.whl (127 kB)
Building wheels for collected packages: ansible, PyYAML, junos-eznc, future, pyeapi, ncclient, yamlordereddictloader
Building wheel for ansible (setup.py): started
Building wheel for ansible (setup.py): finished with status 'done'
Created wheel for ansible: filename=ansible-2.9.11-py3-none-any.whl size=16176769 sha256=31b32a79e61708e9eefa87072490cf73e4f6dd91ff71e340576c3903d9aa1f60
Stored in directory: /tmp/pip-ephem-wheel-cache-docsw7ec/wheels/96/b5/fc/646cc0302950f9dd85ce04f1108809447c7c1c20ebf23f587b
Building wheel for PyYAML (setup.py): started
Building wheel for PyYAML (setup.py): finished with status 'done'
Created wheel for PyYAML: filename=PyYAML-5.3.1-cp38-cp38-linux_x86_64.whl size=44617 sha256=4481668e74b87ce7437c0bab4445e1542b5604c96ec697b49150cc55ececdcb2
Stored in directory: /tmp/pip-ephem-wheel-cache-docsw7ec/wheels/13/90/db/290ab3a34f2ef0b5a0f89235dc2d40fea83e77de84ed2dc05c
Building wheel for junos-eznc (setup.py): started
Building wheel for junos-eznc (setup.py): finished with status 'done'
Created wheel for junos-eznc: filename=junos_eznc-2.5.1-py3-none-any.whl size=190451 sha256=ac4e0a41ee9a9999dd92f240dbd6175c87e2b99517c1181dcb48044371afe99f
Stored in directory: /tmp/pip-ephem-wheel-cache-docsw7ec/wheels/f0/4d/a0/fb9cbe8e782460ad05434b83389823c990f0ff12758700d44e
Building wheel for future (setup.py): started
Building wheel for future (setup.py): finished with status 'done'
Created wheel for future: filename=future-0.18.2-py3-none-any.whl size=491058 sha256=5d78a73e00494f3141a94fa759b509b80f3d7ca911c9d6257be94804d42b247a
Stored in directory: /tmp/pip-ephem-wheel-cache-docsw7ec/wheels/8e/70/28/3d6ccd6e315f65f245da085482a2e1c7d14b90b30f239e2cf4
Building wheel for pyeapi (setup.py): started
Building wheel for pyeapi (setup.py): finished with status 'done'
Created wheel for pyeapi: filename=pyeapi-0.8.3-py3-none-any.whl size=90262 sha256=8ef9fdb2e2f203eb897faf88a101e510509ec3ae799c9e394b1636ffb565fce9
Stored in directory: /tmp/pip-ephem-wheel-cache-docsw7ec/wheels/d1/56/be/60a2a048f7510cc33f862dafa06471e928494ebdf0a0558ec0
Building wheel for ncclient (setup.py): started
Building wheel for ncclient (setup.py): finished with status 'done'
Created wheel for ncclient: filename=ncclient-0.6.8-py2.py3-none-any.whl size=103305 sha256=084051777bba962dc3c5393575964dc667bdf7d7eab4d3df91aaad43dbabefa3
Stored in directory: /tmp/pip-ephem-wheel-cache-docsw7ec/wheels/02/03/15/2fc281ba0891ebe5f1632a0bdfc6c96e6b023baf5270e715e8
Building wheel for yamlordereddictloader (setup.py): started
Building wheel for yamlordereddictloader (setup.py): finished with status 'done'
Created wheel for yamlordereddictloader: filename=yamlordereddictloader-0.4.0-py3-none-any.whl size=4052 sha256=51d614302245f4b6b2d90089d13de3fe6e8bd6d9072796f461180098056a07bd
Stored in directory: /tmp/pip-ephem-wheel-cache-docsw7ec/wheels/50/9a/6f/9cb3312fd9cd01ea93c3fdc1dbee95f5fa0133125d4c7cb09a
Successfully built ansible PyYAML junos-eznc future pyeapi ncclient yamlordereddictloader
Installing collected packages: MarkupSafe, jinja2, PyYAML, pycparser, cffi, six, cryptography, ansible, future, textfsm, pynacl, bcrypt, paramiko, scp, pyserial, netmiko, lxml, ncclient, netaddr, yamlordereddictloader, pyparsing, transitions, ntc-templates, junos-eznc, colorama, dnspython, passlib, ciscoconfparse, pyeapi, chardet, idna, certifi, urllib3, requests, napalm
Successfully installed MarkupSafe-1.1.1 PyYAML-5.3.1 ansible-2.9.11 bcrypt-3.1.7 certifi-2020.6.20 cffi-1.14.1 chardet-3.0.4 ciscoconfparse-1.5.19 colorama-0.4.3 cryptography-3.0 dnspython-2.0.0 future-0.18.2 idna-2.10 jinja2-2.11.2 junos-eznc-2.5.1 lxml-4.5.2 napalm-3.1.0 ncclient-0.6.8 netaddr-0.8.0 netmiko-3.2.0 ntc-templates-1.5.0 paramiko-2.7.1 passlib-1.7.2 pycparser-2.20 pyeapi-0.8.3 pynacl-1.4.0 pyparsing-2.4.7 pyserial-3.4 requests-2.24.0 scp-0.13.2 six-1.15.0 textfsm-1.1.0 transitions-0.8.2 urllib3-1.25.10 yamlordereddictloader-0.4.0
Removing intermediate container dcecc818fb84
---> c691651da2be
Step 4/4 : CMD ["/bin/bash"]
---> Running in 8cf9db0ab6db
Removing intermediate container 8cf9db0ab6db
---> 83e955040d82
Successfully built 83e955040d82
Successfully tagged netdev:0.1
Step 4¶
Check the list of docker images using docker image ls. Notice you now have a netdev image.
ntc devenv-01 ~/lab/netdev $ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
netdev 0.1 83e955040d82 About a minute ago 281MB
python 3.8-slim 07ea617545cd 2 days ago 113MB
ubuntu latest 1e4467b07108 13 days ago 73.9MB
Step 5¶
Create a new container from the freshly baked netdev:0.1 image and try running ansible inside of it!
ntc devenv-01 ~/lab/netdev $ docker run -it --rm netdev:0.1
root@1d0325082173:/# ansible --version
ansible 2.9.11
config file = None
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/lib/python3.8/site-packages/ansible
executable location = /usr/local/bin/ansible
python version = 3.8.5 (default, Aug 4 2020, 16:24:08) [GCC 8.3.0]
Congratulations, your new network development tools container is functional!
In the future you may want to share this container image with your colleagues. You may share the Dockerfile so they can build their own from it or push the image to a Docker registry.