designates my notes. / designates important. / designates very important.
Pages from pdf.
Docker also encourages service-oriented and microservices architectures. Docker recommends that each container run a single application or process.
This should be interesting…
Images are the building blocks of the Docker world. You launch your containers from images. Images are the “build” part of Docker’s life cycle. They have a layered format, using Union file systems, that are built step-by-step using a series of instructions. For example:
• Add a file. • Run a command. • Open a port.
You can consider images to be the “source code” for your containers. They are highly portable and can be shared, stored, and updated.
Docker stores the images you build in registries. There are two types of registries: public and private.
Docker helps you build and deploy containers inside of which you can package your applications and services. As we’ve just learned, containers are launched from images and can contain one or more running processes. You can think about images as the building or packing aspect of Docker and the containers as the running or execution aspect of Docker.
A Docker container is: • An image format. • A set of standard operations. • An execution environment.
• Docker Compose - which allows you to run stacks of containers to represent application stacks, for example web server, application server and database server containers running together to serve a specific application.
• Docker Swarm - which allows you to create clusters of containers, called swarms, that allow you to run scalable workloads.
# Listing 3.37: Deleting all containers
sudo docker rm -f `sudo docker ps -a -q`
This pattern is traditionally called “copy on write”
Local images live on our local Docker host in the /var/lib/docker directory. Each image will be inside a directory named for your storage driver; for example, aufs or devicemapper. You’ll also find all your containers in the /var/lib/docker/containers directory.
# Listing 4.21: Creating a sample repository
mkdir static_web
cd static_web
touch Dockerfile
# Listing 4.22: Our first Dockerfile
# Version: 0.0.1
FROM ubuntu:18.04
LABEL maintainer="james@example.com"
RUN apt-get update; apt-get install -y nginx
RUN echo 'Hi, I am in your container' \
>/var/www/html/index.html
EXPOSE 80
FROM
, should be in upper-case and be followed by an
argument: FROM ubuntu:18.04
. Instructions in the Dockerfile are processed from
the top down, so you should order them accordingly.Docker runs a container from the image.
An instruction executes and makes a change to the container.
Docker runs the equivalent of docker commit to commit a new layer.
Docker then runs a new container from this new image.
The next instruction in the file is executed, and the process repeats until all instructions have been executed.
This means that if your Dockerfile stops for some reason (for example, if an instruction fails to complete), you will be left with an image you can use. This is highly useful for debugging: you can run a container from this image interactively and then debug why your instruction failed using the last image created.
/bin/sh -c
. If you are running the instruction on a platform without a
shell or you wish to execute without a shell (for example, to avoid shell string
munging), you can specify the instruction in exec format:# Listing 4.23: A RUN instruction in exec form
RUN [ "apt-get", " install", "-y", "nginx" ]
We use this format to specify an array containing the command to be executed and then each parameter to pass to the command.
Next, we’ve specified the EXPOSE instruction, which tells Docker that the application in this container will use this specific port on the container. That doesn’t mean you can automatically access whatever service is running on that port (here, port 80) on the container. For security reasons, Docker doesn’t open the port automatically, but waits for you to do it when you run the container using the docker run command.
You can expose ports at run time with the docker run command with the --expose
option.
# Listing 4.24: Running the Dockerfile
cd static_web
sudo docker build -t="jamtur01/static_web" .
# Listing 4.25: Tagging a build
sudo docker build -t="jamtur01/static_web:v1" .
If you don’t specify any tag, Docker will automatically tag your image as latest.
The trailing .
tells Docker to look in the local directory to find the Dockerfile. You can also specify a Git repository as a source for the Dockerfile as we see here:
# Listing 4.26: Building from a Git repository
sudo docker build -t="jamtur01/static_web:v1" \
github.com/turnbullpress/docker-static_web
.dockerignore
exists in the root of the build context then it
is interpreted as a newline-separated list of exclusion patterns. Much like a
.gitignore
file it excludes the listed files from being treated as part of the
build context, and therefore prevents them from being uploaded to the Docker
daemon. Globbing can be done using Go’s filepath.# Listing 4.30: Bypassing the Dockerfile build cache
sudo docker build --no-cache -t="jamtur01/static_web" .
# Listing 4.37: The docker port command
sudo docker port 6751b94bb5c0 80
> 0.0.0.0:49154
# Listing 4.39: Exposing a specific port with -p
sudo docker run -d -p 80:80 --name static_web_80 jamtur01 static_web nginx -g "daemon off;"
# Listing 4.41: Binding to a specific interface
sudo docker run -d -p 127.0.0.1:80:80 --name static_web_lb jamtur01/static_web nginx -g "daemon off;"
# Listing 4.42: Binding to a random port on a specific interface
sudo docker run -d -p 127.0.0.1::80 --name static_web_random jamtur01/static_web nginx -g "daemon off;"
# Listing 4.43: Exposing a port with docker run
sudo docker run -d -P --name static_web_all jamtur01/static_web nginx -g "daemon off;"
# Listing 4.45: Specifying a specific command to run
sudo docker run -i -t jamtur01/static_web /bin/true
# Listing 4.46: Using the CMD instruction
CMD ["/bin/true"]
# Listing 4.47: Passing parameters to the CMD instruction
CMD ["/bin/bash", "-l"]
Closely related to the CMD instruction, and often confused with it, is the ENTRYPOINT instruction.
The ENTRYPOINT instruction provides a command that isn’t as easily overridden. Instead, any arguments we specify on the docker run command line will be passed as arguments to the command specified in the ENTRYPOINT.
# Listing 4.51: Specifying an ENTRYPOINT
ENTRYPOINT ["/usr/sbin/nginx"]
# Listing 4.52: Specifying an ENTRYPOINT parameter
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
# Listing 4.54: Using docker run with ENTRYPOINT
sudo docker run -t -i jamtur01/static_web -g "daemon off;"
# Listing 4.55: Using ENTRYPOINT and CMD together
ENTRYPOINT ["/usr/sbin/nginx"]
CMD ["-h"]
Now when we launch a container, any option we specify will be passed to the Nginx daemon; for example, we could specify -g “daemon off”; as we did above to run the daemon in the foreground. If we don’t specify anything to pass to the container, then the -h is passed by the CMD instruction and returns the Nginx help text: /usr/sbin/nginx -h.
This allows us to build in a default command to execute when our container is run combined with overridable options and flags on the docker run command line.
The WORKDIR instruction provides a way to set the working directory for the con- tainer and the ENTRYPOINT and/or CMD to be executed when a container is launched from the image.
# Listing 4.56: Using the WORKDIR instruction
WORKDIR /opt/webapp/db
RUN bundle install
WORKDIR /opt/webapp
ENTRYPOINT [ "rackup" ]
Here we’ve changed into the /opt/webapp/db directory to run bundle install and then changed into the /opt/webapp directory prior to specifying our ENTRYPOINT instruction of rackup.
You can override the working directory at runtime with the -w flag, for example:
# Listing 4.57: Overriding the working directory
sudo docker run -ti -w /var/log ubuntu pwd
> /var/log
# Listing 4.58: Setting an environment variable in Dockerfile
ENV RVM_PATH /home/rvm/
# Listing 4.61: Setting multiple environment variables using ENV
ENV RVM_PATH=/home/rvm RVM_ARCHFLAGS="-arch i386"
# Listing 4.62: Using an environment variable in other Dockerfile instructions
ENV TARGET_DIR /opt/app
WORKDIR $TARGET_DIR
# Listing 4.65: Using the USER instruction
USER nginx
The VOLUME instruction adds volumes to any container created from the image. A volume is a specially designated directory within one or more containers that bypasses the Union File System to provide several useful features for persistent or shared data:
• Volumes can be shared and reused between containers. • A container doesn’t have to be running to share its volumes. • Changes to a volume are made directly. • Changes to a volume will not be included when you update an image. • Volumes persist even if no containers use them.
# Listing 4.67: Using the VOLUME instruction
VOLUME ["/opt/project"]
# Listing 4.68: Using multiple VOLUME instructions
VOLUME ["/opt/project", "/data" ]
# Listing 4.69: Using the ADD instruction
ADD software.lic /opt/application/software.lic
# Listing 4.73: Adding LABEL instructions
LABEL version="1.0"
LABEL location="New York" type="Data Center" role="Web Server"
The STOPSIGNAL instruction sets the system call signal that will be sent to the container when you tell it to stop. This signal can be a valid number from the kernel syscall table, for instance 9, or a signal name in the format SIGNAME, for instance SIGKILL.
The ARG instruction defines variables that can be passed at build-time via the docker build command. This is done using the –build-arg flag. You can only specify build-time arguments that have been defined in the Dockerfile.
# Listing 4.75: Adding ARG instructions
ARG build
ARG webapp_user=user
# Listing 4.76: Using an ARG instruction
docker build --build-arg build=1234 -t jamtur01/webapp .
The SHELL instruction allows the default shell used for the shell form of commands to be overridden. The default shell on Linux is ‘["/bin/sh", “-c”] and on Windows is [“cmd”, “/S”, “/C”].
The HEALTHCHECK instruction tells Docker how to test a container to check that it is still working correctly. This allows you to check things like a web site being served or an API endpoint responding with the correct data, allowing you to identify issues that appear, even if an underlying process still appears to be running normally.
When a container has a health check specified, it has a health status in addition to its normal status. You can specify a health check like:
# Listing 4.78: Specifying a HEALTHCHECK instruction
HEALTHCHECK --interval=10s --timeout=1m --retries=5 CMD curl http://localhost || exit 1
# Listing 4.84: A new ONBUILD image Dockerfile
FROM ubuntu:18.04
LABEL maintainer="james@example.com"
RUN apt-get update; apt-get install -y apache2
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2
ENV APACHE_PID_FILE /var/run/apache2.pid
ENV APACHE_RUN_DIR /var/run/apache2
ENV APACHE_LOCK_DIR /var/lock/apache2
ONBUILD ADD . /var/www/
EXPOSE 80
ENTRYPOINT ["/usr/sbin/apachectl"]
CMD ["-D", "FOREGROUND"]
# Listing 5.9: Running our first Nginx testing container
sudo docker run -d -p 80 --name website -v $PWD/website:/var/www/html/website jamtur01/nginx nginx
the -v
option is new. This new option allows us to create a volume in our
container from a directory on the host.
Volumes are specially designated directories within one or more containers that bypass the layered Union File System to provide persistent or shared data for Docker. This means that changes to a volume are made directly and bypass the image. They will not be included when we commit or build an image.
Volumes can also be shared between containers and can persist even when containers are stopped.
We can also specify the read/write status of the container directory by adding
either rw
or ro
after that directory
# Listing 5.16: Dockerfile for our Sinatra container
FROM ubuntu:18.04
LABEL maintainer="james@example.com"
ENV REFRESHED_AT 2016-06-01
RUN apt-get update -yqq; apt-get -yqq install ruby ruby-dev build -essential redis-tools
RUN gem install --no-rdoc --no-ri sinatra json redis
RUN mkdir -p /opt/webapp
EXPOSE 4567
CMD [ "/opt/webapp/bin/webapp" ]
You can see that we’ve created another Ubuntu-based image, installed Ruby and RubyGems, and then used the gem binary to install the sinatra, json, and redis gems. The sinatra and json gems contain Ruby’s Sinatra library and support for JSON. The redis gem we’re going to use a little later on to provide integration to a Redis database.
We’ve also created a directory to hold our new web application and exposed the default WEBrick port of 4567.
Finally, we’ve specified a CMD of /opt/webapp/bin/webapp, which will be the bi- nary that launches our web application.
Docker’s internal network is not an overly flexible or powerful solution. We’re mostly going to discuss it to introduce you to how Docker networking functions. We don’t recommend it as a solution for connecting containers.
The more realistic method for connecting containers is Docker Networking.
Docker Networking can connect containers to each other across different hosts.
Containers connected via Docker Networking can be stopped, started or restarted without needing to update connections.
With Docker Networking you don’t need to create a container before you can connect to it. You also don’t need to worry about the order in which you run containers and you get internal container name resolution and discovery inside the network.
internal networking.
Every Docker container is assigned an IP address, provided through an interface created when we installed Docker. That interface is called docker0. Let’s look at that interface on our Docker host now.
# Listing 5.43: Docker iptables and NAT
sudo iptables -t nat -L -n
Secondly, if we restart the container, Docker changes the IP address. Let’s see this now using the docker restart command (we’ll get the same result if we kill our container using the docker kill command).
Container connections are created using networks. This is called Docker Networking and was introduced in the Docker 1.9 release. Docker Networking allows you to setup your own networks through which containers can communicate. Essentially this supplements the existing docker0 network with new, user managed networks. Importantly, containers can now communicate with each across hosts and your networking configuration can be highly customizable.
# Listing 5.49: Creating a Docker network
sudo docker network create app
In addition to bridge networks, which exist on a single host, we can also create overlay networks, which allow us to span multiple hosts. You can read more about overlay networks in the Docker multi-host network documentation.
You can list all current networks using the docker network ls
command.
docker network rm
command.# Listing 5.52: Creating a Redis container inside our Docker network
sudo docker run -d --net=app --name db jamtur01/redis
# Listing 5.54: Linking our Redis container
cd sinatra
sudo docker run -p 4567 --net=app --name network_test -t -i jamtur01/sinatra /bin/bash
root@305c5f27dbd1:/#
We’ve launched a container named network_test inside the app network. We’ve launched it interactively so we can peek inside to see what’s happening. As the container has been started inside the app network, Docker will have taken note of all other containers running inside that network and populated their addresses in local DNS. Let’s see this now in the network_test container.
We first need the dnsutils
and iputils-ping
packages to get the nslookup
and
ping
binaries respectively.
# Listing 5.56: DNS resolution in the network_test container
root@305c5f27dbd1:/# nslookup db
Server: 127.0.0.11
Address:127.0.0.11#53
Non-authoritative answer:
Name: db
Address: 172.18.0.2
nslookup
command to resolve the db container it returns
the IP address: 172.18.0.2. A Docker network will also add the app network as a
domain suffix for the network, any host in the app network can be resolved by
hostname.app, here db.app. Let’s try that now.# Listing 5.57: Pinging db.app in the network_test container
root@305c5f27dbd1:/# ping db.app
PING db.app (172.18.0.2) 56(84) bytes of data.
64 bytes from db (172.18.0.2): icmp_seq=1 ttl=64 time=0.290 ms
# Listing 5.58: The Redis DB hostname in code
redis = Redis.new(:host => 'db', :port => '6379')
docker network connect
command.We can also disconnect a container from a network using the
docker network disconnect
command.
Containers can belong to multiple networks at once so you can create quite complex networking models.
A volume is a specially designated directory within one or more containers that bypasses the Union File System to provide several useful features for persistent or shared data:
• Volumes can be shared and reused between containers. • A container doesn’t have to be running to share its volumes. • Changes to a volume are made directly. • Changes to a volume will not be included when you update an image. • Volumes persist even when no containers use them.
This allows you to add data (e.g., source code, a database, or other content) into an image without committing it to the image and allows you to share that data between containers.
/var/lib/docker/volumes
directory.
You can identify the location of specific volumes using the docker inspect
command.# Listing 6.11: Creating an Apache container
sudo docker run -d -P --volumes-from james_blog jamtur01/apache
09a570cc2267019352525079fbba9927806f782acb88213bd38dde7e2795407d
--volumes -from
. The --volumes-from
flag adds any volumes in the named container
to the newly created container. This means our Apache container has access to
the com- piled Jekyll site in the /var/www/html
volume within the james_blog
container we created earlier. It has that access even though the james_blog
container is not running. As you’ll recall, that is one of the special
properties of volumes. The container does have to exist, though.the --rm
flag, which is useful for single-use or throw- away containers. It
automatically deletes the container after the process running in it is ended.
This example could also work for a database stored in a volume or similar data. Simply mount the volume in a fresh container, perform your backup, and discard the container you created for the backup.
# Listing 6.62: Using docker kill to send signals
sudo docker kill -s <signal> <container>
This will send the specific signal you want (e.g., a HUP) to the container in question rather than killing the container.
I didn’t take any notes on the docker-compose section.
With Docker Compose, we define a set of containers to boot up, and their runtime properties, all defined in a YAML file. Docker Compose calls each of these containers “services” which it defines as:
A container that interacts with other containers in some way and that has specific runtime properties.
Again, very few notes takes because this isn’t what I am interested in. Focus is on ‘consul’.
There are three specific APIs in the Docker ecosystem.
Firstly, we need to remember the Engine API is provided by the Docker daemon. By default, the Docker daemons binds to a socket, unix:///var/run/docker.sock, on the host on which it is running. The daemon runs with root privileges so as to have the access needed to manage the appropriate resources.
The docker group is root-equivalent and should be limited to those users and applications that absolutely need it.
# Listing 8.1: Querying the Docker API locally
curl --unix-socket /var/run/docker.sock http:/info
{"ID":"PH4R:BT7H:44F6:GQGP:FS2O:7OZO:HQ2P:NSVF:MK27:NBGZ:N3VP:
K2O5","Containers":3,"ContainersRunning":3,"ContainersPaused"
:0,"ContainersStopped":0,"Images":3,"
. . .
}