K8S 的 Service 类型



ClusterIP

默认类型,集群内部使用,集群外部无法访问

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: ClusterIP    ## service 类型,不填的话默认 ClusterIP,自动分配 IP,或添加 spec.clusterIP 字段指定
  ports:
  - name: http       ## 定义多个端口的话需要为每个端口指定名字
    port: 80         ## service 暴露的端口
    targetPort: 80   ## 关联的 Pod 的端口,不填的话默认和 port 字段相同
    protocol: TCP    ## 不填的话默认 TCP
  - name: app-2
    port: 5000
  selector:          ## service 会关联定义了 app:my-app 和 component:my-component 两个 label 的 Pod
    app: my-app
    component: my-component

比如通过 deployment 启动 pod (3 个副本)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-deployment
spec:
  selector:
    matchLabels:
      app: my-app
      component: my-component
  replicas: 3
  template:
    metadata:
      labels:
        app: my-app
        component: my-component
    spec:
      containers:
        - name: my-app
          image: my-image
          ports:
            - containerPort: 80
              name: http
            - containerPort: 5000
              name: app-2

这样 service 和 pod 关联了起来,系统会自动为 service 分配 cluster ip,也可以通过 spec.clusterIP 指定

可以通过 service name 访问 pod,比如

curl my-service:5000/xxx

如果执行 curl 的地方和 service 不在一个 namespace,需要加上 namespace

curl my-service.my-namespace:5000/xxx

也可以通过 cluster ip 访问

curl 10.10.1.10:5000/xxx

service 会把请求转发给关联的 pod
由于 pod 有多个副本,也就实现了负载均衡
service 是一个固定的名字,所以还相当于实现了服务发现

这些 service 的建立和数据转发是由 kube-proxy 和 iptables 实现的

Services without selectors

如果 service 关联的不是 Pod,比如要创建一个 service 关联宿主机的 redis 用于测试,这时不指定 selector

apiVersion: v1
kind: Service
metadata:
  name: redis
  labels:
    app: redis
spec:
  ports:
    - port: 6379

再定义一个 endpoint

apiVersion: v1
kind: Endpoints
metadata:
  name: redis
subsets:
  - addresses:
    - ip: 10.0.2.10   # redis ip,不能是 loopback (127.0.0.0/8) 或 link-local (169.254.0.0/16)
    ports:
      - port: 6379

这样就可以通过 service 访问那些不是部署在 K8S 上的服务

NodePort

可以被集群外访问

同样会有一个 ClusterIP 的 service 被创建,同时在集群的每台机器上暴露同一个端口,比如都暴露 31000 端口,每台机器的 31000 端口收到的请求都会转发到 ClusterIP,这样就可以从外网访问内部服务

同时内部网络依然可以使用 ClusterIP 访问

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: NodePort
  selector:
    app: MyApp
  ports:
    - port: 80          ## clusterIP 暴露的端口
      targetPort: 80    ## 关联的 Pod 的端口,不指定的话默认和 port 字段相同
      nodePort: 31000   ## 集群的每台机器上要暴露的端口,不填的话由系统随机指定 (30000-32767)

这种方法有一定缺点:由系统随机指定的话端口号在 30000~32767,自己指定的话有可能会有端口冲突,比如有两个微服务都希望暴露 HTTP 80 端口给外部使用

正式生产环境用的比较少,主要用于 Demo,或者是低要求低成本的应用

LoadBalancer

如果云厂商(比如 AWS,AKS 等)提供外部负载均衡器,那么可以将 service 类型设置为 LoadBalancer

这样云厂商会创建一个外部公网的 load balancer,有单独的域名或 IP 地址,连接到 K8S 的 service,这样比如说有多个服务要暴露 HTTP 80 端口给外部使用,可以申请多个 LoadBalancer,就不会有冲突,每个都有单独的地址

默认依然会创建 ClusterIP 和 NodePort,外部的 LoadBalancer 是把数据发给系统创建的 NodePort,在不同的 NodePort 间做负载均衡,从 v1.20 开始可以通过设置 spec.allocateLoadBalancerNodePorts 为 false 避免创建 NodePort,这依赖于云厂商的 LoadBalancer 能不能直接把数据发给内部的 service

LoadBalancer 的 IP 有可能自己指定,也可能是云厂商自动分配

