Service的工作方式有三種:
第一種: 是Userspace方式
如下圖描述, Client Pod要訪問Server Pod時,它先將請求發給本機內核空間中的service規則,由它再將請求,
轉給監聽在指定套接字上的kube-proxy,kube-proxy處理完請求,並分發請求到指定Server Pod后,再將請求
遞交給內核空間中的service,由service將請求轉給指定的Server Pod。
由於其需要來回在用戶空間和內核空間交互通信,因此效率很差,接着就有了第二種方式.
第二種: iptables模型
此工作方式是直接由內核中的iptables規則,接受Client Pod的請求,並處理完成后,直接轉發給指定ServerPod.
第三種: ipvs模型
它是直接有內核中的ipvs規則來接受Client Pod請求,並處理該請求,再有內核封包后,直接發給指定的Server Pod。
注:
以上不論哪種,kube-proxy都通過watch的方式監控着kube-APIServer寫入etcd中關於Pod的最新狀態信息,
它一旦檢查到一個Pod資源被刪除了 或 新建,它將立即將這些變化,反應再iptables 或 ipvs規則中,以便
iptables和ipvs在調度Clinet Pod請求到Server Pod時,不會出現Server Pod不存在的情況。
自k8s1.1以后,service默認使用ipvs規則,若ipvs沒有被激活,則降級使用iptables規則. 但在1.1以前,service
使用的模式默認為userspace.
查看k8s集群中API Server面向集群內部的service地址:
#其中第一個kubernets,類型為ClusterIP,暴露端口為443/tcp的即為APIServer的向集群內部提供服務的Service.
kubectl get svc
創建Service的清單文件:
kubectl explain svc
kubectl explain svc.spec
type:
service的類型有四種:
1. ExternalName: 用於將集群外部的服務引入到集群內部,在集群內部可直接訪問來獲取服務。
它的值必須是 FQDN, 此FQDN為集群內部的FQDN, 即: ServiceName.Namespace.Domain.LTD.
然后CoreDNS接受到該FQDN后,能解析出一個CNAME記錄, 該別名記錄為真正互聯網上的域名.
如: www.test.com, 接着CoreDNS在向互聯網上的根域DNS解析該域名,獲得其真實互聯網IP.
2. ClusterIP: 用於為集群內Pod訪問時,提供的固定訪問地址,默認是自動分配地址,可使用ClusterIP關鍵字指定固定IP.
3. NodePort: 用於為集群外部訪問Service后面Pod提供訪問接入端口.
這種類型的service工作流程為:
Client----->NodeIP:NodePort----->ClusterIP:ServicePort----->PodIP:ContainerPort
4. LoadBalancer: 用於當K8s運行在一個雲環境內時,若該雲環境支持LBaaS,則此類型可自動觸發創建
一個軟件負載均衡器用於對Service做負載均衡調度.
因為外部所有Client都訪問一個NodeIP,該節點的壓力將會很大, 而LoadBalancer則可解決這個問題。
而且它還直接動態監測后端Node是否被移除或新增了,然后動態更新調度的節點數。
#Service清單文件創建示例: vim redis-svc.yaml #定義一個redis的服務. apiVersion: v1 kind: Service metadata: name: redis namespace: default spec: selector: #指定標簽選擇器選擇的標簽范圍. app: redis role: logstor clusterIP: 10.97.97.97 type: ClusterIP ports: - name: redis port: 6379 #設定Serivce對外提供服務的端口. targetPort: 6379 #設定容器(Pod)的端口,即Pod網絡的端口。 nodePort: #它僅在type為NodePort時才需要指定.
接着創建服務:
kubectl apply -f redis-svc.yaml
kubectl get svc
kubectl describe svc redis #可看到service redis它的詳細配置信息.
Endpoints: 10.224.1.3x:6379 #這個就是Pod的地址,Serice和Pod實際上並非直接聯系,中間
#還有一個Endpoint作為轉發。所以這里顯示的是Endpoint而非Pod.
K8s中資源的全局FQDN格式:
Service_NAME.NameSpace_NAME.Domain.LTD.
Domain.LTD.=svc.cluster.local. #這是默認k8s集群的域名。
創建一個nodePort類型的service,讓節點外主機可以訪問到服務. vim myapp-svc.yaml apiVersion: v1 kind: Service metadata: name: myapp namespace: default spec: selector: app: myapp release: canary clusterIP: 10.99.99.99 #此地址設定為固定很容易沖突, 建議最好不寫,讓其自動分配. type: NodePort ports: - port: 80 #設置Service對外提供服務的端口 targetPort: 80 # 設置Pod對外提供服務的端口. nodePort: 30080 #此宿主機端口也可不指定,系統將自動從3萬~65535分配,若必須設為80,也可以, #但必須保證所以宿主機上的80端口沒有被占用,若有被占用,則該節點上的服務將無法被訪問.
Service的externalName類型原理介紹:
Pod---->SVC[externalName]------[SNAT]----->宿主機的物理網卡------>物理網關----->Internat上提供服務的服務器.
注意: Service是externelName類型時, externalName必須是域名,而且此域名必須能被CoreDNS或CoreDNS能通過
互聯網上的根DNS解析出A記錄.
Service在對外提供服務時,還支持會話粘性:
sessionAffinity : 它支持ClientIP 和 None兩種方式.
None: 即不做會話粘性,進行隨機調度.
ClientIP: 根據客戶端IP來調度,同一個客戶端IP都調度到同一個后端主機。
通過打補丁的方式來測試會話粘性:
#對上面創建的myapp service打補丁,讓其支持基於ClientIP的會話粘性.
kubectl patch svc myapp -p ‘{"spec":{"sessionAffinity":"ClientIP"}}’
kubectl describe svc myapp #可以查看到多了一個Session Affinity的字段.
headless service(無頭service):
所謂headless service指: 沒有ClusterIP的service, 它僅有一個service name.這個服務名解析得到的不是
service的集群IP,而是Pod的IP,當其它人訪問該service時,將直接獲得Pod的IP,進行直接訪問。
示例: vim myapp-svc-headless.yaml apiVersion: v1 kind: Service metadata: name: myapp-headless namespace: default spec: selector: app: myapp release: canary clusterIP: None ports: - port: 80 targetPort: 80
#創建headless service:
kubectl apply -f myapp-svc-headless.yaml
kubectl get svc #將可以看到ClusterIP項為None.
#這里將顯示service name解析為Pod的IP了.
dig -t A myapp-headless.default.svc.cluster.local. @CoreDNS_IP
#CoreDNS_IP的查看方式:
kubectl get svc -n kube-system
#查看Pod的IP:
kubectl get pods -o wide -l app=myapp
Ingress Controller
下圖即為Ingress Controller這種獨特的控制器資源的工作流程圖.
需要注意: Ingress Controller 和 Ingress是兩個不同的資源。
Ingress它是通過headless service的集群內FQDN獲取到它后端所有的Pod資源的IP,因為headless Service沒有
ClusterIP,它的域名對應的IP為PodIP。Ingress獲取PodIP后,在立刻將其寫入到ingress-nginx的配置文件中,
並觸發nginx重讀配置文件。實現動態更新upstream。
另外,外部LB可以直接跳過Service,直接訪問到nginx,這需要將nginx這個Pod作為共享宿主機的網絡名稱空間
才能實現,這時就需要借助於daemonSet控制器來控制着nginx這種七層反代Pod僅允許在指定節點上,並且
每個節點上僅運行一個nginx Pod。
#注:
DaemonSet,RepliceSet,Deployment,StatuefulSet 等它們都是Master上的ControllerManager
的子組件,而Ingress Controller則是獨立運行的一個或一組Pod資源,它通常就是一個擁有七層
調度能力的應用程序,在K8s上這種應用程序有四種:
Nginx:一般默認使用Nginx作為這種應用程序。
Traefik: 它原先設計也是為微服務而生的,就是為了能實現動態生成配置文件。
Envoy: 在服務網格 或 微服務 中通常會比較喜歡用它.
Traefik和Envoy: 它們都可以實現動態監控配置文件發生變化,若發生變化,
則會即時自動重載配置,而無需手動參與。
HAProxy: 它是最不受歡迎的一種解決方案。
查看ingress controller的定義:
kubectl explain ingress
kubectl explain ingress.spec
創建名稱空間的方式:
1. 使用命令:
kubectl create namespace test
kubectl get ns
kubectl delete ns/test #刪除一個test名稱空間, 需要注意: 刪除一個名稱空間,則會刪除其內所有的資源.
2. 使用清單創建名稱空間:
apiVersion: v1
kind: Namespace
metadata:
name: test
實現Ingress-ngnix的示例:
下面這個項目是kubernetes中ingress-nginx項目安裝說明
https://github.com/kubernetes/ingress-nginx/blob/master/docs/deploy/index.md
#由於我是直接用VMware上的VM安裝的K8s,因此使用裸機(Bare-metal)的方式來安裝.
#首先,下載這個主配置文件,它里面幫我們寫好了創建,Ingress-nginx所必須namespace,configMap,ServiceAccount,RBAC,Role,和ingress-nginx等.
# 在使用下面這清單文件時,建議先把ingress-nginx的鏡像先下載下來,避免下載鏡像失敗導致創建ingress-nginx失敗.
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
#接着, 下載裸機所對應的創建Ingress-nginx的Service配置清單.
https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/baremetal/service-nodeport.yaml
#在繼續之前先看下,當前的拓撲圖
上面mandatory.yaml 幫我們創建了Nginx-Ingres-Controller,另外還有一些資源沒有畫出來.
service-nodeport.yaml 幫我們創建了Service/Ingress-nginx
#接着我們需要自己創建Ingress 和 Service/myapp 以及三個myapp Pod vim deploy-demo.yaml apiVersion: v1 kind: Service #這部分創建service/myapp metadata: name: myapp namespace: default spec: selector: #通過下面兩個標簽(label)來選擇Pod app: myapp release: canary ports: - name: http targetPort: 80 port: 80 --- apiVersion: apps/v1 kind: Deployment #這部分來創建Myapp Pod的deployment.apps控制器 metadata: name: myapp-ingress namespace: default spec: replicas: 3 #設置其副本數量控制器ReplicaSet,監控Pod至少保證有3個 selector: matchLabels: #它篩選自己管理的Pod時,使用的label是下面兩個. app: myapp release: canary template: #replicaSet發現Pod不足時,使用此模板定義的規則,創建Pod. metadata: labels: #每次創建的Pod都打上下面兩個label app: myapp release: canary spec: containers: - name: myapp image: harbor.zcf.com/k8s/myapp:v1 ports: - name: http containerPort: 80
#接着創建ingress vim ingress-myapp.yaml apiVersion: extensions/v1beta1 kind: Ingress metadata: name: ingress-myapp namespace: default #注意ingress要和后端提供Web服務的Pod處於同一名稱空間中. annotations: #kubernetes.io為前綴,ingress.class:為鍵名,通過這種定義來告訴ingress, #這里使用的ingress-controller為nginx,你在生成配置時,生成nginx相關的配置。 kubernetes.io/ingress.class: "nginx" spec: rules: ##定義使用虛擬主機來代理后端Web應用.而識別該虛擬主機的域名定義為myapp.test.com - host: myapp.test.com http: paths: - path: backend: #這里要指明后端提供Web服務的前端Service的名稱,該Service負責篩選出提供Web服務的Pod. serviceName: myapp servicePort: 80
#以上四步,都完成后,我們就有了下面這些文件:
deploy-demo.yaml
ingress-myapp.yaml
mandatory.yaml
service-nodeport.yaml
1. kubectl apply -f mandatory.yaml #可驗證以下信息: 其它信息的驗證可參考擴展驗證. # kubectl get ns NAME STATUS AGE default Active 3d14h ingress-nginx Active 6h12m ..... # kubectl get deployments # kubectl describe deployments myapp-ingress # kubectl get replicaset # kubectl describe replicaset myapp-ingress-5b8676cff7 2. kubectl apply -f service-nodeport.yaml #驗證: # kubectl get service -n ingress-nginx ingress-nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx NodePort 172.30.245.115 <none> 80:53712/TCP,443:46652/TCP 6h36m 3. kubectl apply -f deploy-demo.yaml 4. kubectl apply -f ingress-myapp.yaml #驗證: # kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES client-f5cdb799f-2wsmr 1/1 Running 3 2d18h 10.10.97.37 192.168.111.80 <none> <none> myapp-ingress-5b8676cff7-jxmg8 1/1 Running 0 3h51m 10.10.97.60 192.168.111.80 <none> <none> myapp-ingress-5b8676cff7-s2nsf 1/1 Running 0 3h51m 10.10.171.5 192.168.111.81 <none> <none> myapp-ingress-5b8676cff7-wx5q7 1/1 Running 0 3h51m 10.10.171.4 192.168.111.81 <none> <none> # kubectl get deployment NAME READY UP-TO-DATE AVAILABLE AGE client 1/1 1 1 2d18h myapp-ingress 3/3 3 3 3h52m # kubectl describe deployment myapp-ingress Name: myapp-ingress ....... Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable StrategyType: RollingUpdate #deployment控制器默認的為滾動更新 MinReadySeconds: 0 RollingUpdateStrategy: 25% max unavailable, 25% max surge ...... NewReplicaSet: myapp-ingress-5b8676cff7 (3/3 replicas created) ...... # kubectl get replicaset NAME DESIRED CURRENT READY AGE client-f5cdb799f 1 1 1 2d18h myapp-ingress-5b8676cff7 3 3 3 3h52m # kubectl describe replicasets.apps myapp-ingress-5b8676cff7 Name: myapp-ingress-5b8676cff7 Namespace: default Selector: app=myapp,pod-template-hash=5b8676cff7,release=canary .............. Annotations: deployment.kubernetes.io/desired-replicas: 3 deployment.kubernetes.io/max-replicas: 4 #最多只允許多出一個副本,這說明,此ReplicaSet更新策略為:滾動更新,即先創建一個,然后在刪除一個。 deployment.kubernetes.io/revision: 1 Controlled By: Deployment/myapp-ingress #上游控制器是myapp-ingress,控制器類型: deployment Replicas: 3 current / 3 desired #副本數量, 當前3個,期望3個 Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed
5. 進入nginx-ingress-controller中,查看OpenResty的配置文件:
kubectl exec -n ingress-nginx -it nginx-ingress-controller-.... -- /bin/sh
$ cat nginx.conf
#這里面,內容很多,可只看重點部分
# 1. server_name myapp.test.com #這個server段,可重點看。
# 2. upstream upstream_balancer {} #這段可參考,因為它可能是通過balancer_by_lua_block {} 實現獲取Pod列表的.而該段調用的是ruby腳本,我暫時沒看懂。
6. 測試訪問:
再集群外部主機上訪問: 必須能解析 myapp.test.com 這個域名.
http://myapp.test.com:53712/
<h1>WERCOME TO www.zcf.com WEB SITE | Sun Jul 21 02:13:51 UTC 2019 | myapp-ingress-5b8676cff7-s2nsf | 10.10.171.5 | -v1- | </h1>
實驗總結:
當ingress創建完成后,就相當於它將ingress-controller 和 后端提供Web服務的Pod關聯起來了。
Pod的前端Service負責實時監控Pod的變化,並反應在自己的Endpoints中,而ingress通過定義backend為后端Service,從而與后端的Service取得聯系,並獲取Service的Endpoints列表,從而得到它所監控的Pod列表,這些Pod就是實際提供Web服務的后端容器。
當Ingress獲取到后端Pod列表后,它就可以聯系前端的ingress-controller,並根據自己annotations中的定義知道前端ingress-controller所使用的反向代理為nginx,然后ingress就會生成nginx的配置信息,並自定寫入ingress-controller-nginx中。當ingress-controller-nginx獲得配置信息后,它就可以對外提供反向代理服務了。
另外:
ingress中定義rules,指明使用虛擬主機,虛擬主機的域名為myapp.test.com,若有多個虛擬主機,則可定義多個。那么它生成的nginx配置就是定義一個server,並指明其servername為myapp.test.com ,該虛擬主機的location / { proxy_pass http://upstream }可以簡單這么理解,若有多個虛擬主機,就是定義多個server,每個server都要有獨立的域名,比如論壇,商城,博客等。
location中定義的proxy_pass 的upstream的后端服務器地址列表,則是由ingress中backend定義的serviceName所指定的myapp這個service所監控的后端Pod。
這樣以來整個訪問鏈條就構建完成了。
以上四步創建的結構如下圖 【注: nginx-ingress-controller 就是我要說明的 ingress-controller-nginx,再書寫時寫錯了】
但需要注意,ingress僅是動態監控service所監控的后端Pod是否發生變化了,若發生變化,它會立即生成nginx的最新upstream配置,然后再次注入配置到ingress-controller-nginx中,這樣ingress-controller-nginx就可以實時知道后端Pod的變化,並直接將前端Client的訪問流量代理給后端Pod,圖中看上去要先經過ingress,再轉發給Pod,實際上是ingress-controller-nginx直接將請求轉發給Pod,因為ingress已經將Pod的地址寫入到nginx的upstream中了。
下面示例演示配置一個tomcat的七層代理應用: vim tomcat-deploy.yaml apiVersion: v1 kind: Service metadata: #這個名字可根據需要定義, 如: 是一個電商站點,則可取名為 eshop 等名字. name: tomcat namespace: default spec: selector: app: tomcat release: canary ports: - name: http targetPort: 8080 port: 8080 - name: ajp targetPort: 8009 port: 8009 --- apiVersion: apps/v1 kind: Deployment metadata: name: tomcat-deploy namespace: default spec: replicas: 3 selector: matchLabels: app: tomcat release: canary template: metadata: labels: app: tomcat release: canary spec: containers: - name: tomcat image: tomcat:8.5.32-jre8-alpine ports: - name: http containerPort: 8080 ports: - name: ajp containerPort: 8009
以上配置說明:
它定義了一個Deployment的控制器,它下面有3個tomcat Pod,並且都暴露了8080和8009端口,然后還定義了一個service,此service將自己的8080端口映射到Pod的8080端口上,8009也一樣。這個service並不作為訪問這些tomcat Pod的統一入口,而是作為ingress獲取這些tomcat Pod的IP而存在的。
下面測試創建一個TLS類型的secret,然后實現使用同一套證書為兩個網站提供HTTPS
1. 使用Kubeasz部署的集群在制作證書時,必須使用cfssl工具來做,因為kubeasz是使用此工具來做證書的,它制作證書的字符編碼與OpenSSL的字符編碼不同,因此你若使用cfssl制作的ca證書給Openssl制作的證書簽證,是不可以的,但若能修改Openssl默認證書中的字符編碼,應該是可以的,但我沒有研究過如何修改。
cd /etc/kubernetes/ssl #這是kubeasz部署后,CA證書默認存放位置。
cp admin-csr.json test.com-csr.json
vim test.com-csr.json { "CN": "test.com", "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "CN", "ST": "HangZhou", "L": "XS" } ] } # grep -C1 'profile' ca-config.json }, "profiles": { "kubernetes": { # cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes test.com-csr.json | cfssljson -bare test.com
#創建好的證書,不能直接放到nginx中使用,這個證書必須做出k8s中的資源對象,然后,讓nginx來引用這個對象才行.
kubectl create secret tls website-ingress-secret --cert=website.crt --key=website.key
注:
tls: 是要創建secret的類型。
tomcat-ingress-secret:是這個secret的名字.
--cert: 指明TLS類型的secret所使用的證書在哪里。
--key: 指明證書的私鑰的位置
kubectl get secret #查看創建的secret對象
kubectl describe secret website-ingress-secret #查看tomcat-ingress-secret對象的詳細信息。
有了secret對象后,就可以創建基於HTTPS的tomcat訪問了.
vim ingress-tomcat-tls.yaml apiVersion: extensions/v1beta1 kind: Ingress metadata: name: ingress-website-tls namespace: default annotations: kubernetes.io/ingress.class: “nginx” spec: tls: - hosts: #這里的列表可定義多個主機, 意思是這個多個主機都將使用相同的證書來加密響應數據.這就是所謂的SNI - tomcat.test.com - myapp.test.com secretName: website-ingress-secret rules: #rules可定義多個, 每一個就是一個虛擬主機 或 URL映射. - host: tomcat.test.com http: paths: - path: backend: serviceName: tomcat servicePort: 8080 - host: myapp.test.com http: paths: - path: backend: serviceName: myapp servicePort: 80
以上做好以后,就可以創建支持TLS的ingress了.
kubectl apply -f ingress-tomcat-tls.yaml
kubectl get ingress
kubectl describe ingress ingress-tomcat-tls
# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx NodePort 172.30.245.115 <none> 80:53712/TCP,443:46652/TCP 9h
kubectl exec -n ingress-nginx -it nginx-ingress-controller-x... -- /bin/sh
#登陸后可查看nginx的配置文件,確認一下tomcat是否支持HTTPS.
最后,測試訪問:
https://tomcat.test.com:46652/
#測試發現,Client打開網頁后,查看證書,竟然是Kubernetes Ingress Controller頒發的證書,這是怎么回事?
#目前我還沒有找到答案,希望路過的大牛們,多多指點,萬分感謝....