k8s (四) 服务:让客户端发现 pod 并与之通信

服务是一种为一组功能相同的 pod 提供单一不变的接入点的资源。当服务存在时,它的 IP 地址和端口不会改变。客户端通过 IP 地址和端口号建立连接,这些连接会被路由到提供该服务的任意一个 pod 上。通过这种方式,客户端不需要知道每个单独的提供服务的 pod 的地址,这样这些 pod就可以在集群中随时被创建或移除。

一、创建服务

# kubia-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - port: 80 # 该服务的可用端口
    targetPort: 8080 # 服务将连接转发到的容器端口
  selector:
    app: kubia # 具有 app=kubia 标签的 pod 都属于该服务

创建:

kubectl create -f kubia-svc.yaml

查看:

kubectl get svc # svc 是 service 的简写

(上篇文章已经通过 ReplicaSet 启动了 3 个 app=kubia 标签的 pod)通过 curl 命令即可看到效果:

curl 10.96.9.157 # 10.96.9.157 为分配给服务的 IP

在运行中的容器中远程执行命令:

kubectl exec kubia-ntxd8 -- curl -s 10.96.9.157

双横杠(—) 代表着 kubectl 命令项的结束。双横杠之后的内容指在 pod 内部需要执行的命令。

1.1. 配置服务上的会话亲和性

如果多次执行同样的命令,每次调用执行应该在不同的 pod 上。因为服务代理通常将每个连接随机指向选中的后端 pod 中的一个,即使连接来自于同一个客户端。如果希望特定客户端产生的所有请求每次都指向同一个 pod, 可以
设置服务的 sessionAffinity 属性为 ClientIP。Kubernetes 仅仅支持两种形式的会话亲和性服务:None(随机) 和 ClientIP,不支持 cookie,因为 Kubernetes 服务处理的是 TCP 和 UDP 而不是 HTTP。

1.2. 同一服务暴露多个端口

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - name: http # 必须给每个端口指定名字
    port: 80
    targetPort: 8080
  - name: https # 必须给每个端口指定名字
    port: 443
    targetPort: 8443
  selector:
    app: kubia

1.3. 使用命名的端口

好处是即使更换端口号也无需更改服务 spec

在 pod 的定义中指定 port 名称:

kind: Pod
spec:
  containers:
  - image: luksa/kubia
    name: kubia
    ports:
    - name: http    
      containerPort: 8080
    - name: https    
      containerPort: 8443

在服务中引用端口:

kind: Service
metadata:
  name: kubia
spec:
  ports:
  - name: http
    port: 80
    targetPort: http # 映射到容器中被称为 http 的端口
  - name: https
    port: 443
    targetPort: https # 映射到容器中被称为 https 的端口 
  selector:
    app: kubia

二、服务发现

2.1. 通过环境变量发现服务

在 pod 开始运行的时候,Kubernetes 会初始化一系列的环境变量指向现在存在的服务。如果创建的服务早于客户端 pod 的创建,pod 上的进程可以根据环境变量获得服务的 IP 地址和端口号。

因为现有的 pod 创建于服务之前,所以需要先删除掉所有的 pod 这样 ReplicaSet 会创建全新的 pod:

删除 pod:

kubectl delete pods --all

查看环境变量:

kubectl exec kubia-jkj9s -- env

结果:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
... ...
KUBIA_SERVICE_HOST=10.96.9.157
KUBIA_SERVICE_PORT=80

... ...

服务名称中的横杠被转换为下划线,并且当服务名称用作环境变量名称中的前缀时,所有的字母都是大写的。

2.2. 通过 DNS 发现服务

通过 FQDN (全限定域名)连接服务:


kubia 是服务名称,default 代表所在的命名空间;svc.cluster.local 是在所有集群本地服务名称中使用的可配置集群域后缀。(Kubernetes 通过修改每个容器的 /etc/resolv.conf 文件实现)

注意:客户端仍然必须要知道服务的端口号,如果不是标准端口(80 或 5432),客户端可以从环境变量中获取端口号。

在 pod 容器中运行 shell:

```kubectl exec -it kubia-jkj9s bash

访问服务:

curl http://kubia.default.svc.cluster.local

三、连接集群外部的服务

3.1. 服务的 endpoint

服务和 pod 并不是直接相连的,而是通过 endpoint 资源。

查看服务描述,可以看到包含 endpoint 的信息:

kubectl describe svc kubia

endpoint 资源就是暴露一个服务的 IP 地址和端口的列表,也可以使用命令查看:

kubectl get endpoints kubia

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

3.2. 手动配置服务的 endpoint

如果创建了不包含 pod 选择器的服务, Kubernetes 将不会创建 endpoint 资源(毕
竟缺少选择器,将不会知道服务中包含哪些 pod)。这样就需要创建 endpoint 资源来指定该服务的 endpoint 列表。

3.2.1. 创建没有选择器的服务

# external-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: external-service # 服务的名字必须和 endpoint 对象的名字相匹配
spec:
  ports:
  - port: 80

kubectl create -f external-service.yaml

3.2.2. 创建 endpoint

# external-service-endpoints.yaml

apiVersion: v1
kind: Endpoints
metadata:
  name: external-service # endpoint 的名字必须和服务的名字相匹配
subsets:
  - addresses: # 服务将连接重定向到 endpoint 的 IP 地址
    - ip: 11.11.11.11
    - ip: 22.22.22.22
    ports:
    - port: 80 # endpoint 的目标端口

kubectl create -f external-service-endpoints.yaml

3.2.3. 为外部服务创建别名

除了手动配置服务的 endpoint 来代替公开外部服务方法,有一种更简单的方法,就是通过其完全限定域名(FQDN)访问外部服务

apiVersion: v1
kind: Service
metadata:
  name: external-service
spec:
  type: ExternalName # type 设置成 ExternalName
  externalName: api.somecompany.com # 实际服务的完全限定域名
  ports:
  - port: 80

服务创建完成后 pod 可以通过 external-service.svc.cluster.local 域名连接到外部服务,而不是使用服务的实际 FQDN。

四、将服务暴露给外部客户端

有几种方式可以在外部访问服务:

  • 将服务的类型设置成 NodePort:每个集群节点都会在节点上打开一个端口,对于 NodePort 服务,每个集群节点在节点本身(因此得名叫 NodePort)上打开一个端口,并将在该端口上接收到的流量重定向到基础服务。该服务仅在内部集群 IP 和端口上才可访间,但也可通过所有节点上的专用端口访问。
  • 将服务的类型设置成 LoadBalance:NodePort类型的一种扩展,这使得服务可以通过一个专用的负载均衡器来访问,这是由 Kubernetes 中正在运行的云基础设施提供的。负载均衡器将流量重定向到跨所有节点的节点端口。客户端通过负载均衡器的 IP 连接到服务。
  • 创建一个 Ingress 资源,这是一个完全不同的机制,通过一个 IP 地址公开多个服务。它运行在 HTTP 层(网络协议第 7 层)上,因此可以提供比工作在第 4 层的服务更多的功能。

4.1. 使用 NodePort 类型的服务

创建 NodePort 类型的服务:

# kubia-svc-nodeport.yaml

apiVersion: v1
kind: Service
metadata:
  name: kubia-nodeport
spec:
  type: NodePort # NodePort 类型
  ports:
  - port: 80 # 服务集群 IP 的端口号
    targetPort: 8080 # 背后 pod 的目标端口号
    nodePort: 30123 # 通过集群节点的 30123 端口可以访问该服务,如果没有指定则随机分配
  selector:
    app: kubia

kubectl create -f kubia-svc-nodeport.yaml

查看服务:

kubectl get svc kubia-nodeport

因为我们使用的 Minikube,可以运行下面命令访问 NodePort 服务:让客户端发现

minikube service kubia-nodeport

4.2. 通过负载均衡器将服务暴露出来

在云提供商上运行的 Kubernetes 集群通常支持从云基础架构自动提供负载平衡器。所有需要做的就是设置服务的类型为 Load Badancer 而不是 NodePort。

创建 LoadBalance 服务:

# kubia-svc-loadbalancer.yaml

apiVersion: v1
kind: Service
metadata:
  name: kubia-loadbalancer
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: kubia

没有指定端口则随机分配一个端口

kubectl create -f kubia-svc-loadbalancer.yaml

创建服务后,云基础架构需要一段时间才能创建负载均衡器并将其 IP 地址写入服务对象。完成之后 IP 地址将被列为服务的外部 IP 地址:

kubectl get svc kubia-loadbalancer

4.3. 通过 Ingress 暴露服务

为什么需要 Ingress:一个重要的原因是每个 LoadBalancer 服务都需要自己的负载均衡器,以及独有的公有 IP 地址,而 Ingress 只需要一个公网 IP 就能为许多服务提供访问。当客户端向 Ingress 发送 HTTP 请求时,Ingress 会根据请求的主机名和路径决定请求转发到的服务。

Ingress 在网络栈 (HTTP) 的应用层操作,并且可以提供一些服务不能实现的功能,诸如基于 cookie 的会话亲和性 (session affinity) 等功能。

启用 Ingress 组件:

minikube addons enable ingress

因为 minikube 启动参数添加了 —driver=none 参数,不支持 Ingress 后续暂略。。。

posted @ 2021-01-21 11:11  VictorBu  阅读(796)  评论(0编辑  收藏  举报