什么是 Docker:简要回顾历史和基本抽象

10 月 XNUMX 日在 Slurm 开始 Docker 视频课程,其中我们对其进行了全面分析 - 从基本抽象到网络参数。

在本文中,我们将讨论 Docker 的历史及其主要抽象:Image、Cli、Dockerfile。 该讲座面向初学者,因此经验丰富的用户不太可能感兴趣。 不会有血液、阑尾或深度浸没。 非常基础的知识。

什么是 Docker:简要回顾历史和基本抽象

什么是 Docker

我们来看看维基百科上对Docker的定义。

Docker 是用于在容器化环境中自动部署和管理应用程序的软件。

从这个定义来看,没有什么是清楚的。 尤其不清楚“在支持容器化的环境中”的含义。 为了找到答案,让我们回到过去。 让我们从我通常称为“整体时代”的时代开始。

单体时代

单体时代是 2000 年代初,当时所有应用程序都是单体的,具有大量依赖项。 开发花了很长时间。 同时,服务器并不多;我们都知道它们的名字并监控它们。 有这样一个有趣的对比:

宠物是家畜。 在单体时代,我们对待服务器就像对待宠物一样,精心呵护、精心呵护,吹走灰尘。 为了更好的资源管理,我们使用了虚拟化:我们将一台服务器切割成多个虚拟机,从而确保环境的隔离。

基于管理程序的虚拟化系统

大家可能都听说过虚拟化系统:VMware、VirtualBox、Hyper-V、Qemu KVM等。它们提供应用程序隔离和资源管理,但也有缺点。 要进行虚拟化,您需要一个虚拟机管理程序。 而虚拟机管理程序是一种资源开销。 虚拟机本身通常是一个完整的庞然大物——一个包含操作系统、Nginx、Apache,可能还包含 MySQL 的重型映像。 镜像较大,虚拟机操作不方便。 因此,使用虚拟机可能会很慢。 为了解决这个问题,在内核级别创建了虚拟化系统。

内核级虚拟化系统

OpenVZ、Systemd-nspawn、LXC 系统支持内核级虚拟化。 这种虚拟化的一个显着例子是 LXC(Linux 容器)。

LXC 是一个操作系统级虚拟化系统,用于在单个节点上运行 Linux 操作系统的多个隔离实例。 LXC不使用虚拟机,而是创建一个具有自己的进程空间和网络堆栈的虚拟环境。

本质上 LXC 创建容器。 虚拟机和容器有什么区别?

什么是 Docker:简要回顾历史和基本抽象

容器不适合隔离进程:在内核级别的虚拟化系统中发现了漏洞,使它们能够从容器逃逸到主机。 因此,如果需要隔离某些东西,最好使用虚拟机。

虚拟化和容器化之间的差异可以从图中看出。
有硬件管理程序、操作系统之上的管理程序和容器。

什么是 Docker:简要回顾历史和基本抽象

如果你确实想隔离某些东西,硬件虚拟机管理程序很酷。 因为可以在内存页面和处理器级别进行隔离。

有作为程序的虚拟机管理程序,也有容器,我们将进一步讨论它们。 容器化系统没有虚拟机管理程序,但有一个容器引擎来创建和管理容器。 这个东西更轻量级,因此由于与核心一起工作,开销更少或根本没有。

内核级别的容器化是用什么来实现的

允许您创建与其他进程隔离的容器的主要技术是命名空间和控制组。

命名空间:PID、网络、安装和用户。 还有更多,但为了便于理解,我们将重点关注这些。

PID 命名空间限制进程。 例如,当我们创建一个 PID 命名空间并在那里放置一个进程时,它的 PID 为 1。通常在系统中,PID 1 是 systemd 或 init。 因此,当我们将进程放置在新的命名空间中时,它也会收到 PID 1。

网络命名空间允许您限制/隔离网络并在其中放置您自己的接口。 挂载是文件系统的限制。 用户——对用户的限制。

控制组:内存、CPU、IOPS、网络 - 总共约 12 个设置。 否则,它们也称为 Cgroup(“C-groups”)。

