一、使用Service代理外部服务¶
如果我们使用Service代理k8s外部服务,可能适应于以下场景:
-
希望在生产环境中使用某个固定的名称而非 IP 地址访问外部的中间件服务
-
希望 Service 指向另一个 Namespace 中或其他集群中的服务
-
正在将工作负载转移到 Kubernetes 集群,但是一部分服务仍运行在 Kubernetes 集群之外的 backend
当我们使用Service代理k8s外部服务时,在编写svc的yaml文件的时候,切记去除spec.selector字段。
接下来通过示例进行说明:
1.定义一个yaml文件
$ vim nginx-svc-external.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx-svc-external
name: nginx-svc-external
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
sessionAffinity: None
type: ClusterIP
---
apiVersion: v1
kind: Endpoints
metadata:
labels:
app: nginx-svc-external
name: nginx-svc-external
subsets:
- addresses:
- ip: 45.253.16.233
ports:
- name: http
port: 80
protocol: TCP
上面部分参数说明:
-
subsets.addresses.ip(string):必需端点的 IP。不可以是本地回路(127.0.0.0/8)、链路本地(169.254.0.0/16)或链路本地多播(224.0.0.0/24)地址。 IPv6 也被接受,但并非在所有平台上都完全支持。 此外,诸如 kube-proxy 等某些 Kubernetes 组件还没有准备好支持 IPv6。
-
subsets.ports.port (int32):必需端点的端口号。
-
subsets.ports.protocol (string):此端口的 IP 协议。必须是 UDP、TCP 或 SCTP。默认值为 TCP。
-
subsets.ports.name (string):端口的名称。此字段必须与相应 ServicePort 中的
name字段匹配。必须是 DNS_LABEL。 仅当定义了一个端口时才可选。
2.部署服务
$ k create -f nginx-svc-external.yaml
service/nginx-svc-external created
endpoints/nginx-svc-external created
3.查看并测试服务
(1)查看服务
$ k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hpa-nginx ClusterIP 10.0.148.68 <none> 80/TCP 34h
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 10d
nginx-svc-external ClusterIP 10.0.183.157 <none> 80/TCP
(2)测试服务
$ curl 10.0.183.157
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head><title>403 Forbidden</title></head>
<body>
<h1>403 Forbidden</h1>
<p>You don't have permission to access the URL on this server.<hr/>Powered by Tengine</body>
</html>
4.进入上面部署名为nginx-deployment-df755dd4-x952n的Pod节点测试服务。观察到,服务可以访问。
$ k get po
NAME READY STATUS RESTARTS AGE
cluster-test-79b978867f-4x2lw 1/1 Running 107 (30m ago) 9d
etcd 1/1 Running 4 (14h ago) 6d
nginx-deployment-df755dd4-262sn 1/1 Running 0 8h
nginx-deployment-df755dd4-fwzjf 1/1 Running 0 8h
nginx-deployment-df755dd4-x952n 1/1 Running 0 8h
$ k exec -it nginx-deployment-df755dd4-x952n -- sh
Connecting to nginx-svc-external (10.0.183.157:80)
二、多端口服务¶
对于某些服务,你需要公开多个端口。 Kubernetes 允许你在 Service 对象上配置多个端口定义。 为服务使用多个端口时,必须提供所有端口名称,以使它们无歧义。
举个例子:比如将 Service 的 80 端口代理到后端的 9376,443 端口代理到后端的 9377:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
- name: https
protocol: TCP
port: 443
targetPort: 9377
注意:与一般的 Kubernetes 名称一样,端口名称只能包含小写字母数字字符 和 -。 端口名称还必须以字母数字字符开头和结尾。
三、服务发现¶
Kubernetes 支持两种基本的服务发现模式 ——环境变量和 DNS。
3.1 环境变量¶
当 Pod 运行在 Node 上,kubelet 会为每个活跃的 Service 添加一组环境变量。 kubelet 为 Pod 添加环境变量 {SVCNAME}_SERVICE_HOST 和 {SVCNAME}_SERVICE_PORT。 这里 Service 的名称需大写,横线被转换成下划线。
举个例子,我们在安装完k8s集群后,在kube-system命名空间下安装了系统组件metrics-server,其服务如下:
$ k get svc -n kube-system | grep metrics-server
metrics-server ClusterIP 10.0.179.106 <none> 443/TCP 10d
此时在kube-system命名空间下任意Pod都会根据上述service创建一组变量用于服务发现,比如查看metrics-server容器的环境变量中的KUBE_DNS_PORT
$ k get po -n kube-system -l k8s-app=metrics-server
NAME READY STATUS RESTARTS AGE
metrics-server-6bf7dcd649-v7gqt 1/1 Running 17 (4m39s ago) 10d
$ k exec -it metrics-server-6bf7dcd649-v7gqt -n kube-system -- env | grep KUBE_DNS_PORT
KUBE_DNS_PORT_53_UDP_PORT=53
KUBE_DNS_PORT_53_TCP_PORT=53
KUBE_DNS_PORT_9153_TCP_PORT=9153
KUBE_DNS_PORT_53_TCP_ADDR=10.0.0.10
KUBE_DNS_PORT=udp://10.0.0.10:53
KUBE_DNS_PORT_9153_TCP_ADDR=10.0.0.10
KUBE_DNS_PORT_9153_TCP_PROTO=tcp
KUBE_DNS_PORT_53_UDP_PROTO=udp
KUBE_DNS_PORT_53_TCP=tcp://10.0.0.10:53
KUBE_DNS_PORT_9153_TCP=tcp://10.0.0.10:9153
KUBE_DNS_PORT_53_UDP=udp://10.0.0.10:53
KUBE_DNS_PORT_53_TCP_PROTO=tcp
KUBE_DNS_PORT_53_UDP_ADDR=10.0.0.10
注意:当你具有需要访问服务的 Pod 时,并且你正在使用环境变量方法将端口和集群 IP 发布到客户端 Pod 时,必须在客户端 Pod 出现 之前创建服务。 否则,这些客户端 Pod 将不会设定其环境变量。
3.2 DNS¶
支持集群的 DNS 服务器(例如 CoreDNS)监视 Kubernetes API 中的新服务,并为每个服务创建一组 DNS 记录。 如果在整个集群中都启用了 DNS,则所有 Pod 都应该能够通过其 DNS 名称自动解析服务,一般CoreDNS的service地址为service网段的第10个IP。
Kubernetes 还支持命名端口的 DNS SRV(服务)记录。 如果 my-service.my-ns 服务具有名为 http 的端口,且协议设置为 TCP, 则可以对 _http._tcp.my-service.my-ns 执行 DNS SRV 查询以发现该端口号、"http" 以及 IP 地址。
Kubernetes DNS 服务器是唯一的一种能够访问 ExternalName 类型的 Service 的方式。
举个例子,起一个名为redis的pod,进入此Pod后解析kube-system命名空间的kube-dns
1.创建名为redis的Pod
$ k run redis --image=redis:3.2.10-alpine
pod/redis created
$ k get po | grep redis
redis 1/1 Running 0 54s
2.测试解析kube-system命名空间的kube-dns
$ k exec -it redis -- nslookup kube-dns.kube-system
nslookup: can't resolve '(null)': Name does not resolve
Name: kube-dns.kube-system
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
提示:如果kube-dns在默认default命名空间下,不需要添加.kube-system后缀,具体如下
$ k exec -it redis -- nslookup kube-dns
3.3 两种方式对比¶
基于环境变量的服务发现需要在应用程序中显式设置环境变量,并且需要手动更新环境变量,因此管理和扩展服务可能会更加困难;相对来说基于DNS服务发现可以自动更新,并且服务发现与应用程序解耦,因此可以更容易地对服务进行管理和扩展。
基于DNS服务发现需要进行额外的网络查询,因此会有一些网络上的消耗;相对来说基于环境变量的服务发现是基于Spring Cloud的Eureka、Consul工具实现微服务的自我发现。
四、服务代理模式¶
Kubernetes集群中的每个节点会运行一个kube-proxy,kube-proxy 组件负责除 type为ExternalName 以外的服务,实现虚拟IP机制。
服务代理模式主要分为iptables 代理模式和IPVS 代理模式。
4.1 iptables 代理模式¶
在这种模式下,kube-proxy 监视 Kubernetes 控制平面,获知对 Service 和EndpointSlice对象的添加和删除操作。 对于每个 Service,kube-proxy 会添加 iptables 规则,这些规则捕获流向 Service 的 clusterIP 和 port 的流量, 并将这些流量重定向到 Service 后端集合中的其中之一。 对于每个端点,它会添加指向一个特定后端 Pod 的 iptables 规则。
默认情况下,iptables 模式下的 kube-proxy 会随机选择一个后端。如果要实现基于客户端IP的会话亲和性,可以将service.spec.sessionAffinity的值设置为ClusterIP(默认为None)。
4.2 ipvs 代理模式¶
在 ipvs 模式下,kube-proxy 监视 Kubernetes Service 和 EndpointSlice, 然后调用 netlink 接口创建 IPVS 规则, 并定期与 Kubernetes Service 和 EndpointSlice 同步 IPVS 规则。 该控制回路确保 IPVS 状态与期望的状态保持一致。 访问 Service 时,IPVS 会将流量导向到某一个后端 Pod。
IPVS 代理模式基于 netfilter 回调函数,类似于 iptables 模式, 但它使用哈希表作为底层数据结构,在内核空间中生效。 这意味着 IPVS 模式下的 kube-proxy 比 iptables 模式下的 kube-proxy 重定向流量的延迟更低,同步代理规则时性能也更好。 与其他代理模式相比,IPVS 模式还支持更高的网络流量吞吐量。
IPVS 为将流量均衡到后端 Pod 提供了更多选择:
- rr:轮询
- lc:最少连接(打开连接数最少)
- dh:目标地址哈希
- sh:源地址哈希
- sed:最短预期延迟
- nq:最少队列
注意:要在 IPVS 模式下运行 kube-proxy,必须在启动 kube-proxy 之前确保节点上的 IPVS 可用。当 kube-proxy 以 IPVS 代理模式启动时,它会验证 IPVS 内核模块是否可用。 如果未检测到 IPVS 内核模块,则 kube-proxy 会退回到 iptables 代理模式运行。
五、无头服务¶
有时不需要或不想要负载均衡,以及单独的 Service IP。 遇到这种情况,可以通过指定 Cluster IP(spec.clusterIP)的值为 "None" 来创建 Headless Service。
对于无头 Services 并不会分配 Cluster IP,kube-proxy 不会处理它们, 而且平台也不会为它们进行负载均衡和路由。无头 Services 使用的是Endpoint进行互相通信。
无头 Services 一般的格式为:
statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local
上面参数说明:
- serviceName:Headless Services的名字,创建statefulSet时必须指定Headless Services名字
- 0..N-1:Pod所在序号,从0到N-1
- statefulSetName:statefulSet的名字
- namespace:服务所在的命名空间
- .cluster.local:集群域(Cluster domain)