[DevOps] Nginx ingress 사용해보기

안녕하세요? 정리하는 개발자 워니즈입니다. 이번시간에는 Nginx Ingress Controller에 대해서 정리해보는 시간을 갖도록 하겠습니다. 필자가 속한 프로젝트에서는 기존에 별도의 Ingress Layer가 존재하지 않았습니다. 그러다보니 매번 어플리케이션의 연결을 Node Port로 할다을 하고 LB와 연결을 해주는 방식으로 진행했었습니다.

운영을 해보면서 가장큰 문제점은 무엇보다도 매번 어플리케이션들이 생성될 때마다 별도의 Node Port를 할당해야된다는것과 이것이 Host의 외부로 노출이 되다보니 보안상의 위험에 노출이 되었습니다.

이번에 k8s를 재설치 할 일이 생겼었는데 겸사겸사해서 아키텍처를 변경하기로 했습니다.

Ingress Controller가 무엇인지, 그리고 어떤 아키텍처로 설계를 했는지에 대해서 정리해보는 시간을 갖도록 하겠습니다.

Nginx_Ingress_1

1. Ingress란 무엇인가요?

Ingress라는 것을 왜 사용해야하는지부터 생각을 해봐야될 것 같습니다. 최신 소프트웨어 아키텍처는 대부분 마이크로 서비스 아키텍처를 지향하고 있스니다. 일부는 내부의 백엔드 서비스를 하지만, 많은 수가 외부로 노출되는 경우가 있습니다.

많은 수의 어플리케이션들의 라우팅을 관리하고, 정적 컨테츠 또는 프론트엔드와 같은 엔드포인트가 하나의 IP주소를 통해 노출되어 여러 도메인 이름에 대한 컨텐츠를 제공할 수 있는 것은 바로 Ingress가 담당하는 역할입니다.

Ingress는 다른 리소스와 마찬가지로 Kubernetes의 Resource의 한 유형입니다. 목적은 클러스터 내부 서비스에 대한 요청을 라우팅하는 것을 정읳바니다. Ingress는 URL을 클러스터 Service에 매핑을 합니다.

Nginx_Ingrss_2

예를들어, frontend-svc라는 서비스를 도메인에 연결하는 시나리오를 생각해보겠습니다.

도메인 : sample-service.example.com
프론트 서비스 : frontend-svc
백엔듯 버시스 : backend-svc

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: digitalfrontiers-sample-ingress
spec:
  rules:
    - host: sample-service.example.com
      http:
        paths:
          - path: /
            backend:
              serviceName: frontend-svc
              servicePort: 80

Ingress Resouce는 굉장히 심플한 구조입니다.

    - host: sample-service.example.com
      http:
        paths:
          - path: /
            backend:
              serviceName: frontend-svc
              servicePort: 80

Rule 하위로 라우팅 규칙을 지정을 하고 어떤 도메인어떤 서비스어떤포트로 연결을 하는지를 지정하게 됩니다.

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: digitalfrontiers-sample-ingress
spec:
  rules:
    - host: sample-service.example.com
      http:
        paths:
          - path: /
            backend:
              serviceName: frontend-svc
              servicePort: 80
          - path: /api
            backend:
              serviceName: backend-svc
              servicePort: 8081

backend-svc를 추가한 내용은 위와 같습니다. /api의 경로에 대해서는 백엔드 서비스를 이용하도록 지정했습니다.

이러한 방식으로 동일한 IP 주소로 원하는 만큼 서비스를 노출 할 수 있습니다. 이것은 L7로드 밸런서이면서 호스트기반 또는 경로기반의 리다이렉션 및 SSL offlading등을 지원합니다.

2. Ingress-Controller란 무엇인가요?

필자는 처음 k8s를 학습 할 때 Ingress Resource만 생성을 하게 되면, 앞단에 설정이 이미 완료가 된것으로 생각을 했었습니다. 확실하게 정의를 하면, IngressIngress Controller의 설정 정보라고 보시면 될 것 같습니다.

Nginx Ingress Controller

시중에 나와있는 수 많은 수신 컨트롤러가 있지만 가장 대중적으로 많이 사용하는 Nginx 인그레스 컨트롤러에 대해서 정리해보겠습니다. 앞서 설정한 Ingress의 Rule대로 Nginx의 Conf를 자동적으로 수정하게 됩니다.
Nginx Ingress Controller는 Nginx의 POD를 띄우고, Ingress를 통해서 설정파일을 변경하는 구조입니다.

필자는 쿠버네티스에서 관리하는 공식 Nginx Ingrss Controller 인스톨 가이드를 따라 진행 했습니다.
필자는 별도의 쿠버네티스 Managed 서비스를 쓰지 않기 떄문에 Bare-metal인스톨 가이드를 따라 진행했고, 다음의 파일을 설치했습니다.

https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.0/deploy/static/provider/baremetal/deploy.yaml

아래와 같이 필요한 리소스 구성이 되어있고, 가장 중요한것은 Nginx실행을 위한 Deployment와 Configmap입니다. 그리고 추가적으로 Admission Webhook이라는 리소스가 있습니다. 이부분은 추후 별도의 포스팅으로 정리를 하겠습니다. (간단하게는 Nginx의 리소스를 업데이트하기 위해서 Validation을 해주는 역할이라고 보면 됨.)

필요 리소스 : Namespace, Service Account, RBAC 리소스, Nginx Configmap, Nginx Deployment

2. 아키텍처 설계

이전에는 Node Port를 외부로 노출시켜 직접 LB에 연결하는 방식이였습니다. 그러다보니 모든 어플리케이션들이 외부로 노출시키는 포트를 갖고 있었습니다.

따라서 아래와 같은 단점이 있었습니다.

  • LB에서 직접 인증에 대한 처리를 진행하여 개별 LB마다 인증서 작업을 진행해야 한다는 것

  • 개별 LB마다 연결하는 Node Port들이 다르고 신규 추가건이 생기면 연결을 매번 해야한다는 것

Ingress Layer를 도입한 이후로는 인증서에 대한 처리와 어플리케이션 연결 처리를 모두 Nginx에서 담당을 하다보니, Ingress Resource에 명시된 대로 작업이 훨씬 간결하고 운영하기 편리했습니다.

3. Nginx Controller 설치간 고려 사항

설계까지는 모두 완료가 되었고, 이번 목차는 Nginx Ingress Controller의 Deployment와 관련된 내용입니다. Nginx를 띄울때 2가지 방식으로 선택이 가능합니다.

  • Daemoneset
  • Deployment

Daemonset의 경우 k8s의 노드에 IP 주소가 10.10.10.1 ~ 6이고 api.domain.com이라는 도메인에 연결을 한다고 가정했을때, 외부 로드밸런서를 사용하고 이를 6개의 각 Worker Node에 연결하여 트래픽을 분산할 수 있습니다.

위의 경우, Worker Node의 갯수가 젂은 경우 적절하다고 판단되지만, 지속적으로 증가하는 서비스의 경우 Worker Node가 늘어날 가능성이 있고, 컴퓨팅 리소스 확장이 어려운경우 모든 Node의 Nginx를 띄우는것은 비효율적일 것입니다.

template:
  spec:
    hostNetwork: true

Host네트워크를 사용하므로 별도의 포트 변환 없이 80, 443을 사용할 수 있다는 이점이 있습니다. 하지만 Host Network를 사용하는 순간 외부로 80, 443이 오픈이 되고 이는 심각한 보안 위협이 될 수 있다는 점은 인지해야합니다.

Deployment의 경우 Ingress Nginx Controller의 Service Resource의 타입을 Node Port로 지정을 하고 외부로 3xxxx 번대의 포트를 오픈하여 LB와 바인딩 하는 방법입니다.

위의 아키텍처 그림에도 설명되어있듯이, Nginx의 서비스를 노드포트 타입으로 오픈시키고 그것을 LB와 바인딩하였습니다.

LB에서는 80, 443 포트에 대한 Listen을 정의하고 해당 트래픽을 다시 3XXXX의 포트로 전달 하여 Nginx로 전달합니다.

Nginx_ingress_3

4. 적용 테스트

적용 테스트에 앞서 위의 공식 인스톨 가이드를 통해서 Nginx Ingress Controller가 사전에 설치가 되어있어야 합니다.

1) Sample Deployment 작성

필자는 샘플 앱을 사용하였고, github 주소는 다음과 같습니다.

샘플 어플리케이션

apiVersion: v1
kind: Service
metadata:
  name: sample-server
  labels:
    app.name: sample-server
spec:
  type: NodePort
  ports:
  - port: 8080
    targetPort: 8080
    nodePort: 30001
    name: server
  selector:
    app.name: sample-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
  labels:
    app.name: sample-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app.name: sample-server
  template:
    metadata:
      labels:
        app.name: sample-server
    spec:
      imagePullSecrets:
      - name: link-docker-registry
      containers:
      - name:  sample-container
        image: sample-app:v1.0.0
        resources:
          requests:
            memory: 4Gi
            cpu: 2
          limits:
            memory: 4Gi
            cpu: 2
        ports:
        - containerPort: 8080
        env:
        - name: TZ
          value: Asia/Seoul

1번째 어플리케이션은 단순하게 루트로 접속을 했을 때, Hello World를 출력해주는 어플리케이션입니다.

