인터넷에서 가장 바쁜 웹사이트라면 NGINX와 NGINX Plus가 시장을 장악하고 있습니다. 실제로 NGINX는 다른 어떤 웹 서버보다 세계에서 가장 바쁜 100만 개 사이트 중 더 많은 서비스를 제공합니다 . 단일 서버에서 100만 개 이상의 동시 연결을 처리할 수 있는 기능 덕분에 Airbnb, Netflix, Uber와 같은 "하이퍼스케일" 사이트와 앱에서 채택이 촉진되었습니다.
NGINX Plus는 웹 서버, HTTP 역방향 프록시, 부하 분산 장치로 가장 잘 알려져 있지만, TCP와 UDP 애플리케이션을 지원하는 모든 기능을 갖춘 애플리케이션 전송 컨트롤러(ADC)이기도 합니다. 이벤트 기반 아키텍처 와 HTTP 사용 사례에서 성공을 거두는 데 필요한 모든 기타 속성은 사물 인터넷(IoT)에도 마찬가지로 적용할 수 있습니다.
이 문서에서는 NGINX Plus를 사용하여 MQTT 트래픽의 부하를 분산하는 방법을 보여줍니다. MQTT는 원래 1999년에 원격 유전과의 통신을 위해 출시되었습니다. 2013년에 IoT 사용 사례에 맞춰 업데이트되었으며 그 이후로 많은 IoT 배포에 사용되는 프로토콜이 되었습니다. 수백만 개의 기기를 사용하는 프로덕션 IoT 배포에는 로드 밸런서에 높은 성능과 고급 기능이 요구되며, 이 2부로 구성된 블로그 게시물 시리즈에서는 다음과 같은 고급 사용 사례에 대해 설명합니다.
NGINX Plus의 기능을 살펴보기 위해 MQTT 브로커 클러스터가 있는 IoT 환경의 주요 구성 요소를 나타내는 간단한 테스트 환경을 사용하겠습니다. 이 환경의 MQTT 브로커는 Docker 컨테이너 내부에서 실행되는 HiveMQ 인스턴스입니다.
NGINX Plus는 MQTT 브로커의 역방향 프록시 및 부하 분산 장치 역할을 하며, 기본 MQTT 포트 1883에서 수신합니다. 이를 통해 클라이언트에 간단하고 일관된 인터페이스를 제공하는 동시에 백엔드 MQTT 노드는 클라이언트에 어떤 영향도 미치지 않고 확장(심지어 오프라인으로 전환)될 수 있습니다. 테스트 환경의 IoT 장치를 나타내는 클라이언트로 Mosquitto 명령줄 도구를 사용합니다.
이 게시물과 2부의 모든 사용 사례는 이 테스트 환경을 사용하며, 모든 구성은 그림에 표시된 아키텍처에 직접 적용됩니다. 테스트 환경을 구축하는 방법에 대한 전체 지침은 부록 1을 참조하세요.
로드 밸런서 의 주요 기능은 클라이언트에 영향을 주지 않고 백엔드 서버를 추가, 제거하거나 오프라인으로 전환할 수 있도록 애플리케이션에 고가용성을 제공하는 것입니다. 이를 안정적으로 수행하려면 각 백엔드 서버의 가용성을 적극적으로 조사하는 상태 검사가 필요합니다. NGINX Plus는 활성 상태 검사를 통해 실제 클라이언트 요청이 도달하기 전에 로드 밸런싱 그룹에서 실패한 서버를 제거할 수 있습니다.
상태 점검의 유용성은 실제 애플리케이션 트래픽을 얼마나 정확하게 시뮬레이션하고 응답을 분석하는지에 따라 달라집니다. ping과 같은 간단한 서버 활성 상태 확인으로는 백엔드 서비스가 실행 중인지 확인할 수 없습니다. TCP 포트 개방 검사는 애플리케이션 자체가 정상인지 확인하지 않습니다. 여기서는 각 백엔드 서버가 새로운 MQTT 연결을 허용할 수 있는지 확인하는 상태 점검을 통해 테스트 환경에 대한 기본적인 부하 분산을 구성합니다.
두 개의 설정 파일을 변경하고 있습니다.
기본 nginx.conf 파일에서 다음 스트림
블록과 include
지시어를 포함하여 NGINX Plus가 stream_conf.d 하위 디렉토리에 있는 하나 이상의 파일에서 TCP 부하 분산 구성을 읽도록 합니다. 이 디렉토리는 nginx.conf 와 같은 디렉토리에 있습니다. 우리는 nginx.conf 에 실제 구성을 포함하는 대신 이렇게 합니다.
그런 다음 nginx.conf 와 같은 디렉토리에 TCP 및 UDP 구성 파일을 포함하기 위해 stream_conf.d 디렉토리를 생성합니다. 기존의 conf.d 디렉토리는 기본적으로 http
구성 컨텍스트용으로 예약되어 있으므로 거기에 스트림
구성을 추가하면 실패할 것이므로 사용하지 않도록 주의하세요.
stream_mqtt_healthcheck.conf 에서 먼저 MQTT 트래픽에 대한 액세스 로그 형식을 정의합니다(1~2행). 이는 결과 로그를 로그 분석 도구로 가져올 수 있도록 의도적으로 HTTP 일반 로그 형식과 유사합니다.
다음으로, 3개의 MQTT 서버를 포함하는 hive_mq 라는 업스트림
그룹을 정의합니다(4~9행). 테스트 환경에서는 각각 고유한 포트 번호를 사용하여 로컬호스트에서 접근할 수 있었습니다. zone
지시어는 부하 분산 상태 및 상태 정보를 유지하기 위해 모든 NGINX Plus 작업자 프로세스에서 공유되는 메모리 양을 정의합니다.
일치
블록(11~15행)은 MQTT 서버의 가용성을 테스트하는 데 사용되는 상태 검사를 정의합니다. 전송
지시문은 nginx
health
check
의 클라이언트 식별자(ClientId)를 포함하는 완전한 MQTT CONNECT
패킷의 16진수 표현입니다. 이는 상태 검사가 실행될 때마다 업스트림 그룹에 정의된 각 서버로 전송됩니다. 해당 expect
지시문은 NGINX Plus가 서버가 정상이라고 간주하기 위해 반환해야 하는 응답을 설명합니다. 여기서 4바이트 16진수 문자열20
02
00
00
완전한 MQTT CONNACK
패킷입니다. 이 패킷을 수신하면 MQTT 서버가 새로운 클라이언트 연결을 수신할 수 있음을 보여줍니다.
서버
블록(17~25행)은 NGINX Plus가 클라이언트를 처리하는 방법을 구성합니다. NGINX Plus는 기본 MQTT 포트 1883에서 수신하고 모든 트래픽을 hive_mq 업스트림 그룹으로 전달합니다(라인 19). health_check
지시어는 업스트림 그룹에 대해 상태 검사를 수행(기본 빈도 5초)하고 mqtt_conn 일치
블록에서 정의한 검사를 사용함을 지정합니다.
이 기본 구성이 작동하는지 테스트하려면 Mosquitto 클라이언트를 사용하여 테스트 환경에 일부 테스트 데이터를 게시할 수 있습니다.
$ mosquitto_pub -d -h mqtt.example.com -t "topic/test" -m "test123" -i "thing001" 클라이언트 thing001이 CONNECT를 전송하고 있습니다. 클라이언트 thing001이 CONNACK을 수신했습니다. 클라이언트 thing001이 PUBLISH(d0, q0, r0, m1, 'topic/test', ...를 전송하고 있습니다. (7바이트)) 클라이언트 thing001이 DISCONNECT $ tail --lines=1 /var/log/nginx/mqtt_access.log 192.168.91.1 [2017년 3월 23일:11:41:56 +0000] TCP 200 23 4 127.0.0.1:18831을 전송합니다.
액세스 로그의 한 줄은 NGINX Plus가 총 23바이트를 수신했고 그 중 4바이트가 클라이언트로 전송되었음을 보여줍니다( CONNACK
패킷). 또한 MQTT node1이 선택되었음을 볼 수 있습니다(포트 18831). 액세스 로그의 다음 줄에서 볼 수 있듯이 테스트를 반복하면 기본 라운드 로빈 부하 분산 알고리즘은 차례로 node1 , node2 , node3을 선택합니다.
$ tail --lines=4 /var/log/nginx/mqtt_access.log 192.168.91.1 [2017-03-23:11:41:56 +0000] TCP 200 23 4 127.0.0.1:18831 192.168.91.1 [2017-03-23:11:42:26 +0000] TCP 200 23 4 127.0.0.1:18832 192.168.91.1 [2017-03-23:11:42:27 +0000] TCP 200 23 4 127.0.0.1:18833 192.168.91.1 [2017-03-23:11:42:28 +0000] TCP 200 23 4 127.0.0.1:18831
[ 편집자 - 다음 사용 사례는 NGINX JavaScript 모듈에 대한 여러 사례 중 하나일 뿐입니다. 전체 목록은 NGINX JavaScript 모듈의 사용 사례를 참조하세요.]
이 섹션의 코드는 블로그가 처음 게시된 이후 NGINX JavaScript 구현의 변경 사항을 반영하기 위해 다음과 같이 업데이트되었습니다.
들
) 객체를 사용합니다.js_import
지시문은 다음을 대체합니다. js_포함
지시문에 NGINX 플러스 R23 그리고 나중에. 자세한 내용은 NGINX JavaScript 모듈 의 참조 문서를 참조하세요. 예제 구성 섹션에서는 NGINX 구성 및 JavaScript 파일에 대한 올바른 구문을 보여줍니다.라운드 로빈 로드 밸런싱은 클라이언트 연결을 여러 서버 그룹에 분산하는 효과적인 메커니즘입니다. 하지만 MQTT 연결에 적합하지 않은 데에는 몇 가지 이유가 있습니다.
MQTT 서버는 종종 클라이언트와 서버 간에 장시간 연결이 유지되기를 기대하며, 서버에서 많은 양의 세션 상태가 축적될 수 있습니다. 안타깝게도 IoT 기기와 이 기기에 사용되는 IP 네트워크의 특성상 연결이 끊어지는 경우가 잦아 일부 클라이언트는 자주 다시 연결해야 합니다. NGINX Plus는 해시 부하 분산 알고리즘을 사용하여 클라이언트 IP 주소를 기반으로 MQTT 서버를 선택할 수 있습니다. 업스트림 블록에 해시
$remote_addr;
를 추가하기만 하면 세션이 지속되어 주어진 클라이언트 IP 주소에서 새로운 연결이 들어올 때마다 동일한 MQTT 서버가 선택됩니다.
하지만 IoT 기기가 동일한 IP 주소에서 다시 연결될 것이라고 확신할 수는 없습니다. 특히 셀룰러 네트워크(예: GSM 또는 LTE)를 사용하는 경우 더욱 그렇습니다. 동일한 클라이언트가 동일한 MQTT 서버에 다시 연결되도록 하려면 MQTT 클라이언트 식별자를 해싱 알고리즘의 키로 사용해야 합니다.
MQTT ClientId는 초기 CONNECT
패킷의 필수 요소입니다. 즉, 패킷이 업스트림 서버로 프록시되기 전에 NGINX Plus에서 사용할 수 있습니다. NGINX JavaScript를 사용하면 CONNECT
패킷을 구문 분석하고 ClientId를 변수로 추출할 수 있습니다. 이후 이 변수는 해시
지시문에서 MQTT 관련 세션 지속성을 구현하는 데 사용될 수 있습니다.
NGINX JavaScript는 "NGINX 네이티브" 프로그래밍 구성 언어입니다. 이는 NGINX 및 NGINX Plus를 위한 고유한 JavaScript 구현으로, 특히 서버 측 사용 사례와 요청별 처리를 위해 설계되었습니다. MQTT의 세션 지속성 구현에 적합하게 만드는 세 가지 주요 특징이 있습니다.
CONNECT
패킷의 실제 구문 분석에는 20줄 미만의 코드가 필요합니다.NGINX JavaScript를 활성화하는 방법에 대한 지침은 부록 2를 참조하세요.
이 사용 사례에 대한 NGINX Plus 구성은 비교적 간단합니다. 다음 구성은 활성 상태 검사를 통한 부하 분산 의 예를 수정한 버전으로, 간결성을 위해 상태 검사를 제거했습니다.
js_import
지시문을 사용하여 NGINX JavaScript 코드의 위치를 지정하는 것으로 시작합니다. js_set
지시어는 NGINX Plus가 $mqtt_client_id
변수를 평가해야 할 때 setClientId
함수를 호출하도록 지시합니다. 5번째 줄의 mqtt 로그 형식 에 이 변수를 추가하여 액세스 로그에 더 많은 세부 정보를 추가합니다.
12번째 줄에서 해시
지시문에서 $mqtt_client_id를
키로 지정하여 세션 지속성을 활성화합니다. 일관된
매개변수를 사용하면 업스트림 서버에 장애가 발생하더라도 해당 서버의 트래픽이 나머지 서버에 균등하게 분산되어 해당 서버에 이미 설정된 세션에 영향을 미치지 않습니다. 일관된 해싱에 대해서는 웹 캐시 샤딩에 대한 블로그 게시물에서 더 자세히 설명합니다. 여기서도 원칙과 이점이 동일하게 적용됩니다.
js_preread
지시어(18번째 줄)는 요청 처리의 사전 읽기 단계에서 실행되는 NGINX JavaScript 함수를 지정합니다. 사전 읽기 단계는 모든 패킷(양방향)에 대해 트리거되고 프록싱 전에 발생하므로 업스트림
블록에서 필요할 때 $mqtt_client_id
값을 사용할 수 있습니다.
NGINX Plus 구성 파일( stream_mqtt_session_persistence.conf )의 js_import
지시문에 의해 로드되는 mqtt.js 파일에서 MQTT ClientId를 추출하기 위한 JavaScript를 정의합니다.
기본 함수인 getClientId()
는 4번째 줄에서 선언되었습니다. 현재 TCP 세션을 나타내는 s
라는 객체가 전달됩니다. 세션 객체에는 여러 개의 속성이 있으며, 이 중 몇 가지는 이 함수에서 사용됩니다.
5~9행은 현재 패킷이 클라이언트로부터 가장 먼저 수신되도록 보장합니다. 이후의 클라이언트 메시지와 서버 응답은 무시되므로 연결이 설정되면 트래픽 흐름에 추가적인 오버헤드가 발생하지 않습니다.
10~24번째 줄은 MQTT 헤더를 조사하여 패킷이 CONNECT
유형인지 확인하고 MQTT 페이로드가 시작되는 위치를 확인합니다.
27~32번째 줄은 페이로드에서 ClientId를 추출하여 해당 값을 JavaScript 전역 변수 client_id_str
에 저장합니다. 그런 다음 이 변수는 setClientId
함수(43~45행)를 통해 NGINX 구성으로 내보내집니다.
이제 Mosquitto 클라이언트를 다시 사용하여 세 개의 다른 ClientId 값( -i
옵션)과 함께 일련의 MQTT 게시 요청을 전송하여 세션 지속성을 테스트할 수 있습니다.
$ mosquitto_pub -h mqtt.example.com -t "주제/테스트" -m "test123" -i "foo" $ mosquitto_pub -h mqtt.example.com -t "주제/테스트" -m "test123" -i "bar" $ mosquitto_pub -h mqtt.example.com -t "주제/테스트" -m "test123" -i "baz" $ mosquitto_pub -h mqtt.example.com -t "주제/테스트" -m "test123" -i "foo" $ mosquitto_pub -h mqtt.example.com -t "주제/테스트" -m "test123" -i "foo" $ mosquitto_pub -h mqtt.example.com -t "주제/테스트" -m "test123" -i "foo" $ mosquitto_pub -h mqtt.example.com -t "주제/테스트" -m "test123" -i "foo" $ mosquitto_pub -h mqtt.example.com -t "주제/테스트" -m "test123" -i "바" $ mosquitto_pub -h mqtt.example.com -t "주제/테스트" -m "test123" -i "바" $ mosquitto_pub -h mqtt.example.com -t "주제/테스트" -m "test123" -i "바" $ mosquitto_pub -h mqtt.example.com -t "주제/테스트" -m "test123" -i "바즈" $ mosquitto_pub -h mqtt.example.com -t "주제/테스트" -m "test123" -i "바즈"
액세스 로그를 살펴보면 ClientId foo는 항상 node1 (포트 18831)에 연결되고, ClientId bar는 항상 node2 (포트 18832)에 연결되고, ClientId baz는 항상 node3 (포트 18833)에 연결되는 것을 알 수 있습니다.
$ tail /var/log/nginx/mqtt_access.log 192.168.91.1 [2017년 3월 23일:12:24:24 +0000] TCP 200 23 4 127.0.0.1:18831 foo 192.168.91.1 [2017년 3월 23일:12:24:28 +0000] TCP 200 23 4 127.0.0.1:18832 bar 192.168.91.1 [2017년 3월 23일:12:24:32 +0000] TCP 200 23 4 127.0.0.1:18833 baz 192.168.91.1 [2017-03-23:12:24:35 +0000] TCP 200 23 4 127.0.0.1:18831 foo 192.168.91.1 [2017-03-23:12:24:37 +0000] TCP 200 23 4 127.0.0.1:18831 foo 192.168.91.1 [2017-03-23:12:24:38 +0000] TCP 200 23 4 127.0.0.1:18831 foo 192.168.91.1 [2017-03-23:12:24:42 +0000] TCP 200 23 4 127.0.0.1:18832 바 192.168.91.1 [2017-03-23:12:24:44 +0000] TCP 200 23 4 127.0.0.1:18832 바 192.168.91.1 [2017-03-23:12:24:47 +0000] TCP 200 23 4 127.0.0.1:18833 baz 192.168.91.1 [2017-03-23:12:24:48 +0000] TCP 200 23 4 127.0.0.1:18833 baz
세션 지속성이나 다른 부하 분산 알고리즘을 사용하든 관계없이 MQTT ClientId가 액세스 로그 줄에 표시되는 이점도 있습니다.
2부로 구성된 시리즈의 첫 번째 게시물에서는 NGINX Plus가 활성 상태 검사를 사용하여 IoT 애플리케이션의 가용성과 안정성을 개선하는 방법, 그리고 NGINX JavaScript가 TCP 트래픽에 대한 세션 지속성과 같은 계층 7 부하 분산 기능을 제공하여 NGINX Plus를 확장하는 방법을 설명했습니다. 두 번째 부분 에서는 NGINX Plus가 TLS 종료 및 클라이언트 인증을 오프로드하여 IoT 애플리케이션의 보안을 어떻게 강화할 수 있는지 살펴보겠습니다.
NGINX JavaScript와 함께 사용하거나 단독으로 사용할 경우 NGINX Plus의 고유한 고성능 및 효율성으로 인해 IoT 인프라를 위한 소프트웨어 로드 밸런서로 이상적입니다.
NGINX Plus로 NGINX JavaScript를 사용해 보려면 무료 30일 체험판을 시작하거나 저희에게 연락하여 사용 사례에 대해 논의하세요 .
부록
테스트 환경은 격리되어 있고 반복이 가능하도록 가상 머신에 설치했습니다. 하지만 물리적인 "베어 메탈" 서버에 설치하지 못할 이유는 없습니다.
NGINX Plus 관리자 가이드 의 지침을 참조하세요.
모든 MQTT 서버를 사용할 수 있지만 이 테스트 환경은 HiveMQ 기반입니다( 여기서 다운로드 ). 이 예제에서는 각 노드에 Docker 컨테이너를 사용하여 단일 호스트에 HiveMQ를 설치합니다. 다음 지침은 Docker를 사용하여 HiveMQ 배포 에서 가져왔습니다.
hivemq.zip 과 같은 디렉토리에 HiveMQ용 Dockerfile을 만듭니다.
hivemq.zip 과 Dockerfile이 포함된 디렉토리에서 작업하여 Docker 이미지를 생성합니다.
$ docker build -t hivemq:latest .
각각 다른 포트에 노출되는 HiveMQ 노드를 세 개 만듭니다.
$ docker run -p 18831:1883 -d --이름 노드1 하이브엠큐:최신 ff2c012c595a $ docker run -p 18832:1883 -d --이름 노드2 하이브엠큐:최신 47992b1f4910 $ docker run -p 18833:1883 -d --이름 노드3 하이브엠큐:최신 17303b900b64
세 개의 HiveMQ 노드가 모두 실행 중인지 확인하세요. (다음 샘플 출력에서는 읽기 편하도록 COMMAND
, CREATED
, STATUS
열이 생략되었습니다.)
$ docker ps 컨테이너 ID 이미지 ... 포트 이름 17303b900b64 hivemq:latest ... 0.0.0.0:18833->1883/tcp node3 47992b1f4910 hivemq:최신 ... 0.0.0.0:18832->1883/tcp node2 ff2c012c595a hivemq:최신 ... 0.0.0.0:18831->1883/tcp 노드1
Mosquitto 명령줄 클라이언트는 프로젝트 웹사이트에서 다운로드 할 수 있습니다. Homebrew 가 설치된 Mac 사용자는 다음 명령을 실행할 수 있습니다.
$ brew 설치 mosquitto
Docker 이미지 중 하나에 간단한 게시 메시지를 보내 Mosquitto 클라이언트와 HiveMQ 설치를 테스트합니다.
$ mosquitto_pub -d -h mqtt.example.com -t "topic/test" -m "test123" -i "thing001" -p 18831 클라이언트 thing001이 CONNECT를 전송하고 클라이언트 thing001이 CONNACK을 수신함 클라이언트 thing001이 PUBLISH(d0, q0, r0, m1, 'topic/test', ...를 전송함 (7바이트)) 클라이언트 thing001이 DISCONNECT를 전송합니다.
[ngx_snippet 이름='njs-enable-instructions']
"이 블로그 게시물에는 더 이상 사용할 수 없거나 더 이상 지원되지 않는 제품이 참조될 수 있습니다. 사용 가능한 F5 NGINX 제품과 솔루션에 대한 최신 정보를 보려면 NGINX 제품군을 살펴보세요. NGINX는 이제 F5의 일부가 되었습니다. 이전의 모든 NGINX.com 링크는 F5.com의 유사한 NGINX 콘텐츠로 리디렉션됩니다."