Модуль 33

Celery у Docker-середовищі

Ось твій урок у стилі CS50. Вмикаймо уяву, відкриваймо термінал і поїхали!


🎓 Урок: Celery у Docker-середовищі (асинхронність у контейнерах)

1. 🔥 Вступ: Чому ваш сайт "завис"?

Уявіть собі ситуацію. Ви заходите на сайт, реєструєтесь, натискаєте кнопку "Створити акаунт" і… чекаєте. Крутиться спінер завантаження. Минає секунда, дві, п’ять. Ви починаєте нервувати. "Чи натиснув я кнопку? Може, Інтернет зник?".

А насправді, що відбувається "під капотом"? Сервер отримав ваші дані, зберіг їх у базу, а потім почав генерувати PDF-звіт, стискати ваше фото профілю або відправляти вітальний email через повільний SMTP-сервер. І поки він це робить, він не може відповісти вам: "Усе ок, друже, вітаємо!". Він зайнятий.

Риторичне запитання: Чи хотіли б ви, щоб офіціант у ресторані, прийнявши ваше замовлення, йшов на кухню, сам чистив картоплю, смажив м’ясо, і тільки потім повертався до вас, ігноруючи всіх інших клієнтів у залі?

Звісно, ні! Офіціант (ваш веб-сервер) має лише прийняти замовлення і передати його на кухню (воркеру).

Саме для цього нам потрібен Celery — це наша "кухня". А Docker дозволяє нам побудувати цей ресторан так, щоб і офіціанти, і кухарі працювали в ідеально ізольованих, але пов'язаних кімнатах.

Без цієї теми ви будете створювати програми, які "гальмують" на кожній складній операції.


2. 🧠 Теоретична база: Як це працює (Кухня та Тікети)

Давайте розберемо архітектуру. У нас є три головні дійові особи.

  1. Producer (Веб-додаток/Django/Flask): Це офіціант. Він каже: "Треба надіслати лист". Але він не робить це сам. Він створює "тікет" (задачу).
  2. Broker (Redis або RabbitMQ): Це дошка із замовленнями на кухні. Офіціант чіпляє туди тікет. Брокер — це посередник. Він зберігає чергу задач.
  3. Consumer (Celery Worker): Це кухар. Він бачить новий тікет на дошці, бере його і починає готувати (відправляти лист), поки офіціант вже обслуговує іншого клієнта.

А до чого тут Docker?

Раніше ми запускали це все в різних терміналах на одній машині. Але в професійному світі ("production") ми хочемо ізоляції.

У Docker-світі кожен з цих гравців — це окремий контейнер: * Контейнер web (ваш код сайту). * Контейнер redis (ваша дошка оголошень). * Контейнер worker (ваш код сайту + Celery, який виконує задачі).

❗️ Що треба запам’ятати залізно: І web, і worker зазвичай використовують один і той самий Docker-образ (image). Чому? Бо воркеру теж потрібен доступ до ваших моделей бази даних, функцій та налаштувань, щоб виконати задачу. Різниця лише в команді запуску!


3. 🧪 Приклади: Від коду до Docker Compose

Крок 1: Мінімальна задача (Python)

Припустимо, у нас є файл tasks.py.

# tasks.py
import time
from celery import Celery

# Налаштовуємо Celery на роботу з Redis
app = Celery('tasks', broker='redis://redis:6379/0')

@app.task
def long_task(name):
    print(f"Починаю обробку для {name}...")
    time.sleep(10)  # Імітація важкої роботи
    print(f"Готово! {name} оброблено.")
    return f"Hello, {name}!"

Крок 2: Магія Docker Compose

Ось тут стається найцікавіше. Як нам запустити це як оркестр? Дивимось на docker-compose.yml.

Питання до вас: Що ви очікуєте побачити у файлі конфігурації? Скільки сервісів нам потрібно мінімум? (Правильно, три: програма, брокер, воркер).

version: '3.8'

