Lab 2 - Docker

Download as pptx, pdf, or txt
Download as pptx, pdf, or txt
You are on page 1of 61

Docker Container

What are containers?


• A standard way to package an application and
all its dependencies so that it can be moved
between environments and run without
change
• LXC (Linux Containers) is an 
operating-system-level virtualization method
for running multiple isolated Linux systems
(containers) on a control host using a single
Linux kernel.
• The Linux kernel provides the cgroups functionality that allows
limitation and prioritization of resources (CPU, memory, block I/O,
network, etc.) without the need for starting any virtual machines,
and also namespace isolation functionality that allows complete
isolation of an application's view of the operating environment,
including process trees, networking, user IDs and mounted 
file systems.
• LXC combines the kernel's cgroups and support for isolated 
namespaces to provide an isolated environment for applications.
• LXC provides operating system-level virtualization through a virtual
environment that has its own process and network space, instead
of creating a full-fledged virtual machine. LXC relies on the 
Linux kernel cgroups functionality that was released in version
2.6.24. It also relies on other kinds of namespace isolation
functionality, which were developed and integrated into the
mainline Linux kernel.
• Docker is the tooling used for creating Linux
containers. Docker provides developers and
operators with a friendly interface to build,
ship, and run containers on any environment.
• Docker is developed in the Go language and
utilizes LXC, cgroups, and the Linux kernel
itself.
Benefits of using containers
• Docker images can run on many different platforms.
• If you can run an application on a Linux kernel, then you can package it
in Docker, and then run it in different environments without having to
change it. 
• Containers keep the concerns of development and operations
separate. What happens inside the container doesn’t have much impact
on operations that run outside the container.
• So, applications can do their own thing, while the process of running the
container can be standardized.
• Containers are easy to manage – you can quickly create and delete,
download, share, start and stop them.
• Containers use hardware resources more efficiently than virtual
machines, and are more lightweight.
1. RUN YOUR FIRST CONTAINER

• Prerequisites
• You don’t need to know anything about containers to
take this course, and no extensive programming
experience is required. However, you must know how
to run command-line tools from your workstation.
• If you don’t have Docker already, you need install
Docker Community Edition (CE) or use the use the 
Play-with-Docker website. Docker installations are
available from store.docker.com for your Mac,
Windows, or Linux machine.
• Use the Docker CLI to run our first container.
1- Open a terminal on your local computer.
2- Run docker container run -t ubuntu top
– Use the docker container run command to run a
container with the ubuntu image, using
the top command. The -t flag allocates a pseudo-
TTY, which you need for the top to work correctly.
O/P –
• The docker run command will result first in a docker pull to
download the ubuntu image onto your host. Once it is
downloaded, it will start the container. The output for the
running container should look like this:-

• top is a Linux utility that prints the processes on a system


and orders them by resource consumption. Notice that
there is only a single process in this output: it is
the top process itself. We don’t see other processes from
our host in this list because of the PID namespace isolation.
• Containers use Linux namespaces to provide
isolation of system resources from other containers
or the host. The PID namespace provides isolation
for process IDs. If you run top while inside the
container, you will notice that it shows the processes
within the PID namespace of the container, which is
much different than what you can see if you
ran top on the host.
• Even though we are using the ubuntu image, it is
important to note that our container does not have
its own kernel. It uses the kernel of the host and
the ubuntu image is used only to provide the file
system and tools available on an ubuntu system.
3- Inspect the container with –
 docker container exec
The docker container exec command is a way to “enter” a
running container’s namespaces with a new process.
• Open a new terminal. To open a new terminal connected to
node1 using play-with-docker.com, click “Add New Instance”
on the left side, then ssh from node2 into node1 using the IP
that is listed by ‘node1 ‘. For example:-
• In the new terminal, use the docker container ls command to
get the ID of the running container you just created.

• Then use that id to run bash inside that container using


the docker container exec command. Since we are using bash
and want to interact with this container from our terminal,
use the -it flag to run using interactive mode while allocating a
pseudo-terminal.
• $ docker container exec -it b3ad2a23fab3 bash
• We just used the docker container exec command to “enter” our
container’s namespaces with our bash process. Using docker
container exec with bash is a common pattern to inspect a docker
container.
• Notice the change in the prefix of your terminal. For example
`root@b3ad2a23fab3:/`. This is an indication that we are running
bash “inside” of our container.
• From the same terminal, run ps -ef to inspect the running
processes.

