ติดตั้ง RabbitMQ บน VPS ระบบ Message Broker

ติดตั้ง RabbitMQ บน VPS ระบบ Message Broker

มิถุนายน 2026 · อ่าน 9 นาที

RabbitMQ คือระบบ message broker ที่นิยมใช้มากที่สุด — ส่ง message ระหว่าง service แบบ async ทำให้ application ทำงานได้เร็วขึ้นและ scalable กว่า synchronous communication รองรับ protocol หลากหลาย (AMQP, MQTT, STOMP, HTTP) และมี management UI ในตัวสำหรับ monitor queue, connection, channel ได้ real-time บทความนี้สอนติดตั้ง RabbitMQ บน VPS ด้วย Docker, ตั้ง Virtual Host และ User สำหรับ application แต่ละตัว, เข้าถึง Management Console ผ่าน Nginx, และตั้ง clustering เบื้องต้นเพื่อ high availability

RabbitMQ vs อื่นๆ: ทำไมถึงต้องใช้

ติดตั้ง RabbitMQ ด้วย Docker Compose

mkdir -p /opt/rabbitmq/{data,config} && cd /opt/rabbitmq

# สร้าง RabbitMQ config file
cat > config/rabbitmq.conf << 'EOF'
# Default user and password (เปลี่ยน ASAP!)
default_user = guest
default_pass = guest

# Management plugin
management.load_definitions = /etc/rabbitmq/definitions.json

# Advanced settings
channel_max = 0             # unlimited channels
connection_max = 0          # unlimited connections
heartbeat = 60              # heartbeat timeout
frame_max = 131072          # 128 KB

# Disk and memory thresholds
vm_memory_high_watermark.relative = 0.6
disk_free_limit.absolute = 5GB
EOF

# สร้าง definitions.json เพื่อ auto-create vhost, users, permissions (optional)
cat > config/definitions.json << 'EOF'
{
  "vhosts": [
    {"name": "myapp"}
  ],
  "users": [
    {"name": "myapp_user", "password": "CHANGE_ME_STRONG_PASSWORD", "tags": ""}
  ],
  "permissions": [
    {
      "user": "myapp_user",
      "vhost": "myapp",
      "configure": ".*",
      "write": ".*",
      "read": ".*"
    }
  ]
}
EOF

# สร้าง docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: "3.9"

services:
  rabbitmq:
    image: rabbitmq:3.13-management-alpine
    restart: unless-stopped
    ports:
      - "127.0.0.1:5672:5672"     # AMQP port
      - "127.0.0.1:15672:15672"   # Management UI
    environment:
      RABBITMQ_DEFAULT_USER: guest
      RABBITMQ_DEFAULT_PASS: guest
    volumes:
      - ./config/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf
      - ./config/definitions.json:/etc/rabbitmq/definitions.json
      - ./data:/var/lib/rabbitmq
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
      interval: 30s
      timeout: 10s
      retries: 5

volumes:
  rabbitmq_data:
EOF

docker-compose up -d

ตั้ง Virtual Host และ User สำหรับ Application

# ตั้ง vhost ชื่อ "myapp" แยกจาก "/" (default vhost)
docker-compose exec rabbitmq rabbitmqctl add_vhost myapp

# สร้าง user สำหรับ application
docker-compose exec rabbitmq rabbitmqctl add_user myapp_user MySecurePassword123

# กำหนด permissions ให้ user สามารถ configure, write, read queues ใน vhost
docker-compose exec rabbitmq rabbitmqctl set_permissions -p myapp myapp_user ".*" ".*" ".*"

# ตรวจสอบว่า vhost และ user สร้างสำเร็จ
docker-compose exec rabbitmq rabbitmqctl list_vhosts
docker-compose exec rabbitmq rabbitmqctl list_users

ตั้ง Nginx Reverse Proxy สำหรับ Management Console

nano /etc/nginx/sites-available/rabbitmq

