上篇文章有提到,經過POD ID只可以在k8s集羣內部進行訪問,做爲一個博客!只給本身看...好像也行啊...
可是每次都要先登陸到集羣節點中才能看...這就...(腦海裏閃過成噸shell... ssh@192.10o... kubectl get po...,噗~!
這是在玩本身嗎?前端
集羣都有了,服務也部了,咋就不能讓他與萬物互聯了呢?
接下來咱們就一同探祕,k8s中服務是如何暴露出去的 node
Kubernetes 的Pod的壽命是有限的。它們出生而後死亡,它們不會復活。ReplicationController是特別用來動態的建立和銷燬Pods(如:動態伸縮或者執行rolling updates中動態的建立和銷燬pod)。儘管每一個Pod有本身的IP地址,隨着時間變化隨着時間推移即便這些IP地址也不能被認爲是可靠的。這帶來一個問題:若是一些Pod的集合(讓咱們稱之爲backends)爲集羣中的其餘的Pod提供了一些功能(讓咱們稱它們爲frontends),這些frontends應該如何找到並一直知道哪些backends在這樣的集合中呢? nginx
Kubernetes的Service是一種抽象,它定義了一組Pods的邏輯集合和一個用於訪問它們的策略 - 有的時候被稱之爲微服務。
舉個例子,想象一下咱們的博客後端運行了三個副本。這些副本都是能夠替代的 - 前端不關心它們使用的是哪個後端。儘管實際組成後端集合的Pod可能會變化,前端的客戶端卻不須要知道這個變化,也不須要本身有一個列表來記錄這些後端服務。Service抽象能讓你達到這種解耦。git
Kubernetes中的Service是一個REST對象,這點與Pod相似。正如全部的REST對象同樣,向apiserver POST一個Service的定義就能建立一個新的實例。以上篇文章中的echoserver爲例,每個Pod都開放了80端口,而且都有一個"app=my-blog"的標籤。github
{ "kind": "Service", "apiVersion": "v1", "metadata": { "name": "my-blog" }, "spec": { "selector": { "app": "my-blog" }, "ports": [ { "protocol": "TCP", "port": 8080, "targetPort": 80 } ] } }
這個定義會建立一個新的Service對象,名字爲」my-blog」,它指向全部帶有」app=my-blog」標籤的Pod上面的80端口。這個Service同時也會被分配一個IP地址(有時被稱做」cluster ip」),它會被服務的代理所使用(見下面)。這個Service的選擇器,會不斷的對Pod進行篩選,並將結果POST到名字一樣爲「my-blog」的Endpoints對象。 算法
注意一個Service能將一個來源的端口映射到任意的targetPort。默認狀況下,targetPort會被設置成與port字段同樣的值。可能更有意思的地方在於,targetPort能夠是一個字符串,能引用一個後端Pod中定義的端口名。實際指派給該名稱的端口號在每個Pod中可能會不一樣。這爲部署和更新你的Service提供了很大的靈活性。例如,你能夠在你的後端的下一個版本中更改開放的端口,而無需致使客戶出現故障。shell
Kubernetes的Service支持TCP和UDP協議。默認是TCP。後端
說到service不得不提 endpoint。在 Service 建立的同時,還生成了一個 Endpoints。 該 Endpoints 與 Service 同名,它所暴露的地址信息正是對應 Pod 的地址。由此猜想是 Endpoints 維護了 Service 與 Pod 的映射關係。
爲了驗證咱們的猜想,咱們手動刪除 Endpoints,發現以前能成功訪問到 Pod 的 VIP,如今已經已經訪問不到了。api
ServiceController 主要處理的仍是與 LoadBalancer 相關的邏輯,可是 EndpointController 的做用就沒有這麼簡單了,咱們在使用 Kubernetes 時雖然不多會直接與 Endpoint 資源打交道,可是它倒是 Kubernetes 中很是重要的組成部分。瀏覽器
EndpointController 自己並無經過 Informer 監聽 Endpoint 資源的變更,可是它卻同時訂閱了 Service 和 Pod 資源的增刪事件,它會根據 Service 對象規格中的選擇器 Selector 獲取集羣中存在的全部 Pod,並將 Service 和 Pod 上的端口進行映射生成一個 EndpointPort 結構體,對於每個 Pod 都會生成一個新的 EndpointSubset,其中包含了 Pod 的 IP 地址和端口和 Service 的規格中指定的輸入端口和目標端口,在最後 EndpointSubset 的數據會被從新打包並經過客戶端建立一個新的 Endpoint 資源。因此,除了 Service 的變更會觸發 Endpoint 的改變以外,Pod 對象的增刪也會觸發。
在個人理解,service就是將不斷變更的pod作了一個固定的端口映射(別管pod怎麼變化,怎麼漂移,這是個人事),你只須要處理service暴露出來的固定端口便可。究竟是誰來處理service暴露出來的端口呢?那固然是代理了。
咱們知道 Service 的代理是由 kube-proxy 實現的。而它的代理模式(Proxy mode)主要有兩種:userspace 與 iptables。自 K8S v1.2 開始,默認的代理模式就是 iptables(/ipvs),而且它的性能也是要高於 userspace 的
userspace是在用戶空間,經過kuber-proxy實現LB的代理服務。這個是kube-proxy的最初的版本,較爲穩定,可是效率也天然不過高。
iptables的方式。是純採用iptables來實現LB。是目前通常kube默認的方式。
ipvs:這種模式從Kubernetes 1.11進入GA,並在Kubernetes 1.12成爲kube-proxy的默認代理模式。ipvs模式也是基於netfilter,對比iptables模式在大規模Kubernetes集羣有更好的擴展性和性能,支持更加複雜的負載均衡算法(如:最小負載、最少鏈接、加權等),支持Server的健康檢查和鏈接重試等功能。ipvs依賴於iptables,使用iptables進行包過濾、SNAT、masquared。ipvs將使用ipset須要被DROP或MASQUARED的源地址或目標地址,這樣就能保證iptables規則數量的固定,咱們不須要關心集羣中有多少個Service了。
咱們如今要作的呢,是將 VIP 請求給轉發到對應的 Pod 上。而實現此的正是 iptables(/ipvs)。
舉個例子,如今有podA,podB,podC和serviceAB。serviceAB是podA,podB的服務抽象(service)。 那麼kube-proxy的做用就是能夠將pod(無論是podA,podB或者podC)向serviceAB的請求,進行轉發到service所表明的一個具體pod(podA或者podB)上。 請求的分配方法通常分配是採用輪詢方法進行分配。
那有了kube-proxy,service是如何暴露的呢?
service的ServiceTypes能讓你指定你想要哪種服務。默認的和基礎的是ClusterIP,這會開放一個服務能夠在集羣內部進行鏈接。NodePort 和LoadBalancer是兩種會將服務開放給外部網絡的類型。
apiVersion: v1 kind: Service metadata: # Service 實例名稱 name: my-blog spec: ports: - protocol: TCP # Service 端口地址 port: 8080 # Pod 端口地址 targetPort: 80 selector: # 匹配符合標籤條件的 Pod app: my-blog type: NodePort <- Service Type
ServiceType字段的合法值是:
ClusterIP: 僅僅使用一個集羣內部的IP地址 - 這是默認值,在上面已經討論過。選擇這個值意味着你只想這個服務在集羣內部才能夠被訪問到。
NodePort: 在集羣內部IP的基礎上,在集羣的每個節點的端口上開放這個服務。你能夠在任意<NodeIP>:NodePort地址上訪問到這個服務。
若是定義爲NodePort,Kubernetes master 將從給定的配置範圍內(默認:30000-32767)分配端口,每一個 Node 將從該端口代理到 Service。該端口將經過 Service 的 spec.ports[*].nodePort 字段被指定。那麼咱們就可使用任意節點IP:NodePort來訪問到my-blog容器的80端口
LoadBalancer: 在使用一個集羣內部IP地址和在NodePort上開放一個服務以外,向雲提供商申請一個負載均衡器,會讓流量轉發到這個在每一個節點上以<NodeIP>:NodePort的形式開放的服務上。
在使用一個集羣內部IP地址和在NodePort上開放一個Service的基礎上,還能夠向雲提供者申請一個負載均衡器,將流量轉發到已經以NodePort形式開發的Service上。
將上述service文件保存並執行命令
$ kubectl create -f service.yaml service/my-blog created
代表咱們成功建立了一個使用NodePort方式暴露的service
下面咱們來驗證一下
經過上第一行命令,咱們能夠看到剛剛建立的service已經以NodePort的方式對外暴露(第一個紅框),暴露的端口號是32388,經過另外兩行命令咱們的項目my-blog部署在哪一臺物理機節點上(對應EXTERNAL-IP就是物理機的真實IP),這裏懼怕遇到調皮的同窗,因此給它加了個衣服:P
嘗試經過IP+Port的方式請求一下
Amazing! 通了!終於不是獨樂樂了~
不過... 以用戶的習慣,經過IP+端口號來訪問的姿式可能沒法接受
可能咱們還須要再搞個域名,讓用戶能夠直接經過域名來訪問咱們的博客
這時候有些機靈鬼就跳出來了,這還不簡單!上面再搭個nginx不就好了...
確實是這樣...可是若是我再多部署一個博客呢?(那就再加一條nginx解析記錄唄...咱們不是一直這樣用的嗎???
那...咱們還搞個毛k8s
k8s可不是爲了讓你部署一兩個服務而誕生的...它是爲了成千上萬個服務~(那你爲何在教咱們用k8s部署博客???我:你今天有點話多
若是每個服務都要手動去修改nginx解析記錄...你就不怕何時你手多抖兩下嗎?
總結一下上面service直接暴露服務的一些缺點
而Ingress就是爲了解決上面的問題而誕生的
Ingress 的實現分爲兩個部分 Ingress Controller 和 Ingress .
Ingress Controller 會監聽 api server上的 /ingresses 資源 並實時生效。
Ingerss 描述了一個或者多個 域名的路由規則,以 ingress 資源的形式存在。
簡單說: Ingress 描述路由規則, Ingress Controller 負責動態實現規則。
下面咱們來動手實現一下ingress,經過動手來學習
首先須要在咱們的集羣中安裝ingress-nginx-controller
官方文檔:https://kubernetes.github.io/...
// 登陸集羣,在集羣中執行如下命令安裝 ingress-nginx kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
驗證:
$ kubectl get pod -n ingress-nginx NAME READY STATUS RESTARTS AGE nginx-ingress-controller-66f7bd6b88-fwshc 1/1 Running 0 8s
說明安裝成功
可是官方提供的這個yaml並無指定nginx-ingress-controller本身自己的暴露方式
下面咱們來手動設置以hostNetWork的方式暴露服務
$ kubectl edit deploy nginx-ingress-controller -n ingress-nginx // 修改hostNetWork爲true spec.template.spec.hostNetWork: ture deployment.extensions/nginx-ingress-controller edited
修改爲功!下面咱們來建立一個ingress資源
apiVersion: extensions/v1beta1 kind: Ingress metadata: name: my-blog namespace: test annotations: nginx.ingress.kubernetes.io/ssl-redirect: "false" // 這個參數標識 不強制要求使用HTTPS協議進行通訊,文章結尾會講到https的支持 spec: rules: - host: blog.holdno.com http: paths: - path: / backend: serviceName: my-blog servicePort: 8080
經過上面的yaml文件咱們能夠很清晰的看出,咱們想要對用戶暴露的域名是 blog.holdno.com,解析規則是根路徑"/",對應的service爲my-blog,轉發到service的8080端口(前面咱們service暴露的是8080端口到POD的80端口)
$ kubectl create -f ingress.yaml ingress.extensions/my-blog created $ kubectl get ingress -n test NAME HOSTS ADDRESS PORTS AGE my-blog blog.holdno.com 80 10s
ingress咱們就部署成功了
經過上面兩條命令找到nginx-ingress-controller所在的節點的真實IP(在部署ingress時最好是指定某幾臺性能較好的節點,經過nodeSelector進行調度)
經過域名解析平臺將咱們的域名blog.holdno.com解析到節點IP上
curl blog.holdno.com // 下面會出現完美的一幕,你們親手實踐一下吧
到此,咱們便成功的創建了從服務到用戶之間的橋樑!以後不管POD怎麼漂移,對於上層的ingress都是無謂的
雖然橋通了,可是感受部署一個服務的過程非常心累...
後面的文章,咱們將經過k8s提供的client-go來爲你們梳理出一套自動化的部署流程,讓咱們的博客能夠一鍵上雲!
既然支持http了 天然少不了https的需求
那麼咱們就動手一步一步實現 ingress https的支持吧
首先須要瞭解的是 k8s內置的 secret資源,用來保存證書信息
咱們經過阿里雲購買一個免費的證書,並下載到機器上。(假設保存路徑爲 /ssl/blog.crt 和 /ssl/blog.key)
$ kubectl create secret tls my-secret --cert /ssl/blog.crt --key ./ssl/blog.key --namespace test // 必定要注意,secret是區分namespace的,不一樣namespace下的ingress資源和secret資源互相不可見 $ secret/blog created
修改咱們的ingress配置
$ kubectl edit ingress my-blog -n test apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"nginx","nginx.ingress.kubernetes.io/ssl-redirect":"false"},"name":"my-blog","namespace":"test"},"spec":{"rules":[{"host":"blog.holdno.com","http":{"paths":[{"backend":{"serviceName":"my-blog","servicePort":777},"path":"/"}]}}]}} kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/ssl-redirect: "false" // 若是要強制使用https協議,須要將這個參數改成 true creationTimestamp: "2019-09-17T12:20:01Z" generation: 3 name: my-blog namespace: test resourceVersion: "1107154526" selfLink: /apis/extensions/v1beta1/namespaces/test/ingresses/my-blog uid: 78bac7dc-d945-11e9-b8cf-9a285cd2373e spec: rules: - host: blog.holdno.com http: paths: - backend: serviceName: my-blog servicePort: 8080 path: / tls: <- 關鍵配置 - hosts: - blog.holdno.com # 證書對應的host secretName: blog # 對應的證書 也就是咱們上一步建立的secret status: loadBalancer: {} ingress.extensions/my-blog edited
接下里咱們就能夠在瀏覽器中訪問 https://blog.holdno.com
來驗證一下。