Docker
Table of Contents
- 1. Docker 简介
- 2. Docker 基本使用
- 3. Background Containers
- 4. Building Images Interactively
- 5. Building Images With A Dockerfile
- 6. Docker CLI
- 7. Docker Compose 简介
- 8. Tips
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 所示。
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.
Figure 2: Docker Architecture
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/install/
1.3.1. 以非 root 用户运行
docker daemon 总是以 root 用户运行。
把当前用户加入到 docker 组中,可以实现非 root 用户运行 docker client。如:
$ sudo groupadd docker # 创建docker组 $ sudo usermod -aG docker $USER # 把当前用户加入到docker组中
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 命令
参考:
Use the Docker command line: https://docs.docker.com/engine/reference/commandline/cli/
Docker Cheat Sheet: https://github.com/wsargent/docker-cheat-sheet
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(相当于mytestimage:latest) $ 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"
。
5.3.4.2. 覆盖 ENTRYPOINT 设置
执行 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 的源文件是相对于 docker build 的上下文(比如执行 docker build .
时,编译上下文就是当前目录;执行 docker build dir1
时,编译上下文就是目录 dir1),比如:
COPY /bin/ /tmp/ # 上面命令不是复制本地系统的 /bin目录,而是复制 docker build 所指定目录的 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>
也可以映射这个端口为外部可访问。
5.4. Multi-stage Build
从 Docker 17.05 起,我们可以在一个容器中编译,再复制编译结果到另外一个容器中,这个功能称为“Multi-stage Build”。编译产生的中间文件等仅在编译容器中。
下面是一个例子,在容器 maven:3.6-jdk-11 中编译一个 maven 工程,再把编译得到的 war 包复制到容器 tomcat:9.0 中:
FROM maven:3.6-jdk-11 COPY src /usr/src/app/src COPY pom.xml /usr/src/app RUN mvn -f /usr/src/app/pom.xml clean package FROM tomcat:9.0 COPY --from=0 /usr/src/app/target/your-pkg.war /usr/local/tomcat/webapps/
上面命令中 COPY --from=0
表示从第 1 个(编号从数字 0 开始)容器中复制。
当然,你可以为编译容器指定一个名字,如起名为 builder 或者其它( FROM
指令中加个 AS <NAME>
参数即可):
FROM maven:3.6-jdk-11 AS builder COPY src /usr/src/app/src COPY pom.xml /usr/src/app RUN mvn -f /usr/src/app/pom.xml clean package FROM tomcat:9.0 COPY --from=builder /usr/src/app/target/your-pkg.war /usr/local/tomcat/webapps/
参考:https://docs.docker.com/develop/develop-images/multistage-build/
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: ......
6.7. docker history(查看 Docker Image 构建过程及每一层大小)
使用 docker history
可查看 Docker Image 的构建过程及每一层大小,如:
docker history redis:alpine IMAGE CREATED CREATED BY SIZE COMMENT 500703a12fa4 10 months ago /bin/sh -c #(nop) CMD ["redis-server"] 0B <missing> 10 months ago /bin/sh -c #(nop) EXPOSE 6379 0B <missing> 10 months ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B <missing> 10 months ago /bin/sh -c #(nop) COPY file:c48b97ea65422782… 377B <missing> 10 months ago /bin/sh -c #(nop) WORKDIR /data 0B <missing> 10 months ago /bin/sh -c #(nop) VOLUME [/data] 0B <missing> 10 months ago /bin/sh -c mkdir /data && chown redis:redis … 0B <missing> 10 months ago /bin/sh -c set -eux; apk add --no-cache --… 25.4MB <missing> 10 months ago /bin/sh -c #(nop) ENV REDIS_DOWNLOAD_SHA=ba… 0B <missing> 10 months ago /bin/sh -c #(nop) ENV REDIS_DOWNLOAD_URL=ht… 0B <missing> 10 months ago /bin/sh -c #(nop) ENV REDIS_VERSION=6.2.4 0B <missing> 10 months ago /bin/sh -c apk add --no-cache 'su-exec>=0.… 1.29MB <missing> 10 months ago /bin/sh -c addgroup -S -g 1000 redis && addu… 4.7kB <missing> 10 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 10 months ago /bin/sh -c #(nop) ADD file:f278386b0cef68136… 5.6MB
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
命令时,会发生下面事情:
- A network called myapp_default is created.
- A container is created using db’s configuration. It joins the network myapp_default under the name db.
- 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。
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 常用命令
Docker Compose 常用命令如表 1 所示。
命令 | 说明 |
---|---|
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 restart
并不会更新环境变量,如果涉及到环境变量的修改,请先执行 docker-compose stop
再执行 docker-compose up
,参考:https://docs.docker.com/compose/reference/restart/
7.3. 使用 journald 日志引擎
默认,docker 使用 json-file 日志引擎,但它有一个不足:如果 docker-compose 中 service 更新并重启,旧 service 产生的日志使用命令 docker-compose logs service-name
无法再查看到了(由于使用 json-file 日志引擎时,日志保存于以容器 ID 命名的某个子目录中,而更新 service 更新并重启后,容器 ID 会变化,这样旧容器 ID 的日志就无法查看到了)。
如果把日志引擎改为 journald ,使用 journalctl CONTAINER_NAME=your_container
命令可以查看旧 service 所产生的日志。
要设置日志引擎为 journald,可以在 docker 配置文件“/etc/docker/daemon.json”中增加下面设置:
{ "log-driver": "journald" }
默认配置下,journald 最大限制为所在文件系统容量的十分之一。当然,也可以在配置文件“/etc/systemd/journald.conf”中通过配置 SystemMaxUse 精确地控制日志文件的大小。
要查看容器日志,可以执行:
$ docker-compose logs web # 方式一,指定的是service-name,查不到旧日志 $ docker logs myapp_web_1 # 方式二,指定的是容器名字,查不到旧日志 $ journalctl CONTAINER_NAME=myapp_web_1 # 方式三,指定的是容器名字,可以查到旧日志
默认,journald 的日志会转发到 syslog 中。如果容器产生的日志很多,会导致 syslog 变得很大。可以配置为禁止转发,在文件“/etc/systemd/journald.conf”中增加下面设置即可:
ForwardToSyslog=no ForwardToWall=no
重启 journald 即可生效 systemctl restart systemd-journald.service
参考:https://docs.docker.com/config/containers/logging/journald/
7.4. 限制 container 产生 log 文件的大小
使用 json-file 日志引擎时,通过参数“max-size”和“max-file”可以配置 docker-compose.yml 文件中 service 对应 container 所产生的 log 文件的大小和个数,如:
redis: image: "redis:alpine" ports: - "6379:6379" logging: driver: "json-file" options: max-file: "3" max-size: "50m"
7.5. docker compose vs docker-compose
新版的 Docker CLI 中集成了 Compose,这样可以直接以子命令的形式使用 Compose 了,如 docker compose up -d
。
参考:https://docs.docker.com/compose/migrate/#docker-compose-vs-docker-compose
8. Tips
8.1. 配置文件 daemon.json
Docker daemon 的默认配置文件为“/etc/docker/daemon.json”,不存在的话可以创建该文件。
比如,设置 "data-root"
可以修改 docker 的保存数据的目录:
{ "data-root": "/path/to/data-root" }
又如,要增加私有仓库("192.168.0.100:8090")可以添加下面设置:
{ "insecure-registries": ["192.168.0.100:8090"] }
它的详细配置可参见:https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file
8.2. 设置 Container 和 host 机器之间的共享目录
可以使用 run 命令的 -v /outside/dir:/container/dir
(注:两个目录都必须是绝对路径)参数来设置 Container 和 host 机器之间的共享目录。如:
$ docker run -v /Users/cig01/test/:/test -it ubuntu /bin/bash
8.3. stop 所有 container
$ docker stop $(docker ps -a -q)
8.4. 删除所有 images
$ docker rmi -f $(docker images -a -q)
8.5. 删除正在运行 container 的 log
如果使用默认的 json-file 作为日志引擎,执行下面命令可以删除正在运行 container 的 log:
$ sudo truncate -s 0 $(docker inspect --format='{{.LogPath}}' <container_name_or_id>)
上面命令执行完后,使用 docker logs <container_name_or_id>
可验证 log 确实被清空了。
8.6. 通过 image 名字找到所有运行的 container
$ docker ps -q --filter ancestor=<image-name>
8.7. 限制 container 使用 CPU 资源
比如,限制 container 只能使用 0.75 个 CPU 资源(可能很慢):
$ docker run -it --cpus="0.75" ubuntu /bin/bash
参考:https://docs.docker.com/engine/admin/resource_constraints/
8.8. Docker 私有仓库
推荐使用 Harbor 搭建 Docker 私有仓库,它是由 VMware 公司开源的企业级的 Docker Registry 管理项目。安装步骤可参考:https://github.com/goharbor/harbor/blob/master/docs/installation_guide.md
在 Harbor 控制台中注册用户后,可以使用下面命令登录到私有仓库:
$ docker login <host:port> # 会提示输入用户名和密码
注:docker 默认使用 https,由于自己搭建的私有仓库往往没有启用 https,所以登录前需要在文件/etc/docker/daemon.json 中把私有仓库地址配置为 insecure-registries:
{ "insecure-registries":["192.168.1.123:8090"] }
把镜像 push 到私有仓库中:
$ docker tag <img_name>:<tag> <host:port>/<project>/<repo>:<tag> $ docker push <host:port>/<project>/<repo>:<tag>
从私有仓库中 pull 镜像:
$ docker pull <host:port>/<project>/<repo>:<tag>