Kubernetes-Service

1. 简介

kubernets service 是将运行一组pods上的应用程序公开为网络服务的抽象方法。

有了 kubernets service,你就无需修改应用程序即可使用服务发现机制,kubernets 为 pods 提供自己的ip地址,并为一组pod提供相同的DNS名,并且可以在它们之间进行负载均衡。

2. 为什么要用services

创建和销毁 kubernets pod 以匹配集群状态。pod 是非永久性资源。如果你使用 Deployment 来运行你的应用程序,则它可以动态创建和销毁 Pod。

每个 Pod 都有自己的 IP 地址,但是在 Deployment 中,在同一时刻运行的 Pod 集合可能与稍后运行该应用程序的 Pod 集合不同。

这导致了一个问题: 如果一组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”)提供功能, 那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用提供工作负载的后端部分?

3. quick start

3.1 创建svc

资源模板 svc-deploy-nginx.yaml信息如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx-deploy-test
  name: nginx-test-1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-test
  template:
    metadata:
      labels:
        app: nginx-test
    spec:
      containers:
      - image: nginx:latest
        name: nginx
        imagePullPolicy: IfNotPresent

---

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    # must be match pod template .spec.template.labels
    app: nginx-test
  ports:
    - protocol: TCP
      port: 8000
      targetPort: 80

创建一个deploy,和一个 svc

注意 svc selector 对应的是 deploy 的 .spec.template.labels

创建资源

$ kubectl create -f svc-deploy-nginx.yaml

3.2 查看svc

  1. 使用资源文件查看

    $ kubectl get  -f svc-deploy-nginx.yaml -o wide
    # 输出内容如下
    # deploy 信息
    NAME                           READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES         SELECTOR
    deployment.apps/nginx-test-1   3/3     3            3           42m   nginx        nginx:latest   app=nginx-test
    # svc 信息
    NAME                 TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE   SELECTOR
    service/my-service   ClusterIP   10.96.112.5   <none>        8000/TCP   42m   app=nginx-test
    
  2. 使用命令查看

    查看svc 信息

    $ kubectl get svc/my-service -owide
    NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE   SELECTOR
    my-service   ClusterIP   10.96.112.5   <none>        8000/TCP   45m   app=nginx-test
    

    查看endpoints信息

    可以看到成功的绑定了三个pod endpoint

    $ kubectl get ep/my-service
    NAME         ENDPOINTS                                               AGE
    my-service   10.100.132.133:80,10.100.132.139:80,10.100.132.140:80   46m
    

    查看svc 详细信息

    $ kubectl describe svc/my-service
    Name:              my-service
    Namespace:         default
    Labels:            <none>
    Annotations:       <none>
    Selector:          app=nginx-test
    Type:              ClusterIP
    IP:                10.96.112.5
    Port:              <unset>  8000/TCP
    TargetPort:        80/TCP
    Endpoints:         10.100.132.133:80,10.100.132.139:80,10.100.132.140:80
    Session Affinity:  None
    Events:            <none>
    

3.3 访问svc

在上面的实例中svc 并没有指定 svc type,其默认为ClusterIP类型(从上面的详情信息中也能看出)。

我们现在访问下实例中的svc

  1. 在集群内部访问

    在集群机器上可以使用ClusterIp 访问服务,但是无法通过 svc name访问

    在集群pod中可以使用任意方式访问服务

  2. 在集群外部访问

    集群外部 没有办法直接访问 svc type=ClusterIp 的svc

4. svc type

svc 有以下的几种类型:

  • ClusterIP:通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。 这也是默认的 ServiceType

  • NodePort:通过每个节点上的 IP 和静态端口(NodePort)暴露服务。 NodePort 服务会路由到自动创建的 ClusterIP 服务。 通过请求 <节点 IP>:<节点端口>,你可以从集群的外部访问一个 NodePort 服务。

  • LoadBalancer:使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的 NodePort 服务和 ClusterIP 服务上。

  • ExternalName:通过返回 CNAME 和对应值,可以将服务映射到 externalName 字段的内容(例如,foo.bar.example.com)。 无需创建任何类型代理。

    说明: 需使用 kube-dns 1.7 及以上版本或者 CoreDNS 0.0.8 及以上版本才能使用 ExternalName 类型。

4.1 ClusterIp

  1. ClusterIp是默认的svc类型,在创建该资源时,kubernetes会默认分配一个 cluster ip。
  2. svc端口为.spec.ports.port,其代理的后端服务的目标端口为.spec.ports.targetPort
  3. 其cluster ip 只能在集群内部访问,如果想通过svc name 访问其代理的服务,只能在pod中访问。其验证过程可参考3.3 访问svc
  4. 集群外部的资源无法访问ClusterIp类型的svc。

4.2 NodePort

node(节点)port(端口),顾名思义 NodePort类型的svc是通过在集群的宿主机上开放访问的端口,并通过 <节点 IP>:<节点端口>(集群任意节点ip:nodeport 都可以访问)来实现从集群外部访问其svc的。

nodePort 的原理在于在 node 上开了一个端口,将向该端口的流量导入到 kube-proxy,然后由 kube-proxy 进一步到给对应的 pod。(能够将内部服务暴露给外部的一种方式)

  1. 创建资源

    一个deploy 和 一个 nodeport svc,且指定了node 端口为30007(如果不指定,k8s会默认分配一个)

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: nginx-deploy-test
      name: nginx-test-2
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: nginx-test-02
      template:
        metadata:
          labels:
            app: nginx-test-02
        spec:
          containers:
          - image: nginx:latest
            name: nginx
            imagePullPolicy: IfNotPresent
    
    ---
    
    apiVersion: v1
    kind: Service
    metadata:
      name: my-service-2
    spec:
      type: NodePort
      selector:
        app: nginx-test-02
      ports:
        - protocol: TCP
          port: 8000
          targetPort: 80
          # 可选字段
          # 默认情况下,为了方便起见,Kubernetes 会从范围内分配一个端口号(默认:30000-32767)
          nodePort: 30007
    
  2. 访问svc

4.3 LoadBalancer

使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的 NodePort 服务和 ClusterIP 服务上。

4.4 ExternalName

类型为 ExternalName 的服务将服务映射到 DNS 名称,而不是典型的选择器,例如 其他svc 或者 公网域名。 你可以使用 spec.externalName 参数指定这些服务。

  1. 创建服务

    例如,以下 Service 定义将 default 名称空间中的 my-service-2 服务映射到 my-service-1.test.svc.cluster.local(test 名称空间中的 my-service-1 service):

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service-2
    spec:
      type: ExternalName
      externalName: my-service-1.test.svc.cluster.local
    

    服务信息如下:

    服务是在default namespace 下

    没有分配CLUSTER-IP,但是有我们指定的 EXTERNAL-IP

    $ kubectl get svc -owide
    
    NAME           TYPE           CLUSTER-IP   EXTERNAL-IP                           PORT(S)   AGE    SELECTOR
    kubernetes     ClusterIP      10.96.0.1    <none>                                443/TCP   170d   <none>
    my-service-2   ExternalName   <none>       my-service-1.test.svc.cluster.local   <none>    41s    <none>
    

    为了验证映射效果,我们再创建一个my-service-1.test service

    注意 服务都是在test namespace 下

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: nginx-deploy-test
      name: nginx-test-1
      namespace: test
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: nginx-test
      template:
        metadata:
          labels:
            app: nginx-test
        spec:
          containers:
          - image: nginx:latest
            name: nginx
            imagePullPolicy: IfNotPresent
    
    ---
    
    apiVersion: v1
    kind: Service
    metadata:
      name: my-service-1
      namespace: test
    spec:
      selector:
        app: nginx-test
      ports:
        - protocol: TCP
          port: 8000
          targetPort: 80
    

    服务信息如下:

    $ kubectl get -f app-nginx.yaml -owide
    
    NAME                           READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES         SELECTOR
    deployment.apps/nginx-test-1   3/3     3            3           48s   nginx        nginx:latest   app=nginx-test
    
    NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE   SELECTOR
    service/my-service-1   ClusterIP   10.96.188.235   <none>        8000/TCP   47s   app=nginx-test
    
  2. 访问svc

    首先我们创建一个pod 用来访问 验证 svc

    服务信息如下:

    $ kubectl get po -owide
    NAME         READY   STATUS    RESTARTS   AGE     IP               NODE           NOMINATED NODE   READINESS GATES
    helloworld   1/1     Running   0          7d23h   10.100.132.161   k8s-woker-01   <none>           <none>
    

    ok,到这里 资源都以就绪,如果我们进入到 pod/helloworld 去访问 svc/my-service-2,如果能成功访问到naginx 服务,则证明ExternalName svc这种方式是可行的:

4.5 Headless Services

准确来说 Headless Services 并不属于svc的type,而是 clusterip类型的变种

