Introduction to Kubernetes Ingress (Nginx)

Preface

為了能夠讓使用者能夠更直接順利的存取到 kubernetes 眾多運行的 Pods, kubernetes 花了很大把的精力在網路架構與使用這方面,之前已經詳細介紹過 Kubernetes Service用法以及原理
這篇要來跟大家介紹另外一個相輔相成且好用的概念,也就是所謂的 Ingress. 透過 Ingress 我們能夠提供一些更方便的伺服器存取,不論是基於 URL 的存取導向,亦或是簡化整個 SSL 憑證部署的方式
都能夠簡單地完成。 為了能夠順利使用 SSL 憑證,我們也可以搭配 Cert-Manager 來進行憑證的處理,並且將其與 Ingress 給整合。

本文主要會從 Ingress 的基本概念出發,介紹其基本架構並且從最常用的 Ingress Nginx 作為一個使用範例,來介紹實際上整個 Kubernetes 集群內是如何運作的

Introduction

Kubernetes 有一個非常有趣且迷人的地方,就是大量的抽象化,部分的功能則是公開其介面,依賴第三方廠商自行實現該介面來提供此功能。
譬如以前有提過的 Network Policy 就是這樣的一種概念,不同的網路解決方案提供者可以針對自己的應用特性來實現對應的 Network Policy 進而提供多一層的網路保護。
Ingress 也是一個類似的架構,接下來會仔細介紹一下其架構以及如何使用。最後則是會使用 Nginx Ingress Controller 當作一個範例來解釋整個運作

Architecture

KubernetesIngress 裡面我們可以設定一些如何轉發封包的選項,範例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
name: nginx-demo
namespace: default
spec:
rules:
- host: nginx.example.com
http:
paths:
- path: /v1
backend:
serviceName: nginx
servicePort: http
- path: /v2
backend:
serviceName: nginx-v2
servicePort: http
- host: note.example.com
http:
paths:
- path: /
backend:
serviceName: jupyter
servicePort: http

Ingress 內我們可以針對 Host 或是 Path 不同的選項來決定該封包要怎麼轉發。以上述範例來說,我們希望達到的是

  1. 如果看到的是 note.example.com 可以送給特定的 service
  2. 如果看到的是 nginx.example.com ,則根據後面的 path (v1/v2) 來決定最後要怎麼轉發。

這邊要特別注意的是,對於 Kubernetes 來說, Ingress 物件本身只有描述的功用,實際上並不會真的把使用者所描述與敘述的功能給實現完畢,這部分需要依賴剩下的元件來補足。

接下來我們使用下列這張圖示來解釋一下一個完整 Ingress 的架構。

Imgur

圖中標示為 Ingress Resource 的元件就是使用者們透過 Ingress Yaml 去描述預期行為的設定檔,也就是上圖的部分。
綠色的 Backend server 則是後端不同類型的服務器,使用者會預期 Ingress 可以根據 Host/Path 等不同的規則將對應的封包轉發到後端真正服務的 Backend Server.

接下來真正重要的就是 Ingress-Controller 以及 Ingress-Server.
Ingress-Server 普遍上來說,就是一個能夠接受 HTTP/HTTPS 連線的網路伺服器,以本篇文章來說就是 Nginx.
過往的使用經驗上,我們的確可以透過 nginx.conf 的方式來設定 nginx server. 來達到根據不同的情況來決定不同的封包轉發等行為。
但是在 kubernetes ingress 的架構下,使用者並不一定熟悉 nginx.conf 的格式與撰寫,而熟悉的只有 Kubernetes Ingress 的格式。
在此條件下,我們需要一個轉換者,該轉換者能夠將 Ingress Resource 的設定轉換成 Ingress-Server (Nginx) 所能夠處理的格式。
這個角色也就是所謂的 Ingress-Controller.

將上述的設定與使用流程以順序來看

  1. 使用者透過 yaml 部署 ingress 設定到 kubernetes 裡面
  2. Ingress-Controller 偵測到 Ingress Resource 的更動,讀取該設定後產生對應的 Nginx.confIngress-Server 使用
  3. 外部使用者嘗試存取服務,該封包會先到達 Ingress-Server(Nginx).
  4. Ingrss-Server(Nginx) 根據 nginx.conf 的設定決定將該封包轉發到後段的服務器 backend server.

從上述的概念來說,我們可以簡單歸納一下 Ingress 的架構

  1. Kubernetes 本身只提供一個統一的 Ingress 介面,本身不參與該介面的實作
  2. 服務提供者本身必須要實現 Ingress-Server 以及 Ingress-Controller 這兩個元件將使用者描述的抽象概念轉換成實際上可以運作的設定

所以可以看到目前現實上有非常多的 Ingress 實作,不論是 Traefik, Kong, Nginx 甚至是各個公有雲(GKE/AKS/EKS)都有跟自行架構更加整合的實現。

關於 Ingress 更多的概念可以參考官方文件

Annotation

有實際上使用過 Ingress 的玩家會發現在 Yaml 內有各式各樣的 annotation 要使用,譬如

