Kubernetes之Ingress

從前面的學習,我們可以瞭解到Kubernetes暴露服務的方式目前只有三種:LoadBlancer Service、ExternalName、NodePort Service、Ingress;而我們需要將集羣內服務提供外界訪問就會產生以下幾個問題:

Pod 漂移問題:

Kubernetes 具有強大的副本控制能力,能保證在任意副本(Pod)掛掉時自動從其他機器啓動一個新的,還可以動態擴容等,通俗地說,這個 Pod 可能在任何時刻出現在任何節點上,也可能在任何時刻死在任何節點上;那麼自然隨着 Pod 的創建和銷燬,Pod IP 肯定會動態變化;那麼如何把這個動態的 Pod IP 暴露出去?這裏藉助於 Kubernetes 的 Service 機制,Service 可以以標籤的形式選定一組帶有指定標籤的 Pod,並監控和自動負載他們的 Pod IP,那麼我們向外暴露只暴露 Service IP 就行了;這就是 NodePort 模式:即在每個節點上開起一個端口,然後轉發到內部 Pod IP 上,如下圖所示:

Kubernetes之Ingress

端口管理問題:

採用 NodePort 方式暴露服務面臨問題是,服務一旦多起來,NodePort 在每個節點上開啓的端口會及其龐大,而且難以維護。這時,我們可以使用一個Nginx直接對內進行轉發呢?衆所周知的是,Pod與Pod之間是可以互相通信的,而Pod是可以共享宿主機的網絡名稱空間的,也就是說當在共享網絡名稱空間時,Pod上所監聽的就是Node上的端口。那麼這又該如何實現呢?簡單的實現就是使用 DaemonSet 在每個 Node 上監聽 80,然後寫好規則,因爲 Nginx 外面綁定了宿主機 80 端口(就像 NodePort),本身又在集羣內,那麼向後直接轉發到相應 Service IP 就行了,如下圖所示:

Kubernetes之Ingress

域名分配及動態更新問題:

從上面的方法,採用 Nginx-Pod 似乎已經解決了問題,但是其實這裏面有一個很大缺陷:當每次有新服務加入又該如何修改 Nginx 配置呢??我們知道使用Nginx可以通過虛擬主機域名進行區分不同的服務,而每個服務通過upstream進行定義不同的負載均衡池,再加上location進行負載均衡的反向代理,在日常使用中只需要修改nginx.conf即可實現,那在K8S中又該如何實現這種方式的調度呢???

-

假設後端的服務初始服務只有ecshop,後面增加了bbs和member服務,那麼又該如何將這2個服務加入到Nginx-Pod進行調度呢?總不能每次手動改或者Rolling Update 前端 Nginx Pod 吧!!此時 Ingress 出現了,如果不算上面的Nginx,Ingress 包含兩大組件:Ingress Controller 和 Ingress。

Kubernetes之Ingress

Ingress 簡單的理解就是你原來需要改 Nginx 配置,然後配置各種域名對應哪個 Service,現在把這個動作抽象出來,變成一個 Ingress 對象,你可以用 yaml 創建,每次不要去改 Nginx 了,直接改 yaml 然後創建/更新就行了;那麼問題來了:”Nginx 該怎麼處理?”

-

Ingress Controller 這東西就是解決 “Nginx 的處理方式” 的;Ingress Controoler 通過與 Kubernetes API 交互,動態的去感知集羣中 Ingress 規則變化,然後讀取他,按照他自己模板生成一段 Nginx 配置,再寫到 Nginx Pod 裏,最後 reload 一下,工作流程如下圖:

Kubernetes之Ingress

Ingress Controller工作架構如下,借用traefik官方的圖:

Kubernetes之Ingress

你可以將api.domain.com進來的流量路由到集羣裏api的pod上,你可以將backoffice.domain.com流量路由到backoffice的一組pod上(注意:ingress nginx是怎麼識別那些Pod是那個應該加入那個upstream呢?這裏就用到了service使用service的labels將一組服務關聯起來,雖然創建了service但是ingress controller在解析service的是時候實際是解析到後面的Pod上。)

