Kubernetes-入门指南-全-

Kubernetes 入门指南(全)

原文:zh.annas-archive.org/md5/1794743BB21D72736FFE64D66DCA9F0E

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

本书是 Kubernetes 和整体容器管理入门指南。我们将为您介绍 Kubernetes 的特性和功能,并展示它如何融入整体运营策略。您将了解将容器从开发人员的笔记本移到更大规模时面临的障碍。您还将看到 Kubernetes 如何是帮助您自信面对这些挑战的完美工具。

本书内容

第一章,Kubernetes 简介,简要介绍容器和 Kubernetes 编排的如何、什么和为什么,探讨它如何影响您的业务目标和日常操作。

第二章,Pods、服务、复制控制器和标签,使用几个简单的示例来探索核心 Kubernetes 构造,即 Pod、服务、复制控制器、副本集和标签。还将涵盖基本操作,包括健康检查和调度。

第三章,网络、负载均衡器和入口,涵盖了 Kubernetes 的集群网络和 Kubernetes 代理。它还深入探讨了服务,最后,它展示了一些更高级别的隔离特性的简要概述,用于多租户。

第四章,更新、渐进式部署和自动缩放,快速了解如何在最小化停机时间的情况下推出更新和新功能。我们还将研究应用程序和 Kubernetes 集群的扩展。

第五章,部署、任务和 DaemonSets,涵盖了长时间运行的应用程序部署以及短期任务。我们还将研究使用 DaemonSets 在集群中的所有或子集节点上运行容器。

第六章,存储和运行有状态的应用程序,涵盖了跨 Pod 和容器生命周期的存储问题和持久数据。我们还将研究在 Kubernetes 中使用有状态应用程序的新构建。

第七章,持续交付,解释了如何将 Kubernetes 集成到您的持续交付流水线中。我们将看到如何使用 Gulp.js 和 Jenkins 来使用 k8s 集群。

第八章,监控与日志,教你如何在你的 Kubernetes 集群上使用和定制内置和第三方监控工具。我们将研究内置日志记录和监控、Google Cloud 监控/日志服务以及 Sysdig。

第九章,集群联邦,使您能够尝试新的联邦功能,并解释如何使用它们来管理跨云提供商的多个集群。我们还将涵盖前几章中核心构造的联邦版本。

第十章,容器安全,从容器运行时层级到主机本身,教授容器安全的基础知识。它还解释了如何将这些概念应用于运行容器,以及与运行 Kubernetes 相关的一些安全问题和做法。

第十一章,使用 OCP、CoreOS 和 Tectonic 扩展 Kubernetes,探讨了开放标准如何使整个容器生态系统受益。我们将介绍一些知名的标准组织,并涵盖 CoreOS 和 Tectonic,探讨它们作为主机操作系统和企业平台的优势。

第十二章,走向生产就绪,最后一章,展示了一些有用的工具和第三方项目,以及您可以获取更多帮助的地方。

本书所需内容

本书将涵盖下载和运行 Kubernetes 项目。您需要访问 Linux 系统(如果您使用 Windows,VirtualBox 也可行),并对命令行有一定了解。

此外,您应该有一个 Google Cloud Platform 账户。您可以在此处注册免费试用:

cloud.google.com/

此外,本书的一些部分需要有 AWS 账户。您可以在此处注册免费试用:

aws.amazon.com/

本书适合谁

无论您是深入开发、深入运维,还是作为执行人员展望未来,Kubernetes 和本书都适合您。开始使用 Kubernetes 将帮助您了解如何将容器应用程序移入生产环境,并与现实世界的操作策略相结合的最佳实践和逐步演练。您将了解 Kubernetes 如何融入您的日常操作中,这可以帮助您为生产就绪的容器应用程序堆栈做好准备。

对 Docker 容器、一般软件开发和高级操作有一定了解会很有帮助。

惯例

在本书中,您将找到许多文本样式,用以区分不同类型的信息。以下是这些样式的一些示例及其含义的解释。

文本中的代码词、文件夹名称、文件名、文件扩展名和路径名如下所示:"执行一个简单的curl命令到 pod IP。"

URL 如下所示:

swagger.io/

如果我们希望您用自己的值替换 URL 的一部分,则会显示如下:

https://**<你的主机 IP>**/swagger-ui/

资源定义文件和其他代码块设置如下:

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

当我们希望您用自己的值替换列表的一部分时,相关行或项目将以小于和大于符号之间的粗体设置:

subsets:
- addresses:
  - IP: <X.X.X.X>
  ports:
    - name: http
      port: 80
      protocol: TCP

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

$ kubectl get pods

新术语和重要单词以粗体显示。屏幕上看到的单词,例如在菜单或对话框中,会出现在文本中,如:“单击添加新按钮会将您移至下一个屏幕。”

文本中有几个地方涉及到键值对或屏幕上的输入对话框。在这些情况下,输入标签将以粗体显示,而将以粗体斜体显示。例如:“在标有超时的框中输入5s。”

警告或重要注释会以此类框的形式显示。

提示和技巧以此类形式显示。

读者反馈

我们非常欢迎读者的反馈。让我们知道您对这本书的看法-您喜欢或不喜欢的内容。读者的反馈对我们非常重要,因为它有助于我们开发出真正能让您受益的标题。

要发送给我们一般反馈,只需发送电子邮件至feedback@packtpub.com,并在消息主题中提及书名。

如果有您擅长并且有兴趣撰写或贡献书籍的主题,请参阅我们的作者指南,网址为www.packtpub.com/authors

客户支持

现在您是 Packt 图书的骄傲拥有者,我们有很多东西可以帮助您充分利用您的购买。

下载示例代码

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

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

  1. 使用您的电子邮件地址和密码登录或注册到我们的网站。

  2. 将鼠标指针悬停在顶部的 SUPPORT 选项卡上。

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

  4. 在搜索框中输入书名。

  5. 选择您要下载代码文件的书籍。

  6. 从下拉菜单中选择您购买本书的地方。

  7. 单击“下载代码”。

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

  • WinRAR / 7-Zip 适用于 Windows

  • Zipeg / iZip / UnRarX 适用于 Mac

  • 7-Zip / PeaZip 适用于 Linux

该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Getting-Started-with-Kubernetes-Second-Edition。我们还提供了来自丰富图书和视频目录的其他代码包,可在github.com/PacktPublishing/找到。快去看看吧!

下载本书的彩色图像

我们还向您提供了一份 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图像。彩色图像将帮助您更好地理解输出中的变化。您可以从以下位置下载此文件:

www.packtpub.com/sites/default/files/downloads/GettingStartedwithKubernetesSecondEdition_ColorImages.pdf.

勘误

尽管我们已经竭尽全力确保内容的准确性,但错误确实会发生。如果您发现我们书籍中的错误 - 也许是文本或代码中的错误 - 如果您能向我们报告此错误,我们将不胜感激。通过这样做,您可以帮助其他读者免受挫折,帮助我们改进此书的后续版本。如果您发现任何勘误,请访问www.packtpub.com/submit-errata报告它们,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情。一旦您的勘误被验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该书标题的勘误部分的任何现有勘误列表中。

要查看以前提交的勘误,请访问www.packtpub.com/books/content/support并在搜索栏中输入书名。相关信息将显示在勘误部分下面。

盗版

互联网上盗版版权材料是所有媒体都面临的持续问题。在 Packt,我们非常重视保护我们的版权和许可。如果您在互联网上发现我们作品的任何形式的非法复制,请立即向我们提供位置地址或网站名称,以便我们可以采取补救措施。

请通过copyright@packtpub.com与我们联系,并附上涉嫌盗版材料的链接。

我们感谢您帮助我们保护我们的作者以及我们为您提供有价值内容的能力。

问题

如果您对本书的任何方面有问题,请通过questions@packtpub.com与我们联系,我们将尽力解决问题。

Kubernetes 简介

在本书中,我们将帮助您学习构建和管理 Kubernetes 集群。我们将尽可能地介绍一些基本的容器概念和操作上下文。在整本书中,您将得到一些您可以在学习过程中应用的示例。到本书结束时,您应该具有坚实的基础,甚至可以涉足一些更高级的主题,如联邦和安全性。

本章将简要概述容器及其工作原理,以及为何管理和编排对您的业务和/或项目团队至关重要。本章还将简要介绍 Kubernetes 编排如何增强我们的容器管理策略以及如何启动、运行和准备容器部署的基本 Kubernetes 集群。

本章将包括以下主题:

  • 介绍容器的操作和管理

  • 为什么容器管理很重要?

  • Kubernetes 的优势

  • 下载最新的 Kubernetes

  • 安装和启动一个新的 Kubernetes 集群

  • Kubernetes 集群的组件

容器的简要概述

在过去的三年里,容器像野火般风靡。你很难参加一个 IT 会议而不找到关于 Docker 或容器的热门议题。

Docker 是大规模采用和容器领域的激情的核心。正如 Malcom McLean 在 1950 年代通过创建标准化的运输集装箱彻底改变了物理运输世界一样,Linux 容器正在通过使应用程序环境在基础设施景观中可移植和一致来改变软件开发世界。作为一个组织,Docker 将现有的容器技术提升到一个新的水平,使其易于在各种环境和提供者中实施和复制。

什么是容器?

容器技术的核心是控制组cgroups)和命名空间。此外,Docker 使用联合文件系统来增强容器开发过程的优势。

Cgroups 的工作原理是允许主机共享并限制每个进程或容器可以消耗的资源。这对资源利用和安全都很重要,因为它可以防止对主机硬件资源的拒绝服务攻击。多个容器可以共享 CPU 和内存,同时保持在预定义约束内。

命名空间为操作系统内的进程交互提供了另一种隔离形式。命名空间限制了一个进程对其他进程、网络、文件系统和用户 ID 组件的可见性。容器进程只能看到相同命名空间中的内容。来自容器或主机的进程无法直接从容器进程内部访问。此外,Docker 为每个容器提供了自己的网络堆栈,以类似的方式保护套接字和接口。

容器的组成

联合文件系统也是使用 Docker 容器的一个关键优势。容器是从镜像运行的。与虚拟机或云世界中的镜像类似,它代表了特定时间点的状态。容器镜像快照文件系统,但通常比虚拟机小得多。容器共享主机内核,并且通常运行一组更小的进程,因此文件系统和引导期间 tend to be much smaller。尽管这些约束条件并不严格执行。其次,联合文件系统允许高效存储、下载和执行这些镜像。

理解联合文件系统最简单的方法是将其想象成一个独立烘焙的层层蛋糕。Linux 内核是我们的基础层;然后,我们可能会添加一个操作系统,如红帽 LinuxUbuntu。接下来,我们可能会添加一个应用程序,如NginxApache。每个更改都会创建一个新层。最后,随着您进行更改并添加新层,您始终会有一个顶层(考虑一下糖霜),它是一个可写的层。

分层文件系统

使这真正高效的是 Docker 在第一次构建时缓存了这些层。所以,假设我们有一个包含 Ubuntu 的镜像,然后添加 Apache 并构建镜像。接下来,我们使用 Ubuntu 作为基础构建 MySQL。第二次构建将会更快,因为 Ubuntu 层已经被缓存了。本质上,我们的巧克力和香草层,来自前面的分层文件系统图,已经烘焙完成。我们只需要烘焙开心果(MySQL)层,组装并添加糖衣(可写层)。

为什么容器如此酷?

单独的容器并不是一种新技术,事实上它们已经存在多年了。真正使 Docker 脱颖而出的是他们为社区带来的工具和易用性。现代开发实践推广了持续集成和持续部署的使用。当这些技术正确使用时,可以对您的软件产品质量产生深远的影响。

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

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

然而,一直以来在创建与测试和生产环境匹配的开发环境方面存在挑战。通常,这些环境中的不一致性使得很难充分利用持续交付的全部优势。

使用 Docker,开发人员现在能够拥有真正可移植的部署。在开发人员的笔记本电脑上部署的容器很容易部署到内部的暂存服务器上。然后,它们很容易转移到在云中运行的生产服务器上。这是因为 Docker 使用构建文件来构建容器,这些构建文件指定了父层。这样做的一个优点是,可以非常轻松地确保在开发、暂存和生产环境中操作系统、软件包和应用程序版本相同。

因为所有的依赖关系都打包到了层中,所以同一个主机服务器可以运行多个容器,运行各种操作系统或软件包版本。此外,我们可以在同一台主机服务器上使用各种语言和框架,而不会像在带有单个操作系统的虚拟机VM)中那样出现典型的依赖冲突。

资源利用

明确定义的隔离和分层文件系统也使得容器非常适合运行具有非常小的占地面积和特定领域用途的系统。简化的部署和发布流程意味着我们可以快速而频繁地部署。因此,许多公司将他们的部署时间从几周甚至几个月缩短到了几天甚至几小时。这种开发生命周期非常适合于小型、有针对性的团队致力于一个更大的应用程序的小块。

微服务与编排

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

对于微服务的定义可能有点模糊,但是马丁·福勒(Martin Fowler)提出了一个定义,他是一位备受尊敬的软件开发作家和演讲者(你可以在本章末尾的参考资料中参考更多详细信息):

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

随着组织转向容器化,并且微服务在组织中发展,他们很快就会需要一种策略来维护许多容器和微服务。未来几年,一些组织将拥有数百甚至数千个正在运行的容器。

未来的挑战

生命周期进程本身就是运营和管理的重要组成部分。当容器失败时,我们如何自动恢复?哪些上游服务受到这种中断的影响?我们如何在最小停机时间内打补丁我们的应用?随着流量增长,我们如何扩展容器和服务的规模?

网络和处理也是重要的考虑因素。有些进程是同一服务的一部分,可能会受益于靠近网络。例如,数据库可能会向特定的微服务发送大量数据进行处理。我们如何在集群中将容器放置在彼此附近?是否有需要访问的共同数据?如何发现新服务并使其对其他系统可用?

资源利用率也是关键。容器的小占用空间意味着我们可以优化基础架构以实现更大的利用率。扩展弹性云中开始的节省将使我们更进一步地减少浪费的硬件。我们如何最有效地安排工作负载?如何确保我们的重要应用程序始终具有正确的资源?我们如何在备用容量上运行不太重要的工作负载?

最后,可移植性是许多组织转向容器化的关键因素。Docker 使在各种操作系统、云提供商和本地硬件甚至开发人员笔记本电脑上部署标准容器变得非常容易。然而,我们仍然需要工具来移动容器。我们如何在集群的不同节点之间移动容器?我们如何以最小的中断滚动更新?我们使用什么流程执行蓝绿部署或金丝雀发布?

无论您是开始构建单个微服务并将关注点分离到隔离的容器中,还是只是想充分利用应用程序开发中的可移植性和不变性,对管理和编排的需求变得明确起来。这就是编排工具如 Kubernetes 提供最大价值的地方。

Kubernetes 的诞生

KubernetesK8s)是谷歌于 2014 年 6 月发布的一个开源项目。谷歌发布该项目是为了与社区分享他们自己的基础设施和技术优势。

Google 每周在他们的基础设施中启动 20 亿个容器,并已经使用容器技术超过十年。最初,他们正在构建一个名为Borg,现在称为Omega的系统,用于在扩展中的数据中心中调度大量工作负载。多年来,他们吸取了许多经验教训,并重写了他们现有的数据中心管理工具,以便广泛被世界其他地方采纳。其结果便是开源项目 Kubernetes(您可以在本章末尾的参考资料部分中的第 3 点中了解更多详情)。

自 2014 年首次发布以来,K8s 在开源社区的贡献下经历了快速发展,包括 Red Hat、VMware 和 Canonical 等。Kubernetes 的 1.0 版本于 2015 年 7 月正式发布。从那时起,该项目得到了开源社区的广泛支持,目前是 GitHub 上最大的开源社区之一。我们将在整本书中涵盖版本 1.5。K8s 提供了一个工具,用于解决一些主要操作和管理问题。我们将探讨 Kubernetes 如何帮助处理资源利用、高可用性、更新、打补丁、网络、服务发现、监控和日志记录等问题。

我们的第一个集群

Kubernetes 支持各种平台和操作系统。在本书的示例中,我在客户端使用 Ubuntu 16.04 Linux VirtualBox,而在集群本身则使用 Debian 的Google Compute EngineGCE)。我们还将简要介绍在使用 Ubuntu 的Amazon Web ServicesAWS)上运行的集群。

为节省一些资金,GCP 和 AWS 都为他们的云基础设施提供了免费层和试用优惠。如果可能的话,值得使用这些免费试用来学习 Kubernetes。

本书中的大部分概念和示例应该适用于任何 Kubernetes 集群的安装。要获取有关其他平台设置的更多信息,请参考以下 GitHub 链接中的 Kubernetes 入门页面:

kubernetes.io/docs/getting-started-guides/

首先,在安装 Kubernetes 之前,让我们确保我们的环境已经正确设置。从更新软件包开始:

$ sudo apt-get update

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

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

安装gcloud SDK:

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

gcloud出现在我们的路径之前,我们需要启动一个新的 shell。

配置您的Google Cloud PlatformGCP)帐户信息。这应该会自动打开一个浏览器,我们可以从中登录到我们的 Google Cloud 帐户并授权 SDK:

$ gcloud auth login

如果您登录遇到问题或想要使用其他浏览器,可以选择使用--no-launch-browser命令。将 URL 复制并粘贴到您选择的计算机和/或浏览器上。使用您的 Google Cloud 凭据登录,并在权限页面上单击允许。最后,您应该收到一个授权码,可以将其复制并粘贴回等待提示的 shell 中。

默认项目应该已设置,但我们可以通过以下命令进行验证:

$ gcloud config list project

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

$ gcloud config set project <PROJECT ID>

我们可以在以下 URL 中的控制台中找到我们的项目 ID:

console.developers.google.com/project 或者,我们可以列出活动项目:

$ gcloud alpha projects list

现在我们已经设置好了环境,安装最新的 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

在运行kube-up.sh脚本后,我们将看到许多行通过。让我们逐个部分查看它们:

GCE 先决条件检查

如果您的gcloud组件不是最新版本,则可能会提示您更新它们。

前面的图像GCE 先决条件检查显示了先决条件的检查,以及确保所有组件都是最新版本的情况。这是针对每个提供程序的。在 GCE 的情况下,它将验证 SDK 是否已安装以及所有组件是否是最新版本。如果不是,则会在此时看到提示进行安装或更新:

上传集群软件包

现在脚本正在启动集群。同样,这是针对提供程序的。对于 GCE,它首先检查 SDK 是否配置为默认的项目区域。如果设置了,您将在输出中看到它们。

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

Master 创建

然后,它会检查是否已经运行了集群的任何部分。然后,我们最终开始创建集群。在上述图中的输出中 Master 创建 中,我们看到它创建了服务器、IP 地址以及集群的适当防火墙配置:

Minion 创建

最后,它创建了我们集群的minionsnodes。这是我们的容器工作负载实际运行的地方。它将不断循环并等待所有 minions 启动。默认情况下,集群将有四个节点(minions),但 K8s 支持超过 1000 个(很快会更多)。我们将在书中稍后回来扩展节点。

集群完成

现在一切都创建好了,集群已初始化并启动。假设一切顺利,我们将获得主服务器的 IP 地址。此外,请注意,配置以及集群管理凭据都存储在home/<用户名>/.kube/config中:

集群验证

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

成功启动后,在屏幕上打印出 minion 的摘要和集群组件的健康状况:

集群摘要

最后,运行kubectl cluster-info命令,该命令会输出主服务的 URL,包括 DNS、UI 和监视。让我们来看看其中一些组件。

Kubernetes UI

在浏览器中打开并运行以下代码:

https://<你的主服务器 IP>/ui/

默认情况下,证书是自签名的,因此您需要忽略浏览器中的警告,然后继续。之后,我们将看到一个登录对话框。这是我们在 K8s 安装期间列出的凭据的使用位置。我们可以随时通过简单地使用config命令来查找它们:

$ kubectl config view

现在我们有了登录凭据,请使用它们,我们应该会看到一个类似以下图像的仪表板:

Kubernetes UI 仪表板

主仪表板首先将我们带到一个显示不多的页面。有一个链接可以部署一个容器化应用程序,这将带您到一个用于部署的 GUI。这个 GUI 可以是一个非常简单的方式开始部署应用程序,而不必担心 Kubernetes 的 YAML 语法。然而,随着您对容器的使用逐渐成熟,最好使用检入源代码控制的 YAML 定义。

如果您点击左侧菜单中的Nodes链接,您将看到有关当前集群节点的一些指标:

Kubernetes 节点仪表板

在顶部,我们看到 CPU 和内存使用情况的汇总,然后是我们集群节点的列表。单击其中一个节点将带我们到一个页面,显示有关该节点、其健康状况和各种指标的详细信息。

随着我们开始启动真实应用程序并向集群添加配置,Kubernetes UI 将具有许多其他视图,这些视图将变得更加有用。

Grafana

默认安装的另一个服务是Grafana。这个工具将为我们提供一个仪表板,用于查看集群节点上的指标。我们可以使用以下语法在浏览器中访问它:https://<your master ip>/api/v1/proxy/namespaces/kube-system/services/monitoring-grafana

Kubernetes Grafana 仪表板

从主页,点击首页下拉菜单并选择 Cluster。在这里,Kubernetes 实际上正在运行许多服务。Heapster 用于收集podsnodes上的资源使用情况,并将信息存储在InfluxDB中。结果,如 CPU 和内存使用情况,是我们在 Grafana UI 中看到的。我们将在第八章,监控和日志记录中深入探讨此问题。

命令行

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。它可用于查看当前正在运行的服务pods副本控制器等等。以下是立即使用的三个示例:

  • 列出我们集群中的节点:
    $ kubectl get nodes

  • 列出集群事件:
    $ kubectl get events

  • 最后,我们可以查看集群中运行的任何服务,如下所示:
    $ kubectl get services

起初,我们只会看到一个名为kubernetes的服务。此服务是集群的核心 API 服务器。

运行在主节点上的服务

让我们进一步了解我们的新集群及其核心服务。默认情况下,机器以kubernetes-前缀命名。我们可以在启动集群之前使用$KUBE_GCE_INSTANCE_PREFIX修改这个前缀。对于我们刚刚启动的集群,主节点应该被命名为kubernetes-master。我们可以使用gcloud命令行实用程序 SSH 进入机器。以下命令将启动与主节点的 SSH 会话。确保替换您的项目 ID 和区域以匹配您的环境。还要注意,您可以使用以下语法从 Google Cloud 控制台启动 SSH:

$ gcloud compute ssh --zone "<your gce zone>" "kubernetes-master"

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

一旦我们登录,我们应该会得到一个标准的 shell 提示符。让我们运行过滤 ImageStatusdocker 命令:

$ sudo docker ps --format 'table {{.Image}}t{{.Status}}' 

主容器列表

尽管我们尚未在 Kubernetes 上部署任何应用程序,但我们注意到已经有几个容器正在运行。以下是对每个容器的简要描述:

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

  • node-problem-detector:这个容器是一个守护程序,在每个节点上运行,并当前在硬件和内核层检测问题。

  • rescheduler:这是另一个附加容器,确保关键组件始终运行。在资源可用性低的情况下,它甚至可能删除较不重要的 pod 以腾出空间。

  • glbc:这是另一个 Kubernetes 附加容器,使用新的 Ingress 功能提供 Google Cloud 第 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 核心架构

服务运行在 minions 上

我们可以 SSH 到其中一个 minion,但由于 Kubernetes 在整个集群上调度工作负载,因此我们不会在单个 minion 上看到所有容器。但是,我们可以使用 kubectl 命令查看所有 minion 上运行的 Pod:

$ kubectl get pods

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

$ kubectl get pods --namespace=kube-system

我们应该看到类似以下的内容:

