LFS258 [9/15]: Ingress
Tags: kubernetes, devops, nginx
Published: 2020-03-30 14:56:54

# TL;DR

- [Ingress Overview](#Ingress-Overview): เป็น L7 load balancer หลักๆ มี GCE, Nginx, HAProxy และ Traefix เป็นต้น
- [Ingress Controller](#Ingress-Controller): เป็น controller ที่ monitor URI `/ingresses` ของ API server เพื่อนำมา configure rule 
- [Nginx](#Nginx): เป็น Ingress Controller ที่ใช้ Nginx เป็น reverse proxy
- [Google Load Balancer Controller (GLBC)](#Google-Load-Balancer-Controller-(GLBC)): เป็น reverse proxy ของ Google ใช้งานได้บน Goole Cloud ทั้ง GCE และ GKE
- [Ingress API Resources](#Ingress-API-Resources): แสดง YAML file ในการสร้าง Ingress
- [Deploying the Ingress Controller](#Deploying-the-Ingress-Controller): แสดงการ setup Nginx Ingress
- [Creating an Ingress Rule](#Creating-an-Ingress-Rule): สร้าง rule และทำสอบ Nginx Ingress
- [Multiple Rules](#Multiple-Rules): สร้าง rule แบบ multi-domain และทำสอบ
- [Intelligent Connected Proxies](#Intelligent-Connected-Proxies): คือ service mesh เช่น Istio, Enovy และ Linkerd

<a name="Ingress-Overview"></a>

---

# Ingress Overview

ใน article ก่อนหน้านี้ เราได้เรียนรู้เกี่ยวกับ Service ซึ่งใช้ในการ expose application ให้ client ที่อยู่นอก cluster ใช้งานได้ ในบทนี้เราจะเรียนรู้เกี่ยวกับ **Ingress Controllers** และ **Rules** ซึ่งทำหน้าที่เดียวกัน แต่ต่างกันที่ประสิทธิภาพ แทนที่จะต้องใช้ load balancer เป็นตัวช่วย Ingress สามารถทำแบบนั้นได้เลย ไม่ว่าจะเป็น route ตาม domain หรือ URI Path

Ingress Controllers ไม่ได้เป็นส่วนหนึ่งของ `kube-controller-manager` binary แต่มันจะมี controller เป็นของตัวเอง ซึ่งก็มีได้หลาย controller โดย controller จะใช้ Ingress Rules ในการจัดการกับ traffic ทั้งขาเข้าและขาออก

Ingress รองรับ 2 controllers หลัก คือ **GCE** และ **nginx** จริงๆ แล้ว tool อื่นที่ทำหน้าที่เป็น reverse proxy เช่น **HAProxy** และ **Traefix** ก็ใช้งานได้ โดยมันจะ route traffic มายัง Service ที่ on แบบ `ClusterIP` ได้เลยตาม Ingress Rule ที่เรา configure ไว้

<a name="Ingress-Controller"></a>

---

# Ingress Controller

**Ingress Controller** อยู่ใน `networking.k8s.io/v1beta1` resource group เป็น daemon ที่ run ใน Pod ซึ่งจะคอย monitor การเปลี่ยนแปลงของ `/ingresses` บน `kube-apiserver` ถ้าหากพบการเปลี่ยนแปลง มันจะไป configure rules เพื่อ support ให้ โดย traffic ส่วนใหญ่จะเป็น HTTP วิธีนี้จะทำให้สามารถเข้าถึง Services ได้ โดยไม่ต้องสนใจว่า Pods นั้นถูก deploy ไว้ที่ node ไหน

เราสามารถ deploy **Ingress Controller** ได้มากกว่า 1 controller ใน cluster เดียวกัน traffic จะใช้ `annotation` ในการเลือก controller ทีละตัวจนกว่าจะพบตัวที่เหมาะสม 

![inbound-ingress](https://dev-to-uploads.s3.amazonaws.com/i/7bx890ligule8t1cxsca.png)

<a name="Nginx"></a>

---

# Nginx

การ deploy Nginx Controller สามารถดูตัวอย่างของหลากหลาย platform ได้จาก [ingress-nginx](https://github.com/kubernetes/ingress-nginx/tree/master/deploy)

เราสามารถ configure **Nginx Controller** ได้ผ่านทาง `ConfigMap` และ `Annotations` แต่ถ้ายังไม่พอใจเราสามารถ configure จาก [`custom template`](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/custom-template/) ได้ด้วย

คุณสมบัติของ Nginx Controller
- integrate กับ RBAC ได้ง่าย
- ใช้ annotation `kubernetes.io/ingress.class: "nginx"`
- ถ้าจะใช้เป็น L7 revere proxy ต้องระบุ `proxy-real-ip-cidr` ด้วย
- ถ้าจะใช้ feature **session affinity** ต้อง bypass `kube-proxy`
- ไม่ได้ใช้ [`conntrack`](http://conntrack-tools.netfilter.org/) ที่เกิดจาก DNAT ของ iptables
- ถ้าจะให้ terminate TLS ให้ต้องระบุุ host field ด้วย

<a name="Google-Load-Balancer-Controller-(GLBC)"></a>
---

# Google Load Balancer Controller (GLBC)

GLBC คือ GCE L7 load balancer controller ที่จัดการ external loadbalancers ด้วย Kubernetes Ingress API 

ในสร้าง 1 Ingress ต้องมี cloud resource เหล่านี้
1. **Global Forwarding Rule**: เป็นตัว manage VIP ของ Ingress
2. **TargetHttpProxy**: เป็นตัว manage SSL certs และ proxy ระหว่าง VIP และ Backend
3. **URL Map**: routing rules
4. **Backend Service**: รวม หลายๆ Instance Groups เป็น 1 Service Node Port 
5. **Instance Group**: VM ที่ เป็น kubernetes node

Pipeline เป็นแบบนี้

![pipeline](https://dev-to-uploads.s3.amazonaws.com/i/dkzsb2n78oqvz4b7p0na.png)

โดย
- แต่ละ Backend Service ต้องทำการ health check ไปยัง NodePort ของ Service
- แต่ละ port ของ Backend Service ต้องตรงกับ Instance Group
- แต่ละ port ของ Backend Service จะถูก expose ผ่าน firewall บน GCE LB IP ranges (130.211.0.0/22 and 35.191.0.0/16)

ตอนนี้ Ingress สามารถใช้ TLS และ TLS termination ที่ port 443 และยังไม่ support SNI

<a name="Ingress-API-Resources"></a>

---

# Ingress API Resources

Ingress object ยังเป็น extension API มีตัวอย่าง YAML file ดังนี้

``` yaml
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ghost
spec:
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          serviceName: test
          servicePort: 80
```

เราสามารถ manage มันได้ดังนี้

``` bash
$ kubectl get ingress
$ kubectl delete ingress <ingress_name>
$ kubectl edit ingress <ingress_name>
```

<a name="Deploying-the-Ingress-Controller"></a>

---

# Deploying the Ingress Controller

ในการ deploy Nginx Ingress ทำได้ง่ายๆ โดย ดูได้จาก [Github](https://github.com/kubernetes/ingress-nginx/blob/master/docs/deploy/index.md)

ในตัวอย่างจะขอ install แบบ Bare-Metal โดยใช้ NodePort ดังนี้

``` bash
# Install
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml
namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
deployment.apps/nginx-ingress-controller created
limitrange/ingress-nginx created
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/baremetal/service-nodeport.yaml
service/ingress-nginx created

# Verify
$ kubectl get pods --all-namespaces -l app.kubernetes.io/name=ingress-nginx --watch
NAMESPACE       NAME                                        READY   STATUS    RESTARTS   AGE
ingress-nginx   nginx-ingress-controller-7f74f657bd-9fm44   1/1     Running   0          82s
```

<a name="Creating-an-Ingress-Rule"></a>

---

# Creating an Ingress Rule

1. ทำการ create deployment และ expose service ที่ port 2368

    ``` bash
    $ kubectl create deployment ghost --image=ghost
    deployment.apps/ghost created

    $ kubectl expose deployment ghost --port 2368
    service/ghost exposed

    $ kubectl get svc
    NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
    service/ghost        ClusterIP   10.107.189.29   <none>        2368/TCP   11s
    service/kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP    74d

    $ kubectl get pods
    NAME                         READY   STATUS    RESTARTS   AGE
    pod/ghost-577564ddbd-nfqrc   1/1     Running   0          178m
    ```

2. ทำการสร้าง ingress rule โดย request ที่เข้ามา domain เป็น "ghost.192.168.99.100.nip.io" ให้ส่งไปที่ service `ghost`

    ``` bash
    $ cat > ingress.yaml << EOF
    apiVersion: networking.k8s.io/v1beta1
    kind: Ingress
    metadata:
        name: ghost
    spec:
        rules:
        - host: ghost.192.168.99.100.nip.io
        http:
            paths:
            - backend:
                serviceName: ghost
                servicePort: 2368
    EOF
    $ kubectl create -f ingress.yaml
    ingress.networking.k8s.io/ghost created

    $ kubectl get ingress
    NAME    HOSTS                         ADDRESS   PORTS   AGE
    ghost   ghost.192.168.99.100.nip.io             80      20s

    $ kubectl describe ingress/ghost
    Name:             ghost
    Namespace:        default
    Address:          10.107.151.119
    Default backend:  default-http-backend:80 (<none>)
    Rules:
    Host                         Path  Backends
    ----                         ----  --------
    ghost.192.168.99.100.nip.io  
                                    ghost:2368 (192.168.32.59:2368)
    Annotations:
    Events:
    Type    Reason  Age    From                      Message
    ----    ------  ----   ----                      -------
    Normal  CREATE  3m12s  nginx-ingress-controller  Ingress default/ghost
    Normal  UPDATE  2m40s  nginx-ingress-controller  Ingress default/ghost
    ```

3. ทำการ test ดูซิว่าถ้า doamin เป็น **ghost.192.168.99.100.nip.io** จะวิ่งไปที่ service `ghost` หรือไม่ โดยถ้าไปใช่ต้องได้ page ที่มี title เป็น **ghost**

    ``` bash
    $ curl -v -H 'host: ghost.192.168.99.100.nip.io' http://10.107.151.119:80 -o /dev/null
    <title>Ghost</title>
    ```

<a name="Multiple-Rules"></a>

---

# Multiple Rules

1. ทำการ create deployment ชื่อ nginx และ expose service ที่ port 80

    ``` bash
    $ kubectl create deployment nginx --image=nginx
    deployment.apps/nginx created

    $ kubectl expose deployment nginx --port 80
    service/nginx exposed

    $ kubectl get svc
    NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
    ghost        ClusterIP   10.107.189.29   <none>        2368/TCP   12m
    kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP    74d
    nginx        ClusterIP   10.106.200.39   <none>        80/TCP     29s

    $ kubectl get pods
    NAME                     READY   STATUS    RESTARTS   AGE
    ghost-577564ddbd-nfqrc   1/1     Running   0          3h11m
    nginx-86c57db685-vb6gk   1/1     Running   0          63s
    ```

2. ทำการ update ingress rule โดย เพิ่มให้ request ที่มี domain เป็น "nginx.192.168.99.100.nip.io" ให้ส่งไปที่ service `nginx`

    ``` bash
    $ cat > ingress-2.yaml << EOF
    apiVersion: networking.k8s.io/v1beta1
    kind: Ingress
    metadata:
        name: ghost
    spec:
        rules:
        - host: ghost.192.168.99.100.nip.io
        http:
            paths:
            - backend:
                serviceName: ghost
                servicePort: 2368
        - host: nginx.192.168.99.100.nip.io
        http:
            paths:
            - backend:
                serviceName: nginx
                servicePort: 80
    EOF

    $ kubectl apply -f ingress-2.yaml
    ingress.networking.k8s.io/ghost configured

    $ kubectl get ingress
    NAME    HOSTS                                                     ADDRESS          PORTS   AGE
    ghost   ghost.192.168.99.100.nip.io,nginx.192.168.99.100.nip.io   10.107.151.119   80      10m

    $ kubectl describe ingress/ghost
    Name:             ghost
    Namespace:        default
    Address:          10.107.151.119
    Default backend:  default-http-backend:80 (<none>)
    Rules:
    Host                         Path  Backends
    ----                         ----  --------
    ghost.192.168.99.100.nip.io  
                                    ghost:2368 (192.168.32.59:2368)
    nginx.192.168.99.100.nip.io  
                                    nginx:80 (192.168.52.65:80)
    Annotations:
    kubectl.kubernetes.io/last-applied-configuration:  {"apiVersion":"networking.k8s.io/v1beta1","kind":"Ingress","metadata":{"annotations":{},"name":"ghost","namespace":"default"},"spec":{"rules":[{"host":"ghost.192.168.99.100.nip.io","http":{"paths":[{"backend":{"serviceName":"ghost","servicePort":2368}}]}},{"host":"nginx.192.168.99.100.nip.io","http":{"paths":[{"backend":{"serviceName":"nginx","servicePort":80}}]}}]}}

    Events:
    Type    Reason  Age                  From                      Message
    ----    ------  ----                 ----                      -------
    Normal  CREATE  10m                  nginx-ingress-controller  Ingress default/ghost
    Normal  UPDATE  79s (x2 over 9m50s)  nginx-ingress-controller  Ingress default/ghost
    ```

3. ทำการ test ดูซิว่า
    - ถ้า doamin เป็น **ghost.192.168.99.100.nip.io** จะต้องวิ่งไปที่ service `ghost`
    - ถ้า doamin เป็น **nginx.192.168.99.100.nip.io** จะต้องวิ่งไปที่ service `nginx`

    ``` bash
    $ curl -s -H 'host: ghost.192.168.99.100.nip.io' http://10.107.151.119:80 | grep '<title>'
    <title>Ghost</title>

    $ curl -s -H 'host: nginx.192.168.99.100.nip.io' http://10.107.151.119:80 | grep '<title>'
    <title>Welcome to nginx!</title>
    ```

<a name="Intelligent-Connected-Proxies"></a>

---

# Intelligent Connected Proxies

ในกรณีที่ต้องการ feaature ที่เพิ่มขึ้น เช่น service discovery, rate limiting, traffic management หรือ advanced metric เราต้องเปลี่ยนมาใช้ **service mesh** โดย มันจะประกอบด้วย edge proxy และ embeded proxy ซึ่งจะทำงานร่วมกันเพื่อจักการ traffic ตาม rules ที่มาจาก control plane ตัวอย่างของ service mesh เช่น 

- **Envoy**: เป็น proxy ที่มีการออกแบบแบบ modular และ extensible มักถูกใช้เป็น data plane ของ tool อื่นๆ ของ service mesh
- **Istio**: เป็น tool ที่ช่วยเพิ่มความสามารถของ **Envoy** ด้วยการมี control plane ได้มากกว่า 1 ตัว เป็นตัวที่ทำให้ service mesh flexible ขึ้นและมี feature มากขึ้น

    ![istio-envoy](https://istio.io/docs/ops/deployment/architecture/arch.svg)

- **Linkerd**: เป็นอีกหนึ่ง service mesh ที่ออกแบบมาให้่ใช้งานได้ง่าย เร็ว และ เบา