ติดตั้ง Celery + Redis บน VPS ระบบ Task Queue

ติดตั้ง Celery + Redis บน VPS ระบบ Task Queue

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

Celery เป็น distributed task queue framework ที่ช่วยให้ Python app สามารถรัน long-running task ในพื้นหลังแทนที่จะบล็อก HTTP request — ใช้ Redis เป็น message broker เพื่อส่งจดหมาย task ระหว่าง web server และ worker process ทำให้ website ตอบสนองเร็ว และเพิ่ม scalability ได้ง่ายโดยเพิ่ม worker instance ใหม่บน VPS เดียวหรือหลาย VPS บทความนี้สอนติดตั้ง Celery + Redis พร้อมกับ Flower dashboard สำหรับ monitor task status แบบ real-time

สถาปัตยกรรม Celery + Redis

ติดตั้ง Redis

sudo apt-get update && sudo apt-get install -y redis-server

# ตรวจสอบ Redis service ทำงาน
sudo systemctl status redis-server

# ทดสอบ Redis connection
redis-cli ping
# ต้องได้ PONG
Tip: ในการใช้งานจริง ต้องตั้ง Redis password ด้วย — แก้ไข /etc/redis/redis.conf เพิ่ม requirepass YOUR_STRONG_PASSWORD และเปิด port เฉพาะ 127.0.0.1 ด้วย bind 127.0.0.1

ติดตั้ง Celery และ Dependencies

# สร้าง virtual environment สำหรับ project
mkdir -p /opt/myapp && cd /opt/myapp
python3 -m venv venv
source venv/bin/activate

# ติดตั้ง Celery + Redis client + Flower
pip install celery redis flower

# สร้าง requirements.txt เพื่อ reproducible
pip freeze > requirements.txt

สร้าง Celery App และ Task

# สร้างโครงสร้าง project
cat > celery_app.py << 'EOF'
from celery import Celery
import redis
import time

# สร้าง Celery instance ชี้ไปยัง Redis
app = Celery(
    'myapp',
    broker='redis://127.0.0.1:6379/0',
    backend='redis://127.0.0.1:6379/1'
)

# ตั้งค่า Celery
app.conf.update(
    task_serializer='json',
    accept_content=['json'],
    result_serializer='json',
    timezone='Asia/Bangkok',
    enable_utc=False,
    worker_prefetch_multiplier=1,  # Worker ขอ task ทีละหนึ่ง
    worker_max_tasks_per_child=1000,  # Restart worker ทุก 1000 task
)

# ประกาศ task
@app.task(name='add_numbers')
def add_numbers(x, y):
    """Task ธรรมดา — บวก 2 ตัวเลข"""
    return x + y

@app.task(name='send_email')
def send_email(recipient, subject, body):
    """Task ส่งอีเมล (จำลอง 5 วินาที)"""
    time.sleep(5)  # จำลองการส่งจริง
    print(f"Email sent to {recipient}: {subject}")
    return f"Email sent to {recipient}"

@app.task(name='generate_report')
def generate_report(report_type):
    """Task สร้าง report (จำลอง 10 วินาที)"""
    time.sleep(10)
    return f"Report {report_type} generated"

# Periodic task — เดิน cron ทุก 1 นาที
from celery.schedules import crontab

app.conf.beat_schedule = {
    'cleanup-cache': {
        'task': 'cleanup_task',
        'schedule': 60.0,  # รัน every 60 seconds
    },
    'midnight-sync': {
        'task': 'sync_data',
        'schedule': crontab(hour=0, minute=0),  # รัน ที่เที่ยงคืน
    },
}

@app.task(name='cleanup_task')
def cleanup_task():
    print("Running cleanup task...")
    return "Cleanup done"

@app.task(name='sync_data')
def sync_data():
    print("Syncing data at midnight...")
    return "Sync done"

if __name__ == '__main__':
    app.start()
EOF

รัน Celery Worker

# รัน worker ด้วย 4 concurrency processes
celery -A celery_app worker --loglevel=info --concurrency=4

# หรือรันใน background ด้วย systemd
sudo tee /etc/systemd/system/celery.service << 'EOF'
[Unit]
Description=Celery Worker
After=network.target redis-server.service

[Service]
Type=forking
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp
Environment="PATH=/opt/myapp/venv/bin"
ExecStart=/opt/myapp/venv/bin/celery -A celery_app worker --loglevel=info --concurrency=4 --logfile=/var/log/celery.log --pidfile=/var/run/celery.pid

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl start celery
sudo systemctl enable celery
Tip: สำหรับ production ใช้ supervisor หรือ systemd socket activation เพื่อให้ worker restart อัตโนมัติถ้า crash และ ควบคุม resource ได้ดีขึ้น

