K8S四层代理Service

百度网盘链接:https://pan.baidu.com/s/15t_TSH5RRpCFXV-93JHpNw?pwd=8od3  提取码:8od3

11 K8S四层代理Service

11.1 四层负载均衡Service

11.1.1为什么要有Service?

在kubernetes中,Pod是有生命周期的,如果Pod重启它的IP很有可能会发生变化。如果我们的服务都是将Pod的IP地址写死,Pod挂掉或者重启,和刚才重启的pod相关联的其他服务将会找不到它所关联的Pod,为了解决这个问题,在kubernetes中定义了service资源对象,Service 定义了一个服务访问的入口,客户端通过这个入口即可访问服务背后的应用集群实例,service是一组Pod的逻辑集合,这一组Pod能够被Service访问到,通常是通过Label Selector实现的。

可以看下面的图:

 

1、pod ip经常变化,service是pod的代理,我们客户端访问,只需要访问service,就会把请求代理到Pod。

2、pod ip在k8s集群之外无法访问,所以需要创建service,这个service可以在k8s集群外访问的。

11.1.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的实现方式。

11.1.3 Service工作原理

k8s在创建Service时,会根据标签选择器selector(lable selector)来查找Pod,据此创建与Service同名的endpoint对象,当Pod 地址发生变化时,endpoint也会随之发生变化,service接收前端client请求的时候,就会通过endpoint,找到转发到哪个Pod进行访问的地址。(至于转发到哪个节点的Pod,由负载均衡kube-proxy决定)

11.1.4 kubernetes集群中有三类IP地址

1、Node Network(节点网络),物理节点或者虚拟节点的网络,如ens33接口上的网路地址

2、Pod network(pod 网络),创建的Pod具有的IP地址

[root@master1 ~]# kubectl get pods -o wide

NAME             READY   STATUS          IP               NODE      

frontend-h78gw   1/1     Running         10.244.187.76    node2

Node Network和Pod network这两种网络地址是我们实实在在配置的,其中节点网络地址是配置在节点接口之上,而pod网络地址是配置在pod资源之上的,因此这些地址都是配置在某些设备之上的,这些设备可能是硬件,也可能是软件模拟的。

3、Cluster Network(集群地址,也称为service network),这个地址是虚拟的地址(virtual ip),没有配置在某个接口上,只是出现在service的规则当中。

[root@master1 ~]# kubectl get svc

NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)       

kubernetes         ClusterIP   10.96.0.1       <none>        443/TCP  

11.2 Service资源字段解释

[root@master1 ~]# kubectl explain service    //查看定义Service资源需要的字段有哪些?

KIND:     Service

VERSION:  v1

FIELDS:

   apiVersion  <string>       #service资源使用的api组

   kind <string>              #创建的资源类型

   metadata    <Object>       #定义元数据

   spec   <Object>    

[root@master1 ~]# kubectl explain service.spec    //查看service的spec字段如何定义?

KIND:     Service

VERSION:  v1

RESOURCE: spec <Object>

FIELDS:

   allocateLoadBalancerNodePorts <boolean>

   clusterIP   <string>   #动态分配的地址,也可以自己在创建的时候指定,创建之后就改不了了

   clusterIPs  <[]string>

   externalIPs <[]string>

   externalName    <string>

   externalTrafficPolicy     <string>

   healthCheckNodePort  <integer>

   ipFamilies  <[]string>

   ipFamilyPolicy  <string>

   loadBalancerIP  <string>

   loadBalancerSourceRanges  <[]string>

   ports  <[]Object>  #定义service端口,用来和后端pod建立联系

   publishNotReadyAddresses  <boolean>

   selector    <map[string]string>  #通过标签选择器选择关联的pod有哪些

   sessionAffinity <string>

   sessionAffinityConfig <Object>   #service在实现负载均衡的时候还支持sessionAffinity,sessionAffinity,什么意思?会话联系,默认是none,随机调度的(基于iptables规则调度的);如果我们定义sessionAffinity的client ip,那就表示把来自同一客户端的IP请求调度到同一个pod上

   topologyKeys    <[]string>

   type   <string>  #定义service的类型

 

Service的四种类型:

[root@master1 ~]# kubectl explain service.spec.type    //查看定义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.

     https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types

[root@master1 ~]# kubectl explain service.spec.ports    //查看service的spec.ports字段如何定义?

KIND:     Service

VERSION:  v1

RESOURCE: ports <[]Object>

FIELDS:

   appProtocol <string>

   name   <string>   #定义端口的名字

   nodePort    <integer>   #service在物理机映射的端口,默认在 30000-32767 之间

   port   <integer> -required-  #service的端口,这个是k8s集群内部服务可访问的端口

   protocol    <string>

   targetPort  <string>  #targetPort是pod上的端口,从port和nodePort上来的流量,经过kube-proxy流入到后端pod的targetPort上,最后进入容器。

11.3 创建Service:type类型是ClusterIP

[root@master1 ~]# 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@master1 ~]# kubectl apply -f pod_test.yaml

