《K8s 实战》——从集群外部访问 Service

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

  • NodePort:每个集群节点打开一个端口,并将在该端口上收到的流量重定向到该服务
  • LoadBalance:NodePort 类型的一种扩展。服务通过一个专用的负载均衡器来访问,负载均衡器将流量重定向到跨所有节点的节点端口
  • Ingress:通过一个 IP 地址公开多个服务。它运行在 HTTP 层(网络协议第七层,而服务运行在第四层)

1. 使用 NodePort 类型的服务

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: kubia-nodeport
spec:
type: NodePort # 默认ClusterIP
ports:
- port: 80
targetPort: 8080
nodePort: 30123 # 集群节点端口(不指定则随机)
selector:
app: kubia
  • 可通过 <node-ips>:30123<cluster-ip>:80 访问

2. 使用 LoadBalancer 类型的服务

  • 负载均衡器拥有独一无二的可公开访问的 IP 地址,并将连接重定向到服务
  • 若 K8s 在不支持 LoadBalancer 服务的环境中运行,则不会调用负载均衡器,此时服务仍表现为 NodePort 服务
1
2
spec:
type: LoadBalancer
  • 可通过 <external-ip>:80 访问

3. 外部连接的特性

(1) 网络跳数

  • 当访问到某个节点的端口,服务随机转发 Pod,此时 Pod 可能不在此节点上,这就需要额外的网络跳转。可将服务配置为仅将外部连接重定向到接收该连接的节点上的 Pod 来阻止跳转:
1
2
spec:
externalTrafficPolicy: Local
  • 如果服务定义包含此配置,若无本地 Pod 存在,连接将挂起,而且可能会导致 Pod 的负载分布不均衡

(2) 客户端 IP 不会被记录

  • 当集群内的客户端连接到服务时,支持服务的 Pod 可以获取客户端的 IP 地址。但是当通过节点端口接收到连接时,由于对数据包执行了源网络地址转换(SNAT),因此数据包的源 IP 将发生更改
  • Local 外部流量策略会保留客户端 IP,因为接收连接的节点和 Pod 所在节点没有额外跳跃(不执行 SNAT)

二、通过 Ingress 暴露服务

  • 每个 LoadBalancer 服务都需要自己的负载均衡器以及独有的公有 IP,而 Ingress 只需一个公网 IP 便可为多个服务提供访问
  • 客户端向 Ingress 发送 HTTP 请求时,Ingress 会根据请求的主机名和路径决定请求转发到的服务
  • Ingress 在网络栈(HTTP)的应用层,可以提供一些服务不能实现的功能。如基于 Cookie 的会话亲和性
  • 只有 Ingress 控制器在集群中运行,Ingress 资源才能正常工作。不同的 K8s 环境使用不同的控制器实现,有些不提供默认控制器
  • Ingress 目前支持 L7(网络第七层)(HTTP/HTTPS)负载均衡,计划支持 L4 负载均衡

1. 创建 Ingress 资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ cat ig.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: kubia
spec:
rules:
# 接收所有请求主机kubia.example.com的HTTP请求,转发到kubia-nodeport服务的80端口
- host: kubia.example.com # must be a DNS name, not an IP address
http:
paths:
- path: /
backend:
serviceName: kubia-nodeport
servicePort: 80
$ kubectl apply -f ig.yaml

$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
kubia <none> kubia.example.com 192.168.99.100 80 14s
$ vi /etc/hosts
192.168.99.100 kubia.example.com
$ curl http://kubia.example.com
You've hit kubia-5asi2

2. Ingress 工作原理

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

3. 暴露多个服务

  • 将不同的服务映射到不同主机的不同路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
spec:
rules:
- host: kubia.example.com
http:
paths:
- path: /kubia
backend:
serviceName: kubia
servicePort: 80
- path: /foo
backend:
serviceName: foo
servicePort: 80
- host: bar.example.com
http:
paths:
- path: /
backend:
serviceName: bar
servicePort: 80

4. 处理 TLS 传输

  • 当客户端创建到 Ingress 控制器的 TLS 连接时,控制器将终止 TLS 连接。客户端和控制器之间的通信是加密的,而控制器和后端 Pod 之间的通信不是。运行在 Pod 上的应用程序不需要支持 TLS
  • 需要将证书和私钥附加到 Ingress :
1
2
3
$ openssl genrsa -out tls.key 2048
$ openssl req -new -x509 -key tls.key -out tls.cert -days 360 -subj
$ kubectl create secret tls tls-secret --cert=tls.cert --key=tls.key
  • 更新 Ingress 对象,让其接收 kubia.example.com 的 HTTPS 请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ cat ig.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: kubia
spec:
tls:
- hosts: # 接收tls连接
- kubia.example.com
serviceName: tls-secret # 私钥和证书
rules:
- host: kubia.example.com
http:
paths:
- path: /
backend:
serviceName: kubia
servicePort: 80

# 输出程序响应及证书服务器的响应
$ curl -k -v https://kubia.example.com