Docker

Table of Contents

1 Docker简介

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后进行发布。

参考:
Introduction to Docker: http://view.dckr.info/DockerIntro.pdf
Get started with Docker: https://docs.docker.com/get-started/
Engine (docker) CLI reference: https://docs.docker.com/engine/reference/commandline/docker/

1.1 Docker VS VM

Docker(准确地说是Docker Container)和VM的区别如图 1 所示。

docker_vs_vm.jpg

Figure 1: Docker Container VS VM

1.2 Docker architecture

Docker采用C/S (client-server)架构。 The Docker client (command 'docker') talks to the Docker daemon, which does the heavy lifting of building, running, and distributing your Docker containers. The Docker client and daemon can run on the same system, or you can connect a Docker client to a remote Docker daemon. The Docker client and daemon communicate using a REST API, over UNIX sockets or a network interface.

docker_architecture.jpg

Figure 2: Docker Architecture

参考:https://docs.docker.com/engine/docker-overview/

1.2.1 Docker基本概念(Image和Container)

An image is a read-only template with instructions for creating a Docker container. Often, an image is based on another image, with some additional customization. For example, you may build an image which is based on the ubuntu image, but installs the Apache web server and your application, as well as the configuration details needed to make your application run.

A container is a runnable instance of an image. You can create, run, stop, move, or delete a container using the Docker API or CLI. You can connect a container to one or more networks, attach storage to it, or even create a new image based on its current state.

总结:可把docker的概念和面向对象概念类比, Image相当于“类”,而Container相当于“对象实例”。

1.3 安装和启动

各个系统上安装docker的过程参见文档:https://docs.docker.com/engine/installation/

1.3.1 以非root用户运行

docker daemon总是以root用户运行。

把当前用户加入到docker组中,可以实现非root用户运行docker client。如:

$ sudo groupadd docker             # 创建docker组
$ sudo usermod -aG docker $USER    # 把当前用户加入到docker组中

参考:https://docs.docker.com/engine/installation/linux/linux-postinstall/#manage-docker-as-a-non-root-user

1.3.2 检测是否安装成功

使用命令 docker version 可以检测docker是否安装成功。如:

$ docker version
Client:
 Version:      17.03.1-ce
 API version:  1.27
 Go version:   go1.7.5
 Git commit:   c6d412e
 Built:        Tue Mar 28 00:40:02 2017
 OS/Arch:      darwin/amd64

Server:
 Version:      17.03.1-ce
 API version:  1.27 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   c6d412e
 Built:        Fri Mar 24 00:00:50 2017
 OS/Arch:      linux/amd64
 Experimental: true

说明:如果docker daemon没有启动,请先启动它,否则上面命令会出错。在Linux中,可以用 dockerd 启动docker daemon。

1.3.3 systemd

用systemd管理docker daemon可以参考:https://docs.docker.com/engine/admin/systemd/

1.4 bash中docker命令的自动补全

利用Bash completion,可以实现docker命令的自动补全。

Mac中,docker命令的自动补全的配置文件在目录/Applications/Docker.app/Contents/Resources/etc/中。

$ ls /Applications/Docker.app/Contents/Resources/etc/
docker-compose.bash-completion docker-machine.bash-completion docker.bash-completion
docker-compose.zsh-completion  docker-machine.zsh-completion  docker.zsh-completion

参考:
Get Bash completion on Docker Mac: http://blog.alexellis.io/docker-mac-bash-completion/

1.5 查看帮助文档

使用 docker --help 可以查看docker帮助文档。
对于具体的命令可以使用 docker COMMAND --help 来查看COMMAND的相关文档。如:

$ docker ps --help

Usage:	docker ps [OPTIONS]

List containers

Options:
  -a, --all             Show all containers (default shows just running)
  -f, --filter filter   Filter output based on conditions provided
      --format string   Pretty-print containers using a Go template
      --help            Print usage
  -n, --last int        Show n last created containers (includes all states) (default -1)
  -l, --latest          Show the latest created container (includes all states)
      --no-trunc        Don't truncate output
  -q, --quiet           Only display numeric IDs
  -s, --size            Display total file sizes

2 Docker基本使用

2.1 第一个docker命令(docker run)

安装好docker后,可以尝试命令 docker run -i -t ubuntu /bin/bash 作为第一次尝试,如:

$ docker run -i -t ubuntu /bin/bash      # 也可以简写为: docker run -i -t ubuntu
root@203471cd80b7:/#

命令 docker run [OPTIONS] IMAGE [COMMAND] [ARG...]] 表示在一个新的Container中运行命令。

运行前面命令时,会执行下面几个步骤:
(1) 首先检测本地有没有名为ubuntu的Image,如果没有则从Registry中下载(相当于执行 docker pull ubuntu )。
(2) 然后创建一个新的Container,相当于执行 docker create
(3) Docker allocates a read-write filesystem to the container, as its final layer.
(4) Docker creates a network interface to connect the container to the default network, since you did not specify any networking options.
(5) Docker starts the container and executes /bin/bash. Because the container is run interactively (due to -i flag) and attached to your terminal (due to -t flag), you can provide input using your keyboard and output is logged to your terminal.
(6) When you type exit to terminate the /bin/bash command, the container stops.

