NGINX로 Kubernetes Load Balancing

Kubernetes 는 클러스터에서 컨테이너화된 Microservices 기반 애플리케이션을 실행하고 관리하기 위해 Google에서 개발한 Open Source 시스템입니다. Kubernetes를 사용하는 사람들은 종종 Kubernetes 클러스터 외부에서 Kubernetes에서 만든 서비스에 액세스할 수 있도록 만들어야 합니다.

Kubernetes는 서비스 Expose를 위한 기본 제공 솔루션을 제공하지만 아래의 기본 제공 솔루션으로 Kubernetes 서비스 Expose에 설명되어 있지만 이러한 솔루션은 Layer 4 Load Balancing 또는 Round-Robin HTTP Load Balancing으로 제한됩니다.

이 포스트에서는 Kubernetes를 클라우드에서 실행하든 자체 인프라에서 실행하든 관계없이 NGINX Plus를 고급 Layer 7 Load Balancing 솔루션으로 사용하여 Kubernetes 서비스를 인터넷에 Expose 하는 방법을 보여줍니다.

Kubernetes(Pod, Service, Replication Controller 및 Label)와 실행 중인 Kubernetes 클러스터에 대해 기본적으로 이해하고 있다고 가정합니다.

목차

1. Built‑In 솔루션으로 Kubernetes 서비스 Expose
1-1. NodePort
1-2. Load Balancer
1-3. Ingress API
2. NGINX Plus로 Kubernetes 서비스 Expose
2-1. DNS 기반 재구성 활용
2-2. NGINX Plus Pod 구성
2-2-1. NGINX Plus Pod를 호스팅할 노드 선택
2-2-2. NGINX Plus Pod에 대한 Replication Controller 구성
2-2-3. NGINX Plus Docker 이미지를 노드에서 사용 가능하게 만들기
2-2-4. NGINX Plus 구성
2-2-5. Replication Controller 생성
2-3. Simple Kubernetes Service 생성
2-3-1. 서비스에 대한 Replication Controller 생성
2-3-2. 서비스 생성
2-4. Kubernetes 서비스 확장
3. 요약

1. Built‑In 솔루션으로 Kubernetes 서비스 Expose

Kubernetes는 서비스 Expose를 위한 몇 가지 옵션을 제공합니다. 그중 NodePort와 LoadBalancer는 특정 유형의 서비스에 해당합니다. 세 번째 옵션인 Ingress API는 Kubernetes 릴리스 1.1에서 베타로 제공되었습니다.

1-1. NodePort

서비스 유형을 NodePort로 지정하면 각 Kubernetes 노드의 동일한 포트에서 서비스를 사용할 수 있습니다. 서비스를 인터넷에 Expose 하려면 해당 포트에서 하나 이상의 노드를 Expose 합니다. 고가용성을 위해 여러 노드를 Expose 하고 DNS 기반 Load Balancing을 사용하여 노드 간에 트래픽을 분산하거나 선택한 Load Balancer 뒤에 노드를 배치할 수 있습니다.

들어오는 트래픽이 포트의 노드에 도달하면 서비스 Pod 간에 Load Balancing 됩니다. 모든 노드에서 실행되는 Kubernetes 네트워크 Proxy(kube-proxy)가 수행하는 Load Balancing은 TCP/UDP Load Balancing으로 제한됩니다.

1-2. Load Balancer

서비스 유형을 LoadBalancer로 지정하면 Ingress 트래픽을 서비스의 Pod 간에 분배하는 클라우드 Load Balancer를 할당합니다.

LoadBalancer 솔루션은 특정 클라우드 제공 업체와 Google Container Engine에서만 지원되며 자체 인프라에서 Kubernetes를 실행 중인 경우에는 사용할 수 없습니다. 또한 Kubernetes에서는 클라우드 Load Balancer에 세션 지속성 또는 요청 매핑과 같은 고급 기능이 있는 경우에도 Round Robin TCP Load Balancing만 구성할 수 있습니다.

1-3. Ingress API

