K8s Ingress提供了一種方式來管理HTTP和HTTPS流量的入口,通常用於將流量路由到K8s集群內的服務。Ingress資源使用規則來定義如何將流量路由到Kubernetes集群內的服務,上一篇我們使用Service的NodePort將服務提供給外部網路存取,但是NodePort本身的缺點(詳細的缺點可以【點我前往】參考)因此實戰上不太會直接使用這個方式,所以我們需要Ingress將對外的Port根據HostName或是PathName決定我們要將流量轉發到哪一個Service上,以下是Ingress可以實現的功能:
  1. 路由流量:根據特定的路徑或主機名稱將流量路由到不同的服務。
  2. 加密通信:使用TLS來保護HTTP和HTTPS流量的安全傳輸。
  3. 負載平衡:可以將流量分配到多個後端服務,以實現負載平衡和高可用性。
  4. URL重新寫入:可以將流量的URL重寫為另一個URL,以提供更友好的URL。

以下是使用NodePort和Ingress時URL的差異:

使用NodePort時的URL

  • https://www.xxxx.com:30015
  • https://www.xxxx.com:30016

使用Ingress時的URL

  • https://www.xxxx.com/web-app
  • https://www.xxxx.com/web-api
💡 可以看到使用Ingress路由的方式,在辨識度上會優於使用Port的方式

Ingress可以透過多種方式進行配置,如「Nginx」「Traefik」「HAProxy」等常見的反向代理服務,它們都提供了自己的Ingress Controller實現,用於管理Ingress資源並處理流量,選擇哪一種反向代理就看大家喜歡哪一種服務囉!
K8s官網的Ingress流程圖

💡 Ingress本身不是一種Service type而是一種介面




Nginx Ingress Controller安裝

官方的描述,使用Ingress之前必須先安裝Ingerss Controller,如果單獨創建Ingress資源是不會有任何效果的哦!目前K8s支持和維護的Controller有AWS、GCE和我們要使用的Nginx Ingress其餘的控制器可以【點我前往】參考,這裡我們就直接選擇「Nginx Ingress」作為Ingress Controller安裝指南可以【點我前往】,接著輸入以下指令部署
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.5.1/deploy/static/provider/cloud/deploy.yaml
💡 指令包含了Nginx Ingress版本號,如果需要其它版本可以【點我前往】查詢


部署完成後使用以下指令查看Ingress的Pod是否有運作
kubectl get pods --namespace=ingress-nginx


如果想要查看所有關於Ingress-nginx的資源則可以使用以下指令
kubectl get all -n ingress-nginx


可以看到Nginx Ingress啟動了Service並且指到80和443Port,所以我們可以使用localhost去瀏覽器上執行看看就可以看到404的頁面囉(由於我們還沒有指定路由規則因此404是正常的)
💡 由於還沒有指定路由規則,因此看到404是正常的





非雲K8s集群 - MetalLB安裝

K8s預設並沒有提供一個功能完整的LoadBalancer實現。因此,需要使用一些第三方的輔助工具,因此如果安裝了Ingress Controller,在LoadBalancer都會是處於一個Pending的狀態,關於這些問題的相關提問可以【點我前往】參考
💡 以下環境預設都有包含LoadBalancer實現:「雲端」、「Docker Desktop Kubernetes」、「Minikube」

因此在我們自行搭設的K8s環境下就必須要安裝額外的套件來解決負載平衡的問題,Nginx Ingress提供了第三方的套件「MetalLB」相關資訊可以【點我前往】,首先需要先安裝MetalLB,直接使用以下指令,其餘安裝資訊可以【點我前往
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.7/config/manifests/metallb-native.yaml


安裝完成後接著是定義地址的連線池【點我前往】,我們先使用指令查看目前的Node的IP資訊
kubectl get node -A -o wide


創建一個metallb-addressconfig.yaml的檔案,將我們的Node IP設定給連線池,設定好就可以部署上去
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default
  namespace: metallb-system
spec:
  addresses:
  - 192.168.135.129-192.168.135.129
  autoAssign: true
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
spec:
  ipAddressPools:
  - default
💡Ingress Controller是Run在192.168.135.129的Node身上,因此只需要為該節點配置一個IP位置即可
kubectl apply -f metallb-addressconfig.yaml


最後再查看一次Ingress的詳細狀態就可以看到External-IP的欄位不再是Pending狀態囉!
kubectl get all -n ingress-nginx





編寫Ingress定義檔

編寫定義檔之前我們先規劃Nginx Ingress的路由規則,我希望根路由可以直接連到Web APP而Web API則是在子路由之下,定義如下:

  • Web APP:http://localhost
  • Web API:http://localhost/api
  • Web API Swagger UI:http://localhost/api/Swagger/index.html
由於Nginx Ingress的原理是反向代理,因此如果要讓Web API可以處理虛擬路徑,就必須添加兩個MiddleWare(Web API專案為ASP Dot Net Core 6.0)才行,詳細的內容可以到微軟官網查看【點我參考】或是參考大神解說【點我參考
app.UsePathBase("/xxxx");
app.UseRouting();
💡注意!UsePathBase後一定要UseRouting才會套用路由機制哦!
💡將UsePathBase調整成環境變數方便我們在K8s定義檔去做路由的定義,我們在appSettings.json裡面內添加一個【HostUrl__PathBase】的環境變數

Swagger的部分則是需要調整SwaggerEndpoint的路徑
if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint("v1/swagger.json", "v1");
        });
    }
