Docker 探索-容器的生命周期

主进程
在 Docker 的设计中,容器的生命周期其实与容器中 PID 为 1 这个进程有着密切的关系。更确切的说,它们其实是共患难,同生死的兄弟。容器的启动,本质上可以理解为这个进程的启动,而容器的停止也就意味着这个进程的停止,反过来理解亦然。
当我们启动容器时,Docker 其实会按照镜像中的定义,启动对应的程序,并将这个程序的主进程作为容器的主进程 ( 也就是 PID 为 1 的进程 )。而当我们控制容器停止时,Docker 会向主进程发送结束信号,通知程序退出。
而当容器中的主进程主动关闭时 ( 正常结束或出错停止 ),也会让容器随之停止。
通过之前提到的几个方面来看,Docker 不仅是从设计上推崇轻量化的容器,也是许多机制上是以此为原则去实现的。所以,我们最佳的 Docker 实践方法是遵循着它的逻辑,逐渐习惯这种容器即应用,应用即容器的虚拟化方式。虽然在 Docker 中我们也能够实现在同一个容器中运行多个不同类型的程序,但这么做的话,Docker 就无法跟踪不同应用的生命周期,有可能造成应用的非正常关闭,进而影响系统、数据的稳定性。
写时复制机制
写时复制 ( Copy on Write ) 这个词对于开发者来说应该并不陌生,在很多编程语言里,都隐藏了写时复制的实现。在编程里,写时复制常常用于对象或数组的拷贝中,当我们拷贝对象或数组时,复制的过程并不是马上发生在内存中,而只是先让两个变量同时指向同一个内存空间,并进行一些标记,当我们要对对象或数组进行修改时,才真正进行内存的拷贝。
Docker 的写时复制与编程中的相类似,也就是在通过镜像运行容器时,并不是马上就把镜像里的所有内容拷贝到容器所运行的沙盒文件系统中,而是利用 UnionFS 将镜像以只读的方式挂载到沙盒文件系统中。只有在容器中发生对文件的修改时,修改才会体现到沙盒环境上。
也就是说,容器在创建和启动的过程中,不需要进行任何的文件系统复制操作,也不需要为容器单独开辟大量的硬盘空间,与其他虚拟化方式对这个过程的操作进行对比,Docker 启动的速度可见一斑。
采用写时复制机制来设计的 Docker,既保证了镜像在生成为容器时,以及容器在运行过程中,不会对自身造成修改。又借助剔除常见虚拟化在初始化时需要从镜像中拷贝整个文件系统的过程,大幅提高了容器的创建和启动速度。可以说,Docker 容器能够实现秒级启动速度,写时复制机制在其中发挥了举足轻重的作用。
镜像仓库
我们把镜像的结构用 Git 项目的结构做类比,那么镜像仓库就可以看似 GitLab、GitHub 等的托管平台,只不过 Docker 的镜像仓库托管的不是代码项目,而是镜像。
当然,存储镜像并不是镜像仓库最值得炫耀的功能,其最大的作用是实现了 Docker 镜像的分发。借助镜像仓库,我们得到了一个镜像的中转站,我们可以将开发环境上所使用的镜像推送至镜像仓库,并在测试或生产环境上拉取到它们,而这个过程仅需要几个命令,甚至自动化完成。
获取镜像:
sudo docker pull ubuntu
当我们运行这个命令后,Docker 就会开始从镜像仓库中拉取我们所指定的镜像了,在控制台中,我们可以看到镜像拉取的进度。下载进度会分为几行,其实每一行代表的就是一个镜像层。Docker 首先会拉取镜像所基于的所有镜像层,之后再单独拉取每一个镜像层并组合成这个镜像。当然,如果在本地已经存在相同的镜像层 ( 共享于其他的镜像 ),那么 Docker 就直接略过这个镜像层的拉取而直接采用本地的内容。
使用完整的镜像命名来拉取镜像。
sudo docker pull openresty/openresty:1.13.6.2-alpine 1.13.6.2-alpine: Pulling from openresty/openresty ff3a5c916c92: Pull complete ede0a2a1012b: Pull complete 0e0a11843023: Pull complete 246b2c6f4992: Pull complete Digest: sha256:23ff32a1e7d5a10824ab44b24a0daf86c2df1426defe8b162d8376079a548bf2 Status: Downloaded newer image for openresty/openresty:1.13.6.2-alpine
拉取之后可以用docker images 查看到了
Docker Hub 是 Docker 官方建立的中央镜像仓库,除了普通镜像仓库的功能外,它内部还有更加细致的权限管理,支持构建钩子和自动构建,并且有一套精致的 Web 操作页面。
Docker Hub 的地址是:hub.docker.com/
可以在docker hub上拉取别人配置好的镜像:
  • OFFICIAL 代表镜像为 Docker 官方提供和维护,相对来说稳定性和安全性较高
  • STARS 代表镜像的关注人数,这类似 GitHub 的 Stars,可以理解为热度
  • PULLS 代表镜像被拉取的次数,基本上能够表示镜像被使用的频度