LoadBalancer 的端口默认必须都是相同的类型,比如都是 TCP,从 v1.20 开始,可以指定 MixedProtocolLBService 允许使用不同的端口类型,支持的端口类型取决于云厂商

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: LoadBalancer
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
      name: http
    - protocol: TCP
      port: 443
      targetPort: 9375
      name: https
status:
  loadBalancer:
    ingress:
    - ip: 192.0.2.127     ## 指定 loadBalancer 的 IP,也可能有云厂商自动分配
NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                       AGE
my-nginx-ingress-svc   LoadBalancer   10.101.15.17   192.0.2.127     80:31684/TCP,443:30608/TCP    21d

EXTERNAL-IP 就是可以在公网访问的 IP
端口 80 和 443 是公网访问的端口
31684 和 30608 系统自动分配的 NodePort 端口
会转发到 MyApp 的 9375、9376 端口

由于配置 LoadBalancer 厂商需要收取费用,如果有多个服务要申请 LoadBalancer,经济上会增加负担

ExternalName

ExternalName 将一个 service 映射到一个域名上

apiVersion: v1
kind: Service
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: my.database.example.com

就像是做了一个软连接,如果要访问的域名改了,就改这个软连接就可以,其他使用这个 service 的地方都不用改

Headless Services

前面的配置中,要访问 Pod 都是通过访问 ClusterIP 实现的,无法直接访问 Pod,因为 DNS 里没有 Pod 的信息

比如下面是一个正常的 ClusterIP service

NAME          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)           AGE
my-service    ClusterIP   10.102.78.24   <none>        80/TCP,5000/TCP   22m

进入任意 Pod 后通过 nslookup 查看域名对应的 IP,域名格式是 [servicename].[namespace].svc.cluster.local

/# nslookup my-service.default.svc.cluster.local.   ## 最后加点号,不然 nslookup 会自动加个后缀

Server:         10.96.0.10                          ## 自己的服务器
Address:        10.96.0.10#53                       ## 自己的IP

Name:   my-service.default.svc.cluster.local        ## 目标域名
Address: 10.102.78.24                               ## 目标IP

可以看到返回的地址就是 ClusterIP 的地址,这种配置无法访问指定的 Pod,只能由 service 决定访问哪个 Pod

而 Headless 就是将 ClusterIP 指定为 None 的 service

apiVersion: v1
kind: Service
metadata:
  name: my-headless
spec:
  clusterIP: None    ## 指定 IP 为 None
  ports:
  - name: http
    port: 80
  - name: app-2
    port: 5000
  selector:
    app: my-app-headless
    component: my-component-headless

查看创建的 service 可以看到没有分配 Cluster IP

NAME          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)           AGE
my-headless   ClusterIP   None           <none>        80/TCP,5000/TCP   87m

进入任意 Pod 后通过 nslookup 查看域名对应的 IP

/# nslookup my-headless.default.svc.cluster.local.
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   my-headless.default.svc.cluster.local
Address: 172.17.0.5
Name:   my-headless.default.svc.cluster.local
Address: 172.17.0.8
Name:   my-headless.default.svc.cluster.local
Address: 172.17.0.9

可以看到返回了三个 IP,通过 kubectl describe 查看关联的三个 Pod,可以看到返回的就是三个关联 Pod 的 IP

所以 Headless 的作用就是绕过 Cluster IP 提供的负载均衡,由自己决定要访问哪个 Pod

配了 Headless 后除了可以使用 IP 访问 Pod,还可以使用域名 [podname].[servicename].[namespace].svc.cluster.local

通常是有状态服务,或者集群业务(比如部署 zookeeper 集群),才需要能知道具体的 Pod 的地址

StatefulSet

Headless 通常和 StatefulSet 结合使用,因为就是访问有状态的服务,所以才要找到具体的 Pod

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: my-stateful-set
spec:
  serviceName: my-headless      ## 指定关联的 headless service
  selector:
    matchLabels:
      app: my-app-headless
      component: my-component-headless
  replicas: 3
  template:
    metadata:
      labels:
        app: my-app-headless
        component: my-component-headless
    spec:
      containers:
        - name: my-app-headless
          image: my-image
          ports:
            - containerPort: 80
              name: http
            - containerPort: 5000
              name: app-2

StatefulSet 创建的 Pod 名字是固定的,通常还会关联固定的 PVC,这样重启后不仅名字一样,状态也可以恢复

