原文:
annas-archive.org/md5/3c34d287e2879a0f121f1884a118ac03译者:飞龙
第六章:管理高级 Kubernetes 资源
在上一章中,我们介绍了 Kubernetes 及其需求,并讨论了如何使用 MiniKube 和 KinD 启动 Kubernetes 集群。接着,我们查看了 Pod 资源,讨论了如何创建和管理 Pods,如何排查问题,并如何通过探针确保应用程序的可靠性,还探讨了多容器设计模式,以理解 Kubernetes 为什么使用 Pods 而不是容器。我们还了解了 Secrets 和 ConfigMaps。
现在,我们将深入探讨 Kubernetes 的高级部分以及 Kubernetes 命令行最佳实践。
在本章中,我们将讨论以下主要内容:
-
高级 Kubernetes 资源的需求
-
Kubernetes 部署
-
Kubernetes 服务和入口
-
水平 Pod 自动扩展
-
管理有状态的应用程序
-
Kubernetes 命令行最佳实践、技巧和窍门
所以,让我们开始吧!
技术要求
对于本章,我们将为练习启动一个基于云的 Kubernetes 集群,Google Kubernetes Engine(GKE)。因为你将无法在本地系统中启动负载均衡器和 PersistentVolumes,所以在本章中我们不能使用 KinD 和 MiniKube。
目前,Google Cloud Platform(GCP)提供免费的 $300 试用,期限为 90 天,所以你可以前往 cloud.google.com/free 注册。
启动 GKE
注册并登录到控制台后,你可以打开 Google Cloud Shell CLI 来运行命令。
你需要先启用 GKE API,使用以下命令:
$ gcloud services enable container.googleapis.com
要创建一个三节点 GKE 集群,运行以下命令:
$ gcloud container clusters create cluster-1 --zone us-central1-a
就是这样!集群已成功启动并运行。
你还需要克隆以下 GitHub 仓库来进行一些练习:github.com/PacktPublishing/Modern-DevOps-Practices-2e。
运行以下命令将仓库克隆到你的主目录,并使用 cd 进入 ch6 目录以访问所需的资源:
$ git clone https://github.com/PacktPublishing/Modern-DevOps-Practices-2e.git \
modern-devops
$ cd modern-devops/ch6
现在,让我们了解一下为什么我们需要高级 Kubernetes 资源。
高级 Kubernetes 资源的需求
在上一章中,我们讨论了 pod,这是 Kubernetes 的基本构建块,为您的容器在 Kubernetes 环境中提供一切必需的内容。然而,单独的 pod 并不是那么有效。原因在于,尽管它们定义了容器应用及其规范,但它们不会复制、自动修复或维护特定状态。删除 pod 后,该 pod 就不存在了。您不能使用 pod 维护代码的多个版本,也不能使用 pod 进行发布和回滚。使用仅有的 pod 也不能根据流量自动缩放您的应用程序。Pod 无法让您将容器暴露给外部世界,也不提供负载均衡、基于内容和路径的路由、将持久数据存储到外部附加存储等流量管理功能。为了解决这些问题,Kubernetes 为我们提供了特定的高级资源,如 Deployments、Services、Ingresses、PersistentVolumes 和 claims,以及 StatefulSets。让我们在下一节中从 Kubernetes 部署开始。
Kubernetes 部署
让我们通过一个简单的类比来理解 Kubernetes 部署。
想象一下,您是一位在厨房里准备特定菜肴的厨师。您希望确保每次上菜时都能保持一致的完美,同时希望能够更改食谱而不引起混乱。
在 Kubernetes 的世界中,“Deployment” 就像是您的副厨。它帮助您轻松创建和管理您的 pod 的副本。
它是如何工作的:
-
创建一致性:您希望将您的菜肴服务给许多客人。因此,与其分别烹饪每份盘子,不如一次性准备一堆。所有的盘子都应该味道相同,完全按照预期来。Kubernetes 部署为您的 pod 做到了这一点,创建多个相同的 pod 副本,确保它们都具有相同的设置。
-
Deployment资源会逐个缓慢而谨慎地替换旧副本,以确保您的应用始终可用,您的访客(或用户)不会注意到任何中断。 -
优雅回滚:有时,实验并不如预期那样顺利,您可能需要回到原始食谱。就像在厨房里一样,Kubernetes 允许您将 pod 的版本回滚到之前的版本,如果新版本出现问题的话。
-
轻松扩展:想象一下您的餐厅突然迎来了一波顾客,您需要更多的盘子来盛放您的特色菜肴。Kubernetes 部署也能帮助您做到这一点。它可以快速创建更多的 pod 副本来处理增加的需求,并在事态平静下来时移除它们。
-
管理多个厨房:如果您有多家餐馆,您希望您的招牌菜在所有餐馆中味道一致。类似地,如果您在测试、开发和生产等不同环境中使用 Kubernetes,部署可以帮助保持一致性。
本质上,Kubernetes 的 Deployments 帮助管理你的 pod,就像副厨师管理厨房里做的菜肴一样。它们确保一致性、安全性和灵活性,确保你的应用顺利运行,并且可以在不打乱你 软件厨房 的情况下进行更新。
Kubernetes 中的容器应用部署是通过 Deployment 资源来完成的。Deployment 资源背后使用了 ReplicaSet 资源,因此在继续学习 Deployment 资源之前,了解 ReplicaSet 资源会很有帮助。
ReplicaSet 资源
ReplicaSet 资源是 Kubernetes 控制器,帮助你在给定时间运行多个 pod 副本。它们为容器工作负载提供水平扩展,形成容器的水平扩展集的基本构建块,一组相似的容器组合在一起作为一个单元运行。
ReplicaSet 资源定义了在给定时间运行的 pod 副本数目。Kubernetes 控制器随后会尝试维持副本,并在 pod 宕机时重新创建它。
你不应单独使用 ReplicaSet 资源,而应将其作为 Deployment 资源的后端。
为了更好理解,我们来看一个例子。要访问本节的资源,cd 到以下目录:
$ cd ~/modern-devops/ch6/deployments/
ReplicaSet 资源清单 nginx-replica-set.yaml 看起来是这样的:
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
资源清单包括 apiVersion 和 kind,就像其他任何资源一样。它还包含一个 metadata 部分,用于定义资源的 name 和 labels 属性,类似于其他任何 Kubernetes 资源。
spec 部分包含以下属性:
-
replicas:这定义了通过选择器匹配的 pod 副本数目,在给定时间内运行。 -
selector:这定义了ReplicaSet资源将包含 pod 的基础。 -
selector.matchLabels:这定义了选择 pod 的标签及其值。因此,ReplicaSet资源将选择任何具有app:nginx标签的 pod。 -
template:这是一个可选部分,你可以使用它来定义 pod 模板。这个部分的内容非常类似于定义一个 pod,唯一不同的是它没有name属性,因为ReplicaSet资源会为 pod 生成动态名称。如果不包含这个部分,ReplicaSet资源仍会尝试获取具有匹配标签的现有 pod。但由于缺少模板,它不能创建新的 pod。因此,最佳实践是为ReplicaSet资源指定一个模板。
让我们继续应用这个清单,看看会得到什么:
$ kubectl apply -f nginx-replica-set.yaml
现在,让我们运行以下命令列出 ReplicaSet 资源:
$ kubectl get replicaset
NAME DESIRED CURRENT READY AGE
nginx 3 3 0 9s
对的——我们看到有三个期望的副本。目前,3 个副本正在运行,但 0 个副本准备好。让我们等一会儿,然后重新运行以下命令:
$ kubectl get replicaset
NAME DESIRED CURRENT READY AGE
nginx 3 3 3 1m10s
现在,我们看到 3 个已就绪的 pod 正在等待连接。接下来,让我们列出这些 pod,看看 ReplicaSet 资源在幕后做了什么,使用以下命令:
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-6qr9j 1/1 Running 0 1m32s
nginx-7hkqv 1/1 Running 0 1m32s
nginx-9kvkj 1/1 Running 0 1m32s
有三个 nginx pod,每个名称以 nginx 开头,但以随机哈希值结尾。ReplicaSet 资源会在 ReplicaSet 资源名称的末尾附加一个随机哈希,以生成唯一的 pod。是的——Kubernetes 中每种资源的名称都应该是唯一的。
让我们继续使用以下命令删除 ReplicaSet 资源中的一个 pod,并查看结果:
$ kubectl delete pod nginx-9kvkj && kubectl get pod
pod "nginx-9kvkj" deleted
NAME READY STATUS RESTARTS AGE
nginx-6qr9j 1/1 Running 0 8m34s
nginx-7hkqv 1/1 Running 0 8m34s
nginx-9xbdf 1/1 Running 0 5s
我们看到,尽管我们删除了 nginx-9kvkj pod,但 ReplicaSet 资源已经用一个新 pod nginx-9xbdf 替代了它。这就是 ReplicaSet 资源的工作方式。
你可以像删除其他 Kubernetes 资源一样删除 ReplicaSet 资源。你可以运行命令 kubectl delete replicaset <ReplicaSet 名称> 来进行命令式删除,或者使用 kubectl delete -f <清单文件> 进行声明式删除。
我们使用之前的方法,通过以下命令删除 ReplicaSet 资源:
$ kubectl delete replicaset nginx
让我们运行以下命令检查 ReplicaSet 资源是否已被删除:
$ kubectl get replicaset
No resources found in default namespace.
在 default 命名空间中没有任何资源。这意味着 ReplicaSet 资源已被删除。
正如我们讨论的,ReplicaSet 资源不应单独使用,而应作为 Deployment 资源的后台。接下来我们来看一下 Kubernetes Deployment 资源。
Deployment 资源
Kubernetes Deployment 资源有助于管理容器应用程序的部署。它们通常用于管理无状态工作负载。尽管你仍然可以使用它们来管理有状态应用程序,但推荐的有状态应用程序处理方式是使用 StatefulSet 资源。
Kubernetes Deployments 使用 ReplicaSet 资源作为后台,资源链条如下图所示:
图 6.1 – Deployment 链接
让我们以之前的示例为基础,创建一个 nginx Deployment 资源清单——nginx-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
该清单与 ReplicaSet 资源非常相似,唯一不同的是 kind 属性——在这种情况下是 Deployment。
让我们通过以下命令应用该清单:
$ kubectl apply -f nginx-deployment.yaml
所以,Deployment 资源已经创建,让我们看看它创建的资源链条。通过以下命令运行 kubectl get 来列出 Deployment 资源:
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 3/3 3 3 6s
我们看到有一个名为 nginx 的 Deployment 资源,包含 3/3 个已就绪 pod 和 3 个最新的 pod。由于 Deployment 资源管理多个版本,UP-TO-DATE 表示最新的 Deployment 资源是否已成功滚动发布。我们将在后续部分详细讨论这一点。同时,它显示当前有 3 个可用的 pod。
由于我们知道Deployment资源会在后台创建ReplicaSet资源,我们可以使用以下命令查看ReplicaSet资源:
$ kubectl get replicaset
NAME DESIRED CURRENT READY AGE
nginx-6799fc88d8 3 3 3 11s
我们可以看到,Deployment资源创建了一个ReplicaSet资源,其名称以nginx开头,并以一个随机哈希值结尾。这是必要的,因为一个Deployment资源可能包含一个或多个ReplicaSet资源。我们将在后续部分了解这一点。
接下来是 pods,我们可以使用以下命令查看这些 pods:
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-6799fc88d8-d52mj 1/1 Running 0 15s
nginx-6799fc88d8-dmpbn 1/1 Running 0 15s
nginx-6799fc88d8-msvxw 1/1 Running 0 15s
正如预期的那样,我们有三个 pod。每个 pod 的名称都以ReplicaSet资源名称开始,并以一个随机哈希值结束。这就是为什么你会在 pod 名称中看到两个哈希值。
假设你有一个新版本并希望部署一个新的容器镜像。那么,我们可以使用以下命令更新Deployment资源并使用新的镜像:
$ kubectl set image deployment/nginx nginx=nginx:1.16.1
deployment.apps/nginx image updated
要检查部署状态,可以运行以下命令:
$ kubectl rollout status deployment nginx
deployment "nginx" successfully rolled out
像刚才显示的这些命令,通常不会在生产环境中使用,因为它们缺乏使用 Git 版本控制的声明性清单所提供的审计追踪。然而,如果你选择使用这些命令,你始终可以使用以下命令记录上次发布的更改原因:
$ kubectl annotate deployment nginx kubernetes.io/change-cause\
="Updated nginx version to 1.16.1" --overwrite=true
deployment.apps/nginx annotated
要查看部署历史,可以运行以下命令:
$ kubectl rollout history deployment nginx
deployment.apps/nginx
REVISION CHANGE-CAUSE
1 <none>
2 Updated nginx version to 1.16.1
如我们所见,部署历史中有两个修订版本。修订版本1是最初的部署,修订版本2是由于我们运行了kubectl set image命令,正如CHANGE-CAUSE列中所显示的那样。
假设你在部署后发现了问题,并希望回滚到先前的版本。为了这样做,并重新检查部署的状态,可以运行以下命令:
$ kubectl rollout undo deployment nginx && kubectl rollout status deployment nginx
deployment.apps/nginx rolled back
Waiting for deployment "nginx" rollout to finish: 2 out of 3 new replicas have been
updated...
Waiting for deployment "nginx" rollout to finish: 1 old replicas are pending
termination...
deployment "nginx" successfully rolled out
最后,我们可以使用以下命令重新检查部署历史:
$ kubectl rollout history deployment nginx
deployment.apps/nginx
REVISION CHANGE-CAUSE
2 Updated nginx version to 1.16.1
3 <none>
然后我们得到修订版本3,其CHANGE-CAUSE值为<none>。在这种情况下,我们没有像上一个命令那样注释回滚。
提示
始终注释部署更新,因为这样可以更容易查看历史记录,了解部署了什么内容。
现在,让我们来看看一些常见的 Kubernetes 部署策略,以便了解如何有效地使用部署。
Kubernetes 部署策略
更新现有的部署需要指定一个新的容器镜像。这就是为什么我们首先对容器镜像进行版本控制,以便可以根据需要滚动发布和回滚应用程序更改的原因。由于我们所有的应用都运行在容器中——容器本质上是短暂的——这使得我们可以实现多种不同的部署策略。这里有几种部署策略,其中一些如下所示:
-
重新创建:这是所有方法中最简单的一种。删除旧的 pod 并部署一个新的。
-
滚动更新:在运行旧版本的同时,逐步推出新版本的 pods,并随着新 pods 的准备好,逐步删除旧的 pods。
-
蓝绿发布:这是一种派生的部署策略,我们同时运行两个版本,当需要时将流量切换到新版。
-
金丝雀发布:这适用于蓝绿部署(Blue/Green Deployments),其中我们在完全推出版本之前,将一定比例的流量切换到应用程序的新版。
-
A/B 测试:A/B 测试更多是一种应用于蓝绿部署的技术。这是当你希望将新版推出给一部分愿意的用户,并在完全推出新版之前研究他们的使用模式。Kubernetes 并没有开箱即用的 A/B 测试功能,但你可以依赖与 Kubernetes 配合良好的服务网格技术,如 Istio、Linkerd 和 Traefik。
Kubernetes 提供了两种开箱即用的部署策略——Recreate 和 RollingUpdate。
重建
Recreate 策略是最简单直接的部署策略。当你使用 Recreate 策略更新 Deployment 资源时,Kubernetes 会立即停止旧的 ReplicaSet 资源,并根据以下图示创建一个新的 ReplicaSet 资源,且具有所需数量的副本:
图 6.2 – Recreate 策略
Kubernetes 不会删除旧的 ReplicaSet 资源,而是将 replicas 设置为 0。这是为了能够快速回滚到旧版本。这种方法会导致停机时间,因此你只应在有约束的情况下使用。因此,这种策略不是 Kubernetes 的默认部署策略。
提示
如果你的应用程序不支持多个副本,或者它不支持超过一定数量的副本(例如需要维持法定人数的应用程序),或者它不支持同时运行多个版本,那么你可以使用 Recreate 策略。
让我们使用 Recreate 策略更新 nginx-deployment。让我们来看一下 nginx-recreate.yaml 文件:
...
spec:
replicas: 3
strategy:
type: Recreate
...
YAML 文件现在包含一个 strategy 部分,并且设置为 Recreate 类型。现在,让我们应用 nginx-recreate.yaml 文件,并使用以下命令查看 ReplicaSet 资源:
$ kubectl apply -f nginx-recreate.yaml && kubectl get replicaset -w
deployment.apps/nginx configured
NAME DESIRED CURRENT READY AGE
nginx-6799fc88d8 0 0 0 0s
nginx-6889dfccd5 0 3 3 7m42s
nginx-6889dfccd5 0 0 0 7m42s
nginx-6799fc88d8 3 0 0 1s
nginx-6799fc88d8 3 3 0 2s
nginx-6799fc88d8 3 3 3 6s
Deployment 资源会创建一个新的 ReplicaSet 资源——nginx-6799fc88d8——其期望副本数为 0。然后,它将旧的 ReplicaSet 资源的期望副本数设置为 0,并等待旧的 ReplicaSet 资源完全驱逐。接着,它会自动开始推出新的 ReplicaSet 资源,以达到期望的镜像。
滚动更新
当你使用 RollingUpdate 策略更新 Deployment 时,Kubernetes 会创建一个新的 ReplicaSet 资源,同时在新的 ReplicaSet 资源上启动所需数量的 pod,并逐渐关闭旧的 ReplicaSet 资源,正如以下图示所示:
图 6.3 – RollingUpdate 策略
RollingUpdate是默认的部署策略。除了那些无法容忍在给定时间内存在多个版本的应用外,大多数应用都可以使用RollingUpdate策略。
让我们使用RollingUpdate策略更新nginx的Deployment资源。我们将重新使用之前使用的标准nginx-deployment.yaml文件。使用以下命令并查看ReplicaSet资源会发生什么:
$ kubectl apply -f nginx-deployment.yaml && kubectl get replicaset -w
deployment.apps/nginx configured
NAME DESIRED CURRENT READY AGE
nginx-6799fc88d8 3 3 3 49s
nginx-6889dfccd5 1 1 1 4s
nginx-6799fc88d8 2 2 2 53s
nginx-6889dfccd5 2 2 2 8s
nginx-6799fc88d8 1 1 1 57s
nginx-6889dfccd5 3 3 3 11s
nginx-6799fc88d8 0 0 0 60s
如我们所见,旧的ReplicaSet资源—nginx-6799fc88d8—正在被下线,而新的ReplicaSet资源—nginx-6889dfccd5—正在被同时上线。
RollingUpdate策略还有两个选项—maxUnavailable和maxSurge。
当maxSurge定义了在给定时间内我们可以拥有的额外 Pod 的最大数量时,maxUnavailable定义了在给定时间内我们可以拥有的不可用 Pod 的最大数量。
提示
如果你的应用无法容忍超过某个数量的副本,请将maxSurge设置为0。如果你希望保持可靠性,并且应用可以容忍超过设定的副本数,请将maxUnavailable设置为0。你不能将这两个参数都设置为0,因为那样会使任何滚动尝试变得不可能。在设置maxSurge时,确保你的集群有足够的空闲容量来启动额外的 Pod,否则滚动更新将失败。
使用这些设置,我们可以创建不同类型的自定义滚动策略—接下来的部分将讨论一些流行的策略。
渐进式慢滚动
如果你有多个副本,但希望缓慢推出发布,观察应用是否有问题,并在需要时回滚部署,你应该使用这个策略。
让我们使用渐进式慢滚动策略创建一个nginx部署,nginx-ramped-slow-rollout.yaml:
...
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
...
该清单与通用的部署非常相似,只不过它包含了10个副本和一个strategy部分。
strategy部分包含以下内容:
-
type:RollingUpdate -
rollingUpdate: 描述滚动更新属性的部分—maxSurge和maxUnavailable
现在,让我们应用 YAML 文件,并使用以下命令等待部署完全滚动到10个副本:
$ kubectl apply -f nginx-ramped-slow-rollout.yaml \
&& kubectl rollout status deployment nginx
deployment.apps/nginx configured
...
deployment "nginx" successfully rolled out
如我们所见,Pod 已经完全滚动完成。现在,让我们使用以下命令更新Deployment资源,换用不同的nginx镜像版本,看看会发生什么:
$ kubectl set image deployment nginx nginx=nginx:1.16.1 \
&& kubectl get replicaset -w
deployment.apps/nginx image updated
NAME DESIRED CURRENT READY AGE
nginx-6799fc88d8 10 10 10 3m51s
nginx-6889dfccd5 1 1 0 0s
nginx-6799fc88d8 9 10 10 4m
. . . . . . . . . . . . . . .
nginx-6889dfccd5 8 8 8 47s
nginx-6799fc88d8 2 3 3 4m38s
nginx-6889dfccd5 9 9 8 47s
nginx-6799fc88d8 2 2 2 4m38s
nginx-6889dfccd5 9 9 9 51s
nginx-6889dfccd5 10 9 9 51s
nginx-6799fc88d8 1 2 2 4m42s
nginx-6889dfccd5 10 10 10 55s
nginx-6799fc88d8 0 1 1 4m46s
nginx-6799fc88d8 0 0 0 4m46s
所以,我们在这里看到两个ReplicaSet资源—nginx-6799fc88d8和nginx-6889dfccd5。当nginx-6799fc88d8的 Pod 正在从10个 Pod 慢慢减少到0时,一次只减少一个,nginx-6889dfccd5的 Pod 则同时从0个 Pod 逐步增加到10个 Pod。在任何时刻,Pod 的数量都不会超过11。这是因为maxSurge设置为1,而maxUnavailable设置为0。这就是慢滚动的实际效果。
提示
渐进式慢滚动在我们希望在影响许多用户之前保持谨慎时非常有用,但这种策略非常慢,可能只适用于某些应用。
让我们看看更快推出的最佳努力控制策略,而不影响应用程序的可用性。
最佳努力控制的推出
Best-effort controlled rollout 帮助你以最佳努力的方式推出部署,你可以使用它来更快地推出你的发布,并确保你的应用程序可用。它还可以帮助处理那些在特定时间点不允许超过一定数量副本的应用程序。
我们将 maxSurge 设置为 0,并将 maxUnavailable 设置为适当的百分比,以确保在任何给定时间内仍然不可用。可以按照 pod 数量或百分比来指定。
小贴士
使用百分比是更好的选择,因为这样做的话,如果副本数量发生变化,你就不需要重新计算 maxUnavailable 参数了。
让我们来看看清单— nginx-best-effort-controlled-rollout.yaml:
...
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 0
maxUnavailable: 25%
...
现在让我们应用 YAML 文件并看看我们得到了什么:
$ kubectl apply -f nginx-best-effort-controlled-rollout.yaml \
&& kubectl get replicaset -w
deployment.apps/nginx configured
NAME DESIRED CURRENT READY AGE
nginx-6799fc88d8 2 0 0 20m
nginx-6889dfccd5 8 8 8 16m
nginx-6799fc88d8 2 2 1 20m
nginx-6889dfccd5 7 8 8 16m
. . . . . . . . . . . . . . . .
nginx-6889dfccd5 1 1 1 16m
nginx-6799fc88d8 9 9 8 20m
nginx-6889dfccd5 0 1 1 16m
nginx-6799fc88d8 10 9 8 20m
nginx-6889dfccd5 0 0 0 16m
nginx-6799fc88d8 10 10 10 20m
因此,我们看到 ReplicaSet 资源进行滚动式推出,以便任何时候总体 pod 数量最多为 10,总体不可用 pod 数量永远不超过 25%。你还可以注意到,更新 Deployment 资源时并不会创建新的 ReplicaSet 资源,而是使用包含 nginx:latest 镜像的旧 ReplicaSet 资源。还记得我说过更新 Deployment 资源时不会删除旧 ReplicaSet 资源吗?
Deployment 资源本身是调度和管理 pod 的好方式。然而,我们忽视了 Kubernetes 中运行容器的一个重要部分——将它们暴露给内部或外部世界。Kubernetes 提供了几种资源来帮助适当地暴露你的工作负载——主要是 Service 和 Ingress 资源。让我们在下一节中看看这些。
Kubernetes Services 和 Ingresses
故事时间到!让我们简化 Kubernetes Services。
想象一下,你有一群喜欢从你的餐馆订餐的朋友。与其分别将每个订单送到他们家里,不如在他们的社区建立一个中央交付点。这个交付点(或中心)就是你的“服务”。
在 Kubernetes 中,一个 Service 就像是那个中央枢纽。它是你应用程序不同部分(比如你的网站、数据库或其他组件)之间进行通信的一种方式,即使它们在不同的容器或机器上也可以。它为它们提供了易于记忆的地址,以便彼此找到而不至于迷失。
Service 资源有助于将 Kubernetes 工作负载暴露到内部或外部世界。如我们所知,pods 是临时性的资源——它们可以出现也可以消失。每个 pod 都会分配一个独特的 IP 地址和主机名,但一旦 pod 被销毁,pod 的 IP 地址和主机名也会发生变化。假设你的一个 pod 想与另一个 pod 交互,但是由于 pod 的短暂特性,你无法配置一个合适的端点。如果你使用 IP 地址或主机名作为 pod 的端点,而该 pod 被销毁后,你将无法再连接到它。因此,单独暴露一个 pod 并不是一个好主意。
Kubernetes 提供了 Service 资源,以便为一组 pod 提供静态 IP 地址。除了通过单一静态 IP 地址暴露这些 pod 外,它还在轮询配置中为 pod 之间的流量提供负载均衡。它帮助均匀地分配流量到各个 pod,是暴露工作负载的默认方法。
Service 资源还会分配一个静态的 Service 资源 FQDN,而不是集群内的 IP 地址,以确保端点的容错性。
现在,回到 Service 资源,存在多种 Service 资源类型——ClusterIP、NodePort 和 LoadBalancer,每种类型都有各自的使用场景:
图 6.4 – Kubernetes 服务
让我们通过示例来理解这些内容。
ClusterIP 服务资源
ClusterIP Service 资源是默认的 Service 资源类型,用于在 Kubernetes 集群内部暴露 pods。无法从集群外部访问 ClusterIP Service 资源;因此,它们永远不会用于将 pods 暴露到外部世界。ClusterIP Service 资源通常用于暴露后端应用程序,如数据存储和数据库——即三层架构中的业务和数据层。
提示
在选择 Service 资源类型时,一般的经验法则是,始终从 ClusterIP Service 资源开始,必要时再进行修改。这将确保只有需要的服务暴露到外部。
为了更好地理解 ClusterIP Service 资源,让我们首先使用以下命令通过命令式方法创建一个 redis Deployment 资源:
$ kubectl create deployment redis --image=redis
让我们尝试使用 ClusterIP Service 资源暴露 redis 部署的 pods。要访问此部分的资源,请 cd 到以下目录:
$ cd ~/modern-devops/ch6/services/
首先让我们查看 Service 资源清单 redis-clusterip.yaml:
apiVersion: v1
kind: Service
metadata:
labels:
app: redis
name: redis
spec:
ports:
- port: 6379
protocol: TCP
targetPort: 6379
selector:
app: redis
Service 资源清单从 apiVersion 和 kind 开始,和其他资源一样。它有一个 metadata 部分,其中包含 name 和 labels。
spec 部分包含以下内容:
-
ports:该部分包括我们希望通过Service资源暴露的端口列表:A.
port:我们希望暴露的端口。B.
protocol:我们暴露的端口协议(TCP/UDP)。C.
targetPort:目标容器端口,暴露端口将把连接转发到该端口。这使我们能够拥有类似于 Docker 的端口映射。 -
selector:该部分包含用于选择 Pod 组的labels。
让我们使用以下命令应用Service资源清单,看看我们能得到什么:
$ kubectl apply -f redis-clusterip.yaml
让我们运行kubectl get命令列出Service资源并获取集群 IP:
$ kubectl get service redis
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
redis ClusterIP 10.12.6.109 <none> 6379/TCP 16s
我们看到一个redis的Service资源正在以ClusterIP类型运行。但由于这个 Pod 没有对外暴露,访问它的唯一方式是通过集群内的第二个 Pod。
让我们创建一个busybox Pod,以交互模式检查Service资源并使用以下命令运行一些测试:
$ kubectl run busybox --rm --restart Never -it --image=busybox
/ #
这样,我们就看到了一个提示。我们已经启动了busybox容器,并且现在正处于其中。我们将使用telnet应用程序检查 Pods 之间的连通性。
让我们通过以下命令 telnet 集群 IP 和端口,看看是否可达:
/ # telnet 10.96.118.99 6379
Connected to 10.96.118.99
从那里可以访问 IP/端口对。Kubernetes 还提供了内部 DNS 以促进服务发现并连接到Service资源。我们可以使用以下命令对集群 IP 进行反向nslookup,获取Service资源的 FQDN:
/ # nslookup 10.96.118.99
Server: 10.96.0.10
Address: 10.96.0.10:53
99.118.96.10.arpa name = redis.default.svc.cluster.local
如我们所见,IP 地址可以通过 FQDN 访问——redis.default.svc.cluster.local。我们可以根据我们的地理位置使用整个域名或其中的一部分。FQDN 由以下部分组成:<service_name>.<namespace>.svc.<cluster-domain>.local。
Kubernetes 至今一直使用default命名空间,并将继续使用。如果你的源 Pod 位于与Service资源相同的命名空间内,你可以使用service_name来连接到你的Service资源——像下面的示例一样:
/ # telnet redis 6379
Connected to redis
如果你想从位于不同命名空间的 Pod 调用Service资源,可以改用<service_name>.<namespace>,像下面的示例一样:
/ # telnet redis.default 6379
Connected to redis.default
一些服务网格(例如 Istio)支持多集群通信。在这种情况下,您还可以使用集群名称来连接到Service资源,但由于这是一个高级主题,超出了本讨论的范围。
提示
始终使用尽可能短的域名来表示端点,因为它允许在不同环境中更灵活地移动 Kubernetes 资源。
ClusterIP类型的服务非常适合暴露内部 Pod,但如果我们想将 Pod 暴露给外部世界呢?Kubernetes 提供了多种Service资源类型;首先让我们看看NodePort类型的Service资源。
NodePort Service 资源
NodePort Service资源用于将你的 Pods 暴露到外部。创建一个NodePort Service资源会启动一个ClusterIP类型的Service资源,并将ClusterIP端口映射到所有集群节点的随机高端口号(默认范围:30000-32767)。你也可以根据需要指定一个静态的NodePort端口号。因此,使用NodePort Service资源,你可以通过集群中任意节点的 IP 地址和该服务的NodePort来访问你的 Pods。
提示
虽然可以指定静态的NodePort端口号,但你应该避免使用它。这是因为你可能会与其他Service资源发生端口冲突,并且对配置和变更管理产生较高依赖。相反,保持简单,使用动态端口。
以 Flask 应用为例,创建一个flask-app Pod,并使用之前创建的redis Service资源作为其后端,然后我们将通过NodePort暴露该 Pod。
使用以下命令命令式地创建一个 Pod:
$ kubectl run flask-app --image=<your_dockerhub_user>/python-flask-redis
现在,既然我们已经创建了flask-app Pod,使用以下命令查看它的状态:
$ kubectl get pod flask-app
NAME READY STATUS RESTARTS AGE
flask-app 1/1 Running 0 19s
flask-app Pod 已经成功运行,并准备好接受请求。现在是时候理解NodePort类型的Service资源清单flask-nodeport.yaml了:
...
spec:
ports:
- port: 5000
protocol: TCP
targetPort: 5000
selector:
run: flask-app
type: NodePort
清单类似于ClusterIP清单,但包含一个type属性,用于指定Service资源类型——NodePort。
让我们应用这个清单,看看通过以下命令可以得到什么:
$ kubectl apply -f flask-nodeport.yaml
现在,让我们列出Service资源并获取NodePort类型的 Service,使用以下命令:
$ kubectl get service flask-app
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
flask-app NodePort 10.3.240.246 <none> 5000:32618/TCP 9s
我们可以看到类型现在是NodePort,容器端口5000映射到了节点端口32618。
如果你已经登录到任何 Kubernetes 节点,可以通过localhost:32618访问Service资源。但由于我们正在使用 Google Cloud Shell,我们需要 SSH 进入节点才能访问Service资源。
让我们首先列出节点,使用以下命令:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-node-1dhh Ready <none> 17m v1.26.15-gke.4901
gke-node-7lhl Ready <none> 17m v1.26.15-gke.4901
gke-node-zwg1 Ready <none> 17m v1.26.15-gke.4901
如我们所见,我们有三台节点。让我们通过以下命令 SSH 进入gke-node-1dhh节点:
$ gcloud compute ssh gke-node-1dhh
现在,既然我们处在gke-node-1dhh节点,使用以下命令 curl 访问localhost:32618:
$ curl localhost:32618
Hi there! This page was last visited on 2023-06-26, 08:37:50.
然后我们收到了一个响应!你可以 SSH 进入任意节点并使用 curl 访问该端点,应该能得到类似的响应。
要退出节点并返回到 Cloud Shell 提示符,运行以下命令:
$ exit
Connection to 35.202.82.74 closed.
你已回到 Cloud Shell 提示符。
提示
NodePort类型的Service资源是一个中介类型的资源。这意味着虽然它是提供外部服务的重要组成部分,但大多数时候它并不会单独使用。当你在云环境中运行时,你可以使用LoadBalancer类型的Service资源。即使是在本地部署环境下,也不建议为每个Service资源使用NodePort,而应该使用Ingress资源。
现在,让我们来看看广泛用于将你的 Kubernetes 工作负载暴露到外部的 LoadBalancer Service 资源。
LoadBalancer 服务资源
LoadBalancer Service 资源帮助在单一的负载均衡端点上暴露你的 Pod。这些 Service 资源只能在云平台及提供 Kubernetes 控制器来访问外部网络资源的平台上使用。LoadBalancer 服务实际上会启动一个 NodePort Service 资源,然后请求云 API 在节点端口前面启动一个负载均衡器。这样,它提供了一个单一的端点,供外部世界访问你的 Service 资源。
启动一个 LoadBalancer Service 资源很简单——只需将类型设置为 LoadBalancer。
让我们使用以下清单将 Flask 应用程序暴露为负载均衡器——flask-loadbalancer.yaml:
...
spec:
type: LoadBalancer
...
现在,让我们使用以下命令应用清单:
$ kubectl apply -f flask-loadbalancer.yaml
让我们使用以下命令让 Service 资源注意到这些更改:
$ kubectl get svc flask-app
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
flask-app LoadBalancer 10.3.240.246 34.71.95.96 5000:32618
Service 资源类型现在是 LoadBalancer。如你所见,它现在包含了一个外部 IP 以及集群 IP。
然后,你可以使用以下命令在外部 IP 的端口 5000 上执行 curl:
$ curl 34.71.95.96:5000
Hi there! This page was last visited on 2023-06-26, 08:37:50.
然后你会得到与之前相同的响应。你的 Service 资源现在已在外部运行。
提示
LoadBalancer Service 资源通常比较昂贵,因为每当新建一个资源时,它会在你的云服务提供商中启动一个网络负载均衡器。如果你的工作负载基于 HTTP,建议使用 Ingress 资源而不是 LoadBalancer 来节省资源成本并优化流量,因为它们会启动一个应用程序负载均衡器。
虽然 Kubernetes 服务是暴露你的容器应用程序内部和外部的基本构建块,Kubernetes 还提供了 Ingress 资源,用于对流量进行更精细的控制。让我们在下一部分中看看这一点。
Ingress 资源
想象一下,你的餐厅有一个漂亮的前门,顾客通过这个门进入。顾客通过这个主要入口进入餐厅后,可以到达餐厅的不同区域,比如就餐区或酒吧。这个入口就像你的“入口”。
在 Kubernetes 中,Ingress 就像那个前门。它帮助管理外部访问你集群内的服务。你不需要为每个服务单独暴露,你可以使用 Ingress 来决定外部用户如何访问你应用程序的不同部分。
简单来说,Kubernetes 服务就像是你应用程序不同部分的中央交付点,而 Ingress 就像是一个前门,帮助外部用户轻松找到并访问这些部分。
Ingress 资源充当 Kubernetes 中的反向代理。你不需要为你运行的每个应用程序都创建负载均衡器,因为负载均衡器通常转发流量,并不需要很高的计算能力。因此,为每个应用程序都启动负载均衡器并不明智。
因此,Kubernetes 提供了一种通过 Ingress 资源将外部流量路由到集群的方式。这些资源帮助你根据多个条件划分流量。以下是一些设定:
-
基于 URL 路径
-
基于主机名
-
两者的结合
以下图示展示了 Ingress 资源的工作原理:
图 6.5 – Kubernetes 入口资源
Ingress 资源需要一个入口控制器才能正常工作。虽然大多数云服务提供商已经安装了控制器,但你必须在本地或自管理的 Kubernetes 集群中安装入口控制器。有关安装入口控制器的详细信息,请参考 kubernetes.io/docs/concepts/services-networking/ingress-controllers/。你可以安装多个入口控制器,但你需要在清单中注解,明确指定 Ingress 资源应该使用哪个控制器。
对于本章,我们将在前面使用 Ingress 资源,并进行精确的逐一迁移。
为了理解 nginx 入口控制器在 GKE(或其他云)上的工作原理,让我们看看以下图示:
图 6.6 – GKE 上的 nginx 入口控制器
客户端通过一个由入口管理的负载均衡器连接到 Ingress 资源,流量会转发到充当负载均衡器后端的入口控制器。然后,入口控制器根据在 Ingress 资源上定义的路由规则将流量路由到正确的 Service 资源。
现在,让我们使用以下命令安装 nginx 入口控制器:
$ kubectl apply -f \
https://raw.githubusercontent.com/kubernetes/ingress-nginx\
/controller-v1.8.0/deploy/static/provider/cloud/deploy.yaml
这将启动几个资源,位于 ingress-nginx 命名空间下。最显著的是 ingress-nginx-controller Deployment,它通过 ingress-nginx-controller LoadBalancer Service 进行暴露。
现在,让我们通过 Ingress 资源暴露 flask-app Service,但在此之前,我们需要先将 flask-app Service 暴露为 ClusterIP,所以让我们使用以下命令应用相关清单:
$ kubectl apply -f flask-clusterip.yaml
下一步是定义一个 Ingress 资源。记住,由于 GKE 运行在公共云中,因此已经安装并运行了入口控制器。所以,我们可以简单地创建一个入口清单—flask-basic-ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: flask-app
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
defaultBackend:
service:
name: flask-app
port:
number: 5000
该资源定义了一个默认的后端,将所有流量传递到 flask-app pod,因此它是低效的,但为了简便起见,我们先看一下它。
使用以下命令应用清单:
$ kubectl apply -f flask-basic-ingress.yaml
现在,使用以下命令列出 Ingress 资源:
$ kubectl get ingress flask-app
NAME CLASS HOSTS ADDRESS PORTS AGE
flask-app <none> * 80 40s
我们可以看到 flask-app 的 Ingress 资源现在列出了 HOSTS *。这意味着它会监听所有主机上的所有地址。所以,任何不匹配其他 Ingress 规则的流量都会路由到这里。如前所述,我们需要 nginx-ingress-controller 服务的外部 IP 地址来调用通过 Ingress 暴露的所有服务。要获取 nginx-ingress-controller 服务的外部 IP 地址,请运行以下命令:
$ kubectl get svc ingress-nginx-controller -n ingress-nginx
NAME TYPE EXTERNAL-IP
ingress-nginx-controller LoadBalancer 34.120.27.34
我们看到为其分配了一个外部 IP 地址,接下来我们将使用它。
重要提示
记住,Ingress 规则传播到集群中需要一些时间,因此如果你在 curl 端点时最初收到错误,请等待 5 分钟,之后你应该会收到响应。
让我们使用以下命令 curl 这个 IP 地址,看看我们能得到什么:
$ curl 34.120.27.34
Hi there! This page was last visited on 2023-06-26, 09:28:26.
现在,使用以下命令清理 Ingress 资源:
$ kubectl delete ingress flask-app
简单的 Ingress 规则是适得其反的,因为它将所有流量都路由到一个 Service 资源。Ingress 的目的是使用单个负载均衡器将流量路由到多个目标。让我们来看两种实现方法——基于路径的路由和基于名称的路由。
基于路径的路由
假设我们有一个包含两个版本的应用程序,v1 和 v2,并希望它们都在单个端点上共存。在这种情况下,你可以使用 基于路径的路由。
让我们首先使用命令式方法创建这两个应用程序版本,运行以下命令:
$ kubectl run nginx-v1 --image=bharamicrosystems/nginx:v1
$ kubectl run nginx-v2 --image=bharamicrosystems/nginx:v2
现在,使用以下命令将这两个 Pod 暴露为 ClusterIP Service 资源:
$ kubectl expose pod nginx-v1 --port=80
$ kubectl expose pod nginx-v2 --port=80
然后,我们将使用以下清单文件 nginx-app-path-ingress.yaml 创建一个 Ingress 资源,该资源将暴露两个端点——<external-ip>/v1,该端点路由到 v1 Service 资源,以及 <external-ip>/v2,该端点路由到 v2 Service 资源:
...
spec:
rules:
- http:
paths:
- path: /v1
pathType: Prefix
backend:
service:
name: nginx-v1
port:
number: 80
- path: /v2
pathType: Prefix
backend:
service:
name: nginx-v2
port:
number: 80
Ingress 清单包含多个规则。http 规则有两个路径—/v1 和 /v2,其 pathType 值设置为 Prefix。因此,任何以 /v1 开头的 URL 流量将被路由到端口 80 上的 nginx-v1 Service 资源,任何到达 /v2 的流量则被路由到端口 80 上的 nginx-v2 Service 资源。
让我们使用以下命令应用该清单:
$ kubectl apply -f nginx-app-path-ingress.yaml
现在,运行以下命令列出 Ingress 资源:
$ kubectl get ingress nginx-app -w
NAME CLASS HOSTS ADDRESS PORTS AGE
nginx-app <none> * 34.120.27.34 80 114s
现在,我们有了外部 IP,可以使用以下命令 curl 这两个端点,看看我们能得到什么:
$ curl 34.120.27.34/v1/
This is version 1
$ curl 34.120.27.34/v2/
This is version 2
有时基于路径的路由并不总是可行,因为你可能不希望用户记住多个应用程序的路径。不过,你仍然可以通过单一的 Ingress 端点运行多个应用程序——也就是通过使用 基于名称的路由。
基于名称的路由
我们在发起 HTTP 请求时传递的 host 头部。Ingress 资源可以根据该头部进行路由。例如,如果我们想访问 v1 的 Service 资源,可以使用 v1.example.com,而访问 v2 的 Service 资源时,可以使用 v2.example.com URL。
接下来,让我们看一下 nginx-app-host-ingress.yaml 清单,以便更深入理解这个概念:
...
spec:
rules:
- host: v1.example.com
http:
paths:
- path: "/"
pathType: Prefix
backend:
service:
name: nginx-v1
port:
number: 80
- host: v2.example.com
http:
paths:
- path: "/"
pathType: Prefix
backend:
service:
name: nginx-v2
port:
number: 80
现在,清单中包含了多个主机——v1.example.com 路由到 nginx-v1,v2.example.com 路由到 nginx-v2。
现在,让我们应用这个清单并使用以下命令获取 Ingress:
$ kubectl apply -f nginx-app-host-ingress.yaml
$ kubectl get ingress
NAME HOSTS ADDRESS PORTS
nginx-app v1.example.com,v2.example.com 34.120.27.34 80
这次,我们看到两个主机被定义了,v1.example.com 和 v2.example.com,它们运行在同一个地址上。在访问这些端点之前,我们需要在 /etc/hosts 文件中添加一个条目,允许我们的机器将 v1.example.com 和 v2.example.com 解析到 Ingress 地址。
编辑 /etc/hosts 文件,并在末尾添加以下条目:
<Ingress_External_IP> v1.example.com v2.example.com
现在,让我们 curl 两个端点,看看会得到什么:
$ curl v1.example.com
This is version 1
$ curl v2.example.com
This is version 2
如我们所见,基于名称的路由正常工作!你可以通过结合多个主机和基于路径的路由来创建一个更动态的设置。
Service、Ingress、Pod、Deployment 和 ReplicaSet 资源帮助我们在 Kubernetes 中保持一定数量的副本,并帮助通过一个端点为它们提供服务。正如你可能已经注意到的,它们是通过 labels 和 matchLabels 属性的组合来关联的。下图将帮助你更好地理解这一点:
图 6.7 – 连接 Deployment、Service 和 Ingress
到目前为止,我们一直在手动缩放 Pods,但更好的方法是根据资源使用情况和流量自动伸缩副本。Kubernetes 提供了一个名为 HorizontalPodAutoscaler 的资源来处理这个需求。
水平 Pod 自动伸缩
想象一下,你是一个公园小吃摊的经理。在一个阳光明媚的日子里,很多人来享受公园,他们都想要小吃。现在,你有几个工人在小吃摊制作和提供小吃。
Kubernetes 中的水平 Pod 自动伸缩就像是拥有一群神奇的助手,他们根据需要多少人想要小吃(流量),来调整小吃制作员(pods)的数量。
以下是它的工作原理:
-
普通日子:在平常日子里,只有少数人来,可能只需要一两个小吃制作员。在 Kubernetes 的术语中,你只需要几个 Pod 来运行你的应用程序。
-
繁忙的日子:但是当是一个阳光明媚的周末,大家都涌向公园时,会有更多的人想要小吃。你的神奇助手(水平 Pod 自动伸缩)注意到需求的增加,他们说:“我们需要更多的小吃制作员!”因此,更多的小吃制作员(pods)会自动添加,以应对这股人潮。
-
缩小规模:当太阳落山,游客离开时,你不再需要那么多小吃制作员了。你的神奇助手看到需求减少,便说:“我们现在可以减少小吃制作员数量了。”因此,多余的小吃制作员(pods)会被移除,从而节省资源。
-
自动调整:这些神奇的助手会实时监控人流,并调整小吃制作员(pods)的数量。当需求增加时,他们会部署更多;当需求减少时,他们会移除一些。
以同样的方式,Kubernetes 水平 Pod 自动扩缩会监控你的应用程序有多繁忙。如果流量增加(更多人想要使用你的应用),它会自动添加更多的 Pod。如果流量减少,它会缩减 Pod 数量。这可以帮助你的应用程序在流量波动时自动调整,而不需要你手动操作。
所以,水平 Pod 自动扩缩(Horizontal pod autoscaling)就像拥有神奇的助手,确保你的应用程序有正确数量的工作者(Pods)来高效地处理流量(traffic)。
HorizontalPodAutoscaler 是一个 Kubernetes 资源,它可以帮助你根据定义的因素(最常见的是 CPU 和内存)更新 ReplicaSet 资源中的副本数。
为了更好地理解这一点,让我们创建一个 nginx 部署(Deployment),这一次我们将在 Pod 内部设置资源限制。资源限制是使 HorizontalPodAutoscaler 资源能够工作的关键因素。它依赖于限制的百分比利用率来决定何时启动新的副本。我们将使用以下 nginx-autoscale-deployment.yaml 清单文件,路径为 ~/modern-devops/ch6/deployments 来进行这个练习:
...
spec:
replicas: 1
template:
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
cpu: 200m
memory: 200Mi
...
使用以下命令执行新的部署:
$ kubectl apply -f nginx-autoscale-deployment.yaml
让我们使用 LoadBalancer 类型的 Service 资源来暴露这个部署,并获取外部 IP:
$ kubectl expose deployment nginx --port 80 --type LoadBalancer
$ kubectl get svc nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
nginx LoadBalancer 10.3.243.225 34.123.234.57 80:30099/TCP
现在,让我们对这个部署进行自动扩缩。Deployment 资源需要至少 1 个 Pod 副本,最多可以有 5 个 Pod 副本,同时保持平均 CPU 利用率为 25%。使用以下命令创建一个 HorizontalPodAutoscaler 资源:
$ kubectl autoscale deployment nginx --cpu-percent=25 --min=1 --max=5
现在我们已经创建了 HorizontalPodAutoscaler 资源,我们可以使用 Google Cloud Shell 中预安装的 hey 负载测试工具对应用程序进行负载测试。但是,在启动负载测试之前,打开一个复制的 Shell 会话,并使用以下命令查看 Deployment 资源:
$ kubectl get deployment nginx -w
打开另一个复制的 Shell 会话,并使用以下命令查看 HorizontalPodAutoscaler 资源:
$ kubectl get hpa nginx -w
现在,在原始窗口中,运行以下命令以启动负载测试:
$ hey -z 120s -c 100 http://34.123.234.57
它将开始一个持续 2 分钟的负载测试,10 个并发用户持续地攻击 Service。如果你打开正在监控 HorizontalPodAutoscaler 资源的窗口,你将看到以下输出。当我们开始执行负载测试时,平均利用率达到了 46%。HorizontalPodAutoscaler 资源等待一段时间后,增加副本数,首先是 2,然后是 4,最后是 5。当测试完成时,利用率迅速下降至 27%,25%,最终降至 0%。当利用率降至 0% 时,HorizontalPodAutoscaler 资源会逐渐减少副本数,从 5 减少到 1:
$ kubectl get hpa nginx -w
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
nginx deployment/nginx <unknown>/25% 1 5 1 32s
nginx deployment/nginx 46%/25% 1 5 1 71s
nginx deployment/nginx 46%/25% 1 5 2 92s
nginx deployment/nginx 92%/25% 1 5 4 2m2s
nginx deployment/nginx 66%/25% 1 5 5 2m32s
nginx deployment/nginx 57%/25% 1 5 5 2m41s
nginx deployment/nginx 27%/25% 1 5 5 3m11s
nginx deployment/nginx 23%/25% 1 5 5 3m41s
nginx deployment/nginx 0%/25% 1 5 4 4m23s
nginx deployment/nginx 0%/25% 1 5 2 5m53s
nginx deployment/nginx 0%/25% 1 5 1 6m30s
同样地,我们将看到当 HorizontalPodAutoscaler 资源进行操作时,Deployment 的副本数发生变化:
$ kubectl get deployment nginx -w
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 1/1 1 1 18s
nginx 1/2 1 1 77s
nginx 2/2 2 2 79s
nginx 2/4 2 2 107s
nginx 3/4 4 3 108s
nginx 4/4 4 4 109s
nginx 4/5 4 4 2m17s
nginx 5/5 5 5 2m19s
nginx 4/4 4 4 4m23s
nginx 2/2 2 2 5m53s
nginx 1/1 1 1 6m30s
除了 CPU 和内存之外,你还可以使用其他参数来扩展你的工作负载,例如网络流量。你还可以使用外部度量指标,比如延迟以及其他因素,来决定何时扩展你的流量。
提示
虽然你应该使用 HorizontalPodAutoscaler 资源来处理 CPU 和内存,但你也应考虑基于外部度量(如响应时间和网络延迟)进行扩展。这将确保更好的可靠性,因为这些直接影响客户体验,对你的业务至关重要。
到目前为止,我们一直在处理无状态的工作负载。然而,从实际的角度来看,一些应用程序需要保存状态。让我们来看一下管理有状态应用程序的一些考虑因素。
管理有状态的应用程序
想象一下,你是一个魔法图书馆的馆长。你有一堆存储着宝贵知识的魔法书。每本书都有独特的故事,并且被放在书架上的特定位置。这些书就像你的“有状态应用程序”,管理它们需要额外的细心。
在技术世界中管理有状态应用程序,就像在你的图书馆中照料这些魔法书一样。
以下是它的工作原理:
-
有状态的书籍:你书库中的一些书是“有状态的”。这意味着它们保存着随着时间变化的重要信息,例如书签或读者的笔记。
-
固定位置:就像每本书在书架上都有一个特定的位置一样,有状态的应用程序也必须位于特定的位置。它们可能需要放在某些机器上,或者使用特定的存储来确保数据的安全。
-
维护库存:你必须记住每本书的位置。同样,管理有状态应用程序意味着要记住它们的确切位置和配置。
-
小心处理:当有人借阅一本有状态的书时,你必须确保他们归还时书籍完好无损。同样,对于有状态的应用程序,你必须小心地处理更新和变更,以避免丢失重要数据。
-
备份法术:有时,你施放一个法术来创建一本书的副本,以防万一原本的书出了问题。对于有状态的应用程序,你会备份数据,以便在出现问题时恢复它。
-
谨慎移动:如果你需要重新安排图书馆的布局,你会一次移动一本书,以确保没有书籍丢失。同样,对于有状态的应用程序,如果你需要在机器或存储之间移动它们,必须谨慎操作,以避免数据丢失。
在技术的世界里,管理有状态应用程序意味着要特别小心那些保存重要数据的应用程序。你需要确保它们放置在正确的位置,仔细处理更新,并创建备份以确保宝贵的信息安全,就像你在神奇的图书馆中保护你的魔法书一样!
部署资源适用于无状态工作负载,因为它们在更新副本集资源时不需要考虑任何状态问题,但它们无法有效地与有状态工作负载一起工作。要管理此类工作负载,可以使用有状态副本集资源。
有状态副本集资源
有状态副本集资源有助于管理有状态应用程序。它们类似于部署资源,但与部署资源不同,它们还会跟踪状态,并且需要卷和服务资源来运行。有状态副本集资源为每个 pod 维护一个粘性标识符。这意味着一个 pod 上挂载的卷不能被另一个 pod 使用。在有状态副本集资源中,Kubernetes 通过为 pods 编号而不是生成随机哈希来为 pods 排序。有状态副本集资源中的 pods 也按顺序进行滚动更新和缩容。如果某个 pod 崩溃并被重新创建,则相同的卷会被挂载到该 pod 上。
下图展示了一个有状态副本集资源:
图 6.8 – 有状态副本集资源
有状态副本集资源具有稳定且唯一的网络标识符,因此,它需要一个无头服务资源。无头服务是没有集群 IP 的服务资源。相反,Kubernetes DNS 会将服务资源的 FQDN 直接解析到 pods。
由于有状态副本集资源需要持久化数据,因此它需要持久卷才能运行。因此,让我们看看如何使用 Kubernetes 管理卷。
管理持久卷
持久卷是 Kubernetes 资源,用于处理存储。它们可以帮助您管理和挂载硬盘、固态硬盘、文件存储以及其他块存储和网络存储实体。您可以手动配置持久卷,也可以在 Kubernetes 中使用动态配置。当使用动态配置时,Kubernetes 会通过云控制器管理器请求云提供商提供所需的存储。让我们看看两种方法,了解它们如何工作。
静态配置
使用磁盘信息创建的持久卷资源。然后,开发人员可以在他们的有状态副本集资源中使用此持久卷资源,如下图所示:
图 6.9 – 静态配置
现在让我们来看一个静态配置的示例。
要访问本节的资源,cd 到以下目录:
$ cd ~/modern-devops/ch6/statefulsets/
因此,我们首先需要在云平台中创建一个磁盘。由于我们使用的是 Google Cloud,我们将继续使用 gcloud 命令来完成此操作。
使用以下命令创建持久化区域磁盘。确保使用与您的 Kubernetes 集群相同的区域。由于我们使用的是us-central1-a区域的 Kubernetes 集群,接下来我们将使用相同的区域:
$ gcloud compute disks create nginx-manual \
--size 50GB --type pd-ssd --zone us-central1-a
Created [https://www.googleapis.com/compute/v1/projects/<project_id>/zones/us-central1-a/
disks/nginx-manual].
NAME ZONE SIZE_GB TYPE STATUS
nginx-manual us-central1-a 50 pd-ssd READY
由于磁盘现在已经准备好,我们可以从中创建一个持久卷资源。
清单文件nginx-manual-pv.yaml如下所示:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nginx-manual-pv
labels:
usage: nginx-manual-disk
spec:
capacity:
storage: 50G
accessModes:
- ReadWriteOnce
gcePersistentDisk:
pdName: nginx-manual
fsType: ext4
spec部分包含capacity、accessModes以及它需要配置的磁盘类型。你可以为 PersistentVolumes 指定一个或多个访问模式:
-
ReadWriteOnce:每次只有一个 Pod 可以读取和写入磁盘;因此,你不能将这样的卷挂载到多个 Pod。 -
ReadOnlyMany:多个 Pod 可以同时读取同一个卷,但没有 Pod 可以写入该磁盘。 -
ReadWriteMany:多个 Pod 可以同时读取和写入同一个卷。
提示
并非所有类型的存储都支持所有的访问模式。你需要在初步需求分析和架构评估阶段决定卷的类型。
好的——现在我们来应用清单,通过以下命令来配置PersistentVolume资源:
$ kubectl apply -f nginx-manual-pv.yaml
现在,让我们使用以下命令检查 PersistentVolume 是否可用:
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS
nginx-manual-pv 50G RWO Retain Available
由于 PersistentVolume 现在已经可用,我们必须创建一个无头的Service资源,帮助在StatefulSet资源中保持网络身份。以下是描述该资源的nginx-manual-service.yaml清单:
apiVersion: v1
kind: Service
metadata:
name: nginx-manual
labels:
app: nginx-manual
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx-manual
它与常规的Service资源非常相似,只是我们将clusterIP设置为None。
现在,让我们使用以下命令来应用清单:
$ kubectl apply -f nginx-manual-service.yaml
随着Service资源的创建,我们可以创建一个使用已创建的PersistentVolume和Service资源的StatefulSet资源。StatefulSet资源清单nginx-manual-statefulset.yaml如下所示:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-manual
spec:
selector:
matchLabels:
app: nginx-manual
serviceName: "nginx-manual"
replicas: 1
template:
metadata:
labels:
app: nginx-manual
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: html
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 40Gi
selector:
matchLabels:
usage: nginx-manual-disk
清单包含了多个部分。虽然大多数部分与Deployment资源清单类似,但它需要一个volume定义和一个单独的volumeClaimTemplates部分。volumeClaimTemplates部分包括accessModes、resources和selector部分。selector部分定义了matchLabels属性,用于选择特定的PersistentVolume资源。在这种情况下,它选择了我们之前定义的PersistentVolume资源。它还包含serviceName属性,定义了它将使用的无头Service资源。
现在,让我们继续使用以下命令应用清单:
$ kubectl apply -f nginx-manual-statefulset.yaml
现在,让我们检查一些元素,看看我们目前的进展。StatefulSet资源创建了一个PersistentVolumeClaim资源,用来声明我们之前创建的PersistentVolume资源。
使用以下命令获取PersistentVolumeClaim资源:
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES
html-nginx-manual-0 Bound nginx-manual-pv 50G RWO
正如我们所看到的,StatefulSet资源已经创建了一个名为html-nginx-manual-0的PersistentVolumeClaim资源,该资源绑定到了nginx-manual-pv的PersistentVolume资源上。因此,手动配置已正确工作。
如果我们使用以下命令查询PersistentVolume资源,我们将看到其状态现在显示为Bound:
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS
nginx-manual-pv 50G RWO Retain Bound
现在,让我们使用以下命令查看 Pod 的状态:
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-manual-0 1/1 Running 0 14s
如我们所见,StatefulSet资源已经创建了一个 Pod,并附加了一个序列号,而不是随机哈希值。它希望在 Pod 之间保持顺序,并将之前挂载的相同卷挂载到这些 Pod 上。
现在,让我们打开 Pod 的 Shell,并使用以下命令在/usr/share/nginx/html目录中创建一个文件:
$ kubectl exec -it nginx-manual-0 -- /bin/bash
root@nginx-manual-0:/# cd /usr/share/nginx/html/
root@nginx-manual-0:/usr/share/nginx/html# echo 'Hello, world' > index.html
root@nginx-manual-0:/usr/share/nginx/html# exit
太好了!那么,让我们继续删除 Pod,并使用以下命令查看是否能在相同位置再次找到文件:
$ kubectl delete pod nginx-manual-0
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-manual-0 1/1 Running 0 3s
$ kubectl exec -it nginx-manual-0 -- /bin/bash
root@nginx-manual-0:/# cd /usr/share/nginx/html/ && cat index.html
Hello, world
root@nginx-manual-0:/usr/share/nginx/html# exit
正如我们所见,即使我们删除了 Pod,文件仍然存在。
静态配置并不是最好的做法,因为你必须手动跟踪和配置卷。这涉及很多手动操作,可能容易出错。一些希望在开发和运维之间保持分隔的组织可能会使用这种技术。Kubernetes 允许这种配置。然而,对于更适合 DevOps 的组织来说,动态配置是更好的方法。
动态配置
动态配置是指 Kubernetes 通过与云服务提供商交互为你提供存储资源。当我们手动配置磁盘时,我们通过gcloud命令行与云 API 进行交互。如果你们的组织后来决定迁移到其他云服务提供商,这将破坏许多现有的脚本,你将不得不重新编写存储配置步骤。Kubernetes 本质上是可移植且平台无关的。你可以在任何云平台上以相同的方式配置资源。
但是,不同的云服务提供商有不同的存储方案。那么,Kubernetes 如何知道它需要配置什么样的存储呢?其实,Kubernetes 使用StorageClass资源来解决这个问题。StorageClass资源是 Kubernetes 资源,定义了在有人使用它时需要提供的存储类型。
下图展示了动态配置的示意:
图 6.10 – 动态配置
让我们看一个存储类清单示例,fast-storage-class.yaml,它在 GCP 中配置了一个 SSD:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd
StorageClass资源包含了一个供应者和该供应者所需的任何参数。你可能已经注意到,我使用了fast这个名称,而不是gce-ssd或类似的名称。原因是我们希望保持名称尽可能通用。
提示
保持通用的存储类名称,如fast、standard、block和shared,避免使用特定于云平台的名称。因为存储类名称会在持久化卷声明中使用,如果你迁移到另一个云服务提供商,你可能会需要修改大量清单来避免混淆。
让我们继续使用以下命令应用清单:
$ kubectl apply -f fast-storage-class.yaml
当StorageClass资源创建完毕后,让我们使用它来动态配置一个nginx的StatefulSet资源。
我们需要先创建一个Service资源清单,nginx-dynamic-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: nginx-dynamic
labels:
app: nginx-dynamic
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx-dynamic
该清单与手动 Service 资源非常相似。让我们继续使用以下命令来应用它:
$ kubectl apply -f nginx-dynamic-service.yaml
现在,让我们查看 StatefulSet 资源清单,nginx-dynamic-statefulset.yaml:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-dynamic
spec:
...
serviceName: "nginx-dynamic"
template:
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
...
volumeClaimTemplates:
- metadata:
name: html
spec:
storageClassName: "fast"
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 40Gi
该清单与手动清单类似,但在 volumeClaimTemplates 部分包含 storageClassName 属性,并且缺少 selector 部分,因为我们正在动态配置存储。使用以下命令应用清单:
$ kubectl apply -f nginx-dynamic-statefulset.yaml
当创建 StatefulSet 资源时,让我们继续使用以下命令检查 PersistentVolumeClaim 和 PersistentVolume 资源:
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
html-nginx-dynamic-0 Bound pvc-6b78 40Gi RWO fast
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM
pvc-6b78 40Gi RWO Delete Bound default/html-nginx-dynamic-0
我们可以看到声明已绑定到一个动态配置的持久卷。现在,让我们继续运行以下命令,以类似的方式测试这个 StatefulSet 资源。
让我们使用以下命令在 nginx-dynamic-0 pod 中创建一个文件:
$ kubectl exec -it nginx-dynamic-0 -- bash
root@nginx-dynamic-0:/# cd /usr/share/nginx/html/
root@nginx-dynamic-0:/usr/share/nginx/html# echo 'Hello, dynamic world' > index.html
root@nginx-dynamic-0:/usr/share/nginx/html# exit
现在,删除 pod,并再次打开一个 shell 会话,通过以下命令检查文件是否存在:
$ kubectl delete pod nginx-dynamic-0
$ kubectl get pod nginx-dynamic-0
NAME READY STATUS RESTARTS AGE
nginx-dynamic-0 1/1 Running 0 13s
$ kubectl exec -it nginx-dynamic-0 -- bash
root@nginx-dynamic-0:/# cd /usr/share/nginx/html/
root@nginx-dynamic-0:/usr/share/nginx/html# cat index.html
Hello, dynamic world
root@nginx-dynamic-0:/usr/share/nginx/html# exit
正如我们所见,即使删除了 pod,文件也存在于卷中。这就是动态配置生效的方式!
您可能已经注意到,本章节中我们多次使用了 kubectl 命令。在您进行一天中的各种活动时,尽可能使用快捷方式和最佳实践是有意义的。让我们看看在使用 kubectl 时的一些最佳实践。
Kubernetes 命令行最佳实践,技巧和窍门
对于经验丰富的 Kubernetes 开发人员和管理员,kubectl 是他们大部分时间运行的命令。以下步骤将简化您的生活,节省大量时间,让您专注于更重要的活动,并使您脱颖而出。
使用别名
大多数系统管理员出于一个很好的理由使用别名——它们节省宝贵的时间。Linux 中的别名是命令的不同名称,它们主要用于缩短最常用的命令;例如,ls -l 变成 ll。
您可以使用以下别名与 kubectl 一起使用,使生活更轻松。
k 代表 kubectl
是的——没错。通过以下别名,您可以使用 k 而不是输入 kubectl:
$ alias k='kubectl'
$ k get node
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready master 5m7s v1.26.1
kind-worker Ready <none> 4m33s v1.26.1
这将节省大量时间和麻烦。
使用 kubectl --dry-run
kubectl --dry-run 可以帮助您从命令生成 YAML 清单,并节省大量输入时间。您可以编写一个命令来生成资源,并附加 --dry-run=client -o yaml 字符串以从该命令生成 YAML 清单。该命令不会在集群中创建资源,而是仅输出清单。以下命令将使用 --dry-run 生成 Pod 清单:
$ kubectl run nginx --image=nginx --dry-run=client -o yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: nginx
name: nginx
spec:
containers:
- image: nginx
name: nginx
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
现在您有一个可以根据自己喜好编辑的骨架 YAML 文件。
现在,想象一天中多次输入此命令!在某些时候,这变得令人疲倦。为什么不使用以下别名缩短它呢?
$ alias kdr='kubectl --dry-run=client -o yaml'
您随后可以使用别名生成其他清单。
要生成一个Deployment资源清单,使用以下命令:
$ kdr create deployment nginx --image=nginx
你可以使用 dry run 来从命令式命令生成几乎所有资源。然而,有些资源没有命令式命令,例如DaemonSet资源。你可以为最接近的资源生成清单并对其进行修改,以适应此类资源。DaemonSet清单与Deployment清单非常相似,因此你可以生成一个Deployment清单并将其修改为适应DaemonSet清单。
现在,让我们来看看一些最常用的kubectl命令及其可能的别名。
kubectl apply和delete的别名
如果你使用清单,你通常会在集群中使用kubectl apply和kubectl delete命令,因此使用以下别名是很有意义的:
$ alias kap='kubectl apply -f'
$ alias kad='kubectl delete -f'
然后,你可以使用以下命令来应用或删除资源:
$ kap nginx-deployment.yaml
$ kad nginx-deployment.yaml
在排查容器问题时,我们大多数人使用busybox。让我们来看看如何优化它。
使用别名通过 busybox 排查容器问题
我们使用以下命令来打开busybox会话:
$ kubectl run busybox-test --image=busybox -it --rm --restart=Never -- <cmd>
现在,白天打开多个busybox会话可能会让人感到疲劳。怎样通过使用以下别名来最小化开销呢?
$ alias kbb='kubectl run busybox-test --image=busybox -it --rm --restart=Never --'
然后,我们可以使用以下命令打开一个新的busybox pod 的 shell 会话:
$ kbb sh
/ #
现在,这样更干净、更简单。同样,你还可以为其他经常使用的命令创建别名。以下是一个示例:
$ alias kgp='kubectl get pods'
$ alias kgn='kubectl get nodes'
$ alias kgs='kubectl get svc'
$ alias kdb='kubectl describe'
$ alias kl='kubectl logs'
$ alias ke='kubectl exec -it'
依此类推,根据你的需求。你也许已经习惯了在bash中使用自动补全功能,当你输入几个单词后按Tab键,命令会自动补全。kubectl也提供了命令的自动补全,但默认情况下并未启用。现在,让我们来看一下如何在bash中启用kubectl的自动补全功能。
使用kubectl的 bash 自动补全
要启用kubectl的bash自动补全功能,使用以下命令:
$ echo "source <(kubectl completion bash)" >> ~/.bashrc
该命令将kubectl的 completion bash命令作为源添加到你的.bashrc文件中。因此,下次你登录到 shell 时,应该就能使用kubectl自动补全功能了。这样在输入命令时可以节省大量时间。
总结
本章开始时,我们通过Deployment和ReplicaSet资源来管理 pods,并讨论了一些关键的 Kubernetes 部署策略。接着,我们研究了 Kubernetes 服务发现和模型,并理解了为什么需要一个单独的实体来将容器暴露给内部或外部世界。然后,我们了解了不同的Service资源及其使用场景。我们还讨论了Ingress资源,并介绍了如何使用它们为容器工作负载创建反向代理。接着,我们深入研究了 Horizontal Pod 自动扩展,并使用多个指标自动扩展 pods。
我们研究了状态考虑因素,并学习了如何使用 PersistentVolume、PersistentVolumeClaim 和 StorageClass 资源进行静态和动态存储配置,并讨论了一些围绕它们的最佳实践。我们还研究了 StatefulSet 资源,这些是帮助你调度和管理有状态容器的关键资源。最后,我们还探讨了有关 kubectl 命令行的一些最佳实践、技巧和窍门,以及如何有效地使用它们。
本章和上一章所涵盖的内容只是 Kubernetes 的核心。Kubernetes 是一个功能庞大的工具,足够写一本完整的书籍,因此这些章节只是给你概述了它的基本内容。请随时查阅 Kubernetes 官方文档的详细资源:kubernetes.io。
在下一章中,我们将深入探讨云计算世界,了解 容器即服务 (CaaS) 和无服务器容器服务。
问题
-
Kubernetes 部署在镜像更新时会删除旧的
ReplicaSet资源。(正确/错误) -
Kubernetes 支持的主要部署策略有哪些?(选择两个)
A. 重建
B. 滚动更新
C. 渐进式慢速发布
D. 最佳努力控制的滚动发布
-
你可以使用哪些类型的资源来将容器暴露到外部?(选择三个)
A.
ClusterIP 服务B.
NodePort 服务C.
LoadBalancer 服务D.
Ingress -
最好的实践是先从
ClusterIP服务开始,必要时再更改服务类型。(正确/错误) -
Deployment资源适用于有状态工作负载。(正确/错误) -
使用 Ingress 时,你可以运行哪些类型的工作负载?
A. HTTP
B. TCP
C. FTP
D. SMTP
-
你会为动态卷配置定义哪些资源?(选择两个)
A.
StorageClassB.
PersistentVolumeClaimC.
PersistentVolumeD.
StatefulSet -
为了使你的水平扩展更具意义,你应该使用哪些参数来扩展你的 Pod?(选择三个)
A. CPU
B. 内存
C. 外部指标,如响应时间
D. 每秒数据包(PPS)
-
在
Ingress资源中,路由形式有哪些?(选择两个)A. 简单
B. 基于路径的
C. 基于名称的
D. 复杂
答案
-
错误。一个镜像部署只是将旧的
ReplicaSet资源缩放到0。 -
A 和 B
-
B、C 和 D
-
正确
-
错误。请改用
StatefulSet资源。 -
A
-
A 和 B
-
A、B 和 C
-
B 和 C
第七章:容器即服务(CaaS)和容器的无服务器计算
在前两章中,我们介绍了 Kubernetes 及其如何帮助无缝管理容器。现在,让我们看看自动化和管理容器部署的其他方式——容器即服务(CaaS)和容器的无服务器计算。CaaS 提供基于容器的虚拟化,抽象了所有管理工作,帮助你管理容器而无需担心底层的基础设施和编排。
对于简单的部署和较少复杂的应用,CaaS 可以成为救世主。无服务器计算是一个广泛的术语,涵盖了无需我们担心背后基础设施的应用。它的一个额外好处是你可以完全专注于应用本身。我们将详细讨论 CaaS 技术,如Amazon Elastic Container Service(Amazon ECS)与Amazon Web Services Fargate(AWS Fargate),并简要讨论其他基于云的 CaaS 服务,如Azure Kubernetes Services(AKS)、Google Kubernetes Engine(GKE)和Google Cloud Run。然后,我们将深入探讨流行的开源无服务器 CaaS 解决方案——Knative。
本章我们将涵盖以下主要内容:
-
无服务器服务的需求
-
Amazon ECS 与弹性计算云(EC2)和 Fargate
-
其他 CaaS 服务
-
使用 Knative 的开源 CaaS
技术要求
本章的练习需要一个有效的 AWS 订阅。AWS 是市场上最受欢迎、功能最丰富的云平台。目前,AWS 为一些产品提供免费套餐。你可以在aws.amazon.com/free注册。尽管本章使用了一些付费服务,但我们会尽量减少在练习中使用的付费服务。
你还需要克隆以下 GitHub 仓库进行一些练习:
github.com/PacktPublishing/Modern-DevOps-Practices-2e
运行以下命令将仓库克隆到你的主目录。然后,cd到ch7目录以访问所需的资源:
$ git clone https://github.com/PacktPublishing/Modern-DevOps-Practices-2e.git \
modern-devops
$ cd modern-devops/ch7
由于仓库中包含带有占位符字符串的文件,你必须将<your_dockerhub_user>字符串替换为实际的 Docker Hub 用户。使用以下命令替换占位符:
$ find ./ -type f -exec sed -i -e \
's/<your_dockerhub_user>/<your actual docker hub user>/g' {} \;
那么,让我们开始吧!
无服务器服务的需求
到目前为止,许多组织一直在集中精力进行基础设施的提供和管理。他们优化围绕他们构建的应用程序的资源、机器和基础设施的数量。然而,他们应该集中精力做他们最擅长的事情——软件开发。除非你的组织打算投入大量资金组建一个昂贵的基础设施团队来做大量繁重的后台工作,否则你最好将精力集中在编写和构建高质量的应用程序上,而不是关注如何以及在哪里运行和优化它们。
无服务器服务为这个问题提供了缓解。你不再需要集中精力关注如何托管基础设施来运行应用程序,而是可以声明你想要运行的内容,无服务器服务会为你管理它。这对于那些没有预算投入大量资金进行基础设施建设的小型企业来说,已经成为一项福音,他们可以快速启动,而无需浪费太多时间搭建和维护基础设施来运行应用程序。
无服务器服务还提供容器和应用程序工作负载的自动部署和扩展。你可以在几分钟甚至几秒钟内从 0 扩展到 100 个实例。最棒的是,在某些服务中,你只需为实际使用的部分付费,而不是为分配的部分付费。
本章将重点介绍一个非常受欢迎的 AWS 容器管理服务——ECS以及 AWS 的容器无服务器服务——AWS Fargate。然后,我们将简要地考察其他云平台的相关服务,最后介绍一种开源的基于容器的无服务器解决方案——Knative。
现在,让我们继续了解 Amazon ECS。
Amazon ECS 与 EC2 和 Fargate
Amazon ECS 是 AWS 提供的一个容器编排平台。它易于使用和管理,在后台使用 Docker,并且可以将工作负载部署到Amazon EC2,这是基于虚拟机(VM)的解决方案,或者AWS Fargate,一种无服务器服务。
这是一个高度可扩展的解决方案,可以在几秒钟内部署容器。它使得托管、运行、停止和启动容器变得非常容易。正如 Kubernetes 提供pods一样,ECS 提供任务,帮助你运行容器工作负载。一个任务可以包含一个或多个根据逻辑关系分组的容器。你还可以将一个或多个任务分组为服务。服务类似于 Kubernetes 控制器,管理任务并确保所需数量的任务副本在正确的时间、正确的地方运行。ECS 使用简单的 API 调用提供许多功能,例如创建、更新、读取和删除任务和服务。
ECS 还允许你根据多个放置策略来放置容器,同时考虑高可用性(HA)和资源优化。你可以根据优先级(成本、可用性或两者的结合)调整放置算法。因此,你可以使用 ECS 运行一次性的批处理工作负载或长期运行的微服务,所有操作都可以通过简单易用的 API 接口完成。
ECS 架构
在我们深入了解 ECS 架构之前,了解一些常见的 AWS 术语是很重要的。我们先来看看一些 AWS 资源:
-
us-east-1、us-west-1、ap-southeast-1、eu-central-1等。 -
us-east-1a、us-east-1b等。 -
AWS 虚拟私有云(VPC):AWS VPC 是你在 AWS 内创建的一个隔离的网络资源。你将一个专用的私有 IP 地址范围关联到它,从中你的其他资源(如 EC2 实例)可以获取其 IP 地址。AWS VPC 跨越一个 AWS 区域。
-
子网:子网顾名思义是在 VPC 内的一个子网络。你必须将提供给 VPC 的 IP 地址范围细分并与子网关联。资源通常位于子网内,每个子网跨越一个可用区(AZ)。
-
路由表:AWS 路由表在 VPC 子网内以及与互联网之间路由流量。每个 AWS 子网通过子网路由表关联与路由表相关联。
-
互联网网关:互联网网关允许 AWS 子网与互联网之间的连接。
-
身份与访问管理(IAM):AWS IAM 帮助你控制用户和其他 AWS 资源对资源的访问。它们帮助你实现基于角色的访问控制(RBAC)以及最小权限原则(PoLP)。
-
Amazon EC2:EC2 允许你在子网内启动虚拟机,也称为实例。
-
AWS 自动扩展组(ASGs):AWS ASG 与 Amazon EC2 一起工作,为你的实例提供高可用性(HA)和可扩展性。它监控你的 EC2 实例,并确保始终有一定数量的健康实例在运行。它还会根据机器负载的增加自动扩展实例,以应对更多的流量。它使用实例配置文件和启动配置来决定新 EC2 实例的属性。
-
Amazon CloudWatch:Amazon CloudWatch 是一项监控和可观察性服务。它允许你收集、跟踪和监控指标、日志文件,并设置警报以在特定条件下采取自动化操作。CloudWatch 有助于理解应用程序的性能、健康状况和资源利用情况。
ECS 是一个基于云的区域服务。当你启动一个 ECS 集群时,实例会跨越多个可用区(AZ),在这些可用区中,你可以使用简单的清单调度任务和服务。ECS 清单非常类似于docker-compose YAML 清单,我们可以在其中指定要运行的任务以及构成服务的任务。
您可以在现有的 VPC 中运行 ECS。我们可以在 Amazon EC2 或 AWS Fargate 中调度任务。
您的 ECS 集群可以附加一个或多个 EC2 实例。您还可以选择通过在 EC2 实例中安装 ECS 节点代理将现有 EC2 实例附加到集群。该代理会将容器的状态和任务信息发送到 ECS 调度器。然后,它与容器运行时交互,以便在节点内调度容器。它们类似于 Kubernetes 生态系统中的kubelet。如果您在 EC2 实例中运行容器,您需要为分配给集群的 EC2 实例数量付费。
如果您打算使用 Fargate,基础设施将完全抽象化,您必须指定容器将消耗的 CPU 和内存数量。您支付的是容器实际消耗的 CPU 和内存,而不是您分配给机器的资源。
提示
尽管在 Fargate 中您只为实际消耗的资源付费,但它比在 EC2 上运行任务更昂贵,尤其是对于长时间运行的服务,如 Web 服务器。一个经验法则是将长时间运行的在线任务放在 EC2 中运行,而将批处理任务放在 Fargate 中运行。这样可以实现最佳的成本优化。
当我们调度一个任务时,AWS 会通过从容器注册表拉取所需的容器镜像,在托管的 EC2 或 Fargate 服务器上启动容器。每个任务都有一个附加的弹性网络接口(ENI)。多个任务会被组合成一个服务,该服务确保所有必需的任务同时运行。
Amazon ECS 使用任务调度器来调度集群上的容器。根据放置逻辑、可用性和成本要求,它会将容器放置在集群的适当节点上。调度器还确保在给定时间节点上运行所需数量的任务。
下图清晰地解释了 ECS 集群架构:
图 7.1 – ECS 架构
Amazon 提供了 ECS 的命令行界面(CLI)用于与 ECS 集群交互。它是一个简单的命令行工具,您可以用来管理 ECS 集群、创建和管理集群上的任务和服务。
现在,让我们继续安装 ECS CLI。
安装 AWS 和 ECS CLI
AWS CLI 作为deb包提供,包含在公共的apt仓库中。要安装它,请运行以下命令:
$ sudo apt update && sudo apt install awscli -y
$ aws --version
aws-cli/1.22.34 Python/3.10.6 Linux/5.19.0-1028-aws botocore/1.23.34
在 Linux 环境中安装 ECS CLI 非常简单。我们只需下载二进制文件并使用以下命令将其移至系统路径:
$ sudo curl -Lo /usr/local/bin/ecs-cli \
https://amazon-ecs-cli.s3.amazonaws.com/ecs-cli-linux-amd64-latest
$ sudo chmod +x /usr/local/bin/ecs-cli
运行以下命令来检查ecs-cli是否已正确安装:
$ ecs-cli --version
ecs-cli version 1.21.0 (bb0b8f0)
如我们所见,ecs-cli已成功安装在我们的系统上。
下一步是允许ecs-cli与您的 AWS API 连接。为此,您需要导出 AWS CLI 环境变量。运行以下命令进行操作:
$ export AWS_SECRET_ACCESS_KEY=...
$ export AWS_ACCESS_KEY_ID=...
$ export AWS_DEFAULT_REGION=...
一旦我们设置了环境变量,ecs-cli 将使用它们来进行 AWS API 的身份验证。在下一部分中,我们将使用 ECS CLI 启动一个 ECS 集群。
启动 ECS 集群
我们可以使用 ECS CLI 命令来启动 ECS 集群。你可以在 EC2 和 Fargate 上运行你的容器,因此首先我们将创建一个运行 EC2 实例的集群。然后,我们将在集群中添加 Fargate 任务。
要连接到你的 EC2 实例,你需要在 AWS 中生成一个密钥对。为此,请运行以下命令:
$ aws ec2 create-key-pair --key-name ecs-keypair
此命令的输出将提供密钥对的 JSON 文件。提取 JSON 文件的密钥材料,并将其保存在一个名为 ecs-keypair.pem 的单独文件中。记得在保存文件时将 \n 字符替换为换行符。
一旦我们生成了密钥对,我们可以使用以下命令通过 ECS CLI 创建一个 ECS 集群:
$ ecs-cli up --keypair ecs-keypair --instance-type t2.micro \
--size 2 --cluster cluster-1 --capability-iam
INFO[0002] Using recommended Amazon Linux 2 AMI with ECS Agent 1.72.0 and Docker version
20.10.23
INFO[0003] Created cluster cluster=cluster-1 region=us-east-1
INFO[0004] Waiting for your cluster resources to be created...
INFO[0130] Cloudformation stack status stackStatus=CREATE_IN_PROGRESS
VPC created: vpc-0448321d209bf75e2
Security Group created: sg-0e30839477f1c9881
Subnet created: subnet-02200afa6716866fa
Subnet created: subnet-099582f6b0d04e419
Cluster creation succeeded.
当我们执行此命令时,AWS 会在后台使用 CloudFormation 启动一堆资源。CloudFormation 是 AWS 的基础设施即代码(IaC)解决方案,它通过可重用的模板帮助你在 AWS 上部署基础设施。CloudFormation 模板包含多个资源,例如 VPC、安全组、VPC 内的子网、路由表、路由、子网路由表关联、互联网网关、IAM 角色、实例配置文件、启动配置、ASG、VPC 网关附件以及集群本身。ASG 包含两个正在运行并为集群提供服务的 EC2 实例。请保留输出的副本;稍后的练习中我们需要使用这些细节。
现在我们的集群已经启动,我们将启动我们的第一个任务。
创建任务定义
ECS 任务类似于 Kubernetes pod。它们是 ECS 的基本构建块,由一个或多个相关的容器组成。任务定义是 ECS 任务的蓝图,定义了 ECS 任务的外观。它们与 docker-compose 文件非常相似,且采用 YAML 格式编写。ECS 还使用所有版本的 docker-compose 允许我们定义任务。它们帮助你定义容器及其镜像、资源需求、运行位置(EC2 或 Fargate)、卷和端口映射以及其他网络需求。
提示
使用 docker-compose 清单来启动任务和服务是一个好主意,因为它将帮助你将配置与开放标准对齐。
任务是一个有限的过程,仅运行一次。即使它是一个长时间运行的过程,例如 Web 服务器,任务仍然只运行一次,等待长时间运行的进程结束(理论上会一直运行)。任务的生命周期遵循Pending(待处理) -> Running(运行中) -> Stopped(已停止)状态。因此,当你调度任务时,任务进入Pending状态,尝试从容器注册表中拉取镜像。然后,它尝试启动容器。一旦容器启动,它就进入Running状态。当容器执行完成或出错时,它将进入Stopped状态。一个启动错误的容器会直接从Pending状态转到Stopped状态。
现在,让我们继续在刚刚创建的 ECS 集群中部署一个nginx Web 服务器任务。
要访问本节的资源,cd到以下目录:
$ cd ~/modern-devops/ch7/ECS/tasks/EC2/
我们将在这里使用docker-compose任务定义。所以,让我们从定义以下docker-compose.yml文件开始:
version: '3'
services:
web:
image: nginx
ports:
- "80:80"
logging:
driver: awslogs
options:
awslogs-group: /aws/webserver
awslogs-region: us-east-1
awslogs-stream-prefix: ecs
YAML 文件定义了一个web容器,使用nginx镜像,主机端口80映射到容器端口80。它使用awslogs日志驱动程序,将日志流式传输到 Amazon CloudWatch。它会将日志流传输到us-east-1区域中的/aws/webserver日志组,并使用ecs流前缀。
任务定义还包括资源定义——也就是说,我们希望为任务保留的资源量。因此,我们将需要定义以下的ecs-params.yaml文件:
version: 1
task_definition:
services:
web:
cpu_shares: 100
mem_limit: 524288000
这个 YAML 文件定义了容器的cpu_shares(以毫核为单位)和mem_limit(以字节为单位)。现在,我们来看看如何将此任务调度为 EC2 任务。
在 ECS 上调度 EC2 任务
让我们使用ecs-cli应用配置并使用以下命令调度任务:
$ ecs-cli compose up --create-log-groups --cluster cluster-1 --launch-type EC2
现在任务已经调度并且容器正在运行,让我们列出所有任务以获取容器的详细信息,并查找它运行的位置。为此,运行以下命令:
$ ecs-cli ps --cluster cluster-1
Name State Ports TaskDefinition
cluster-1/fee1cf28/web RUNNING 34.237.218.7:80->80 EC2:1
如我们所见,Web 容器正在34.237.218.7:80的cluster-1上运行。现在,使用以下命令来 curl 此端点,看看我们能得到什么:
$ curl 34.237.218.7:80
<html>
<head>
<title>Welcome to nginx!</title>
...
</html>
这里,我们看到了默认的nginx首页!我们已经成功地在 ECS 上使用 EC2 启动类型调度了一个容器。你可能想要复制这个任务来处理更多的流量。这被称为水平扩展。我们将在下一部分看到如何实现。
扩展任务
我们可以使用ecs-cli轻松地扩展任务。使用以下命令将任务扩展到2:
$ ecs-cli compose scale 2 --cluster cluster-1 --launch-type EC2
现在,使用以下命令检查集群中是否有两个容器在运行:
$ ecs-cli ps --cluster cluster-1
Name State Ports TaskDefinition
cluster-1/b43bdec7/web RUNNING 54.90.208.183:80->80 EC2:1
cluster-1/fee1cf28/web RUNNING 34.237.218.7:80->80 EC2:1
如我们所见,集群上正在运行两个容器。现在,让我们查询 CloudWatch 以获取容器的日志。
从 CloudWatch 查询容器日志
要从 CloudWatch 查询日志,我们必须使用以下命令列出日志流:
$ aws logs describe-log-streams --log-group-name /aws/webserver \
--log-stream-name-prefix ecs | grep logStreamName
"logStreamName": "ecs/web/b43bdec7",
"logStreamName": "ecs/web/fee1cf28",
如我们所见,这里有两个日志流——每个任务一个。logStreamName 遵循 <log_stream_prefix>/<task_name>/<task_id> 的约定。因此,要获取 ecs/b43bdec7/web 的日志,请运行以下命令:
$ aws logs get-log-events --log-group-name/aws/webserver \
--log-stream ecs/web/b43bdec7
在此,您将在响应中看到以 JSON 格式显示的日志流。现在,让我们来看一下如何停止正在运行的任务。
停止任务
ecs-cli 使用友好的 docker-compose 语法进行所有操作。使用以下命令停止集群中的任务:
$ ecs-cli compose down --cluster cluster-1
让我们列出容器,查看任务是否已停止,使用以下命令:
$ ecs-cli ps --cluster cluster-1
INFO[0001] Stopping container... container=cluster-1/b43bdec7/web
INFO[0001] Stopping container... container=cluster-1/fee1cf28/web
INFO[0008] Stopped container... container=cluster-1/b43bdec7/web
desiredStatus=STOPPED lastStatus=STOPPED taskDefinition="EC2:1"
INFO[0008] Stopped container... container=cluster-1/fee1cf28/web
desiredStatus=STOPPED lastStatus=STOPPED taskDefinition="EC2:1"
如我们所见,两个容器都已停止。
在 EC2 上运行任务并不是一种无服务器的方式。您仍然需要配置和管理 EC2 实例,尽管 ECS 管理集群上的工作负载,但您仍然需要为已配置的 EC2 实例的资源支付费用。AWS 提供了 Fargate 作为一种无服务器解决方案,按资源消耗付费。让我们看看如何将相同的任务创建为 Fargate 任务。
在 ECS 上调度 Fargate 任务
在 Fargate 上调度任务与 EC2 非常相似。在这里,我们需要将launch-type值指定为FARGATE。
要在 Fargate 上调度相同的任务,请运行以下命令:
$ ecs-cli compose up --create-log-groups --cluster cluster-1 --launch-type FARGATE
FATA[0001] ClientException: Fargate only supports network mode 'awsvpc'.
哎呀!我们遇到问题了!它抱怨网络类型。对于 Fargate 任务,我们必须提供网络类型awsvpc,而不是默认的桥接网络。awsvpc网络是一种覆盖网络,实施了awsvpc网络类型。但在此之前,Fargate 任务需要一些配置。
要访问本节的资源,请cd进入以下目录:
$ cd ~/modern-devops/ch7/ECS/tasks/FARGATE/
首先,我们必须假设一个任务执行角色,以便 ECS 代理能够进行 AWS API 认证并与 Fargate 交互。
为此,请创建以下task-execution-assume-role.json文件:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
然后,使用以下命令来假设任务执行角色:
$ aws iam --region us-east-1 create-role --role-name ecsTaskExecutionRole \
--assume-role-policy-document file://task-execution-assume-role.json
ECS 提供了一个默认的角色策略,名为AmazonECSTaskExecutionRolePolicy,其中包含多种权限,帮助您与 CloudWatch 和Elastic Container Registry(ECR)进行交互。以下 JSON 代码概述了该策略的权限:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
我们需要通过以下命令将该角色策略分配给我们之前假设的ecsTaskExecution角色:
$ aws iam attach-role-policy \
--policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy \
--role-name ecsTaskExecutionRole
一旦我们将策略分配给ecsTaskExecution角色,我们需要在创建 ECS 集群时获取两个子网和安全组的 ID。您可以在创建集群时的命令行输出中找到这些详细信息。我们将在以下的ecs-params.yml文件中使用这些详细信息:
version: 1
task_definition:
task_execution_role: ecsTaskExecutionRole
ecs_network_mode: awsvpc
task_size:
mem_limit: 0.5GB
cpu_limit: 256
run_params:
network_configuration:
awsvpc_configuration:
subnets:
- "subnet-088b52c91a6f40fd7"
- "subnet-032cd63290da67271"
security_groups:
- "sg-097206175813aa7e7"
assign_public_ip: ENABLED
ecs-params.yml 文件包括我们创建的 task_execution_role 和设置为 awsvpc 的 ecs_network_mode,因为 Fargate 要求如此。我们已将 task_size 定义为拥有 0.5GB 的内存和 256 毫核 CPU。由于 Fargate 是一种无服务器解决方案,我们只需为所消耗的 CPU 核心和内存付费。run_params 部分包括 network_configuration,其中包含 awsvpc_configuration。在这里,我们指定了在创建 ECS 集群时创建的两个子网。我们还必须指定与 ECS 集群一起创建的 security_groups。
注意
使用你 ECS 集群的子网和安全组,而不是复制本示例中的内容。
现在我们已经准备好在 Fargate 上启动任务,让我们运行以下命令:
$ ecs-cli compose up --create-log-groups --cluster cluster-1 --launch-type FARGATE
现在,让我们通过以下命令检查任务是否成功运行:
$ ecs-cli ps --cluster cluster-1
Name State Ports TaskDefinition
cluster-1/8717a149/web RUNNING 3.80.173.230:80 FARGATE:1
如我们所见,任务正在 3.80.173.230:80 上运行,作为 Fargate 任务。让我们使用以下命令 curl 这个 URL,看看是否能收到响应:
$ curl 3.80.173.230:80
<html>
<head>
<title>Welcome to nginx!</title>
...
</body>
</html>
如我们所见,我们得到了默认的 nginx 首页。
现在,让我们继续使用以下命令删除我们创建的任务:
$ ecs-cli compose down --cluster cluster-1
如我们所知,任务有一个固定的生命周期,一旦停止,它就会停止。你无法再次启动相同的任务。因此,我们必须创建一个服务来确保始终有一定数量的任务在运行。我们将在下一节中创建一个服务。
在 ECS 上调度服务
ecs-cli 命令行。
提示
对于长期运行的应用程序(如 Web 服务器),始终使用服务。对于批处理任务,始终使用任务,因为我们不希望在任务结束后重新创建任务。
要将 nginx Web 服务器作为服务运行,我们可以使用以下命令:
$ ecs-cli compose service up --create-log-groups \
--cluster cluster-1 --launch-type FARGATE
INFO[0001] Using ECS task definition TaskDefinition="FARGATE:1"
INFO[0002] Auto-enabling ECS Managed Tags
INFO[0013] (service FARGATE) has started 1 tasks: (task 9b48084d). timestamp="2023-07-03
11:24:42 +0000 UTC"
INFO[0029] Service status desiredCount=1 runningCount=1 serviceName=FARGATE
INFO[0029] (service FARGATE) has reached a steady state. timestamp="2023-07-03 11:25:00
+0000 UTC"
INFO[0029] (service FARGATE) (deployment ecs-svc/94284856) deployment
completed. timestamp="2023-07-03 11:25:00 UTC"
INFO[0029] ECS Service has reached a stable state desiredCount=1 runningCount=1
serviceName=FARGATE
INFO[0029] Created an ECS service service=FARGATE taskDefinition="FARGATE:1"
从日志中我们可以看到,服务正在尝试确保任务的期望数量与任务的运行数量相匹配。如果你的任务被删除,ECS 会用一个新的任务替换它。
让我们列出任务,看看通过以下命令能得到什么:
$ ecs-cli ps --cluster cluster-1
Name State Ports TaskDefinition
cluster-1/9b48084d/web RUNNING 18.234.123.71:80 FARGATE:1
如我们所见,服务已创建一个新任务,并在 18.234.123.71:80 上运行。让我们尝试通过以下命令访问该 URL:
$ curl 18.234.123.71
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</html>
我们在响应中得到默认的 nginx 首页。现在,让我们尝试浏览任务的日志。
使用 ECS CLI 浏览容器日志
除了使用 Amazon CloudWatch 外,你还可以使用便捷的 ECS CLI 来做到这一点,无论你的日志存储在哪里。这帮助我们从单一视图看到所有内容。
运行以下命令来执行此操作:
$ ecs-cli logs --task-id 9b48084d --cluster cluster-1
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform
configuration
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/
default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2023/07/03 11:24:57 [notice] 1#1: nginx/1.25.1
2023/07/03 11:24:57 [notice] 1#1: built by gcc 12.2.0 (Debian 12.2.0-14)
2023/07/03 11:24:57 [notice] 1#1: OS: Linux 5.10.184-175.731.amzn2.x86_64
2023/07/03 11:24:57 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 65535:65535
2023/07/03 11:24:57 [notice] 1#1: start worker processes
2023/07/03 11:24:57 [notice] 1#1: start worker process 29
2023/07/03 11:24:57 [notice] 1#1: start worker process 30
13.232.8.130 - - [03/Jul/2023:11:30:38 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.81.0"
"-"
如我们所见,我们可以浏览该服务正在运行的特定任务的日志。现在,让我们继续删除该服务。
删除 ECS 服务
要删除服务,运行以下命令:
$ ecs-cli compose service down --cluster cluster-1
INFO[0001] Deleted ECS service service=FARGATE
INFO[0001] Service status desiredCount=0 runningCount=1 serviceName=FARGATE
INFO[0006] Service status desiredCount=0 runningCount=0 serviceName=FARGATE
INFO[0006] (service FARGATE) has stopped 1 running tasks: (task
9b48084d11cf49be85141fd9bfe9e1c3). timestamp="2023-07-03 11:34:10 +0000 UTC"
INFO[0006] ECS Service has reached a stable state desiredCount=0 runningCount=0
serviceName=FARGATE
如我们所见,服务已被删除。
请注意,即使我们创建了多个任务实例,它们会运行在不同的 IP 地址上并且可以单独访问。然而,任务需要负载均衡,我们需要提供一个单一的端点。让我们来看一个可以用来管理这个问题的解决方案。
在 ECS 上运行负载均衡容器
负载均衡是多实例应用程序的重要功能。它们帮助我们在单一端点上提供应用服务。因此,你可以同时运行多个实例,而最终用户不需要关心他们访问的是哪个实例。AWS 提供了两种主要的负载均衡解决方案——第四层的网络负载均衡器(NLB)和第七层的应用负载均衡器(ALB)。
提示
虽然两种负载均衡器各有其使用场景,但第七层负载均衡器对于基于 HTTP 的应用程序具有显著优势。它提供了先进的流量管理功能,例如基于路径和主机的路由。
现在,让我们使用以下命令创建一个 ALB,将其用作我们任务的前端:
$ aws elbv2 create-load-balancer --name ecs-alb --subnets <SUBNET-1> <SUBNET-2> \
--security-groups <SECURITY_GROUP_ID> --region us-east-1
上述命令的输出包含了LoadBalancerARN和DNSName的值。我们将在后续步骤中使用它们,所以请保存输出的副本。
下一步是创建一个目标组。目标组定义了任务的组和它们将监听的端口,负载均衡器将把流量转发到该组。使用以下命令定义目标组:
$ aws elbv2 create-target-group --name target-group --protocol HTTP \
--port 80 --target-type ip --vpc-id <VPC_ID> --region us-east-1
你将在响应中获得targetGroupARN值。请妥善保管,因为我们将在下一步中需要它。
接下来,我们需要在负载均衡器上运行一个监听器。它应该将流量从负载均衡器转发到目标组。使用以下命令来实现:
$ aws elbv2 create-listener --load-balancer-arn <LOAD_BALANCER_ARN> \
--protocol HTTP --port 80 \
--default-actions Type=forward,TargetGroupArn=<TARGET_GROUP_ARN> \
--region us-east-1
你将在响应中获得listenerARN值。请妥善保管,因为我们将在下一步中需要它。
现在我们已经定义了负载均衡器,我们需要运行ecs-cli compose service up来部署我们的服务。我们还将提供目标组作为参数,以将我们的服务与负载均衡器关联。
要访问本节的资源,请cd到以下目录:
$ cd ~/modern-devops/ch7/ECS/loadbalancing/
运行以下命令:
$ ecs-cli compose service up --create-log-groups --cluster cluster-1 \
--launch-type FARGATE --target-group-arn <TARGET_GROUP_ARN> \
--container-name web --container-port 80
既然服务和任务已经在 Fargate 上运行,我们可以将服务扩展到三个所需任务。为此,请运行以下命令:
$ ecs-cli compose service scale 3 --cluster cluster-1
由于我们的服务已经扩展到三个任务,现在让我们访问在第一步中获取的负载均衡器 DNS 端点。这应该会为我们提供默认的nginx响应。运行以下命令:
$ curl ecs-alb-1660189891.us-east-1.elb.amazonaws.com
<html>
<head>
<title>Welcome to nginx!</title>
…
</html>
如我们所见,我们从负载均衡器获得了默认的nginx响应。这表明负载均衡工作正常!
ECS 提供了许多其他功能,如水平自动扩展、可自定义的任务调度算法等,但这些超出了本书的范围。请阅读 ECS 文档,了解该工具的其他方面。现在,让我们看看市场上其他流行的 CaaS 产品。
其他 CaaS 服务
Amazon ECS 提供了一种灵活的方式来管理你的容器工作负载。当你拥有一个较小、较简单的架构时,它非常适用,并且你不想增加使用像 Kubernetes 这样复杂的容器编排引擎的额外开销。
提示
如果你完全依赖于 AWS 并且没有未来的多云或混合云战略,ECS 是一个优秀的工具选择。Fargate 使得部署和运行容器变得更容易,无需担心背后的基础设施。
ECS 与 AWS 及其架构紧密耦合。为了解决这个问题,我们可以使用 AWS 内的托管服务,如弹性 Kubernetes 服务(EKS)。它提供了 Kubernetes API 来调度工作负载。这使得管理容器更加灵活,因为你可以轻松地启动 Kubernetes 集群,并使用一个标准的开源解决方案,在任何地方安装和运行。这不会将你绑定到特定的供应商。然而,EKS 比 ECS 略贵,并且增加了每小时0.10 美元的集群管理费用。但与它带来的好处相比,这点费用算不了什么。
如果你不是在 AWS 上运行,也有其他提供商的选择。下一个大三云服务提供商是 Azure,它提供了Azure Kubernetes 服务(AKS),这是一种托管的 Kubernetes 解决方案,可以帮助你在几分钟内开始使用。AKS 提供了一种完全托管的解决方案,支持按需事件驱动的弹性工作节点供应。它还与Azure DevOps良好集成,为你提供更快的端到端(E2E)开发体验。与 AWS 一样,Azure 也收取每小时0.10 美元的集群管理费用。
**Google Kubernetes Engine(GKE)**是最强大的 Kubernetes 平台之一。由于 Kubernetes 项目来自 Google,并且它是该项目在开源社区中的最大贡献者,GKE 通常会更快推出新版本,并且是第一个发布安全补丁的解决方案。此外,它是最具功能性的 Kubernetes 平台之一,提供了可自定义的解决方案,并提供了多个插件作为集群配置。因此,你可以选择在启动时安装哪些内容,并进一步加固集群。然而,所有这些都需要付出代价,因为 GKE 与 AWS 和 Azure 一样,收取每小时0.10 美元的集群管理费用。
如果你的架构不复杂,并且只需要管理少量容器,你可以使用 Google Cloud Run,而不必使用 Kubernetes。Google Cloud Run 是基于开源 Knative 项目构建的无服务器 CaaS 解决方案,帮助你运行容器而不受供应商锁定的限制。由于它是无服务器的,你只需为使用的容器数量及其资源利用付费。它是一个完全可扩展、与 Google Cloud 的 DevOps 和监控解决方案(如 Cloud Code、Cloud Build、Cloud Monitoring 和 Cloud Logging)良好集成的解决方案。最棒的是,它可以与 AWS Fargate 相媲美,并将所有基础设施工作抽象化。因此,它是一个最小运维或无运维的解决方案。
既然我们提到了 Knative 作为开源 CaaS 解决方案,接下来我们将更详细地讨论它。
基于 Knative 的开源 CaaS
正如我们所见,市场上已有多种供应商特定的 CaaS 服务。然而,大多数服务的问题在于它们绑定于单一的云服务提供商。我们的容器部署规范因此会变得供应商特定,最终导致供应商锁定。作为现代的 DevOps 工程师,我们必须确保所提议的解决方案最适合架构需求,而避免供应商锁定是最重要的要求之一。
然而,Kubernetes 本身并不是无服务器的。你必须定义基础设施,并且长期运行的服务在特定时间应该至少有一个实例运行。这使得管理微服务应用变得繁琐且资源密集。
等等!我们说过微服务有助于优化基础设施的使用。没错——这是正确的,但它们是在容器空间内实现这一点的。试想一下,你有一个共享的虚拟机集群,应用的不同部分会随流量进行扩展,而每个部分的应用会有不同的高峰和低谷。通过这种简单的多租户方式,可以节省大量基础设施资源。
然而,这也意味着每次都必须运行每个微服务的至少一个实例——即使没有任何流量!嗯,这并不是我们所期望的最佳利用方式。那怎样做才更好呢?如何在第一次请求时创建实例,而在没有流量时不创建实例呢?这样可以节省大量资源,特别是在空闲时。你可以拥有成百上千个微服务组成应用,而在空闲期间,这些微服务是没有实例的。如果将其与管理的 Kubernetes 服务结合,并通过流量自动扩展虚拟机实例,你就可以在空闲期间保持最少的实例。
在开源和云原生领域,曾有人尝试开发一种开源、供应商无关、无服务器框架用于容器。我们有 Knative,它是 云原生计算基金会 (CNCF) 采用的解决方案。
提示
Cloud Run 服务在后台使用 Knative。所以,如果你使用 Google Cloud,你可以使用 Cloud Run 来使用完全托管的无服务器服务。
要了解 Knative 是如何工作的,我们先来看看 Knative 的架构。
Knative 架构
Knative 项目结合了现有 CNCF 项目的元素,如 Kubernetes 和 kubectl 命令行。Knative 为开发者提供了其 API,开发者可以通过 kn 命令行工具使用它。用户通过 Istio 访问这些 API,而 Istio 的流量管理功能是 Knative 的一个关键组件。以下图表描述了这一过程:
图 7.2 – Knative 架构
Knative 由两个主要模块组成——serving 和 eventing。serving 模块帮助我们使用 HTTP/S 端点维护无状态应用程序,而 eventing 模块与事件引擎(如 Kafka 和 Google Pub/Sub)集成。由于我们主要讨论的是 HTTP/S 流量,本书将重点讨论 Knative 的 serving 模块。
Knative 维护服务 Pod,这些 Pod 帮助在工作负载 Pod 内路由流量,并使用 Istio Ingress Gateway 组件作为代理。它为你的服务提供一个虚拟端点并监听该端点。当它发现该端点有请求时,它会创建所需的 Kubernetes 组件来处理该流量。因此,Knative 具备从零工作负载 Pod 扩展的功能,因为当接收到流量时,它会启动一个 Pod。以下图示说明了这一过程:
图 7.3 – Knative 服务架构
Knative 端点由三部分组成——<app-name>、<namespace> 和 <custom-domain>。其中 name 和 namespace 类似于 Kubernetes 服务,而 custom-domain 是我们定义的。它可以是你所在组织的合法域名,也可以是 MagicDNS 解决方案,如 sslip.io,我们将在实践中使用。如果你使用的是自己组织的域名,你必须创建 DNS 配置,将该域名解析到 Istio Ingress Gateway 的 IP 地址。
现在,让我们继续安装 Knative。
对于练习,我们将使用 GKE。由于 GKE 是一个高度可靠的 Kubernetes 集群,因此它非常适合与 Knative 集成。如前所述,Google Cloud 提供 90 天 $300 的免费试用。如果你还没有注册,可以在 cloud.google.com/free 上注册。
启动 GKE
一旦你注册并进入控制台,你可以打开 Google Cloud Shell CLI 来运行以下命令。
你需要先使用以下命令启用 GKE API:
$ gcloud services enable container.googleapis.com
要创建一个从 1 个节点扩展到 5 个节点的自动扩展 GKE 集群,请运行以下命令:
$ gcloud container clusters create cluster-1 --num-nodes 2 \
--enable-autoscaling --min-nodes 1 --max-nodes 5 --zone us-central1-a
就这样!集群已启动并运行。
你还需要克隆以下 GitHub 仓库以进行一些练习:
github.com/PacktPublishing/Modern-DevOps-Practices-2e
运行以下命令将仓库克隆到你的主目录中。然后,cd 到 ch7 目录以访问所需资源:
$ git clone https://github.com/PacktPublishing/Modern-DevOps-Practices-2e.git \
modern-devops
既然集群已经启动并运行,让我们继续安装 Knative。
安装 Knative
我们将安装定义 Knative 资源作为 Kubernetes API 资源的 CRD。
要访问本节的资源,cd 到以下目录:
$ cd ~/modern-devops/ch7/knative/
运行以下命令安装 CRD:
$ kubectl apply -f \
https://github.com/knative/serving/releases/download/knative-v1.10.2/serving-crds.yaml
如我们所见,Kubernetes 已安装了一些 CRD。接下来,我们必须安装 Knative serving 模块的核心组件。使用以下命令来执行此操作:
$ kubectl apply -f \
https://github.com/knative/serving/releases/download/knative-v1.10.2/serving-core.yaml
既然核心服务组件已经安装,下一步是在 Kubernetes 集群中安装 Istio。为此,请运行以下命令:
$ curl -L https://istio.io/downloadIstio | sh -
$ sudo mv istio-*/bin/istioctl /usr/local/bin
$ istioctl install --set profile=demo -y
既然 Istio 已经安装,我们将等待 Istio Ingress Gateway 组件分配外部 IP 地址。运行以下命令检查,直到响应中返回外部 IP:
$ kubectl -n istio-system get service istio-ingressgateway
NAME TYPE EXTERNAL-IP PORT(S)
istio-ingressgteway LoadBalancer 35.226.198.46 15021,80,443
如我们所见,已分配给我们一个外部 IP——35.226.198.46。我们将在接下来的操作中使用此 IP。
现在,我们将使用以下命令安装 Knative Istio 控制器:
$ kubectl apply -f \
https://github.com/knative/net-istio/releases/download/knative-v1.10.1/net-istio.yaml
既然控制器已经安装,我们必须配置 DNS,以便 Knative 可以提供自定义端点。为此,我们可以使用 MagicDNS 解决方案,称为 sslip.io,你可以在实验中使用它。MagicDNS 解决方案将任何端点解析到子域中存在的 IP 地址。例如,35.226.198.46.sslip.io 会解析到 35.226.198.46。
注意
在生产环境中不要使用 MagicDNS。它是一个实验性的 DNS 服务,应该仅用于评估 Knative。
运行以下命令配置 DNS:
$ kubectl apply -f \
https://github.com/knative/serving/releases/download/knative-v1.10.2\
/serving-default-domain.yaml
如你所见,它提供了一个批处理作业,当 DNS 请求发生时会触发。
现在,让我们安装 HorizontalPodAutoscaler (HPA) 插件,以便在集群上随着流量自动扩展 pod。为此,请运行以下命令:
$ kubectl apply -f \
https://github.com/knative/serving/releases/download/knative-v1.10.2/serving-hpa.yaml
这完成了 Knative 的安装。
现在,我们需要安装并配置 kn 命令行工具。使用以下命令来执行此操作:
$ sudo curl -Lo /usr/local/bin/kn \
https://github.com/knative/client/releases/download/knative-v1.10.0/kn-linux-amd64
$ sudo chmod +x /usr/local/bin/kn
在下一节中,我们将部署第一个 Knative 应用程序。
在 Knative 上部署 Python Flask 应用程序
为了理解 Knative,让我们尝试构建并部署一个 Flask 应用程序,该应用程序在响应中输出当前的时间戳。让我们从构建应用开始。
构建 Python Flask 应用程序
我们需要创建几个文件来构建这样的应用。
app.py 文件如下所示:
import os
import datetime
from flask import Flask
app = Flask(__name__)
@app.route('/')
def current_time():
ct = datetime.datetime.now()
return 'The current time is : {}!\n'.format(ct)
if __name__ == "__main__":
app.run(debug=True,host='0.0.0.0')
我们将需要以下 Dockerfile 来构建这个应用:
FROM python:3.7-slim
ENV PYTHONUNBUFFERED True
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . ./
RUN pip install Flask gunicorn
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 app:app
现在,让我们使用以下命令来构建 Docker 容器:
$ docker build -t <your_dockerhub_user>/py-time .
既然镜像已经准备好,我们让我们使用以下命令将其推送到 Docker Hub:
$ docker push <your_dockerhub_user>/py-time
既然我们已经成功推送了镜像,就可以在 Knative 上运行它了。
在 Knative 上部署 Python Flask 应用
我们可以使用 kn 命令行或创建一个清单文件来部署应用。使用以下命令来部署应用:
$ kn service create py-time --image <your_dockerhub_user>/py-time
Creating service 'py-time' in namespace 'default':
9.412s Configuration "py-time" is waiting for a Revision to become ready.
9.652s Ingress has not yet been reconciled.
9.847s Ready to serve.
Service 'py-time' created to latest revision 'py-time-00001' is available at URL:
http://py-time.default.35.226.198.46.sslip.io
如我们所见,Knative 已经部署了应用并提供了一个自定义端点。让我们使用 curl 访问该端点看看返回的结果:
$ curl http://py-time.default.35.226.198.46.sslip.io
The current time is : 2023-07-03 13:30:20.804790!
我们在响应中获得了当前时间。正如我们所知道的,Knative 应该会检测到 pod 没有流量并将其删除。让我们观察 pod 一段时间,看看会发生什么:
$ kubectl get pod -w
NAME READY STATUS RESTARTS AGE
py-time-00001-deployment-jqrbk 2/2 Running 0 5s
py-time-00001-deployment-jqrbk 2/2 Terminating 0 64s
如我们所见,在 1 分钟的不活动后,Knative 开始终止 pod。现在,这就是我们所说的从零扩展。
要永久删除服务,我们可以使用以下命令:
$ kn service delete py-time
我们刚才查看了以命令式的方式部署和管理应用程序。那么,如果我们想像之前那样声明配置该怎么办?我们可以创建一个 CRD 清单,使用由 apiVersion 提供的 Service 资源——serving.knative.dev/v1。
我们将创建以下名为 py-time-deploy.yaml 的清单文件:
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: py-time
spec:
template:
spec:
containers:
- image: <your_dockerhub_user>/py-time
既然我们已经创建了这个文件,我们将使用 kubectl CLI 来应用它。这使得部署与 Kubernetes 保持一致。
注意
虽然这是一个 service 资源,但不要将其与典型的 Kubernetes Service 资源混淆。它是由 apiVersion serving.knative.dev/v1 提供的自定义资源。这就是为什么 apiVersion 非常重要。
让我们继续运行以下命令来实现:
$ kubectl apply -f py-time-deploy.yaml
service.serving.knative.dev/py-time created
到此,服务已经创建完成。要获取服务的端点,我们需要通过 kubectl 查询 ksvc 资源。运行以下命令来实现:
$ kubectl get ksvc py-time
NAME URL
py-time http://py-time.default.35.226.198.46.sslip.io
URL 是我们必须访问的自定义端点。让我们使用以下命令 curl 自定义端点:
$ curl http://py-time.default.35.226.198.46.sslip.io
The current time is : 2023-07-03 13:30:23.345223!
这次我们也得到了相同的响应!所以,如果你想继续使用 kubectl 来管理 Knative,你完全可以这么做。
Knative 根据接收到的负载帮助扩展应用——自动水平扩展。让我们对应用进行负载测试,以观察它的实际应用。
在 Knative 上进行应用负载测试
我们将使用 hey 工具进行负载测试。由于您的应用程序已经部署好,运行以下命令来进行负载测试:
$ hey -z 30s -c 500 http://py-time.default.35.226.198.46.sslip.io
执行完命令后,运行以下命令来查看当前运行的 py-time pod 实例:
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
py-time-00001-deployment-52vjv 2/2 Running 0 44s
py-time-00001-deployment-bhhvm 2/2 Running 0 44s
py-time-00001-deployment-h6qr5 2/2 Running 0 42s
py-time-00001-deployment-h92jp 2/2 Running 0 40s
py-time-00001-deployment-p27gl 2/2 Running 0 88s
py-time-00001-deployment-tdwrh 2/2 Running 0 38s
py-time-00001-deployment-zsgcg 2/2 Running 0 42s
如我们所见,Knative 已经创建了七个 py-time pod 实例。这就是水平自动扩展的实际应用。
现在,让我们通过运行以下命令来查看集群节点:
$ kubectl get nodes
NAME STATUS AGE
gke-cluster-1-default-pool-353b3ed4-js71 Ready 3m17s
gke-cluster-1-default-pool-353b3ed4-mx83 Ready 106m
gke-cluster-1-default-pool-353b3ed4-vf7q Ready 106m
如我们所见,GKE 已经在节点池中创建了另一个节点,因为它接收到了额外的流量激增。这非常了不起,因为我们拥有 Kubernetes API 可以实现我们想要的功能。我们已经自动水平扩展了我们的 pod。我们还自动水平扩展了集群的工作节点。这意味着我们有了一个完全自动化的解决方案,可以运行容器,而不必担心管理的细节!这就是开源无服务器架构的实际应用!
总结
本章节介绍了 CaaS 和无服务器 CaaS 服务。这些服务帮助我们轻松管理容器应用,无需担心底层基础设施和它们的管理。我们以亚马逊的 ECS 为例,进行了深入讲解。接着,我们简要讨论了市场上其他可用的解决方案。
最后,我们介绍了 Knative,这是一个开源无服务器解决方案,适用于运行在 Kubernetes 之上的容器,并使用了许多其他开源 CNCF 项目。
在下一章节中,我们将深入探讨使用 Terraform 的基础设施即代码(IaC)。
问题
-
ECS 允许我们部署到以下哪些环境?(选择两个)
A. EC2
B. AWS Lambda
C. Fargate
D. 亚马逊 Lightsail
-
ECS 背后使用了 Kubernetes。(对/错)
-
我们应该始终使用 ECS 中的服务而不是任务来处理批处理作业。(对/错)
-
我们应该始终使用 Fargate 来处理批处理作业,因为它运行时间短,而且我们只需为在此期间消耗的资源付费。(对/错)
-
以下哪些是实现 Kubernetes API 的 CaaS 服务?(选择三个)
A. GKE
B. AKS
C. EKS
D. ECS
-
Google Cloud Run 是一项无服务器服务,背后使用了 Knative。(对/错)
-
以下哪一项是作为 Knative 模块提供的?(选择两个)
A. 服务管理
B. 事件管理
C. 计算
D. 容器
答案
-
A, C
-
错
-
错
-
对
-
A, B, C
-
对
-
A, B
第三部分:管理配置与基础设施
本部分深入探讨了公共云中的基础设施与配置管理,探索了各种能够实现基础设施自动化、配置管理和不可变基础设施的工具。
本部分包含以下章节:
-
第八章,使用 Terraform 实现基础设施即代码(IaC)
-
第九章,使用 Ansible 实现配置管理
-
第十章,使用 Packer 实现不可变基础设施

被折叠的 条评论
为什么被折叠?