Kubernetes之Ingress

看起來按照nginx來理解轉發是client------>nginx------>svc------>pod;
實際上轉發是client----->nginx------>pod,是直接負載到svc後面的Pod上面的

#例如上面trafik圖裏的Ingress大致就是下面這樣:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
spec:
  rules:
  - host: api.mydomain.com
    http:
      paths:
      - backend:
          serviceName: api
          servicePort: 80
  - host: domain.com
    http:
      paths:
      - path: /web/*
        backend:
          serviceName: web
          servicePort: 8080
  - host: backoffice.domain.com
    http:
      paths:
      - backend:
          serviceName: backoffice
          servicePort: 8080

#只要創建了上面的Ingress後,ingress controller裏會監聽到從而生成對應的配置段後動態reload配置文件
#另外ingress也能多路徑,如下:

spec:
  rules:
  - host: xxxx.xxxx.xxx
    http:
      paths:
      - backend:
          serviceName: service-index
          servicePort: 80
        path: /
      - backend:
          serviceName: service-test-api
          servicePort: 80
        path: /api/

部署Ingress Nginx:

使用Ingress功能步驟:(注意先後順序,如果先執行了mandatory.yaml文件在執行service-nodeport.yaml文件使用kubectl logs -f 看ingress-controller的pod的日誌會有很多報錯信息)

  • 下載Ingress-controller相關的YAML文件,並給Ingress-controller創建獨立的名稱空間命名爲ingress-nginx;
  • 創建Ingress-controller的service,以實現接入集羣外部流量;
  • 部署Ingress-controller;
  • 部署後端的服務,如tomcat,並通過service進行暴露;
  • 部署Ingress,進行定義規則,使Ingress-controller和後端服務的Pod組進行關聯。

1、創建名稱空間:

apiVersion: v1
kind: Namespace
metadata:
  name: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

2、創建Ingress-controller的service(使用Nodeport的方式實現的接入集羣外部流量)

下載YAML文件:

wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/provider/baremetal/service-nodeport.yaml

修改成如下配置:

apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  type: NodePort
  ports:
    - name: http
      port: 80
      targetPort: 80
      protocol: TCP
      nodePort: 30080
    - name: https
      port: 443
      targetPort: 443
      protocol: TCP
      nodePort: 30443
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

---

4、執行部署Ingress Nginx YAML文件

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/mandatory.yaml

5、創建後端服務

#創建後端服務的service

kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2 # tells deployment to run 2 pods matching the template
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

---
#創建後端服務的pod

kind: Service
apiVersion: v1
metadata:
  name: nginx-service
  namespace: default 
spec:
  type: ClusterIP
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 80
  selector:
    app: nginx

部署ingress:


[root@k8s-master ~]# kubectl explain ingress
KIND:     Ingress
VERSION:  extensions/v1beta1

DESCRIPTION:
     Ingress is a collection of rules that allow inbound connections to reach
     the endpoints defined by a backend. An Ingress can be configured to give
     services externally-reachable urls, load balance traffic, terminate SSL,
     offer name based virtual hosting etc.

FIELDS:
   apiVersion   <string>
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#resources

   kind <string>
     Kind is a string value representing the REST resource this object
     represents. Servers may infer this from the endpoint the client submits
     requests to. Cannot be updated. In CamelCase. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

   metadata <Object>
     Standard object's metadata. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

   spec <Object>
     Spec is the desired state of the Ingress. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

   status   <Object>
     Status is the current state of the Ingress. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status
[root@k8s-master ingress-nginx]# kubectl explain ingress.spec
KIND:     Ingress
VERSION:  extensions/v1beta1

RESOURCE: spec <Object>

DESCRIPTION:
     Spec is the desired state of the Ingress. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

     IngressSpec describes the Ingress the user wishes to exist.

FIELDS:
   backend  <Object>     #定義後端有哪幾個主機
     A default backend capable of servicing requests that don't match any rule.
     At least one of 'backend' or 'rules' must be specified. This field is
     optional to allow the loadbalancer controller or defaulting logic to
     specify a global default.

   rules    <[]Object>    #定義規則
     A list of host rules used to configure the Ingress. If unspecified, or no
     rule matches, all traffic is sent to the default backend.

   tls  <[]Object>       #HTTPS服務
     TLS configuration. Currently the Ingress only supports a single TLS port,
     443. If multiple members of this list specify different hosts, they will be
     multiplexed on the same port according to the hostname specified through
     the SNI TLS extension, if the ingress controller fulfilling the ingress
     supports SNI.

多個Ingress controllers:

如果您正在運行多個Ingress controllers,在本機的Ingress controllers或者雲提供商上(GKE),則需要kubernetes.io/ingress.class: "nginx"

例如:

metadata:
  name: foo
  annotations:
    kubernetes.io/ingress.class: "gce"

將以GCE控制器作爲目標,迫使它忽略nginx控制器。

metadata:
  name: foo
  annotations:
    kubernetes.io/ingress.class: "nginx"

將以目標對準nginx控制器,迫使GCE控制器忽略它。

Rewrite(重寫):

必須條件:您需要通過指定一個Ingress controllers,以確保您的Ingress controllers目標ingress.class 的annotations標籤。

ingress可以使用以下annotations標籤控制重寫
名稱 描述
nginx.ingress.kubernetes.io/rewrite-target 必須重定向流量的目標URI string
nginx.ingress.kubernetes.io/ssl-redirect 指示位置部分是否僅可訪問SSL(當Ingress包含證書時默認爲True) bool
nginx.ingress.kubernetes.io/force-ssl-redirect 即使Ingress未啓用TLS,也強制重定向到HTTPS bool
nginx.ingress.kubernetes.io/app-root 定義Controller必須重定向的應用程序根,如果它在'/'上下文中 string
nginx.ingress.kubernetes.io/use-regex 指示Ingress上定義的路徑是否使用正則表達式 bool

編寫ingress的配置清單:

[root@k8s-master ingress]# vim ingress-myapp.yaml
apiVersion: extensions/v1beta1      #api版本
kind: Ingress       #清單類型
metadata:           #元數據
  name: ingress-myapp    #ingress的名稱
  namespace: default     #所屬名稱空間
  annotations:           #註解信息
    kubernetes.io/ingress.class: "nginx"
spec:      #規格
  rules:   #定義後端轉發的規則
  - host: nginx.testdomain.com    #通過域名進行轉發
    http:
      paths:       
      - path:       #配置訪問路徑,如果通過url進行轉發,需要修改;空默認爲訪問的路徑爲"/"
        backend:    #配置後端服務
          serviceName: nginx-service
          servicePort: 8080

找到ingress nginx的pod名字後通過命令查看裏面nginx配置文件能找到有對應的配置段生成

$ kubectl -n ingress-nginx exec nginx-ingress-controller-6cdcfd8ff9-t5sxl -- cat /etc/nginx/nginx.conf
...
    ## start server nginx.testdomain.com
    server {
        server_name nginx.testdomain.com ;

        listen 80;

        set $proxy_upstream_name "-";

        location / {

            set $namespace      "default";
            set $ingress_name   "nginx-ingress";
            set $service_name   "nginx";
            set $service_port   "8080";
            set $location_path  "/";
            ........
    ## end server nginx.testdomain.com      
...

找一臺非集羣的機器,設置hosts文件把域名nginx.testdomain.com設置到對svc的那個node的ip上,打開瀏覽器訪問nginx.testdomain.com即可發現集羣內的nginx已經暴露在集羣外。

Node節點IP

192.168.91.22 nginx.testdomain.com
192.168.91.22 tomcat.testdomain.com

構建TLS站點:

(1)準備證書

[root@k8s-master ingress]# openssl genrsa -out tls.key 2048 
Generating RSA private key, 2048 bit long modulus
.......+++
.......................+++
e is 65537 (0x10001)

[root@k8s-master ingress]# openssl req -new -x509 -key tls.key -out tls.crt -subj /C=CN/ST=Beijing/L=Beijing/O=DevOps/CN=tomcat.magedu.com

(2)生成secret

[root@k8s-master ingress]# kubectl create secret tls tomcat-ingress-secret --cert=tls.crt --key=tls.key
secret/tomcat-ingress-secret created
[root@k8s-master ingress]# kubectl get secret
NAME                    TYPE                                  DATA      AGE
default-token-j5pf5     kubernetes.io/service-account-token   3         39d
tomcat-ingress-secret   kubernetes.io/tls                     2         9s
[root@k8s-master ingress]# kubectl describe secret tomcat-ingress-secret
Name:         tomcat-ingress-secret
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  kubernetes.io/tls

Data
====
tls.crt:  1294 bytes
tls.key:  1679 bytes

(3)創建ingress

[root@k8s-master ingress]# cp ingress-tomcat.yaml ingress-tomcat-tls.yaml
[root@k8s-master ingress]# vim ingress-tomcat-tls.yaml 
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-tomcat-tls
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  tls:
  - hosts:
    - tomcat.magedu.com
    secretName: tomcat-ingress-secret
  rules:
  - host: tomcat.magedu.com
    http:
      paths:
      - path:
        backend:
          serviceName: tomcat
          servicePort: 8080

[root@k8s-master ingress]# kubectl apply -f ingress-tomcat-tls.yaml 
ingress.extensions/ingress-tomcat-tls created
[root@k8s-master ingress]# kubectl get ingress
NAME                 HOSTS               ADDRESS   PORTS     AGE
ingress-myapp        myapp.magedu.com              80        4h
ingress-tomcat-tls   tomcat.magedu.com             80, 443   5s
tomcat               tomcat.magedu.com             80        1h
[root@k8s-master ingress]# kubectl describe ingress ingress-tomcat-tls
Name:             ingress-tomcat-tls
Namespace:        default
Address:          
Default backend:  default-http-backend:80 (<none>)
TLS:
  tomcat-ingress-secret terminates tomcat.magedu.com
Rules:
  Host               Path  Backends
  ----               ----  --------
  tomcat.magedu.com  
                        tomcat:8080 (<none>)
Annotations:
  kubectl.kubernetes.io/last-applied-configuration:  {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"nginx"},"name":"ingress-tomcat-tls","namespace":"default"},"spec":{"rules":[{"host":"tomcat.magedu.com","http":{"paths":[{"backend":{"serviceName":"tomcat","servicePort":8080},"path":null}]}}],"tls":[{"hosts":["tomcat.magedu.com"],"secretName":"tomcat-ingress-secret"}]}}

  kubernetes.io/ingress.class:  nginx
Events:
  Type    Reason  Age   From                      Message
  ----    ------  ----  ----                      -------
  Normal  CREATE  20s   nginx-ingress-controller  Ingress default/ingress-tomcat-tls

(4)訪問測試:https://tomcat.magedu.com:30443

Kubernetes之Ingress

Ingress Controller高可用:

這裏來討論下Ingress Controller的Nginx pod高可用,上面的例子裏svc我使用的nodePort,Nodeport端口不是web端口(但是可以修改Nodeport的範圍改成web端口),如果進來流量負載到Nodeport上可能某個流量路線到某個node上的時候因爲Ingress Controller的pod不在這個node上,會走這個node的kube-proxy轉發到Ingress Controller的pod上,多走一趟路。(例如:我們Ingress Controller的pod在192.168.91.22上,但是你域名解析的是192.168.91.21 nginx.testdomain.com這樣,這個時候192.168.91.22這個node上的kube-proxy會把請求轉發到192.168.91.21上)。故建議使用daemonset+nodeSelector的方式來ingress controller的Pod

Ingress Controller部署方式沒多大區別開心就好:

daemonSet + nodeSeletor (一個Node節點運行一個Ingress Controller的Pod,當有多個Node的時候可以使用污點或者通過指定nodeSelector來指定部分主機來運行Ingress Controller的Pod)
deploy設置replicas數量 + nodeSeletor + pod互斥

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章