说明:如果你是第一次运行上面命令,得到的输出可能为:

$ docker run -i -t ubuntu /bin/bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
c62795f78da9: Pull complete
d4fceeeb758e: Pull complete
5c9125a401ae: Pull complete
0062f774e994: Pull complete
6b33fd031fac: Pull complete
Digest: sha256:c2bbf50d276508d73dd865cda7b4ee9b5243f2648647d21e3a471dd3cc4209a0
Status: Downloaded newer image for ubuntu:latest
root@910b355177d5:/#

进入到Container的交互终端后,你可以在Container中输入shell命令了。不过,这个ubuntu仅包含一些必要的组件,很多命令(如vi等)没有默认提供(你可以通过apt-get安装)。如:

$ docker run -i -t ubuntu /bin/bash
root@203471cd80b7:/# ps
  PID TTY          TIME CMD
    1 ?        00:00:00 bash
   10 ?        00:00:00 ps
root@203471cd80b7:/# ls
bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
boot  etc  lib   media  opt  root  sbin  sys  usr
root@203471cd80b7:/# vi
bash: vi: command not found

参考:
https://docs.docker.com/engine/docker-overview/
https://docs.docker.com/engine/reference/commandline/run/

2.1.1 自定义容器名字

使用 --name 选项可以为容器自定义命名,如:

$ docker run --name myname -it ubuntu /bin/bash              # 命名容器为myname

容器名称是唯一的。如果已经命名了一个叫name1的容器,当你要再次使用name1这个名称时,需要先用 docker rm 来删除之前创建的同名容器。不过,在执行 docker run 的时候如果添加 --rm 标记,则容器在终止后会立刻删除,这可避免名字冲突。

$ docker run --name myname --rm -it ubuntu /bin/bash         # --rm 容器终止后会立刻删除掉容器

2.2 基本docker命令

2.2.1 查看本地的Image (docker images)

使用命令 docker images 可以列出本地系统中存在的Images。如:

$ docker images
REPOSITORY                     TAG                  IMAGE ID            CREATED             SIZE
ubuntu                         latest               6a2f32de169d        4 days ago          117 MB
2.2.1.1 查找远程Registry中的Images (docker search)

使用命令 docker search 可以查找远程Registry中的Images。如:

$ docker search nginx
NAME                                     DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
nginx                                    Official build of Nginx.                        5788      [OK]
jwilder/nginx-proxy                      Automated Nginx reverse proxy for docker c...   998                  [OK]
richarvey/nginx-php-fpm                  Container running Nginx + PHP-FPM capable ...   367                  [OK]
jrcs/letsencrypt-nginx-proxy-companion   LetsEncrypt container to use with nginx as...   165                  [OK]
webdevops/php-nginx                      Nginx with PHP-FPM                              77                   [OK]
million12/nginx-php                      Nginx + PHP-FPM 5.5, 5.6, 7.0 (NG), CentOS...   77                   [OK]
h3nrik/nginx-ldap                        NGINX web server with LDAP/AD, SSL and pro...   38                   [OK]
bitnami/nginx                            Bitnami nginx Docker Image                      26                   [OK]
evild/alpine-nginx                       Minimalistic Docker image with Nginx            15                   [OK]
webdevops/nginx                          Nginx container                                 7                    [OK]
1science/nginx                           Nginx Docker images that include Consul Te...   4                    [OK]
blacklabelops/nginx                      Dockerized Nginx Reverse Proxy Server.          4                    [OK]
frekele/nginx                            docker run --rm --name nginx -p 80:80 -p 4...   3                    [OK]
ixbox/nginx                              Nginx on Alpine Linux.                          3                    [OK]
dock0/nginx                              Arch container running nginx                    2                    [OK]
servivum/nginx                           Nginx Docker Image with Useful Tools            2                    [OK]
drupaldocker/nginx                       NGINX for Drupal                                2                    [OK]
xataz/nginx                              Light nginx image                               2                    [OK]
tozd/nginx                               Dockerized nginx.                               1                    [OK]
tianon/nginx                             DEPRECATED; use nginx:*                         1                    [OK]
xutongle/nginx                           nginx http                                      1                    [OK]
unblibraries/nginx                       Baseline non-PHP nginx container                0                    [OK]
c4tech/nginx                             Several nginx images for web applications.      0                    [OK]
watsco/nginx                             nginx:1.11-alpine                               0                    [OK]
funkygibbon/nginx                        nginx + openssl automated build, customisa...   0                    [OK]

2.2.2 查看系统中的Container (docker ps)

使用命令 docker ps 可以列出系统中正在运行的Containers。如:

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
aef40053a33e        ubuntu              "/bin/bash"         40 seconds ago      Up 38 seconds                           festive_newton

命令 docker ps -q ,仅显示Containers的ID,其它字段不显示。如:

$ docker ps -q
aef40053a33e

命令 docker ps -l ,仅显示上一个启动的Containers。如:

$ docker ps -l
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
aef40053a33e        ubuntu              "/bin/bash"         3 minutes ago       Up 3 minutes                            festive_newton

