Kubernetes-和-Docker-企业级指南(全)

Kubernetes 和 Docker 企业级指南(全)

原文:zh.annas-archive.org/md5/9023162EFAC3D4D142381E2C55E3B624

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Kubernetes 已经风靡全球,成为 DevOps 团队开发、测试和运行应用程序的标准基础设施。大多数企业要么已经在运行它,要么计划在未来一年内运行它。在任何主要的招聘网站上查看职位发布,几乎每个知名公司都有 Kubernetes 职位空缺。快速的采用速度导致与 Kubernetes 相关的职位在过去 4 年增长了超过 2000%。

公司普遍面临的一个问题是缺乏企业级 Kubernetes 知识。由于这项技术相对较新,甚至对于生产工作负载来说更是新颖,公司在尝试构建可靠运行集群的团队时遇到了问题。寻找具有基本 Kubernetes 技能的人变得更容易,但寻找具有企业集群所需知识的人仍然是一个挑战。

这本书是为谁准备的

我们创建这本书是为了帮助 DevOps 团队将他们的技能扩展到超越 Kubernetes 的基础知识。这本书是根据我们在多个企业环境中使用集群的多年经验创建的。

有许多介绍 Kubernetes 和安装集群、创建部署以及使用 Kubernetes 对象基础知识的书籍可供选择。我们的计划是创建一本超越基础集群的书籍,并且为了保持书籍的合理长度,我们不会重复介绍 Kubernetes 的基础知识。在阅读本书之前,您应该具有一些 Kubernetes 的经验。

虽然本书的主要重点是扩展具有企业特性的集群,但书的第一部分将提供对关键 Docker 主题和 Kubernetes 对象的复习。您应该对 Kubernetes 对象有扎实的理解,以便充分利用更高级的章节。

这本书涵盖了什么

第一章, 理解 Docker 和容器基础知识,帮助您了解 Docker 和 Kubernetes 为开发人员解决了哪些问题。您将被介绍到 Docker 的不同方面,包括 Docker 守护程序、数据、安装和使用 Docker CLI。

第二章《使用 Docker 数据》讨论了容器需要存储数据,有些用例只需要临时磁盘,而其他需要持久磁盘。在这一章中,你将了解持久数据以及 Docker 如何与卷、绑定挂载和 tmpfs 一起使用来存储数据。

第三章《理解 Docker 网络》向你介绍了 Docker 中的网络。它将涵盖创建不同类型的网络、添加和移除容器网络以及暴露容器服务。

第四章《使用 KinD 部署 Kubernetes》展示了 KinD 是一个强大的工具,允许你创建从单节点集群到完整多节点集群的 Kubernetes 集群。这一章超越了基本的 KinD 集群,解释了如何使用运行 HAproxy 的负载均衡器来负载均衡工作节点。通过这一章的学习,你将了解 KinD 的工作原理,以及如何创建一个自定义的多节点集群,这将用于接下来章节的练习中。

第五章《Kubernetes 训练营》涵盖了集群包含的大部分对象,无论你需要对 Kubernetes 进行复习,还是你是新手。它通过描述每个对象的功能和在集群中的作用来解释这些对象。这一章旨在作为一个复习,或者对象的“口袋指南” - 它不包含每个对象的详尽细节,因为那将需要另一本书。

第六章《服务、负载均衡和外部 DNS》教会你如何使用服务暴露 Kubernetes 部署。每种服务类型都有例子进行解释,你将学会如何使用 Layer-7 和 Layer-4 负载均衡器来暴露它们。在这一章中,你将超越简单的 Ingress 控制器的基础,安装 MetalLB,为服务提供 Layer-4 访问。你还将安装一个名为 external-dns 的孵化器项目,为 MetalLB 暴露的服务提供动态名称解析。

第七章《将身份验证集成到您的集群中》考虑了一旦构建完成,用户将如何访问您的集群的问题。在本章中,我们将详细介绍 OpenID Connect 的工作原理以及为什么应该使用它来访问您的集群。我们还将介绍应该避免的几种反模式以及为什么应该避免它们。

第八章《RBAC 策略和审计》演示了一旦用户访问集群,您需要能够限制他们的访问。无论您是为用户提供整个集群还是只是一个命名空间,您都需要了解 Kubernetes 如何通过其基于角色的访问控制系统(RBAC)授权访问。在本章中,我们将详细介绍如何设计 RBAC 策略,如何调试它们以及多租户的不同策略。

第九章《保护 Kubernetes 仪表板》着眼于 Kubernetes 仪表板,这通常是用户在集群启动后尝试启动的第一件事。关于安全性(或缺乏安全性)有相当多的神话。您的集群还将由其他 Web 应用程序组成,例如网络仪表板、日志系统和监控仪表板。本章将介绍仪表板的架构、如何正确地保护它,以及如何不部署它的示例,并详细说明为什么不应该这样做。

第十章《创建 Pod 安全策略》涉及运行您的 Pod 实例的节点的安全性。我们将讨论如何安全地设计您的容器,使它们更难被滥用,以及如何构建策略来限制您的容器访问它们不需要的资源。我们还将介绍 PodSecurityPolicy API 的弃用以及如何处理它。

第十一章《使用 Open Policy Agent 扩展安全性》为您提供了部署 OpenPolicyAgent 和 GateKeeper 的指导,以实现无法使用 RBAC 或 PodSecurityPolicies 实施的策略。我们将介绍如何部署 GateKeeper,如何在 Rego 中编写策略,以及如何使用 OPA 的内置测试框架测试您的策略。

第十二章, 使用 Falco 和 EFK 进行审计, 讨论了 Kubernetes 包括 API 访问的事件日志记录,但它没有能力记录可能在 Pod 内执行的事件。为了解决这个限制,我们将安装一个捐赠给 CNCF 的项目,名为 Falco。你还将学习如何使用 FalcoSideKick 和EFK堆栈(ElasticSearch,FluentD 和 Kibana)呈现 Falco 捕获的数据。你将通过在 Kibana 中查找事件并创建包含重要事件的自定义仪表板来获得实际操作经验。

第十三章, 备份工作负载, 教你如何使用 Velero 为灾难恢复或集群迁移创建集群工作负载的备份。你将亲自动手创建示例工作负载的备份,并将备份恢复到全新的集群,以模拟集群迁移。

第十四章, 平台配置, 让你构建一个用于自动化多租户集群的平台,其中包括 GitLab、Tekton、ArgoCD 和 OpenUnison。我们将探讨如何构建流水线以及如何自动化它们的创建。我们将探讨用于驱动流水线的对象如何相互关联,如何在系统之间建立关系,最后,如何创建用于自动化流水线部署的自助式工作流程。

为了充分利用本书

你应该对 Linux、基本命令和工具(如 Git 和 vi 文本编辑器)有基本的了解。

这些章节包含理论和动手练习。我们认为练习有助于加强理论,但并非必须理解每个主题。如果你想在书中做练习,你需要满足下表中的要求:

所有练习都使用 Ubuntu,但大多数练习也适用于其他 Linux 安装。Falco 章节中的步骤特定于 Ubuntu,其他 Linux 安装可能无法正确部署练习。

如果你使用的是本书的数字版本,我们建议你自己输入代码或通过 GitHub 存储库访问代码(链接在下一节中提供)。这样做将帮助你避免与复制和粘贴代码相关的潜在错误。

下载示例代码文件

您可以从www.packt.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packtpub.com/support并注册,以便文件直接发送到您的邮箱。

您可以按照以下步骤下载代码文件:

  1. 登录或注册www.packt.com

  2. 选择支持选项卡。

  3. 单击代码下载

  4. 搜索框中输入书名,然后按照屏幕上的说明操作。

下载文件后,请确保使用以下最新版本解压或提取文件夹:

  • Windows 上的 WinRAR/7-Zip

  • Mac 上的 Zipeg/iZip/UnRarX

  • Linux 上的 7-Zip/PeaZip

本书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Kubernetes-and-Docker-The-Complete-Guide。如果代码有更新,将在现有的 GitHub 存储库上进行更新。

我们还有来自丰富书籍和视频目录的其他代码包,可在github.com/PacktPublishing/上找到!快去看看吧!

实际代码

本书的实际代码视频可以在bit.ly/2OQfDum上观看。

下载彩色图像

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图像。您可以在此处下载:https://static.packt-cdn.com/downloads/9781839213403_ColorImages.pdf

使用的约定

本书中使用了许多文本约定。

文本中的代码:指示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。以下是一个示例:“要识别的最后一个组件是apiGroups。这是 URL 模型中的另一个不一致的地方。”

代码块设置如下:

apiVersion:rbac.authorization.k8s.io/v1

类型:ClusterRole

元数据:

名称:cluster-pod-and-pod-logs-reader

规则:

  • apiGroups:[""]

资源:["pods", "pods/log"]

动词:["get", "list"]

当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体显示:

  • hostPath:

路径:/usr/share/ca-certificates

类型:DirectoryOrCreate

名称:usr-share-ca-certificates

**  - hostPath:**

**      路径:/var/log/k8s**

**      类型:DirectoryOrCreate**

name: var-log-k8s

  • hostPath:

    path: /etc/kubernetes/audit
    
    type: DirectoryOrCreate
    

    name: etc-kubernetes-audit

任何命令行输入或输出都以以下方式编写:

PS C:\Users\mlb> kubectl create ns not-going-to-work namespace/not-going-to-work created

粗体:表示一个新术语,一个重要单词,或者你在屏幕上看到的单词。例如,菜单或对话框中的单词会以这种方式出现在文本中。这是一个例子:“点击屏幕底部的完成登录按钮。”

提示或重要说明

看起来像这样。

第一部分:Docker 和容器基础知识

在本节中,我们将回顾重要的 Docker 和容器概念,以及 Docker 为开发人员提供的好处。在快速回顾 Docker 之后,我们将继续讨论如何向容器添加持久数据,以及如何使用 Docker 网络将容器暴露给外部世界。这些基本概念对于充分理解 Kubernetes 以及理解容器如何在集群中运行至关重要。

本书的这部分包括以下章节:

  • 第一章,理解 Docker 和容器基础知识

  • 第二章,处理持久数据

  • 第三章,理解 Docker 网络

第一章:Docker 和容器基础知识

容器是我们多年来见过的最具变革性的技术之一。技术公司、企业和最终用户都采用它来处理日常工作负载。越来越多的通用现成COTS)应用程序正在从传统安装转变为完全容器化的部署。随着这样一个巨大的技术转变,任何信息技术领域的人都有必要了解容器。

在这一章中,我们将介绍容器解决的问题。在介绍容器的重要性之后,我们将介绍引发现代容器狂热的运行时,Docker。通过本章的学习,您将了解如何安装 Docker 以及如何使用最常见的 Docker CLI 命令。

在这一章中,我们将涵盖以下主题:

  • 理解容器化的需求

  • 理解 Docker

  • 安装 Docker

  • 使用 Docker CLI

让我们开始吧!

技术要求

这一章有以下技术要求:

  • 一个 Ubuntu 18.04 服务器,至少需要 4GB 的 RAM,建议使用 8GB

您可以通过访问以下 GitHub 存储库获取本章的代码:github.com/PacktPublishing/Kubernetes-and-Docker-The-Complete-Guide

理解容器化的需求

您可能在办公室或学校经历过这样的对话:

开发者:“这是新的应用程序。经过数周的测试,你是第一个获得新版本的人。”

….. 过了一会儿 ….

用户:“它不工作。当我点击提交按钮时,它显示了一个缺少依赖项的错误。”

开发者:“这很奇怪;在我的机器上一切都正常。”

这是开发者交付应用程序时可能遇到的最令人沮丧的事情之一。经常出现的问题与开发者在他们的机器上使用的库有关,但这些库并未包含在软件包的分发中。似乎一个简单的解决方法是将所有的库都包含在发布中,但如果这个发布包含一个新的库,它会覆盖旧版本,而这可能是其他应用程序所需要的。

开发人员需要考虑他们的新发布,以及与用户工作站上任何现有软件可能存在的冲突。这经常成为一个需要更大的部署团队在不同的系统配置上测试应用程序的谨慎平衡行为。这可能会导致开发人员需要额外的重做,或者在一些极端情况下,与现有应用程序完全不兼容。

多年来,已经有各种尝试使应用程序交付变得更加容易。首先,有诸如 VMware 的 Thinapp 之类的解决方案,它们虚拟化了一个应用程序(不要与虚拟化操作系统混淆)。它允许您将应用程序及其依赖项打包到一个单个可执行包中。这种打包消除了应用程序依赖项与另一个应用程序的依赖项发生冲突的问题,因为应用程序在一个自包含的包中。这种打包提供的应用程序隔离不仅消除了依赖性问题,还提供了更高级别的安全性,并减轻了操作系统迁移的负担。

在阅读本书之前,您可能已经听说过应用程序流式传输,这听起来像是解决“在我的机器上可以运行”的问题的一个很好的解决方案。尽管有许多原因导致它没有如预期般成功。首先,大多数提供的解决方案都是需要大量投资的付费解决方案。除了许可证,它们还需要一个“干净的 PC”,这意味着对于您想要虚拟化的每个应用程序,您都需要从一个基本系统开始。您想要创建的软件包使用了基本安装和初始系统快照后添加的任何内容之间的差异。然后,这些差异被打包到您的分发文件中,可以在任何工作站上执行。

我们提到应用程序虚拟化是为了突出多年来“在我的机器上可以运行”的应用程序问题已经有了不同的解决方案。像 Thinapp 这样的产品只是解决这个问题的一种尝试。其他尝试包括在运行 Citrix 或远程桌面的服务器上运行应用程序,Linux 容器,甚至虚拟机。

引入 Docker

行业甚至最终用户需要更简单、更便宜的东西——Docker 容器应运而生。容器并不是一种新技术;多年来它们以各种形式被使用。Docker 所做的是让它们对普通开发人员可访问。

Docker 为大众带来了一个抽象层。它易于使用,并且在创建软件包之前不需要为每个应用程序清理 PC,因此提供了解决依赖问题的解决方案,但最吸引人的是它是免费的。 Docker 成为 GitHub 上许多项目的标准,团队经常会创建 Docker 容器并向团队成员分发 Docker 镜像或Dockerfile,提供标准的测试或开发环境。最终,最终用户的采用是最终将 Docker 引入企业并最终使其成为标准的原因。

虽然有很多关于 Docker 的书,但本书侧重于与容器交互所使用的 Docker 的基本主题。本书将侧重于在尝试使用本地 Kubernetes 环境时需要了解的内容。Docker 有一个悠久而有趣的历史,以及它是如何演变成我们今天使用的标准容器镜像格式的。我们鼓励你阅读有关这家公司以及他们是如何引领我们今天所知的容器世界的历史。

虽然我们的重点不是全面教授 Docker,但我们认为那些对 Docker 新手会受益于对一般容器概念的快速入门。如果您有一些 Docker 经验并且了解临时和无状态等术语,请继续阅读安装 Docker部分。

理解 Docker

本书假设您对 Docker 和容器概念有一些基本的了解。我们意识到并非每个人都可能以前使用过 Docker 或容器,因此我们希望对容器概念和使用 Docker 进行快速入门。

重要提示

如果您是容器的新手,我们建议阅读 Docker 网站上可以找到的文档以获取更多信息:docs.docker.com/

容器是临时的

首要理解的主题是容器镜像是临时的。

对于那些对 Docker 新手来说,临时的意思是短暂的。按设计,容器可以随时被销毁,并在没有用户干预的情况下重新启动。在前面的例子中,有人交互地向 web 服务器添加了文件。这些添加的文件只是临时的,因为基础镜像中并不包含这些文件。

这意味着一旦创建并运行容器,对镜像进行的任何更改在容器从 Docker 主机中删除或销毁时都不会被保存。让我们看一个例子:

  1. 您在主机上启动一个使用NGINX运行的 web 服务器的容器,而没有任何基本的HTML页面。

  2. 使用 Docker 命令,您执行copy命令将一些 web 文件复制到容器的文件系统中。

  3. 为了测试复制是否成功,您浏览网站并确认它正在提供正确的网页。

  4. 对结果感到满意后,您停止容器并从主机中删除它。当天晚些时候,您想向同事展示网站,然后启动 NGINX 容器。您再次浏览网站,但当网站打开时,您收到 404 错误(页面未找到错误)。

在您停止并从主机中删除容器之前上传的文件发生了什么?

容器重新启动后无法找到您上传的网页的原因是因为所有容器都是短暂的

基本容器镜像中的内容是每次容器最初启动时都会包含的内容。您在容器内部进行的任何更改都是短暂的。

如果您需要向现有镜像添加永久文件,则需要使用包含文件的镜像重新构建镜像,或者,正如我们将在本章后面的持久数据部分中解释的那样,您可以在容器中挂载 Docker 卷。在这一点上,要理解的主要概念是容器是短暂的。

但等等!您可能会想,"如果容器是短暂的,那我是如何向服务器添加网页的呢?"。短暂只是意味着更改不会被保存;这并不妨碍您对正在运行的容器进行更改。

对正在运行的容器进行的任何更改都将被写入临时层,称为容器层,这是本地主机文件系统上的一个目录。Docker 存储驱动程序负责处理使用容器层的请求。此位置将存储容器文件系统中的任何更改,因此当您将 HTML 页面添加到容器时,它们将存储在本地主机上。容器层与正在运行的镜像的容器 ID 相关联,并且将保留在主机系统上,直到通过使用 CLI 或运行 Docker 清理作业从 Docker 中删除容器。

如果一个容器是临时的,镜像无法被写入,你如何修改容器中的数据呢?Docker 使用镜像分层来创建多个链接的层,看起来像是一个单一的文件系统。

Docker 镜像

在高层次上,Docker 镜像是一组镜像层,每个层都有一个包含该层元数据的 JSON 文件。这些都被组合在一起,创建了当容器镜像启动时你与之交互的运行应用程序。

你可以在 Docker 的 GitHub 上阅读更多关于镜像内容的信息 github.com/moby/moby/blob/master/image/spec/v1.md

镜像层

正如我们在前一节中提到的,运行中的容器使用一个位于基础镜像层“之上”的容器层,如下图所示:

图 1.1 – Docker 镜像层

图 1.1 – Docker 镜像层

镜像层无法被写入,因为它们处于只读状态,但是临时的容器层是可写的。你添加到容器中的任何数据都存储在这个层中,并且只要容器在运行,它就会被保留。

为了高效处理多个层,Docker 实现了写时复制,这意味着如果文件已经存在,它将不会被创建。但是,如果当前镜像中需要一个不存在的文件,它将被写入。在容器世界中,如果一个文件存在于较低的层中,它上面的层就不需要包含它。例如,如果第一层中有一个名为 /opt/nginx/index.html 的文件,第二层就不需要在它的层中包含相同的文件。

这解释了系统如何处理已经存在或不存在的文件,但是对于已经被修改的文件呢?有时候你需要“替换”一个位于较低层的文件。当你构建一个镜像或者临时修复一个运行中的容器问题时,你可能需要这样做。写时复制系统知道如何处理这些问题。由于镜像是自上而下读取的,容器只使用最高层的文件。如果你的系统在第一层有一个 /opt/nginx/index.html 文件,并且你修改并保存了这个文件,运行中的容器会将新文件存储在容器层中。由于容器层是最顶层的层,所以镜像层中旧版本的 index.html 总是会在新版本之前被读取。

持久数据

我们将在第二章中讨论如何使用持久磁盘,使用 Docker 数据。现在,我们只提供一个简要介绍。

仅限于短暂容器将严重限制 Docker 的用例。很可能您会有一些用例需要持久存储,或者数据会在停止容器后保留。

这可能看起来与我们之前关于容器是短暂的说法相矛盾,但这仍然是真的。当您将数据存储在容器镜像层中时,基础镜像不会改变。当容器从主机中移除时,容器层也会被移除。如果使用相同的镜像来启动一个新的容器,也会创建一个新的容器镜像层。因此,容器是短暂的,但通过向容器添加 Docker 卷,您可以在容器之外存储数据,从而获得数据持久性。

Docker 通过几种方法提供持久性,我们将在第二章中更详细地讨论,使用 Docker 数据,但现在知道 Docker 确实提供了一种持久化数据的方法。

访问运行在容器中的服务

我们将在第三章中讨论如何暴露容器,理解 Docker 网络。现在,我们只提供一个简要介绍。

与物理机或虚拟机不同,容器不直接连接到网络。当容器需要发送或接收流量时,它通过 Docker 主机系统使用桥接NAT 网络连接。这意味着当您运行一个容器并且希望接收传入的流量请求时,您需要为希望接收流量的每个容器公开端口。在基于 Linux 的系统上,iptables有规则来将流量转发到 Docker 守护程序,它将为每个容器服务分配的端口。

这完成了对基本容器和 Docker 的介绍。在下一节中,我们将解释如何在主机上安装 Docker。

安装 Docker

本书中的实践练习将需要您拥有一个可用的 Docker 主机。您可以按照本书中的步骤操作,或者您可以执行本书 GitHub 存储库中位于chapter1目录下的脚本install-docker.sh

今天,你几乎可以在所有硬件平台上安装 Docker。每个版本的 Docker 在每个平台上的操作和外观都是相同的,这使得需要开发跨平台应用程序的人们可以轻松开发和使用 Docker。通过在不同平台之间使功能和命令相同,开发人员不需要学习不同的容器运行时来运行图像。

以下是 Docker 可用平台的表格。你可以看到,有多个操作系统的安装,以及多个 CPU 架构的安装:

图 1.2 - 可用的 Docker 平台

图 1.2 - 可用的 Docker 平台

重要提示

使用一种架构创建的图像无法在不同的架构上运行。这意味着你不能基于 x86 硬件创建图像,然后期望同样的图像在运行 ARM 处理器的树莓派上运行。同样重要的是要注意,虽然你可以在 Windows 机器上运行 Linux 容器,但你不能在 Linux 机器上运行 Windows 容器。

用于安装 Docker 的安装程序在不同平台之间有所不同。幸运的是,Docker 已经在他们的网站上记录了许多安装程序:docs.docker.com/install/

在本章中,我们将在 Ubuntu 18.04 系统上安装 Docker。如果你没有 Ubuntu 机器可以安装,你仍然可以阅读安装步骤,因为每个步骤都将得到解释,不需要你有一个运行的系统来理解这个过程。如果你有不同的 Linux 安装,你可以使用 Docker 网站上概述的安装程序docs.docker.com/。为 CentOS、Debian、Fedora、Ubuntu 提供了步骤,并为其他 Linux 发行版提供了通用步骤。

准备安装 Docker

在开始安装之前,我们需要考虑使用什么存储驱动程序。存储驱动程序提供了联合文件系统,管理容器的层以及容器的可写层是如何访问的。

在大多数安装中,你不需要更改默认的存储驱动程序,因为会选择默认选项。如果你运行的是至少 4.0 或更高版本的 Linux 内核,你的 Docker 安装将使用overlay2存储驱动程序;较早的内核将安装AUFS存储驱动程序。

供参考,除了overlay2AUFS驱动程序外,Docker 还支持devicemapperbtrfszfsvfs存储驱动程序。但是,这些在新系统中很少使用,这里仅作为参考提到。

如果您想了解每个存储驱动程序,请查看以下 Docker 网页,该网页详细介绍了每个驱动程序及其用例:docs.docker.com/storage/storagedriver/select-storage-driver/

现在您了解了存储驱动程序的要求,下一步是选择安装方法。您可以使用三种方法之一安装 Docker:

  • 将 Docker 存储库添加到您的主机系统中。

  • 手动安装软件包。

  • 使用 Docker 提供的安装脚本。

第一种选项被认为是最佳选项,因为它允许轻松安装和更新 Docker 引擎。第二个选项适用于没有互联网访问服务器的企业,也称为“空隙”服务器。第三个选项用于安装 Docker 的边缘和测试版本,不建议用于生产环境。

由于首选方法是将 Docker 的存储库添加到我们的主机,我们将使用该选项并解释应该使用的过程来添加存储库并安装 Docker。

在 Ubuntu 上安装 Docker

现在我们已经准备好了一切,让我们安装 Docker:

  1. 第一步是通过执行apt-get update来更新软件包索引:

sudo apt-get update

  1. 接下来,我们需要添加可能在主机系统上缺失的任何软件包,以允许 HTTPS apt 访问:

sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common

  1. 要从 Docker 的存储库中拉取软件包,我们需要添加它们的密钥。您可以使用以下命令添加密钥,该命令将下载 gpg 密钥并将其添加到您的系统中:

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add –

  1. 现在将 Docker 的存储库添加到您的主机系统中:

sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

  1. 完成所有先决条件后,您可以在服务器上安装 Docker:

sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io

  1. Docker 现在已安装在您的主机上,但与大多数新服务一样,Docker 目前未运行,并且尚未配置为随系统启动。要启动 Docker 并在启动时启用它,请使用以下命令:

sudo systemctl enable docker && systemctl start docker

现在我们已经安装了 Docker,让我们先进行一些配置。首先,我们将授予 Docker 权限。

授予 Docker 权限

在默认安装中,Docker 需要 root 访问权限,因此您需要以 root 身份运行所有 Docker 命令。您可以将您的用户帐户添加到服务器上的一个新组,以便在不需要对每个命令使用 sudo 的情况下提供 Docker 访问权限。

如果您以标准用户身份登录并尝试运行 Docker 命令,您将收到一个错误:

在尝试连接到 Docker 守护程序套接字 unix:///var/run/docker.sock 时,出现了权限被拒绝的情况:获取 http://%2Fvar%2Frun%2Fdocker.sock/v1.40/images/json:拨号 unix /var/run/docker.sock:连接:权限被拒绝

要允许您的用户或任何其他您想要添加的用户执行 Docker 命令,您需要创建一个新的组并将用户添加到该组中。以下是一个示例命令,您可以使用它来添加当前登录的用户:

使用 sudo groupadd docker 命令和 sudo usermod -aG docker $USER 命令

第一个命令创建了 docker 组,而第二个命令将您当前登录的用户帐户添加到 docker 组中。

要将新成员添加到您的帐户,您需要注销系统并重新登录,这将更新您的组。

最后,您可以通过运行标准的 hello world 镜像来测试它是否有效(请注意,我们不需要 sudo 来运行 Docker 命令):

docker run hello-world

如果您看到以下输出,则已成功安装 Docker 并授予非 root 帐户对 Docker 的访问权限:

图 1.3- hello-world 的输出

图 1.3- hello-world 的输出

现在我们已经授予了 Docker 无需 sudo 运行的权限,我们可以通过学习如何使用 Docker CLI 来开始解锁我们可以使用的命令。

使用 Docker CLI

当您运行 hello-world 容器来测试您的安装时,您使用了 Docker CLI。Docker 命令是您将用于与 Docker 守护程序交互的命令。使用这个单个可执行文件,您可以执行以下操作,以及更多:

  • 启动和停止容器

  • 拉取和推送镜像

  • 在活动容器中运行 shell

  • 查看容器日志

  • 创建 Docker 卷

  • 创建 Docker 网络

  • 清理旧的镜像和卷

本章不打算详尽解释每个 Docker 命令;相反,我们将解释一些您需要用来与 Docker 守护程序和容器交互的常见命令。由于我们认为卷和网络对于本书非常重要,我们将对这些主题进行更详细的介绍。

您可以将 Docker 命令分为两类:通用 Docker 命令和 Docker 管理命令。标准的 Docker 命令允许您管理容器,而管理命令允许您管理 Docker 选项,如管理卷和网络。

docker 帮助

忘记选项或命令的语法是很常见的,Docker 意识到了这一点。每当您在尝试记住一个命令时遇到困难,您总是可以使用docker help命令来刷新您的记忆。

docker 运行

要运行一个容器,使用提供的镜像名称docker run命令。在执行docker run命令之前,您应该了解在启动容器时可以提供的选项。

在其最简单的形式中,您可以用来运行 NGINX Web 服务器的示例命令是docker run bitnami/nginx:latest。虽然这将启动一个运行 NGINX 的容器,但它将在前台运行:

图 1.4 – NGINX 容器启动

图 1.4 – NGINX 容器启动

要将容器作为后台进程运行,您需要在 Docker 命令中添加-d选项,这将以分离模式运行容器。现在,当您运行一个分离的容器时,您只会看到容器 ID,而不是交互式或附加的屏幕:

图 1.5 – Docker 运行输出

图 1.5 – Docker 运行输出

默认情况下,容器启动后将被赋予一个随机名称。在我们之前的分离示例中,容器被赋予了名称silly_keldysh

图 1.6 – Docker 命名示例

如果您没有为容器分配名称,当您开始在单个主机上运行多个容器时,很快就会变得混乱。为了使管理更容易,您应该始终使用一个易于管理的名称启动容器。Docker 提供了另一个选项,即run命令:--name选项。在我们之前的示例基础上,我们将命名我们的容器为nginx-test。我们的新docker run命令将如下所示:

docker run --name nginx-test -d bitnami/nginx:latest

就像运行任何分离的镜像一样,这将返回容器的 ID,但不会返回您提供的名称。为了验证容器是否以名称nginx-test运行,我们可以使用docker ps命令列出容器。

docker ps

每天,您都需要检索正在运行的容器的列表或已停止的容器的列表。Docker CLI 有一个名为ps的选项,它将列出所有正在运行的容器,或者如果您向ps命令添加额外选项,还会列出所有正在运行和已停止的容器。输出将列出容器,包括它们的容器 ID、镜像标签、入口命令、创建日期、状态、端口和容器名称。以下是当前正在运行的容器的示例:

图 1.7 – 当前正在运行的容器

如果您要查找的容器当前正在运行,这将很有帮助。如果容器已停止,或者更糟糕的是,如果您启动了容器但未能启动然后停止了呢?您可以通过向docker ps命令添加-a选项来查看所有容器的状态,包括先前运行的容器。当您执行docker ps -a时,您将看到与标准ps命令相同的输出,但您会注意到列表可能包括其他容器。

您如何区分正在运行的容器和已停止的容器?如果您查看列表的STATUS字段,正在运行的容器将显示运行时间;例如,Up xx hoursUp xx days。但是,如果由于任何原因容器已停止,状态将显示停止的时间;例如,Exited (1) 3 days ago

图 1.8 – Docker PS 输出

图 1.8 – Docker PS 输出

停止的容器并不意味着运行镜像时出现问题。有些容器可能执行单个任务,一旦完成,容器可能会正常停止。确定退出是正常的还是由于启动失败的一种方法是检查容器的日志。

docker start 和 stop

要停止运行的容器,请使用docker stop选项加上要停止的容器的名称。您可能希望停止容器,因为主机上的资源可能有限,只能同时运行几个容器。

如果您需要在将来的某个时间启动该容器进行额外的测试或开发,请执行docker start 容器名称,这将使用最初启动时的所有选项启动容器,包括分配的任何网络或卷。

docker attach

您可能需要交互式地访问容器来解决问题或查看日志文件。连接到正在运行的容器的一种方法是使用docker attach 容器名称命令。当您连接到正在运行的容器时,您将连接到正在运行的容器进程,因此如果您连接到运行进程的容器,您不太可能只看到任何一种命令提示符。实际上,您可能会在一段时间内什么都看不到,直到容器向屏幕输出一些数据。

一旦您连接到容器,您必须小心 - 您可能会意外停止运行的进程,从而停止容器。让我们以连接到运行 NGINX 的 Web 服务器为例。首先,我们需要使用docker ps来验证容器是否正在运行:

图 1.9 - docker ps 输出

图 1.9 - docker ps 输出

使用attach命令,我们执行docker attach bbadb2bddaab

图 1.10 - docker attach 输出

图 1.10 - docker attach 输出

如前面的屏幕截图所示,一旦您连接到正在运行的容器进程,似乎什么都没有发生。当您连接到一个进程时,您只能与该进程进行交互,并且您将看到的唯一输出是发送到标准输出的数据。在 NGINX 容器的情况下,attach命令已连接到 NGINX 进程。为了证明这一点,我们将离开附件并从另一个会话中 curl 到 Web 服务器。一旦我们 curl 到容器端口,您将看到日志输出到附加的控制台:

图 1.11 - 容器的标准输出

图 1.11 - 容器的标准输出

附加到运行的容器具有不同的好处,这取决于容器中运行的内容。

我们提到一旦附加到容器中,您需要小心。对 Docker 不熟悉的人可能会附加到 NGINX 图像并假设服务器上没有任何操作或附加失败。由于他们认为可能存在问题,因为它只是坐在那里,他们可能决定使用标准的Ctrl + C键盘命令跳出容器。这将把他们送回到 bash 提示符,他们可以运行docker ps来查看运行的容器:

图 1.12 - docker ps 输出

图 1.12 - docker ps 输出

NGINX 容器在哪里?我们没有执行docker stop命令,容器在我们附加到容器之前一直在运行。为什么容器在附加后停止了?

当附加到容器时,您附加到正在运行的进程。所有键盘命令将以与在交互式 shell 中运行 NGINX 的物理服务器相同的方式起作用。这意味着当用户使用Ctrl + C返回到提示符时,他们停止了运行的 NGINX 进程。如果容器的运行进程停止,容器也将停止,这就是为什么docker ps命令不显示运行的容器的原因。

不是使用ctrl-c返回提示符,用户应该使用Ctrl + P,然后是Ctrl + Q

有一个attach命令的替代方法:docker exec命令。exec命令不同于attach命令,因为您提供要在容器上执行的进程。

docker exec

与附加到容器相比,与运行的容器进行交互的更好选择是exec命令。您可以使用docker exec命令在容器中执行进程,而不是附加到容器。您需要提供容器名称和要在图像中执行的进程。当然,该进程必须包含在运行的图像中 - 如果图像中没有 bash 可执行文件,则在尝试在容器中执行 bash 时将收到错误。

我们将再次以 NGINX 容器为例。我们将使用docker ps验证 NGINX 是否正在运行,然后使用容器 ID 或名称,我们进入容器。命令语法是docker exec <选项> <容器名称> <进程>

图 1.13 – docker exec 示例

图 1.13 – docker exec 示例

我们包含的选项是-it,它告诉exec在交互式 TTY 会话中运行。在这里,我们要执行的进程是 bash。请注意名称已从原始用户和主机名更改。主机名是Blade,而容器名称是0a7c916e7411。您可能还注意到当前工作目录已从~更改为/app,提示不是以 root 用户身份运行,如$提示所示。

您可以像使用标准SSH连接一样使用此会话;您正在容器中运行 bash。

由于我们没有连接到容器,ctrl-c不会停止任何进程运行。要退出交互式会话,您只需输入exit,然后按Enter,即可退出容器。然后运行docker ps,您会注意到容器仍处于运行状态:

图 1.14 – docker ps 输出

图 1.14 – docker ps 输出

接下来,让我们看看关于 Docker 日志文件我们能学到什么。

docker logs

docker logs命令允许您使用使用docker ps命令检索的容器名称或容器 ID 检索容器的日志。您可以查看ps命令中列出的任何容器的日志;无论它当前是否正在运行或已停止。

日志文件通常是排除容器启动失败或容器处于退出状态的唯一方法。例如,如果您尝试运行一个镜像,镜像启动后突然停止,您可以通过查看该容器的日志找到答案。

要查看容器的日志,可以使用docker logs <容器 ID 或名称>命令。

要查看容器 ID 为7967c50b260f的容器的日志,您将使用以下命令:

docker logs 7967c50b260f

这将把容器的日志输出到您的屏幕上,这可能非常长且冗长。由于许多日志可能包含大量信息,您可以通过为logs命令提供附加选项来限制输出。以下表列出了查看日志的可用选项:

docker rm

一旦您为容器命名,除非使用docker rm命令将其删除,否则无法使用分配的名称启动不同的容器。如果您有一个名为nginx-test的正在运行的容器,它已停止,并且您尝试使用名称nginx-test启动另一个容器,Docker 守护程序将返回错误,指出该名称正在使用中:

图 1.15 – Docker 命名冲突错误

图 1.15 – Docker 命名冲突错误

这个容器没有在运行,但守护进程知道该容器名称先前已被使用,并且仍在先前运行的容器列表中。

如果您想重复使用相同的名称,您需要在启动另一个具有该名称的容器之前删除容器。这是在测试容器映像时经常发生的情况。您可能启动一个容器,只是发现应用程序或映像存在问题。您停止容器,修复映像/应用程序问题,并希望重新部署使用相同的名称。由于该名称先前已被使用并且仍然是 Docker 历史的一部分,您需要在重用名称之前删除映像。

我们还没有讨论卷,但是在删除具有附加卷的容器时,最好在删除命令中添加-v选项。将-v选项添加到docker rm命令将删除附加到容器的任何卷。

总结

在本章中,您了解了 Docker 如何解决常见的开发问题,包括令人头痛的“在我的机器上可以运行”问题。我们还介绍了您将在日常使用中使用的最常用的 Docker CLI 命令。我们通过查看如何处理容器的持久数据和自定义容器网络来结束本章。

在下一章中,我们将介绍容器为什么以及如何使用持久数据。我们将解释可以附加到容器的每种数据类型,包括卷、绑定挂载和 tmpfs。

问题

  1. 单个 Docker 映像可以在任何 Docker 主机上使用,而不管使用的架构如何。

A. 真

B. 假

  1. Docker 使用什么来将多个镜像层合并成单个文件系统?

A. 合并文件系统

B. NTFS 文件系统

C. EXT4 文件系统

D. 联合文件系统

  1. 在使用版本高于 4.0 的内核时,最常用的 Docker 存储驱动是什么?

A. AUFS

B. ZFS

C. VFS

D. Overlay2

  1. 当您交互式地编辑容器的文件系统时,更改会写入哪一层?

A. 操作系统层

B. 最底层

C. 容器层

D. 临时层

  1. 假设镜像包含所需的二进制文件,哪个 Docker 命令允许您访问容器的 bash 提示符?

A. docker shell -it /bin/bash

B. docker run -it /bin/bash

C. docker exec -it /bin/bash

D. docker spawn -it /bin/bash

  1. 当容器停止时,Docker 守护程序将删除容器的所有痕迹。

A. 真

B. 假

  1. 哪个命令会显示所有容器的列表,包括任何已停止的容器?

A. docker ps -all

B. docker ps -a

C. docker ps -list

D. docker list all

第二章:使用 Docker 数据

曾经,容器用于无状态工作负载,可以快速扩展,快速移动,并且在不影响工作负载的情况下从一个位置移动到另一个位置。无状态工作负载限制了您可以运行的容器类型,禁止您运行除基础镜像中存储的项目之外的任何内容。

在本章中,我们将涵盖以下主题:

  • 为什么需要持久数据

  • Docker 卷

  • Docker 绑定挂载

  • Docker tmpfs 挂载

让我们开始吧!

技术要求

本章没有任何技术要求。

如果您想使用本章中的示例创建卷,可以使用我们在第一章中创建的 Docker 主机,Docker 和容器基础。本章中将使用的卷对于未来章节并非必需。

您可以访问第一章中创建 Docker 主机的代码,Docker 和容器基础,访问以下 GitHub 存储库:github.com/PacktPublishing/Kubernetes-and-Docker-The-Complete-Guide

为什么需要持久数据

让我们考虑一个示例用例,您可能希望从容器向持久位置写入数据。

假设您有一个系统,需要您部署由MySQL数据库和运行在NGINX上的基于 Web 的应用程序。您使用标准docker run命令为应用程序启动两个容器。您与用户进行了一些初始测试,他们确认一切都按预期工作。用户在添加记录、查询记录、编辑和删除方面都取得了成功 - 一切都正常工作。容器运行几天后,您收到一封来自安全部门的电子邮件,告知所有用户所有 MySQL 服务器需要尽快部署新补丁以解决安全漏洞。

您可以快速处理一个包含最新补丁的新 MySQL 镜像,并将其推送到您的容器注册表。与用户一起工作,您决定部署新容器的时间,并通过以下方式开始该过程:

  • 停止运行的容器。

  • 使用与先前图像相同的容器名称运行新图像。为此,您需要使用docker rm命令删除当前容器。

  • 启动新的 MySQL 容器。

容器启动后,你登录验证 MySQL 是否在运行,并确认补丁已经应用。接下来,你登录应用程序并搜索数据库中的记录,收到一个未找到记录的错误。你觉得这很奇怪,因为系统已经运行了几天。为了进一步排除故障,你直接登录数据库检查数据库的健康状况,发现服务器上没有数据库。

你的数据库发生了什么?为什么看起来数据库服务器已经被清空并重新初始化?你知道系统在过去几天一直在运行,一切都按预期运行,也就是说,直到你下载了一个新的 MySQL 镜像并在原始容器的位置启动它。

回想一下我们在第一章中关于短暂讨论,Docker 和容器基础,容器的可写层是短暂的,当容器被移除时,所有写入其中的数据都会丢失,即使你使用相同的名称启动一个容器。当你移除容器以重用名称时,包含数据库的容器层被删除。新容器启动时,它创建了一个新的容器层,当 MySQL 启动时,它没有看到任何基本文件,并创建了一个新的、完全初始化的 MySQL 实例。

当然,你不希望每次需要部署数据库服务器的新版本时都丢失数据库。由于容器层是短暂的,你需要将数据库存储在基础层之外的某个东西上,这样在镜像升级之间可以持久存在。到目前为止,我们一直告诉你容器是短暂的,那么你如何配置容器来保存数据呢?

幸运的是,Docker 包括使用两种方法向容器添加持久数据的能力:

  • Docker 卷

  • Docker 绑定挂载

还有第三种选项,使用主机的 RAM 提供存储,称为tmpfs。这种类型的挂载在容器重启、Docker 重启或主机重启时不是持久的。它只用作临时存储数据的位置,存储在高速 RAM 中,是真正的短暂的。虽然它不提供持久性,但在选择 tmpfs 的特定用例中可能是有益的。

当您需要运行需要持久数据的容器时,应选择哪个选项?卷和绑定挂载都类似地存储文件在本地主机文件系统上,以提供持久性给您的容器。最终决定取决于理解每个选项是如何管理的,以及如果不正确使用可能会暴露哪些数据。

Docker 卷

Docker 卷是向容器添加持久数据的首选选项。卷只是本地主机上的一个目录,通过卷挂载映射到容器中。创建卷时,在主机文件系统上创建一个新目录,通常在/var/lib/docker/volumes//下。如果您有主机的 root 访问权限,您可以像查看其他目录一样查看文件结构。以下屏幕截图显示了在运行使用卷的 Docker 主机上volumes目录下的目录:

图 2.1 - Docker 文件夹目录

图 2.1 - Docker 文件夹目录

为了在重启之间保持信息,Docker 使用 Boltdb 在主机上的各种数据库中存储关键元数据,Boltdb 是用 Go 编写的快速数据库,用于存储持久键值。当浏览/var/lib/docker文件夹时,您可能会遇到两个 Boltdb 数据库:

  • /var/lib/docker/volumes/metadata.db:维护 Docker 卷的元数据,如名称、驱动程序、标签和选项

  • /var/lib/docker/network/files/local-kv.db:维护 Docker 网络的元数据

由于本章重点是数据,我们将使用metadata.db数据库。正如您将在本章后面看到的,当您创建 Docker 卷时,可以提供名称或标签等选项。这些信息存储在数据库中以保持卷的持久性。

以下是 JSON 格式的metadata.db中关于名为webdata的 Docker 卷的示例:

webdata{"Name":"webdata","Driver":"local","Labels":null,"Options":null}

每个 Docker 卷在/var/lib/docker/volumes目录中都有一个目录。在每个卷文件夹中,有一个名为_data的目录,其中包含容器的实际数据。

在使用卷与容器之前,必须创建它。可以使用 Docker CLI 手动创建 Docker 卷,也可以在启动容器时由 Docker 守护程序自动创建。由于这两种方法都是由 Docker 创建的,它们由 Docker 自身拥有和管理,因此可以使用 Docker CLI 轻松管理和跟踪它们。

使用 CLI 创建卷

要创建 Docker 卷,我们可以使用卷管理选项:

docker volume <选项>

可用的选项如下:

表 2.1 - 卷管理选项

表 2.1 - 卷管理选项

要创建新卷,请使用 create 选项:

docker volume create <可选卷名称>

执行 create 后,您将看到创建的卷名称。如果您没有提供可选的卷名称,Docker 将分配卷 ID 作为名称。在不提供卷名称的情况下创建卷称为匿名卷:

图 2.2 - 匿名卷输出

图 2.2 - 匿名卷输出

匿名卷在向主机添加使用卷的额外容器时可能难以跟踪。因此,最佳做法是在创建时为卷命名,而不是允许 Docker 生成一个很长的匿名卷名称。

在创建时为其提供卷名称的任何卷都称为命名卷。要创建命名卷,您需要将卷名称提供给 docker volume create 命令:

图 2.3 - 命名卷输出

](image/Fig_2.3_B15514.jpg)

图 2.3 - 命名卷输出

使用任一方法创建卷后,您可以通过查看 /var/lib/docker/volumes 来验证为您的卷创建了目录。您会发现一个以 create 命令返回的卷名称命名的目录。

现在您知道如何创建卷,下一步是将其与容器一起使用。

在容器中挂载卷

在容器中挂载卷的过程与命名卷或匿名卷的过程相同。我们已经介绍了使用 Docker CLI 创建卷的过程,但我们没有解释如何让 Docker 自动创建卷。

在本节中,我们将解释如何让 Docker 自动创建卷并挂载它。我们还将解释如何将先前创建的命名卷挂载到容器中。

在容器中挂载卷时,您需要为docker start命令提供两个选项中的一个。您可以使用--mount-v这两个选项来挂载卷。如果您运行的是标准容器,您可以使用任一选项,但-v是最常用的选项。

在本章的前面,我们创建了一个名为pv-mysql-data的卷。现在我们想要使用命名卷启动我们的 MySQL 容器,这样我们就会有一个持久的数据库。要在容器中挂载卷,您需要在启动容器时传递-v选项。-v选项需要两个参数:卷名称和容器挂载点。在下面的示例命令中,您可以看到启动 MySQL 的命令,使用了命名卷挂载。我们还添加了一些额外的选项传递给容器,特别是-e选项,用于设置环境变量:

docker run --name mysql-01 -v pv-mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=my-password -d mysql

这将启动一个运行 MySQL 的容器,名称为mysql-01,在容器中挂载了一个名为pv-mysql-data的卷,挂载点为/var/lib/mysql。最后一个选项,使用了-e,创建了一个名为MYSQL_ROOT_PASSWORD的环境变量,其值为my-password

容器启动后,您可以查看卷目录,通过检查目录中的数据来验证 MySQL 是否正确挂载了卷:

图 2.4 – MySQL 持久卷

图 2.4 – MySQL 持久卷

正如您所看到的,新卷包含了在第一次启动新的 MySQL 服务器时创建的所有标准文件。

我们还提到过,您可以让 Docker 自动创建卷,而不是自己创建卷。要让 Docker 在容器启动时创建卷,请运行与使用预先创建的卷相同的命令。例如,使用之前的 MySQL 示例,假设我们没有使用docker volume create命令预先创建卷。

让我们执行相同的命令:

docker run --name mysql-01 -v pv-mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=my-password -d mysql

当我们这样做时,Docker 守护程序将会意识到没有名为pv-mysql-data的现有卷,它将在挂载到容器之前创建一个。

挂载现有卷

与容器层不同,卷也存储在本地主机上,如果容器从 Docker 中移除,卷不会丢失。在我们之前的 MySQL 升级示例中,我们将数据库服务器升级到了一个更新的版本。由于我们希望保持容器名称与先前部署的容器相同,我们移除了旧容器,并使用相同的名称启动了新容器。通过移除容器,我们移除了存储在本地文件系统上的容器层。

为了避免在升级后丢失我们的数据库,或者在容器从主机中移除后,我们可以在新运行的容器中挂载现有卷。由于卷默认情况下不会被删除,数据仍然存储在本地文件系统上,并且可以被任何其他容器使用。

当我们停止并移除我们的 MySQL 服务器时,卷仍然保持完好。当我们启动新的 MySQL 容器时,我们只需要使用相同的卷名称提供-v选项。这就是为什么创建一个命名卷优于匿名卷。在我们的例子中,我们创建了一个名为pv-mysql-data的命名卷。要运行新的容器,使用相同的卷,我们可以使用docker run命令和-v选项,如下所示:

docker run --name mysql-01 -v pv-mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=my-password -d mysql:v2

这将使用与先前容器相同的卷启动我们的新mysql:v2容器,现有数据库完全完好无损。

在多个容器中挂载卷

如果您有一个需要多个容器的应用程序,并且每个容器都需要相同的数据,那么您可以为每个容器创建一个卷并将数据复制到每个容器中,但更有效的方法是在多个容器之间共享单个卷。

Docker 卷的一个独特特性是多个容器可以访问同一个卷。虽然这听起来像是一个提供共享数据的简单解决方案,但你需要记住,并非每个应用程序在多个进程访问相同数据时都能很好地运行。以我们的数据库示例为例,启动使用相同卷的第二个容器会导致第二个容器的 MySQL 服务器在启动时失败。由于文件被第一个容器的数据库锁定,第二个容器无法获得对数据库的独占访问以挂载它。如果你查看第二个实例的日志,你会看到这里显示的错误:

图 2.5 – 锁定错误输出

图 2.5 – 锁定错误输出

有一些用例中,共享卷将为你的应用程序带来好处。想象一下,你在主机上运行了几个实例的 Web 服务器,它们只呈现每晚从一个文件夹中检索的数据。一个选择是每晚使用流水线创建一个新的容器,但是在这个例子中,我们假设你没有建立 CI/CD 系统。

相反,你可能在主机上有一个过程将数据拉入到创建了 Docker 卷的目录中。每个 Web 服务器将使用卷挂载,使用相同的 Docker 卷。这允许每个容器从单个共享位置访问数据,而无需对基本镜像进行任何更改。

为了实现这一点,当容器启动时,你只需要为每个容器提供相同的挂载选项。例如,我们在主机上创建了一个名为webdata的卷,我们想要启动四个 NGINX 服务器,它们将使用 Docker 卷中的数据。当你启动每个实例时,只需使用相同的webdata命名卷:

docker run --name webserver01 -v webdata:/opt/web/data -d bitnami/nginx:latest docker run --name webserver02 -v webdata:/opt/web/data -d bitnami/nginx:latest docker run --name webserver03 -v webdata:/opt/web/data -d bitnami/nginx:latest docker run --name webserver04 -v webdata:/opt/web/data -d bitnami/nginx:latest

由于 NGINX 服务器只会读取数据,我们不会遇到我们在 MySQL 中遇到的任何锁定错误。

现在我们已经在谈论卷并创建了一些卷,您可能会意识到当主机上有许多卷时,可能会难以记住存在哪些卷。下一节将讨论我们卷的一些基本管理选项。

列出 Docker 卷

请记住,卷由 Docker 管理,因此守护程序知道每个已创建的卷。如果要列出您或 Docker 守护程序创建的每个卷,请使用docker volume list选项:

图 2.6 - docker 卷列表输出

图 2.6 - docker 卷列表输出

此选项将显示 Docker 当前管理的所有卷。

清理卷

默认情况下,Docker 永远不会删除为容器创建的卷。我们已经知道,当我们使用卷停止和启动容器时,数据会持久存在,但是当我们不再需要数据时该怎么办呢?

想象一下,/var/lib/docker/volumes文件夹中的数据量可能会以惊人的速度增长。因此,最佳做法是修剪或删除卷以释放磁盘空间 - 我们通常将其称为垃圾收集过程的一部分。

从不再需要的镜像中删除数据的最简单方法是在从 Docker 中删除容器时使用-v选项。在本书中,我们已经多次使用docker rm 来从主机中删除镜像。如果您有一个附加到容器的卷,并且希望在删除镜像时删除数据,可以将-v选项添加到rm命令中,这将删除与容器关联的所有卷。要删除我们的 MySQL 服务器和持久数据,我们将使用以下命令:

docker rm -v mysql

有时您可能不希望通过删除容器来删除数据。删除卷是无法撤消的,因此更好的做法是删除容器并保留卷一段时间。在一定时间后,您可能更愿意删除卷或卷。

如果要删除单个或多个卷,可以使用docker volume rm选项。您可以在rm选项后提供卷名称或名称。系统将删除提供的每个卷,从而释放主机系统上的磁盘空间。

您可能担心自己可能会删除正在容器使用的卷。不用担心 – Docker 已经为您考虑到了。如果您尝试删除任何当前正在运行的容器使用的卷,或者分配给已停止容器的卷,您将收到错误:

守护程序的错误响应:删除 test-auto: 卷正在使用 - [51000e2f61c79ae705cdac78692fa5590fb2b26d3d0eb0a3916df230 daf1b617]

Docker 只允许您删除尚未被运行容器打开的卷。

您可能有许多要删除的卷。虽然您可以使用remove命令提供每个名称,但 Docker 提供了另一个选项,称为清理。清理将查看所有卷,并删除任何当前未挂载在运行容器上的卷。

谨慎使用此选项 – 只有在您知道未使用的卷不包含您出于任何原因需要的数据时才使用它。此过程是最终的,一旦运行,它将删除所有未使用的卷。

要删除未使用的卷,我们使用docker volume prune选项。此命令将查看所有卷,并删除任何未附加到运行容器的卷。当您发出命令时,它将警告您它将删除任何没有至少一个运行容器的卷:

图 2.7 – docker 清理确认

图 2.7 – docker 清理确认

谨慎选择 y 以确认 – 此操作没有撤销命令。一旦执行,您将收到已删除卷的摘要,包括它们的名称和总共回收的磁盘空间:

图 2.8 – docker 清理结果

图 2.8 – docker 清理结果

这完成了本章的 Docker 卷部分。请记住,卷只是为容器提供数据持久性的一种方式。另一种方法称为绑定挂载,虽然它也像卷一样使用主机文件系统,但绑定挂载有一个非常不同的用例。

Docker 绑定挂载

您可以用来为容器提供持久数据的第二个选项称为绑定挂载。总的来说,卷和绑定挂载可能看起来相似;两者都使用本地主机的文件系统,并且都使用docker -v选项进行挂载。虽然它们共享许多相同的特征,但绑定挂载与卷挂载在一个主要领域有所不同:它 由 Docker 管理。

与 Docker 卷不同,绑定挂载不能由 Docker 创建。您不能使用docker volume create选项创建绑定挂载;但是,当容器启动时,Docker 可以自动创建绑定挂载。尽管 Docker 可以在主机上创建绑定挂载位置,但它不会“管理”挂载。

由于 Docker 不管理绑定挂载,因此无法使用 Docker 命令删除它们。Docker 不会在列表中跟踪绑定挂载的位置。如果您在主机的文件系统的不同区域创建绑定挂载,您需要跟踪每个挂载的位置,以便在不再需要数据时手动删除它们。

绑定挂载是主机文件系统上的任何位置的目录,使用docker -v选项绑定到容器中。与卷不同,卷通常位于预定义位置,例如/var/lib/docker/volumes,绑定挂载可以位于主机文件系统的任何位置。由于目录位于文件系统上,当尝试访问目录时可能会遇到权限问题。这个讨论超出了本书的范围,并涉及到 Linux 文件系统权限的领域。

您可以绑定任何现有目录或创建一个新目录,可以通过预先创建目录或让 Docker 在容器启动时创建目录。

在我们解释如何使用绑定挂载之前,您可能会问自己,“为什么要使用绑定挂载?”或“为什么要使用卷?我为什么要关心我应该使用哪一个?”我们将解释为什么您可能或可能不想使用绑定挂载,然后将它们与卷进行比较。

当您需要与运行的容器共享主机系统上的某些内容时,绑定挂载可能是有益的。例如,您在本地笔记本电脑上开发应用程序,并且希望在最终编写代码之前测试应用程序。您可以将源代码存储在/source中,然后在编译时,您可以将可执行文件和任何库存储在/apps/testapp中。让我们来看一下:

  1. 您可以使用-v选项启动一个容器,该选项将在容器中绑定挂载本地主机的/apps/testapp文件夹。这与在容器中使用卷并没有太大区别。实际上,当使用docker run -v选项将卷挂载到容器时,挂载绑定位置的语法与挂载卷到容器时相同。但是,与在-v选项中提供卷名称不同,您需要提供本地主机目录;例如:

docker run -d -v /apps/testapp:/bin/testapp ubuntu:latest

这将启动一个运行 Ubuntu 的容器,容器内部有一个挂载点,绑定到本地主机的/apps/testapp路径,编译的应用程序就在这里。

  1. 要测试应用程序,您需要附加到运行中的镜像,并通过运行/bin/testapp/testapp来执行应用程序。由于容器的挂载使用了本地主机目录,您可以测试该应用程序,如果遇到任何错误,您可以修复代码并简单地重新编译可执行文件。

  2. 一旦重新编译,您可以再次在容器中运行新的可执行文件来测试应用程序。这样可以避免在发现问题后创建多个测试镜像并在镜像之间进行迭代。一旦成功测试了应用程序,您可以停止容器并创建包含应用程序的最终容器镜像。