Ingress 리소스를 생성하면 사용자 지정 URL(예: URL /foo의 서비스 A 및 URL /bar의 서비스 B) 및 여러 가상 호스트 이름(예: foo.example.com의 경우 하나의 서비스 그룹 및 bar.example.com은 다른 그룹). Ingress Controller는 Ingress 리소스를 소비하고 외부 Load Balancer를 설정합니다.

Ingress Controller는 표준 Kubernetes 배포의 일부가 아닙니다. 필요에 가장 적합한 Controller를 선택하거나 직접 구현하여 Kubernetes 클러스터에 추가해야 합니다. 많은 Controller 구현이 곧 나타날 것으로 예상되지만 현재 사용 가능한 유일한 구현은 Google Compute Engine 또는 Google Container Engine에서 Kubernetes를 실행하는 경우에만 작동하는 Google Compute Engine HTTP Load Balancer 용 Controller입니다. Ingress API는 실제 Load Balancer가 고급 기능을 지원하더라도 Round-Robin HTTP Load Balancing만 지원합니다.

NGINX 및 NGINX Plus 용 NGINX Ingress Controller는 이제 GitHub Repository에서 사용할 수 있습니다. 제품 세부 정보는 NGINX Ingress Controller를 참조하세요.

위에서 언급한 솔루션은 설정이 간단하고 즉시 사용할 수 있지만 고급 기능, 특히 Layer 7 Load Balancing과 관련된 기능을 제공하지 않습니다.

2. NGINX Plus로 Kubernetes 서비스 Expose

NGINX Plus를 Kubernetes와 통합하려면 NGINX Plus 구성이 Kubernetes와 동기화된 상태를 유지하여 Pod 추가 또는 삭제와 같은 Kubernetes 서비스에 대한 변경 사항을 반영해야 합니다. NGINX Open Source를 사용하면 NGINX 구성 파일을 수동으로 수정하고 구성을 Reload 할 수 있습니다. NGINX Plus에는 구성을 동적으로 업데이트하는 두 가지 방법이 있습니다.

API 사용 – 이 메서드는 NGINX Plus API를 사용하여 NGINX Plus 구성에서 Kubernetes Pod에 대한 항목을 추가 및 제거하고 Kubernetes API를 사용하여 Pod의 IP 주소를 검색합니다. 이 방법을 사용하려면 약간의 코드를 작성해야 하며, 여기서는 자세히 다루지 않습니다.

DNS 이름 re-resolving – 이 메서드는 다음 섹션에 설명된 대로 NGINX Plus를 한 번만 구성하면 됩니다.

2-1. DNS 기반 재구성 활용

이미 실행 중인 Kubernetes 클러스터와 클러스터 관리에 사용할 수 있는 Kubectl 유틸리티가 있는 호스트가 있다고 가정합니다. 자세한 내용은 해당 클러스터 유형에 대한 Kubernetes 시작 가이드를 참조하십시오. 또한 NGINX Plus Docker 이미지를 빌드 해야 하며 자세한 지침은 자습서-Docker와 함께 NGINX 및 NGINX Plus 배포에서 확인할 수 있습니다.

할 일에 대한 개요는 다음과 같습니다.

NGINX Plus Pod를 구성하여 2단계에서 만들고 있는 서비스를 Expose 하고 Load Balancing 합니다.

간단한 웹 애플리케이션 서비스를 만듭니다.

서비스를 확장 및 축소하고 NGINX Plus가 어떻게 자동으로 재구성되는지 확인합니다.

Note: Google Compute Engine에서 실행되는 Kubernetes 1.0.6과 로컬 Vagrant 설정을 사용하여 이 포스트에 설명된 솔루션을 테스트했습니다. 주황색으로 표시된 설정값은 각 환경마다 다를 수도 있습니다.

2-2. NGINX Plus Pod 구성

우리는 인터넷에 Expose 되는 노드의 Kubernetes Pod에 NGINX Plus를 배치하고 있습니다. 이 Pod는 Replication Controller에 의해 생성되며 Kubernetes 관련 NGINX Plus 구성 파일은 NGINX Plus Pod와 노드 간에 공유되는 폴더에 상주하므로 유지 관리가 더 간단해집니다.

