Kubernetes 网络介绍(六)
我们将讨论 Kubernetes 中的网络抽象,主要涉及服务发现和负载均衡。最值得注意的是,后续是关于Service 和Ingress的。由于选项众多,试图解决多种用例,这两个资源历来非常复杂。它们是 Kubernetes 网络堆栈中最显眼的部分,因为它们定义了 Kubernetes 上工作负载的基本网络特性。这是开发人员与 Kubernetes 上部署的应用程序的网络堆栈进行交互的地方。本文将涵盖 Kubernetes 网络抽象的基本示例及其工作原理的详细信息。为了跟上进度,您需要以下工具:
Docker KIND Linkerd
您需要熟悉 kubectl exec 和 Docker exec 命令。如果您不熟悉,我们的代码仓库将包含我们讨论的所有命令,所以不必过于担心。请注意,大多数这些工具主要用于调试和显示实现细节;在正常操作中,您可能不一定需要它们。Docker、KIND 和 Linkerd 的安装可在各自的网站上获取。
将探讨这些 Kubernetes 网络抽象:
StatefulSets Endpoints Endpoint slices Services NodePort Cluster Headless External LoadBalancer Ingress Ingress controller Ingress rules Service meshes Linkerd
为了探索这些抽象概念,我们将使用以下步骤在我们的 Kubernetes 集群中部署示例:
部署一个带有 ingress 启用的 KIND 集群。 探索 StatefulSets。 部署 Kubernetes 服务。 部署一个 ingress 控制器。 部署一个 Linkerd 服务网格。
这些抽象概念是 Kubernetes API 为开发人员和管理员提供,以程序化控制集群内外通信流的核心。理解并掌握如何部署这些抽象概念对于集群内任何工作负载的成功至关重要。在处理这些示例后,您将了解在特定情况下为应用程序使用哪些抽象概念。
使用 KIND 集群配置 YAML,我们可以通过下一段命令使用 KIND 创建该集群。如果这是第一次运行,它将花费一些时间下载所有用于工作和控制平面的 Docker 镜像。
Statefulsets
StatefulSets 是 Kubernetes 中用于管理类似部署的容器的一种工作负载抽象。与部署不同,StatefulSets 为需要它们的应用程序添加了以下功能:
稳定、独特的网络标识符 稳定且持久的存储 有序,优雅的部署和扩展 有顺序的,自动化的滚动更新
部署资源更适合那些不需要这些要求的应用程序(例如,一个将数据存储在外部数据库中的服务)。我们为 Golang 最小化 Web 服务器使用的数据库使用了 StatefulSet。数据库包含一个服务,一个用于 Postgres 用户名的 ConfigMap,一个密码,一个测试数据库名称,以及一个 StatefulSet 用于运行 Postgres 的容器。我们现在来部署它:
kubectl apply -f database.yaml
service/postgres created
configmap/postgres-config created
statefulset.apps/postgres created
让我们探讨使用 StatefulSet 时 DNS 和网络的影响。
为了在集群内部测试 DNS,我们可以使用 dnsutils 镜像;这个镜像为 gcr.io/kubernetes-e2e-test-images/dnsutils:1.3,用于 Kubernetes 测试:
kubectl apply -f dnsutils.yaml
pod/dnsutils created
kubectl get pods
NAME READY STATUS RESTARTS AGE
dnsutils 1/1 Running 0 9s
配置了两个副本后,我们看到 StatefulSet 部署了 postgres-0 和 postgres-1,按照顺序,具有 IP 地址分别为 10.244.1.3 和 10.244.2.3 的特性:
| kubectl get pods -o wide NAME READY STATUS | RESTARTS | AGE | IP | NODE | | |
|----------------------------------------------|------------|---------|------|--------|------------|--------------|
| dnsutils | 1/1 | Running | 0 | 15m | 10.244.3.2 | kind-worker3 |
| postgres-0 | 1/1 | Running | 0 | 15m | 10.244.1.3 | kind-worker2 |
| postgres-1 | 1/1 | Running | 0 | 14m | 10.244.2.3 | kind-worker |
这是我们的无头服务 Postgres 的名称,客户可以使用它进行查询以返回端点 IP 地址:
| kubectl get svc postgres NAME TYPE CLUSTER-IP | EXTERNAL-IP | PORT(S) | AGE | |
|-------------------------------------------------|---------------|-----------|----------|-----|
| postgres | ClusterIP | <none> | 5432/TCP | 23m |
使用我们的 dnsutils 镜像,我们可以看到 StatefulSets 的 DNS 名称将返回这些 IP 地址以及 Postgres 服务的集群 IP 地址:
kubectl exec dnsutils -- host postgres-0.postgres.default.svc.cluster.local.
postgres-0.postgres.default.svc.cluster.local has address 10.244.1.3
kubectl exec dnsutils -- host postgres-1.postgres.default.svc.cluster.local.
postgres-1.postgres.default.svc.cluster.local has address 10.244.2.3
kubectl exec dnsutils -- host postgres
postgres.default.svc.cluster.local has address 10.105.214.153
StatefulSets 尝试模仿一组固定的持久化机器。作为状态化工作负载的通用解决方案,特定行为在特定使用场景中可能会令人沮丧。用户经常遇到的一个问题是,在使用.spec .updateStrategy.type: RollingUpdate,和.spec.podManagementPolicy: OrderedReady 这两个默认设置时,更新可能需要手动干预来修复。在这种设置下,如果更新后的容器从未变得可用,用户必须手动干预。此外,StatefulSets 需要一个服务,最好是无头的,负责容器的网络标识,最终用户负责创建这个服务。StatefulSets 提供了许多配置选项,并且存在许多第三方替代方案(包括通用状态化工作负载控制器和针对特定软件的工作负载控制器)。StatefulSets 在 Kubernetes 中为特定用例提供功能。它们不应用于日常应用程序部署。在本节稍后,我们将讨论更合适的网络抽象,适用于常规部署。在我们的下一部分,我们将探讨端点和端点切片,这是 Kubernetes 服务的核心。
Endpoints
端点帮助识别服务所驱动的运行中的容器。端点由服务创建和管理。稍后我们会单独讨论服务,以避免一次性覆盖太多新内容。目前,只需知道服务包含一个标准的标签选择器,该选择器定义了哪些容器在端点中运行。
在图 5-1 中,我们可以看到流量被引导到节点 2,pod 5 上的一个端点。
图 5-1. 服务中的端点
让我们讨论如何在集群中创建和维护这个端点。每个端点包含一个端口列表(适用于所有容器)和两个地址列表:就绪和未就绪:
apiVersion: v1
kind: Endpoints
metadata:
labels:
name: demo-endpoints
subsets:
- addresses:
- ip: 10.0.0.1
notReadyAddresses:
- ip: 10.0.0.2
ports:
- port: 8080
protocol: TCP
地址如果通过了 pod 就绪检查,则列在 .addresses 中。如果未通过,则列在 .notReadyAddresses 中。这使得端点成为服务发现工具,您可以通过监控 Endpoints 对象来查看所有 pod 的健康状况和地址:
kubectl get endpoints clusterip-service
NAME ENDPOINTS
clusterip-service 10.244.1.5:8080,10.244.2.7:8080,10.244.2.8:8080 + 1 more...
我们可以使用 kubectl describe 查看所有地址的更详细信息:
kubectl describe endpoints clusterip-service
Name: clusterip-service
Namespace: default
Labels: app=app
Annotations: endpoints.kubernetes.io/last-change-trigger-time=2021-01-30T18:51:36Z
Subsets:
Addresses:
- 10.244.1.5
- 10.244.2.7
- 10.244.2.8
- 10.244.3.9
NotReadyAddresses: <none>
Ports:
- Name: <unset>
Port: 8080
Protocol: TCP
Events: <none>
让我们移除应用标签,看看 Kubernetes 会如何响应。在单独的终端中运行这个命令。这将允许我们实时查看 pod 的变化:
kubectl get pods -w
在另一个单独的终端中,我们同样使用端点来做同样的事情:
kubectl get endpoints -w
我们现在需要获取一个要从 Endpoints 对象中移除的 pod 名称:
kubectl apply -f dnsutils.yaml
pod/dnsutils created
kubectl get pods
NAME READY STATUS RESTARTS AGE
dnsutils 1/1 Running 0 9s
0
使用 kubectl label,我们可以修改 pod 的 app-5586fc9d77-7frts app=app 标签:
kubectl apply -f dnsutils.yaml
pod/dnsutils created
kubectl get pods
NAME READY STATUS RESTARTS AGE
dnsutils 1/1 Running 0 9s
1
两端点和容器的命令都会看到一些变化,原因相同:移除了容器上的标签。端点控制器注意到带有 app=app 标签的容器发生了变化,部署控制器也注意到了这一点。因此,Kubernetes 做了它通常会做的事情:让实际状态反映期望的状态:
kubectl apply -f dnsutils.yaml
pod/dnsutils created
kubectl get pods
NAME READY STATUS RESTARTS AGE
dnsutils 1/1 Running 0 9s
2
部署有四个容器,但我们的重新标记的容器仍然存在:app-5586fc9d77-7frts:
kubectl apply -f dnsutils.yaml
pod/dnsutils created
kubectl get pods
NAME READY STATUS RESTARTS AGE
dnsutils 1/1 Running 0 9s
3
pod app-5586fc9d77-6dcg2 现在是部署和端点对象的一部分,具有 IP 地址 10.244.1.6:
kubectl apply -f dnsutils.yaml
pod/dnsutils created
kubectl get pods
NAME READY STATUS RESTARTS AGE
dnsutils 1/1 Running 0 9s
4
如常,我们可以通过 kubectl describe 看到详细的完整视图:
kubectl apply -f dnsutils.yaml
pod/dnsutils created
kubectl get pods
NAME READY STATUS RESTARTS AGE
dnsutils 1/1 Running 0 9s
5
对于大规模部署,该端点对象可能会变得非常大,大到实际上会减慢集群中的更改速度。为了解决这个问题,Kubernetes 的维护者们提出了端点切片(Endpoint Slices)这一概念。
Endpoint Slices
您可能在问,它们与端点有何不同?这就是我们真正开始深入探讨 Kubernetes 网络的细节之处。
在典型的集群中,Kubernetes 在每台节点上运行 kube-proxy。kube-proxy 负责服务工作的节点特定部分,通过处理路由和对外负载均衡到服务中的所有 pod。为了做到这一点,kube-proxy 监控集群中的所有端点,以便了解所有服务应路由到的所有适用 pod。
现在,想象一下我们有一个大型集群,包含数千个节点和数万个容器。这意味着有数千个 kube-proxies 在监视端点。当 Endpoints 对象中的地址发生变化(例如,由于滚动更新、规模扩展、驱逐、健康检查失败或其他任何原因)时,更新后的 Endpoints 对象会被推送到所有监听的 kube-proxies。由于容器数量的增加,导致 Endpoints 对象更大,变化更频繁,这最终会对 etcd、Kubernetes API 服务器以及网络本身造成压力。
Kubernetes 的扩展限制复杂且依赖于特定标准,但监视端点通常是具有数千个节点的集群中常见的问题。从经验来看,许多 Kubernetes 用户认为端点监视是集群规模的最终瓶颈。
这个问题是 kube-proxy 设计的产物,以及对任何容器应该立即能够无预警地路由到任何服务的期望。端点切片是一种方法,允许 kube-proxy 的基本设计继续进行,同时在大规模集群中大幅减少大型服务使用的观察瓶颈。
端点切片与端点对象具有相似的内容,但还包括一个端点数组:
kubectl apply -f dnsutils.yaml
pod/dnsutils created
kubectl get pods
NAME READY STATUS RESTARTS AGE
dnsutils 1/1 Running 0 9s
6
端点和端点切片之间的有意义差异不是模式,而是 Kubernetes 如何处理它们。对于“常规”的端点,Kubernetes 服务为服务中的所有容器创建一个端点。服务创建多个端点切片,每个切片包含服务中的一组容器;图 5-2 展示了这组容器。服务的所有端点切片的并集包含服务中的所有容器。这样,由于新容器、删除容器或容器健康状况的变化导致的 IP 地址更改,将导致向观察者传输的数据量要小得多。因为 Kubernetes 没有事务性 API,同一个 IP 地址可能在多个切片中暂时出现。任何消费端点切片的代码(如 kube-proxy)都必须能够处理这种情况。
端点切片中的最大地址数使用--maxendpoints-per-slice kube-controller-manager 标志设置。当前默认值为 100,最大值为 1000。端点切片控制器在创建新切片之前尝试填充现有端点切片,但不重新平衡端点切片。端点切片控制器将端点镜像到端点切片,以允许系统在将端点切片视为真相来源的同时继续写入端点。这种行为以及一般端点的未来尚未确定(然而,作为 v1 资源,端点将在有大量通知的情况下逐渐淘汰)。有四个例外情况将阻止镜像:
没有对应的服务。
对应的服务资源选择 pods。
Endpoints 对象具有标签 endpointslice.kubernetes.io/skipmirror: true。
Endpoints 对象具有注释 control-plane.alpha.kubernetes.io/leader。
第 5-2 图. 端点与 EndpointSlice 对象
你可以通过获取过滤为所需名称的端点切片来获取特定服务的所有端点切片,在 .metadata.labels."kubernetes.io/service-name" 中。
让我们使用 kubectl get endpointslice 查看集群中的一些端点:
kubectl apply -f dnsutils.yaml
pod/dnsutils created
kubectl get pods
NAME READY STATUS RESTARTS AGE
dnsutils 1/1 Running 0 9s
7
如果我们想要了解更多的端点切片详情,例如 clusterip-service-l2n9q,我们可以使用 kubectl describe 命令来获取:
kubectl apply -f dnsutils.yaml
pod/dnsutils created
kubectl get pods
NAME READY STATUS RESTARTS AGE
dnsutils 1/1 Running 0 9s
8
在输出中,我们看到 pod 从 TargetRef 提供给 endpoint slice。拓扑信息给出了 pod 部署到的工作节点的主机名。最重要的是,Addresses 返回 endpoint 对象的 IP 地址。
端点和端点切片非常重要,因为它们标识了负责服务的 pod,无论部署的是哪种类型。在稍后,我们将回顾如何使用端点和标签进行故障排除。接下来,我们将研究 Kubernetes 的所有服务类型。
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...