命令 docker ps -a ,可以把已经处于stopped状态(当Container的最后一个程序退出后,就处于stopped状态了)的Containers也显示出来。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
aef40053a33e        ubuntu              "/bin/bash"         14 hours ago        Exited (0) 14 hours ago                         festive_newton
203471cd80b7        ubuntu              "/bin/bash"         17 hours ago        Exited (0) 16 hours ago                         condescending_swartz
910b355177d5        ubuntu              "/bin/bash"         18 hours ago        Exited (0) 17 hours ago                         clever_montalcini
a87a3eb6b0d0        ubuntu              "/bin/bash"         18 hours ago        Exited (127) 18 hours ago                       hopeful_lovelace
a6e2bc3ec12d        ubuntu              "/bin/bash"         18 hours ago        Exited (0) 18 hours ago                         quizzical_dubinsky
48c93b69807a        ubuntu              "/bin/bash"         22 hours ago        Exited (0) 22 hours ago                         ecstatic_gates
5f43a09eb4a5        ubuntu              "/bin/bash"         22 hours ago        Exited (0) 22 hours ago                         nifty_albattani

2.2.3 重新启动Stopped containers (docker start)

在Container的最后一个交互终端中exit后,Container会进入stopped状态。

$ docker run -i -t ubuntu /bin/bash
root@c3a1518ef18f:/# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 02:21 ?        00:00:00 /bin/bash
root        67     1  0 03:20 ?        00:00:00 ps -ef
root@c3a1518ef18f:/# exit       # 在最后一个shell中执行exit后,container会进入stopped状态
exit
$ docker ps -al
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
c3a1518ef18f        ubuntu              "/bin/bash"         About an hour ago   Exited (0) 1 minutes ago                       wizardly_curran

使用命令 docker start 可以启动一个stopped状态的Container。如:

$ docker start c3a1518ef18f
c3a1518ef18f
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
c3a1518ef18f        ubuntu              "/bin/bash"         About an hour ago   Up 9 seconds                            wizardly_curran

接着,可以使用命令docker attach进入到其交互终端。如:

$ docker attach c3a1518ef18f
root@c3a1518ef18f:/#

如果想启动Stopped containers的同时进入到其交互终端,可以指定 --attach 选项(或者 -a )。如:

$ docker start --attach c3a1518ef18f
root@c3a1518ef18f:/#

2.2.4 在Container和host机器之间复制文件 (docker cp)

可以使用 docker cp 命令在Container和host机器之间复制文件。如:

$ docker cp c3a1518ef18f:/tmp/file1 .          # 把container中的文件/tmp/file1复制到host机器的当前目录中
$ docker cp file2 c3a1518ef18f:/tmp/           # 把host机器的当前目录中的file2复制到container中的/tmp/目录中

2.3 从外部访问容器内网络应用(-P, -p)

启动容器时,如果不指定对应参数,在容器外部是无法通过网络来访问容器内的网络应用的。

要让外部访问容器内的网络应用,需要通过 -P 进行自动端口配置。如:

$ docker run -d -P nginx
$ docker ps                  # 输出中有端口的映射关系
CONTAINER ID IMAGE ... PORTS ...
e40ffb406c9e nginx ... 0.0.0.0:32769->80/tcp, 0.0.0.0:32768->443/tcp ...

也可以通过 -p port-on-host:port-on-container 参数来手动指定端口映射。

$ docker run -d -p 8000:80 nginx

3 Background Containers

可以在执行 docker run 时指定 --detach 选项(可简写为 -d ),把Container设置在后台运行。如:

$ docker run -d -i -t ubuntu /bin/bash
c3a1518ef18f2d7e82de6f10a9261d74f5220de758f84148968547c70e54b131
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
c3a1518ef18f        ubuntu              "/bin/bash"         7 minutes ago       Up 7 minutes                            wizardly_curran

3.1 把后台Container放到前台 (docker attach)

使用命令 docker attach 可以把后台Container放到前台执行。如:

$ docker attach c3a1518ef18f
root@c3a1518ef18f:/#

注:有时Container中正在运行的程序并不是一个交互式的shell,这时你把它放到前台执行也得不到一个交互式shell。通过下面命令可以开户一个新的交互式shell:

$ docker exec -it [container-id] bash

参考:https://stackoverflow.com/questions/20932357/docker-enter-running-container-with-new-tty

3.2 把Container放到后台 (^P^Q)

除了在启动时直接把Container放到后台外,我们也可以在Container的交互终端上输入快捷键^P^Q (即Ctrl+p, Ctrl+q),把前台的Container放入到后台中。

通过执行 docker run 时指定 --detach-keys 选项,可以定制这个快捷键。如下面命令可以定制把Container放到后台的快捷键为“Ctrl+x, x”:

$ docker run -d -i -t --detach-keys ctrl-x,x ubuntu /bin/bash

注意:如果你在启动Container时没有指定 -i -t ,那么你无法通过快捷键^P^Q把Container放到后台外,这时你杀掉docker client进程可以把对应的Container放到后台。

