본문 바로가기

Technical/Cloud, Virtualization, Containers

[Kubernetes ingress part-1] Nginx ingress 구현과 사용 방법


K8s(쿠버네티스) 클러스터 내에 구현된 Application을 외부로 노출하여 서비스할 경우에 주로 Nginx의 LB 기능을 이용한 Ingress Load Balancer를 사용하게 된다. 기본적으로 제공되는 이러한 Nginx 컨테이너 방식 외에도 Haproxy, Traefik Ingress등의 3rd party 솔루션들이 존재하는데, 다음 part-2 에서는 최근 hot하게 뜨고 있는 Traefik Ingress를 사용한 구현을 다룰 예정이다.



필요 요소와 서비스 구성


  • Kubernetes Cluster(Local cluster, minikube or dind-cluster not in the cloud like AWS or GCP)
  • Backend Service(like web or specific app server)
  • 테스트 수행용 컨테이너(curl로 클러스터 내부 접속 테스트)
  • Local DNS(Cluster 외부, named 서버)


예제로 사용하는 백엔드 서비스는 단순한 web server로, 앞선 포스팅(http://bryan.wiki/287) 에서 만들어 본 웹서버 컨테이너 이미지(drlee001/kubeweb)을 사용한다. 





위 그림은 본 구현에서 적용된 전체 요소들의 논리적인 구성과 관계를 나타낸 것이다. 텍스트가 작아서 잘 보이지 않을 경우, 이미지를 클릭하여 원본 크기로 자세히 들여다 보도록 하자.



테스트 수행용 컨테이너 기동


클러스터 내부의 각 서비스에 대한 접속 테스트를 curl 로 수행할 때 사용할 Pod로, 네트워크를 테스트하기 위해 많이 사용하는 drlee001/net-tester 컨테이너를 기동해 둔다.


<00-test-pod.yaml>

apiVersion: v1
kind: Pod
metadata:
  name: net-tester-for-ingress
spec:
  containers:
  - image: drlee001/net-tester
    imagePullPolicy: IfNotPresent
    name: net-tester-container
  restartPolicy: Never

[root@kubemaster nginx-ingress-sample]# kubectl create -f 00-test-pod.yaml

[root@kubemaster nginx-ingress-sample]# kubectl get pods | grep net-tester

net-tester-for-ingress                      1/1       Running   0          13h



Backend Service 기동


Load Balancing 의 대상이 되는 예제 서비스로, 이미 만들어진 drlee001/kubeweb 컨테이너를 deploy 하고 ClusterIP 로 service를 기동해 둔다. 여기서는 2개의 서로 다른 서비스에 대해 하나의 Ingress를 통해 Load balancing이 가능하도록 deploy-test-1(port 7777로 접속) 과 deploy-test-2(port 8888로 접속)으로 구분하여 구축해 두도록 한다.


<01-deploy-2backends.yaml>

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: deploy-test-1
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: web-front-end
        department: group1
    spec:
      containers:
      - name: tiny-webserver-1
        image: drlee001/kubeweb
        env:
        - name: PORT_ARGS
          value: "--port=7777"
        ports:
        - containerPort: 7777
          name: web-port
          protocol: TCP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: deploy-test-2
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: web-front-end
        department: group2
    spec:
      containers:
      - name: tiny-webserver-2
        image: drlee001/kubeweb
        env:
        - name: PORT_ARGS
          value: "--port=8888"
        ports:
        - containerPort: 8888
          name: web-port
          protocol: TCP

[root@kubemaster nginx-ingress-sample]# kubectl create -f 01-deploy-2backends.yaml

deployment "deploy-test-1" created

deployment "deploy-test-2" created

[root@kubemaster nginx-ingress-sample]# kubectl get deployment | grep deploy-test

deploy-test-1              2         2         2            2           13h

deploy-test-2              2         2         2            2           13h



다음으로, 2개의 서로 다른 deployment 에 대응하는 Cluster 내부 접속 endpoint 를 제공할 서비스를 구축한다.


<02-svc-2backends.yaml>

apiVersion: v1
kind: Service
metadata:
  name: backend-svc-1
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: web-port
  selector:
    app: web-front-end
    department: group1
---
apiVersion: v1
kind: Service
metadata:
  name: backend-svc-2
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: web-port
  selector:
    app: web-front-end
    department: group2

[root@kubemaster nginx-ingress-sample]# kubectl create -f 02-svc-2backends.yaml

service "backend-svc-1" created

service "backend-svc-2" created

[root@kubemaster nginx-ingress-sample]# kubectl get svc | grep backend-svc

backend-svc-1                          ClusterIP   10.133.108.20    <none>        80/TCP                         13h

backend-svc-2                          ClusterIP   10.131.128.20    <none>        80/TCP                         13h



Default backend 서비스 기동


Ingress LB를 통해서 들어온 request가, 등록된 Ingress rule(나중에 정의해서 사용할)에 매칭되지 않을 경우의 적당한 response 를 위해서, 이러한 request 를 받아줄 별도의 내부 서비스가 필요하다. 이를 default backend(일종의 랜딩페이지) 라고 하는데, port 8080으로 들어온 http 접속에 대해 하위 path가 '/' 인 경우 404(not found)를, '/healthz' 인 경우 200(ok) 를 응답하는 간단한 web server 이다. 여기서는 구글이 제공하는 defaultbackend 컨테이너를 사용한다.


<03-default-backend-for-ingress.yaml>

apiVersion: v1
kind: Service
metadata:
  name: default-http-backend
spec:
  type: ClusterIP
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080
  # Matches Deployment
  selector:
    app: default-http-backend
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: default-http-backend
spec:
  replicas: 2
  template:
    metadata:
      # Matches Deployment
      labels:
        app: default-http-backend
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: default-http-backend
        # Any image is permissable as long as:
        # 1. It serves a 404 page at /
        # 2. It serves 200 on a /healthz endpoint
        image: gcr.io/google_containers/defaultbackend:1.0
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 30
          timeoutSeconds: 5
        ports:
        - containerPort: 8080
        resources:
          limits:
            cpu: 10m
            memory: 20Mi
          requests:
            cpu: 10m
            memory: 20Mi

[root@kubemaster nginx-ingress-sample]# kubectl create -f 03-default-backend-for-ingress.yaml

deployment "default-http-backend" created

service "default-http-backend" created

[root@kubemaster nginx-ingress-sample]# kubectl get svc | grep default-http

default-http-backend                   ClusterIP   10.142.34.97     <none>        80/TCP                         20h



테스트용 컨테이너로 서비스 정상 동작 확인


[root@kubemaster nginx-ingress-sample]# kubectl get svc

NAME                                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                        AGE

backend-svc-1                          ClusterIP   10.133.108.20    <none>        80/TCP                         20h

backend-svc-2                          ClusterIP   10.131.128.20    <none>        80/TCP                         20h

default-http-backend                   ClusterIP   10.142.34.97     <none>        80/TCP                         20h


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://backend-svc-1

<b> Pod/Hostname: deploy-test-1-76754b9b75-9p2lx Port: 7777</b><br/><br/>


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://backend-svc-2

<b> Pod/Hostname: deploy-test-2-6f57b84975-9jnf5 Port: 8888</b><br/><br/>


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://default-http-backend

 default backend - 404



Nginx Ingress Controller 설정값 저장을 위한 Configmap 등록


Nginx는 웹서버 기능과 함께 로드밸런서 역할도 수행할 수 있게 만들어져 있다. 편리하게도 kubernetes 클러스터 내에 이 설정 값을 configmap 형태로 등록해 두고, 필요에 따라 설정값을 바꿔가면서 운영할 수 있는데, 진행 과정 상 Nginx Ingress Controller를 띄우기 전에 미리 등록해 두어야 한다. 


<04-configmap-nginx-ingress-controller.yaml>

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-ingress-controller-conf
  labels:
    app: nginx-ingress-lb
    group: lb
data:
  # for VTS page of the Nginx load balancer
  enable-vts-status: 'true'
  enable-sticky-sessions: 'true'

[root@kubemaster nginx-ingress-sample]# kubectl create -f 04-configmap-nginx-ingress-controller.yaml

configmap "nginx-ingress-controller-conf" created



Nginx Ingress Controller 실행


<05-deploy-nginx-ingress-controller.yaml>

# Add: SA, ClusterRole, ClusterRoleBinding
# Name Space: default
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ingress
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: system:ingress
rules:
- apiGroups:
  - ""
  resources: ["configmaps","secrets","endpoints","events","services"]
  verbs: ["list","watch","create","update","delete","get"]
- apiGroups:
  - ""
  - "extensions"
  resources: ["services","nodes","ingresses","pods","ingresses/status"]
  verbs: ["list","watch","create","update","delete","get"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: ingress
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:ingress
subjects:
  - kind: ServiceAccount
    name: ingress
    # Could be like 'kube-system' ...
    namespace: default
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx-ingress-controller
  # Could be like 'kube-system' ...
  namespace: default
spec:
  replicas: 2
  revisionHistoryLimit: 3
  template:
    metadata:
      labels:
        app: nginx-ingress-lb
    spec:
      serviceAccountName: ingress
      terminationGracePeriodSeconds: 60
      containers:
        - name: nginx-ingress-controller
          image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2
          imagePullPolicy: IfNotPresent
          readinessProbe:
            httpGet:
              path: /healthz
              port: 18080
              scheme: HTTP
          livenessProbe:
            httpGet:
              path: /healthz
              port: 18080
              scheme: HTTP
            initialDelaySeconds: 10
            timeoutSeconds: 5
          args:
            - /nginx-ingress-controller
            - --default-backend-service=$(POD_NAMESPACE)/default-http-backend
            - --configmap=$(POD_NAMESPACE)/nginx-ingress-controller-conf
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - containerPort: 80
            - containerPort: 18080

[root@kubemaster nginx-ingress-sample]# kubectl create -f 05-deploy-nginx-ingress-controller.yaml

deployment "nginx-ingress-controller" created


[root@kubemaster nginx-ingress-sample]# kubectl get deployment nginx-ingress-controller

NAME                       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

nginx-ingress-controller   2         2         2            2           20h



여기까지 진행하였다면 다음과 같은 여러 개의 pod 가 실행되고 있음을 확인할 수 있다.


[root@kubemaster nginx-ingress-sample]# kubectl get pods -o wide

NAME                                        READY     STATUS    RESTARTS   AGE       IP              NODE

default-http-backend-7c7764588c-8w4qg       1/1       Running   0          20h       10.38.0.6       kubenode3

default-http-backend-7c7764588c-qtbfk       1/1       Running   0          20h       10.32.0.5       kubenode1

deploy-test-1-76754b9b75-9p2lx              1/1       Running   0          21h       10.40.0.5       kubenode2

deploy-test-1-76754b9b75-hwnwc              1/1       Running   0          21h       10.38.0.5       kubenode3

deploy-test-2-6f57b84975-9jnf5              1/1       Running   0          21h       10.40.0.6       kubenode2

deploy-test-2-6f57b84975-zhrs8              1/1       Running   0          21h       10.32.0.3       kubenode1

net-tester-for-ingress                      1/1       Running   0          21h       10.32.0.2       kubenode1

nginx-ingress-controller-6679d99f68-gzkhb   1/1       Running   0          20h       10.40.0.7       kubenode2

nginx-ingress-controller-6679d99f68-wnshp   1/1       Running   0          20h       10.32.0.6       kubenode1


이 중에서 실제로 로드밸런서 역할을 담당할 pod가 nginx-ingress-controller-*-* 같이 생긴 pod들이다(사실 하나만 띄워도 되지만, 고가용성-HA-를 위해서는 2개 이상을 사용). net-tester를 통해서 각각의 nginx-ingress-controller 에 직접 접속이 되는지 확인해 보자. 아직은 따로 Ingress Rule을 적용하지 않았기 때문에 '404' 응답이 나오면 정상적인 상태이다(실은, 이 '404' 응답은 그림에서 보이듯이 default-http-backend 에서 보내주는 것이다).


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://10.40.0.7

default backend - 404

[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://10.32.0.6

default backend - 404 



Ingress Rule 디플로이


이제 모든 준비는 끝났고, Nginx Ingress Controller 가 로드밸런서 역할을 수행할 수 있도록 Ingress Rule(Policy, 또는 Ingress Object) 를 디플로이하고 서비스로 노출(Expose)시키는 단계와, curl을 통한 접속 테스트, DNS 등록을 통한 실제 접속을 해 보는 일만 남았다. 아래 yaml을 보면 총 3개의 도메인에 대한 rule이 존재하는데, Nginx 또는 Apache httpd 등으로 웹서버를 운영해 보았다면, 하나의 웹서버에서 서로 다른 도메인에 대해 각각 특정한 웹페이지를 연결해 주는  'VirtualHost' 의 동작 방식과 유사함을 보게 될 것이다.


<06-ingress-rule.yaml>

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx-ingress
  namespace: default
spec:
  rules:
  - host: kubeweb-7777.bryan.local
    http:
      paths:
      - backend:
          serviceName: backend-svc-1
          servicePort: 80
  - host: kubeweb-8888.bryan.local
    http:
      paths:
      - backend:
          serviceName: backend-svc-2
          servicePort: 80
  - host: kubeweb.bryan.local
    http:
      paths:
      - path: /svc1
        backend:
          serviceName: backend-svc-1
          servicePort: 80
      - path: /svc2
        backend:
          serviceName: backend-svc-2
          servicePort: 80
      - path: /nginx_status
        backend:
          serviceName: nginx-ingress-nodeport
          servicePort: 18080

[root@kubemaster nginx-ingress-sample]# kubectl create -f 06-ingress-rule.yaml

ingress "nginx-ingress" created

[root@kubemaster nginx-ingress-sample]# kubectl get ingress

NAME            HOSTS                                                                   ADDRESS   PORTS     AGE

nginx-ingress   kubeweb-7777.bryan.local,kubeweb-8888.bryan.local,kubeweb.bryan.local             80        20h


여기까지는 Kubernetes 클러스터 내부에서의 연결 통로만 설정이 완료된 상태다.


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb-7777.bryan.local http://10.40.0.7

<b> Pod/Hostname: deploy-test-1-76754b9b75-hwnwc Port: 7777</b><br/><br/>

[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb-8888.bryan.local http://10.40.0.7

<b> Pod/Hostname: deploy-test-2-6f57b84975-zhrs8 Port: 8888</b><br/><br/>

* Host Header 를 조작하여 접속을 시도하는 세션에 도메인명을 담아서 접속하면 Ingress Rule 에 따라 2개의 서비스 각각에 분기 접속되는 것을 확인할 수 있다(위의 06-ingress-rule.yaml에서 정의된 첫 번 째, 두 번 째 rule에 매칭된다)


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb.bryan.local http://10.40.0.7

default backend - 404 

[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb.bryan.local http://10.40.0.7/svc1

<b> Pod/Hostname: deploy-test-1-76754b9b75-9p2lx Port: 7777</b><br/><br/> 

[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb.bryan.local http://10.40.0.7/svc2

<b> Pod/Hostname: deploy-test-2-6f57b84975-9jnf5 Port: 8888</b><br/><br/>

* 위와 비슷하지만 이번에는 kubeweb,bryan.local 도메인에 대한 URL path 별로 접속이 분기됨을 확인할 수 있다(06-ingress-rule.yaml에서 정의된 세 번 째 rule에 매칭된다)



Ingress 서비스 노출(Expose)


Kubernetes Cluster 의 외부에서 웹브라우저 등을 통해서 내부에 존재하는 backend-svc-1, backend-svc-2 에 접속하기 위해서는 NodePort 방식으로 접속하면 된다(AWS나 Google Cloud Engine과 같은 퍼블릭클라우드에 Kubernetes 가 설치된 경우에는 Loadbalancer 방식으로 접속하면 된다).


<07-svc-expose-by-nodeport.yaml>

apiVersion: v1
kind: Service
metadata:
  name: nginx-ingress-nodeport
spec:
  type: NodePort
  ports:
    - port: 80
      nodePort: 30100
      name: http
    - port: 18080
      nodePort: 30101
      name: http-mgmt
  selector:
    app: nginx-ingress-lb

[root@kubemaster nginx-ingress-sample]# kubectl create -f 07-svc-expose-by-nodeport.yaml

service "nginx-ingress-nodeport" created

[root@kubemaster nginx-ingress-sample]# kubectl get svc nginx-ingress-nodeport -o wide

NAME                     TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                        AGE       SELECTOR

nginx-ingress-nodeport   NodePort   10.138.77.194   <none>        80:30100/TCP,18080:30101/TCP   21h       app=nginx-ingress-lb


이제 클러스터 외부에서 웹브라우저를 통해 NodePort 로 접속을 시도해 보아야 할텐데, 그 전에 한 가지 더 할 일이 있다. 클러스터 외부의 DNS의 존파일에 여기서 사용할 도메인의 A 레코드들을 등록해 주어야 한다. 다음과 같이 DNS 서버에 추가로 작업해 주고 'systemctl restart named' 로 named 서비스를 재시작한다.


[root@bryan-dns ~]# vi /var/named/bryan.local.zone 

...

; For testing nginx-ingress

kubeweb-7777 IN A 10.255.10.171

IN A 10.255.10.172

IN A 10.255.10.173


kubeweb-8888 IN A 10.255.10.171

IN A 10.255.10.172

IN A 10.255.10.173


kubeweb IN A 10.255.10.171

IN A 10.255.10.172

IN A 10.255.10.173



웹브라우저로 Ingress 접속 확인








다음 캡처 이미지는 ConfigMap 에서 enable-vts-status 로 사용하도록 하고 ingress-rule.yaml 에서 연결되도록 설정하고 NodePort로 expose시킨 Ingress Status 모니터링 화면이다.



* Reference: dasblinkenlichten.com 포스팅 내용과 아이디어를 다수 참조 하였음


* 다음 포스팅인 Part-2 에서는 Traefik Ingress를 활용한 Ingress 로드밸런서를 구현해 보고, Nginx 방식에 비해 어떤 점이 다르고 또 장점은 무엇인지 생각하는 시간을 가져보기로 한다. 구현과 테스트 방식은 이번 Part-1 과 유사하며, 양 쪽에 놓고 비교해 보는 자료로 활용되기를 기대한다.



- Barracuda -