• Kubernetes Service


    Kubernetes Service 定义了这样一种抽象:一个 Pod 的逻辑分组,一种可以访问它们的策略(通常称为微服务)。 这一组 Pod 能够被 Service 访问到,通常是通过 Label Selector现的。
    对 Kubernetes 集群中的应用,Kubernetes 提供了简单的 Endpoints API,只要 Service 中的一组 Pod 发生变更,应用程序就会被更新。 对非 Kubernetes 集群中的应用,Kubernetes 提供了基于 VIP 的网桥的方式访问 Service,再由 Service 重定向到 backend Pod。

    定义 Service

    一个 Service 在 Kubernetes 中是一个 REST 对象,和 Pod 类似。 像所有的 REST 对象一样, Service 定义可以基于 POST 方式,请求 apiserver 创建新的实例。

    kind: Service
    apiVersion: v1
    metadata:
      name: my-service
    spec:
      selector:
        app: MyApp
      ports:
        - protocol: TCP
          port: 80
          targetPort: 9376
    

    创建一个名称为 “my-service” 的 Service 对象,它会将请求代理到使用 TCP 端口 9376,并且具有标签 "app=MyApp" 的 Pod 上。这个 Service 将被指派一个 IP 地址(通常称为 “Cluster IP”),它会被服务的代理使用。 该 Service 的 selector 将会持续评估,处理结果将被 POST 到一个名称为 “my-service” 的 Endpoints 对象上。Service 能够将一个接收端口映射到任意的 targetPort。
    Kubernetes Service 能够支持 TCP 和 UDP 协议,默认 TCP 协议。

    没有 selector 的 Service

    Servcie 抽象了该如何访问 Kubernetes Pod,但也能够抽象其它类型的 backend:

    • 希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。
    • 希望服务指向另一个 Namespace 中或其它集群中的服务。
    • 正在将工作负载转移到 Kubernetes 集群,和运行在 Kubernetes 集群之外的 backend。

    在任何这些场景中,都能够定义没有 selector 的 Service :

    kind: Service
    apiVersion: v1
    metadata:
      name: my-service
    spec:
      ports:
        - protocol: TCP
          port: 80
          targetPort: 9376
    

    由于这个 Service 没有 selector,就不会创建相关的 Endpoints 对象。可以手动将 Service 映射到指定的 Endpoints:

    kind: Endpoints
    apiVersion: v1
    metadata:
      name: my-service
    subsets:
      - addresses:
          - ip: 1.2.3.4
        ports:
          - port: 9376
    

    访问没有 selector 的 Service,与有 selector 的 Service 的原理相同。请求将被路由到用户定义的 Endpoint(该示例中为 1.2.3.4:9376)。

    VIP 和 Service 代理

    在 Kubernetes 集群中,每个 Node 运行一个 kube-proxy 进程。kube-proxy 负责为 Service 实现了一种 VIP(虚拟 IP)的形式,而不是 ExternalName 的形式。

    userspace 代理模式

    kube-proxy 会监视 Kubernetes master 对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会在本地 Node 上打开一个端口(随机选择)。 任何连接到“代理端口”的请求,都会被代理到 Service 的backend Pods 中的某个上面(如 Endpoints 所报告的一样)。 使用哪个 backend Pod,是基于 Service 的 SessionAffinity 来确定的。 最后,它按照iptables 规则,捕获到达该 Service 的 clusterIP(是虚拟 IP)和 Port 的请求,并重定向到代理端口,代理端口再代理请求到 backend Pod。
    网络返回的结果是,任何到达 Service 的 IP:Port 的请求,都会被代理到一个合适的 backend,不需要客户端知道关于 Kubernetes、Service、或 Pod 的任何信息。

    默认的策略是,通过 round-robin 算法来选择 backend Pod。 实现基于客户端 IP 的会话亲和性,可以通过设置 service.spec.sessionAffinity 的值为 "ClientIP" (默认值为 "None")。

    iptables 代理模式

    在k8s v1.2版本之前默认使用userspace提供vip代理服务,从 Kubernetes v1.2 起,默认是使用 iptables 代理。

    kube-proxy 会监视 Kubernetes master 对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会创建相关 iptables 规则,从而捕获到达该 Service 的 clusterIP(虚拟 IP)和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某个上面。 对于每个 Endpoints 对象,它也会创建 iptables 规则,这个规则会选择一个 backend Pod。默认的策略是,随机选择一个 backend。 实现基于客户端 IP 的会话亲和性,可以将 service.spec.sessionAffinity 的值设置为 "ClientIP" (默认值为 "None")。
    和 userspace 代理类似,网络返回的结果是,任何到达 Service 的 IP:Port 的请求,都会被代理到一个合适的 backend,不需要客户端知道关于 Kubernetes、Service、或 Pod 的任何信息。 这应该比 userspace 代理更快、更可靠。然而,不像 userspace 代理,如果初始选择的 Pod 没有响应,iptables 代理能够自动地重试另一个 Pod,所以它需要依赖 readiness probes。

    多端口 Service

    很多 Service 需要暴露多个端口。对于这种情况,Kubernetes 支持在 Service 对象中定义多个端口。 当使用多个端口时,必须给出所有的端口的名称,这样 Endpoint 就不会产生歧义:

    kind: Service
    apiVersion: v1
    metadata:
      name: my-service
    spec:
        selector:
          app: MyApp
        ports:
          - name: http
            protocol: TCP
            port: 80
            targetPort: 9376
          - name: https
            protocol: TCP
            port: 443
            targetPort: 9377
    

    服务发现

    Kubernetes 支持2种基本的服务发现模式 —— 环境变量和 DNS。

    环境变量

    当 Pod 运行在 Node 上,kubelet 会为每个活跃的 Service 添加一组环境变量。 它同时支持 Docker links兼容变量(查看 makeLinkVariables)、简单的 {SVCNAME}_SERVICE_HOST 和 {SVCNAME}_SERVICE_PORT 变量,这里 Service 的名称需大写,横线被转换成下划线。
    一个名称为 "redis-master" 的 Service 暴露了 TCP 端口 6379,同时给它分配了 Cluster IP 地址 10.0.0.11,这个 Service 生成了如下环境变量:

    REDIS_MASTER_SERVICE_HOST=10.0.0.11
    REDIS_MASTER_SERVICE_PORT=6379
    REDIS_MASTER_PORT=tcp://10.0.0.11:6379
    REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
    REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
    REDIS_MASTER_PORT_6379_TCP_PORT=6379
    REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11
    

    Pod 想要访问的任何 Service 必须在 Pod 自己之前被创建,否则这些环境变量就不会被赋值。

    DNS

    DNS 服务器监视着创建新 Service 的 Kubernetes API,从而为每一个 Service 创建一组 DNS 记录。 如果整个集群的 DNS 一直被启用,那么所有的 Pod 应该能够自动对 Service 进行名称解析。
    Kubernetes 也支持对端口名称的 DNS SRV(Service)记录。 如果名称为 "my-service.my-ns" 的 Service 有一个名为 "http" 的 TCP 端口,可以对 "_http._tcp.my-service.my-ns" 执行 DNS SRV 查询,得到 "http" 的端口号。
    Kubernetes DNS 服务器是唯一的一种能够访问 ExternalName 类型的 Service 的方式。
    Kubernetes 从 1.3 版本起, DNS 是内置的服务,通过插件管理器 集群插件自动被启动。Kubernetes DNS 在集群中调度 DNS Pod 和 Service ,配置 kubelet 以通知个别容器使用 DNS Service 的 IP 解析 DNS 名字。

    发布服务--type类型

    对一些应用希望通过外部(Kubernetes 集群外部)IP 地址暴露 Service。
    Kubernetes ServiceTypes 允许指定一个需要的类型的 Service,默认是 ClusterIP 类型。
    Type 的取值以及行为如下:

    • ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。
    • NodePort:通过每个 Node 上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过求 :,可以从集群的外部访问一个 NodePort 服务。如果设置 type 的值为 "NodePort",Kubernetes master 将从给定的配置范围内(默认:30000-32767)分配端口,每个 Node 将从该端口(每个 Node 上的同一端口)代理到 Service。该端口将通过 Service 的 spec.ports[*].nodePort 字段被指定。如果需要指定的端口号,可以配置 nodePort 的值。
    • LoadBalancer:使用云提供商的负载局衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务。
    • ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com)。 没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。

    k8s中有3种IP地址:

    • Node IP:Node节点的IP地址,这是集群中每个节点的物理网卡的IP地址;
    • Pod IP:Pod的IP地址,这是Docker Engine根据docker0网桥的IP地址段进行分配的,通常是一个虚拟的二层网络;
    • Cluster IP:Service 的IP地址,这也是一个虚拟的IP,但它更像是一个“伪造”的IP地址,因为它没有一个实体网络对象,所以无法响应ping命令。它只能结合Service Port组成一个具体的通信服务端口,单独的Cluster IP不具备TCP/IP通信的基础。在k8s集群之内,Node IP网、Pod IP网与Cluster IP网之间的通信采用的是k8s自己设计的一种编程实现的特殊的路由规则,不同于常见的IP路由实现。

    Service的基本用法

    在集群中暴露 Pod

    run-my-nginx.yaml

    apiVersion: apps/v1beta1
    kind: Deployment
    metadata:
      name: my-nginx
    spec:
      replicas: 2
      template:
        metadata:
          labels:
            run: my-nginx
        spec:
          containers:
          - name: my-nginx
            image: nginx
            ports:
            - containerPort: 80
    

    创建及查看pod:

    $ kubectl create -f ./run-my-nginx.yaml
    $ kubectl get pods -l run=my-nginx -o wide
    NAME                        READY     STATUS    RESTARTS   AGE       IP            NODE
    my-nginx-3800858182-jr4a2   1/1       Running   0          13s       10.244.3.4    kubernetes-minion-905m
    my-nginx-3800858182-kna2y   1/1       Running   0          13s       10.244.2.5    kubernetes-minion-ljyd
    $ kubectl get pods -l run=my-nginx -o yaml | grep podIP
        podIP: 10.244.3.4
        podIP: 10.244.2.5
    

    直接通过Pod的IP地址和端口号可以访问容器内的应用服务,但是Pod的IP地址是不可靠的,如果容器应用本身是分布式的部署方式,通过多个实例共同提供服务,就需要在这些实例的前端设置一个负载均衡器来实现请求的分发。kubernetes中的Service就是设计出来用于解决这些问题的核心组件。

    创建 Service

    创建 Service后每个 Service 被分配一个唯一的 IP 地址(也称为 clusterIP)。 这个 IP 地址与一个 Service 的生命周期绑定在一起,当 Service 存在的时候它也不会改变。
    使用 kubectl expose 为 2个 Nginx 副本创建一个 Service:

    $ kubectl expose deployment/my-nginx
    service "my-nginx" exposed
    

    这等价于使用 kubectl create -f 命令创建,对应如下的 yaml 文件:

    apiVersion: v1
    kind: Service
    metadata:
      name: my-nginx
      labels:
        run: my-nginx
    spec:
      ports:
      - port: 80
        protocol: TCP
      selector:
        run: my-nginx
    

    创建一个 Service,对应具有标签 run: my-nginx 的 Pod,目标 TCP 端口 80,并且在一个抽象的 Service 端口(targetPort:容器接收流量的端口;port:抽象的 Service 端口,可以使任何其它 Pod 访问该 Service 的端口)上暴露。

    $ kubectl get svc my-nginx
    NAME       CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
    my-nginx   10.0.162.149   <none>        80/TCP    21s
    $ kubectl describe svc my-nginx
    Name:                my-nginx
    Namespace:           default
    Labels:              run=my-nginx
    Selector:            run=my-nginx
    Type:                ClusterIP
    IP:                  10.0.162.149
    Port:                <unset> 80/TCP
    Endpoints:           10.244.2.5:80,10.244.3.4:80
    Session Affinity:    None
    No events.
    
    $ kubectl get ep my-nginx
    NAME       ENDPOINTS                     AGE
    my-nginx   10.244.2.5:80,10.244.3.4:80   1m
    

    访问 Service

    环境变量
    当 Pod 在 Node 上运行时,kubelet 会为每个活跃的 Service 添加一组环境变量。创建pod副本先于 Service。

    $ kubectl exec my-nginx-3800858182-jr4a2 -- printenv | grep SERVICE
    KUBERNETES_SERVICE_HOST=10.0.0.1
    KUBERNETES_SERVICE_PORT=443
    KUBERNETES_SERVICE_PORT_HTTPS=443
    //避免调度器可能在同一个机器上放置所有 Pod,如果该机器宕机则所有的 Service 都会挂掉,掉 2 个 Pod,等待 Deployment 去创建它们。 这次 Service 会 先于 副本存在。
    $ kubectl scale deployment my-nginx --replicas=0; kubectl scale deployment my-nginx --replicas=2;
    
    $ kubectl get pods -l run=my-nginx -o wide
    NAME                        READY     STATUS    RESTARTS   AGE     IP            NODE
    my-nginx-3800858182-e9ihh   1/1       Running   0          5s      10.244.2.7    kubernetes-minion-ljyd
    my-nginx-3800858182-j4rm4   1/1       Running   0          5s      10.244.3.8    kubernetes-minion-905m
    

    DNS
    Kubernetes 提供了一个 DNS 插件 Service,它使用 skydns 自动为其它 Service 指派 DNS 名字。 如果它在集群中处于运行状态,可以通过如下命令来检查:

    $ kubectl get services kube-dns --namespace=kube-system
    NAME       CLUSTER-IP   EXTERNAL-IP   PORT(S)         AGE
    kube-dns   10.0.0.10    <none>        53/UDP,53/TCP   8m
    $ kubectl run curl --image=radial/busyboxplus:curl -i --tty
    Waiting for pod default/curl-131556218-9fnch to be running, status is Pending, pod ready: false
    Hit enter for command prompt
    [ root@curl-131556218-9fnch:/ ]$ nslookup my-nginx
    Server:    10.0.0.10
    Address 1: 10.0.0.10
    
    Name:      my-nginx
    Address 1: 10.0.162.149
    

    Service 安全

    现在修改 Nginx 副本,启动一个使用在秘钥中的证书的 https 服务器和 Servcie,都暴露端口(80 和 443):
    nginx-secure-app.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: my-nginx
      labels:
        run: my-nginx
    spec:
      type: NodePort
      ports:
      - port: 8080
        targetPort: 80
        protocol: TCP
        name: http
      - port: 443
        protocol: TCP
        name: https
      selector:
        run: my-nginx
    ---
    apiVersion: apps/v1beta1
    kind: Deployment
    metadata:
      name: my-nginx
    spec:
      replicas: 1
      template:
        metadata:
          labels:
            run: my-nginx
        spec:
          volumes:
          - name: secret-volume
            secret:
              secretName: nginxsecret
          containers:
          - name: nginxhttps
            image: bprashanth/nginxhttps:1.0
            ports:
            - containerPort: 443
            - containerPort: 80
            volumeMounts:
            - mountPath: /etc/nginx/ssl
              name: secret-volume
    
    • 在相同的文件中包含了 Deployment 和 Service 的规格
    • Nginx server 处理 80 端口上的 http 流量,以及 443 端口上的 https 流量,Nginx Service 暴露了这两个端口。
    • 每个容器访问挂载在 /etc/nginx/ssl 卷上的秘钥
    $ kubectl delete deployments,svc my-nginx; kubectl create -f ./nginx-secure-app.yaml
    $ kubectl get pods -o yaml | grep -i podip
        podIP: 10.244.3.5
    node $ curl -k https://10.244.3.5
    ...
    <h1>Welcome to nginx!</h1>
    

    暴露 Service

    Kubernetes 支持两种实现方式:NodePort 和 LoadBalancer。
    1)通过设置nodePort映射到物理机,同时设置Service的类型为NodePort
    webapp-svc-nodeport.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: webapp-nodeport
    spec:
      type: NodePort
      ports:
      - port: 8090
        targetPort: 8080
        nodePort: 8090
      selector:
        app: webapp
    

    创建这个Service:

    # kubectl create -f webapp-svc-nodeport.yaml
    service "webapp-nodeport" created
    # kubectl get svc
    NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)         AGE
    kubernetes        ClusterIP   10.10.10.1     <none>        443/TCP         22d
    mysql             ClusterIP   10.10.10.200   <none>        3306/TCP        22d
    webapp-nodeport   NodePort    10.10.10.191   <none>        8090:8090/TCP   11s
    # kubectl get pod -o wide
    NAME                   READY     STATUS    RESTARTS   AGE       IP           NODE
    dapi-test-pod-volume   1/1       Running   4          12d       172.17.0.7   10.0.2.6
    pod-affinity           1/1       Running   3          11d       172.17.0.4   10.0.2.6
    webapp                 1/1       Running   0          15m       172.17.0.2   10.0.2.6
    webapp-hostnetwork     1/1       Running   0          8m        10.0.2.10    10.0.2.10
    

    通过物理机的IP和端口访问:

    # curl 10.0.2.6:8090
    

    2)通过设置LoadBalancer映射到云服务商提供的LoadBalancer地址
    status.loadBalancer.ingress.ip设置的1.2.3.4为云服务商提供的负载均衡器的IP地址。对该Service的访问请求将会通过LoadBalancer转发到后端的Pod上,负载分发的实现方式依赖云服务商提供的LoadBalancer的实现机制。
    只需要将 Service 的 Type 由 NodePort 改成 LoadBalancer。

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
    spec:
      selector:
        app: Myapp
      ports:
      - protocol: TCP
        port: 80
        targetPort: 9376
        nodePort: 30061
      clusterIP: 10.0.171.239
      loadBalancerIP: 1.1.1.1
      type: LoadBalancer
    status:
      loadBalancer:
        ingree:
        - ip: 1.2.3.4
    
  • 相关阅读:
    Intel CPU编号详解
    matplotlib(二)——matplotlib控制坐标轴第一个刻度到原点距离
    matplotlib(一)——matplotlib横轴坐标密集字符覆盖
    博客园定制页面(五)——使用自定义JS脚本(公告栏显示时间)
    PyCharm(二)——PyCharm打开本地项目不显示项目文件
    nohup命令
    URL是如何解析的
    Python 得到主机字节序
    Python 得到ISP地址
    亚马逊s3上传大文件Python
  • 原文地址:https://www.cnblogs.com/aresxin/p/Kubernetes-Service.html
Copyright © 2020-2023  润新知