3.3 在Container中执行命令 (docker exec)

当Container在后台执行时,如果想在Container中执行命令,我们可以先用 docker attach 把它放到前台,这时可以提交命令,执行完后,可以用快捷键^P^Q把Container再次放入后台。

我们还可以用命令 docker exec 直接向后台Container中提交命令执行。注:也可以向前台Container提交命令执行,在host机器中打开另外一个终端,执行 docker exec 即可。

$ docker exec -i -t c3a1518ef18f touch /tmp/file1
$ docker exec -i -t c3a1518ef18f ls /tmp/file1
/tmp/file1

3.4 停止Container (docker kill/stop)

There are two ways we can terminate our detached container:
(1) Killing it using the docker kill command.
(2) Stopping it using the docker stop command.
The first one stops the container immediately, by using the KILL signal (the KILL signal cannot be intercepted).
The second one is more graceful. It sends a TERM signal, and after 10 seconds, if the container has not stopped, it sends KILL.

4 Building Images Interactively

下面将介绍如何建立一个新的Image。

4.1 从Container中建立一个新的Image (docker commit/diff)

首先,我们启动一个全新的Container,并在Container中增加一个文件。如:

$ docker run -i -t ubuntu /bin/bash
root@89342bc98066:/# echo "test" > /tmp/file1
root@89342bc98066:/# exit
exit

通过命令 docker diff 可以显示出对Container的所有改变。如:

$ docker diff 89342bc98066          #查看Container 89342bc98066的改变
C /root
A /root/.bash_history
C /tmp
A /tmp/file1

The docker commit command will create a new layer with those changes, and a new image using this new layer.

$ docker commit 89342bc98066                      #这里指定Container ID
sha256:d5c209fda61e4bd20f460c3b76ae1430b0ba361db7ccfe6d22f09c2ffa7ea511    #返回的是新Image的ID

通过 docker images ,可以看到刚刚新建立的Image。如:

$ docker images
REPOSITORY                     TAG                  IMAGE ID            CREATED             SIZE
<none>                         <none>               d5c209fda61e        2 seconds ago       117 MB
ubuntu                         latest               6a2f32de169d        5 days ago          117 MB

现在,我们可以基于这个新的Image来启动Container。如:

$ docker run -i -t d5c209fda61e /bin/bash
root@46cd0d36b923:/# cat /tmp/file1
test

4.1.1 Tagging images

用ID来引用新创建的Image不是很方便,我们可以给Image指定一个tag。这可以通过命令 docker tag 来实现。如:

$ docker images     # 下面是运行 docker tag 前的输出
REPOSITORY                     TAG                  IMAGE ID            CREATED             SIZE
<none>                         <none>               d5c209fda61e        2 seconds ago       117 MB
ubuntu                         latest               6a2f32de169d        5 days ago          117 MB
$ docker tag d5c209fda61e mytestimage   # 运行 docker tag,为ID为d5c209fda61e的Image指定tag名mytestimage
$ docker images     # 下面是运行 docker tag 后的输出
REPOSITORY                     TAG                  IMAGE ID            CREATED             SIZE
mytestimage                    latest               d5c209fda61e        28 minutes ago      117 MB
ubuntu                         latest               6a2f32de169d        5 days ago          117 MB

使用docker run时,指定Images的tag名字可以方便地(比指定Images的ID要更方便)新建Container。如:

$ docker run -i -t mytestimage /bin/bash
root@1a972f6a8af6:/#

当然,你可以在用 docker images 命令新建Image时就指定tag名字。

5 Building Images With A Dockerfile

为了更好地把创建Images的过程自动化,我们建立Image的步骤放入Dockerfile中,然后用命令 docker build 来自动生成Images。

参考:
Dockerfile reference: https://docs.docker.com/engine/reference/builder/
Best practices for writing Dockerfiles: https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/
Dockerfile 最佳实践:http://blog.csdn.net/candcplusplus/article/details/53366024

5.1 一个简单的Dockerfile

下面是利用Dockerfile创建Image的简单实例。

首先,新建一个目录(如myimage),在这个目录中创建好Dockerfile

$ mkdir myimage
$ cd myimage
$ vim Dockerfile

Dokerfile的内容如下:

# A simple Dockerfile
FROM ubuntu
RUN echo "test" > /tmp/file1

docker build 命令即可建立一个新Image。如:

$ docker build -t mytestimage2 .       # 同时指定的新Image的tag
Sending build context to Docker daemon 2.048 kB
Step 1/2 : FROM ubuntu
 ---> 6a2f32de169d
Step 2/2 : RUN echo "test" > /tmp/file1
 ---> Running in 12b18a5d8df4
 ---> 7cda0e79c53e
Removing intermediate container 12b18a5d8df4
Successfully built 7cda0e79c53e

运行成功结束后,用 docker images 命令可以看到刚刚建立的Image。如:

$ docker images
REPOSITORY                     TAG                  IMAGE ID            CREATED             SIZE
mytestimage2                   latest               7cda0e79c53e        7 minutes ago       117 MB
mytestimage                    latest               d5c209fda61e        About an hour ago   117 MB
ubuntu                         latest               6a2f32de169d        5 days ago          117 MB

