当前位置: 首页 > news >正文

K8s in Action 阅读笔记——【5】Services: enabling clients to discover and talk to pods

K8s in Action 阅读笔记——【5】Services: enabling clients to discover and talk to pods

你已了解Pod以及如何通过ReplicaSets等资源部署它们以确保持续运行。虽然某些Pod可以独立完成工作,但现今许多应用程序需要响应外部请求。例如,在微服务的情况下,Pod通常会响应来自群集内部或群集外部客户端的HTTP请求。

如果Pod想要使用其他Pod提供的服务,它们需要找到这些Pod,但与非Kubernetes世界不同,系统管理员无法在客户端配置文件中指定提供服务的服务器的确切IP地址或主机名,因为Pod是临时的。

在Kubernetes中,Pod在调度到节点后在启动之前会被分配一个IP地址,因此客户端在调用之前无法知道Pod的IP地址。此外,由于水平扩展,多个Pod可能提供相同的服务,每个Pod都有自己的IP地址。因此,客户端应该通过单个IP地址访问所有这些Pod,而不需要关心支持服务的Pod数量及其IP地址,不必维护所有单个Pod IP的列表

为解决上述问题,kubernetes提供了一个资源——Service。

5.1 Introducing services

Kubernetes Service是为了创建一个单一和恒定的入口点到一组提供相同服务的Pod而创建的资源。每个服务都有一个IP地址和端口,只要该服务存在,它们不会改变。客户端可以打开到该IP和端口的连接,然后这些连接会被路由到支持该服务的Pod之一。这样,服务的客户端不需要知道提供服务的单个Pod的位置,从而允许这些Pod随时在集群中移动。

让我们回顾一下这样一个例子:你有一个前端Web服务器和一个后端数据库服务器。可能会有多个Pod都充当前端,但只有一个后端数据库Pod。你需要解决两个问题才能使系统正常运行:

  • 外部客户端需要连接到前端Pod,而不需要关心是否只有一个Web服务器或数百个。
  • 前端Pod需要连接到后端数据库。因为数据库运行在一个Pod内,所以它可能随着时间的推移在集群中移动,导致其IP地址发生变化。你不希望每次移动后端数据库时都重新配置前端Pod。

通过为前端Pod创建一个Service并配置为可从集群外部访问,你可以公开一个单一且固定的IP地址,外部客户端可以通过该地址连接到Pod。同样,通过为后端Pod创建一个Service,你为后端Pod创建了一个稳定的地址。即使Pod的IP地址发生变化,服务地址也不会改变。此外,通过创建Service,你还可以通过环境变量或DNS轻松地通过名称找到后端服务,以便前端Pod找到它。图5.1显示了系统的所有组件(两个Service、支持这些Service的两组Pod以及它们之间的相互依赖关系)。

image-20230527205729863

现在你已经理解了服务背后的基本思想,接下来让我们深入了解如何创建它们。

5.1.1 Creating services

一个服务可以由多个Pod支持,连接会在这些支持Pod之间进行负载平衡。但是,怎么定义哪些Pod是服务的一部分呢?你可能还记得标签选择器在ReplicationController和其他Pod控制器中的使用,以指定同一集合内的Pod。服务也使用同样的机制,如图5.2所示。

image-20230527210007553

通过kubectl expose创建服务

创建服务最简单的方法是通过kubectl expose命令,在第2章中使用它来暴露先前创建的ReplicationController。该命令创建了一个Service资源,其Pod选择器与ReplicationController使用的相同,从而通过单个IP地址和端口公开所有Pod。

现在,你将不使用expose命令,而是通过向Kubernetes API服务器发布YAML手动创建服务。

通过YAML文件创建服务

# kubia-svc.yaml
apiVersion: v1
kind: Service
metadata:name: kubia
spec:selector:app: kubiaports:- port: 80targetPort: 8080

正在定义一个名为kubia的服务,它将接受端口80上的连接,并将每个连接路由到与app=kubia标签选择器匹配的一个Pod的端口8080。

测试服务

应用上述YAML后,可以查看已经创建的Service:

$ kubectl  get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP   14d
kubia        ClusterIP   10.97.113.157   <none>        80/TCP    6s

从列表中可以看出,分配给该服务的IP地址为10.97.113.157 。因为这是集群IP,所以只能从集群内部访问它。服务的主要目的是向集群中的其他pod公开pod组,但通常也希望向外部公开服务。

可以通过以下几种方式向集群内的服务发送请求:

  • 最显而易见的方式是创建一个 pod,将请求发送到服务的集群 IP,记录响应。然后,可以检查 pod 的日志,看看服务的响应是什么。
  • 可以 ssh 进入其中一个 Kubernetes 节点,使用 curl 命令。
  • 可以通过 kubectl exec 命令在一个现有的 pod 中执行 curl 命令。

kubectl exec 命令允许你远程在 pod 中现有的容器内运行任意命令。当你想要检查一个容器的内容、状态和/或环境时,这个命令非常有用。使用 kubectl get pods 命令列出 pods 并选择一个 pod 作为 exec 命令的目标(在下面的示例中,我选择了 kubia-7nog1 pod 作为目标)。你还需要获取你的服务的集群 IP(例如使用 kubectl get svc 命令)。在自己运行下面的命令时,请务必使用你自己的 pod 名称和服务 IP 替换它们:

$ kubectl get pod
NAME          READY   STATUS    RESTARTS   AGE
kubia-2k9d4   1/1     Running   0          12m
kubia-x4tl7   1/1     Running   0          12m
kubia-xqvlb   1/1     Running   0          12m
$ kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP   14d
kubia        ClusterIP   10.97.113.157   <none>        80/TCP    12m
$ kubectl exec kubia-2k9d4 -- curl -s http://10.97.113.157
You've hit kubia-xqvlb

