让客户端发现 pod 并与之通信——《K8s in action》读书笔记5

让客户端发现 pod 并与之通信

1、Service 介绍

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

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - port: 80          # 该 Service 的端口。
    targetPort: 8080  # 该 Service 对应后端容器的端口。
  - name: https
    poret: 443
    targetPort: https # 如果 pod 内部使用了端口命名,可以直接指定端口名称。
  selector:
    app: kubia        # 设置选择器选择 metadata.app=kubia 的 pod 作为后端。

1.1、为什么需要Service?

  • 客户端无法提前知道提供服务的 pod 的 IP 地址。K8s 会在 pod 启动前才分配 IP 地址。
  • 每个 pod 的生命周期时短暂的,其 IP 地址也是不稳定。
  • 水平伸缩意味着一个服务背后有多个 pod 提供相同的服务。

1.2、服务发现

  • 环境变量。 K8s 可以自动注入当前svc对应的环境变量(XXX_SERVICE_HOST、XXX_SERVICE_PORT)给新创建的 pod。
  • DNS发现服务。 K8s 自带的域名解析服务器 core-dns 可以通过全限定域名(FQDN)解析服务的 IP 地址。K8s 通过修改每个容器的 /etc/resolve.conf 使 core-dns生效。

2、访问集群外部服务

  • 方式一:通过手动配置 EndPoints 的方式,可以让 k8s 集群内的客户端像访问集群内的服务一样地访问集群外部服务。
  • 方式二:通过配置 ExternalName 类型的 Service,完全绕过 Service 直接访问外部域名。

2.1、EndPoints 介绍

EndPoints 是一种介于 Service 和 Pod 之间的一种资源,它暴露一个服务的 IP 地址和端口的列表。当我们在 Service 的 spec 中定义 pod 选择器时,选择器会动态调整 Service 背后的 Endpoints,从而实现 Service 发现后端 pod 的逻辑。相反,如果我们不为 Service 定义 pod 选择器,那么 K8s 不会创建其对应的 Endpoints,需要手动创建 Endpoints 资源。

apiVersion: v1
kind: Service
metadata: 
  name: external-service  # Endpoints 的名称必须与 Service 的名称匹配。
spec:
  ports: 
  - port: 80
---
apiVersion: v1
kind: Endpoints
metadata: 
  name: external-service  # Endpoints 的名称必须与 Service 的名称匹配。
subsets:
  - addresses:
    - ip: 11.11.11.11     # Service 将连接重定向到这里的 IP 地址。
    - ip: 22.22.22.22
    ports:
    - port: 80            # 目标端口

2.2、ExternalName 类型的 Service

ExternalName 服务仅在 DNS 级别实施——为服务创建了简单的 CNAME DNS记录,因此连接到此 Service 的客户端将直接连接到外部服务,完全绕过 Service 资源的代理。出于这个原因,这些类型的 Service 不会被配置 ClusterIP。

(PS:CNAME 记录指向完全限定域名而不是数字 IP 地址。)

apiVersion: v1
kind: Service
metadata: 
  name: external-service
spec:
  type: ExternalName    # 上文中未指定类型,则默认为 ClusterIP 类型。
  externalName: someapi.somecompany.com
  ports:
  - port: 80

3、暴露服务给集群外部

将集群内的服务暴露给集群外部的客户端,大致有三种方式:

  • NodePort。简单粗暴,每台节点都绑一个固定端口。
  • LoadBalance。使用外部的负载均衡器。
  • Ingress。七层网络代理。

3.1、使用 NodePort 类型的服务

NodePort 类型的 Service 会在每一个节点开放同一个端口,用户可以通过任意 Node 节点的 IP 地址配合 nodePort 来访问该服务。此方式需要为客户端打开每一台节点上对应端口的防火墙策略,否则用户无法访问。此方法还有一个缺点,就是端口占用数量太多了,一个集群能开放的端口数量就是一台机器的端口数量了。

apiVersion: v1
kind: Service
metadata: 
  name: kubia-nodeport
spec:
  type: NodePort
  ports:
  - port: 80            # ClusterIP 的端口。
    targetPort: 8080    # 背后 pod 的实际端口。
    nodePort: 30123     # 创建 NodePort 此端口可以不指定,K8s 将随机选择一个端口。
  selector:
    app: kubia

