---
lang: zh-tw
---
第八章 使用Ingress執行HTTP負載平衡
===
原本讓外部訪問svc的方式:
NodePort --> 每個svc連接唯一的port
LoadBalancer --> 每個svc都要對應一個LoadBalancer
#### ingress可以使用單一的入口點來管理外部訪問
在Kubernetes中,基於HTTP的負載平衡系統稱為Ingress
* 將其組態配置標準化
* 成為正規的Kubernetes物件
* 將多個Ingress物件合併到單一負載平衡器

可以像建立Kubernetes其他物件一樣,建立或修改Ingress物件,但需安裝需要的控制器。
沒有一個HTTP LoadBalancer可以通用,AWS(ELB). GCP(Cloud Load Balancing)也都有提供LoadBalancer的功能,並提供各自的ingress controller去建立ingress。
### 安裝 NGINX ingress controller
因為在本地只能用NodePort
NGINX會用NodePort當作單一入口點,後續就透過ingress來做流量分流
```
# install helm
$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
$ chmod 700 get_helm.sh
$ ./get_helm.sh
```
```
# install NGINX ingress controller using helm
helm repo add nginx-stable https://helm.nginx.com/stable
helm repo update
helm install nginx-ingress nginx-stable/nginx-ingress --set rbac.create=true
```
```
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-ingress-controller-676fc965f7-f9r9h 1/1 Running 0 40m
```
```
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 105m
nginx-ingress-controller LoadBalancer 10.111.242.125 localhost 80:31420/TCP,443:30138/TCP 44m
```
EXTERNAL-IP欄位,會是IP位址(GCP.Azure)或主機名(AWS)
### 配置DNS
Ingress控制器將基於該主機名所傳入請求指向到對應的upstream service
外部負載平衡器是IP地址,則需要建立A record
外部負載平衡器是主機名,則需建立CNAME紀錄
如果沒有自有的域名,則可以透過編輯/etc/hosts檔案來新增IP地址以設定本機配置
```
$ vi /etc/hosts
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting. Do not change this entry.
##
127.0.0.1 web.example.com
127.0.0.1 localhost
```
### 使用Ingress
demo.yaml including namespace, deployment, service, ingress 來建立一個簡單的網站
note: ingress controller 可以跨namespace管理
```yaml
apiVersion: v1
kind: Namespace
metadata:
name: web
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
namespace: web
spec:
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: httpd
image: httpd:2.4.53-alpine
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: web-server-service
namespace: web
spec:
selector:
app: web
ports:
- protocol: TCP
port: 5000
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-server-ingress
namespace: web
spec:
ingressClassName: nginx
rules:
- host: web.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-server-service
port:
number: 5000
```
```
# deploy namespace, deployment, service, ingress
$ kubectl apply -f demo.yaml
```
```
$ kubectl get deployment -n web
NAME READY UP-TO-DATE AVAILABLE AGE
web-server 1/1 1 1 56m
```
```
$ kubectl get svc -n web
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web-server-service ClusterIP 10.110.61.54 <none> 5000/TCP 56m
```
```
$ kubectl get ingress -n web
NAME CLASS HOSTS ADDRESS PORTS AGE
web-server-ingress nginx web.example.com localhost 80 56m
```