命令中的双破折线(–)表示命令选项的结束,后面的所有内容都是应该在 pod 中执行的命令。如果命令没有以破折号开头的参数,则不需要使用双破折线。但在你的情况下,如果你不在这里使用双破折线,则 -s 选项会被解释为 kubectl exec 的选项,并导致错误

让我们回顾一下运行该命令时发生的情况。图5.3显示了事件序列。你指示Kubernetes在一个pod的容器中执行curl命令。curl向由三个pod支持的服务IP发送了一个HTTP请求。Kubernetes服务代理拦截了连接,从三个pod中选择了一个随机的pod,并将请求转发给它。运行在该pod中的Node.js然后处理了请求并返回了一个包含pod名称的HTTP响应。curl然后将响应打印到标准输出中,kubectl拦截并将其打印到本地机器的标准输出中。

image-20230527212516608

在服务上配置会话关联

如果你多次执行相同的命令,每次调用应该都会命中不同的pod,因为服务代理通常会将每个连接转发到一个随机选择的后备pod,即使连接来自同一个客户端。然而,如果你希望某个特定客户端的所有请求都被重定向到同一个pod,你可以将服务的sessionAffinity属性设置为ClientIP(而不是默认值None),如下所示。

image-20230527213100740

这将使服务代理将所有源自同一个客户端IP的请求重定向到同一个pod。作为一个练习,你可以创建一个额外的服务,并将 session affinity设置为 ClientIP,然后尝试发送请求。Kubernetes仅支持两种类型的服务会话亲和性: None和ClientIP。Kubernetes服务不在HTTP级别上操作。服务处理TCP和UDP数据包,并不关心它们携带的有效负载。因为cookie是HTTP协议的一种构造,所以服务不知道它们,这就解释了为什么会话亲和性不能基于cookie。

在同一服务中公开多个端口

你的服务只暴露了一个端口,但服务也可以支持多个端口。例如,如果你的 pod 监听了两个端口,比如说 8080 用于 HTTP,8443 用于 HTTPS,你可以使用一个单一的服务将端口80和443都转发到 pod 的端口8080和8443上。

创建具有多个端口的服务时,必须为每个端口指定名称。

示例文件如下:

apiVersion: v1 
kind: Service 
metadata: name: kubia
spec: ports: - name: http port: 80 targetPort: 8080 - name: https port: 443 targetPort: 8443 selector: app: kubia

标签选择器作为一个整体应用于服务——它不能为每个端口单独配置。如果希望不同的端口映射到pod的不同子集,则需要创建两个服务。

使用命名端口

在所有这些示例中,你都是通过端口号来引用目标端口的,但是你也可以为每个pod的端口指定一个名称,并在服务规范中通过名称来引用它。这会使服务规范稍微清晰一些,特别是在端口号不为人所知的情况下。

示例如下:

# 在一个Pod中命名端口
kind: Pod 
spec: containers: - name: kubia ports: - name: http containerPort: 8080 - name: https containerPort: 8443

然后,可以在服务规范中按名称引用这些端口,如下所示。

# 在Service中引用已经命名的端口
apiVersion: v1 
kind: Service 
spec: ports: - name: http port: 80 targetPort: http - name: https port: 443 targetPort: https

给端口命名能够使你以后在不改变Service配置的情况下更改端口号

5.1.2 Discovering services

客户端 pods 怎么知道服务的 IP 和端口号呢?你需要先创建服务,然后手动查找它的 IP 地址,并将 IP 地址传递给客户端 pod 的配置选项中吗?并不是。Kubernetes 还提供了一些方式让客户端 pod 来发现服务的 IP 地址和端口号

通过环境变量发现服务

当一个 pod 启动时,Kubernetes 会初始化一组环境变量,指向每个此时存在的服务。如果在创建客户端 pods 之前就创建了服务,这些 pods 中的进程可以通过检查它们的环境变量来获取服务的 IP 地址和端口。

为了查看这些环境变量的内容,让我们检查一个正在运行的 pod 中的环境变量。你已经学会了可以使用 kubectl exec 命令在 pod 中运行命令,但是由于你在创建 pods 之后才创建了服务,服务的环境变量可能还没有被设置。

因此,你需要首先解决这个问题。在查看服务的环境变量之前,你需要先删除所有的 pods,并让 ReplicationController 创建新的 pods:

$ kubectl delete pod --all
pod "kubia-2k9d4" deleted
pod "kubia-x4tl7" deleted
pod "kubia-xqvlb" deleted

再来看看重新创建的Pod中的环境变量:

$ kubectl exec kubia-ppvdj env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=kubia-ppvdj
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBIA_PORT_80_TCP_ADDR=10.97.113.157
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
KUBIA_SERVICE_PORT=80
KUBIA_PORT_80_TCP=tcp://10.97.113.157:80
KUBIA_PORT_80_TCP_PROTO=tcp
KUBERNETES_SERVICE_PORT=443
KUBIA_PORT=tcp://10.97.113.157:80
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBIA_SERVICE_HOST=10.97.113.157  # 服务的集群IP
KUBIA_PORT_80_TCP_PORT=80
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
NPM_CONFIG_LOGLEVEL=info
NODE_VERSION=7.9.0
YARN_VERSION=0.22.0
HOME=/root

通过DNS发现服务

还记得在第三章中当你列出 kube-system 命名空间中的 pods 吗?其中一个 pod 叫做 kube-dns。kube-system 命名空间还包括与该名称相同的相应服务。

正如其名称所示,该 pod 运行 DNS 服务器,所有在集群中运行的其他 pods 都会自动配置为使用该 DNS 服务器(Kubernetes 通过修改每个容器的 /etc/resolv.conf 文件来实现这一点)。在 pod 中运行的进程执行的任何 DNS 查询都将由 Kubernetes 自己的 DNS 服务器处理,该 DNS 服务器知道你的系统中运行的所有服务。

pod是否使用内部DNS服务器可以通过每个pod的规范中的dnsPolicy属性进行配置。

每个服务在内部DNS服务器中获得一个DNS条目,知道服务名称的客户机pod可以通过其完全限定域名(FQDN)访问它,而不是求助于环境变量。