也可以在CLI中 docker search 这个命令搜索 Docker Hub
管理镜像
我们所提到的 docker images 可以列出本地 Docker 中的所有镜像外,如果我们要获得镜像更详细的信息,我们可以通过 docker inspect 这个命令
使用sudo docker inspect nginx(名称也可以换成image ID 或者容器ID)
删除镜像:
sudo docker rmi ubuntu:latest
sudo docker rmi ubuntu:latest Untagged: ubuntu:latest Untagged: ubuntu@sha256:de774a3145f7ca4f0bd144c7d4ffb2931e06634f11529653b23eba85aef8e378 Deleted: sha256:cd6d8154f1e16e38493c3c2798977c5e142be5e5d41403ca89883840c6d51762 Deleted: sha256:2416e906f135eea2d08b4a8a8ae539328482eacb6cf39100f7c8f99e98a78d84 Deleted: sha256:7f8291c73f3ecc4dc9317076ad01a567dd44510e789242368cd061c709e0e36d Deleted: sha256:4b3d88bd6e729deea28b2390d1ddfdbfa3db603160a1129f06f85f26e7bcf4a2 Deleted: sha256:f51700a4e396a235cee37249ffc260cdbeb33268225eb8f7345970f5ae309312 Deleted: sha256:a30b835850bfd4c7e9495edf7085cedfad918219227c7157ff71e8afe2661f63
删除镜像的过程其实是删除镜像内的镜像层,在删除镜像命令打印的结果里,我们可以看到被删除的镜像层以及它们的 ID。当然,如果存在两个镜像共用一个镜像层的情况,你也不需要担心 Docker 会删除被共享的那部分镜像层,只有当镜像层只被当前被删除的镜像所引用时,Docker 才会将它们从硬盘空间中移除。
docker rmi 命令也支持同时删除多个镜像,只需要通过空格传递多个镜像 ID 或镜像名即可。
运行和管理容器:
我们可以看到,Docker 容器的生命周期里分为五种状态,其分别代表着:
  • Created:容器已经被创建,容器所需的相关资源已经准备就绪,但容器中的程序还未处于运行状态。
  • Running:容器正在运行,也就是容器中的应用正在运行。
  • Paused:容器已暂停,表示容器中的所有程序都处于暂停 ( 不是停止 ) 状态。
  • Stopped:容器处于停止状态,占用的资源和沙盒环境都依然存在,只是容器中的应用程序均已停止。
  • Deleted:容器已删除,相关占用的资源及存储在 Docker 中的管理信息也都已释放和移除。
