62 图带你入门 Docker
![62图带你入门Docker](https://static001.geekbang.org/infoq/1d/1d8dca282f250c28238eddae1c5a0528.png)
这周分享的内容是关于 Docker 的基础,大概的内容分为下面的两个部分,另外还做了个视频。
前言
第一趴---Docker 容器圈简介
![](https://static001.geekbang.org/infoq/f1/f144e1820f5f8b5b57343c6557410b0a.png)
第二趴---Docker 基本操作
![](https://static001.geekbang.org/infoq/aa/aab847ec3c2a444e9a29b8f7ad413cfa.png)
----
容器圈
容器这个新生事物,现在还可以说是新生事物吗?对于我们学生而言,我觉得没毛病,你说呢?
容器技术可说重塑了整个云计算市场的形态,带动了一批年轻有为的容器技术儿,不过「容器」这个概念是 Docker 公司发明的么,不是,它只是众多 Pass 项目中的最底层,没人关注的那一部分而已。
什么是 Pass 项目?
Pass 项目之所会被很多公司所接受,自然是因为解放了部分开发人员的劳动力,尽快干玩活儿早点下班。其依赖的就是「应用托管」的能力,在电脑上斗过地主的应该知道,托管了以后就会自动出牌,同样的道理,为了尽量的弥补本地和云上的环境差异,就出现了 Pass 开源项目。
举个例子来说,运维人员小仙云上部署一个 Cloud Foundry 项目,开发人员只需要简单的一行代码就可以实现将本地的应用部署到云上
![](https://static001.geekbang.org/infoq/d2/d2431507788034f8318ef9697f8039b1.png)
就这样一行代码就实现了将本地应用上传到云上,属实很轻松。
那么这个命令执行的基本原理是怎样的?
实际上,我们可以将其最核心的组件理解为一套应用的打包和*分发机制*。云上部署的 Cloud Foundry 会为大部分编程语言定义一种打包的格式,当开发人员执行命令的时候,实际上是将可执行文件和*启动脚本*打包上传到云上的 Coulud Foudry 中,然后 Cloud Foundry 通过相应的调度器选择一个虚拟机的 Agent 将压缩包下载后启动
那如何区分虚拟机中的不同应用呢?
虚拟机一般不可能只跑一个应用,因为这样确实也太浪费资源了,我们可以想想,现在手上的电脑,可以用 Vmvare 导入几个虚拟机,所以诸如 Cloud Foundry 通过引入操作系统的 Cgroups 和 Namespace 等机制,从而来为每个应用单独创建一个叫做「沙盒」的隔离环境,然后在这些「沙盒」中启动应用,通过这样的方法就让虚拟机中应用各自互不干扰,让其自由翱翔,至于 Cgroups 和 Namespace 的实现原理,后续我们再共同的探讨
这里所谓的隔离环境就是「容器」。
那 Docker 和这 Pass 项目的 Cloud Foundry 的容器有啥不一样?
自然不一样,不然现在我们一旦提到容器,想到的不会是 Docker,而是 Cloud Foundry 了吧。Cloud Foundry 的首席产品经理就觉得没什么,毕竟自己放的屁都是香的!
不一样,而且当时还发了一份报告,报告中还写到:“ Docker 不就是使用了 Cgroups 和 Namespace 实现的「沙盒」而已,不用过于关注”。
没想到的是,随后短短的几个月,Docker 项目迅速起飞以至于其他所有 Paas 社区都还没来及反应过来,就已经宣告出局
什么魔力让 Docker 一发不可收拾?
就是提出了镜像的概念。上面我们说过,Paas 提供的一套应用打包的功能,看起很轻松省事,但是一旦使用了 Paas,你就要终身服务于它,毕竟他是提供商,是「爸爸」,用户需要为每个版本,每种语言去维护一个包,这个打包的过程是需要多次的尝试,多次试错后,才能摸清本地应用和远端 Paas 的脾气,从而顺利部署。
而 Docker 镜像恰恰就是解决了打包这一根本问题。
什么是 Docker 镜像?
Docker 镜像也是一个压缩包,只是这个压缩包不只是可执行文件,环境部署脚本,它还包含了完整的操作系统。因为大部分的镜像都是基于某个操作系统来构建,所以很轻松的就可以构建本地和远端一样的环境。
这就很牛皮了,如果我们的应用是在 Centos7 上部署,我们只需要将项目环境部署在基于 Centos7 的环境中,然后无论在哪里去解压这个压缩包,都可以保证环境的一致性。在整个过程中,我们根本不需要进行任何的配置,因为这个压缩包可以保证:本地的环境和云端是一致的,这也是 Docker 镜像的精髓
开发者体验到了 Docker 的便利,从而很快宣布 Paas 时代的结束,不过对于大规模应用的部署,Docker 能否实现在当时还是个问号
就在 2014 年的 DockerCon 上,紧接着发布了自研的「Docker swarm」,*Docker* 就这样 一度奔向高潮,即将就到达了自己梦想之巅。
为什么会推出 Docker Swarm?
虽然 Docker 通过「容器」完成了对 Paas 的「降维打击」,但是 Docker 的目的是:如何让更多的开发者将项目部署在自己的项目上,从技术,商业,市场多方位的争取开发者的群体,为此形成自家的 Paas 平台做铺垫
Docker 项目虽然很受欢迎,就目前看来只是一个创建和启动容器的小工具。需要应该清楚的一点是,用户最终部署的还是他们的网站,服务甚至云计算业务。所以推出一个完整的整体对外提供集群管理功能的 Swarm 势在必行,这个项目中的最大亮点即直接使用了 Docker 原生的 API 来完成对集群的管理。
对于单机项目,只需要执行下面一条语句即可实现容器
![](https://static001.geekbang.org/infoq/1e/1e05d3e0f8523feb6e2a45ee41611bf5.png)
对于多机的项目,只需要执行
![](https://static001.geekbang.org/infoq/26/26e2f0535a7af3fce65f4d177fa754a4.png)
你看,从单机切换到多机,使用的方法也就参数不同而已,所以这样一个原生的「Docker 容器集群管理」一发布就受到大家的青睐。随着自身生态的逐渐完善,借助这一波浪潮并通过各种并购来强大自己的平层能力
要说最成功的案例,非 Fig 项目莫属。之所以这么屌,是因为作者提出了「容器编排」的概念。
什么是容器编排?
其实这也不是什么新鲜内容,比如在 Linux 中的 Makefile 和常见的 SHELL 脚本,它是一种通过工具或者配置来完成一组虚拟机或者关联资源的定义、配置、创建等工具。
容器的编排是怎么样的呢
我们先以 Fig 为例,假设开发人员小黑,现在要部署一个项目,其中包含了应用容器 A,数据库容器 B,负载容器 C,这个时候 Fig 只需要将三个容器定义在一个配置文件,然后指定他们的关联关系,执行下面的命令即可
![](https://static001.geekbang.org/infoq/9c/9c13c367cd153514d91c961bf9b57ce6.png)
当然也可以在 Fig 的配置文件中配置各种容器的副本,然后加上 Swarm 的集群管理功能,这样不就是 Paas 了么。只是这个项目被收购以后,修改名字为 compose 了,后续也会的 compose 进行详细的阐述
----
就这样一个以「鲸鱼」为商标的 Docker,火遍全球,因为它秉持将「开发者」群体放在食物链的顶端。一分钟实现网站的部署,三分钟搭建集群,这么多年以来,很多后端开发者很少将眼光放在 Linux 技术上,开发者们为了深入的了解 Docker 的技术原理,终于将眼光放入诸如 Cgroups 和 Namespace 技术中。
就在这一时之间,后端及云计算领域的大佬都汇聚于这个「小鲸鱼」的身边。随后到了考虑集群的方案,论集群的管理调度能力,还不得不提 Berkeley 的 Mesos,专注于大数据领域,更加关注的是计算密集型的业务。凭借着它天生的两层调度机制,让它很快发布了一个叫做 Marathon 的项目,这个项目随即成为了 Swarm 的有力竞争对手。
这还没完,说了这么久,还没有提到在基础设施领域翘楚的 Google 公司,是的,同在这一年,宣告了一个叫做 Kubernetes 项目的诞生。
随着 Docker 生态的建立,Docker swarm,*Docker compose*,Machine 形成了三件套,此时大量围绕 Docker 项目的网络,存储,监控都涌现。在令人兴奋的背后,是对它更多的担忧,这主要来源于对 Docker 商业化战略的顾虑,Docker 公司对于 Docker 着绝对的权威并在多个场合直接和谷歌,微软对干
其实在 Docker 兴起的时候,谷歌也开源了一个 Linux 容器:Imctfy,在当时这个项目在 Docker 面前真是弟弟,所以向 Docker 公司表示想合作的意愿,Docker 显然不同意,且在之后的不久自己发布了一个容器运行时的库 Libcontainer,可能由于太急躁,其代码可读性极差,不稳定和频繁的变更,让社区叫苦不迭
为了切割 Docker 项目的话语权,决定成立一个中立的基金会。所以于 2015 年将这个 Libcontainer 捐出,并修改名称为 Runc,然后依据 RunC 项目,制定了一套容器和镜像的标准和规范----OCI
什么是 OCI
为了让 Docker 不能太嚣张,其他玩家构建自身平台的时候不依赖于 Docker 项目,提出一个标准和规范----OCI。这一标准并没有改变 Docker 在容器领域一家独大的现状。Google 坐不住了,必须得搞点大招
Google 给 *RedHat* 等伙伴打了电话,说咱们共同牵头发起一个基金会-----CNCF。目的很简单,以 kubernetes 为基础,建立一个以由开源基础设置主导,按照独立基金会方式运营的平台级社区,来对抗 Docker 公司为核心的容器商业生态
为了做好这个事儿,CNCF 必须完成两件事儿
必须在编排领取足够的优势
CNCF 必须以 kubernetes 为核心,覆盖更多的场景
CNCF 如何解决第一个问题----编排能力
Swarm 的无缝集成以及 Mesos 的大规模集群的调度管理能力,很明显,如果继续往这两个方向发展,后面的路不一定好走。所以,kubernetes 选择的方式是 Borg,其基础特性是 Google 在容器化基础设施多年来实践的经验,这也正是项目从一开始就避免了和 Swarm ,mesos 社区同质化的重要手段
看似很有技巧,怎么落地?
RedHat 正好擅长这玩意呀,它能真正的理解开源社区运作和项目研发真谛的合作伙伴。作为 Docker 一方,主要不管的强调「Docker native」,但是由于 kubernetes 没有跟 Swarm 展开同质化的竞争,所以这个「Docker Native」的说法并没有什么杀伤力。反而其独特的设计理念和号召力,让其构建了一个完全与众不同的容器编排管理生态。
就这样很快就把 Swarm 甩在了身后。随机开始探讨第二个问题,CNCF 添加了一系列容器工具和项目,面对这样的压迫,Docker 在 2016 年决定放弃现有的 Swarm 项目,而是将容器编排等全部内置到 Docker 项目中。
而 kubunetes 的应对方案也蛮有意思,开启「民主化架构」,*kubernetes* 为开发者暴露可以扩展的插件机制,让用户可以随意的通过植入代码的方式介入到 kubernetes 的每一个阶段,很快,整个容器圈出现了优秀的作品:火热的微服务治理项目 lstio 等
面对 kubernetes 的 强力攻击,Docker 公司不得不面对失败的事实,只好放弃开源社区专注于商业化转型,所以于 2017 年将容器运行时部分 containerd 捐赠给了 CNCF,从而将 Docker 项目改名为 Moby,然后交给社区维护,于 2017 年,Docker 公司宣布将在企业版内置 kubernetes 项目,这也标志了 kubernetes「编排之争」的结束
----
Docker 能做什么
Docker 是一个用于开发,发布,运行应用的程序于一体的开放平台。如果我们需要将货物整齐的摆放在船上且互不影响,那么一种可行的方案即通过集装箱进行标准化,我们将各种货品通过集装箱打包,然后统一的放在船上进行运输,Docker 其实就是这样一个将各种软件进行打包成集装箱,然后分发。
----
Docker 的安装
Docker 是一个跨平台的解决方案,支持各大平台比如 Centos,*Ubuntu* 等 Linux 发行版。下面讲述的是在 Centos 中的使用,安装
卸载当前已经存在的旧版 Docker,执行下面的命令
![](https://static001.geekbang.org/infoq/bb/bb47e4ff31c488292861d120679945a6.png)
添加 Docker 安装源
![](https://static001.geekbang.org/infoq/0e/0e25c4d20a0c087cd1bd088e0f138347.png)
安装最新版本
![](https://static001.geekbang.org/infoq/c3/c3557f52121f29622e2084b66a8ae9c8.png)
如果需要安装指定版本,可以通过下面命令查看版本并选择需要的版本
![](https://static001.geekbang.org/infoq/61/6165320b92caf586bc19ab72f80335f0.png)
安装完成,启动 Docker
![](https://static001.geekbang.org/infoq/df/df139ed91a66d3cd8d077eb83e93e59c.png)
按照国际案例,先跑一个 helloworld
![](https://static001.geekbang.org/infoq/35/35acf3a62edc3322ad754a667268a73b.png)
运行上述命令,Docker 首先会检查本地是否有 hello-world 这个镜像,如果发现本地没有这个镜像,Docker 就会去 Docker Hub 官方仓库下载此镜像,然后运行它。最后我们看到该镜像输出 "Hello from Docker!" 并退出。
Docker 核心概念
Docker 的操作主要围绕镜像,*容器*,仓库三大核心概念
什么是镜像?
一句话说即镜像是 Docker 容器启动的先决条件,因为镜像会提供容器运行的一些基础文件和配置文件,是容器启动的基础。说白了,要启动容器,需要镜像来提供一些基础环境。
使用的镜像的方式有哪些?
自定义创建镜像。首先找一个基础镜像,比如此镜像是 Centos,然后在此镜像基础上自定义需要的内容。举个例子,基础镜像为 Centos,先安装 Nginx 服务,然后部署咱们的应用程序,最后做一些自定义的配置,这样一个镜像就完成了,此镜像的操作系统是 Centos,其中包含了 Nginx 服务
从仓库寻找别人已经做好的镜像。直接去 Docker hub 或者其他公开仓库 下载即可
什么是容器?
容器是镜像的运行实体。镜像是静态的只读文件,可是容器是要运行的,需要可写文件层。所以容器运行着真正的应用进程,所以自然会有创建,运行,停止,暂停和删除五种状态
既然容器是直接运行的运行程序,那它是有自己的命名空间嘛?
容器有自己独立的命名空间和资源限制,意味着在容器内部,你无法看到主机上面的进程,环境变量等信息,这就是容器和物理机上的进程本质区别
什么是仓库?
镜像仓库类似于代码仓库,用来分发和存储镜像,分为公共镜像和*私有镜像*。Docker hub 是 Docker 的官方公开镜像仓库,很多的官方镜像都可以在上面找到,但是访问很慢,所以可以找国内的镜像源,当然后面我们也会自己搭建一个私有镜像仓库
三者的关系是怎么样的?
![](https://static001.geekbang.org/infoq/81/81da168c794bda8d9edb5922366a82ba.png)
上图清晰的展现了镜像是容器的基石,容器是在镜像的基础上创建的。一个镜像可以创建多个容器,仓库用来存放和分发镜像
Docker 架构
容器技术的发展可说突飞猛进了,市面上除了 Docker 容器还有 coreos 的 rkt,lxc 等,这么多种容器,是不是需要一个标准呢,不然就太容易乱套了
你可能会说直接把 Docker 作为标准不就好了,但是有这么多相关的容器技术,谁不想吃个好瓜,除此之外,当时的编排的技术也竞争火爆,当时的三主力分别是 Docker Swarm,*kubernetes* 以及 mesos。作为原生的 Docker Swarm 自然优势明显,但是 kubernetes 不同意啊,它们觉得调度形式太单一了
因此爆发了容器大战,OCI 也就在此出现。
OCI 是开放的容器标准,轻量级开放的治理结构,目前主要有两个标准,分别是容器运行时标准和容器镜像标准
在如此竞争激烈下面,Docker 的架构成为了下面这个样子
![](https://static001.geekbang.org/infoq/51/51e2892a1331ed8c0d121994e6f5197a.png)
Docker 的整体架构为 CS 架构,客户端和服务端两部分组成,客户端发送命令,服务端接受处理指令,其通信的方式有多种,即可以通过 Unix 套接字通信,也可以网络链接远程通信
Docker 客户端
我们平时通常使用 Docker 命令行的方式和服务端打交道,其实还可以通过 REST API 的方式和 Docker 服务端交互,甚至使用各种预言的 sdk 和 Docker 的服务端交互,美滋滋
Docker 服务端
Docker 服务端是 Docker 后台服务的总称。其中 Dockerd 是一个非常重要的后台进程,它负责响应并处理 Docker 客户端的请求,然后转化为 Docker 的具体操作
Docker 重要的组件
我们去 Docker 默认安装路径先看看有哪些组件
![](https://static001.geekbang.org/infoq/79/79dda54b66ce8252bd3e1c1b3257bad7.png)
这里主要说明下 runc 和 contained 组件
runc:是一个用来运行容器的轻量级工具
contained:是容器标准化后的产物,从 Dockerd 剥离出来,contained 通过 contained-shim 启动并管理 runc,可以说 contained 是真正管理容器的生命周期
![](https://static001.geekbang.org/infoq/2b/2bd8763871c118e4b0245941eadf7387.png)
通过上图,可以看到,dockerd 通过 gRPC 与 containerd 通信,由于 dockerd 与真正的容器运行时,runC 中间有了 containerd 这一 OCI 标准层,使得 dockerd 可以确保接口向下兼容。
gRPC 是一种远程服务调用。containerd-shim 的意思是垫片,类似于拧螺丝时夹在螺丝和螺母之间的垫片。containerd-shim 的主要作用是将 containerd 和真正的容器进程解耦,使用 containerd-shim 作为容器进程的父进程,从而实现重启 dockerd 不影响已经启动的容器进程。
docker 各个组件之间的关系
启动一个容器
![](https://static001.geekbang.org/infoq/9a/9aec0bbafe5587a6c4430779c8d82e8f.png)
启动完成,通过下面命令查看 docker 的 pid
![](https://static001.geekbang.org/infoq/36/36c6eebb77276a0c6b87665532644247.png)
此时发现其 pid 为 4428,随后我们查看进程之间的关系
通过 pstree 查看进程之间的关系
![](https://static001.geekbang.org/infoq/c3/c3f3a1063f0404ab01ed09031e142125.png)
注意,docker19 就看不到两者是父子关系了
可以先使用 ps aux | grep contained ,然后使用 pstree 查看 contained 的 pid ,实际上,Docker 启动的时候,contained 就启动了,dockerd 和 contained 一直就存在。当执行了 docker run 以后,contained 就会创建 contained-shim 充当垫片进程,然后启动容器的真正进程 sleep 3600,这和架构图一致
Docker 相关组件
docker
对于我们最直观的即 Docker 命令,作为 Docker 客户端的完整实现,通过 Docker 命令来实现所有的 Docker 客户与服务端的通信
Docker 客户端于服务端的交互过程是怎么样的呢
Docker 组件向服务端发送请求后,服务端根据请求执行具体的动作并将结果返回给 Docker,*Docker* 解析服务端的返回结果,并将结果通过命令行标准输出展示给用户。这样一次完整的客户端服务端请求就完成了
dockerd
dockerd 为 Docker 服务端后台的常驻进程,负责接收客户端的请求,处理具体的任务并将结果返回客户端
那么 Docke r 客户端采用哪几种方式发送请求
第一种方式:通过 unix 套接字与服务端通信,配置的格式为:unix://socket_path
。默认的 dockerd 生成的 socket 文件存放在 /var/run/docker.sock
,此文件只能是 root 用户才能访问,这也是为什么刚安装完 Docker 后只能 root 来进行访问操作
第二种方式:采用 TCP 的方式与服务端通信,配置格式为:tcp://host:por
,为了保证安全,通常还需要使用 TLS 认证
第三种方式:通过 fd 文件描述符的方式,配置格式为:fd://这种格式一般用于 systemd 管理的系统中。
docker-init
在 Linux 中,有一个叫做 init 的进程,是所有进程的父进程,用来回收那些没有回收的进程,同样的道理,在容器内部,可以通过加上参数 --init
的方式,让 1 号进程管理所有的子进程,例如回收僵尸进程
举个例子示范,以镜像 busybox 为例
![](https://static001.geekbang.org/infoq/83/8302e53b1a508f7acfc3e0e62ce9fdd2.png)
此时的 1 号进程为为 sh 进程,如果加上 --init
![](https://static001.geekbang.org/infoq/9d/9db91bcb31b6a8007022ca7cf19495fe.png)
你会发现,此时的 1 号进程为 docker-init,而不是 sh 了
docker-proxy
docker-proxy 用来将容器启动的端口映射到主机,方便主机的访问。
假设目前启动一个 nginx 容器并将容器的 80 端口映射到主机的 8080 端口
![](https://static001.geekbang.org/infoq/5c/5cfb0be6ed66bd49d3bbf98d78b449dd.png)
查看容器 IP
![](https://static001.geekbang.org/infoq/3f/3f2abfec2f4ba48470a62f1df89656ec.png)
此时使用 ps 查看主机是否有 docker-proxy 进程
![](https://static001.geekbang.org/infoq/a6/a6741b0841f8582a6c20cba92cd7dd27.png)
可以发现当进行端口映射的时候,docker 为我们创建了一个 docker-proxy 进程,并且通过参数将容器的 IP 和端口传递给 docker-proxy,然后 proxy 通过 iptables 完成 nat 的转发
![](https://static001.geekbang.org/infoq/c6/c6206b46ec5055c5827671a51b6d05be.png)
从最后一句可以看出,当我们主机访问 8080 端口的时候,iptable 将流量会转发给 172.17.0.2
的 80 ,从而实现主机上直接访问容器的业务
使用 curl 访问一下 nginx 容器
![](https://static001.geekbang.org/infoq/ad/adff27ad67f25ca9cfe375c265816204.png)
contained 组件
containerd
contained 主要负责容器的生命周期管理,同时还会负责一些其他的功能
主要负责那些功能?
镜像的管理
接收 dockerd 的请求
管理存储相关资源
管理网络资源
containerd-shim
containerd-shim
的意思是垫片,类似于拧螺丝时夹在螺丝和螺母之间的垫片。containerd-shim
的主要作用是将 containerd
和真正的容器进程解耦,使用 containerd-shim
作为容器进程的父进程,从而实现重启 containerd
不影响已经启动的容器进程。
ctr
ctr
实际上是 containerd-ctr
,它是 containerd
的客户端,主要用来开发和调试,在没有 dockerd
的环境中,ctr
可以充当 docker
客户端的部分角色,直接向 containerd
守护进程发送操作容器的请求。
Docker 镜像使用
来,继续,我们看看镜像是什么。镜像是一个只读的镜像模版且包含了启动容器所需要的文件结构内容。镜像不包含动态数据,构建完成将不会改变
对于镜像都有哪些操作?
![](https://static001.geekbang.org/infoq/5f/5faf1f8eb9988f3d29a14ad6a4c57e9c.png)
对于镜像的操作分为:
拉取镜像:通过 docker pull 拉取远程仓库的镜像
重命名镜像:通过 docker tag 重命名镜像
查看镜像:通过 docker image ls 查看本地已经存在的镜像
删除镜像:通过 docekr rmi 删除没有用的镜像
构建镜像
- 第一种是通过 docker build 命令基于 dockerfile 构建镜像,推荐
- 第二种是通过 docker commit 基于运行的容器提交为镜像
拉取镜像
拉取镜像直接使用 docker pull 命令即可,命令的格式如下
![](https://static001.geekbang.org/infoq/9f/9f7c5a3652c0165565a76bc44183760e.png)
registry 为注册的服务器,docker 默认会从官网 docker.io 上拉取镜像,当然可以将 registry 注册为自己的服务器
repository 为镜像仓库,library 为默认的镜像仓库
image 为镜像的名称
tag 为给镜像打的标签
现在举个例子,这里有个镜像叫做 busybox,这个镜像集成了上百个常用的 Linux 命令,可以通过这个镜像方便快捷的查找生产环境中的问题,下面我们一起操作一波
docker pull busybox
首先会在本地镜像库查找,如果不存在本地库则直接去官网拉取镜像。拉取完镜像后随即查看镜像
查看镜像---docker images
如果要查看指定的镜像,则使用 docker image ls 命令进一步查询
重命名镜像采用打标签的方式重命名,格式如下
![](https://static001.geekbang.org/infoq/0e/0e875c798713fa586628bfb44cd9dbea.png)
我们仔细观察这两个镜像,就会发现这两个镜像的 IMAGE ID 其实是一样的,这是什么原因呢
实际上他们都是指向的同一个镜像文件,只不过其别名不一样而已,如果此时不想要 mybox 镜像,想删除这个镜像
使用 docker rmi 删除镜像
此时再次使用 docker images 查看确实删除了
如何自己构建自己镜像呢
之前说过,有两种方式,一种是通过 docker commit 的方式,一种是 docker build 的方式。首先看看使用容器提交镜像的方式
![](https://static001.geekbang.org/infoq/fd/fd8dfa9183e8da13f734ebce47b91dcb.png)
此时启动了一个 busybox 容器并进入到容器,并在容器中创建一个文件,并写入内容
![](https://static001.geekbang.org/infoq/e5/e5298559560e21df33b7c6803a4ccbb4.png)
此时就在当前目录下创建了一个 hello.txt 文件并写入了内容。现在新建另外一个窗口,然后提交为一个镜像
![](https://static001.geekbang.org/infoq/1d/1dbb04b45091523ac73bdc055f99fb07.png)
然后使用 docker image ls 查看发现确实生成了镜像
然后我们再看看使用 dockerfile 的方式
dockerfile 的每一行的命令都会生成独立的镜像层并拥有唯一的 id
dockerfile 命令是完全透明的,通过查看 dockerfile 的内容,就可以知道镜像是怎么一步步构建的
dockerfile 为纯文本,方便做版本控制
先看看都有哪些命令
![](https://static001.geekbang.org/infoq/8f/8ffb61d81f563f9cfee306ee870cee72.png)
这么多,不存在的,我们先看一个 dockerfile 就知道如何用了
![](https://static001.geekbang.org/infoq/ae/ae0bdb3cc81f09493b593cd97cb56cca.png)
首先第一行表示基于什么镜像构建
第二行是拷贝文件
nginx
。repo
到容器内的/etc/yum.repos.d
第三行为容器中运行
yum install
命令,安装nginx
命令到容器
第四行为生命容器使用 80 端口对外开放
第五行定义容器启动时的环境变量
HOST=mynginx
,容器启动后可以获取到环境变量HOST
的值为mynginx
。
第六行定义容器的启动命令,命令格式为
json
数组。这里设置了容器的启动命令为nginx
,并且添加了nginx
的启动参数-g 'daemon off;'
,使得nginx
以前台的方式启动。
基本操作已经会了,现在我们看看镜像的实现原理
![](https://static001.geekbang.org/infoq/d8/d8d1cff256bc861063a807a1db8e9b2e.png)
第一行:创建一个
busybox
镜像层
第二行:拷贝本机
test
文件到镜像内
第三行 在
tmp
文件夹创建一个目录testdir
为了清楚的看见镜像的存储结构,通过 docker build
构建镜像
![](https://static001.geekbang.org/infoq/30/30b667c51bac9703870dabc89c5a5d96.png)
因为我的 docker
使用的是 overlay2
文件驱动,所以进入到 /var/lib/docker/overlay2
,使用 tree
查看
可以清楚的看到,dockerfile
的每一行命令都会生成一个镜像层
![](https://static001.geekbang.org/infoq/94/94c112145bdd2b44cbcd0b5e6bbcdf50.png)
Docker 容器操作
我们通过一个镜像可以轻松的创建一个容器,一个镜像可以有多个容器,在运行容器的时候,实际上是在容器内部创建了这个文件系统的读写副本,如下图所示
![](https://static001.geekbang.org/infoq/83/833d54caee87319422108e647f3bac14.png)
容器的生命周期是怎么样的?
容器的生命周期一共有五个状态分别为
created 初建状态
running 运行状态
stopped 停止状态
opaused 暂停状态
deleted 删除状态
通过 docker cretate 进入容器的初建状态,然后通过 docker start 进入运行状态,通过 docker stop 进入停止状态,运行状态的容器可以通过 docker pause 让其变为暂停状态,为了详细的查看这些过程,我们实操一下
创建并启动容器
![](https://static001.geekbang.org/infoq/c0/c0517759f9c7ed0eb537f7a86a445b9a.png)
通过 docker create 创建的容器处于停止的状态,使用 docker start busybox 进入启动状态
![](https://static001.geekbang.org/infoq/74/744554c2df16035b06e775d304b565a4.png)
当使用 docker run 创建并启动容器的时候,docker 后台的执行逻辑为
首先检查本地是否有 busybox 镜像,不存在则取 dockerhub 中拉取
使用 busybox 镜像启动一个容器
分配文件系统,并在镜像的只读层外创建一个读写层
从 docker ip 池分配个 ip 给容器
运行镜像
可以进入交互模式么
同时使用 -it
参数可以让我们进入交互模式,容器内部和主机是完全隔离的。另外由于此时的 sh
为 1 号进程,所以如果通过 exit
退出 sh
,那么容器也就退出,所以对于容器而言,杀死容器中的主进程,那么容器也就会被杀死
通过 docker stop 停止容器,其原理是给运行中的容器给 sigterm
信号,如果容器为 1 号进程接受并处理sigterm
,则等待 1 号进程处理完毕后就退出,如果等待一段时间后还是没有处理,则会通过发送 sigkill 命令强制终止容器
如何进入容器?
想要进入容器,有三种方案,分别是 docker attach
,docker exec
,nsenter
等
使用 docker attach 方式进入容器
![](https://static001.geekbang.org/infoq/94/949f800ba02c12d2e02a153fa250ee8d.png)
通过 docker ps -a
查看当前的进程信息
![](https://static001.geekbang.org/infoq/a7/a7f05986505e3071d70285aadf448521.png)
可是当我们在进行窗口进行 docker attach 的时候,这个命令就不好用了,所以使用 docker exec 的方式
使用
docker exec
进入容器
![](https://static001.geekbang.org/infoq/6b/6b2c8b7b94c8571cf894b77283049950.png)
奇怪的发现居然是两个 sh
进程,主要是因为,当我们使用 docker exec
方式进入容器的时候,会单独启动一个 sh
进程,此时的多个窗口都是独立且不受干扰,也是非常常用的方式
删除容器
现在基本上知道了如何创建,启动容器,那么怎么删除容器呢
使用 docker rm 的方式删除容器
![](https://static001.geekbang.org/infoq/a9/a98c4219ef495d9004441828a09846a6.png)
如果此时,容器正在运行,那么需要添加 -f
的方式停止正在运行的容器
如果想要导出容器怎么操作呢
这简单,不过在导出之前先进入容器创建一个文件
![](https://static001.geekbang.org/infoq/26/2607cff018f60cb027c110dcf038f30f.png)
然后导出为文件
![](https://static001.geekbang.org/infoq/a3/a3f7102bed5d3b98caca515b0fae2a2f.png)
此时会在当前目录生成一个 busybox.tar
文件,此时就可以将其拷贝到其他的机器上使用
那如何导入容器呢
通过 docker import
的方式导入,然后使用 docker run
启动就完成了容器的迁移
![](https://static001.geekbang.org/infoq/36/36e16cab07d80f4cc7c8a01ba6198594.png)
此时容器名称为 busybox:test
,然后我们使用 docker run
启动并进入容器
![](https://static001.geekbang.org/infoq/1c/1c83b018e73e5185525fefedf02de76b.png)
此时发现之前在 /tmp
创建的目录也被迁移了过来
仓库
容器的基本操作应该都会了,那么我们应该如何去存储和分发这些镜像,这就需要介绍下仓库;
我们可以使用共有镜像仓库分发,也可以搭建私有的仓库
仓库是啥玩意
钱钱仓库放钱,这个仓库放镜像。Github
放代码,我们理解镜像的仓库可以联想 Github
仓库。
在学习的过程中,不太能区分注册服务器和仓库的关系。注册服务器其实是用来存放仓库的实际机器,而仓库我们可以将其理解为具体的项目或者目录。一个注册服务器可以存放多个仓库,每个仓库可以存放多个镜像
公有仓库
Docker hub 是当前全球最大的镜像市场,差不多超过 10w 个容器镜像,大部分操作系统镜像都来自于此。
![](https://static001.geekbang.org/infoq/14/14fdc9452a9241e33d7ecefafd3be3e4.png)
如何使用公共镜像仓库和存储镜像
注册 Docker hub
![](https://static001.geekbang.org/infoq/71/71225e38a1cbe00853485be8c6f053e1.png)
创建仓库
![](https://static001.geekbang.org/infoq/b3/b32c9d4fa4bd47c248097787d42deaec.png)
实战镜像推送到仓库
此时假设我的账户是 xiaolantest,创建一个 busybox 的仓库,随后将镜像推送到仓库中。
第一步:拉取 busybox 镜像
![](https://static001.geekbang.org/infoq/0f/0f899ad977383e454076d994b6d800a4.png)
第二步:推送镜像之前先登录镜像服务器(注意用户名密码哦),出现 login Succeeded 表示登录成功
![](https://static001.geekbang.org/infoq/80/8077d9c964ae335eaa4ea66fcc1bfcd8.png)
第三步:推送之前还要做一件事,重新对镜像命名,这样测能正确的推动到自己创建的仓库中
![](https://static001.geekbang.org/infoq/1b/1b6d5c3fed5339bc4afdeb104d8aaf2c.png)
第四步:docker push 到仓库中
![](https://static001.geekbang.org/infoq/8a/8a1e7a66829dc0041278e9a17a37f7da.png)
私有仓库
Docker 官方提供了开源的镜像仓库 Distribution,镜像存放于 Docker hub 的 Registry 中
启动本地镜像仓库
![](https://static001.geekbang.org/infoq/8a/8a1e7a66829dc0041278e9a17a37f7da.png)
使用 docker ps 查看启动的容器
![](https://static001.geekbang.org/infoq/97/97ef6796b4c54bb782c7b2acf904e4bb.png)
重命名镜像
此时 Docker 为busybox
镜像创建了一个别名localhost:5000/busybox
,localhost:5000
为主机名和端口,Docker 将会把镜像推送到这个地址。
![](https://static001.geekbang.org/infoq/cf/cf2f5351c4ce4c429ada2ae345bad57f.png)
推送镜像到本地仓库
![](https://static001.geekbang.org/infoq/bb/bbda0210db33722d37a9628465d6298d.png)
删除之前存在的镜像
此时,我们验证一下从本地镜像仓库拉取镜像。首先,我们删除本地的busybox
和localhost:5000/busybox
镜像。
![](https://static001.geekbang.org/infoq/fb/fb791c64d9296a9aa82a0978bd8f6636.png)
查看当前本地镜像
![](https://static001.geekbang.org/infoq/12/122e995f2563666741f0309b9023c457.png)
可以看到此时本地已经没有busybox
这个镜像了。下面,我们从本地镜像仓库拉取busybox
镜像:
![](https://static001.geekbang.org/infoq/40/402c1ec654a12ee287547937e3a648a5.png)
随后再使用 docker image ls busybox
命令,这时可以看到我们已经成功从私有镜像仓库拉取 busybox
镜像到本地了
总结
不知不觉,每周下班开始写,做实验,截图,至此也就告一段落了,开篇思维导图由于完整版太大,我计划一共分为四篇文章,分别为 Docker 基本认识,Docker 实战,Docker 底层原理以及 Docker 集群安全四个方面来分享,后续精简后再给大家。本篇文章从 Docker 容器圈到基本使用,写的应该蛮清楚了,另外说明一下,由于直接使用代码很可能导致格式排版混乱,所以全部采用截图的方式,更加直观和清晰,当然如有不妥之处也望大家指正。点赞,转发,收藏,感谢!我们下期见!
版权声明: 本文为 InfoQ 作者【我是程序员小贱】的原创文章。
原文链接:【http://xie.infoq.cn/article/de954030a32531bd39cd6c7b6】。文章转载请联系作者。
评论