Docker 基础

核心概念

Docker 大部分操作都围绕着三大核心概念:镜像、容器和仓库。

镜像、容器与仓库

  1. Docker 镜像类似于虚拟机镜像,可以将它理解为一个只读的模版。镜像是创建 Docker 容器的基础。
  2. Docker 容器类似于一个轻量级的沙箱,Docker 利用容器来运行和隔离应用。容器是从镜像创建的应用运行实例,容器彼此互相隔离。
  3. Docker 仓库类似于代码仓库,是 Docker 集中存放镜像文件的场所。

Docker 与虚拟机之间的区别

Docker&VM

虚拟机和容器都是在硬件和操作系统以上的,虚拟机有 Hypervisor 层,Hypervisor 是整个虚拟机的核心所在。他为虚拟机提供了虚拟的运行平台,管理虚拟机的操作系统运行。每个虚拟机都有自己的系统和系统库以及应用。

容器没有 Hypervisor 这一层,并且每个容器是和宿主机共享硬件资源及操作系统,那么由 Hypervisor 带来性能的损耗,在 Linux 容器这边是不存在的。
但是虚拟机技术也有其优势,能为应用提供一个更加隔离的环境,不会因为应用程序的漏洞给宿主机造成任何威胁。同时还支持跨操作系统的虚拟化,例如你可以在Linux 操作系统下运行 Windows 虚拟机。

从虚拟化层面来看,传统虚拟化技术是对硬件资源的虚拟,容器技术则是对进程的虚拟,从而可提供更轻量级的虚拟化,实现进程和资源的隔离。

从架构来看,Docker 比虚拟化少了两层,取消了 Hypervisor 层和 GuestOS 层,使用 Docker Engine 进行调度和隔离,所有应用共用主机操作系统,因此在体量上,Docker 较虚拟机更轻量级,在性能上优于虚拟化,接近裸机性能。从应用场景来看,Docker 和虚拟化则有各自擅长的领域,在软件开发、测试场景和生产运维场景中各有优劣。

使用 Docker 镜像

搜寻镜像

使用 docker search 可以搜索远端仓库中共享的镜像。

默认参数:

  • --format string :格式化输出内容
  • --no-trunc=true|false :输出信息不截断显示,默认为否
  • --limit int :限制输出结果个数,默认为 25 个

获取镜像

使用 docker pull 直接从 Docker Hub 镜像源下载镜像。格式为:docker pull NAME[:TAG]。NAME 是镜像仓库的名称(用来区分镜像),TAG 是镜像的标签(用来表示版本信息)。对于 Docker 镜像来说,如果不显式指定 TAG,则会默认选择 latest 标签,这会下载仓库中最新版本的镜像。

镜像文件一般由若干层(layer)组成,镜像的仓库名称中还应该添加仓库地址作为前缀。如果从非官方的仓库下载,则需要在仓库名称前指定完整的仓库地址。

pull 子命令:

  • -a, --all-tags=true|false :是否获取仓库中的所有镜像,默认为否
  • --registry-mirror=proxy_url :指定镜像代理服务地址

查看镜像信息

使用 docker images 或者 docker image ls 可以列出本地主机上已有镜像的基本信息。镜像的 ID 信息唯一标识了镜像。TAG 信息用来标记来自同一仓库的不同镜像。

镜像大小信息只是表示该镜像的逻辑体积大小,实际上由于相同的镜像层本地只会存储一份,物理上占用的存储空间会小于各镜像的逻辑体积之和。

images 子命令:

  • -a, --all=true|false :列出所有的镜像文件(包括临时文件),默认为否
  • --digests=true|false :列出镜像的数字摘要值,默认为否
  • -f, --filter=[] :过滤列出的镜像
  • --format="TEMPLATE" :控制输出格式
  • --no-trunc=true|false :对输出结果中太长的部分是否进行阶段,默认为是
  • -q, --quiet=true|false :仅输出 ID 信息,默认为否

  • docker tag ubuntu:latest myubuntu:latest :为本地镜像任意添加新的标签
  • docker [image] inspect :获取镜像的详细信息,-f 指定特定内容
  • docker history :列出各层的创建信息