etcd-empty-dir-cleanup-kubernetes-master 
etcd-server-events-kubernetes-master 
etcd-server-kubernetes-master 
fluentd-cloud-logging-kubernetes-master 
fluentd-cloud-logging-kubernetes-minion-group-xxxx
heapster-v1.2.0-xxxx 
kube-addon-manager-kubernetes-master 
kube-apiserver-kubernetes-master 
kube-controller-manager-kubernetes-master 
kube-dns-xxxx 
kube-dns-autoscaler-xxxx 
kube-proxy-kubernetes-minion-group-xxxx 
kube-scheduler-kubernetes-master 
kubernetes-dashboard-xxxx 
l7-default-backend-xxxx 
l7-lb-controller-v0.8.0-kubernetes-master 
monitoring-influxdb-grafana-xxxx 
node-problem-detector-v0.1-xxxx 
rescheduler-v0.2.1-kubernetes-master

前六行应该看起来很熟悉。其中一些是我们看到在主节点上运行的服务,而在节点上也会看到其中的部分。还有一些其他服务我们还没有看到。kube-dns 选项提供了 DNS 和服务发现的基本结构,kubernetes-dashboard-xxxx 是 Kubernetes 的用户界面,l7-default-backend-xxxx 提供了新的第 7 层负载均衡功能的默认负载均衡后端,heapster-v1.2.0-xxxxmonitoring-influx-grafana 提供了 Heapster 数据库和用于监视集群资源使用情况的用户界面。最后,kube-proxy-kubernetes-minion-group-xxxx 是将流量定向到集群上正确后备服务和 Pod 的代理。

如果我们 SSH 到一个随机的 minion,我们会看到几个容器跨越其中一些 Pod 运行。示例可能看起来像这样的图片:

图片

Minion 容器列表

同样,我们在主节点上看到了类似的服务排列。我们在主节点上没有看到的服务包括以下内容:

  • 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 上创建的那个。我们可以用一个简单的命令拆除集群:

$ kube-down.sh

与其他提供商合作

默认情况下,Kubernetes 使用 GCE 提供商进行 Google Cloud。我们可以通过设置KUBERNETES_PROVIDER环境变量来覆盖此默认值。此表中列出的值支持以下提供商:

提供商 KUBERNETES_PROVIDER 值 类型
谷歌计算引擎 gce 公有云
谷歌容器引擎 gke 公有云
亚马逊网络服务 aws 公有云
微软 Azure azure 公有云
Hashicorp Vagrant vagrant 虚拟开发环境
VMware vSphere vsphere 私有云/本地虚拟化
运行 CoreOS 的 Libvirt libvirt-coreos 虚拟化管理工具
Canonical Juju(Ubuntu 背后的人) juju 操作系统服务编排工具

Kubernetes 提供商

让我们尝试在 AWS 上设置集群。作为先决条件,我们需要安装并为我们的帐户配置 AWS 命令行界面CLI)。AWS CLI 的安装和配置文档可以在以下链接找到:

然后,这是一个简单的环境变量设置,如下所示:

$ export KUBERNETES_PROVIDER=aws

再次,我们可以使用kube-up.sh命令来启动集群,如下所示:

$ kube-up.sh

与 GCE 一样,设置活动将需要几分钟。它将在我们的 AWS 帐户中的S3中分阶段文件,并创建适当的实例、虚拟专用云VPC)、安全组等等。然后,将设置并启动 Kubernetes 集群。一旦一切都完成并启动,我们应该在输出的末尾看到集群验证:

AWS 集群验证

请注意,集群启动的区域由KUBE_AWS_ZONE环境变量确定。默认情况下,此值设置为us-west-2a(该区域是从此可用区派生的)。即使您在 AWS CLI 中设置了区域,它也将使用KUBE_AWS_ZONE中定义的区域。

再次,我们将 SSH 进入 master。这次,我们可以使用本机 SSH 客户端。我们会在/home/<username>/.ssh中找到密钥文件:

$ ssh -v -i /home/<username>/.ssh/kube_aws_rsa ubuntu@<Your master IP>

我们将使用sudo docker ps --format 'table {{.Image}}t{{.Status}}'来探索正在运行的容器。我们应该看到类似下面的东西:

Master 容器列表(AWS)

我们看到与我们的 GCE 集群相同的一些容器。但也有一些缺失的。我们看到了核心 Kubernetes 组件,但缺少了 fluentd-gcp 服务以及一些新的实用程序,如 node-problem-detectorreschedulerglbckube-addon-manageretcd-empty-dir-cleanup。这反映了各个公共云提供商之间在 kube-up 脚本中的一些微妙差异。这最终由庞大的 Kubernetes 开源社区的努力决定,但 GCP 通常最先拥有许多最新功能。

在 AWS 提供程序上,ElasticsearchKibana 已经为我们设置好。我们可以使用以下语法找到 Kibana UI 的 URL:

https://<your master ip>/api/v1/proxy/namespaces/kube-system/services/kibana-logging

就像 UI 的情况一样,您将被提示输入管理员凭据,可以使用 config 命令获取,如下所示:

$ kubectl config view

初次访问时,你需要设置你的索引。你可以保留默认值,并选择 @timestamp 作为时间字段名称。然后,单击创建,你将进入索引设置页面。从那里,点击顶部的 Discover 标签页,你可以探索日志仪表板:

Kubernetes Kibana 仪表板

重置集群

你刚刚对在 AWS 上运行集群有了一点体验。在本书的剩余部分,我将以 GCE 集群为例。为了更好地跟随示例,你可以轻松地回到 GCE 集群。

简单地拆除 AWS 集群,如下所示:

$ kube-down.sh

然后,再次使用以下方式创建 GCE 集群:

$ export KUBERNETES_PROVIDER=gce
$ kube-up.sh

修改 kube-up 参数

值得了解 kube-up.sh 脚本使用的参数。kubernetes/cluster/ 文件夹下的每个提供程序都有自己的 su 文件夹,其中包含一个 config-default.sh 脚本。

例如,kubernetes/cluster/aws/config-default.sh 中有使用 AWS 运行 kube-up.sh 的默认设置。在该脚本的开头,你将看到许多这些值被定义以及可以用于覆盖默认值的环境变量。

在以下示例中,ZONE 变量被设置用于脚本,并且使用名为 KUBE_AWS_ZONE 的环境变量的值。如果此变量未设置,将使用默认值 us-west-2a

ZONE=${KUBE_AWS_ZONE:-us-west-2a}

了解这些参数将帮助你更好地使用 kube-up.sh 脚本。

kube-up.sh 的替代方法

kube-up.sh 脚本仍然是在你所选择的平台上开始使用 Kubernetes 的一种相当方便的方式。然而,它并非没有缺陷,有时在条件不尽如人意时可能会出现问题。

幸运的是,自 K8 成立以来,已经出现了许多创建集群的替代方法。其中两个 GitHub 项目是 KOPskube-aws。尽管后者与 AWS 绑定,但它们都提供了一种轻松启动新集群的替代方法:

另外,出现了许多受管服务,包括Google Container EngineGKE)和微软Azure Container ServiceACS),它们提供了自动安装和一些受管的集群操作。我们将在第十二章中简要演示这些内容,走向生产就绪

从零开始

最后,还有从零开始的选项。幸运的是,在 1.4 版本中,Kubernetes 团队在简化集群设置过程方面投入了重大精力。为此,他们引入了用于 Ubuntu 16.04、CentOS 7 和 HypriotOS v1.0.1+ 的 kubeadm。

让我们快速了解如何使用 kubeadm 工具从头开始在 AWS 上搭建集群。

集群设置

我们需要提前为集群主节点和节点进行部署。目前,我们受到先前列出的操作系统和版本的限制。此外,建议您至少有 1GB 的 RAM 并且所有节点之间必须具有网络连接。

在本次演示中,我们将在 AWS 上使用一个 t2.medium(主节点)和三个 t2.micro(工作节点)大小的实例。这些实例具有可突发的 CPU,并且配备了所需的最低 1GB RAM。我们需要创建一个主节点和三个工作节点。

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

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

主节点安全组规则

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

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

节点安全组规则

一旦您拥有这些 SG,继续在 AWS 上启动四个实例(一个 t2.medium 和三个 t2.micros),使用 Ubuntu 16.04。如果您对 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

2. 安装 Google Cloud 的公共密钥:

 $ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg |
   apt-key add -    

3. 接下来,使用您喜欢的编辑器为 Kubernetes 包下载创建源列表:

 $ vi /etc/apt/sources.list.d/kubernetes.list

4. 使用以下内容作为此文件的内容并保存:

 deb http://apt.kubernetes.io/ kubernetes-xenial main

图示 1-1. /etc/apt/sources.list.d/kubernetes.list

5. 再次更新您的源:

 $ apt-get update

6. 安装 Docker 和核心 Kubernetes 组件:

 $ apt-get install -y docker.io 
 $ apt-get install -y kubelet kubeadm kubectl kubernetes-cni

设置主节点

在您之前选择为 master 的实例上,我们将运行主初始化。再次以 root 身份运行以下命令:

$ kubeadm init 

请注意,初始化只能运行一次,所以如果遇到问题,您将 kubeadm reset

加入节点

成功初始化后,您将获得一个可以被节点使用的加入命令。将其复制下来以供稍后的加入过程使用。它应该类似于这样:

$ 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

一旦您在 master 上有了这个文件,请使用以下命令创建组件:

$ 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

如果一切顺利,这将显示三个节点和一个主节点,如下所示:

Calico 设置

概要

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

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

参考资料

  1. Malcom McLean 在 PBS 网站上的条目:www.pbs.org/wgbh/theymadeamerica/whomade/mclean_hi.html

  2. Martin Fowler 关于微服务的观点:martinfowler.com/articles/microservices.html

  3. Kubernetes GitHub 项目页面:github.com/kubernetes/kubernetes

  4. www.thoughtworks.com/continuous-integration

  5. docs.docker.com/

  6. kubernetes.io/docs/getting-started-guides/kubeadm/

Pod、Service、Replication Controller 和 Label

本章将介绍核心 Kubernetes 构件,即podservicereplication controllerreplica setlabel。将包括几个简单的应用示例,以演示每个构件。本章还将介绍集群的基本操作。最后,将通过几个示例介绍健康检查调度

本章将讨论以下主题:

  • Kubernetes 的整体架构

  • 介绍核心 Kubernetes 构件,即 pod、service、replication controller、replica set 和 label。

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

  • 了解如何监视服务和容器的健康状况

  • 了解如何根据可用集群资源设置调度约束

架构

尽管Docker在容器管理方面带来了有用的抽象层和工具,但 Kubernetes 也为规模化编排容器和管理完整应用程序堆栈提供了类似的帮助。

K8s在堆栈上升,为我们提供处理应用程序或服务级别管理的构件。这为我们提供了自动化和工具,以确保高可用性、应用程序堆栈和服务的整体可移植性。K8s 还允许我们对资源使用进行更精细的控制,例如 CPU、内存和磁盘空间。

Kubernetes 通过给我们关键构件来组合多个容器、端点和数据成为完整的应用程序堆栈和服务,提供了这种更高级别的编排管理。K8s 还提供了管理堆栈及其组件的何时、何地和多少的工具:

Kubernetes 核心架构

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

注意仔细理解期望状态和实际状态的概念。这是 Kubernetes 管理集群及其工作负载的关键。K8s 的所有部件都在不断工作,监视当前的实际状态,并将其与管理员通过 API 服务器或kubectl脚本定义的期望状态同步。有时这些状态不会匹配,但系统始终在努力协调这两者。

主节点

实际上,master是我们集群的大脑。在这里,我们有核心 API 服务器,它维护 RESTful Web 服务,用于查询和定义我们期望的集群和工作负载状态。重要的是要注意,控制平面只通过主节点发起更改,而不是直接访问节点。

另外,主节点包括 调度器,它与 API 服务器一起工作,以在实际的从节点上调度 pod 形式的工作负载。这些 pod 包含组成我们应用程序堆栈的各种容器。默认情况下,基本的 Kubernetes 调度器将 pod 分布在整个集群中,并使用不同的节点来匹配 pod 的副本。Kubernetes 还允许为每个容器指定必要的资源,因此可以通过这些额外因素来改变调度。

复制控制器/副本集与 API 服务器一起工作,确保任何时候运行正确数量的 pod 副本。这是理想状态概念的典范。如果我们的复制控制器/副本集定义了三个副本,而我们的实际状态是两个 pod 副本正在运行,那么调度器将被调用,在集群的某个地方添加第三个 pod。如果在任何给定时间集群中运行的 pod 过多,也是如此。通过这种方式,K8s 总是朝着理想状态的方向努力。

最后,我们有 etcd 作为分布式配置存储运行。Kubernetes 状态存储在这里,etcd 允许监视值的变化。将其视为大脑的共享内存。

节点(以前称为 minions)

在每个节点中,我们有一些组件组成。kubelet 与 API 服务器交互,以更新状态并启动调度器调用的新工作负载。

Kube-proxy 提供基本负载均衡,并将特定服务的流量引导到后端合适的 pod。请参考本章后面的 服务 部分。

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

在 v1.0 中,minion 更名为 node,但在一些网络命名脚本和文档中仍然保留了 minion 一词的痕迹。为了清晰起见,在整本书中,我在一些地方同时添加了 minion 和 node 这两个术语。

核心构造

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

Pods

Pods 允许你将相关容器在网络和硬件基础设施方面保持密切联系。数据可以靠近应用程序,因此可以在不经历网络遍历的高延迟情况下进行处理。同样,常见的数据可以存储在多个容器之间共享的卷上。Pods 本质上允许你逻辑地将容器和应用程序堆栈的各部分组合在一起。

虽然 Pod 内部可能运行一个或多个容器,但 Pod 本身可能是在 Kubernetes 节点(从属节点)上运行的众多 Pod 之一。正如我们将看到的,Pod 为我们提供了一个逻辑容器组,我们可以在其中复制、调度并通过负载均衡服务端点。

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并注册,将文件直接通过电子邮件发送给您。

使用您喜欢的编辑器创建以下文件:

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

清单 2-1nodejs-pod.yaml

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

$ 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 显示为运行状态,我们就可以使用此功能在 Pod 内运行命令:

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

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

运行命令后,您将看到一些 HTML 代码。在本章后面我们将有一个更漂亮的视图,但现在,我们可以看到我们的 Pod 确实按预期运行。

标签

标签为我们提供了另一种分类级别,在日常运维和管理方面非常有帮助。类似于标记,标签可以用作服务发现的基础,同时也是日常运维和管理任务的有用分组工具。

标签只是简单的键值对。您会在 Pod、复制控制器、副本集、服务等资源上看到它们。该标签充当选择器,告诉 Kubernetes 为各种操作使用哪些资源。可以将其视为过滤选项。

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

容器的余生

正如 AWS 的首席技术官 Werner Vogels 所说的那样一切都会遇到故障:容器和 Pod 可能会崩溃、损坏,甚至可能会被手忙脚乱的管理员误关闭一个节点上的操作。强大的策略和安全实践如最低权限原则会遏制部分这类事件,但不可抗拒的工作量屠杀发生是运维的现实。

幸运的是,Kubernetes 提供了两个非常有价值的构建,以将这个庄严的事务都整洁地隐藏在幕后。服务和复制控制器/副本集使我们能够在几乎没有干扰和优雅恢复的情况下保持我们的应用程序运行。

服务

服务允许我们将访问方式与我们应用程序的消费者进行分离。使用可靠的端点,用户和其他程序能够无缝访问运行在您的集群上的 Pod。

K8s 通过确保集群中的每个节点运行一个名为kube-proxy的代理来实现这一点。正如其名称所示,kube-proxy的工作是将服务端点的通信代理回到运行实际应用程序的相应 Pod。

kube-proxy 架构

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

服务定义的更新是从 K8s 集群主节点监视和协调,并传播到运行在每个节点上的kube-proxy 守护程序

目前,kube-proxy 正在节点主机上运行。未来计划将其和 kubelet 默认容器化。

复制控制器和副本集

复制控制器RCs)正如其名称所示,管理着 Pod 和包含的容器镜像运行的节点数。它们确保特定数量的此镜像的实例正在被运行。

随着您开始将容器和 Pod 运营化,您需要一种方式来滚动更新、扩展正在运行的副本数量(上下扩展),或者只需确保您的堆栈至少运行一个实例。RCs 提供了一个高级机制,以确保整个应用程序和集群的运行正常。

RC(Replication Controllers)的任务很简单,即确保您的应用程序具有所需的规模。您定义要运行的 Pod 副本的数量,并为其提供如何创建新 Pod 的模板。与服务一样,我们将使用选择器和标签来定义 Pod 在复制控制器中的成员资格。

Kubernetes 不要求复制控制器的严格行为,这对长时间运行的进程非常理想。事实上,作业控制器可以用于短期工作负载,允许作业运行到完成状态,并且非常适合批处理工作。

副本集,是一种新型类型,目前处于 Beta 版,代表了复制控制器的改进版本。目前的主要区别在于能够使用新的基于集合的标签选择器,正如我们将在下面的示例中看到的。

我们的第一个 Kubernetes 应用程序

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

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

使用您喜欢的编辑器创建以下文件:

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

清单 2-2nodejs-controller.yaml

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

  • 类型:这告诉 K8s 我们正在创建的资源类型。在这种情况下,类型是 ReplicationControllerkubectl 脚本使用单个 create 命令来处理所有类型的资源。这里的好处是您可以轻松创建各种类型的资源,而无需为每种类型指定单独的参数。但是,这要求定义文件能够识别它们所指定的内容。

  • apiVersion:这只是告诉 Kubernetes 我们正在使用的模式的版本。

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

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

现在,让我们来看一下服务定义:

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

清单 2-3: nodejs-rc-service.yaml

这里的 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) 列中,我们应该有一个私有和一个公共的(截图中为 130.211.186.84)IP 地址。如果您看不到外部 IP,请等待一分钟以从 GCE 中分配 IP。让我们尝试在浏览器中打开公共地址来连接:

容器信息应用程序

您应该看到类似 容器信息应用程序 的图形。如果我们多次访问,您应该注意到容器名称的变化。基本上,服务负载均衡器在后端可用的 pod 之间轮转。

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

hide.me/en/proxy

让我们试着玩一下混沌猴,关闭一些容器,看看 Kubernetes 会做什么。为了做到这一点,我们需要查看 pod 实际运行的位置。首先,让我们列出我们的 pod:

$ kubectl get pods

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

当前正在运行的 Pod

现在,让我们对运行node-js容器的一个 Pod 获取更多详细信息。你可以使用上一个命令中列出的一个 Pod 名称执行此操作:

$ kubectl describe pod/node-js-sjc03

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

Pod 描述

你应该看到前面的输出。我们需要的信息是Node:部分。让我们使用节点名称SSH(缩写为安全外壳)进入运行此工作负载的(从属)节点:

$ 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容器在运行,但仔细观察你会发现容器 id不同,并且创建时间戳显示只是几秒钟前。如果你返回到服务的 URL,它正常运行。现在可以退出 SSH 会话了。

在这里,我们已经看到 Kubernetes 扮演着随时待命的运维角色,确保我们的应用程序始终运行。

让我们看看是否能找到任何中断的证据。进入 Kubernetes UI 中的事件页面。你可以通过导航到 K8s 主仪表板上的 Nodes 页面找到它。从列表中选择一个节点(我们 SSH 进入的相同节点)并滚动到节点详细信息页面的 Events 下。

你将看到一个类似于以下截图的屏幕:

Kubernetes UI 事件页面

你应该看到三个最近的事件。首先,Kubernetes 拉取镜像。其次,它使用拉取的镜像创建一个新的容器。最后,它再次启动该容器。你会注意到,从时间戳来看,所有这些都发生在不到一秒的时间内。所花费的时间可能会根据集群大小和镜像拉取而有所不同,但恢复非常快。

更多关于标签的信息

如前所述,标签只是简单的键值对。它们可用于 Pod、复制控制器、副本集、服务等。如果你回忆一下我们的服务 YAML,在 清单 2-3nodejs-rc-service.yaml中,有一个selector属性。selector属性告诉 Kubernetes 在查找要转发流量的 Pod 时使用哪些标签。

K8s 允许用户直接在复制控制器、副本集和服务上使用标签。让我们修改我们的副本和服务,以包含更多标签。再次使用你喜欢的编辑器创建这两个文件,如下所示:

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

清单 2-4nodejs-labels-controller.yaml

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

清单 2-5nodejs-labels-service.yaml

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

$ 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)
<键名> 仅使用键名选择包含此键的标签资源 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 --create-external-load-balancer=true** 这将创建一个名为testing-vip的服务,以及一个可以用于通过端口80 访问此 pod 的公共 VIP(负载均衡器 IP)。还有许多其他可选参数可用。可以使用以下命令查找这些参数:

kubectl expose --help

副本集

正如之前讨论的,复制集是复制控制器的新版本和改进版。它们利用基于集合的标签选择,但在撰写本文时仍被视为 beta 版本。

这是一个基于ReplicaSet的示例,与列表 2-4中的ReplicationController类似:

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

列表 2-6nodejs-labels-replicaset.yaml

健康检查

Kubernetes 提供两层健康检查。首先,以 HTTP 或 TCP 检查的形式,K8s 可以尝试连接到特定端点,并在成功连接时给出健康状态。其次,可以使用命令行脚本执行特定于应用程序的健康检查。

让我们看看一些健康检查的实际操作。首先,我们将创建一个带有健康检查的新控制器:

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

列表 2-7nodejs-health-controller.yaml

注意livenessprobe元素的添加。这是我们的核心健康检查元素。从这里,我们可以指定httpGettcpScoketexec。在这个例子中,我们使用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,它将从回答服务端点的 pods 池中删除一个容器。

让我们修改健康检查,指向我们容器中存在的页面,这样我们就有了一个正确的健康检查。我们还将添加一个 readiness 检查,并指向不存在的状态页面。打开nodejs-health-controller.yaml文件,并修改spec部分以匹配Listing 2-8,然后保存为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

Listing 2-8nodejs-health-controller-2.yaml

这一次,我们将删除旧的 RC,这将导致其中的 pods 被终止,并使用我们更新的 YAML 文件创建一个新的 RC:

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

现在,当我们描述其中一个 pods 时,我们只会看到 pod 和容器的创建。然而,您会注意到服务负载均衡器 IP 不再起作用。如果我们在新节点上运行describe命令,我们将注意到一个Readiness probe failed错误消息,但 pod 本身仍在运行。如果我们将 readiness 探针路径更改为path: /,我们将再次能够满足主服务的请求。现在在编辑器中打开nodejs-health-controller-2.yaml,并进行更新。然后,再次删除并重新创建复制控制器:

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

现在负载均衡器 IP 应该可以再次工作了。保留这些 pods,因为我们将在 Chapter 3,网络、负载均衡器和入口中再次使用它们。

TCP 检查

Kubernetes 还支持通过简单的 TCP 套接字检查和自定义命令行脚本进行健康检查。以下片段是这两种用例在 YAML 文件中的示例:

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

Listing 2-9使用命令行脚本进行健康检查

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

Listing 2-10使用简单的 TCP 套接字连接进行健康检查

生命周期钩子或优雅关闭

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

下面的示例控制器定义了一个 postStart 动作和一个 preStop 动作,在 Kubernetes 将容器移入其生命周期的下一阶段之前执行(你可以在本章末尾的 参考文献 中的第 1 点中查看更多详情):

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"]

清单 2-11apache-hooks-controller.yaml

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

postStartpreStop 钩子都支持 httpGetexec 操作。对于 preStop,将会将一个名为 reason 的参数发送给处理程序作为参数。参见以下表格获取有效值:

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

有效的 preStop 原因(请参阅 参考文献 中的第 1 点)

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

应用程序调度

