네트워킹

네트워킹 (Networking)

Nomad의 네트워킹 기능, Nomad에서 네트워킹이 동작하는 방식, 다양한 패턴과 구성, 그리고 Nomad 네트워킹이 Kubernetes 네트워킹과 어떻게 다른지에 대한 개념 정보를 알아봐요.

출처: 문서

본문

이 페이지는 Nomad의 네트워킹 기능에 대한 개념 정보, Nomad에서 네트워킹이 어떻게 동작하는지, 다양한 패턴과 구성, 그리고 Nomad 네트워킹이 Kubernetes 네트워킹과 어떻게 다른지에 대해 제공해요.

소개

Nomad는 워크로드 오케스트레이터이며 배포의 스케줄링 측면에 집중하고, 네트워킹 같은 영역에는 가능한 한 조금만 관여해요.

Nomad의 네트워킹은 보통 인프라가 아닌 *구성(configuration)*을 통해 수행돼요. 즉, Nomad는 DNS 서버나 로드 밸런서 같은 추가 구성 요소를 배후에서 실행하는 대신, 워크로드를 연결하는 데 필요한 정보에 접근할 수 있는 방법을 제공해요.

할당 네트워킹

Nomad에서 스케줄링의 기본 단위는 할당(allocation)이에요. 즉, 같은 할당의 모든 태스크는 같은 클라이언트에서 실행되고 디스크와 네트워킹 같은 공용 리소스를 공유해요. 할당은 network 블록을 사용해 포트 같은 네트워크 리소스에 대한 접근을 요청할 수 있어요. 기본 network 블록은 다음과 같이 정의할 수 있어요.

job "..." {
  # ...
  group "..." {
    network {
      port "http" {}
    }
    # ...
  }
}

Nomad는 클라이언트의 min_dynamic_port와 max_dynamic_port 사이에서 아직 할당되지 않은 무작위 포트를 예약해요. 그런 다음 Nomad는 호스트 네트워크 인터페이스에서 할당으로 포트 매핑을 만들어요.

태스크는 NOMAD_PORT_ 환경 변수를 사용해 선택된 포트 번호에 접근하여 클라이언트의 IP 주소와 주어진 포트에서 워크로드를 바인딩하고 노출할 수 있어요.

구체적인 구성 과정은 무엇을 실행하는지에 따라 달라요. 그러나 보통은 다음과 같은 구성 파일을 만들기 위해 template을 사용해요.

job "..." {
  # ...
  group "..." {
    network {
      port "http" {}
    }

    task "..." {
      # ...
      config {
        args = [
          "--port=${NOMAD_PORT_http}",
        ]
      }
    }
  }
}

명령줄 인자를 통해 구성을 전달할 수도 있어요.

port에 static 값을 설정하면 무작위 대신 특정 포트 번호를 요청할 수도 있어요. 스케줄링 충돌을 피하기 위해 수동으로 관리하기 어려울 수 있으므로 로드 밸런서와 시스템 작업 같은 특수한 워크로드에만 사용해야 해요.

태스크가 클라이언트 포트 중 하나에서 수신 대기하면 다른 프로세스가 클라이언트의 IP와 포트를 사용해 태스크에 직접 접근할 수 있지만, 먼저 프로세스가 이러한 값을 찾아야 해요. 이 과정을 서비스 디스커버리(service discovery)라고 해요.

IP와 포트를 사용해 할당을 연결할 때는 네트워크 토폴로지와 라우팅 구성이 Nomad 클라이언트 서로 통신할 수 있도록 하는 것이 중요해요.

바인딩 주소와 0.0.0.0

Nomad의 port 스탠자는 호스트 IP와 포트를 예약해요. 워크로드는 여전히 네트워크 네임스페이스 안에서 수신 주소를 선택해요.

수신 주소 일반적인 용도
0.0.0.0 네임스페이스의 모든 주소에서 연결을 수락해요. 트래픽이 호스트(또는 다른 인터페이스)에서 전달되고 프로세스가 단일 IP에 고정되지 않아야 할 때 사용해요. NOMAD_PORT_(host 모드) 또는 매핑된 to 포트(bridge 모드)와 함께 사용해요.
NOMAD_IP_<label> Nomad가 해당 포트에 할당한 호스트 IP에서만 수신해요.
127.0.0.1 서비스를 할당에만 비공개로 유지해요(bridge 모드와 서비스 메시 프록시에서 흔히 사용).

