Cài đặt Nginx Ingress Controller cho cụm Kubernetes

Cài đặt Nginx Ingress Controller cho cụm Kubernetes

Trong bài viết này ta sẽ học cách cài đặt Nginx Ingress Controller trên Kubernetes và cách cấu hình ingress sử dụng DNS.

Có hai loại Nginx Ingress Controller:

  1. Nginx ingress controller bởi kubernetes community

  2. Nginx ingress controller bởi Nginx Inc

We will be using the kubernetes community Nginx controller. Ta sẽ sử dụng phiên bản đầu tiên.

Kiến trúc Ingress & Nginx Ingress Controller

Đây là kiến trúc cơ bản của Kubernetes Ingress sử dụng Nginx controller.

💡
Bạn có thể tham khảo bài viết Kubernetes Ingress để hiểu rõ hơn về Ingress nhé.

Nginx Ingress Controller Kubernetes Manifests

Mọi Kubernetes manifest được sử dụng trong bài viết này sẽ ở Github repository này.

git clone https://github.com/techiescamp/nginx-ingress-controller

Đầu tiên ta sẽ tìm hiểu các object liên quan tới quá trình triển khai Nginx Controller. Sau đó ta sẽ triển khai bằng manifest hoặc Helm chart.

Triển khai controller như sau:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.1/deploy/static/provider/cloud/deploy.yaml
💡
Nếu muốn hiểu rõ hơn về mọi object trong controller và cách chúng hoạt động, bạn nên tạo từng object một để tìm hiểu. Sau khi đã hiểu chúng, bạn có thể dùng manifest trên hoặc Helm chart để triển khai.

Triển khai Nginx Ingress Controller qua các manifest

Ta cần triển khai các object sau để Nginx Controller có thể hoạt động được:

  1. ingress-nginx namespace

  2. Service account/Roles/ClusterRoles cho Nginx admission controller

  3. Kiểm tra cấu hình webhook

  4. Các job để tạo và update các webhook CA bundle.

  5. Service account/Roles/ClusterRoles cho Nginx controller deployment

  6. Nginx controller configmap

  7. Các service cho nginx controller và admission controller

  8. Ingress controller deployment

Sự cần thiết của Admission Controller và Validating Webhook

Kubernetes Admission Controller là một đoạn code dùng để kiểm tra hoặc cập nhật các object trước khi tạo chúng. Trong ingress controller, admission controller được dùng để kiểm tra các ingress object. Admission controller code là một phầ của Nginx controller nghe tại port 8443.

Tại sao ta cần admission controller?

Nếu không có admission controller, bạn có thể triển khai ingress object có thể chứa các cấu hình sai. Cấu hình sai có thể dẫn đến lỗi toàn bộ rule liên quan đến Ingress Controller đó.

Với admission controller, ta có thể đảm bảo ingress object ta tạo có cấu hình chính xác và sẽ không làm hỏng routing rule.

Đây là cách admission controller hoạt động cho Nginx:

  1. Khi triển khai một ingress YAML, admission sẽ chặn request để kiểm tra.

  2. Kubernetes API gửi ingress object tới admission controller endpoint dựa trên các admission webhook endpoint.

  3. Sau đó nó gửi request tới Nginx deployment trên port 8443 để kiểm tra ingress object.

  4. Admission controller gửi response tới Kubernetes API.

  5. Nếu nó là một response hợp lệ thì API sẽ tạo Ingress Object.

Tạo một namespace

Ta sẽ triển khai Nginx controller trong namespace ingress-nginx:

kubectl create ns ingress-nginx

Tạo Admission Controller Roles và Service Account

Ta cần một Role và ClusterRole với các quyền cần thiết và bind tới ingress-nginx-admission service account.

Tạo admission-service-account.yaml:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx-admission
  namespace: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  annotations:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx-admission
  namespace: ingress-nginx
rules:
- apiGroups:
  - ""
  resources:
  - secrets
  verbs:
  - get
  - create

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx-admission
  namespace: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: ingress-nginx-admission
subjects:
- kind: ServiceAccount
  name: ingress-nginx-admission
  namespace: ingress-nginx


---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx-admission
rules:
- apiGroups:
  - admissionregistration.k8s.io
  resources:
  - validatingwebhookconfigurations
  verbs:
  - get
  - update

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx-admission
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: ingress-nginx-admission
subjects:
- kind: ServiceAccount
  name: ingress-nginx-admission
  namespace: ingress-nginx

