-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocker-compose.workers.yml
More file actions
189 lines (183 loc) · 8.58 KB
/
Copy pathdocker-compose.workers.yml
File metadata and controls
189 lines (183 loc) · 8.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
x-logging: &default-logging
driver: "local"
options:
max-size: "${LOG_MAX_SIZE:-150m}"
max-file: "${LOG_MAX_FILE:-5}"
services:
workerserver:
# JEDEN worker Celery obsluguje OBIE kolejki (celery + denorm). Wczesniej
# byly dwa osobne kontenery (workerserver-general + workerserver-denorm),
# kazdy forkowal `nproc` dzieci prefork (kazde = pelna kopia Django ~250 MB),
# wiec zjadaly ~2x. Konsolidacja oszczedza jedna kopie Django i polowe dzieci.
# Zadania kolejki `denorm` (`flush_single`) sa krotkie, wiec spokojnie dziela
# jeden worker z kolejka domyslna - bez dedykowanego serwera i bez priorytetu
# (kombu robi round-robin po kolejkach; to akceptowalne, patrz spec single-worker).
#
# CELERY_QUEUE=celery,denorm jest ustawione JAWNIE (a nie polegamy na nowym
# domyslnym entrypoincie obrazu), zeby konsolidacja dzialala plynnie takze na
# OBECNYM opublikowanym obrazie - inaczej do czasu wydania nowego obrazu kolejka
# denorm zostalaby bez konsumenta. Concurrency (domyslnie 75% rdzeni) i recykling
# procesow czyta `celery_tasks.py` (app.conf) z env DOPIERO na nowym obrazie;
# na starym te zmienne sa ignorowane (nieszkodliwie). Knoby: CELERY_WORKER_*.
image: iplweb/bpp_workerserver:${DOCKER_VERSION:-latest}
restart: always
logging: *default-logging
env_file: ${BPP_CONFIGS_DIR}/.env
environment:
CELERY_QUEUE: "celery,denorm"
# Recykling procesu-dziecka po przekroczeniu progu RSS (KB) - oddaje pamiec
# do OS, tnie narost dlugo zyjacego Pythona. Czytane przez celery_tasks.py
# (app.conf) na obrazie z czerwca 2026+. Nadpisywalne w .env.
CELERY_WORKER_MAX_MEMORY_PER_CHILD: "${CELERY_WORKER_MAX_MEMORY_PER_CHILD:-300000}"
# Opcjonalnie: jawny procent rdzeni (domyslnie 75% w obrazie) lub recykling
# po N zadaniach - odkomentuj/ustaw w .env w razie potrzeby:
# CELERY_WORKER_CONCURRENCY_PERCENT / CELERY_WORKER_MAX_TASKS_PER_CHILD.
volumes:
- staticfiles:/staticroot
- media:/mediaroot
depends_on:
# Musi czekać na appserver, gdyż appserver aktualizuje ewentualnie baze danych.
# Czy nawet w ogóle ją buduje...
appserver:
condition: service_healthy
# Twardy gating na broker (Redis pelni role brokera + result backend) -
# eliminuje startup race przy `up --force-recreate` (worker startowal
# zanim broker skonczyl handshake, dostawal RST i zostawal w stanie
# unhealthy bez wyjscia procesu).
redis:
condition: service_healthy
deploy:
resources:
limits:
# Fallback do starej nazwy WORKER_GENERAL_* (sprzed konsolidacji+renamu),
# zeby `git pull && make up` na starym .env zadzialal bez recznej edycji.
memory: ${WORKER_MEM_LIMIT:-${WORKER_GENERAL_MEM_LIMIT:-1536m}}
cpus: "${WORKER_CPU_LIMIT:-${WORKER_GENERAL_CPU_LIMIT:-2.0}}"
labels:
# Sidecar autoheal restartuje ten kontener gdy image-level healthcheck
# (`celery inspect ping`) zglosi unhealthy - typowo gdy proces zyje, ale
# kombu wisi w reconnect loop po zerwanym connection do brokera.
autoheal: "true"
ofelia.enabled: "true"
# Nocny restart zbija skumulowany memory leak Celery workera (Python long-running).
# kill 1 -> SIGTERM do PID 1 -> graceful shutdown -> restart:always wskrzesza.
ofelia.job-exec.restart_self.schedule: "0 5 5 * * *"
ofelia.job-exec.restart_self.command: "kill 1"
denorm-queue:
# run only SINGLE service, this is only a mechanism to pass
# PostgreSQL LISTEN to Celery queue
image: iplweb/bpp_denorm_queue:${DOCKER_VERSION:-latest}
logging: *default-logging
env_file: ${BPP_CONFIGS_DIR}/.env
# entrypoint: ["python", "src/manage.py", "denorm_queue"]
# healthcheck:
# test: ["CMD-SHELL", "pgrep -f 'denorm_queue' > /dev/null"]
# start_period: 10s
# interval: 30s
# timeout: 5s
# retries: 3
restart: always
depends_on:
workerserver:
condition: service_healthy
deploy:
resources:
limits:
memory: ${DENORM_QUEUE_MEM_LIMIT:-320m}
cpus: "${DENORM_QUEUE_CPU_LIMIT:-1.0}"
labels:
ofelia.enabled: "true"
# Restart najpozniej w staggered secie - po wszystkich pozostalych, zeby NOTIFY
# ze swiezo podniesionego workera mialy do czego trafic.
ofelia.job-exec.restart_self.schedule: "0 25 5 * * *"
ofelia.job-exec.restart_self.command: "kill 1"
workerserver-status:
image: iplweb/bpp_workerserver:${DOCKER_VERSION:-latest}
logging: *default-logging
env_file: ${BPP_CONFIGS_DIR}/.env
entrypoint: celery -A django_bpp.celery_tasks status
depends_on:
workerserver:
condition: service_healthy
profiles: ['manual']
celerybeat:
image: iplweb/bpp_beatserver:${DOCKER_VERSION:-latest}
logging: *default-logging
env_file: ${BPP_CONFIGS_DIR}/.env
restart: always
volumes:
- staticfiles:/staticroot
- media:/mediaroot
depends_on:
redis:
condition: service_healthy
appserver:
condition: service_started
healthcheck:
# Dyspozytor wstecznie zgodny z OBRAZEM (DOCKER_VERSION moze pinowac stary obraz):
# - NOWY obraz (czerwiec 2026+) ma /app/healthcheck_beat.py: lekka sonda sprawdza
# tylko swiezosc /tmp/celerybeat-heartbeat (HeartbeatScheduler dotyka go co tick),
# bez importu Django i bez sprawdzania redisa (redis ma wlasny healthcheck; beat
# i tak depend_on redis: service_healthy). ~30-50 ms -> celerybeat wstaje w
# ~kilkanascie s zamiast ~218 s.
# - STARY obraz nie ma tego pliku -> spadamy na healthcheck_broker.py (pelny
# cold-import django_bpp.celery_tasks + broker connect). Gdybysmy twardo wskazali
# nowy skrypt, stary obraz mialby brak pliku -> healthcheck zawsze fail ->
# autoheal restartowalby beata w kolko. Dlatego dispatch przez [ -f ].
# interval/timeout/start_period zostaja hojne, bo MUSZA obsluzyc stara, ciezka sonde
# (4-10 s pod obciazeniem). Na nowym obrazie dluzsze okno nie szkodzi - sukces sondy
# i tak natychmiast przelacza w healthy, niezaleznie od start_period.
test: ["CMD-SHELL", "if [ -f /app/healthcheck_beat.py ]; then exec python /app/healthcheck_beat.py; else exec python /app/healthcheck_broker.py; fi"]
interval: 10s
timeout: 10s
start_period: 300s
retries: 3
deploy:
resources:
limits:
memory: ${CELERYBEAT_MEM_LIMIT:-480m}
# Beat jest w spoczynku ~0% CPU, ale przy starcie sam cold-importuje caly Django
# (a stara fallback-sonda healthcheck_broker.py importuje go co interval). cpus to
# SUFIT (CFS quota), nie rezerwacja - 1.0 pozwala temu importowi burstnac do 1
# rdzenia, wiec beat bootuje szybciej i heartbeat (-> healthy) pojawia sie wczesniej.
# NIE odbiera quoty zadnej innej usludze (celerybeat nie jest w CPU_SERVICES
# configure-resources). Bylo 0.25 -> import ocieral sie o 10s timeout, start ~218s.
# Z nowa lekka sonda 0.25 by wystarczylo, ale 1.0 zostaje (przyspiesza boot, idle ~0%).
cpus: "${CELERYBEAT_CPU_LIMIT:-1.0}"
labels:
# Sidecar autoheal restartuje celerybeat gdy broker probe zglosi unhealthy
# (np. po zerwaniu polaczenia z brokerem w runtime - beat zyje, ale przestaje
# publikowac taski).
autoheal: "true"
ofelia.enabled: "true"
ofelia.job-exec.restart_self.schedule: "0 20 5 * * *"
ofelia.job-exec.restart_self.command: "kill 1"
flower:
image: mher/flower:2.0.1
restart: always
logging: *default-logging
env_file: ${BPP_CONFIGS_DIR}/.env
environment:
- CELERY_BROKER_URL=redis://redis:6379/${DJANGO_BPP_REDIS_DB_BROKER:-1}
- FLOWER_PORT=5555
- FLOWER_URL_PREFIX=/flower
# Ogranicza historie zadan trzymana w RAM, zeby limit 128m byl bezpieczny.
- FLOWER_MAX_TASKS=${FLOWER_MAX_TASKS:-10000}
depends_on:
redis:
condition: service_healthy
expose:
- 5555
deploy:
resources:
limits:
memory: ${FLOWER_MEM_LIMIT:-128m}
cpus: "${FLOWER_CPU_LIMIT:-0.5}"
labels:
ofelia.enabled: "true"
# Flower akumuluje historie zadan Celery; nocny restart zbija konsumpcje pamieci.
# mher/flower 2.0.1 opiera sie na python:3.11-slim - ma /bin/kill z procps,
# ale Docker exec uruchamia go jako root przez PID namespace kontenera,
# wiec "kill 1" wysyla SIGTERM do PID 1 bezposrednio (bez shell-a).
ofelia.job-exec.restart_self.schedule: "0 15 5 * * *"
ofelia.job-exec.restart_self.command: "kill 1"