Kawaii_Jordy

[Clustering] Guide in docker 본문

취준/RabbitMQ

[Clustering] Guide in docker

Kawaii_Jordy 2021. 6. 11. 17:59

Overview

rabbitmq cluster 한개 또는 여러개의 노드들의 논리적인 그룹이며, 각각의 노드들은 users, vhosts, queues, exchanges, bindings, runtime parameter, state(distributed 분산) 를 공유한다.

Cluster Formation 클러스터구성


Ways of Forming a Cluster 클러스터를 구성하는 방법들

아래와 같이 클러스터를 구성하는 방법들이 있고 자세한 내용은 Cluster Formation guide 참조.

  • Declaratively by listing cluster nodes in config file
  • Declaratively using DNS-based discovery
  • Declaratively using AWS (EC2) instance discovery (via a plugin)
  • Declaratively using Kubernetes discovery (via a plugin)
  • Declaratively using Consul-based discovery (via a plugin)
  • Declaratively using etcd-based discovery (via a plugin)
  • Manually with rabbitmqctl

How RabbitMQ nodes are identified: node names


rabbitmq 는 어떻게 노드들을 식별하는가? 

Node Names (Identifiers)

rabbitmq 노드들은 노드네임으로 식별한다. 노드네임@호스트네임 두파트로 구성된다. ex) rabbit@node1

클러스터 노드네임은 반드시 유니크해야한다. 하나의 호스트에서 한개이상의 노드를 운영할때는 노드네임을 다르게 한다. ex) rabbit1@node, rabbit2@node2

클러스터 안에서 노드의 인식, 통신에는 노드의이름이 사용된다. 모든노드네임의 호스트파트는 resolve(도메인명에 대한 IP 변환작업) 된다. 

CLI tools 또한 노드네임을 사용하여 식별하고 주소를 확인한다.

Requirements for clustering


Cluster Formation Requirements

클러스팅을 위한 요구사항들.

Hostname Resolution

호스트네임을 IP주소에 매핑하기 위한 설정이 필요하다.

  • DNS records
  • Local host files unix (/etc/hosts)

Port Access

rabbitmq 노드들은 클라이언와 연결하기 위해서 port 를 사용(bind)한다.

  • 4369: Erlang 노드 포트 매핑 데몬 포트
  • 25672: rabbitmq 노드들의 통신을 위해 사용되는 Erlang 노드 포트
  • 5671, 5672: AMQP 클라이언트 포트
  • 15672: rabbitmq_management 플러그인 포트
  • 35672-35682: used by CLI tools (Erlang distribution client ports) for communication with nodes and is allocated from a dynamic range (computed as server distribution port + 10000 through server distribution port + 10010). See networking guide for details.
  • 61613, 61614: STOMP clients without and with TLS (only if the STOMP plugin is enabled)
  • 1883, 8883: (MQTT clients without and with TLS, if the MQTT plugin is enabled
  • 15674: STOMP-over-WebSockets clients (only if the Web STOMP plugin is enabled)
  • 15675: MQTT-over-WebSockets clients (only if the Web MQTT plugin is enabled)
  • 15692: Prometheus metrics (only if the Prometheus plugin is enabled)

What data is and isn't replicated between cluster nodes


Nodes in a Cluster

What is Replicated?

RabbitMQ Broker의 동작에 필요한 모든 data/state는 클러스터의 모든 노드에 복제된다. 단 메시지큐는 제외된다.

기본으로 메시지큐는 하나의 노드에 존재하지만 클러스터 내의 다른 노드를 통해서도 사용이 가능하다. (visible & reachable)

클러스터 내의 노드간 메시지큐를 복제하려면  high availability 문서를 보자.

Nodes are Equal Peers

일부 분산시스템은 리더(doner) 를 갖지만, rabbitmq 는 해당되지 않는다. rabbitmq 클러스서 내의 모든 노드는 동일한 피어입니다.

How CLI Tools Authenticate to Nodes (and Nodes to Each Other): the Erlang Cookie

rabbitmq 노드들은 Erlang Cooke 를 사용하여 인증하고 통신을 한다.

일반적으로 Unix 환경에서 cookie는 /var/lib/rabbitmq/.erlang.cookie 혹은 $HOME/.erlang.cookie에 위치한다.

Windows에서는 C:\Users\Current User\.erlang.cookie, C:\Documents and Settings\Current User\.erlang.cookie 혹은 C:\Windows\.erlang.cookie에 존재한다.

rabbitmq-server, rabbitmqctl 스크립트에서 “-setcookie cookie” 옵션을 추가하여 cookie값을 설정할 수 있다.

Test to communicate: the Erlang Cookie

clouddev001 서버에서 Erlang 노드를 실행하고 clouddev002 서버에서 clouddev001 서버로 노드정보를 요청해본다.

Erlang 노드 실행시 Cookie는 자동생성된다. 노드간 Cookie값이 다르면 통신이 되지 않는다.

하나의 노드에 여러개의 Erlang 노드가 실행되어 있다면 net_adm:names(). 요청시 "{ok, [{"foo", 42226}, {"bar", 42236}]}" 과 같이 응답을 하는데 

이는 epmd(Erlang Port Mapper Daemon, port 4369) 에서 관리되는 매핑정보이다. empd 데몬을 종료하는 커맨드 empd -kill

# clouddev001 서버
]# yum install erlang-erl_interface
]# erl -sname foo
Erlang R16B03-1 (erts-5.10.4) [source] [64-bit] [smp:6:6] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.4  (abort with ^G)
(foo@clouddev001)1> net_adm:names().
{ok,[{"foo",42226}]}
(foo@clouddev001)2> erlang:get_cookie().
'JSELFZGRHCULMXGBDKNI'
(foo@clouddev001)3> node().
foo@clouddev001
ctrl+z
]# netstat -nlp | grep 42226
tcp        0      0 0.0.0.0:42226           0.0.0.0:*               LISTEN      1185/beam.smp

