DockerInAction-Running software in containers

来源:互联网 发布:ubuntu glib 安装 编辑:程序博客网 时间:2024/06/09 17:46

Getting help with the Docker command line

docker help gives you only high-level information about what commands are available. To get detailed information about a specific command, include the command in the COMMAND argument.

docker help cp

That will display a usage pattern for docker cp, a general description of what the command does, and a detailed breakdown of its arguments.

Controlling containers: building a website monitor

This example uses three containers. The first will run NGINX; the second will run a program called a mailer. Both of these will run as detached containers. Detached means that the container will run in the background, without being attached to any input or output stream. A third program, called an agent, will run in an interactive container.

2.1

Creating and starting a new container

docker run --detach \    --name web nginx:latest # Note the detach flag

When you run this command, Docker will install nginx:latest from the NGINX repository hosted on Docker Hub and run the software.

Running detached containers is a perfect fit for programs that sit quietly in the background. That type of program is called a daemon.

docker run -d \    --name mailer \ # Start detached

This command uses the short form of the --detach flag to start a new container named mailer in the background.

Running interactive containers

docker run --interactive --tty \    --link web:web \    --name web_test \    busybox:latest /bin/sh # Create a virtual terminal and bin stdin

The command uses two flags on the run command: –interactive (or -i) and –-tty (or –t). First, the --interactive option tells Docker to keep the standard input stream (stdin) open for the container even if no terminal is attached. Second, the --tty option tells Docker to allocate a virtual terminal for the container, which will allow you to pass signals to the container.

To finish the work for your client, you need to start an agent. This is a monitoring agent that will test the web server as you did in the last example and send a message with the mailer if the web server stops.

docker run -it \    --name agent \    --link web:insideweb \    --link mailer:insidemailer \    dockerinaction/ch2_agent # Create a virtual terminal and bind stdin

Listing, stopping, restarting, and viewing output of containers

docker ps

Running the command will display the following information about each running container:

  • The container ID
  • The image used
  • The command executed in the container
  • The time since the container was created
  • The duration that the container has been running
  • The network ports exposed by the container
  • The name of the container

At this point you should have three running containers with names: web, mailer, and agent.

Choose the appropriate ones to restart the containers that were missing from the list of running containers.

docker restart webdocker restart mailerdocker restart agent

Examine the logs for each container. Start with the web container:

docker logs web

That should display a long log with several lines that contain this substring:

"GET / HTTP/1.0" 200

Examine the log output for mailer and agent as well:

docker logs mailerdocker logs agent

The docker logs command has a flag, --follow or -f, that will display the logs and then continue watching and updating the display with changes to the log as they occur.

The docker stop command tells the program with PID #1 in the container to halt.

docker stop web    # Stop the web server by stopping the containerdocker logs mailer # Wait a couple seconds and check the mailer logs

Look for a line at the end of the mailer logs that reads like:

“Sending email: To: admin@work  Message: The service is down!”

Solved problems and the PID namespace

docker run -d --name namespaceA \    busybox:latest /bin/sh -c "sleep 30000"docker run -d --name namespaceB \    busybox:latest /bin/sh -c "nc -l -p 0.0.0.0:80"docker exec namespaceA ps # 1docker exec namespaceB ps # 2

Command 1 above should generate a process list similar to the following:

PID   USER     COMMAND  1   root     /bin/sh -c sleep 30000  5   root     sleep 30000  6   root     ps

Command 2 above should generate a slightly different process list:

PID   USER     COMMAND  1   root     /bin/sh -c nc -l -p 0.0.0.0:80  7   root     nc -l -p 0.0.0.0:80  8   root     ps

Without a PID namespace, the processes running inside a container would share the same ID space as those in other containers or on the host. A container would be able to determine what other processes were running on the host machine. Worse, namespaces transform many authorization decisions into domain decisions. That means processes in one container might be able to control processes in other containers.

Like most Docker isolation features, you can optionally create containers without their own PID namespace. You can try this yourself by setting the --pid flag on docker create or docker run and setting the value to host.

docker run --pid host busybox:latest ps # Should list all processes running on the computer

This is a basic software conflict example. You can see it in action by trying to run two copies of NGINX in the same container:

docker run –d --name webConflict nginx:latestdocker logs webConflict # The output should be emptydocker exec webConflict nginx -g 'daemon off;' # Start a second nginx process in the same container

The last command should display output like:

2015/03/29 22:04:35 [emerg] 10#0: bind() to 0.0.0.0:80 failed (98:Address already in use)nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)...

Run each in a different container, like this:

docker run -d --name webA nginx:latest # Start the first nginx instancedocker logs webA                       # Verify that it is working, should be emptydocker run -d --name webB nginx:latest # Start the second instancedocker logs webB                       # Verify that it is working, should be empty

Eliminating metaconflicts: building a website farm

2.2

Flexible container identification

docker run -d --name webid nginx # Create a container named "webid"docker run -d --name webid nginx # Create another container named "webid"

The second command here will fail with a conflict error:

FATA[0000] Error response from daemon: Conflict. The name "webid" isalready in use by container 2b5958ba6a00. You have to delete (or rename)that container to be able to reuse that name.

By default Docker assigns a unique (human-friendly) name to each container it creates. You can always rename the container with the docker rename command:

docker rename webid webid-old    # Rename the current web container to "webid-old"docker run -d --name webid nginx # Create another container named "webid"

In addition to the name, Docker assigns a unique identifier that was mentioned in the first example. These are hex-encoded 1024-bit numbers and look something like this:

7cb5d2b9a7eab87f07182b5bf58936c9947890995b1b94f412912fa822a9ecb5

You can use these identifiers in place of the container name with any command that needs to identify a specific container. For example, you could use the previous ID with a stop or exec command:

docker exec \    7cb5d2b9a7eab87f07182b5bf58936c9947890995b1b94f412912fa822a9ecb5 \psdocker stop \    7cb5d2b9a7eab87f07182b5bf58936c9947890995b1b94f412912fa822a9ecb5

In most Docker interfaces, you’ll see container IDs truncated to their first 12 characters. So the previous two commands could be written like this:

docker exec 7cb5d2b9a7ea psdocker stop 7cb5d2b9a7ea

When a new container is started in detached mode, the container ID will be written to the terminal (stdout).

docker create nginx

The result should be a line like:

b26a631e536d3caae348e9fd36e7661254a11511eb2274fb55f9f7c788721b0d

You can simply assign that result to a shell variable and use it again later:

CID=$(docker create nginx:latest) # This will work on POSIX-compliant shellsecho $CID

Both the docker run and docker create commands provide another flag to write the ID of a new container to a known file:

docker create --cidfile /tmp/web.cid nginx # Create a new stopped containercat /tmp/web.cid                           # Inspect the file

For example, if you want to get the truncated ID of the last created container, you can use this:

CID=$(docker ps --latest --quiet) # This will work on POSIX-compliant shellsecho $CIDCID=$(docker ps -l –q)            # Run again with the short-form flagsecho $CID

If you want to get the full container ID, you can use the --no-trunc option on the docker ps command.

Container state and dependencies

MAILER_CID=$(docker run -d dockerinaction/ch2_mailer) # Make sure mailer from first example is runningWEB_CID=$(docker create nginx)AGENT_CID=$(docker create --link $WEB_CID:insideweb \    --link $MAILER_CID:insidemailer \    dockerinaction/ch2_agent)

Neither of the new containers you started appears in the list of containers because docker ps shows only running containers by default. Those containers were specifically created with docker create and never started (the exited state). To see all the containers (including those in the exited state), use the -a option:

docker ps -a

2.3

Now that you’ve verified that both of the containers were created, you need to start
them. For that you can use the docker start command:

docker start $AGENT_CIDdocker start $WEB_CID

Docker reported a message like this one:

Error response from daemon: Cannot start container 03e65e3c6ee34e714665a8dc4e33fb19257d11402b151380ed4c0a5e38779d0a: Cannot link to a non running container: /clever_wright AS /modest_hopper/ insidewebFATA[0000] Error: failed to start one or more containers

You need to start the web container first:

docker start $WEB_CIDdocker start $AGENT_CID

The link mechanism injects IP addresses into dependent containers, and containers that aren’t running don’t have IP addresses.

Whether you’re using docker run or docker create, the resulting containers need to be started in the reverse order of their dependency chain.

At this point you can put everything together into one concise script that looks like the following:

MAILER_CID=$(docker run -d dockerinaction/ch2_mailer)WEB_CID=$(docker run -d nginx)AGENT_CID=$(docker run -d \    --link $WEB_CID:insideweb \    --link $MAILER_CID:insidemailer \    dockerinaction/ch2_agent)

Building environment-agnostic systems

Docker has three specific features to help build environment-agnostic systems:

  • Read-only file systems
  • Environment variable injection
  • Volumes

Read-only file systems

Using the --read-only flag:

docker run -d --name wp --read-only wordpress:4docker inspect --format "{{.State.Running}}" wp

The docker inspect command will display all the metadata (a JSON document) that Docker maintains for a container.

In this case, the container isn’t running. To determine why, examine the logs for the container:

docker logs wp

That should output something like:

error: missing WORDPRESS_DB_HOST and MYSQL_PORT_3306_TCP environment variablesDid you forget to --link some_mysql_container:mysql or set an external db with -e WORDPRESS_DB_HOST=hostname:port?

You can install MySQL using Docker just like WordPress:

docker run -d --name wpdb \    -e MYSQL_ROOT_PASSWORD=ch2demo \    mysql:5

Once that is started, create a different WordPress container that’s linked to this new database container:

docker run -d --name wp2 \ # Use a unique name    --link wpdb:mysql \    # Create a link to the database    -p 80 --read-only \    wordpress:4

Check one more time that WordPress is running correctly:

docker inspect --format "{{.State.Running}}" wp2

You can tell that WordPress failed to start again. Examine the logs to determine the cause:

docker logs wp2

There should be a line in the logs that is similar to the following:

... Read-only file system: AH00023: Couldn't create the rewrite-map mutex(file /var/lock/apache2/rewrite-map.1)

You need to use a volume to make that exception.

# Start the container with specific volumes for read only exceptionsdocker run -d --name wp3 --link wpdb:mysql -p 80 \    -v /run/lock/apache2/ \  # Create specific volumes for writeable space    -v /run/apache2/ \    --read-only wordpress:4

An updated version of the script you’ve been working on should look like this:

SQL_CID=$(docker create -e MYSQL_ROOT_PASSWORD=ch2demo mysql:5)docker start $SQL_CIDMAILER_CID=$(docker create dockerinaction/ch2_mailer)docker start $MAILER_CIDWP_CID=$(docker create --link $SQL_CID:mysql -p 80 \    -v /run/lock/apache2/ -v /run/apache2/ \    --read-only wordpress:4)docker start $WP_CIDAGENT_CID=$(docker create --link $WP_CID:insideweb \    --link $MAILER_CID:insidemailer \    dockerinaction/ch2_agent)docker start $AGENT_CID

Environment variable injection

docker run --env MY_ENVIRONMENT_VAR="this is a test" \ # Inject an environment variable    busybox:latest \    env                                                # Execute the env command inside the container

The --env flag or -e for short—can be used to inject any environment variable.

You’ll need to use environment variable injection to set the database name for each independent site:

docker create --link wpdb:mysql \    -e WORDPRESS_DB_NAME=client_a_wp wordpress:4 # For client Adocker create --link wpdb:mysql \    -e WORDPRESS_DB_NAME=client_b_wp wordpress:4 # For client B

First, set the computer to run only a single MySQL container:

DB_CID=$(docker run -d -e MYSQL_ROOT_PASSWORD=ch2demo mysql:5)MAILER_CID=$(docker run -d dockerinaction/ch2_mailer)

Then the site provisioning script would be this:

if [ ! -n "$CLIENT_ID" ]; then  # Assume $CLIENT_ID variable is set as input to script    echo "Client ID not set”    exit 1fiWP_CID=$(docker create \    --link $DB_CID:mysql \      # Create link using DB_CID    --name wp_$CLIENT_ID \    -p 80 \    -v /run/lock/apache2/ -v /run/apache2/ \    -e WORDPRESS_DB_NAME=$CLIENT_ID \    --read-only wordpress:4)docker start $WP_CIDAGENT_CID=$(docker create \    --name agent_$CLIENT_ID \    --link $WP_CID:insideweb \    --link $MAILER_CID:insidemailer \    dockerinaction/ch2_agent)docker start $AGENT_CID

2.4

Building durable containers

Automatically restarting containers

Using the --restart flag at container-creation time, you can tell Docker to do any of the following:

  • Never restart (default)
  • Attempt to restart when a failure is detected
  • Attempt for some predetermined time to restart when a failure is detected
  • Always restart the container regardless of the condition

Docker uses an exponential backoff strategy for timing restart attempts.

docker run -d --name backoff-detector --restart always busybox date

Then after a few seconds use the trailing logs feature to watch it back off and restart:

docker logs -f backoff-detector

Keeping containers running with supervisor and startup processes

A supervisor process, or init process, is a program that’s used to launch and maintain the state of other programs. On a Linux system, PID #1 is an init process. It starts all the other system processes and restarts them in the event that they fail unexpectedly.

Using a supervisor process inside your container will keep the container running in the event that the target process—a web server, for example—fails and is restarted.

Start an example container:

docker run -d -p 80:80 --name lamp-test tutum/lampdocker top lamp-test

The top subcommand will show the host PID for each of the processes in the container.

To get that list, run the following exec subcommand:

docker exec lamp-test ps

The process list generated will have listed apache2 in the CMD column:

PID TTY   TIME CMD1 ?       00:00:00 supervisord433 ?     00:00:00 mysqld_safe835 ?     00:00:00 apache2842 ?     00:00:00 ps
docker exec lamp-test kill <PID>

Running this command will run the Linux kill program inside the lamp-test container and tell the apache2 process to shut down. The container logs will clearly show these events:

...... exited: apache2 (exit status 0; expected)... spawned: 'apache2' with pid 820... success: apache2 entered RUNNING state, process has stayed up for >      than 1 seconds (startsecs)

A common alternative to the use of init or supervisor programs is using a startup script that at least checks the preconditions for successfully starting the contained software. These are sometimes used as the default command for the container. For example, the WordPress containers that you’ve created start by running a script to validate and set default environment variables before starting the WordPress process. You can view this script by overriding the default command and using a command to view the contents of the startup script:

docker run wordpress:4 cat /entrypoint.sh

Running that command will result in an error messages like:

error: missing WORDPRESS_DB_HOST and MYSQL_PORT_3306_TCP environment variables...

Entrypoints are perfect places to put code that validates the preconditions of a con- tainer.

docker run --entrypoint="cat" \ # Use "cat" as the entrypoint    wordpress:4 /entrypoint.sh  # Pass /entrypoint.sh as the argument to cat

Cleaning up

To remove a container from your computer, use the docker rm command.

docker rm wp

If you try to remove a container that’s running, paused, or restarting, Docker will display a message like the following:

 Error response from daemon: Conflict, You cannot remove a running container.             Stop the container before attempting removal or use -f        FATA[0000] Error: failed to remove one or more containers

The processes running in a container should be stopped before the files in the con- tainer are removed. You can do this with the docker stop command or by using the -f flag on docker rm. The key difference is that when you stop a process using the -f flag, Docker sends a SIG_KILL signal, which immediately terminates the receiving process. In contrast, using docker stop will send a SIG_HUP signal.

You can avoid the cleanup burden by specifying –rm on the command. Doing so will automatically remove the container as soon as it enters the exited state.

docker run --rm --name auto-exit-test busybox:latest echo Hello Worlddocker ps -a

You should also use the -v flag for reasons.The docker CLI makes it is easy to compose a quick cleanup command:

docker rm -vf $(docker ps -a -q)
0 0