控制组管理容器的资源。 通过控制组,我们可以说容器消耗的资源不应超过一定数量。

为了使容器化充分发挥作用,需要使用其他技术:功能、写入时复制等。

能力是我们告诉流程它能做什么和不能做什么。 在内核级别,这些只是带有许多参数的位图。 例如,root 用户拥有完全权限并且可以执行所有操作。 时间服务器可以更改系统时间:它具有 Time Capsule 上的功能,仅此而已。 通过权限,您可以灵活配置进程限制,从而保护自己。

Copy-on-write 系统允许我们处理 Docker 镜像并更有效地使用它们。

Docker 目前存在与 Cgroups v2 的兼容性问题,因此本文专门关注 Cgroups v1。

但让我们回到历史。

当虚拟化系统出现在内核级别时,它们就开始被积极使用。 虚拟机管理程序的开销消失了,但仍然存在一些问题:

  • 大镜像:他们将操作系统、库、一堆不同的软件推送到同一个 OpenVZ 中,最终镜像仍然相当大;
  • 打包和交付没有正常的标准,因此依赖问题仍然存在。 在某些情况下,两段代码使用相同的库,但版本不同。 他们之间可能会发生冲突。

为了解决所有这些问题,下一个时代已经到来。

容器时代

当容器时代到来时,与之合作的理念发生了变化:

  • 一个进程 - 一个容器。
  • 我们将流程所需的所有依赖项传递给其容器。 这需要将单体应用切割成微服务。
  • 镜像越小越好——可能的漏洞更少,推出速度更快等等。
  • 实例变得短暂。

还记得我说过的关于宠物与牛的说法吗? 以前,实例就像家畜,但现在它们变得像牛一样。 以前,有一个整体——一个应用程序。 现在有 100 个微服务、100 个容器。 某些容器可能有 2-3 个副本。 对我们来说控制每个容器变得不那么重要了。 对我们来说更重要的是服务本身的可用性:这组容器的作用。 这改变了监控方法。

2014-2015 年,Docker 蓬勃发展——我们现在要讨论的技术。

Docker 改变了理念并标准化了应用程序打包。 使用 Docker,我们可以打包应用程序,将其发送到存储库,从那里下载并部署它。

我们把需要的东西都放到了Docker容器中,这样依赖问题就解决了。 Docker 保证可重复性。 我想很多人都遇到过不可重复性:一切都适合你,你把它推到生产中,然后它就停止工作了。 有了 Docker,这个问题就迎刃而解了。 如果您的 Docker 容器启动并执行其需要执行的操作,那么它很可能会在生产环境中启动并执行相同的操作。

关于开销的题外话

关于管理费用总是存在争议。 有些人认为 Docker 不会带来额外的负载,因为它使用 Linux 内核及其容器化所需的所有进程。 就像,“如果你说 Docker 是开销,那么 Linux 内核也是开销。”

另一方面,如果深入的话,Docker 中确实有几个东西,拉长一点,可以说是开销很大的。

第一个是 PID 命名空间。 当我们将一个进程放置在命名空间中时,它会被分配 PID 1。同时,该进程还有另一个 PID,该 PID 位于容器外部的主机命名空间上。 例如,我们在容器中启动Nginx,它的PID变为1(主进程)。 在主机上它的 PID 为 12623。而且很难说它有多少开销。

第二件事是Cgroup。 我们以内存来理解Cgroups,即限制容器内存的能力。 启用后,计数器和内存统计将被激活:内核需要了解已分配了多少页以及该容器仍有多少页可用。 这可能是一个开销,但我还没有看到任何关于它如何影响性能的确切研究。 而我自己并没有注意到,运行在Docker中的应用程序突然出现了性能的急剧下降。

还有一个关于性能的说明。 一些内核参数从主机传递到容器。 特别是一些网络参数。 因此,如果你想在Docker中运行一些高性能的东西,例如,一些会主动使用网络的东西,那么你至少需要调整这些参数。 例如,一些 nf_conntrack。

关于 Docker 概念

