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