server {
    listen 80;
    server_name rabbitmq.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name rabbitmq.yourdomain.com;

    ssl_certificate     /etc/letsencrypt/live/rabbitmq.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/rabbitmq.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:15672;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

ln -s /etc/nginx/sites-available/rabbitmq /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
certbot --nginx -d rabbitmq.yourdomain.com

ส่ง Message จาก Python Application

# ติดตั้ง pika library
pip install pika

# ตัวอย่าง producer (send message)
cat > producer.py << 'EOF'
import pika

# เชื่อมต่อ RabbitMQ
credentials = pika.PlainCredentials('myapp_user', 'MySecurePassword123')
parameters = pika.ConnectionParameters(
    host='127.0.0.1',
    port=5672,
    virtual_host='myapp',
    credentials=credentials,
    heartbeat=60
)
connection = pika.BlockingConnection(parameters)
channel = connection.channel()

# สร้าง queue และ exchange
channel.queue_declare(queue='emails', durable=True)
channel.exchange_declare(exchange='tasks', exchange_type='topic', durable=True)
channel.queue_bind(queue='emails', exchange='tasks', routing_key='email.*')

# ส่ง message
message = '{"to": "[email protected]", "subject": "Hello"}'
channel.basic_publish(
    exchange='tasks',
    routing_key='email.send',
    body=message,
    properties=pika.BasicProperties(delivery_mode=2)  # persistent
)

print("Message sent!")
connection.close()
EOF

python3 producer.py

# ตัวอย่าง consumer (receive message)
cat > consumer.py << 'EOF'
import pika
import json

credentials = pika.PlainCredentials('myapp_user', 'MySecurePassword123')
parameters = pika.ConnectionParameters(
    host='127.0.0.1',
    port=5672,
    virtual_host='myapp',
    credentials=credentials
)
connection = pika.BlockingConnection(parameters)
channel = connection.channel()

channel.queue_declare(queue='emails', durable=True)

def callback(ch, method, properties, body):
    try:
        msg = json.loads(body)
        print(f"Received: {msg}")
        # ส่ง email ที่นี่
        ch.basic_ack(delivery_tag=method.delivery_tag)
    except Exception as e:
        print(f"Error: {e}")
        ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)

channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue='emails', on_message_callback=callback)

print("Waiting for messages...")
channel.start_consuming()
EOF

# รันในแยก terminal หรือใน background
python3 consumer.py &

ตั้ง Dead Letter Queue (DLQ) สำหรับ Message ที่ Fail

# สร้าง exchange และ queue สำหรับ dead letter
docker-compose exec rabbitmq rabbitmqctl eval \
'amqp_client:start(),
Channel = amqp_connection:open_channel(Connection),
{ok, Connection} = amqp_connection:start(network, [
  {username, <<"myapp_user">>},
  {password, <<"MySecurePassword123">>},
  {virtual_host, <<"myapp">>},
  {host, "localhost"}
]),
amqp_channel:call(Channel, #amqp_queue.declare{queue = <<"emails_dlq">>, durable = true}),
amqp_channel:close(Channel),
amqp_connection:close(Connection).'

# หรือใช้ management API
curl -X PUT http://localhost:15672/api/queues/myapp/emails_dlq \
  -u guest:guest \
  -H "content-type:application/json" \
  -d '{"durable":true,"arguments":{}}'

# กำหนด original queue ให้ส่ง failed message ไปยัง DLQ
curl -X PUT http://localhost:15672/api/queues/myapp/emails \
  -u guest:guest \
  -H "content-type:application/json" \
  -d '{
    "durable":true,
    "arguments":{
      "x-dead-letter-exchange":"",
      "x-dead-letter-routing-key":"emails_dlq",
      "x-message-ttl":86400000
    }
  }'

ติดตั้ง Clustering สำหรับ High Availability (RA)

# Cluster ต้องมี minimal 2 nodes เพื่อ failover
# ตั้ง node 2 ใน docker-compose.yml

