1. 容器及Docker基础知识
# 容器
容器是一种基础工具;泛指任何可以用于容纳其他物品的工具,可以部分或完全封闭,被用于容纳、储存、运输物品;物体可以被放置在容器中,而容器则可以保护内容物。
比如,通常用的带杯盖的水杯,就是一个容器,它的用途是装水,更确切的解释就是将水约束起来,当盖上杯盖后,就算水杯里的水翻江倒海,也不会溢出水杯外。这里的水杯就是容器,而水则是程序的进程,容器就是将进程约束起来,就算进程异常或者被劫持也不会影响到系统中其他进程的运行,这就是容器的作用。
什么是 Docker
IT 软件中所说的 docker,是指容器化技术,用于支持创建和使用 Linux 容器。
Docker 的思想来自于集装箱,集装箱解决了什么问题? 在一艘大船上,可以把货物规整的摆放起来。并且各种各样的货物被集装箱标准化了,集装箱和集装箱之间不会相互影响。那么我就不需要专门运送水果的船和专门运送化学品的船了。只要这些货物在集装箱里封装的好,那么我就可以用一艘大船把它们都运走。
docker 就是类似的概念。云计算就好比这艘大船,docker就是集装箱。
docker 使用 google 公司推出的 Go 语言开发实现,基于 Linux 内核的 Cgroup、namespace,以及 OverlayFS 等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术。由于隔离的进程独立于宿主机和其他的隔离进程,因此也称为容器。最初实现是基于 LXC ,从 0.7 版本以后开始去除 LXC ,转而使用自行开发的 libcontainer,从 1.11 版本开始,则进一步演进为使用 runC 和 containerd。
Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。它是目前最流行的 Linux 容器解决方案。
Cgroups
容器的隔离技术源自 Linux 容器 LXC(linux container),而LXC 又是基于 cgroup 的 namespace,chroot等。
cgroup 对于容器至关重要,cgroup 全称为 control group ,是 Linux 内核提供的功能,简单来说,它的作用就是把系统运行的进程按用户自定义的群组区分,也就是说一个容器,就是一个group。
Cgroups 是什么
cgroups 的全称:control groups 。cgroups 是Linux内核提供的一种可以限制单个进程或者多个进程所使用资源的机制,可以对 cpu,内存等资源实现精细化的控制,目前越来越火的轻量级容器 docker 就使用了 cgroups 提供的资源限制能力来完成 cpu,内存等部分的资源控制。
Cgroups 主要作用
cgroup 有限制使用资源的能力:
子系统 | 介绍 |
---|---|
blkio | 为块设备指定 输入/输出限制,比如物理设备(磁盘、固态硬盘、USB等等) |
cpu | 使用调度程序提供对 CPU 的 cgroup 任务访问 |
cpuacct | 自动生成 cgroup 中任务所使用的 CPU 报告 |
cpuset | 为cgroup中的任务分配独立CPU(多核系统)和内存节点 |
devices | 允许或者拒绝 cgroup 中的任务访问设备 |
freezer | 挂起或者恢复 cgroup 中的任务 |
memory | 设定 cgroup中任务使用的内存限制,并自动生成由哪些任务使用的内存资源报告 |
net_cls | 使用等级标识符(classid)标记网络数据包 |
ns | 名称空间 |
这里面每一个子系统都需要与内核的其他模块配合来完成资源的控制,比如对 cpu 资源的限制是通过进程调度模块根据 cpu 子系统的配置来完成的;对内存资源的限制则是内存模块根据 memory 子系统的配置来完成的,而对网络数据包的控制则需要 Traffic Control 子系统来配合完成。
Namespace
Namespace 是什么
Linux Namespace是Linux提供的一种内核级别环境隔离的方法。不知道你是否还记得很早以前的Unix有一个叫chroot的系统调用 (通过修改根目录把用户jail到一个特定目录下) ,chroot提供了一种简单的隔离模式:chroot内部的文件系统无法访问外部的内容。 Linux Namespace在此基础上,提供了对UTS、IPC、mount、PID、network、User等的隔离机制。
一个隔离用户空间应该看到的以下组件:
- UTS - 主机名和域名
- 根文件系统
- IPC(进程间通信专用通道)在同一个隔离空间内是可以通信的,跨隔离空间是无法通信的。
- PID 需要为每个隔离口空间伪装一个PID为 1 的进程
- User、Group 需要单独做隔离
- Network
Linux 开始在内核级把这些资源作为可以切分为多个相互隔离的空间,就称为:名称空间。
为了支撑容器机制的实现,Linux内核级对以上 6种名称空间的切分原生支持,叫做 namespaces
,直接通过系统调用向外输出。整个 Linux 领域的容器化技术就是靠内核级的 6 个 namespaces
与 chroot
来实现的。
namespace | 系统调用参数 | 隔离内容 | 相关内核版本 |
---|---|---|---|
Mount namespaces | CLONE_NEWNS | 挂载点(文件系统) | Linux 2.4.19 |
UTS namespaces | CLONE_NEWUTS | 主机名和域名 | Linux 2.6.19 |
IPC namespaces | CLONE_NEWIPC | 信号量、消息队列和共享内存 | Linux 2.6.19 |
PID namespaces | CLONE_NEWPID | 进程编号 | Linux 2.6.24 |
Network namespaces | CLONE_NEWNET | 网络设备、网络栈、端口等 | 始于Linux 2.6.24 完成于 Linux 2.6.29 |
User namespaces | CLONE_NEWUSER | 用户和用户组 | 始于 Linux 2.6.23 完成于 Linux 3.8) |
有了 Cgroups
和 namespaces
就可以愉快的使用容器了。 实际上容器的隔离能力相比主机级虚拟化来说,差很多。毕竟多个容器同属于一个内核,只是在内核空间强行设置了边界,而不像主机级虚拟化,大家原本就不属于同一个内核,容器的隔离远不如主机级虚拟化好。因此为了避免这种问题,实用了类似 selinux
等这样的机制来加强容器的边界管理。
docker容器的原则是一个容器只运行一个进程。
为什么使用 Docker
作为一种新兴的虚拟化方式,Docker
跟传统的虚拟化方式相比具有众多的优势。 这个疑问可以通过,docker 和 传统的虚拟化进行对比来探讨。
Linux 容器是 Linux 发展出的另一种虚拟化技术,Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离,相当于是在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。
虚拟机与容器的比较
更高效的利用系统资源
由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销, Docker
对系统资源的利用率更高。 无论是应用执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以运行更多数量的应用。
更快速的启动时间
传统的虚拟机技术启动应用服务往往需要数分钟,而 Docker
容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。
一致的运行环境
开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致,导致有些 bug 并未在开发过程中被发现。 而 Docker
的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 「这段代码在我机器上没问题啊」 这类问题。
持续交付和部署
对开发和运维( devops) 人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。
使用 Docker
可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员可以通过 dockerfile 来进行镜像构建,并结合 持续集成系统进行集成测试,而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合持续部署系统进行自动部署。
而且使用 dockerfile 使镜像构建透明化,不仅仅开发团队可以理解应用运行环境,也方便运维团队理解应用运行所需条件,帮助更好的生产环境中部署该镜像。
更轻松的迁移
由于 Docker
确保了执行环境的一致性,使得应用的迁移更加容易。Docker
可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。
更轻松的维护和扩展
Docker
使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,Docker
团队同各个开源项目团队一起维护了一大批高质量的 官方镜像(hub.docker.com), 既可以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的镜像制作成本。
**对比传统虚拟机总结 **
特性 | 容器 | 虚拟机 |
---|---|---|
启动 | 秒级 | 分钟级 |
硬盘使用 | 一般为 MB | 一般为 GB |
性能 | 接近原生 | 弱于 |
系统支持量 | 单机支持上千个容器 | 一般几十个 |
在什么场景下使用docker
八个 docker 的真实应用场景。
简化配置
这是Docker公司宣传的Docker的主要使用场景。 虚拟机的最大好处是能在你的硬件设施上运行各种配置不一样的平台(软件、系统),Docker在降低额外开销的情况下提供了同样的功能。 它能让你将运行环境和配置放在代码中然后部署,同一个Docker的配置可以在不同的环境中使用,这样就降低了硬件要求和应用环境之间耦合度。
代码流水线管理
前一个场景对于管理代码的流水线起到了很大的帮助。代码从开发者的机器到最终在生产环境上的部署,需要经过很多的中间环境。而每一个中间环境都有自己微小的差别,Docker给应用提供了一个从开发到上线均一致的环境,让代码的流水线变得简单不少。
提高开发效率
理想状态中,要达到第一个目标,我们需要将每一个服务都跑在独立的虚拟机中以便监控生产环境中服务的运行状态。 然而,我们却不想每次都需要网络连接,每次重新编译的时候远程连接上去特别麻烦。 这就是Docker做的特别好的地方,开发环境的机器通常内存比较小,之前使用虚拟的时候,我们经常需要为开发环境的机器加内存,而现在Docker可以轻易的让几十个服务在Docker中跑起来。
隔离应用
有很多种原因会让你选择在一个机器上运行不同的应用,比如之前提到的提高开发效率的场景等。
我们经常需要考虑两点,一是因为要降低成本而进行服务器整合,二是将一个整体式的应用拆分成松耦合的单个服务(译者注:微服务架构)。
整合服务器
正如通过虚拟机来整合多个应用,Docker隔离应用的能力使得Docker可以整合多个服务器以降低成本。 由于没有多个操作系统的内存占用,以及能在多个实例之间共享没有使用的内存,Docker可以比虚拟机提供更好的服务器整合解决方案。
调试能力
Docker提供了很多的工具,这些工具不一定只是针对容器,但是却适用于容器。它们提供了很多的功能,包括可以为容器设置检查点、设置版本和查看两个容器之间的差别,这些特性可以帮助调试Bug。
多租户环境
一个Docker有意思的使用场景是在多租户的应用中,它可以避免关键应用的重写。我们一个特别的关于这个场景的例子是为IoT(译者注:物联网)的应用开发一个快速、易用的多租户环境。这种多租户的基本代码非常复杂,很难处理,重新规划这样一个应用不但消耗时间,也浪费金钱。 使用Docker,可以为每一个租户的应用层的多个实例创建隔离的环境,这不仅简单而且成本低廉,当然这一切得益于Docker环境的启动速度和其高效的diff
命令。
快速部署
在虚拟机之前,引入新的硬件资源需要消耗几天的时间。虚拟化技术(Virtualization)将这个时间缩短到了分钟级别。而Docker通过为进程仅仅创建一个容器而无需启动一个操作系统,再次将这个过程缩短到了秒级。这正是Google和Facebook都看重的特性。
Docker 的基础概念
docker 的三个基本概念:
从上图可以看到,Docker 中包括三个基本的概念:
- images(镜像)
- container(容器)
- repository(仓库)
镜像是 Docker 运行容器的前提,仓库是存放镜像的场所,可见镜像更是 docker 的核心。
image(镜像)
Docker 镜像可以看作是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
从左边看到了多个只读层,它们重叠在一起。除了最下面一层,其它层都会有一个指针指向下一层。这些层是Docker 内部的实现细节,并且能够在主机的文件系统上访问到。统一文件系统 (union file system) 技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。可以在图片的右边看到这个视角的形式。
container(容器)
容器 (container) 的定义和镜像 (image) 几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的。
由于容器的定义并没有提及是否要运行容器,所以实际上,容器 = 镜像 + 读写层。
Repositry(仓库)
Docker 仓库是集中存放镜像文件的场所。镜像构建完成后,可以很容易的在当前宿主上运行,但是, 如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry (仓库注册服务器)就是这样的服务。有时候会把仓库 (Repository) 和仓库注册服务器 (Registry) 混为一谈,并不严格区分。Docker 仓库的概念跟 Git 类似,注册服务器可以理解为 GitHub 这样的托管服务。实际上,一个 Docker Registry 中可以包含多个仓库 (Repository) ,每个仓库可以包含多个标签 (Tag),每个标签对应着一个镜像。所以说,镜像仓库是 Docker 用来集中存放镜像文件的地方类似于我们之前常用的代码仓库。
通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本 。我们可以通过<仓库名>:<标签>的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签.。
仓库又可以分为两种形式:
- public(公共仓库)
- private(私有仓库)
Docker Registry 公有仓库是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。
除了使用公开服务外,用户还可以在本地搭建私有 Docker Registry 。Docker 官方提供了 Docker Registry镜像,可以直接使用做为私有 Registry 服务。当用户创建了自己的镜像之后就可以使用 push 命令将它上传到公有或者私有仓库,这样下次在另外一台机器上使用这个镜像时候,只需要从仓库上 pull 下来就可以了。
把 Docker 的一些常见概念如 Image , Container , Repository 做了详细的阐述,也从传统虚拟化方式的角度阐述了 docker 的优势,从下图可以直观地看到 Docker 的架构:
Docker 使用 C/S 结构,即客户端/服务器体系结构。 Docker 客户端与 Docker 服务器进行交互,Docker服务端负责构建、运行和分发 Docker 镜像。 Docker 客户端和服务端可以运行在一台机器上,也可以通过 RESTful 、 stock 或网络接口与远程 Docker 服务端进行通信。
这张图展示了 Docker 客户端、服务端和 Docker 仓库(即 Docker Hub 和 Docker Cloud ),默认情况下Docker 会在 Docker 中央仓库寻找镜像文件,这种利用仓库管理镜像的设计理念类似于 Git ,当然这个仓库是可以通过修改配置来指定的,甚至可以创建自己的私有仓库。