[บันทึกช่วยจำ] ลองเล่น NGINX Ingress ใน K8S Cluster บน On-Premises

ครั้งนี้เรามาต่อยอดจากครั้งที่แล้วจากเรื่อง [บันทึกช่วยจำ] ลองเล่น Terraform + Kubespray + Ansible = K8S Cluster บน VMware โดยที่เราจะลองใช้งาน NGINX มาใช้งานเป็น Ingress บน Service กัน

โดยทางผู้เขียนทำการปรับปรุง code บน Terraform จากครั้งเก่าให้สามารถสร้าง HA Proxy อีกชุดเพื่อทำหน้าที่ NLB เพื่อทำการกระจายโหลดเข้าไปที่ฝั่ง worker ทั้งหมดของ K8S Cluster เพื่อให้สะดวกให้การสร้าง K8S Cluster มากยิ่งขึ้น โดยเมื่อเราทำการสั่งคำสั่งในการสร้าง K8S Cluster จนเสร็จแล้วเราจะได้หน้าตาของ infrastructure ดังนี้

คราวนี้เราก็พร้อมจะไปลองใช้งาน NGINX กันแล้ว แต่ก่อนที่จะไปลองเล่นกันเรามาดูส่วนประกอบต่างๆ กันก่อนว่ามีอะไรบ้าง

Ingress, Ingress Resource เป็น service ประเภทหนึ่งมีหน้าที่บริการ application ต่างๆ ที่อยู่ภายใน cluster ส่งออกไปข้างนอกโดยที่เราสามารถกำหนด rules ต่างๆได้ดัง

Content-based routing: ใช้ในการกำหนดการว่าหากมีการเรียกแบบนี้ให้ไป service ไหน โดยจะมีการแบ่งออกเป็น 2 แบบอีก

  • Host-based routing. เป็นการกำหนดใช้ Hostname (FQDN) เป็นตัวกำหนด routing ว่าเรียกแบบนี้เข้ามาให้ไปที่ services อะไร เช่น images.demo.domain
  • Path-based routing. เป็นการกำหนดใช้ pathเป็นตัวกำหนด routing ว่าเรียกแบบนี้เข้ามาให้ไปที่ services อะไร เช่น demo.domain/images

TLS/SSL termination: ใช้ในการทำเรื่อง Offload SSL certificate

NGINX Ingress Controller เป็น application ตัวหนึ่งที่จะรันอยู่ใน cluster ทุกๆ worker node ทำหน้าที่ เป็น reverse proxy และ load balancer โดยรองรับทั้ง NGINX และ NGINX Plus

น่จะพร้อมลองเล่นจริงๆ แล้วละ เราไปลงเมื่อกันเลยดีกว่า

Clone โปรเจค kubernetes-ingress ของ nginxinc จาก github ซะก่อน

ขั้นตอนที่ 1 — ทำการ clone โปรเจค kubernetes-ingress มาที่เครื่องของเรา

dcadmin@k8s-operation:~$ git clone https://github.com/nginxinc/kubernetes-ingress/
dcadmin@k8s-operation:~$ cd kubernetes-ingress/deployments

ตั้งค่าเกี่ยว RBAC

ขั้นตอนที่ 1 — สร้าง namespace และ service account สำหรับ Ingress controller

dcadmin@k8s-operation:~$ kubectl apply -f common/ns-and-sa.yaml

ขั้นตอนที่ 2 — สร้าง cluster role และทำการ binding cluster role ให้กับ service account ที่สร้างขึ้นมา

dcadmin@k8s-operation:~$ kubectl apply -f rbac/rbac.yaml

สร้าง Common Resources

ขั้นตอนที่ 1 — สร้าง secret เพื่เก็บค่า TLS certificate สำหรับ default server

dcadmin@k8s-operation:~$ kubectl apply -f common/default-server-secret.yaml

ขั้นตอนที่ 2— สร้าง config map สำหรับการตั้งค่า NGINX

dcadmin@k8s-operation:~$ kubectl apply -f common/nginx-config.yaml

