K8s Service原理介紹

Service的工做方式有三種:
 第一種: 是Userspace方式
  以下圖描述, Client Pod要訪問Server Pod時,它先將請求發給本機內核空間中的service規則,由它再將請求,
  轉給監聽在指定套接字上的kube-proxy,kube-proxy處理完請求,並分發請求到指定Server Pod後,再將請求
  遞交給內核空間中的service,由service將請求轉給指定的Server Pod。
  因爲其須要來回在用戶空間和內核空間交互通訊,所以效率不好,接着就有了第二種方式.前端

  

 第二種: iptables模型
  此工做方式是直接由內核中的iptables規則,接受Client Pod的請求,並處理完成後,直接轉發給指定ServerPod.node

  

  第三種: ipvs模型
  它是直接有內核中的ipvs規則來接受Client Pod請求,並處理該請求,再有內核封包後,直接發給指定的Server Pod。nginx

  

  注:
  以上不論哪一種,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是否被移除或新增了,而後動態更新調度的節點數。git

#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集羣的域名。github

 

建立一個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,進行直接訪問。web

示例:
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。redis

  

 #注:
  DaemonSet,RepliceSet,Deployment,StatuefulSet 等它們都是Master上的ControllerManager
 的子組件,而Ingress Controller則是獨立運行的一個或一組Pod資源,它一般就是一個擁有七層
   調度能力的應用程序,在K8s上這種應用程序有四種:
  Nginx:通常默認使用Nginx做爲這種應用程序。
  Traefik: 它原先設計也是爲微服務而生的,就是爲了能實現動態生成配置文件。
  Envoy: 在服務網格 或 微服務 中一般會比較喜歡用它.
  Traefik和Envoy: 它們均可以實現動態監控配置文件發生變化,若發生變化,
          則會即時自動重載配置,而無需手動參與。
  HAProxy: 它是最不受歡迎的一種解決方案。json

 

查看ingress controller的定義:
  kubectl explain ingress
  kubectl explain ingress.specvim

 

建立名稱空間的方式:
  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頒發的證書,這是怎麼回事?
  #目前我尚未找到答案,但願路過的大牛們,多多指點,萬分感謝....

  

相關文章
相關標籤/搜索