zoukankan      html  css  js  c++  java
  • 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
    
  • 相关阅读:
    2015531 网络攻防 Exp1 PC平台逆向破解(5)M
    2017-2018-1 20155331 嵌入式C语言
    20155330 《网络对抗》 Exp9 web安全基础实践
    20155330 《网络对抗》 Exp8 Web基础
    20155330 《网络对抗》 Exp7 网络欺诈防范
    20155330 《网络对抗》 Exp6 信息搜集与漏洞扫描
    20155330 《网络对抗》 Exp5 MSF基础应用
    20155330 《网络攻防》 Exp4 恶意代码分析
    20155330 《网络攻防》 Exp3 免杀原理与实践
    20155330 《网络对抗》 Exp2 后门原理与实践
  • 原文地址:https://www.cnblogs.com/aresxin/p/Kubernetes-Service.html
Copyright © 2011-2022 走看看