cat >> docker-compose.yml << 'EOF'

  rabbitmq2:
    image: rabbitmq:3.13-management-alpine
    restart: unless-stopped
    environment:
      RABBITMQ_DEFAULT_USER: guest
      RABBITMQ_DEFAULT_PASS: guest
      RABBITMQ_ERLANG_COOKIE: rabbitmq-cluster-cookie
    ports:
      - "127.0.0.1:5673:5672"
      - "127.0.0.1:15673:15672"
    volumes:
      - ./config/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf
      - ./rabbitmq2_data:/var/lib/rabbitmq
    depends_on:
      - rabbitmq

  rabbitmq3:
    image: rabbitmq:3.13-management-alpine
    restart: unless-stopped
    environment:
      RABBITMQ_DEFAULT_USER: guest
      RABBITMQ_DEFAULT_PASS: guest
      RABBITMQ_ERLANG_COOKIE: rabbitmq-cluster-cookie
    ports:
      - "127.0.0.1:5674:5672"
      - "127.0.0.1:15674:15672"
    volumes:
      - ./config/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf
      - ./rabbitmq3_data:/var/lib/rabbitmq
    depends_on:
      - rabbitmq

volumes:
  rabbitmq2_data:
  rabbitmq3_data:
EOF

docker-compose up -d

# เชื่อม node 2 และ 3 เข้า cluster
docker-compose exec rabbitmq2 rabbitmqctl stop_app
docker-compose exec rabbitmq2 rabbitmqctl reset
docker-compose exec rabbitmq2 rabbitmqctl join_cluster rabbit@rabbitmq
docker-compose exec rabbitmq2 rabbitmqctl start_app

docker-compose exec rabbitmq3 rabbitmqctl stop_app
docker-compose exec rabbitmq3 rabbitmqctl reset
docker-compose exec rabbitmq3 rabbitmqctl join_cluster rabbit@rabbitmq
docker-compose exec rabbitmq3 rabbitmqctl start_app

# ตรวจสอบ cluster status
docker-compose exec rabbitmq rabbitmqctl cluster_status

# ตั้ง queue mirroring — replicate queue ไปยัง nodes อื่น
docker-compose exec rabbitmq rabbitmqctl set_policy ha-all \
  "^emails$" '{"ha-mode":"all","ha-sync-batch-size":5}'
Tip: ถ้า RabbitMQ memory เต็มจะเปลี่ยนสถานะเป็น "memory alarm" ให้เพิ่ม vm_memory_high_watermark.relative ใน config หรือเอา old message ออก ด้วย rabbitmqctl clear_queue QUEUE_NAME

ตรวจสอบว่า RabbitMQ ทำงานถูกต้อง

# ตรวจ container ทั้งหมด
docker-compose ps

# ตรวจ RabbitMQ status
docker-compose exec rabbitmq rabbitmqctl status | head -30

# ดู queue ทั้งหมด
docker-compose exec rabbitmq rabbitmqctl list_queues name messages consumers

# ดู connection ที่เชื่อมต่ออยู่
docker-compose exec rabbitmq rabbitmqctl list_connections name user vhost

# ดู channel ที่เปิดอยู่
docker-compose exec rabbitmq rabbitmqctl list_channels

# ทดสอบ connection เข้า RabbitMQ AMQP port
telnet localhost 5672
# ต้องได้ AMQP handshake string ขึ้นมา

# ทดสอบ Management Console HTTP
curl -u guest:guest http://localhost:15672/api/overview | python3 -m json.tool | head -20
Tip: ถ้า consumer ยังไม่เชื่อมต่อ queue message จะค้างอยู่ตลอด เช็ก consumers column ว่าเป็น 0 ไหม ถ้า 0 แสดงว่าไม่มี consumer เชื่อมต่อ — ตรวจ consumer code หรือ network connectivity

แก้ปัญหาที่พบบ่อย

Error: "403 ACCESS-REFUSED - operation not permitted on /"

User ไม่มี permission เข้า vhost "/" (default vhost) — ต้องให้ permission ให้ชัดเจน

