通常狀況下,Kubernetes 的 Cluster Network 是屬於私有網絡,只能在 Cluster Network 內部才能訪問部署的應用。那麼如何才能將 Kubernetes 集羣中的應用暴露到外部網絡,爲外部用戶提供服務呢?
本文就來說一講從外部網絡訪問 Kubernetes Cluster 中 Pod 和 Serivce 的幾種經常使用的實現方式。node
咱們首先來了解一下 Kubernetes 中的 Pod 和 Service 的概念以及二者間的關係。nginx
Pod (容器組),英文中 Pod 是豆莢的意思。從名字的含義能夠看出,Pod 是一組有依賴關係的容器。Pod 是 Kubernetes 集羣中最基本的資源對象,每一個 Pod 由一個或多個業務容器和一個根容器 (Pause 容器) 組成。web
Kubernetes 爲每一個 Pod 分配了惟一的 IP(即:Pod IP),Pod 裏的多個容器共享這個 IP。Pod 內的容器除了 IP,還共享相同的網絡命名空間、端口、存儲卷等,也就是說這些容器之間能經過 Localhost 來通訊。Pod 包含的容器都會運行在同一個節點上,也能夠同時啓動多個相同的 Pod 用於 Failover 或者 Load balance。後端
Pod 的生命週期是短暫的,Kubernetes 會根據應用的配置對 Pod 進行建立、銷燬並根據監控指標進行伸縮擴容。Kubernetes 在建立 Pod 時能夠選擇集羣中的任何一臺空閒的節點上進行,所以其網絡地址是不固定的。因爲 Pod 的這一特色,通常不建議直接經過 Pod 的地址去訪問應用。api
爲了解決訪問 Pod 不方便直接訪問的問題,Kubernetes 採用了 Service 對 Pod 進行封裝。Service 是對後端提供服務的一組 Pod 的抽象,Service 會綁定到一個固定的虛擬 IP上。該虛擬 IP 只在 Kubernetes Cluster 中可見,但其實該虛擬 IP 並不對應一個虛擬或者物理設備,而只是 IPtables 中的規則,而後再經過 IPtables 將服務請求路由到後端的 Pod 中。經過這種方式,能夠確保服務消費者能夠穩定地訪問 Pod 提供的服務,而不用關心 Pod 的建立、刪除、遷移等變化以及如何用一組 Pod 來進行負載均衡。安全
實現 Service 這一功能的關鍵是由 Kubernetes 中的 Kube-Proxy 來完成的。Kube-Proxy 運行在每一個節點上,監聽 API Server 中服務對象的變化,再經過管理 IPtables 來實現網絡的轉發。Kube-Proxy 目前支持三種模式:UserSpace、IPtables、IPVS。下面咱們來講說這幾種模式的異同:bash
UserSpace
服務器
UserSpace 是讓 Kube-Proxy 在用戶空間監聽一個端口,全部的 Service 都轉發到這個端口,而後 Kube-Proxy 在內部應用層對其進行轉發。網絡
Kube-Proxy 會爲每一個 Service 隨機監聽一個端口 (Proxy Port),並增長一條 IPtables 規則。從客戶端到 ClusterIP:Port 的報文都會被重定向到 Proxy Port,Kube-Proxy 收到報文後,經過 Round Robin (輪詢) 或者 Session Affinity(會話親和力,即同一 Client IP 都走同一鏈路給同一 Pod 服務)分發給對應的 Pod。app
這種方式最大的缺點顯然就是 UserSpace 會形成全部報文都走一遍用戶態,形成總體性能降低,這種方在 Kubernetes 1.2 之後已經再也不使用了。
IPtables
IPtables 方式徹底由 IPtables 來實現,這種方式直接使用 IPtables 來作用戶態入口,而真正提供服務的是內核的 Netilter。Kube-Proxy 只做爲 Controller,這也是目前默認的方式。
Kube-Proxy 的 IPtables 方式也是支持 Round Robin 和 Session Affinity 特性。
Kube-Proxy 監聽 Kubernetes Master 增長和刪除 Service 以及 Endpoint 的消息。對於每個 Service,Kube Proxy 建立相應的 IPtables 規則,並將發送到 Service Cluster IP 的流量轉發到 Service 後端提供服務的 Pod 的相應端口上。
注:雖然能夠經過 Service 的 Cluster IP 和服務端口訪問到後端 Pod 提供的服務,但該 Cluster IP 是 Ping 不通的。其緣由是 Cluster IP 只是 IPtables 中的規則,並不對應到一個任何網絡設備。IPVS 模式的 Cluster IP 是能夠 Ping 通的。
IPVS
Kubernetes 從 1.8 開始增長了 IPVS 支持,IPVS 相對 IPtables 效率會更高一些。使用 IPVS 模式須要安裝 ipvsadm
、ipset
工具包和加載 ip_vs
內核模塊。
注:IPVS 是 LVS 項目的一部分,是一款運行在 Linux Kernel 當中的 4 層負載均衡器,性能異常優秀。使用調優後的內核,能夠輕鬆處理每秒 10 萬次以上的轉發請求。目前在中大型互聯網項目中,IPVS 被普遍的用於承接網站入口處的流量。
瞭解完 Pod 和 Service 的基本概念後,咱們就來具體講一講從外部網絡訪問 Kubernetes Cluster 中 Pod 和 Serivce 的幾種常見的實現方式。目前主要包括以下幾種:
hostNetwork
hostPort
ClusterIP
NodePort
LoadBalancer
Ingress
這是一種直接定義 Pod 網絡的方式。
若是在 Pod 中使用 hostNetwork:true
配置的話,在這種 Pod 中運行的應用程序能夠直接看到啓動 Pod 主機的網絡接口。在主機的全部網絡接口上均可以訪問到該應用程序,如下是使用主機網絡的 Pod 的示例定義:
apiVersion: v1
kind: Pod
metadata:
name: nginx-hostnetwork
spec:
hostNetwork: true
containers:
- name: nginx-hostnetwork
image: nginx:1.7.9複製代碼
部署該 Pod:
$ kubectl create -f nginx-hostnetwork.yml
pod "nginx-hostnetwork" created複製代碼
訪問該 Pod 所在主機的 80 端口:
$ curl http://$HOST_IP:80
......
<title>Welcome to nginx!</title>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
......複製代碼
將看到 Nginx 默認的歡迎頁面,說明能夠正常訪問。
注意:每次啓動這個 Pod 的時候均可能被調度到不一樣的節點上,這樣全部外部訪問 Pod 所在節點主機的 IP 也就是不固定的,並且調度 Pod 的時候還須要考慮是否與宿主機上的端口衝突,所以通常狀況下除非您知道須要某個特定應用佔用特定宿主機上的特定端口時才使用 hostNetwork: true
的方式。
這種 Pod 的網絡模式有一個用處就是能夠將網絡插件包裝在 Pod 中,而後部署在每一個宿主機上,這樣該 Pod 就能夠控制該宿主機上的全部網絡。
這也是一種直接定義 Pod 網絡的方式。
hostPort
是直接將容器的端口與所調度的節點上的端口路由,這樣用戶就能夠經過宿主機的IP加上 <hostPort>
來訪問 Pod 了,如: <hostIP>:<hostPort>
。
apiVersion: v1
kind: Pod
metadata:
name: nginx-hostport
spec:
containers:
- name: nginx-hostport
image: nginx:1.7.9
ports:
- containerPort: 80
hostPort: 8088複製代碼
部署該 Pod:
$ kubectl create -f nginx-hostport.yml
pod "nginx-hostport" created複製代碼
訪問該 Pod 所在主機的 8088 端口:
$ curl http://$HOST_IP:8088
......
<title>Welcome to nginx!</title>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
......複製代碼
將看到 Nginx 默認的歡迎頁面,說明能夠正常訪問。
這種方式和 hostNetwork: true
有同一個缺點,由於 Pod 從新調度的時候該 Pod 被調度到的宿主機可能會變更,這樣 <hostIP>
就變化了,用戶必須本身維護一個 Pod 與所在宿主機的對應關係。
這種網絡方式能夠用來作 Ingress Controller
,外部流量都須要經過 Kubenretes 節點宿主機的 80 和 443 端口。
這是一種經過 kubectl port-forward
指令來實現數據轉發的方法。kubectl port-forward
命令能夠爲 Pod 設置端口轉發,經過在本機指定監聽端口,訪問這些端口的請求將會被轉發到 Pod 的容器中對應的端口上。
首先,咱們來看下 Kubernetes Port Forward 這種方式的工做機制:
使用 Kubectl 建立 Port Forward 後,Kubectl 會主動監聽指定的本地端口。
$ kubectl port-forward pod-name local-port:container-port複製代碼
當向 Local-Port 創建端口鏈接並向該端口發送數據時,數據流向會通過如下步驟:
數據發往 Kubctl 監聽的 Local-Port。
Kubectl 經過 SPDY 協議將數據發送給 ApiServer。
ApiServer 與目標節點的 Kubelet 創建鏈接,並經過 SPDY 協議將數據發送到目標 Pod 的端口上。
目標節點的 Kubelet 收到數據後,經過 PIPE(STDIN、STDOUT)與 Socat 通訊。
Socat 將 STDIN 的數據發送給 Pod 內部指定的容器端口,並將返回的數據寫入到 STDOUT。
STDOUT 的數據由 Kubelet 接收並按照相反的路徑發送回去。
注:SPDY 協議未來可能會被替換爲 HTTP/2。
接下來,咱們用一個實例來演示如何將本地端口轉發到 Pod 中的端口,這裏以一個運行了 Nginx 的 Pod 爲例:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 2 9d複製代碼
驗證 Nginx 服務器監聽的端口:
$ kubectl get pods nginx --template='{{(index (index .spec.containers 0).ports 0).containerPort}}{{"\n"}}'
80複製代碼
將節點上的 8900 端口轉發到 Nginx Pod 的 80 端口:
# 執行 Kubectl port-forward 命令的時候須要指定 Pod 名稱和端口轉發規則。
$ kubectl port-forward nginx 8900:80
Forwarding from 127.0.0.1:8900 -> 80
Forwarding from [::1]:8900 -> 80複製代碼
注:須要在全部 Kubernetes 節點上都須要安裝 Socat,關於 Socat 更詳細介紹可參考:「Socat 入門教程」 。
驗證轉發是否成功:
$ curl http://127.0.0.1:8900
......
<title>Welcome to nginx!</title>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
......複製代碼
因爲這種類型的轉發端口是綁定在本地的,這種方式也僅適用於調試服務。
Service 的類型 ( ServiceType ) 決定了 Service 如何對外提供服務。根據類型不一樣服務能夠只在 Kubernetes Cluster 中可見,也能夠暴露到 Cluster 外部。Service 目前有三種類型:ClusterIP、NodePort 和 LoadBalancer。
Service 中常見的三種端口的含義:
port
Service 暴露在 Cluster IP 上的端口,也就是虛擬 IP 要綁定的端口。port 是提供給集羣內部客戶訪問 Service 的入口。
nodePort
nodePort 是 Kubernetes 提供給集羣外部客戶訪問 Service 的入口。
targetPort
targetPort 是 Pod 裏容器的端口,從 port 和 nodePort 上進入的數據最終會通過 Kube-Proxy 流入到後端 Pod 裏容器的端口。若是 targetPort 沒有被顯式聲明,那麼會默認轉發到 Service 接受請求的端口(和 port 端口的值同樣)。
總的來講,port 和 nodePort 都是 Service 的端口,前者暴露給集羣內客戶訪問服務,後者暴露給集羣外客戶訪問服務。從這兩個端口到來的數據都須要通過反向代理 Kube-Proxy 流入後端 Pod 裏容器的端口,從而到達 Pod 上的容器內。
ClusterIP 是 Service 的缺省類型,這種類型的服務會自動分配一個只能在集羣內部能夠訪問的虛擬 IP,即:ClusterIP。ClusterIP 爲你提供一個集羣內部其它應用程序能夠訪問的服務,外部沒法訪問。ClusterIP 服務的 YAML 相似這樣:
apiVersion: v1
kind: Service
metadata:
name: my-nginx
selector:
app: my-nginx
spec:
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP複製代碼
雖然咱們從集羣外部不能直接訪問一個 ClusterIP 服務,可是你可使用 Kubernetes Proxy API 來訪問它。
Kubernetes Proxy API 是一種特殊的 API,Kube-APIServer 只是代理這類 API 的 HTTP 請求,而後將請求轉發到某個節點上的 Kubelet 進程監聽的端口上。最後實際是由該端口上的 REST API 響應請求。
在 Master 節點上建立 Kubernetes API 的代理服務:
$ kubectl proxy --port=8080複製代碼
kubectl proxy
默認是監聽在 127.0.0.1 上的,若是你須要對外提供訪問,可以使用一些基本的安全機制。
$ kubectl proxy --port=8080 --address=192.168.100.211 --accept-hosts='^192\.168\.100\.*'複製代碼
若是須要更多的命令使用幫助,可使用 kubectl help proxy
。
如今,你可使用 Kubernetes Proxy API 進行訪問。好比:須要訪問一個服務,可使用 /api/v1/namespaces/<NAMESPACE>/services/<SERVICE-NAME>/proxy/
。
# 適用於 Kubernetes 1.10
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.254.0.1 <none> 443/TCP 10d
my-nginx ClusterIP 10.254.154.119 <none> 80/TCP 8d
$ curl http://192.168.100.211:8080/api/v1/namespaces/default/services/my-nginx/proxy/
......
<title>Welcome to nginx!</title>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
......複製代碼
若是你須要直接訪問一個 Pod,可使用 /api/v1/namespaces/<NAMESPACE>/pods/<POD-NAME>/proxy/
。
# 適用於 Kubernetes 1.10
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
my-nginx-86555897f9-5p9c2 1/1 Running 2 8d
my-nginx-86555897f9-ws674 1/1 Running 6 8d
$ curl http://192.168.100.211:8080/api/v1/namespaces/default/pods/my-nginx-86555897f9-ws674/proxy/
......
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
......複製代碼
因爲訪問 Kubernetes Proxy API 要求使用已受權用戶運行 kubectl ,所以不該該使用此方法將你的服務公開到公網上或將其用於生產,這種方法主要仍是用於調試服務。
NodePort 在 Kubenretes 裏是一個普遍應用的服務暴露方式。基於 ClusterIP 提供的功能,爲 Service 在 Kubernetes 集羣的每一個節點上綁定一個端口,即 NodePort。集羣外部可基於任何一個 NodeIP:NodePort
的形式來訪問 Service。Service 在每一個節點的 NodePort 端口上都是可用的。
NodePort 服務與默認的 ClusterIP 服務在 YAML 定義上有兩點區別:首先,type 是 NodePort。其次還有一個稱爲 nodePort 的參數用來指定在節點上打開哪一個端口。 若是你不指定這個端口,它會選擇一個隨機端口。
apiVersion: v1
kind: Pod
metadata:
name: my-nginx
labels:
name: my-nginx
spec:
containers:
- name: my-nginx
image: nginx:1.7.9
ports:
- containerPort: 80複製代碼
nodePort
值的默認範圍是 30000-32767,這個值是能夠在 API Server 的配置文件中用 --service-node-port-range
來自定義。
kind: Service
apiVersion: v1
metadata:
name: my-nginx
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 31000
selector:
name: my-nginx複製代碼
NodePort 類型的服務會在全部的 Kubenretes 節點(運行有 Kube-Proxy 的節點)上統一暴露出一個端口對外提供服務,這樣集羣外就可使用 Kubernetes 任意一個節點的 IP 加上指定端口(這裏定義的是:31000)訪問該服務了。Kube-Proxy 會自動將流量以 Round-Robin 的方式轉發給該 Service 的每個 Pod。
NodePort 類型的服務並不影響原來虛擬 IP 的訪問方式,內部節點依然能夠經過 VIP:Port
的方式進行訪問。NodePort 這種服務暴露方式也存在一些不足:
節點上的每一個端口只能有一個服務。
若是節點 IP 地址發生更改,則須要相應機制處理該問題。
基於以上緣由,NodePort 比較適用的場景爲演示程序或臨時應用,不建議在生產環境中使用這種方法對外暴露服務。
LoadBalancer 是基於 NodePort 和雲服務供應商提供的外部負載均衡器,經過這個外部負載均衡器將外部請求轉發到各個 NodeIP:NodePort
以實現對外暴露服務。
LoadBalancer
只能在 Service 上定義。LoadBalancer 是一些特定公有云提供的負載均衡器,須要特定的雲服務商支持。好比:AWS、Azure、OpenStack 和 GCE (Google Container Engine) 。
kind: Service
apiVersion: v1
metadata:
name: my-nginx
spec:
type: LoadBalancer
ports:
- port: 80
selector:
name: my-nginx複製代碼
查看服務:
$ kubectl get svc my-nginx
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx 10.97.121.42 10.13.242.236 8086:30051/TCP 39s複製代碼
集羣內部可使用 ClusterIP 加端口來訪問服務,如:10.97.121.42:8086。
外部能夠用如下兩種方式訪問該服務:
使用任一節點的 IP 加 30051 端口訪問該服務。
使用 EXTERNAL-IP
來訪問,這是一個 VIP,是雲供應商提供的負載均衡器 IP,如:10.13.242.236:8086。
LoadBalancer 這種方式最大的不足就是每一個暴露的服務須要使用一個公有云提供的負載均衡器 IP,這可能會付出比較大的成本代價。
從上面幾種 Service 的類型的結論來看,目前 Service 提供的負載均衡功能在使用上有如下限制:
只提供 4 層負載均衡,不支持 7 層負載均衡功能,好比:不能按須要的匹配規則自定義轉發請求。
使用 NodePort 類型的 Service,須要在集羣外部部署一個外部的負載均衡器。
使用 LoadBalancer 類型的 Service,Kubernetes 必須運行在特定的雲服務上。
與 Service 不一樣,Ingress 實際上不是一種服務。相反,它位於多個服務以前,充當集羣中的智能路由器或入口點。
Ingress
是自 Kubernetes 1.1 版本後引入的資源類型。Ingress 支持將 Service 暴露到 Kubernetes 集羣外,同時能夠自定義 Service 的訪問策略。Ingress 可以把 Service 配置成外網可以訪問的 URL,也支持提供按域名訪問的虛擬主機功能。例如,經過負載均衡器實現不一樣的二級域名到不一樣 Service 的訪問。
實際上 Ingress
只是一個統稱,其由 Ingress
和 Ingress Controller
兩部分組成。Ingress
用做將原來須要手動配置的規則抽象成一個 Ingress 對象,使用 YAML 格式的文件來建立和管理。Ingress Controller
用做經過與 Kubernetes API 交互,動態的去感知集羣中 Ingress 規則變化。
使用 Ingress
前必需要先部署 Ingress Controller
,Ingress Controller
是以一種插件的形式提供。Ingress Controller
一般是部署在 Kubernetes 之上的 Docker 容器,Ingress Controller
的 Docker 鏡像裏包含一個像 Nginx 或 HAProxy 的負載均衡器和一個 Ingress Controller
。Ingress Controller
會從 Kubernetes 接收所需的 Ingress
配置,而後動態生成一個 Nginx 或 HAProxy 配置文件,並從新啓動負載均衡器進程以使更改生效。換句話說,Ingress Controller
是由 Kubernetes 管理的負載均衡器。
注:不管使用何種負載均衡軟件( 好比:Nginx、HAProxy、Traefik等)來實現 Ingress Controller,官方都將其統稱爲 Ingress Controller。
Kubernetes Ingress 提供了負載均衡器的典型特性:HTTP 路由、粘性會話、SSL 終止、SSL直通、TCP 和 UDP 負載平衡等。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx-ingress
spec:
backend:
serviceName: my-nginx-other
servicePort: 8080
rules:
- host: foo.mydomain.com
http:
paths:
- backend:
serviceName: my-nginx-foo
servicePort: 8080
- host: mydomain.com
http:
paths:
- path: /bar/*
backend:
serviceName: my-nginx-bar
servicePort: 8080複製代碼
外部可經過 foo.yourdomain.com
或者 mydomain.com/bar/
兩個不一樣 URL 來訪問對應的後端服務,而後 Ingress Controller
直接將流量轉發給後端 Pod,不需再通過 Kube-Proxy 的轉發,這種方式比 LoadBalancer 更高效。
總的來講 Ingress 是一個很是靈活和愈來愈獲得廠商支持的服務暴露方式,包括:Nginx、HAProxy、Traefik、還有各類 Service Mesh,而其它服務暴露方式更適用於服務調試、特殊應用的部署。
http://www.google.com
http://t.cn/RdskHHt
http://t.cn/RdskYv5
http://t.cn/Rg5BG4h
http://t.cn/R6CD4ak
http://t.cn/Rgcurto
http://t.cn/RgcdjDw
http://t.cn/RgMWBpo
http://t.cn/RgMWFM1
http://t.cn/RgC3uk8