现在我们了解了如何在 pod 中运行容器,甚至从失败中恢复,了解如何在我们的集群节点上调度新容器可能会很有用。

如前所述,Kubernetes 调度程序的默认行为是在集群的节点之间分布容器副本。在所有其他约束条件都不存在的情况下,调度程序会将新的 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 定义的示例:

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"

列表 2-12nodejs-constraints-controller.yaml

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

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

复制控制器成功完成,但如果我们运行get pods命令,我们会注意到node-js-constraints pods 陷入了等待状态。如果我们用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. github.com/GoogleCloudPlatform/kubernetes/blob/release-1.0/docs/user-guide/container-environment.md#container-hooks

网络、负载平衡和入口控制器

在这一章节中,我们将要覆盖 Kubernetes 集群如何处理网络,以及它和其他方法的不同之处。我们将描述 Kubernetes 网络解决方案的三个要求,并探讨为什么这些对于操作的便捷性至关重要。另外,我们将深入介绍服务以及 Kubernetes 代理在每个节点上的工作方式。最后,我们将简要概述一些用于多租户的更高级别的隔离特性。

这个章节将会讨论以下内容:

  • Kubernetes 网络

  • 高级服务概念

  • 服务发现

  • DNS

  • 命名空间限制和配额

Kubernetes 网络

网络是生产级别运作的重要考虑因素。在服务层面上,我们需要一种可靠的方法来找到和与应用程序组件通信。引入容器和聚类使得事情更加复杂,因为现在我们必须考虑多个网络命名空间。通信和发现现在需要穿越容器 IP 空间、主机网络,甚至是多个数据中心的网络拓扑。

Kubernetes 受益于其祖先来自 Google 在过去十年使用的聚类工具。网络是 Google 超越竞争对手的领域之一,其拥有地球上最大的网络之一。早些时候,Google 构建了自己的硬件交换机和软件定义网络(SDN),以在日常网络操作中获得更多的控制、冗余和效率(您可以在本章节结尾的“参考”部分中的第 1 点中了解更多详细信息)。从每周运行和网络化的 20 亿个容器中汲取的许多经验教训已经提炼成了 Kubernetes,并指导了 K8s 网络的实现方式。

在 Kubernetes 中进行网络操作需要每个 Pod 有其自己的 IP 地址。基础设施提供商的实现细节可能会有所不同。但是,所有实现都必须遵守一些基本规则。首先和其次,Kubernetes 不允许在容器与容器或容器与节点(minion)之间使用网络地址转换(NAT)。此外,内部容器 IP 地址必须与用于与其通信的 IP 地址匹配。

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

K8s 通过一个占位符来实现这种整个 pod 范围的 IP 魔法。记住,我们在第一章中看到的pause容器,在介绍 Kubernetes在主节点上运行的服务部分,通常被称为pod 基础设施容器,它的重要工作是为稍后启动的应用容器保留网络资源。实质上,pause容器持有整个 pod 的网络命名空间和 IP 地址,并且可以被所有正在运行的容器使用。pause容器首先加入并持有命名空间,随后在 pod 中启动时,后续容器加入其中。

网络选项

Kubernetes 提供了各种网络选项。有些解决方案适用于 AWS 和 GCP 中的本机网络层。还有各种覆盖插件,其中一些将在下一节中讨论。最后,还支持容器网络接口CNI)插件。CNI 旨在成为容器的通用插件架构。它目前得到了几个编排工具的支持,如 Kubernetes、Mesos 和 CloudFoundry。更多信息请访问:

github.com/containernetworking/cni.

请始终参考 Kubernetes 文档以获取最新和完整的支持网络选项列表。

网络比较

为了更好地理解容器中的网络,可以研究其他容器网络的方法。以下方法并非穷尽列表,但应该让你对可用选项有所了解。

Docker

Docker 引擎默认创建三种类型的网络。这些是桥接主机

桥接网络是默认选择,除非另有说明。在此模式下,容器有自己的网络命名空间,然后通过虚拟接口桥接到主机(或在 K8s 情况下是节点)网络。在桥接网络中,两个容器可以使用相同的 IP 范围,因为它们是完全隔离的。因此,服务通信需要通过网络接口的主机侧进行一些额外的端口映射。

Docker 还支持主机网络,允许容器使用主机网络堆栈。性能得到了极大的改善,因为它消除了一个网络虚拟化的层级;然而,你失去了拥有独立网络命名空间的安全性。此外,必须更加谨慎地管理端口使用,因为所有容器共享一个 IP。

最后,Docker 支持一个 none 网络,它创建一个没有外部接口的容器。如果检查网络接口,只显示一个回环设备。

在所有这些场景中,我们仍然位于单个机器上,而且在主机模式之外,容器 IP 空间对于该机器外部是不可用的。连接跨越两台机器的容器然后需要进行 NAT端口映射 以进行通信。

Docker 用户定义的网络

为了解决跨机器通信问题并提供更大的灵活性,Docker 还通过网络插件支持用户定义的网络。这些网络独立于容器本身存在。通过这种方式,容器可以加入相同的现有 网络。通过新的插件架构,可以为不同的网络用例提供各种驱动程序。

这些中的第一个是 bridge 驱动程序,它允许创建与默认桥接网络类似的网络。

第二个是 overlay 驱动程序。为了跨多个主机进行协调,它们都必须就可用网络及其拓扑达成一致。覆盖驱动程序使用分布式键值存储来在多个主机之间同步网络创建。

Docker 还支持一个 Macvlan 驱动程序,该驱动程序使用主机上的接口和子接口。Macvlan 提供了更有效的网络虚拟化和隔离,因为它绕过了 Linux 桥接。

插件机制将允许 Docker 中的各种网络可能性。事实上,许多第三方选项,如 Weave,已经创建了自己的 Docker 网络插件。

Weave

Weave 为 Docker 容器提供了覆盖网络。它可以作为新的 Docker 网络插件接口的插件使用,并且还与 Kubernetes 兼容通过 CNI 插件。像许多覆盖网络一样,许多人批评封装开销对性能的影响。请注意,他们最近添加了一个具有 Virtual Extensible LAN (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

平衡设计

强调 Kubernetes 正在尝试通过将 IP 放置在 pod 级别来实现的平衡是很重要的。在主机级别使用唯一的 IP 地址存在问题,因为容器数量增加。必须使用端口来公开特定容器上的服务并允许外部通信。除此之外,运行可能知道或不知道彼此(及其自定义端口)的多个服务并管理端口空间的复杂性成为一个重大问题。

但是,为每个容器分配一个 IP 地址可能过度。在规模可观的情况下,需要使用覆盖网络和 NAT 来解决每个容器的问题。覆盖网络会增加延迟,并且 IP 地址也将被后端服务占用,因为它们需要与其前端对等体进行通信。

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

如果您需要覆盖网络的灵活性,仍然可以在 pod 级别使用覆盖层。Weave、Flannel 和 Project Calico 以及现在可用的大量其他插件和覆盖层都可以与 Kubernetes 一起使用。

这在调度工作负载的背景下也非常有帮助。对于调度器来说,拥有一个简单且标准的结构来匹配约束并了解集群网络上任何给定时间的空间是至关重要的。这是一个具有各种应用程序和任务的动态环境,因此在这里增加额外的复杂性会产生连锁效应。

还涉及服务发现的影响。上线的新服务必须确定并注册一个 IP 地址,其他服务或至少集群可以通过该 IP 地址访问它们。如果使用了 NAT,服务将需要另一个机制来学习其外部可访问的 IP。

高级服务

让我们探讨与服务和容器之间通信相关的 IP 策略。如果你还记得,在服务部分,第二章 Pods, Services, Replication Controllers, and 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 直接编码在 iptables 规则中。这消除了对 kube-proxy 实际代理流量的依赖。请求将直接发送到 iptables,然后转发到 Pod。这样做更快,也消除了一个可能的故障点。如我们在健康检查部分中讨论的那样,就像您的朋友一样,此模式还丢失了重试 Pod 的能力。

外部服务

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

让我们再深入一点,在第二章中的标签更多内容部分对其中一个服务进行describe命令:

$ kubectl describe service/node-js-labels

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

服务描述

在上述图中的输出中,您会注意到几个关键元素。我们的Namespace:设置为defaultType:LoadBalancer,并且我们在LoadBalancer Ingress:下列出了外部 IP。此外,我们看到了Endpoints:,它显示了可用于响应服务请求的 Pod 的 IP。

内部服务

让我们深入了解我们可以部署的其他类型的服务。首先,默认情况下,服务只面向内部。您可以指定clusterIP类型来实现此目的,但是,如果未定义类型,则clusterIP是假定的类型。让我们看一个例子;请注意缺少type元素:

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

清单 3-1nodejs-service-internal.yaml

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

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

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

内部服务列表

如您所见,我们有一个新的服务,但只有一个 IP。此外,IP 地址无法从外部访问。这次我们无法从 Web 浏览器测试服务。但是,我们可以使用便捷的kubectl exec命令,并尝试从其他一些 Pod 连接。您需要运行node-js-pod清单 2-1nodejs-pod.yaml)。然后,您可以执行以下命令:

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

这使我们能够像在 node-js-pod 容器中有一个 shell 一样运行 docker exec 命令。然后它命中内部服务 URL,该 URL 转发到具有 node-js 标签的任何 pod。

如果一切正常,您应该会得到原始 HTML 输出。因此,您成功创建了一个仅内部可用的服务。这对于您希望向集群中运行的其他容器提供的后端服务可能会有用,但不对整个世界开放。

自定义负载均衡

K8s 允许的第三种服务类型是 NodePort 类型。这种类型允许我们通过特定端口在主机或节点(minion)上暴露服务。通过这种方式,我们可以使用任何节点(minion)的 IP 地址,并在分配的节点端口上访问我们的服务。Kubernetes 将默认在 3000-32767 范围内分配节点端口,但您也可以指定自己的自定义端口。在 清单 3-2 中的示例中,我们选择端口 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 

清单 3-2nodejs-service-nodeport.yaml

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

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

输出应该有类似以下的消息:

新的 GCP 防火墙规则

您会注意到有关打开防火墙端口的消息。与外部负载均衡器类型类似,NodePort 使用节点上的端口将您的服务外部暴露出来。例如,如果您想在节点前面使用自己的负载均衡器,则这可能很有用。在测试新服务之前,让我们确保在 GCP 上打开这些端口。

从 GCE VM 实例控制台,点击任何节点(minion)的详细信息。然后点击网络,通常是默认的,除非在创建时另有规定。在防火墙规则中,我们可以通过单击添加防火墙规则来添加规则。

创建一个规则,如下图所示(tcp:300010.0.0.0/0 IP 范围上):

创建新的 GCP 防火墙规则

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

http://<Minoion IP 地址>:<NodePort>/

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

跨节点代理

记住,kube-proxy 在所有节点上运行,因此,即使 pod 在那里没有运行,流量也会被代理到适当的主机。参考跨节点流量图以了解流量如何流动。用户向外部 IP 或 URL 发出请求。此时请求由节点处理。然而,该 pod 恰好没有在此节点上运行。这并不是问题,因为 pod IP 地址是可路由的。因此,Kube-proxyiptables 简单地将流量传递到此服务的 pod IP。然后网络路由完成在 节点 2 上,请求的应用程序驻留在那里:

跨节点流量

自定义端口

服务还允许你将流量映射到不同的端口;然后容器和 pod 将自己暴露出来。我们将创建一个服务,将流量暴露到 90 端口并转发到 pod 上的 80 端口。我们将称这个 pod 为 node-js-90 来反映自定义端口号。创建以下两个定义文件:

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 

清单 3-3: nodejs-customPort-controller.yaml

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 

清单 3-4: nodejs-customPort-service.yaml

你会注意到在服务定义中,我们有一个 targetPort 元素。这个元素告诉服务使用池中的 pod/容器的端口。就像我们在之前的例子中看到的,如果你不指定 targetPort,它会假定与服务相同的端口。这个端口仍然被用作服务端口,但是在这种情况下,我们将在 90 端口上暴露服务,而容器则在 80 端口上提供内容。

创建这个 RC 和服务并打开适当的防火墙规则,就像我们在上一个示例中所做的一样。外部负载均衡器 IP 可能需要一段时间才能传播到 get service 命令。一旦传播完成,你就应该能够以以下格式在浏览器中打开并查看我们熟悉的 web 应用程序:

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

多个端口

另一个自定义端口的用例是多个端口的情况。许多应用程序会暴露多个端口,比如 80 端口上的 HTTP 和 8888 端口上的 web 服务器。下面的示例展示了我们的应用同时在这两个端口上响应。再次强调,我们还需要为这个端口添加防火墙规则,就像我们之前为 清单 3-2: nodejs-service-nodeport.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 

清单 3-5: nodejs-multi-controller.yaml

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 

清单 3-6: nodejs-multi-service.yaml

应用程序和容器本身必须同时监听这两个端口才能工作。在这个例子中,端口 8888 被用来表示一个虚假的管理员界面。

例如,如果你想监听 443 端口,你需要在服务器上使用适当的 SSL 套接字进行监听。

内部访问

我们之前讨论过 Kubernetes 如何使用服务抽象来代理分布在集群中的后端 pod 的流量。虽然这在扩展和 pod 恢复方面都很有帮助,但是这种设计并没有解决更高级的路由场景。

为此,Kubernetes 添加了一个 Ingress 资源,允许对后端服务进行自定义代理和负载均衡。可以把它想象成在流量到达我们的服务之前的路由路径中的一个额外层或跳跃。就像一个应用程序有一个服务和支持的 pod 一样,Ingress 资源需要一个 Ingress 入口点和一个执行自定义逻辑的 Ingress 控制器。入口点定义了路由,控制器实际处理路由。在我们的示例中,我们将使用默认的 GCE 后端。

使用 Ingress API 时需要注意的一些限制可以在这里找到:

github.com/kubernetes/contrib/blob/master/ingress/controllers/gce/BETA_LIMITATIONS.md

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

$ kubectl get rc --namespace=kube-system

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

GCE Layer 7 Ingress 控制器

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

首先,我们将使用我的 httpwhalesay 镜像创建几个新的复制控制器。这是原始的 whalesay 的一次混音,可以在浏览器中显示。以下清单显示了 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

清单 3-7. whale-rcs.yaml

请注意,我们正在创建具有相同容器的 pod,但具有不同的启动参数。记下这些参数以备后用。我们还将为这些 RC 的每一个创建 Service 端点:

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

清单 3-8. whale-svcs.yaml

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

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

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

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

清单 3-9. whale-ingress.yaml

再次使用 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 的东西创建一个服务,因此没有标签可供选择,我们可以很容易地通过服务和端点定义来实现,如下所示:

apiVersion: v1 
kind: Service 
metadata: 
  name: custom-service 
spec: 
  type: LoadBalancer 
  ports: 
  - name: http 
    protocol: TCP 
    port: 80 

清单 3-10nodejs-custom-service.yaml

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

清单 3-11nodejs-custom-endpoint.yaml

在上面的示例中,您需要用实际 IP 地址替换 <X.X.X.X>,新服务可以指向该地址。在我的案例中,我使用了我们之前在 清单 3-6 中创建的 node-js-multi 服务的公共负载均衡器 IP。现在就去创建这些资源吧。

如果我们现在运行一个 get endpoints 命令,我们将看到这个 IP 地址关联到 custom-service 端点的 80 端口。此外,如果我们查看服务详情,我们将在 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 将不会分配 IP 地址,而是仅为每个 Pod 在 DNS 中分配 A 记录。如果您使用 DNS,则服务应该可以从集群内的 node-js-nonenode-js-none.default.cluster.local 访问。我们有以下代码:

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

清单 3-12nodejs-headless-service.yaml

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

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

服务发现

