将运行在一组 Pods 上的应用程序公开为网络服务的抽象方法。
使用Kubernetes,您无需修改应用程序即可使用不熟悉的服务发现机制。 Kubernetes为Pods提供自己的IP地址和一组Pod的单个DNS名称,并且可以在它们之间进行负载平衡。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: my-port
protocol: TCP
port: 80
targetPort: 9376
服务最常见的是抽象化对 Kubernetes Pod 的访问,但是它们也可以抽象化其他种类的后端。 实例:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
apiVersion: v1
kind: Endpoints
metadata:
name: my-service
subsets:
- addresses:
- ip: 192.0.2.42
ports:
- port: 9376
访问没有 selector 的 Service,与有 selector 的 Service 的原理相同。
ExternalName Service 是 Service 的特例,它没有 selector,也没有使用 DNS 名称代替。
kube-proxy 会监视 Kubernetes 控制节点对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会安装 iptables 规则,从而捕获到达该 Service 的 clusterIP 和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某个上面。 对于每个 Endpoints 对象,它也会安装 iptables 规则,这个规则会选择一个 backend 组合。
默认的策略是,kube-proxy 在 iptables 模式下随机选择一个 backend。
使用 iptables 处理流量具有较低的系统开销,因为流量由 Linux netfilter 处理,而无需在用户空间和内核空间之间切换。 这种方法也可能更可靠。
如果 kube-proxy 在 iptables模式下运行,并且所选的第一个 Pod 没有响应,则连接失败。 使用 Pod readiness 探测器 验证后端 Pod 可以正常工作,以便 iptables 模式下的 kube-proxy 仅看到测试正常的后端。
在 ipvs 模式下,kube-proxy监视Kubernetes服务和端点,调用 netlink 接口相应地创建 IPVS 规则, 并定期将 IPVS 规则与 Kubernetes 服务和端点同步。
而相比于 iptables,IPVS 在内核中的实现其实也是基于 Netfilter 的 NAT 模式,所以在转发这一层上,理论上 IPVS 并没有显著的性能提升。但是,IPVS 并不需要在宿主机上为每个 Pod 设置 iptables 规则,而是把对这些“规则”的处理放到了内核态,从而极大地降低了维护这些规则的代价。“将重要操作放入内核态”是提高性能的重要手段。
IPVS 模块只负责上述的负载均衡和代理功能。而一个完整的 Service 流程正常工作所需要的包过滤、SNAT 等操作,还是要靠 iptables 来实现。只不过,这些辅助性的 iptables 规则数量有限,也不会随着 Pod 数量的增加而增加。
IPVS代理模式基于类似于 iptables 模式的 netfilter 挂钩函数,但是使用哈希表作为基础数据结构,并且在内核空间中工作。 这意味着,与 iptables 模式下的 kube-proxy 相比,IPVS 模式下的 kube-proxy 重定向通信的延迟要短,并且在同步代理规则时具有更好的性能。与其他代理模式相比,IPVS 模式还支持更高的网络流量吞吐量。
IPVS提供了更多选项来平衡后端Pod的流量。 这些是:
要在 IPVS 模式下运行 kube-proxy,必须在启动 kube-proxy 之前使 IPVS Linux 在节点上可用,否则将退回到以 iptables 代理模式运行。
在这些代理模型中,绑定到服务IP的流量:如果要确保每次都将来自特定客户端的连接传递到同一Pod,则可以通过将 service.spec.sessionAffinity 设置为 “ClientIP” (默认值是 “None”),来基于客户端的IP地址选择会话关联。
您还可以通过适当设置 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds 来设置最大会话停留时间。 (默认值为 10800 秒,即 3 小时)。
对于某些服务,您需要公开多个端口。 Kubernetes允许您在Service对象上配置多个端口定义。 为服务使用多个端口时,必须提供所有端口名称,以使它们无歧义。 例如:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
- name: https
protocol: TCP
port: 443
targetPort: 9377
通过设置 spec.clusterIP 字段来指定自己的集群 IP 地址。替换一个已经已存在的 DNS 条目,或遗留系统已经配置了一个固定的 IP 且很难重新配置。
Kubernetes 支持2种基本的服务发现模式 —— 环境变量和 DNS。
当 Pod 运行在 Node 上,kubelet 会为每个活跃的 Service 添加一组环境变量。它同时支持 Docker links兼容 变量(查看 makeLinkVariables)、简单的 {SVCNAME}_SERVICE_HOST 和 {SVCNAME}_SERVICE_PORT 变量,这里 Service 的名称需大写,横线被转换成下划线。
举个例子,一个名称为 "redis-master" 的 Service 暴露了 TCP 端口 6379,同时给它分配了 Cluster IP 地址 10.0.0.11,这个 Service 生成了如下环境变量:
REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11
/ # echo ${REDIS_MASTER_SERVICE_HOST}
10.0.0.11
注意??:
使用群集的DNS服务器(例如CoreDNS)监视 Kubernetes API 中的新服务,并为每个服务创建一组 DNS 记录。 如果在整个群集中都启用了 DNS,则所有 Pod 都应该能够通过其 DNS 名称自动解析服务。
例如,如果您在 Kubernetes 命名空间 "my-ns" 中有一个名为 "my-service" 的服务, 则控制平面和DNS服务共同为 "my-service.my-ns" 创建 DNS 记录。 "my-ns" 命名空间中的Pod应该能够通过 my-service 进行名称查找来找到它( "my-service.my-ns" 也可以工作)。
其他命名空间中的Pod必须将名称限定为 my-service.my-ns 。 这些名称将解析为为服务分配的群集IP。
Kubernetes 还支持命名端口的 DNS SRV(服务)记录。 如果 "my-service.my-ns" 服务具有名为 "http" 的端口,且协议设置为TCP, 则可以对 _http._tcp.my-service.my-ns 执行DNS SRV查询查询以发现该端口号, "http"以及IP地址。
Kubernetes DNS 服务器是唯一的一种能够访问 ExternalName 类型的 Service 的方式。 更多关于 ExternalName 信息可以查看DNS Pod 和 Service。
[root@m3 ~]# vim pod-dns.yaml
apiVersion: v1
kind: Service
metadata:
name: default-subdomain
spec:
selector:
name: busybox
clusterIP: None
ports:
- name: foo
port: 1234
targetPort: 1234
---
apiVersion: v1
kind: Pod
metadata:
name: busybox1
labels:
name: busybox
spec:
hostname: busybox-1
subdomain: default-subdomain
containers:
- image: busybox
command:
- sleep
- "3600"
name: busybox
[root@m3 ~]# kubectl apply -f pod-dns.yaml
有时不需要或不想要负载均衡,以及单独的 Service IP,可以通过指定 Cluster IP(spec.clusterIP)的值为 "None" 来创建 Headless Service。
您可以使用 headless Service 与其他服务发现机制进行接口,而不必与 Kubernetes 的实现捆绑在一起。
对这 headless Service 并不会分配 Cluster IP,kube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由。 DNS 如何实现自动配置,依赖于 Service 是否定义了 selector。
对定义了 selector 的 Headless Service,Endpoint 控制器在 API 中创建了 Endpoints 记录,并且修改 DNS 配置返回 A 记录(地址),通过这个地址直接到达 Service 的后端 Pod 上。
对没有定义 selector 的 Headless Service,Endpoint 控制器不会创建 Endpoints 记录。 然而 DNS 系统会查找和配置,无论是:
[root@m3 ~]# vim svc-Endpoints.yaml
apiVersion: v1
kind: Endpoints
metadata:
name: mysql-service
subsets:
- addresses:
- ip: 192.168.1.108
hostname: mysql-service
ports:
- port: 3306
---
apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
ports:
- port: 3306
如果将 type 字段设置为 NodePort,则 Kubernetes 控制平面将在 --service-node-port-range 标志指定的范围内分配端口(默认值:30000-32767)。 每个节点将那个端口代理到您的服务中。 您的服务在其 .spec.ports[*].nodePort 字段中要求分配的端口。
如果您想指定特定的IP代理端口,则可以将 kube-proxy 中的 --nodeport-addresses 标志设置为特定的IP块。该标志采用逗号分隔的IP块列表(例如10.0.0.0/8、192.0.2.0/25)来指定 kube-proxy 应该认为是此节点本地的IP地址范围。
如果需要特定的端口号,则可以在 nodePort 字段中指定一个值。注意可能发生的端口冲突。 您还必须使用有效的端口号,该端口号在配置用于NodePort的范围内。
Service 能够通过 <NodeIP>:spec.ports[*].nodePort 和 spec.clusterIp:spec.ports[*].port 而对外可见。
[root@m3 ~]# vim svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
type: NodePort
ports:
- name: http
port: 80
targetPort: 80
nodePort: 30008
selector:
run: my-nginx
[root@m3 ~]# kubectl apply -f svc-nodeport.yaml
service/my-nginx created
[root@s3 ~]# iptables-save | grep "my-nginx"
-A KUBE-EXTERNAL-SERVICES -p tcp -m comment --comment "default/my-nginx:http has no endpoints" -m addrtype --dst-type LOCAL -m tcp --dport 28080 -j REJECT --reject-with icmp-port-unreachable
-A KUBE-SERVICES -d 10.96.154.242/32 -p tcp -m comment --comment "default/my-nginx:http has no endpoints" -m tcp --dport 80 -j REJECT --reject-with icmp-port-unreachable
[root@s3 ~]# iptables-save | grep "KUBE-POSTROUTING"
:KUBE-POSTROUTING - [0:0]
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
可以看到,这条规则设置在 POSTROUTING 检查点,也就是说,它给即将离开这台主机的 IP 包,进行了一次 SNAT 操作,将这个 IP 包的源地址替换成了这台宿主机上的 CNI 网桥地址,或者宿主机本身的 IP 地址(如果 CNI 网桥不存在的话)。当然,这个 SNAT 操作只需要对 Servicespec.externalTrafficPolicy 转发出来的 IP 包进行(否则普通的 IP 包就被影响了)。而 iptables 做这个判断的依据,就是查看该 IP 包是否有一个“0x4000”的“标志”。你应该还记得,这个标志正是在 IP 包被执行 DNAT 操作之前被打上去的。
可以将 Service 的 spec.externalTrafficPolicy 字段设置为 local,这就保证了所有 Pod 通过 Service 收到请求之后,一定可以看到真正的、外部 client 的源地址。
这时候,一台宿主机上的 iptables 规则,会设置为只将 IP 包转发给运行在这台宿主机上的 Pod。
client
^ / \
/ / \
/ v X
node 1 node 2
^ |
| |
| v
endpoint
使用支持外部负载均衡器的云提供商的服务,设置 type 的值为 "LoadBalancer",将为 Service 提供负载均衡器。 负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过 Service 的 status.loadBalancer 字段被发布出去。 实例:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
clusterIP: 10.0.171.239
loadBalancerIP: 78.11.24.19
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: 146.148.47.155
来自外部负载均衡器的流量将直接打到 backend Pod 上,不过实际它们是如何工作的,这要依赖于云提供商。
在这些情况下,将根据用户设置的 loadBalancerIP 来创建负载均衡器。 某些云提供商允许设置 loadBalancerIP。如果没有设置 loadBalancerIP,将会给负载均衡器指派一个临时 IP。 如果设置了 loadBalancerIP,但云提供商并不支持这种特性,那么设置的 loadBalancerIP 值将会被忽略掉。
类型为 ExternalName 的服务将服务映射到 DNS 名称,而不是典型的选择器,例如 my-service 或者 cassandra。 您可以使用 spec.externalName 参数指定这些服务。
例如,以下 Service 定义将 prod 名称空间中的 my-service 服务映射到 my.database.example.com:
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
如果外部的 IP 路由到集群中一个或多个 Node 上,Kubernetes Service 会被暴露给这些 externalIPs。 通过外部 IP(作为目的 IP 地址)进入到集群,打到 Service 的端口上的流量,将会被路由到 Service 的 Endpoint 上。 externalIPs 不会被 Kubernetes 管理,它属于集群管理员的职责范畴。
根据 Service 的规定,externalIPs 可以同任意的 ServiceType 来一起指定。 在上面的例子中,my-service 可以在 “80.11.12.10:80”(externalIP:port) 上被客户端访问。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
externalIPs:
- 80.11.12.10
[root@m3 ~]# vim svc-test.yaml
apiVersion: v1
kind: Service
metadata:
name: hostnames
spec:
selector:
app: hostnames
ports:
- name: default
protocol: TCP
port: 80 #监听的端口号
targetPort: 9376
[root@m3 ~]# kubectl apply -f svc-test.yaml
service/hostnames created
[root@m3 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hostnames ClusterIP 10.96.132.245 <none> 80/TCP 4s
[root@m3 ~]# vim deploy-test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hostnames
spec:
selector:
matchLabels:
app: hostnames
replicas: 2
template:
metadata:
labels:
app: hostnames
spec:
containers:
- name: hostnames
image: k8s.gcr.io/serve_hostname
ports:
- containerPort: 9376
protocol: TCP
[root@m3 ~]# kubectl apply -f deploy-test.yaml
deployment.apps/hostnames created
[root@m3 ~]# kubectl get endpoints hostnames
NAME ENDPOINTS AGE
hostnames 10.100.130.119:9376,10.100.152.254:9376 42m
Service 提供的是 Round Robin 方式的负载均衡,我们称为:ClusterIP 模式的 Service。
[root@m3 ~]# kubectl get svc hostnames
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hostnames ClusterIP 10.96.132.245 <none> 80/TCP 43m
[root@m3 ~]# curl 10.96.132.245:80
hostnames-78f4d6f547-6tc6q
[root@m3 ~]# curl 10.96.132.245:80
hostnames-78f4d6f547-tp7fc
[root@s3 ~]# iptables-save | grep hostnames
-A KUBE-SERVICES ! -s 10.100.0.0/16 -d 10.96.132.245/32 -p tcp -m comment --comment "default/hostnames:default cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.96.132.245/32 -p tcp -m comment --comment "default/hostnames:default cluster IP" -m tcp --dport 80 -j KUBE-SVC-ODX2UBAZM7RQWOIU
[root@s3 ~]# iptables-save | grep KUBE-SVC-ODX2UBAZM7RQWOIU
:KUBE-SVC-ODX2UBAZM7RQWOIU - [0:0]
-A KUBE-SERVICES -d 10.96.132.245/32 -p tcp -m comment --comment "default/hostnames:default cluster IP" -m tcp --dport 80 -j KUBE-SVC-ODX2UBAZM7RQWOIU
-A KUBE-SVC-ODX2UBAZM7RQWOIU -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-JVSOCRZ6JY75X2W7
-A KUBE-SVC-ODX2UBAZM7RQWOIU -j KUBE-SEP-3WIYSPFWPJKZWGOR
这一组规则,实际上是一组随机模式(–mode random)的 iptables 链。
而随机转发的目的地,分别是KUBE-SEP-JVSOCRZ6JY75X2W7,KUBE-SEP-3WIYSPFWPJKZWGOR
[root@s3 ~]# iptables-save | grep KUBE-SEP-JVSOCRZ6JY75X2W7
:KUBE-SEP-JVSOCRZ6JY75X2W7 - [0:0]
-A KUBE-SEP-JVSOCRZ6JY75X2W7 -s 10.100.130.119/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-JVSOCRZ6JY75X2W7 -p tcp -m tcp -j DNAT --to-destination 10.100.130.119:9376
-A KUBE-SVC-ODX2UBAZM7RQWOIU -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-JVSOCRZ6JY75X2W7
需要注意的是,iptables 规则的匹配是从上到下逐条进行的,所以为了保证上述三条规则每条被选中的概率都相同,我们应该将它们的 probability 字段的值分别设置为 1/2和1。但在 DNAT 规则之前,iptables 对流入的 IP 包还设置了一个“标志”(–set-xmark)。
而 DNAT 规则的作用,就是在 PREROUTING 检查点之前,也就是在路由之前,将流入 IP 包的目的地址和端口,改成–to-destination 所指定的新的目的地址和端口。可以看到,这个目的地址和端口,正是被代理 Pod 的 IP 地址和端口。这样,访问 Service VIP 的 IP 包经过上述 iptables 处理之后,就已经变成了访问具体某一个后端 Pod 的 IP 包了。
原文:https://www.cnblogs.com/wangzhangtao/p/12168676.html