Eli's Blog

1. Service 概念

通过创建 Service, 可以为一组功能相同的容器提供一个统一的入口,并将请求均衡负载发送到后端的各个容器上

  • 通过 Label Selector ,实现 SVC 与容器组关联
  • 负载均衡算法默认使用 RR (Round-Robin 轮询调度)
  • 亲和性:通过service.spec.sessionAffinity = ClientIP 来启用 SessionAffinity 策略
  • 只提供 4 层负载均衡能力 (基于 IP:PORT 转发),没有7层功能 (通过主机名或域名负载均衡)

svc

2. Service 类型

kube-proxy

2.1 ClusterIp

默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟IP。在每个 node 节点使用 iptables,将发向 ClusterIp 的流量转发至 kube-proxy,然后 kube-proxy 自己内部实现负载均衡算法,查询到该 Service 下对应 Pod 的地址和端口,进而将数据转发给对应的 Pod

clusterip

工作原理:

  • apiserver: 用户通过 kubectl 向 apiserver 下发创建 service 的命令,apiserver 接收到请求后,将数据存储到 etcd 中
  • kube-proxy: 该进程负载监控 Service 和 Pod 的变化 (etcd),并将变化信息更新到本地 iptables中
  • iptables: 使用NAT 等技术,将 VIP 的流量转发至 Endpoint 中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 5
selector: # 绑定Pod
matchLabels:
app: myapp
release: stable
template:
metadata:
labels:
app: myapp
release: stable
env: test
spec:
containers:
- name: myapp
image: nginx:1.21.3
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: myapp-svc
spec:
type: ClusterIP
selector: # 绑定Pod
app: myapp
release: stable
ports:
- name: http
port: 80
targetPort: 80
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 62d
myapp-svc ClusterIP 10.96.92.170 <none> 80/TCP 154m

# 通过 ClusterIp 访问
$ curl 10.96.92.170

# ipvs转发
$ ipvsadm -Ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.96.92.170:80 rr
-> 10.244.0.4:80 Masq 1 0 0
-> 10.244.0.5:80 Masq 1 0 0
-> 10.244.1.5:80 Masq 1 0 1
-> 10.244.1.6:80 Masq 1 0 0
-> 10.244.1.7:80 Masq 1 0 1

2.2 NodePort

在每个节点上暴露一个相同的端口,外部可以通过 IP:PORT 方式访问集群服务

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: myapp-svc-nodeport
spec:
type: NodePort
selector:
app: myapp
release: stable
ports:
- name: http
port: 80
targetPort: 80
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ kubectl get svc
myapp-svc-nodeport NodePort 10.96.112.231 <none> 80:35473/TCP 5s

$ curl 10.96.112.231

$ curl 192.168.80.240:35473

# 访问流程
$ ipvsadm -Ln
TCP 192.168.80.240:35473 rr
-> 10.244.0.4:80 Masq 1 0 0
-> 10.244.0.5:80 Masq 1 0 0
-> 10.244.1.5:80 Masq 1 0 0
-> 10.244.1.6:80 Masq 1 0 0
-> 10.244.1.7:80 Masq 1 0 0
TCP 10.96.112.231:80 rr
-> 10.244.0.4:80 Masq 1 0 0
-> 10.244.0.5:80 Masq 1 0 0
-> 10.244.1.5:80 Masq 1 0 0
-> 10.244.1.6:80 Masq 1 0 0
-> 10.244.1.7:80 Masq 1 0 0

2.3 LoadBalancer

在 NodePort 基础上,借助 Cloud Provider 创建一个外部负载均衡器,并将请求转发到 <NodeIp>:<NodePort>

2.4 ExternalName

通过返回具有该名称的 CNAME 记录,将集群外部的服务引入集群内部使用

1
2
3
4
5
6
7
apiVersion: v1
kind: Service
metadata:
name: external-name-svc
spec:
type: ExternalName
externalName: hub.docker.com
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
external-name-svc ExternalName <none> hub.docker.com <none> 7s

# 获取 DNS 地址信息
$ kubectl get pod -n kube-system -o wide | grep dns
coredns-7c6c659fcf-fk7ql 1/1 Running 0 29m 10.244.2.2 k8s-master <none> <none>

