• 五、Service详解


    一、Service简介

    1.1 service作用

    作用:

    • 使集群内部能访问pod,或者集群外访问pod

    • 用于pod的服务发现与负载均衡(TCP/UDP 4层)

    • 通过selector指定某一类pod的标签相关联pod

    • 底层原理是通过iptables和IPVS二种网络模式来实现的服务发现跟负载均衡

    为什么要用到service,因为Pod是不稳定的,随时可能停止在被控制器拉起,这样ip就会发生变化,所以需要service提供一个固定的ip来访问这些pod

    pod的服务发现:

    通过deployment或者replicas控制器创建的pod,指定了pod的副本个数,当pod的副本个数扩容或裁剪的时候,service是自动发现新增的pod并给他流量,自动发现裁剪的pod,不再给这类下线的pod流量,也就访问不到下线的pod了。

    负载均衡:

    使用service关联多个pod,流量是负载均衡到每个pod上的。

    底层原理:

    Service工作模式有三种:userspace(k8s 1.1版本之前使用),iptables(k8s 1.10版本之前使用),ipvs(k8s 1.11版本之后一直默认使用)

    目前k8s默认使用ipvs模式来转发流量的,Service也是通过这种模式来找pod的。

    参考资料:Service三种工作模式

    Iptables实现流量转发

    image-20211120192517593

    可使用命令查看规则

    iptables -L -n #查看
    

    ipvs实现流量转发

    image-20211120192923243

    可使用命令查看规则

    ipvsadm -ln
    

    参考资料:iptables跟ipvs区别

    1.2 service类型

    service类型分为:

    • ClusterIP

    ​ 默认,分配一个只有集群内部节点可以访问的虚拟IP,集群内部节点指的是master跟node节点

    • NodePort

    ​ 在每个集群节点上分配一个端口作为外部访问入口,集群外部节点也能访问

    • LoadBalancer

    ​ 负载均衡器,工作在特定的Cloud Provider(云平台)上,例如Google Cloud,AWS,OpenStack

    • ExternalName

    ​ 表示把集群外部的服务引入到集群内部中来,即实现了集群内部pod和集群外部的服务进行通信;也可用于不同命名空间之间的通信。

    参考资料:LoadBalancer详解ExternalName详解

    补充:

    ClusterIP根据是否生成ClusterIP又可分为普通Service和Headless Service(无头服务)

    • 普通Service:

      为Kubernetes的Service分配一个集群内部可访问的固定的虚拟IP(Cluster IP), 实现集群内的访问。

    • Headless Service: (无头服务)

      该服务不会分配Cluster IP, 也不通过kube-proxy做反向代理和负载均衡。而是通过DNS为每个pod提供一个域名,这样我们能访问到每个单独的pod。

    参考资料:Headless Service详解

    二、使用Service

    2.1 ClusterIP类型

    集群IP模式,只能在集群内部进行访问,如master跟node节点上,将某一类相同标签的pod映射出来,分配一个service的ip来进行访问。

    如下图所示,使用Deployment资源创建了3个pod,将这三个pod的80端口映射为8000端口,并提供一个Service的ip来访问这些pod,这时我们在集群中的任意节点(即Master或者Node节点上)通过Service的IP加端口就能负载均衡访问到这3个pod。

    image-20211120171542597

    下面我们来创建一个该类型的Service服务体验一下。

    1.使用命令创建方式

    定义一个名为my-dep的Deployment资源,里面包含有3个pod副本,每个pod中含有一个nginx容器

    kubectl create deployment my-dep --image=nginx --replicas=3
    kubectl get deploy
    

    创建Service,默认就是--type=ClusterIP模式,这里也可以不写--type=ClusterIP

    kubectl expose deploy my-dep --port=8000 --target-port=80 --type=ClusterIP --protocol=TCP
    

    查看当前的service

     kubectl get services
     kubectl get svc
    

    可以看到该Service的集群IP为10.96.100.203,暴露的端口为8000,我们在任意集群节点上都可访问curl http://10.96.100.203:8000 负载均衡访问到3个pod中。

    什么,你还不信是负载均衡访问到后面的pod的?那么我们分别修改这3个pod中的index.html文件

    #查看创建的pod名
    kubectl get pods
    
    #依次修改每个pod的html页面,注意进入容器后使用ctrl+d退出容器
    kubectl exec -it my-dep-5b7868d854-bntks -- /bin/bash
    echo 'this is 1 page' > /usr/share/nginx/html/index.html
    
    #修改第二个pod
    ...
    echo 'this is 2 page' > /usr/share/nginx/html/index.html
    
    #修改第三个pod
    ...
    echo 'this is 3 page' > /usr/share/nginx/html/index.html
    
    #访问Service服务
    curl http://10.96.100.203:8000
    

    image-20211121152315038

    2.使用yaml文件创建方式

    1.创建deployment的yaml文件

    [root@k8s-master01 ~]# vim nginx-dep.yml
    apiVersion: apps/v1  #版本信息使用kubectl explain deployment查看
    kind: Deployment
    metadata:
      name: nginx-dep
    
    spec:               
      replicas: 2     #定义2个pod副本             
      selector:
        matchLabels:
          app: nginx-dep  #匹配的pod标签名
          
      template:               
        metadata:
          name: nginx          
          labels:
            app: nginx-dep         # pod的标签名,要跟matchLabels里定义的一致
        spec:
          containers:              
          - name: nginx          
            image: nginx:1.15-alpine
            imagePullPolicy: IfNotPresent
            ports:
            - containerPort: 80
    
    #应用yaml清单
    kubectl apply -f nginx-dep.yml
    

    2.创建Service的yaml文件

    这里关联的是标签名为nginx-dep的pod

    [root@k8s-master01 ~]# vim nginx-svc.yml
    apiVersion: v1
    kind: Service
    metadata:
      name: nginx-svc
      namespace: default
      labels:
        app: nginx-svc
        
    spec:
      clusterIP: 10.96.139.8     #指定ClusterIP,不写此行会自动分配一个Service的ip
      type: ClusterIP   #可以不写此行,默认为ClusterIP模式
      selector:
        app: nginx-dep #关联的pod的标签名
      ports:
      - port: 8000
        protocol: TCP
        targetPort: 80
        
    #应用yaml清单
    kubectl apply -f nginx-svc.yml
    

    查看Pod的标签名

    kubectl get pods --show-labels
    kubectl get pod -l app=nginx-dep
    

    3.查看service,跟deployment,endpoints节点信息

    kubectl get svc,deploy,ep
    

    image-20211121171454976

    这里的endpoints指的是pod,可以查看pod的IP地址

    导出yaml文件

    kubectl get svc nginx-svc -o yaml> nginx-svc.yaml
    

    编辑修改资源

    kubectl edit svc nginx-svc
    

    Service服务发现

    关于Service的服务发现,我们来做个实验。

    这里就着前面搞的那个deloyment资源,原来是3个pod副本,我们改成2个副本

    kubectl get deploy
    
    #编辑my-dep资源
    kubectl edit deploy my-dep
    ...
    replicas: 2 #修改副本数为2
    ...
    
    #访问Service服务
    curl http://10.96.100.203:8000
    

    这时发现还是能正常访问Service服务,这就是Service的服务发现功能,自动感知pod扩容和裁剪,无需我们人工操作

    使用Service名访问

    前面我们是通过访问service的IP发现是可以负载均衡到后面的pod上的,我们也可以在容器内部访问service的名字达到一样的效果。

    kubectl get svc #得到Service名字my-dep
    kubectl get pods #查看pod
    kubectl exec -it my-dep-5b7868d854-crh94 -- /bin/bash #进入容器
    curl my-dep.default.svc:8000
    curl my-dep:8000
    

    image-20211121161100466

    此时的my-dep.default.svc:8000中的my-dep为服务名,default为命名空间,svc表示service,可简写为my-dep:8000,需要进入容器中访问才能用这种方式,因为有coredns解析,但在集群节点上无法使用,集群节点只能用Service_ip:8000的方式访问,因为没有dns解析

    2.2 NodePort类型

    在每台集群节点上都映射端口,这样访问集群任意节点的端口就能访问到pod提供的服务,这就不限于集群中了,集群外也能访问。

    下面我们来创建一个该类型的Service服务体验一下。

    1.使用命令创建方式

    定义一个名为my-dep的Deployment资源,里面包含有3个pod副本,每个pod中含有一个nginx容器。

    kubectl create deployment my-dep --image=nginx --replicas=3
    kubectl get deploy
    

    创建Service,指定类型为NodePort

    kubectl expose deploy my-dep --port=8000 --target-port=80 --type=NodePort --protocol=TCP
    

    查看当前的service

     kubectl get services
     kubectl get svc
    

    这里可以看到名为my-dep的Service使用了10.96.27.163的集群IP,暴露了一个8000端口,用于集群内部访问;还有一个38780端口,可用于集群外部访问,这个38780端口是在每个集群节点上都开了

    访问

    #在集群内部访问
    curl 10.96.27.163:8000
    
    #10.154.0.111是我的master的IP地址,其他两台是我的node节点
    #在集群外部使用浏览器访问http://10.154.0.111:38780
    #在集群外部使用浏览器访问http://10.154.0.112:38780
    #在集群外部使用浏览器访问http://10.154.0.113:38780
    

    注意,这个38780端口在任何一台集群节点上都开放了,该端口是随机暴露的,范围在30000-32767之间

    如果觉得访问集群节点加端口的方式不很方便,可以使用Nginx做负载均衡,或者Ingress控制器做负载均衡。

    2.使用yaml文件创建方式

    这里使用我们之前创建的Deployment关联的那2个pod,对了,每个pod可以关联多个Service,名为nginx-dep的pod之前关联了ClusterIP类型的Service,这里我们继续用它关联NodePort类型的Service。

    1.创建NodePort的Service

    [root@k8s-master01 ~]# vim nginx-svc2.yml
    apiVersion: v1
    kind: Service
    metadata:
      name: nginx-svc2
      namespace: default
      labels:
        app: nginx-svc2
        
    spec:
      type: NodePort   #这里改为NodePort模式
      selector:
        app: nginx-dep #关联的pod的标签名
      ports:
      - port: 8000
        protocol: TCP
        targetPort: 80
        
    #应用yaml清单
    kubectl apply -f nginx-svc2.yml
    

    2.查看service,跟deployment,endpoints节点信息

    kubectl get svc,deploy,ep
    

    image-20211122235313577

    如果不想使用集群IP加端口的方式访问,可以部署一台Nginx作为负载均衡器,这样能更加规范化。

    补充:

    我们也可以编辑之前ClusterIP类型创建的名为my-dep的Service,将其改为ClusterIP模式

    #编辑名为nginx-svc的Service
    kubectl edit service nginx-svc
    type: NodePort #修改type为NodePort
    
    #或者
    kubectl patch service nginx-svc -p '{"spec": {"type":"NodePort"}}'
    

    2.3 loadbalancer类型

    使用云厂商的提供的负载均衡器,关联到 loadbalancer类型的Service,这样我们就能在集群外访问云服务厂商的LB,访问到我们的pod服务了。

    访问流程:用户-->域名-->云服务提供端提供LB-->NodeIP:Port(serviceIP)-->Pod IP:端口

    image-20211115103559869

    具体方式就是修改Service的type: LoadBalancer,然后在云平台上创建一个LB,关联到该Service即可,我没有使用过厂商的云平台,这里可以自行查看文档,应该很简单的。

    参考资料:腾讯云LoadBalancer华为云LoadBalancer

    2.4 ExternalName类型

    ExternalName类型的Service,就是将该Service名跟集群外部服务地址做一个映射,使之访问Service名称就是访问外部服务。

    这里使用两个案例来介绍,一个是绑定外部域名,也可以绑定其他命名空间的域名。

    1.绑定集群外部域名

    把外部的服务绑定到集群内部的ExternalName类型的Service上,这样访问该Service名就能访问到外部服务,先来看一个小例子

    1.先创建一个带有curl命令的busybox的pod

    [root@k8s-master01 ~]# vim busybox.yml
    apiVersion: v1
    kind: Pod
    metadata:
      name: busybox
    spec:
      containers:
      - name: liveness
        image: yauritux/busybox-curl
        imagePullPolicy: IfNotPresent
        args:
        - /bin/sh
        - -c
        - sleep 30000000
    

    2.再创建一个ExternalName类型的Service,绑定的是外部域名www.k8sec.com

    [root@k8s-master01 ~]# vim externelname.yml
    apiVersion: v1
    kind: Service
    metadata:
      name: k8sec        
      namespace: default
    spec:
      type: ExternalName
      externalName: www.k8sec.com    # 对应的外部域名为www.k8sec.com
      
    #应用YAML文件
    kubectl apply -f externelname.yml
    

    3.查看Service

    kubectl get svc
    

    image-20211123150415292

    4.访问

    这时我们就能通过访问名为k8sec.default.svc的Service访问到www.k8sec.com

    #进入第一步创建的busybox
    kubectl exec -it busybox -- /bin/sh
    curl -I k8sec
    curl -I k8sec.default.svc
    

    image-20211123150558281

    5, 查看名为k8sec的Service的dns解析

    yum install -y bind-utils
    dig -t A k8sec.default.svc.cluster.local. @10.96.0.10 #这里10.96.0.10是集群dns地址
    

    可以看出其实是做了一个CNAME,CNAME就是域名映射,将名为k8sec.default.svc的Service映射到www.k8sec.com,我们访问这个Service名,就相当于访问到了www.k8sec.com

    这么做的好处就是,一旦外部服务发生了地址变更,如www.k8sec.com挂了,只需要修改Service中的externalName字段即可,对于集群内部是无感知的。

    同样我们也可以换成别的服务,如mysql服务,externalName字段改为mysql服务的VIP,这样集群内部就能通过Service名访问到外部的mysql服务了。

    2.绑定其他命名空间的域名

    这里要注意一点的是,不同命名空间的pod是可以直接通信的,我们使用ExternalName类型的Service本质上就是创建一个用于访问的别名。

    1.创建test命名空间

    kubectl create ns test
    

    2.在test命名空间中创建nginx的Service

    kubectl create deployment nginx --image=nginx -n test
    kubectl expose deploy nginx --port=80 --target-port=80 -n test
    

    3.在默认命名空间中创建一个busybox

    [root@k8s-master01 ~]# vim busybox.yml
    apiVersion: v1
    kind: Pod
    metadata:
      name: busybox
    spec:
      containers:
      - name: liveness
        image: yauritux/busybox-curl
        imagePullPolicy: IfNotPresent
        args:
        - /bin/sh
        - -c
        - sleep 30000000
    

    4.创建ExternalName类型的Service绑定test命名空间的Service

    apiVersion: v1
    kind: Service
    metadata:
      name: nginx-external
    spec:
      type: ExternalName
      externalName: nginx.test.svc.cluster.local 
      ports:
      - name: http
        port: 80
        targetPort: 80
    

    5.验证

    #进入处于默认命名空间的busybox中,访问nginx-external
    kubectl exec -it busybox -- /bin/sh
    curl -I nginx-external
    curl -I nginx-external.default.svc
    

    image-20211123154033953

    6.查看dns解析

    dig -t A nginx-external.default.svc.cluster.local.  @10.96.0.10
    

    还有一点要注意: 集群内的Pod会继承Node上的DNS解析规则。所以只要Node可以访问的服务,Pod中也可以访问到, 这就实现了集群内服务访问集群外服务

    参考资料:ExternalNames说明ExternalNames案例

    三、补充

    3.1 sessionAffinity

    可以将来自同一个客户端的请求始终转发至同一个后端的Pod对象,用于pod的会话保持,就是第一次访问谁,后面都访问这个节点。

    举个例子,如果你使用账号密码登录,不使用会话保持,那么每次刷新页面就会重新登录一次,因为http是无状态的,每次请求都没有联系。

    使用了会话保持就能基于客户端IP地址识别客户端身份,不再每次访问就登录一次。

    跟nginx的ip_hash算法类似。

    1.设置sessionAffinity为Clientip,启用sessionAffinity

    #直接修改service里的参数
    kubectl patch svc my_service -p '{"spec": {"sessionAffinity":"ClientIP"}}'
    
    #或者
    kubectl edit svc my-service
    ...
    sessionAffinity: ClientIP
    
    #取消sessionAffinity 
    kubectl patch svc my_service -p '{"spec": {"sessionAffinity":"None"}}'
    

    3.2 headless service

    普通的ClusterIP service是service name解析为cluster ip,然后cluster ip对应到后面的pod ip

    而无头service是指service name 直接解析为后面的pod ip,也就是说没有ClusterIP了

    1, 编写YAML文件

    [root@k8s-master01 ~]# vim headless-service.yml
    apiVersion: v1
    kind: Service
    metadata:
       name: headless-service
       namespace: default
    spec:
       clusterIP: None                                  # None就代表是无头service
       type: ClusterIP                                  # ClusterIP类型,也是默认类型
       ports:                                                # 指定service 端口及容器端口
       - port: 80                                          # service ip中的端口
         protocol: TCP
         targetPort: 80                                 # pod中的端口
       selector:                                           # 指定后端pod标签
         app: nginx                     # 可通过kubectl get pod -l app=nginx查看哪些pod在使用此标签
    

    注意这里的 clusterIP: None 是没有IP的

    2, 应用YAML文件创建无头服务

    kubectl apply -f headless-service.yml
    

    3, 验证

    kubectl get svc
    

    可以看到headless-service没有CLUSTER-IP,用None表示

    image-20211123161235281

    我们查看headless-service的DNS解析发现解析到了如下两个IP地址

    dig -t A headless-service.default.svc.cluster.local. @10.96.0.10
    

    image-20211123161335174

    接着查看pod的ip会发现,这两个IP是一致的,也就是说headless service做dns解析是直接解析到pod的

     kubectl get pods -o wide
    

    image-20211123161553372

    需要注意的是对这类 Service 并不会分配 Cluster IP,kube-proxy 不会处理它们,而且集群也不会为它们进行负载均衡和路由。

    这种Service主要用于statefulset控制器,用于部署有状态的pod应用,以后学到了再说吧。

    参考资料:Service详解

    3.3 kube-DNS

    DNS服务监视Kubernetes API,为每一个Service创建DNS记录用于域名解析

    headless service需要DNS来解决访问问题

    1, 查看kube-dns服务的IP

    kubectl get svc -n kube-system
    #查看到coreDNS的服务地址是10.96.0.10
    

    image-20211123162412708

    四、参考资料

    黑马Linux-k8s第三天视频

    尚硅谷云原生课程-p54-p56

    今天的学习是为了以后的工作更加的轻松!
  • 相关阅读:
    Solr4.2 新特性 DocValues [转]
    Facet with Lucene
    Lucene 4.8
    Lucene 4.3
    lucene 4.0
    美团搜索-搜索引擎关键字智能提示的一种实现[转]
    C# 多线程编程,传参,接受返回值
    C# WebService服务器搭建、发布、上线、调试
    无线网卡连接网络后共享给本地有线网卡使用(Win10)
    C# 创建Dll文件供程序调用方法
  • 原文地址:https://www.cnblogs.com/tz90/p/15593814.html
Copyright © 2020-2023  润新知