diff --git a/docs/architecture/README.md b/docs/architecture/README.md new file mode 100644 index 0000000..cab1e2e --- /dev/null +++ b/docs/architecture/README.md @@ -0,0 +1,94 @@ +# Модель архитектуры в нотации ArchiMate + +Модель контура **УГМК** (`d8-ugmk-prod`) в двух форматах — содержимое одинаковое: + +| Файл | Формат | Чем открывать | +|---|---|---| +| `ugmk-technology.archimate` | нативный формат [Archi](https://www.archimatetool.com/) | десктопный Archi: File → Open | +| `ugmk-technology.xml` | The Open Group ArchiMate Model Exchange File Format 3.1 | любой инструмент с импортом Open Exchange, в том числе archi-online.com: кнопка **Import, save, and create models** → Open Exchange | +| `preview.html` | SVG-рендер всех представлений | любой браузер, без установки чего-либо | + +`ugmk-technology.xml` проверен на соответствие официальной схеме `archimate3_Diagram.xsd` — валидатор ошибок не нашёл. Если сторонний инструмент не берёт нативный `.archimate`, используйте его. + +Отличие обменной версии: вложенность узлов на схемах развёрнута в плоский список с абсолютными координатами (в обменном формате трактовка координат вложенного узла неоднозначна между реализациями). Визуально раскладка та же, но компоненты внутри кластера не будут «вложены» в него как дочерние — при перетаскивании контейнера содержимое останется на месте. + +Сейчас в модели проработан **технологический слой**. Прикладной слой присутствует только как потребители технологических сервисов — межсервисные REST-связи приложений и бизнес-слой будут добавлены отдельно. + +## Состав + +70 элементов, 153 отношения, 9 представлений. + +| Представление | Что показывает | +|---|---| +| T1 | Кластер, ПО внутри него, внешние узлы, технологические сервисы. Без приложений — обзорная схема | +| T2 | Маршрутизация HTTPS — 21 приложение | +| T3 | Аутентификация OIDC — 10 | +| T4 | Управление секретами — 25 | +| T5 | Реляционное хранилище — 23 | +| T6 | Кэш — 3 | +| T7 | Событийная шина — 12 | +| T8 | Очередь сообщений AMQP — 11 | +| T9 | Объектное хранилище — 13 | + +Разбиение на представления по сервисам сделано осознанно: все 118 связей «сервис → приложение» на одной схеме нечитаемы. В модели они лежат одним набором, представления — это срезы. + +Метамодель намеренно урезана. Технологический слой: `Node`, `SystemSoftware`, `TechnologyService`, `Artifact`. Прикладной: `ApplicationComponent`. Отношения: `Composition`, `Realization`, `Serving`, `Assignment`. Не используются `Device`, `Path`, `CommunicationNetwork`, `TechnologyFunction` — в этом контуре они не несут информации. + +## Откуда взяты связи + +Не нарисованы от руки. Выведены из манифестов, поэтому проверяемы: + +| Связь | Источник | +|---|---| +| Состав кластера | `clusters/d8-ugmk-prod/kustomization.yaml` | +| Версии чартов | `infrastructure//base/helmrelease.yaml` | +| Приложение → PostgreSQL | env `POSTGRES_*`, `DB__HOST` или путь Vault `secrets/data/apps//postgres` | +| Приложение → RabbitMQ | env `RABBITMQ*`, `AMQP__*` | +| Приложение → Kafka | env `KAFKA*`, `BOOTSTRAP_SERVERS` | +| Приложение → S3 | env `S3__*`, `S3_ENDPOINT`, `S3_ACCESS`, `S3_BUCKET` | +| Приложение → Redis | env `REDIS_HOST`, `REDIS__*` | +| Приложение → Vault | аннотации `vault.hashicorp.com/agent-inject*` | +| Приложение → Zitadel | env `ZITADEL*`, `JWKS`, `OIDC`, секрет `jwt-public` | +| Приложение → Istio | `service:` в маршрутах `infrastructure/istio-config/d8-ugmk-prod/istio-config.yaml` (закомментированные не считаются) | + +Сканируются `*.yaml` в `apps//base/` и `apps//d8-ugmk-prod/`; документация (`*.md`) игнорируется, иначе прозаические упоминания переменных дают ложные срабатывания. + +## Что вскрылось при построении + +- **Camunda развёрнута, но потребителей нет.** `camunda-contour` 11.0.14 в контуре есть, наружу опубликованы Operate, Tasklist, Optimize, Identity, Keycloak — но ни одно приложение не сконфигурировано на Zeebe. Единственные ссылки на Camunda в `apps/` — оверлеи `cde/brusnika-prod` и `cde/brusnika-stage`. +- **Четыре приложения ни к чему не подключены**: `projects`, `prescriptions`, `cross-section`, `faas` — не используют ни один технологический сервис и не имеют маршрута в `istio-config`. В модели они есть, но ни в одно представление не попали. Для сравнения: в `yc-ecp` маршрут `/projects/static/` существует. +- **Наблюдаемости в контуре нет** — ни vmstack, ни prometheus-stack, ни openobserve, ни otel, в отличие от Brusnika и WB. Если мониторинг там есть, он живёт вне этого репозитория. +- **Сертификаты самоподписанные** — `ClusterIssuer sarex-selfsigned`, не Let's Encrypt. +- **Redis только у `pm`.** В остальных приложениях `REDIS_HOST` встречается лишь в текстах `CONFIGURATION.md`. +- **Ingress прибит к одному узлу** `um-sarex-k8s-frontend-01` через `nodeSelector` — единственная точка отказа внешнего трафика. +- **S3 внешний** (`s3.uralmine.com`), MinIO в контуре не разворачивается. + +## Скрипты + +`scan_ugmk.py` — строит матрицу «приложение → технологический сервис» и кладёт в `matrix.json`. Полезен, когда состав контура изменился: перезапустить и сравнить. + +``` +py scan_ugmk.py matrix.json +``` + +`build_archimate.py` — одноразовая генерация модели из `matrix.json`. + +``` +py build_archimate.py ugmk-technology.archimate +``` + +`to_open_exchange.py` — конвертирует нативный файл в обменный формат. Запускать после каждой правки модели, иначе `.xml` разъедется с `.archimate`. + +``` +py to_open_exchange.py ugmk-technology.archimate ugmk-technology.xml +``` + +`render_preview.py` — рисует все представления в один HTML со встроенным SVG. Пересобирать вместе с обменным форматом. + +``` +py render_preview.py ugmk-technology.archimate preview.html +``` + +**Внимание:** после того как модель отредактирована в Archi, источником истины становится `.archimate`, а повторный запуск генератора затрёт расстановку элементов. Пересобирать целиком имеет смысл только при перестройке модели с нуля. + +Требуется Python 3, внешних зависимостей нет. На этой машине запускать через `py`, а не `python`. diff --git a/docs/architecture/build_archimate.py b/docs/architecture/build_archimate.py new file mode 100644 index 0000000..e6cf31e --- /dev/null +++ b/docs/architecture/build_archimate.py @@ -0,0 +1,255 @@ +"""Собирает .archimate (формат Archi 5) для технологического слоя контура УГМК.""" +import json, pathlib, sys, io +from xml.sax.saxutils import escape, quoteattr + +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") +HERE = pathlib.Path(__file__).parent +DATA = json.loads((HERE / "matrix.json").read_text(encoding="utf-8")) +OUT = pathlib.Path(sys.argv[1]) + +elements = [] # (folder, id, type, name, doc) +relations = [] # (id, type, src, tgt) +_rc = [0] + +def el(folder, eid, etype, name, doc=""): + elements.append((folder, eid, etype, name, doc)); return eid + +def rel(rtype, src, tgt): + _rc[0] += 1 + rid = f"rel-{_rc[0]:03d}" + relations.append((rid, rtype, src, tgt)); return rid + +REL_INDEX = {} +def R(rtype, src, tgt): + REL_INDEX[(rtype, src, tgt)] = rel(rtype, src, tgt); return REL_INDEX[(rtype, src, tgt)] + +# ---------------------------------------------------------------- технологический слой +T = "technology" +nd_cluster = el(T, "nd-cluster", "Node", "Кластер d8-ugmk-prod (Deckhouse)", + "clusters/d8-ugmk-prod/kustomization.yaml. GitOps через FluxCD v2.") +nd_front = el(T, "nd-frontend", "Node", "um-sarex-k8s-frontend-01", + "Выделенный frontend-узел. К нему прибит ingress-gateway через nodeSelector: " + "clusters/d8-ugmk-prod/istio-ingress/ingress-controller.yaml") +nd_s3 = el(T, "nd-s3", "Node", "s3.uralmine.com — объектное хранилище УГМК", + "Внешнее по отношению к кластеру. MinIO в контуре не разворачивается.") +nd_nfs = el(T, "nd-nfs", "Node", "NFS-хранилище (StorageClass nfs01)", + "Единственный storageClass в контуре, 9 упоминаний в манифестах.") +nd_gitea = el(T, "nd-gitea", "Node", "Gitea 10.222.255.165:3000 (infra/iac)", + "Источник для Flux: clusters/d8-ugmk-prod/flux-system/gotk-sync.yaml") +nd_cr = el(T, "nd-cr", "Node", "cr.yandex/crp3ccidau046kdj8g9q", + "Реестр чартов (OCI) и образов контейнеров.") + +SS = [ + ("ss-istio-gw", "Istio Ingress Gateway", "Deckhouse IngressIstioController «sarex», inlet NodePort 30080/30443. clusters/d8-ugmk-prod/istio-ingress/ingress-controller.yaml"), + ("ss-istio-mesh", "Istio Service Mesh", "chart istio-config-contour 0.1.0. Gateway, VirtualService, RequestAuthentication."), + ("ss-certmanager", "cert-manager", "ClusterIssuer sarex-selfsigned — сертификаты самоподписанные, не Let's Encrypt. Wildcard *.uralmine.com и *.sarex-k8s.uralmine.com."), + ("ss-zitadel", "Zitadel", "chart idp-contour 4.12.13, ns zitadel."), + ("ss-vault", "Vault + Agent Injector", "chart vault-contour 0.2.3, ns vault. Единственный механизм доставки секретов, путь secrets/data/apps//postgres."), + ("ss-postgres", "PostgreSQL", "chart postgresql-contour 17.0.7, ns postgresql. PVC 20Gi на nfs01."), + ("ss-redis", "Redis", "Разворачивается только в ns pm: apps/pm/d8-ugmk-prod/redis.yaml."), + ("ss-kafka", "Kafka", "chart kafka-contour 0.1.15, ns kafka."), + ("ss-rabbit", "RabbitMQ", "chart rabbitmq-contour 15.2.4, ns rabbitmq. Публикуется наружу на rabbitmq.sarex-k8s.uralmine.com."), + ("ss-camunda", "Camunda Platform", "chart camunda-contour 11.0.14, ns camunda. ВНИМАНИЕ: ни одно приложение контура на неё не сконфигурировано — потребителей нет."), +] +for sid, name, doc in SS: + el(T, sid, "SystemSoftware", name, doc); R("Composition", nd_cluster, sid) + +CAMUNDA_SUB = ["Zeebe", "Zeebe Gateway", "Operate", "Tasklist", "Optimize", "Identity", "Connectors"] +cam_ids = [] +for n in CAMUNDA_SUB: + cid = "ss-cam-" + n.lower().replace(" ", "-") + el(T, cid, "SystemSoftware", n, "infrastructure/camunda/d8-ugmk-prod/camunda.yaml") + R("Composition", "ss-camunda", cid); cam_ids.append(cid) + +ar_charts = el(T, "ar-charts", "Artifact", "Helm-чарты *-contour (OCI)") +ar_images = el(T, "ar-images", "Artifact", "Образы контейнеров") +R("Composition", nd_cr, ar_charts); R("Composition", nd_cr, ar_images) + +# технологические сервисы: id -> (имя, кто реализует, ключ в матрице) +TS = [ + ("ts-ingress", "Маршрутизация HTTPS-трафика", ["ss-istio-gw", "ss-istio-mesh"], "istio"), + ("ts-tls", "Выпуск TLS-сертификатов", ["ss-certmanager"], None), + ("ts-oidc", "Аутентификация OIDC", ["ss-zitadel"], "oidc"), + ("ts-secrets", "Управление секретами", ["ss-vault"], "vault"), + ("ts-rdbms", "Реляционное хранилище", ["ss-postgres"], "postgres"), + ("ts-cache", "Кэш", ["ss-redis"], "redis"), + ("ts-events", "Событийная шина", ["ss-kafka"], "kafka"), + ("ts-amqp", "Очередь сообщений AMQP", ["ss-rabbit"], "rabbitmq"), + ("ts-bpmn", "Оркестрация процессов BPMN", ["ss-camunda"], "camunda"), + ("ts-object", "Объектное хранилище", ["nd-s3"], "s3"), +] +for tid, name, realizers, _ in TS: + el(T, tid, "TechnologyService", name) + for r in realizers: + R("Realization", r, tid) + +R("Assignment", nd_front, "ss-istio-gw") +R("Serving", "ts-tls", "ss-istio-gw") +R("Serving", nd_nfs, "ss-postgres") +R("Serving", nd_gitea, nd_cluster) +R("Serving", nd_cr, nd_cluster) + +# ---------------------------------------------------------------- прикладной слой +app_id = {a: "ac-" + a for a in DATA["apps"]} +for a in DATA["apps"]: + el("application", app_id[a], "ApplicationComponent", a, f"apps/{a}/d8-ugmk-prod/") + +consumers = {} +for tid, _, _, key in TS: + if key is None: + consumers[tid] = []; continue + if key == "istio": + lst = [a for a in DATA["apps"] if a in DATA["exposed"]] + else: + lst = [a for a in DATA["apps"] if DATA["matrix"][a][key]] + consumers[tid] = lst + for a in lst: + R("Serving", tid, app_id[a]) + +# ---------------------------------------------------------------- представления +views = [] + +def view(name, doc=""): + v = {"id": f"view-{len(views)+1}", "name": name, "doc": doc, "objects": [], "conns": []} + views.append(v); return v + +_oc = [0] +def obj(v, eid, x, y, w, h, parent=None): + _oc[0] += 1 + o = {"id": f"do-{_oc[0]:04d}", "el": eid, "b": (x, y, w, h), "children": [], "src": [], "tgt": []} + (parent["children"] if parent else v["objects"]).append(o) + return o + +def conn(v, rtype, so, to): + key = (rtype, so["el"], to["el"]) + if key not in REL_INDEX: + raise KeyError(f"нет отношения в модели: {key}") + _oc[0] += 1 + cid = f"cn-{_oc[0]:04d}" + so["src"].append({"id": cid, "rel": REL_INDEX[key], "tgt": to["id"]}) + to["tgt"].append(cid) + +# --- T1: инфраструктура без приложений +v1 = view("T1. Технологический слой — контур УГМК", + "Что развёрнуто в кластере d8-ugmk-prod и какие технологические сервисы это даёт. " + "Приложения-потребители вынесены в отдельные представления T2–T9.") +o_ts = {} +for i, (tid, _, _, _) in enumerate(TS): + o_ts[tid] = obj(v1, tid, 20 + i * 165, 30, 150, 60) + +o_cluster = obj(v1, nd_cluster, 20, 140, 1270, 400) +o_ss = {} +row1 = ["ss-istio-gw", "ss-istio-mesh", "ss-certmanager", "ss-zitadel", "ss-vault"] +for i, sid in enumerate(row1): + o_ss[sid] = obj(v1, sid, 20 + i * 240, 45, 220, 70, o_cluster) +row2 = ["ss-postgres", "ss-redis", "ss-kafka", "ss-rabbit"] +for i, sid in enumerate(row2): + o_ss[sid] = obj(v1, sid, 20 + i * 240, 145, 220, 70, o_cluster) +o_ss["ss-camunda"] = obj(v1, "ss-camunda", 20, 245, 1230, 130, o_cluster) +for i, cid in enumerate(cam_ids): + obj(v1, cid, 10 + i * 174, 50, 162, 60, o_ss["ss-camunda"]) + +o_s3 = obj(v1, nd_s3, 1320, 140, 330, 70) +o_nfs = obj(v1, nd_nfs, 1320, 230, 330, 70) +o_gitea = obj(v1, nd_gitea, 1320, 320, 330, 70) +o_cr = obj(v1, nd_cr, 1320, 410, 330, 130) +obj(v1, ar_charts, 15, 50, 145, 60, o_cr) +obj(v1, ar_images, 170, 50, 145, 60, o_cr) +o_front = obj(v1, nd_front, 20, 570, 300, 60) + +for tid, _, realizers, _ in TS: + for r in realizers: + conn(v1, "Realization", o_ss.get(r) or o_s3, o_ts[tid]) +conn(v1, "Assignment", o_front, o_ss["ss-istio-gw"]) +conn(v1, "Serving", o_ts["ts-tls"], o_ss["ss-istio-gw"]) +conn(v1, "Serving", o_nfs, o_ss["ss-postgres"]) +conn(v1, "Serving", o_gitea, o_cluster) +conn(v1, "Serving", o_cr, o_cluster) + +# --- T2..T9: потребители каждого сервиса +PER_ROW = 6 +for tid, tname, realizers, key in TS: + lst = consumers[tid] + if not lst: + continue + v = view(f"T{len(views)+1}. Потребители: {tname}", + f"{len(lst)} из {len(DATA['apps'])} приложений контура. Связи выведены из манифестов " + f"apps/*/base и apps/*/d8-ugmk-prod.") + ots = obj(v, tid, 20, 20, 260, 60) + oss = [obj(v, r, 320 + i * 280, 20, 260, 60) for i, r in enumerate(realizers)] + for o in oss: + conn(v, "Realization", o, ots) + oapps = [] + for i, a in enumerate(lst): + oapps.append(obj(v, app_id[a], 20 + (i % PER_ROW) * 190, 150 + (i // PER_ROW) * 80, 170, 55)) + for oa in oapps: + conn(v, "Serving", ots, oa) + +# ---------------------------------------------------------------- сериализация +FOLDERS = [("Strategy", "strategy"), ("Business", "business"), ("Application", "application"), + ("Technology & Physical", "technology"), ("Motivation", "motivation"), + ("Implementation & Migration", "implementation_migration"), ("Other", "other"), + ("Relations", "relations"), ("Views", "diagrams")] + +def emit_obj(o, ind): + p = " " * ind + tgt = f' targetConnections={quoteattr(" ".join(o["tgt"]))}' if o["tgt"] else "" + x, y, w, h = o["b"] + # Порядок и имена — как их пишет сам Archi: , затем bounds, + # sourceConnection, вложенные . Имена EClass в метамодели + # (DiagramModelArchimateObject) в XML не используются. + s = f'{p}\n' + s += f'{p} \n' + for c in o["src"]: + s += (f'{p} \n') + for c in o["children"]: + s += emit_obj(c, ind + 2) + s += f'{p}\n' + return s + +out = ['', + '', + ' Технологический слой контура d8-ugmk-prod, выведенный из репозитория infra/iac. ' + 'Прикладной слой присутствует только как потребители технологических сервисов — ' + 'межсервисные связи приложений моделируются отдельно.'] + +for fname, ftype in FOLDERS: + inner = "" + if ftype in ("technology", "application"): + for folder, eid, etype, name, doc in elements: + if folder != ftype: + continue + if doc: + inner += (f' \n' + f' {escape(doc)}\n' + f' \n') + else: + inner += f' \n' + elif ftype == "relations": + for rid, rtype, src, tgt in relations: + inner += (f' \n') + elif ftype == "diagrams": + for v in views: + inner += f' \n' + if v["doc"]: + inner += f' {escape(v["doc"])}\n' + for o in v["objects"]: + inner += emit_obj(o, 6) + inner += ' \n' + if inner: + out.append(f' \n{inner} ') + else: + out.append(f' ') + +out.append('') +OUT.write_text("\n".join(out) + "\n", encoding="utf-8") + +print(f"записано: {OUT}") +print(f"элементов: {len(elements)} отношений: {len(relations)} представлений: {len(views)}") +for v in views: + n_obj = sum(1 + len(o["children"]) + sum(len(c["children"]) for c in o["children"]) for o in v["objects"]) + print(f" {v['name']} ({n_obj} объектов)") diff --git a/docs/architecture/matrix.json b/docs/architecture/matrix.json new file mode 100644 index 0000000..eedf7aa --- /dev/null +++ b/docs/architecture/matrix.json @@ -0,0 +1,417 @@ +{ + "apps": [ + "attachments", + "auth-flow", + "bim", + "cde", + "checklists", + "comparisons", + "contracts", + "control-interface", + "cross-section", + "django", + "document-link", + "documentations", + "drawings", + "eav", + "faas", + "flows", + "iam", + "inspections", + "issues", + "mapper", + "measurements", + "message-hub", + "notes", + "pm", + "prescriptions", + "processing", + "projects", + "remarks", + "reviews", + "rfi", + "stamp-verification", + "subscriptions", + "system-log", + "transmittal", + "workspaces" + ], + "matrix": { + "attachments": { + "postgres": true, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": true, + "redis": false, + "vault": true, + "oidc": false + }, + "auth-flow": { + "postgres": false, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": false, + "oidc": false + }, + "bim": { + "postgres": true, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": true, + "oidc": false + }, + "cde": { + "postgres": false, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": true, + "oidc": false + }, + "checklists": { + "postgres": true, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": true, + "oidc": true + }, + "comparisons": { + "postgres": true, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": true, + "oidc": true + }, + "contracts": { + "postgres": true, + "rabbitmq": true, + "kafka": true, + "camunda": false, + "s3": true, + "redis": false, + "vault": true, + "oidc": true + }, + "control-interface": { + "postgres": false, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": false, + "oidc": false + }, + "cross-section": { + "postgres": false, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": false, + "oidc": false + }, + "django": { + "postgres": true, + "rabbitmq": true, + "kafka": true, + "camunda": false, + "s3": true, + "redis": true, + "vault": true, + "oidc": true + }, + "document-link": { + "postgres": false, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": false, + "oidc": false + }, + "documentations": { + "postgres": true, + "rabbitmq": true, + "kafka": false, + "camunda": false, + "s3": true, + "redis": false, + "vault": true, + "oidc": true + }, + "drawings": { + "postgres": true, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": true, + "oidc": false + }, + "eav": { + "postgres": true, + "rabbitmq": false, + "kafka": true, + "camunda": false, + "s3": true, + "redis": false, + "vault": true, + "oidc": true + }, + "faas": { + "postgres": false, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": false, + "oidc": false + }, + "flows": { + "postgres": true, + "rabbitmq": true, + "kafka": true, + "camunda": false, + "s3": false, + "redis": false, + "vault": true, + "oidc": true + }, + "iam": { + "postgres": true, + "rabbitmq": false, + "kafka": true, + "camunda": false, + "s3": true, + "redis": false, + "vault": true, + "oidc": true + }, + "inspections": { + "postgres": true, + "rabbitmq": false, + "kafka": true, + "camunda": false, + "s3": false, + "redis": false, + "vault": true, + "oidc": false + }, + "issues": { + "postgres": true, + "rabbitmq": true, + "kafka": true, + "camunda": false, + "s3": true, + "redis": false, + "vault": true, + "oidc": true + }, + "mapper": { + "postgres": true, + "rabbitmq": true, + "kafka": true, + "camunda": false, + "s3": true, + "redis": false, + "vault": true, + "oidc": false + }, + "measurements": { + "postgres": false, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": true, + "oidc": false + }, + "message-hub": { + "postgres": true, + "rabbitmq": false, + "kafka": true, + "camunda": false, + "s3": true, + "redis": true, + "vault": true, + "oidc": false + }, + "notes": { + "postgres": true, + "rabbitmq": true, + "kafka": true, + "camunda": false, + "s3": true, + "redis": false, + "vault": true, + "oidc": false + }, + "pm": { + "postgres": true, + "rabbitmq": true, + "kafka": true, + "camunda": false, + "s3": true, + "redis": true, + "vault": true, + "oidc": false + }, + "prescriptions": { + "postgres": false, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": false, + "oidc": false + }, + "processing": { + "postgres": true, + "rabbitmq": true, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": true, + "oidc": true + }, + "projects": { + "postgres": false, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": false, + "oidc": false + }, + "remarks": { + "postgres": false, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": false, + "oidc": false + }, + "reviews": { + "postgres": false, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": false, + "oidc": false + }, + "rfi": { + "postgres": true, + "rabbitmq": true, + "kafka": false, + "camunda": false, + "s3": true, + "redis": false, + "vault": true, + "oidc": false + }, + "stamp-verification": { + "postgres": false, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": false, + "oidc": false + }, + "subscriptions": { + "postgres": true, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": true, + "redis": false, + "vault": true, + "oidc": false + }, + "system-log": { + "postgres": true, + "rabbitmq": false, + "kafka": true, + "camunda": false, + "s3": false, + "redis": false, + "vault": true, + "oidc": false + }, + "transmittal": { + "postgres": true, + "rabbitmq": true, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": true, + "oidc": false + }, + "workspaces": { + "postgres": true, + "rabbitmq": false, + "kafka": false, + "camunda": false, + "s3": false, + "redis": false, + "vault": true, + "oidc": false + } + }, + "exposed": [ + "auth-flow", + "bim", + "camunda", + "cde", + "checklists", + "control-interface", + "django", + "document-link", + "documentations", + "eav", + "flows", + "iam", + "inspections", + "issues", + "pm", + "processing", + "rabbitmq", + "remarks", + "reviews", + "rfi", + "stamp-verification", + "transmittal", + "workspaces", + "zitadel" + ] +} \ No newline at end of file diff --git a/docs/architecture/preview.html b/docs/architecture/preview.html new file mode 100644 index 0000000..734be71 --- /dev/null +++ b/docs/architecture/preview.html @@ -0,0 +1,128 @@ + + +СДЕ Sarex — контур УГМК + +