apiVersion: v1
kind: Service
metadata:
  name: sample2-server
  labels:
    app.name: sample2-server
spec:
  type: NodePort
  ports:
  - port: 8080
    targetPort: 8080
    nodePort: 30002
    name: server
  selector:
    app.name: sample2-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample2-deployment
  labels:
    app.name: sample2-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app.name: sample2-server
  template:
    metadata:
      labels:
        app.name: sample2-server
    spec:
      imagePullSecrets:
      - name: link-docker-registry
      containers:
      - name:  sample2-container
        image: sample-app:v2.0.0
        resources:
          requests:
            memory: 4Gi
            cpu: 2
          limits:
            memory: 4Gi
            cpu: 2
        ports:
        - containerPort: 8080
        env:
        - name: TZ
          value: Asia/Seoul

2번째 어플리케이션은 루트로 접속했을 때와 nginx-test로 접속했을 때 nginx-test라는 문구를 출력해주는 것을 추가했습니다.

2) Secret 작성

kubectl create secret tls ab-tls --key ./decrypt_nginx-test/*.nginx-test.com.key --cert ./decrypt_nginx-test/*.nginx-test_fullchain.crt 

kubectl create secret tls me-tls --key ./decrypt_nginx-test2/*.nginx-test2.me.key --cert ./decrypt_nginx-test2/*.nginx-test2_fullchain.crt 

필자는 TLS 종료까지 적용하기 위해서 인증서를 다운받아서, fullchain과 key를 이용하여 Secret에 인증서를 참조 할수 있도록 생성했습니다. 생성된 인증서는 Ingress Resource에서 참조 합니다.

3) Ingrss Resource 작성

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: sample-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
  tls:
  - hosts:
    - nginx-test.sample-test.com
    secretName: ab-tls
  - hosts:
    - nginx-test2.sample-test.me
    secretName: me-tls
  rules:
  - host: nginx-test.sample-test.com
    http:
      paths:
      - path: /
        backend:
          serviceName: sample-server
          servicePort: 8080
  - host: nginx-test2.sample-test.me
    http:
      paths:
      - path: /
        backend:
          serviceName: sample2-server
          servicePort: 8080

Ingress를 보면

  • tls하위로 어떤 도메인으로 들어왔을 경우, 어떤 인증서를 참조할지를 명시했습니다.
  • rules하위로 어떤 도메인으로 들어왔을 경우, 어떤 path에 대해서 backend는 어떤 서비스를 참조할지를 명시했습니다.

해당 도메인을 통해서 접속을 하게 되면, 정상적으로 라우팅이 되는 것을 확인 할 수 있습니다.

5. Nginx 사용자 지정 설정

nginx 파드가 시작되면 구성파일이 configmap을 통해 주입됩니다. 프로젝트에서 제공하는 기본 configmap은 다음과 같습니다.

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-configuration
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

nginx 파드에는 이미지에서 가져온 기본 nginx 구성이 있지만 필요에 따라 구성 수준을 조정해야하는 경우가 있습니다. 이렇게 하려면 데이터 섹션에 특정 매개변수를 추가하면 됩니다.

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-configuration
  namespace: ingress-nginx
  labels:
    app: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
data:
  proxy-connect-timeout: "15"
  proxy-buffering: "on"
  proxy-buffers: "64 4k"
  proxy-buffers-number: "4"
  proxy-buffer-size: "16k"
  forwarded-for-header: "X-Forwarded-For"
  http2-max-field-size: "32k"
  http2-max-header-size: "128k"

이 예에서는 ConfigMap에 일부 프록시 관련 구성 매개변수를 설정합니다. 따라서 nginx 파드가 시작될 때마다 이러한 매개변수도 고려해야 합니다.

6. 마치며..

Ingress 및 Ingress 컨트롤러에 대하여 간략하게 정리를 해보았습니다. Ingrss가 없이 사용하던 이전의 아키텍처 대비해서 Ingress Layer를 추가함으로써 얻을 수 있는 이점이 많아졌습니다. 무엇보다도 Nginx인 Web Layer가 추가되면서 할수 있는 다양한 기능( 라우팅, Access Log 수집, TLS 종료 등)들을 활용할 수 있었습니다.

다음시간에는 Nginx의 Admission Controller에 대해서 정리를 해보는 시간을 갖도록 하겠습니다.

7. 참조

kubernetes nginx 공식 가이드
쿠버네티스 Ingress 개념 및 사용방법, 온-프레미스 환경에서 Ingress 구축하기
Configuration of Kubernetes (k8) services with NGINX Ingress controller
Kubernetes Ingress with NGINX
Kubernetes Nginx Ingress Controller for On-Premise Environments

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다