@nitra/cursor 12.8.5 → 12.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/bin/n-cursor.js +5 -5
- package/package.json +1 -1
- package/rules/abie/js/http_route_base.mdc +25 -0
- package/rules/abie/js/ua_http_route.mdc +1 -1
- package/rules/abie/main.mdc +12 -0
- package/rules/adr/js/hooks.mdc +32 -0
- package/rules/adr/js/madr_format.mdc +96 -0
- package/rules/adr/js/settings_policy.mdc +34 -0
- package/rules/adr/main.mdc +13 -95
- package/rules/bun/js/bunfig.mdc +12 -0
- package/rules/bun/js/layout.mdc +60 -0
- package/rules/bun/js/lint.mdc +9 -0
- package/rules/bun/js/package_json.mdc +19 -0
- package/rules/bun/main.mdc +9 -61
- package/rules/capacitor/js/ios_spm.mdc +69 -0
- package/rules/capacitor/js/version.mdc +29 -0
- package/rules/capacitor/main.mdc +8 -22
- package/rules/changelog/js/agent-workflow.mdc +15 -0
- package/rules/changelog/js/changelog-format.mdc +33 -0
- package/rules/changelog/js/comparison-models.mdc +40 -0
- package/rules/changelog/main.mdc +4 -98
- package/rules/ci4/js/marksman_config.mdc +31 -0
- package/rules/ci4/js/vscode_extensions.mdc +33 -0
- package/rules/ci4/main.mdc +14 -14
- package/rules/docker/js/compile.mdc +44 -0
- package/rules/docker/js/hadolint.mdc +50 -0
- package/rules/docker/js/mirror.mdc +13 -0
- package/rules/docker/js/multistage.mdc +13 -0
- package/rules/docker/js/native-addon.mdc +43 -0
- package/rules/docker/js/nginx-tag.mdc +7 -0
- package/rules/docker/js/nginx-user.mdc +37 -0
- package/rules/docker/js/non-root.mdc +39 -0
- package/rules/docker/main.mdc +15 -196
- package/rules/ga/js/lint_toolchain.mdc +15 -0
- package/rules/ga/js/required_workflows.mdc +35 -0
- package/rules/ga/js/vscode.mdc +17 -0
- package/rules/ga/js/workflow_common.mdc +108 -0
- package/rules/ga/js/workflows.mdc +32 -0
- package/rules/ga/js/zizmor.mdc +7 -0
- package/rules/ga/main.mdc +17 -125
- package/rules/graphql/js/tooling.mdc +13 -0
- package/rules/graphql/js/vscode_extensions.mdc +13 -0
- package/rules/graphql/main.mdc +3 -22
- package/rules/hasura/js/internal_urls.mdc +27 -0
- package/rules/hasura/js/migrations.mdc +13 -0
- package/rules/hasura/js/svc_hl.mdc +17 -0
- package/rules/hasura/main.mdc +8 -30
- package/rules/image-avif/js/avif_generation.mdc +26 -0
- package/rules/image-avif/js/package_json_optout.mdc +21 -0
- package/rules/image-avif/main.mdc +7 -34
- package/rules/image-compress/js/package_json.mdc +7 -0
- package/rules/image-compress/js/package_setup.mdc +13 -0
- package/rules/image-compress/main.mdc +4 -12
- package/rules/js/docs/index.md +3 -3
- package/rules/js/js/dep-policy.mdc +17 -0
- package/rules/js/js/eslint-config.mdc +28 -0
- package/rules/js/js/extensions.mdc +8 -0
- package/rules/js/js/file-extensions.mdc +12 -0
- package/rules/js/js/for-in.mdc +26 -0
- package/rules/js/js/jscpd.mdc +42 -0
- package/rules/js/js/knip.mdc +15 -0
- package/rules/js/js/lint-js-workflow.mdc +58 -0
- package/rules/js/js/oxlintrc.mdc +20 -0
- package/rules/js/js/package-json.mdc +31 -0
- package/rules/js/js/tests.mdc +9 -0
- package/rules/js/js/utils-lib-structure.mdc +15 -0
- package/rules/js/main.mdc +21 -214
- package/rules/js-bun-db/js/bun-sql-migration.mdc +15 -0
- package/rules/js-bun-db/js/connection.mdc +42 -0
- package/rules/js-bun-db/js/pg-format-identifiers.mdc +102 -0
- package/rules/js-bun-db/js/pg-format-shim.mdc +99 -0
- package/rules/js-bun-db/js/pg-leftover.mdc +27 -0
- package/rules/js-bun-db/js/pg-listen-notify.mdc +51 -0
- package/rules/js-bun-db/js/query-safety.mdc +117 -0
- package/rules/js-bun-db/js/sql-array.mdc +88 -0
- package/rules/js-bun-db/js/unsafe.mdc +65 -0
- package/rules/js-bun-db/main.mdc +15 -605
- package/rules/js-bun-redis/js/imports.mdc +47 -0
- package/rules/js-bun-redis/js/package_json.mdc +44 -0
- package/rules/js-bun-redis/main.mdc +3 -11
- package/rules/js-mssql/js/mssql-in-list.mdc +38 -0
- package/rules/js-mssql/js/mssql-pool.mdc +56 -0
- package/rules/js-mssql/js/mssql-query-template.mdc +33 -0
- package/rules/js-mssql/js/mssql-tvp.mdc +75 -0
- package/rules/js-mssql/js/mssql-version.mdc +7 -0
- package/rules/js-mssql/main.mdc +10 -198
- package/rules/js-run/js/check-env.mdc +35 -0
- package/rules/js-run/js/conn-aliases.mdc +109 -0
- package/rules/js-run/js/jsconfig.mdc +20 -0
- package/rules/js-run/js/otel-configmap.mdc +6 -0
- package/rules/js-run/js/pino.mdc +6 -0
- package/rules/js-run/js/project-structure.mdc +11 -0
- package/rules/js-run/js/runtime.mdc +14 -0
- package/rules/js-run/js/scope.mdc +11 -0
- package/rules/js-run/js/settimeout.mdc +11 -0
- package/rules/js-run/js/temporal.mdc +5 -0
- package/rules/js-run/main.mdc +16 -218
- package/rules/k8s/js/configmap.mdc +41 -0
- package/rules/k8s/js/deployment_resources.mdc +49 -0
- package/rules/k8s/js/hasura_httproute.mdc +91 -0
- package/rules/k8s/js/hpa_apiversion.mdc +27 -0
- package/rules/k8s/js/ingress_gateway.mdc +16 -0
- package/rules/k8s/js/kustomize_structure.mdc +144 -0
- package/rules/k8s/js/lint_k8s.mdc +72 -0
- package/rules/k8s/js/multidoc_yaml.mdc +5 -0
- package/rules/k8s/js/network_policy.mdc +136 -0
- package/rules/k8s/js/schema_modeline.mdc +57 -0
- package/rules/k8s/js/service.mdc +44 -0
- package/rules/k8s/js/topology_hpa_pdb.mdc +181 -0
- package/rules/k8s/main.mdc +30 -843
- package/rules/nginx-default-tpl/js/dockerfile.mdc +36 -0
- package/rules/nginx-default-tpl/js/http-route.mdc +41 -0
- package/rules/nginx-default-tpl/js/ini-keys.mdc +21 -0
- package/rules/nginx-default-tpl/js/template-structure.mdc +86 -0
- package/rules/nginx-default-tpl/js/vscode.mdc +37 -0
- package/rules/nginx-default-tpl/main.mdc +6 -112
- package/rules/npm-module/js/docs/index.md +5 -5
- package/rules/npm-module/js/docs/rule_meta.md +6 -6
- package/rules/npm-module/js/docs/skill_meta.md +8 -8
- package/rules/npm-module/js/header_doc_pointer.mdc +18 -0
- package/rules/npm-module/js/package_structure.mdc +62 -0
- package/rules/npm-module/js/rule_meta.mdc +11 -0
- package/rules/npm-module/js/skill_meta.mdc +11 -0
- package/rules/npm-module/main.mdc +10 -55
- package/rules/php/js/lint_php_yml.mdc +12 -0
- package/rules/php/js/tooling.mdc +66 -0
- package/rules/php/main.mdc +7 -66
- package/rules/python/js/lint_python_yml.mdc +23 -0
- package/rules/python/js/pyproject_toml.mdc +32 -0
- package/rules/python/js/tooling.mdc +23 -0
- package/rules/python/main.mdc +9 -33
- package/rules/rego/js/rego-lint.mdc +31 -0
- package/rules/rego/js/vscode_extensions.mdc +11 -0
- package/rules/rego/js/vscode_settings.mdc +13 -0
- package/rules/rego/main.mdc +8 -24
- package/rules/rust/js/coverage.mdc +28 -0
- package/rules/rust/js/lint.mdc +22 -0
- package/rules/rust/js/tauri_composition.mdc +8 -0
- package/rules/rust/js/vscode_extensions.mdc +12 -0
- package/rules/rust/main.mdc +8 -38
- package/rules/security/js/rego_policies.mdc +15 -0
- package/rules/security/js/sample_secret.mdc +19 -0
- package/rules/security/js/trufflehog.mdc +21 -0
- package/rules/security/main.mdc +7 -35
- package/rules/style/js/admin-table.mdc +88 -0
- package/rules/style/js/colors.mdc +21 -0
- package/rules/style/js/gap.mdc +22 -0
- package/rules/style/js/quasar-fixes.mdc +32 -0
- package/rules/style/js/quasar.mdc +7 -0
- package/rules/style/js/tooling.mdc +85 -0
- package/rules/style/main.mdc +13 -253
- package/rules/tauri/js/cargo_mutants_config.mdc +39 -0
- package/rules/tauri/js/tool_surface.mdc +21 -0
- package/rules/tauri/js/tooling.mdc +25 -0
- package/rules/tauri/main.mdc +8 -78
- package/rules/test/js/cargo_mutants_config.mdc +18 -0
- package/rules/test/js/docs/index.md +7 -7
- package/rules/test/js/location.mdc +52 -0
- package/rules/test/js/no-console-store-restore.mdc +11 -0
- package/rules/test/js/no-process-chdir.mdc +15 -0
- package/rules/test/js/no-relative-fs-path.mdc +22 -0
- package/rules/test/js/sandbox-aware-test.mdc +28 -0
- package/rules/test/js/stryker_config.mdc +26 -0
- package/rules/test/js/vitest-config-pool-forks.mdc +33 -0
- package/rules/test/main.mdc +18 -184
- package/rules/text/js/ci-lint-text.mdc +15 -0
- package/rules/text/js/cspell.mdc +81 -0
- package/rules/text/js/dotenv-linter.mdc +16 -0
- package/rules/text/js/forbidden-prettier.mdc +13 -0
- package/rules/text/js/markdownlint.mdc +25 -0
- package/rules/text/js/oxfmt.mdc +35 -0
- package/rules/text/js/package-json.mdc +26 -0
- package/rules/text/js/shellcheck.mdc +18 -0
- package/rules/text/js/v8r.mdc +23 -0
- package/rules/text/js/vscode.mdc +86 -0
- package/rules/text/main.mdc +20 -237
- package/rules/vue/js/composition-api.mdc +82 -0
- package/rules/vue/js/nheader-layout.mdc +171 -0
- package/rules/vue/js/node-imports.mdc +25 -0
- package/rules/vue/js/quasar-ui.mdc +32 -0
- package/rules/vue/js/structure.mdc +101 -0
- package/rules/vue/js/testing.mdc +32 -0
- package/rules/vue/js/tfm-translations.mdc +26 -0
- package/rules/vue/js/vite-config.mdc +126 -0
- package/rules/vue/js/vite-env.mdc +55 -0
- package/rules/vue/js/vue-imports.mdc +25 -0
- package/rules/vue/main.mdc +16 -640
- package/scripts/auto-rules.mjs +6 -6
- package/scripts/auto-skills.mjs +3 -3
- package/scripts/docs/auto-rules.md +17 -31
- package/scripts/docs/auto-skills.md +18 -163
- package/scripts/docs/index.md +16 -16
- package/scripts/lib/docs/index.md +36 -36
- package/scripts/lib/docs/mirror-parity.md +7 -7
- package/scripts/lib/docs/rule-meta.md +12 -12
- package/scripts/lib/docs/skill-meta.md +9 -9
- package/scripts/lib/docs/worktree-notice.md +10 -8
- package/scripts/lib/rule-meta.mjs +6 -6
- package/scripts/lib/skill-meta.mjs +6 -6
- package/scripts/lib/worktree-notice.mjs +2 -2
- package/scripts/utils/docs/index.md +14 -14
package/rules/k8s/main.mdc
CHANGED
|
@@ -7,867 +7,54 @@ alwaysApply: false
|
|
|
7
7
|
|
|
8
8
|
# Kubernetes YAML у шляхах з `k8s`
|
|
9
9
|
|
|
10
|
-
Для кожного файлу `*.yaml`, у шляху якого є сегмент директорії **`k8s`**
|
|
10
|
+
Для кожного файлу `*.yaml`, у шляху якого є сегмент директорії **`k8s`** — перевірка modeline `$schema`, структури Kustomize, маніфестів Deployment / Service / NetworkPolicy та лінт через kubeconform і kubescape.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
# yaml-language-server: $schema=https://...
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
Далі — вміст маніфесту. Зайвий порожній рядок між коментарем і YAML не додавай, якщо в проєкті не прийнято інше.
|
|
17
|
-
|
|
18
|
-
**Modeline — опційний:** якщо для конкретного поєднання `apiVersion`/`kind` **немає** надійної публічної схеми (yannh/datree/schemastore не покривають), залиш файл **без** рядка `# yaml-language-server: $schema=…`. **Заборонено** ставити `$schema=file:…` як заглушку — це створює видимість валідації без неї. Без modeline `check-k8s` не валідує URL, але **`lint-k8s`** (kubeconform/kubescape) продовжить роботу. Якщо modeline присутній — він обов'язково перший рядок і **тільки** `https://` URL, який відповідає очікуваному за `apiVersion`/`kind`.
|
|
19
|
-
|
|
20
|
-
**Виняток — modeline заборонено:** `apiVersion: alb.yc.io/v1alpha1`, `kind: HttpBackendGroup` (Yandex ALB) — рядка **`# yaml-language-server: $schema=…`** у файлі **не** має бути (ні в першому рядку, ні далі). Перший рядок — одразу YAML (`apiVersion:` тощо). Перевірка — **`rules/k8s/fix.mjs`**.
|
|
21
|
-
|
|
22
|
-
**Розширення:** усі маніфести під **`k8s`**, включно з **`kustomization.yaml`**, — лише **`.yaml`** (розширення **`.yml`** не використовуй).
|
|
23
|
-
|
|
24
|
-
**Скоп — поза `.github/`:** правило стосується YAML-маніфестів у каталогах **`k8s`** (визначаються відносно кореня репо, не за абсолютним шляхом). Файли під **`.github/workflows/`** та **`.github/actions/`** ця перевірка **не** зачіпає — їхній скоп визначає **`ga.mdc`** (там канон — **`.yml`**). Це робить два правила несуперечливими навіть у проєктах, де сам корінь репо випадково має ім'я `k8s/` (тоді сегмент `k8s` присутній у абсолютному шляху всіх файлів, але **відносно кореня** його там немає).
|
|
12
|
+
**Скоп — поза `.github/`:** правило стосується YAML-маніфестів у каталогах **`k8s`** (визначаються відносно кореня репо, не за абсолютним шляхом). Файли під **`.github/workflows/`** та **`.github/actions/`** ця перевірка **не** зачіпає — їхній скоп визначає **`ga.mdc`** (там канон — **`.yml`**).
|
|
25
13
|
|
|
26
14
|
**Dockerfile / hadolint** — окреме правило **`docker.mdc`** і **`npx @nitra/cursor fix docker`**.
|
|
27
15
|
|
|
28
|
-
##
|
|
29
|
-
|
|
30
|
-
Окремо від modeline `$schema` у редакторі варто ганяти CLI-лінтери (**kubeconform** і **kubescape**) по тих самих дерев’ях **`…/k8s`**.
|
|
31
|
-
|
|
32
|
-
**Залежності:** виконувані файли kubeconform, kubescape і kubectl у **PATH** (kustomize використовуємо як вшиту підкоманду **`kubectl kustomize`** — окремий бінарник `kustomize` не потрібен); не додавай їх у **devDependencies**.
|
|
33
|
-
|
|
34
|
-
**Версія Kubernetes для kubeconform** має відповідати PIN yannh у цьому правилі та в **`rules/k8s/fix.mjs`** (зараз **`-kubernetes-version 1.33.9`** — semver без префікса `v`, еквівалент релізу **v1.33.9**; набір схем **`v1.33.9-standalone-strict`**). Для CRD додатково підключай реєстр [datreeio/CRDs-catalog](https://github.com/datreeio/CRDs-catalog) другим **`-schema-location`**, як у [прикладах kubeconform](https://github.com/yannh/kubeconform#readme). За потреби **`-ignore-missing-schemas`**, якщо частина CRD ще без публічної схеми.
|
|
35
|
-
|
|
36
|
-
**kubescape — вхід через зібраний kustomize-маніфест:** для кожного dir-у з `kustomization.yaml` (`kind: Kustomization`; **`kind: Component`** пропускається — він не білдиться окремо) `lint-k8s` виконує **`kubectl kustomize <dir>`** і передає stdout у **`kubescape scan <tmp-file>`** з порогом **`--severity-threshold high`** (вбудована в kubectl підкоманда `kustomize` — окремий бінарник `kustomize` не потрібен; рендеринг локальний і не потребує доступу до кластера). Маніфест проходить через тимчасовий файл, бо **`kubescape scan` у v4.x не читає stdin** (`-` як шлях → `no resources found to scan`; прапорця `--input`/`--stdin` немає); тимчасова директорія створюється під `os.tmpdir()` і прибирається після скану. Збірка через kustomize нормалізує namespace на workload-manifest-файлах і **NetworkPolicy у `base/networkpolicy.yaml`** (через `base/kustomization.yaml` `namespace:`), що дає коректний матчинг `podSelector` у `C-0260` (`Missing network policy`) і дозволяє kubescape бачити дерево overlays / components зі справжніми ресурсами. Якщо в дереві **`…/k8s`** немає жодного `kustomization.yaml` (проєкт без Kustomize) — fallback на старий dir-скан **`kubescape scan <каталог-k8s>`**. Перший запуск kubescape може завантажувати артефакти — у CI потрібна мережа або [offline](https://github.com/kubescape/kubescape#readme). На відміну від kubeconform, у **kubescape scan** немає прапорця **`-kubernetes-version`**: перевірка йде за **framework/control** (NSA, MITRE, CIS тощо), а не проти OpenAPI-схеми конкретного релізу Kubernetes. **Орієнтир** для репозиторію той самий, що й для kubeconform — кластер **v1.33.9** (див. **`-kubernetes-version 1.33.9`** вище); для CIS і подібних наближень обирай актуальний framework під політику команди (**`kubescape list frameworks`**, див. [CLI reference](https://github.com/kubescape/kubescape/blob/master/docs/cli-reference.md)).
|
|
37
|
-
|
|
38
|
-
### Винятки kubescape: `.kubescape-exceptions.json`
|
|
39
|
-
|
|
40
|
-
Якщо в **корені проєкту** є файл **`.kubescape-exceptions.json`** — `lint-k8s` автоматично передає його в `kubescape scan` через **`--exceptions`** ([postureExceptionPolicy](https://github.com/kubescape/kubescape/blob/master/docs/exceptions.md)). Файл — JSON-масив об'єктів з полями `name`, `policyType: "postureExceptionPolicy"`, `actions` (`["alertOnly"]` — знижує fail до alert, не блокує lint), `resources` (resource designator) і `posturePolicies` (масив `controlID`).
|
|
41
|
-
|
|
42
|
-
Канонічний кейс — **C-0012** (`Applications credentials in configuration files`, High): control тригериться на **імʼя** env, що містить підрядок `secret`/`password`/`key`/`token`, а **не** на значення. Для `HASURA_GRAPHQL_JWT_SECRET` у ConfigMap значення — публічний JWT-конфіг (`jwk_url`, `issuer`), не credentials, але kubescape падає лише через імʼя. Точкове виключення для ConfigMap із цим env — канон: [.kubescape-exceptions.json.snippet.json](./js/templates/kubescape_exceptions/.kubescape-exceptions.json.snippet.json)
|
|
43
|
-
|
|
44
|
-
Підстав свою `attributes.name` (рядок або regex), якщо ConfigMap зветься інакше; виключай контрольно, а не глобально (не додавай винятки без `attributes.name`/`labels`, бо тоді C-0012 знімається для усіх ConfigMap-ів проєкту і реальні витоки credentials теж пройдуть).
|
|
45
|
-
|
|
46
|
-
Лінт запускається через правило **`n-cursor lint k8s`** (реалізація — **`npm/rules/k8s/js/lint.mjs`**, делегує kubeconform/kubescape у **`npm/rules/k8s/lint/lint.mjs`**). Окремий `package.json`-скрипт `lint-k8s` не потрібен і не перевіряється.
|
|
47
|
-
|
|
48
|
-
Додай workflow **`.github/workflows/lint-k8s.yml`** (гілки **`dev`** і **`main`**, лише **`.yml`**, узгоджено з **`ga.mdc`**). **Не** дублюй **`setup-node`**, **`oven-sh/setup-bun`**, **`actions/cache`** і **`bun install`** у job — після **`checkout`** використовуй локальний composite **`setup-bun-deps`** (шлях **`./.github/actions/setup-bun-deps`** або **`./npm/github-actions/setup-bun-deps`**, як у репозиторії). Встановлення **kubeconform** / **kubescape** лишаються окремими кроками.
|
|
49
|
-
|
|
50
|
-
```yaml title=".github/workflows/lint-k8s.yml"
|
|
51
|
-
name: Lint K8s
|
|
52
|
-
|
|
53
|
-
on:
|
|
54
|
-
push:
|
|
55
|
-
branches:
|
|
56
|
-
- dev
|
|
57
|
-
- main
|
|
58
|
-
paths:
|
|
59
|
-
- '**/k8s/**/*.yaml'
|
|
60
|
-
|
|
61
|
-
pull_request:
|
|
62
|
-
branches:
|
|
63
|
-
- dev
|
|
64
|
-
- main
|
|
65
|
-
|
|
66
|
-
concurrency:
|
|
67
|
-
group: ${{ github.ref }}-${{ github.workflow }}
|
|
68
|
-
cancel-in-progress: true
|
|
69
|
-
|
|
70
|
-
jobs:
|
|
71
|
-
lint-k8s:
|
|
72
|
-
runs-on: ubuntu-latest
|
|
73
|
-
permissions:
|
|
74
|
-
contents: read
|
|
75
|
-
steps:
|
|
76
|
-
- uses: actions/checkout@v6
|
|
77
|
-
with:
|
|
78
|
-
persist-credentials: false
|
|
79
|
-
|
|
80
|
-
- uses: ./.github/actions/setup-bun-deps
|
|
81
|
-
|
|
82
|
-
- name: Install kubeconform
|
|
83
|
-
run: |
|
|
84
|
-
curl -sSL "https://github.com/yannh/kubeconform/releases/download/v0.7.0/kubeconform-linux-amd64.tar.gz" | tar xz
|
|
85
|
-
sudo mv kubeconform /usr/local/bin/
|
|
86
|
-
|
|
87
|
-
- name: Install kubescape
|
|
88
|
-
run: |
|
|
89
|
-
curl -s https://raw.githubusercontent.com/kubescape/kubescape/master/install.sh | /bin/bash
|
|
90
|
-
echo "$HOME/.kubescape/bin" >> $GITHUB_PATH
|
|
91
|
-
|
|
92
|
-
# kustomize не встановлюємо окремо — використовуємо вбудовану підкоманду `kubectl kustomize`
|
|
93
|
-
# (kubectl уже доступний на github-hosted runner'ах: https://github.com/actions/runner-images).
|
|
94
|
-
|
|
95
|
-
- name: Lint K8s
|
|
96
|
-
run: n-cursor lint k8s --read-only
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Deployment: `resources.requests` (CPU і memory)
|
|
100
|
-
|
|
101
|
-
У кожному контейнері **`Deployment`** обов’язкові **`resources.requests.cpu`** і **`resources.requests.memory`** (непорожні скаляри Kubernetes Quantity).
|
|
102
|
-
|
|
103
|
-
### Шар **`…/k8s/…/base/…`** (dev / щільний packing)
|
|
104
|
-
|
|
105
|
-
У **всіх** `Deployment` у файлах під **`…/k8s/…/base/…`** значення **жорстко фіксовані** (для **cpu** допускається число **`0.02`** у YAML):
|
|
106
|
-
|
|
107
|
-
```yaml
|
|
108
|
-
resources:
|
|
109
|
-
requests:
|
|
110
|
-
cpu: '0.02'
|
|
111
|
-
memory: '128Mi'
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
**HPA і PDB у base не тримаємо**: ні локальних `hpa.yaml` / `pdb.yaml` поруч із workload-manifest-файлами, ні через `resources` / `components` / `bases`. Канон — sibling каталог **`components/`** (Kustomize Component) поруч з `base/`, який підключають лише прод-overlays (див. розділ нижче). **NetworkPolicy** — навпаки: **обов'язковий і у `base/`**, у вигляді `base/networkpolicy.yaml` поруч з workload-маніфестом, підключений через `base/kustomization.yaml` `resources:` — щоб обмеження діяли і на dev-середовищі.
|
|
115
|
-
|
|
116
|
-
### Поза base (оверлеї, окремі каталоги)
|
|
117
|
-
|
|
118
|
-
Якщо ще не підібрано власні ліміти під сервіс, орієнтир для **`requests`**:
|
|
119
|
-
|
|
120
|
-
```yaml
|
|
121
|
-
resources:
|
|
122
|
-
requests:
|
|
123
|
-
cpu: '0.5'
|
|
124
|
-
memory: '512Mi'
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
У прод-оверлеях підіймай **`cpu` / `memory`** до реального споживання через **`patches`** або окремі фрагменти маніфестів. **`check k8s`** не вимагає саме **`0.5` / `512Mi`** поза base — лише непорожні **`requests.cpu`** і **`requests.memory`**.
|
|
128
|
-
|
|
129
|
-
```yaml title="k8s/prod/kustomization.yaml (фрагмент)"
|
|
130
|
-
patches:
|
|
131
|
-
- target:
|
|
132
|
-
kind: Deployment
|
|
133
|
-
name: backend-api
|
|
134
|
-
patch: |-
|
|
135
|
-
- op: replace
|
|
136
|
-
path: /spec/template/spec/containers/0/resources/requests/cpu
|
|
137
|
-
value: '500m'
|
|
138
|
-
- op: replace
|
|
139
|
-
path: /spec/template/spec/containers/0/resources/requests/memory
|
|
140
|
-
value: 1Gi
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
Поле **`imagePullPolicy`** скрипт **не** перевіряє (залишається політиці Kubernetes за тегом образу).
|
|
144
|
-
|
|
145
|
-
Образ **`hasura/graphql-engine`**: дозволений лише канонічний тег зі списку **`allowed_hasura_images`** у rego-пакеті **`k8s.manifest`** (`policy/manifest/manifest.rego` — єдине джерело істини; допускається префікс **`docker.io/`**); решта — помилка **check k8s**.
|
|
146
|
-
|
|
147
|
-
### HTTPRoute для Deployment з `hasura/graphql-engine`
|
|
148
|
-
|
|
149
|
-
**Прив'язка:** **check k8s** вважає **HTTPRoute** Hasura-маршрутом, якщо в **тому самому каталозі** є **`Deployment`** з образом **`hasura/graphql-engine`**, а його **`metadata.name`** збігається з **`metadata.name`** цього **`HTTPRoute`**. Саме за цією конвенцією скрипт шукає пари для звірки канону.
|
|
150
|
-
|
|
151
|
-
**Префікс параметризовано:** **`<prefix>`** — рядок перед **`/ql`** у першому Hasura-правилі (**`Exact <prefix>/ql`**). Може бути порожнім (**`<prefix>`** = **``**, шлях **`/ql`**) або непорожнім (наприклад **`<prefix>`** = **`/notify`**, шлях **`/notify/ql`**). Усі інші Hasura-правила цього **HTTPRoute** мають містити той самий **`<prefix>`**.
|
|
152
|
-
|
|
153
|
-
**Канон — 4 правила у цьому порядку** (додаткові правила поверх канону дозволені — вони просто ігноруються під час зіставлення):
|
|
154
|
-
|
|
155
|
-
1. **`Exact <prefix>/ql`** → **`RequestRedirect`** **`ReplaceFullPath <prefix>/ql/console`** **`statusCode: 302`**.
|
|
156
|
-
2. **`Exact <prefix>/ql/`** → те саме (редирект на **`<prefix>/ql/console`** 302).
|
|
157
|
-
3. **`PathPrefix <prefix>/ql`** → **`URLRewrite`** **`ReplacePrefixMatch /`**, один **`backendRef`** на headless **Service** (**`-hl`**).
|
|
158
|
-
4. **WebSocket:** **`PathPrefix <prefix>/ql`** + header **`Upgrade: websocket`** → **`URLRewrite`** **`ReplacePrefixMatch /`** + **`RequestHeaderModifier`** **`remove: [Authorization]`** (авторизація для WebSocket іде всередині messages). Той самий **`backendRef`**.
|
|
159
|
-
|
|
160
|
-
**`parentRefs`**, **`hostnames`**, **`metadata.namespace`** / **`name`** підлаштуй під середовище. У **`backendRefs.name`** вказуй **headless** **Service** з суфіксом **`-hl`** (див. розділ **«Service: `svc.yaml` і `svc-hl.yaml`»**); у прикладі **`db-h-hl`** заміни на фактичне ім’я.
|
|
161
|
-
|
|
162
|
-
```yaml
|
|
163
|
-
# yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/gateway.networking.k8s.io/httproute_v1beta1.json
|
|
164
|
-
apiVersion: gateway.networking.k8s.io/v1
|
|
165
|
-
kind: HTTPRoute
|
|
166
|
-
metadata:
|
|
167
|
-
name: db-h
|
|
168
|
-
namespace: dev
|
|
169
|
-
spec:
|
|
170
|
-
parentRefs:
|
|
171
|
-
- group: gateway.networking.k8s.io
|
|
172
|
-
kind: Gateway
|
|
173
|
-
name: gw
|
|
174
|
-
namespace: dev
|
|
175
|
-
sectionName: https
|
|
176
|
-
hostnames:
|
|
177
|
-
- aiml.live
|
|
178
|
-
rules:
|
|
179
|
-
- matches:
|
|
180
|
-
- path:
|
|
181
|
-
type: Exact
|
|
182
|
-
value: /ql
|
|
183
|
-
filters:
|
|
184
|
-
- type: RequestRedirect
|
|
185
|
-
requestRedirect:
|
|
186
|
-
path:
|
|
187
|
-
type: ReplaceFullPath
|
|
188
|
-
replaceFullPath: /ql/console
|
|
189
|
-
statusCode: 302
|
|
190
|
-
- matches:
|
|
191
|
-
- path:
|
|
192
|
-
type: Exact
|
|
193
|
-
value: /ql/
|
|
194
|
-
filters:
|
|
195
|
-
- type: RequestRedirect
|
|
196
|
-
requestRedirect:
|
|
197
|
-
path:
|
|
198
|
-
type: ReplaceFullPath
|
|
199
|
-
replaceFullPath: /ql/console
|
|
200
|
-
statusCode: 302
|
|
201
|
-
- matches:
|
|
202
|
-
- path:
|
|
203
|
-
type: PathPrefix
|
|
204
|
-
value: /ql
|
|
205
|
-
filters:
|
|
206
|
-
- type: URLRewrite
|
|
207
|
-
urlRewrite:
|
|
208
|
-
path:
|
|
209
|
-
type: ReplacePrefixMatch
|
|
210
|
-
replacePrefixMatch: /
|
|
211
|
-
backendRefs:
|
|
212
|
-
- name: db-h-hl
|
|
213
|
-
port: 8080
|
|
214
|
-
# у websocket авторизація йде всередині messages
|
|
215
|
-
# Той самий URLRewrite, що й для HTTP: інакше бекенд бачить /ql/v1/graphql замість /v1/graphql
|
|
216
|
-
- matches:
|
|
217
|
-
- path:
|
|
218
|
-
type: PathPrefix
|
|
219
|
-
value: /ql
|
|
220
|
-
headers:
|
|
221
|
-
- type: Exact
|
|
222
|
-
name: Upgrade
|
|
223
|
-
value: websocket
|
|
224
|
-
filters:
|
|
225
|
-
- type: URLRewrite
|
|
226
|
-
urlRewrite:
|
|
227
|
-
path:
|
|
228
|
-
type: ReplacePrefixMatch
|
|
229
|
-
replacePrefixMatch: /
|
|
230
|
-
- type: RequestHeaderModifier
|
|
231
|
-
requestHeaderModifier:
|
|
232
|
-
remove:
|
|
233
|
-
- Authorization
|
|
234
|
-
backendRefs:
|
|
235
|
-
- name: db-h-hl
|
|
236
|
-
port: 8080
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
## Service: заборонені анотації GKE
|
|
240
|
-
|
|
241
|
-
У **`kind: Service`** не додавай у **`metadata.annotations`** **`cloud.google.com/neg`** і **`cloud.google.com/backend-config`** (legacy під Ingress / старе балансування GKE). **check k8s** падає, якщо ключ є.
|
|
242
|
-
|
|
243
|
-
## Service: `svc.yaml` і `svc-hl.yaml` (Gateway API)
|
|
244
|
-
|
|
245
|
-
Пара файлів для кластерного й headless **Service**: **`svc.yaml`** + **`svc-hl.yaml`** в одному каталозі, **`spec.type: ClusterIP`** / **`clusterIP: None`**, імена **`-hl`**, узгодженість пар, маршрути **`gateway.networking.k8s.io`** (**HTTPRoute**, **GRPCRoute**, **TCPRoute**, **TLSRoute**, **UDPRoute**) — **backendRef** лише на сервіси з суфіксом **`-hl`**; якщо **kustomization** посилається на **`svc.yaml`**, у **тому ж** **`kustomization.yaml`** має бути посилання на sibling **`svc-hl.yaml`**. Скрипт **не** створює файли — додай **`svc-hl.yaml`** вручну (копія з правками **name** / **clusterIP**).
|
|
246
|
-
|
|
247
|
-
**Точні умови та повідомлення `fail`** — верхній JSDoc **`npm/scripts/rules/k8s/fix.mjs`**.
|
|
248
|
-
|
|
249
|
-
### Gateway API: не дублюй namespace у `backendRef`
|
|
250
|
-
|
|
251
|
-
У маршрутах Gateway API (**HTTPRoute**, **GRPCRoute**, **TCPRoute**, **TLSRoute**, **UDPRoute**) у `spec.rules[*].backendRefs[*]` **не** додавай поле **`namespace`**, якщо його значення збігається з **`metadata.namespace`** самого маршруту. За замовчуванням Gateway API визначає backend у тому ж namespace, що й маршрут, тож такий рядок — мертвий: він плутає під час перенесень між середовищами і ламається мовчки, якщо overlay змінює namespace маршруту через Kustomize, а в backendRef залишився старий рядок. Прибери поле — поведінка не зміниться. **`check k8s`** падає на такому збігу.
|
|
252
|
-
|
|
253
|
-
```yaml
|
|
254
|
-
# ❌ погано — namespace дублює metadata.namespace маршруту
|
|
255
|
-
apiVersion: gateway.networking.k8s.io/v1
|
|
256
|
-
kind: HTTPRoute
|
|
257
|
-
metadata:
|
|
258
|
-
name: admin-site
|
|
259
|
-
namespace: dev-b2b
|
|
260
|
-
spec:
|
|
261
|
-
rules:
|
|
262
|
-
- backendRefs:
|
|
263
|
-
- name: auth-hl
|
|
264
|
-
namespace: dev-b2b # ← прибери
|
|
265
|
-
port: 8080
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
```yaml
|
|
269
|
-
# ✅ добре — namespace лише в metadata
|
|
270
|
-
apiVersion: gateway.networking.k8s.io/v1
|
|
271
|
-
kind: HTTPRoute
|
|
272
|
-
metadata:
|
|
273
|
-
name: admin-site
|
|
274
|
-
namespace: dev-b2b
|
|
275
|
-
spec:
|
|
276
|
-
rules:
|
|
277
|
-
- backendRefs:
|
|
278
|
-
- name: auth-hl
|
|
279
|
-
port: 8080
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
Якщо backend **дійсно** живе в іншому namespace — поле **`namespace`** у `backendRef` обов'язкове (і потрібен `ReferenceGrant` у тому namespace); правило цього випадку не торкається.
|
|
283
|
-
|
|
284
|
-
## ConfigMap: ім'я збігається з Deployment
|
|
285
|
-
|
|
286
|
-
Якщо в `k8s/base/` є **`configmap.yaml`** і **Deployment**, і цей Deployment посилається рівно на **один** ConfigMap — `metadata.name` ConfigMap має збігатися з `metadata.name` Deployment. Точні умови перевірки — **`rules/k8s/fix.mjs`**.
|
|
287
|
-
|
|
288
|
-
## ConfigMap для Hasura-Deployment
|
|
289
|
-
|
|
290
|
-
Якщо в `k8s/base/` поруч із **`configmap.yaml`** є **Deployment** з образом **`hasura/graphql-engine`**, у `data` ConfigMap **обов'язково** мають бути env-ключі:
|
|
291
|
-
|
|
292
|
-
- **`HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS`** зі значенням **`"true"`**;
|
|
293
|
-
- **`HASURA_GRAPHQL_ENABLE_RELAY`** зі значенням **`"false"`**;
|
|
294
|
-
- **`HASURA_GRAPHQL_ENABLE_TELEMETRY`** зі значенням **`"false"`**;
|
|
295
|
-
- **`HASURA_GRAPHQL_ENABLED_LOG_TYPES`** зі значенням **`"startup,http-log"`** (точний рядок);
|
|
296
|
-
- **`HASURA_GRAPHQL_ENABLED_APIS`** зі значенням **`"metadata,graphql,pgdump"`** (точний рядок) — **значення для base/dev**;
|
|
297
|
-
- **`HASURA_GRAPHQL_DISABLE_EVENTING`** — ключ обов'язковий, значення довільне (за замовчуванням **`"true"`**).
|
|
298
|
-
|
|
299
|
-
Точні умови перевірки — rego-пакет **`k8s.hasura_configmap`** (cross-file прив'язка ConfigMap↔Deployment — у `rules/k8s/js/manifests.mjs`).
|
|
300
|
-
|
|
301
|
-
```yaml
|
|
302
|
-
data:
|
|
303
|
-
HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS: 'true'
|
|
304
|
-
HASURA_GRAPHQL_ENABLE_RELAY: 'false'
|
|
305
|
-
HASURA_GRAPHQL_ENABLE_TELEMETRY: 'false'
|
|
306
|
-
HASURA_GRAPHQL_ENABLED_LOG_TYPES: 'startup,http-log'
|
|
307
|
-
HASURA_GRAPHQL_ENABLED_APIS: 'metadata,graphql,pgdump'
|
|
308
|
-
HASURA_GRAPHQL_DISABLE_EVENTING: 'true'
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
### `HASURA_GRAPHQL_ENABLED_APIS` поза base/dev
|
|
312
|
-
|
|
313
|
-
`pgdump` дозволено **лише** для **base**/**dev**. Кожен **не-base** overlay (`k8s/<env>/`, де `<env>` ≠ `base`/`dev`), що успадковує Hasura-base, **зобов'язаний** у своєму **`kustomization.yaml`** перевизначити `HASURA_GRAPHQL_ENABLED_APIS` до **`"metadata,graphql"`** (без `pgdump`) — патчем JSON6902 або Strategic Merge на ціль **ConfigMap**. Перевірка — cross-file у `rules/k8s/js/manifests.mjs` (`validateHasuraOverlayEnabledApisOverride`); `kind: Component` пропускається.
|
|
314
|
-
|
|
315
|
-
```yaml title="k8s/prod/kustomization.yaml"
|
|
316
|
-
patches:
|
|
317
|
-
- target:
|
|
318
|
-
kind: ConfigMap
|
|
319
|
-
name: db-h
|
|
320
|
-
patch: |
|
|
321
|
-
- op: replace
|
|
322
|
-
path: /data/HASURA_GRAPHQL_ENABLED_APIS
|
|
323
|
-
value: metadata,graphql
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
## Kustomize: структура каталогів (`base` / overlays)
|
|
327
|
-
|
|
328
|
-
Трансформуй дерева **`**/k8s`**, щоб **винести спільне** через [Kustomize](https://kustomize.io/): один канонічний **`base`** і тонкі **overlays** для інших середовищ.
|
|
329
|
-
|
|
330
|
-
### Джерело правди — середовище dev
|
|
331
|
-
|
|
332
|
-
- За основу бери **все, що відповідає середовищу dev** (як воно має виглядати в кластері для dev).
|
|
333
|
-
- У **такому вигляді** цей набір стає каталогом **`base`**: спільні маніфести без окремої директорії **`dev/`**.
|
|
334
|
-
- Окремої директорії **`dev`** **не повинно існувати**: за середовище **dev** відповідає **`base`** (застосування **`kubectl apply -k …/base`** або збірка з overlay, який посилається на `base`).
|
|
335
|
-
|
|
336
|
-
### Overlays (не-dev)
|
|
337
|
-
|
|
338
|
-
- У каталозі кожного іншого середовища (наприклад **`ua/`**, **`prod/`**) має бути **мінімум файлів**: типово лише **`kustomization.yaml`** (посилання на `base`, `patches`, `replacements`, `components` тощо) і ресурси чи додаткові YAML, **необхідні лише для цього overlay**.
|
|
339
|
-
- Відмінності від dev вносяться **оверрайдами** (patches, `images`, `replicas`, `configMapGenerator` тощо), а не копіюванням повного дерева з `base`.
|
|
340
|
-
|
|
341
|
-
### Namespace
|
|
342
|
-
|
|
343
|
-
- **`base/kustomization.yaml`:** поле **`namespace:`** має бути **непорожнім** (перевіряє **check k8s**, якщо файл є).
|
|
344
|
-
|
|
345
|
-
- **Коли `metadata.namespace` обов’язковий у файлі:** YAML під **`k8s`** — непорожній **`metadata.namespace`** для namespaced **kind** (винятки — кластерні **kind**, перелік **`CLUSTER_SCOPED_KINDS`** у **`rules/k8s/fix.mjs`**). У overlays Kustomize значення в маніфесті буде перезаписано полем **`namespace:`** з відповідного **`kustomization.yaml`**, тому в `base` пиши канонічний dev-namespace.
|
|
346
|
-
|
|
347
|
-
- **Не додавай** окремі **patches** Kustomize, які лише змінюють **namespace**: **namespace** визначає Kustomize; у overlays додаткові зміни — без дублювання логіки **namespace**.
|
|
348
|
-
|
|
349
|
-
### Рядки, що змінюються між середовищами
|
|
350
|
-
|
|
351
|
-
- У manifest-файлах у **`base`** для полів (або значень), які **будуть відрізнятися** в інших середовищах, на **тому самому рядку** додай коментар:
|
|
352
|
-
|
|
353
|
-
```yaml
|
|
354
|
-
image: my-app:dev-tag # буде замінено через kustomize
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
Текст коментаря узгодь у команді; важливо, щоб було видно, що значення **навіть у base** може бути замінене overlay.
|
|
358
|
-
|
|
359
|
-
### Міграція зі старої структури
|
|
360
|
-
|
|
361
|
-
- Після перенесення маніфестів у **`base`** та overlays і перевірки (**`check k8s`**, **`lint-k8s`**) **видали** застарілі файли та директорії, які замінені новою схемою (дубльовані копії, колишні шляхи без Kustomize), щоб у репозиторії не залишалося зайвих або суперечливих маніфестів.
|
|
362
|
-
|
|
363
|
-
### Зміна image — через `images:`, не через `patches[]`
|
|
364
|
-
|
|
365
|
-
Підміну image у Pod-шаблоні Deployment в overlay роби директивою `images:`, а не JSON6902-патчем `op: replace` на `/spec/template/spec/containers/<N>/image`. `images:` — канонічний механізм Kustomize (матчить за іменем образу, не за індексом контейнера, тож стійкий до перестановки).
|
|
366
|
-
|
|
367
|
-
- У **`name`** — те, що **дослівно** стоїть у `image:` у base (або в попередньому шарі) **без тегу**: інакше підміна не спрацює. Тег у `name` зайвий — Kustomize матчить лише за іменем.
|
|
368
|
-
- **`newName`** — кінцеве ім'я образу (як правило, збігається з `name`); `**newTag**` — тег для прода. Якщо `newTag` дорівнює тегу, який вже в base, його можна не вказувати.
|
|
369
|
-
- **`digest`** (`@sha256:…`) у `name` / `newName` не чіпай — це не тег.
|
|
370
|
-
|
|
371
|
-
```yaml title="k8s/prod/kustomization.yaml (фрагмент)"
|
|
372
|
-
images:
|
|
373
|
-
- name: europe-west4-docker.pkg.dev/abie-ua/c/apply-on-invoice-discount
|
|
374
|
-
newName: europe-west4-docker.pkg.dev/abie-ua/c/apply-on-invoice-discount
|
|
375
|
-
newTag: v2025-04-29
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
**`check k8s` автоматично** для кожного `kustomization.yaml`:
|
|
379
|
-
|
|
380
|
-
1. конвертує кожну JSON6902-операцію `op: replace` на `/spec/template/spec/containers/<N>/image` (target `kind: Deployment`) у запис `images:` (визначає оригінальний image у base через `resources:` / `bases` / `components` / `crds`). Якщо у `patches[i].patch` після конвертації не залишилось ops — патч прибирається повністю; інакше у `patches[i].patch` залишаються лише не-image ops у вихідному порядку;
|
|
381
|
-
2. чистить існуючий блок `images:` — зрізає `:tag` з `name` і видаляє `newTag`, який збігається з відрізаним тегом.
|
|
382
|
-
|
|
383
|
-
## Ingress → Gateway API (GKE)
|
|
384
|
-
|
|
385
|
-
Якщо в дереві **`k8s`** трапляється маніфест з **`kind: Ingress`**, його потрібно **замінити на Gateway API**, а не залишати Ingress.
|
|
386
|
-
|
|
387
|
-
1. **HTTPRoute** — окремий файл **`hr.yaml`** (або узгоджене ім’я в команді), **`kind: HTTPRoute`**, `apiVersion` з групи **`gateway.networking.k8s.io`** (див. приклад `$schema` для HTTPRoute у розділі «Визначення схеми YAML»).
|
|
388
|
-
2. **HealthCheckPolicy (GKE)** — окремий файл **`hc.yaml`**, наприклад:
|
|
389
|
-
|
|
390
|
-
```yaml
|
|
391
|
-
apiVersion: networking.gke.io/v1
|
|
392
|
-
kind: HealthCheckPolicy
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
Для `$schema` у першому рядку див. приклад **HealthCheckPolicy** у тому ж розділі (datree CRDs-catalog).
|
|
396
|
-
|
|
397
|
-
**`spec.targetRef`** (типово **`kind: Service`**) має вказувати на **headless** сервіс — ім’я з суфіксом **`-hl`** (див. **«Service: `svc.yaml` і `svc-hl.yaml`»**); для проєктів **abie** точні умови — Rego-пакет **`abie.health_check_policy`** + **abie.mdc**.
|
|
398
|
-
|
|
399
|
-
За потреби розшир **`target`** (`name`, `namespace`), щоб однозначно вказати об’єкт.
|
|
400
|
-
|
|
401
|
-
**`check k8s`:** заборонено **`kind: Ingress`**.
|
|
402
|
-
|
|
403
|
-
## Deployment: `topologySpreadConstraints`, HPA / PDB через `components/`, NetworkPolicy у `base/`
|
|
404
|
-
|
|
405
|
-
Для **кожного** `kind: Deployment` у каталозі **`…/k8s/…/base/`** (у будь-якому файлі `.yaml`, наприклад **`deploy.yaml`**, **`deployment.yaml`**) сам Deployment має канонічні **`spec.template.spec.topologySpreadConstraints`**, а **HPA і PDB** живуть у **sibling каталозі** **`…/k8s/…/components/`** (Kustomize Component, фіксована назва каталогу — `components`). У `base/` локальні `hpa.yaml` і `pdb.yaml` **заборонені** (file-existence error). У дереві base-kustomize HPA / PDB також **не дозволені** через `resources` / `components` / `bases`.
|
|
406
|
-
|
|
407
|
-
Кожен **Deployment** має явно задавати безпечну rollout strategy: **`spec.strategy.type: RollingUpdate`**, **`spec.strategy.rollingUpdate.maxUnavailable: 0`**, **`spec.strategy.rollingUpdate.maxSurge: 1`**. Це гарантує, що під час оновлення image Kubernetes спершу створить один новий Pod, дочекається його `Ready`, і лише потім прибере один старий Pod; для prod-overlays, що успадковують `base`, це запобігає короткому вікну без ready backend-ів.
|
|
408
|
-
|
|
409
|
-
```yaml
|
|
410
|
-
strategy:
|
|
411
|
-
type: RollingUpdate
|
|
412
|
-
rollingUpdate:
|
|
413
|
-
maxUnavailable: 0
|
|
414
|
-
maxSurge: 1
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
**NetworkPolicy** — інша історія: оскільки обмеження мережі мають діяти **і на dev**, NP лежить **у `base/`** (а не в `components/`). Для **кожного** з **`Deployment`**, **`StatefulSet`**, **`DaemonSet`**, **`Job`**, **`CronJob`** під `k8s` обов'язковий **NetworkPolicy**: у **`…/k8s/…/base/`** — у **`base/networkpolicy.yaml`** поруч з workload-маніфестом (multi-doc, якщо workload-ів кілька); у **не-base** — **`networkpolicy.yaml`** поруч із маніфестом workload у тому ж каталозі (overlay-specific override). `metadata.name` NetworkPolicy **= `metadata.name`** workload; `spec.podSelector.matchLabels.app` **= мітка `app`** з `spec.selector.matchLabels` (для **Job** — з `spec.template.metadata.labels.app`, для **CronJob** — з `spec.jobTemplate.spec.template.metadata.labels.app`; у Job/CronJob ручний `spec.selector` невалідний без `manualSelector: true`, тож джерело — pod-template labels). У `base/kustomization.yaml` `resources:` має бути `networkpolicy.yaml`. Відсутні документи **`check k8s`** створює автоматично і додає `networkpolicy.yaml` у `base/kustomization.yaml` `resources:`.
|
|
418
|
-
|
|
419
|
-
**Канонічна структура `<pkg>/k8s/components/`** (sibling до `base/`) — лише HPA і PDB:
|
|
420
|
-
|
|
421
|
-
- **`kustomization.yaml`** — `apiVersion: kustomize.config.k8s.io/v1alpha1`, `kind: Component`, `resources: [hpa.yaml, pdb.yaml]` (відсортовано за алфавітом).
|
|
422
|
-
- **`hpa.yaml`** — `autoscaling/v2`, `HorizontalPodAutoscaler`, **без** `metadata.namespace` (namespace задає kustomization-споживач), `spec.scaleTargetRef.name` **= `metadata.name`** Deployment з base, dev-like значення `minReplicas: 1`, `maxReplicas: 1`.
|
|
423
|
-
- **`pdb.yaml`** — `policy/v1`, `PodDisruptionBudget`, **без** `metadata.namespace`, `spec.selector.matchLabels.app` **= `spec.selector.matchLabels.app`** Deployment, dev-like `minAvailable: 0`.
|
|
424
|
-
|
|
425
|
-
**Канонічний `base/networkpolicy.yaml`** — `networking.k8s.io/v1`, `NetworkPolicy`, **без** `metadata.namespace` (namespace додає `base/kustomization.yaml`); один або кілька документів (`---`) — по одному на кожен workload (**Deployment** / **StatefulSet** / **DaemonSet** / **Job** / **CronJob**) у тому ж `base/`; `metadata.name` **= `metadata.name`** workload, `spec.podSelector.matchLabels.app` **= мітка `app`** workload. **Ingress:** `from.podSelector: {}` (інші Pod у namespace). **Egress (усі workload-и):** kube-dns через `kube-system` namespaceSelector (UDP/TCP 53); link-local DNS `169.254.0.0/16` (UDP/TCP 53, GKE NodeLocal DNSCache); **TCP 80 і 443** на `0.0.0.0/0` (HTTP/HTTPS назовні); **in-cluster** — `to.namespaceSelector: {}` з **явним списком TCP-портів** (`80, 443, 5432, 3306, 1433, 6379, 8080, 4222, 4317, 4318`; трафік на `*.svc` / Pod-и в кластері). **StatefulSet** додатково має egress/ingress `to/from.podSelector: {}` для intra-replica реплікації. Заборонено: `egress: [{}]`; `to.namespaceSelector: {}` без `ports:` (catch-all). Додаткові правила можна дописати поряд — superset-підхід. Канон — два **повних** snippet-файли (без merge у runtime; JS-генератор/rego обирають один за `kind` workload-у через анотацію `metadata.annotations['nitra.dev/workload-kind']`): [deployment.snippet.yaml](./policy/network_policy/template/deployment.snippet.yaml) (для `Deployment`/`Job`/`CronJob`/`DaemonSet`) та [stateful-set.snippet.yaml](./policy/network_policy/template/stateful-set.snippet.yaml) (для `StatefulSet`; містить deployment-канон + intra-replica правила). Якщо документа для workload немає — **`check k8s`** дописує його автоматично.
|
|
426
|
-
|
|
427
|
-
Інші назви каталогу (`scale/`, `hpa-component/`, `pdb-component/`) — fail.
|
|
428
|
-
|
|
429
|
-
**Overlays** (`ua/`, прод-overlays) підключають `components: [- ../components]` і додають JSON6902-патчі для прод-значень: `/spec/minReplicas`, `/spec/maxReplicas` (HPA), `/spec/minAvailable` (PDB). NetworkPolicy успадковується з base через `resources: [- ../base]` — окремий `networkpolicy.yaml` у overlay не обов'язковий і додається тільки для overlay-specific правил. Dev-середовище (`base`) HPA/PDB не отримує — як і потрібно — а NetworkPolicy діє з тим самим каноном.
|
|
430
|
-
|
|
431
|
-
**`<pkg>/k8s/components/kustomization.yaml`** має `kind: Component` (не `kind: Kustomization`) — це **джерело** канонічних HPA/PDB для всіх overlays, а не overlay сам по собі. Прод-перезаписи (`/spec/minReplicas`, `/spec/maxReplicas`, `/spec/minAvailable`) живуть у `<env>/kustomization.yaml`, що підключає Component через `components:`. У самому Component patches не потрібні — він env-неутральний; **`check k8s`** не вимагає прод-патчів від `components/kustomization.yaml`.
|
|
432
|
-
|
|
433
|
-
У **не-base** оверлеях (без `components/`) поруч із `Deployment` лишається звична схема: окремі **`hpa.yaml`**, **`networkpolicy.yaml`** і **`pdb.yaml`**, якщо такі потрібні для цього середовища (NP overlay-specific override). Для **StatefulSet**, **DaemonSet**, **Job**, **CronJob** у не-base — обов'язкового локального `networkpolicy.yaml` немає, бо NP вже успадковується з base; додавайте лише якщо overlay змінює правила. **`check k8s`** звіряє прив'язку за іменами (і **дописує** відсутні NetworkPolicy-документи автоматично у файл поруч):
|
|
434
|
-
|
|
435
|
-
- **`hpa.yaml`** (поза **`…/base/`**) — `autoscaling/v2`, `HorizontalPodAutoscaler`, `spec.scaleTargetRef.name` **= `metadata.name`** Deployment.
|
|
436
|
-
- **`networkpolicy.yaml`** (overlay-specific, опціональний) — той самий канон egress/ingress, що в `base/` (multi-doc, якщо у каталозі кілька workload-ів).
|
|
437
|
-
- **`pdb.yaml`** — `policy/v1`, `PodDisruptionBudget`, `spec.selector.matchLabels.app` **= `spec.selector.matchLabels.app`** Deployment.
|
|
438
|
-
- **`topologySpreadConstraints`** — запис з `maxSkew: 1`, `topologyKey: kubernetes.io/hostname`, `whenUnsatisfiable: ScheduleAnyway`, `labelSelector.matchLabels.app` рівне тій самій мітці `app`.
|
|
439
|
-
|
|
440
|
-
**Перевірка структури `components/`** (для кожного Deployment у `base/`): наявність каталогу, валідний `kustomization.yaml` як Component, `hpa.yaml` і `pdb.yaml` з відповідністю до Deployment-name / app-label. Алгоритм — функція `validateComponentsForBaseDeployment` у **`rules/k8s/fix.mjs`**.
|
|
441
|
-
|
|
442
|
-
**Локальні шляхи в `kustomization.yaml`:** кожен запис без `://` (remote) з `resources` / `bases` / `components` / `crds`, `patchesStrategicMerge`, `patches[].path`, `patchesJson6902[].path`, `configurations[]`, `replacements[].path` має вказувати на **існуючий** у репозиторії файл (`.yaml` / `.yml`) або **каталог**; биті посилання — помилка **`check k8s`**.
|
|
443
|
-
|
|
444
|
-
**Порядок `resources`:** у маніфесті з **`apiVersion: kustomize.config.k8s.io/…`**, **`kind: Kustomization`**, елементи **`resources:`** (лише непорожні рядки) мають бути **відсортовані за алфавітом (англ. локаль, як `localeCompare('en')` у `rules/k8s/fix.mjs`)**. Поля **`bases`**, **`components`**, **`crds`** цією перевіркою **не** впорядковуються.
|
|
445
|
-
|
|
446
|
-
### HTTPRoute → NetworkPolicy ingress (GCLB + Envoy)
|
|
447
|
-
|
|
448
|
-
Якщо в каталозі workload є **`HTTPRoute`** (Gateway API; `apiVersion: gateway.networking.k8s.io/*`) з **`backendRef`** на **`<workload>-hl`** Service (mapping через `service.spec.selector.app`), **`check k8s`** автоматично додає в NetworkPolicy цього workload **ingress-правило** з фіксованим набором CIDR-ів і **TCP-портами з `backendRefs[].port`** (дедуп, відсортовано за зростанням).
|
|
449
|
-
|
|
450
|
-
Без цього правила трафік від **GKE Gateway** (Envoy data-plane з proxy-only subnet регіону, наприклад `us-central1-proxy-only` = `10.10.0.0/23`) і **Google health checks** (`35.191.0.0/16`, `130.211.0.0/22`) блокується базовим NetworkPolicy (бо canon ingress допускає тільки `podSelector: {}` — intra-namespace pod ↔ pod).
|
|
451
|
-
|
|
452
|
-
CIDR-набір зафіксовано (без конфігурації):
|
|
453
|
-
|
|
454
|
-
- `35.191.0.0/16` — GCP HC global
|
|
455
|
-
- `130.211.0.0/22` — GCP HC global (legacy)
|
|
456
|
-
- `10.0.0.0/8` — широкий range, покриває proxy-only subnets усіх регіонів GKE
|
|
457
|
-
|
|
458
|
-
Приклад згенерованого ingress-правила (поверх baseline canon):
|
|
459
|
-
|
|
460
|
-
```yaml
|
|
461
|
-
ingress:
|
|
462
|
-
- from:
|
|
463
|
-
- podSelector: {}
|
|
464
|
-
- from:
|
|
465
|
-
- ipBlock: { cidr: 35.191.0.0/16 }
|
|
466
|
-
- ipBlock: { cidr: 130.211.0.0/22 }
|
|
467
|
-
- ipBlock: { cidr: 10.0.0.0/8 }
|
|
468
|
-
ports:
|
|
469
|
-
- { protocol: TCP, port: 8080 }
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
Якщо workload не прив'язаний до жодного HTTPRoute — правило **не** додається; NP лишається baseline (intra-namespace + canon egress). Не-HTTP routes (`GRPCRoute`, `TCPRoute`, `TLSRoute`, `UDPRoute`) поки не покриті — додається лише за HTTPRoute. Алгоритм: функція `collectHttpRouteIngressForWorkload` у **`rules/k8s/js/manifests.mjs`** індексує `HTTPRoute.backendRefs` і `Service` у каталозі, визначає через `selector.app`, дедуп TCP-портів. Виклики — з `appendNetworkPolicyDocuments` і `regenerateLegacyNetworkPolicyDocsInFile`.
|
|
473
|
-
|
|
474
|
-
### Env-залежні межі (за сегментом після `/k8s/`)
|
|
475
|
-
|
|
476
|
-
**Dev-like середовища** — сегмент `base`, `dev`, або з суфіксом `-qa` (напр. `tr-qa`):
|
|
477
|
-
|
|
478
|
-
- У **зібраному** маніфесті (після Kustomize), якщо лишився **HPA**: `minReplicas` — рівно **1**, `maxReplicas` — рівно **1**.
|
|
479
|
-
- PDB: `minAvailable` — рівно **0**.
|
|
480
|
-
|
|
481
|
-
**Прод-середовища** — усе інше (будь-який overlay без суфікса `-qa`):
|
|
482
|
-
|
|
483
|
-
- HPA: `minReplicas` — мінімум **2**, `maxReplicas` — мінімум **2**.
|
|
484
|
-
- PDB: `minAvailable` — мінімум **1**.
|
|
485
|
-
|
|
486
|
-
### Прод-оверрайди у `kustomization.yaml`
|
|
487
|
-
|
|
488
|
-
У прод-накладенні `kustomization.yaml`, що підключає `components: [- ../components]` (тобто overlay-tree містить **HorizontalPodAutoscaler** і **PodDisruptionBudget**), у `patches[]` **обов'язкові** JSON6902-перевизначення прод-значень:
|
|
489
|
-
|
|
490
|
-
- **`HorizontalPodAutoscaler`**: `/spec/minReplicas` і `/spec/maxReplicas` (мінімум 2).
|
|
491
|
-
- **`PodDisruptionBudget`**: `/spec/minAvailable` (мінімум 1).
|
|
492
|
-
|
|
493
|
-
Формат patch — JSON6902 або Strategic Merge; `check k8s` перевіряє **наявність** відповідних JSON Pointer-ів у **`patches[]`**.
|
|
494
|
-
|
|
495
|
-
```yaml title="k8s/prod/kustomization.yaml (фрагмент)"
|
|
496
|
-
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
497
|
-
kind: Kustomization
|
|
498
|
-
namespace: prod
|
|
499
|
-
resources:
|
|
500
|
-
- ../base
|
|
501
|
-
components:
|
|
502
|
-
- ../components
|
|
503
|
-
patches:
|
|
504
|
-
- target:
|
|
505
|
-
kind: HorizontalPodAutoscaler
|
|
506
|
-
name: backend-api
|
|
507
|
-
patch: |-
|
|
508
|
-
- op: replace
|
|
509
|
-
path: /spec/minReplicas
|
|
510
|
-
value: 2
|
|
511
|
-
- op: replace
|
|
512
|
-
path: /spec/maxReplicas
|
|
513
|
-
value: 10
|
|
514
|
-
- target:
|
|
515
|
-
kind: PodDisruptionBudget
|
|
516
|
-
name: backend-api
|
|
517
|
-
patch: |-
|
|
518
|
-
- op: replace
|
|
519
|
-
path: /spec/minAvailable
|
|
520
|
-
value: 1
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
### Приклади `components/` і `base/networkpolicy.yaml`
|
|
524
|
-
|
|
525
|
-
```yaml title="k8s/components/kustomization.yaml"
|
|
526
|
-
apiVersion: kustomize.config.k8s.io/v1alpha1
|
|
527
|
-
kind: Component
|
|
528
|
-
resources:
|
|
529
|
-
- hpa.yaml
|
|
530
|
-
- pdb.yaml
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
```yaml title="k8s/base/kustomization.yaml (фрагмент)"
|
|
534
|
-
apiVersion: kustomize.config.k8s.io/v1beta1
|
|
535
|
-
kind: Kustomization
|
|
536
|
-
namespace: dev
|
|
537
|
-
resources:
|
|
538
|
-
- deploy.yaml
|
|
539
|
-
- networkpolicy.yaml
|
|
540
|
-
- svc.yaml
|
|
541
|
-
- svc-hl.yaml
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
```yaml title="k8s/base/networkpolicy.yaml"
|
|
545
|
-
# yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.9-standalone-strict/networkpolicy-networking-v1.json
|
|
546
|
-
apiVersion: networking.k8s.io/v1
|
|
547
|
-
kind: NetworkPolicy
|
|
548
|
-
metadata:
|
|
549
|
-
name: backend-api
|
|
550
|
-
annotations:
|
|
551
|
-
nitra.dev/workload-kind: Deployment
|
|
552
|
-
spec:
|
|
553
|
-
podSelector:
|
|
554
|
-
matchLabels:
|
|
555
|
-
app: backend-api
|
|
556
|
-
policyTypes:
|
|
557
|
-
- Ingress
|
|
558
|
-
- Egress
|
|
559
|
-
ingress:
|
|
560
|
-
- from:
|
|
561
|
-
- podSelector: {}
|
|
562
|
-
egress:
|
|
563
|
-
- to:
|
|
564
|
-
- namespaceSelector:
|
|
565
|
-
matchLabels:
|
|
566
|
-
kubernetes.io/metadata.name: kube-system
|
|
567
|
-
podSelector:
|
|
568
|
-
matchLabels:
|
|
569
|
-
k8s-app: kube-dns
|
|
570
|
-
ports:
|
|
571
|
-
- protocol: UDP
|
|
572
|
-
port: 53
|
|
573
|
-
- protocol: TCP
|
|
574
|
-
port: 53
|
|
575
|
-
- to:
|
|
576
|
-
- ipBlock:
|
|
577
|
-
cidr: 169.254.0.0/16
|
|
578
|
-
ports:
|
|
579
|
-
- protocol: UDP
|
|
580
|
-
port: 53
|
|
581
|
-
- protocol: TCP
|
|
582
|
-
port: 53
|
|
583
|
-
- to:
|
|
584
|
-
- ipBlock:
|
|
585
|
-
cidr: 0.0.0.0/0
|
|
586
|
-
ports:
|
|
587
|
-
- protocol: TCP
|
|
588
|
-
port: 80
|
|
589
|
-
- protocol: TCP
|
|
590
|
-
port: 443
|
|
591
|
-
- to:
|
|
592
|
-
- namespaceSelector: {}
|
|
593
|
-
ports:
|
|
594
|
-
- protocol: TCP
|
|
595
|
-
port: 80
|
|
596
|
-
- protocol: TCP
|
|
597
|
-
port: 443
|
|
598
|
-
- protocol: TCP
|
|
599
|
-
port: 5432
|
|
600
|
-
- protocol: TCP
|
|
601
|
-
port: 3306
|
|
602
|
-
- protocol: TCP
|
|
603
|
-
port: 1433
|
|
604
|
-
- protocol: TCP
|
|
605
|
-
port: 6379
|
|
606
|
-
- protocol: TCP
|
|
607
|
-
port: 8080
|
|
608
|
-
- protocol: TCP
|
|
609
|
-
port: 4222
|
|
610
|
-
- protocol: TCP
|
|
611
|
-
port: 4317
|
|
612
|
-
- protocol: TCP
|
|
613
|
-
port: 4318
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
```yaml title="k8s/base/networkpolicy.yaml — workload з HTTPRoute (з GCLB ingress)"
|
|
617
|
-
# yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.9-standalone-strict/networkpolicy-networking-v1.json
|
|
618
|
-
apiVersion: networking.k8s.io/v1
|
|
619
|
-
kind: NetworkPolicy
|
|
620
|
-
metadata:
|
|
621
|
-
name: backend-api
|
|
622
|
-
annotations:
|
|
623
|
-
nitra.dev/workload-kind: Deployment
|
|
624
|
-
spec:
|
|
625
|
-
podSelector:
|
|
626
|
-
matchLabels:
|
|
627
|
-
app: backend-api
|
|
628
|
-
policyTypes:
|
|
629
|
-
- Ingress
|
|
630
|
-
- Egress
|
|
631
|
-
ingress:
|
|
632
|
-
- from:
|
|
633
|
-
- podSelector: {}
|
|
634
|
-
- from: # auto-added by check k8s for HTTPRoute-paired workloads
|
|
635
|
-
- ipBlock: { cidr: 35.191.0.0/16 }
|
|
636
|
-
- ipBlock: { cidr: 130.211.0.0/22 }
|
|
637
|
-
- ipBlock: { cidr: 10.0.0.0/8 }
|
|
638
|
-
ports:
|
|
639
|
-
- { protocol: TCP, port: 8080 }
|
|
640
|
-
egress:
|
|
641
|
-
# ... (ідентично до базового прикладу вище)
|
|
642
|
-
```
|
|
643
|
-
|
|
644
|
-
```yaml title="k8s/components/hpa.yaml"
|
|
645
|
-
# yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.9-standalone-strict/horizontalpodautoscaler-autoscaling-v2.json
|
|
646
|
-
apiVersion: autoscaling/v2
|
|
647
|
-
kind: HorizontalPodAutoscaler
|
|
648
|
-
metadata:
|
|
649
|
-
name: backend-api
|
|
650
|
-
spec:
|
|
651
|
-
scaleTargetRef:
|
|
652
|
-
apiVersion: apps/v1
|
|
653
|
-
kind: Deployment
|
|
654
|
-
name: backend-api
|
|
655
|
-
minReplicas: 1 # прод overlay підіймає до >= 2
|
|
656
|
-
maxReplicas: 1 # прод overlay підіймає до >= 2
|
|
657
|
-
metrics:
|
|
658
|
-
- type: Resource
|
|
659
|
-
resource:
|
|
660
|
-
name: cpu
|
|
661
|
-
target:
|
|
662
|
-
type: Utilization
|
|
663
|
-
averageUtilization: 70
|
|
664
|
-
behavior:
|
|
665
|
-
scaleUp:
|
|
666
|
-
stabilizationWindowSeconds: 15
|
|
667
|
-
policies:
|
|
668
|
-
- type: Percent
|
|
669
|
-
value: 100
|
|
670
|
-
periodSeconds: 30
|
|
671
|
-
- type: Pods
|
|
672
|
-
value: 4
|
|
673
|
-
periodSeconds: 30
|
|
674
|
-
selectPolicy: Max
|
|
675
|
-
scaleDown:
|
|
676
|
-
stabilizationWindowSeconds: 300
|
|
677
|
-
policies:
|
|
678
|
-
- type: Percent
|
|
679
|
-
value: 25
|
|
680
|
-
periodSeconds: 120
|
|
681
|
-
selectPolicy: Min
|
|
682
|
-
```
|
|
683
|
-
|
|
684
|
-
```yaml title="k8s/components/pdb.yaml"
|
|
685
|
-
# yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.9-standalone-strict/poddisruptionbudget-policy-v1.json
|
|
686
|
-
apiVersion: policy/v1
|
|
687
|
-
kind: PodDisruptionBudget
|
|
688
|
-
metadata:
|
|
689
|
-
name: backend-api
|
|
690
|
-
spec:
|
|
691
|
-
minAvailable: 0 # прод overlay підіймає до >= 1
|
|
692
|
-
selector:
|
|
693
|
-
matchLabels:
|
|
694
|
-
app: backend-api
|
|
695
|
-
```
|
|
696
|
-
|
|
697
|
-
```yaml title="k8s/base/deploy.yaml (фрагмент)"
|
|
698
|
-
spec:
|
|
699
|
-
template:
|
|
700
|
-
spec:
|
|
701
|
-
containers:
|
|
702
|
-
- name: backend-api
|
|
703
|
-
image: example.registry/backend-api:tag
|
|
704
|
-
resources:
|
|
705
|
-
requests:
|
|
706
|
-
cpu: '0.02'
|
|
707
|
-
memory: '128Mi'
|
|
708
|
-
topologySpreadConstraints:
|
|
709
|
-
- maxSkew: 1
|
|
710
|
-
topologyKey: kubernetes.io/hostname
|
|
711
|
-
whenUnsatisfiable: ScheduleAnyway
|
|
712
|
-
labelSelector:
|
|
713
|
-
matchLabels:
|
|
714
|
-
app: backend-api
|
|
715
|
-
```
|
|
716
|
-
|
|
717
|
-
Точні умови та повідомлення `fail` — JSDoc на початку `npm/scripts/rules/k8s/fix.mjs` і функції `validateComponentsForBaseDeployment` / `prodOverlayHpaPdbOverrideNeeds`.
|
|
718
|
-
|
|
719
|
-
## HorizontalPodAutoscaler: `autoscaling/v2`
|
|
720
|
-
|
|
721
|
-
У manifest-файлах під **`k8s`** заборонено **`apiVersion: autoscaling/v1`** (legacy HPA з єдиною метрикою CPU). Мігруй **HorizontalPodAutoscaler** на **`autoscaling/v2`**: поле **`spec.metrics`** (замість **`spec.targetCPUUtilizationPercentage`**) з **`type: Resource`** і **`target.type: Utilization`** / **`AverageUtilization`** — підтримує декілька метрик і зовнішні метрики. `check k8s` падає на будь-якому документі з **`apiVersion: autoscaling/v1`**.
|
|
722
|
-
|
|
723
|
-
Ресурси **batch** (наприклад **CronJob**, **Job**): застаріле **`apiVersion: batch/v1beta1`** у файлах під **`k8s` під час `check k8s` переписується** на **`apiVersion: batch/v1`**.
|
|
724
|
-
|
|
725
|
-
```yaml
|
|
726
|
-
# yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.9-standalone-strict/horizontalpodautoscaler-autoscaling-v2.json
|
|
727
|
-
apiVersion: autoscaling/v2
|
|
728
|
-
kind: HorizontalPodAutoscaler
|
|
729
|
-
metadata:
|
|
730
|
-
name: app
|
|
731
|
-
spec:
|
|
732
|
-
scaleTargetRef:
|
|
733
|
-
apiVersion: apps/v1
|
|
734
|
-
kind: Deployment
|
|
735
|
-
name: app
|
|
736
|
-
minReplicas: 1
|
|
737
|
-
maxReplicas: 5
|
|
738
|
-
metrics:
|
|
739
|
-
- type: Resource
|
|
740
|
-
resource:
|
|
741
|
-
name: cpu
|
|
742
|
-
target:
|
|
743
|
-
type: Utilization
|
|
744
|
-
averageUtilization: 70
|
|
745
|
-
```
|
|
746
|
-
|
|
747
|
-
3. **JSON patch у kustomization:** де можливо, змінюй ресурс через **`op: replace`** (одна операція на `path`), а не пару **`remove` + `add`** на той самий шлях. **`add`** / **`remove`** лишай лише коли **`replace`** не підходить (наприклад додати новий ключ або прибрати поле без заміни).
|
|
748
|
-
|
|
749
|
-
```yaml title="overlay/kustomization.yaml (фрагмент)"
|
|
750
|
-
patches:
|
|
751
|
-
- target:
|
|
752
|
-
kind: Deployment
|
|
753
|
-
name: x
|
|
754
|
-
patch: |-
|
|
755
|
-
- op: replace
|
|
756
|
-
path: /spec/template/spec/nodeSelector
|
|
757
|
-
value:
|
|
758
|
-
preem: "false"
|
|
759
|
-
```
|
|
760
|
-
|
|
761
|
-
### `patches[].target`: лише `kind` і `name`
|
|
762
|
-
|
|
763
|
-
У `patches[].target` залишай **тільки** **`kind`** і **`name`** — поля **`group`** і **`version`** прибирай. Kustomize визначає ціль за GVK+name; `group`/`version` — звужувальні фільтри, потрібні лише за реальної колізії `kind+name` між різними API-групами або версіями. У межах одного namespace apiserver зберігає об'єкт у єдиному storage-GVK, тож для звичайних маніфестів така колізія неможлива, і `group`/`version` у `target` — мертвий шум, який ламається мовчки під час змін API (наприклад, перехід `v1beta1` → `v1`).
|
|
764
|
-
|
|
765
|
-
```yaml
|
|
766
|
-
# ❌ зайві group / version
|
|
767
|
-
patches:
|
|
768
|
-
- target:
|
|
769
|
-
group: gateway.networking.k8s.io
|
|
770
|
-
version: v1beta1
|
|
771
|
-
kind: Gateway
|
|
772
|
-
name: gw
|
|
773
|
-
|
|
774
|
-
# ✅
|
|
775
|
-
patches:
|
|
776
|
-
- target:
|
|
777
|
-
kind: Gateway
|
|
778
|
-
name: gw
|
|
779
|
-
```
|
|
780
|
-
|
|
781
|
-
**Виняток:** залишай `group` / `version`, лише якщо в дереві overlay реально співіснують ресурси з однаковими `kind`+`name`, але різними API-групами/версіями (наприклад, дві CRD з одним `kind`). У такому разі вкажи мінімальний набір полів, потрібний для дисамбігуації.
|
|
782
|
-
|
|
783
|
-
### Структурний сорт `patches[]` і inline JSON6902
|
|
784
|
-
|
|
785
|
-
`patches[]` у `kustomization.yaml` має бути відсортовано за tuple **`target.kind` → `target.name` → `target.namespace` → `path`** (`localeCompare('en', { sensitivity: 'base' })`). Це робить діфи передбачуваними і прибирає «гойдання» порядку при додаванні нових цілей. Поля `target.group` / `target.version` у tuple не входять — для них діє правило «patches[].target: лише kind і name».
|
|
786
|
-
|
|
787
|
-
```yaml
|
|
788
|
-
# ❌ atlas йде перед apruv
|
|
789
|
-
patches:
|
|
790
|
-
- target:
|
|
791
|
-
kind: ReferenceGrant
|
|
792
|
-
name: atlas-to-base
|
|
793
|
-
- target:
|
|
794
|
-
kind: ReferenceGrant
|
|
795
|
-
name: apruv-to-base
|
|
796
|
-
|
|
797
|
-
# ✅
|
|
798
|
-
patches:
|
|
799
|
-
- target:
|
|
800
|
-
kind: ReferenceGrant
|
|
801
|
-
name: apruv-to-base
|
|
802
|
-
- target:
|
|
803
|
-
kind: ReferenceGrant
|
|
804
|
-
name: atlas-to-base
|
|
805
|
-
```
|
|
806
|
-
|
|
807
|
-
Усередині кожного inline `patches[i].patch` (literal block scalar — масив JSON6902-операцій) операції теж сортуються за **`path`**, **але лише** коли набір «безпечний»: усі ops — `add` / `replace` і всі `path` попарно дизʼюнктні (жоден не префікс іншого, наприклад `/spec` і `/spec/replicas`). Інакше порядок не чіпається — у `move` / `copy` / `test` / `remove` чи на спільних шляхах послідовність ops семантично значуща (RFC 6902), і пересорт ламає логіку.
|
|
16
|
+
## Активація
|
|
808
17
|
|
|
809
|
-
|
|
810
|
-
# ❌ minReplicas перед maxReplicas (за алфавітом max < min)
|
|
811
|
-
patch: |-
|
|
812
|
-
- op: add
|
|
813
|
-
path: /spec/minReplicas
|
|
814
|
-
value: 2
|
|
815
|
-
- op: replace
|
|
816
|
-
path: /spec/maxReplicas
|
|
817
|
-
value: 10
|
|
18
|
+
Правило активується автоматично для `**/k8s/**/*.yaml` (через `globs`). Додаткова конфігурація в `.n-cursor.json` не потрібна.
|
|
818
19
|
|
|
819
|
-
|
|
820
|
-
patch: |-
|
|
821
|
-
- op: replace
|
|
822
|
-
path: /spec/maxReplicas
|
|
823
|
-
value: 10
|
|
824
|
-
- op: add
|
|
825
|
-
path: /spec/minReplicas
|
|
826
|
-
value: 2
|
|
827
|
-
```
|
|
20
|
+
[k8s-schema-modeline](./js/schema_modeline.mdc)
|
|
828
21
|
|
|
829
|
-
|
|
22
|
+
[k8s-lint-k8s](./js/lint_k8s.mdc)
|
|
830
23
|
|
|
831
|
-
|
|
24
|
+
[k8s-deployment-resources](./js/deployment_resources.mdc)
|
|
832
25
|
|
|
833
|
-
|
|
834
|
-
2. **`apiVersion: v1`** → yannh, PIN набору схем **`v1.33.9-standalone-strict`**, ref репозиторію для raw URL — **`master`** (каталог версії не є коренем репо на GitHub):
|
|
835
|
-
`https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/<PIN>/<kind>-v1.json`
|
|
836
|
-
`<kind>`: літери в нижньому регістрі без роздільників між CamelCase (наприклад `Service` → `service`).
|
|
26
|
+
[k8s-hasura-httproute](./js/hasura_httproute.mdc)
|
|
837
27
|
|
|
838
|
-
|
|
28
|
+
[k8s-service](./js/service.mdc)
|
|
839
29
|
|
|
840
|
-
|
|
841
|
-
# yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.9-standalone-strict/secret-v1.json
|
|
842
|
-
```
|
|
30
|
+
[k8s-configmap](./js/configmap.mdc)
|
|
843
31
|
|
|
844
|
-
|
|
845
|
-
`https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/<PIN>/<kind>-<group-частина>-<version>.json`
|
|
846
|
-
де **`<group-частина>`** — **перший сегмент** `group` до першої крапки: для груп без крапок збігається з усією group (`apps/v1` + `Deployment` → `deployment-apps-v1.json`); для `*.k8s.io` / `*.apiserver.k8s.io` — лише префікс до `.k8s.io` (`networking.k8s.io/v1` + `Ingress` → `ingress-networking-v1.json`; `networking.k8s.io/v1` + `NetworkPolicy` → `networkpolicy-networking-v1.json`; `rbac.authorization.k8s.io/v1` + `ClusterRole` → `clusterrole-rbac-v1.json`; `flowcontrol.apiserver.k8s.io/v1` + `FlowSchema` → `flowschema-flowcontrol-v1.json`). У yannh **немає** файлів з фрагментом `-k8s-io-` у назві — патерн `<group-з-крапками-як-дефіси>` дає 404.
|
|
847
|
-
4. **Інакше** (CRD тощо) → [datreeio/CRDs-catalog](https://github.com/datreeio/CRDs-catalog). Типово для `$schema` у редакторі — **GitHub Pages**:
|
|
848
|
-
`https://datreeio.github.io/CRDs-catalog/<group>/<kind>_<version>.json`
|
|
849
|
-
(`<kind>` — лише літери та цифри в нижньому регістрі, без роздільників між CamelCase, як для yannh.)
|
|
32
|
+
[k8s-kustomize-structure](./js/kustomize_structure.mdc)
|
|
850
33
|
|
|
851
|
-
|
|
34
|
+
[k8s-topology-hpa-pdb](./js/topology_hpa_pdb.mdc)
|
|
852
35
|
|
|
853
|
-
|
|
854
|
-
# yaml-language-server: $schema=https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/secrets.infisical.com/infisicalsecret_v1alpha1.json
|
|
855
|
-
```
|
|
36
|
+
[k8s-network-policy](./js/network_policy.mdc)
|
|
856
37
|
|
|
857
|
-
|
|
38
|
+
[k8s-ingress-gateway](./js/ingress_gateway.mdc)
|
|
858
39
|
|
|
859
|
-
|
|
860
|
-
# yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/gateway.networking.k8s.io/httproute_v1beta1.json
|
|
861
|
-
```
|
|
40
|
+
[k8s-hpa-apiversion](./js/hpa_apiversion.mdc)
|
|
862
41
|
|
|
863
|
-
|
|
42
|
+
[k8s-multidoc-yaml](./js/multidoc_yaml.mdc)
|
|
864
43
|
|
|
865
|
-
|
|
866
|
-
# yaml-language-server: $schema=https://datreeio.github.io/CRDs-catalog/networking.gke.io/healthcheckpolicy_v1.json
|
|
867
|
-
```
|
|
44
|
+
## Швидкий gate через conftest (Rego)
|
|
868
45
|
|
|
869
|
-
|
|
46
|
+
Підмножину пер-документних правил продубльовано як rego-полісі у **`npm/rules/k8s/policy/`** (запускається через **`bun run lint-rego`** для `*_test.rego` юніт-тестів і через **`npx @nitra/cursor fix k8s`** для прогону по реальних YAML). JS authoritative; rego — швидкий gate для одиничного маніфеста.
|
|
870
47
|
|
|
871
|
-
|
|
48
|
+
Пакети (директорія в **`npm/rules/k8s/policy/`** → namespace → що перевіряє):
|
|
872
49
|
|
|
873
|
-
|
|
50
|
+
- **`manifest/`** → `k8s.manifest` — per-doc: заборона `kind: Ingress` і `autoscaling/v1`; заборонені GKE-анотації Service; resources.requests.cpu/memory в Deployment; дозволений образ hasura; RollingUpdate strategy; topologySpreadConstraints.
|
|
51
|
+
- **`base_manifest/`** → `k8s.base_manifest` — Deployment у `base/`: фіксовані cpu=`0.02` / memory=`128Mi`; непорожній `metadata.namespace` для namespaced kinds.
|
|
52
|
+
- **`base_kustomization/`** → `k8s.base_kustomization` — `base/kustomization.yaml`: непорожній `namespace:`; заборона `hpa.yaml`/`pdb.yaml` у `resources:`.
|
|
53
|
+
- **`gateway/`** → `k8s.gateway` — HTTPRoute/GRPCRoute/TCPRoute/TLSRoute/UDPRoute: backendRef на headless Service (суфікс `-hl`); заборона redundant `namespace` у backendRef; HealthCheckPolicy: `targetRef.name` з суфіксом `-hl`.
|
|
54
|
+
- **`hasura_configmap/`** → `k8s.hasura_configmap` — ConfigMap поруч з Hasura Deployment: обов'язкові env-ключі та значення.
|
|
55
|
+
- **`hasura_httproute/`** → `k8s.hasura_httproute` — HTTPRoute парний з Hasura Deployment: канон 4 правил (Exact redirect × 2 + PathPrefix rewrite + WebSocket).
|
|
56
|
+
- **`hpa_pdb/`** → `k8s.hpa_pdb` — структурна перевірка HPA (`autoscaling/v2`, spec.behavior, spec.metrics) і PDB (`policy/v1`, spec.selector.matchLabels).
|
|
57
|
+
- **`kustomization/`** → `k8s.kustomization` — `kustomization.yaml`: сортування `resources[]` і `patches[]`; заборона `remove`+`add` на той самий path (вимагає `replace`).
|
|
58
|
+
- **`network_policy/`** → `k8s.network_policy` — NetworkPolicy: apiVersion, spec.podSelector, policyTypes Ingress+Egress, superset-перевірка egress/ingress за snippet-файлами.
|
|
59
|
+
- **`svc_yaml/`** → `k8s.svc_yaml` — `svc.yaml`: `spec.type: ClusterIP`.
|
|
60
|
+
- **`svc_hl_yaml/`** → `k8s.svc_hl_yaml` — `svc-hl.yaml`: `metadata.name` з суфіксом `-hl`; `spec.clusterIP: None`.
|