Skip to content

Commit f372474

Browse files
authored
Merge pull request #427 from sjoerdie/feature/celery-health-check
🩺 add celery healthcheck
2 parents a5a0a50 + e1a0036 commit f372474

File tree

4 files changed

+86
-6
lines changed

4 files changed

+86
-6
lines changed

Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ COPY ./bin/celery_worker.sh /celery_worker.sh
5757
COPY ./bin/celery_flower.sh /celery_flower.sh
5858
COPY ./bin/check_celery_worker_liveness.py ./bin/
5959
COPY ./bin/setup_configuration.sh /setup_configuration.sh
60-
RUN mkdir /app/log /app/config
60+
RUN mkdir /app/log /app/config /app/tmp
6161

6262
# copy frontend build statics
6363
COPY --from=frontend-build /app/src/objects/static /app/src/objects/static

bin/celery_worker.sh

+4-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ if [[ "$ENABLE_COVERAGE" ]]; then
1515
fi
1616

1717
echo "Starting celery worker $WORKER_NAME with queue $QUEUE"
18-
exec $_binary --workdir src --app objects.celery worker \
18+
exec $_binary --workdir src --app "objects.celery" worker \
1919
-Q $QUEUE \
2020
-n $WORKER_NAME \
2121
-l $LOGLEVEL \
2222
-O fair \
23-
-c $CONCURRENCY
23+
-c $CONCURRENCY \
24+
-E \
25+
--max-tasks-per-child=50

docker-compose.yml

+17-2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ services:
3636
- DEMO_TOKEN=demo-random-string
3737
- DEMO_PERSON=Demo
3838
- DEMO_EMAIL=demo@demo.local
39+
healthcheck:
40+
test: ["CMD", "python", "-c", "import requests; exit(requests.head('http://localhost:8000/admin/').status_code not in [200, 302])"]
41+
interval: 30s
42+
timeout: 5s
43+
retries: 3
44+
# This should allow for enough time for migrations to run before the max
45+
# retries have passed. This healthcheck in turn allows other containers
46+
# to wait for the database migrations.
47+
start_period: 30s
3948
ports:
4049
- 8000:8000
4150
depends_on:
@@ -59,9 +68,15 @@ services:
5968
build: *web_build
6069
environment: *web_env
6170
command: /celery_worker.sh
71+
healthcheck:
72+
test: ["CMD", "python", "/app/bin/check_celery_worker_liveness.py"]
73+
interval: 30s
74+
timeout: 5s
75+
retries: 3
76+
start_period: 10s
6277
depends_on:
63-
- db
64-
- redis
78+
web:
79+
condition: service_healthy
6580
volumes: *web_volumes
6681

6782
celery-flower:

src/objects/celery.py

+64-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,72 @@
1-
from celery import Celery
1+
from pathlib import Path
2+
3+
from django.conf import settings
4+
5+
from celery import Celery, bootsteps
6+
from celery.signals import setup_logging, worker_ready, worker_shutdown
27

38
from .setup import setup_env
49

510
setup_env()
611

712
app = Celery("objects")
813
app.config_from_object("django.conf:settings", namespace="CELERY")
14+
app.conf.ONCE = {
15+
"backend": "celery_once.backends.Redis",
16+
"settings": {
17+
"url": settings.CELERY_BROKER_URL,
18+
"default_timeout": 60 * 60, # one hour
19+
},
20+
}
21+
922
app.autodiscover_tasks()
23+
24+
25+
# Use django's logging settings as these are reset by Celery by default
26+
@setup_logging.connect()
27+
def config_loggers(*args, **kwargs):
28+
from logging.config import dictConfig
29+
30+
dictConfig(settings.LOGGING)
31+
32+
33+
HEARTBEAT_FILE = Path(settings.BASE_DIR) / "tmp" / "celery_worker_heartbeat"
34+
READINESS_FILE = Path(settings.BASE_DIR) / "tmp" / "celery_worker_ready"
35+
36+
37+
#
38+
# Utilities for checking the health of celery workers
39+
#
40+
class LivenessProbe(bootsteps.StartStopStep):
41+
requires = {"celery.worker.components:Timer"}
42+
43+
def __init__(self, worker, **kwargs):
44+
self.requests = []
45+
self.tref = None
46+
47+
def start(self, worker):
48+
self.tref = worker.timer.call_repeatedly(
49+
10.0,
50+
self.update_heartbeat_file,
51+
(worker,),
52+
priority=10,
53+
)
54+
55+
def stop(self, worker):
56+
HEARTBEAT_FILE.unlink(missing_ok=True)
57+
58+
def update_heartbeat_file(self, worker):
59+
HEARTBEAT_FILE.touch()
60+
61+
62+
@worker_ready.connect
63+
def worker_ready(**_):
64+
READINESS_FILE.touch()
65+
66+
67+
@worker_shutdown.connect
68+
def worker_shutdown(**_):
69+
READINESS_FILE.unlink(missing_ok=True)
70+
71+
72+
app.steps["worker"].add(LivenessProbe)

0 commit comments

Comments
 (0)