---
# System prepended metadata

title: 第八章 使用Ingress執行HTTP負載平衡

---

---
lang: zh-tw
---

第八章 使用Ingress執行HTTP負載平衡
===
原本讓外部訪問svc的方式：
NodePort --> 每個svc連接唯一的port
LoadBalancer --> 每個svc都要對應一個LoadBalancer

#### ingress可以使用單一的入口點來管理外部訪問

在Kubernetes中，基於HTTP的負載平衡系統稱為Ingress
* 將其組態配置標準化
* 成為正規的Kubernetes物件
* 將多個Ingress物件合併到單一負載平衡器

![](https://hackmd.io/_uploads/HyaDvb-Sn.png)



可以像建立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
```

![](https://hackmd.io/_uploads/SJyJeXWS3.png)



### 使用多個主機名

本機配置

```
$ 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
```
![](https://hackmd.io/_uploads/SyGdUX-S3.png)
![](https://hackmd.io/_uploads/SJpFLXWS2.png)


### 使用路徑
可以用來管理單一域名在不同路徑上管理多個服務
```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







