k8s-svc

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. 在集群内部访问
    k8s-svc

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

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

  2. 在集群外部访问
    k8s-svc

    集群外部 没有办法直接访问 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
    k8s-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这种方式是可行的:

    k8s-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~
k8s-svc

在本机*问:
k8s-svc

接下来我们就使用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-svc

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

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

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

    符合预期
    k8s-svc

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. 访问测试

    k8s-svc

7. 选择自己的 IP 地址

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

上一篇:golang cronexpr定时任务包使用


下一篇:结合简单的控制台程序和K8S的cronjob完成定时任务