Triển khai manifest:

kubectl apply -f admission-service-account.yaml

Tạo cấu hình Validating Webhook

Tạo validating-webhook.yaml:

---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx-admission
webhooks:
- admissionReviewVersions:
  - v1
  clientConfig:
    service:
      name: ingress-nginx-controller-admission
      namespace: ingress-nginx
      path: /networking/v1/ingresses
  failurePolicy: Fail
  matchPolicy: Equivalent
  name: validate.nginx.ingress.kubernetes.io
  rules:
  - apiGroups:
    - networking.k8s.io
    apiVersions:
    - v1
    operations:
    - CREATE
    - UPDATE
    resources:
    - ingresses
  sideEffects: None

Tạo ValidatingWebhookConfiguration :

kubectl apply -f validating-webhook.yaml

Triển khai các jobs để cập nhật webhook certificate

ValidatingWebhookConfiguration chỉ hoạt động thông qua HTTPS. Vì vậy nó cần CA bundle.

Ta dùng kube-webhook-certgen để tạo CA cert bundle với job đầu tiên. CA cert đã được tạo sẽ được lưu trong secret tên là ingress-nginx-admission

Job thứ hai patch ValidatingWebhookConfiguration với CA bundle.

Tạo jobs.yaml:


---
apiVersion: batch/v1
kind: Job
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx-admission-create
  namespace: ingress-nginx
spec:
  template:
    metadata:
      labels:
        app.kubernetes.io/component: controller
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/name: ingress-nginx
      name: ingress-nginx-admission-create
    spec:
      containers:
      - args:
        - create
        - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc
        - --namespace=$(POD_NAMESPACE)
        - --secret-name=ingress-nginx-admission
        env:
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        image: k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1
        imagePullPolicy: IfNotPresent
        name: create
        securityContext:
          allowPrivilegeEscalation: false
      nodeSelector:
        kubernetes.io/os: linux
      restartPolicy: OnFailure
      securityContext:
        runAsNonRoot: true
        runAsUser: 2000
      serviceAccountName: ingress-nginx-admission
---
apiVersion: batch/v1
kind: Job
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx-admission-patch
  namespace: ingress-nginx
spec:
  template:
    metadata:
      labels:
        app.kubernetes.io/component: admission-webhook
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/name: ingress-nginx
      name: ingress-nginx-admission-patch
    spec:
      containers:
      - args:
        - patch
        - --webhook-name=ingress-nginx-admission
        - --namespace=$(POD_NAMESPACE)
        - --patch-mutating=false
        - --secret-name=ingress-nginx-admission
        - --patch-failure-policy=Fail
        env:
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        image: k8s.gcr.io/ingress-nginx/kube-webhook-certgen:v1.1.1
        imagePullPolicy: IfNotPresent
        name: patch
        securityContext:
          allowPrivilegeEscalation: false
      nodeSelector:
        kubernetes.io/os: linux
      restartPolicy: OnFailure
      securityContext:
        runAsNonRoot: true
        runAsUser: 2000
      serviceAccountName: ingress-nginx-admission

Sau khi các job được thực thi bạn có thể describe ValidatingWebhookConfigurationand để thấy patched bundle.

kubectl describe ValidatingWebhookConfiguration ingress-nginx-admission

Tạo Ingress Controller Roles và Service Account

Tạo ingress-service-account.yaml:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/component: admission-webhook
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx
  namespace: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx
  namespace: ingress-nginx
rules:
- apiGroups:
  - ""
  resources:
  - namespaces
  verbs:
  - get
- apiGroups:
  - ""
  resources:
  - configmaps
  - pods
  - secrets
  - endpoints
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - services
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - networking.k8s.io
  resources:
  - ingresses
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - networking.k8s.io
  resources:
  - ingresses/status
  verbs:
  - update
- apiGroups:
  - networking.k8s.io
  resources:
  - ingressclasses
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resourceNames:
  - ingress-controller-leader
  resources:
  - configmaps
  verbs:
  - get
  - update
- apiGroups:
  - ""
  resources:
  - configmaps
  verbs:
  - create
- apiGroups:
  - ""
  resources:
  - events
  verbs:
  - create
  - patch

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx
  namespace: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: ingress-nginx
subjects:
- kind: ServiceAccount
  name: ingress-nginx
  namespace: ingress-nginx

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx
rules:
- apiGroups:
  - ""
  resources:
  - configmaps
  - endpoints
  - nodes
  - pods
  - secrets
  - namespaces
  verbs:
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - nodes
  verbs:
  - get
- apiGroups:
  - ""
  resources:
  - services
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - networking.k8s.io
  resources:
  - ingresses
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - events
  verbs:
  - create
  - patch
- apiGroups:
  - networking.k8s.io
  resources:
  - ingresses/status
  verbs:
  - update
- apiGroups:
  - networking.k8s.io
  resources:
  - ingressclasses
  verbs:
  - get
  - list
  - watch

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: ingress-nginx
subjects:
- kind: ServiceAccount
  name: ingress-nginx
  namespace: ingress-nginx

Triển khai manifest:

 kubectl apply -f ingress-service-account.yaml

Tạo Configmap

Với ConfigMap này, bạn có thể tùy chỉnh cài đăt của Nginx. Ví dụ bạn có thể đặt custom header và hầu hết các cài đặt của Nginx.

Tạo configmap.yaml :

---
apiVersion: v1
data:
  allow-snippet-annotations: "true"
kind: ConfigMap
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx-controller
  namespace: ingress-nginx

Triển khai ConfigMap:

kubectl apply -f configmap.yaml

Tạo Ingress Controller và Admission Controller Services

Tạo file services.yaml:

---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  externalTrafficPolicy: Local
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - appProtocol: http
    name: http
    port: 80
    protocol: TCP
    targetPort: http
  - appProtocol: https
    name: https
    port: 443
    protocol: TCP
    targetPort: https
  selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  type: LoadBalancer
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx-controller-admission
  namespace: ingress-nginx
spec:
  ports:
  - appProtocol: https
    name: https-webhook
    port: 443
    targetPort: webhook
  selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  type: ClusterIP

Triển khai service:

kubectl apply -f services.yaml

ingress-nginx-controller tạo một LoadBalancer trong nền tảng cloud bạn đang triển khai.

Bạn có thể lấy load balancer IP/DNS sử dụng câu lệnh:

kubectl --namespace ingress-nginx get services -o wide -w ingress-nginx-controller
💡
Với mỗi cloud provider sẽ có những annotation riêng bạn có thể dùng để map IP tĩnh và các config khác tới LoadBalander.

Tạo Ingress Controller Deployment

Tạo deployment.yaml:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  minReadySeconds: 0
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app.kubernetes.io/component: controller
      app.kubernetes.io/instance: ingress-nginx
      app.kubernetes.io/name: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/component: controller
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/name: ingress-nginx
    spec:
      containers:
      - args:
        - /nginx-ingress-controller
        - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller
        - --election-id=ingress-controller-leader
        - --controller-class=k8s.io/ingress-nginx
        - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
        - --validating-webhook=:8443
        - --validating-webhook-certificate=/usr/local/certificates/cert
        - --validating-webhook-key=/usr/local/certificates/key
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: LD_PRELOAD
          value: /usr/local/lib/libmimalloc.so
        image: k8s.gcr.io/ingress-nginx/controller:v1.1.1
        imagePullPolicy: IfNotPresent
        lifecycle:
          preStop:
            exec:
              command:
              - /wait-shutdown
        livenessProbe:
          failureThreshold: 5
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        name: controller
        ports:
        - containerPort: 80
          name: http
          protocol: TCP
        - containerPort: 443
          name: https
          protocol: TCP
        - containerPort: 8443
          name: webhook
          protocol: TCP
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        resources:
          requests:
            cpu: 100m
            memory: 90Mi
        securityContext:
          allowPrivilegeEscalation: true
          capabilities:
            add:
            - NET_BIND_SERVICE
            drop:
            - ALL
          runAsUser: 101
        volumeMounts:
        - mountPath: /usr/local/certificates/
          name: webhook-cert
          readOnly: true
      dnsPolicy: ClusterFirst
      nodeSelector:
        kubernetes.io/os: linux
      serviceAccountName: ingress-nginx
      terminationGracePeriodSeconds: 300
      volumes:
      - name: webhook-cert
        secret:
          secretName: ingress-nginx-admission

Tạo deployment:

kubectl apply -f deployment.yaml

Kiểm tra trạng thái pod:

kubectl get pods -n ingress-nginx

Map Domain Name tới Ingress Loadbalancer IP

