简答
Service为了给客户端提供固定的访问端点,因此在客户端和服务端提供了中间层叫Service,Service名称解析是强依赖于Dns解析组件的,部署完k8s还需要部署CoreDns。
简答
通过Service直接对外提供服务
ServiceType设置为NodePort、LoadBalancer或External Name方式,进行相关配置
配置Ingress结合Service对外提供服务
结合Ingress 控制器(如Nginx Ingress 控制器)
简述
Service 是 k8s 中为多个 pod 公开网络服务的抽象方法。在 k8s 中,每个 pod 都有自己的 ip 地址,而且 Service 可以为一组 pod 提供相同的 DNS(Domain Name System根据域名查出IP地址) ,使得多个 pod 之间可以相互通讯,k8s 可以在这些 pod 之间进行负载均衡。
k8s与传统物理概念对比
k8s概念 | 传统物理概念 | 说明 | 备注 |
---|---|---|---|
pod | 单台物理机实例 | Pod代表的是集群上处于运行状态的一组容器 | |
workload | 应用 | 内置workload工作负载包括:无状态、有状态、守护进程和批处理 | |
service(服务发现) | 负载均衡 | ||
负载均衡 | 网关配置 |
简述
一方面是因为 Pod 的 IP 不是固定的,另一方面则是因为一组 Pod 实例之间总会有负载均衡的需求。
问题举例
如果一组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”)提供功能, 那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用提供工作负载的后端部分?
内容介绍
Service 在 Kubernetes 中是一个 REST 对象,可以基于 POST 方式,请求 API server 创建新的实例。
apiVersion: v1
kind: Service
metadata:
name: hostnames
spec:
selector:
app: hostnames
ports:
- name: default
protocol: TCP
port: 80
targetPort: 9376
上述配置,创建一个名称为 “hostnames” 的 Service 对象,通过selector选择算符声明这个 Service 只代理携带了 “app=hostnames” 标签的 Pod,并且它会将请求代理到使用 TCP 端口 9376。
Kubernetes 为该服务分配一个 IP 地址(有时称为 “集群IP”),该 IP 地址由服务代理使用。
内容介绍
定义一个Deployment,声明创建了一个 ReplicaSet,负责启动三个 hostnames Pods
apiVersion: apps/v1
kind: Deployment
metadata:
name: hostnames
spec:
selector:
matchLabels:
app: hostnames
replicas: 3
template:
metadata:
labels:
app: hostnames
spec:
containers:
- name: hostnames
image: k8s.gcr.io/serve_hostname
ports:
- containerPort: 9376
protocol: TCP
操作步骤
kubectl apply -f ${路径}/hostnames-deployment.yaml
kubectl rollout status deployment/hostnames
输出类似于:
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment "hostnames" successfully rolled out
kubectl get deployments
输出类似于:
NAME READY UP-TO-DATE AVAILABLE AGE
hostnames 3/3 3 3 18s
kubectl get rs
输出类似于:
NAME DESIRED CURRENT READY AGE
hostnames-75675f5897 3 3 3 18s
注意 ReplicaSet 的名称始终被格式化为[Deployment名称]-[随机字符串]。 其中的随机字符串是使用 pod-template-hash 作为种子随机生成的。
kubectl get pods --show-labels
输出类似:
NAME READY STATUS RESTARTS AGE LABELS
hostnames-75675f5897-7ci7o 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453
hostnames-75675f5897-kzszj 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453
hostnames-75675f5897-qqcnn 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453
所创建的 ReplicaSet 确保总是存在三个 hostnames Pod。
注意事项
你必须在 Deployment 中指定适当的选择算符和 Pod 模板标签(在本例中为 app: hostnames)。 标签或者选择算符不要与其他控制器(包括其他 Deployment 和 StatefulSet)重叠。 Kubernetes 不会阻止你这样做,但是如果多个控制器具有重叠的选择算符, 它们可能会发生冲突执行难以预料的操作。
pod-template-hash标签
不要更改此标签。
Deployment 控制器将 pod-template-hash 标签添加到 Deployment 所创建或收留的每个 ReplicaSet 。
此标签可确保 Deployment 的子 ReplicaSets 不重叠。 标签是通过对 ReplicaSet 的 PodTemplate 进行哈希处理。 所生成的哈希值被添加到 ReplicaSet 选择算符、Pod 模板标签,并存在于在 ReplicaSet 可能拥有的任何现有 Pod 中。
Endpoints(端点)
被 selector 选中的 Pod,就称为 Service 的 Endpoints。
kubectl get endpoints hostnames
输出类似:
NAME ENDPOINTS AGE
hostnames 10.244.0.5:9376,10.244.0.6:9376,10.244.0.7:9376 1h
需要注意的是,只有处于 Running 状态,且 readinessProbe 检查通过的 Pod,才会出现在 Service 的 Endpoints 列表里。
VIP(虚拟IP)
通过 Service 的 VIP 地址访问到它所代理的 Pod
$ kubectl get svc hostnames
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hostnames ClusterIP 10.0.1.175 80/TCP 5s
$ curl 10.0.1.175:80
hostnames-0uton
$ curl 10.0.1.175:80
hostnames-yp2kp
$ curl 10.0.1.175:80
hostnames-bvc05
这个 VIP 地址 10.0.1.175 是 Kubernetes 自动为 Service 分配的。Service 提供的是 Round Robin 方式的负载均衡。对于这种方式,我们称为:ClusterIP 模式的 Service。
kube-proxy 组件 + iptables
在Kubernetes集群中每个Node运行一个kube-proxy进程,kube-proxy 负责为 Service 实现了一种 VIP(虚拟 IP)的形式。
创建Service -> Kubernetes -> kube-proxy -> Service的Informer -> 创建宿主机iptables规则
访问流程
VIP跳转
登录宿主机查询iptables规则
# svc相关的iptables都在nat表里面
iptables-save -t nat
输出类似:
-A KUBE-SERVICES -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames: cluster IP" -m tcp --dport 80 -j KUBE-SVC-NWV5X2332I4OT4T3
这条 iptables 规则的含义是:凡是目的地址是 10.0.1.175、目的端口是 80 的 IP 包,都应该跳转到另外一条名叫 KUBE-SVC-NWV5X2332I4OT4T3 的 iptables 链进行处理。
10.0.1.175 正是这个 Service 的 VIP。所以这一条规则,就为这个 Service 设置了一个固定的入口地址。并且,由于 10.0.1.175 只是一条 iptables 规则上的配置,并没有真正的网络设备,所以你 ping 这个地址,是不会有任何响应的。
iptables链追踪
跳转到的 KUBE-SVC-NWV5X2332I4OT4T3 规则,实际上是一组规则的集合,这一组规则,实际上是一组随机模式(–mode random)的 iptables 链。
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-WNBA2IHDGP2BOBGZ
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-X3P2623AGDH6CDF3
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -j KUBE-SEP-57KPRZ3JQVENLNBR
随机转发的目的地,分别是 KUBE-SEP-WNBA2IHDGP2BOBGZ、KUBE-SEP-X3P2623AGDH6CDF3 和 KUBE-SEP-57KPRZ3JQVENLNBR。
而这三条链指向的最终目的地,其实就是这个 Service 代理的三个 Pod。所以这一组规则,就是 Service 实现负载均衡的位置。
iptables规则匹配
iptables 规则的匹配是从上到下逐条进行的,所以为了保证上述三条规则每条被选中的概率都相同,我们应该将它们的 probability 字段的值分别设置为 1/3(0.333…)、1/2 和 1。
匹配原理:第一条规则被选中的概率就是 1/3;而如果第一条规则没有被选中,那么这时候就只剩下两条规则了,所以第二条规则的 probability 就必须设置为 1/2;类似地,最后一条就必须设置为 1。
-A KUBE-SEP-57KPRZ3JQVENLNBR -s 10.244.3.6/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-57KPRZ3JQVENLNBR -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.3.6:9376
-A KUBE-SEP-WNBA2IHDGP2BOBGZ -s 10.244.1.7/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-WNBA2IHDGP2BOBGZ -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.1.7:9376
-A KUBE-SEP-X3P2623AGDH6CDF3 -s 10.244.2.3/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-X3P2623AGDH6CDF3 -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.2.3:9376
这三条链,其实是三条 DNAT 规则。但在 DNAT 规则之前,iptables 对流入的 IP 包还设置了一个“标志”(–set-xmark)。
“标志”作用
宿主机器的iptables只对 NodePort Service转发出来的 IP 包进行SNAT(否则普通的 IP 包就被影响了)。而 iptables 做这个判断的依据,就是查看该 IP 包是否有一个“0x4000”的“标志”。
DNAT 规则作用
在 PREROUTING 检查点(路由)之前,将流入 IP 包的目的地址和端口,改成–to-destination 所指定的新的目的地址和端口。可以看到,这个目的地址和端口,正是被代理 Pod 的 IP 地址和端口。
总结
访问 Service VIP 的 IP 包经过上述 iptables 处理之后,就已经变成了访问具体某一个后端 Pod 的 IP 包了。不难理解,这些 Endpoints 对应的 iptables 规则,正是 kube-proxy 通过监听 Pod 的变化事件,在宿主机上生成并维护的。
基于 iptables 的 Service 实现,是制约 Kubernetes 项目承载更多量级的 Pod 的主要障碍。kube-proxy 通过 iptables 处理 Service 的过程,其实需要在宿主机上设置相当多的 iptables 规则。而且,kube-proxy 还需要在控制循环里不断地刷新这些规则来确保它们始终是正确的。
# ip addr
...
73:kube-ipvs0: mtu 1500 qdisc noop state DOWN qlen 1000
link/ether 1a:ce:f5:5f:c1:4d brd ff:ff:ff:ff:ff:ff
inet 10.0.1.175/32 scope global kube-ipvs0
valid_lft forever preferred_lft forever
# ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.102.128.4:80 rr
-> 10.244.3.6:9376 Masq 1 0 0
-> 10.244.1.7:9376 Masq 1 0 0
-> 10.244.2.3:9376 Masq 1 0 0
输出结果可见,这三个 IPVS 虚拟主机的 IP 地址和端口,对应的正是三个被代理的 Pod,任何发往 10.102.128.4:80 的请求,就都会被 IPVS 模块转发到某一个后端 Pod 上。
注意事项
IPVS 模块只负责上述的负载均衡和代理功能。而一个完整的 Service 流程正常工作所需要的包过滤、SNAT 等操作,还是要靠 iptables 来实现。只不过,这些辅助性的 iptables 规则数量有限,也不会随着 Pod 数量的增加而增加。
要在 IPVS 模式下运行 kube-proxy,必须在启动 kube-proxy 之前使 IPVS 在节点上可用。
当 kube-proxy 以 IPVS 代理模式启动时(设置–proxy-mode=ipvs 来开启这个功能),它将验证 IPVS 内核模块是否可用。 如果未检测到 IPVS 内核模块,则 kube-proxy 将退回到以 iptables 代理模式运行。
总结
IPVS代理模式基于类似于 iptables 模式的 netfilter 挂钩函数的 NAT 模式, 但是使用哈希表作为基础数据结构,并且在内核空间中工作。 这意味着,与 iptables 模式下的 kube-proxy 相比,IPVS 模式下的 kube-proxy 重定向通信的延迟要短,并且在同步代理规则时具有更好的性能。 与其他代理模式相比,IPVS 模式还支持更高的网络流量吞吐量。
“将重要操作放入内核态”是提高性能的重要手段。
前言
在 Kubernetes 里,/etc/hosts 文件是单独挂载的,这也是为什么 kubelet 能够对 hostname 进行修改并且 Pod 重建后依然有效的原因。这跟 Docker 的 Init 层是一个原理。
建议使用附加组件 为 Kubernetes 集群设置 DNS 服务
支持集群的 DNS 服务器(例如 CoreDNS)监视 Kubernetes API 中的新服务,并为每个服务创建一组 DNS 记录(从域名解析 IP 的记录)。 如果在整个集群中都启用了 DNS,则所有 Pod 都应该能够通过其 DNS 名称自动解析服务。
ClusterIP Service DNS
对于 ClusterIP 模式的 Service 来说(比如我们上面的例子),它的 DNS 记录的格式是:…svc.cluster.local。当你访问这条 DNS 记录的时候,它解析到的就是该 Service 的 VIP 地址。
ClusterIP Service 代理的Pod DNS
它代理的 Pod 被自动分配的 DNS 记录的格式是:…pod.cluster.local(podName.namespace.pod.cluster.local)。这条记录指向 Pod 的 IP 地址。
内容介绍
Headless Service 为你提供的,则是一个 Pod 的稳定的 DNS 名字,并且,这个名字是可以通过 Pod 名字和 Service 名字拼接出来的。
Headless Service DNS
对于指定了 clusterIP=None 的 Headless Service 来说,它的 DNS 记录的格式也是:…svc.cluster.local。但是,当你访问这条 DNS 记录的时候,它返回的是所有被代理的 Pod 的 IP 地址的集合。当然,如果你的客户端没办法解析这个集合的话,它可能会只会拿到第一个 Pod 的 IP 地址。
Headless Service 代理的Pod DNS
它代理的 Pod 被自动分配的 DNS 记录的格式是:…svc.cluster.local(如:myPodName.myServiceName.myNameSpace.svc.cluster.local)。这条记录也指向 Pod 的 IP 地址。
样例
如果你为 Pod 指定了 Headless Service,并且 Pod 本身声明了 hostname 和 subdomain 字段,那么这时候 Pod 的 DNS 记录就会变成:【pod的hostname】…svc.cluster.local(hostname.subdomain.myNameSpace.svc.cluster.local),比如:
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
在上面这个 Service 和 Pod 被创建之后,你就可以通过 busybox-1.default-subdomain.default.svc.cluster.local 解析到这个 Pod 的 IP 地址了。
SNAT
SNAT(Source Network Address Translation,源地址转换)是Linux防护墙的一种地址转换操作,也是iptables命令中的一种数据包控制类型,其作用是根据指定条件修改数据包的源IP地址。
DNAT
DNAT(Destination Network Address Translation,目标地址转换)是Linux防火墙的另一种地址转换操作,同样也是iptables命令中的一种数据包控制类型,其作用是根据指定条件修改数据包的目标IP地址和目标端口。
服务(Service)
深入剖析 Kubernetes
服务发现与负载均衡