Docker 卷无法提供相同的解决方案,因为它们都位于主机上的一个目录中,无法指向现有目录。

使用绑定挂载还有其他示例用例。要记住的主要观点是,绑定挂载可以用于在主机系统上的任何位置与容器共享数据。在正确的场景中使用绑定挂载非常有用,但如果使用不当,可能会导致系统不稳定或安全漏洞。

毋庸置疑,当在容器中使用主机系统上的任何现有目录时,您应该小心。将主机的/etc目录绑定到容器中可能会导致意想不到的结果。在容器中运行 shell 的人可以轻松地删除或编辑文件,或多个文件,使主机系统不稳定。

Docker tmpfs 挂载

在本章的开头,我们提到 Docker 允许您将主机的 RAM 用作容器数据的临时存储位置。这种类型的挂载不会持久化数据,但对于适当的工作负载,它可以是一个非常有用的存储位置。tmpfs 提供了一些独特的优势,这些优势在卷或绑定挂载中是不可用的:

  • 大小可以预先定义以限制所消耗的 RAM 量。

  • 提供非常快速的数据访问。

tmpfs 挂载也有一些限制:

  • 它们仅在 Linux 上可用;不支持 Windows。

  • 单个 tmpfs 只能挂载到一个容器。

在容器中使用 tmpfs 挂载

可以通过添加--mount或使用--tmpfs选项来启动容器并使用 tmpfs 挂载。一般来说,您应该默认使用--mount选项,因为--tmpfs不允许对挂载进行任何自定义。如果使用--tmpfs,您将无法设置大小限制或任何文件模式安全性。由于这种类型的挂载将使用昂贵的资源,即主机的 RAM,您将希望为您的挂载创建一个大小。由于这些限制,我们强烈建议您不要使用--tmpfs来创建您的 tmpfs 挂载。

要为 NGINX 容器使用 tmpfs 挂载,您需要在启动镜像时使用--mount选项:

docker run --mount type=tmpfs,target=/opt/html,tmpfs-mode=1770,tmpfs-size=1000000 --name nginx-test -d bitnami/nginx:latest

run命令中指定--mount后,挂载选项允许您添加多个选项。在我们的示例中,我们添加了挂载类型、容器中的目标挂载、文件模式和大小的选项。以下表格显示了每个选项的详细信息:

表 2.2 - Docker tmpfs 选项

表 2.2 - Docker tmpfs 选项

现在我们已经创建了一个 tmpfs 挂载,我们想要列出主机上的卷,以验证我们有一个 tmpfs 挂载。您可能还记得,我们可以使用docker volume ls命令列出卷。创建 tmpfs 挂载后的输出如下所示:

图 2.9 - Docker 卷列表

图 2.9 - Docker 卷列表

请注意,列表是空的;根据 Docker 守护程序,没有正在使用的卷。tmpfs 挂载不会出现在卷列表中,因为它们不是“真正的”卷;只有在使用docker volume ls命令时才会出现卷。

如果您想验证容器中的 tmpfs 挂载,可以查看容器的docker inspect命令输出,并查找"Mounts"部分:

图 2.10 – Docker 检查输出

图 2.10 – Docker 检查输出

您还可以通过使用docker exec命令执行 Linux df命令来验证运行中的容器内的挂载:

图 2.11 – Linux 中的 tmpfs 挂载

图 2.11 – Linux 中的 tmpfs 挂载

正如您在df命令输出的突出显示部分中所看到的,挂载点存在于容器中,大小是正确的。

我们想展示一个最后的例子,即使用一个未指定 tmpfs 空间大小的挂载。如果您未指定大小,Docker 将使用主机 RAM 的一半创建一个挂载。我们的示例服务器有26 GB RAM,并且我们已经使用相同的容器执行了docker run命令,而没有指定大小。

请注意,在mount命令中我们确实指定了大小。要验证容器是否具有/opt/html目标,我们可以使用docker exec执行df -u命令,并确认已创建/opt/html挂载。正如您可能已经注意到的那样,卷的大小为13 GB,这是主机26 GB RAM 的一半。需要指出的是,虽然卷设置为 26 GB,但目前没有数据,因此尚未消耗任何 RAM。只有在容器中存储数据时才会消耗 RAM,因此您需要非常小心处理 tmpfs 卷,否则可能会在主机上耗尽 RAM 并可能导致系统崩溃。

总结

在本章中,您学会了多种处理容器中数据的方法。阅读完本章后,您应该知道如何决定要为容器使用何种适当类型的存储。

在下一章中,我们将通过学习 Docker 默认桥接网络来学习 Docker 网络。我们还将学习如何使用和创建用户定义的桥接,并在网络上公开容器服务。

问题

  1. 以下哪种卷为容器提供持久存储?

A. tmpfs

B. 绑定挂载

C. 卷

D. SAN

  1. Docker 卷必须在挂载之前创建。

A. 真

B. 错误

  1. 您在具有 64 GB RAM 的主机上创建了一个 tmpfs 卷。在创建过程中,您未设置大小。Docker 会创建多大的卷?

A. 1 GB

B. 64 GB

C. 20 GB

D. 32 GB

  1. 当您删除一个容器时,Docker 卷会自动删除。

A. 真

B. 错误

  1. 哪种卷类型由 Docker 守护程序管理?

A. 绑定卷

B. 命名卷

C. 所有卷

D. 无

  1. 如何删除未使用的绑定卷?

A. 不需要;Docker 会自动删除它

B. docker volume prune

C. docker volume bind prune

D. 您必须手动删除绑定文件夹

第三章:理解 Docker 网络

到目前为止,我们已经专注于在主机系统上本地访问容器的方法。有些情况下,只需要本地容器访问,但通常情况下,您会希望将容器暴露给外部用户或系统。将容器暴露给外部用户并不总是简单地运行一个容器。您需要考虑 Docker 提供的各种选项,以连接您的容器到网络。为了避免挫折,您还应该了解系统如何使用 IP 进行通信,包括在暴露 IP 端口方面的任何限制。

在本章中,我们将涵盖以下主题:

  • 探索 Docker 网络

  • 创建用户定义的桥接网络

让我们开始吧!

技术要求

本章没有任何技术要求。

如果您想使用本章中的示例创建网络,可以使用我们在第一章中创建的 Docker 主机,Docker 和容器基础。本章中将使用的网络对于未来的章节并非必需。

您可以访问第一章中创建 Docker 主机的代码,Docker 和容器基础,访问以下 GitHub 存储库:github.com/PacktPublishing/Kubernetes-and-Docker-The-Complete-Guide

探索 Docker 网络

在本节中,我们将讨论 Docker 中可用的网络选项以及如何使用它们将您的容器暴露给外部用户和系统。Docker 包括各种网络选项,以连接容器到您的网络,如果没有包括的网络选项符合您的要求,您可以从许多第三方网络附加组件中进行选择,这些组件提供了基本网络堆栈中可能不包括的功能。默认情况下,Docker 网络侧重于单个主机,但对于更复杂的用例,它包括网络功能,通过使用 Docker Swarm 来促进跨主机网络。由于行业已经摆脱了使用 Docker Swarm 转向其他提供,如 Kubernetes,本章将专注于单主机网络。

为了避免在暴露容器时可能出现的挫折,您应该对 IP 如何使用端口进行通信有很好的理解。你们中的许多人可能对 IP 很了解,但我们认为提供一个关于 TCP/IP 如何使用端口进行通信的简短复习会很有益处。

快速 TCP/IP 端口复习

我们假设您对 TCP/IP 有一定的了解,但对于那些对此还不熟悉的人来说,重点介绍一些网络主题是很重要的,因为这些主题将在本章中被引用。理解端口是一个至关重要的概念,以充分了解在 Docker 中暴露服务的限制。

正如您可能知道的那样,当您在系统上配置 IP 时,您会为系统中的每个网络适配器分配一个唯一的 IP 地址。当进行传入或传出连接时,请求包括 IP 地址和165535之间的端口。您可能在请求中并不总是看到端口,因为许多应用程序将根据所使用的协议自动包含在默认请求中。当您在浏览器中输入 URL 时,通常只需使用协议和主机名输入 URL。例如,假设您想打开主 Kubernetes 页面,所以您在浏览器中输入kubernetes.io。不显示或不需要的是端口。HTTP 的默认端口是 80,所有浏览器都会默认请求使用端口80 - 在幕后,浏览器正在请求kubernetes.io:80

IP 地址和端口的组合称为套接字,表示为<IP 地址>:<端口>(即192.168.1.1:443)。套接字是双向通信所必需的。当您请求一个网页时,您的计算机将使用从4915265535之间随机选择的端口进行传出请求。如果您对 IP 还不熟悉,这可能有点令人困惑,因此以下是一个从工作站到两个不同的 Web 服务器的连接示例:

图 3.1 – 套接字示例

图 3.1 – 套接字示例

步骤 1中,工作站向运行在192.168.100.10上的 Web 服务器发送请求。工作站使用随机生成的端口来启动到端口80的 Web 服务器的传出请求。接下来,在步骤 2中,Web 服务器使用客户端 IP 192.168.100.150和端口49160回复客户端。

我们添加了步骤 3,以显示与另一个 Web 服务器的同时连接将生成不同的出站端口,从而针对端口 80 上的第二个 Web 服务器。

将端口绑定到服务

在服务器端,您可能正在运行像 NGINX 这样的服务器来托管网站,您必须将套接字绑定到 Web 服务器进程。绑定的 IP 地址可以是单个 IP,也可以使用地址 0.0.0.0 绑定到所有可用的服务器 IP 地址上的所有 IP 地址。

作为示例,我们将使用具有单个网络卡和单个 IP 地址的主机。当您想要将端口绑定到进程时,您配置该进程以使用服务器的 IP 并分配端口80。我们想要强调上一句中的一个关键词,绑定 - 根据定义,绑定某物是将其紧密地绑定或固定。当您将端口绑定到 IP 地址时,它是专门绑定的,并且不能绑定到使用该 IP 地址的任何其他运行中的进程。换句话说,由于套接字已被绑定,它不能绑定到主机上的任何其他进程。

如果您尝试配置已绑定的端口上的进程,您将收到类似以下错误的消息:

绑定到 192.168.1.1:443 失败:端口已被分配。

如果您想在主机上运行另一个进程,您只需要指定一个唯一的端口。在运行 NGINX 的同一台服务器上,您可以运行在端口3306上运行的 MySQL 服务器。由于该端口与在端口80上运行的 Web 服务器不同,因此它创建了一个唯一的套接字。

使用端口公开容器的方式取决于您为容器使用的 Docker 网络驱动程序。在本章中,我们将解释常见的网络选项以及如何在主机上配置和使用每个选项。一旦您了解了这些选项,我们将通过查看如何使用端口分配将容器暴露给本地主机系统外的用户来完成本章。

首先,让我们开始讨论 Docker 包含的各种网络驱动程序。

Docker 网络驱动程序

Docker 的网络系统是模块化的。基本的 Docker 安装包括一些网络驱动程序,如果您需要专门的网络驱动程序,还可以从其他供应商那里获得选项。对于本书的目的,我们将仅使用网络的包含网络驱动程序。

默认情况下,您可以选择使用五种网络选项。实际上,您有四种选项,第五种选项是无; 也就是说,禁用网络。Docker 包含的选项在下表中详细说明:

图 3.2 - Docker 网络驱动程序

](image/Fig_3.1a_B15514.jpg)

图 3.2 - Docker 网络驱动程序

默认的 Docker 安装将包括一些预配置的网络:

  • 默认桥接网络

  • 默认主机网络

大多数 Docker 用户只是使用默认的桥接网络,这对大多数用例都有效,但它确实有一些需要考虑的限制。

在本节中,我们将介绍默认桥接网络、自定义桥接网络和无网络选项。主机网络选项主要用于当您的主机是 Docker Swarm 的一部分时使用,但如果您了解在使用它时端口暴露的限制,也可以在没有 Swarm 的情况下使用。

默认桥接网络

在前面的表中,您了解到桥接网络只为在同一 Docker 主机上运行的容器提供网络。除非您使用 Docker Swarm 运行多个 Docker 主机,否则通常会使用桥接网络与 Docker 容器一起使用。安装 Docker 时,它将创建所谓的默认 Docker 桥接网络。通过为所有安装提供默认桥接,Docker 使得在容器中使用网络变得非常简单。许多用户只是开始使用默认的网络设置和选项来使用 Docker,从而启动容器而不了解默认桥接的限制和潜在安全风险。在使用 Docker 主机之前,您应该始终考虑创建用户定义的桥接,或根据您的要求创建多个桥接。

Docker 包含一个易于使用的默认网络,那么为什么要考虑创建用户定义的桥接?由于默认桥接保持向后兼容,许多桥接功能必须受到限制。由于这些限制,与用户定义的桥接相比,默认桥接被认为是较差的。虽然这听起来有点苛刻,但请考虑以下清单,其中详细说明了在使用默认桥接时需要考虑的事项:

  • 当启动一个容器时,如果没有指定网络,它将使用默认的桥接。这意味着多个容器默认情况下可以通信,而不需要考虑工作负载。

考虑: 如果您运行多个容器,并且希望将某些容器与其他容器隔离开来,您可能会无意中允许容器之间的通信,因为它们使用默认桥接。

  • 默认桥接限制容器之间的通信仅限于 IP 地址。连接到用户定义的桥接的容器可以使用容器名称或 IP 地址进行通信。使用用户定义的桥接的容器可以使用 IP 地址或主机名进行通信。

考虑: 当您启动容器时,IP 地址可能与上次运行镜像时不同。

如果您想配置一个具有多个相互作用的容器的应用程序,您可以使用容器名称,这些名称将在重新启动时保持不变。如果您使用默认桥接,您可能需要更改配置文件,因为容器以不同的 IP 地址启动。

  • 使用默认桥接的容器需要在将它们移动到不同网络之前停止。但是,在使用用户定义的桥接的容器上,您可以在不重新启动容器的情况下更改网络。

考虑: 根据您的工作负载,您可能无法在没有约定的维护窗口的情况下停止运行的容器。虽然在大多数公司中,网络更改仍然需要更改请求,但如果您使用用户定义的桥接,则可以在不停止容器的情况下进行更改。这将限制对应用程序的任何潜在影响,并在新的网络桥接上配置错误时提供快速回退。

  • 使用单个默认桥接会限制所有容器的网络选项。由于所有容器都在单个网络上,所有容器的网络设置都相同。

考虑: 您可能需要一些容器运行巨幅帧的要求,而其他容器将使用标准的 MTU 大小。如果您只使用单个默认桥接,您只能设置一个 MTU 大小。但是,您可以创建一个将 MTU 设置为 9000 的用户定义的桥接,另一个保持默认 MTU 大小为 1500。

有了这个,你就可以看到为什么我们提到默认桥接比用户定义的桥接差。根据您的用例,您可能可以将默认桥接用于您所有的需求,就本书而言,我们将使用默认桥接进行练习。然而,在运行 Docker 的生产环境中,您应该始终创建一个新的用户定义的桥接。

现在您已经了解了各种网络选项以及每种类型的优缺点,是时候深入了解管理和创建 Docker 网络了。在创建网络之前,我们将查看 Docker 默认包含的默认网络以及如何使用 Docker CLI 查看网络的详细信息。

查看可用网络

要查看 Docker 主机上所有现有的网络,我们可以使用 Docker CLI 中的network管理选项和ls选项。当您执行 Docker 网络列表时,输出将类似于以下内容:

图 3.3 - 默认 Docker 网络列表

图 3.3 - 默认 Docker 网络列表

上面的列表来自基本的 Docker 安装,因此只有三个默认网络选项可用。

docker network ls命令不包含太多信息;它旨在为您提供可用网络的快速摘要。要深入了解网络的详细信息,您可以要求 Docker 检查网络,这将提供所有网络设置。

检索网络的详细信息

一旦您创建了多个用户定义的网络,您可能会开始迷失每个网络的设置,或者每个网络上运行的容器。您可以使用docker network inspect 选项在主机上查看每个网络的详细信息。inspect命令的输出包含有关网络的详细信息,包括子网、网关、驱动类型以及所有连接的容器:

图 3.4 - 网络检查输出

图 3.4 - 网络检查输出

前面的截图显示网络是一个桥接网络,但我们已经从输出的"Driver": "bridge"部分知道了这一点。在输出的容器部分,您可以看到桥接网络有两个容器连接到它。第一个容器名为NGINX1,IP 地址为192.168.10.3,而第二个容器名为frontend,IP 地址为192.168.10.2inspect命令还显示了每个容器的分配 MAC 地址,以及(如果启用)IPV6 地址。

现在您知道如何跟踪主机上的网络了,让我们深入了解用户定义的桥接网络。

创建用户定义的桥接网络

当您创建一个新的用户定义网络时,您可以提供大多数标准 IP 选项,这些选项在 Docker 之外创建新网络时会使用。您可以为子网、IP 范围和网关设置选项。请记住,您在这里定义的网络仅在您的 Docker 主机内部,并且您分配的 IP 地址将无法在主机外部寻址。要了解更多高级选项,请访问docs.docker.com/engine/reference/commandline/network_create/#specify-advanced-options上的高级 Docker 网络页面。

要创建一个用户定义的网络,我们在 Docker CLI 中使用网络管理选项,以及创建选项。语法非常简单;您只需要为新网络提供所需的网络名称,Docker 将创建新网络。要创建一个名为 frontend 的新网络,我们只需要执行以下命令:

图 3.5 – 创建 Docker 网络的输出

图 3.5 – 创建 Docker 网络的输出

这将返回网络 ID。如果您再次列出网络,您将看到一个新的桥接网络可用:

图 3.6 – Docker 网络列表

图 3.6 – Docker 网络列表

由于我们除了网络名称之外没有指定任何选项,Docker 将为网络分配一个不重叠的 IP 范围。

如果您想要创建一个名为 backend 的第二个网络,该网络使用192.168.10.0/24子网,网关为192.168.10.1,您只需要在docker network create命令中添加--subnet--gateway

图 3.7 – 添加选项示例

图 3.7 – 添加选项示例

当您创建一个新网络,就像我们为后端网络示例所做的那样,Docker 会在主机上绑定一个新的 IP,该 IP 等于我们在创建命令中使用的网关地址。以下是在我们的主机上使用ip addr的输出:

图 3.8 – 在网络创建后添加主机 IP

图 3.8 – 主机 IP 在网络创建后添加

这将允许您的主机将网络流量路由到任何连接到交换机的容器。容器可以通过使用主机 IP 作为默认网关来访问主机外部的网络资源。

现在您已经创建了一个用户定义网络,让我们看看如何在运行镜像时将新网络分配给容器。

将容器连接到用户定义网络

您可以通过在docker run命令中添加--network选项来在启动容器时将容器连接到特定网络。要将新的 NGINX 容器连接到我们之前创建的 frontend 网络,我们只需要在启动容器时添加--network=frontend

图 3.9 - 在启动时连接网络

图 3.9 - 在启动时连接网络