正如我们之前讨论的,Kubernetes 主节点会跟踪所有服务定义和更新。发现可以通过以下三种方式之一进行。前两种方法使用 Linux 环境变量。支持 Docker 链接样式的环境变量,但 Kubernetes 也有其自己的命名约定。这是使用 K8s 环境变量的示例,我们的 node-js 服务示例可能看起来像这样(注意 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

清单 3-13服务环境变量

通过 DNS 进行发现的另一种选择。虽然环境变量在 DNS 不可用时可能很有用,但它也有缺点。系统仅在创建时创建变量,因此稍后启动的服务将无法发现,或者需要一些额外的工具来更新所有系统环境。

DNS

DNS 通过允许我们通过名称引用服务来解决使用环境变量时出现的问题。随着服务重新启动、扩展或出现新的情况,DNS 条目将被更新,确保服务名称始终指向最新的基础架构。在大多数支持的提供商中,默认设置了 DNS。

如果您的提供商支持 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

多租户

Kubernetes 还具有在集群级别进行隔离的附加结构。在大多数情况下,您可以运行 Kubernetes 而不必担心命名空间;如果未指定,所有内容都将在默认命名空间中运行。但是,在运行多租户社区或希望对集群资源进行广泛分离和隔离的情况下,可以使用命名空间来实现此目的。

首先,Kubernetes 有两个命名空间——defaultkube-systemkube-system命名空间用于所有在第一章中看到的系统级容器,在运行在节点上的服务节中。用户创建的所有其他内容都在默认命名空间中运行。但是,用户的资源定义文件可以选择指定自定义命名空间。为了进行实验,让我们看看如何构建一个新的命名空间。

首先,我们需要创建一个命名空间定义文件,就像这个清单中的一个:

apiVersion: v1 
kind: Namespace 
metadata: 
  name: test 

清单 3-14test-ns.yaml

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

$ kubectl create -f test-ns.yaml

现在我们可以创建使用test命名空间的资源。以下是一个使用这个新命名空间的 pod 的示例:

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

清单 3-15ns-pod.yaml

虽然 pod 仍然可以访问其他命名空间中的服务,但它需要使用长 DNS 格式的<service-name>.<namespace-name>.cluster.local。例如,如果您要从清单 3-15ns-pod.yaml内的容器中运行一个命令,您可以使用node-js.default.cluster.local访问第二章中的 Node.js 示例,Pods, Services, Replication Controllers, and Labels

这里有一个关于资源利用的注记。在本书的某个时候,您可能会在集群上耗尽空间以创建新的 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命名空间目前没有设置资源限制配额

假设我们想要限制这个新命名空间的占地面积;我们可以设置如下的配额:

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

清单 3-16quota.yaml

实际上,命名空间将用于更大的应用程序社区,可能永远不会有这么低的配额。我之所以使用这个例子,是为了更轻松地说明示例中的功能。

在这里,我们将为测验命名空间创建一个3个 pod、1个 RC 和1个服务的配额。正如你可能猜到的那样,这又一次由我们值得信赖的create命令执行:

$ kubectl create -f quota.yaml

现在我们已经做好了,让我们对命名空间使用describe,如下所示:

$ kubectl describe namespace/test

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

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

您会注意到现在在配额部分列出了一些值,而限制部分仍然为空白。我们还有一个Used列,它让我们知道我们当前离限制有多近。让我们尝试使用以下定义来启动一些 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" 

列表 3-17busybox-ns.yaml

您会注意到我们正在创建此基本 pod 的四个副本。在使用create构建此 RC 后,再次在test命名空间上运行describe命令。您会注意到 pod 和 RC 的Used值已达到最大值。然而,我们要求四个副本,但只看到三个正在使用的 pod。

让我们看看我们的 RC 正在发生什么。您可以尝试使用此处的命令来执行此操作:

kubectl describe rc/busybox-ns

但是,如果你尝试,你会受挫于从服务器收到的not found消息。这是因为我们在一个新的命名空间创建了这个 RC,如果没有指定,kubectl会假定默认命名空间。这意味着我们在访问test命名空间中的资源时需要在每个命令中指定--namepsace=test

我们还可以通过处理上下文设置来设置当前命名空间。首先,我们需要找到我们的当前上下文,这是通过以下命令找到的:

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

接下来,我们可以获取该上下文并设置命名空间变量如下:

**$ kubectl config set-context <当前上下文> --namespace=test**

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

使用指定了命名空间的命令运行。如果您已按提示框中所示设置了当前命名空间,可以省略--namespace参数:

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

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

命名空间配额

如您在前面的图像中所见,前三个 pod 已成功创建,但我们的最后一个失败了,出现了Limited to 3 pods错误。

这是一种在社区规模上设置资源限制的简单方法。值得注意的是,您还可以设置 CPU、内存、持久卷和密钥的配额。此外,限制的工作方式与配额类似,但它们为命名空间内的每个 pod 或容器设置了限制。

关于资源使用的说明

由于本书中的大多数示例都使用 GCP 或 AWS,保持所有内容运行可能成本很高。如果使用默认的集群大小,尤其是如果保留每个示例运行,则很容易耗尽资源。因此,您可能希望定期删除旧的 pod、复制控制器、副本集和服务。您还可以销毁集群,并使用第一章——介绍 Kubernetes作为降低云服务提供商账单的方法。

摘要

我们深入研究了 Kubernetes 中的网络和服务。现在你应该了解了 K8s 中网络通信的设计,并且能够在内部和外部轻松访问你的服务。我们看到了 kube-proxy 如何在本地和整个集群中平衡流量。此外,我们探讨了新的 Ingress 资源,使我们能够更精细地控制流入流量。我们还简要地了解了 Kubernetes 中如何实现 DNS 和服务发现。最后,我们简单地看了一下多租户环境下的命名空间和隔离。

参考资料

  1. www.wired.com/2015/06/google-reveals-secret-gear-connects-online-empire/

更新、渐进式发布和自动扩展

本章将扩展核心概念,向您展示如何在最小中断上线时间内推出更新并测试应用程序的新功能。它将介绍进行应用程序更新、渐进式发布和 A/B 测试的基础知识。此外,我们还将了解如何扩展 Kubernetes 集群本身。

本章将讨论以下主题:

  • 应用程序扩展

  • 滚动更新

  • A/B 测试

  • 应用程序自动扩展

  • 扩展您的集群

从版本 1.2 开始,Kubernetes 发布了一个部署 API。在处理扩展和应用程序更新方面,部署是推荐的方法。然而,在撰写本书时,它仍被视为测试版,而滚动更新已经稳定了几个版本。本章将探讨滚动更新作为引入扩展概念的一种方法,然后在下一章深入探讨使用部署的首选方法。

示例设置

在探索 Kubernetes 中用于扩展和更新的各种功能之前,我们将需要一个新的示例环境。我们将使用具有蓝色背景的先前容器映像的变体(请参考本章后面的v0.1 和 v0.2(并排)图像进行比较)。我们有以下代码:

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

列表 4-1pod-scaling-controller.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

列表 4-2pod-scaling-service.yaml

使用以下命令创建这些服务:

$ kubectl create -f pod-scaling-controller.yaml
$ kubectl create -f pod-scaling-service.yaml

服务的公共 IP 地址可能需要一段时间才能创建。

扩展

随着时间推移,在 Kubernetes 集群中运行应用程序时,您会发现一些应用程序需要更多资源,而其他应用程序可以使用更少的资源。我们希望有一种更无缝的方式来使我们的应用程序进行扩展。而不是删除整个 RC(及其关联的 pod)。

幸运的是,Kubernetes 包含一个scale命令,专门用于此目的。scale命令既适用于复制控制器,也适用于新的部署抽象。目前,我们将使用复制控制器探索其用法。在我们的新示例中,只有一个副本在运行。您可以使用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 命令允许我们更新整个 RC 或仅更新每个副本使用的底层 Docker 镜像。我们还可以指定更新间隔,这将允许我们逐个更新一个 pod,并等待继续下一个。

让我们以我们的缩放示例为例,并对我们容器映像的 0.2 版本执行滚动更新。我们将使用 2 分钟的更新间隔,这样我们可以观察到进程如何进行:

$ kubectl rolling-update node-js-scale --image=jonbaier/pod-scaling:0.2 --update-period="2m"

您应该会看到一些关于创建名为 node-js-scale-XXXXX 的新 RC 的文本,其中 X 字符将是一串随机的数字和字母。此外,您将看到一个循环的开始,它开始一个新版本的副本,并从现有 RC 中删除一个。这个过程将继续,直到新的 RC 具有完整的副本计数运行。

如果我们想实时跟踪,可以打开另一个终端窗口,并使用 get pods 命令,再加上一个标签过滤器,来查看正在发生的情况:

$ kubectl get pods -l name=node-js-scale

此命令将筛选出名称中带有 node-js-scale 的 pod。如果在发出 rolling-update 命令后运行此命令,您应该会看到几个 pod 在运行,因为它逐个创建新版本并逐个删除旧版本。

前面 rolling-update 命令的完整输出应该看起来像下面的截图一样:

缩放输出

正如我们在这里所看到的,Kubernetes 首先创建了一个名为 node-js-scale-10ea08ff9a118ac6a93f85547ed28f6 的新 RC。然后,K8s 逐个循环,创建一个新的 pod 在新的控制器中,并从旧的控制器中删除一个。这个过程会持续到新的控制器达到完整的副本数量,而旧的控制器为零。之后,旧的控制器被删除,新的控制器被重命名为原始的控制器名称。

如果现在运行 get pods 命令,您会注意到所有的 pod 仍然有一个更长的名称。或者,我们可以在命令中指定一个新控制器的名称,Kubernetes 将使用该名称创建一个新的 RC 和 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 缩放 v0.2,背景为浅黄色:

v0.1 和 v0.2(并列)

值得注意的是,在整个更新过程中,我们只关注了 pod 和 RC。我们没有对我们的服务做任何操作,但服务仍然正常运行,并且现在指向我们的 pod 的新版本。这是因为我们的服务正在使用标签选择器进行成员身份验证。因为我们的旧副本和新副本都使用相同的标签,所以服务没有问题使用新副本来提供请求服务。更新是逐个 pod 进行的,所以对于服务的用户来说是无缝的。

测试、发布和切换

滚动更新功能可以很好地适用于简单的蓝绿部署场景。但是,在具有多个应用程序堆栈的实际蓝绿部署中,可能存在各种相互依赖关系,需要进行深入测试。update-period 命令允许我们添加一个 timeout 标志,其中可以进行一些测试,但这并不总是令人满意的测试目的。

类似地,您可能希望部分更改持续时间更长,一直到负载均衡器或服务级别。例如,您可能希望在一部分用户身上运行新用户界面功能的 A/B 测试。另一个例子是在新添加的集群节点等新基础设施上运行您应用程序的金丝雀发布(在这种情况下是副本)。

让我们看一个 A/B 测试的例子。对于此示例,我们需要创建一个新的服务,该服务使用 sessionAffinity。我们将亲和性设置为 ClientIP,这将允许我们将客户端转发到相同的后端 pod。这是关键的,如果我们希望我们的一部分用户看到一个版本,而其他用户看到另一个版本:

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

清单 4-3:pod-AB-service.yaml

像往常一样使用 create 命令创建此服务,如下所示:

$ kubectl create -f pod-AB-service.yaml

这将创建一个指向我们运行应用程序版本 0.2 和 0.3 的 pod 的服务。接下来,我们将创建两个 RC,用于创建应用程序的两个副本。一个集合将具有应用程序版本 0.2,另一个将具有版本 0.3,如下所示:

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

清单 4-4:pod-A-controller.yaml

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

清单 4-5:pod-B-controller.yaml

请注意,我们有相同的服务标签,因此这些副本也将根据此选择器添加到服务池中。我们还定义了 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 的 RC(node-js-scale-a)。作为最后的清理,我们可以完全删除该控制器,如下所示:

$ kubectl delete rc/node-js-scale-a

应用自动扩展

Kubernetes 最近增加的一个功能是水平 Pod 自动缩放器。这种资源类型非常有用,因为它为我们提供了自动设置应用程序扩展阈值的方式。目前,该支持仅针对 CPU,但也有自定义应用程序指标的 alpha 支持。

让我们使用本章开头的node-js-scale复制控制器,并加上一个自动扩缩组件。在开始之前,让我们确保使用以下命令缩减到一个副本:

$ kubectl scale --replicas=1 rc/node-js-scale

现在我们可以创建一个水平 Pod 自动缩放器,其定义如下:

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

表 4-6. node-js-scale-hpa.yaml

继续使用kubectl create -f命令创建这个。现在我们可以列出 hpas 并获得描述:

$ kubectl get hpa 

我们也可以使用kubectl autoscale命令在命令行中创建自动缩放。前面的 YAML 看起来像下面这样:

$ kubectl autoscale rc/node-js-scale --min=1 --max=3 --cpu-percent=20

这将显示一个具有目标 CPU 为 30%的node-js-scale复制控制器上的自动扩展器。此外,您将看到最小 pod 设置为 1,最大设置为 3:

没有负载的水平 Pod 自动缩放器

让我们还查询一下我们的 pod,看看现在有多少在运行:

$ kubectl get pods -l name=node-js-scale

由于我们的 HPA 显示 0% 利用率,因此我们只应该看到一个 node-js-scale pod,因此我们需要生成一些负载。我们将使用在许多容器演示中常见的流行应用程序 boom。以下清单将帮助我们创建连续的负载,直到我们可以达到自动缩放器的 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"]

清单 4-7. boomload.yaml

使用此清单的 kubectl create -f 命令,然后准备好开始监视我们之前使用的 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 环境变量自定义起始节点(minions)的数量。默认情况下,它设置为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 实例组的模板

你会看到一些设置,但模板的核心部分在“自定义元数据”下。在这里,你将看到一些环境变量以及一个在创建新机器实例后运行的启动脚本。这些是允许我们创建新机器并自动将它们添加到可用集群节点的核心组件。

因为新机器的模板已经创建,所以在 GCE 中扩展我们的集群非常简单。一旦进入控制台的计算部分,只需在侧边栏的“实例模板”链接上方找到“实例组”即可。同样,你应该看到一个名为 kubernetes-minion-group 或类似的组。点击该组以查看详细信息,如下面的截图所示:

用于从实例模板创建 GCE 实例组的模板

你会看到一个 CPU 指标图和三个在此列出的实例。默认情况下,集群会创建三个节点。我们可以通过点击页面顶部的“编辑组”按钮来修改这个组:

GCE 实例组编辑页面

你应该看到我们刚才审查的实例模板中选择了 kubernetes-minion-template。你还会看到一个自动缩放设置,默认为关闭状态,以及一个实例计数为3。简单地将此数值增加到4,然后点击“保存”。你将被带回到组详细信息页面,会看到一个显示待定更改的弹出对话框。

你还会在“实例组”编辑页面上看到一些自动修复属性。这会重新创建失败的实例,并允许你设置健康检查以及在执行操作之前的初始延迟时间。

几分钟后,你将在详细信息页面上看到一个新的实例。我们可以使用命令行中的get nodes命令来测试是否准备就绪:

$ kubectl get nodes

关于自动缩放和一般缩减的一些警告首先,如果我们重复之前的过程并将倒计时减少到四,GCE 会移除一个节点。但是,并不一定是你刚刚添加的节点。好消息是,Pod 将在剩余的节点上重新调度。然而,它只能重新调度可用资源的地方。如果你接近满负荷并关闭一个节点,那么有很大的机会一些 Pod 将无法重新调度。此外,这不是一个实时迁移,因此任何应用程序状态在过渡中都将丢失。底线是,在缩小规模或实施自动缩放方案之前,你应该仔细考虑其影响。

关于在 GCE 中的一般自动扩展的更多信息,请参考cloud.google.com/compute/docs/autoscaler/?hl=en_US#scaling_based_on_cpu_utilization链接。

在 AWS 上扩展集群

AWS 提供商代码也使得扩展集群变得非常容易。与 GCE 类似,AWS 设置使用自动扩展组来创建默认的四个从节点。将来,自动扩展组有望被集成到 Kubernetes 集群自动扩展功能中。目前,我们将通过手动设置来完成。

这也可以很容易地通过 CLI 或 Web 控制台进行修改。在控制台中,从 EC2 页面,只需转到左侧菜单底部的 Auto Scaling Groups 部分。你应该会看到一个类似于 kubernetes-minion-group 的名称。选择此组,你将会看到如下屏幕截图所示的详细信息:

Kubernetes minion autoscaling details

我们可以通过点击编辑来轻松扩展这个组。然后,将所需、最小和最大值更改为5,然后点击保存。几分钟后,你将会有第五个节点可用。你可以再次使用get nodes命令来检查这一点。

缩小规模的过程相同,但请记住我们在前一节在 GCE 上扩展集群中讨论了相同的考虑因素。工作负载可能会被放弃,或者至少会意外重新启动。

手动扩展

对于其他提供商,创建新的从节点可能不是一个自动化的过程。根据你的提供商,你需要执行各种手动步骤。查看cluster目录中的特定于提供商的脚本可能会有所帮助。

总结

现在我们对 Kubernetes 中应用程序扩展的基础有了更多的了解。我们还研究了内置功能以便进行滚动更新以及一个用于测试和缓慢集成更新的手动流程。我们看了一下如何扩展底层集群的节点,并增加我们 Kubernetes 资源的总体容量。最后,我们探讨了一些新的自动扩展概念,包括集群和应用程序本身。

在下一章中,我们将探讨利用新的deployments资源类型来扩展和更新应用程序的最新技术,以及在 Kubernetes 上可以运行的其他工作负载类型。

部署、作业和 DaemonSets

本章将介绍 Kubernetes 支持的各种工作负载类型。我们将介绍用于经常更新和长时间运行的应用程序的部署。我们还将重新审视使用部署进行应用程序更新和渐进式部署的主题。此外,我们还将查看用于短暂任务的作业。最后,我们将查看DaemonSets,它允许程序在 Kubernetes 集群中的每个节点上运行。

本章将讨论以下内容:

  • 部署

  • 使用部署进行应用程序扩展

  • 使用部署进行应用程序更新

  • 作业

  • DaemonSets

部署

在上一章中,我们探讨了使用旧的滚动更新方法进行应用程序更新的一些核心概念。从版本 1.2 开始,Kubernetes 添加了 Deployment 构造,它改进了滚动更新和 Replication Controllers 的基本机制。顾名思义,它使我们可以更精细地控制代码部署本身。部署允许我们暂停和恢复应用程序的部署。此外,它保留了过去部署的历史,并允许用户轻松回滚到以前的版本。

在接下来的列表 5-1中,我们可以看到该定义与 Replication Controller 非常相似。主要区别在于,现在我们可以对部署对象进行更改和更新,并让 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

列表 5-1node-js-deploy.yaml

我们可以运行熟悉的create命令,带上可选的--record标志,以便将部署的创建记录在发布历史中。否则,我们将只看到发布历史中的后续更改:

$ kubectl create -f node-js-deploy.yaml --record 

如果在您的集群上未启用此 beta 类型,您可能需要添加--validate=false

我们应该会看到部署成功创建的消息。几分钟后,它将完成创建我们的 pod,我们可以用get pods命令自行检查。我们添加了-l标志,只看到与此部署相关的 pod:

$ kubectl get pods -l name=node-js-deploy

我们创建了一个服务,就像我们之前使用 Replication Controllers 那样。下面是我们刚刚创建的部署的Service定义。我们会注意到,它几乎与我们以前创建的服务完全相同:

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

列表 5-2. node-js-deploy-service.yaml

使用 kubectl 创建此服务后,您将能够通过服务 IP 或者如果您在此命名空间的 pod 内部,则通过服务名称访问部署的 pod。

扩展

scale 命令的使用方式与我们的 Replication Controller 中的一样。要扩展,我们只需使用部署名称并指定新的副本数量,如下所示:

$ kubectl scale deployment node-js-deploy --replicas 3

如果一切顺利,我们将只会在终端窗口的输出中看到关于部署扩展的消息。我们可以再次使用先前的get pods命令来检查正在运行的 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的文本。

我们可以使用以下rollout status命令再次检查状态:

$ kubectl rollout status deployment/node-js-deploy

我们应该看到一些关于成功部署的文本。如果您看到任何关于等待部署完成的文本,您可能需要等待片刻,或者可以检查日志以查看问题。

完成后,再次运行get pods命令,就像之前一样。这次我们将看到新列出的 pods:

更新后的部署 Pod 列表

再次将您的一个 pod 名称插入我们之前运行的describe命令中。这次我们应该看到镜像已经更新为0.2

在幕后发生的事情是 Kubernetes 为我们部署了一个新版本。它基本上创建了一个具有新版本的新副本集。一旦这个 pod 在线并且健康,它就会杀死一个旧版本。它继续这个行为,扩展新版本并缩减旧版本,直到只剩下新的 pods。

以下图示描述了您的工作流程供参考:

部署生命周期

值得注意的是,回滚定义允许我们在部署定义中控制 pod 替换方法。有一个strategy.type字段,默认为RollingUpdate和前面的行为。可选地,我们也可以指定Recreate作为替换策略,它将首先杀死所有旧的 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的文本。现在再次运行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

我们可以按下Ctrl + C来终止status命令,然后再次运行get pods命令:

$ kubectl get pods -l name=node-js-deploy

我们现在应该看到一个ErrImagePull,如下面的截图:

图像拉取错误

正如我们预期的那样,它不能拉取图像的 42.0 版本,因为该版本不存在。此外,如果我们在集群上资源不足或者达到了为我们命名空间设置的限制,我们可能还会在部署方面遇到问题。此外,部署可能因许多应用程序相关原因而失败,例如健康检查失败、权限问题和应用程序错误等。

每当发生无法滚动部署的失败时,我们可以通过使用rollout undo命令轻松回滚到先前的版本。此命令将把我们的部署退回到之前的版本:

$ kubectl rollout undo deployment/node-js-deploy

之后,我们可以再次运行rollout status命令,应该会看到一切都成功滚动了。再次运行rollout history命令,我们会看到我们尝试滚动到版本42.0,以及回滚到0.3的情况:

回滚后的滚动历史

在运行撤消时,我们还可以指定--to-revision标志以回滚到特定版本。在我们的滚动成功后,但我们后来发现有逻辑错误时,这可能很方便。

自动缩放

正如你所看到的,部署是对复制控制器的重大改进,使我们能够无缝更新我们的应用程序,同时与 Kubernetes 的其他资源以类似的方式集成。

在前一章中我们看到的另一个领域,也支持部署,就是水平 Pod 自动缩放器HPAs)。正如你可能猜到的那样,这也与部署完美集成。我们将快速重制前一章的 HPAs,这次使用我们到目前为止创建的部署:

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: node-js-deploy
spec:
  minReplicas: 3
  maxReplicas: 6
  scaleTargetRef:
    apiVersion: v1
    kind: Deployment
    name: node-js-deploy
  targetCPUUtilizationPercentage: 10

Listing 5-3. node-js-deploy-hpa.yaml

我们已经将 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 10 -n
        100 ; sleep 1 ;     
        done"]

Listing 5-4. boomload-deploy.yaml

像往常一样创建 listing 5-4。现在使用交替的 kubectl get hpakubectl get deploy 命令监视 HPA。几分钟后,我们应该看到负载跳到 10% 以上。再过一会儿,我们还应该看到 pod 数量增加到 6 个副本:

HPA 增加和 Pod 扩容

同样,我们可以通过删除我们的负载生成 pod 并等待片刻来清理这一点:

$ kubectl delete deploy boomload-deploy

同样,如果我们观察 HPA,我们将开始看到 CPU 使用率下降。几分钟后,我们的 CPU 负载将降至 0%,然后 Deployment 将缩减到 3 个副本。

工作

部署和复制控制器是确保长时间运行的应用程序始终处于运行状态并能够容忍各种基础设施故障的好方法。然而,有一些情况下这并不能解决 —— 特别是短期运行的、仅运行一次的任务以及定期计划的任务。在这两种情况下,我们需要任务运行直到完成,然后在下一个计划的时间间隔开始终止并重新启动。

为了解决这种类型的工作负载,Kubernetes 添加了一个 批处理 API,其中包括 Job 类型。此类型将创建 1 到 n 个 pod,并确保它们全部成功完成退出。根据 restartPolicy,我们可以允许 pod 简单地失败而不进行重试(restartPolicy: Never),或者在 pod 退出而没有成功完成时进行重试(restartPolicy: OnFailure)。在这个例子中,我们将使用后者的技术:

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

Listing 5-5: longtask.yaml

让我们用以下命令运行:

$ kubectl create -f longtask.yaml

如果一切顺利,您将在屏幕上看到打印出 job "long-task" created

这告诉我们该任务已创建,但并不告诉我们是否成功完成了。要检查这一点,我们需要使用以下命令查询任务状态:

$ kubectl describe jobs/long-task

任务状态

您应该看到我们有1个成功完成的任务,在Events日志中,有一个 SuccessfulCreate 消息。如果我们使用kubectl get pods命令,我们将看不到我们的long-task pods 在列表中,但是如果列表底部指出有未显示的已完成作业,则可能会注意到该消息。我们需要再次使用-a--show-all标志运行命令,以查看long-task pod 和已完成的作业状态。

让我们深入一点,以证明工作已成功完成。我们可以使用logs命令查看 pod 的日志。但是,我们也可以使用 UI 来完成这个任务。打开浏览器,转到以下 UI 网址:https://**<your master ip>**/ui/

点击Jobs,然后从列表中选择long-task,以便我们可以查看详细信息。然后,在 Pods 部分,单击那里列出的 pod。这将给我们提供 Pod 详细信息页面。在详细信息底部,单击查看日志,我们将看到日志输出:

作业日志

如您在上图中所见,whalesay 容器已经完成了 ASCII 艺术,并且我们自定义的消息来自示例中的运行时参数。

其他类型的作业

虽然此示例提供了关于短期运行作业的基本介绍,但它仅涉及一次性任务的用例。实际上,批处理工作通常是并行进行的,或者作为定期发生的任务的一部分。

并行作业

使用并行作业,我们可能正在从正在进行的队列中获取任务,或者仅运行一组不相互依赖的任务。在从队列中获取作业的情况下,我们的应用程序必须了解依赖关系,并具有逻辑来决定如何处理任务以及下一步要处理的内容。Kubernetes 只是在调度这些作业。

您可以从 Kubernetes 文档和批处理 API 参考中了解有关并行作业的更多信息(您可以在本章末尾的参考部分中查看有关此的更多详细信息)。

计划任务

对于需要定期运行的任务,Kubernetes 还发布了 alpha 版的CronJob类型。正如我们所期望的,此类作业使用底层的 cron 格式来指定我们希望运行的任务的时间表。默认情况下,我们的集群不会启用 alpha 批处理功能,但是我们可以查看一个示例CronJob列表,以了解这些类型的工作负载将如何继续工作:

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

清单 5-6.longtask-cron.yaml

如您所见,计划部分反映了具有以下格式的 crontab:

分钟 小时 月中日 月份 周中的日

在本示例中,15 10 * * 6创建了一个任务,将在每个星期六的上午 10:15 运行。

守护进程集

虽然 Replication Controllers 和 Deployments 在确保特定数量的应用程序实例正在运行方面表现出色,但它们是在最佳适合的情况下进行的。这意味着调度器会寻找满足资源需求(可用 CPU、特定存储卷等)的节点,并尝试在节点和区域之间分配。

这对于创建高可用和容错应用程序非常有效,但是对于我们需要在集群的每个节点上运行代理的情况怎么办?虽然默认的分布确实尝试使用不同的节点,但它不保证每个节点都有副本,实际上只会填充与 RC 或 Deployment 规范中指定的数量相当的节点。

为了减轻这一负担,Kubernetes 引入了DaemonSet,它简单地定义了一个 pod 在集群的每个节点或定义的一部分节点上运行。这对于许多生产相关的活动非常有用,例如监控和日志代理、安全代理和文件系统守护程序。

实际上,Kubernetes 已经在一些核心系统组件中使用了这种能力。如果我们回顾一下第一章,Kubernetes 简介,我们会看到一个node-problem-detector在节点上运行。这个 pod 实际上是作为DaemonSet在集群的每个节点上运行的。我们可以通过在kube-system命名空间中查询 DaemonSets 来看到这一点:

$ kubectl get ds --namespace=kube-system

kube-system DaemonSets

你可以在以下清单中找到关于node-problem-detector以及yaml的更多信息:kubernetes.io/docs/admin/node-problem/#node-problem-detector

apiVersion: extensions/v1beta1
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/

清单 5-7. node-problem-detector 定义

节点选择

正如前面提到的,我们也可以将 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 定义,并且不限于 DaemonSets。让我们仔细看看作业示例(对我们之前的长任务示例进行了轻微修改)。

首先,我们可以在节点上看到这些。让我们获取我们节点的名称:

$ kubectl get nodes

使用前一个命令的输出中的名称并将其插入到这个命令中:

$ kubectl describe node <node-name>

来自节点描述的摘录

现在让我们给这个节点添加一个昵称标签:

$ kubectl label nodes <node-name> nodenickname=trusty-steve

如果我们再次运行kubectl describe node命令,我们将看到此标签列在默认值旁边。现在我们可以调度工作负载并指定这个特定的节点。以下是我们早期的长时间运行任务的修改版本,添加了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

图 5-8. longtask-nodeselector.yaml

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 中的核心构造有一个良好的基础。我们探讨了新的 Deployment 抽象及其如何改进基本的 Replication Controller,从而实现了平滑的更新和与服务及自动缩放的坚实集成。我们还查看了作业和 DaemonSets 中的其他类型的工作负载。你学会了如何运行短期或批处理任务,以及如何在我们的集群中的每个节点上运行代理。最后,我们简要地看了一下节点选择以及如何用它来过滤集群中用于我们工作负载的节点。

我们将在本章学到的内容的基础上继续,然后在下一章中查看有状态应用程序,探索关键的应用程序组件和数据本身。

参考资料

  1. kubernetes.io/docs/user-guide/jobs/#parallel-jobs

存储和运行有状态应用程序

在本章中,我们将讨论如何附加持久卷并为有状态应用程序和数据创建存储。我们将详细介绍存储方面的问题以及如何在容器的生命周期内跨 pod 持久化数据。我们将探索PersistentVolumes类型以及PersistentVolumeClaim。最后,我们将看一下版本 1.5 中新发布的StatefulSets

本章将讨论以下主题:

  • 持久存储

  • PersistentVolumes

  • PersistentVolumeClaims

  • StorageClasses

  • StatefulSets

持久存储

到目前为止,我们只处理了可以随意启动和停止的工作负载,没有任何问题。然而,现实世界的应用程序通常具有状态,并记录我们希望(甚至坚持)不要丢失的数据。容器本身的瞬时性质可能是一个很大的挑战。如果您还记得我们在第一章 Kubernetes 简介中对分层文件系统的讨论,顶层是可写的。(它也是糖霜,非常美味。)但是,当容器死亡时,数据也会随之而去。 Kubernetes 重新启动的已崩溃容器也是如此。

这就是或磁盘发挥作用的地方。一个存在于容器之外的卷使我们能够在容器中断期间保存重要数据。此外,如果我们在 pod 级别有一个卷,数据可以在同一应用程序堆栈中的多个容器之间以及同一 pod 内部共享。

Docker 本身对卷有一些支持,但 Kubernetes 为我们提供了持久存储,可以持续存在于单个容器的生命周期之外。这些卷与 pod 相关联,并与这些 pod 一起生存和死亡。此外,一个 pod 可以有多个来自各种来源的卷。让我们看一下其中一些来源。

临时磁盘

在容器崩溃和 pod 内数据共享之间实现改进的持久性的最简单方法之一是使用emptydir卷。此卷类型可与节点机器本身的存储卷或可选的 RAM 磁盘一起使用,以实现更高的性能。