2-2-1. NGINX Plus Pod를 호스팅할 노드 선택

NGINX Plus Pod가 실행되는 노드를 지정하기 위해 해당 노드에 Label을 추가합니다. 다음을 실행하여 모든 노드의 목록을 가져옵니다.

$ kubectl get nodes
NAME         LABELS                              STATUS
10.245.1.3   Kubernetes.io/hostname=10.245.1.3   Ready
10.245.1.4   Kubernetes.io/hostname=10.245.1.4   Ready
10.245.1.5   Kubernetes.io/hostname=10.245.1.5   Ready

첫 번째 노드를 선택하고 다음을 실행하여 Label을 추가합니다.

$ kubectl label node 10.245.1.3 role=nginxplus

2-2-2. NGINX Plus Pod에 대한 Replication Controller 구성

NGINX Plus Pod를 직접 생성하는 것이 아니라 Replication Controller를 통해 생성합니다. nginxplus-rc.yaml이라는 Kubernetes 선언 파일에서 NGINX Plus Pod에 대한 Replication Controller를 구성합니다.

  • 우리는 Replica 수를 하나로 설정했습니다. 즉, Kubernetes는 하나의 NGINX Plus Pod가 항상 실행되도록 합니다. Pod가 실패하면 새 Pod로 교체됩니다.
  • nodeSelector 필드에서 우리는 NGINX Plus Pod가 nginxplus 역할로 Label이 지정된 노드에 생성되도록 지정합니다.
  • NGINX Plus 컨테이너는 80 및 8080의 두 포트를 Expose 하고 이들과 노드의 포트 80 및 8080 사이에 매핑을 설정합니다.
  • NGINX Plus 컨테이너는 노드에 있는 /etc/nginx/conf.d 폴더도 공유합니다. 아래 NGINX Plus 구성에서 자세히 설명했듯이 폴더를 공유하면 컨테이너 이미지를 다시 빌드 하지 않고도 NGINX Plus를 재구성할 수 있습니다.
apiVersion: v1
kind: ReplicationController
metadata:
  name: nginxplus-rc
spec:
  replicas: 1
  selector:
    app: nginxplus
  template:
    metadata:
      labels:
        app: nginxplus
    spec:
      nodeSelector:
        role: nginxplus
      containers:
      - name: nginxplus
        imagePullPolicy: IfNotPresent
        image: nginxplus
        ports:
          - name: http
            containerPort: 80
            hostPort: 80
          - name: http-alt
            containerPort: 8080
            hostPort: 8080
        volumeMounts:
          - mountPath: "/etc/nginx/conf.d"
            name: etc-nginx-confd
      volumes:
        - hostPath:
            path: "/etc/nginx/conf.d"
          name: etc-nginx-confd

2-2-3. NGINX Plus Docker 이미지를 노드에서 사용 가능하게 만들기

위에서 말했듯이 우리는 이미 NGINX Plus Docker 이미지를 구축했습니다. 이제 노드에서 사용할 수 있도록 합니다. 단순화를 위해 개인 Docker Repository를 사용하지 않고 이미지를 노드에 수동으로 로드하기만 합니다.

Docker 이미지를 빌드 한 호스트에서 다음 명령을 실행하여 이미지를 파일에 저장합니다.

$ docker save -o nginxplus.tar nginxplus

nginxplus.tar를 노드로 전송하고 노드에서 다음 명령을 실행하여 파일에서 이미지를 로드합니다.

$ docker load -i nginxplus.tar

2-2-4. NGINX Plus 구성

NGINX Plus 컨테이너의 /etc/nginx 폴더에는 NGINX Plus 패키지와 함께 제공되는 기본 기본 nginx.conf 구성 파일이 유지됩니다. 기본 파일의 include 지시문은 /etc/nginx/conf.d 폴더에서 다른 구성 파일을 읽습니다. NGINX Plus Replication Controller(nginxplus-rc.yaml)의 선언 파일에 지정된 대로 NGINX Plus 노드의 /etc/nginx/conf.d 폴더를 컨테이너와 공유합니다. 공유는 NGINX Plus Docker 이미지를 다시 빌드 하지 않고도 노드에 있는 폴더에 저장된 구성 파일을 변경할 수 있음을 의미합니다. 컨테이너에서 직접 폴더를 만든 경우 수행해야 합니다. Kubernetes 관련 구성 파일(backend.conf)을 공유 폴더에 넣습니다.

먼저 노드에 /etc/nginx/conf.d 폴더를 생성합니다.

$ sudo mkdir -p /etc/nginx/conf.d

그런 다음 backend.conf 파일을 만들고 다음 지시문을 포함합니다.

resolver – NGINX Plus가 Upstream 서버를 식별하는 데 사용하는 도메인 이름을 주기적으로 다시 확인하는 데 사용하는 DNS 서버를 정의합니다(upstream 블록 내부의 server 지시문에서 다음 글머리 기호에서 설명). 도메인 이름 kube-dns.kube-system.svc.cluster.local로 이 DNS 서버를 식별합니다. 유효한 매개변수는 NGINX Plus에 5초마다 재해석 요청을 보내도록 지시합니다.

upstream – Expose 중인 Kubernetes 서비스를 제공하는 서버를 포함하는 Backend라는 upstream 그룹을 만듭니다. 서버를 개별적으로 나열하는 대신 단일 server 지시문에서 정규화된 호스트 이름으로 서버를 식별합니다. resolve 매개변수는 resolver 지시문으로 지정된 설정에 따라 런타임에 호스트명을 다시 해석하도록 NGINX Plus에 지시합니다.

Kubernetes DNS와 NGINX Plus(R10 이상) 모두 DNS 서비스(SRV) 레코드를 지원하므로 NGINX Plus는 DNS를 통해 upstream 서버의 포트 번호를 가져올 수 있습니다. NGINX Plus가 SRV 레코드를 요청하도록 service 매개변수를 포함하여 서비스에서 Expose된 포트의 이름(_http) 및 프로토콜(_tcp)을 지정합니다. 아래 서비스에 대한 Replication Controller 만들기에서 설명한 webapp-svc.yaml 파일에서 해당 값을 선언합니다.

server – 두개의 가상 서버를 정의합니다.

첫 번째 서버는 포트 80에서 수신 대기하고 서비스 인스턴스를 실행하는 Pod 간에 /webapp(당사 서비스)에 대한 수신 요청을 Load Balancing 합니다. Active Health Check도 설정했습니다.

두 번째 서버는 포트 8080에서 수신 대기합니다. 여기에서 NGINX Plus의 실시간 활동 모니터링을 설정합니다. 나중에 이를 사용하여 NGINX Plus가 올바르게 재구성되었는지 확인합니다.

resolver kube-dns.kube-system.svc.cluster.local valid=5s;

upstream backend {
    zone upstream-backend 64k;
    server webapp-svc.default.svc.cluster.local service=_http._tcp resolve;
}

server {
    listen 80;
    status_zone backend-servers;

    location /webapp {
        proxy_pass http://backend;
        health_check;
    }
}

server {
    listen 8080;
    root /usr/share/nginx/html;

    location = /dashboard.html { }

    location = / {
        return 302 /dashboard.html;
    }

    location /api {
        api write=on;
    }
}

2-2-5. Replication Controller 생성

이제 다음 명령을 실행하여 Replication Controller를 만들 준비가 되었습니다.

$ kubectl create -f nginxplus-rc.yaml

NGINX Plus Pod가 생성되었는지 확인하기 위해 다음을 실행합니다.

$ kubectl get pods
NAME                 READY     STATUS    RESTARTS   AGE
nginxplus-rc-0ts5t   1/1       Running   0          17s

로컬 Vagrant 설정에서 Kubernetes를 실행하고 있으므로 노드의 외부 IP 주소가 10.245.1.3임을 알고 이 예제의 나머지 부분에서 해당 주소를 사용합니다. 클라우드 공급자에서 Kubernetes를 실행 중인 경우 다음을 실행하여 노드의 외부 IP 주소를 가져올 수 있습니다.