在Pod容器中运行Shell

你可以使用kubectl exec命令在pod的容器中运行bash(或任何其他shell)。通过这种方式,你可以随意探索容器,而不必为要运行的每个命令执行kubectl exec。

要正确使用shell,你需要将-it选项传递给kubectl exec:

$ kubectl exec kubia-ppvdj -it -- bash
root@kubia-ppvdj:/#

你现在在容器里了。你可以使用curl命令以以下任何一种方式访问kubiaservice

root@kubia-ppvdj:/# curl http://kubia.default.svc.cluster.local
You've hit kubia-ppvdj
root@kubia-ppvdj:/# curl http://kubia.default
You've hit kubia-ppvdj
root@kubia-ppvdj:/# curl http://kubia
You've hit kubia-wxvx6

你可以使用服务的名称作为请求 URL 中的主机名来访问服务。因为每个 pod 容器内部的 DNS 解析器配置,你可以省略命名空间和 svc.cluster.local 后缀。查看容器中的 /etc/resolv.conf 文件:

root@kubia-ppvdj:/# cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local cs1cloud.internal

通过exit命令退出shell

理解为什么不能ping通服务IP

尝试ping通服务IP

root@kubia-ppvdj:/# ping kubia
PING kubia.default.svc.cluster.local (10.97.113.157): 56 data bytes
^C--- kubia.default.svc.cluster.local ping statistics ---
4 packets transmitted, 0 packets received, 100% packet loss

发现无法Ping通,这是因为服务的集群 IP 是一个虚拟 IP,只有与服务端口结合才有意义

5.2 Connecting to services living outside the cluster

到目前为止,我们已经讨论了由群集内运行的一个或多个 pod 支持的服务。但有些情况下,你想通过 Kubernetes 服务功能公开外部服务。而不是让服务重定向到群集中的 pod,你希望它重定向到外部 IP 和端口。

这使你能够利用服务负载均衡和服务发现。在群集中运行的客户端 pod 可以像连接内部服务一样连接到外部服务。

5.2.1 Introducing service endpoints

在介绍如何操作之前,先进一步解释服务的相关信息。服务并不会直接链接到 pod。相反,在它们之间会有一个资源存在——Endpoints 资源。如果在服务上使用了 kubectl describe 命令,你可能已经注意到了它的 endpoints,如下所示:

$ kubectl describe svc kubia
Name:              kubia
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=kubia
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.97.113.157
IPs:               10.97.113.157
Port:              <unset>  80/TCP
TargetPort:        8080/TCP
Endpoints:         10.244.1.200:8080,10.244.1.201:8080,10.244.1.202:8080
Session Affinity:  None
Events:            <none>

Endpoints资源是公开服务的IP地址和端口列表。端点资源和其他Kubernetes资源一样,所以你可以用kubectl get命令显示它的基本信息:

$ kubectl get endpoints kubia
NAME    ENDPOINTS                                               AGE
kubia   10.244.1.200:8080,10.244.1.201:8080,10.244.1.202:8080   94m

尽管pod选择器是在服务规范中定义的,但在重定向传入连接时并不直接使用它。相反,选择器用于构建ip和端口列表,然后将其存储在Endpoints资源中。当客户端连接到服务时,服务代理选择其中一个IP和端口对,并将传入的连接重定向到在该位置侦听的服务器。

5.2.2 Manually configuring service endpoints

服务端点与服务解耦使它们可以手动配置和更新。如果你创建了一个没有 Pod 选择器的服务,Kubernetes甚至不会创建Endpoints资源(毕竟,没有选择器,它不知道要包括哪些Pod在服务中)。你需要自己创建Endpoints资源来指定服务的端点列表。要创建具有手动管理端点的服务,你需要创建一个Service和一个Endpoints资源。

创建没有选择器的服务

# external-service.yaml
apiVersion: v1
kind: Service
metadata:# 服务的名称必须与Endpoints的名称匹配name: external-service
spec: # 没有定义选择器ports:- port: 80

为没有选择器的服务创建端点资源

Endpoints是一个单独的资源,而不是服务的属性。因为你没有使用选择器创建服务,因此相应的Endpoints资源没有自动创建,所以你需要自己创建。如下所示:

# external-service-endpoints.yaml
apiVersion: v1
kind: Endpoints
metadata:# 服务的名称必须与Endpoints的名称匹配name: external-service
subsets:- addresses:# 服务将转发连接到的端点的ip- ip: 11.11.11.11 - ip: 22.22.22.22ports:# 端点的目标端口- port: 80

Endpoints对象需要与服务具有相同的名称,并包含服务的目标IP地址和端口列表。在Service和Endpoints资源都被发送到服务器后,该服务就可以像具有Pod选择器的任何常规服务一样使用了。在创建服务之后创建的容器将包含服务的环境变量,并且所有连接到其IP:port对的连接将在服务端点之间进行负载均衡。

图5.4显示了三个使用外部端点连接到服务的pod

image-20230527230837943

如果后来你决定将外部服务迁移到在Kubernetes内运行的Pod,你可以向服务添加选择器,从而使它的Endpoints自动管理。相反地,去掉一个Service的选择器,就能让Kubernetes停止更新它的Endpoints。这意味着服务的IP地址可以保持不变,而服务的实际实现也可以发生变化

5.2.3 Creating an alias for an external service

不必手动配置服务端点来暴露外部服务,还有一种更简单的方法,就是通过完全限定域名(fully qualified domain name FQDN)引用外部服务。

创建ExternalName服务

要创建一个作为外部服务别名的服务,你可以创建一个类型字段设置为ExternalName的Service资源。例如,假设有一个公共API可用于api.somecompany.com。你可以定义一个指向它的服务,如下所示。

# external-service-externalname.yaml
apiVersion: v1
kind: Service
metadata:name: external-servicenamespace: default
spec:type: ExternalName# 服务的完全限定域名externalName: someapi.somecompany.comports:- port: 80