Deploy Ingress Controller

ขั้นตอนที่ 1 — สร้าง NGINX Controller โดยใช้ DaemonSet ระบบจะทำการสร้าง Ingress Controller ทุกๆ node บน cluster

dcadmin@k8s-operation:~$ kubectl apply -f daemon-set/nginx-ingress.yaml

ขั้นตอนที่ 2— ตรวจสอบสถานะ Ingress Controller

dcadmin@k8s-operation:~$ kubectl get pods --namespace=nginx-ingress

ผลลัพธ์ที่ได้

NAME                  READY   STATUS    RESTARTS   AGE
nginx-ingress-2h79f 1/1 Running 0 33s
nginx-ingress-85dfh 1/1 Running 0 33s
nginx-ingress-jzhck 1/1 Running 0 33s

ทดลอง Deploy Application

ขั้นตอนที่ 1 — สร้าง deployment สำหรับ main application

dcadmin@k8s-operation:~$ kubectl apply -f nginx-deployment-main.yaml

nginx-deployment-main.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy-main
labels:
run: nginx
spec:
replicas: 2
selector:
matchLabels:
run: nginx-main
template:
metadata:
labels:
run: nginx-main
spec:
containers:
- image: nginx
name: nginx

ขั้นตอนที่ 2— สร้าง service สำหรับ main application

dcadmin@k8s-operation:~$ kubectl apply -f nginx-service-main.yaml

nginx-service-main.yaml

apiVersion: v1
kind: Service
metadata:
name: nginx-service-main
labels:
run: nginx
spec:
ports:
- port: 80
protocol: TCP
selector:
run: nginx-main

ขั้นตอนที่ 3 — ตรวจสอบ pods และ service

dcadmin@k8s-operation:~$ kubectl get pods,services

ผลลัพธ์ที่ได้

NAME                                     READY   STATUS    RESTARTS   AGE
pod/nginx-deploy-main-7cc547b6f7-8p2pm 1/1 Running 0 104s
pod/nginx-deploy-main-7cc547b6f7-nqx8p 1/1 Running 0 104s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 35m
service/nginx-service-main ClusterIP 10.233.55.99 <none> 80/TCP 104s

ขั้นตอนที่ 4 — สร้าง ingress resource เพื่อกำหนด rule การ routing โดยทางผู้เขียนจะใช้ Hostname (FQDN) เป็นชื่อ k8s.lab.local ทั้งนี้ผู้ที่ลองทำตามต้องไปทำการแก้ไขไฟล์ hosts ในเครื่องตัวเองให้ระบบ k8s.lab.local ไปที่ VIP ของ HA Proxy ด้วย

dcadmin@k8s-operation:~$ kubectl apply -f ingress-resource.yaml

ingress-resource.yaml

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-resource
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: k8s.lab.local
http:
paths:
- backend:
serviceName: nginx-service-main
servicePort: 80

ตรวจสอบ ingress resource

dcadmin@k8s-operation:~$ kubectl get ingress

ผลลัพธ์ที่ได้

NAME               HOSTS           ADDRESS   PORTS   AGE
ingress-resource k8s.lab.local 80 73s

ดูรายละเอียดของ ingress resource

dcadmin@k8s-operation:~$ kubectl describe ingress

ผลลัพธ์ที่ได้

Name:             ingress-resource
Namespace: default
Address:
Default backend: default-http-backend:80 (<none>)
Rules:
Host Path Backends
---- ---- --------
k8s.lab.local
nginx-service-main:80 (10.233.74.3:80,10.233.88.2:80)

Annotations:
kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"networking.k8s.io/v1beta1","kind":"Ingress","metadata":{"annotations":{},"name":"ingress-resource","namespace":"default"},"spec":{"rules":[{"host":"k8s.lab.local","http":{"paths":[{"backend":{"serviceName":"nginx-service-main","servicePort":80}}]}}]}}
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal AddedOrUpdated 72s nginx-ingress-controller Configuration for default/ingress-resource was added or updated
Normal AddedOrUpdated 72s nginx-ingress-controller Configuration for default/ingress-resource was added or updated
Normal AddedOrUpdated 72s nginx-ingress-controller Configuration for default/ingress-resource was added or updated

