diff --git a/docker-compose.yml b/docker-compose.yml index ee6384ae..aa4657ec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,14 +2,55 @@ version: '3.9' services: web: + container_name: web build: context: . dockerfile: ./Dockerfile - ports: - - "8000:8000" image: ghcr.io/procollab-github/api:latest restart: always env_file: - .env environment: - HOST: 0.0.0.0 \ No newline at end of file + HOST: 0.0.0.0 + grafana: + container_name: grafana + image: grafana/grafana:latest + ports: + - "3000" + volumes: + - grafana-data:/var/lib/grafana + - grafana-configs:/etc/grafana + environment: + - GF_SERVER_ROOT_URL=%(protocol)s://%(domain)s:%(http_port)s/grafana + - GF_SERVER_SERVE_FROM_SUB_PATH=true + prometheus: + container_name: prometheus + image: prom/prometheus:v2.36.0 + ports: + - "9090" + volumes: + - prom-data:/prometheus + - ./prometheus:/etc/prometheus + node-exporter: + image: prom/node-exporter:v1.3.1 + ports: + - "9100" + volumes: + - /proc:/host/proc:ro + - /sys:/host/sys:ro + - /:/rootfs:ro + command: + - '--path.procfs=/host/proc' + - '--path.sysfs=/host/sys' + - '--collector.filesystem.mount-points-exclude' + - '^/(sys|proc|dev|host|etc|rootfs/var/lib/docker/containers|rootfs/var/lib/docker/overlay2|rootfs/run/docker/netns|rootfs/var/lib/docker/aufs)($$|/)' + nginx: + container_name: nginx + build: ./nginx + ports: + - 8000:80 +volumes: + grafana-data: + grafana-configs: + prom-data: + prom-configs: diff --git a/nginx/Dockerfile b/nginx/Dockerfile new file mode 100644 index 00000000..29ffd3c9 --- /dev/null +++ b/nginx/Dockerfile @@ -0,0 +1,4 @@ +FROM nginx:1.21-alpine + +RUN rm /etc/nginx/conf.d/default.conf +COPY nginx.conf /etc/nginx/conf.d \ No newline at end of file diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 00000000..e6e2390c --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,19 @@ +server { + + listen 80; + + server_name api.procollab.ru; + client_max_body_size 100M; + + location / { + proxy_pass http://web:8000; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_redirect off; + } + + location /grafana { + proxy_pass http://grafana:3000; + proxy_set_header Host $host; + } +} \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 20cea1ce..ad34671a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -718,6 +718,20 @@ files = [ [package.dependencies] Django = ">=3.2" +[[package]] +name = "django-prometheus" +version = "2.3.1" +description = "Django middlewares to monitor your application with Prometheus.io." +optional = false +python-versions = "*" +files = [ + {file = "django-prometheus-2.3.1.tar.gz", hash = "sha256:f9c8b6c780c9419ea01043c63a437d79db2c33353451347894408184ad9c3e1e"}, + {file = "django_prometheus-2.3.1-py2.py3-none-any.whl", hash = "sha256:cf9b26f7ba2e4568f08f8f91480a2882023f5908579681bcf06a4d2465f12168"}, +] + +[package.dependencies] +prometheus-client = ">=0.7" + [[package]] name = "django-rest-passwordreset" version = "1.3.0" @@ -1385,6 +1399,20 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "prometheus-client" +version = "0.17.1" +description = "Python client for the Prometheus monitoring system." +optional = false +python-versions = ">=3.6" +files = [ + {file = "prometheus_client-0.17.1-py3-none-any.whl", hash = "sha256:e537f37160f6807b8202a6fc4764cdd19bac5480ddd3e0d463c3002b34462101"}, + {file = "prometheus_client-0.17.1.tar.gz", hash = "sha256:21e674f39831ae3f8acde238afd9a27a37d0d2fb5a28ea094f0ce25d2cbf2091"}, +] + +[package.extras] +twisted = ["twisted"] + [[package]] name = "psycopg2-binary" version = "2.9.6" @@ -2094,4 +2122,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "ef8563738ba478c84503d02564df0dcfa0beed07b4ccbfc364d641d9f0f2a0bf" +content-hash = "744dbc01831ebe828f058ad0d6ae74f2913a9e5cd63c2136f0eacf54b16649dc" diff --git a/procollab/settings.py b/procollab/settings.py index 1c8568a0..44133692 100644 --- a/procollab/settings.py +++ b/procollab/settings.py @@ -6,6 +6,7 @@ from decouple import config from sentry_sdk.integrations.django import DjangoIntegration + mimetypes.add_type("application/javascript", ".js", True) mimetypes.add_type("text/css", ".css", True) mimetypes.add_type("text/html", ".html", True) @@ -26,7 +27,9 @@ TAGGIT_CASE_INSENSITIVE = True CSRF_TRUSTED_ORIGINS = [ - "http://localhost", + "http://localhost:8000", + "http://127.0.0.1:8000", + "http://0.0.0.0:8000", "https://api.procollab.ru", "https://procollab.ru", "https://www.procollab.ru", @@ -40,7 +43,9 @@ "0.0.0.0", "api.procollab.ru", "app.procollab.ru", + "dev.procollab.ru", "procollab.ru", + "web", # From Docker ] PASSWORD_HASHERS = [ @@ -99,9 +104,11 @@ "drf_yasg", "channels", "taggit", + "django_prometheus", ] MIDDLEWARE = [ + "django_prometheus.middleware.PrometheusBeforeMiddleware", "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", @@ -112,6 +119,7 @@ "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "debug_toolbar.middleware.DebugToolbarMiddleware", + "django_prometheus.middleware.PrometheusAfterMiddleware", ] # CORS_ALLOWED_ORIGINS = [ @@ -169,14 +177,15 @@ if DEBUG: DATABASES = { "default": { - "ENGINE": "django.db.backends.sqlite3", + "ENGINE": "django_prometheus.db.backends.sqlite3", "NAME": "db.sqlite3", } } CACHES = { "default": { - "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "BACKEND": "django_prometheus.cache.backends.filebased.FileBasedCache", + "LOCATION": "/var/tmp/django_cache", } } @@ -215,7 +224,7 @@ DATABASES = { "default": { - "ENGINE": "django.db.backends.postgresql", + "ENGINE": "django_prometheus.db.backends.postgresql", "NAME": config("DATABASE_NAME", default="postgres", cast=str), "USER": config("DATABASE_USER", default="postgres", cast=str), "PASSWORD": config("DATABASE_PASSWORD", default="postgres", cast=str), @@ -313,3 +322,23 @@ SELECTEL_CONTAINER_PASSWORD = config( "SELECTEL_CONTAINER_PASSWORD", cast=str, default="PWD" ) + +PROMETHEUS_LATENCY_BUCKETS = ( + 0.01, + 0.025, + 0.05, + 0.075, + 0.1, + 0.25, + 0.5, + 0.75, + 1.0, + 2.5, + 5.0, + 7.5, + 10.0, + 25.0, + 50.0, + 75.0, + float("inf"), +) diff --git a/procollab/urls.py b/procollab/urls.py index afdd895d..4da47be5 100644 --- a/procollab/urls.py +++ b/procollab/urls.py @@ -51,6 +51,7 @@ path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), path("api/token/verify/", TokenVerifyView.as_view(), name="token_verify"), path("", include("metrics.urls", namespace="metrics")), + path("django_prometheus/", include("django_prometheus.urls")), ] if settings.DEBUG: diff --git a/prometheus/prometheus.yml b/prometheus/prometheus.yml new file mode 100644 index 00000000..35fe0993 --- /dev/null +++ b/prometheus/prometheus.yml @@ -0,0 +1,10 @@ +global: + scrape_interval: 15s + evaluation_interval: 15s + +scrape_configs: + - job_name: monitoring + metrics_path: /django_prometheus/metrics + static_configs: + - targets: + - web:8000 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 2fa10eaa..345711fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,6 +58,7 @@ python-magic = "^0.4.27" python-magic-bin = {version = "^0.4.14", markers = "sys_platform == 'win32'"} requests = "^2.31.0" coreapi = "^2.3.3" +django-prometheus = "^2.3.1" [build-system] diff --git a/users/migrations/0039_alter_customuser_first_name_and_more.py b/users/migrations/0039_alter_customuser_first_name_and_more.py new file mode 100644 index 00000000..e71839ed --- /dev/null +++ b/users/migrations/0039_alter_customuser_first_name_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.2.3 on 2023-07-25 20:49 + +from django.db import migrations, models +import users.validators + + +class Migration(migrations.Migration): + + dependencies = [ + ("users", "0038_alter_customuser_options_customuser_ordering_score"), + ] + + operations = [ + migrations.AlterField( + model_name="customuser", + name="first_name", + field=models.CharField( + max_length=255, validators=[users.validators.user_name_validator] + ), + ), + migrations.AlterField( + model_name="customuser", + name="last_name", + field=models.CharField( + max_length=255, validators=[users.validators.user_name_validator] + ), + ), + migrations.AlterField( + model_name="customuser", + name="patronymic", + field=models.CharField( + blank=True, + max_length=255, + null=True, + validators=[users.validators.user_name_validator], + ), + ), + ]