$ kubectl get nodes node-name -o json | grep -i externalIP -A 1
                "type": "ExternalIP",
                "address": XXX.XXX.XXX.XXX

클라우드에서 실행 중인 경우 NGINX Plus 노드가 들어오는 트래픽을 허용하도록 방화벽 규칙을 설정하는 것을 잊지 마십시오.

노드의 외부 IP 주소에 있는 포트 8080에서 사용할 수 있는 NGINX Plus 라이브 활동 모니터링 대시보드(이 경우 http://10.245.1.3:8080/dashboard.html)를 통해 NGINX Plus Pod가 실행 중인지 확인할 수 있습니다. 그러나 이 점을 살펴보면 아직 서비스를 만들지 않았기 때문에 서비스에 대한 서버가 보이지 않습니다.

The NGINX Plus live activity monitoring dashboard before we create the Kubernetes services

2-3. Simple Kubernetes Service 생성

이제 Kubernetes 서비스를 만들 차례입니다. 당사의 서비스는 두 개의 웹 서버로 구성되어 있으며, 각 서버는 실행 중인 컨테이너에 대한 정보가 포함된 웹 페이지를 제공합니다.

2-3-1. 서비스에 대한 Replication Controller 생성

먼저 Replication Controller를 만들어 지정된 수의 웹 서버 Replica(Pod)이 클러스터에서 항상 실행되도록 합니다. 선언 파일(webapp-rc.yaml)은 다음과 같습니다.

apiVersion: v1
kind: ReplicationController
metadata:
  name: webapp-rc
spec:
  replicas: 2
  selector:
    app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      containers:
      - name: hello
        image: nginxdemos/hello 
        ports:
        - containerPort: 80

Controller는 두 개의 웹 서버로 구성됩니다. 포트 80을 Expose 하는 단일 컨테이너가 있는 Pod로 구성된 Controller를 선언합니다. nginxdemos/hello 이미지는 Docker Hub에서 가져옵니다.

Replication Controller를 생성하기 위해 다음 명령을 실행합니다.

$ kubectl create -f webapp-rc.yaml

Pod가 생성되었는지 확인하기 위해 다음 명령을 실행할 수 있습니다. Label 선택기 app=webapp를 사용하여 이전 단계에서 Replication Controller에 의해 생성된 Pod만 가져옵니다.

$ kubectl get pods -l app=webapp
NAME             READY     STATUS    RESTARTS   AGE
webapp-rc-544f1   1/1       Running   0          2m
webapp-rc-uk6pm   1/1       Running   0          2m

2-3-2. 서비스 생성

다음으로 Replication Controller에서 생성한 Pod에 대한 서비스를 생성합니다. 다음 파일(webapp-service.yaml)로 서비스를 선언합니다.

apiVersion: v1
kind: Service
metadata:
  name: webapp-svc
spec:
  clusterIP: None
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
    name: http
  selector:
    app: webapp

여기서는 ClusterIP 필드를 None으로 설정하여 특별한 Headless 서비스를 선언합니다. 이 유형의 서비스에서는 클러스터 IP 주소가 할당되지 않으며 kube Proxy를 통해 서비스를 사용할 수 없습니다. Kubernetes DNS에 대한 DNS 쿼리는 여러 A 레코드(Pod의 IP 주소)를 반환합니다.

또한 NGINX Plus가 Pod를 연결하는 데 사용할 포트를 선언합니다. 포트 및 대상 포트 번호를 지정하는 것 외에도 이름(HTTP) 및 프로토콜(TCP)을 지정합니다. NGINX Plus 구성 파일에서 해당 값과 SRV 레코드를 사용하여 DNS를 통해 Pod의 포트 번호를 가져오도록 NGINX Plus에 지시합니다.

Selector 필드를 app: webapp로 설정하여 NGINX Replication Controller(webapp-rc.yaml에 정의됨)에서 생성된 Pod, 즉 서비스에 속하는 Pod를 선언합니다.

다음 명령을 실행하여 서비스를 생성합니다.

$ kubectl create -f webapp-service.yaml

이제 Dashboard 페이지를 새로 고치고 오른쪽 상단 모서리에 있는 Upstreams 탭을 클릭하면 추가한 두 개의 서버가 표시됩니다.

he NGINX Plus live activity monitoring dashboard after the Kubernetes services are created

또한 NGINX Plus가 서비스 Pod 간에 트래픽을 Load Balancing 하고 있는지 확인할 수 있습니다. 그렇다면 브라우저에서 http://10.245.1.3/webapp/에 액세스하면 호스트 이름 및 IP 주소와 같이 웹 서버가 실행 중인 컨테이너에 대한 정보가 페이지에 표시됩니다.

이 페이지를 여러 번 새로 고치고 상태 Dashboard 를 보면 요청이 두 개의 Upstream 서버에 어떻게 분산되는지 알 수 있습니다.

2-4. Kubernetes 서비스 확장

이제 서비스에 Pod를 두 개 더 추가하고 NGINX Plus 구성이 다시 자동으로 업데이트되는지 확인하겠습니다. 이 명령을 실행하여 Replication Controller를 확장하여 Pod 수를 4개로 변경합니다.

$ kubectl scale rc webapp-rc --replicas=4
scaled

NGINX Plus가 재구성되었는지 확인하기 위해 다시 Dashboard를 볼 수 있지만 이번에는 대신 NGINX Plus API를 사용합니다. 다음 명령을 실행합니다. 10.245.1.3은 NGINX Plus 노드의 외부 IP 주소이고 3은 NGINX Plus API 버전입니다. JSON 출력을 깔끔하게 포맷하기 위해 jq로 Pipe 합니다.

$ curl -s 10.245.1.3:8080/api/3/http/upstreams/backend/servers | jq
{
  "peers": [
    {
      "id": 1,
      "server": "10.0.0.1:80",
      "backup": false,
      "weight": 1,
      "state": "unhealthy",
      "active": 0,
      "requests": 1,
      "responses": {
        "1xx": 0,
        "2xx": 0,
        "3xx": 0,
        "4xx": 0,
        "5xx": 0,
        "total": 0
      },
      "sent": 0,
      "received": 0,
      "fails": 0,
      "unavail": 0,
      "health_checks": {
        "checks": 1,
        "fails": 1,
        "unhealthy": 1,
        "last_passed": false
      },
      "downtime": 33965,
      "downstart": 1445378182275,
      "selected": 1445378131000
    },
    {
      "id": 2,
      "server": "10.246.1.6:80",
      ...
    },
    {
      "id": 3,
      "server": "10.246.3.2:80",
       ...
    {
      "id": 4,
      "server": "10.0.0.2:80",
      ...
    }
  ],
  "keepalive": 0
}

JSON 출력의 peers 배열에는 각 웹 서버에 대해 하나씩 정확히 4개의 요소가 있습니다.

이제 Pod 수를 4개에서 1개로 줄이고 NGINX Plus 상태를 다시 확인하겠습니다.

$ kubectl scale rc webapp-rc --replicas=1
scaled

$ curl -s 10.245.1.3:8080/api/3/http/upstreams/backend/servers | jq

이제 JSON 출력의 peers 배열에는 하나의 요소만 포함됩니다.

이제 NGINX Plus를 실행하고 있으므로 세션 지속성, SSL/TLS Termination, 요청 라우팅, 고급 모니터링 등과 같은 고급 기능을 활용할 수 있습니다.

3. 요약

NGINX Plus에서 사용할 수 있는 즉석 재구성 옵션을 사용하면 API를 통해 프로그래밍 방식으로 또는 전적으로 DNS를 통해 Kubernetes와 쉽게 통합할 수 있습니다. Kubernetes 서비스를 인터넷에 Expose 하기 위해 NGINX Plus를 사용하면 현재 내장된 Kubernetes Load Balancing 솔루션에 없는 많은 기능을 제공합니다.

NGINX Plus가 Kubernetes와 함께 작동하는 방식을 알아보거나 테스트해 보려면 지금 30일 무료 평가판을 신청하거나 사용 사례에 대해 최신 소식을 빠르게 전달받고 싶으시면 아래 뉴스레터를 구독하세요.