Consul 서비스 메시

Consul 서비스 메시 (Consul service mesh)

Consul 서비스 메시를 Nomad와 함께 사용해 서비스 간 안전한 통신을 구현하는 방법을 알아봐요.

출처: 문서

본문

소개

서비스 메시(service mesh)는 워크로드를 직접 연결하기 위해 인프라를 배포하고 구성하는 네트워킹 패턴이에요. 배포되는 가장 흔한 인프라 조각은 사이드카 프록시(sidecar proxy)예요. 이 프록시들은 보통 격리된 네트워크 네임스페이스에서 주 워크로드와 함께 실행되어 모든 네트워크 트래픽이 프록시를 통과하게 해요.

프록시는 데이터를 이동하는 역할을 담당하므로 종종 데이터 플레인(data plane) 으로 불려요. 반면 이를 구성하는 구성 요소는 데이터의 흐름을 제어하는 역할을 담당하므로 컨트롤 플레인(control plane) 의 일부예요.

트래픽을 공통된 인프라 계층으로 모음으로써 컨트롤 플레인은 모든 프록시에 구성을 중앙 집중화하고 자동으로 적용해 자동 트래픽 암호화, 세밀한 라우팅, 메시 전체의 서비스 기반 접근 제어 권한 같은 기능을 활성화할 수 있어요.

Consul service mesh는 상호 전송 계층 보안(mTLS)을 사용해 서비스 간 연결 인가와 암호화를 제공해요. 애플리케이션은 서비스 메시 구성에서 사이드카 프록시를 사용해 서비스 메시를 전혀 인식하지 않고도 인바운드·아웃바운드 연결에 대한 TLS 연결을 자동으로 설정할 수 있어요.

참고: Nomad의 서비스 메시 통합은 Linux 네트워크 네임스페이스를 요구해요. Consul 서비스 메시는 Windows나 macOS에서는 실행되지 않아요.

Nomad와 Consul 서비스 메시 통합

프로덕션 환경에서 Nomad와 Consul은 같은 데이터 센터 안에 존재해요.

Nomad는 Consul과 통합해 Nomad 작업과 태스크 그룹 간의 안전한 서비스 간 통신을 제공해요. Consul 서비스 메시를 지원하기 위해 Nomad는 같은 태스크 그룹의 태스크가 네트워킹 스택을 공유할 수 있게 하는 작업용 새 네트워킹 모드를 추가해요. 작업 명세를 몇 가지 변경하면 작업 작성자는 서비스 메시 통합을 선택할 수 있어요. 서비스 메시가 활성화되면 Nomad는 작업 파일의 애플리케이션 옆에 프록시를 실행해요. 프록시(Envoy)는 클러스터의 다른 애플리케이션과의 안전한 통신을 제공해요.

Nomad 작업 명세 작성자는 Nomad의 Consul 서비스 메시 통합을 사용해 TLS 인증서를 직접 관리하지 않고도 공용 클라우드에서 실행되는 마이크로서비스 아키텍처의 서비스 세그멘테이션을 구현할 수 있어요. 애플리케이션이 확장·축소되거나 Nomad에 의해 재스케줄링되어도 서비스 메시의 보안 기능이 계속 작동하므로 이는 작업 명세 작성자에게 투명해요.

Consul ACL이 활성화된 상태로 Consul 서비스 메시 통합을 사용하려면 Consul Service Mesh로 Nomad 작업 보호하기 가이드를 참고해요.

네트워크 모드

