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




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM