Kubernetes:深入了解Service
2021-02-05 16:17
标签:track image uber meta comment 延时 done automake label 用这个配置文件创建一个 发现有一条默认的 第一行为 回到 下面查一下具有web标签的 目前是有三个 上文提到过,说白了就是 可以看到写了标签的位置有三处,作用如下。 流量转发用的是 应用部署到 上面的 查看访问结果 没问题,这个就是以 没问题,代理模式一共是两种,一种是 现在有一个问题,暴露端口的范围只是 如果真的用 用户请求→代理服务器→ 其实流量转发是由每个 看一下之前创建的 现在在一个 可以看到有两条规则,主要是第二条,意思是有请求要访问 三个 可以看到一个 大概就是这样,不难理解,每个 再来了解一下 其二这些规则都是从上到下逐条匹配的,如果你 相信很多人都用过 使用了 这个设备的 这种规则啥子意思应该都能看懂吧, 对比一下, 一般不会去动他,默认的就够用了 Kubernetes:深入了解Service 标签:track image uber meta comment 延时 done automake label 原文地址:https://www.cnblogs.com/opesn/p/13124284.htmlKubernetes:深入了解Service
说白了就是与外界连通,大概分为5个部分。
·Service定义。
·Pod与Service的关系
·Service类型。
·Service代理模式。
·DNS
部署一个应用,例如写一个deployment,里面有一个字段replicas来指定副本数,一个应用会用很多的副本分布在各个节点上,如果某一个pod由于某种原因崩溃掉了,replicas会帮你再启一个pod,保证副本数量,新启的这个pod肯定是和原来一样的,重启之后容器的IP就发生变化了,但是要怎么动态感知容器IP呢?这就引入了Service的概念,Service可以动态感知全部Pod的IP,并且能提供对外访问入口。
Service的设计目的主要是防止Pod失联,他能获取到所有Pod的IP,所以你不用管容器的IP是什么了,全部交给Service去做就可以了。可以定义Pod的访问策略,也提供了负载均衡的功能,帮你将请求转发到后端的一个pod中,可以支持三种模式,分别是ClusterIP、NodePort、LoadBalancer,Service的底层实现主要是由iptables和IPVS两种网络模式,这两种模式决定了怎么去转发流量。
Service 定义
[root@k8s01 yml]# nginx_service.yaml
apiVersion: v1 ## API 版本
kind: Service ## 绑定的资源对象为Service
metadata: ## Service源数据,指定service名称和工作空间
name: nginx-service ## Service名称
namespace: default ## Service工作空间
spec:
clusterIP: 10.0.0.213 ## 默认使用cluster IP
ports: ## port 来定义Service的入口和容器的入口
- name: http ##指定端口名称
port: 80 ## Service 入口
protocol: TCP ## 使用协议
targetPort: 80 ## pod端口,也就是容器端口
selector: ##标签选择器,通过标签来匹配要关联的Pod
app: web ##关联到标签为app:web 的pod
Service[root@k8s01 yml]# kubectl create -f nginx_service.yaml
service/nginx-service created
[root@k8s01 yml]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.0.0.1 Service,他的作用是在集群内部访问apiserver的,通过kubernetes这个DNS名称。Service要想动态感知后端IP的变化,他会用到一个名为endpoints的控制器,也就是每个Service会对应一个endpoints控制器,endpoints来帮他关联后端的pod,Service只是关联某一组Pod,动态感知的具体实现时交给endpoints控制器来完成的,下面看一下。[root@k8s01 yml]# kubectl create -f deployment.yaml
deployment.apps/nginx-deployment created
[root@k8s01 yml]# kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 192.168.10.91:6443,192.168.10.94:6443 3d22h
nginx-service 10.244.2.22:80 16s
apiserver的IP及端口,默认就会创建这一条Service,第二行就是我们刚刚创建的那个。Service,看上文类型为ClusterIP,也是默认的Service类型,分配的IP是我们刚制定的213,如不指定会有一个随机的IP,那个deployment之前删掉了,现在加回来。[root@k8s01 yml]# kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 192.168.10.91:6443,192.168.10.94:6443 3d22h
nginx-service 10.244.0.35:80,10.244.1.56:80,10.244.2.23:80 5m53s
[root@k8s01 yml]#
endpoints这里决定了将数据包转发到哪里,关联到这个Service是通过标签的,也就是上文的 selector: ##标签选择器,通过标签来匹配要关联的Pod
app: web ##关联到标签为app:web 的pod
pod,通过下面的命令。[root@k8s01 yml]# kubectl get pod -l app=web -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-79cfcb457-hrzm5 1/1 Running 0 3m13s 10.244.2.23 k8s05 pod,可以看到容器IP和运行节点,容器IP和上面的对比一下,也就是说如果现在有个容器崩了,他帮你又拉起一个容器,新容器新的IP地址,由ENDPOINTS控制器帮你感知到新启动的容器IP和端口,并加入到相对应的Service中,这块对用户来说完全是透明的,查看这个Service的详细信息。[root@k8s01 yml]# kubectl describe services nginx-service
Name: nginx-service
Namespace: default
Labels: Pod与Service的关系
Service通过标签选择器来匹配一组Pod,通常来说这组Pod都是在deployment里面定义的,现在看一下之前创建的nginx-deployment标签那里是怎么写的,[root@k8s01 yml]# cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: web
spec:
selector:
matchLabels:
app: web
replicas: 3
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
[root@k8s01 yml]#
#第一处这个标签可以定义多个,但是不要重复
labels:
app: web
#此为控制器使用的标签匹配,
selector:
matchLabels:
app: web
#这个是Pod标签,如果控制器要匹配到这一组Pod,控制器标签和Pod标签要保证一致,才能匹配到,Service实现了Pod的负载均衡,提供的是四层负载均衡,也就是TCP/UDP级别的。
template:
metadata:
labels:
app: web
Service 类型
有三种类型,分别如下。
ClusterIP #默认类型,分配一个集群内部可以访问的虚拟IP(VIP),主要用于集群内部通讯
NodePort #在每个Node上分配一个端口作为外部访问入口。
LoadBalancer #工作在特定Cloud Provider上,如谷歌云,aws,OpenStack
ClusterIP
iptables/IPVS去实现的,接收到请求后选一个Pod IP & Port将流量转发过去,访问流程大概就是请求先到iptables/IPVS→Service→Pod,Service逻辑上将Pod绑定到一起,对于用户是无感知的,你只需要知道Service的IP即可,这个主要是集群内部用的,下面的就是之前写的一个Cluster类型的Service[root@k8s01 yml]# cat nginx_service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: default
spec:
clusterIP: 10.0.0.213
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: web
NodePort
K8S集群中,你面临的问题就是如何让用户访问到你部署的应用,这就会用到最常用的一种方式,NodePort就会在每一个node上暴露一个端口出去,作为访问的统一入口,基于上面的配置改一下。[root@k8s01 yml]# cat nginx_service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: default
spec:
type: NodePort
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
nodePort: 30010
selector:
app: web
[root@k8s01 yml]# kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.0.0.1 Service就是将标签为nginx的Pod上的80端口发布到了Node节点的30010端口,Node节点的端口范围是在apiserver`中指定的,也就是这里。[root@k8s01 yml]# cat /opt/kubernetes/cfg/kube-apiserver.conf |grep service-node-port
--service-node-port-range=30000-32767 [root@k8s01 yml]#
[root@k8s01 yml]# for i in 192.168.10.{92,93,95}:30010;do curl -I $i;done
HTTP/1.1 200 OK
Server: nginx/1.17.10
Date: Fri, 29 May 2020 10:18:16 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 14 Apr 2020 14:19:26 GMT
Connection: keep-alive
ETag: "5e95c66e-264"
Accept-Ranges: bytes
HTTP/1.1 200 OK
Server: nginx/1.17.10
Date: Fri, 29 May 2020 10:18:16 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 14 Apr 2020 14:19:26 GMT
Connection: keep-alive
ETag: "5e95c66e-264"
Accept-Ranges: bytes
HTTP/1.1 200 OK
Server: nginx/1.17.10
Date: Fri, 29 May 2020 10:18:16 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 14 Apr 2020 14:19:26 GMT
Connection: keep-alive
ETag: "5e95c66e-264"
Accept-Ranges: bytes
IPVS方式去做的负载均衡,全部节点能用ipvsadm看到规则,下面看一下,全部node节点安装ipvsadm,执行命令看一下。[root@k8s01 yml]# ansible nodes -m yum -a "name=ipvsadm state=installed"
[root@k8s01 yml]# ansible nodes -m shell -a "ipvsadm -ln | grep 30010 -A 3"
192.168.10.92 | CHANGED | rc=0 >>
TCP 192.168.10.92:30010 rr
-> 10.244.0.42:80 Masq 1 0 0
-> 10.244.1.64:80 Masq 1 0 0
-> 10.244.2.29:80 Masq 1 0 0
--
TCP 10.244.0.0:30010 rr
-> 10.244.0.42:80 Masq 1 0 0
-> 10.244.1.64:80 Masq 1 0 0
-> 10.244.2.29:80 Masq 1 0 0
--
TCP 10.244.0.1:30010 rr
-> 10.244.0.42:80 Masq 1 0 0
-> 10.244.1.64:80 Masq 1 0 0
-> 10.244.2.29:80 Masq 1 0 0
--
TCP 127.0.0.1:30010 rr
-> 10.244.0.42:80 Masq 1 0 0
-> 10.244.1.64:80 Masq 1 0 0
-> 10.244.2.29:80 Masq 1 0 0
TCP 172.17.0.1:30010 rr
-> 10.244.0.42:80 Masq 1 0 0
-> 10.244.1.64:80 Masq 1 0 0
-> 10.244.2.29:80 Masq 1 0 0
192.168.10.93 | CHANGED | rc=0 >>
TCP 192.168.10.93:30010 rr
-> 10.244.0.42:80 Masq 1 0 0
-> 10.244.1.64:80 Masq 1 0 0
-> 10.244.2.29:80 Masq 1 0 0
--
TCP 10.244.1.0:30010 rr
-> 10.244.0.42:80 Masq 1 0 0
-> 10.244.1.64:80 Masq 1 0 0
-> 10.244.2.29:80 Masq 1 0 0
--
TCP 10.244.1.1:30010 rr
-> 10.244.0.42:80 Masq 1 0 0
-> 10.244.1.64:80 Masq 1 0 0
-> 10.244.2.29:80 Masq 1 0 0
--
TCP 127.0.0.1:30010 rr
-> 10.244.0.42:80 Masq 1 0 0
-> 10.244.1.64:80 Masq 1 0 0
-> 10.244.2.29:80 Masq 1 0 0
TCP 172.17.0.1:30010 rr
-> 10.244.0.42:80 Masq 1 0 0
-> 10.244.1.64:80 Masq 1 0 0
-> 10.244.2.29:80 Masq 1 0 0
192.168.10.95 | CHANGED | rc=0 >>
TCP 192.168.10.95:30010 rr
-> 10.244.0.42:80 Masq 1 0 0
-> 10.244.1.64:80 Masq 1 0 0
-> 10.244.2.29:80 Masq 1 0 0
--
TCP 10.244.2.0:30010 rr
-> 10.244.0.42:80 Masq 1 0 0
-> 10.244.1.64:80 Masq 1 0 0
-> 10.244.2.29:80 Masq 1 0 0
--
TCP 10.244.2.1:30010 rr
-> 10.244.0.42:80 Masq 1 0 0
-> 10.244.1.64:80 Masq 1 0 0
-> 10.244.2.29:80 Masq 1 0 0
--
TCP 127.0.0.1:30010 rr
-> 10.244.0.42:80 Masq 1 0 0
-> 10.244.1.64:80 Masq 1 0 0
-> 10.244.2.29:80 Masq 1 0 0
TCP 172.17.0.1:30010 rr
-> 10.244.0.42:80 Masq 1 0 0
-> 10.244.1.64:80 Masq 1 0 0
-> 10.244.2.29:80 Masq 1 0 0
[root@k8s01 yml]#
iptables,另一种就是现在用的ipvs30000-50000,所以实际应用的时候还会加一个代理,譬如一个nginx,使用代理服务器作为访问入口,upstream地址池随便写几个node-IP:port就行了,Pod少这样玩还可以,Pod多了够你受的,Ingress可以解决这个问题NodePort+代理服务发布服务,NodePort的访问流程如下。NodeIP:Port→PodIP:PortLoadBalancer
[root@k8s01 yml]# kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.0.0.1 80端口为Pod端口,30010端口是在Node节点暴露的端口,所以外部访问的端口为30010,我需要知道这个端口才能把代理和Service关联起来,上文我指定了这个端口,不指定的话就是随机的,但是使用公有云的LoadBalancer就不用了,他是自动帮你添加创建的Service及生成的端口和每个NodeIp到LoadBalancer,LoadBalancer提供特定云提供商底层LB接口,直接用去调用就好,他和NodePort的访问流程其实一样,就是他关联后端节点端口是自动的。service代理模式
Node上部署的kube-proxy组件完成的,网络代理有两种模式,一种是iptables,另一种是IPVS,目前默认使用的都是iptables,现在IPVS属于是稳定版,在部署的时候采用的就是IPVS,看一下kube-proxy的配置文件。[root@k8s02 ~]# cat /opt/kubernetes/cfg/kube-proxy.conf
KUBE_PROXY_OPTS="--logtostderr=false --v=2 --proxy-mode=ipvs --masquerade-all=true --log-dir=/opt/kubernetes/logs --config=/opt/kubernetes/cfg/kube-proxy-config.yml"
[root@k8s02 ~]#
iptables
安装升级iptables
[root@k8s02 tools]# yum -y install gcc make libnftnl-devel libmnl-devel autoconf automake libtool bison flex libnetfilter_conntrack-devel libnetfilter_queue-devel libpcap-devel
Loaded plugins: fastestmirror
[root@k8s02 tools]# wget wget https://www.netfilter.org/projects/iptables/files/iptables-1.6.2.tar.bz2
[root@k8s02 tools]# yum -y install conntrack
tar xf iptables-1.6.2.tar.bz2
cd iptables-1.6.2/
./autogen.sh
./configure
make -j4
make install
cd /usr/local/sbin
\cp iptables /sbin
\cp iptables-restore /sbin/
\cp iptables-save /sbin/
iptables属于是一个内核级的防火墙,当你创建一个Service的时候,kube-proxy会帮你在所有Node上生成iptables规则,在Node节点可以通过iptables-save查看所有规则,规则的多少在于你Pod&Service的数量,目前用的是ipvs模式,现在将k8s02节点切换至iptables模式,默认用的就是iptables模式,所以把配置文件中的指定删掉就行了,也就是这几行。[root@k8s02 ~]# cat /opt/kubernetes/cfg/kube-proxy.conf | grep mode -A 1
--proxy-mode=ipvs --masquerade-all=true [root@k8s03 opt]# cat /opt/kubernetes/cfg/kube-proxy-config.yml
mode: ipvs
ipvs:
scheduler: "rr"
[root@k8s02 ~]# systemctl restart kubelet.service
[root@k8s02 ~]# systemctl restart kube-proxy.service
Service[root@k8s01 yml]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.0.0.1 Node-1节点上检索一下iptables规则,找带有 10.0.0.169 的规则。[root@k8s02 ~]# iptables-save | grep "10.0.0.169"
-A KUBE-SERVICES -d 10.0.0.169/32 -p tcp -m comment --comment "default/nginx-service:http cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.0.0.169/32 -p tcp -m comment --comment "default/nginx-service:http cluster IP" -m tcp --dport 80 -j KUBE-SVC-6IM33IEVEEV7U3GP
[root@k8s02 ~]#
10.0.0.169这个IP,目标端口是80的都会跳转到一个名为KUBE-SVC-6IM33IEVEEV7U3GP的链中,这就是在你创建Service的时候iptables模式会先创建这条规则,然后我们再看一下KUBE-SVC-6IM33IEVEEV7U3GP`链都有啥子规则,主要是下面的这三条。[root@k8s02 ~]# iptables-save | grep "KUBE-SVC-6IM33IEVEEV7U3GP"
-A KUBE-SVC-6IM33IEVEEV7U3GP -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-2TARFLZWM4OAEKXX
-A KUBE-SVC-6IM33IEVEEV7U3GP -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-3OSEFXMGUVYQSZI7
-A KUBE-SVC-6IM33IEVEEV7U3GP -j KUBE-SEP-6SCCYNK5UB2KUKJH
[root@k8s02 ~]#
Pod会对应有三条链,这是一组规则的集合,这组规则就实现了怎么做负载均衡,你可以理解为规则有一个权重值,例如第一条--probability 0.33332999982,这表示有30%的几率会被匹配到这条规则上,iptables的规则匹配是从上到下的,第二条有50%的几率,如果上两条都没匹配到,那就走最后一条了,然后你又会发现这三条规则分别又关联了三条不一样的链,检索一下这三条链都写了什么。[root@k8s02 ~]# iptables-save |egrep "KUBE-SEP-2TARFLZWM4OAEKXX|KUBE-SEP-3OSEFXMGUVYQSZI7|KUBE-SEP-6SCCYNK5UB2KUKJH"
:KUBE-SEP-2TARFLZWM4OAEKXX - [0:0]
:KUBE-SEP-3OSEFXMGUVYQSZI7 - [0:0]
:KUBE-SEP-6SCCYNK5UB2KUKJH - [0:0]
-A KUBE-SEP-2TARFLZWM4OAEKXX -s 10.244.0.2/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-2TARFLZWM4OAEKXX -p tcp -m tcp -j DNAT --to-destination 10.244.0.2:80
-A KUBE-SEP-3OSEFXMGUVYQSZI7 -s 10.244.1.69/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-3OSEFXMGUVYQSZI7 -p tcp -m tcp -j DNAT --to-destination 10.244.1.69:80
-A KUBE-SEP-6SCCYNK5UB2KUKJH -s 10.244.2.32/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-6SCCYNK5UB2KUKJH -p tcp -m tcp -j DNAT --to-destination 10.244.2.32:80
-A KUBE-SVC-6IM33IEVEEV7U3GP -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-2TARFLZWM4OAEKXX
-A KUBE-SVC-6IM33IEVEEV7U3GP -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-3OSEFXMGUVYQSZI7
-A KUBE-SVC-6IM33IEVEEV7U3GP -j KUBE-SEP-6SCCYNK5UB2KUKJH
[root@k8s02 ~]#
DNAT的操作,这个操作会将数据包转发到后面的IP:Port上,上面可以看到一共是三个,这三个正是Service代理的IP和端口,看一下ENDPOINTS控制器。[root@k8s01 ~]# kubectl get endpoints nginx-service
NAME ENDPOINTS AGE
nginx-service 10.244.0.2:80,10.244.1.69:80,10.244.2.32:80 2d16h
[root@k8s01 ~]#
endpoints都有相对应的iptables规则,这是由kube-proxy组件搞定的,出现变化也会刷新这些规则,这就是使用iptables做流量转发和负载均衡实现的方法,负载均衡就是用了iptables一个权重值的概念来实现轮训转发,流量转发使用了DNAT帮你转发到具体的Pod中,下面来看看IPVS是怎么工作的。IPVS
IPVS模式是怎么工作的,在使用iptables的情况下,会发现有两个问题,第一个创建一个Service,iptables就会创建很多规则,也会涉及到更新,例如我一个pod挂掉了重新启了一个,IP变了,iptables的规则就会刷新,这是一个非增量式的更新,规则越多,消耗越大。Service&Pod越来越多,那自然的你iptables规则就越来越多,问题就是匹配会有延时,因为一个请求进来,iptables会从上到下进行匹配,规则越多匹配越慢,这两个问题很致命,在大规模的情况下不建议使用iptables模式,而是使用IPVS模式。LVS,其实LVS就是通过IPVS内核调度模块实现的负载均衡,LVS只支持四层负载均衡,如果你用过LVS,IPVS你就很好理解了,一个内核级的调度模块。IPVS模式之后,它也是由kube-proxy来维护的,当你创建一个Service,他会生成一个虚拟设备kube-ipvs0,看一下。[root@k8s03 ~]# ip addr| grep ipvs
5: kube-ipvs0: IP绑定的正是所有Service的IP,也就是CLUSTER-IP,都会被绑定到这里,可以看到之前nginx-service使用的203在这里,再看一下IPVS规则,也找nginx-service的。[root@k8s03 ~]# ipvsadm -ln | grep "10.0.0.169" -A 3
TCP 10.0.0.169:80 rr
-> 10.244.1.64:80 Masq 1 0 0
-> 10.244.1.66:80 Masq 1 0 0
-> 10.244.2.30:80 Masq 1 0 0
[root@k8s03 ~]#
kube-proxy通过IPVS去刷新规则,IPVS会比iptables的性能好很多,因为IPVS是工作在内核态,而iptables是工作在用户态,IPVS维护成本也比较低,不像iptables那么麻烦。iptables与IPVS小结
iptables比较灵活,功能也很强大,但问题就是在于更新规则都是全量去更新的,规则越多,操作越慢,再就是规则匹配这块,从上到下匹配,规则越多,匹配越慢。IPVS是工作在内核态的,有更好的性能,而且调度算法也支持很多,iptables暂时只支持轮训,IPVS可以设置很多调度算法,默认rr(轮训),还可以使用wrr(加权轮询)&lc(最小连接)&wlc(加权最小连接)&ip hash,在实际生产环境中最好使用ipvs,具体制定使用什么算法可以在配置文件中指定,使用如下参数--ipvs-scheduler=算法