💡注意!v1前面不能有斜線!
2023/03/17 補充:需要在ConfigureServices方法裡正確配置Swagger,並指定basePath
services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });

    // 設置basePath,例如:/api
    options.DocumentFilter<BasePathFilter<("/api");
});
上面的BasePathFilter是一個自定義過濾器
public class BasePathFilter : IDocumentFilter
{
    private readonly string _basePath;

    public BasePathFilter(string basePath)
    {
        _basePath = basePath;
    }

    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        swaggerDoc.Servers.Add(new OpenApiServer { Url = _basePath });
    }
}


調整後定義檔請參考以下:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wadweb-deployment
  labels:
    app: wadweb-deployment
spec:
  selector:
    matchLabels:
      app: wadweb
  replicas: 2
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      name: wadweb
      labels:
        app: wadweb
    spec:
      containers:
      - name: wadwebapp
        image: "你的registryURL/wad-web:v2.0.0"
				imagePullPolicy: Always
        resources:
          limits:
            cpu: 200m
            memory: 500Mi
          requests:
            cpu: 100m
            memory: 200Mi
        ports:
          - containerPort: 80
            name: http
      - name: wadwebapi
        image: "你的registryURL/wad-web-api:v2.0.0"
				imagePullPolicy: Always
        resources:
          limits:
            cpu: 2000m
            memory: 2Gi
          requests:
            cpu: 1000m
            memory: 1Gi
        env:
          - name: DOTNET_RUNNING_IN_CONATINER
            value: "true"
          - name: ASPNETCORE_ENVIRONMENT
            value: "Development"
          - name: ASPNETCORE_URLS
            value: "http://+:8080"
          - name: ConnectionStrings__DefaultConnection
            value: "Server=你的SQLServer Connection;Initial Catalog=WADREQM;Persist Security Info=False;User ID=你的SQLServer帳號;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=true;Connection Timeout=30;"
          - name: Connection__Key
            value: "我是密碼"
          - name: HostUrl__PathBase
            value: "api"            
          - name: HostUrl__RemoteHost
            value: "http://192.168.1.34:8080"
          - name: TZ
            value: "Asia/Taipei"
        volumeMounts:
          - name: fileupload
            mountPath: /app/upload
        ports:
          - containerPort: 8080
            name: http        
      volumes:
        - name: fileupload
          hostPath:
            path: /run/desktop/mnt/host/d/Kubernetes/fileupload
            type: DirectoryOrCreate
      restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: wadweb-service
spec:
  type: ClusterIP
  selector:
    app: wadweb
  ports:
  - name: wadwebapp-port
    port: 80
    targetPort: 80
  - name: wadapiapp-port
    port: 8080
    targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: wadweb-ingress
  # annotations:
    # nginx.ingress.kubernetes.io/ssl-redirect: "false" 
    # nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: wadweb-service
            port:
              number: 8080
			- path: /
        pathType: Prefix
        backend:
          service:
            name: wadweb-service
            port:
              number: 80
---
  • metadata.annotations:這裡可以設定一些Ingress的客製化功能,如果是Nginx就可以設定如:rewrite-target、ssl-redirect等等的設定,詳細內容可以參考官網【點我前往
    • nginx.ingress.kubernetes.io/rewrite-target:重寫請求的URL的路徑,在不修改或是無法修改程式碼的情況就可以使用,詳細內容可以參考官網【點我前往
    • 💡 在正式環境我會直接用rewrite target,因為不是所有的服務你都可以修改,因此用rewrite target是我覺得最彈性的方式
  • spec.ingressClassName:設定Ingress的類別,如果沒有設定或被省略則需要配置一個默認Ingress類(默認值是nginx),詳細內容可以參考官網【點我前往
💡設定必須和Ingress Controller定義檔裡面的環境變數「ingress-class」一樣
  • spec.rules.http.paths[].path:路由路徑名稱
  • spec.rules.http.paths[].pathType:路徑匹配規則,可以依照使用情景設定
    • Preflix:以/為分隔的URL路徑前綴匹配,前綴符合就符合規則,匹配有區分大小寫
    • Exact:精確匹配URL路徑,且區分大小寫
    • 💡關於Preflix和Exact的詳細規則可以【點我前往】參考
  • spec.rules.http.paths[].backend:設定對應的服務以及其名稱和Port
  • 💡Web API的路由設定為/api,對應到wadweb-service服務Port是8080 Web APP則直接使用根目錄,對應到wadweb-service服務Port則是80



部署設定好的Ingress定義檔

準備好定義檔就可以直接使用指令部署上去

部署後可以使用以下指令查看Ingress資源,在詳細資源裡面可以看到根路由和子路由/api的入口已經指定到我們的Service以及其對應的Port了
kubectl get ingress
kubectl describe ingress wadweb-ingress



最後打開Chrome上直接使用定義好的路由,輸入http://localhost」http://localhost/api」就可以連到Web APP和Web API囉!
使用Nginx Ingress的根路由可以導向Web APP

使用Nginx Ingress的子路由/api/Swagger/index.html則可以導向Web API Swagger UI介面


API的部分使用PostMan打「http://localhost/api」Web API
使用Nginx Ingress的子路由/api

如果我們有好幾個服務,每個服務都有各自的Port,在管理上也真的是很麻煩,因此我們透過Nginx Ingress將外部所有的流量集中起來,再經過設定好的的路由,把流量透過Service分派到各個Pod裡面,最終我們只需要管理一個Ingress和定義好路由規則即可,而Ingress本身又有負載平衡的功能【L7 LoadBalancer】就不會因為使用LoadBalance類型的Service(每個雲服務供應商的價格都不一樣)造成成本高的問題了!