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
|
```bash
|
||||||
./tools/generate-inventory.py cluster.test.yml
|
./tools/generate-inventory.py cluster.test.yml
|
||||||
cd ansible
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Note:** All Ansible commands should be run from the project root directory.
|
||||||
|
|
||||||
## Full Deployment
|
## Full Deployment
|
||||||
|
|
||||||
### Deploy entire cluster with proxies (recommended for new setups):
|
### Deploy entire cluster with proxies (recommended for new setups):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook playbook-full-with-proxies.yml
|
ansible-playbook ansible/playbook-full-with-proxies.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
### Deploy cluster without proxies:
|
### Deploy cluster without proxies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook playbook-full.yml
|
ansible-playbook ansible/playbook-full.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
## Targeted Deployments
|
## Targeted Deployments
|
||||||
@ -36,13 +37,13 @@ ansible-playbook playbook-full.yml
|
|||||||
### Deploy only to master node:
|
### Deploy only to master node:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook playbook-master.yml --limit="af-test"
|
ansible-playbook ansible/playbook-master.yml --limit="af-test"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Deploy only to worker nodes:
|
### Deploy only to worker nodes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook playbook-worker.yml
|
ansible-playbook ansible/playbook-worker.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
## Deploy Specific Steps
|
## Deploy Specific Steps
|
||||||
@ -50,7 +51,7 @@ ansible-playbook playbook-worker.yml
|
|||||||
To start at a specific task (useful for debugging or partial deployments):
|
To start at a specific task (useful for debugging or partial deployments):
|
||||||
|
|
||||||
```bash
|
```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
|
## 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:
|
Run with dry-run and verbose output for debugging:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook playbook-full.yml --check --diff -vv
|
ansible-playbook ansible/playbook-full.yml --check --diff -vv
|
||||||
```
|
```
|
||||||
|
|
||||||
## DAGs Only Deployment
|
## DAGs Only Deployment
|
||||||
@ -66,7 +67,7 @@ ansible-playbook playbook-full.yml --check --diff -vv
|
|||||||
To update only DAG files and configurations:
|
To update only DAG files and configurations:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook playbook-dags.yml
|
ansible-playbook ansible/playbook-dags.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
## Vault Management
|
## Vault Management
|
||||||
|
|||||||
@ -14,7 +14,11 @@ RUN apt-get update && \
|
|||||||
python3-dev \
|
python3-dev \
|
||||||
wget \
|
wget \
|
||||||
tar \
|
tar \
|
||||||
xz-utils && \
|
xz-utils \
|
||||||
|
iputils-ping \
|
||||||
|
curl \
|
||||||
|
traceroute \
|
||||||
|
tcpdump && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/man /usr/share/doc /usr/share/doc-base
|
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
|
# Version: 2025-08-20-02
|
||||||
# This file contains custom hooks for the Airflow environment.
|
# This file contains custom hooks for the Airflow environment.
|
||||||
from airflow import settings
|
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':
|
if ti.dag_id == 'ytdlp_ops_worker_per_url':
|
||||||
# Safely access dag_run and conf. The ti.dag_run attribute may not be populated
|
# Query the DagRun from the DB using run_id to reliably get the conf.
|
||||||
# when the hook is called during TaskInstance creation.
|
# The ti.dag_run attribute is not always populated when the hook is called.
|
||||||
dag_run = getattr(ti, 'dag_run', None)
|
dag_run = session.query(DagRun).filter(DagRun.run_id == ti.run_id).first()
|
||||||
conf = getattr(dag_run, 'conf', {}) if dag_run else {}
|
conf = dag_run.conf if dag_run else {}
|
||||||
worker_queue = conf.get('worker_queue')
|
worker_queue = conf.get('worker_queue')
|
||||||
|
|
||||||
if 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
|
ti.queue = worker_queue
|
||||||
else:
|
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'
|
ti.queue = 'queue-dl'
|
||||||
|
|
||||||
# Register the hook only in appropriate contexts
|
# Register the hook only in appropriate contexts
|
||||||
|
|||||||
@ -4,9 +4,9 @@
|
|||||||
"host": "{{ hostvars[groups['airflow_master'][0]].ansible_host }}",
|
"host": "{{ hostvars[groups['airflow_master'][0]].ansible_host }}",
|
||||||
"login": "admin",
|
"login": "admin",
|
||||||
"password": "0153093693-0009",
|
"password": "0153093693-0009",
|
||||||
"port": 9000,
|
"port": 80,
|
||||||
"extra": {
|
"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",
|
"region_name": "us-east-1",
|
||||||
"aws_access_key_id": "admin",
|
"aws_access_key_id": "admin",
|
||||||
"aws_secret_access_key": "0153093693-0009",
|
"aws_secret_access_key": "0153093693-0009",
|
||||||
|
|||||||
@ -19,7 +19,7 @@ x-airflow-common:
|
|||||||
# This section is auto-generated by Ansible from the inventory.
|
# This section is auto-generated by Ansible from the inventory.
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
{% for host in groups['all'] %}
|
{% 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 %}
|
{% endfor %}
|
||||||
env_file:
|
env_file:
|
||||||
# The .env file is located in the project root (e.g., /srv/airflow_dl_worker),
|
# 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.
|
# This section is auto-generated by Ansible from the inventory.
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
{% for host in groups['all'] %}
|
{% 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 %}
|
{% endfor %}
|
||||||
env_file:
|
env_file:
|
||||||
# The .env file is located in the project root, one level above the 'configs' directory.
|
# The .env file is located in the project root, one level above the 'configs' directory.
|
||||||
@ -237,14 +237,15 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- proxynet
|
- proxynet
|
||||||
ports:
|
ports:
|
||||||
- "9000:9000"
|
- "80:80"
|
||||||
|
- "81:81"
|
||||||
volumes:
|
volumes:
|
||||||
- ./configs/nginx.conf:/etc/nginx/nginx.conf:ro
|
- ./configs/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
minio:
|
minio:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
test: ["CMD", "curl", "-f", "http://localhost:80/minio/health/live"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 5
|
retries: 5
|
||||||
@ -261,7 +262,7 @@ services:
|
|||||||
entrypoint: >
|
entrypoint: >
|
||||||
/bin/sh -c "
|
/bin/sh -c "
|
||||||
set -e;
|
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
|
# Retry loop for bucket creation
|
||||||
MAX_ATTEMPTS=10
|
MAX_ATTEMPTS=10
|
||||||
SUCCESS=false
|
SUCCESS=false
|
||||||
|
|||||||
@ -44,6 +44,7 @@ services:
|
|||||||
- ./.env # Path is relative to the project directory
|
- ./.env # Path is relative to the project directory
|
||||||
volumes:
|
volumes:
|
||||||
- context-data:/app/context-data
|
- context-data:/app/context-data
|
||||||
|
- ./logs/communication_logs:/app/communication_logs
|
||||||
{% if service_role != 'management' %}
|
{% if service_role != 'management' %}
|
||||||
# Mount the generated endpoints file to make it available to the server
|
# Mount the generated endpoints file to make it available to the server
|
||||||
- ./configs/camoufox_endpoints.json:/app/config/camoufox_endpoints.json:ro
|
- ./configs/camoufox_endpoints.json:/app/config/camoufox_endpoints.json:ro
|
||||||
|
|||||||
@ -2,7 +2,7 @@ events {
|
|||||||
worker_connections 1024;
|
worker_connections 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream {
|
http {
|
||||||
upstream minio_servers {
|
upstream minio_servers {
|
||||||
server minio:9000;
|
server minio:9000;
|
||||||
}
|
}
|
||||||
@ -12,12 +12,24 @@ stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 9000;
|
listen 80;
|
||||||
proxy_pass minio_servers;
|
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 {
|
server {
|
||||||
listen 9001;
|
listen 81;
|
||||||
proxy_pass minio_console_servers;
|
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-файла)
|
(Замените `"hostname"` на имя хоста из вашего inventory-файла)
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook ansible/playbooks/pause_worker.yml --limit "hostname"
|
ansible-playbook -i ansible/inventory.ini ansible/playbooks/pause_worker.yml --limit "hostname"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Возобновить работу воркера:**
|
**Возобновить работу воркера:**
|
||||||
```bash
|
```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)
|
## Механизм привязки воркеров к конкретным машинам (Worker Pinning / Affinity)
|
||||||
|
|||||||
@ -32,7 +32,7 @@ def dispatch_url_to_worker(**context):
|
|||||||
"""
|
"""
|
||||||
# --- Check for worker pause lock file ---
|
# --- Check for worker pause lock file ---
|
||||||
# This path must be consistent with the Ansible playbook.
|
# 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()
|
hostname = socket.gethostname()
|
||||||
if os.path.exists(lock_file_path):
|
if os.path.exists(lock_file_path):
|
||||||
logger.info(f"Worker '{hostname}' is paused. Lock file found at '{lock_file_path}'. Skipping URL pull.")
|
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."),
|
'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."),
|
'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': 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."),
|
'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."),
|
'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.
|
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
|
## Full Deployment
|
||||||
|
|
||||||
### Deploy entire cluster with proxies (recommended for new setups):
|
### Deploy entire cluster with proxies (recommended for new setups):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook playbook-full-with-proxies.yml
|
ansible-playbook ansible/playbook-full-with-proxies.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
### Deploy cluster without proxies:
|
### Deploy cluster without proxies:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook playbook-full.yml
|
ansible-playbook ansible/playbook-full.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
## Targeted Deployments
|
## Targeted Deployments
|
||||||
@ -21,13 +24,13 @@ ansible-playbook playbook-full.yml
|
|||||||
### Deploy only to master node:
|
### Deploy only to master node:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook playbook-master.yml --limit="af-test"
|
ansible-playbook ansible/playbook-master.yml --limit="af-test"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Deploy only to worker nodes:
|
### Deploy only to worker nodes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook playbook-worker.yml
|
ansible-playbook ansible/playbook-worker.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
## DAGs Only Deployment
|
## DAGs Only Deployment
|
||||||
@ -35,7 +38,7 @@ ansible-playbook playbook-worker.yml
|
|||||||
To update only DAG files and configurations:
|
To update only DAG files and configurations:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ansible-playbook playbook-dags.yml
|
ansible-playbook ansible/playbook-dags.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
## Managing Worker State (Pause/Resume)
|
## Managing Worker State (Pause/Resume)
|
||||||
@ -48,7 +51,7 @@ This command creates the lock file, causing the `ytdlp_ops_dispatcher` DAG to sk
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Replace "worker-hostname" with the target host from your inventory
|
# 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
|
### To Resume a Worker
|
||||||
@ -57,5 +60,5 @@ This command removes the lock file, allowing the worker to resume picking up tas
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Replace "worker-hostname" with the target host from your inventory
|
# 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_uid: 1003
|
||||||
airflow_worker_dir: /srv/airflow_dl_worker
|
airflow_worker_dir: /srv/airflow_dl_worker
|
||||||
ansible_user: alex_p
|
ansible_user: alex_p
|
||||||
|
camoufox_base_port: 9070
|
||||||
camoufox_base_vnc_port: 5901
|
camoufox_base_vnc_port: 5901
|
||||||
deploy_group: ytdl
|
deploy_group: ytdl
|
||||||
dir_permissions: '0755'
|
dir_permissions: '0755'
|
||||||
@ -39,4 +40,4 @@ shadowsocks_mode: tcp_and_udp
|
|||||||
shadowsocks_timeout: 20
|
shadowsocks_timeout: 20
|
||||||
ssh_user: alex_p
|
ssh_user: alex_p
|
||||||
ytdlp_base_port: 9090
|
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:
|
rsync_opts:
|
||||||
- "--exclude=__pycache__/"
|
- "--exclude=__pycache__/"
|
||||||
- "--exclude=*.pyc"
|
- "--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
|
- vim
|
||||||
- python3-pip
|
- python3-pip
|
||||||
- iputils-ping
|
- iputils-ping
|
||||||
|
- traceroute
|
||||||
|
- fail2ban
|
||||||
|
- conntrack
|
||||||
|
- tcpdump
|
||||||
state: present
|
state: present
|
||||||
update_cache: yes
|
update_cache: yes
|
||||||
|
|
||||||
|
|||||||
@ -149,9 +149,3 @@
|
|||||||
|
|
||||||
roles:
|
roles:
|
||||||
- ytdlp-worker
|
- 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:
|
tasks:
|
||||||
- name: "Create lock file to pause worker"
|
- name: "Create lock file to pause worker"
|
||||||
file:
|
file:
|
||||||
path: "{{ airflow_worker_dir }}/AIRFLOW.PREVENT_URL_PULL.lock"
|
path: "{{ airflow_worker_dir }}/inputfiles/AIRFLOW.PREVENT_URL_PULL.lockfile"
|
||||||
state: touch
|
state: touch
|
||||||
owner: "{{ ssh_user }}"
|
owner: "{{ ssh_user }}"
|
||||||
group: "{{ deploy_group }}"
|
group: "{{ deploy_group }}"
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
tasks:
|
tasks:
|
||||||
- name: "Archive lock file to resume worker"
|
- name: "Archive lock file to resume worker"
|
||||||
command: >
|
command: >
|
||||||
mv {{ airflow_worker_dir }}/AIRFLOW.PREVENT_URL_PULL.lock
|
mv {{ airflow_worker_dir }}/inputfiles/AIRFLOW.PREVENT_URL_PULL.lockfile
|
||||||
{{ 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) }}
|
{{ 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:
|
args:
|
||||||
removes: "{{ airflow_worker_dir }}/AIRFLOW.PREVENT_URL_PULL.lock"
|
removes: "{{ airflow_worker_dir }}/inputfiles/AIRFLOW.PREVENT_URL_PULL.lockfile"
|
||||||
become: yes
|
become: yes
|
||||||
|
|||||||
@ -218,6 +218,7 @@
|
|||||||
owner: "999" # UID for the 'postgres' user in the official postgres image
|
owner: "999" # UID for the 'postgres' user in the official postgres image
|
||||||
group: "999" # GID for the 'postgres' group in the official postgres image
|
group: "999" # GID for the 'postgres' group in the official postgres image
|
||||||
mode: '0700'
|
mode: '0700'
|
||||||
|
recurse: yes
|
||||||
become: yes
|
become: yes
|
||||||
|
|
||||||
- name: Set proper ownership and permissions on master logs directory contents
|
- 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)
|
- name: Ensure worker is not paused on deploy (remove .lock file)
|
||||||
file:
|
file:
|
||||||
path: "{{ airflow_worker_dir }}/AIRFLOW.PREVENT_URL_PULL.lock"
|
path: "{{ airflow_worker_dir }}/inputfiles/AIRFLOW.PREVENT_URL_PULL.lockfile"
|
||||||
state: absent
|
state: absent
|
||||||
become: yes
|
become: yes
|
||||||
|
|
||||||
- name: Clean up old renamed lock files (older than 7 days)
|
- name: Clean up old renamed lock files (older than 7 days)
|
||||||
ansible.builtin.find:
|
ansible.builtin.find:
|
||||||
paths: "{{ airflow_worker_dir }}"
|
paths: "{{ airflow_worker_dir }}/inputfiles"
|
||||||
patterns: "AIRFLOW.PREVENT_URL_PULL.lock.removed-*"
|
patterns: "AIRFLOW.PREVENT_URL_PULL.lockfile.removed-*"
|
||||||
age: "7d"
|
age: "7d"
|
||||||
use_regex: false
|
use_regex: false
|
||||||
register: old_lock_files
|
register: old_lock_files
|
||||||
@ -22,6 +22,24 @@
|
|||||||
become: yes
|
become: yes
|
||||||
when: old_lock_files.files | length > 0
|
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
|
- name: Check if YT-DLP worker deployment directory exists
|
||||||
stat:
|
stat:
|
||||||
path: "{{ airflow_worker_dir }}"
|
path: "{{ airflow_worker_dir }}"
|
||||||
@ -141,6 +159,26 @@
|
|||||||
become: yes
|
become: yes
|
||||||
become_user: "{{ ssh_user }}"
|
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"
|
- name: "Log: Building Camoufox (remote browser) image"
|
||||||
debug:
|
debug:
|
||||||
msg: "Building the Camoufox image locally. This image provides remote-controlled Firefox browsers for token generation."
|
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"
|
path: "/srv/shadowsocks-rust/docker-compose.proxies.yaml"
|
||||||
register: proxy_compose_file
|
register: proxy_compose_file
|
||||||
|
|
||||||
- name: "Log: Starting YT-DLP worker services"
|
- name: "Log: Starting all worker services"
|
||||||
debug:
|
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:
|
community.docker.docker_compose_v2:
|
||||||
project_src: "{{ airflow_worker_dir }}"
|
project_src: "{{ airflow_worker_dir }}"
|
||||||
files:
|
files:
|
||||||
- "configs/docker-compose-ytdlp-ops.yaml"
|
- "configs/docker-compose-ytdlp-ops.yaml"
|
||||||
|
- "configs/docker-compose.camoufox.yaml"
|
||||||
|
- "configs/docker-compose.airflow.yml"
|
||||||
state: present
|
state: present
|
||||||
remove_orphans: true
|
remove_orphans: true
|
||||||
pull: "{{ 'never' if fast_deploy | default(false) else 'missing' }}"
|
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(',') }}"
|
CAMOUFOX_PROXIES="{{ (worker_proxies | default([])) | join(',') }}"
|
||||||
VNC_PASSWORD="{{ vault_vnc_password }}"
|
VNC_PASSWORD="{{ vault_vnc_password }}"
|
||||||
CAMOUFOX_BASE_VNC_PORT={{ camoufox_base_vnc_port }}
|
CAMOUFOX_BASE_VNC_PORT={{ camoufox_base_vnc_port }}
|
||||||
CAMOUFOX_PORT={{ camoufox_port }}
|
CAMOUFOX_PORT={{ camoufox_base_port }}
|
||||||
|
|
||||||
# --- Account Manager Configuration ---
|
# --- Account Manager Configuration ---
|
||||||
ACCOUNT_ACTIVE_DURATION_MIN={{ account_active_duration_min | default(7) }}
|
ACCOUNT_ACTIVE_DURATION_MIN={{ account_active_duration_min | default(7) }}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
global_vars:
|
global_vars:
|
||||||
# Docker image versions
|
# 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"
|
airflow_image_name: "pangramia/ytdlp-ops-airflow:latest"
|
||||||
|
|
||||||
# Default ports
|
# Default ports
|
||||||
@ -9,6 +9,7 @@ global_vars:
|
|||||||
envoy_port: 9080
|
envoy_port: 9080
|
||||||
envoy_admin_port: 9901
|
envoy_admin_port: 9901
|
||||||
management_service_port: 9091
|
management_service_port: 9091
|
||||||
|
camoufox_base_port: 9070
|
||||||
camoufox_base_vnc_port: 5901
|
camoufox_base_vnc_port: 5901
|
||||||
|
|
||||||
# Default UID
|
# 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('--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('--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('--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('--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('--machine-id', help='Identifier for the client machine. Defaults to hostname.')
|
||||||
parser.add_argument('--verbose', action='store_true', help='Enable verbose output')
|
parser.add_argument('--verbose', action='store_true', help='Enable verbose output')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user