一、介绍
容器(Container)是用来隔离虚拟环境的基础设施,在 Docker 里,它也被引申为隔离出来的虚拟环境。
如果把镜像理解为编程中的类,那么容器就可以理解为类的实例。镜像内存放的是不可变化的东西,当以它们为基础的容器启动后,容器内也就成为了一个“活”的空间。
在官方的定义中,Docker 的容器应该有三项内容组成:
- 一个 Docker 镜像
- 一个程序运行环境
- 一个指令集合
二、主进程
在 Docker 的设计中,容器的生命周期其实与容器中 PID 为 1 这个进程有着密切的关系。更确切的说,它们其实是同生共死的关系。容器的启动,本质上可以理解为这个进程的启动,而容器的停止也就意味着这个进程的停止。
当启动容器时,Docker 其实会按照镜像中的定义,启动对应的程序,并将这个程序的主进程作为容器的主进程(也就是 PID 为 1 的进程)。而当停止容器时,Docker 会向主进程发送结束信号,通知程序退出。而当容器中的主进程主动关闭时(正常结束或出错停止),也会让容器随之停止。
三、Copy on Write
Copy on Write(写时复制机制):在很多编程语言里,都隐藏了 Copy on Write 的实现。在编程里,Copy on Write 常常用于对象或数组的拷贝中,当拷贝对象或数组时,复制的过程并不是马上发生在内存中,而只是先让两个变量同时指向同一个内存空间,并进行一些标记,当要对对象或数组进行修改时,才真正进行内存的拷贝。
Docker 的 Copy on Write 与编程中的相类似,也就是在通过镜像运行容器时,并不是马上就把镜像里的所有内容拷贝到容器所运行的沙盒文件系统中,而是利用 UnionFS 将镜像以只读的方式挂载到沙盒文件系统中。只有在容器中发生对文件的修改时,修改才会体现到沙盒环境上。
也就是说,容器在创建和启动的过程中,不需要进行任何的文件系统复制操作,也不需要为容器单独开辟大量的硬盘空间,与其他虚拟化方式对这个过程的操作进行对比,Docker 启动的速度就快了许多。
采用 Copy on Write 机制来设计的 Docker,既保证了镜像在生成为容器与容器在运行过程中,不会对自身造成修改。又借助剔除常见虚拟化在初始化时需要从镜像中拷贝整个文件系统的过程,大幅提高了容器的创建和启动速度。可以说,Docker 容器能够实现秒级启动速度, Copy on Write 机制在其中发挥了举足轻重的作用。
四、docker 容器的状态
容器的状态:
- Created:容器已经被创建,容器所需的相关资源已经准备就绪,但容器中的程序还未处于运行状态。
- Running:容器正在运行,也就是容器中的应用正在运行。
- Paused:容器已暂停,表示容器中的所有程序都处于暂停(不是停止)状态。
- Stopped:容器处于停止状态,占用的资源和沙盒环境都依然存在,只是容器中的应用程序均已停止。
- Deleted:容器已删除,相关占用的资源及存储在 Docker 中的管理信息也都已释放和移除。
五、创建容器
使用 docker create
命令创建容器,后面参数是镜像名,需要带上版本,如果没有带上版本,默认 latest
版本:
$ sudo docker create redis:5.0.7
ad45a360674e3b3cc7f5d23f5e5a4041b0cbde1d1499e2636412247c08ff1fe2
docker create
命令会返回 docker 为容器分配的id,此时,容器就处于 Created 状态.
容器名
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b70067d2e6e5 redis:5.0.7 "docker-entrypoint.s…" 10 seconds ago Created gracious_mendeleev
默认容器名是两个随机的单词,用下划线连接起来的,例如:gracious_mendeleev
。
可以使用 --name
来指定容器名称。
sudo docker create --name my-redis redis:5.0.7
66cc23ef8991a6ad088ae92b82d18362a5ac4adb3e251398cae7a303cc48cd92
# 查看所有已有容器
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
66cc23ef8991 redis:5.0.7 "docker-entrypoint.s…" 26 seconds ago Created my-redis
b70067d2e6e5 redis:5.0.7 "docker-entrypoint.s…" 4 minutes ago Created gracious_mendeleev
六、启动容器
通过 docker create
命令创建的容器,是处于 Created 状态的,其内部的应用程序还没有启动。需要通过命令 docker start
来启动它。
docker start
后面的参数是容器名称或者容器id(可以是能标识到唯一容器的部分id)。
$ sudo docker start my-redis
运行 docker start
命令后容器就处于 Running 状态。
在 docker 里面,可以通过命令 docker run
将 docker create
和 docker start
两步操作合成为一步 :
$ sudo docker run --name redis -d redis:5.0.7
69c708611465da6a86ea5b621b234f87203f1895c8a77d61da03e546f2e551cc
通过 docker run
创建的容器,在创建完成之后会直接启动起来,不需要再使用 docker start
去启动了。
注意 : docker run
在启动容器时,会采用“前台”运行这种方式,这时候控制台就会衔接到容器上,不能再进行其他操作了。可以通过 -d
或 --detach
参数告诉 Docker 在后台运行程序。
使用命令 docker run
时,可以加上参数 -it
创建运行容器,并进入容器 :
$ sudo docker run -it --name redis -d redis:5.0.7
还有一种 -rm
参数,容器停止后就删除,可以测试环境用 :
$ docker run -it --rm --name redis -d redis:5.0.7
七、查看容器
可以通过 docker ps
命令列出 docker 中正在运行的容器:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
69c708611465 redis:5.0.7 "docker-entrypoint.s…" 14 minutes ago Up 14 minutes 6379/tcp redis
由于docker ps
命令列出的容器是处于运行中的容器,如果要列出所有状态的容器,需要增加 -a
或 --all
参数。
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
adf432ce4a8a elasticsearch:7.1.0 "/usr/local/bin/dock…" 5 minutes ago Exited (137) 50 seconds ago elasticsearch
7eb3839cb51e nginx:1.17.6 "nginx -g 'daemon of…" About an hour ago Created nginx
69c708611465 redis:5.0.7 "docker-entrypoint.s…" 2 hours ago Up 2 hours 6379/tcp redis
在 docker ps
命令的结果中,可以看到几项关于容器的信息。其中 CONTAINER ID 代表容器 ID、IMAGE 代表容器所基于的镜像、CREATED 代表容器的创建时间、NAMES 代表容器的名称。
返回的 COMMAND 表示的是容器中主程序(也就是与容器生命周期所绑定进程所关联的程序)的启动命令,这条命令是在镜像内定义的,而容器的启动其实质就是启动这条命令。
返回的 STATUS 表示容器所处的状态,但是这里还记录了其他的一些信息。在这里,常见的状态表示有三种:
- Created 此时容器已创建,但还没有被启动过。
- Up [Time] 这时候容器处于正在运行状态,而这里的 Time 表示容器从开始运行到查看时的时间。
- Exited ([Code]) [Time] 容器已经结束运行,这里的 Code 表示容器结束运行时,主程序返回的程序退出码,而 Time 则表示容器结束到查看时的时间。
七、停止和删除容器
可以使用 docker stop
停止容器 :
$ sudo docker stop nginx
容器停止后,其维持的文件系统沙盒环境还是存在的,内部被修改的内容也都会保留,可以通过 docker start 命令将这个容器再次启动。
也使用 docker rm
删除容器 :
$ sudo docker rm nginx
正在运行中的容器默认情况下是不能被删除的,可以通过增加 -f
或 --force
参数来强制停止并删除容器。
随手删除容器
在短时间内不需要使用容器时,最佳的做法是删除它而不是仅仅停止它。这样做的原因是 :
- 在使用虚拟机或其他虚拟化所搭建的虚拟环境时,更倾向于使用一个干净的系统镜像并搭建程序的运行环境,由于将这类虚拟环境制作成镜像的成本较高,耗时也非常久,所以对于一些细小的改动倾向于修改后保持虚拟环境不被清除。但是在 Docker 中,打包镜像的成本是非常低的,其速度也快,所以如果要为程序准备一些环境或者配置,完全可以直接将它们打包至新的镜像中,下次直接使用这个新的镜像创建容器。
- 容器中应用程序所产生的一些文件数据,是非常重要的,如果这些数据随着容器的删除而丢失,其损失是非常巨大的。对于这类由应用程序所产生的数据,并且需要保证它们不会随着容器的删除而消失的,可以使用 Docker 中的数据卷来单独存放。由于数据卷是独立于容器存在的,所以其能保证数据不会随着容器的删除而丢失。
- 事实上,容器的随用随删既能保证在不需要它们的时候它们不会枉占很多资源,也保证了每次我们建立和启动容器时,它们都是崭新版本。
八、容器执行命令
容器是一个隔离运行环境的东西,它里面除了镜像所规定的主进程外,其他的进程也是能够运行的,Docker 提供了一个命令 docker exec 容器标识 命令
来让容器运行命令。
例如: 使用 more
命令查看容器的主机名定义。
# 查看在运行的容器
sudo docker ps
# 在容器名为 redis 的容器里面运行命令 : more /etc/hostname
sudo docker exec redis more /etc/hostname
# 也可以使用容器id(可以是id的部分,只要能够唯一标识到容器id就可以)在对应容器执行命令
sudo docker exec 69c708611465 more /etc/hostname
如图 :
九、进入容器
docker 可以在容器外部是容器执行命令,也可以通过 sh
或者 bash
进入容器执行命令。但是 bash 的功能要比 sh 丰富,如果能够使用 bash 可以优先考虑使用它 :
# 这里的 redis 也可以像上面那样 换成对应的容器 id 标识
sudo docker exec -it redis bash
上面进入容器的命令选项 -it
是不能够缺少的,其中 -i (–interactive) 表示保持输入流,只有使用它才能保证控制台程序能够正确识别我们的命令。而 -t (–tty) 表示启用一个伪终端,形成命令输入与容器 bash 的交互,如果没有它,无法看到 bash 内部的执行结果。
十、查看容器主线程运行日志
# -f 挂起这个终端,动态查看日志
$ docker logs -f 容器名