[root@master1 ~]# kubectl get pods -l run=my-nginx -o wide  

NAME                            STATUS           IP                  NODE

my-nginx-5b56ccd65f-26vcz       Running          10.244.187.101      node2

my-nginx-5b56ccd65f-95n7p       Running          10.244.209.149      node1

请求pod ip地址,查看结果:

[root@master1 ~]# curl 10.244.187.101

<!DOCTYPE html>

<html>

<h1>Welcome to nginx!</h1>

</body>

</html>

[root@master1 ~]# curl 10.244.209.149

<!DOCTYPE html>

<html>

<h1>Welcome to nginx!</h1>

</body>

</html>

[root@master1 ~]# 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@master1 ~]# kubectl delete pods my-nginx-5b56ccd65f-26vcz

[root@master1 ~]# kubectl get pods -l run=my-nginx -o wide

NAME                          STATUS      IP               NODE

my-nginx-5b56ccd65f-7xzr4     Running     10.244.187.102   node2 

my-nginx-5b56ccd65f-95n7p     Running     10.244.209.149   node1 

通过上面可以看到重新生成了一个pod:my-nginx-5b56ccd65f-7xzr4,ip是10.244.187.102,在k8s中创建pod,如果pod被删除了,重新生成的pod ip地址会发生变化,所以需要在pod前端加一个固定接入层。

 

[root@master1 ~]# kubectl get pods --show-labels    //查看pod标签

[root@master1 ~]# vim service_test.yaml   //创建Service

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的Pod,目标TCP端口 80,并且在一个抽象的Service端口(targetPort:容器接收流量的端口;port:抽象的 Service 端口,可以使任何其它 Pod访问该 Service 的端口)上暴露。

[root@master1 ~]# kubectl apply -f service_test.yaml

[root@master1 ~]# 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@master1 ~]# 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的。

[root@master1 ~]# kubectl describe svc my-nginx    //查看service详细信息

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@master1 ~]# 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。

service只要创建完成,我们就可以直接解析它的服务名,每一个服务创建完成后都会在集群dns中动态添加一个资源记录,添加完成后我们就可以解析了,资源记录格式是:

SVC_NAME.NS_NAME.DOMAIN.LTD.

服务名.命名空间.域名后缀

集群默认的域名后缀是:svc.cluster.local

就像我们上面创建的my-nginx这个服务,它的完整名称解析就是:my-nginx.default.svc.cluster.local

[root@master1 ~]# kubectl exec -it my-nginx-5b56ccd65f-7xzr4 -- /bin/bash

root@my-nginx-5b56ccd65f-7xzr4:/# curl my-nginx.default.svc.cluster.local

<!DOCTYPE html>

<h1>Welcome to nginx!</h1>

root@my-nginx-5b56ccd65f-7xzr4:/# exit

11.4 创建Service:type类型是NodePort

[root@master1 ~]# vim pod_nodeport.yaml   //创建一个pod资源

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@master1 ~]# kubectl apply -f pod_nodeport.yaml

[root@master1 ~]# kubectl get pods -l run=my-nginx-nodeport -owide        

my-nginx-nodeport-7776f84758-9w5mh   1/1     Running   0          6m53s   10.244.121.36   node1

my-nginx-nodeport-7776f84758-h6hk8   1/1     Running   0          6m53s   10.244.102.86   node2

[root@master1 ~]# vim service_nodeport.yaml   //创建service,代理pod

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@master1 ~]# kubectl apply -f service_nodeport.yaml

[root@master1 ~]# 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  

[root@master1 ~]# curl 10.100.156.7

<!DOCTYPE html>

<h1>Welcome to nginx!</h1>

</html>

注意:10.100.156.7是k8s集群内部的service ip地址,只能在k8s集群内部访问,在集群外无法访问。

[root@master1 ~]# curl 192.168.40.180:30380    //在集群外访问service

<!DOCTYPE html>

<h1>Welcome to nginx!</h1>

</html>

 

在浏览器访问service:

 

[root@master1 ~]# ipvsadm -Ln

IP Virtual Server version 1.2.1 (size=4096)

Prot LocalAddress:Port Scheduler Flags

  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn

TCP  172.17.0.1:30380 rr

  -> 10.244.102.86:80             Masq    1      0          0        

  -> 10.244.121.36:80             Masq    1      0          0

转发流程:

客户端请求http://192.168.40.180:30380 -> docker0虚拟网卡:172.17.0.1:30380 -> 10.244.121.36/86:80

11.5 创建Service:type类型是ExternalName

应用场景:跨名称空间访问

需求:default名称空间下的client 服务想要访问nginx-ns名称空间下的nginx-svc服务。

[root@master1 ~]# 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@master1 ~]# kubectl create ns nginx-ns

[root@master1 ~]# kubectl apply -f server_nginx.yaml

[root@master1 ~]# kubectl get pods -n nginx-ns

nginx-7cf7d6dbc8-lzm6j   1/1     Running   0          10m

 

[root@master1 ~]# 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@master1 ~]# kubectl apply -f nginx_svc.yaml

 