创建服务之后,Pod可以通过external-service.default.svc.cluster.local域名(甚至是externalservice)连接到外部服务,而无需使用服务的实际FQDN。

5.3 Exposing services to external clients

到目前为止,我们只讨论了如何在群集内部将服务提供给 Pod 使用。但是,你还想将某些服务(例如前端 Web 服务器)暴露给外部客户端,以便它们可以访问这些服务,如图 5.5 所示。

你有几种方式使服务可从外部访问:

  • 将服务类型设置为 NodePort——对于 NodePort 服务,每个群集节点都会在节点本身上打开一个端口(因此得名),并将接收到的流量重新定向到底层服务。该服务不仅可以在内部群集 IP 和端口上访问,还可以通过所有节点上的专用端口访问。
  • 将服务类型设置为 LoadBalancer,这是 NodePort 类型的扩展——这使服务通过云基础设施提供的专用负载均衡器可访问。负载均衡器会将流量重定向到所有节点上的节点端口。客户端通过负载均衡器的 IP 连接服务。
  • 创建 Ingress 资源,这是一种通过单个 IP 地址暴露多个服务的根本不同的机制——它在 HTTP 层级(网络层 )运作,因此可以提供更多的功能。

5.3.1 Using a NodePort service

暴露一组pod给外部客户端的第一种方法是创建一个服务并将其类型设置为NodePort。通过创建一个NodePort服务,你让Kubernetes在所有节点上保留一个端口(相同的端口号在所有节点上使用),并将传入的连接转发给服务中的pod。

这类似于常规服务(实际类型为ClusterIP),但NodePort服务不仅可以通过服务的内部集群IP访问,还可以通过任何节点的IP和保留的节点端口访问。

创建NodePort服务

# kubia-svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:name: kubia-nodeportnamespace: default
spec:selector:app: kubiatype: NodePortports:- port: 80 # 这是服务的内部集群IP的端口targetPort: 8080 # 目标Pod的端口nodePort: 30123 # 可以通过每个集群节点的端口30123访问该服务。

可以将类型设置为NodePort,并指定此服务应跨所有集群节点绑定到的节点端口。指定端口不是必需的;如果省略它,Kubernetes将选择一个随机端口。

测试NodePort服务

可以查看已经创建的NodePort服务

$ kubectl get svc kubia-nodeport
NAME             TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubia-nodeport   NodePort   10.105.54.75   <none>        80:30123/TCP   59s

看 EXTERNAL-IP 列。它显示 ,表示该服务可以通过任何群集节点的 IP 地址访问。PORT(S) 列显示了集群 IP 的内部端口 (80) 和节点端口 (30123)。该服务可以在以下地址访问:

  • 10.105.54.75:80
  • <节点一的IP>:30123
  • <节点二的IP>:30123、等等
root@yjq-k8s1:~# curl 10.105.54.75:80
You've hit kubia-ppvdjroot@yjq-k8s1:~# curl 33.33.33.110:30123
You've hit kubia-ppvdjyjq@DESKTOP-NKBDBDM:~$ curl 33.33.33.110:30123
You've hit kubia-qfwt4

图 5.6 显示了你的服务在集群节点的 30123 端口上暴露(如果你在 GKE 上运行,则适用此情况;Minikube 只有一个节点,但原理相同)。对其中任一端口的传入连接都将重定向到随机选择的一个 pod 上,该 pod 可能是正在运行传入连接所在的节点上的那个 pod,也可能不是。

image-20230528140034886

30123 端口接收到的连接可能会被转发到第一个节点上运行的 pod 或者第二个节点上运行的 pod 中的其中一个。

5.3.2 Exposing a service through an external load balancer

在云提供商上运行的 Kubernetes 集群通常支持从云基础架构自动提供负载均衡器。你只需要将服务的类型设置为 LoadBalancer 而不是 NodePort。负载均衡器将拥有自己独特的、公开可访问的 IP 地址,并将所有连接重定向到你的服务。因此,你可以通过负载均衡器的 IP 地址访问你的服务。

如果 Kubernetes 运行在不支持 LoadBalancer 服务的环境中,负载均衡器将不会被提供,但服务仍将像 NodePort 服务一样运行。这是因为 LoadBalancer 服务是 NodePort 服务的一个扩展。

5.3.3 Understanding the peculiarities of external connections

理解和避免不必要的网络跳跃

当外部客户端通过节点端口连接到服务时(这也包括通过负载均衡器首先通过连接的情况),随机选择的 pod 可能正在运行与接收连接的节点不同的节点上。需要进行额外的网络跳跃才能到达 pod,但这可能并不总是理想的。

可以通过将服务配置为仅将外部流量重定向到正在运行与接收连接的节点上的 pod,来防止这种额外跳跃。这是通过在服务的 spec 部分中设置 externalTrafficPolicy 字段来完成的:

spec: externalTrafficPolicy: Local

如果一个服务定义包括这个设置,并且一个外部连接通过服务的节点端口打开,服务代理将选择一个本地运行的 pod。如果不存在本地的 pods,则连接将挂起(它不会像没有使用这个标注时那样被转发到一个随机的全局 pod)。因此,你需要确保负载均衡器仅将连接转发到至少有一个这样的 pod 的节点

使用这个标注也有其他的缺点。通常,连接会均匀地分布在所有的 pods 中,但是当使用这个标注时,情况就不再如此了。

想象一下有两个节点和三个 pods。假设节点 A 运行一个 pod,节点 B 运行另外两个。如果负载均衡器均匀地将连接分布在这两个节点上,节点 A 上的 pod 将收到所有连接的 50%,但节点 B 上的两个 pods 将仅仅每个收到 25%,如图 5.8 所示。

image-20230528150503706

注意客户端 IP 的非保留

