您现在的位置是:首页 >学无止境 >kubernetes四层负载均衡 —— service(一)网站首页学无止境
kubernetes四层负载均衡 —— service(一)
目录
一、四层负载均衡Service:概念、原理解读
1.为什么要有Service?
在kubernetes中,Pod是有生命周期的,如果Pod重启它的IP很有可能会发生变化。如果我们的服务都是将Pod的IP地址写死,Pod挂掉或者重启,和刚才重启的pod相关联的其他服务将会找不到它所关联的Pod,为了解决这个问题,在kubernetes中定义了service资源对象,Service 定义了一个服务访问的入口,客户端通过这个入口即可访问服务背后的应用集群实例,service是一组Pod的逻辑集合,这一组Pod能够被Service访问到,通常是通过Label Selector实现的。
可以看下面的图:
pod ip经常变化,service是pod的代理,我们客户端访问,只需要访问service,就会把请求代理到Pod
pod ip在k8s集群之外无法访问,所以需要创建service,这个service可以在k8s集群外访问的。
2.Service概述
service是一个固定接入层,客户端可以通过访问service的ip和端口访问到service关联的后端pod,这个service工作依赖于在kubernetes集群之上部署的一个附件,就是kubernetes的dns服务(不同kubernetes版本的dns默认使用的也是不一样的,1.11之前的版本使用的是kubeDNs,较新的版本使用的是coredns),service的名称解析是依赖于dns附件的,因此在部署完k8s之后需要再部署dns附件,kubernetes要想给客户端提供网络功能,需要依赖第三方的网络插件(flannel,calico等)。每个K8s节点上都有一个组件叫做kube-proxy,kube-proxy这个组件将始终监视着apiserver中有关service资源的变动信息,需要跟master之上的apiserver交互,随时连接到apiserver上获取任何一个与service资源相关的资源变动状态,这种是通过kubernetes中固有的一种请求方法watch(监视)来实现的,一旦有service资源的内容发生变动(如创建,删除),kube-proxy都会将它转化成当前节点之上的能够实现service资源调度,把我们请求调度到后端特定的pod资源之上的规则,这个规则可能是iptables,也可能是ipvs,取决于service的实现方式。
3.Service工作原理
k8s在创建Service时,会根据标签选择器selector(lable selector)来查找Pod,据此创建与Service同名的endpoint对象,当Pod 地址发生变化时,endpoint也会随之发生变化,service接收前端client请求的时候,就会通过endpoint,找到转发到哪个Pod进行访问的地址。(至于转发到哪个节点的Pod,由负载均衡kube-proxy决定)
4.kubernetes集群中有三类IP地址
1、Node Network(节点网络):物理节点或者虚拟节点的网络,如ens33接口上的网路地址
[root@hd1.com ~]# ip addr
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP
link/ether 00:0c:29:87:60:d5 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.11/24 brd 192.168.1.255 scope global noprefixroute ens33
2、Pod network(pod 网络),创建的Pod具有的IP地址
[root@hd1.com ~]# kubectl get pods -o wide
NAME READY STATUS IP NODE
frontend-h78gw 1/1 Running 10.244.187.76 hd3.com
Node Network和Pod network这两种网络地址是我们实实在在配置的,其中节点网络地址是配置在节点接口之上,而pod网络地址是配置在pod资源之上的,因此这些地址都是配置在某些设备之上的,这些设备可能是硬件,也可能是软件模拟的
3、Cluster Network(集群地址,也称为service network),这个地址是虚拟的地址(virtual ip),没有配置在某个接口上,只是出现在service的规则当中。
[root@hd1.com ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP
二、创建Service资源
1.#查看定义Service资源需要的字段有哪些?
[root@hd1.com ~]# kubectl explain service
KIND: Service
VERSION: v1
DESCRIPTION:
Service is a named abstraction of software service (for example, mysql)
consisting of local port (for example 3306) that the proxy listens on, and
the selector that determines which pods will answer requests sent through
the proxy.
FIELDS:
apiVersion <string> #service资源使用的api组
kind<string> #创建的资源类型
metadata<Object> #定义元数据
spec<Object> #查看service的spec字段如何定义?
2.Service的四种类型
#查看定义Service.spec.type需要的字段有哪些?
[root@hd1.com ~]# kubectl explain service.spec.type
KIND: Service
VERSION: v1
FIELD: type <string>
DESCRIPTION:
type determines how the Service is exposed. Defaults to ClusterIP. Valid
options are ExternalName, ClusterIP, NodePort, and LoadBalancer.
"ClusterIP" allocates a cluster-internal IP address for load-balancing to
endpoints. Endpoints are determined by the selector or if that is not
specified, by manual construction of an Endpoints object or EndpointSlice
objects. If clusterIP is "None", no virtual IP is allocated and the
endpoints are published as a set of endpoints rather than a virtual IP.
"NodePort" builds on ClusterIP and allocates a port on every node which
1、ExternalName:
适用于k8s集群内部容器访问外部资源,它没有指定selector去选择pod,也没有定义任何的端口和Endpoint。
以下Service 定义的是将prod名称空间中的my-service服务映射到my.database.example.com
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
当查询主机 my-service.prod.svc.cluster.local 时,群集DNS将返回值为my.database.example.com的CNAME记录。
service的FQDN(完全限定域名)是: <service_name>.<namespace>.svc.cluster.local my-service.prod. svc.cluster.local
该样例目的是让处于 default 命名空间下的 httpd-pod 访问到处于 nginx-ns 命名空间下的 nginx-svc
像以往 svc-name.ns-name.svc.cluster.local 方式跨命名空间访问的方式
2、ClusterIP:
通过k8s集群内部IP暴露服务,选择该值,服务只能够在集群内部访问,这也是默认的ServiceType。
3、NodePort:
通过每个Node节点上的IP和静态端口暴露k8s集群内部的服务。通过请求<NodeIP>:<NodePort>可以把请求代理到内部的pod。Client----->NodeIP:NodePort----->Service Ip:ServicePort----->PodIP:ContainerPort。
4、LoadBalancer:
使用云提供商的负载均衡器,可以向外部暴露服务。外部的负载均衡器可以路由到NodePort服务和ClusterIP服务。
3.Service的端口
#查看service的spec.ports字段如何定义?
[root@hd1.com ~]# kubectl explain service.spec.ports
FIELDS:
appProtocol <string>
name<string> #定义端口的名字
nodePort<integer>
|#宿主机上映射的端口,比如一个Web应用需要被k8s集群之外的其他用户访问,那么需要配置type=NodePort,若配置nodePort=30001,那么其他机器就可以通过浏览器访问scheme://k8s集群中的任何一个节点ip:30001即可访问到该服务,例如http://192.168.1.12:30001。如果在k8s中部署MySQL数据库,MySQL可能不需要被外界访问,只需被内部服务访问,那么就不需要设置NodePort
port<integer> -required- #service的端口,这个是k8s集群内部服务可访问的端口
protocol<string>
targetPort <string>
# targetPort是pod上的端口,从port和nodePort上来的流量,经过kube-proxy流入到后端pod的targetPort上,最后进入容器。与制作容器时暴露的端口一致(使用DockerFile中的EXPOSE),例如官方的nginx暴露80端口。
三、创建Service
1.type类型时ClusterIP
1、创建Pod
#把nginx.tar.gz上传到hd3.com和hd2.com,手动解压
[root@hd2.com ~]# docker load -i nginx.tar.gz
[root@hd3.com ~]# docker load -i nginx.tar.gz
[root@hd1.com ~]# vim pod_test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80 #pod中的容器需要暴露的端口
#更新资源清单文件
[root@hd1.com ~]# kubectl apply -f pod_test.yaml
#查看刚才创建的Pod ip地址
[root@hd1.com ~]# kubectl get pods -l run=my-nginx -o wide
NAME STATUS IP NODE
my-nginx-5b56ccd65f-26vcz Running 10.244.187.101 hd3.com
my-nginx-5b56ccd65f-95n7p Running 10.244.209.149 hd2.com
#请求pod ip地址,查看结果
[root@hd1.com ~]# curl 10.244.187.101
<!DOCTYPE html>
<html>
<h1>Welcome to nginx!</h1>
</body>
</html>
[root@hd1.com ~]# curl 10.244.209.149
<!DOCTYPE html>
<html>
<h1>Welcome to nginx!</h1>
</body>
</html>
[root@hd1.com ~]# kubectl exec -it my-nginx-5b56ccd65f-26vcz -- /bin/bash
root@my-nginx-5b56ccd65f-26vcz:/# curl 10.244.209.149
<!DOCTYPE html>
<html>
<h1>Welcome to nginx!</h1>
</html>
root@my-nginx-5b56ccd65f-26vcz:/#exit
需要注意的是,pod虽然定义了容器端口,但是不会使用调度到该节点上的80端口,也不会使用任何特定的NAT规则去路由流量到Pod上。 这意味着可以在同一个节点上运行多个 Pod,使用相同的容器端口,并且可以从集群中任何其他的Pod或节点上使用IP的方式访问到它们。
查看pod标签:
[root@hd1.com service]# kubectl get pods --show-labels
2、创建Service
[root@hd1.com service]# cat service_test.yaml
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
type: ClusterIP #只能在集群内部访问
ports:
- port: 80 #service的端口,暴露给k8s集群内部服务访问
protocol: TCP
targetPort: 80 #pod容器中定义的端口
selector:
run: my-nginx #选择拥有run=my-nginx标签的pod
上述yaml文件将创建一个 Service,将具有标签run=my-nginx 并且端口是80 的pod 在一个抽象的Service端口80上暴露。
targetPort:容器接收流量的端口;
port:抽象的 Service 端口,可以使任何其它 Pod访问该 Service 的端口)
[root@hd1.com service]# kubectl apply -f service_test.yaml
service/my-nginx created
[root@hd1.com ~]# kubectl get svc -l run=my-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx ClusterIP 10.99.198.177 <none> 80/TCP 143m
#在k8s控制节点访问service的ip:端口就可以把请求代理到后端pod
[root@hd1.com ~]# curl 10.99.198.177:80
<!DOCTYPE html>
<html>
<h1>Welcome to nginx!</h1>
</html>
#通过上面可以看到请求service IP:port跟直接访问pod ip:port看到的结果一样,这就说明service可以把请求代理到它所关联的后端pod
注意:上面的10.99.198.177:80地址只能是在k8s集群内部可以访问,在外部无法访问,比方说我们想要通过浏览器访问,那么是访问不通的,如果想要在k8s集群之外访问,是需要把service type类型改成NodePort的
#查看service详细信息
[root@hd1.com ~]# kubectl describe svc my-nginx
Name: my-nginx
Namespace: default
Labels: run=my-nginx
Annotations: <none>
Selector: run=my-nginx
Type: ClusterIP
IP Families: <none>
IP: 10.99.198.177
IPs: 10.99.198.177
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.187.102:80,10.244.209.149:80
Session Affinity: None
Events: <none>
[root@hd1.com ~]# kubectl get ep my-nginx
NAME ENDPOINTS AGE
my-nginx 10.244.187.102:80,10.244.209.149:80 142m
service可以对外提供统一固定的ip地址,并将请求重定向至集群中的pod。其中“将请求重定向至集群中的pod”就是通过endpoint与selector协同工作实现。selector是用于选择pod,由selector选择出来的pod的ip地址和端口号,将会被记录在endpoint中。
endpoint便记录了所有pod的ip地址和端口号。当一个请求访问到service的ip地址时,就会从endpoint中选择出一个ip地址和端口号,然后将请求重定向至pod中。具体把请求代理到哪个pod,需要的就是kube-proxy的轮询实现的。
service不会直接到pod,service是直接到endpoint资源,就是地址加端口,再由endpoint再关联到pod。
2.type类型是NodePort
1、创建一个pod资源
[root@hd1.com ~]# vim pod_nodeport.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx-nodeport
spec:
selector:
matchLabels:
run: my-nginx-nodeport
replicas: 2
template:
metadata:
labels:
run: my-nginx-nodeport
spec:
containers:
- name: my-nginx-nodeport-container
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
#更新资源清单文件
[root@hd1.com ~]# kubectl apply -f pod_nodeport.yaml
deployment.apps/my-nginx-nodeport created
#查看pod是否创建成功
[root@hd1.com ~]# kubectl get pods -l run=my-nginx-nodeport
NAME READY STATUS RESTARTS AGE
my-nginx-nodeport-6f8c64fc6c-86wnc 1/1 Running 0 67s
my-nginx-nodeport-6f8c64fc6c-8wrpq 1/1 Running 0 67s
2、创建service,代理pod
[root@hd1.com ~]# cat service_nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: my-nginx-nodeport
labels:
run: my-nginx-nodeport
spec:
type: NodePort
ports:
- port: 80
protocol: TCP
targetPort: 80
nodePort: 30380
selector:
run: my-nginx-nodeport
更新资源清单文件
[root@hd1.com ~]# kubectl apply -f service_nodeport.yaml
service/my-nginx-nodeport created
#查看刚才创建的service
[root@hd1.com ~]# kubectl get svc -l run=my-nginx-nodeport
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
my-nginx-nodeport NodePort 10.100.156.7 <none> 80:30380/TCP
#访问service
[root@hd1.com ~]# curl 10.100.156.7
<!DOCTYPE html>
<h1>Welcome to nginx!</h1>
</html>
注意:10.100.156.7是k8s集群内部的service ip地址,只能在k8s集群内部访问,在集群外无法访问。
#在集群外访问service
[root@hd1.com ~]# curl 192.168.1.11:30380
<!DOCTYPE html>
<h1>Welcome to nginx!</h1>
</html>
#在浏览器访问service
服务请求走向:Client-ànode ip:30380->service ip:80-àpod ip:container port
Client ->192.168.1.11:30380->10.100.156.7:80->pod ip:80
3.type类型是ExternalName
#注意:k8s不同的命名空间下的pod之间可以访问,而docker namespace 实现的是资源隔离 应用场景:跨名称空间访问
需求:default名称空间下的client 服务想要访问jx名称空间下的nginx-svc服务
需要把busybox.tar.gz上传到hd3.com和hd2.com上,手动解压,
[root@hd1.com exter]# docker load -i busybox.tar.gz
[root@hd1.com exter]# vim client.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: client
spec:
replicas: 1
selector:
matchLabels:
app: busybox
template:
metadata:
labels:
app: busybox
spec:
containers:
- name: busybox
image: busybox:1.28
command: ["/bin/sh","-c","sleep 36000"]
[root@hd1.com exter]# kubectl apply -f client.yaml
[root@hd1.com exter]# vim client_svc.yaml
apiVersion: v1
kind: Service
metadata:
name: client-svc
spec:
type: ExternalName
externalName: nginx-svc.jx.svc.cluster.local
ports:
- name: http
port: 80
targetPort: 80
#该文件中指定了到 nginx-svc 的软链,让使用者感觉就好像调用自己命名空间的服务一样。
#查看pod是否正常运行
[root@hd1.com exter]# kubectl get pods
NAME READY STATUS RESTARTS
client-76b6556d97-xk7mg 1/1 Running 0
[root@hd1.com exter]# kubectl apply -f client_svc.yaml
[root@hd1.com exter]# kubectl create ns nginx-ns
[root@hd1.com exter]# vim server_nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: nginx-ns
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
[root@hd1.com exter]# kubectl apply -f server_nginx.yaml
#查看pod是否创建成功
[root@hd1.com exter]# kubectl get pods -n nginx-ns
NAME READY STATUS RESTARTS AGE
nginx-7cf7d6dbc8-lzm6j 1/1 Running 0 10m
[root@hd1.com exter]# vim nginx_svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: nginx-ns
spec:
selector:
app: nginx
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
[root@hd1.com exter]# kubectl apply -f nginx_svc.yaml
#登录到client pod
[root@hd1.com exter]# kubectl exec -it client-76b6556d97-xk7mg -- /bin/sh
/ # wget -q -O - client-svc.default.svc.cluster.local
wget -q -O - nginx-svc.nginx-ns.svc.cluster.local
上面两个请求的结果一样