# clouddev002 서버
]# yum install erlang-erl_interface
]# /usr/lib64/erlang/lib/erl_interface-3.7.15/bin/erl_call -c 'JSELFZGRHCULMXGBDKNI' -a 'erlang node' -n foo@clouddev001                         
foo@clouddev001
]# /usr/lib64/erlang/lib/erl_interface-3.7.15/bin/erl_call -c 'JSELFZGRHCULMXGBDKNI' -a 'net_adm names' -n foo@clouddev001            
{ok, [{"foo", 42226}]}

Node Counts and Quorum

What clustering means for clients


Clustering and Clients
Clustering and Observability
Node Failure Handling

RabbitMQ broker는 각각의 노드의 실패를 용인한다. 각 노드의 스타트업과 중단이 각 노드의 사정에 따라 언제든 수행할 수 있다. 단, 중단의 순간에 클러스터를 구성하는 노드 중 하나만에라도 그 사실을 알릴 수는 있어야 한다.

Metrics and Statistics
Disk and RAM Nodes 

노드는 disk 모드나 RAM 모드이다. 거의 대부분은 disk 모드이길 원할 것이다. RAM 모드는 high queue, exchange, or binding이 마구 휘젓는(churn) 클러스터의 성능을 향상시키기 위한 정도에 사용될 만한 특별한 모드이다. 잘 모르겠을 때는, disk 노드만 사용하면 된다.

 

Clustering Transcript with rabbitmqctl

clouddev001, clouddev002, clouddev003 서버 3대로 rabbitmqctl 을 커맨드를 사용하여 메뉴얼 구성을 한다.

Install and start

공식문서상에는 rabbitmq-server -detached 로 실행을 하는데 -detached 을 사용하게 되면 pid파일을 생성하지 않고 백그라운드로 실행이되기때문에 systemd 로 관리가 불가능해진다. 가능한 사용하지 말것

# clouddev001, clouddev002, clouddev003 서버공통
]# vi /etc/hosts
+ 10.0.0.2 clouddev001
+ 10.0.0.3 clouddev002
+ 10.0.0.4 clouddev003
 
]# yum install rabbitmq-server
]# rabbitmq-plugins enable rabbitmq_management
]# systemctl start rabbitmq-server
]# netstat -nlp | grep beam.smp
tcp        0      0 0.0.0.0:15672           0.0.0.0:*               LISTEN      2402/beam.smp      
tcp        0      0 0.0.0.0:25672           0.0.0.0:*               LISTEN      2402/beam.smp      
tcp6       0      0 :::5672                 :::*                    LISTEN      2402/beam.smp
 
]# rabbitmqctl cluster_status
Cluster status of node rabbit@clouddev001 ...
[{nodes,[{disc,[rabbit@clouddev001]}]},
 {running_nodes,[rabbit@clouddev001]},
 {cluster_name,<<"rabbit@clouddev001">>},
 {partitions,[]}]
...done.

Creating a Cluster

# clouddev001 서버
]# scp /var/lib/rabbitmq/.erlang.cookie root@clouddev002:/var/lib/rabbitmq/.erlang.cookie
]# scp /var/lib/rabbitmq/.erlang.cookie root@clouddev003:/var/lib/rabbitmq/.erlang.cookie
 