### 使用多個主機名
本機配置
```
$ vi /etc/hosts
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting. Do not change this entry.
##
127.0.0.1 web.example.com purple.example.com green.example.com
127.0.0.1 localhost
```
demo-hosts.yaml 額外部署兩個服務 purple, green
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: purple-server
namespace: web
spec:
selector:
matchLabels:
app: purple
template:
metadata:
labels:
app: purple
spec:
containers:
- name: httpd
image: gcr.io/kuar-demo/kuard-amd64:purple
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: purple-service
namespace: web
spec:
selector:
app: purple
ports:
- protocol: TCP
port: 8080
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: green-server
namespace: web
spec:
selector:
matchLabels:
app: green
template:
metadata:
labels:
app: green
spec:
containers:
- name: httpd
image: gcr.io/kuar-demo/kuard-amd64:green
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: green-service
namespace: web
spec:
selector:
app: green
ports:
- protocol: TCP
port: 8080
targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: server-ingress
namespace: web
spec:
ingressClassName: nginx
rules:
- host: purple.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: purple-service
port:
number: 8080
- host: green.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: green-service
port:
number: 8080
```
```
# deploy namespace, deployment, service, ingress
$ kubectl apply -f demo-hosts.yaml
```
```
$ kubectl describe ingress server-ingress -n web
Name: server-ingress
Labels: <none>
Namespace: web
Address: localhost
Ingress Class: nginx
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
purple.example.com
/ purple-service:8080 (10.1.0.15:8080)
green.example.com
/ green-service:8080 (10.1.0.16:8080)
Annotations: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal AddedOrUpdated 3m8s (x8 over 18m) nginx-ingress-controller Configuration for web/server-ingress was added or updated
```


### 使用路徑
可以用來管理單一域名在不同路徑上管理多個服務
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-server-ingress
namespace: web
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: web.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-server-service
port:
number: 5000
- path: /green
pathType: Prefix
backend:
service:
name: green-service
port:
number: 8080
```
```
$ kubectl describe ingress web-server-ingress -n web
Name: web-server-ingress
Labels: <none>
Namespace: web
Address: localhost
Ingress Class: nginx
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
web.example.com
/ web-server-service:5000 (10.1.0.13:80)
/green green-service:8080 (10.1.0.16:8080)
Annotations: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal AddedOrUpdated 5m49s (x13 over 124m) nginx-ingress-controller Configuration for web/web-server-ingress was added or updated
```
### 清除
```
$ kubectl delete -f demo.hosts.yaml
```
```
$ kubectl get ingress -n web
No resources found in web namespace.
```
## 進階Ingress技巧
### 運行多個Ingress控制器
在同一個cluster中運行多個ingress控制器,可以使用annotation配置 kubernetes.io/ingress.class ,這樣可以指定ingress物件要用哪一個控制器。
該值應該是一個string,這個值會指定哪些控制器應查看此物件。然後,應使用相同的值配置在控制器本身,並且應使用帶有正確annotation的ingress物件。
如果沒有配置 kubernetes.io/ingress.class 的annotation,則視為未定義。可能有多個控制器將爭先恐後地想取得ingress使用權,並寫入ingress物件的status欄位。
### 多個Ingress物件
如果有多個ingress物件,則控制器會全部讀取他們,並嘗試將他們合併為同一個配置。但是,如果重複和衝突的配置,則會視為未定義的。但不同的控制器的行為可能會有所不同。
### Ingress和Namespace
ingress物件只能對應至相同namespace中的upstream service。
但controller可以部署在任何namespace都不受影響。
### 路徑重寫
有些ingress控制器支援路徑重寫。可修改HTTP請求中的路徑。通常由ingress物件上的annotation配置,用於該物件指定的所有請求。不僅支援單純的路徑重寫,還支援正則表示式。
* 例如:
如果使用NGINX ingress控制器,則可指定annotation其主鍵是 nginx.ingress.kubernetes.io/rewrite-target:/ ,即使upstream service並沒有設計子路徑的處理。
但經常導致錯誤,如果要為了配合複雜的應用程式開發,最好避免使用子路徑。
### 支援TLS
越來越需要使用TLS和HTTPS,首先需要使用TSL證書和密鑰建立一個secret物件。
上傳證書後,可以在ingress物件中引用他。如果多個ingress物件為同一主機名指定證書,則視為未定義的。
https://cloud.google.com/kubernetes-engine/docs/how-to/ingress-multi-ssl#google-managed-certs
Create GCP managed cert.
```yaml
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
name: FIRST_CERT_NAME
spec:
domains:
- FIRST_DOMAIN
---
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
name: SECOND_CERT_NAME
spec:
domains:
- SECOND_DOMAIN
```
Specifying certificates for your Ingress by GKE Ingress controller
```ymal
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-gmc-ingress
annotations:
networking.gke.io/managed-certificates: "FIRST_CERT_NAME,SECOND_CERT_NAME"
spec:
rules:
- host: FIRST_DOMAIN
http:
paths:
- pathType: ImplementationSpecific
backend:
service:
name: my-mc-service
port:
number: 60001
- host: SECOND_DOMAIN
http:
paths:
- pathType: ImplementationSpecific
backend:
service:
name: my-mc-service
port:
number: 60002
```
### Ingress的替代實現方法
* 每個雲供應商都具有一個ingress實作
* NGINX ingress
* Ambassador.Gloo
* Traefik