3.2、通过 LoadBalancer 将服务暴露出来

LoadBalancer 类型的 Service 是一个具有额外的基础设施提供的负载均衡器 NodePort 服务。它也会自动创建一个 nodePort 的端口,但是用户不感知此端口,因此我们不需要配置防火墙规则开放这个端口。当然,如果配置防火墙规则开放了这个 nodePort 端口,那么效果就像前面的 NodePort 类型的 Service 一样。

apiVersion: v1
kind: Service
metadata: 
  name: kubia-loadbalancer
spec:
  type: LoadBalancer    # 自动从当前 K8s 集群的基础架构获取负载均衡器。(华为云CCE服务对应华为云ELB服务。)
  ports:
  - port: 80            # ClusterIP 的端口。
    targetPort: 8080    # 背后 pod 的实际端口。
  selector:
    app: kubia

3.3、通过 Ingress 暴露服务

通过 Ingress 暴露服务的优势:

  • 可以通过一个公网IP为多个服务提供访问入口。
  • 由于是 7 层网络代理,因此可以提供一些 Service 无法提供的功能(如cookie的会话亲和性等功能)。

实现原理

暂略。
(nginx-ingress-controller主页:https://kubernetes.github.io/ingress-nginx/)

Ingress资源

apiVersion: v1
kind: Ingress
metadata: 
  name: kubia
spec:
  tls:                          # 集群外部的客户端发起HTTPS请求时,客户端与ingress-controller之间的连接是TLS加密传输的,而ingress-controller到后端的pod之间则是HTTP明文传输的。
  - hosts:
    - kubia.example.com
    secretName: tls-secret
  rules:
  - host: kubia.example.com     # ingress 将域名 kubia.example.com 映射到后端的 Service
    http:
      paths:
      - path: /kubia            # 对 kubia.example.com/kubia 的请求转发至 kubia 的 Service
        backend:
          serviceName: kubia
          servicePort: 80
      - path: /bar              # 对 kubia.example.com/bar 的请求转发至 bar 的 Service
        backend:
          serviceName: bar
          servicePort: 80
  - host: bar.example.com
    http:
      paths:
      - path: /                 # 对 bar.example.com 的请求转发至 bar 的 Service
        backend:
          serviceName: bar
          servicePort: 80

为Ingress创建TLS认证的步骤

# 生成私钥
openssl genrsa -out tls.key 2048
# 生成证书(自签名)
openssl req -new -x509 -key tls.key -out tls.cert -days 360 -subj /CN=kubia.example.com

# 通过私钥文件和证书文件生成一个Secret
kubectl create secret tls tls-secret --cert-tls.cert --key=tls.key

# 配置 ingress-controller 以及 Ingress 资源
# ...

curl -k -v https://kubia.example.com/kubia

4、pod 就绪后发出信号

TODO

5、使用 headless 服务来发现独立的 pod

TODO

6、排除服务故障经验

TODO

7、Tips

# 双横杠(--)意味着 kubectl 命令项的结束。
# 后面的内容是指在 pod 内部需要执行的命令(否则-s会被解析成kubectl exec的参数)。
# 如果执行的命令没有以横杠开始的参数,则不需要使用双横杠。
kubectl exec kubia-7nog1 -- curl -s http://10.111.249.153
  • pod 是否使用内部的 DNS 服务器是根据 pod 中 spec 的 dnsPolicy 属性来决定的。
  • ping 一个 svc 的 FQDN 或 clusterIP 是不通的,因为 clusterIP 是一个虚拟 IP,只有在与服务端口结合是才有意义。(PS:11章会解释其工作的原理。)
  • JSONPath使用指导:https://kubernetes.io/docs/reference/kubectl/jsonpath/
  • 使用 kubectl explain 检查服务的会话亲和性是否设置为None。
  • 通过配置 Service 的 spec.externalTrafficPolicy=Local 将外部通信重定向到接受连接的节点上运行的 pod 来阻止额外跳数。此配置引发的两个缺点:
    • 必须确保负载平衡器将连接转发给至少具有一个 pod 的节点。
    • 使用 local 外部流量策略的服务可能会导致跨 pod 的负载分布不均衡(A节点一个pod,B节点两个pod,则三个pod的流量比为2:1:1)。

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 nz_nuaa@163.com
github