#!/bin/sh set -e # Only the web (gunicorn) container should run migrations / collectstatic. # The celery worker shares this image but skips DB schema mutation to avoid races. # # ⚠️ migrate 必须跨 pod 串行化(MySQL GET_LOCK):2026-07-07 事故——连推 3 个 commit 触发 # 3 轮 rolling 部署,多个新 pod 并发跑 migrate,数据迁移(积分 ×10 rescale)被交错重放 6 次, # 测试库单价被乘成 ×10⁶(¥2 → 2,000,000)。锁把并发压成串行;后到者拿到锁时迁移已被 # 先到者记录,migrate 自然 no-op。锁超时 600s 拿不到 → 快速失败重启,绝不裸跑。 case "$1" in gunicorn) echo "[entrypoint] running migrations (serialized via DB advisory lock)..." python - <<'PYEOF' import os import django os.environ.setdefault("DJANGO_SETTINGS_MODULE", "airshelf.settings.production") django.setup() from django.core.management import call_command from django.db import connection if connection.vendor == "mysql": with connection.cursor() as cursor: cursor.execute("SELECT GET_LOCK('airshelf_migrate', 600)") acquired = cursor.fetchone()[0] if not acquired: raise SystemExit("[entrypoint] FATAL: migrate advisory lock timeout (another pod stuck?)") try: call_command("migrate", interactive=False) finally: with connection.cursor() as cursor: cursor.execute("SELECT RELEASE_LOCK('airshelf_migrate')") else: call_command("migrate", interactive=False) PYEOF echo "[entrypoint] collecting static..." python manage.py collectstatic --noinput ;; esac exec "$@"