[root@master1 ~]# 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

        imagePullPolicy: IfNotPresent

        command: ["/bin/sh","-c","sleep 36000"]

[root@master1 ~]# kubectl apply -f client.yaml

 

[root@master1 ~]# vim client_svc.yaml

apiVersion: v1

kind: Service

metadata:

  name: client-svc

spec:

  type: ExternalName

  externalName: nginx-svc.nginx-ns.svc.cluster.local

  ports:

  - name: http

    port: 80

    targetPort: 80

[root@master1 ~]# kubectl get pods

client-76b6556d97-xk7mg              1/1     Running      0       

[root@master1 ~]# kubectl apply -f client_svc.yaml

该文件中指定了到 nginx-svc的软链,让使用者感觉就好像调用自己命名空间的服务一样。

 

[root@master1 ~]# kubectl exec -it  client-76b6556d97-xk7mg -- /bin/sh    //登录到client pod

/ # wget -q -O - client-svc.default.svc.cluster.local

    wget -q -O - nginx-svc.nginx-ns.svc.cluster.local    //两个请求的结果一样

11.6 k8s最佳实践

11.6.1 k8s集群引用外部的mysql数据库

在node2上安装mysql数据库:

[root@node2 ~]# yum install mariadb-server.x86_64 -y

[root@node2 ~]# systemctl start mariadb

[root@master1 ~]# vim mysql_service.yaml

apiVersion: v1

kind: Service

metadata:

  name: mysql

spec:

  type: ClusterIP

  ports:

  - port: 3306

[root@master1 ~]# kubectl apply -f mysql_service.yaml

[root@master1 ~]# kubectl get svc | grep mysql

mysql               ClusterIP      10.107.232.103              3306/TCP  

 

[root@master1 ~]# kubectl describe svc mysql

......

Port:              <unset>  3306/TCP

TargetPort:        3306/TCP

Endpoints:         <none>  #还没有endpoint

Session Affinity:  None

......

 

[root@master1 ~]# vim mysql_endpoint.yaml

apiVersion: v1

kind: Endpoints

metadata:

  name: mysql

subsets:

- addresses:

  - ip: 192.168.40.182

  ports:

  - port: 3306

[root@master1 ~]# kubectl apply -f mysql_endpoint.yaml

[root@master1 ~]# kubectl describe svc mysql

......

Port:              <unset>  3306/TCP

TargetPort:        3306/TCP

Endpoints:         192.168.1.62:3306    #这个就是定义的外部数据库

......

上面配置就是将外部IP地址和服务引入到k8s集群内部,由service作为一个代理来达到能够访问外部服务的目的。

11.7 coredns组件详解

11.7.1 DNS是什么?

DNS全称是Domain Name System:域名系统,是整个互联网的电话簿,它能够将可被人理解的域名翻译成可被机器理解IP地址,使得互联网的使用者不再需要直接接触很难阅读和理解的IP地址。域名系统在现在的互联网中非常重要,因为服务器的 IP 地址可能会经常变动,如果没有了 DNS,那么可能 IP 地址一旦发生了更改,当前服务器的客户端就没有办法连接到目标的服务器了,如果我们为 IP 地址提供一个『别名』并在其发生变动时修改别名和 IP 地址的关系,那么我们就可以保证集群对外提供的服务能够相对稳定地被其他客户端访问。DNS 其实就是一个分布式的树状命名系统,它就像一个去中心化的分布式数据库,存储着从域名到 IP 地址的映射。

CoreDNS其实就是一个DNS服务,而DNS作为一种常见的服务发现手段,所以很多开源项目以及工程师都会使用CoreDNS 为集群提供服务发现的功能,Kubernetes就在集群中使用CoreDNS解决服务发现的问题。 作为一个加入CNCF(Cloud Native Computing Foundation)的服务,CoreDNS的实现非常简单。

11.7.2 验证coredns

[root@master1 ~]# vim dig.yaml

apiVersion: v1

kind: Pod

metadata:

  name: dig

  namespace: default

spec:

  containers:

  - name: dig

    image: busybox:1.28

    imagePullPolicy: IfNotPresent

    command:

      - sleep

      - "3600"

  restartPolicy: Always

[root@master1 ~]# kubectl apply -f dig.yaml

[root@master1 ~]# kubectl get svc | grep kubernetes

kubernetes          ClusterIP      10.96.0.1        443/TCP        5d13h

[root@master1 ~]# kubectl exec -it dig -- nslookup kubernetes  //解析dns,如有以下返回说明dns安装成功

Server:        10.96.0.10

Address:  10.96.0.10#53

Name: kubernetes.default.svc.cluster.local

Address: 10.96.0.1

 

kubernetes.default.svc.cluster.local    服务名.名称空间.默认后缀

在k8s中创建service之后,service默认的FQDN是<servicename>.<namespace>.svc.cluster.local,那么k8s集群内部的服务就可以通过FQDN访问。

posted @ 2022-12-27 00:45  Krill_ss  阅读(306)  评论(0编辑  收藏  举报