แก้ไข hosts ที่เครื่องเราเองด้วยนะ

172.16.31.78    k8s.lab.local

ขั้นตอนที่ 5— ทดสอบลองเรียกใช้งาน main application

เย้เรียกเข้าใช้งานได้แล้ว

ทดลอง Deploy Application ตัวต่อไป

ขั้นตอนที่ 1 — สร้าง deployment สำหรับ cat application

dcadmin@k8s-operation:~$ kubectl apply -f nginx-deployment-cat.yaml

nginx-deployment-cat.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
labels:
run: nginx
name: nginx-deploy-cat
spec:
replicas: 2
selector:
matchLabels:
run: nginx-cat
template:
metadata:
labels:
run: nginx-cat
spec:
volumes:
- name: webdata
emptyDir: {}
initContainers:
- name: web-content
image: busybox
volumeMounts:
- name: webdata
mountPath: "/webdata"
command: ["/bin/sh", "-c", 'echo "<title>Welcome to nginx!</title><h1>CAT Application<h1/><p>&nbsp;____&nbsp; &nbsp; &nbsp;_&nbsp; &nbsp; ____<br />/ ___|&nbsp; &nbsp; / \&nbsp; &nbsp;|_ _|<br />| |&nbsp; &nbsp; &nbsp; &nbsp; / _ \&nbsp; &nbsp;| |<br />| |___ / ___ \ | |<br /> \____/_/&nbsp; &nbsp; \_\_|</p>" > /webdata/index.html']
containers:
- image: nginx
name: nginx
volumeMounts:
- name: webdata
mountPath: "/usr/share/nginx/html"

ขั้นตอนที่ 2 — สร้าง service สำหรับ cat application

dcadmin@k8s-operation:~$ kubectl apply -f nginx-service-cat.yaml

nginx-service-cat.yaml

apiVersion: v1
kind: Service
metadata:
name: nginx-service-cat
labels:
run: nginx
spec:
ports:
- port: 80
protocol: TCP
selector:
run: nginx-cat

ขั้นตอนที่ 3 — ตรวจสอบ pods และ service

dcadmin@k8s-operation:~$ kubectl get pods,services

ผลลัพธ์ที่ได้

NAME                                     READY   STATUS    RESTARTS   AGE
pod/nginx-deploy-cat-c6cdd4b64-4ltvp 1/1 Running 0 22s
pod/nginx-deploy-cat-c6cdd4b64-h7kc5 1/1 Running 0 22s
pod/nginx-deploy-main-7cc547b6f7-8p2pm 1/1 Running 0 24m
pod/nginx-deploy-main-7cc547b6f7-nqx8p 1/1 Running 0 24m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 58m
service/nginx-service-cat ClusterIP 10.233.36.73 <none> 80/TCP 22s
service/nginx-service-main ClusterIP 10.233.55.99 <none> 80/TCP 24m

ขั้นตอนที่ 4 — แล้วเราจะเข้าใช้ cat application ยังไงนะ ลองเรียก cat.k8s.lab.local

อย่าลืม แก้ไข hosts ที่เครื่องเราเองด้วยนะ

172.16.31.78    cat.k8s.lab.local

ยังเรียกไม่ได้เพราะยังไม่ได้เพิ่ม rule บน ingress resource

ขั้นตอนที่ 5— เพิ่ม rule สำหรับ cat.k8s.lab.local ให้เรียกไปที่ nginx-service-cat

dcadmin@k8s-operation:~$ kubectl apply -f ingress-resource.yaml

ingress-resource.yaml

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-resource
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: k8s.lab.local
http:
paths:
- backend:
serviceName: nginx-service-main
servicePort: 80
- host: cat.k8s.lab.local
http:
paths:
- backend:
serviceName: nginx-service-cat
servicePort: 80