通常情况下,当集群内部的客户端连接到服务时,支持服务的 pods 可以获得客户端的 IP 地址。但是当连接通过节点端口接收时,数据包的源 IP 会被更改,因为会对数据包执行源网络地址转换 (Source Network Address Translation,SNAT)。

支持 pod 无法看到实际的客户端 IP,这可能会对一些需要知道客户端 IP 的应用程序造成问题。例如,对于 web 服务器,这意味着访问日志将不会显示浏览器的 IP。

5.4 Exposing services externally through an Ingress resource

Ingress:入口

使用Ingress其中一个重要的原因是,每个 LoadBalancer 服务都需要自己的负载均衡器和公共 IP 地址,而一个 Ingress 只需要一个,即使提供对数十个服务的访问。当客户端向 Ingress 发送 HTTP 请求时,请求中的主机和路径确定将请求转发到哪个服务,如图 5.9 所示。Ingress 在网络协议栈(HTTP)的应用程序层运行,并可以提供基于 cookie 的会话亲和性等功能,但服务不能提供这些功能。

image-20230528151112592

本集群中使用Istio实现Ingress功能。

5.5 Signaling when a pod is ready to accept connections

如果 pod 的标签与服务的 pod 选择器匹配,那么它们将作为服务的endpoints包含在内。当创建一个带有正确标签的新 pod 时,它将成为服务的一部分,并且请求将开始被重定向到该 pod。但是,如果该 pod 暂时还没有准备好立即开始服务请求呢?

该 pod 可能需要时间来加载配置或数据,或者可能需要执行启动前的一些准备工作,以防止第一个用户请求花费太长时间,影响用户体验。在这种情况下,你不希望立即开始向 pod 发送请求,特别是当已经运行的实例可以适当地且快速地处理请求时。因此,在Pod完全准备好之前,不要将请求转发到正在启动的Pod是有意义的。

5.5.1 Introducing readiness probes

在前一章我们学习了存活探针(liveness probes),接下来我们介绍就绪探针(readiness probes)

readiness probe 周期性调用来确定特定 pod 是否应该接收客户端请求。当容器的 readiness probe 返回成功时,表示容器可以准备接受请求。

就绪探针的类型

跟存活探针类型,就绪探针也有三种类型:

  • HTTP GET探针在你指定的容器IP地址、端口和路径上执行HTTP GET请求。如果探针接收到响应并且响应代码不表示错误(换句话说,如果HTTP响应代码是2xx或3xx),则该探针被认为是成功的。如果服务器返回错误响应代码或根本没有响应,则探针将被视为失败,容器将被重新启动。
  • TCP Socket 探针尝试打开容器指定端口的 TCP 连接。如果连接成功建立,探针就算成功。否则,容器将会被重启。
  • Exec 探针在容器内部执行任意命令,并检查该命令的退出状态码。如果状态码为 0,探针就算成功。其他所有状态码都被视为失败。

就绪探针的工作机制

当一个容器被启动时,可以配置Kubernetes在执行第一次readiness检查之前等待一段可配置的时间。此后,它会定期地调用该探针,并根据就绪探针的结果进行操作。如果一个Pod报告它不可用,它将被从服务中移除。如果Pod再次变为就绪状态,则重新添加。

与存活探针不同的是,如果一个容器不能通过就绪探测,它不会被杀死或重启。这是存活性和就绪性探针之间的重要区别。存活探针通过终止不健康的容器并将其替换为新的健康的容器来保持Pod的健康状态,而就绪探针确保只有准备好为请求提供服务的Pod才能接收它们。这在容器启动后大多是必要的,但在容器运行一段时间后也很有用。

正如图5.11中所示,如果Pod的就绪检查失败,则将其从Endpoints对象中删除。连接到服务的客户端将不会被重定向到Pod。这个效果与Pod根本不匹配服务的标签选择器时的效果相同。

image-20230528152712468

5.5.2 Adding a readiness probe to a pod

向Pod模板中加入就绪探针

当ReplicationController的YAML文件在文本编辑器中打开时,在Pod的模板中查找容器规范,并将以下就绪性探针定义添加到spec.template.spec.containers下的第一个容器中:

就绪性探针将定期在容器内执行命令ls /var/ready。如果文件存在,则ls命令返回零退出代码;否则,返回非零退出代码。如果该文件存在,则就绪性探针将成功;否则,将失败。

观察就绪探测结果

删除现有的Pod,新定义的就绪探针就会起作用,会发现所有的Pod都没有就绪,这是因为他们当作还没有创建文件/var/ready。

$ kubectl get pod
NAME          READY   STATUS    RESTARTS   AGE
kubia-94xhp   0/1     Running   0          57s
kubia-hddrx   0/1     Running   0          57s
kubia-jnw56   0/1     Running   0          57s

通过创建/var/ready文件使其中一个的就绪探测开始返回成功

$ kubectl exec kubia-94xhp -- touch /var/ready$ kubectl get pod
NAME          READY   STATUS    RESTARTS   AGE
kubia-94xhp   0/1     Running   0          112s
kubia-hddrx   0/1     Running   0          112s
kubia-jnw56   0/1     Running   0          112s

可以发现Pod还没有准备好,这是为什么呢,使用kubectl describe命令来看看Pod:

$ kubectl describe  kubia-94xhp
Readiness:      exec [ls /var/ready] delay=0s timeout=1s period=10s #success=1 #failure=3

就绪性探针会定期检查,默认为每10秒检查一次。因为就绪性探针还没有被调用,所以该Pod目前还没有准备好。但是最迟在10秒钟后,该Pod应该变为就绪状态,并将其IP列为服务的唯一endpoint(运行kubectl get endpoints kubia-loadbalancer进行确认)。

$ kubectl get pod
NAME          READY   STATUS    RESTARTS   AGE
kubia-94xhp   1/1     Running   0          5m1s
kubia-hddrx   0/1     Running   0          5m1s
kubia-jnw56   0/1     Running   0          5m1s$ kubectl get endpoints
NAME             ENDPOINTS           AGE
kubernetes       33.33.33.110:6443   14d
kubia            10.244.1.232:8080   18h
kubia-nodeport   10.244.1.232:8080   110m