# clouddev001, clouddev002, clouddev003 서버
]# systemctl restart rabbitmq-server
 
# clouddev002 서버
]# rabbitmqctl stop_app
Stopping node rabbit@clouddev002 ...
...done.
]# rabbitmqctl reset
Resetting node rabbit@clouddev002 ...
...done.
]# rabbitmqctl join_cluster rabbit@clouddev001
Clustering node rabbit@clouddev002 with rabbit@clouddev001 ...
...done.
]# rabbitmqctl start_app
Starting node rabbit@clouddev002 ...
...done.
]# rabbitmqctl cluster_status
Cluster status of node rabbit@clouddev002 ...
[{nodes,[{disc,[rabbit@clouddev001,rabbit@clouddev002]}]},
 {running_nodes,[rabbit@clouddev001,rabbit@clouddev002]},
 {cluster_name,<<"rabbit@clouddev001">>},
 {partitions,[]}]
...done.
 
 
# clouddev003 서버
]# rabbitmqctl stop_app
Stopping node rabbit@clouddev003 ...
...done.
 
]# rabbitmqctl reset
Resetting node rabbit@clouddev003 ...
...done.
 
]# rabbitmqctl join_cluster rabbit@clouddev001
Clustering node rabbit@clouddev003 with rabbit@clouddev001 ...
...done.
 
]# rabbitmqctl start_app
Starting node rabbit@clouddev003 ...
...done.
 
]# rabbitmqctl cluster_status
Cluster status of node rabbit@clouddev003 ...
[{nodes,[{disc,[rabbit@clouddev001,rabbit@clouddev002,rabbit@clouddev003]}]},
 {running_nodes,[rabbit@clouddev001,rabbit@clouddev002,rabbit@clouddev003]},
 {cluster_name,<<"rabbit@clouddev001">>},
 {partitions,[]}]
...done.

Restart Cluster Nodes

클러스터에 속한 노드들은 언제든 중단되도 괜찮다. 심지어 장비가 갑자기 멈춰도 괜찮다. 두 경우 모두, 한 노드가 멈춰져도 나머지 노드들은 영향받지 않고 계속해서 운영된다. 그리고 다시 클러스터에 조인하는 노드들은 나머지 노드들을 자동으로 "catch up" 한다.

# clouddev003 노드를 재시작해본다.
 
]# rabbitmqctl cluster_status
Cluster status of node rabbit@clouddev003 ...
[{nodes,[{disc,[rabbit@clouddev001,rabbit@clouddev002,rabbit@clouddev003]}]},
 {running_nodes,[rabbit@clouddev001,rabbit@clouddev002,rabbit@clouddev003]},
 {cluster_name,<<"rabbit@clouddev001">>},
 {partitions,[]}]
...done.
 
]# rabbitmqctl stop_app
Stopping node rabbit@clouddev003 ...
...done.
 
]# rabbitmqctl cluster_status
Cluster status of node rabbit@clouddev003 ...
[{nodes,[{disc,[rabbit@clouddev001,rabbit@clouddev002,rabbit@clouddev003]}]}]
...done.
 
]# systemctl restart rabbitmq-server
]# rabbitmqctl cluster_status      
Cluster status of node rabbit@clouddev003 ...
[{nodes,[{disc,[rabbit@clouddev001,rabbit@clouddev002,rabbit@clouddev003]}]},
 {running_nodes,[rabbit@clouddev001,rabbit@clouddev002,rabbit@clouddev003]},
 {cluster_name,<<"rabbit@clouddev001">>},
 {partitions,[]}]
...done.

클러스터링의 몇가지 중요한 사항이 있다.

- 클러스터의 모든 노드가 내려갈 때, 마지막으로 내려가는 노드는 다시 온라인으로 돌아올 때에 가장 먼저 들어와야 한다. 그렇게 하지 않으면,다른 노드들이 30초 동안은 마지막으로 나간 노드가 온라인으로 돌아오길 기다리지만, 결국엔 fail 한다. 마지막으로 내려간 노드가 다시 돌아올 수 없다면, forget_cluster_node 명령어를 이용해서 클러스터에서 제거할 수 있다. rabbitmqctl manpage를 참고해서 더 알아보면 좋다.

- 전원이 나가는 경우처럼 갑자기 동시에 컨트롤 할 수 없게, 모든 클러스터 노드들이 중지된다면, 모든 노드들은 이렇게 생각한다: "나보다 뒤에 내려간 노드가 있을거야". 이런 경우에는 force_boot 명령어를 어떤 노드에서 실행해서 그 노드가 bootable 하도록 만들어 줄 수 있다. 이 또한 rabbitmqctl manpage를 참고하도록.