ตรวจสอบ ingress resource

dcadmin@k8s-operation:~$ kubectl get ingress

ผลลัพธ์ที่ได้

NAME               HOSTS           ADDRESS   PORTS   AGE
ingress-resource k8s.lab.local,cat.k8s.lab.local 80 29m

ดูรายละเอียดของ ingress resource

dcadmin@k8s-operation:~$ kubectl describe ingress

ผลลัพธ์ที่ได้

Name:             ingress-resource
Namespace: default
Address:
Default backend: default-http-backend:80 (<none>)
Rules:
Host Path Backends
---- ---- --------
k8s.lab.local
nginx-service-main:80 (10.233.74.3:80,10.233.88.2:80)
cat.k8s.lab.local
nginx-service-cat:80 (10.233.84.3:80,10.233.88.3:80)

Annotations:
kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"networking.k8s.io/v1beta1","kind":"Ingress","metadata":{"annotations":{},"name":"ingress-resource","namespace":"default"},"spec":{"rules":[{"host":"k8s.lab.local","http":{"paths":[{"backend":{"serviceName":"nginx-service-main","servicePort":80}}]}},{"host":"cat.k8s.lab.local","http":{"paths":[{"backend":{"serviceName":"nginx-service-cat","servicePort":80}}]}}]}}
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal AddedOrUpdated 4s (x2 over 32m) nginx-ingress-controller Configuration for default/ingress-resource was added or updated
Normal AddedOrUpdated 4s (x2 over 32m) nginx-ingress-controller Configuration for default/ingress-resource was added or updated
Normal AddedOrUpdated 4s (x2 over 32m) nginx-ingress-controller Configuration for default/ingress-resource was added or updated

ขั้นตอนที่ 5 — ทดสอบลองเรียกใช้งาน cat application อีกครั้ง

เย้เข้าได้แล้ว

ทดลอง Deploy Application อีกสัก 2 ตัว

ขั้นตอนที่ 1 — สร้าง deployment สำหรับ dog application

dcadmin@k8s-operation:~$ kubectl apply -f nginx-deployment-dog.yaml

nginx-deployment-dog.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
labels:
run: nginx
name: nginx-deploy-dog
spec:
replicas: 2
selector:
matchLabels:
run: nginx-dog
template:
metadata:
labels:
run: nginx-dog
spec:
volumes:
- name: webdata
emptyDir: {}
initContainers:
- name: web-content
image: busybox
volumeMounts:
- name: webdata
mountPath: "/webdata"
command: ["/bin/sh", "-c", 'echo "<title>Welcome to nginx!</title><h1>DOG Application<h1/><p>____&nbsp; ___&nbsp; &nbsp;____<br />|&nbsp; &nbsp;_ \/&nbsp; _ \ / ___|<br />| |&nbsp; | | |&nbsp; | | |&nbsp; _<br />| |_| | |_| | |_| |<br />|___/\___/ \___|</p>" > /webdata/index.html']
containers:
- image: nginx
name: nginx
volumeMounts:
- name: webdata
mountPath: "/usr/share/nginx/html"

ขั้นตอนที่ 2 — สร้าง service สำหรับ dog application

dcadmin@k8s-operation:~$ kubectl apply -f nginx-service-dog.yaml

nginx-service-dog.yaml

apiVersion: v1
kind: Service
metadata:
name: nginx-service-dog
labels:
run: nginx
spec:
ports:
- port: 80
protocol: TCP
selector:
run: nginx-dog

ขั้นตอนที่ 3— สร้าง deployment สำหรับ rabbit application

dcadmin@k8s-operation:~$ kubectl apply -f nginx-deployment-rabbit.yaml