5.6 Using a headless service for discovering individual pods

你已经了解到如何通过服务提供稳定的IP地址,让客户端连接到支持每个服务的Pod(或其他终点)。服务的每个连接被转发到一个随机选择的支持Pod。但是,如果客户端需要连接到所有这些Pod?如果支持的Pod本身每个都需要连接到所有其他支持Pod?通过服务连接显然不是解决这个问题的方法。那么该怎么做呢?

为了让客户端连接到所有Pod,它需要找出每个单独Pod的IP。一个选项是让客户端调用Kubernetes API服务器,并通过API调用获取Pod列表及其IP地址,但由于你应该始终努力保持应用程序与Kubernetes松耦合,因此使用API服务器并不理想。

幸运的是,Kubernetes允许客户端通过DNS查找来发现Pod的IP。通常,当你对服务执行DNS查找时,DNS服务器会返回单个IP(服务的集群IP)。但是,如果你告诉Kubernetes你对服务不需要集群IP(通过在服务规范中将clusterIP字段设置为None来实现),则DNS服务器将返回Pod IP而不是单个服务IP

与返回单个DNSA记录不同,DNS服务器将返回该服务的多个A记录,每个A记录指向当时支持该服务的单个Pod的IP。因此,客户端可以执行简单的DNSA记录查找,并获得属于服务的所有Pod的IP。然后,客户端可以使用该信息连接到其中的一个、多个或全部。

5.6.1 Creating a headless service

在服务规范中将clusterIP字段设置为None将使服务headless,因为Kubernetes不会通过IP分配为其分配集群IP,以便客户端可以通过此方式连接到其后面的Pod。现在你将创建一个名为kubia-headless的无头服务。如下所示:

# kubia-svc-headless.yaml
apiVersion: v1
kind: Service
metadata:name: kubia-headlessnamespace: default
spec:selector:app: kubiaclusterIP: None # 使服务变得headlessports:- port: 80 # 这是服务的内部集群IP的端口targetPort: 8080 # 目标Pod的端口

你可以使用kubectl create命令创建服务,然后使用kubectl get和kubectl describe命令查看服务。你会看到它没有集群IP,而其Endpoints包括(部分)匹配其Pod选择器的Pod。我说“部分”,因为你的Pod包含一个就绪性探针,因此仅将准备就绪的Pod列为服务的终点。在继续之前,请确保至少两个Pod报告为就绪状态,方法是创建/var/ready文件,如同前一个示例中所示:

$ kubectl get pod
NAME          READY   STATUS    RESTARTS   AGE
kubia-94xhp   1/1     Running   0          26m
kubia-hddrx   1/1     Running   0          26m
kubia-jnw56   0/1     Running   0          26m

5.6.2 Discovering pods through DNS

现在你的Pod已经就绪,可以尝试执行DNS查找,以查看是否获得了实际的Pod IP。你需要从其中一个Pod内部进行查找。不幸的是,你的kubia容器映像不包含nslookup(或dig)二进制文件,因此无法使用它来执行DNS查找。

你正在尝试的只是从集群内运行的Pod内部执行DNS查找。为什么不基于包含所需二进制文件的映像运行一个新的Pod呢?为了执行与DNS相关的操作,你可以使用Docker Hub上提供的tutum/dnsutils容器映像,该映像包含nslookup和dig二进制文件。要运行Pod,你可以通过创建一个YAML描述文件并将其传递给kubectl create的整个过程,有一种更快的方式。

创建如下Pod

$ kubectl run dnsutils --image=tutum/dnsutils --command -- sleep infinity

让我们使用新创建的Pod执行DNS查找:

$ kubectl exec dnsutils -- nslookup kubia-headless
Server:         10.96.0.10
Address:        10.96.0.10#53Name:   kubia-headless.default.svc.cluster.local
Address: 10.244.1.232
Name:   kubia-headless.default.svc.cluster.local
Address: 10.244.1.231

DNS服务器为kubia-headless.default.svc.cluster.local FQDN返回了两个不同的IP地址。这些IP地址是报告准备就绪的两个Pod的IP地址。你可以使用kubectl get pods -o wide命令来列出显示Pod的IP地址以确认这一点。

这与DNS返回常规(非headless)服务(例如kubia服务)的IP地址不同,后者返回服务的集群IP地址。尽管headless服务可能与常规服务不同,但从客户端的角度来看,它们并没有太大区别。即使使用headless服务,客户端仍然可以通过连接服务的DNS名称连接到其Pod。但对于headless服务,因为DNS返回的是Pod的IP地址,所以客户端直接连接到Pod而不是通过服务代理连接。

5.7 Troubleshooting services

如果无法通过服务访问Pod,应该按照以下列表进行检查

  1. 首先,请确保你是从集群内部连接到服务的集群IP而不是从外部连接。
  2. 不要浪费时间通过ping服务IP来判断服务是否可访问(记住,服务的集群IP是虚拟IP,ping它永远不会起作用)。
  3. 如果定义了就绪探针,请确保执行成功,否则Pod将不是服务的一部分。
  4. 要确认Pod是否是服务的一部分,请使用kubectl get endpoints命令检查相应的Endpoints对象。
  5. 如果你尝试通过服务的FQDN或部分FQDN(例如myservice.mynamespace.svc.cluster.local或myservice.mynamespace)来访问服务但无法访问,请尝试使用其集群IP而不是FQDN。
  6. 检查你是否连接到服务公开的端口而不是目标端口。
  7. 尝试直接连接到Pod IP以确认Pod是否在正确的端口上接受连接。
  8. 如果甚至无法通过Pod的IP访问应用程序,请确保应用程序未仅绑定到localhost。

相关文章:

K8s in Action 阅读笔记——【5】Services: enabling clients to discover and talk to pods