Breaking Up a Cluster

클러스터에서 노드를 빼려면 제대로 제거해야 한다. rabbit@clouddev003을 클러스터로 부터 제거해서 독립적인 operation 할 수 있도록 해보자. 그러기 위해선 rabbit@clouddev003을 중지시키고, 노드를 리셋하고, RabbitMQ 어플리케이션을 재시작한다. clouddev001, clouddev002 에서 더이상 clouddev003 클러스터 노드를 제거한다. 

# clouddev003 서버
]# rabbitmqctl cluster_status
Cluster status of node rabbit@clouddev003 ...
[{nodes,[{disc,[rabbit@clouddev001,rabbit@clouddev002,rabbit@clouddev003]}]},
 {running_nodes,[rabbit@clouddev001,rabbit@clouddev002,rabbit@clouddev003]},
 {cluster_name,<<"rabbit@clouddev001">>},
 {partitions,[]}]
...done.
 
]# rabbitmqctl stop_app
Stopping node rabbit@clouddev003 ...
...done.
 
 
# clouddev002 서버
]# rabbitmqctl cluster_status
Cluster status of node rabbit@clouddev002 ...
[{nodes,[{disc,[rabbit@clouddev001,rabbit@clouddev002]}]},
 {running_nodes,[rabbit@clouddev001,rabbit@clouddev002]},
 {cluster_name,<<"rabbit@clouddev001">>},
 {partitions,[]}]
...done.
 
 
]# rabbitmqctl forget_cluster_node rabbit@clouddev003
Removing node rabbit@clouddev003 from cluster ...
...done.
 
]# rabbitmqctl cluster_status                       
Cluster status of node rabbit@clouddev002 ...
[{nodes,[{disc,[rabbit@clouddev001,rabbit@clouddev002]}]},
 {running_nodes,[rabbit@clouddev001,rabbit@clouddev002]},
 {cluster_name,<<"rabbit@clouddev001">>},
 {partitions,[]}]
...done.
 
# clouddev003 서버
# 리셋을하지 않고 start_app 하면 clouddev003 노드는 아직 clouddev001. clouddev002 노드와 클러스터 구성이 되어 있는지 알기때문에 에러가 발생한다.
]# rabbitmqctl reset
Resetting node rabbit@clouddev003 ...
...done.
 
]# rabbitmqctl start_app
Starting node rabbit@clouddev003 ...
...done.
 
]# rabbitmqctl cluster_status
Cluster status of node rabbit@clouddev003 ...
[{nodes,[{disc,[rabbit@clouddev003]}]},
 {running_nodes,[rabbit@clouddev003]},
 {cluster_name,<<"rabbit@clouddev003">>},
 {partitions,[]}]
...done.

How to reset a cluster node

# clouddev003 서버
]# rabbitmqctl stop_app
Stopping node rabbit@clouddev003 ...
...done.
 
]# rabbitmqctl reset
Resetting node rabbit@clouddev003 ...
...done.

가끔 모든 데이터를 지우고 클러스터에 리조인할때 필요하다.

Upgrading clusters

A Cluster on a Single Machine
Hostname Changes
Firewalled nodes
Erlang Versions Across the Cluster

Connecting to Clusters form Clints

로드밸런서를 사용하여 가용성을 확보하자. haproxy 추천

Cluster with RAM nodes

Creating RAM nodes

클러스터에 노드를 조인시킬 때 그 노드를 RAM 노드로 선언할 수 있다. 

전에 클러스터링 한 것 처럼 rabbitmqctl join_cluster 를 쓰지만 --ram 플래그도 같이 넘겨주면 된다.

클러스터 status 를 확인하면 RAM 노드인 것을 확인할 수 있다.

Change node types

노드1과 노드2의 노드 타입을 바꿔보자. change_cluster_node_type 명령어를 사용하고, 노드를 먼저 중지시켜야 한다.

Queue Mirroring for H/A

클러스터는 메시지큐를 복제하지 않으므로 HA 구성을 위해서 메시지큐 미러링이 필요하다.

]# rabbitmqctl set_policy ha-all '^(?!amq\.).*' '{"ha-mode": "all", "ha-sync-mode":"automatic"}'

https://pygirl.tistory.com/33

'취준 > RabbitMQ' 카테고리의 다른 글

AMQP RPC 소비 과정  (0) 2021.06.16
AMQP 기본 사용 방법  (0) 2021.06.16
[amqplib] detail options  (0) 2021.06.14
Comments