nginx-deployment-rabbit.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
labels:
run: nginx
name: nginx-deploy-rabbit
spec:
replicas: 2
selector:
matchLabels:
run: nginx-rabbit
template:
metadata:
labels:
run: nginx-rabbit
spec:
volumes:
- name: webdata
emptyDir: {}
initContainers:
- name: web-content
image: busybox
volumeMounts:
- name: webdata
mountPath: "/webdata"
command: ["/bin/sh", "-c", 'echo "<title>Welcome to nginx!</title><h1>RABBIT Application<h1/><p>____&nbsp; &nbsp; &nbsp; &nbsp; _&nbsp; &nbsp; &nbsp;___&nbsp; ___&nbsp; ___ ____<br />|&nbsp; &nbsp;_ \&nbsp; &nbsp; &nbsp; / \&nbsp; &nbsp;| __ )| __ )_ _|_&nbsp; &nbsp;_|<br />|&nbsp; |_) |&nbsp; &nbsp;/ _ \&nbsp; |&nbsp; _ \|&nbsp; _&nbsp; \| |&nbsp; &nbsp; | |<br />|&nbsp; &nbsp;_ &lt;&nbsp; / ___ \| |_) | |_) | |&nbsp; &nbsp; | |<br />|_| \_\/_/&nbsp; &nbsp; \_\___/|___/___||_|</p>" > /webdata/index.html']
containers:
- image: nginx
name: nginx
volumeMounts:
- name: webdata
mountPath: "/usr/share/nginx/html"

ขั้นตอนที่ 4 — สร้าง service สำหรับ rabbit application

dcadmin@k8s-operation:~$ kubectl apply -f nginx-service-rabbit.yaml

nginx-service-rabbit.yaml

apiVersion: v1
kind: Service
metadata:
name: nginx-service-rabbit
labels:
run: nginx
spec:
ports:
- port: 80
protocol: TCP
selector:
run: nginx-rabbit

ขั้นตอนที่ 5— ตรวจสอบ pods และ service

dcadmin@k8s-operation:~$ kubectl get pods,services

ผลลัพธ์ที่ได้

NAME                                       READY   STATUS    RESTARTS   AGE
pod/nginx-deploy-cat-c6cdd4b64-4ltvp 1/1 Running 0 18m
pod/nginx-deploy-cat-c6cdd4b64-h7kc5 1/1 Running 0 18m
pod/nginx-deploy-dog-5dd6cb45c7-9qqrn 1/1 Running 0 65s
pod/nginx-deploy-dog-5dd6cb45c7-fz6wn 1/1 Running 0 65s
pod/nginx-deploy-main-7cc547b6f7-8p2pm 1/1 Running 0 42m
pod/nginx-deploy-main-7cc547b6f7-nqx8p 1/1 Running 0 42m
pod/nginx-deploy-rabbit-5557fdf458-vnkxm 1/1 Running 0 61s
pod/nginx-deploy-rabbit-5557fdf458-xsq8l 1/1 Running 0 61s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 76m
service/nginx-service-cat ClusterIP 10.233.36.73 <none> 80/TCP 18m
service/nginx-service-dog ClusterIP 10.233.36.210 <none> 80/TCP 66s
service/nginx-service-main ClusterIP 10.233.55.99 <none> 80/TCP 42m
service/nginx-service-rabbit ClusterIP 10.233.4.255 <none> 80/TCP 61s

ขั้นตอนที่ 6 — แก้ไข hosts ที่เครื่องเราเพื่อเพิ่ม hostname ที่เราจะเรียกเข้าไปใช้งาน service

172.16.31.78    dog.k8s.lab.local
172.16.31.78 rabbit.k8s.lab.local

ขั้นตอนที่ 7— เพิ่ม rule สำหรับ dog.k8s.lab.local และ rabbit.k8s.lab.local ให้เรียกไปที่ nginx-service-dog และ nginx-service-rabbit

dcadmin@k8s-operation:~$ kubectl apply -f ingress-resource.yaml

ingress-resource.yaml

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-resource
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: k8s.lab.local
http:
paths:
- backend:
serviceName: nginx-service-main
servicePort: 80
- host: cat.k8s.lab.local
http:
paths:
- backend:
serviceName: nginx-service-cat
servicePort: 80
- host: dog.k8s.lab.local
http:
paths:
- backend:
serviceName: nginx-service-dog
servicePort: 80
- host: rabbit.k8s.lab.local
http:
paths:
- backend:
serviceName: nginx-service-rabbit
servicePort: 80