Consul 서비스 메시는 작동에 네트워크 격리를 요구하므로 작업 그룹의 network mode를 bridge 또는 적절히 구성된 cni/* 네트워크로 설정해야 해요.

Consul 서비스 메시와 함께 커스텀 cni/* 네트워크를 사용하려면 각별한 주의가 필요해요. 다양한 네트워크 구성 때문에 Nomad 팀과 엔터프라이즈 지원은 커스텀 네트워크 구성을 지원하는 능력이 제한적이에요. 커스텀 CNI 네트워크를 Consul 서비스 메시와 함께 사용하는 것은 본인 책임이에요. 그래도 Nomad의 bridge 네트워크를 본보기로 네트워크 구성을 모델링할 수 있어요. 네트워크를 구성할 때 다음 특성을 고려해요.

  • Nomad는 격리된 네트워크 네임스페이스를 제공하지만, CNI 구성은 주 태스크를 호스트 네트워크에 노출하지 않아야 해요.
  • 들어오는 트래픽이 사이드카 서비스에 광고될 IP:포트의 사이드카에 도달할 수 있어야 해요.
  • 서로 다른 alloc의 사이드카 간에 트래픽이 흐를 수 있어야 해요.

Nomad Consul 서비스 메시 예시

다음 섹션은 웹 대시보드와 백엔드 카운팅 서비스 간의 안전한 통신을 활성화하는 예시를 살펴봐요. 웹 대시보드와 카운팅 서비스는 Nomad가 관리해요. Nomad는 또한 이 애플리케이션과 함께 Envoy 프록시를 실행하도록 구성해요. 대시보드는 포트 9001의 localhost로 카운팅 서비스에 연결하도록 구성돼요. 프록시는 Nomad가 관리하며 카운팅 서비스와의 mTLS 통신을 처리해요.

사전 요구 사항

Consul

Nomad와의 Consul 서비스 메시 통합은 Consul 1.6 이상을 요구해요. Consul 에이전트는 다음 명령으로 dev 모드에서 실행할 수 있어요.

참고: Nomad의 Consul 서비스 메시 통합은 Consul이 $PATH에 있어야 해요.

$ consul agent -dev

비 dev Consul 에이전트에서 서비스 메시를 사용하려면 최소한 GRPC 포트를 활성화하고 connect를 켜도록 설정해야 해요. 이를 위해 형식에 따라 Consul 클라이언트 구성에 추가 정보를 추가해야 해요. TLS를 실행하는 1.14.0보다 높은 버전의 Consul 에이전트는 grpc 대신 grpc_tls 구성 파라미터를 설정해야 해요. 추가 참고 자료는 Consul 포트 문서를 참고해요.

HCL 구성의 경우:

# ...

ports {
  grpc = 8502
}

connect {
  enabled = true
}

JSON 구성의 경우:

{
  // ...
  "ports": {
    "grpc": 8502
  },
  "connect": {
     "enabled": true
  }
}
Consul TLS

참고: Consul 1.14+는 TLS 활성화 grpc 리스너가 동작하는 방식에 하위 호환되지 않는 변경을 만들었어요. TLS가 활성화된 Consul 1.14를 사용할 때 사용자는 Connect와 함께 작동하도록 추가 Nomad 에이전트 구성을 지정해야 해요. consul.grpc_ca_file 값이 이제 반드시 구성되어야 하고(Nomad 1.4.4에서 도입), consul.grpc_address는 새 표준 grpc_tls 포트인 8503을 사용하도록 설정해야 할 가능성이 높아요.

consul {
  grpc_ca_file = "/etc/tls/consul-agent-ca.pem"
  grpc_address = "127.0.0.1:8503"
  ca_file      = "/etc/tls/consul-agent-ca.pem"
  cert_file    = "/etc/tls/dc1-client-consul-0.pem"
  key_file     = "/etc/tls/dc1-client-consul-0-key.pem"
  ssl          = true
  address      = "127.0.0.1:8501"
}
Consul 접근 제어 목록

참고: Nomad v1.3.0부터 Nomad가 Connect 활성 서비스를 대신해 자동 생성하는 Consul Service Identity ACL 토큰은 전역(Global) 범위가 아닌 로컬(Local) 범위로 생성되며 더 이상 전역으로 복제되지 않아요.

Nomad가 등록한 Connect 서비스의 cross-Consul 데이터 센터 요청을 용이하게 하려면, Consul 에이전트가 그 요청과 관련된 서비스 및 노드 메타데이터를 읽을 수 있는 충분한 권한의 ACL 정책을 가진 기본 익명(anonymous) ACL 토큰으로 구성되어야 해요. 이 메커니즘은 Consul #7414에 설명돼 있어요. 일반적인 Consul 에이전트 익명 토큰은 다음과 같은 ACL 정책을 포함할 수 있어요.

service_prefix "" { policy = "read" }
node_prefix    "" { policy = "read" }
Transparent proxy

Nomad의 transparent proxy 지원을 사용하면 태스크 그룹의 네트워크 네임스페이스가 트래픽이 Envoy 프록시를 통과하도록 구성돼요. transparent_proxy 블록이 활성화되면:

  • Nomad는 consul-cni CNI 플러그인을 호출해 네트워크 네임스페이스에 iptables 규칙을 구성해 할당의 아웃바운드 트래픽이 프록시를 통과하도록 강제해요.
  • 로컬 Consul 에이전트가 DNS를 제공하면 Nomad는 Consul 에이전트의 IP 주소를 태스크의 /etc/resolv.conf의 네임서버로 설정해요.
  • Consul은 서비스 인텐션에 따라 워크로드가 접근할 수 있는 각 업스트림 서비스에 대한 가상 IP를 제공해요.

transparent proxy를 사용하는 데는 몇 가지 중요한 요구 사항이 있어요.

  • 일반적인 필수 CNI 플러그인과 함께 consul-cni CNI 플러그인이 클라이언트 호스트에 설치되어 있어야 해요.
  • Consul DNS와 가상 IP를 사용하려면 Consul의 DNS 리스너가 워크로드 네트워크 네임스페이스에 노출되도록 구성해야 해요. Consul bind_addr를 비공개 IP 주소에 바인딩하면 공용 IP에 Consul 에이전트를 노출하지 않고 이를 할 수 있어요(기본값은 client_addr 사용).
  • 할당이 서비스 메시 밖의 애플리케이션에 대한 DNS 조회를 하길 원한다면 Consul 에이전트가 recursors로 구성되어야 해요.
  • 워크로드의 태스크는 Envoy 사이드카 프록시와 같은 Unix 사용자 ID (UID)를 사용할 수 없어요.
  • 할당에 network.dns 블록을 설정할 수 없어요(no_dns를 설정하지 않는 한, 아래 참고).

예를 들어 go-sockaddr/template로 서브넷 10.37.105.0/20에 바인딩하고 재귀 DNS를 OpenDNS 네임서버로 설정한 HCL 구성:

bind_addr   = "{{ GetPrivateInterfaces | include \"network\" \"10.37.105.0/20\" | limit 1 | attr \"address\" }}"

recursors = ["208.67.222.222", "208.67.220.220"]
Nomad

프록시가 서로 연결되려면 Nomad가 라우팅 가능한 인터페이스에 스케줄링해야 해요. 다음 단계는 Consul 서비스 메시용으로 구성된 Nomad dev 에이전트를 시작하는 방법을 보여줘요.

$ sudo nomad agent -dev-connect
Container Network Interface (CNI) 플러그인

Nomad는 Consul 서비스 메시 사이드카 프록시를 보호하는 데 사용되는 네트워크 네임스페이스를 구성하기 위해 CNI 참조 플러그인을 사용해요. 네트워크 네임스페이스를 사용하는 모든 Nomad 클라이언트 노드에는 이 CNI 플러그인이 설치되어 있어야 해요.

transparent_proxy 모드를 사용하려면 Nomad 클라이언트 노드에 consul-cni 플러그인도 설치되어 있어야 해요. CNI 플러그인 설치 방법에 대한 자세한 내용은 Linux 설치 후 단계를 참고해요.

서비스 메시 활성 서비스 실행

위에서 설명한 대로 transparent proxy 모드용 Consul DNS가 활성화된 상태로 Nomad와 Consul이 실행되면, HCL을 servicemesh.nomad.hcl 파일에 복사하고 nomad job run servicemesh.nomad.hcl을 실행해 다음 서비스 메시 활성 서비스를 Nomad에 제출해요.

job "countdash" {
  datacenters = ["dc1"]

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

    service {
      name = "count-api"
      port = "9001"

      connect {
        sidecar_service {
          proxy {
            transparent_proxy {}
          }
        }
      }
    }

    task "web" {
      driver = "docker"

      config {
        image = "hashicorpdev/counter-api:v3"
      }
    }
  }

  group "dashboard" {
    network {
      mode = "bridge"

      port "http" {
        static = 9002
        to     = 9002
      }
    }

    service {
      name = "count-dashboard"
      port = "http"

      connect {
        sidecar_service {
          proxy {
            transparent_proxy {}
          }
        }
      }
    }

    task "dashboard" {
      driver = "docker"

      env {
        COUNTING_SERVICE_URL = "http://count-api.virtual.consul"
      }

      config {
        image = "hashicorpdev/counter-dashboard:v3"
      }
    }
  }
}

작업에는 API 서비스와 웹 프런트엔드라는 두 태스크 그룹이 포함돼요.

API 서비스

API 서비스는 브리지 네트워크를 가진 태스크 그룹으로 정의돼요.

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

  # ...
}

API 서비스는 Consul 서비스 메시를 통해서만 접근할 수 있으므로 네트워크에 포트를 정의하지 않아요. connect 블록은 서비스 메시를 활성화하고 transparent_proxy 블록은 Consul DNS와 함께 사용할 때 서비스가 가상 IP 주소로 도달 가능하도록 보장해요.

group "api" {

  # ...

  service {
    name = "count-api"
    port = "9001"

    connect {
      sidecar_service {
        proxy {
          transparent_proxy {}
        }
      }
    }
  }

  # ...

}

service 블록의 port는 API 서비스가 수신하는 포트예요. Envoy 프록시는 네트워크 네임스페이스 내부의 해당 포트로 트래픽을 자동으로 라우팅해요. 현재는 named 포트가 될 수 없고 하드코딩된 포트 값이어야 한다는 점을 참고해요. GH-9907을 참고해요.

웹 프런트엔드

웹 프런트엔드는 브리지 네트워크와 정적 전달 포트를 가진 태스크 그룹으로 정의돼요.

group "dashboard" {
  network {
    mode = "bridge"

    port "http" {
      static = 9002
      to     = 9002
    }
  }

  # ...

}

static = 9002 파라미터는 Nomad 스케줄러가 호스트 네트워크 인터페이스에 포트 9002를 예약하도록 요청해요. to = 9002 파라미터는 그 호스트 포트를 네트워크 네임스페이스 내부의 포트 9002로 전달해요.

이를 통해 http://<host_ip>:9002를 방문해 브라우저에서 웹 프런트엔드에 연결할 수 있어요.

웹 프런트엔드는 Consul 서비스 메시를 통해 API 서비스에 연결해요.

service {
  name = "count-dashboard"
  port = "http"

  connect {
    sidecar_service {
      proxy {
        transparent_proxy {}
      }
    }
  }
}

transparent_proxy를 가진 connect 블록은 웹 프런트엔드의 네트워크 네임스페이스가 count-api 서비스에 대한 모든 접근을 Envoy 프록시를 통해 라우팅하도록 구성해요.

웹 프런트엔드는 환경 변수 $COUNTING_SERVICE_URL로 API 서비스와 통신하도록 구성돼요.

env {
  COUNTING_SERVICE_URL = "http://count-api.virtual.consul"
}

transparent_proxy 블록은 count-api.virtual.consul 이름이 가상 IP 주소로 해석되도록 DNS 조회가 Consul에 대해 이루어지게 해요. 가상 IP는 올바른 서비스 포트로만 향하므로 포트 번호를 지정할 필요가 없다는 점을 참고해요.

수동으로 구성된 업스트림

Consul DNS와 transparent_proxy 모드 없이 Connect를 사용할 수도 있어요. 이 접근 방식은 Nomad 작업 명세의 upstreams 블록에서 서비스 인텐션 정보를 복제해야 하므로 권장되지 않아요. 그러나 Consul DNS는 ACL로 보호되지 않으므로, 신뢰할 수 없는 워크로드에 Consul DNS를 노출하고 싶지 않다면 이렇게 할 수 있어요.

그 경우 작업 명세에 upstream 블록을 추가할 수 있어요. count-api 서비스에는 transparent_proxy 블록이 필요 없어요.

group "api" {

  # ...

  service {
    name = "count-api"
    port = "9001"

    connect {
      sidecar_service {}
    }
  }

  # ...

}

하지만 count-dashboard 서비스에는 upstreams 블록을 추가해야 해요.

service {
  name = "count-dashboard"
  port = "http"

  connect {
    sidecar_service {
      proxy {
        upstreams {
          destination_name = "count-api"
          local_bind_port  = 8080
        }
      }
    }
  }
}

upstreams 블록은 접근할 원격 서비스(count-api)와 네트워크 네임스페이스 내부에서 그 서비스를 노출할 포트(8080)를 정의해요.

웹 프런트엔드도 API 서비스와 통신하기 위해 환경 변수를 사용해야 해요.

env {
  COUNTING_SERVICE_URL = "http://${NOMAD_UPSTREAM_ADDR_count_api}"
}

이 환경 변수 값은 업스트림의 주소로 보간돼요. 환경 변수에서는 대시(-)가 밑줄(_)로 변환되므로 count-api는 count_api가 된다는 점을 참고해요.

Envoy 프록시

Consul Service Mesh는 [Envoy]를 프록시로 사용해요. Nomad는 초기 프록시 구성을 생성하기 위해 Consul의 [consul connect envoy -bootstrap] CLI 명령을 호출해요.

Nomad는 Envoy 프록시를 실행하기 위해 prestart 사이드카 Docker 태스크를 주입해요. 이 태스크는 [sidecar_task] 블록을 사용해 사용자 지정할 수 있어요.

게이트웨이

메시는 선택된 서비스만 참여할 수 있는 폐쇄된 경계를 정의하므로, 메시 전역 연결에 사용할 수 있는 게이트웨이(gateway)라는 특수 프록시가 있어요. Nomad는 [gateway] 블록을 사용해 이 게이트웨이를 배포할 수 있어요. Nomad는 gateway 서비스가 있는 모든 group에 Envoy 프록시 태스크를 주입해요.

Consul Service Mesh가 제공하는 게이트웨이 유형은 다음과 같아요.

  • 메시 게이트웨이(Mesh gateways) 는 서로 다른 서비스 메시 간의 통신을 허용하며 [mesh] 파라미터로 배포돼요.
  • 인그레스 게이트웨이(Ingress gateways) 는 메시 밖의 서비스가 메시 안의 서비스에 연결하도록 허용하며 [ingress] 파라미터로 배포돼요.
  • 이그레스 게이트웨이(Egress gateways) 는 메시 안의 서비스가 메시 밖의 서비스와 통신하도록 허용하며 [terminating] 파라미터로 배포돼요.

제한 사항

  • Nomad와 함께 Connect를 사용하기 위한 최소 Consul 버전은 Consul v1.8.0이에요.
  • 클라이언트 노드에서 Envoy 프록시 사이드카를 실행하려면 consul 바이너리가 Nomad의 $PATH에 있어야 해요.
  • 네트워크 네임스페이스를 사용하는 Consul 서비스 메시는 Linux에서만 지원돼요.
  • Consul 1.9 이전에는 Nomad 에이전트가 재시작되는 동안 Envoy 사이드카 프록시가 연결을 끊고 중지해요.

문제 해결

사이드카 서비스가 올바르게 실행되지 않으면 다음 방법으로 잠재적인 envoy 실패를 조사할 수 있어요.

  • 관련 connect-* 태스크의 태스크 로그
  • 태스크 시크릿(민감한 정보를 포함할 수 있음): envoy CLI 명령: secrets/.envoy_bootstrap.cmd, 환경 변수: secrets/.envoy_bootstrap.env
  • 추가 Allocation 로그 파일: alloc/logs/envoy_bootstrap.stderr.0

예를 들어 b36a로 시작하는 할당 ID의 경우:

nomad alloc status -short b36a  # to get the connect-* task name
nomad alloc logs -task connect-proxy-count-api -stderr b36a
nomad alloc exec -task connect-proxy-count-api b36a cat secrets/.envoy_bootstrap.cmd
nomad alloc exec -task connect-proxy-count-api b36a cat secrets/.envoy_bootstrap.env
nomad alloc fs b36a alloc/logs/envoy_bootstrap.stderr.0

참고: alloc이 성공적으로 시작할 수 없다면 디버깅 파일은 호스트 파일 시스템에서만 접근할 수 있을 수 있어요. 그러나 사이드카 태스크 시크릿 디렉토리는 임시 파일 시스템에 마운트된 시스템에서는 사용할 수 없을 수 있어요.

Envoy 프록시를 부트스트랩하려면 Consul ACL 토큰과 서비스 등록이 로컬 Consul 에이전트가 연결된 어떤 Consul 서버에든 성공적으로 복제되어 있어야 해요. Nomad 클라이언트는 지수 백오프와 타임아웃으로 이 값을 폴링해요. 명령줄 또는 client.meta 에이전트 구성 블록에서 노드 메타데이터 값을 설정해 특정 노드의 타임아웃을 조정할 수 있어요. 기본값은 아래에 표시돼요.

nomad node meta apply -node-id $nodeID \
    consul.token_preflight_check.timeout=10s \
    consul.token_preflight_check.base=500ms \
    consul.service_preflight_check.timeout=60s \
    consul.service_preflight_check.base=1s

더 알아보기 (Learn more)