K8s in Action 阅读笔记——【5】Services: enabling clients to discover and talk to pods 你已了解Pod以及如何通过ReplicaSets等资源部署它们以确保持续运行。虽然某些Pod可以独立完成工作&#xff0c;但现今许多应用程序需要响应外部请求。例如&#xff0c;在微服务的情况…...

牛客网DAY2(编程题)

圣诞节来啦&#xff01;请用CSS给你的朋友们制作一颗圣诞树吧~这颗圣诞树描述起来是这样的&#xff1a; 1. "topbranch"是圣诞树的上枝叶&#xff0c;该上枝叶仅通过边框属性、左浮动、左外边距即可实现。边框的属性依次是&#xff1a;宽度为100px、是直线、颜色为gr…...

Java经典笔试题—day14

Java经典笔试题—day14 &#x1f50e;选择题&#x1f50e;编程题&#x1f36d;计算日期到天数转换&#x1f36d;幸运的袋子 &#x1f50e;结尾 &#x1f50e;选择题 (1)定义学生、教师和课程的关系模式 S (S#,Sn,Sd,Dc,SA &#xff09;&#xff08;其属性分别为学号、姓名、所…...

一个帮助写autoprefixer配置的网站

前端需要用到postcss的工具&#xff0c;用到一个插件叫autoprefixer&#xff0c;这个插件能够给css属性加上前缀&#xff0c;进行一些兼容的工作。 如何安装之类的问题在csdn上搜一下都能找到&#xff08;注意&#xff0c;vite是包含postcss的&#xff0c;不用在项目中安装pos…...

C语言中的类型转换

C语言中的类型转换 隐式类型转换 整型提升 概念&#xff1a; C语言的整型算术运算总是至少以缺省&#xff08;默认&#xff09;整型类型的精度来进行的为了获得这个精度&#xff0c;表达式中字符和短整型操作数在使用之前被转换为普通整型&#xff0c;这种转换成为整型提升 如…...

String底层详解(包括字符串常量池)

String a “abc”; &#xff0c;说一下这个过程会创建什么&#xff0c;放在哪里&#xff1f; JVM会使用常量池来管理字符串直接量。在执行这句话时&#xff0c;JVM会先检查常量池中是否已经存有"abc"&#xff0c;若没有则将"abc"存入常量池&#xff0c;否…...

C++ 里面lambda和函数指针的转换

问题说明 原始问题&#xff0c;代码如下会编译报错&#xff1a; using DecisionFn bool(*)();class Decide { public:Decide(DecisionFn dec) : _dec{dec} {} private:DecisionFn _dec; };int main() {int x 5;Decide greaterThanThree{ [x](){ return x > 3; } };retur…...

前端Rust开发WebAssembly与Swc插件快速入门

前言 现代前端对速度的追求已经进入二进制工具时代&#xff0c;Rust 开发成为每个人的必修课。 一般我们将常见的前端 Rust 开发分为以下几类&#xff0c;难度由上至下递增&#xff1a; 开发 wasm 。 开发 swc 插件。 开发代码处理工具。 我们将默认读者具备最简单的 Rus…...

【C++ 学习 ⑧】- STL 简介

目录 一、什么是 STL&#xff1f; 二、STL 的版本 三、STL 的 6 大组件和 13 个头文件 四、学习 STL 的 3 个境界 五、STL 的缺陷 参考资料&#xff1a; STL教程&#xff1a;C STL快速入门&#xff08;非常详细&#xff09; (biancheng.net)。 C STL是什么&#xff0c;有…...

论文笔记--Deep contextualized word representations

论文笔记--Deep contextualized word representations 1. 文章简介2. 文章概括3 文章重点技术3.1 BiLM(Bidirectional Language Model)3.2 ELMo3.3 将ELMo用于NLP监督任务 4. 文章亮点5. 原文传送门 1. 文章简介 标题&#xff1a;Deep contextualized word representations作者…...

【MySQL高级篇笔记-性能分析工具的使用 (中) 】

此笔记为尚硅谷MySQL高级篇部分内容 目录 一、数据库服务器的优化步骤 二、查看系统性能参数 三、统计SQL的查询成本&#xff1a;last_query_cost 四、定位执行慢的 SQL&#xff1a;慢查询日志 1、开启慢查询日志参数 2、查看慢查询数目 3、慢查询日志分析工具&#xf…...

大学生数学建模题论文

大学生数学建模题论文篇1 浅论高中数学建模与教学设想 论文关键词&#xff1a;数学建模 数学 应用意识 数学建模教学 论文摘要&#xff1a;为增强学生应用数学的意识&#xff0c;切实培养学生解决实际问题的能力&#xff0c;分析了高中数学建模的必要性&#xff0c;并通过对高中…...

论文阅读 —— 滤波激光SLAM

文章目录 FAST-LIO2FAST-LIOIMUR2LIVER3LIVEEKFLINS退化摘要第一句 FAST-LIO2 摘要&#xff1a; 本文介绍了FAST-LIO2&#xff1a;一种快速、稳健、通用的激光雷达惯性里程计框架。 FAST-LIO2建立在高效紧耦合迭代卡尔曼滤波器的基础上&#xff0c;有两个关键的新颖之处&#…...

JavaScript键盘事件

目录 一、keydown&#xff1a;按下键盘上的任意键时触发。 二、keyup&#xff1a;释放键盘上的任意键时触发。 三、keypress&#xff1a;在按下并释放能够产生字符的键时触发&#xff08;不包括功能键等&#xff09;。 四、input&#xff1a;在文本输入框或可编辑元素的内容…...

opengl灯光基础:2.1 光照基础知识

光照&#xff1a; 光照以不同的方式影响着我们看到的世界&#xff0c;有时甚至是以很戏剧化的方式。当手电筒照射在物体上时&#xff0c;我们希望物体朝向光线的一侧看起来更亮。我们所居住的地球上的点&#xff0c;在中午朝向太阳时候被照得很亮&#xff0c;但随着地球的自转…...