The primary goal of Ingress is to receive external traffic to services running on Kubernetes. Ideally in projects, a DNS would be mapped to the ingress controller Loadbalancer IP.

Mục tiêu chính của Ingress là nhận các traffic bên ngoài tới các dịch vụ trong Kubernetes. Lý tưởng nhất là trong các dự án, DNS sẽ được ánh xạ tới IP Loadbalancer của ingress controller.

Single DNS Mapping

You can map a single domain directly as an A record to the load balancer IP. Using this you can have only one domain for the ingress controller and multiple path-based traffic routing.

Bạn có thể map một domain trực tiếp như một A record tới load balancer IP. Sử dụng cách này bạn sẽ chỉ có một domain cho Ingress Controller và nhiều path-based traffic routing.

Ví dụ:

www.example.com --> Loadbalancer IP

Bạn có thể có nhiều path-based traffic routing.

Ví dụ

http://www.example.com/app1
http://www.example.com/app2
http://www.example.com/app1/api
http://www.example.com/app2/api

Wildcard DNS Mapping

Nếu map một wildcard DNS tới load balancer, bạn có thể có DNS endpoint linh hoạt thông qua Ingress.

Khi đã thêm wildcard entry vào DNS record, bạn cần phải mention DNS cần thiết trong Ingress object sau đó Nginx Ingress controller sẽ đảm nhiệm việc route nó tới endpoint cần thiết.

Ví dụ:

*.example.com --> Loadbalancer IP
*.apps.example.com --> Loadbalancer IP

path-based routing:

#URL one

http://demo1.example.com/api
http://demo1.example.com/api/v1
http://demo1.example.com/api/v2

#app specific urls

http://grafana.apps.example.com
http://prometheus.apps.example.com

#URL two

http://demo2.apps.example.com/api
http://demo2.apps.example.com/api/v1
http://demo2.apps.example.com/api/v2

Để demo, mình đã map một wildcard DNS tới LoadBalancer IP. Dựa trên nhà cung cấp DNS của bạn, bạn có thể thêm DNS record.

Ảnh dưới đây hiển thị DNS record mình đã dùng cho demo này. Mình dùng EKS nên thay vì LoadBalancer IP thì mình có DNS của network load balancer endpoint - là một CNAME.

Triển khai một ứng dụng demo

For testing ingress, we will deploy a demo application and add a ClusterIp service to it. This application will be accessible only within the cluster without ingress.

Để kiểm tra Ingress, ta sẽ triển khai một ứng dụng demo và thêm ClusterIP service cho nó. Ứng dụng này sẽ chỉ có thể truy cập trong cụm mà không có Ingress.

  1. Tạo dev namespace:
kubectl create namespace dev
  1. Tạo hello-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-app
  namespace: dev
spec:
  selector:
    matchLabels:
      app: hello
  replicas: 3
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
      - name: hello
        image: "gcr.io/google-samples/hello-app:2.0"
  1. Tạo deployment:
kubectl create -f hello-app.yaml

Kiểm tra trạng thái deployment:

kubectl get deployments -n dev
  1. Tạo hello-app-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: hello-service
  namespace: dev
  labels:
    app: hello
spec:
  type: ClusterIP
  selector:
    app: hello
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
  1. Tạo service:
kubectl create -f hello-app-service.yaml

Tạo Ingress Object cho ứng dụng

Giờ hãy tạo Ingress object để truy cập ứng dụng thông qua DNS.

  1. Tạo ingress.yaml :
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
  namespace: dev
spec:
  ingressClassName: nginx
  rules:
  - host: "demo.apps.mlopshub.com"
    http:
      paths:
        - pathType: Prefix
          path: "/"
          backend:
            service:
              name: hello-service
              port:
                number: 80
  1. Describe ingress object để kiểm tra cấu hình
kubectl describe ingress  -n dev

Giờ nếu truy cập demo.apps.mlopshub.com ta sẽ có thể truy cập hello app. (Hãy thay domain của bạn vào nhé).

TLS với Nginx Ingress

You can configure TLS certificates with each ingress object. The TLS gets terminated at the ingress controller level.

Bạn có thể cấu hình TLS certificate với từng Ingress object. TLS sẽ bị terminate ở level Ingress Controller.

Kết luận

Ta đã học về cách cài đặt Nginx Ingress Controller.

Với Nginx controller configmap, bạn có thể cấu hình mọi cài đặt của Nginx mà không cần deploy lại controller.