СДЕ Sarex — контур УГМК

+

Автогенерация из ugmk-technology.archimate. Зелёный — технологический слой, +голубой — прикладной. Пунктир с полым треугольником — realization, стрелка — serving, +ромб — composition, линия с шариком — assignment.

+

T1. Технологический слой — контур УГМК

Что развёрнуто в кластере d8-ugmk-prod и какие технологические сервисы это даёт. Приложения-потребители вынесены в отдельные представления T2–T9.

+ + + + + + + + + + + +Кластер d8-ugmk-prod (Deckhouse)Camunda Platformcr.yandex/crp3ccidau046kdj8g9qs3.uralmine.com — объектное хранилище УГМКNFS-хранилище (StorageClass nfs01)Gitea 10.222.255.165:3000 (infra/iac)um-sarex-k8s-frontend-01Istio Ingress GatewayIstio Service Meshcert-managerZitadelVault + Agent InjectorPostgreSQLRedisKafkaRabbitMQZeebeZeebe GatewayOperateTasklistOptimizeIdentityConnectorsМаршрутизацияHTTPS-трафикаВыпускTLS-сертификатовАутентификация OIDCУправление секретамиРеляционное хранилищеКэшСобытийная шинаОчередь сообщенийAMQPОркестрация процессовBPMNОбъектное хранилищеHelm-чарты *-contour(OCI)Образы контейнеров