再次改进我们的持久性超出一个单独的容器,但是当一个 pod 被移除时,数据将会丢失。机器重启也会清除 RAM 类型磁盘中的任何数据。有时我们只需要一些共享的临时空间或者有处理数据并在死亡之前将其传递给另一个容器的容器。无论情况如何,以下是使用这个临时磁盘和 RAM 支持选项的快速示例。

打开您喜欢的编辑器,并创建一个类似以下Listing 6-1的文件:

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 

Listing 6-1: storage-memory.yaml

前面的例子现在可能已经是家常便饭了,但我们将再次发出一个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 来运行命令以查看所有文件夹:

图片

容器内的临时存储

再次说明,这个文件夹是非常临时的,因为所有内容都存储在节点(minion)的 RAM 中。当节点重新启动时,所有文件都将被删除。接下来我们将看一个更加永久的例子。

云存储卷

许多公司已经在公共云中运行了大量基础架构。幸运的是,Kubernetes 原生支持两个最受欢迎提供者提供的持久存储。

GCE 持久磁盘

我们从 GCE 网站中获得了以下内容:

Google Persistent Disk 是 Google 云平台的持久且高性能的块存储。持久磁盘提供 SSD 和 HDD 存储,可附加到运行在 Google Compute Engine 或 Google Container Engine 中的实例。存储卷可以透明地调整大小,快速备份,并具有支持同时读取的能力。(你可以在本章末尾的参考部分中查看关于此的更多详细信息)

让我们创建一个新的 GCE 持久磁盘

  1. 从控制台中,在 Compute Engine 中,转到“磁盘”。在这个新屏幕上,点击“创建磁盘”按钮。我们将看到一个类似于下图的屏幕。

  2. 为此卷选择一个名称并简要描述它。确保区域与集群中的节点相同。GCE PD 只能附加到相同区域中的机器上。

  3. 在“名称”字段中输入 mysite-volume-1。选择与集群中至少一个节点匹配的区域。选择“无”(空白磁盘)作为“源类型”,并在“大小(GB)”中输入 10(10 GB)作为值。最后,点击“创建”:

图片

GCE 新持久磁盘

在 GCE 上,持久磁盘的好处是它们允许挂载到多台机器(在我们的情况下是节点)。然而,当挂载到多台机器时,卷必须处于只读模式。因此,让我们首先将其挂载到单个 Pod 上,以便我们可以创建一些文件。使用 列表 6-2storage-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 

列表 6-2storage-gce.yaml

首先,让我们发出一个 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!。现在保存文件并退出编辑器。如果你还记得 列表 6-2 中提到的,PD 是直接挂载到 Nginx HTML 目录的。所以,让我们在节点上仍然保持 SSH 会话的情况下测试一下。对我们之前记下的 pod IP 执行一个简单的curl命令:

$ curl <Pod IP from Describe> 

你应该能看到来自我的 GCE PD 的消息,或者你在index.html文件中保存的任何消息。在真实的场景中,我们可以为整个网站或任何其他中央存储使用这个卷。让我们来看看运行一组负载均衡的 Web 服务器,它们都指向同一个卷。

首先,使用两个exit命令离开 SSH 会话。在继续之前,我们需要删除test-gce pod,这样卷就可以被挂载为只读在多个节点上:

$ kubectl delete pod/test-gce 

现在我们可以创建一个 RC,它将运行三个 Web 服务器,所有服务器都挂载相同的持久磁盘,如下所示:

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 

列表 6-3http-pd-controller.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 

列表 6-4http-pd-service.yaml

现在创建这两个资源。等待一段时间以分配外部 IP。之后,describe命令将给我们一个可以在浏览器中使用的 IP:

$ kubectl describe service/http-pd 

以下屏幕截图是上述命令的结果:

K8s 服务与 GCE PD 跨三个 pod 共享

如果你还没有看到LoadBalancer Ingress字段,那可能需要更多时间来分配。将LoadBalancer Ingress中的 IP 地址键入到浏览器中,你应该能看到我们之前输入的熟悉的index.html文件显示出来的文本!

AWS 弹性块存储

K8s 还支持 AWS 弹性块存储EBS)卷。与 GCE PD 相同,EBS 卷需要附加到在相同可用性区域运行的实例上。进一步的限制是,EBS 只能一次挂载到单个实例上。

为了简洁起见,我们不会演示 AWS 示例,但包含了一个示例 YAML 文件以帮助你开始。同样,记得在 pod 之前创建 EBS 卷:

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 

列表 6-5storage-aws.yaml

其他存储选项

Kubernetes 支持各种其他类型的存储卷。完整列表可以在这里找到:kubernetes.io/v1.0/docs/user-guide/volumes.html#types-of-volumes

这里有一些可能特别感兴趣的:

  • nfs:这种类型允许我们挂载网络文件共享NFS),对于持久化数据并在基础设施中共享它非常有用

  • gitrepo:你可能已经猜到了,这个选项将一个 Git 仓库克隆到一个新的空文件夹中。

持久卷和存储类

到目前为止,我们已经看到了在我们的 Pod 定义中直接供应存储的例子。如果您完全控制您的集群和基础设施,这种方法运行得很好,但在更大规模上,应用程序所有者将希望使用单独管理的存储。通常,一个中央 IT 团队或云提供商本身将负责管理存储的细节,并让应用程序所有者担心他们的主要关注点,即应用程序本身。

为了适应这一点,我们需要一种方法,让应用程序指定和请求存储而不用担心如何提供存储。这就是PersistentVolumesPersistentVolumeClaims发挥作用的地方。PersistentVolumes类似于我们之前创建的volumes,但是它们由集群管理员提供,并且不依赖于特定的 Pod。然后,Pod 可以使用PersistentVolumeClaims声明来声明此卷。

PersistentVolumeClaims允许我们指定所需存储的细节。我们可以定义存储量以及访问类型,如ReadWriteOnce(一个节点读写)、ReadOnlyMany(多个节点只读)和ReadWriteMany(多个节点读写)。当然,支持哪些模式取决于支持的后端存储提供者。例如,我们在 AWS EBS 示例中看到,挂载到多个节点不是一个选项。

此外,Kubernetes 提供了另外两种指定特定分组或存储卷类型的方法。第一种是使用选择器,就像我们之前为 Pod 选择所见的那样。在这里,标签可以应用于存储卷,然后声明可以引用这些标签以进一步过滤它们所提供的卷。其次,Kubernetes 具有 StorageClass 的概念,允许我们指定一个存储提供程序和用于其提供的卷类型的参数。

我们将在下一节深入研究 StorageClasses,但这里有一个PersistentVolumeClaims的快速示例,以便说明目的。您可以在注释中看到我们请求以ReadWriteOnce模式和solidstate存储类的1Gi存储,并带有aws-storage标签。

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: demo-claim
  annotations:
    volume.beta.kubernetes.io/storage-class: "solidstate"
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  selector:
    matchLabels:
      release: "aws-storage"

清单 6-6: pvc-example.yaml

有状态副本集

StatefulSets的目的是为具有状态数据的应用程序部署提供一些一致性和可预测性。到目前为止,我们已经将应用程序部署到集群中,定义了对所需资源(如计算和存储)的松散要求。集群已经在满足这些要求的任何节点上安排了我们的工作负载。虽然我们可以使用其中一些约束以更可预测的方式部署,但如果我们有一个构造来帮助我们提供这种一致性将会很有帮助。

当我们完成此书稿时,StatefulSets 已经设置为 GA 版本 1.6。它们之前是 beta 版本 1.5,之前称为 PetSets(1.3 和 1.4 中的 alpha 版本)。

这就是 StatefulSets 的用武之地。StatefulSets 首先为我们提供了有序和可靠的命名,用于网络访问和存储索赔。这些 pod 本身的命名采用以下约定,其中 N 从 0 到副本数:

"Name of Set"-N

这意味着名为 db 的 Statefulset 有 3 个副本将创建以下 pod:

db-0
db-1
db-2

这使得 Kubernetes 能够将网络名称和 PersistentVolumes 与特定的 pod 关联起来。此外,它还用于对 pod 的创建和终止进行排序。Pod 将从 0 开始启动,并从 N 开始终止。

一个有状态的示例

让我们看一个有状态应用程序的示例。首先,我们将想要创建和使用一个 StorageClass,正如我们之前讨论的一样。这将允许我们连接到 Google Cloud Persistent Disk provisioner。Kubernetes 社区正在为各种 StorageClasses 构建 provisioners,包括 GCP 和 AWS。每个 provisioner 都有一组可用的参数。GCP 和 AWS 提供商都可以让您选择磁盘类型(固态、标准等)以及故障域,这是需要与其附加的 pod 匹配的。AWS 还允许您指定加密参数以及针对 Provisioned IOPs 卷的 IOPs。还有一些其他 provisioner 正在进行中,包括 Azure 和各种非云选项:

kind: StorageClass
apiVersion: storage.k8s.io/v1beta1
metadata:
  name: solidstate
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-ssd
  zone: us-central1-b

图示 6-7: solidstate-sc.yaml

使用以下命令与前面的列表一起创建 StorageClass 类型的 SSD 驱动器在 us-central1-b 中:

$ kubectl create -f solidstate.yaml

接下来,我们将使用我们信任的 httpwhalesay 演示创建一个 StatefulSet 类型(您可以在本章末尾的 参考资料 中参考更多详细信息)。虽然该应用程序确实包含任何真实状态,但我们可以看到存储索赔并探索通信路径:

apiVersion: apps/v1beta1
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

图示 6-8: sayhey-statefulset.yaml

使用以下命令开始创建此 StatefulSet。如果您仔细观察 pod 的创建,您将看到它连续创建 whaleset-0、whaleset-1 和 whaleset-2:

$ kubectl create -f sayhey-statefulset.yaml

紧接着,我们可以使用熟悉的 get 子命令看到我们的 StatefulSet 和相应的 pod:

$ kubectl get statefulsets
$ kubectl get pods

这些 pod 应该会创建类似于以下图像的输出:

StatefulSet 列表

get pods 输出将显示以下内容:

由 StatefulSet 创建的 pod

根据您的时间安排,可能仍在创建 pod。如前面的图像所示,第三个容器仍在启动。

我们还可以看到集合为每个 pod 创建的卷和索赔。首先是 PersistentVolumes 本身:

$ kubectl get pv 

上述命令应显示三个名为www-whaleset-N的 PersistentVolumes。我们注意到大小为1Gi,访问模式设置为ReadWriteOnceRWO),就像我们在 StorageClass 中定义的一样:

持久卷清单

接下来,我们可以查看为每个 pod 保留卷的 PersistentVolumeClaims:

$ kubectl get pvc

以下是前述命令的输出:

持久卷索赔清单

在这里,您会注意到与 PV 自身相同的设置很多。您可能还会注意到声明名称的结尾(或上一个列表中的 PV 名称)看起来像www-whaleset-Nwww是我们在之前的 YAML 定义中指定的挂载名称。然后将其附加到 pod 名称以创建实际的 PV 和 PVC 名称。我们还可以确保与之关联的适当磁盘与匹配的 pod 相关联。

另一个这种对齐很重要的领域是网络通信。StatefulSets 在这里也提供一致的命名。在我们这样做之前,让我们创建一个服务端点,这样我们就有了一个常见的入口点用于传入的请求:

apiVersion: v1
kind: Service
metadata:
  name: sayhey-svc
  labels:
    app: sayhey
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: sayhey

清单 6-9:sayhey-svc.yaml

$ kubectl create -f sayhey-svc.yaml

现在让我们在其中一个 pod 中打开一个 shell,并查看是否可以与集合中的另一个 pod 通信:

$ kubectl exec whaleset-0 -i -t bash

执行上述命令会在第一个鲸鱼集群(whaleset)的 pod 中给我们提供一个 bash shell。现在我们可以使用Service名称来发出一个简单的 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 条目。同样,我们将看到"集合名称"-N的约定,然后添加完全限定的服务 URL。下面的示例展示了这一点,对于我们集合中的第二个 podwhaleset-1

$ curl whaleset-1.sayhey-svc.default.svc.cluster.local

在鲸鱼集群(whaleset)的 Bash shell 中运行此命令将显示来自whaleset-1的输出:

HTTP Whalesay curl 输出(whalesay-1 Pod)

您现在可以使用exit退出此 shell。

出于学习目的,更详细地描述此部分的某些项目也很有益。例如,kubectl describe svc sayhey-svc将显示服务端点中的所有三个 pod IP 地址。

概要

在本章中,我们探讨了各种持久存储选项以及如何在我们的 pod 中实现它们。我们查看了 PersistentVolumes 以及允许我们将存储分配和应用程序存储请求分开的 PersistentVolumeClaims。此外,我们还查看了 StorageClasses,以根据规范为存储组提供存储。

我们还探讨了新的 StatefulSets 抽象,并学习了如何以一致和有序的方式部署有状态的应用程序。在下一章中,我们将看看如何将 Kubernetes 与持续集成和交付流水线集成起来。

参考资料

  1. cloud.google.com/persistent-disk/

  2. HTTP Whalesay 是 Docker whalesaym 的一种适应,而 Docker whalesaym 又是 Linux cowsay(大约在 1999 年,Tony Monroe)的一种适应 - hub.docker.com/r/docker/whalesay/

持续交付

本章将向读者展示如何将他们的构建流程和部署与 Kubernetes 集群集成。它将涵盖如何使用 Gulp.js 和 Jenkins 与您的 Kubernetes 集群配合使用的概念。

本章将讨论以下主题:

  • 与持续部署流水线集成

  • 使用 Gulp.js 与 Kubernetes

  • 将 Jenkins 与 Kubernetes 集成

与持续交付流水线集成

持续集成和交付是现代开发工作室的关键组成部分。对于任何正在开发自己软件的公司来说,上线速度收益的平均时间至关重要。我们将看看 Kubernetes 如何帮助您。

CI/CD(持续集成/持续交付的缩写)通常需要在代码库推送更改时提供临时构建和测试服务器。Docker 和 Kubernetes 非常适合这项任务,因为可以轻松在几秒钟内创建容器,并在运行构建后同样轻松地将其删除。此外,如果您的集群上已经有大量基础设施可用,那么利用空闲容量进行构建和测试是有意义的。

在本文中,我们将探讨用于构建和部署软件的两种流行工具:

  • Gulp.js:这是一个简单的任务执行器,用于使用JavaScriptNode.js自动化构建过程。

  • Jenkins:这是一个完整的持续集成服务器

Gulp.js

Gulp.js 为我们提供了Build as code的框架。类似于基础设施即代码,这使我们能够以程序方式定义我们的构建过程。我们将通过一个简短的示例演示如何从 Docker 镜像构建到最终 Kubernetes 服务创建一个完整的工作流程。

先决条件

对于本文的这一部分,您需要已安装并准备好一个NodeJS环境,包括node 包管理器npm)。如果您尚未安装这些软件包,可以在docs.npmjs.com/getting-started/installing-node找到安装说明。

您可以通过执行node -v命令检查 NodeJS 是否已正确安装。

您还需要Docker CE和一个DockerHub帐户来推送一个新的镜像。您可以在docs.docker.com/installation/找到安装 Docker CE 的说明。

您可以在hub.docker.com/轻松创建一个 DockerHub 帐户。

在您拥有凭证后,您可以使用$ docker login命令通过 CLI 登录。

Gulp 构建示例

让我们从创建一个名为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文件,来运行我们的所有任务。同样,如果你希望复制它们,这些文件也可以在书籍文件包中找到。参考以下代码:

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 

清单 7-1node-gulp-controller.yaml

正如你所见,我们有一个基本的控制器。你需要用你的 Docker Hub 用户名替换**<your username>**/node-gulp:latest

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 

清单 7-2node-gulp-service.yaml

接下来,我们有一个简单的服务,它选择我们控制器中的 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' 
])); 

清单 7-3gulpfile.js

最后,我们有gulpfile.js文件。这是我们定义所有构建任务的地方。同样,在**<your username>**/node-gulp部分填入你的 Docker Hub 用户名。

浏览文件,首先,克隆任务从 GitHub 下载我们的镜像源代码。拉取任务在克隆的存储库上执行git pull。接下来,docker-build命令从container-info子文件夹构建镜像并将其推送到 DockerHub。最后,我们有create-kube-podupdate-kube-pod命令。你可以猜到,create-kube-pod命令首次创建我们的控制器和服务,而update-kube-pod命令只是替换控制器。

让我们继续运行这些命令,看看我们的端到端工作流程:

$ gulp clone
$ gulp docker-build

第一次运行时,你可以执行以下create-kube-pod命令:

$ gulp create-kube-pod

就是这样。如果我们对node-gulp服务运行快速的kubectl describe命令,我们可以获取到新服务的外部 IP。浏览该 IP,你会看到熟悉的container-info应用程序正在运行。请注意,主机以node-gulp开头,就像我们在前面提到的 pod 定义中命名的一样:

由 Gulp 构建启动的服务

在后续更新中,像这样运行pullupdate-kube-pod命令:

$ gulp pull
$ gulp docker-build
$ gulp update-kube-pod

这只是一个非常简单的示例,但你可以开始看到如何通过几行简单的代码协调你的构建和部署端到端是多么容易。接下来,我们将看看如何使用 Kubernetes 来实际运行 Jenkins 构建。

用于 Jenkins 的 Kubernetes 插件

我们可以使用 Kubernetes 运行我们的 CI/CD 流水线的一种方式是在容器化环境中运行我们的 Jenkins 构建节点。幸运的是,已经有一个插件,由 Carlos Sanchez 编写,允许你在 Kubernetes 的 pods 中运行 Jenkins 构建节点。

先决条件

你将需要一个 Jenkins 服务器来执行下一个示例。如果你没有可用的,你可以使用 Docker Hub 上提供的一个 Docker 镜像 hub.docker.com/_/jenkins/

通过 Docker CLI 运行它就是这么简单的:

docker run --name myjenkins -p 8080:8080 -v /var/jenkins_home jenkins 

安装插件

登录到您的 Jenkins 服务器,从主页仪表板中,点击“管理 Jenkins”。然后,从列表中选择“管理插件”。

对于安装新的 Jenkins 服务器的注意事项:当您首次登录到 Jenkins 服务器时,它会要求您安装插件。选择默认插件或不安装插件将不会安装任何插件:

Jenkins 主面板

凭证插件是必需的,但应默认安装。如果有疑问,可以在“已安装”选项卡中查看,如下所示的截图:

Jenkins 已安装的插件

接下来,我们可以点击“可用”选项卡。Kubernetes 插件应该位于“集群管理和分布式构建”或“Misc (cloud)”下。有许多插件,因此您也可以在页面上搜索 Kubernetes。勾选 Kubernetes 插件的框,并点击“安装而不重启”。

这将安装 Kubernetes 插件和 Durable Task 插件:

插件安装

如果您希望安装非标准版本或只是喜欢调整,您可以选择下载插件。最新的 KubernetesDurable Task 插件可以在这里找到:

Kubernetes 插件: wiki.jenkins-ci.org/display/JENKINS/Kubernetes+Plugin         Durable Task 插件: wiki.jenkins-ci.org/display/JENKINS/Durable+Task+Plugin 接下来,我们可以点击“高级”选项卡,然后滚动到“上传插件”。导航到durable-task.hpi文件,然后点击“上传”。您应该会看到一个显示安装进度条的屏幕。一两分钟后,它将更新为“成功”。

最后,安装主要的 Kubernetes 插件。在左侧,点击“管理插件”,然后再次点击“高级”选项卡。这次,上传kubernetes.hpi文件,然后点击“上传”。几分钟后,安装应该完成。

配置 Kubernetes 插件

点击“返回仪表板”或左上角的 Jenkins 链接。从主仪表板页面,点击“凭据”链接。从列表中选择一个域;在我的情况下,我只是使用了默认的全局凭据域。点击“添加凭据”:

添加凭据屏幕

将“类型”留为空,将“范围”设置为全局。添加您的 Kubernetes 管理员凭据。请记住,您可以通过运行config命令找到这些凭据:

$ kubectl config view

您可以将 ID 留空,给它一个明智的描述,然后点击“确定”按钮。

现在我们已经保存了凭证,我们可以添加我们的 Kubernetes 服务器。点击左上角的 Jenkins 链接,然后选择“管理 Jenkins”。从那里,选择“配置系统”,然后滚动到最底部的“云”部分。从“添加新云”下拉菜单中选择 Kubernetes,将出现一个 Kubernetes 部分,如下所示:

新 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”和“配置全局安全性”下找到端口设置。

额外的乐趣

Fabric8 自称为一个集成平台。它包括各种日志记录、监视和持续交付工具。它还有一个漂亮的控制台、一个 API 注册表,以及一个 3D 游戏,让您可以射击您的 Pod。这是一个非常酷的项目,实际上是在 Kubernetes 上运行的。请参考 fabric8.io/

在您的 Kubernetes 集群上设置起来非常简单,所以请参考 fabric8.io/guide/getStarted/gke.html

总结

我们看了两个可与 Kubernetes 一起使用的持续集成工具。我们简要介绍了如何在我们的集群上部署 Gulp.js 任务。我们还看了一个新插件,用于将 Jenkins 构建从节点集成到您的 Kubernetes 集群中。现在,您应该更好地了解 Kubernetes 如何与您自己的 CI/CD 管道集成。

监控和日志记录

本章将涵盖对我们的 Kubernetes 集群中内置和第三方监控工具的使用和自定义。我们将介绍如何使用这些工具来监视我们集群的健康和性能。此外,我们还将研究内置日志记录、Google Cloud Logging 服务和 Sysdig

本章将讨论以下主题:

  • Kubernetes 如何使用 cAdvisor、Heapster、InfluxDB 和 Grafana

  • 自定义默认 Grafana 仪表盘

  • 使用 FluentD 和 Grafana

  • 安装和使用日志记录工具

  • 使用流行的第三方工具,如 StackDriver 和 Sysdig,来扩展我们的监控能力

监控操作

实际监控远不止检查系统是否正常运行。尽管像你在第二章中学到的那样,Pods、Services、Replication Controllers 和 Labels 中的 健康检查 部分所学的可以帮助我们隔离问题应用程序。但是,只有在系统下线之前能够预见问题并加以缓解时,运营团队才能最好地为业务服务。

监控的最佳实践是测量核心资源的性能和使用情况,并观察是否存在偏离正常基线的趋势。容器在这方面也不例外,管理我们的 Kubernetes 集群的一个关键组件是清晰地了解所有节点上的操作系统、网络、系统(CPU 和内存)和存储资源的性能和可用性。

在本章中,我们将研究几种选项,以监控和测量所有集群资源的性能和可用性。此外,当出现异常趋势时,我们还将查看一些警报和通知选项。

内置监控

如果你回忆一下第一章中关于 Kubernetes 的介绍,我们注意到我们的节点已经运行了许多监控服务。我们可以通过以下方式再次运行 get pods 命令,并指定 kube-system 命名空间来查看这些服务:

$ kubectl get pods --namespace=kube-system

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

系统 Pod 列表

再次看到了各种各样的服务,但这一切又是如何结合在一起的呢?如果你回忆一下第二章中关于 节点(原名为 minions) 部分,每个节点都在运行一个 kublet。Kublet 是节点与 API 服务器交互和更新的主要接口。其中一个更新是节点资源的 度量值。实际上,资源使用情况的报告由一个名为 cAdvisor 的程序执行。

cAdvisor 是 Google 的另一个开源项目,提供了有关容器资源使用情况的各种指标。指标包括 CPU、内存和网络统计信息。不需要告诉 cAdvisor 关于单个容器的信息;它会收集节点上所有容器的指标,并将其报告给 kublet,然后再报告给 Heapster。

Google 的开源项目 Google 有许多与 Kubernetes 相关的开源项目。查看它们,使用它们,甚至贡献您自己的代码!

cAdvisor 和 Heapster 在下一节中提到:

