root: move database calls from ready() to dedicated startup signal (#9081)
* root: move database calls from ready() to dedicated startup signal Signed-off-by: Jens Langhammer <jens@goauthentik.io> * optimise gunicorn startup to only do DB code in one worker Signed-off-by: Jens Langhammer <jens@goauthentik.io> * always use 2 workers in compose Signed-off-by: Jens Langhammer <jens@goauthentik.io> * send startup signals for test runner Signed-off-by: Jens Langhammer <jens@goauthentik.io> * remove k8s import that isn't really needed Signed-off-by: Jens Langhammer <jens@goauthentik.io> * ci: bump nested actions Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix @reconcile_app not triggering reconcile due to changed functions Signed-off-by: Jens Langhammer <jens@goauthentik.io> * connect startup with uid Signed-off-by: Jens Langhammer <jens@goauthentik.io> * adjust some log levels Signed-off-by: Jens Langhammer <jens@goauthentik.io> * remove internal healthcheck we didn't really use it to do anything, and we shouldn't have to since the live/ready probes are handled by django anyways and so the container runtime will restart the server if needed Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add setproctitle for gunicorn and celery process titles Signed-off-by: Jens Langhammer <jens@goauthentik.io> * configure structlog early to use it Signed-off-by: Jens Langhammer <jens@goauthentik.io> * Revert "configure structlog early to use it" This reverts commit 16778fdbbca0f5c474d376c2f85c6f8032c06044. * Revert "adjust some log levels" This reverts commit a129f7ab6aecf27f1206aea1ad8384ce897b74ad. Signed-off-by: Jens Langhammer <jens@goauthentik.io> # Conflicts: # authentik/root/settings.py * optimize startup to not spawn a bunch of one-off processes Signed-off-by: Jens Langhammer <jens@goauthentik.io> * idk why this shows up Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@ -7,7 +7,6 @@ function log {
|
||||
|
||||
function wait_for_db {
|
||||
python -m lifecycle.wait_for_db
|
||||
python -m lifecycle.migrate
|
||||
log "Bootstrap completed"
|
||||
}
|
||||
|
||||
@ -65,7 +64,6 @@ if [[ "${AUTHENTIK_REMOTE_DEBUG}" == "true" ]]; then
|
||||
fi
|
||||
|
||||
if [[ "$1" == "server" ]]; then
|
||||
wait_for_db
|
||||
set_mode "server"
|
||||
# If we have bootstrap credentials set, run bootstrap tasks outside of main server
|
||||
# sync, so that we can sure the first start actually has working bootstrap
|
||||
@ -75,7 +73,6 @@ if [[ "$1" == "server" ]]; then
|
||||
fi
|
||||
run_authentik
|
||||
elif [[ "$1" == "worker" ]]; then
|
||||
wait_for_db
|
||||
set_mode "worker"
|
||||
check_if_root "python -m manage worker"
|
||||
elif [[ "$1" == "worker-status" ]]; then
|
||||
|
||||
@ -2,13 +2,11 @@
|
||||
|
||||
import os
|
||||
from hashlib import sha512
|
||||
from multiprocessing import cpu_count
|
||||
from os import makedirs
|
||||
from pathlib import Path
|
||||
from tempfile import gettempdir
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from kubernetes.config.incluster_config import SERVICE_HOST_ENV_NAME
|
||||
from prometheus_client.values import MultiProcessValue
|
||||
|
||||
from authentik import get_full_version
|
||||
@ -17,11 +15,18 @@ from authentik.lib.logging import get_logger_config
|
||||
from authentik.lib.utils.http import get_http_session
|
||||
from authentik.lib.utils.reflection import get_env
|
||||
from authentik.root.install_id import get_install_id_raw
|
||||
from lifecycle.migrate import run_migrations
|
||||
from lifecycle.wait_for_db import wait_for_db
|
||||
from lifecycle.worker import DjangoUvicornWorker
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from gunicorn.app.wsgiapp import WSGIApplication
|
||||
from gunicorn.arbiter import Arbiter
|
||||
|
||||
from authentik.root.asgi import AuthentikAsgi
|
||||
|
||||
wait_for_db()
|
||||
|
||||
_tmp = Path(gettempdir())
|
||||
worker_class = "lifecycle.worker.DjangoUvicornWorker"
|
||||
worker_tmp_dir = str(_tmp.joinpath("authentik_worker_tmp"))
|
||||
@ -35,17 +40,14 @@ bind = f"unix://{str(_tmp.joinpath('authentik-core.sock'))}"
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings")
|
||||
os.environ.setdefault("PROMETHEUS_MULTIPROC_DIR", prometheus_tmp_dir)
|
||||
|
||||
preload = True
|
||||
|
||||
max_requests = 1000
|
||||
max_requests_jitter = 50
|
||||
|
||||
logconfig_dict = get_logger_config()
|
||||
|
||||
# if we're running in kubernetes, use fixed workers because we can scale with more pods
|
||||
# otherwise (assume docker-compose), use as much as we can
|
||||
if SERVICE_HOST_ENV_NAME in os.environ:
|
||||
default_workers = 2
|
||||
else:
|
||||
default_workers = max(cpu_count() * 0.25, 1) + 1 # Minimum of 2 workers
|
||||
default_workers = 2
|
||||
|
||||
workers = CONFIG.get_int("web.workers", default_workers)
|
||||
threads = CONFIG.get_int("web.threads", 4)
|
||||
@ -100,6 +102,18 @@ def pre_fork(server: "Arbiter", worker: DjangoUvicornWorker):
|
||||
worker._worker_id = _next_worker_id(server)
|
||||
|
||||
|
||||
def post_worker_init(worker: DjangoUvicornWorker):
|
||||
"""Notify ASGI app that its started up"""
|
||||
# Only trigger startup DB logic on first worker
|
||||
# Startup code that imports code or is otherwise needed in every worker
|
||||
# does not use this signal, so we can skip this safely
|
||||
if worker._worker_id != 1:
|
||||
return
|
||||
app: "WSGIApplication" = worker.app
|
||||
root_app: "AuthentikAsgi" = app.callable
|
||||
root_app.call_startup()
|
||||
|
||||
|
||||
if not CONFIG.get_bool("disable_startup_analytics", False):
|
||||
env = get_env()
|
||||
should_send = env not in ["dev", "ci"]
|
||||
@ -129,3 +143,5 @@ if CONFIG.get_bool("remote_debug"):
|
||||
import debugpy
|
||||
|
||||
debugpy.listen(("0.0.0.0", 6800)) # nosec
|
||||
|
||||
run_migrations()
|
||||
|
||||
@ -68,7 +68,7 @@ def release_lock(cursor: Cursor):
|
||||
cursor.execute("SELECT pg_advisory_unlock(%s)", (ADV_LOCK_UID,))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
def run_migrations():
|
||||
conn = connect(
|
||||
dbname=CONFIG.get("postgresql.name"),
|
||||
user=CONFIG.get("postgresql.user"),
|
||||
@ -117,3 +117,7 @@ if __name__ == "__main__":
|
||||
)
|
||||
finally:
|
||||
release_lock(curr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_migrations()
|
||||
|
||||
@ -11,52 +11,61 @@ from redis.exceptions import RedisError
|
||||
|
||||
from authentik.lib.config import CONFIG
|
||||
|
||||
CONFIG.log("info", "Starting authentik bootstrap")
|
||||
|
||||
# Sanity check, ensure SECRET_KEY is set before we even check for database connectivity
|
||||
if CONFIG.get("secret_key") is None or len(CONFIG.get("secret_key")) == 0:
|
||||
CONFIG.log("info", "----------------------------------------------------------------------")
|
||||
CONFIG.log("info", "Secret key missing, check https://goauthentik.io/docs/installation/.")
|
||||
CONFIG.log("info", "----------------------------------------------------------------------")
|
||||
sysexit(1)
|
||||
def check_postgres():
|
||||
while True:
|
||||
try:
|
||||
conn = connect(
|
||||
dbname=CONFIG.get("postgresql.name"),
|
||||
user=CONFIG.get("postgresql.user"),
|
||||
password=CONFIG.get("postgresql.password"),
|
||||
host=CONFIG.get("postgresql.host"),
|
||||
port=CONFIG.get_int("postgresql.port"),
|
||||
sslmode=CONFIG.get("postgresql.sslmode"),
|
||||
sslrootcert=CONFIG.get("postgresql.sslrootcert"),
|
||||
sslcert=CONFIG.get("postgresql.sslcert"),
|
||||
sslkey=CONFIG.get("postgresql.sslkey"),
|
||||
)
|
||||
conn.cursor()
|
||||
break
|
||||
except OperationalError as exc:
|
||||
sleep(1)
|
||||
CONFIG.log("info", f"PostgreSQL connection failed, retrying... ({exc})")
|
||||
CONFIG.log("info", "PostgreSQL connection successful")
|
||||
|
||||
|
||||
while True:
|
||||
try:
|
||||
conn = connect(
|
||||
dbname=CONFIG.get("postgresql.name"),
|
||||
user=CONFIG.get("postgresql.user"),
|
||||
password=CONFIG.get("postgresql.password"),
|
||||
host=CONFIG.get("postgresql.host"),
|
||||
port=CONFIG.get_int("postgresql.port"),
|
||||
sslmode=CONFIG.get("postgresql.sslmode"),
|
||||
sslrootcert=CONFIG.get("postgresql.sslrootcert"),
|
||||
sslcert=CONFIG.get("postgresql.sslcert"),
|
||||
sslkey=CONFIG.get("postgresql.sslkey"),
|
||||
)
|
||||
conn.cursor()
|
||||
break
|
||||
except OperationalError as exc:
|
||||
sleep(1)
|
||||
CONFIG.log("info", f"PostgreSQL connection failed, retrying... ({exc})")
|
||||
CONFIG.log("info", "PostgreSQL connection successful")
|
||||
def check_redis():
|
||||
REDIS_PROTOCOL_PREFIX = "redis://"
|
||||
if CONFIG.get_bool("redis.tls", False):
|
||||
REDIS_PROTOCOL_PREFIX = "rediss://"
|
||||
REDIS_URL = (
|
||||
f"{REDIS_PROTOCOL_PREFIX}:"
|
||||
f"{quote_plus(CONFIG.get('redis.password'))}@{quote_plus(CONFIG.get('redis.host'))}:"
|
||||
f"{CONFIG.get_int('redis.port')}/{CONFIG.get('redis.db')}"
|
||||
)
|
||||
while True:
|
||||
try:
|
||||
redis = Redis.from_url(REDIS_URL)
|
||||
redis.ping()
|
||||
break
|
||||
except RedisError as exc:
|
||||
sleep(1)
|
||||
CONFIG.log("info", f"Redis Connection failed, retrying... ({exc})", redis_url=REDIS_URL)
|
||||
CONFIG.log("info", "Redis Connection successful")
|
||||
|
||||
REDIS_PROTOCOL_PREFIX = "redis://"
|
||||
if CONFIG.get_bool("redis.tls", False):
|
||||
REDIS_PROTOCOL_PREFIX = "rediss://"
|
||||
REDIS_URL = (
|
||||
f"{REDIS_PROTOCOL_PREFIX}:"
|
||||
f"{quote_plus(CONFIG.get('redis.password'))}@{quote_plus(CONFIG.get('redis.host'))}:"
|
||||
f"{CONFIG.get_int('redis.port')}/{CONFIG.get('redis.db')}"
|
||||
)
|
||||
while True:
|
||||
try:
|
||||
redis = Redis.from_url(REDIS_URL)
|
||||
redis.ping()
|
||||
break
|
||||
except RedisError as exc:
|
||||
sleep(1)
|
||||
CONFIG.log("info", f"Redis Connection failed, retrying... ({exc})", redis_url=REDIS_URL)
|
||||
CONFIG.log("info", "Redis Connection successful")
|
||||
|
||||
CONFIG.log("info", "Finished authentik bootstrap")
|
||||
def wait_for_db():
|
||||
CONFIG.log("info", "Starting authentik bootstrap")
|
||||
# Sanity check, ensure SECRET_KEY is set before we even check for database connectivity
|
||||
if CONFIG.get("secret_key") is None or len(CONFIG.get("secret_key")) == 0:
|
||||
CONFIG.log("info", "----------------------------------------------------------------------")
|
||||
CONFIG.log("info", "Secret key missing, check https://goauthentik.io/docs/installation/.")
|
||||
CONFIG.log("info", "----------------------------------------------------------------------")
|
||||
sysexit(1)
|
||||
check_postgres()
|
||||
check_redis()
|
||||
CONFIG.log("info", "Finished authentik bootstrap")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
wait_for_db()
|
||||
|
||||
@ -12,3 +12,5 @@ class DjangoUvicornWorker(UvicornWorker):
|
||||
"lifespan": "off",
|
||||
"ws": "wsproto",
|
||||
}
|
||||
|
||||
_worker_id: int
|
||||
|
||||
Reference in New Issue
Block a user