T2. Потребители: Маршрутизация HTTPS-трафика

21 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod.

+ + + + + + + + + + + +Маршрутизация HTTPS-трафикаIstio Ingress GatewayIstio Service Meshauth-flowbimcdechecklistscontrol-interfacedjangodocument-linkdocumentationseavflowsiaminspectionsissuespmprocessingremarksreviewsrfistamp-verificationtransmittalworkspaces

T3. Потребители: Аутентификация OIDC

10 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod.

+ + + + + + + + + + + +Аутентификация OIDCZitadelchecklistscomparisonscontractsdjangodocumentationseavflowsiamissuesprocessing

T4. Потребители: Управление секретами

25 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod.

+ + + + + + + + + + + +Управление секретамиVault + Agent Injectorattachmentsbimcdechecklistscomparisonscontractsdjangodocumentationsdrawingseavflowsiaminspectionsissuesmappermeasurementsmessage-hubnotespmprocessingrfisubscriptionssystem-logtransmittalworkspaces

T5. Потребители: Реляционное хранилище

23 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod.

+ + + + + + + + + + + +Реляционное хранилищеPostgreSQLattachmentsbimchecklistscomparisonscontractsdjangodocumentationsdrawingseavflowsiaminspectionsissuesmappermessage-hubnotespmprocessingrfisubscriptionssystem-logtransmittalworkspaces