5.2 基本格式

Dockerfile不区分大小写,但还是约定指令使用大写。
# 开头的是注释,行内的 # 都被当做参数(不是Dockerfile注释)。

5.3 常用Instruction

5.3.1 FROM

FROM指令用于指定当前构造image的base image,它要位于Dockerfile中除空白和注释外的第一行。如:

FROM ubuntu
FROM ubuntu:12.04
FROM training/sinatra
FROM localhost:5000/funtoo

5.3.2 RUN

RUN用于在image构建过程中执行特定的命令,并生成一个(一层)中间镜像。

RUN有两种格式:

RUN <command>
RUN ["executable", "param1", "param2"]

第一种称为shell格式(Linux中默认使用 sh -c 来执行命令),第二种称为exec格式。

shell格式的例子:

RUN apt-get update

由于shell格式默认使用sh执行命令,所以上面命令相当于:

RUN ["sh", "-c", "apt-get update"]

exec格式的例子:

RUN ["apt-get", "update"]

说明1:后面介绍的CMD和ENTRYPOINT也有这两种格式。

说明2:exec格式中不会启动shell。

# 下面命令是exec格式,不会启动shell,所以没有shell变量替换,会直接输出字符串$HOME。
RUN [ "echo", "$HOME" ]

下面两种方式可以显示展开后的$HOME:

RUN echo "$HOME"
RUN [ "sh", "-c", "echo $HOME" ]

说明1:exec格式后面接的是字符串数组,它采用的JSON格式。也就是说命令 RUN ["apt-get", "update"] 中, ["apt-get", "update"] 部分应该是一个合法的JSON格式。违反JSON格式的常见错误有:

# 下面写法是错误的,因为RUN(exec格式)后的不是合法JSON格式(JSON中要求字符串用双引号,而不能是单引号)
RUN ['apt-get', 'update']
# 下面写法是错误的,字符\在JSON中有特殊含义,应该转义,正确写法为:RUN ["c:\\windows\\system32\\tasklist.exe"]
RUN ["c:\windows\system32\tasklist.exe"]
5.3.2.1 最佳实践(Cache Busting)

永远将RUN apt-get update和apt-get install组合成一条RUN声明 ,例如:

RUN apt-get update && apt-get install -y \
        package-bar \
        package-baz \
        package-foo

设你有一个Dockerfile文件:

# 这是不推荐的写法
FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y curl

构建镜像后,所有的层都在 Docker 的缓存中。假设你后来又修改了其中的apt-get install,添加了一个包,变为了:

# 这是不推荐的写法
FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y curl nginx

Docker重新构建时,发现修改后的RUN apt-get update指令和之前的完全一样。所以,apt-get update不会执行,而是使用之前的缓存镜像。因为apt-get update没有运行,后面的apt-get install可能安装的是过时的curl和nginx版本。 下面是推荐的写法:

# 这是推荐的写法
RUN apt-get update && apt-get install -y \
    curl \
    nginx

由于一个RUN命令会生成一个(一层)中间镜像,所以为了减少image大小,不推荐使用两个RUN命令:

# 这是不推荐的写法,没必要增加一个(一层)中间镜像,应该合并为一个RUN
RUN apt-get update && apt-get install -y curl
RUN apt-get update && apt-get install -y nginx

5.3.3 CMD

CMD指令用于指定 docker run 时默认执行的命令。 如果一个Dockerfile文件中有多个CMD,则只有最后一个CMD指令会生效。
CMD仅仅是提供一个默认命令,如果执行 docker run 时指定了程序(如 docker run -i -t ubuntu /bin/bash 指定了执行程序 /bin/bash ),则会执行命令行中指定的程序,CMD中指定的命令不会被执行。

下面的例子中将使用 figlet (一个把显示字符为大号的特殊形式的小程序)来测试CMD和ENTRYPOINT的使用。下面是figlet的简单例子:

$ figlet -f script hello
 _          _   _
| |        | | | |
| |     _  | | | |  __
|/ \   |/  |/  |/  /  \_
|   |_/|__/|__/|__/\__/

假设你有下面Dockerfile:

FROM ubuntu
RUN apt-get update
RUN ["apt-get", "install", "figlet"]
CMD figlet -f script hello

根据这个Dockerfile构建一个image,如:

$ docker build -t myfiglet .
...
Successfully built 042dff3b4a8d

运行这个image,如:

$ docker run myfiglet             # docker run时没有在命令行指定程序,默认会执行CMD中指定的命令(即 figlet -f script hello )。
 _          _   _
| |        | | | |
| |     _  | | | |  __
|/ \   |/  |/  |/  /  \_
|   |_/|__/|__/|__/\__/

不过,一旦我们在执行 docker run 在命令行指定了某个程序(下面例子中这个程序是bash),则CMD中指定的默认命令不会被执行。如:

$ docker run -it myfiglet bash    # 会执行bash,而不是CMD指定的默认命令。
root@7ac86a641116:/#

注:当指定了ENTRYPOINT后,CMD的含义会发生改变,后方将说明。