创建容器:
sudo docker create nginx
f6e154a473645fb8e857f9e0e0f8dbe166f3bd09a7acb0a999d32487e2a65987
执行 docker create 后,Docker 会根据我们所给出的镜像创建容器,在控制台中会打印出 Docker 为容器所分配的容器 ID,此时容器是处于 Created 状态的。
可以使用容器ID 也可以使用容器名来操作容器,给容器命名 需要在创建的时候 添加--name选项
sudo docker create --name nginx nginx:1.12
启动容器
通过 docker create 创建的容器,是处于 Created 状态的,其内部的应用程序还没有启动,所以我们需要通过 docker start 命令来启动它
sudo docker start nginx
由于我们为容器指定了名称,这样的操作会更加自然。
当容器启动后,其中的应用就会运行起来,容器的几个生命周期也会绑定到了这个应用上,这个之前我们已经提及,这里就不在赘述。只要应用程序还在运行,那么容器的状态就会是 Running,除非进行一些修改容器的操作
我们通过 docker run 这个命令将 docker create 和 docker start 这两步操作合成为一步,进一步提高工作效率。
$ sudo docker run --name nginx -d nginx:1.12 89f2b769498a50f5c35a314ab82300ce9945cbb69da9cda4b022646125db8ca7
这里需要注意的一点是,通常来说我们启动容器会期望它运行在“后台”,而 docker run 在启动容器时,会采用“前台”运行这种方式,这时候我们的控制台就会衔接到容器上,不能再进行其他操作了。我们可以通过 -d 或 --detach 这个选项告诉 Docker 在启动后将程序与控制台分离,使其进入“后台”运行。
管理容器
查看容器的状态,docker ps 默认是 处于运行状态的容器,如果要列出所有的 添加 -a 参数
在 docker ps 的结果中,我们可以看到几项关于容器的信息。其中 CONTAINER ID、IMAGE、CREATED、NAMES 大家都比较容易理解,分别表示容器 ID,容器所基于的镜像,容器的创建时间和容器的名称。
结果中的 COMMAND 表示的是容器中主程序 ( 也就是与容器生命周期所绑定进程所关联的程序 ) 的启动命令,这条命令是在镜像内定义的,而容器的启动其实质就是启动这条命令。
结果中的 STATUS 表示容器所处的状态,其值和我们之前所谈到的状态有所区别,主要是因为这里还记录了其他的一些信息。在这里,常见的状态表示有三种:
  • Created 此时容器已创建,但还没有被启动过。
  • Up [ Time ] 这时候容器处于正在运行状态,而这里的 Time 表示容器从开始运行到查看时的时间。
  • Exited ([ Code ]) [ Time ] 容器已经结束运行,这里的 Code 表示容器结束运行时,主程序返回的程序退出码,而 Time 则表示容器结束到查看时的时间
停止和删除容器
sudo docker stop nginx
容器停止后,其维持的文件系统沙盒环境还是存在的,内部被修改的内容也都会保留,我们可以通过 docker start 命令将这个容器再次启动。
sudo docker rm nginx
当我们需要完全删除容器时,可以通过 docker rm 命令将容器进行删除。
正在运行中的容器默认情况下是不能被删除的,我们可以通过增加 -f 或 --force 选项来让 docker rm 强制停止并删除容器,不过这种做法并不推荐
进入容器: 这是重点哟 敲黑板
容器是一个隔离运行环境的东西,它里面除了镜像所规定的主进程外,其他的进程也是能够运行的,Docker 为我们提供了一个命令 docker exec 来让容器运行我们所给出的命令。
sudo docker exec nginx more /etc/hostname
docker exec 命令能帮助我们在正在运行的容器中运行指定命令,这对于服务控制,运维监控等有着不错的应用场景。但是在开发过程中,我们更常使用它来作为我们进入容器的桥梁。
这里说的进入容器,就是通过 docker exec 命令来启动 sh 或 bash,并通过它们实现对容器内的虚拟环境的控制。
由于 bash 的功能要比 sh 丰富,所以在能够使用 bash 的容器里,我们优先选择它作为控制台程序。
sudo docker exec -it nginx bash root@83821ea220ed:/#
-i --interactive 表示保持我们的输入流,只有使用它才能保证控制台程序能够正确识别我们的命令
-t --tty 表示启用一个伪终端,形成我们与 bash 的交互,如果没有它,我们无法看到 bash 内部的执行结果。
衔接到容器
Docker 为我们提供了一个 docker attach 命令,用于将当前的输入输出流连接到指定的容器上。
sudo docker attach nginx
这个命令最直观的效果可以理解为我们将容器中的主程序转为了“前台”运行 ( 与 docker run 中的 -d选项有相反的意思 )。
由于我们的输入输出流衔接到了容器的主程序上,我们的输入输出操作也就直接针对了这个程序,而我们发送的 Linux 信号也会转移到这个程序上。例如我们可以通过 Ctrl + C 来向程序发送停止信号,让程序停止 ( 从而容器也会随之停止 )。
在实际开发中,由于 docker attach 限制较多,功能也不够强大,所以并没有太多用武之地,这里我们就一笔带过.

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注