ตั้ง Flower Monitoring Dashboard

# รัน Flower บน port 5555
flower -A celery_app --port=5555 --logfile=/var/log/flower.log

# หรือสร้าง systemd service สำหรับ Flower
sudo tee /etc/systemd/system/flower.service << 'EOF'
[Unit]
Description=Flower Celery Monitoring
After=network.target

[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp
Environment="PATH=/opt/myapp/venv/bin"
ExecStart=/opt/myapp/venv/bin/flower -A celery_app --port=5555 --logfile=/var/log/flower.log

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl start flower
sudo systemctl enable flower

# เปิด Flower ได้ที่ http://localhost:5555 หรือหลังเว็บ Nginx
curl http://localhost:5555

เรียก Task จาก Flask / Django App

# Flask example
cat > app.py << 'EOF'
from flask import Flask, jsonify
from celery_app import add_numbers, send_email, generate_report

app = Flask(__name__)

@app.route('/add//')
def route_add(x, y):
    # Queue task แบบ async
    result = add_numbers.delay(x, y)
    return jsonify({
        'task_id': result.id,
        'status': 'Task queued',
        'result_url': f'/result/{result.id}'
    })

@app.route('/send_email/')
def route_email(recipient):
    result = send_email.delay(
        recipient=recipient,
        subject='Hello',
        body='This is async email'
    )
    return jsonify({'task_id': result.id})

@app.route('/result/')
def get_result(task_id):
    from celery_app import app as celery_app
    result = celery_app.AsyncResult(task_id)

    return jsonify({
        'task_id': task_id,
        'status': result.status,
        'result': result.result if result.ready() else None
    })

if __name__ == '__main__':
    app.run(debug=True)
EOF

# ทดสอบ request
curl http://localhost:5000/add/10/20
# ตอบกลับ task_id ทันที ไม่ต้องรอ 10 วินาที

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

sudo tee /etc/nginx/sites-available/flower << 'EOF'
server {
    listen 80;
    server_name flower.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name flower.yourdomain.com;

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

    # Basic Auth เพื่อป้องกัน
    auth_basic "Flower Admin";
    auth_basic_user_file /etc/nginx/.htpasswd;

    location / {
        proxy_pass http://127.0.0.1:5555;
        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;

        # WebSocket support สำหรับ real-time updates
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
EOF

# สร้าง htpasswd
sudo htpasswd -c /etc/nginx/.htpasswd admin
# ระบุรหัสผ่าน

sudo ln -s /etc/nginx/sites-available/flower /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

# ตั้ง SSL certificate
certbot --nginx -d flower.yourdomain.com

Celery Beat — ตั้ง Scheduled Task

# สร้าง beat service สำหรับรัน periodic task
sudo tee /etc/systemd/system/celery-beat.service << 'EOF'
[Unit]
Description=Celery Beat Scheduler
After=network.target redis-server.service

[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp
Environment="PATH=/opt/myapp/venv/bin"
ExecStart=/opt/myapp/venv/bin/celery -A celery_app beat --loglevel=info

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl start celery-beat
sudo systemctl enable celery-beat

# ตรวจสอบ beat ว่ากำหนด schedule ถูกต้อง
celery -A celery_app inspect scheduled

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

# ตรวจ Redis connection
redis-cli ping
# ต้องได้ PONG

# ตรวจ queue โดยตรง
redis-cli LLEN celery
# หรือดู all keys ใน Redis
redis-cli KEYS '*'

# ตรวจ Celery worker status
celery -A celery_app inspect active
# ต้องแสดง worker info และ task running

# ตรวจ celery app ว่ามี task อะไร
celery -A celery_app inspect registered

# ดู worker stat (memory, request count, pool size)
celery -A celery_app inspect stats

# ส่ง test task จาก command line
python3 -c "from celery_app import add_numbers; result = add_numbers.delay(5, 3); print(f'Task ID: {result.id}')"

# เช็ค result
python3 -c "from celery_app import app; import sys; result = app.AsyncResult(sys.argv[1]); print(result.get())" TASK_ID
Tip: ถ้า worker ไม่รับ task อาจเป็นเพราะ Redis connection failed หรือ worker process ตาย ให้ดู systemd log: sudo journalctl -u celery -n 50

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

Error: "Connection refused" — Cannot connect to Redis

Redis service ไม่ทำงาน หรือไม่ listening บน port 6379

# ตรวจ Redis service status
sudo systemctl status redis-server

# ถ้า inactive ให้ start
sudo systemctl start redis-server

# ตรวจ Redis กำลัง listen บน port 6379
sudo netstat -tlnp | grep redis
# ต้องเห็น 127.0.0.1:6379 LISTEN

# ตรวจ redis.conf bind address
sudo grep "^bind" /etc/redis/redis.conf
# ต้องเป็น 127.0.0.1 หรือ 0.0.0.0 (ถ้า multi-server)

Error: "Unexpected end-of-stream" — Task ไม่ return result

Worker process crashed หรือ hung ขณะรัน task — result backend ไม่ได้ result

# ตรวจ worker log
sudo tail -100 /var/log/celery.log

# Restart worker
sudo systemctl restart celery

# ลอง reduce concurrency ถ้า server resource จำกัด
# แก้ใน /etc/systemd/system/celery.service
# ExecStart=... --concurrency=2  # ลด จาก 4 เป็น 2

# ถ้า task นาน > 5 นาที ต้องเพิ่ม task_time_limit
# ใน celery_app.py:
# app.conf.task_time_limit = 3600  # 1 ชั่วโมง
# app.conf.task_soft_time_limit = 3300  # soft limit 55 นาที

Error: "Task stuck" — Task queue ไม่ลด

Task dead letter queue (DLQ) ที่ worker ไม่สามารถประมวลผลได้

# ตรวจ Redis queue depth
redis-cli LLEN celery
# ถ้ามีค่าสูง ๆ task กำลังต่อคิว

# Purge all pending task (ระวัง!)
celery -A celery_app purge
# ตอบ y เพื่อ confirm

# ลองเพิ่ม worker concurrency
celery -A celery_app worker --loglevel=info --concurrency=8

# ตรวจ task execution time ด้วย Flower
# เข้า flower.yourdomain.com → Tasks → เลือก task ดู execution time

Error: "ModuleNotFoundError" — cannot import celery_app

Virtual environment ไม่ activate หรือ path ไม่ถูกต้อง

# ตรวจ venv activate
source /opt/myapp/venv/bin/activate
which python3
# ต้องชี้ไป /opt/myapp/venv/bin/python3

# ตรวจ celery_app.py อยู่ใน /opt/myapp
ls -la /opt/myapp/celery_app.py

# ตรวจ PYTHONPATH
export PYTHONPATH=/opt/myapp:$PYTHONPATH
celery -A celery_app inspect active

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

Celery ต้องใช้ Redis หรือ RabbitMQ ไหม?

ต้องใช้ message broker หนึ่งในสองตัวนี้ Redis เป็นตัวเลือกง่าย ๆ สำหรับโครงการเล็ก เพราะตั้งง่าย แต่ RabbitMQ มี feature หลาย และ persistence ดีกว่า Redis ใช้ memory เท่านั้นอาจ lose message ถ้า crash (ยกเว้นตั้ง RDB/AOF persistence) สำหรับ production บน VPS แนะนำ Redis พอสำหรับ task < 1000/วินาที RabbitMQ ถ้าต้องการ reliability สูงและ scale ได้ยวนั้น

Flower dashboard ปลอดภัยไหม? จำเป็นต้องทำ Basic Auth?

Flower แสดง task data และ worker status ทั้งหมด ถ้าเปิดเข้ามาจาก internet ได้ อันตราย เพราะอาจ reveal sensitive info (task argument, worker IP) ปกติต้องทำ Basic Auth ด้วย htpasswd หรือ SSL client certificate หรือใส่ไว้หลัง corporate firewall เท่านั้น เลี่ยงเปิด port 5555 ออก internet

Celery worker ก็อบแกร่ทำ? สามารถ scale มากเท่าไหร่?

ได้ สามารถเพิ่ม worker instance ได้มากเท่าที่ VPS รองรับ การลิมิตคือ Redis throughput (ประมาณ 100k message/วินาที) และ worker memory แต่ละ worker ต้อง RAM 100-500 MB ขึ้นอยู่กับ complexity ของ task VPS 4 GB RAM รองรับ 8-10 worker process ได้ สำหรับ scale ขนาดใหญ่กว่านั้นลองแยก worker เป็น VPS คนละตัวแล้วชี้ broker URL เดียวกัน


ถ้าต้องการ VPS ไทยที่รัน Celery worker หลาย instance ได้แบบเสถียร ลอง เช่า VPS ไทยของ asiagb — server ในไทย latency ต่ำ เหมาะกับงาน background processing ก่อนเลือก VPS ลองตรวจสอบโครงสร้างเครือข่ายด้วย dnsxray.com