上述命令将在名为 frontend 的用户定义网络上启动一个新的 NGINX 容器。

更改运行中容器的网络

我们提到使用用户定义网络而不是默认网络的一个优势是能够在不停止容器的情况下随时更改容器的网络。

要更改运行中容器的网络,您可以使用称为connectdisconnect的 Docker 网络选项,以及网络名称和容器名称。当您使用connect选项时,您向容器添加一个网络,而如果您使用disconnect选项,则从容器中删除一个网络,而无需停止容器。

在我们的 NGINX 示例中,我们分配了 frontend 网络,但是如果我们想要将其更改为 backend 网络,我们只需要添加一个网络并删除另一个。第一步是使用docker network connect命令连接 backend 网络:

图 3.10 - 连接网络

图 3.10 - 连接网络

这将把名为 backend 的网络连接到我们名为 frontend 的容器。

第二步是使用disconnect选项删除 frontend 网络:

图 3.11 - 断开网络

图 3.11 - 断开网络

您可能想知道是否可以将容器连接到多个网络,答案是可以的。如果您需要容器访问需要巨幅帧的网络,但它也需要访问标准网络连接,您可以将容器连接到两个不同的用户定义网络。详细说明这种情况超出了本书的范围,并且可能会变成一个复杂的话题,因为它可能需要在容器中创建自定义路由,但知道它是可以做到的,并且有特定的用例。

删除网络

如果您不再需要用户定义的网络,可以使用docker network rm 命令从主机中删除网络。要删除之前创建的前端网络,我们将使用带有网络名称frontenddocker network rm命令:

图 3.12 - 删除网络

图 3.12 - 删除网络

如果您有多个要删除的网络,那么可以使用prune命令,它将删除主机上所有未使用的网络。这类似于清理未使用的卷。您只需要运行docker network prune来删除未使用的网络:

图 3.13 - 清理网络

图 3.13 - 清理网络

一旦您确认要继续,Docker 将列出已删除的网络。在我们的示例中,它删除了一个名为network4的网络。

就像我们之前讨论的volume prune命令一样,这是一个单向过程。当您选择是来清理网络时,就没有撤销操作,所以在确认操作之前一定要100%确定您要删除网络。

在没有网络的情况下运行容器

请记住,如果您启动一个没有网络选项的容器,它将连接到默认的桥接网络。您可能需要测试一个可能包含可疑内容的容器,并且让它连接到网络可能会对整个网络造成风险。

这与现实世界没有什么不同。例如,如果你有一台机器看起来像是恶意行为,你会关闭网络端口或者拔掉网络电缆。在容器世界中,我们可能在主机上运行多个容器,而且可能不能简单地拔掉电缆而不影响许多工作负载。我们有两个选项可用,取决于容器是否已经在运行,如果是的话,你不想丢失运行状态。

如果容器已经停止,你可以在启动容器时使用--network=none选项来简单地启动不附加网络的容器。这与你在启动具有用户定义网络的容器时遵循的相同过程,只是我们将网络名称指定为none

如果你怀疑容器中有恶意运行的东西,你可能无法停止容器。如果你需要查看运行中的进程、RAM 内容等,停止镜像会销毁任何正在运行的东西,你可能会丢失宝贵的信息。与其停止并重新启动容器,使用网络值为 none,你可以只是将运行中的容器与网络断开连接。这将限制容器影响本地镜像之外的任何东西的能力。

暴露容器服务

现在你知道了 Docker 中可用的网络选项,下一步是在启动容器时暴露任何想要接受流量的端口。在暴露端口时有两个选项。第一个是使用主机网络选项,而第二个是在容器启动时在桥接网络上暴露端口。使用主机网络选项更容易,因为你不需要任何手动端口映射,但随着运行容器数量的增加,这会使跟踪端口变得困难。我们建议只在计划运行单个容器时使用主机网络选项。

使用主机网络暴露端口

正如我们在本章前面提供的表格中所述,"使用主机网络驱动程序的容器将不会获得 IP 地址,也不需要暴露端口来允许传入流量。"由于容器将直接在主机网络上启动,完全绕过 Docker 的网络堆栈,你不需要为容器暴露任何端口。

例如,要在主机上使用主机网络运行 NGINX 的 Web 服务器,您将运行docker run命令,同时提供host作为网络类型:

图 3.14 - 使用主机网络选项

图 3.14 - 使用主机网络选项

我们想要验证容器是否正在运行,并查看可能暴露的任何端口,因此我们将运行docker ps来列出正在运行的容器。容器显示为正在运行,但在PORTS下,我们没有看到任何列出的内容:

图 3.15 - 从主机连接的示例端口视图

图 3.15 - 从主机连接的示例端口视图

由于容器是使用主机网络选项启动的,我们不需要暴露任何端口。我们一直在使用的 NGINX 容器在端口8080上运行,并且由于它绕过了 Docker 网络,证明它正在使用端口8080的唯一方法是在主机系统上运行netstat

图 3.16 - 主机 netstat 显示端口 8080 正在使用

图 3.16 - 主机 netstat 显示端口 8080 正在使用

看起来容器正在运行并在端口 8080 上监听。在网络上的另一台机器上,我们可以打开浏览器并输入 Docker 主机机器的 IP 地址并使用端口 8080:

图 3.17 - NGINX 在主机网络上运行

图 3.17 - NGINX 在主机网络上运行

在这里,我们收到了 NGINX 的欢迎页面,证明容器正在运行。

由于docker ps命令未列出正在使用的端口,您可以看到在使用主机网络选项时,如果主机运行多个容器,您可能会开始丢失已分配的端口。这就是为什么我们建议限制使用主机网络选项,除非您正在运行单个容器或需要主机网络的容器。

重要提示

正如我们所述,主机上的套接字必须是唯一的。在我们的示例中,我们一直在使用单个 NIC 和单个 IP 地址,这意味着端口只能在主机上使用一次。由于套接字是 IP 地址和端口的组合,您可以向单个 NIC 添加第二个 IP 地址,或者添加一个带有新 IP 地址的附加 NIC,以创建一个新的唯一套接字。这将允许您将已分配端口的新绑定到另一个进程,因为套接字将是唯一的。

现在,让我们停止 NGINX 容器,并再次查看netstat,以显示该端口不再被使用。我们将使用以下命令:

sudo docker stop nginx

sudo docker rm nginx

然后,我们将使用 netstat 来显示活动端口:

图 3.18 – 主机 netstat 显示端口 8080 未被使用

图 3.18 – 主机 netstat 显示端口 8080 未被使用

如你所见,主机上唯一打开的端口是 SSH;端口8080已经关闭,因为容器已经停止。

使用桥接网络暴露端口

使用主机网络选项似乎使暴露端口变得容易,因为你实际上不需要做任何事情来暴露它们。这一开始可能看起来很吸引人,但如果你在主机上运行多个容器,并且有多个配置为在相同的端口上运行,比如我们的 NGINX 容器的端口 8080,由于端口冲突,你将受限于一个容器。

当一个选项看起来更容易时,通常意味着它的健壮性或可配置性较差,这就是为什么它一开始看起来更容易。这就是在使用主机网络与桥接网络暴露端口时的情况,但一旦你了解了我们如何以及为什么使用桥接网络暴露端口,你就会明白为什么它提供了更好的全面解决方案。

当你想要在使用桥接网络的容器上暴露端口时,你只需要在启动容器时指定要打开的端口。如果你有多个桥接网络,你还需要提供网络名称,但在我们的示例中,我们假设你正在使用内置的桥接网络。

在 Docker 中暴露端口时,你需要使用incoming port:destination port的语法来指定传入的(Docker 主机)端口和目的地(容器)端口。通常,这些数字会保持一致以保持简单,但也会有一些情况需要使用不同的端口。

如果你只为目的地提供了一个端口,那么将假定为 TCP 连接。你可能需要为容器暴露 UDP 端口,为了将端口暴露为 UDP 端口,只需在目的地端口分配中添加/udp。因此,你的语法将变成incoming port:destination port/udp

使用我们的示例 Web 服务器,我们将使用默认的桥接网络在端口8080上启动容器,使用docker run命令和-p选项来指定端口;也就是说,docker run -p 8080:8080 -p 8443:8443

图 3.19 – 暴露端口 8080

图 3.19 - 暴露端口 8080

为了验证容器正在运行,我们将使用 docker ps 命令,并注意 PORTS 列,该列显示了容器的映射端口:

图 3.20 - docker ps 输出显示分配的端口

](image/Fig_3.19_B15514.jpg)

图 3.20 - docker ps 输出显示分配的端口

在这里,我们可以看到容器正在运行,并且我们已将传入主机端口 8080 和 8443 映射到容器端口 8080 和 8443。对 Docker 主机上 8080 和 8443 端口的任何传入请求,无论在任何接口(0.0.0.0)上,都将被转发到容器。

就像我们使用主机网络时一样,我们可以看到主机正在使用 netstat 监听 8080 和 8443 端口:

图 3.21 - 主机 netstat 显示端口 8080

图 3.21 - 主机 netstat 显示端口 8080

您的项目现在需要为另一个开发站点部署第二个 Web 服务器,并且您希望部署另一个 NGINX 容器。该镜像使用端口 8080 和 8443,但这两个端口都被我们的第一个 NGINX 容器使用。在主机上尝试使用 8080 和 8443 运行另一个容器将导致端口已分配错误:

图 3.22 - 端口冲突示例

](image/Fig_3.21_B15514.jpg)

图 3.22 - 端口冲突示例

一个解决方案是创建另一个容器,监听不同的端口,比如 8081 和 8444,但这开始变得难以维护。相反,记住当您暴露一个端口时,您指定了传入和目标端口。我们想使用相同的 NGINX 镜像,所以我们不能更改容器端口,但我们可以更改主机上的传入端口。当我们启动第二个容器时,我们将每个端口增加一,以避免与现有的 8080 和 8443 规则冲突,但我们仍将将端口转发到新容器的 8080 和 8433。这可能听起来很混乱,所以最好看一个示例 docker run 命令;即 docker run -p 8081:8080 -p 80444:8443 –name nginx2 bitnami/nginx:latest:

图 3.23 - 分配端口 8081 和 8443 的示例

图 3.23 - 分配端口 8081 和 8443 的示例

由于 Docker 返回了新的容器 ID,我们可以看到,通过逐一增加传入端口,我们不再有任何冲突的端口。

列出当前运行的容器将显示两个 NGINX 容器和端口映射:

图 3.24 – 显示运行中的两个 NGINX 服务器的 docker ps

图 3.24 – 显示运行中的两个 NGINX 服务器

从网络上的另一台机器浏览到主机的 8081 端口将显示默认的 NGINX 欢迎页面:

图 3.25 – 浏览示例到端口 8081 的 NGINX

图 3.25 – 浏览示例到端口 8081 的 NGINX

查看 netstat,我们可以看到主机上的所有四个端口都在监听:

图 3.26 – 主机 netstat 显示分配的四个 NGINX 端口

图 3.26 – 主机 netstat 显示分配的四个 NGINX 端口

如果您需要运行另一个 NGINX 容器,您可以使用另一个主机端口,可能是80828445。主要的要点是主机上的传入端口必须是唯一的,以避免端口冲突。容器的端口可以相同,因为每个容器都在自己的命名空间中运行,并且每个容器都有自己的资源和端口。

总结

在本章中,您了解了 IP 套接字以及主机如何使用端口与服务器建立连接。您了解到套接字是 IP 地址和端口的组合,必须在主机上是唯一的。然后,我们介绍了 Docker 提供的每种网络类型及其各自的用例,以及何时使用默认桥接网络、自定义桥接网络、主机网络或无网络。最后,您学会了如何将容器暴露给外部用户。

在下一章中,我们将开始探索 Kubernetes,通过探索 Kubernetes 集群,查看其控制平面,了解kublet和 API 之间的区别,学习工作节点的功能,并审查超过 25 个 Kubernetes 对象。

问题

  1. 由于 Docker 创建了默认的桥接网络,因此没有理由创建自定义桥接。

A. True

B. False

  1. 以下哪项是套接字的示例?

A. http:192.168.100.10

B. 192.168.100.10

C. 192.168.100.10:80

D. https://192.168.100.10

  1. 如何在已将其主机端口(8080)绑定到另一个容器的主机上启动一个名为nginx-web的端口为8080的 Web 服务器的容器?

A. docker run -d nginx-web bitnami/nginx

B. docker run -p 8080:8080 -d nginx-web bitnami/nginx –force

C. docker run -p 8081:8080 -d nginx-web bitnami/nginx

D. 由于主机上绑定了端口8080,你无法在端口8080上运行容器

  1. 你怀疑一个镜像可能包含恶意软件。你需要安全地运行这个镜像来查看它的内容。哪个 Docker 命令可以减轻任何网络影响?

A. docker run -isolate -it badimage bash

B. docker run -p 0:0 -it badimage bash

C. docker run -it badimage bash

D. docker run --network=none -it badimage bash

  1. 一旦一个容器连接到自定义桥接网络,你就无法更改连接的网络。

A. True

B. False

  1. 你可以在容器上暴露的最高 IP 端口号是多少?

A. 没有限制

B. 65535

C. 65530

D. 65532

E. 65435

第二部分:创建 Kubernetes 开发集群,理解对象和暴露服务

在这第二部分,我们将转向 Kubernetes 集群和对象。本节的第一章将解释如何使用一个流行的工具来创建 Kubernetes 集群,称为 KinD。我们将解释如何创建不同的集群,从单节点集群到使用 HAProxy 作为工作节点负载均衡器的多节点集群。有了一个工作的 Kubernetes 集群,我们将进入下一章,该章将回顾 Kubernetes 基础设施组件和最常用的 Kubernetes 对象。我们通过解释一个常常被误解的主题来结束本节,即在集群中暴露服务。我们将研究 Kubernetes 服务类型,讨论如何使用第 7 层和第 4 层负载均衡器,并看看如何使用 External-DNS 为服务注册动态名称。

