DockerInAction-Build automation and advanced image considerations

来源:互联网 发布:精通matlab最优化计算 编辑:程序博客网 时间:2024/05/22 00:16

Packaging Git with a Dockerfile

# An example Dockerfile for installing Git on UbuntuFROM ubuntu:latestMAINTAINER "dockerinaction@allingeek.com"RUN apt-get install -y gitENTRYPOINT ["git"]

Before dissecting this example, build a new image from it with the docker build command from the same directory containing the Dockerfile. Tag the new image with auto:

docker build --tag ubuntu-git:auto .

Outputs several lines about steps and output from apt-get and will finally display a message like this:

Successfully built 0bca8436849b

View the list of all your ubuntu-git images and test the newest one with this command:

docker images

The new build tagged “auto” should now appear in the list:

REPOSITORY   TAG      IMAGE ID      CREATED         VIRTUAL SIZEubuntu-git   auto     0bca8436849b  10 seconds ago  225.9 MBubuntu-git   latest   826c66145a59  10 minutes ago  226.6 MBubuntu-git   removed  826c66145a59  10 minutes ago  226.6 MBubuntu-git   1.9      3e356394c14e  41 hours ago    226 MB

The first instruction must be FROM. If you’re starting from an empty image and your software has no dependencies, or you’ll provide all the dependencies, then you can start from a special empty repository named scratch.

The builder validated that the image specified by the FROM instruction was installed as the first step of the build. If it were not, Docker would have automatically tried to pull the image.

The output will show which steps the builder was able to skip in favor of cached results:

Sending build context to Docker daemon 2.048 kBSending build context to Docker daemonStep 0 : FROM ubuntu:latest ---> b39b81afc8caStep 1 : MAINTAINER "dockerinaction@allingeek.com" ---> Using cache ---> 80a695671201Step 2 : RUN apt-get install -y git ---> Using cache # Note use of cache ---> 1c20f8970532Step 3 : ENTRYPOINT git ---> Using cache # Note use of cache ---> 89d726cf3514Step 4 : RUN This will not work ---> Running in f68f0e0418b5/bin/sh: 1: This: not foundINFO[0001] The command [/bin/sh -c This will not work] returned a non-zerocode: 127

A Dockerfile primer

Metadata instructions

Create a new file named .dockerignore and copy in the following lines:

.dockerignoremailer-base.dfmailer-logging.dfmailer-live.df

Create a new file named mailer-base.df and add the following lines:

FROM debian:wheezyMAINTAINER Jeff Nickoloff "dia@allingeek.com"RUN groupadd -r -g 2200 example && \    useradd -rM -g example -u 2200 exampleENV APPROOT="/app" \    APP="mailer.sh" \    VERSION="0.6"LABEL base.name="Mailer Archetype" \      base.version="${VERSION}"WORKDIR $APPROOTADD . $APPROOTENTRYPOINT ["/app/mailer.sh"]EXPOSE 33333# Do not set the default user in the base otherwise# implementations will not be able to update the image# USER example:example

The -f flag tells the builder which filename to use as input:

docker build -t dockerinaction/mailer-base:0.6 -f mailer-base.df .
  • ENV sets environment variables for an image similar to the –env flag on docker run or docker create.
  • The LABEL instruction is used to define key/value pairs that are recorded as additional metadata for an image or container. This mirrors the –label flag on docker run and docker create.
  • The result of the WORKDIR instruction will be an image with the default working directory set to /app. Setting WORKDIR to a location that doesn’t exist will create that location just like the command-line option.
  • The EXPOSE command creates a layer that opens TCP port 33333.
  • The FROM instruction sets the layer stack to start from the debian:wheezy image. Any new layers built will be placed on top of that image.
  • The MAINTAINER instruction sets the Author value in the image metadata.
  • The ENTRYPOINT instruction sets the executable to be run at container startup.
  • The last commented line is a metadata instruction USER. It sets the user and group for all further build steps and containers created from the image.

You’ll need to inspect the image:

docker inspect dockerinaction/mailer-base:0.6

The relevant lines are these:

"Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "APPROOT=/app","APP=mailer.sh","VERSION=0.6"],..."Labels": {    "base.name": "Mailer Archetype","base.version": "0.6" },..."WorkingDir": "/app"

File system instructions

A file named mailer-logging.df:

FROM dockerinaction/mailer-base:0.6COPY ["./log-impl", "${APPROOT}"]RUN chmod a+x ${APPROOT}/${APP} && \    chown example:example /var/logUSER example:exampleVOLUME ["/var/log"]CMD ["/var/log/mailer.log"]
  • The COPY instruction will copy files from the file system where the image is being built into the build container.
  • The second new instruction is VOLUME. This behaves exactly as you’d expect if you understand what the –volume flag does on a call to docker run or docker create.
  • The CMD command represents an argument list for the entrypoint. The default entrypoint for a container is /bin/sh.

Create a directory at ./log-impl. Inside that directory create a file named mailer.sh and copy the following script into the file:

#!/bin/shprintf "Logging Mailer has started.\n"while truedo    MESSAGE=$(nc -l -p 33333)    printf "[Message]: %s\n" "$MESSAGE" > $1    sleep 1done

This script will start a mailer daemon on port 33333 and write each message that it receives to the file specified in the first argument to the program.

Build and run the container:

docker build -t dockerinaction/mailer-logging -f mailer-logging.df .docker run -d --name logging-mailer dockerinaction/mailer-logging

Name this file mailer-live.df:

FROM dockerinaction/mailer-base:0.6ADD ["./live-impl", "${APPROOT}"]RUN apt-get update && \    apt-get install -y curl python && \    curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py" && \    python get-pip.py && \    pip install awscli && \    rm get-pip.py && \    chmod a+x "${APPROOT}/${APP}"RUN apt-get install -y netcatUSER example:exampleCMD ["mailer@dockerinaction.com", "pager@dockerinaction.com"]

The ADD instruction will
* Fetch remote source files if a URL is specified
* Extract the files of any source determined to be an archive file

Next, create a new subdirectory named live-impl under the location containing mailer-live.df. Add the following script to a file in that directory named mailer.sh:

#!/bin/shprintf "Live Mailer has started.\n"while truedo  MESSAGE=$(nc -l -p 33333)  aws ses send-email --from $1 \    --destination {\"ToAddresses\":[\"$2\"]} \    --message "{\"Subject\":{\"Data\":\"Mailer Alert\"},\                \"Body\":{\"Text\":{\"Data\":\"$MESSAGE}\"}}}"sleep 1 done

It will wait for connections on port 33333, take action on any received messages, and then sleep for a moment before waiting for another message.

Build and run the container:

docker build -t dockerinaction/mailer-live -f mailer-live.df .docker run -d --name live-mailer dockerinaction/mailer-live

Injecting downstream build-time behavior

The ONBUILD instruction defines instructions to execute if the resulting image is used as a base for another build.

The upstream Dockerfile would use a set of instructions like this:

ONBUILD COPY [".", "/var/myapp"]ONBUILD RUN go build /var/myapp

The instructions following ONBUILD instructions aren’t executed when their containing Dockerfile is built. Instead, those instructions are recorded in the resulting image’s metadata under ContainerConfig.OnBuild. The previous instructions would result in the following metadata inclusions:

"ContainerConfig": {    "OnBuild": [        "COPY [\".\", \"/var/myapp\"]",        "RUN go build /var/myapp"]}

When a downstream Dockerfile uses the upstream image(the one with the ONBUILD instructions) in a FROM instruction, those ONBUILD instructions are executed after the FROM instruction and before the next instruction in a Dockerfile.

Using startup scripts and multiprocess containers

Environmental preconditions validation

That script validates that the container context is set in a way that’s compatible with the contained version of WordPress. If any required condition is unmet (a link is undefined or a variable is unset), then the script will exit before starting WordPress, and the container will stop unexpectedly.

This type of startup script is generally use-case specific. If you’re packaging a specific piece of software in an image, you’ll need to write the script yourself. Your script should validate as much of the assumed context as possible. This should include the following:

  • Presumed links (and aliases)
  • Environment variables
  • Network access
  • Network port availability
  • Root file system mount parameters (read-write or read-only)
  • Volumes
  • Current user

At container startup, this script enforces that either another container has been linked to the web alias and has exposed port 80 or the WEB_HOST environment variable has been defined:

#!/bin/bashset -eif [ -n "$WEB_PORT_80_TCP" ]; then  if [ -z "$WEB_HOST" ]; then    WEB_HOST='web'  else    echo >&2 '[WARN]: Linked container, "web" overridden by $WEB_HOST.'    echo >&2 "===> Connecting to WEB_HOST ($WEB_HOST)"  fifiif [ -z "$WEB_HOST" ]; then  echo >&2 '[ERROR]: specify a linked container, "web" or WEB_HOST environ-   ment variable'  exit 1fiexec "$@" # run the default command

Initialization processes

Using an init process is the best way to launch multiple programs, clean up orphaned processes, monitor processes, and automatically restart any failed processes.

When evaluating any init program for use in a container, consider these factors:

  • Additional dependencies the program will bring into the image
  • File sizes
  • How the program passes signals to its child processes (or if it does)
  • Required user access
  • Monitoring and restart functionality (backoff-on-restart features are a bonus)
  • Zombie process cleanup features

Building hardened application images

Hardening an image is the process of shaping it in a way that will reduce the attack surface inside any Docker containers based on it.

A general strategy for hardening an application image is to minimize the software included with it.

There are three things that you can do to harden an image beyond that general strategy. First, you can enforce that your images are built from a specific image. Second, you can make sure that regardless of how containers are built from your image, they will have a sensible default user. Last, you should eliminate a common path for root user escalation.

0 0
原创粉丝点击