ตรวจสอบ ingress resource

dcadmin@k8s-operation:~$ kubectl get ingress

ผลลัพธ์ที่ได้

NAME               HOSTS                                                           ADDRESS   PORTS   AGE
ingress-resource k8s.lab.local,cat.k8s.lab.local,dog.k8s.lab.local + 1 more... 80 41m

ดูรายละเอียดของ ingress resource

dcadmin@k8s-operation:~$ kubectl describe ingress

ผลลัพธ์ที่ได้

Name:             ingress-resource
Namespace: default
Address:
Default backend: default-http-backend:80 (<none>)
Rules:
Host Path Backends
---- ---- --------
k8s.lab.local
nginx-service-main:80 (10.233.74.3:80,10.233.88.2:80)
cat.k8s.lab.local
nginx-service-cat:80 (10.233.84.3:80,10.233.88.3:80)
dog.k8s.lab.local
nginx-service-dog:80 (10.233.74.4:80,10.233.84.4:80)
rabbit.k8s.lab.local
nginx-service-rabbit:80 (10.233.74.5:80,10.233.88.4:80)
Annotations:
kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"networking.k8s.io/v1beta1","kind":"Ingress","metadata":{"annotations":{},"name":"ingress-resource","namespace":"default"},"spec":{"rules":[{"host":"k8s.lab.local","http":{"paths":[{"backend":{"serviceName":"nginx-service-main","servicePort":80}}]}},{"host":"cat.k8s.lab.local","http":{"paths":[{"backend":{"serviceName":"nginx-service-cat","servicePort":80}}]}},{"host":"dog.k8s.lab.local","http":{"paths":[{"backend":{"serviceName":"nginx-service-dog","servicePort":80}}]}},{"host":"rabbit.k8s.lab.local","http":{"paths":[{"backend":{"serviceName":"nginx-service-rabbit","servicePort":80}}]}}]}}
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal AddedOrUpdated 64s (x3 over 42m) nginx-ingress-controller Configuration for default/ingress-resource was added or updated
Normal AddedOrUpdated 64s (x3 over 42m) nginx-ingress-controller Configuration for default/ingress-resource was added or updated
Normal AddedOrUpdated 64s (x3 over 42m) nginx-ingress-controller Configuration for default/ingress-resource was added or updated

ขั้นตอนที่ 8— ทดสอบลองเรียกใช้งาน dog application และ rabbit application

หมายเหตุ ในส่วนของ Path-based routing ทางผู้เขียนได้ทดลองแล้วพบว่าไม่สามารถทำงาน และยังสาเหตุไม่เจอเพราะอะไร หากเจอสาเหตุ และวิธีการแก้ไขแล้วจะนำมาอัพเดตในบทความครับ

สุดท้ายการลองเล่นครั้งก็เป็นเพียงการใช้งานเบื้องต้น ตัว nginx ยังไม่ลูกเล่นอีกมากมายที่ให้เราใช้งาน แต่ต้องเรียนรู้ และหัดใช้งานกันต่อไป

สุดท้ายแล้วหากท่านไหนหลงเข้ามาอ่านแล้วเห็นว่าผมยังเข้าใจผิดในเรื่องใด สามารถให้ข้อมูลเพิ่มเติมได้ครับ

Ref: https://docs.nginx.com/nginx-ingress-controller/overview/

Ref: https://github.com/nginxinc/kubernetes-ingress

นายช่างระบบระบบคอมพิวเตอร์ทั้วๆ ที่ชอบทดลอง และเรียนรู้สิ่งใหม่ไปเรื่อยๆ

นายช่างระบบระบบคอมพิวเตอร์ทั้วๆ ที่ชอบทดลอง และเรียนรู้สิ่งใหม่ไปเรื่อยๆ