NAME                             READY   STATUS    RESTARTS   AGE
my-stateful-set-0                1/1     Running   0          107m
my-stateful-set-1                1/1     Running   0          107m
my-stateful-set-2                1/1     Running   0          107m

进入任意 Pod,通过 nslookup 可以得到 Pod 的 IP

/# nslookup my-headless.default.svc.cluster.local.
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   my-headless.default.svc.cluster.local
Address: 172.17.0.5
Name:   my-headless.default.svc.cluster.local
Address: 172.17.0.8
Name:   my-headless.default.svc.cluster.local
Address: 172.17.0.9


/# nslookup my-stateful-set-0.my-headless.default.svc.cluster.local.
Server:         10.96.0.10
Address:        10.96.0.10#53

Name:   my-stateful-set-0.my-headless.default.svc.cluster.local
Address: 172.17.0.5

这样就可以通过固定的名字,访问具体的 Pod

Ingress

前面提到,如果要暴露服务到外网,使用 LoadBalancer 的话成本比较高,使用 NodePort 的话不能重用端口

Ingress 可以暴露端口给外网,然后将不同的 URL 映射到不同的 Service,类似于 Nginx 之类的代理服务器

Ingress 只支持 HTTP 或 HTTPS

Ingress 配置例子

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minimal-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"               ## 指定 Ingress Controller 的类型,比如 nginx
                                                       ## 这里的 nginx 需要和 ingress controller 的配置匹配
                                                       ## 比如 ingress nginx controller 的配置 --ingress-class=nginx
                                                       ## 可以改成其他名字

    nginx.ingress.kubernetes.io/rewrite-target: /      ## 配置 Ingress Controller
spec:
  ## ingressClassName: external-lb        ## 1.18 版本引入,替代  kubernetes.io/ingress.class
  rules:
  - host: "foo.bar.com"      ## 可选项,可以匹配不同域名,就算同一个 IP 也可以有多个域名
    http:
      paths:                 ## 配置映射,可以配多个
      - path: /testpath      ## 结合 pathType: Prefix 表示匹配所有 /testpath/ 为前缀的 URL 
        pathType: Prefix     ## 除了 Prefix 还有 Exact 和 ImplementationSpecific
        backend:
          service:
            name: test       ## 要转发的 service
            port:
              number: 80     ## 要转发的端口

低版本的 K8S 的配置

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /testpath
        backend:
          serviceName: test
          servicePort: 80

这里的 ingress 只是配置映射关系,需要相应的 ingress controller 执行,官方提供了很多种 controller
https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/

比如 nginx
https://kubernetes.github.io/ingress-nginx/deploy
上面提供了 image 和不同环境下(AKS,AWS,Minikube 等)的 yaml 文件可以直接 apply 部署

以云厂商的 nginx 为例,通常是部署一个 ingress controller 的 deployment,用于运行 nginx,并监控 Ingress 配置,如果 Ingress 配置有变化,可以相应的更改 nginx 的配置,更改 nginx 的映射关系,同时会部署一个 LoadBalancer 或 NodePort 的 service 将 nginx 和外网连上,这个 LoadBalancer、NodePort 的 targetPort 指向的是 ingress controller pod 的端口

在 v1.18 前,ingress controller 通过 kubernetes.io/ingress.class 字段指定
从 v1.18 开始,则通过 ingressClassName 字段,配合 IngressClass 资源指定

spec:
  ingressClassName: external-lb
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: external-lb
spec:
  controller: k8s.io/ingress-nginx
  parameters:
    apiGroup: k8s.example.com
    kind: IngressParameters
    name: external-lb

可以通过 ingress class 的 annotation 的 ingressclass.kubernetes.io/is-default-class 字段将 ingress class 指定为默认 controller,这样 ingress 配置就可以不用指定 controller

NAME           HOSTS     ADDRESS         PORTS      AGE
my-ingress     *         20.102.6.105    80, 443    21d

HOSTS 是 ingress 监听的域名,不指定的话就监听 IP 对应的所有 host

ADDRESS 是外网可以访问的地址,也是和 Ingress Controller 关联的 LoadBalancer 的外部地址

ingress 的一些属性比如负载均衡、安全性等,取决于选择的 controller 类型

总结起来,数据流就是
Client -> LoadBalancer/NodePort -> Ingress Controller (如 nginx,并监控 Ingress 的配置) -> Service -> Pod



posted @ 2021-03-18 01:24  moon~light  阅读(2796)  评论(1编辑  收藏  举报