T6. Потребители: Кэш

3 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod.

+ + + + + + + + + + + +КэшRedisdjangomessage-hubpm

T7. Потребители: Событийная шина

12 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod.

+ + + + + + + + + + + +Событийная шинаKafkacontractsdjangoeavflowsiaminspectionsissuesmappermessage-hubnotespmsystem-log

T8. Потребители: Очередь сообщений AMQP

11 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod.

+ + + + + + + + + + + +Очередь сообщений AMQPRabbitMQcontractsdjangodocumentationsflowsissuesmappernotespmprocessingrfitransmittal

T9. Потребители: Объектное хранилище

13 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod.

+ + + + + + + + + + + +Объектное хранилищеs3.uralmine.com — объектное хранилищеУГМКattachmentscontractsdjangodocumentationseaviamissuesmappermessage-hubnotespmrfisubscriptions
+ \ No newline at end of file diff --git a/docs/architecture/render_preview.py b/docs/architecture/render_preview.py new file mode 100644 index 0000000..364f528 --- /dev/null +++ b/docs/architecture/render_preview.py @@ -0,0 +1,157 @@ +"""Рендерит представления из .archimate в один самодостаточный HTML со встроенным SVG. +Нужен, чтобы схемы можно было посмотреть без установки Archi.""" +import sys, io, pathlib, html +import xml.etree.ElementTree as ET + +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") +XSI = "{http://www.w3.org/2001/XMLSchema-instance}type" +src, dst = pathlib.Path(sys.argv[1]), pathlib.Path(sys.argv[2]) +root = ET.parse(src).getroot() + +LAYER = { # цвета Archi по слоям + "ApplicationComponent": "#B5FFFF", "ApplicationService": "#B5FFFF", + "Node": "#C9E7B7", "SystemSoftware": "#C9E7B7", + "TechnologyService": "#C9E7B7", "Artifact": "#C9E7B7", +} +STROKE = {"Application": "#2a7f8f", "Technology": "#4a7f3a"} + +concepts, rels = {}, {} +for folder in root.findall("folder"): + for e in folder.findall("element"): + et = (e.get(XSI) or "").split(":")[-1] + if et.endswith("Relationship"): + rels[e.get("id")] = (et[:-len("Relationship")], e.get("source"), e.get("target")) + elif et != "ArchimateDiagramModel": + concepts[e.get("id")] = (et, e.get("name") or "") + +def collect(node, ox, oy, nodes, conns): + b = node.find("bounds") + x, y = ox + int(b.get("x", 0)), oy + int(b.get("y", 0)) + w, h = int(b.get("width", 120)), int(b.get("height", 55)) + nodes[node.get("id")] = (node.get("archimateElement"), x, y, w, h) + for c in node.findall("sourceConnection"): + conns.append((c.get("relationship"), c.get("source"), c.get("target"))) + for ch in node.findall("child"): + collect(ch, x, y, nodes, conns) + +def wrap(s, width): + out, line = [], "" + for word in s.split(): + if len(line) + len(word) + 1 > width and line: + out.append(line); line = word + else: + line = f"{line} {word}".strip() + if line: + out.append(line) + return out[:4] + +def edge_point(x, y, w, h, tx, ty): + """Точка пересечения луча к (tx,ty) с границей прямоугольника.""" + cx, cy = x + w / 2, y + h / 2 + dx, dy = tx - cx, ty - cy + if dx == 0 and dy == 0: + return cx, cy + sx = (w / 2) / abs(dx) if dx else float("inf") + sy = (h / 2) / abs(dy) if dy else float("inf") + s = min(sx, sy) + return cx + dx * s, cy + dy * s + +MARKERS = """ + + + + + + + + + + + +""" + +STYLE = { + "Serving": ('stroke-dasharray="none"', 'marker-end="url(#open)"', ""), + "Realization": ('stroke-dasharray="6,4"', 'marker-end="url(#hollow)"', ""), + "Composition": ('stroke-dasharray="none"', "", 'marker-start="url(#diamond)"'), + "Assignment": ('stroke-dasharray="none"', 'marker-end="url(#filled)"', 'marker-start="url(#ball)"'), +} + +parts = [] +views = [e for f in root.findall("folder") for e in f.findall("element") + if (e.get(XSI) or "").endswith("ArchimateDiagramModel")] + +for v in views: + nodes, conns = {}, [] + for ch in v.findall("child"): + collect(ch, 0, 0, nodes, conns) + if not nodes: + continue + maxx = max(x + w for _, x, _, w, _ in nodes.values()) + 40 + maxy = max(y + h for _, _, y, _, h in nodes.values()) + 40 + + svg = [f'', MARKERS] + + # связи под блоками + for rid, so, to in conns: + if so not in nodes or to not in nodes or rid not in rels: + continue + rtype = rels[rid][0] + _, sx, sy, sw, sh = nodes[so] + _, tx, ty, tw, th = nodes[to] + x1, y1 = edge_point(sx, sy, sw, sh, tx + tw / 2, ty + th / 2) + x2, y2 = edge_point(tx, ty, tw, th, sx + sw / 2, sy + sh / 2) + dash, mend, mstart = STYLE.get(rtype, ('stroke-dasharray="none"', 'marker-end="url(#open)"', "")) + svg.append(f'') + + # блоки: контейнеры раньше содержимого — сортируем по площади убыв. + for nid, (eref, x, y, w, h) in sorted(nodes.items(), key=lambda kv: -kv[1][3] * kv[1][4]): + etype, name = concepts.get(eref, ("", eref or "?")) + fill = LAYER.get(etype, "#EEEEEE") + stroke = STROKE["Application"] if etype.startswith("Application") else STROKE["Technology"] + big = w * h > 60000 + svg.append(f'') + lines = wrap(name, max(12, w // 7)) + if big: # подпись контейнера — сверху слева + svg.append(f'{html.escape(name)}') + else: + fs = 11 if len(lines) > 2 else 12 + y0 = y + h / 2 - (len(lines) - 1) * fs * 0.62 + for i, ln in enumerate(lines): + svg.append(f'' + f'{html.escape(ln)}') + svg.append("") + + doc = v.find("documentation") + parts.append(f'

{html.escape(v.get("name") or "")}

' + + (f'

{html.escape(doc.text)}

' if doc is not None and doc.text else "") + + "".join(svg) + "
") + +page = f""" + +{html.escape(root.get('name') or 'Модель')} + +

{html.escape(root.get('name') or '')}

+

Автогенерация из ugmk-technology.archimate. Зелёный — технологический слой, +голубой — прикладной. Пунктир с полым треугольником — realization, стрелка — serving, +ромб — composition, линия с шариком — assignment.

+{''.join(parts)} +""" + +dst.write_text(page, encoding="utf-8", newline="\n") +print(f"записано: {dst} ({len(parts)} представлений)") diff --git a/docs/architecture/scan_ugmk.py b/docs/architecture/scan_ugmk.py new file mode 100644 index 0000000..9d79bd9 --- /dev/null +++ b/docs/architecture/scan_ugmk.py @@ -0,0 +1,69 @@ +"""Собирает матрицу «приложение UGMK -> используемый технологический сервис» +из манифестов base + d8-ugmk-prod. Только *.yaml, документацию не читаем.""" +import re, pathlib, json, sys, io +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") + +ROOT = pathlib.Path(r"C:\Users\user\PycharmProjects\infra\iac") +CLUSTER = "d8-ugmk-prod" + +# 1. какие приложения подключены к кластеру +kust = (ROOT / "clusters" / CLUSTER / "kustomization.yaml").read_text(encoding="utf-8") +apps = sorted(set(re.findall(r"\.\./\.\./apps/([a-z0-9-]+)/" + CLUSTER, kust))) + +# 2. маркеры использования технологических сервисов +MARKERS = { + "postgres": [r"POSTGRES_[A-Z]", r"DB__HOST", r"postgresql\.postgresql\.svc", + r"secrets/data/apps/[a-z0-9-]+/postgres"], + "rabbitmq": [r"RABBITMQ", r"AMQP__", r"rabbitmq\.rabbitmq\.svc"], + "kafka": [r"KAFKA", r"kafka\.kafka\.svc", r"BOOTSTRAP_SERVERS"], + "camunda": [r"ZEEBE", r"CAMUNDA"], + "s3": [r"S3__", r"S3_ENDPOINT", r"S3_ACCESS", r"S3_BUCKET"], + "redis": [r"REDIS_HOST", r"REDIS__", r"redis\.[a-z-]+\.svc"], + "vault": [r"vault\.hashicorp\.com/agent-inject"], + "oidc": [r"ZITADEL", r"JWKS", r"OIDC", r"jwt-public"], +} + +def scan(app): + text = [] + for sub in ("base", CLUSTER): + d = ROOT / "apps" / app / sub + if not d.is_dir(): + continue + for f in d.rglob("*.yaml"): + text.append(f.read_text(encoding="utf-8", errors="replace")) + blob = "\n".join(text) + return {svc: any(re.search(p, blob) for p in pats) for svc, pats in MARKERS.items()} + +matrix = {app: scan(app) for app in apps} + +# 3. кто опубликован наружу через istio (destination host -> namespace) +istio_raw = (ROOT / "infrastructure" / "istio-config" / CLUSTER / "istio-config.yaml").read_text(encoding="utf-8") +# закомментированные маршруты не считаем развёрнутыми +istio = "\n".join(l for l in istio_raw.splitlines() if not l.lstrip().startswith("#")) +exposed = sorted(set(re.findall(r"service:\s*([a-z0-9-]+)\.([a-z0-9-]+)\.svc", istio))) +exposed_ns = sorted({ns for _, ns in exposed}) + +print("ПРИЛОЖЕНИЙ В КОНТУРЕ:", len(apps)) +print() +hdr = ["app"] + list(MARKERS) + ["istio"] +print(" | ".join(h.ljust(9) for h in hdr)) +print("-" * 110) +for app, row in matrix.items(): + cells = [app.ljust(9)] + ["+".ljust(9) if row[s] else "".ljust(9) for s in MARKERS] + cells.append("+".ljust(9) if app in exposed_ns else "".ljust(9)) + print(" | ".join(cells)) + +print() +print("ИТОГО потребителей на сервис:") +for s in MARKERS: + n = sum(1 for r in matrix.values() if r[s]) + print(f" {s:10s} {n:2d}") +print(f" {'istio':10s} {len([a for a in apps if a in exposed_ns]):2d}") +print() +print("namespace, опубликованные через istio:", ", ".join(exposed_ns)) +print() +print("ВСЕГО SERVING-СВЯЗЕЙ:", sum(sum(r.values()) for r in matrix.values()) + len([a for a in apps if a in exposed_ns])) + +(pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else "matrix.json")).write_text( + json.dumps({"apps": apps, "matrix": matrix, "exposed": exposed_ns}, ensure_ascii=False, indent=2), + encoding="utf-8") diff --git a/docs/architecture/to_open_exchange.py b/docs/architecture/to_open_exchange.py new file mode 100644 index 0000000..aac4e59 --- /dev/null +++ b/docs/architecture/to_open_exchange.py @@ -0,0 +1,106 @@ +"""Конвертирует нативный .archimate (формат Archi) в The Open Group +ArchiMate Model Exchange File Format — его понимают сторонние инструменты. + +Вложенность узлов на схемах разворачивается в плоский список с абсолютными +координатами: в обменном формате трактовка координат вложенных узлов +неоднозначна, а плоская раскладка даёт одинаковый результат везде. +Контейнер выводится раньше содержимого, поэтому визуально вложенность +сохраняется. +""" +import sys, io, pathlib +import xml.etree.ElementTree as ET +from xml.sax.saxutils import escape, quoteattr + +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") +XSI = "{http://www.w3.org/2001/XMLSchema-instance}type" +NS = "http://www.opengroup.org/xsd/archimate/3.0/" + +src, dst = pathlib.Path(sys.argv[1]), pathlib.Path(sys.argv[2]) +root = ET.parse(src).getroot() + +def t(e): + return (e.get(XSI) or "").split(":")[-1] + +def text(e, tag): + c = e.find(tag) + return c.text if c is not None and c.text else "" + +elements, relationships, views = [], [], [] + +for folder in root.findall("folder"): + ftype = folder.get("type") + for e in folder.findall("element"): + et = t(e) + if ftype == "diagrams": + views.append(e) + elif et.endswith("Relationship"): + relationships.append((e.get("id"), et[:-len("Relationship")], + e.get("source"), e.get("target"))) + else: + elements.append((e.get("id"), et, e.get("name") or "", text(e, "documentation"))) + +def flatten(node, ox=0, oy=0, out=None, conns=None): + """Возвращает плоский список узлов с абсолютными координатами и соединения.""" + out = [] if out is None else out + conns = [] if conns is None else conns + b = node.find("bounds") + x = ox + int(b.get("x", 0)); y = oy + int(b.get("y", 0)) + out.append((node.get("id"), node.get("archimateElement"), + x, y, int(b.get("width", 120)), int(b.get("height", 55)))) + for c in node.findall("sourceConnection"): + conns.append((c.get("id"), c.get("relationship"), + c.get("source"), c.get("target"))) + for ch in node.findall("child"): + flatten(ch, x, y, out, conns) + return out, conns + +out = ['', + f'', + f' {escape(root.get("name") or "model")}'] +purpose = root.find("purpose") +if purpose is not None and purpose.text: + out.append(f' {escape(purpose.text)}') + +out.append(" ") +for eid, etype, name, doc in elements: + out.append(f' ') + out.append(f' {escape(name)}') + if doc: + out.append(f' {escape(doc)}') + out.append(" ") +out.append(" ") + +out.append(" ") +for rid, rtype, s, tg in relationships: + out.append(f' ') +out.append(" ") + +out.append(" ") +out.append(" ") +for v in views: + out.append(f' ') + out.append(f' {escape(v.get("name") or "")}') + doc = text(v, "documentation") + if doc: + out.append(f' {escape(doc)}') + nodes, conns = [], [] + for child in v.findall("child"): + flatten(child, 0, 0, nodes, conns) + for nid, eref, x, y, w, h in nodes: + out.append(f' ') + for cid, rref, s, tg in conns: + out.append(f' ') + out.append(" ") +out.append(" ") +out.append(" ") +out.append("") + +dst.write_text("\n".join(out) + "\n", encoding="utf-8", newline="\n") +print(f"записано: {dst}") +print(f" элементов: {len(elements)}") +print(f" отношений: {len(relationships)}") +print(f" представлений: {len(views)}") diff --git a/docs/architecture/ugmk-technology.archimate b/docs/architecture/ugmk-technology.archimate new file mode 100644 index 0000000..6c403e9 --- /dev/null +++ b/docs/architecture/ugmk-technology.archimate @@ -0,0 +1,1036 @@ + + + Технологический слой контура d8-ugmk-prod, выведенный из репозитория infra/iac. Прикладной слой присутствует только как потребители технологических сервисов — межсервисные связи приложений моделируются отдельно. + + + + + apps/attachments/d8-ugmk-prod/ + + + apps/auth-flow/d8-ugmk-prod/ + + + apps/bim/d8-ugmk-prod/ + + + apps/cde/d8-ugmk-prod/ + + + apps/checklists/d8-ugmk-prod/ + + + apps/comparisons/d8-ugmk-prod/ + + + apps/contracts/d8-ugmk-prod/ + + + apps/control-interface/d8-ugmk-prod/ + + + apps/cross-section/d8-ugmk-prod/ + + + apps/django/d8-ugmk-prod/ + + + apps/document-link/d8-ugmk-prod/ + + + apps/documentations/d8-ugmk-prod/ + + + apps/drawings/d8-ugmk-prod/ + + + apps/eav/d8-ugmk-prod/ + + + apps/faas/d8-ugmk-prod/ + + + apps/flows/d8-ugmk-prod/ + + + apps/iam/d8-ugmk-prod/ + + + apps/inspections/d8-ugmk-prod/ + + + apps/issues/d8-ugmk-prod/ + + + apps/mapper/d8-ugmk-prod/ + + + apps/measurements/d8-ugmk-prod/ + + + apps/message-hub/d8-ugmk-prod/ + + + apps/notes/d8-ugmk-prod/ + + + apps/pm/d8-ugmk-prod/ + + + apps/prescriptions/d8-ugmk-prod/ + + + apps/processing/d8-ugmk-prod/ + + + apps/projects/d8-ugmk-prod/ + + + apps/remarks/d8-ugmk-prod/ + + + apps/reviews/d8-ugmk-prod/ + + + apps/rfi/d8-ugmk-prod/ + + + apps/stamp-verification/d8-ugmk-prod/ + + + apps/subscriptions/d8-ugmk-prod/ + + + apps/system-log/d8-ugmk-prod/ + + + apps/transmittal/d8-ugmk-prod/ + + + apps/workspaces/d8-ugmk-prod/ + + + + + clusters/d8-ugmk-prod/kustomization.yaml. GitOps через FluxCD v2. + + + Выделенный frontend-узел. К нему прибит ingress-gateway через nodeSelector: clusters/d8-ugmk-prod/istio-ingress/ingress-controller.yaml + + + Внешнее по отношению к кластеру. MinIO в контуре не разворачивается. + + + Единственный storageClass в контуре, 9 упоминаний в манифестах. + + + Источник для Flux: clusters/d8-ugmk-prod/flux-system/gotk-sync.yaml + + + Реестр чартов (OCI) и образов контейнеров. + + + Deckhouse IngressIstioController «sarex», inlet NodePort 30080/30443. clusters/d8-ugmk-prod/istio-ingress/ingress-controller.yaml + + + chart istio-config-contour 0.1.0. Gateway, VirtualService, RequestAuthentication. + + + ClusterIssuer sarex-selfsigned — сертификаты самоподписанные, не Let's Encrypt. Wildcard *.uralmine.com и *.sarex-k8s.uralmine.com. + + + chart idp-contour 4.12.13, ns zitadel. + + + chart vault-contour 0.2.3, ns vault. Единственный механизм доставки секретов, путь secrets/data/apps/<app>/postgres. + + + chart postgresql-contour 17.0.7, ns postgresql. PVC 20Gi на nfs01. + + + Разворачивается только в ns pm: apps/pm/d8-ugmk-prod/redis.yaml. + + + chart kafka-contour 0.1.15, ns kafka. + + + chart rabbitmq-contour 15.2.4, ns rabbitmq. Публикуется наружу на rabbitmq.sarex-k8s.uralmine.com. + + + chart camunda-contour 11.0.14, ns camunda. ВНИМАНИЕ: ни одно приложение контура на неё не сконфигурировано — потребителей нет. + + + infrastructure/camunda/d8-ugmk-prod/camunda.yaml + + + infrastructure/camunda/d8-ugmk-prod/camunda.yaml + + + infrastructure/camunda/d8-ugmk-prod/camunda.yaml + + + infrastructure/camunda/d8-ugmk-prod/camunda.yaml + + + infrastructure/camunda/d8-ugmk-prod/camunda.yaml + + + infrastructure/camunda/d8-ugmk-prod/camunda.yaml + + + infrastructure/camunda/d8-ugmk-prod/camunda.yaml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Что развёрнуто в кластере d8-ugmk-prod и какие технологические сервисы это даёт. Приложения-потребители вынесены в отдельные представления T2–T9. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 21 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 25 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 23 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod. + + + + + + + + + + + + + + + + + + + + + + 12 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 11 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 13 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/architecture/ugmk-technology.xml b/docs/architecture/ugmk-technology.xml new file mode 100644 index 0000000..ed3c10c --- /dev/null +++ b/docs/architecture/ugmk-technology.xml @@ -0,0 +1,785 @@ + + + СДЕ Sarex — контур УГМК + Технологический слой контура d8-ugmk-prod, выведенный из репозитория infra/iac. Прикладной слой присутствует только как потребители технологических сервисов — межсервисные связи приложений моделируются отдельно. + + + attachments + apps/attachments/d8-ugmk-prod/ + + + auth-flow + apps/auth-flow/d8-ugmk-prod/ + + + bim + apps/bim/d8-ugmk-prod/ + + + cde + apps/cde/d8-ugmk-prod/ + + + checklists + apps/checklists/d8-ugmk-prod/ + + + comparisons + apps/comparisons/d8-ugmk-prod/ + + + contracts + apps/contracts/d8-ugmk-prod/ + + + control-interface + apps/control-interface/d8-ugmk-prod/ + + + cross-section + apps/cross-section/d8-ugmk-prod/ + + + django + apps/django/d8-ugmk-prod/ + + + document-link + apps/document-link/d8-ugmk-prod/ + + + documentations + apps/documentations/d8-ugmk-prod/ + + + drawings + apps/drawings/d8-ugmk-prod/ + + + eav + apps/eav/d8-ugmk-prod/ + + + faas + apps/faas/d8-ugmk-prod/ + + + flows + apps/flows/d8-ugmk-prod/ + + + iam + apps/iam/d8-ugmk-prod/ + + + inspections + apps/inspections/d8-ugmk-prod/ + + + issues + apps/issues/d8-ugmk-prod/ + + + mapper + apps/mapper/d8-ugmk-prod/ + + + measurements + apps/measurements/d8-ugmk-prod/ + + + message-hub + apps/message-hub/d8-ugmk-prod/ + + + notes + apps/notes/d8-ugmk-prod/ + + + pm + apps/pm/d8-ugmk-prod/ + + + prescriptions + apps/prescriptions/d8-ugmk-prod/ + + + processing + apps/processing/d8-ugmk-prod/ + + + projects + apps/projects/d8-ugmk-prod/ + + + remarks + apps/remarks/d8-ugmk-prod/ + + + reviews + apps/reviews/d8-ugmk-prod/ + + + rfi + apps/rfi/d8-ugmk-prod/ + + + stamp-verification + apps/stamp-verification/d8-ugmk-prod/ + + + subscriptions + apps/subscriptions/d8-ugmk-prod/ + + + system-log + apps/system-log/d8-ugmk-prod/ + + + transmittal + apps/transmittal/d8-ugmk-prod/ + + + workspaces + apps/workspaces/d8-ugmk-prod/ + + + Кластер d8-ugmk-prod (Deckhouse) + clusters/d8-ugmk-prod/kustomization.yaml. GitOps через FluxCD v2. + + + um-sarex-k8s-frontend-01 + Выделенный frontend-узел. К нему прибит ingress-gateway через nodeSelector: clusters/d8-ugmk-prod/istio-ingress/ingress-controller.yaml + + + s3.uralmine.com — объектное хранилище УГМК + Внешнее по отношению к кластеру. MinIO в контуре не разворачивается. + + + NFS-хранилище (StorageClass nfs01) + Единственный storageClass в контуре, 9 упоминаний в манифестах. + + + Gitea 10.222.255.165:3000 (infra/iac) + Источник для Flux: clusters/d8-ugmk-prod/flux-system/gotk-sync.yaml + + + cr.yandex/crp3ccidau046kdj8g9q + Реестр чартов (OCI) и образов контейнеров. + + + Istio Ingress Gateway + Deckhouse IngressIstioController «sarex», inlet NodePort 30080/30443. clusters/d8-ugmk-prod/istio-ingress/ingress-controller.yaml + + + Istio Service Mesh + chart istio-config-contour 0.1.0. Gateway, VirtualService, RequestAuthentication. + + + cert-manager + ClusterIssuer sarex-selfsigned — сертификаты самоподписанные, не Let's Encrypt. Wildcard *.uralmine.com и *.sarex-k8s.uralmine.com. + + + Zitadel + chart idp-contour 4.12.13, ns zitadel. + + + Vault + Agent Injector + chart vault-contour 0.2.3, ns vault. Единственный механизм доставки секретов, путь secrets/data/apps/<app>/postgres. + + + PostgreSQL + chart postgresql-contour 17.0.7, ns postgresql. PVC 20Gi на nfs01. + + + Redis + Разворачивается только в ns pm: apps/pm/d8-ugmk-prod/redis.yaml. + + + Kafka + chart kafka-contour 0.1.15, ns kafka. + + + RabbitMQ + chart rabbitmq-contour 15.2.4, ns rabbitmq. Публикуется наружу на rabbitmq.sarex-k8s.uralmine.com. + + + Camunda Platform + chart camunda-contour 11.0.14, ns camunda. ВНИМАНИЕ: ни одно приложение контура на неё не сконфигурировано — потребителей нет. + + + Zeebe + infrastructure/camunda/d8-ugmk-prod/camunda.yaml + + + Zeebe Gateway + infrastructure/camunda/d8-ugmk-prod/camunda.yaml + + + Operate + infrastructure/camunda/d8-ugmk-prod/camunda.yaml + + + Tasklist + infrastructure/camunda/d8-ugmk-prod/camunda.yaml + + + Optimize + infrastructure/camunda/d8-ugmk-prod/camunda.yaml + + + Identity + infrastructure/camunda/d8-ugmk-prod/camunda.yaml + + + Connectors + infrastructure/camunda/d8-ugmk-prod/camunda.yaml + + + Helm-чарты *-contour (OCI) + + + Образы контейнеров + + + Маршрутизация HTTPS-трафика + + + Выпуск TLS-сертификатов + + + Аутентификация OIDC + + + Управление секретами + + + Реляционное хранилище + + + Кэш + + + Событийная шина + + + Очередь сообщений AMQP + + + Оркестрация процессов BPMN + + + Объектное хранилище + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + T1. Технологический слой — контур УГМК + Что развёрнуто в кластере d8-ugmk-prod и какие технологические сервисы это даёт. Приложения-потребители вынесены в отдельные представления T2–T9. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + T2. Потребители: Маршрутизация HTTPS-трафика + 21 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + T3. Потребители: Аутентификация OIDC + 10 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod. + + + + + + + + + + + + + + + + + + + + + + + + + + T4. Потребители: Управление секретами + 25 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + T5. Потребители: Реляционное хранилище + 23 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + T6. Потребители: Кэш + 3 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod. + + + + + + + + + + + + T7. Потребители: Событийная шина + 12 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + T8. Потребители: Очередь сообщений AMQP + 11 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod. + + + + + + + + + + + + + + + + + + + + + + + + + + + + T9. Потребители: Объектное хранилище + 13 из 35 приложений контура. Связи выведены из манифестов apps/*/base и apps/*/d8-ugmk-prod. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +