1

将服务暴露给外部客户端 P136

图 5.5 将服务暴露给外部客户端

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

  • 将服务的类型设置成 NodePort
  • 将服务的类型设置为 LoadBalance
  • 创建一个 Ingress 资源

使用 NodePort 类型的服务 P137

通过创建一个 NodePort 服务,可以让 Kubernetes 在其所有节点上保留一个端口(所有节点上都使用相同端口号),并将传入的连接转发给作为服务部分的 pod 。 P137

创建 NodePort 类型的服务 P137

可以使用如下描述文件 kubia-svc-nodeport.yaml 创建一个 NodePort 类型的服务。

# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 资源类型为 Service
kind: Service
metadata:
  # Service 的名称
  name: kubia-nodeport
spec:
  # 指定服务类型为 NodePort
  type: NodePort
  # 该服务可用的端口
  ports:
    # 第一个可用端口的名字
    - name: http
      # 可用端口为 80
      port: 80
      # 服务将连接转发到容器的 8080 端口
      targetPort: 8080
      # 通过集群节点的 30000 端口可以访问该服务
      nodePort: 30000
    # 第二个可用端口的名字
    - name: https
      # 可用端口为 443
      port: 443
      # 服务将连接转发到容器的 8443 端口
      targetPort: 8443
      # 通过集群节点的 32767 端口可以访问该服务
      nodePort: 32767
  # 具有 app=kubia 标签的 pod 都属于该服务
  selector:
    app: kubia

nodePort 属性不是强制的,如果忽略就会随机选择一个端口。 P137

kubectl get services kubia-nodeport: 查看该服务的基础信息

NAME               TYPE           CLUSTER-IP       EXTERNAL-IP       PORT(S)                      AGE
kubia-nodeport     NodePort       10.111.59.156    <none>            80:30000/TCP,443:32767/TCP   2s

PORT(S) 列显示集群 IP 内部端口 (80, 443) 和节点端口 (30000, 32767) ,可通过 10.111.59.156:80<any-node-ip>:30000 等访问服务。 P138

图 5.6 外部客户端通过节点 1 或者节点 2 连接到 NodePort 服务

使用 JSONPath 输出需要的信息:通过指定 kubectl 的 JSONPath ,我们可以只输出需要的信息。例如: kubectl get nodes -o jsonpath='{.items[*].status.addresses[0].address}' 将输出所有节点的 IP 地址。

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

负载均衡器拥有自己独一无二的可公开访问的 IP 地址,并将所有连接重定向到服务。 P140

创建 LoadBalance 服务 P140

可以使用如下描述文件 kubia-svc-loadbalancer.yaml 创建一个 LoadBalancer 类型的服务。

# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 资源类型为 Service
kind: Service
metadata:
  # Service 的名称
  name: kubia-loadbalancer
spec:
  type: LoadBalancer
  # 该服务可用的端口
  ports:
    # 第一个可用端口的名字
    - name: http
      # 可用端口为 80
      port: 80
      # 服务将连接转发到容器的 8080 端口
      targetPort: 8080
    # 第二个可用端口的名字
    - name: https
      # 可用端口为 443
      port: 443
      # 服务将连接转发到容器的 8443 端口
      targetPort: 8443
  # 具有 app=kubia 标签的 pod 都属于该服务
  selector:
    app: kubia

使用 minikube 会发现服务的 EXTERNAL-IP 一直为 <pending> ,我们可以使用 minikube 自带的 minikube tunnel 命令可以完成暴露(02. 开始使用 Kubernetes 和 Docker 介绍过相关处理及踩过的坑)。

图 5.7 外部客户端连接一个 LoadBalancer 服务.png

LoadBalancer 类型的服务是一个具有额外的基础设施提供的负载均衡器 NodePort 服务。使用 kubectl describe service kubia-loadbalancer 命令可以发现该服务选择了一个节点端口。 P141

了解外部连接的特性 P142

了解并防止不必要的网络跳数:当外部客户端通过节点端口连接到服务时,随机选择的 pod 并不一定在接收连接的同一节点上。可能需要额外的网络跳转才能到达 pod 。可配置 Service.spec.externalTrafficPolicy 的值为 Local 指定仅将外部通信重定向到接收连接的节点上运行的 pod 。

  • 如果接收连接的节点上没有运行对应的 pod ,那么连接将挂起,所以需要确保负载均衡器将连接转发给至少具有一个 pod 的节点
  • 假设有一个两节点三个 pod 的集群(节点 A 运行一个 pod ,节点 B 运行两个 pod),如果负载均衡器在两个节点间均匀分布连接,那么 pod 的负载分布不均衡

图 5.8 使用 Local 外部流量策略的服务可能会导致 pod 的负载分布不均衡

客户端 IP 是不记录的:当通过节点端口接收到连接时,由于对数据包执行了源网络地址转换 (SNAT) ,因此数据包对源 IP 将发生更改。如果配置 Service.spec.externalTrafficPolicy 的值为 Local ,那么将会保留客户端 IP ,因为在接收连接的节点和托管目标 pod 的节点之间没有额外的跳跃(不执行 SNAT )。 P143

通过 Ingress 暴露服务 P143

为什么需要 Ingress P144

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

图 5.9 通过一个 Ingress 暴露多个服务

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

Ingress 控制器是必不可少的 P144

只有 Ingress 控制器在集群中运行, Ingress 资源才能正常工作。 P144

在 minikube 上启动 Ingress 的扩展功能 P145

minikube addons list: 可以列出所有的插件及其启用状态

minikube addons enable ingress: 启用 ingress 插件

kubectl get pods -n kube-system: 查看 kube-system 命名空间下的 pod ,可以发现 Ingress 控制器 pod

创建 Ingress 资源 P145

可以使用如下描述文件 kubia-ingress.yaml 创建一个 Ingress 资源。

# 遵循 extensions/v1beta1 版本的 Kubernetes API
apiVersion: extensions/v1beta1
# 资源类型为 Ingress
kind: Ingress
metadata:
  # Ingress 的名称
  name: kubia
spec:
  # Ingress 的规则列表
  rules:
    # 第一条规则匹配的域名为 kubia.example.com
    - host: kubia.example.com
      # 匹配 http
      http:
        # 匹配的路径列表
        paths:
          # 第一条路径为 /
          - path: /
            # 该路径将被转发到的后端服务
            backend:
              # 将被转发到 kubia-nodeport 服务
              serviceName: kubia-nodeport
              # 对应服务的端口为 80
              servicePort: 80

minikube 下创建 Ingress 资源时报错了,提示超时。后来找到一种解决方案:使用 kubectl edit ValidatingWebhookConfiguration/ingress-nginx-admission 进行编辑,找到 failurePolicy: Fail 这行,并将 Fail 改为 Ignore ,然后就能成功创建 Ingress 资源了,等一段时间后就可以看见其分配了一个 IP 地址 (192.168.64.70) 。

为了能将指定的域名 kubia.example.com 指向分配的 IP 地址 (192.168.64.70),可以使用 SwitchHosts 这个软件进行快速切换。

此时我们在主机上就可以通过 curl kubia.example.com 访问 kubia-nodeport 服务了。

了解 Ingress 的工作原理 P147

  1. 客户端对 kubia.example.com 执行 DNS 查找,本地操作系统返回了 Ingress 控制器的 IP
  2. 客户端向 Ingress 控制器发送 HTTP 请求,并在 Host 头中指定 kubia.example.com
  3. 控制器从头部确定客户端尝试访问哪个服务,通过与该服务关联的 Endpoint 对象查看 pod IP ,并将客户端的请求转发给其中一个 pod

图 5.10 通过 Ingress 访问 pod

Ingress 控制器不会将请求转发给服务,只用它来选择一个 pod 。大多数控制器都是这样工作的。 P147

通过相同的 Ingress 暴露多个服务 P147

Ingerssrulespaths 都是数组,所以它们可以包含多个条目,因此一个 Ingress 可以将多个域名和路径映射到多个服务。 P147

配置 Ingress 处理 TLS 传输 P149

Ingress 创建 TLS 认证 P149

当客户端创建到 Ingress 控制器到 TLS 连接时,控制器将终止 TLS 连接。客户端和控制器之间到通信是加密的,而控制器和后端 pod 之间的通信则未加密。运行在 pod 上的应用程序不需要支持 TLS 。 P149

为了让 Ingress 控制器负责处理与 TLS 相关的所有内容,需要将证书和私钥附加到 Ingress 。这两个必须资源存储在称为 Secret 的 Kubernetes 资源中(将在第 7 章中详细介绍 Secret),然后在 Ingress 的描述文件中引用它。 P149

openssl genrsa -out tls.key 2048: 创建私钥

openssl req -new -x509 -key tls.key -out tls.cert -days 365 -subj /CN=kubia.example.com: 创建证书

kubectl create secret tls tls-secret --cert=tls.cert --key=tls.key: 创建 Secret 资源

然后我们就可以改写 kubia-ingress.yaml 得到 kubia-ingress-tls.yaml 描述文件:

...
spec:
  # 配置 TLS
  tls:
    # 第一条配置的域名列表
    - hosts:
      - kubia.example.com
      # 这些域名使用 tls-secret 获得私钥和证书
      secretName: tls-secret
  ...

然后我们就可以使用 curl -k -v https://kubia.example.com 通过 HTTPS 访问服务了。( minikube 下未进行上述操作前也可以访问,不过可以发现是 Ingress 控制器使用了假证书) P150

pod 就绪后发出信号 P150

与存活探测器(04. 副本机制和其他控制器:部署托管的 pod 中介绍过)类似, Kubernetes 还允许为容器定义就绪探测器。就绪探测器会定期调用,并确保特定的 pod 是否接收客户端请求。当容器的就绪探测器返回成功时,表示容器已准备好接收请求。 P151

就绪探测器的类型P151

  • Exec 探测器:在容器内执行任意命令,并检查命令的退出状态码。如果状态码是 0 ,则探测成功,认为容器已经就绪,所有其他状态码都被认为失败
  • HTTP GET 探测器:对容器的 IP 地址(指定的端口和路径)执行 HTTP GET 请求。如果探测器收到响应,并且响应状态码不代表错误(状态码为 2xx 或 3xx ),则认为探测成功,认为容器已经就绪。如果服务器返回错误响应状态码或者没有响应,那么探测就被认为是失败的
  • TCP Socket探测器:尝试与容器指定端口建立 TCP 连接。如果连接成功建立,则探测成功,认为容器已经就绪

了解就绪探测器的操作 P151

启动容器时,可以为 Kubernetes 配置一个等待时间,经过等待时间后才可以执行第一次准备就绪检查。之后,它会周期性地调用探测器,并根据就绪探测器的结果采取行动。如果某个 pod 报告它尚未准备就绪,那么就会从服务中删除该 pod ;如果这个 pod 再次准备就绪,那么就会将给 pod 重新添加到服务中。 P151

图 5.11 就绪探测失败的 pod 从服务的 endpoint 中移除

就绪探测器和存活探测器的区别 P151

存活探测器通过杀死异常的容器并用新的正常容器替代它们来保持 pod 正常工作,而就绪探测器确保只有准备好处理请求的 pod 才可以接收请求,并不会终止或重新启动容器。 P151

就绪探测器的重要性:确保客户端只与正常的 pod 交互,并且永远不会知道系统存在的问题。 P152

了解就绪探测器的实际作用 P154

务必定义就绪探测器 P155

应该始终定义一个就绪探测器,即使它只是向基准 URL 发送 HTTP 请求一样简单。如果没有将就绪探测器添加到 pod 中,那么它们启动后几乎立即成为服务端点。 P155

不要将停止 pod 的逻辑纳入到就绪探测器中 P155

当一个容器关闭时,运行在其中的应用程序通常会在收到终止信号后立即停止接收连接。但在启动关机程序后,没有必要让就绪探测器返回失败以达到从所有服务中移除 pod 目的,因为在该容器删除后, Kubernetes 就会自动从所有服务中移除该容器。 P155

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

要让客户端连接到所有 pod ,需要找出每个 pod 的 IP 。 Kubernetes 允许客户通过 DNS 查找发现 pod IP 。通常,当执行服务的 DNS 查找时, DNS 服务器会返回单个 IP —— 服务的集群 IP 。但是,如果告诉 Kubernetes ,不需要为服务提供集群 IP (通过在服务 spec 中将 clusterIP 字段设置为 None 来完成此操作),则 DNS 服务器将会返回 pod IP 而不是单个服务 IP 。 P155

DNS 服务器不会返回单个 DNS A 记录,而是会为该服务返回多个 A 记录,每个记录指向当时支持该服务的单个 pod 的 IP 。客户端因此可以做一个简单的 DNS A 记录查询并获取属于该服务的所有 pod 的 IP 。客户端可以使用该信息连接到其中的一个、多个或全部。 P155

创建 headless 服务 P156

可以使用如下描述文件 kubia-svc-headless.yaml 创建一个 headless 的 Service 资源。

# 遵循 v1 版本的 Kubernetes API
apiVersion: v1
# 资源类型为 Service
kind: Service
metadata:
  # Service 的名称
  name: kubia-headless
spec:
  # 该服务的集群 IP 为 None ,使其变为 headless 的
  clusterIP: None
  # 该服务可用的端口
  ports:
    # 第一个可用端口的名字
    - name: http
      # 可用端口为 80
      port: 80
      # 服务将连接转发到容器的 8080 端口
      targetPort: 8080
    # 第二个可用端口的名字
    - name: https
      # 可用端口为 443
      port: 443
      # 服务将连接转发到容器的 8443 端口
      targetPort: 8443
  # 具有 app=kubia 标签的 pod 都属于该服务
  selector:
    app: kubia

通过 DNS 发现 pod P156

kubia 容器镜像不包含 nslookup 二进制文件,所以需要用一个新的容器镜像来执行相应的命令。 P156

kubectl run dnsutils --image=tutum/dnsutils --generator=run-pod/v1 --command -- sleep infinity: 创建一个可以执行 nslookup 命令的 pod

kubectl exec dnsutils nslookup kubia-headless: 在 dnsutils pod 内执行 nslookup kubia-headless 命令,可以发现 DNS 服务器为 kubia-headless.default.svc.cluster.local FQDN 返回了多个 IP ,且它们都是 pod 的 IP ,可以通过 kubectl get pods -o wide 进行确认

kubectl exec dnsutils nslookup kubia: 在 dnsutils pod 内执行 nslookup kubia 命令,可以发现 DNS 服务器为 kubia.default.svc.cluster.local FQDN 返回了一个 IP ,该 IP 是服务的集群 IP

尽管 headless 服务看起来可能与常规服务不同,但是在客户的视角上它们并无不同。对于 headless 服务,由于 DNS 返回了 pod 的 IP ,客户端直接连接到该 pod ,而不是通过服务代理(注意这里是直接访问的 pod ,所以对应的端口要改成 pod 的端口)。 P157

注意: headless 服务仍然提供跨 pod 的负载均衡,但是通过 DNS 轮循机制不是通过服务代理 P157

发现所有的 pod —— 包括未就绪的 pod P157

可以通过在 Service.metadata.annotations 下面增加一条 service.alpha.kubernetes.io/tolerate-unready-endpoints: "true" 告诉 Kubernetes 无论 pod 的准备状态如何,希望将所有 pod 添加到服务中 P158

排除服务故障 P158

如果无法通过服务访问 pod ,应根据下面的列表进行排查: P158

  • 确保从集群内连接到服务的集群 IP
  • 不要通过 ping 服务 IP 来判断服务是否可访问(服务的集群 IP 是虚拟 IP ,是无法 ping 通的)
  • 如果已经定义了就绪探测器,请确保它返回成功;否则该 pod 不会成为服务的一部分
  • 要确认某个容器是服务的一部分,请使用 kubectl get endpoints 来检查相应的端点对象
  • 如果尝试通过 FQDN 或其中一部分来访问服务,但并不起作用,请查看是否可以使用其集群 IP 而不是 FQDN 来访问服务
  • 检查是否连接到服务公开的端口,而不是目标端口
  • 尝试直接连接到 pod IP 以确认 pod 正在接收正确端口上的连接
  • 如果甚至无法通过 pod 的 IP 访问应用,请确保应用不是仅绑定到 localhost (127.0.0.1)
本文首发于公众号:满赋诸机(点击查看原文) 开源在 GitHub :reading-notes/kubernetes-in-action

满赋诸机
12 声望3 粉丝