1
2
3
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /

首先,根據上述的架構概念,我們可以知道 Ingress-Controller 會去讀取 Ingress 的設定,然後來進行後續的動作。
假想一個情況,系統內同時有多個 Ingress-Controller 的實現,使用者要如何指派該 Ingress 要使用哪一個 Ingress-Controller 來使用?

再者個情況下,我們可以透過 annotations 的方式來加註一些額外的資訊,當然這些資訊不是標準的,反而是各個 Ingress-Controller 自行決定看到什麼樣的資訊來進行什麼處理。

此外,不同的 Ingress-Server 提供的功能與使用方式都不同,基本上 Ingress Resource 很難有一個完美的介面來滿足所有的實現,因此大部分的情況下,不同的 Ingress-Controller/Ingress-Server 都會要求使用者在 Annotation 的部分使用特定的字眼來描述額外的功能,譬如

1
nginx.ingress.kubernetes.io/rewrite-target: /

所以在選擇 Ingess 的使用上,遇到任何問題的時候,如果是 Ingress Resource 的介面問題,則可以尋求 Kubernetes 官方文件的幫助,如果是更細緻的需求,則該查詢 Ingress-Controller 的說明文件,看看自己的需求與相對應的設定是否有辦法完成。

Compare

Kubernetes Service 比較起來,兩者都在提供便捷的網路存取服務
Server 針對的單位主要是 Pod(Container), 提供一個更方便的方式讓用戶端可以不用在意後端 Pod(Container) 的真實 IP 地址。
Ingress 目前的使用上更偏向是 HTTP/HTTPS 的應用,在上述的 Service 上搭建一層更方便的服務,可以根據 Host(NameBasd Virtaul Hosting) 或是 Path(Fanout) 來決定後續真正轉發的對象,而該對象則是不同後端服務所搭建起來的 Service.

因此在使用上,這兩者並沒有誰取代誰的問題,反而是根據需求來使用,大部分情況下都是互相整合來提供更方便與好用的功能。

Example

上述已經介紹完關於 Ingress 的基本概念,接下來要使用 Nginx 作為 Ingress-Server 來實際搭建一個 Ingress 的範例。

接下來的所有範例文件都可以在 KubeDemo 內找到對應的檔案。

該範例的架構圖如下
Imgur

我們會在 Kubernetes 裡面進行下列部署

  1. 搭建一個基於 NginxIngress-Controller/Ingress-Server
  2. 透過 Deployment 部署三套不同的後端服務,分別是 Jupyter Notebook 以及兩個有者不同 Index.htmlNginx Server.
  3. 透過三個不同的 kubernetes service 將上述的 Deployment 包裝起來提供更方便的存取功能
  4. 部署對應的 Ingress, 希望可以完成
    • 存取 note.example.com 會存取到 jupyter notebook
    • 存取 nginx.example.com/v1 會存取到 nginx
    • 存取 nginx.example.com/v2 會存取到 nginx-v2

接下來我們就要依據上述的概念進行相關檔案的部署。

我的測試環境是基於 MAC 上透過 Vagrant 創建一個 UbuntuOS 並且使用 Kubeadm 實現的一個小型 Kubernetes 叢集。

Nginx Controller

首先我們要先部署 Nginx Controllerkubernetes 叢集內,詳細的安裝方式可以參閱其官網

首先我們要先安裝相關的資源,譬如 RBAC 以及相關的 Deployment.

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

接下來為了讓外界的服務可以存取到該 Nginx Server (Ingress-Server),這邊會根據你的機器環境而有所不同。
以我 Baremetal 的環境,我需要部署下列的資源,透過 Kubernetes Service NodePort 的方式讓我的 Nginx Server 可以被外界存取

1
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/provider/baremetal/service-nodeport.yaml

上述的部屬都會安裝到 ingress-nginxkubernetes namespace 上,所以透過下列的指令觀察安裝的情形

1
2
3
4
5
6
7
8
9
10
11
12
$ kubectl -n ingress-nginx get all
NAME READY STATUS RESTARTS AGE
pod/nginx-ingress-controller-d88dbf49c-9b6td 1/1 Running 0 23h

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ingress-nginx NodePort 10.111.134.97 <none> 80:32663/TCP,443:31309/TCP 1d

NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx-ingress-controller 1 1 1 1 1d

NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-ingress-controller-d88dbf49c 1 1 1 1d

Outside Internet Access

為了從外部MAC的瀏覽器進行測試,我對機器上的 /etc/hosts 進行了相關的修改, 加上了下列的資料,讓我可以透過相關的設定來存取該 NodePortNginx(Ingress-Server).

1
172.17.8.101 nginx.example.com note.example.com

172.17.8.101 是我VM(Ubuntu)Virtaul IP address.

Backend Servers

Jupyter

基本上需要的就是 Deployment 配上一個對應的 Service 即可
詳細的請參閱 KubeDemo

1
kubectl apply -f https://raw.githubusercontent.com/hwchiu/kubeDemo/master/ingress/jupyter.yml

Nginx

