Kubernetes-入门指南第三版-全-

Kubernetes 入门指南第三版(全)

原文:annas-archive.org/md5/cd5da86ed7d5bb1e918abd86b113e0c3

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

本书是一个关于开始使用 Kubernetes 和整体容器管理的指南。我们将向您展示 Kubernetes 的特性和功能,以及它如何融入整体运营策略。您将了解在将容器从开发人员的笔记本转移到更大规模上进行管理时遇到的难题。您还将看到 Kubernetes 如何成为帮助您充满信心地面对这些挑战的完美工具。

本书适合谁

无论您是专注于开发,还是深耕运维,抑或是以执行官身份展望未来,Kubernetes 和本书都适合您。《Getting Started with Kubernetes》将帮助您了解如何将容器应用程序推向生产环境,并结合最佳实践和逐步操作指南,将其与现实世界的运营策略联系起来。您将了解 Kubernetes 如何融入您的日常运营中,这可以帮助您为生产准备好容器应用程序堆栈。

对 Docker 容器,一般的软件开发和高级操作有一些了解将有所帮助。

要充分利用本书

本书将涵盖下载和运行 Kubernetes 项目。如果您在 Windows 上,可以使用 Linux 系统(VirtualBox 可行),并且对命令行有一定的熟悉度。

此外,您应该拥有 Google Cloud Platform 账户。您可以在此处免费试用:cloud.google.com/

此外,本书的某些部分需要 AWS 账户。您可以在此处免费试用:aws.amazon.com/

下载示例代码文件

您可以从您的账户在www.packt.com下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packt.com/support并注册,文件将通过电子邮件直接发送给您。

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

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

  2. 选择“支持”选项卡。

  3. 单击“代码下载与勘误”。

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

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

  • 适用于 Windows 的 WinRAR/7-Zip

  • 适用于 Mac 的 Zipeg/iZip/UnRarX

  • 适用于 Linux 的 7-Zip/PeaZip

本书的代码捆绑包也托管在 GitHub 上:github.com/PacktPublishing/Getting-Started-with-Kubernetes-third-edition。如果代码有更新,将在现有的 GitHub 仓库中更新。

我们还有来自我们丰富书籍和视频目录的其他代码捆绑包可供查看!请访问github.com/PacktPublishing/

下载彩色图片

我们还提供了一个 PDF 文件,里面包含了本书中使用的截图/图表的彩色版本。您可以在此下载:www.packtpub.com/sites/default/files/downloads/9781788994729_ColorImages.pdf

使用的约定

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

CodeInText:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入以及 Twitter 用户名。例如:“Master 节点的最后两个主要部分是 kube-controller-managercloud-controller-manager。”

一段代码会如下设置:

"conditions": [
  {
    "type": "Ready",
    "status": "True"
  }

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

"conditions": [
  {
    "type": "Ready",
    "status": "True"
  }

任何命令行输入或输出如下所示:

$ kubectl describe pods/node-js-pod

粗体:表示新术语、重要词汇或您在屏幕上看到的词汇。例如,菜单或对话框中的词汇会这样出现在文本中。比如:“点击 Jobs,然后从列表中选择 long-task,这样我们就可以看到详细信息。”

警告或重要提示以这种形式呈现。

小贴士和技巧如下所示。

联系我们

我们始终欢迎读者的反馈。

一般反馈:如果您对本书的任何部分有疑问,请在邮件主题中注明书名,并通过 customercare@packtpub.com 给我们发送邮件。

勘误:虽然我们已尽力确保内容的准确性,但仍可能会出现错误。如果您在本书中发现任何错误,我们将不胜感激,若您能将其报告给我们。请访问 www.packt.com/submit-errata,选择您的书籍,点击“勘误提交表单”链接,并输入相关细节。

盗版:如果您在互联网上发现我们作品的任何非法复制品,恳请您提供该材料的地址或网站名称。请通过 copyright@packt.com 联系我们,并附上链接。

如果您有兴趣成为作者:如果您在某个领域有专业知识,并且有兴趣写书或为书籍做贡献,请访问 authors.packtpub.com

评论

请留下评价。当您阅读并使用了本书后,何不在您购买本书的网站上留下评价?潜在的读者可以查看您的客观意见以做出购买决定,而我们在 Packt 也可以了解您对我们产品的看法,我们的作者也能看到您对其书籍的反馈。谢谢!

欲了解更多关于 Packt 的信息,请访问 packt.com

第一章:Kubernetes 简介

本书将帮助你构建、扩展和管理生产级 Kubernetes 集群。本书的每一部分将为你提供容器的核心概念,以及如何运行现代 Web 服务的操作背景,这些服务需要每天 24 小时、每周 7 天、每年 365 天保持可用。随着本书的进展,你将通过具体的基于代码的示例,了解 Kubernetes 的多种抽象,并能够将它们部署到运行中的集群中,以便获得真实的反馈。到本书结束时,你将掌握 Kubernetes 的核心概念构建模块,并且对如何处理以下几种范式有清晰的理解:

  • 编排

  • 调度

  • 网络

  • 安全

  • 存储

  • 身份与认证

  • 基础设施管理

本章将为为什么选择 Kubernetes? 打下基础,并概述现代容器历史,深入探讨容器的工作原理,以及为什么调度、编排和管理容器平台非常重要。我们将把这些与实际的业务和产品目标联系起来。本章还将简要介绍 Kubernetes 编排如何提升我们的容器管理策略,以及如何让一个基本的 Kubernetes 集群上线,运行,并为容器部署做好准备。

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

  • 介绍容器操作和管理

  • 容器管理的重要性

  • Kubernetes 的优势

  • 下载最新版本的 Kubernetes

  • 安装并启动新的 Kubernetes 集群

  • Kubernetes 集群的组成部分

技术要求

你需要安装以下工具:

  • Python

  • AWS CLI

  • Google Cloud CLI

  • Minikube

本章将详细介绍这些工具的安装和配置。如果你已经知道如何操作,可以立即进行设置。

容器简要概述

不管你信不信,容器及其前身已经在 Linux 和 Unix 操作系统中存在了超过 15 年。如果深入研究容器如何运行的基本原理,你会发现它们的根源可以追溯到 1970 年发明的 chroot 技术。从 2000 年初开始,FreeBSD、Linux、Solaris、Open VZ、Warden,最终到 Docker 都在为最终用户封装容器化技术方面作出了重要的尝试。

尽管 VServer 项目和首个提交(在一台机器上运行多个通用 Linux 服务器,具有高度独立性和安全性ieeexplore.ieee.org/document/1430092/?reload=true)) 可能是容器历史上最有趣的历史节点之一,但显然,正是 Docker 在 2013 年底全面投入容器生态系统并决定将 dotCloud 更名为 Docker,点燃了容器生态系统的火焰。他们对容器吸引力的广泛营销为我们今天所看到的广泛市场采纳奠定了基础,也为我们在此讨论的庞大容器编排和调度平台奠定了直接基础。

在过去的五年里,容器的流行程度像野火一样迅猛增长。曾经仅限于开发者笔记本、测试或开发环境的容器,如今已成为强大生产系统的构建模块。它们正在运行高度安全的银行工作负载和交易系统,为物联网提供动力,保持我们的按需经济运转,并扩展到数百万个容器,以确保 21 世纪的产品在云端和私有数据中心中高效运行。此外,容器化技术已经渗透到我们的技术时代精神中,全球的每一个技术大会都为构建、运行或在容器中开发分配了大量的讲座和会议。

这个引人入胜的故事的开端是 Docker 及其引人注目的开发者友好工具套件。Docker for macOS 和 Windows、Compose、Swarm 和 Registry 是一些极具威力的工具,塑造了工作流程并改变了公司开发软件的方式。它们为容器在软件交付生命周期SDLC)的核心地带存在架起了一座桥梁,围绕这些容器形成了一个引人注目的生态系统。正如马尔科姆·麦克林(Malcom McLean)在 1950 年代通过创建标准化的集装箱彻底改变了物理运输世界,今天这种集装箱被用于从冰块托盘到汽车等各种物品的运输,Linux 容器正在通过使应用环境在基础设施中保持可移植和一致性,从而彻底改变软件开发世界。

我们将从容器的主流化、投入生产并在组织内部大规模应用的故事开始。接下来我们将探讨是什么造就了容器的崛起。

什么是容器?

容器是一种操作系统虚拟化技术,类似于其前身的虚拟机。还有一些较不为人知的虚拟化类型,如应用虚拟化、网络虚拟化和存储虚拟化。虽然这些技术自 1960 年代以来就已存在,但 Docker 对容器范式的封装代表了资源隔离的现代实现,利用了内建的 Linux 内核特性,如 chroot、控制组cgroups)、UnionFS 和命名空间,实现了在进程级别的完全隔离资源控制。

容器使用这些技术创建轻量级镜像,这些镜像作为独立、完全封装的软件单元,携带它所需的所有内容。这些内容可能包括应用程序二进制文件、任何系统工具或库、基于环境的配置和运行时。隔离的这一特殊属性非常重要,因为它允许开发人员和运维人员利用容器的全功能特性,在不论运行环境的情况下都能顺利运行。这包括开发人员的笔记本电脑以及任何预生产或生产环境。

将应用程序打包机制与运行环境解耦是一个强大的概念,它为工程团队提供了清晰的关注点分离。这使得开发人员能够专注于将核心业务功能构建到应用程序代码中并管理自己的依赖关系,而运维人员则可以简化应用程序的持续集成、发布和部署,而无需担心其配置问题。

容器技术的核心是三个关键概念:

  • cgroups

  • 命名空间

  • 联合文件系统

cgroups

cgroups 通过允许主机共享并限制每个进程或容器可以消耗的资源来工作。这对于资源利用率和安全性都非常重要,因为它防止了对主机硬件资源的拒绝服务DoS)攻击。多个容器可以共享 CPU 和内存,同时保持在预定义的限制范围内。cgroups 允许容器提供对内存、磁盘 I/O、网络和 CPU 的访问。你还可以访问设备(例如,/dev/foo)。cgroups 还支持容器约束的软硬限制,我们将在后面的章节中讨论这些内容。

有七种主要的 cgroups:

  • Memory cgroup:该 cgroup 跟踪组的页面访问情况,并可以为物理内存、内核内存和总内存定义限制。

  • Blkio cgroup:该 cgroup 跟踪每个组的 I/O 使用情况,监控每个块设备的读写活动。你可以按设备、按操作与字节数,甚至按读与写来限制。

  • CPU cgroup:该 cgroup 跟踪每个 CPU 上的用户和系统 CPU 时间以及使用情况。这允许你设置权重,但不能设置限制。

  • Freezer cgroup:在批量管理系统中非常有用,这些系统经常停止和启动任务以便高效地调度资源。SIGSTOP 信号用于暂停一个进程,且该进程通常并不察觉它正在被暂停(或恢复)。

  • CPUset cgroup:这允许你将一个组固定到多核 CPU 架构中的特定 CPU 上。你可以按应用程序进行固定,这将防止它在 CPUs 之间移动。这样可以通过增加本地内存访问或减少线程切换来提高代码的性能。

  • Net_cls/net_prio cgroup:这用于跟踪由 cgroup 中的进程生成的出口流量类别(net_cls)或优先级(net_prio)。

  • 设备 cgroup:这控制了组对设备节点的读写权限。

命名空间

命名空间为操作系统内的进程交互提供了另一种隔离形式,创建了我们所说的容器工作空间。Linux 命名空间是通过名为 unshare 的系统调用创建的,而 clonesetns 则允许你以其他方式操作命名空间。

unshare() 允许一个进程(或线程)解除与其他进程(或线程)共享的部分执行上下文。部分执行上下文(如挂载命名空间)在使用 FORK(2) 创建新进程时会隐式共享(更多信息请访问 man7.org/linux/man-pages/man2/fork.2.html),而其他部分,如虚拟内存,可能在使用 CLONE(2) 创建进程或线程时根据显式请求共享(更多信息请访问 man7.org/linux/man-pages/man2/clone.2.html)。

命名空间限制了进程对其他进程、网络、文件系统和用户 ID 组件的可见性。容器进程仅能看到同一命名空间中的内容。容器中的进程或主机进程不能直接从该容器进程内访问。此外,Docker 为每个容器提供了自己的网络堆栈,类似地保护了套接字和接口。

如果 cgroups 限制了你可以使用多少资源,那么命名空间限制了你可以看到什么资源。下图展示了容器的组成:

在 Docker 引擎中,使用以下命名空间:

  • pid:通过提供独立的进程 ID 集来实现进程隔离,这些进程 ID 来自其他命名空间。这些命名空间是嵌套的。

  • net:通过提供一个回环接口来虚拟化网络堆栈,并可以创建物理和虚拟网络接口,这些接口一次只能存在于一个命名空间中。

  • ipc:管理进程间通信的访问。

  • mnt:控制文件系统挂载点。这是 Linux 内核中创建的第一类命名空间,可以是私有的或共享的。

  • uts:Unix 时间共享系统通过允许单个系统为不同进程提供不同的主机和域名命名方案,从而隔离版本 ID 和内核。进程gethostnamesethostname使用此命名空间。

  • user:此命名空间允许你将容器中的 UID/GID 映射到主机,并且避免在容器中额外配置。

联合文件系统

联合文件系统也是使用 Docker 容器的一个关键优势。容器是从镜像运行的。类似于虚拟机或云计算中的镜像,它表示某个特定时间点的状态。容器镜像快照了文件系统,但通常比虚拟机要小得多。容器共享主机内核,并通常运行一个较小的进程集,因此文件系统和启动时间通常会小得多——尽管这些约束并不是严格执行的。其次,联合文件系统可以高效地存储、下载和执行这些镜像。容器采用了复制写入存储的概念,可以立即创建一个全新的容器,而无需等待复制整个文件系统。这类似于其他系统中的瘦分配(thin provisioning),即按需分配存储:

复制写入存储(Copy-on-write storage)跟踪变化的内容,因此它类似于分布式版本控制系统DVCS),如 Git。用户可以利用复制写入存储的多个选项:

  • AUFS 和 overlay 文件级别

  • 块级别的设备映射器

  • BTRFS 和 ZFS 以及文件系统级别

理解联合文件系统(union filesystems)最简单的方法是将其视为一层一层的蛋糕,每一层都是独立制作的。Linux 内核是我们的基础层;然后,我们可以添加如 Red Hat Linux 或 Ubuntu 这样的操作系统。

接下来,我们可能会添加一个应用程序,如 nginx 或 Apache。每次更改都会创建一个新的层。最终,当你进行更改并添加新的层时,你总是会有一个顶层(想象它是糖霜),这是一个可写层。联合文件系统利用这一策略,使每一层都轻量且快速。

在 Docker 的情况下,存储驱动程序负责将这些层叠加在一起,并提供一个统一的视图来查看这些系统。这堆层的最上层是一个可写层,你将在其中进行工作:可写容器层。我们可以将下方的每一层视为容器镜像层:

使这一切真正高效的是 Docker 会在首次构建时缓存图层。假设我们有一个带有 Ubuntu 的镜像,然后添加 Apache 并构建该镜像。接下来,我们使用 Ubuntu 作为基础构建 MySQL。第二次构建会更快,因为 Ubuntu 图层已经被缓存。本质上,我们前面示意图中的巧克力和香草图层已经烤好了。我们只需要烤制开心果(MySQL)图层,组装并加上糖霜(可写图层)。

为什么容器如此酷?

另一个令人兴奋的事实是,不仅开源社区拥抱了容器和 Kubernetes,云服务提供商也深度融入了容器生态系统,并投入数百万美元支持工具、生态系统和管理平台,这些可以帮助管理容器。这意味着你有更多的选择来运行容器工作负载,并且你将拥有更多工具来管理集群上应用程序的调度和编排。

我们将探讨 Kubernetes 用户可用的一些特定机会,但在本书出版时,所有主要的云服务提供商CSPs)都提供某种形式的托管或管理 Kubernetes 服务:

  • 亚马逊 Web 服务:AWS 提供Kubernetes 弹性容器服务EKS)(更多信息请访问 aws.amazon.com/eks/),这是一项托管服务,可简化在其云中运行 Kubernetes 集群。你也可以使用 kops 自行创建集群(更多信息请访问 kubernetes.io/docs/setup/custom-cloud/kops/)。该产品仍在积极开发中:

  • 谷歌云平台:GCP 提供谷歌 Kubernetes 引擎GKE)(更多信息请访问 cloud.google.com/kubernetes-engine/),这是一个强大的集群管理器,可以在云中部署、管理和扩展容器化应用程序。谷歌已经运行容器化工作负载超过 15 年,这个平台是进行复杂工作负载管理的绝佳选择:

当你利用这些系统时,你可以获得 Kubernetes 集群的内置管理功能,这使你能够专注于集群的优化、配置和部署。

持续集成/持续部署的优势

ThoughtWorks 将持续集成定义为一种开发实践,要求开发人员每天多次将代码集成到共享的代码库中。通过持续的构建和部署过程,组织能够将质量控制和测试作为日常工作周期的一部分。最终的结果是更新和修复错误的速度大大加快,整体质量得到了提高。

然而,在创建与测试和生产环境匹配的开发环境方面,一直存在挑战。通常,这些环境之间的不一致性使得无法充分发挥持续交付的优势。持续集成是加速组织软件交付生命周期的第一步,它帮助你更快、更可靠地将软件功能交付给客户。

持续交付/部署的概念利用持续集成使开发人员能够拥有真正可移植的部署。部署在开发人员笔记本电脑上的容器可以轻松地部署到内部的暂存服务器上。之后,它们可以轻松转移到运行在云中的生产服务器上。这得益于容器的特性,容器构建文件指定了父层,就像我们之前讨论的那样。这样一个优势是,它非常容易确保开发、暂存和生产环境中的操作系统、软件包和应用版本一致。由于所有的依赖项都被打包进层中,相同的主机服务器可以运行多个容器,这些容器可能使用不同的操作系统或软件包版本。此外,我们可以在同一主机服务器上运行各种语言和框架,而不会像在单一操作系统的虚拟机中那样发生典型的依赖冲突。

这为应用程序的持续交付/部署奠定了基础,因为运营团队或开发人员可以专注于确保部署和应用程序发布的正确性,而不必担心依赖项的复杂性。

持续交付是一个过程,其中所有的代码更改会自动构建、测试(持续集成),然后发布到生产环境(持续交付)。如果这个过程能够捕获正确的质量标准、安全保证和单元/集成/系统测试,开发团队将能够持续发布生产就绪且可部署的工件,这些工件经过自动化和标准化的流程处理。

需要注意的是,CD 要求工程团队不仅仅自动化单元测试。为了在诸如 Kubernetes 等复杂的调度和编排系统中使用 CD,团队需要在将应用程序部署到客户之前,验证应用程序在多个维度上的功能。我们将在后面的章节中探索 Kubernetes 所提供的部署策略。

最后,值得记住的是,利用 Kubernetes 与 CI/CD 结合,能够减少技术公司面临的许多常见问题的风险:

  • 长发布周期:如果需要很长时间才能将代码发布给用户,那么他们将错过潜在的功能,这会导致收入损失。如果你有手动测试或发布过程,这将会减慢将更改部署到生产环境的速度,从而影响到客户。

  • 修复代码很难:当你缩短发布周期时,你能够更接近问题产生的时间发现并修复 bug。这降低了修复成本,因为 bug 的引入和发现时间之间存在相关性。

  • 更好的发布:发布得越多,你在发布方面就会变得越好。挑战你的开发者和运维人员,要求他们在 CI/CD 流程中构建自动化、监控和日志记录,将使你的管道更加健壮。随着发布频率的提高,发布之间的差异也会减少。差异越小,团队就能更快地排查潜在的破坏性更改,这反过来又给他们更多的时间进一步完善发布流程。这是一个良性循环!

由于所有的依赖都打包在该层中,同一主机服务器可以运行多个容器,分别运行不同的操作系统或软件包版本。此外,我们可以在同一主机服务器上使用多种语言和框架,而不会像在 VM 中使用单一操作系统时那样发生典型的依赖冲突。

资源利用率

明确定义的隔离和层级文件系统使得容器非常适合运行具有非常小的占用空间和特定领域目的的系统。简化的部署和发布过程意味着我们可以快速且频繁地进行部署。因此,许多公司已将部署时间从几周或几个月缩短到几天甚至几小时。在某些情况下,这种开发生命周期非常适合小型、专注的团队,负责处理一个大型应用程序中的小部分。

微服务与编排

当我们将一个应用程序分解为非常具体的领域时,我们需要一种统一的方式来在各个部分和领域之间进行沟通。Web 服务多年来一直在发挥这一作用,但容器所带来的额外隔离和细粒度聚焦为微服务铺平了道路。

微服务的定义可能有些模糊,但有一位软件开发领域的尊敬作者和演讲者马丁·福勒提出了这样的定义:

简而言之,微服务架构风格是一种将单一应用开发为一组小型服务的方式,每个服务都在自己的进程中运行,并通过轻量级机制进行通信,通常是 HTTP 资源 API。这些服务围绕业务能力构建,并由完全自动化的部署机制独立部署。这些服务的集中管理最低限度,服务可能采用不同的编程语言编写,并使用不同的数据存储技术。

随着容器化的转折点到来,并且随着微服务在组织中的发展,它们很快就需要一种策略来维护大量的容器和微服务。一些组织将在未来几年内运行数百甚至数千个容器。

未来的挑战

生命周期过程本身是操作和管理中的一个重要环节。我们如何在容器失败时自动恢复?哪些上游服务会受到这种故障的影响?我们如何在最小的停机时间内修补我们的应用?随着流量的增长,我们如何扩展我们的容器和服务?

网络和处理也是重要的关注点。有些进程属于同一服务,并且可能受益于与网络的接近。例如,数据库可能会将大量数据发送到特定的微服务进行处理。我们如何将容器放置在集群中彼此接近的位置?是否有共同的数据需要访问?我们如何让新服务被发现并提供给其他系统?

资源利用率也是关键。容器的小巧体积意味着我们可以优化基础设施以提高利用率。在弹性云中实现的节省将使我们进一步减少硬件浪费。我们如何最有效地调度工作负载?如何确保我们的重要应用总是拥有正确的资源?我们如何在空闲容量上运行不太重要的工作负载?

最后,可移植性是推动许多组织实现容器化的关键因素。Docker 使得在各种操作系统、云服务商、内部硬件甚至开发者笔记本上部署标准容器变得非常容易。然而,我们仍然需要工具来迁移容器。我们如何在集群中的不同节点之间移动容器?如何在最小中断的情况下推出更新?我们使用什么过程来执行蓝绿部署或金丝雀发布?

无论你是开始构建独立的微服务并将关注点分离到独立的容器中,还是只是想充分利用应用开发中的可移植性和不可变性,管理和编排的需求变得十分明显。这正是像 Kubernetes 这样的编排工具展现最大价值的地方。

我们的第一个集群

Kubernetes 支持多种平台和操作系统。在本书的示例中,我使用了 Ubuntu 16.04 Linux VirtualBox(www.virtualbox.org/wiki/Downloads)作为客户端,使用Google Compute EngineGCE)和 Debian 作为集群本身。我们还将简要了解一个运行在Amazon Web ServicesAWS)上的 Ubuntu 集群。

为了节省一些费用,GCP(cloud.google.com/free/)和 AWS(aws.amazon.com/free/)都提供免费的层级和试用优惠,用于他们的云基础设施。如果可能的话,值得利用这些免费试用来学习 Kubernetes。

本书中的大多数概念和示例应该适用于任何 Kubernetes 集群的安装。要获取有关其他平台设置的更多信息,请参考 Kubernetes 入门页面,它将帮助你选择适合你集群的解决方案:kubernetes.io/docs/getting-started-guides/

在 GCE 上运行 Kubernetes

我们有几种方式来设置开发环境的先决条件。虽然在这个示例中我们会在本地机器上使用 Linux 客户端,但你也可以使用 Google Cloud Shell 来简化依赖和设置。你可以查看该文档:cloud.google.com/shell/docs/,然后跳转到教程中的gcloud auth login部分。

回到本地安装,首先让我们确保环境已正确设置,然后再安装 Kubernetes。从更新软件包开始:

$ sudo apt-get update

你应该会看到类似以下的输出:

$ sudo apt update
[sudo] password for user:
Hit:1 http://archive.canonical.com/ubuntu xenial InRelease
Ign:2 http://dl.google.com/linux/chrome/deb stable InRelease
Hit:3 http://archive.ubuntu.com/ubuntu xenial InRelease
Get:4 http://security.ubuntu.com/ubuntu xenial-security InRelease [102 kB]
Ign:5 http://dell.archive.canonical.com/updates xenial-dell-dino2-mlk InRelease
Hit:6 http://ppa.launchpad.net/webupd8team/sublime-text-3/ubuntu xenial InRelease
Hit:7 https://download.sublimetext.com apt/stable/ InRelease
Hit:8 http://dl.google.com/linux/chrome/deb stable Release
Get:9 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [102 kB]
Hit:10 https://apt.dockerproject.org/repo ubuntu-xenial InRelease
Hit:11 https://deb.nodesource.com/node_7.x xenial InRelease
Hit:12 https://download.docker.com/linux/ubuntu xenial InRelease
Ign:13 http://dell.archive.canonical.com/updates xenial-dell InRelease
<SNIPPED...>
Fetched 1,593 kB in 1s (1,081 kB/s)
Reading package lists... Done
Building dependency tree
Reading state information... Done
120 packages can be upgraded. Run 'apt list --upgradable' to see them.
$

如果没有安装 Python 和curl,请安装它们:

$ sudo apt-get install python
$ sudo apt-get install curl

安装gcloud SDK:

$ curl https://sdk.cloud.google.com | bash

gcloud进入我们的路径之前,我们需要启动一个新的命令行。

配置你的 GCP 帐户信息。此操作应该会自动打开一个浏览器,我们可以在其中登录 Google Cloud 帐户并授权 SDK:

$ gcloud auth login

如果你遇到登录问题或想使用其他浏览器,你可以选择使用--no-launch-browser命令。复制并粘贴网址到你选择的机器和/或浏览器。使用你的 Google Cloud 凭证登录并在权限页面点击“允许”。最后,你应该会收到一个授权码,你可以复制并粘贴回命令行,提示符将等待输入。

应该设置一个默认项目,但我们可以通过以下命令来验证这一点:

$ gcloud config list project

我们可以修改此设置,并通过以下命令设置一个新的默认项目。确保使用项目 ID 而不是项目名称,如下所示:

$ gcloud config set project <PROJECT ID>

我们可以在控制台的以下网址找到我们的项目 ID:console.developers.google.com/project。或者,我们也可以通过$ gcloud alpha projects list列出活动项目。

你可以在 GCP 仪表板的console.developers.google.com/project中启用项目的 API 访问,或者 Kubernetes 脚本将在下一部分提示你这么做:

接下来,你需要切换到一个可以安装 Kubernetes 二进制文件的目录。我们将设置并下载软件:

$ mkdir ~/code/gsw-k8s-3
$ cd ~/code/gsw-k8s-3

安装最新版本的 Kubernetes 只需一步,具体如下:

$ curl -sS https://get.k8s.io | bash

根据你的连接速度,下载 Kubernetes 可能需要一两分钟。早期版本会自动调用kube-up.sh脚本并开始构建我们的集群。在 1.5 版本中,我们需要手动调用kube-up.sh脚本来启动集群。默认情况下,它会使用 Google Cloud 和 GCE:

$ kubernetes/cluster/kube-up.sh

如果此时由于缺少组件而出现错误,你需要在本地 Linux 机器上添加一些组件。如果你在使用 Google Cloud Shell,或者在 GCP 中使用 VM,你可能不会看到这个错误:

$ kubernetes_install cluster/kube-up.sh... 
Starting cluster in us-central1-b using provider gce
... calling verify-prereqs
missing required gcloud component "alpha"
missing required gcloud component "beta"
$

你可以看到这些组件缺失,并且需要它们来利用kube-up.sh script

$ gcloud components list
Your current Cloud SDK version is: 193.0.0
The latest available version is: 193.0.0
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Components │
├───────────────┬──────────────────────────────────────────────────────┬──────────────────────────┬───────────┤
│ Status │ Name │ ID │ Size │
├───────────────┼──────────────────────────────────────────────────────┼──────────────────────────┼───────────┤
│ Not Installed │ App Engine Go Extensions │ app-engine-go │ 151.9 MiB │
│ Not Installed │ Cloud Bigtable Command Line Tool │ cbt │ 4.5 MiB │
│ Not Installed │ Cloud Bigtable Emulator │ bigtable │ 3.7 MiB │
│ Not Installed │ Cloud Datalab Command Line Tool │ datalab │ < 1 MiB │
│ Not Installed │ Cloud Datastore Emulator │ cloud-datastore-emulator │ 17.9 MiB │
│ Not Installed │ Cloud Datastore Emulator (Legacy) │ gcd-emulator │ 38.1 MiB │
│ Not Installed │ Cloud Pub/Sub Emulator │ pubsub-emulator │ 33.4 MiB │
│ Not Installed │ Emulator Reverse Proxy │ emulator-reverse-proxy │ 14.5 MiB │
│ Not Installed │ Google Container Local Builder │ container-builder-local │ 3.8 MiB │
│ Not Installed │ Google Container Registry's Docker credential helper │ docker-credential-gcr │ 3.3 MiB │
│ Not Installed │ gcloud Alpha Commands │ alpha │ < 1 MiB │
│ Not Installed │ gcloud Beta Commands │ beta │ < 1 MiB │
│ Not Installed │ gcloud app Java Extensions │ app-engine-java │ 118.9 MiB │
│ Not Installed │ gcloud app PHP Extensions │ app-engine-php │ │
│ Not Installed │ gcloud app Python Extensions │ app-engine-python │ 6.2 MiB │
│ Not Installed │ gcloud app Python Extensions (Extra Libraries) │ app-engine-python-extras │ 27.8 MiB │
│ Not Installed │ kubectl │ kubectl │ 12.3 MiB │
│ Installed │ BigQuery Command Line Tool │ bq │ < 1 MiB │
│ Installed │ Cloud SDK Core Libraries │ core │ 7.3 MiB │
│ Installed │ Cloud Storage Command Line Tool │ gsutil │ 3.3 MiB │
└───────────────┴──────────────────────────────────────────────────────┴──────────────────────────┴───────────┘
To install or remove components at your current SDK version [193.0.0], run:
 $ gcloud components install COMPONENT_ID
 $ gcloud components remove COMPONENT_ID
To update your SDK installation to the latest version [193.0.0], run:
 $ gcloud components update

你可以通过将它们添加到你的 shell 来更新组件:

$ gcloud components install alpha beta
Your current Cloud SDK version is: 193.0.0
Installing components from version: 193.0.0
┌──────────────────────────────────────────────┐
│ These components will be installed. │
├───────────────────────┬────────────┬─────────┤
│ Name │ Version │ Size │
├───────────────────────┼────────────┼─────────┤
│ gcloud Alpha Commands │ 2017.09.15 │ < 1 MiB │
│ gcloud Beta Commands │ 2017.09.15 │ < 1 MiB │
└───────────────────────┴────────────┴─────────┘
For the latest full release notes, please visit:
 https://cloud.google.com/sdk/release_notes
Do you want to continue (Y/n)? y
╔════════════════════════════════════════════════════════════╗
╠═ Creating update staging area ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: gcloud Alpha Commands ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: gcloud Beta Commands ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Creating backup and activating new installation ═╣
╚════════════════════════════════════════════════════════════╝
Performing post processing steps...done. 
Update done!

运行kube-up.sh脚本后,你将看到很多行滚动显示。让我们一段一段地看一下它们:

如果你的gcloud组件没有更新,你可能会被提示更新它们。

上面的截图显示了检查先决条件,以及确保所有组件都是最新的。这是针对每个提供商的特定步骤。在 GCE 的情况下,它会验证 SDK 是否已安装,并且所有组件是否都是最新的。如果不是,你会在此时看到安装或更新的提示:

现在,脚本正在启动集群。再次强调,这对每个提供商都是特定的。对于 GCE,它首先检查 SDK 是否已为默认项目和区域进行配置。如果已设置,你会在输出中看到这些信息:

你可能会看到一个输出,表示存储桶尚未创建。这是正常的!创建脚本将自动创建它。

BucketNotFoundException: 404 gs://kubernetes-staging-22caacf417 bucket does not exist.

接下来,它将服务器二进制文件上传到 Google Cloud 存储,正如在创建 gs:... 行中看到的:

然后,它检查是否有任何已在运行的集群。接下来,我们最终开始创建集群。在上面的截图输出中,我们可以看到它正在创建主服务器、IP 地址以及为集群配置适当的防火墙设置:

最后,它会为我们的集群创建从节点或节点。这是我们的容器工作负载实际运行的地方。它将不断循环并等待所有从节点启动。默认情况下,集群将有四个节点(从节点),但 K8s 支持拥有超过 1,000 个节点(并且很快会超过)。稍后我们将在本书中回到节点扩展的话题:

Attempt 1 to create kubernetes-minion-template
WARNING: You have selected a disk size of under [200GB]. This may result in poor I/O performance. For more information, see: https://developers.google.com/compute/docs/disks#performance.
Created [https://www.googleapis.com/compute/v1/projects/gsw-k8s-3/global/instanceTemplates/kubernetes-minion-template].
NAME MACHINE_TYPE PREEMPTIBLE CREATION_TIMESTAMP
kubernetes-minion-template n1-standard-2 2018-03-17T11:14:04.186-07:00
Created [https://www.googleapis.com/compute/v1/projects/gsw-k8s-3/zones/us-central1-b/instanceGroupManagers/kubernetes-minion-group].
NAME LOCATION SCOPE BASE_INSTANCE_NAME SIZE TARGET_SIZE INSTANCE_TEMPLATE AUTOSCALED
kubernetes-minion-group us-central1-b zone kubernetes-minion-group 0 3 kubernetes-minion-template no
Waiting for group to become stable, current operations: creating: 3
Group is stable
INSTANCE_GROUPS=kubernetes-minion-group
NODE_NAMES=kubernetes-minion-group-176g kubernetes-minion-group-s9qw kubernetes-minion-group-tr7r
Trying to find master named 'kubernetes-master'
Looking for address 'kubernetes-master-ip'
Using master: kubernetes-master (external IP: 104.155.172.179)
Waiting up to 300 seconds for cluster initialization.

现在,一切都已创建,集群已初始化并启动。假设一切顺利,我们将获得主节点的 IP 地址:

... calling validate-cluster
Validating gce cluster, MULTIZONE=
Project: gsw-k8s-3
Network Project: gsw-k8s-3
Zone: us-central1-b
No resources found.
Waiting for 4 ready nodes. 0 ready nodes, 0 registered. Retrying.
No resources found.
Waiting for 4 ready nodes. 0 ready nodes, 0 registered. Retrying.
Waiting for 4 ready nodes. 0 ready nodes, 1 registered. Retrying.
Waiting for 4 ready nodes. 0 ready nodes, 4 registered. Retrying.
Found 4 node(s).
NAME STATUS ROLES AGE VERSION
kubernetes-master Ready,SchedulingDisabled <none> 32s v1.9.4
kubernetes-minion-group-176g Ready <none> 25s v1.9.4
kubernetes-minion-group-s9qw Ready <none> 25s v1.9.4
kubernetes-minion-group-tr7r Ready <none> 35s v1.9.4
Validate output:
NAME STATUS MESSAGE ERROR
etcd-1 Healthy {"health": "true"}
scheduler Healthy ok
controller-manager Healthy ok
etcd-0 Healthy {"health": "true"}
Cluster validation succeeded

另外,请注意,配置以及集群管理凭据存储在home/<Username>/.kube/config中。

然后,脚本将验证集群。此时,我们不再运行特定于提供商的代码。验证脚本将通过kubectl.sh脚本查询集群。这个脚本是管理我们集群的核心脚本。在这种情况下,它检查找到的、已注册的并处于就绪状态的从节点的数量。它会循环执行,给集群最多 10 分钟的时间来完成初始化。

启动成功后,集群从节点和组件健康状况的摘要将显示在屏幕上:

Done, listing cluster services:
Kubernetes master is running at https://104.155.172.179
GLBCDefaultBackend is running at https://104.155.172.179/api/v1/namespaces/kube-system/services/default-http-backend:http/proxy
Heapster is running at https://104.155.172.179/api/v1/namespaces/kube-system/services/heapster/proxy
KubeDNS is running at https://104.155.172.179/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
kubernetes-dashboard is running at https://104.155.172.179/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy
Metrics-server is running at https://104.155.172.179/api/v1/namespaces/kube-system/services/https:metrics-server:/proxy
Grafana is running at https://104.155.172.179/api/v1/namespaces/kube-system/services/monitoring-grafana/proxy
InfluxDB is running at https://104.155.172.179/api/v1/namespaces/kube-system/services/monitoring-influxdb:http/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

最后,执行kubectl cluster-info命令,它会输出主节点服务的 URL,包括 DNS、UI 和监控。让我们看看这些组件中的一些。

如果你想获取更多的调试信息和/或诊断集群问题,可以使用kubectl cluster-info dump查看集群的情况。此外,如果你需要暂停并休息一下,并且想节省空闲时间,可以登录到 GUI 并将kubernetes-minion-group实例组设置为零,这将移除所有实例。铅笔图标会帮助你编辑组;将其设置为零。如果你想再次继续,别忘了把它改回三!

你也可以直接停止管理节点。你需要点击停止按钮来关闭它:

如果你想重新启动集群,请重新启动服务器以继续。它们需要一些时间来启动并相互连接。

如果你想同时操作多个集群,或者想使用不同于默认值的名称,可以查看<kubernetes>/cluster/gce/config-default.sh文件,以便更细粒度地配置集群。

Kubernetes UI

从 Kubernetes v1.3.x 开始,你不能再通过公共 IP 地址访问 GUI。为了解决这个问题,我们将使用kubectl proxy命令。首先,从配置命令中获取令牌,然后我们将使用它来启动 UI 的本地代理版本:

$ kubectl config view |grep token
 token: RvoYTIn4rExi1bNRzk56g0PU0srZbzOf
$ kubectl proxy --port=8001

打开浏览器并输入以下 URL:https://localhost/ui/

如果你使用的是 macOS,也可以输入这些命令自动打开浏览器窗口:$ open https://localhost/ui/;如果你使用的是 Linux,可以输入$ xdg-open https://localhost/ui

证书默认是自签名的,因此你需要在浏览器中忽略警告才能继续。之后,我们将看到一个登录对话框:

在这个登录对话框中,你需要输入之前命令中获得的 token。

这里是我们使用 K8s 安装过程中列出的凭据的位置。我们可以随时通过使用 config 命令 $ kubectl config view 找到它们。

使用 Token 选项并登录到你的集群:

现在我们输入了 token,你应该会看到如下截图中的仪表盘:

主面板开始时显示的内容不多。这里有一个链接,点击后会带你到一个部署容器化应用的 GUI 界面。这种 GUI 是一种非常简单的方式,可以让你在无需担心 Kubernetes YAML 语法的情况下开始部署应用。然而,随着容器使用的深入,使用已经提交到源代码控制中的 YAML 定义是一种更好的实践。

如果你点击左侧菜单中的 Nodes 链接,你将看到当前集群节点的一些度量信息:

在顶部,我们可以看到 CPU 和内存使用情况的汇总,并列出了我们的集群节点。点击其中一个节点会带我们到该节点的详细信息页面,查看节点的健康状况和各种度量指标。

Kubernetes UI 还提供了许多其他视图,这些视图在我们开始启动实际应用并为集群添加配置时会变得更加有用。

Grafana

另一个默认安装的服务是 Grafana。这个工具将为我们提供一个仪表盘,用来查看集群节点的度量信息。我们可以通过以下语法在浏览器中访问它:

https://localhost/api/v1/proxy/namespaces/kube-system/services/monitoring-grafana

Grafana 仪表盘应该看起来像这样:

从主页面,点击首页下拉菜单并选择“集群”。在这里,Kubernetes 实际上运行着许多服务。Heapster 用来收集 pod 和节点的资源使用情况,并将信息存储在 InfluxDB 中。我们在 Grafana UI 中看到的结果,如 CPU 和内存使用情况,正是这些数据。我们将在第八章中深入探讨这一内容,监控与日志记录

命令行

kubectl 脚本包含了用于探索我们集群和正在运行的工作负载的命令。你可以在 /kubernetes/client/bin 文件夹中找到它。我们将在本书中多次使用这个命令,因此我们可以花点时间设置我们的环境。我们可以通过将二进制文件夹添加到 PATH 中来完成设置,方法如下:

$ export PATH=$PATH:/<Path where you downloaded K8s>/kubernetes/client/bin
$ chmod +x /<Path where you downloaded K8s>/kubernetes/client/bin

你可以选择将 kubernetes 文件夹下载到你的主文件夹之外,因此可以根据需要修改前面的命令。另一个好主意是通过将 export 命令添加到你的主目录下的 .bashrc 文件末尾,使这些更改永久生效。

现在我们已经将 kubectl 添加到路径中,可以开始使用它了。它有很多命令。由于我们还没有启动任何应用程序,因此大多数命令可能不会很有趣。但是,我们可以立刻探索两个命令。

首先,我们已经在初始化时看到过 cluster-info 命令,但我们可以随时再次运行它,命令如下:

$ kubectl cluster-info

另一个有用的命令是 get。它可以用于查看当前运行的服务、Pod、复制控制器等。以下是几个开箱即用的实用示例:

  • 列出我们集群中的节点:
    $ kubectl get nodes
  • 列出集群事件:
    $ kubectl get events
  • 最后,我们可以看到集群中运行的任何服务,如下所示:
    $ kubectl get services

一开始,我们只会看到一个服务,名为 kubernetes。该服务是集群的核心 API 服务器。

对于任何前述的命令,你总是可以在末尾添加 -h 标志来了解命令的用法。

在主节点上运行的服务

让我们更深入地了解我们的新集群及其核心服务。默认情况下,机器的名称带有 kubernetes- 前缀。我们可以在启动集群之前,使用 $KUBE_GCE_INSTANCE_PREFIX 来修改这个前缀。对于我们刚启动的集群,主节点的名称应该是 kubernetes-master。我们可以使用 gcloud 命令行工具来通过 SSH 连接到该机器。以下命令将启动与主节点的 SSH 会话。请确保将你的项目 ID 和区域替换为与你的环境匹配的值:

$ gcloud compute ssh --zone "<your gce zone>" "kubernetes-master"  $ gcloud compute ssh --zone "us-central1-b" "kubernetes-master"
Warning: Permanently added 'compute.5419404412212490753' (RSA) to the list of known hosts.

Welcome to Kubernetes v1.9.4!

You can find documentation for Kubernetes at:
  http://docs.kubernetes.io/

The source for this release can be found at:
  /home/kubernetes/kubernetes-src.tar.gz
Or you can download it at:
  https://storage.googleapis.com/kubernetes-release/release/v1.9.4/kubernetes-src.tar.gz

It is based on the Kubernetes source at:
  https://github.com/kubernetes/kubernetes/tree/v1.9.4

For Kubernetes copyright and licensing information, see:
  /home/kubernetes/LICENSES

jesse@kubernetes-master ~ $ 

如果你在通过 Google Cloud CLI 使用 SSH 时遇到问题,可以使用控制台,它内置了一个 SSH 客户端。只需进入 VM 实例的详细信息页面,你就会看到 kubernetes-master 列表中的 SSH 选项。或者,在 VM 实例的详细信息页面顶部也有 SSH 选项。

一旦我们登录,我们应该会看到标准的 shell 提示符。让我们运行 docker 命令,筛选出 ImageStatus

$ docker container ls --format 'table {{.Image}}\t{{.Status}}' 

即使我们还没有在 Kubernetes 上部署任何应用程序,我们也可以注意到已经有几个容器在运行。以下是每个容器的简要描述:

  • fluentd-gcp:这个容器收集并将集群日志文件发送到 Google Cloud Logging 服务。

  • node-problem-detector:这个容器是一个守护进程,运行在每个节点上,目前用于检测硬件和内核层面的问题。

  • rescheduler:这是另一个附加容器,确保关键组件始终在运行。当资源不足时,它甚至可以移除不太关键的 Pod,以腾出空间。

  • glbc:这是另一个 Kubernetes 插件容器,利用新的 Ingress 功能提供 Google Cloud Layer 7 负载均衡。

  • kube-addon-manager:这个组件是 Kubernetes 通过各种插件进行扩展的核心。它还会定期应用对/etc/kubernetes/addons目录的任何更改。

  • etcd-empty-dir-cleanup:一个清理etcd中空键的工具。

  • kube-controller-manager:这是一个控制器管理器,负责控制多种集群功能,确保准确且最新的复制是其重要职责之一。此外,它还监控、管理并发现新的节点。最后,它还管理和更新服务端点。

  • kube-apiserver:这个容器运行 API 服务器。正如我们在 Swagger 界面中所探讨的,这个 RESTful API 允许我们创建、查询、更新和删除 Kubernetes 集群的各种组件。

  • kube-scheduler:这个调度器将未调度的 Pod 绑定到节点,基于当前的调度算法。

  • etcd:这个组件运行由 CoreOS 构建的etcd软件,它是一个分布式且一致的键值存储。这是 Kubernetes 集群状态存储、更新和由 K8s 各个组件获取的地方。

  • pause:这个容器通常被称为 Pod 基础设施容器,用于设置并保持每个 Pod 的网络命名空间和资源限制。

我省略了许多这些名称中的amd64,以使其更具通用性。Pod 的目的保持不变。

要退出 SSH 会话,只需在提示符下输入exit

在下一章中,我们还将展示这些服务如何在第一张图中共同工作,Kubernetes 核心架构

运行在工作节点上的服务

我们可以通过 SSH 连接到其中一个工作节点,但由于 Kubernetes 会将工作负载调度到整个集群,所以我们无法在单个工作节点上看到所有容器。然而,我们可以使用kubectl命令查看所有工作节点上运行的 Pod:

$ kubectl get pods
No resources found.

由于我们尚未在集群中启动任何应用程序,因此暂时看不到任何 Pod。然而,实际上有几个系统 Pod 在运行 Kubernetes 基础设施的部分组件。我们可以通过指定kube-system命名空间来查看这些 Pod。我们稍后会深入探讨命名空间及其重要性,但现在,可以使用--namespace=kube-system命令查看这些 K8s 系统资源,如下所示:

$ kubectl get pods --namespace=kube-system
jesse@kubernetes-master ~ $ kubectl get pods --namespace=kube-system
NAME READY STATUS RESTARTS AGE
etcd-server-events-kubernetes-master 1/1 Running 0 50m
etcd-server-kubernetes-master 1/1 Running 0 50m
event-exporter-v0.1.7-64464bff45-rg88v 1/1 Running 0 51m
fluentd-gcp-v2.0.10-c4ptt 1/1 Running 0 50m
fluentd-gcp-v2.0.10-d9c5z 1/1 Running 0 50m
fluentd-gcp-v2.0.10-ztdzs 1/1 Running 0 51m
fluentd-gcp-v2.0.10-zxx6k 1/1 Running 0 50m
heapster-v1.5.0-584689c78d-z9blq 4/4 Running 0 50m
kube-addon-manager-kubernetes-master 1/1 Running 0 50m
kube-apiserver-kubernetes-master 1/1 Running 0 50m
kube-controller-manager-kubernetes-master 1/1 Running 0 50m
kube-dns-774d5484cc-gcgdx 3/3 Running 0 51m
kube-dns-774d5484cc-hgm9r 3/3 Running 0 50m
kube-dns-autoscaler-69c5cbdcdd-8hj5j 1/1 Running 0 51m
kube-proxy-kubernetes-minion-group-012f 1/1 Running 0 50m
kube-proxy-kubernetes-minion-group-699m 1/1 Running 0 50m
kube-proxy-kubernetes-minion-group-sj9r 1/1 Running 0 50m
kube-scheduler-kubernetes-master 1/1 Running 0 50m
kubernetes-dashboard-74f855c8c6-v4f6x 1/1 Running 0 51m
l7-default-backend-57856c5f55-2lz6w 1/1 Running 0 51m
l7-lb-controller-v0.9.7-kubernetes-master 1/1 Running 0 50m
metrics-server-v0.2.1-7f8dd98c8f-v9b4c 2/2 Running 0 50m
monitoring-influxdb-grafana-v4-554f5d97-l7q4k 2/2 Running 0 51m
rescheduler-v0.3.1-kubernetes-master 1/1 Running 0 50m

前六行应该很熟悉。这些是我们在主节点上看到的服务,我们将在节点上看到它们的一部分。还有一些我们尚未看到的额外服务。kube-dns选项提供 DNS 和服务发现功能,kubernetes-dashboard-xxxx是 Kubernetes 的用户界面,l7-default-backend-xxxx提供新的七层负载均衡能力的默认负载均衡后端,而heapster-v1.2.0-xxxxmonitoring-influx-grafana提供 Heapster 数据库和用户界面,用于监控集群中的资源使用情况。

最后,kube-proxy-kubernetes-minion-group-xxxx是代理,它将流量引导到集群中运行的正确后端服务和 Pod。kube-apiserver验证并配置 API 对象的数据,其中包括服务、复制控制器、Pod 以及其他 Kubernetes 对象。rescheduler确保调度关键系统附加组件,前提是集群有足够的可用资源。

如果我们 SSH 进入某个随机的 minion 节点,我们会看到几个容器在这些 Pod 上运行。一个示例可能如下所示:

再次,我们在主节点上看到了类似的服务。我们没有在主节点上看到的服务包括:

  • kubedns:这个容器监控 Kubernetes 中的服务和端点资源,并将任何变化同步到 DNS 查询。

  • kube-dnsmasq:这是另一个提供 DNS 缓存的容器。

  • dnsmasq-metrics:它提供集群中 DNS 服务的指标报告。

  • l7-defaultbackend:这是处理 GCE L7 负载均衡器和 Ingress 的默认后端。

  • kube-proxy:这是你集群的网络和服务代理。这个组件确保服务流量被正确地引导到集群中运行工作负载的地方。我们将在本书后面更深入地探讨这个内容。

  • heapster:这个容器用于监控和分析。

  • addon-resizer:这个集群工具用于调整容器规模。

  • heapster_grafana:它跟踪资源使用情况和监控。

  • heapster_influxdb:这个时序数据库用于存储 Heapster 数据。

  • cluster-proportional-autoscaler:这个集群工具用于根据集群的大小调整容器的规模。

  • exechealthz:它对 Pod 进行健康检查。

再次说明,我已经省略了这些名称中的amd64,以使其更具通用性。Pod 的目的保持不变。

拆除集群

好的,这是我们在 GCE 上的第一个集群,但让我们来看看其他提供商。为了简化,我们需要删除刚刚在 GCE 上创建的集群。我们可以通过一个简单的命令来拆除集群:

$ cluster/kube-down.sh

与其他提供商合作

默认情况下,Kubernetes 使用 Google Cloud 的 GCE 提供者。为了使用其他云提供者,我们可以探索一系列快速扩展的工具集。我们以 AWS 为例,它有两个主要选项:kops(github.com/kubernetes/kops)和 kube-aws(github.com/kubernetes-incubator/kube-aws)。作为参考,以下KUBERNETES_PROVIDER值列在此表中:

提供者 KUBERNETES_PROVIDER 值 类型
Google Compute Engine gce 公有云
Google 容器引擎 gke 公有云
亚马逊 Web 服务 aws 公有云
微软 Azure azure 公有云
Hashicorp vagrant vagrant 虚拟开发环境
VMware vSphere vsphere 私有云/本地虚拟化
运行 CoreOS 的 libvirt libvirt-coreos 虚拟化管理工具
Canonical Juju(Ubuntu 背后的团队) juju 操作系统服务编排工具

CLI 设置

让我们尝试在 AWS 上设置集群。作为前提条件,我们需要安装并配置 AWS CLI 以便为我们的账户提供支持。AWS CLI 的安装和配置文档可以在以下链接中找到:

你还需要按照 AWS 推荐的方式配置你的凭证(参考 docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials)以便使用 kops。要开始使用,你需要首先安装 CLI 工具(参考 github.com/kubernetes/kops/blob/master/docs/install.md)。如果你在 Linux 上运行,可以按以下方式安装工具:

curl -Lo kops https://github.com/kubernetes/kops/releases/download/$(curl -s https://api.github.com/repos/kubernetes/kops/releases/latest | grep tag_name | cut -d '"' -f 4)/kops-darwin-amd64
chmod +x ./kops
sudo mv ./kops /usr/local/bin/

如果你在 macOS 上安装这个,可以通过命令行终端使用brew update && brew install kops命令。提醒一下,如果你还没有安装kubectl,你需要先安装它!查看前面的链接中的说明来确认安装过程。

IAM 设置

为了使用 kops,我们需要在 AWS 中创建一个 IAM 角色,并赋予以下权限:

AmazonEC2FullAccess
AmazonRoute53FullAccess
AmazonS3FullAccess
IAMFullAccess
AmazonVPCFullAccess

一旦你在 AWS GUI 中手动创建了这些组件,你可以从你的 PC 上运行以下命令来设置正确的权限:

aws iam create-group --group-name kops

aws iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/AmazonEC2FullAccess --group-name kops
aws iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/AmazonRoute53FullAccess --group-name kops
aws iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess --group-name kops
aws iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/IAMFullAccess --group-name kops
aws iam attach-group-policy --policy-arn arn:aws:iam::aws:policy/AmazonVPCFullAccess --group-name kops

aws iam create-user --user-name kops

aws iam add-user-to-group --user-name kops --group-name kops

aws iam create-access-key --user-name kops 

为了使用这个新创建的 kops 用户与 kops 工具交互,你需要从输出的 JSON 中复制SecretAccessKeyAccessKeyID,然后按如下方式配置 AWS CLI:

# configure the aws client to use your new IAM user
aws configure # Use your new access and secret key here
aws iam list-users # you should see a list of all your IAM users here
# Because "aws configure" doesn't export these vars for kops to use, we export them now
export AWS_ACCESS_KEY_ID=$(aws configure get aws_access_key_id)
export AWS_SECRET_ACCESS_KEY=$(aws configure get aws_secret_access_key)

我们将使用基于 gossip 的集群来绕过 kops 对公共 DNS 区域的配置要求。这需要 kops 1.6.2 或更高版本,并允许你创建一个本地注册的集群,要求名称以.k8s.local结尾。稍后会详细介绍。

如果你想了解如何通过提供商购买并设置公共路由 DNS,你可以在 kops 文档中查看可用的场景:github.com/kubernetes/kops/blob/master/docs/aws.md#configure-dns

集群状态存储

由于我们在云中使用配置管理构建资源,因此我们需要将集群的表示存储在一个专用的 S3 桶中。这个“真相来源”将使我们能够维护一个集中位置,用于 Kubernetes 集群的配置和状态。请在桶名称前加上唯一的值。

目前,你需要自己设置好kubectlkopsaws cli以及 IAM 凭证!

现在一定要在us-east-1区域创建你的桶,因为 kops 目前对桶的位置有明确要求:

aws s3api create-bucket \
 --bucket gsw-k8s-3-state-store \
 --region us-east-1

让我们继续设置版本控制,以便在出现问题时可以将集群回滚到先前的状态。看看基础设施即代码的强大功能!

aws s3api put-bucket-versioning --bucket gsw-k8s-3-state-store --versioning-configuration Status=Enabled

创建你的集群

我们将继续使用前面提到的.k8s.local设置,以简化集群的 DNS 设置。如果你更喜欢,也可以使用 kops 中提供的名称和状态标志,避免使用环境变量。让我们先准备好本地环境:

$ export NAME=gswk8s3.k8s.local
$ export KOPS_STATE_STORE=s3://gsw-k8s-3-state-store
$ aws s3api create-bucket --bucket gsw-k8s-3-state-store --region us-east-1
{
 "Location": "/gsw-k8s-3-state-store"
}
$

让我们在俄亥俄州启动我们的集群,并首先验证我们是否能看到该地区:

$ aws ec2 describe-availability-zones --region us-east-2
{
 "AvailabilityZones": [
 {
 "State": "available", 
 "ZoneName": "us-east-2a", 
 "Messages": [], 
 "RegionName": "us-east-2"
 }, 
 {
 "State": "available", 
 "ZoneName": "us-east-2b", 
 "Messages": [], 
 "RegionName": "us-east-2"
 }, 
 {
 "State": "available", 
 "ZoneName": "us-east-2c", 
 "Messages": [], 
 "RegionName": "us-east-2"
 }
 ]
}

太好了!让我们来构建一些 Kubernetes。我们将使用最基本的 kops 集群命令,尽管文档中也提供了更复杂的示例(github.com/kubernetes/kops/blob/master/docs/high_availability.md):

kops create cluster --zones us-east-2a ${NAME}

使用 kops,通常在 Kubernetes 中,所有内容都将创建在自动扩展组ASGs)中。

在这里阅读更多关于 AWS 自动扩展组的信息——它们至关重要:docs.aws.amazon.com/autoscaling/ec2/userguide/AutoScalingGroup.html

一旦你运行此命令,你将看到大量的配置输出,采用我们称之为 dry run 格式的形式。这类似于 Terraform 中的 Terraform 计划,允许你查看你即将构建的 AWS 资源,并相应地编辑输出。

在输出的最后,你会看到以下文本,它为你提供了关于下一步的一些基本建议:

Must specify --yes to apply changes
Cluster configuration has been created.

Suggestions:
* list clusters with: kops get cluster
* edit this cluster with: kops edit cluster gwsk8s3.k8s.local
* edit your node instance group: kops edit ig --name=gwsk8s3.k8s.local nodes
* edit your master instance group: kops edit ig --name=gwsk8s3.k8s.local master-us-east-2a

Finally configure your cluster with: kops update cluster gwsk8s3.k8s.local --yes

如果你在~/.ssh目录中没有 SSH 密钥对,你需要创建一个。本文将指导你完成步骤:help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/

一旦你确认输出结果看起来不错,就可以创建集群:

kops update cluster gwsk8s3.k8s.local --yes

这将给你提供大量关于集群创建的输出,你可以跟随这些信息:

I0320 21:37:34.761784 29197 apply_cluster.go:450] Gossip DNS: skipping DNS validation
I0320 21:37:35.172971 29197 executor.go:91] Tasks: 0 done / 77 total; 30 can run
I0320 21:37:36.045260 29197 vfs_castore.go:435] Issuing new certificate: "apiserver-aggregator-ca"
I0320 21:37:36.070047 29197 vfs_castore.go:435] Issuing new certificate: "ca"
I0320 21:37:36.727579 29197 executor.go:91] Tasks: 30 done / 77 total; 24 can run
I0320 21:37:37.740018 29197 vfs_castore.go:435] Issuing new certificate: "apiserver-proxy-client"
I0320 21:37:37.758789 29197 vfs_castore.go:435] Issuing new certificate: "kubecfg"
I0320 21:37:37.830861 29197 vfs_castore.go:435] Issuing new certificate: "kube-controller-manager"
I0320 21:37:37.928930 29197 vfs_castore.go:435] Issuing new certificate: "kubelet"
I0320 21:37:37.940619 29197 vfs_castore.go:435] Issuing new certificate: "kops"
I0320 21:37:38.095516 29197 vfs_castore.go:435] Issuing new certificate: "kubelet-api"
I0320 21:37:38.124966 29197 vfs_castore.go:435] Issuing new certificate: "kube-proxy"
I0320 21:37:38.274664 29197 vfs_castore.go:435] Issuing new certificate: "kube-scheduler"
I0320 21:37:38.344367 29197 vfs_castore.go:435] Issuing new certificate: "apiserver-aggregator"
I0320 21:37:38.784822 29197 executor.go:91] Tasks: 54 done / 77 total; 19 can run
I0320 21:37:40.663441 29197 launchconfiguration.go:333] waiting for IAM instance profile "nodes.gswk8s3.k8s.local" to be ready
I0320 21:37:40.889286 29197 launchconfiguration.go:333] waiting for IAM instance profile "masters.gswk8s3.k8s.local" to be ready
I0320 21:37:51.302353 29197 executor.go:91] Tasks: 73 done / 77 total; 3 can run
I0320 21:37:52.464204 29197 vfs_castore.go:435] Issuing new certificate: "master"
I0320 21:37:52.644756 29197 executor.go:91] Tasks: 76 done / 77 total; 1 can run
I0320 21:37:52.916042 29197 executor.go:91] Tasks: 77 done / 77 total; 0 can run
I0320 21:37:53.360796 29197 update_cluster.go:248] Exporting kubecfg for cluster
kops has set your kubectl context to gswk8s3.k8s.local

和 GCE 一样,设置过程会花费几分钟。它将在 S3 中暂存文件,并在我们的 AWS 账户中创建适当的实例、虚拟私有云VPC)、安全组等。然后,Kubernetes 集群将被设置并启动。一旦一切完成并启动,我们应该会看到下一步的选项:

Cluster is starting. It should be ready in a few minutes.

Suggestions:
 * validate cluster: kops validate cluster
 * list nodes: kubectl get nodes --show-labels
 * ssh to the master: ssh -i ~/.ssh/id_rsa admin@api.gswk8s3.k8s.local
The admin user is specific to Debian. If not using Debian please use the appropriate user based on your OS.
 * read about installing addons: https://github.com/kubernetes/kops/blob/master/docs/addons.md

你将能够看到实例和安全组,VPC 也会为你的集群创建。kubectl 上下文也会指向你的新 AWS 集群,这样你就可以与之交互:

再次,我们将通过 SSH 进入主节点。这一次,我们可以使用原生的 SSH 客户端,并以 admin 用户身份登录,因为 Kops 中 Kubernetes 的 AMI 是 Debian。我们将找到密钥文件,路径为/home/<username>/.ssh

$ ssh -v -i /home/<username>/.ssh/<your_id_rsa_file> admin@<Your master IP>

如果你在 SSH 密钥方面遇到问题,你可以通过创建一个密钥并将其添加到集群来手动设置密钥,并检查集群是否需要滚动更新:

$ kops create secret --name gswk8s3.k8s.local sshpublickey admin -i ~/.ssh/id_rsa.pub
$ kops update cluster --yes
Using cluster from kubectl context: gswk8s3.k8s.local
I0320 22:03:42.823049 31465 apply_cluster.go:450] Gossip DNS: skipping DNS validation
I0320 22:03:43.220675 31465 executor.go:91] Tasks: 0 done / 77 total; 30 can run
I0320 22:03:43.919989 31465 executor.go:91] Tasks: 30 done / 77 total; 24 can run
I0320 22:03:44.343478 31465 executor.go:91] Tasks: 54 done / 77 total; 19 can run
I0320 22:03:44.905293 31465 executor.go:91] Tasks: 73 done / 77 total; 3 can run
I0320 22:03:45.385288 31465 executor.go:91] Tasks: 76 done / 77 total; 1 can run
I0320 22:03:45.463711 31465 executor.go:91] Tasks: 77 done / 77 total; 0 can run
I0320 22:03:45.675720 31465 update_cluster.go:248] Exporting kubecfg for cluster
kops has set your kubectl context to gswk8s3.k8s.local

Cluster changes have been applied to the cloud.

Changes may require instances to restart: kops rolling-update cluster

$ kops rolling-update cluster --name gswk8s3.k8s.local
NAME STATUS NEEDUPDATE READY MIN MAX NODES
master-us-east-2a Ready 0 1 1 1 1
nodes Ready 0 2 2 2 2

No rolling-update required.
$

一旦你进入集群主节点,我们可以查看容器。我们将使用sudo docker ps --format 'table {{.Image}}t{{.Status}}'来查看正在运行的容器。我们应该能看到以下内容:

admin@ip-172-20-47-159:~$ sudo docker container ls --format 'table {{.Image}}\t{{.Status}}'
IMAGE STATUS
kope/dns-controller@sha256:97f80ad43ff833b254907a0341c7fe34748e007515004cf0da09727c5442f53b Up 29 minutes
gcr.io/google_containers/pause-amd64:3.0 Up 29 minutes
gcr.io/google_containers/kube-apiserver@sha256:71273b57d811654620dc7a0d22fd893d9852b6637616f8e7e3f4507c60ea7357 Up 30 minutes
gcr.io/google_containers/etcd@sha256:19544a655157fb089b62d4dac02bbd095f82ca245dd5e31dd1684d175b109947 Up 30 minutes
gcr.io/google_containers/kube-proxy@sha256:cc94b481f168bf96bd21cb576cfaa06c55807fcba8a6620b51850e1e30febeb4 Up 30 minutes
gcr.io/google_containers/kube-controller-manager@sha256:5ca59252abaf231681f96d07c939e57a05799d1cf876447fe6c2e1469d582bde Up 30 minutes
gcr.io/google_containers/etcd@sha256:19544a655157fb089b62d4dac02bbd095f82ca245dd5e31dd1684d175b109947 Up 30 minutes
gcr.io/google_containers/kube-scheduler@sha256:46d215410a407b9b5a3500bf8b421778790f5123ff2f4364f99b352a2ba62940 Up 30 minutes
gcr.io/google_containers/pause-amd64:3.0 Up 30 minutes
gcr.io/google_containers/pause-amd64:3.0 Up 30 minutes
gcr.io/google_containers/pause-amd64:3.0 Up 30 minutes
gcr.io/google_containers/pause-amd64:3.0 Up 30 minutes
gcr.io/google_containers/pause-amd64:3.0 Up 30 minutes
gcr.io/google_containers/pause-amd64:3.0 Up 30 minutes
protokube:1.8.1

我们可以看到一些与 GCE 集群相同的容器。然而,有几个容器缺失。我们可以看到 Kubernetes 的核心组件,但fluentd-gcp服务缺失,另外一些较新的工具,如node-problem-detectorreschedulerglbckube-addon-manageretcd-empty-dir-cleanup也缺失。这反映了不同公有云提供商之间kube-up脚本的微妙差异。最终,这是由庞大的 Kubernetes 开源社区的努力决定的,但 GCP 通常会首先引入许多最新的功能。

你还可以使用一个命令来检查集群的状态:kops validate cluster,它可以帮助你确保集群按预期运行。Kops 还提供了许多方便的模式,可以让你用输出、配置和集群的提供者做各种操作。

其他模式

还有一些其他模式需要考虑,包括以下几种:

  • 构建 Terraform 模型--target=terraform。Terraform 模型将构建在out/terraform中。

  • 构建 CloudFormation 模型--target=cloudformation。CloudFormation JSON 文件将构建在out/cloudformation中。

  • 指定要运行的 K8s 构建版本--kubernetes-version=1.2.2

  • 在多个可用区运行节点--zones=us-east-1b,us-east-1c,us-east-1d

  • 使用高可用主节点运行--master-zones=us-east-1b,us-east-1c,us-east-1d

  • 指定节点数量--node-count=4

  • 指定节点大小--node-size=m4.large

  • 指定主节点大小--master-size=m4.large

  • 覆盖默认的 DNS 区域--dns-zone=<my.hosted.zone>

完整的 CLI 文档可以在这里找到:github.com/kubernetes/kops/tree/master/docs/cli

另一个诊断集群状态的工具是 componentstatuses 命令,它会告诉你 Kubernetes 主要组件的状态:

$ kubectl get componentstatuses
NAME STATUS MESSAGE ERROR
scheduler Healthy ok
controller-manager Healthy ok
etcd-0 Healthy {"health": "true"}

重置集群

你刚刚体验了在 AWS 上运行集群的过程。接下来本书的例子将基于 GCE 集群。为了获得最佳的跟随体验,你可以轻松地回到 GCE 集群。

只需按以下方式拆除 AWS 集群:

$ kops delete cluster --name ${NAME} --yes

如果省略 --yes 标志,您将看到类似的干运行输出,可以确认后继续。然后,再次使用以下命令创建 GCE 集群,确保回到安装 Kubernetes 代码的目录:

$ cd ~/<kubernetes_install_dir>
$ kube-up.sh

调查其他部署自动化方法

如果你想了解更多关于集群自动化的其他工具,我们建议你访问 kube-deploy 仓库,它包含了社区维护的 Kubernetes 集群部署工具的参考资料。

访问 github.com/kubernetes/kube-deploy 了解更多。

本地替代方案

kube-up.sh 脚本和 kops 是开始在你选择的平台上使用 Kubernetes 的非常方便的方法。然而,它们也不是没有缺陷,有时在条件不完全符合时可能会遇到问题。

幸运的是,自 K8 诞生以来,已经出现了多种替代创建集群的方法。我们特别推荐 Minikube,它是一个非常简单且适用于本地开发的环境,可以用来测试 Kubernetes 配置。

这个项目可以在这里找到:github.com/kubernetes/minikube

需要特别提到的是,运行 Minikube 时,你的机器需要有一个虚拟机管理程序(hypervisor)。对于 Linux,可以使用 kvm/kvm2 或 VirtualBox,macOS 上可以使用原生的 xhyve 或 VirtualBox。对于 Windows,Hyper-V 是默认的虚拟机管理程序。

这个项目的主要限制是它仅运行单个节点,这限制了我们对某些需要多台机器的高级主题的探索。然而,Minikube 对于简单或本地开发来说是一个很好的资源,并且可以通过以下命令在你的 Linux 虚拟机上轻松安装:

$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/

或者按照以下方式在 macOS 上安装:

$ brew cask install minikube

我们将通过以下命令介绍如何开始使用 Minikube:

$ minikube start
Starting local Kubernetes v1.7.5 cluster...
Starting VM...
SSH-ing files into VM...
Setting up certs...
Starting cluster components...
Connecting to cluster...
Setting up kubeconfig...
Kubectl is now configured to use the cluster.

你可以非常简单地创建一个示例部署:

$ kubectl run hello-minikube --image=k8s.gcr.io/echoserver:1.4 --port=8080
deployment "hello-minikube" created
$ kubectl expose deployment hello-minikube --type=NodePort
service "hello-minikube" exposed

一旦您的集群和服务启动并运行,您可以通过使用 kubectl 工具和 context 命令与其进行交互。您可以使用 minikube dashboard 访问 Minikube 仪表盘。

Minikube 由 localkube 提供支持 (github.com/kubernetes/minikube/tree/master/pkg/localkube) 和 libmachine (github.com/docker/machine/tree/master/libmachine) 提供支持。快去看看吧!

此外,我们已经参考了多种托管服务,包括 GKE、EKS 和 Microsoft Azure Container Service (ACS),它们提供自动化安装和一些托管集群操作。我们将在第十四章 强化 Kubernetes 中查看这些服务的演示。

从零开始

最后,也可以选择从零开始。幸运的是,从 1.4 版本开始,Kubernetes 团队将重点放在简化集群设置过程上。为此,他们为 Ubuntu 16.04、CentOS 7 和 HypriotOS v1.0.1+ 引入了 kubeadm 工具。

让我们快速看看如何使用 kubeadm 工具从零开始在 AWS 上部署集群。

集群设置

我们需要预先配置集群的主节点和节点。目前,我们仅限于前面列出的操作系统和版本。此外,建议至少有 1 GB 内存。所有节点必须彼此具有网络连接。

在本教程中,我们将需要一台 t2.medium(主节点)和三台 t2.micro(节点)实例,这些实例具有突发 CPU,且配备了至少 1 GB 内存。我们需要创建一个主节点和三个工作节点。

我们还需要为集群创建一些安全组。主节点需要以下端口:

类型 协议 端口范围 来源
所有流量 所有 所有
所有流量 所有 所有
SSH TCP 22
HTTPS TCP 443

下表显示了端口的节点安全组:

类型 协议 端口范围 来源
所有流量 所有 所有
所有流量 所有 所有
SSH TCP 22

一旦您拥有这些 SG,继续使用 Ubuntu 16.04 启动四个实例(一个 t2.medium 和三个 t2.micro)。如果您是 AWS 新手,请参考以下网址的 EC2 实例启动文档:docs.aws.amazon.com/AWSEC2/latest/UserGuide/LaunchingAndUsingInstances.html

确保将 t2.medium 实例标识为主节点,并关联主节点安全组。将其他三个命名为节点,并将节点安全组与这些实例关联。

这些步骤来自手册中的操作流程。如需更多信息或使用 Ubuntu 以外的系统,请参考 kubernetes.io/docs/getting-started-guides/kubeadm/

安装 Kubernetes 组件(kubelet 和 kubeadm)

接下来,我们需要 SSH 进入所有四个实例并安装 Kubernetes 组件。

作为 root 用户,在所有四个实例上执行以下步骤:

  1. 更新软件包并安装 apt-transport-https 软件包,以便我们能够从使用 HTTPS 的源下载:
 $ apt-get update 
 $ apt-get install -y apt-transport-https
  1. 安装 Google Cloud 公钥:
 $ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg |
   apt-key add -    
  1. 接下来,让我们设置仓库:
 cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
 deb http://apt.kubernetes.io/ kubernetes-xenial main
 EOF
 apt-get update
 apt-get install -y kubelet kubeadm kubectl docker.io kubernetes-cni

您需要确保主节点上 kubelet 使用的 cgroup 驱动程序已正确配置,以便与 Docker 配合使用。请确保您在主节点上,然后运行以下命令:

docker info | grep -i cgroup
cat /etc/systemd/system/kubelet.service.d/10-kubeadm.conf

如果这些项不匹配,您将需要更改 kubelet 配置以与 Docker 驱动程序匹配。运行 sed -i "s/cgroup-driver=systemd/cgroup-driver=cgroupfs/g" /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 应该可以修复设置,或者您也可以手动打开 systemd 文件,并将正确的标志添加到相应的环境中。完成后,重新启动服务:

$ systemctl daemon-reload
$ systemctl restart kubelet

设置主节点

在您之前选择的主节点实例上,我们将执行主节点初始化。再次以 root 身份运行以下命令,您应该看到如下输出:

$ kubeadm init
[init] using Kubernetes version: v1.11.3
[preflight] running pre-flight checks
I1015 02:49:42.378355 5250 kernel_validator.go:81] Validating kernel version
I1015 02:49:42.378609 5250 kernel_validator.go:96] Validating kernel config
[preflight/images] Pulling images required for setting up a Kubernetes cluster
[preflight/images] This might take a minute or two, depending on the speed of your internet connection
[preflight/images] You can also perform this action in beforehand using 'kubeadm config images pull'
[kubelet] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[preflight] Activating the kubelet service
[certificates] Generated ca certificate and key.
[certificates] Generated apiserver certificate and key.
[certificates] apiserver serving cert is signed for DNS names [master kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.cluster.local] and IPs [10.96.0.1 172.17.0.71]
[certificates] Generated apiserver-kubelet-client certificate and key.
[certificates] Generated sa key and public key.
[certificates] Generated front-proxy-ca certificate and key.
[certificates] Generated front-proxy-client certificate and key.
[certificates] Generated etcd/ca certificate and key.
[certificates] Generated etcd/server certificate and key.
[certificates] etcd/server serving cert is signed for DNS names [master localhost] and IPs [127.0.0.1 ::1]
[certificates] Generated etcd/peer certificate and key.
[certificates] etcd/peer serving cert is signed for DNS names [master localhost] and IPs [172.17.0.71 127.0.0.1 ::1]
[certificates] Generated etcd/healthcheck-client certificate and key.
[certificates] Generated apiserver-etcd-client certificate and key.
[certificates] valid certificates and keys now exist in "/etc/kubernetes/pki"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/admin.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/kubelet.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/controller-manager.conf"
[kubeconfig] Wrote KubeConfig file to disk: "/etc/kubernetes/scheduler.conf"
[controlplane] wrote Static Pod manifest for component kube-apiserver to "/etc/kubernetes/manifests/kube-apiserver.yaml"
[controlplane] wrote Static Pod manifest for component kube-controller-manager to "/etc/kubernetes/manifests/kube-controller-manager.yaml"
[controlplane] wrote Static Pod manifest for component kube-scheduler to "/etc/kubernetes/manifests/kube-scheduler.yaml"
[etcd] Wrote Static Pod manifest for a local etcd instance to "/etc/kubernetes/manifests/etcd.yaml"
[init] waiting for the kubelet to boot up the control plane as Static Pods from directory "/etc/kubernetes/manifests"
[init] this might take a minute or longer if the control plane images have to be pulled
[apiclient] All control plane components are healthy after 43.001889 seconds
[uploadconfig] storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace
[kubelet] Creating a ConfigMap "kubelet-config-1.11" in namespace kube-system with the configuration for the kubelets in the cluster
[markmaster] Marking the node master as master by adding the label "node-role.kubernetes.io/master=''"
[markmaster] Marking the node master as master by adding the taints [node-role.kubernetes.io/master:NoSchedule]
[patchnode] Uploading the CRI Socket information "/var/run/dockershim.sock" to the Node API object "master" as an annotation
[bootstraptoken] using token: o760dk.q4l5au0jyx4vg6hr
[bootstraptoken] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials
[bootstraptoken] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstraptoken] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstraptoken] creating the "cluster-info" ConfigMap in the "kube-public" namespace
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of machines by running the following on each node
as root:

  kubeadm join 172.17.0.71:6443 --token o760dk.q4l5au0jyx4vg6hr --discovery-token-ca-cert-hash sha256:453e2964eb9cc0cecfdb167194f60c6f7bd8894dc3913e0034bf0b33af4f40f5 

要开始使用您的集群,您需要以普通用户身份运行:

mkdir -p $HOME/.kube
 sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
 sudo chown $(id -u):$(id -g) $HOME/.kube/config

现在,您应该为集群部署一个 Pod 网络。使用以下命令 kubectl apply -f [podnetwork].yaml,并选择在 kubernetes.io/docs/concepts/cluster-administration/addons/ 列出的选项之一。

现在,您可以通过在每个节点上运行以下命令来加入任意数量的机器:

以 root 用户身份执行:

kubeadm join --token <token> <master-ip>:<master-port> --discovery-token-ca-cert-hash sha256:<hash>

请注意,初始化只能运行一次,因此如果遇到问题,您需要使用kubeadm reset

加入节点

初始化成功后,您将获得一个 join 命令,该命令可供节点使用。请记下该命令以便稍后用于加入过程。它应类似于以下内容:

$ kubeadm join --token=<some token> <master ip address>

该令牌用于身份验证集群节点,因此请确保将其安全存储以备将来使用。

网络配置

我们的集群需要一个网络层,以便 Pod 之间进行通信。请注意,kubeadm 要求使用兼容 CNI 的网络架构。当前可用的插件列表可以在这里找到:kubernetes.io/docs/admin/addons/

对于我们的示例,我们将使用 Calico。我们将需要使用以下 yaml 在集群上创建 Calico 组件。为了方便,你可以在这里下载它:docs.projectcalico.org/v1.6/getting-started/kubernetes/installation/hosted/kubeadm/calico.yaml

当你将此文件放到主节点上后,使用以下命令创建组件:

$ kubectl apply -f calico.yaml

给它一点时间来运行设置,然后列出 kube-system 节点以检查:

$ kubectl get pods --namespace=kube-system

你应该得到一个类似以下的列表示,包含三个新的 Calico pods 和一个未显示的已完成作业:

Calico 设置

加入集群

现在,我们需要在每个节点实例上运行之前复制的 join 命令:

$ kubeadm join --token=<some token> <master ip address>

完成后,你应该能通过运行以下命令从主节点查看所有节点:

$ kubectl get nodes

如果一切顺利,你将看到三个节点和一个主节点,如下所示:

总结

我们简要了解了容器是如何工作的,以及它们如何适应微服务中的新架构模式。你现在应该对这两种力量如何需要各种操作和管理任务有了更好的理解,也应该了解 Kubernetes 如何提供强大的功能来解决这些挑战。我们在 GCE 和 AWS 上创建了两个不同的集群,探讨了启动脚本以及 Kubernetes 的一些内置功能。最后,我们查看了 kops 中 kube-up 脚本的替代方法,并尝试使用 AWS 上的 Ubuntu 16.04 和 kubeadm 工具进行手动集群配置。

在下一章,我们将探讨 K8s 提供的核心概念和抽象,用于管理容器和完整的应用程序堆栈。我们还将了解基本的调度、服务发现和健康检查。

问题

  1. 列出三个可以轻松部署 Kubernetes 集群的地方。

  2. 在容器之前,还有哪些其他类型的虚拟化技术?

  3. 尽可能列出所有的 cgroup 控制!

  4. 为什么启用 CI/CD 与容器结合对组织来说如此重要?

  5. 在 AWS 或 GCE 上启动和运行 Kubernetes 集群需要哪些前提条件?

  6. 列出 Kubernetes 主节点上运行的四个服务。提示:这些是容器。

  7. kube-up.sh 脚本有哪些替代方案?

  8. 用于从头构建 Kubernetes 集群的工具是什么?

进一步阅读

想了解更多关于 Kubernetes 上的 DevOps 实践吗?查看 DevOps with Kuberneteswww.packtpub.com/virtualization-and-cloud/devops-kubernetes

你还可以通过Kubernetes Cookbook了解不同的应用和自动化方法:www.packtpub.com/virtualization-and-cloud/kubernetes-cookbook

第二章:使用核心 Kubernetes 构建块构建基础

本章将介绍核心的 Kubernetes 构建块,主要包括 pods、services、replication controllers、replica sets 和 labels。我们将描述 Kubernetes 组件、API 的维度以及 Kubernetes 对象。我们还将深入探讨主要的 Kubernetes 集群组件。章节中将包括一些简单的应用示例,以演示每个构建块的使用。本章还将介绍集群的基本操作。最后,我们将通过一些示例介绍健康检查和调度。

本章将涵盖以下主题:

  • Kubernetes 的整体架构

  • Kubernetes 架构在系统理论中的背景

  • 核心 Kubernetes 构建块、架构和组件介绍

  • 标签如何简化 Kubernetes 集群的管理

  • 监控服务和容器健康

  • 基于可用集群资源设置调度约束

技术要求

你需要启用并登录你的 Google Cloud Platform 账户,或者你可以使用本地的 Minikube Kubernetes 实例。你还可以通过网络使用 Play with Kubernetes:labs.play-with-k8s.com/

这是本章的 GitHub 仓库:github.com/PacktPublishing/Getting-Started-with-Kubernetes-third-edition/tree/master/Code-files/Chapter02

Kubernetes 系统

要理解 Kubernetes 的复杂架构和组件,我们应该退后一步,从整体系统的角度来审视,以便理解每个活动部分的背景和位置。本书主要聚焦于 Kubernetes 软件的技术部分和过程,但让我们从自上而下的视角来审视整个系统。在下面的图中,你可以看到 Kubernetes 系统的主要部分,这是思考本书中将描述和使用的部分分类的好方法:

让我们从底层开始,逐一查看每个部分。

核心

Kubernetes 系统的核心致力于提供一个标准的 API 和操作方式,使得操作员和/或软件可以在集群上执行工作。核心是构建上层功能所需的最基本的功能集,这些功能应该被认为是绝对稳定的。这个层级的每一部分都有明确的文档,这些部分是构建更高级概念所必需的。你可以将这里的 API 视为 Kubernetes 控制平面的核心部分。

集群控制平面是 Kubernetes 核心的前半部分,它提供了 RESTful API,允许操作员执行集群中大多数基于 CRUD 的操作。需要注意的是,Kubernetes 核心,进而是集群控制平面,是在多租户的考虑下构建的,因此该层必须足够灵活,以便在单个集群内提供团队或工作负载的逻辑隔离。集群控制平面遵循 API 约定,允许其利用身份和审计等共享服务,并可以访问集群的命名空间和事件。

核心的后半部分是执行。虽然 Kubernetes 中有许多控制器,如副本控制器、复制集和部署,但 kubelet 是最重要的控制器,它构成了节点和 Pod API 的基础,允许我们与容器执行层进行交互。Kubernetes 基于 kubelet 构建了 Pod 的概念,Pod 允许我们管理多个容器及其组成存储,这是系统的核心功能。稍后我们会深入探讨 Pod。

在核心下方,我们可以看到 kubelet 依赖的各种组件,以管理容器、网络、容器存储、镜像存储、云提供商和身份。我们故意将这些描述得比较模糊,因为每个组件都有多个选项,你可以从标准和流行的实现中进行选择,或是尝试一些新兴技术。为了让你了解基础层的选项有多少,我们将在这里概述容器运行时和网络插件选项。

容器运行时选项:你将使用 Kubernetes 的容器运行时接口CRI)与两种主要的容器运行时进行交互:

  • containerd

  • rkt

到目前为止,你仍然可以在 Kubernetes 上运行 Docker 容器,并且由于 containerd 是默认的运行时,它对操作员是透明的。你可以在集群中运行所有相同的docker <action>命令,来检查并收集关于集群的信息。

还有几种竞争性的、正在兴起的格式:

您可以在此处阅读有关 CRI 的更多信息:blog.kubernetes.io/2016/12/container-runtime-interface-cri-in-kubernetes.html

网络插件:如果您打算依赖云提供商的网络分割,或者将在单节点集群上运行,可以使用 CNI 来利用以下任何插件或简单的 Kubenet 网络实现:

  • 硅青

  • Contiv

  • 尾迹

  • 绒布

  • Kube-router

  • 多路

  • Calico

  • 罗曼娜

  • 编织网

应用层

应用层,通常称为服务组件或编排层,在 Kubernetes 中执行我们非常重视的所有有趣的事情:基本部署和路由、服务发现、负载平衡和自愈。为了集群运营商能够管理集群的生命周期,这些基本组件必须在此层中存在且功能正常。大多数容器化应用程序将依赖于此层的全部功能,并将与这些功能交互,以在多个集群主机上提供应用程序的“编排”。当应用程序扩展或更改配置设置时,应用层将由此层管理。应用层关心集群的期望状态、应用组合、服务发现、负载平衡和路由,并利用所有这些部件将数据从正确的 A 点流向正确的 B 点。

治理层

治理层由高级自动化和策略强制执行组成。这一层可以被视为应用管理层的有主见的版本,因为它提供了强制租户、收集度量、智能提供和容器自动扩展的能力。此层的 API 应被视为运行容器化应用程序的选项。

治理层允许运营商控制授权方法的使用,以及围绕网络和存储的配额和控制。在此层级,功能应适用于大型企业关心的场景,如运营、安全和合规性场景。

接口层

接口层由其他自定义 Kubernetes 分发可能使用的常用工具、系统、用户界面和库组成。kubectl 库是接口层的一个很好的例子,重要的是它不被视为 Kubernetes 系统中的特权部分;它被认为是一个客户端工具,以便为 Kubernetes API 提供最大的灵活性。如果运行 $ kubectl -h,您将清楚地了解到暴露给接口层的功能。

这一层的其他组件包括集群联合工具、仪表盘、Helm 以及 client-nodeKubernetesClientpython 等客户端库。这些工具为你提供常见任务的支持,比如你不需要为身份验证编写代码等。这些库使用 Kubernetes 服务账户对集群进行身份验证。

生态系统

Kubernetes 系统的最后一层是生态系统,这是迄今为止最繁忙和最紧张的部分。Kubernetes 对容器编排和管理的方式是为用户提供互补选择;外部系统可以利用可用的插件和通用 API。可以将 Kubernetes 系统中的生态系统组件分为三种类型:

  • Kubernetes 之上: 所有“让事情运行起来”的粘合软件和基础设施都位于这一层,包括操作理念,如 ChatOps 和 DevOps、日志记录和监控、持续集成和交付、大数据系统以及函数即服务。

  • Kubernetes 内部: 简而言之,容器内部的内容不属于 Kubernetes。Kubernetes,或 K8s,完全不关心你在容器内运行什么。

  • Kubernetes 之下: 这些是图表底部详细描述的灰色方块。你需要为每一块基础技术配备相应的技术,才能使 Kubernetes 正常运行,生态系统就是你获得这些技术的地方。集群状态存储可能是最著名的生态系统组件之一:etcd。集群引导工具,如 minikubebootkubekopskube-awskubernetes-anywhere,是其他社区提供的生态系统工具示例。

既然我们已经了解了更大的背景,现在让我们继续了解 Kubernetes 系统的架构。

架构

尽管容器为应用程序管理带来了有益的抽象层和工具,但 Kubernetes 提供了额外的功能,用于在大规模下调度和编排容器,同时管理完整的应用生命周期。

K8s 向上移动堆栈,提供了处理应用程序或服务级别管理的构建块。这为我们提供了自动化和工具,确保高可用性、应用程序堆栈和服务范围的可移植性。K8s 还允许更细粒度的资源使用控制,如跨基础设施的 CPU、内存和磁盘空间。

Kubernetes 架构由三个主要部分组成:

  • 集群控制平面(master

  • 集群状态(一个名为 etcd 的分布式存储系统)

  • 集群节点(运行代理程序 kubelets 的独立服务器)

Master

集群控制平面,也称为 Master,根据集群的当前状态和期望状态做出全局决策,并响应在集群中传播的事件。这包括当复制控制器的复制因子不满足时启动和停止 pod,或运行计划任务。

控制平面的总体目标是报告并朝着期望的状态努力。Master 节点运行的 API 依赖于持久状态存储 etcd,并利用 watch 策略来最小化变化延迟,同时启用去中心化组件协调。

Master 的组件可以实际运行在集群中的任何机器上,但最佳实践和生产就绪的系统要求将 Master 组件放置在一台机器上(或使用多主机高可用配置)。将所有 Master 组件运行在一台机器上,可以让操作员避免在这些机器上运行用户容器,这样做能提供更可靠的控制平面操作。Master 节点上运行的组件越少,越好!

当我们深入了解 Master 节点时,会详细介绍 Master 组件,包括 kube-apiserver、etcd、kube-schedulerkube-controller-managercloud-controller-manager。需要注意的是,Kubernetes 设计这些组件的目标是提供一个针对主要持久存储资源的 RESTful API,并采用 CRUD(创建、读取、更新、删除)策略。稍后在本章中,我们会讨论与容器特定编排和调度相关的基本原语,当我们学习服务、入口、Pod、部署、StatefulSet、CronJobs 和 ReplicaSets 时。

集群状态

Kubernetes 架构中的第二个主要部分是集群状态,即 etcd 键值存储。etcd 是一致的且高度可用的,旨在快速且可靠地为 Kubernetes 提供集群的当前和期望状态的访问。etcd 能通过领导选举和分布式锁等核心概念提供这种分布式数据协调。Kubernetes API 通过其 API 服务器负责更新与集群 RESTful 操作对应的 etcd 中的对象。记住这一点非常重要:API 服务器负责管理 Kubernetes 中世界的“图像”。该生态系统中的其他组件监视 etcd 的变化,以便修改自身并进入期望的状态。

这点特别重要,因为我们在 Kubernetes Master 中描述的每个组件,以及在下面节点中调查的那些组件,都是无状态的,这意味着它们的状态存储在其他地方,而那个“其他地方”就是 etcd。

Kubernetes 并不会对集群中的事情采取具体的行动;Kubernetes API 通过 API 服务器将应该实现的内容写入 etcd,然后 Kubernetes 的各个组件会将其实现。etcd 通过一个简单的 HTTP/JSON API 提供了这个接口,这使得与它的交互非常简单。

etcd 在考虑 Kubernetes 安全模型时也非常重要,因为它存在于 Kubernetes 系统的非常底层,这意味着任何能够向 etcd 写入数据的组件都拥有 root 权限访问集群。稍后,我们将探讨如何将 Kubernetes 系统划分为多个层次,以最大限度地减少这种暴露。你可以将 etcd 看作是 Kubernetes 的基础层,同时还涉及到容器运行时、镜像注册表、文件存储、云服务提供商接口以及其他 Kubernetes 管理但没有明确意见的依赖项。

在非生产环境的 Kubernetes 集群中,你会看到 etcd 的单节点实例化,这是为了节省计算成本、简化操作或减少复杂性。然而,需要特别注意的是,生产就绪集群的多主节点策略(2n+1 节点)是至关重要的,这样才能有效地在主节点之间复制数据并确保容错性。建议你查看 etcd 文档以获取更多信息。

查看 etcd 文档,请点击:github.com/coreos/etcd/blob/master/Documentation/docs.md

如果你站在集群前,你可以通过检查 componentsta`tus`escs 来查看 etcd 的状态:

[node3 /]$ kubectl get componentstatuses
NAME                 STATUS MESSAGE          ERROR
scheduler            Healthy ok
controller-manager   Healthy ok
etcd-0               Healthy {"health": "true"}

由于 AKS 生态系统中的一个 bug,目前在 Azure 上无法使用。你可以在这里跟踪该问题,以查看何时解决:

github.com/Azure/AKS/issues/173: kubectl get componentstatus fails for scheduler and controller-manager #173

如果你看到一个不健康的 etcd 服务,它可能会像这样:

[node3 /]$ kubectl get cs

NAME                  STATUS       MESSAGE      ERROR
etcd-0                Unhealthy                 Get http://127.0.0.1:2379/health: dial tcp 127.0.0.1:2379: getsockopt: connection refused
controller-manager    Healthy      ok
scheduler             Healthy      ok

集群节点

第三个也是最后一个主要的 Kubernetes 组件是集群节点。虽然主节点组件仅在 Kubernetes 集群的一个子集上运行,但节点组件会在所有地方运行;它们负责管理运行中的 pod、容器和其他原语,并提供运行时环境。节点组件有三个:

  • Kubelet

  • Kube-proxy

  • 容器运行时

我们稍后会详细了解这些组件的具体情况,但首先要注意几点关于节点组件的内容。kubelet 可以被看作是 Kubernetes 中的主要控制器,它提供了由容器运行时执行容器功能所使用的 pod/node API。该功能通过容器及其对应的存储卷组成 pod 的概念。Pod 的概念为应用开发者提供了一个直观的打包范式,从而设计他们的应用,并让我们最大化容器的可移植性,同时实现跨集群多个实例的调度与编排能力。

有趣的是,许多 Kubernetes 组件是运行在 Kubernetes 本身上的(换句话说,是由 kubelets 驱动的),包括 DNS、Ingress、Dashboard 以及 Heapster 的资源监控:

Kubernetes 核心架构

在前面的图示中,我们看到 Kubernetes 的核心架构。大多数管理交互都是通过 kubectl 脚本和/或对 API 的 RESTful 服务调用来完成的。

如前所述,请仔细注意期望状态与实际状态的概念。这是 Kubernetes 如何管理集群及其工作负载的关键。K8s 的所有组件始终在不断监视当前的实际状态,并与通过 API 服务器或 kubectl 脚本由管理员定义的期望状态进行同步。有时这些状态可能不匹配,但系统始终在努力使两者一致。

让我们更深入地探讨 Master 和节点实例。

Master

我们现在知道 Master 是集群的大脑。它拥有核心 API 服务器,维护 RESTful Web 服务用于查询和定义我们期望的集群和工作负载状态。需要注意的是,控制平面仅通过 Master 节点来发起更改,而不是直接访问节点。

此外,Master 节点还包括 调度器。复制控制器/副本集与 API 服务器协作,确保在任何给定时刻都运行正确数量的 Pod 副本。这是期望状态概念的一个典范。如果我们的复制控制器/副本集定义了三个副本,而实际状态是只有两个 Pod 副本在运行,那么调度器会被触发,向集群中添加一个新的 Pod。同样的,如果集群中某一时刻有过多的 Pod 运行,调度器也会进行调整。通过这种方式,K8s 始终在朝着期望状态推进。

如前所述,我们将更详细地了解每个 Master 组件。kube-apiserver 负责为集群提供 API,作为 Master 提供的控制平面的前端。实际上,apiserver 是通过一个名为 kubernetes 的服务进行暴露的,我们使用 kubelet 安装 API 服务器。此服务通过 kube-apiserver.yaml 文件进行配置,该文件位于每个管理节点的 /etc/kubernetes/manifests/ 目录下。

kube-apiserver 是 Kubernetes 高可用性的重要组成部分,因此它被设计为水平扩展。我们将在本书后面讨论如何构建高可用集群,但可以简单地说,你需要将 kube-apiserver 容器分布在多个 Master 节点上,并在前端提供一个负载均衡器。

由于我们已经详细讨论了集群状态存储,现在可以简单地说,etcd 代理正在所有 Master 节点上运行。

下一个组件是 kube-scheduler,它确保所有的 Pod 都被关联并分配到节点上进行操作。调度器与 API 服务器协同工作,在实际的 minion 节点上调度以 Pod 形式运行的工作负载。这些 Pod 包括构成我们应用栈的各种容器。默认情况下,基础的 Kubernetes 调度器会将 Pod 分布到集群中的不同节点,并使用不同的节点来匹配 Pod 副本。Kubernetes 还允许为每个容器指定必要的资源、硬件和软件策略约束、亲和性或反亲和性要求,以及数据卷的位置,以便调度可以根据这些附加因素进行调整。

主节点的最后两个主要部分是 kube-controller-managercloud-controller-manager。正如你从它们的名称中可以猜到的那样,虽然这两个服务都在容器编排和调度中扮演重要角色,kube-controller-manager 有助于编排 Kubernetes 的核心内部组件,而 cloud-controller-manager 则与不同的供应商及其云提供商的 API 进行交互。

kube-controller-manager 实际上是一个 Kubernetes 守护进程,它嵌入了 Kubernetes 的核心控制循环,也就是所谓的控制器:

  • Node 控制器,用于管理 Pod 的可用性并在 Pod 停止时进行管理

  • Replication 控制器,确保系统中每个复制控制器对象都有正确数量的 Pod

  • Endpoints 控制器,控制 API 中的端点记录,从而管理为支持定义选择器的服务提供 Pod 或一组 Pod 的 DNS 解析

为了减少控制器组件的复杂性,它们都被打包并通过这个单一的守护进程作为 kube-controller-manager 来运行。

cloud-controller-manager 则关注外部组件,并运行与您的集群所使用的云提供商特定的控制器循环。此设计的最初目的是将 Kubernetes 的内部开发与云供应商特定的代码解耦。这是通过使用插件来实现的,插件防止了 Kubernetes 依赖于那些与其价值主张无关的代码。我们可以预期,随着时间的推移,未来版本的 Kubernetes 将完全将供应商特定的代码从 Kubernetes 代码库中移除,且这些供应商特定的代码将由供应商自身维护,之后由 Kubernetes cloud-controller-manager 调用。这一设计避免了多个 Kubernetes 组件与云提供商之间的通信需求,具体包括 kubelet、Kubernetes 控制器管理器和 API 服务器。

节点(以前称为 minions)

在每个节点中,我们有几个已提到的组件。让我们详细看一下每个组件。

kubelet 与 API 服务器交互以更新状态,并启动由调度器调用的新工作负载。如前所述,这个代理会在集群的每个节点上运行。kubelet 的主要接口是一个或多个 PodSpecs,确保容器和配置的健康性。

kube-proxy 提供基本的负载均衡,并将目标服务的流量引导到后台的正确 Pod。它维护这些网络规则,以通过连接转发实现服务抽象。

节点的最后一个主要组件是容器运行时,它负责启动、运行和停止容器。Kubernetes 生态系统引入了 OCI 运行时规范,旨在让容器调度器/协调器接口变得更加开放。虽然 Docker、rkt 和 runc 是目前的主要实现,但 OCI 的目标是提供一个通用接口,让你可以使用自己的运行时。目前,Docker 是压倒性的主流运行时。

在这里阅读更多关于 OCI 运行时规范的信息:github.com/opencontainers/runtime-spec

在你的集群中,节点可以是虚拟机或裸金属硬件。与控制器和 Pod 等其他项相比,节点并不是 Kubernetes 创建的抽象对象。相反,Kubernetes 使用 cloud-controller-manager 与云提供商的 API 交互,云提供商负责节点的生命周期管理。这意味着当我们在 Kubernetes 中实例化一个节点时,我们只是创建了一个代表你给定基础设施中机器的对象。由 Kubernetes 来决定节点是否与该对象定义一致。Kubernetes 通过节点的 IP 地址来验证节点的可用性,IP 地址通过 metadata.name 字段收集。你可以通过以下状态键来发现这些节点的状态。

地址是我们可以找到主机名、私有和公有 IP 等信息的地方。这些信息会根据你的云提供商的实现而有所不同。condition 字段将提供节点状态的视图,涵盖磁盘、内存、网络和基本配置等方面。

以下是可用的节点状态条件表:

一个健康的节点如果运行,会显示类似于以下的状态,你将看到如下输出:

$ kubectl get nodes -o json
"conditions": [
  {
    "type": "Ready",
    "status": "True"
  }
]

容量 很简单:它指的是在给定节点上可用的 CPU、内存,以及可运行的 Pod 数量。节点自报告其容量,并将调度适当数量资源的责任交给 Kubernetes。Info 键也很直白,提供了 Docker、操作系统和 Kubernetes 的版本信息。

需要注意的是,Kubernetes 和节点之间关系的主要组件是节点控制器,我们之前提到过它是核心系统控制器之一。这个关系中有三个战略性元素:

  • 节点健康:当您在私有、公共或混合云场景中运行大规模集群时,您不可避免地会失去一些机器。即使是在数据中心,考虑到集群足够大,您也会定期看到大规模的故障。节点控制器负责更新节点的 NodeStatus,将其状态设置为 NodeReadyConditionUnknown,具体取决于实例的可用性。这个管理非常重要,因为如果发生 ConditionUnknown,Kubernetes 将需要将 pods(从而是容器)迁移到可用的节点上。您可以通过 --node-monitor-period 设置集群中节点的健康检查间隔。

  • IP 分配:每个节点需要一些 IP 地址,以便它可以将 IP 分配给服务和/或容器。

  • 节点列表:为了在多台机器上管理 pods,我们需要保持一个最新的可用机器列表。基于前述的 NodeStatus,节点控制器将保持此列表的更新。

当我们研究跨可用区AZs)的高可用集群时,将会深入了解节点控制器的具体情况,因为这需要将节点跨 AZs 分布以提供可用性。

最后,我们有一些默认的 pods,它们为节点运行各种基础设施服务。正如我们在上一章中简要探讨过的,pods 包括域名系统DNS)、日志记录和 pod 健康检查服务。默认 pod 将与我们调度的 pods 一起在每个节点上运行。

在 v1.0 版本中,minion 被重命名为节点,但在某些机器命名脚本和网络上的文档中,仍然可以看到 minion 这个术语的痕迹。为了清晰起见,我在本书中的某些地方同时加入了 minion 和节点这两个术语。

核心构件

现在,让我们深入一些,探索 Kubernetes 提供的一些核心抽象。这些抽象将使我们更容易思考我们的应用程序,并减轻生命周期管理、高可用性和调度的负担。

Pods

Pods 允许您将相关的容器在网络和硬件基础设施上保持接近。数据可以存放在应用程序附近,因此可以在不产生高延迟的情况下进行处理。同样,常用数据可以存储在多个容器共享的卷上。Pods 本质上允许您将容器和应用程序栈的各个部分进行逻辑分组。

虽然 pods 内可以运行一个或多个容器,但 pod 本身可能是 Kubernetes 节点(minion)上运行的多个 pod 之一。正如我们所看到的,pods 给我们提供了一个容器的逻辑分组,基于这些容器我们可以进行复制、调度和负载均衡服务端点。

Pod 示例

让我们快速看一下 Pod 的实际操作。我们将在集群上启动一个 Node.js 应用程序。你需要一个正在运行的 GCE 集群;如果你还没有启动一个,请参阅第一章中的我们的第一个集群部分,Kubernetes 简介

现在,让我们为我们的定义创建一个目录。在这个示例中,我将在我们主目录下的/book-examples子文件夹中创建一个文件夹:

$ mkdir book-examples
$ cd book-examples
$ mkdir 02_example
$ cd 02_example

你可以从你的账户中下载示例代码文件,访问www.packtpub.com,这是你购买的所有 Packt Publishing 书籍的下载地址。如果你是从其他地方购买的这本书,你可以访问www.packtpub.com/support并注册以便直接通过邮件接收文件。

使用你喜欢的编辑器创建以下文件,并命名为nodejs-pod.yaml

apiVersion: v1 
kind: Pod 
metadata: 
  name: node-js-pod 
spec: 
  containers: 
  - name: node-js-pod 
    image: bitnami/apache:latest 
    ports: 
    - containerPort: 80

该文件创建了一个名为node-js-pod的 Pod,并在端口80上运行最新的bitnami/apache容器。我们可以使用以下命令检查这一点:

$ kubectl create -f nodejs-pod.yaml
pod "node-js-pod" created 

这将为我们提供一个运行指定容器的 Pod。我们可以通过运行以下命令查看更多关于 Pod 的信息:

$ kubectl describe pods/node-js-pod

你将看到大量信息,如 Pod 的状态、IP 地址,甚至相关的日志事件。你会注意到 Pod 的 IP 地址是私有 IP 地址,因此我们不能直接从本地机器访问它。别担心,因为kubectl exec命令类似于 Docker 的exec功能。你可以通过多种方式获取 Pod 的 IP 地址。简单的get命令可以显示 Pod 的 IP 地址,我们使用模板输出来查找状态输出中的 IP 地址:

$ kubectl get pod node-js-pod --template={{.status.podIP}}

你可以直接使用该 IP 地址,或者执行该命令并用反引号括起来,exec进入 Pod。一旦 Pod 显示为运行状态,我们可以使用此功能在 Pod 内部运行命令:

$ kubectl exec node-js-pod -- curl <private ip address>

--or--
 $ kubectl exec node-js-pod -- curl `kubectl get pod node-js-pod --template={{.status.podIP}}`

默认情况下,这将在它找到的第一个容器中运行命令,但你可以使用-c参数选择特定的容器。

运行该命令后,你应该会看到一些 HTML 代码。我们将在本章稍后的部分看到更美观的视图,但现在我们可以看到我们的 Pod 确实按预期运行。

如果你有容器使用经验,你可能也曾经执行过' exec'命令。你可以在 Kubernetes 中做类似的操作:

master $ kubectl exec -it node-js-pod -- /bin/bash
root@node-js-pod:/opt/bitnami/apache/htdocs# exit
master $ 

你也可以直接使用exec命令在容器中运行其他命令。请注意,如果命令的参数与kubectl中的参数相同,你需要使用两个破折号来分隔命令的参数:

$ kubectl exec node-js-pod ls / 
$ kubectl exec node-js-pod ps aux
$ kubectl exec node-js-pod -- uname -a

标签

标签为我们提供了另一层次的分类,这在日常操作和管理中非常有帮助。类似于标签,标签可以作为服务发现的基础,以及日常操作和管理任务的有用分组工具。标签附加在 Kubernetes 对象上,并且是简单的键值对。你会在 pods、复制控制器、复制集、服务等上看到它们。标签本身以及其中的键/值是基于一组受限变量的,因此可以使用优化的算法和数据结构高效地评估对它们的查询。

标签向 Kubernetes 指示应在哪些资源上进行各种操作。可以将其视为一种筛选选项。需要注意的是,标签旨在对操作员和应用程序开发人员有意义且可用,但并不意味着对集群有任何语义定义。标签用于组织和选择对象的子集,并可以在创建时添加到对象中,也可以在集群操作过程中随时修改。标签用于管理目的,例如当你想知道某个特定服务的所有后端容器时,通常可以通过与该服务对应的容器上的标签来获取它们。使用这种管理方式时,你通常会在一个对象上看到多个标签。

Kubernetes 集群管理通常是一项跨领域的操作,涉及不同资源和服务的扩展,多个存储设备和数十个节点的管理,因此是一项高度多维的操作。

标签允许 Kubernetes 对象的水平、垂直和对角封装。你经常会看到如下标签:

  • environment: dev, environment: integration, environment: staging, environment: UAT, environment: production

  • tier: web, tier: stateless, tier: stateful, tier: protected

  • tenancy: org1, tenancy: org2

一旦你掌握了标签,就可以使用选择器根据特定的标签组合识别一组新对象。目前有基于等式的选择器和基于集合的选择器。基于等式的选择器允许操作员按键/值对进行筛选,为了select(or)一个对象,它必须满足所有指定的约束。这种选择器通常用于选择特定的节点,例如可能需要与特别快速的存储一起运行。基于集合的选择器更复杂,允许操作员根据特定的值筛选键。这种选择器通常用于确定一个对象属于哪里,例如一个层次、租户区域或环境。

简而言之,一个对象可以附加多个标签,但选择器可以为对象或对象集提供唯一性。

我们将在本章后面更深入地探讨标签,但首先我们将探讨剩下的三个构造:服务、复制控制器和副本集。

容器的余生

正如 AWS 首席技术官 Werner Vogels 所说的,一切都在不断地失败;容器和 pod 可能会崩溃、损坏,或者甚至可能仅仅因为一个笨拙的管理员在某个节点上乱摸而被意外关闭。强有力的策略和安全实践,如实施最小权限,能够减少一些此类事件,但工作负载的意外终止是不可避免的,它是运维的事实之一。

幸运的是,Kubernetes 提供了两个非常有价值的构造,能够将这一严肃的事务整理得井井有条。服务和复制控制器/副本集让我们能够在几乎没有中断的情况下保持应用程序运行,并实现优雅的恢复。

服务

服务允许我们将访问抽象化,消费者可以通过可靠的端点访问运行在集群中的 pod,用户和其他程序可以无缝访问。这与我们 Kubernetes 的核心构造之一——pod,正好相反。

Pod 的定义是短暂的,当它们死掉时不会被复活。如果我们相信复制控制器会根据需要创建和销毁 pod,那么我们还需要另一个构造来创建逻辑上的分离和访问策略。

在这里,我们有服务,它们使用标签选择器来定位一组不断变化的 pod。服务非常重要,因为我们希望前端服务不需要关心后端服务的具体细节,反之亦然。尽管组成这些层的 pod 是可替换的,但通过 ReplicationControllers 管理对象之间的关系的服务,使得不同类型的应用得以解耦。

对于需要 IP 地址的应用程序,提供了虚拟 IP(VIP),它可以将流量以轮询方式转发到后端 pod。对于云原生应用或微服务,Kubernetes 提供了 Endpoints API,用于简化服务之间的通信。

K8s 通过确保集群中的每个节点都运行一个名为 kube-proxy 的代理来实现这一点。顾名思义,kube-proxy 的工作是将服务端点的通信代理回运行实际应用程序的对应 pod:

kube-proxy 架构

服务负载均衡池的成员资格由选择器和标签来决定。具有匹配标签的 pod 会被添加到候选列表中,服务将流量转发到这些 pod。虚拟 IP 地址和端口用作服务的入口点,然后流量会被转发到由 K8s 或你的定义文件所定义的目标端口上的随机 pod。

服务定义的更新会被 K8s 集群主节点监控并协调,并传播到每个节点上运行的 kube-proxy 守护进程

此时,kube-proxy 正在节点主机上运行。未来有计划将其与 kubelet 默认容器化。

服务是一个 RESTful 对象,它依赖于通过 POST 事务向 apiserver 创建 Kubernetes 对象的新实例。以下是一个名为 service-example.yaml 的简单服务示例:

kind: Service
apiVersion: v1
metadata:
  name: gsw-k8s-3-service
spec:
  selector:
    app: gswk8sApp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080

这会创建一个名为 gsw-k8s-3-service 的服务,开放目标端口 8080,并带有 app:gswk8sApp 的键值标签。虽然选择器会被控制器持续评估,但 IP 地址分配的结果(也称为集群 IP)会被发布到 gsw-k8s-3-service 的端点对象。kind 字段是必需的,ports 也是必需的,而 selectortype 是可选的。

除了之前提到的策略外,Kube-proxy 还运行其他形式的虚拟 IP 服务。我们将在后续章节中介绍三种不同的代理模式:

  • 用户空间

  • Iptables

  • Ipvs

副本控制器和副本集

副本控制器已被弃用,推荐使用部署(Deployments),该方式配置副本集(ReplicaSets)。这种方法是一种更强大的应用复制方式,回应了容器运行社区的反馈。我们将在第四章中进一步探讨部署、作业、副本集、守护进程集和有状态集,实现可靠的容器原生应用程序。以下信息仅供参考。

副本控制器RCs)顾名思义,管理一个 Pod 和其中包含的容器镜像所运行的节点数量。它们确保镜像的实例以指定的副本数运行。RCs 确保一个或多个相同的 Pod 始终处于运行状态并可为应用流量提供服务。

当你开始操作容器和 Pod 时,你需要一种方法来推出更新,扩展正在运行的副本数量(无论是扩展还是缩减),或者确保至少有一个堆栈实例始终在运行。RCs 提供了一种高级机制,确保整个应用程序和集群中的事物正常运行。RC 创建的 Pod 如果失败会被替换,终止时会被删除。即使你的应用程序中只有一个 Pod,仍然建议使用 RC。

RC 的任务仅仅是确保应用程序的期望规模。你定义想要运行的 Pod 副本数,并提供一个模板来创建新 Pod。与服务类似,我们将使用选择器和标签来定义 Pod 在 RC 中的成员资格。

Kubernetes 不要求严格的副本控制器行为,这对于长期运行的过程来说是理想的。实际上,作业控制器可用于短生命周期的工作负载,它们允许作业运行到完成状态,并非常适合批处理工作。

副本集是一个新的类型,目前处于测试版,它代表了复制控制器的改进版本。目前,主要的区别是可以使用新的基于集合的标签选择器,正如我们在以下示例中所看到的。

我们的第一个 Kubernetes 应用

在我们继续之前,让我们看看这三个概念如何运作。Kubernetes 随附了许多预安装的示例,但我们将从头开始创建一个新示例,以说明其中的一些概念。

我们已经创建了一个 pod 定义文件,但正如你所学到的,通过复制控制器来运行我们的 pod 有很多优点。再次使用我们之前创建的 book-examples/02_example 文件夹,我们将创建一些定义文件,并使用复制控制器方法启动一个 Node.js 服务器集群。此外,我们还将为它添加一个负载均衡的服务。

使用你喜欢的编辑器创建以下文件并命名为 nodejs-controller.yaml

apiVersion: v1 
kind: ReplicationController 
metadata: 
  name: node-js 
  labels: 
    name: node-js 
spec: 
  replicas: 3 
  selector: 
    name: node-js 
  template: 
    metadata: 
      labels: 
        name: node-js 
    spec: 
      containers: 
      - name: node-js 
        image: jonbaier/node-express-info:latest 
        ports: 
        - containerPort: 80

这是我们集群的第一个资源定义文件,让我们仔细看看。你会注意到它有四个一级元素(kindapiVersionmetadataspec)。这些元素在所有顶级 Kubernetes 资源定义中都是常见的:

  • Kind:这告诉 K8s 我们正在创建的资源类型。在本例中,类型是 ReplicationControllerkubectl 脚本使用一个 create 命令来创建所有类型的资源。这样做的好处是你可以轻松地创建多种类型的资源,而无需为每种类型单独指定参数。然而,这要求定义文件能够识别它们指定的内容。

  • apiVersion:这只是告诉 Kubernetes 我们使用的是哪个版本的 schema。

  • Metadata:在这里,我们将为资源指定一个名称,并指定标签,这些标签将在给定操作中用于搜索和选择资源。metadata 元素还允许你创建注解,这些注解用于非标识信息,可能对客户端工具和库有用。

  • 最后,我们有 spec,它将根据我们创建的资源的 kindtype 而有所不同。在本例中,它是 ReplicationController,确保所需数量的 pods 正在运行。replicas 元素定义了所需的 pod 数量,selector 元素告诉控制器需要监视哪些 pod,最后,template 元素定义了启动新 pod 的模板。template 部分包含我们之前在 pod 定义中看到的相同部分。一个需要注意的重要事项是,selector 的值需要与 pod 模板中指定的 labels 值匹配。请记住,这种匹配用于选择被管理的 pod。

现在,让我们看一下名为 nodejs-rc-service.yaml 的服务定义:

apiVersion: v1 
kind: Service 
metadata: 
  name: node-js 
  labels: 
    name: node-js 
spec: 
  type: LoadBalancer 
  ports: 
  - port: 80 
  selector: 
    name: node-js

如果你正在使用 Google Cloud Platform 的免费试用版,你可能会遇到 LoadBalancer 类型服务的问题。此类型会创建一个外部 IP 地址,但试用账户只允许一个静态地址。

对于这个例子,你无法通过 Minikube 使用外部 IP 地址访问该示例。在 Kubernetes 1.5 以上版本中,你可以使用 Ingress 来公开服务,但这超出了本章的范围。

这里的 YAML 文件类似于 ReplicationController。主要区别在于服务的 spec 元素。在这里,我们定义了 Service 类型、监听的 portselector,这些告诉 Service 代理哪些 pod 可以响应服务。

Kubernetes 支持 YAML 和 JSON 格式的定义文件。

创建 Node.js express 复制控制器:

$ kubectl create -f nodejs-controller.yaml

输出如下:

replicationcontroller "node-js" created

这为我们提供了一个复制控制器,确保始终运行三个容器副本:

$ kubectl create -f nodejs-rc-service.yaml

输出如下:

service "node-js" created 

在 GCE 上,这将创建一个外部负载均衡器和转发规则,但你可能需要添加额外的防火墙规则。在我的情况下,防火墙已经为端口 80 开放。然而,你可能需要打开这个端口,特别是当你部署的是一个包含非 80443 端口的服务时。

好的,现在我们有了一个正在运行的服务,这意味着我们可以通过一个可靠的 URL 访问 Node.js 服务器。让我们来看一下我们正在运行的服务:

$ kubectl get services

以下截图是执行上述命令后的结果:

服务列表

在前面的截图(服务列表)中,我们应该注意到 node-js 服务正在运行,在 IP(S) 列中,我们应该同时看到一个私有 IP 和一个公共 IP(截图中的 130.211.186.84)。如果你没有看到外部 IP,可能需要等一分钟,让 GCE 分配 IP。我们来试试通过浏览器打开公共地址看看是否能够连接:

容器信息应用

你应该看到类似于前面截图的内容。如果我们访问多次,你应该注意到容器名称会发生变化。本质上,服务负载均衡器正在后台的可用 pod 之间进行轮换。

浏览器通常会缓存网页,所以要真正看到容器名称的变化,你可能需要清除缓存,或者使用像这样的代理:hide.me/en/proxy

让我们来试试玩一下混乱猴子(chaos monkey),杀掉一些容器,看看 Kubernetes 会做什么。为了实现这一点,我们需要查看 pod 实际运行的位置。首先,让我们列出我们的 pods:

$ kubectl get pods

以下截图是执行上述命令后的结果:

当前运行的 pods

现在,让我们获取运行 node-js 容器的一个 pod 的更多详细信息。你可以通过 describe 命令和上一条命令中列出的 pod 名称来执行此操作:

$ kubectl describe pod/node-js-sjc03

以下截图是执行上述命令的结果:

Pod 描述

你应该看到前述输出。我们需要的信息是 Node: 部分。让我们使用节点名称通过 SSH(即 Secure Shell 的缩写)登录到运行此工作负载的节点(从节点):

$ gcloud compute --project "<Your project ID>" ssh --zone "<your gce zone>" "<Node from
pod describe>"

一旦通过 SSH 登录到节点,如果我们运行 sudo docker ps 命令,我们应该能看到至少两个容器:一个运行 pause 镜像,另一个运行实际的 node-express-info 镜像。如果 K8s 在这个节点上调度了多个副本,你可能会看到更多容器。让我们获取 jonbaier/node-express-info 镜像(而非 gcr.io/google_containers/pause)的容器 ID,并杀死它,看看会发生什么。将这个容器 ID 保存起来,稍后使用:

$ sudo docker ps --filter="name=node-js"
$ sudo docker stop <node-express container id>
$ sudo docker rm <container id>
$ sudo docker ps --filter="name=node-js"

除非你非常迅速,否则你可能会注意到 node-express-info 容器仍在运行,但仔细观察会发现 container id 不同,并且创建时间戳显示仅仅是几秒钟前。如果你回到服务 URL,它仍然按正常方式运行。现在可以退出 SSH 会话了。

在这里,我们已经看到 Kubernetes 扮演了值班运维的角色,确保我们的应用始终在运行。

让我们看看能否找到停机的证据。进入 Kubernetes UI 的事件页面。你可以通过主 K8s 仪表板的节点页面找到它。选择列表中的一个节点(即我们通过 SSH 登录的节点),然后滚动到该节点详情页面中的事件部分。

你会看到一个类似以下截图的画面:

Kubernetes UI 事件页面

你应该看到三个最近的事件。首先,Kubernetes 拉取镜像。其次,它使用拉取的镜像创建一个新容器。最后,它重新启动该容器。你会注意到,从时间戳来看,这一切都在不到一秒钟内完成。所需时间可能会根据集群大小和镜像拉取情况有所不同,但恢复速度非常快。

更多关于标签的内容

如前所述,标签只是简单的键值对。它们可用于 Pod、复制控制器、副本集、服务等。如果你回想一下我们的服务 YAML nodejs-rc-service.yaml,里面有一个 selector 属性。selector 属性告诉 Kubernetes 使用哪些标签来找到要为该服务转发流量的 Pods。

K8s 允许用户直接在复制控制器、副本集和服务上操作标签。让我们修改我们的副本和服务,添加一些标签。再次使用你喜欢的编辑器创建这两个文件,并命名为 nodejs-labels-controller.yamlnodejs-labels-service.yaml,如下面所示:

apiVersion: v1 
kind: ReplicationController 
metadata: 
  name: node-js-labels 
  labels: 
    name: node-js-labels 
    app: node-js-express 
    deployment: test 
spec: 
  replicas: 3 
  selector: 
    name: node-js-labels 
    app: node-js-express 
    deployment: test 
  template: 
    metadata: 
      labels: 
        name: node-js-labels 
        app: node-js-express 
        deployment: test 
    spec: 
      containers: 
      - name: node-js-labels 
        image: jonbaier/node-express-info:latest 
        ports: 
        - containerPort: 80
apiVersion: v1 
kind: Service 
metadata: 
  name: node-js-labels 
  labels: 
    name: node-js-labels 
    app: node-js-express 
    deployment: test 
spec: 
  type: LoadBalancer 
  ports: 
  - port: 80 
  selector: 
    name: node-js-labels 
    app: node-js-express 
    deployment: test

按如下方式创建复制控制器和服务:

$ kubectl create -f nodejs-labels-controller.yaml
$ kubectl create -f nodejs-labels-service.yaml

让我们看看如何在日常管理中使用标签。下表展示了我们选择标签的选项:

运算符 描述 示例
=== 可以使用这两种样式来选择值等于右侧字符串的键 name = apache
!= 选择值与右侧字符串不相等的键 Environment != test
in 选择标签键值在该集合中的资源 tier in (web, app)
notin 选择标签键值不在该集合中的资源 tier notin (lb, app)
<Key name> 仅使用键名选择包含此键的资源 tier

标签选择器

我们来尝试查找带有 test 部署的副本:

$ kubectl get rc -l deployment=test

以下截图是前面命令的结果:

复制控制器列表

你会注意到,它只返回了我们刚才启动的复制控制器。那么带有名为 component 标签的服务呢?使用以下命令:

$ kubectl get services -l component

以下截图是前面命令的结果:

列出带有名为 component 标签的服务

在这里,我们只看到了核心的 Kubernetes 服务。最后,让我们获取一下我们在本章中启动的 node-js 服务器。请看以下命令:

$ kubectl get services -l "name in (node-js,node-js-labels)"

以下截图是前面命令的结果:

列出带有标签名称和值为 node-js 或 node-js-labels 的服务

此外,我们还可以在多个 pod 和服务中执行管理任务。例如,我们可以终止所有属于 demo 部署的复制控制器(如果我们有任何正在运行的),如下所示:

$ kubectl delete rc -l deployment=demo

否则,终止所有属于 productiontest 部署的服务(如果我们有任何正在运行的服务),如下所示:

$ kubectl delete service -l "deployment in (test, production)"

需要注意的是,虽然标签选择在日常管理任务中非常有用,但它确实需要我们在部署中保持良好的规范。我们需要确保我们有一个标签标准,并且在我们运行的所有 Kubernetes 资源定义文件中都严格遵循该标准。

虽然我们迄今为止使用了服务定义的 YAML 文件来创建服务,但实际上,你也可以仅使用 kubectl 命令来创建它们。要尝试这个,首先运行 get pods 命令并获取一个 node-js pod 的名称。接下来,使用以下 expose 命令为该 pod 创建一个服务端点:

$ kubectl expose pods node-js-gxkix --port=80 --name=testing-vip --type=LoadBalancer 这将创建一个名为 testing-vip 的服务,并且还会创建一个公共 vip(负载均衡器 IP),可以通过端口 80 访问该 pod。还可以使用许多其他可选参数,相关信息可以通过以下命令查看:kubectl expose --help

副本集

如前所述,副本集是复制控制器的新改进版。以下是它们功能的基本示例,我们将在第四章中进一步扩展,实现可靠的容器原生应用程序,并介绍高级概念。

这是一个基于并且类似于ReplicationControllerReplicaSet示例。将此文件命名为nodejs-labels-replicaset.yaml

apiVersion: extensions/v1beta1 
kind: ReplicaSet 
metadata: 
  name: node-js-rs 
spec: 
  replicas: 3 
  selector: 
    matchLabels: 
      app: node-js-express 
      deployment: test 
    matchExpressions: 
      - {key: name, operator: In, values: [node-js-rs]} 
  template: 
    metadata: 
      labels: 
        name: node-js-rs 
        app: node-js-express 
        deployment: test 
    spec: 
      containers: 
      - name: node-js-rs 
        image: jonbaier/node-express-info:latest 
        ports: 
        - containerPort: 80

健康检查

Kubernetes 提供了三层健康检查。首先,通过 HTTP 或 TCP 检查,K8s 可以尝试连接到特定端点,并在成功连接时返回健康状态。其次,可以使用命令行脚本执行应用程序特定的健康检查。我们还可以使用exec容器在容器内运行健康检查。任何以0状态退出的都将被认为是健康的。

让我们来看看一些健康检查的实际操作。首先,我们将创建一个名为nodejs-health-controller.yaml的新控制器,并进行健康检查:

apiVersion: v1 
kind: ReplicationController 
metadata: 
  name: node-js 
  labels: 
    name: node-js 
spec: 
  replicas: 3 
  selector: 
    name: node-js 
  template: 
    metadata: 
      labels: 
        name: node-js 
    spec: 
      containers: 
      - name: node-js 
        image: jonbaier/node-express-info:latest 
        ports: 
        - containerPort: 80 
        livenessProbe: 
          # An HTTP health check  
          httpGet: 
            path: /status/ 
            port: 80 
          initialDelaySeconds: 30 
          timeoutSeconds: 1

注意添加了livenessprobe元素。这是我们的核心健康检查元素。在这里,我们可以指定httpGettcpSocketexec。在此示例中,我们使用httpGet对容器上的 URI 进行简单检查。如果路径和端口未能成功返回,探测器将重新启动 pod。

状态码在200399之间的都被探测器认为是健康的。

最后,initialDelaySeconds为我们提供了灵活性,允许我们延迟健康检查,直到 pod 完成初始化。timeoutSeconds值则是探测器的超时时间。

让我们使用我们新启用健康检查的控制器来替换旧的node-js RC。我们可以使用replace命令来完成此操作,它将替换复制控制器的定义:

$ kubectl replace -f nodejs-health-controller.yaml

单独替换 RC 并不会替换我们的容器,因为它仍然拥有来自第一次运行的三个健康的 pod。让我们杀掉这些 pod,并让更新后的ReplicationController使用健康检查替换它们:

$ kubectl delete pods -l name=node-js

现在,等待一两分钟后,我们可以列出 RC 中的 pod,并抓取其中一个 pod 的 ID,通过describe命令对其进行更深入的检查:

$ kubectl describe rc/node-js

以下截图是前述命令的结果:

node-js 复制控制器描述

现在,使用以下命令查看其中一个 pod:

$ kubectl describe pods/node-js-7esbp

以下截图是前述命令的结果:

node-js-1m3cs pod 描述

在顶部,我们将看到整体的 pod 详细信息。根据你的时机,在State下,它将显示为RunningWaiting,并带有CrashLoopBackOff的原因和一些错误信息。稍微向下,你可以看到我们的Liveness探针的信息,可能会看到失败计数大于0。再往下,我们可以看到 pod 事件。再次根据时机,你很可能会看到该 pod 的多个事件。在一两分钟内,你会注意到杀死、启动和创建事件会重复出现。你还应该在Killing条目中看到容器不健康的说明。这是因为我们的健康检查失败,因为我们没有在/status处有页面响应。

你可能会注意到,如果你打开浏览器访问服务的负载均衡器地址,它仍然会响应一个页面。你可以通过kubectl get services命令找到负载均衡器的 IP 地址。

这有多种原因。首先,健康检查失败是因为/status不存在,但指向该服务的页面在重启之间仍然正常工作。其次,livenessProbe仅负责在健康检查失败时重启容器。还有一个单独的readinessProbe,它会将容器从回答服务端点的 pod 池中移除。

让我们修改健康检查,以检查容器中确实存在的页面,这样我们就能进行适当的健康检查。我们还会添加一个就绪检查,并将其指向不存在的状态页面。打开nodejs-health-controller.yaml文件,并修改spec部分,使其与以下内容匹配,然后将文件保存为nodejs-health-controller-2.yaml

apiVersion: v1 
kind: ReplicationController 
metadata: 
  name: node-js 
  labels: 
    name: node-js 
spec: 
  replicas: 3 
  selector: 
    name: node-js 
  template: 
    metadata: 
      labels: 
        name: node-js 
    spec: 
      containers: 
      - name: node-js 
        image: jonbaier/node-express-info:latest 
        ports: 
        - containerPort: 80 
        livenessProbe: 
          # An HTTP health check  
          httpGet: 
            path: / 
            port: 80 
          initialDelaySeconds: 30 
          timeoutSeconds: 1 
        readinessProbe: 
          # An HTTP health check  
          httpGet: 
            path: /status/ 
            port: 80 
          initialDelaySeconds: 30 
          timeoutSeconds: 1

这次,我们将删除旧的 RC,它会连带杀死 pod,并使用更新的 YAML 文件创建新的 RC:

$ kubectl delete rc -l name=node-js-health
$ kubectl create -f nodejs-health-controller-2.yaml

现在,当我们描述某个 pod 时,我们只会看到 pod 和容器的创建情况。然而,你会注意到服务的负载均衡器 IP 地址不再工作。如果我们在其中一个新节点上运行describe命令,我们会看到Readiness probe failed的错误信息,但 pod 本身仍在运行。如果我们将就绪探针路径改为path: /,我们将能够再次从主服务中处理请求。现在,打开nodejs-health-controller-2.yaml文件,进行更新。然后,再次删除并重新创建复制控制器:

$ kubectl delete rc -l name=node-js
$ kubectl create -f nodejs-health-controller-2.yaml

现在负载均衡器的 IP 应该再次工作。保持这些 pod,因为我们将在第三章中再次使用它们,网络、负载均衡器和 Ingress

TCP 检查

Kubernetes 也支持通过简单的 TCP 套接字检查进行健康检查,也可以使用自定义的命令行脚本。

以下代码片段是两种用例在 YAML 文件中的示例。

使用命令行脚本进行健康检查:

livenessProbe: 
  exec: 
    command: 
    -/usr/bin/health/checkHttpServce.sh 
  initialDelaySeconds:90 
  timeoutSeconds: 1

使用简单的 TCP 套接字连接进行健康检查:

livenessProbe: 
  tcpSocket: 
    port: 80 
  initialDelaySeconds: 15 
  timeoutSeconds: 1

生命周期钩子或优雅关闭

当你在实际场景中遇到故障时,你可能会发现你希望在容器关闭之前或启动之后采取额外的操作。Kubernetes 实际上为这种用例提供了生命周期钩子。

以下示例控制器定义文件 apache-hooks-controller.yaml,定义了在 Kubernetes 将容器移入生命周期的下一个阶段之前,执行 postStartpreStop 动作:

apiVersion: v1 
kind: ReplicationController 
metadata: 
  name: apache-hook 
  labels: 
    name: apache-hook 
spec: 
  replicas: 3 
  selector: 
    name: apache-hook 
  template: 
    metadata: 
      labels: 
        name: apache-hook 
    spec: 
      containers: 
      - name: apache-hook 
        image: bitnami/apache:latest 
        ports: 
        - containerPort: 80 
        lifecycle: 
          postStart: 
            httpGet: 
              path: http://my.registration-server.com/register/ 
              port: 80 
          preStop: 
            exec: 
              command: ["/usr/local/bin/apachectl","-k","graceful-
              stop"]

你会注意到,对于 postStart 钩子,我们定义了一个 httpGet 动作,而对于 preStop 钩子,我们定义了一个 exec 动作。就像我们的健康检查一样,httpGet 动作尝试对特定的端点和端口组合进行 HTTP 调用,而 exec 动作则在容器内运行本地命令。

httpGetexec 动作都支持 postStartpreStop 钩子。在 preStop 的情况下,名为 reason 的参数将作为参数发送到处理程序。请参阅下表,了解有效值:

原因参数 失败描述
删除 通过 kubectl 或 API 发出的删除命令
健康 健康检查失败
依赖性 依赖失败,如磁盘挂载失败或默认基础设施 Pod 崩溃

有效的 preStop 原因

查看这里的参考部分:github.com/kubernetes/kubernetes/blob/release-1.0/docs/user-guide/container-environment.md#container-hooks

需要注意的是,钩子调用至少会执行一次。因此,动作中的任何逻辑都应优雅地处理多次调用。另一个重要的注意事项是,postStart 在 Pod 进入准备就绪状态之前执行。如果钩子本身失败,Pod 会被视为不健康。

应用调度

现在我们已经理解了如何在 Pods 中运行容器,甚至在故障发生时进行恢复,那么了解新容器是如何在我们的集群节点上调度的也许会很有用。

如前所述,Kubernetes 调度器的默认行为是将容器副本分布在集群中的各个节点上。在没有其他约束的情况下,调度器会将新的 Pod 安排到节点上,这些节点上其他 Pod 的数量最少,并且这些 Pod 属于匹配的服务或复制控制器。

此外,调度器还提供了根据节点可用资源添加约束的功能。今天,这些约束包括最小的 CPU 和内存分配。在 Docker 中,这些通过 CPU-shares 和内存限制标志在后台进行处理。

当定义了额外的约束时,Kubernetes 会检查节点是否有可用资源。如果节点不满足所有约束,它将移到下一个节点。如果没有节点满足条件,则我们将在日志中看到调度错误。

Kubernetes 的路线图中还计划支持网络和存储。由于调度是容器整体操作和管理中的一个重要环节,随着项目的发展,我们应当预期在这一领域会有许多新增功能。

调度示例

让我们快速看一个设置资源限制的示例。如果我们查看 K8s 仪表板,可以使用 https://<your master ip>/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard 获取集群当前资源使用情况的快照,并点击左侧菜单中的 Nodes。

我们将看到一个仪表板,如下图所示:

Kube 节点仪表板

该视图显示了整个集群、节点和主节点的 CPU 和内存总量。在这种情况下,我们的 CPU 使用率较低,但内存使用量较大。

让我们看看当我尝试启动更多 pod 时会发生什么,不过这次我们请求 512 Mi 内存和 1500 m CPU。我们使用 1500 m 来指定 1.5 个 CPU;由于每个节点只有 1 个 CPU,这应该会导致失败。以下是 RC 定义的示例。请将此文件保存为 nodejs-constraints-controller.yaml

apiVersion: v1 
kind: ReplicationController 
metadata: 
  name: node-js-constraints 
  labels: 
    name: node-js-constraints 
spec: 
  replicas: 3 
  selector: 
    name: node-js-constraints 
  template: 
    metadata: 
      labels: 
        name: node-js-constraints 
    spec: 
      containers: 
      - name: node-js-constraints 
        image: jonbaier/node-express-info:latest 
        ports: 
        - containerPort: 80 
        resources: 
          limits: 
            memory: "512Mi" 
            cpu: "1500m"

要打开前述文件,请使用以下命令:

$ kubectl create -f nodejs-constraints-controller.yaml

复制控制器成功完成,但如果我们运行 get pods 命令,我们会发现 node-js-constraints 的 pod 卡在了待定状态。如果我们使用 describe pods/<pod-id> 命令仔细查看,会发现调度错误(对于 pod-id,请使用第一个命令中的 pod 名称之一):

$ kubectl get pods
$ kubectl describe pods/<pod-id>

以下截图显示了前面命令的结果:

Pod 描述

请注意,在底部的事件部分,Events 中列出的 WarningFailedScheduling pod 错误后面会伴随有 fit failure on node....Insufficient cpu 错误。正如你所见,Kubernetes 没能在集群中找到一个符合我们定义的所有约束的匹配位置。

如果我们现在将 CPU 限制修改为 500 m,然后重新创建复制控制器,我们应该会在几秒钟内看到所有三个 pod 启动。

总结

我们已经了解了 Kubernetes 的整体架构,以及用于构建服务和应用堆栈的核心构件。你应该对这些抽象如何简化堆栈和/或服务的生命周期管理有了更深入的理解,而不仅仅是单个组件的管理。此外,我们还亲自了解了如何通过 pod、服务和复制控制器管理一些简单的日常任务。我们还看了如何通过健康检查使用 Kubernetes 自动响应故障。最后,我们探讨了 Kubernetes 调度器以及用户可以指定的一些约束条件来影响调度位置。

在下一章中,我们将深入探讨 Kubernetes 的网络层。我们将了解网络是如何实现的,并且也会研究用于流量路由的核心 Kubernetes 代理。我们还将讨论服务发现和逻辑命名空间分组。

问题

  1. 健康检查的三种类型是什么?

  2. 复制控制器的替代技术是什么?

  3. 请列出 Kubernetes 系统的五个层级

  4. 请列出 Kubernetes 的两个网络插件

  5. Kubernetes 可用的两种容器运行时选项是什么?

  6. Kubernetes 架构的三个主要组件是什么?

  7. 哪种类型的选择器根据特定值过滤键和值?

进一步阅读

第三章:使用网络、负载均衡器和入口

在本章中,我们将讨论 Kubernetes 对集群网络的方法以及与其他方法的不同之处。我们将描述 Kubernetes 网络解决方案的关键要求,并探讨为简化集群操作而至关重要的原因。我们将深入探讨 Kubernetes 集群中的 DNS,深入研究容器网络接口CNI)和插件生态系统,并深入了解服务以及 Kubernetes 代理在每个节点上的工作原理。最后,我们将简要概述一些用于多租户高级隔离功能的特性。

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

  • Kubernetes 网络

  • 高级服务概念

  • 服务发现

  • DNS、CNI 和入口

  • 命名空间限制和配额

技术要求

您需要一个运行中的 Kubernetes 集群,就像我们在前几章创建的那个一样。您还需要通过kubectl命令访问部署该集群。

此章节的 GitHub 代码库可以在github.com/PacktPublishing/Getting-Started-with-Kubernetes-third-edition/tree/master/Code-files/Chapter03找到。

容器网络

在生产级运营中,网络是一个重要关注点。在服务级别上,我们需要一种可靠的方式让我们的应用组件找到并与彼此通信。引入容器和集群使事情变得更加复杂,因为现在我们必须考虑多个网络命名空间。现在,通信和发现变成了一个必须要处理容器 IP 空间、主机网络甚至有时多个数据中心网络拓扑的技能。

Kubernetes 受益于过去十年 Google 使用的集群工具的传承。在全球最大的网络之一中,网络是 Google 超越竞争对手的一个领域。此前,Google 构建了自己的硬件交换机和软件定义网络SDN),以提供更多控制、冗余和效率,用于日常网络操作。从每周运行和联网 20 亿个容器中获得的许多经验教训已经融入 Kubernetes,并影响了 K8s 网络的实施方式。

Docker 的方法

为了理解 K8s 网络模型背后的动机,让我们回顾一下 Docker 对容器网络的方法。

Docker 默认网络

以下是 Docker 的一些默认网络:

  • 桥接网络:在非 Swarm 场景下,Docker 将使用桥接网络驱动程序(称为bridge)来允许独立的容器相互通信。你可以将桥接视为一个链路层设备,它在不同网络段之间转发网络流量。如果容器连接到同一桥接网络,它们可以相互通信;如果没有连接,它们则无法通信。除非另有指定,桥接网络是默认选择。在这种模式下,容器有自己的网络命名空间,然后通过虚拟接口与主机(或 K8s 中的节点)网络相连接。在桥接网络中,由于容器之间完全隔离,两个容器可以使用相同的 IP 地址范围。因此,服务通信需要通过主机侧的网络接口进行额外的端口映射。

  • 主机模式:Docker 还为独立容器提供基于主机的网络,这会创建一个名为docker0的虚拟桥接,分配私有 IP 地址空间给使用该桥接的容器。每个容器都会获得一个虚拟以太网(veth)设备,你可以在容器内看到它作为eth0。由于移除了一个网络虚拟化层,性能得到了显著提升;然而,你失去了拥有独立网络命名空间的安全性。此外,由于所有容器共享一个 IP 地址,端口使用必须更加小心管理。

还有一种 none 网络,它会创建一个没有外部接口的容器。如果你检查网络接口,只会看到一个loopback设备。

在所有这些场景中,我们仍然处于单个机器上,除非是主机模式,否则容器的 IP 空间无法在该机器外部访问。要在两台机器之间连接容器,则需要 NAT 和端口映射来进行通信。

Docker 用户定义的网络

为了解决跨机器通信问题并提供更大的灵活性,Docker 还通过网络插件支持用户定义的网络。这些网络独立于容器本身存在。通过这种方式,容器可以加入到同一个现有的网络中。通过新的插件架构,可以为不同的网络使用场景提供多种驱动程序,如下所示:

  • Swarm 模式:在 Swarm 集群环境下,默认行为是覆盖网络,它允许你连接在多个机器上运行的多个 Docker 守护进程。为了在多个主机之间进行协调,所有容器和守护进程必须对可用的网络及其拓扑达成一致。覆盖网络引入了大量复杂性,包括 Kubernetes 避免的动态端口映射。

你可以在这里阅读更多关于覆盖网络的信息:docs.docker.com/network/overlay/

  • Macvlan:Docker 还提供 macvlan 地址分配,它与 Kubernetes 提供的网络模型最为相似,因为它为每个 Docker 容器分配一个 MAC 地址,使其看起来像是网络上的一个物理设备。Macvlan 提供了更高效的网络虚拟化和隔离,因为它绕过了 Linux 桥接。需要注意的是,截至本书出版时,大多数云服务提供商不支持 Macvlan。

由于这些选项,Docker 必须在每台主机的基础上管理复杂的端口分配,并且必须维护这些信息,并将其传播到集群中的其他所有机器。Docker 使用 gossip 协议来管理端口的转发和代理。

Kubernetes 的方法

Kubernetes 的网络方法与 Docker 的不同,让我们来看看具体有哪些差异。我们可以通过考虑集群调度和编排中的四个主要主题来了解 Kubernetes:

  • 通过为 Pod 提供 IP 地址空间,而非为容器提供,从而解耦容器间通信

  • Pod 与 Pod 之间的通信以及服务作为 Kubernetes 网络模型中主要的通信范式

  • Pod 与服务之间以及外部与服务之间的通信,通过 services 对象提供

这些考虑因素是 Kubernetes 网络模型的一种有意义的简化,因为无需跟踪动态端口映射。同样,IP 地址分配是在 Pod 级别进行的,这意味着 Kubernetes 的网络通信要求每个 Pod 都有自己的 IP 地址。这意味着一个 Pod 中的所有容器共享该 IP 地址,并且被认为在同一个网络命名空间中。我们将在本章稍后讨论内部和外部服务时,探讨如何管理这一共享的 IP 资源。Kubernetes 通过不允许容器间或容器与节点(minion)间使用 网络地址转换NAT)来促进 Pod 与 Pod 之间的通信。此外,内部容器的 IP 地址必须与用于与其通信的 IP 地址匹配。这强调了 Kubernetes 的假设:所有 Pod 都能与所有其他 Pod 进行通信,无论它们落在哪个主机上,这种通信随后为 Pod 内部提供了一个本地 IP 地址空间,从而为容器提供了网络连接。所有在同一主机中的容器可以通过本地主机在其保留端口上相互通信。这种没有 NAT 的扁平 IP 空间简化了当你开始扩展到数千个 Pod 时的网络变更。

这些规则将大部分复杂性从我们的网络堆栈中剔除,并简化了应用程序的设计。此外,它们消除了重新设计从现有基础设施迁移过来的遗留应用程序网络通信的需求。在绿地应用程序中,它们允许以更大的规模处理数百甚至数千个服务和应用程序的通信。

敏锐的读者可能也注意到,这种模型与 VM 和物理主机的 IP 架构兼容,VM 或物理主机每个都有一个单一地址,类似于 pod 的结构。这意味着你无需改变服务发现、负载均衡、应用配置和端口管理的方式,在使用 Kubernetes 时可以迁移你的应用管理工作流。

K8s 通过使用一个 pod 容器占位符来实现这种 pod 级别的 IP 魔法。记住,我们在第一章引言部分看到的暂停容器,在主节点上运行的服务一节中,通常被称为 pod 基础设施容器,它的一个重要任务是为后续启动的应用容器保留网络资源。实际上,暂停容器持有整个 pod 的网络命名空间和 IP 地址,所有在 pod 中运行的容器都可以使用它。暂停容器首先加入并持有命名空间,而 pod 中的后续容器在启动时通过 Docker 的--net=container:%ID%功能加入该命名空间。

如果你想查看暂停容器中的代码,它就在这里:github.com/kubernetes/kubernetes/blob/master/build/pause/pause.c

Kubernetes 可以通过使用 CNI 插件来为生产工作负载提供前述功能,或者使用 kubenet 网络来简化集群通信。当你的集群依赖于云服务提供商的安全组或网络访问控制列表NACLs)提供的逻辑分区时,也可以使用 Kubernetes。现在我们来深入了解具体的网络选项。

网络选项

我们建议的网络模型有两种方法。首先,你可以使用生态系统中现有的其中一个 CNI 插件。这涉及到与 AWS、GCP 和 Azure 的本地网络层配合的解决方案。还有一些支持叠加的插件,我们将在下一节中介绍。CNI 旨在成为容器的通用插件架构。目前,多个编排工具,如 Kubernetes、Mesos 和 CloudFoundry,都支持 CNI。

网络插件被视为 alpha 版本,因此它们的功能、内容和配置将快速变化。

如果你在寻找一个更简单的替代方案来进行测试并使用较小的集群,你可以使用 kubenet 插件,它使用bridgehost-local CNI 插件,并简化了cbr0的实现。这个插件仅在 Linux 上可用,并且不提供任何高级功能。由于它通常与云提供商的网络配置一起使用,它不处理策略或跨节点网络通信。

就像 CPU、内存和存储一样,Kubernetes 利用网络命名空间,每个命名空间都有自己的 iptables 规则、接口和路由表。Kubernetes 使用 iptables 和 NAT 来管理位于单个物理地址后面的多个逻辑地址,尽管你可以选择为你的集群提供多个物理接口(网卡)。大多数人会发现自己需要生成多个逻辑接口,并使用诸如复用、虚拟桥接和使用 SR-IOV 的硬件交换等技术来创建多个设备。

你可以在 github.com/containernetworking/cni 找到更多信息。

始终参考 Kubernetes 文档,了解最新和完整的支持网络选项列表。

网络对比

为了更好地理解容器中的网络,查看流行的容器网络选择是很有帮助的。以下方法并非详尽无遗,但应该能让你了解可用的选项。

Weave

Weave 提供了一个 Docker 容器的覆盖网络。它可以作为插件与新的 Docker 网络插件接口一起使用,并且通过 CNI 插件与 Kubernetes 兼容。像许多覆盖网络一样,很多人批评其封装开销对性能的影响。请注意,他们最近添加了一个预览版本,支持 虚拟可扩展局域网VXLAN)封装,这大大提高了性能。更多信息,请访问 blog.weave.works/2015/06/12/weave-fast-datapath/

 

Flannel

Flannel 来自 CoreOS,是一个基于 etcd 的覆盖网络。Flannel 为每个主机/节点分配一个完整的子网,类似于 Kubernetes 中每个 Pod 或容器组的可路由 IP 的做法。Flannel 包含内核级的 VXLAN 封装模式,以提供更好的性能,并具有类似于 Docker 覆盖插件的实验性多网络模式。更多信息,请访问 github.com/coreos/flannel

Project Calico

Project Calico 是一个基于第 3 层的网络模型,利用 Linux 内核内置的路由功能。路由通过 边界网关协议BGP)传播到每个主机上的虚拟路由器。Calico 可以用于从小规模部署到大规模互联网级别的安装。由于它在网络栈的较低层次工作,因此无需额外的 NAT、隧道或覆盖。它可以直接与底层网络基础设施进行交互。此外,它还支持网络级别的 ACL 以提供额外的隔离和安全性。更多信息,请访问 www.projectcalico.org/

Canal

Canal将 Calico 用于网络策略和 Flannel 用于覆盖网络的功能合并成一个解决方案。它支持 Calico 和 Flannel 类型的覆盖网络,并使用 Calico 的策略执行逻辑。用户可以根据需要选择覆盖网络或非覆盖网络选项,因为该方案结合了前述两个项目的特点。欲了解更多信息,请访问:github.com/tigera/canal

Kube-router

Kube-router 选项是一个专门构建的网络解决方案,旨在提供易于使用且高效的性能。它基于 Linux 的 LVS/IPVS 内核负载均衡技术作为代理。它还使用基于内核的网络,并使用 iptables 作为网络策略执行器。由于它不使用覆盖技术,因此它有可能成为未来的高性能选择。欲了解更多信息,请访问以下网址:github.com/cloudnativelabs/kube-router

平衡设计

需要指出的是,Kubernetes 试图通过将 IP 地址放置在 Pod 级别来实现平衡。使用主机级别的唯一 IP 地址在容器数量增加时会变得有问题。必须使用端口来暴露特定容器上的服务并允许外部通信。除此之外,运行多个服务可能相互了解或不互相了解(以及它们的自定义端口),并且管理端口空间将成为一个大问题。

然而,为每个容器分配 IP 地址可能会显得过于复杂。在大规模应用场景中,需要使用覆盖网络和 NAT 来为每个容器分配地址。覆盖网络会增加延迟,而且 IP 地址也会被后端服务占用,因为它们需要与前端服务进行通信。

在这里,我们真正看到了 Kubernetes 在应用程序和服务级别提供的抽象优势。如果我有一个 Web 服务器和一个数据库,我们可以将它们放在同一个 Pod 中,并使用一个 IP 地址。Web 服务器和数据库可以使用本地接口和标准端口进行通信,无需任何自定义设置。此外,后端服务不会无谓地暴露给集群中其他地方运行的应用堆栈(但可能在同一主机上)。由于 Pod 看到的 IP 地址与其中运行的应用程序看到的相同,服务发现无需任何额外的转换。

如果你需要覆盖网络的灵活性,仍然可以在 Pod 级别使用覆盖网络。Weave、Flannel 和 Project Calico 可以与 Kubernetes 一起使用,还有许多其他可用的插件和覆盖网络。

在调度工作负载的上下文中,这也是非常有用的。为调度程序提供一个简单且标准的结构,以便匹配约束并理解集群网络中任何时刻的可用空间,是至关重要的。这是一个动态环境,运行着各种应用程序和任务,因此任何额外的复杂性都会产生连锁反应。

这对于服务发现也有影响。新的服务上线时,必须确定并注册一个 IP 地址,以便其他网络,至少是集群,可以访问它们。如果使用 NAT,服务将需要一个额外的机制来学习它们面向外部的 IP。

高级服务

让我们探索与服务和容器之间的通信相关的 IP 策略。如果你还记得,在第二章《Pods、Services、Replication Controllers 和 Labels》的服务部分中,你了解到 Kubernetes 使用kube-proxy来确定为每个请求提供服务的正确 pod IP 地址和端口。在幕后,kube-proxy实际上是使用虚拟 IP 和 iptables 来实现这一切的。

kube-proxy现在有两种模式——用户空间iptables。截至目前,1.2 版本的 iptables 是默认模式。在这两种模式下,kube-proxy都在每个主机上运行。它的首要任务是监控来自 Kubernetes 主控的 API。任何服务的更新都会触发 kube-proxy 更新 iptables。例如,当创建一个新服务时,会选择一个虚拟 IP 地址,并在 iptables 中设置一条规则,将流量通过一个随机端口引导到 kube-proxy。因此,我们现在可以在此节点上捕获面向服务的流量。由于kube-proxy在所有节点上运行,我们就可以在集群范围内解析服务VIP(虚拟 IP 的简称)。此外,DNS 记录也可以指向这个 VIP。

在用户空间模式下,我们在 iptables 中创建了一个钩子,但流量的代理仍然由kube-proxy处理。此时,iptables 规则只是将流量发送到kube-proxy中的服务入口。一旦kube-proxy收到特定服务的流量,它必须将流量转发到该服务候选池中的一个 pod。它通过在服务创建时选择的随机端口来实现这一点。

请参考下面的图示以概览流程:

Kube-proxy 通信

也可以通过在服务定义中使用sessionAffinity元素,始终将来自同一客户端 IP 的流量转发到同一后台 pod/container。

在 iptables 模式下,pod 直接在 iptable 规则中编码。这消除了对 kube-proxy 的依赖,从而直接将流量传递给 iptables,再到 pod。这种方式更快,减少了可能的故障点。正如我们在第二章 健康检查 部分中讨论的那样,Pod、服务、复制控制器和标签Readiness probe 在这种模式下非常有用,因为它弥补了丧失重试 pod 功能的问题。

外部服务

在上一章中,我们看到了一些服务示例。为了测试和演示的目的,我们希望所有的服务都可以被外部访问。这是通过在服务定义中的 type: LoadBalancer 元素配置的。LoadBalancer 类型会在云提供商上创建一个外部负载均衡器。我们需要注意的是,不同的云提供商对外部负载均衡器的支持情况和实现方式各不相同。在我们的案例中,我们使用的是 GCE,因此集成非常顺利。唯一需要额外设置的就是打开防火墙规则,允许外部服务端口的访问。

让我们深入了解一下,使用 describe 命令查看第二章 Pod、服务、复制控制器和标签更多关于标签 部分的一个服务:

$ kubectl describe service/node-js-labels

以下截图是前述命令的执行结果:

服务描述

在前面的截图输出中,你会注意到几个关键元素。我们的 Namespace: 设置为 defaultType:LoadBalancer,并且我们可以在 LoadBalancer Ingress: 下看到外部 IP。此外,我们还可以看到 Endpoints:,它显示了可以响应服务请求的 pod 的 IP 地址。

服务发现

正如我们之前讨论的,Kubernetes 主节点会跟踪所有服务定义和更新。发现服务的方式有三种。前两种方法使用 Linux 环境变量。虽然支持 Docker 链接样式的环境变量,但 Kubernetes 也有自己的命名约定。下面是我们 node-js 服务示例使用 K8s 环境变量的样子(注意 IP 会有所不同):

NODE_JS_PORT_80_TCP=tcp://10.0.103.215:80
NODE_JS_PORT=tcp://10.0.103.215:80
NODE_JS_PORT_80_TCP_PROTO=tcp
NODE_JS_PORT_80_TCP_PORT=80
NODE_JS_SERVICE_HOST=10.0.103.215
NODE_JS_PORT_80_TCP_ADDR=10.0.103.215
NODE_JS_SERVICE_PORT=80

另一种发现服务的方式是通过 DNS。虽然当 DNS 不可用时,环境变量可以发挥作用,但它也有一些缺点。系统只会在创建时创建变量,因此后续上线的服务将无法被发现,或者需要一些额外的工具来更新所有系统环境变量。

内部服务

让我们探索可以部署的其他类型的服务。首先,默认情况下,服务仅面向内部。你可以通过指定 clusterIP 类型来实现这一点,但如果没有定义类型,clusterIP 会被视为默认类型。让我们看一个例子,nodejs-service-internal.yaml;注意没有 type 元素:

apiVersion: v1 
kind: Service 
metadata: 
  name: node-js-internal 
  labels: 
    name: node-js-internal 
spec: 
  ports: 
  - port: 80 
  selector: 
    name: node-js 

使用此清单创建服务定义文件。你将需要一个健康版本的 node-js RC(清单 nodejs-health-controller-2.yaml)。如你所见,选择器与我们在前一章启动的名为 node-js 的 pods 匹配。我们将创建服务,然后列出当前运行的服务,并按如下过滤:

$ kubectl create -f nodejs-service-internal.yaml
$ kubectl get services -l name=node-js-internal

以下截图是前一个命令的结果:

内部服务清单

如你所见,我们有了一个新服务,但只有一个 IP 地址。此外,该 IP 地址无法从外部访问。这一次我们无法通过 Web 浏览器测试该服务。然而,我们可以使用便捷的 kubectl exec 命令并尝试从其他 pods 连接。你需要运行 node-js-podnodejs-pod.yaml)。然后,你可以执行以下命令:

$ kubectl exec node-js-pod -- curl <node-js-internal IP>

这允许我们像在 node-js-pod 容器中拥有一个 shell 一样运行 docker exec 命令。然后它会访问内部服务 URL,该 URL 会转发到带有 node-js 标签的任何 pods。

如果一切顺利,你应该会收到原始的 HTML 输出。你已经成功创建了一个仅限内部的服务。这对于你希望向运行在集群中的其他容器提供服务,但又不对外开放的后台服务非常有用。

自定义负载均衡

K8s 允许的第三种服务类型是 NodePort 类型。此类型允许我们通过主机或节点(minion)在特定端口公开服务。通过这种方式,我们可以使用任何节点(minion)的 IP 地址并访问在分配的节点端口上的服务。Kubernetes 默认会在 300032767 的范围内分配一个节点端口,但你也可以指定自己的自定义端口。在以下清单 nodejs-service-nodeport.yaml 的示例中,我们选择端口 30001,如下所示:

apiVersion: v1 
kind: Service 
metadata: 
  name: node-js-nodeport 
  labels: 
    name: node-js-nodeport 
spec: 
  ports: 
  - port: 80 
    nodePort: 30001 
  selector: 
    name: node-js 
  type: NodePort 

再次创建此 YAML 定义文件并创建你的服务,如下所示:

$ kubectl create -f nodejs-service-nodeport.yaml

输出应该包含类似这样的消息:

新的 GCP 防火墙规则

关于开启防火墙端口的提示。与外部负载均衡器类型类似,NodePort 正在使用节点上的端口将服务暴露到外部。如果你希望在节点前面使用自己的负载均衡器,这将非常有用。在我们测试新服务之前,让我们确保在 GCP 上打开这些端口。

从 GCE VM 实例控制台中,点击你任一节点(minion)的详细信息。然后,点击网络,通常默认情况下它会是创建时指定的网络。进入防火墙规则,我们可以通过点击“添加防火墙规则”来添加规则。

创建如下所示的规则(tcp:300010.0.0.0/0 IP 范围内):

创建新的防火墙规则页面

现在,我们可以通过打开浏览器并使用集群中任何节点(minion)的 IP 地址来测试我们的新服务。测试新服务的格式如下:

http://<Minoion IP Address>:<NodePort>/

最后,最新版本添加了ExternalName类型,它将CNAME映射到服务。

跨节点代理

记住,kube-proxy在所有节点上运行,所以即使 Pod 没有在该节点上运行,流量也会被代理到适当的主机。参见跨节点流量截图,了解流量如何流动的可视化效果。用户向外部 IP 或 URL 发起请求。此请求由Node服务。在这种情况下,Pod 并没有运行在此节点上。但这不是问题,因为 Pod 的 IP 地址是可路由的。所以,kube-proxyiptables会将流量直接传递到该服务的 Pod IP 上。网络路由会在Node 2上完成,这里是请求的应用程序所在:

跨节点流量

自定义端口

服务还允许你将流量映射到不同的端口;然后,容器和 Pod 会暴露自己。我们将创建一个服务,暴露端口90并将流量转发到 Pod 上的端口80。我们将把这个 Pod 命名为node-js-90,以反映自定义的端口号。创建以下两个定义文件,nodejs-customPort-controller.yamlnodejs-customPort-service.yaml

apiVersion: v1 
kind: ReplicationController 
metadata: 
  name: node-js-90 
  labels: 
    name: node-js-90 
spec: 
  replicas: 3 
  selector: 
    name: node-js-90 
  template: 
    metadata: 
      labels: 
        name: node-js-90 
    spec: 
      containers: 
      - name: node-js-90 
        image: jonbaier/node-express-info:latest 
        ports: 
        - containerPort: 80 
apiVersion: v1 
kind: Service 
metadata: 
  name: node-js-90 
  labels: 
    name: node-js-90 
spec: 
  type: LoadBalancer 
  ports: 
  - port: 90 
    targetPort: 80 
  selector: 
    name: node-js-90

如果你使用的是 Google Cloud Platform 的免费试用版,可能会遇到LoadBalancer类型服务的问题。该类型会创建多个外部 IP 地址,但试用账户仅限于一个静态地址。

你会注意到,在服务定义中,我们有一个targetPort元素。这个元素告诉服务在池中使用哪个端口供 Pod/容器使用。正如我们在前面的示例中看到的,如果没有指定targetPort,则假定它与服务端口相同。这个端口仍然作为服务端口使用,但在这种情况下,我们将通过端口90暴露服务,而容器在端口80上提供内容。

创建这个 RC 和服务并打开相应的防火墙规则,像我们在上一个示例中做的那样。可能需要一些时间,直到外部负载均衡器的 IP 被传播到get service命令中。一旦它传播完毕,你应该能够通过以下格式打开并在浏览器中看到我们熟悉的 Web 应用程序:

http://<external service IP>:90/

多个端口

另一个自定义端口的使用案例是多个端口。许多应用程序会暴露多个端口,比如 HTTP 在端口80和端口8888上为 Web 服务器提供服务。以下示例展示了我们的应用程序在这两个端口上的响应。同样,我们也需要为这个端口添加一个防火墙规则,像我们之前为nodejs-service-nodeport.yaml列表做的那样。将列表保存为nodejs-multi-controller.yamlnodejs-multi-service.yaml

apiVersion: v1 
kind: ReplicationController 
metadata: 
  name: node-js-multi 
  labels: 
    name: node-js-multi 
spec: 
  replicas: 3 
  selector: 
    name: node-js-multi 
  template: 
    metadata: 
      labels: 
        name: node-js-multi 
    spec:
      containers: 
      - name: node-js-multi 
        image: jonbaier/node-express-multi:latest 
        ports: 
        - containerPort: 80 
        - containerPort: 8888
apiVersion: v1 
kind: Service 
metadata: 
  name: node-js-multi 
  labels: 
    name: node-js-multi 
spec: 
  type: LoadBalancer 
  ports: 
  - name: http 
    protocol: TCP 
    port: 80 
  - name: fake-admin-http 
    protocol: TCP 
    port: 8888 
  selector: 
    name: node-js-multi 

应用程序和容器本身必须监听这两个端口才能使其正常工作。在此示例中,端口 8888 用于表示一个虚拟的管理界面。例如,如果你想监听端口 443,你需要在服务器上监听一个适当的 SSL 套接字。

Ingress

我们之前讨论过,Kubernetes 如何使用服务抽象作为将流量代理到分布在集群中的后端 pod 的方式。虽然这种方式在扩展和 pod 恢复方面非常有用,但有一些更复杂的路由场景是此设计无法解决的。

为此,Kubernetes 添加了一个 ingress 资源,允许进行自定义代理和负载均衡到后端服务。可以将其视为在流量到达我们的服务之前,路由路径中的额外一层或跳点。就像应用程序有服务和后端 pod 一样,ingress 资源需要一个 Ingress 入口点和一个执行自定义逻辑的 ingress 控制器。入口点定义路由,控制器实际处理路由。这对于捕获通常会被边缘路由器丢弃或转发到集群外部的流量非常有用。

Ingress 本身可以配置为为内部服务提供外部可访问的 URL,终止 SSL,提供基于名称的虚拟主机(就像传统 Web 服务器中的做法),或进行负载均衡。Ingress 本身无法处理请求,但需要额外的 ingress 控制器来实现对象中列出的功能。你会看到 nginx 和其他负载均衡或代理技术作为控制器框架的一部分参与其中。在以下示例中,我们将使用 GCE,但你需要自己部署控制器才能利用此功能。目前流行的一个选择是基于 nginx 的 ingress-nginx 控制器。

你可以在这里查看:github.com/kubernetes/ingress-gce/blob/master/BETA_LIMITATIONS.md#glbc-beta-limitations

Ingress 控制器作为运行守护进程的 pod 部署。该 pod 监视 Kubernetes apiserver/ingresses 端点,查看 ingress 资源的变化。对于我们的示例,我们将使用默认的 GCE 后端。

Ingress 的类型

有几种不同类型的 ingress,例如:

  • 单服务 ingress:此策略通过创建一个没有规则的默认后端的 ingress 来暴露单个服务。你也可以使用 Service.Type=LoadBalancerService.Type=NodePort,或使用端口代理来实现类似的功能。

  • Fanout:由于 IP 地址仅在 Kubernetes 网络内部可用,因此你需要使用简单的 fanout 策略来适应边缘流量,并为集群中的正确端点提供 ingress。这在实践中类似于负载均衡器。

  • 基于名称的托管: 这种方法类似于服务名称指示(SNI),它允许 Web 服务器在同一个 TCP 端口和 IP 地址上呈现多个 HTTPS 网站,并使用不同的证书。

Kubernetes 使用主机头来路由请求。在以下示例片段ingress-example.yaml中,展示了基于名称的虚拟托管的样子:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: name-based-hosting
spec:
  rules:
  - host: example01.foo.com
    http:
      paths:
      - backend:
          serviceName: sevice01
          servicePort: 8080
  - host: example02.foo.com
    http:
      paths:
      - backend:
          serviceName: sevice02
          servicePort: 8080

正如你可能记得的,在第一章《Kubernetes 简介》中,我们看到 GCE 集群默认带有提供第 7 层负载均衡功能的后端。如果我们查看kube-system命名空间,就能看到这个控制器正在运行:

$ kubectl get rc --namespace=kube-system

我们应该看到一个名为l7-default-backend-v1.0的 RC,如下所示:

GCE 第 7 层 Ingress 控制器

这提供了 Ingress 控制器部分,实际上它会路由我们在 Ingress 入口点中定义的流量。让我们为 Ingress 创建一些资源。

首先,我们将创建一些新的复制控制器,使用httpwhalesay镜像。这是原始 whalesay 的重混版,原始 whalesay 在浏览器中显示。以下列出了whale-rcs.yaml中的 YAML 配置。注意那三个破折号,它们允许我们将多个资源合并到一个 YAML 文件中:

apiVersion: v1
kind: ReplicationController
metadata:
  name: whale-ingress-a
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: whale-ingress-a
    spec:
      containers:
      - name: sayhey
        image: jonbaier/httpwhalesay:0.1
        command: ["node", "index.js", "Whale Type A, Here."]
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: ReplicationController
metadata:
  name: whale-ingress-b
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: whale-ingress-b
    spec:
      containers:
      - name: sayhey
        image: jonbaier/httpwhalesay:0.1
        command: ["node", "index.js", "Hey man, It's Whale B, Just
        Chillin'."]
        ports:
        - containerPort: 80

请注意,我们正在创建具有相同容器但不同启动参数的 Pod。记下这些参数,稍后会用到。我们还将为这些 RC 创建Service端点,如whale-svcs.yaml列出的那样:

apiVersion: v1
kind: Service
metadata:
  name: whale-svc-a
  labels:
    app: whale-ingress-a
spec:
  type: NodePort
  ports:
  - port: 80
    nodePort: 30301
    protocol: TCP
    name: http
  selector:
    app: whale-ingress-a
---
apiVersion: v1
kind: Service
metadata:
  name: whale-svc-b
  labels:
    app: whale-ingress-b
spec:
  type: NodePort
  ports:
  - port: 80
    nodePort: 30284
    protocol: TCP
    name: http
  selector:
    app: whale-ingress-b
---
apiVersion: v1
kind: Service
metadata:
 name: whale-svc-default
 labels:
   app: whale-ingress-a
spec:
  type: NodePort
  ports:
  - port: 80
    nodePort: 30302
    protocol: TCP
    name: http
  selector:
    app: whale-ingress-a

再次使用kubectl create -f命令创建这些,如下所示:

$ kubectl create -f whale-rcs.yaml $ kubectl create -f whale-svcs.yaml

我们应该看到关于成功创建 RC 和服务的消息。接下来,我们需要定义 Ingress 入口点。我们将使用http://a.whale.heyhttp://b.whale.hey作为我们的演示入口点,如下所示的whale-ingress.yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: whale-ingress
spec:
  rules:
  - host: a.whale.hey
    http:
      paths:
      - path: /
        backend:
          serviceName: whale-svc-a
          servicePort: 80
  - host: b.whale.hey
    http:
      paths:
      - path: /
        backend:
          serviceName: whale-svc-b
          servicePort: 80

再次使用kubectl create -f命令创建这个 Ingress。创建成功后,我们需要等待一会儿,直到 GCE 为 Ingress 分配一个静态 IP 地址。使用以下命令来监视 Ingress 资源:

$ kubectl get ingress

一旦 Ingress 获得 IP 地址,我们应该在ADDRESS中看到一条条目,如下所示:

Ingress 描述

由于这不是一个注册的域名,我们需要在curl命令中指定解析,像这样:

$ curl --resolve a.whale.hey:80:130.211.24.177 http://a.whale.hey/

这应该显示以下内容:

Whalesay A

我们还可以尝试第二个 URL。这样做,我们将获得第二个 RC:

$ curl --resolve b.whale.hey:80:130.211.24.177 http://b.whale.hey/

Whalesay B

请注意,这些镜像几乎相同,只是每个鲸鱼的文字反映了我们之前启动的每个 RC 的启动参数。因此,我们的两个 Ingress 点正在将流量引导到不同的后端。

在这个示例中,我们使用了默认的 GCE 后端作为 Ingress 控制器。Kubernetes 允许我们构建自己的控制器,而 nginx 也有几个可用版本。

迁移、多集群等

正如我们到目前为止所看到的,Kubernetes 提供了高度的灵活性和定制性,用于在集群中创建围绕容器的服务抽象。然而,有时你可能希望指向集群外部的某些内容。

这类情况的一个例子是与遗留系统或甚至在另一个集群上运行的应用程序进行合作。对于前者来说,这是一个完全有效的策略,用于迁移到 Kubernetes 和容器化。我们可以通过在 Kubernetes 中管理服务端点,同时利用 K8s 的编排概念将堆栈拼接起来,作为开始。此外,我们甚至可以开始逐步引入堆栈的各个部分,比如前端,当组织重构应用程序为微服务和/或容器化时。

为了允许访问非 Pod 基础的应用程序,服务构造允许你使用集群外部的端点。实际上,每次你创建一个使用选择器的服务时,Kubernetes 都会创建一个端点资源。endpoints 对象会跟踪负载均衡池中的 Pod IP 地址。你可以通过运行 get endpoints 命令看到这一点,如下所示:

$ kubectl get endpoints

你应该看到类似下面的内容:

NAME               ENDPOINTS
http-pd            10.244.2.29:80,10.244.2.30:80,10.244.3.16:80
kubernetes         10.240.0.2:443
node-js            10.244.0.12:80,10.244.2.24:80,10.244.3.13:80

你会注意到当前集群上运行的所有服务的条目。对于大多数服务,端点只是每个在 RC 中运行的 Pod 的 IP 地址。如前所述,Kubernetes 会根据选择器自动执行此操作。当我们在控制器中扩展具有匹配标签的副本时,Kubernetes 会自动更新端点。

如果我们想为一个不是 Pod 的服务创建一个服务,而该服务没有标签可以选择,我们可以通过定义 nodejs-custom-service.yaml 服务和 nodejs-custom-endpoint.yaml 端点,轻松实现,如下所示:

apiVersion: v1 
kind: Service 
metadata: 
  name: custom-service 
spec: 
  type: LoadBalancer 
  ports: 
  - name: http 
    protocol: TCP 
    port: 80
apiVersion: v1 
kind: Endpoints 
metadata: 
  name: custom-service 
subsets: 
- addresses: 
  - ip: <X.X.X.X> 
  ports: 
    - name: http 
      port: 80 
      protocol: TCP 

在前面的示例中,你需要将 <X.X.X.X> 替换为一个真实的 IP 地址,新的服务可以指向该地址。在我的案例中,我使用了之前在 ingress-example.yaml 中创建的 node-js-multi 服务的公共负载均衡器 IP。现在可以开始创建这些资源了。

如果我们现在运行 get endpoints 命令,我们将在 80 端口看到这个 IP 地址,它与 custom-service 端点关联。此外,如果我们查看服务详情,我们将在 Endpoints 部分看到该 IP 地址:

$ kubectl describe service/custom-service

我们可以通过浏览器打开 custom-service 的外部 IP 来测试这个新服务。

自定义地址

定制服务的另一个选项是使用 clusterIP 元素。在到目前为止的示例中,我们没有指定 IP 地址,这意味着它为我们选择了服务的内部地址。然而,我们可以添加这个元素,并提前选择 IP 地址,像这样:clusterip: 10.0.125.105

有时您可能不希望负载均衡,而是希望为每个 pod 使用A记录的 DNS。例如,需要将数据均匀复制到所有节点的软件可能依赖于A记录来分发数据。在这种情况下,我们可以使用以下示例,并将clusterip设置为None

Kubernetes 不会为每个 pod 分配 IP 地址,而是仅为每个 pod 分配A记录。如果使用 DNS,则从集群内部可以通过node-js-nonenode-js-none.default.cluster.local访问服务。为此,我们将使用以下清单nodejs-headless-service.yaml

apiVersion: v1 
kind: Service 
metadata: 
  name: node-js-none 
  labels: 
    name: node-js-none 
spec: 
  clusterIP: None 
  ports: 
  - port: 80 
  selector: 
    name: node-js 

创建此服务后,请使用可靠的exec命令测试它:

$ kubectl exec node-js-pod -- curl node-js-none

DNS

DNS 通过允许我们按名称引用服务来解决环境变量带来的问题。随着服务的重新启动、扩展或新增,DNS 条目会更新,确保服务名称始终指向最新的基础设施。大多数支持的提供商默认设置了 DNS。您可以通过添加集群附加组件来为您的集群添加 DNS 支持(kubernetes.io/docs/concepts/cluster-administration/addons/)。

如果您的提供商支持 DNS 但未设置,请在创建 Kubernetes 集群时在默认提供商配置中配置以下变量:

ENABLE_CLUSTER_DNS="${KUBE_ENABLE_CLUSTER_DNS:-true}" DNS_SERVER_IP="10.0.0.10"

DNS_DOMAIN="cluster.local"

DNS_REPLICAS=1

启用 DNS 后,服务可以以两种形式访问,即服务名称本身<service-name>或包括命名空间的完全限定名称<service-name>.<namespace-name>.cluster.local。在我们的示例中,它看起来类似于node-js-90node-js-90.default.cluster.local

DNS 服务器基于通过 API 创建的新服务创建 DNS 记录。共享 DNS 命名空间中的 pod 将能够看到彼此,并且可以使用 DNS SRV 记录记录端口。

Kubernetes DNS 由集群中的一个 DNS pod 和一个 Service 组成,它直接与 kubelets 和容器通信,以便将 DNS 名称转换为 IP 地址。具有 clusterIP 的服务被赋予my-service.my-namespace.svc.cluster.local地址。如果服务没有 clusterIP(也称为 headless),它将以轮询方式解析为指向服务 pod 的多个 IP 地址。还有一些可以设置的 DNS 策略。

Kubernetes 孵化项目之一,CoreDNS,也可用于服务发现。它取代了本地的kube-dns DNS 服务,需要 Kubernetes v1.9 或更高版本。在初始化过程中,您需要使用kubeadm来尝试 CoreDNS。您可以使用以下命令在集群上安装它:

$ kubeadm init --feature-gates=CoreDNS=true

如果你想了解更多关于 CoreDNS 的示例使用案例,可以查看这篇博客:coredns.io/2017/05/08/custom-dns-entries-for-kubernetes/

多租户

Kubernetes 还有一个额外的隔离构件,用于集群级别的隔离。在大多数情况下,你可以运行 Kubernetes 而无需担心命名空间;如果没有指定,所有内容都会运行在默认命名空间中。不过,在运行多租户社区或想要进行大规模集群资源隔离和分割的情况下,可以使用命名空间来实现这一目标。虽然 Kubernetes 的端到端多租户功能尚未完全完善,但通过 RBAC、容器权限、Ingress 规则和清晰的网络管理,可以接近这一目标。如果你现在对企业级多租户感兴趣,Red Hat 的Openshift OriginOO)是学习的好地方。

你可以在github.com/openshift/origin上查看 OO。

开始时,Kubernetes 有两个命名空间——defaultkube-systemkube-system命名空间用于我们在第一章《Kubernetes 简介》中的Minion 上运行的服务部分看到的所有系统级容器。UI、日志、DNS 等都运行在kube-system中。用户创建的其他所有内容都运行在默认命名空间中。不过,我们的资源定义文件可以选择性地指定自定义命名空间。为了实验,我们来看一下如何构建一个新的命名空间。

首先,我们需要创建一个命名空间定义文件test-ns.yaml,就像下面的代码行一样:

apiVersion: v1 
kind: Namespace 
metadata: 
  name: test

我们可以继续使用我们方便的create命令来创建这个文件:

$ kubectl create -f test-ns.yaml

现在,我们可以创建使用test命名空间的资源。以下列表,ns-pod.yaml,是使用这个新命名空间的一个 Pod 示例:

apiVersion: v1 
kind: Pod 
metadata: 
  name: utility 
  namespace: test 
spec: 
  containers: 
  - image: debian:latest 
    command: 
      - sleep 
      - "3600" 
    name: utility 

虽然 Pod 仍然可以访问其他命名空间中的服务,但它需要使用<service-name>.<namespace-name>.cluster.local的长 DNS 格式。例如,如果你在ns-pod.yaml中运行命令,可以使用node-js.default.cluster.local来访问第二章《Pods、Services、Replication Controllers 和 Labels》中的 Node.js 示例。

这里有一个关于资源利用的提示。在本书的某个地方,你可能会遇到集群空间不足以创建新的 Kubernetes 资源的情况。具体时间取决于集群的大小,但最好记住这一点,并不时进行清理。使用以下命令删除旧的示例:

$ kubectl delete pod <pod name> $ kubectl delete svc <service name> $ kubectl delete rc <replication controller name> $ kubectl delete rs <replicaset name>

限制

让我们再检查一下我们的新命名空间。运行describe命令,如下所示:

$ kubectl describe namespace/test

以下截图是前面命令的结果:

描述命名空间

Kubernetes 允许你通过配额来限制单个 Pod 或容器使用的资源以及整个命名空间使用的资源。你会注意到,当前在 test 命名空间上没有设置资源限制或配额。

假设我们想要限制这个新命名空间的占用资源,我们可以像下面这样设置配额 quota.yaml

apiVersion: v1 
kind: ResourceQuota 
metadata: 
  name: test-quotas 
  namespace: test 
spec: 
  hard:  
    pods: 3 
    services: 1 
    replicationcontrollers: 1 

实际上,命名空间通常用于较大的应用程序社区,并且其配额通常不会这么低。我在此示例中使用它是为了方便展示此功能。

在这里,我们将为 test 命名空间创建一个 3 个 Pod、1 个 RC 和 1 个服务的配额。正如你可能猜到的,这一切都是通过我们可靠的 create 命令执行的,如下所示:

$ kubectl create -f quota.yaml

既然我们已经设置好了,那就用 describe 查看命名空间,如下所示:

$ kubectl describe namespace/test

以下截图是前述命令的结果:

配额设置后的命名空间描述

你会注意到,我们现在在配额部分列出了一些值,而限制部分仍然是空白的。我们还有一个 Used 列,它让我们知道当前接近配额限制的程度。现在,尝试使用以下定义的 busybox-ns.yaml 启动一些 Pod:

apiVersion: v1 
kind: ReplicationController 
metadata: 
  name: busybox-ns 
  namespace: test 
  labels: 
    name: busybox-ns 
spec: 
  replicas: 4 
  selector: 
    name: busybox-ns 
  template: 
    metadata: 
      labels: 
        name: busybox-ns 
    spec: 
      containers: 
      - name: busybox-ns 
        image: busybox 
        command: 
          - sleep 
          - "3600" 

你会注意到我们正在创建这个基础 Pod 的四个副本。在用 create 创建这个 RC 后,再次运行 describe 命令查看 test 命名空间。你会发现 Pod 和 RC 的 Used 值已达到最大。但我们请求了四个副本,而只能看到使用中的三个 Pod。

让我们看看我们的 RC(复制控制器)发生了什么。你可以尝试用以下命令来查看:

kubectl describe rc/busybox-ns

然而,如果你尝试这样做,你会看到服务器返回 not found 的信息。这是因为我们在一个新命名空间中创建了这个 RC,而 kubectl 默认假设是默认命名空间,如果没有指定的话。这意味着当我们想要访问 test 命名空间中的资源时,每个命令都需要指定 --namespace=test

我们也可以通过修改上下文设置来设置当前命名空间。首先,我们需要找到当前的上下文,使用以下命令可以找到:

**$ kubectl config view | grep current-context**

接下来,我们可以用以下代码设置命名空间变量:

**$ kubectl config set-context <Current Context> --namespace=test**

现在,您可以运行 kubectl 命令而无需指定命名空间。只需记住在想查看默认命名空间中运行的资源时,切换回去即可。

按照以下命令运行时,指定命名空间。如果你按照提示框所示设置了当前命名空间,那么可以省略 --namespace 参数:

$ kubectl describe rc/busybox-ns --namespace=test

以下截图是前述命令的结果:

命名空间配额

正如前面图片所示,前三个 pod 已成功创建,但最后一个 pod 因为出现 Limited to 3 pods 错误而创建失败。

这是为社区规模资源分配设置限制的简便方法。值得注意的是,你还可以为 CPU、内存、持久卷和机密设置配额。此外,限制与配额的工作方式类似,但它为每个命名空间内的 pod 或容器设置限制。

关于资源使用的说明

由于本书中的大多数示例都使用 GCP 或 AWS,因此保持所有服务运行可能会非常昂贵。如果你一直保持所有示例运行,使用默认集群大小容易耗尽资源。因此,你可能需要定期删除旧的 pod、复制控制器、副本集和服务。你还可以销毁集群并使用第一章 Kubernetes 简介 中的内容重新创建集群,从而降低云服务商账单。

总结

在本章中,我们深入探讨了 Kubernetes 中的网络和服务。你现在应该理解了 K8s 中网络通信的设计,并能够舒适地访问你的服务,无论是内部访问还是外部访问。我们看到 kube-proxy 如何在本地和整个集群中平衡流量。此外,我们还探讨了新的 Ingress 资源,它们使我们能更精细地控制传入流量。我们还简要了解了 Kubernetes 中如何实现 DNS 和服务发现。最后,我们简要回顾了命名空间和多租户隔离。

问题

  1. 给出 Docker 网络方式与 Kubernetes 网络方式不同的两种方式。

  2. NAT 代表什么?

  3. Kubernetes 网络模型的两大类是什么?

  4. 至少列出两种可供 Kubernetes 使用的第三方覆盖网络选项。

  5. Kubernetes 在哪个层级(或替代地,在哪个对象上)分配 IP 地址?

  6. kube-proxy 可用的模式有哪些?

  7. Kubernetes 允许的三种服务类型是什么?

  8. 用于定义容器和服务端口的元素是什么?

  9. 列出两种或更多 Kubernetes 可用的 Ingress 类型。

  10. 如何为你的 Kubernetes 集群提供多租户支持?

深入阅读

第四章:实现可靠的容器原生应用程序

本章将介绍 Kubernetes 支持的各种类型的工作负载。我们将讨论适用于经常更新且长期运行的应用程序的部署。我们还将重新探讨应用程序更新和使用部署进行渐进式发布的话题。此外,我们将介绍用于短期任务的作业。我们还将研究 DaemonSets,它允许程序在 Kubernetes 集群的每个节点上运行。如果你注意到的话,本章不会涉及 StatefulSets,但我们将在下一章讨论它们,届时我们将探讨存储以及 Kubernetes 如何帮助你管理集群上的存储和有状态应用程序。

本章将涵盖以下内容:

  • 部署

  • 使用部署进行应用程序扩展

  • 使用部署进行应用程序更新

  • 作业

  • DaemonSets

技术要求

你需要一个正在运行的 Kubernetes 集群,就像我们在前几章中创建的那样。你还需要通过 kubectl 命令访问该集群并进行部署。

本章的 GitHub 仓库链接:github.com/PacktPublishing/Getting-Started-with-Kubernetes-third-edition/tree/master/Code-files/Chapter04

Kubernetes 如何管理状态

如前所述,我们知道 Kubernetes 努力在给定的集群中强制执行操作员期望的状态。部署使操作员能够定义一个最终状态,并通过控制速率的机制对无状态服务(如微服务)进行更改。由于 Kubernetes 是一个管理元数据、当前状态和一组对象规范的控制平面和数据平面,部署为你的应用程序提供了更深层次的控制。可以使用几种典型的部署模式:重建、滚动更新、通过选择器的蓝绿发布、通过副本的金丝雀发布和通过 HTTP 头的 A/B 测试。

部署

在上一章中,我们探讨了使用旧的滚动更新方法进行应用程序更新的一些核心概念。从版本 1.2 开始,Kubernetes 添加了 Deployment 构造,它改进了滚动更新和 ReplicationControllers 的基本机制。顾名思义,它使我们能够对代码部署本身进行更精细的控制。部署允许我们通过声明性定义和对 Pod 和 ReplicaSets 的更新来暂停和恢复应用程序的发布。此外,它们还会保留过去部署的历史记录,允许用户轻松回滚到先前的版本。

不再推荐使用 ReplicationControllers,而是使用配置 ReplicaSet 的 Deployment 来为无状态服务或应用程序设置应用可用性。此外,不要直接管理由部署创建的 ReplicaSets;应仅通过 Deployment API 进行管理。

部署的使用案例

我们将更详细地探讨一些部署的典型场景:

  • 部署一个 ReplicaSet

  • 更新一组 Pod 的状态

  • 回滚到先前版本的部署

  • 扩展以适应集群负载

  • 暂停并使用 Deployment 状态来进行更改或指示一个卡住的部署

  • 清理一个部署

在以下的 node-js-deploy.yaml 文件代码中,我们可以看到定义与 ReplicationController 非常相似。主要区别在于,我们现在可以对部署对象进行更改和更新,并让 Kubernetes 管理底层 Pod 和副本的更新:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: node-js-deploy
  labels:
    name: node-js-deploy
spec:
  replicas: 1
  template:
    metadata:
      labels:
        name: node-js-deploy
    spec:
      containers:
      - name: node-js-deploy
        image: jonbaier/pod-scaling:0.1
        ports:
        - containerPort: 80

在这个示例中,我们通过 metadata 下的 name 字段创建了一个名为 node-js-deploy 的 Deployment。我们正在创建一个由 selector 字段管理的单个 Pod,selector 字段将帮助 Deployment 确定管理哪些 Pod。spec 指示该 Pod 运行 jobbaier/pod-scaling 容器,并通过 containerPort 将流量引导到端口 80

我们可以使用熟悉的 create 命令,并附加可选的 --record 标志,这样 Deployment 的创建过程将记录在回滚历史中。否则,我们只能通过 $ kubectl create -f node-js-deploy.yaml --record 命令查看随后的更改记录。

如果您的集群没有启用此 beta 类型,您可能需要添加 --validate=false

我们应该会看到部署成功创建的消息。几秒钟后,它将完成 Pod 的创建,我们可以使用 get pods 命令自行检查。我们添加 -l 标志只查看与此部署相关的 Pods:

$ kubectl get pods -l name=node-js-deploy 

如果您想查看部署的状态,可以执行以下命令:

$ kubectl get deployments

你还可以看到部署状态,这在将来更新我们的部署时会更加有用。你可以使用 kubectl rollout status deployment/node-js-deploy 来查看当前的状态。

我们创建一个服务,就像之前用 ReplicationControllers 时那样。以下是我们刚刚创建的 Deployment 的 Service 定义。请注意,它几乎与我们过去创建的 Services 完全相同。将以下代码保存为 node-js-deploy-service.yaml 文件:

apiVersion: v1
kind: Service
metadata:
  name: node-js-deploy
  labels:
    name: node-js-deploy
spec:
  type: LoadBalancer
  ports:
  - port: 80
  sessionAffinity: ClientIP
  selector:
    name: node-js-deploy

一旦通过 kubectl 创建了这个服务,您就可以通过服务 IP 或服务名称访问部署的 Pods,前提是您位于该命名空间的某个 Pod 内。

扩展

scale 命令的工作方式与我们在 ReplicationController 中一样。要扩展副本数,我们只需要使用部署名称并指定新的副本数,如下所示:

$ kubectl scale deployment node-js-deploy --replicas 3

如果一切顺利,我们将在终端窗口的输出中看到一个关于部署被扩展的消息。我们可以使用之前的get pods命令检查当前运行的 pod 数量。在最新版本的 Kubernetes 中,你还可以为集群设置 pod 扩展,这样你就可以根据集群的 CPU 利用率进行水平自动扩展,从而扩展 pods。你需要设置 pod 的最大和最小数量才能启动这个功能。

这是该命令在这个示例中的样子:

$ kubectl autoscale deployment node-js-deploy --min=25 --max=30 --cpu-percent=75
deployment "node-js-deploy" autoscaled

了解更多关于水平 pod 扩展的信息,请参阅此教程:kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/

还有一个比例扩展的概念,它允许你同时运行多个版本的应用程序。例如,当你增量更新一个向后兼容的 API 微服务版本时,这种实现会非常有用。在进行这种类型的部署时,你将使用.spec.strategy.rollingUpdate.maxUnavailable.spec.strategy.rollingUpdate.maxSurge来限制更新期间部署中不可用的最大 pod 数量,或限制超过所需 pod 数量的最大创建 pod 数量。

更新与滚动发布

部署允许通过几种不同的方式进行更新。首先是kubectl set命令,它允许我们更改部署配置而无需手动重新部署。目前,它只允许更新镜像,但随着应用程序或容器镜像的新版本的处理,我们将需要频繁地执行此操作。

让我们回顾一下前一节中的部署。我们现在应该有三个副本在运行。通过运行get pods命令并加上部署筛选器来验证这一点:

$ kubectl get pods -l name=node-js-deploy

我们应该看到三个 pod,类似于下面截图中的内容:

部署 pod 列表

选择我们设置中的一个 pod,将其替换为以下命令中的{POD_NAME_FROM_YOUR_LISTING},并运行此命令:

$ kubectl describe pod/{POD_NAME_FROM_YOUR_LISTING} | grep Image:

我们应该看到类似下面截图的输出,当前镜像版本为0.1

当前 pod 镜像

既然我们知道当前的部署在运行什么版本,接下来我们试着更新到下一个版本。这可以通过使用kubectl set命令并指定新版本来轻松实现,如下所示:

$ kubectl set image deployment/node-js-deploy node-js-deploy=jonbaier/pod-scaling:0.2
$ deployment "node-js-deploy" image updated

如果一切顺利,我们应该在屏幕上看到显示deployment "node-js-deploy" image updated的文本。

我们可以通过以下rollout status命令再次检查状态:

$ kubectl rollout status deployment/node-js-deploy

另外,我们也可以在编辑器窗口中直接编辑部署,使用kubectl edit deployment/node-js-deploy,并将.spec.template.spec.containers[0].imagejonbaier/pod-scaling:0.1更改为jonbaier/pod-scaling:0.2。这两种方法都可以更新你的部署,提醒一下,你可以使用kubectl status命令检查更新状态:

$ kubectl rollout status deployment/node-js-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment "node-js-deployment" successfully rolled out

我们应该看到一些文本,说明部署已经成功推出。如果你看到任何关于等待推出完成的文本,可能需要等一下它完成,或者检查日志寻找问题。

完成后,像之前一样运行get pods命令。这一次,我们将看到列出了新的 pods:

更新后的部署 pod 列表

再次,将你其中一个 pod 的名称输入到我们之前运行的describe命令中。这次,我们应该看到镜像已经更新为 0.2。

背后发生的事情是 Kubernetes 已经为我们推出了一个新版本。它基本上创建了一个新的 ReplicaSet 并使用新版本。一旦这个 pod 上线并且健康,它就会终止一个旧版本的 pod。它会继续这种行为,扩展新版本并缩减旧版本,直到只剩下新的 pod。另一种间接观察此行为的方法是查看 Deployment 对象用于更新所需应用状态的 ReplicaSet。

记住,你不是直接与 ReplicaSet 交互,而是通过 Deployment 元素向 Kubernetes 提供指令,并让 Kubernetes 根据这些指令对集群对象存储和状态进行必要的更改。快速查看你运行image update命令后创建的 ReplicaSets,你将看到如何通过多个 ReplicaSets 实现镜像更改而不发生应用停机:

$ kubectl get rs
NAME                               DESIRED CURRENT READY AGE
node-js-deploy-1556879905          3       3       3     46s
node-js-deploy-4657899444          0       0       0     85s

以下图表描述了工作流,供你参考:

部署生命周期

值得注意的是,回滚定义允许我们在部署定义中控制 pod 替换方法。有一个strategy.type字段,默认为RollingUpdate和之前的行为。我们还可以选择将Recreate作为替代策略,先终止所有旧的 pods,然后再创建新版本的 pods。

历史和回滚

rollout API 的一个有用功能是能够跟踪部署历史。在查看历史之前,让我们再进行一次更新。再次运行kubectl set命令,并指定版本 0.3:

$ kubectl set image deployment/node-js-deploy node-js-deploy=jonbaier/pod-scaling:0.3
$ deployment "node-js-deploy" image updated 

我们再次会看到屏幕上显示deployment "node-js-deploy" image updated的文本。现在,再次运行get pods命令:

$ kubectl get pods -l name=node-js-deploy

让我们也来看看我们的部署历史。运行rollout history命令:

$ kubectl rollout history deployment/node-js-deploy 

我们应该看到类似以下的输出:

部署历史

如我们所见,历史记录显示了初始的发布创建、我们第一次更新到 0.2 以及最后更新到 0.3。除了状态和历史记录,rollout命令还支持pauseresumeundo子命令。rollout pause命令允许我们在发布过程中暂停命令。这在故障排除时非常有用,也适用于金丝雀发布类型的部署,我们希望在发布到整个用户群之前做最后的版本测试。当我们准备继续发布时,可以简单地使用rollout resume命令。

但是,如果出现问题怎么办?这时候,rollout undo命令和发布历史记录就非常有用了。我们通过尝试更新到一个尚未可用的 Pod 版本来模拟这个问题。我们将镜像设置为 42.0 版本,但该版本并不存在:

$ kubectl set image deployment/node-js-deploy node-js-deploy=jonbaier/pod-scaling:42.0

我们仍然应该看到屏幕上显示deployment "node-js-deploy" image updated的文本。但是如果检查状态,我们会看到它仍在等待:

$ kubectl rollout status deployment/node-js-deploy
Waiting for rollout to finish: 2 out of 3 new replicas have been updated... 

在这里,我们看到发布在更新了三个 Pod 中的两个后已暂停,但 Kubernetes 足够智能,知道在此停止,以防止由于容器镜像名称错误导致整个应用程序下线。我们可以按Ctrl + C来终止status命令,然后再次运行get pods命令:

$ kubectl get pods -l name=node-js-deploy

现在我们应该看到ErrImagePull,如下图所示:

镜像拉取错误

正如我们预期的那样,它无法拉取 42.0 版本的镜像,因为该版本并不存在。这个错误是指容器陷入了镜像拉取循环,最新版本的 Kubernetes 中标记为ImagePullBackoff。如果我们在集群中资源耗尽或达到命名空间设置的限制,也可能会遇到发布问题。当然,发布失败还可能是由于一些应用相关的原因,比如健康检查失败、权限问题和应用程序缺陷等。

如果不改变maxUnavailablespec.replicas的值,完全有可能创建一个完全不可用的发布,因为默认值都是1

每当发布失败时,我们可以轻松地通过rollout undo命令回滚到之前的版本。此命令会将我们的发布回滚到上一个版本:

$ kubectl rollout undo deployment/node-js-deploy

之后,我们可以再运行一次rollout status命令,应该会看到所有内容都成功发布。再次运行kubectl rollout history deployment/node-js-deploy命令,我们将看到我们尝试发布版本 42.0 并回滚到 0.3 的记录:

回滚后的发布历史

我们也可以在运行撤销操作时指定--to-revision标志,以回滚到特定版本。这在我们的发布成功,但在后期发现逻辑错误时非常有用。

自动伸缩

如你所见,Deployment 比 ReplicationController 有了很大的改进,使我们能够无缝地更新应用程序,同时与 Kubernetes 的其他资源集成,方式类似。

另一个我们在上一章看到的领域,也是支持 Deployment 的,是水平 Pod 自动扩缩HPAs)。HPA 帮助你通过根据 CPU 利用率自动调整 pod 数量来管理集群资源利用率。通过 HPA 可以扩缩的对象有三个,不包括 DaemonSets:

  • Deployment(推荐方法)

  • ReplicaSet

  • ReplicationController(不推荐)

HPA 作为一个控制循环实现,类似于我们之前讨论过的其他控制器,你可以通过调整其同步周期来调节控制器管理器的灵敏度,方法是使用--horizontal-pod-autoscaler-sync-period(默认值为 30 秒)。

我们将通过快速重建上一章的 HPAs,这次使用我们目前创建的 Deployments。将以下代码保存到node-js-deploy-hpa.yaml文件中:

apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: node-js-deploy
spec:
  minReplicas: 3
  maxReplicas: 6
  scaleTargetRef:
    apiVersion: v1
    kind: Deployment
    name: node-js-deploy
  targetCPUUtilizationPercentage: 10

由于这些工具仍处于测试版,因此 API 正在迅速变化,请注意apiVersion元素,它以前是autoscaling/v1,但现在是autoscalingv2beta1

我们已将 CPU 阈值降低到 10%,并将最小和最大 pod 数量分别更改为36。使用我们可靠的kubectl create -f命令创建上述 HPA。完成后,我们可以通过kubectl get hpa命令检查它是否可用:

水平 Pod 自动扩缩器

我们还可以通过kubectl get deploy命令检查是否只有3个 pod 在运行。现在,让我们增加一些负载来触发自动扩缩器:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: boomload-deploy
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: loadgenerator-deploy
    spec:
      containers:
      - image: williamyeh/boom
        name: boom-deploy
        command: ["/bin/sh","-c"]
        args: ["while true ; do boom http://node-js-deploy/ -c 100 -n 500 ; sleep 1 ; done"]

按照惯例创建boomload-deploy.yaml文件。现在,使用交替的kubectl get hpakubectl get deploy命令来监控 HPA。几分钟后,我们应该看到负载跃升超过10%。再过一会儿,我们还应该看到 pod 的数量增加,直到达到6个副本:

HPA 增加并且 pod 扩展

同样,我们可以通过删除负载生成 pod 并等待片刻来清理这个过程:

$ kubectl delete deploy boomload-deploy

同样,如果我们监视 HPA,开始会看到 CPU 使用率下降。几分钟后,我们将回到0%的 CPU 负载,然后 Deployment 将缩放回3个副本。

Jobs

Deployment 和 ReplicationController 是确保长期运行的应用程序始终保持在线并能够容忍各种基础设施故障的好方法。然而,仍有一些用例不适用,特别是短期运行的、仅执行一次的任务以及定期计划任务。在这两种情况下,我们需要任务运行直到完成,然后终止,并在下一个计划的时间间隔重新启动。

为了应对这种类型的工作负载,Kubernetes 增加了 batch API,其中包括 Job 类型。该类型将创建 1 到 n 个 Pod,并确保它们都成功完成并退出。根据 restartPolicy,我们可以选择让 Pod 在失败后不重试(restartPolicy: Never),或在 Pod 未成功完成时进行重试(restartPolicy: OnFailure)。在这个示例中,我们将使用后者技术,如 longtask.yaml 列表所示:

apiVersion: batch/v1
kind: Job
metadata:
  name: long-task
spec:
  template:
    metadata:
      name: long-task
    spec:
      containers:
      - name: long-task
        image: docker/whalesay
        command: ["cowsay", "Finishing that task in a jiffy"]
      restartPolicy: OnFailure

让我们使用以下命令来运行它:

$ kubectl create -f longtask.yaml

如果一切顺利,你将看到屏幕上打印出 job "long-task" created

这告诉我们作业已经创建,但并没有告诉我们是否成功完成。要检查这一点,我们需要使用以下命令查询作业状态:

$ kubectl describe jobs/long-task

作业状态

你应该会看到有 1 个任务成功,并且在 Events 日志中,我们有一条 SuccessfulCreate 消息。如果我们使用 kubectl get pods 命令,我们将看不到 long-task Pod 在列表中,但可能会注意到列表底部有一条消息,说明有已完成的作业未显示。我们需要再次运行命令,并加上 -a--show-all 标志,才能看到 long-task Pod 和已完成的作业状态。

让我们深入一点,验证任务是否成功完成。我们可以使用 logs 命令查看 Pod 日志。但是,我们也可以使用 UI 来完成这项任务。打开浏览器并访问以下 UI URL:https://<your master ip>/ui/

点击 “Jobs”,然后从列表中选择 long-task,这样我们就可以查看详细信息。接着,在 Pods 部分,点击列出的 Pod。这将带我们进入 Pod 详细信息页面。在页面底部,点击 “View Logs”,我们将看到日志输出:

作业日志

正如你在前面的截图中看到的,whalesay 容器已经完成,并且显示了 ASCII 艺术和我们在示例中通过运行时参数传递的自定义消息。

其他类型的作业

虽然这个示例提供了短期运行任务的基础介绍,但它仅处理一次性任务的使用案例。实际上,批量任务通常是并行执行的,或者作为定期任务的一部分。

并行作业

使用并行作业时,我们可能会从一个正在进行的队列中获取任务,或者仅运行一组相互独立的任务。在从队列中提取任务的情况下,我们的应用程序必须了解任务之间的依赖关系,并具有逻辑来决定如何处理任务以及接下来要处理的任务。Kubernetes 只是调度这些作业。

你可以通过 Kubernetes 文档和批处理 API 参考了解更多关于并行作业的内容。

定时作业

对于需要定期运行的任务,Kubernetes 还发布了一个 alpha 版本的 CronJob 类型。正如我们预期的那样,这种类型的任务使用底层的 cron 格式来指定我们希望运行任务的时间表。默认情况下,我们的集群将不会启用 alpha 批处理功能,但我们可以查看一个示例 CronJob 列表,了解这些类型的工作负载将如何在未来运行。将以下代码保存为 longtask-cron.yaml 文件:

apiVersion: batch/v2alpha1
kind: CronJob
metadata:
  name: long-task-cron
spec:
  schedule: "15 10 * * 6"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: long-task-cron
            image: docker/whalesay
            command: ["cowsay", "Developers! Developers! Developers!
          \n\n Saturday task    
            complete!"]
          restartPolicy: OnFailure

如你所见,调度部分反映了一个 cron 表,格式为:分钟 小时 月日 月份 星期几。在此示例中,15 10 * * 6 创建了一个每周六上午 10:15 运行的任务。

DaemonSets

虽然 ReplicationControllers 和 Deployments 很擅长确保特定数量的应用实例在运行,但它们是在最佳匹配的上下文中进行工作的。这意味着调度器会寻找满足资源需求(如可用 CPU、特定存储卷等)的节点,并尝试将负载分配到不同的节点和区域。

这种方式非常适合创建高可用和容错的应用程序,但如果我们需要一个代理在集群中的每个节点上都运行,该怎么办呢?虽然默认的调度会尝试使用不同的节点,但它并不能保证每个节点都有一个副本,实际上,它只会填充与 ReplicationController 或 Deployment 规格中指定的数量等量的节点。

为了减轻这一负担,Kubernetes 引入了 DaemonSet,它简单地定义了一个 Pod,在集群中的每个节点或这些节点的定义子集上运行。这对于一些生产相关的活动非常有用,例如监控和日志代理、安全代理以及文件系统守护进程。

在 Kubernetes 1.6 版本中,RollingUpdate 被添加为 DaemonSet 对象的更新策略。此功能允许你根据 spec.template 的更新对 Pod 执行串行更新。在下一个版本 1.7 中,添加了历史记录功能,操作员可以根据 spec.template 的修订历史回滚更新。

你可以通过以下 kubectl 示例命令回滚部署:

$ kubectl rollout history ds example-app --revision=2 

实际上,Kubernetes 已经在其一些核心系统组件中使用了这些功能。如果我们回顾 第一章,Kubernetes 简介,我们看到 node-problem-detector 正在节点上运行。这个 Pod 实际上是在集群中的每个节点上作为 DaemonSet 运行的。我们可以通过查询 kube-system 命名空间中的 DaemonSets 来看到这一点:

$ kubectl get ds --namespace=kube-system

kube-system DaemonSets

你可以在以下 node-problem-detector 定义 列表中找到关于 node-problem-detectoryaml 的更多信息:kubernetes.io/docs/admin/node-problem/#node-problem-detector

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-problem-detector-v0.1
  namespace: kube-system
  labels:
    k8s-app: node-problem-detector
    version: v0.1
    kubernetes.io/cluster-service: "true"
spec:
  template:
    metadata:
      labels:
        k8s-app: node-problem-detector
        version: v0.1
        kubernetes.io/cluster-service: "true"
    spec:
      hostNetwork: true
      containers:
      - name: node-problem-detector
        image: gcr.io/google_containers/node-problem-detector:v0.1
        securityContext:
          privileged: true
        resources:
          limits:
            cpu: "200m"
            memory: "100Mi"
          requests:
            cpu: "20m"
            memory: "20Mi"
        volumeMounts:
        - name: log
          mountPath: /log
          readOnly: true
        volumes:
        - name: log
          hostPath:
            path: /var/log/

节点选择

如前所述,我们也可以安排 DaemonSets 在节点的一个子集上运行。这可以通过称为nodeSelectors的东西来实现。它们允许我们通过查找特定的标签和元数据来约束 pod 运行的节点。它们只是简单地匹配每个节点标签上的键值对。

默认标签列在以下表格中:

默认节点标签 描述
kubernetes.io/hostname 这显示底层实例或机器的主机名
beta.kubernetes.io/os 这显示基础操作系统作为 Go 语言报告
beta.kubernetes.io/arch 这显示基础处理器架构作为 Go 语言报告
beta.kubernetes.io/instance-type 这是底层云提供商的实例类型(仅限云)
failure-domain.beta.kubernetes.io/region 这是底层云提供商的区域(仅限云)
failure-domain.beta.kubernetes.io/zone 这是底层云提供商的容错区域(仅限云)

表 5.1 - Kubernetes 默认节点标签

我们不仅限于 DaemonSets,因为 nodeSelectors 实际上也适用于 pod 定义。让我们更详细地看一下作业示例(稍微修改了我们之前的长期任务示例)。

首先,我们可以在节点本身看到这些。让我们获取我们节点的名称:

$ kubectl get nodes

使用前一个命令的输出中的名称,并将其插入到此命令中:

$ kubectl describe node <node-name>

从节点描述中摘录

现在让我们为此节点添加一个昵称标签:

$ kubectl label nodes <node-name> nodenickname=trusty-steve

如果我们再次运行kubectl describe node命令,我们将看到此标签列在默认标签旁边。现在,我们可以安排工作负载并指定此特定节点。以下列表longtask-nodeselector.yaml是我们之前长时间运行任务的修改版本,添加了nodeSelector

apiVersion: batch/v1
kind: Job
metadata:
  name: long-task-ns
spec:
  template:
    metadata:
      name: long-task-ns
    spec:
      containers:
      - name: long-task-ns
        image: docker/whalesay
        command: ["cowsay", "Finishing that task in a jiffy"]
      restartPolicy: OnFailure
      nodeSelector:
        nodenickname: trusty-steve

使用kubectl create -f从此列表创建作业。

一旦成功,它将根据前述规范创建一个 pod。由于我们定义了nodeSelector,它将尝试在具有匹配标签的节点上运行 pod,并在找不到候选者时失败。我们可以通过在查询中指定作业名称来找到该 pod,如下所示:

$ kubectl get pods -a -l job-name=long-task-ns

我们使用-a标志显示所有 pod。作业的生命周期很短,一旦进入完成状态,它们将不会在基本的kubectl get pods查询中显示。我们还使用-l标志指定带有job-name=long-task-ns标签的 pod。这将给出 pod 名称,我们可以将其推入以下命令:

$ kubectl describe pod <Pod-Name-For-Job> | grep Node:

结果应显示此 pod 运行在的节点名称。如果一切顺利,它应与我们之前使用trusty-steve标签标记的节点匹配。

总结

现在,你应该已经掌握了 Kubernetes 核心构件的基础。我们探讨了新的部署抽象及其如何改进基本的 ReplicationController,支持平滑更新,并与服务和自动伸缩实现紧密集成。我们还研究了其他类型的工作负载,如作业(Jobs)和 DaemonSets。你学会了如何运行短时或批处理任务,以及如何在集群中的每个节点上运行代理。最后,我们简要了解了节点选择以及如何使用它来过滤集群中用于工作负载的节点。

我们将在本章所学的基础上,进入下一章探讨有状态应用程序,深入了解关键应用组件以及数据本身。

问题

  1. 请列举四种 Kubernetes 部署的使用场景。

  2. 部署定义中的哪个元素告诉部署管理哪个 Pod?

  3. 你需要激活哪个标志才能查看你的更改历史?

  4. 部署使用什么底层机制(实际上是一个 Kubernetes 对象)来更新你的容器镜像?

  5. 使 Pod 根据 CPU 负载进行伸缩的技术名称是什么?

  6. 对于一个短暂、短生命周期的任务,你应该运行哪种类型的工作负载?

  7. DaemonSet 的目的是什么?

第五章:探索 Kubernetes 存储概念

为了支持现代微服务和其他无状态应用程序,Kubernetes 操作员需要有一种方法来管理集群上的有状态数据存储。尽管将尽可能多的状态保留在集群外部的专用数据库集群中,作为云原生服务的一部分是有优势的,但通常也需要为无状态和临时服务保持一个记录或状态集群。我们将探讨在容器编排和调度领域中被认为是一个更困难的问题:在一个依赖声明性状态、将物理设备与逻辑对象解耦,并且采用不可变系统更新方式的世界中,如何管理特定位置的可变数据。我们将探索为现代数据库引擎设置可靠、复制存储的策略。

在本章中,我们将讨论如何附加持久卷并为有状态应用程序和数据创建存储。我们将介绍存储相关问题,以及如何在多个 Pod 和容器生命周期之间持久化数据。我们将探讨PersistentVolumes类型,以及PersistentVolumeClaim。最后,我们将查看 StatefulSets 及如何使用动态卷供给。

本章将涵盖以下主题:

  • 持久存储

  • PersistentVolumes

  • PersistentVolumeClaim

  • 存储类

  • 动态卷供给

  • StatefulSets

技术要求

你需要有一个正在运行的 Kubernetes 集群来进行这些示例操作。请在你选择的云提供商上启动集群,或使用本地的 Minikube 实例。

本仓库的代码可以在这里找到:github.com/PacktPublishing/Getting-Started-with-Kubernetes-third-edition/tree/master/Code-files/Chapter05.

持久存储

到目前为止,我们只处理了那些可以随时启动和停止的工作负载,没有任何问题。然而,现实世界中的应用程序通常携带状态并记录数据,这些数据我们希望(甚至坚持)不丢失。容器本身的瞬态特性可能是一个巨大的挑战。如果你回想一下我们在第一章《Kubernetes 简介》中的分层文件系统讨论,最上层是可写的。(它也很美味。)然而,当容器死亡时,数据也随之消失。对于 Kubernetes 重新启动的崩溃容器也是如此。

这就是卷或磁盘发挥作用的地方。卷存在于容器之外,并且与 Pod 绑定,这使得我们可以在容器宕机时保存重要数据。此外,如果我们在 Pod 层面上有一个卷,数据可以在同一个应用堆栈和同一个 Pod 内的容器之间共享。Kubernetes 中的卷本身是一个目录,Pod 为其上的容器提供这个目录。spec.volumes 中有许多不同类型的卷,我们将会探索这些卷,它们通过 spec.containers.volumeMounts 参数挂载到容器中。

要查看所有可用的卷类型,请访问kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes

Docker 本身对卷有一定的支持,但 Kubernetes 为我们提供了超越单个容器生命周期的持久存储。这些卷与 Pod 绑定,随着 Pod 的生死而生死。此外,Pod 可以有来自多种来源的多个卷。让我们来看看其中的一些来源。

临时磁盘

在容器崩溃和 Pod 内数据共享方面,实现更好持久性的最简单方法之一是使用 emptydir 卷。这种卷类型可以与节点机器本身的存储卷或可选的 RAM 磁盘一起使用,以提供更高的性能。

我们再次改进了单个容器之外的持久化存储,但当 Pod 被移除时,数据将丢失。机器重启也会清除任何来自 RAM 类型磁盘的数据。有时候我们只需要一些共享的临时空间,或者有些容器处理数据后将其交给另一个容器处理,直到它们消失。无论情况如何,这里有一个使用 RAM 支持选项的临时磁盘的快速示例。

打开你喜欢的编辑器,创建一个 storage-memory.yaml 文件并输入以下代码:

apiVersion: v1 
kind: Pod 
metadata: 
  name: memory-pd 
spec: 
  containers: 
  - image: nginx:latest 
    ports: 
    - containerPort: 80 
    name: memory-pd 
    volumeMounts: 
    - mountPath: /memory-pd 
      name: memory-volume 
  volumes: 
  - name: memory-volume 
    emptyDir: 
      medium: Memory 

上述示例现在可能已经是顺手的操作了,但我们将再次发出一个 create 命令,接着是一个 exec 命令,来查看容器中的文件夹:

$ kubectl create -f storage-memory.yaml
$ kubectl exec memory-pd -- ls -lh | grep memory-pd

这将为我们提供容器本身的 Bash Shell。ls 命令会显示我们在顶层看到一个 memory-pd 文件夹。我们使用 grep 来过滤输出,但你也可以不加 | grep memory-pd 直接运行命令来查看所有文件夹:

容器内部的临时存储

再次强调,这个文件夹是临时的,因为所有内容都存储在节点(工作节点)的 RAM 中。当节点重启时,所有文件将被删除。接下来我们将查看一个更持久的示例。

云卷

让我们继续进行更强大的存储配置。我们将介绍两种类型的持久卷,以说明如何使用 AWS 和 GCE 的块存储引擎为你的 Kubernetes 集群提供有状态存储。由于许多公司已经在云基础设施上做出了大量投资,我们将通过两个关键示例让你快速上手。你可以将这些类型的卷或持久卷视为存储类。这些与我们之前创建的 emptyDir 不同,因为即使 Pod 被删除,GCE 持久磁盘或 AWS EBS 卷的内容也会保留。展望未来,这为运维人员提供了一个巧妙的功能:能够预先填充数据到这些磁盘中,并且可以在 Pod 之间切换。

GCE 持久磁盘

让我们首先挂载一个 gcePersistentDisk。你可以在这里查看更多关于这些磁盘的信息:cloud.google.com/compute/docs/disks/

Google 持久磁盘是 Google Cloud Platform 上耐用且高性能的块存储。持久磁盘提供 SSD 和 HDD 存储,可以附加到运行在 Google 计算引擎或 Google 容器引擎中的实例上。存储卷可以透明地调整大小,快速备份,并支持同时读取。

在我们能够在集群中使用它之前,你需要使用 GCE GUI、API 或 CLI 创建一个持久磁盘,所以我们开始吧:

  1. 在控制台中,进入计算引擎(Compute Engine),转到磁盘(Disks)。在此新屏幕上,点击“创建磁盘”按钮。我们将看到类似以下的 GCE 新持久磁盘截图:

GCE 新持久磁盘

  1. 为这个卷选择一个名称并简要描述它。确保区域与集群中的节点相同。GCE 持久磁盘只能附加到相同区域的机器上。

  2. 在“名称”字段中输入 mysite-volume-1。选择一个至少有一个节点的区域,选择“无”(空白磁盘)作为源类型,并在大小(GB)中输入 10(10 GB)。最后,点击“创建”:

GCE 上持久磁盘的一个优点是,它们允许挂载到多个机器(在我们的情况中是节点)。然而,当挂载到多个机器时,卷必须处于只读模式。因此,让我们首先将其挂载到单个 Pod,以便我们可以创建一些文件。使用以下代码创建一个 storage-gce.yaml 文件,来创建一个将磁盘以读写模式挂载的 Pod:

apiVersion: v1 
kind: Pod 
metadata: 
  name: test-gce 
spec: 
  containers: 
  - image: nginx:latest 
    ports: 
    - containerPort: 80 
    name: test-gce 
    volumeMounts: 
    - mountPath: /usr/share/nginx/html 
      name: gce-pd 
  volumes: 
  - name: gce-pd 
    gcePersistentDisk: 
      pdName: mysite-volume-1 
      fsType: ext4 

首先,让我们执行一个 create 命令,接着执行一个 describe 命令,查看它运行在哪个节点上:

$ kubectl create -f storage-gce.yaml 
$ kubectl describe pod/test-gce

注意节点并保存 Pod IP 地址以备后用。然后,打开一个 SSH 会话连接到该节点:

使用持久磁盘描述的 Pod

输入以下命令:

$ gcloud compute --project "<Your project ID>" ssh --zone "<your gce zone>" "<Node running test-gce pod>" 

既然我们已经从正在运行的容器内查看过卷了,这次我们直接从节点(minion)本身访问它。我们将运行 df 命令来查看它挂载的位置,但首先我们需要切换到 root 用户:

$ sudo su -
$ df -h | grep mysite-volume-1

如你所见,GCE 卷直接挂载到节点本身。我们可以使用早期 df 命令输出中列出的挂载路径。现在使用 cd 命令切换到该文件夹。然后,使用你喜欢的编辑器创建一个名为 index.html 的新文件:

$ cd /var/lib/kubelet/plugins/kubernetes.io/gce-pd/mounts/mysite-volume-1
$ vi index.html 

输入一条简单的消息,比如 Hello from my GCE PD!。现在,保存文件并退出编辑器。如果你还记得 storage-gce.yaml 文件,持久化磁盘直接挂载到 nginx 的 HTML 目录。所以,在我们仍然保持 SSH 会话打开的情况下,让我们来测试一下。使用 curl 命令访问我们之前记下的 pod IP:

$ curl <Pod IP from Describe> 

你应该看到 Hello from my GCE PD! 或者你在 index.html 文件中保存的任何消息。在现实场景中,我们可以将该卷用于整个网站或任何其他中央存储。让我们来看看如何运行一组负载均衡的 web 服务器,所有服务器都指向相同的卷。

首先,使用两个 exit 命令退出 SSH 会话。在继续之前,我们需要删除 test-gce pod,以便该卷可以在多个节点上以只读方式挂载:

$ kubectl delete pod/test-gce 

现在,我们可以创建一个 ReplicationController,它将运行三个 web 服务器,并且都挂载相同的持久化磁盘,代码如下所示。将以下代码保存为 http-pd-controller.yaml 文件:

apiVersion: v1 
kind: ReplicationController 
metadata: 
  name: http-pd 
  labels: 
    name: http-pd 
spec: 
  replicas: 3 
  selector: 
    name: http-pd 
  template: 
    metadata: 
      name: http-pd 
      labels:
        name: http-pd
    spec: 
      containers: 
      - image: nginx:latest 
        ports: 
        - containerPort: 80 
        name: http-pd 
        volumeMounts: 
        - mountPath: /usr/share/nginx/html 
          name: gce-pd 
      volumes: 
      - name: gce-pd 
        gcePersistentDisk: 
          pdName: mysite-volume-1 
          fsType: ext4 
          readOnly: true 

让我们还创建一个外部服务并将其保存为 http-pd-service.yaml 文件,这样我们就可以从集群外部访问它:

apiVersion: v1 
kind: Service 
metadata: 
  name: http-pd 
  labels: 
    name: http-pd 
spec: 
  type: LoadBalancer 
  ports: 
  - name: http 
    protocol: TCP 
    port: 80 
  selector: 
    name: http-pd 

现在就创建这两个资源吧。稍等片刻,等待外部 IP 分配完毕。之后,使用 describe 命令我们可以得到一个可以在浏览器中使用的 IP:

$ kubectl describe service/http-pd

以下截图是前面命令的结果:

K8s 服务与 GCE PD 共享的三个 pod

如果你还没有看到 LoadBalancer Ingress 字段,可能需要更多时间才能分配。将 LoadBalancer Ingress 中的 IP 地址输入浏览器,你应该能看到你之前输入文本的 index.html 文件!

AWS 弹性块存储

K8s 还支持 AWS 弹性块存储EBS)卷。与 GCE 持久化磁盘类似,EBS 卷要求附加到同一可用区中运行的实例上。另一个限制是,EBS 卷一次只能挂载到单个实例。和之前一样,你需要通过 API 调用、CLI 或者手动登录到 GUI 来创建 volumeID 引用的 EBS 卷。如果你已授权使用 AWS CLI,可以使用以下命令创建卷:

$ aws ec2 create-volume --availability-zone=us-west-1a eu-west-1a --size=20 --volume-type=gp2

确保你的卷是在与你的 Kubernetes 集群相同的区域内创建的!

为了简洁起见,我们将不详细演示一个 AWS 示例,但已经包含了一个示例 YAML 文件,帮助你入门。再次提醒,记得在部署 pod 之前创建 EBS 卷。将以下代码保存为storage-aws.yaml文件:

apiVersion: v1 
kind: Pod 
metadata: 
  name: test-aws 
spec: 
  containers: 
  - image: nginx:latest 
    ports: 
    - containerPort: 80 
    name: test-aws 
    volumeMounts: 
    - mountPath: /usr/share/nginx/html 
      name: aws-pd 
  volumes: 
  - name: aws-pd 
    awsElasticBlockStore: 
      volumeID: aws://<availability-zone>/<volume-id> 
      fsType: ext4 

其他存储选项

Kubernetes 支持多种其他类型的存储卷。完整列表请见此处:kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes

以下是几个可能特别感兴趣的选项:

  • nfs:这种类型允许我们挂载网络文件共享NFS),这对于数据持久化和在基础设施之间共享数据非常有用。

  • gitrepo:正如你可能猜到的,这个选项会将 Git 仓库克隆到一个新的空文件夹中。

持久卷和存储类

到目前为止,我们已看到在 pod 定义中直接配置存储的示例。如果你完全控制集群和基础设施,这种方式非常有效,但在更大的规模上,应用程序所有者可能希望使用独立管理的存储。通常,中央 IT 团队或云服务提供商会处理存储的配置细节,而应用程序所有者则专注于他们的主要任务——应用程序本身。Kubernetes 中这种关注点和职责的分离,使得你可以围绕一个可以由独立工程团队管理的存储子系统来组织你的工程重点。

为了适应这一点,我们需要一种方式让应用程序指定和请求存储,而无需关注存储是如何提供的。这就是PersistentVolumesPersistentVolumeClaim派上用场的地方。

PersistentVolumes 类似于我们之前创建的卷,但它们是由集群管理员提供的,不依赖于特定的 pod。PersistentVolumes 是提供给集群的资源,就像任何其他对象一样。Kubernetes API 提供了这种对象的接口,支持 NFS、EBS 持久磁盘或之前描述的任何其他卷类型。一旦卷被创建,你可以使用PersistentVolumeClaims请求为应用程序提供存储。

PersistentVolumeClaims是一个抽象,它允许用户指定所需存储的详细信息。我们可以定义存储的大小,以及访问类型,如ReadWriteOnce(由一个节点读写)、ReadOnlyMany(由多个节点只读)和ReadWriteMany(由多个节点读写)。集群操作员负责为应用程序操作员提供多种存储选项,以满足不同访问模式、大小、速度和耐用性等要求,而无需最终用户了解具体的实现细节。集群操作员支持的模式取决于后端存储提供商。例如,我们在 AWS aws-ebs示例中看到,多个节点挂载不是一个选项,而在 GCP Persistent Disks 中,磁盘可以在只读模式下被多个节点共享。

此外,Kubernetes 提供了两种其他方法来指定特定类型或分组的存储卷。第一种是使用选择器,就像我们之前用于 Pod 选择一样。在这里,标签可以应用于存储卷,然后声明可以引用这些标签来进一步筛选所提供的卷。第二种,Kubernetes 有StorageClass的概念,它允许我们指定存储提供者和其提供的卷类型的参数。

PersistentVolumesPersistentVolumeClaims有一个生命周期,包括以下阶段:

  • 供应

  • 静态或动态

  • 绑定

  • 使用

  • 回收

  • 删除、保留或回收

我们将在下一节深入探讨存储类,但这里有一个PersistentVolumeClaim的快速示例,供说明使用。你可以在注释中看到,我们请求在ReadWriteOnce模式下分配1Gi存储,StorageClasssolidstate,并且标签为aws-storage。请将以下代码保存为pvc-example.yaml文件:

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: demo-claim
spec:
  accessModes:
  - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 1Gi
  storageClassName: ssd
  selector:
    matchLabels:
      release: "aws-storage"
  matchExpressions:
      - {key: environment, operator: In, values: [dev, stag, uat]}

从 Kubernetes 1.8 版本开始,PersistentVolumeClaim也支持对gcePersistentDiskawsElasticBlockStoreCinderglusterfsrbd卷类型进行扩展的 alpha 支持。这些类似于你可能在 VMware 等系统中看到的薄配置,它们允许通过allowVolumeExpansion字段来调整存储类的大小,只要你使用的是 XFS 或 Ext3/Ext4 文件系统。以下是一个快速示例:

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
 name: Cinder-volume-01
provisioner: kubernetes.io/cinder
parameters:
 resturl: "http://192.168.10.10:8080"
 restuser: ""
 secretNamespace: ""
 secretName: ""
allowVolumeExpansion: true

动态卷供应

现在我们已经探讨了如何通过卷、存储类、持久卷和持久卷声明来构建存储,让我们看看如何将这一切动态化,并利用云的内建扩展能力!动态供应消除了对预先制作存储的需求;它依赖于应用程序用户的请求。你可以使用StorageClass API 对象来创建动态资源。

首先,我们可以创建一个清单,定义我们将用于动态存储的存储类类型。我们将在这里使用一个 vSphere 示例来尝试另一种存储类:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata: 
  name: durable-medium
provisioner: kubernetes.io/vsphere-volume
parameters:
  type: thin

一旦我们拥有清单,我们可以通过将其作为类包含在新的 PersistentVolumeClaim 中来使用此存储。你可能记得在 Kubernetes 1.6 之前的版本中,它被称为 volume.beta.kubernetes.io/storage-class,但现在你可以简单地将这个属性包含在 PersistentVolumeClaim 对象中。请记住,storageClassName 的值必须与集群操作员提供的可用动态 StorageClass 匹配。以下是一个示例:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: webtier-vclaim-01
spec:
 accessModes:
   - ReadWriteMany
 storageClassName: durable-medium
 resources:
   requests:
     storage: 20Gi

当这个声明被删除时,存储会被动态删除。你可以通过确保启用 DefaultStorageClass 准入控制器,并确保将一个 StorageClass 对象设置为默认值来使其成为集群的默认值。

StatefulSets

StatefulSets 的目的是为具有状态数据的应用程序部署提供一些一致性和可预测性。到目前为止,我们已将应用程序部署到集群中,定义了围绕所需资源(如计算和存储)的宽松要求。集群已将我们的工作负载调度到任何能够满足这些要求的节点上。虽然我们可以利用一些约束以更可预测的方式进行部署,但如果有一个构造可以帮助我们提供这种一致性,那就更有帮助了。

StatefulSets 在 1.6 版本中正式发布。它们之前在 1.5 版本中是 beta 版本,在此之前(1.3 和 1.4 版本)被称为 Pet Sets(alpha)。

这就是 StatefulSets 的作用。StatefulSets 首先为我们提供了用于网络访问和存储声明的编号和可靠命名。Pod 本身的命名遵循以下约定,其中 N 从 0 到副本数:

"Name of Set"-N

这意味着一个名为 db 的 StatefulSet,具有三个副本,将创建以下 Pod:

db-0
db-1
db-2

这为 Kubernetes 提供了一种将网络名称和 PersistentVolumes 与特定 Pod 关联的方法。此外,它还用于控制 Pod 的创建和终止顺序。Pod 将从 0 启动到 N,并从 N 终止到 0

一个有状态的示例

让我们来看一个有状态应用的示例。首先,我们需要创建并使用一个StorageClass,正如我们之前讨论的那样。这将允许我们连接到 Google Cloud Persistent Disk 提供者。Kubernetes 社区正在为各种 StorageClasses 构建提供者,包括 GCP 和 AWS。每个提供者都有自己的一组可用参数。GCP 和 AWS 提供者都允许您选择磁盘类型(固态、标准等),以及与之匹配的 pod 附加的故障域。AWS 还允许您指定加密参数以及为预配置的 IOPs 卷设置 IOPs。还有许多其他提供者正在开发中,包括 Azure 以及各种非云选项。将以下代码保存为 solidstate-sc.yaml 文件:

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: solidstate
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-ssd
  zone: us-central1-b

使用以下命令结合前面的列表来在 us-central1-b 创建一个 StorageClass 类型的 SSD 磁盘:

$ kubectl create -f solidstate.yaml

接下来,我们将创建一个 StatefulSet 类型,并使用我们可靠的 httpwhalesay 示例应用。虽然这个应用并不包含任何实际的状态,但我们可以看到存储声明并探索通信路径,如 sayhey-statefulset.yaml 列表所示:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: whaleset
spec:
  serviceName: sayhey-svc
  replicas: 3
  template:
    metadata:
      labels:
        app: sayhey
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: sayhey
        image: jonbaier/httpwhalesay:0.2
        command: ["node", "index.js", "Whale it up!."]
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
      annotations:
        volume.beta.kubernetes.io/storage-class: solidstate
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

使用以下命令开始创建这个 StatefulSet。如果您仔细观察 pod 创建过程,您将看到它依次创建 whaleset-0whaleset-1whaleset-2

$ kubectl create -f sayhey-statefulset.yaml

紧接着,我们可以使用熟悉的 get 子命令查看我们的 StatefulSet 和相应的 pods:

$ kubectl get statefulsets
$ kubectl get pods

这些 pod 应该会生成类似于以下图片的输出:

StatefulSet 列表

get pods 的输出将显示以下内容:

StatefulSet 创建的 pod

根据您的时机,pod 可能仍在创建中。正如您在前面的截图中看到的,第三个容器仍在启动中。

我们还可以看到该集合为每个 pod 创建并声明的卷。首先是 PersistentVolumes 本身:

$ kubectl get pv

前面的命令应该显示名为 www-whaleset-N 的三个 PersistentVolumes。我们注意到其大小为 1Gi,访问模式设置为 ReadWriteOnce (RWO),正如我们在 StorageClass 中定义的那样:

PersistentVolumes 列表

接下来,我们可以查看为每个 pod 保留卷的 PersistentVolumeClaim

$ kubectl get pvc

以下是前一个命令的输出:

PersistentVolumeClaim 列表

你会注意到这里与 PersistentVolumes 本身有许多相同的设置。你还会注意到声明名称的结尾(或之前列出的 PersistentVolumeClaim 名称)看起来像是 www-whaleset-Nwww 是我们在前面 YAML 定义中指定的挂载名称。接着,这个名称会与 Pod 名称一起拼接,形成实际的 PersistentVolumePersistentVolumeClaim 名称。我们还可以确保将正确的磁盘与匹配的 Pod 进行关联。

网络通信中,另一个需要对齐的领域是 StatefulSets 的一致命名。在此之前,我们需要创建一个服务端点 sayhey-svc.yaml,这样我们就有了一个通用的入口点来处理传入的请求:

apiVersion: v1
kind: Service
metadata:
  name: sayhey-svc
  labels:
    app: sayhey
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: sayhey
$ kubectl create -f sayhey-svc.yaml

现在,让我们在其中一个 Pod 中打开一个 shell,看看我们能否与该集合中的另一个 Pod 进行通信:

$ kubectl exec whaleset-0 -i -t bash

上述命令给了我们第一个 whaleset Pod 的 bash shell。现在,我们可以使用服务名称发出简单的 HTTP 请求。我们可以使用短名称 sayhey-svc 以及完全限定名称 sayhey-svc.default.svc.cluster.local

$ curl sayhey-svc
$ curl sayhey-svc.default.svc.cluster.local

你将看到类似于以下截图的输出。服务端点作为所有三个 Pod 的公共通信点:

HTTP whalesay curl 输出(whalesay-0 Pod)

现在,让我们看看我们能否与 StatefulSet 中的特定 Pod 进行通信。如我们之前所注意到的,StatefulSet 以有序的方式命名 Pod。它还以类似的方式为它们分配主机名,因此每个 Pod 在集合中都有一个特定的 DNS 条目。同样,我们将看到"Name of Set"-N的惯例,然后添加完全限定的服务 URL。以下示例展示了 whaleset-1,即集合中的第二个 Pod:

$ curl whaleset-1.sayhey-svc.default.svc.cluster.local

从我们现有的 Bash shell 中运行此命令,位于 whaleset-0,将显示来自 whaleset-1 的输出:

HTTP whalesay curl 输出(whalesay-1 Pod)

现在,你可以通过 exit 退出这个 shell。

为了学习目的,描述这一部分中的某些项目的更多细节也可能是有益的。例如,kubectl describe svc sayhey-svc 将显示服务端点中的所有三个 Pod IP 地址。

总结

在本章中,我们探索了多种持久化存储选项以及如何在我们的 Pod 中实现它们。我们研究了PersistentVolumesPersistentVolumeClaim,它们使我们能够分离存储供应和应用存储请求。此外,我们还研究了StorageClasses,它根据规范来提供一组存储。

我们还探索了新的 StatefulSets 抽象,并了解了如何以一致且有序的方式部署有状态应用程序。在下一章中,我们将探讨如何将 Kubernetes 与持续集成和交付管道集成。

问题

  1. 请列出 Kubernetes 支持的四种卷类型。

  2. 你可以使用哪个参数来启用一个简单的、半持久的临时磁盘?

  3. 举出两种使PersistentVolumes云服务提供商CSPs)中易于实现的支持技术。

  4. 创建不同类型StorageClasses的一个好理由是什么?

  5. 举出PersistentVolumePersistentVolumeClaim生命周期中的两个阶段

  6. 哪个 Kubernetes 对象用于提供基于状态存储的应用?

进一步阅读

第六章:应用程序更新、渐进式发布和自动扩展

本章将扩展核心概念,向您展示如何推出更新并测试应用程序的新功能,同时尽量减少对正常运行时间的干扰。它将涵盖应用更新、渐进式发布和 A/B 测试的基础知识。此外,我们还将研究如何扩展 Kubernetes 集群本身。

在版本 1.2 中,Kubernetes 发布了部署(Deployments)API。部署是今后处理扩展和应用程序更新的推荐方式。如前几章所述,ReplicationControllers 已不再是管理应用更新的推荐方式。然而,由于它们仍然是许多操作员的核心功能,我们将在本章中探索滚动更新,以介绍扩展概念,然后在下一章深入探讨使用部署的首选方法。

我们还将调查 Helm 和 Helm Charts 的功能,这将帮助您管理 Kubernetes 资源。Helm 是管理 Kubernetes 包的一种方式,就像 apt/yum 在 Linux 生态系统中管理代码一样。Helm 还允许您与他人共享您的应用程序,最重要的是创建可重现的 Kubernetes 应用程序构建。

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

  • 应用程序扩展

  • 滚动更新

  • A/B 测试

  • 应用程序自动扩展

  • 扩展您的集群

  • 使用 Helm

技术要求

您需要启用并登录到您的 Google Cloud Platform 帐户,或者可以使用本地的 Minikube 实例来运行 Kubernetes。您还可以通过网络使用 Play with Kubernetes:labs.play-with-k8s.com/

以下是本章的 GitHub 仓库:github.com/PacktPublishing/Getting-Started-with-Kubernetes-third-edition/tree/master/Code-files/Chapter06.

示例设置

在开始探索 Kubernetes 中内置的各种扩展和更新功能之前,我们需要一个新的示例环境。我们将使用之前容器镜像的一个变种,背景为蓝色(请参见本章后面的 v0.1 和 v0.2(并排) 图像进行比较)。我们在 pod-scaling-controller.yaml 文件中有以下代码:

apiVersion: v1 
kind: ReplicationController 
metadata: 
  name: node-js-scale 
  labels: 
    name: node-js-scale 
spec: 
  replicas: 1 
  selector: 
    name: node-js-scale 
  template: 
    metadata: 
      labels: 
        name: node-js-scale 
    spec: 
      containers: 
      - name: node-js-scale 
        image: jonbaier/pod-scaling:0.1 
        ports: 
        - containerPort: 80

将以下代码保存为 pod-scaling-service.yaml 文件:

apiVersion: v1 
kind: Service 
metadata: 
  name: node-js-scale 
  labels: 
    name: node-js-scale 
spec: 
  type: LoadBalancer 
  sessionAffinity: ClientIP 
  ports: 
  - port: 80 
  selector: 
    name: node-js-scale

使用以下命令创建这些服务:

$ kubectl create -f pod-scaling-controller.yaml
$ kubectl create -f pod-scaling-service.yaml

服务的公共 IP 地址可能需要一些时间来创建。

扩展

随着时间的推移,当你在 Kubernetes 集群中运行应用程序时,你会发现一些应用程序需要更多资源,而另一些则能以较少的资源运行。与其删除整个ReplicationControllers(及相关 Pods),我们更希望有一种无缝的方法来扩展或收缩我们的应用程序。

幸运的是,Kubernetes 包含一个专门用于此目的的scale命令。scale命令既适用于ReplicationControllers,也适用于新的部署抽象。现在,我们将探讨它与ReplicationControllers一起使用的方式。在我们的新示例中,只有一个副本在运行。你可以通过get pods命令检查这一点:

$ kubectl get pods -l name=node-js-scale

让我们试着用以下命令将其扩展到三个:

$ kubectl scale --replicas=3 rc/node-js-scale

如果一切顺利,你将只在终端窗口的输出中看到scaled这个词。

可选地,你可以指定--current-replicas标志作为验证步骤。只有当前运行的副本数量与该计数匹配时,扩展操作才会发生。

在再次列出我们的 Pods 之后,我们应该现在能看到三个名称类似于node-js-scale-XXXXX的 Pods,其中X字符是一个随机字符串。

你也可以使用scale命令减少副本的数量。在任何一种情况下,scale命令都会添加或移除必要的 Pod 副本,并且服务会自动更新并平衡到新的或剩余的副本上。

平滑更新

随着我们的资源需求变化,我们的应用程序的扩展和收缩对于许多生产场景来说非常有用,但对于简单的应用更新怎么办呢?任何生产系统都会进行代码更新、修复补丁和功能添加。这些更新可能是每月、每周,甚至每天发生。确保我们有一种可靠的方法来推送这些更改而不会中断用户使用是至关重要的。

再次受益于 Kubernetes 系统多年的经验。1.0 版本中内置了对滚动更新的支持。rolling-update命令允许我们更新整个ReplicationControllers,或者仅更新每个副本使用的底层 Docker 镜像。我们还可以指定更新间隔,这样可以一次更新一个 Pod,并在继续进行到下一个之前等待。

让我们以扩展示例为例,执行一个滚动更新,将容器镜像更新到 0.2 版本。我们将使用 2 分钟的更新间隔,这样可以通过以下方式观察整个过程:

$ kubectl rolling-update node-js-scale --image=jonbaier/pod-scaling:0.2 --update-period="2m"

你应该会看到一些关于创建一个新的名为node-js-scale-XXXXXReplicationControllers的文本,其中X字符是一个由数字和字母组成的随机字符串。此外,你还会看到一个循环的开始,它会启动新版本的一个副本,并移除现有ReplicationControllers中的一个副本。这个过程将继续,直到新的ReplicationControllers拥有完整数量的副本。

如果我们想实时跟踪,可以打开另一个终端窗口,使用get pods命令和标签过滤器来查看发生了什么:

$ kubectl get pods -l name=node-js-scale

该命令将筛选出名称中包含node-js-scale的 pod。如果你在执行rolling-update命令后运行这个命令,你应该会看到几个 pod 在运行,因为它在创建新版本并逐个删除旧版本。

上一个rolling-update命令的完整输出应该类似于下面这张截图:

扩展输出

正如我们所见,Kubernetes 首先创建了一个名为node-js-scale-10ea08ff9a118ac6a93f85547ed28f6的新ReplicationController。然后 K8s 逐个循环,先在新控制器中创建一个新的 pod,再从旧控制器中删除一个 pod。这个过程一直持续,直到新控制器的副本数量达到完整,旧控制器的副本数量为零。之后,旧控制器被删除,新控制器被重命名为原控制器的名称。

如果你现在运行get pods命令,你会注意到这些 pod 仍然有较长的名称。或者,我们可以在命令中指定一个新的控制器名称,Kubernetes 会使用该名称创建新的ReplicationControllers和 pod。再次强调,旧名称的控制器在更新完成后会自动消失。我建议你为更新后的控制器指定一个新的名称,以避免将来在 pod 命名时产生混淆。使用这种方法的相同update命令如下所示:

$ kubectl rolling-update node-js-scale node-js-scale-v2.0 --image=jonbaier/pod-scaling:0.2 --update-period="2m"

使用我们在第一部分创建的服务的静态外部 IP 地址,我们可以在浏览器中打开该服务。我们应该能看到标准的容器信息页面。然而,你会注意到标题现在显示为 Pod Scaling v0.2,背景为浅黄色:

v0.1 和 v0.2(并排显示)

值得注意的是,在整个更新过程中,我们只关注了 pod 和ReplicationControllers。我们没有对服务做任何操作,但服务依然正常运行,并且现在指向新版本的 pod。这是因为我们的服务使用标签选择器来进行成员管理。由于我们的旧副本和新副本使用相同的标签,服务没有问题,可以使用新 pod 来处理请求。更新是在 pod 上逐个进行的,因此对服务的用户来说是无缝的。

测试、发布和切换

滚动更新功能在简单的蓝绿部署场景中可以很好地工作。然而,在实际的蓝绿部署中,涉及多个应用程序的堆栈可能会有各种依赖关系,需要进行深入的测试。update-period命令允许我们添加一个timeout标志,在这里可以进行一些测试,但这对于测试目的来说并不总是足够的。

类似地,您可能希望将部分更改持续较长时间,并一直持续到负载均衡器或服务级别。例如,您可能希望对一部分用户运行新用户界面功能的 A/B 测试。另一个例子是将您的应用程序以金丝雀发布(在这种情况下是一个副本)的方式运行在新基础设施上,比如新增的集群节点。

让我们看一个 A/B 测试的示例。在这个示例中,我们需要创建一个使用sessionAffinity的新服务。我们将亲和性设置为ClientIP,这样可以将客户端引导到相同的后端 Pod。以下列出的pod-AB-service.yaml文件是关键,如果我们希望一部分用户看到一个版本,而其他用户看到另一个版本:

apiVersion: v1 
kind: Service 
metadata: 
  name: node-js-scale-ab 
  labels: 
    service: node-js-scale-ab 
spec: 
  type: LoadBalancer 
  ports: 
  - port: 80 
  sessionAffinity: ClientIP 
  selector: 
    service: node-js-scale-ab

按照以下方式,像往常一样使用create命令创建此服务:

$ kubectl create -f pod-AB-service.yaml

这将创建一个服务,指向运行应用程序版本 0.2 和 0.3 的 Pod。接下来,我们将创建两个ReplicationControllers,它们会创建应用程序的两个副本。一组将运行版本 0.2,另一组将运行版本 0.3,如pod-A-controller.yamlpod-B-controller.yaml文件所示:

apiVersion: v1 
kind: ReplicationController 
metadata: 
  name: node-js-scale-a 
  labels: 
    name: node-js-scale-a 
    version: "0.2" 
    service: node-js-scale-ab 
spec: 
  replicas: 2 
  selector: 
    name: node-js-scale-a 
    version: "0.2" 
    service: node-js-scale-ab 
  template: 
    metadata: 
      labels: 
        name: node-js-scale-a 
        version: "0.2" 
        service: node-js-scale-ab 
    spec: 
      containers: 
      - name: node-js-scale 
        image: jonbaier/pod-scaling:0.2 
        ports: 
        - containerPort: 80 
        livenessProbe: 
          # An HTTP health check 
          httpGet: 
            path: / 
            port: 80 
          initialDelaySeconds: 30 
          timeoutSeconds: 5 
        readinessProbe: 
          # An HTTP health check 
          httpGet: 
            path: / 
            port: 80 
          initialDelaySeconds: 30 
          timeoutSeconds: 1
apiVersion: v1 
kind: ReplicationController 
metadata: 
  name: node-js-scale-b 
  labels: 
    name: node-js-scale-b 
    version: "0.3" 
    service: node-js-scale-ab 
spec: 
  replicas: 2 
  selector: 
    name: node-js-scale-b 
    version: "0.3" 
    service: node-js-scale-ab 
  template: 
    metadata: 
      labels: 
        name: node-js-scale-b 
        version: "0.3" 
        service: node-js-scale-ab 
    spec: 
      containers: 
      - name: node-js-scale 
        image: jonbaier/pod-scaling:0.3 
        ports: 
        - containerPort: 80 
        livenessProbe: 
          # An HTTP health check 
          httpGet: 
            path: / 
            port: 80 
          initialDelaySeconds: 30 
          timeoutSeconds: 5 
        readinessProbe: 
          # An HTTP health check 
          httpGet: 
            path: / 
            port: 80 
          initialDelaySeconds: 30 
          timeoutSeconds: 1

请注意,我们有相同的服务标签,因此这些副本也将根据此选择器添加到服务池中。我们还定义了livenessProbereadinessProbe,以确保我们的新版本按预期工作。同样,使用create命令启动控制器:

$ kubectl create -f pod-A-controller.yaml
$ kubectl create -f pod-B-controller.yaml

现在,我们有一个平衡两个版本应用的服务。在真正的 A/B 测试中,我们现在希望开始收集每个版本的访问指标。同样,我们将sessionAffinity设置为ClientIP,因此所有请求将指向同一 Pod。一部分用户将看到 v0.2,另一部分用户将看到 v0.3。

因为我们开启了sessionAffinity,所以您的测试很可能每次都会显示相同的版本。这是预期的,您需要尝试从多个 IP 地址连接,以查看每个版本的用户体验。

由于每个版本都运行在独立的 Pod 上,您可以轻松分离日志记录,甚至为 Pod 定义添加一个日志容器,采用边车日志模式。为了简洁起见,本书不讨论此设置,但我们将在第八章《监控与日志》中讨论一些日志工具。

我们可以开始看到这个过程如何对金丝雀发布或手动蓝绿部署有所帮助。我们还可以看到推出新版本并逐步过渡到新版本是多么容易。

让我们快速看看一个基本的过渡。实际上,只需几个scale命令,命令如下:

$ kubectl scale --replicas=3 rc/node-js-scale-b
$ kubectl scale --replicas=1 rc/node-js-scale-a
$ kubectl scale --replicas=4 rc/node-js-scale-b
$ kubectl scale --replicas=0 rc/node-js-scale-a

使用get pods命令,并结合-l过滤器,在scale命令之间观察过渡过程。

现在,我们已经完全过渡到版本 0.3(node-js-scale-b)。所有用户现在都会看到版本 0.3 的网站。我们有四个版本 0.3 的副本,没有版本 0.2 的副本。如果你运行 get rc 命令,你会注意到我们仍然有一个版本 0.2 的 ReplicationControllersnode-js-scale-a)。作为最终的清理,我们可以完全移除这个控制器,如下所示:

$ kubectl delete rc/node-js-scale-a

应用程序自动扩缩

Kubernetes 最近新增了一个特性,那就是水平 pod 自动扩缩器。这个资源类型非常有用,因为它为我们提供了一种自动设置应用程序扩展阈值的方法。目前,支持的阈值仅限于 CPU,但也有对自定义应用程序指标的 alpha 支持。

让我们使用章节开头的 node-js-scale ReplicationController,并添加自动扩缩组件。在我们开始之前,让我们确保使用以下命令将副本缩减回一个:

$ kubectl scale --replicas=1 rc/node-js-scale

现在,我们可以创建一个水平 pod 自动扩缩器,node-js-scale-hpa.yaml,并定义以下 hpa 配置:

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: node-js-scale
spec:
  minReplicas: 1
  maxReplicas: 3
  scaleTargetRef:
    apiVersion: v1
    kind: ReplicationController
    name: node-js-scale
  targetCPUUtilizationPercentage: 20

继续使用 kubectl create -f 命令创建它。现在,我们可以列出水平 pod 自动扩缩器并获取描述:

$ kubectl get hpa 

我们还可以通过命令行使用 kubectl autoscale 命令创建自动扩缩。之前的 YAML 文件将如下所示:

$ kubectl autoscale rc/node-js-scale --min=1 --max=3 --cpu-percent=20

这将显示我们一个 node-js-scale ReplicationController 的自动扩缩器,目标 CPU 使用率为 30%。另外,你会看到最小 pod 数为 1,最大为 3:

没有负载时的水平 pod 自动扩缩器

让我们查询一下我们的 pod,看看现在有多少个正在运行:

$ kubectl get pods -l name=node-js-scale

我们应该只看到一个 node-js-scale pod,因为我们的水平 pod 自动扩缩器显示 CPU 使用率为 0%,所以我们需要生成一些负载。我们将使用在许多容器演示中常见的流行应用程序 boom。以下清单 boomload.yaml 将帮助我们创建持续的负载,直到我们达到自动扩缩器的 CPU 阈值:

apiVersion: v1
kind: ReplicationController
metadata:
  name: boomload
spec:
  replicas: 1
  selector:
    app: loadgenerator
  template:
    metadata:
      labels:
        app: loadgenerator
    spec:
      containers:
      - image: williamyeh/boom
        name: boom
        command: ["/bin/sh","-c"]
        args: ["while true ; do boom http://node-js-scale/ -c 10 -n 100      
        ; sleep 1 ; done"]

使用kubectl create -f命令和这个清单,然后准备开始监控hpa。我们可以使用之前用过的kubectl get hpa命令来做到这一点。

可能需要一些时间,但我们应该开始看到当前的 CPU 使用率增加。一旦超过我们设定的 20% 阈值,自动扩缩器将开始工作:

负载开始后,水平 pod 自动扩缩器

一旦看到这一点,我们可以再次运行 kubectl get pod,看到现在有几个 node-js-scale pod:

$ kubectl get pods -l name=node-js-scale

我们现在可以通过终止我们的负载生成 pod 来清理:

$ kubectl delete rc/boomload

现在,如果我们观察 hpa,我们应该开始看到 CPU 使用率下降。可能需要几分钟,但最终我们会回到 0% 的 CPU 负载。

扩展集群

所有这些技术对于扩展应用程序都非常有用,但集群本身呢?在某个时刻,你会将节点填满,并需要更多资源来调度新 pod 以容纳你的工作负载。

自动扩展

当你创建集群时,可以通过 NUM_MINIONS 环境变量自定义启动时的节点(minion)数量。默认值设置为 4。

此外,Kubernetes 团队已经开始在集群本身中构建自动扩展功能。目前,这项功能仅在 GCE 和 GKE 上受支持,但其他云提供商也在进行相关工作。此功能使用了 KUBE_AUTOSCALER_MIN_NODESKUBE_AUTOSCALER_MAX_NODESKUBE_ENABLE_CLUSTER_AUTOSCALER 环境变量。

以下示例展示了如何在运行 kube-up.sh 之前设置自动扩展的环境变量:

$ export NUM_MINIONS=5
$ export KUBE_AUTOSCALER_MIN_NODES=2
$ export KUBE_AUTOSCALER_MAX_NODES=5
$ export KUBE_ENABLE_CLUSTER_AUTOSCALER=true 

同时,请记住,集群启动后更改这些设置将没有效果。你需要拆除集群并重新创建。因此,本节将展示如何在不重建集群的情况下向现有集群添加节点。

一旦你使用这些设置启动集群,你的集群将根据集群中计算资源的使用情况,自动根据最小和最大限制进行扩展和缩减。

GKE 集群在启动时也支持自动扩展,前提是使用了 alpha 功能。前面的示例将使用类似 --enable-autoscaling --min-nodes=2 --max-nodes=5 的标志进行命令行启动。

在 GCE 上扩展集群

如果你希望扩展现有集群,可以通过几个步骤实现。在 GCE 上手动扩展集群其实非常简单。现有的基础设施使用了 GCE 中的托管实例组,这使得你可以通过实例模板轻松地向组中添加更多标准配置的机器。

你可以在 GCE 控制台中轻松查看该模板。首先,打开控制台;默认情况下,这将打开你的默认项目控制台。如果你使用其他项目来管理 Kubernetes 集群,只需从页面顶部的项目下拉菜单中选择它。

在侧边面板中,找到“计算”部分,然后选择“计算引擎”,接着选择“实例模板”。你应该能看到一个名为 kubernetes-minion-template 的模板。如果你自定义过集群命名设置,名称可能会略有不同。点击该模板查看详细信息。参考以下截图:

GCE 的 minion 实例模板

你会看到一系列设置,但模板的核心部分位于“自定义元数据”下。在这里,你会看到一系列环境变量,以及在创建新机器实例后运行的启动脚本。这些是允许我们创建新机器并将其自动添加到可用集群节点中的核心组件。

由于新机器的模板已创建,因此在 GCE 中扩展集群非常简单。进入控制台的 Compute 部分后,只需前往侧边面板上方的实例组链接。您应该会看到一个名为 kubernetes-minion-group 或类似名称的组。点击该组查看详细信息,如下图所示:

GCE 实例组中的从节点

您将看到一个包含 CPU 指标图表和三个实例的页面。默认情况下,集群会创建三个节点。我们可以通过点击页面顶部的 EDIT GROUP 按钮来修改此组:

GCE 实例组编辑页面

您应该会看到在我们之前查看的 Instance template 中选择了 kubernetes-minion-template。您还会看到一个自动扩展设置,默认情况下是关闭的,并且实例数量为 3。只需将其增加到 4 并点击保存。此时,您将被带回到组详情页面,并且会看到一个弹出对话框,显示待处理的更改。

您还会在实例组编辑页面看到一些自动修复属性。这些属性会重新创建失败的实例,并允许您设置健康检查以及在采取操作前的初始延迟时间。

几分钟后,您将在详情页中看到一个新实例。我们可以使用命令行中的 get nodes 命令来测试它是否已经准备好:

$ kubectl get nodes

关于自动扩展和缩容的警告:首先,如果我们重复前面的操作并将倒计时减少到四个,GCE 将删除一个节点。然而,它不一定是您刚刚添加的节点。好消息是,Pods 将被重新调度到剩余的节点上。但它只能在资源可用的地方重新调度。如果您接近满负荷并关闭了一个节点,那么很可能会有一些 Pods 无法重新调度到其它地方。此外,这不是实时迁移,因此在过渡过程中,任何应用程序的状态都将丢失。归根结底,在进行缩容或实施自动扩展方案之前,您应该仔细考虑其潜在影响。

有关 GCE 中一般自动扩展的更多信息,请参考 cloud.google.com/compute/docs/autoscaler/?hl=zh_CN#scaling_based_on_cpu_utilization 链接。

在 AWS 上扩展集群

AWS 提供者代码同样使得扩展集群变得非常简单。与 GCE 类似,AWS 设置使用自动扩展组来创建默认的四个从节点。未来,自动扩展组有望与 Kubernetes 集群的自动扩展功能集成。现在,我们将手动设置操作。

这也可以通过命令行接口或网页控制台轻松修改。在控制台中,从 EC2 页面,简单地进入左侧菜单底部的自动扩展组部分。你应该会看到一个类似于 kubernetes-minion-group 的名称。选择此组,你将看到以下截图所示的详细信息:

Kubernetes 节点自动扩展详细信息

我们可以通过点击“编辑”轻松地扩展此组。然后,将所需的、最小值和最大值更改为5,并点击“保存”。几分钟后,你将看到第五个节点可用。你可以再次使用get nodes命令来检查。

缩减规模的过程是相同的,但请记住,我们在之前的在 GCE 上扩展集群部分中讨论了相同的注意事项。工作负载可能会被放弃,或者至少会意外重启。

手动扩展

对于其他提供商,创建新节点可能不是一个自动化的过程。根据你的提供商,你可能需要执行各种手动步骤。查看cluster目录中的特定提供商脚本可能会有所帮助。

管理应用程序

在本书写作时,出现了新的软件,它希望从整体角度解决管理 Kubernetes 应用程序的问题。随着应用程序安装和持续管理变得越来越复杂,像 Helm 这样的软件希望减轻集群操作员在创建、版本控制、发布和导出应用程序安装及配置时的压力。你可能还听说过 GitOps 这个术语,它使用 Git 作为真理源,从中可以管理所有 Kubernetes 实例。

尽管我们将在下一章深入探讨持续集成与持续交付CI/CD),但首先让我们看看通过利用 Kubernetes 生态系统中的软件包管理可以获得哪些优势。首先,理解我们在 Kubernetes 生态系统中的软件包管理所要解决的问题是很重要的。Helm 和类似的程序与 aptyumrpmdpgk、Aptitude 和 Zypper 等软件包管理器有很多相似之处。这些软件帮助用户度过了 Linux 的早期阶段,那时程序通常以源代码形式发布,安装文档、配置文件和必要的组件需要操作员自行设置。当然,现如今 Linux 发行版使用了大量预构建的包,这些包为用户社区提供,供用户在他们选择的操作系统中使用。在许多方面,我们正处于 Kubernetes 软件管理的早期阶段,存在许多不同的方法来安装软件,覆盖 Kubernetes 系统的不同层次。但是否还有其他原因让你希望为 Kubernetes 引入类似 GNU Linux 风格的软件包管理器呢?也许你认为通过使用容器、Git 和配置管理,你能够独立管理。

在 Kubernetes 集群的应用管理中,有几个重要的维度需要考虑,请牢记:

  1. 你希望能够借鉴他人的经验。当你在集群中安装软件时,你希望能够利用构建该软件的团队的专业知识,或者已经以最佳性能配置好软件的专家的经验。

  2. 你需要一种可重复、可审计的方法来维护集群在不同环境中的应用特定配置。例如,在使用简单工具如 cURL,或在 makefile 或其他软件包编译工具中,很难为不同环境构建特定的内存设置。

简而言之,我们希望在部署像数据库、缓存层、Web 服务器、键/值存储和其他你可能在 Kubernetes 集群中运行的技术时,利用生态系统的专业知识。这个生态系统中有许多潜在的参与者,例如 Landscaper (github.com/Eneco/landscaper)、Kubepack (github.com/kubepack/pack)、Flux (github.com/weaveworks/flux)、Armada (github.com/att-comdev/armada) 和 helmfile (cdp.packtpub.com/getting_started_with_kubernetes__third_edition/wp-admin/post.php?post=29&action=pdfpreview)。在这一部分,我们将特别关注 Helm (github.com/helm/helm),它最近已被 CNCF 接纳为孵化项目,并且它在解决我们这里描述的问题时的方法。

开始使用 Helm

我们将看到 Helm 如何通过使用 charts 来简化 Kubernetes 应用程序的管理,charts 是包含 chart.yml 文件的包,其中描述了包的内容,并且包含多个模板,这些模板包含 Kubernetes 可以用来操作其系统内对象的清单。

注意:Kubernetes 是基于一种理念构建的,即操作员定义所需的最终状态,Kubernetes 会随着时间的推移,通过最终一致性来强制执行该状态。Helm 的应用程序管理方法遵循相同的原则。就像你可以通过 kubectl 使用命令式命令、命令式目标配置和声明式对象配置来管理对象一样,Helm 利用声明式对象风格,这种风格具有最高的功能曲线和最高的难度。

让我们快速开始使用 Helm。首先,确保你已经通过 SSH 连接到我们之前使用的 Kubernetes 集群。你会注意到,与许多 Kubernetes 组件一样,我们将使用 Kubernetes 来安装 Helm 及其组件。你也可以使用 Minikube 上的本地 Kubernetes 安装。首先,检查并确保 kubectl 已设置为使用正确的集群:

$ kubectl config current-context
kubernetes-admin@kubernetes
Next up, let's grab the helm install script and install it locally. Make sure to read the script through first so you're comfortable with that it does!

接下来,让我们获取 Helm 安装脚本并在本地安装它。确保先阅读脚本,以便你了解它的功能!

你可以在这里阅读脚本内容:raw.githubusercontent.com/kubernetes/helm/master/scripts/get

现在,让我们运行安装脚本并获取相关组件:

master $ curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get > get_helm.sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 6740 100 6740 0 0 22217 0 --:--:-- --:--:-- --:--:-- 22244
master $ chmod 700 get_helm.sh
$ ./get_helm.sh
master $ ./get_helm.sh
Helm v2.9.1 is available. Changing from version v2.8.2.
Downloading https://kubernetes-helm.storage.googleapis.com/helm-v2.9.1-linux-amd64.tar.gz
Preparing to install into /usr/local/bin
helm installed into /usr/local/bin/helm
Run 'helm init' to configure helm

现在我们已经拉取并安装了 Helm,我们可以使用helm init在集群中安装 Tiller。你也可以在本地运行 Tiller 进行开发,但对于生产安装和本次演示,我们将在集群内部直接作为组件运行 Tiller。Tiller 将在配置时使用之前的上下文,所以请确保你使用的是正确的端点:

master $ helm init
Creating /root/.helm
Creating /root/.helm/repository
Creating /root/.helm/repository/cache
Creating /root/.helm/repository/local
Creating /root/.helm/plugins
Creating /root/.helm/starters
Creating /root/.helm/cache/archive
Creating /root/.helm/repository/repositories.yaml
Adding stable repo with URL: https://kubernetes-charts.storage.googleapis.com
master $ helm init
Creating /root/.helm
Creating /root/.helm/repository
Creating /root/.helm/repository/cache
Creating /root/.helm/repository/local
Creating /root/.helm/plugins
Creating /root/.helm/starters
Creating /root/.helm/cache/archive
Creating /root/.helm/repository/repositories.yaml
Adding stable repo with URL: https://kubernetes-charts.storage.googleapis.com
Adding local repo with URL: http://127.0.0.1:8879/charts
$HELM_HOME has been configured at /root/.helm.
Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.
Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.
For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation
Happy Helming!

现在我们已经安装了 Helm,让我们看看如何通过安装 MySQL 来直接管理应用程序,使用其中一个官方稳定版 charts。我们会确保获取到最新的 repositories,然后进行安装:

$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Skip local chart repository
...Successfully got an update from the "stable" chart repository
Update Complete.  Happy Helming!

你可以通过运行install命令,helm install stable/mysql,预览 Helm 管理 MySQL 的强大功能,这是 Helm 版的手册页,用于应用程序安装:

$ helm install stable/mysql
NAME:   guilded-otter
LAST DEPLOYED: Mon Jun  4 01:49:46 2018
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1beta1/Deployment
NAME                 DESIRED CURRENT UP-TO-DATE  AVAILABLE AGE
guilded-otter-mysql  1 1 1         0 0s
==> v1/Pod(related)
NAME                                  READY STATUS RESTARTS AGE
guilded-otter-mysql-5dd65c77c6-46hd4  0/1 Pending 0 0s
==> v1/Secret
NAME                 TYPE DATA AGE
guilded-otter-mysql  Opaque 2 0s
==> v1/ConfigMap
NAME                      DATA AGE
guilded-otter-mysql-test  1 0s
==> v1/PersistentVolumeClaim
NAME                 STATUS VOLUME CAPACITY  ACCESS MODES STORAGECLASS AGE
guilded-otter-mysql  Pending 0s
==> v1/Service
NAME                 TYPE CLUSTER-IP    EXTERNAL-IP PORT(S) AGE
guilded-otter-mysql  ClusterIP 10.105.59.60  <none> 3306/TCP 0s

Helm 在这里安装了许多我们识别为 Kubernetes 对象的组件,包括 Deployment、Secret 和 ConfigMap。你可以通过helm ls查看你的 MySQL 安装,使用helm delete <cluster_name>删除你的 MySQL 安装。你也可以通过helm init <chart_name>创建自己的 charts,并使用 Helm lint 对这些 charts 进行 lint 检查。

如果你想了解更多关于 Helm 强大工具的使用,可以查看文档:docs.helm.sh/。在下一个章节中,我们还会深入探讨更多的综合示例,重点讲解 CI/CD。

总结

现在我们应该对 Kubernetes 中应用程序扩展的基础知识有了更深入的了解。我们还研究了内置功能,以便进行更新发布,并探讨了测试和逐步集成更新的手动过程。我们查看了如何扩展底层集群的节点,并增加 Kubernetes 资源的整体容量。最后,我们探索了一些新的自动扩展概念,既适用于集群,也适用于我们的应用程序本身。

在下一个章节中,我们将探讨最新的扩展和更新应用程序的技术,特别是新的deployments资源类型,以及我们可以在 Kubernetes 上运行的其他类型的工作负载。

问题

  1. 哪个命令可以增加复制控制器的数量,并通过新的 Deployments 抽象满足应用程序需求?

  2. 提供平稳发布以避免中断用户体验的策略叫什么?

  3. 在部署期间,哪种类型的会话亲和性是可用的?

  4. 最近 Kubernetes 新增了什么功能,允许集群中的 Pods 水平扩展?

  5. 设置了哪些环境变量可以使集群根据需求扩展 Kubernetes 节点?

  6. 哪种软件工具允许你安装应用程序并利用产品团队的安装设置经验?

  7. Helm 安装文件叫什么?

进一步阅读

如果你想了解更多关于 Helm 的信息,可以访问它的网页:www.helm.sh/blog/index.html。如果你想了解更多关于集群自动扩缩容背后的软件,可以查看 Kubernetes 的 autoscaler 仓库:github.com/kubernetes/autoscaler

第七章:设计持续集成和交付

本章将向读者展示如何将构建管道和部署与 Kubernetes 集群集成。它将介绍如何将 gulp.js 和 Jenkins 与 Kubernetes 集群结合使用的概念。我们还将使用 Helm 和 Minikube 展示如何通过更新的、更先进的方法来实现持续集成和交付。

本章将涵盖以下主题:

  • 将 Kubernetes 与持续部署管道集成

  • 使用 gulp.js 与 Kubernetes

  • 将 Jenkins 与 Kubernetes 集成

  • 安装并使用 Helm 和 Jenkins

技术要求

你需要启用并登录到你的 Google Cloud Platform 账户,或者你也可以使用本地的 Minikube Kubernetes 实例。你还可以使用为 Web 设计的 Play with Kubernetes 应用,网址为 labs.play-with-k8s.com/

本章的 GitHub 仓库链接:github.com/PacktPublishing/Getting-Started-with-Kubernetes-third-edition/tree/master/Code-files/Chapter07.

将 Kubernetes 与持续交付管道集成

持续集成和交付是现代开发中的关键组成部分。持续集成/持续交付 (CI/CD) 通常在构建运行后容易移除它们。此外,如果你的集群中已经有了大量基础设施,可以利用闲置容量进行构建和测试。

在本文中,我们将探讨两种在构建和部署软件中常用的工具:

  • gulp.js:这是一个简单的任务运行器,用于通过 JavaScript 和 Node.js 自动化构建过程

  • Jenkins:这是一个功能齐全的持续集成服务器

gulp.js

gulp.js 为我们提供了构建作为代码的框架。类似于基础设施即代码,它允许我们以编程方式定义构建过程。我们将通过一个简短的示例来演示如何创建一个完整的工作流,从 Docker 镜像构建到最终的 Kubernetes 服务。

先决条件

在本文的这一部分,你需要安装并准备好 Node.js 环境,包括 node 包管理器 (npm)。如果你还没有安装这些包,你可以在 docs.npmjs.com/getting-started/installing-node 查找安装说明。

你可以使用 node -v 命令检查 Node.js 是否正确安装。

您还需要 Docker CE 和 Docker Hub 账户来推送新镜像。您可以在docs.docker.com/installation/找到安装 Docker CE 的说明。您可以轻松在hub.docker.com/创建一个 DockerHub 账户。

在获取凭证后,您可以使用 CLI 通过 $ docker login 命令登录。

gulp.js 构建示例

我们先创建一个名为 node-gulp 的项目目录:

$ mkdir node-gulp
$ cd node-gulp

接下来,我们将安装 gulp 包,然后通过运行带有版本标志的 npm 命令来检查它是否准备就绪,如下所示:

$ npm install -g gulp

您可能需要打开一个新的终端窗口,以确保 gulp 已加入您的路径中。另外,确保使用以下命令导航回您的 node-gulp 目录:

 $ gulp -v

接下来,我们将在项目文件夹中本地安装 gulp,并安装 gulp-gitgulp-shell 插件,如下所示:

$ npm install --save-dev gulp
$ npm install gulp-git -save
$ npm install --save-dev gulp-shell

最后,我们需要创建一个 Kubernetes 控制器和服务定义文件,以及一个 gulpfile.js 文件,以运行我们所有的任务。同样,如果您愿意,也可以直接将这些文件复制过来,这些文件可以在书籍文件包中找到。请参阅以下 node-gulp-controller.yaml 文件:

apiVersion: v1 
kind: ReplicationController 
metadata: 
  name: node-gulp 
  labels: 
    name: node-gulp 
spec: 
  replicas: 1 
  selector: 
    name: node-gulp 
  template: 
    metadata: 
      labels: 
        name: node-gulp 
    spec: 
      containers: 
      - name: node-gulp 
        image: <your username>/node-gulp:latest 
        imagePullPolicy: Always 
        ports: 
        - containerPort: 80 

如您在前面的代码中看到的,我们有一个基本的控制器。您需要将 <your username>/node-gulp:latest 替换为您的 Docker Hub 用户名。将以下代码保存为 node-gulp-service.yaml 文件:

apiVersion: v1 
kind: Service 
metadata: 
  name: node-gulp 
  labels: 
    name: node-gulp 
spec: 
  type: LoadBalancer 
  ports: 
  - name: http 
    protocol: TCP 
    port: 80 
  selector: 
    name: node-gulp 

接下来,我们有一个简单的服务,它从我们的控制器中选择 pods,并创建一个外部负载均衡器以供访问,如前所述:

var gulp = require('gulp'); 
var git = require('gulp-git'); 
var shell = require('gulp-shell'); 

// Clone a remote repo 
gulp.task('clone', function(){ 
  return git.clone('https://github.com/jonbaierCTP/getting-started-with-kubernetes-se.git', function (err) { 
    if (err) throw err; 
  }); 

}); 

// Update codebase 
gulp.task('pull', function(){ 
  return git.pull('origin', 'master', {cwd: './getting-started-with-kubernetes-se'}, function (err) { 
    if (err) throw err; 
  }); 
}); 

//Build Docker image 
gulp.task('docker-build', shell.task([ 
  'docker build -t <your username>/node-gulp ./getting-started-with-kubernetes-se/docker-image-source/container-info/', 
  'docker push <your username>/node-gulp' 
])); 

//Run new pod 
gulp.task('create-kube-pod', shell.task([ 
  'kubectl create -f node-gulp-controller.yaml', 
  'kubectl create -f node-gulp-service.yaml' 
])); 

//Update pod 
gulp.task('update-kube-pod', shell.task([ 
  'kubectl delete -f node-gulp-controller.yaml', 
  'kubectl create -f node-gulp-controller.yaml' 
])); 

最后,我们有前面的 gulpfile.js 文件。在这里定义了我们所有的构建任务。同样,在 <your username>/node-gulp 部分填写您自己的 Docker Hub 用户名。

浏览文件时,首先我们可以看到克隆任务从 GitHub 下载我们的镜像源代码。拉取任务对克隆的仓库执行 git pull。接下来,docker-build 命令从 container-info 子文件夹构建一个镜像并推送到 Docker Hub。最后,我们有 create-kube-podupdate-kube-pod 命令。正如您可能猜到的,create-kube-pod 命令首次创建我们的控制器和服务,而 update-kube-pod 命令则简单地替换控制器。

让我们继续运行这些命令,看看我们的端到端工作流:

$ gulp clone
$ gulp docker-build

第一次运行时,您也可以运行 create-kube-pod 命令,如下所示:

$ gulp create-kube-pod

就是这些。如果我们运行一个快速的 kubectl describe 命令来查看 node-gulp 服务,我们可以获得新服务的外部 IP。浏览到该 IP,您会看到熟悉的 container-info 应用程序正在运行。请注意,主机名以 node-gulp 开头,就像我们在前面提到的 pod 定义中命名的一样:

在随后的更新中,运行 pullupdate-kube-pod 命令,如下所示:

$ gulp pull
$ gulp docker-build
$ gulp update-kube-pod

这是一个非常简单的示例,但你可以开始看到如何通过几行简单的代码轻松地协调你的构建和部署流程。接下来,我们将看看如何使用 Kubernetes 通过 Jenkins 来实际运行构建。

Jenkins 的 Kubernetes 插件

我们可以利用 Kubernetes 来实现我们的 CI/CD 管道,其中一种方法是将 Jenkins 构建从属节点运行在容器化环境中。幸运的是,Carlos Sanchez 已经写了一个插件,允许你在 Kubernetes 的 Pod 中运行 Jenkins 从属节点。

前提条件

你需要准备一个 Jenkins 服务器来进行下一个示例。如果你没有可以使用的服务器,可以在hub.docker.com/_/jenkins/上使用一个 Docker 镜像。

从 Docker CLI 运行它非常简单,只需要以下命令:

docker run --name myjenkins -p 8080:8080 -v /var/jenkins_home jenkins

安装插件

登录到你的 Jenkins 服务器,在首页仪表盘上,点击 Manage Jenkins。

给安装新 Jenkins 服务器的用户的提醒:当你第一次登录到 Jenkins 服务器时,它会要求你安装插件。请选择默认插件,否则将不会安装任何插件!

然后,在 Manage Jenkins 页面上,从列表中选择 Manage Plugins,如下所示:

Jenkins 的主仪表盘

需要安装 Credentials 插件,但它应该已经默认安装。如果不确定,我们可以检查 Installed 标签,如下截图所示:

Jenkins 中已安装的插件

接下来,让我们点击 Available 标签。Kubernetes 插件应该位于 Cluster Management 和 Distributed Build 或 Misc (cloud)下。插件很多,因此你也可以选择在页面上搜索 Kubernetes。勾选 Kubernetes 插件的框,然后点击 Install without restart。这将安装 Kubernetes 插件和 Durable Task 插件:

Jenkins 中的插件安装界面

如果你希望安装非标准版本,或者只是喜欢调试,你可以选择下载插件。最新的 Kubernetes 和 Durable task 插件可以在这里找到:

接下来,我们可以点击 Advanced 标签并向下滚动到 Upload plugin。定位到 durable-task.hpi 文件并点击 Upload。你应该会看到一个显示安装进度条的屏幕。经过一两分钟后,它会更新为 Success。

最后,安装主 Kubernetes 插件。在左侧,点击 Manage Plugins 然后再次点击 Advanced 标签。这次,上传 kubernetes.hpi 文件并点击 Upload。几分钟后,安装应该完成。

配置 Kubernetes 插件

点击“返回到仪表盘”或左上角的 Jenkins 链接。在主仪表盘页面上,点击凭证链接。从列表中选择一个域;在我的情况下,我使用了默认的全局凭证域。点击“添加凭证”,然后你将看到以下屏幕:

添加凭证屏幕

将“种类”保持为“用户名和密码”,将“范围”设置为“全局(Jenkins、节点、项目、所有子项目等)”。添加你的 Kubernetes 管理员凭证。记住,你可以通过运行以下config命令找到它们:

$ kubectl config view

你可以将 ID 留空,在描述中填写合适的内容,然后点击“确定”按钮。

现在我们已经保存了凭证,可以添加我们的 Kubernetes 服务器。点击左上角的 Jenkins 链接,然后选择“管理 Jenkins”。在这里,选择“配置系统”并向下滚动到云部分。从“添加新云”下拉菜单中选择 Kubernetes,将会出现一个 Kubernetes 部分,如下所示:

Jenkins 中的新 Kubernetes 云设置页面

你需要以https://<Master IP>/的形式指定你的主节点的 URL。

接下来,从下拉菜单中选择我们添加的凭证。由于 Kubernetes 默认使用自签名证书,你还需要勾选“禁用 https 证书检查”复选框。

点击“测试连接”,如果一切顺利,你应该会看到按钮旁显示“连接成功”。

如果你使用的是旧版本的插件,可能看不到“禁用 https 证书检查”复选框。如果是这种情况,你需要直接在 Jenkins 主节点上安装自签名证书。

最后,我们将通过从“添加 Pod 模板”下拉菜单中选择 Kubernetes Pod 模板来添加一个 pod 模板,位于镜像旁边。

这将创建另一个新的部分。在名称和标签部分使用jenkins-slave。点击容器旁的“添加”按钮,再次使用jenkins-slave作为名称。使用csanchez/jenkins-slave作为 Docker 镜像,并将工作目录留空为/home/jenkins

标签可以在构建设置中后续使用,强制构建使用 Kubernetes 集群:

Kubernetes 集群添加

这是扩展集群添加的 pod 模板,如下图所示:

Kubernetes Pod 模板

点击保存,设置完成。现在,在 Jenkins 中创建的新构建可以使用我们刚刚创建的 Kubernetes pod 中的从节点。

这是关于防火墙的另一个注意事项。Jenkins 主节点需要能够被 Kubernetes 集群中的所有机器访问,因为 Pod 可能会出现在任何地方。你可以在 Jenkins 中通过“管理 Jenkins | 配置全局安全”查看端口设置。

Helm 和 Minikube

让我们尝试使用其他工具设置一些 CI/CD,以便可以体验 Kubernetes 生态系统中的最新产品。首先,让我们探索如何使用 Helm 安装 Jenkins 的简易性。

首先,打开 Minikube 仪表板,这样你可以看到我们安装各种东西时发生了什么。使用以下命令来实现:

$ minikube dashboard

让我们为 Jenkins 环境创建一个命名空间,如下所示:

$ kubectl get namespaces
NAME          STATUS AGE
default       Active 3d
kube-public   Active 3d kube-system   Active 3d

现在,让我们创建一个模板:

$ mkdir -p ~/gsw-k8s-helm && cd ~/gsw-k8s-helm
$ cat <<K8s >> namespace-jenkins.yaml
apiVersion: v1
kind: Namespace
metadata:
 name: gsw-k8s-jenkins
K8s

现在,你可以按以下方式创建命名空间:

kubectl create -f namespace-jenkins.yaml
namespace "gsw-k8s-jenkins" created

有两种方法可以验证它是否已实际创建。首先,你可以通过 minikube dashboard 命令查看仪表板:

其次,你可以使用 kubectl get namespaces 查看 CLI:

$ helm-jenkins jesse$ kubectl get namespaces
NAME              STATUS AGE
default           Active 5d
gsw-k8s-jenkins   Active 1d
kube-public       Active 5d
kube-system       Active 5d

让我们为 Jenkins 创建一个持久化存储卷。这将允许我们在 Minikube 重启时将数据持久化到集群中。在生产环境中,你需要使用某种类型的存储块或驱动程序。让我们创建一个名为 jenkins-persistjenkins-volume.yaml 文件。

这是你需要放入该文件的内容:

apiVersion: v1
kind: PersistentVolume
metadata:
 name: jenkins-persist
 namespace: jenkins-project
spec:
 storageClassName: jenkins-persist
 accessModes:
   - ReadWriteOnce
 capacity:
   storage: 20Gi
 persistentVolumeReclaimPolicy: Retain
 hostPath:
   path: /storage/jenkins-volume/

现在,让我们为 Jenkins 创建存储卷:

$ kubectl create -f jenkins-volume.yaml
persistentvolume "jenkins-persist" created

很棒!现在,我们准备好使用 Helm 来轻松安装 Jenkins。让我们使用以下 values 文件进行安装:

# Default values for jenkins.
# This is a YAML-formatted file.
# Declare name/value pairs to be passed into your templates.
# name: value
## Overrides for generated resource names
# See templates/_helpers.tpl
# nameOverride:
# fullnameOverride:
Master:
 Name: jenkins-master
 Image: "jenkins/jenkins"
 ImageTag: "2.127"
 ImagePullPolicy: "Always"
 Component: "jenkins-master"
 UseSecurity: true
 AdminUser: admin
 # AdminPassword: <defaults to random>
 Cpu: "200m"
 Memory: "256Mi"
 ServicePort: 8080
 # For minikube, set this to NodePort, elsewhere use LoadBalancer # <to set explicitly, choose port between 30000-32767>
ServiceType: NodePort
 NodePort: 32000
 ServiceAnnotations: {}
 ContainerPort: 8080
 # Enable Kubernetes Liveness and Readiness Probes
 HealthProbes: true
 HealthProbesTimeout: 60
 SlaveListenerPort: 50000
 LoadBalancerSourceRanges:
 - 0.0.0.0/0
 # List of plugins to be install during Jenkins master start
 InstallPlugins:
 - kubernetes:1.7.1
 - workflow-aggregator:2.5
 - workflow-job:2.21
 - credentials-binding:1.16
 - git:3.9.1
 - greenballs:1.15
 # Used to approve a list of groovy functions in pipelines used
the script-security plugin. Can be viewed under /scriptApproval
 ScriptApproval:
 - "method groovy.json.JsonSlurperClassic parseText
java.lang.String"
 - "new groovy.json.JsonSlurperClassic"
 - "staticMethod
org.codehaus.groovy.runtime.DefaultGroovyMethods leftShift
java.util.Map java.util.Map"
 - "staticMethod
org.codehaus.groovy.runtime.DefaultGroovyMethods split
java.lang.String"
 CustomConfigMap: false
 NodeSelector: {}
 Tolerations: {}
Agent:
 Enabled: true
 Image: jenkins/jnlp-slave
 ImageTag: 3.10-1
 Component: "jenkins-slave"
 Privileged: false
 Cpu: "200m"
 Memory: "256Mi"
 # You may want to change this to true while testing a new image
 AlwaysPullImage: false
 # You can define the volumes that you want to mount for this
container
 # Allowed types are: ConfigMap, EmptyDir, HostPath, Nfs, Pod,
Secret
 volumes:
 - type: HostPath
 hostPath: /var/run/docker.sock
 mountPath: /var/run/docker.sock
 NodeSelector: {}
Persistence:
 Enabled: true
 ## A manually managed Persistent Volume and Claim
 ## Requires Persistence.Enabled: true
 ## If defined, PVC must be created manually before volume will
be bound
 # ExistingClaim:
 ## jenkins data Persistent Volume Storage Class
 StorageClass: jenkins-pv
 Annotations: {}
 AccessMode: ReadWriteOnce
 Size: 20Gi
 volumes:
 # - name: nothing
 # emptyDir: {}
 mounts:
 # - mountPath: /var/nothing
 # name: nothing
 # readOnly: true
NetworkPolicy:
 # Enable creation of NetworkPolicy resources.
 Enabled: false
 # For Kubernetes v1.4, v1.5 and v1.6, use 'extensions/v1beta1'
 # For Kubernetes v1.7, use 'networking.k8s.io/v1'
 ApiVersion: networking.k8s.io/v1
## Install Default RBAC roles and bindings
rbac:
 install: true
 serviceAccountName: default
 # RBAC api version (currently either v1beta1 or v1alpha1)
 apiVersion: v1beta1
 # Cluster role reference
 roleRef: cluster-admin

现在我们已经设置了 values 文件,让我们使用它来部署 Jenkins:

helm install --name gsw-k8s-jenkins -f jenkins-vaules.yaml stable/jenkins --namespace gsw-k8s-jenkins
NAME:   gsw-k8s-jenkins
LAST DEPLOYED: Mon Jun 18 22:44:34 2018
NAMESPACE: gsw-k8s-jenkins
STATUS: DEPLOYED
RESOURCES:
…

我们可以通过访问 Kubernetes 密钥存储 API 来获取随机生成的 Jenkins 密钥:

$ kubectl get secret --namespace gsw-k8s-jenkins gsw-k8s-jenkins -o jsonpath="{.data.jenkins-admin-password}" | base64 --decode; echo
<YOUR_PASSWORD_HERE>

使用以下命令验证 Jenkins 是否已安装:

$ helm ls
NAME REVISION UPDATED STATUS CHART NAMESPACE
gsw-k8s-jenkins 1 Mon Jun 18 22:44:34 2018 DEPLOYED jenkins-0.16.3 gsw-k8s-jenkins

然后,打开 Jenkins 的主页。你应该能够通过 http://192.168.99.100:3200 访问主页。

有趣的额外内容

fabric8 自称为一个集成平台。它包括各种日志记录、监控和持续交付工具。它还拥有一个漂亮的控制台、一个 API 注册表和一个可以让你射击 pods 的 3D 游戏。它是一个非常酷的项目,实际上是在 Kubernetes 上运行的。该项目的网站可以在 fabric8.io/ 找到。

fabric8 可以通过一个简单的命令在你的 Kubernetes 集群上轻松设置,因此请参考 fabric8.io/guide/getStarted/gke.html/ 了解更多信息。

总结

我们查看了两种可以与 Kubernetes 一起使用的持续集成工具。我们简要地演示了如何在集群中部署 gulp.js 任务。我们还看到了一个新插件,它用于将 Jenkins 构建代理集成到 Kubernetes 集群中。你现在应该对 Kubernetes 如何与你自己的 CI/CD 流水线集成有了更清晰的认识。

问题

  1. gulp.js 让我们能够构建什么类型的软件?

  2. 我们安装的流行 CI/CD 系统的名称是什么?

  3. Jenkins 的另一种安装方法是什么?

  4. 在 Kubernetes 上运行 Jenkins 需要哪种类型的存储卷?

  5. 在 Kubernetes 上运行 Jenkins 的另一个要求是什么?

  6. 部署时使用的控制器是什么类型的?

  7. 我们使用了什么工具来安装 gulp.js?

深入阅读

如果你想了解有关 Node.js 和 gulp.js 生态系统的更多信息,可以查看以下书籍:

如果你希望获得如何使用 Jenkins 的更多指导,请阅读以下内容:

第八章:监控与日志记录

本章将介绍在我们的 Kubernetes 集群中使用和定制内置及第三方监控工具。我们将讨论如何使用这些工具来监控集群的健康状态和性能。此外,我们还将了解内置日志、Google Cloud Logging 服务以及 Sysdig。

本章将涵盖以下内容:

  • Kubernetes 如何使用 cAdvisor、Heapster、InfluxDB 和 Grafana

  • 自定义默认的 Grafana 仪表板

  • 使用 Fluentd 和 Grafana

  • 安装和使用日志工具

  • 使用流行的第三方工具,如 Stackdriver 和 Sysdig,来扩展我们的监控能力

技术要求

你需要启用并登录到你的 Google Cloud Platform 账户,或者使用本地的 Minikube 实例来运行 Kubernetes。你也可以通过 Web 使用 Play with Kubernetes:labs.play-with-k8s.com/

监控操作

真实世界中的监控远不止于检查系统是否正常运行。尽管像你在第二章《基于核心 Kubernetes 构件建立基础》中的健康检查部分学到的健康检查可以帮助我们隔离问题应用程序,但运营团队能够在系统停机前预见问题并加以缓解,才能更好地为业务服务。

监控的最佳实践是衡量核心资源的性能和使用情况,并观察偏离正常基线的趋势。容器在这方面并不例外,管理 Kubernetes 集群的关键组成部分是能够清晰地查看所有节点的操作系统、网络、系统(CPU 和内存)以及存储资源的性能和可用性。

本章将探讨几种监控和衡量集群资源的性能与可用性的方法。此外,我们还将研究当出现不规则趋势时,如何进行告警和通知。

内置监控

如果你还记得第一章《Kubernetes 简介》,我们提到我们的节点已经运行了多个监控服务。我们可以通过如下命令,指定 kube-system 命名空间,再次查看这些服务:

$ kubectl get pods --namespace=kube-system

以下截图是前面命令的结果:

系统 pod 列表

同样,我们看到多种服务,但这些是如何协同工作的呢?如果你还记得,第二章中关于节点(以前称为 minions)的部分 Chapter 2,构建核心 Kubernetes 结构的基础,每个节点都在运行kubeletkubelet是节点与 API 服务器交互并进行更新的主要接口。此类更新之一是节点资源的指标。资源使用的实际报告是由名为 cAdvisor 的程序执行的。

cAdvisor 程序是 Google 的另一个开源项目,它提供关于容器资源使用的各种指标。指标包括 CPU、内存和网络统计信息。无需为单独的容器配置 cAdvisor,它会收集节点上所有容器的指标并将其报告给kubelet,然后kubelet将这些数据报告给 Heapster。

Google's open source projects:Google 有很多与 Kubernetes 相关的开源项目。查看它们,使用它们,甚至贡献你自己的代码!

cAdvisor 和 Heapster 都在以下 GitHub 部分中提到:

Contrib 是一个包含各种组件的统称,这些组件并非 Kubernetes 核心的一部分。它可以在github.com/kubernetes/contrib找到。LevelDB 是一个键值存储库,它在 InfluxDB 的创建中被使用。你可以在github.com/google/leveldb找到它。

Heapster 是 Google 的另一个开源项目;你可能开始看到一些模式(参见前面的信息框)。Heapster 运行在一个 minion 节点上的容器中,汇总来自kubelet的数据。提供了一个简单的 REST 接口来查询数据。

在使用 GCE 设置时,我们会为我们安装一些额外的软件包,这样可以节省时间,并提供一个完整的包来监控我们的容器工作负载。正如我们从前面的系统 Pod 列表截图中看到的,标题中还有一个包含influx-grafana的 Pod。

InfluxDB 在其官方网站上的描述如下:

一个开源的分布式时间序列数据库,没有外部依赖。

InfluxDB 基于一个键值存储包(参见前面的Google 的开源项目信息框),非常适合存储和查询基于事件或时间的统计数据,比如 Heapster 提供的数据。

最后,我们有 Grafana,它提供了一个仪表板和图表界面,用于显示存储在 InfluxDB 中的数据。使用 Grafana,用户可以创建自定义监控仪表板,并立即查看 Kubernetes 集群的健康状况,从而了解整个容器基础设施的运行情况。

探索 Heapster

快速查看 REST 接口,我们可以通过 SSH 连接到正在运行 Heapster Pod 的节点。首先,我们可以列出 Pod,找到正在运行 Heapster 的 Pod,命令如下:

$ kubectl get pods --namespace=kube-system

Pod 的名称应以 monitoring-heapster 开头。运行 describe 命令查看它运行在哪个节点,命令如下:

$ kubectl describe pods/<Heapster monitoring Pod> --namespace=kube-system

从下面截图的输出中,我们可以看到 Pod 正在 kubernetes-minion-merd 中运行。还请注意稍后会用到的 Pod 的 IP 地址,见下几行:

Heapster Pod 详细信息

接下来,我们可以使用熟悉的 gcloud ssh 命令 SSH 连接到此主机,命令如下:

$ gcloud compute --project "<Your project ID>" ssh --zone "<your gce zone>" "<kubernetes minion from describe>"

从这里,我们可以直接通过 Pod 的 IP 地址访问 Heapster REST API。记住,Pod 的 IP 地址不仅在容器内部可路由,而且在节点本身也能路由。Heapster API 正在监听 8082 端口,我们可以在 /api/v1/metric-export-schema/ 路径下获取完整的指标列表。

现在让我们通过执行 curl 命令查询我们从 describe 命令中保存的 Pod IP 地址,命令如下:

$ curl -G <Heapster IP from describe>:8082/api/v1/metric-export-schema/

我们将看到一个非常长的列表。第一部分展示了所有可用的指标,最后两部分列出了可以用来过滤和分组的字段。为了方便阅读,我已经添加了以下表格:

Metric 描述 单位 类型
uptime 容器启动以来经过的毫秒数 毫秒 累计
cpu/usage 所有核心的累计 CPU 使用情况 纳秒 累计
cpu/limit CPU 限制(以毫核为单位) - 测量
memory/usage 总内存使用量 字节 测量
memory/working_set 总的工作集使用情况;工作集是当前使用的内存,且不容易被内核丢弃 字节 测量
memory/limit 内存限制 字节 测量
memory/page_faults 页面错误的数量 - 累计
memory/major_page_faults 主要页面错误的数量 - 累计
network/rx 网络接收的累计字节数 字节 累计
network/rx_errors 网络接收过程中累计的错误数 - 累计
network/tx 网络发送的累计字节数 字节 累计
network/tx_errors 网络发送过程中累计的错误数 - 累计
filesystem/usage 文件系统消耗的总字节数 字节 测量
filesystem/limit 文件系统的总大小(字节数) 字节 测量
filesystem/available 文件系统中剩余的可用字节数 字节 测量

表 6.1:可用的 Heapster 指标

字段 描述 标签类型
nodename 容器运行所在的节点名称 常见
hostname 容器运行的主机名 常见
host_id 特定于主机的标识符,由云提供商或用户设置 Common
container_base_image 用户定义的容器内运行的镜像名称 Common
container_name 用户提供的容器名称或系统容器的完整名称 Common
pod_name Pod 的名称 Pod
pod_id Pod 的唯一 ID Pod
pod_namespace Pod 的命名空间 Pod
namespace_id Pod 命名空间的唯一 ID Pod
labels 用户提供的标签,以逗号分隔 Pod

表 6.2. 可用的 Heapster 字段

自定义仪表盘

现在我们已经有了字段,可以开始有些有趣的操作了。回想一下我们在第一章中看到的 Grafana 页面,Kubernetes 入门。让我们再次打开它,方法是访问我们集群的监控 URL。注意,你可能需要用集群凭据登录。请参照以下链接格式:https://<your master IP>/api/v1/proxy/namespaces/kube-system/services/monitoring-grafana

我们会看到默认的主页仪表盘。点击主页旁边的向下箭头,选择集群。这会显示 Kubernetes 集群仪表盘,现在我们可以将自己的统计数据添加到仪表盘上。滚动到页面底部,点击添加一行。这会为新的一行创建一个空间,并在屏幕的左侧显示一个绿色标签。

让我们首先添加每个节点(minion)的文件系统使用情况视图。点击绿色标签以展开,然后选择添加面板,再选择图形。屏幕上应该会出现一个空白图形,并伴有一个查询面板用于我们的自定义图形。

此面板中的第一个字段应该显示一个查询,查询以 SELECT mean("value") FROM 开头。点击此字段旁边的 A 字符以展开它。将 FROM 后面的第一个字段保持默认值,然后点击下一个包含选择度量值的字段。将出现一个下拉菜单,其中列出了我们在前面表格中看到的 Heapster 指标。选择 filesystem/usage_bytes_gauge。现在,在 SELECT 行中,点击 mean(),然后点击 x 符号将其移除。接下来,点击行末的 + 符号,添加选择器和 max。然后,你将看到一个 GROUP BY 行,包含 time($interval) 和 fill(none)。仔细点击 fill,而不是 (none) 部分,然后再次点击 x 以移除它。

然后,点击行末的 + 符号,选择标签(hostname)。最后,在屏幕底部我们应该能看到按时间间隔分组的选项。在那里输入5s,然后你应该能看到类似以下截图的内容:

Heapster Pod 详情

接下来,点击“坐标轴”标签,以便设置单位和图例。在左侧 Y 轴下,点击单位旁边的字段并将其设置为数据 | 字节,标签设置为“磁盘空间使用”。在右侧 Y 轴下,将单位设置为无 | 无。接下来,在“图例”标签下,确保勾选“选项中的显示”和“值中的最大值”。

现在,让我们快速进入“常规”标签页并选择一个标题。在我的例子中,我将其命名为Filesystem Disk Usage by Node (max)

我们不想丢失我们刚刚创建的漂亮新图表,因此让我们点击右上角的保存图标。它看起来像一个软盘(如果你不知道它是什么,可以用 Google 图片搜索)。

点击保存图标后,我们会看到一个绿色的对话框,确认仪表板已保存。现在我们可以点击图表详情面板上方、图表本身下方的 x 符号。

这将返回到仪表板页面。如果我们向下滚动,应该能看到我们的新图表。让我们再向这一行添加一个面板。同样使用绿色标签,然后选择“添加面板 | 单一统计”。再次,一个空白面板将出现,下面是一个设置表单。

假设我们想监控一个特定节点的网络使用情况。我们可以通过首先进入“度量”标签页轻松实现。然后,展开查询字段并将 FROM 字段中的第二个值设置为 network/rx。现在,我们可以通过点击行末的+符号并从下拉菜单中选择 hostname 来指定 WHERE 子句。在 hostname =之后,点击选择标签值并从列表中选择一个从节点。

最后,保留mean()作为第二个 SELECT 字段,示例如下:

单一统计选项

在“选项”标签页中,确保将单位格式设置为数据 | 字节,并勾选“火花线”旁边的框。火花线为我们提供了近期数值变化的快速历史视图。我们可以使用背景模式来填充整个背景;默认情况下,它使用数值下方的区域。

在“着色”中,我们可以选择性地勾选“值”或“背景”框,并选择阈值和颜色。这将允许我们根据指定的阈值层次为值选择不同的颜色。请注意,阈值值必须使用未格式化的数字版本。

现在,让我们返回到“常规”标签页,并将标题设置为Network bytes received (Node35ao)。使用你的从节点标识符。

再次保存我们的工作并返回到仪表板。现在,我们应该看到如下所示的行:

自定义仪表板面板

Grafana 还有许多其他面板类型,您可以尝试,比如仪表板列表、插件列表、表格和文本。

正如我们所看到的,创建一个自定义仪表板并一眼监控集群健康状态其实是很简单的。

FluentD 和 Google Cloud Logging

回顾本章开始时的系统 Pod 列表截图,你可能注意到有一些 Pod 以 fluentd-cloud-logging-kubernetes 开头。这些 Pod 在使用 GCE 提供程序进行 K8s 集群配置时会出现。

每个节点上都有这样的 Pod,它的唯一目的是处理 Kubernetes 日志。如果我们登录到 Google Cloud Platform 帐户,我们可以查看在那里处理的一些日志。只需使用左侧菜单,在 Stackdriver 下选择 Logging。这样我们将进入一个日志列表页面,页面顶部有多个下拉菜单。如果这是你第一次访问该页面,第一个下拉菜单很可能会被设置为 Cloud HTTP Load Balancer。

在此下拉菜单中,我们将看到多个 GCE 类型的条目。选择 GCE VM 实例,然后选择 Kubernetes 主节点或其中一个节点。在第二个下拉菜单中,我们可以选择不同的日志组,包括 kubelet。我们还可以按事件日志级别和日期进行筛选。此外,我们可以使用播放按钮观看实时流入的事件,如下所示:

Google Cloud 日志过滤器

FluentD

现在我们知道 fluentd-cloud-logging-kubernetes Pods 正在将数据发送到 Google Cloud,但为什么我们需要 FluentD 呢?简而言之,FluentD 是一个收集器。

它可以配置多个来源来收集和标记日志,然后将其发送到不同的输出点进行分析、告警或归档。我们甚至可以在数据传输到目的地之前,通过插件对数据进行转换。

不是所有的提供程序设置都会默认安装 FluentD,但它是推荐的方式之一,能为我们未来的监控操作提供更大的灵活性。AWS Kubernetes 设置也使用 FluentD,但它将事件转发到 Elasticsearch。

探索 FluentD:如果你对 FluentD 配置的内部工作原理感兴趣,或者只是想自定义日志收集,我们可以很容易地通过 kubectl exec 命令和本章前面运行的命令中的某个 Pod 名称进行探索。首先,让我们看看能否找到 FluentD 的 config 文件:**$ kubectl exec fluentd-cloud-logging-kubernetes-minion-group-r4qt --namespace=kube-system -- ls /etc/td-agent**

我们将查看 etc 文件夹中的 td-agent 文件夹,它是 fluent 的子文件夹。在这个目录中搜索时,我们应该能看到一个 td-agent.conf 文件。我们可以通过一个简单的 cat 命令查看该文件,如下所示:**$ kubectl exec fluentd-cloud-logging-kubernetes-minion-group-r4qt --namespace=kube-system -- cat /etc/td-agent/td-agent.conf**

我们应该查看多个来源,包括各种 Kubernetes 组件、Docker 和一些 GCP 元素。虽然我们可以在这里进行更改,但请记住,这是一个正在运行的容器,如果 Pod 死亡或重启,我们的更改将不会保存。如果我们真的想要定制,最好将此容器作为基础并构建一个新的容器,之后可以将其推送到仓库以供以后使用。

发展我们的监控操作

虽然 Grafana 为我们提供了监控容器操作的良好起点,但它仍在不断发展中。在实际操作环境中,拥有一个完整的仪表板视图在发现问题时非常有用。然而,在日常场景中,我们更倾向于主动并且希望在问题出现时就收到通知。这种告警能力对于确保运营团队处于领先地位、避免被动反应模式至关重要。

在这个领域有许多可用的解决方案,我们将特别查看其中的两个:GCE 监控(Stackdriver)和 Sysdig。

GCE(Stackdriver)

Stackdriver 是一个很好的公共云基础设施监控起点。它实际上是由 Google 所拥有,因此被集成作为 Google Cloud Platform 的监控服务。在你开始担心锁定问题之前,Stackdriver 也与 AWS 有良好的集成。此外,Stackdriver 提供告警功能,支持将通知发送到各种平台,并支持 Webhooks 用于其他用途。

注册 GCE 监控

在 GCE 控制台的 Stackdriver 部分,点击“监控”。这将打开一个新窗口,在这里我们可以注册 Stackdriver 的免费试用。接下来,我们可以添加我们的 GCP 项目,并可选地添加一个 AWS 账户。这需要更多的步骤,但页面中已提供说明。最后,我们将获得关于如何在集群节点上安装代理的说明。我们可以暂时跳过这一步,但稍后会回来处理。

点击“继续”,设置每日告警,然后再次点击“继续”。

点击“启动监控”继续。我们将被带到主仪表板页面,在这里我们可以看到集群中节点的一些基本统计信息。如果从侧边菜单选择“资源”,然后选择“实例”,我们将进入一个页面,列出所有节点。通过点击单个节点,即使没有安装代理,我们也能看到一些基本信息。

Stackdriver 还提供可以安装在节点上的监控和日志代理。然而,它目前不支持 GCE kube-up 脚本中默认使用的容器操作系统。如果你想要更详细的代理安装,你仍然可以看到 GCE 或 AWS 中任何节点的基本指标,但需要使用另一个操作系统。

警报

接下来,我们可以查看作为监控服务一部分的告警策略。从实例详情页,点击页面顶部“事件”部分中的“创建告警策略”按钮。

我们将点击添加条件并选择一个指标阈值。在目标部分,将 RESOURCE TYPE 设置为实例(GCE)。然后,将 APPLIES TO 设置为组和 kubernetes。将 CONDITION TRIGGERS IF 保持为任何成员违反时触发。

在配置部分,将 IF METRIC 保持为 CPU 使用率(GCE 监控),并将 CONDITION 设置为如上所示。现在,将 THRESHOLD 设置为80,并将 FOR 中的时间设置为 5 分钟。

然后点击保存条件:

Google Cloud 监控警报策略

接下来,我们将添加一个通知。在通知部分,将方法保持为电子邮件,并输入你的电子邮件地址。

我们可以跳过文档部分,但在这里我们可以为警报消息添加文本和格式。

最后,将策略命名为 过高的 CPU 负载,并点击保存策略。

现在,每当我们某个实例的 CPU 使用率超过 80%,我们就会收到电子邮件通知。如果我们需要查看我们的策略,可以在警报下拉菜单中找到它们,然后在屏幕左侧的菜单中选择策略概览。

超越使用 Sysdig 进行系统监控

监控我们的云系统是一个很好的开始,但容器本身的可视化呢?虽然有许多云监控和可视化工具,但 Sysdig 以其能够深入挖掘,不仅限于系统操作,还专门针对容器的能力而脱颖而出。

Sysdig 是开源软件,被誉为一款通用的系统可视化工具,原生支持容器。它是一个命令行工具,可以提供对我们之前查看过的各个领域的深入了解,如存储、网络和系统进程。它的独特之处在于提供的细节级别和对这些进程和系统活动的可视化。此外,Sysdig 还原生支持容器,为我们提供了容器操作的全面视图。这是一个非常推荐的工具,适合你的容器操作工具库。Sysdig 的官方网站是 www.sysdig.org/

Sysdig Cloud

我们稍后将看看 Sysdig 工具及其一些有用的命令行 UI。然而,Sysdig 团队还开发了一款商业产品,名为 Sysdig Cloud,它提供了我们在本章前面讨论过的高级仪表盘、警报和通知服务。此外,Sysdig Cloud 的区别在于对容器的高可视化,包括我们应用拓扑的一些漂亮的可视化效果。

如果你更愿意跳过 Sysdig Cloud 部分并直接尝试命令行工具,只需跳到本章后面的 Sysdig 命令行 部分。

如果你还没有注册,快去 www.sysdigcloud.com 注册 Sysdig Cloud。

在首次激活并登录后,我们将进入欢迎页面。点击“下一步”,会显示一个页面,提供各种选项来安装 Sysdig 代理。在我们的示例环境中,我们将使用 Kubernetes 设置。选择 Kubernetes 后,会显示一个页面,提供 API 密钥和安装说明的链接。安装说明会指导你如何在集群中创建 Sysdig 代理 DaemonSet。别忘了在安装页面添加 API 密钥。

在代理连接之前,我们将无法继续安装页面。在创建 DaemonSet 并等待片刻后,页面应该会继续到 AWS 集成页面。你可以填写这些信息,但在本次操作中,我们将点击“跳过”。然后,点击“开始使用”。

截至本文撰写时,Sysdig 和 Sysdig Cloud 尚未完全兼容 GCE kube-up脚本中默认部署的最新容器操作系统——Google 的容器优化操作系统:cloud.google.com/container-optimized-os/docs

我们将被带到 Sysdig Cloud 的主仪表盘屏幕。我们应该在“探索”标签下看到至少两个从属节点。我们应该看到类似下面截图的内容,显示我们的从属节点:

Sysdig Cloud 探索页面

该页面展示了一个表格视图,左侧的链接让我们可以查看一些关键指标,例如 CPU、内存、网络等。虽然这是一个很好的开始,但详细视图将为我们提供每个节点的更深入的视图。

详细视图

让我们来看看这些视图。选择一个从属节点,然后向下滚动到下面显示的详细信息部分。默认情况下,我们应该看到“按进程概览”视图(如果没有选中,只需从左侧列表中点击它)。如果图表难以阅读,只需使用每个图表左上角的最大化图标来放大视图。

有多种有趣的视图可以探索。这里只列出其他几个,服务|HTTP 概览和主机与容器|按容器概览为我们提供了一些很好的图表供检查。在后者的视图中,我们可以看到每个容器的 CPU、内存、网络和文件使用情况的统计数据。

拓扑视图

此外,底部还有三个拓扑视图。这些视图非常适合帮助我们理解应用程序如何进行通信。点击拓扑|网络流量,并等待几秒钟,直到视图完全加载。它应该与以下截图相似:

Sysdig Cloud 网络拓扑视图

请注意,该视图绘制了从属节点与集群中主节点之间的通信流动。你还可能会在节点框的右上角看到一个加号(+)符号。点击其中一个从属节点上的加号,并使用视图区域顶部的缩放工具来放大查看详细信息,如下图所示:

Sysdig Cloud 网络拓扑详细视图

请注意,我们现在可以看到所有在主节点内运行的 Kubernetes 组件。我们可以看到各个组件如何协同工作。我们可以看到kube-proxykubelet进程在运行,还有一些带有 Docker 鲸鱼标志的框,这些表示它们是容器。如果我们放大并使用加号图标,我们可以看到这些是我们的 Pod 和核心 Kubernetes 进程的容器,正如我们在第一章的主节点上运行的服务中看到的,Kubernetes 简介部分。

另外,如果你在监控的节点中包括了主节点,你可以看到kubelet从某个从节点发起通信,并一路跟踪到主节点的kube-apiserver容器。

我们甚至有时能看到实例与 GCE 基础设施进行通信,更新元数据。这个视图非常适合帮助我们形成一个大致的思路,了解我们的基础设施和底层容器是如何相互通信的。

指标

接下来,让我们切换到左侧菜单中“视图”(Views)旁的“指标”(Metrics)选项卡。在这里,还有各种有用的视图。

让我们来看一下 System 中的 capacity.estimated.request.total.count。这个视图展示了节点在完全负载时能够处理多少请求的估计值。这对于基础设施规划非常有用:

Sysdig Cloud 容量估算视图

警报

现在我们拥有了这么多有用的信息,让我们创建一些通知。向上滚动到页面顶部,找到一个从节点条目旁边的铃铛图标。点击它会打开一个创建警报的对话框。在这里,我们可以设置类似于本章前面部分手动设置的警报。不过,这里还有使用基准线(BASELINE)和主机比较(HOST COMPARISON)选项的功能。

使用基准线(BASELINE)选项非常有帮助,因为 Sysdig 会监视节点的历史模式,并在某个指标超出预期的阈值时发出警报。无需手动设置,这样可以大大节省通知设置的时间,并帮助我们的运维团队在问题出现之前采取主动。请参见以下截图:

Sysdig Cloud 新警报

主机比较(HOST COMPARISON)选项也非常有帮助,因为它可以让我们与其他主机比较指标,并在某个主机的指标与其他主机显著不同时发出警报。一个典型的使用场景是监控从节点的资源使用情况,确保我们的调度约束不会在集群中某个地方造成瓶颈。

你可以选择任何你喜欢的选项,为其命名并设置警告级别。启用通知方法。Sysdig 支持电子邮件、SNS(即 简单通知服务)和 PagerDuty 作为通知方法。你还可以选择启用 Sysdig Capture,以便更深入地了解问题。设置完成后,只需点击创建,你就会在问题出现时开始收到警报。

Sysdig 命令行

无论你是仅使用开源工具,还是尝试完整的 Sysdig Cloud 套件,命令行工具都是一个很好的伙伴,可以帮助你追踪问题或更深入地了解你的系统。

在核心工具中,除了主要的 sysdig 工具外,还有一个命令行风格的 UI,名为 csysdig。让我们看一些有用的命令。

查找适合你操作系统的安装说明:www.sysdig.org/install/

安装完成后,首先通过以下命令查看网络活动最多的进程:

$ sudo sysdig -pc -c topprocs_net

以下截图是前述命令的结果:

按网络活动排序的 Sysdig 顶级进程

这是一个交互式视图,将显示按网络活动排序的顶级进程。此外,还有许多可以与 sysdig 一起使用的命令。以下是一些其他有用的命令:

$ sudo sysdig -pc -c topprocs_cpu
$ sudo sysdig -pc -c topprocs_file
$ sudo sysdig -pc -c topprocs_cpu container.name=<Container Name NOT ID>

更多示例可以在 www.sysdig.org/wiki/sysdig-examples/ 找到。

Csysdig 命令行 UI

仅仅因为我们在某个节点的 shell 中,并不意味着我们不能使用 UI。Csysdig 是一个可定制的 UI,用于探索 Sysdig 提供的所有指标和洞察。只需在提示符中输入csysdig

$ csysdig

输入 csysdig 后,我们将看到机器上所有进程的实时列表。在屏幕底部,你会看到一个包含各种选项的菜单。如果你喜欢使用键盘,可以点击 Views 或按 F2。在左侧菜单中有各种选项,我们将查看线程。双击线程(Threads)。

在某些操作系统和 SSH 客户端上,你可能会遇到功能键的问题。检查你的终端设置,确保功能键使用的是 VT100+ 序列。

我们可以看到系统上当前运行的所有线程以及一些资源使用信息。默认情况下,我们会看到一个更新频繁的大列表。如果点击筛选器,F4(对于不擅长使用鼠标的用户),我们可以缩小列表范围。

在筛选框中输入 kube-apiserver,如果你在主节点上;或输入 kube-proxy,如果你在一个节点(minion)上,然后按 Enter。现在视图会过滤出该命令下的所有线程:

Csysdig 线程

如果我们想进一步检查,可以简单地选择列表中的一个线程,点击“Dig”或按 F6 键。现在,我们可以看到命令的系统调用的详细列表,实时显示。这是一个非常有用的工具,可以深入了解在我们集群上运行的容器和进程。

点击“Back”或按 Backspace 键返回到上一个屏幕。然后,再次进入“Views”视图。这一次,我们将查看 Containers 视图。同样,我们可以进行过滤,并使用 Dig 视图深入了解系统调用级别的情况。

另一个你可能会注意到的菜单项是“Actions”,它在最新版本中提供。这些功能允许我们从进程监控到操作和响应。它使我们能够从 Csysdig 的各种进程视图中执行各种操作。例如,容器视图中有操作可以进入 Bash shell、杀死容器、检查日志等。值得了解各种操作和快捷键,甚至为常见操作添加你自己的自定义快捷键。

Prometheus

一个新兴的监控工具是名为 Prometheus 的开源工具。Prometheus 是一个由 SoundCloud 团队开发的开源监控工具。你可以在 prometheus.io 上找到更多关于该项目的信息。

他们的网站提供以下功能:

Prometheus 总结

Prometheus 为 Kubernetes 集群的操作员提供了许多价值。让我们来看一下软件的一些重要维度:

  • 操作简单:它被设计为使用本地存储作为可靠性的独立服务器运行

  • 它很精准:你可以使用类似 JQL、DDL、DCL 或 SQL 查询的查询语言来定义告警,并提供多维度的状态视图

  • 大量的库:你可以使用十多种语言和众多客户端库来检查你的服务和软件

  • 高效:通过内存和磁盘上的高效自定义格式存储数据,你可以轻松通过分片和联合来进行扩展,创建一个强大的平台,从中发出强有力的查询,构建强大的数据模型、临时表格、图表和警报。

此外,Prometheus 是 100% 开源的,并且(截至 2018 年 7 月)目前是 CNCF 的一个孵化项目。你可以像安装其他软件一样使用 Helm 安装它,或者按照我们将在这里详细介绍的手动安装方法进行安装。我们今天要讨论 Prometheus 的原因之一是由于 Kubernetes 系统的整体复杂性。由于有大量的活动组件、多个服务器以及可能的不同地理区域,我们需要一个能够应对所有这些复杂性的系统。

Prometheus 的一个好处是它的拉取机制,这使得你可以专注于通过 HTTP 将节点的度量标准作为纯文本暴露出来,Prometheus 然后可以将这些数据拉取到一个集中监控和日志记录的位置。它也是用 Go 编写的,并受到封闭源 Borgmon 系统的启发,这使得它与我们的 Kubernetes 集群完美契合。让我们开始安装吧!

Prometheus 安装选项

与之前的例子一样,我们需要使用本地的 Minikube 安装或我们已经创建的 GCP 集群。登录到你选择的集群,然后让我们开始设置 Prometheus。由于软件的快速发展,实际上有很多安装 Prometheus 的选择:

  • 最简单的手动方法;如果你想根据入门文档从头开始构建软件,你可以访问prometheus.io/docs/prometheus/latest/getting_started/并启动 Prometheus 自身的监控。

  • 中间道路,使用 Helm;如果你想采取中间路线,你可以通过 Helm 在你的集群上安装 Prometheus(github.com/helm/charts/tree/master/stable/prometheus)。

  • 高级的 Operator 方法;如果你想使用最新最强的技术,让我们来看看 Kubernetes 的 Operator 软件类,并使用它来安装 Prometheus。Operator 是由 CoreOS 创建的,CoreOS 最近被 Red Hat 收购了。这应该意味着 Project Atomic 和 Container Linux 会有一些有趣的变化。不过,我们稍后会详细讨论!我们在这里将使用 Operator 模式。

Operator 旨在构建在 Helm 风格的软件管理之上,目的是将额外的人类操作知识融入到应用程序的安装、维护和恢复中。你可以将 Operator 软件视为一个 SRE Operator:一个在运行软件方面的专家。

Operator 是一个特定于应用的控制器,扩展了 Kubernetes API,以便管理复杂的有状态应用,如缓存、监控系统和关系型或非关系型数据库。Operator 利用 API 来创建、配置和管理这些有状态系统,代表用户执行操作。虽然 Deployments 在处理无状态 Web 应用的无缝管理方面非常出色,但 Kubernetes 中的 Deployment 对象在扩展、升级、故障恢复和重新配置这些有状态系统时,面临着协调所有移动部件的挑战。

你可以在这里阅读更多关于扩展 Kubernetes API 的内容:kubernetes.io/docs/concepts/extend-kubernetes/api-extension/

Operator 利用了我们在其他章节中讨论的一些 Kubernetes 核心概念。资源(如 ReplicaSets)和控制器(例如 Deployments、Services 和 DaemonSets)与编码在 Operator 软件中的手动操作步骤的附加操作知识一起使用。例如,当你手动扩展一个 etcd 集群时,过程中的关键步骤之一是为新的 etcd 成员创建一个 DNS 名称,这个名称可以在将其添加到集群后用来路由到新的成员。使用 Operator 模式时,这些系统化的知识被构建到Operator类中,为集群管理员提供对 etcd 软件的无缝更新。

创建 Operator 的难点在于理解相关有状态软件的底层功能,并将其编码到资源配置和控制循环中。请记住,Kubernetes 可以被看作是一个大型的分布式消息队列,消息以 YAML 格式的声明性状态存在,集群操作员定义这些状态,Kubernetes 系统负责将其落实。

创建 Operator 的提示

如果你想在未来创建自己的Operator,可以记住 CoreOS 提供的以下提示。鉴于它们的应用特定领域,在管理复杂应用时需要注意几点。首先,你会有一组系统流程活动,Operator应该能够执行这些操作。这些操作包括创建用户、创建数据库、修改用户权限和密码、删除用户(例如创建许多系统时默认安装的用户)。

你还需要管理你的安装依赖项,即那些在系统正常运行之前必须存在并配置的项。CoreOS 还建议在创建Operator时遵循以下原则:

  • 部署的单步操作:确保你的Operator可以通过一个不需要额外操作的命令来初始化并运行。

  • 新的第三方类型:你的Operator应该利用第三方 API 类型,用户在创建使用你软件的应用程序时会使用这些类型。

  • 使用基础功能:确保你的Operator使用核心的 Kubernetes 对象,如 ReplicaSets、Services 和 StatefulSets,以便充分利用 Kubernetes 开源项目中所投入的大量工作。

  • 兼容且默认工作:确保你构建的Operators与旧版本兼容,并设计你的系统,使得即使Operator被停止或意外从集群中删除,系统仍能继续运行且不受影响。

  • 版本:确保你的Operator支持版本控制,以便集群管理员不会回避更新你的软件。

  • 测试:同样,确保将Operator进行抗破坏性测试,例如使用混沌猴(Chaos Monkey)进行测试!你的Operator应该能够承受节点、pods、存储、配置和网络故障的影响。

安装 Prometheus

让我们通过我们发现的新模式来安装 Prometheus。首先,使用 Prometheus 定义文件来创建部署。我们将在这里使用 Helm 来安装 Operator!

确保你已经安装了 Helm,并确保你已经初始化了它:

$ helm init
master $ helm init
Creating /root/.helm
...
Adding stable repo with URL: https://kubernetes-charts.storage.googleapis.com
Adding local repo with URL: http://127.0.0.1:8879/charts
$HELM_HOME has been configured at /root/.helm.
...
Happy Helming!
$

接下来,我们可以安装此演示所需的各种Operator包:

$ helm repo add coreos https://s3-eu-west-1.amazonaws.com/coreos-charts/stable/
"coreos" has been added to your repositories

现在,安装Operator

$ helm install coreos/prometheus-operator --name prometheus-operator

你可以先检查安装情况来确认它已经安装并在运行:

$ helm ls prometheus-operator
NAME                    REVISION UPDATED                        STATUS CHART NAMESPACE prometheus-operator     1 Mon Jul 23 02:10:18 2018        DEPLOYED prometheus-operator-0.0.28 default

然后,查看 pods:

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
prometheus-operator-d75587d6-bmmvx 1/1 Running 0 2m

现在,我们可以安装kube-prometheus,让所有依赖项开始运行:

$ helm install coreos/kube-prometheus --name kube-prometheus --set global.rbacEnable=true
NAME:   kube-prometheus
LAST DEPLOYED: Mon Jul 23 02:15:59 2018
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
==> v1/Alertmanager
NAME             AGE
kube-prometheus  1s

==> v1/Pod(related)
NAME                                                  READY STATUS RESTARTS AGE
kube-prometheus-exporter-node-45rwl                   0/1 ContainerCreating 0 1s
kube-prometheus-exporter-node-d84mp                   0/1 ContainerCreating 0 1s
kube-prometheus-exporter-kube-state-844bb6f589-z58b6  0/2 ContainerCreating 0 1s
kube-prometheus-grafana-57d5b4d79f-mgqw5              0/2 ContainerCreating 0 1s

==> v1beta1/ClusterRoleBinding
NAME                                     AGE
psp-kube-prometheus-alertmanager         1s
kube-prometheus-exporter-kube-state      1s
psp-kube-prometheus-exporter-kube-state  1s
psp-kube-prometheus-exporter-node        1s
psp-kube-prometheus-grafana              1s
kube-prometheus                          1s
psp-kube-prometheus                      1s
…

我们在这里截断了输出,因为有大量信息。让我们再次查看 pods:

$ kubectl get pods
NAME                                                   READY STATUS RESTARTS AGE
alertmanager-kube-prometheus-0                         2/2 Running 0 3m
kube-prometheus-exporter-kube-state-85975c8577-vfl6t   2/2
Running 0 2m
kube-prometheus-exporter-node-45rwl                    1/1 Running 0 3m
kube-prometheus-exporter-node-d84mp                    1/1 Running 0 3m
kube-prometheus-grafana-57d5b4d79f-mgqw5               2/2 Running 0 3m
prometheus-kube-prometheus-0                           3/3 Running 1 3m
prometheus-operator-d75587d6-bmmvx                     1/1 Running 0 8m

干得不错!

如果你将prometheus-kube-prometheus-0的端口转发到8448,你应该能够看到 Prometheus 仪表板,我们将在后续章节中重新访问它,探索 Kubernetes 集群的高可用性和生产化。你可以通过http://localhost:8449/alerts查看。

总结

我们简要了解了 Kubernetes 的监控和日志记录。你现在应该熟悉 Kubernetes 如何使用 cAdvisor 和 Heapster 收集集群中所有资源的指标。此外,我们看到 Kubernetes 通过提供预配置好的 InfluxDB 和 Grafana,帮助我们节省了时间。仪表板可以根据我们的日常操作需求轻松定制。

此外,我们还了解了使用 FluentD 和 Google Cloud Logging 服务的内置日志记录功能。另外,Kubernetes 通过为我们设置基本配置,节省了大量时间。

最后,你了解了各种可用于监控容器和集群的第三方选项。使用这些工具将帮助我们更深入地了解应用程序的健康状况和状态。所有这些工具结合在一起,提供了一个强大的工具集来管理日常操作。最后,我们探讨了不同的 Prometheus 安装方法,旨在构建更强大的生产系统。

在下一章中,我们将探讨新的集群联邦功能。尽管仍然处于测试阶段,但这一功能将允许我们在不同的数据中心甚至云中运行多个集群,同时从单一控制平面管理和分发应用程序。

问题

  1. 列出 Kubernetes 内置的两种监控工具

  2. 内置监控工具运行在哪个命名空间?

  3. 大多数监控工具使用的图表软件是什么?

  4. FluentD 被称为什么?

  5. Google 的原生监控系统是什么?

  6. 使用 Prometheus 的两个好理由是什么?

进一步阅读

如果你想了解更多关于 Kubernetes 操作符框架的内容,请查看这篇博客文章:coreos.com/blog/introducing-operator-framework

如果你想查看一段关于 Kubernetes 监控的视频,请访问这个视频:www.packtpub.com/mapt/video/virtualization_and_cloud/9781789130003/65553/65558/monitoring-your-infrastructure

第九章:操作系统、平台以及云服务和本地服务提供商

本章的前半部分将讲解开放标准如何鼓励多样化的容器实现生态系统。我们将讨论开放容器倡议OCI)及其提供开放容器规范的使命。本章的后半部分将讨论可用于运行容器化工作负载的各种操作系统,如 CoreOS。我们还将探讨作为宿主操作系统的优势,包括性能和对各种容器实现的支持。此外,我们还将简要介绍 CoreOS 的 Tectonic 企业版。我们将讨论主要云服务提供商CSPs)提供的各种托管平台,并比较它们的优劣。

本章将讨论以下主题:

  • 为什么标准如此重要?

  • OCI 和 云原生计算基金会CNCF

  • 容器规范与实现

  • 各种面向容器的操作系统

  • Tectonic

  • 可运行 Kubernetes 工作负载的 CSP 平台

技术需求

你需要启用并登录你的 Google Cloud Platform 账户,或者可以使用本地的 Minikube 实例来运行 Kubernetes。你还可以在 labs.play-with-k8s.com/ 上使用在线的 Play with Kubernetes。

你还需要 GitHub 凭证,我们将在本章后面介绍如何设置。

本章的 GitHub 仓库可以在 github.com/PacktPublishing/Getting-Started-with-Kubernetes-third-edition/tree/master/Code-files/Chapter09 找到。

标准的重要性

在过去两年中,容器化技术在流行度上经历了巨大的增长。尽管 Docker 一直处于这一生态系统的核心,但容器领域的参与者越来越多。已经有很多替代容器化技术和 Docker 实现的选项(如 rkt、Garden 等)。此外,还有丰富的第三方工具生态系统,它们增强并补充了你的容器基础设施。尽管 Kubernetes 设计用于管理容器的状态及其编排、调度和网络方面的内容,但关键是,这些工具构成了构建云原生应用的基础。

正如我们在本书一开始提到的,容器最吸引人的地方之一就是它们能够将我们的应用程序打包,以便在不同的环境层(即开发、测试和生产)以及不同的基础设施提供商(如 GCP、AWS、本地等)之间进行部署。

要真正支持这种部署灵活性,我们不仅需要容器本身拥有一个共同的平台,还需要底层的规范遵循一套共同的基本规则。这将允许既灵活又高度专业化的实现。例如,一些工作负载可能需要在一个高度安全的实现上运行。为了提供这一点,实施过程必须对一些方面做出更有意图的决策。无论哪种情况,如果我们的容器建立在所有实现都同意并支持的共同结构上,我们将获得更多的灵活性和自由。

在接下来的页面中,我们将探讨 Kubernetes 生态系统中许多竞争标准的构建模块。我们将解释它们是如何变化和发展的,以及它们在未来可能扮演的角色。

我们将在第三版中更深入地探讨的一个例子是 CRI-O 项目,它是在 OCI 宪章创建之后诞生的。让我们确保理解这个使命的重要性。

OCI 宪章

OCI 宪章的使命是确保开源社区拥有一个稳定的平台,从而使行业参与者能够贡献出构建容器驱动应用所需的可移植、开放且供应商中立的运行时。Linux 基金会是宪章的持有者,它是 CNCF 的姊妹组织。我们将在第十一章,Kubernetes SIGs、孵化项目和 CNCF 中更深入地探讨基础设施的影响。

如果你想了解更多关于这些基础的内容,可以访问他们的网站:www.linuxfoundation.org/www.cncf.io/

虽然 OCI 宪章试图标准化生态系统的构建模块,但它并不试图在宏观层面上定义系统,也不推广某种特定的路径或解决方案。它还定义了一个过程,通过这些基础设施帮助技术以负责任的方式成熟,以确保最好的技术能够到达最终用户。这些被定义为以下几个阶段:

  1. 沙盒

  2. 孵化

  3. 毕业

就本章涉及 OCI 的具体内容来说,让我们看看他们还在努力完成什么。首先,我们正在尝试创建一个格式规范。这个规范将指出几个重要维度,以便达成共识:

  • 提供一种格式:为了确保能够跨多个运行时使用的规范,你需要一个标准的容器格式和运行时规范。容器格式由磁盘上的根文件系统表示,并且需要额外的配置,使得给定的容器能够在系统上运行。推动将标准化分类为以下几个层级:基础、可选和超出范围。

  • 提供一个运行时:这个更直接,因为它旨在提供一个可执行文件,可以通过消费上述容器格式和运行时规范直接运行容器。

《宪章》还激励了许多项目,其中前两个是 runc 项目,第三个涉及在 OCI 规范项目中定义自己的规范。新项目由成员通过审查流程添加,该流程需要当前技术监督委员会TOB)的三分之二批准。如果我们更深入地了解 OCI 的治理原则,网站列出了六项指导原则:

  • 技术领导力

  • 通过贡献产生影响

  • 有限的范围,有限的政治

  • 极简结构

  • 代表性领导力

  • 遵守反垄断法规

这些条目融合了哲学和逻辑框架,鼓励竞争、合作、精英主义以及许多敏捷和 DevOps 从业者长期以来所利用的持续改进周期。

现在让我们深入了解一下这个倡议本身。

OCI

首批获得广泛行业参与的倡议之一是 OCI。在 36 个行业合作伙伴中,有 Docker、Red Hat、VMware、IBM、Google 和 AWS,如 OCI 网站所列,网址为www.opencontainers.org/

OCI 的目的是将实现方案,如 Docker 和 rkt,与容器化工作负载的格式和运行时标准分离。根据他们自己的定义,OCI 规范的目标有三个基本原则(你可以在章节末尾的进一步阅读部分找到更多细节):

  • 创建一个容器镜像格式和运行时的正式规范,允许符合规范的容器在所有主要的、合规的操作系统和平台上可移植,不受人为技术障碍的限制。

  • 接受、维护和推动与这些标准相关的项目。它将力求就容器的标准操作集(启动、执行、暂停等)以及与容器运行时相关的运行时环境达成一致。

  • 将先前提到的标准与其他提议的标准(包括 appc 规范)进行协调。

通过遵循这些原则,OCI 希望加强一个协作和包容的生态系统,提供一个丰富且不断发展的工具集,以满足当今复杂应用工作负载的需求,无论是云原生还是传统的。

此外,在此领域的标准发展中,还有一些指导原则。这些原则源自创建 appc 的人的创始信念,具体如下:

  • 安全性:通过使用安全的加密原理和图像及应用代码的监控链条,通过可插拔接口隔离容器。

  • 可移植性:确保容器能够在各种软件、云和硬件之间保持可移植性。

  • 去中心化:容器镜像应简洁明了,并应利用联邦和命名空间的优势。

  • 开放:运行时和格式应由社区构建,并具有多个可互换的部分。

  • 向后兼容:鉴于 Docker 和容器的受欢迎程度(近 90 亿次下载),向后兼容性应给予高度优先考虑。

  • 可组合:容器操作工具应良好集成,但保持模块化。

  • 代码:共识应基于运行中的、可工作的代码,并遵循最简原则,符合领域驱动设计。它应是稳定和可扩展的。

容器运行时接口

让我们来看看其中一个更新的、针对 Kubernetes 的 OCI 基础项目——CRI-O。CRI-O 目前是 Kubernetes 孵化器的一部分,但随着其成熟,它可能会脱离并成为独立项目。CRI-O 设计的一个引人注目的特点是,它从不破坏 Kubernetes。这与其他运行时不同,因为其他运行时的设计目的是做许多事情,如构建镜像、管理安全性、编排和检查镜像。而 CRI-O 仅仅是为了帮助 Kubernetes 编排和调度容器。

你可以通过github.com/kubernetes-incubator/cri-o/获取 CRI-O 项目的代码并阅读文档。

为此,CRI-O 与 CRI 本身一起开发,并与 Kubernetes 系统的上游版本保持一致。下图展示了 CRI-O 如何与 OCI 配合工作:

为了实现这一工作流,发生了以下情况:

  1. 操作员决定启动一个 Pod,这会导致 Kubernetes 使用kubelet来启动 Pod。然后,该kubelet通过 CRI 与 CRI-O 守护进程进行通信。

  2. CRI-O 随后使用多个基于 OCI 标准的库,从注册表中拉取并解压给定的容器镜像。通过这些操作,CRI-O 生成一个 JSON 数据块,供下一步运行容器使用。

  3. CRI-O 启动一个兼容 OCI 的运行时,然后运行容器进程。这可能是 runc,或者是新的 Kata 容器运行时(它已经吸收了 Intel 的清晰容器计划)。

你会注意到,CRI-O 在库和运行时之间充当了一个交错层,它通过使用标准格式来完成大多数目标。这确保了目标是让 Kubernetes 始终有效运行。以下是展示本节描述的流程系统的图示:

对于网络,CRI-O 将利用容器网络接口CNI),这与 CRI 类似,但处理的是网络堆栈。你应该开始看到一些模式的出现。

CRI-O 是一个实现,帮助实现 OCI 规范。这使得用户可以理所当然地将容器运行时作为实现细节,而专注于应用程序如何与 Kubernetes 系统中的对象和抽象进行交互。

尝试使用 CRI-O

让我们看看一些安装方法,帮助你自己尝试 CRI-O。为了开始,你需要一些工具,包括 runc 或其他 OCI 兼容的运行时,以及 socat、iproute 和 iptables。你可以选择几种方式在 Kubernetes 中运行 CRI-O:

  • 在全规模集群中,使用kube-admsystemd通过--container-runtime-endpoint /var/run/crio/crio.sock来利用 CRI-O 套接字。

  • 使用 Minikube,通过特定的命令行选项启动它。

  • 在 Atomic 系统上使用--system-package=no -n cri-o --storage ostree registry.centos.org/projectatomic/cri-o:latest安装。

如果你想从源代码构建 CRI-O,你可以在笔记本电脑上运行以下命令。你需要安装一些依赖项,才能使这个构建阶段正常工作。首先,运行以下命令安装你的依赖项。

以下命令适用于 Fedora、CentOS 和 RHEL 发行版:

yum install -y \
 btrfs-progs-devel \
 device-mapper-devel \
 git \
 glib2-devel \
 glibc-devel \
 glibc-static \
 go \
 golang-github-cpuguy83-go-md2man \
 gpgme-devel \
 libassuan-devel \
 libgpg-error-devel \
 libseccomp-devel \
 libselinux-devel \
 ostree-devel \
 pkgconfig \
 runc \
 skopeo-containers

这些命令适用于 Debian、Ubuntu 及相关发行版:

apt-get install -y \
 btrfs-tools \
 git \
 golang-go \
 libassuan-dev \
 libdevmapper-dev \
 libglib2.0-dev \
 libc6-dev \
 libgpgme11-dev \
 libgpg-error-dev \
 libseccomp-dev \
 libselinux1-dev \
 pkg-config \
 go-md2man \
 runc \
 skopeo-containers

其次,你需要像下面这样获取源代码:

git clone https://github.com/kubernetes-incubator/cri-o # or your fork
cd cri-o

一旦你有了代码,就可以继续构建它:

make install.tools
make
sudo make install

你可以使用额外的构建标志添加如seccomp、SELinux 和apparmor等内容,格式为:make BUILDTAGS='seccomp apparmor'

你可以使用 Kubernetes 中的local-up-cluster.sh脚本在本地运行 Kubernetes。我还会向你展示如何在 Minikube 上运行它。

首先,克隆 Kubernetes 仓库:

git clone https://github.com/kubernetes/kubernetes.git

接下来,你需要启动 CRI-O 守护进程,并运行以下命令,使用 CRI-O 启动集群:

CGROUP_DRIVER=systemd \
 CONTAINER_RUNTIME=remote \
 CONTAINER_RUNTIME_ENDPOINT='unix:///var/run/crio/crio.sock  --runtime-request-timeout=15m' \
 ./hack/local-up-cluster.sh

如果你有一个正在运行的集群,你也可以使用以下网址的说明,将运行时从 Docker 切换到 CRI-O:github.com/kubernetes-incubator/cri-o/blob/master/kubernetes.md/

让我们还检查一下如何在 Minikube 上使用 CRI-O,这是最简单的实验方式之一:

minikube start \
 --network-plugin=cni \
 --extra-config=kubelet.container-runtime=remote \
 --extra-config=kubelet.container-runtime-endpoint=/var/run/crio/crio.sock \
 --extra-config=kubelet.image-service-endpoint=/var/run/crio/crio.sock \
 --bootstrapper=kubeadm

最后,我们可以使用 GCP 平台创建一个 CRI-O 集群并开始实验:

gcloud compute instances create cri-o \
 --machine-type n1-standard-2 \
 --image-family ubuntu-1610 \
 --image-project ubuntu-os-cloud

让我们使用这些机器快速进行一个教程。使用gcloud compute ssh cri-o通过 SSH 连接到机器。

一旦你进入服务器,我们需要安装cri-ocrioctlcnirunc程序。首先获取runc二进制文件:

wget https://github.com/opencontainers/runc/releases/download/v1.0.0-rc4/runc.amd64

设置可执行权限并将其移动到你的路径,如下所示:

chmod +x runc.amd64
sudo mv runc.amd64 /usr/bin/runc

你可以通过检查版本来验证它是否工作正常:

$ runc -version
runc version 1.0.0-rc4
commit: 2e7cfe036e2c6dc51ccca6eb7fa3ee6b63976dcd
spec: 1.0.0

你需要从源代码安装 CRI-O 二进制文件,因为目前没有发布任何二进制文件。

首先,下载最新的二进制发布版本并安装 Go:

wget https://storage.googleapis.com/golang/go1.8.5.linux-amd64.tar.gz
sudo tar -xvf go1.8.5.linux-amd64.tar.gz -C /usr/local/
mkdir -p $HOME/go/src
export GOPATH=$HOME/go
export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin

这应该很熟悉,因为你安装 Go 的方式和任何其他项目相同。检查你的版本:

go version
go version go1.8.5 linux/amd64

接下来,使用以下命令获取crictl

go get github.com/kubernetes-incubator/cri-tools/cmd/crictl
cd $GOPATH/src/github.com/kubernetes-incubator/cri-tools
make
make install

下载完成后,你需要从源代码构建 CRI-O:

sudo apt-get update && apt-get install -y libglib2.0-dev \
 libseccomp-dev \
 libgpgme11-dev \
 libdevmapper-dev \
 make \
 git

现在,获取并安装 CRI-O:

go get -d github.com/kubernetes-incubator/cri-o
cd $GOPATH/src/github.com/kubernetes-incubator/cri-o
make install.tools
Make
sudo make install

完成后,你需要通过 sudo make install.config 创建配置文件。你需要确保在 /etc/crio/cirio.conf 文件中使用了有效的注册表选项。其示例如下:

registries = ['registry.access..com', 'registry.fedoraproject.org', 'docker.io']

此时,我们已经准备好启动 CRI-O 系统守护进程,我们可以通过使用 systemctl 来实现。让我们创建一个 crio.service

$ vim /etc/systemd/system/crio.service

添加以下文本:

[Unit]
Description=OCI-based implementation of Kubernetes Container Runtime Interface
Documentation=https://github.com/kubernetes-incubator/cri-o

[Service]
ExecStart=/usr/local/bin/crio
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

完成后,我们可以重新加载 systemctl 并启用 CRI-O:

$ sudo systemctl daemon-reload && \
 sudo systemctl enable crio && \
 sudo systemctl start crio

完成后,我们可以通过检查端点的版本来验证是否成功安装了 CRI-O,如下所示:

$ sudo crictl --runtime-endpoint unix:///var/run/crio/crio.sock version
Version:  0.1.0
RuntimeName:  cri-o
RuntimeVersion:  1.10.0-dev
RuntimeApiVersion:  v1alpha1

接下来,我们需要获取最新版本的 CNI 插件,以便我们可以从源代码构建并使用它。我们将使用 Go 来获取我们的源代码:

go get -d github.com/containernetworking/plugins
cd $GOPATH/src/github.com/containernetworking/plugins
./build.sh

接下来,安装 CNI 插件到你的集群中:

sudo mkdir -p /opt/cni/bin
sudo cp bin/* /opt/cni/bin/

现在,我们可以配置 CNI,使得 CRI-O 可以使用它。首先,创建一个目录来存储配置文件,然后我们将设置以下两个配置文件:

sudo mkdir -p /etc/cni/net.d

接下来,你需要创建并配置 10-mynet.conf

sudo sh -c 'cat >/etc/cni/net.d/10-mynet.conf <<-EOF
{
"cniVersion": "0.2.0",
   "name": "mynet",
   "type": "bridge",
   "bridge": "cni0",
   "isGateway": true,
   "ipMasq": true,
   "ipam": {
       "type": "host-local",
       "subnet": "10.88.0.0/16",
       "routes": [
           { "dst": "0.0.0.0/0"  }
       ]
   }
}
EOF'

然后,按如下方式构建 loopback 接口:

sudo sh -c 'cat >/etc/cni/net.d/99-loopback.conf <<-EOF
{
 "cniVersion": "0.2.0",
 "type": "loopback"
}
EOF'

接下来,我们将需要一些来自 Project Atomic 的特殊容器来使其工作。skopeo 是一个符合 OCI 标准的命令行工具,可以对容器镜像和镜像仓库执行各种操作。按如下方式安装容器:

sudo add-apt-repository ppa:projectatomic/ppa
sudo apt-get update
sudo apt-get install skopeo-containers -y

使用 sudo systemctl restart crio 重启 CRI-O 以加载 CNI 配置。太棒了!现在我们已经安装了这些组件,开始构建一些东西吧!

首先,我们将使用来自 Kubernetes 孵化器的模板策略创建一个沙箱。

这个模板不是生产就绪的!

首先切换到带有模板的 CRI-O 源树,如下所示:

cd $GOPATH/src/github.com/kubernetes-incubator/cri-o

接下来,你需要创建并捕获 pod 的 ID:

sudo mkdir /etc/containers/
sudo cp test/policy.json /etc/containers

你可以使用 critcl 获取 pod 状态,如下所示:

sudo crictl inspectp --output table $POD_ID
ID: cd6c0883663c6f4f99697aaa15af8219e351e03696bd866bc3ac055ef289702a
Name: podsandbox1
UID: redhat-test-crio
Namespace: redhat.test.crio
Attempt: 1
Status: SANDBOX_READY
Created: 2016-12-14 15:59:04.373680832 +0000 UTC
Network namespace: /var/run/netns/cni-bc37b858-fb4d-41e6-58b0-9905d0ba23f8
IP Address: 10.88.0.2
Labels:
group -> test
Annotations:
owner -> jwhite
security.alpha.kubernetes.io/seccomp/pod -> unconfined
security.alpha.kubernetes.io/sysctls ->
kernel.shm_rmid_forced=1,net.ipv4.ip_local_port_range=1024 65000
security.alpha.kubernetes.io/unsafe-sysctls -> kernel.msgmax=8192

我们将再次使用 crictl 工具拉取 Redis 服务器的容器镜像:

sudo crictl pull quay.io/crio/redis:alpine
CONTAINER_ID=$(sudo crictl create $POD_ID test/testdata/container_redis.json test/testdata/sandbox_config.json)

接下来,我们将启动并检查 Redis 容器的状态,如下所示:

sudo crictl start $CONTAINER_ID
sudo crictl inspect $CONTAINER_ID

此时,你应该能够 telnet 进入 Redis 容器以测试其功能:

telnet 10.88.0.2 6379
Trying 10.88.0.2…
Connected to 10.88.0.2.
Escape character is '^]'.

干得漂亮—你已经手动创建了一个 pod 和容器,使用了 Kubernetes 系统的一些核心抽象!你可以使用以下命令停止容器并关闭 pod:

sudo crictl stop $CONTAINER_ID
sudo crictl rm $CONTAINER_ID
sudo crictl stopp $POD_ID
sudo crictl rmp $POD_ID
sudo crictl pods
sudo crictl ps

更多关于容器运行时的信息

有许多基于容器和虚拟机的选项适用于符合 OCI 标准的实现。我们知道 runc,这是 OCI 运行时的标准参考实现,容器就使用这个。还有以下选项可用:

还有基于虚拟机的实现,它们采取了不同的安全路径:

这些项目中最有趣的是列表中的最后一个——Kata 容器,它将 clear container 和 runV 结合成一个统一的包。这些基础性组件已经在企业中大规模投入生产使用,而 Kata 旨在为容器化环境提供一个安全、轻量的虚拟机。通过利用 runV,Kata 容器可以在任何 KVM 兼容的虚拟机中运行,如 Xen、KVM 和 vSphere,同时仍与 CRI-O 保持兼容,这一点非常重要!Kata 希望能提供容器的速度和虚拟机的安全性。

这是 Kata 网站上的一个图表,详细解释了架构的视觉细节:

CNCF

另一个在业内广泛接受的倡议是 CNCF。尽管它仍然专注于容器化工作负载,但 CNCF 的运作层级略高,处于应用设计层面。

它的目的是提供一套标准的工具和技术,用于构建、操作和编排云原生应用栈。云计算为我们提供了多种新技术和实践,这些可以改进并演化我们的经典软件设计。CNCF 也特别关注微服务导向开发的新范式。

作为 CNCF 的创始参与者,Google 捐赠了 Kubernetes 开源项目。其目标是提高生态系统中的互操作性,并支持与其他项目的更好集成。CNCF 已经托管了各种关于编排、日志、监控、追踪和应用程序韧性等项目。

欲了解更多关于 CNCF 的信息,请参阅 cncf.io/

我们将在接下来的章节中更多地讨论 CNCF、特殊兴趣小组SIGs)及其中的相关领域。

现在,请考虑这份景观和路径图:www.cncf.io/blog/2018/03/08/introducing-the-cloud-native-landscape-2-0-interactive-edition/

标准容器规范

OCI 努力的核心成果之一是创建和开发全面的容器规范。该规范包括五个核心原则,所有容器都应遵循,我将简要解释一下:

  • 容器必须具有创建、启动和停止容器的标准操作,适用于所有实施。

  • 容器必须与内容无关,这意味着容器内部的应用类型不会改变容器本身的标准操作或发布。

  • 容器还必须与基础设施无关。可移植性至关重要;因此,容器必须能够在 GCE 中和在您公司的数据中心或开发者的笔记本上同样轻松地运行。

  • 容器还必须设计用于自动化,这使我们能够在构建过程中自动化,以及更新和部署管道。虽然这个规则有点模糊,但容器的实施不应要求繁琐的手动步骤来进行创建和发布。

  • 最后,实施必须支持工业级交付。这再次意味着与构建和部署管道相关,并要求在基础设施和部署层之间容器的可移植性和传输中实现高效率。

规范还为容器格式和运行时定义了核心原则。您可以在 open containers GitHub 页面上阅读更多规范信息,网址为github.com/opencontainers/specs

尽管核心规范可能有点抽象,但 runc 的实施是 OCI 规范的一个具体示例,是一个容器运行时和镜像格式。您可以在 GitHub 上阅读更多技术细节,网址为github.com/opencontainers/runc

一些流行容器工具的支持格式和运行时是 runc。它由 Docker 捐赠给 OCI,并基于 Docker 平台使用的相同基础设施工作创建而成。自发布以来,它得到了许多项目的欢迎。

即使是流行的开源 PaaS Cloud Foundry 也宣布将在 Garden 中使用 runc。Garden 为 Diego 提供了容器化的基础设施,作为类似于 Kubernetes 的编排层。

rkt 的实现最初基于 appc 规范。appc 规范实际上是 CoreOS 团队早期的一次尝试,旨在围绕容器化形成一个共同的规范。现在,CoreOS 参与了 OCI,他们正在努力将 appc 规范并入 OCI;这应该会导致容器生态系统中的更高兼容性。

CoreOS

虽然规格提供了我们一个共同的基础,但也有一些趋势在容器操作系统的选择上不断发展。现在有几种量身定制的操作系统正在开发,专门用于运行容器负载。尽管实现方式各异,但它们都有类似的特点。重点在于精简的安装基础、原子化的操作系统更新以及签名应用程序,以实现高效和安全的操作。

一个正在获得越来越多关注的操作系统是 CoreOS。CoreOS 在安全性和资源利用方面都提供了重要的优势。它通过完全去除包依赖关系来提供资源利用。相反,CoreOS 将所有应用程序和服务运行在容器中。通过仅提供一小部分支持容器运行所需的服务,并绕过虚拟化程序的使用,CoreOS 使我们能够使用更多的资源池来运行容器化应用程序。这使得用户能够从基础设施中获得更高的性能,并实现更好的容器与节点(服务器)使用比例。

最近,CoreOS 被红帽收购,这意味着当前版本的 Container Linux 将会与红帽的容器操作系统产品 Project Atomic 竞争。这两款产品最终将合并为 Red Hat CoreOS。如果你考虑到 Fedora 对 Red Hat Enterprise Linux 采取的上游社区方法,那么可以推测 Red Hat CoreOS 也会有类似的方式。

这也意味着红帽将会集成 Tectonic(我们将在本章稍后讨论)以及 Quay,这是 CoreOS 收购的企业级容器注册中心。需要注意的是,rkt 容器标准不会成为收购的一部分,而是将成为一个社区支持的项目。

如果你想查看前面部分讨论的相关官方公告,可以查看以下这些帖子:

下面是关于各种容器操作系统的简要概述。最近出现了几种其他针对容器优化的操作系统:

  • Red Hat Enterprise Linux Atomic Host 专注于安全性,默认启用 SELinux,并采用与 CoreOS 相似的原子式操作系统更新。请参阅以下链接:access.redhat.com/articles/rhel-atomic-getting-started

  • Ubuntu Snappy 同样利用了将操作系统组件与框架和应用程序分离所带来的效率和安全性提升。通过使用应用程序镜像和验证签名,我们获得了一个高效的基于 Ubuntu 的操作系统,用于容器工作负载,www.ubuntu.com/cloud/tools/snappy

  • Ubuntu LXD 运行容器虚拟机管理程序,并提供了一种将基于 Linux 的虚拟机轻松迁移到容器的路径:www.ubuntu.com/cloud/lxd

  • VMware Photon 是另一个轻量级容器操作系统,专门为 vSphere 和 VMware 平台进行了优化。它支持 Docker、rkt 和 Garden,并且还提供了一些镜像,您可以在流行的公共云提供商上运行。请参阅以下链接:vmware.github.io/photon/

利用容器的隔离特性,我们提高了可靠性,并减少了每个应用程序更新的复杂性。现在,应用程序可以与支持库一起更新,每当新的容器版本发布时,正如下图所示:

CoreOS 更新过程

最后,CoreOS 在安全性方面有一些额外的优势。首先,操作系统可以作为一个整体进行更新,而不是通过单独的软件包(参见前面的图示)。这样可以避免许多部分更新带来的问题。为了实现这一点,CoreOS 使用了两个分区:一个作为活动的操作系统分区,另一个则用于接收完整的更新。更新成功完成后,重新启动时会提升第二个分区。如果出现问题,原始分区将作为备用。

系统所有者还可以控制这些更新的应用时间。这为我们提供了灵活性,可以优先处理关键更新,同时根据实际的调度安排处理更常见的更新。此外,整个更新过程都是签名并通过 SSL 进行传输,以增强安全性。

rkt

如前所述,rkt 将继续作为一个社区驱动的项目进行开发。rkt 是另一个专注于安全性的实现。rkt 的主要优势是它以不需要守护进程的方式运行引擎,而不像今天的 Docker 那样以 root 用户身份运行守护进程。最初,rkt 在容器镜像的信任建立方面也具有优势。然而,Docker 最近的更新取得了很大进展,特别是新的内容信任功能。

最终结论是,rkt 仍然是一种实现方案,专注于在生产环境中安全地运行容器。rkt 使用一种名为 ACI 的镜像格式,但也支持基于 Docker 的镜像。在过去的一年里,rkt 进行了重大更新,现在已经是版本 1.24.0。它作为一种在生产环境中安全运行 Docker 镜像的方式,获得了很大的发展势头。

这是一个展示 rkt 执行链如何工作的示意图:

此外,CoreOS 正在与 Intel®合作,集成新的 Intel®虚拟化技术,这使得容器能够在更高层次的隔离中运行。此硬件增强的安全性允许容器在基于内核的虚拟机KVM)进程中运行,以类似我们今天在虚拟化监控程序中看到的方式提供与内核的隔离。

etcd

CoreOS 生态系统中另一个值得一提的核心部分是他们的开源 etcd 项目。etcd 是一个分布式且一致的键值存储。它使用 RESTful API 与 etcd 进行接口,因此很容易与你的项目进行集成。

如果这个听起来很熟悉,那是因为我们在第一章《Kubernetes 简介》的运行在主节点上的服务这一节中看到了这个过程。Kubernetes 实际上利用 etcd 来跟踪集群的配置和当前状态。K8s 还利用它的服务发现功能。更多详细信息,请参阅github.com/coreos/etcd

带有 CoreOS 的 Kubernetes

现在我们了解了这些好处,让我们来看看使用 CoreOS 的 Kubernetes 集群。文档支持多个平台,但最容易启动的是使用 CoreOS CloudFormation 和 CLI 脚本的 AWS 平台。

如果你有兴趣在其他平台上运行带有 CoreOS 的 Kubernetes,可以在 CoreOS 文档中找到更多细节,网址是coreos.com/kubernetes/docs/latest/你可以在此找到最新的 AWS 操作指南 coreos.com/kubernetes/docs/latest/kubernetes-on-aws.html

你可以按照本章之前介绍的步骤,在 CoreOS 上启动 Kubernetes。你需要在 AWS 上创建一个密钥对,并指定一个区域、集群名称、集群大小和 DNS 来继续操作。

此外,我们还需要创建一个 DNS 条目,并且需要像 Route 53 这样的服务或生产 DNS 服务。在按照说明操作时,你需要将 DNS 设置为一个你有权限设置记录的域名或子域名。集群启动并运行后,我们需要更新记录,并定义一个动态端点。

就是这样!现在我们已经有了一个运行 CoreOS 的集群。这个脚本创建了所有必要的 AWS 资源,例如 虚拟私有云VPCs)、安全组和 IAM 角色。集群已启动并运行,我们可以使用 status 命令获取端点,并按如下方式更新 DNS 记录:

$ kube-aws status

复制前一个命令输出中列出的 Controller DNS Name 旁边的条目,然后编辑你的 DNS 记录,使你之前指定的域名或子域名指向这个负载均衡器。

如果你忘记了指定的域名或需要检查配置,你可以使用你喜欢的编辑器查看生成的 kubeconfig 文件。它的内容大概是这样的:

apiVersion: v1
kind: Config
clusters:
- cluster:
    certificate-authority: credentials/ca.pem
    server: https://coreos.mydomain.com
  name: kube-aws-my-coreos-cluster-cluster
contexts:
- context:
    cluster: kube-aws-my-coreos-cluster-cluster
    namespace: default
    user: kube-aws-my-coreos-cluster-admin
  name: kube-aws-my-coreos-cluster-context
users:
- name: kube-aws-my-coreos-cluster-admin
  user:
    client-certificate: credentials/admin.pem
    client-key: credentials/admin-key.pem
current-context: kube-aws-my-coreos-cluster-context

在这种情况下,server 行将包含你的域名。

如果这是一个全新的机器,你需要单独下载 kubectl,因为它没有和 kube-aws 一起打包:

**$ wget https://storage.googleapis.com/kubernetes-release/release/v1.0.6/bin/linux/amd64/kubectl**

现在我们可以使用 kubectl 查看我们的新集群:

$ ./kubectl --kubeconfig=kubeconfig get nodes

我们应该看到一个节点列出,名称是 EC2 内部 DNS。注意 kubeconfig,它告诉 Kubernetes 使用配置文件的路径,以便使用刚创建的集群。这在我们想要从同一台机器管理多个集群时也非常有用。

Tectonic

在 CoreOS 上运行 Kubernetes 是一个很好的开始,但你可能会发现你需要更高层次的支持。这时就需要 Tectonic,CoreOS 为在 CoreOS 上运行 Kubernetes 提供的企业级解决方案。Tectonic 使用了我们之前讨论过的许多组件。它支持 Docker 和 rkt 运行时。此外,Kubernetes、etcd 和 flannel 被打包在一起,提供完整的集群编排栈。我们在 第三章,与网络、负载均衡器和 Ingress 配合工作 中简要讨论了 flannel。它是一个覆盖网络,使用类似于原生 Kubernetes 模型的模型,并使用 etcd 作为后端。

CoreOS 提供的支持包类似于 Red Hat,此外,它还为 Tectonic 所构建的开源软件提供 24/7 支持。Tectonic 还提供定期的集群更新,并且配有一个漂亮的控制面板,展示 Kubernetes 所有组件的视图。CoreUpdate 使用户可以更好地控制自动更新过程。此外,它还提供了用于监控、SSO 和其他安全功能的模块。

随着 CoreOS 被集成到 Red Hat 中,这项服务将逐步被 Red Hat 的方案取代。

你可以在 coreos.com/tectonic/docs/latest/install/aws/index.html 找到更多信息以及最新的安装指南。

控制面板亮点

以下是 Tectonic 控制面板的一些亮点截图:

Tectonic 主控制面板

Tectonic 现在已经全面推出,仪表盘也已经具备了一些不错的功能。正如下面的截图所示,我们可以看到关于复制控制器的许多详细信息,甚至可以通过 GUI 点击按钮轻松实现扩展与收缩:

Tectonic 复制控制器详细信息

这张图很大,因此被分成了两页。以下截图是接续前一张截图的内容:

另一个不错的功能是事件页面。在这里,我们可以实时查看事件,暂停它们,并根据事件的严重性和资源类型进行过滤:

事件流

在仪表盘系统中随时浏览的一个有用功能是命名空间:过滤选项。只需点击任何资源页面顶部的命名空间旁的下拉菜单,就可以按命名空间过滤视图。如果我们想要排除 Kubernetes 系统的 pod,或者只查看某个特定资源集合,这会很有帮助:

命名空间过滤

托管平台

云中有几种托管 Kubernetes 选项可供选择。这些平台即服务PaaS)能够提供稳定的操作模式,帮助你向生产环境推进。以下是亚马逊、微软和谷歌提供的主要 PaaS 概览。

亚马逊 Web 服务

弹性容器服务ECS)在本章写作时刚刚推出。AWS 正在准备一个网络插件,用以区分自己与其他提供商,名为 vpc-cni。它允许 Kubernetes 中的 pod 网络使用 AWS 上的 弹性网络接口ENIs)。使用 ECS 时,您需要为管理节点付费,这与微软和谷歌的做法不同。ECS 的启动过程目前也更复杂,且无法通过 CLI 使用单一命令创建。

微软 Azure

Azure 容器服务是继 Google Kubernetes Engine 之后,云中运行时间第二长的托管 Kubernetes 服务。你可以使用 Azure 模板和资源管理器通过 Terraform 快速部署集群。微软提供了先进的网络功能、与 Azure Active Directory 的集成以及监控作为其突出特点。

Google Kubernetes Engine

Google Kubernetes Engine 是运行容器化工作负载的另一个优秀选择。在撰写时,它被认为是最强大的选择之一。GKE 能够自动调整集群大小,而 AWS 和 Azure 则提供手动扩展。GKE 提供一键启动,是部署 Kubernetes 集群最快的方式。它还提供Alpha 模式,你可以在 Alpha 频道发布中尝试前沿功能。GKE 提供区域和可用区的高可用性,后者将主节点区域分布开来,提供业内领先的高可用性。

总结

在本章中,我们研究了容器社区中新兴的标准化机构,以及它们如何使用开放规格来推动技术的进步。我们审视了各种容器框架和运行时。我们初步了解了 CNCF,并尝试了 CRI-O。

我们还深入了解了 CoreOS,这是容器和 Kubernetes 社区中的一个关键玩家。我们探讨了 CoreOS 正在开发的技术,旨在增强和补充容器编排,并亲自体验了如何在 Kubernetes 中使用其中一些技术。最后,我们查看了 Tectonic 的企业级支持服务以及目前可用的一些功能。

我们还研究了云服务提供商提供的一些主要 PaaS。

在下一章中,我们将探索更广泛的 Kubernetes 生态系统以及将集群从开发和测试环境迁移到生产环境的工具。

进一步阅读

第十章:高可用性和可扩展性设计

本章将涉及高级概念,如高可用性、可扩展性,以及 Kubernetes 操作员需要满足的要求,以便开始探索在生产环境中运行 Kubernetes 的主题。我们将查看 Google 和 Azure 提供的平台即服务PaaS)服务,并使用在云环境中运行生产工作负载的常用原则。

本章将涵盖以下主题:

  • 高可用性简介

  • 高可用性最佳实践

  • 多区域设置

  • 安全最佳实践

  • 在托管的 Kubernetes PaaS 上设置高可用性

  • 集群生命周期事件

  • 如何使用 admission 控制器

  • 参与工作负载 API

  • 什么是自定义资源定义CRD)?

技术要求

您需要访问您的 Google Cloud Platform 账户,以便探索这些选项中的一些。您也可以使用本地的 Minikube 设置来测试其中的一些功能,但我们在这里讨论的许多原则和方法需要云中的服务器。

高可用性简介

为了理解本章的目标,我们首先需要讨论高可用性和可扩展性的更一般性术语。让我们分别了解它们,以便理解各个部分如何协同工作。

我们将讨论所需的术语,并开始理解我们在云中构建、构思和运行 Kubernetes 集群时所使用的基本组成部分。

让我们深入了解高可用性、正常运行时间和停机时间。

我们如何衡量可用性?

高可用性HA)的理念是您的应用程序是可用的,意味着终端用户能够访问它。为了创建高度可用的应用程序,您的应用程序代码和用户交互的前端需要大部分时间都保持可用。这个术语来源于系统设计领域,它定义了系统的架构、接口、数据和模块,以满足给定的一组需求。系统设计的例子在从产品开发到分布式系统理论的多个学科中都有出现。在高可用性(HA)中,系统设计帮助我们理解实现可靠和高性能系统所需的逻辑和物理设计要求。

在业界,我们将高可用性称为五个九的可用性。这个99.999的可用性意味着每天、每周、每月和每年都有特定的停机时间。

如果您想了解更多关于五个九可用性方程背后的数学内容,您可以在这里阅读关于下取整和上取整函数的文章:en.wikipedia.org/wiki/Floor_and_ceiling_functions

我们还可以看看一般的可用性公式,您可以使用它来理解给定系统的可用性:

Downtime per year in hours = (1 - Uptime Availability) x 365 x 24

正常运行时间和停机时间

在我们查看日、周和年期间的净可用性之前,先深入了解什么是“上线”或“下线”。我们还需要定义一些关键术语,以便理解可用性对我们业务的意义。

Uptime

Uptime 是衡量给定系统、应用程序、网络或其他逻辑和物理对象的运行时间,指的是该对象已启用并可供相应终端用户使用的时间。这可以是内部系统、外部项目,或者仅通过其他计算机系统交互的项目。

Downtime

Downtime 与 Uptime 类似,但衡量的是给定系统、应用程序、网络或其他逻辑和物理对象无法为终端用户提供服务的时间。Downtime 的定义有一定的解释空间,因为它是指系统未按原计划执行其主要功能的时间。最常见的 Downtime 示例是臭名昭著的 404 页面,你可能之前见过:

为了理解前面提到的概念下系统的可用性,我们可以使用已知的 Uptime 和 Downtime 数据来计算:

Availability Percentage = (Uptime / (Uptime + Downtime) x 100

对于具有冗余组件的系统(这些冗余组件提高了系统的整体稳定性),有更复杂的计算方法,但现在我们暂时以具体示例为主。我们将在本章稍后探讨 Kubernetes 的冗余组件。

根据这些公式,你可以自己测量 Kubernetes 集群的 Uptime,接下来让我们看几个例子。

让我们看一看这些概念背后的数学。首先,Uptime 可用性是 平均故障间隔时间 (MTBF) 除以 平均修复时间 (MTTR) 和 MTBF 之和的函数。

我们可以通过以下方式计算 MTBF:

MTBF = ‘Total hours in a year' / ‘Number of yearly failures'

MTTR 表示如下:

MTTR = (‘Amount of failure' x ‘Time to repair the system') / ‘Total number of failures'

这可以通过以下公式表示:

Uptime Availability = MTBF/(MTTR + MTBF)
Downtime per Year (Hours) = (1 – Uptime Ratio) x 365 x 24

五个 9 的可用性

我们可以更深入地探讨行业标准的五个 9 的可用性与更少的 9 的比较。我们可以使用 服务水平协议 (SLA) 来理解终端用户和 Kubernetes 操作员之间的合同,保证底层硬件和 Kubernetes 软件对应用所有者的可用性。

SLA 是一个保证的可用性级别。值得注意的是,随着可用性增加,成本会变得非常高。

这里是一些 SLA 等级:

  • 对于 99.9% 可用性的 SLA,你的 Downtime 可以是:

    • 每天:1 分钟,26.4 秒

    • 每周:10 分钟,4.8 秒

    • 每月:43 分钟,49.7 秒

    • 每年:8 小时 45 分钟,57.0 秒

  • 对于 99.99% 可用性的 SLA,你的 Downtime 可以是:

    • 每天:8.6 秒

    • 每周:1 分钟,0.5 秒

    • 每月:4 分钟,23.0 秒

    • 每年:52 分钟,35.7 秒

  • 对于 99.999% 可用性的 SLA,你的 Downtime 可以是:

    • 每天:0.9 秒

    • 每周:6.0 秒

    • 每月:26.3 秒

    • 每年: 5 分钟, 15.6 秒

正如您所见,具有五个九的可用性,您的 Kubernetes 群集没有太多喘息的空间。还需要注意的是,您的群集的可用性与应用程序的可用性有关。

那是什么意思呢?嗯,应用程序本身也会出现问题和代码错误,这些问题和错误超出了 Kubernetes 群集的领域和控制范围。因此,特定应用程序的正常运行时间和可用性将等于(甚至很少等于,考虑到人为错误)或少于您群集的一般可用性。

因此,让我们来分析 Kubernetes 中 HA 的各个部分。

HA 最佳实践

要构建高可用的 Kubernetes 系统,需要注意可用性常常是人员和流程的功能,也是技术故障的功能。尽管硬件和软件经常出现故障,但人员及其参与过程对所有系统的可用性是一个非常可预测的拖累。

值得注意的是,本书不涉及如何为微服务架构设计失败处理机制,而这在处理群集调度和网络系统(如 Kubernetes)中的一些(或全部)系统故障中占据了很大一部分。

还有另一个重要的概念需要考虑:优雅降级。

优雅降级是指你将功能分层和模块化,因此即使系统的某些部分发生灾难性故障,仍然能够提供一定级别的可用性。在网页设计中,也有相应的逐步增强术语,但我们在这里不会使用这种模式。优雅降级是系统具有容错性的结果,对于关键任务和面向客户的系统非常理想。

在 Kubernetes 中,有两种优雅降级的方法:

  • 基础设施降级:这种降级依赖于复杂的算法和软件,以处理硬件的不可预测故障,或软件定义的硬件(如虚拟机、软件定义网络SDN)等)。我们将探讨如何使 Kubernetes 的关键组件高度可用,以便在这种形式下提供优雅降级。

  • 应用降级:虽然这在很大程度上取决于前述的微服务最佳实践架构策略,但我们将在这里探讨几种模式,这些模式将使您的用户成功。

在这些场景中,我们的目标是尽可能为最终用户提供完整的功能,但如果出现应用程序、Kubernetes 组件或底层基础设施的故障,目标应当是为用户提供某种程度的访问和可用性。我们将努力通过核心 Kubernetes 策略完全抽象掉底层基础设施的故障,同时我们将构建缓存、故障转移和回滚机制,以应对应用程序故障。最后,我们将以高可用的方式构建 Kubernetes 组件。

抗脆弱性

在深入探讨这些内容之前,退一步考虑塔勒布在《Antifragility》一书中讨论的抗脆弱性这一更大的概念是很有意义的。

若要了解更多关于塔勒布的书籍,查看他的书籍主页:www.penguinrandomhouse.com/books/176227/antifragile-by-nassim-nicholas-taleb/9780812979688/

在应对 Kubernetes 系统的复杂性以及如何利用更广泛的 Kubernetes 生态系统生存和发展的过程中,有一些关键概念非常重要,需要加以强化。

首先,冗余是关键。为了应对系统多个层级的故障,重要的是在系统中构建冗余和容错部分。这些冗余层可以利用诸如 Raft 共识等算法,旨在为多个对象在容错分布式系统中提供一个控制平面,以便它们达成一致。此类冗余依赖于 N+1 冗余,以应对物理或逻辑对象的丢失。

我们稍后会查看 etcd,进一步探讨冗余问题。

第二,触发、应对、探索和修复故障场景是关键。你需要强制使 Kubernetes 系统发生故障,以理解它在极限情况下或边缘情况中的表现。Netflix 的 Chaos Monkey 是一种标准且被广泛使用的测试复杂系统可靠性的方法。

你可以在这里阅读更多关于 Netflix 的 Chaos Monkey 的内容:github.com/Netflix/chaosmonkey

第三,我们需要确保我们的系统中有正确的模式,并且我们要实施正确的模式,以便在 Kubernetes 中构建抗脆弱性。重试逻辑、负载均衡、断路器、超时、健康检查和并发连接检查是这个抗脆弱性维度的关键项。Istio 和其他服务网格是这一主题中的先进技术。

你可以在这里阅读更多关于 Istio 以及如何管理流量的内容:istio.io/docs/concepts/traffic-management/

高可用集群(HA 集群)

为了创建能够应对反脆弱模式并提高集群正常运行时间的 Kubernetes 集群,我们可以使用系统的核心组件创建高可用性集群。让我们先了解一下在主要云服务提供商中,使用他们的服务创建 Kubernetes 集群时可以获得什么。

主要云服务提供商的高可用性功能

为了实现你基础设施的五个 9 的正常运行时间,哪些 Kubernetes 组件需要具有高可用性?首先,你应该考虑云服务提供商CSP)在后台为你做了多少事情。

对于Google Kubernetes EngineGKE),几乎所有组件都是开箱即用的管理。你不需要担心管理节点或与之相关的任何费用。GKE 目前还拥有最强大的自动扩展功能。Azure Kubernetes ServiceAKS)和亚马逊Elastic Kubernetes ServiceEKS)都使用自管理的自动扩展功能,这意味着你需要通过使用自动扩展组来管理集群的扩展。

GKE 还能够处理管理节点的自动更新,无需用户干预,但还提供与 AKS 一起的即插即用自动更新功能,以便操作员可以选择何时进行无缝升级。EKS 仍在处理这些细节。

EKS 在多个可用区AZ)之间提供高可用性的主节点/工作节点,而 GKE 在其区域模式中提供类似的功能,类似于 AWS 的区域。AKS 目前不为主节点提供高可用性,但集群中的工作节点分布在多个 AZ 中,以提供高可用性。

Kubernetes 的高可用性(HA)方法

如果你打算在托管的 PaaS 之外运行 Kubernetes,你需要采用两种策略之一来运行高可用性 Kubernetes 集群。在本章中,我们将介绍一个带有堆叠主节点的示例,并描述更复杂的外部 etcd 集群方法。

在这种方法中,你将结合 etcd 和管理(控制平面)节点,以减少运行集群所需的基础设施量。这意味着你至少需要三台机器才能实现高可用性。如果你在云中运行,这也意味着你需要将实例分布在三个可用区中,以利用将机器分布在多个区提供的正常运行时间。

堆叠主节点将在你的架构图中如下所示:

第二种选项是在基础设施复杂度增加的情况下提高潜在可用性的方式。您可以使用外部 etcd 集群,从而为控制平面和 etcd 成员创建分离,进一步提高潜在的可用性。此种方式的设置需要至少六台服务器,且这些服务器应分布在多个可用区,就像第一个例子中那样:

为了实现这两种方法之一,您需要一些前提条件。

前提条件

如前面所述,您将需要三台机器作为主节点,三台机器作为工作节点,并且如果选择外部 etcd 集群,还需要另外三台机器。

以下是机器的最低要求——您应使用以下操作系统之一:

  • Ubuntu 16.04+

  • Debian 9

  • CentOS 7

  • RHEL 7

  • Fedora 25/26(最佳努力)

  • Container Linux(已测试版本:1576.4.0)

在每台机器上,您需要至少 2 GB 的 RAM、两个或更多的 CPU,并且所有集群中的机器之间需要完全的网络连接(公共或私有网络均可)。每个节点还需要一个唯一的主机名、MAC 地址和product_uuid

如果您运行在任何类型的管理网络中(数据中心、云或其他),您还需要确保您的机器上开放了所需的安全组和端口。最后,您需要禁用交换分区以使kubelet正常工作。

要查看所需开放的端口列表,请访问kubernetes.io/docs/setup/independent/install-kubeadm/#check-required-ports

在某些云服务提供商中,虚拟机可能会共享相同的product_uuids,尽管它们共享相同 MAC 地址的可能性较小。检查这些信息很重要,因为 Kubernetes 网络和 Calico 将使用它们作为唯一标识符,如果它们相同,就会出现错误。您可以使用以下命令检查这两项:

LANG=C ifconfig -a | grep -Po 'HWaddr \K.*$'

上述命令将为您获取 MAC 地址,下面的命令将告诉您uuid

sudo cat /sys/class/dmi/id/product_uuid

设置

现在,让我们开始设置这些机器。

您需要在控制平面节点上以 root 用户身份运行这里的所有命令。

首先,您需要设置 SSH。Calico 将设置您的网络,因此我们将使用您机器的 IP 地址来开始这一过程。请记住,Kubernetes 网络有三个基本层:

  • 运行在您节点上的容器和 Pod,这些节点可以是虚拟机或硬件服务器。

  • 服务,它们是一个聚合和抽象层,允许您使用各种 Kubernetes 控制器来设置应用程序,并确保 Pod 根据可用性需求进行调度。

  • Ingress,它允许来自集群外部的流量并将其路由到正确的容器。

因此,我们需要设置 Calico 来处理这些不同的层。您需要获取节点的 CIDR 地址,建议在此示例中安装 Calico。

您可以在 CNI 网络文档中找到更多信息:kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#pod-network

您需要确保配置机器上的 SSH 代理能够访问集群中的所有其他节点。启动代理,然后将我们的身份添加到会话中:

eval $(ssh-agent)
ssh-add ~/.ssh/id_rsa

您可以通过使用-A标志来测试以确保它正常工作,该标志会在 SSH 隧道中保留您的身份。连接到另一个节点后,您可以使用-E标志来保留环境变量:

sudo -E -s

接下来,我们需要将来自云环境的负载均衡器放置在kube-apiserver前面。这将确保集群的 API 服务器在某一台机器宕机或无响应时仍然可以访问。对于此示例,您应该使用支持 TCP 的负载均衡器,如 Elastic Load Balancer(AWS)、Azure Load Balancer(Azure)或 TCP/UDP Load Balancer(GCE)。

确保您的负载均衡器可以通过 DNS 解析,并且您设置了一个健康检查,监听kube-apiserver端口6443。一旦负载均衡器就位,您可以使用nc -v LB_DNS_NAME PORT测试与 API 服务器的连接。设置好云负载均衡器后,确保将所有控制平面节点添加到负载均衡器中。

堆叠节点

为了运行一组堆叠节点,您需要使用kubeadm-conf-01.yaml模板引导第一个控制平面节点。再次说明,这个示例使用 Calico,但您可以根据需要配置网络。为了使示例正常工作,您需要替换以下值:

  • LB_DNS

  • LB_PORT

  • CONTROL01_IP

  • CONTROL01_HOSTNAME

打开一个新文件,kubeadm-conf-01.yaml,使用您喜欢的 IDE:

apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.0
apiServerCertSANs:
- "LB_DNS"
api:
   controlPlaneEndpoint: "LB_DNS:LB_PORT"
etcd:
 local:
   extraArgs:
     listen-client-urls: "https://127.0.0.1:2379,https://CONTROL01_IP:2379"
     advertise-client-urls: "https://CONTROL01_IP:2379"
     listen-peer-urls: "https://CONTROL01_IP:2380"
     initial-advertise-peer-urls: "https://CONTROL01_IP:2380"
     initial-cluster: "CONTROL01_HOSTNAME=https://CONTROL01_IP:2380"
   serverCertSANs:
     - CONTROL01_HOSTNAME
     - CONTROL01_IP
   peerCertSANs:
     - CONTROL01_HOSTNAME
     - CONTROL01_IP
networking:
   podSubnet: "192.168.0.0/16"

在获取此文件后,使用以下命令执行它:

kubeadm init --config kubeadm-conf-01.yaml

命令完成后,您需要将以下证书和文件列表复制到其他控制平面节点:

/etc/kubernetes/pki/ca.crt
/etc/kubernetes/pki/ca.key
/etc/kubernetes/pki/sa.key
/etc/kubernetes/pki/sa.pub
/etc/kubernetes/pki/front-proxy-ca.crt
/etc/kubernetes/pki/front-proxy-ca.key
/etc/kubernetes/pki/etcd/ca.crt
/etc/kubernetes/pki/etcd/ca.key
/etc/kubernetes/admin.conf

为了继续进行,我们需要在第二个节点上添加另一个模板文件,以创建kubeadm-conf-02.yaml下的第二个堆叠节点。像之前一样,您需要将以下值替换为您自己的:

  • LB_DNS

  • LB_PORT

  • CONTROL02_IP

  • CONTROL02_HOSTNAME

打开一个新文件,kubeadm-conf-02.yaml,使用您喜欢的 IDE:

apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.0
apiServerCertSANs:
- "LOAD_BALANCER_DNS"
api:
   controlPlaneEndpoint: "LB_DNS:LB_PORT"
etcd:
 local:
   extraArgs:
     listen-client-urls: "https://127.0.0.1:2379,https://CONTROL02_IP:2379"
     advertise-client-urls: "https://CONTROL02_IP:2379"
     listen-peer-urls: "https://CONTROL02_IP:2380"
     initial-advertise-peer-urls: "https://CONTROL01_IP:2380"
     initial-cluster: "CONTROL01_HOSTNAME=https://CONTROL01_IP:2380,CONTROL02_HOSTNAME=https://CONTROL02_IP:2380"
     initial-cluster-state: existing
   serverCertSANs:
     - CONTROL02_HOSTNAME
     - CONTROL02_IP
   peerCertSANs:
     - CONTROL02_HOSTNAME
     - CONTROL02_IP
networking:
   podSubnet: "192.168.0.0/16"

在运行此模板之前,您需要将复制的文件移动到正确的目录。以下是一个示例,您的系统应该类似:

 mkdir -p /etc/kubernetes/pki/etcd
 mv /home/${USER}/ca.crt /etc/kubernetes/pki/
 mv /home/${USER}/ca.key /etc/kubernetes/pki/
 mv /home/${USER}/sa.pub /etc/kubernetes/pki/
 mv /home/${USER}/sa.key /etc/kubernetes/pki/
 mv /home/${USER}/front-proxy-ca.crt /etc/kubernetes/pki/
 mv /home/${USER}/front-proxy-ca.key /etc/kubernetes/pki/
 mv /home/${USER}/etcd-ca.crt /etc/kubernetes/pki/etcd/ca.crt
 mv /home/${USER}/etcd-ca.key /etc/kubernetes/pki/etcd/ca.key
 mv /home/${USER}/admin.conf /etc/kubernetes/admin.conf

一旦您复制了这些文件,您可以运行一系列的kubeadm命令来吸收证书,然后引导第二个节点:

kubeadm alpha phase certs all --config kubeadm-conf-02.yaml
kubeadm alpha phase kubelet config write-to-disk --config kubeadm-conf-02.yaml
kubeadm alpha phase kubelet write-env-file --config kubeadm-conf-02.yaml
kubeadm alpha phase kubeconfig kubelet --config kubeadm-conf-02.yaml
systemctl start kubelet

一旦完成上述步骤,您还可以将节点添加到 etcd 中。您首先需要设置一些变量,以及正在运行节点的虚拟机的 IP 地址:

export CONTROL01_IP=<YOUR_IP_HERE>
export CONTROL01_HOSTNAME=cp01H
export CONTROL02_IP=<YOUR_IP_HERE>
export CONTROL02_HOSTNAME=cp02H

一旦设置了这些变量,请运行以下kubectlkubeadm命令。首先,添加证书:

export KUBECONFIG=/etc/kubernetes/admin.conf
kubectl exec -n kube-system etcd-${CONTROL01_HOSTNAME} -- etcdctl --ca-file /etc/kubernetes/pki/etcd/ca.crt --cert-file /etc/kubernetes/pki/etcd/peer.crt --key-file /etc/kubernetes/pki/etcd/peer.key --endpoints=https://${CONTROL01_IP}:2379 member add ${CONTROL02_HOSTNAME} https://${CP1_IP}:2380

接下来,配置etcd的阶段:

kubeadm alpha phase etcd local --config kubeadm-config-02.yaml

此命令将导致 etcd 集群在短时间内不可用,但这是有意设计的。然后,您可以部署kubeconfigcontrolplane中的其余组件,并将节点标记为主节点:

kubeadm alpha phase kubeconfig all --config kubeadm-conf-02.yaml
kubeadm alpha phase controlplane all --config kubeadm-conf-02.yaml
kubeadm alpha phase mark-master --config kubeadm-conf-02.yaml

我们将再次通过第三个节点运行此过程,在 etcd 的extraArgs下增加更多价值:

您需要在第三台机器上创建第三个kubeadm-conf-03.yaml文件。按照之前的模板进行操作,并替换变量:

apiVersion: kubeadm.k8s.io/v1alpha2
kind: MasterConfiguration
kubernetesVersion: v1.11.0
apiServerCertSANs:
- "LB_DNS"
api:
   controlPlaneEndpoint: "LB_DNS:LB_PORT"
etcd:
 local:
   extraArgs:
     listen-client-urls: "https://127.0.0.1:2379,https://CONTROL03_IP:2379"
     advertise-client-urls: "https://CONTROL03_IP:2379"
     listen-peer-urls: "https://CONTROL03_IP:2380"
     initial-advertise-peer-urls: "https://CONTROL03_IP:2380"
     initial-cluster: "CONTRL01_HOSTNAME=https://CONTROL01_IP:2380,CONTROL02_HOSTNAME=https://CONTROL02_IP:2380,CONTRL03_HOSTNAME=https://CONTROL03_IP:2380"
     initial-cluster-state: existing
   serverCertSANs:
     - CONTRL03_HOSTNAME
     - CONTROL03_IP
   peerCertSANs:
     - CONTRL03_HOSTNAME
     - CONTROL03_IP
networking:
   podSubnet: "192.168.0.0/16"

您需要再次移动文件:

 mkdir -p /etc/kubernetes/pki/etcd
 mv /home/${USER}/ca.crt /etc/kubernetes/pki/
 mv /home/${USER}/ca.key /etc/kubernetes/pki/
 mv /home/${USER}/sa.pub /etc/kubernetes/pki/
 mv /home/${USER}/sa.key /etc/kubernetes/pki/
 mv /home/${USER}/front-proxy-ca.crt /etc/kubernetes/pki/
 mv /home/${USER}/front-proxy-ca.key /etc/kubernetes/pki/
 mv /home/${USER}/etcd-ca.crt /etc/kubernetes/pki/etcd/ca.crt
 mv /home/${USER}/etcd-ca.key /etc/kubernetes/pki/etcd/ca.key
 mv /home/${USER}/admin.conf /etc/kubernetes/admin.conf

并且,您需要再次运行以下命令以引导它们:

kubeadm alpha phase certs all --config kubeadm-conf-03.yaml
kubeadm alpha phase kubelet config write-to-disk --config kubeadm-conf-03.yaml
kubeadm alpha phase kubelet write-env-file --config kubeadm-conf-03.yaml
kubeadm alpha phase kubeconfig kubelet --config kubeadm-conf-03.yaml
systemctl start kubelet

然后,再次将节点添加到 etcd 集群中:

export CONTROL01_IP=<YOUR_IP_HERE>
export CONTROL01_HOSTNAME=cp01H
export CONTROL03_IP=<YOUR_IP_HERE>
export CONTROL03_HOSTNAME=cp03H

接下来,我们可以设置 etcd 系统:

export KUBECONFIG=/etc/kubernetes/admin.conf

kubectl exec -n kube-system etcd-${CONTROL01_HOSTNAME} -- etcdctl --ca-file /etc/kubernetes/pki/etcd/ca.crt --cert-file /etc/kubernetes/pki/etcd/peer.crt --key-file /etc/kubernetes/pki/etcd/peer.key --endpoints=https://${CONTROL01_IP}:2379 member add ${CONTROL03_HOSTNAME} https://${CONTROL03_IP}:2380

kubeadm alpha phase etcd local --config kubeadm-conf-03.yaml

完成后,我们可以再次部署控制平面的其余组件,并将节点标记为主节点。执行以下命令:

kubeadm alpha phase kubeconfig all --config kubeadm-conf-03.yaml

kubeadm alpha phase controlplane all --config kubeadm-conf-03.yaml

kubeadm alpha phase mark-master --config kubeadm-conf-03.yaml

做得很好!

安装工作节点

一旦配置了主节点,您可以将工作节点加入集群。在添加工作节点之前,您需要配置 Pod 网络。您可以在此处找到有关 Pod 网络附加组件的更多信息:kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#pod-network

集群生命周期

还有一些关键事项,我们应该讨论,以便您全面了解可以帮助您创建高可用性 Kubernetes 集群的项目。让我们讨论如何使用准入控制器、工作负载和自定义资源定义来扩展您的集群。

准入控制器

准入控制器是 Kubernetes 代码,允许您在对 Kubernetes API 服务器进行身份验证和授权后拦截调用。核心 Kubernetes 系统中包含标准准入控制器,人们也会编写自己的控制器。有两个控制器比其他控制器更重要:

  • MutatingAdmissionWebhook 负责按顺序调用 Webhooks 来变更给定请求。这个控制器仅在集群操作的变更阶段运行。你可以使用这样的控制器将业务逻辑构建到集群中,通过像 CREATEDELETEUPDATE 这样的操作来自定义准入逻辑。你还可以做一些事情,比如通过 StorageClass 自动化存储的供应。例如,如果一个部署创建了一个 PersistentVolumeClaim,那么 webhook 可以自动响应并供应 StorageClass。通过 MutatingAdmissionWebhook,你还可以在容器构建之前,注入一个 sidecar 到容器中。

  • ValidatingAdmissionWebhook 是准入控制器在验证阶段运行的内容,负责调用任何会验证给定请求的 webhook。在这里,webhooks 是并行调用的,这与 MutatingAdmissionWebhook 的串行特性不同。需要特别理解的是,它调用的任何 webhook 都不允许更改原始对象。一个验证 webhook 的例子是增加配额。

准入控制器及其变更和验证 webhook 非常强大,并且重要的是,它们为 Kubernetes 操作员提供了额外的控制功能,而无需重新编译诸如 kube-apiserver 之类的二进制文件。最强大的例子是 Istio,它使用 webhook 注入其 Envoy sidecar 来实现负载均衡、断路器和部署功能。你还可以使用 webhook 来限制在多租户系统中创建的命名空间。

你可以将变更看作是 Kubernetes 系统中的一个修改,而验证则是一个检查。随着相关软件生态系统的发展,从安全性和验证的角度来看,使用这些类型的功能变得越来越重要。你可以使用控制器及其变更和检查功能,做一些事情,比如覆盖镜像拉取策略,以便在集群中启用或阻止使用某些镜像。

这些准入控制器本质上是集群控制平面的一部分,只能由集群管理员运行。

这是一个非常简单的例子,我们将在准入控制器中检查命名空间是否存在。

NamespaceExists: 这个准入控制器检查除了 Namespace 本身以外的所有命名空间资源请求。如果请求中引用的命名空间不存在,该请求将被拒绝。你可以在 kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#namespaceexists 阅读更多相关内容。

首先,让我们为我们的集群获取 Minikube,并检查哪些命名空间存在:

master $ kubectl get namespaces
 NAME STATUS AGE
 default Active 23m
 kube-public Active 23m
 kube-system Active 23m

很棒!现在,让我们尝试创建一个简单的部署,将其放入一个不存在的命名空间。你认为会发生什么?

master $ kubectl run nodejs --image nodej2 --namespace not-here
 Error from server (NotFound): namespaces "not-here" not found

那么,为什么会发生这种情况呢?如果您猜测我们的 ValidatingAdmissionWebhook 捕捉到该请求并阻止了它,那么您猜对了!

使用准入控制器

您可以通过两条不同的命令在服务器中打开和关闭准入控制器。根据您的服务器配置以及您启动 kube-apiserver 的方式,您可能需要对 systemd 或用于启动 API 服务器的清单文件进行更改。

通常,为了启用服务器,您需要执行以下命令:

kube-apiserver --enable-admission-plugins

如果要禁用它,您需要将其更改为以下内容:

kube-apiserver --disable-admission-plugins=

如果您运行的是 Kubernetes 1.10 或更高版本,则有一组推荐的准入控制器供您使用。您可以使用以下命令启用它们:

kube-apiserver --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota

在早期版本的 Kubernetes 中,没有区分变更和验证的概念,因此您需要阅读文档,以理解在早期版本的软件中使用准入控制器的影响。

工作负载 API

工作负载 API 是理解如何在 Kubernetes 中管理对象的一个重要概念,它帮助我们理解这些管理在多个版本发布过程中是如何逐渐稳定的。在 Kubernetes 的早期,Pod 和它们的工作负载与共享 CPU、网络、存储和生命周期事件的容器紧密耦合。Kubernetes 引入了诸如副本、部署和标签等概念,并帮助管理 12-factor 应用程序。随着 Kubernetes 运维人员进入有状态工作负载,StatefulSets 被引入。

随着时间的推移,Kubernetes 工作负载的概念逐渐演变为多个部分的集合:

  • Pods

  • ReplicationController

  • ReplicaSet

  • Deployment

  • DaemonSet

  • StatefulSet

这些部分是当前在 Kubernetes 中编排合理工作负载类型的技术前沿,但不幸的是,API 被分散在 Kubernetes 代码库的多个不同部分。为了解决这个问题,开发者花费了数月的艰苦工作,将所有这些代码集中到 apps/v1 API 中,并且在此过程中进行了多次破坏性兼容性更改。在迁移到 apps/v1 时,做出了一些关键决策:

  • 默认选择器行为:未指定的标签选择器会默认使用从模板标签中自动生成的选择器

  • 不可变选择器:虽然在某些情况下更改选择器对部署有用,但更改选择器一直不符合 Kubernetes 的推荐做法,因此做出了更改,允许启用金丝雀类型的部署和 Pod 重标记,而这一过程是由 Kubernetes 协调的

  • 默认滚动更新:Kubernetes 开发者希望 RollingUpdate 是默认的更新形式,现在它已经成为默认选项

  • 垃圾回收:在 1.9 和 apps/v1 中,垃圾回收变得更加积极,您不会再看到 DaemonSets、ReplicaSets、StatefulSets 或 Deployments 被删除后还存在挂起的 Pod

如果你希望获得更多关于这些决策的输入,你可以加入 应用程序特别兴趣小组,该小组可以在这里找到:github.com/kubernetes/community/tree/master/sig-apps

目前,你可以认为工作负载 API 是稳定的,并且向后兼容。

自定义资源定义

在我们的高可用性章节中,我们将触及的最后一个部分是自定义资源。这些是 Kubernetes API 的扩展,并且与我们之前讨论的准入控制器相辅相成。向集群添加自定义资源有几种方法,我们将在这里讨论这些方法。

作为复习,请记住 Kubernetes 中的非自定义资源是 Kubernetes API 中的一个端点,它存储了一组相似的 API 对象。你可以使用自定义资源来增强特定的 Kubernetes 安装。我们将在后面的章节中看到 Istio 的示例,它使用 CRD 来配置前置条件。自定义资源可以通过 kubectl 进行修改、更改和删除。

当你将自定义资源与控制器配对时,你就能创建一个声明式 API,这使你能够设置在集群生命周期之外的资源状态。我们在本书前面通过操作员模式提到过自定义控制器和自定义资源的模式示例。决定是否创建 Kubernetes 自定义资源时,你有几个选项。文档建议使用以下决策表来做选择:

图片来源:kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#should-i-add-a-custom-resource-to-my-kubernetes-cluster

决定编写自定义资源的一个关键点是确保你的 API 是声明式的。如果是声明式的,那么它非常适合用作自定义资源。你可以通过两种方式编写自定义资源:通过自定义资源定义(CRD)或者通过 API 聚合。API 聚合需要编程,我们在本章不会深入讨论这个话题,但你可以在这里阅读更多信息:kubernetes.io/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/

使用 CRD(自定义资源定义)

虽然聚合 API 更加灵活,但 CRD 更易于使用。让我们尝试从 Kubernetes 文档中创建示例 CRD。

首先,你需要启动 Minikube 集群和 GCP 上的 GKE 集群,这将是你自己的集群或类似 Katacoda 的游乐场。让我们进入 Google Cloud Shell 并尝试一下。

进入你的 GCP 首页后,点击 CLI 图标,在下图中该图标被红色框住:

一旦进入 Shell,创建一个快速的 Kubernetes 集群。如果旧版本不受支持,可能需要修改集群版本:

gcloud container clusters create gswk8s \
  --cluster-version 1.10.6-gke.2 \
  --zone us-east1-b \
  --num-nodes 1 \
  --machine-type n1-standard-1
<lots of text>
...
Creating cluster gsk8s...done.
Created [https://container.googleapis.com/v1/projects/gsw-k8s-3/zones/us-east1-b/clusters/gsk8s].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-east1-b/gsk8s?project=gsw-k8s-3
kubeconfig entry generated for gsk8s.
NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS
gsk8s us-east1-b 1.10.6-gke.2 35.196.63.146 n1-standard-1 1.10.6-gke.2 1 RUNNING

接下来,向resourcedefinition.yaml中添加以下文本:

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  # name must match the spec fields below, and be in the form: <plural>.<group>
  name: crontabs.stable.example.com
spec:
  # group name to use for REST API: /apis/<group>/<version>
  group: stable.example.com
  # list of versions supported by this CustomResourceDefinition
  version: v1
  # either Namespaced or Cluster
  scope: Namespaced
  names:
    # plural name to be used in the URL: /apis/<group>/<version>/<plural>
    plural: crontabs
    # singular name to be used as an alias on the CLI and for display
    singular: crontab
    # kind is normally the CamelCased singular type. Your resource
manifests use this.
    kind: CronTab
    # shortNames allow shorter string to match your resource on the CLI
    shortNames:
    - cront

添加完毕后,我们可以创建它:

anonymuse@cloudshell:~ (gsw-k8s-3)$ kubectl apply -f resourcedefinition.yaml
customresourcedefinition "crontabs.stable.example.com" created

很好!这意味着我们的 RESTful 端点将在以下 URI 下可用:

/apis/stable.example.com/v1/namespaces/*/crontabs/。现在,我们可以使用此端点来管理自定义对象,这是我们 CRD 值的另一部分。

创建一个名为os-crontab.yaml的自定义对象,以便我们可以将一些任意的 JSON 数据插入该对象。在我们的例子中,我们将添加与 cron 相关的操作系统元数据和 crontab 的时间间隔。

添加以下内容:

apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
  name: cron-object-os-01
spec:
  intervalSpec: "* * 8 * *"
  os: ubuntu
anonymuse@cloudshell:~ (gsw-k8s-3)$ kubectl create -f os-crontab.yaml
crontab "cron-object-os-01" created

创建资源后,您可以像对待任何其他 Deployment、StatefulSet 或其他 Kubernetes 对象一样获取它:

anonymuse@cloudshell:~ (gsw-k8s-3)$ kubectl get crontab
NAME                AGE
cron-object-os-01   38s

如果我们检查该对象,预计会看到一堆标准配置,以及我们在 CRD 中编码的intervalSpec和操作系统数据。让我们检查一下,看它是否存在。

我们可以使用在 CRD 中定义的替代名称cront来查找它。我已将数据标记如下——做得好!

anonymuse@cloudshell:~ (gsw-k8s-3)$ kubectl get cront-o yaml
apiVersion: v1
items:
- apiVersion: stable.example.com/v1
  kind: CronTab
  metadata:
    clusterName: ""
    creationTimestamp: 2018-09-03T23:27:27Z
    generation: 1
    name: cron-object-os-01
    namespace: default
    resourceVersion: "2449"
    selfLink: /apis/stable.example.com/v1/namespaces/default/crontabs/cron-object-os-01
    uid: eb5dd081-afd0-11e8-b133-42010a8e0095
  spec:
    intervalSpec: '* * 8 * *'
    os: Ubuntu
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""

总结

在本章中,我们探讨了高可用性(HA)的核心组件。我们深入研究了可用性、uptime 和脆弱性等概念,并探讨了如何实现五个九的 uptime。

此外,我们还探讨了高可用性集群的关键组件——etcd 和控制平面节点,并留出了空间来想象通过使用主要云服务提供商的托管 PaaS 产品,我们如何将 HA 构建到我们的集群中。

稍后,我们探讨了集群生命周期,并深入研究了 Kubernetes 系统的一些关键功能,如准入控制器、工作负载 API 和 CRS。

最后,我们在 GCP 中的 GKE 集群上创建了一个 CRD,以便了解如何开始构建这些自定义软件组件。

问题

  1. 有哪些方法可以衡量一个应用程序的质量?

  2. Uptime的定义是什么?

  3. Kubernetes 系统应该争取多少个 9 的可用性?

  4. 当系统以预定义的方式失败,但仍然提供有限的功能时,这意味着什么?

  5. 哪个 PaaS 提供跨多个可用区的高度可用的主节点和工作节点?

  6. 什么是堆叠节点?

  7. 收集所有控制器并将它们整合到单一统一 API 中的 API 名称是什么?

进一步阅读

如果您想了解更多关于高可用性和 Kubernetes 管理的信息,可以查阅以下 Packt 资源:

第十一章:Kubernetes SIGs、孵化项目和 CNCF

在本章中,我们将讨论如何参与 Kubernetes 生态系统中的社交方面。我们将详细讲解云原生计算基金会CNCF)的运作方式,以及全球范围内协调开源软件的各种努力。我们生态系统中的兴趣遍及各个层次,从个人贡献者到财富 100 强大公司。

我们将探讨 CNCF 及其前身——Linux 和 Apache 基金会,如何引导对人力和软件经济的兴趣和贡献。重点领域将管理治理、跟踪和流程,旨在保持人、过程和技术在可持续、可靠的模型中不断发展。在本章中,我们将探索几个关键领域:

  • Kubernetes 生态系统中的社区是如何构建的?它与传统的自由和开源软件FOSS)或开源软件OSS)运动有何不同?

  • 你如何参与讨论,以便理解并参与生态系统的发展?

  • 主要项目是什么,它们是如何分类的?

  • 如何在变化中选择适合的工具?

  • 如何一般性地参与开源软件?

技术要求

为了更快地推进本章内容,你应确保已经设置好 GitHub 账号,并正确配置了 SSH 密钥和账户信息。你可能会问,为什么这很重要?因为,要参与 CNCF,以及 Linux 或 Apache 基金会,你需要一种方式来浏览、使用并贡献代码。Git 是参与的底层工具和流程,因此我们会在这里确保我们的工具集已正确设置,然后再进入更高级的话题。

你可以注册 GitHub 账号,一旦添加了账户,你可以在网站的GitHub 指南部分查看帮助内容,网址为guides.github.com/。在本章中,你需要设置 SSH 密钥,才能开始克隆、签名和提交代码。

如果你使用 Windows,你需要使用 Git Bash 或类似工具来生成密钥。你可以从gitforwindows.org/下载 Git Bash。

首先安装软件,然后我们将设置你的环境。安装过程如下所示:

设置 Git 进行贡献

输入以下命令,用你的电子邮件地址替换我的地址:

$ ssh-keygen -t rsa -b 4096 -C "jesse@gsw-k8s-3rd.com"
Generating public/private rsa key pair.
Enter file in which to save the key (/c/Users/jesse/.ssh/id_rsa):
Created directory '/c/Users/jesse/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /c/Users/jesse/.ssh/id_rsa.
Your public key has been saved in /c/Users/jesse/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:AtDI+/yPNxi8y6WzdTecvd6U/ir6Q8pBtg0dv/ZhHlY jesse@gsw-k8s-3rd.com
The key's randomart image is:
+---[RSA 4096]----+
| ..o             |
|  o..       . |
|   ..    . o |
|  . .    + . . E|
|   o .. So +   ..|
|    o o. o.ooo=.|
|     . +o..+=.=o+|
|     .==o.o.o..=.|
|      **...o.++o+|
+----[SHA256]-----+
$ ~/Documents/Code

这将生成一个密钥对,你可以将其添加到你的ssh-agent中。如果你不想使用 SSH 密钥,也可以选择使用 GitHub Desktop,但我们建议你使用本地 CLI 工具。

确保代理正在运行,使用以下命令:

$ eval $(ssh-agent -s)
Agent pid 11684

然后你可以按照以下步骤将密钥添加到代理中:

$ ssh-add ~/.ssh/id_rsa
Identity added: /c/Users/jesse/.ssh/id_rsa (/c/Users/jesse/.ssh/id_rsa)

我们这里不详细介绍操作步骤,但你可以在help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/找到 macOS 和 Linux 的操作说明。

接下来,我们将把你的公共密钥添加到 GitHub 账户,以便继续进行本章的其他内容。访问github.com,点击你的个人资料,打开设置页面:

接下来,我们点击 SSH 和 GPG 密钥,并添加你在机器上创建的密钥:

点击“New SSH key”,然后添加你生成的id_rsa.pub密钥。重要的是,不要添加id_rsa密钥,因为它是私密的,应该保密并且离线存储!

你可以通过以下命令在 Windows 中将你的公共 SSH 密钥复制到剪贴板:

$ clip < ~/.ssh/id_rsa.pub

配置好后,你可以通过以下命令来测试它:

$ ssh -vT git@github.com
OpenSSH_7.7p1, OpenSSL 1.0.2o 27 Mar 2018
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: Connecting to github.com [192.30.253.113] port 22.
debug1: Connection established.
….SNIP...
Hi anonymuse! You've successfully authenticated, but GitHub does not provide shell access.
debug1: channel 0: free: client-session, nchannels 1
Transferred: sent 3328, received 2048 bytes, in 0.1 seconds
Bytes per second: sent 36660.1, received 22560.1
debug1: Exit status 1

如果你看到欢迎信息显示你的用户名,说明一切设置完成!

Git 的好处

一旦你拥有了密钥,添加到你的 GitHub 账户中,以完成两项重要任务:

  • Fork、拉取请求和贡献:你将能够使用自己的代码库创建私有的 fork 和拉取请求,从而开始为容器生态系统中的项目做贡献。你需要 SSH 密钥以及前面提到的程序,才能与 Git 进行交互,而 Git 是支持这一协作的底层技术。GitLab 和 Bitbucket 也有类似的设置,但 GitHub 目前是最受欢迎的工具,恰好是所有 CNCF 项目所在的地方。

  • 数字化监控链:你将能够签署你的提交。为了参与许多前沿的 Kubernetes 生态系统项目,你需要数字签名你的提交,以便能够追溯到你。我们在本书中提到的许多技术被用于支撑世界上最先进公司的大型基础设施,对于开源软件(OSS)来说,建立一个强大的代码开发监控链至关重要。SSL 的指纹和你的机器是验证和授权的关键组成部分。

CNCF 结构

作为复习,让我们回顾一下整个 Kubernetes 系统,以便概念上理解本章所提到的生态系统在其中的位置:

在本章中,我们讨论的是前面图示中最顶层、最绿色的那一层。该层由数百家公司和产品组成,支持运行 Kubernetes 所需的软件和框架。你可以在以下几个地方找到这一层的最高级别分组:

  • 第一个需要检查的地方是 Kubernetes 社区的 GitHub 仓库:

    • 你可以在github.com/kubernetes/community找到代码库,它是任何有兴趣加入 Kubernetes 系统代码部分的人的良好起点。在前面的图示中,考虑从内核到接口的层次,也就是第一到第四层。这里将是我们找到特别兴趣小组SIGs)的地方,SIGs 将帮助我们扩展到生态系统层,探索支撑技术,使 Kubernetes 能够专注于其核心功能。
  • 你可以进一步调查生态系统的第二个地方——CNCF 生态图:

    • 该生态系统实际上被分为几个有用的部分,可以帮助从个人用户到大型企业的所有人做出决策,选择采用哪些技术、等待哪些技术的成熟、以及舍弃哪些技术。这里是我们深入了解支持生态系统的地方,目的是理解哪些是 Kubernetes 核心所必需的,哪些是由生态系统提供的。

Kubernetes 文档通过以下引述简洁地回答了 Kubernetes 是什么这个问题:

Kubernetes 提供了一个以容器为中心的管理环境。它代表用户工作负载协调计算、网络和存储基础设施。这结合了平台即服务(PaaS)的简单性与基础设施即服务(IaaS)的灵活性,并使得跨基础设施提供商的可移植性成为可能。

欲了解更多信息,请访问kubernetes.io/docs/concepts/overview/what-is-kubernetes/

那么,如果这就是 Kubernetes,那它不是做什么呢?

Kubernetes 不是

关于 Kubernetes 生态系统的最简洁——也是目前最好的——观点,以适合既运行自己小型集群的个人,又适合希望理解 Kubernetes 生态系统巨大范围的高管的层次,便是云原生路线图,如图所示:

路线图帮助我们拆解当前在 Kubernetes 核心容器管理环境之外所进行的所有支持工作,正如我们在上一部分所提到的那样。除了网络、存储和计算之外,还有许多活动的部分需要协同工作,才能使复杂的、基于微服务的、云原生应用在大规模上运行。要支持 Kubernetes PaaS 系统,还需要什么?

你应该将每一层视为一个选择;选择一项技术(或多项技术,来展示概念验证和决策)并查看它如何运作。

例如,让我们以容器化为例:此时,将应用程序作为容器化工作负载运行已成为标准,但你的组织可能需要一些时间来重新架构应用程序,或学习如何使用 Dockerfile 构建云原生应用。

在将应用程序迁移到云端或容器编排与调度平台时,传统上涉及到的有6Rs

这是一个展示前面提示框中提到的 6Rs 的图示,你可以利用它来更新你的应用程序:

虽然这个 6Rs 公式最初是为了考虑迁移到云端而设计的,但在迁移到容器时也非常有用。请记住,并非所有应用程序都适合在容器中运行(保留),而有些应该为开源软件(OSS)淘汰(退役)。开始迁移到容器化工作负载的一个好方法是直接将一个大型的单体应用程序,如Java .war文件或 Python 程序,放入容器中并按原样运行(重新托管)。为了实现容器化的最大效益,并利用 Kubernetes 的前沿特性,你很可能需要探索重构、重新构想和重新设计你的应用程序(重构)。

任何运行平台的人接下来的关注点是持续集成与持续交付CICD)。你需要管理基础设施和应用程序作为代码,以实现无缝发布、更新和测试。在这个新世界中,基础设施和应用程序在软件领域都是一等公民。

可观测性和分析在控制基础设施和应用程序的高度复杂软件系统中同样重要。CNCF 将解决方案分为沙盒、已毕业和孵化区域:

  • 沙盒:OpenMetrics 旨在创建一个通用标准,基于 Prometheus 构建,以大规模传输度量数据。OpenMetrics 使用标准文本格式,以及协议缓冲区来序列化结构化数据,以语言和平台中立的方式进行处理。

  • 孵化中:在这里,我们看到了 Fluentd、Jaeger 和 OpenTracing。Fluentd 已经存在一段时间了,对于那些使用Elasticsearch、Logstash、KibanaELK)栈收集度量数据的人来说,它是一个开源的数据聚合器,允许你将来自不同来源的日志统一起来。Jaeger 帮助运维人员通过提供追踪功能来监控和解决复杂分布式系统中的问题,这些追踪有助于发现现代微服务系统中的问题。与 OpenMetrics 类似,OpenTracing 是一个旨在为微服务和开源软件建立分布式追踪标准的努力。随着我们的系统越来越多地与不知道细节的 API 互联,检查这些系统之间的连接变得愈发重要。

  • 已毕业:与 Kubernetes 一起,Prometheus 仍然是 CNCF 中目前唯一一个已毕业的项目。Prometheus 是一个监控和告警系统,可以使用多种不同的时间序列数据库来显示系统状态。

服务网格和发现是沿着云原生路线图的下一个步骤。这个层级可以被视为 Kubernetes 基础功能之上的额外能力集,Kubernetes 可以被视为以下能力集合的组合:

  • 一个单一的 Kubernetes API 控制平面

  • 一个身份验证和授权模型

  • 一个命名空间化、可预测、集群范围的资源描述方案

  • 一个容器调度和编排域

  • 一个 Pod 到 Pod 和入口网络路由域

该地图中的三个产品是 CoreDNS、Envoy 和 Linkerd。CoreDNS 替代了集群中的kube-dns,并提供了将多个插件链接在一起的能力,以创建更深层次的功能用于查找客户提供者。CoreDNS 将很快取代kube-dns,成为 Kubernetes 的默认 DNS 提供者。Envoy 是一个服务代理,内置于流行的 Istio 产品中。Istio 是一个控制层,使用 Envoy 二进制作为数据平面,为一组同质的软件或服务提供通用功能。Envoy 为在 Kubernetes 上运行的应用程序提供服务网格的基础能力,提供了额外的韧性层,包括断路器、速率限制、负载均衡、服务发现、路由以及通过度量和日志形式的应用程序自省。Linkerd 几乎具备与 Envoy 相同的所有功能,它也是服务网格的数据平面。

网络是我们可以加入 Kubernetes 生态系统的下一个构建模块。容器网络接口CNI)是当前在 CNCF 生态系统内开发的多个接口之一。为了应对现代应用程序的复杂功能需求,Kubernetes 集群网络的多个选项正在开发中。当前的选项包括:

  • Calico

  • Flannel

  • Weave Net

  • Cilium

  • Contiv

  • SR-IOV

  • Knitter

Kubernetes 团队还提供了一组核心插件,用于管理系统中的 IP 地址分配和接口创建。

github.com/containernetworking/plugins/了解更多关于标准插件的信息。

从 GitHub 项目主页读取,CNI 被描述如下:

CNI(容器网络接口),是一个云原生计算基金会(Cloud Native Computing Foundation,CNCF)项目,包含用于编写插件以配置 Linux 容器中网络接口的规范和库,以及一些受支持的插件。CNI 只关心容器的网络连接性,并在容器被删除时移除已分配的资源。由于专注于这一点,CNI 得到了广泛的支持,且其规范易于实现。

欲了解更多关于云原生计算基金会的信息,请访问www.cncf.io/

目前在分布式数据库部分的路线图中活动并不多,原因在于目前大多数在 Kubernetes 上运行的工作负载通常是无状态的。目前有一个正在孵化的项目,名为 Vitess,旨在为广受欢迎的 MySQL 数据库系统提供横向扩展模型。为了在 Kubernetes 的 Pod 结构化基础设施上扩展 MySQL,Vitess 的开发者专注于对 MySQL 的数据存储进行分片,以便将其分布到集群的各个节点上。这与其他 NoSQL 系统类似,通过这种方式,数据依赖于在多个节点之间进行复制和分散。自 2011 年起,Vitess 就已在 YouTube 大规模使用,是那些希望深入探索 Kubernetes 上有状态工作负载的用户的一个有前景的技术。

对于那些推动 Kubernetes 系统极限的操作员来说,有几种高性能选项可以提高系统速度。gRPC 是一个由 Google 开发的远程过程调用RPC)框架,旨在帮助客户端和服务器透明地进行通信。gRPC 支持多种语言,包括 C++、Java、Python、Go、Ruby、C#、Node.js 等。gRPC 使用 ProtoBuf,并基于一个简单的概念:服务应具有可以从另一个远程服务调用的方法。通过在代码中定义这些方法和参数,gRPC 允许构建大型复杂的应用程序,并将其拆分成多个部分。NATS 是一个消息队列,实施了一个分布式队列系统,提供发布/订阅和请求/回复功能,从而为进程间通信IPC)提供一个高度可扩展且安全的基础。

容器运行时部分的路线图是一个存在一定争议的领域。目前在 CNCF 中有两个选项:containerdrkt。这两种技术目前并未遵循容器运行时接口CRI)标准,这是一个新的标准,旨在创建对容器运行时应具备功能的共同理解。在 CNCF 之外,有一些示例目前已符合 CRI 标准:

  • CRI-O

  • Docker CRI shim

  • Frakti

  • rkt

也有一些有趣的参与者,例如 Kata Containers,它符合开放容器倡议OCI)标准,并试图通过利用 Hyper 的 runV 和 Intel 的 Clear Containers 技术,提供运行在轻量级虚拟机上的容器。在这里,Kata 取代了传统的 runC 运行时,提供带有其独立迷你内核的轻量级虚拟机容器。

最后一块拼图是软件分发,它由 Notary 和 TUF 框架覆盖。这些工具旨在帮助安全分发软件。Notary 是一个客户端/服务器框架,允许人们在可选择的数据集合中建立信任。简而言之,发布者可以签署数据内容,然后将其发送给有权限访问公钥加密系统的消费者,这样他们就可以验证发布者的身份和数据。

TUF 框架被 Notary 使用,Notary 是一个允许安全更新软件系统的框架。TUF 被用于通过空中下载OTA)向汽车提供安全更新。

Kubernetes SIGs

除了前面提到的所有参与者,还有一组互补的 SIG 定期召开会议,讨论来自 Kubernetes 生态系统中某个特定领域的问题和机会。在这些 SIG 内部,还有一些子领域工作组,旨在实现特定目标。还有一些子项目,进一步细分兴趣领域,以及委员会,专门定义元标准并处理社区范围内的问题。

以下是当前运行中的 SIG 列表,包括当前的主席和会议安排:

名称 主席 会议
API Machinery (github.com/kubernetes/community/blob/master/sig-api-machinery/README.md)
定期 SIG 会议:每周三上午 11:00 PT(太平洋时间)(每两周一次) (docs.google.com/document/d/1FQx0BPlkkl1Bn0c9ocVBxYIKojpmrS1CFP5h0DI68AE/edit)
Apps (github.com/kubernetes/community/blob/master/sig-apps/README.md)
定期 SIG 会议:每周一上午 9:00 PT(太平洋时间)(每周一次)
架构 (github.com/kubernetes/community/blob/master/sig-architecture/README.md)
定期 SIG 会议:每周四 19:00 UTC(每周一次)
Auth (github.com/kubernetes/community/blob/master/sig-auth/README.md)
定期 SIG 会议:每周三 太平洋时间 11:00 (每两周一次)
AWS (github.com/kubernetes/community/blob/master/sig-aws/README.md)
定期 SIG 会议:每周五 太平洋时间 9:00 (每两周一次)
Azure (github.com/kubernetes/community/blob/master/sig-azure/README.md)
定期 SIG 会议:每周三 UTC 16:00 (每两周一次)
大数据 (github.com/kubernetes/community/blob/master/sig-big-data/README.md)
定期 SIG 会议: 每周三 UTC 17:00 (每周一次)

如果你想参加其中一场会议,可以查看这里的主列表:github.com/kubernetes/community/blob/master/sig-list.md

如何参与

我们想与您分享的最后一件事是,帮助您指引正确的方向,以便您能够直接开始为 Kubernetes 或其他相关软件做贡献。Kubernetes 有一个很棒的贡献者指南,您应该考虑贡献,理由有很多:

  • 这是理解 Kubernetes 核心概念和内部工作机制的绝佳方式。编写该系统的软件将为你作为操作员或开发人员提供独特的视角,了解一切如何运作。

  • 这是一个结识其他有动力、聪明的人的有趣方式。世界变得越来越互联互通,开源软件正在推动一些全球最大公司的发展。直接参与这些技术的工作将让你接触到世界上最先进公司的工程师,甚至可能为你打开重大的职业机会之门。

  • Kubernetes 本质上是一个社区项目,依赖于成员和用户的贡献。直接参与文档更新、错误修复和功能创建的贡献,推动了生态系统的发展,并为每个人提供了更好的体验。

如果你想了解更多关于如何成为 Kubernetes 贡献者的信息,可以阅读 github.com/kubernetes/community/tree/master/contributors/guide/

总结

在这一章中,你了解了围绕 Kubernetes 系统的 Kubernetes 生态系统。你阅读了 CNCF 的核心组成部分,我们还探索了 Cloud Native Trail Map,以了解所有支持的技术。我们还了解了 SIGs,以及你如何开始为 Kubernetes 本身做出贡献以及为什么这很重要!

问题

  1. 请列举至少一个在 CNCF 中毕业的项目

  2. 请列举至少三个在 CNCF 孵化的项目

  3. 请列举至少一个 CNCF 沙箱中的项目

  4. CNCF 中委员会的目标是什么?

  5. 为什么参与开源软件开发很重要?

  6. Git 贡献需要什么样的加密材料?

进一步阅读

如果你想了解更多关于如何精通 Git 的内容,可以查看 Packt Publishing 提供的以下资源:www.packtpub.com/application-development/mastering-git

第十二章:集群联合与多租户

本章将讨论新的联合能力以及如何使用它们来管理跨云提供商的多个集群。我们还将涵盖核心构件的联合版本。我们将带你了解联合的 Deployments、ReplicaSets、ConfigMaps 和 Events。

本章将讨论以下主题:

  • 联合集群

  • 联合多个集群

  • 检查和控制跨多个集群的资源

  • 在多个集群中启动资源

技术要求

你需要启用并登录你的 Google Cloud Platform 账户,或者可以使用本地的 Minikube 实例。你还可以使用 Web 上的 Play with Kubernetes:labs.play-with-k8s.com/。此外,还有 Katacoda 的在线沙箱:www.katacoda.com/courses/kubernetes/playground

你还需要 GitHub 凭证,我们将在本章稍后介绍如何设置。以下是本章的 GitHub 仓库:github.com/PacktPublishing/Getting-Started-with-Kubernetes-third-edition/tree/master/Code-files/Chapter12

联合简介

虽然联合在 Kubernetes 中仍然是一个相对较新的概念,但它为一个备受追捧的跨云提供商解决方案奠定了基础。通过使用联合,我们可以在本地和一个或多个公共云提供商中运行多个 Kubernetes 集群,并利用我们所有组织资源的完整集合来管理应用程序。

这为避免云提供商锁定和实现高可用部署提供了路径,可以将应用程序服务器放置在多个集群中,并允许与我们联合集群中单点服务的其他服务进行通信。我们可以在特定提供商或地理位置发生故障时改善隔离,同时为扩展和利用整体基础设施提供更大的灵活性。

当前,联合平面支持以下资源:ConfigMap、DaemonSets、Deployment、Events、Ingress、Namespaces、ReplicaSets、Secrets 和 Services。请注意,联合及其组件仍处于 alpha 和 beta 阶段,因此功能可能会有些不稳定。

为什么使用联合?

使用 Kubernetes 集群联合有几个主要的优点。正如前面提到的,联合可以提高 Kubernetes 集群的可用性和租户能力。通过在单一云服务提供商CSP)的可用区或区域之间进行扩展,或者跨多个 CSP 进行扩展,联合将高可用性的概念提升到了一个新的水平。有些人称之为全球调度,这将使你能够引导流量,以最大化在现货市场中变得可用的便宜 CSP 资源。你还可以利用全球调度将工作负载集群迁移到最终用户群体,提高应用程序的性能。

还有机会将整个集群视为 Kubernetes 对象,并按集群而不是按机器处理故障。集群联合可以让操作员通过将流量引导到冗余的、可用的集群来自动恢复整个集群的故障。

应该注意的是,尽管联合增加了集群的高可用性潜力,但显著增加的复杂性也降低了可靠性,特别是在集群管理不当的情况下。你可以通过使用 Kubernetes 的托管 PaaS 版本(例如 GKE)来管理一些复杂性,将集群管理交给 GCP,将大大减轻团队的操作负担。

联合还可以使你的团队支持混合环境,将本地集群与云中的资源配对。根据你的流量路由要求,这可能需要额外的工程支持,通常表现为服务网格。

联合提供了许多技术特性,这些特性使得可用性有更高的潜力。

联合的构建模块

联合通过提供两种不同类型的构建模块,使得跨集群管理资源变得容易。第一种是资源,第二种是服务发现:

  • 跨集群资源同步:联合是将你需要的许多资源保持同步的粘合剂,以运行一组应用程序。当你在多个集群中运行大量应用程序,并且涉及许多资源和对象类型时,联合是保持集群有序和管理良好的关键。你可能会发现需要在多个集群中保持应用程序部署,并提供单一的管理视图。

  • 多集群服务发现:有许多资源在集群之间共享得很好,例如 DNS、负载均衡器、对象存储和入口。联合使你能够自动配置这些服务,使其具备多集群感知能力,从而可以在多个集群之间路由应用流量,并管理控制平面。

正如我们接下来要学习的,Kubernetes 联邦是通过一个名为kubefed的工具来管理的,该工具有许多命令行标志,可以让你管理多个集群以及我们之前讨论的构建块。我们将使用的kubefed的主要构建块如下:

  • kubefed init:初始化联邦控制平面

  • kubefed join:将一个集群加入到联邦中

  • kubefed options:打印所有命令继承的标志列表

  • kubefed unjoin:将一个集群从联邦中移除

  • kubefed version:打印客户端和服务器版本信息

这里有一个方便的选项列表,可以使用:

      --alsologtostderr                              log to standard error as well as files
      --as string                                    Username to impersonate for the operation
      --as-group stringArray                         Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
      --cache-dir string                             Default HTTP cache directory (default "/Users/jrondeau/.kube/http-cache")
      --certificate-authority string                 Path to a cert file for the certificate authority
      --client-certificate string                    Path to a client certificate file for TLS
      --client-key string                            Path to a client key file for TLS
      --cloud-provider-gce-lb-src-cidrs cidrs        CIDRs opened in GCE firewall for LB traffic proxy & health checks (default 130.211.0.0/22,209.85.152.0/22,209.85.204.0/22,35.191.0.0/16)
      --cluster string                               The name of the kubeconfig cluster to use
      --context string                               The name of the kubeconfig context to use
      --default-not-ready-toleration-seconds int     Indicates the tolerationSeconds of the toleration for notReady:NoExecute that is added by default to every pod that does not already have such a toleration. (default 300)
      --default-unreachable-toleration-seconds int   Indicates the tolerationSeconds of the toleration for unreachable:NoExecute that is added by default to every pod that does not already have such a toleration. (default 300)
  -h, --help                                         help for kubefed
      --insecure-skip-tls-verify                     If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
      --ir-data-source string                        Data source used by InitialResources. Supported options: influxdb, gcm. (default "influxdb")
      --ir-dbname string                             InfluxDB database name which contains metrics required by InitialResources (default "k8s")
      --ir-hawkular string                           Hawkular configuration URL
      --ir-influxdb-host string                      Address of InfluxDB which contains metrics required by InitialResources (default "localhost:8080/api/v1/namespaces/kube-system/services/monitoring-influxdb:api/proxy")
      --ir-namespace-only                            Whether the estimation should be made only based on data from the same namespace.
      --ir-password string                           Password used for connecting to InfluxDB (default "root")
      --ir-percentile int                            Which percentile of samples should InitialResources use when estimating resources. For experiment purposes. (default 90)
      --ir-user string                               User used for connecting to InfluxDB (default "root")
      --kubeconfig string                            Path to the kubeconfig file to use for CLI requests.
      --log-backtrace-at traceLocation               when logging hits line file:N, emit a stack trace (default :0)
      --log-dir string                               If non-empty, write log files in this directory
      --log-flush-frequency duration                 Maximum number of seconds between log flushes (default 5s)
      --logtostderr                                  log to standard error instead of files (default true)
      --match-server-version                         Require server version to match client version
  -n, --namespace string                             If present, the namespace scope for this CLI request
      --password string                              Password for basic authentication to the API server
      --request-timeout string                       The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
  -s, --server string                                The address and port of the Kubernetes API server
      --stderrthreshold severity                     logs at or above this threshold go to stderr (default 2)
      --token string                                 Bearer token for authentication to the API server
      --user string                                  The name of the kubeconfig user to use
      --username string                              Username for basic authentication to the API server
  -v, --v Level                                      log level for V logs
      --vmodule moduleSpec                           comma-separated list of pattern=N settings for file-filtered logging

这里有一个高层次的图示,展示了当这些组件被串联起来时的样子:

关键组件

Kubernetes 中的联邦功能有两个关键组件。这些组件构成了联邦控制平面。

第一个是federation-controller-manager,它嵌入了操作联邦所需的核心控制循环。federation-controller-manager通过apiserver监视集群的状态,并进行更改,以达到期望的状态。

第二个是federation-apiserver,它验证并配置 Kubernetes 对象,如 pods、services 和 controllers。federation-apiserver是集群的前端,所有其他组件都通过它进行交互。

联邦服务

现在我们已经在脑海中构建了联邦的基础框架,接下来让我们在设置联邦之前,再回顾一下一个方面:一个跨多个集群部署的公共服务是如何工作的?

联邦服务的创建方式与常规服务非常相似:首先,将服务的期望状态和属性发送到 API 端点,然后通过 Kubernetes 架构实现。主要的区别有两个:

  • 一个非联邦服务将直接向集群 API 端点发出 API 调用

  • 联邦服务将向federation/v1beta1的联邦 API 端点发出调用,然后将 API 调用重定向到联邦控制平面内的所有单个集群。

第二种类型的服务允许我们扩展诸如跨集群边界的 DNS 服务发现等功能。DNS resolv链能够利用服务联邦和公共 DNS 记录,在多个集群之间解析名称。

联邦服务的 API 与常规服务 100%兼容。

当创建服务时,联邦会处理几件事。首先,它在所有kubefed指定的集群中创建匹配的服务。这些服务的健康状况会被监控,以便流量可以路由或重新路由到它们。最后,联邦确保通过 Route 53 或 Google Cloud DNS 等提供商,有一组确切的公共 DNS 记录可用。

在你的 Kubernetes 集群内,不同 Pod 上的微服务将利用这一切机制来定位联邦服务,无论是在自己的集群内,还是导航到你联邦图中最近的健康实例。

设置联邦

虽然我们可以使用已经运行的集群来完成其余的示例,但我强烈建议你从头开始。集群和上下文的默认命名对于联邦系统可能会产生问题。请注意,--cluster-context--secret-name标志可以帮助你绕过默认命名,但对于首次进行联邦设置来说,仍然可能会令人困惑且不太直接。

因此,从头开始是我们在本章中演示示例的方式。你可以使用新的独立云服务提供商(AWS 和/或 GCE)账户,或者通过运行以下命令摧毁当前集群并重置你的 Kubernetes 控制环境:

$ kubectl config unset contexts $ kubectl config unset clusters

使用以下命令仔细检查确保没有列出任何内容:

$ kubectl config get-contexts $ kubectl config get-clusters

接下来,我们将需要将kubefed命令添加到路径中并使其可执行。返回到你提取 Kubernetes 下载的文件夹。kubefed命令位于/kubernetes/client/bin文件夹中。运行以下命令进入bin文件夹并更改执行权限:

$ sudo cp kubernetes/client/bin/kubefed /usr/local/bin
$ sudo chmod +x /usr/local/bin/kubefed

上下文

上下文由 Kubernetes 控制平面使用,以便存储多个集群的认证信息和配置。这允许我们通过同一个kubectl访问和管理多个集群。你总是可以通过之前使用的get-contexts命令查看可用的上下文。

联邦的新集群

再次确保你进入 Kubernetes 下载的文件夹,并进入cluster子文件夹:

$ cd kubernetes/cluster/

在我们继续之前,请确保你已安装、验证并配置了 GCE 命令行工具和 AWS 命令行工具。如果你在新机器上需要帮助,可以参考第一章,Kubernetes 简介

首先,我们将创建 AWS 集群。请注意,我们正在添加一个名为OVERRIDE_CONTEXT的环境变量,这将允许我们将上下文名称设置为符合 DNS 命名标准的名称。DNS 是联邦的关键组件,因为它使我们能够进行跨集群发现和服务通信。在一个联邦环境中,集群可能位于不同的数据中心甚至不同的提供商之间,这一点尤为重要。

运行以下命令以创建你的 AWS 集群:

$ export KUBERNETES_PROVIDER=aws
$ export OVERRIDE_CONTEXT=awsk8s
$ ./kube-up.sh

接下来,我们将再次使用OVERRIDE_CONTEXT环境变量创建一个 GCE 集群:

$ export KUBERNETES_PROVIDER=gce
$ export OVERRIDE_CONTEXT=gcek8s
$ ./kube-up.sh

如果我们现在查看上下文,我们会注意到刚才创建的awsk8sgcek8sgcek8s前面的星号表示kubectl当前指向并正在执行的上下文:

$ kubectl config get-contexts 

前面的命令应该会产生如下所示的输出:

初始化联邦控制平面

现在我们有了两个集群,接下来在 GCE 集群中设置联邦控制平面。首先,我们需要确保处于 GCE 环境中,然后初始化联邦控制平面:

$ kubectl config use-context gcek8s
$ kubefed init master-control --host-cluster-context=gcek8s --dns-zone-name="mydomain.com" 

上述命令创建了一个新的联邦环境,名为master-control。它使用gcek8s集群/环境来托管联邦组件(如 API 服务器和控制器)。它假定 GCE DNS 作为联邦的 DNS 服务。你需要更新dns-zone-name,并填写你管理的域名后缀。

默认情况下,DNS 提供商是 GCE。你可以使用--dns-provider="aws-route53"将其设置为 AWS route53;然而,许多用户发现开箱即用的实现仍然存在问题。

如果我们再次检查环境,会看到三个环境:

$ kubectl config get-contexts 

上述命令应生成如下内容:

在继续之前,我们先确保所有联邦组件都在运行。联邦控制平面使用federation-system命名空间。使用带有命名空间的kubectl get pods命令来监控进度。当看到两个 API 服务器 pod 和一个控制器 pod 时,表示已就绪:

$ kubectl get pods --namespace=federation-system 

现在我们已经设置并运行了联邦组件,接下来让我们切换到该环境以进行后续步骤:

$ kubectl config use-context master-control

向联邦系统添加集群

现在我们有了联邦控制平面,可以将集群添加到联邦系统中。首先,我们将加入 GCE 集群,然后加入 AWS 集群:

$ kubefed join gcek8s --host-cluster-context=gcek8s --secret-name=fed-secret-gce
$ kubefed join awsk8s --host-cluster-context=gcek8s --secret-name=fed-secret-aws

联邦资源

联邦资源允许我们跨多个集群和/或区域进行部署。目前,Kubernetes 1.5 版本支持联邦 API 中的多个核心资源类型,包括 ConfigMap、DaemonSets、Deployment、Events、Ingress、Namespaces、ReplicaSets、Secrets 和 Services。

让我们看一下一个联邦部署,它将允许我们在 AWS 和 GCE 之间调度 pods。将以下文件保存为node-js-deploy-fed.yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: node-js-deploy
  labels:
    name: node-js-deploy
spec:
  replicas: 3
  template:
    metadata:
      labels:
        name: node-js-deploy
    spec: 
      containers: 
      - name: node-js-deploy 
        image: jonbaier/pod-scaling:latest 
        ports: 
        - containerPort: 80

使用以下命令创建此部署:

$ kubectl create -f node-js-deploy-fed.yaml

现在,尝试列出这个部署的 pods:

$ kubectl get pods

我们应该看到类似于前面显示的消息。这是因为我们仍在使用master-control或联邦环境,它本身不运行 pods。然而,我们会在联邦平面中看到部署,如果检查事件,我们会看到该部署实际上已经在我们的两个联邦集群上创建:

$ kubectl get deployments
$ kubectl describe deployments node-js-deploy 

我们应该看到如下内容。注意,Events:部分显示了我们在 GCE 和 AWS 环境中的部署:

我们还可以使用以下命令查看联邦事件:

$ kubectl get events

所有三个 Pod 启动可能需要一段时间。一旦启动,我们可以切换到每个集群的上下文,并查看每个集群中的一些 Pod。请注意,我们现在可以使用get pods,因为我们在单个集群中,而不是在控制平面中:

$ kubectl config use-context awsk8s
$ kubectl get pods

$ kubectl config use-context gcek8s
$ kubectl get pods

我们应该看到三个 Pod 分布在集群中,其中两个在一个集群,第三个在另一个集群。Kubernetes 已经在集群中分配了它们,无需人工干预。任何失败的 Pod 将被重新启动,但现在我们有了来自两个云提供商的冗余。

联邦配置

在现代软件开发中,将配置变量与应用程序代码本身分离是很常见的做法。这样,可以更容易地更新服务 URL、凭证、公共路径等。将这些值保存在外部配置文件中意味着我们可以轻松更新配置,而不需要重新构建整个应用程序。

这种分离解决了最初的问题,但真正的可移植性来自于当你能够完全去除应用程序中的依赖时。Kubernetes 提供了一个配置存储库,专门用于这个目的。ConfigMap 是一个简单的结构,用来存储键值对。

Kubernetes 还支持用于更敏感的配置数据的 Secrets。这个内容将在第十章,集群身份验证、授权和容器安全性中详细介绍。你可以在单集群或联邦控制平面中使用此处的示例,就像我们在这里演示ConfigMap一样。

让我们看一个例子,它将允许我们存储一些配置,然后在多个 Pod 中使用这些配置。以下列表适用于联邦集群和单集群,但我们将在这个例子中继续使用联邦配置。

ConfigMap类型可以通过字面量值、平面文件和目录,最终通过 YAML 定义文件来创建。以下列表是configmap-fed.yaml文件的 YAML 定义:

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-application-config
  namespace: default
data:
  backend-service.url: my-backend-service

首先,让我们切换回我们的联邦平面:

$ kubectl config use-context master-control

现在,使用以下命令创建此列表:

$ kubectl create -f configmap-fed.yaml

让我们展示一下我们刚刚创建的configmap对象。-o yaml标志帮助我们显示完整信息:

$ kubectl get configmap my-application-config -o yaml

现在我们有了一个ConfigMap对象,让我们启动一个可以使用该ConfigMap对象的联邦ReplicaSet。这将创建跨集群的 Pod 副本,这些副本可以访问ConfigMap对象。ConfigMap可以通过环境变量或挂载卷访问。这个例子将使用一个挂载卷,提供一个文件夹层次结构以及每个键的文件,文件内容表示相应的值。将以下文件保存为configmap-rs-fed.yaml

apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
  name: node-js-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      name: node-js-configmap-rs
  template:
    metadata:
      labels:
        name: node-js-configmap-rs
    spec:
      containers:
      - name: configmap-pod
        image: jonbaier/node-express-info:latest
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: configmap-volume
          mountPath: /etc/config
volumes:
      - name: configmap-volume
        configMap:
          name: my-application-config

使用kubectl create -f configmap-rs-fed.yaml创建此 Pod。创建后,我们需要切换到其中一个 Pod 正在运行的集群。你可以选择任意一个,但我们在这里使用 GCE 上下文:

$ kubectl config use-context gcek8s

现在我们专门进入了 GCE 集群,让我们在这里检查configmaps

$ kubectl get configmaps

如你所见,ConfigMap被本地传播到每个集群。接下来,让我们从我们的联邦ReplicaSet中找到一个 Pod:

$ kubectl get pods

让我们从列表中选择一个node-js-rs Pod 名称,并使用kubectl exec运行一个 bash shell:

$ kubectl exec -it node-js-rs-6g7nj bash

然后,进入我们在 Pod 定义中设置的/etc/config文件夹。列出该目录会显示一个文件,其名称是我们之前定义的ConfigMap

$ cd /etc/config
$ ls

如果我们使用以下命令显示文件的内容,我们应该能看到之前输入的值my-backend-service

$ echo $(cat backend-service.url)

如果我们查看我们联邦集群中的任何 Pod,都会看到相同的值。这是一种很好的方法,将配置与应用程序解耦并分发到我们的集群阵列中。

联邦水平 Pod 自动伸缩器

让我们看看另一个可以与联邦模型一起使用的新资源:水平 Pod 自动伸缩器HPAs)。

这是在单个集群中它们的架构:

资料来源:kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#how-does-the-horizontal-pod-autoscaler-work

这些 HPA 的行为与普通 HPA 相似,具有相同的功能和基于 API 的兼容性——只是通过联邦,管理将遍历你的集群。这是一个 alpha 功能,因此默认情况下在集群中未启用。为了启用它,你需要使用--runtime-config=api/all=true选项运行federation-apiserver。目前,唯一有效的度量标准是 CPU 利用率度量。

首先,让我们创建一个包含 HPA 配置的文件,命名为node-hpa-fed.yaml

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
 name: nodejs
 namespace: default
spec:
 scaleTargetRef:
   apiVersion: apps/v1beta1   kind: Deployment
name: nodejs
 minReplicas: 5
 maxReplicas: 20
 targetCPUUtilizationPercentage: 70

我们可以使用以下命令将其添加到我们的集群:

kubectl --context=federation-cluster create -f node-hpa-fed.yaml

在这种情况下,--context=federation-cluster是告诉kubectl将请求发送到federation-apiserver而不是kube-apiserver

例如,如果你想将此 HPA 限制为某些 Kubernetes 集群的子集,可以使用集群选择器来限制联邦对象,方法是使用federation.alpha.kubernetes.io/cluster-selector注解。它的功能类似于 nodeSelector,但作用于完整的 Kubernetes 集群。很酷!你需要创建一个 JSON 格式的注解。以下是 ClusterSelector 注解的具体示例:

metadata:
  annotations:
     federation.alpha.kubernetes.io/cluster-selector: '[{"key": "hipaa", "operator":
       "In", "values": ["true"]}, {"key": "environment", "operator": "NotIn", "values": ["nonprod"]}]'

这个示例将保持带有hipaa标签的工作负载不在带有nonprod标签的环境中运行。

要查看顶级联合 API 对象的完整列表,请参见以下链接:kubernetes.io/docs/reference/federation/

你可以检查你的集群,查看是否在单个位置创建了 HPA,通过指定上下文:

kubectl --context=gce-cluster-01 get HPA nodejs

完成 HPA 后,可以使用以下 kubectl 命令将其删除:

kubectl --context=federation-cluster delete HPA nodejs

如何使用联合 HPA

以前使用的 HPA 是确保你的集群在工作负载增加时进行扩展的必要工具。HPA 在集群中的默认行为是首先在所有集群中均匀分配最大副本。假设你在联合控制平面中注册了 10 个 Kubernetes 集群。如果你的 spec.maxReplicas = 30,那么每个集群将收到以下 HPA spec

spec.maxReplicas = 10

如果你将 spec.minReplicas = 5 设置为值,那么一些集群将接收到以下内容:

spec.minReplicas = 1

这是由于无法将副本总数设置为 0。需要注意的是,联合会操作它在联合集群中创建的最小/最大副本,而不是直接监控目标对象的指标(在我们的例子中是 CPU)。联合 HPA 控制器依赖于联合集群中的 HPA 来监控 CPU 利用率,然后根据需要调整副本数(例如当前副本数和期望副本数)。

其他联合资源

到目前为止,我们已经看到联合部署(Deployments)、副本集(ReplicaSets)、事件(Events)和配置映射(ConfigMaps)的实际应用。守护进程集(DaemonSets)、入口(Ingress)、命名空间(Namespaces)、秘密(Secrets)和服务(Services)也受到支持。你的具体设置可能会有所不同,你可能有一组与我们这里的示例不同的集群。如前所述,这些资源仍处于测试阶段,因此值得花一些时间来实验各种资源类型,并了解联合构建在你的基础设施组合中得到的支持程度。

让我们来看一些示例,这些示例展示了如何从联合视角利用其他常见的 Kubernetes API 对象。

事件

如果你想查看仅存储在联合控制平面中的事件,可以使用以下命令:

kubectl --context=federation-cluster get events

作业

当你去创建一个作业时,你将使用与之前类似的概念。以下是你在联合上下文中创建作业时的样子:

kubectl --context=federation-cluster create -f fedjob.yaml

你可以通过以下命令,在联合上下文中获取这些作业的列表:

kubectl --context=gce-cluster-01 get job fedjob

与 HPA 一样,你可以使用适当的规格将作业分布到多个基础集群中。相关定义为 spec.parallelismspec.completions,可以通过指定正确的 ReplicaAllocationPreferencesfederation.kubernetes.io/job-preferences 键来修改。

真正的多云

这是一个值得关注的激动人心的领域。随着它的成长,它为我们提供了一个很好的起点,来进行多云实施,并提供跨区域、数据中心甚至云提供商的冗余。

虽然 Kubernetes 提供了一个简单而激动人心的路径来实现多云基础设施,但需要注意的是,生产环境中的多云不仅仅是分布式部署。除了日志记录、监控、合规性和主机加固等功能外,在多提供商环境中,还需要管理更多的能力。

真正的多云采用需要一个精心规划的架构,而 Kubernetes 在实现这一目标方面迈出了重要一步。

开始多云部署

在本次练习中,我们将使用 Istio 的多云功能将两个集群连接起来。通常,我们会从头开始在两个云服务提供商(CSP)中创建两个集群,但为了更专注于逐步探讨单一的隔离概念,我们将使用 GKE 来启动集群,这样我们可以专注于 Istio 多云功能的内部工作原理。

让我们开始吧,首先登录到你的 Google Cloud 项目!首先,你需要在 GUI 中创建一个名为gsw-k8s-3的项目,如果还没创建的话,并将你的 Google Cloud Shell 指向该项目。如果你已经指向了你的 GCP 账户,可以忽略这一步。

点击此按钮,轻松访问 CLI 工具:

启动 Shell 后,你可以将其指向你的项目:

anonymuse@cloudshell:~$ gcloud config set project gsw-k8s-3
Updated property [core/project].
anonymuse@cloudshell:~ (gsw-k8s-3)$

接下来,我们将为项目 ID 设置一个环境变量,通过该变量可以回显查看:

anonymuse@cloudshell:~ (gsw-k8s-3)$ proj=$(gcloud config list --format='value(core.project)')
anonymuse@cloudshell:~ (gsw-k8s-3)$ echo $proj
Gsw-k8s-3

现在,让我们创建一些集群。设置区域和集群名称的变量:

zone="us-east1-b"
cluster="cluster-1"

首先,创建集群一:

gcloud container clusters create $cluster --zone $zone --username "
 --cluster-version "1.10.6-gke.2" --machine-type "n1-standard-2" --image-type "COS" --disk-size "100" \
 --scopes gke-default \
 --num-nodes "4" --network "default" --enable-cloud-logging --enable-cloud-monitoring --enable-ip-alias --async

WARNING: Starting in 1.12, new clusters will not have a client certificate issued. You can manually enable (or disable) the issuance of the client certificate using the `--[no-]issue-client-certificate` flag. This will enable the autorepair feature for nodes. Please see https://cloud.google.com/kubernetes-engine/docs/node-auto-repair for more information on node autorepairs.

WARNING: Starting in Kubernetes v1.10, new clusters will no longer get compute-rw and storage-ro scopes added to what is specified in --scopes (though the latter will remain included in the default --scopes). To use these scopes, add them explicitly to --scopes. To use the new behavior, set container/new_scopes_behavior property (gcloud config set container/new_scopes_behavior true).

NAME       TYPE LOCATION    TARGET STATUS_MESSAGE  STATUS START_TIME  END_TIME
cluster-1        us-east1-b                   PROVISIONING

你可能需要将集群版本更新到更新的 GKE 版本,因为随着更新的发布,旧版本会逐渐不再支持。例如,你可能会看到如下消息:

ERROR: (gcloud.container.clusters.create) ResponseError: code=400, message=EXTERNAL: Master version "1.9.6-gke.1" is unsupported. 你可以访问以下网页,了解当前支持的 GKE 版本:cloud.google.com/kubernetes-engine/release-notes

接下来,指定cluster-2

cluster="cluster-2"

现在,创建它,你会看到上面的消息。我们这次会省略这些:

gcloud container clusters create $cluster --zone $zone --username "admin" \
--cluster-version "1.10.6-gke.2" --machine-type "n1-standard-2" --image-type "COS" --disk-size "100" \
 --scopes gke-default \
 --num-nodes "4" --network "default" --enable-cloud-logging --enable-cloud-monitoring --enable-ip-alias --async

你会看到上面的相同消息。你可以通过点击+图标来创建另一个 Google Cloud Shell 窗口,并创建一些watch命令来查看集群的创建情况。在实例创建期间,花点时间这样做:

在该窗口中,执行以下命令:gcloud container clusters list。你应该会看到如下内容:

gcloud container clusters list
<snip>
Every 1.0s: gcloud container clusters list                                     cs-6000-devshell-vm-375db789-dcd6-42c6-b1a6-041afea68875: Mon Sep 3 12:26:41 2018

NAME       LOCATION MASTER_VERSION  MASTER_IP MACHINE_TYPE  NODE_VERSION NUM_NODES STATUS
cluster-1  us-east1-b 1.10.6-gke.2    35.237.54.93 n1-standard-2  1.10.6-gke.2 4 RUNNING
cluster-2  us-east1-b 1.10.6-gke.2    35.237.47.212 n1-standard-2  1.10.6-gke.2 4 RUNNING

在仪表板上,它会显示如下:

接下来,我们将获取集群凭据。此命令将允许我们为每个特定集群设置kubeconfig上下文:

for clusterid in cluster-1 cluster-2; do gcloud container clusters get-credentials $clusterid --zone $zone; done
Fetching cluster endpoint and auth data.
kubeconfig entry generated for cluster-1.
Fetching cluster endpoint and auth data.
kubeconfig entry generated for cluster-2.

让我们确保可以使用kubectl获取每个集群的上下文:

anonymuse@cloudshell:~ (gsw-k8s-3)$ kubectl config use-context "gke_${proj}_${zone}_cluster-1"
Switched to context "gke_gsw-k8s-3_us-east1-b_cluster-1".

如果在执行每个集群上下文切换后,你运行kubectl get pods --all-namespaces,你应该会看到每个集群类似的输出:

anonymuse@cloudshell:~ (gsw-k8s-3)$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system event-exporter-v0.2.1-5f5b89fcc8-2qj5c 2/2 Running 0 14m
kube-system fluentd-gcp-scaler-7c5db745fc-qxqd4 1/1 Running 0 13m
kube-system fluentd-gcp-v3.1.0-g5v24 2/2 Running 0 13m
kube-system fluentd-gcp-v3.1.0-qft92 2/2 Running 0 13m
kube-system fluentd-gcp-v3.1.0-v572p 2/2 Running 0 13m
kube-system fluentd-gcp-v3.1.0-z5wjs 2/2 Running 0 13m
kube-system heapster-v1.5.3-5c47587d4-4fsg6 3/3 Running 0 12m
kube-system kube-dns-788979dc8f-k5n8c 4/4 Running 0 13m
kube-system kube-dns-788979dc8f-ldxsw 4/4 Running 0 14m
kube-system kube-dns-autoscaler-79b4b844b9-rhxdt 1/1 Running 0 13m
kube-system kube-proxy-gke-cluster-1-default-pool-e320df41-4mnm 1/1 Running 0 13m
kube-system kube-proxy-gke-cluster-1-default-pool-e320df41-536s 1/1 Running 0 13m
kube-system kube-proxy-gke-cluster-1-default-pool-e320df41-9gqj 1/1 Running 0 13m
kube-system kube-proxy-gke-cluster-1-default-pool-e320df41-t4pg 1/1 Running 0 13m
kube-system l7-default-backend-5d5b9874d5-n44q7 1/1 Running 0 14m
kube-system metrics-server-v0.2.1-7486f5bd67-h9fq6 2/2 Running 0 13m

接下来,我们需要创建一个 Google Cloud 防火墙规则,以便每个集群能够相互通信。我们需要收集所有集群的网络数据(标签和 CIDR),然后使用 gcloud 创建防火墙规则。CIDR 范围大概是这样的:

anonymuse@cloudshell:~ (gsw-k8s-3)$ gcloud container clusters list --format='value(clusterIpv4Cidr)'
10.8.0.0/14
10.40.0.0/14

这些标签将是每个节点的,总共会有八个标签:

anonymuse@cloudshell:~ (gsw-k8s-3)$ gcloud compute instances list --format='value(tags.items.[0])'
gke-cluster-1-37037bd0-node
gke-cluster-1-37037bd0-node
gke-cluster-1-37037bd0-node
gke-cluster-1-37037bd0-node
gke-cluster-2-909a776f-node
gke-cluster-2-909a776f-node
gke-cluster-2-909a776f-node
gke-cluster-2-909a776f-node

现在让我们运行完整的命令来创建防火墙规则。注意 join_by 函数是一个巧妙的技巧,允许我们在 Bash 中将多个数组元素连接起来:

function join_by { local IFS="$1"; shift; echo "$*"; }
ALL_CLUSTER_CIDRS=$(gcloud container clusters list --format='value(clusterIpv4Cidr)' | sort | uniq)
echo $ALL_CLUSTER_CDIRS
ALL_CLUSTER_CIDRS=$(join_by , $(echo "${ALL_CLUSTER_CIDRS}"))
echo $ALL_CLUSTER_CDIRS
ALL_CLUSTER_NETTAGS=$(gcloud compute instances list --format='value(tags.items.[0])' | sort | uniq)
echo $ALL_CLUSTER_NETTAGS
ALL_CLUSTER_NETTAGS=$(join_by , $(echo "${ALL_CLUSTER_NETTAGS}"))
echo $ALL_CLUSTER_NETTAGS
gcloud compute firewall-rules create istio-multicluster-test-pods \
 --allow=tcp,udp,icmp,esp,ah,sctp \
 --direction=INGRESS \
 --priority=900 \
 --source-ranges="${ALL_CLUSTER_CIDRS}" \
 --target-tags="${ALL_CLUSTER_NETTAGS}" 

这将设置我们的安全防火墙规则,完成后在 GUI 中应该类似于这个样子:

让我们创建一个管理员角色,以便在未来的步骤中使用。首先,使用 KUBE_USER="<YOUR_EMAIL>" 设置 KUBE_USER 为与你的 GCP 账户相关联的电子邮件地址。接下来,我们将创建一个 clusterrolebinding

kubectl create clusterrolebinding gke-cluster-admin-binding \
 --clusterrole=cluster-admin \
 --user="${KUBE_USER}"
clusterrolebinding "gke-cluster-admin-binding" created

接下来,我们将使用 Helm 安装 Istio 控制平面,创建一个命名空间,并使用 chart 部署 Istio。

使用 kubectl config current-context 检查确保你正在使用 cluster-1 作为上下文。接下来,我们将使用以下命令安装 Helm:

curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get > get_helm.sh
 chmod 700 get_helm.sh
./get_helm.sh
Create a role for tiller to use. Youll need to clone the Istio repo first:
git clone https://github.com/istio/istio.git && cd istio
Now, create a service account for tiller.
kubectl apply -f install/kubernetes/helm/helm-service-account.yaml
And then we can intialize Tiller on the cluster.
/home/anonymuse/.helm
Creating /home/anonymuse/.helm/repository
...
To prevent this, run `helm init` with the --tiller-tls-verify flag.
For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation
Happy Helming!
anonymuse@cloudshell:~/istio (gsw-k8s-3)$

现在,切换到另一个 Istio 特定的上下文,我们将在其自己的命名空间中安装 Istio:

kubectl config use-context "gke_${proj}_${zone}_cluster-1"

将 Istio 的安装 chart 复制到我们的主目录:

helm template install/kubernetes/helm/istio --name istio --namespace istio-system > $HOME/istio_master.yaml

为其创建一个命名空间并进行安装,同时启用注入:

kubectl create ns istio-system \
 && kubectl apply -f $HOME/istio_master.yaml \
 && kubectl label namespace default istio-injection=enabled

我们现在将设置更多的环境变量,以便收集 pilot、statsD、policy 和 telemetry pod 的 IP 地址:

export PILOT_POD_IP=$(kubectl -n istio-system get pod -l istio=pilot -o jsonpath='{.items[0].status.podIP}')
export POLICY_POD_IP=$(kubectl -n istio-system get pod -l istio=mixer -o jsonpath='{.items[0].status.podIP}')
export STATSD_POD_IP=$(kubectl -n istio-system get pod -l istio=statsd-prom-bridge -o jsonpath='{.items[0].status.podIP}')
export TELEMETRY_POD_IP=$(kubectl -n istio-system get pod -l istio-mixer-type=telemetry -o jsonpath='{.items[0].status.podIP}')

现在我们可以为我们的远程集群 cluster-2 生成一个清单:

helm template install/kubernetes/helm/istio-remote --namespace istio-system \
 --name istio-remote \
 --set global.remotePilotAddress=${PILOT_POD_IP} \
 --set global.remotePolicyAddress=${POLICY_POD_IP} \
 --set global.remoteTelemetryAddress=${TELEMETRY_POD_IP} \
 --set global.proxy.envoyStatsd.enabled=true \
 --set global.proxy.envoyStatsd.host=${STATSD_POD_IP} > $HOME/istio-remote.yaml

现在,我们将在目标集群 cluster-2 上安装最小的 Istio 组件,并启用 sidecar 注入。按顺序运行以下命令:

kubectl config use-context "gke_${proj}_${zone}_cluster-2"
kubectl create ns istio-system
kubectl apply -f $HOME/istio-remote.yaml
kubectl label namespace default istio-injection=enabled

现在,我们将创建更多的框架来利用 Istio 的功能。我们需要创建一个文件,以便配置 kubeconfig 与 Istio 一起使用。首先,使用 cd 切换回主目录。--minify 标志将确保您仅看到与当前上下文相关的输出。现在,输入以下一组命令:

export WORK_DIR=$(pwd)
CLUSTER_NAME=$(kubectl config view --minify=true -o "jsonpath={.clusters[].name}")
CLUSTER_NAME="${CLUSTER_NAME##*_}"
export KUBECFG_FILE=${WORK_DIR}/${CLUSTER_NAME}
SERVER=$(kubectl config view --minify=true -o "jsonpath={.clusters[].cluster.server}")
NAMESPACE=istio-system
SERVICE_ACCOUNT=istio-multi
SECRET_NAME=$(kubectl get sa ${SERVICE_ACCOUNT} -n ${NAMESPACE} -o jsonpath='{.secrets[].name}')
CA_DATA=$(kubectl get secret ${SECRET_NAME} -n ${NAMESPACE} -o "jsonpath={.data['ca\.crt']}")
TOKEN=$(kubectl get secret ${SECRET_NAME} -n ${NAMESPACE} -o "jsonpath={.data['token']}" | base64 --decode)

使用以下 cat 命令创建一个文件。这将把这里的内容注入到位于 ~/${WORK_DIR}/{CLUSTER_NAME} 的文件中:

cat <<EOF > ${KUBECFG_FILE}
apiVersion: v1
clusters:
 - cluster:
 certificate-authority-data: ${CA_DATA}
 server: ${SERVER}
 name: ${CLUSTER_NAME}
contexts:
 - context:
 cluster: ${CLUSTER_NAME}
 user: ${CLUSTER_NAME}
 name: ${CLUSTER_NAME}
current-context: ${CLUSTER_NAME}
kind: Config
preferences: {}
users:
 - name: ${CLUSTER_NAME}
 user:
 token: ${TOKEN}
EOF

接下来,我们将创建一个 secret,使得在 cluster-1 上的 Istio 控制平面可以访问 cluster-2 上的 istio-pilot。切换回第一个集群,创建一个 Secret,并给它加上标签:

anonymuse@cloudshell:~ (gsw-k8s-3)$ kubectl config use-context gke_gsw-k8s-3_us-east1-b_cluster-1
Switched to context "gke_gsw-k8s-3_us-east1-b_cluster-1".
kubectl create secret generic ${CLUSTER_NAME} --from-file ${KUBECFG_FILE} -n ${NAMESPACE}
kubectl label secret ${CLUSTER_NAME} istio/multiCluster=true -n ${NAMESPACE}

完成这些任务后,让我们利用这些工具将 Google 的代码示例之一 bookinfo 部署到两个集群中。首先在第一个集群上运行:

kubectl config use-context "gke_${proj}_${zone}_cluster-1"
kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml
kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml
kubectl delete deployment reviews-v3

现在,为了将 bookinfo 部署到远程集群,创建一个名为 reviews-v3.yaml 的文件。文件内容可以在本章的代码库目录中找到:

##################################################################################################
# Ratings service
##################################################################################################
apiVersion: v1
kind: Service
metadata:
 name: ratings
 labels:
 app: ratings
spec:
 ports:
 - port: 9080
 name: http
---
##################################################################################################
# Reviews service
##################################################################################################
apiVersion: v1
kind: Service
metadata:
 name: reviews
 labels:
 app: reviews
spec:
 ports:
 - port: 9080
 name: http
 selector:
 app: reviews
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
 name: reviews-v3
spec:
 replicas: 1
 template:
 metadata:
 labels:
 app: reviews
 version: v3
 spec:
 containers:
 - name: reviews
 image: istio/examples-bookinfo-reviews-v3:1.5.0
 imagePullPolicy: IfNotPresent
 ports:
 - containerPort: 9080

让我们在远程集群 cluster-2 上安装这个部署:

kubectl config use-context "gke_${proj}_${zone}_cluster-2"
kubectl apply -f $HOME/reviews-v3.yaml

完成后,你需要获取 Istio 的isto-ingressgateway服务的外部 IP,以便查看bookinfo主页上的数据。你可以运行此命令来打开它。你需要多次重新加载该页面才能看到 Istio 的负载均衡生效。你可以按住F5键来多次重新加载页面。

你可以访问http://<GATEWAY_IP>/productpage来查看评论。

删除集群

为了在完成后清理控制面板,你可以运行以下命令。

首先,删除防火墙规则:

gcloud compute firewall-rules delete istio-multicluster-test-pods
The following firewalls will be deleted:
 - [istio-multicluster-test-pods]
Do you want to continue (Y/n)? y
Deleted [https://www.googleapis.com/compute/v1/projects/gsw-k8s-3/global/firewalls/istio-multicluster-test-pods].
anonymuse@cloudshell:~ (gsw-k8s-3)$

接下来,我们将删除我们的cluster-admin-rolebinding

anonymuse@cloudshell:~ (gsw-k8s-3)$ kubectl delete clusterrolebinding gke-cluster-admin-bindingclusterrolebinding "gke-cluster-admin-binding" deleted
anonymuse@cloudshell:~ (gsw-k8s-3)$

最后,让我们删除我们的 GKE 集群:

anonymuse@cloudshell:~ (gsw-k8s-3)$ gcloud container clusters delete cluster-1 --zone $zone
The following clusters will be deleted. - [cluster-1] in [us-east1-b]
Do you want to continue (Y/n)? y
Deleting cluster cluster-1...done.
Deleted [https://container.googleapis.com/v1/projects/gsw-k8s-3/zones/us-east1-b/clusters/cluster-1].
anonymuse@cloudshell:~ (gsw-k8s-3)

在 GUI 中,你可以看到集群正在被删除:

你也可以通过你的watch命令在命令行中看到它:

使用你的其他集群运行相同的命令。你可以再次检查计算引擎控制台,确保你的实例正在被删除:

总结

在本章中,我们了解了 Kubernetes 中的新联邦功能。我们看到了如何将集群部署到多个云提供商,并从单一的控制平面管理它们。我们还在 AWS 和 GCE 中跨集群部署了一个应用程序。尽管这些功能是新的,且主要还处于 Alpha 和 Beta 阶段,但我们现在应该具备利用它们的技能,随着它们的不断发展,它们将成为 Kubernetes 标准操作模型的一部分。

在下一章中,我们将探讨另一个高级话题:安全性。我们将介绍安全容器的基础知识以及如何保护你的 Kubernetes 集群。我们还将介绍 Secrets 构建块,它让我们能够存储敏感的配置数据,类似于我们之前的ConfigMap示例。

问题

  1. 联邦的主要目标是什么?

  2. 使用联邦的主要优势是什么?

  3. 联邦的构建块是什么?

  4. 控制联邦的 Kubernetes CLI 命令是什么?

  5. Kubernetes 联邦的两个软件组件是什么?

  6. HPA 和联邦 HPA 的主要区别是什么?

  7. 有哪些类型的联邦资源?

进一步阅读

如果你想了解更多关于掌握 Kubernetes 的信息,可以查看另一本优秀的 Packt 资源《Mastering Kubernetes》(www.packtpub.com/application-development/mastering-kubernetes-second-edition)。

第十三章:集群认证、授权和容器安全

本章将讨论容器安全的基础知识,从容器运行时级别到主机本身。我们将讨论如何将这些概念应用到 Kubernetes 集群中运行的工作负载,并探讨一些与运行 Kubernetes 集群相关的安全问题和实践。

本章将讨论以下主题:

  • 基本容器安全

  • 容器镜像安全与持续漏洞扫描

  • Kubernetes 集群安全

  • Kubernetes secrets

容器安全基础

容器安全是一个深奥的领域,本身就可以写成一本书。话虽如此,我们将覆盖一些高层次的关注点,并为你提供一个起点,让你开始思考这个领域。

第一章Kubernetes 简介中的容器简要概述部分,我们查看了 Linux 内核中一些核心的隔离功能,这些功能使容器技术成为可能。理解容器如何工作是掌握管理容器时各种安全问题的关键。

深入探讨的好资料是NCC 的白皮书理解与强化 Linux 容器。在第七部分,该论文探讨了容器部署中各种攻击向量的关注点,我将在此进行总结。

保持容器隔离

在前一节提到的论文中,讨论的最明显的一个特性是逃脱容器隔离/虚拟化的构造。现代容器实现通过使用命名空间来隔离进程,并允许控制容器可用的 Linux 功能,从而防止此类逃脱。此外,越来越多地采用对容器环境的安全默认配置。例如,Docker 默认只启用一小部分功能。网络也是逃脱的一个途径,由于大多数现代容器设置中有多种网络选项,这可能会非常具有挑战性。

论文中讨论的下一个领域是两个容器之间的攻击。用户命名空间模型在这里为我们提供了额外的保护,通过将容器内的 root 用户映射到主机机器上的低权限用户。网络仍然是一个问题,这需要在选择和实施容器网络解决方案时进行适当的谨慎和关注。

容器内部的攻击是另一种攻击向量,和之前的关注点一样,命名空间和网络在这里是保护的关键。另一个至关重要的方面是应用程序安全。代码仍然需要遵循安全编码实践,并且软件应该保持最新并定期打补丁。最后,容器镜像的高效性有一个附加好处——减少攻击面。镜像应当仅包含必要的包和软件。

资源耗尽和调度安全

类似于拒绝服务DoS)攻击,我们在计算领域的其他方面也见过资源耗尽的现象,这在容器世界中同样是一个重要问题。虽然 cgroups 在 CPU、内存和磁盘使用等资源上提供了一些限制,但资源耗尽仍然是一个有效的攻击途径。像 Docker 这样的工具提供了对 cgroups 限制的默认设置,而 Kubernetes 也提供了可以施加在集群中容器组上的额外限制。理解这些默认设置并根据你的部署进行调整非常重要。

虽然 Linux 内核和支持容器的特性为我们提供了一定程度的隔离,但它们对于 Linux 操作系统来说还是相对较新的功能。因此,它们仍然存在一些自身的漏洞和缺陷。内置的能力和命名空间机制可能存在问题,并且这些问题在进行安全容器操作时需要被追踪。

NCC 文档中最后提到的一个领域是容器管理层本身的攻击。Docker 引擎、镜像仓库和调度工具都是重要的攻击载体,在制定策略时应考虑到这些因素。我们将在接下来的章节中更深入地讨论如何解决仓库和 Kubernetes 作为调度层的问题。

如果你有兴趣了解更多关于 Docker 实现的具体安全特性,可以查看这里:docs.docker.com/engine/security/security/

镜像仓库

漏洞管理是任何现代 IT 操作的关键组成部分。零日漏洞在不断增加,即使是有补丁的漏洞,修复起来也可能相当繁琐。首先,应用程序的所有者必须了解他们的漏洞和潜在的修复措施。然后,这些补丁必须集成到系统和代码中,通常这需要额外的部署或维护窗口。即使有漏洞的可视化,修复通常也会滞后,通常需要大型组织几个月的时间才能进行修补。

尽管容器极大地改善了应用程序更新和减少停机时间的过程,但在漏洞管理方面仍然存在一个固有的挑战。特别是因为攻击者只需暴露一个这样的漏洞,因此任何不到 100%的系统修补都可能存在被攻破的风险。

所需的是一个更快速的反馈循环来解决漏洞问题。持续扫描并与软件部署生命周期紧密结合,是加速漏洞信息处理和修复的关键。幸运的是,这正是最新的容器管理和安全工具中正在构建的方法。

持续的漏洞扫描

一个在这一领域出现的开源项目是 clair。clair 是一个用于静态分析 appc (github.com/appc/spec) 和 Docker (github.com/moby/moby/blob/master/image/spec/v1.md) 容器漏洞的开源项目。

你可以通过以下链接访问 clair:github.com/coreos/clair

clair 会扫描你的代码以检查 常见漏洞和暴露 (CVE) 。它可以集成到你的 CI/CD 管道中,并在新的构建响应时运行。如果发现漏洞,可以作为反馈进入管道,甚至停止部署并使构建失败。这迫使开发人员在正常发布过程中意识到并修复漏洞。

clair 可以与多个容器镜像仓库和 CI/CD 管道集成。

clair 甚至可以在 Kubernetes 上部署:github.com/coreos/clair/blob/master/Documentation/running-clair.md#kubernetes-helm

clair 也被用作 CoreOS 的 Quay 镜像仓库中的扫描机制。Quay 提供了许多企业功能,包括持续漏洞扫描 (quay.io/)。

Docker Hub 和 Docker Cloud 都支持安全扫描。同样,推送到仓库的容器会自动扫描 CVE,并在发现漏洞时发送通知。此外,还会对代码进行二进制分析,匹配组件的签名与已知版本的签名。

还有许多其他扫描工具也可以用来扫描你的镜像仓库,包括 OpenSCAP、Twistlock、Aqua Sec 等等。

镜像签名与验证

无论你是在内部使用私有镜像仓库,还是使用像 Docker Hub 这样的公共仓库,了解你只运行开发人员编写的代码非常重要。恶意代码或中间人攻击下载的潜在风险,是保护容器镜像时必须考虑的重要因素。

因此,rkt 和 Docker 都支持签名镜像的能力,并验证内容未发生变化。发布者可以在将镜像推送到仓库时使用密钥进行签名,用户则可以在客户端下载时验证客户端的签名。

这是来自 rkt 文档的内容:

"在执行远程获取的 ACI 之前,rkt 将根据 ACI 创建者生成的附加签名对其进行验证。

欲了解更多信息,请访问以下链接:

以下内容摘自 Docker 文档:

“内容信任使你能够验证通过任何通道从注册中心接收到的所有数据的完整性和发布者。”

欲了解更多信息,请访问docs.docker.com/engine/security/trust/content_trust/ 以下内容摘自 Docker Notary GitHub 页面:

“Notary 项目包括一个服务器和一个客户端,用于运行和与受信的集合进行交互。”

欲了解更多信息,请访问 github.com/docker/notary

Kubernetes 集群安全

Kubernetes 在其最新版本中继续增加了许多安全功能,并拥有一套完善的控制点,可以在你的集群中使用——从安全节点通信到 pod 安全,甚至敏感配置数据的存储。

安全 API 调用

每次 API 调用时,Kubernetes 都会应用一系列安全控制。这个安全生命周期如下图所示:

API 调用生命周期

在建立安全的 TLS 通信后,API 服务器会经过授权和身份验证。最后,申请会在到达 API 服务器之前,经过一个入站控制器循环处理。

安全节点通信

Kubernetes 支持在 API 服务器和任何客户端之间使用安全通信通道,包括节点本身。无论是图形界面还是像 kubectl 这样的命令行工具,我们都可以使用证书与 API 服务器进行通信。因此,API 服务器是集群中任何变更的核心交互点,必须加以保护。

在 GCE 等部署环境中,每个节点上的 kubelet 默认会进行安全通信部署。此设置使用 TLS 引导和新的证书 API 来通过 TLS 客户端证书与 API 服务器建立安全连接,并使用 证书颁发机构 (CA) 集群。

授权和身份验证插件

Kubernetes 中用于身份验证和授权的插件机制仍在开发中。它们已经取得了很大进展,但仍有一些插件处于测试阶段,且增强功能仍在开发中。这里还有一些与这些功能集成的第三方提供商,因此在构建强化策略时请考虑到这一点。

目前,认证支持令牌、密码和证书形式,并计划在后续阶段添加插件功能。支持 OpenID Connect 令牌,并且提供了几个第三方实现,如 CoreOS 的 Dex 和 Cloud Foundry 的用户帐户和认证。

授权目前已经支持三种模式。基于角色的访问控制 (RBAC) 模式在 1.8 版本中已正式发布,并将基于角色的标准认证模型引入 Kubernetes。基于属性的访问控制 (ABAC) 长期以来一直得到支持,并允许用户通过文件中的属性定义权限。

此外,还支持 Webhook 机制,它允许通过 REST Web 服务调用与第三方授权进行集成。最后,我们还提供了新的节点授权方法,根据节点运行的 Pods 授予 kubelet 权限。

您可以通过以下链接了解更多关于各个领域的内容:

准入控制器

Kubernetes 还提供了一种机制来集成,并以额外的验证作为最后一步。这可以采取镜像扫描、签名检查或任何能够以指定方式响应的形式。

当 API 调用被发出时,钩子会被调用,服务器可以进行验证。准入控制器还可以用于转换请求,并添加或更改原始请求。一旦操作执行完毕,便会发送一个响应,指示 Kubernetes 允许或拒绝该调用。

如前一节所提到的,这对于验证或测试镜像尤其有帮助。ImagePolicyWebhook 插件提供了一种准入控制器,允许与额外的镜像检查进行集成。

欲了解更多信息,请访问以下文档中的“使用准入控制器”页面:kubernetes.io/docs/admin/admission-controllers/

RBAC

如本章前面提到的,Kubernetes 现在已将 RBAC 作为集群内授权的核心组件。Kubernetes 为这种控制提供了两种级别。首先是 ClusterRole,它提供对集群范围内资源的授权。这对于跨多个团队、产品或集群范围的资源(如底层集群节点)执行访问控制非常有用。其次是 Role,它仅提供对特定命名空间内资源的访问。

一旦你有了一个角色,你需要一种方式来为用户提供该角色的成员身份。这些被称为绑定,我们有ClusterRoleBindingRoleBinding。与角色本身一样,前者用于集群范围的访问,而后者则用于特定命名空间内的应用。

本书不会深入讲解 RBAC 的细节,但这是你在准备生产环境部署时需要探索的内容。下一节中讨论的PodSecurityPolicy通常利用 Roles 和 RoleBindings 来控制每个用户可以访问哪些策略。

有关更多信息,请参阅此文档:kubernetes.io/docs/reference/access-authn-authz/rbac/

Pod 安全策略和上下文

Kubernetes 安全工具包中的最新功能之一是 Pod 安全策略和上下文。这些功能允许用户控制容器进程和附加卷的用户和组,限制使用主机网络或命名空间,甚至将根文件系统设置为只读。此外,我们还可以限制可用的功能,并为每个 Pod 中容器应用的标签设置 SELinux 选项。

除了 SELinux,Kubernetes 还通过注释增加了对在 Pod 中使用 AppArmor 的 beta 支持。有关更多信息,请参考以下文档页面:kubernetes.io/docs/admin/apparmor/

PodSecurityPolicies 是通过我们在本书中早些时候提到的准入控制器来强制执行的。默认情况下,Kubernetes 并没有启用 PodSecurityPolicy,因此,如果你有一个正在运行的 GKE 集群,你可以尝试以下操作:

$ kubectl get psp

你应该看到'No resources found.',假设你还没有启用它们。

让我们通过使用前几章中的 Docker 镜像来尝试一个示例。如果我们在没有应用 PodSecurityPolicy 的集群上使用以下run命令,它将顺利运行:

$ kubectl run myroottest --image=jonbaier/node-express-info:latest

接下来,使用kubectl get pods命令,过一分钟左右,我们应该能在列表中看到以myroottest开头的 Pod。

在继续之前,请使用以下代码清理一下:

$ kubectl delete deployment myroottest 

启用 PodSecurityPolicies

现在,让我们在一个可以利用 PodSecurityPolicies 的集群上尝试。如果你使用的是 GKE,创建一个启用了 PodSecurityPolicy 的集群非常简单。请注意,你需要启用 Beta API:

$ gcloud beta container clusters create [Cluster Name] --enable-pod-security-policy --zone=[Zone To Deply Cluster]

如果你有一个现有的 GKE 集群,你可以使用类似于前面命令的方式启用它。只需将create关键字替换为update

对于使用 kube-up 创建的集群,像我们在第一章《Kubernetes 入门》中看到的那样,Kubernetes,您需要在 API 服务器上启用准入控制器。查看此处以获取更多信息:kubernetes.io/docs/concepts/policy/pod-security-policy/#enabling-pod-security-policies

一旦启用 PodSecurityPolicy,您可以使用以下代码查看已应用的策略:

$ kubectl get psp

GKE 默认的 Pod 安全策略

您会注意到 GKE 已经定义了一些预定义的策略。您可以使用以下代码探索这些策略的详细信息及其用于创建这些策略的 YAML 文件:

$ kubectl get psp/[PSP Name] -o yaml

需要注意的是,PodSecurityPolicies 与 Kubernetes 的 RBAC 功能协同工作。GKE 定义了一些默认角色、角色绑定和命名空间。因此,我们在与 Kubernetes 交互时会看到不同的行为。例如,在 GCloud Shell 中使用kubectl时,您可能以集群管理员身份发送命令,因此可以访问所有策略,包括 gce.privileged。然而,使用之前的kubectl run命令会通过 kube-controller-manager 调用 Pods,这将受到绑定到其角色的策略的限制。因此,如果您仅使用 kubectl 创建一个 Pod,它将顺利创建,但通过使用 run 命令时,我们将受到限制。

继续使用我们之前的 kubectl run 方法,让我们尝试与前述相同的部署:

$ kubectl run myroottest --image=jonbaier/node-express-info:latest

现在,如果我们接着使用kubectl get pods,将不会看到任何以myroottest为前缀的 Pods。我们可以通过描述我们的部署进一步调查:

$ kubectl describe deployment myroottest

通过使用前述命令输出中列出的副本集名称,我们可以获取失败的详细信息。运行以下命令:

$ kubectl describe rs [ReplicaSet name from deployment describe]

在底部的事件下,您将看到以下 Pod 安全策略验证错误:

Replica set Pod 安全策略验证错误

再次强调,因为run命令使用了控制器管理器,而该角色没有绑定允许使用现有PodSecurityPolicies,所以我们无法运行任何 Pods。

理解安全地运行容器不仅仅是管理员添加约束的任务非常重要。必须与开发人员合作,确保他们正确地创建镜像。

您可以在源代码中找到所有 PodSecurityPolicies 的可能参数,但为了方便起见,我已创建以下表格。您可以在我的新站点 www.kubesheets.com 上找到更多这样的实用查询:

参数 类型 描述 是否必需
Privileged bool 允许或禁止以特权模式运行 Pod。
DefaultAddCapabilities []v1.Capaility 定义一个默认的能力集,这些能力会添加到容器中。如果 pod 指定了丢弃的能力集,这里添加的能力将被覆盖。值是 POSIX 能力的字符串,去掉前缀 CAP_。例如,CAP_SETUID 应为 SETUID (man7.org/linux/man-pages/man7/capabilities.7.html)。
RequiredDropCapabilities []v1.Capaility 定义一个必须从容器中丢弃的能力集。Pod 不能指定这些能力中的任何一个。值是 POSIX 能力的字符串,去掉前缀 CAP_。例如,CAP_SETUID 应为 SETUID (man7.org/linux/man-pages/man7/capabilities.7.html)。
AllowedCapabilities []v1.Capaility 定义一个允许的能力集,可以添加到容器中。Pod 可以指定这些能力中的任何一个。值是 POSIX 能力的字符串,去掉前缀 CAP_。例如,CAP_SETUID 应为 SETUID (man7.org/linux/man-pages/man7/capabilities.7.html)。
Volumes []string 此列表定义可以使用哪些卷。若要支持所有类型,请保持为空 (github.com/kubernetes/kubernetes/blob/release-1.5/pkg/apis/extensions/v1beta1/types.go#L1127)。
HostNetwork bool 允许或不允许 pod 使用主机网络。
HostPorts []HostPortRange 允许我们限制可以暴露的主机端口。
HostPID bool 允许或不允许 pod 使用主机 PID。
HostIPC bool 允许或不允许 pod 使用主机 IPC。
SELinux SELinuxStrategyOptions 将其设置为以下策略选项之一,如此处定义:kubernetes.io/docs/concepts/policy/pod-security-policy/#selinux
RunAsUser RunAsUserStrategyOptions 将其设置为以下策略选项之一,如此处定义:kubernetes.io/docs/concepts/policy/pod-security-policy/#users-and-groups
SupplementalGroups SupplementalGroupsStrategyOptions 将其设置为以下策略选项之一,如此处定义:kubernetes.io/docs/concepts/policy/pod-security-policy/#users-and-groups
FSGroup FSGroupStrategyOptions 将其设置为此处定义的策略选项之一:kubernetes.io/docs/user-guide/pod-security-policy/#strategies
ReadOnlyRootFilesystem bool 将此设置为 true 将拒绝该 Pod 或强制其以只读根文件系统运行。
allowedHostPaths []AllowedHostPath 提供可以在卷中使用的主机路径的白名单。
allowedFlexVolumes []AllowedFlexVolume 提供可以挂载的 Flex Volumes 白名单。
allowPrivilegeEscalation bool 控制是否可以使用 setuid 来改变进程运行的用户。默认值为 true
defaultAllowPrivilegeEscalation bool 设置 allowPrivilegeEscalation 的默认值。

其他考虑事项

除了我们刚刚回顾的功能外,Kubernetes 还有许多其他构造体应纳入整个集群硬化过程。在本书早些时候,我们介绍了命名空间,它为多租户提供了逻辑隔离。虽然命名空间本身并不隔离实际的网络流量,但一些网络插件(如 Calico 和 Canal)为网络策略提供了额外的功能。我们还介绍了可以为每个命名空间设置的配额和限制,这些配额和限制应该用于防止单个租户或项目在集群中消耗过多资源。

保护敏感应用数据(Secrets)

有时,我们的应用程序需要保存敏感信息。这些信息可能是登录数据库或服务的凭证或令牌。将这些敏感信息存储在镜像本身中是需要避免的。在这里,Kubernetes 提供了一个通过 Secrets 进行管理的解决方案。

Secrets 为我们提供了一种存储敏感信息的方法,避免将明文版本包含在资源定义文件中。Secrets 可以挂载到需要它们的 Pod 上,然后在 Pod 内部作为文件访问,文件内容为敏感值。或者,你也可以通过环境变量暴露这些 secrets。

由于 Kubernetes 仍然依赖于明文存储在 etcd 中,你可能希望探索与更成熟的秘密存储库(如 Hashicorp 的 Vault)的集成。甚至有一个 GitHub 项目用于集成:github.com/Boostport/kubernetes-vault

我们可以轻松地通过 YAML 或命令行创建一个 secret。Secrets 需要进行 base64 编码,但如果我们使用 kubectl 命令行工具,这一编码过程会为我们自动完成。

让我们从以下 Secret 开始:

$ kubectl create secret generic secret-phrases --from-literal=quiet-phrase="Shh! Dont' tell"

我们可以使用以下命令检查 secret:

$ kubectl get secrets

现在我们已经成功创建了机密,接下来让我们创建一个可以使用该机密的 Pod。机密在 Pod 中通过附加的卷进行消费。在以下的secret-pod.yaml文件中,您会注意到我们使用volumeMount将机密挂载到容器中的一个文件夹:

apiVersion: v1
kind: Pod
metadata:
  name: secret-pod
spec:
  containers:
  - name: secret-pod
    image: jonbaier/node-express-info:latest
    ports:
    - containerPort: 80
      name: web
    volumeMounts:
      - name: secret-volume
        mountPath: /etc/secret-phrases
  volumes:
  - name: secret-volume
    secret:
      secretName: secret-phrases

使用kubectl create -f secret-pod.yaml创建此 Pod。创建后,我们可以通过kubectl exec进入 Pod 并更改目录到我们在 Pod 定义中设置的/etc/secret-phrases文件夹。列出该目录会显示一个文件,文件名是我们之前创建的机密的名称:

$ kubectl exec -it secret-pod bash
$ cd /etc/secret-phrases
$ ls

如果我们显示其内容,应该看到我们之前编码的短语,Shh! Dont' tell

$ cat quiet-phrase

通常,这会用于数据库或服务的用户名和密码,或任何敏感的凭据和配置信息。

请记住,机密信息仍处于初期阶段,但它们是生产操作中至关重要的组成部分。未来版本计划进行多项改进。目前,机密信息仍然以明文形式存储在 etcd 服务器中。然而,机密结构确实允许我们控制哪些 Pods 可以访问它,并且它将信息存储在 tmpfs 中,但不会为每个 Pod 存储静态数据。您可以限制访问 etcd 的用户,并在停用服务器时执行额外的清除操作,但对于准备投入生产的系统,您可能需要更多的保护措施。

总结

在本章中,我们回顾了基本的容器安全性和一些关键的考虑领域。我们还简单讨论了基本的镜像安全性和持续的漏洞扫描。随后,我们探讨了 Kubernetes 的整体安全特性,包括存储敏感配置信息的机密、API 调用的安全性,甚至为在集群中运行的 Pods 设置安全策略和上下文。

现在,您应该有一个坚实的起点来保护您的集群并朝着生产环境迈进。为此,下一章将介绍朝生产环境迈进的整体策略,并将讨论一些第三方供应商提供的工具,以填补空白并在过程中提供帮助。

问题

  • 哪个组件可以作为管理的中心点,并防止漏洞被释放?

  • 在 Kubernetes 集群中,有哪三种授权方法?

  • PodSecurityPolicy 中的哪个参数禁止运行特权容器?

  • 如何列出您在集群中有权访问的所有机密信息?

深入阅读

第十四章:加固 Kubernetes

本章我们将讨论迁移到生产环境时的注意事项。我们还将向你展示一些有用的工具和第三方项目,这些工具和项目在整个 Kubernetes 社区中可用,并告诉你可以去哪里获得更多帮助。

本章将讨论以下主题:

  • 生产特性

  • 从 Kubernetes 生产环境中学到的经验

  • 加固集群

  • Kubernetes 生态系统

  • 哪里可以获得帮助?

准备好进入生产环境

到目前为止,本书我们已经走过了许多使用 Kubernetes 的典型操作。如我们所见,K8s 提供了多种特性和抽象,简化了容器部署日常管理的负担。

有许多特性定义了适用于容器的生产就绪系统。下图提供了生产就绪集群的主要关注点的高层次视图。这并不是一个详尽无遗的列表,但它旨在为进入生产操作提供一些可靠的基础:

容器操作的生产特性

我们已经看到 Kubernetes 的核心概念和抽象如何解决其中的一些问题。服务抽象提供了内建的服务发现和健康检查功能,既适用于服务层,也适用于应用层。我们还通过复制控制器和部署构造获得了无缝的应用更新和可扩展性。所有的核心抽象,如服务、复制控制器、副本集和 Pod,都与核心调度和亲和规则集协同工作,提供了简便的服务和应用组合。

Kubernetes 内建支持多种持久存储选项,网络模型提供了可管理的网络操作,并可以与其他第三方提供商合作。我们还简要了解了与一些市场上流行工具的 CI/CD 集成。

此外,我们还内建了系统事件跟踪,并且与主要的云服务提供商一起,提供了开箱即用的监控和日志记录设置。我们还看到如何将其扩展到第三方提供商,如 Stackdriver 和 Sysdig。这些服务还涵盖了节点的整体健康状况和主动的趋势偏离警报。

核心构造还帮助我们解决了应用和服务层的高可用性问题。调度器可以与自动扩展机制一起使用,在节点级别提供这一功能。然后,还支持使 Kubernetes 主节点本身实现高可用性。在第十二章《集群联邦与多租户》中,我们简要回顾了新联邦功能,这些功能预示着未来的多云和多数据中心模型。

最后,我们探索了一种新的操作系统,它为我们提供了一个轻量的基础,可以构建在其上,并提供安全的更新机制用于修补和更新。这个轻量的基础与调度相结合,可以帮助我们实现高效的资源利用。此外,我们还关注了一些强化的安全问题,并探索了可用的镜像信任和验证工具。安全性是一个广泛的话题,针对这个话题本身就有能力矩阵。

准备好,开始吧

尽管仍然存在一些漏洞,但剩余的各种安全和操作问题正在被第三方公司积极解决,正如我们将在接下来的章节中看到的那样。展望未来,Kubernetes 项目将继续发展,围绕 K8s 和 Docker 的项目和合作伙伴社区也将不断壮大。社区正在以惊人的速度弥补剩余的空白。

从生产中获得的经验教训

Kubernetes 已经存在足够长的时间,现在有很多公司在运行 Kubernetes。在我们的日常工作中,我们看到 Kubernetes 在多个行业的生产环境中运行,并且在各种配置下使用。让我们来探索一下业界在提供面向客户的工作负载时的做法。从一个高层次的角度来看,有几个关键领域:

  • 确保在集群中设置限制。

  • 为你的应用程序使用适当的工作负载类型。

  • 给所有东西打标签!标签非常灵活,可以包含很多信息,帮助识别对象、路由流量或决定放置位置。

  • 不要使用默认值。

  • 调整 Kubernetes 核心组件的默认值。

  • 使用负载均衡器,而不是直接在节点的端口上暴露服务。

  • 构建你的基础设施即代码,并使用诸如 CloudFormation 或 Terraform 之类的预配置工具,以及 Chef、Ansible 或 Puppet 之类的配置工具。

  • 在你还没有建立 Kubernetes 的专业知识之前,考虑不要在生产集群中运行有状态的工作负载。

  • 调查更高功能的模板语言,以维护集群的状态。在接下来的章节中,我们将探索几种不可变基础设施的选项。

  • 在可能的情况下,使用 RBAC、最小特权原则和关注点分离。

  • 所有集群间的通信都使用启用 TLS 的方式。你可以为集群中的kubelet通信设置 TLS 和证书轮换。

  • 在你还不熟悉 Kubernetes 的管理之前,构建大量的小型集群。这虽然带来更多的操作开销,但它能帮助你更快地进入深水区,看到更多的失败,体验更多的运维负担。

  • 随着你对 Kubernetes 的熟练掌握,构建更大的集群,使用命名空间、网络分段和授权功能将集群拆分成多个部分。

  • 一旦你管理了几个大型集群,就使用kubefed进行管理。

  • 如果可能,使用云服务提供商内建的高可用性功能,在 Kubernetes 平台上运行。例如,在 GCP 上使用 GKE 运行区域集群。此功能将你的节点分布在区域中的多个可用区。这使得系统能够抵御单一区域的故障,并为主节点的零停机时间升级提供概念性的构建模块。

在接下来的部分,我们将更详细地探讨这些概念之一——限制。

设置限制

如果你之前做过容器相关的工作,你会知道,设置容器资源限制是容器管理中最先和最容易设置的事项之一,通常形式为以下几种指标:

  • CPU

  • 内存

  • 请求

你可能熟悉使用 Docker 的 CLI 来设置资源的运行时限制,指定标志以限制这些项目及其他:

docker run --it --cpu-rt-runtime=950000 \
 --ulimit rtprio=99 \
 --memory=1024m \
 --cpus=".5"
 alpine /bin/sh

在这里,我们设置了一个运行时参数,创建了一个ulimit,并设置了内存和 CPU 配额。在 Kubernetes 中,故事稍有变化,因为你可以将这些限制应用于特定的命名空间,这使你可以根据集群的域来定义你的限制。你有四个主要的参数来在 Kubernetes 中设置资源限制:

spec.containers[].resources.limits.cpu
spec.containers[].resources.requests.cpu
spec.containers[].resources.limits.memory
spec.containers[].resources.requests.memory

调度限制

当你创建一个带有内存限制的 Pod 时,Kubernetes 会寻找一个带有正确标签和选择器的节点,并确保该节点拥有 Pod 所需的足够资源类型,包括 CPU 和内存。Kubernetes 负责确保一个节点上所有 Pod 的总内存请求不小于该节点的总资源。这有时会导致意想不到的结果,因为即使 Pod 的实际利用率较低,节点的容量限制也可能已经达到。这是系统的设计方式,旨在适应不同的负载水平。

你可以查看 Pod 日志,找出何时发生了这个问题:

$ kubectl describe pod web| grep -C 3 Events
Events:
FirstSeen LastSeen Count From Subobject PathReason Message
74s 15s 2 {scheduler } FailedScheduling Failed for reason PodExceedsFreeCPU and possibly others

你可以通过删除不需要的 Pod,确保你的 Pod 整体大小不超过任何一个可用节点,或者简单地为集群添加更多资源来解决这些问题。

内存限制示例

让我们通过一个例子来演示。首先,我们将创建一个命名空间来容纳我们的内存限制:

master $ kubectl create namespace low-memory-area
namespace "low-memory-area" created

一旦我们创建了命名空间,我们可以创建一个设置LimitRange对象的文件,这将允许我们强制执行内存限制和请求的默认值。创建一个名为memory-default.yaml的文件,内容如下:

apiVersion: v1
kind: LimitRange
metadata:
 name: mem-limit-range
spec:
 limits:
 - default:
     memory: 512Mi
   defaultRequest:
     memory: 256Mi
   type: Container

现在,我们可以在该命名空间中创建它:

master $ kubectl create -f test.ym --namespace=low-memory-area
limitrange "mem-limit-range" created

让我们在低内存区域命名空间中创建一个没有内存限制的 Pod,看看会发生什么。

创建以下 low-memory-pod.yaml 文件:

apiVersion: v1
kind: Pod
metadata:
 name: low-mem-demo
spec:
 containers:
 - name: low-mem-demo
   image: redis

然后,我们可以使用这个命令来创建 Pod:

kubectl create -f low-memory-pod.yaml --namespace=low-memory-area
pod "low-mem-demo" created

让我们看看是否已经将我们的资源限制添加到容器的 Pod 配置中,而无需在 Pod 配置中显式指定。注意内存限制已经生效!我们已删除了一些信息输出以提高可读性:

kubectl get pod low-mem-demo --output=yaml --namespace=low-memory-area

以下是前面代码的输出:

apiVersion: v1
kind: Pod
metadata:
 annotations:
 kubernetes.io/limit-ranger: 'LimitRanger plugin set: memory request for container
 low-mem-demo; memory limit for container low-mem-demo'
 creationTimestamp: 2018-09-20T01:41:40Z
 name: low-mem-demo
 namespace: low-memory-area
 resourceVersion: "1132"
 selfLink: /api/v1/namespaces/low-memory-area/pods/low-mem-demo
 uid: 52610141-bc76-11e8-a910-0242ac11006a
spec:
 containers:
 - image: redis
 imagePullPolicy: Always
 name: low-mem-demo
 resources:
 limits:
 memory: 512Mi
 requests:
 memory: 256Mi
 terminationMessagePath: /dev/termination-log
 terminationMessagePolicy: File
 volumeMounts:
 - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
 name: default-token-t6xqm
 readOnly: true
 dnsPolicy: ClusterFirst
 nodeName: node01
 restartPolicy: Always
 schedulerName: default-scheduler
 securityContext: {}
 serviceAccount: default
 serviceAccountName: default
 terminationGracePeriodSeconds: 30
 tolerations:
 - effect: NoExecute
 key: node.kubernetes.io/not-ready
 operator: Exists
 tolerationSeconds: 300
 - effect: NoExecute
 key: node.kubernetes.io/unreachable
 operator: Exists
 tolerationSeconds: 300
 volumes:
 - name: default-token-t6xqm
 secret:
 defaultMode: 420
 secretName: default-token-t6xqm
 hostIP: 172.17.1.21
 phase: Running
 podIP: 10.32.0.3
 qosClass: Burstable
 startTime: 2018-09-20T01:41:40Z

你可以使用以下命令删除 Pod:

Kubectl delete pod low-mem-demo --namespace=low-memory-area
pod "low-mem-demo" delete

配置资源限制有很多选项。如果你创建了内存限制,但没有指定默认请求,则请求将设置为最大可用内存,这将与内存限制相对应。看起来会像下面这样:

resources:
 limits:
   memory: 4096m
 requests:
   memory: 4096m

在具有多样化工作负载和基于 API 的关系的集群中,为你的容器和相应应用程序设置内存限制非常重要,以防止不当行为的应用程序干扰集群。服务之间不会隐式地了解彼此,因此如果你没有正确配置限制,它们非常容易因资源耗尽而出现问题。

调度 CPU 约束

让我们来看另一种资源管理类型,即约束。这里我们使用 CPU 维度,探讨如何为命名空间中的给定容器和 Pod 设置可用资源的最大值和最小值。你可能想在 Kubernetes 集群上限制 CPU 的原因有很多:

  • 如果你有一个命名空间集群,里面有不同级别的生产和非生产工作负载,你可能想为生产工作负载指定更高的资源限制。你可以允许生产环境使用四核 CPU;将开发、预发布或 UAT 类型的工作负载限制为单个 CPU;或者根据环境需求错开它们。

  • 你还可以禁止请求比节点可用 CPU 资源更多的 Pod。如果你在云服务提供商上运行某种类型的机器,你可以确保需要 X 核心的工作负载不会被调度到小于 X 核心的机器上。

CPU 约束示例

让我们继续创建另一个命名空间来容纳我们的示例:

kubectl create namespace cpu-low-area

现在,让我们为 CPU 约束设置一个 LimitRange,它使用毫核(millicpus)作为度量单位。如果你请求 500 m,那意味着你请求的是 500 毫核或毫 CPU,等同于符号表示法中的 0.5。当你请求 0.5 或 500 m 时,意味着你请求了平台提供的任何形式的半个 CPU(vCPU、核心、超线程、vCore 或 vCPU)。

就像我们之前做的那样,让我们为 CPU 约束创建一个 LimitRange

apiVersion: v1
kind: LimitRange
metadata:
 name: cpu-demo-range
spec:
 limits:
 - max:
     cpu: "500m"
   min:
     cpu: "300m"
   type: Container

现在,我们可以创建 LimitRange

kubectl create -f cpu-constraint.yaml --namespace=cpu-low-area

一旦我们创建了 LimitRange,就可以检查它了。你会注意到,defaultRequest 被指定为与最大值相同,因为我们没有特别指定。Kubernetes 会将 defaultRequest 设置为最大值:

kubectl get limitrange cpu-demo-range --output=yaml --namespace=cpu-low-area

limits:
- default:
 cpu: 500m
 defaultRequest:
 cpu: 500m
 max:
 cpu: 500m
 min:
 cpu: 300m
 type: Container

这是预期的行为。当在此命名空间中进一步调度容器时,Kubernetes 会首先检查 Pod 是否指定了请求和限制。如果没有,它会应用默认值。接着,控制器会确认 CPU 请求是否超过 LimitRange 中的下限,即 300 m。此外,它还会检查上限,以确保对象的请求不超过 500 m。

你可以通过查看 Pod 的 YAML 输出再次检查容器约束:

kubectl get pod cpu-demo-range --output=yaml --namespace=cpu-low-area

resources:
 limits:
 cpu: 500m
 requests:
 cpu: 300m

现在,别忘了删除这个 Pod:

kubectl delete pod cpu-demo-range --namespace=cpu-low-area

集群安全性

让我们看一下在生产中加强集群安全的其他常见建议。这些使用案例涵盖了对集群的有意恶意攻击以及意外误用。让我们看看如何保护集群。

首先,你需要确保对 Kubernetes API 的访问是受控的。鉴于 Kubernetes 中的所有操作都是通过 API 驱动的,我们应该首先保护这个接口。我们可以通过几个设置来控制对该 API 的访问:

  • 加密所有流量:为了保持通信安全,你应该确保为集群中的 API 通信设置传输层安全性TLS)。我们在本书中回顾的多数安装方法都会创建必要的组件证书,但始终由集群操作员负责识别所有可能没有使用更安全设置的本地端口。

  • 认证你的访问:就像任何大型计算机系统一样,你需要确保用户的身份已经建立。对于小型集群,你可以使用证书或令牌,而对于更大的生产集群,则应使用 OpenID 或 LDAP。

  • 控制访问权限:在你确定了访问 API 角色的身份后,你需要确保通过 Kubernetes 内置的基于角色的访问控制 (RBAC) 过滤器来传递已认证的访问请求,它帮助操作员通过角色和用户限制控制和访问。有两个授权插件可供使用,分别是节点插件和 RBAC 插件,以及NodeRestriction准入插件。需要记住的一个关键点是,随着集群规模的增大,角色粒度应该增大,特别是从非生产环境到生产环境时。

默认情况下,kubelet的身份验证是关闭的。你可以通过开启证书轮换来启用kubelet的授权/身份验证功能。

你可以在这里阅读更多关于证书轮换的内容:kubernetes.io/docs/reference/command-line-tools-reference/kubelet-authentication-authorization/

我们还可以在运行时修改 Kubernetes 的使用方式:

  • 长期在生产环境中使用 Kubernetes 的操作员会将其视为我们之前关于限制和策略讨论的参考点。如前所述,资源配额限制了在命名空间中提供的资源数量,而限制范围则约束了资源的最小和最大尺寸。

  • 你可以通过定义安全上下文来确定 Pod 的权限。在这里,你可以指定像特定的 Linux 用户、组、卷挂载访问、是否允许权限提升等设置。

  • 你还可以通过使用网络策略来限制对集群逻辑分区的访问。你可以确保某些命名空间对用户不可访问,或者决定用户是否能够设置带有特定负载均衡器配置的服务,或在主机节点上开放端口。

虽然前述的模式对于 Kubernetes 内部的操作非常有用,但在从外部角度保护集群时,你还应该采取一些额外的措施:

  • 首先,启用并监控日志!虽然这看起来是显而易见的,但我们常常看到很多问题来自于没有监控日志,或者没有根据日志设置警报的人。另一个提示:不要将日志存储在集群内部!如果你的集群被攻破,日志将成为恶意代理的重要信息来源。

  • 确保限制对 etcd 集群的访问。这可以通过设置安全组或防火墙来实现,并确保 etcd 集群节点从基础设施角度具有适当的身份和访问管理。从集群角度来看,确保始终使用 TLS 证书进行身份验证,并使用强身份凭证。在任何情况下,集群中的任何组件都不应具有对整个 etcd 键值空间的读写权限。

  • 确保审核 alpha/beta 组件并检查第三方集成。确保你了解启用它时在使用什么,并且知道它启用后会做什么!新兴功能或第三方工具可能会创建攻击面或威胁模型,而你未必了解它们的依赖关系。特别要小心需要在 kube-system 内部执行工作的工具,因为它是集群中特别强大的部分。

  • 在 etcd 中加密你的机密数据。这是对任何计算机系统的良好建议,Kubernetes 也不例外。备份同样需要加密,以确保攻击者无法通过检查这些资源来获取集群访问权限。

对于你的生产集群,你还应该做一些事情,如扫描容器镜像、运行 YAML 文件的静态分析、尽可能以非 root 用户身份运行容器,并运行入侵检测系统IDS)。当你完成这些设置后,就可以开始探索外部服务网格的功能能力。

第三方公司

自 Kubernetes 项目初始发布以来,合作伙伴的生态系统逐渐壮大。我们在前几章中已经看到了 CoreOS、Sysdig 和许多其他公司,但在这个领域中还有许多不同的项目和公司。我们将重点介绍一些可能在你向生产环境过渡时有用的内容。这绝不是一份详尽的列表,仅仅是为了提供一些有趣的起点。

私有注册表

在许多情况下,组织不希望将其应用程序和/或知识产权放入公共仓库。对于这种情况,私有仓库解决方案可以帮助安全地将部署整合到底端。

谷歌云提供Google 容器注册表,地址是cloud.google.com/container-registry/

Docker 有自己信任的注册表服务,地址是www.docker.com/docker-trusted-registry

Quay 还提供安全的私有仓库、漏洞扫描,并且来自 CoreOS 团队,可以在quay.io/找到。

Google Kubernetes Engine

谷歌是原始 Kubernetes 项目的主要作者,并且仍然是一个主要贡献者。尽管本书主要集中在我们自己运行 Kubernetes 上,谷歌也通过 Google Cloud Platform 提供完全托管的容器服务。

Google Kubernetes EngineGKE)网站上查找更多信息,地址是cloud.google.com/container-engine/

Kubernetes 将在 GKE 上安装并由谷歌工程师管理。他们还提供私有仓库和与现有私有网络的集成。

你可以通过以下步骤创建你的第一个 GKE 集群:

  1. 在 GCP 控制台中,在计算部分点击容器引擎,然后点击容器集群。

  2. 如果这是你第一次创建集群,页面中间会有一个信息框。点击创建容器集群按钮。

  3. 为你的集群选择一个名称和区域。你还可以选择节点的机器类型(实例大小)和集群中的节点数量(集群大小)。你还会看到一个节点镜像选项,允许你为节点选择基础操作系统和机器镜像。主节点由谷歌团队自己管理和更新。

  4. 保留Stackdriver 日志Stackdriver 监控的选项。点击创建,几分钟后,你将有一个新的集群可以使用。

  5. 你将需要kubectl,它包含在 Google SDK 中,来开始使用你的 GKE 集群。有关安装 SDK 的详细信息,请参考第一章,Kubernetes 简介。一旦拥有 SDK,我们可以使用cloud.google.com/container-engine/docs/before-you-begin#install_kubectl中概述的步骤配置kubectl和 SDK,以适应我们的集群。

Azure Kubernetes 服务

另一个云托管的产品是微软的 Azure Kubernetes 服务 (AKS)。AKS 非常棒,因为它允许你选择 Docker Swarm、Kubernetes 和 Mesos 等行业标准工具。然后它为你创建一个托管的集群,但使用其中一个工具集作为基础。其优点是,你仍然可以使用该工具的原生 API 和管理工具,但将云基础设施的管理交给 Azure。

你可以在以下链接了解更多关于 ACS 的信息:azure.microsoft.com/en-us/services/container-service/

ClusterHQ

ClusterHQ 提供了一种将有状态数据引入容器化应用程序的解决方案。它们提供了 Flocker,这是一个用于管理持久存储卷与容器的工具,以及 FlockerHub,它为您的数据卷提供了一个存储库。

Portworx

Portworx 是另一个在存储领域的参与者。它提供将持久化存储引入容器的解决方案。此外,它还具备快照、加密,甚至是多云复制等功能。

如需了解更多信息,请参考 Portworx 网站:portworx.com/

Shippable

Shippable 是一个持续集成、持续部署和发布自动化平台,内置支持多种现代容器环境。该产品宣称支持任何语言,并提供统一的打包和测试支持。

如需了解更多信息,请参考 Shippable 网站:app.shippable.com/

Twistlock

Twistlock.io 是一款专为容器量身定制的漏洞扫描和硬化工具。它提供了执行策略、根据 CIS 标准进行硬化,并扫描任何流行注册表中的镜像漏洞的能力。它还与流行的 CI/CD 工具和许多编排工具(如 Kubernetes)提供了 RBAC 解决方案的扫描集成。

如需了解更多信息,请参考 Twistlock 网站:www.twistlock.com/

Aqua Sec

Aqua Sec 是另一种安全工具,提供多种功能。包括与流行注册表的镜像扫描、策略执行、用户访问控制以及容器强化等功能。此外,Aqua Sec 还具有网络分段等有趣的功能。

如需了解更多信息,请参考 Aqua 网站:www.aquasec.com/

Mesosphere(Kubernetes on Mesos)

Mesosphere 本身正在围绕开源的 Apache Mesos 项目构建一款商业化支持的产品。Apache Mesos 是一个集群管理系统,提供调度和资源共享,类似于 Kubernetes,但层次更高。该开源项目被多家知名公司使用,如 Twitter 和 Airbnb。

你可以在以下网站找到有关 Mesos OS 项目和 Mesosphere 产品的信息:

Mesos 本质上是模块化的,允许为各种平台使用不同的框架。现在有了 Kubernetes 框架,我们可以在保持 K8s 应用级抽象的同时,利用 Mesos 中的集群管理。有关更多信息,请参阅以下链接:github.com/kubernetes-incubator/kube-mesos-framework

Deis

Deis 项目提供了一个基于 Kubernetes 的开源平台即服务PaaS)解决方案。这使得公司可以在本地或公有云上部署自己的 PaaS。Deis 提供应用组合和部署工具、包管理(在 Pod 级别)以及服务代理。

OpenShift

另一种 PaaS 解决方案是 Red Hat 的 OpenShift。OpenShift 平台使用 Red Hat Atomic 平台作为运行容器的安全、精简操作系统。在版本 3 中,Kubernetes 作为所有容器操作的编排层被添加到 PaaS 中。这是一个用于大规模管理 PaaS 安装的绝佳组合。

有关 OpenShift 的更多信息,请访问enterprise.openshift.com/.

总结

在本章中,我们留下了一些线索,帮助你在继续使用 Kubernetes 的旅程中前进。你应该已经掌握了一些生产特征,以帮助你起步。Docker 和 Kubernetes 的社区都非常庞大。如果你在过程中需要一些帮助,我们也提供了一些额外的资源。

到目前为止,你已经了解了 Kubernetes 中容器操作的全貌。你应该对 Kubernetes 如何简化容器部署管理以及如何规划将容器从开发者的笔记本转移到生产服务器上充满信心。现在,出去吧,开始部署你的容器!

问题

  • 生产系统的一些关键特征是什么?

  • 有哪些第三方监控系统的示例?

  • 哪些工具可以帮助你构建基础设施即代码?

  • 什么是 RBAC?

  • 你可以在 Kubernetes 集群中设置哪些限制?

  • 可以在哪些维度上设置约束?

  • 应该使用哪种技术来确保集群内的通信安全?

进一步阅读

Kubernetes 项目是一个开源努力,因此有一个广泛的贡献者和爱好者社区。为了获得更多帮助,一个很好的资源是 Kubernetes 的 Slack 频道:slack.kubernetes.io/

Google Groups 上也有一个 Kubernetes 小组。你可以通过groups.google.com/forum/#!forum/kubernetes-users加入。

如果你喜欢这本书,你可以在我的博客和 Twitter 页面找到更多的文章、操作指南和各种想法:

第十五章:Kubernetes 基础设施管理

在本章中,我们将讨论如何更改支持 Kubernetes 基础设施的基础设施,无论它是完全公共云平台还是混合安装。我们将讨论如何处理底层实例和资源不稳定的方法,以及如何在部分可用的底层硬件上运行高可用工作负载的策略。本章将涵盖一些关键主题,以帮助你理解如何以这种方式管理基础设施:

  • 我们如何规划部署 Kubernetes 组件?

  • 我们如何保护 Kubernetes 基础设施?

  • 我们如何升级集群和kubeadm

  • 我们如何扩展集群?

  • 我们有哪些外部资源可用?

在本章中,你将学习以下内容:

  • 集群升级

  • 如何管理kubeadm

  • 集群扩展

  • 集群维护

  • SIG 集群生命周期组

技术要求

你需要启用并登录到 Google Cloud Platform 账户,或者可以使用本地 Minikube Kubernetes 实例。你还可以通过网络使用 Play with Kubernetes:labs.play-with-k8s.com/

本章的 GitHub 仓库链接是:github.com/PacktPublishing/Getting-Started-with-Kubernetes-third-edition/tree/master/Code-files/Chapter15.

规划集群

回顾我们在本书中迄今为止完成的工作,构建 Kubernetes 集群时有许多选择。让我们简要概述一下在计划构建集群时可用的选项。我们有几个关键领域需要在规划时进行调查。

选择正确的方案

选择集群时,第一步也是最重要的一步是选择适合你 Kubernetes 集群的托管平台。从高层次来看,以下是你可以选择的方案:

  • 本地解决方案包括以下内容:

    • Minikube:一个单节点 Kubernetes 集群

    • LXD 上的 Ubuntu:这使用 LXD 来部署一个包含九个实例的 Kubernetes 集群

    • IBM 的 Cloud Private-CE:这使用 VirtualBox 在n+1实例上部署 Kubernetes

    • kubeadm-dind(Docker-in-Docker):这允许多节点 Kubernetes 集群

  • 托管解决方案包括以下内容:

    • 谷歌 Kubernetes 引擎

    • 亚马逊弹性容器服务

    • Azure Kubernetes 服务

    • Stackpoint

    • Openshift 在线

    • IBM 云 Kubernetes 服务

    • Giant Swarm

  • 在所有上述云平台及更多平台上,都有许多即用型解决方案,允许你使用社区维护的脚本快速启动完整的集群

截至本书出版时,以下是项目和解决方案的列表:

访问此链接了解更多现成解决方案:kubernetes.io/docs/setup/pick-right-solution/#turnkey-cloud-solutions

保护集群

如我们所讨论的,确保在保护集群时关注几个关键领域。确保您已经阅读并对以下领域的集群配置(代码中)进行了更改:

  • 日志记录:确保您的 Kubernetes 日志已启用。您可以在这里了解更多关于审计日志的信息:kubernetes.io/docs/tasks/debug-application-cluster/audit/

  • 确保启用了身份验证,以便您的用户、操作员和服务可以作为唯一标识符进行身份识别。有关身份验证的更多信息,请参见:kubernetes.io/docs/reference/access-authn-authz/authentication/

  • 确保您已正确分离职责、使用基于角色的访问控制,并通过授权实现细粒度权限。您可以在这里了解有关基于 HTTP 的控制的更多信息:kubernetes.io/docs/reference/access-authn-authz/authorization/

  • 确保您已将 API 限制为特定权限和组。您可以在这里了解更多关于 API 访问的信息:kubernetes.io/docs/reference/access-authn-authz/controlling-access/

  • 在适当的情况下,启用准入控制器,以便在请求通过身份验证和授权控制后进一步重新验证请求。这些控制器可以执行额外的、基于业务逻辑的验证步骤,以进一步保护您的集群。您可以在这里了解更多关于准入控制器的信息:kubernetes.io/docs/reference/access-authn-authz/controlling-access

  • 通过 sysctl 接口调整 Linux 系统参数。这允许您修改节点级别和命名空间 sysctl 功能的内核参数。系统参数分为安全和不安全两类。sysctl 可以调整多个子系统。可能的参数如下:

    • abi:执行域和个性

    • fs:特定文件系统、文件句柄、inode、目录项和配额调优

    • kernel:全局内核信息/调优

    • net:网络

    • sunrpc:SUN 远程过程调用RPC

    • vm:内存管理调优、缓冲区和缓存管理

    • user:每个用户每个用户命名空间限制

您可以在这里了解更多关于 sysctl 调用的信息:kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/

您可以通过运行以下命令启用不安全的 sysctl 值:

kubelet --allowed-unsafe-sysctls ‘net.ipv4.route.min_pmtu'

这是授权、身份验证和准入控制协同工作的示意图:

调优示例

如果你想尝试修改sysctls,你可以按每个 pod 设置一个安全上下文,方法如下:

apiVersion: v1
kind: Pod
metadata:
 name: sysctl-example
spec:
 securityContext:
   sysctls:
   - name: kernel.shm_rmid_forced
     value: "0"
   - name: net.core.somaxconn
     value: "10000"
   - name: kernel.msgmax
     value: "65536"
   - name: ipv4.ip_local_port_range
      value: ‘1024 65535'

你也可以调整诸如 ARP 缓存等变量,因为 Kubernetes 在大规模运行时会消耗大量 IP,这可能会耗尽 ARP 缓存空间。改变这些设置在大规模 HPC 集群中很常见,也能帮助解决 Kubernetes 地址耗尽的问题。你可以按以下方式设置这些值:

net.ipv4.neigh.default.gc_thresh1 = 90000
net.ipv4.neigh.default.gc_thresh2 = 100000
net.ipv4.neigh.default.gc_thresh3 = 120000

升级集群

为了长时间运行你的集群,你需要根据需要更新集群。管理集群升级有多种方式,升级的难度取决于你之前选择的平台。通常,托管的平台即服务PaaS)选项较为简单,而自己搭建的选项则需要你来管理集群升级。

升级 PaaS 集群

升级 PaaS 集群比更新自己搭建的集群要简单得多。让我们来看看主要的云服务提供商是如何更新他们的托管 Kubernetes 平台的。

在 Azure 中,管理集群的控制平面和节点的升级相对简单。你可以使用以下命令检查你的集群可以升级到哪些版本:

az aks get-upgrades --name “myAKSCluster” --resource-group myResourceGroup --output table
Name ResourceGroup MasterVersion NodePoolVersion Upgrades

------- --------------- --------------- ----------------- -------------------

default gsw-k8s-aks 1.8.10 1.8.10 1.9.1, 1.9.2, 1.9.6

在升级 AKS 集群时,你必须通过次要版本进行升级。AKS 会处理向集群中添加新节点的过程,并且会管理 cordon 和 drain 过程,以防止对正在运行的应用程序造成任何中断。你可以在接下来的部分中查看 drain 过程的工作方式。

你可以按如下方式运行upgrade命令。在在生产工作负载上运行之前,你应该先进行此功能的实验,以便了解它对运行中应用程序的影响:

az aks upgrade --name myAKSCluster --resource-group myResourceGroup --kubernetes-version 1.9.6

你应该看到很多输出,标识出更新内容,通常类似于这样:

{
  "id": "/subscriptions/<Subscription ID>/resourcegroups/myResourceGroup/providers/Microsoft.ContainerService/managedClusters/myAKSCluster",
  "location": "eastus",
  "name": "myAKSCluster",
  "properties": {
    "accessProfiles": {
      "clusterAdmin": {
        "kubeConfig": "..."
      },
      "clusterUser": {
        "kubeConfig": "..."
      }
    },
    "agentPoolProfiles": [
      {
        "count": 1,
        "dnsPrefix": null,
        "fqdn": null,
        "name": "myAKSCluster",
        "osDiskSizeGb": null,
        "osType": "Linux",
        "ports": null,
        "storageProfile": "ManagedDisks",
        "vmSize": "Standard_D2_v2",
        "vnetSubnetId": null
      }
    ],
    "dnsPrefix": "myK8sClust-myResourceGroup-4f48ee",
    "fqdn": "myk8sclust-myresourcegroup-4f48ee-406cc140.hcp.eastus.azmk8s.io",
    "kubernetesVersion": "1.9.6",
    "linuxProfile": {
      "adminUsername": "azureuser",
      "ssh": {
        "publicKeys": [
          {
            "keyData": "..."
          }
        ]
      }
    },
    "provisioningState": "Succeeded",
    "servicePrincipalProfile": {
      "clientId": "e70c1c1c-0ca4-4e0a-be5e-aea5225af017",
      "keyVaultSecretRef": null,
      "secret": null
    }
  },
  "resourceGroup": "myResourceGroup",
  "tags": null,
  "type": "Microsoft.ContainerService/ManagedClusters"
}

你还可以显示当前版本:

az aks show --name myAKSCluster --resource-group myResourceGroup --output table

要升级 GCE 集群,你将遵循类似的流程。在 GCE 的情况下,有两种机制可以让你更新集群:

  • 对于管理节点的升级,GCP 删除并重新创建主节点,使用相同的持久磁盘PD)以保持你的状态在升级过程中不变。

  • 在你的工作节点上,你将使用 GCP 的托管实例组,并执行滚动升级集群的操作,其中每个节点都会被销毁并替换,以避免对工作负载造成中断。

你可以将集群主节点升级到特定版本:

cluster/gce/upgrade.sh -M v1.0.2

或者,你也可以使用此命令更新整个集群:

cluster/gce/upgrade.sh -M v1.0.2

要升级 Google Kubernetes Engine 集群,你有一个简单的用户发起的选项。你需要设置你的项目 ID:

gcloud config set project [PROJECT_ID]

另外,确保你拥有最新的gcloud组件:

gcloud components update

在 GCP 上更新 Kubernetes 集群时,您将获得以下好处。您可以降级节点,但不能降级主节点:

  • GKE 会在不中断应用程序的情况下处理节点和 Pod 的排空操作

  • 替换节点将以与前一个节点相同的配置重新创建

  • GKE 将更新集群中的以下软件组件:

    • kubelet

    • kube-proxy

    • Docker 守护进程

    • 操作系统

您可以使用以下命令查看您的服务器有哪些升级选项:

gcloud container get-server-config

请注意,存储在hostPathemptyDir目录中的数据将在升级过程中被删除,只有 PD(持久磁盘)会被保留。您可以为集群启用 GKE 的自动节点更新,也可以手动执行更新。

要启用自动节点自动升级,请阅读此文档:cloud.google.com/kubernetes-engine/docs/concepts/node-auto-upgrades

您也可以使用 --enable-autoupgrade 命令创建默认启用自动升级的集群:

gcloud container clusters create [CLUSTER_NAME] --zone [COMPUTE_ZONE] \
 --enable-autoupgrade

如果您希望手动更新集群,可以发出特定的命令。对于生产环境系统,建议关闭自动升级,并在低流量或维护窗口期间进行升级,以确保对应用程序的干扰最小化。建立对升级的信心后,您可以尝试自动升级。

要手动启动节点升级,您可以运行以下命令:

gcloud container clusters upgrade [CLUSTER_NAME]

如果您想升级到特定版本的 Kubernetes,可以添加 --cluster-version 标签。

您可以查看集群上正在进行的操作列表,以跟踪更新操作:

gcloud beta container operations list
NAME TYPE ZONE TARGET STATUS_MESSAGE STATUS START_TIME END_TIME
operation-1505407677851-8039e369 CREATE_CLUSTER us-west1-a my-cluster DONE 20xx-xx-xxT16:47:57.851933021Z 20xx-xx-xxT16:50:52.898305883Z
operation-1505500805136-e7c64af4 UPGRADE_CLUSTER us-west1-a my-cluster DONE 20xx-xx-xxT18:40:05.136739989Z 20xx-xx-xxT18:41:09.321483832Z
operation-1505500913918-5802c989 DELETE_CLUSTER us-west1-a my-cluster DONE 20xx-xx-xxT18:41:53.918825764Z 20xx-xx-xxT18:43:48.639506814Z

然后,您可以使用以下命令描述您的特定升级操作:

gcloud beta container operations describe [OPERATION_ID]

上述命令将告诉您有关集群升级操作的详细信息:

gcloud beta container operations describe operation-1507325726639-981f0ed6
endTime: '20xx-xx-xxT21:40:05.324124385Z'
name: operation-1507325726639-981f0ed6
operationType: UPGRADE_CLUSTER
selfLink: https://container.googleapis.com/v1/projects/.../kubernetes-engine/docs/zones/us-central1-a/operations/operation-1507325726639-981f0ed6
startTime: '20xx-xx-xxT21:35:26.639453776Z'
status: DONE
targetLink: https://container.googleapis.com/v1/projects/.../kubernetes-engine/docs/zones/us-central1-a/clusters/...
zone: us-central1-a

扩展集群

与 PaaS 和托管集群类似,您有多个选项可以扩展您的生产 Kubernetes 集群。

在 GKE 和 AKS 上

升级 GKE 集群时,您需要做的就是发出一个扩缩命令,修改您的从属组中的实例数量。您可以使用以下命令调整控制集群的节点池大小:

gcloud container clusters resize [CLUSTER_NAME] \
 --node-pool [POOL_NAME]
 --size [SIZE]

请记住,新的节点将与当前节点池中的机器具有相同的配置创建。当额外的 Pod 被调度时,它们将调度到新的节点上。现有的 Pod 不会被迁移或重新平衡到新节点上。

扩展 AKS 集群引擎是一个类似的操作,您需要指定 --resource-group 节点计数为所需的节点数量:

az aks scale --name myAKSCluster --resource-group gsw-k8s-group --node-count 1

DIY 集群

当你向手动搭建的 Kubernetes 集群中添加资源时,你需要做更多的工作。为了使节点在通过扩展组自动添加或通过基础设施代码手动添加时能够自动加入,你需要确保通过--register-node标志启用节点的自动注册。如果该标志开启,新的节点将尝试自动注册自己。这是默认行为。

你也可以使用预验证过的令牌手动将节点加入到集群中。如果你使用以下令牌初始化 kubeadm

kubeadm init --token=101tester101 --kubernetes-version $(kubeadm version -o short)

然后你可以使用这个命令将其他节点添加到集群中:

kubeadm join --discovery-token-unsafe-skip-ca-verification --token=101tester101:6443

通常在 kubeadm 的生产环境安装中,你不会指定令牌,需要从 kubeadm init 命令中提取并存储它。

节点维护

如果你正在扩展或缩减集群,了解手动节点注销和排水的过程是至关重要的。我们将在这里使用 kubectl drain 命令,在从集群中移除节点之前,先将所有 Pod 从节点中移除。从节点中移除所有 Pod 可以确保当你移除实例或虚拟机时,不会有正在运行的工作负载。

使用以下命令获取可用节点列表:

kubectl get nodes

一旦我们有了节点列表,排水节点的命令就很简单:

kubectl drain <node>

该命令执行需要一些时间,因为它需要重新调度节点上的工作负载到其他有可用资源的机器上。一旦排水完成,你可以通过你喜欢的编程 API 移除该节点。如果你只是为了维护而移除节点,你可以使用 uncordon 命令将其重新加入可用节点:

kubectl uncordon <node>

其他配置选项

一旦你建立了对 Kubernetes 集群配置管理的理解,建议探索那些提供增强机制或抽象的额外工具,以便配置集群的状态。

ksonnet 是一个这样的工具,它允许你围绕不同的配置构建一个结构,以保持多个环境的配置。ksonnet 使用另一个强大的工具叫做 Jsonnet 来维护集群的状态。ksonnet 是一种与我们在之前章节中讨论的 Helm 方法不同的集群管理方式,它不是通过依赖关系定义包,而是采取可组合的原型方法,你构建 JSON 模板,这些模板会被 ksonnet CLI 渲染并应用到集群上。你从创建原型的部分开始,配置后变成组件,这些组件可以组合成应用程序。这有助于避免代码库中的重复代码。你可以在这里查看:ksonnet.io/

摘要

在本章中,我们讨论了如何对提供计算、存储和网络能力的基础设施进行更改,无论它是纯粹的公共云平台还是混合安装。在观察公共云平台时,我们讨论了处理底层实例和资源不稳定性的方法,以及如何在部分可用的底层硬件上运行高可用性工作负载的策略。

此外,我们还介绍了一个关键主题,讲解如何使用诸如 kubeadmkubectl 以及公共云提供商工具等工具来构建基础设施,这些工具能够按需扩展和缩减你的集群。

问题

  1. 列举两个可用的 Kubernetes 本地解决方案

  2. 列举三个 Kubernetes 的托管解决方案

  3. 确保集群安全的四个关键领域是什么?

  4. 升级每个主要云服务提供商托管的 Kubernetes 集群的命令是什么?

  5. 哪个云提供商的 PaaS 解决方案最适合 Kubernetes 的生产环境?

  6. 哪个命令用于将一个节点从轮换中移除?

  7. 哪个命令用于将其重新添加?

深入阅读

如果你想了解更多关于 Jsonnet 的信息,可以查看这个链接:jsonnet.org/

第十六章:评估

第一章:Kubernetes 简介

  1. Minikube、Google Cloud Platform 和 Azure Kubernetes Service。

  2. 虚拟机、FreeBSD Jail、LXC(Linux 容器)、Open VZ 和 Solaris 容器。

  3. 内存、文件系统 CPU、线程、进程、命名空间和内存接口文件。

  4. 它允许公司将增量更新交付到软件。它还允许将软件从开发者的笔记本电脑打包到生产环境。

  5. 设置帐户和计费。你还需要在 GCE 上启用 API。

  6. kube-apiserveretcdkube-schedulerkube-controller-managercloud-controller-manager

  7. kopskubespraykubeadmbootkube

  8. kubeadm

第二章:通过核心 Kubernetes 构建基础

  1. HTTP、TCP 和应用程序级健康检查

  2. ReplicaSet

  3. 生态系统、接口、治理、应用程序和核心

  4. Calico 和 Flannel

  5. rktkatafrakticontainerdrunv

  6. 集群控制平面、集群状态和集群节点

  7. 基于等式的选择器

第三章:与网络、负载均衡器和 Ingress 配合工作

  1. 通信是由 Pod 之间的通信进行管理,而不是容器之间的通信。Pod 与服务之间的通信由服务对象提供。K8s 不使用 NAT 来进行容器间的通信。

  2. 网络地址转换

  3. 使用覆盖网络的 CNI 插件,或使用桥接和主机本地特性的 kubenet 插件。

  4. Canal、Calico、Flannel 和 Kube-router。

  5. Pods。

  6. 用户空间、iptables 和 ipvs。

  7. 虚拟 IP、服务代理和多端口。

  8. 规范。

  9. GCE、nginx、Kong、Traefik 和 HAProxy。

  10. 使用命名空间、RBAC、容器权限、入口规则以及明确的网络管理。

第四章:实施可靠的容器原生应用程序

  1. Kubernetes 部署的四种用例如下:

    • 发布 ReplicaSet

    • 更新一组 Pod 的状态

    • 回滚到部署的早期版本

    • 扩展以适应集群负载

  2. 选择器。

  3. 记录标志,--record

  4. ReplicationControllers

  5. 水平 Pod 自动伸缩。

  6. 定时任务。

  7. DaemonSet 简单地定义了一个 Pod,在集群中的每个节点或这些节点的指定子集上运行。这对于许多与生产相关的活动非常有用,例如监控和日志代理、安全代理以及文件系统守护进程。

第五章:探索 Kubernetes 存储概念

  1. 持久化的临时磁盘、云卷、emptyDirnfs

  2. emptydir

  3. AWS 中的 EBS 卷和 Azure 上的磁盘存储

  4. 不同的应用性能或耐用性要求。

  5. 绑定、使用、回收和扩展。

  6. 持久化卷声明。

第六章:应用更新、渐进式发布和自动伸缩

  1. kubectl scale --replicas

  2. rolling-update

  3. ClientIP

  4. 水平 Pod 自动伸缩

  5. 最小值和最大值的 CPU 和内存使用设置

  6. Helm

  7. 一个图表

第七章:为持续集成和交付设计

  1. Node.js

  2. Jenkins

  3. Helm 图表

  4. 持久化卷

  5. 安装 Jenkins 插件

  6. 一个 ReplicationController

  7. npm

第八章:监控和日志记录

  1. cAdvisor 和 Heapster。

  2. Kube-system。

  3. Grafana。

  4. 一个收集器。

  5. Stackdriver。

  6. 使用 Prometheus 的好理由如下:

    • 操作简单:它被设计为使用本地存储作为可靠性的单独服务器运行。

    • 它很精确:您可以使用类似于 JQL、DDL、DCL 或 SQL 查询的查询语言来定义警报,并提供状态的多维视图。

    • 许多库:您可以使用超过十种语言和众多客户端库来审视您的服务和软件。

    • 高效:使用在内存和磁盘上存储的高效、自定义格式,您可以通过分片和联合轻松扩展,从而创建一个强大的平台,用于发出可以构建强大数据模型和特设表、图表和警报的强大查询。

第十章:设计高可用性和可扩展性

  1. 可用性、响应性和耐久性。

  2. 正常运行时间是衡量给定系统、应用程序、网络或其他逻辑和物理对象已上线并可供适当终端用户使用的时间。

  3. 可用性的五个 9

  4. 意味着它以优雅的方式失败。

  5. Google Kubernetes Engine (GKE)。

  6. 一组主节点,具有 Kubernetes 控制平面和 collocated 的 etcd 服务器。

  7. 工作负载 API。

第十一章:Kubernetes SIGs、孵化项目和 CNCF

  1. Kubernetes 和 Prometheus。

  2. Linkerd、rkt、CNI、TUF、Vitess、CoreDNS、Jaeger、Envoy。

  3. Spiffe、Spire、Open Policy Agent、Telepresence、Harbor、TiKV、Cortex 和 Buildpacks。查看更多信息:www.cncf.io/sandbox-projects/

  4. 委员会旨在定义元标准并解决社区范围的问题。

  5. 这是理解 Kubernetes 核心概念和内部工作原理的绝佳方式。这也是与其他有动力、聪明的人们相遇的有趣方式。最后,Kubernetes 本质上是一个社区项目,依赖于其成员和用户的贡献。

  6. SSH 密钥和 SSL 连通性。

第十二章:集群联邦和多租户

  1. 使用联合,我们可以在本地和一个或多个公共云提供商上运行多个 Kubernetes 集群,并管理利用我们组织资源的整套应用程序。

  2. 联合允许您增加 Kubernetes 集群的可用性和租户能力。

  3. 跨集群资源同步和多集群服务发现。

  4. Kubefed。

  5. Federation-controller-manager 和 federation-apiserver。

  6. HPA 将以类似于常规 HPA 的方式进行操作,具有相同的功能和相同的基于 API 的兼容性——只是,通过联合,Pod 的管理将穿越您的集群。

  7. 部署、副本集、事件、配置映射、守护集、入口、命名空间、秘密和服务。

第十三章:集群认证、授权和容器安全

  1. 容器仓库或注册表

  2. 以下任意三个:Node、ABAC、RBAC、Webhook

  3. 特权

  4. kubectl get secrets

第十四章:Kubernetes 强化

  1. 数据加密、机密、服务发现、合规性、RBAC、系统事件跟踪和趋势偏差警报

  2. Stackdriver、Sysdig、Datadog 和 Sensu。

  3. Terraform 或 CloudFormation,以及 Ansible、Chef 或 Puppet

  4. 最小权限原则

  5. CPU、内存和限制

  6. 最大值和最小值

  7. 传输层安全性(TLS)

第十五章:Kubernetes 基础设施管理

  1. kubeadm-dind、Minikube 和基于 LXD 的 Ubuntu。

  2. Google Kubernetes Engine、Amazon Elastic Container Services、Azure Kubernetes Service 和 Stackpoint.io。

  3. 日志记录、身份验证、授权和 Linux 系统参数。

  4. 升级每个主要 CSP 托管的 Kubernetes 集群的命令如下:

az aks upgrade --name <CLUSTER_NAME> --resource-group <RESOURCE_GROUP> --kubernetes-version <VERSION> 

gcloud container clusters upgrade <CLUSTER_NAME>
  1. Google Compute Platform,使用 EKS

  2. kubectl drain <node>

  3. kubectl uncordon <node>

posted @ 2025-06-30 19:28  绝不原创的飞龙  阅读(12)  评论(0)    收藏  举报