原文:
zh.annas-archive.org/md5/9CADC322D770A4D3AD0027E7CB5CC592
译者:飞龙
第五章:使用 Helm 管理复杂的应用程序
在之前的章节中,您开始学习如何构建和部署所需的配置,以在您的 Kubernetes 集群上运行不同的应用程序。
一旦您超越了部署最简单的应用程序,您会发现您的应用程序通常有一个或多个组件是协同工作的。例如,您可能有一个 Web 应用程序,它从数据库中显示信息,该数据库还使用定期作业定期更新该信息。为了使该应用程序能够正确运行,这两个组件都需要被部署并正确运行。此外,这两个组件可能共享一些配置,例如后端数据库的凭据。
在将应用程序部署到我们的 Kubernetes 集群时,我们可能会遇到另一个问题,即可重用性的问题。也许我们需要在多个上下文或环境中运行相同的工具或应用程序。例如,许多组织都有一个用于测试软件新版本的暂存环境。
在维护多个环境时,我们理想情况下希望每个环境中的配置尽可能匹配,但当然,配置中可能需要一些差异。维护每个环境的 Kubernetes 清单的多个副本可能会出错,并且不能保证在一个环境中运行的应用程序在另一个环境中也能正常工作。
Helm 是 Kubernetes 生态系统中的一款流行工具,它解决了这些问题。它为我们提供了一种构建相关 Kubernetes 对象的软件包(称为图表)的方式,可以以一种协调的方式部署到集群中。它还允许我们对这些软件包进行参数化,以便在不同的上下文中重复使用,并部署到可能需要这些服务的不同环境中。
与 Kubernetes 一样,Helm 的开发由 Cloud Native Computing Foundation 监督。除了 Helm(软件包管理器)之外,社区还维护了一个标准图表的存储库,用于安装和运行各种开源软件,例如 Jenkins CI 服务器、MySQL 或 Prometheus,使用 Helm 可以简单地安装和运行涉及许多基础 Kubernetes 资源的复杂部署。
在本章中,您将学习:
-
如何安装
helm
命令行工具 -
如何安装 Helm 的集群组件 Tiller
-
如何使用社区维护的图表将服务部署到您的集群
-
创建图表时需要了解的语法
-
如何在自己的图表存储库中托管图表,以便在组织内或更广泛地共享您的图表
-
将 Helm 图表集成到您自己的部署流程的策略
安装 Helm
如果您已经设置了自己的 Kubernetes 集群,并且在您的机器上正确配置了 kubectl
,那么安装 Helm 就很简单。
macOS
在 macOS 上,安装 Helm 客户端的最简单方法是使用 Homebrew:
$ brew install kubernetes-helm
Linux 和 Windows
Helm 的每个版本都包括 Linux、Windows 和 macOS 的预构建二进制文件。访问github.com/kubernetes/helm/releases
下载您所需的平台版本。
要安装客户端,只需解压并将二进制文件复制到您的路径上。
例如,在 Linux 机器上,您可能会执行以下操作:
$ tar -zxvf helm-v2.7.2-linux-amd64.tar.gz
$ mv linux-amd64/helm /usr/local/bin/helm
安装 Tiller
一旦您在您的机器上安装了 Helm CLI 工具,您可以开始安装 Helm 的服务器端组件 Tiller。
Helm 使用与 kubectl
相同的配置,因此首先检查您将要安装 Tiller 的上下文:
$ kubectl config current-context
minikube
在这里,我们将把 Tiller 安装到 Minikube 上下文引用的集群中。在这种情况下,这正是我们想要的。如果您的 kubectl
当前没有指向另一个集群,您可以快速切换到您想要使用的上下文,就像这样:
$ kubectl config use-context minikube
如果您仍然不确定是否使用了正确的上下文,请快速查看完整配置,并检查集群服务器字段是否正确:
$ kubectl config view --minify=true
minify
标志会删除当前上下文中未引用的任何配置。一旦您确认 kubectl
连接的集群是正确的,我们可以设置 Helm 的本地环境并将 Tiller 安装到您的集群上:
$ helm init
$HELM_HOME has been configured at /Users/edwardrobinson/.helm.
Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster.
Happy Helming!
我们可以使用 kubectl
来检查 Tiller 是否确实在我们的集群上运行:
$ kubectl -n kube-system get deploy -l app=helm
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
tiller-deploy 1 1 1 1 3m
一旦我们验证了 Tiller 在集群上正确运行,让我们使用 version
命令。这将验证我们能够正确连接到 Tiller 服务器的 API,并返回 CLI 和 Tiller 服务器的版本号:
$ helm version
Client: &version.Version{SemVer:"v2.7.2", GitCommit:"8478fb4fc723885b155c924d1c8c410b7a9444e6", GitTreeState:"clean"}
Server: &version.Version{SemVer:"v2.7.2", GitCommit:"8478fb4fc723885b155c924d1c8c410b7a9444e6", GitTreeState:"clean"}
安装图表
让我们首先通过使用社区提供的图表之一来安装一个应用程序。
您可以在hub.kubeapps.com/
发现社区为 Helm 图表制作的应用程序。除了简化将各种应用程序部署到 Kubernetes 集群中,这也是一个学习社区在为 Helm 打包应用程序时使用的一些最佳实践的好资源。
Helm 图表可以存储在存储库中,因此可以通过名称简单安装它们。默认情况下,Helm 已配置为使用一个名为Stable的远程存储库。
这使我们可以在安装 Helm 后立即尝试一些常用的应用程序。
在安装图表之前,您需要了解三件事:
-
要安装的图表的名称
-
要为此发布指定的名称(如果省略此项,Helm 将为此发布创建一个随机名称)
-
要安装图表的集群上的命名空间(如果省略此项,Helm 将使用默认命名空间)
Helm 将特定图表的每个不同安装称为一个发布。每个发布都有一个唯一的名称,如果以后要更新、升级或删除集群中的发布,将使用该名称。能够在单个集群上安装多个图表实例使 Helm 与我们对传统软件包管理器的想法有些不同,传统软件包管理器通常与单台机器绑定,并且通常一次只允许安装一个特定软件包。但一旦您习惯了这些术语,就会非常容易理解:
-
图表是包含有关如何将特定应用程序或工具安装到集群的所有信息的软件包。您可以将其视为一个模板,可重复使用以创建打包的应用程序或工具的许多不同实例或发布。
-
发布是将图表命名安装到特定集群的过程。通过按名称引用发布,Helm 可以对特定发布进行升级,更新已安装工具的版本或进行配置更改。
-
存储库是存储图表以及索引文件的 HTTP 服务器。当配置了存储库的位置后,Helm 客户端可以通过从该存储库下载图表然后创建一个新的发布来安装该图表。
在将图表安装到集群之前,您需要确保 Helm 知道您要使用的存储库。您可以通过运行helm repo list
命令列出当前使用的存储库:
$ helm repo list
NAME URL
stable https://kubernetes-charts.storage.googleapis.com
local http://127.0.0.1:8879/charts
默认情况下,Helm 配置了一个名为 stable 的存储库,指向社区图表存储库,以及一个指向本地地址的本地存储库,用于测试您自己的本地存储库(您需要运行helm serve
)。
使用helm repo add
命令将 Helm 存储库添加到此列表非常简单。您可以通过运行以下命令添加包含与本书相关的一些示例应用程序的 Helm 存储库:
$ helm repo add errm https://charts.errm.co.uk
"errm" has been added to your repositories
为了从配置的存储库中获取最新的图表信息,您可以运行以下命令:
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Skip local chart repository
...Successfully got an update from the "errm" chart repository
...Successfully got an update from the "stable" chart repository
Update Complete. Happy Helming!
让我们从我的 Helm 存储库中提供的最简单的应用程序之一kubeslate
开始。这提供了有关您的集群的一些非常基本的信息,例如您正在运行的 Kubernetes 版本以及您的集群中的 Pod、部署和服务的数量。我们将从这个应用程序开始,因为它非常简单,不需要任何特殊的配置来在 Minikube 上运行,或者在任何其他集群上运行。
从集群上的存储库安装图表非常简单:
$ helm install --name=my-slate errm/kubeslate
您应该会看到来自helm
命令的大量输出。
首先,您将看到有关发布的一些元数据,例如其名称、状态和命名空间:
NAME: my-slate
LAST DEPLOYED: Mon Mar 26 21:55:39 2018
NAMESPACE: default
STATUS: DEPLOYED
接下来,您应该会看到有关 Helm 已指示 Kubernetes 在集群上创建的资源的一些信息。正如您所看到的,已创建了一个服务和一个部署:
RESOURCES:
==> v1/Service
NAME TYPE CLUSTER-IP PORT(S) AGE
my-slate-kubeslate ClusterIP 10.100.209.48 80/TCP 0s
==> v1/Deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
my-slate-kubeslate 2 0 0 0 0s
==> v1/Pod(related)
NAME READY STATUS AGE
my-slate-kubeslate-77bd7479cf-gckf8 0/1 ContainerCreating 0s
my-slate-kubeslate-77bd7479cf-vvlnz 0/1 ContainerCreating 0s
最后,有一个部分包含图表作者提供的一些注释,以便为我们提供有关如何开始使用应用程序的一些信息:
注意:
访问kubeslate
。
- 首先启动 kubectl 代理:
**kubectl proxy**
- 现在在浏览器中打开以下 URL:
**http://localhost:8001/api/v1/namespaces/default/services/my-slate-kubeslate:http/proxy**
如果您看到ServiceUnavailable / no endpoints available for service
,请尝试重新加载页面,因为 Pod 的创建可能需要一些时间。
尝试按照这些说明自己打开 Kubeslate 在浏览器中。
使用 Helm 部署的 Kubeslate
配置图表
当您使用 Helm 发布图表时,可能需要更改某些属性或提供配置。幸运的是,Helm 为图表的用户提供了一种标准的方式来覆盖一些或所有的配置值。
在本节中,我们将看看作为图表用户,您可能如何向 Helm 提供配置。在本章的后面,我们将看看如何创建自己的图表,并使用传递的配置来允许您的图表进行自定义。
当我们调用helm install
时,有两种方式可以提供配置值:将它们作为命令行参数传递,或者提供一个配置文件。
这些配置值与图表提供的默认值合并。这使得图表作者可以提供默认配置,让用户快速上手,但仍然允许用户调整重要设置或启用高级功能。
使用 set 标志在命令行上向 Helm 提供单个值。 kubeslate
图表允许我们使用podLabels
变量为其启动的 pod(s)指定附加标签。让我们发布 kubeslate 图表的新版本,然后使用podLabels
变量添加一个额外的hello
标签,其值为world
:
$ helm install --name labeled-slate --set podLabels.hello=world errm/kubeslate
运行此命令后,您应该能够证明您传递给 Helm 的额外变量确实导致 Helm 启动的 pod 具有正确的标签。使用带有我们使用 Helm 应用的标签的标签选择器的kubectl get pods
命令应返回刚刚使用 Helm 启动的 pod:
$ kubectl get pods -l hello=world
NAME READY STATUS
labeled-slate-kubeslate-5b75b58cb-7jpfk 1/1 Running
labeled-slate-kubeslate-5b75b58cb-hcpgj 1/1 Running
除了在创建新版本时能够向 Helm 传递配置外,还可以使用升级命令更新预先存在的版本的配置。当我们使用 Helm 更新配置时,这个过程与上一章中更新部署资源时的过程大致相同,如果我们想要避免服务中断,许多考虑因素仍然适用。例如,通过启动服务的多个副本,我们可以避免停机时间,因为部署配置的新版本会被推出。
让我们还将我们原始的 kubeslate 发布升级,以包括我们应用于第二个发布的相同的hello: world pod
标签。正如您所看到的,upgrade
命令的结构与install
命令非常相似。但是,我们不是使用--name
标志指定发布的名称,而是将其作为第一个参数传递。这是因为当我们将图表安装到集群时,发布的名称是可选的。如果我们省略它,Helm 将为发布创建一个随机名称。但是,当执行升级时,我们需要针对要升级的现有发布,因此此参数是必需的:
$ helm upgrade my-slate --set podLabels.hello=world errm/kubeslate
如果您现在运行helm ls
,您应该会看到名为my-slate
的发布已经升级到 Revision 2。您可以通过重复我们的kubectl get
命令来测试由此发布管理的部署是否已升级以包括此 pod 标签:
$ kubectl get pods -l hello=world
NAME READY STATUS
labeled-slate-kubeslate-5b75b58cb-7jpfk 1/1 Running
labeled-slate-kubeslate-5b75b58cb-hcpgj 1/1 Running
my-slate-kubeslate-5c8c4bc77-4g4l4 1/1 Running
my-slate-kubeslate-5c8c4bc77-7pdtf 1/1 Running
我们现在可以看到,我们的四个发布中的每个都有两个 pod,现在都与我们传递给kubectl get
的标签选择器匹配。
使用set
标志在命令行上传递变量在我们只想为一些变量提供值时很方便。但是,当我们想要传递更复杂的配置时,将值提供为文件可能更简单。让我们准备一个配置文件,将几个标签应用到我们的 kubeslate pod 上:
values.yml
podLabels:
hello: world
access: internal
users: admin
然后,我们可以使用helm
命令将此配置文件应用到我们的发布上:
$ helm upgrade labeled-slate -f values.yml errm/kubeslate
创建您自己的图表
现在您已经有了一点 Helm 的经验,并且可以使用命令行工具从社区存储库安装图表,我们将看看如何利用 Helm 为自己的应用程序构建图表。
我们将使用 Helm 来部署我们在第四章中手动部署的 versions 应用程序。这里的目标是复制我们在第四章中进行的部署,但这次将配置封装在 Helm 图表中,以便简单地进行配置更改,部署我们代码的新版本,甚至多次部署相同的配置。
Helm 使构建图表并将其部署到集群变得非常容易。Helm 命令行工具有一些命令,可以让我们非常快速地开始。helm create
命令将为我们的新图表创建一个骨架,我们可以快速填写应用程序的配置。
$ helm create version-app
Creating version-app
$ tree version-app
version-app
├── Chart.yaml
├── values.yaml
└── templates
├── NOTES.txt
├── _helpers.tpl
├── deployment.yaml
└── service.yaml
2 directories, 7 files
让我们看看 Helm 创建的每个文件,然后看看我们需要添加的配置,以部署我们的版本化 Web 服务来自第四章,管理应用程序中的变更。
Chart.yaml
该文件包含有关此图表的一些基本元数据,例如名称、描述和版本号。此文件是必需的。
values.yaml
该文件包含此图表的默认配置值。这些值将在安装图表时用于渲染模板资源,除非提供了覆盖值。
模板
该目录包含将被渲染以生成此图表提供的资源定义的模板。当我们运行helm new
命令时,会为我们创建一些骨架模板文件。
NOTES.txt
是一个特殊文件,用于向您的图表用户提供安装后的消息。在本章早些时候,当我们安装 kube-ops-dashboard 时,您看到了一个示例。
与我们在之前章节手工创建的 YAML 资源一样,Helm 不会对我们为资源指定的文件名附加任何重要性。如何组织模板目录中的资源取决于您。我们刚刚创建的骨架图表带有一些文件,但如果您需要创建更多资源,可以向模板目录添加其他文件。
deployment.yaml
包含一个部署的简单清单,service.yaml
包含此部署的简单服务清单,_helpers.tpl
包含一些预定义的辅助函数,您可以在整个图表中重复使用。
当您运行helm new
时,可能会创建一些其他文件。这些是用于一些更高级功能的可选文件,我们现在可以忽略它们,但如果您愿意,可以从图表中完全删除它们。
有一些标准的模板目录工作方式,遵循社区图表存储库中的规则。您可能希望查看这些规则,因为它们有助于保持您的工作有条不紊。但除非您计划尝试将您的图表发布到社区存储库,否则没有必要严格遵循这些准则:docs.helm.sh/chart_best_practices
。
使其成为您自己的
让我们按照以下步骤来编辑这个图表,以便部署我们自己的应用程序。首先看一下生成的 deployment.yaml
文件。您会注意到它看起来非常类似于我们在第四章中生成的清单,管理应用程序中的变更,但有一个重要的区别:所有特定的配置值都已经替换为变量调用。例如,看一下指定容器镜像的那一行:
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
您会注意到,当将变量的引用插入模板时,它被两个花括号括起来,就像这样:{{ variable }}
。其次,您还会注意到用于访问对象的嵌套属性的点表示法。.Values
对象指的是所有的值,可以是从图表中的 values.yaml
文件(默认)提供的,也可以是在部署图表时从命令行覆盖的。
因此,为了配置我们部署中要使用的图像的源,让我们从编辑 values.yaml
文件开始。找到配置图像的部分,并编辑以拉取我们在第四章中部署的应用程序的版本:
image:
repository: errm/versions
tag: 0.0.1
pullPolicy: IfNotPresent
在编辑 values.yaml
文件的同时,让我们也编辑用于配置 Helm 为我们的部署创建的服务的值。我们需要将容器暴露的端口从 80
更改为 3000
,并且我们应该将服务的名称从 nginx
更改为更具描述性的名称:
service:
name: versions
type: ClusterIp
externalPort: 80
internalPort: 3000
如果我们回过头来看 deployment.yaml
和 service.yaml
,我们可以看到能够在我们的 Kubernetes 资源中使用模板注入变量的一个优势。
通过在 values.yaml
文件中更改 service.internalPort
的值,我们有了一个单一的真相来源;在这种情况下,就是我们的容器暴露的端口。这个单一的真相来源在 deployment.yaml
中被使用了三次,然后又在 service.yaml
中被使用了一次。当然,对于这样一个简单的例子,我们可以手动编辑这些文件,但这会增加维护配置的成本,需要搜索多个资源,并理解不同配置值之间的交互方式。
当我构建 Helm 图表时,我试图想象我的未来自己使用该图表。我的目标是暴露足够的变量,使图表足够灵活,可以在多个环境中重复使用和重新部署,而无需更改或甚至查看模板。为了实现这一点,选择描述性变量名称并为这些变量的使用提供清晰的文档非常重要
README.md
文件。
使用 Helm 命令行客户端部署我们的图表非常简单,而不是引用远程存储库中图表的名称(例如,stable/kube-ops-view
)。我们可以通过指向磁盘上的图表目录来运行我们的 Helm 命令:
$ helm install --name happy-bear version-app/
NAME: happy-bear
LAST DEPLOYED: Sun Dec 3 13:22:13 2017
NAMESPACE: default
STATUS: DEPLOYED
RESOURCES:
==> v1/Service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
happy-bear-version-app ClusterIP 10.0.0.121 <none> 80/TCP
==> v1/Deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE
happy-bear-version-app 1 1 1 0
==> v1/Pod(related)
NAME READY STATUS
happy-bear-version-app-6597799867-ct5lk 0/1 ContainerCreating
现在图表已安装到我们的集群上,让我们测试它是否正常工作。最简单的方法是运行kubectl proxy
来设置到 kubernetes API 的本地代理,并使用服务端点来查看我们的服务。Helm 为我们创建的图表创建了一个服务,其名称由发布的名称和图表的名称组合而成。因此,假设kubectl proxy
在端口8001
上启动,我们应该能够在以下 URL 查看我们的服务:http://localhost:8001/api/v1/namespaces/default/services/happy-bear-version-app:80/
。
开发和调试
随着我们的图表变得更加复杂,并且利用 Helm 提供的模板语言的更多功能来构建我们自己的抽象层,您可能会注意到,要推理 Kubernetes 返回的错误变得更加困难。因为我们无法直接看到我们提交给 Kubernetes 的资源,因此很难找出错误或错误配置的来源。
幸运的是,Helm 有一些选项可以帮助我们在开发图表时调试它们:
-
--dry-run
:此选项允许我们将图表提交到 Tiller 服务器,在那里它将以与我们部署图表时完全相同的方式进行验证,而无需实际提交资源到 Kubernetes。这样我们可以快速查看和理解图表中的任何错误,而无需在我们的集群上使用资源。 -
--debug
:此选项允许我们查看大量有用的调试信息;实际上,有时可能会有点压倒性。首先,我们看到一些标记为[debug]
的日志信息。这包括有关 Helm 客户端如何连接到 Tiller 以及正在部署的图表的一些详细信息。
接下来是发布元数据。这由Chart.yaml
中的图表元数据和有关发布的计算信息组成,例如其编号以及制作日期和时间。
下一节,“计算值”,显示了 Helm 在渲染模板以生成此版本的资源时将使用的确切值。如果在发布时没有传递任何额外的变量,这应该与values.yaml
的内容相同,但如果您在调用 Helm 时提供了覆盖,这将非常有用,以便了解模板使用的确切变量。 HOOKS
部分显示了将由 Helm 钩子机制创建的资源。您将在本章后面了解有关钩子的一些信息。
最后,MANIFEST
部分列出了计算资源,因为它们将被提交到 Kubernetes。当您开发图表模板时,这是非常宝贵的,可以快速查看您的图表在不同值下的行为。您会发现,将这两个选项与helm install
或helm upgrade
一起使用非常有用,用于调试您的图表,以及验证您的工作并建立对图表或值的更改是否产生预期效果的信心。
模板语言
Helm 的模板语言基于 Go 模板语言。基本上,Helm 提供了来自 Go 编程语言的标准模板语言,以及一些额外的函数和使变量在模板内可用的机制。
您已经看到如何使用模板语言将信息放入 YAML 格式的 Kubernetes 资源中。Helm 提供的函数调用用双花括号括起来,如{{ this }}
。
如果我们只是想将变量包含到我们的模板中,我们可以直接按名称引用它。Helm 将其变量命名空间化在一些对象内,这些对象暴露给模板。您可能已经注意到,我们的values.yaml
文件中的值(由命令行传入的任何覆盖变量修改)在.Values
对象中可用。除了这个对象,Helm 还在模板内提供了其他对象:
-
.Release
: 此对象描述了发布本身,并包括许多属性,可用于自定义资源以适应其父发布。通常,您将使用这些值来确保此发布的资源不会与同一图表的另一个发布的资源发生冲突。 -
.Release.Name
: 这是发布的名称。它可以通过helm install
传递给--name
标志,或者可能会自动生成。 -
.Release.Time.Seconds
: 这是发布创建时的时间,作为 UNIX 风格的时间戳。如果您需要向资源名称添加唯一值,这可能很有用。 -
.Release.Namespace
: 这表示此发布的 Kubernetes 命名空间。 -
.Release.Service
: 这表示进行发布的服务。目前,这始终是 Tiller,但如果 Helm 有另一种实现,可能会以不同的方式填充此属性。 -
.Release.Revision
: 这是一个用于跟踪发布更新的数字。它从 1 开始,每次通过helm upgrade
升级发布时都会增加。 -
.Release.IsUpgrade
和.Release.IsInstall
: 这些是布尔值,指示生成此发布的操作是图表的新安装,还是对现有发布的升级。这些可能被用于仅在图表生命周期的特定阶段执行操作。 -
.Chart
: 图表对象包含来自Chart.yaml
的字段。 -
.Files
: 此对象允许您访问图表中包含的非模板文件的内容。它公开了两个函数,.Get
和.GetBytes
,允许您以文本或字节的形式读取文件的内容。这对于提供静态配置文件或其他未包含在容器映像中的数据作为图表的一部分可能很有用。 -
.Capabilities
: 此对象提供有关 Tiller 正在运行的集群的信息。如果要创建一个可以与多个版本的 Kubernetes 一起使用的图表,查询此信息可能很有用。您将在本章后面看到一个示例。 -
.Template
: 此对象提供了一个.Name
和一个.BasePath
属性,其中包括 Helm 当前正在呈现的模板的文件名和目录。
函数
Helm 的模板语言提供了超过 60 个函数,可以操作和格式化我们传递给模板的数据。
其中一些函数是 Go 模板语言的一部分,但大多数是 Sprig 模板语言的一部分。
当您开始使用 Helm 时,随手准备文档可能会很有用,这样您就可以找到所需的函数。
在 Helm 模板语言中调用模板函数有两种方式。其中之一涉及调用一个函数,并将一个值作为参数传递。
例如,{{ upper "hello" }}
将产生输出HELLO
。
调用函数的第二种方式是作为管道。您可以将管道想象成类似于 UNIX 管道;它提供了一种简洁的方式将一个函数的结果传递给另一个函数。这使我们能够组合多个函数以获得我们想要的结果。
我们可以将我们的第一个示例重写为{{ "hello" | upper }}
,结果将完全相同。这种形式的优势在于当我们想要对一个值应用多个函数时。当我们使用管道运算符时,上一个函数的结果被传递到下一个函数作为最后一个参数。这使我们还可以调用需要多个参数的函数,并且这也是 Helm 中大多数函数被优化为将要操作的值作为最后一个参数的原因。
例如,我们可以使用trunc
函数形成一个流水线,将我们的字符串截断为一定数量的字符,然后使用upper
函数将结果转换为大写,就像这样:{{ "hello" | trunc 4 | upper }}
。当然,结果将是HELL
。
流程控制
通过能够从图表中获取单个值并将其包含在图表的许多地方,我们已经从 Helm 中获得了很多价值,就像本章前面的示例中,我们在几个相关的地方引用了相同的端口号。例如,您还可以使用此技术来确保由不同容器提供的多个不同组件的系统始终部署到相同的版本号。
我们在 Helm 图表中使用变量的另一个重要方式是为我们的模板提供信号,以更改我们的配置,甚至将整个功能转换为可选的附加功能,可能并非始终启用。
有三种结构允许我们使用 Helm 模板构建非常强大的抽象:if...else
,range
和with
。
Helm 中if...else
结构的结构对于使用过编程语言的人来说应该非常熟悉。我们使用if
关键字来测试变量或表达式。如果测试通过,我们执行第一个分支中的操作;如果不通过,则退回到else
分支指示的操作。
以下是一个示例,您可以根据变量的值在NOTES.txt
模板中提供自定义消息:
{{ if .Values.production }}
WARNING THIS IS A PRODUCTION ENVIRONMENT - do not use for testing.
{{ else }}
THIS IS A TEST ENVIRONMENT; any data will be purged at midnight.
{{ end }}
if
函数可以嵌套在else
分支中,以提供更复杂的行为。在这个例子中,查询Capabilities
对象,以便模板化资源可以为CronJob
资源使用正确的 API 版本。这种能力很有用,因为它允许您更改配置以支持 Kubernetes 的更新版本,但保持向后兼容性。如果我们对支持的版本进行的两个测试都失败了,那么我们明确地抛出一个错误,这将停止图表的安装:
{{- if and ge .Capabilities.KubeVersion.Minor "8" -}}
apiVersion: batch/v1beta1
{{- else if ge .Capabilities.KubeVersion.Minor "5" -}}
apiVersion: batch/v1alpha1
{{- else -}}
{{required "Kubernetes version 1.5 or higher required" nil }}
{{- end -}}
像这样围绕基于特性标志或甚至版本号的配置提供切换是管理配置中变化的非常有用的工具。它允许您向图表添加一个选项,在安全性中测试它,然后只有在您满意时才启用它。
range
关键字用于循环遍历集合。它可以循环遍历简单列表或具有键值结构的集合。
让我们首先在我们的values.yaml
文件中添加一个值列表:
users:
- yogi
- paddington
- teddy
然后我们可以使用range
关键字来循环遍历我们列表中的数据,并在我们的模板中使用值:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
usernames: |-
{{- range .Values.users }}
{{ . }}
{{- end }}
在这个例子中,我们使用了|-
标记,这是 YAML 的一部分。它表示用户名字符串是多行的。这将导致每个用户名在ConfigMap
中以新行分隔。
正如您在这里看到的,当我们在列表上使用 range 函数时,在每次迭代中,特殊的.
变量都会被列表中的值替换。
渲染时,此模板产生以下结果:
apiVersion: v1
kind: ConfigMap
metadata:
name: ordered-dolphin-configmap
data:
usernames: |-
yogi
paddington
teddy
在下一个示例中,我们将把 range 函数的结果分配给两个变量。当我们对列表这样做时,第一个变量包括一个索引,您会注意到当我们分配一个变量时,我们会用$
作为前缀:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
user_id.properties: |-
{{- range $index, $user := .Values.users }}
user.{{ $user }}={{ $index }}
{{- end }}
当渲染此模板时,输出如下:
apiVersion: v1
kind: ConfigMap
metadata:
name: interested-ibex-configmap
data:
user_id.properties: |-
user.yogi.id=0
user.paddington.id=1
user.teddy.id=2
在使用 range 函数循环遍历键值结构时,我们还可以使用变量来捕获键和值。
让我们在我们的values.yaml
文件中考虑以下数据:
users:
yogi:
food: picnic
height: 1500
paddington:
food: marmalade
height: 1066
teddy:
food: honey
height: 500
现在我们在用户变量中有一些键值数据,让我们用它来配置一些 pod 的环境变量:
apiVersion: v1
kind: Pod
metadata:
name: {{ .Release.Name }}-env-pod
spec:
containers:
- image: alpine
name: bear-env
env:
{{- range $name, $user := .Values.users }}
{{- range $var, $value := $user }}
- name: {{ $name | upper }}_BEAR_{{ $var | upper }}
value: {{ $value | quote }}
{{- end }}
{{- end }}
command: ["env"]
当我们使用 range 关键字循环遍历键值结构时,键成为第一个返回的变量,值成为第二个。通过嵌套循环,就像在这种情况下一样,可以在值文件中使用相当复杂的数据结构。
Kubernetes 资源中某些变量的类型很重要。在前面的例子中,环境变量中的值必须始终是一个字符串,因此我们使用了quote
管道函数来确保其他类型的值(如数字)是正确的字符串类型。
渲染时,此模板将生成一个 pod 清单,如下所示:
apiVersion: v1
kind: Pod
metadata:
name: solemn-whale-env-pod
spec:
containers:
- image: alpine
name: bear-env
env:
- name: PADDINGTON_BEAR_FOOD
value: "marmalade"
- name: PADDINGTON_BEAR_HEIGHT
value: "1066"
- name: TEDDY_BEAR_FOOD
value: "honey"
- name: TEDDY_BEAR_HEIGHT
value: "500"
- name: YOGI_BEAR_FOOD
value: "picnic"
- name: YOGI_BEAR_HEIGHT
value: "1500"
command: ["env"]
钩子
到目前为止,我们一直在使用 Helm 来帮助我们生成我们的应用程序需要提交到 Kubernetes 的资源。在理想的世界中,这将是 Helm 这样的工具所需要做的一切。Kubernetes 旨在是声明性的;换句话说,我们提交描述集群状态的资源,Kubernetes 会处理其余的工作。
不幸的是,在现实世界中,有时我们仍然需要明确地采取一些行动来使我们的应用程序正确运行。也许当您安装应用程序时,您需要运行脚本来初始化数据库架构或设置一些默认用户。也许当您安装应用程序的新版本时,您需要运行脚本来迁移数据库架构,以使其与应用程序的新版本兼容。
Helm 提供了一个钩子机制,允许我们在发布的生命周期的八个特定点上采取行动。为了在 Helm 图表中定义一个钩子,您需要向资源添加helm.sh/hook
注释。您可以在任何资源上使用钩子注释,以确保它在适当的时间创建。但通常,创建作业类型的资源非常有用。如果您的资源是作业类型,Tiller 将阻塞,直到作业成功运行完成。这意味着如果您使用pre-
钩子之一,那么您的应用程序可以依赖于该作业已经运行。
-
pre-install
:此操作在 Tiller 渲染图表模板后但在任何资源提交到 Kubernetes API 之前运行。此操作在通过安装图表创建新发布时运行。如果您还需要在发布升级时运行钩子,您应该将此钩子与pre-upgrade
钩子结合使用。您可以利用此钩子来初始化将被应用程序使用的资源。 -
post-install
:此操作在所有资源已提交到 Kubernetes API 后运行。例如,您可以使用此操作运行一个脚本,向聊天室发送通知,或者向监控工具注册图表的新实例。 -
pre-delete
:此钩子在删除请求发出时,在从 Kubernetes 删除任何资源之前运行。例如,如果您需要备份应用程序存储的数据,这可能会很有用。 -
post-delete
:此钩子在 Helm 删除作为发布的一部分创建的资源后运行。您可以利用此钩子清理应用程序使用的任何未由 Helm 或 Kubernetes 管理的外部资源。 -
pre-upgrade
:此钩子提供与pre-install
钩子相同的功能,但每次升级发布时运行。您可以使用此钩子运行数据库迁移脚本。 -
post-upgrade
:此钩子提供与post-install
钩子相同的功能,但每次升级发布时运行。同样,这可能用于通知目的。 -
pre-rollback
:此钩子在提交回滚发布升级的更改到 Kubernetes API 之前运行。 -
post-rollback
:此钩子在提交回滚发布升级的请求到 Kubernetes 后运行。根据您的应用程序的期望,您可能在此处或在pre-rollback
钩子中运行脚本来回滚数据库更改。 -
让我们看一个例子,我们将使用一个钩子来运行设置脚本:
apiVersion: batch/v1
kind: Job
metadata:
name: "{{.Release.Name}}-setup"
labels:
heritage: {{.Release.Service | quote }}
release: {{.Release.Name | quote }}
chart: "{{.Chart.Name}}-{{.Chart.Version}}"
annotations:
"helm.sh/hook": pre-install
spec:
template:
metadata:
name: "{{.Release.Name}}-setup"
labels:
heritage: {{.Release.Service | quote }}
release: {{.Release.Name | quote }}
chart: "{{.Chart.Name}}-{{.Chart.Version}}"
spec:
restartPolicy: Never
containers:
- name: setup
image: errm/awesome-application
command: ["bin/setup"]
关于此作业定义的一切都与我们在第四章中看到的标准 Kubernetes 资源定义相同,管理应用程序中的变更。是作业元数据中添加的注释使 Helm 能够将此定义视为钩子,而不是我们应用程序的受管部分。
单个资源可以用于实现多个钩子。例如,如果您希望设置脚本在每次更新发布时以及首次安装时运行,我们可以更改钩子注释为:
annotations:
"helm.sh/hook": pre-install,pre-upgrade
Helm 允许您使用钩子机制创建任何 Kubernetes 资源。例如,如果使用钩子创建的作业依赖于ConfigMap
或Secret
,这可能很有用。
如果您有多个需要按特定顺序创建的钩子资源,可以使用helm.sh/hook-weight
注释。此权重可以是任何正整数或负整数。当 Helm 评估特定的钩子时,资源将按照这些权重按升序排序。由于注释只能保存字符串,因此重要的是引用钩子权重中使用的数字。
例如,具有注释"helm.sh/hook-weight": "-5"
的资源将在"helm.sh/hook-weight": "5"
之前运行,但会在具有注释"helm.sh/hook-weight": "-10"
的资源之后运行。
Helm 的钩子系统中有一个小问题,一开始可能会令人困惑,但幸运的是,一旦您理解了它,就有一些简单的方法来解决它。
Helm 跟踪您使用模板创建的几乎所有资源。这意味着当您升级发布时,Helm 可以更新发布管理的所有资源,当删除发布时,Helm 可以删除它创建的所有资源。唯一的例外是钩子创建的资源。一旦它们被创建,Helm 就不再管理它们,而是由 Kubernetes 接管。
在使用图表时,这可能会导致两个不同的问题:
首先,删除图表时,钩子创建的资源不会被删除。除非手动删除资源,否则这可能意外地使用集群中的资源。其次,如果您使用的钩子在图表发布的生命周期中可以被调用多次,那么您的资源名称可能会发生冲突。
使用我们的示例作业,如果我们将钩子注释更新为"helm.sh/hook": pre-install,pre-upgrade
,我们会发现当安装图表时,作业将正确运行,但是当我们升级发布时,Helm 会尝试创建一个与pre-install
钩子中已创建的作业同名的新作业。这将导致错误,从而阻止升级继续进行。
解决此问题的一种方法是在作业名称中包含发布修订号,如下所示:
metadata:
name: "{{.Release.Name}}-setup-{{ Release.Revision }}"
虽然这可以防止作业名称冲突,但这意味着每次升级发布都会创建一个新资源,所有这些资源在不再需要时可能需要手动清理。
Helm 提供了另一个注释来帮助我们解决这个问题。helm.sh/hook-delete-policy
允许我们指示 Helm 在成功执行后删除资源,或在失败后删除资源,或两者都删除。
注释"helm.sh/hook-delete-policy": hook-succeeded
对于大多数用例非常有用,例如设置脚本作业示例。如果作业成功运行,则会被删除,清理资源,以便在下次升级图表时创建具有相同名称的新实例。如果作业失败,则会保留在 Kubernetes 服务器上,以便进行调试目的的检查。
如果您正在使用 Helm 作为自动化工作流程的一部分,确保通过安装图表创建的所有资源都被删除,无论结果如何,您可能希望使用以下注释:
"helm.sh/hook-delete-policy": hook-succeeded,hook-failed
打包 Helm 图表
在开发图表时,可以简单地使用 Helm CLI 直接从本地文件系统部署图表。但是,Helm 还允许您创建自己的存储库,以便共享您的图表。
Helm 存储库是存储在标准 HTTP Web 服务器上特定目录结构中的打包 Helm 图表的集合,以及索引。
一旦您满意您的图表,您将希望打包它,以便在 Helm 存储库中分发。使用helm package
命令很容易做到这一点。当您开始使用存储库分发图表时,版本控制变得重要。Helm 存储库中图表的版本号需要遵循 SemVer 2 指南。
要构建打包的图表,首先要检查您是否在Chart.yaml
中设置了适当的版本号。如果这是您第一次打包图表,那么默认值将是 OK:
$ helm package version-app
Successfully packaged chart and saved it to: ~/helm-charts/version-app-0.1.0.tgz
您可以使用helm serve
命令测试打包的图表,而无需将其上传到存储库。此命令将为当前目录中找到的所有打包图表提供服务,并动态生成索引:
$ helm serve
Regenerating index. This may take a moment.
Now serving you on 127.0.0.1:8879
现在您可以尝试使用本地存储库安装您的图表:
$ helm install local/version-app
您可以测试构建索引
Helm 存储库只是存储在目录中的一组打包图表。为了发现和搜索特定存储库中可用的图表和版本,Helm 客户端会下载一个包含有关每个打包图表及其可下载位置的元数据的特殊index.yaml
。
为了生成这个索引文件,我们需要将我们想要在索引中的所有打包的图表复制到同一个目录中:
cp ~/helm-charts/version-app-0.1.0.tgz ~/helm-repo/
然后,为了生成index.yaml
文件,我们使用helm repo index
命令。您需要传递打包图表将被提供的根 URL。这可以是 Web 服务器的地址,或者在 AWS 上,您可以使用 S3 存储桶:
helm repo index ~/helm-repo --url https://helm-repo.example.org
图表索引是一个非常简单的格式,列出了每个可用图表的名称,然后提供了每个命名图表的每个版本的列表。索引还包括一个校验和,以验证从存储库下载图表:
apiVersion: v1
entries:
version-app:
- apiVersion: v1
created: 2018-01-10T19:28:27.802896842Z
description: A Helm chart for Kubernetes
digest: 79aee8b48cab65f0d3693b98ae8234fe889b22815db87861e590276a657912c1
name: version-app
urls:
- https://helm-repo.example.org/version-app-0.1.0.tgz
version: 0.1.0
generated: 2018-01-10T19:28:27.802428278Z
我们新的图表存储库生成的index.yaml
文件。
一旦我们创建了index.yaml
文件,只需将打包的图表和索引文件复制到您选择使用的主机上。如果您使用 S3,可能会像这样:
aws s3 sync ~/helm-repo s3://my-helm-repo-bucket
为了使 Helm 能够使用您的存储库,您的 Web 服务器(或 S3)需要正确配置。
Web 服务器需要提供带有正确内容类型标头(text/yaml
或text/x-yaml
)的index.yaml
文件。
图表需要在索引中列出的 URL 上可用。
使用您的存储库
一旦您设置了存储库,就可以配置 Helm 来使用它:
helm repo add my-repo https://helm-repo.example.org
my-repo has been added to your repositories
当您添加一个存储库时,Helm 会验证它确实可以连接到给定的 URL 并下载索引文件。
您可以通过使用helm search
来搜索您的图表来检查这一点:
$ helm search version-app
NAME VERSION DESCRIPTION
my-repo/version-app 0.1.1 A Helm chart for Kubernetes
Helm 的组织模式
在使用 Kubernetes 部署自己的应用程序的组织中,有一些策略您可能想要考虑,以便生成和维护用于管理您使用的应用程序的部署的图表。
每个应用程序一个图表
在您的组织中使用 Helm 的最简单方法是为要部署到 Kubernetes 的每个应用程序创建一个新的图表。
当您有一个可能部署到多个不同上下文的应用程序时,比如测试、暂存和生产环境,这可以确保在每个环境之间有一致性,同时也可以简单地提供可能是特定于环境的配置的覆盖。
为您的应用程序创建 Helm 图表可以帮助在较大的组织中,应用程序可能需要在没有构建和管理应用程序的团队的帮助下部署到多个不同的环境。
例如,移动应用程序或前端 Web 开发人员可能会使用 Helm 将另一个团队开发的后端 API 应用部署到测试或开发环境。如果开发后端的团队提供了一个 Helm 图表,那么其他团队可以简单地部署而无需深入了解如何安装和配置该应用程序。
如果相同的 Helm 图表用于部署到生产环境以及测试和开发环境,那么可以更简单地减少生产和开发环境之间不可避免的漂移。
使用 Helm 模板语言的控制流功能可以在适当的情况下提供不同的配置是很简单的。例如,在暂存或生产环境中,您的应用程序可能依赖将数据保存到 EBS 卷,而在开发机器上,应用程序可能只是保存到本地卷。
当您的图表部署时,可能需要覆盖一些值。例如,在生产环境中,您可能希望运行更多 pod 的副本,而在开发机器上,单个副本可能就足够了。
如果您的应用程序可以通过添加更多 pod 的副本来水平扩展,那么在所有环境中提供相同的内存和 CPU 限制,然后通过添加额外的 pod 来扩展生产流量,而不是给每个 pod 提供更大的资源限制是有意义的。这样做可以更容易地调试由于内存不足错误或 CPU 资源不足而导致应用程序被终止的问题,因为单个 pod 在开发和生产集群上将具有相同的资源。
共享图表
如果您的组织维护基于服务或微服务的系统,通常会在部署的不同服务之间保持一定程度的标准化。
在每个应用程序之间保持一致的部署模式的一种方法是提供一个 Helm 图表,可以用来部署所有服务。
如果这样做,您会发现需要为图表提供的配置和模板本身变得更加复杂。但这种工作方式的优势在于,它可以让您快速将新的配置最佳实践应用到所有应用程序中。
在更简单的 Helm 图表中,我们为应用程序的每个 pod 提供了一个新模板。当一个图表要被多个应用程序重用时,每个应用程序可能需要不同的 pod。
例如,一个应用程序可能需要一个 Web 服务器和一个每小时运行的批处理作业,而另一个服务提供一个管理界面和一个用于处理消息队列中的后台作业的工作程序。
为了能够使用一个图表部署两个不同类型的应用程序,您需要生成一个模板——不是针对应用程序中的每个 pod,而是针对您的服务合同支持的每种类型的 pod。
例如,您可能有一个用于管理长时间运行的 pod 的模板,该模板使用 Kubernetes 部署资源,以及另一个用于使用CronJob
资源管理批处理作业的模板。然后,为了启用和配置这些模板,您可以在部署应用程序时提供应用程序需要的每个 pod 的列表。
我制作了一个采用这种方法的示例图表。它可以在github.com/errm/charts/tree/master/app
找到。
库图表
如果您的组织有配置和部署模式,您希望在不同的应用程序之间共享,但共享图表的方法提供的灵活性不够,或者在模板中导致过于复杂的逻辑,那么另一种选择是提供包含模板或函数的库图表,这些模板或函数可以作为应用程序的依赖项,为需要它们的每个图表提供共同的组件或配置。
这样可以让您在能够根据特定应用程序定制您的图表的同时,仍然能够使用共享功能,以减少配置的重复或强制执行最佳实践或其他组织范围的部署模式。
下一步
Helm 之所以强大,是因为它让你可以在一组 Kubernetes 资源上构建自己的抽象,而几乎不需要额外的努力。你可能需要花一点时间学习如何使用模板语言,以及如何集成构建和更新图表、制作和更新发布与你的开发和发布流程。
Helm 可用于各种场景,您可以在其中部署资源到 Kubernetes 集群,从为他人提供一种简单的方式在他们自己的集群上安装您编写的应用程序,到在更大的组织内形成内部平台即服务的基石。除了本章包含的内容之外,还有很多东西等待您去学习。
Helm 有出色的文档,可以在docs.helm.sh/
上访问。
学习如何有效使用 Helm 的另一个很好的来源是社区维护的图表存储库github.com/helm/charts
。您会发现在那里可以通过查看可用的图表学习到很多技巧和最佳实践。
第六章:生产规划
Kubernetes 为开发人员提供了一个极好的平台,可以快速构建高度灵活的分布式应用程序。通过在 Kubernetes 上运行我们的应用程序,我们可以利用各种工具来简化它们的操作,并使它们更加可靠、抗错误,并且最终高度可用。
为了依赖于我们的应用程序可以从 Kubernetes 继承的一些保证和行为,重要的是我们了解 Kubernetes 的行为方式,以及对生产系统产生影响的一些因素。
作为集群管理员,重要的是你要了解你正在运行的应用程序的要求,以及这些应用程序的用户。
在生产中了解 Kubernetes 的行为方式至关重要,因此在开始提供关键任务流量之前,获得在 Kubernetes 上运行应用程序的实际经验是非常宝贵的。例如,当 GitHub 将他们的主要应用程序迁移到 Kubernetes 时,他们首先将内部用户的流量转移到他们基于 Kubernetes 的新基础设施,然后再切换到他们的主要生产流量。
“来自内部用户的负载帮助我们发现问题,修复错误,并开始逐渐熟悉 Kubernetes 在生产中的运行。在此期间,我们努力增加自己的信心,通过模拟未来预期执行的程序,编写运行手册,并进行故障测试。”—Jesse Newland (githubengineering.com/kubernetes-at-github/
)
虽然我可以涵盖一些在 AWS 上使用 Kubernetes 进行生产时可能会遇到的事情,但重要的是要理解每个应用程序和组织在很多方面都是独特的。你应该把 Kubernetes 看作一个工具包,它将帮助你为你的组织构建一个强大而灵活的环境。Kubernetes 并不是一个能够消除对运维专业知识需求的魔法子弹;它是一个帮助你管理应用程序的工具。
设计过程
设计过程如下:
当你考虑准备使用 Kubernetes 来管理你的生产基础设施时,你不应该把 Kubernetes 看作你的最终目标。它是一个构建平台的基础,用于运行系统。
当你考虑构建一个满足组织中不同人员需求的平台时,定义你将对 Kubernetes 提出的要求变得更加简单。在尝试规划生产环境时,你需要了解你的组织的需求。显然,你想要管理的软件的技术要求很重要。但了解你的组织需要支持的运营流程也很关键。
采用 Kubernetes 为具有复杂软件要求的组织带来了许多好处。不幸的是,这种复杂性也可能导致在安全地成功采用 Kubernetes 方面出现挑战。
初始规划
你应该考虑你的初始推出的重点在哪里。你应该寻找一个既能够快速提供有价值的结果,又具有较低风险的应用程序。如果我们想想 GitHub 的例子,他们最初把重点放在为内部用户构建基础设施,以便快速测试他们软件的更改。通过专注于审查或分期基础设施,他们找到了一个适用于 Kubernetes 的应用程序,既能够为他们组织的开发人员快速提供价值,又是一个对他们的业务风险较低的领域,因为它只被内部用户访问。
像这样既具有即时有用性又对停机影响较小的应用程序非常有用。它们使你的组织能够在使用 Kubernetes 时获得宝贵的运营经验,并在尝试处理生产工作负载之前消除错误和其他问题。
在开始使用 Kubernetes 时,选择你的组织运营的最简单的应用程序并开始围绕它构建流程和工具可能是诱人的。然而,这可能是一个错误,因为这可能会导致你对应用程序应该如何操作做出假设,这可能会使将相同的流程和配置应用到更复杂的应用程序变得更加困难。
如果您选择开始构建支持不需要任何后端服务(如数据库)的简单应用程序的平台,您可能会错过一些需要考虑的事情作为部署过程的一部分。例如,由数据库支持的应用程序通常需要运行迁移脚本来在部署新版本的应用程序时更新架构。如果您首先设计部署流程以满足非常简单应用程序的需求,您可能要到后来才能发现这些要求。请记住,部署一个只需要平台提供的部分功能子集的简单应用程序将始终比部署一个需要您在设计时没有考虑到的更复杂的应用程序要简单得多。
如果您选择将精力集中在初始采用 Kubernetes 的单个应用程序上,请确保选择一个代表您组织需求的应用程序。很容易会开始为一个全新的项目使用 Kubernetes,因为您可以考虑平台的应用开发决策。但请记住,一个新应用程序可能会比使用时间更长的应用程序简单得多。在 GitHub 的例子中,他们选择首先部署的应用程序是他们组织运营的最大的应用程序,提供许多核心服务。
如果您的组织有一个每次部署都需要大量运营时间和精力的应用程序,那么这可能是初始采用 Kubernetes 的一个很好的选择。这些应用程序将因其需求而为您的开发和运营团队所熟知,并且他们将立即能够开始利用 Kubernetes 来解决以前花费时间和精力的问题。
成功的规划
为了成功地实施采用 Kubernetes 的项目,有一些事情您应该尽量避免。
一个很容易陷入的陷阱是改变得太快太多。如果您决定采用容器化和 Kubernetes,很容易会在此过程中采用许多新的流程和工具。这可能会显著减慢您的进展,因为最初是为了在容器中运行应用程序的项目很快就会扩展到包括您的组织想要采用的许多其他工具和流程。
您应该努力避免范围蔓延,并尽量改变尽可能少的内容,以便尽快交付您对 Kubernetes 的初始采用。重要的是不要试图一次实现太多容器化的承诺,因为这将阻碍您的采用,并可能导致整个项目的失败。
尝试考虑您当前部署应用程序的环境,并首先复制其功能,然后添加额外的功能。我们在本书的其余部分讨论的许多工具和流程可能确实是您的 Kubernetes 集群的可选项,可以在以后的日期添加,以提供额外的有价值的服务,但不应视为采用的障碍。
如果您有机会在额外的部署中减少 Kubernetes 部署提供的基础设施范围,您应该考虑这样做。这样可以减少组织需要理解的新工具和流程的范围。这将使您有机会在以后更详细地关注这个主题,并参考您在 Kubernetes 上运行应用程序时获得的运营经验。
以日志管理为例,如果您当前的流程是使用 SSH 登录服务器并查看日志文件,您可以使用kubectl logs
命令为您的 Kubernetes 集群的操作员提供相同的功能。实施一个解决方案来聚合和搜索集群生成的日志可能是可取的,但不一定是使用 Kubernetes 的阻碍因素。
如果您当前将应用程序部署到运行 Linux 发行版的服务器上,该发行版作为容器映像 readily 可用,您应该坚持使用该发行版,而不是在这个阶段寻找替代方案,因为您的开发人员和运营人员已经了解它的工作原理,您不必投入时间来修复不兼容性。学习在 Kubernetes 上操作您的应用程序应该是您的重点,而不是学习如何配置新的操作系统发行版。
规划成功的部署。
改变组织中的流程和责任可能是诱人的。但在采用像 Kubernetes 这样的新工具时,尝试这样做可能是有风险的。例如,如果在您的组织中有一个负责部署和监控应用程序的运维团队,那么在采用 Kubernetes 时并不是将这一责任交给其他人(比如开发团队)或尝试自动化手动流程的正确时机。
这可能令人沮丧,因为通常采用 Kubernetes 是作为改进组织使用的流程和工具的更广泛计划的一部分。您应该等到成功建立 Kubernetes 的使用和操作后再进行。这将使您在有一个稳定的基础之后更好地引入新工具和流程。您应该将采用 Kubernetes 视为建立一个灵活的基础,以便在将来实施对工具和流程的任何更改。
一旦您的应用基础设施在 Kubernetes 上运行,您会发现实施新工具、服务和流程变得更加简单。一旦您拥有了一个 Kubernetes 集群,您会发现尝试新工具的障碍大大降低。您可以通过向集群提交新配置来快速评估和尝试新工具,而不是花费大量时间进行规划和配置。
发现需求
设计需求如下图所示:
在准备生产时,可用性、容量和性能是我们应该考虑的关键属性。在收集集群的功能需求时,可以帮助将需求归类为涉及这些属性的需求。
重要的是要明白,可能无法在不做出一些权衡的情况下优化所有三个属性。例如,对于依赖非常高网络性能的应用程序,AWS 提供了一个称为集群放置组的工具。这确保了通过在 AWS 数据中心内以某种方式配置 EC2 VM 来提供最佳网络性能,从而在它们之间提供快速的网络互连(可能是通过将它们放置在 AWS 数据中心内的相近位置)。通过这种方式配置实例,可以在集群放置组内的机器之间实现最高的网络吞吐量(超过 5 GB)和最低的延迟。对于一些需要这些性能水平的应用程序来说,这可能是一种值得的优化。
然而,由于集群放置组内的 EC2 实例不能跨多个可用区,因此这种设置的可靠性可能较低,因为潜在的电源或连接问题可能会影响特定区域内的所有实例,特别是如果它们被部署以最大化互连速度。如果您的应用程序对这种高性能网络没有要求,将可靠性换取更高性能的做法确实是不明智的。
这些属性的最重要的属性是生产系统的一个非常重要的属性——可观察性。可观察性实际上描述了集群操作员了解应用程序发生了什么的能力。如果无法了解应用程序是否按照预期执行和行为,就无法评估、改进和演变系统的设计。在设计集群时,这是一个重要的反馈循环,它使您能够根据运营经验维护和改进集群。如果在规划集群时不考虑可观察性,要调试集群本身和应用程序的问题就会更加困难。
在规划阶段讨论应用程序需求时,很难理解应用程序的需求会是什么。对集群性能、底层硬件以及运行在其上的应用程序有良好的可观察性,可以让您做出务实的决策,并且足够灵活,以便在发现更多关于它们在生产工作负载下的行为以及随着功能随时间的开发而发生变化时,支持应用程序所做出的更改。
最后,也许最重要的属性要考虑的是安全性。将集群的安全性留到规划过程的最后是一个错误。请记住,尽管单靠安全性本身不会导致项目的成功,但未能确保集群的安全性可能会导致灾难性后果。
最近的研究和披露显示,未加密的 Kubernetes 集群已经成为那些想要利用您的计算能力进行加密货币挖矿和其他不良目的的人的有吸引力的目标,更不用说访问您的集群可能被用来访问您组织内保存的敏感数据的潜力。
安全性应该在集群的整个生命周期中得到考虑和监控;事实上,您应该尝试并了解每一个其他要求的安全性影响。您需要考虑您的组织成员如何与 Kubernetes 进行交互,同时制定计划确保您的集群和在其上运行的应用程序的配置和软件的安全。
在本章的后续部分,我们将介绍一些想法,帮助您了解在这些属性方面可能需要考虑的事项。希望本章能够让您充分了解自己的需求,并开始规划生产集群。关于实施计划所需的具体知识,请继续阅读;本书的后半部分几乎完全专注于您实施计划所需的实用知识。
可用性
可用性如下图所示:
在规划生产系统时,考虑到可用性是最重要的事情之一。几乎总是我们运行软件来为用户提供服务。如果由于任何原因我们的软件无法满足用户的请求,那么通常我们就无法满足他们的期望。根据您的组织提供的服务,不可用可能会导致用户不满意、不便或甚至遭受损失或伤害。制定任何生产系统的充分计划的一部分是了解停机时间或错误可能如何影响您的用户。
可用性的定义取决于您的集群正在运行的工作负载类型和您的业务需求。规划 Kubernetes 集群的关键部分是了解用户对您正在运行的服务的需求。
例如,考虑一个每天给用户发送业务报告的批处理作业。只要您能确保它每天至少运行一次,大致在正确的时间,您就可以认为它的可用性达到 100%,而可以在白天或晚上任何时间访问的 Web 服务器需要在用户需要访问时可用且无错误。
当 CEO 在早上 9 点到达工作时,收件箱中已经准备好阅读的报告时,他们会很高兴。他们不会在意任务在午夜时分未能成功运行,并在几分钟后成功重试。然而,如果托管他们用来阅读电子邮件的 Web 邮件应用程序的应用服务器在一天中的任何时候甚至短暂不可用,他们可能会受到打扰和不便:
计算服务可用性的简单公式
一般来说,系统工程师认为给定服务的可用性是成功请求占总请求次数的百分比。
我们可以认为即使批处理作业失败了几次,也是可用的。我们对系统的要求(每天向正确的人发送报告)只需要作业至少成功完成一次。如果我们通过重试优雅地处理故障,对我们的用户没有影响。
您应该为系统计划的确切数字,当然,主要取决于用户的需求和组织的优先级。然而,值得记住的是,为了更高的可用性而设计的系统几乎总是比可以接受停机时间的类似系统更复杂,需要更多资源。随着服务接近 100%的可用性,实现额外可靠性的成本和复杂性呈指数增长。
如果您还不了解它们,可以合理地开始讨论组织内的可用性要求。您应该这样做,以便设定目标并了解在 Kubernetes 上运行软件的最佳方法。以下是一些您应该尝试回答的问题:
-
你知道你的用户是如何访问你的服务的吗? 例如,如果你的用户使用移动设备,那么连接到互联网可能本来就更不可靠,掩盖了你的服务的正常运行时间(或其他情况)。
-
如果你正在将你的服务迁移到 Kubernetes,你知道它目前的可靠性吗?
-
你能够对不可用性进行货币价值评估吗? 例如,电子商务或广告技术组织将知道在停机期间将会损失多少金额。
-
你的用户准备接受多少不可用性水平? 你有竞争对手吗?
你可能已经注意到,所有这些问题都是关于你的用户和你的组织;对于任何一个问题,都没有确定的技术答案,但你需要能够回答它们以了解你正在构建的系统的要求。
为了提供一个在网络上可以访问的高可用服务,比如一个网页服务器,我们需要确保服务能够在需要时响应请求。由于我们无法确保我们的服务运行在的底层机器是 100%可靠的,我们需要运行多个实例的软件,并且只将流量路由到那些能够响应请求的实例上。
这个批处理作业的语义意味着(在合理范围内),我们并不太关心作业执行所需的时间,而网页服务器响应所需的时间则非常重要。有许多研究表明,即使是对网页加载时间增加了不到一秒的延迟,也会对用户产生显著和可测量的影响。因此,即使我们能够隐藏故障(例如通过重试失败的请求),我们的余地要小得多,甚至我们甚至可能认为高优先级的请求如果超过特定阈值的时间就已经失败了。
你可能选择在 Kubernetes 上运行你的应用程序的一个原因是因为你听说过它的自愈特性。Kubernetes 将管理我们的应用程序,并在需要时采取行动,以确保我们的应用程序继续以我们要求的方式运行。这是 Kubernetes 对配置的声明性方法的一个有益的效果。
使用 Kubernetes,我们将要求集群上运行某项服务的特定数量的副本。即使发生影响运行应用程序的情况,例如节点故障或应用程序实例由于内存泄漏而定期被终止,控制平面也能够采取行动来确保这种条件继续成立。
与依赖操作员选择特定基础机器(或一组机器)运行应用程序的命令式部署程序形成对比。如果机器故障,甚至如果应用程序实例表现不佳,则需要手动干预。我们希望为用户提供所需的服务而不中断。
对于始终开启或延迟敏感的应用程序,例如 Web 服务器,Kubernetes 为我们提供机制来运行我们应用程序的多个副本,并测试我们服务的健康状况,以便从服务中删除失败的实例,甚至重新启动。
对于批处理作业,Kubernetes 将重试失败的作业,并将它们重新调度到其他节点,如果底层节点失败。这种重启和重新调度失败应用程序的语义依赖于 Kubernetes 控制平面的功能。一旦 pod 在特定节点上运行,它将继续运行,直到发生以下情况:
-
它退出
-
它被 kubelet 终止,因为使用了太多内存
-
API 服务器请求将其终止(可能是为了重新平衡集群或为了为具有更高优先级的 pod 腾出空间)
这意味着控制平面本身可以暂时不可用,而不会影响集群上运行的应用程序。但是,直到控制平面再次可用之前,没有失败的 pod,或者在已经失败的节点上运行的 pod 将被重新调度。显然,您还需要 API 服务器可用以与其交互,因此还应考虑组织推送新配置到集群的需求(例如,部署应用程序的新版本)。
我们将讨论一些策略和工具,您可以使用它们来提供一个高可用的控制平面,详情请参阅第七章 生产就绪的集群。
容量
容量如下图所示:
运行诸如 Kubernetes 之类的系统意味着您可以在应用程序启动的时间内对服务的额外需求做出实际回应。这个过程甚至可以通过诸如水平 Pod 自动缩放器(我们将在第八章中讨论的抱歉,我的应用程序吃掉了集群)这样的工具自动化。
当我们将这种灵活性与我们随意启动新的 EC2 实例的能力相结合时,容量规划比过去要简单得多。Kubernetes 和 AWS 允许我们构建只在任何给定时间使用所需资源量的应用程序。我们可以对应用程序的使用要求做出反应,而不是预期对我们的应用程序的需求并预先承诺使用资源。Kubernetes 最终使我们能够实现云计算的一个承诺:我们只支付我们使用的资源。
在使用 AWS 支付的资源上最有效地使用资源时,您应该考虑一些因素。
EC2 实例类型
在准备启动 Kubernetes 集群时,您可能会考虑集群中将使用的实例的类型和大小。您选择的实例可能会对 Kubernetes 集群的利用率、性能和成本产生重大影响。
当 Kubernetes 将您的 pod 调度到集群中的工作节点时,它会考虑作为 pod 定义的一部分的资源请求和限制。
通常,您的 pod 规范将请求一定数量的 CPU(或其分数)和一定数量的内存。在 AWS 上,Kubernetes 使用 AWS 的 vCPU 作为其度量单位。vCPU(在大多数实例类型上)是一个单 CPU(超)线程,而不是 CPU 核心。如果您请求了 CPU 的分数,则 Kubernetes 会为您的 pod 分配一个 vCPU 的份额。内存以字节为单位请求。
EC2 实例有几种不同类型,提供不同的 CPU 到内存比例。
EC2 实例类型
EC2 实例类型显示在以下表中:
类别 | 类型 | CPU 到内存比例:vCPU:GiB | 备注 |
---|---|---|---|
突发型 | T3 | 1 CPU : 2 GiB | 提供 5-40%的 CPU 基线+可突发额外使用。 |
CPU 优化 | C5 | 1 CPU : 2 GiB | |
通用型 | M5 | 1 CPU : 4 GiB | |
内存优化 | R5 | 1 CPU : 8 GiB | |
X1 | 1 CPU : 15GiB | ||
X1e | 1 CPU : 30GiB | ||
只有在需要它们提供的额外资源(GPU 和/或本地存储)时,您才应该考虑以下实例类型: | |||
GPU | P3 | 1 CPU : 7.6GiB | 1 GPU : 8 CPU (NVIDIA Tesla V100) |
P2 | 1 CPU : 4GiB | i. 1 GPU : 4 CPU (NVIDIA K80) | |
存储 | H1 | 1 CPU : 4GiB | 2TB HDD : 8 CPU |
D2 | 1 CPU : 7.6GiB | 3TB HDD : 2 CPU | |
I3 | 1 CPU : 7.6GiB | 475GiB SSD : 2 CPU |
在准备集群时,我们应该考虑组成集群的实例类型和实例大小。
当 Kubernetes 将我们的 pod 调度到集群中的节点时,它当然是希望尽可能多地将容器放入集群中。然而,如果大多数 pod 的 CPU 到内存请求比在底层节点中显着不同,这可能会受到阻碍。
例如,考虑这样一个场景:我们在集群中部署了请求 1 个 CPU 和 2GiB 内存的 pod。如果我们的集群由m5.xlarge
实例(4 vCPU 和 16 GiB 内存)组成,每个节点将能够运行四个 pod。一旦这四个 pod 在该节点上运行,就无法再将更多的 pod 调度到该节点,但是一半的内存将被闲置,实际上处于被困的状态。
如果您的工作负载非常同质化,那么确定哪种实例类型将为您的应用程序提供最佳的 CPU 到内存比率当然是非常简单的。然而,大多数集群运行多个应用程序,每个应用程序需要不同数量的内存和 CPU(甚至可能还需要其他资源)。
在第八章中,抱歉,我的应用程序吃掉了集群,我们讨论了如何使用集群自动缩放器自动向 AWS 自动缩放组添加和删除实例,以便在任何给定时间将您的集群大小调整到与集群要求相匹配。我们还讨论了如何使用集群自动缩放器来扩展具有多种不同实例类型的集群,以应对在这些集群中运行的工作负载的大小和形状相当动态,可能会不时发生变化的 CPU 到内存比率的问题。
广度与深度
亚马逊为每个系列提供了许多不同的实例大小;例如,m5 和 c5 系列都有六种不同的实例大小,每一级别提供的资源是前一级别的两倍。因此,最大的实例比最小的实例多 48 倍资源。我们应该如何选择用于构建集群的实例大小?
-
您的实例大小限制了集群上可运行的最大 Pod 大小。实例需要比您最大的 Pod 大 10-20%,以考虑系统服务(如日志记录或监控工具、Docker 和 Kubernetes 本身)的开销。
-
较小的实例将允许您以较小的增量扩展您的集群,增加利用率。
-
较少(较大)的实例可能更容易管理。
-
较大的实例可能会使用较低比例的资源来执行集群级任务,例如日志传送和指标。
-
如果您想使用监控或日志记录工具,例如 Datadog、Sysdig、NewRelic 等,其定价是基于每个实例模型的,较少的较大实例可能更具成本效益。
-
较大的实例可以提供更多的磁盘和网络带宽,但如果您在每个实例上运行更多的进程,这可能不会带来任何优势。
-
较大的实例大小在超级管理程序级别更不太可能受到嘈杂邻居问题的影响。
-
较大的实例通常意味着更多的 Pod 共存。当旨在增加利用率时,这通常是有利的,但有时可能会导致意外的资源限制模式。
性能
影响性能的集群的关键组件如下图所示:
磁盘性能
如果您的一些应用程序依赖于磁盘性能,了解连接到您实例的 EBS 卷的性能特征可能会非常有用。
所有当前一代的 EC2 实例都依赖于 EBS 存储。EBS 存储实际上是共享的网络附加存储,因此性能可能会受到多种因素的影响。
如果您的集群正在运行在最新一代的 EC2 实例上,您将使用 EBS 优化。这意味着专用带宽可用于对 EBS 卷的 I/O 操作,有效消除了 EBS 和其他网络活动之间的竞争。
EBS 卷可用的总最大带宽取决于 EC2 实例的大小。在一个运行多个容器的系统中,可能每个容器都连接了一个或多个 EBS 卷,您应该意识到这个上限适用于实例上所有正在使用的卷的总和。
如果您计划运行期望进行大量磁盘 I/O 的工作负载,您可能需要考虑实例可用的总 I/O。
EBS 基于两种基本技术提供了四种卷类型。gp2
和io2
卷基于固态硬盘(SSD)技术,而 st1 和 sc1 卷基于硬盘驱动器(HDD)技术。
这种多样性的磁盘对我们很有用,因为广义上,我们可以将您的应用程序可能提供的工作负载分为两组。首先,那些需要对文件系统进行快速随机读取和/或写入的工作负载。属于这一类别的工作负载包括数据库、Web 服务器和引导卷。对于这些工作负载,性能的限制通常是每秒 I/O 操作(IOPS)。其次,有一些工作负载需要尽可能快地从磁盘进行顺序读取。这包括 Map Reduce、日志管理和数据存储,如 Kafka 或 Casandra,这些应用程序已经专门优化,尽可能地进行顺序读取和写入。
在实例级别存在硬性上限,限制了您可以通过 EBS 卷实现的最大性能。附加到单个实例的所有 EBS 卷的最大 IOPS 在 c5 和 m5 实例上可达到 64,000。最小的 c5 和 m5 实例只提供 1,600 IOPS。需要牢记这些限制,无论是如果您想在较小的 EC2 实例类型上运行需要更高磁盘性能的工作负载,还是在较大的实例类型上使用多个 EBS 卷。
gp2
gp2
EBS 卷应该是大多数通用应用的首选。它们以适中的价格提供固态硬盘(SSD)性能。gp2
卷的性能基于一个信用系统。这些卷提供基准性能,并随着时间累积信用,允许在需要时性能突发到 3,000 IOPS,直到累积的信用用尽。
当创建一个gp2
卷时,它会自动获得一个信用余额,允许它在 30 分钟内突发到 3,000 IOPS。当卷被用作引导卷或需要快速复制数据到卷作为引导过程的一部分时,这非常有用。
突发积分的积累速度和gp2
卷的基准性能与卷的大小成正比。小于 33 GiB 的卷始终具有 100 IOPS 的最低基准性能。大于 1 TB 的卷的基准性能大于 3,000 IOPS,因此您不需要考虑突发积分。单个gp2
卷可用的最大性能为 3.3 TB(及更大)的卷的 10,000 IOPS。
如果您的工作负载需要从gp2
卷中获得更高的性能,一个快速的解决方法是使用更大的卷(即使您的应用程序不需要它提供的存储空间)。
您可以通过将 IOPS 乘以块大小(256 KiB)来计算卷支持的最大吞吐量。但是,gp2
卷将总吞吐量限制为 160 MiB/s,因此大于 214 GiB 的卷将仅提供 160 MiB/s。
监视与磁盘使用相关的指标的能力对于了解磁盘性能如何影响您的应用程序,并确定您何时以及在哪里达到性能限制非常宝贵。
io2
对于可靠性能至关重要且gp2
卷无法提供足够 IOPS 的应用程序,可以使用io2
卷(也称为预留 IOPS 卷)。如果它们所附加的实例支持它们,io2
卷可以被配置为提供最多 32,000 IOPS。创建io2
实例时,需要预先指定所需的 IOPS(我们将在第九章中讨论如何在 Kubernetes 中执行此操作,存储状态)。可以为单个卷配置的最大 IOPS 取决于卷的大小,IOPS 和存储的 GiB 之间的比率为50:1
。因此,为了配置最大 IOPS,您需要请求至少 640 GiB 的卷。
对于所需 IOPS 数量小于gp2
卷支持的 IOPS(10,000)且所需吞吐量小于 160 MiB/s 的情况,支持类似性能特征的gp2
卷通常会比io2
卷的价格低一半。除非您知道自己需要io2
卷的增强性能特征,否则大多数通用用途都应坚持使用gp2
卷。
st1
对于已经针对顺序读取进行了优化的应用程序,其中主要的性能指标是吞吐量,也许令人惊讶的是,尽管 SSD 目前占据主导地位,但最佳性能仍然由旋转磁盘提供。
st1(和sc1)卷是 AWS 上可用的最新类型的 EBS 卷。它们旨在为诸如 Map Reduce、日志处理、数据仓库和流式工作负载(如 Kafka)等工作负载提供高吞吐量。st1 卷以不到 gp2 实例成本的一半提供高达 500 MiB/s 的吞吐量。缺点是它们支持的 IOPS 要低得多,因此对于随机或小写入来说性能要差得多。您可能会对 SSD 进行的 IOPS 计算略有不同,因为块大小要大得多(1 MB 对比 256 KB)。因此,进行小写入将花费与顺序写入完整 1 MB 块一样长的时间。
如果您的工作负载已经正确优化以利用 st1 卷的性能特性,那么考虑使用它们是非常值得的,因为成本大约是 gp2 卷的一半。
就像 gp2 卷一样,st1 使用了性能突发模型。然而,积累的信用额允许吞吐量突破基准性能。基准性能和信用额积累速度与卷大小成正比。对于大于 2 TiB 的卷,最大突发性能为 500 MiB/s,对于大于 12.5 TiB 的卷,最大基准性能为 500 MiB/s,对于这样大小(或更大)的卷,无需考虑突发特性,因为性能是恒定的。
sc1
sc1
卷提供了 AWS 上最低成本的块存储。它们提供了与st1
卷类似的性能配置文件,但大约只有一半的速度,成本也只有一半。您可以考虑将它们用于需要从文件系统存储和检索数据,但访问不太频繁或性能对您来说不那么重要的应用程序。
sc1
卷可以被视为归档或 blob 存储系统(如s3
)的替代方案,因为其成本大致相似,但具有无需使用特殊库或工具即可读写数据的优势,并且在数据可以被读取和使用之前具有更低的延迟。
在 Kafka 或日志管理等用例中,你可能会考虑使用 sc1
卷来存储旧数据,这样可以保持在线存储,以便立即使用,但访问频率较低,因此你希望优化存储成本。
网络
在运行分布式系统时,网络性能可能是应用程序整体可观察性能的关键因素。
鼓励构建应用程序的架构模式,其中不同组件之间的通信主要通过网络进行(例如,SOA 和微服务),会导致应用程序中的集群内部网络成为性能瓶颈。集群数据存储在进行写操作以及在扩展或维护操作期间重新平衡集群时,也可能对集群内部网络提出高要求。
当运行暴露给互联网或其他广域网的服务时,网络性能当然也是需要考虑的因素。
最新一代的 EC2 实例类型受益于 AWS 描述为增强网络的网络接口。要从中受益,你需要运行相对较新的实例类型(M5、C5 或 R4),并安装亚马逊的弹性网络适配器的特殊网络驱动程序。幸运的是,如果你使用主要 Linux 发行版的官方 AMI,这些都应该已经为你完成。
你可以使用 modinfo
命令检查是否安装了正确的驱动程序:
$ modinfo ena
filename: /lib/modules/4.4.11-
23.53.amzn1.x86_64/kernel/drivers/amazon/net/ena/ena.ko
version: 0.6.6
license: GPL
description: Elastic Network Adapter (ENA)
author: Amazon.com, Inc. or its affiliates
...
如果未安装 弹性网络接口 的驱动程序,你将看到类似以下的内容:
$ modinfo ena
ERROR: modinfo: could not find module ena
增强网络带来的性能提升并不需要额外费用,因此在准备生产时,你应该检查是否正确配置了增强网络。常见使用中唯一不支持增强网络的实例类型是 t2 可突发性能实例。
EC2 实例的网络性能与实例大小成正比,每种实例类型中最大的实例大小才能达到 10 或 20 GBps 的网络吞吐量。即使使用最大的 EC2 实例大小,只有在与集群放置组中的其他实例进行通信时,才能实现网络吞吐量的最大值。
集群放置组可用于请求亚马逊在其数据中心的特定区域同时启动您需要的每个实例,以便获得最快的速度(和最低的延迟)。为了提高网络性能,我们可以调整两个变量:
-
增加实例大小:这样可以使实例获得更快的网络,并增加共存,从而更有可能在服务之间进行本地主机网络调用。
-
将您的实例添加到集群放置组:这可以确保您的实例在物理上靠近,从而提高网络性能。
在做出这样的决定之前,你需要知道网络是否真的是你的性能瓶颈,因为所有这些选择会使你的集群更容易受到 AWS 基础设施中潜在故障的影响。因此,除非你已经知道你的特定应用程序会对集群网络提出特定要求,否则不应该试图优化以获得更高的性能。
安全
以下图表显示了一些影响安全性的关键领域:
保护集群基础设施的配置和软件的安全性至关重要,特别是如果您计划将其上运行的服务暴露到互联网上。
你应该考虑,如果你将服务暴露到公共互联网上,而这些服务有众所周知的软件漏洞或配置错误,可能只是几个小时之内,你的服务就会被用于扫描易受攻击系统的自动化工具所发现。
重要的是,你要将集群的安全性视为一个不断变化的目标。这意味着你或者你使用的工具需要意识到新的软件漏洞和配置漏洞。
Kubernetes 软件和主机的基础操作系统软件的漏洞将由 Kubernetes 社区和您的操作系统供应商进行更新和修补,只需要操作员有一个应用更新的程序即可。
环境配置更为关键,因为验证其安全性和正确性的责任完全落在你的肩上。除了花时间验证和测试配置外,你还应该将配置的安全性视为一个不断变化的目标。在更新时,你应该确保花时间审查 Kubernetes 变更日志中的更改和建议。
始终进行更新
Kubernetes 的新次要版本大约每三个月发布一次。该项目可以对每个发布的次要版本发布补丁级更新,频率最多每周一次。补丁级更新通常包括修复更重大的错误和安全问题的修复。Kubernetes 社区目前同时支持三个次要版本,随着每个新的次要版本发布,最旧的受支持版本的常规补丁级更新将结束。这意味着在计划和构建集群时,您需要计划对 Kubernetes 软件进行两种维护:
-
补丁级更新:每个月多次:
-
这些应该保持非常紧密的兼容性,大多数情况下应该是微不足道的。
-
它们应该简单易行,几乎没有(或没有)停机时间。
-
次要版本升级:每 3 到 9 个月:
-
在次要版本之间升级时,您可能需要对集群的配置进行微小更改。
-
Kubernetes 确实保持良好的向后兼容性,并且有一种在删除或更改配置选项之前废弃配置选项的策略。只需记住在更改日志和日志输出中注意废弃警告。
-
如果您正在使用第三方应用程序(或编写了自己的工具),这些应用程序依赖于测试版或 alpha API,则可能需要在升级集群之前更新这些工具。只使用稳定 API 的工具应该在次要版本更新之间继续工作。
-
- 您可能需要考虑以下事项:
-
一个测试环境,您可以在其中应用 Kubernetes 软件的更新,以验证任何更改,然后再将其发布到生产环境。
-
如果您检测到任何错误,可以通过程序或工具回滚任何版本升级。
-
监控可以让您确定您的集群是否按预期运行。
-
您用于更新组成集群的机器上的软件的程序确实取决于您使用的工具。
您可能采取两种主要策略——就地升级和基于不可变镜像的更新策略。
就地更新
有几种工具可以让您升级集群节点的底层操作系统。例如,对于基于 Debian 的系统,可以使用unattended-upgrades
工具,对于基于 Red Hat 的系统,可以使用yum-cron
工具,这些工具可以在没有任何操作员输入的情况下在节点上安装更新的软件包。
当然,在生产环境中,如果特定更新导致系统失败,这可能有一定风险。
通常,如果您正在管理具有自动更新的系统,您将使用软件包管理器将基本组件(如 Kubernetes 和 etcd)固定到特定版本,然后以更受控制的方式升级这些组件,可能使用配置管理工具,如 Puppet、Chef 或 Ansible。
以这种自动化方式升级软件包时,当更新某些组件时,系统需要重新启动。诸如 KUbernetes REboot Daemon(Kured)(github.com/weaveworks/kured
)之类的工具可以监视特定节点需要重新启动的信号,并编排重新启动集群中的节点,以维护集群上运行的服务的正常运行时间。首先通过发出信号通知 Kubernetes Scheduler 重新调度工作负载到其他节点,然后触发重新启动。
还有一种新型操作系统,例如 CoreOS 的 Container Linux 或 Google 的 Container-Optimized OS,对更新采取了略有不同的方法。这些新的面向容器的 Linux 发行版根本不提供传统的软件包管理器,而是要求您将不在基本系统中运行的所有内容(如 Kubernetes)作为容器运行。
这些系统处理基本操作系统的更新方式更像是在消费类电子产品中找到的固件更新系统。这些操作系统中的基本根文件系统是只读的,并且从两个特殊分区中挂载。这允许系统在后台下载新的操作系统镜像到未使用的分区。当系统准备好升级时,它将被重新启动,并且来自第二分区的新镜像将被挂载为根文件系统。
这样做的好处是,如果升级失败或导致系统变得不稳定,可以简单地回滚到上一个版本;事实上,这个过程甚至可以自动化。
如果您正在使用 Container Linux,您可以使用 Container Linux Update Operator 来编排由于操作系统更新而需要重新启动的操作(github.com/coreos/container-linux-update-operator
)。使用这个工具,您可以确保在重新启动之前,主机上的工作负载被重新调度。
不可变镜像
虽然有工具可以帮助管理原地升级您的主机,但是采用不可变镜像的策略也有一些优势。
一旦您使用 Kubernetes 管理运行在基础架构上的应用程序,需要安装在节点上的软件就变得标准化了。这意味着管理主机配置的更新变得更加简单,因为它们是不可变的镜像。
这可能很有吸引力,因为它允许您以与使用 Docker 构建应用程序容器类似的方式来管理构建和部署节点软件。
通常,如果采用这种方法,您将希望使用一种工具来简化以 AMI 格式构建镜像并使其可用于其他工具启动新的 EC2 实例以替换使用先前镜像启动的实例。packer 就是这样一种工具。
网络安全
在 AWS 上运行 Kubernetes 时,您需要配置四个不同的层次,以正确地保护集群上的流量。
基础节点网络
为了使 Pod 和服务之间的流量在集群上传递,您需要配置应用于节点的 AWS 组以允许此流量。如果您使用覆盖网络,这通常意味着允许特定端口上的流量,因为所有通信都是封装在单个端口上传递的(通常作为 UDP 数据包)。例如,flannel 覆盖网络通常配置为通过端口 7890 上的 UDP 进行通信。
当使用原生 VPC 网络解决方案,比如amazon-vpc-cni-k8s
时,通常需要允许所有流量在节点之间传递。amazon-vpc-cni-k8s
插件将多个 Pod IP 地址与单个弹性网络接口关联起来,因此通常无法使用安全组以更精细的方式管理基础架构节点网络。
节点-主节点网络
在正常操作中,运行在您的节点上的 kubelet 需要连接到 Kubernetes API 以发现它预期运行的 Pod 的定义。
通常,这意味着允许工作节点向控制平面安全组的 443 端口进行 TCP 连接。
控制平面连接到暴露在端口 10250 上的 API 上的 kubelet。这对于logs
和exec
功能是必需的。
外部网络
正确理解外部集群允许访问节点的流量是保持集群安全的关键部分。
最近,一些研究人员发现了大量本来受到保护的集群,允许任何人在互联网上访问 Kubernetes 仪表板,从而访问集群本身。
通常,在这些情况下,集群管理员未能正确配置仪表板以对用户进行身份验证。但是,如果他们仔细考虑了向更广泛的互联网公开的服务,可能会避免这些违规行为。仅将这样的敏感服务暴露给特定 IP 地址或通过 VPN 访问您的 VPC 的用户,将提供额外的安全层。
当您想要将服务(或入口控制器)暴露给更广泛的互联网时,Kubernetes 负载均衡器服务类型将为您配置适当的安全组(以及提供弹性负载均衡器(ELB))。
Kubernetes 基础设施- pod 网络
Kubernetes 默认情况下不提供控制集群上运行的 pod 之间的网络访问的任何设施。集群上运行的任何 pod 都可以连接到任何其他 pod 或服务。
对于完全受信任的应用程序的较小部署来说,这可能是合理的。如果您想要提供策略来限制集群上运行的不同应用程序之间的连接,则需要部署一个网络插件,该插件将执行 Kubernetes 网络策略,例如 Calico、Romana 或 WeaveNet。
虽然有很多网络插件可用于支持 Kubernetes 网络策略的执行,但如果您选择使用 AWS 支持的原生 VPC 网络,建议使用 Calico,因为 AWS 支持此配置。AWS 提供了示例配置,以在其 GitHub 存储库中部署 Calico 与amazon-vpc-cni-k8s
插件:github.com/aws/amazon-vpc-cni-k8s
。
Kubernetes API 提供了NetworkPolicy
资源,以提供控制流量从 pod 进入和流出的策略。每个NetworkPolicy
都以标签选择器和命名空间为目标,影响它将影响的 pod。由于默认情况下 pod 没有网络隔离,如果您希望严格提供默认的NetworkPolicy
以阻止尚未提供特定网络策略的 pod 的流量,这可能是有用的。
请查看 Kubernetes 文档,了解一些默认网络策略的示例,以便默认情况下允许或拒绝所有流量:kubernetes.io/docs/concepts/services-networking/network-policies/#default-policies
。
IAM 角色
Kubernetes 与 AWS 有一些深度集成。这意味着 Kubernetes 可以执行诸如提供 EBS 卷并将其附加到集群中的 EC2 实例、设置 ELB 以及为您配置安全组等任务。
为了使 Kubernetes 具有执行这些操作所需的访问权限,您需要提供 IAM 凭据,以允许控制平面和节点获得所需的访问权限。
通常,最方便的方法是将与相关 IAM 角色关联的实例配置文件附加到实例上,以授予实例上运行的 Kubernetes 进程所需的权限。在第三章中,云端之手中,我们使用kubeadm
启动了一个小集群的示例。在规划生产集群时,您还应该考虑一些其他因素:
-
您是否运行多个集群? 您是否需要隔离集群资源?
-
您的集群上运行的应用程序是否还需要访问需要身份验证的 AWS 内部资源?
-
您的集群中的节点是否需要使用 AWS IAM Authenticator 对 Kubernetes API 进行身份验证?如果您正在使用 Amazon EKS,这也适用。
如果您在 AWS 账户中运行多个集群(例如,用于生产和暂存或开发环境),值得考虑如何定制 IAM 角色,以防止集群干扰彼此的资源。
理论上,一个集群不应该干扰另一个集群创建的资源,但您可能会重视每个环境单独提供的 IAM 角色所提供的额外安全性。在生产和开发或分段环境之间不共享 IAM 角色是一个良好的做法,可以防止一个环境中的配置错误(甚至是 Kubernetes 中的错误)对与另一个集群关联的资源造成伤害。Kubernetes 交互的大多数资源都带有kubernetes.io/cluster/<cluster name>
标签。对于其中一些资源,IAM 提供了将某些操作限制为与该标签匹配的资源的能力。以这种方式限制删除操作是减少潜在危害的一种方式。
当集群上运行的应用程序需要访问 AWS 资源时,有多种方法可以向 AWS 客户端库提供凭据,以便正确进行身份验证。您可以将凭据作为配置文件或环境变量挂载为秘密,然后提供给您的应用程序。但提供 IAM 凭据的最便捷的方法之一是使用与实例配置文件相同的机制将 IAM 角色与您的 pod 关联起来。
诸如kube2iam
或kiam
之类的工具拦截 AWS 客户端库对元数据服务的调用,并根据 pod 上设置的注释提供令牌。这允许 IAM 角色作为您正常部署过程的一部分进行分配。
kiam (github.com/uswitch/kiam
) 和 kube2iam (github.com/jtblin/kube2iam
) 是两个类似的项目,旨在为 Kubernetes pod 提供 IAM 凭据。这两个项目都作为每个节点上的代理运行,添加网络路由以路由到 AWS 元数据服务的流量。kiam 另外还运行一个负责从 AWS API 请求令牌并维护所有运行中 pod 所需凭据的缓存的中央服务器组件。这种方法在生产集群中被认为更可靠,并减少了节点代理所需的 IAM 权限。
使用这些工具之一的另一个优势是,它可以防止集群上运行的应用程序使用分配给底层实例的权限,从而减少了应用程序可能错误或恶意访问资源以提供控制平面服务的风险。
验证
在设置集群时,您可能会做出许多不同选择来配置您的集群。重要的是,您需要一种快速验证集群是否能够正确运行的方法。
这是 Kubernetes 社区为了证明不同的 Kubernetes 发行版是“一致的”而解决的问题。为了获得特定 Kubernetes 发行版的一致性认证,需要对集群运行一组集成测试。这些测试对于供应预打包的 Kubernetes 安装的供应商来证明其发行版是否正确运行非常有用。对于集群操作员来说,它也非常有用,可以快速验证软件更新或配置更改是否使集群处于可操作状态。
Kubernetes 一致性测试基于 Kubernetes 代码库中的一些特殊自动化测试。这些测试作为 Kubernetes 代码库端到端验证的一部分运行在测试集群上,并且在每次对代码库的更改合并之前必须通过。
当然,您可以下载 Kubernetes 代码库(并设置 Golang 开发环境)并配置它直接运行一致性测试。但是,有一个名为Sonobuoy的工具可以为您自动化这个过程。
Sonobuoy 可以简化在集群上以简单和标准化的方式运行一组 Kubernetes 一致性测试。使用 Sonobuoy 的最简单方法是使用托管的基于浏览器的服务scanner.heptio.com/
。该服务会提供一个清单供您提交到您的集群,然后在测试完成后显示测试结果。如果您想在自己的集群上运行所有内容,可以安装一个命令行工具,按照github.com/heptio/sonobuoy
上的说明运行测试并收集结果。
Kubernetes 一致性测试很重要,因为它涵盖了各种 Kubernetes 功能,可以在部署应用程序之前提前警告您是否存在任何配置错误。当您更改集群配置时,如果更改可能影响集群功能,这些测试会非常有帮助。
尽管 Kubernetes 一致性测试侧重于测试集群的功能,安全基准测试会检查集群的配置是否符合已知的不安全配置设置,确保集群配置符合当前的安全最佳实践。
互联网安全中心发布了逐步检查清单,您可以手动按照这些清单来测试集群是否符合安全最佳实践。
您可以免费下载这些基准测试的副本:www.cisecurity.org/benchmark/kubernetes/
。
在构建集群时阅读并遵循这些清单中的建议可能会很有用,因为它将帮助您理解特定配置值的原因。
一旦您设置好了集群,自动验证配置可能会很有用,以便在更新和更改时避免配置意外偏离安全配置。
kube-bench
是一个工具,它提供了一种自动运行 CIS 基准测试的方式:github.com/aquasecurity/kube-bench
。
您可能会发现编写自己的集成测试也很有用,这些测试可以检查您是否能成功部署和操作自己的一些应用程序。在快速开发集群配置时,这些测试可以作为一个重要的健全性检查。
有许多工具可以用来执行这样的测试。我建议使用您组织中的工程师已经熟悉的任何测试自动化工具。您可以使用专门设计用于运行自动化测试的工具,比如 cucumber,但是一个简单的 shell 脚本,部署一个应用程序到您的集群,然后检查它是否可访问,也是一个很好的开始。
可观测性
可观测性显示在以下图表中:
能够监视和调试集群是设计生产集群时最重要的要点之一。幸运的是,有许多解决方案可以很好地支持 Kubernetes 的日志和指标管理。
日志记录
每当您想要了解您的应用程序在做什么时,大多数运维人员首先想到的是查看应用程序生成的日志。
日志很容易理解,而且不需要任何特殊工具来生成,因为您的应用程序可能已经支持某种形式的日志记录。
在 Kubernetes 中,您可以直接查看和追踪应用程序写入标准输出和标准错误的日志。如果您在自己的计算机或服务器上使用过docker logs
命令,那么使用kubectl logs
命令应该对您来说很熟悉。
这比登录每个节点查看特定容器生成的日志更方便。除了查看特定 pod 的日志外,kubectl logs
还可以显示与特定标签表达式匹配的所有 pod 的日志。
如果您需要搜索应用程序生成的日志以查找特定事件,或者如果您需要查看过去特定时间生成的日志,那么您需要考虑部署一个解决方案来聚合和管理您的日志。
实现此功能的最常用工具是Fluentd。Fluentd 是一个非常灵活的工具,可以用于从各种来源收集日志,然后将其推送到一个或多个目的地。如果您的组织已经维护或使用第三方工具来聚合应用程序日志,您几乎肯定会找到一种方法来配置 Fluentd 以将运行在 Kubernetes 上的应用程序的应用程序日志存储在您选择的工具中。Fluentd 团队和更广泛的社区维护着超过 800 个不同的插件,支持许多不同的输入、输出和过滤选项。
由于 Fluentd 是基于 Ruby 编程语言构建的,它的插件使用 Rubygems 软件包系统进行分发。按照惯例,所有 Fluentd 插件的名称都以fluent-plugin开头,并且当前所有可用的插件都在此处列出:www.fluentd.org/plugins/all
。由于其中一些插件是由更广泛的社区维护的,因此值得对您计划使用的插件进行一些初始测试。插件的质量可能有所不同,这取决于特定插件所处的开发阶段以及维护频率。您可以使用gem install
命令安装和管理 Fluentd 插件,或者使用bundler工具控制 Fluentd 插件的确切版本。您可以在此处阅读有关在 Fluentd 安装中安装插件的更多信息:docs.fluentd.org/v1.0/articles/plugin-management
。
监控
查看应用程序的日志输出可能是有用的,如果您知道应用程序存在问题并希望调试原因。但是,如果您不知道系统中出现问题的位置,或者只是想评估系统的健康状况,那么这将变得更加困难。
您的日志非常灵活,因为您的应用程序可以以非结构化的方式向日志端点写入任何信息。在大型系统中,这可能会变得非常压倒,以及需要过滤和分析此输出的工作量可能会变得复杂。
监控或指标收集采取了不同的方法。通过定义反映系统、Kubernetes 和基础设施的性能和运行情况的测量,您可以更快地回答有关系统健康和性能的问题。
收集的指标也是自动警报系统中最有用的信息源之一。它们可以警告您的组织成员有关应用程序或基础设施异常行为。
有许多商业和开源工具可用于收集指标并创建警报。您所做的决定很可能会受到您的组织和您的要求的影响。
正如我已经说过的,试图一次向您的组织引入太多新工具或流程可能会冒险。在许多情况下,许多监控工具已经支持与 Kubernetes 集成。如果是这种情况,考虑继续使用您的组织习惯使用的现有工具可能是明智的。
无论您选择哪些工具来记录应用程序、集群和基础设施的指标,您都应该仔细考虑如何使负责开发和部署应用程序的组织成员能够轻松地展示他们的指标。作为规划集群的一部分,尝试编写公开指标的流程文档,该文档应该由部署新应用程序到您的集群的开发人员遵循。您应该尽量使这个过程尽可能简单。如果需要自动化流程的步骤并提供默认配置值,您应该这样做以使流程简单化。如果从应用程序中导出新指标的过程复杂或需要大量手动步骤,那么您的组织的应用程序暴露它们的可能性就会降低。
如果流程简单且无摩擦,那么通过默认监控文化变得更加简单。例如,如果您选择使用 Prometheus,您可以像这样记录流程:
-
- 在端口
9102
上暴露一个端点/metrics
- 在端口
-
向您的 pod 添加注释
"prometheus.io/scrape": true
在这个例子中,通过配置具有合理默认值的 Prometheus,从 pod 中暴露指标对于开发人员来说变得快速简单。可以暴露更复杂的配置方式,以便 Prometheus 抓取指标,但是通过使用众所周知的默认值,可以使设置过程更简单,并且可以更容易地在应用程序中包含标准的 Prometheus 库。无论您选择使用哪种系统来收集指标,尽量在可能的情况下遵循这些原则。
直接从应用程序 pod 和基础设施收集指标可以提供有关应用程序行为的深入丰富的信息。当您需要了解应用程序的具体信息时,这些信息非常有用,并且在预防问题方面非常有用。例如,关于磁盘使用情况的指标可以用于提供警报,警告操作员有可能导致应用程序失败的状态。
黑匣子监控
虽然特定于应用程序的指标提供了有用的根本原因分析和预警洞察,但黑匣子监控采取了相反的方法。通过将应用程序视为封闭实体,并执行面向用户的端点,您可以展现性能不佳的应用程序的症状。黑匣子监控可以通过使用诸如 Prometheus Blackbox 导出器之类的工具来实现。但另一个常见的模式是使用商业服务。其主要优势在于它们通常允许您从多个位置(也许是全球范围内)探测应用程序,真正地在用户和应用程序之间的完整基础设施堆栈上进行探测。
警报
记录关于在 Kubernetes 上运行的系统状态的指标是使您的系统易于观察的第一阶段。收集了指标之后,有几种方法可以使您收集的数据易于采取行动。
大多数指标收集工具都提供了一些方法来为组织中不同成员重要的指标构建图形和仪表板。例如,许多 Prometheus 用户使用 Grafana 构建仪表板来公开重要的指标。
虽然仪表板是了解特定系统或业务流程的表现的好方法,但你的系统有一些方面需要更主动的方法。
任何值得一试的度量收集系统都会提供一种向组织成员发出警报的方式。然而,当你收集度量和使用任何系统向团队发送警报时,有一些原则你应该考虑:
-
警报应该是可执行的:当将仪表板上的图表或仪表上的度量提升为警报时,确保只对需要立即人工干预的状态发送警报,而不仅仅是警告或信息。警告或信息性警报应该出现在你的仪表板上,而不是在你的寻呼机上。
-
警报应该节制使用:警报会打断人们当前正在做的事情:工作、休息,甚至最糟糕的是睡觉。如果一个人收到太多的警报,它们可能会成为压力的原因,并且在警报疲劳设置并且失去吸引注意力的力量。在设计警报机制时,你应该考虑记录你的组织成员被你的警报打断的频率。
警报应该是有针对性的——你应该考虑谁应该对特定的警报负责并适当地指导它。警报可以指向多个系统,如 bug 跟踪器、电子邮件、聊天系统,甚至寻呼应用程序。重要的是,接收组织中最关键的警报的人能够承担责任并管理响应。不太重要的警报可能会分配给 bug 跟踪工具中的一个团队或组。如果你的组织使用聊天系统,如 Slack、HipChat 或 IRC,你可能希望将特定应用程序的警报指向团队使用的频道或房间,该团队开发或负责该应用程序的运行。只需记住确保保持在可接受的水平,否则你的警报很快就会被需要知道它们的人忽视。
追踪
追踪是可观察性家族中最年轻的成员,因此通常是组织选择实施的最后一个。追踪系统的理念是测量单个请求通过你的应用程序所需的时间。
这可能不会暴露比为单体应用程序配置良好的指标更有趣的信息。但对于具有分布式或微服务架构的大规模系统,其中单个请求可能通过数十甚至数百个独立进程,跟踪可以帮助准确定位性能问题发生的时间和地点。
在实施从应用程序收集跟踪信息的系统时,您有多种选择。
AWS 的内置跟踪解决方案包括 X-Ray,支持 Java、Go、Node.js、Python、Ruby 和.NET 应用程序。对于这些技术,向您的应用程序添加分布式跟踪只是向应用程序添加库并正确配置的问题。aws.amazon.com/xray/
。
与 AWS 的解决方案竞争的是一些旨在在 OpenTracing 旗帜下共同工作的工具。
OpenTracing 为九种语言提供了客户端库,这些库与九种不同的开源和商业工具兼容,旨在收集跟踪数据。由于 OpenTracing 的开放性质,一些应用程序框架和基础设施组件选择添加对其跟踪格式的支持。您可以在opentracing.io
了解更多关于 OpenTracing 的信息。
总结
本章希望能让您了解在决定在生产环境中运行 Kubernetes 时,您可以做出的多种不同选项和决策。不要因为选项和选择的深度和广度而却步,因为 Kubernetes 非常容易上手,特别是在 AWS 上。
在下一章中,我们将开始实际工作,设置集群并准备开始工作。我们不可能涵盖所有选项,更不用说 Kubernetes 周围社区制作的所有附加组件和附加工具,但我们将提供一个稳定的起点,让您可以开始实施自己的计划。
希望本章能为您和您的团队提供一个指南,讨论和规划满足您组织需求的集群。然后,您可以开始实施在阅读本章时可能确定的功能和功能。
如果在启动自己的集群时有一件事要记住:保持简单,傻瓜。 Kubernetes 使您能够在需要时轻松向您的工具库中添加新工具,因此不要过于复杂或过快地过度设计。从可能起作用的最简单设置开始,即使您认为需要稍后添加复杂性;通常,您会发现简单的解决方案完全有效。
利用 Kubernetes 本身的优势,它将允许您快速发展基础设施,从小处开始,需要时再添加功能和工具到系统中,而不是提前添加!
第七章:一个适合生产的集群
在上一章中,我们花了一些时间思考规划 Kubernetes 集群的框架。希望对你来说应该很清楚,构建集群时需要根据正在运行的系统的要求做出许多决策。
在本章中,我们将采取更加实际的方法来解决这个问题。我们将不再试图涵盖我们可以使用的众多选项,而是首先做出一些选择,然后构建一个完全功能的集群,作为许多不同用例的基础配置。
在本章中,我们将涵盖以下主题:
-
Terraform
-
准备节点镜像和节点组
-
配置附加组件
构建一个集群
本章中包含的信息只是构建和管理集群的一种可能方式。构建 Kubernetes 集群时,有许多选择要做,几乎可以选择同样多的工具。出于本章的目的,我选择使用可以简单说明构建集群过程的工具。如果您或您的团队更喜欢使用不同的工具,那么本章中概述的概念和架构将很容易转移到其他工具上。
在这一章中,我们将以一种更适合生产工作负载的方式启动我们的集群。我们在这里所做的大部分工作都将与第三章“云端之手”中的内容相似,但我们将在两个关键方面进一步完善我们在那里概述的流程。首先,在构建依赖的基础设施时,能够快速部署新的基础设施实例是非常重要的,而且要能够重复进行。我们希望能够做到这一点,因为这样可以简单地测试我们想要对基础设施进行的变更,而且是无风险的。通过自动化 Kubernetes 集群的配置,我们实现了之前讨论过的不可变基础设施模式。我们可以快速部署一个替代集群,然后在将工作负载迁移到新集群之前进行测试,而不是冒险升级或更改我们的生产基础设施。
为了实现这一点,我们将使用 Terraform 基础设施配置工具与 AWS 进行交互。Terraform 允许我们使用一种类似编程语言的方式将我们的基础设施定义为代码。通过将基础设施定义为代码,我们能够使用诸如版本控制之类的工具,并遵循其他软件开发实践来管理我们基础设施的演变。
在本章中,我们将做出许多关于在 AWS 上运行的 Kubernetes 集群应该是什么样子以及如何管理的决定。对于本章和我们将要处理的示例,我心中有以下要求。
-
说明性:我们将看到符合平均生产用例要求的 Kubernetes 集群是什么样子。这个集群反映了我在设计用于真实生产的 Kubernetes 集群时所做的决定。为了使本章尽可能清晰易懂,我尽量保持集群及其配置尽可能简单。
-
灵活性:我们将创建一些您可以视为模板并添加或更改以满足您需求的东西。
-
可扩展性:无论您设计 Kubernetes 集群(或者任何基础设施),都应该考虑您现在所做的决定可能会阻止您以后扩展或扩展该基础设施。
显然,当您构建自己的集群时,您应该对自己的需求有一个更加具体的想法,这样您就能够根据自己的需求定制您的集群。我们将在这里构建的集群将是任何生产就绪系统的绝佳起点,然后您可以根据需要自定义和添加。
本章中的许多配置都已经被缩短了。您可以在github.com/PacktPublishing/Kubernetes-on-AWS/tree/master/chapter07
查看本章中使用的完整配置。
开始使用 Terraform
Terraform 是一个命令行工具,您可以在工作站上运行它来对基础设施进行更改。Terraform 是一个单一的二进制文件,只需安装到您的路径上即可。
您可以从www.terraform.io/downloads.html
下载 Terraform,支持六种不同的操作系统,包括 macOS、Windows 和 Linux。下载适用于您操作系统的 ZIP 文件,解压缩,然后将 Terraform 二进制文件复制到您的路径上。
Terraform 使用扩展名为.tf
的文件来描述您的基础架构。因为 Terraform 支持在许多不同的云平台上管理资源,它可以包含相关提供者的概念,这些提供者根据需要加载,以支持不同云提供商提供的不同 API。
首先,让我们配置 AWS Terraform 提供者,以便准备构建一个 Kubernetes 集群。创建一个新目录来保存 Kubernetes 集群的 Terraform 配置,然后创建一个文件,在其中我们将配置 AWS 提供者,如下所示的代码:
aws.tf
provider "aws" {
version = "~> 1.0"
region = "us-west-2"
}
保存文件,然后运行以下命令:
terraform.init
Initializing provider plugins...
- Checking for available provider plugins on https://releases.hashicorp.com...
- Downloading plugin for provider "aws" (1.33.0)...
Terraform has been successfully initialized!
当您使用支持的提供者时,Terraform 可以发现并下载所需的插件。请注意,我们已经配置了提供者的 AWS 区域为us-west-2
,因为这是我们在本例中将要启动集群的区域。
为了让 Terraform 与 AWS API 通信,您需要为 AWS 提供者提供一些凭据。我们在第三章中学习了如何获取凭据,云的探索。如果您遵循了第三章中的建议,并使用aws configure
命令设置了您的凭据,那么 Terraform 将从您的本地配置文件中读取默认凭据。
另外,Terraform 可以从AWS_ACCESS_KEY_ID
和AWS_SECRET_ACCESS_KEY
环境变量中读取 AWS 凭据,或者如果您在 EC2 实例上运行 Terraform,它可以使用 EC2 实例角色提供的凭据。
也可以通过在 AWS 提供者块中内联添加access_key
和secret_key
参数来静态配置凭据,但我并不真的推荐这种做法,因为这样会使得将配置检入版本控制系统变得更加困难。
默认情况下,Terraform 使用名为terraform.tfstate
的本地文件来跟踪您基础架构的状态。这样可以跟踪自上次运行 Terraform 以来对配置所做的更改。
如果你将是唯一管理基础架构的人,那么这可能是可以接受的,但你需要安全地备份状态文件。它应被视为敏感信息,如果丢失,Terraform 将无法正常运行。
如果您正在使用 AWS,我建议使用 S3 作为后端。您可以在 Terraform 文档中阅读如何设置这一点,网址为www.terraform.io/docs/backends/types/s3.html
。如果配置正确,S3 存储是非常安全的,如果您正在团队中工作,那么您可以利用 DynamoDB 表作为锁,以确保多个 Terraform 实例不会同时运行。如果您想使用这个功能,请在backend.tf
文件中设置配置,否则删除该文件。
变量
Terraform 允许我们定义变量,以使我们的配置更具重用性。如果以后您想要将您的配置用作模块来定义多个集群,这将特别有用。我们不会在本章中涵盖这一点,但我们可以遵循最佳实践并定义一些关键变量,以便您可以简单地塑造集群以满足您的需求。
创建一个variables.tf
文件来包含项目中的所有变量是标准的。这很有帮助,因为它作为关于如何控制您的配置的高级文档。
正如你所看到的,选择变量的描述性名称并添加可选的描述字段之间,整个文件都相当自解释。因为我为每个变量提供了默认值,所以我们可以在不传递这些变量的任何值的情况下运行 Terraform,如下面的代码所示:
variables.tf
variable "cluster_name" {
default = "lovelace"
}
variable "vpc_cidr" {
default = "10.1.0.0/16"
description = "The CIDR of the VPC created for this cluster"
}
variable "availability_zones" {
default = ["us-west-2a","us-west-2b"]
description = "The availability zones to run the cluster in"
}
variable "k8s_version" {
default = "1.10"
description = "The version of Kubernetes to use"
}
网络
我们将首先创建一个配置文件来描述我们的 Kubernetes 集群的网络设置。您可能会注意到这个网络的设计,因为它与我们在第三章中手动创建的网络非常相似,但是增加了一些内容,使其更适合生产环境。
Terraform 配置文件可以用注释进行文档化,为了更好地说明这个配置,我提供了一些注释形式的评论。你会注意到它们被/*
和*/
包围着。
为了支持高可用性,我们将为多个可用区域创建子网,如下面的代码所示。在这里,我们使用了两个,但如果您想要更高的弹性,您可以轻松地将另一个可用区域添加到availability_zones
变量中:
networking.tf
/* Set up a VPC for our cluster.
*/resource "aws_vpc" "k8s" {
cidr_block = "${var.vpc_cidr}"
enable_dns_hostnames = true
tags = "${
map(
"Name", "${var.cluster_name}",
"kubernetes.io/cluster/${var.cluster_name}", "shared",
)
}"
}
/* In order for our instances to connect to the internet
we provision an internet gateway.*/
resource "aws_internet_gateway" "gateway" {
vpc_id = "${aws_vpc.k8s.id}"
tags {
Name = "${var.cluster_name}"
}
}
/* For instances without a Public IP address we will route traffic
through a NAT Gateway. Setup an Elastic IP and attach it.
We are only setting up a single NAT gateway, for simplicity.
If the availability is important you might add another in a
second availability zone.
*/
resource "aws_eip" "nat" {
vpc = true
depends_on = ["aws_internet_gateway.gateway"]
}
resource "aws_nat_gateway" "nat_gateway" {
allocation_id = "${aws_eip.nat.id}"
subnet_id = "${aws_subnet.public.*.id[0]}"
}
我们将为我们集群使用的每个可用区域提供两个子网。一个公共子网,它可以直接连接到互联网,Kubernetes 将在其中提供可供互联网访问的负载均衡器。还有一个私有子网,Kubernetes 将用它来分配给 pod 的 IP 地址。
因为私有子网中可用的地址空间将是 Kubernetes 能够启动的 pod 数量的限制因素,所以我们为其提供了一个大的地址范围,其中有 16382 个可用的 IP 地址。这应该为我们的集群提供一些扩展空间。
如果您只打算运行对外部不可访问的内部服务,那么您可能可以跳过公共子网。您可以在本章的示例文件中找到完整的networking.tf
文件。
计划和应用
Terraform 允许我们通过添加和更改定义基础设施的代码来逐步构建我们的基础设施。然后,如果您希望,在阅读本章时,您可以逐步构建您的配置,或者您可以使用 Terraform 一次性构建整个集群。
每当您使用 Terraform 对基础设施进行更改时,它首先会生成一个将要进行的更改的计划,然后应用这个计划。这种两阶段的操作在修改生产基础设施时是理想的,因为它可以让您在实际应用到集群之前审查将要应用的更改。
一旦您将网络配置保存到文件中,我们可以按照一些步骤安全地为我们的基础设施提供。
我们可以通过运行以下命令来检查配置中的语法错误:
terraform validate
如果您的配置正确,那么不会有输出,但如果您的文件存在语法错误,您应该会看到一个解释问题的错误消息。例如,缺少闭合括号可能会导致错误,如Error parsing networking.tf: object expected closing RBRACE got: EOF
。
一旦您确保您的文件已正确格式化为 Terraform,您可以使用以下命令为您的基础设施创建变更计划:
terraform plan -out k8s.plan
该命令将输出一个摘要,显示如果运行此计划将对基础架构进行的更改。-out
标志是可选的,但这是一个好主意,因为它允许我们稍后应用这些更改。如果您在运行 Terraform 计划时注意输出,那么您应该已经看到了这样的消息:
To perform exactly these actions, run the following command to apply:
terraform apply "k8s.plan"
当您使用预先计算的计划运行terraform apply
时,它将进行在生成计划时概述的更改。您也可以运行terraform plan
命令而不预先生成计划,但在这种情况下,它仍将计划更改,然后在应用更改之前提示您。
Terraform 计算基础架构中不同资源之间的依赖关系,例如,它确保在创建路由表和其他资源之前先创建 VPC。一些资源可能需要几秒钟才能创建,但 Terraform 会等待它们可用后再继续创建依赖资源。
如果您想删除 Terraform 在您的 AWS 帐户中创建的资源,只需从相关的.tf
文件中删除定义,然后计划并应用您的更改。当您测试 Terraform 配置时,删除特定配置创建的所有资源可能很有用,以便测试从头开始配置基础架构。如果需要这样做,terraform destroy
命令非常有用;它将从基础架构中删除在 Terraform 文件中定义的所有资源。但是,请注意,这可能导致关键资源被终止和删除,因此您不应该在运行中的生产系统上使用此方法。在删除任何资源之前,Terraform 将列出它们,然后询问您是否要删除它们。
控制平面
为了为我们的集群提供一个弹性和可靠的 Kubernetes 控制平面,我们将首次大幅偏离我们在第三章中构建的简单集群,云端的追求。
正如我们在第一章中所学到的,“谷歌的基础设施服务于我们其他人”,Kubernetes 控制平面的关键组件是支持 etcd 存储、API 服务器、调度程序和控制器管理器。如果我们想要构建和管理一个弹性的控制平面,我们需要跨多个实例管理这些组件,最好分布在多个可用区。
由于 API 服务器是无状态的,并且调度程序和控制器管理器具有内置的领导者选举功能,因此在 AWS 上运行多个实例相对简单,例如,通过使用自动扩展组。
运行生产级别的 etcd 略微棘手,因为在添加或删除节点时,应小心管理 etcd 以避免数据丢失和停机。在 AWS 上成功运行 etcd 集群是一项相当困难的任务,需要手动操作或复杂的自动化。
幸运的是,AWS 开发了一项服务,几乎消除了在配置 Kubernetes 控制平面时涉及的所有操作复杂性——Amazon EKS,或者使用全名,亚马逊弹性容器服务用于 Kubernetes。
通过 EKS,AWS 将代表您在多个可用区管理和运行组成 Kubernetes 控制平面的组件,从而避免任何单点故障。有了 EKS,您不再需要担心执行或自动化运行稳定 etcd 集群所需的操作任务。
我们应该牢记,使用 EKS 时,我们集群基础设施的关键部分现在由第三方管理。您应该对 AWS 能够比您自己的团队更好地提供弹性控制平面感到满意。这并不排除您设计集群以在控制平面故障时具有一定的抗性的可能性——例如,如果 kubelet 无法连接到控制平面,那么正在运行的容器将保持运行,直到控制平面再次可用。您应该确保您添加到集群中的任何其他组件都能以类似的方式应对临时停机。
EKS 减少了管理 Kubernetes 最复杂部分(控制平面)所需的工作量,从而减少了设计集群和维护集群所需的时间(和金钱)。此外,即使是规模适中的集群,EKS 服务的成本也明显低于在多个 EC2 实例上运行自己的控制平面的成本。
为了让 Kubernetes 控制平面管理 AWS 账户中的资源,你需要为 EKS 提供一个 IAM 角色,EKS 本身将扮演这个角色。
EKS 在你的 VPC 中创建网络接口,以允许 Kubernetes 控制平面与 kubelet 通信,从而提供日志流和执行等服务。为了控制这种通信,我们需要在启动时为 EKS 提供一个安全组。你可以在本章的示例文件中的control_plane.tf
中找到用于配置控制平面的完整 Terraform 配置。
我们可以使用 Terraform 资源来查询 EKS 集群,以获取用于访问 Kubernetes API 的端点和证书颁发机构。
这些信息,结合 Terraform 的模板功能,允许我们生成一个kubeconfig
文件,其中包含连接到 EKS 提供的 Kubernetes API 所需的信息。我们以后可以使用这个文件来配置附加组件。
如果你愿意,你也可以使用这个文件手动连接到集群,使用 kubectl 命令,可以通过将文件复制到默认位置~/.kube/config
,或者通过--kubeconfig
标志或KUBECONFIG
环境变量传递其位置给 kubectl,如下面的代码所示:
KUBECONFIG
环境变量在管理多个集群时非常有用,因为你可以通过分隔它们的路径轻松加载多个配置;例如:
将 KUBECONFIG 环境变量设置为$HOME/.kube/config:/path/to/other/conf
。
kubeconfig.tpl
apiVersion: v1
kind: Config
clusters:
- name: ${cluster_name}
cluster:
certificate-authority-data: ${ca_data}
server: ${endpoint}
users:
- name: ${cluster_name}
user:
exec:
apiVersion: client.authentication.k8s.io/v1alpha1
command: aws-iam-authenticator
args:
- "token"
- "-i"
- "${cluster_name}"
contexts:
- name: ${cluster_name}
context:
cluster: ${cluster_name}
user: ${cluster_name}
current-context: ${cluster_name}
准备节点镜像
就像我们在第三章中所做的那样,云端之手,我们现在将为集群中的工作节点准备一个 AMI。但是,我们将通过Packer自动化这个过程。Packer 是一个在 AWS(和其他平台)上构建机器镜像的简单工具。
安装 Packer
就像 Terraform 一样,Packer 被分发为一个单一的二进制文件,只需将其复制到您的路径上。您可以在 Packer 网站上找到详细的安装说明www.packer.io/intro/getting-started/install.html
。
安装了 Packer 之后,您可以运行packer version
来检查您是否已经正确地将其复制到您的路径上。
Packer 配置
Packer 配置为一个 JSON 格式的配置文件,您可以在ami/node.json
中看到。
这里的示例配置有三个部分。第一个是变量列表。在这里,我们使用变量来存储我们将在镜像中安装的重要软件的版本号。这将使得在将来可用时,构建和测试具有更新版本的 Kubernetes 软件的镜像变得简单。
配置的第二部分配置了构建器。Packer 允许我们选择使用一个或多个构建器来构建支持不同云提供商的镜像。由于我们想要构建一个用于 AWS 的镜像,我们使用了amazon-ebs
构建器,它通过启动临时 EC2 实例然后从其根 EBS 卷的内容创建 AMI 来创建镜像(就像我们在第三章中遵循的手动过程,云的到来)。这个构建器配置允许我们选择我们的机器将基于的基础镜像;在这里,我们使用了官方的 Ubuntu 服务器镜像,一个可信的来源。构建器配置中的ami-name
字段定义了输出镜像将被赋予的名称。我们已经包含了所使用的 Kubernetes 软件的版本和时间戳,以确保这个镜像名称是唯一的。拥有唯一的镜像名称让我们能够精确地定义在部署服务器时使用哪个镜像。
最后,我们配置了一个 provisioner 来安装我们的镜像所需的软件。Packer 支持许多不同的 provisioners,可以安装软件,包括诸如 Chef 或 Ansible 之类的完整配置管理系统。为了保持这个示例简单,我们将使用一个 shell 脚本来自动安装我们需要的软件。Packer 将上传配置的脚本到构建实例,然后通过 SSH 执行它。
我们只是使用一个简单的 shell 脚本,但如果您的组织已经在使用配置管理工具,那么您可能更喜欢使用它来安装您的镜像所需的软件,特别是因为它可以简单地包含您组织的基本配置。
在这个脚本中,我们正在安装我们的工作节点将需要加入 EKS 集群并正确运行的软件和配置,如下面的列表所示。在实际部署中,可能还有其他工具和配置,您希望除了这些之外添加。
-
Docker:Docker 目前是与 Kubernetes 一起使用的经过最全面测试和最常见的容器运行时
-
kubelet:Kubernetes 节点代理
-
ekstrap:配置 kubelet 连接到 EKS 集群端点
-
aws-iam-authenticator:允许节点使用节点的 IAM 凭据与 EKS 集群进行身份验证
我们使用以下代码安装这些元素:
install.sh
#!/bin/bash
set -euxo pipefail
...
# Install aws-iam-authenticator
curl -Lo /usr/local/bin/heptio-authenticator-aws https://github.com/kubernetes-sigs/aws-iam-authenticator/releases/download/v0.3.0/heptio-authenticator-aws_0.3.0_linux_amd64
chmod +x /usr/local/bin/heptio-authenticator-aws
apt-get install -y \
docker-ce=$DOCKER_VERSION* \
kubelet=$K8S_VERSION* \
ekstrap=$EKSTRAP_VERSION*
# Cleanup
apt-get clean
rm -rf /tmp/*
# Cleanup
apt-get clean
rm -rf /tmp/*
一旦您为 Packer 准备好配置,您可以使用packer build
命令在您的 AWS 账户中构建 AMI,如下面的代码所示。这将启动一个临时的 EC2 实例。将新的 AMI 保存到您的账户中,并清理临时实例:
packer build node.json
如果您的组织使用持续集成服务,您可能希望配置它以便定期构建您的节点镜像,以便获取基本操作系统的安全更新。
节点组
现在我们已经为集群中的工作节点准备好了一个镜像,我们可以设置一个自动扩展组来管理启动 EC2 实例,这些实例将组成我们的集群。
EKS 不会限制我们以任何特定的方式管理我们的节点,因此自动扩展组并不是管理集群中节点的唯一选项,但使用它们是管理集群中多个工作实例的最简单方式之一。
如果您想在集群中使用多种实例类型,您可以为您想要使用的每种实例类型重复启动配置和自动扩展组配置。在这个配置中,我们正在按需启动c5.large
实例,但您应该参考第六章 生产规划,了解有关为您的集群选择适当实例大小的更多信息。
配置的第一部分设置了我们的实例要使用的 IAM 角色。这很简单,因为 AWS 提供了托管策略,这些策略具有 Kubernetes 所需的权限。AmazonEKSWorkerNodePolicy
代码短语允许 kubelet 查询有关 EC2 实例、附加卷和网络设置的信息,并查询有关 EKS 集群的信息。AmazonEKS_CNI_Policy
提供了vpc-cni-k8s
网络插件所需的权限,以将网络接口附加到实例并为这些接口分配新的 IP 地址。AmazonEC2ContainerRegistryReadOnly
策略允许实例从 AWS Elastic Container Registry 中拉取 Docker 镜像(您可以在第十章中了解更多关于使用此功能的信息,管理容器镜像)。我们还将手动指定一个策略,允许kube2iam
工具假定角色,以便为在集群上运行的应用程序提供凭据,如下面的代码所示:
nodes.tf
/*
IAM policy for nodes
*/
data "aws_iam_policy_document" "node" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
...
resource "aws_iam_instance_profile" "node" {
name = "${aws_iam_role.node.name}"
role = "${aws_iam_role.node.name}"
}
我们的工作节点在能够向 Kubernetes API 服务器注册之前,需要具有正确的权限。在 EKS 中,IAM 角色和用户之间的映射是通过向集群提交配置映射来配置的。
您可以在 EKS 文档中阅读有关如何将 IAM 用户和角色映射到 Kubernetes 权限的更多信息,网址为docs.aws.amazon.com/eks/latest/userguide/add-user-role.html
。
Terraform 将使用我们在设置控制平面时生成的kubeconfig
文件,通过 local-exec provisioner 使用kubectl
将此配置提交到集群,如下面的nodes.tf
继续的代码所示:
/*
This config map configures which IAM roles should be trusted by Kubernetes
*/
resource "local_file" "aws_auth" {
content = <<YAML
apiVersion: v1
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
data:
mapRoles: |
- rolearn: ${aws_iam_role.node.arn}
username: system:node:{{EC2PrivateDNSName}}
groups:
- system:bootstrappers
- system:nodes
YAML
filename = "${path.module}/aws-auth-cm.yaml"
depends_on = ["local_file.kubeconfig"]
provisioner "local-exec" {
command = "kubectl --kubeconfig=${local_file.kubeconfig.filename} apply -f ${path.module}/aws-auth-cm.yaml"
}
}
接下来,我们需要准备安全组来控制与我们的节点之间的网络流量。
我们将设置一些规则,以允许以下通信流,这些流对于我们的集群能够正常运行是必需的:
-
节点需要相互通信,用于集群内的 pod 和服务通信。
-
运行在节点上的 Kubelet 需要连接到 Kubernetes API 服务器,以便读取和更新有关集群状态的信息。
-
控制平面需要连接到端口
10250
上的 Kubelet API;这用于功能,如kubectl exec
和kubectl logs
。 -
为了使用 API 的代理功能将流量代理到 pod 和服务,控制平面需要连接到在集群中运行的 pod。在这个例子中,我们打开了所有端口,但是,例如,如果您只在您的 pod 上打开了非特权端口,那么您只需要允许流量到 1024 以上的端口。
我们使用以下代码设置这些规则。nodes.tf
的代码如下:
resource "aws_security_group" "nodes" {
name = "${var.cluster_name}-nodes"
description = "Security group for all nodes in the cluster"
vpc_id = "${aws_vpc.k8s.id}"
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
...
resource "aws_security_group_rule" "nodes-control_plane-proxy" {
description = "API (proxy) communication to pods"
from_port = 0
to_port = 65535
protocol = "tcp"
security_group_id = "${aws_security_group.nodes.id}"
source_security_group_id = \
"${aws_security_group.control_plane.id}"
type = "ingress"
}
现在我们已经准备好运行我们的节点的基础设施,我们可以准备一个启动配置并将其分配给一个自动扩展组,以实际启动我们的节点,如下面的代码所示。
显然,我在这里选择的实例类型和磁盘大小可能不适合您的集群,因此在选择集群实例大小时,您需要参考第六章中的信息,即生产规划。所需的磁盘大小将在很大程度上取决于您的应用程序的平均镜像大小。nodes.tf
的代码如下:
data "aws_ami" "eks-worker" {
filter {
name = "name"
values = ["eks-worker-${var.k8s_version}*"]
}
most_recent = true
owners = ["self"]
}
...
resource "aws_autoscaling_group" "node" {
launch_configuration = "${aws_launch_configuration.node.id}"
max_size = 2
min_size = 10
name = "eks-node-${var.cluster_name}"
vpc_zone_identifier = ["${aws_subnet.private.*.id}"]
tag {
key = "Name"
value = "eks-node-${var.cluster_name}"
propagate_at_launch = true
}
tag {
key = "kubernetes.io/cluster/${var.cluster_name}"
value = "owned"
propagate_at_launch = true
}
}
kubernetes.io/cluster/<node name>
标签被ekstrap
工具用来发现 EKS 端点以注册节点到集群,并被kubelet
用来验证它是否已连接到正确的集群。
配置附加组件
Kubernetes 的许多功能来自于它易于通过添加额外的服务来扩展以提供额外的功能。
我们将通过部署kube2iam
来看一个例子。这是一个守护程序,在我们的集群中的每个节点上运行,并拦截由我们的 pod 中运行的进程发出的对 AWS 元数据服务的调用。
通过使用 DaemonSet 在集群中的每个节点上运行一个 pod 来为这样的服务提供服务是一个简单的方法,如下面的代码所示。这种方法已经在我们的集群中用于将aws-vpc-cni
网络插件部署到每个节点,并运行kube-proxy
,这是运行在每个节点上的 Kubernetes 组件,负责将流向服务 IP 的流量路由到底层 pod:
kube2iam.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: kube2iam
namespace: kube-system
---
apiVersion: v1
kind: List
items:
... kube2iam.tf
resource "null_resource" "kube2iam" {
triggers = {
manifest_sha1 = "${sha1(file("${path.module}/kube2iam.yaml"))}"
}
provisioner "local-exec" {
command = " kubectl --kubeconfig=${local_file.kubeconfig.filename} apply -f
${path.module}/kube2iam.yaml"
}
}
变革管理
使用像 Terraform 这样的工具来管理您的 Kubernetes 集群比我们在第三章中探索的手动方法具有许多优势,云的选择。当您想要测试对配置的更改,甚至当您要升级集群正在运行的 Kubernetes 版本时,能够快速轻松地重复配置集群的过程非常有用。
将基础设施定义为代码的另一个关键优势是,您可以使用版本控制工具随着时间的推移跟踪对基础设施所做的更改。其中一个关键优势是,每次进行更改时,您都可以留下提交消息。您现在做出的决定可能看起来很明显,但记录为什么以某种方式选择做某事将肯定有助于您和其他人在将来与您的配置一起工作,特别是因为那些其他人可能没有在您进行更改时拥有相同的上下文。
许多软件工程师已经写了很多关于写好提交消息的东西。最好的建议是确保您包含尽可能多的信息来解释为什么需要进行更改。如果您需要返回到配置几个月后,您未来的自己会感谢您。
考虑这个提交消息:
Update K8s Node Security Groups
Open port 80 on the Node Security Group
还要考虑这个提交消息:
Allow deveopers to access the guestbook app
The guestbook is served from port 80\. We are allowing the control plane access to this port on the Node security groups, so developers can test the application using kubectl proxy.
Once the application is in production and we provision a LoadBalancer, we can remove these rules.
第一个提交消息很糟糕,因为它只是解释了你做了什么,而这应该很明显,只需看看配置如何改变就可以了。第二个消息提供了更多的信息。重要的是,第二个消息解释了为什么需要进行更改,并提供了一些对将来对集群进行更改的人有用的信息。没有这个重要的上下文,您可能会想知道为什么打开了端口80
,并担心如果更改了该信息会发生什么。
在生产环境中操作 Kubernetes 集群不仅仅是关于如何在第一天启动集群;而是确保您可以随着时间的推移更新和扩展集群,以继续满足组织的要求。
总结
我们在本章中构建的集群仍然非常简单,实际上反映了我们可以在接下来的章节中建立的起点。然而,它确实满足了生产就绪的以下基本要求:
-
可靠性:通过使用 EKS,我们已经配置了一个可靠的控制平面,我们可以依赖它来管理我们的集群。
-
可扩展性:通过通过自动扩展组操作我们的节点,我们可以简单地在几秒钟内增加集群的额外容量。
-
可维护性:通过使用 Terraform 将我们的基础设施定义为代码,我们已经简化了将来管理我们的集群。通过为我们的节点机器使用的 AMI 设置构建过程,我们能够快速重建镜像以引入安全更新和更新版本的节点软件。