5.3.4 ENTRYPOINT

ENTRYPOINT的最佳用处是设置镜像的主命令,允许将镜像当成命令一样来运行。

我们接着上面的例子来介绍ENTRYPOINT。比如我们想要像下面这样使用image(这使得镜像变和像命令一样使用):

$ docker run myfiglet salut      # “命令”是在Dockerfile中配置的,而“命令的参数”是在命令行中指定的。相当于执行figlet -f script salut
           _       _
 ___  __ _| |_   _| |_
/ __|/ _` | | | | | __|
\__ \ (_| | | |_| | |_
|___/\__,_|_|\__,_|\__|

在Dockerfile中用ENTRYPOINT即可实现这个效果。如:

FROM ubuntu
RUN apt-get update
RUN ["apt-get", "install", "figlet"]

# 下面这行指定了“部分的”的默认执行命令,docker run时指定的命令会作为参数追加到ENTRYPOINT所指定的命令上。
ENTRYPOINT ["figlet", "-f", "script"]

假设编译出来的image名为myfiglet。运行 docker run myfiglet salut 相当于在image中运行 figlet -f script salut

注:和CMD类似,如果指定多个ENTRYPOINT,则只有最后一个生效。

5.3.4.1 CMD和ENTRYPOINT一起使用

当指定了ENTRYPOINT后,CMD的含义就发生了改变,不再是直接的运行其命令,而是将CMD的内容作为参数传给ENTRYPOINT指令 ,换句话说实际执行时,将变为:

<ENTRYPOINT> "<CMD>"

比如,有下面Dockerfile:

FROM ubuntu
RUN apt-get update
RUN ["apt-get", "install", "figlet"]

ENTRYPOINT ["figlet", "-f", "script"]
CMD ["hello world"]

假设编译出来的image名为myfiglet。运行 docker run myfiglet 相当于在image中运行 figlet -f script "hello world"

注: docker run 时指定 --entrypoint 参数可以覆盖Dockerfile中ENTRYPOINT中的设置。如:

$ docker run -it --entrypoint bash myfiglet    # 会执行bash,而不会执行figlet。--entrypoint会覆盖Dockerfile中ENTRYPOINT中的设置
root@6027e44e2955:/#

上面命令中“–entrypoint bash”必须在images名字“myfiglet”的前面。下面是错误的用法:

$ docker run -it myfiglet --entrypoint bash    # 这是错误的用法!--entrypoint bash不会生效。

5.3.5 COPY和ADD

COPY指令可以复制文件到image中,复制过程中源文件的各种元数据(比如读、写、执行权限、文件变更时间等)都会保留。

COPY指令有两种格式:

COPY <src>... <dest>
COPY ["<src>",... "<dest>"]

如果路径名中包含空格,则必须使用第二种格式。

COPY的源文件是相对于Dockerfile所在目录,比如:

COPY /bin/ /tmp/
# 上面命令不是复制本地系统的/bin目录,而是复制Dockerfile所在目录的bin子目录,相当于下面命令
COPY bin/ /tmp/

“<src>”可以是多个,甚至可以是通配符,其通配符规则要满足Go的 filepath.Match 规则,如:

COPY hom* /mydir/
COPY hom?.txt /mydir/

“<dest>”可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。目标路径不需要事先创建,如果目录(或子目录)不存在会在复制文件前先行创建它们。

ADD和COPY类似,只不过是增加一些高级功能。比如复制本地文件中自动解压:

# 下面指令会使foo.tar.gz压缩文件解压到容器的/tmp目录
ADD /foo.tar.gz /tmp/
# 下面指令并不会解压bar.tar.gz,因为它不是本地文件
ADD http://foo.com/bar.tar.gz /tmp/

Docker官方的最佳实践文档中提到, 尽量地使用COPY,而尽量不使用ADD ,因为COPY的语义很明确,就是复制文件而已,而ADD则包含了更复杂的功能,其行为也不一定很清晰。最适合使用ADD的场合,就是所提及的需要自动解压缩的场合。

5.3.6 ENV

ENV的作用是设置环境变量,其后的其它指令(如RUN等),还是运行时的应用程序,都可以直接使用用ENV指令定义的环境变量。

如官方node镜像的Dockerfile中,就有类似这样的代码:

ENV NODE_VERSION 7.2.0

RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
  && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
  && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
  && grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
  && tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
  && rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
  && ln -s /usr/local/bin/node /usr/local/bin/nodejs

注:用 docker run-e 选项也可以设置环境变量,如:

$ docker run -e 'MyEnv=MyValue' ubuntu bash '-c' 'echo $MyEnv'
MyValue

5.3.7 ARG(使–build-arg中定义的参数在RUN指令中可用)

假设执行 docker build 时指定了 --build-arg ,如:

$ docker build --build-arg myarg=myvalue .

在Dockerfile中,可以使用 ARG 指令使–build-arg中定义的参数在RUN指令中可用。

ARG myarg
RUN echo $myarg   # 输出 myvalue

注:ARG和ENV类似,它们设置的变量在RUN命令中都能使用。和ENV不同的是, ARG所设置的变量,在以后容器运行时就不存在了,只能在构造过程中使用。

5.3.7.1 Predefined ARGs

下面这些变量是内置的ARG,用户不用再显式地指定了:

HTTP_PROXY
http_proxy
HTTPS_PROXY
https_proxy
FTP_PROXY
ftp_proxy
NO_PROXY
no_proxy

5.3.8 EXPOSE

EXPOSE指令可以声明运行时容器提供服务端口,比如提供Web服务的镜像将使用EXPOSE 80,而提供MongoDB服务的镜像使用EXPOSE 27017。注:它仅仅是声明,不会主动开启端口。这样,当使用 docker run -P 时,通过EXPOSE指定的端口会映射为容器外可访问。

EXPOSE 8080
EXPOSE 80 443
EXPOSE 53/tcp 53/udp

注:就算没有通过EXPOSE声明端口,使用 docker run -p <port> 也可以映射这个端口为外部可访问。

6 Docker CLI

6.1 docker logs

docker logs 可以获取Container的日志。你可以通过它查看Container默认启动命令(可由Dockerfile中CMD或ENTRYPOINT指定)的输出。

6.2 docker inspect

docker inspect 命令显示docker容器或者image的相关信息。它返回是的JSON格式的信息。

比如,我们想要查看docker中container的IP:

$ docker inspect  a30fb0bc1985 |grep IPAddress
            "SecondaryIPAddresses": null,
            "IPAddress": "172.17.0.2",
                    "IPAddress": "172.17.0.2",

使用grep在JSON数据中查找信息不够精确,我们可以通过 --format 参数指定“Go模版”来精确地找到container的IP,如:

$ docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'  a30fb0bc1985

6.3 docker top

docker top 命令可以查看Container中运行的进程。有些Container没有包含top命令(甚至ps命令也没有),这里docker top就很有用了。

6.4 docker export/import(导出/导入Container快照)

使用 docker export 命令,可以导出Container快照到tar文件。如:

$ docker ps -al
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                        PORTS               NAMES
1a972f6a8af6        mytestimage         "/bin/bash"         48 minutes ago      Exited (127) 27 minutes ago                       eager_wescoff
$ docker export 1a972f6a8af6 > mytestimage.tar

使用 docker import 命令,可以把Container快照文件导入为Image。如:

$ docker import mytestimage.tar test/ubuntu:v1.0
sha256:a795cfb6e4a781c2a6a56fcb7c67fb885a6a447cd1c4e8bfe231786f2c967e02
$ docker images
REPOSITORY                     TAG                  IMAGE ID            CREATED                  SIZE
test/ubuntu                    v1.0                 a795cfb6e4a7        Less than a second ago   97.3 MB
mytestimage2                   latest               7cda0e79c53e        23 minutes ago           117 MB
mytestimage                    latest               d5c209fda61e        About an hour ago        117 MB
ubuntu                         latest               6a2f32de169d        5 days ago               117 MB

6.5 docker save/load(保存和加载Image)

用导出(docker expor)后再导入(docker import)的方式会丢失所有的历史;而保存(docker save)后再加载(docker load)的镜像不会丢失历史和层(layer)。这意味着使用导出后再导入的方式,你将无法回滚到之前的层(layer),而使用保存后再加载的方式持久化整个镜像,就可以做到层回滚。

6.6 docker system

6.6.1 docker system df(查看空间使用)

使用 docker system df 命令可查询镜像(Images)、容器(Containers)和本地卷(Local Volumes)的空间占用情况。如:

$ docker system df
TYPE                TOTAL               ACTIVE              SIZE                RECLAIMABLE
Images              5                   2                   16.43 MB            11.63 MB (70%)
Containers          2                   0                   212 B               212 B (100%)
Local Volumes       2                   1                   36 B                0 B (0%)

命令 docker system df 后增加参数 -v 可显示更多的信息。如:

$ docker system df -v
Images space usage:

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE                SHARED SIZE         UNIQUE SIZE         CONTAINERS
my-curl             latest              b2789dd875bf        6 minutes ago       11 MB               11 MB               5 B                 0
my-jq               latest              ae67841be6d0        6 minutes ago       9.623 MB            8.991 MB            632.1 kB            0
<none>              <none>              a0971c4015c1        6 minutes ago       11 MB               11 MB               0 B                 0
alpine              latest              4e38e38c8ce0        9 weeks ago         4.799 MB            0 B                 4.799 MB            1
alpine              3.3                 47cf20d8c26c        9 weeks ago         4.797 MB            4.797 MB            0 B                 1

Containers space usage:

CONTAINER ID        IMAGE               COMMAND             LOCAL VOLUMES       SIZE                CREATED             STATUS                      NAMES
4a7f7eebae0f        alpine:latest       "sh"                1                   0 B                 16 minutes ago      Exited (0) 5 minutes ago    hopeful_yalow
f98f9c2aa1ea        alpine:3.3          "sh"                1                   212 B               16 minutes ago      Exited (0) 48 seconds ago   anon-vol

Local Volumes space usage:

NAME                                                               LINKS               SIZE
07c7bdf3e34ab76d921894c2b834f073721fccfbbcba792aa7648e3a7a664c2e   2                   36 B
my-named-vol                                                       0                   0 B

6.6.2 docker system prune(清理无用空间)

使用 docker system prune 命令可以清理docker占用的无用空间。默认地,它会删除:所有已停止的容器、所有未被任何容器所使用的卷、所有未被任何容器所使用的网络、所有悬空的镜像(没有任何Tag的镜像)。如:

$ docker system prune
WARNING! This will remove:
	- all stopped containers
	- all volumes not used by at least one container
	- all networks not used by at least one container
	- all dangling images
Are you sure you want to continue? [y/N] y
Deleted Containers:
......

7 Docker Compose简介

Compose 是一个“定义和运行多个Docker容器的应用”的工具。 Compose项目是Docker官方的开源项目,负责实现对Docker容器集群的快速编排。

在工作中,经常需要多个容器相互配合来完成某项任务。例如,一个Web项目,除了Web服务容器本身,往往还有后端的数据库服务容器、负载均衡容器等等。Compose可以满足了这样的需求,它允许用户通过一个 docker-compose.yml 模板文件来定义一组相互关联的应用容器(称为一个project)。

参考:
Overview of Docker Compose: https://docs.docker.com/compose/overview/

7.1 docker-compose.yml配置文件

7.1.1 入门例子

关于docker-compose的入门例子可以参考:Get started with Docker Compose

7.1.2 默认创建网络

默认地,使用 docker-compose up 时,会根据“project name”来创建一个网络。“project name”默认为docker-compose.yml文件所在文件夹的名字,不过“project name”还可以通过docker-compose的命令行参数-p或者 COMPOSE_PROJECT_NAME 环境变量来指定。

比如,在目录myapp中,有下面docker-compose.yml文件:

version: '3'

services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
volumes:
    db_data:

运行 docker-compose up 命令时,会发生下面事情:

  1. A network called myapp_default is created.
  2. A container is created using db’s configuration. It joins the network myapp_default under the name db.
  3. A container is created using wordpress’s configuration. It joins the network myapp_default under the name wordpress.

这样,网络myapp_default中有两个运行容器,在db对应的容器中,可以通过名字wordpress访问另外一个容器,反之亦然。

注:如果运行 docker-compose -p xxx up ,则创建的网络名字会为xxx_default,而不是myapp_default。

参考:
https://docs.docker.com/compose/networking/

7.1.3 加入到存在的网络(而不是创建网络)

如果不想加入到默认网络,也可以定制docker-compose.yml文件中service对应container所加入的网络。

下面的例子中,容器web和db都会加入到网络your_network_name(这个网络必须事先存在)中。

version: "3"

networks:
  net1:
    external:
      name: your_network_name

services:
  web:
    build: .
    ports:
      - "8000:8000"
    networks:
       - net1
  db:
    image: postgres
    ports:
      - "8001:5432"
    networks:
       - net1

上面docker-compose.yaml文件中的your_network_name必须是存在的网络名字(通过 docker network ls 可以检查一下是否存在),可以通过 docker network create your_network_name 来创建。

参考:https://docs.docker.com/compose/networking/#using-a-pre-existing-network

7.2 docker-compose常用命令

Table 1: Docker Compose常用命令
命令 说明
build Build or rebuild services
bundle Generate a Docker bundle from the Compose file
config Validate and view the Compose file
create Create services
down Stop and remove containers, networks, images, and volumes
events Receive real time events from containers
exec Execute a command in a running container
help Get help on a command
images List images
kill Kill containers
logs View output from containers
pause Pause services
port Print the public port for a port binding
ps List containers
pull Pull service images
push Push service images
restart Restart services
rm Remove stopped containers
run Run a one-off command
scale Set number of containers for a service
start Start services
stop Stop services
top Display the running processes
unpause Unpause services
up Create and start containers
version Show the Docker-Compose version information

参考: docker-compose --help

8 Tips

8.1 设置Container和host机器之间的共享目录

可以使用run命令的 -v /outside/dir:/container/dir (注:两个目录都必须是绝对路径)参数来设置Container和host机器之间的共享目录。如:

$ docker run -v /Users/cig01/test/:/test -it ubuntu /bin/bash

8.2 stop所有container

$ docker stop $(docker ps -a -q)

8.3 删除所有images

$ docker rmi -f $(docker images -a -q)

8.4 删除正在运行container的log

使用下面命令可以删除正在运行container的log:

$ sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' <container_name_or_id>)

上面命令执行完后,使用 docker logs <container_name_or_id> 可验证log确实被清空了。

8.5 通过image名字找到所有运行的container

$ docker ps -q  --filter ancestor=<image-name>

8.6 限制container使用CPU资源

比如,限制container只能使用0.75个CPU资源(可能很慢):

$ docker run -it --cpus="0.75" ubuntu /bin/bash

参考:https://docs.docker.com/engine/admin/resource_constraints/


Author: cig01

Created: <2017-04-01 Sat 00:00>

Last updated: <2018-05-04 Fri 18:46>

Creator: Emacs 25.3.1 (Org mode 9.1.4)