删除镜像

当使用 docker rmidocker image rm 命令,并在后面跟上镜像的 ID 时,会先尝试删除所有指向该镜像的标签,然后删除该镜像文件本身。

当有该镜像创建的容器存在时,镜像文件默认是无法被删除的。

  • docker ps -a :查看本机上存在的所有容器
  • docker rm a21c0840213e :删除某镜像的容器
  • docker rmi IMAGE[IMAGE...] :删除镜像
  • docker rmi -f ... :强制删除镜像,即使有容器依赖它
  • docker image prune :清理临时的镜像文件
    • -a, -all :删除所有无用镜像
    • -f, -force :强制删除镜像,而不进行提示确认

创建镜像

创建镜像的方法主要有三种:基于已有镜像的容器创建,基于本地模板导入,基于 Dockerfile 创建。

基于已有镜像的容器创建

主要是使用 docker [container] commit 命令。格式为 docker [container] commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] ,主要选项包括:

  • -a, --author="" :作者信息
  • -c, --change=[] :提交的时候执行 Dockerfile 指令
  • -m, --message="" :提交消息
  • -p, --pause=true :提交时暂停容器运行

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[sky@localhost ~]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 16.04 c9d990395902 12 days ago 113MB
[sky@localhost ~]$ docker run -it ubuntu:16.04 /bin/Bash
root@b3bfcc12d372:/# cd /home
root@b3bfcc12d372:/home# ls
root@b3bfcc12d372:/home# touch test
root@b3bfcc12d372:/home# exit
exit
[sky@localhost ~]$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b3bfcc12d372 ubuntu:16.04 "/bin/Bash" About a minute ago Exited (0) 15 seconds ago adoring_hodgkin
[sky@localhost ~]$ docker commit -m "Add a new file" b3bfcc12d372 test:0.1
sha256:a75407088cacdf22fa0999d14b6ab041d210e860495b5909d97834b0854c17ab
[sky@localhost ~]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test 0.1 a75407088cac 11 seconds ago 113MB
ubuntu 16.04 c9d990395902 12 days ago 113MB

启动一个镜像,在其中进行修改操作,在 /home 下创建一个 test 文件,之后退出。此刻容器已经发生了改变,用 docker commit 提交为一个新镜像。提交时可以使用 ID 或名称来指定容器。

基于本地模板导入

用户可以从一个操作系统模板文件导入一个镜像,主要使用 docker [container] import 命令。格式为 docker [container] import [OPTIONS] file|URL|-[REPOSITORY[:TAG]]

存出和载入镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[sky@localhost ~]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test 0.1 a75407088cac 15 minutes ago 113MB
ubuntu 16.04 c9d990395902 12 days ago 113MB
[sky@localhost ~]$ docker save -o ubuntu_test.tar test:0.1
[sky@localhost ~]$ ls
Desktop Documents Downloads Music Pictures Public Templates ubuntu_test.tar Videos
[sky@localhost ~]$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b3bfcc12d372 ubuntu:16.04 "/bin/Bash" 19 minutes ago Exited (0) 18 minutes ago adoring_hodgkin
[sky@localhost ~]$ docker rmi test:0.1
Untagged: test:0.1
Deleted: sha256:a75407088cacdf22fa0999d14b6ab041d210e860495b5909d97834b0854c17ab
Deleted: sha256:16b8359aa81923a40feff265c15ca3adcb85808cedc6716c05a9f417e8220e3d
[sky@localhost ~]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 16.04 c9d990395902 12 days ago 113MB
[sky@localhost ~]$ docker load --input ubuntu_test.tar
92e62126a9d9: Loading layer [==================================================>] 3.584kB/3.584kB
Loaded image: test:0.1
[sky@localhost ~]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test 0.1 a75407088cac 17 minutes ago 113MB
ubuntu 16.04 c9d990395902 12 days ago 113MB

