Linux-架构实用手册-全-
Linux 架构实用手册(全)
原文:
zh.annas-archive.org/md5/7D24F1F94933063822D38A8D8705DDE3
译者:飞龙
前言
欢迎阅读《架构师的 Linux 实战》,深入了解架构师在处理基于 Linux 的解决方案时的思维过程。本书将帮助您达到架构和实施不同 IT 解决方案所需的知识水平。
此外,它将向您展示开源软件的灵活性,通过演示行业中最广泛使用的一些产品,为您提供解决方案并分析每个方面,从设计阶段的最开始,一直到实施阶段,在那里我们将从零开始构建我们设计中提出的基础设施。
深入探讨设计解决方案的技术方面,我们将详细剖析每个方面,以实施和调整基于 Linux 的开源解决方案。
这本书是为谁写的
本书面向 Linux 系统管理员、Linux 支持工程师、DevOps 工程师、Linux 顾问以及任何其他类型的希望学习或扩展基于 Linux 和开源软件的架构、设计和实施解决方案的技术专业人士。
本书涵盖了什么
第一章,设计方法论简介,通过分析提出的问题以及在设计解决方案时应该提出的正确问题,来提取必要的信息以定义正确的问题陈述。
第二章,定义 GlusterFS 存储,介绍了 GlusterFS 是什么,并定义了存储集群。
第三章,设计存储集群,探讨了使用 GlusterFS 及其各种组件实施集群存储解决方案的设计方面。
第四章,在云基础设施上使用 GlusterFS,解释了在云上实施 GlusterFS 所需的配置。
第五章,分析 Gluster 系统的性能,详细介绍了先前配置的解决方案,解释了所采取的配置以及对性能进行测试的实施。
第六章,创建高可用的自愈架构,讨论了 IT 行业是如何从使用单片应用程序发展为基于云的、容器化的、高可用的微服务的。
第七章,理解 Kubernetes 集群的核心组件,探讨了核心 Kubernetes 组件,展示了每个组件以及它们如何帮助我们解决客户的问题。
第八章,设计 Kubernetes 集群,深入探讨了 Kubernetes 集群的要求和配置。
第九章,部署和配置 Kubernetes,介绍了 Kubernetes 集群的实际安装和配置。
第十章,使用 ELK Stack 进行监控,解释了弹性堆栈的每个组件以及它们的连接方式。
第十一章,设计 ELK Stack,涵盖了部署弹性堆栈时的设计考虑。
第十二章,使用 Elasticsearch、Logstash 和 Kibana 管理日志,描述了弹性堆栈的实施、安装和配置。
第十三章,使用 Salty 解决管理问题,讨论了业务需要具有用于基础设施的集中管理实用程序的需求,例如 Salt。
第十四章,Getting Your Hands Salty,介绍了如何安装和配置 Salt。
第十五章,设计最佳实践,介绍了设计具有弹性和防故障解决方案所需的一些不同最佳实践。
充分利用本书
需要一些基本的 Linux 知识,因为本书不解释 Linux 管理的基础知识。
本书中给出的示例可以在云端或本地部署。其中一些设置是在微软的云平台 Azure 上部署的,因此建议您拥有 Azure 帐户以便跟随示例。Azure 提供免费试用以评估和测试部署,更多信息可以在azure.microsoft.com/free/
找到。此外,有关 Azure 的更多信息可以在azure.microsoft.com
找到。
由于本书完全围绕 Linux 展开,因此连接到互联网是必需的。这可以从 Linux 桌面(或笔记本电脑)、macOS 终端或Windows 子系统(WSL)中完成。
本书中所示的所有示例都使用可以轻松从可用存储库或各自的来源获得的开源软件,无需支付许可证。
一定要访问项目页面以表达一些爱意——开发这些项目需要付出很多努力:
下载示例代码文件
您可以从www.packt.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了本书,可以访问www.packt.com/support并注册,以便直接通过电子邮件接收文件。
您可以按照以下步骤下载代码文件:
-
请登录或注册www.packt.com。
-
选择“支持”选项卡。
-
单击“代码下载和勘误”。
-
在搜索框中输入书名,然后按照屏幕上的说明操作。
下载文件后,请确保使用最新版本的解压缩软件解压缩文件夹:
-
Windows 上的 WinRAR/7-Zip
-
Mac 上的 Zipeg/iZip/UnRarX
-
Linux 上的 7-Zip/PeaZip
该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/-Hands-On-Linux-for-Architects
。如果代码有更新,将在现有的 GitHub 存储库上进行更新。
我们还有来自丰富书籍和视频目录的其他代码包,可以在github.com/PacktPublishing/
上找到。去看看吧!
下载彩色图像
我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图像。您可以在此处下载:www.packtpub.com/sites/default/files/downloads/9781789534108_ColorImages.pdf
。
使用的约定
本书中使用了许多文本约定。
CodeInText
:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名。这是一个例子:“该命令中的两个关键点是address-prefix
标志和subnet-prefix
标志。”
代码块设置如下:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gluster-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
当我们希望引起您对代码块的特定部分的注意时,相关行或项目会以粗体显示:
SHELL ["/bin/bash", "-c"]
RUN echo "Hello I'm using bash"
任何命令行输入或输出都会以以下方式编写:
yum install -y zfs
粗体:表示一个新术语,一个重要单词,或者您在屏幕上看到的单词。例如,菜单或对话框中的单词会以这种方式出现在文本中。这里有一个例子:“要确认数据是否被发送到集群,请转到 kibana 屏幕上的 Discover”。
警告或重要说明会显示为这样。
提示和技巧会显示为这样。
联系我们
我们始终欢迎读者的反馈。
一般反馈:如果您对本书的任何方面有疑问,请在邮件主题中提及书名,并发送电子邮件至customercare@packtpub.com
。
勘误:尽管我们已经尽一切努力确保内容的准确性,但错误确实会发生。如果您在这本书中发现了错误,我们将不胜感激,如果您能向我们报告。请访问www.packt.com/submit-errata,选择您的书,点击勘误提交表格链接,并输入详细信息。
盗版:如果您在互联网上发现我们作品的任何形式的非法副本,我们将不胜感激,如果您能向我们提供位置地址或网站名称。请通过链接copyright@packt.com
与我们联系。
如果您有兴趣成为作者:如果您在某个专题上有专业知识,并且有兴趣撰写或为一本书做出贡献,请访问authors.packtpub.com。
评论
请留下评论。一旦您阅读并使用了这本书,为什么不在购买它的网站上留下评论呢?潜在的读者可以看到并使用您的客观意见来做出购买决定,我们在 Packt 可以了解您对我们产品的看法,我们的作者可以看到您对他们的书的反馈。谢谢!
有关 Packt 的更多信息,请访问packt.com。
第一部分:使用 GlusterFS 的高性能存储解决方案
在本节中,读者将能够了解在使用 GlusterFS 部署高性能存储解决方案时需要做出的决策。
本节包括以下章节:
-
第一章,设计方法论简介
-
第二章,定义 GlusterFS 存储
-
第三章,设计存储集群
-
第四章,在云基础架构上使用 GlusterFS
-
第五章,在 Gluster 系统中分析性能
第一章:设计方法论简介
如今,IT 解决方案需要更高的性能和数据可用性,设计一个满足这些要求的强大实施方案是许多 IT 专家每天都要面对的挑战。
在本章中,你将学习从鸟瞰 IT 解决方案架构的基础知识,无论是在任何类型的环境中,还是虚拟化基础设施、裸金属甚至公共云,解决方案设计的基本概念都适用于任何环境。
你将探讨以下主题:
-
定义解决方案设计的阶段及其重要性
-
分析问题并提出正确的问题
-
考虑可能的解决方案
-
实施解决方案
充分理解在构建解决方案时需要考虑的方面对于项目的成功至关重要,因为这将决定哪些软件、硬件和配置将帮助你实现符合客户需求的期望状态。
定义解决方案设计的阶段及其重要性
像许多事情一样,设计解决方案是一个逐步的过程,不仅涉及技术方面,也不一定涉及技术方面的人员。通常情况下,你将受到一个客户经理、项目经理或者如果你很幸运的话,一个懂得技术需求的 CTO 的委托。他们正在寻找一个能够帮助他们向客户提供解决方案的专家。这些请求通常不包含你需要提供解决方案所需的所有信息,但这是了解你的目标的一个开始。
例如,想象一下,你收到了一个项目经理的电子邮件,其中包含以下陈述。
我们需要一个能够承受至少 10,000 个网站访问量并在更新期间保持可用以及在故障期间存活的解决方案。我们的预算相当有限,所以我们需要尽可能少地花费,几乎没有前期成本。我们还预计在项目的生命周期中这个解决方案会获得动力。
从上面的陈述中,你只能得到一个大致的要求,但没有给出具体的细节。因此,你只知道基本信息:我们需要一个能够承受至少 10,000 个网站访问量的解决方案,这对于设计来说是不够的,因为你需要尽可能多的信息来解决客户暴露出的问题。这就是你必须尽可能多地询问细节,以便为客户提供准确的提案,这将是客户对项目的第一印象。这部分非常关键,因为它将帮助你了解你是否理解了客户的愿景。
同样重要的是要理解,你需要为客户提供几种不同的解决方案,因为客户是决定哪种最符合他们业务需求的人。记住,每种解决方案都有其优点和缺点。客户决定后,你将有必要继续实施你的提案,这可能会引发更多的挑战。通常情况下,这将需要一些最终的定制调整或在最初的概念验证(POC)中没有考虑到的变化。
从我们之前的分析中,你可以看到你需要遵循的过程的四个明确定义的阶段,以达到下图所示的最终交付:
我们可以涵盖许多更多的阶段和设计方法,但由于它们不在本书的范围内,我们将专注于这四个一般阶段,以帮助你了解你将在其中构建解决方案的过程。
分析问题并提出正确的问题
在获得初始前提之后,您需要将其分解成较小的部分,以便了解所需的内容。每个部分都会引发不同的问题,您稍后将向客户提出这些问题。这些问题将有助于填补 POC 的空白,确保您的问题涵盖所有业务需求,从所有视角来看:业务视角、功能视角,最后是技术视角。跟踪出现的问题以及它们将解决的业务需求的一个好方法是拥有一个清单,询问问题是从哪个视角提出的,并解决或回答了什么。
重要的是要注意,随着问题变成答案,它们也可能伴随着约束或其他障碍,这些也需要在 POC 阶段进行解决和提及。客户将不得不同意这些约束,并在选择最终解决方案时起决定性作用。
从我们之前的例子中,您可以通过将其分解为不同的视角来分析前提。
我们需要一个可以承受至少 10,000 次网站点击并且在更新期间保持可用以及在停机期间幸存的解决方案。我们的预算相当有限,因此我们需要尽可能少地花费,几乎没有预付成本。我们还期望在项目的生命周期中获得动力。
技术视角
从这个角度来看,我们将分析前提的所有技术方面-这是您需要提供解决方案的初始技术要求。
我们将以以下方式进行分析:
-
您可以从前提中了解到,您的客户需要一种可以承受一定数量的网站点击的解决方案,但您无法确定 Web 服务器是否已经设置好,以及客户是否只需要一个负载平衡解决方案。或者,客户可能需要一个 Web 服务器,即 NGINX、Apache 或类似的服务器,以及负载平衡解决方案。
-
客户提到他们的网站至少有 10,000 次点击,但他们没有提到这些点击是每秒、每天、每周,甚至每月的并发点击。
-
您还可以看到,他们需要在更新期间保持可用,并且能够在公司出现故障时继续为其网站提供服务,但所有这些陈述都非常一般,因为可用性是以 9s 来衡量的。您拥有的 9s 越多,就越好(实际上,这是一年中停机时间的百分比测量;99%的可用性意味着每年只能有 526 分钟的停机时间)。停机也很难预测,几乎不可能说您永远不会出现停机,因此,您需要为此做好计划。在发生灾难时,您必须为您的解决方案设定恢复点目标(RPO)和恢复时间目标(RTO)。客户没有提到这一点,了解企业可以承受多长时间的停机时间至关重要。
-
在预算方面,这通常是从业务角度来看的,但技术方面受到直接影响。项目预算似乎很紧张,客户希望尽可能少地花费在他们的解决方案上,但他们没有提到确切的数字,您需要这些数字来调整您的提案。几乎没有预付成本?这是什么意思?我们是否正在重新利用现有资源并构建新的解决方案?如何实施没有预付成本的设计?克服低预算或没有预付成本的一种方法,至少在软件方面,是利用开源软件(OSS),但这是我们需要向客户询问的事情。
-
获得动力只能意味着他们预测他们的用户群最终会增长,但您需要估计他们预测这种增长的规模和速度,因为这将意味着您必须使解决方案准备好进行垂直或水平扩展。垂直扩展,通过留出空间最终增加资源,并考虑业务的采购流程,如果您需要购买更多资源,如 RAM、CPU 或存储。水平扩展也将涉及采购流程,并需要相当长的时间将新节点/服务器/VM/容器集成到解决方案中。这些都不包括在前提中,这是至关重要的信息。
这里我们对水平和垂直扩展进行了比较。水平扩展增加了更多的节点,而垂直扩展增加了现有节点的更多资源:
以下是一些示例问题的列表,您可以询问以澄清不清楚的地方:
-
这个解决方案是为新的/现有的网站或 Web 服务器吗?
-
当您说 10,000 次点击时,这是每秒同时发生的还是每天/每周/每月发生的?
-
您是否有关于用户群规模有多大的估计或当前数据?
-
考虑到预算有限,我们可以使用 OSS 吗?
-
您是否有技术资源来支持解决方案,如果我们使用 OSS?
-
您是否已经有任何更新基础设施,或者已经实施了版本控制软件?
-
当您说几乎没有前期成本时,这是否意味着您已经拥有硬件、资源或基础设施(虚拟或云)可供我们回收和/或重用于我们的新解决方案?
-
是否有任何灾难恢复站点可以使用,以提供高可用性?
-
如果您的用户群增长,这会产生更多的存储需求还是只需要计算资源?
-
您是否计划执行任何备份?您的备份方案是什么?
从技术角度来看,一旦开始设计您的 POC,将会根据解决方案中将使用的软件或硬件产生更多问题。您需要知道它们如何适应或需要什么来调整到客户的现有基础设施(如果有的话)。
业务角度
在这里,我们将从业务角度分析该声明,考虑可能影响我们设计的所有方面:
-
一个主要的要求是性能,因为这会影响解决方案可以承受多少点击。由于这是解决方案的主要目标之一,它需要根据业务期望进行规模化。
-
预算似乎是影响项目设计和范围的主要约束。
-
没有提及实际可用的预算。
-
可用性要求影响业务在发生故障时应该如何应对。由于没有具体的服务级别协议(SLA),这需要澄清以适应业务需求。
-
一个主要的问题是前期成本。通过利用 OSS,可以大大降低这一成本,因为没有许可费用。
-
已经提到解决方案需要在维护操作期间保持运行。这可能表明客户愿意投资于维护操作以进行进一步的升级或增强。
-
声明中提到,我们也希望这会带来动力,表明解决方案所需资源的数量将发生变化,从而直接影响其消耗的资金数量。
以下是澄清业务疑问时要问的问题:
-
根据性能要求,当性能低于预期基线时,业务会受到什么影响?
-
项目的实际预算是多少?
-
预算是否考虑了维护操作?
-
考虑到可能的非计划中断和维护,您的网站每年确切可以停机多长时间?这会影响业务连续性吗?
-
如果发生故障,应用程序可以容忍多长时间不接收数据?
-
我们是否有任何数据可以估计您的用户群体将增长多少?
-
您是否有任何采购流程?
-
批准新硬件或资源采购需要多长时间?
功能角度
在功能角度,您将审查解决方案的功能方面:
-
您知道客户需要 10,000 次点击,但是什么类型的用户将使用这个网站?
-
您可以看到它需要 10,000 次点击,但是假设并未指定用户将如何使用它。
-
假设陈述指出他们需要在更新期间可用的解决方案。由此,我们假设应用程序将被更新,但是如何更新?
为了澄清功能角度的差距,我们可以要求以下信息:
-
什么类型的用户将使用您的应用程序?
-
您的用户在您的网站上会做什么?
-
这个应用程序会经常更新或维护吗?
-
谁将维护和支持这个解决方案?
-
这个网站是为内部公司用户还是外部用户?
需要注意的是,功能角度与业务角度有很大的重叠,因为它们都试图解决类似的问题。
一旦我们收集了所有信息,您可以建立一个文件总结您解决方案的要求;确保您与客户一起审查,并且他们同意所需的内容,以便考虑此解决方案为完成。
考虑可能的解决方案
一旦消除了最初假设中出现的所有疑问,您可以继续构建一个更加详细和具体的陈述,其中包括所有收集到的信息。我们将继续使用我们之前的陈述,并假设我们的客户回答了我们之前的所有问题,我们可以构建一个更详细的陈述,如下所示。
我们需要一个新的 Web 服务器,用于我们的金融应用程序,可以每秒承受至少 10,000 次网页点击,来自大约 2,000 个用户,以及另外三个将使用其数据的应用程序。它将能够通过使用至少四个节点的高可用性实现来承受维护和故障。该项目的预算将为初始实施提供 20,000 美元,并且该项目将利用 OSS,从而降低前期成本。解决方案将部署在现有的虚拟环境中,其支持将由我们的内部 Linux 团队处理,并且更新将由我们自己的更新管理解决方案在内部进行。用户群体将每两个月增长一次,这在我们的采购流程范围内,允许我们相当快速地获取新资源,而不会产生资源争用的长时间。用户增长将主要影响计算资源。
正如您所看到的,这是一个更完整的陈述,您可以开始工作。您知道它将利用现有的虚拟基础设施。 OSS 是可行的,还需要高可用性,并且将通过已经存在的更新和版本控制基础设施进行更新,因此可能只需要为您的新解决方案添加监控代理。
一个非常简化的概述,没有太多细节的可能设计如下:
在图表中,您可以看到这是一个 Web 服务器集群,为使用解决方案的客户和应用程序提供高可用性和负载均衡。
由于您已经利用了大部分现有基础设施,可能的 POC 选项较少,因此这个设计将非常直接。尽管如此,我们可以玩一些变量,为我们的客户提供几种不同的选择。例如,对于 Web 服务器,我们可以有一个使用 Apache 的解决方案,另一个使用 NGINX,或者两者结合,Apache 托管网站,NGINX 提供负载均衡。
POC
有了完整的陈述和已经定义的几个选项,我们可以继续提供基于可能路线之一的 POC。
POC 是演示一个想法或方法的过程,在我们的情况下是一个解决方案,旨在验证给定功能。此外,它提供了解决方案在环境中的行为的广泛概述,允许进一步测试以进行特定工作负载和用例的微调。
任何 POC 都有其优点和缺点,但主要重点是让客户和架构师探索实际工作环境的解决方案的不同概念。重要的是要注意,作为架构师,您对于使用哪个 POC 作为最终解决方案有很大的影响,但客户是选择哪些约束和优势更适合他们业务的人。
以选择 NGINX 作为负载均衡器以提供高可用性和性能改进给托管应用文件的 Apache Web 服务器为例,我们可以使用缩减资源来实现一个可行的解决方案。我们可以只部署两个节点来演示负载均衡功能,并故意关闭其中一个节点来演示高可用性。
以下是描述先前示例的图表:
这不需要在设计阶段设想的完整四节点集群,因为我们不测试整个解决方案的完整性能。对于性能或负载测试,可以通过较少的并发用户为应用程序提供接近实际工作负载。虽然较少的用户永远不会提供完整实施的精确性能数字,但它提供了一个基准数据,可以后续推断出实际性能的近似值。
例如,对于性能测试,我们可以只有四分之一的用户群和一半的资源来加载应用程序,而不是有 2,000 个用户。这将大大减少所需的资源量,同时提供足够的数据来分析最终解决方案的性能。
此外,在信息收集阶段,编写一个记录不同 POC 的文档是一个好主意,因为如果客户将来想构建类似的解决方案,它可以作为一个起点。
实施解决方案
一旦客户根据其业务需求选择了最佳路线,我们就可以开始构建我们的设计。在这个阶段,您将面临不同的障碍,因为在开发或 QA 环境中实施 POC 可能与生产环境不同。在 QA 或开发中有效的东西现在可能在生产中失败,并且可能存在不同的变量;所有这些问题只会在实施阶段出现,您需要意识到,在最坏的情况下,这可能意味着需要更改初始设计的大量内容。
这个阶段需要与客户和客户的环境进行实际操作,因此确保您所做的更改不会影响当前的生产是至关重要的。与客户合作也很重要,因为这将使他们的 IT 团队熟悉新解决方案;这样,当签署完成时,他们将对其及其配置有所了解。
在这个阶段,实施指南的制作是最重要的部分之一,因为它将记录解决方案的每一步和每一个小的配置。它还将有助于将来,如果出现问题,支持团队需要知道如何配置才能解决问题。
总结
设计解决方案需要不同的方法。本章介绍了设计阶段的基础知识以及它们的重要性。
第一阶段通过分析设计旨在解决的问题,同时提出正确的问题。这将有助于定义实际需求并将范围缩小到真正的业务需求。与初始问题陈述一起工作将在后续阶段带来问题,使得这个阶段非常重要,因为它将防止不必要地来回移动。
然后,我们考虑了解决已定义问题的可能路径或解决方案。通过在前一阶段提出正确的问题,我们应该能够构建几个选项供客户选择,并且稍后可以实施 POC。POC 有助于客户和架构师了解解决方案在实际工作环境中的行为。通常,POC 是最终解决方案的缩减版本,使实施和测试更加灵活。
最后,实施阶段涉及项目的实际配置和实际操作方面。根据 POC 期间的发现,可以进行更改以适应每个基础设施的具体情况。通过这个阶段提供的文档将有助于使各方保持一致,以确保解决方案得到预期的实施。
在下一章中,我们将着手解决影响每种实施的问题,无论是云提供商、软件还是设计,展示高性能冗余存储的必要性。
问题
-
解决方案设计的阶段是什么?
-
设计解决方案时提出正确问题为什么重要?
-
为什么我们应该提供几种设计选项?
-
可以提出哪些问题以获取有助于设计更好解决方案的信息?
-
什么是 POC?
-
实施阶段发生了什么?
-
POC 如何帮助最终实施?
进一步阅读
在随后的章节中,我们将介绍为特定问题创建解决方案的过程。由于这些解决方案将在 Linux 中实施,我们建议阅读Oliver Pelz的Linux 基础 www.packtpub.com/networking-and-servers/fundamentals-linux
.
第二章:定义 GlusterFS 存储
每天,应用程序需要更快的存储,可以支持成千上万个并发 I/O 请求。GlusterFS 是一个高度可扩展的冗余文件系统,可以同时向许多客户端提供高性能 I/O。我们将定义集群的核心概念,然后介绍 GlusterFS 如何发挥重要作用。
在前一章中,我们已经讨论了设计解决方案的不同方面,以提供高可用性和性能,以满足许多要求的应用程序。在本章中,我们将解决一个非常具体的问题,即存储。
在本章中,我们将涵盖以下主题:
-
理解集群的核心概念
-
选择 GlusterFS 的原因
-
解释软件定义存储(SDS)
-
探索文件、对象和块存储之间的区别
-
解释对高性能和高可用存储的需求
技术要求
本章将重点介绍定义 GlusterFS。您可以参考项目的主页github.com/gluster/glusterfs
或www.gluster.org/
。
此外,项目的文档可以在docs.gluster.org/en/latest/
找到。
什么是集群?
我们可以利用 SDS 的许多优势,它允许轻松扩展和增强容错能力。GlusterFS 是一款软件,可以创建高度可扩展的存储集群,同时提供最大性能。
在我们解决这个特定需求之前,我们首先需要定义集群是什么,为什么它存在,以及集群可能能够解决什么问题。
计算集群
简而言之,集群是一组计算机(通常称为节点),它们协同工作在相同的工作负载上,并可以将负载分布到集群的所有可用成员上,以增加性能,同时允许自我修复和可用性。请注意,在现实中,任何计算机都可以添加到集群中,因此术语服务器之前并未使用。从简单的树莓派到多 CPU 服务器,集群可以由一个小型的双节点配置制成,也可以由数据中心中的数千个节点制成。
这是一个集群的例子:
从技术上讲,集群允许工作负载通过添加具有相似资源特征的相同类型的服务器来扩展性能。理想情况下,集群将具有同质硬件,以避免节点具有不同性能特征的问题,并且同时使维护相对相同 - 这意味着具有相同 CPU 系列、内存配置和软件的硬件。向集群添加节点的想法允许您计算工作负载以减少其处理时间。根据应用程序,计算时间有时甚至可以线性减少。
为了进一步理解集群的概念,想象一下您有一个应用程序,它获取历史财务数据。然后,该应用程序接收这些数据并根据存储的信息创建预测。在单个节点上,预测过程(集群上的进程通常被称为作业)大约需要六天才能完成,因为我们处理了数 TB 的数据。添加具有相同特征的额外节点将处理时间缩短到四天。再添加第三个节点将进一步缩短完成时间至三天。
请注意,虽然我们增加了三倍的计算资源,但计算时间仅减少了大约一半。一些应用程序可以线性扩展性能,而其他应用程序则没有相同的可扩展性,需要更多的资源来获得更少的收益,直至收益递减的点。为了获得最小的时间收益,添加更多资源是不划算的。
考虑到所有这些,我们可以指出定义集群的几个特征:
-
它可以通过添加计算资源来帮助减少处理时间
-
它可以进行垂直和水平扩展
-
它可以是冗余的,也就是说,如果一个节点失败,其他节点应该接管工作负载
-
它可以允许增加资源以供应用程序使用
-
它是一个资源池,而不是单独的服务器
-
它没有单点故障
存储集群
现在我们已经了解了如何计算集群,让我们继续讨论集群的另一个应用。
存储集群的主要功能不是聚合计算资源以减少处理时间,而是聚合可用空间以提供最大的空间利用率,同时提供某种形式的冗余。随着存储大量数据的需求增加,需要以更低的成本进行存储,同时仍然保持增加的数据可用性。存储集群通过允许单个单片式存储节点一起工作,形成一个大型的可用存储空间池,来解决这个问题。因此,它允许存储解决方案在不需要部署专门的专有硬件的情况下达到 PB 级别。
例如,假设我们有一个单节点,可用空间为 500 TB,我们需要实现 1-PB标记,并提供冗余。这个单独的节点成为单点故障,因为如果它宕机,那么数据就无法访问。此外,我们已经达到了最大的硬盘驱动器(HDD)容量。换句话说,我们无法进行水平扩展。
为了解决这个问题,我们可以添加两个具有相同配置的节点,因为已经存在的节点提供了总共 1 PB 的可用空间。现在,让我们在这里做一些数学运算,500 TB 乘以 3 应该大约是 1.5 PB,对吗?答案绝对是肯定的。然而,由于我们需要为这个解决方案提供高可用性,第三个节点充当备份,使解决方案能够容忍单节点故障而不中断客户端的通信。这种允许节点故障的能力完全归功于 SDS 和存储集群的强大功能,比如 GlusterFS,接下来我们将探讨它。
什么是 GlusterFS?
GlusterFS 是 Gluster 的一个开源项目,该项目于 2011 年被 Red Hat 公司收购。这个收购并不意味着您必须获取 Red Hat 订阅或支付 Red Hat 才能使用它,因为正如前面提到的,它是一个开源项目;因此,您可以自由安装它,查看其源代码,甚至为项目做出贡献。尽管 Red Hat 提供基于 GlusterFS 的付费解决方案,但我们将在本章中讨论开源软件(OSS)和项目本身。
以下图表显示了 Gluster 项目中的贡献者和提交数量:
要理解 GlusterFS,我们必须了解它与传统存储的区别。为了做到这一点,我们需要了解 SDS 背后的概念,包括 GlusterFS 是什么。
传统存储是一个行业标准的存储阵列,其中包含专有软件,与硬件供应商绑定。所有这些都限制了您遵循存储提供商设定的一组规则:
-
可扩展性限制
-
硬件兼容性限制
-
客户端操作系统限制
-
配置限制
-
供应商锁定
SDS
有了 SDS,许多,如果不是所有,前面的限制都消失了,因为它通过不依赖于任何硬件提供了令人印象深刻的可扩展性。您基本上可以从任何供应商那里获取包含您所需存储的行业标准服务器,并将其添加到您的存储池中。只需执行这一简单步骤,您就已经克服了前面四个限制。
成本降低
SDS部分的示例大大降低了运营费用(OPEX)成本,因为你不必为现有供应商存储阵列购买昂贵的扩展架,这可能需要数周才能到货并安装。你可以快速获取存储在数据中心角落的服务器,并用它为现有应用程序提供存储空间。这个过程称为插件可扩展性,并且存在于大多数开源 SDS 项目中。从理论上讲,SDS 在可扩展性方面的潜力是无限的。
可扩展性
当你向存储池添加新服务器时,SDS 会扩展,并增加存储集群的弹性。根据你的配置,数据会分布在多个成员节点上,通过镜像或创建数据的奇偶校验提供额外的高可用性。
控制
你还需要明白,SDS 并不是从虚无中创造空间,也不会将存储的概念与硬件(如硬盘、固态硬盘(SSD)或任何旨在存储信息的硬件设备)分离。这些硬件设备将始终是实际数据存储的地方。SDS 添加了一个逻辑层,允许你控制数据的存储位置和方式。它利用了最基本的组件,即应用程序编程接口(API),允许你管理和维护存储集群和逻辑卷,为其他服务器、应用程序甚至自我修复集群的监控代理提供存储容量。
市场正在向 SDS 发展
SDS 是未来,这就是存储行业的发展方向。事实上,预测未来几年,大约 70%的当前存储阵列将作为纯软件解决方案或虚拟存储设备(VSAs)提供。传统的网络附加存储(NAS)解决方案比当前的 SDS 实施要贵 30%,中档磁盘阵列甚至更昂贵。考虑到每年企业数据消耗约增长 40%,成本仅下降 25%,你可以看到为什么我们在不久的将来会向 SDS 世界迈进。
随着在公共、私人和混合云中运行的应用数量不断增长,消费者和企业数据的消耗呈指数级增长。这些数据通常是使命关键的,并且需要高水平的弹性。以下是一些这些应用的列表:
-
电子商务和在线商店
-
金融应用
-
企业资源规划
-
医疗保健
-
大数据
-
客户关系管理
当公司存储这种类型的数据(称为大数据)时,他们不仅需要对其进行归档,还需要访问它,并且尽可能地降低延迟。想象一种情景,你在医生的预约中被要求进行 X 光检查,当你到达时,他们告诉你需要等一周才能得到你的扫描结果,因为他们没有存储空间来保存你的图像。当然,这种情况不会发生,因为每家医院都有一个高效的采购流程,他们可以根据存储消耗来预测使用情况,并决定何时开始购买和安装新的硬件,但你明白我的意思。在你的 SDS 层中安装一个符合 POSIX 标准的服务器并准备好使用,速度更快、更高效。
大规模存储
许多其他公司也需要数据湖作为辅助存储,主要是为了以原始形式存储数据以进行分析、实时分析、机器学习等。SDS 非常适合这种类型的存储,主要是因为所需的维护很少,还有我们之前讨论过的经济原因。
我们主要讨论了 SDS 的经济性和可扩展性,但也很重要提到它带来的高灵活性。SDS 可用于从存档数据和存储丰富媒体到为虚拟机(VM)提供存储,作为私有云中对象存储的端点,甚至在容器中。它可以部署在前面提到的任何基础设施上。它可以在您选择的公共云中运行,在您当前的本地虚拟基础设施中运行,甚至在 Docker 容器或 Kubernetes pod 中运行。事实上,它非常灵活,以至于您甚至可以使用称为 heketi 的 RESTful 管理接口将 Kubernetes 与 GlusterFS 集成,每当您需要为您的 pod 提供持久卷时,它都会动态分配卷。
块、文件和对象存储
现在我们已经了解了为什么 SDS 是下一代工作负载的未来,是时候深入了解使用 SDS 可以实现的存储类型了。
传统的存储区域网络(SAN)和 NAS 解决方案更常用的是使用诸如 Internet 小型计算机系统接口(iSCSI)、光纤通道(FC)、以太网上的光纤通道(FCoE)、网络文件系统(NFS)和服务器消息块(SMB)/通用互联网文件系统(CIFS)等协议提供存储。然而,由于我们更多地向云端迁移,我们的存储需求也发生变化,这就是对象存储发挥作用的地方。我们将探讨对象存储是什么,以及它与块和文件存储的比较。GlusterFS 也是一种文件存储解决方案,但它具有可以进一步配置的块和对象存储功能。
以下图表显示了块、文件和对象存储:
块存储、文件存储和对象存储在客户端存储数据的方式上有很大不同,因此它们的用途也完全不同。
块存储
SAN 主要是利用块存储的地方,使用诸如 FC 或 iSCSI 之类的协议,这些协议本质上是在 FC 和 TCP/IP 上映射 SCSI 协议。
典型的 FC SAN 如下图所示:
典型的 iSCSI SAN 如下图所示:
数据存储在逻辑块地址中。在检索数据时,应用程序通常会说:“我要从地址 XXYYZZZZ 获取 X 个块。”这个过程往往非常快(不到一毫秒),使得这种类型的存储在延迟上非常低,是一种非常事务性导向的存储形式,非常适合随机访问。然而,当涉及跨多个系统共享时,它也有其缺点。这是因为块存储通常以原始形式呈现,您需要在其上方使用一个文件系统,该文件系统可以支持在不同系统之间进行多次写入而不会损坏,换句话说,是一个集群文件系统。
当涉及到高可用性或灾难恢复时,这种类型的存储也有一些缺点;因为它以原始形式呈现,存储控制器和管理器因此不知道如何使用这种存储。因此,当涉及将其数据复制到恢复点时,它只考虑块,并且一些文件系统在回收或清零块时表现糟糕,这导致未使用的块也被复制,从而导致存储利用不足。
由于其优势和低延迟,块存储非常适合结构化数据库、随机读/写操作以及存储多个虚拟机镜像,这些镜像查询磁盘时可能有数百甚至数千次的 I/O 请求。为此,集群文件系统被设计为支持来自不同主机的多次读写。
然而,由于其优点和缺点,块存储需要相当多的维护和管理——您需要关注要放在块设备上的文件系统和分区。此外,您还需要确保文件系统保持一致和安全,具有正确的权限,并且在访问它的所有系统中没有损坏。虚拟机中还有其他文件系统存储在其虚拟磁盘中,这也增加了另一层复杂性——数据可以写入虚拟机的文件系统和超级管理程序的文件系统。两个文件系统都有文件的进出,它们需要适当地清零以便在薄量复制场景中回收块,正如我们之前提到的,大多数存储阵列并不知道实际写入它们的数据。
文件存储
另一方面,文件存储或 NAS 要简单得多。您不必担心分区,也不必担心选择和格式化适合多主机环境的文件系统。
NAS 通常使用 NFS 或 SMB/CIFS 协议,主要用于将数据存储在共享文件夹中作为非结构化数据。这些协议在扩展性或满足云中面临的高媒体需求方面并不是很好,比如社交媒体服务以及每天创建/上传成千上万张图片或视频。这就是对象存储发挥作用的地方,但我们将在本章后面讨论对象存储。
文件存储,顾名思义,是在文件级别的存储上工作,当您向 NAS 发出请求时,您请求的是文件或文件系统中的一部分,而不是一系列逻辑地址。对于 NAS,这个过程是从主机(存储被挂载的地方)中抽象出来的,您的存储阵列或 SDS 负责访问后端的磁盘并检索您请求的文件。文件存储还具有本地功能,如文件锁定、用户和组集成(当我们谈论 OSS 时,主要是指 NFS),安全性和加密。
尽管 NAS 为客户端提供了抽象和简化,但它也有其缺点,因为 NAS 在很大程度上,如果不是完全地,依赖于网络。它还有一个额外的文件系统层,延迟远高于块存储。许多因素可能导致延迟或增加往返时间(RTT)。您需要考虑诸如您的 NAS 距离客户端有多少跳、TCP 窗口缩放,或者设备访问文件共享时未启用巨帧。此外,所有这些因素不仅影响延迟,而且在涉及 NAS 解决方案的吞吐量时起着关键作用,这也是文件存储最擅长的地方。
以下图表展示了文件存储共享的多功能性:
对象存储
对象存储与 NAS(文件存储)和 SAN(块存储)完全不同。尽管数据仍然通过网络访问,但数据检索的方式是独特不同的。您不会通过文件系统访问文件,而是通过使用 HTTP 方法的 RESTful API 访问。
对象存储在一个扁平的命名空间中存储,可以存储数百万甚至数十亿个对象;这是其高可扩展性的关键,因为它不受节点数量的限制,就像常规文件系统(如 XFS 和 EXT4)一样。重要的是要知道,命名空间可以有分区(通常称为存储桶),但它们不能像文件系统中的常规文件夹那样嵌套,因为命名空间是扁平的:
在将对象存储与传统存储进行比较时,经常使用自动停车与代客泊车的类比。为什么这类似呢?因为在传统文件系统中,当你存储文件时,你将其存储在一个文件夹或目录中,你需要记住文件存储的位置,就像把车停在一个停车位上一样,你需要记住你的车停在哪个号码和楼层。另一方面,使用对象存储时,当你上传数据或将文件放入一个存储桶时,你会得到一个唯一的标识符,以后可以用来检索它;你不需要记住它存储在哪里。就像代客泊车员会为你取车一样,你只需要给他们你离开车时收到的票。
继续使用代客泊车的比喻,通常你会给代客泊车提供关于他们需要为你取车的信息,不是因为他们需要,而是因为这样可以更好地识别你的车,比如车的颜色、车牌号或车型将对他们有很大帮助。对象存储的过程也是一样的。每个对象都有自己的元数据、唯一 ID 和文件本身,这些都是存储对象的一部分。
下图显示了对象存储中包含的对象的组成部分:
正如我们已经多次提到的,对象存储是通过 RESTful API 访问的。因此,理论上,任何支持 HTTP 协议的设备都可以通过 HTTP 方法(如 PUT 或 GET)访问你的对象存储桶。这听起来不安全,但事实上,大多数软件定义的对象存储都有某种类型的身份验证方法,你需要一个身份验证令牌才能检索或上传文件。使用 Linux 的 curl 工具的简单请求可能如下所示:
curl -X PUT -T "${path_to_file}" \
-H "Host: ${bucket_name}.s3.amazonaws.com" \
-H "Date: ${date}" \
-H "Content-Type: ${contentType}" \
-H "Authorization: AWS ${s3Key}:${signature}" \
https://${bucket}.s3.amazonaws.com/${file}
在这里,我们可以看到多个不同的设备如何通过 HTTP 协议连接到云中的对象存储桶:
为什么选择 GlusterFS?
现在我们了解了 SDS、存储集群以及块、文件和对象存储之间的区别,我们可以看一下企业客户选择 GlusterFS 作为他们存储需求的一些原因。
正如之前所述,GlusterFS 是一个 SDS,它是一个位于传统本地存储挂载点之上的层,允许将多个节点之间的存储空间聚合成单个存储实体或存储集群。GlusterFS 可以在私有、公共或混合云上运行。尽管其主要用途是文件存储(NAS),但几个插件允许它通过 gluster-block 插件用作块存储的后端,通过 gluster-swift 插件用作对象存储的后端。
定义 GlusterFS 的一些主要特点如下:
-
商品硬件
-
可以部署在私有、公共或混合云上
-
没有单点故障
-
可扩展性
-
异步地理复制
-
性能
-
自愈
-
灵活性
GlusterFS 特点
让我们逐个了解这些特点,以了解为什么 GlusterFS 对企业客户如此有吸引力。
商品硬件- GlusterFS 几乎可以运行在任何东西上
从树莓派上的 ARM(高级精简指令集计算机)到各种 x86 硬件,Gluster 只需要作为砖块使用的本地存储,为卷奠定基础存储。不需要专用硬件或专门的存储控制器。
在其最基本的配置中,可以使用单个格式为 XFS 的磁盘与单个节点一起使用。虽然不是最佳配置,但可以通过添加更多的砖块或更多的节点来实现进一步的增长。
GlusterFS 可以部署在私有、公共或混合云上
从容器镜像到专用于 GlusterFS 的完整 VM,对云客户来说,一个主要的吸引点是,由于 GlusterFS 仅仅是软件,它可以部署在私有、公共或混合云上。因为没有供应商,跨不同云提供商的卷是完全可能的。允许使用高可用性设置的多云提供商卷,这样当一个云提供商出现问题时,卷流量可以转移到完全不同的提供商,几乎没有或没有停机时间,这取决于配置。
无单点故障
根据卷配置,数据分布在集群中的多个节点上,消除了单点故障,因为没有head
或master
节点控制集群。
可扩展性
GlusterFS 允许通过垂直添加新的砖块或通过水平添加新的节点来平稳扩展资源。
所有这些都可以在线完成,而集群提供数据,而不会对客户的通信造成任何中断。
异步地理复制
GlusterFS 采用无单点故障概念,提供地理复制,允许数据异步复制到完全不同地理数据中心的集群中。
以下图表显示了跨多个站点的地理复制:
性能
由于数据分布在多个节点上,我们还可以同时有多个客户端访问集群。从多个来源同时访问数据的过程称为并行性,GlusterFS 通过将客户端指向不同的节点来实现增加性能。此外,通过添加砖块或节点,可以增加性能,有效地通过水平或垂直扩展。
自愈
在意外停机的情况下,剩余节点仍然可以提供流量服务。如果在其中一个节点宕机时向集群添加了新数据,则需要在节点恢复后同步这些数据。
GlusterFS 将在访问这些新文件时自动自愈,触发节点之间的自愈操作并复制丢失的数据。这对用户和客户是透明的。
灵活性
GlusterFS 可以部署在现有硬件或虚拟基础设施上,也可以作为 VM 在云上部署,或作为容器。它的部署方式并不受限制,客户可以决定最适合他们需求的方式。
远程直接内存访问(RDMA)
RDMA 允许 Gluster 服务器和 Gluster 客户端之间进行超低延迟和极高性能的网络通信。GlusterFS 可以利用 RDMA 进行高性能计算(HPC)应用和高并发工作负载。
Gluster 卷类型
通过了解 GlusterFS 的核心特性,我们现在可以定义 GlusterFS 提供的不同类型的卷。这将有助于我们在接下来的章节中深入讨论 GlusterFS 解决方案的实际设计。
GlusterFS 提供了选择最适合工作负载需求的卷类型的灵活性;例如,对于高可用性要求,我们可以使用复制卷。这种类型的卷在两个或更多节点之间复制数据,导致每个节点的精确副本。
让我们快速列出可用的卷类型,稍后我们将讨论它们各自的优缺点:
-
分布式
-
复制
-
分布式复制
-
分散
-
分布式分散
分布式
顾名思义,数据分布在卷和节点的砖块之间。这种类型的卷允许可用空间的无缝和低成本增加。主要缺点是没有数据冗余,因为文件分配在可能在同一节点或不同节点上的砖块之间。它主要用于高存储容量和并发应用。
将这种卷类型视为一堆磁盘(JBOD)或线性逻辑卷管理器(LVM),其中空间只是聚合而没有任何分割或奇偶校验。
以下图表显示了一个分布式卷:
复制
在复制卷中,数据被复制到不同节点上的砖块上。扩展复制卷需要添加相同数量的副本。例如,如果我有一个带有两个副本的卷,我想要扩展它,我需要总共四个副本。
复制卷可以与 RAID1 相比,其中数据在所有可用节点之间进行镜像。它的缺点之一是可扩展性相对有限。另一方面,它的主要特点是高可用性,因为即使在意外停机的情况下,数据也可以被访问。
对于这种类型的卷,必须实施机制来避免脑裂情况。当新数据被写入卷时,不同的节点集被允许分别处理写入时就会发生脑裂。服务器仲裁是这样一种机制,因为它允许存在一个决胜者。
以下图表显示了一个复制卷:
分布式复制
分布式复制卷类似于复制卷,其主要区别在于复制卷是分布式的。为了解释这一点,考虑有两个单独的复制卷,每个卷有 10TB 的空间。当两者都被分布时,卷最终拥有总共 20TB 的空间。
当需要高可用性和冗余性时,主要使用这种类型的卷,因为集群可以容忍节点故障。
以下图表显示了一个分布式复制卷:
分散
分散卷通过在所有可用的砖块上分割数据,并同时允许冗余性,兼具分布式和复制卷的优点。砖块应该是相同大小的,因为一旦最小的砖块满了,卷就会暂停所有写入。例如,想象一个分散卷,比如 RAID 5 或 6,数据被分割并创建奇偶校验,允许从奇偶校验中重建数据。虽然这个类比帮助我们理解这种类型的卷,但实际过程完全不同,因为它使用了数据被分成片段的纠错码。分散卷提供了性能、空间和高可用性的正确平衡。
分布式分散
在分布式分散卷中,数据分布在分散类型的卷中。冗余性是在分散卷级别提供的,具有与分布式复制卷相似的优势。
想象一下在两个 RAID 5 阵列之上的 JBOD 上存储数据——扩展这种类型的卷需要额外的分散卷。虽然不一定是相同的大小,但理想情况下,它应该保持相同的特性以避免复杂性。
对高度冗余存储的需求
随着应用程序可用空间的增加,对存储的需求也在增加。应用程序可能需要随时访问它们的信息,而不会出现任何可能导致整个业务连续性受到威胁的中断。没有公司希望出现停机,更不用说中央基础设施的中断导致损失、客户无法服务和用户因错误决策而无法登录到他们的帐户。
让我们考虑将数据存储在传统的单片存储阵列上——这样做可能会带来重大风险,因为一切都集中在一个地方。一个包含公司所有信息的大型存储阵列意味着操作风险,因为该阵列容易出现故障。每一种硬件——无论多么好——都会在某个时候出现故障。
单体阵列倾向于通过在磁盘级别使用传统的 RAID 方法来提供某种形式的冗余来处理故障。虽然这对为数百用户提供服务的小型本地存储来说是不错的,但是当我们达到 PB 级别并且存储空间和活跃并发用户大幅增加时,这可能不是一个好主意。在特定情况下,RAID 恢复可能导致整个存储系统崩溃或性能下降到应用程序无法正常工作的程度。此外,随着磁盘容量的增加和单个磁盘性能在过去几年内保持不变,现在恢复单个磁盘需要更长的时间;重建 1TB 磁盘不同于重建 10TB 磁盘。
存储集群,如 GlusterFS,通过提供最适合工作负载的方法来处理冗余。例如,当使用复制卷时,数据从一个节点镜像到另一个节点。如果一个节点宕机,那么流量会无缝地转向剩余的节点,对用户来说完全透明。一旦问题节点得到处理,它可以迅速重新加入集群,在那里它将进行数据的自我修复。与传统存储相比,存储集群通过将数据分发到集群的多个成员来消除单点故障。
提高可用性意味着我们可以达到应用程序服务级别协议并保持所需的正常运行时间。
灾难恢复
逃不过灾难——无论是自然灾害还是人为错误。重要的是我们为此做好了多少准备,以及我们能多快多高效地恢复。
实施灾难恢复协议对业务连续性至关重要。在继续之前,有两个术语我们需要了解:恢复时间目标(RTO)和恢复点目标(RPO)。让我们快速浏览一下每个术语。RTO 是从故障或事件导致中断开始恢复所需的时间。简单地说,它指的是我们可以多快将应用程序恢复正常。另一方面,RPO 指的是数据可以在不影响业务连续性的情况下回溯多远的时间,也就是说,你可以丢失多少数据。
RPO 的概念看起来像这样:
RTO
如前所述,这是在故障后恢复功能所需的时间。根据解决方案的复杂性,RTO 可能需要相当长的时间。
根据业务需求,RTO 可能只有几个小时。这就是设计高度冗余解决方案的作用所在——通过减少恢复所需的时间。
RPO
这是可以丢失的数据量,仍然可以返回到恢复点的时间,换句话说,这是恢复点被采取的频率;在备份的情况下,备份的频率(可以是每小时、每天或每周),以及在存储集群的情况下,更改的复制频率。
需要考虑的一点是变化可以被复制的速度,因为我们希望变化几乎可以立即被复制;然而,由于带宽限制,实时复制大多数时候是不可能的。
最后,需要考虑的一个重要因素是数据如何被复制。一般来说,有两种类型的复制:同步和异步。
同步复制
同步复制意味着数据在写入后立即复制。这对于最小化 RPO 是有用的,因为从一个节点到另一个节点的数据之间没有等待或漂移。GlusterFS 复制的卷提供了这种复制。带宽也应该被考虑,因为更改需要立即提交。
异步复制
异步复制意味着数据在时间片段内复制,例如,每 10 分钟。在设置过程中,根据业务需求和可用带宽等多个因素选择 RPO。
带宽是主要考虑因素;这是因为根据更改的大小,实时复制可能无法适应 RPO 窗口,需要更长的复制时间,直接影响 RPO 时间。如果有无限带宽可用,应选择同步复制。
回顾过去,作为 IT 架构师,我们花费了大量时间试图找出如何使我们的系统更具弹性。事实上,成功降低 RTO 和 RPO 时间可能标志着部分考虑周到的解决方案和完全设计良好的设计之间的差异。
高性能的需求
随着越来越多的用户访问相同的资源,响应时间变慢,应用程序开始处理时间变长。传统存储的性能在过去几年没有改变——单个 HDD 的吞吐量约为 150 MB/s,响应时间为几毫秒。随着闪存介质和诸如非易失性内存扩展(NVMe)之类的协议的引入,单个 SSD 可以轻松实现每秒几十亿字节的吞吐量和亚毫秒的响应时间;SDS 可以利用这些新技术提供增加的性能并显著减少响应时间。
企业存储被设计为处理数百个客户的多个并发请求,这些客户都希望尽快获取他们的数据,但当性能限制达到时,传统的单体存储开始变慢,导致应用程序失败,因为请求未能及时完成。提高这种类型存储的性能代价高,并且在大多数情况下,无法在存储仍在提供数据的情况下完成。
增加性能的需求来自存储服务器负载的增加;随着数据消费的激增,用户存储的信息量比以往更多,并且需要比以前更快地获取。
应用程序还需要尽快将数据传递给它们;例如,考虑股票市场,数千用户每秒多次请求数据。与此同时,另外一千用户不断地写入新数据。如果单个交易未能及时提交,人们将无法根据正确的信息做出买卖股票的决定。
前面的问题是架构师在设计能够提供应用所需性能的解决方案时必须面对的问题。花费适当的时间正确规划存储解决方案可以使整个流程更加顺畅,减少设计和实施之间的来回。
诸如 GlusterFS 之类的存储系统可以同时为数千个并发用户提供服务,而不会出现性能显著下降,因为数据分布在集群中的多个节点上。这种方法比访问单个存储位置(例如传统阵列)要好得多。
并行 I/O
I/O 是指向存储系统请求和写入数据的过程。该过程通过 I/O 流进行,数据一次请求一个块、文件或对象。
并行 I/O 是指多个流同时在同一存储系统上执行操作的过程。这增加了性能并减少了访问时间,因为各种文件或块同时读取或写入。
相比之下,串行 I/O 是执行单个 I/O 流的过程,这可能会导致性能降低和延迟或访问时间增加。
存储集群(如 GlusterFS)利用并行 I/O 的优势,因为数据分布在多个节点上,允许多个客户端同时访问数据,而不会出现延迟或吞吐量下降。
总结
在本章中,我们深入了解了集群的核心概念,并将其定义为一组计算机节点,它们一起处理相同类型的工作负载。计算集群的主要功能是执行运行 CPU 密集型工作负载的任务,旨在减少处理时间。存储集群的功能是将可用的存储资源聚合到一个单一的存储空间中,简化管理,并允许您有效地达到 PB 级别或超过 1PB 的可用空间。然后,我们探讨了 SDS 如何改变数据存储方式,以及 GlusterFS 是领导这一变革的项目之一。SDS 允许简化存储资源的管理,同时添加了传统的单片式存储阵列无法实现的功能。
为了进一步了解应用程序与存储的交互方式,我们定义了块、文件和对象存储之间的核心区别。主要地,块存储处理存储设备中的逻辑数据块,文件存储通过从存储空间读取或写入实际文件来工作,对象存储为每个对象提供元数据以进行进一步交互。有了这些不同存储交互方式的概念,我们继续指出了使 GlusterFS 对企业客户具有吸引力的特征,以及这些特性如何与 SDS 的定义联系起来。
最后,我们深入探讨了为什么高可用性和高性能对于每个存储设计都是必不可少的主要原因,以及如何执行并行或串行 I/O 会影响应用程序性能。
在下一章中,我们将深入探讨架构 GlusterFS 存储集群的实际过程。
问题
-
我如何优化我的存储性能?
-
GlusterFS 更适合哪种工作负载?
-
哪些云提供商提供对象存储?
-
GlusterFS 提供哪些类型的存储?
-
红帽是否拥有 GlusterFS?
-
我使用 GlusterFS 需要付费吗?
-
Gluster 是否提供灾难恢复或复制?
进一步阅读
-
- Ceph Cookbook – Second Edition * by Vikhyat Umrao and Michael Hackett:
prod.packtpub.com/in/virtualization-and-cloud/ceph-cookbook-second-edition
- Ceph Cookbook – Second Edition * by Vikhyat Umrao and Michael Hackett:
-
- Mastering Ceph * by Nick Fisk:
prod.packtpub.com/in/big-data-and-business-intelligence/mastering-ceph
- Mastering Ceph * by Nick Fisk:
-
- Learning Ceph – Second Edition * by Anthony D'Atri and Vaibhav Bhembre:
prod.packtpub.com/in/virtualization-and-cloud/learning-ceph-second-edition
- Learning Ceph – Second Edition * by Anthony D'Atri and Vaibhav Bhembre:
第三章:设计存储集群
软件定义的存储已经改变了我们存储数据的方式;随着功能的增加,设计正确解决方案时需要考虑的要求也在增加。在设计存储集群时需要考虑大量的变量。
本章探讨了使用 GlusterFS 及其各种组件实现软件定义存储解决方案的不同设计方面。
在本章中,我们将涵盖以下主题:
-
GlusterFS 计算需求
-
选择合适的存储大小
-
定义性能需求
-
决定高可用性的正确方法
-
确定工作负载如何联系在一起
技术要求
对于本章,我们将使用以下网址上提供的 GlusterFS 文档:
GlusterFS 计算需求
与任何软件一样,GlusterFS 有一组由开发人员定义的要求,以确保其按预期工作。文档中描述的实际要求相对较低,几乎每台在过去 10 年中销售的计算机都可以运行 GlusterFS。这可能不是最佳性能水平,但它仍然显示了在混合条件下运行的灵活性。
对于计算需求,我们主要有以下两个资源需要考虑在设计使用 GlusterFS 的解决方案时:
-
内存
-
CPU
内存
对于内存,选择相对简单——尽可能多地使用。不幸的是,没有无限的内存,但尽可能多地使用内存这一说法是非常真实的,因为 GlusterFS 使用 RAM 作为每个节点的读取缓存,同时 Linux 内核使用内存作为预读缓存,以加快对频繁访问的文件的读取速度。
根据砖块布局和选择的文件系统,可用内存在读取性能中起着重要作用。以使用高级 ZFS 文件系统的砖块为例,它使用 RAM 作为其自适应替换缓存(ARC)。这增加了一个额外的缓存层,位于高速 RAM 上。缺点是它会消耗尽可能多的内存,因此选择提供大量内存的服务器会有很大帮助。
GlusterFS 并不需要大量的内存——每个节点拥有 32GB 或更多的内存可以确保缓存足够大,以分配频繁访问的文件,如果通过在每个节点上添加更多的砖块来扩展集群的规模,那么应该考虑增加更多的内存,以增加缓存的可用内存。
为什么缓存很重要?
考虑以下:即使是旧的 DDR2 内存技术也可以提供 GBps 级的吞吐量和几纳秒的延迟。另一方面,从常规旋转介质(硬盘驱动器)读取的吞吐量在大多数情况下峰值为 150MBps,延迟在几百毫秒。
从缓存中读取总是比访问磁盘快——等待磁盘移动其磁头,找到请求的数据块,然后将其发送回控制器并传递到应用程序。
需要记住的一件事是,缓存需要先预热;这是允许系统确定哪些文件经常被访问,然后将数据移动到缓存中的过程。在预热期间,请求会变慢,因为它们首先必须从磁盘中获取。
CPU
任何软件都需要 CPU 周期,GlusterFS 也不例外。CPU 要求相对较低,并且取决于所使用的卷的类型,例如,复制卷比分散卷需要更少的 CPU。
CPU 需求也受到砖块使用的文件系统类型和其具有的功能的影响。回到 ZFS 的例子,如果启用了压缩,这会增加 CPU 的负载,而没有足够的 CPU 资源会大大降低性能。
对于简单的存储服务器和在 brick 级别没有高级功能的情况,任何四个 CPU 或更多的都足够了。当启用文件系统功能,比如压缩时,需要八个或更多的 CPU 以获得最佳性能。此外,更多的 CPU 允许对集群进行更多并发 I/O。这在为高性能计算(HPC)应用程序设计存储集群时至关重要,其中成千上万的用户同时进行 I/O 操作。
使用以下规则作为一般的经验法则:
-
对于高并发工作负载,选择更多的 CPU,超过八个 CPU,取决于并发级别
-
对于低性能要求和成本效益的解决方案,选择较少数量的 CPU,例如四个 CPU
云考虑
许多云提供商为其虚拟机大小提供了一组固定的资源,不允许自定义 vCPU 到 RAM 的比例。找到合适的平衡取决于哪种 VM 大小提供了必要的资源。
GlusterFS 在云中的概念将在接下来的章节中进一步探讨。然而,为了对这个概念有一个概述,让我们使用微软的 Azure 提供的 VM 大小来探索一下。
Azure VM 系列从通用计算到特定工作负载,如 GPU。对于 GlusterFS,我们真的很喜欢 L 系列 VM,这些 VM 针对存储工作负载进行了优化。这个 VM 系列具有良好的 vCPU 到 RAM 比例,并提供了任何系列中最高的存储性能与成本比。
这个一般的想法可以应用到其他云供应商。应选择提供优秀和具有成本效益的 vCPU 到 RAM 比例的 VM 大小。
你需要多少空间?
如果我们可以使用我们需要的空间,那不是很好吗?实际上,存储是有成本的,无限的存储是不存在的。
在确定可用存储空间时,必须考虑以下因素:
-
GlusterFS 卷类型
-
应用程序需要的空间
-
预期增长
GlusterFS 卷类型
让我们从一些技术考虑开始。每个 GlusterFS 卷在可用空间方面都有特点。根据卷类型,最终可用空间可能比您最初计算的要少。我们将探讨我们在第二章中描述的每种卷类型的空间考虑,定义 GlusterFS 存储。
分布式
这种卷类型相当简单。每个节点的可用空间之和是全局命名空间(GlusterFS 卷挂载的另一个名称)上的总空间。
例如,一个 50TB 卷的请求,砖块所需的空间恰好是 50TB。这可以分成每个 10TB 的五个节点或每个 25TB 的两个节点。
复制
对于复制卷,一半的可用原始砖块空间用于数据的镜像或复制。这意味着在确定这种类型的卷的大小时,您需要至少将所请求的存储容量加倍。这取决于卷的具体配置。一个经验法则是可用容量是砖块上总空间的一半。
例如,如果请求的是 50TB 卷,节点配置应该至少在两个节点之间有 100TB 的砖块空间。
分散
分散卷的大小更难确定,因为它们的功能类似于 RAID 5,其中数据分布在节点上,并且一个节点的容量用于奇偶校验。这取决于卷的配置,但您可以预期随着节点数的增加,空间利用效率会提高。
进一步解释,对于一个 50 TB 的卷的请求,可以在 6 个节点上配置,每个节点有 10 TB。请注意额外考虑了一个节点。选择 5 个每个 10 TB 的节点会导致只有 40 TB 的卷,这不符合请求的大小。
应用程序所需的空间
每个应用程序都有自己的一套要求,存储要求和其他要求一样重要。
提供媒体文件需要的资源比用户较少且媒体文件不多的网站要多得多。准确了解存储系统的预期使用方式可以正确地确定解决方案的大小,并防止存储估算不足以满足最初需求的情况发生。
确保你了解应用程序开发人员推荐的最低要求,并了解它如何与存储交互,因为这有助于避免头疼的情况。
预期增长
作为架构师,你的工作是提出正确的问题。在涉及存储时,确保增长率或变化率被考虑在内。
考虑到数据增长是无法避免的,提前思考可以避免复杂的情况,即没有足够的空间,因此为未来的利用留出一些余地是一个好的做法。允许 10%或更多的空间应该是一个很好的起点,所以如果请求 50 TB 的空间,那么在解决方案中再增加 5 TB 的空间。
选择最具成本效益的路线。虽然 GlusterFS 允许无缝扩展,但尽量避免使用这个功能作为简单的解决方案,并确保从一开始就定义了正确的大小,并考虑了未来的增长。
性能考虑
性能差的应用程序可能比根本无法工作的应用程序更糟糕。让某些东西一半的时间工作是非常令人沮丧和对任何企业来说成本高昂的。
作为架构师,你需要设计满足规格或更好的解决方案,以避免由于性能不佳而导致问题的情况。
首先要做的是定义性能要求是什么。大多数情况下,应用程序开发人员在他们的文档中提到了性能要求。不满足这些最低要求意味着应用程序要么根本无法工作,要么几乎无法工作。这两种情况都是不可接受的。
设计面向性能的解决方案时需要注意以下几点:
-
吞吐量
-
延迟
-
IOPS
-
I/O 大小
吞吐量
吞吐量是在一定时间内传输的数据量的函数,通常以兆字节每秒(MBps)描述。这意味着每秒从存储系统发送或接收 X 数量的数据。
根据工作负载的不同,最高吞吐量可能无法实现,因为应用程序无法执行足够大或足够快的 I/O 操作。这里没有硬性建议的数字。尽量追求最高可能的吞吐量,并确保存储集群能够维持所需的并发级别所需的传输速率。
延迟
延迟是至关重要的,需要额外小心,因为一些应用程序对高延迟或响应时间非常敏感。
延迟是衡量 I/O 操作完成所需的时间的指标,通常以毫秒为单位(1 秒等于 1000 毫秒)。高延迟或响应时间会导致应用程序响应时间变长,甚至完全停止工作。
力求达到最低的延迟。这是一个情况,其中获得最低可能的数字总是最好的方法。在延迟方面,没有不够的情况,或者在这种情况下,响应时间不够短。考虑你使用的存储介质类型。传统的硬盘驱动器的响应时间(或寻道时间)在几百毫秒范围内,而新型固态硬盘可以达到亚毫秒甚至微秒的水平。
IOPS
每秒输入/输出操作是一定时间内(在本例中是秒)的一定数量的操作的函数。这是衡量每秒可以执行多少操作的指标,许多应用程序提供了关于 IOPS 的最低要求。
大多数应用程序提供了其所需的最低 IOPS 要求。确保满足这些要求,否则应用程序可能无法按预期运行。
在设计存储解决方案时,确保 IOPS 被视为在制定大小决策时的主要决定因素。
I/O 大小
I/O 大小是每个操作执行的数据量。这取决于工作负载类型,因为每个应用与存储系统的交互方式不同。I/O 大小直接影响了之前提到的性能方面。
较小的 I/O 会导致较低的吞吐量,但如果足够快,会导致更高的 IOPS 和较低的延迟。另一方面,较大的 I/O 提供更高的吞吐量,但通常会产生较低的 IOPS,因为在相同的时间内执行较少的操作。
关于 I/O 大小没有明确的建议。在理想的、不现实的世界中,I/O 足够大且足够快,这会导致高吞吐量和高 IOPS。但在现实中,要么是一个,要么是另一个。小的 I/O 在吞吐量方面较慢,但完成得足够快,因此 IOPS 似乎更高。而大的 I/O 则相反,吞吐量更高,但由于完成时间较长,IOPS 下降。
GlusterFS 性能
在设计 GlusterFS 存储集群时,需要考虑以下方面,以便提高性能:
-
卷类型
-
砖块布局
-
节点数量
-
调整参数
卷类型
所选的卷以不同的方式影响性能,因为 GlusterFS 为每种类型分配数据方式不同。
例如,复制卷在节点之间镜像数据,而分散卷试图最大化节点使用并并行使用它们。
如果性能是分散或分布式卷的主要目标,请考虑分布卷不提供冗余,而分散卷则以性能降级为代价提供冗余。
砖块布局
将所有磁盘都放在一个大的砖块中的节点与将磁盘分组在较小数量的砖块中的节点性能表现不同。砖块布局是性能的最大贡献因素,因为这直接决定了磁盘的使用方式。
如果所有磁盘最终都放在一个砖块中,性能会受到影响。通常,更多的砖块和较少的磁盘会带来更好的性能和更低的延迟。
考虑为构成砖块的磁盘配置软件 RAID0。例如,您可以有 10 个可用的磁盘,并且为了简单起见,可以在单个砖块上配置所有 10 个磁盘的 RAID0。或者,您可以选择更高效的方式,配置五个砖块,其中每个砖块由 RAID0 中的两个磁盘组成。
这也可以实现更平稳的增长,因为添加更多砖块并不需要添加大量的磁盘。您应该以更少的磁盘组成更多的砖块为目标。
在下图中,我们可以看到每个砖块由两个不同的磁盘组成:
节点数量
增加集群中节点的数量可以实现更高的并发性。虽然性能增益可能不是线性的,但增加节点可以允许更多的用户和应用程序访问卷。
目标是拥有足够的节点以实现可用空间和并发性的平衡。这里没有固定的数量,但作为架构师,您的工作是通过测试来定义特定解决方案的正确节点数量。在 POC 阶段,使用较少的节点进行测试,并检查性能是否可接受。
调整参数
文件系统可调参数,如块大小,可以发挥重要作用,目标是使工作负载 I/O 大小、GlusterFS 卷块大小和文件系统块大小相匹配。
通常,4K 是最常用的块大小,适用于一般工作负载。对于大量的小文件,选择较小的块大小。对于大文件,选择较大的块大小,例如 1M。
实现高可用性的最佳方法
使用 GlusterFS,高可用性可以通过卷配置实现;决定如何实现取决于应用程序需求、可用空间和所需性能。
由于 GlusterFS 处理高可用性,因此无需在存储单元级别配置任何形式的冗余。特别是在云实例和虚拟机中,没有可以出问题的物理磁盘。对于物理安装,通过为本地磁盘配置 RAID5 或 RAID6 来增加额外的冗余层总是更好,以实现性能和弹性的平衡。现在,让我们继续使用云部署。
使用 GlusterFS 时,只有两种卷类型提供高可用性:复制和分散。复制卷相对较简单,因为数据只是从一个节点复制到另一个节点。这提供了较低的性能,但配置、部署和维护都相对容易。
复制
当不需要极端性能时,请选择复制卷。根据卷应该容忍的节点或存储单元数量选择副本的数量。请考虑使用更高的副本数量将减少可用空间的数量,但会增加卷的可用性。
以下示例显示,丢失复制卷中的一个节点不会中断卷的运行:
分散
分散卷在高可用性和性能之间提供了良好的平衡;当两者都是要求时,这应该是首选卷。配置分散卷是一个更复杂的过程,因为冗余是像 RAID5 设置一样处理的,其中一个节点被用作奇偶校验。冗余值可以在卷创建时选择,这允许更大的灵活性。
在下图中,您可以看到丢失一个节点不会中断卷的运行:
当存在特定需求时,请计划高可用性。请记住,卷类型可以混合使用。例如,分布式复制卷将具有良好的可用空间和冗余性的混合。
地理复制
地理复制允许通过本地网络或互联网在不同站点之间进行数据的异步复制。这通过在不同地理位置拥有数据的副本来提供高可用性,并确保在发生故障时进行灾难恢复。
当存在需要额外冗余层的特定用例时,考虑使用地理复制路线。请记住,这是异步复制,因此在发生灾难时,请考虑前几章中解释的 RPO 和 RTO 时间。
以下图表让您对地理复制的工作原理有一个大致的了解——站点 A通过广域网复制到站点 B:
工作负载如何定义需求
将视频文件传送到流媒体服务器与托管大型数据库不同。I/O 是以完全不同的方式进行的,了解工作负载与存储系统的交互方式对成功地确定和设计强大的存储解决方案至关重要。
文档
当尝试确定存储需求时,应用程序文档是您的最佳伙伴。当应用程序已经存在时,询问管理员软件对性能的期望以及当不满足最低要求时的反应。
系统工具
使用诸如iostat
之类的工具可以很好地了解应用程序与存储的交互方式,例如通过使用以下命令:
iostat -dxctm 1
前面的代码显示了每个块设备的使用情况,areq-sz
列(以前称为avgrq-sz
)显示了以千字节为单位的平均请求大小,这是了解应用程序通常使用的 I/O 大小的良好起点。
输出看起来类似于以下截图:
在前面的图像中,我们可以欣赏到块设备及其相应的性能。
文件类型和大小
举例来说,为媒体流服务器设计存储解决方案需要使用大块大小,因为媒体文件往往比小文本文件大。如果你为砖块使用更大的块大小,GlusterFS 卷不仅会更有效地利用空间,还将允许更快的操作,因为事务大小与文件大小匹配。
另一方面,传感器日志记录的存储服务器通常会创建包含文本的大量小文件,因此需要较小的块大小来匹配正在创建的文件的大小。使用较小的块大小可以避免为仅有 1K 大小的文件分配整个块(例如 4K)。
提出正确的问题
作为架构师,您的目标是确保工作负载非常清晰。存储服务器的预期用途定义了需要分配多少资源。未能这样做可能导致资源浪费,这反过来意味着浪费金钱,或者在最坏的情况下,可能导致解决方案无法按规格执行,从而导致应用程序失败和用户无法工作。
从第一章中记住,设计方法论简介:提出正确的问题。在调整存储解决方案时,您可以问以下问题:
-
当前实施消耗了多少空间(如果已经有一个)?
-
应用程序的性能要求是什么?
-
有多少用户与应用程序交互?
-
是否需要高可用性?
-
应用程序如何存储其数据?
-
它是否创建大文件并向其附加数据?
-
它是否创建大量小文件?
这些问题的可能答案可能是以下内容:
-
目前,该应用程序消耗了 20TB,但我们预计每个月增长 5%,并稳定在 80TB。
-
该应用程序需要至少 100MB/s 的吞吐量和不超过 10ms 的延迟。
-
目前,大约有 300 个用户可以访问该应用程序;同时,我们已经看到了 150 个用户的高峰,但我们预计用户数量将显著增加。
-
我们可以忍受一段时间无法访问存储,但我们确实需要能够相对快速地从故障中恢复,并且可能在外部有数据的副本。
-
应用程序主要将其信息保存在小文件中。
-
它不附加数据,如果需要更多空间,它只是创建更多小文件。
-
是的,我们已经看到创建了数千个大小不超过 4KB 的文件。
从前面的例子中,您可以推断出该应用程序创建了大量小文件,并且可以容忍一段时间的停机,但需要外部复制以实现平稳的灾难恢复。性能要求似乎相对较高,因此我们可以选择启用地理复制的分散或分布式卷。
总结
设计存储解决方案的过程需要了解许多变量。在本章中,我们确定了决定需要多少空间取决于 GlusterFS 卷类型、应用程序要求和数据利用率的预估增长。
根据卷类型,可用空间会受到影响,分布式卷会聚合所有可用空间,使其成为最节省空间的卷,而复制卷使用一半可用原始空间进行镜像。
应用程序和用户群决定了需要多少空间。这是因为,根据所提供的数据类型,存储需求会发生变化。提前考虑并规划存储增长可以避免资源耗尽的潜在风险,并且在大小确定时至少留出 10%的缓冲区应该适应大多数情况。
通过性能要求,我们定义了吞吐量、延迟、IOPS 和 I/O 大小的概念,以及它们如何相互作用。我们定义了在为 GlusterFS 配置最佳性能时涉及的变量,每个卷都具有其性能特征,以及在尝试优化 GlusterFS 卷时砖块布局起着重要作用。
我们还定义了高可用性要求如何影响大小,以及每个卷提供不同级别的 HA。当需要灾难恢复时,GlusterFS 地理复制通过将数据复制到另一个物理区域提供所需的可用性级别,从而在发生灾难时平稳恢复服务。
最后,我们讨论了工作负载如何定义解决方案的设计,以及如何使用工具来验证应用程序与存储的交互方式,从而正确配置存储集群。我们还了解了文件类型和大小如何定义性能行为和空间利用率,以及如何提出正确的问题可以更好地理解工作负载,从而实现更高效和优化的解决方案。
最重要的是要始终询问应用程序和工作负载如何与其资源交互。这可以实现最高效的设计。
在下一章中,我们将介绍 GlusterFS 所需的实际配置。
问题
-
GlusterFS 的计算要求是什么?
-
GlusterFS 如何使用 RAM?
-
什么是缓存?
-
并发性如何影响 CPU 大小?
-
GlusterFS 卷如何影响可用空间?
-
应用程序需要多少空间?
-
什么是预期增长?
-
什么是吞吐量、延迟 IOPS 和 I/O 大小?
-
什么是砖块布局?
-
什么是地理复制?
进一步阅读
-
架构数据密集型应用 by Anuj Kumar
-
Microsoft Azure 存储基础 by Chukri Soueidi
-
Ritesh Modi 的 Azure 架构师
第四章:在云基础设施上使用 GlusterFS
在对 GlusterFS 的核心概念有了很好的理解之后,我们现在可以深入到存储集群的安装、配置和优化中。
我们将在 Azure 上使用云提供商搭建一个三节点集群的 GlusterFS,作为本示例的云提供商。然而,这些概念也可以应用于其他云提供商。
在本章中,我们将涵盖以下主题:
-
配置 GlusterFS 后端存储
-
安装和配置 GlusterFS
-
设置卷
-
优化性能
技术要求
本章的技术资源列表如下:
- Azure 虚拟机(VM)大小的详细视图:
docs.microsoft.com/en-us/azure/virtual-machines/linux/sizes-storage
- Azure 磁盘大小和类型的详细视图:
azure.microsoft.com/en-us/pricing/details/managed-disks/
- ZFS on Linux 项目的主页:
github.com/zfsonlinux/zfs/wiki/RHEL-and-CentOS
- CentOS 上的 GlusterFS 安装指南:
wiki.centos.org/HowTos/GlusterFSonCentOS
- GlusterFS 在 Gluster 网站上的快速入门指南:
docs.gluster.org/en/latest/Quick-Start-Guide/Quickstart/
- 在管理员指南上设置 GlusterFS 卷:
docs.gluster.org/en/latest/Administrator%20Guide/Setting%20Up%20Volumes/
- GlusterFS 调整卷以获得更好的性能:
docs.gluster.org/en/latest/Administrator%20Guide/Managing%20Volumes/#tuning-options
设置用于后端存储的砖
以下是我们将使用的组件列表:
-
Azure L4s VM,具有 4 个 vCPU 和 32GB 的 RAM
-
每个 VM 有四个 S10 128GB 的磁盘
-
CentOS 7.5
-
ZFS on Linux 作为砖的文件系统
-
一个由四个磁盘组成的单个 RAID 0 组
-
GlusterFS 4.1
Azure 部署
在深入讨论如何配置砖之前,我们首先需要在 Azure 中部署节点。在本示例中,我们使用存储优化的 VM 系列,即 L 系列。值得一提的是,Azure 提供了一个 30 天的免费试用期,可以用于在承诺任何部署之前进行测试。
在 Azure 中,性能在几个级别上进行定义。第一级是 VM 限制,即 VM 允许的最大性能。L 系列家族提供了价格与性能的正确平衡,因为这些 VM 经过优化,提供了更高的 IOPS 和吞吐量,而不是提供高计算或内存资源。性能定义的第二级是通过连接到 VM 的磁盘。在本示例中,我们将使用标准的硬盘驱动器作为一种经济实惠的解决方案。如果需要更高的性能,磁盘可以随时迁移到高级的固态硬盘存储。
本示例的确切 VM 大小将是 L4s,提供四个 vCPU 和 32GB 的 RAM,足够用于一般用途的小型存储集群。在正确配置时,最大可达 125MB/s 和 5k IOPS,仍然保持可观的性能。
最近推出了一代新的面向存储优化的 VM,提供了一个本地可访问的 2 TB NVMe SSD。此外,它提供了增加的核心数和内存,使这些新的 VM 成为 GlusterFS 设置与Z 文件系统(ZFS)的理想选择。新的 L8s_v2 VM 可以用于这个特定的设置,产品页面上可以看到大小和规格(docs.microsoft.com/en-us/azure/virtual-machines/linux/sizes-storage#lsv2-series
)。
以下截图显示了可用性集、当前故障域和当前更新域的设置:
在 Azure 中部署 GlusterFS 设置时,请确保每个节点都落在不同的更新和故障域上。这是通过使用可用性集来实现的(参考前面的截图)。这样做可以确保如果平台重新启动一个节点,其他节点仍然保持运行并提供数据。
最后,对于 Azure 设置,我们需要每个节点512 GB,总共 1.5 TB 原始空间,或 1 TB 可用空间。实现这一点的最具成本效益的方法是使用单个S20 512 GB磁盘,因为每月每千兆字节的价格约为$0.04。选择单个磁盘会影响性能,因为单个标准磁盘只提供最大 500 IOPS 和 60 MB/s。考虑性能并接受我们在成本方面会失去一些效率的事实,我们将使用四个S10 128 GB 磁盘组成单个 RAID0 组。S10磁盘每月每千兆字节的价格为$0.05,而S20磁盘每月每千兆字节的价格为$0.04。您可以参考以下表格,其中计算是基于托管磁盘的成本除以其相应的大小进行的:
确保所有三个节点都部署在同一区域和相同的资源组中,以保持一致性。
ZFS 作为砖的后端
我们在第三章中讨论了 ZFS,构建存储集群。ZFS 是由 Sun Microsystems 开发的文件系统,后来被 Oracle 收购。该项目后来被开源,并移植到了 Linux。尽管该项目仍处于测试阶段,但大多数功能都运行良好,大部分问题已经排除,该项目现在专注于添加新功能。
ZFS 是一个软件层,将磁盘管理、逻辑卷和文件系统合而为一。诸如压缩、自适应替换缓存(ARC)、重复数据删除和快照等高级功能使其成为与 GlusterFS 作为砖的后端一起使用的理想选择。
安装 ZFS
让我们从安装 ZFS 开始;有一些依赖项,比如动态内核模块(DKMS),它们存放在 EPEL 存储库中。
请注意,这里运行的大多数命令都假定是以 root 身份运行的;可以在每个命令之前加上sudo
作为非 root 帐户运行命令。
要安装所需的组件,我们可以使用以下命令:
yum install -y epel-release
yum install -y http://download.zfsonlinux.org/epel/zfs-release.el7_5.noarch.rpm
接下来,我们将使用以下命令:
yum install -y zfs
以下命令用于启用 ZFS 组件:
systemctl enable zfs.target
systemctl enable --now zfs-import-scan.service
配置 zpools
安装并启用 ZFS 后,我们现在可以创建 zpools。Zpool 是在 ZFS 中创建的卷的名称。
由于我们将使用由四个磁盘组成的单个 RAID 0 组,我们可以创建一个名为brick1
的 zpool;这需要在所有三个节点上完成。此外,让我们创建一个名为bricks
的目录,位于根目录(/
)下;这个目录包含了一个带有砖名称的目录下的砖。执行此操作所需的命令如下:
mkdir -p /bricks/brick1
这将创建目录树,如下所示:
zpool create brick1 /dev/disk/by-id/scsi-360022480f0da979b536cde32a4a17406 \
/dev/disk/by-id/scsi-360022480fb9d18bbdfb9175fd3e0bbf2 \
/dev/disk/by-id/scsi-360022480fb9d18bbdfb9175fd3e0bae4 \
/dev/disk/by-id/scsi-360022480fb9d18bbdfb9175fd3e049f2
进一步解释该命令,brick1
是 zpool 的名称。然后,我们指示磁盘的路径。在本例中,我们使用磁盘的 ID,因为这样可以避免磁盘更改顺序时出现问题。虽然 ZFS 不受磁盘顺序不同的影响,但最好通过使用永远不会更改的 ID 来避免问题。
ZFS 可以使用整个磁盘,因为它会自动创建所需的分区。
创建zpool
实例后,我们可以使用zpool status
命令检查是否已正确完成:
让我们启用压缩并将池的挂载点更改为先前创建的目录。要执行此操作,请运行以下命令:
zfs set compression=lz4 brick1
您还需要运行以下命令:
zfs set mountpoint=/bricks/brick1 brick1
第一个命令使用lz4
算法启用压缩,其 CPU 开销较低。第二个命令更改了 zpool 的挂载点。在更改设置时,请确保使用正确的池名称。
完成此操作后,我们应该在df
命令中看到 ZFS 卷已挂载在/bricks/brick1
下:
我们需要在最近添加的挂载点上创建一个目录以用作 brick;共识是使用卷的名称。在这种情况下,我们将卷命名为gvol1
,然后简单地创建目录:
mkdir -p /bricks/brick1/gvol1
这需要在所有节点上完成。
将 ZFS 缓存添加到池中(可选)
在 Azure 中,每个 VM 都有一个临时资源驱动器。这个临时资源驱动器的性能比添加到它的数据磁盘要高得多。这个驱动器是临时的,这意味着一旦 VM 被取消分配,数据就会被擦除;由于不需要在重新启动时持续保留数据,这应该非常适合作为读取缓存驱动器。
由于驱动器在每次stop/deallocate/start
周期中都会被擦除,因此我们需要调整一些 ZFS 的单元文件,以允许在每次重新启动时添加磁盘。驱动器将始终是/dev/sdb
,并且由于不需要在其上创建分区,因此我们可以简单地告诉 ZFS 在系统引导时每次将其添加为新磁盘。
可以通过编辑位于/usr/lib/systemd/system/zfs-mount.service
下的zfs-mount.service
的systemd
单元来实现。这种方法的问题在于 ZFS 更新将覆盖对先前单元所做的更改。解决此问题的一种方法是运行sudo systemctl edit zfs-mount
并添加以下代码:
[Service]
ExecStart=/sbin/zpool remove brick1 /dev/sdb
ExecStart=/sbin/zpool add brick1 cache /dev/sdb
要应用更改,请运行以下命令:
systemctl daemon-reload
现在,我们已确保缓存驱动器将在每次重新启动后添加,我们需要更改在 Azure VM 上运行的 Linux 代理的特定于 Azure 的配置。该代理负责创建临时资源驱动器,由于我们将用于其他目的,因此我们需要告诉代理不要创建临时磁盘。为实现此目的,我们需要编辑位于/etc/waagent.conf
中的文件,并查找以下行:
ResourceDisk.Format=y
然后,您需要将其更改为以下行:
ResourceDisk.Format=n
完成此操作后,我们可以通过运行以下命令将缓存驱动器添加到池中:
zpool add brick1 cache /dev/sdb -f
-f
选项只能在第一次使用时使用,因为它会删除先前创建的文件系统。请注意,需要对 VM 进行stop/deallocate/start
周期以阻止代理程序默认格式化资源磁盘,因为它默认会得到一个ext4
文件系统。
先前的过程也可以应用于使用更快的 NVMe 驱动器的新 Ls_v2 VM,例如 L8s_v2;只需将/dev /sdb
替换为/dev/nvme0n1
。
您可以验证缓存磁盘是否已添加如下:
由于我们将使用单个 RAID 组,因此这将用作整个 brick 的读取缓存,从而在读取 GlusterFS 卷的文件时提供更好的性能。
在节点上安装 GlusterFS
每个节点已经配置了砖块后,我们最终可以安装 GlusterFS。安装相对简单,只需要几个命令。
安装软件包
我们将使用 CentOS 提供的软件包。要安装 GlusterFS,我们首先安装存储库如下:
yum install -y centos-release-gluster41
然后,我们安装glusterfs-server
软件包:
yum install -y glusterfs-server
然后,确保启用并启动glusterd
服务:
这些命令需要在将成为集群一部分的每个节点上运行;这是因为每个节点都需要启用软件包和服务。
创建受信任的池
最后,我们需要创建一个受信任的池。受信任的池是将成为集群一部分的节点列表,其中每个 Gluster 节点都信任其他节点,从而允许创建卷。
要创建受信任的池,请从第一个节点运行以下代码:
gluster peer probe gfs2
gluster peer probe gfs3
您可以按以下方式验证节点是否显示:
该命令可以从任何节点运行,并且主机名或 IP 地址需要修改以包括其他节点。在这种情况下,我已将每个节点的 IP 地址添加到/etc/hosts
文件中,以便进行简单的配置。理想情况下,主机名应该在 DNS 服务器上注册,以便在网络内进行名称解析。
安装完成后,gluster
节点应允许创建卷。
创建卷
我们现在已经到达可以创建卷的阶段;这是因为我们已经配置了砖块并且安装了 GlusterFS 所需的软件包。
创建分散卷
我们将在三个节点之间使用分散卷类型,以实现高可用性和性能的良好平衡。所有节点的原始空间总共约为 1.5 TB;但是,分布式卷的可用空间将约为 1 TB。
要创建分散卷,请使用以下代码:
gluster volume create gvol1 disperse 3 gfs{1..3}:/bricks/brick1/gvol1
然后,使用以下代码启动卷:
gluster volume start gvol1
确保使用以下代码正确启动:
gluster volume status gvol1
现在应该显示卷如下:
挂载卷
卷现在已创建,并且可以在客户端上挂载;最好的方法是使用本机的glusterfs-fuse
客户端进行此操作,它允许在其中一个节点宕机时自动进行故障转移。
要安装gluster-fuse
客户端,请使用以下代码:
yum install -y glusterfs-fuse
然后,在根目录下创建一个名为gvol1
的目录:
mkdir /gvol1
最后,我们可以按以下方式在客户端上挂载 GlusterFS 卷:
mount -t glusterfs gfs1:/gvol1 /gvol1
您指定的节点并不重要,因为可以从任何节点访问卷。如果其中一个节点宕机,客户端将自动将 I/O 请求重定向到剩余的节点。
优化性能
创建和挂载卷后,我们可以调整一些参数以获得最佳性能。主要是在文件系统级别(在本例中为 ZFS)和 GlusterFS 卷级别上进行性能调整。
GlusterFS 调优
在这里,主要变量是performance.cache-size
。此设置指定要分配为 GlusterFS 卷的读取缓存的 RAM 量。默认情况下,它设置为 32 MB,这相当低。鉴于所选的 VM 具有足够的 RAM,可以使用以下命令将其提高到 4 GB:
gluster volume set gvol1 performance.cache-size 4GB
一旦集群开始增长,另一个重要的参数是performance.io-thread-count
。这控制卷生成多少 I/O 线程。默认值为16
个线程,对于中小型集群来说足够了。但是,一旦集群规模开始增大,可以将其加倍。要更改此设置,请使用以下命令:
gluster volume set gvol1 performance.io-thread-count 16
应该测试此设置,以检查增加计数是否改善性能。
ZFS
我们将主要更改两个设置:ARC 和 L2ARC feed 性能。
ARC
ZFS 的主要设置是其读取缓存,称为 ARC。允许分配更多的 RAM 给 ZFS 可以大大提高读取性能。由于我们已经为 Gluster 卷读取缓存分配了 4GB,并且 VM 有 32GB 可用,我们可以为 ZFS 分配 26GB 的 RAM,这将留下大约 2GB 给操作系统。
要更改 ARC 允许的最大大小,使用以下代码:
echo 27917287424 > /sys/module/zfs/parameters/zfs_arc_max
在这里,数字是以字节为单位的 RAM 数量,本例中为 26GB。这样做会即时更改设置,但不会使其持久化。要在引导时应用设置,创建一个名为/etc/modprobe.d/zfs.conf
的文件,并添加以下值:
options zfs zfs_arc_max=27917287424
通过这样做,您可以使更改在引导时持久化。
L2ARC
L2ARC 是指第二级读取缓存;这是先前添加到 zpools 的缓存磁盘。改变数据馈送到缓存的速度有助于减少将常访问文件填充到缓存中所需的时间。该设置以每秒字节为单位指定。要更改它,可以使用以下命令:
echo 2621440000 > /sys/module/zfs/parameters/l2arc_max_write
与前面的设置一样,这适用于运行的内核。要使其持久化,将以下行添加到/etc/modprobe.d/zfs.conf
文件中:
options zfs l2arc_write_max=2621440000
此设置允许最大 256MB/s 的 L2ARC feed;如果 VM 大小更改为更高级别,则应将设置至少增加一倍。
最后,您应该在每个节点上得到一个类似这样的文件:
关于 ZFS,在其他类型的文件系统上,改变块大小有助于提高性能。ZFS 具有可变的块大小,允许小文件和大文件实现类似的结果,因此无需更改此设置。
总结
安装 ZFS、创建 zpools、安装 GlusterFS 和创建卷后,我们得到了一个性能可观的解决方案,可以承受节点故障并仍然为其客户端提供数据。
对于设置,我们使用 Azure 作为云提供商。虽然每个提供商都有自己的一套配置挑战,但核心概念也可以在其他云提供商上使用。
然而,这种设计有一个缺点。当向 zpools 添加新磁盘时,条带不对齐,导致新的读取和写入产生较低的性能。通过一次添加整套磁盘可以避免这个问题;较低的读取性能大部分由 RAM 上的读取缓存(ARC)和缓存磁盘(L2ARC)覆盖。
对于 GlusterFS,我们使用了一个平衡性能和高可用性的分散布局。在这个三节点集群设置中,我们可以承受一个节点故障,而不会阻止来自客户端的 I/O。
主要的要点是在设计解决方案时要有批判性的思维。在这个例子中,我们利用现有的资源来实现一个符合规格并利用我们提供的配置的解决方案。确保您始终问自己这个设置将如何影响结果,以及如何更改它以使其更有效。
在下一章中,我们将测试和验证设置的性能。
问题
-
什么是 GlusterFS 的 bricks?
-
什么是 ZFS?
-
什么是 zpool?
-
什么是缓存磁盘?
-
如何安装 GlusterFS?
-
什么是受信任的池?
-
如何创建 GlusterFS 卷?
-
什么是 performance.cache-size?
-
什么是 ARC?
进一步阅读
-
通过 Geoff Webber-Cross 学习 Microsoft Azure:
www.packtpub.com/networking-and-servers/learning-microsoft-azure
-
通过 Florian Klaffenbach、Jan-Henrik Damaschke 和 Oliver Michalski 实施 Azure 解决方案:
www.packtpub.com/virtualization-and-cloud/implementing-azure-solutions
-
《Azure 架构师》作者 Ritesh Modi:
www.packtpub.com/virtualization-and-cloud/azure-architects
第五章:分析 Gluster 系统的性能
在第四章中,在云基础设施上使用 GlusterFS,我们已经完成了 GlusterFS 的工作实施,现在可以专注于解决方案的测试方面。我们将概述部署的情况,并解释选择组件背后的原因。
一旦配置被定义,我们可以通过测试性能来验证我们是否达到了预期的结果。然后,我们可以在执行 I/O 时故意关闭节点来进行可用性测试。
最后,我们将看到如何在垂直和水平两个方向上扩展解决方案。
在本章中,我们将涵盖以下主题:
-
实施的高级概述
-
进行性能测试
-
性能可用性测试
-
在垂直和水平两个方向上扩展解决方案
技术要求
本章的技术资源列表如下:
-
Zpool iostat-用于在 ZFS 上进行性能监控:
docs.oracle.com/cd/E19253-01/819-5461/gammt/index.html
-
Sysstat-用于实时块性能统计:
github.com/sysstat/sysstat
-
iostat man 页面包含命令的不同选项:
sebastien.godard.pagesperso-orange.fr/man_iostat.html
-
FIO 文档提供配置参数和用法:
media.readthedocs.org/pdf/fio/latest/fio.pdf
-
GlusterFS 监控工作负载文档,说明如何查看统计信息:
gluster.readthedocs.io/en/latest/Administrator%20Guide/Monitoring%20Workload/
实施概述
在第四章中部署和配置了解决方案后,在云基础设施上使用 GlusterFS,我们可以验证实施的性能。主要目标是了解如何实现以及可用的工具。
让我们先退一步,看看我们实施了什么。
集群的概述
在第四章中,在云基础设施上使用 GlusterFS,我们在 Azure 的虚拟机(VM)上部署了 GlusterFS 4.1 版本。我们使用 ZFS 作为砖块的存储后端,通过在三节点设置中每个节点使用四个磁盘。以下图表提供了这个分布的高级概述:
这个设置提供了 1TB 的可用空间。卷可以容忍整个节点宕机,同时仍然向客户端提供数据。
这个设置应该能够提供大约 375 兆字节每秒(MB/s),同时处理数百个客户端,并且应该相当容易在垂直和水平两个方向上扩展。
性能测试
现在我们需要验证理论性能是否可以通过实际实施来实现。让我们将这个分解成几个部分。
性能理论
让我们根据设置的规格来确定应该获得多少性能。考虑到每个节点应该提供最大 125MB/s。磁盘子系统完全能够提供性能,因为每个磁盘产生 60MB/s。
总体可实现的性能应该在 375MB/s 左右,假设客户端可以通过发送或请求足够的数据到卷来跟上。
性能工具
我们将使用三种主要工具来验证和测试解决方案的性能:
-
zpool iostat
-
iostat
-
灵活 I/O 测试工具(FIO)
这些工具每个都在不同的级别上工作。现在让我们详细说明每个工具的作用以及如何理解它们提供的信息。
ZFS zpool iostat 命令
ZFS 在后端卷级别上工作;zpool iostat -v
命令为 ZFS 卷中的每个成员提供性能统计信息,并为整个 ZFS 卷提供统计信息。
该命令可以通过传递秒数来提供实时数据,它将在经过该时间段后迭代。例如,zpool iostat -v 1
每秒报告磁盘统计信息。这里,-v
选项显示池的每个成员及其各自的数据。
该工具有助于以尽可能低的级别呈现性能,因为它显示了来自每个磁盘、每个节点的数据:
请注意,我们使用了额外的-L
和-P
选项,以便打印设备文件的绝对路径或通用唯一标识符(UUID);这是因为我们使用每个磁盘的唯一标识符创建了池。
从前面的屏幕截图中,我们可以看到四个主要组,如下:
-
pool
: 这是使用每个成员创建的。 -
capacity
: 这是分配给每个设备的空间量。 -
operations
: 这是在每个设备上执行的 IOPS 数量。 -
bandwidth
: 这是每个设备的吞吐量。
在第一行中,该命令打印自上次启动以来的统计信息。请记住,该工具有助于从 ZFS 池级别呈现性能。
iostat
作为sysstat
软件包的一部分,iostat
提供了来自每个设备的低级性能统计数据。iostat
绕过文件系统和卷,并呈现系统中每个块设备的原始性能数据。
iostat
工具可以使用选项运行,以更改在屏幕上打印的信息,例如,iostat -dxctm 1
。让我们探讨每个部分的作用:
-
iostat
: 这是主要命令。 -
d
: 这打印设备利用率。 -
x
: 这显示扩展设备统计信息。 -
c
: 这显示了 CPU 利用率。 -
t
: 这显示每个报告打印的时间。 -
m
: 这确保统计数据以 MB/s 显示。 -
1
: 这是iostat
打印数据的时间,以秒为单位。
在下面的屏幕截图中,您可以看到iostat
以不同的列显示信息:
没有必要浏览所有列,但最重要的列如下:
-
Device
: 这显示系统上存在的块设备。 -
r/s
: 这些是每秒的读取操作。 -
w/s
: 这些是每秒的写入操作。 -
rMB/s
: 这些是从设备读取的 MB/s。 -
wMB/s
: 这些是写入设备的 MB/s。 -
r_await
: 这是读取请求的平均时间(毫秒)。 -
w_await
: 这是写请求的平均时间(毫秒)。
r_await
和w_await
列与avg-cpu %iowait
时间一起是必不可少的;这是因为这些指标可以帮助确定是否有一个设备的延迟增加超过其他设备。高 CPU iowait
时间意味着 CPU 不断等待 I/O 完成,这反过来可能意味着块设备具有较高的延迟。
iostat
工具可以在集群中的每个节点上运行,为组成 GlusterFS 卷的每个磁盘提供低级别统计信息。
有关其他列的详细信息可以在iostat
的 man 页面上找到。
FIO 测试员
FIO 是一个基准测试工具,用于通过生成合成工作负载进行性能测试,并提供 I/O 指标摘要。
请注意,fio
在 CentOS 上不是默认安装的,但它可以在基本存储库中找到,并且可以通过运行sudo yum install -y fio
来安装。
这个工具非常有用,因为它允许我们执行接近系统实际工作负载的测试——通过允许用户更改块大小、文件大小和线程数量等参数。FIO 可以提供接近真实性能的数据。这种定制水平可能会让人感到困惑,因为它提供了许多工作负载模拟选项,一些选项一开始并不直观。
使用 FIO 进行测试的最简单方法是创建一个配置文件,告诉软件如何行为;配置文件看起来像这样:
[global]
name=rw-nocache-random
rw=randrw
rwmixread=50
rwmixwrite=50
group_reporting=1
bs=1M
direct=1
numjobs=4
time_based=1
runtime=180
ioengine=libaio
iodepth=64
[file1]
size=10G
filename=rw-nocache-random.1
让我们分解一下,以便了解配置文件的每个部分是如何工作的:
-
[global]
:这表示影响整个测试的配置参数(可以设置单个文件的参数)。 -
name=
:这是测试的名称;可以是任何有意义的名称。 -
rw=randrw
:这告诉 FIO 要执行什么类型的 I/O;在这种情况下,它执行随机读写。 -
rwmixread
和rwmixwrite
:这告诉 FIO 执行读写的百分比或混合比例——在这种情况下,是 50-50 的混合比例。 -
group_reporting=1
:这用于为整个测试提供统计信息,而不是为每个作业提供统计信息。 -
bs=1M
:这是 FIO 在执行测试时使用的块大小;可以将其更改为模拟预期工作负载的值。 -
numjobs=4
:这控制每个文件打开多少个线程。理想情况下,可以用来匹配将使用存储的用户或线程的数量。 -
runtime=180
:这控制测试运行的时间,以秒为单位。 -
ioengine=libaio
:这控制要使用的 I/O 引擎类型。最常见的是libaio
,因为它最接近大多数工作负载。 -
iodepth=64
:这控制测试的 I/O 深度;较高的数字允许存储设备充分利用。
最后,文件组控制创建测试文件的数量和它们的大小。可以向该组添加某些设置,例如iodepth
,这些设置仅影响定义了该参数的文件。另一个考虑因素是,fio
根据numjobs
参数为每个文件打开一个线程。在前面的配置中,它将打开总共 16 个线程。
要运行 FIO,只需进入挂载点所在的目录,并将其指向配置文件,如下所示:
cd /gvol1
fio /root/test.fio
请注意,FIO 需要 root 权限,因此请确保使用sudo
来运行 FIO。
在 FIO 运行时,它会显示吞吐量和 IOPS 等统计信息:
完成后,FIO 会在屏幕上报告测试统计信息。要查找的主要内容是读写操作的 IOPS 和带宽。
从测试结果中,我们可以看到 GlusterFS 卷可以同时维持约 150MB/s 的读写操作。我们与集群的理论最大性能相差 75MB/s;在这种特定情况下,我们达到了网络限制。
FIO 在验证性能和检测问题方面非常有效;fio
可以在挂载 Gluster 卷的客户端上运行,也可以直接在每个节点的存储单元上运行。您可以使用 FIO 来测试现有解决方案,以验证性能需求;只需确保根据需要测试的内容更改 FIO 配置中的设置。
GlusterFS 提供了一些工具,可以从卷的角度监视性能。这些工具可以在 GlusterFS 的文档页面上找到,位于监视工作负载下。
可用性测试
确保集群能够容忍节点宕机是至关重要的,因为我们可以确认如果节点丢失,不会发生停机。
这可以通过强制关闭其中一个节点来完成,而其他节点继续提供数据。为了作为合成工作负载,我们可以使用 FIO 在关闭其中一个节点时执行连续测试。
在下面的屏幕截图中,我们可以看到gfs2
节点不存在,但 FIO 测试继续按预期提供数据:
扩展
这种设置的扩展相对简单。如前所述,我们可以通过在每个节点上添加更多磁盘来纵向扩展,或者通过在集群中添加更多节点来横向扩展。
纵向扩展比横向扩展要简单得多,因为它需要更少的资源。例如,如果在每个节点上将单个磁盘添加到 ZFS 池中,则可以将可用空间增加 256 GB,如果添加了三个 128 GB 磁盘。
可以使用以下命令将磁盘添加到 ZFS 池中:
zpool add brick1 /dev/disk/by-id/<disk-id>
从前一个命令中,brick1
是池的名称,disk-id
是最近添加的磁盘的 UUID。
横向扩展需要在新节点上镜像完全相同的设置,然后将其添加到集群中。这需要一组新的磁盘。优点是可用空间和性能将相应增长。
摘要
在本章中,我们概述了前一章第四章中完成的实施,即在云基础架构上使用 GlusterFS,以便我们可以对实施的内容有一个新的理解,以便了解我们如何测试性能。根据先前的设置,实施应该能够实现理论上的 375 MB/s 吞吐量。我们可以使用几个在不同级别工作的工具来验证这个数字。
对于 ZFS 卷,我们可以使用zpool iostat
命令,该命令提供 ZFS 卷的每个块设备的数据。iostat
可用于确定系统中存在的所有块设备的性能。这些命令只能在集群的每个节点上运行。为了验证实现的实际性能,我们使用了 FIO 工具,该工具可以通过更改 I/O 执行方式的参数来模拟特定的工作负载。该工具可用于每个节点的砖级别,或者在 GlusterFS 卷上的每个 Gluster 客户端上,以获得集群可实现的性能的概述。
我们已经了解了如何通过有意关闭其中一个节点并通过 FIO 执行测试来执行可用性测试。最后,可以通过纵向扩展(在每个节点的每个卷中添加磁盘)或横向扩展(在集群中添加一个全新节点)来扩展解决方案。您从本章的主要收获是考虑如何使用广泛可用的工具来验证已实施的配置。这只是一组工具。可能还有许多其他工具可用,这些工具可能更适合您正在实施的解决方案。
在下一章中,我们将介绍创建高可用性自愈架构。
问题
-
MB/s 是什么?
-
zpool iostat
是什么? -
我在哪里可以运行
zpool iostat
? -
iostat
是什么? -
r_await
是什么意思? -
CPU IOWAIT 时间是什么?
-
FIO 是什么?
-
我怎样才能运行 FIO?
-
FIO 配置文件是什么?
-
我怎样才能在 Gluster 集群中验证可用性?
-
我怎样才能纵向扩展?
进一步阅读
-
- Mohamed Waly 的《学习 Microsoft Azure 存储》:
www.packtpub.com/big-data-and-business-intelligence/learning-microsoft-azure-storage
- Mohamed Waly 的《学习 Microsoft Azure 存储》:
第二部分:使用 Kubernetes 构建高可用性 Nginx Web 应用程序
在本节中,读者将学习了解使用 Kubernetes 作为部署和管理容器化应用程序的编排器的优势,以及如何部署这样的解决方案。
本节包括以下章节:
-
[第六章],创建高可用性的自愈架构
-
[第七章],理解 Kubernetes 集群的核心组件
-
[第八章],设计 Kubernetes 集群
-
[第九章],部署和配置 Kubernetes
第六章:创建高可用的自愈架构
在本章中,我们将介绍 IT 行业是如何从使用单片应用程序发展到云原生、容器化和高可用的微服务的。
通过开源,我们可以提供解决方案,使我们能够根据用户消费量创建高可用性和按需扩展我们的应用程序。
我们将在本章中涵盖以下主题:
-
描述微服务
-
为什么容器是微服务的家园
-
我们如何可以编排我们的容器
-
探索开源中最常用的编排器 Kubernetes。
微服务
微服务用于以模块化方式设计应用程序,其中每个模块都独立部署,并通过 API 相互通信。所有这些模块共同工作,以交付一个单一应用程序,其中每个功能都有其自己的目的。
例如,让我们看看一个在线商店。我们只能看到主网站;然而,在后台有几个微服务起作用,一个服务用于接受订单,另一个根据您以前的浏览推荐物品,支付处理,评论处理等等。
以下图表是微服务应用程序的一个示例:
从本质上讲,微服务应用程序不需要一个庞大的团队来支持整个应用程序。一个团队只支持整体图景中的一个或两个模块,从而在支持和专业知识方面创造了更细粒度的方法。支持和开发不仅是细粒度的,还存在故障。在单个微服务故障的情况下,只有应用程序的那一部分会失败。
继续我们的在线商店示例,假设处理评论和评论的微服务失败了。这是因为我们的网站是使用微服务构建的,所以我们网站的只有那个组件将对我们的客户不可用。
然而,他们仍然可以继续购买和使用网站,而用户将无法看到他们感兴趣的产品的评论,这并不意味着我们整个网站的可用性受到损害。根据问题的原因,您可以修补微服务或重新启动它。不再需要为了修补或重新启动整个网站。
作为基础架构工程师,您可能会想,为什么我必须知道什么是微服务或它的好处?原因很简单。作为架构师或基础架构工程师,您正在为这种类型的应用程序构建基础架构。无论它们是在单个主机上运行的单片应用程序,还是在多个容器中分布的微服务,它肯定会影响您设计客户架构的方式。
Linux 将是您的好朋友,因为您将找到多个开源工具,将帮助您维护高可用性、负载平衡和持续集成(CI)/持续交付(CD),使用 Docker、Kubernetes、Jenkins、Salt 和 Puppet 等工具。因此,每当客户询问您应该从哪个操作系统环境开始设计他的微服务应用程序时,Linux 将是您的答案。
目前,Docker Swarm 和 Kubernetes 在容器编排方面处于领先地位。在微服务方面,当为客户设计基础架构时,容器也将是您的首选。
我们将在第七章中深入探讨 Kubernetes,了解 Kubernetes 集群的核心组件,并展示它如何帮助您编排和交付一个优雅而复杂的解决方案,用于托管微服务和其他类型的应用程序。
然而,在谈论 Kubernetes 或容器编排之前,我们需要解释容器的概念,以便理解为什么它们非常适合托管微服务应用程序。
Linux 中的容器已经有一段时间了,但直到几年前(随着 Docker Engine 的发布),它们才在所有技术社区中获得了动力和赞誉。容器在合适的时间出现了,并随着微服务架构的兴起而留了下来,并且正在塑造我们设计和执行它的方式。
让我们退一步,以便您了解这种技术的好处。想象一下,您有一个简单的单体应用程序,它正在运行一个 API,您可以从中查询用户列表以及他们从您托管的网站购买的商品。
过了一段时间,您的客户发现他们的 API 在其他应用程序中变得非常受欢迎,这些应用程序现在在高峰时段进行数千次 HTTP GET
请求。当前的基础设施无法处理如此多的请求,因此您的客户要求您以一种可以处理更多请求的方式扩展他们的基础设施。问题在于,因为这是一个单体应用程序,您不仅需要计算 API 所需的资源,还需要考虑与 API 一起托管的网店前端,即使 API 是您实际上需要扩展的唯一内容。
这将是一种资源浪费,因为您还将包括不需要任何额外副本或资源的网店前端。您正在浪费宝贵的,有时是昂贵的(如果您在公共云中)存储、内存和 CPU 资源,用于一些实际上并不需要的东西。
因此,这就是微服务和为托管这种类型的应用程序而设计的容器发挥作用的地方。使用容器镜像中的微服务,您无需每次需要扩展服务以满足需求时都要提供新的服务器,也无需重新启动服务器,或者在执行应用程序或操作系统更新时每次都要处理软件包依赖关系。只需使用一个简单的命令(docker container run companyreg.io/storeapi:latest
),您的应用程序就可以启动并准备好提供请求。同样,如果您的应用程序失败,只需重新启动容器或提供一个新的容器,您就可以继续进行。如果对微服务进行的更新存在错误,只需回滚到以前的镜像版本,您就可以重新启动;无需开始卸载更新的库或处理依赖关系问题。
容器还允许应用程序部署的一致性,因为您可能知道,有多种安装软件包的方式。您可以通过apt
、yum
和apk
等软件包管理器进行安装,也可以通过git
、/curl/wget
、pip
和juju
进行安装,取决于您的安装方式,也将定义您的维护方式。
想象一下,开发人员将其软件包发送给开放性分析标准(OPS)团队进行部署,每个 OPS 工程师以不同的方式部署应用程序!这将变得难以支持和跟踪。具有您的应用程序的容器镜像将创建一致性,因为无论您将其部署为容器的位置在哪里,它都将在所有部署的地方具有相同的位置,用于所有配置文件、二进制文件、库和依赖项。所有内容都将被隔离到一个具有自己的进程命名空间(PID 命名空间)、网络命名空间和挂载命名空间(MNT 命名空间)的容器中。
将应用程序架构为微服务的目的是为了为应用程序中的每个微服务提供隔离,以便它们可以轻松管理和维护——容器正好实现了这一点。你甚至可以定义每次容器启动时如何启动应用程序——一致性在这里起着主导作用。
创建容器镜像
构建容器的方式是通过一个叫做Dockerfile的东西。Dockerfile 基本上是一组关于如何构建容器镜像的指令;一个典型的 Dockerfile 如下所示:
FROM ubuntu:latest
LABEL maintainer="WebAdmin@company.com"
RUN apt update
RUN apt install -y apache2
RUN mkdir /var/log/my_site
ENV APACHE_LOG_DIR /var/log/my_site
ENV APACHE_RUN_DIR /var/run/apache2
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
COPY /my_site/ /var/www/html/
EXPOSE 80
CMD ["/usr/sbin/apache2","-D","FOREGROUND"]
如你所见,这是一组非常易读的指令。即使不知道每个指令的作用,我们也可以假设它的功能,因为它非常类似于英语。这个 Dockerfile 只是一个例子,迄今为止是最有效的方法。
镜像本质上就像虚拟机(VM)世界中的模板;它是一组只读层,包含了部署容器所需的所有信息——从单个镜像中,你可以部署多个容器,因为它们都在自己的可写层上运行。
例如,每当你拉取一个镜像,你会看到以下输出:
[dsala@redfedora ~]# docker pull httpd:latest
latest: Pulling from library/httpd
d660b1f15b9b: Pull complete
aa1c79a2fa37: Pull complete
f5f6514c0aff: Pull complete
676d3dd26040: Pull complete
4fdddf845a1b: Pull complete
520c4b04fe88: Pull complete
5387b1b7893c: Pull complete
Digest: sha256:8c84e065bdf72b4909bd55a348d5e91fe265e08d6b28ed9104bfdcac9206dcc8
Status: Downloaded newer image for httpd:latest
你看到的每个Pull complete
实例对应着镜像的一个层。那么,这些层是什么,它们来自哪里呢?
当我们构建镜像时,我们在 Dockerfile 中定义的一些指令将创建一个新的层。文件中的每个指令都在容器中的读写层中执行,在构建结束时,将提交到最终层堆栈,形成最终镜像。需要注意的是,即使构建过程中的每个指令都在容器中执行,并非所有命令都会创建数据,使得镜像在大小和层面上变得更大——其中一些只会写入到所谓的镜像清单中,这本质上是一个包含所有镜像元数据的文件。
让我们更深入地探讨每个命令。
FROM
FROM
指令指示了你的初始镜像是什么,基本上是你将开始构建自己镜像的基础。
你在这里放置的内容将取决于你的需求,例如,哪个镜像预先安装了我应用程序需要的库,哪个镜像已经安装了我需要编译应用程序的编译器,或者哪个镜像对我们最终大小的影响最小。例如,你的应用程序是基于 Python 2 构建的。你可以直接使用python:2.7
镜像,而不是使用 CentOS 或 Ubuntu 作为初始镜像,然后手动安装 Python,因为它已经预先安装了 Python。
显然,这里还有更多需要考虑的事情,但我们将在本章后面讨论镜像构建的最佳实践时再详细介绍。
由于这个指令会使用另一个镜像作为基础,你的最终镜像将继承基础镜像的层;因此,最终层的总数将如下所示:
最终镜像层 = 基础镜像层 + 你创建的层
LABEL
LABEL
指令非常直观——它使用键值对为你的镜像添加元数据标签,稍后可以通过docker inspect
命令检索。你可以使用它来添加用户需要知道的数据。通常用于添加镜像作者的信息,比如他们的电子邮件或公司:
LABEL maintener="john.doe@company.com"
由于这个指令只是元数据,不会为你的镜像添加额外的层。
RUN
使用RUN
,你将运行需要准备容器来运行你的应用程序的命令;例如,安装软件包、编译代码,创建用户或目录。RUN
有两种运行命令的方式。
shell 形式如下:
RUN <command>
在这种形式下,默认情况下所有命令都将使用/bin/sh -c
shell 运行,尽管你可以使用SHELL
指令来更改 shell,如下所示:
SHELL ["/bin/bash", "-c"]
RUN echo "Hello I'm using bash"
SHELL
关键字只能以 JSON 数组格式运行,这导致我们可以使用第二种形式来运行RUN
指令。
执行形式如下:
RUN ["echo","hello world"]
这里的主要区别,除了格式之外,在执行形式中不会调用 shell,因此不会发生正常的变量替换——相反,您必须调用 shell 作为 shell 的命令,以便 shell 能够提供变量扩展:
RUN ["/bin/bash","-c","echo $HOME"]
由于RUN
关键字的性质,每个实例都将在新层上执行并提交到最终图像,因此,每次使用RUN
都会向图像添加一个新层。
ENV
对于ENV
,没有太多可说的——这个指令为环境设置变量。它们将在构建时使用,并在容器运行时可用。ENV
不会为容器生成额外的层,因为它将环境变量存储在图像清单中作为元数据:
ENV <key>=<value>
ENV
的参数以<key>
/<value>
对的形式处理,其中<key>
参数是变量名,<value>
参数是其内容或值。您可以使用=
符号声明它们,也可以不使用。引号和反斜杠可用于转义值字段中的空格。
以下所有变体都是有效的:
ENV USER="Jane Doe"
ENV USER=Jane\ Doe
ENV USER Jane Doe
COPY
使用COPY
,我们可以将文件或目录从本地主机(执行 Docker 构建的位置)复制到我们的图像中。这非常有用,因为您实际上正在将内容移动到图像中,以便您可以复制应用程序、文件或任何可能需要的内容以使容器工作。正如我们之前提到的,任何向容器添加实际数据的指令都将创建一个新层,因此会增加最终图像的存储占用。
此指令与RUN
具有相同的形式;您可以使用 JSON 格式化,也可以将<src>
源分开放置到<dst>
目的地:
COPY <src> <dst>
COPY ["<src1>","<src2>","<dst>"]
有几个陷阱我们需要经历。首先,如果任何文件名或目录名中有空格,您必须使用 JSON 数组格式。
其次,默认情况下,所有文件和目录都将以用户标识符(UID)和组标识符(GID)0
(root)复制。要覆盖此设置,可以使用--chown=<UID>:<GID>
标志,如下所示:
COPY --chown=JANE:GROUP <src> <dst>
chown
接受数字 ID 或用户或组的名称。如果只有其中一个,则定义如下:
COPY --chown=JANE <src> <dst>
COPY
将假定用户和组都是相同的。
如果要复制名称相似的文件,则可以始终使用通配符——COPY
将使用 Go 的filepath.Match
规则,该规则可以在golang.org/pkg/path/filepath#Match
找到。
定义<src>
和<dst>
条目的方式非常重要,因为它们遵循以下三条规则:
-
在
<src>
中定义的路径必须在构建的上下文中,基本上,所有位于您运行 Docker 构建PATH
命令时指定的目录中的文件和目录。 -
如果要复制目录,则始终以
/
结尾。这样,Docker 就知道这是一个目录,而不是要复制的单个文件。此外,如果是目录,则其中的所有文件也将被复制。 -
<dst>
中定义的路径将始终是绝对路径,除非您使用WORKDIR
指令指定相对于的工作目录。
最后要说的是COPY
指令,我必须补充的是COPY
只支持复制本地文件。如果要使用 URL 从远程服务器复制文件,必须使用ADD
指令,它遵循与COPY
相同的规则,但对于 URL 还有一些其他注意事项。这超出了本章的范围,但您可以在docs.docker.com
了解更多信息。
EXPOSE
使用EXPOSE
关键字,我们实际上并没有发布我们在这里指定的容器端口;相反,我们正在为容器的用户创建一个指南,让他们知道在启动容器时应该发布哪些端口。
因此,这只是在图像的清单中再次创建的元数据,稍后可以使用docker inspect
检索。不会使用此关键字创建其他图层。
在EXPOSE
指令中定义的端口可以是用户数据报协议(UDP)或传输控制协议(TCP),但是,默认情况下,如果未指定协议,则假定为 TCP。
以下是一些EXPOSE
指令的示例:
EXPOSE 80
EXPOSE 53/udp
EXPOSE 80/tcp
CMD 和 ENTRYPOINT
这可能是 Dockerfile 中最重要的指令,因为它们告诉容器在启动时要运行什么。我们将逐个讨论它们,并探讨它们如何相互作用以及它们之间的区别。
让我们先从ENTRYPOINT
开始。正如我们之前提到的,这个指令允许您定义启动容器时要运行的可执行文件。您可以在 Dockerfile 中添加多个ENTRYPOINT
定义,但只有最后一个会在docker container run
上执行。
当您使用run
参数运行容器时,通常可以添加命令行参数。除非您在使用docker container run
时使用--entrypoint
标志覆盖ENTRYPOINT
可执行文件,否则这些参数将被追加到ENTRYPOINT
参数。
让我们看一些例子。假设我们正在使用一个具有以下 Dockerfile 的容器:
FROM alpine
ENTRYPOINT ["echo","Hello from Entrypoint"]
现在,假设我们构建了图像并将其标记为entrypointexample
。当我们在不带额外命令行参数的情况下运行此容器时,它将显示如下:
[dsala@redfedora]# docker container run entrypointexample
Hello from Entrypoint
如果我们向run
命令添加命令行参数,我们将看到类似这样的东西:
[dsala@redfedora]# docker container run entrypointexample /bin/bash
Hello from Entrypoint /bin/bash
正如您所看到的,它实际上并没有执行 BASH shell,而是将/bin/bash
作为我们在 Dockerfile 中定义的echo
命令的字符串。让我们考虑一个更明确的例子,因为前一个例子中,我只是想演示即使您传递了实际命令或尝试执行 shell,它仍然会将其作为ENTRYPOINT
的参数传递。这是一个更清晰的例子,带有一个简单的字符串:
[dsala@redfedora]# docker container run entrypointexample I AM AN ARGUMENT
Hello from Entrypoint I AM AN ARGUMENT
现在,如果我们传递--entrypoint
标志,我们将覆盖ENTRYPOINT
可执行文件:
[dsala@redfedora]# docker container run --entrypoint /bin/ls entrypointexample -lath /var
total 0
drwxr-xr-x 1 root root 6 Aug 8 01:22 ..
drwxr-xr-x 11 root root 125 Jul 5 14:47 .
dr-xr-xr-x 2 root root 6 Jul 5 14:47 empty
drwxr-xr-x 5 root root 43 Jul 5 14:47 lib
drwxr-xr-x 2 root root 6 Jul 5 14:47 local
drwxr-xr-x 3 root root 20 Jul 5 14:47 lock
drwxr-xr-x 2 root root 6 Jul 5 14:47 log
drwxr-xr-x 2 root root 6 Jul 5 14:47 opt
lrwxrwxrwx 1 root root 4 Jul 5 14:47 run -> /run
drwxr-xr-x 3 root root 18 Jul 5 14:47 spool
drwxrwxrwt 2 root root 6 Jul 5 14:47 tmp
drwxr-xr-x 4 root root 29 Jul 5 14:47 cache
好的,那么为什么这个命令的格式是这样的呢?正如我们之前看到的,--entrypoint
标志只替换可执行文件——所有额外的参数都必须作为参数传递。这就是为什么我们的ls
在最后有-lath /var
参数的原因。这里还有一些其他需要注意的地方,它们对应于ENTRYPOINT
指令的形式。
与其他 Dockerfile 指令一样,ENTRYPOINT
有两种形式,shell 和 exec:
ENTRYPOINT command argument1 argument2
ENTRYPOINT ["executable", "param1", "param2"]
对于 exec 形式,适用于之前 Dockerfile 指令的相同规则也适用于这里。
在 exec 形式中不会调用 shell,因此$PATH
变量不存在,您将无法在不提供完整路径的情况下使用可执行文件——这就是为什么我们使用/bin/ls
而不是ls
。此外,您可以看到您首先在 JSON 数组中定义可执行文件,然后是其参数,这个第一个字段是--entrypoint
标志将替换的内容。在使用该标志时,任何额外的参数都必须作为docker container run
命令参数传递,就像我们在示例中所做的那样。
另一方面,shell 形式将加载/bin/sh
,以便环境变量可用。让我们看一个例子;这是一个使用 exec 形式的 Dockerfile 的容器:
FROM alpine
ENTRYPOINT ["echo", "$PATH"]
假设我们构建了图像并将其标记为pathexampleexec
。当我们运行容器时,我们将看到以下内容:
[dsala@redfedora]#docker container run pathexampleexec
$PATH
这是一个使用 shell 形式的 Dockerfile 的容器:
FROM alpine
ENTRYPOINT echo $PATH
当我们运行容器时,我们将看到以下内容:
[dsala@redfedora]# docker container run pathexampleshell
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
现在,假设您想为应用程序设置一些默认参数,但是您希望用户能够覆盖并使用不同的参数。这就是CMD
的作用;使用CMD
,您可以为可执行文件指定默认参数,但如果用户在docker container run
上运行容器时使用命令参数,这些参数将被覆盖。您必须小心声明ENTRYPOINT
,因为如果使用 shell 形式声明ENTRYPOINT
,所有CMD
定义将被忽略。
让我们看几个例子;以下是要运行的容器的 Dockerfile:
FROM alpine
ENTRYPOINT echo Hello
CMD ["I'm Ignored"]
假设先前提到的容器已构建并标记为cmdexample
,下面是其运行情况:
[dsala@redfedora]# docker container run cmdexample
Hello
现在,如果我们对ENTRYPOINT
使用执行形式,CMD 参数将被附加到ENTRYPOINT
。参考 Dockerfile:
FROM alpine
ENTRYPOINT ["echo", "hello from ENTRY"]
CMD ["hello", "from CMD"]
假设镜像已构建并标记为execcmdexample
,以下是输出:
[dsala@redfedora]# docker container run execcmdexmple
hello from ENTRY hello from CMD
请注意,这次CMD
条目被附加到ENTRYPOINT
作为参数。但是,请记住,CMD
的内容只是默认值;如果我们在docker container run
上指定参数,这些参数将覆盖CMD
中的参数。
使用与前面示例相同的 Dockerfile,我们将得到类似以下的内容:
[dsala@redfedora]# docker container run execcmdexmple "hello" "from" "run"
hello from ENTRY hello from run
CMD
和ENTRYPOINT
之间有几种组合,您可以在以下来自docs.docker.com
的图表中看到所有这些组合:
使用最佳实践构建容器镜像
Dockerfiles are like recipes for your applications, but you can't just throw in the ingredients and hope for the best. Creating an efficient image requires you to be careful about how you utilize the tools at your disposal.
容器的整个目的是占用空间小 - 对于 100 MB 应用程序来说,拥有 1 GB 以上的镜像并不代表占用空间小,也不高效。微服务也是如此;为微服务拥有小的容器镜像不仅提高了性能,而且存储利用率降低了安全漏洞和故障点,并且还能节省金钱。
容器镜像在主机本地和容器注册表中存储。公共云提供商根据注册表的存储利用率收取费用,而不是根据您在其中存储的镜像数量收费。想象一下注册表就像容器的 GitHub。假设您必须从云提供商的注册表中拉取镜像;您认为拉取 1 GB 镜像还是 100 MB 镜像会更快?镜像大小很重要。
构建镜像时要考虑的第一件事是要使用的基础镜像。不要使用大型镜像(如完整的 Linux 发行版,Ubuntu,Debian 或 CentOS),因为这些镜像有很多工具和可执行文件,您的应用程序不需要运行,而是使用较小的镜像,例如 Alpine:
**REPOSITORY ** | SIZE |
---|---|
centos |
200 MB |
ubuntu |
83.5 MB |
debian |
101 MB |
alpine |
4.41 MB |
您会发现大多数镜像都有自己的精简版本,例如httpd
和nginx
:
REPOSITORY | TAG | SIZE |
---|---|---|
httpd |
alpine |
91.4 MB |
httpd |
latest |
178 MB |
nginx |
alpine |
18.6 MB |
nginx |
latest |
109 MB |
正如您所看到的,httpd
:alpine
几乎比httpd
:latest
小了 50%,而nginx
:alpine
小了 80%!
更小的镜像不仅会减少存储消耗,还会减少攻击面。这是因为较小的容器具有较小的攻击面;让我们看看最新的 Ubuntu 镜像与最新的 Alpine 镜像。
对于 Ubuntu,我们可以看到最新标签的漏洞数量增加了;以下是 Docker Hub 页面的截图:
对于 Alpine Linux,计数降至零,如下截图所示:
在上面的截图中,我们可以看到与 Ubuntu 相比的漏洞计数。即使在今天,最新的 Alpine 图像也没有任何漏洞。相比之下,Ubuntu 有七个不需要的组件存在漏洞,这些组件甚至不需要我们的应用程序运行。
另一件需要考虑的事情是图像的分层;每次在构建中运行RUN
语句时,它都会添加一个更多的层和大小到最终图像中。减少RUN
语句的数量以及在其中运行的内容将大大减少图像的大小。
让我们看看我们的第一个 Dockerfile,如下所示:
FROM ubuntu:latest
LABEL maintainer="WebAdmin@company.com"
RUN apt update
RUN apt install -y apache2
RUN mkdir /var/log/my_site
ENV APACHE_LOG_DIR /var/log/my_site
ENV APACHE_RUN_DIR /var/run/apache2
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
COPY /my_site/ /var/www/html/
EXPOSE 80
CMD ["/usr/sbin/apache2","-D","FOREGROUND"]
我们可以将RUN
指令修改为以下方式:
RUN apt update && \
apt install -y apache2 --no-install-recommends && \
apt clean && \
mkdir /var/my_site/ /var/log/my_site
现在,我们将不再创建三个层,而是通过在单个语句中运行所有命令来产生一个层。
请记住,在RUN
中执行的所有操作都是使用/bin/sh -c
或您在SHELL
中指定的任何其他 shell 来执行的,因此&
、;
和\
都会被接受,就像在常规 shell 中一样。
然而,我们不仅删除了额外的RUN
指令;我们还添加了apt clean
来清理容器的缓存,然后使用了--no-install-recommend
标志来避免安装任何不必要的软件包,从而减少了存储空间和攻击面:
以下是原始图像的详细信息:
仓库 | 大小 |
---|---|
bigimage |
221 MB |
以下是较小图像的详细信息:
仓库 | 大小 |
---|---|
smallerimage |
214 MB |
当然,这并不是一个巨大的差异,但这只是一个例子,没有安装任何真正的应用程序。在生产图像中,您将不得不做的不仅仅是安装apache2
。
现在让我们使用我们学到的两种技术,来精简我们的图像:
FROM alpine
RUN apk update && \
apk add mini_httpd && \
mkdir /var/log/my_site
COPY /my_site/ /var/www/localhost/htdocs/
EXPOSE 80
CMD ["/usr/sbin/mini_httpd", "-D", "-d", "/var/www/localhost/htdocs/"]
这是图像的最终大小:
仓库 | 大小 |
---|---|
finalimage |
5.79 MB |
现在,您可以看到大小上有很大的差异——我们从 221 MB 减少到 217 MB,最终得到了一个 5.79 MB 的图像!这两个图像都完成了完全相同的任务,即提供网页服务,但占用的空间完全不同。
容器编排
现在我们知道如何创建我们的图像,我们需要一种方法来维护我们应用程序的期望状态。这就是容器编排器的作用。容器编排器回答了以下问题:
-
如何维护我的应用程序,使其具有高可用性?
-
如何根据需求扩展每个微服务?
-
如何在多个主机之间负载均衡我的应用程序?
-
如何限制我的应用程序在主机上的资源消耗?
-
如何轻松部署多个服务?
使用容器编排器,管理您的容器从未像现在这样简单或高效。有几种编排器可用,但最常用的是 Docker Swarm 和 Kubernetes。我们将在本章后面讨论 Kubernetes,并深入了解第七章中的核心组件。
所有编排器共同之处在于它们的基本架构是一个由一些主节点组成的集群,这些主节点监视您的期望状态,并将其保存在数据库中。主节点将根据工作节点的状态启动或停止容器,工作节点负责容器工作负载。每个主节点还负责根据您预定义的要求来指示哪个容器必须在哪个节点上运行,并根据需要扩展或重新启动任何失败的实例。
然而,编排器不仅通过重新启动和按需启动容器来提供高可用性,Kubernetes 和 Docker Swarm 还具有控制流量到后端容器的机制,以便为应用程序服务的传入请求提供负载均衡。
以下图表展示了流向编排集群的流量:
让我们更深入地探索 Kubernetes。
Kubernetes
Kubernetes 目前是迄今为止最受欢迎的容器编排器。许多公共云提供商现在将其作为事实上的容器编排器采用;例如,Azure 的Azure Kubernetes Services(AKS),亚马逊网络服务的弹性容器服务 for Kubernetes(EKS),以及谷歌云的Google Kubernetes Engine(GKE)。这些解决方案大多是托管的,为用户抽象出管理平面,以便使用,并采用与公共云负载均衡器和 DNS 服务集成的云原生解决方案。
Kubernetes 处于平台即服务(PaaS)解决方案和基础设施即服务(IaaS)解决方案之间,因为它为您提供了一个运行容器和管理数据的平台,但它仍然允许您提供软件定义的基础设施,如负载均衡器、网络管理、入口控制和资源分配。
通过 Kubernetes,我们可以自动化部署容器的过程,并在控制资源消耗的同时保持所需的状态,同时为不同的应用程序提供高可用性和隔离。
Kubernetes 具有我们之前提到的基本编排器组件;它有工作节点、主节点和保存集群状态的数据库。我们将在第七章中深入探讨 Kubernetes 的概念,理解 Kubernetes 集群的核心组件。
以下图表显示了 Kubernetes 的基本架构:
总结
在本章中,我们讨论了 IT 是如何从单块设计演变为微服务的,并且容器如何帮助我们通过允许模块化基础架构来实现这种架构。我们以在线商店为例,演示了微服务如何允许特定组件的可伸缩性,而无需关闭整个应用程序。此外,我们探讨了相同示例如何通过讨论微服务方法允许应用程序的一部分失败而不影响整个解决方案的高可用性设计(即,只有评论部分失败而不会导致整个在线商店崩溃)。
随后,我们学习了容器是如何通过 Dockerfile 从镜像创建的,Dockerfile 使用可读的一组指令来创建基础镜像。在 VM 的背景下,镜像可以被视为模板的对应物。
从这个 Dockerfile 中,我们学到了FROM
语句表示初始镜像是什么,LABEL
指令如何向容器添加元数据,RUN
如何执行需要准备容器运行应用程序的命令,以及ENV
如何设置用于构建容器的环境变量。
此外,我们讨论了构建容器镜像时的一些最佳实践,例如使用更小的镜像(如 Alpine),以及选择更小的镜像如何帮助减少构建容器中存在的漏洞数量。
最后,我们简要地介绍了一些更受欢迎的编排工具,如 Docker Swarm 和 Kubernetes。
在下一章中,我们将深入探讨 Kubernetes 集群的核心组件。
问题
-
Kubernetes 的组件是什么?
-
GKE、EKS 和 AKS 之间有什么区别?
-
容器免受利用的安全性有多高?
-
在容器中部署应用程序有多容易?
-
Docker 容器和 Kubernetes 是否专属于 Linux?
进一步阅读
-
Gigi Sayfan 的《精通 Kubernetes》:
www.packtpub.com/virtualization-and-cloud/mastering-kubernetes
-
Joseph Heck 的《面向开发人员的 Kubernetes》:
www.packtpub.com/virtualization-and-cloud/kubernetes-developers
-
Gigi Sayfan 的《使用 Kubernetes 进行微服务实践》:
www.packtpub.com/virtualization-and-cloud/hands-microservices-kubernetes
-
Jonathan Baier,Jesse White 的《Kubernetes 入门-第三版》:
www.packtpub.com/virtualization-and-cloud/getting-started-kubernetes-third-edition
-
Russ McKendrick,Scott Gallagher 的《精通 Docker-第二版》:
www.packtpub.com/virtualization-and-cloud/mastering-docker-second-edition
-
Russ McKendrick 等人的《Docker Bootcamp》:
www.packtpub.com/virtualization-and-cloud/docker-bootcamp
参考文献/来源
-
什么是微服务?:
microservices.io/
-
Docker Hub:
hub.docker.com/
-
生产级容器编排:
kubernetes.io/
第七章:理解 Kubernetes 集群的核心组件
在本章中,我们将从每个控制器的组成到如何部署和调度 pod 中的容器,对主要的 Kubernetes 组件进行一个全面的了解。了解 Kubernetes 集群的方方面面对于能够基于 Kubernetes 作为容器化应用程序的编排器部署和设计解决方案至关重要:
-
控制平面组件
-
Kubernetes 工作节点的组件
-
Pod 作为基本构建块
-
Kubernetes 服务、负载均衡器和 Ingress 控制器
-
Kubernetes 部署和 DaemonSets
-
Kubernetes 中的持久存储
Kubernetes 控制平面
Kubernetes 主节点是核心控制平面服务所在的地方;并非所有服务都必须驻留在同一节点上;然而,出于集中和实用性的考虑,它们通常以这种方式部署。这显然引发了服务可用性的问题;然而,通过拥有多个节点并提供负载平衡请求,可以轻松克服这些问题,从而实现高度可用的主节点集。
主节点由四个基本服务组成:
-
kube-apiserver
-
kube-scheduler
-
kube-controller-manager
-
etcd 数据库
主节点可以在裸金属服务器、虚拟机或私有或公共云上运行,但不建议在其上运行容器工作负载。我们稍后会详细了解更多。
以下图表显示了 Kubernetes 主节点的组件:
kube-apiserver
API 服务器是将所有内容联系在一起的东西。它是集群的前端 REST API,接收清单以创建、更新和删除诸如服务、pod、Ingress 等 API 对象。
kube-apiserver是我们应该与之交谈的唯一服务;它也是唯一一个写入并与etcd
数据库交谈以注册集群状态的服务。通过kubectl
命令,我们将发送命令与其交互。这将是我们在处理 Kubernetes 时的瑞士军刀。
kube-controller-manager
kube-controller-manager守护程序,简而言之,是一组无限控制循环,以简单的单个二进制文件的形式进行交付。它监视集群的定义期望状态,并确保通过移动实现所需的所有组件来实现和满足它。kube-controller-manager 不仅仅是一个控制器;它包含了集群中监视不同组件的几个不同循环。其中一些是服务控制器、命名空间控制器、服务账户控制器等。您可以在 Kubernetes GitHub 存储库中找到每个控制器及其定义:
github.com/kubernetes/kubernetes/tree/master/pkg/controller
。
kube-scheduler
kube-scheduler将您新创建的 pod 调度到具有足够空间满足 pod 资源需求的节点上。它基本上监听 kube-apiserver 和 kube-controller-manager,以获取新创建的 pod,并将其放入队列,然后由调度程序安排到可用节点上。kube-scheduler 的定义可以在这里找到:
github.com/kubernetes/kubernetes/raw/master/pkg/scheduler
。
除了计算资源外,kube-scheduler 还会读取节点的亲和性和反亲和性规则,以确定节点是否能够运行该 pod。
etcd 数据库
etcd数据库是一个非常可靠的一致性键值存储,用于存储 Kubernetes 集群的状态。它包含了节点正在运行的 pod 的当前状态,集群当前有多少个节点,这些节点的状态是什么,部署有多少个副本正在运行,服务名称等。
正如我们之前提到的,只有 kube-apiserver 与 etcd 数据库通信。如果 kube-controller-manager 需要检查集群的状态,它将通过 API 服务器获取 etcd 数据库的状态,而不是直接查询 etcd 存储。kube-scheduler 也是如此,如果调度程序需要通知某个 pod 已停止或分配到另一个节点,它将通知 API 服务器,API 服务器将在 etcd 数据库中存储当前状态。
通过 etcd,我们已经涵盖了 Kubernetes 主节点的所有主要组件,因此我们已经准备好管理我们的集群。但是,集群不仅由主节点组成;我们仍然需要执行重型工作并运行我们的应用程序的节点。
Kubernetes 工作节点
在 Kubernetes 中执行此任务的工作节点简单地称为节点。在 2014 年左右,它们曾被称为 minions,但后来这个术语被替换为节点,因为这个名称与 Salt 的术语混淆,并让人们认为 Salt 在 Kubernetes 中扮演了重要角色。
这些节点是您将运行工作负载的唯一位置,因为不建议在主节点上运行容器或负载,因为它们需要可用于管理整个集群。
节点在组件方面非常简单;它们只需要三个服务来完成任务:
-
kubelet
-
kube-proxy
-
容器运行时
让我们更深入地探讨这三个组件。
容器运行时
为了能够启动容器,我们需要一个容器运行时。这是将在节点内核中为我们的 pod 创建容器的基本引擎。kubelet 将与此运行时进行通信,并根据需要启动或停止我们的容器。
目前,Kubernetes 支持任何符合 OCI 规范的容器运行时,例如 Docker、rkt、runc、runsc 等。
您可以从 OCI GitHub 页面了解有关所有规范的更多信息:github.com/opencontainers/runtime-spec
。
kubelet
kubelet 是 Kubernetes 的一个低级组件,是继 kube-apiserver 之后最重要的组件之一;这两个组件对于在集群中提供 pod/容器至关重要。kubelet 是在 Kubernetes 节点上运行的一个服务,它监听 API 服务器以创建 pod。kubelet 只负责启动/停止并确保 pod 中的容器健康;kubelet 将无法管理未由其创建的任何容器。
kubelet 通过与容器运行时进行通信来实现目标,这是通过所谓的容器运行时接口(CRI)实现的。CRI 通过 gRPC 客户端为 kubelet 提供可插拔性,可以与不同的容器运行时进行通信。正如我们之前提到的,Kubernetes 支持多个容器运行时来部署容器,这就是它如何实现对不同引擎的多样化支持的方式。
您可以通过以下 GitHub 链接检查 kubelet 的源代码:github.com/kubernetes/kubernetes/tree/master/pkg/kubelet
。
kube-proxy
kube-proxy 是集群中每个节点上的一个服务,它使得 pod、容器和节点之间的通信成为可能。该服务监视 kube-apiserver 以获取定义的服务的更改(服务是 Kubernetes 中一种逻辑负载均衡器;我们将在本章后面更深入地了解服务),并通过 iptables 规则保持网络最新,以将流量转发到正确的端点。Kube-proxy 还在 iptables 中设置规则,对服务后面的 pod 进行随机负载平衡。
以下是 kube-proxy 创建的一个 iptables 规则的示例:
-A KUBE-SERVICES -d 10.0.162.61/32 -p tcp -m comment --comment "default/example: has no endpoints" -m tcp --dport 80 -j REJECT --reject-with icmp-port-unreachable
这是一个没有端点的服务(没有 pod 在其后面)。
现在我们已经了解了构成集群的所有核心组件,我们可以谈谈我们可以如何使用它们以及 Kubernetes 将如何帮助我们编排和管理我们的容器化应用程序。
Kubernetes 对象
Kubernetes 对象就是这样:它们是逻辑持久对象或抽象,将代表您集群的状态。您负责告诉 Kubernetes 您对该对象的期望状态,以便它可以努力维护它并确保该对象存在。
要创建一个对象,它需要具备两个要素:状态和规范。状态由 Kubernetes 提供,并且它是对象的当前状态。Kubernetes 将根据需要管理和更新该状态,以符合您的期望状态。另一方面,spec
字段是您提供给 Kubernetes 的内容,并且是您告诉它描述您所需对象的内容,例如,您希望容器运行的图像,您希望运行该图像的容器数量等。每个对象都有特定的spec
字段,用于执行其任务类型,并且您将在发送到 kube-apiserver 的 YAML 文件中提供这些规范,该文件将使用kubectl
将其转换为 JSON 并将其发送为 API 请求。我们将在本章后面更深入地了解每个对象及其规范字段。
以下是发送到kubectl
的 YAML 的示例:
cat << EOF | kubectl create -f -
kind: Service
apiVersion: v1
metadata:
Name: frontend-service
spec:
selector:
web: frontend
ports:
- protocol: TCP
port: 80
targetPort: 9256
EOF
对象定义的基本字段是最初的字段,这些字段不会因对象而异,并且非常直观。让我们快速浏览一下它们:
-
kind
:kind
字段告诉 Kubernetes 您正在定义的对象类型:pod、服务、部署等 -
apiVersion
:因为 Kubernetes 支持多个 API 版本,我们需要指定一个 REST API 路径,以便将我们的定义发送到该路径 -
metadata
:这是一个嵌套字段,这意味着您有更多的子字段可以写入 metadata,您可以在其中编写基本定义,例如对象的名称,将其分配给特定命名空间,并为其标记一个标签,以将您的对象与其他 Kubernetes 对象相关联
因此,我们现在已经了解了最常用的字段及其内容;您可以在以下 GitHub 页面了解有关 Kuberntes API 约定的更多信息:
github.com/kubernetes/community/raw/master/contributors/devel/api-conventions.md
。
对象的某些字段在创建对象后可以进行修改,但这将取决于对象和您要修改的字段。
以下是您可以创建的各种 Kubernetes 对象的简短列表:
-
Pod
-
卷
-
服务
-
部署
-
入口
-
秘钥
-
配置映射
还有许多其他内容。
让我们更仔细地看看这些项目中的每一个。
Pods - Kubernetes 的基础
Pod 是 Kubernetes 中最基本且最重要的对象。一切都围绕它们展开;我们可以说 Kubernetes 是为了 pod!所有其他对象都是为了服务它们,它们所做的所有任务都是为了使 pod 达到您期望的状态。
那么,什么是 pod,为什么 pod 如此重要?
Pod 是一个逻辑对象,在同一网络命名空间上运行一个或多个容器,相同的进程间通信(IPC),有时,根据 Kubernetes 的版本,还在相同的进程 ID(PID)命名空间上运行。这是因为它们将运行我们的容器,因此将成为关注的中心。Kubernetes 的整个目的是成为一个容器编排器,而通过 pod,我们使编排成为可能。
正如我们之前提到的,同一 pod 上的容器生活在一个“泡泡”中,它们可以通过 localhost 相互通信,因为它们彼此之间是本地的。一个 pod 中的一个容器与另一个容器具有相同的 IP 地址,因为它们共享网络命名空间,但在大多数情况下,您将以一对一的方式运行,也就是说,一个 pod 中只有一个容器。在非常特定的情况下才会在一个 pod 中运行多个容器,比如当一个应用程序需要一个数据推送器或需要以快速和有弹性的方式与主要应用程序通信的代理时。
定义 pod 的方式与定义任何其他 Kubernetes 对象的方式相同:通过包含所有 pod 规范和定义的 YAML:
kind: Pod
apiVersion: v1
metadata:
name: hello-pod
labels:
hello: pod
spec:
containers:
- name: hello-container
image: alpine
args:
- echo
- "Hello World"
让我们来看看在spec
字段下创建我们的 pod 所需的基本 pod 定义:
-
容器:容器是一个数组;因此,在它下面有一系列子字段。基本上,它定义了将在 pod 上运行的容器。我们可以为容器指定一个名称,要从中启动的图像,以及我们需要它运行的参数或命令。参数和命令之间的区别与我们在第六章中讨论的
CMD
和ENTRYPOINT
的区别相同,当时我们讨论了创建 Docker 镜像。请注意,我们刚刚讨论的所有字段都是针对containers
数组的。它们不是 pod 的spec
的直接部分。 -
restartPolicy: 这个字段就是这样:它告诉 Kubernetes 如何处理容器,在零或非零退出代码的情况下,它适用于 pod 中的所有容器。您可以从 Never、OnFailure 或 Always 中选择。如果未定义 restartPolicy,Always 将是默认值。
这些是您将在 pod 上声明的最基本的规范;其他规范将要求您对如何使用它们以及它们如何与各种其他 Kubernetes 对象进行交互有更多的背景知识。我们将在本章后面重新讨论它们,其中一些如下:
-
卷
-
Env
-
端口
-
dnsPolicy
-
initContainers
-
nodeSelector
-
资源限制和请求
要查看当前在集群中运行的 pod,可以运行kubectl get pods
:
dsala@MININT-IB3HUA8:~$ kubectl get pods
NAME READY STATUS RESTARTS AGE
busybox 1/1 Running 120 5d
或者,您可以运行kubectl describe pods
,而不指定任何 pod。这将打印出集群中运行的每个 pod 的描述。在这种情况下,只会有busybox
pod,因为它是当前唯一正在运行的 pod:
dsala@MININT-IB3HUA8:~$ kubectl describe pods
Name: busybox
Namespace: default
Priority: 0
PriorityClassName: <none>
Node: aks-agentpool-10515745-2/10.240.0.6
Start Time: Wed, 19 Sep 2018 14:23:30 -0600
Labels: <none>
Annotations: <none>
Status: Running
IP: 10.244.1.7
Containers:
busybox:
[...] (Output truncated for readability)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Pulled 45s (x121 over 5d) kubelet, aks-agentpool-10515745-2 Container image "busybox" already present on machine
Normal Created 44s (x121 over 5d) kubelet, aks-agentpool-10515745-2 Created container
Normal Started 44s (x121 over 5d) kubelet, aks-agentpool-10515745-2 Started container
Pods 是有生命期的,这是了解如何管理应用程序的关键。您必须明白,一旦 pod 死亡或被删除,就无法将其恢复。它的 IP 和在其上运行的容器将消失;它们是完全短暂的。作为卷挂载的 pod 上的数据可能会存活,也可能不会,这取决于您如何设置;然而,这是我们将在本章后面讨论的问题。如果我们的 pod 死亡并且我们失去它们,我们如何确保所有的微服务都在运行?嗯,部署就是答案。
部署
单独的 pod 并不是很有用,因为在单个 pod 中运行我们的应用程序的实例超过一个是不太有效率的。在没有一种方法来查找它们的情况下,在不同的 pod 上为我们的应用程序提供数百个副本将会很快失控。
这就是部署发挥作用的地方。通过部署,我们可以使用控制器来管理我们的 pod。这不仅允许我们决定要运行多少个,还可以通过更改容器正在运行的图像版本或图像本身来管理更新。部署是您大部分时间将要处理的内容。除了 pod 和我们之前提到的任何其他对象,它们在 YAML 文件中都有自己的定义:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
deployment: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
让我们开始探索它们的定义。
在 YAML 的开头,我们有更一般的字段,如apiVersion
,kind
和metadata
。但在spec
下,我们将找到此 API 对象的特定选项。
在spec
下,我们可以添加以下字段:
-
选择器:使用选择器字段,部署将知道在应用更改时要针对哪些 pod。在选择器下有两个字段:
matchLabels
和matchExpressions
。使用matchLabels
,选择器将使用 pod 的标签(键/值对)。重要的是要注意,您在这里指定的所有标签都将被ANDed
。这意味着 pod 将要求具有您在matchLabels
下指定的所有标签。matchExpressions
很少使用,但您可以通过阅读我们在进一步阅读部分推荐的书籍来了解更多信息。 -
副本:这将说明部署需要通过复制控制器保持运行的 pod 数量;例如,如果指定了三个副本,并且其中一个 pod 死亡,复制控制器将监视副本规范作为期望的状态,并通知调度程序安排一个新的 pod,因为当前状态现在是 2,因为 pod 死亡。
-
RevisionHistoryLimit:每次对部署进行更改,此更改都将保存为部署的修订版本,您稍后可以恢复到以前的状态,或者保留更改的记录。您可以使用
kubectl
rollout history deployment/<部署名称>来查看历史记录。使用revisionHistoryLimit
,您可以设置一个数字,指定要保存多少条记录。 -
策略:这将让您决定如何处理任何更新或水平 pod 扩展。要覆盖默认值(即
rollingUpdate
),您需要编写type
键,您可以在两个值之间进行选择:recreate
或rollingUpdate
。虽然recreate
是更新部署的快速方式,它将删除所有 pod 并用新的替换它们,但这意味着您必须考虑到这种策略将导致系统停机。另一方面,rollingUpdate
更加平稳和缓慢,非常适合可以重新平衡其数据的有状态应用程序。rollingUpdate
为另外两个字段打开了大门,这些字段是maxSurge
和maxUnavailable
。第一个字段将是在执行更新时您希望超出总数的 pod 数量;例如,具有 100 个 pod 和 20%maxSurge
的部署将在更新时增长到最多 120 个 pod。下一个选项将让您选择在 100 个 pod 场景中愿意杀死多少百分比的 pod 以用新的替换它们。在存在 20%maxUnavailable
的情况下,只有 20 个 pod 将被杀死并用新的替换,然后继续替换部署的其余部分。 -
模板:这只是一个嵌套的 pod spec 字段,您将在其中包含部署将要管理的 pod 的所有规范和元数据。
我们已经看到,通过部署,我们管理我们的 pod,并帮助我们将它们保持在我们期望的状态。所有这些 pod 仍然处于所谓的集群网络中,这是一个封闭的网络,其中只有 Kubernetes 集群组件可以相互通信,甚至有自己的一组 IP 范围。我们如何从外部与我们的 pod 通信?我们如何访问我们的应用程序?这就是服务发挥作用的地方。
服务
名称service并不能完全描述 Kubernetes 中服务的实际作用。Kubernetes 服务是将流量路由到我们的 pod 的东西。我们可以说服务是将 pod 联系在一起的东西。
假设我们有一个典型的前端/后端类型的应用程序,其中我们的前端 pod 通过 pod 的 IP 地址与我们的后端 pod 进行通信。如果后端的 pod 死掉,我们知道 pod 是短暂的,因此我们失去了与后端的通信,现在我们陷入了困境。这不仅是因为新的 pod 将不具有与死掉的 pod 相同的 IP 地址,而且现在我们还必须重新配置我们的应用程序以使用新的 IP 地址。这个问题和类似的问题都可以通过服务来解决。
服务是一个逻辑对象,告诉 kube-proxy 根据服务后面的哪些 pod 创建 iptables 规则。服务配置它们的端点,这是服务后面的 pod 的称呼方式,与部署了解要控制哪些 pod 一样,选择器字段和 pod 的标签。
这张图展示了服务如何使用标签来管理流量:
服务不仅会让 kube-proxy 创建路由流量的规则;它还会触发一些称为kube-dns的东西。
Kube-dns 是一组在集群上运行的SkyDNS
容器的 pod,提供 DNS 服务器和转发器,它将为服务和有时为了方便使用而创建的 pod 创建记录。每当您创建一个服务时,将创建一个指向服务内部集群 IP 地址的 DNS 记录,形式为service-name.namespace.svc.cluster.local
。您可以在 Kubernetes GitHub 页面上了解更多关于 Kubernetes DNS 规范的信息:github.com/kubernetes/dns/raw/master/docs/specification.md
。
回到我们的例子,现在我们只需要配置我们的应用程序以与服务的完全合格的域名(FQDN)通信,以便与我们的后端 pod 通信。这样,无论 pod 和服务具有什么 IP 地址都无关紧要。如果服务后面的 pod 死掉,服务将通过使用 A 记录来处理一切,因为我们将能够告诉我们的前端将所有流量路由到 my-svc。服务的逻辑将处理其他一切。
在声明要在 Kubernetes 中创建的对象时,您可以创建几种类型的服务。让我们逐个了解它们,看看哪种类型最适合我们需要的工作:
-
ClusterIP:这是默认服务。每当您创建 ClusterIP 服务时,它将创建一个仅在 Kubernetes 集群内可路由的集群内部 IP 地址的服务。这种类型非常适合只需要彼此交谈而不需要离开集群的 pod。
-
NodePort:当您创建这种类型的服务时,默认情况下将分配一个从
30000
到32767
的随机端口来转发流量到服务的端点 pod。您可以通过在ports
数组中指定节点端口来覆盖此行为。一旦定义了这一点,您将能够通过<Nodes-IP>
:<Node-Port>
访问您的 pod。这对于通过节点 IP 地址从集群外部访问您的 pod 非常有用。 -
LoadBalancer:大多数情况下,您将在云提供商上运行 Kubernetes。在这些情况下,LoadBalancer 类型非常理想,因为您将能够通过云提供商的 API 为您的服务分配公共 IP 地址。这是当您想要与集群外部的 pod 通信时的理想服务。使用 LoadBalancer,您不仅可以分配公共 IP 地址,还可以使用 Azure 从您的虚拟专用网络中分配私有 IP 地址。因此,您可以从互联网或在您的私有子网上内部与您的 pod 通信。
让我们回顾一下服务的 YAML 定义:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: front-end
type: NodePort
ports:
- name: http
port: 80
targetPort: 8080
nodePort: 30024
protocol: TCP
服务的 YAML 非常简单,规范会有所不同,取决于您正在创建的服务类型。但您必须考虑的最重要的事情是端口定义。让我们来看看这些:
-
port
:这是暴露的服务端口 -
targetPort
:这是服务发送流量到 Pod 的端口 -
nodePort
:这是将被暴露的端口。
虽然我们现在了解了如何与集群中的 Pods 进行通信,但我们仍然需要了解每次 Pod 终止时我们如何管理丢失数据的问题。这就是持久卷(PV)发挥作用的地方。
Kubernetes 和持久存储
在容器世界中,持久存储是一个严重的问题。当我们学习 Docker 镜像时,我们了解到唯一持久跨容器运行的存储是镜像的层,而且它们是只读的。容器运行的层是读/写的,但当容器停止时,该层中的所有数据都会被删除。对于 Pods 来说也是一样的。当容器死掉时,写入其中的数据也会消失。
Kubernetes 有一组对象来处理跨 Pod 的存储。我们将讨论的第一个对象是卷。
卷
卷解决了持久存储时的最大问题之一。首先,卷实际上不是对象,而是 Pod 规范的定义。当您创建一个 Pod 时,您可以在 Pod 规范字段下定义一个卷。此 Pod 中的容器将能够在其挂载命名空间上挂载卷,并且卷将在容器重新启动或崩溃时可用。但是,卷与 Pod 绑定,如果删除 Pod,卷也将消失。卷上的数据是另一回事;数据持久性将取决于该卷的后端。
Kubernetes 支持多种类型的卷或卷源以及它们在 API 规范中的称呼,这些类型包括来自本地节点的文件系统映射、云提供商的虚拟磁盘以及软件定义的存储支持的卷。当涉及到常规卷时,本地文件系统挂载是最常见的。需要注意的是使用本地节点文件系统的缺点是数据将不会在集群的所有节点上可用,而只在调度 Pod 的节点上可用。
让我们来看一下如何在 YAML 中定义一个带有卷的 Pod:
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /test-pd
name: test-volume
volumes:
- name: test-volume
hostPath:
path: /data
type: Directory
请注意spec
下有一个名为volumes
的字段,然后另一个名为volumeMounts
。
第一个字段(volumes
)是您为该 Pod 定义要创建的卷的位置。该字段将始终需要一个名称,然后是一个卷源。根据源的不同,要求也会有所不同。在这个例子中,源将是hostPath
,这是节点的本地文件系统。hostPath
支持多种类型的映射,从目录、文件、块设备,甚至 Unix 套接字。
在第二个字段volumeMounts
下,我们有mountPath
,这是您在容器内定义要将卷挂载到的路径。name
参数是您指定给 Pod 要使用哪个卷的方式。这很重要,因为您可以在volumes
下定义多种类型的卷,而名称将是 Pod 知道哪些卷挂载到哪个容器的唯一方式。
我们不会详细介绍所有不同类型的卷,因为除非你要使用特定的卷,否则了解它们是无关紧要的。重要的是要知道它们的存在以及我们可以拥有什么类型的来源。
您可以在 Kubernetes 网站的卷定义中了解更多关于不同类型的卷(kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes
)以及 Kubernetes API 参考文档中的卷定义(kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#volume-v1-core
)。
让卷随 Pod 一起消失并不理想。我们需要持久存储,这就是 PV 的需求产生的原因。
持久卷、持久卷索赔和存储类别
卷和 PV 之间的主要区别在于,与卷不同,PV 实际上是 Kubernetes API 对象,因此您可以像单独的实体一样单独管理它们,因此它们甚至在删除 pod 后仍然存在。
您可能想知道为什么这个小节中混合了 PV、持久卷索赔(PVC)和存储类别。这是因为我们不能谈论其中一个而不谈论其他;它们都彼此依赖,了解它们如何相互作用以为我们的 pod 提供存储是至关重要的。
让我们从 PV 和 PVC 开始。与卷一样,PV 具有存储源,因此卷具有的相同机制在这里也适用。您可以有一个软件定义的存储集群提供逻辑单元号(LUNs),云提供商提供虚拟磁盘,甚至是本地文件系统提供给 Kubernetes 节点,但是这里,它们被称为持久卷类型而不是卷源。
PV 基本上就像存储阵列中的 LUN:您创建它们,但没有映射;它们只是一堆已分配的存储,等待使用。这就是 PVC 发挥作用的地方。PVC 就像 LUN 映射:它们支持或绑定到 PV,也是您实际定义、关联和提供给 pod 以供其容器使用的东西。
您在 pod 上使用 PVC 的方式与普通卷完全相同。您有两个字段:一个用于指定要使用的 PVC,另一个用于告诉 pod 要在哪个容器上使用该 PVC。
PVC API 对象定义的 YAML 应该包含以下代码:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gluster-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
pod
的 YAML 应该包含以下代码:
kind: Pod
apiVersion: v1
metadata:
name: mypod
spec:
containers:
- name: myfrontend
image: nginx
volumeMounts:
- mountPath: "/mnt/gluster"
name: volume
volumes:
- name: volume
persistentVolumeClaim:
claimName: gluster-pvc
当 Kubernetes 管理员创建 PVC 时,有两种方式可以满足此请求:
-
静态:已经创建了几个 PV,然后当用户创建 PVC 时,可以满足要求的任何可用 PV 都将绑定到该 PVC。
-
动态:某些 PV 类型可以根据 PVC 定义创建 PV。创建 PVC 时,PV 类型将动态创建 PV 对象并在后端分配存储;这就是动态配置。动态配置的关键在于您需要第三种 Kubernetes 存储对象,称为存储类别。
存储类别就像是对存储进行分层的一种方式。您可以创建一个类别,用于提供慢速存储卷,或者另一个类别,其中包含超快速 SSD 驱动器。但是,存储类别比仅仅分层要复杂一些。正如我们在创建 PVC 的两种方式中提到的,存储类别是使动态配置成为可能的关键。在云环境中工作时,您不希望手动为每个 PV 创建每个后端磁盘。存储类别将设置一个称为提供程序的东西,它调用必要的卷插件以与您的云提供商的 API 进行通信。每个提供程序都有自己的设置,以便它可以与指定的云提供商或存储提供商进行通信。
您可以按以下方式配置存储类别;这是一个使用 Azure-disk 作为磁盘提供程序的存储类别的示例:
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: my-storage-class
provisioner: kubernetes.io/azure-disk
parameters:
storageaccounttype: Standard_LRS
kind: Shared
每个存储类别的提供程序和 PV 类型都有不同的要求和参数,以及卷,我们已经对它们的工作原理和用途有了一个总体概述。了解特定的存储类别和 PV 类型将取决于您的环境;您可以通过点击以下链接了解它们中的每一个:
-
kubernetes.io/docs/concepts/storage/storage-classes/#provisioner
-
kubernetes.io/docs/concepts/storage/persistent-volumes/#types-of-persistent-volumes
摘要
在本章中,我们了解了 Kubernetes 是什么,它的组件,以及使用编排的优势。
现在,您应该能够识别每个 Kubernetes API 对象,它们的目的和用例。您应该能够理解主节点如何控制集群以及工作节点中容器的调度。
问题
-
什么是 Kubernetes?
-
Kubernetes 的组件是什么?
-
Kubernetes 的 API 对象是什么?
-
我们可以用 Kubernetes 做什么?
-
什么是容器编排器?
-
什么是 Pod?
-
什么是部署?
进一步阅读
-
精通 Kubernetes,由 Packt Publishing 出版:
prod.packtpub.com/in/virtualization-and-cloud/mastering-kubernetes
-
面向开发人员的 Kubernetes,由 Packt Publishing 出版:
prod.packtpub.com/in/virtualization-and-cloud/kubernetes-developers
-
开始使用 Kubernetes,由 Packt Publishing 出版:
prod.packtpub.com/in/virtualization-and-cloud/getting-started-kubernetes-third-edition
第八章:设计 Kubernetes 集群
现在我们了解了组成 Kubernetes 集群的基础知识,我们仍然需要了解如何将所有 Kubernetes 组件放在一起,以及如何满足它们的要求来提供一个可用于生产的 Kubernetes 集群。
在这一章中,我们将研究如何确定这些要求以及它们将如何帮助我们维持稳定的工作负载并实现成功的部署。
在本章中,我们将探讨以下主题:
-
Kube-sizing
-
确定存储考虑因素
-
确定网络要求
-
自定义 kube 对象
Kube-sizing
在设计 Kubernetes 集群时,我们不仅需要担心如何配置部署对象来托管我们的应用程序,或者如何配置服务对象来提供跨我们的 pod 的通信,还需要考虑托管所有这些的位置。因此,我们还需要考虑所需的资源,以平衡我们的应用程序工作负载和控制平面。
etcd 考虑因素
我们将需要至少一个三节点的 etcd
集群,以便在一个节点失败的情况下能够支持自身。因为 etcd
使用一种称为 Raft 的分布式普查算法,所以建议使用奇数个集群。这是因为,为了允许某个操作,集群的成员超过 50% 必须同意。例如,在一个两节点集群的情况下,如果其中一个节点失败,另一个节点的投票只占集群的 50%,因此,集群失去了法定人数。现在,当我们有一个三节点集群时,单个节点的故障只代表了 33.33% 的投票损失,而剩下的两个节点的投票仍然占 66.66%,以允许该操作。
以下链接是一个很棒的网站,您可以在其中学习 Raft 算法的工作原理:thesecretlivesofdata.com/raft/
。
对于 etcd
,我们可以为我们的集群选择两种部署模型。我们可以将其运行在与我们的 kube-apiserver 相同的节点上,或者我们可以有一组单独的集群来运行我们的键值存储。无论哪种方式,这都不会改变 etcd
如何达成法定人数,因此您仍然需要在控制平面管理节点上安装奇数个 etcd
。
对于 Kubernetes 的使用情况,etcd
不会消耗大量的计算资源,如 CPU 或内存。尽管 etcd
会积极地缓存键值数据并使用大部分内存来跟踪观察者,但两个核心和 8 GB 的内存将是绰绰有余的。
当涉及到磁盘时,这就需要更加严格。etcd
集群严重依赖磁盘延迟,因为共识协议以日志的方式持久存储元数据。etcd
集群的每个成员都必须存储每个请求,任何延迟的重大波动都可能触发集群领导者选举,这将导致集群不稳定。etcd
的 硬盘驱动器(HDD)是不可能的,除非您在 Raid 0 磁盘上运行 15k RPM 磁盘以从磁性驱动器中挤出最高性能。固态硬盘(SSD)是最佳选择,具有极低的延迟和更高的 每秒输入/输出操作(IOPS),它们是托管您的键值存储的理想候选者。值得庆幸的是,所有主要的云提供商都提供 SSD 解决方案来满足这种需求。
kube-apiserver 大小
控制平面组件所需的剩余资源将取决于它们将管理的节点数量以及您将在其上运行的附加组件。需要考虑的另一件事是,您可以将这些主节点放在负载均衡器后面,以减轻负载并提供高可用性。此外,您还可以在争用期间始终水平扩展您的主节点。
考虑到所有这些,并考虑到etcd
将与我们的主节点一起托管,我们可以说,具有 2 到 4 个 vCPU 和 8 到 16 GB RAM 的虚拟机(VMs)的三个主节点集群将足以处理大于或等于 100 个工作节点。
工作节点
另一方面,工作节点将承担繁重的工作——这些节点将运行我们的应用工作负载。标准化这些节点的大小将是不可能的,因为它们属于假设发生什么的情景。我们需要确切地知道我们将在节点上运行什么类型的应用程序,以及它们的资源需求,以便我们能够正确地对其进行规模化。节点不仅将根据应用程序的资源需求进行规模化,而且我们还必须考虑在其中运行超过我们计划的 pod 的时期。例如,您可以对部署执行滚动更新以使用新的镜像,具体取决于您如何配置您的maxSurge
;这个节点将不得不处理 10%到 25%的额外负载。
容器非常轻量级,但当编排器开始运行时,您可以在单个节点上运行 30、40 甚至 100 个容器!这会大幅增加每个主机的资源消耗。虽然 pod 具有资源限制功能和规范来限制容器的资源消耗,但您仍然需要考虑这些容器所需的资源。
在资源需求高和争用期间,节点始终可以进行水平扩展。然而,始终可以使用额外的资源来避免任何不良的内存不足(OOMs)杀手。因此,通过拥有额外的资源池来规划未来和假设发生什么的情景。
负载均衡器考虑
我们的节点仍然需要与我们的 API 服务器通信,并且正如我们之前提到的,拥有多个主节点需要一个负载均衡器。当涉及到从我们的节点到主节点的负载均衡请求时,我们有几个选项可供选择,具体取决于您运行集群的位置。如果您在公共云中运行 Kubernetes,可以选择使用云提供商的负载均衡器选项,因为它们通常是弹性的。这意味着它们会根据需要自动扩展,并提供比您实际需要的更多功能。基本上,负载均衡请求到 API 服务器将是负载均衡器执行的唯一任务。这将导致我们的场景——因为我们在这里坚持使用开源解决方案,所以您可以配置运行 HAProxy 或 NGINX 的 Linux 框来满足您的负载均衡需求。在选择 HAProxy 和 NGINX 之间没有错误答案,因为它们提供了您所需的功能。
到目前为止,基本架构将如下截图所示:
存储考虑
存储需求并不像常规主机或虚拟化程序那样直截了当。我们的节点和 pod 将使用几种类型的存储,我们需要适当地对它们进行分层。由于您正在运行 Linux,将存储分层到不同的文件系统和存储后端将非常容易——没有逻辑卷管理器(LVM)或不同的挂载点无法解决的问题。
基本的 Kubernetes 二进制文件,如kubelet
和kube-proxy
,可以在基本存储上运行,与操作系统文件一起;不需要非常高端的存储,任何 SSD 都足以满足它们的需求。
另一方面,我们有存储,容器镜像将存储和运行。回到[第六章](6da53f60-978c-43a4-9dc9-f16b14405709.xhtml),创建高可用的自愈架构,我们了解到容器由只读层组成。这意味着当磁盘在单个节点上运行数十甚至数百个容器时,它们将受到读取请求的严重打击。用于此的存储后端将必须以非常低的延迟提供读取请求。在 IOPS 和延迟方面的具体数字将因环境而异,但基础将是相同的。这是因为容器的性质——提供更高读取性能而非写入的磁盘将更可取。
存储性能并不是唯一需要考虑的因素。存储空间也非常重要。计算所需空间将取决于以下两个因素:
-
你将要运行的镜像有多大?
-
你将运行多少不同的镜像,它们的大小是多少?
这将直接消耗/var/lib/docker
或/var/lib/containerd
中的空间。考虑到这一点,为/var/lib/docker
或containerd/
设置一个单独的挂载点,具有足够的空间来存储你将在 pod 上运行的所有镜像,将是一个不错的选择。请注意,这些镜像是临时的,不会永远存在于你的节点上。Kubernetes 确实在 kubelet 中嵌入了垃圾收集策略,当达到指定的磁盘使用阈值时,将删除不再使用的旧镜像。这些选项是HighThresholdPercent
和LowThresholdPercent
。你可以使用 kubelet 标志进行设置:--eviction-hard=imagefs.available
或--eviction-soft=imagefs.available
。这些标志已经默认配置为在可用存储空间低于 15%时进行垃圾收集,但是你可以根据需要进行调整。eviction-hard
是需要达到的阈值,以开始删除镜像,而eviction-soft
是需要达到的百分比或数量,以停止删除镜像。
一些容器仍将需要某种读/写卷以用于持久数据。如[第七章](d89f650b-f4ea-4cda-9111-a6e6fa6c2256.xhtml)中所讨论的,Kubernetes 集群的核心组件,有几种存储供应商,它们都适用于不同的场景。你需要知道的是,由于 Kubernetes 存储类别的存在,你有一系列可用的选项。以下是一些值得一提的开源软件定义存储解决方案:
-
Ceph
-
GlusterFS
-
OpenStack Cinder
-
网络文件系统(NFS)
每个存储供应商都有其优势和劣势,但详细介绍每个存储供应商已超出了本书的范围。我们在之前的章节中对 Gluster 进行了很好的概述,因为在后续章节中我们将用它来进行示例部署。
网络要求
为了了解我们集群的网络要求,我们首先需要了解 Kubernetes 网络模型以及它旨在解决的问题。容器网络可能很难理解;然而,它有三个基本问题:
-
容器如何相互通信(在同一台主机上和在不同的主机上)?
-
容器如何与外部世界通信,外部世界如何与容器通信?
-
谁分配和配置每个容器的唯一 IP 地址?
同一主机上的容器可以通过虚拟桥相互通信,您可以使用bridge-utils
软件包中的brctl
实用程序看到这一点。这由 Docker 引擎处理,称为 Docker 网络模型。容器通过分配 IP 来附加到名为docker0
的虚拟桥上的veth
虚拟接口。这样,所有容器都可以通过它们的veth
虚拟接口相互通信。Docker 模型的问题出现在容器分配在不同主机上,或者外部服务想要与它们通信时。为解决这个问题,Docker 提供了一种方法,其中容器通过主机的端口暴露给外部世界。请求进入主机 IP 地址的某个端口,然后被代理到该端口后面的容器。
这种方法很有用,但并非理想。您无法将服务配置为特定端口或在动态端口分配方案中—我们的服务将需要标志每次部署时连接到正确的端口。这可能会很快变得非常混乱。
为了避免这种情况,Kubernetes 实现了自己的网络模型,必须符合以下规则:
-
所有 pod 可以在没有网络地址转换(NAT)的情况下与所有其他 pod 通信
-
所有节点可以在没有 NAT 的情况下与所有 pod 通信
-
pod 看到的 IP 与其他人看到的 IP 相同
有几个开源项目可以帮助我们实现这个目标,最适合您的项目将取决于您的情况。以下是其中一些:
-
Project Calico
-
Weave Net
-
Flannel
-
Kube-router
为 pod 分配 IP 并使它们相互通信并不是唯一需要注意的问题。Kubernetes 还提供基于 DNS 的服务发现,因为通过 DNS 记录而不是 IP 进行通信的应用程序更有效和可扩展。
基于 Kubernetes 的 DNS 服务发现
Kubernetes 在其 kube-system 命名空间中部署了一个部署,我们将在本章后面重新讨论命名空间。该部署由一个包含一组容器的 pod 组成,形成一个负责在集群中创建所有 DNS 记录并为服务发现提供 DNS 请求的 DNS 服务器。
Kubernetes 还将创建一个指向上述部署的服务,并告诉 kubelet 默认配置每个 pod 的容器使用服务的 IP 作为 DNS 解析器。这是默认行为,但您可以通过在 pod 规范上设置 DNS 策略来覆盖此行为。您可以从以下规范中进行选择:
-
默认:这个是反直觉的,因为实际上并不是默认的。使用此策略,pod 将继承运行该 pod 的节点的名称解析。例如,如果一个节点配置为使用
8.8.8.8
作为其 DNS 服务器,那么resolv.conf
中的 pod 也将被配置为使用相同的 DNS 服务器。 -
ClusterFirst:这实际上是默认策略,正如我们之前提到的,任何使用 ClusterFirst 运行的 pod 都将使用
kube-dns
服务的 IP 配置resolv.conf
。不是本地集群的任何请求都将转发到节点配置的 DNS 服务器。
并非所有 Kubernetes 对象都具有 DNS 记录。只有服务和在某些特定情况下,pod 才会为它们创建记录。DNS 服务器中有两种类型的记录:A 记录和服务记录(SRV)。A 记录是根据创建的服务类型创建的;我们这里指的不是spec.type
。有两种类型的服务:普通服务,我们在第七章中进行了修订,理解 Kubernetes 集群的核心组件,并对应于type
规范下的服务;和无头服务。在解释无头服务之前,让我们探讨普通服务的行为。
对于每个普通服务,将创建指向服务的集群 IP 地址的 A 记录;这些记录的结构如下:
<service-name>.<namespace>.svc.cluster.local
与服务运行在相同命名空间的任何 pod 都可以通过其shortname: <service-name>
字段解析服务。这是因为命名空间之外的任何其他 pod 都必须在 shortname 实例之后指定命名空间:
<service-name>.<namespace>
对于无头服务,记录的工作方式有些不同。首先,无头服务是一个没有分配集群 IP 的服务。因此,无法创建指向服务 IP 的 A 记录。要创建无头服务,您以这种方式定义.spec.clusterIP
命名空间为none
,以便不为其分配 IP。然后,Kubernetes 将根据此服务的端点创建 A 记录。基本上,通过selector
字段选择 pod,尽管这不是唯一的要求。由于 A 记录的创建格式,pod 需要几个新字段,以便 DNS 服务器为它们创建记录。
Pods 将需要两个新的规范字段:hostname
和subdomain
。hostname
字段将是 pod 的hostname
字段,而subdomain
将是您为这些 pod 创建的无头服务的名称。这将指向每个 pod 的 IP 的 A 记录如下:
<pod hostname>.<subdomian/headless service name>.<namespace>.svc.cluster.local
此外,将创建另一个仅包含无头服务的记录,如下所示:
<headless service>.<namespace>.svc.cluster.local
此记录将返回服务后面所有 pod 的 IP 地址。
我们现在已经有了开始构建我们的集群所需的东西。但是,还有一些设计特性不仅包括 Kubernetes 二进制文件及其配置,还可以调整 Kubernetes API 对象。我们将在下一节中介绍一些您可以执行的调整。
自定义 kube 对象
在涉及 Kubernetes 对象时,一切都将取决于您尝试为其构建基础架构的工作负载或应用程序的类型。因此,与其设计或构建任何特定的自定义,我们将介绍如何在每个对象上配置最常用和有用的规范。
命名空间
Kubernetes 提供命名空间作为将集群分割成多个虚拟集群的一种方式。将其视为一种将集群资源和对象进行分割并使它们在逻辑上相互隔离的方式。
命名空间只会在非常特定的情况下使用,但 Kubernetes 带有一些预定义的命名空间:
-
默认:这是所有没有命名空间定义的对象将放置在其中的默认命名空间。
-
kube-system:由 Kubernetes 集群创建的和为其创建的任何对象都将放置在此命名空间中。用于集群基本功能的必需对象将放置在这里。例如,您将找到
kube-dns
,kubernetes-dashboard
,kube-proxy
或任何外部应用程序的其他组件或代理,例如fluentd
,logstash
,traefik
和入口控制器。 -
kube-public:为任何人可见的对象保留的命名空间,包括非经过身份验证的用户。
创建命名空间非常简单直接;您可以通过运行以下命令来执行:
kubectl create namespace <name>
就是这样——现在您有了自己的命名空间。要将对象放置在此命名空间中,您将使用metadata
字段并添加namespace
键值对;例如,考虑来自 YAML pod 的这段摘录:
apiVersion: v1
kind: Pod
metadata:
namespace: mynamespace
name: pod1
您将发现自己为通常非常庞大并且有相当数量的用户或不同团队在使用资源的集群创建自定义命名空间。对于这些类型的场景,命名空间非常完美。命名空间将允许您将一个团队的所有对象与其余对象隔离开来。名称甚至可以在相同的类对象上重复,只要它们在不同的命名空间中。
命名空间不仅为对象提供隔离,还可以为每个命名空间设置资源配额。假设您有几个开发团队在集群上工作——一个团队正在开发一个非常轻量级的应用程序,另一个团队正在开发一个非常占用资源的应用程序。在这种情况下,您不希望第一个开发团队从资源密集型应用程序团队那里消耗任何额外的计算资源——这就是资源配额发挥作用的地方。
限制命名空间资源
资源配额也是 Kubernetes API 对象;但是,它们被设计为专门在命名空间上工作,通过在每个分配的空间上创建计算资源的限制,甚至限制对象的数量。
ResourceQuota
API 对象与 Kubernetes 中的任何其他对象一样,通过传递给kubectl
命令的YAML
文件声明。
基本资源配额定义如下:
apiVersion: v1
kind: ResourceQuota
Metadata:
Namespace: devteam1
name: compute-resources
spec:
hard:
pods: "4"
requests.cpu: "1"
requests.memory: 1Gi
limits.cpu: "2"
limits.memory: 2Gi
我们可以设置两种基本配额:计算资源配额和对象资源配额。如前面的例子所示,pods
是对象配额,其余是计算配额。
在这些领域中,您将指定提供的资源的总和,命名空间不能超过。例如,在此命名空间中,运行的pods
的总数不能超过4
,它们的资源总和不能超过1
个 CPU 和2Gi
RAM 内存。
可以为任何可以放入命名空间的 kube API 对象分配的每个命名空间的最大对象数;以下是可以使用命名空间限制的对象列表:
-
持久卷索赔(PVCs)
-
服务
-
秘密
-
配置映射
-
复制控制器
-
部署
-
副本集
-
有状态集
-
作业
-
定期作业
在计算资源方面,不仅可以限制内存和 CPU,还可以为存储空间分配配额——但是这些配额仅适用于 PVCs。
为了更好地理解计算配额,我们需要更深入地了解这些资源是如何在 pod 基础上管理和分配的。这也是一个很好的时机来了解如何更好地设计 pod。
自定义 pod
在非受限命名空间上没有资源限制的 pod 可以消耗节点的所有资源而不受警告;但是,您可以在 pod 的规范中使用一组工具来更好地处理它们的计算分配。
当您为 pod 分配资源时,实际上并不是将它们分配给 pod。相反,您是在容器基础上进行分配。因此,具有多个容器的 pod 将为其每个容器设置多个资源约束;让我们考虑以下示例:
apiVersion: v1
kind: Pod
metadata:
name: frontend
spec:
containers:
- name: db
image: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "password"
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
- name: wp
image: wordpress
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
在此 pod 声明中,在containers
定义下,我们有两个尚未涵盖的新字段:env
和resources
。resources
字段包含我们的containers
的计算资源限制和要求。通过设置limits
,您告诉容器它可以要求该资源类型的最大资源数量。如果容器超过限制,将重新启动或终止。
request
字段指的是 Kubernetes 将向该容器保证的资源量。为了使容器能够运行,主机节点必须有足够的空闲资源来满足请求。
CPU 和内存以不同的方式进行测量。例如,当我们分配或限制 CPU 时,我们使用 CPU 单位进行讨论。设置 CPU 单位有几种方法;首先,您可以指定圆整或分数,例如 1、2、3、0.1 和 1.5,这将对应于您要分配给该容器的虚拟核心数。另一种分配的方法是使用milicore表达式。一个 milicore(1m),是您可以分配的最小 CPU 数量,相当于 0.001 CPU 核心;例如,您可以进行以下分配:
cpu: "250m"
这将与编写以下内容相同:
cpu: 0.25
分配 CPU 的首选方式是通过 Millicores,因为 API 会将整数转换为 Millicores。
对于内存分配,您可以使用普通的内存单位,如千字节或基字节;其他内存单位也是如此,如 E、P、T、G 和 M。
回到资源配额,我们可以看到单个容器资源管理将如何与命名空间中的资源配额一起发挥作用。这是因为资源配额将告诉我们在容器中每个命名空间中可以设置多少限制和请求。
我们没有修改的第二个字段是env
字段。通过env
,我们为容器配置环境变量。通过变量声明,我们可以将设置、参数、密码和更多配置传递给我们的容器。在 pod 中声明变量的最简单方式如下:
...
env:
- name: VAR
value: “Hello World”
现在容器将可以在其 shell 中访问VAR
变量内容,称为$VAR
。正如我们之前提到的,这是声明变量并为其提供值的最简单方式。然而,这并不是最有效的方式——当您以这种方式声明值时,该值将仅存在于 pod 声明中。
如果我们需要编辑值或将该值传递给多个 pod,这将变得很麻烦,因为您需要在每个需要它的 pod 上键入相同的值。这就是我们将介绍另外两个 Kubernetes API 对象:Secrets
和ConfigMaps
的地方。
通过ConfigMaps
和Secrets
,我们可以以持久且更模块化的形式存储变量的值。实质上,ConfigMaps
和Secrets
是相同的,但是 secrets 中包含的值是以base64
编码的。Secrets 用于存储诸如密码或私钥等敏感信息,基本上是任何类型的机密数据。您不需要隐藏的所有其他数据都可以通过ConfigMap
传递。
创建这两种类型的对象的方式与 Kubernetes 中的任何其他对象相同——通过YAML
。您可以按以下方式创建ConfigMap
对象:
apiVersion: v1
kind: ConfigMap
metadata:
name: my-config
data:
super.data: much-data
very.data: wow
在这个定义上唯一的区别,与本章中的所有其他定义相比,就是我们缺少了specification
字段。相反,我们有data
,在其中我们将放置包含我们想要存储的数据的键值对。
对于Secrets
,这个过程有点不同。这是因为我们需要存储的密钥的值必须进行编码。为了将值存储在秘密的键中,我们将值传递给base64
,如下所示:
[dsala@RedFedora]$ echo -n “our secret” | base64
WW91IEhhdmUgRGVjb2RlZCBNeSBTZWNyZXQhIENvbmdyYXR6IQ==
当我们有字符串的base64
哈希时,我们就可以创建我们的秘密了。
以下代码块显示了一个使用base64
配置了秘密值的YAML
文件:
apiVersion: v1
kind: Secret
metadata:
name: kube-secret
type: Opaque
data:
password: WW91IEhhdmUgRGVjb2RlZCBNeSBTZWNyZXQhIENvbmdyYXR6IQ==
使用我们的ConfigMaps
和Secrets
对象在 pod 中,我们在env
数组中使用valueFrom
字段:
apiVersion: v1
kind: Pod
metadata:
name: secret-pod
spec:
containers:
- name: secret-container
image: busybox
env:
- name: SECRET_VAR
valueFrom:
secretKeyRef:
name: kube-secret
key: password
在这里,secretKeyRef
下的名称对应于Secret
API 对象的名称,而key
是Secret
中data
字段中的key
。
对于ConfigMaps
,它看起来几乎一样;但是,在valueFrom
字段中,我们将使用configMapKeyRef
而不是secretKeyRef
。
ConfigMap
声明如下:
…
env:
- name: CONFMAP_VAR
valueFrom:
configMapKeyRef:
name: my-config
key: very.data
现在您已经了解了定制 pod 的基础知识,您可以查看一个真实的示例,网址为kubernetes.io/docs/tutorials/configuration/configure-redis-using-configmap/
。
摘要
在本章中,我们学习了如何确定 Kubernetes 集群的计算和网络要求。我们还涉及了随之而来的软件要求,比如etcd
,以及为什么奇数编号的集群更受青睐(由于人口普查算法),因为集群需要获得超过 50%的选票才能达成共识。
etcd
集群可以在 kube-apiserver 上运行,也可以有一个专门用于etcd
的独立集群。在资源方面,2 个 CPU 和 8GB 的 RAM 应该足够了。在决定etcd
的存储系统时,选择延迟较低、IOPS 较高的存储,如 SSD。然后我们开始调整 kube-apiserver 的大小,它可以与etcd
一起运行。鉴于这两个组件可以共存,每个节点的资源应该在 8 到 16GB 的 RAM 和 2 到 4 个 CPU 之间。
为了正确调整工作节点的大小,我们必须记住这是实际应用工作负载将要运行的地方。这些节点应根据应用程序要求进行调整,并且在可能会运行超过计划数量的 pod 的时期,例如在滚动更新期间,应考虑额外的资源。继续讨论集群的要求,我们提到负载均衡器如何通过在集群中平衡请求来帮助主节点的通信。
Kubernetes 的存储需求可能会非常庞大,因为许多因素可能会影响整体设置,倾向于选择有利于读取而不是写入的存储系统更可取。此外,Kubernetes 的一些最常见的存储提供者如下:
-
Ceph
-
GlusterFS(在第二章中涵盖,定义 GlusterFS 存储到第五章,分析 Gluster 系统的性能)
-
OpenStack Cinder
-
NFS
然后我们转向网络方面,了解了 Kubernetes 如何提供诸如基于 DNS 的服务发现之类的服务,负责在集群中创建所有 DNS 记录并为服务发现提供 DNS 请求。Kubernetes 中的对象可以定制以适应每个工作负载的不同需求,而诸如命名空间之类的东西被用作将您的集群分成多个虚拟集群的一种方式。资源限制可以通过资源配额来实现。
最后,可以定制 pod 以允许分配绝对最大的资源,并避免单个 pod 消耗所有工作节点的资源。我们详细讨论了各种存储考虑和要求,包括如何定制 kube 对象和 pod。
在下一章中,我们将介绍如何部署 Kubernetes 集群并学习如何配置它。
问题
-
为什么奇数编号的
etcd
集群更受青睐? -
etcd
可以与 kube-apiserver 一起运行吗? -
为什么建议
etcd
的延迟较低? -
什么是工作节点?
-
在调整工作节点大小时应考虑什么?
-
Kubernetes 的一些存储提供者是什么?
-
为什么需要负载均衡器?
-
命名空间如何使用?
进一步阅读
-
掌握 Kubernetes 作者 Gigi Sayfan:
www.packtpub.com/virtualization-and-cloud/mastering-kubernetes
-
面向开发人员的 Kubernetes 作者 Joseph Heck:
www.packtpub.com/virtualization-and-cloud/kubernetes-developers
-
使用 Kubernetes 进行微服务实践 作者 Gigi Sayfan:
www.packtpub.com/virtualization-and-cloud/hands-microservices-kubernetes
-
开始使用 Kubernetes-第三版 作者 Jonathan Baier,Jesse White:
www.packtpub.com/virtualization-and-cloud/getting-started-kubernetes-third-edition
-
掌握 Docker-第二版 作者 Russ McKendrick,Scott Gallagher:
www.packtpub.com/virtualization-and-cloud/mastering-docker-second-edition
-
《Docker Bootcamp》由 Russ McKendrick 等人编写:
www.packtpub.com/virtualization-and-cloud/docker-bootcamp
第九章:部署和配置 Kubernetes
在了解了 Kubernetes 内部组件及其相互作用方式之后,现在是时候学习如何设置它们了。手动安装 Kubernetes 集群可能是一个非常痛苦和微妙的过程,但通过完成所需的步骤,我们可以更好地学习和理解其内部组件。在执行手动安装后,我们还可以探索其他可用于自动化此过程的替代方案和工具。以下是本章将学习的内容的摘要:
-
创建我们的计算环境
-
引导控制平面
-
引导工作节点
-
配置集群网络和 DNS 设置
-
托管 Kubernetes 服务的示例
通过每一步,我们将更接近完成 Kubernetes 的完整安装,并准备在开发环境中进行测试。
基础设施部署
为了部署将运行我们的 Kubernetes 集群的基础设施,我们将使用 Microsoft Azure。您可以通过创建免费试用或使用任何其他公共云提供商,或者您自己的本地 IT 基础设施来跟随。具体步骤将取决于您的选择。
安装 Azure CLI
在使用 Linux 时,在 Azure 中部署资源有两种方式:您可以从门户或通过 Azure CLI 进行。我们将两者都使用,但用于不同的场景。
让我们开始在我们的 Linux 工作站或 Windows 子系统上安装 Azure CLI。
请注意,所有命令都假定由具有 root 权限的帐户或 root 帐户本身发出(但这并不推荐)。
对于基于 RHEL/Centos 的发行版,您需要执行以下步骤:
- 下载并
import
存储库密钥,如下命令所示:
rpm --import https://packages.microsoft.com/keys/microsoft.asc
- 创建存储库配置文件,如下命令所示:
cat << EOF > /etc/yum.repos.d/azure-cli.repo
[azure-cli]
name=Azure CLI
baseurl=https://packages.microsoft.com/yumrepos/azure-cli
enabled=1
gpgcheck=1
gpgkey=https://packages.microsoft.com/keys/microsoft.asc
EOF
- 使用以下命令安装
azure-cli
:
yum install azure-cli
- 使用以下命令登录到您的 Azure 订阅:
az login
如果您不在桌面环境中,您可以使用:az login --use-device-code,因为常规的“az login”需要通过 Web 浏览器执行登录。
安装 Azure CLI 后,我们仍然需要设置一些默认值,这样我们就不必一遍又一遍地输入相同的标志选项。
配置 Azure CLI
Azure 上的每个资源都位于资源组和地理位置中。因为我们所有的资源都将位于同一个资源组和位置中,让我们将它们配置为默认值。要做到这一点,请运行以下命令:
az configure --defaults location=eastus group=Kube_Deploy
对于我们的示例,我们将使用“东部”作为位置,因为这是离我们最近的位置。组名将取决于您将如何命名您的资源组-在我们的情况下,是Kube_Deploy
。
配置了默认值后,让我们继续实际创建包含我们资源的资源组,使用以下命令:
az group create -n “Kube_Deploy”
高级设计概述
创建了我们的资源组并选择了我们的位置后,让我们通过以下代码高层次地查看我们将要创建的设计:
<design picture>
我们现在需要注意的重要事项是 VM 的数量、网络架构和防火墙规则,因为这些是我们将直接在我们的第一步中配置的元素。
在我们开始配置资源之前,让我们先看一下我们的网络需求。
我们有以下要求:
-
以下三组不同的、不重叠的子网:
-
VM 子网
-
Pod 子网
-
服务子网
-
以下资源的静态分配的 IP 地址:
-
主节点
-
工作节点
-
管理 VM
-
负载均衡器的公共 IP
-
DNS 服务器
对于我们的 VM 子网,我们将使用以下地址空间:
192.168.0.0/24
服务 CIDR 将如下:
10.20.0.0/24
最后,我们的 POD CIDR 将会更大一些,以便它可以分配更多的 POD,如下面的代码所示:
10.30.0.0/16
现在让我们开始配置我们需要使这种架构成为可能的网络资源。
配置网络资源
首先,我们将创建包含 VM 子网的虚拟网络。要做到这一点,运行以下命令:
az network vnet create -n kube-node-vnet \
--address-prefix 192.168.0.0/16 \
--subnet-name node-subnet \
--subnet-prefix 192.168.0.0/24
这个命令中的两个关键点是address-prefix
标志和subnet-prefix
标志。
使用address-prefix
标志,我们将指定定义 VNET 上可以放置哪些子网的地址空间。例如,我们的 VNET 前缀是192.16.0.0/16
。这意味着我们不能在这个 CIDR 之外放置任何地址;例如,10.0.0.0/24
是行不通的。
子网前缀将是分配给连接到我们子网的 VM 的地址空间。现在我们已经创建了我们的 VNET 和子网,我们需要一个静态的公共 IP 地址。在 Azure 和任何公共云提供商中,公共 IP 是与 VM 分开的资源。
通过运行以下命令来创建我们的公共 IP:
az network public-ip create -n kube-api-pub-ip \
--allocation-method Static \
--sku Standard
创建后,可以通过运行以下查询来记录 IP:
az network public-ip show -n kube-api-pub-ip --query "ipAddress"
有了我们的 VNET、子网和公共 IP 分配完毕,我们只需要最后一个资源,一个防火墙,为我们的 VMS 提供安全保障。在 Azure 中,防火墙称为网络安全组(NSG)。创建 NSG 的过程非常简单,如下命令所示:
az network nsg create -n kube-nsg
创建 NSG 后,我们使用以下命令将 NSG 分配给我们的子网:
az network vnet subnet update -n node-subnet \
--vnet-name kube-node-vnet \
--network-security-group kube-nsg
配置计算资源
网络设置完成后,我们准备开始创建一些 VM。但在创建任何 VM 之前,我们需要创建用于访问我们的 VM 的 SSH 密钥。
我们将创建的第一对密钥是用于我们的管理 VM。这个 VM 将是唯一一个可以从外部世界访问 SSH 的 VM。出于安全原因,我们不希望暴露任何集群节点的22
端口。每当我们想要访问我们的任何节点时,我们都将从这个 VM 中进行访问。
要创建 SSH 密钥,请在您的 Linux 工作站上运行ssh-keygen
:
ssh-keygen
现在让我们使用以下命令创建管理 VM:
az vm create -n management-vm \
--admin-username <USERNAME> \
--size Standard_B1s \
--image CentOS \
--vnet-name kube-node-vnet \
--subnet node-subnet \
--private-ip-address 192.168.0.99 \
--nsg kube-nsg \
--ssh-key-value ~/.ssh/id_rsa.pub
记得用所需的用户名替换<USERNAME>
字段。
下一步是我们需要配置第一个 NSG 规则的地方。这个规则将允许来自我们自己网络的流量进入我们的管理 VM 的22
端口,这样我们就可以通过 SSH 连接到它。让我们使用以下命令设置这个规则:
az network nsg rule create --nsg-name kube-nsg \
-n mgmt_ssh_allow \
--direction Inbound \
--priority 100 \
--access Allow \
--description "Allow SSH From Home" \
--destination-address-prefixes '192.168.0.99' \
--destination-port-ranges 22 \
--protocol Tcp \
--source-address-prefixes '<YOUR IP>' \
--source-port-ranges '*' \
--direction Inbound
source-address-prefixes
是您的 ISP 提供的公共 IP 地址,因为这些 IP 可能是动态的,如果发生变化,您可以在 Azure 门户中的网络安全组规则中编辑 IP。
现在让我们连接到我们的 VM,创建 SSH 密钥,以便我们可以连接到我们的集群 VM。要检索我们管理 VM 的公共 IP 地址,运行以下查询:
az vm show -d -n management-vm --query publicIps
现在让我们使用之前创建的私钥 SSH 进入我们的 VM,如下所示:
ssh <username>@<public ip> -i <path to private key>
只有在您使用与创建密钥对的用户不同的用户登录时,才需要指定私钥。
现在我们在管理 VM 中,再次运行ssh-keygen
,最后退出 VM。
为了在 Azure 数据中心发生灾难时提供高可用性,我们的主节点将位于可用性集上。让我们创建可用性集。
如果您不记得可用性集是什么,您可以回到我们的 Gluster 章节,重新了解它的功能。
要创建可用性集,请运行以下命令:
az vm availability-set create -n control-plane \
--platform-fault-domain-count 3 \
--platform-update-domain-count 3
现在我们可以继续创建我们的第一个控制平面节点。让我们首先将我们管理 VM 的公共 SSH 密钥保存到一个变量中,以便将密钥传递给主节点,如下命令所示:
MGMT_KEY=$(ssh <username>@<public ip> cat ~/.ssh/id_rsa.pub)
要创建三个控制器节点,请运行以下for
循环:
for i in 1 2 3; do
az vm create -n kube-controller-${i} \
--admin-username <USERNAME> \
--availability-set control-plane \
--size Standard_B2s \
--image CentOS \
--vnet-name kube-node-vnet \
--subnet node-subnet \
--private-ip-address 192.168.0.1${i} \
--public-ip-address "" \
--nsg kube-nsg \
--ssh-key-value ${MGMT_KEY};
done
我们在这些 VM 上使用的大小很小,因为这只是一个测试环境,我们实际上不需要很多计算资源。在真实环境中,我们会根据我们在第八章中探讨的考虑因素来确定 VM 的大小,设计一个 Kubernetes 集群。
最后但并非最不重要的,我们使用以下命令创建我们的工作节点:
for i in 1 2; do
az vm create -n kube-node-${i} \
--admin-username <USERNAME>\
--size Standard_B2s \
--image CentOS \
--vnet-name kube-node-vnet \
--subnet node-subnet \
--private-ip-address 192.168.0.2${i} \
--public-ip-address "" \
--nsg kube-nsg \
--ssh-key-value ${MGMT_KEY}
done
准备管理 VM
创建了控制器和工作节点后,我们现在可以登录到我们的管理 VM 并开始安装和配置我们将需要引导我们的 Kubernetes 集群的工具。
从现在开始,我们大部分时间将在管理 VM 上工作。让我们 SSH 到 VM 并开始安装我们的工具集。
首先,我们需要下载工具来创建我们集群服务之间通信所需的证书。
首先使用以下命令安装依赖项:
johndoe@management-vm$ sudo yum install git gcc
johndoe@management-vm$ sudo wget -O golang.tgz https://dl.google.com/go/go1.11.1.linux-amd64.tar.gz
johndoe@management-vm$ sudo tar -C /usr/local -xzvf golang.tgz
安装了Go lang后,您需要更新您的PATH
变量并创建一个名为GOPATH
的新变量。您的 TLS 证书生成工具 CFFSL 将安装在此路径下。为此,您可以执行以下操作:
johndoe@management-vm$ sudo cat << EOF > /etc/profile.d/paths.sh
export PATH=$PATH:/usr/local/go/bin:/usr/local/bin
export GOPATH=/usr/local/
EOF
然后运行以下命令在当前 shell 中加载变量:
johndoe@management-vm$ sudo source /etc/profile.d/paths.sh
变量设置好后,现在我们准备好使用以下命令获取我们的cffsl
工具包:
johndoe@management-vm$ go get -u github.com/cloudflare/cfssl/cmd/cfssl
johndoe@management-vm$ go get -u github.com/cloudflare/cfssl/cmd/cfssljson
这两个二进制文件将保存在我们的GOPATH
变量下。
生成证书
安装了 CFSSL 二进制文件并加载到我们的PATH
中后,我们可以开始生成我们的证书文件。在此安装的这一部分中,我们将生成大量文件,因此最好创建一个目录结构来适当存储它们。
证书颁发机构
我们需要生成的第一个文件是用于签署其余组件证书的证书颁发机构文件。
我们将把所有证书存储在~/certs/
目录下,但首先我们需要创建该目录。让我们使用以下命令设置这个目录:
johndoe@management-vm$ mkdir ~/certs
现在我们有了目录,让我们从以下命令开始生成 CA 配置文件,其中将包含由我们的 CA 签发的证书的到期日期以及 CA 将用于什么目的的信息:
johndoe@management-vm$ cd ~/certs
johndoe@management-vm$ cat << EOF > ca-config.json
{
"signing": {
"default": {
"expiry": "8760h"
},
"profiles": {
"kubernetes": {
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
],
"expiry": "8760h"
}
}
}
}
EOF
有了我们的 CA 配置,现在我们可以开始签发证书签名请求。
我们要生成的第一个 CSR 是用于我们的 CA 的。让我们使用以下命令设置这个:
johndoe@management-vm$ cat << EOF > ca-csr.json
{
"CN": "Kubernetes",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "New York",
"O": "Kubernetes",
"OU": "CA",
"ST": "NY"
}
]
}
EOF
现在我们有了两个JSON
文件,我们实际上可以使用cffsl
并使用以下命令生成我们的证书:
johndoe@management-vm$ cfssl gencert -initca ca-csr.json | cfssljson -bare ca
如下命令所示,将生成三个文件,ca.csr
,ca.pem
和ca-key.pem
。第一个ca.csr
是证书签名请求。另外两个分别是我们的公共证书和私钥:
johndoe@management-vm$ ls
ca-config.json ca.csr ca-csr.json ca-key.pem ca.pem
从现在开始,我们生成的任何证书都将是这种情况。
客户端证书
现在我们的 CA 已配置并生成了其证书文件,我们可以开始为我们的管理员用户和每个工作节点上的 kubelet 签发证书。
我们要创建的过程和文件与 CA 的非常相似,但在生成它们所使用的命令中有轻微的差异。
使用以下命令创建我们的admin certs
目录:
johndoe@management-vm$ mkdir ~/certs/admin/
johndoe@management-vm$ cd ~/certs/admin/
首先,创建管理员用户证书。此证书是供我们的管理员通过kubectl
管理我们的集群使用的。
同样,我们将使用以下命令生成csr
的json
:
johndoe@management-vm$ cat << EOF > admin-csr.json
{
"CN": "admin",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "New York",
"O": "system:masters",
"OU": "Kubernetes",
"ST": "NY"
}
]
}
EOF
有了我们的 JSON 准备好了,现在让我们使用以下命令签名并创建管理员证书:
johndoe@management-vm$ cfssl gencert \
-ca=../ca.pem \
-ca-key=../ca-key.pem \
-config=../ca-config.json \
-profile=kubernetes \
admin-csr.json | cfssljson -bare admin
与管理员和 CA 证书相比,创建kubelet
证书的过程有些不同。kubelet
证书要求我们在证书中填写主机名字段,因为这是它的标识方式。
使用以下命令创建目录:
johndoe@management-vm$ mkdir ~/certs/kubelet/
johndoe@management-vm$ cd ~/certs/kubelet/
然后使用以下命令创建json
csr
,其中没有太多变化:
johndoe@management-vm$ cat << EOF > kube-node-1-csr.json
{
"CN": "system:node:kube-node-1",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "New York",
"O": "system:nodes",
"OU": "Kubernetes",
"ST": "NY"
}
]
}
EOF
然而,当生成certs
时,过程有些不同,如下命令所示:
johndoe@management-vm$ cfssl gencert \
-ca=../ca.pem \
-ca-key=../ca-key.pem \
-config=../ca-config.json \
-hostname=192.168.0.21,kube-node-1 \
-profile=kubernetes \
kube-node-1-csr.json | cfssljson -bare kube-node-1
如您所见,主机名字段将包含节点将具有的任何 IP 或 FQDN。现在为每个工作节点生成证书,填写与生成证书的节点对应的信息。
控制平面证书
让我们开始为我们的 kube 主要组件创建证书。
与之前的步骤一样,创建一个包含主节点组件证书的目录,并按以下方式为每个组件生成证书文件:
johndoe@management-vm$ mkdir ~/certs/control-plane/
johndoe@management-vm$ cd ~/certs/control-plane/
对于kube-controller-manager
,使用以下命令:
johndoe@management-vm$ cat << EOF > kube-controller-manager-csr.json
{
"CN": "system:kube-controller-manager",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "New York",
"O": "system:kube-controller-manager",
"OU": "Kubernetes",
"ST": "NY"
}
]
}
EOF
johndoe@management-vm$ cfssl gencert \
-ca=../ca.pem \
-ca-key=../ca-key.pem \
-config=../ca-config.json \
-profile=kubernetes \
kube-controller-manager-csr.json | cfssljson -bare kube-controller-manager
对于kube-proxy
,使用以下命令:
johndoe@management-vm$ cat << EOF > kube-proxy-csr.json
{
"CN": "system:kube-proxy",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "New York",
"O": "system:node-proxier",
"OU": "Kubernetes",
"ST": "NY"
}
]
}
EOF
johndoe@management-vm$ cfssl gencert \
-ca=../ca.pem \
-ca-key=../ca-key.pem \
-config=../ca-config.json \
-profile=kubernetes \
kube-proxy-csr.json | cfssljson -bare kube-proxy
对于kube-scheduler
,使用以下命令:
johndoe@management-vm$ cat << EOF > kube-scheduler-csr.json
{
"CN": "system:kube-scheduler",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "New York",
"O": "system:kube-scheduler",
"OU": "Kubernetes",
"ST": "NY"
}
]
}
EOF
johndoe@management-vm$ cfssl gencert \
-ca=../ca.pem \
-ca-key=../ca-key.pem \
-config=../ca-config.json \
-profile=kubernetes \
kube-scheduler-csr.json | cfssljson -bare kube-scheduler
现在我们需要创建 API 服务器。您会注意到它与我们在kubelets
中使用的过程类似,因为这个证书需要主机名参数。但是对于kube-api
证书,我们不仅会提供单个节点的主机名和 IP 地址,还会提供 API 服务器将使用的所有可能的主机名和 IP 地址:负载均衡器的公共 IP,每个主节点的 IP,以及一个特殊的 FQDN,kubernetes.default
。所有这些将包含在一个单独的证书中。
首先使用以下命令创建一个单独的目录:
johndoe@management-vm$ mkdir ~/certs/api/
johndoe@management-vm$ cd ~/certs/api/
现在,让我们使用以下命令为主机名创建一个变量:
johndoe@management-vm$API_HOSTNAME=10.20.0.1,192.168.0.11,kube-controller-1,192.168.0.12,kube-controller-2,<PUBLIC_IP>,127.0.0.1,localhost,kubernetes.default
请注意,您应该用您的公共 IP 地址替换<PUBLIC_IP>
。
现在,让我们使用以下命令创建证书:
johndoe@management-vm$ cat << EOF > kubernetes-csr.json
{
"CN": "kubernetes",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "New York",
"O": "Kubernetes",
"OU": "Kubernetes",
"ST": "NY"
}
]
}
EOF
johndoe@management-vm$ cfssl gencert \
-ca=../ca.pem \
-ca-key=../ca-key.pem \
-config=../ca-config.json \
-hostname=${API_HOSTNAME} \
-profile=kubernetes \
kubernetes-csr.json | cfssljson -bare kubernetes
此时,只缺少一个证书——服务账户证书。这个证书不是为任何普通用户或 Kubernetes 组件特定的。服务账户证书由 API 服务器用于签署用于服务账户的令牌。
我们将把这些密钥对存储在与 API 证书相同的目录中,所以我们只需创建json
并运行cfssl
gencert
命令,如下命令所示:
johndoe@management-vm$ cat << EOF > service-account-csr.json
{
"CN": "service-accounts",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "US",
"L": "New York",
"O": "Kubernetes",
"OU": "Kubernetes",
"ST": "NY"
}
]
}
EOF
johndoe@management-vm$ cfssl gencert \
-ca=../ca.pem \
-ca-key=../ca-key.pem \
-config=../ca-config.json \
-profile=kubernetes \
service-account-csr.json | cfssljson -bare service-account
发送我们的证书回家
所有证书生成完毕后,是时候将它们移动到相应的节点上了。Microsoft Azure 可以通过 VM 名称内部解析,所以我们可以轻松地移动证书。
使用以下命令将证书移动到kubelets
:
johndoe@management-vm$ cd ~/certs/kubelets
johndoe@management-vm$ scp ../ca.pem \
kube-node-1.pem \
kube-node-1-key.pem \
johndoe@kube-node-1:~/
对于其余的节点重复以上步骤。
使用以下命令将证书移动到控制平面:
johndoe@management-vm$ cd ~/certs/api
johndoe@management-vm$ scp ../ca.pem \
../ca-key.pem \
kubernetes.pem \
kubernetes-key.pem \
service-account.pem \
service-account-key.pem \
johndoe@kube-controller-1:~/
对最后的控制器重复以上步骤。
Kubeconfigs
要能够与 Kubernetes 通信,您需要知道 API 的位置。您还需要告诉 API 您是谁以及您的凭据是什么。所有这些信息都包含在kubeconfigs
中。这些配置文件包含了您到达和对集群进行身份验证所需的所有信息。用户不仅将使用kubeconfig
文件来访问集群,还将使用它来访问其他服务。这就是为什么我们将为每个组件和用户生成多个kubeconfig
文件。
安装 kubectl
要能够创建kubeconfig
文件,我们需要kubectl
。您将首先在管理 VM 中安装kubectl
以生成配置文件,但稍后我们还将使用它来管理我们的集群。
首先,添加我们将获取kubectl
的存储库,如下命令所示:
johndoe@management-vm$ sudo cat << EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
最后,使用yum
进行安装,如下命令所示:
johndoe@management-vm$sudo yum install kubectl
控制平面 kubeconfigs
我们将要生成的第一个 kubeconfigs 是我们的控制平面组件。
为了保持秩序,我们将继续将文件组织到目录中。所有我们的kubeconfigs
将放在同一个目录中,如下命令所示:
johndoe@management-vm$ mkdir ~/kubeconfigs
johndoe@management-vm$ cd ~/kubeconfigs
有了我们创建的目录,让我们开始生成kubeconfigs
!
Kube-controller-manager
kube-controller-manager
kubeconfig
:
johndoe@management-vm$ kubectl config set-cluster kubernetes \
--certificate-authority=../certs/ca.pem \
--embed-certs=true \
--server=https://127.0.0.1:6443 \
--kubeconfig=kube-controller-manager.kubeconfig
johndoe@management-vm$ kubectl config set-credentials \
system:kube-controller-manager \
--client-certificate=../certs/control-plane/kube-controller-manager.pem \
--client-key=../certs/control-plane/kube-controller-manager-key.pem \
--embed-certs=true \
--kubeconfig=kube-controller-manager.kubeconfig
johndoe@management-vm$ kubectl config set-context default \
--cluster=kubernetes \
--user=system:kube-controller-manager \
--kubeconfig=kube-controller-manager.kubeconfig
johndoe@management-vm$ kubectl config use-context default --kubeconfig=kube-controller-manager.kubeconfig
Kube-scheduler
Kube-scheduler
kubeconfig
:
johndoe@management-vm$ kubectl config set-cluster kubernetes \
--certificate-authority=../certs/ca.pem \
--embed-certs=true \
--server=https://127.0.0.1:6443 \
--kubeconfig=kube-scheduler.kubeconfig
johndoe@management-vm$ kubectl config set-credentials system:kube-scheduler \
--client-certificate=../certs/control-plane/kube-scheduler.pem \
--client-key=../certs/control-plane/kube-scheduler-key.pem \
--embed-certs=true \
--kubeconfig=kube-scheduler.kubeconfig
johndoe@management-vm$ kubectl config set-context default \
--cluster=kubernetes \
--user=system:kube-scheduler \
--kubeconfig=kube-scheduler.kubeconfig
johndoe@management-vm$ kubectl config use-context default --kubeconfig=kube-scheduler.kubeconfig
Kubelet 配置
对于我们的kubelets
,我们将需要每个节点一个kubeconfig
。为了简化操作,我们将使用 for 循环为每个节点创建一个配置,如下命令所示。请注意,您需要用您自己的公共 IP 地址替换<KUBE_API_PUBLIC_IP>
:
johndoe@management-vm$ for i in 1 2; do
kubectl config set-cluster kubernetes \
--certificate-authority=../certs/ca.pem \
--embed-certs=true \
--server=https://<KUBE_API_PUBLIC_IP>:6443 \
--kubeconfig=kube-node-${i}.kubeconfig
kubectl config set-credentials system:node:kube-node-${i} \
--client-certificate=../certs/kubelets/kube-node-${i}.pem \
--client-key=../certs/kubelets/kube-node-${i}-key.pem \
--embed-certs=true \
--kubeconfig=kube-node-${i}.kubeconfig
kubectl config set-context default \
--cluster=kubernetes \
--user=system:node:kube-node-${i} \
--kubeconfig=kube-node-${i}.kubeconfig
kubectl config use-context default --kubeconfig=kube-node-${i}.kubeconfig
done
最后,我们的工作节点将需要的最后一个kubeconfig
是kube-proxy kubeconfig
。我们只会生成一个,因为它不包含任何特定的节点配置,我们可以将相同的配置复制到所有节点。
Kube-proxy
kube-proxy
kubeconfig
:
johndoe@management-vm$ kubectl config set-cluster kubernetes \
--certificate-authority=../certs/ca.pem \
--embed-certs=true \
--server=https://<PUBLIC_IP>:6443 \
--kubeconfig=kube-proxy.kubeconfig
johndoe@management-vm$ kubectl config set-credentials system:kube-proxy \
--client-certificate=../certs/controllers/kube-proxy.pem \
--client-key=../certs/controllers/kube-proxy-key.pem \
--embed-certs=true \
--kubeconfig=kube-proxy.kubeconfig
johndoe@management-vm$ kubectl config set-context default \
--cluster=kubernetes \
--user=system:kube-proxy \
--kubeconfig=kube-proxy.kubeconfig
johndoe@management-vm$ kubectl config use-context default \ --kubeconfig=kube-proxy.kubeconfig
现在我们有了控制平面 kubeconfigs 和工作节点,我们现在将使用以下命令为管理员用户创建kubeconfig
。这个kubeconfig
文件是我们将用来连接到集群并管理其 API 对象的文件:
johndoe@management-vm$ kubectl config set-cluster kubernetes \
--certificate-authority=../certs/ca.pem \
--embed-certs=true \
--server=https://127.0.0.1:6443 \
--kubeconfig=admin.kubeconfig
johndoe@management-vm$ kubectl config set-credentials admin \
--client-certificate=../certs/admin/admin.pem \
--client-key=../certs/admin/admin-key.pem \
--embed-certs=true \
--kubeconfig=admin.kubeconfig
johndoe@management-vm$ kubectl config set-context default \
--cluster=kubernetes \
--user=admin \
--kubeconfig=admin.kubeconfig
johndoe@management-vm$ kubectl config use-context default \ --kubeconfig=admin.kubeconfig
移动配置文件
现在我们的 kubeconfigs 需要传输到它们各自的 VM。为此,我们将遵循与移动证书相同的过程。
首先,让我们使用以下命令移动进入工作节点的 kubeconfigs:
johndoe@management-vm$ scp kube-node-1.kubeconfig kube-proxy.kubeconfig johndoe@kube-node-1:~/
对每个节点重复。
在节点上放置了 kubeconfigs 后,我们现在可以使用以下命令移动kube-api
服务器配置:
johndoe@management-vm$ scp admin.kubeconfig kube-controller-manager.kubeconfig \
kube-scheduler.kubeconfig johndoe@kube-controller-1:~/
对每个控制器重复。
安装控制平面
现在我们将安装控制平面所需的二进制文件。
ETCD
在这个设计中,我们决定将etcd
与我们的kube-apiserver
一起运行。我们将开始下载二进制文件并为我们的数据库配置systemd
单元。
安装 etcd
现在是时候在我们的控制器节点中开始安装etcd
集群了。要安装etcd
,我们将从管理 VM 中 SSH 到每个控制器并运行以下程序。
我们将通过以下命令下载和提取二进制文件:
johndoe@kube-controller-1$ wget -O etcd.tgz \
https://github.com/etcd-io/etcd/releases/download/v3.3.10/etcd-v3.3.10-linux-amd64.tar.gz
johndoe@kube-controller-1$ tar xzvf etcd.tgz
johndoe@kube-controller-1$ sudo mv etcd-v3.3.10-linux-amd64/etcd* /usr/local/bin/
johndoe@kube-controller-1$ sudo mkdir -p /etc/etcd /var/lib/etcd
在提取了二进制文件之后,我们需要使用以下命令将 kubernetes API 和 CA 证书复制到我们的etcd
目录中:
johndoe@kube-controller-1$ cp /home/johndoe/ca.pem \
/home/johndoe/kubernetes-key.pem \
/home/johndoe/kubernetes.pem /etc/etcd
在创建systemd
单元文件之前,让我们设置一些变量,以使事情变得更容易一些。
这两个变量将是唯一的,如以下命令所示:
johndoe@kube-controller-1$ ETCD_NAME=$(hostname)
johndoe@kube-controller-1$ I_IP=192.168.0.11
下一个和最后一个变量将在所有节点上相同;它将包含每个ectd
集群成员的主机名和 IP,如以下命令所示:
I_CLUSTER=kube-controller-1=https://192.168.0.11:2380,kube-controller-2=https://192.168.0.12:2380,kube-controller-3=https://192.168.0.13:2380
现在我们有了变量,让我们创建systemd
单元文件,如以下命令所示:
johndoe@kube-controller-1$sudo cat << EOF | sudo tee /etc/systemd/system/etcd.service
[Unit]
Description=etcd
Documentation=https://github.com/coreos
[Service]
ExecStart=/usr/local/bin/etcd \\
--name ${ETCD_NAME} \\
--cert-file=/etc/etcd/kubernetes.pem \\
--key-file=/etc/etcd/kubernetes-key.pem \\
--peer-cert-file=/etc/etcd/kubernetes.pem \\
--peer-key-file=/etc/etcd/kubernetes-key.pem \\
--trusted-ca-file=/etc/etcd/ca.pem \\
--peer-trusted-ca-file=/etc/etcd/ca.pem \\
--peer-client-cert-auth \\
--client-cert-auth \\
--initial-advertise-peer-urls https://${I_IP}:2380 \\
--listen-peer-urls https://${I_IP}:2380 \\
--listen-client-urls https://${I_IP}:2379,https://127.0.0.1:2379 \\
--advertise-client-urls https://${I_IP}:2379 \\
--initial-cluster-token etcd-cluster-0 \\
--initial-cluster ${I_CLUSTER} \\
--initial-cluster-state new \\
--data-dir=/var/lib/etcd
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
现在我们重新加载、启用并启动守护进程,使用以下命令:
johndoe@kube-controller-1$ systemctl daemon-reload && \
systemctl enable etcd && \
systemctl start etcd && \
systemctl status etcd
一旦您为每个节点重复了这个过程,您可以通过运行以下命令来检查集群的状态:
johndoe@kube-controller-3$ ETCDCTL_API=3 etcdctl member list \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/etcd/ca.pem \
--cert=/etc/etcd/kubernetes.pem \
--key=/etc/etcd/kubernetes-key.pem
加密 etcd 数据
API 服务器可以加密存储在etcd
中的数据。为此,我们将在创建kube-apiserver systemd
单元文件时使用一个名为--experimental-encryption-provider-config
的标志。但在传递该标志之前,我们需要创建一个包含我们加密密钥的 YAML。
我们只会创建一个 YAML 定义并将其复制到每个控制器节点。您应该从管理 VM 执行此操作,以便可以轻松地将文件传输到所有控制器。让我们使用以下命令设置这一点:
johndoe@management-vm$ CRYPT_KEY=$(head -c 32 /dev/urandom | base64)
输入 YAML 定义如下:
johndoe@management-vm$ cat << EOF > crypt-config.yml
kind: EncryptionConfig
apiVersion: v1
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: ${CRYPT_KEY}
- identity: {}
EOF
最后,使用以下命令将密钥移动到每个节点:
johndoe@management-vm$ for i in 1 2 3; do
scp crypt-config.yml johndoe@kube-controller-${i}:~/
done
安装 Kubernetes 控制器二进制文件
现在etcd
已经就位,我们可以开始安装kube-apiserver
、kube-controller-manager
和kube-scheduler
。
Kube-apiserver
让我们从第一个控制器节点 SSH 并使用以下命令下载所需的二进制文件:
johndoe@management-vm$ ssh johndoe@kube-controller-1
johndoe@kube-controller-1$ wget "https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kube-apiserver" \
"https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kubectl["](https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kubectl)
现在使用以下命令将二进制文件移动到/usr/local/bin/
:
johndoe@kube-controller-1$ sudo mkdir -p /etc/kubernetes/config
johndoe@kube-controller-1$ sudo chmod +x kube*
johndoe@kube-controller-1$ sudo mv kube-apiserver kubectl /usr/local/bin/
接下来,我们将使用以下命令创建和移动所有 API 服务器所需的目录和证书:
johndoe@kube-controller-1$ sudo mkdir -p /var/lib/kubernetes/
johndoe@kube-controller-1$ sudo cp /home/johndoe/ca.pem \
/home/johndoe/ca-key.pem \
/home/johndoe/kubernetes-key.pem \
/home/johndoe/kubernetes.pem \
/home/johndoe/service-account-key.pem \
/home/johndoe/service-account.pem \
/home/johndoe/crypt-config.yml \
/var/lib/kubernetes/
在创建systemd
单元文件之前,让我们使用以下命令声明一些变量:
johndoe@kube-controller-1$ I_IP=192.168.0.11
johndoe@kube-controller-1$ CON1_IP=192.168.0.11
johndoe@kube-controller-1$ CON2_IP=192.168.0.12
johndoe@kube-controller-1$ CON2_IP=192.168.0.13
只有I_IP
变量在每个节点上是唯一的,它将取决于您正在执行该过程的节点的 IP。其他三个变量在所有节点上都是相同的。
现在变量设置好了,我们可以开始创建单元文件,如以下命令所示:
johndoe@kube-controller-1$ sudo cat << EOF | sudo tee /etc/systemd/system/kube-apiserver.service
[Unit]
Description=Kubernetes API Server
Documentation=https://github.com/kubernetes/kubernetes
[Service]
ExecStart=/usr/local/bin/kube-apiserver \\
--advertise-address=${I_IP} \\
--allow-privileged=true \\
--apiserver-count=3 \\
--audit-log-maxage=30 \\
--audit-log-maxbackup=3 \\
--audit-log-maxsize=100 \\
--audit-log-path=/var/log/audit.log \\
--authorization-mode=Node,RBAC \\
--bind-address=0.0.0.0 \\
--client-ca-file=/var/lib/kubernetes/ca.pem \\
--enable-admission-plugins=Initializers,NamespaceLifecycle,NodeRestriction,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota \\
--enable-swagger-ui=true \\
--etcd-cafile=/var/lib/kubernetes/ca.pem \\
--etcd-certfile=/var/lib/kubernetes/kubernetes.pem \\
--etcd-keyfile=/var/lib/kubernetes/kubernetes-key.pem \\
--etcd-servers=https://$CON1_IP:2379,https://$CON2_IP:2379 \\
--event-ttl=1h \\
--experimental-encryption-provider-config=/var/lib/kubernetes/crypt-config.yml \\
--kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \\
--kubelet-client-certificate=/var/lib/kubernetes/kubernetes.pem \\
--kubelet-client-key=/var/lib/kubernetes/kubernetes-key.pem \\
--kubelet-https=true \\
--runtime-config=api/all \\
--service-account-key-file=/var/lib/kubernetes/service-account.pem \\
--service-cluster-ip-range=10.20.0.0/24 \\
--service-node-port-range=30000-32767 \\
--tls-cert-file=/var/lib/kubernetes/kubernetes.pem \\
--tls-private-key-file=/var/lib/kubernetes/kubernetes-key.pem \\
--v=2 \\
--kubelet-preferred-address-types=InternalIP,InternalDNS,Hostname,ExternalIP,ExternalDNS
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
Kube-controller-manager
要安装kube-controller-manager
,步骤将非常相似,只是在这一点上,我们将开始使用 kubeconfigs。
首先,使用以下命令下载kube-controller-manager
:
johndoe@kube-controller-1$ wget "https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kube-controller-manager"
johndoe@kube-controller-1$sudo chmod +x kube-controller-manager
johndoe@kube-controller-1$sudo mv kube-controller-manager /usr/local/bin/
使用以下命令移动kubeconfig
并创建kube-controller-manager
的单元文件:
johndoe@kube-controller-1$ sudo cp \
/home/johndoe/kube-controller-manager.kubeconfig /var/lib/kubernetes/
johndoe@kube-controller-1$ cat << EOF | sudo tee \ /etc/systemd/system/kube-controller-manager.service
[Unit]
Description=Kubernetes Controller Manager
Documentation=https://github.com/kubernetes/kubernetes
[Service]
ExecStart=/usr/local/bin/kube-controller-manager \\
--address=0.0.0.0 \\
--cluster-cidr=10.30.0.0/16 \\
--cluster-name=kubernetes \\
--cluster-signing-cert-file=/var/lib/kubernetes/ca.pem \\
--cluster-signing-key-file=/var/lib/kubernetes/ca-key.pem \\
--kubeconfig=/var/lib/kubernetes/kube-controller-manager.kubeconfig \\
--leader-elect=true \\
--root-ca-file=/var/lib/kubernetes/ca.pem \\
--service-account-private-key-file=/var/lib/kubernetes/service-account-key.pem \\
--service-cluster-ip-range=10.20.0.0/24 \\
--use-service-account-credentials=true \\
--v=2
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
Kube-scheduler
在控制平面中安装的最后一个组件是kube-scheduler
。除了创建systemd
单元文件外,我们还将创建一个包含调度程序基本配置的 YAML 文件。
首先,让我们下载二进制文件。使用以下命令下载kube-scheduler
并将其移动到/usr/local/bin/
:
johndoe@kube-controller-1$ wget \
"https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kube-scheduler"
johndoe@kube-controller-1$ chmod +x kube-scheduler
johndoe@kube-controller-1$ sudo mv kube-scheduler /usr/local/bin/
使用以下命令将kubeconfig
文件移动到kubernetes
文件夹中:
johndoe@kube-controller-1$sudo cp /home/johndoe/kube-scheduler.kubeconfig /var/lib/kubernetes/
kube-scheduler.yml
如下所示:
johndoe@kube-controller-1$sudo cat << EOF | sudo tee /etc/kubernetes/config/kube-scheduler.yml
apiVersion: componentconfig/v1alpha1
kind: KubeSchedulerConfiguration
clientConnection:
kubeconfig: "/var/lib/kubernetes/kube-scheduler.kubeconfig"
leaderElection:
leaderElect: true
EOF
kube-scheduler.service
如下所示:
johndoe@kube-controller-1$ sudo cat << EOF | sudo tee /etc/systemd/system/kube-scheduler.service
[Unit]
Description=Kubernetes Scheduler
Documentation=https://github.com/kubernetes/kubernetes
[Service]
ExecStart=/usr/local/bin/kube-scheduler \\
--config=/etc/kubernetes/config/kube-scheduler.yml \\
--v=2
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
在继续下一步之前,重复在每个控制器节点上的安装控制平面部分中的所有步骤。
启动控制平面
在每个控制器节点上完成每个组件的安装后,我们准备启动和测试服务。
为此,首先使用以下命令启用和启动所有systemd
单元:
johndoe@kube-controller-1$ sudo systemctl daemon-reload
johndoe@kube-controller-1$ sudo systemctl enable kube-apiserver kube-controller-manager kube-scheduler
johndoe@kube-controller-1$ sudo systemctl start kube-apiserver kube-controller-manager kube-scheduler
johndoe@kube-controller-1$ sudo systemctl status kube-apiserver kube-controller-manager kube-scheduler
最后,为了能够自己使用kubectl
,我们需要设置要连接的集群的上下文,并将kubeconfig
管理员设置为默认值。我们目前设置的kubeconfig
管理员指向localhost
作为kube-apiserver
端点。这暂时没问题,因为我们只想测试我们的组件。
在kube-controller-1
中输入以下命令:
johndoe@kube-controller-1$ mkdir /home/johndoe/.kube/
johndoe@kube-controller-1$ cat /home/johndoe/admin.kubeconfig > /home/johndoe/.kube/config
johndoe@kube-controller-1$ kubectl get cs
输出应如下所示:
NAME STATUS MESSAGE ERROR
controller-manager Healthy ok
scheduler Healthy ok
etcd-0 Healthy {"health": "true"}
etcd-1 Healthy {"health": "true"}
etcd-2 Healthy {"health": "true"}
为 kubelets 设置 RBAC 权限。
我们的 API 服务器将需要权限与kubelets
API 进行通信。为此,我们创建将绑定到 Kubernetes 用户的集群角色。我们将在一个控制器节点上执行此操作,因为我们将使用kubectl
,并且更改将应用于整个集群。
集群角色
使用以下命令创建包含权限的集群角色:
johndoe@kube-controller-1$ cat << EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: system:kube-apiserver-to-kubelet
rules:
- apiGroups:
- ""
resources:
- nodes/proxy
- nodes/stats
- nodes/log
- nodes/spec
- nodes/metrics
verbs:
- "*"
EOF
集群角色绑定
现在使用以下命令将角色绑定到 Kubernetes 用户:
johndoe@kube-controller-1$ cat << EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: system:kube-apiserver
namespace: ""
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:kube-apiserver-to-kubelet
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: kubernetes
EOF
负载均衡器设置
我们需要将请求负载均衡到所有 kube-controller 节点。因为我们在云上运行,我们可以创建一个负载均衡器对象,该对象将在所有节点上负载均衡请求。不仅如此,我们还可以配置健康探测,以监视控制器节点的状态,看它们是否可用来接收请求。
创建负载均衡器
负载均衡器是我们一直在保存公共 IP 的地方。LB 将成为我们从外部访问集群的入口。我们需要创建规则来健康检查端口80
,并将kubectl
请求重定向到6443
。
让我们按照以下步骤来实现这一点。
Azure 负载均衡器
我们将不得不回到我们安装了 Azure CLI 的工作站,以完成接下来的一系列步骤。
在您的工作站上创建负载均衡器并为其分配公共 IP,请运行以下命令:
az network lb create -n kube-lb \
--sku Standard \
--public-ip-address kube-api-pub-ip
现在我们已经创建了负载均衡器,我们仍然需要配置三件事:
-
后端池
-
健康探测
-
负载均衡规则
后端池
到目前为止,我们一直在通过 Azure CLI 进行与 Azure 相关的一切。让我们通过 Azure 门户按照以下步骤,以便您也可以熟悉门户:
要创建后端池,请转到 kube-lb 对象,如下面的屏幕截图所示:
当您在负载均衡器对象内时,请转到后端池并单击添加,如下面的屏幕截图所示:
当您单击“添加”时,将出现一个菜单。将您的后端池命名为kube-lb-backend
,并确保选择所有 kube-controller 节点及其各自的 IP,如下截图所示:
示例
单击“添加”以完成。我们已成功设置了后端 VM。
健康探测
在我们创建负载均衡规则之前,我们需要创建健康探测,告诉我们的 LB 哪些节点可以接收流量。因为在撰写本章时,Azure 中的负载均衡器不支持 HTTPS 健康探测,我们需要通过 HTTP 公开/healthz
端点。为此,我们将在我们的控制节点上安装 Nginx,并将传入到端口80
的代理请求传递到端口6443
。
通过 SSH 返回到您的控制节点,并在每个节点上执行以下过程:
johndoe@kube-controller-1$ sudo yum install epel-release && yum install nginx
安装 Nginx 后,在/etc/nginx/nginx.conf
中替换server
条目为以下内容:
server {
listen 80;
server_name kubernetes.default.svc.cluster.local;
location /healthz {
proxy_pass https://127.0.0.1:6443/healthz;
proxy_ssl_trusted_certificate /var/lib/kubernetes/ca.pem;
}
}
因为我们正在运行基于 RHEL 的发行版,默认情况下启用了 SELINUX;因此,它将阻止 Nginx 访问端口6443
上的 TCP 套接字。为了允许这种行为,我们需要运行以下命令。
首先,我们安装所需的软件包来管理 SELINUX,如下命令所示:
johndoe@kube-controller-1$ sudo yum install policycoreutils-python
安装软件包后,我们运行以下命令允许连接到端口6443
:
johndoe@kube-controller-1$ sudo semanage port -a -t http_port_t -p tcp 6443
最后,我们使用以下命令启动nginx
:
johndoe@kube-controller-1$ sudo systemctl daemon-reload && \
systemctl enable nginx --now
如果您想测试这个,您可以随时在localhost
上运行curl
,就像这样:
johndoe@kube-controller-1$ curl -v http://localhost/healthz
如果一切配置正确,将生成以下输出:
* About to connect() to localhost port 80 (#0)
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /healthz HTTP/1.1
> User-Agent: curl/7.29.0
> Host: localhost
> Accept: */* < HTTP/1.1 200 OK
< Server: nginx/1.12.2
< Date: Sun, 28 Oct 2018 05:44:35 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 2
< Connection: keep-alive
<
* Connection #0 to host localhost left intact
Ok
请记住为每个控制节点重复所有这些过程。
现在健康端点已暴露,我们已准备好在负载均衡器中创建健康探测规则。
回到kube-lb
菜单,在设置下,与我们配置后端池的地方相同,选择健康探测,然后单击“添加”。
菜单出现后,填写字段,如下截图所示:
负载均衡规则
我们已经准备好创建负载均衡规则,并使我们的负载均衡器准备就绪。
该过程与我们在后端池和健康探测中使用的过程相同。转到 kube-lb 下的设置菜单,选择负载均衡规则。单击“添加”并填写出现的对话框,如下截图所示:
一切准备就绪后,我们只需要打开我们的网络安全组,允许在端口6443
上进行连接。
在 Azure CLI 工作站上运行以下命令以创建规则:
az network nsg rule create --nsg-name kube-nsg \
-n pub_https_allow \
--direction Inbound \
--priority 110 \
--access Allow \
--description "Allow HTTPS" \
--destination-address-prefixes '*' \
--destination-port-ranges 6443 \
--protocol Tcp \
--source-address-prefixes '*' \
--source-port-ranges '*' \
--direction Inbound
等待几分钟生效,然后在浏览器中导航至https://<LB_IP>:6443/version
。
您应该看到类似以下内容:
{
"major": "1",
"minor": "12",
"gitVersion": "v1.12.0",
"gitCommit": "0ed33881dc4355495f623c6f22e7dd0b7632b7c0",
"gitTreeState": "clean",
"buildDate": "2018-09-27T16:55:41Z",
"goVersion": "go1.10.4",
"compiler": "gc",
"platform": "linux/amd64"
}
这将表明您可以通过 LB 访问 API 服务器。
工作节点设置
现在是配置和安装我们的工作节点的时候了。在这些节点上,我们将安装kubelet
、kube 代理、容器运行时和容器网络接口插件。
从管理 VM 中的第一个工作节点 SSH 登录,如下命令所示:
johndoe@management-vm$ ssh johndoe@kube-node-1
下载和准备二进制文件
在配置任何服务之前,我们需要下载任何依赖项并设置所需的存储库。之后,我们可以开始下载二进制文件并将它们移动到它们各自的位置。
添加 Kubernetes 存储库
我们需要配置的存储库是 Kubernetes 存储库。通过这样,我们将能够下载kubectl
。让我们使用以下命令设置:
johndoe@kube-node-1$ sudo cat << EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
安装依赖项和 kubectl
通过配置repo
,我们可以开始下载kubectl
和我们将下载的二进制文件所需的任何依赖项。让我们使用以下命令设置:
johndoe@kube-node-1$ sudo yum install -y kubectl socat conntrack ipset libseccomp
下载和存储工作节点二进制文件
现在我们的依赖项准备好了,我们可以使用以下命令下载所需的工作节点二进制文件:
johndoe@kube-node-1$ wget \
https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.12.0/crictl-v1.12.0-linux-amd64.tar.gz \
https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kubelet \
https://github.com/containernetworking/plugins/releases/download/v0.6.0/cni-plugins-amd64-v0.6.0.tgz \
https://github.com/opencontainers/runc/releases/download/v1.0.0-rc5/runc.amd64 \
https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kube-proxy \
https://github.com/containerd/containerd/releases/download/v1.1.2/containerd-1.1.2.linux-amd64.tar.gz
现在让我们使用以下命令创建最近下载的二进制文件的文件夹结构:
johndoe@kube-node-1$ sudo mkdir -p \
/etc/cni/net.d \
/opt/cni/bin \
/var/lib/kube-proxy \
/var/lib/kubelet \
/var/lib/kubernetes \
/var/run/kubernetes
我们将名称更改为runc
以方便使用并符合约定,如以下命令所示:
johndoe@kube-node-1$ mv runc.amd64 runc
我们使用以下命令为其余的二进制文件授予可执行权限:
johndoe@kube-node-1$ chmod +x kube-proxy kubelet runc
给予它们可执行权限后,我们可以使用以下命令将它们移动到/usr/local/bin/
:
johndoe@kube-node-1$ sudo mv kube-proxy kubelet runc /usr/local/bin/
一些下载的文件是 TAR 存档文件,我们需要使用以下命令untar
并将其存储在各自的位置:
johndoe@kube-node-1$ tar xvzf crictl-v1.12.0-linux-amd64.tar.gz
johndoe@kube-node-1$ sudo mv crictl /usr/local/bin/
johndoe@kube-node-1$ sudo tar xvzf cni-plugins-amd64-v0.6.0.tgz -C /opt/cni/bin/
johndoe@kube-node-1$ tar xvzf containerd-1.1.2.linux-amd64.tar.gz
johndoe@kube-node-1$ sudo mv ./bin/* /bin/
Containerd 设置
现在我们准备好开始配置每个服务了。第一个是containerd
。
使用以下命令创建配置目录:
johndoe@kube-node-1$ sudo mkdir -p /etc/containerd/
现在我们创建toml
配置文件,告诉containerd
要使用哪个容器运行时。让我们使用以下命令进行设置:
johndoe@kube-node-1$ sudo cat << EOF | sudo tee /etc/containerd/config.toml
[plugins]
[plugins.cri.containerd]
snapshotter = "overlayfs"
[plugins.cri.containerd.default_runtime]
runtime_type = "io.containerd.runtime.v1.linux"
runtime_engine = "/usr/local/bin/runc"
runtime_root = ""
EOF
最后,让我们使用以下命令设置systemd
单元文件:
johndoe@kube-node-1$ sudo cat << EOF | sudo tee /etc/systemd/system/containerd.service
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target
[Service]
ExecStartPre=/sbin/modprobe overlay
ExecStart=/bin/containerd
Restart=always
RestartSec=5
Delegate=yes
KillMode=process
OOMScoreAdjust=-999
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity
[Install]
WantedBy=multi-user.target
EOF
kubelet
我们工作节点中的主要服务是kubelet
。让我们创建它的配置文件。
首先,我们需要使用以下命令将kubelet
证书移动到它们的位置:
johndoe@kube-node-1$ sudo mv /home/johndoe/${HOSTNAME}-key.pem /home/johndoe/${HOSTNAME}.pem /var/lib/kubelet/
johndoe@kube-node-1$ sudo mv /home/johndoe/${HOSTNAME}.kubeconfig /var/lib/kubelet/kubeconfig
johndoe@kube-node-1$ sudo mv /home/johndoe/ca.pem /var/lib/kubernetes/
现在我们创建 YAML 配置文件,其中包含 DNS 服务器 IP 地址、集群域和证书文件的位置等内容。让我们使用以下命令进行设置:
johndoe@kube-node-1$ sudo cat << EOF | sudo tee /var/lib/kubelet/kubelet-config.yaml
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
authentication:
anonymous:
enabled: false
webhook:
enabled: true
x509:
clientCAFile: "/var/lib/kubernetes/ca.pem"
authorization:
mode: Webhook
clusterDomain: "cluster.local"
clusterDNS:
- "10.20.0.10"
runtimeRequestTimeout: "15m"
tlsCertFile: "/var/lib/kubelet/${HOSTNAME}.pem"
tlsPrivateKeyFile: "/var/lib/kubelet/${HOSTNAME}-key.pem"
EOF
最后,我们使用以下命令创建服务单元文件:
johndoe@kube-node-1$ sudo cat << EOF | sudo tee /etc/systemd/system/kubelet.service
[Unit]
Description=Kubernetes Kubelet
Documentation=https://github.com/kubernetes/kubernetes
After=containerd.service
Requires=containerd.service
[Service]
ExecStart=/usr/local/bin/kubelet \\
--config=/var/lib/kubelet/kubelet-config.yaml \\
--container-runtime=remote \\
--container-runtime-endpoint=unix:///var/run/containerd/containerd.sock \\
--image-pull-progress-deadline=2m \\
--kubeconfig=/var/lib/kubelet/kubeconfig \\
--network-plugin=cni \\
--register-node=true \\
--v=2 \\
--hostname-override=${HOSTNAME} \\
--allow-privileged=true
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
kube-proxy
下一个要创建的服务是kube-proxy
。
我们使用以下命令移动先前创建的kubeconfigs
:
johndoe@kube-node-1$ sudo mv /home/johndoe/kube-proxy.kubeconfig /var/lib/kube-proxy/kubeconfig
与kubelet
一样,kube-proxy
还需要一个配置YAML
,其中包含集群 CIDR 和kube-proxy
将运行的模式。让我们使用以下命令进行设置:
johndoe@kube-node-1$ sudo cat << EOF | sudo tee /var/lib/kube-proxy/kube-proxy-config.yaml
kind: KubeProxyConfiguration
apiVersion: kubeproxy.config.k8s.io/v1alpha1
clientConnection:
kubeconfig: "/var/lib/kube-proxy/kubeconfig"
mode: "iptables"
clusterCIDR: "10.30.0.0/16"
EOF
最后,我们使用以下命令为kube-proxy
创建一个单元文件:
johndoe@kube-node-1$ sudo cat << EOF | sudo tee /etc/systemd/system/kube-proxy.service
[Unit]
Description=Kubernetes Kube Proxy
Documentation=https://github.com/kubernetes/kubernetes
[Service]
ExecStart=/usr/local/bin/kube-proxy \\
--config=/var/lib/kube-proxy/kube-proxy-config.yaml
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
启动服务
完成所有 kube 节点上的这些步骤后,您可以使用以下命令在每个节点上启动服务:
johndoe@kube-node-1$ sudo systemctl daemon-reload && \
systemctl enable containerd kubelet kube-proxy && \
systemctl start containerd kubelet kube-proxy && \
systemctl status containerd kubelet kube-proxy
Kubernetes 网络
在我们的集群中还有一些事情要做:我们需要安装网络提供程序并配置 DNS。
准备节点
我们的节点必须能够转发数据包,以便我们的 pod 能够与外部世界通信。Azure VM 默认情况下未启用 IP 转发,因此我们必须手动启用它。
为此,请转到 Azure CLI 工作站并运行以下命令:
for i in 1 2; do
az network nic update \
-n $(az vm show --name kube-node-${i} --query [networkProfile.networkInterfaces[*].id] --output tsv | sed 's:.*/::') \
--ip-forwarding true
done
这将在 VM 的 NIC 上启用 IP 转发功能。
现在我们必须在工作节点上启用 IP 转发内核参数。
从管理 VM 登录到每个工作节点,并使用以下命令启用 IPv4 转发:
johndoe@kube-node-1$ sudo sysctl net.ipv4.conf.all.forwarding=1
johndoe@kube-node-1$ sudo echo "net.ipv4.conf.all.forwarding=1" | tee -a /etc/sysctl.conf
配置远程访问
现在,为了从管理 VM 运行kubectl
命令,我们需要创建一个使用集群的管理员证书和公共 IP 地址的kubeconfig
。让我们使用以下命令进行设置:
johndoe@management-vm$ kubectl config set-cluster kube \
--certificate-authority=/home/johndoe/certs/ca.pem \
--embed-certs=true \
--server=https://104.45.174.96:6443
johndoe@management-vm$ kubectl config set-credentials admin \
--client-certificate=/home/johndoe/certs/admin/admin.pem \
--client-key=~/certs/admin/admin-key.pem
johndoe@management-vm$ kubectl config set-context kube \
--cluster=kube \
--user=admin
johndoe@management-vm$ kubectl config use-context kube
安装 Weave Net
配置了管理 VM 上的远程访问后,我们现在可以在不必登录到控制节点的情况下运行kubectl
命令。
要安装 Weave Net,请从管理 VM 运行以下kubectl
命令:
johndoe@management-vm$ kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version=$(kubectl version | base64 | tr -d '\n')&env.IPALLOC_RANGE=10.30.0.0/16"
安装了 Weave Net 后,现在我们的 pod 将有 IP 分配。
DNS 服务器
现在我们将配置我们的 DNS 服务器,它将由 Core DNS 提供,这是一个基于插件的开源 DNS 服务器。让我们使用以下命令进行设置:
johndoe@management-vm$ kubectl create -f https://raw.githubusercontent.com/dsalamancaMS/CoreDNSforKube/master/coredns.yaml
使用以下命令检查 DNS pod:
johndoe@management-vm$ kubectl get pods -n kube-system
创建了 DNS 服务器 pod 后,我们已成功完成了 Kubernetes 集群的安装。如果您愿意,您可以创建以下部署来再次测试集群:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
现在我们已经看到了从头开始创建集群所需的步骤,我想谈谈托管的 Kubernetes 解决方案。
在云上管理 Kubernetes
像你在本章中看到的那样,安装和使 Kubernetes 集群可用并为生产做好准备是一个非常漫长和复杂的过程。如果有任何一步出错,您的整个部署可能会变得毫无意义。因此,许多云提供商提供了托管的 Kubernetes 解决方案——以某种方式作为服务的 Kubernetes。在这种托管解决方案中,云提供商或服务提供商将管理集群的主节点,其中包括所有 Kubernetes 控制器、API 服务器,甚至etcd
数据库。这是一个重大优势,因为使用托管服务意味着您不必担心主节点的维护,因此您不必担心以下问题:
-
更新 SSL 证书
-
更新/升级
etcd
数据库 -
更新/升级每个主节点二进制文件
-
向集群注册额外节点
-
如果出现问题,缺乏支持
-
与云基础设施的透明集成
-
操作系统补丁和维护
通过忘记这些,我们可以专注于重要的事情,比如在我们的集群上配置 pod 和创建工作负载。有了托管服务,学习曲线大大降低,因为我们的员工可以主要专注于 Kubernetes 的功能,而不是它的工作原理,以便他们维护它。
在撰写本文时,一些值得一提的托管 Kubernetes 服务来自以下三个最大的云提供商:
-
Azure Kubernetes 服务(AKS)
-
亚马逊网络服务弹性容器服务(EKS)
-
谷歌 Kubernetes 引擎(GKE)
除了托管的 Kubernetes 服务外,还有几个基于 Kubernetes 的开源项目和非开源项目。这些项目并非完全托管,而是在后台使用 Kubernetes 来实现其目标。以下是一些更知名的项目:
-
Okd(红帽 OpenShift 的上游社区项目)
-
红帽 OpenShift
-
SUSE 容器即服务(Caas)平台
-
Mesosphere Kubernetes 引擎
总结
在本章中,我们学习了配置 Kubernetes 集群的基本步骤。我们还了解了 Azure 命令行界面以及如何在 Azure 中配置资源。我们还尝试了整个部署过程中的不同工具,如 CFSSL 和 Nginx。
我们学习了并配置了kubectl
配置文件,使我们能够访问我们的集群,并部署了一个虚拟部署来测试我们的集群。最后,我们看了运行托管集群的好处以及主要公共云提供商中可以找到的不同类型的托管服务。
下一章将解释每个组件的作用。读者将了解不同组件及其目的。
问题
-
如何安装 Kubernetes?
-
什么是
kubeconfig
? -
我们如何创建 SSL 证书?
-
什么是 AKS?
-
我们如何使用 Azure CLI?
-
我们如何在 Azure 中配置资源组?
-
我们如何安装
etcd
?
进一步阅读
-
Packt Publishing 的《精通 Kubernetes》:
prod.packtpub.com/in/application-development/mastering-kubernetes-second-edition
-
Packt Publishing 的《面向开发人员的 Kubernetes》:
prod.packtpub.com/in/virtualization-and-cloud/kubernetes-developers
-
Packt Publishing 的《使用 Kubernetes 进行微服务实践》:
prod.packtpub.com/in/virtualization-and-cloud/hands-microservices-kubernetes
参考文献/来源:
-
生成自签名证书:
coreos.com/os/docs/latest/generate-self-signed-certificates.html
-
CloudFlare 的 PKI/TLS 工具包:
github.com/cloudflare/cfssl
-
Go 编程语言:
golang.org/doc/install
第三部分:弹性堆栈
本节重点介绍如何实现包括 Elasticsearch、Logstash 和 Kibana 的 ELK 堆栈,用于环境日志感知。
本节包括以下章节:
-
第十章,使用 ELK 堆栈进行监控
-
第十一章,设计 ELK 堆栈
-
第十二章,使用 Elasticsearch、Logstash 和 Kibana 管理日志
第十章:使用 ELK Stack 进行监控
监控是任何环境的重要组成部分,无论是生产、QA 还是开发;Elastic Stack(ELK Stack)通过允许来自不同来源的日志、指标和事件聚合到一个可索引的位置:Elasticsearch,有助于简化这一任务。
ELK Stack 是三种不同软件的集合:
-
Elasticsearch
-
Logstash
-
Kibana
在这一章中,我们将解释每个组件的作用。
在本章中,我们将涵盖以下主题:定义 Elasticsearch 的主要功能
-
探索集中日志的概念
-
Kibana 如何帮助整合其他组件
技术要求
以下是本章的技术要求列表:
-
Elasticsearch 产品页面:
www.elastic.co/products/elasticsearch
-
Logstash 概述:
www.elastic.co/products/logstash
-
Logstash 的可用输入插件:
www.elastic.co/guide/en/logstash/current/input-plugins.html
-
Grok 模式匹配:
www.elastic.co/guide/en/logstash/current/plugins-filters-grok.html
-
Kibana 用户指南:
www.elastic.co/guide/en/kibana/current/index.html
了解监控的必要性
想象一下,您被要求向 CIO 提供历史数据,因为一个正在进行的项目需要了解整个生态系统平均使用多少 CPU,但企业从未花时间实施良好的监控系统。因此,您唯一的选择是登录到每个系统并运行本地命令,将结果记录到电子表格中,进行一些数学运算以获得平均结果,之后您意识到数据已不再有效,必须重新进行所有操作。这正是为什么我们有 Elasticsearch 等监控系统的原因。同样的过程可能只需要几分钟。不仅如此,您将获得准确的数据和实时报告。让我们更多地了解监控是什么,以及为什么您作为架构师应该认为它是有史以来最好的东西。
监控是指从任何给定环境中获取原始数据,对其进行聚合、存储和分析,以一种可理解的方式。
所有环境都应该有某种形式的监控,从用于跟踪登录失败的简单日志文件到负责分析来自数千台主机的数据的更健壮的系统。监控数据使系统管理员能够在问题发生之前检测到问题,并使架构师能够基于数据为未来或正在进行的项目做出决策。
您可能还记得第一章中对设计方法论的介绍,我们谈到了如何提出正确的问题可以帮助设计更好的解决方案,并且同时给出正确的答案;例如,它可以帮助基于历史使用数据做出大小决策。提供使用数据给架构师有助于正确地确定解决方案的大小。他们不仅利用未来的使用统计数据,还利用过去的实例,例如在周末等高峰时段记录的使用高峰。
让我们试着将为什么我们需要监控压缩成四个主要领域:
-
通过历史数据做出决策
-
主动检测问题
-
了解环境性能
-
预算计划
通过历史数据做出决策
监控能够让我们回到过去并分析使用趋势,以帮助识别机会领域。例如,在第一章中提到的情景,客户需要一个能够每秒维持 10,000 次点击量的 Web 服务器解决方案。作为架构师,你请求访问他们现有解决方案的使用数据,并在查看他们的使用趋势后,确定每个月的第一周使用量增加了十倍。
虽然用户在这些日子里可能不会抱怨问题,但你应该考虑到在这些时间内高使用量往往会消耗资源。从监控系统中获取的数据可能会导致一个决定,即服务器需要分配更多资源(例如更多的 CPU 和 RAM)比之前计算的,或者需要向集群中添加更多的服务器(如果可能的话)。
没有这些数据,没有人会知道由于激增而需要更多资源。能够区分正常使用和激增有助于在设计和规模化解决方案时做出正确选择。
从同样的情景中,我们可以从历史数据使用中得出结论,即当前解决方案在过去几个月一直能够维持每秒 10,000 次的点击量。这可能意味着客户一直能够实现期望的性能,但实际上他们需要的是一个能够处理使用激增的解决方案,正如前面提到的。
主动检测问题
想象一下,当你准备下班时,突然有人报告数据库服务器无法接收连接。你登录服务器后发现问题比最初报告的严重得多。数据库所在的磁盘现在都被报告为损坏。你仔细查看系统日志,发现过去四个月一直报告磁盘错误;然而,由于没有健壮的监控系统,没有人知道这些错误。现在数据丢失了,你不得不恢复一个旧的备份,需要数小时才能恢复到生产状态。
不幸的是,这种情况并不罕见,大多数时候,IT 工作是被动的,这意味着如果出现问题,有人会报告问题,然后有人会去修复问题。如果实施了监控系统并配置为报告错误,这种情况本来可以完全避免。在磁盘彻底损坏之前就可以更换磁盘。
能够在问题发生之前主动检测问题,在我们看来,是监控系统中最关键的方面之一。在问题发生之前预测问题可能发生的地方有助于通过采取行动来减少停机时间。例如,在前面的情景中,更换磁盘可以防止数据丢失。预测变化还有助于通过防止因停机或故障而导致的业务损失,以及增加生产(或正常运行时间)来降低运营成本。
了解环境性能
在第五章中,在 Gluster 系统中分析性能,我们对 GlusterFS 实施进行了性能测试。通过监控系统,可以通过汇总历史数据和平均统计数据来简化性能基线的获取过程。
通过查看历史数据,我们可以看到在一定时间内任何给定系统的平均性能,从而让架构师定义什么是正常的,什么是不正常的。通过获得基线,我们可以更深入地了解环境在一天、一周甚至一个月内的行为。例如,我们可以确定存储服务器在一天内的吞吐量大约为 200 MB/s,当用户在一天的开始登录时,吞吐量会增加到 300 MB/s。起初,100 MB/s 的增加可能看起来像是一个问题,但是通过查看数据,这似乎是一个趋势,并且是标准行为。
有了这些信息,我们知道基线大约是 200 MB/s,峰值为 300 MB/s。当解决方案进行基准测试时,预期的性能应该符合这个规格。如果我们得到低于这个数字的结果,我们知道存在问题,需要进行调查以确定性能不佳的原因。这可能是解决方案的重新设计,也可能是配置的实际问题。另一方面,高数字表明解决方案即使在负载高峰时也能按规格运行。
没有这些数据,我们将不知道异常行为是什么样子,也无法确认这是否是一个实际问题,或者了解环境的正常情况。了解解决方案的性能和使用情况可以帮助发现可能看起来并不存在问题的问题。例如,考虑先前的数字情况,用户通常与存储服务器进行正常交互并且具有平均响应时间;然而,通过监控数据,我们观察到即使在正常用户负载下,我们只能获得 50 MB/s 的吞吐量。从用户的角度来看,一切似乎都很好,但当询问时,他们确实报告说即使响应时间良好,传输时间也比平常长,进一步调查发现一个节点需要维护。
在前面的例子中,仅通过查看性能数据,就可以识别出解决方案性能不佳的情况,并采取措施避免业务中断和损失。这就是通过使用数据来理解环境的力量。
预算规划
数据使用趋势可以更精细地控制预算规划,因为了解需要多少存储空间可以帮助避免未能提供足够空间的情况。
在第一章中,设计方法论简介,我们谈到了企业的采购流程,以及如何坚持时间表对于不同公司来说是至关重要的。了解空间需求和使用情况对于这个过程至关重要,因为它可以帮助预测例如解决方案何时会耗尽空间,并且可以帮助做出关于获取新存储空间的决策。
通过监控系统,了解业务每天消耗的存储量(也称为每日变化率),可以让系统管理员和架构师预测业务可以利用当前可用空间运行多长时间。这也可以让他们预测解决方案何时会耗尽空间,以便在存储空间耗尽之前采取行动,这是每个 IT 部门都应该避免的情况。
了解资源利用率对任何业务都至关重要,因为它可以防止不必要的设备采购。使用数据来决定是否应该向现有环境添加更多资源可以通过选择适当的设备数量来减少成本。当应用由于资源不足(或过时的硬件)而性能不佳时,与当前环境按预期工作并且仍有一些增长空间的数据是不同的。
如今,监控的需求比以往任何时候都更为关键。随着 IT 环境中数据的近乎指数级增长,通过基于数据的决策来预测行为并采取积极的行动只有通过监控系统才能实现,比如 ELK Stack。
集中式日志
在深入探讨 ELK Stack 的组成之前,让我们先探讨一下集中式日志的概念。
想象一下以下情景;环境中似乎存在安全漏洞,并且在一些服务器上发现了一些奇怪的文件。查看/var/log/secure
文件,您会发现来自多个地址的 root 登录,并且您想知道哪些系统受到了影响。只有一个问题——环境中有 5000 多台 Linux 服务器,您必须登录到每个系统并查看日志。每个主机可能需要大约一分钟来 grep;这将需要连续 83 个小时查看系统日志。
这种必须去每个节点的问题可以通过聚合和将日志放在一个集中的位置来解决。虽然其他行业似乎正在走去中心化服务的路线,但将所有环境的日志放在一个位置可以帮助简化任务,比如调查可能影响多个系统的事件。在一个单一位置查找可以减少故障排除所需的时间,并同时允许管理员更有效地在环境中寻找问题。
集中式日志架构如下:
来自多个应用程序的日志被发送到日志解析器(如 Logstash),然后移动到索引器(如 Elasticsearch)。每个主机都有一个代理负责将日志发送到解析器。
解析器的工作是将数据转换为易于索引的形式,然后将数据发送到索引器。
在下一部分中,我们将看看组成 ELK Stack 的组件。
Elasticsearch 概述
现在,我们将深入探讨 ELK Stack 的组件,我们将从最重要的组件 Elasticsearch 开始。
Elasticsearch 基于一个名为 Lucene 的 Apache 项目。它的作用是对数据进行索引并存储以便以后检索。Elasticsearch 接收来自不同来源的数据并将其存储在一个集中的位置,或者如果设置为集群,则存储在多个节点上。对于这种设置,我们将使用 Logstash 作为数据源;但是,Elasticsearch 也可以直接从 Beats 接收数据,这是我们稍后将讨论的。在其核心,Elasticsearch 是一个能够非常快速地检索数据的分析和搜索引擎;由于数据一旦存储就被索引,Elasticsearch 将数据存储为 JSON 文档。
定义 Elasticsearch 的一些特点如下:
-
快速
-
可扩展
-
高度可用
快速
搜索几乎是实时的;这意味着,当您输入搜索词时,Elasticsearch 几乎立即返回结果。这要归功于索引和数据存储为 JSON。
可扩展
通过简单地向集群添加更多节点,可以快速扩展 Elasticsearch 集群。
高度可用
当配置为集群时,Elasticsearch 在多个节点之间分配分片,并在一个或多个节点失败时创建分片的副本。
分片是 JSON 文档的一个片段。Elasticsearch 创建分片的副本并将它们分配到集群节点上。这使得集群能够承受灾难性故障,因为数据仍然存在作为副本。
Logstash
大多数时候,例如日志文件之类的数据是为了让人类能够轻松理解事件的含义而设计的。这种类型的数据是非结构化的,因为机器无法轻松地索引事件,因为它们不遵循相同的结构或格式。例如,系统日志和 Apache。虽然每个日志提供不同类型的事件,但都不遵循相同的格式或结构,对于索引系统来说,这就成了一个问题。这就是 Logstash 的用武之地。
Logstash 数据处理解析器能够同时从多个来源接收数据,然后通过解析将数据转换为结构化形式,然后将其作为索引的易搜索数据发送到 Elasticsearch。
Logstash 的主要特点之一是可用于过滤器的大量插件,例如 Grok,可以更灵活地解析和索引各种类型的数据。
Grok
Grok 是 Logstash 中可用的插件;它从诸如系统日志、MySQL、Apache 和其他 Web 服务器日志之类的来源获取非结构化数据,并将其转换为结构化和可查询的数据,以便轻松地摄入到 Elasticsearch 中。
Grok 将文本模式组合成与日志匹配的内容,例如数字或 IP 地址。其模式如下:
%{SYNTAX:SEMANTIC}
在这里,SYNTAX
是匹配文本的模式的名称,“SEMANTIC”是给文本段落的标识符。
HTTP 的事件示例如下:
55.3.244.1 GET /index.html 15824 0.043
这可能是用于此的一个模式匹配:
%{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes} %{NUMBER:duration}
因此,通过将所有内容放在实际的过滤器配置中,它看起来像这样:
input {
file {
path => "/var/log/http.log"
}
}
filter {
grok {
match => { "message" => "%{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes} %{NUMBER:duration}" }
}
}
自定义模式
在运行自定义应用程序时,Logstash 可能没有正确的模式来匹配语法和语义。Logstash 允许创建可以匹配自定义数据的自定义模式。前面示例中的相同逻辑可以用来匹配数据。
Kibana 将所有内容整合在一起
虽然 Elasticsearch 是 ELK Stack 的重要组成部分,Logstash 是解析和处理部分,但 Kibana 是将所有其他内容聚合在一起的工具。
可视化数据的能力使用户能够赋予其数据意义。仅仅查看原始数据,很难理解其含义。Kibana 通过图表、地图和其他方法来可视化存储在 Elasticsearch 中的数据。
以下是从实时演示中获取的 Kibana 界面的快速浏览:
Kibana 仪表板
我们可以看到使用多个模块显示不同指标来解释数据是多么容易。
Kibana 能够轻松理解大型数据集。作为一个基于浏览器的应用程序,它可以从任何地方访问。这也允许轻松与他人共享仪表板和报告。它可以与 Elasticsearch 一起安装;但是,对于更大的部署,将主机分配给 Kibana 是一个很好的做法。此外,Kibana 在 Node.js 上运行,因此几乎可以安装在可以运行 Node.js 的所有系统上,从各种 Linux 到 Windows 和 MacOS。
总结
在本章中,我们探讨了监控的需求,并了解了从环境中获取数据、汇总数据并存储数据的过程,以便以后进行进一步分析。通过仅仅瞥一眼数据就能够塑造数据并了解环境行为,有助于提高运营效率。
监控使我们能够在问题发生或变成更大问题之前主动检测问题。这是通过观察趋势来实现的,这绝对是实施和设计监控解决方案的最关键原因之一。我们还谈到了能够主动采取行动,以及这如何有助于减少停机时间和解决问题上的浪费金钱;通过塑造数据可以实现这一点。
性能也是受益于数据分析的领域。您可能还记得之前章节提到的,能够基线和测量性能使得在设计解决方案时能够进行细粒度控制。拥有历史数据可以帮助做出影响设计性能的决策,同时还可以根据来自运行环境的真实数据来规划预算。
我们介绍了拥有集中式日志系统的主要原因,它可以帮助简化管理任务;而不是连接到环境中的每个系统,从单个位置查看所有日志可以节省时间,并且可以进行更快,更高效的调查。
我们还概述了 ELK Stack 的每个组件。 Elasticsearch 是主要组件,用于存储和分析数据。我们注意到它非常快,因为数据存储为 JSON 文档;解决方案可扩展,因为可以轻松添加节点;并且高度可用,因为数据分布在节点之间。
Logstash 通过插件提供数据转换和过滤,例如 GROK,它可以将SYNTAX
与SEMANTIC
进行匹配,例如将 IP 与客户端进行匹配。
最后,我们看了 Kibana 如何通过允许数据可视化和通过全面的图形进行分析来连接所有其他组件。
在下一章中,我们将介绍每个组件的要求。
问题
-
监控是什么?
-
监控如何帮助做出业务决策?
-
如何主动检测问题?
-
监控如何允许性能基线?
-
监控如何帮助识别异常行为?
-
集中日志的主要需求是什么?
-
Elasticsearch 是什么?
-
Elasticsearch 以什么格式存储数据?
-
Logstash 是什么?
-
Kibana 是什么?
进一步阅读
-
James Lee, Tao Wei 的《实战大数据建模》:
www.packtpub.com/big-data-and-business-intelligence/hands-big-data-modeling
-
Hector Cuesta, Dr. Sampath Kumar 的《实用数据分析-第二版》:
www.packtpub.com/big-data-and-business-intelligence/practical-data-analysis-second-edition
第十一章:设计一个 ELK Stack
设计一个符合要求规格的Elastic Stack需要特别注意。Elasticsearch,Logstash 和 Kibana(ELK)的每个组件都有特定的要求。正确的大小对于最佳性能和功能至关重要。
本章将介绍在部署 Elastic Stack 时的设计考虑因素,考虑到每个组件的需求以及特定的设置细节。在本章中,我们将描述每个组件如何受不同资源影响,如何处理资源约束,以及如何计划和为不同场景进行大小调整。
在本章中,我们将讨论以下主题:
-
Elasticsearch CPU 大小要求
-
内存大小如何影响 Elasticsearch 性能
-
Elasticsearch 中数据的存储方式以及如何为性能进行大小调整
-
Logstash 和 Kibana 的要求
技术要求
尽管在www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html
找到的文档已经过时,但硬件要求可以作为 CPU 大小的起点。有关更有用的文档,请访问以下链接:
-
索引速度设置指南:
www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html
-
更改 Elasticsearch 的堆配置:
www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html
-
平均系统内存延迟:
www.crucial.com/usa/en/memory-performance-speed-latency
-
Elasticsearch 系统路径:
www.elastic.co/guide/en/elasticsearch/reference/master/path-settings.html
-
Logstash 持久队列:
www.elastic.co/guide/en/logstash/current/persistent-queues.html
-
Logstash 目录路径:
www.elastic.co/guide/en/logstash/current/dir-layout.html
Elasticsearch CPU 要求
与任何软件一样,为正确的 CPU 要求进行大小调整决定了整体应用程序的性能和处理时间。错误的 CPU 配置可能导致应用程序无法使用,因为处理时间太长而使用户感到沮丧,更不用说慢处理时间可能导致应用程序完全失败。
虽然 Elasticsearch 在索引和搜索时并不严重依赖 CPU,但在设计一个性能良好且及时返回结果的 Elastic Stack 时需要考虑几件事情。
尽管 Elastic 没有发布 CPU 的硬性要求,但有一些可以作为经验法则的事情。
CPU 数量
通常,拥有更多的核心更好,对于大多数工作负载来说可能是这样。Elasticsearch 通过在多个 CPU 上调度任务来利用系统上可用的多个核心;然而,它并不需要大量的 CPU 处理能力,因为大部分操作是在已经索引的文件上执行的。
大多数云提供商(如果您在云上部署)对高 CPU 数量的虚拟机有提高的费率,为了避免不必要的成本,应该选择一个内存比 CPU 更多的 VM 类型。
在为足够的 CPU 资源进行调整时,应允许一定的增长空间,而无需在中途更改设置。对于小型设置,至少需要两个 CPU。对于测试目的和少量索引/来源,甚至一个 CPU 就足够了,但性能会受到影响,特别是如果所有组件(Elasticsearch、Logstash 和 Kibana)都部署在同一系统上。
CPU 速度
虽然没有关于最低 CPU 速度(时钟速度)要求的硬性文件,但现在很难找到低于 2 GHz 的 CPU。这个低水位似乎是 Elasticsearch 避免问题的最低要求。
即使只有一个 CPU,超过 2 GHz 的性能也是可以接受的;这对于测试目的是足够的。对于生产环境,寻找时钟速度超过 2 GHz 或 2.3 GHz 的 CPU 以避免问题。
CPU 性能影响
如果在 CPU 方面配置不正确,Elasticsearch 主要会在以下三个方面受到影响:
-
启动时间
-
每秒索引
-
搜索延迟
启动
在启动时,CPU 使用率可能会急剧上升,因为 JVM 启动并且 Elasticsearch 从集群中读取数据。较慢的 CPU 配置将导致 Elasticsearch 启动时间较长。
如果 Elasticsearch 节点需要不断重启,正确的 CPU 配置将有助于减少达到运行状态所需的时间。
每秒索引
CPU 配置直接影响 Elasticsearch 能够处理的每秒索引数量,因为一旦索引更多文档,CPU 就会耗尽。理想情况下,具有多个核心的 Elasticsearch 可以利用多个 CPU 进行索引,允许更多客户端发送数据而不会丢失任何指标或事件。
搜索延迟
性能可能会在搜索返回结果所需的时间方面受到最大影响。请记住,Elasticsearch 的主要特点之一是它可以多快地检索数据并显示数据。
CPU 配置不足会导致搜索时间超出预期,这可能导致用户体验不佳。
在下面的截图中,我们可以看到搜索延迟急剧上升到近 80 毫秒,并在 20 毫秒左右徘徊:
在 Kibana 中监控延迟
请注意,上述截图是从一个只有一个低于 2 GHz 运行的 CPU 的配置不足的系统中获取的。延迟可能会更糟,但这是从一个运行在快速 NVMe 驱动器上的系统中获取的,这可以使延迟低至 100 微秒。
建议
为了获得最佳结果,需要实施正确的 CPU 设置。以下两种主要情况影响 CPU 大小:
-
测试/开发
-
生产
测试/开发
对于测试,任何超过一个 CPU 和 2 GHz 的东西对于小型测试都足够了,有几个客户端向 Elasticsearch 发送数据。搜索结果可能会有点慢,但不会出现任何问题。
生产
对于生产环境,请确保使用至少 2.3 GHz 或更高的 CPU。CPU 数量并不会对性能产生很大影响,但至少需要两个 CPU 才能确保最佳运行。一旦添加了更多客户端,CPU 数量可能需要进行修改以满足额外需求;如果 CPU 成为约束,可以添加更多的 Elasticsearch 节点。
最后,在核心数量与时钟速度之间进行选择时,Elasticsearch 利用具有多个核心。较少但更快的核心的性能优势并不像拥有更多较慢核心那样令人印象深刻。
在 Azure 上部署时,可以使用 DS2v3 VM 类型进行小型设置,因为它提供了两个 CPU 和足够的 RAM 以满足基本需求。
一旦正确调整了 CPU 大小,我们可以专注于系统内存(RAM)如何影响 Elasticsearch 的性能和可用性。
Elasticsearch 的内存大小
为 Elasticsearch 分配足够的 RAM 可能是要考虑的最重要的资源因素,以避免问题和性能不佳的设置。
内存是一种资源,拥有大量内存从来不是问题。作为架构师,在调整内存大小时需要考虑几件事情。与 CPU 资源类似,没有关于最低内存要求的硬性文件。
文件系统缓存
拥有大量 RAM 总是一个好主意,因为文件系统缓存或 Linux 页面缓存。
内核使用空闲系统内存来缓存、读取或写入请求,通过将一部分 RAM 分配给 I/O 请求,大大加快了 Elasticsearch 的搜索或索引速度。
如下截图所示,内核已分配大约 1.2GB 作为页面缓存:
利用页面缓存可以帮助减少搜索或传入索引时的响应时间;确保尽可能调整 RAM 大小。有一个点会平衡缓存使用,不会再有更多的 RAM 用于页面缓存。在这一点上,值得监控进程,以尝试识别这个阈值,避免不必要的费用。举个例子,如果一个虚拟机(VM)被调整为 32GB 的 RAM,但只使用大约 10GB 用于缓存,从未超过这个数字,那么调整为更小的 VM 可能是值得的,因为剩余的 RAM 将被闲置。
如下截图所示,在 Kibana 仪表板中,您可以监控 Elasticsearch 的缓存使用情况,这可能有助于确定是否有资源被闲置:
监控 Elasticsearch 的缓存使用情况
禁用交换
交换是一种机制,允许内核在不经常访问或内存压力(即系统内存不足)时将内存页面移动到磁盘上。交换的主要问题之一是,当内存页面移动到磁盘时,其访问时间比在 RAM 中要慢得多。
DDR4 内存的平均传输速率约为 10GB/s,更令人印象深刻的是,平均响应时间(或延迟)仅为 13 纳秒。将其与市场上甚至最快的 NVMe SSD 驱动器进行比较,后者的传输速率仅为 3.5GB/s,延迟约为 400 微秒。您很快就会意识到这成为一个问题:并非所有的云提供商或本地设置都使用 NVMe 驱动器,甚至交换到速度更慢的旋转介质都可能产生非常糟糕的结果。
因此,Elasticsearch 建议禁用所有形式的交换,而是依赖于正确的系统内存大小。
内存不足
错误的内存配置将导致不同的行为。可以归结为两种不同的情况:内存不足但足够运行系统,以及内存不足以至于 Elasticsearch 甚至无法启动。
在第一种情况下,存在内存约束,但有足够的内存让 Elasticsearch 启动和运行,主要问题是没有足够的内存用于页面缓存,导致搜索缓慢,每秒索引减少。在这种情况下,Elasticsearch 能够运行,但整体性能有所降低。
另一种情况可以分为两种不同的情况:一种是没有足够的内存启动 Elasticsearch,另一种是 Elasticsearch 能够启动,但一旦添加了一些索引,就会耗尽内存。为了避免系统崩溃,Linux 有一个称为“内存不足杀手”的机制。
无法启动
Elasticsearch 使用 JVM,默认情况下设置为使用至少 1GB 的堆内存。这意味着 Java 需要为 JVM 分配至少 1GB 的 RAM,因此要使 Elasticsearch 以最低配置启动,需要大约 2.5GB 的 RAM。
最简单的方法是通过使用systemctl status elasticsearch
来验证 Elasticsearch 服务的状态,它将返回类似于以下的错误消息:
在进一步检查错误日志时,我们可以清楚地看到 JVM 未能分配所需的内存,如下面的代码所示:
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 899284992 bytes for committing reserved memory.
# Possible reasons:
# The system is out of physical RAM or swap space
# In 32 bit mode, the process size limit was hit
# Possible solutions:
# Reduce memory load on the system
# Increase physical memory or swap space
# Check if swap backing store is full
# Use 64 bit Java on a 64 bit OS
# Decrease Java heap size (-Xmx/-Xms)
# Decrease number of Java threads
# Decrease Java thread stack sizes (-Xss)
# Set larger code cache with -XX:ReservedCodeCacheSize=
# This output file may be truncated or incomplete.
#
# Out of Memory Error (os_linux.cpp:2760), pid=933, tid=0x00007f1471c0e700
使用默认的 1 GB 堆进行测试已经足够。对于生产环境,请确保将堆大小增加到至少 2 GB,并根据需要进行调整。
要增加堆大小,请编辑/etc/elasticsearch/jvm.options
文件并找到以下选项:
-Xms1g
-Xmx1g
将这两个选项更改为以下内容:
-Xms2g
-Xmx2g
-Xms2g
短语表示 Java 应具有 2 GB 的最小堆,-Xmx2g
表示 2 GB 的最大堆。
OOM 杀手
内存不足杀手(OOM killer)机制的主要目的是通过杀死正在运行的进程来避免系统崩溃。每个进程都有一个oom_score
值。OOM killer 根据这个分数决定要杀死哪个进程;分数越高,进程在内存饥饿情况下被杀死的可能性就越大。这个分数是根据进程如果被杀死会释放多少内存来计算的。
如果我们以前的情景作为起点,Elasticsearch 能够以最少 2.5 GB 启动,一旦更多的索引/源添加到 Elasticsearch,它将开始需要更多的系统内存,直到没有更多的内存,并且系统接近完全崩溃。在那一刻,OOM killer 将跳入并杀死占用最多内存的进程(或进程)—在我们的情况下,是 Elasticsearch。
当查看/var/log/messages
下的事件时,我们可以看到 OOM killer 何时启动并杀死 Java 进程,然后 Elasticsearch 服务失败,如下面的截图所示:
建议
理想情况下,应为 Elasticsearch 分配足够的内存。内存的最低要求约为 2.5 GB,但这可能会导致系统很快耗尽内存。
对于测试目的,2.5 GB 对于一些源/索引可能足够。性能无疑会受到影响,但它仍然可以使用。
对于生产环境,请确保至少有 4 GB 或更多的系统内存。这应该允许 Elasticsearch 正常启动并运行多个配置的源/索引。确保相应增加 JVM 的堆大小,并考虑为页面缓存留出一些 RAM,以便在与文件系统交互时获得更快的响应时间。
接下来,我们将看一下 Elasticsearch 所需的存储配置。
Elasticsearch 的存储配置
Elasticsearch 的存储需求相对简单,可以分为两个主要类别:
-
存储容量
-
存储性能
让我们一起看看这两个选项,以及这里做出的决定如何影响整体性能。
容量
存储容量直接影响 Elasticsearch 能够存储多少数据。与许多其他情况一样,这是一个需要考虑的重要和复杂的要求,因为它取决于许多其他影响空间利用的变量。
主要变量将是发送到 Elasticsearch 的日志/指标的大小。这取决于每天(或每月)生成的日志数量。例如,如果每天的日志速率为 100 MB,那么这意味着至少需要 3 GB 的可用空间才能存储一个月的日志(100 MB x 30 天 = 3 GB)。
请注意,这是单个来源所需的最小空间。理想情况下,应该考虑一些额外空间,因为数据会经常变化,每天的 100MB 可能不是每个月的所有天都是恒定的,或者其他月份可能由于负载增加而有更高的速率。此外,一旦添加更多来源(或客户端),数据使用量将相应增长。
默认情况下,Elasticsearch 将其数据存储在/var/lib/elasticsearch
目录下。
性能
Elasticsearch 的主要特点之一是其能够非常快速地检索数据。虽然这是通过使用增强的存储文档的机制来实现的,但正确的性能设置肯定有助于实现几乎实时的搜索结果。
Elastic 没有提供存储需求的硬性数字,但在/var/lib/elasticsearch
目录中使用固态硬盘(SSD)有助于减少搜索时的延迟,因为与 HDD 相比,SSD 的延迟显著较低。SSD 还有助于数据摄入,因为写入会更快得到确认,从而允许更多并发的传入索引。这反映在 Kibana 监控仪表板上可以看到的每秒索引数中。
在云端进行大小设置,这实际上取决于提供商,因为有些提供商将磁盘的性能基于其大小,但其他提供商允许手动配置性能(如 IOPS 和吞吐量)。
拥有较慢的设置将导致搜索时间比预期的长,以及数据摄入速度较慢,因为磁盘设置不可靠且较慢。
考虑事项
对于空间,考虑一个大小,可以为意外的数据增长提供足够的空间。例如,如果整个月的预期数据使用量为 500GB,那么至少考虑 700GB 的大小;这样可以给您一个缓冲区,并避免出现没有足够空间留给 Elasticsearch 索引的情况。500GB 是一个很好的起点,因为它为测试/生产提供了足够的空间,同时可以计算实际数据使用量和数据变化(如果之前未知)。
为了提高性能,考虑使用更快的存储解决方案,如 SSD,以实现低延迟搜索和更快的索引。对于云端,大多数提供商都有一些可以与 Elasticsearch 一起使用的 SSD 产品。确保至少为了获得最佳性能而配置了至少 500 IOPS。
对于 Azure,您可以使用 P10 磁盘——这是一种 SSD,可以提供高达 500 IOPS 的性能——或者选择成本更低的 E10 作为替代方案,以达到相同的效果。
现在我们将看看 Logstash 和 Kibana 需要考虑的内容。
Logstash 和 Kibana 的要求
对于 Logstash 和 Kibana 没有特定的要求,但在设计 Elastic Stack 时要考虑一些事项总是一个好方法。
Logstash
Logstash 对 CPU 和内存的要求不高,但这完全取决于有多少来源在向 Logstash 提供数据,因为 Logstash 解析每个事件都需要一些额外的开销来完成这个过程。如果 Logstash 要单独安装(没有其他组件在同一系统上),那么一个 vCPU 和 2GB 的 RAM 应该足够小型/测试部署。理想情况下,应该监控实际使用情况并相应地调整系统。Logstash 默认具有用于临时存储事件的内存队列;当处理事件时,这种行为可以更改为使用持久队列。这样可以实现持久一致性,并避免在故障期间丢失数据。此外,持久队列有助于吸收事件的突发增长,充当客户端和 Logstash 之间的缓冲区。
在使用持久队列进行存储容量时,/var/lib/logstash
目录需要能够在 Logstash 处理事件时存储事件。空间量取决于两个因素:将数据发送到 Elasticsearch 的出口速度和发送到 Logstash 的事件数量。最低要求为 1GB,当来源数量增加时,空间需要相应增加。
Kibana
Kibana 的要求完全取决于同时访问仪表板的用户数量。分配给 Kibana 的资源量需要根据预期的使用情况来确定,例如,预期的用户群是什么?这些用户中有多少人会同时访问 Kibana?
对于小型部署/测试,最低要求由 JVM 决定。一个 vCPU 和 2GB 的 RAM 对于几个用户来说足够了,但一旦更多用户开始使用仪表板,RAM 将成为第一个资源瓶颈。
一般来说,Elastic Stack 有相当宽松的要求,主要由使用和来源数量决定。在软件方面,主要要求是 Java;由于所有组件都使用 JVM,因此可以使用 open JDK 或官方 JDK。
摘要
在本章中,我们介绍了在设计使用 Elasticsearch、Logstash 和 Kibana 的 Elastic Stack 时所需的要求。对于 Elasticsearch,我们确定了小型设置的最低 CPU 要求为两个 vCPU,CPU 速度应保持在 2 GHz 以上。如果不满足这些最低要求,Elasticsearch 将需要更长的时间启动,并且性能将更慢。这表现为每秒索引数量的减少和搜索延迟的增加,这两者都是需要避免的,以便我们能够充分利用 Elasticsearch 提供的几乎即时搜索。
在设计 Elasticsearch 设置时,内存大小可能是最重要的规格。系统内存的一部分将用于文件系统缓存(也称为页面缓存),这有助于搜索和每秒索引。不建议交换,因为与实际的 RAM 访问相比,它被认为是非常慢的,因此应该在 Elasticsearch 节点上禁用交换。如果未满足正确的内存要求,Elasticsearch 将无法启动,因为 JVM 启动时没有足够的内存。另一方面,如果有足够的内存来启动 JVM,但随着时间的推移负载增加,系统耗尽内存,OOM 或内存耗尽杀手将被启用,以避免导致应用程序失败的系统崩溃。所需的最小 RAM 量为 2.5 GB,但资源限制将相对快速地被看到。
在设置 Elasticsearch 时,存储容量和性能起着重要作用。容量取决于需要保留的数据量和配置的来源数量。延迟需要保持在最低水平,以便我们的搜索速度快。理想情况下,应该使用 SSD。
最后,对于 Logstash 和 Kibana,每个组件的最低要求是一个 vCPU 和 2GB 的 RAM。对于 Logstash,持久队列有空间要求。
在下一章中,我们将利用本章学到的知识,跳入使用 Elasticsearch、Logstash 和 Kibana 部署 Elastic Stack。
问题
-
Elasticsearch 建议使用多少个 CPU?
-
Elasticsearch 的推荐最低 CPU 速度是多少?
-
拥有错误的 CPU 配置会如何影响 Elasticsearch 的性能?
-
什么是页面缓存?
-
为什么建议在 Elasticsearch 节点上禁用交换?
-
内存不足会如何影响 Elasticsearch?
-
Elasticsearch 的最低内存要求是多少?
-
Elasticsearch 默认存储数据的位置在哪里?
-
为什么建议使用 SSD 来进行 Elasticsearch?
-
Logstash 的最低要求是什么?
-
什么是持久队列?
-
什么影响了 Kibana 的资源使用?
进一步阅读
想要了解更多信息,您可以阅读以下书籍:
- 《Linux:强大的服务器管理》,作者 Uday R. Sawant 等人:
www.packtpub.com/networking-and-servers/linux-powerful-server-administration
第十二章:使用 Elasticsearch、Logstash 和 Kibana 管理日志
部署 Elasticsearch、Logstash 和 Kibana(ELK Stack)相对简单,但在安装这些组件时需要考虑几个因素。虽然这不会是 Elastic Stack 的深入指南,但主要的收获将是实施方面、在过程中做出的决策以及作为架构师在做出这些决策时应该考虑的方式。
本章将帮助您作为架构师定义部署 ELK Stack 所需的方面,以及在使用组成 Elastic Stack 的组件时要使用的配置。
在本章中,我们将讨论以下主题:
-
安装和配置 Elasticsearch
-
安装和配置 Logstash 和 Kibana
-
安装和解释 Beats
-
配置 Kibana 仪表板
技术要求
本章将使用以下工具和安装:
-
Elasticsearch 安装指南:
www.elastic.co/guide/en/elasticsearch/reference/current/_installation.html
-
XFS 条带大小和条带单元“如何”:
xfs.org/index.php/XFS_FAQ#Q:_How_to_calculate_the_correct_sunit.2Cswidth_values_for_optimal_performance
-
Elasticsearch 配置细节:
www.elastic.co/guide/en/elasticsearch/reference/current/settings.html
-
避免 Elasticsearch 中的脑裂:
www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html#split-brain
-
Elasticsearch 集群状态 API:
www.elastic.co/guide/en/elasticsearch/reference/current/cluster-state.html
-
Logstash 安装指南:
www.elastic.co/guide/en/logstash/current/installing-logstash.html
-
Kibana 用户指南和安装方法:
www.elastic.co/guide/en/kibana/current/rpm.html
-
Beats 模块的 Logstash 过滤器示例:
www.elastic.co/guide/en/logstash/current/logstash-config-for-filebeat-modules.html
-
Logstash 配置文件的结构:
www.elastic.co/guide/en/logstash/current/configuration-file-structure.html
-
Filebeat 安装过程:
www.elastic.co/guide/en/beats/filebeat/current/filebeat-installation.html
-
Metricbeat 安装概述和细节:
www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-installation.html
部署概述
对于此部署,我们将使用 Elasticsearch 版本 6.5(这是撰写时的最新版本)。这意味着所有后续组件必须是相同的版本。基本操作系统将是 CentOS 7.6。虽然此特定部署将在本地虚拟机(VM)设置上实施,但这些概念仍然可以应用于云。
Elasticsearch 将使用 2 个节点在 2 个 vCPU VM 上部署,每个节点配备 4 GB 的 RAM(在第十一章中,设计 ELK 堆栈,我们确定了所需的最小 RAM 约为 2.5 GB)。VM 的底层存储为非易失性内存表达(NVMe),因此在其他地方复制设置时需要考虑一些因素。就空间而言,Elasticsearch 节点将分别具有 64 GB 的磁盘空间;节点将把 64 GB 磁盘挂载到/var/lib/elasticsearch
目录。
Logstash 和 Kibana 将在相同的 VM 上部署,使用 2 个 vCPU 和 4 GB 的 RAM。如第十一章所示,设计 ELK 堆栈,Logstash 需要持久存储队列。因此,我们将使用一个 32 GB 的专用磁盘。该磁盘将挂载到/var/lib/logstash
目录以进行持久排队。
我们可以总结部署所需的内容如下:
-
基本操作系统是 CentOS 7.6
-
Elasticsearch v6.5
-
Logstash v6.5
-
Kibana v6.5
-
Elasticsearch 使用 2 个节点在 2 个 vCPU VM 上,每个节点配备 4 GB 的 RAM
-
在单个 VM 上使用 2 个 vCPU 和 4 GB 的 RAM 部署 Logstash 和 Kibana
-
Elasticsearch 节点使用 64 GB 磁盘
-
32 GB 磁盘用于 Logstash 持久队列
以下图表说明了整个实施过程,并将让您了解事物是如何连接的:
安装 Elasticsearch
从无到有的功能性 Elasticsearch 设置需要安装软件;这可以通过多种方式和不同平台完成。以下是一些安装选项:
-
从源代码安装
-
为基于 Debian 的 Linux 发行版安装
deb
-
为Red Hat Enterprise Linux(RHEL)、CentOS、嵌入式系统的声音库(SLES)、OpenSLES 和基于 RPM 的发行版安装
rpm
-
为 Windows 安装
msi
-
部署 Docker 镜像
对于此设置,我们将使用 RPM 存储库以保持版本一致,并在更新可用时简化目的。
RPM 存储库
要安装 RHEL 和 CentOS 的 RPM 存储库,我们需要在/etc/yum.repos.d
目录中创建一个文件。在这里,文件的名称并不重要,但实际上,它需要有意义。文件的内容指示了yum
将如何搜索软件。
创建一个名为/etc/yum.repos.d/elastic.repo
的文件,其中包含以下代码细节:
[elasticsearch-6.x]
name=Elasticsearch repository for 6.x packages
baseurl=https://artifacts.elastic.co/packages/6.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md
创建了存储库文件后,只需运行以下命令:
yum makecache
这将刷新所有配置的存储库的元数据。在安装 Elasticsearch 之前,我们需要安装 OpenJDK 版本1.8.0
;为此,我们可以运行以下命令:
yum install java-1.8.0-openjdk
接下来,确认已安装java
,如下所示:
java -version
然后,您应该看到类似以下输出:
[root@elastic1 ~]# java -version
openjdk version "1.8.0_191"
OpenJDK Runtime Environment (build 1.8.0_191-b12)
OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)
然后,我们可以继续安装elasticsearch
,如下所示:
yum install elasticsearch
在启动 Elasticsearch 之前,需要进行一些配置。
Elasticsearch 数据目录
Elasticsearch 的默认配置将数据目录设置为/var/lib/elasticsearch
路径。这是通过/etc/elasticsearch/elasticsearch.yml
文件中的path.data
配置选项控制的:
# ---------------------------------Paths-------------------------------
#
# Path to directory where to store the data (separate multiple locations by comma):
#
path.data: /var/lib/elasticsearch
在此设置中,将挂载一个 64 GB 的磁盘到此位置。
在 Azure 部署时,请确保path.data
选项配置为使用数据磁盘而不是操作系统磁盘。
磁盘分区
在创建文件系统之前,需要对磁盘进行分区。为此,我们可以使用parted
实用程序。
首先,我们需要将磁盘初始化为gpt
;为此,我们可以使用以下命令:
sudo parted /dev/sdX mklabel gpt
然后,我们创建分区:
sudo parted /dev/sdX mkpart xfs 0GB 64GB
在这里,我们告诉parted
从0GB
到64GB
创建一个分区,或者从磁盘的开始到结束。此外,我们使用了xfs
签名,因为这是将用于数据目录的文件系统。
最后,通过运行以下命令验证分区是否已成功创建并具有正确的边界:
sudo parted /dev/sdX print
输出应类似于以下代码块:
[root@elastic1 ~]# parted /dev/sdb print
Model: ATA VBOX HARDDISK (scsi)
Disk /dev/sdb: 68.7GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:
Number Start End Size File system Name Flags
1 1049kB 64.0GB 64.0GB xfs
格式化文件系统
要能够在新创建的分区上存储数据,我们首先需要创建一个文件系统。对于此设置,我们将使用 XFS 文件系统。
要格式化磁盘,请运行mkfs.xfs
命令,如下所示:
[root@elastic1]# mkfs.xfs /dev/sdb1
meta-data=/dev/sdb1 isize=512 agcount=4, agsize=3906176 blks
= sectsz=512 attr=2, projid32bit=1
= crc=1 finobt=0, sparse=0
data = bsize=4096 blocks=15624704, imaxpct=25
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0 ftype=1
log =internal log bsize=4096 blocks=7629, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0
默认情况下,XFS 使用与内存页大小匹配的 4K 块大小;这也适用于相对较小的文件。
请注意,指定了设备文件的分区,而不是整个磁盘。虽然可以使用磁盘本身,但建议在分区上创建文件系统。此外,如果文件系统将用于 RAID 设置,则更改条带单元和条带大小通常有助于提高性能。
使用 fstab 进行持久挂载
现在文件系统已经创建,我们需要确保它在每次重启后都能正确挂载到正确的位置。
通常情况下,不建议使用设备文件挂载文件系统,特别是在云中。这是因为磁盘顺序可能会改变,导致磁盘的设备文件混乱。为了解决这个问题,我们可以使用磁盘的 UUID,这是一个唯一的标识符,即使磁盘移动到另一个系统,它也会持续存在。
获取磁盘的 UUID,请运行blkid
命令:
[root@elastic1 ~]# blkid
/dev/sda1: UUID="58c91edb-c361-470e-9805-a31efd85a472" TYPE="xfs"
/dev/sda2: UUID="H3KcJ3-gZOS-URMD-CD1J-8wIn-f7v9-mwkTWn" TYPE="LVM2_member"
/dev/sdb1: UUID="561fc663-0b63-4d2a-821e-12b6caf1115e" TYPE="xfs" PARTLABEL="xfs" PARTUUID="7924e72d-15bd-447d-9104-388dd0ea4eb0"
在这种情况下,/dev/sdb1
是我们将用于 Elasticsearch 的 64GB 磁盘。有了 UUID,我们可以将其添加到控制在启动时挂载的文件系统的/etc/fstab
文件中。只需编辑文件并添加以下条目:
UUID=561fc663-0b63-4d2a-821e-12b6caf1115e /var/lib/elasticsearch xfs defaults,nobarrier,noatime,nofail 0 0
以下是从上述命令中需要注意的一些重要细节:
-
nobarrier
:这有助于写入性能,因为它禁用了 XFS 用于确认写入是否已经到达持久存储的机制。这通常用于物理存储系统,其中没有电池备份写缓存。 -
noatime
:当文件被访问或修改时,这会禁用记录机制。启用atime
时,每次读取都会导致一小部分写入,因为访问时间需要更新。禁用可以帮助读取,因为它不会产生任何不必要的写入。 -
nofail
:这允许系统在支持挂载点的磁盘丢失时正常启动。这在部署在云上且无法访问控制台时特别有帮助。
接下来,在启动 Elasticsearch 服务之前,验证磁盘是否已挂载到正确的位置:
[root@elastic1 /]# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/centos-root 14G 1.6G 12G 12% /
devtmpfs 1.9G 0 1.9G 0% /dev
tmpfs 1.9G 0 1.9G 0% /dev/shm
tmpfs 1.9G 8.5M 1.9G 1% /run
tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup
/dev/sdb1 60G 33M 60G 1% /var/lib/elasticsearch
/dev/sda1 1014M 184M 831M 19% /boot
tmpfs 379M 0 379M 0% /run/user/0
最后,确保正确配置了/var/lib/elasticsearch
目录的所有权:
chown elasticsearch: /var/lib/elasticsearch
配置 Elasticsearch
在启动 Elasticsearch 服务之前,我们需要定义控制 Elasticsearch 行为的几个参数。配置文件以 YAML 格式存储在/etc/elasticsearch/elasticsearch.yml
中。让我们探讨需要更改的主要参数。
Elasticsearch YAML
Elasticsearch 的中央控制是通过/etc/elasticsearch/elasticsearch.yml
文件完成的,该文件以 YAML 格式存储。默认配置文件有相当完整的文档说明每个参数控制的内容,但作为配置过程的一部分,有一些条目应该被更改。
要查找的主要参数如下:
-
集群名称
-
发现设置
-
节点名称
-
网络主机
-
路径设置
集群名称
只有当 Elasticsearch 节点在其配置中指定了相同的集群名称时,它们才能加入集群。这是通过cluster.name
参数处理的;对于此设置,我们将使用elastic-cluster
:
# --------------------------------Cluster------------------------------
#
# Use a descriptive name for your cluster:
#
cluster.name: elastic-cluster
#
应该在两个节点上配置此设置,以便它们具有相同的值。否则,第二个节点将无法加入集群。
发现设置
发现参数控制 Elasticsearch 如何管理用于集群和主节点选举的节点内通信。
关于发现的两个主要参数是discovery.zen.ping.unicast.hosts
和discovery.zen.minimum_master_nodes
。
discovery.zen.ping.unicast.hosts
设置控制将用于集群的节点。由于我们的设置将使用两个节点,因此node1
的配置应具有node2
的 DNS 名称,而node2
应具有node1
的 DNS 名称。
discovery.zen.minimum_master_nodes
设置控制集群中主节点的最小数量;这用于避免出现多个活动主节点的分裂脑场景。可以根据简单的方程式计算此参数的数量,如下所示:
在这里,N是集群中节点的数量。对于此设置,由于只需配置2
个节点,设置应为2
。两个参数应如下所示:
# -----------------------------Discovery-------------------------------
#
# Pass an initial list of hosts to perform discovery when new node is started:
# The default list of hosts is ["127.0.0.1", "[::1]"]
#
discovery.zen.ping.unicast.hosts: ["elastic2"]
#
# Prevent the "split brain" by configuring the majority of nodes (total number of master-eligible nodes / 2 + 1):
#
discovery.zen.minimum_master_nodes: 2
#
# For more information, consult the zen discovery module documentation.
对于node2
,将discovery.zen.ping.unicast.hosts: ["elastic2"]
更改为discovery.zen.ping.unicast.hosts: ["elastic1"]
。
节点名称
默认情况下,Elasticsearch 使用随机生成的 UUID 作为其节点名称,这不太用户友好。该参数相对简单,因为它控制特定节点的名称。对于此设置,我们将使用elasticX
,其中X
是节点编号;node1
应如下所示:
#------------------------------Node---------------------------------
#
# Use a descriptive name for the node:
#
node.name: elastic1
将node2
更改为符合命名约定,因此它是elastic2
。
网络主机
这控制 Elasticsearch 将绑定到哪个 IP 地址并监听请求。默认情况下,它绑定到回环 IP 地址;需要更改此设置以允许来自集群的其他节点或允许其他服务器上的 Kibana 和 Logstash 发送请求。此设置还接受特殊参数,例如网络接口。对于此设置,我们将通过将network.host
参数设置为0.0.0.0
来使 Elasticsearch 监听所有地址。
在两个节点上,确保设置如下:
#-----------------------------Network-------------------------------
#
# Set the bind address to a specific IP (IPv4 or IPv6):
#
network.host: 0.0.0.0
路径设置
最后,路径参数控制 Elasticsearch 存储其数据和日志的位置。
默认情况下,它配置为将数据存储在/var/lib/elasticsearch
下,并将日志存储在/var/log/elasticsearch
下:
#-------------------------------Paths---------------------------------
#
# Path to directory where to store the data (separate multiple locations by comma):
#
path.data: /var/lib/elasticsearch
#
# Path to log files:
#
path.logs: /var/log/elasticsearch
该参数的一个关键方面是,在path.data
设置下,可以指定多个路径。Elasticsearch 将使用此处指定的所有路径来存储数据,从而提高整体性能和可用空间。对于此设置,我们将保留默认设置,即在前面的步骤中挂载数据磁盘到/var/lib/elasticsearch
目录下。
启动 Elasticsearch
现在我们已经配置了 Elasticsearch,我们需要确保服务在启动时能够自动正确地启动。
启动并启用 Elasticsearch 服务,如下所示:
systemctl start elasticsearch && systemctl enable elasticsearch
然后,通过运行以下命令验证 Elasticsearch 是否正确启动:
curl -X GET "elastic1:9200"
输出应类似于以下代码块:
[root@elastic1 /]# curl -X GET "elastic1:9200"
{
"name" : "elastic1",
"cluster_name" : "elastic-cluster",
"cluster_uuid" : "pIH5Z0yAQoeEGXcDuyEKQA",
"version" : {
"number" : "6.5.3",
"build_flavor" : "default",
"build_type" : "rpm",
"build_hash" : "159a78a",
"build_date" : "2018-12-06T20:11:28.826501Z",
"build_snapshot" : false,
"lucene_version" : "7.5.0",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
添加 Elasticsearch 节点
此时,我们可以将第二个节点添加到 Elasticsearch 集群中。
应将相同的配置应用于先前的步骤,确保更改设置以反映node2
的 DNS 名称。
要将节点添加到集群,我们只需简单地启动 Elasticsearch 服务。
服务启动时,消息被记录在/var/log/elasticsearch
,这表明节点已成功添加到集群中:
[2018-12-23T01:39:03,834][INFO ][o.e.c.s.ClusterApplierService] [elastic2] detected_master {elastic1}{XVaIWexSQROVVxYuSYIVXA}{fgpqeUmBRVuXzvlf0TM8sA}{192.168.1.150}{192.168.1.150:9300}{ml.machine_memory=3973599232, ml.max_open_jobs=20, xpack.installed=true, ml.enabled=true}, added {{elastic1}{XVaIWexSQROVVxYuSYIVXA}{fgpqeUmBRVuXzvlf0TM8sA}{192.168.1.150}{192.168.1.150:9300}{ml.machine_memory=3973599232, ml.max_open_jobs=20, xpack.installed=true, ml.enabled=true},}, reason: apply cluster state (from master [master {elastic1}{XVaIWexSQROVVxYuSYIVXA}{fgpqeUmBRVuXzvlf0TM8sA}{192.168.1.150}{192.168.1.150:9300}{ml.machine_memory=3973599232, ml.max_open_jobs=20, xpack.installed=true, ml.enabled=true} committed version [1]])
您可以使用以下代码来确认集群正在运行:
curl -X GET "elastic1:9200/_cluster/state?human&pretty"
输出应类似于以下代码块:
{
"cluster_name" : "elastic-cluster",
"compressed_size" : "10kb",
"compressed_size_in_bytes" : 10271,
"cluster_uuid" : "pIH5Z0yAQoeEGXcDuyEKQA",
"version" : 24,
"state_uuid" : "k6WuQsnKTECeRHFpHDPKVQ",
"master_node" : "XVaIWexSQROVVxYuSYIVXA",
"blocks" : { },
"nodes" : {
"XVaIWexSQROVVxYuSYIVXA" : {
"name" : "elastic1",
"ephemeral_id" : "fgpqeUmBRVuXzvlf0TM8sA",
"transport_address" : "192.168.1.150:9300",
"attributes" : {
"ml.machine_memory" : "3973599232",
"xpack.installed" : "true",
"ml.max_open_jobs" : "20",
"ml.enabled" : "true"
}
},
"ncVAbF9kTnOB5K9pUhsvZQ" : {
"name" : "elastic2",
"ephemeral_id" : "GyAq8EkiQGqG9Ph-0RbSkg",
"transport_address" : "192.168.1.151:9300",
"attributes" : {
"ml.machine_memory" : "3973599232",
"ml.max_open_jobs" : "20",
"xpack.installed" : "true",
"ml.enabled" : "true"
}
}
},
"metadata" : {
...(truncated)
对于需要添加到集群的任何后续节点,应遵循先前的步骤,确保cluster.name
参数设置为正确的值。
安装 Logstash 和 Kibana
有了 Elasticsearch 集群正在运行,我们现在可以继续安装 Logstash 和 Kibana。
在前面步骤中使用的存储库对于剩余的组件是相同的。因此,应该将之前用于添加存储库的相同过程应用于 Logstash 和 Kibana 节点。
这是一个总结,之前已经探讨过相同的过程:
-
将存储库添加到
/etc/yum.repos.d/elastic.repo
-
更新
yum
缓存为sudo yum makecache
-
使用
sudo yum install logstash kibana
安装 Logstash 和 Kibana -
为
/var/lib/logstash
初始化磁盘和sudo parted /dev/sdX mklabel gpt
-
创建
sudo parted /dev/sdX mkpart xfs 0GB 32GB
分区(注意这是一个 32GB 磁盘) -
创建
sudo mkfs.xfs /dev/sdX1
文件系统 -
更新
fstab
-
更新
sudo chown logstash: /var/lib/logstash
目录权限
Logstash systemd
单元默认情况下不会被添加;要这样做,运行 Logstash 提供的脚本:
sudo /usr/share/logstash/bin/system-install
最后,一个特定的组件是必需的,那就是一个协调的 Elasticsearch 节点。这将作为 Elasticsearch 集群的负载均衡器,Kibana 用于安装 Elasticsearch。
sudo yum install elasticsearch
有关协调节点配置的更多信息在配置 Kibana部分提供。
配置 Logstash
与 Elasticsearch 类似,Logstash 的主配置文件位于/etc/logstash/logstash.yml
下,并且某些设置需要更改以实现所需的功能。
Logstash YAML
首先,应调整node.name
参数,以便正确标识 Logstash 节点。默认情况下,它使用机器的主机名作为node.name
参数。然而,由于我们在同一系统上运行 Logstash 和 Kibana,值得改变这个设置以避免混淆。
接下来,我们需要考虑排队设置;这些控制 Logstash 如何管理队列类型以及它存储队列数据的位置。
第一个设置是queue.type
,它定义了 Logstash 使用的队列类型。对于这个设置,我们使用持久队列:
# ------------ Queuing Settings --------------
#
# Internal queuing model, "memory" for legacy in-memory based queuing and
# "persisted" for disk-based acked queueing. Defaults is memory
#
queue.type: persisted
#
由于排队设置为持久,事件需要存储在临时位置,然后再发送到 Elasticsearch;这由path.queue
参数控制:
# If using queue.type: persisted, the directory path where the data files will be stored.
# Default is path.data/queue
#
# path.queue:
#
如果保持默认设置,Logstash 将使用path.data/queue
目录来存储队列中的事件。path.data
目录默认为/var/lib/logstash
,这是我们配置 32GB 磁盘的位置;这是期望的配置。如果需要指定另一个位置用于排队,应调整此设置以匹配正确的路径。
在logstash.yml
文件中需要更改的最后一个设置是queue.max_bytes
设置,它控制队列允许的最大空间。对于这个设置,由于我们为此目的添加了一个专用的 32GB 磁盘,可以将设置更改为 25GB,以便在需要更多空间时提供缓冲。设置应如下所示:
# If using queue.type: persisted, the total capacity of the queue in number of bytes.
# If you would like more unacked events to be buffered in Logstash, you can increase the
# capacity using this setting. Please make sure your disk drive has capacity greater than
# the size specified here. If both max_bytes and max_events are specified, Logstash will pick
# whichever criteria is reached first
# Default is 1024mb or 1gb
#
queue.max_bytes: 25gb
作为一个选项,xpack.monitoring.enabled
设置可以设置为 true,以通过 Kibana 启用监视。
确保yaml
文件中的参数在行首没有空格,否则可能无法加载配置。
Logstash 管道
Logstash 输出由通过放置在/etc/logstash/conf.d/
下的文件配置的管道控制;这些文件控制 Logstash 如何摄取数据,处理数据,然后将其作为 Elasticsearch 的输出返回。管道配置类似于以下代码:
# The # character at the beginning of a line indicates a comment. Use
# comments to describe your configuration.
input {
}
# The filter part of this file is commented out to indicate that it is
# optional.
# filter {
#
# }
output {
}
在这里,input
部分定义要接受的数据以及来源;在这个设置中,我们将使用beats
作为输入。过滤器部分控制数据在发送到输出之前的转换方式,输出部分定义数据发送到哪里。在这种情况下,我们将数据发送到 Elasticsearch 节点。
让我们为syslog
消息创建一个配置文件,以便通过 Logstash 进行过滤,然后发送到 Elasticsearch 集群。该文件需要放置在/etc/logstash/conf.d
中,因为输入将来自beats
模块;让我们称之为beats-syslog.conf
文件:
sudo vim /etc/logstash/conf.d/beats-syslog.conf
文件的内容如下:
input {
beats {
port => 5044
}
}
filter {
if [fileset][module] == "system" {
if [fileset][name] == "auth" {
grok {
match => { "message" => ["%{SYSLOGTIMESTAMP:[system][auth][timestamp]} %{SYSLOGHOST:[system][auth][hostname]} sshd(?:\[%{POSINT:[system][auth][pid]}\])?: %{DATA:[system][auth][ssh][event]} %{DATA:[system][auth][ssh][method]} for (invalid user )?%{DATA:[system][auth][user]} from %{IPORHOST:[system][auth][ssh][ip]} port %{NUMBER:[system][auth][ssh][port]} ssh2(: %{GREEDYDATA:[system][auth][ssh][signature]})?",
"%{SYSLOGTIMESTAMP:[system][auth][timestamp]} %{SYSLOGHOST:[system][auth][hostname]} sshd(?:\[%{POSINT:[system][auth][pid]}\])?: %{DATA:[system][auth][ssh][event]} user %{DATA:[system][auth][user]} from %{IPORHOST:[system][auth][ssh][ip]}",
"%{SYSLOGTIMESTAMP:[system][auth][timestamp]} %{SYSLOGHOST:[system][auth][hostname]} sshd(?:\[%{POSINT:[system][auth][pid]}\])?: Did not receive identification string from %{IPORHOST:[system][auth][ssh][dropped_ip]}",
"%{SYSLOGTIMESTAMP:[system][auth][timestamp]} %{SYSLOGHOST:[system][auth][hostname]} sudo(?:\[%{POSINT:[system][auth][pid]}\])?: \s*%{DATA:[system][auth][user]} :( %{DATA:[system][auth][sudo][error]} ;)? TTY=%{DATA:[system][auth][sudo][tty]} ; PWD=%{DATA:[system][auth][sudo][pwd]} ; USER=%{DATA:[system][auth][sudo][user]} ; COMMAND=%{GREEDYDATA:[system][auth][sudo][command]}",
"%{SYSLOGTIMESTAMP:[system][auth][timestamp]} %{SYSLOGHOST:[system][auth][hostname]} groupadd(?:\[%{POSINT:[system][auth][pid]}\])?: new group: name=%{DATA:system.auth.groupadd.name}, GID=%{NUMBER:system.auth.groupadd.gid}",
"%{SYSLOGTIMESTAMP:[system][auth][timestamp]} %{SYSLOGHOST:[system][auth][hostname]} useradd(?:\[%{POSINT:[system][auth][pid]}\])?: new user: name=%{DATA:[system][auth][user][add][name]}, UID=%{NUMBER:[system][auth][user][add][uid]}, GID=%{NUMBER:[system][auth][user][add][gid]}, home=%{DATA:[system][auth][user][add][home]}, shell=%{DATA:[system][auth][user][add][shell]}$",
"%{SYSLOGTIMESTAMP:[system][auth][timestamp]} %{SYSLOGHOST:[system][auth][hostname]} %{DATA:[system][auth][program]}(?:\[%{POSINT:[system][auth][pid]}\])?: %{GREEDYMULTILINE:[system][auth][message]}"] }
pattern_definitions => {
"GREEDYMULTILINE"=> "(.|\n)*"
}
remove_field => "message"
}
date {
match => [ "[system][auth][timestamp]", "MMM d HH:mm:ss", "MMM dd HH:mm:ss" ]
}
geoip {
source => "[system][auth][ssh][ip]"
target => "[system][auth][ssh][geoip]"
}
}
else if [fileset][name] == "syslog" {
grok {
match => { "message" => ["%{SYSLOGTIMESTAMP:[system][syslog][timestamp]} %{SYSLOGHOST:[system][syslog][hostname]} %{DATA:[system][syslog][program]}(?:\[%{POSINT:[system][syslog][pid]}\])?: %{GREEDYMULTILINE:[system][syslog][message]}"] }
pattern_definitions => { "GREEDYMULTILINE" => "(.|\n)*" }
remove_field => "message"
}
date {
match => [ "[system][syslog][timestamp]", "MMM d HH:mm:ss", "MMM dd HH:mm:ss" ]
}
}
}
}
output {
elasticsearch {
hosts => ["elastic1", "elastic2"]
manage_template => false
index => "%{[@metadata][beat]}-%{[@metadata][version]}-%{+YYYY.MM.dd}"
}
}
确保output
部分具有 Elasticsearch 节点的 DNS 名称或 IP 地址:
output {
elasticsearch {
hosts => ["elastic1", "elastic2"]
manage_template => false
index => "%{[@metadata][beat]}-%{[@metadata][version]}-%{+YYYY.MM.dd}"
}
}
在此管道配置中,beats
模块将日志发送到 Logstash 节点。然后 Logstash 将处理数据并在 Elasticsearch 节点之间进行负载均衡输出。现在我们可以继续配置 Kibana。
配置 Kibana
Elastic Stack 的最后一部分是 Kibana;配置方式与 Elasticsearch 和 Logstash 类似,由/etc/kibana/kibana.yml
处理。
Kibana YAML
默认情况下,Kibana 侦听端口5601
;这由server.port
参数控制,如果需要在不同端口访问 Kibana,则可以更改。对于此设置,将使用默认设置。
server.host
设置控制 Kibana 将侦听请求的地址。由于需要从外部来源(即localhost
之外)访问,我们可以使用以下设置:
# Specifies the address to which the Kibana server will bind. IP addresses and host names are both valid values.
# The default is 'localhost', which usually means remote machines will not be able to connect.
# To allow connections from remote users, set this parameter to a non-loopback address.
server.host: "0.0.0.0"
server.name
参数默认为 Kibana 运行的主机名,但由于 Logstash 与 Kibana 一起运行,我们可以更改此参数以标识 Kibana 部分:
# The Kibana server's name. This is used for display purposes.
server.name: "kibana"
最后,elasticsearch.url
指定了 Kibana 将连接到哪个 Elasticsearch 节点。正如我们之前提到的,我们将使用一个 Elasticsearch 协调节点来充当其他两个节点之间的负载均衡器。
以下是用于所有查询的 Elasticsearch 实例的 URL:
elasticsearch.url: "http://localhost:9200"
协调节点
协调节点是一个 Elasticsearch 节点,不接受输入,不存储数据,也不参与主节点或从节点的选举。
该节点的目标是在集群中的不同 Elasticsearch 节点之间负载均衡 Kibana 的请求。安装过程与之前使用的相同,即确保 Java(open JDK)也已安装。
配置将不同,因为我们想要实现一些目标:
-
禁用主节点角色
-
禁用摄入节点角色
-
禁用数据节点角色
-
禁用跨集群搜索
为此,我们需要在/etc/elasticsearch/elasticsearch.yml
文件中设置以下设置:
cluster.name: elastic-cluster
node.name: coordinate
network.host: 0.0.0.0
node.master: false
node.data: false
node.ingest: false
cluster.remote.connect: false
discovery.zen.ping.unicast.hosts: ["elastic1", "elastic2"]
启动 Logstash 和 Kibana
所有组件已配置完成后,我们可以启动 Logstash、Kibana 和协调 Elasticsearch 节点。
Logstash 可以首先启动,因为它不需要其他组件中的任何一个处于运行状态:
sudo systemctl start logstash && sudo systemctl enable logstash
然后,我们可以启动和启用elasticsearch
协调节点:
sudo systemctl start elasticsearch && sudo systemctl enable elasticsearch
最后,kibana
可以通过相同的过程进行:
sudo systemctl start kibana && sudo systemctl enable kibana
要验证所有内容是否正确启动,请将浏览器指向端口5601
上的kibana
地址http://kibana:5601
。单击监控,然后单击启用监控;几秒钟后,您将看到类似以下屏幕截图的内容:
您应该看到所有组件都在线;黄色状态是由于未复制的系统索引,但这是正常的。
有了这些,集群已经运行起来,准备好接受来自日志和指标的传入数据。我们将使用 Beats 向集群提供数据,我们将在下一节中探讨。
Beats 是什么?
Beats 是 Elastic.co(Elasticsearch 背后的公司)的轻量级数据发货人。Beats 旨在易于配置和运行。
Beats 是方程式的客户端部分,驻留在要监视的系统上。Beats 从环境中的服务器捕获指标、日志等,并将它们发送到 Logstash 进行进一步处理,或者发送到 Elasticsearch 进行索引和分析。
有多个官方 Beats(由 Elastic 开发和维护),社区还开发了大量的开源 Beats。
我们将在此设置中使用的主要 Beats 是Filebeat和Metricbeat。
Filebeat
Filebeat 功能从来源(如 syslog、Apache 和 Nginx)收集日志,然后将其发送到 Elasticsearch 或 Logstash。
需要在需要数据收集的每台服务器上安装 Filebeat 客户端才能启用。该组件允许将日志发送到集中位置进行无缝搜索和索引。
Metricbeat
Metricbeat 收集指标,如 CPU 使用率、内存使用率、磁盘 IO 统计和网络统计,然后将其发送到 Elasticsearch 或 Logstash。
实际上,没有必要进一步转换度量数据,因此直接将数据馈送到 Elasticsearch 更有意义。
应在需要监视资源使用情况的所有系统中安装 Metricbeat;在 Elasticsearch 节点上安装 Metricbeat 可以让您更密切地控制资源使用情况,以避免问题。
还有其他 Beats,例如以下内容:
-
Packetbeat:用于网络流量监控
-
Journalbeat:用于
systemd
日志 -
Auditbeat:用于审计数据,如登录
此外,Beats 可以通过模块进一步适应特定需求。例如,Metricbeat 具有一个模块用于收集 MySQL 性能统计信息。
让我们不要错过一拍-安装 Beats
通过 Elasticsearch 提供的 Beats 的安装可以通过之前用于安装 Elasticsearch、Logstash 和 Kibana 的 Elastic 存储库来完成。
首先,在 Elasticsearch 节点中安装 Filebeat:
sudo yum install -y filebeat
安装后,通过运行以下代码确认已完成:
filebeat version
输出应类似于以下命令块:
[root@elastic1 ~]# filebeat version
filebeat version 6.5.4 (amd64), libbeat 6.5.4 [bd8922f1c7e93d12b07e0b3f7d349e17107f7826 built 2018-12-17 20:22:29 +0000 UTC]
要安装metricbeat
,过程与它位于同一存储库相同:
sudo yum install metricbeat
要在其他客户端上安装 Beats,只需像之前解释的那样添加 Elastic 存储库并通过yum
安装即可。如果分发中没有可用的存储库,Beats 也可以作为独立软件包提供。
配置 Beats 客户端
在 Elasticsearch 节点上安装了 Filebeat 和 Metricbeat 后,我们可以继续配置它们将数据馈送到 Logstash 和 Elasticsearch。
Filebeat YAML
现在,毫无疑问,大多数 Elastic 组件都是通过 YAML 文件进行配置的。Filebeat 也不例外,其配置由/etc/filebeat/filebeat.yml
文件处理。
首先,我们需要告诉filebeat
在哪里查找要发送到 Logstash 的日志文件。在yaml
文件中,这在filebeat.inputs
部分中;将enabled: false
更改为enabled: true
,如下所示:
#=========================== Filebeat inputs =============================
filebeat.inputs:
# Each - is an input. Most options can be set at the input level, so
# you can use different inputs for various configurations.
# Below are the input specific configurations.
- type: log
# Change to true to enable this input configuration.
enabled: true
# Paths that should be crawled and fetched. Glob based paths.
paths:
- /var/log/*.log
Filebeat 附带了 Kibana 仪表板,便于可视化发送的数据。这允许 Filebeat 加载仪表板,然后将 Kibana 地址添加到setup.kibana
部分:
#==============================Kibana================================
# Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API.
# This requires a Kibana endpoint configuration.
setup.kibana:
# Kibana Host
# Scheme and port can be left out and will be set to the default (http and 5601)
# In case you specify and additional path, the scheme is required: http://localhost:5601/path
# IPv6 addresses should always be defined as: https://[2001:db8::1]:5601
host: "kibana:5601"
加载dashboards
,如下所示:
filebeat setup --dashboards
此配置只需要针对每个新的 Beat 安装执行一次;在进一步安装 Filebeat 时无需更改此设置,因为仪表板已经加载。
由于我们将要将数据发送到 Logstash,因此注释掉output.elasticsearch
部分;然后取消注释output.logstash
部分并添加 Logstash 的详细信息:
#------------------------ Elasticsearch output ----------------------------
#output.elasticsearch:
# Array of hosts to connect to.
# hosts: ["localhost:9200"]
# Optional protocol and basic auth credentials.
#protocol: "https"
#username: "elastic"
#password: "changeme"
#-------------------------- Logstash output -------------------------------
output.logstash:
# The Logstash hosts
hosts: ["logstash:5044"]
接下来,我们将使用 Filebeat 的系统模块将输出发送到 Logstash;要启用此功能,只需运行以下命令:
filebeat modules enable system
然后,加载索引模板到elasticsearch
,如下所示:
filebeat setup --template -E output.logstash.enabled=false -E 'output.elasticsearch.hosts=["elastic1:9200", "elastic2"]'
最后,启动并启用filebeat
,如下所示:
sudo systemctl enable filebeat && sudo systemctl start filebeat
要验证数据是否已发送,可以使用提供的仪表板之一来可视化syslog
事件。在 Kibana 上,转到仪表板并在搜索栏中键入Syslog Dashboard
;您将看到类似以下截图的内容:
Kibana 仪表板显示了Syslog Dashboard
的搜索结果
Metricbeat YAML
Metricbeat 遵循与 Filebeat 类似的过程,需要编辑/etc/metricbeat/metricbeat.yml
文件以将输出发送到 Elasticsearch,并加载 Kibana 仪表板(即,它们需要运行一次)。
为此,编辑metricbeat.yml
文件以允许 Metricbeat 加载 Kibana 仪表板:
setup.kibana:
host: "kibana:5601"
接下来,指定Elasticsearch
集群:
#------------------------ Elasticsearch output ----------------------------
output.elasticsearch:
# Array of hosts to connect to.
hosts: ["elastic1:9200", "elastic2:9200"]
加载 Kibana 仪表板
,如下:
metricbeat setup --dashboards
默认情况下,metricbeat
启用了系统模块,它将捕获 CPU、系统负载、内存和网络的统计信息。
启动并启用metricbeat
服务,如下:
sudo systemctl enable metricbeat && sudo systemctl start metricbeat
要确认数据是否被发送到集群,请转到 kibana 屏幕上的Discover
,然后选择metricbeat-*
索引模式并验证事件是否被发送:
使用metricbeat-*
索引模式过滤的事件
下一步
此时,集群现在已经完全可用。剩下的就是在集群的其他节点上安装 Metricbeat 和 Filebeat,以确保完全可见集群的健康和资源使用情况。
向集群添加更多客户端只是安装适当的 Beat,具体取决于需要监视什么以及需要索引哪些日志。
如果集群的负载增加,可以选择多种选项——要么向集群添加更多节点以平衡负载请求,要么增加每个节点的可用资源数量。在某些情况下,简单地增加更多资源是一种更具成本效益的解决方案,因为它不需要配置新节点。
这样的实现可以用于监视 Kubernetes 设置的性能和事件(例如第十一章中描述的设置,设计 ELK Stack)。一些 Beats 具有特定的模块,用于从 Kubernetes 集群中提取数据。
最后,可以对此设置进行的一个增强是让 Beat 客户端指向协调 Elasticsearch 节点,以充当节点之间的负载均衡器;这样可以避免在 Beats 的输出配置中硬编码每个 Elasticsearch 节点,只需要一个单一的地址。
总结
在本章中,我们经历了许多步骤来配置 Elastic Stack,这是四个主要组件的集合——Elasticsearch、Logstash、Kibana 和 Beats。对于这个设置,我们使用了三个虚拟机;我们托管了两个 Elasticsearch 节点,然后在单个系统上安装了 Logstash 和 Kibana,每个组件都使用了 6.5 版本。我们使用 Elastic Stack 提供的 RPM 存储库安装了 Elasticsearch;使用yum
安装了所需的软件包。Elasticsearch 配置是使用elasticsearch.yml
文件完成的,该文件控制elasticsearch
的行为。我们定义了一些对于功能性集群是必需的设置,比如cluster.name
参数和discovery.zen.minimum_master_nodes
。
通过配置集群名称和发现设置,我们添加了一个新的 Elasticsearch 节点,这允许节点自动加入集群。然后,我们开始安装 Kibana 和 Logstash,它们都在用于 Elasticsearch 的相同 RPM 存储库中提供;通过它们各自的.yml
文件进行配置 Logstash 和 Kibana。
一旦所有三个主要组件都启动,并且操作准备好接受传入数据,我们就开始安装 Beats,这些是 Elasticsearch 和 Logstash 用来摄取数据的数据传输器。对于日志和事件,我们使用 Filebeat,对于内存使用和 CPU 等系统指标,我们使用 Metricbeat。
在下一章中,我们将学习系统管理的挑战和 Salt 的架构。
问题
-
如何安装 Elasticsearch?
-
如何分区磁盘?
-
如何持久地挂载文件系统?
-
哪个文件控制 Elasticsearch 配置?
-
cluster.name
设置是做什么的? -
Elasticsearch 集群中推荐的节点数量是多少?
-
如何将 Elasticsearch 节点添加到现有集群中?
-
安装 Logstash 和 Kibana 需要哪些步骤?
-
什么是持久性排队?
-
什么是协调节点?
-
Beats 是什么?
-
Filebeat 用于什么?
进一步阅读
- 《Linux 基础》作者 Oliver Pelz:
www.packtpub.com/networking-and-servers/fundamentals-linux
第四部分:使用 Saltstack 进行系统管理
在本节中,读者将能够了解“基础设施即代码”(IaC)的工作原理以及使用 Saltstack 进行系统管理的优势。然后概述一些最佳设计实践。
本节包括以下章节:
-
第十三章,“用咸水解决管理问题”
-
第十四章,“让你的手变咸”
-
第十五章,“设计最佳实践”
第十三章:用 Salty 解决管理问题
在本章中,我们将发现并讨论为什么企业需要为其基础设施拥有集中管理工具,包括异构环境带来的高复杂性。我们将讨论解决这个问题的解决方案,以及以下内容:
-
新技术给我们的业务带来复杂性
-
我们如何集中化系统管理。
-
基础设施即代码(IaC)如何帮助我们维护系统状态
-
利用 IaC 的工具
-
SaltStack 平台及其组件
让我们开始我们的系统管理之旅。
集中化系统管理
理解系统管理背后的原因很容易被忽视。我们经常假设,只因为一个企业拥有庞大的 IT 基础设施,就需要解决其清单管理的问题。虽然这显然是真的,但其中还有更多。我们作为架构师的工作包括倾听客户的问题,并理解他们究竟在寻找什么。
新技术和系统管理
在这个不断发展的 IT 世界中,变化迅速。几乎每天都会出现新技术。虚拟化、物联网和云等技术正在塑造和改变我们使用 IT 的方式,通过不断扩大我们的基础设施,这是裸金属时代从未见过的。
所有这些变化和指数级增长意味着 IT 经理有更多的东西要管理,但时间更少来培训他们的员工支持这些技术,因此许多企业几乎无法跟上步伐。这可能导致他们不愿意采用新技术。但许多企业别无选择,只能采用这些技术,因为他们担心变得无关紧要,无法满足客户的需求。如果他们的竞争对手占据优势并提供更好更快的服务,他们很可能会破产。
公司希望尽快采用这些技术,以在竞争对手之上取得优势,但新技术往往伴随着陡峭的学习曲线。在此期间,IT 人员需要学习如何管理和维护新系统,这导致保持关键系统和工作负载可用性成为一项挑战。不遵守我们的 SLA 成为真正的威胁;想象一下,开发人员需要运维团队在我们的开发环境系统中应用库补丁以测试新版本,因为我们的运维人员(或至少一半)正在接受培训,开发人员很容易绕过标准化的变更请求流程并自行应用更新。在这种情况下,影子 IT 非常普遍,我们需要尽一切努力避免。影子 IT 可能使我们的公司不符合监管标准。
虽然 IT 领导者推动采用新技术,但他们往往面临非常有限且日益减少的预算来进行这种转型。这也直接影响我们的关键系统和工作负载,因为对系统管理的投资减少并转向创新。迈向创新并不是坏事,因为最终它将使我们能够提供更好的服务,但重要的是要理解,这也会对我们现有环境的维护产生后果。
随着新技术的出现,新基础设施也随之而来;混合环境变得越来越普遍,了解如何以最佳和最有效的方式管理这些混合环境至关重要。
重新掌控我们自己的基础设施
掌控我们的基础设施是系统管理的主要目标。但是拥有控制权意味着什么?清单清单、版本控制、自动打补丁和软件分发都是系统管理的一部分。所有这些都是更大格局的一部分,IT 可以重新掌控其基础设施,并确保无论他们正在运行什么 Linux 发行版,都可以确保其系统的合规性和标准化。
通常我们的系统是分开的;这种分离是因为它们可能在特征上有所不同。我们可能有基于 Red Hat Enterprise Linux 的发行版或基于 Debian 的发行版的系统,具有不同架构的系统,如 x86、功率服务器,甚至 ARM。所有这些系统甚至可能不会相互通信或为相同的目的服务;它们都成为 IT 必须维护和管理的存储。
想象一下,在没有工具来集中和自动化任务的情况下,手动在每个独立的存储中执行系统管理的各种任务。人为错误是这种情况最直接的威胁,其次是 IT 业务必须承担的大量复杂性、时间和成本,包括培训员工、雇佣员工以及为每种不同的系统类型购买特定的管理工具。
集中工具分散问题
集中配置管理可以帮助我们以受控、一致和稳定的方式控制系统的变更。对于运行集群或配置为高可用性的系统来说,这是完美的,因为集群中的所有节点都必须具有完全相同的配置。通过配置管理,我们还可以理解某些文件的权限背后的原因,所有系统上安装的软件包,甚至配置文件中的一行代码。
通过配置管理工具实施的这些变更或配置也可以回滚,因为市场上大多数工具都带有版本控制,任何拼写错误、人为错误或不兼容的更新都可以轻松回滚。
随着我们慢慢过渡到云环境,虚拟机和资源变得越来越成为商品和服务。可以帮助我们管理、配置和维护云基础设施的配置管理工具变得非常有价值。通过这些类型的工具,我们可以以更具弹性的方式处理基础设施,并以描述性的方式定义它,这意味着我们可以拥有部署相同基础设施的模板或根据定义实施更改;这就是我们所说的基础设施即代码(IaC)。
编码实现期望状态
IaC 背后的整个理念是在我们的环境中实现一致性和版本控制。IaC 寻求一种更具描述性和标准的资源配置方式,避免独特和特殊的部署,以防止由于每个组件的独特性而重新创建环境变得非常复杂的情况。
IaC 工具通过特定语言或现有语言(如 YAML 或 JSON)定义配置;以下是一个从 Terraform 模板中提取的示例,该模板定义了 Microsoft Azure 中的虚拟机:
resource "azurerm_resource_group" "test" {
name = "example"
location = "East US 2"
}
resource "azurerm_kubernetes_cluster" "test" {
name = "exampleaks"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
dns_prefix = "acctestagent1"
agent_pool_profile {
name = "default"
count = 1
vm_size = "Standard_B1_ls"
os_type = "Linux"
os_disk_size_gb = 30
}
service_principal {
client_id = "00000000-0000-0000-0000-000000000000"
client_secret = "00000000000000000000000000000000"
}
tags = {
Environment = "Production"
}
}
output "client_certificate" {
value = "${azurerm_kubernetes_cluster.test.kube_config}"
}
output "kube_config" {
value = "${azurerm_kubernetes_cluster}"
}
在云基础设施世界中,弹性是关键。现在我们没有在数据中心等待使用的现有资源。在云中,我们按需付费,拥有虚拟机或存储空间会增加我们的月度账单,这并不理想。通过 IaC,我们可以根据需求扩展或缩减这些环境。例如,我们知道我们有一个应用程序,只在工作时间内消耗最大,并需要额外的实例来支持负载。但在工作时间之外,一个实例就足以支持负载。通过 IaC,我们可以编写脚本在早上创建额外的实例,并在一天结束时减少实例。每个实例都不是唯一的,我们可以利用通过 IaC 使用描述性文件的配置管理工具来实现这一点。
有几种工具可以完成上述示例,但许多工具不仅可以在云中或虚拟化环境中提供基础设施。其他配置管理工具甚至可以做得更多;它们可以推送配置文件,安装软件包,创建用户,甚至文件系统。这些工具执行配置的方式和方法有几种。许多工具需要代理,但还有一些是无代理的。
配置管理工具执行其更改的方式基本上是通过推送或拉取。这将取决于(但并非总是)工具是否使用代理或无代理。大多数无代理工具将您在 IaC 文件中声明的配置更改推送到云中的 API,或者通过 SSH 发送更改,当您通过命令行或脚本执行工具时。
另一方面,拉取几乎总是通过代理进行。代理不断地向配置管理服务器查询定义,验证所需状态,以防有所更改,然后从服务器拉取这些更改并应用到其主机上。
推送和拉取可以以两种不同的方式应用:声明式和命令式。声明式方式指定所需状态是什么,并且更改将按照 IaC 规范文件中定义的方式应用。命令式方式包括按特定顺序运行一组指令或命令,告诉系统如何达到所需状态。
通过 IaC 进行配置管理的一些开源工具如下:
-
Puppet
-
Chef
-
Ansible
-
Terraform
-
盐
-
Vagrant
我们将在第十四章《Getting Your Hands Salty》中深入了解盐及其组件。
理解 NaCl
我们了解了 IaC 是什么,以及系统管理背后的困难。但作为未来解决方案的架构师,我们需要知道并了解哪些工具可以帮助我们的客户面对配置管理带来的挑战。
在本节中,我们将讨论如何使用盐,或者称为盐堆平台,来帮助我们实现集中、灵活和弹性的管理基础设施。
介绍盐
盐是一个由 Tomas S Hatch 于 2011 年开发的 Python 开源项目。最初,它并不是一个配置管理工具,而是一个数据收集工具和远程命令执行软件,利用了 ZeroMQ 库。同年晚些时候,通过状态添加了配置管理功能,我们稍后将进行审查。
由于盐是用 Python 编写的,因此它具有高度的可扩展性和模块化,可以轻松编写自定义模块以进一步扩展其功能。
理解盐不仅是一个配置管理工具至关重要,但在这些章节中,我们将专注于其配置管理能力,因为这是当前主题的性质。在“进一步阅读”部分,我将添加几本其他书籍推荐,如果您想了解更多关于盐的其他功能。
在盐中定义所需状态的方式,或者换句话说,盐支持的语言是多种多样的。主要和默认语言是 YAML,支持 Jinja 模板。
创建新用户的 YAML 定义示例如下:
doge:
user.present:
- fullname: much doge
- shell: /bin/bash
- home: /home/doge
YAML 是盐的数据渲染语言;数据渲染将文件中的定义转换为盐消耗的 Python 数据结构。
以下是盐支持的其他数据渲染语言:
-
dson
-
hjson
-
json5
-
json
-
pydsl
-
pyobjects
-
py
-
stateconf
-
yamlex
Salt 有两种渲染类型。第一种是我们刚刚讨论的:数据渲染。第二种是文本渲染,这是Jinja
所属的类别。这些文本渲染不是返回 Python 数据结构,而是返回文本,稍后会为数据渲染进行翻译。
文本渲染对于设置变量或循环非常有用,如果我们需要重复几个具有不同值但相同结构的定义。例如,我们可以创建一个Jinja
模板,并使用相同的文件创建多个用户,而不是为每个用户创建一个 YAML 文件,如下所示:
{% for user in [dsala, eflores, elilu] %}
{{ user }}:
user.present:
- home: /home/{{ user }}
- shell: /bin/bash
上面的示例将创建三个用户,而不是通过文件或定义创建一个用户。这种方式更有效,因为我们不仅节省了时间和工作,而且不需要一遍又一遍地输入相同的定义,如果需要在数组中添加更多用户,也可以轻松实现,而不必为额外的用户创建全新的文件或定义。
除了Jinja
,Salt 文本渲染还支持其他模板引擎,例如以下:
-
Cheetah
-
Genshi
-
GPG
-
Jinja
-
Mako
-
NaCl
-
Pass
-
Py
-
Wempy
在接下来的章节中,我们将专注于Jinja
和 YAML。
SaltStack 平台
我们之前讨论了 IaC 的不同方法和途径。Salt 非常适合我们理解所有这些,因为 Salt 既使用推送和拉取方法,也同时使用声明式和命令式的方法。
让我们简要了解一下 Salt 的基本功能:
与任何其他客户端/服务器集群一样,Salt 由两种基本类型的节点组成:
-
Master:这个服务器或服务器组负责协调 minion,并在 minion 查询其所需状态时。主服务器也是发送要在 minion 上执行的命令的服务器。
-
Minion:由主服务器管理的服务器。
主服务器从两个 TCP 端口监听:4505
和4506
。这两个端口具有非常不同的角色和非常不同的连接类型。
4505
端口或发布者是所有 minion 监听主服务器消息的地方。4506
端口或请求服务器是 minion 通过安全方式直接请求特定文件或数据的地方。Salt 的网络传输利用 ZeroMQ 消息队列系统,该系统使用椭圆曲线加密,使用在主服务器和 minion 中生成的 4,096 位 RSA 密钥,我们将在本章后面看到。
Salt 是一种基于代理的工具,主服务器和 minion 之间的所有通信都是通过安装在 minion 上的代理实现的。Minion 负责与主服务器发起通信。
这很重要,因为在可能或可能没有互联网的分段网络中,您的主服务器和 minion 之间会有许多安全边界,并且每个 minion 可能没有为其定义的唯一地址。在主服务器发起通信的情况下,您的堆栈中的所有 minion 可能都必须具有公共 IP 地址,或者每次添加要管理的 minion 时都必须实现大量的网络配置和网络地址转换(NAT)。
由于 Salt 通信的方式,您可以将主服务器放在 DMZ 区域,具有公共可寻址的 IP 地址,并且所有 minion 连接到这些 IP。您的主服务器将始终少于 minion,因此需要实现的网络配置将大大减少。Salt 是一个高度可扩展的平台,一些堆栈包含数千个 minion;想象一下,必须配置网络以便三四个主服务器可以连接数千个 minion。
拥有公共 IP 的主服务器可能会让人感到害怕,但请记住,只要验证 RSA 密钥指纹,您就可以确保节点之间的所有通信都得到了保护,这要归功于 ZeroMQ 的加密机制。
Salt 功能
在对 Salt 的架构进行简要概述之后,现在是时候了解其不同的功能和能力了。
远程命令执行模块
记住我们说过 Salt 同时使用推送和拉取方法以及声明性和命令性方法。远程命令执行功能是我们如何以命令性方式利用 Salt 的推送方法。
如果你需要在多个随从或特定随从上远程运行命令,你将使用执行模块。让我们看一个简单的例子:
dsala@master1:~$ salt ‘*’ cmd.run ‘ls /home’
minion-1:
jdoe
dev1
master:
dsala
eflores
前面的命令向注册到主服务器的随从推送了ls
。让我们更仔细地看看这些命令:
-
salt
:这是 Salt 在远程随从上并行执行命令的最基本命令。 -
'*'
:表示我们将在所有由我们的主服务器管理的服务器上运行该命令;你也可以定义特定的目标。 -
cmd.run
:要调用的执行模块。 -
'ls /home'
:执行模块的参数。 -
输出:按随从名称排序,后跟该服务器的输出。
执行模块是 Salt 使用其远程执行框架的最基本形式。还记得我们说过 Salt 是用 Python 编写的吗?嗯,执行模块实际上是具有一组服务于共同目的的函数的 Python 模块。Salt 附带了几个预构建模块,你可以使用它们,甚至可以编写自己的模块并将它们添加到你的 SaltStack 平台上。所有执行模块都应该是与发行版无关的,但你可能会遇到一些在某些发行版中不可用的模块。以win_
开头的函数大多是特定于 Windows 的模块。
在我们之前的例子中,我们使用了cmd
模块的run
函数。我们使用模块中的函数的格式涉及定义要导入的模块,后跟一个句点和函数。每当调用一个函数时,Salt 都会按照以下方式进行:
-
从执行命令的主服务器发送到指定目标的发布者端口(
4505
)。 -
目标随从评估命令并决定是否要运行该命令。
-
运行命令的随从格式化输出并将其发送到主服务器的请求服务器端口(
4506
)。
了解执行模块是不够的,我们还需要知道我们可以使用什么。许多预定义模块是最常用的,值得一看它们的主要功能是什么。
sys 模块
这个模块相当于man
命令。使用sys
,我们可以查询、列出,甚至检查每个函数接受哪些参数。你会发现自己主要使用sys
模块的以下函数:
-
list_modules
:此函数将列出目标随从可用的模块。重要的是要注意,执行模块是在随从自身上执行的,而不是在执行命令的主服务器上。 -
list_functions
:使用list_functions
,你可以列出某个模块可用的函数。 -
argspec
:列出所需函数的可用参数和默认值。
现在我们可以运行sys
模块的前述函数之一,看一个真实的例子:
dsala@master1:~$ sudo salt 'minion1' sys.argspec pkg.install
minion1:
----------
pkg.install:
----------
args:
- name
- refresh
- fromrepo
- skip_verify
- debconf
- pkgs
- sources
- reinstall
- ignore_epoch
defaults:
- None
- False
- None
- False
- None
- None
- None
- False
- False
kwargs:
True
varargs:
None
pkg 模块
现在我们已经使用了pkg
函数作为sys
模块的示例,我想谈谈pkg
模块。这是 Salt 提供的另一个最常用的模块。该模块处理与包相关的所有任务,从安装和升级到删除包。由于 Salt 试图尽可能与发行版无关,pkg
模块实际上在底层调用一组不同的模块和函数,这些模块和函数特定于调用模块的发行版。例如,如果pkg.install
针对的是基于 Ubuntu 的系统,当随从收到消息时,实际上会调用aptpkg
模块。这就是为什么pkg
被称为虚拟模块。
pkg
调用的一些不同模块如下:
-
aptpkg
:对于使用apt-get
软件包管理的 Debian 发行版。 -
brew
:适用于使用 Homebrew 软件包管理的 macOS。 -
yumpkg
:使用yum
或dnf
作为软件包管理器的基于 Red Hat 的发行版。 -
zypper
:对于使用zypper
作为软件包管理器的基于 SUSE 的发行版。
以下是使用pkg
安装nginx
web 服务器的示例:
dsala@master1:~$ sudo salt 'minion1' pkg.install nginx
minion1:
----------
nginx:
----------
new:
1.15.10
old:
测试模块
最后,但同样重要的是,我想和你谈谈测试模块。测试模块将允许我们测试我们的 SaltStack 平台。例如,检查 minions 的健康状态、它们正在运行的 Salt 版本,甚至只是让它们发送一个回声,都是可能的。
可以使用sys.list_functions
函数找到测试模块的不同功能,但值得一提的是,您可能会经常使用一些最常见的功能:
-
ping:ping 函数测试 minions 的响应;这不是一个 ICMP ping 命令。
-
version:返回您的 minions 的 Salt 版本。
-
versions_information:返回所有 Salt 依赖项、内核版本、发行版版本和 Salt 版本的完整列表。
盐状态
现在我们已经了解了远程执行框架,我们可以开始探索 Salt 提供的其他系统。远程执行框架是所谓的状态系统的基础。状态系统是一种声明性和幂等的方式,利用 IaC 文件来配置 minion 的期望状态。状态系统利用状态模块,这些模块与执行模块非常相似,但不同之处在于 Salt 状态实际上会检查 minion 中是否已经存在所需的配置。例如,让我们看一下以下状态定义:
dsala@master:/srv/salt/httpd $ cat httpd.sls
httpd_package:
pkg.installed:
- name: httpd
上述状态将在运行时在目标服务器上安装httpd
(Apache)软件包,但仅当软件包不存在时。如果软件包不存在,状态模块将调用本地的pkg.install
执行函数,并在 minion(s)中安装软件包。
看一下我们从/srv/salt
目录中cat
文件的事实。这个目录是盐状态目录树的默认位置,状态定义放在这里。在这个目录中,您将创建包含公式的文件夹,这些公式是一组包含部署应用程序所需的所有必要配置的盐状态。例如,我们不仅可以安装httpd
,还可以配置虚拟主机并下载包含实际网站的 Git 存储库,这些网站将在 Apache web 服务器上运行。
目录树遵循一组规则,以便您调用状态模块并运行公式,但这将是第十四章“Getting Your Hands Salty”的主题,届时我们将深入探讨配置和实际用法。
盐的特征
我们已经学会了可以通过定义 minion 名称或通过*
在所有 minions 上运行执行模块。但是,当您的主服务器管理数百甚至数千个 minions 时,在整个堆栈上或在单个 minions 上运行 Salt 状态和执行模块并不理想。
在这里,Salt 引入了grains
接口,它允许我们通过特定特征识别 minions,甚至为共享相同目的或特征的 minions 设置自己的标签或角色类型,因此我们可以执行更有针对性的配置管理。
我们可以利用grains
接口,使用与在 Salt 中执行任何命令相同的语法:
dsala@master:~$ salt “minion1” grains.items
通过上述命令,我们列出了我们所针对的系统的所有不同的硬件和软件特征。在输出中,我们可以看到诸如操作系统系列、系统架构,甚至我们用来运行 VM 的 hypervisor 等信息。
这将帮助我们创建通过所谓的top
文件定位特定系统的状态定义,我们将在第十四章中讨论这一点。使用grains
并定位所有Debian
系列 VM 的 Salt 状态顶部文件定义示例如下:
base:
'os_family:Debian:
- match: grain
- httpd
如前所述,我们还可以在从属服务器中创建自定义的grains
来定义角色,并使用唯一值对标记我们的从属服务器。这对于将从属服务器分组到特定任务非常有用;例如,所有 QA 团队的 VM 可以使用键值对departement: qa
进行标记。另一种分组方式可能是按角色,例如appfoo: frontend
等。有许多使用谷物定位的方法,所有这些都取决于我们想要如何管理或推送和维护所需的状态。
盐柱
通过谷物,我们可以定位特定的从属服务器,但最终,我们定义了那些在顶层文件中的定位策略,这些文件是公式的一部分。公式通常存储在 Git 存储库中,有时甚至是公共的。这就是为什么我们不能,或者更确切地说,我们不应该在 Salt 状态中声明敏感信息。在我们之前的章节中,Dockerfile 也是如此,Kubernetes 通过Secrets API 对象解决了这个问题。Salt 有自己的秘密版本,称为Pillars。
与谷物不同,盐柱存储在主服务器中,而不是从属服务器。只有被盐柱定位的从属服务器才能访问盐柱中的信息。这使得它非常适合存储敏感信息。在存储敏感信息时,盐柱也可以在静止状态下加密,而且由于盐的渲染系统,盐柱将在盐柱编译期间解密。
盐柱通过仅在主服务器中存储敏感数据来减少敏感数据的暴露面:
通过 Salt 柱,我们完成了对 SaltStack 平台提供的基本组件的简要概述。我们将在第十四章中更深入地讨论它们,并通过实际示例进行操作,以便通过 Salt 开始管理系统。
摘要
在本章中,我们讨论了企业在维护基础设施时面临的不同问题。我们介绍了不同的技术,如 IaC 和集中式系统管理。我们介绍了 IaC 如何将更改推送或拉取到受控系统中,并了解了几个利用 IaC 的应用程序。
我们还讨论了盐是什么,以及它的不同组件如何帮助我们实现集中式的受控基础设施。
在下一章中,我们将学习如何设计一个 Salt 解决方案并安装软件。
问题
-
什么是系统管理?
-
系统管理背后的挑战是什么?
-
什么应用程序可以帮助我们进行系统管理?
-
什么是基础设施即代码?
-
我们可以用哪些不同类型的方法来管理我们的系统?
-
盐是什么?
-
盐有哪些不同的组件?
进一步阅读
-
Gartner:'每个预算都是 IT 预算'
-
Forrester:
www.forrester.com/report/Cloud+Investments+Will+Reconfigure+Future+IT+Budgets/-/E-RES83041#
-
声明式与命令式配置管理模型:
www.upguard.com/blog/articles/declarative-vs.-imperative-models-for-configuration-management
-
Salt 配置管理:
red45.wordpress.com/2011/05/29/salt-configuration-management/
-
使用 grains 进行目标定位:
docs.saltstack.com/en/latest/topics/targeting/grains.html
第十四章:变得更加熟悉 Salt
在经过Salt的基本概念后,我们最终将在本章中开始实践 Salt。我们将有机会在真实情景下工作,并为潜在客户设计和安装概念验证基础设施。我们将做一些如下的事情:
-
通过 Terraform 配置云基础设施
-
安装和配置 Salt 主服务器
-
安装和配置 minions
-
为 minions 创建状态和公式
-
通过 Salt 配置负载均衡器
执行这些任务后,您应该具备基本知识和实践经验,可以开始更深入地学习 Salt。
使用 Salt 进行实践
我们已经了解了 Salt 的不同组件和软件的功能,以及它如何帮助我们控制我们的基础设施。但我们还没有使用任何组件来实际维护任何系统,甚至安装 Salt。因此,让我们开始使用 Salt,并开始利用我们新获得的知识。
在开始之前,我们将设置一个情景,以便更清楚地了解本章中我们将要做的事情,它将与一个真实情景相关。
情景
您已被 Don High 先生聘用,为他的公司设计系统管理平台。他希望在 Azure 虚拟机(VMs)上运行他的 Web 服务器工作负载,采用基础设施即服务(IaaS)模型。
他的设置非常简单:他希望有两台虚拟机运行一个用Node.js
编写的网站,位于 nginx 负载均衡器前面,将流量路由到网站的虚拟机中。他的所有基础设施都必须通过配置管理解决方案进行管理,以便每次他们提供新的虚拟机时,应用程序都会加载,并且可能需要运行其网站所需的任何配置。
他还告诉你的另一件事是,公司的员工在 Azure 中没有部署任何资源,并且他们希望看到基础设施即代码(IaC)如何在云中部署,以便他们的开发人员将来能够使用它。
构建我们的初始基础设施
我们在上一章中提到了Terraform,并且我们希望利用我们的客户要求我们通过 IaC 软件部署他的基础设施的事实,所以这是使用这个伟大工具的绝佳机会。
在执行每一步之前,我们将简要解释每一步,但如果您想了解更多,我们将在进一步阅读部分建议更多关于 Terraform 的深入讨论的书籍。
设置 Terraform
我们假设您将从类 Unix 工作站执行以下步骤。安装 Terraform 非常简单。Terraform 只是一个可以从terraform.io
网站下载的二进制文件。
www.terraform.io/downloads.html
在我的情况下,我将使用 macOS 终端安装 Terraform:
下载后,您可以继续并在路径的一部分解压缩二进制文件:
通过运行terraform version
检查 Terraform 版本:
安装了 Terraform 后,我们需要安装 Azure CLI 以配置访问客户的 Azure 订阅。您可以在我们的安装 Kubernetes章节中找到安装 Azure CLI 和设置订阅的步骤。
安装了 Azure CLI 并设置了默认帐户后,我们可以配置 Terraform 以使用适当的凭据,以便它能够部署基础设施。
首先,我们将创建一个目录来存储我们的 Terraform 文件:
dsala@NixMachine: ~ $ mkdir terrafiles
接下来,我们将通过 Azure CLI 创建一个服务主体 ID,该 ID 将用于验证 Terraform 与我们的订阅。
将此命令的输出中的订阅 ID 保存到$SUB_ID
变量中:
dsala@NixMachine: ~ $ az account show --query "{subscriptionId:id}"
dsala@NixMachine: ~ $ SUB_ID=<subscription id>
现在,运行以下命令来创建服务主体:
dsala@NixMachine: ~ $ az ad sp create-for-rbac \
--role="Contributor" \
--scopes="/subscriptions/${SUB_ID}"
注意从上一个命令返回的appId
、password
和tenant
的值。
现在,在terrafiles
目录中,创建一个名为terraform.tfvars
的文件。
这个文件很特殊,因为当我们执行 Terraform 时,Terraform 会自动加载默认情况下存在的任何具有这个名称的文件。
这个文件应包含以下信息:
subscription_id = "azure-subscription-id"
tenant_id = "tenant-from-service-principal"
client_id = "appId-from-service-principal"
client_secret = "password-from-service-principal"
当你准备好文件后,创建另一个名为az_creds.tf
的文件,其中包含以下内容:
variable subscription_id {}
variable tenant_id {}
variable client_id {}
variable client_secret {}
provider "azurerm" {
subscription_id = "${var.subscription_id}"
tenant_id = "${var.tenant_id}"
client_id = "${var.client_id}"
client_secret = "${var.client_secret}"
}
这个文件将是我们的变量文件,并且它将把凭据变量加载到 Azure 资源管理器 Terraform 提供程序中。
创建 IaC
现在我们准备开始创建我们的 IaC 声明文件。Terraform 使用自己的语言称为Hashicorp 配置语言(HCL)。你可以在以下链接找到更多信息:www.terraform.io/docs/configuration/index.html
。
让我们开始定义我们的资源。创建一个名为main.tf
的文件。这将是我们的主模块文件。一个模块是一组共享共同目标或属于同一应用程序的资源。
main.tf
的名称是 Hashicorp 推荐的名称,Hashicorp 是 Terraform 开源项目的公司所有者,用于最小模块。
你可以在 Terraform 文档中了解更多关于模块的信息:www.terraform.io/docs/modules/index.html
。
我们的文件应包含接下来我们将声明的所有资源。
这是将包含我们 Azure 资源的资源组:
resource "azurerm_resource_group" "salt" {
name = "Salt"
location = "East US"
}
这是我们子网的虚拟网络:
resource "azurerm_virtual_network" "salt" {
name = "saltnet"
address_space = ["10.0.0.0/16"]
location = "${azurerm_resource_group.salt.location}"
resource_group_name = "${azurerm_resource_group.salt.name}"
}
请注意,我们通过以下语法从先前的资源中获取值:
"resource_type.local_name.value".
这是我们 VM 的地址空间的子网(s):
resource "azurerm_subnet" "salt" {
name = "saltsubnet"
resource_group_name = "${azurerm_resource_group.salt.name}"
virtual_network_name = "${azurerm_virtual_network.salt.name}"
address_prefix = "10.0.0.0/24"
}
在这里,我们只创建一个包含我们的主节点和 minions 的子网,但只要它们在 VNET 地址空间内,你可以随时创建单独的子网,以便主节点和 minions 进行网络隔离。
创建了虚拟网络和子网后,我们需要为虚拟机创建防火墙规则。Azure 中的防火墙称为网络安全组,我们将继续使用网络安全组提供程序来创建防火墙及其规则。
这是我们负载均衡器的网络安全组:
resource "azurerm_network_security_group" "saltlb" {
name = "lb-nsg"
location = "${azurerm_resource_group.salt.location}"
resource_group_name = "${azurerm_resource_group.salt.name}"
}
以下是用于访问负载均衡器 VM 的网络安全组规则。
https
的端口:
resource "azurerm_network_security_rule" "httpslb" {
name = "https"
priority = 100
direction = "inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "8443"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = "${azurerm_resource_group.salt.name}"
network_security_group_name = "${azurerm_network_security_group.saltlb.name}"
}
http
端口:
resource "azurerm_network_security_rule" "httplb" {
name = "http"
priority = 101
direction = "inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "8080"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = "${azurerm_resource_group.salt.name}"
network_security_group_name = "${azurerm_network_security_group.saltlb.name}"
}
access
的 SSH 端口:
resource "azurerm_network_security_rule" "sshlb" {
name = "sshlb"
priority = 103 direction = "inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*" destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = "${azurerm_resource_group.salt.name}"
network_security_group_name = "${azurerm_network_security_group.saltlb.name}"
}
主 VM 的第二个网络安全组如下:
resource "azurerm_network_security_group" "saltMaster" {
name = "masternsg"
location = "${azurerm_resource_group.salt.location}"
resource_group_name = "${azurerm_resource_group.salt.name}"
}
以下是主 VM 的网络安全组规则。
以下是 Salt 的publisher
端口:
resource "azurerm_network_security_rule" "publisher" {
name = "publisher"
priority = 100
direction = "inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "4505"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = "${azurerm_resource_group.salt.name}"
network_security_group_name = "${azurerm_network_security_group.saltMaster.name}"
}
以下是 Salt 的请求服务器端口:
resource "azurerm_network_security_rule" "requestsrv" {
name = "requestsrv"
priority = 101
direction = "inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "4506"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = "${azurerm_resource_group.salt.name}"
network_security_group_name = "${azurerm_network_security_group.saltMaster.name}"
}
主机的ssh
端口如下:
resource "azurerm_network_security_rule" "sshmaster" {
name = "ssh"
priority = 103
direction = "inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = "${azurerm_resource_group.salt.name}"
network_security_group_name = "${azurerm_network_security_group.saltMaster.name}"
}
minions 的网络安全组如下:
resource "azurerm_network_security_group" "saltMinions" {
name = "saltminions"
location = "${azurerm_resource_group.salt.location}"
resource_group_name = "${azurerm_resource_group.salt.name}"
}
这个最后的网络安全组很特殊,因为我们不会为它创建任何规则。Azure 提供的默认规则只允许 VM 与 Azure 资源通信,这正是我们这些 VM 所希望的。
我们 Nginx 负载均衡器 VM 的公共 IP 地址如下:
resource "azurerm_public_ip" "saltnginxpip" {
name = "lbpip"
location = "${azurerm_resource_group.salt.location}"
resource_group_name = "${azurerm_resource_group.salt.name}"
public_ip_address_allocation = "static"
}
我们负载均衡器的虚拟网络接口如下:
resource "azurerm_network_interface" "saltlb" {
name = "lbnic"
location = "${azurerm_resource_group.salt.location}"
resource_group_name = "${azurerm_resource_group.salt.name}"
network_security_group_id = "${azurerm_network_security_group.saltlb.id}"
ip_configuration {
name = "lbip"
subnet_id = "${azurerm_subnet.salt.id}"
private_ip_address_allocation = "dynamic"
public_ip_address_id = "${azurerm_public_ip.saltnginxpip.id}"
}
}
我们的 Web 服务器 VM 的虚拟网络接口如下:
resource "azurerm_network_interface" "saltminions" {
count = 2
name = "webnic${count.index}"
location = "${azurerm_resource_group.salt.location}"
resource_group_name = "${azurerm_resource_group.salt.name}"
network_security_group_id = "${azurerm_network_security_group.saltMinions.id}"
ip_configuration {
name = "web${count.index}"
subnet_id = "${azurerm_subnet.salt.id}"
private_ip_address_allocation = "dynamic"
}
}
以下是我们主 VM 的公共 IP 地址:
resource "azurerm_public_ip" "saltmasterpip" {
name = "masterpip"
location = "${azurerm_resource_group.salt.location}"
resource_group_name = "${azurerm_resource_group.salt.name}"
allocation_method = "Dynamic"
}
这个公共 IP 地址将用于我们 SSH 到主 VM;这就是为什么我们要动态分配它。
主 VM 的虚拟网络接口如下:
resource "azurerm_network_interface" "saltmaster" {
name = "masternic"
location = "${azurerm_resource_group.salt.location}"
resource_group_name = "${azurerm_resource_group.salt.name}"
network_security_group_id = "${azurerm_network_security_group.saltMaster.id}"
ip_configuration {
name = "masterip"
subnet_id = "${azurerm_subnet.salt.id}"
private_ip_address_allocation = "static"
private_ip_address = "10.0.0.10"
public_ip_address_id = "${azurerm_public_ip.saltmasterpip.id}"
}
}
以下是 Web 服务器 VMs:
resource "azurerm_virtual_machine" "saltminions" {
count = 2
name = "web-0${count.index}"
location = "${azurerm_resource_group.salt.location}"
resource_group_name = "${azurerm_resource_group.salt.name}"
network_interface_ids = ["${element(azurerm_network_interface.saltminions.*.id, count.index)}"]
vm_size = "Standard_B1s"
storage_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
storage_os_disk {
name = "webosdisk${count.index}"
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Standard_LRS"
}
os_profile {
computer_name = "web-0${count.index}"
admin_username = "dsala"
}
os_profile_linux_config {
disable_password_authentication = true
ssh_keys = {
path = "/home/dsala/.ssh/authorized_keys"
key_data = "${file("~/.ssh/id_rsa.pub")}"
}
}
}
用你自己的信息替换os_profile.admin_username
和os_profile_linux_config.key_data
。
主 VM 如下:
resource "azurerm_virtual_machine" "saltmaster" {
name = "salt"
location = "${azurerm_resource_group.salt.location}"
resource_group_name = "${azurerm_resource_group.salt.name}"
network_interface_ids = ["${azurerm_network_interface.saltmaster.id}"]
vm_size = "Standard_B1ms"
storage_image_reference {
publisher = "OpenLogic"
offer = "CentOS"
sku = "7.5"
version = "latest"
}
storage_os_disk {
name = "saltos"
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Standard_LRS"
}
os_profile {
computer_name = "salt"
admin_username = "dsala"
}
os_profile_linux_config {
disable_password_authentication = true
ssh_keys = {
path = "/home/dsala/.ssh/authorized_keys"
key_data = "${file("~/.ssh/id_rsa.pub")}"
}
}
}
以下是 Nginx 负载均衡器 VM:
resource "azurerm_virtual_machine" "saltlb" {
name = "lb-vm"
location = "${azurerm_resource_group.salt.location}"
resource_group_name = "${azurerm_resource_group.salt.name}"
network_interface_ids = ["${azurerm_network_interface.saltlb.id}"]
vm_size = "Standard_B1ms"
storage_image_reference {
publisher = "OpenLogic"
offer = "CentOS"
sku = "7.5"
version = "latest"
}
storage_os_disk {
name = "lbos"
caching = "ReadWrite"
create_option = "FromImage"
managed_disk_type = "Standard_LRS"
}
os_profile {
computer_name = "lb-vm"
admin_username = "dsala"
}
os_profile_linux_config {
disable_password_authentication = true
ssh_keys = {
path = "/home/dsala/.ssh/authorized_keys"
key_data = "${file("~/.ssh/id_rsa.pub")}"
}
}
}
保存了所有先前创建的资源的文件后,运行terraform init
命令;这将使用 Terraform 文件初始化当前目录并下载 Azure Resource Manager 插件:
如果您想了解更多关于init
命令的信息,您可以访问www.terraform.io/docs/commands/init.html
。
运行init
命令后,我们将继续运行terraform plan
命令,该命令将计算实现我们在tf
文件中定义的所需状态的所有必要更改。
在运行terraform apply
命令之前,这不会对现有基础设施进行任何更改:
有关plan
命令的更多信息,请访问www.terraform.io/docs/commands/plan.html
。
完成plan
命令后,您可以继续运行terraform apply
,然后会提示您确认应用更改:
完成后,您应该能够看到以下消息:
Apply complete! Resources: 18 added, 0 changed, 0 destroyed.
Installing, Configuring and Managing Salt
安装 Salt 有两种方式:您可以使用引导脚本安装主服务器和 minions,也可以通过 Salt 存储库手动安装和配置它们。
我们将覆盖两种方式,以便熟悉安装过程。
使用软件包管理器安装 Salt
在我们当前的基础设施中,我们有一个主服务器和三个 minions。我们的主服务器和一个 minion 正在运行 CentOS 7.5,其余的 VM 都在 Ubuntu 16.04 上。在这两个发行版上,流程会有所不同,但在两者上有一些相同的步骤。
安装 CentOS yum
以前,Salt 只能通过 EPEL 存储库获得。但现在 SaltStack 有自己的存储库,我们可以从那里导入并执行安装。
首先,在主 VM 中安装 SSH,然后运行以下命令导入 SaltStack 存储库:
[dsala@salt ~]$ sudo yum install \
https://repo.saltstack.com/yum/redhat/salt-repo-latest.el7.noarch.rpm
可选的,您可以运行yum clean expire-cache
,但由于这是一个新的虚拟机,这是不必要的。
完成后,我们将继续安装salt-master
包:
[dsala@salt ~]$ sudo yum install salt-master -y
继续启用systemd
salt-master 服务单元:
[dsala@salt ~]$ sudo systemctl enable salt-master --now
检查服务是否正在运行:
一旦服务正常运行,请通过运行以下命令检查 VM 的私有 IP 是否与我们在 Terraform 定义中配置的 IP 一致:
[dsala@salt ~]$ ifconfig eth0 | grep inet | head -1 | awk '{print $2}'
确认了 IP 地址后,打开另一个终端并 SSH 到负载均衡器 minion。重复在主 VM 中添加存储库的过程。
添加存储库后,运行以下命令安装salt-minion
包:
[dsala@lb-vm ~]$ sudo yum install salt-minion -y
通过运行以下命令启用和启动systemd
服务单元:
[dsala@lb-vm ~]$ sudo systemctl enable salt-minion --now
在我们对其进行任何更改之前,让我们检查服务是否成功启动:
我们可以看到服务上出现错误,说主服务器已更改公钥,我们无法连接到 Salt 主服务器。现在我们需要配置 minion 与主服务器通信。但首先,让我们安装剩下的两个 Ubuntu minions,因为在两个发行版上注册 minions 的过程是相同的。
Ubuntu apt-getting Salt
这唯一复杂的部分是,由于我们的 Web 服务器没有分配给它们的公共 IP 地址,您必须从主 VM 或负载均衡器 VM 对它们进行 SSH。为此,您可以从这两个 VM 中的任何一个设置 SSH 密钥认证到 minions。如果您正在阅读本书,您将熟悉如何执行此任务。
登录到 Web 服务器 VM 后,在两个 VM 中执行以下任务。
导入 Salt 存储库的gpg
密钥:
运行以下命令创建存储库:
dsala@web-00:~$ echo "deb http://repo.saltstack.com/apt/ubuntu/16.04/amd64/latest xenial main" \
| sudo tee /etc/apt/sources.list.d/saltstack.list
添加了存储库后,运行apt update
,您应该能够看到存储库已列出:
继续安装salt-minion
软件包:
dsala@web-00:~$ sudo apt install salt-minion -y
通过运行以下命令启用并检查salt-minion
服务的状态:
dsala@web-00:~$ sudo systemctl enable salt-minion --now && systemctl status salt-minion
您应该看到与 CentOS LB 虚拟机中看到的相同的消息。
通过引导脚本安装 Salt
通过引导脚本安装 Salt 的第二种方法。此脚本会自动检测我们的发行版并下载定义的软件包。该脚本还为我们提供了-A
标志,该标志将主机的地址添加到我们的 minions 中。
要获取脚本,您可以使用wget
或curl
;官方 SaltStack 使用curl
:
user@master:~$ curl -L https://bootstrap.saltstack.com -o install_salt.sh
此脚本适用于主机和 minions;运行脚本时使用的标志不同。
要安装主机组件,请使用-M
标志运行脚本,用于主机和-P
允许安装任何 Python pip
软件包。我们还可以使用-A
指定主机地址,并告诉脚本不要使用-N
标志在主机中安装 minion 服务:
user@master:~$ sudo sh install_salt.sh -P -M
要安装 minion,只需运行此命令:
user@master:~$ sudo sh install_salt.sh -P -A <salt master IP>
主机和 minion 握手
在安装的这个阶段,我们将允许我们的 minions 与主机通信,验证它们的指纹,并设置配置文件。
首先,我们将 SSH 到主机 VM 并编辑主机的配置文件,告诉 salt-master 守护程序要绑定到哪个 IP。
编辑/etc/salt/master
文件,查找interface:
行,并添加主机的 IP 地址:
修改文件后,运行daemon-reload
和restart
命令,以便服务确认更改:
[dsala@salt ~]$ sudo systemctl daemon-reload && sudo systemctl restart salt-master
您可以通过运行ss
命令来验证 Salt 主机是否在正确的 IP 地址上监听:
现在我们的主机正在监听我们需要的 IP 地址,是时候配置我们的 minions 了。
让我们开始修改 minion 的配置文件。请记住,这些步骤需要在所有 minions 上执行,而不管它们的发行版如何。
查找/etc/salt/minion
文件,并通过在master:
下添加主机的 IP 地址来编辑它。我们将找到一个已配置的值:master: salt
;这是因为 Salt 默认情况下通过对主机名salt
进行 DNS 查询来查找主机,但是因为我们打算在将来拥有多个主机,所以我们将使用我们主机 VM 的静态 IP 地址设置此文件:
在我们的 minions 可以交换密钥之前,我们需要将主机的指纹添加到我们 minions 的配置文件中。
SSH 回到主机并运行以下命令以获取主机的公共指纹:
复制master.pub
的值,并返回编辑 minion 的配置文件。在 minion 的配置文件中,使用在前一步中获得的主机公钥编辑master_finger: ' '
行:
完成最后一个任务后,重新加载并重新启动 minion 守护程序:
[dsala@web-00 ~]$ sudo systemctl daemon-reload && sudo systemctl restart salt-master
在退出每个 minion 之前,运行以下命令并注意 minion 的指纹:
[dsala@web-00 ~]$ sudo salt-call --local key.finger
一旦您注意到所有 minions 的指纹,请继续登录到主机。
在主机上,我们将比较主机看到的指纹与我们在每个 minion 本地看到的指纹。通过这种方式,我们将确定我们将接受的 minions 确实是我们的 minions。
要做到这一点,在主机上运行以下命令:salt-key -F
。这将打印所有密钥,因此您不必逐个打印每个密钥:
确保密钥相同,然后我们将继续接受密钥。
在salt-key -F
命令下,我们看到有未接受的密钥需要验证;我们将运行salt-key -A
来接受所有待处理的密钥,然后可以运行salt-key -L
来验证这些密钥是否被接受:
现在我们的 minion 已经经过身份验证,我们可以继续从 master 发出命令。
为了测试我们的 minion,我们将从测试模块调用ping
函数:
所有 minion 应该回应True
,表示 Salt minion 守护程序正在响应,我们准备开始管理我们的基础设施。
使用 Salt 工作
我们的 SaltStack 已经运行起来了,我们准备开始为我们的虚拟机创建公式和定制配置。
创建 WebServer 公式
我们现在将创建必要的状态文件来创建安装和配置我们的 web 服务器的公式。
在开始之前,我们需要首先创建我们的状态树,其中将包含所有状态文件:
[dsala@salt ~]$ sudo mkdir /srv/salt
在这个目录中,我们将创建一个名为top.sls
的文件。这个文件告诉 Salt 要应用哪些状态到哪些 minion 上。和 Salt 中的每个定义一样,top.sls
是一个基于 YAML 的文件,其中包含要定位的 minion 和应用到这些 minion 的状态文件。
在/srv/salt
目录中创建一个名为top.sls
的文件,内容如下:
base:
'web*':
- webserver.nodejs
base:
表示我们正在工作的环境;由于这是一个简单的环境,我们只需要基本环境;如果要处理多个环境,可以参考我们在进一步阅读部分建议的一本书。
接下来,我们有web*
条目;这个条目告诉 Salt 要应用状态的 minion IDs。正如你所看到的,你可以使用 globbing 来定位 minion IDs。
最后,- webserver.nodejs
是我们指示要应用的状态;webserver
表示nodejs.sls
文件所在的文件夹。由于 YAML 是由 Python 解释器读取的,我们需要用句点(.)而不是斜杠(/)来定义路径。最后一个词将是要加载的.sls
文件的名称。
因为我们定义了Node.js
文件在一个名为webserver
的目录中,这个目录将存储我们所有的 web 服务器状态文件,我们需要创建这样一个目录:
[dsala@salt ~]$ sudo mkdir /srv/salt/webserver
现在我们有了存储状态定义的目录,让我们创建我们的第一个状态定义,安装node.js
包和npm
。在/srv/salt/webserver/
目录中创建一个名为nodejs.sls
的文件,内容如下:
nodejs:
pkg.installed
npm:
pkg.installed
nodejs
字段是要安装的包,后面是要调用的pkg.installed
函数。
创建了state
文件后,将state
文件应用到 web 服务器 minion 上:
[dsala@salt ~]$ sudo salt 'web*' state.apply
过一会儿,你将收到应用更改和持续时间的输出:
以下示例的输出已经被截断以提高可读性。
安装了 Node.JS 后,我们现在需要为 Node.JS 网站创建用户。
我们将创建另一个状态文件来定义用户配置。
在/srv/salt/webserver/
目录下创建另一个名为webuser.sls
的文件,内容如下声明:
webuser:
user.present:
- name: webuser
- uid: 4000
- home: /home/webuser
在执行状态之前,修改top.sls
文件以反映新添加的状态文件:
base:
'web*':
- webserver.nodejs
- webserver.webuser
再次执行salt '*' state.apply
命令,你应该会收到用户创建的输出:
现在我们有了将运行网站的用户,是时候将网站文件复制到我们的网站服务器上了。为此,我们将创建另一个状态文件,使用 Git 下载网站文件并加载到 VM 中。
修改你的top.sls
文件,并在同一个 web 服务器目录下添加另一个名为gitfetch
的状态:
base:
'web*':
- webserver.nodejs
- webserver.webuser
- webserver.gitfetch
现在,继续使用git.latest
函数创建gitfetch.sls
文件,以从 Git 存储库下载代码并在每次下载存储库时安装Node.js
依赖项:
node-app:
git.latest:
- name: https://github.com/dsalamancaMS/SaltChap.git
- target: /home/webuser/app
- user: webuser
dep-install:
cmd.wait:
- cwd: /home/webuser/app
- runas: webuser
- name: npm install
- watch:
- git: node-app
继续运行state.apply
函数,以在两台 Web 服务器上下载应用程序。运行命令后,您应该能够看到类似于以下内容的输出:
有了我们的 Web 服务器中的代码,我们几乎完成了我们的 Ubuntu minions 的配置。
现在我们需要将我们的 Node.JS 应用程序作为守护程序运行。
为此,我们将使用 Supervisor 开源项目:github.com/Supervisor/supervisor
。
现在,让我们配置 Salt,使Supervisor
监视我们的 Node.JS Web 应用程序。编辑top.sls
文件,添加以下行,就像我们以前做过的那样:
- webserver.suppkg
在创建supervisor
状态文件之前,我们首先需要创建要推送到我们 minions 的supervisor
配置文件。在 Web 服务器目录中创建一个名为supervisor.conf
的文件,内容如下:
[program:node-app]
command=nodejs .
directory=/home/webuser/app
user=webuser
现在创建suppkg.sls
状态文件,负责管理之前的配置文件,在 Web 服务器文件夹下:
supervisor:
pkg.installed:
- only_upgrade: False
service.running:
- watch:
- file: /etc/supervisor/conf.d/node-app.conf
/etc/supervisor/conf.d/node-app.conf:
file.managed:
- source: salt://webserver/supervisor.conf
创建文件后,继续运行salt 'web*' state.apply
命令以应用最新状态。
应用了最后一个状态后,我们的 Web 应用程序应该已经启动运行。您可以尝试通过curl
命令访问它:
现在我们的 Web 服务器已经准备好了,我们将对它们进行标记。还记得上一章我们谈到的 grains 吗。这就是我们接下来要做的事情。
让我们继续为我们的web-00
和web-01
服务器打上适当的角色标签。
要做到这一点,为每台服务器运行以下命令:
您可以通过运行以下grep
来检查角色是否成功应用:
创建负载均衡公式
现在我们的两台 Web 服务器已经正确设置,我们可以配置我们的最后一个 minion。这个 minion 将运行 Nginx,以便在负载均衡器后面平衡和代理请求到我们的 Web 服务器。
让我们创建一个目录,我们将在其中存储我们的负载均衡器的所有状态:
[dsala@salt ~]$ sudo mkdir /srv/salt/nginxlb
创建目录后,让我们继续最后一次编辑我们的top.sls
文件,以包括负载均衡器
状态文件。top.sls
文件应该如下所示:
在创建我们的负载均衡器
状态文件之前,我们将创建要推送到我们负载均衡器
VM 的 Nginx 配置文件。创建一个名为nginx.conf
的文件,内容如下:
events { }
http {
upstream webapp {
server web-00:8080;
server web-01:8080;
}
server {
listen 8080;
location / {
proxy_pass http://webapp;
}
}
}
现在,让我们继续创建我们的最终状态文件。在/srv/salt/
的nginxlb
目录下创建一个名为lb.sls
的文件,内容如下:
epel-release:
pkg.installed
nginx:
pkg.installed:
- only_upgrade: False
service.running:
- watch:
- file: /etc/nginx/nginx.conf
/etc/nginx/nginx.conf:
file.managed:
- source: salt://nginxlb/nginx.conf
应用最终更改,您可以运行state.apply
命令。
完成后,您可以继续测试负载均衡器,运行 cURL 到其公共 IP 地址:
通过这个最终配置,我们已经完成了对 Don High 先生的概念验证。一个非常重要的事实要注意的是,这个例子还远远没有准备好投入生产;这只是一个例子,展示了 Salt Stack 的基本功能和可能性。
总结
在本章中,我们最终通过 IaC 部署了 Salt,与 Salt 进行了互动。我们使用 Terraform 设置了我们的初始环境,并且要开始使用 Terraform,我们只需从terraform.io
下载二进制文件。可以通过terraform version
命令检查 Terraform 的版本。安装了 Terraform 后,我们获取了连接到我们的 Azure 订阅的正确详细信息,使用 AZ CLI。
一旦 Terraform 能够连接到 Azure,我们就开始创建 IaC 声明文件,其中包含了在 Azure 中正确部署我们想要的资源的必要信息,以我们想要的方式。
通过 Terraform 部署完成后,我们开始安装 Salt。这可以通过操作系统的软件包管理器(yum
和apt
)或引导脚本的两种不同方式来完成。
在通过软件包管理器安装时,我们需要添加 Salt 存储库,因为它在基本存储库中不可用;我们通过从saltstack
网站下载rpm
来完成这一点。
为了安装 master,我们运行了sudo yum install salt-master
,为了安装 minions,我们运行了sudo yum install salt-minion -y
。对于 Ubuntu,过程类似,只是使用了apt
软件包管理器。
在 Salt 完成安装后,我们启用了systemctl
单元。一旦 Salt 运行起来,我们需要允许 minions 与 master 通信;这是通过 SSH 指纹完成的。
在这一点上,Salt 正在运行,minions 正在与 master 通信,所以我们开始创建 web 服务器公式,运行必要的定义来部署应用程序。
在下一章中,本书的最后一章,我们将介绍设计解决方案时的一些最佳实践。
第十五章:设计最佳实践
总结这本书时,我们的最后一章将讨论你必须遵循的不同最佳实践,以设计一个具有弹性和防故障的解决方案。尽管这是本书的最后一章,但它将作为一个起点,帮助你考虑在迁移到云端时需要考虑哪些事项。
我们将涵盖以下主题的基础知识:
-
转移到云端
-
容器设计
-
持续集成流水线
-
持续部署流水线
-
自动化测试
我们将在本章中涵盖的主题和实践远非详尽,我们将进行一个宏观概述。有了这些基础知识,你可以开始加强你在每个领域的知识,为你的客户做出最终的设计决策。
根据场合设计
在之前的章节中,我们学到了针对非常具体的解决方案所需的一切。在这里,我们将讨论一般性的内容,你需要遵循或至少尝试遵循的基本规则或建议,以便你创建的每个设计。但不要被我接下来要说的所困惑;最佳实践本身并不存在。每个解决方案都将有其自己的特性、目标和独特的特点。始终努力满足你所处的情况和客户的业务需求。
然而,许多解决方案将不得不遵守某些行业标准,因为它们可能处理敏感信息。在这些类型的场景中,我们已经有了一套非常明确定义的规则和政策,我们的设计必须满足这些规则。这打破了我们所有设计都是不同的说法,但再次强调,这些是非常特定的行业的非常特定的场景。在处理敏感数据时,我们需要遵守的一些标准包括:
-
《健康保险可携带性和责任法案》(HIPAA)
-
《支付卡行业数据安全标准》(PCI-DSS)
-
《通用数据保护条例》(GDPR)
这些标准是固定的,无论是在本地还是国际上,并由各自的管理机构监管。但并非所有的设计模式或满足特定解决方案要求的方式都像这些那样清晰明了。
作为解决方案架构师,你将发现自己处于许多场景中,这将帮助你扩展你的作品集并将其应用于不同的解决方案。你创建的每个设计只有其最薄弱的环节才能够强大。在设计时,始终尝试看看你如何能够打破你的设计:
-
它在哪些地方存在故障点?
-
它在哪些地方存在瓶颈?
-
我的服务器能够承受负荷吗?
这些是你需要问自己的一些问题的几个例子。我们需要塑造我们的思维方式,并更经常地问自己“为什么?”为什么我们要以这种方式或那种方式做某事?质疑我们所做的每一个决定是至关重要的。
改变我们的思维方式是我们可以做的最好的事情,因为现在的技术发展速度比以往任何时候都要快。技术可能会随着时间的推移而发生变化,而我们今天实施的东西明天可能完全无法使用,但我们的思维方式将使我们能够适应并从所有必要的角度进行分析,以便我们取得成功。
每种情况和环境都是不同的,但在撰写本文时,我们可以说你将会处理两种主要类型的环境:
-
本地/裸金属环境
-
云环境
在本章中,我们将讨论你在这些环境中工作时需要处理的基本考虑因素。
本地环境
Linux 是适应性强的;它几乎可以在任何地方运行。如果未来几年我在割草机上发现 Linux 内核,我也不会感到惊讶。在 IT 变得越来越重要的当今世界,随着物联网的兴起,Linux 的存在前所未有地增加。因此,作为 Linux 架构师,我们需要准备几乎可以应对任何情况的设计。
在本地环境中,我们很可能面临两种情况:
-
裸金属服务器
-
虚拟机(VMs)
两者将非常不同,因为我们将有各种选项来使我们的解决方案更具弹性。
裸金属服务器
裸金属服务器非常适合需要大量资源运行的工作负载。小型工作负载不适合放在单个服务器上;例如,一个不会提供大量用户请求的小型网络应用在 64 核 1TB 内存的物理服务器上没有位置。这是对资源的浪费和糟糕的经济决策。大部分时间,这台服务器的 90%将是完全空闲的,浪费了宝贵的资源,可以用于其他用途。这些类型的应用应该放入虚拟机或完全容器化。
在将基础架构移至裸金属上或在裸金属上创建基础架构之前,我们应该了解所构建基础架构的应用程序的资源需求。
需要大量资源进行数据处理和高性能计算的系统将充分利用可用资源。以下解决方案是裸金属服务器上运行的示例:
-
Type 1/ Type 2 虚拟化监控器(基于内核的虚拟机(KVM),Linux 容器(LXC),XEN)
-
Linux 用于 SAP HANA
-
Apache Hadoop
-
Linux 用于 Oracle DB
-
大规模的 MongoDB 部署用于内存缓存
-
高性能计算(HPC)
内部应用程序规定其内存需求超过数百 GB 或数百 CPU 核心的所有应用程序都更适合在裸金属服务器上运行,因为 RAM/CPU 不会被用于不属于您为其设计服务器的工作负载的任何其他开销过程。
虚拟机
虚拟化监控器在裸金属服务器上也更好;因为它们将在多个托管的虚拟机之间共享资源,所以需要大量资源。需要注意的一点是,监控器的一些资源将被监控器本身消耗,这会在硬件中断和其他操作上产生资源开销。
有时,在构建物理服务器时,我们会非常关注应用程序所需的 CPU 核心。对于虚拟化监控器,CPU 时间优先分配给虚拟机,或者按照可用核心的先到先服务的原则分配给虚拟机;根据配置方式,CPU 资源在运行的虚拟机之间共享。相反,RAM 内存不会在虚拟机之间共享,我们需要在实施资源平衡时小心谨慎。部署具有必要 CPU 核心但具有足够 RAM 的服务器,可以满足我们可能面临的任何争用期的需求是需要考虑的。在单个主机上运行数百个虚拟机时,我们可能会很快耗尽内存并开始交换,这是我们要避免的情况。
在资源配置方面,我们还需要考虑的是,如果我们运行一组虚拟化监控器,可能会出现集群节点需要进行维护或由于意外故障而宕机的情况。正是因为这样的情况,我们应该始终留出一些资源,以便能够处理由于上述原因可能发生故障转移的虚拟机的额外意外工作负载。
在处理虚拟化程序时,你必须小心,因为你不会只在一个物理主机上运行单个工作负载。虚拟机的数量和虚拟机本身将始终变化,除非你配置了某种亲和规则。诸如你的网络接口卡支持多少网络带宽之类的事情至关重要。根据主机虚拟化程序的资源量,数十甚至数百个虚拟机将共享相同的网络硬件来执行它们的 I/O。例如,这就是决定是否需要 10GbE 网络卡而不是 1GbE 网络卡的地方。
在选择物理主机的网络接口时,还有一件事需要考虑,那就是你将使用的存储类型;例如,如果你正在考虑使用网络文件系统(NFS)解决方案或 iSCSI 解决方案,你必须记住,很多时候它们将共享与常规网络流量相同的接口。如果你知道你正在设计的基础架构将有非常拥挤的网络并且需要良好的存储性能,最好选择另一种方法,比如选择一个拥有专门用于存储 I/O 的独立硬件的光纤通道存储区域网络。
网络分割对于虚拟化环境、管理流量、应用程序网络流量和存储网络流量至关重要,这些流量应始终进行分割。你可以通过多种方式实现这一点,比如为每个目的分配专用的网络接口卡,或者通过 VLAN 标记。每个虚拟化程序都有自己的一套工具来实现分割,但其背后的思想是相同的。
云环境
与云环境合作为设计 IT 解决方案提供了大量选择。无论云服务提供商如何,你都可以从这些服务中进行选择:
-
基础设施即服务(IaaS)
-
平台即服务(PaaS)
-
软件即服务(SaaS)
你的选择将取决于你的客户在云架构模型方面的成熟度。但在我们甚至能谈论云环境的设计模式或最佳实践之前,我们需要讨论如何将你的本地环境迁移到云端,或者你如何开始将云作为客户基础架构的一部分。
通往云的旅程
这些迁移策略源自 Gartner 的研究。Gartner 还提出了第五种策略,称为用 SaaS 替代。
本节讨论了以下研究论文:
通过回答五个关键问题制定有效的云计算策略,Gartner,David W Cearley,2015 年 11 月,更新于 2017 年 6 月 23 日。
当迁移到云端时,我们不必把云看作目的地,而是看作一个旅程。尽管听起来有点俗套,但确实如此。每个客户通往云的道路都会不同;有些道路会很容易,而其他一些则会非常艰难。这将取决于是什么促使客户做出迁移决定,以及他们决定如何迁移他们的基础架构。一些客户可能决定不仅将基础架构迁移到 IaaS 模型,还要利用这次迁移,将一些工作负载现代化为 PaaS 甚至无服务器模型。无论选择哪种道路,每条道路都需要不同程度的准备工作。典型的过渡可能如下所示:
每一步都需要对应用程序或基础架构进行更大程度的变更。
我们可以将上述步骤视为一个更大旅程的一部分,该旅程始于对要迁移的资产的评估。
让我们更详细地探讨迁移的每一步。
评估
在这一步中,我们将评估我们想要迁移的工作负载。在确定迁移候选项后,我们应该始终对我们的虚拟机或物理服务器进行清点,并计算维护基础设施的总拥有成本(TCO)。硬件成本、支持维护合同、电费,甚至空间租金等都会在这里起作用。这将帮助我们了解在迁移到云上时我们将节省多少成本。这些数据对于说服管理层和任何可能对将基础设施迁移到云服务提供商的成本优势产生疑虑的 C 级决策者至关重要。
开始迁移的理想情况是寻找那些不需要整个基础架构迁移就可以投入生产的较小的应用程序。具有少量依赖关系的应用程序是开始评估的完美选择。需要考虑的依赖关系包括需要一起迁移的服务器、应用程序的网络需求(如端口和 IP 操作范围)。以下问题将帮助我们为成功迁移做好准备:
-
我使用的 Linux 发行版是否得到我要迁移到的云服务提供商的认可?
-
我是否正在运行云服务提供商支持的内核版本?
-
我是否需要安装任何额外的内核模块?
-
我的云服务提供商是否需要在我的操作系统上运行任何类型的代理?
有了这些问题的答案,我们可以开始执行实际的迁移。
迁移
在将基础架构迁移到云上时,有四种基本的方法:
-
提取和迁移
-
重构
-
重新架构
-
重建
这些方法中的每一种都将利用云的不同服务和不同功能。选择使用哪种方法将取决于许多因素,例如您需要多快迁移、您愿意为迁移付出多少努力,以及您是否希望在迁移过程中利用迁移并使您的工作负载现代化。
提取和迁移
这种方法实际上是重新托管,因为您将把您的本地物理服务器或虚拟机迁移到您云服务提供商的虚拟机中。这种方法是所有方法中最简单和最快的,因为您将按照本地环境将您的环境和应用程序迁移过去。对于这种方法,不需要进行代码更改或重新架构您的应用程序。在这里,您只需要利用您选择的云服务提供商的 IaaS 优势。
如果需要按需增加存储或计算资源的灵活性,以及无需硬件维护和管理,都是这种模式中可以利用的优势。
重构
通过重构,您的应用程序需要最少或不需要任何代码更改。通过这种方法,我们可以利用 IaaS 和 PaaS 功能的混合。将三层 Web 应用程序迁移到托管中间件和托管数据库是这种迁移模型的完美示例。
使用托管数据库或托管中间件,我们无需担心诸如操作系统管理、数据库引擎安装和管理、框架更新、安全补丁,甚至为负载平衡配置额外实例等问题,因为这一切都已经为我们处理好了。我们只需要上传我们的代码并选择我们需要运行的框架。我们仍然可以运行单片应用程序,只需要进行很少的代码更改;这种方法的主要目的是通过摆脱管理和配置等事务来进行迁移,从而增加我们工作负载的灵活性。
重新架构
重新架构在迁移时确实涉及对我们的应用程序进行重大更改,但这个阶段是我们现代化业务的阶段。
我们可以通过利用容器和 Kubernetes 等技术,将一个庞大的应用程序拆分成微服务。我们将使我们的应用程序更具可移植性、可扩展性、灵活性,并准备通过 DevOps 等方法交付。有了微服务、容器和 DevOps 带来的自动化,你不仅可以更快地将应用程序交付到生产环境,还可以更有效地利用应用程序运行的计算资源。
重新架构可能并不容易,也不是将工作负载迁移到云的最快方式,但从长远来看,它将为您带来实质性的优势和成本节约。
重建
重新架构需要进行重大的代码更改,但这种迁移模型的最终目标是充分利用迁移到云的机会,并创建所谓的云原生应用。
云原生应用是利用 PaaS 和 SaaS 等云服务的应用程序,这些应用程序旨在在云上运行。其中一些甚至可以完全在无服务器计算上运行。无服务器计算是直接在云服务上运行代码,或者使用云提供商已经提供的 API 或服务。将几个相互消耗并共同努力实现共同目标或结果的服务组合在一起,这就是我们所说的云原生应用。
迁移到云的整个理念是为了节省:在经济上节省,在维护上节省,在通过迁移到更具弹性和韧性的平台上节省恢复时间。但我们并不总是能自动地充分利用所有这些好处。迁移后,我们仍然需要做一些工作,以使我们的新云工作负载完全优化。
优化
也许如果您通过搬迁和转移来迁移您的基础设施,那么这个过程可能会很容易,而且在那个虚拟机上运行的任何工作负载可能已经在生产环境中,几乎没有任何变化。问题在于,您的虚拟机仍然与在本地环境中一样大。您仍然让虚拟机只使用其实际总计算资源的一小部分。在云中,这是在浪费金钱,因为您支付虚拟机运行的时间,但您支付这些时间的价格是基于该虚拟机的总资源量,无论您是否使用了其中的 100%。
这个阶段是我们实际开始进行适当的大小调整和优化我们的基础设施,以实际使用我们真正需要的资源,以充分利用云的弹性。所有云服务提供商都有工具和服务,您可以使用这些工具来监视虚拟机和其他服务的资源消耗。有了这些工具,我们可以轻松地识别和解决我们的大小需求,以一种成本有效的方式。
云的弹性不仅允许我们根据需求调整资源,而且无需等待 IT 运维团队在我们的虚拟化主机或专用物理服务器资源不足时分配或购买新硬件。
我们还可以根据我们设定的资源阈值按需为我们正在使用的服务提供额外的虚拟机或实例。对这些资源的请求会自动地负载均衡到我们的额外实例,这样我们只需要在资源争用期间支付额外的资源。
优化并不仅仅是为了获得更好的价格而减少虚拟机的大小。我们可以优化的其他领域包括管理和上市时间。采用 PaaS 和 SaaS 等方法可以帮助我们实现这一点。
一旦我们的应用程序在云上的虚拟机上运行,我们可以轻松地开始过渡到这些更受管理的服务。受管理的服务帮助我们忘记操作系统的维护或中间件配置,我们的开发人员可以花更多时间实际开发和部署应用程序,而不是与运维团队争论库需要更新以使最新版本的生产应用程序运行,这最终使我们的上市时间更快,管理或操作系统支持合同的花费更少。
更快的上市时间,更少的管理,以及运维和开发之间更少的冲突,这就是 DevOps 所关注的。我们在迁移阶段的几个阶段中提到了 DevOps,但让我们更深入地了解一下 DevOps 是什么,以及它试图在更接近的层面上实现什么。
DevOps
综合而言,DevOps 是开发和运维的结合。它是开发人员和系统管理员之间的联合和协作,使得 DevOps 成为可能。请注意我们说的是协作;重要的是要理解协作是 DevOps 的核心。与 Scrum 框架等有权威的方法不同,DevOps 没有标准,但它遵循一套实践,这些实践源自这两个团队之间的文化交流,以实现更短的开发周期和更频繁的部署,采用敏捷方法。
你经常会看到 DevOps 这个术语被错误使用,例如:
-
职位(DevOps 工程师):DevOps 的性质是跨运维和开发团队的协作,因此 DevOps 不是一个职位或一个专门从事 DevOps 的团队。
-
一套工具:用于帮助实现 DevOps 背后目标的工具也经常被混淆。Kubernetes、Docker 和 Jenkins 经常被误解为 DevOps,但它们只是达到目的的手段。
-
标准:正如我们之前提到的,DevOps 运动没有任何管理其实践和流程的权威;实施和遵循一套基本实践,并根据自身业务需求进行调整的是人们。
我们现在知道 DevOps 是一种文化运动,这给我们带来了更频繁的开发周期、频率和运维与开发之间的整合。现在,让我们了解采用 DevOps 的好处背后的问题。
整体瀑布
传统的软件应用程序开发方法称为瀑布。瀑布是一种线性顺序的软件开发方式;基本上,你只能朝一个方向前进。它是从制造业和建筑业中被软件工程采用的。瀑布模型的步骤如下:
-
需求
-
设计
-
实施
-
验证
-
维护
主要问题在于,由于这种方法是为制造业和建筑业而发明的,它根本不具备敏捷性。在这些行业中,你面临的每一个变化或问题都可能让你付出很大的代价,因此在进入下一个阶段之前,必须考虑所有的预防措施。因此,每个阶段都需要相当长的时间,因此上市时间大大缩短。
在这种方法中,在甚至开始创建应用程序之前,开发人员必须设计所有的特性,并且在编写一行代码之前就花费了大量时间进行讨论和规划。这种情况对这种方法的起源来说是有意义的,因为如果你正在建造一座摩天大楼或者一座住宅,你希望在开始建造之前就知道它将如何设计和结构。在软件开发中,你获得反馈越快,就能越快地适应并进行必要的更改以满足客户的需求。在瀑布模型中,直到产品几乎准备好并且更改更难实施时才提供反馈。
瀑布模型本质上是庞大而笨重的,即使我们有不同的团队在产品的不同特性上工作,最终所有这些特性都会被编译在一起,以交付一个单一的大版本。对于这种类型的单体应用,如果有质量保证(QA)团队,他们必须测试该版本的所有特性。这需要很长时间,甚至会进一步增加产品上市的时间。最糟糕的情况是需要进行更改或者一个错误通过了 QA 进入生产。回滚将意味着整个版本的发布,而不仅仅是带有错误的版本,这在进行大版本发布时会带来很大的风险。
敏捷解决庞大的问题
瀑布模型让我们太晚才意识到我们认为会起作用的事情在安装阶段或者甚至在生产阶段并没有按计划进行。进行这些更改涉及很多步骤,而且调整速度缓慢而痛苦。
软件发展迅速,客户的需求可能会在设计过程中发生变化。这就是为什么我们需要比瀑布模型更敏捷和灵活的方法。我们获得反馈越快,就能越快地适应并交付客户的确切期望。
这正是敏捷方法的用途。敏捷旨在通过多次发布软件,每次都经过一系列测试和反馈,以便更快地获得并以更敏捷的方式进行更改和调整。
敏捷是一个改变游戏规则的方法,但它在运维和开发之间产生了冲突。
部署更频繁可能会变得不规范,并且每次都会有所不同,如果不同的工程师执行部署。比如说你在晚上部署,第二天早上负责部署的人和上次部署的工程师可能会有完全不同的部署方式。这种情况会产生差异,并可能导致问题。例如,如果发生了什么事情需要回滚,负责回滚的人可能不知道部署时采取的步骤,以便回滚更改。
这些发布可能会不可预测地影响系统的可用性。运维工程师的绩效评估是他们管理的系统的稳定性,他们有兴趣保持系统的稳定。将不可预测的更改部署到生产环境是他们想要避免的。另一方面,开发人员的绩效评估是他们能够多快地将新的更改、功能和发布投入生产。你可以看到这两个团队有完全相反的目标,他们几乎必须互相斗争来实现这些目标。
团队之间不同的目标使每个团队都处于孤立状态。这会产生信息孤岛,并把问题或应用程序抛到另一个团队那里。这会导致非协作的工作环境,每个人都互相指责,事情进展得更慢,而不是解决问题。
持续的 CI/CD 文化
到目前为止,我觉得你已经注意到我们还没有谈论任何使 DevOps 成为可能的工具。这是因为工具不会解决所有这些类型的问题。它们将帮助您和您的客户强化 DevOps 文化,但并不是使 DevOps 成为可能的原因。
在我们交付产品之前进行标准化和测试对于敏捷和 DevOps 至关重要,工具将帮助我们实现这两个目标。让我们来看看敏捷工作流程和 DevOps 工作流程:
以下是敏捷工作流程的概述:
以下是与 DevOps 的比较:
很明显,它们两者是相辅相成的,它们彼此重叠,因为它们追求相同的目标。DevOps 有额外的步骤,比如操作和监控,这些步骤发生在代码部署之后。这些步骤非常直观;监控包括监视我们的应用在生产环境中的行为,检查它是否存在任何错误,或者它是否正在使用分配给它的所有资源。操作硬件、虚拟机或 PaaS 的部署位置。
持续部署(CD)和持续集成(CI)的理念是为了给我们带来标准化和确保变更和发布尽快进入生产并且失败最少的手段。如果发生故障,我们也可以快速轻松地回滚。CI/CD 的整个目的是自动化手动流程,许多公司仍然手动编译发布,并且通过电子邮件发送二进制文件和部署代码的说明给运维人员。为了实现 CI/CD,我们有工具可以帮助我们自动化整个构建、测试、部署和发布周期。
典型的 CI/CD 流水线是由对 Git 存储库的提交触发的,然后触发自动化构建过程,通常生成一个构件或一个发布,然后触发应用程序的自动化测试和自动化部署。
让我们来看看一些不同的开源工具,对每个工具进行简要解释,并说明它属于 DevOps 周期的哪个阶段。
这还远远不是一个详尽的清单,解释只是它们用途的简要总结:
-
代码:
-
Git:一个版本控制系统,允许开发人员拥有他们的代码分布式存储库,并跟踪开发周期中的变更。
-
GitHub、GitLab、Bitbucket:这三个都是 Git 类型的存储库而不是工具。然而,它们值得一提,因为它们是行业中最常用的公共和私有 Git 存储库。
-
Apache 子版本(SVN):这是另一个版本控制系统。尽管自从 Git 发布以来它不再像以前那样受欢迎,但它值得一提,因为您可能会在传统环境中遇到它。
-
构建:
-
Docker:Docker,正如我们在第十四章中讨论的那样,Getting Your Hands Salty,是一个工具,您可以使用它来构建您的容器镜像,而不受应用程序使用的语言的限制。Docker 在底层使用Buildkit,它也可以作为一个独立产品用于构建 Docker 镜像。
-
Apache Ant:这个工具是第一个取代为 Java 应用程序制作的著名 Unix 构建二进制文件的工具。它使用
xml
来定义构建的步骤。这个工具主要用于 Java 应用程序。 -
Apache Maven:Apache Maven 也是另一个 Java 构建工具,但它解决了 Apache Ant 缺乏的依赖管理等问题。
-
Gradle:Gradle 是基于 Apache Ant 和 Apache Maven 构建的,但 Gradle 使用自己基于 Groovy 的特定语言来定义所需的步骤。Gradle 是最模块化的,几乎所有功能都是通过插件添加的。
-
Grunt:这是 JavaScript 的 Ant 或 Maven 等效工具;它自动化并运行任务,如 linting、单元测试、最小化和编译。Grunt 高度模块化,因为有成千上万的插件可用。
-
测试:
-
Selenium:这主要是一个 Web 应用程序测试工具,可以运行在大多数现代 Web 浏览器上。使用 Selenium,您不一定需要了解测试编程语言,因为它提供了一个 IDE 和使用几种最流行的编程语言的选项。
-
Apache JMeter:这基本上是一个负载性能工具,它在服务器上生成大量负载,以测试静态和动态内容,以便您可以分析其在不同负载类型下的性能。
-
Appium:另一方面,Appium 不仅可以测试 Web 应用程序,还可以对移动和桌面应用程序进行测试。
-
发布、部署、管理、编排、操作:
-
Jenkins:这可能是 DevOps 文化中使用最广泛的工具。Jenkins 是一个自动化服务器,通过调用构建和发布过程的自动化触发器,以及在管道中配置的任何自动化测试,使所有步骤都成为可能。
-
Ansible:这主要是一个配置管理工具,但它也可以通过其模块化帮助我们发布我们的应用程序,并提供一种便捷的方式来开发您自己的 playbooks 以运行在一组服务器上。
-
Puppet:这是另一个配置管理工具,它帮助我们维护配置并管理环境服务器上的软件包补丁安装。
-
Helm:将 Helm 视为 Kubernetes 的
yum
或apt
:它本身无法自动化任何部署过程,但借助诸如 Jenkins 之类的工具,您可以使用它将自定义图表部署到 Kubernetes,并在需要回滚时保留发布历史。 -
Monitor:
-
Nagios:这是经典的监控集中工具,监控从系统性能到服务状态等各种内容。
-
Prometheus:这是云原生计算基金会旗下的一个项目。它允许我们创建自己的指标和警报。
-
Fluentbit:这允许您收集多个日志和/或数据,并将其发送到多个目的地以进行日志收集或处理。
总结
作为最后一章,我们总结了在设计解决方案时需要考虑的一些因素。在本章中,我们介绍了在处理不同场景时应该牢记的事项。
了解我们将在哪里以及如何部署我们的解决方案有助于我们了解可能存在的要求类型;例如,某些行业将有无法忽视的硬性要求,如 HIPAA、PCI 和 GDPR。
然后,我们谈到了部署本地解决方案以及不同的工作负载对裸金属的更好适用性,以及在实施虚拟机时需要考虑的因素。
我们提到了如何转移到云端并不像点击一个门户网站然后等待那样简单,而是一个旅程,因为它允许根据云端提供的众多选项对工作负载进行现代化。
此外,我们提到了迁移现有工作负载的不同方法,如提升和转移、重构、重新架构和重建。
最后,我们描述了 DevOps 如何通过统一开发和运营方面来帮助塑造行业,以及这如何与 CI/CD 改变了软件部署和使用方式相关联。
问题
-
HIPAA 是什么?
-
哪些工作负载更适合在裸金属上运行?
-
虚拟化监视器应该在裸金属上运行吗?
-
虚拟机共享资源吗?
-
什么是网络分割?
-
什么是提升和转移?
-
什么是重构?
-
什么是重新架构?
进一步阅读
大型计算机程序的生产:sunset.usc.edu/csse/TECHRPTS/1983/usccse83-501/usccse83-501.pdf
管理大型软件系统的开发:www-scf.usc.edu/~csci201/lectures/Lecture11/royce1970.pdf
Azure 迁移中心:azure.microsoft.com/en-gb/migration/get-started/
将数据中心迁移到云 IaaS 的 3 条路径:www.gartner.com/smarterwithgartner/3-journeys-for-migrating-a-data-center-to-cloud-iaas/
第十六章:评估
第一章:设计方法简介
-
问题陈述→信息收集→解决方案提议→实施。
-
因为它允许建立正确的要求。
-
为客户选择合适的解决方案留出空间。
-
在“分析问题并提出正确问题”部分进行了探讨。
-
概念验证。
-
实际的解决方案已交付并测试。
-
它使我们能够探索解决方案的不同概念,以及实际工作环境的解决方案。
第二章:定义 GlusterFS 存储
-
第五章,在 Gluster 系统中分析性能进一步分析了这一点。
-
文件存储更适合 GlusterFS 的工作方式。
-
几乎所有云提供商现在都提供对象存储。
-
文件存储、块存储(通过 iSCSI Initiator)和对象存储(通过插件)。
-
不,但它确实为项目做出了贡献。
-
GlusterFS 是免费的开源软件;只需从您喜欢的软件包管理器下载即可。
-
它通过地理复制功能实现。
第三章:设计存储集群
-
这取决于所使用的卷类型,但 2 个 CPU 和 4GB 以上的 RAM 是一个很好的起点。
-
GlusterFS 将使用 brick 的文件系统缓存机制。
-
这是一个快速的存储层,I/O 将在此处提供,而不是转到较慢的存储。缓存可以是 RAM 或更快的存储介质,如固态硬盘。
-
随着并发性的增加,软件将需要更多的 CPU 周期来处理请求。
-
分布式将聚合空间,复制将镜像数据,因此“减半”空间,分散将聚合空间,但将消耗一个节点用于奇偶校验。将其视为 RAID5。
-
取决于许多变量,如保留期、数据进入等等...
-
预期的数据增长量。
-
吞吐量是一定时间内给定数据量的函数,通常显示为 MB/s 或每秒兆字节
每秒输入输出操作数(IOPS)是每秒一定数量的操作的函数
I/O 大小指的是设备执行的请求大小
-
GlusterFS 使用的存储位置的布局。
-
GlusterFS 的数据从一个集群复制到另一个集群的过程,通常位于不同的地理位置。
第四章:在云基础架构上使用 GlusterFS
-
GlusterFS 用于存储实际数据的存储位置。
-
Z 文件系统,由 Sun Microsystems 创建并后来开源的高级文件系统。
-
一个 ZFS 存储池。
-
用于读取请求的磁盘,通常比 zpool 中使用的磁盘更快,延迟更低。
-
通常通过操作系统的软件包管理器,如 yum。
-
一组 GlusterFS 节点,将参与同一集群。
-
Gluster 卷创建<卷名称><卷类型><节点数><节点和 brick 路径>。
-
此设置控制将用于缓存的内存量。
-
自适应替换缓存,这是 ZFS 的缓存算法。
第五章:在 Gluster 系统中分析性能
-
每秒兆字节,吞吐量测量。
-
显示 ZFS 的 I/O 统计信息。
-
sysstat 软件包的一部分,用于块设备 I/O 统计。
-
这是读取延迟,以毫秒为单位测量。
-
CPU 等待未完成 I/O 的时间量。
-
灵活 I/O 测试,用于 I/O 基准测试的工具。
-
通过配置文件或直接通过命令传递参数。
-
一个告诉 FIO 如何运行测试的文件。
-
通过故意杀死其中一个节点。
-
通过增加节点上的资源或增加磁盘大小。
第六章:创建高可用的自愈架构
-
主要的 Kubernetes 组件分为控制平面和 API 对象。
-
它们三个都是由三个主要的公共云提供商 Google、亚马逊和微软提供的托管 Kubernetes 解决方案。
-
容器的攻击面较小,但这并不意味着它们不会受到利用,但主要的容器运行时项目都得到了很好的维护,如果检测到利用,将会迅速解决。
-
这将取决于您尝试运行的应用程序类型以及您对技术的熟悉程度。将应用程序迁移到容器通常很容易,但以最有效的方式进行迁移需要工作。
-
不,您可以在 Windows 上找到 Docker Engine,并且在撰写本文时,Kubernetes Windows 节点处于测试版。
第七章:理解 Kubernetes 集群的核心组件
-
Kubernetes,在撰写本文时,是市场上最主要的容器编排器。
-
Kubernetes 由组成集群的二进制文件和称为 API 对象的逻辑对象组成。
-
Kubernetes API 对象是编排器管理的逻辑对象。
-
我们可以运行编排的容器化工作负载。
-
容器编排器是负责管理我们运行的容器和与保持我们的工作负载高度可用相关的不同任务的工具。
-
Pod 是 Kubernetes 的最小逻辑对象,它封装了一个或多个共享命名空间中的容器。
-
部署是由 Kubernetes 复制控制器管理的一组复制的 Pod。
第八章:在 Azure 上设计 Kubernetes
-
由于 ETCD 的大多数机制,通常首选奇数以便能够始终获得多数票。
-
是的,但它也可以在单独的一组笔记本上运行。
-
由于心跳和领导者选举频率,建议使用较低的延迟。
-
工作节点或节点是负责运行容器工作负载的集群成员。
-
工作负载的类型以及每种工作负载将运行的容器数量。
-
所有存储提供商或提供者都可以在此处找到:
kubernetes.io/docs/concepts/storage/storage-classes/#provisioner
-
需要负载均衡器来将请求发送到所有复制的 Pod。
-
命名空间可用于逻辑上分区我们的集群,并为每个逻辑分区分配角色和资源。
第九章:部署和配置 Kubernetes
-
有几种安装 Kubernetes 的方法,从自动配置工具如
kubeadm
和kubespray
到完全手动安装。您可以在以下链接找到有关安装方法的更多信息:kubernetes.io/docs/setup/
-
kubeconfig
文件包含与 API 服务器通信和认证所需的所有必要信息。 -
您可以使用多种工具创建 SSL 证书,在本书中我们使用了
cffssl
。但您也可以使用openssl
和easyrsa
。 -
Azure Kubernetes Services(AKS)是微软为其公共云 Azure 提供的托管 Kubernetes 解决方案。
-
Azure CLI 可以在任何操作系统中使用,因为它是基于 Python 的命令行界面。
-
您可以通过 Azure CLI、PowerShell 或 Azure GUI 创建资源组。
-
您可以在以下链接找到安装 etcd 的不同方法:
play.etcd.io/install
第十章:使用 ELK 堆栈进行监控
-
积极收集数据的过程。
-
通过了解使用趋势,可以根据实际数据做出购买更多资源等决策。
-
通过将数据放在一个地方,可以在事件发生之前主动检测到事件。
-
通过了解存储系统的正常行为,从而为性能提供基线。
-
当看到不应该出现的峰值时,这可能意味着不稳定的行为。
-
与通过环境中的每个主机进行检查相比,可以通过单个集中位置进行检查。
-
用于数据索引和分析的软件。
-
Elasticsearch 以 json 格式存储数据。
-
Logstash 是一个数据处理解析器,允许在发送到 Elasticsearch 之前对数据进行操作。
-
Kibana 为 Elasticsearch 提供可视化界面,允许数据轻松可视化。
第十一章:设计 ELK 堆栈
-
至少需要 2 个 CPU 核心才能在较小的部署中实现最佳功能。
-
至少 2GHz。
-
较慢或少于 2 个 CPU 主要影响 Elasticsearch 的启动时间、索引速率和延迟。
-
内核使用可用的 RAM 来缓存对文件系统的请求。
-
如果发生交换,搜索延迟将受到严重影响。
-
如果内存不足,Elasticsearch 将无法启动,一旦运行,如果内存不足,OOM 将终止 Elasticsearch。
-
最低要求是 2.5GB,但建议使用 4GB。
-
/var/lib/elasticsearch
-
较低的延迟有助于索引/搜索延迟。
-
2GB RAM 和 1 个 CPU。
-
这是一个存储位置,logstash 将在崩溃的情况下持续存储队列。
-
有多少用户将同时访问仪表板。
第十二章:使用 Elasticsearch、Logstash 和 Kibana 管理日志
-
Elasticsearch 可以通过软件包管理器安装。
-
这是通过 parted 完成的。
-
将磁盘的 UUID 添加到
/etc/fstab
。 -
/etc/elasticsearch/elasticsearch.yml
-
这给集群命名,名称应在节点之间保持一致,以便每个节点加入相同的集群。
-
数字由
(N/2)+1
决定。 -
通过使用相同的 cluster.name 设置,第二个节点将加入相同的集群。
-
添加存储库,通过
yum
安装,为 logstash 分区磁盘。 -
这是一个存储位置,logstash 将在崩溃的情况下持续存储队列。
-
协调节点是一个 Elasticsearch 节点,不接受输入,不存储数据,也不参与主/从选举。
-
Beats 是来自 Elastic.co 的轻量级数据船运工具。
-
Filebeat 的功能是从诸如
syslog
、apache 和其他来源收集日志,然后将其发送到 Elasticsearch 或 Logstash。
第十三章:用 Salty Solutions 解决管理问题
-
是维护现有 IT 基础设施的任务。
-
集中所有基础设施,无论其操作系统或架构如何。
-
Puppet、Chef、Ansible、Spacewalk、SaltStack 等等。
-
编写期望状态的特定语言,可以描述 IT 基础设施的状态。
-
推送和拉取。
-
Salt 是一个开源项目/软件,旨在解决系统管理的许多挑战。
-
主节点和从节点。
第十四章:设计盐溶液并安装软件
-
任何 Linux 发行版。
-
一个自管理节点是最低要求。
-
为我们的 SaltStack 提供高可用性和负载平衡。
-
手动安装二进制文件,以及通过引导脚本。
-
通过它们的密钥。
-
通过
test.ping
函数。 -
Grains 包含特定于从属者的信息(元数据)或标签。Pillars 包含配置和敏感信息。
第十五章:设计最佳实践
-
HIPAA 代表《健康保险可携带性和责任法》,这是处理健康和医疗数据的标准。
-
类型 1/类型 2 的 Hypervisors(基于内核的虚拟机(KVM),Linux 容器(LXC),XEN)
用于 SAP HANA 的 Linux
Apache Hadoop
用于 Oracle DB 的 Linux
大型 MongoDB 部署用于内存缓存
高性能计算(HPC)
-
是的,理想情况下,虚拟化程序需要访问资源,以更有效地为虚拟机提供资源。
-
是的,CPU 是主要的共享资源,因为物理 CPU 必须为同一节点中的所有 VM 提供周期。
-
允许不同的网络流量通过不同的 NIC /子网。
-
这是一种迁移方法,可以将现有工作负载从本地迁移到云端。
-
这是一种迁移方法,利用架构的一些变化,以利用云端提供的解决方案。
-
这是一种迁移方法,涉及将解决方案重新架构到云端。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析