• Kubernetes Service简介


    为什么需要Service?

    在 K8s 集群里面会通过 pod 去部署应用,与传统的应用部署不同,传统应用在给定的机器上面去部署,我们知道怎么去调用别的机器的 IP 地址。但是在 K8s 集群里面应用是通过 pod 去部署的, 而 pod 生命周期是短暂的。在 pod 的生命周期过程中,比如它创建或销毁,它的 IP 地址都会发生变化,这样就不能使用传统的部署方式,不能指定Pod IP 去访问指定的应用。

    另外在 K8s 的应用部署里,之前虽然学习了 deployment 的应用部署模式,但还是需要创建一个 pod 组,然后这些 pod 组需要提供一个统一的访问入口,以及怎么去控制流量负载均衡到这个组里面。比如说测试环境、预发环境和线上环境,其实在部署的过程中需要保持同样的一个部署模板以及访问方式。因为这样就可以用同一套应用的模板在不同的环境中直接发布。

    什么是Service?

    Service服务是Kubernetes里的核心资源对象之一,Kubernetes里的每个Service其实就是我们经常提起的微服务架构中的一个微服务。Service向上提供了外部网络的访问和Pod网络的访问;向下则通过负载均衡把请求分配到不同的Pod中去,如下图所示:

    由于每个 Pod 都会被分配一个单独的 IP 地址,而且每个Pod都提供了一个独立的Endpoint(所谓Endpoint,即:Pod IP+Container Port)以被客户端访问。现在通过Deployment的方式部署,会创建多个Pod副本,那么客户端该如何访问它们呢?

    运行在每个Node上的 kube-proxy 进程起到了负载均衡的作用,负责把对Service的请求转发到后端的某个Pod实例上,并在内部实现服务的负载均衡与会话保持机制。每个Service都被分配了一个全局唯一的虚拟IP地址,这个虚拟IP被称为Cluster IP。这样一来,每个服务就变成了具备唯一IP地址的通信节点,服务调用就变成了最基础的TCP网络通信问题。随着Pod的销毁和重新创建,新Pod的IP地址与之前旧Pod的不同。而Service一旦被创建,Kubernetes就会自动为它分配一个可用的Cluster IP,而且在Service的整个生命周期内,它的Cluster IP不会发生改变。

    集群内访问Service

    在集群里面,其他 pod 要怎么访问到我们所创建的这个 service 呢?有三种方式:

    • 通过 service 的虚拟 IP 去访问。
    • 直接访问服务名,依靠 DNS 解析。
    • 通过环境变量访问,在同一个 namespace 里的 pod 启动时,K8s 会把 service 的一些 IP 地址、端口,以及一些简单的配置,通过环境变量的方式放到 K8s 的 pod 里面。

    (具体的通过下面的演示加以说明)

    Headless Service

    在某些应用场景中,开发人员希望自己控制负载均衡的策略,不使用Service提供的默认负载均衡的功能,或者应用程序希望知道属于同组服务的其他实例。Kubernetes提供了Headless Service来实现这种功能, 即不为Service设置ClusterIP(入口IP地址),仅通过Label Selector将后端的Pod列表返回给调用的客户端。例如:

    apiVersion: v1
    kind: Service
    metadata:
      labels: 
        run: nginx
      name: nginx  
    spec:
      ports: 
      - port: 80  
        protocol: TCP
      clusterIP: None  # 表示不分配 clusterIP
      selector:  
        run: nginx
    

    这样,Service就不再具有一个特定的ClusterIP地址,对其进行访问将获得包含Label“app=nginx”的全部Pod列表,然后客户端程序自行决定 如何处理这个Pod列表。

    向集群外暴露Service

    前面介绍的都是在集群里面 node 或者 pod 去访问 service,service 怎么去向外暴露呢?怎么把集群内的应用暴露给公网去访问呢?这里 service 也有两种类型去解决这个问题,一个是 NodePort,一个是 LoadBalancer。

    在继续讲解之前,需要先弄明白Kubernetes里的3种IP,这3种IP分别如下:

    • Node IP:Node 的 IP 地址。Node IP 是 k8s 集群中每个节点的物理网卡的 IP 地址,是一个真实存在的物理网络。集群之外的节点访问集群内的某个节点或某个服务时,都必须通过 Node IP 通信。
    • Pod IP:Pod 的 IP 地址。它是Docker Engine根据docker0 网桥的IP地址段进行分配的,通常是一个虚拟的二层网络。Kubernetes里一个Pod里的容器访问另外一个Pod里的容器时,就是通过 Pod IP所在的虚拟二层网络进行通信的,而真实的TCP/IP流量是通过 Node IP所在的物理网卡流出的。
    • Cluster IP:Service 的 IP 地址,也是一种虚拟的IP。Cluster IP仅仅作用于Kubernetes Service这个对象,并由 Kubernetes管理和分配IP地址,它不存在一个“实体网络对象”与之对应,和我们熟知的网络很不一样,因此单独的 Cluster IP 也不具备 TCP/IP 通信基础。它只属于k8s集群这个封闭的空间,集群之外的节点无法直接使用Cluster IP进行访问。(搞清楚这一点至关重要,由此也才会引出“服务暴露”之类的问题)

    理解了Cluster IP后,回到“外部系统如何访问Service”这个问题中来,即如何理解 NodePort 和 LoadBalancer?

    NodePort 方式

    这种方式就是暴露出节点上的一个端口,这样相当于在节点的一个端口上面访问到之后就会再去做一层转发,转发到虚拟的 IP 地址上面,就是刚刚宿主机上面 service 虚拟 IP 地址。

    apiVersion: v1
    kind: Service
    metadata:
      labels: 
        run: nginx
      name: nginx  
    spec:
    	type: NodePort  # 新增Service Type为NodePort(默认为ClusterIP) 
      ports: 
      - port: 80 
        protocol: TCP
        targetPort: 80  
        nodePort: 81 # 
      selector:  
        run: nginx
    

    通过物理机的 IP 地址和 nodePort 81端口号访问服务,就可以访问到某一个被代理的 Pod 的 targetPort 80 端口了。

    LoadBalancer 方式

    这种方式适用于公有云上的 Kubernetes 服务,对应的 Service.yaml 如下:

    apiVersion: v1
    kind: Service
    metadata:
      labels: 
        run: nginx
      name: nginx  
    spec:
      ports: 
      - port: 80 
        protocol: TCP
        targetPort: 80  
      selector:  
        run: nginx
      type: LoadBalancer  # 新增Service Type为LoadBalancer(默认为ClusterIP)
    

    该Service的访问请求将会通过LoadBalancer转发到后端Pod上,负载分发的实现方式则依赖于云服务商提供的LoadBalancer的实现机制。(具体的暂不理解)

    示例演示

    实验步骤:

    1、创建一个Deployment 来产生一组服务pod

    2、创建一个Service 来负载均衡这一组Pod

    3、在集群中创建一个Pod以不同的方式来访问Service

    4、修改服务类型,通过NodePort和Loadbalancer类型来暴露服务到外部

    所需的文件包括service.yaml 和deploy.yaml,如下:

    Service定义文件,service.yaml

    apiVersion: v1
    kind: Service
    metadata:
      labels: 
        run: nginx
      name: nginx  # 定义服务的名称
    spec:
      ports: # 表示通过前端(port)的80端口均衡负载到后端(targetPort)的80端口,默认targetPort与port相同
      - port: 80  # port属性定义了Service的虚端口
        protocol: TCP
        targetPort: 80  # 即具体业务进程在容器内的targetPort上提供TCP/IP接入
      selector:  # 表示拥有“run=nginx”的label的pod都隶属于这个Service
        run: nginx
    

    Deployment定义文件,deploy.yaml

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        run: nginx 
      name: nginx
    spec:
      replicas: 2
      selector:
        matchLabels:
          run: nginx
      template:
        metadata:
          labels:
            run: nginx # 拥有“run=nginx”的label的pod都隶属于之前创建的 nginx Service
        spec:
          containers:
          - image: nginx:alpine
            name: nginx
            imagePullPolicy: IfNotPresent
    

    1)创建Deployment

    # kubectl create -f deploy.yaml
    

    创建好后,通过kubectl get查看相应相应资源是否已经创建完成,如下:

    # kubectl get pod -o wide -l run=nginx
    NAME                     READY   STATUS    RESTARTS   AGE   IP                NODE        NOMINATED NODE   READINESS GATES
    nginx-686449ff6b-4dclq   1/1     Running   0          14h   192.168.125.175   k8sslave2   <none>           <none>
    nginx-686449ff6b-n5gsz   1/1     Running   0          14h   192.168.157.89    k8sslave1   <none>           <none>
    

    可以看到这两个pod暴露出来的IP分别是192.168.125.175 和 192.168.157.89,并且分别运行在slave2和slave1节点上。

    2)创建Service

    #kubectl create -f service.yaml
    

    创建好后,通过kubectl get查看一下是否成功创建

    # kubectl get svc
    NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
    kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP   119s # 这个是系统默认的
    nginx        ClusterIP   10.102.53.115   <none>        80/TCP    111s # 这是刚刚创建好的
    

    我们看一下其具体信息,如下:

    # kubectl describe svc nginx
    Name:              nginx
    Namespace:         default
    Labels:            run=nginx
    Annotations:       <none>
    Selector:          run=nginx
    # 注意此时的服务类型是 ClusterIP,也就是只允许集群内部访问,不对外暴露服务
    Type:              ClusterIP
    # 这个IP就是服务发现的IP(通过访问这个虚拟IP地址来访问服务,由负载均衡机制分配到后端的相应Pod上)
    IP:                10.102.53.115
    Port:              <unset>  80/TCP
    TargetPort:        80/TCP
    # endpoint就是Pod IP:Container Port,这个Pod IP就是上面已经查看过的
    Endpoints:         192.168.125.175:80,192.168.157.89:80
    Session Affinity:  None
    Events:            <none>
    

    3)创建一个Pod作为client来访问这个服务(通过前面所说的3种方式)

    首先,我们随意创建一个Pod(这步未显示),并进入相应的容器中。

    # kubectl exec -it sise-7cfd9b6578-55h4w bash
    

    在该容器内部访问Service,执行curl 10.102.53.115(这里的ip就是nigix这个Service的cluster ip),但是显示 curl 命令未安装,故使用wget命令。

    方式1:直接通过IP地址访问

    root@sise-7cfd9b6578-55h4w:/usr/src/app# wget -O- 10.102.53.115
    

    输出如下,表示正常访问。

    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    <style>
        body {
             35em;
            margin: 0 auto;
            font-family: Tahoma, Verdana, Arial, sans-serif;
        }
    </style>
    </head>
    <body>
    <h1>Welcome to nginx!</h1>
    <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p>
    

    方式2:通过服务名称进行访问

    root@sise-7cfd9b6578-55h4w:/usr/src/app# wget -O- nginx
    

    输出如下:它会自动把服务名称解析成对应的虚拟IP

    --2019-11-11 05:44:03--  http://nginx/
    Resolving nginx (nginx)... 10.102.53.115
    Connecting to nginx (nginx)|10.102.53.115|:80... connected.
    HTTP request sent, awaiting response... 200 OK
    ...
    

    方式3:通过环境变量的方式进行访问

    先通过env命令查看一下环境变量是否已经成功设置:

    root@sise-7cfd9b6578-55h4w:/usr/src/app# env
    HOSTNAME=sise-7cfd9b6578-55h4w
    GPG_KEY=C01E1CAD5EA2C4F0B8E3571504C367C218ADD4FF
    KUBERNETES_PORT=tcp://10.96.0.1:443
    KUBERNETES_PORT_443_TCP_PORT=443
    TERM=xterm
    KUBERNETES_SERVICE_PORT=443
    KUBERNETES_SERVICE_HOST=10.96.0.1
    NGINX_SERVICE_HOST=10.102.53.115 # 说明已经成功注入
    NGINX_PORT_80_TCP_PROTO=tcp
    ...
    

    然后执行wget -O- $NGINX_SERVICE_HOST,效果是一样的。

    4)修改服务类型,从集群外部访问服务

    上面实验中的Service Type是ClusterIP,现在把它修改成LoadBalancer,修改Service.yaml文件如下:

    apiVersion: v1
    kind: Service
    metadata:
      labels: 
        run: nginx
      name: nginx  
    spec:
      ports: 
      - port: 80 
        protocol: TCP
        targetPort: 80  
      selector:  
        run: nginx
      type: LoadBalancer  # 新增Service Type为LoadBalancer(默认为ClusterIP) 
    

    然后更新该service:

    #kubectl apply -f service.yaml
    

    现在再看一下这个service有什么不同

    # kubectl get svc nginx -o wide
    NAME    TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE   SELECTOR
    nginx   LoadBalancer   10.102.53.115   <pending>     80:31338/TCP   49m   run=nginx
    

    发现,TYPE 字段已经变成了 LoadBalancer,但 EXTERNAL-IP 字段的值一直没出来,这是因为,我的集群环境是在本机的虚拟机中搭建的,关于这个问题详细解释,详见kubernetes-service-external-ip-pending

    如果在云厂商提供的环境中搭建的集群,那么就会正常显示EXTERNAL-IP,然后可以从外部直接访问这个EXTERNAL-IP,就可以访问pod提供的服务了。

    最后,需要强调的是,pod的生命周期和Service生命周期是没有关系的,我们可以delete掉当前这两个Pod,Deployment根据副本数会自动帮我们新创建两个pod,这时候新创建的pod和刚刚被删掉的pod的pod ip是不一样的,但Service对外提供的虚拟IP却是始终没变过。这也就是为什么要抽象出Service的原因之一。

  • 相关阅读:
    编写通用shell脚本启动java项目,适用于多数服务,只需修改服务名即可
    linux操作系统安装jdk
    搭建dubbo+zookeeper+dubboadmin分布式服务框架(windows平台下)
    二.传统服务的拆分策略
    一.把传统服务做成dubbo分布式服务架构的步骤
    Android IOS WebRTC 音视频开发总结(六)-- iOS开发之含泪经验
    Android WebRTC 音视频开发总结(五)-- webrtc开发原型
    Android WebRTC 音视频开发总结(四)-- webrtc传输模块
    Android WebRTC 音视频开发总结(三)-- 信令服务和媒体服务
    Android WebRTC 音视频开发总结(二)-- webrtcdemo介绍
  • 原文地址:https://www.cnblogs.com/kkbill/p/13400053.html
Copyright © 2020-2023  润新知