存出镜像

如果要导出镜像到本地文件,可以使用 docker [image] save 命令。

  • docker save busybox > busybox.tar
  • docker save --output busybox.tar busybox
  • docker save -o busybox.tar busybox

载入镜像

使用 docker [image] load 将导出的 tar 文件再导入到本地镜像库。

  • docker load < busybox.tar.gz
  • docker load --input fedora.tardocker load -i busybox.tar.gz

上传镜像

使用 docker [image] push 命令上传镜像到仓库。格式为 docker [image] push NAME[:TAG] | [REGISTRY_HOST[:REGISTRY_PORT]/]NAME[:TAG]

操作 Docker 容器

创建容器

新建容器

使用 docker [container] create 命令新建一个容器。使用 docker create 命令新建的容器处于停止状态,可以使用 docker [container] start 命令来启动。

  • -a, --attach :是否绑定到标准输入、输出和错误
  • -d, --detach=true|false :是否在后台运行容器,默认为否

新建并启动容器

除了创建容器后通过 start 命令来启动,也可以直接新建并启动容器。所需命令为 docker [container] run ,等价于先执行 docker create 再执行 docker start

推荐使用 docker run

  • docker run ubuntu /bin/echo 'Hello World'
  • docker run -it ubuntu:16.04 /bin/Bash
  • -t 选项让 docker 分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上
  • -i 则让容器的标准输入保持打开
  • --detach, -d 后台以守护态(Daemonized)形式运行,并返回容器 ID,默认为 False
  • --name 为容器自定义命名
  • --rm 容器在终止后会立刻删除,不能和 -d 同时使用

用户可以按 Ctrl+d 或输入 exit 命令来退出容器。

停止容器

暂停容器

  • docker [container] pause CONTAINER [CONTAINER...] :暂停一个运行的容器
  • docker [container] unpause CONTAINER [CONTAINER...] :恢复一个暂停的容器

终止容器

使用 docker [container] stop 来终止一个运行中的容器。格式为 docker [container] stop [-t|--time[=10]][CONTAINER...]

首先向容器发送 SIGTERM 信号,等待一段超时时间(默认为 10 秒后),再发送 SIGKILL 信号来终止容器

当 docker 容器中指定的应用终结时,容器也会自动终止。

  • docker [container] prune :清除所有处于停止状态的容器
  • docker kill 命令会直接发送 SIGKILL 信号来强行终止容器
  • docker ps -qa 命令能看到所有容器的 ID
  • docker [container] start :重新启动终止状态的容器

docker restart 会将一个运行态的容器先终止,然后在重新启动它

进入容器

在使用 -d 参数时,容器启动会进入后台,用户无法看到容器中的信息,也无法进行操作。

attach 命令

attach 是 Docker 自带的命令,命令格式为 docker attach [--detach-keys[=[]]][--no-stdin] [--sig-proxy[=true]][CONTAINER]

支持三个主要选项:

  • --detach-keys[=[]] :指定退出 attach 模式的快捷键序列,默认是 CTRL-p CTRL-q
  • --no-stdin=true|false :是否关闭标准输入,默认是保持打开
  • --sig-proxy=true|false :是否代理收到的系统信号给应用进程,默认为 true。

当多个窗口同时用 attach 命令练到同一个容器的时候,所有窗户都会同步显示。当某个窗口因命令阻塞时,其他窗口也无法执行操作了。

exec 命令

命令格式为 docker exec [-d|--detach] [--detach-keys[=[]]] [-i|--interactive] [--privileged] [-t|--tty] [-u|--user[=USER]] CONTAINER COMMAND [ARG..]

比较重要的参数有:

  • -i, --interactive=true|false :打开标准输入接收用户输入命令,默认为 false
  • --privileged=true|false :是否给执行命令以高权限,默认为 false
  • -t, --tty=true|false :分配伪终端,默认为 false
  • -u, --user="" :执行命令的用户名或 ID

