service是kubernetes中最核心的资源对象之一,service和pod之间是通过Label串起来,相同的Service的pod的Label是一样的. 同一个service下的所有pod是通过kube-proxy实现负载均衡.而每个service都会分配一个全局唯一的虚拟ip,也就cluster ip.
在该service整个生命周期内,cluster ip保持不变,而在kubernetes中还有一个dns服务,它会把service的name解析为cluster ip。
Service可以看作是一组提供相同服务的Pod对外的访问接口。借助Service,应用可以方便地实现服务发现和负载均衡。(可以通过Ingress实现)
service默认只支持4层负载均衡能力,没有7层功能。(可以通过Ingress实现)
ClusterIP:默认值,k8s系统给service自动分配的虚拟IP,只能在集群内部访问。
NodePort:将Service通过指定的Node上的端口暴露给外部,访问任意NodeIP:nodePort都将路由到ClusterIP。
LoadBalancer:在 NodePort 的基础上,借助 cloud provider 创建一个外部的负载均衡器,并将请求转发到:NodePort,此模式只能在云服务器上使用。
ExternalName:将服务通过 DNS CNAME 记录方式转发到指定的域名(通过 spec.externlName 设定)。
Service 是由 kube-proxy 组件,加上 iptables 来共同实现的.
kube-proxy 通过 iptables 处理 Service 的过程,需要在宿主机上设置相当多的 iptables 规则,如果宿主机有大量的Pod,不断刷新iptables规则,会消耗大量的CPU资源。
IPVS模式的service,可以使K8s集群支持更多量级的Pod。
IPVS模式下,kube-proxy会在service创建后,在宿主机上添加一个虚拟网卡:kube-ipvs0,并分配service IP。
VIP和Service代理
在Kubernetes集群中,每个Node运行一个kube-proxy进程。kube-proxy负责为Service实现了一种VIP(虚拟IP)的形式,而不是ExternalName的形式。在Kubernetes v1.0版本,代理完全在userspace。在Kubernetes v1.1版本,新增了iptables代理,但并不是默认的运行模式。从Kubernetes v1.2起,默认就是iptables代理。
在Kubernetes v1.0版本,Service是“4层”(TCP/UDP over IP)概念。在Kubernetes v1.1版本,新增了 Ingress API(beta版),用来表示“7层”(HTTP)服务。
没有selector的Service
Service抽象了该如何访问Kubernetes Pod,但也能抽象其他类型的backend,例如:
希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。
希望服务指向另一个Namespace中或其他集群中的服务。
正在将工作负载转移到Kubernetes集群,和运行在Kubernetes集群之外的backend。
在任何这些场景中,都能够定义没有selector 的 Service :
第一种: 是Userspace方式
这种模式,kube-proxy会监视Kubernetes master对service对象和Endpoints对象的添加和移除。对每个Service,它会在本地Node上打开一个端口(随机选择)。任何连接到“代理端口”的请求,都会被代理到Service的backend Pods中的某一个上面。使用哪个backend Pod,是基于Service的SessionAffinity来确定的。最后,它安装iptables规则,捕获到达该Service的clusterIP(是虚拟IP)和Port的请求,并重定向到代理端口,代理端口再代理请求到backend Pod。
网络返回的结果是,任何到达Service的IP:Port的请求,都会被代理到一个合适的backend,不需要客户端知道关于Kubernetes,service或pod的任何信息。
默认的策略是,通过round-robin算法来选择backend Pod。实现基于客户端IP的会话亲和性,可以通过设置service.spec.sessionAffinity的值为“ClientIP”(默认值为“None”);
如下图描述,
要访问Server Pod时,它先将请求发给本机内核空间中的service规则,由它再将请求,
转给监听在指定套接字上的kube-proxy,kube-proxy处理完请求,并分发请求到指定Server Pod后,再将请求
递交给内核空间中的service,由service将请求转给指定的Server Pod。
由于其需要来回在用户空间和内核空间交互通信,因此效率很差,接着就有了第二种方式.
第二种: iptables模型
此工作方式是直接由内核中的iptables规则,接受Client Pod的请求,并处理完成后,直接转发给指定ServerPod.
这种模式,kube-proxy会监视Kubernetes master对象和Endpoinnts对象的添加和移除。对每个Service,它会安装iptables规则,从而捕获到达该Service的clusterIP(虚拟IP)和端口的请求,进而将请求重定向到Service的一组backend中某个上面。对于每个Endpoints对象,它也会安装iptables规则,这个规则会选择一个backend Pod。
默认的策略是,随机选择一个backend。实现基于客户端IP的会话亲和性,可以将service.spec.sessionAffinity的值设置为“ClientIP”(默认值为“None”)
和userspace代理类似,网络返回的结果是,任何到达Service的IP:Port的请求,都会被代理到一个合适的backend,不需要客户端知道关于Kubernetes,service或Pod的任何信息。这应该比userspace代理更快,更可靠。然而,不想userspace代理,如果始出选择的Pod没有响应,iptables代理不能自动地重试另一个Pod,所以它需要依赖readiness probes;
第三种: ipvs模型
它是直接有内核中的ipvs规则来接受Client Pod请求,并处理该请求,再有内核封包后,直接发给指定的Server Pod。
这种模式,kube-proxy会监视Kubernetes service对象和Endpoints,调用netlink接口以相应地创建ipvs规则并定期与Kubernetes service对象和Endpoints对象同步ipvs规则,以确保ipvs状态与期望一致。访问服务时,流量将被重定向到其中一个后端Pod。
与iptables类似,ipvs基于netfilter的hook功能,但使用哈希表作为底层数据结构并在内核空间中工作。这意味着ipvs可以更快地重定向流量,并且在同步代理规则时具有更好的性能。此外,ipvs为负载均衡算法提供了更多的选项,例如:
rr:轮询调度
lc:最小连接数
dh:目标哈希
sh:源哈希
sed:最短期望延迟
nq: 不排队调度
注意: ipvs模式假定在运行kube-proxy之前在节点上都已经安装了IPVS内核模块。当kube-proxy以ipvs代理模式启动时,kube-proxy将验证节点上是否安装了IPVS模块,如果未安装,则kube-proxy将回退到iptables代理模式。
以上不论哪种,kube-proxy都通过watch的方式监控着kube-APIServer写入etcd中关于Pod的最新状态信息,
它一旦检查到一个Pod资源被删除了 或 新建,它将立即将这些变化,反应在iptables 或 ipvs规则中,以便iptables和ipvs在调度Clinet Pod请求到Server Pod时,不会出现Server Pod不存在的情况。
自k8s1.1以后,service默认使用ipvs规则,若ipvs没有被激活,则降级使用iptables规则. 但在1.1以前,service
使用的模式默认为userspace.
Service 是由 kube-proxy 组件,加上 iptables 来共同实现的.
kube-proxy 通过 iptables 处理 Service 的过程,需要在宿主机上设置相当多的 iptables 规则,如果宿主机有大量的Pod,不断刷新iptables规则,会消耗大量的CPU资源。
1. ExternalName:
用于将集群外部的服务引入到集群内部,在集群内部可直接访问来获取服务。它的值必须是 FQDN, 此FQDN为集群内部的FQDN, 即: ServiceName.Namespace.Domain.LTD.然后CoreDNS接受到该FQDN后,能解析出一个CNAME记录, 该别名记录为真正互联网上的域名.如: www.test.com, 接着CoreDNS在向互联网上的根域DNS解析该域名,获得其真实互联网IP.
2. ClusterIP:
用于为集群内Pod访问时,提供的固定访问地址,默认是自动分配地址,可使用ClusterIP关键字指定固定IP.
3. NodePort:
用于为集群外部访问Service后面Pod提供访问接入端口.
这种类型的service工作流程为:
Client----->NodeIP:NodePort----->ClusterIP:ServicePort----->PodIP:ContainerPort
4. LoadBalancer
用于当K8s运行在一个云环境内时,若该云环境支持LBaaS,则此类型可自动触发创建一个软件负载均衡器 ,用于对Service做负载均衡调度.
因为外部所有Client都访问一个NodeIP,该节点的压力将会很大, 而LoadBalancer则可解决这个问题。而且它还直接动态监测后端Node是否被移除或新增了,然后动态更新调度的节点数。
Service 是由 kube-proxy 组件,加上 iptables 来共同实现的.
kube-proxy 通过 iptables 处理 Service 的过程,需要在宿主机上设置相当多的 iptables 规则,如果宿主机有大量的Pod,不断刷新iptables规则,会消耗大量的CPU资源。
IPVS模式的service,可以使K8s集群支持更多量级的Pod。
集群内部访问pod
vim deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: myapp:v1
ports:
- containerPort: 80
kubectl apply -f deployment.yaml
kubectl get pod -o wide
kubectl get svc
kubectl describe svc myservice
运行一个容器,访问后端的两个pod:
kubectl run test -it --image=busyboxplus
curl 10.244.3.12
curl 10.244.1.15
可以访问,这是因为在创建集群时就创建了一个覆盖型的网络
那么如果我们想要外部主机来访问pod呢?此时我们就可以通过创建service使外部主机访问
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
ports:
- name: http
port: 80
targetPort: 80
selector:
app: nginx
type: ClusterIP
目前该web-service还没有后端 Endpoints
创建两个后端的pod
标签必须和web-service的保持一致 app: nginx
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: reg.westos.org/k8s/myapp:v1
ports:
- containerPort: 80
[kubeadm@server1 manifest]$ vim de.yaml
[kubeadm@server1 manifest]$ kubectl apply -f de.yaml
deployment.apps/deployment-nginx created
[kubeadm@server1 manifest]$ kubectl get pod
NAME READY STATUS RESTARTS AGE
deployment-nginx-84f7d65dcf-5bf9q 1/1 Running 0 6s
deployment-nginx-84f7d65dcf-qjknh 1/1 Running 0 6s
test 1/1 Running 1 3m42s
[kubeadm@server1 manifest]$
此时,我们的web-service有了两个后端
pod间内部通过访问ClusterIP分配的虚拟VIP负载到了后端的两个pod容器
ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType
Headless Service不需要分配一个VIP,而是直接以DNS记录的方式解析出被代理Pod的IP地址。
域名格式: ( s e r v i c e n a m e ) . (servicename). (servicename).(namespace).svc.cluster.local
无头服务,不分配ip地址,使用域名
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
spec:
ports:
- name: http
port: 80
targetPort: 80
selector:
app: nginx
clusterIP: None
以DNS记录的方式解析出被代理Pod的IP地址:
使用域名访问:
kubectl apply -f headless.yaml
kubectl get svc
kubectl describe svc nginx-svc
kubectl attch test -it
nslookup nginx-svc
以DNS记录的方式解析出被代理Pod的IP地址。
通过这种方式实现访问,即使在后端pod滚动更新之后依然可以通过DNS进行解 析
Kubernetes 提供了一个 DNS 插件 Service。
kubectl get services kube-dns --namespace=kube-system
kubectl delete pod --all
pod "deployment-nginx-58f549b56d-4qswl" deleted
pod "deployment-nginx-58f549b56d-7sz7c" deleted
pod "deployment-nginx-58f549b56d-gwswr" deleted
$ dig -t A nginx-svc.default.svc.cluster.local @10.96.0.10
...
;; QUESTION SECTION:
;nginx-svc.default.svc.cluster.local. IN A
;; ANSWER SECTION:
nginx-svc.default.svc.cluster.local. 30 IN A 10.244.2.111
nginx-svc.default.svc.cluster.local. 30 IN A 10.244.1.120
nginx-svc.default.svc.cluster.local. 30 IN A 10.244.0.61
iptables -t nat -nL | grep 10.224.2.26
我们可以看到这些策略,是在service创建时增加的
当pod创建的越来越多时,iptables的刷新频率就会越来越大,刷新策略会对cpu造成一定的压力,我们可以使用lvs来实现pod的负载均衡,直接使用linux内核
在所有节点安装ipvsadm(master+node):
当前没有任何策略
yum install -y ipvsadm
ipvsadm -l #没有任何策略
kubectl -n kube-system get cm
kubectl -n kube-system edit cm kube-proxy
删除原来的 kube-system pod,更新kube-proxy pod:
除了手动删除更新,也可以使用命令:
kubectl get pod -n kube-system |grep kube-proxy | awk '{system("kubectl delete pod "$1" -n kube-system")}'
ipvsadm -ln
内部访问web-service的虚拟VIP实现了对pod的负载
在每个node节点上发现kube-ipvs0的接口,只要创建一个service,就会加一个,10.100.84.9就是server2上创建的service的VIP地址
且三个节点相同
vxlan模式跨主机通信过程:
通过ip addr命令查看到容器在物理机上的veth卡。
因为这里使用了cni接口标准,这个veth卡会桥接在cni0的网桥上。这个我们可以通过brctl show进行查看
数据包走到了cni0的网桥后,根据已知的目标ip,10.244.2.6,可以查找路由表,根据路由和掩码,选择对应的iface,也就是flannel.1。且下一跳,也就是10.244.2.0。
进入到flannel.1如何知道应该发向哪个物理机呢。这个时候,其实是通过arp来获取。可以通过arp命令查看到对应的mac地址。
这个mac地址在vxlan中,可以通过bridge fdb show来进行查看。可以看到,如果是发向ae:39:2b:5e:b1:d0 的地址,则目标机器在172.25.254.3机器上。则数据就会流转到172.25.254.3上了。经过vxlan封包后的数据包就会经过eth0设备发向到172.25.254.3上。
在172.25.254.3上,首先经过了iptables链,而后在flannel.1的Iface上则接收到该数据包。这里我们可以看到,flannel.1的mac地址就是ae:39:2b:5e:b1:d0。
到达flannel.1后,根据路由表,查看10.244.2.6的路由应送到server3的cni0的网桥上。
这里我们查看cni0的网桥信息。
到达网桥后,就可以根据地址将数据送到10.244.2.26的对应的veth上,进而在容器中收到对应的数据包了。
以上就是两个处于不同物理机上的容器间发送数据包的流程。相比较来说,从容器到物理机的ping就简单多了。这个流程就是veth->cni0->eth0->对端物理机ip。这里就不详细叙述了。
同一台物理机上不同容器的ping只需要经过cni0的网桥就可以了。
[kubeadm@server1 manifest]$ kubectl edit svc web-service
service/web-service edited
[kubeadm@server1 manifest]$
从外部访问的第三种方式叫做ExternalName。
ExternalName的模式,从外部访问的方式,用于让pod去访问集群外部的资源,它本身没有绑定任何的资源。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: ExternalName
externalName: www.baidu.com
用于让pod去访问集群外部的资源
并没有分配ip,只是通过CNAME的方式,添加了一条记录。
pod内部可以正常访问
当更改外部资源为www.westos.org时,可以检测到,但集群内部不做任何的修改。
service允许为其分配一个公有IP。
vim ex-service.yaml
apiVersion: v1
kind: Service
metadata:
name: ex-service
spec:
selector:
app: nginx
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
externalIPs:
- 172.25.0.100
$ kubectl get svc ex-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ex-service ClusterIP 10.111.60.13 172.25.0.100 80/TCP 6s
访问公有ip可以访问到集群内部的pod
从外部访问 Service 的第二种方式,适用于公有云上的 Kubernetes 服务。这时候,你可以指定一个 LoadBalancer 类型的 Service
vim lb-service.yaml
apiVersion: v1
kind: Service
metadata:
name: lb-nginx
spec:
ports:
- name: http
port: 80
targetPort: 80
selector:
app: nginx
type: LoadBalancer
在service提交后,Kubernetes就会调用 CloudProvider 在公有云上为你创建一个负载均衡服务,并且把被代理的 Pod 的 IP地址配置给负载均衡服务做后端。