本书的这一部分包括以下章节:

  • [第四章](B15514_04_Final_ASB_ePub.xhtml#_idTextAnchor083),使用 KinD 部署 Kubernetes 集群

  • [第五章](B15514_05_Final_ASB_ePub.xhtml#_idTextAnchor150),Kubernetes 训练营

  • [第六章](B15514_06_Final_ASB_ePub.xhtml#_idTextAnchor174),服务、负载均衡器和 External-DNS

第四章:使用 KinD 部署 Kubernetes

学习 Kubernetes 最大的障碍之一是拥有足够的资源来创建用于测试或开发的集群。像大多数 IT 专业人员一样,我们喜欢在笔记本电脑上拥有一个 Kubernetes 集群,用于演示和测试产品。

通常,您可能需要运行多个集群进行复杂的演示,比如多集群服务网格或测试kubefed2。这些场景将需要多台服务器来创建必要的集群,这又需要大量的 RAM 和一个虚拟化程序。

在多集群场景下进行全面测试,您需要为每个集群创建六个节点。如果您使用虚拟机创建集群,您需要有足够的资源来运行 6 个虚拟机。每台机器都会有一些开销,包括磁盘空间、内存和 CPU 利用率。

但是,如果您可以只使用容器来创建一个集群呢?使用容器而不是完整的虚拟机将使您能够由于降低的系统要求而运行额外的节点,用单个命令在几分钟内创建和删除集群,脚本化集群创建,并允许您在单个主机上运行多个集群。

使用容器来运行 Kubernetes 集群为您提供了一个环境,对于大多数人来说,使用虚拟机或物理硬件部署将会很困难,因为资源受限。为了解释如何在本地仅使用容器运行集群,我们将使用 KinD 在您的 Docker 主机上创建一个 Kubernetes 集群。我们将部署一个多节点集群,您将在未来的章节中用来测试和部署诸如 Ingress 控制器、认证、RBAC、安全策略等组件。

在本章中,我们将涵盖以下主题:

  • 介绍 Kubernetes 组件和对象

  • 使用开发集群

  • 安装 KinD

  • 创建一个 KinD 集群

  • 审查您的 KinD 集群

  • 为 Ingress 添加自定义负载均衡器

让我们开始吧!

技术要求

本章具有以下技术要求:

  • 使用第一章中的步骤安装的 Docker 主机,Docker 和容器基础知识

  • 本书 GitHub 存储库中的安装脚本

您可以通过访问本书的 GitHub 存储库来获取本章的代码:github.com/PacktPublishing/Kubernetes-and-Docker-The-Complete-Guide

注意

我们认为有必要指出,本章将涉及多个 Kubernetes 对象,其中一些没有太多的上下文。[第五章](B15514_05_Final_ASB_ePub.xhtml#_idTextAnchor150),Kubernetes Bootcamp详细介绍了 Kubernetes 对象,其中许多带有您可以使用的命令来理解它们,因此我们认为在阅读本章时使用集群将会很有用。

本章涵盖的大多数基本 Kubernetes 主题将在以后的章节中讨论,因此如果在阅读本章后某些主题有点模糊,不要担心!它们将在以后的章节中详细讨论。

介绍 Kubernetes 组件和对象

由于本章将涉及常见的 Kubernetes 对象和组件,我们想提供一个术语表,您将在其中看到每个术语的简要定义,以提供上下文。

在[第五章](B15514_05_Final_ASB_ePub.xhtml#_idTextAnchor150),Kubernetes Bootcamp中,我们将介绍 Kubernetes 的组件和集群中包含的基本对象集。我们还将讨论如何使用 kubectl 可执行文件与集群进行交互:

表 4.1 – Kubernetes 组件和对象

表 4.1 – Kubernetes 组件和对象

虽然这些只是 Kubernetes 集群中可用的一些对象,但它们是我们将在本章中提到的主要对象。了解每个对象是什么,并具有它们功能的基本知识将有助于您理解本章并部署 KinD 集群。

与集群交互

为了测试我们的 KinD 安装,我们将使用 kubectl 可执行文件与集群进行交互。我们将在[第五章](B15514_05_Final_ASB_ePub.xhtml#_idTextAnchor150),Kubernetes Bootcamp中介绍 kubectl,但由于我们将在本章中使用一些命令,我们想提供我们将在表格中使用的命令以及选项提供的解释:

表 4.2 – 基本 kubectl 命令

表 4.2 – 基本 kubectl 命令

在这一章中,您将使用这些基本命令部署我们在本书中将使用的集群的部分。

接下来,我们将介绍开发集群的概念,然后重点介绍用于创建开发集群的最流行工具之一:KinD。

使用开发集群

多年来,已经创建了各种工具来安装开发 Kubernetes 集群,使管理员和开发人员能够在本地系统上进行测试。这些工具中的许多工作都适用于基本的 Kubernetes 测试,但它们经常存在限制,使它们不太适合快速、高级的场景。

一些最常见的解决方案如下:

  • Docker Desktop

  • minikube

  • kubeadm

每种解决方案都有其优势、局限性和使用情况。有些解决方案限制您只能在单个节点上运行控制平面和工作节点。其他解决方案提供多节点支持,但需要额外的资源来创建多个虚拟机。根据您的开发或测试需求,这些解决方案可能无法完全满足您的需求。

似乎每隔几周就会出现一种新的解决方案,用于创建开发集群的最新选项之一是来自Kubernetes in DockerKinD)Kubernetes SIG 的项目。

使用单个主机,KinD 允许您创建多个集群,每个集群可以有多个控制平面和工作节点。运行多个节点的能力允许进行高级测试,而使用其他解决方案可能需要更多的资源。KinD 在社区中受到了很好的评价,并且在github.com/kubernetes-sigs/kind上有一个活跃的 Git 社区,以及一个 Slack 频道(#kind)。

注意

不要将 KinD 用作生产集群或将 KinD 集群暴露在互联网上。虽然 KinD 集群提供了大部分您在生产集群中想要的功能,但它是为生产环境而设计的。

为什么我们选择 KinD 来写这本书?

当我们开始写这本书时,我们希望包括理论和实践经验。KinD 允许我们提供脚本来快速创建和关闭集群,虽然其他解决方案也可以做类似的事情,但 KinD 可以在几分钟内创建一个新的多节点集群。我们希望将控制平面和工作节点分开,以提供一个更“真实”的集群。为了限制硬件要求并使 Ingress 更容易配置,我们将在本书的练习中只创建一个两节点集群。

多节点集群可以在几分钟内创建,一旦测试完成,集群可以在几秒内被拆除。快速创建和销毁集群的能力使 KinD 成为我们练习的理想平台。KinD 的要求很简单:您只需要运行的 Docker 守护程序来创建集群。这意味着它与大多数操作系统兼容,包括以下操作系统:

  • Linux

  • 运行 Docker 桌面的 macOS

  • 运行 Docker 桌面的 Windows

  • 运行 WSL2 的 Windows

重要提示

在撰写本文时,KinD 不支持 Chrome OS。

虽然 KinD 支持大多数操作系统,但我们选择了 Ubuntu 18.04 作为我们的主机系统。本书中的一些练习需要文件放在特定的目录中,选择单个 Linux 版本可以确保练习按设计工作。如果您在家里没有 Ubuntu 服务器,可以在 GCP 等云提供商中创建虚拟机。Google 提供 300 美元的信用额度,足够您运行单个 Ubuntu 服务器数周。您可以在cloud.google.com/free/查看 GCP 的免费选项。

现在,让我们解释一下 KinD 的工作原理以及基本的 KinD Kubernetes 集群是什么样子的。

使用基本的 KinD Kubernetes 集群

从高层次来看,您可以将 KinD 集群视为由一个单个Docker 容器组成,该容器运行控制平面节点和工作节点以创建 Kubernetes 集群。为了使部署简单而稳健,KinD 将每个 Kubernetes 对象捆绑到一个单一的镜像中,称为节点镜像。此节点镜像包含创建单节点集群或多节点集群所需的所有 Kubernetes 组件。

一旦集群运行起来,您可以使用 Docker 执行进入控制平面节点容器并查看进程列表。在进程列表中,您将看到运行控制平面节点的标准 Kubernetes 组件:

图 4.1 - 显示控制平面组件的主机进程列表

图 4.1 - 显示控制平面组件的主机进程列表

如果您要执行到工作节点以检查组件,您将看到所有标准的工作节点组件:

图 4.2 - 显示工作组件的主机进程列表

(图像/Fig_4.2_B15514.jpg)

图 4.2 - 显示工作组件的主机进程列表

我们将在第五章《Kubernetes Bootcamp》中涵盖标准的 Kubernetes 组件,包括 kube-apiserver,kubelets,kube-proxy,kube-scheduler 和 kube-controller-manager。

除了标准的 Kubernetes 组件,KinD 节点都有一个不是大多数标准安装的组件:Kindnet。当您安装基本的 KinD 集群时,Kindnet 是包含的默认 CNI。虽然 Kindnet 是默认的 CNI,但您可以选择禁用它并使用其他选择,比如 Calico。

现在您已经看到了每个节点和 Kubernetes 组件,让我们来看看基本 KinD 集群包含的内容。要显示完整的集群和所有正在运行的组件,我们可以运行 kubectl get pods --all-namespaces 命令。这将列出集群的所有运行组件,包括我们将在第五章《Kubernetes Bootcamp》中讨论的基本组件。除了基本集群组件之外,您可能会注意到在一个名为 local-path-storage 的命名空间中有一个正在运行的 pod,以及一个名为 local-path-provisioner 的 pod。这个 pod 正在运行 KinD 包含的附加组件之一,为集群提供自动配置 PersistentVolumeClaims 的能力:

图 4.3 - kubectl get pods 显示 local-path-provisioner

图 4.3 - kubectl get pods 显示 local-path-provisioner

大多数开发集群提供类似的常见功能,人们需要在 Kubernetes 上测试部署。它们都提供一个 Kubernetes 控制平面和工作节点,大多数包括用于网络的默认 CNI。很少有提供超出这些基本功能的功能,随着 Kubernetes 工作负载的成熟,您可能会发现需要额外的插件,比如 local-path-provisioner。我们将在本书的一些练习中大量利用这个组件,因为如果没有它,我们将更难创建一些流程。

为什么您应该关心开发集群中的持久卷?大多数运行 Kubernetes 的生产集群将为开发人员提供持久存储。通常,存储将由基于块存储、S3 或 NFS 的存储系统支持。除了 NFS,大多数家庭实验室很少有资源来运行功能齐全的存储系统。本地路径供应程序通过为您的 KinD 集群提供昂贵存储解决方案提供的所有功能,消除了用户的这一限制。

在《第五章 Kubernetes Bootcamp》中,我们将讨论一些 Kubernetes 存储的 API 对象。我们将讨论 CSIdrivers、CSInodes 和 StorageClass 对象。这些对象被集群用来提供对后端存储系统的访问。一旦安装和配置完成,pod 将使用 PersistentVolumes 和 PersistentVolumeClaims 对象来消耗存储。存储对象很重要,但当它们首次发布时,大多数人很难测试,因为它们没有包含在大多数 Kubernetes 开发产品中。

KinD 意识到了这个限制,并选择捆绑来自 Rancher 的一个名为本地路径供应程序的项目,该项目基于 Kubernetes 1.10 中引入的 Kubernetes 本地持久卷。

您可能想知道为什么有人需要一个附加组件,因为 Kubernetes 本地主机持久卷已经有了原生支持。虽然已经为本地持久存储添加了支持,但 Kubernetes 还没有添加自动供应的能力。CNCF 确实提供了一个自动供应程序,但它必须作为一个单独的 Kubernetes 组件安装和配置。KinD 使自动供应变得容易,因为供应程序包含在所有基本安装中。

Rancher 的项目为 KinD 提供了以下内容:

  • 当创建 PVC 请求时自动创建 PersistentVolumes

  • 一个名为 standard 的默认 StorageClass

当自动供应程序看到一个 PersistentVolumeClaim 请求命中 API 服务器时,将创建一个 PersistentVolume,并将 pod 的 PVC 绑定到新创建的 PVC 上。

本地路径供应程序为 KinD 集群增加了一个功能,极大地扩展了您可以运行的潜在测试场景。如果没有自动供应持久磁盘的能力,测试许多需要持久磁盘的预构建部署将是一项挑战。

借助 Rancher 的帮助,KinD 为您提供了一个解决方案,以便您可以尝试动态卷、存储类和其他存储测试,否则在数据中心之外是不可能运行的。我们将在多个章节中使用提供程序为不同的部署提供卷。我们将指出这些以强调使用自动配置的优势。

理解节点镜像

节点镜像是为 KinD 提供魔力以在 Docker 容器内运行 Kubernetes 的关键。这是一个令人印象深刻的成就,因为 Docker 依赖于运行 systemd 的系统和大多数容器镜像中不包括的其他组件。

KinD 从一个基础镜像开始,这是团队开发的一个包含运行 Docker、Kubernetes 和 systemd 所需的一切的镜像。由于基础镜像是基于 Ubuntu 镜像的,团队移除了不需要的服务,并为 Docker 配置了 systemd。最后,使用基础镜像创建节点镜像。

提示

如果您想了解基础镜像是如何创建的细节,您可以查看 KinD 团队在 GitHub 仓库中的 Dockerfile,网址为 github.com/kubernetes-sigs/kind/blob/controlplane/images/base/Dockerfile

KinD 和 Docker 网络

由于 KinD 使用 Docker 作为容器引擎来运行集群节点,所有集群都受到与标准 Docker 容器相同的网络约束限制。在《第三章》《理解 Docker 网络》中,我们对 Docker 网络和 Docker 默认网络堆栈的潜在限制进行了复习。这些限制不会限制从本地主机测试您的 KinD Kubernetes 集群,但当您想要从网络上的其他计算机测试容器时,可能会导致问题。

除了 Docker 网络考虑因素,我们还必须考虑 Kubernetes 容器网络接口(CNI)。在官方上,KinD 团队将网络选项限制为两种 CNI:Kindnet 和 Calico。Kindnet 是他们唯一支持的 CNI,但您可以选择禁用默认的 Kindnet 安装,这将创建一个没有安装 CNI 的集群。在集群部署后,您可以部署 Calico 等 CNI 清单。

许多用于小型开发集群和企业集群的 Kubernetes 安装都使用 Tigera 的 Calico 作为 CNI,因此,我们选择在本书的练习中使用 Calico 作为我们的 CNI。

跟踪套娃

运行诸如 KinD 之类的解决方案可能会令人困惑,因为它是一个容器中的容器部署。我们将其比作俄罗斯套娃,一个娃娃套进另一个娃娃,然后再套进另一个,依此类推。当你开始使用 KinD 来搭建自己的集群时,你可能会迷失在主机、Docker 和 Kubernetes 节点之间的通信路径中。为了保持理智,你应该对每个组件的运行位置有一个清晰的理解,以及如何与每个组件进行交互。

下图显示了必须运行的三个层,以形成一个 KinD 集群。重要的是要注意,每个层只能与直接位于其上方的层进行交互。这意味着第 3 层中的 KinD 容器只能看到第 2 层中运行的 Docker 镜像,而 Docker 镜像只能看到第 1 层中运行的 Linux 主机。如果你想要直接从主机与运行在你的 KinD 集群中的容器进行通信,你需要通过 Docker 层,然后到第 3 层的 Kubernetes 容器。

了解这一点很重要,这样你才能有效地将 KinD 作为测试环境使用:

图 4.4 - 主机无法直接与 KinD 通信

图 4.4 - 主机无法直接与 KinD 通信

举个例子,假设你想要将 web 服务器部署到你的 Kubernetes 集群中。你在 KinD 集群中部署了一个 Ingress 控制器,并且你想要使用 Chrome 在你的 Docker 主机上或者网络上的另一台工作站上测试该网站。你尝试以端口 80 定位主机,并在浏览器中收到了失败。为什么会失败呢?

运行 web 服务器的 pod 位于第 3 层,无法直接接收来自主机或网络上的机器的流量。为了从主机访问 web 服务器,你需要将流量从 Docker 层转发到 KinD 层。请记住,在第三章**,理解 Docker 网络中,我们解释了如何通过向容器添加监听端口来将容器暴露给网络。在我们的情况下,我们需要端口 80 和端口 443。当一个容器启动时带有一个端口,Docker 守护程序将把来自主机的传入流量转发到正在运行的 Docker 容器:

图 4.5 - 主机通过 Ingress 控制器与 KinD 通信

图 4.5 - 主机通过 Ingress 控制器与 KinD 通信

在 Docker 容器上暴露了端口 80 和 443 后,Docker 守护程序现在将接受端口 80 和 443 的传入请求,并且 NGINX Ingress 控制器将接收流量。这是因为我们在 Docker 层上两个地方都暴露了端口 80 和 443。我们在 Kubernetes 层上通过使用主机端口 80 和 443 运行我们的 NGINX 容器来暴露它。这个安装过程将在本章后面解释,但现在,您只需要了解基本流程。

在主机上,您对 Kubernetes 集群中具有 Ingress 规则的 Web 服务器发出请求:

  1. 请求查看了所请求的 IP 地址(在本例中为本地 IP 地址)。

  2. 运行我们的 Kubernetes 节点的 Docker 容器正在监听端口 80 和 443 的 IP 地址,因此请求被接受并发送到正在运行的容器。

  3. 您的 Kubernetes 集群中的 NGINX pod 已配置为使用主机端口 80 和 443,因此流量被转发到该 pod。

  4. 用户通过 NGINX Ingress 控制器从 Web 服务器接收所请求的网页。

这有点令人困惑,但你使用 KinD 并与其交互的次数越多,这就变得越容易。

要满足开发需求,您需要了解 KinD 的工作原理。到目前为止,您已经了解了节点镜像以及如何使用该镜像创建集群。您还了解了 KinD 网络流量是如何在 Docker 主机和运行集群的容器之间流动的。有了这些基础知识,我们将继续使用 KinD 创建一个 Kubernetes 集群。

安装 KinD

本章的文件位于 KinD 目录中。您可以使用提供的文件,也可以根据本章的内容创建自己的文件。我们将在本节中解释安装过程的每个步骤。

注意

在撰写本文时,KinD 的当前版本为 0.8.1。版本 0.8.0 引入了一个新功能;即在重启和 Docker 重新启动之间维护集群状态。

安装 KinD - 先决条件

在创建集群之前,KinD 需要满足一些先决条件。在本节中,我们将详细介绍每个要求以及如何安装每个组件。

安装 Kubectl

由于 KinD 是一个单个可执行文件,它不会安装 kubectl。如果您尚未安装 kubectl 并且正在使用 Ubuntu 18.04 系统,可以通过运行 snap install 来安装它:

sudo snap install kubectl --classic

安装 Go

在我们创建 KinD 集群之前,您需要在主机上安装 Go。如果您已经安装并且正常工作,可以跳过此步骤。安装 Go 需要您下载 Go 存档,提取可执行文件,并设置项目路径。以下命令可用于在您的机器上安装 Go。

可以通过运行 /chapter4/install-go.sh 从本书存储库执行安装 Go 的脚本:

wget https://dl.google.com/go/go1.13.3.linux-amd64.tar.gz

tar -xzf go1.13.3.linux-amd64.tar.gz

sudo mv go /usr/local

mkdir -p $HOME/Projects/Project1

cat << 'EOF' >> ~/.bash_profile

export -p GOROOT=/usr/local/go

export -p GOPATH=$HOME/Projects/Project1

export -p PATH=\(GOPATH/bin:\)GOROOT/bin:$PATH

EOF

source ~/.bash_profile

前面列表中的命令将执行以下操作:

  • 将 Go 下载到您的主机,解压缩存档,并将文件移动到 /usr/local

  • 在您的主目录中创建一个名为 Projects/Project1 的 Go 项目文件夹。

  • 将 Go 环境变量添加到 .bash_profile,这些变量是执行 Go 应用程序所需的。

现在您已经具备了先决条件,我们可以继续安装 KinD。

安装 KinD 二进制文件

安装 KinD 是一个简单的过程;可以通过一个命令完成。您可以通过运行本书存储库中包含的脚本来安装 KinD,该脚本位于 /chapter4/install-kind.sh。或者,您可以执行以下命令:

GO111MODULE="on" go get sigs.k8s.io/kind@v0.7.0

安装完成后,您可以通过在提示符中键入 kind version 来验证 KinD 是否已正确安装:

kind version

这将返回已安装的版本:

kind v0.7.0 go1.13.3 linux/amd64

KinD 可执行文件提供了您需要维护集群生命周期的每个选项。当然,KinD 可执行文件可以创建和删除集群,但它还提供了以下功能:

  • 创建自定义构建基础和节点映像的能力

  • 可以导出 kubeconfig 或日志文件

  • 可以检索集群、节点或 kubeconfig 文件

  • 可以将映像加载到节点中

现在您已经安装了 KinD 实用程序,您几乎可以准备好创建您的 KinD 集群了。在执行一些create cluster命令之前,我们将解释 KinD 提供的一些创建选项。

创建一个 KinD 集群

现在您已经满足了所有的要求,您可以使用 KinD 可执行文件创建您的第一个集群。KinD 实用程序可以创建单节点集群,也可以创建一个运行多个控制平面节点和多个工作节点的复杂集群。在本节中,我们将讨论 KinD 可执行文件的选项。在本章结束时,您将拥有一个运行的双节点集群 - 一个单一的控制平面节点和一个单一的工作节点。

重要提示

在本书的练习中,我们将安装一个多节点集群。简单的集群配置是一个示例,不应该用于我们的练习。

创建一个简单的集群

要创建一个简单的集群,在单个容器中运行控制平面和工作节点,您只需要使用create cluster选项执行 KinD 可执行文件。

让我们创建一个快速的单节点集群,看看 KinD 如何快速创建一个快速开发集群。在您的主机上,使用以下命令创建一个集群:

kind create cluster

这将快速创建一个集群,其中包含一个单一的 Docker 容器中的所有 Kubernetes 组件,使用kind作为集群名称。它还将为 Docker 容器分配一个kind-control-plane的名称。如果您想要分配一个集群名称,而不是默认名称,您需要在create cluster命令中添加--name 选项:

创建集群"kind" ...

确保节点镜像(kindest/node:v1.18.2)

准备节点

编写配置

启动控制平面

安装 CNI

安装 StorageClass

将 kubectl 上下文设置为"kind-kind"

现在您可以使用您的集群:

kubectl cluster-info --context kind-kind

create命令将创建集群并修改 kubectl config文件。KinD 将把新集群添加到当前的 kubectl config文件中,并将新集群设置为默认上下文。

我们可以通过使用 kubectl 实用程序列出节点来验证集群是否成功创建:

kubectl get nodes

这将返回正在运行的节点,对于基本集群来说,是单个节点:

名称               状态     角色     年龄     版本

kind-control-plane Ready    master   130m  v1.18.2

部署此单节点集群的主要目的是向您展示 KinD 可以多快地创建一个用于测试的集群。对于我们的练习,我们希望将控制平面和工作节点分开,以便我们可以使用下一节中的步骤删除此集群。

删除集群

测试完成后,您可以使用删除命令删除集群:

kind delete cluster –name

删除命令将快速删除集群,包括您的kubeconfig文件中的任何条目。

快速单节点集群对于许多用例都很有用,但您可能希望为各种测试场景创建多节点集群。创建更复杂的集群需要您创建一个配置文件。

创建集群配置文件

创建多节点集群(例如具有自定义选项的两节点集群)时,我们需要创建一个集群配置文件。配置文件是一个 YAML 文件,其格式应该看起来很熟悉。在此文件中设置值允许您自定义 KinD 集群,包括节点数、API 选项等。我们将用于创建本书集群的配置文件如下 - 它包含在本书的存储库中的/chapter4/cluster01-kind.yaml中:

种类:集群

apiVersion:kind.x-k8s.io/v1alpha4

网络:

apiServerAddress:"0.0.0.0"

disableDefaultCNI:true

kubeadmConfigPatches:

  • |

apiVersion:kubeadm.k8s.io/v1beta2

种类:集群配置

元数据:

名称:配置

网络:

服务子网:"10.96.0.1/12"

Pod 子网:"192.168.0.0/16"

节点:

  • 角色:控制平面

  • 角色:工作节点

额外端口映射:

  • containerPort:80

主机端口:80

  • containerPort:443

主机端口:443

额外挂载:

  • hostPath:/usr/src

容器路径:/usr/src

文件中每个自定义选项的详细信息在下表中提供:

表 4.3 - KinD 配置选项

表 4.3 - KinD 配置选项

如果您计划创建一个超出单节点集群的集群而不使用高级选项,您将需要创建一个配置文件。了解可用的选项将允许您创建具有高级组件(如 Ingress 控制器或多个节点)的 Kubernetes 集群,以测试部署的故障和恢复过程。

现在您知道如何创建一个简单的一体化容器来运行集群,以及如何使用配置文件创建多节点集群,让我们讨论一个更复杂的集群示例。

多节点集群配置

如果您只想要一个没有任何额外选项的多节点集群,您可以创建一个简单的配置文件,列出您在集群中想要的节点数量和类型。以下配置文件将创建一个包含三个控制平面节点和三个工作节点的集群:

种类:集群

apiVersion: kind.x-k8s.io/v1alpha4

节点:

  • 角色:控制平面

  • 角色:控制平面

  • 角色:控制平面

  • 角色:工作节点

  • 角色:工作节点

  • 角色:工作节点

使用多个控制平面服务器会引入额外的复杂性,因为我们在配置文件中只能针对单个主机或 IP。为了使这个配置可用,我们需要在集群前部署一个负载均衡器。

KinD 已经考虑到了这一点,如果您部署多个控制平面节点,安装将创建一个额外的运行 HAProxy 负载均衡器的容器。如果我们查看多节点配置的运行容器,我们将看到六个节点容器运行和一个 HAProxy 容器:

表 4.4 - KinD 配置选项

表 4.4 - KinD 配置选项

请记住,在第三章**,理解 Docker 网络中,我们解释了端口和套接字。由于我们只有一个主机,每个控制平面节点和 HAProxy 容器都在唯一的端口上运行。每个容器都需要暴露给主机,以便它们可以接收传入的请求。在这个例子中,需要注意的是分配给 HAProxy 的端口,因为那是集群的目标端口。如果您查看 Kubernetes 配置文件,您会看到它是针对127.0.0.1:32791,这是分配给 HAProxy 容器的端口。

当使用kubectl执行命令时,它直接发送到 HAProxy 服务器。使用 KinD 在集群创建期间创建的配置文件,HAProxy 容器知道如何在三个控制平面节点之间路由流量:

由 kind 生成

全局

日志 /dev/log local0

日志 /dev/log local1 注意

守护程序

默认值

日志 全局

模式 tcp

选项 dontlognull

TODO: 调整这些

连接超时 5000

客户端超时 50000

服务器超时 50000

前端 控制平面

绑定 *:6443

默认后端 kube-apiservers

后端 kube-apiservers

选项 httpchk GET /healthz

TODO: 我们应该进行验证(!)

服务器 config2-control-plane 172.17.0.8:6443 检查 检查-ssl 验证 无

server config2-control-plane2 172.17.0.6:6443 check check-ssl verify none

server config2-control-plane3 172.17.0.5:6443 check check-ssl verify none

如前面的配置文件所示,有一个名为kube-apiservers的后端部分,其中包含三个控制平面容器。每个条目都包含一个控制平面节点的 Docker IP 地址,端口分配为 6443,指向容器中运行的 API 服务器。当您请求127.0.0.1:32791时,该请求将命中 HAProxy 容器。使用 HAProxy 配置文件中的规则,请求将被路由到列表中的三个节点之一。

由于我们的集群现在由负载均衡器前端,我们有一个高可用的控制平面用于测试。

注意

包含的 HAProxy 镜像不可配置。它只用于处理控制平面和负载均衡 API 服务器。由于这个限制,如果您需要为工作节点使用负载均衡器,您将需要提供自己的负载均衡器。

一个使用案例是,如果您想要在多个工作节点上使用 Ingress 控制器。您需要在工作节点前面放置一个负载均衡器,以接受传入的 80 和 443 请求,并将流量转发到运行 NGINX 的每个节点。在本章末尾,我们提供了一个示例配置,其中包括用于将流量负载均衡到工作节点的自定义 HAProxy 配置。

自定义控制平面和 Kubelet 选项

您可能希望进一步测试 OIDC 集成或 Kubernetes 功能门。KinD 使用与 kubeadm 安装相同的配置。例如,如果您想要将集群与 OIDC 提供程序集成,可以将所需的选项添加到配置补丁部分:

kind: Cluster

apiVersion: kind.x-k8s.io/v1alpha4

kubeadmConfigPatches:

  • |

kind: ClusterConfiguration

metadata:

name: config

apiServer:

extraArgs:

oidc-issuer-url: "https://oidc.testdomain.com/auth/idp/k8sIdp"

oidc-client-id: "kubernetes"

oidc-username-claim: sub

oidc-client-id: kubernetes

oidc-ca-file: /etc/oidc/ca.crt

nodes:

  • role: control-plane

  • role: control-plane

  • role: control-plane

  • role: worker

  • role: worker

  • rol: worker

有关可用配置选项的列表,请查看 Kubernetes 网站上的使用 kubeadm 自定义控制平面配置 kubernetes.io/docs/setup/production-environment/tools/kubeadm/control-plane-flags/

现在你已经创建了集群文件,你可以创建你的 KinD 集群。

创建自定义的 KinD 集群

终于了!现在你已经熟悉了 KinD,我们可以继续并创建我们的集群了。

我们需要创建一个受控的、已知的环境,所以我们将为集群命名,并提供我们在上一节讨论过的配置文件。

确保你在chapter4目录下的克隆存储库中。

为了使用我们需要的选项创建一个 KinD 集群,我们需要使用以下选项运行 KinD 安装程序:

kind create cluster --name cluster01 --config c

luster01-kind.yaml 选项--name将集群名称设置为 cluster01,而--config告诉安装程序使用配置文件cluster01-kind.yaml

当你在主机上执行安装程序时,KinD 将开始安装并告诉你正在执行的每一步。整个集群创建过程应该不到 2 分钟:

图 4.6 – KinD 集群创建输出

图 4.6 – KinD 集群创建输出

部署的最后一步是创建或编辑现有的 Kubernetes 配置文件。无论哪种情况,安装程序都会创建一个名为kind-的新上下文,并将其设置为默认上下文。

虽然看起来集群安装过程已经完成了它的任务,但是集群还没有准备好。一些任务需要几分钟才能完全初始化,而且由于我们禁用了默认的 CNI 来使用 Calico,我们仍然需要部署 Calico 来提供集群网络。

安装 Calico

为了为集群中的 pod 提供网络,我们需要安装一个容器网络接口,或者 CNI。我们选择安装 Calico 作为我们的 CNI,而由于 KinD 只包括 Kindnet CNI,我们需要手动安装 Calico。

如果你在创建步骤之后暂停并查看集群,你会注意到一些 pod 处于等待状态:

coredns-6955765f44-86l77 0/1 Pending 0 10m

coredns-6955765f44-bznjl 0/1 Pending 0 10m

local-path-provisioner-7 0/1 Pending 0 11m 745554f7f-jgmxv

这里列出的 pods 需要一个可用的 CNI 才能启动。这将把 pods 置于等待网络的挂起状态。由于我们没有部署默认的 CNI,我们的集群没有网络支持。为了将这些 pods 从挂起状态变为运行状态,我们需要安装一个 CNI - 对于我们的集群来说,就是 Calico。

为了安装 Calico,我们将使用标准的 Calico 部署,只需要一个清单。要开始部署 Calico,请使用以下命令:

kubectl apply -f https://docs.projectcalico.org/v3.11/manifests/calico.yaml

这将从互联网上拉取清单并将其应用到集群。在部署过程中,您会看到创建了许多 Kubernetes 对象:

图 4.7 - Calico 安装输出

图 4.7 - Calico 安装输出

安装过程大约需要一分钟,您可以使用kubectl get pods -n kube-system来检查其状态。您会看到创建了三个 Calico pods。其中两个是calico-node pods,另一个是calico-kube-controller pod:

名称                    准备状态 重启次数 年龄

calico-kube-controllers 1/1 Running 0 64s -5b644bc49c-nm5wn

calico-node-4dqnv 1/1 Running 0 64s

calico-node-vwbpf 1/1 Running 0 64s

如果您再次检查kube-system命名空间中的两个 CoreDNS pods,您会注意到它们已经从之前安装 Calico 之前的挂起状态变为运行状态:

coredns-6955765f44-86l77 1/1 Running 0 18m

coredns-6955765f44-bznjl 1/1 Running 0 18m

现在集群安装了一个可用的 CNI,任何依赖网络的 pods 都将处于运行状态。

安装 Ingress 控制器

我们有一个专门的章节来解释 Ingress 的所有技术细节。由于我们正在部署一个集群,并且未来的章节需要 Ingress,我们需要部署一个 Ingress 控制器来展示一个完整的集群构建。所有这些细节将在第六章中更详细地解释,服务、负载均衡和外部 DNS

安装 NGINX Ingress 控制器只需要两个清单,我们将从互联网上拉取以使安装更容易。要安装控制器,请执行以下两行:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.28.0/deploy/static/mandatory.yaml

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.27.0/deploy/static/provider/baremetal/service-nodeport.yaml

部署将创建一些 Ingress 所需的 Kubernetes 对象,这些对象位于名为ingress-nginx的命名空间中:

图 4.8 – NGINX 安装输出

图 4.8 – NGINX 安装输出

我们还有一步,以便我们有一个完全运行的 Ingress 控制器:我们需要将端口 80 和 443 暴露给运行的 pod。这可以通过对部署进行修补来完成。在这里,我们已经包含了修补部署的修补程序:

kubectl patch deployments -n ingress-nginx nginx-ingress-controller -p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx-ingress-controller","ports":[{"containerPort":80,"hostPort":80},{"containerPort":443,"hostPort":443}]}]}}}}'

恭喜!您现在拥有一个完全运行的、运行 Calico 的双节点 Kubernetes 集群,带有一个 Ingress 控制器。

审查您的 KinD 集群

现在有一个 Kubernetes 集群,我们有能力直接查看 Kubernetes 对象。这将帮助您理解上一章,我们在其中涵盖了 Kubernetes 集群中包含的许多基本对象。特别是,我们将讨论包含在您的 KinD 集群中的存储对象。

KinD 存储对象

请记住,KinD 包括 Rancher 的自动配置程序,为集群提供自动持久磁盘管理。在第五章《Kubernetes Bootcamp》中,我们讨论了与存储相关的对象,现在我们有了一个配置了存储系统的集群,我们可以更详细地解释它们。

由于能够使用本地主机路径作为 PVC 的功能是 Kubernetes 的一部分,因此我们在 KinD 集群中将不会看到任何CSIdriver对象。自动配置程序不需要的一个对象是:它不需要CSIdriver

我们将讨论的 KinD 集群中的第一个对象是我们的CSInodes。在 bootcamp 中,我们提到创建此对象是为了将任何 CSI 对象与基本节点对象解耦。可以运行工作负载的任何节点都将具有CSInode对象。在我们的 KinD 集群中,两个节点都有CSInode对象。您可以通过执行kubectl get csinodes来验证这一点:

名称                      创建于

cluster01-control-plane   2020-03-27T15:18:19Z

cluster01-worker          2020-03-27T15:19:01Z

如果我们使用kubectl describe csinodes 来描述其中一个节点,您将看到对象的详细信息:

图 4.9 - CSInode 描述

图 4.9 - CSInode 描述

要指出的主要事项是输出的Spec部分。这列出了可能安装的任何驱动程序的详细信息,以支持后端存储系统。由于我们没有后端存储系统,我们在集群上不需要额外的驱动程序。

为了展示一个节点会列出什么样的示例,这里是一个安装了两个驱动程序,支持两种不同供应商存储解决方案的集群的输出:

图 4.10 - 多驱动器示例

图 4.10 - 多驱动器示例

如果您查看这个节点的spec.drivers部分,您将看到两个不同的名称部分。第一个显示我们安装了一个驱动程序来支持 NetApp SolidFire,而第二个是支持 Reduxio 的存储解决方案的驱动程序。

存储驱动程序

正如我们已经提到的,您的 KinD 集群没有安装任何额外的存储驱动程序。如果您执行kubectl get csidrivers,API 将不会列出任何资源。

KinD 存储类

要连接到任何集群提供的存储,集群需要一个StorageClass对象。Rancher 的提供者创建了一个名为 standard 的默认存储类。它还将该类设置为默认的StorageClass,因此您不需要在 PVC 请求中提供StorageClass名称。如果没有设置默认的StorageClass,每个 PVC 请求都将需要在请求中提供StorageClass名称。如果未启用默认类并且 PVC 请求未能设置StorageClass名称,PVC 分配将失败,因为 API 服务器将无法将请求链接到StorageClass

注意

在生产集群中,通常认为省略分配默认的StorageClass是一个好的做法。根据您的用户,您可能会有一些部署忘记设置一个类,而默认的存储系统可能不适合部署的需求。这个问题可能直到它变成一个生产问题才会出现,并且可能会影响业务收入或公司的声誉。如果您不分配一个默认的类,开发人员将会有一个失败的 PVC 请求,并且在对业务造成任何伤害之前会发现这个问题。

要列出集群上的存储类,请执行kubectl get storageclasses,或者使用sc而不是storageclasses的缩写版本:

图 4.11 - 默认存储类

图 4.11 - 默认存储类

接下来,让我们学习如何使用配置程序。

使用 KinD 的存储配置程序

使用包含的配置程序非常简单。由于它可以自动配置存储并设置为默认类,任何进来的 PVC 请求都会被配置 pod 看到,然后创建PersistentVolumePersistentVolumeClaim

为了展示这个过程,让我们通过必要的步骤。以下是在基本 KinD 集群上运行get pvget pvc的输出:

图 4.12 - PV 和 PVC 示例

图 4.12 - PV 和 PVC 示例

请记住,PersistentVolume不是一个命名空间对象,所以我们不需要在命令中添加命名空间选项。PVC 是命名空间对象,所以我告诉 Kubernetes 向我展示所有命名空间中可用的 PVC。由于这是一个新的集群,没有默认的工作负载需要持久磁盘,所以没有 PV 或 PVC 对象。

如果没有自动配置程序,我们需要在 PVC 声明卷之前创建 PV。由于我们在集群中运行了 Rancher 配置程序,我们可以通过部署一个带有 PVC 请求的 pod 来测试创建过程,就像这里列出的一样:

种类:PersistentVolumeClaim

apiVersion:v1

元数据:

名称:test-claim

规格:

访问模式:

  • ReadWriteOnce

资源:

请求:

存储:1Mi


种类:Pod

apiVersion:v1

元数据:

名称:test-pvc-claim

规格:

容器:

  • 名称:测试 pod

图像:busybox

命令:

  • “/bin/sh”

参数:

  • “-c”

  • “touch /mnt/test && exit 0 || exit 1”

volumeMounts:

  • 名称:测试-pvc

mountPath:“/mnt”

重启策略:“永不”

卷:

  • 名称:测试-pvc

persistentVolumeClaim:

claimName:test-claim

这个 PVC 请求将在默认命名空间中命名为test-claim,并且请求一个 1MB 的卷。由于 KinD 为集群设置了默认的StorageClass,我们确实需要包括StorageClass选项。

要创建 PVC,我们可以使用 kubectl 执行create命令,例如kubectl create -f pvctest.yaml - Kubernetes 将返回,指出 PVC 已创建,但重要的是要注意,这并不意味着 PVC 完全工作。PVC 对象已创建,但如果 PVC 请求中缺少任何依赖项,它仍将创建对象,尽管无法完全创建 PVC 请求。

创建 PVC 后,可以使用两种选项之一检查实际状态。第一个是一个简单的get命令;也就是kubectl get pvc。由于我的请求在默认命名空间中,所以在get命令中不需要包含命名空间值(请注意,我们必须缩短卷的名称以适应页面):

名称 状态 容量 访问模式 存储类 年龄

测试声明 Bound pvc-9c56cf65-d661-49e3- 1Mi RWO 标准 2s

我们知道我们在清单中创建了 PVC 请求,但我们没有创建 PV 请求。如果我们现在查看 PV,我们将看到从我们的 PVC 请求创建了一个 PV。同样,我们缩短了 PV 名称以适应单行输出:

名称 容量 访问模式 回收策略 状态 声明

pvc-9c56cf65-d661-49e3- 1Mi RWO 删除 已绑定 默认/测试声明

这完成了 KinD 存储部分。

由于许多工作负载需要持久磁盘,了解 Kubernetes 工作负载如何与存储系统集成非常重要。在本节中,您了解了 KinD 如何向集群添加自动配置程序。我们将在下一章第五章**,Kubernetes Bootcamp中加强我们对这些 Kubernetes 存储对象的知识。

为 Ingress 添加自定义负载均衡器

注意

这一部分是一个复杂的主题,涵盖了添加一个自定义的 HAProxy 容器,您可以使用它来负载均衡 KinD 集群中的工作节点。您不应该在我们将用于剩余章节的 KinD 集群上部署这些步骤。

我们为任何想要了解如何在多个工作节点之间进行负载平衡的人添加了这一部分。

KinD 不包括用于工作节点的负载均衡器。包含的 HAProxy 容器只为 API 服务器创建一个配置文件;团队不正式支持对默认镜像或配置的任何修改。由于您在日常工作中将与负载均衡器进行交互,我们希望添加一个关于如何配置自己的 HAProxy 容器以在三个 KinD 节点之间进行负载均衡的部分。

首先,我们不会在本书的任何章节中使用这个配置。我们希望让练习对每个人都可用,所以为了限制所需的资源,我们将始终使用在本章前面创建的双节点集群。如果您想测试带有负载均衡器的 KinD 节点,我们建议使用不同的 Docker 主机,或者等到您完成本书并删除 KinD 集群后再进行测试。

安装先决条件

我们假设您有一个基于以下配置的 KinD 集群:

  • 任意数量的控制平面节点

  • 三个工作节点

  • 集群名称为cluster01

  • 一个可用的Kindnet 或 CalicoCNI

  • 已安装 NGINX Ingress 控制器 - 补丁以在主机上监听端口 80 和 443

创建 KinD 集群配置

由于您将在 Docker 主机上使用暴露在端口 80 和 443 上的 HAProxy 容器,因此您不需要在集群的config文件中暴露任何端口。

为了使测试部署更容易,您可以使用这里显示的示例集群配置,它将创建一个简单的六节点集群,并禁用 Kindnet:

kind: Cluster

apiVersion: kind.x-k8s.io/v1alpha4

networking:

apiServerAddress: "0.0.0.0"

disableDefaultCNI: true

kubeadmConfigPatches:

  • |

apiVersion: kubeadm.k8s.io/v1beta2

kind: ClusterConfiguration

元数据:

名称:config

网络:

serviceSubnet: "10.96.0.1/12"

podSubnet: "192.168.0.0/16"

nodes:

  • 角色:控制平面

  • 角色:控制平面

  • 角色:控制平面

  • 角色:工作节点

  • 角色:工作节点

  • 角色:工作节点

您需要使用本章前面使用的相同清单安装 Calico。安装 Calico 后,您需要使用本章前面提供的步骤安装 NGINX Ingress 控制器。

一旦您部署了 Calico 和 NGINX,您应该有一个可用的基本集群。现在,您可以继续部署自定义的 HAProxy 容器。

部署自定义的 HAProxy 容器

HAProxy 在 Docker Hub 上提供了一个容器,很容易部署,只需要一个配置文件就可以启动容器。

要创建配置文件,您需要知道集群中每个工作节点的 IP 地址。在本书的 GitHub 存储库中,我们已经包含了一个脚本文件,可以为您找到这些信息,创建配置文件,并启动 HAProxy 容器。它位于HAProxy目录下,名为HAProxy-ingress.sh

为了帮助您更好地理解这个脚本,我们将分解脚本的各个部分,并详细说明每个部分执行的内容。首先,以下代码块正在获取我们集群中每个工作节点的 IP 地址,并将结果保存在一个变量中。我们将需要这些信息用于后端服务器列表:

!/bin/bash

worker1=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' cluster01-worker)

worker2=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' cluster01-worker2)

worker3=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' cluster01-worker3)

接下来,由于我们在启动容器时将使用绑定挂载,我们需要在已知位置拥有配置文件。我们选择将其存储在当前用户的主目录下,一个名为HAProxy的目录中:

在当前用户的主目录下创建一个 HAProxy 目录

mkdir ~/HAProxy

接下来,脚本的以下部分将创建HAProxy目录:

为工作节点创建 HAProxy.cfg 文件

tee ~/HAProxy/HAProxy.cfg <<EOF

配置的全局部分设置了全局性的安全和性能设置。

全局

log /dev/log local0

log /dev/log local1 notice

守护进程

defaults部分用于配置将应用于配置值中所有前端和后端部分的值:

默认值

全局日志

tcp 模式

连接超时 5000

客户端超时 50000

服务器超时 50000

前端 workers_https

绑定 *:443

tcp 模式

use_backend ingress_https

后端 ingress_https

选项 httpchk GET /healthz

tcp 模式

服务器 worker $worker1:443 检查端口 80

服务器 worker2 $worker2:443 检查端口 80

服务器 worker3 $worker3:443 检查端口 80

这告诉 HAProxy 创建一个名为workers_https的前端,以及绑定传入请求的 IP 地址和端口,使用 TCP 模式,并使用名为ingress_https的后端。

ingress_https后端包括了三个使用端口 443 作为目的地的工作节点。检查端口是一个健康检查,将测试端口 80。如果服务器在端口 80 上回复,它将被添加为请求的目标。虽然这是一个 HTTPS 端口 443 规则,但我们只使用端口 80 来检查 NGINX pod 的网络回复:

前端 workers_http

绑定*:80

use_backend ingress_http

后端 ingress_http

模式 http

选项 httpchk GET /healthz

服务器 worker $worker1:80 检查端口 80

服务器 worker2 $worker2:80 检查端口 80

服务器 worker3 $worker3:80 检查端口 80

这个前端部分创建了一个前端,接受端口 80 上的传入 HTTP 流量。然后使用后端ingress_http中的服务器列表作为端点。就像在 HTTPS 部分一样,我们使用端口 80 来检查是否有任何运行在端口 80 上的节点。任何回复检查的端点都将被添加为 HTTP 流量的目的地,而任何没有运行 NGINX 的节点将不会回复,这意味着它们不会被添加为目的地:

EOF

这结束了我们文件的创建。最终文件将在HAProxy目录中创建:

启动工作节点的 HAProxy 容器

docker run --name HAProxy-workers-lb -d -p 80:80 -p 443:443 -v ~/HAProxy:/usr/local/etc/HAProxy:ro HAProxy -f /usr/local/etc/HAProxy/HAProxy.cfg

最后一步是启动一个运行 HAProxy 的 Docker 容器,其中包含我们创建的配置文件,其中包含三个工作节点,暴露在 Docker 主机上的端口 80 和 443 上。

现在您已经学会了如何为工作节点安装自定义的 HAProxy 负载均衡器,让我们看看配置是如何工作的。

理解 HAProxy 流量流向

集群将总共有八个容器在运行。其中六个容器将是标准的 Kubernetes 组件,即三个控制平面服务器和三个工作节点。另外两个容器是 KinD 的 HAProxy 服务器和您自己的自定义 HAProxy 容器:

图 4.13-运行自定义 HAProxy 容器

图 4.13-运行自定义 HAProxy 容器

这个集群输出与我们的两节点集群有一些不同。请注意,工作节点没有暴露在任何主机端口上。工作节点不需要任何映射,因为我们有我们的新 HAProxy 服务器在运行。如果您查看我们创建的 HAProxy 容器,它是在主机端口 80 和 443 上暴露的。这意味着对端口 80 或 443 的任何传入请求都将被定向到自定义的 HAProxy 容器。

默认的 NGINX 部署只有一个副本,这意味着 Ingress 控制器正在单个节点上运行。如果我们查看 HAProxy 容器的日志,我们将看到一些有趣的东西:

[注意] 093/191701 (1):新工作人员#1(6)分叉

[警告] 093/191701 (6):服务器 ingress_https/worker 已关闭,原因:第 4 层连接问题,信息:“SSL 握手失败(连接被拒绝)”,检查持续时间:0 毫秒。剩下 2 个活动服务器和 0 个备份服务器。0 个会话活动,0 个重新排队,0 个在队列中剩余。

[警告] 093/191702 (6):服务器 ingress_https/worker3 已关闭,原因:第 4 层连接问题,信息:“SSL 握手失败(连接被拒绝)”,检查持续时间:0 毫秒。剩下 1 个活动服务器和 0 个备份服务器。0 个会话活动,0 个重新排队,0 个在队列中剩余。

[警告] 093/191702 (6):服务器 ingress_http/worker 已关闭,原因:第 4 层连接问题,信息:“连接被拒绝”,检查持续时间:0 毫秒。剩下 2 个活动服务器和 0 个备份服务器。0 个会话活动,0 个重新排队,0 个在队列中剩余。

[警告] 093/191703 (6):服务器 ingress_http/worker3 已关闭,原因:第 4 层连接问题,信息:“连接被拒绝”,检查持续时间:0 毫秒。剩下 1 个活动服务器和 0 个备份服务器。0 个会话活动,0 个重新排队,0 个在队列中剩余。

您可能已经注意到日志中有一些错误,比如 SSL 握手失败和连接被拒绝。虽然这些看起来像错误,但实际上它们是工作节点上的失败检查事件。请记住,NGINX 只在一个 pod 上运行,而且由于我们在 HAProxy 后端配置中有所有三个节点,它将检查每个节点上的端口。未能回复的任何节点将不用于负载平衡流量。在我们当前的配置中,这确实进行了负载平衡,因为我们只在一个节点上有 NGINX。但是,它确实为 Ingress 控制器提供了高可用性。

如果您仔细查看日志输出,您将看到在定义的后端上有多少个活动服务器;例如:

检查持续时间:0 毫秒。剩下 1 个活动服务器和 0 个备份服务器。

日志输出中的每个服务器池显示 1 个活动端点,因此我们知道 HAProxy 已成功找到端口 80 和 443 上的 NGINX 控制器。

要找出 HAProxy 服务器连接到哪个 worker,我们可以使用日志中的失败连接。每个后端将列出失败的连接。例如,根据其他两个 worker 节点显示为DOWN的日志,我们知道正在工作的节点是cluster01-worker2

服务器 ingress_https/worker 已宕机 服务器 ingress_https/worker3 已宕机

让我们模拟节点故障,以证明 HAProxy 为 NGINX 提供了高可用性。

模拟 Kubelet 故障

请记住,KinD 节点是临时的,停止任何容器可能会导致其在重新启动时失败。那么,我们如何模拟 worker 节点故障,因为我们不能简单地停止容器?

要模拟故障,我们可以停止节点上的 kubelet 服务,这将提醒kube-apisever不在节点上调度任何其他 pod。在我们的示例中,我们想证明 HAProxy 为 NGINX 提供了 HA 支持。我们知道正在运行的容器在worker2上,因此这是我们想要“关闭”的节点。

停止kubelet的最简单方法是向容器发送docker exec命令:

docker exec cluster01-worker2 systemctl stop kubelet

您不会从此命令中看到任何输出,但是如果您等待几分钟,让集群接收更新的节点状态,您可以通过查看节点列表来验证节点是否已关闭:

kubectl get nodes。

您将收到以下输出:

图 4.14 - worker2 处于 NotReady 状态

图 4.14 - worker2 处于 NotReady 状态

这验证了我们刚刚模拟了 kubelet 故障,并且worker2处于NotReady状态。

在 kubelet“故障”之前运行的任何 pod 将继续运行,但是kube-scheduler在 kubelet 问题解决之前不会在节点上调度任何工作负载。由于我们知道 pod 不会在节点上重新启动,我们可以删除 pod,以便它可以在不同的节点上重新调度。

您需要获取 pod 名称,然后将其删除以强制重新启动:

kubectl get pods -n ingress-nginx

nginx-ingress-controller-7d6bf88c86-r7ztq

kubectl delete pod nginx-ingress-controller-7d6bf88c86-r7ztq -n ingress-nginx

这将强制调度程序在另一个工作节点上启动容器。它还会导致 HAProxy 容器更新后端列表,因为 NGINX 控制器已移动到另一个工作节点。

如果您再次查看 HAProxy 日志,您将看到 HAProxy 已更新后端以包括cluster01-worker3,并且已将cluster01-worker2从活动服务器列表中删除:

[警告] 093/194006 (6) : 服务器 ingress_https/worker3 已启动,原因:Layer7 检查通过,代码:200,信息:“OK”,检查持续时间:4 毫秒。2 个活动服务器和 0 个备用服务器在线。0 个会话重新排队,队列中总共 0 个。

[警告] 093/194008 (6) : 服务器 ingress_http/worker3 已启动,原因:Layer7 检查通过,代码:200,信息:“OK”,检查持续时间:0 毫秒。2 个活动服务器和 0 个备用服务器在线。0 个会话重新排队,队列中总共 0 个。

[警告] 093/195130 (6) : 服务器 ingress_http/worker2 已关闭,原因:Layer4 超时,检查持续时间:2000 毫秒。1 个活动服务器和 0 个备用服务器剩下。0 个会话活动,0 个重新排队,0 个剩余在队列中。

[警告] 093/195131 (6) : 服务器 ingress_https/worker2 已关闭,原因:Layer4 超时,检查持续时间:2001 毫秒。1 个活动服务器和 0 个备用服务器剩下。0 个会话活动,0 个重新排队,0 个剩余在队列中。

如果您计划将此 HA 集群用于其他测试,您将需要重新启动cluster01-worker2上的 kubelet。如果您计划删除 HA 集群,只需运行 KinD 集群删除,所有节点将被删除。

删除 HAProxy 容器

一旦删除了 KinD 集群,您将需要手动删除我们添加的 HAProxy 容器。由于 KinD 没有创建我们的自定义负载均衡器,删除集群不会删除容器。

要删除自定义的 HAProxy 容器,请运行docker rm命令以强制删除镜像:

docker rm HAProxy-workers-lb –force

这将停止容器并将其从 Docker 的列表中删除,从而允许您在将来的 KinD 集群中再次使用相同的名称运行它。

总结

在本章中,您了解了名为 KinD 的 Kubernetes SIG 项目。我们详细介绍了如何在 KinD 集群中安装可选组件,包括 Calico 作为 CNI 和 NGINX 作为 Ingress 控制器。最后,我们介绍了包含在 KinD 集群中的 Kubernetes 存储对象的详细信息。

希望通过本章的帮助,您现在了解使用 KinD 可以为您和您的组织带来的力量。它提供了一个易于部署、完全可配置的 Kubernetes 集群。在单个主机上运行的集群数量在理论上仅受主机资源的限制。

在下一章中,我们将深入研究 Kubernetes 对象。我们将下一章称为Kubernetes 训练营,因为它将涵盖大多数基本的 Kubernetes 对象以及它们各自的用途。下一章可以被视为“Kubernetes 口袋指南”。它包含了 Kubernetes 对象的快速参考以及它们的作用,以及何时使用它们。

这是一个内容丰富的章节,旨在为那些具有 Kubernetes 经验的人提供复习,或者为那些新手提供速成课程。我们撰写本书的目的是超越基本的 Kubernetes 对象,因为当今市场上有许多涵盖 Kubernetes 基础知识的书籍。

问题

  1. 在创建持久卷索赔之前必须创建哪个对象?

A. PVC

B. 磁盘

C. 持久卷

D. 虚拟磁盘

  1. KinD 包括一个动态磁盘提供程序。是哪家公司创建了这个提供程序?

A. 微软

B. CNCF

C. VMware

D. 牧场主

  1. 如果您创建了一个具有多个工作节点的 KinD 集群,您将安装什么来将流量引导到每个节点?

A. 负载均衡器

B. 代理服务器

C. 什么都不用

D. 网络负载均衡器

  1. 正确或错误:Kubernetes 集群只能安装一个 CSIdriver。

A. 正确

B. 错误

第五章:Kubernetes 训练营

我们相信你们中的许多人在某种程度上使用过 Kubernetes——您可能在生产环境中运行集群,或者您可能使用过 kubeadm、Minikube 或 Docker Desktop 进行试验。我们的目标是超越 Kubernetes 的基础知识,因此我们不想重复 Kubernetes 的所有基础知识。相反,我们添加了这一章作为一个训练营,供那些可能对 Kubernetes 还不熟悉,或者可能只是稍微玩过一下的人参考。

由于这是一个训练营章节,我们不会深入讨论每个主题,但到最后,您应该对 Kubernetes 的基础知识有足够的了解,以理解剩下的章节。如果您对 Kubernetes 有很强的背景,您可能仍然会发现本章对您有用,因为它可以作为一个复习,我们将在第六章《服务、负载均衡和外部 DNS》开始讨论更复杂的主题。

在这一章中,我们将介绍运行中的 Kubernetes 集群的组件,包括控制平面和工作节点。我们将详细介绍每个 Kubernetes 对象及其用例。如果您以前使用过 Kubernetes,并且熟悉使用 kubectl 并完全了解 Kubernetes 对象(如 DaemonSets,StatefulSets,ReplicaSets 等),您可能希望跳转到第六章《服务、负载均衡和外部 DNS》,在那里我们将使用 KinD 安装 Kubernetes。

在本章中,我们将涵盖以下主题:

  • Kubernetes 组件概述

  • 探索控制平面

  • 了解工作节点组件

  • 与 API 服务器交互

  • 介绍 Kubernetes 对象

技术要求

本章有以下技术要求:

  • 具有至少 4GB 随机存取内存(RAM)的 Ubuntu 18.04 服务器

  • 一个 KinD Kubernetes 集群

您可以在以下 GitHub 存储库中访问本章的代码:github.com/PacktPublishing/Kubernetes-and-Docker-The-Complete-Guide

Kubernetes 组件概述

在任何基础设施中,了解系统如何共同提供服务总是一个好主意。如今有这么多安装选项,许多 Kubernetes 用户并不需要了解 Kubernetes 组件如何集成。

短短几年前,如果您想运行一个 Kubernetes 集群,您需要手动安装和配置每个组件。安装一个运行的集群是一个陡峭的学习曲线,经常导致挫折,让许多人和公司说“Kubernetes 太难了”。手动安装的优势在于,您真正了解每个组件是如何交互的,如果安装后您的集群遇到问题,您知道要查找什么。

如今,大多数人会在云服务提供商上点击一个按钮,几分钟内就可以拥有一个完全运行的 Kubernetes 集群。本地安装也变得同样简单,谷歌、红帽、牧场等提供了选项,消除了安装 Kubernetes 集群的复杂性。我们看到的问题是,当安装后遇到问题或有疑问时。由于您没有配置 Kubernetes 组件,您可能无法向开发人员解释 Pod 是如何在工作节点上调度的。最后,由于您正在运行第三方提供的安装程序,他们可能启用或禁用您不知道的功能,导致安装可能违反您公司的安全标准。

要了解 Kubernetes 组件如何协同工作,首先必须了解 Kubernetes 集群的不同组件。以下图表来自Kubernetes.io网站,显示了 Kubernetes 集群组件的高级概述:

图 5.1 - Kubernetes 集群组件

图 5.1 - Kubernetes 集群组件

正如您所看到的,Kubernetes 集群由多个组件组成。随着我们在本章中的进展,我们将讨论这些组件及它们在 Kubernetes 集群中的作用。

探索控制平面

顾名思义,控制平面控制集群的每个方面。如果您的控制平面崩溃,您可能可以想象到您的集群将遇到问题。没有控制平面,集群将没有任何调度能力,这意味着正在运行的工作负载将保持运行,除非它们被停止和重新启动。由于控制平面非常重要,因此建议您至少有三个主节点。许多生产安装运行超过三个主节点,但安装节点的数量应始终是奇数。让我们看看为什么控制平面及其组件对运行中的集群如此重要,通过检查每个组件。

Kubernetes API 服务器

在集群中要理解的第一个组件是kube-apiserver组件。由于 Kubernetes 是应用程序编程接口API)驱动的,进入集群的每个请求都经过 API 服务器。让我们看一个简单的使用 API 端点的获取节点请求,如下所示:

https://10.240.100.100:6443/api/v1/nodes?limit=500

Kubernetes 用户常用的一种与 API 服务器交互的方法是 kubectl 实用程序。使用 kubectl 发出的每个命令在幕后调用一个 API 端点。在前面的示例中,我们执行了一个kubectl get nodes命令,该命令将一个 API 请求发送到端口6443上的10.240.100.100上的kube-apiserver进程。API 调用请求了/api/vi/nodes端点,返回了集群中节点的列表,如下截图所示:

图 5.2 - Kubernetes 节点列表

图 5.2 - Kubernetes 节点列表

没有运行的 API 服务器,集群中的所有请求都将失败。因此,可以看到,始终运行kube-apiserver组件非常重要。通过运行三个或更多的主节点,我们可以限制失去主节点的任何影响。

注意

当运行多个主节点时,您需要在集群前面放置一个负载均衡器。Kubernetes API 服务器可以由大多数标准解决方案,包括 F5、HAProxy 和 Seesaw。

Etcd 数据库

毫不夸张地说,Etcd 就是您的 Kubernetes 集群。Etcd 是一个快速且高可用的分布式键值数据库,Kubernetes 使用它来存储所有集群数据。集群中的每个资源在数据库中都有一个键。如果您登录到运行 Etcd 的节点或 Pod,您可以使用etcdctl可执行文件查看数据库中的所有键。以下代码片段显示了运行 KinD 集群的示例:

EtcdCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --key=/etc/kubernetes/pki/etcd/server.key --cert=/etc/kubernetes/pki/etcd/server.crt get / --prefix --keys-only

前面命令的输出包含太多数据,无法在本章中列出。基本的 KinD 集群将返回大约 317 个条目。所有键都以/registry/

posted @ 2024-05-06 18:36  绝不原创的飞龙  阅读(5)  评论(0编辑  收藏  举报