例如:

1
2
3
4
5
6
[sky@localhost ~]$ docker exec -it bea233 /bin/Bash
root@bea233bba02f:/# ls
bin dev home lib64 mnt proc run srv tmp var
boot etc lib media opt root sbin sys usr
root@bea233bba02f:/# exit
exit

删除容器

可以使用 docekr rm 命令来删除处于终止会退出状态的容器,命令格式为 docker rm [-f|--force] [-l|--link] [-v|--volumes] CONTAINER [CONTAINER...]

主要选项包括:

  • -f, --force=false :是否强行终止并删除一个运行中的容器
  • -l, --link=false :删除容器的连接,但保留容器
  • -v, --volumes=false :删除容器挂载的数据卷

默认情况下,docker rm 命令只能删除处于终止或退出状态的容器,并不能删除还处于运行状态的容器。如果要直接删除一个运行中的容器,可以添加 -f 参数。

导入和导出容器

导出容器

导出容器是指导出一个已经创建的容器到一个文件,不管此时这个容器是否处于运行状态,可以使用 docker export 命令,该命令的格式为 docker export [-o|--output[=""]] CONTAINER 。其中,可以通过 -o 选项来指定导出的 tar 文件名,也可以直接通过重定向来实现。

举例:

1
2
3
docker export -o test_for_run.tar bea233

docker export bea233 > test_for_run.tar

导入容器