# ให้ user myapp_user access ไป vhost "myapp"
docker-compose exec rabbitmq rabbitmqctl set_permissions -p myapp myapp_user ".*" ".*" ".*"

# ถ้ามี legacy code ที่ยังชี้ "/" ให้ permission ชั่วคราว แต่อย่ามากทะอพสำหรับ production
docker-compose exec rabbitmq rabbitmqctl set_permissions -p "/" myapp_user "" "" "read"

Error: "PRECONDITION_FAILED - inequivalent arg 'x-expires' for queue"

Queue arguments conflicting — queue ถูกสร้างด้วย TTL หรือ DLX แต่ code พยายามสร้างใหม่ด้วย argument ต่างกัน

# ลบ queue เก่า
docker-compose exec rabbitmq rabbitmqctl delete_queue myqueue -p myapp

# สร้างใหม่หรือให้ code idempotent (passive declare เท่านั้น)
# ใน Python pika:
channel.queue_declare(queue='myqueue', durable=True, passive=False)

Consumer ไม่ receive message แม้ message ส่งมาแล้ว

Queue binding ไม่ตรงกับ routing key — message ส่งไปแต่ไม่มี queue subscribe

# ตรวจ binding ระหว่าง queue กับ exchange
docker-compose exec rabbitmq rabbitmqctl list_bindings exchange_name queue_name

# สร้าง binding ใหม่ถ้าไม่มี
docker-compose exec rabbitmq rabbitmqctl bind_queue myqueue myexchange "myapp.*"

# ตรวจ dead letter queue ว่ามี message ไหม (ถ้า message reject)
docker-compose exec rabbitmq rabbitmqctl list_queues name messages | grep dlq

RabbitMQ crashed/restart ซ้ำๆ

Memory หรือ Erlang cookie conflicting — ถ้า cluster node cookie ไม่ตรงกันจะ error

# ตรวจ log
docker-compose logs rabbitmq --tail=50

# ตรวจว่า Erlang cookie ตรงกันทั้ง 3 node
docker-compose exec rabbitmq cat /var/lib/rabbitmq/.erlang.cookie
docker-compose exec rabbitmq2 cat /var/lib/rabbitmq/.erlang.cookie

# ถ้าไม่ตรง ให้ลบ data volume และสร้างใหม่ (ระวัง! จะสูญเสีย data)
docker-compose down -v
docker-compose up -d

คำถามที่พบบ่อย

RabbitMQ vs Redis for messaging ต่างกันอย่างไร?

Redis ใช้ memory เก็บ message ถูกวิปมากแต่ restart ข้อมูลหาย RabbitMQ เก็บ disk ช้าสักนิด แต่ persistent และ reli­able กว่า สำหรับ short-term cache ใช้ Redis แต่สำหรับ mission-critical queue ต้อง RabbitMQ ทั้งสองใช้ได้พร้อมกันใน architecture เดียวกัน (Redis สำหรับ session/cache, RabbitMQ สำหรับ async task)

Message TTL (Time To Live) ทำอย่างไร?

กำหนด x-message-ttl ใน queue arguments เป็น milliseconds RabbitMQ จะ expire message หลังจากเวลาผ่านไป ตัวอย่าง: x-message-ttl: 3600000 = 1 ชั่วโมง ถ้า message ไม่ถูก consume ภายในเวลานั้นจะถูกลบอัตโนมัติ ทำให้ queue ไม่พองตัวจากเก่า message

Producer/Consumer ต้องเชื่อมต่อ RabbitMQ ตรง IP:port ได้ไหม?

ได้แต่ไม่แนะนำสำหรับ production — ใช้ HAProxy หรือ load balancer หน้า RabbitMQ cluster เพื่อให้ client ไม่ต้องรู้ node ไหน failover ได้ auto HAProxy backend สำหรับ AMQP ต้องใช้ tcp mode ไม่ใช่ http mode เพราะ AMQP binary protocol