類似上述 Jupyter 的安裝流程,但是為了客製化 index.html 的內容,會額外部署一個 configMap 來產生不同的內容

1
2
kubectl apply -f https://raw.githubusercontent.com/hwchiu/kubeDemo/master/ingress/nginx.yaml
kubectl apply -f https://raw.githubusercontent.com/hwchiu/kubeDemo/master/ingress/nginx2.yaml

這些應用程式都會部署到 default 這個 namespace,所以可以用下列指令確保部署正確

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
kubectl -n default get all
NAME READY STATUS RESTARTS AGE
pod/jupyter 1/1 Running 0 15h
pod/nginx-7dd9f89db4-tvfkk 1/1 Running 0 15h
pod/nginx-v2-5c45597f57-746p8 1/1 Running 0 15h

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/jupyter LoadBalancer 10.106.190.88 <pending> 80:32444/TCP 15h
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 89d
service/nginx ClusterIP 10.110.237.87 <none> 80/TCP 15h
service/nginx-v2 ClusterIP 10.103.43.44 <none> 80/TCP 15h

NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx 1 1 1 1 15h
deployment.apps/nginx-v2 1 1 1 1 15h

NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-7dd9f89db4 1 1 1 15h
replicaset.apps/nginx-v2-5c45597f57 1 1 1 15h

上述服務完畢後,先用 curl 針對三個 serviceClusterIP 去確認服務有正常起來

1
2
3
4
5
6
$ curl 10.110.237.87
Nginx V1
$ curl 10.103.43.44
Nginx V2
$ curl 10.106.190.88/tree
....

Deploy Ingress

上述相關的服務都部署完畢後,接下來就要部署 Ingress 物件進去,我們期望的行為是 Nginx Controller 能夠讀取這個 Ingress 的物件並且產生對應的 nginx.confNginx Server(Ingress-Server) 使用.
所以先來看一下對應的 Ingress Resource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /
name: nginx-demo
namespace: default
spec:
rules:
- host: nginx.example.com
http:
paths:
- path: /v1
backend:
serviceName: nginx
servicePort: http
- path: /v2
backend:
serviceName: nginx-v2
servicePort: http
- host: note.example.com
http:
paths:
- path: /
backend:
serviceName: jupyter
servicePort: http

該物件部署完畢後,透過下列指令觀察部署結果

1
2
3
$ kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
nginx-demo nginx.example.com,note.example.com 80 15h

Check Nginx

接下來我們將直接進入到 Nginx Controller 去觀察一下是否有對應的 nginx.conf 被產生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ kubectl -n ingress-nginx exec -it $(kubectl -n ingress-nginx get pods -l app.kubernetes.io/name=ingress-nginx -o jsonpath="{.items[0].metadata.name}") bash
[email protected]:/etc/nginx$
[email protected]:/etc/nginx$ grep "example.com" nginx.conf
## start server nginx.example.com
server_name nginx.example.com ;
## end server nginx.example.com
## start server note.example.com
server_name note.example.com ;
## end server note.example.com
[email protected]:/etc/nginx$ grep "v1" nginx.conf
ssl_protocols TLSv1.2;
location ~* "^/v1\/?(?<baseuri>.*)" {
set $location_path "/v1";
rewrite "(?i)/v1/(.*)" /$1 break;
rewrite "(?i)/v1$" / break;
[email protected]:/etc/nginx$ grep "v2" nginx.conf
location ~* "^/v2\/?(?<baseuri>.*)" {
set $service_name "nginx-v2";
set $location_path "/v2";
set $proxy_upstream_name "default-nginx-v2-http";
rewrite "(?i)/v2/(.*)" /$1 break;
rewrite "(?i)/v2$" / break;

到這邊為止,基本上的一切都順利設定完畢了,接下來就可以開啟瀏覽器嘗試去瀏覽看看.

Access the Nginx Server

因為我們的 Nginx Server 是基於 NorePort 的方式來供對外存取,所以我們要先確認一下開啟的NodePort資訊是什麼

1
2
3
$ kubectl -n ingress-nginx get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx NodePort 10.111.134.97 <none> 80:32663/TCP,443:31309/TCP 1d

在我的範例環境中,預設 HTTP 所使用的的連接埠是 32663, 因此等下需要使用這個資訊來進行測試連線

首先,我們先針對 nginx.example.com 進行測試,結果如下圖
Imgur
Imgur

結果如預期般,可以透過不同的 PATH 來導向不同的服務後端

接下來測試 node.example.com, 看看是否能夠針對 host 來導向不同的後端

Imgur

的確也能夠正常運作,意味者我們的 Ingress 測試滿順利的,都能夠如預期般的運作

Summary

本篇文章旨在透過簡單的介紹讓大家知道 Kubernetes Ingress 的基本架構與介紹,最後透過一個常用的 Nginx Ingress 實作來實際使用看看Ingress 的架構。
不同的 Ingress 的後端實現功能的方法都不盡相同,同時能夠支援的功能也都會有些許的差異,這部分就仰賴各位在選擇對應的解決方案時的研究與先行測試。