Contrib 是各种不属于核心 Kubernetes 的组件的集合。其位置在:

github.com/kubernetes/contrib.

LevelDB 是 InfluxDB 创建时使用的键存储库。其位置在:

github.com/google/leveldb.

Heapster 是 Google 的又一个开源项目;你可能开始看到这里出现了一个主题(参见前面的信息框)。Heapster 在一个 minion 节点上的容器中运行,并从 kublet 聚合数据。提供了一个简单的 REST 接口来查询数据。

使用 GCE 设置时,为我们设置了一些额外的包,这为我们节省了时间,并提供了一个完整的包来监视我们的容器工作负载。从前面的 系统 pod 列表 截图中可以看到,还有另一个带有 influx-grafana 的 pod。

InfluxDB 在其官方网站上描述如下(您可以在本章末尾的 参考 部分的第 1 点中找到更多详细信息):

一个没有外部依赖的开源分布式时间序列数据库。

InfluxDB 基于一个键存储包(参考前面的 Google 的开源项目 信息框)构建,并且非常适合存储和查询事件或者时间统计信息,例如由 Heapster 提供的那些。

最后,我们有 Grafana,它为存储在 InfluxDB 中的数据提供了仪表板和图形界面。使用 Grafana,用户可以创建自定义监控仪表板,并立即查看其 Kubernetes 集群的健康状况,因此也可以查看其整个容器基础架构的健康状况。

探索 Heapster

让我们通过 SSH 快速查看 Heapster pod 所在节点的 REST 接口。首先,我们可以列出 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/ 获取完整的指标列表。

现在让我们通过向我们从 describe 命令保存的 Pod IP 地址发出 curl 命令来查看列表,如下所示:

$ curl -G <Heapster IP from describe>:8082/api/v1/metric-export-schema/

我们将看到一个相当长的列表。第一部分显示所有可用的指标。最后两部分列出了我们可以按其过滤和分组的字段。为了您的方便,我添加了以下略微易读一些的表格:

指标 描述 单位 类型
正常运行时间 容器启动以来的毫秒数 毫秒 累计
cpu/使用率 所有核心上的累计 CPU 使用率 纳秒 累计
cpu/限制 毫核 CPU 限制 - 测量值
内存/使用量 总内存使用量 字节 测量值
内存/工作集 总工作集使用量;工作集是内核正在使用且不容易丢弃的内存 字节 测量值
内存/限制 内存限制 字节 测量值
内存/页面错误 页面错误数 - 累计
内存/主要页面错误 主要页面错误数 - 累计
网络/接收 累计接收到网络的字节数 字节 累计
网络/接收错误 接收到网络时的累计错误数 - 累计
网络/发送 累计发送到网络的字节数 字节 累计
网络/发送错误 发送到网络时的累计错误数 - 累计
文件系统/使用量 文件系统上消耗的总字节数 字节 测量值
文件系统/限制 文件系统总大小(以字节为单位) 字节 测量值
文件系统/可用 文件系统中剩余的可用字节数 字节 测量值

表 6.1。可用的 Heapster 指标

字段 描述 标签类型
nodename 容器运行的节点名称 通用
hostname 容器运行的主机名 通用
host_id 特定于主机的标识符,由云提供商或用户设置 通用
container_base_image 在容器内运行的用户定义的镜像名称 通用
container_name 容器的用户提供的名称或系统容器的完整容器名称 通用
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 轴下,点击单位旁边的字段,将其设置为 data -> bytes,标签设置为磁盘空间已用。在右 Y 轴下,将单位设置为 none -> none。接下来,在 图例 标签下,确保选中在选项中显示和在值中最大化。

现在,让我们快速转到通用标签并选择一个标题。在我的情况下,我将其命名为节点的文件系统磁盘使用情况(最大值)

我们不想丢失我们创建的这张漂亮的新图表,所以让我们点击右上角的保存图标。它看起来像一个软盘(如果你不知道这是什么,你可以进行谷歌图片搜索)。

在我们点击保存图标后,我们会看到一个绿色的对话框,确认仪表板已保存。现在我们可以点击位于图表详细信息面板上方和图表本身下方的 x 符号。

这将带我们返回仪表板页面。如果我们一直往下滚动,我们会看到我们的新图形。让我们在这一行再添加另一个面板。再次使用绿色标签,然后选择 Add Panel -> singlestat。又一次,一个空面板将出现,下面是一个设置表单。

假设我们想要监视特定节点并监视网络使用情况。我们可以首先转到 Metrics 选项卡来轻松完成这项任务。然后展开查询字段,并将 FROM 字段中的第二个值设置为 network/rx。现在,我们可以通过点击行末尾的+符号并从下拉菜单中选择主机名,在 WHERE 子句中指定条件。在 hostname = 后点击 select tag value,并从列表中选择一个 minion 节点。

最后,将mean()留给第二个 SELECT 字段:

Singlestat 选项

在 Options 选项卡中,确保 Unit format 设置为 data -> bytes,然后在 Spark lines 旁的 Show 框中打勾。sparkline可以让我们快速查看价值的最近变化历史。我们可以使用 Background mode 来占据整个背景;默认情况下,它使用值下面的区域。

在 Coloring 中,我们可以选择 Value 或 Background 框并选择 Thresholds and Colors。这将使我们能够根据我们指定的阈值层选择不同颜色的值。请注意,阈值数必须使用未格式化版本。

现在,让我们返回到 General 选项卡,并将标题设置为Network bytes received (Node35ao)。使用您的 minion 节点的标识符。再次保存我们的工作并返回仪表板。我们现在应该有一个类似以下截图的行:

自定义仪表板面板

Grafana 还有其他许多不同类型的面板供您尝试,例如仪表板列表、插件列表、表格和文本。

正如我们所看到的,构建自定义仪表板并一目了然地监视我们集群的健康状况非常容易。

FluentD 和 Google Cloud Logging

回顾一下本章一开始的System pod listing截图,你可能会注意到一些以fluentd-cloud-logging-kubernetes...开头的 pod。在使用 GCE 供应商为您的 K8s 集群提供服务时,这些 pod 会出现。我们集群中的每个节点都有一个这样的 pod,其唯一目的是处理 Kubernetes 日志的处理。

如果我们登录到我们的 Google Cloud Platform 帐户,就可以看到一些在那里处理的日志。只需在左侧,在 Stackdriver 下选择 Logging。这将带我们到一个带有顶部多个下拉菜单的日志列表页面。如果这是您第一次访问该页面,第一个下拉菜单可能会被设定为 Cloud HTTP Load Balancer。

在此下拉菜单中,我们将看到许多 GCE 类型的条目。选择 GCE VM 实例,然后选择 Kubernetes 主节点或其中一个节点。在第二个下拉菜单中,我们可以选择各种日志组,包括 kublet。我们还可以按事件日志级别和日期进行过滤。此外,我们可以使用播放按钮实时观看事件流:

Google Cloud Logging 过滤器

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 云平台监控服务进行集成。在您的锁定警报开始响起之前,StackDriver 还具有与 AWS 的良好集成。此外,StackDriver 还具有警报功能,支持向各种平台发送通知,并支持用于其他内容的 Webhook。

注册 GCE 监控

在 GCE 控制台中,在Stackdriver部分点击监控。这将打开一个新窗口,我们可以在其中注册 Stackdriver 的免费试用。然后,我们可以添加我们的 GCP 项目,以及可选的 AWS 帐户。这需要一些额外的步骤,但页面上包含了说明。最后,我们将收到有关如何在我们的集群节点上安装代理的说明。我们现在可以跳过这一步,但一会儿会回来。

点击继续,设置您的每日警报,然后再次点击继续

点击启动监控继续。我们将被带到主仪表板页面,在那里我们将看到集群中节点的一些基本统计信息。如果我们从侧边栏中选择资源,然后选择实例,我们将被带到列出所有节点的页面。通过点击单个节点,即使没有安装代理,我们也可以再次看到一些基本信息。

Stackdriver 还提供可安装在节点上的监控和日志代理。但是,它当前不支持 GCE kube-up 脚本中默认使用的容器 OS。您仍然可以查看 GCE 或 AWS 中任何节点的基本指标,但如果您想要详细的代理安装,则需要使用另一个操作

警报

接下来,我们可以查看作为监控服务一部分提供的警报策略。从实例详细信息页面,点击页面顶部的创建警报策略按钮。

我们会点击添加条件,并选择一个指标阈值。在目标部分,将资源类型设置为实例(GCE)。然后,将适用于设置为组和 kubernetes。将条件触发如果设置为任何成员违反。

配置部分,将如果指标保持为 CPU 使用率(GCE 监控),将条件保持为上述。现在将阈值设置为80,并将时间设置为 5 分钟。

点击保存条件:

Google Cloud 监控警报策略

接下来,我们将添加一个通知。在通知部分,将方法保持为电子邮件,并输入您的电子邮件地址。

我们可以跳过文档部分,但这是我们可以添加文本和格式到警报消息的地方。

最后,将策略命名为过高的 CPU 负载,然后点击保存策略

现在,每当我们的实例之一的 CPU 使用率超过 80%,我们将收到电子邮件通知。如果我们需要审查我们的策略,我们可以在警报下拉菜单中找到它们,然后在屏幕左侧的菜单中找到策略概览

超越系统监控与 Sysdig

监控我们的云系统是一个很好的开始,但是对于容器本身的可见性呢?虽然有各种各样的云监控和可见性工具,但 Sysdig 以其不仅可以深入了解系统操作而且特别是容器而脱颖而出。

Sysdig 是开源的,并被称为具有对容器的本地支持的通用系统可见性工具(您可以在本章末尾的参考资料部分的第 2 点中了解更多详情)。它是一个命令行工具,可以提供我们之前看过的领域的见解,例如存储、网络和系统进程。它的独特之处在于提供了这些进程和系统活动的详细信息和可见性水平。此外,它对容器有本地支持,这为我们提供了我们容器操作的全貌。这是您容器操作工具库中强烈推荐的工具。Sysdig 的主要网站是 www.sysdig.org/

Sysdig Cloud

我们将马上看一下 Sysdig 工具和一些有用的基于命令行的用户界面。然而,Sysdig 团队还开发了一款商业产品,名为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 脚本默认部署的最新容器操作系统不完全兼容,该操作系统是谷歌的 Container-Optimized OS:cloud.google.com/container-optimized-os/docs

我们将被带到主 Sysdig Cloud 仪表板屏幕。在 Explore 选项卡下,我们应该看到至少两个 minion 节点。我们应该看到类似以下带有我们的 minion 节点的截图:

Sysdig Cloud 探索页面

此页面显示了一个表格视图,左侧的链接让我们探索一些关键的 CPU、内存、网络等指标。虽然这是一个很好的开始,但详细视图将让我们更深入地了解每个节点。

详细视图

让我们来看看这些视图。选择其中一个 minion 节点,然后滚动到下方出现的详细部分。默认情况下,我们应该看到 System: Overview by Process 视图(如果未选中,请从左侧的列表中单击它)。如果图表难以阅读,只需点击每个图表左上角的最大化图标即可获得更大的视图。

有各种有趣的视图可供探索。仅列出其中一些,Services | HTTP Overview 和 Hosts & Containers | Overview by Container 为我们提供了一些很棒的图表供检查。在后一视图中,我们可以看到容器的 CPU、内存、网络和文件使用情况统计。

拓扑视图

此外,底部还有三个拓扑视图。这些视图非常适合帮助我们了解我们的应用程序如何通信。点击 Topology | Network Traffic,等待几秒钟让视图完全填充。它应该看起来类似以下截图:

Sysdig Cloud 网络拓扑视图

我们注意到视图将集群中的 minion 节点与主节点之间的通信流量进行了映射。您还可以在节点方框的右上角看到一个 + 符号。点击其中一个 minion 节点,然后使用视图区域顶部的缩放工具放大到细节,如下面的截图所示:

Sysdig Cloud 网络拓扑详细视图

请注意,现在我们可以看到运行在主节点内的 Kubernetes 的所有组件。我们可以看到各种组件是如何协同工作的。我们将看到 kube-proxykublet 进程正在运行,以及一些带有 Docker 鲸鱼标志的方框,这表示它们是容器。如果我们放大并使用加号图标,我们将看到这些是我们的 Pod 和核心 Kubernetes 进程的容器,就像我们在第一章 Chapter 1,Introduction to Kubernetes 中运行在主节点上的服务部分中所见到的一样。

此外,如果您的监控节点中包括了主节点,我们可以观察 kublet 从 minion 发起通信,并一直跟踪到主节点中的 kube-apiserver 容器。

我们甚至有时可以看到实例与 GCE 基础架构进行通信以更新元数据。此视图非常适合形成我们的基础架构和底层容器之间如何通信的心理图像。

指标

接下来,让我们切换到左侧菜单旁边的 Metrics 标签。在这里,还有各种有用的视图。

让我们来看看 System 中的 capacity.estimated.request.total.count。这个视图向我们展示了一个节点在完全加载时可以处理多少请求的估计值。这对基础设施规划非常有用:

Sysdig Cloud 容量估算视图

警报

现在我们有了所有这些很棒的信息,让我们创建一些通知。滚动到页面顶部,找到一个 minion 条目旁边的铃铛图标。这将打开一个创建警报的对话框。在这里,我们可以设置类似于本章前面所做的手动警报。但是,还有使用 BASELINEHOST COMPARISON 的选项。

使用 BASELINE 选项非常有帮助,因为 Sysdig 将监视节点的历史模式,并在任何一个指标偏离预期指标阈值时向我们发出警报。不需要手动设置,因此这可以真正节省通知设置的时间,并帮助我们的运维团队在问题出现之前采取主动措施。请参考以下图片:

Sysdig Cloud 新警报

HOST COMPARISON 选项也是一个很好的帮助,因为它允许我们将指标与其他主机进行比较,并在一个主机具有与组不同的指标时发出警报。一个很好的用例是监视 minion 节点之间的资源使用情况,以确保我们的调度约束没有在集群的某个地方创建瓶颈。

您可以选择任何您喜欢的选项并给它一个名称和警告级别。启用通知方法。Sysdig 支持电子邮件,SNS(简称简单通知服务)和 PagerDuty 作为通知方法。您还可以选择启用 Sysdig Capture 以更深入地了解问题。一切都设置好后,只需点击创建,您就会开始收到问题警报。

sysdig 命令行

无论您只使用开源工具还是尝试完整的 Sysdig Cloud 套装,命令行实用程序都是跟踪问题或更深入了解系统的绝佳伴侣。

在核心工具中,有一个主要的 sysdig 实用程序,还有一个名为 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(如果您喜欢使用键盘)。在左侧菜单中,有各种选项,但我们将查看线程。双击以选择线程。

在某些操作系统和某些 SSH 客户端上,您可能会遇到功能键的问题。检查终端的设置,并确保功能键使用 VT100+序列。

我们可以看到当前系统上所有正在运行的线程以及一些关于资源使用情况的信息。默认情况下,我们看到的是一个经常更新的大列表。如果我们点击过滤器,F4用于鼠标受挑战者,我们可以简化列表。

在过滤框中键入kube-apiserver(如果您在主节点上)或kube-proxy(如果您在节点(minion)上),然后按Enter。视图现在仅过滤该命令中的线程:

Csysdig 线程

如果我们想进一步检查,我们可以简单地选择列表中的一个线程,然后点击 Dig 或按下F6。现在我们可以实时查看来自命令的系统调用的详细列表。这可以是一个非常有用的工具,可以深入了解我们集群上正在运行的容器和处理。

点击“返回”或按下Backspace键返回上一个屏幕。然后,再次转到“Views”。这次,我们将查看容器视图。再次,我们可以过滤并且还可以使用 Dig 视图来更深入地查看发生在系统调用级别的情况。

在这里你可能注意到的另一个菜单项是 Actions,在最新版本中可用。这些功能使我们能够从进程监视转到操作和响应。它使我们能够从 csysdig 的各种进程视图中执行各种操作。例如,容器视图具有进入 bash shell、杀死容器、检查日志等操作。值得了解各种操作和快捷键,甚至添加您自己的常见操作的自定义快捷键。

Prometheus

监控领域的一个新手是一个名为Prometheus的开源工具。Prometheus 是一个由 SoundCloud 团队构建的开源监控工具。您可以从prometheus.io了解更多关于该项目的信息。

他们的网站提供以下功能(您可以在本章末尾的参考资料中查看更多关于此的详细信息):

  • 一个多维度的数据模型(由度量名称和键/值对标识的时间序列)

  • 一个灵活的查询语言来利用这种多维性

  • 不依赖于分布式存储;单服务器节点是自主的

  • 时间序列收集通过 HTTP 的拉模型实现

  • 通过一个中间网关支持推送时间序列

  • 目标通过服务发现或静态配置发现

  • 多种图形和仪表板支持模式

CoreOS 在这里有一篇关于如何在 Kubernetes 中设置 Prometheus 的好博文:

coreos.com/blog/monitoring-kubernetes-with-prometheus.html

总结

我们简要了解了如何使用 Kubernetes 监控和记录。现在您应该对 Kubernetes 如何使用 cAdvisor 和 Heapster 收集给定集群中所有资源的指标有所了解。此外,我们还看到 Kubernetes 通过提供 InfluxDB 和 Grafana 设置和配置可以为我们节省时间。仪表板可以根据我们的日常运营需求进行轻松定制。

此外,我们还查看了使用 FluentD 和 Google 云日志服务的内置日志功能。此外,Kubernetes 通过为我们设置基础提供了极大的节约时间。

最后,您了解了监控我们的容器和集群的各种第三方选项。使用这些工具将使我们能够更深入地了解我们应用程序的健康和状态。所有这些工具结合在一起,为我们提供了一个扎实的工具集来管理我们的日常运营。

在下一章中,我们将探讨新的集群联邦功能。尽管仍然主要处于测试阶段,但这个功能将允许我们在不同数据中心甚至云中运行多个集群,但通过单一控制平面管理和分发应用程序。

参考文献

  1. stackdriver.com/

  2. www.sysdig.org/wiki/

  3. prometheus.io/docs/introduction/overview/

集群联合

本章将讨论新的联合能力以及如何使用它们来管理跨云提供商的多个集群。我们还将介绍核心结构的联合版本。我们将为您介绍联合部署、副本集、配置映射和事件。

本章将讨论以下主题:

  • 联合集群

  • 将多个集群联合起来

  • 跨多个集群检查和控制资源

  • 在多个集群中启动资源

介绍联合

尽管 联合 在 Kubernetes 中仍然非常新颖,但它为高度追求的跨云提供商解决方案奠定了基础。使用联合,我们可以在本地和一个或多个公共云提供商上运行多个 Kubernetes 集群,并管理利用我们组织的全部资源的应用程序。

这开始为避免云提供商锁定和高可用部署铺平道路,可以将应用服务器放置在多个集群中,并允许与我们的联合集群中位于单个点的其他服务进行通信。我们可以提高对特定提供商或地理位置的中断的隔离性,同时提供更大的灵活性,以便扩展和利用总体基础架构。

目前,联合平面支持这些资源(配置映射、守护程序集、部署、事件、入口、命名空间、副本集、密码和服务)。请注意,联合及其组件处于发布的 Alpha 和 Beta 阶段,因此功能可能仍然有些不稳定。

设置联合

尽管我们可以使用我们为其余示例运行的集群,但我强烈建议您从头开始。集群和上下文的默认命名可能对联合系统有问题。请注意,--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 

上述命令应该产生类似以下的内容:

上下文清单 #2

在继续之前,让我们确保所有联邦组件都在运行。联邦控制平面使用federation-system命名空间。使用指定了命名空间的kubectl get pods命令来监视进度。一旦看到两个 API 服务器 pod 和一个控制器 pod,你就应该准备好了:

$ kubectl get pods --namespace=federation-system

联邦 pod 清单 #

现在我们已经设置并运行了联邦组件,让我们切换到该上下文以进行下一步:

$ 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:

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

*列表 9-1. *node-js-deploy-fed.yaml

使用以下命令创建此部署:

$ kubectl create -f node-js-deploy-fed.yaml

现在让我们尝试列出此部署中的 Pods:

$ kubectl get pods

联邦上下文中没有 Pods

我们应该看到类似于上述所示的消息。这是因为我们仍在使用 master-control 或联邦上下文,该上下文本身不运行 Pods。然而,我们将在联邦平面上看到部署,并且如果我们检查事件,我们将看到部署实际上是在我们的联合集群上创建的:

$ kubectl get deployments
$ kubectl describe deployments node-js-deploy 

我们应该看到类似以下的内容。请注意,Events: 部分显示了我们在 GCE 和 AWS 上的部署情况:

联邦 Pods 部署

我们还可以使用以下命令查看联邦事件:

$ kubectl get events

联邦事件

所有三个 Pods 都运行可能需要一段时间。一旦发生这种情况,我们就可以切换到每个集群上下文,并在每个集群上看到一些 Pods。请注意,我们现在可以使用 get pods,因为我们在各个集群上而不是在控制平面上:

$ kubectl config use-context awsk8s
$ kubectl get pods

AWS 集群上的 Pods

$ kubectl config use-context gcek8s
$ kubectl get pods

GCE 集群上的 Pods

我们应该看到三个 Pods 分布在集群中,其中两个在一个集群上,第三个在另一个集群上。Kubernetes 在没有任何手动干预的情况下将其分布在集群中。任何失败的 Pods 将被重新启动,但现在我们增加了两个云提供商的冗余性。

联邦配置

在现代软件开发中,将配置变量与应用代码本身分离是很常见的。这样做可以更容易地更新服务 URL、凭据、常见路径等。将这些值放在外部配置文件中意味着我们可以在不重新构建整个应用程序的情况下轻松更新配置。

这种分离解决了最初的问题,但真正的可移植性是当您完全从应用程序中移除依赖关系时实现的。Kubernetes 提供了一个专门用于此目的的配置存储。ConfigMaps 是存储键值对的简单结构。

Kubernetes 还支持用于更敏感的配置数据的Secrets。这将在第十章 容器安全性中更详细地介绍。您可以在单个集群上或在我们此处演示的联邦控制平面上使用该示例,就像我们在这里使用 ConfigMaps 一样。

让我们看一个示例,让我们能够存储一些配置,然后在各种 pod 中使用它。以下清单将适用于联合和单个集群,但我们将继续为此示例使用联合设置。ConfigMap种类可以使用文字值、平面文件和目录,最后是 YAML 定义文件创建。以下清单是一个 YAML 定义文件:

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-application-config
  namespace: default
data:
  backend-service.url: my-backend-service

Listing 9-2: configmap-fed.yaml

让我们首先切换回我们的联合平面:

$ 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对象,让我们启动一个可以使用ConfigMap的联合ReplicaSet。这将在我们的集群中创建可以访问ConfigMap对象的 pod 的副本。ConfigMap可以通过环境变量或挂载卷进行访问。这个示例将使用一个提供文件夹层次结构和每个键内容表示值的挂载卷:

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

Listing 9-3: configmap-rs-fed.yaml

使用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

GCE 集群上的 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,我们将看到相同的值。这是一种很好的方式,可以将配置从应用程序中解耦并分布到我们的集群中。

其他联合资源

到目前为止,我们看到了联合部署、ReplicaSets、事件和 ConfigMaps 在行动中。DaemonSetsIngressNamespacesSecretsServices也受到支持。您的具体设置将有所不同,您可能会有一组与我们此处示例不同的集群。如前所述,这些资源仍处于测试阶段,因此值得花些时间尝试各种资源类型,并了解联合构造对您的特定基础设施组合的支持情况。

真正的多云

这是一个值得关注的激动人心的领域。随着它的发展,我们有一个真正良好的开始来进行多云实现,并在地区、数据中心甚至云提供商之间提供冗余。

虽然 Kubernetes 提供了一条简便且令人兴奋的多云基础设施路径,但重要的是要注意,生产多云需要比分布式部署更多的东西。从日志记录和监控到合规性和主机加固的全套功能,在多提供商设置中有很多要管理的内容。

真正的多云采用将需要一个精心规划的架构,而 Kubernetes 在追求这一目标上迈出了一大步。

摘要

在本章中,我们介绍了 Kubernetes 中的新联邦能力。我们看到了如何将集群部署到多个云提供商,并从单个控制平面管理它们。我们还在 AWS 和 GCE 中的集群中部署了一个应用程序。虽然这些功能是新的,仍主要处于 alpha 和 beta 阶段,但我们现在应该有能力利用它们,并将其作为标准 Kubernetes 运行模型的一部分。

在下一章中,我们将深入探讨另一个高级话题,安全性。我们将介绍安全容器的基础知识,以及如何保护您的 Kubernetes 集群。我们还将研究 Secrets 结构,它使我们能够存储类似于我们之前的 ConfigMap 示例中的敏感配置数据。

容器安全

本章将从容器运行时级别到主机本身讨论容器安全的基础知识。我们将讨论如何将这些概念应用于在 Kubernetes 集群中运行的工作负载,并讨论一些与运行您的 Kubernetes 集群特定相关的安全关注点和实践。

本章将讨论以下主题:

  • 基本容器安全

  • 容器镜像安全和持续性漏洞扫描

  • Kubernetes 集群安全

  • Kubernetes 秘密

容器安全基础

容器安全是一个深入的主题领域,本身就可以填满一本书。话虽如此,我们将涵盖一些高级别的关注点,并为思考这一领域提供一个起点。

在 第一章* Kubernetes 介绍 * 的 * 容器简介 * 部分,我们看到了 Linux 内核中支持容器技术的一些核心隔离特性。理解容器工作原理的细节是理解管理它们中的各种安全问题的关键。

深入研究的好文章是 NCC 的白皮书理解和加固 Linux 容器(您可以在本章末尾的参考文献部分的第 1 点中查看更多详细信息)。在 第七部分 中,该论文探讨了容器部署中涉及的各种攻击向量,我将总结(您可以在本章末尾的参考文献部分的第 1 点中查看更多详细信息)。

保持容器受限

论文中讨论的最明显的特征之一是逃避容器结构的孤立/虚拟化。现代容器实现使用命名空间来隔离进程,并允许控制容器可用的 Linux 权限。此外,越来越多地采用安全的默认配置来配置容器环境。例如,默认情况下,Docker 仅启用了一小部分权限(您可以在本章末尾的参考文献部分的第 2 点中查看更多详细信息)。网络是另一个逃逸的途径,由于现代容器设置中插入了各种网络选项,这可能是具有挑战性的。

论文中讨论的下一个领域是两个容器之间的攻击。用户命名空间模型在这里为我们提供了额外的保护,通过将容器内的 root 用户映射到主机上的较低级别用户。网络当然仍然是一个问题,需要在选择和实施容器网络解决方案时进行适当的勤勉和关注。

容器内部的攻击是另一个向量,与之前的担忧一样,命名空间和网络对于保护至关重要。在这种情况下至关重要的另一个方面是应用程序本身的安全性。代码仍然需要遵循安全编码实践,并且软件应该定期更新和打补丁。最后,容器镜像的效率具有缩小攻击面的附加好处。镜像应该只包含必需的软件包和软件。

资源耗尽和编排安全性

与拒绝服务攻击类似,在计算的各个其他领域中,资源耗尽在容器世界中非常相关。虽然 cgroups 对 CPU、内存和磁盘使用等资源使用提供了一些限制,但仍然存在有效的资源耗尽攻击途径。诸如 Docker 等工具为 cgroups 的限制提供了一些起始默认值,Kubernetes 还提供了可以放置在集群中运行的容器组的额外限制。了解这些默认值并为您的部署进行调整至关重要。

尽管 Linux 内核和启用容器的功能给我们提供了一定形式的隔离,但它们对于 Linux 操作系统来说还是相当新的。因此,它们仍然包含自己的错误和漏洞。用于能力和命名空间的内置机制可能会存在问题,跟踪这些问题作为安全容器操作的一部分是很重要的。

NCC 论文涵盖的最后一个领域是容器管理层本身的攻击。Docker 引擎、镜像仓库和编排工具都是重要的攻击向量,应在制定策略时予以考虑。我们将在接下来的章节更深入地研究如何解决镜像仓库和作为编排层的 Kubernetes。

如果你对 Docker 实现的特定安全功能感兴趣,请查看这里:

docs.docker.com/engine/security/security/.

镜像仓库

漏洞管理是现代 IT 运营的关键组成部分。零日漏洞正在增加,即使是有补丁的漏洞也可能很难修复。首先,应用程序所有者必须了解其漏洞和潜在补丁。然后,这些补丁必须集成到系统和代码中,通常需要额外的部署或维护窗口。即使对漏洞有所了解,修复也经常有延迟,通常需要大型组织数月时间才能打补丁。

虽然容器极大地改进了更新应用程序和最小化停机时间的过程,但漏洞管理中仍然存在挑战。特别是因为攻击者只需暴露一个这样的漏洞;任何少于 100% 的系统都未打补丁都存在被攻击的风险。

需要的是更快的反馈循环来解决漏洞。持续扫描并与软件部署生命周期结合是加速漏洞信息和修复的关键。幸运的是,这正是最新容器管理和安全工具正在构建的方法。

持续漏洞扫描

在这个领域出现了一个开源项目 Clair。我们从 Clair GitHub 页面了解到:Clair 是一个用于对 appcdocker 容器进行静态分析漏洞的开源项目

您可以在以下链接访问 Clair:

github.com/coreos/clair

Clair 会针对 公共漏洞和利用 (CVEs) 扫描您的代码。它可以集成到您的 CI/CD 流水线中,并在新构建的响应中运行。如果发现漏洞,它们可以作为反馈进入流水线,甚至停止部署并失败构建。这迫使开发人员在其正常发布过程中意识到并纠正漏洞。

Clair 可以与多个容器镜像仓库和 CI/CD 流水线集成。

Clair 甚至可以部署在 Kubernetes 上:github.com/coreos/clair#kubernetes

Clair 也被用作 CoreOS 的 Quay 镜像仓库中的扫描机制。Quay 提供了许多企业功能,包括持续漏洞扫

quay.io/

Docker Hub 和 Docker Cloud 都支持安全扫描。再次强调,推送到仓库的容器会自动针对 CVE 进行扫描,并根据任何发现发送漏洞通知。此外,还会对代码进行二进制分析,以与已知版本的组件签名进行匹配。

还有一系列其他的扫描工具可用于扫描您的镜像仓库,包括 OpenSCAP 以及 TwistlockAquaSec,我们将在第十二章,走向生产就绪中介绍。

镜像签名和验证

无论您是在内部使用私有镜像仓库还是在 Docker Hub 等公共仓库上使用,重要的是要知道您正在运行的只是您的开发人员编写的代码。下载时恶意代码或中间人攻击的潜在可能性是保护容器镜像的重要因素。

因此,rkt 和 Docker 都支持签署映像并验证内容未更改的能力。发布者可以在将映像推送到存储库时使用密钥签署映像,用户可以在客户端下载并验证签名后使用:

来自 rkt 文档:

"在执行远程获取的 ACI 之前,rkt 将根据 ACI 创建者生成的附加签名对其进行验证。"

Kubernetes 集群安全

Kubernetes 在其最新版本中持续添加了许多安全功能,并且具有一套完整的控制点,可用于您的集群;从安全的节点通信到 Pod 安全,甚至是敏感配置数据的存储。

安全的 API 调用

在每次 API 调用期间,Kubernetes 都会应用一些安全控制。此安全生命周期在此处描述:

API 调用生命周期

在建立安全 TLS 通信之后,API 服务器通过授权身份验证。最后,在请求到达 API 服务器之前,会应用一个准入控制器循环。

安全的节点通信

Kubernetes 支持在 API 服务器和任何客户端之间包括节点本身之间建立安全通信渠道。无论是 GUI 还是kubectl等命令行实用程序,我们都可以使用证书与 API 服务器进行通信。因此,API 服务器是对集群进行任何更改的中心交互点,是一个关键组件以确保安全。

在诸如 GCE 之类的部署中,默认情况下在每个节点上部署了kubelet以进行安全通信。此设置使用 TLS 引导启动和新证书 API 与 API 服务器建立安全连接,使用 TLS 客户端证书和证书颁发机构CA)集群。

授权和身份验证插件

Kubernetes 中用于身份验证和授权的插件机制还处于初期阶段。但是,这些功能在接下来的几个版本中也在继续开发。还有第三方提供者与这里的功能集成。

目前支持身份验证的形式包括令牌、密码和证书,并计划在以后的阶段添加插件功能。支持 OpenID Connect 令牌,还有几个第三方实现,如来自 CoreOS 的 Dex 和来自 Cloud Foundry 的 aser 账户和身份验证。

授权已经支持了三种模式。完整的RBAC(即基于角色的访问控制)模式仍在进行中,最终将从 Kubernetes 本身引入成熟的基于角色的身份验证。基于属性的访问控制ABAC)已经得到支持,并允许用户通过文件中的属性定义权限。最后,支持 webhook 机制,允许通过 REST Web 服务调用与第三方授权进行集成。

请在这里了解更多关于每个领域的信息:

准入控制器

Kubernetes 还提供了一种与附加验证集成的机制作为最后一步。这可以是图像扫描、签名检查或任何能够以指定方式响应的东西。当进行 API 调用时,会调用该钩子,服务器可以运行其验证。准入控制器还可用于转换请求并添加或更改原始请求。操作运行后,然后发送带有指示 Kubernetes 允许或拒绝调用的状态的响应。

这对于验证或测试图像尤其有帮助,正如我们在上一节中提到的那样。ImagePolicyWebhook插件提供了一个准入控制器,允许与额外的图像检查集成。

欲了解更多信息,请访问以下文档中的“使用准入控制器”页面:

kubernetes.io/docs/admin/admission-controllers/.

Pod 安全策略和上下文

Kubernetes 安全工具箱中的最新添加之一是Pod 安全策略和上下文。这允许用户控制容器进程和附加卷的用户和组,限制使用主机网络或命名空间,甚至将根文件系统设置为只读。此外,我们可以限制可用的功能,并为应用于每个 pod 中的容器的标签设置 SELinux 选项。

除了 SELinux 外,Kubernetes 还通过注释添加了对使用 AppArmor 的支持。有关更多信息,请参阅以下文档页面:

kubernetes.io/docs/admin/apparmor/.

我们将通过一个示例演示如何使用 pod 安全上下文为我们的 pod 添加一些约束。由于功能仍处于 beta 阶段,我们需要启用 beta 扩展 API,并在使用的准入控制器列表中添加PodSecurityPolicy

启用 beta API

首先,您需要 SSH 到您的主节点,切换到root用户,然后使用您首选的编辑器编辑/etc/kubernetes/manifests/kube-apiserver.manifest文件。同样,我们可以通过 Google Cloud CLI 进行 SSH,或者使用 Google Cloud Console,在 VM 实例页面上有一个内置的 SSH 客户端。

最佳实践是不要直接 SSH 到节点本身。然而,在本书的几个地方,我们已经这样做了,用于说明目的。重要的是要了解节点本身运行的情况,并且有时候对于学习和故障排除可能是必要的。话虽如此,当您只需要在集群或 pod 中运行命令时,请使用诸如kubectl exec之类的工具。

滚动到命令部分,我们应该看到类似以下清单的内容:

"bin/sh",
"-c",
"/usr/local/bin/kube-apiserver --v=2 --cloud-config=/etc/gce.conf --address=127.0.0.1 --allow-
privileged=true --authorization-policy-file=/etc/srv/kubernetes/abac-authz-policy.jsonl --basic-auth-file=/etc/srv/kubernetes/basic_auth.csv --cloud-provider=gce --client-ca-file=/etc/srv/kubernetes/ca.crt 
--etcd-servers=http://127.0.0.1:2379 --etcd-servers-overrides=/events#http://127.0.0.1:4002 --secure-port=443 --tls-cert-file=/etc
/srv/kubernetes/server.cert --tls-private-key-file=/etc/srv/kubernetes/server.key --token-auth-file=/etc/srv/kubernetes/known_tokens.csv --storage-backend=etcd2 --target-ram-mb=180 --service-cluster-ip-range=10.0.0.0/16 --etcd-quorum-read=false --admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota 
--authorization-mode=ABAC --allow-privileged=true 1>>/var/log/kube-apiserver.log 2>&1"

你的清单可能会有所不同,所以只需按照下面粗体标记的参数添加。同时,复制原始清单作为备份,以便以后需要时可以恢复:

"bin/sh",
"-c",
"/usr/local/bin/kube-apiserver --v=2 --cloud-config=/etc/gce.conf --address=127.0.0.1 
--allow-privileged=true --authorization-policy-file=/etc/srv/kubernetes/abac-authz-policy.jsonl --basic-auth-file=/etc/srv/kubernetes/basic_auth.csv --cloud-provider=gce --client-ca-file=/etc/srv/kubernetes/ca.crt --etcd-servers=http://127.0.0.1:2379 --etcd-servers-overrides=/events#http://127.0.0.1:4002 --secure-port=443 --tls-cert-file=/etc/srv/kubernetes/server.cert --tls-private-key-file=/etc/srv/kubernetes/server.key --token-auth-file=/etc/srv/kubernetes/known_tokens.csv --storage-backend=etcd2 --target-ram-mb=180 --service-cluster-ip-range=10.0.0.0/16 --etcd-quorum-read=false 
--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,PodSecurityPolicy --authorization-mode=ABAC --allow-privileged=true --runtime-config=extensions/v1beta1=true,extensions/v1beta1/podsecuritypolicy=true 1>>/var/log/kube-apiserver.log 2>&1"

如果你拥有 root shell,保存文件并退出sudo。如果一切顺利,Kubernetes 应该注意到清单的更改并重新启动 API 服务器。这可能需要几分钟,在重启期间,kubectl可能会失去响应。我通常使用以下命令来观察:

$ kubectl get pods --namespace=kube-system

观察STATUSAGE列。一旦重启成功,我们将有一个RunningSTATUS和一个几分钟或更短的AGE

如果我们在清单中有任何拼写错误,我们可能会在STATUS中看到错误,甚至会获得一个永久性无响应的kubectl。如果发生这种情况,我们将需要恢复我们之前的参数。如果一切都失败了,您可以重新启动实例。GCE 设置的默认值有一个引导脚本,该脚本将使用默认设置替换清单。

一旦您的 API 服务器更新并运行,我们就可以添加一个安全策略,并运行一个定义了 pod 安全上下文的 pod。该策略在集群级别运行,并强制执行所有 pod 的策略。pod 安全上下文设置在 pod 定义中,并且仅适用于该 pod。

创建 PodSecurityPolicy

现在我们已经添加了PodSecurityPolicy准入控制器,我们需要在列表 10-2下面进一步创建一个示例之前添加一个 pod 安全策略。如果我们现在尝试创建该 pod,我们将看到类似于这样的错误:

没有 PodSecurityPolicy 的 Pod 错误

再次强调,pod 安全策略适用于整个集群:

{
  "kind": "PodSecurityPolicy",
  "apiVersion":"extensions/v1beta1",
  "metadata": {
    "name": "default"
  },
  "spec": {
    "privileged": false,
    "seLinux": {
      "rule": "RunAsAny"
    },
    "supplementalGroups": {
      "rule": "RunAsAny"
    },
    "runAsUser": {
      "rule": "RunAsAny"
    },
    "fsGroup": {
      "rule": "RunAsAny"
    },
    "volumes": ["*"],
    "readOnlyRootFilesystem": true
  }
}

列表 10-1default-security-policy.json

使用以下命令创建:

$ kubectl create -f default-security-policy.json

上述默认策略不允许容器以特权模式运行。它允许任何 seLinux 标签,任何附加的组 ID,任何用户运行第一个进程,以及任何文件系统的组 ID。它还支持所有类型的卷。

您可以在源代码中找到所有可能的参数,但为方便起见,我创建了以下表格。您可以在我的新网站上找到更多类似这样的便捷查找:

www.kubesheets.com

参数 类型 描述 必需
Privileged bool 允许或禁止以特权运行 Pod。
DefaultAddCapabilities []v1.Capaility 这定义了添加到容器中的一组默认功能。如果 Pod 指定了一个要删除的功能,那么将覆盖然后添加到这里。值是 POSIX 功能的字符串,减去前缀CAP_。例如,CAP_SETUID 将是 SETUIDman7.org/linux/man-pages/man7/capabilities.7.html
RequiredDropCapabilities []v1.Capaility 这定义了必须从容器中丢弃的一组功能。Pod 不能指定这些功能中的任何一个。值是 POSIX 功能的字符串,减去前缀CAP_。例如,CAP_SETUID 将是 SETUIDman7.org/linux/man-pages/man7/capabilities.7.html
AllowedCapabilities []v1.Capaility 这定义了一组允许并可以添加到容器中的功能。Pod 可以指定这些功能中的任何一个。值是 POSIX 功能的字符串,减去前缀CAP_。例如,CAP_SETUID 将是 SETUIDman7.org/linux/man-pages/man7/capabilities.7.html
Volumes []FSType 此列表定义可以使用的卷。留空以使用所有类型。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/user-guide/pod-security-policy/#strategies
RunAsUser RunAsUserStrategyOptions 将其设置为以下策略选项之一,如此处所定义:kubernetes.io/docs/user-guide/pod-security-policy/#strategies
SupplementalGroups SupplementalGroupsStrategyOptions 将其设置为以下策略选项之一,如此处所定义:kubernetes.io/docs/user-guide/pod-security-policy/#strategies
FSGroup FSGroupStrategyOptions 将其设置为以下策略选项之一,如此处所定义:kubernetes.io/docs/user-guide/pod-security-policy/#strategies
ReadOnlyRootFilesystem bool 将其设置为 true 将会拒绝该 pod 或强制其以只读根文件系统运行。

表 10-1. Pod 安全策略参数(你可以在本章末尾的参考文献第 3 点中查看更多详细信息)

现在我们对集群有了一个基本策略,让我们创建一个 Pod。首先,我们将创建一个带有我们的 node-express-info 容器的 Pod

apiVersion: v1
kind: Pod
metadata:
    name: node-js-nopsc
spec:
  containers:
  - name: node-js-nopsc
    image: jonbaier/node-express-info:latest
    ports:
    - containerPort: 80

清单 10-2: nodejs-pod-nopsc.yaml

使用上述清单创建 pod。然后使用 kubectl exec 命令获取 pod 内部的 shell。接下来,我们将尝试使用 touch 命令创建一个文件:

$ kubectl exec -it node-js-nopsc bash root@node-js-nopsc:/src# touch file.txt

我们应该会收到类似于 touch: cannot touch 'file.txt': Read-only file system 的错误。这是因为我们将 ReadOnlyFileSystem 属性设置为 true,所以所有容器(无论是否定义了 pod 安全上下文)现在都以只读根文件系统运行。键入 exit 退出此 pod。

使用 PodSecurityContext 创建一个 pod

现在我们已经看到了 pod 安全策略的影响,让我们来探索一下 pod 安全上下文。在这里,我们可以定义 seLinuxOptions,它让我们能够为 pod 中的容器定义标签上下文。我们还可以定义 runAsUser,以指定每个容器将使用的 UID,以及 runAsNonRoot 标志,它将简单地阻止以 UID 0root 运行的容器启动。我们还可以使用 supplementalGroup 指定每个容器中第一个进程的组(GID)。最后,我们可以使用 fsGroup 指定文件系统所有权和新文件的组(GID)。

清单 10-4 是我们先前的 node-express-info pod 的一个版本,其中 runAsNonRoot 设置为 true。重要的是要理解,如果在 Dockerfile 中没有定义用户,则 root(UID 0)是默认用户。 清单 10-3 显示了我们 node-express-info 容器的 Dockerfile。我们没有定义 USER 指令,因此它将以 root 身份运行:

FROM node:latest

ADD src/ /src
WORKDIR /src

RUN npm install

ENV PORT=80

CMD ["node", "index.js"]

清单 10-3: node-express-info Dockerfile

apiVersion: v1
kind: Pod
metadata:
    name: node-js-pod
spec:
  containers:
  - name: node-js-pod
    image: jonbaier/node-express-info:latest
    ports:
    - containerPort: 80
  securityContext:
    runAsNonRoot: true

清单 10-4: nodejs-pod-psc.yaml

理解安全上下文与容器构建方式之间的关系很重要。如果我们尝试使用 kubectl create -f nodejs-pod-psc.yaml 创建上述的 Listing 10-4,我们会发现它永远不会启动,并给出 验证非根错误

验证非根错误

理解以安全方式运行容器不仅仅是管理员添加约束的任务。这项工作必须与将正确创建图像的开发人员合作进行。

清理工作

我们制定的策略可能对学习和开发来说过于严格,因此您可能希望删除它。您可以使用以下命令执行此操作:

$ kubectl delete psp default 

您还需要撤消在本节开头对 Kubernetes 主节点上的 /etc/kubernetes/manifests/kube-apiserver.manifest 所做的更改。具体来说,您应该从 admission-control 部分的列表中删除 PodSecurityPolicy

额外的考虑事项

除了我们刚刚审查的功能之外,Kubernetes 还有许多其他构造应该在整个集群强化过程中考虑。在本书的前面,我们看到了为多租户提供逻辑分离的命名空间。虽然命名空间本身不会隔离实际的网络流量,但一些网络插件,如 Calico 和 Canal,提供了额外的网络策略功能。我们还看到了可以为每个命名空间设置的配额和限制,应该用于防止单个租户或项目在集群中消耗过多的资源。

保护敏感应用程序数据(秘密)

有时,我们的应用程序需要保存敏感信息。这可以是登录到数据库或服务的凭据或令牌。将这些敏感信息存储在图像本身中是应该避免的。在这里,Kubernetes 在秘密构造中为我们提供了一个解决方案。

秘密给了我们一种在资源定义文件中不包含明文版本的敏感信息的存储方式。秘密可以挂载到需要它们的 pod 中,然后在 pod 内部以包含秘密值的文件形式访问。或者,你也可以通过环境变量暴露秘密。

我们可以轻松地使用 YAML 或命令行创建秘密。秘密确实需要进行 base64 编码,但如果我们使用 kubectl 命令行,则此编码将为我们执行。

让我们从以下秘密开始:

$ kubectl create secret generic secret-phrases --from-literal=quiet-phrase="Shh! Dont' tell"

然后我们可以使用以下命令检查秘密:

$ kubectl get secrets

现在我们已经成功创建了秘密,让我们创建一个可以使用秘密的 pod。通过附加卷的方式,秘密在 pod 中被消耗。在下面的 Listing 10-5 中,您会注意到我们使用 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

Listing 10-5secret-pod.yaml

使用kubectl create -f secret-pod.yaml命令创建此 Pod。一旦创建,我们可以通过kubectl exec获取该 Pod 中的 bash shell,然后切换到我们在 Pod 定义中设置的/etc/secret-phrases文件夹。列出该目录会显示一个单独的文件,文件名为我们之前创建的秘密:

$ kubectl exec -it secret-pod bash
$ cd /etc/secret-phrases
$ ls

如果我们显示这些内容,应该会看到我们之前编码的短语Shh! Dont' tell

$ cat quiet-phrase

通常,这将用于数据库或服务的用户名和密码,或任何敏感凭据和配置数据。

请注意,Secrets 仍处于早期阶段,但它们是生产操作的重要组成部分。这里计划对未来发布进行一些改进。目前,Secrets 仍以明文形式存储在 etcd 服务器中。但是,Secrets 构建确实允许我们控制哪些 Pod 可以访问它,并将信息存储在 tmpfs 上,但不会为每个 Pod 持续存储它。您可能希望对生产就绪系统采取更多保护措施。