# 解析域名 i 记录
$ dig -t A external-name-svc.default.svc.cluster.local. @10.244.2.2
;; ANSWER SECTION:
external-name-svc.default.svc.cluster.local. 5 IN CNAME hub.docker.com.
hub.docker.com. 5 IN CNAME elb-default.us-east-1.aws.dckr.io.
elb-default.us-east-1.aws.dckr.io. 5 IN CNAME us-east-1-elbdefau-1nlhaqqbnj2z8-140214243.us-east-1.elb.amazonaws.com.
us-east-1-elbdefau-1nlhaqqbnj2z8-140214243.us-east-1.elb.amazonaws.com. 5 IN A 3.211.28.26
us-east-1-elbdefau-1nlhaqqbnj2z8-140214243.us-east-1.elb.amazonaws.com. 5 IN A 3.217.79.149
us-east-1-elbdefau-1nlhaqqbnj2z8-140214243.us-east-1.elb.amazonaws.com. 5 IN A 54.147.41.176

;; Query time: 283 msec
;; SERVER: 10.244.2.2#53(10.244.2.2)
;; WHEN: Mon Nov 08 20:21:05 CST 2021
;; MSG SIZE rcvd: 591

3. Service 代理模式

kube-proxy 进程:负责为 Service 实现一种 VIP(虚拟IP)的形式,代理模式有如下三种:

  • userspace

  • iptables

  • ipvs

  • Ingress: 支持 7 层服务

为什么不使用 round-robin DNS? dns 存在缓存,当有Pod节点故障时,无法自动处理

3.1 userspace

流量路径:

  1. 经过防火墙 iptables
  2. 经过 kube-proxy 转发
  3. 到达 pod

缺点:kube-proxy 压力很大

userspace

3.2 iptables

流量路径:

  1. 经过 iptables 直接转发
  2. 达到 pod

userspace

3.3 ipvs

流量路径:

  1. 经过 ipvs 直接转发
  2. 达到 pod

ipvs

kube-proxy 监控 Service 和 Endpoints,调用 netlink 接口以相应地创建 ipvs 规则,并定期与 Service 和 Endpoints 对象同步 ipvs 规则,以确保 ipvs 状态与期望一致。访问服务时,流量将被重定向到其中一个后端 Pod

与 iptables 类似,ipvs 于 netfilter 的 hook 功能,但使用hash表作为底层数据结构并在内核空间中工作。这意味着 ipvs 可以快速地重定向流量,并且在同步代理规则时具有更好的性能。

ipvs 负载均衡算法:

  • rr: round-robin, 轮训调度
  • lc: least connection, 最小连接数
  • dh: destination hashing, 目标hash
  • sh: source hashing, 源hash
  • sed: shortest expected delay, 最短期望延迟
  • nq: never queue, 不排队调度

4. Headless Service

当不需要负载均衡和独立 Service IP 时,可以指定 spec.clusterIP 为 None来创建 Headless Service。这类 Service 并不会分配 Cluster IP,kube-proxy 也不会处理它们,而且平台也不会其对进行负载均衡和路由。虽然没有svc,但依旧可以通过访问域名,路由到不同Pod上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: myapp-headless-svc
spec:
type: ClusterIP
selector:
app: myapp
release: stable
clusterIP: None
ports:
- name: http
port: 80
targetPort: 80
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
myapp-headless-svc ClusterIP None <none> 80/TCP 7s

# 解析域名 i 记录
$ dig -t A myapp-headless-svc.default.svc.cluster.local. @10.244.2.2
;; ANSWER SECTION:
myapp-headless-svc.default.svc.cluster.local. 5 IN A 10.244.1.6
myapp-headless-svc.default.svc.cluster.local. 5 IN A 10.244.0.4
myapp-headless-svc.default.svc.cluster.local. 5 IN A 10.244.1.5
myapp-headless-svc.default.svc.cluster.local. 5 IN A 10.244.0.5
myapp-headless-svc.default.svc.cluster.local. 5 IN A 10.244.1.7

;; Query time: 0 msec
;; SERVER: 10.244.2.2#53(10.244.2.2)
;; WHEN: Mon Nov 08 20:26:27 CST 2021
;; MSG SIZE rcvd: 385

# busybox 测试
$ kubectl run -it --rm dns-test --image=busybox:1.28.4 -- /bin/sh
# nslookup myapp-headless-svc