Updates on task pining and permissions, with pause ansible
This commit is contained in:
parent
6e90ab2673
commit
4605e0e6a5
17
README.md
17
README.md
@ -14,21 +14,22 @@ Generate the inventory and configuration files from your cluster definition:
|
||||
|
||||
```bash
|
||||
./tools/generate-inventory.py cluster.test.yml
|
||||
cd ansible
|
||||
```
|
||||
|
||||
**Note:** All Ansible commands should be run from the project root directory.
|
||||
|
||||
## Full Deployment
|
||||
|
||||
### Deploy entire cluster with proxies (recommended for new setups):
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook-full-with-proxies.yml
|
||||
ansible-playbook ansible/playbook-full-with-proxies.yml
|
||||
```
|
||||
|
||||
### Deploy cluster without proxies:
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook-full.yml
|
||||
ansible-playbook ansible/playbook-full.yml
|
||||
```
|
||||
|
||||
## Targeted Deployments
|
||||
@ -36,13 +37,13 @@ ansible-playbook playbook-full.yml
|
||||
### Deploy only to master node:
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook-master.yml --limit="af-test"
|
||||
ansible-playbook ansible/playbook-master.yml --limit="af-test"
|
||||
```
|
||||
|
||||
### Deploy only to worker nodes:
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook-worker.yml
|
||||
ansible-playbook ansible/playbook-worker.yml
|
||||
```
|
||||
|
||||
## Deploy Specific Steps
|
||||
@ -50,7 +51,7 @@ ansible-playbook playbook-worker.yml
|
||||
To start at a specific task (useful for debugging or partial deployments):
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook-master.yml --limit="af-test" --start-at-task="Prepare Caddy asset extraction directory"
|
||||
ansible-playbook ansible/playbook-master.yml --limit="af-test" --start-at-task="Prepare Caddy asset extraction directory"
|
||||
```
|
||||
|
||||
## Debug Deployments
|
||||
@ -58,7 +59,7 @@ ansible-playbook playbook-master.yml --limit="af-test" --start-at-task="Prepare
|
||||
Run with dry-run and verbose output for debugging:
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook-full.yml --check --diff -vv
|
||||
ansible-playbook ansible/playbook-full.yml --check --diff -vv
|
||||
```
|
||||
|
||||
## DAGs Only Deployment
|
||||
@ -66,7 +67,7 @@ ansible-playbook playbook-full.yml --check --diff -vv
|
||||
To update only DAG files and configurations:
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook-dags.yml
|
||||
ansible-playbook ansible/playbook-dags.yml
|
||||
```
|
||||
|
||||
## Vault Management
|
||||
|
||||
@ -14,7 +14,11 @@ RUN apt-get update && \
|
||||
python3-dev \
|
||||
wget \
|
||||
tar \
|
||||
xz-utils && \
|
||||
xz-utils \
|
||||
iputils-ping \
|
||||
curl \
|
||||
traceroute \
|
||||
tcpdump && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/man /usr/share/doc /usr/share/doc-base
|
||||
|
||||
|
||||
@ -1,20 +1,24 @@
|
||||
# Version: 2025-08-20-02
|
||||
# This file contains custom hooks for the Airflow environment.
|
||||
from airflow import settings
|
||||
from airflow.models.dagrun import DagRun
|
||||
from airflow.utils.session import provide_session
|
||||
|
||||
def task_instance_mutation_hook(ti):
|
||||
|
||||
@provide_session
|
||||
def task_instance_mutation_hook(ti, session=None):
|
||||
if ti.dag_id == 'ytdlp_ops_worker_per_url':
|
||||
# Safely access dag_run and conf. The ti.dag_run attribute may not be populated
|
||||
# when the hook is called during TaskInstance creation.
|
||||
dag_run = getattr(ti, 'dag_run', None)
|
||||
conf = getattr(dag_run, 'conf', {}) if dag_run else {}
|
||||
# Query the DagRun from the DB using run_id to reliably get the conf.
|
||||
# The ti.dag_run attribute is not always populated when the hook is called.
|
||||
dag_run = session.query(DagRun).filter(DagRun.run_id == ti.run_id).first()
|
||||
conf = dag_run.conf if dag_run else {}
|
||||
worker_queue = conf.get('worker_queue')
|
||||
|
||||
if worker_queue:
|
||||
print(f"Mutating queue for task {ti.task_id} to {worker_queue} based on dag_run.conf")
|
||||
print(f"MUTATION HOOK: For dag '{ti.dag_id}', pinning task '{ti.task_id}' (run_id: {ti.run_id}) to queue '{worker_queue}'.")
|
||||
ti.queue = worker_queue
|
||||
else:
|
||||
print(f"No worker_queue in conf for {ti.dag_id}. Falling back to 'queue-dl'")
|
||||
print(f"MUTATION HOOK: For dag '{ti.dag_id}', no 'worker_queue' in conf for run_id '{ti.run_id}'. Falling back to 'queue-dl'.")
|
||||
ti.queue = 'queue-dl'
|
||||
|
||||
# Register the hook only in appropriate contexts
|
||||
|
||||
@ -4,9 +4,9 @@
|
||||
"host": "{{ hostvars[groups['airflow_master'][0]].ansible_host }}",
|
||||
"login": "admin",
|
||||
"password": "0153093693-0009",
|
||||
"port": 9000,
|
||||
"port": 80,
|
||||
"extra": {
|
||||
"endpoint_url": "http://{{ hostvars[groups['airflow_master'][0]].ansible_host }}:9000",
|
||||
"endpoint_url": "http://{{ hostvars[groups['airflow_master'][0]].ansible_host }}:80",
|
||||
"region_name": "us-east-1",
|
||||
"aws_access_key_id": "admin",
|
||||
"aws_secret_access_key": "0153093693-0009",
|
||||
|
||||
@ -19,7 +19,7 @@ x-airflow-common:
|
||||
# This section is auto-generated by Ansible from the inventory.
|
||||
extra_hosts:
|
||||
{% for host in groups['all'] %}
|
||||
- "{{ hostvars[host]['inventory_hostname'] }}:{{ hostvars[host]['ansible_host'] }}"
|
||||
- "{{ hostvars[host]['inventory_hostname'] }}:{{ hostvars[host]['ansible_host'] | default(hostvars[host]['inventory_hostname']) }}"
|
||||
{% endfor %}
|
||||
env_file:
|
||||
# The .env file is located in the project root (e.g., /srv/airflow_dl_worker),
|
||||
|
||||
@ -57,7 +57,7 @@ x-airflow-common:
|
||||
# This section is auto-generated by Ansible from the inventory.
|
||||
extra_hosts:
|
||||
{% for host in groups['all'] %}
|
||||
- "{{ hostvars[host]['inventory_hostname'] }}:{{ hostvars[host]['ansible_host'] }}"
|
||||
- "{{ hostvars[host]['inventory_hostname'] }}:{{ hostvars[host]['ansible_host'] | default(hostvars[host]['inventory_hostname']) }}"
|
||||
{% endfor %}
|
||||
env_file:
|
||||
# The .env file is located in the project root, one level above the 'configs' directory.
|
||||
@ -237,14 +237,15 @@ services:
|
||||
networks:
|
||||
- proxynet
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "80:80"
|
||||
- "81:81"
|
||||
volumes:
|
||||
- ./configs/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
test: ["CMD", "curl", "-f", "http://localhost:80/minio/health/live"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
@ -261,7 +262,7 @@ services:
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
set -e;
|
||||
/usr/bin/mc alias set minio http://nginx-minio-lb:9000 $$MINIO_ROOT_USER $$MINIO_ROOT_PASSWORD;
|
||||
/usr/bin/mc alias set minio http://nginx-minio-lb:80 $$MINIO_ROOT_USER $$MINIO_ROOT_PASSWORD;
|
||||
# Retry loop for bucket creation
|
||||
MAX_ATTEMPTS=10
|
||||
SUCCESS=false
|
||||
|
||||
@ -44,6 +44,7 @@ services:
|
||||
- ./.env # Path is relative to the project directory
|
||||
volumes:
|
||||
- context-data:/app/context-data
|
||||
- ./logs/communication_logs:/app/communication_logs
|
||||
{% if service_role != 'management' %}
|
||||
# Mount the generated endpoints file to make it available to the server
|
||||
- ./configs/camoufox_endpoints.json:/app/config/camoufox_endpoints.json:ro
|
||||
|
||||
@ -2,7 +2,7 @@ events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
stream {
|
||||
http {
|
||||
upstream minio_servers {
|
||||
server minio:9000;
|
||||
}
|
||||
@ -12,12 +12,24 @@ stream {
|
||||
}
|
||||
|
||||
server {
|
||||
listen 9000;
|
||||
proxy_pass minio_servers;
|
||||
listen 80;
|
||||
location / {
|
||||
proxy_pass http://minio_servers;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 9001;
|
||||
proxy_pass minio_console_servers;
|
||||
listen 81;
|
||||
location / {
|
||||
proxy_pass http://minio_console_servers;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,135 +0,0 @@
|
||||
# Архитектура и описание YTDLP Airflow DAGs
|
||||
|
||||
Этот документ описывает архитектуру и назначение DAG'ов, используемых для скачивания видео с YouTube. Система построена на модели непрерывного, самоподдерживающегося цикла для параллельной и отказоустойчивой обработки.
|
||||
|
||||
## Основной цикл обработки
|
||||
|
||||
Обработка выполняется двумя основными DAG'ами, которые работают в паре: оркестратор и воркер.
|
||||
|
||||
### `ytdlp_ops_orchestrator` (Система "зажигания")
|
||||
|
||||
- **Назначение:** Этот DAG действует как "система зажигания" для запуска обработки. Он запускается вручную для старта указанного количества параллельных циклов-воркеров.
|
||||
- **Принцип работы:**
|
||||
- Он **не** обрабатывает URL-адреса самостоятельно.
|
||||
- Его единственная задача — запустить сконфигурированное количество DAG'ов `ytdlp_ops_worker_per_url`.
|
||||
- Он передает всю необходимую конфигурацию (пул аккаунтов, подключение к Redis и т.д.) воркерам.
|
||||
|
||||
### `ytdlp_ops_worker_per_url` (Самоподдерживающийся воркер)
|
||||
|
||||
- **Назначение:** Этот DAG обрабатывает один URL и спроектирован для работы в непрерывном цикле.
|
||||
- **Принцип работы:**
|
||||
1. **Запуск:** Начальный запуск инициируется `ytdlp_ops_orchestrator`.
|
||||
2. **Получение задачи:** Воркер извлекает один URL из очереди `_inbox` в Redis. Если очередь пуста, выполнение воркера завершается, и его "линия" обработки останавливается.
|
||||
3. **Обработка:** Он взаимодействует с сервисом `ytdlp-ops-server` для получения `info.json` и прокси, после чего скачивает видео.
|
||||
4. **Продолжение или остановка:**
|
||||
- **В случае успеха:** Он запускает новый экземпляр самого себя, создавая непрерывный цикл для обработки следующего URL.
|
||||
- **В случае сбоя:** Цикл прерывается (если `stop_on_failure` установлено в `True`), останавливая эту "линию" обработки. Это предотвращает остановку всей системы из-за одного проблемного URL или аккаунта.
|
||||
|
||||
## Управляющие DAG'и
|
||||
|
||||
### `ytdlp_mgmt_proxy_account`
|
||||
|
||||
- **Назначение:** Это основной инструмент для мониторинга и управления состоянием ресурсов, используемых `ytdlp-ops-server`.
|
||||
- **Функциональность:**
|
||||
- **Просмотр статусов:** Позволяет увидеть текущий статус всех прокси и аккаунтов (например, `ACTIVE`, `BANNED`, `RESTING`).
|
||||
- **Управление прокси:** Позволяет вручную банить, разбанивать или сбрасывать статус прокси.
|
||||
- **Управление аккаунтами:** Позволяет вручную банить или разбанивать аккаунты.
|
||||
|
||||
### `ytdlp_mgmt_queues`
|
||||
|
||||
- **Назначение:** Предоставляет набор инструментов для управления очередями Redis, используемыми в конвейере обработки.
|
||||
- **Функциональность (через параметр `action`):**
|
||||
- `add_videos`: Добавление одного или нескольких URL-адресов YouTube в очередь.
|
||||
- `clear_queue`: Очистка (удаление) указанного ключа Redis.
|
||||
- `list_contents`: Просмотр содержимого ключа Redis (списка или хэша).
|
||||
- `check_status`: Проверка общего состояния очередей (тип, размер).
|
||||
- `requeue_failed`: Перемещение всех URL-адресов из очереди сбоев `_fail` обратно в очередь `_inbox` для повторной обработки.
|
||||
|
||||
## Стратегия управления ресурсами (Прокси и Аккаунты)
|
||||
|
||||
Система использует интеллектуальную стратегию для управления жизненным циклом и состоянием аккаунтов и прокси, чтобы максимизировать процент успеха и минимизировать блокировки.
|
||||
|
||||
- **Жизненный цикл аккаунта ("Cooldown"):**
|
||||
- Чтобы предотвратить "выгорание", аккаунты автоматически переходят в состояние "отдыха" (`RESTING`) после периода интенсивного использования.
|
||||
- По истечении периода отдыха они автоматически возвращаются в `ACTIVE` и снова становятся доступными для воркеров.
|
||||
|
||||
- **Умная стратегия банов:**
|
||||
- **Сначала бан аккаунта:** При возникновении серьезной ошибки (например, `BOT_DETECTED`) система наказывает **только аккаунт**, который вызвал сбой. Прокси при этом продолжает работать.
|
||||
- **Бан прокси по "скользящему окну":** Прокси банится автоматически, только если он демонстрирует **систематические сбои с РАЗНЫМИ аккаунтами** за короткий промежуток времени. Это является надежным индикатором того, что проблема именно в прокси.
|
||||
|
||||
- **Мониторинг:**
|
||||
- DAG `ytdlp_mgmt_proxy_account` является основным инструментом для мониторинга. Он показывает текущий статус всех ресурсов, включая время, оставшееся до активации забаненных или отдыхающих аккаунтов.
|
||||
- Граф выполнения DAG `ytdlp_ops_worker_per_url` теперь явно показывает шаги, такие как `assign_account`, `get_token`, `ban_account`, `retry_get_token`, что делает процесс отладки более наглядным.
|
||||
|
||||
## Внешние сервисы
|
||||
|
||||
### `ytdlp-ops-server` (Thrift Service)
|
||||
|
||||
- **Назначение:** Внешний сервис, который предоставляет аутентификационные данные (токены, cookies, proxy) для скачивания видео.
|
||||
- **Взаимодействие:** Worker DAG (`ytdlp_ops_worker_per_url`) обращается к этому сервису перед началом загрузки для получения необходимых данных для `yt-dlp`.
|
||||
|
||||
## Логика работы Worker DAG (`ytdlp_ops_worker_per_url`)
|
||||
|
||||
Этот DAG является "рабочей лошадкой" системы. Он спроектирован как самоподдерживающийся цикл для обработки одного URL за запуск.
|
||||
|
||||
### Задачи и их назначение:
|
||||
|
||||
- **`pull_url_from_redis`**: Извлекает один URL из очереди `_inbox` в Redis. Если очередь пуста, DAG завершается со статусом `skipped`, останавливая эту "линию" обработки.
|
||||
- **`assign_account`**: Выбирает аккаунт для выполнения задачи. Он будет повторно использовать тот же аккаунт, который был успешно использован в предыдущем запуске в своей "линии" (привязка аккаунта). Если это первый запуск, он выбирает случайный аккаунт.
|
||||
- **`get_token`**: Основная задача. Она обращается к `ytdlp-ops-server` для получения `info.json`.
|
||||
- **`handle_bannable_error_branch`**: Если `get_token` завершается с ошибкой, требующей бана, эта задача-развилка решает, что делать дальше, в зависимости от политики `on_bannable_failure`.
|
||||
- **`ban_account_and_prepare_for_retry`**: Если политика разрешает повтор, эта задача банит сбойный аккаунт и выбирает новый для повторной попытки.
|
||||
- **`retry_get_token`**: Выполняет вторую попытку получить токен с новым аккаунтом.
|
||||
- **`ban_second_account_and_proxy`**: Если и вторая попытка неудачна, эта задача банит второй аккаунт и использованный прокси.
|
||||
- **`download_and_probe`**: Если `get_token` (или `retry_get_token`) завершилась успешно, эта задача использует `yt-dlp` для скачивания медиа и `ffmpeg` для проверки целостности скачанного файла.
|
||||
- **`mark_url_as_success`**: Если `download_and_probe` завершилась успешно, эта задача записывает результат в хэш `_result` в Redis.
|
||||
- **`handle_generic_failure`**: Если любая из основных задач завершается с неисправимой ошибкой, эта задача записывает подробную информацию об ошибке в хэш `_fail` в Redis.
|
||||
- **`decide_what_to_do_next`**: Задача-развилка, которая запускается после успеха или неудачи. Она решает, продолжать ли цикл.
|
||||
- **`trigger_self_run`**: Задача, которая фактически запускает следующий экземпляр DAG, создавая непрерывный цикл.
|
||||
|
||||
## Механизм привязки воркеров к конкретным машинам (Worker Pinning / Affinity)
|
||||
|
||||
Для обеспечения того, чтобы все задачи, связанные с обработкой одного конкретного URL, выполнялись на одной и той же машине (воркере), система использует комбинацию из трех компонентов: Оркестратора, Диспетчера и специального хука Airflow.
|
||||
|
||||
### 1. `ytdlp_ops_orchestrator` (Оркестратор)
|
||||
|
||||
- **Роль:** Инициирует процесс обработки.
|
||||
- **Действие:** При запуске он создает несколько DAG-запусков `ytdlp_ops_dispatcher`. Каждый такой запуск предназначен для обработки одного URL.
|
||||
- **Передача параметров:** Оркестратор передает свои параметры конфигурации (например, `account_pool`, `redis_conn_id`, `service_ip`) каждому запуску диспетчера.
|
||||
|
||||
### 2. `ytdlp_ops_dispatcher` (Диспетчер)
|
||||
|
||||
- **Роль:** Основной механизм обеспечения привязки.
|
||||
- **Действие:**
|
||||
1. **Получает URL:** Извлекает один URL из очереди Redis (`_inbox`).
|
||||
2. **Определяет воркер:** Использует `socket.gethostname()` для определения имени текущей машины (воркера), на которой он выполняется.
|
||||
3. **Формирует имя очереди:** Создает уникальное имя очереди для этого воркера, например, `queue-dl-dl-worker-1`.
|
||||
4. **Запускает Worker DAG:** Инициирует запуск DAG `ytdlp_ops_worker_per_url`, передавая ему:
|
||||
* Извлеченный `url_to_process`.
|
||||
* Сформированное имя очереди `worker_queue` через параметр `conf`.
|
||||
* Все остальные параметры, полученные от оркестратора.
|
||||
- **Ключевой момент:** Именно на этом этапе устанавливается связь между конкретным URL и конкретным воркером, на котором началась обработка этого URL.
|
||||
|
||||
### 3. `task_instance_mutation_hook` (Хук изменения задач)
|
||||
|
||||
- **Расположение:** `airflow/config/custom_task_hooks.py`
|
||||
- **Роль:** Является механизмом, который обеспечивает выполнение *всех* задач Worker DAG на нужной машине.
|
||||
- **Как это работает:**
|
||||
1. **Регистрация:** Хук регистрируется в конфигурации Airflow и вызывается перед запуском *каждой* задачи.
|
||||
2. **Проверка DAG ID:** Хук проверяет, принадлежит ли задача (`TaskInstance`) DAG `ytdlp_ops_worker_per_url`.
|
||||
3. **Извлечение `conf`:** Если да, он безопасно извлекает `conf` из `DagRun`, связанного с этой задачей.
|
||||
4. **Изменение очереди:**
|
||||
* Если в `conf` найден ключ `worker_queue` (что будет true для всех запусков, инициированных диспетчером), хук *переопределяет* стандартную очередь задачи на это значение.
|
||||
* Это означает, что Airflow планировщик поставит эту задачу именно в ту очередь, которая прослушивается нужным воркером.
|
||||
5. **Резервный вариант:** Если `worker_queue` не найден (например, DAG запущен вручную), задача возвращается в стандартную очередь `queue-dl`.
|
||||
- **Ключевой момент:** Этот хук гарантирует, что *все последующие задачи* в рамках одного запуска `ytdlp_ops_worker_per_url` (например, `get_token`, `download_and_probe`, `mark_url_as_success`) будут выполнены на том же воркере, который изначально получил URL в диспетчере.
|
||||
|
||||
### Резюме
|
||||
|
||||
Комбинация `Оркестратор -> Диспетчер -> Хук` эффективно реализует привязку задач к воркерам:
|
||||
|
||||
1. **Оркестратор** запускает процесс.
|
||||
2. **Диспетчер** связывает конкретный URL с конкретным воркером, определяя его имя хоста и передавая его как `worker_queue` в Worker DAG.
|
||||
3. **Хук** гарантирует, что все задачи Worker DAG выполняются в очереди, соответствующей этому воркеру.
|
||||
|
||||
Это позволяет системе использовать локальные ресурсы воркера (например, кэш, временные файлы) эффективно и предсказуемо для обработки каждого отдельного URL.
|
||||
@ -107,12 +107,12 @@
|
||||
**Поставить воркер на паузу:**
|
||||
(Замените `"hostname"` на имя хоста из вашего inventory-файла)
|
||||
```bash
|
||||
ansible-playbook ansible/playbooks/pause_worker.yml --limit "hostname"
|
||||
ansible-playbook -i ansible/inventory.ini ansible/playbooks/pause_worker.yml --limit "hostname"
|
||||
```
|
||||
|
||||
**Возобновить работу воркера:**
|
||||
```bash
|
||||
ansible-playbook ansible/playbooks/resume_worker.yml --limit "hostname"
|
||||
ansible-playbook -i ansible/inventory.ini ansible/playbooks/resume_worker.yml --limit "hostname"
|
||||
```
|
||||
|
||||
## Механизм привязки воркеров к конкретным машинам (Worker Pinning / Affinity)
|
||||
|
||||
@ -32,7 +32,7 @@ def dispatch_url_to_worker(**context):
|
||||
"""
|
||||
# --- Check for worker pause lock file ---
|
||||
# This path must be consistent with the Ansible playbook.
|
||||
lock_file_path = '/srv/airflow_dl_worker/AIRFLOW.PREVENT_URL_PULL.lock'
|
||||
lock_file_path = '/opt/airflow/inputfiles/AIRFLOW.PREVENT_URL_PULL.lockfile'
|
||||
hostname = socket.gethostname()
|
||||
if os.path.exists(lock_file_path):
|
||||
logger.info(f"Worker '{hostname}' is paused. Lock file found at '{lock_file_path}'. Skipping URL pull.")
|
||||
|
||||
@ -264,7 +264,7 @@ with DAG(
|
||||
),
|
||||
'queue_name': Param(DEFAULT_QUEUE_NAME, type="string", description="[Worker Param] Base name for Redis queues."),
|
||||
'redis_conn_id': Param(DEFAULT_REDIS_CONN_ID, type="string", description="[Worker Param] Airflow Redis connection ID."),
|
||||
'clients': Param('web', type="string", description="[Worker Param] Comma-separated list of clients for token generation. Full list: web, mweb, ios, android, web_safari, web_embedded, web_music, web_creator"),
|
||||
'clients': Param('web', type="string", description="[Worker Param] Comma-separated list of clients for token generation. Full list: web, web_safari, web_embedded, web_music, web_creator, mweb, web_camoufox, web_safari_camoufox, web_embedded_camoufox, web_music_camoufox, web_creator_camoufox, mweb_camoufox, android, android_music, android_creator, android_vr, ios, ios_music, ios_creator, tv, tv_simply, tv_sample, tv_embedded"),
|
||||
'account_pool': Param('ytdlp_account', type="string", description="[Worker Param] Account pool prefix or comma-separated list."),
|
||||
'account_pool_size': Param(10, type=["integer", "null"], description="[Worker Param] If using a prefix for 'account_pool', this specifies the number of accounts to generate (e.g., 10 for 'prefix_01' through 'prefix_10'). Required when using a prefix."),
|
||||
'service_ip': Param(DEFAULT_YT_AUTH_SERVICE_IP, type="string", description="[Worker Param] IP of the ytdlp-ops-server. Default is from Airflow variable YT_AUTH_SERVICE_IP or hardcoded."),
|
||||
|
||||
17
ansible.cfg
Normal file
17
ansible.cfg
Normal file
@ -0,0 +1,17 @@
|
||||
[defaults]
|
||||
timeout = 30
|
||||
inventory = ansible/inventory.ini
|
||||
roles_path = ansible/roles
|
||||
retry_files_enabled = False
|
||||
host_key_checking = False
|
||||
vault_password_file = .vault_pass
|
||||
|
||||
[inventory]
|
||||
enable_plugins = ini
|
||||
|
||||
[ssh_connection]
|
||||
pipelining = True
|
||||
control_path_dir = ~/.ansible/cp
|
||||
# SSH connection timeout increased for jump host connections
|
||||
# Enable connection sharing (ControlMaster) to speed up multiple tasks.
|
||||
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ConnectTimeout=60 -o ConnectionAttempts=3
|
||||
@ -2,18 +2,21 @@
|
||||
|
||||
This directory contains the Ansible playbooks, roles, and configurations for deploying and managing the YT-DLP Airflow cluster.
|
||||
|
||||
**Note:** All commands should be run from the project root, not from within this directory.
|
||||
Example: `ansible-playbook ansible/playbook-full.yml`
|
||||
|
||||
## Full Deployment
|
||||
|
||||
### Deploy entire cluster with proxies (recommended for new setups):
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook-full-with-proxies.yml
|
||||
ansible-playbook ansible/playbook-full-with-proxies.yml
|
||||
```
|
||||
|
||||
### Deploy cluster without proxies:
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook-full.yml
|
||||
ansible-playbook ansible/playbook-full.yml
|
||||
```
|
||||
|
||||
## Targeted Deployments
|
||||
@ -21,13 +24,13 @@ ansible-playbook playbook-full.yml
|
||||
### Deploy only to master node:
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook-master.yml --limit="af-test"
|
||||
ansible-playbook ansible/playbook-master.yml --limit="af-test"
|
||||
```
|
||||
|
||||
### Deploy only to worker nodes:
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook-worker.yml
|
||||
ansible-playbook ansible/playbook-worker.yml
|
||||
```
|
||||
|
||||
## DAGs Only Deployment
|
||||
@ -35,7 +38,7 @@ ansible-playbook playbook-worker.yml
|
||||
To update only DAG files and configurations:
|
||||
|
||||
```bash
|
||||
ansible-playbook playbook-dags.yml
|
||||
ansible-playbook ansible/playbook-dags.yml
|
||||
```
|
||||
|
||||
## Managing Worker State (Pause/Resume)
|
||||
@ -48,7 +51,7 @@ This command creates the lock file, causing the `ytdlp_ops_dispatcher` DAG to sk
|
||||
|
||||
```bash
|
||||
# Replace "worker-hostname" with the target host from your inventory
|
||||
ansible-playbook playbooks/pause_worker.yml --limit "worker-hostname"
|
||||
ansible-playbook ansible/playbooks/pause_worker.yml --limit "worker-hostname"
|
||||
```
|
||||
|
||||
### To Resume a Worker
|
||||
@ -57,5 +60,5 @@ This command removes the lock file, allowing the worker to resume picking up tas
|
||||
|
||||
```bash
|
||||
# Replace "worker-hostname" with the target host from your inventory
|
||||
ansible-playbook playbooks/resume_worker.yml --limit "worker-hostname"
|
||||
ansible-playbook ansible/playbooks/resume_worker.yml --limit "worker-hostname"
|
||||
```
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
[defaults]
|
||||
inventory = ansible/inventory.ini
|
||||
roles_path = ansible/roles
|
||||
retry_files_enabled = False
|
||||
host_key_checking = False
|
||||
vault_password_file = .vault_pass
|
||||
|
||||
[inventory]
|
||||
enable_plugins = ini
|
||||
@ -6,6 +6,7 @@ airflow_master_dir: /srv/airflow_master
|
||||
airflow_uid: 1003
|
||||
airflow_worker_dir: /srv/airflow_dl_worker
|
||||
ansible_user: alex_p
|
||||
camoufox_base_port: 9070
|
||||
camoufox_base_vnc_port: 5901
|
||||
deploy_group: ytdl
|
||||
dir_permissions: '0755'
|
||||
@ -39,4 +40,4 @@ shadowsocks_mode: tcp_and_udp
|
||||
shadowsocks_timeout: 20
|
||||
ssh_user: alex_p
|
||||
ytdlp_base_port: 9090
|
||||
ytdlp_ops_image: pangramia/ytdlp-ops-server:latest
|
||||
ytdlp_ops_image: pangramia/ytdlp-ops-server:3.10.1-exp
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
---
|
||||
# Variables for af-test
|
||||
master_host_ip: 89.253.223.97
|
||||
redis_port: 52909
|
||||
shadowsocks_proxies:
|
||||
sslocal-rust-1087:
|
||||
server: "91.103.252.51"
|
||||
server_port: 8388
|
||||
local_port: 1087
|
||||
vault_password_key: "vault_ss_password_1"
|
||||
sslocal-rust-1086:
|
||||
server: "62.60.178.45"
|
||||
server_port: 8388
|
||||
local_port: 1086
|
||||
vault_password_key: "vault_ss_password_2"
|
||||
sslocal-rust-1081:
|
||||
server: "79.137.207.43"
|
||||
server_port: 8388
|
||||
local_port: 1081
|
||||
vault_password_key: "vault_ss_password_2"
|
||||
worker_proxies:
|
||||
- "socks5://sslocal-rust-1086:1086"
|
||||
- "socks5://sslocal-rust-1081:1081"
|
||||
@ -1,23 +0,0 @@
|
||||
---
|
||||
# Variables for dl002
|
||||
master_host_ip: 89.253.223.97
|
||||
redis_port: 52909
|
||||
shadowsocks_proxies:
|
||||
sslocal-rust-1087:
|
||||
server: "91.103.252.51"
|
||||
server_port: 8388
|
||||
local_port: 1087
|
||||
vault_password_key: "vault_ss_password_1"
|
||||
sslocal-rust-1086:
|
||||
server: "62.60.178.45"
|
||||
server_port: 8388
|
||||
local_port: 1086
|
||||
vault_password_key: "vault_ss_password_2"
|
||||
sslocal-rust-1081:
|
||||
server: "79.137.207.43"
|
||||
server_port: 8388
|
||||
local_port: 1081
|
||||
vault_password_key: "vault_ss_password_2"
|
||||
worker_proxies:
|
||||
- "socks5://sslocal-rust-1081:1081"
|
||||
- "socks5://sslocal-rust-1086:1086"
|
||||
@ -45,3 +45,16 @@
|
||||
rsync_opts:
|
||||
- "--exclude=__pycache__/"
|
||||
- "--exclude=*.pyc"
|
||||
|
||||
- name: Sync Config to WORKER server
|
||||
ansible.posix.synchronize:
|
||||
src: "../airflow/config/{{ item }}"
|
||||
dest: /srv/airflow_dl_worker/config/
|
||||
archive: yes
|
||||
rsync_path: "sudo rsync"
|
||||
rsync_opts:
|
||||
- "--exclude=__pycache__/"
|
||||
- "--exclude=*.pyc"
|
||||
loop:
|
||||
- "airflow.cfg"
|
||||
- "custom_task_hooks.py"
|
||||
|
||||
@ -54,6 +54,10 @@
|
||||
- vim
|
||||
- python3-pip
|
||||
- iputils-ping
|
||||
- traceroute
|
||||
- fail2ban
|
||||
- conntrack
|
||||
- tcpdump
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
|
||||
@ -149,9 +149,3 @@
|
||||
|
||||
roles:
|
||||
- ytdlp-worker
|
||||
- airflow-worker
|
||||
|
||||
post_tasks:
|
||||
- name: Include camoufox verification tasks
|
||||
include_tasks: tasks/verify_camoufox.yml
|
||||
when: not fast_deploy | default(false)
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
tasks:
|
||||
- name: "Create lock file to pause worker"
|
||||
file:
|
||||
path: "{{ airflow_worker_dir }}/AIRFLOW.PREVENT_URL_PULL.lock"
|
||||
path: "{{ airflow_worker_dir }}/inputfiles/AIRFLOW.PREVENT_URL_PULL.lockfile"
|
||||
state: touch
|
||||
owner: "{{ ssh_user }}"
|
||||
group: "{{ deploy_group }}"
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
tasks:
|
||||
- name: "Archive lock file to resume worker"
|
||||
command: >
|
||||
mv {{ airflow_worker_dir }}/AIRFLOW.PREVENT_URL_PULL.lock
|
||||
{{ airflow_worker_dir }}/AIRFLOW.PREVENT_URL_PULL.lock.removed-{{ ansible_date_time.year }}{{ '%02d' | format(ansible_date_time.month) }}{{ '%02d' | format(ansible_date_time.day) }}-{{ '%02d' | format(ansible_date_time.hour) }}{{ '%02d' | format(ansible_date_time.minute) }}
|
||||
mv {{ airflow_worker_dir }}/inputfiles/AIRFLOW.PREVENT_URL_PULL.lockfile
|
||||
{{ airflow_worker_dir }}/inputfiles/AIRFLOW.PREVENT_URL_PULL.lockfile.removed-{{ ansible_date_time.year }}{{ '%02d' | format(ansible_date_time.month | int) }}{{ '%02d' | format(ansible_date_time.day | int) }}-{{ '%02d' | format(ansible_date_time.hour | int) }}{{ '%02d' | format(ansible_date_time.minute | int) }}
|
||||
args:
|
||||
removes: "{{ airflow_worker_dir }}/AIRFLOW.PREVENT_URL_PULL.lock"
|
||||
removes: "{{ airflow_worker_dir }}/inputfiles/AIRFLOW.PREVENT_URL_PULL.lockfile"
|
||||
become: yes
|
||||
|
||||
@ -218,6 +218,7 @@
|
||||
owner: "999" # UID for the 'postgres' user in the official postgres image
|
||||
group: "999" # GID for the 'postgres' group in the official postgres image
|
||||
mode: '0700'
|
||||
recurse: yes
|
||||
become: yes
|
||||
|
||||
- name: Set proper ownership and permissions on master logs directory contents
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
---
|
||||
- name: Ensure worker is not paused on deploy (remove .lock file)
|
||||
file:
|
||||
path: "{{ airflow_worker_dir }}/AIRFLOW.PREVENT_URL_PULL.lock"
|
||||
path: "{{ airflow_worker_dir }}/inputfiles/AIRFLOW.PREVENT_URL_PULL.lockfile"
|
||||
state: absent
|
||||
become: yes
|
||||
|
||||
- name: Clean up old renamed lock files (older than 7 days)
|
||||
ansible.builtin.find:
|
||||
paths: "{{ airflow_worker_dir }}"
|
||||
patterns: "AIRFLOW.PREVENT_URL_PULL.lock.removed-*"
|
||||
paths: "{{ airflow_worker_dir }}/inputfiles"
|
||||
patterns: "AIRFLOW.PREVENT_URL_PULL.lockfile.removed-*"
|
||||
age: "7d"
|
||||
use_regex: false
|
||||
register: old_lock_files
|
||||
@ -22,6 +22,24 @@
|
||||
become: yes
|
||||
when: old_lock_files.files | length > 0
|
||||
|
||||
- name: Ensure YT-DLP worker inputfiles directory exists
|
||||
file:
|
||||
path: "{{ airflow_worker_dir }}/inputfiles"
|
||||
state: directory
|
||||
owner: "{{ ssh_user }}"
|
||||
group: "{{ deploy_group }}"
|
||||
mode: '0755'
|
||||
become: yes
|
||||
|
||||
- name: Ensure YT-DLP worker logs directory exists
|
||||
file:
|
||||
path: "{{ airflow_worker_dir }}/logs"
|
||||
state: directory
|
||||
owner: "{{ airflow_uid }}"
|
||||
group: "{{ deploy_group }}"
|
||||
mode: '0775'
|
||||
become: yes
|
||||
|
||||
- name: Check if YT-DLP worker deployment directory exists
|
||||
stat:
|
||||
path: "{{ airflow_worker_dir }}"
|
||||
@ -141,6 +159,26 @@
|
||||
become: yes
|
||||
become_user: "{{ ssh_user }}"
|
||||
|
||||
- name: Clean up old root docker-compose files to prevent conflicts
|
||||
ansible.builtin.file:
|
||||
path: "{{ airflow_worker_dir }}/{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- "docker-compose.yml"
|
||||
- "docker-compose.yaml"
|
||||
- "docker-compose.override.yml"
|
||||
- "docker-compose.airflow.yml"
|
||||
become: yes
|
||||
|
||||
- name: Template docker-compose file for Airflow worker
|
||||
template:
|
||||
src: "{{ playbook_dir }}/../airflow/configs/docker-compose-dl.yaml.j2"
|
||||
dest: "{{ airflow_worker_dir }}/configs/docker-compose.airflow.yml"
|
||||
mode: "{{ file_permissions }}"
|
||||
owner: "{{ ssh_user }}"
|
||||
group: "{{ deploy_group }}"
|
||||
become: yes
|
||||
|
||||
- name: "Log: Building Camoufox (remote browser) image"
|
||||
debug:
|
||||
msg: "Building the Camoufox image locally. This image provides remote-controlled Firefox browsers for token generation."
|
||||
@ -168,15 +206,21 @@
|
||||
path: "/srv/shadowsocks-rust/docker-compose.proxies.yaml"
|
||||
register: proxy_compose_file
|
||||
|
||||
- name: "Log: Starting YT-DLP worker services"
|
||||
- name: "Log: Starting all worker services"
|
||||
debug:
|
||||
msg: "Starting the core YT-DLP worker services: ytdlp-ops-service (Thrift API), envoy (load balancer), and camoufox (remote browsers)."
|
||||
msg: "Starting all worker services: ytdlp-ops, camoufox, and airflow-worker."
|
||||
|
||||
- name: Start YT-DLP worker service
|
||||
- name: Start all worker services
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ airflow_worker_dir }}"
|
||||
files:
|
||||
- "configs/docker-compose-ytdlp-ops.yaml"
|
||||
- "configs/docker-compose.camoufox.yaml"
|
||||
- "configs/docker-compose.airflow.yml"
|
||||
state: present
|
||||
remove_orphans: true
|
||||
pull: "{{ 'never' if fast_deploy | default(false) else 'missing' }}"
|
||||
|
||||
- name: Include camoufox verification tasks
|
||||
include_tasks: ../../../tasks/verify_camoufox.yml
|
||||
when: not fast_deploy | default(false)
|
||||
|
||||
@ -50,7 +50,7 @@ YTDLP_TIMEOUT=600
|
||||
CAMOUFOX_PROXIES="{{ (worker_proxies | default([])) | join(',') }}"
|
||||
VNC_PASSWORD="{{ vault_vnc_password }}"
|
||||
CAMOUFOX_BASE_VNC_PORT={{ camoufox_base_vnc_port }}
|
||||
CAMOUFOX_PORT={{ camoufox_port }}
|
||||
CAMOUFOX_PORT={{ camoufox_base_port }}
|
||||
|
||||
# --- Account Manager Configuration ---
|
||||
ACCOUNT_ACTIVE_DURATION_MIN={{ account_active_duration_min | default(7) }}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
global_vars:
|
||||
# Docker image versions
|
||||
ytdlp_ops_image: "pangramia/ytdlp-ops-server:latest"
|
||||
ytdlp_ops_image: "pangramia/ytdlp-ops-server:3.10.1-exp"
|
||||
airflow_image_name: "pangramia/ytdlp-ops-airflow:latest"
|
||||
|
||||
# Default ports
|
||||
@ -9,6 +9,7 @@ global_vars:
|
||||
envoy_port: 9080
|
||||
envoy_admin_port: 9901
|
||||
management_service_port: 9091
|
||||
camoufox_base_port: 9070
|
||||
camoufox_base_vnc_port: 5901
|
||||
|
||||
# Default UID
|
||||
|
||||
@ -41,7 +41,7 @@ def parse_args():
|
||||
parser.add_argument('--host', default='127.0.0.1', help="Thrift server host. Using 127.0.0.1 avoids harmless connection errors when the local Envoy proxy only listens on IPv4.")
|
||||
parser.add_argument('--port', type=int, default=9080, help='Thrift server port')
|
||||
parser.add_argument('--profile', default='default_profile', help='The profile name (accountId) to use for the request.')
|
||||
parser.add_argument('--client', help='Specific client to use (e.g., web, ios, android). Overrides server default.')
|
||||
parser.add_argument('--client', help='Specific client to use (e.g., web, ios). Overrides server default. Append "_camoufox" to any client name (e.g., "web_camoufox") to force the browser-based generation strategy.')
|
||||
parser.add_argument('--output', help='Output file path for the info.json. If not provided, prints to stdout.')
|
||||
parser.add_argument('--machine-id', help='Identifier for the client machine. Defaults to hostname.')
|
||||
parser.add_argument('--verbose', action='store_true', help='Enable verbose output')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user