大屏时代:引领信息可视化的新潮流

在信息时代的浪潮下&#xff0c;数据已经成为推动各行各业发展的重要动力。然而&#xff0c;海量的数据如何快速、直观地呈现给用户&#xff0c;成为了一个亟待解决的难题。在这样的背景下&#xff0c;可视化大屏应运而生&#xff0c;以其出色的表现力和交互性成为信息展示的佼…...

ChatGTP全景图 | 背景+技术篇

引言&#xff1a;人类以为的丰功伟绩&#xff0c;不过是开端的开端……我们在未来100年取得的技术进步&#xff0c;将远超我们从控制火种到发明车轮以来所取得的一切成就。——By Sam Altman 说明&#xff1a;ChatGPT发布后&#xff0c;我第一时间体验了它的对话、翻译、编程、…...

计算机专业学习的核心是什么?

既然是学习CS&#xff0c;那么在这里&#xff0c;我粗浅的把计算机编程领域的知识分为三个部分&#xff1a; 基础知识 特定领域知识 框架和开发技能 基础知识是指不管从事任何方向的软件工程师都应该掌握的&#xff0c;比如数据结构、算法、操作系统。 特定领域知识就是你…...

基于springboot地方旅游系统的设计与实现

摘 要 本次设计内容是基于Springboot的旅游系统的设计与实现&#xff0c;采用B/S三层架构分别是Web表现层、Service业务层、Dao数据访问层&#xff0c;并使用Springboot&#xff0c;MyBatis二大框架整合开发服务器端&#xff0c;前端使用vue&#xff0c;elementUI技术&…...

一些学习资料链接

组件化和CocoaPods iOS 组件化的三种方案_迷曳的博客-CSDN博客 CocoaPods 私有化 iOS组件化----Pod私有库创建及使用 - 简书 CocoaPods1.9.1和1.8 使用 出现CDN: trunk URL couldnt be downloaded: - 简书 cocoapod制作私有库repo - 简书 【ios开发】 上传更新本地项目到…...

Webpack打包图片-JS-Vue

1 Webpack打包图片 2 Webpack打包JS代码 3 Babel和babel-loader 5 resolve模块解析 4 Webpack打包Vue webpack5打包 的过程&#xff1a; 在webpack的配置文件里面编写rules&#xff0c;type类型有多种&#xff0c;每个都有自己的作用&#xff0c;想要把小内存的图片转成bas…...

进程控制(Linux)

进程控制 fork 在Linux中&#xff0c;fork函数是非常重要的函数&#xff0c;它从已存在进程中创建一个新进程。新进程为子进程&#xff0c;而原进程为父进程。 返回值&#xff1a; 在子进程中返回0&#xff0c;父进程中返回子进程的PID&#xff0c;子进程创建失败返回-1。 …...

C Primer Plus第十四章编程练习答案

学完C语言之后&#xff0c;我就去阅读《C Primer Plus》这本经典的C语言书籍&#xff0c;对每一章的编程练习题都做了相关的解答&#xff0c;仅仅代表着我个人的解答思路&#xff0c;如有错误&#xff0c;请各位大佬帮忙点出&#xff01; 由于使用的是命令行参数常用于linux系…...

又名管道和无名管道

一、进程间通信&#xff08;IPC&#xff0c;InterProcess Communication&#xff09; 概念&#xff1a;就是进程和进程之间交换信息。 常用通信方式 无名管道&#xff08;pipe&#xff09; 有名管道 &#xff08;fifo&#xff09; 信号&#xff08;signal&#xff09; 共…...

操作系统复习4.1.0-文件管理结构

定义 一组有意义的信息的集合 属性 文件名、标识符、类型、位置、大小、创建时间、上次修改时间、文件所有者信息、保护信息 操作系统向上提供的功能 创建文件、删除文件、读文件、写文件、打开文件、关闭文件 这6个都是系统调用 创建文件 创建文件时调用Create系统调用…...

【嵌入式烧录/刷写文件】-2.6-剪切/保留Intel Hex文件中指定地址范围内的数据

案例背景&#xff1a; 有如下一段HEX文件&#xff0c;保留地址范围0x9140-0x91BF内的数据&#xff0c;删除地址范围0x9140-0x91BF外的数据。 :2091000058595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F70717273747576775F :2091200078797A7B7C7D7E7F808182838485868788898A…...

JavaScript表单事件(下篇)

目录 八、keydown: 当用户按下键盘上的任意键时触发。 九、keyup: 当用户释放键盘上的键时触发。 十、keypress: 当用户按下键盘上的字符键时触发。 十一、focusin: 当表单元素或其子元素获得焦点时触发。 十二、focusout: 当表单元素或其子元素失去焦点时触发。 十三、c…...

机器学习 | SVD奇异值分解

本文整理自哔哩哔哩视频&#xff1a;什么是奇异值分解SVD–SVD如何分解时空矩阵 &#x1f4da;奇异值分解是什么&#xff1f; M是原始矩阵&#xff0c;它可以是任意的矩阵&#xff0c;奇异值分解就是将它分解为三个矩阵相乘。U和V是方阵&#xff0c;∑是不规则矩阵&#xff0c;…...

chatgpt赋能python:Python取值:介绍

Python取值&#xff1a;介绍 Python是一种非常流行的高级编程语言&#xff0c;适用于各种任务&#xff0c;包括数据科学、机器学习、Web开发和自动化。它被广泛使用&#xff0c;因为它易于学习、易于使用、易于阅读和易于维护。Python中的取值对于程序员来说是一个极其有用的工…...

广播风暴的成因以及如何判断、解决

广播风暴&#xff08;broadcast storm&#xff09;简单的讲是指当广播数据充斥网络无法处理&#xff0c;并占用大量网络带宽&#xff0c;导致正常业务不能运行&#xff0c;甚至彻底瘫痪&#xff0c;这就发生了“广播风暴”。一个数据帧或包被传输到本地网段 &#xff08;由广播…...