Typically, it is best to break down services into the simplest components and then containerize each of them independently. However, when initially migrating an application it is not always easy to break it up into little pieces but you can start with big containers and work towards breaking them into smaller pieces.
In this lab we will create an all-in-one container image comprised of multiple services. We will also observe several bad practices when composing Dockerfiles and explore how to avoid those mistakes. In lab 3 we will decompose the application into more manageable pieces.
This lab should be performed on workstation.example.com unless otherwise instructed.
Expected completion: 20-25 minutes
Agenda:
- Overview of monolithic application
- Build docker image
- Run container based on docker image
- Exploring the running container
- Connecting to the application
- Review Dockerfile practices
Our monolithic application we are going to use in this lab is a simple wordpress application. Rather than decompose the application into multiple parts we have elected to put the database and the wordpress application into the same container. Our container image will have:
- mariadb and all dependencies
- wordpress and all dependencies
To perform some generic configuration of mariadb and wordpress there are startup configuration scripts that are executed each time a container is started from the image. These scripts configure the services and then start them in the running container.
To build the docker image for this lab please execute the following commands:
cd ~/summit-2017-container-lab/labs/lab2/bigapp/
docker build -t bigimg .
This can take a while to build. While you wait you may want to peek at the Review Dockerfile Practices section at the end of this lab chapter.
To run the docker container based on the image we just built use the following command:
docker run -p 80 --name=bigapp -e DBUSER=user -e DBPASS=mypassword -e DBNAME=mydb -d bigimg
docker ps
Take a look at some of the arguments we are passing to docker. We are telling docker that the image will be listening on port 80 inside the container and to randomly assign a port on the host that maps to port 80 in the container. Next we are providing a name
of bigapp
. After that we are setting some environment variables that will be passed into the container and consumed by the configuration scripts to set up the container. Finally, we pass it the name of the image that we built in the prior step.
Now that the container is running we will explore the
container to see what's going on inside. First off, the processes were
started and any output that goes to stdout will come to the console of
the container. You can run docker logs
to see the output. To follow
or "tail" the logs use the -f
option.
NOTE: You are able to use the name of the container rather
than the container id for most docker
commands.
docker logs -f bigapp
NOTE: When you are finished inspecting the log, just CTRL-C out.
If you need to inspect more than just the stderr/stdout of the machine
then you can enter into the namespace of the container to inspect
things more closely. The easiest way to do this is to use docker exec
. Try it out:
docker exec -it bigapp /bin/bash
pstree
cat /var/www/html/wp-config.php | grep '=='
tail /var/log/httpd/access_log /var/log/httpd/error_log /var/log/mariadb/mariadb.log
Explore the running processes. Here you will httpd and MySQL running in the background.
ps aux
Press CTRL+d
or type exit
to leave the container shell.
First detect the host port number that is is mapped to the container's port 80:
docker port bigapp 80
Now connect to the port via the web browser on your machine using http://cdk.example.com:<port>
. You can also use curl to connect, for example:
curl -L http://cdk.example.com:<port>
So we have built a monolithic application using a somewhat complicated Dockerfile. There are a few principles that are good to follow when creating a Dockerfile that we did not follow for this monolithic app.
To illustrate some problem points in our Dockerfile it has been replicated below with some commentary added:
FROM registry.access.redhat.com/rhel7
>>> No tags on image specification - updates could break things
MAINTAINER Student <[email protected]>
# ADD set up scripts
ADD scripts /scripts
>>> If a local script changes then we have to rebuild the entire
>>> docker image from this point on and we don't take advantage of
>>> the cache
RUN chmod 755 /scripts/*
# Add in custom yum repository and update
ADD ./custom.repo /etc/yum.repos.d/custom.repo
RUN yum -y update
>>> Running a yum clean all in the same statement would clear the yum
>>> cache in our intermediate cached image layer
# Common Deps
RUN yum -y install openssl
RUN yum -y install psmisc
# Deps for wordpress
RUN yum -y install httpd
RUN yum -y install php
RUN yum -y install php-mysql
RUN yum -y install php-gd
RUN yum -y install tar
# Deps for mariadb
RUN yum -y install mariadb-server
RUN yum -y install net-tools
RUN yum -y install hostname
>>> Can group all of the above into one yum statement to minimize
>>> intermediate layers. However, during development, it can be nice
>>> to keep them separated so that your "build/run/debug" cycle can
>>> take advantage of layers and caching. Just be sure to clean it up
>>> before you publish. You can check out the history of the image you
>>> have created by running *docker history bigimg*.
# Add in wordpress sources
COPY latest.tar.gz /latest.tar.gz
>>> Consider using a specific version of Wordpress to control the installed version
RUN tar xvzf /latest.tar.gz -C /var/www/html --strip-components=1
RUN rm /latest.tar.gz
RUN chown -R apache:apache /var/www/
>>> Can group above 4 statements into one multi-line statement to minimize
>>> space used by intermediate layers. (i.e. latest.tar.gz would not be
>>> stored in any image).
EXPOSE 80
CMD ["/bin/bash", "/scripts/start.sh"]
More generally:
- Use a specific tag for the source image. Image updates may break things.
- Place rarely changing statements towards the top of the file. This allows the re-use of cached image layers when rebuilding.
- Group statements into multi-line statements. This avoids layers that have files needed only for build.
- Use
LABEL RUN
instruction to prescribe how the image is to be run. - Avoid running application as root user where possible.
- Use
VOLUME
instruction to create a host mount point for persistent storage.
In the next lab we will fix these issues and break the application up into separate services.