호스트 게시 주소를 0.0.0.0으로 설정하는 작업 수준 필드는 없어요. 호스트 측 매핑은 선택된 host_network의 주소를 사용해요(설정하지 않으면 "default" 네트워크). bridge 및 cni/* 모드에서 클라이언트가 사용자 지정 host_network 블록을 정의하지 않으면 bind_wildcard_default_host_network가 기본적으로 true가 되어 매핑 규칙이 호스트 포트의 모든 대상 주소와 일치해요. 클라이언트 host_network 블록을 정의하면 매핑이 그 주소들에 고정돼요.

전체 예시는 network 블록 참조를 참고해요.

브리지 네트워킹

Linux 클라이언트는 bridge라는 네트워크 mode를 지원해요. 브리지 네트워크는 가상 네트워크 스위치처럼 동작해 브리지에 연결된 프로세스들이 서로 도달할 수 있게 하면서 다른 프로세스로부터 격리해요.

Nomad의 브리지 네트워크는 Container Network Interface (CNI) 참조 플러그인을 활용해 워크로드 네트워킹을 구성하기 위한 운영 체제 비종속적 인터페이스를 제공해요. Nomad의 네트워크 플러그인 지원은 Nomad의 내장 컴퓨트 리소스 스케줄링을 확장해 특수 네트워크 구성으로 태스크를 스케줄링할 수 있게 해요. Nomad는 이를 CNI 참조 플러그인과 CNI 구성 파일의 조합으로 구현해요. Nomad는 CNI 사양을 준수하는 모든 네트워크 플러그인을 지원하지만, 프로덕션에 배포하기 전에 플러그인 호환성을 Nomad 버전과 확인해야 해요.

브리지 네트워킹이 동작하는 방식

할당이 브리지 네트워킹을 사용하면, Nomad 에이전트는 bridge CNI plugin을 사용해 (아직 없으면) nomad라는 이름(또는 bridge_network_name에 설정된 값)의 브리지를 만들어요. 이 모드를 사용하기 전에 먼저 CNI 플러그인을 클라이언트에 설치해야 해요. 기본적으로 Nomad는 각 Nomad 클라이언트에 단일 브리지를 만들어요.

bridge 네트워크 모드를 사용하는 할당은 격리된 네트워크 네임스페이스에서 실행되고 브리지에 연결돼요. 이를 통해 Nomad는 호스트의 무작위 포트를 할당 내부에서 태스크가 기대하는 특정 포트 번호에 매핑할 수 있어요.

예를 들어 다음 network 블록으로 기본적으로 포트 3000에서 수신하는 HTTP 서버를 구성할 수 있어요.

job "..." {
  # ...
  group "..." {
    network {
      mode = "bridge"

      port "http" {
        to = 3000
      }
    }
    # ...
  }
}

다른 클라이언트의 할당 간 통신을 허용하기 위해 Nomad는 호스트 네트워크 인터페이스에서 브리지로 요청을 전달하는 iptables 규칙을 만들어요. 그 결과 세 가지 다른 네트워크 접근 범위가 생겨요.

  • 루프백 인터페이스(localhost 또는 127.0.0.1)에 바인딩된 태스크는 할당 내부에서만 접근할 수 있어요.
  • port 포워딩 없이 브리지(또는 0.0.0.0 같은 일반 주소)에 바인딩된 태스크는 같은 클라이언트 내에서만 접근할 수 있어요.
  • port 포워딩과 함께 브리지(또는 0.0.0.0 같은 일반 주소)에 바인딩된 태스크는 외부 소스에서 접근할 수 있어요.

경고: bridge 네트워크 모드를 사용할 때 어떤 유형의 외부 접근도 방지하려면 워크로드를 루프백 인터페이스에만 바인딩해야 해요.

브리지 네트워킹은 서비스 메시의 핵심이며 Consul Service Mesh를 사용할 때 요구 사항이에요.

Docker와 함께하는 브리지 네트워킹

Docker 데몬은 자체 네트워크 구성을 관리하고 자체 브리지 네트워크, 네트워크 네임스페이스, iptables 규칙을 만들어요. docker 태스크 드라이버를 사용하는 태스크는 Nomad가 만든 브리지 대신 Docker 브리지에 연결하며, 기본적으로 각 컨테이너는 자체 Docker 관리 네트워크 네임스페이스에서 실행돼요.

bridge 네트워크 모드를 사용할 때 Nomad는 infra_image에 정의된 이미지를 사용해 placeholder 컨테이너를 만들어 할당의 모든 태스크가 공유하는 Docker 네트워크 네임스페이스를 초기화해 서로 통신할 수 있게 해요.

Docker 태스크 드라이버에는 자체 작업 수준의 network_mode 구성이 있어요. 기본값은 그룹 수준의 network.mode 구성에 따라 달라요.

group "..." {
  network {
    mode = "bridge"
  }

  task "..." {
    driver = "docker"

    config {
      # This conflicts with the group-level network.mode configuration and
      # should not be used.
      network_mode = "bridge"
      # ...
    }
  }
}

경고: 작업 수준의 network_mode는 그룹 수준의 network.mode 구성과 충돌해 예상치 못한 결과를 만들 수 있어요. 그룹 network.mode = "bridge"를 설정했다면 Docker config network_mode는 설정하지 말아야 해요.

이 다이어그램은 Docker 태스크가 잘못 구성되었을 때 어떤 일이 발생하는지 보여줘요.

가장 오른쪽 할당의 태스크들은 서로 다른 네트워크 네임스페이스에 배치되었기 때문에 루프백 인터페이스를 사용해 서로 통신할 수 없어요.

그룹 network.mode가 bridge이므로 Nomad는 모든 태스크를 위한 공유 네트워크 네임스페이스를 만들기 위해 pause 컨테이너를 만들지만, 작업 수준의 network_mode를 bridge로 설정하면 태스크가 다른 네임스페이스에 배치돼요. 이로 인해 예를 들어 태스크가 서비스 메시 배포에서 사이드카 프록시와 통신하지 못할 수 있어요.

자세한 내용은 network_mode 문서와 Networking 섹션을 참고해요.

참고: 비 Linux 환경의 Docker Desktop은 로컬 가상 머신을 실행해 추가적인 간접 계층을 더해요. 자세한 내용은 FAQ를 참고해요.

다른 도구와의 비교

Kubernetes와 Docker Compose

Kubernetes와 Docker Compose의 네트워킹은 Nomad와 다르게 동작해요. 컨테이너에 접근하려면 Docker Compose의 db나 Kubernetes의 db.prod.svc.cluster.local 같은 정규화된 도메인 이름(FQDN)을 사용해요. 이 과정은 호스트 이름을 해석하고 여러 컨테이너에 요청을 분산하기 위해 추가 인프라에 의존해요.

Docker Compose는 서비스(service)라는 단위를 사용해 여러 컨테이너를 실행하고 관리할 수 있게 해요.

version: "3.9"
services:
  web:
    build: .
    ports:
      - "8000:8000"
  db:
    image: postgres
    ports:
      - "8001:5432"

다른 컨테이너에서 서비스에 접근하려면 postgres://db:5432처럼 서비스 이름을 직접 참조할 수 있어요. 이 패턴을 활성화하기 위해 Docker Compose에는 내부 DNS 서비스와 사용자에게 투명한 로드 밸런서가 포함돼요. Swarm 모드에서 실행할 때 Docker Compose는 호스트 간에 요청을 라우팅하기 위해 오버레이 네트워크도 필요로 해요.

Kubernetes는 Pod 집합에 어떻게 접근하는지 선언하는 데 사용할 수 있는 Service 추상화를 제공해요.

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

Service에 접근하려면 my-service.prod.svc.cluster.local 같은 FQDN을 사용해요. 이 이름은 모든 노드에서 실행되는 애드온인 DNS 서비스에 의해 해석돼요. 이 서비스와 함께 각 노드는 Service와 일치하는 모든 Pod에 요청을 분산하는 kube-proxy 인스턴스도 실행해요.

Consul의 DNS 인터페이스와 DNS 포워딩으로 클라이언트를 구성하고 로드 밸런서를 배포하면 Nomad에서도 같은 FQDN 네트워킹 스타일을 사용할 수 있어요.

Nomad와의 또 다른 핵심 차이는 Kubernetes와 Docker Compose에서 각 컨테이너가 자체 IP 주소를 가져 물리적 IP 주소를 가상 주소에 매핑하기 위해 가상 네트워크가 필요하다는 점이에요. Docker Compose를 Swarm 모드에서 사용하는 경우 여러 호스트 간에 트래픽을 활성화하기 위해 overlay도 필요해요. 이렇게 하면 같은 서비스를 실행하는 여러 컨테이너가 같은 포트 번호에서 수신할 수 있어요.

Nomad에서 할당은 실행 중인 클라이언트의 IP 주소를 사용하고 무작위 포트 번호를 할당받아요. DNS를 사용한 Nomad 서비스 디스커버리는 A 또는 AAAA 레코드 대신 SRV 레코드를 사용해요.

다음 주제

추가 리소스

더 알아보기 (Learn more)