services:
  # 1. Наш Брокер (Пошта/Дошка)
  redis:
    image: redis:alpine

  # 2. Наш Веб-сервіс (Офіціант) - імітуємо просто запуск скрипта
  web:
    build: .
    command: python app.py  # Запускає веб-сервер
    depends_on:
      - redis
    volumes:
      - .:/app

  # 3. Наш Воркер (Кухар)
  worker:
    build: .  # ТА Ж САМА збірка, що і для web!
    # АЛЕ інша команда запуску:
    command: celery -A tasks worker --loglevel=info
    depends_on:
      - redis
    volumes:
      - .:/app
    environment:
      - CELERY_BROKER_URL=redis://redis:6379/0

Чому це працює? Ми використовуємо один код (build: .), але наказуємо контейнерам робити різні речі. Контейнер web слухає користувачів. Контейнер worker мовчки сидить і дивиться на redis, чекаючи на роботу.


4. 🛠 Практична частина

Завдання для закріплення. Не бійтеся помилятися — це найкращий спосіб навчання!

Завдання 1: "Hello World" у логах Створіть структуру файлів з прикладу вище. Запустіть docker-compose up. Викличте задачу з web контейнера (можна через docker-compose exec web python -> from tasks import long_task; long_task.delay('Student')). Знайдіть у логах контейнера worker момент, коли задача почалася і закінчилася.

Завдання 2: "Саботаж" Зупиніть контейнер worker (docker-compose stop worker), але залиште web і redis. Відправте 5 задач. Питання: Де вони ділися? Дія: Запустіть worker знову. Що сталося? (Спойлер: вони мають виконатися, бо Redis їх дбайливо зберіг).

Завдання 3: Scaling (Масштабування) Уявіть, що прийшов "Чорний п'ятниця". Один кухар не справляється. Спробуйте команду: docker-compose up -d --scale worker=3. Тепер відправте 10 важких задач одночасно. Подивіться в логи: як задачі розподілилися між трьома воркерами?

Завдання 4: Реальний кейс Напишіть функцію, яка не просто робить sleep, а, наприклад, записує поточний час у текстовий файл log.txt. Нюанс: Перевірте, в якому контейнері з'явився файл? (Пам'ятайте про Volume).


5. 💡 Мислення як у розробника

Як відрізнити новачка від профі в роботі з Celery та Docker?

1. Помилка: "Передача об'єктів" Новачок: Передає в задачу цілий об'єкт користувача (User object). Профі: Передає тільки user_id. Чому? Поки задача дійде до виконання в черзі, дані користувача в базі могли змінитися. Воркер має сам дістати свіжі дані з бази за ID. До того ж, серіалізація складних об'єктів часто ламається.

2. Помилка: "Воркер без коду" Новачок: Думає, що воркер — це якась магічна хмара. Профі: Розуміє, що воркер — це копія вашого проекту. Якщо ви змінили код у tasks.py, вам треба перезапустити (або перебудувати) і контейнер воркера теж!

3. Порада: Моніторинг Коли задачі виконуються у фоні, ви не бачите помилок на екрані браузера. Профі: Завжди додає інструмент типу Flower (веб-інтерфейс для Celery) у docker-compose, щоб візуально бачити: "Ага, ця задача впала, бо SMTP-сервер недоступний".


6. 🧩 Підсумок

Отже, що ми сьогодні зробили? Ми взяли синхронний, повільний процес і розбили його на швидкі, асинхронні шматки. Ми використали Docker, щоб розкласти компоненти (Брокер, Воркер, Додаток) по поличках, як інгредієнти на професійній кухні.

Тепер ви вмієте: 1. Описувати сервіс worker у docker-compose.yml. 2. Розуміти потік даних: App -> Redis -> Worker. 3. Масштабувати обробку задач однією командою.

Що далі? Ви запитаєте: "А як зробити так, щоб задача виконувалася щоранку о 9:00, наприклад, розсилка новин?". Для цього нам знадобиться Celery Beat — диригент нашого оркестру. Але це… вже тема наступного уроку!

Кодимо далі! 🚀