导出的文件又可以使用 docker import 命令导入变成镜像,命令格式为 docker import [-c|--change[=[]] [-m|--message[=MESSAGE]] file|URL|-[RESPOSITORY[:TAG]]

用户可以通过 -c, --change=[] 选项在导入的同时执行对容器进行修改的 Dockerfile 指令。

实际上,既可以使用 docker load 命令来导入镜像存储文件到本地镜像库,也可以使用 docker import 命令来导入一个容器快照到本地镜像库。

两者区别在于容器快照文件将丢失所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也更大。

查看容器

  1. 查看容器详情可以使用 docker container inspect [options] container [container...]
  2. 查看容器内进程可以使用 docker [container] top [options] container [container...]
  3. 查看统计信息可以使用 docker [container] stats [options] [container...] 会显示 CPU、内存、存储、网络等使用情况的统计信息

Docker 数据管理

容器中的管理数据主要有两种方式:

  • 数据卷(Data Volumes):容器内数据直接映射到本地主机环境
  • 数据卷容器(Data Volume Containers):使用特定容器维护数据卷

Docker 容器中文件上传与下载

上传

  • docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH - [OPTIONS]:保持源目标中的链接
  • docker cp /root/test.txt ecef8319d2c8:/root/

该命令的意思是将当前操作系统(Linux)家目录(root)下的文件 test.txt 拷贝到容器 id 为 ecef8319d2c8 的家目录(root)文件夹下。

下载

  • docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH
  • docker cp ecef8319d2c8:/root/test.txt /root/

该命令的意思是将容器 id 为 ecef8319d2c8 的家目录(root)文件夹下的文件 test.txt 拷贝到当前操作系统(Linux)家目录(root)下

数据卷

数据卷是一个可供容器使用的特殊目录,它将主机操作系统目录直接映射进容器,类似于 Linux 中的 mount 操作。

在容器内创建一个数据卷

使用 volume 子命令快速在本地创建一个数据卷:

1
2
$ docker volume create -d local test
test

或者在用 docker run 命令的时候,使用 -v 标记可以在容器内创建一个数据卷。多次重复使用 -v 可以创建多个数据卷。

挂载一个主机目录作为数据卷

使用 -v 标记也可以指定挂载一个本地的已有目录到容器中去作为数据卷(推荐方式)

举例:

docker run -d -p --name web -v /src/webapp:/opt/webapp training/webapp python app.py

上面的命令使用加载主机的 /src/webapp 目录到容器 training/webapp/opt/webapp 目录。

-P 是将容器服务暴露的端口,是自动映射到本地主机的临时端口。

本地目录的路径必须是绝对路径,如果目录不存在 Docker,会自动创建。

Docker 挂载数据卷的默认权限是读写(rw),用户也可以通过 ro 指定为只读:

docekr run -d -P --name web -v /src/webapp:/opt/webapp:ro

挂载一个本地主机文件作为数据卷

-v 标记也可以从主机挂载单个文件到容器中作为数据卷(不推荐)

数据卷容器

数据卷容器也是一个容器,但是它的目的是专门用来提供数据卷供其他容器挂载。

举例:

首先,创建一个数据卷容器 dbdata,并在其中创建一个数据卷挂载到 /dbdata

docker run -it /dbdata --name dbdata ubuntu:16.04

然后,可以在其他容器中使用 --volumes-from 来挂载 dbdata 容器中的数据卷:

docker run -it --volumes-from dbdata --name db1 ubuntu:16.04

使用 --volumes-from 参数所挂载数据卷的容器自身并不需要保持在运行状态。

如果删除了挂载的容器,数据卷并不会被自动删除。如果要删除一个数据卷,必须在删除最后一个还挂载它的容器时显式使用 docker rm -V 命令来指定同时删除关联的容器。

利用数据卷容器来迁移数据

备份

使用下面的命令来备份 dbdata 数据卷容器内的数据卷:

docker run --volumes-from dbdata -v $(pwd):/backup --name worker ubuntu tar cvf /backup/backup.tar /dbdata

首先利用 ubuntu 镜像创建了一个容器 worker。使用 --volumes-from dbdata 参数来让 worker 容器挂载 dbdata 容器的数据卷(即 dbdata 数据卷);使用 -v $(pwd):/backup 参数来挂载本地的当前目录到 worker 容器的 /backup 目录。

worker 容器启动后,使用了 tar cvf /backup/backup.tar /dbtada 命令来将 /dbdata 下内容备份为容器内的 /backup/backup.tar ,即宿主主机当前目录下的 backup.tar

恢复

如果要将数据恢复到一个容器,可以参照下面的步骤。

先创建一个带有数据卷的容器 dbdata2 :

docker run -v /dbdata --name dbdata2 ubuntu /bin/Bash

然后创建另一个新的容器,挂载 dbdata2 的容器,并使用 untar 解压备份文件到所挂载的容器卷中:

docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar

端口映射

当容器中运行一些网络应用,可以通过 -p 或者 -P 参数来指定端口映射。当使用 -P(大写) 标记时,Docker 会随机映射一个 49000 - 49900 的端口到内部容器开放的网络端口。-p(小写) 则可以指定要映射的端口,并且一个指定端口上只可以绑定一个容器。

  • 使用 HostPort : ContainerPort 格式:

    1
    docker run -d -p 5000:5000 training/webapp python app.py
  • 使用 IP : HostPort : ContainerPort 格式指定映射使用一个特定地址

    1
    docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py
  • 使用 IP : ContainerPort 绑定 localhost 的任意端口到 5000 端口,本地主机会自动分配一个端口

    1
    docker run -d -p 127.0.0.1::5000 training/webapp python app.py

    还可以使用 udp 标记来指定 udp 端口

    1
    docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py

Dockerfile

Dockerfile 是一个文本格式的配置文件,用户可以使用 Dockerfile 来快速创建自定义的镜像。

基本结构

Dockerfile 由命令语句组成,并且支持以 # 开头的注释行。

Dockerfile 分为四个部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# escape=\ (backslash)
# This dockerfile uses the ubuntu image
# VERSION 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..

# Base image to use, this must be set as the first line
FROM ubuntu:xeniel

# Maintainer: docker_user <docker_user at email.com>(@docker_user)
LABEL maintainer docker_user docker_user@email.com

# Commands to update the image
RUN echo "deb http://archive.ubuntu.com/ubuntu/ xeniel main universe" >> /etc/apt/sources.list
Run apt-get update && apt-get install -y nginx
Run echo "\ndaemon off;" >> /etc/nginx/nginx.conf

# Commands when creating a new container
CMD /usr/sbin/nginx

主题部分首先使用 FROM 指明所基于的镜像名称,接下来使用 LABEL 指令说明维护者信息。后面则是镜像操作指令,例如 RUN 指令将对镜像执行跟随的命令。每运行一条 RUN 指令,镜像就添加新的一层,并提交。最后是 CMD 指令,用来指定运行容器时的操作命令。

指令说明

ARG

定义创建镜像过程中使用的变量。

格式为 ARG <name> [=<default value>]

在执行 docker build 时,可以通过 -build-arg[=] 来为变量赋值。当镜像编译成功后,ARG 指定的变量将不再存在。

FROM

指定所创建镜像的基础镜像,如果本地不存在,则默认会去 Docker Hub 下载指定镜像。

格式为 FROM <image> [AS <name>] ,或 FROM <image>:<tag> [AS <name>] ,或 FROM <image>@<digest> [AS <name>]

任何 Dockerfile 中的第一条指令必须为 FROM 指令。如果在同一个 Dockerfile 中创建多个镜像,可以使用多个 FROM 指令(每个镜像一次)。

MAINTAINER

指定维护者信息,格式为 MAINTAINER <name> 。该信息会写入生成镜像的 Author 属性域中。

RUN

运行指定命令。格式为 RUN <command>RUN ["executable", "param1", "param2"] 。后一个指令会被解析为 Json 数组,必须用双引号。

前者默认在 shell 终端中运行命令,即 /bin/sh -c ;后者则使用 exec 执行,不会启动 shell 环境。

指定使用其他终端类型可以通过第二种方式实现,如 RUN ["/bin/Bash", "-c", "echo hello"]

每条 RUN 指令将在当前镜像的基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用 \ 来换行。

CMD

CMD 用来指定启动容器时默认执行的命令。支持三种格式:

  • CMD ["executable","param1","param2"] 使用 exec 执行,推荐方式;
  • CMD command param1 param2 在 /bin/sh 中执行,提供给需要交互的应用;
  • CMD ["param1","param2"] 提供给 ENTRYPOINT 的默认参数;

每个 Dockerfile 只能有一条 CMD 命令。如果指定了多条命令,只有最后一条会被执行。如果用户启动容器时候指定了运行的命令,则会覆盖掉 CMD 指定的命令。

LABEL

指定生成镜像的元数据标签信息。

格式为 LEBEL <key>=<value> <key>=<value> ...

EXPOSE

声明镜像内服务所监听的端口。

格式为 EXPOSE <port> [<port> ...]

该指令只是起到声明作用,并不会自动完成端口映射。

ENV

指定环境变量,在镜像生成过程中会被后续 RUN 指令使用,在镜像启动的容器中也会存在。

格式为 ENV <key> <value>ENV <key>=<value>

指令指定的环境变量在运行时可以被覆盖掉。

ADD

将复制指定的 <src> 路径下的内容到容器中的 <dest> 路径下。

格式为 ADD <src> <dest>

COPY

格式为 COPY <src> <dest>

复制本地主机的 <src> (为 Dockerfile 所在目录的相对路径)到容器中的 <dest>

当使用本地目录为源目录时,推荐使用 COPY 。

ENTRYPOINT

指定镜像的默认入口命令,该入口命令会在启动容器时作为根命令执行,所有传入值作为该命令的参数。

  • ENTRYPOINT ["executable", "param1", "param2"]
  • ENTRYPOINT command param1 param2 (shell中执行)

此时,CMD 指令指定值将作为根命令的参数。

每个 Dockerfile 中只能有一个 ENTRYPOINT ,当指定多个时,只有最后一个起效。

VOLUME

创建一个数据卷挂载点。

格式为 VOLUME ["/data"]

USER

指定运行容器时的用户名或 UID,后续的 RUN 等指令也会使用指定的用户身份。

格式为 USER daemon

WORKDIR

为后续的 RUN、CMD 和 ENTRYPOINT 指令配置工作目录。

格式为 WORKDIR /path/to/workdir

ONBUILD

配置当所创建的镜像作为其他镜像的基础镜像时,所执行的创建操作指令。

格式为 ONBUILD [INSTRUCTTION]

HEALHCHECK

配置所启动的容器如何进行健康检查。

格式有两种:

  • HEALTHCHECK [OPTIONS] CMD command :根据所执行命令返回值是否为 0 来判断
  • HEALTHCHECK NONE :禁止基础镜像中的健康检查

option 支持:

  • --interval=DURATION (默认为:30s) :过多久检查一次
  • --timeout=DURATION (默认为:30s) :每次检查等待结果的超时
  • --retries=N (默认为:3) :如果失败了,重试几次才最终确认失败。

SHELL

指定其他命令使用 shell 时的默认 shell 类型。

格式为: SHELL ["executable", "parameters"]

默认值为 ["/bin/sh", "-c"]

创建镜像

编写完成 Dockerfile 之后,可以通过 docker build 命令来创建镜像。

基本的格式为 docker build [选项] 路径 ,该命令将读取指定路径下(包括子目录)的 Dockerfile,并将
该路径下所有内容发送给 Docker 服务端,由服务端来创建镜像。因此一般建议放置 Dockerfile 的目录为空
目录。也可以通过 .dockerignore 文件(每一行添加一条匹配模式)来让 Docker 忽略路径下的目录和文
件。

  • 如果使用非内容路径下的 Dokcerfile,可以通过 -f 选项来指定其路径
  • 要指定生成镜像的标签信息,可以使用 -t 选项

为镜像添加 SSH 服务

Docker 很多镜像是不带 SSH 服务的,这里介绍两种创建带有 SSH 服务的镜像

基于 commit 命令创建

  1. 安装镜像

    1
    2
    docker pull ubuntu:18.04
    docker run -it ubuntu:18.04 Bash
  2. 配置 SSH 服务

    1
    2
    root@xxxx:/# apt-get update
    root@xxxx:/# apt-get install openssh-server

    如果需要正常启动 SSH 服务,目录 /var/run/sshd 必须存在。

    1
    2
    root@xxxx:/# mkdir -p /var/run/sshd
    root@xxxx:/# /usr/sbin/sshd -D &

    查看容器的 22 端口

    1
    2
    3
    4
    5
    6
    root@xxxx:/# apt-get install net-tools
    root@xxxx:/# netstat -tunlp
    Active Internet connections (only servers)
    Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
    tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
    tcp6 0 0 :::22 :::* LISTEN -
  3. 修改 SSH 服务的安全登录配置,取消 pam 登陆限制

    1
    root@xxxx:/# sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd
  4. 在 root 用户目录下创建 .ssh 目录,并复制需要登录的公钥信息到 authorized_keys 文件中

    1
    2
    root@xxxx:/# mkdir root/.ssh
    root@xxxx:/# vim /root/.ssh/authorized_keys
  5. 创建自动启动 SSH 服务的可执行文件 run.sh

    1
    2
    root@xxxx:/# vim /run.sh
    root@xxxx:/# chmod +x run.sh
    1
    2
    #!/bin/bash
    /usr/sbin/sshd -D
  6. 保存镜像

    1
    docker commit <name> sshd:ubuntu
  7. 使用镜像

    1
    docker run -p 10022:22 -d sshd:ubuntu /run.sh
  8. 访问

    1
    ssh 192.168.1.1 -p 10022

使用 Dockfile 创建

  1. 创建工作目录,创建文件

    1
    2
    3
    mkdir sshd_ubuntu
    cd sshd_ubuntu/
    touch Dockerfile run.sh
  2. 编写 run.sh 脚本和 authorized_keys 文件

    1
    2
    #!/bin/Bash
    /usr/sbin/sshd -D
    1
    2
    3
    $ ssh-keygen -t rsa
    ...
    cat ~/.ssh/id_rsa.pub > authorized_keys
  3. 编写 Dockerfile

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    # 设置镜像
    FROM ubuntu:18.04

    # 作者信息
    MAINTAINER docker_user

    # 更新
    RUN apt-get update

    # 安装 ssh 服务
    RUN apt-get install -y openssh-server
    RUN mkdir -p /var/run/sshd
    RUN mkdir -p /root/.ssh

    # 取消 pam 限制
    RUN sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd

    # 复制配置文件到相应位置,并赋予脚本可执行权限
    ADD authorized_keys /root/.ssh/authorized_keys
    ADD run.sh /run.sh
    RUN chmod 755 /run.sh

    # 开放端口
    EXPOSE 22

    # 设置自启动命令
    CMD ["/run.sh"]
  4. 创建镜像

    1
    docker build -t sshd:dockerfile .
  5. 启动镜像

    1
    docker run -d -p 10122:22 sshd:dockerfile
  6. 访问

    1
    ssh 192.168.1.1 -p 10122

Compose、Machine 和 Swarm

Docker Machine 已经从 Docker for Mac 中删除,需要单独下载。

这一部分内容建议直接阅读 《Docker Practice》 ,这里附上个人两次实践的 docker-compose.yml 文件 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
version: '3'

services:
php-workspace:
container_name: lab1-php
build:
context: ./php
dockerfile: Dockerfile
restart: always
depends_on:
- mysql-db
volumes:
- ./code:/var/www/html/
networks:
- front
- back

nginx:
container_name: lab1-nginx
build:
context: ./nginx
dockerfile: Dockerfile
restart: always
depends_on:
- php-workspace
- mysql-db
ports:
- "8080:8080"
volumes:
- ./log/nginx:/var/log/nginx/
networks:
- front

mysql-db:
container_name: lab1-mysql
image: mysql:5.7
restart: always
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: "123"
volumes:
- ./mysql/db_data:/var/lib/mysql
- ./mysql/init:/docker-entrypoint-initdb.d/
networks:
- back

networks:
front:
driver: "bridge"
back:
driver: "bridge"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
version: '3'

services:
php-workspace:
image: docker-php_php-workspace:latest
deploy:
mode: replicated
replicas: 3
depends_on:
- mysql-db
volumes:
- ./code:/var/www/html/
networks:
- front
- back

nginx:
image: docker-php_nginx:latest
deploy:
restart_policy:
condition: on-failure
# resources:
# limits:
# cpus: '0.50'
# memory: 50M
# reservations:
# cpus: '0.25'
# memory: 20M
mode: replicated
replicas: 3
depends_on:
- php-workspace
- mysql-db
ports:
- "8080:8080"
volumes:
- ./log/nginx:/var/log/nginx/
networks:
- front

mysql-db:
image: mysql:5.7
deploy:
restart_policy:
condition: on-failure
# resources:
# limits:
# cpus: '0.50' # 50% of a single core
# memory: 50M # no more than 50M of memory
# reservations:
# cpus: '0.25' # 25% CPU time reserved
# memory: 20M # 20M of memory reserved
placement:
constraints:
- node.role == manager
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: "123"
volumes:
- ./mysql/db_data:/var/lib/mysql/
- ./log/mysql:/var/log/mysql/
- ./mysql/init:/docker-entrypoint-initdb.d/
networks:
- back

visualizer:
image: dockersamples/visualizer:stable
ports:
- "9090:8080"
stop_grace_period: 1m30s
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
deploy:
placement:
constraints:
- node.role == manager

networks:
front:
driver: "overlay"
back:
driver: "overlay"