摘要

我们研究了基本容器安全性和一些重要的考虑因素。我们还涉及了基本镜像安全性和持续漏洞扫描。在本章稍后,我们将查看 Kubernetes 的整体安全功能,包括用于存储敏感配置数据的 Secrets,安全的 API 调用,甚至为在我们集群上运行的容器设置安全策略和上下文。

现在,您应该已经有了一个稳固的起点,用于保护您的集群并向生产环境迈进。为此,下一章将涵盖向生产环境迈进的整体策略,并且还将查看一些第三方供应商提供的填补空白并在路上协助您的工具。

参考资料

  1. www.nccgroup.trust/globalassets/our-research/us/whitepapers/2016/april/ncc_group_understanding_hardening_linux_containers-10pdf/

  2. github.com/docker/docker/blob/master/oci/defaults_linux.go#L62-L77

  3. github.com/kubernetes/kubernetes/blob/release-1.5/pkg/apis/extensions/v1beta1/types.go#L1075

使用 OCP、CoreOS 和 Tectonic 扩展 Kubernetes

本章的前半部分将介绍开放标准如何鼓励多样化的容器实现生态系统。我们将查看开放容器倡议OCI)及其提供开放容器规范的使命。本章的后半部分将介绍 CoreOS 及其作为主机操作系统的优势,包括性能和对各种容器实现的支持。此外,我们还将简要介绍 CoreOS 提供的 Tectonic 企业版。

本章将讨论以下主题:

  • 为什么标准很重要?

  • 开放容器倡议和云原生计算基金会

  • 容器规范与实现

  • CoreOS 及其优势

  • Tectonic

标准的重要性

过去两年来,容器化技术的普及度有了巨大的增长。尽管 Docker 一直处于这一生态系统的中心位置,但容器领域的参与者数量正在增加。目前已经有许多替代方案用于容器化和 Docker 实现本身(rktGarden 等)。此外,还有丰富的第三方工具生态系统,可以增强和补充您的容器基础设施。Kubernetes 明确地位于该生态系统的编排方面,但底线是所有这些工具都构成了构建云原生应用程序的基础。

正如我们在书的开头提到的,容器最吸引人的一个特点是它们能够将我们的应用程序打包部署到各种环境层(即开发、测试和生产)和各种基础设施提供商(如 GCP、AWS、本地部署等)。

要真正支持这种部署灵活性,我们不仅需要容器本身具有共同的平台,还需要底层规范遵循一套共同的基本规则。这将允许实现既灵活又高度专业化。例如,一些工作负载可能需要在高度安全的实现上运行。为了提供这一点,实现将不得不对一些实现的方面做出更多有意识的决定。无论哪种情况,如果我们的容器建立在所有实现都同意并支持的共同结构上,我们将拥有更多的灵活性和自由度。

开放容器倡议

首个获得广泛行业参与的倡议之一是 OCI。在 36 家行业合作伙伴中包括 Docker、Red Hat、VMware、IBM、Google 和 AWS,在 OCI 网站上列出了它们的名单:

www.opencontainers.org/

OCI 的目的是将实现(如 Docker 和 rkt)与容器化工作负载的格式和运行时的标准规范分开。根据它们自己的术语,OCI 规范的目标有三个基本原则(你可以在本章末尾的参考资料部分的第 1 点中找到更多详细信息):

  • 创建容器镜像格式和运行时的正式规范,这将使符合规范的容器可以在所有主要的、符合规范的操作系统和平台上可移植,而不会受到人为的技术障碍的限制。

  • 接受、维护和推进与这些标准相关的项目(项目)。它将寻求达成一致的一组容器操作(启动、执行、暂停等)以及与容器运行时相关的运行时环境的标准。

  • 将之前提到的标准与其他提议的标准进行协调,包括 appc 规范。

云原生计算基金会

另一个也被广泛接受的倡议是云原生计算基金会CNCF)。虽然仍然专注于容器化工作负载,但 CNCF 在堆栈中的应用设计层次上运作得更高。其目的是提供一套标准的工具和技术,用于构建、运行和编排云原生应用堆栈。云计算使我们可以访问各种新技术和实践,可以改进和演进我们的经典软件设计。这也特别关注于面向微服务的新范式开发。

作为 CNCF 的创始成员,谷歌已将 Kubernetes 开源项目捐赠为第一步。目标是增加生态系统中的互操作性,并支持与其他项目更好地集成。CNCF 已经在编排、日志记录、监控、追踪和应用程序弹性方面托管了各种项目。

有关 CNCF 的更多信息,请参阅cncf.io/.

标准容器规范

OCI 努力的一个核心结果是创建和发展全面容器规范。该规范有五个所有容器都必须遵循的核心原则,我将简要概述一下(你可以在本章末尾的参考资料部分的第 2 点找到更多详细信息):

  • 容器必须具有在所有实现中创建、启动和停止容器的标准操作

  • 容器必须是内容不可知的,这意味着容器内部应用程序的类型不会改变容器本身的标准操作或发布。

  • 容器也必须是基础设施不可知的。可移植性至关重要;因此,容器必须能够在 GCE 中与在公司数据中心或开发人员的笔记本电脑上同样轻松地运行。

  • 一个容器还必须设计用于自动化,这使我们能够在构建、更新和部署流水线之间自动化。虽然这条规则有点模糊,但容器实现不应要求繁琐的手动步骤来创建和发布。

  • 最后,实现必须支持工业级交付。再次,这涉及到构建和部署流水线,要求对容器在基础设施和部署层之间的可移植性和传输的高效流程。

规范还定义了容器格式和运行时的核心原则。你可以在 GitHub 项目上阅读更多有关规范的信息。

github.com/opencontainers/specs

虽然核心规范可能有点抽象,但runC实现是 OCI 规范的具体示例,以容器运行时和镜像格式的形式呈现。你可以在以下网址的 runC 网站和 GitHub 上阅读更多技术细节:

一系列热门容器工具的支持格式和运行时基于runC。这是由 Docker 捐赠给 OCI 的,并且是从 Docker 平台中使用的相同基础工作创建的。自发布以来,它已被许多项目欢迎。

即使是流行的开源 PaaS,Cloud Foundry也宣布将在 Garden 中使用 runC。 Garden 为 Deigo 提供容器化基础设施,Deigo 则充当类似 Kubernetes 的编排层。

rkt 的实现最初基于appc规范。实际上,appc 规范是 CoreOS 团队早期尝试形成关于容器化的共同规范。现在 CoreOS 正在参与 OCI,并且正在努力将 appc 规范合并到 OCI 中;这应该会导致容器生态系统中更高水平的兼容性。

CoreOS

虽然规范为我们提供了一个共同基础,但在我们容器的操作系统选择周围也有一些趋势正在演变。有几个特别为运行容器工作负载而开发的定制操作系统。尽管实现各不相同,但它们都具有类似的特征。专注于精简安装基础、原子操作系统更新和签名应用程序以实现高效和安全的运行。

正在受到欢迎的一个操作系统是CoreOS。CoreOS 在安全性和资源利用方面都提供了主要好处。它通过完全删除软件包依赖关系来提供资源利用。相反,CoreOS 将所有应用程序和服务都运行在容器中。通过仅提供支持运行容器所需的一小组服务,并且绕过使用虚拟化程序的需要,CoreOS 让我们能够使用更大比例的资源池来运行我们的容器化应用程序。这使得用户可以从他们的基础设施获得更高的性能和更好的容器与节点(服务器)使用比例。

更多容器操作系统 最近出现了几个其他容器优化的操作系统。

Red Hat Enterprise Linux Atomic Host 专注于安全性,默认启用 SELinux 并提供类似于 CoreOS 的 Atomic 更新操作系统。请参考以下链接:

网址为access.redhat.com/articles/rhel-atomic-getting-started

Ubuntu Snappy 也利用了将操作系统组件与框架和应用程序分离的效率和安全性提升。使用应用程序镜像和验证签名,我们可以获得一个高效的基于 Ubuntu 的操作系统,用于我们的容器工作负载,网址为www.ubuntu.com/cloud/tools/snappy

Ubuntu LXD 运行一个容器虚拟化程序,并提供了一个轻松迁移 Linux-based VMs 到容器的路径:

网址为www.ubuntu.com/cloud/lxd

VMware Photon 是另一个针对 vSphere 和 VMware 平台进行优化的轻量级容器操作系统。它运行 Docker、rkt 和 Garden,并且还有一些可以在热门公共云提供商上运行的镜像。请参考以下链接:

网址为vmware.github.io/photon/

利用容器的隔离性,我们增加了可靠性并降低了每个应用程序更新的复杂性。现在应用程序可以在每个新的容器发布准备就绪时与支持的库一起进行更新:

CoreOS 更新

最后,CoreOS 在安全领域还有一些额外的优势。首先,该操作系统可以作为一个整体单位进行更新,而不是通过单个软件包进行更新(参见上图)。这避免了许多由部分更新引起的问题。为了实现这一点,CoreOS 使用两个分区——一个作为活动的操作系统分区,另一个用于接收完整的更新。一旦更新成功完成,系统将重新启动并提升辅助分区。如果出现任何问题,原始分区可用于回退。

系统所有者还可以控制何时应用这些更新。这使我们可以灵活地优先考虑关键更新,同时与更常见的更新的实际调度一起工作。此外,整个更新都经过签名并通过 SSL 进行传输,以增加整个过程的安全性。

rkt

CoreOS 生态系统的一个核心组成部分是它自己的容器运行时,名为 rkt。正如我们之前提到的,rkt 是另一种专注于安全性的实现。rkt 的主要优势在于以无守护程序的根用户身份运行引擎,就像 Docker 今天所做的那样。最初,rkt 在建立容器镜像的信任方面也有优势。然而,Docker 的最新更新通过新的内容信任功能取得了长足的进步。

简而言之,rkt 仍然是一个专注于在生产环境中运行容器的实现。rkt 使用一种名为ACI的图像格式,但它也支持运行基于 Docker 的图像。在过去的一年中,rkt 经历了重大更新,现在已经到了 1.24.0 版本。它作为在生产环境中安全运行 Docker 图像的方法已经获得了很大的动力。

此外,CoreOS 正在与Intel®合作,集成新的Intel®虚拟化技术,该技术允许容器在更高级别的隔离中运行。这种硬件增强的安全性允许容器在类似于当今我们看到的 hypervisors 的内核中运行,从而提供了与内核隔离相似的隔离。

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 记录,并且需要一个像Route53这样的服务或者一个生产 DNS 服务。在按照说明操作时,您需要将 DNS 设置为您有权限设置记录的域或子域。在集群运行起来并且有定义动态端点后,我们需要更新记录。

就这样!我们现在有一个运行 CoreOS 的集群。该脚本创建了所有必要的 AWS 资源,例如虚拟私有云VPCs)、安全组和 IAM 角色。现在集群已经运行起来,我们可以使用 status 命令获取端点并更新我们的 DNS 记录:

$ kube-aws status

复制紧挨着Controller DNS Name列出的条目,然后编辑您的 DNS 记录,将之前指定的域名或子域指向该负载均衡器。

如果您忘记了指定的域名,或者需要检查配置,可以使用您喜欢的编辑器查看生成的 kubeconifg 文件。它看起来会像这样:

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 使用刚刚创建的集群的配置文件的路径。如果我们想要从同一台机器管理多个集群,这也很有用。

Tect

在 CoreOS 上运行 Kubernetes 是一个很好的开始,但您可能会发现您需要更高级别的支持。来看看Tectonic,CoreOS 提供的用于在 CoreOS 上运行 Kubernetes 的企业级产品。Tectonic 使用了我们已经讨论过的许多组件。CoreOS 是操作系统,支持 Docker 和 rkt 运行时。此外,Kubernetes、etcd 和 flannel 被打包在一起,形成了一个完整的集群编排栈。我们在第三章简要讨论了 flannel,网络、负载均衡器和入口。它是一个覆盖网络,使用了类似于原生 Kubernetes 模型的模型,并将 etcd 用作后端。

类似于 Red Hat,CoreOS 还提供了类似的支持套餐,为构建在其上的开源软件 Tectonic 提供 24x7 支持。Tectonic 还提供定期的集群更新以及一个漂亮的仪表板,显示了 Kubernetes 的所有组件的视图。CoreUpdate 允许用户更多地控制自动更新。此外,它还提供了用于监控、SSO 和其他安全功能的模块。

您可以在此处找到更多信息以及最新的安装说明:

coreos.com/tectonic/docs/latest/install/aws/index.html.

仪表板亮点

这里是 Tectonic 仪表板的一些亮点:

Tectonic 主仪表板

Tectonic 现在已经普遍可用,仪表板已经具有一些不错的功能。如下截图所示,我们可以看到关于我们的复制控制器的很多详细信息,甚至可以使用 GUI 通过点击按钮来进行上下缩放:

Tectonic 复制控制器详细信息

这个图形相当大,所以它跨两页显示:

另一个很好的功能是 Events 页面。在这里,我们可以观看事件直播,暂停,并根据事件严重程度和资源类型进行筛选:

事件流

浏览仪表板系统中任何位置的一个有用功能是命名空间:过滤选项。只需点击显示资源的任何页面顶部旁边的下拉菜单旁边的“Namespace:”,我们就可以按命名空间筛选我们的视图。如果我们想要过滤掉 Kubernetes 系统 pod 或只查看特定的一组资源,这可能会有所帮助:

命名空间过滤

摘要

在本章中,我们看到了容器社区中新兴的标准机构,以及它们如何通过开放的规范来塑造技术,使其变得更好。我们还仔细研究了 CoreOS,这是容器和 Kubernetes 社区中的关键参与者。我们探索了他们正在开发的技术,以增强和补充容器编排,并亲自看到了如何在 Kubernetes 中使用其中的一些。最后,我们看了一下 Tectonic 的企业支持套件以及目前可用的一些功能。

在下一章,也就是最后一章中,我们将探讨更广泛的 Kubernetes 生态系统以及可用于将您的集群从开发和测试转移到完全成熟的生产环境的工具。

参考资料

  1. www.opencontainers.org/faq/(在 OCI 任务有多广泛? 下)

  2. github.com/opencontainers/specs/blob/master/principles.md

朝着生产就绪方向

在本章中,我们将讨论转向生产的考虑因素。我们还将展示 Kubernetes 社区中的一些有用工具和第三方项目,并介绍您可以获取更多帮助的地方。

本章将讨论以下主题:

  • 生产特性

  • Kubernetes 生态系统

  • 如何获得帮助?

准备就绪投入生产

我们通过 Kubernetes 进行了许多典型操作。正如我们所见,K8s 提供了各种功能和抽象,可以减轻容器部署的日常管理负担。

有许多特性定义了一个容器生产就绪系统。以下图表提供了一个高级视图,涵盖了生产就绪集群的主要关注点。这绝不是一个详尽的列表,但旨在为投入生产操作提供一些坚实的基础:

容器运行的生产特性

我们看到了 Kubernetes 的核心概念和抽象如何解决一些这些问题。服务抽象在服务和应用程序级别都内置了服务发现和健康检查。我们还从复制控制器和部署构造中获得无缝应用程序更新和可伸缩性。所有服务、复制控制器、副本集和 Pod 的核心抽象都与核心调度和亲和力规则集一起工作,并为我们提供了易于服务和应用程序组合的功能。

内置支持各种持久存储选项,并且网络模型提供了可管理的网络操作,并提供与其他第三方提供商合作的选项。此外,我们简要介绍了市场上一些热门工具与 CI/CD 集成。

此外,我们还内置了系统事件跟踪,并与主要云服务提供商集成,提供了监控和日志记录的即用即连设置。我们还看到了如何将这些扩展到第三方提供商,如StackDriverSysdig。这些服务还关注节点的整体健康状况,并提供主动的趋势偏差警报。

核心构建还帮助我们解决应用程序和服务层的高可用性问题。调度器可以与自动缩放机制一起在节点级别提供此功能。然后,支持使 Kubernetes 主节点本身高度可用。在第九章中,集群联合,我们简要介绍了承诺未来多云和多数据中心模型的新联合功能。

最后,我们探讨了一种新的操作系统类型,为我们提供了精简的基础和用于补丁和更新的安全更新机制。精简的基础,结合调度,可以帮助我们实现高效的资源利用。此外,我们还关注了一些加固问题,并探索了可用的图像信任和验证工具。安全性是一个广泛的话题,针对这个话题存在一系列功能矩阵。

准备好,开始吧

尽管仍有一些空白,剩余的安全性和运维问题正在被第三方公司积极解决,我们将在下一节中看到。未来,Kubernetes 项目将继续发展,周围的 K8s 和 Docker 项目以及合作伙伴社区也会不断壮大。社区正在以惊人的速度弥补剩余缺陷。

第三方公司

自 Kubernetes 项目最初发布以来,合作伙伴生态系统不断增长。我们在之前的章节中曾关注了 CoreOS、Sysdig 和许多其他公司,但在这个领域有各种项目和公司。我们将突出一些可能在您向生产环境迈进时有用的项目。这绝对不是一个详尽的清单,只是为了提供一些有趣的起点。

私有注册表

在许多情况下,组织可能不希望将他们的应用程序和/或知识产权放入公共存储库中。对于这些情况,私有注册表解决方案有助于在端到端安全地集成部署。

谷歌云提供 Google Container Registry cloud.google.com/container-registry/

Docker 提供了自己的 Trusted Registry www.docker.com/docker-trusted-registry

Quay.io 也提供安全的私有注册表、漏洞扫描,并来自 CoreOS 团队 quay.io/

谷歌容器引擎

谷歌是原始 Kubernetes 项目的主要作者,仍然是主要贡献者。尽管本书主要关注在我们自己运行 Kubernetes,谷歌还通过 Google 云平台提供了一个完全托管的容器服务。

Google Container Engine (GKE) 网站上找到更多信息 cloud.google.com/container-engine/

cloud.google.com/container-engine/

Kubernetes 将安装在 GCE 上,并由 Google 工程师进行管理。他们还提供私有注册表和与现有私有网络集成。

创建您的第一个 GKE 集群 从 GCP 控制台,在 Compute 中,点击容器引擎,然后点击容器集群。

如果这是您第一次创建集群,在页面中间会有一个信息框。点击创建一个容器集群按钮。

为您的集群和区域选择一个名称。您还将能够为您的节点选择机器类型(实例大小)和您希望在您的集群中有多少个节点(集群大小)。您还将看到一个节点镜像的选择,它让您可以为节点自身选择基础操作系统和机器镜像。主节点由 Google 团队自行管理和更新。保留 Stackdriver Logging 和 Stackdriver Monitoring 勾选状态。单击创建,几分钟后,您将拥有一个新的可以使用的集群。

您需要使用包含在 Google SDK 中的 kubectl 来开始使用您的 GKE 集群。有关安装 SDK 的详细信息,请参阅 第一章,Kubernetes 简介。一旦我们有了 SDK,我们可以使用以下步骤为我们的集群配置 kubectl 和 SDK

cloud.google.com/container-engine/docs/before-you-begin#install_kubectl.

Azure 容器服务

另一个云托管的选择是微软的Azure 容器服务ACS)。ACS 非常好,因为它允许您选择诸如 Docker Swarm、Kubernetes 和 Mesos 等行业标准工具。然后,它为您创建一个托管的集群,但使用其中一个工具集作为基础。优点是您仍然可以使用工具的本机 API 和管理工具,但将云基础设施的管理留给 Azure。

了解有关 ACS 的更多信息

azure.microsoft.com/en-us/services/container-service/.

ClusterHQ

ClusterHQ 提供了一种将有状态数据引入您的容器化应用的解决方案。他们提供 Flocker,这是一个用于管理容器持久性存储卷的工具,以及 FlockerHub,为您的数据卷提供存储库。

请参考 ClusterHQ 网站以获取更多信息。

clusterhq.com/.

Portworx

Portworx 是存储领域的另一家参与者。它提供了将持久性存储引入您的容器的解决方案。此外,它还具有用于快照、加密甚至多云复制的功能。

请参考 portworx 网站以获取更多信息。

portworx.com/.

Shippable

Shippable 是一个持续集成、持续部署和发布自动化平台,内置对各种现代容器环境的支持。该产品标榜支持任何语言,并统一支持打包和测试。

请参考 Shippable 网站以获取更多信息。

app.shippable.com/.

Twistlock

Twistlock.io 是一个专为容器量身定制的漏洞和加固工具。它提供了执行策略的能力,根据 CIS 标准进行加固,并扫描任何流行注册表中的图像以查找漏洞。它还提供了与流行 CI/CD 工具的扫描集成,以及为诸如 Kubernetes 等编排工具的 RBAC 解决方案。

请参考 Twistlock 网站获取更多信息。

www.twistlock.io/

AquaSec

AquaSec 是另一个提供各种功能的安全工具。它与流行注册表的图像扫描、策略执行、用户访问控制和容器加固等功能都有涉及。此外,AquaSec 在网络分割方面还具有一些有趣的功能。

请参考 Aqua 的网站获取更多信息。

www.aquasec.com/

Mesosphere(Kubernetes on Mesos)

Mesosphere 本身正在围绕开源的 Apache Mesos 项目构建一个商业支持的产品(DCOS)。Apache Mesos 是一个类似于 Kubernetes 的集群管理系统,提供调度和资源共享,但在更高的层面上。这个开源项目被一些知名公司使用,如 TwitterAirBnB

在这些网站上获取有关 Mesos OS 项目和 Mesosphere 提供的更多信息:

由于 Mesos 的模块化特性,它允许在各种平台上使用不同的框架。现在已经有了一个 Kubernetes 框架,所以我们可以利用 Mesos 中的集群管理,同时保持 K8s 中有用的应用级抽象。请参考以下链接:

github.com/kubernetes-incubator/kube-mesos-framework

Deis

Deis 项目提供了基于 Kubernetes 的开源平台即服务PaaS)解决方案。这使得公司可以在本地部署或在公共云上部署他们自己的 PaaS。Deis 提供了应用程序组合和部署工具,包管理(在 pod 级别),以及服务经纪等功能。

您可以参考以下网站了解更多关于 Deis 的信息:

deis.com/

OpenShift

另一个 PaaS 解决方案是来自红帽的 OpenShift。OpenShift 平台使用红帽 Atomic 平台作为运行容器的安全且轻量的操作系统。在第 3 版中,Kubernetes 被添加为在 PaaS 上进行所有容器操作的编排层。这是一个管理大规模 PaaS 安装的绝佳组合。

关于 OpenShift 的更多信息,请访问 enterprise.openshift.com/.

想了解更多?

Kubernetes 项目是一个开源项目,因此有广泛的贡献者和爱好者社区。要找到更多帮助的资源之一是 Kubernetes Slack 频道:slack.kubernetes.io/

Google 群组中还有一个 Kubernetes 群组。你可以加入它

groups.google.com/forum/#!forum/kubernetes-users

如果你喜欢这本书,你可以在我的博客和 Twitter 页面上找到更多我的文章、操作指南和各种思考:

摘要

在这最后一章中,我们留下了一些线索,引导你继续你的 Kubernetes 之旅。你应该有一套坚实的生产特性来帮助你入门。在 Docker 和 Kubernetes 的世界中有一个广泛的社区。如果需要在旅途中有一个友好的面孔,我们还提供了一些额外的资源。

到目前为止,我们已经看到了 Kubernetes 的容器操作的全景。你应该对 Kubernetes 如何简化容器部署的管理以及如何计划将容器从开发人员的笔记本迁移到生产服务器上有更多信心。现在出发,开始部署你的容器吧!

posted @ 2024-05-20 12:00  绝不原创的飞龙  阅读(20)  评论(0编辑  收藏  举报