有时不需要或不想要负载均衡,以及单独的 Service IP。 遇到这种情况,可以通过指定 Cluster IP(spec.clusterIP)的值为 "None" 来创建 Headless Service。

对这无头 Service 并不会分配 Cluster IP,kube-proxy 不会处理它们, 而且平台也不会为它们进行负载均衡和路由。 DNS 如何实现自动配置,依赖于 Service 是否定义了选择算符。

无选择算符的服务

对没有定义选择算符的无头服务,Endpoint 控制器不会创建 Endpoints 记录。 然而 DNS 系统会查找和配置,无论是:

  • 对于 ExternalName类型的服务,查找其 CNAME 记录
  • 对所有其他类型的服务,查找与 Service 名称相同的任何 Endpoints 的记录

关于Headless Services 在k8s-statefulset博文中 有详细的记录,感兴趣的可以访问了解下。

5. externalIPs

Service 的 externalIPs(spec.externalIPs)可以设置一组外部的 IP 地址,并且将流量导入到集群内部。

如图:

启动一个springboot项目 并初始化一个接口,访问后返回 Hello World~

在本机上访问:

接下来我们就使用externalIPs192.168.0.101流量导入到集群内部

目标:集群pod中访问192.168.0.101:8080时不是访问此时的这个springboot项目,而是访问集群内的externalIPs svc

  1. 首先到集群master节点访问一下springboot项目

    访问成功

    $ curl 192.168.0.101:8080
    Hello World~
    
  2. 创建externalIPs svc,和想导入到集群的目标pod

    本实例主要是为了展示流量的切换,但其实spec.externalIPs 可以指定符合ip 规则的任意地址(即使设置的ip不可达),k8s都会帮你 在访问设置地址的服务时,将流量导入到spec.selector的服务中去

    实际上就是 如果你把spec.externalIPs地址设置成123.123.123.123,当你在服务中访问123.123.123.123时,其实访问的是spec.selector指定的服务

    其实更像是 给spec.selector到服务指定了一个 ipv4 的别名

    apiVersion: v1
    kind: Service
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      ports:
      - port: 8080
        targetPort: 80
        name: web
      externalIPs:
      - "192.168.0.101"
      # 将 192.168.0.101 流量导入到 下面的Deployment中
      selector:
        app: nginx
        
    ---
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: web
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - name: nginx
            image: nginx
            ports:
            - containerPort: 80
              name: web
    

    服务信息如下:

    $ kubectl get deploy -owide
    NAME   READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES   SELECTOR
    web    2/2     2            2           3h25m   nginx        nginx    app=nginx
    
    $ kubectl get svc/nginx -owide
    NAME         TYPE        CLUSTER-IP     EXTERNAL-IP     PORT(S)    AGE    SELECTOR
    nginx        ClusterIP   10.96.41.170   192.168.0.101   8080/TCP   4m     app=nginx
    
  3. 创建一个pod 并进入pod ,在pod中访问192.168.0.101:8080查看访问的是外部的springboot项目还是集群内的deploy/web

    很显然:k8s成功的将流量导入到了集群内部

    如果在集群节点上访问192.168.0.101:8080,是不能将结果导入到deploy上的,只有在集群服务内部访问才有效

  4. 此时我们将svc/nginx 删除掉,看下访问的结果

    符合预期

6. 管理外部的服务

场景:如果我们的中间件或者数据库服务不是在k8s集群内的,而是在其他的服务器上,但是我们还是想通过访问svc的方式去访问这些外部的服务,我们应该怎么做呢?

  1. 声明一个没有选择符的svc
  2. 创建一个同名(和第一步中的svc name 相同)的 endpoints 服务去管理这些外部的服务地址

实例:

实例通过创建svc 和 endpoints,实现访问svc-name,svc 将流量导出到外部的springboot(项目信息如5.0中提到的)项目中

  1. 创建服务

    apiVersion: v1
    kind: Service
    metadata:
      name: my-service
    spec:
      ports:
        - protocol: TCP
          port: 80
          targetPort: 8080
    
    ---
    
    apiVersion: v1
    kind: Endpoints
    metadata:
      # must be match svc-name
      name: my-service
    subsets:
      - addresses:
          # 指向外部的springboot 服务
          - ip: 192.168.0.101
        ports:
          - port: 8080
    
  2. 访问测试

7. 选择自己的 IP 地址

Service 创建的请求中,可以通过设置 spec.clusterIP 字段来指定自己的集群 IP 地址。

posted @ 2022-01-03 23:11  张铁牛  阅读(363)  评论(0编辑  收藏  举报