简单来说,容器是镜像的一个运行实例。所不同的是,镜像是静态的只读文件,而容器带有运行时需要的可写文件层。
如果认为虚拟机是模拟运行的一整套操作系统(包括内核、应用运行态环境和其他系统环境)和跑在上面的应用,那么 Docker 容器就是独立运行的一个 (或一组)应用,以及它们必需的运行环境。
docker create
使用 docker create 命令新建一个容器,新建的容器处于停止状态,可以使用 docker start 命令来启动它。
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
33197473a278 hello-world "/hello" 6 days ago Exited (0) 6 days ago frosty_galileo
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker create -it ubuntu:16.04
1f51e44dab220bfffb73bbd27cd7fe08ecf1109d368094329efb1818c32477f3
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1f51e44dab22 ubuntu:16.04 "/bin/bash" 9 seconds ago Created confident_wu
33197473a278 hello-world "/hello" 6 days ago Exited (0) 6 days ago frosty_galileo
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker start 1f
1f
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1f51e44dab22 ubuntu:16.04 "/bin/bash" 40 seconds ago Up 2 seconds confident_wu
33197473a278 hello-world "/hello" 6 days ago Exited (0) 6 days ago frosty_galileo
docker run
除了创建容器后通过 start 命令来启动,也可以直接新建并启动容器。所需要的命令主要为docker run,等价于先执行 docker create 命令,再执行 docker start 命令。
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker run ubuntu:16.04 /bin/echo "hello world"
hello world
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c6813fb16675 ubuntu:16.04 "/bin/echo 'hello wo…" About a minute ago Exited (0) About a minute ago determined_blackwell
1f51e44dab22 ubuntu:16.04 "/bin/bash" 6 minutes ago Up 5 minutes confident_wu
33197473a278 hello-world "/hello" 6 days ago Exited (0) 6 days ago frosty_galileo
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$
当利用 docker run 来创建并启动容器时,Docker 在后台运行的标准操作包括:
下面的命令启动一个 bash 终端,允许用户进行交互:
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker run -ti ubuntu:16.04 /bin/bash
root@acb806366cf1:/# pwd
/
root@acb806366cf1:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@acb806366cf1:/# exit
exit
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$
输入 exit
命令可以退出当前的容器
运行一个容器,使用 docker run
命令即可。 另 docker run -参数
含义:
docker run --name container-name -d image-name
--name
:为容器起一个名称;-d
:detached,执行完这句命令后,控制台将不会阻塞,可以继续输入命令操作,不会阻塞,也就是启动守护式容器,如果执行 docker run --name mycentos -it centos
会进入启动容器的命令控制台,也就是启动交互式容器;-i
:以交互方式运行容器,通常与 -t
搭配使用;-t
:为容器重新分配一个伪输入终端,通常与 -i
搭配使用;-P
:随机端口映射;-p
:指定端口映射,后面会有端口映射详细讲解;image-name
:要运行的镜像名称;需要让 Docker 容器在后台以守护态 (Daemonized) 形式运行。此时,可以通过添加 -d 参数来实现。获取容器的输出信息,可以通过 docker logs命令。
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker run -d ubuntu:16.04 /bin/echo "this is test output"
89860f528c9a9c4c552ee23837a7254ffcace89a9abf30a1d83e72947341d396
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
89860f528c9a ubuntu:16.04 "/bin/echo 'this is …" 6 seconds ago Exited (0) 4 seconds ago admiring_driscoll
acb806366cf1 ubuntu:16.04 "/bin/bash" 9 minutes ago Exited (0) 9 minutes ago gifted_bartik
c6813fb16675 ubuntu:16.04 "/bin/echo 'hello wo…" 26 minutes ago Exited (0) 26 minutes ago determined_blackwell
1f51e44dab22 ubuntu:16.04 "/bin/bash" 31 minutes ago Up 31 minutes confident_wu
33197473a278 hello-world "/hello" 6 days ago Exited (0) 6 days ago frosty_galileo
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker logs 89
this is test output
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$
可以使用 docker stop 来终止一个运行中的容器。该命令的格式为
docker stop [-t|--time[=10]] CONTAINER...
首先向容器发送 SIGTERM 信号,等待一段超时时间 (默认为 10秒) 后,再发送 SIGKILL 信号来终止容器
docker kill 命令会直接发送 SIGKILL 信号来强行终止容器。
此外,当 Docker 容器中指定的应用终结时,容器也会自动终止。例如对于上一节中只启动了一个终端的容器,用户通过 exit 命令或 Ctrl+d 来退出终端时,所创建的容器立刻终止,处于 stopped 状态。
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
89860f528c9a ubuntu:16.04 "/bin/echo 'this is …" 5 minutes ago Exited (0) 5 minutes ago admiring_driscoll
acb806366cf1 ubuntu:16.04 "/bin/bash" 14 minutes ago Exited (0) 14 minutes ago gifted_bartik
c6813fb16675 ubuntu:16.04 "/bin/echo 'hello wo…" 31 minutes ago Exited (0) 31 minutes ago determined_blackwell
1f51e44dab22 ubuntu:16.04 "/bin/bash" 36 minutes ago Up 36 minutes confident_wu
33197473a278 hello-world "/hello" 6 days ago Exited (0) 6 days ago frosty_galileo
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker stop 1f
1f
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
89860f528c9a ubuntu:16.04 "/bin/echo 'this is …" 6 minutes ago Exited (0) 6 minutes ago admiring_driscoll
acb806366cf1 ubuntu:16.04 "/bin/bash" 15 minutes ago Exited (0) 15 minutes ago gifted_bartik
c6813fb16675 ubuntu:16.04 "/bin/echo 'hello wo…" 32 minutes ago Exited (0) 32 minutes ago determined_blackwell
1f51e44dab22 ubuntu:16.04 "/bin/bash" 37 minutes ago Exited (0) 3 seconds ago confident_wu
33197473a278 hello-world "/hello" 6 days ago Exited (0) 6 days ago frosty_galileo
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$
处于终止状态的容器,可以通过 docker start 命令来重新启动,docker restart 命令会将一个运行态的容器先终止,然后再重新启动它。
docker exec
在使用 -d 参数时,容器启动后会进入后台,用户无法看到容器中的信息,也无法进行操作。使用 docker exec 命令,可以在容器内直接执行任意命令。
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker start ac
ac
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
89860f528c9a ubuntu:16.04 "/bin/echo 'this is …" 13 minutes ago Exited (0) About a minute ago admiring_driscoll
acb806366cf1 ubuntu:16.04 "/bin/bash" 22 minutes ago Up 3 seconds gifted_bartik
c6813fb16675 ubuntu:16.04 "/bin/echo 'hello wo…" 40 minutes ago Exited (0) 40 minutes ago determined_blackwell
1f51e44dab22 ubuntu:16.04 "/bin/bash" 45 minutes ago Exited (0) 7 minutes ago confident_wu
33197473a278 hello-world "/hello" 6 days ago Exited (0) 6 days ago frosty_galileo
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker exec -ti ac /bin/bash
root@acb806366cf1:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@acb806366cf1:/# exit
exit
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$
docker rm -f|--force[-v|--volumes]CONTAINER[CONTAINER…]
使用 docker rm 命令来删除处于终止或退出状态的容器,主要支持的选项包括:
默认情况下,docker rm 命令只能删除处于终止或退出状态的容器,并不能删除还处于运行状态的容器。
如果要直接删除一个运行中的容器,可以添加 - f 参数。Docker 会先发送 SIGKILL 信号给容器,终止其中的应用,之后强行删除。
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
89860f528c9a ubuntu:16.04 "/bin/echo 'this is …" 19 minutes ago Exited (0) 7 minutes ago admiring_driscoll
acb806366cf1 ubuntu:16.04 "/bin/bash" 28 minutes ago Up 5 minutes gifted_bartik
c6813fb16675 ubuntu:16.04 "/bin/echo 'hello wo…" 45 minutes ago Exited (0) 45 minutes ago determined_blackwell
1f51e44dab22 ubuntu:16.04 "/bin/bash" About an hour ago Exited (0) 13 minutes ago confident_wu
33197473a278 hello-world "/hello" 6 days ago Exited (0) 6 days ago frosty_galileo
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker rm ac
Error response from daemon: You cannot remove a running container acb806366cf1e0374c5fd3eeac2e1d0a227fe88ae3b30006e0c6c0e2f1800348. Stop the container before attempting removal or force remove
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker rm -f ac
ac
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
89860f528c9a ubuntu:16.04 "/bin/echo 'this is …" 20 minutes ago Exited (0) 7 minutes ago admiring_driscoll
c6813fb16675 ubuntu:16.04 "/bin/echo 'hello wo…" About an hour ago Exited (0) About an hour ago determined_blackwell
1f51e44dab22 ubuntu:16.04 "/bin/bash" About an hour ago Exited (0) 14 minutes ago confident_wu
33197473a278 hello-world "/hello" 6 days ago Exited (0) 6 days ago frosty_galileo
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$
docker export[-o|-- output[=""]]CONTAINER
导出容器是指导出一个已经创建的容器到一个文件,不管此时这个容器是否处于运行状态,可以使用 docker export 命令,其中,可以通过 - o 选项来指定导出的 tar 文件名,也可以直接通过重定向来实现。
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
89860f528c9a ubuntu:16.04 "/bin/echo 'this is …" 20 minutes ago Exited (0) 7 minutes ago admiring_driscoll
c6813fb16675 ubuntu:16.04 "/bin/echo 'hello wo…" About an hour ago Exited (0) About an hour ago determined_blackwell
1f51e44dab22 ubuntu:16.04 "/bin/bash" About an hour ago Exited (0) 14 minutes ago confident_wu
33197473a278 hello-world "/hello" 6 days ago Exited (0) 6 days ago frosty_galileo
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker export -o 111.tar 89
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker export 89 > 222.tar
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ ls
111.tar 222.tar ubuntu.tar
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$
之后,可将导出的 tar 文件传输到其他机器上,然后再通过导入命令导入到系统中,从而实现容器的迁移。
导出的文件又可以使用 docker import 命令导入变成镜像。
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ ls
111.tar 222.tar ubuntu.tar
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ cat 222.tar | docker import - 222_import:v1.0
sha256:78ca641fd62347d9017eed3f1dbf5f1d5df05349b52c557f0b048ae444150421
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
222_import v1.0 78ca641fd623 19 seconds ago 85.9MB
test_image latest 1cd880207b3e 3 days ago 116MB
ubuntu 16.04 a51debf7e1eb 2 weeks ago 116MB
hello-world latest 4ab4c602aa5e 3 months ago 1.84kB
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$
实际上,既可以使用 docker load
命令来导入镜像存储文件到本地镜像库,也可以使用 docker import
命令来导入一个容器快照到本地镜像库。
docker load
适用于镜像docker import
适用于容器这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息 (即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也更大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。
在上面关于docker容器生命周期管理中stop 和 kill 都是关闭容器,但是其中的kill是怎么实现强制杀死运行中的容器的呢?
这里需要说明下关linux下关于终止进程的信号:SIGTERM 和 SIGKILL
SIGKILL信号:无条件终止进程信号。进程接收到该信号会立即终止,不进行清理和暂存工作。该信号不能被忽略、处理和阻塞,它向系统管理员提供了可以杀死任何进程的方法。
SIGTERM信号:程序终结信号,可以由kill命令产生。与SIGKILL不同的是,SIGTERM信号可以被阻塞和终止,以便程序在退出前可以保存工作或清理临时文件等。
docker stop 会先发出SIGTERM信号给进程,告诉进程即将会被关闭。在-t指定的等待时间过了之后,将会立即发出SIGKILL信号,直接关闭容器。
docker kill 直接发出SIGKILL信号关闭容器。但也可以通过-s参数修改发出的信号。
docker restart 中同样可以设置 -t 等待时间,当等待时间过后会立刻发送SIGKILL信号,直接关闭容器。
因此会发现在docker stop的等待过程中,如果终止docker stop的执行,容器最终没有被关闭。而docker kill几乎是立刻发生,无法撤销。
可用通过如下命令 docker insepct
查看容器内部细节,返回为 json
:
docker insepct container-id
docker cp 文件 container-id:目标文件/文件夹
示例:
docker cp /tmp/test.txt 7f237caad43b:/tmp
将宿主机 /tmp/test.txt
文件拷贝到容器 7f237caad43b中 /tmp
目录中。
docker cp container-id:目标文件/文件夹 宿主机目标文件/文件夹
示例:
docker cp 7f237caad43b:/tmp/log.log /tmp
将容器 7f237caad43b 中 /tmp/log.log
拷贝到宿主机 /tmp
目录下。
查看容器实时运行的日志
docker logs -f -t --tail=行数 容器ID或容器名
查看容器从某个时刻开始的日志
docker logs -f -t --since="2018-11-28" --tail=10 容器ID或容器名
在 Docker 的设计中,容器的生命周期其实与容器中 PID 为 1 这个进程有着密切的关系。更确切的说,它们其实是共患难,同生死的兄弟。容器的启动,本质上可以理解为这个进程的启动,而容器的停止也就意味着这个进程的停止,反过来理解亦然。
当我们启动容器时,Docker 其实会按照镜像中的定义,启动对应的程序,并将这个程序的主进程作为容器的主进程 ( 也就是 PID 为 1 的进程 )。而当我们控制容器停止时,Docker 会向主进程发送结束信号,通知程序退出。
而当容器中的主进程主动关闭时 ( 正常结束或出错停止 ),也会让容器随之停止。
通过之前提到的几个方面来看,Docker 不仅是从设计上推崇轻量化的容器,也是许多机制上是以此为原则去实现的。所以,我们最佳的 Docker 实践方法是遵循着它的逻辑,逐渐习惯这种容器即应用,应用即容器的虚拟化方式。虽然在 Docker 中我们也能够实现在同一个容器中运行多个不同类型的程序,但这么做的话,Docker 就无法跟踪不同应用的生命周期,有可能造成应用的非正常关闭,进而影响系统、数据的稳定性。
写时复制 ( Copy on Write ) 这个词对于开发者来说应该并不陌生,在很多编程语言里,都隐藏了写时复制的实现。在编程里,写时复制常常用于对象或数组的拷贝中,当我们拷贝对象或数组时,复制的过程并不是马上发生在内存中,而只是先让两个变量同时指向同一个内存空间,并进行一些标记,当我们要对对象或数组进行修改时,才真正进行内存的拷贝。
Docker 的写时复制与编程中的相类似,也就是在通过镜像运行容器时,并不是马上就把镜像里的所有内容拷贝到容器所运行的沙盒文件系统中,而是利用 UnionFS 将镜像以只读的方式挂载到沙盒文件系统中。只有在容器中发生对文件的修改时,修改才会体现到沙盒环境上。
也就是说,容器在创建和启动的过程中,不需要进行任何的文件系统复制操作,也不需要为容器单独开辟大量的硬盘空间,与其他虚拟化方式对这个过程的操作进行对比,Docker 启动的速度可见一斑。
采用写时复制机制来设计的 Docker,既保证了镜像在生成为容器时,以及容器在运行过程中,不会对自身造成修改。又借助剔除常见虚拟化在初始化时需要从镜像中拷贝整个文件系统的过程,大幅提高了容器的创建和启动速度。可以说,Docker 容器能够实现秒级启动速度,写时复制机制在其中发挥了举足轻重的作用。
Docker 为我们提供了一个 docker attach 命令,用于将当前的输入输出流连接到指定的容器上。
docker attach nginx
这个命令最直观的效果可以理解为我们将容器中的主程序转为了“前台”运行 ( 与 docker run 中的 -d 选项有相反的意思 )。
由于我们的输入输出流衔接到了容器的主程序上,我们的输入输出操作也就直接针对了这个程序,而我们发送的 Linux 信号也会转移到这个程序上。例如我们可以通过 Ctrl + C 来向程序发送停止信号,让程序停止 ( 从而容器也会随之停止 )。