Docker 由几个组件组成:

  1. Docker Daemon 是同一个容器引擎; 启动容器。
  2. Docker CII 是一个 Docker 管理实用程序。
  3. Dockerfile - 有关如何构建映像的说明。
  4. 图像 — 推出容器的图像。
  5. 容器。
  6. Docker 注册表是一个镜像存储库。

从原理上讲,它看起来像这样:

什么是 Docker:简要回顾历史和基本抽象

Docker 守护进程在 Docker_host 上运行并启动容器。 有一个客户端发送命令:构建映像、下载映像、启动容器。 Docker 守护进程进入注册表并执行它们。 Docker 客户端可以在本地(Unix 套接字)进行访问,也可以通过 TCP 从远程主机进行访问。

让我们详细了解一下每个组件。

Docker 守护进程 - 这是服务器部分,它在主机上工作:下载图像并从中启动容器,在容器之间创建网络,收集日志。 当我们说“创造一个形象”时,恶魔也在这样做。

码头工人命令行界面 — Docker 客户端部分,用于使用守护进程的控制台实用程序。 我再说一遍,它不仅可以在本地运行,还可以通过网络运行。

基本命令:

docker ps - 显示当前在 Docker 主机上运行的容器。
docker images - 显示本地下载的图像。
docker search <> - 在注册表中搜索图像。
docker pull <> - 将镜像从注册表下载到机器上。
码头工人构建< > - 收集图像。
docker run <> - 启动容器。
docker rm <> - 删除容器。
docker log <> - 容器日志
docker start/stop/restart <> - 使用容器

如果你掌握了这些命令并且有信心使用它们,那么你就认为自己在用户级别对 Docker 的熟练程度达到了 70%。

Dockerfile - 创建图像的说明。 几乎每个指令命令都是一个新层。 让我们看一个例子。

什么是 Docker:简要回顾历史和基本抽象

Dockerfile 是这样的:命令在左边,参数在右边。 这里的每个命令(通常写在 Dockerfile 中)都会在 Image 中创建一个新层。

即使看左边,你也能大致明白发生了什么。 我们说:“为我们创建一个文件夹” - 这是一层。 “使文件夹正常工作”是另一层,依此类推。 夹心蛋糕让生活更轻松。 如果我创建另一个 Dockerfile 并更改最后一行中的某些内容 - 我运行“python”“main.py”以外的其他内容,或者从另一个文件安装依赖项 - 那么前面的层将被重新用作缓存。

图片 - 这是容器包装;容器是从镜像中启动的。 如果我们从包管理器的角度来看 Docker(就好像我们正在使用 deb 或 rpm 包一样),那么镜像本质上就是一个 rpm 包。 通过 yum install 我们可以安装应用程序、删除它、在存储库中查找它并下载它。 这里的情况大致相同:容器从镜像启动,它们存储在 Docker 注册表中(类似于存储库中的 yum),每个镜像都有一个 SHA-256 哈希、一个名称和一个标签。

镜像是根据 Dockerfile 的指令构建的。 Dockerfile 中的每条指令都会创建一个新层。 图层可以重复使用。

Docker 注册表 是一个 Docker 镜像存储库。 与操作系统类似,Docker 有一个公共标准注册表 - dockerhub。 但您可以构建自己的存储库、自己的 Docker 注册表。

容器 - 从图像中启动的内容。 我们根据 Dockerfile 的说明构建了一个映像,然后从该映像启动它。 该容器与其他容器隔离;它必须包含应用程序工作所需的一切。 在这种情况下,一个容器 - 一个进程。 碰巧你必须做两个进程,但这有点违背Docker的思想。

“一容器,一个进程”的要求与 PID 命名空间有关。 当 PID 为 1 的进程在 Namespace 中启动时,如果它突然死亡,那么整个容器也会死亡。 如果那里正在运行两个进程:一个是存活的,另一个是死亡的,那么容器仍然会继续存活。 但这是最佳实践的问题,我们将在其他材料中讨论它们。

要更详细地了解该课程的特点和完整计划,请点击链接:“Docker 视频课程“。

作者:Marcel Ibraev,经过认证的 Kubernetes 管理员、Southbridge 的执业工程师、Slurm 课程的演讲者和开发者。

来源: habr.com

添加评论