Saltar a contenido

Ажиллагаа

Байршуулалт, ажиглалт, нөөцлөлт. deploy/, .github/workflows/deploy.yml ба ажиллаж байгаа суулгацаас уншиж бичигдсэн.

Баримтын төв · Архитектур · Гарын авлага


Хаалттай хэрэглэгчийн бүртгэл

Gerege Nexus нь Gerege Eco System-ийн хаалттай суурь платформ. platform.access_mode=private тохиргоо registry.platform_settings хүснэгтэд хадгалагдана. Энэ нь .env хувьсагч биш бөгөөд deploy хийхэд хэвээр үлдэнэ. GET /api/v1/auth/sso/config хариуны access_mode нь private байх ёстой.

eID нь хүнийг таньж баталгаажуулна. Нэвтрэх эрхгүй хэрэглэгчид байгууллагын админ урилга эсвэл урьдчилсан бүртгэлээр эрх олгоно. Нэг хүнийг нэвтрүүлэхийн тул платформыг public горимд шилжүүлэхгүй. Операторын консолын эрх болон TOTP тохиргоо тусдаа хэвээр байна.

Үйлчилгээний хаягууд

Нэр Юу Ард нь
nexus.gerege.mn Платформ өөрөө — frontend ба /api/v1/* Next.js + Go
admin.nexus.gerege.mn Операторын консол — /api/platform/v1/* Мөн тэр хоёр
dwh.nexus.gerege.mn Дата агуулахын зураглал Статик, өөрийн контейнер
backups.nexus.gerege.mn Шифрлэгдсэн нөөцийн сан (MinIO) + тайлбар хуудас MinIO + nginx
monitor.nexus.gerege.mn Grafana Ажиглалтын стек
docs.nexus.gerege.mn Энэ суулгацын баримт Статик, MkDocs — docs/mkdocs/deploy.sh nexus-root

Энэ хост дээр open.gerege.mn мөн ажилладаг тул энэ суулгацын стекүүд өөрийн нэр, өөрийн loopback порттой: private_nexus_* контейнерууд, Grafana 3031, Alertmanager 9193, DWH 3019. MinIO нь хостын нэг сан — суулгац бүр өөрийн bucket-тэй (private-nexus).

Хоёр урсгал өөр origin дээр байгаа нь гоо зүйн биш: cookie нь hostname-аар хязгаарлагддаг тул тусдаа нэр нь тусдаа ambient authority гэсэн үг.

Платформын нүүр хуудас эдгээрийг карт болгож харуулна. Хаягууд нь кодод биш, суулгацын орчинд (SERVICE_URL_ADMIN, _DWH, _BACKUPS, _MONITOR, _DOCS, мөн үндэсний _EID): admin.nexus.gerege.mn бол энэ суулгацынх, өөр хэн ч биш. Тохируулаагүй үйлчилгээ зурагдахгүй, нэг ч тохируулаагүй бол хэсэг бүхэлдээ зурагдахгүй.

Консолын хаалга нь мөн платформын landing дизайнтай: нэвтрэх маягт нь hero дотор, доор нь эрхийн загвар ба impersonation-ий нөхцөлүүд. Session-гүй ирсэн хүн ард нь юу байгааг мэдэлгүй үлдэх ёсгүй — тэр хаяг юу болохыг хэлэх нь нууц задруулах биш, нэвтрэх шалгалт нь ард нь хэвээр байна.

Статик хуудсууд контейнерт

dwh.* нь deploy/docker-compose.dwh.yml-ийн nginx:alpine контейнерээс үйлчилнэ — read-only файлын систем, no-new-privileges, зөвхөн loopback. Хостын nginx-ээс шууд файл өгч ч болох байсан (docs.* тэгдэг) ч тэр төсөл дараа нь Metabase, Cube, Postgres нэмнэ: тэдгээр нь бүгд контейнер болох тул нэгдүгээрх нь ч контейнер байсан нь хожим stack-ийн хажууд зөөхийг compose файлд мөр нэмэх ажил болгоно.

Байршуулалт

Одоогийн private суулгац: https://nexus.gerege.mn, сервер 38.180.145.75, ажлын зам /opt/private-gerege-nexus, Compose project private-gerege-nexus. open.gerege.mn тусдаа стек хэвээр байна.

Нөөц Private Nexus
Контейнерийн угтвар private_nexus
Frontend / API порт 127.0.0.1:3010 / 127.0.0.1:8088
PostgreSQL / Redis порт 127.0.0.1:5433 / 127.0.0.1:6380
Өгөгдлийн volume private_nexus_postgres_data
Каталогийн volume private_nexus_catalog_cache
TLS ба nginx deploy/nginx/nexus-private.conf

GitHub secrets: DEPLOY_HOST, DEPLOY_USER, DEPLOY_SSH_KEY, PRODUCTION_ENV. Сүүлийнх нь бүрэн dotenv тохиргоо; бодит нууц утгыг git-д оруулахгүй. Үндсэн hostname болон admin.nexus.gerege.mn операторын консол ажиллана. PRODUCTION_ENV-д CONTROL_PLANE_HOST=admin.nexus.gerege.mn заавал байна; ингэснээр анхны setup операторын TOTP алхмыг харуулна. Бусад нэмэлт үйлчилгээний домэйнүүд энэ private суулгацын CI/CD-д холбогдоогүй.

main руу push хийхэд CI ажиллаж, зөвхөн амжилттай commit deploy хийнэ. Гараар gh workflow run deploy.yml ажиллуулахад мөн тухайн commit-ийн амжилттай CI-г шаарддаг. Нууц тохиргоо дутуу бол workflow алдаагаар зогсоно.

Дараагийн deploy бүр migration-аас өмнө .env, PostgreSQL dump, nginx тохиргоог серверийн backups/<UTC timestamp>/ хавтсанд хадгална. Image pull амжилтгүй бол одоогийн контейнеруудыг өөрчлөхгүй. Migration буцаахыг автоматаар оролдохгүй; шаардлагатай үед backup-ыг ашиглаж сэргээнэ.

CI ногоон болсны дараа deploy.yml:

ghcr.io-д backend ба frontend image бүтээж түлхэх
   ↓  (tag = commit sha, мөн latest)
хост руу ssh: compose файл хуулах → pull → migrate → restart

Хоёр зүйл зориуд:

  • Хувилбар нь image дотор ldflags-аар шатаагдана. Тэгэхгүй бол суулгац бүр өөрийн хувилбарыг мэдэхгүй болно — каталог "platform": ">=1.1.0" шаардахад хариулах зүйлгүй.
  • Хуучныг шинэ нь бэлэн болтол буулгахгүй. GHCR дөнгөж түлхсэн хувийн image-д denied гэж хариулах нь ховор биш бөгөөд нэг удаа тэр нь тухайн ажиллагааны өөрийнх нь түлхсэн image-ийн rollout-ыг унагаасан.

Тохиргоо CI-гаас ирнэ, хостоос биш

Production тохиргоог PRODUCTION_ENV secret-д хадгална. Workflow .env.next файлаар дамжуулж, тусгаарласан порт ба volume нэрийг шалгаад image татсаны дараа .env болгон идэвхжүүлнэ. Серверийн .env-д гараар хийсэн өөрчлөлт дараагийн deploy-д солигдох тул эхлээд GitHub secret-ийг шинэчилнэ.

Үндэсний интеграц, Google login, SMTP, нэмэлт үйлчилгээний хаягийг зөвхөн энэ суулгацад зориулсан credential болон callback тохиргоотой үед нэмнэ.

Ажиглалт

deploy/docker-compose.monitoring.yml — тусдаа стек, тусдаа сүлжээ, платформын сүлжээнд зөвхөн scrape хийхээр холбогдоно.

Хадгалах хугацаа
Prometheus 3.1 Хэмжүүр 60 хоног / 20 GB
Loki 3.4 Лог 31 хоног
Tempo 2.7 Trace 72 цаг
Alloy 1.7 Цуглуулагч —
Grafana 11.5 Дэлгэц —
Alertmanager 0.28 Сэрэмжлүүлэг —

Экспортерууд: node_exporter, cAdvisor, postgres_exporter, redis_exporter.

Trace 72 цаг байгаа шалтгаан: trace нь хүсэлт болсноос хойш минут, эсвэл цагийн дотор уншигддаг. Долоо хоногийн настай trace-ыг хэн ч нээдэггүй.

Хэмжүүр

Хэмжүүр бүр OpenTelemetry-ийн SDK-аар дамжиж, Prometheus exporter-ээр /metrics дээр гарна. HTTP-ийнх нь semantic convention-ыг дагана:

http_server_request_duration_seconds{http_request_method,http_route,http_response_status_code}

Хүсэлтийн тоо нь тусдаа counter биш — тэр гистограммын _count цуврал.

http.request.method нь хаалттай олонлог. Танихгүй үйл үг _OTHER болж нугалагдана: PROPFIND гэж дуудсан сканнер time series үүсгэх ёсгүй.

Гадаад систем бүр нэг histogram-аар хэмжигдэнэ — external_request_duration_seconds{system,operation,status} — системийн нэр нь мөн адил хаалттай олонлог, танихгүй нь other.

Exemplar

Trace асаалттай үед гистограммын сэмпл бүр trace_id авч явна. Латенси графикийн удаан цэг дээр дарахад Grafana тэр яг тэр хүсэлтийн trace-ыг нээнэ — «аль хүсэлт удаан байсан бэ» гэдгийг таамаглах хэрэггүй.

/metrics нь OpenMetrics хэлбэрээр үйлчилдэг байх ёстой, эс бөгөөс exemplar дамжихгүй.

Лог

Alloy Docker-ийн лог цуглуулж Loki руу өгнө. Backend нь slog-оор JSON бичдэг тул | json ажиллана.

Хоёр урхи, хоёулаа энэ суулгац дээр бодитоор гарсан:

  • LogQL-ийн шүүлт том жижиг үсэг ялгана. |= "audit" нь юу ч олохгүй — бичигдсэн зүйл нь "msg":"AUDIT_EVENT".
  • $__rate_interval нь зөвхөн Prometheus-ийнх. Loki дээр $__auto.

Мөн: Grafana нь панелийг панелийн датасурс дээр ажиллуулдаг, target-ийнх дээр биш. Хоёр нь зөрвөл LogQL нь Prometheus руу очиж parse алдаа өгнө.

Дэлгэцүүд

deploy/monitoring/grafana/dashboards/ — provisioning-оор ирдэг, гараар үүсгэдэггүй:

api-overview · infrastructure · logs · security · resilience · external-systems · monitoring-self

Сүүлийнх нь хамгийн чухал: ажиглалт өөрөө ажиллаж байгаа эсэх. Унтарсан Prometheus нь бүх зүйл хэвийн байгаа мэт харагддаг.

Сэрэмжлүүлэг

31 дүрэм, зургаан файлд. Хамрах хүрээ:

  • API — унтарсан, латенси, алдааны төсвийн шаталт (хурдан / удаан)
  • Дэд бүтэц — диск, санах ой, Postgres, Redis, контейнерын дахин эхлэл, TLS
  • Консол — нэвтрэлтийн бүтэлгүйтэл, түгжээ, break-glass ашиглалт, бүртгэгдээгүй бичилт
  • Гадаад систем — удаан, доройтсон, бүтэлгүй
  • Нөөцлөлт — бүтэлгүй, хуучирсан, хэзээ ч харагдаагүй, хостоос гараагүй
  • Ажиглалт өөрөө — Alertmanager мэдэгдэхгүй байна, дүрэм тооцоолж чадахгүй байна, Loki лог татгалзаж байна, target унтарсан

NexusTLSExpiryUnknown нь тусгай: тэр нь «гэрчилгээ дуусах гэж байна» гэсэн үг биш, «хэн ч хэмжихгүй байна» гэсэн үг. Хугацааг бичдэг cron ажил нь гараар суулгагддаг бөгөөд нэг хост дээр огт суулгагдаагүй байсан.

Брэндлэлт ба Монгол хэл

Ажиглалтын домэйн нь платформын нэг хэсэг мэт харагдах ёстой: bare домэйн дээр landing хуудас, Grafana дээр Gerege лого, нэр, tab icon, цагаан нэвтрэх карт, монгол хэл. Grafana OSS-д брэндлэх цэг байхгүй (white labeling нь Enterprise) тул бүгд nginx дээр хийгдэнэ:

MONITOR_DOMAIN=monitor.nexus.gerege.mn deploy/scripts/setup_monitor_branding.sh

Скрипт нь ажиллаж буй Grafana-аас хоёр webpack chunk-ийн нэрийг уншиж (нэр нь агуулгын hash агуулдаг), монгол орчуулгын chunk-ийг deploy/monitoring/grafana/branding/i18n/mn.txt-ээс барьж, хэлний жагсаалт дахь Svenska-г Монгол болгож, /var/www/monitor руу landing хуудас, хэв маяг, скрипт, логог тавиад nginx-ийн snippet-ийг үүсгэнэ. Төгсгөлд нь долоон шалгалтыг домэйнээр дамжуулан хийнэ — файл байрандаа байгаа эсэх биш, браузарт юу ирж байгааг.

Гурван зүйлийг санах:

  • Grafana шинэчлэх бүрд дахин ажиллуулна. chunk-ийн hash өөрчлөгдөхөд орлуулалт таарахаа болино; тэр үед швед хэл эргэж ирнэ, өөр юу ч эвдрэхгүй.
  • Монгол хэл нь sv-SE-ийн үүрэнд сууна (Grafana-д mn locale байхгүй). Тиймээс GRAFANA_DEFAULT_LANGUAGE=sv-SE нь зөвхөн скрипт ажилласан хостод зөв.
  • Орчуулга хэсэгчилсэн (255 түлхүүр). Дутуу түлхүүр англи руугаа буцдаг тул mn.txt-д мөр нэмээд скриптийг дахин ажиллуулахад л хангалттай.

Нөөцлөлт

deploy/scripts/backup.sh, cron дээр өдөр бүр.

pg_dump → хуучныг цэвэрлэх → platform_backups-д мөр бичих
        → node_exporter-ийн textfile руу хэмжүүр бичих
        → age-ээр шифрлэж off-site руу илгээх

Гурав дахь алхам нь консол уншдаг мөр. Дөрөв дэх нь Prometheus уншдаг — шөнө дунд хэн нэгэнд сэрэмжлүүлэг илгээж чадах цорын ганц хувилбар. Хэмжигдэхгүй нөөцлөлт нь нөөцлөлт байхгүйтэй бараг адил: cron-ий чимээгүй бүтэлгүйтэл нь сэргээх өдрөө л илэрдэг.

Хостод зөвхөн age-ийн нийтийн түлхүүр байна. Эвдэрсэн платформ өөрийн илгээсэн зүйлээ уншиж чадахгүй; хувийн түлхүүр нь операторт байна. Түүнийг хост дээр үлдээвэл шифрлэлт нь утгагүй; түүнийг алдвал нөөцлөлт нь утгагүй.

Off-site сан нь MinIO, объектын хувилбарлалт асаалттай, байршуулалтын түлхүүр нь зөвхөн нэмэх эрхтэй. Тэр нь эвдэрсэн хост нь өөрийн өмнөх нөөцлөлтүүдийг устгаж чадахгүй гэсэн үг.

Хэмжүүр:

nexus_backup_last_run_timestamp_seconds
nexus_backup_last_success_timestamp_seconds
nexus_backup_last_size_bytes
nexus_backup_last_ok
nexus_backup_offsite_ok

Бүтэлгүйтсэн ажиллагаа нь өмнөх амжилтын мөчийг хадгална — эс бөгөөс нэг бүтэлгүйтэл нь «хэзээ ч амжилттай болоогүй»-тэй ялгагдахаа болино. Тохируулаагүй суулгац дээр offsite_ok нь 0 хэвээр байна, тэр нь зөв: хуулбар өөр газар байхгүй гэдэг нь хэмжигдэх ёстой баримт.

Сэргээлт

Нөөцлөлт нь сэргээгдэх хүртэл нөөцлөлт биш. Шалгах арга:

age -d -i key.txt backup.sql.gz.age | gunzip > restore.sql
createdb restore_check && psql restore_check < restore.sql
psql restore_check -c "select count(*) from information_schema.tables
                        where table_schema in ('workspace','registry','operator')"

Хаягдах өгөгдлийн сан руу — ажиллаж байгаа руу биш.

nginx-ийн хоёр урхи

Хоёулаа энэ хост дээр бодитоор гарсан, хоёулаа хариу 200 хэвээр байхад чимээгүй ажилладаг. Тиймээс curl -I нь vhost нэмсний дараах заавал хийх алхам.

add_header өв залгамжлахгүй. Хүү блок дотор НЭГ ч add_header байвал дээд түвшнийхийг нь бүгдийг нь хаяна. location / дотор Cache-Control нэмсэн нь server түвшний дөрвөн толгойг — CSP, HSTS, X-Frame-Options, X-Content-Type-Options — чимээгүй унтраасан. Хуудас зөв харагдсаар, хариу 200 хэвээр; зөвхөн толгойг нь уншиж байж мэдэгдэнэ. Шийдэл нь бүх add_header-ийг нэг түвшинд байлгах.

Certbot HTTP/2 бичдэггүй. Тэр нь listen 443 ssl мөрийг өөрөө бичдэг ба протоколыг нэмдэггүй. Үр дүнд нь 2026-08-28-ны HTTP/2 шилжилтийн дараа үүсгэсэн vhost бүр — docs, backups, monitor, cp — HTTP/1.1 дээр үлдсэн байв. http2 on; нь тусдаа заавар байх ёстой: тэгвэл гэрчилгээ шинэчлэх бүрд алга болохгүй.

curl -s -o /dev/null -w "http/%{http_version} %{http_code}\n" https://<нэр>/
curl -sI https://<нэр>/ | grep -iE 'content-security|strict-transport|x-frame'

Олон байгууллагыг нэг дор нээх

Консолын маягт нэгийг нээдэг. Суулгац эхлэх өдөр — зохицуулагч хоёр зуун лицензтэй компанийг, яам харьяа агентлагуудаа оруулах — тэр нь ажлын урсгал биш, хэн ч зарцуулахгүй үдэш болно. tenant-bootstrap ч тусалдаггүй: тэр нь эхний ажиллуулалтын хэрэгсэл бөгөөд байгууллага үүссэний дараа зориуд татгалздаг.

POST /api/platform/v1/tenants/bulk
{"organisations":[{"name":"…","slug":"…","admin_email":"…","reason":"…"}, …]}

Гурван шинж чанар, ач холбогдлын дарааллаар:

  • Гүйлгээ БИШ. 137 дахь мөр унасан нь өмнөх 136 байгууллагыг устгах ёсгүй. Мөр бүр өөрийн аудитын бичлэгтэй, өөрийн үйлдэл — оператор маягтыг 200 удаа илгээсэнтэй яг адил.
  • Мөр бүр тайлагнана. Хариу нь тоо ч биш, эхний алдаа ч биш, үр дүнгийн жагсаалт. 200 мөр илгээсэн хүн алийг нь буугаагүйг, яагаад гэдгийг мэдэх ёстой.
  • Дахин илгээх нь аюулгүй. Байгаа slug нь exists, алдаа биш. Файл засагдаад дахин илгээгддэг; хоёр дахь ажиллуулалт нь шинэ хоёр мөрийг нуусан алдааны хана байвал гурав дахь удаа хэн ч илгээхгүй.

Нэг хүсэлтэд дээд тал нь 200 байгууллага. Энэ нь гүйцэтгэлийн хязгаар биш, дэлбэрэлтийн радиус: дуудагч талын давталтын алдаа нь татгалзсан хүсэлт байх ёстой болохоос, тус бүрдээ хоёр superadmin шаардах дөрвөн мянган байгууллага биш.

CSV, Excel, файл байхгүй. Консол JSON ярьдаг бөгөөд компанийн жагсаалт барьсан хүн түүнийг хөрвүүлж чадна; энд задлагч бичих нь кодчиллын алдаа амьдрах хоёр дахь газар болох бөгөөд доор нь энэ endpoint хэвээр хэрэгтэй.

Гараар хийгддэг зүйлс

Байршуулалт автомат, гэхдээ эдгээр нь биш. Шинэ хост дээр мартагддаг зүйлс — тэдгээрийн нууц бүр prod:/<суулгац> дээр байдаг тул шинийг зохиохын өмнө тэндээс хар:

  • backup.sh-ийн cron бичлэг
  • TLS дуусах хугацааг бичдэг cron ажил. Скрипт нь одоо репод байна — deploy/scripts/nexus-tls-expiry.sh, суулгах гурван мөр нь түүний толгойд. Өмнө нь энэ нь зөвхөн баримт доторх хуулж тавих блок байсан бөгөөд шинэ хост бүр түүнгүйгээр босдог байв: хэмжигдээгүй нь эрүүлтэй яг адил харагддаг
  • Docker 29 дээр cAdvisor-ын containerd тохиргоо (--containerd-namespace=moby ба pid: host) — үүнгүйгээр нэг л цуврал экспортлогдоно, мөн энэ нь дахин эхлүүлэхийг шаарддаг, дахин үүсгэхийг биш
  • Хуучин Docker image-ийн цэвэрлэгээ. ghcr-retention.yml нь GHCR талыг цэвэрлэдэг ч хостын дискийг цэвэрлэдэггүй: deploy бүр хоёр image нэмнэ ба тэдгээр нь өөрөө хэзээ ч явахгүй. Гарын авлага.

Дэлгэрэнгүй алхмуудыг Гарын авлага.