• You should see only the top process, bash process, and


our ps process.
• For comparison, exit the container, and run ps -ef or top on the
host. These commands will work on Linux or Mac. For Windows,
you can inspect the running processes using tasklist.
• These namespaces together provide the isolation for
containers that allow them to run together securely and
without conflict with other containers running on the same
system.
• Note: Namespaces are a feature of the Linux kernel. But
Docker allows you to run containers on Windows and Mac…
how does that work? The secret is that embedded in the
Docker product is a Linux subsystem. Docker open-sourced
this Linux subsystem to a new project: LinuxKit.
2. RUN MULTIPLE CONTAINERS

1- Explore the Docker Store.The Docker Store (


https://hub.docker.com/search/?q=&type=image) is the public central
registry for Docker images. Anyone can share images here publicly. The
Docker Store contains community and official images that can also be
found directly on the Docker Hub.
• When searching for images you will find filters for “Store” vs
“Community” images. “Store” images include content that has been
verified and scanned for security vulnerabilities by Docker. Go one step
further and search for “Certified” images, that are deemed enterprise-
ready and are tested with Docker Enterprise Edition product. It is
important to avoid using unverified content from the Docker Store when
developing your own images that are intended to be deployed into the
production environment. These unverified images may contain security
vulnerabilities or possibly even malicious software.
• In the next step of this lab, you will start a couple of
containers using some verified images from the Docker Store:
nginx web server, and mongo database.
2 - Run an Nginx server.Let’s run a container using the official
Nginx image  (https://hub.docker.com/_/nginx)from the
Docker Store.
docker container run --detach --publish 8080:80 --name nginx nginx
• We are using a couple of new flags here. The --detach flag will run this
container in the background. The publish flag publishes port 80 in the
container (the default port for nginx), using port 8080 on our host.
Remember that the NET namespace gives processes of the container
their own network stack. The --publish flag is a feature that allows us
to expose networking through the container onto the host.
• How do you know port 80 is the default port for nginx? Because it is
listed in the documentation on the Docker Store. In general, the
documentation for the verified images is very good, and you will want
to refer to them when running containers using those images.
• We are also specifying the --name flag, which names the container.
Every container has a name. If you don’t specify one, Docker will
randomly assign one for you. Specifying your own name makes it
easier to run subsequent commands on your container since you can
reference the name instead of the id of the container.
• For example: docker container inspect nginx, instead of docker
container inspect 5e1.
• Since this is the first time you are running the nginx container,
it will pull down the nginx image from the Docker Store.
Subsequent containers created from the Nginx image will use
the existing image located on your host.
• Nginx is a lightweight web server. You can access it on port
8080 on your localhost.
3 - Access the nginx server on http://localhost:8080. 
4 - Now, run a mongoDB server. We will use the official mongoDB
image from the Docker Store. Instead of using the latest tag
(which is the default if no tag is specified), we will use a
specific version of the mongo image: 3.4.
docker container run --detach --publish 8081:27017 --name
mongo mongo:3.4
• Again, since this is the first time we are running a mongo
container, we will pull down the mongo image from the
Docker Store. We are using the --publish flag to expose the
27017 mongo port on our host. We have to use a port other
than 8080 for the host mapping, since that port is already
exposed on our host. Again refer to the official docs on the
Docker Store to get more details about using the mongo
image.
5 - Access http://localhost:8081 to see some output from mongo. 

6 - Check your running containers with docker container ls.


• You should see that you have an Nginx web server container, and a
MongoDB container running on your host. Note that we have not
configured these containers to talk to each other.
• You can see the “nginx” and “mongo” names that we gave to our
containers, and the random name (in my case “priceless_kepler”)
that was generated for the ubuntu container.
• You can also see that the port mappings that we specified with the --
publish flag. For more details information on these running
containers you can use the docker container inspect [container
id] command.
• Note: You didn’t have to install anything on your host (other
than Docker) to run these processes! Each container includes
the dependencies that it needs within the container, so you
don’t need to install anything on your host directly.
• Running multiple containers on the same host gives us the
ability to fully utilize the resources (cpu, memory, etc)
available on single host. This can result in huge cost savings
for an enterprise.
• While running images directly from the Docker Store can be
useful at times, it is more useful to create custom images, and
refer to official images as the starting point for these images.
We will dive into building our own custom images in Lab 2.
3. CLEAN UP

1 - Completing this lab results in a bunch of running


containers on your host. Let’s clean these up.
• Get a list of the containers running using docker
container ls.

• Run docker container stop [container id] for each


container in the list. You can also use the names of the
containers that you specified before.
• Note: You only have to reference enough digits of the ID to be unique.
Three digits is almost always enough.
3- Remove the stopped containers.
docker system prune 
is a really handy command to clean up your system. It will remove any
stopped containers, unused volumes and networks, and dangling images.
• Key takeaways -
• Containers are composed of Linux namespaces and control groups
that provide isolation from other containers and the host.
• Because of the isolation properties of containers, you can schedule
many containers on a single host without worrying about
conflicting dependencies. This makes it easier to run multiple
containers on a single host: fully utilizing resources allocated to
that host, and ultimately saving some money on server costs.
• Avoid using unverified content from the Docker Store when
developing your own images because these images might contain
security vulnerabilities or possibly even malicious software.
• Containers include everything they need to run the processes
within them, so there is no need to install additional dependencies
directly on your host.
Creating custom docker images
OVERVIEW

•  Create a custom Docker image built from a


Dockerfile. Once you build the image, you will
push it to a central registry, where it can be
pulled to be deployed on other environments.
Also, get a brief understanding of image
layers, and how Docker incorporates “copy-on-
write” and the union file system to efficiently
store images and run containers. You will use a
few Docker commands in this lab.
1. CREATE A PYTHON APP (WITHOUT USING DOCKER)

• Create app.py with the following contents:-

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
return "hello world!"

if __name__ == "__main__":
app.run(host='0.0.0.0')
• This is a simple Python app that uses flask to expose a http
web server on port 5000 (5000 is the default port for flask).
Don’t worry if you are not too familiar with Python or Flask.
These concepts can be applied to an application written in any
language.
• Optional: If you have Python and pip installed, you can locally
run this app. If not, move on to the next step.
2. CREATE AND BUILD THE DOCKER IMAGE

• If you don’t have Python installed locally, don’t worry,


because you don’t need it. One of the advantages of
using Docker containers is that you can build Python into
your containers without having Python installed on your
host.
1 - Create the Dockerfile with the following contents:-
FROM python:3.6.1-alpine
RUN pip install flask
CMD ["python","app.py"]
COPY app.py /app.py
• A Dockerfile lists the instructions needed to build a docker image. Let’s
go through the above file line by line.
• FROM python:3.6.1-alpine – >
• This is the starting point for your Dockerfile. Every Dockerfile must start
with a FROM line that is the starting image to build your layers on top of.
• In this case, we are selecting the python:3.6.1-alpine base layer since it
already has the version of python and pip that we need to run our
application. The alpine version means that it uses the alpine distribution,
which is significantly smaller than an alternative flavor of Linux.
• A smaller image means it will download (deploy) much faster, and it also
has advantages for security because it has a smaller attack surface.
• Here we are using the “3.6.1-alpine” tag for the python image. Take a
look at the available tags for the official python image on the Docker Hub
. It is best practice to use a specific tag when inheriting a parent image so
that changes to the parent dependency are controlled. If no tag is
specified, the “latest” tag takes into effect, which is acts as a dynamic
pointer that points to the latest version of an image.
• For security reasons, it is very important to understand the
layers that you build your docker image on top of. For that
reason, it is highly recommended to only use “official”
images found in the docker hub, or non-community images
found in the docker-store. These images are vetted to meet
certain security requirements, and also have very good
documentation for users to follow. You can find more
information about this python base image, as well as all
other images that you can use, on the docker store.
• RUN pip install flask 
• The RUN command executes commands needed to set up
your image for your application, such as installing packages,
editing files, or changing file permissions. In this case we
are installing flask. The RUN commands are executed at
build time, and are added to the layers of your image.
• CMD ["python","app.py"] 
• CMD is the command that is executed when you start
a container. Here we are using CMD to run our python
app.
• There can be only one CMD per Dockerfile. If you
specify more thane one CMD, then the last CMD will
take effect. The parent python:3.6.1-alpine also
specifies a CMD (CMD python2). You can take a look at
the Dockerfile for the official python:alpine image.
• You can use the official python image directly to run
python scripts without installing python on your host.
But today, we are creating a custom image to include
our source, so that we can build an image with our
application and ship it around to other environments.
• COPY app.py /app.py 
• This copies the app.py in the local directory (where you will run docker
image build) into a new layer of the image. This instruction is the last line
in the Dockerfile. Layers that change frequently, such as copying source
code into the image, should be placed near the bottom of the file to take
full advantage of the Docker layer cache. This allows us to avoid
rebuilding layers that could otherwise be cached. For instance, if there
was a change in the FROM instruction, it would invalidate the cache for
all subsequent layers of this image. We will demonstrate a this little later
in this lab.
• It seems counter-intuitive to put this after the CMD
["python","app.py"] line. Remember, the CMD line is executed only when
the container is started, so we won’t get a file not found error here.
• And there you have it: a very simple Dockerfile. See the 
full list of commands  (
https://docs.docker.com/engine/reference/builder/)you can put into a
Dockerfile. Now that we defined our Dockerfile, let’s use it to build our
custom docker image.
• 2 - Build the Docker image.Pass in -t to name
your image python-hello-world.
• $ docker image build -t python-hello-world .
3 - Run docker image ls to verify that your image
shows up in your image list.
$ docker image ls

• Notice that your base image, python:3.6.1-


alpine, is also in your list.
3. RUN THE DOCKER IMAGE

• Now that you have built the image, you can run it to see that it
works.
1 - Run the Docker image.

$ docker run -p 5001:5000 -d python-hello-world

• -p flag maps a port running inside the container to your host. In


this case, you’re mapping the Python app running on port 5000
inside the container to port 5001 on your host. Note that if port
5001 is already in use by another application on your host, you
may have to replace 5001 with another value, such as 5002.
2 - Navigate to http://localhost:5001 in a
browser to see the results.You should see
“hello world!” on your browser.
• 3 - Check the log output of the container.If you
want to see logs from your application you can
use the docker container logs command. By
default, docker container logs prints out what
is sent to standard out by your application.
Use docker container ls to find the id for your
running container.
• $ docker container logs [container id]
4. PUSH TO A CENTRAL REGISTRY

1 - Navigate to Docker Hub (https://hub.docker.com/)and create a free


account, if you haven’t already.For this lab, you will use the Docker Hub as
your central registry. Docker hub is a free service to publicly store available
images, or you can pay to store private images.
• Most organizations that heavily use Docker will set up their own registry
internally. To simplify things, we will use the Docker Hub, but the following
concepts apply to any registry.
2 - Log in to the Docker registry account by typing docker login on your
terminal.
$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you
don't have a Docker ID, head over to https://hub.docker.com to create one.
Username:
3 - Tag your image with your username.The
Docker Hub naming convention is to tag your
image with [dockerhub username]/[image
name]. To do this, tag your previously created
image python-hello-world to fit that format.
• $ docker tag python-hello-world [dockerhub
username]/python-hello-world
4 - Once you have a properly tagged image, use
the docker push command to push your image
to the Docker Hub registry.
$ docker push [username]/python-hello-world
• Check out your image on docker hub in your
browser.Navigate to Docker Hub, and go to your profile
to see your newly uploaded image.
• Now that your image is on Docker Hub, other developers
and operations can use the docker pull command to
deploy your image to other environments.
• Note: Docker images contain all the dependencies that
they need to run an application within the image. This is
useful because you no longer have deal with
environment drift (version differences) when you rely on
dependencies that are installed on every environment
you deploy to. You also don’t have to go through
additional steps to provision these environments. Just
one step: install docker, and you’re good to go.
5. DEPLOY A CHANGE

1 - Update app.py by replacing the string “Hello World” with “Hello Beautiful


World!” in app.py.Your file should have the following contents:-
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
return "Hello Beautiful World!"

if __name__ == "__main__":
app.run(host='0.0.0.0')
• 2 - Now that your app is updated, you need to rebuild your
app and push it to the Docker Hub registry.First rebuild, this
time use your Docker Hub username in the build command.
• $ docker image build -t [username]/python-hello-world .
• Notice the “Using cache” for Steps 1-3. These layers of the
Docker image have already been built, and docker image
build will use these layers from the cache, instead of
rebuilding them.
• There is a caching mechanism in place for pushing layers too.
Docker Hub already has all but one of the layers from an
earlier push, so it only pushes the one layer that has changed.
• When you change a layer, every layer built on top of that will
have to be rebuilt. Each line in a Dockerfile builds a new layer
that is built on the layer created from the lines before it. This
is why the order of the lines in your Dockerfile is important.
We optimized our Dockerfile so that the layer that is most
likely to change (COPY app.py /app.py) is the last line of the
Dockerfile. Generally for an application, your code changes at
the most frequent rate. This optimization is particularly
important for CI/CD processes, where you want your
automation to run as fast as possible.
6. UNDERSTAND IMAGE LAYERS

• One of the major design properties of Docker


is its use of the union file system.
• Consider the Dockerfile that you created
before:
FROM python:3.6.1-alpine
RUN pip install flask
CMD ["python","app.py"]
COPY app.py /app.py
• Each of these lines is a layer. Each layer contains only the
delta, or changes from the layers before it. To put these
layers together into a single running container, Docker
makes use of the union file system to overlay layers
transparently into a single view.
• Each layer of the image is read-only, except for the very
top layer which is created for the container. The
read/write container layer implements “copy-on-write”
which means that files that are stored in lower image
layers are pulled up to the read/write container layer only
when edits are being made to those files. Those changes
are then stored in the container layer. The “copy-on-
write” function is very fast, and in almost all cases, does
not have a noticeable effect on performance. 
• Since image layers are read-only, they can be shared by
images and by running containers. For instance, creating
a new python app with its own Dockerfile with similar
base layers, would share all the layers that it had in
common with the first python app
FROM python:3.6.1-alpine
RUN pip install flask
CMD ["python","app2.py"]
COPY app2.py /app2.py
• You can also experience the sharing of layers when you start
multiple containers from the same image. Since the
containers use the same read-only layers, you can imagine
that starting up containers is very fast and has a very low
footprint on the host.
• You may notice that there are duplicate lines in this
Dockerfile and the Dockerfile you created earlier in this lab.
Although this is a very trivial example, you can pull common
lines of both Dockerfiles into a “base” Dockerfile, that you
can then point to, with each of your child Dockerfiles using
the FROM command.
• Image layering enables the docker caching mechanism for
builds and pushes. For example, the output for your
last docker push shows that some of the layers of your
image already exists on the Docker Hub.
$ docker push [username]/python-hello-world

• To look more closely at layers, you can use


the docker image history command of the
python image we created.
• $ docker image history python-hello-world
• Each line represents a layer of the image. You’ll notice that
the top lines match to your Dockerfile that you created, and
the lines below are pulled from the parent python image.
Don’t worry about the <missing> tags. These are still normal
layers; they have just not been given an ID by the docker
system.
7. CLEAN UP

• Completing this lab results in a lot of running containers on


your host. Let’s clean these up.
1 - Get a list of the containers running using docker container ls.
$ docker container ls
2 - Run docker container stop [container id] for each container in
the list that is running.
3 - Remove the stopped containers.
docker system prune
 is a really handy command to clean up your system. It will
remove any stopped containers, unused volumes and networks,
and dangling images.
• Key takeaways -
• The Dockerfile is how you create reproducible builds for your application and
how you integrate your application with Docker into the CI/CD pipeline.
• Docker images can be made available to all of your environments through a
central registry. The Docker Hub is one example of a registry, but you can
deploy your own registry on servers you control.
• Docker images contain all the dependencies that it needs to run an application
within the image. This is useful because we no longer have deal with
environment drift (version differences) when we rely on dependencies that are
install on every environment we deploy to.
• Docker makes use of the union file system and “copy on write” to reuse layers
of images. This lowers the footprint of storing images and significantly
increases the performance of starting containers.
• Image layers are cached by the Docker build and push system. No need to
rebuild or repush image layers that are already present on the desired system.
• Each line in a Dockerfile creates a new layer, and because of the layer cache,
the lines that change more frequently (for example, adding source code to an
image) should be listed near the bottom of the file.

You might also like