From f39a2ddbf0816994a52a1a9a3e8b4e7a58cb836b Mon Sep 17 00:00:00 2001 From: Oleg Kishinskiy Date: Thu, 18 Jun 2026 16:48:42 +0300 Subject: [PATCH] Add Russian Translates --- 000_template/README_RU.md | 285 ++++ .../README_RU.md | 355 ++++ 003_kubernetes_tutorial/README_RU.md | 1519 +++++++++++++++++ 005_gitlab_ci/README_RU.md | 343 ++++ 4 files changed, 2502 insertions(+) create mode 100644 000_template/README_RU.md create mode 100644 001_github_actions_importing_workflows/README_RU.md create mode 100644 003_kubernetes_tutorial/README_RU.md create mode 100644 005_gitlab_ci/README_RU.md diff --git a/000_template/README_RU.md b/000_template/README_RU.md new file mode 100644 index 0000000..b314faa --- /dev/null +++ b/000_template/README_RU.md @@ -0,0 +1,285 @@ +# Руководство по стилю + +Используйте этот файл в качестве руководства по стилю при создании новых документов cue-by-example, и +используйте файл [template.md](template.md) в качестве шаблона, который можно копировать и адаптировать. + +## Соглашения + +### Заголовки разделов + +Используйте один заголовок H1 (`#`) вверху, чтобы обозначить название документа. + +В строке Markdown непосредственно под строкой H1 включите элемент HTML ``, +содержащий ваше предпочтительное указание авторства документа и для каждого соавтора. +Например: + +--- + +``` +# Использование CUE для продвижения в Голливуде +от [Джорджа Клуни](https://www.imdb.com/name/nm0000123/) +и [Хэлли Берри](https://www.imdb.com/name/nm0000932/) +``` + +--- + +... который отображается как: + +--- + +# Использование CUE для продвижения в Голливуде +от [Джорджа Клуни](https://www.imdb.com/name/nm0000123/) +и [Хэлли Берри](https://www.imdb.com/name/nm0000932/) + +--- + +Используйте заголовки H2 (`##`) для разделения изолированных, самостоятельных сценариев. + +Используйте заголовки H3 (`###`) для обозначения разделов внутри одного сценария. + +Используйте заголовки H4 (`####`) для именования отдельных шагов в разделе. Это позволит +пользователям ссылаться на шаг, что будет полезно, если пользователь застрял и +нуждается в помощи в CUE Slack, чтобы определить, на каком шаге он застрял. + +В строке H4 добавьте префикс к названию каждого шага, который пользователь должен выполнить, +иконкой :arrow_right: (`:arrow_right:`). + +Например: + +--- + +``` +#### :arrow_right: Обработать doodahs + +Обработка doodahs удаленно проста, если вы помните, что нужно очистить widgets. +Чтобы сделать это, ... +``` + +--- + +... который отображается как: + +--- + +#### :arrow_right: Обработать doodahs + +Обработка doodahs удаленно проста, если вы помните, что нужно очистить widgets. +Чтобы сделать это, ... + +--- + +Пожалуйста, включите как минимум: + +- вводный раздел, охватывающий **по крайней мере** предварительные требования, которые читатель + должен знать, чтобы успешно использовать документ +- раздел с шагами, которые нужно выполнить, с маркерами шагов, как описано выше +- заключительный раздел + +### Файлы + +Обозначьте каждый файл, который пользователь должен создать: + +- иконкой :floppy_disk: (`:floppy_disk:`) +- за которой в той же строке следует путь и имя файла в блоке встроенного кода +- за которым следует блок кода с преамбулой типа содержимого, содержащий + содержимое файла. + +Например: + +--- + +```` # здесь четыре обратных апострофа *только* чтобы позволить трем апострофам ниже отображаться +:floppy_disk: `some_file.cue` +```CUE +package cbe123 + +some_content: "some string" +``` +```` + +--- + +... который отображается как: + +--- + +:floppy_disk: `some_file.cue` +```CUE +package cbe123 + +some_content: "some string" +``` + +--- + +### Блоки команд оболочки + +Обозначьте одну или несколько команд, которые пользователь должен выполнить в оболочке: + +- иконкой :computer: (`:computer:`) +- за которой в той же строке следует слово `terminal` в блоке встроенного кода +- за которым следует блок кода с типом содержимого `sh` +- с каждой командой, которую пользователь должен выполнить, на отдельной строке, без **никакого префикса** +- с любыми критическими комментариями после связанной команды, разделенными + подходящим символом комментария оболочки (обычно `#`). + +Отсутствие префикса означает, что при использовании кнопки "копировать", которую GitHub +автоматически размещает в правом верхнем углу каждого блока кода, читателю будет предоставлен +полезный текст для вставки. Если бы вы включили префикс, например `$`, то +читатель не смог бы вставить команды непосредственно в терминал. + +Включите пример вывода в отдельном блоке кода после блока команд, +чтобы помочь пользователю оценить, выполнилась ли команда должным образом на его локальной машине. + +Например: + +--- + +```` # здесь четыре обратных апострофа *только* чтобы позволить трем апострофам ниже отображаться +:computer: `terminal` +```sh +echo "hello world" | tr 'a-z' 'A-Z' | tr -s 'A-Z' # это действительно важная команда +echo 'CUE это круто!' +``` + +Ожидаемый вывод: +``` +HELO WORLD +CUE это круто! +``` +```` + +--- + +... который отображается как: + +--- + +:computer: `terminal` +```sh +echo "hello world" | tr 'a-z' 'A-Z' | tr -s 'A-Z' # это действительно важная команда +echo 'CUE это круто!' +``` + +Ожидаемый вывод: +``` +HELO WORLD +CUE это круто! +``` + +--- + +#### Сворачивание длинного содержимого файлов или оболочки + +Когда содержимое файла или блока оболочки достаточно длинное и нарушает поток +руководства, рассмотрите возможность размещения длинного содержимого внутри элемента HTML +`
` с возможностью "раскрытия кликом". + +Этот элемент имеет 2 части: + +- короткую преамбулу, которая всегда видна (элемент ``) +- свернутое содержимое, которое становится видимым только после клика + +Пожалуйста, используйте элемент сворачивания следующим образом: + +- Разместите элемент внутри пары горизонтальных линий (`
`), чтобы обозначить для + читателя, где заканчивается развернутое содержимое +- Оберните имя файла в элемент `` (вместо одинарных обратных апострофов) +- После имени файла добавьте слова `(нажмите, чтобы открыть)` +- Поместите пустую строку (в исходном коде) между закрывающим тегом `
` и + началом блока кода файла (```), чтобы форматирование markdown GitHub + работало корректно + +Вот пример использования элемента `
` для сворачивания файла: + +```` + +Здесь файл, который может быть вам полезен: + +
+
+ +:floppy_disk: a_file.cue (нажмите, чтобы открыть) + + +```text +Д +лин +ный +файл +но +не +на +са +мо +го +дел +просто +при +мер +``` +
+
+```` + +Это отображается следующим образом: + +Здесь файл, который может быть вам полезен: + +
+
+ +:floppy_disk: a_file.cue (нажмите, чтобы открыть) + + +```text +Д +лин +ный +файл +но +не +на +са +мо +го +дел +просто +при +мер +``` +
+
+ +### Блоки предупреждений и информации + +Если вашему читателю нужно предупредить или проинформировать в определенной точке +документа, используйте таблицу Markdown, как в следующем примере, с одним из +заголовков точно так, как указано: + +--- + +``` +| :exclamation: ПРЕДУПРЕЖДЕНИЕ :exclamation: | +|:------------------------------------------ | +| Этот текст предупреждения должен находиться на одной строке в исходном коде markdown, так как при наличии разрыва строки форматирование нарушится. Хотя это может привести к громоздкому исходному тексту, отрендеренный результат выглядит нормально. Чтобы принудительно сделать разрыв строки, используйте HTML-тег `
`, например так:
Чтобы принудительно сделать пустую строку, используйте два, например так:

Эта строка исходного кода, в отличие от двух выше, **не** нуждается в завершающем символе вертикальной черты. Большинство элементов форматирования markdown работают корректно в таблицах, таких как [ссылки](https://example.com), *курсив*, **жирный**, и `встроенные блоки кода`. Что-либо с несколькими строками, например блоки кода, вероятно, не будет работать. + +| :grey_exclamation: Информация :grey_exclamation: | +|:----------------------------------------------- | +| Этот информационный блок менее "громкий", чем ПРЕДУПРЕЖДЕНИЕ выше. Все примечания по форматированию и содержанию из примера ПРЕДУПРЕЖДЕНИЯ также применимы здесь. +``` + +--- + +Это отображается следующим образом: + +--- + +| :exclamation: ПРЕДУПРЕЖДЕНИЕ :exclamation: | +|:------------------------------------------ | +| Этот текст предупреждения должен находиться на одной строке в исходном коде markdown, так как при наличии разрыва строки форматирование нарушится. Хотя это может привести к громоздкому исходному тексту, отрендеренный результат выглядит нормально. Чтобы принудительно сделать разрыв строки, используйте HTML-тег `
`, например так:
Чтобы принудительно сделать пустую строку, используйте два, например так:

Эта строка исходного кода, в отличие от двух выше, **не** нуждается в завершающем символе вертикальной черты. Большинство элементов форматирования markdown работают корректно в таблицах, таких как [ссылки](https://example.com), *курсив*, **жирный**, и `встроенные блоки кода`. Что-либо с несколькими строками, например блоки кода, вероятно, не будет работать. + +| :grey_exclamation: Информация :grey_exclamation: | +|:----------------------------------------------- | +| Этот информационный блок менее "громкий", чем ПРЕДУПРЕЖДЕНИЕ выше. Все примечания по форматированию и содержанию из примера ПРЕДУПРЕЖДЕНИЯ также применимы здесь. +--- \ No newline at end of file diff --git a/001_github_actions_importing_workflows/README_RU.md b/001_github_actions_importing_workflows/README_RU.md new file mode 100644 index 0000000..5ac2d9d --- /dev/null +++ b/001_github_actions_importing_workflows/README_RU.md @@ -0,0 +1,355 @@ +# Управление рабочими процессами GitHub Actions с помощью CUE +от [Jonathan Matthews](https://jonathanmatthews.com) + +Это руководство объясняет, как преобразовать файлы рабочих процессов GitHub Actions из YAML в +CUE, проверить правильность этих рабочих процессов, а затем использовать инструментарий CUE для +повторной генерации YAML. + +Это позволяет переключиться на CUE как на источник истины для рабочих процессов GitHub Actions +и выполнять клиентскую проверку, без необходимости GitHub знать, что вы управляете своими +рабочими процессами с помощью CUE. + +| :exclamation: ПРЕДУПРЕЖДЕНИЕ :exclamation: | +|:---------------------------------------------- | +| Это руководство требует, чтобы вы использовали `cue` версии `v0.11.0-alpha.2` или выше. **Процесс, описанный ниже, не будет работать с более ранними версиями**. Проверьте версию вашей команды `cue`, выполнив `cue version`, и [обновите её](https://cuelang.org/dl), если это необходимо. + +## Предварительные требования + +- У вас есть набор файлов рабочих процессов GitHub Actions. + - Примеры, показанные в этом руководстве, используют состояние первого коммита репозитория CUE + [github-actions-example](https://github.com/cue-examples/github-actions-example/tree/2b9d2f240d0c677c30218282dc10f95dfd566453/.github/workflows), + но вам не нужно использовать этот репозиторий каким-либо образом. +- У вас + [установлен CUE](https://alpha.cuelang.org/docs/introduction/installation/) + локально -- это позволяет вам запускать команды `cue` + - У вас должна быть установлена версия `v0.11.0-alpha.2` или выше. +- У вас есть учетная запись [GitHub](https://github.com) -- это позволяет вам использовать + реестр CUE Central. +- У вас есть учетная запись [Central Registry](https://registry.cue.works) -- это + позволяет вам получить схему для проверки ваших рабочих процессов GitHub Actions. +- У вас установлен [`git`](https://git-scm.com/downloads). + +## Шаги + +### Преобразование рабочих процессов YAML в CUE + +#### :arrow_right: Начните с чистого состояния git + +Перейдите в корневую директорию репозитория, содержащего ваши файлы рабочих процессов GitHub +Actions, и убедитесь, что вы начинаете этот процесс с чистого состояния git, без измененных файлов. Например: + +:computer: `terminal` +```sh +cd github-actions-example # наш пример репозитория +git status # должен сообщить "working tree clean" +``` + +#### :arrow_right: Инициализируйте модуль CUE + +Инициализируйте модуль CUE с именем организации и репозитория, с которыми вы работаете. Например: + +:computer: `terminal` +```sh +cue mod init github.com/cue-examples/github-actions-example +``` + +#### :arrow_right: Импортируйте рабочие процессы YAML + +Используйте `cue` для импорта ваших файлов рабочих процессов YAML: + +:computer: `terminal` +```sh +cue import ./.github/workflows/ --with-context -p github -f -l workflows: -l 'strings.TrimSuffix(path.Base(filename),path.Ext(filename))' +``` + +Проверьте, что для каждого рабочего процесса YAML был создан файл CUE в +директории `.github/workflows`. Например: + +:computer: `terminal` +```sh +ls .github/workflows/ +``` + +Ваш вывод должен выглядеть примерно так, с соответствующими парами файлов YAML и CUE: + +```text +workflow1.cue +workflow1.yml +workflow2.cue +workflow2.yml +``` +Обратите внимание, что каждый рабочий процесс был импортирован в структуру `workflows`, в +местоположение, полученное из исходного имени файла: + +:computer: `terminal` +```sh +head -5 .github/workflows/*.cue +``` + +Вывод должен отражать ваши рабочие процессы. В нашем примере: + +```text +==> .github/workflows/workflow1.cue <== +package github + +workflows: workflow1: { + on: [ + "push", + +==> .github/workflows/workflow2.cue <== +package github + +workflows: workflow2: { + on: [ + "push", +``` +#### :arrow_right: Сохраните рабочие процессы CUE в отдельной директории + +Создайте директорию с именем `github` для хранения ваших файлов рабочих процессов GitHub Actions на основе CUE. Например: + +:computer: `terminal` +```sh +mkdir -p internal/ci/github +``` + +Вы можете изменить иерархию и именование **родительских** директорий `github` в +соответствии с макетом вашего репозитория. Если вы это сделаете, вам нужно будет адаптировать +некоторые команды и код CUE при следовании этому руководству. + +Переместите вновь созданные файлы CUE в их выделенную директорию. Например: + +:computer: `terminal` +```sh +mv ./.github/workflows/*.cue internal/ci/github +``` + +### Проверка рабочих процессов + +#### :arrow_right: Аутентифицируйте команду `cue` в реестре CUE Central + +Выполните эту команду и следуйте инструкциям, которые она отображает: + +:computer: `terminal` +```sh +cue login +``` + +Это позволит вам получать модули из Central Registry. + +#### :arrow_right: Добавьте зависимость от модуля GitHub Actions + +:computer: `terminal` +```sh +cue mod get github.com/cue-tmp/jsonschema-pub/exp1/githubactions@v0.3.0 +``` + +Эта команда указывает точную версию модуля GitHub Actions для +обеспечения воспроизводимости этого процесса. + +Модуль GitHub Actions *выглядит* как временный, потому что он +был создан в рамках работы проекта CUE по выяснению, как и где +хранить сторонние схемы. Хотя в конечном итоге он будет находиться в более постоянном +и подходящем месте (что будет отражено в этом руководстве), эта +*версия* модуля не исчезнет с "временного" местоположения - так что его +можно безопасно использовать! + +#### :arrow_right: Примените схему + +Нам нужно сказать CUE, чтобы она применила схему к каждому рабочему процессу. + +Для этого мы создадим файл по адресу `internal/ci/github/workflows.cue` в нашем +примере. + +Однако, если импорт рабочих процессов, который вы выполнили ранее, *уже* +создал файл с тем же путем и именем, просто выберите другое имя файла CUE, +которое *еще не существует*. Поместите файл в директорию `internal/ci/github/`. + +:floppy_disk: `internal/ci/github/workflows.cue` +```cue +package github + +import "github.com/cue-tmp/jsonschema-pub/exp1/githubactions" + +// Каждый член структуры workflows должен быть допустимым #Workflow. +workflows: [_]: githubactions.#Workflow +``` + +#### :arrow_right: Проверьте свои рабочие процессы + +:computer: `terminal` +```sh +cue vet ./internal/ci/github +``` + +Если эта команда завершается неудачей и выдает какой-либо вывод, то CUE считает, что по крайней мере +один из ваших рабочих процессов недействителен. Очень вероятно, что CUE прав (и +нашел проблему), даже если GitHub Actions успешно обрабатывает ваши файлы рабочих процессов +-- потому что GitHub Actions может быть снисходительным и чрезмерно терпимым +в применении своих собственных правил схемы! Вам нужно будет решить эту проблему перед +продолжением, обновив свои рабочие процессы внутри новых файлов CUE. Если у вас +возникают трудности с их исправлением, пожалуйста, приходите и попросите помощи в дружелюбном рабочем пространстве CUE +[Slack](https://cuelang.org/s/slack) или +[Discord сервере](https://cuelang.org/s/discord)! + +### Генерация YAML из CUE + +#### :arrow_right: Создайте команду рабочего процесса CUE + +Создайте файл CUE по адресу `internal/ci/github/ci_tool.cue`, содержащий следующую команду рабочего процесса. +Адаптируйте элемент с комментарием `TODO`: + +:floppy_disk: `internal/ci/github/ci_tool.cue` +```cue +package github + +import ( + "path" + "encoding/yaml" + "tool/file" +) + +_goos: string @tag(os,var=os) + +// Восстановить все файлы рабочих процессов +command: regenerate: { + workflow_files: { + // TODO: обновите _toolFile, чтобы отразить иерархию директорий, содержащую этот файл. + let _toolFile = "internal/ci/github/ci_tool.cue" + let _workflowDir = path.FromSlash(".github/workflows", path.Unix) + let _donotedit = "Code generated by \(_toolFile); DO NOT EDIT." + + clean: { + glob: file.Glob & { + glob: path.Join([_workflowDir, "*.yml"], _goos) + files: [...string] + } + for _, _filename in glob.files { + "Delete \(_filename)": file.RemoveAll & {path: _filename} + } + } + + create: { + for _workflowName, _workflow in workflows + let _filename = _workflowName + ".yml" { + "Generate \(_filename)": file.Create & { + $after: [for v in clean {v}] + filename: path.Join([_workflowDir, _filename], _goos) + contents: "# \(_donotedit)\n\n\(yaml.Marshal(_workflow))" + } + } + } + } +} +``` + +Внесите изменения, указанные в комментарии `TODO`. + +Эта команда рабочего процесса будет экспортировать каждый рабочий процесс на основе CUE обратно в требуемый файл YAML, +по запросу. + +#### :arrow_right: Протестируйте команду рабочего процесса CUE + +При наличии измененного файла `ci_tool.cue` проверьте, что команда рабочего процесса `regenerate` +доступна **из оболочки, находящейся в корне репозитория**. Например: + +:computer: `terminal` +```sh +cd $(git rev-parse --show-toplevel) # убедитесь, что мы находимся в корне репозитория +cue help cmd regenerate ./internal/ci/github # префикс "./" обязателен +``` + +Ваш вывод **должен** начинаться со следующего: + +```text +Regenerate all workflow files + +Usage: + cue cmd regenerate [flags] +``` +| :exclamation: ПРЕДУПРЕЖДЕНИЕ :exclamation: | +|:---------------------------------------------- | +| Если вы *не* видите объяснение использования команды рабочего процесса `regenerate` (или если вы получаете сообщение об ошибке), то **либо** ваша команда рабочего процесса не настроена так, как требует CUE, **либо** вы используете версию CUE старше `v0.11.0-alpha.2`. Если вы [обновились как минимум до этой версии](https://cuelang.org/dl), но объяснение использования все еще не отображается, то: (1) дважды проверьте содержимое файла `ci_tool.cue` и внесенные в него изменения; (2) убедитесь, что его местоположение в репозитории точно такое же, как указано в этом руководстве; (3) убедитесь, что имя файла *в точности* `ci_tool.cue`; (4) проверьте, что файл `internal/ci/github/workflows.cue` имеет то же содержимое, что и показано выше; (5) выполните `cue vet ./internal/ci/github` и проверьте, что ваши рабочие процессы действительно успешно проходят проверку - другими словами: были ли они действительно действительны до того, как вы вообще начали этот процесс? Наконец, убедитесь, что вы выполнили все шаги в этом руководстве и что вы вызвали команду `cue help` из корневой директории репозитория. Если вы действительно застряли, пожалуйста, присоединяйтесь к [сообществу CUE](https://cuelang.org/community/) и попросите помощи! + +#### :arrow_right: Восстановите файлы рабочих процессов YAML + +Выполните команду рабочего процесса `regenerate` для создания файлов рабочих процессов YAML из CUE. Например: + +:computer: `terminal` +```sh +cue cmd regenerate ./internal/ci/github # префикс "./" обязателен +``` + +#### :arrow_right: Проверьте изменения в файлах рабочих процессов YAML + +Проверьте, что каждый файл рабочего процесса YAML имеет одно изменение по сравнению с оригиналом: + +:computer: `terminal` +```sh +git diff .github/workflows/ +``` + +Ваш вывод должен выглядеть примерно как следующий пример: + +```diff +diff --git a/.github/workflows/workflow1.yml b/.github/workflows/workflow1.yml +--- a/.github/workflows/workflow1.yml ++++ b/.github/workflows/workflow1.yml +@@ -1,3 +1,5 @@ ++# Code generated by internal/ci/github/ci_tool.cue; DO NOT EDIT. ++ + "on": + - push + - pull_request +diff --git a/.github/workflows/workflow2.yml b/.github/workflows/workflow2.yml +--- a/.github/workflows/workflow2.yml ++++ b/.github/workflows/workflow2.yml +@@ -1,3 +1,5 @@ ++# Code generated by internal/ci/github/ci_tool.cue; DO NOT EDIT. ++ + "on": + - push + - pull_request +``` + +Единственное изменение в каждом файле YAML - это добавление заголовка, который предупреждает +читателя не редактировать файл напрямую. + +#### :arrow_right: Добавьте и зафиксируйте файлы в git + +Добавьте свои файлы в git. Например: + +:computer: `terminal` +```sh +git add .github/workflows/ internal/ci/github/ cue.mod/module.cue +``` + +Обязательно включите немного измененные файлы рабочих процессов YAML в +`.github/workflows/` вместе со всеми новыми файлами в `internal/ci/github/` и +ваш файл `cue.mod/module.cue`. + +Зафиксируйте свои файлы в git с соответствующим комментарием к коммиту: + +:computer: `terminal` +```sh +git commit -m "ci: create CUE sources for GHA workflows" +``` + +## Заключение + +**Отлично - ваши файлы рабочих процессов GitHub Actions были импортированы в CUE!** + +Теперь ими можно управлять с помощью CUE, что приведет к более безопасным и предсказуемым +изменениям. Использование схемы для проверки ваших рабочих процессов означает, что вы будете обнаруживать +и исправлять многие типы ошибок раньше, чем раньше, без ожидания медленного +цикла "git add/commit/push; проверить, не провалился ли CI". + +Отныне каждый раз, когда вы вносите изменения в файл рабочего процесса CUE, немедленно +восстанавливайте файлы YAML, необходимые для GitHub Actions, и фиксируйте свои изменения +во всех файлах CUE и YAML. Например: + +:computer: `terminal` +```sh +cue cmd regenerate ./internal/ci/github/ # префикс "./" обязателен +git add .github/workflows/ internal/ci/github/ +git commit -m "ci: added new release workflow" # пример сообщения +``` \ No newline at end of file diff --git a/003_kubernetes_tutorial/README_RU.md b/003_kubernetes_tutorial/README_RU.md new file mode 100644 index 0000000..efb9164 --- /dev/null +++ b/003_kubernetes_tutorial/README_RU.md @@ -0,0 +1,1519 @@ +# Управление Kubernetes с помощью CUE +от [The CUE Project](https://cuelang.org/) + +## Введение + +В этом руководстве мы покажем, как преобразовать файлы конфигурации Kubernetes +для набора микросервисов. + +Файлы конфигурации являются обработанными и переименованными версиями +реальных файлов конфигурации. +Файлы организованы в иерархии каталогов, группируя связанные сервисы +в подкаталоги. +Это распространенный паттерн. +Инструментарий `cue` был оптимизирован для этого варианта использования. + +В этом руководстве мы рассмотрим следующие темы: + +1. преобразование заданных файлов YAML в CUE +1. вынесение общих паттернов в родительские каталоги +1. использование инструментария для перезаписи файлов CUE с удалением ненужных полей +1. повтор шагов 2 для разных подкаталогов +1. определение команд рабочего процесса для работы с конфигурацией +1. извлечение шаблонов CUE непосредственно из исходного кода Kubernetes Go +1. ручная настройка конфигурации +1. сопоставление конфигурации Kubernetes с `docker-compose` (TODO) + +### Контекст этого руководства + +Набор данных, содержащийся в каталоге этого руководства, основан на реальном случае, +используя разные имена для сервисов. +Все несоответствия реальной настройки воспроизведены в файлах, +чтобы получить реалистичное представление о том, как будет вести себя преобразование в CUE +на практике. + +#### :arrow_right: Ознакомьтесь с контекстом + +Заданные файлы YAML упорядочены по различным каталогам. Посмотрите, какие файлы присутствуют: + +:computer: `terminal` +```sh +find ./original -type f +``` + +Это покажет следующий вывод, который мы здесь сократили: + +``` +./original/services/frontend/bartender/kube.yaml +./original/services/frontend/breaddispatcher/kube.yaml +./original/services/frontend/host/kube.yaml +./original/services/frontend/maitred/kube.yaml +./original/services/frontend/valeter/kube.yaml +./original/services/frontend/waiter/kube.yaml +./original/services/frontend/waterdispatcher/kube.yaml +./original/services/infra/download/kube.yaml +./original/services/infra/etcd/kube.yaml +./original/services/infra/events/kube.yaml +[ ... усечено ... ] +``` + +Каждый подкаталог содержит связанные микросервисы, которые часто разделяют схожие +характеристики и конфигурации. +Конфигурации включают большое разнообразие объектов Kubernetes, включая +сервисы, развертывания, карты конфигураций, +демон-набор, набор с сохранением состояния и задание cron. + +Результат первого руководства находится в каталоге `quick`, для "быстрого и грязного" +подхода. +Ручная оптимизированная конфигурация может быть найдена в каталоге `manual`. + +## Импорт существующей конфигурации + +#### :arrow_right: Сделайте копию каталога данных + +:computer: `terminal` +```sh +cp -a original tmp +cd tmp +``` + +#### :arrow_right: Инициализируйте модуль CUE + +:computer: `terminal` +```sh +cue mod init +``` + +Мы инициализируем модуль CUE, чтобы мы могли рассматривать все наши файлы конфигурации в +подкаталогах как часть одного пакета. +Мы делаем это позже, присваивая всем одинаковое имя пакета. + +Создание модуля также позволяет нашим пакетам импортировать внешние пакеты. + +#### :arrow_right: Инициализируйте модуль Go + +:computer: `terminal` +```sh +go mod init mod.test +``` + +Мы инициализируем модуль Go, чтобы позже мы могли разрешить +зависимость пакета Go `k8s.io/api/apps/v1`: + +#### :arrow_right: Попытка 1: импорт файлов YAML в один пакет CUE + +Давайте попробуем использовать команду `cue import` для преобразования заданных файлов YAML в +CUE, в пакет `kube`: + +:computer: `terminal` +```sh +cd services +cue import ./... -p kube +``` + +Нам нужно было указать пакет, к которому они должны принадлежать (`-p kube`), +потому что у нас несколько пакетов и файлов. + +Это выведет следующее сообщение об ошибке: + +``` +path, list, or files flag needed to handle multiple objects in file ./services/frontend/bartender/kube.yaml +``` + +Это сообщение об ошибке показывается, потому что многие из файлов содержат более одного +объекта Kubernetes. +Кроме того, мы создаем единую конфигурацию, которая содержит все объекты +из всех файлов. + +#### :arrow_right: Попытка 2: импорт файлов YAML с использованием динамических адресов объектов + +Нам нужно организовать все объекты Kubernetes так, чтобы каждый был индивидуально +идентифицируем в единой конфигурации. +Мы делаем это, определяя разные структуры для каждого типа, помещая каждый объект +в соответствующую структуру с ключом по его имени. +Это позволяет объектам разных типов иметь одинаковые имена, +как это разрешено в Kubernetes. +Для этого мы говорим `cue` поместить каждый объект в дерево конфигурации +по пути с "kind" как первый элемент и "name" как второй. + +:computer: `terminal` +```sh +cue import ./... -p kube -l 'strings.ToCamel(kind)' -l metadata.name -f +``` + +Добавленный флаг `-l` определяет метки для каждого объекта, основанные на значениях из +каждого объекта, используя обычный синтаксис CUE для меток полей. +В этом случае мы используем вариант в верблюжьем регистре поля `kind` каждого объекта и +используем поле `name` раздела `metadata` как имя для каждого объекта. +Мы также добавили флаг `-f` для перезаписи нескольких файлов, которые успешно завершились ранее. + +Посмотрим, что произошло, запустив: + +:computer: `terminal` +```sh +find . -type f +``` + +Это покажет что-то подобное следующему: + +``` +./frontend/bartender/kube.yaml +./frontend/bartender/kube.cue +./frontend/breaddispatcher/kube.yaml +./frontend/breaddispatcher/kube.cue +./frontend/host/kube.yaml +./frontend/host/kube.cue +./frontend/maitred/kube.yaml +./frontend/maitred/kube.cue +./frontend/valeter/kube.yaml +./frontend/valeter/kube.cue +[ ... усечено ... ] +``` + +Каждый из файлов YAML преобразован в соответствующие файлы CUE. +Комментарии файлов YAML сохраняются. + +Однако результат не полностью удовлетворителен. +Взгляните на `mon/prometheus/configmap.cue`. + +:floppy_disk: `mon/prometheus/configmap.cue` +```cue +package kube + +configMap: prometheus: { + apiVersion: "v1" + kind: "ConfigMap" + metadata: name: "prometheus" + data: { + "alert.rules": """ + groups: + - name: rules.yaml +[ ... усечено ... ] +``` + +Файл конфигурации все еще содержит YAML, встроенный в строковое значение одного +из полей. +Оригинальный файл YAML мог выглядеть как структурированные данные, но +большая часть из него была строкой, содержащей, надеемся, действительный YAML. + +#### :arrow_right: Попытка 3: импорт файлов YAML с использованием обработки встроенных строк + +Используйте опцию `-R` команды `cue import` для попытки обнаружения структурированных строк YAML или JSON, +встроенных в файлы конфигурации, и рекурсивного их преобразования: + + + +:computer: `terminal` +```sh +cue import ./... -p kube -l 'strings.ToCamel(kind)' -l metadata.name -f -R +``` + +Повторно изучите импортированный файл CUE: + +:floppy_disk: `mon/prometheus/configmap.cue` +```cue +package kube + +import yaml656e63 "encoding/yaml" + +configMap: prometheus: { + apiVersion: "v1" + kind: "ConfigMap" + metadata: name: "prometheus" + data: { + "alert.rules": yaml656e63.Marshal(_cue_alert_rules) +[ ... усечено ... ] +``` + +Это выглядит лучше! +Результирующий файл конфигурации заменяет оригинальную встроенную строку +на вызов `yaml.Marshal`, преобразующий структурированный источник CUE в +строку с эквивалентным файлом YAML. + +#### :arrow_right: Проверьте вывод CUE + +Поля, начинающиеся с подчеркивания (`_`), не включаются при выводе +файла конфигурации (за исключением случаев, когда имя поля заключено в двойные кавычки). + +Проверьте, что поле `_cue_alert_rules`, добавленное выше при использовании опции +`-R` команды `cue import`, отсутствует в *выводе* `cue`, запустив: + +:computer: `terminal` +```sh +cue eval ./mon/prometheus -e configMap.prometheus +``` + +Это покажет следующее: + +```cue +apiVersion: "v1" +kind: "ConfigMap" +metadata: { + name: "prometheus" +} +data: { + "alert.rules": """ + groups: + - name: rules.yaml + rules: +[ ... усечено ... ] +``` + +Ура! + +## Быстрое и грязное преобразование + +В этом руководстве мы покажем, как быстро устранить шаблонный код из набора +конфигураций. +Ручная настройка обычно дает лучшие результаты, но требует значительно +больше размышлений, в то время как быстрый и грязный подход приближает вас к цели. +Результат такого быстрого преобразования также формирует хорошую основу для +более обдуманной ручной оптимизации. + +### Создание шаблона верхнего уровня + +После импорта файлов YAML выше мы можем начать процесс упрощения. + +#### :arrow_right: Сохраните эталонную копию + +Перед началом реструктуризации давайте сохраним полную оценку (чтобы мы +могли проверить, что упрощения дают те же результаты), запустив: + +:computer: `terminal` +```sh +cue eval -c ./... >snapshot +``` + +Опция `-c` указывает `cue`, что допустимы только конкретные значения, то есть действительный JSON. + +#### :arrow_right: Начните создавать шаблон + +Мы сосредоточимся на объектах, определенных в различных файлах `kube.cue`. +Быстрый осмотр показывает, что многие развертывания и сервисы разделяют +общую структуру. + +Мы копируем один из файлов, содержащих оба, как основу для создания нашего шаблона +в корень дерева каталогов. + +:computer: `terminal` +```sh +cp frontend/breaddispatcher/kube.cue . +``` + +Измените этот файл, как показано ниже. + +:floppy_disk: `./kube.cue` +```cue +package kube + +service: [ID=_]: { + apiVersion: "v1" + kind: "Service" + metadata: { + name: ID + labels: { + app: ID // по соглашению + domain: "prod" // всегда одно и то же в заданных файлах + component: string // варьируется по каталогам + } + } + spec: { + // Любой порт имеет следующие свойства. + ports: [...{ + port: int + protocol: *"TCP" | "UDP" // из определения Kubernetes + name: string | *"client" + }] + selector: metadata.labels // мы хотим, чтобы они были одинаковыми + } +} + +deployment: [ID=_]: { + apiVersion: "apps/v1" + kind: "Deployment" + metadata: name: ID + spec: { + // 1 - значение по умолчанию, но мы разрешаем любое число + replicas: *1 | int + template: { + metadata: labels: { + app: ID + domain: "prod" + component: string + } + // у нас всегда есть один одноименный контейнер + spec: containers: [{name: ID}] + } + } +} +``` + +Заменив имя сервиса и развертывания на `[ID=_]`, мы изменили +определение на шаблон, соответствующий любому полю. +CUE связывает имя поля с `ID` в результате. +При импорте мы использовали `metadata.name` как ключ для имен объектов, +поэтому теперь мы можем установить это поле в `ID`. + +Шаблоны применяются к (объединяются с) всем записям в структуре, в которой +они определены, +поэтому нам нужно либо удалить поля, специфичные для определения `breaddispatcher`, +обобщить их, либо удалить. + +Одна из меток, определенных в метаданных Kubernetes, кажется, всегда устанавливается +в имя родительского каталога. +Мы обеспечиваем это, определяя `component: string`, что означает, что поле +с именем `component` должно быть установлено в некоторое строковое значение, и затем определяем это +позже. +Любое недостаточно специфицированное поле приводит к ошибке при преобразовании, например, +в JSON. +Поэтому развертывание или сервис будет действительным только если эта метка определена. + + + +#### :arrow_right: Протестируйте шаблон + +Давайте сравним результат объединения нашего нового шаблона с нашим оригинальным снимком, запустив: + +:computer: `terminal` +```sh +cue eval -c ./... >snapshot2 +``` + +Эта команда завершается неудачей, показывая нам это (усеченное) сообщение об ошибке: + +``` +// :kube +deployment.alertmanager.spec.template.metadata.labels.component: incomplete value string: + ./kube.cue:36:16 +service.alertmanager.metadata.labels.component: incomplete value string: + ./kube.cue:11:15 +service.alertmanager.spec.selector.component: incomplete value string: + ./kube.cue:11:15 +// :kube +service."node-exporter".metadata.labels.component: incomplete value string: + ./kube.cue:11:15 +[ ... усечено ... ] +``` + +Упс. +Менеджер оповещений не указывает метку `component`. +Это демонстрирует, как ограничения могут использоваться для выявления несоответствий +в ваших конфигурациях. + +#### :arrow_right: Исправьте шаблон + +Поскольку очень мало объектов *не* указывают эту метку, мы изменим +конфигурации, чтобы включить их везде. +Мы делаем это, устанавливая новое поле верхнего уровня в каждом каталоге +в имя каталога и изменяем наш основной файл шаблона, чтобы использовать его. + +Запустите этот скрипт для редактирования ваших файлов встроенно и добавления некоторых файлов CUE в каждый +каталог: + +:computer: `terminal` +```sh +# установите метку компонента в наше новое поле верхнего уровня +sed -i.bak 's/component:.*string/component: #Component/' kube.cue +rm kube.cue.bak + +# добавьте новое поле верхнего уровня в наши предыдущие определения шаблонов +cat <> kube.cue + +#Component: string +EOF + +# добавьте файл с меткой компонента в каждый каталог +ls -d */ | sed 's/.$//' | xargs -I DIR sh -c 'cd DIR; echo "package kube + +#Component: \"DIR\" +" > kube.cue; cd ..' + +# отформатируйте файлы +cue fmt kube.cue */kube.cue +``` + +#### :arrow_right: Протестируйте шаблон снова + +Посмотрим, исправили ли мы шаблон: + +:computer: `terminal` +```sh +cue eval -c ./... >snapshot2 +diff -w --side-by-side snapshot snapshot2 +``` + +Это показывает нам, что, помимо того, что `snapshot2` имеет более согласованные метки и +некоторое переупорядочивание, ничего материально не изменилось между `snapshot` и +`snapshot2`. + +#### :arrow_right: Сохраните результат как новую базовую линию + +:computer: `terminal` +```sh +cp snapshot2 snapshot +``` + +#### :arrow_right: Удалите шаблонный код + +Много шаблонного кода, теперь подразумеваемого содержимым в шаблоне, может быть +удалено с помощью `cue trim`. + +Сначала проверьте общее количество строк во всех ваших файлах `kube.cue` до +удаления шаблонного кода: + +:computer: `terminal` +```sh +find . | grep kube.cue | xargs wc -l | tail -1 +``` + +Это покажет что-то вроде ... + +``` + 1887 total +``` + +Теперь используйте `cue trim` для удаления шаблонного кода и повторно проверьте количество строк +оставшихся: + +:computer: `terminal` +```sh +cue trim ./... +find . | grep kube.cue | xargs wc -l | tail -1 +``` + +Вы должны увидеть значительное сокращение: + +``` + 1312 total +``` + +`cue trim` удаляет конфигурацию из файлов, которая уже генерируется +шаблонами или пониманиями. +Делая это, он удалил более 500 строк конфигурации, или более 30%! + +#### :arrow_right: Проверьте удаление шаблонного кода + +Проверьте, что ничего не изменилось семантически, запустив: + +:computer: `terminal` +```sh +cue eval -c ./... >snapshot2 +diff -wu snapshot snapshot2 | wc -l +``` + +Сообщенное количество строк будет 0. + +#### :arrow_right: Улучшите шаблон + +Мы можем сделать лучше. + +Первое, что следует отметить, это то, что DaemonSets и StatefulSets разделяют схожую +структуру с развертываниями. +Мы обобщаем шаблон верхнего уровня следующим образом, добавляя к `kube.cue`: + +:computer: `terminal` +```sh +cat <> kube.cue + +daemonSet: [ID=_]: _spec & { + apiVersion: "apps/v1" + kind: "DaemonSet" + _name: ID +} + +statefulSet: [ID=_]: _spec & { + apiVersion: "apps/v1" + kind: "StatefulSet" + _name: ID +} + +deployment: [ID=_]: _spec & { + apiVersion: "apps/v1" + kind: "Deployment" + _name: ID + spec: replicas: *1 | int +} + +configMap: [ID=_]: { + metadata: name: ID + metadata: labels: component: #Component +} + +_spec: { + _name: string + + metadata: name: _name + metadata: labels: component: #Component + spec: selector: {} + spec: template: { + metadata: labels: { + app: _name + component: #Component + domain: "prod" + } + spec: containers: [{name: _name}] + } +} +EOF +cue fmt +``` + +Общая конфигурация была вынесена в `_spec`. +Мы ввели `_name` для помощи в указании и ссылке +на имя объекта. +Для полноты мы добавили `configMap` как запись верхнего уровня. + +Обратите внимание, что мы еще не удалили старое определение `deployment`. +Это нормально. +Поскольку оно эквивалентно новому, объединение их не окажет никакого эффекта. +Мы оставляем его удаление как упражнение для читателя. + +#### :arrow_right: Дальнейшее улучшение шаблона + +Далее мы наблюдаем, что все развертывания, наборы с сохранением состояния и демон-наборы имеют +сопутствующий сервис, который разделяет многие из тех же полей. +Мы снова добавляем к шаблону в `kube.cue`: + +:computer: `terminal` +```sh +cat <> kube.cue + +// Определим опцию _export и установим значение по умолчанию true +// для всех портов, определенных во всех контейнерах. +_spec: spec: template: spec: containers: [...{ + ports: [...{ + _export: *true | false // включить порт в сервис + }] +}] + +for x in [deployment, daemonSet, statefulSet] for k, v in x { + service: "\(k)": { + spec: selector: v.spec.template.metadata.labels + + spec: ports: [ + for c in v.spec.template.spec.containers + for p in c.ports + if p._export { + let Port = p.containerPort // Port - это псевдоним + port: *Port | int + targetPort: *Port | int + }, + ] + } +} +EOF +cue fmt +``` + +Этот пример вводит несколько новых концепций. +Открытые списки обозначаются многоточием (`...`). +Значение, следующее за многоточием, объединяется с любыми последующими элементами и +определяет "тип" или шаблон для дополнительных элементов списка. + +Объявление `Port` - это псевдоним. +Псевдонимы видны только в их лексической области видимости и не являются частью модели. +Они могут использоваться для отображения затененных полей в вложенных областях или, +в данном случае, для уменьшения шаблонного кода без введения новых полей. + +Наконец, этот пример вводит понимания списков и полей. +Понимания списков аналогичны пониманиям списков, найденным в других +языках. +Понимания полей позволяют вставлять поля в структуры. +В этом случае понимание поля добавляет одноименный сервис для любого +развертывания, демон-набора и набора с сохранением состояния. +Понимания полей также могут использоваться для условного добавления поля. + +Указание `targetPort` не обязательно, но поскольку многие файлы определяют его, +его определение здесь позволит удалить эти определения +с помощью `cue trim`. +Мы добавляем опцию `_export` для портов, определенных в контейнерах, чтобы указать, +включать ли их в сервис, и явно устанавливаем это в false +для соответствующих портов в `infra/events`, `infra/tasks` и `infra/watcher`. + +Для целей этого руководства вот несколько быстрых патчей: + +:computer: `terminal` +```sh +cat <>infra/events/kube.cue + +deployment: events: spec: template: spec: containers: [{ ports: [{_export: false}, _] }] +EOF +cat <>infra/tasks/kube.cue + +deployment: tasks: spec: template: spec: containers: [{ ports: [{_export: false}, _] }] +EOF +cat <>infra/watcher/kube.cue + +deployment: watcher: spec: template: spec: containers: [{ ports: [{_export: false}, _] }] +EOF +``` + +На практике было бы более правильной формой добавить это поле в оригинальное +объявление порта. + +#### :arrow_right: Проверьте улучшение шаблона + +Мы проверяем, что все изменения приемлемы, и сохраняем другой снимок. +Затем мы запускаем trim для дальнейшего сокращения нашей конфигурации: + +:computer: `terminal` +```sh +cue trim ./... +find . | grep kube.cue | xargs wc -l | tail -1 +``` + +После удаления переписанного и теперь избыточного определения развертывания мы сэкономили +почти еще 100 строк, даже после добавления шаблона. Вы можете проверить, +что определения сервисов теперь исчезли в большинстве файлов. +Остается либо некоторая дополнительная конфигурация, либо несоответствия, которые +вероятно следует очистить. + +#### :arrow_right: Сверните структуры с одним элементом + +У нас есть еще один трюк в рукаве. + +С опцией `-s` или `--simplify` мы можем указать `trim` или `fmt` свернуть +структуры с одним элементом в одну строку. + +Например, проверьте верхнюю часть `frontend/breaddispatcher/kube.cue`: + +:computer: `terminal` +```sh +head frontend/breaddispatcher/kube.cue +``` + +Это покажет что-то похожее на: + +``` +package kube + +deployment: breaddispatcher: { + spec: { + template: { + metadata: { + annotations: { + "prometheus.io.scrape": "true" + "prometheus.io.port": "7080" + } +``` + +Теперь используйте флаг `-s` команды `cue trim`: + +:computer: `terminal` +```sh +cue trim ./... -s +head -7 frontend/breaddispatcher/kube.cue +``` + +... который *теперь* покажет: + +``` +package kube + +deployment: breaddispatcher: spec: template: { + metadata: annotations: { + "prometheus.io.scrape": "true" + "prometheus.io.port": "7080" + } +``` + +Проверьте сокращение строк в всей конфигурации: + +:computer: `terminal` +```sh +find . | grep kube.cue | xargs wc -l | tail -1 +``` + +Еще 150 строк потеряно! +Сворачивание строк таким образом может улучшить читаемость конфигурации +путем удаления значительного количества пунктуации. + +#### :arrow_right: Сохраните результат как новую базовую линию + +:computer: `terminal` +```sh +cue eval -c ./... >snapshot2 +cp snapshot2 snapshot +``` + +### Повторите для нескольких подкаталогов + +В предыдущем разделе мы определили шаблоны для сервисов и развертываний +в корне нашей структуры каталогов для захвата общих черт всех +сервисов и развертываний. +Кроме того, мы определили метку, специфичную для каталога. +В этом разделе мы рассмотрим обобщение объектов по каталогам. + +#### :arrow_right: Шаблон каталога `frontend` + +Мы наблюдаем, что все развертывания в подкаталогах `frontend` +имеют один контейнер с одним портом, +который обычно `7080`, но иногда `8080`. +Кроме того, большинство имеют две аннотации, связанные с prometheus, в то время как некоторые имеют одну. +Мы оставляем несоответствия в портах, но добавляем обе аннотации +безусловно. + +:computer: `terminal` +```sh +cat <> frontend/kube.cue + +deployment: [string]: spec: template: { + metadata: annotations: { + "prometheus.io.scrape": "true" + "prometheus.io.port": "\(spec.containers[0].ports[0].containerPort)" + } + spec: containers: [{ + ports: [{containerPort: *7080 | int}] // 7080 - значение по умолчанию + }] +} +EOF +cue fmt ./frontend + +# проверьте различия +cue eval -c ./... >snapshot2 +diff -wu snapshot snapshot2 +``` + +Это должно показать что-то похожее на ... + +``` +--- snapshot 2022-02-21 06:04:10.919832150 +0000 ++++ snapshot2 2022-02-21 06:04:11.907780310 +0000 +@@ -188,6 +188,7 @@ + metadata: { + annotations: { + "prometheus.io.scrape": "true" ++ "prometheus.io.port": "7080" + } + labels: { + app: "host" +@@ -327,6 +328,7 @@ + metadata: { + annotations: { + "prometheus.io.scrape": "true" ++ "prometheus.io.port": "8080" + } + labels: { + app: "valeter" +``` + +Единственное различие - это добавление аннотаций в две строки, улучшая +согласованность. + +Обрежьте и проверьте сокращение строк в всей конфигурации: + +:computer: `terminal` +```sh +cue trim ./frontend/... -s +find . | grep kube.cue | xargs wc -l | tail -1 +``` + +Еще около 40 строк удалено. +Мы могли привыкнуть к большим сокращениям, но на данный момент просто +не так много осталось удалить: в некоторых файлах frontend осталось только 4 строки +конфигурации. + +#### :arrow_right: Сохраните результат как новую базовую линию + +:computer: `terminal` +```sh +cue eval -c ./... >snapshot2 +cp snapshot2 snapshot +``` + +#### :arrow_right: Шаблон каталога `kitchen` + +В этом каталоге мы наблюдаем, что все развертывания без исключения +имеют один контейнер с портом `8080`, все имеют одинаковый пробник живучести, +одну строку аннотации prometheus, и большинство имеют +два или три диска с похожими паттернами. + +Давайте добавим все, кроме дисков, на данный момент: + +:computer: `terminal` +```sh +cat <> kitchen/kube.cue + +deployment: [string]: spec: template: { + metadata: annotations: "prometheus.io.scrape": "true" + spec: containers: [{ + ports: [{ + containerPort: 8080 + }] + livenessProbe: { + httpGet: { + path: "/debug/health" + port: 8080 + } + initialDelaySeconds: 40 + periodSeconds: 3 + } + }] +} +EOF +cue fmt ./kitchen +``` + +Diff показывает, что одна аннотация prometheus была добавлена к сервису. +Мы предполагаем, что это случайное упущение, и принимаем различия. + +Диски должны быть определены как в разделе спецификации шаблона, так и в +контейнере, где они используются. +Мы предпочитаем держать эти два определения вместе. +Мы берем определение томов из `expiditer` (первая конфигурация в этом +каталоге с двумя дисками) и обобщаем его: + +:computer: `terminal` +```sh +cat <> kitchen/kube.cue + +deployment: [ID=_]: spec: template: spec: { + _hasDisks: *true | bool + + // понимание полей с использованием только "if" + if _hasDisks { + volumes: [{ + name: *"\(ID)-disk" | string + gcePersistentDisk: pdName: *"\(ID)-disk" | string + gcePersistentDisk: fsType: "ext4" + }, { + name: *"secret-\(ID)" | string + secret: secretName: *"\(ID)-secrets" | string + }, ...] + + containers: [{ + volumeMounts: [{ + name: *"\(ID)-disk" | string + mountPath: *"/logs" | string + }, { + mountPath: *"/etc/certs" | string + name: *"secret-\(ID)" | string + readOnly: true + }, ...] + }] + } +} +EOF + +cat <> kitchen/souschef/kube.cue + +deployment: souschef: spec: template: spec: { + _hasDisks: false +} + +EOF +cue fmt ./kitchen/... +``` + +Это определение шаблона не идеально: определения позиционные, поэтому если +конфигурации будут определять диски в другом порядке, не будет +повторного использования или даже возникнут конфликты. +Также обратите внимание, что для того, чтобы справиться с этим ограничением, почти все значения полей +просто значения по умолчанию и могут быть переопределены экземплярами. +Лучший способ - определить карту томов, +аналогично тому, как мы организовали объекты Kubernetes верхнего уровня, +и затем генерировать эти два раздела из этой карты. +Это требует некоторого проектирования, и не относится к +руководству "быстро и грязно". +Позже в этом документе мы представим ручную оптимизированную конфигурацию. + +Мы добавляем два диска по умолчанию и определяем опцию `_hasDisks` для отказа. +Конфигурация `souschef` - это единственная, которая не определяет диски. + +#### :arrow_right: Обрежьте и проверьте различия + +:computer: `terminal` +```sh +cue trim -s ./kitchen/... + +# проверьте различия +cue eval -c ./... >snapshot2 +diff -wu snapshot snapshot2 +cp snapshot2 snapshot +find . | grep kube.cue | xargs wc -l | tail -1 +``` + +Diff показывает, что мы добавили опцию `_hasDisks`, но в остальном не раскрывает +различий. +Мы также еще раз значительно сократили конфигурацию. + +Однако, при более внимательном изучении оставшихся файлов мы видим много оставшихся +полей в спецификациях дисков в результате несогласованного именования. +Сокращение конфигураций, как мы делали в этом упражнении, выявляет несоответствия. +Несоответствия можно удалить, просто удалив переопределения в +конкретной конфигурации. +Оставляя их как есть, дается четкий сигнал о том, что конфигурация несогласована. + +### Руководство "Быстро и грязно": выводы + +Есть еще некоторый выигрыш в других каталогах. +При почти 1000-строчном, или 55%, сокращении, мы оставляем остальное как упражнение для +читателя. + +Мы показали, как CUE можно использовать для уменьшения шаблонного кода, обеспечения согласованности, +и выявления несоответствий. +Возможность иметь дело с согласованностями и несоответствиями является следствием +модели на основе ограничений и сложнее реализовать в языках на основе наследования. + +Мы также косвенно показали, как CUE хорошо подходит для машинной манипуляции. +Это фактор синтаксиса и независимости от порядка, которая следует из его +семантики. +Команда `trim` - это один из многих возможных автоматизированных инструментов рефакторинга, +сделанных возможными этим свойством. +Также это было бы сложнее сделать с языками конфигурации на основе наследования. + +## Определение команд рабочего процесса + +Команда `cue export` может использоваться для преобразования созданной конфигурации обратно +в JSON. +В нашем случае это требует "значения вывода" верхнего уровня +для преобразования наших сопоставленных объектов Kubernetes обратно в список. +Обычно этот вывод направляется в такие инструменты, как `kubectl` или `etcdctl`. + +На практике это означает многократный ввод одних и тех же команд. +Следующий шаг часто заключается в написании вспомогательных инструментов. +Но поскольку часто нет решения "один размер подходит всем", это привело к +распространению умеренно полезных инструментов. +Инструмент `cue` предоставляет альтернативу, позволяя объявлять +часто используемые команды в самом CUE. +Преимущества: + +- добавленные доменные знания, которые CUE может использовать для улучшенного анализа, +- только один язык для изучения, +- легкое обнаружение команд рабочего процесса, +- дальнейшая конфигурация не требуется, +- обеспечение единообразных стандартов CLI в командах рабочего процесса, +- стандартизированные команды рабочего процесса по всей организации. + +Команды рабочего процесса определяются в файлах с окончанием `_tool.cue` в том же пакете, что и +где определены файлы конфигурации, на которых должны работать команды рабочего процесса. +Значения верхнего уровня в конфигурации видны файлам инструментов +до тех пор, пока они не затенены полями верхнего уровня в файлах инструментов. +Поля верхнего уровня в файлах инструментов не видны в файлах конфигурации +и не являются частью какой-либо модели. + +Определения инструментов также имеют доступ к дополнительным встроенным пакетам. +Конфигурация CUE полностью герметична, запрещая любое внешнее влияние. +Это свойство позволяет автоматический анализ и манипуляции +такие как команда `trim`. +Однако определения инструментов имеют доступ к таким вещам, как флаги командной строки +и переменные окружения, генераторы случайных чисел, списки файлов и так далее. + +Мы определяем следующие инструменты для нашего примера: + +- `ls`: список объектов Kubernetes, определенных в нашей конфигурации +- `dump`: сброс всех выбранных объектов как потока YAML +- `create`: отправка всех выбранных объектов в `kubectl` для создания + +### Подготовки + +Для работы с Kubernetes нам нужно преобразовать нашу карту объектов Kubernetes +обратно в простой список. + +#### :arrow_right: Создайте файл `kube_tool.cue` + +:floppy_disk: `kube_tool.cue` +```cue +package kube + +objects: [ for v in objectSets for x in v {x}] + +objectSets: [ + service, + deployment, + statefulSet, + daemonSet, + configMap, +] +``` + +### Список объектов + +Команды рабочего процесса определяются в разделе `command` на верхнем уровне файла инструмента. +Команда рабочего процесса `cue` определяет флаги командной строки, переменные окружения, а также +набор задач. +Примеры задач - загрузка или запись файла, вывод чего-то на консоль, +загрузка веб-страницы или выполнение команды. + +#### :arrow_right: Определите команду рабочего процесса `ls` + +Команда рабочего процесса `ls` выведет все наши объекты. Поместите ее в файл `ls_tool.cue`: + +:floppy_disk: `ls_tool.cue` +```cue +package kube + +import ( + "text/tabwriter" + "tool/cli" + "tool/file" +) + +command: ls: { + task: print: cli.Print & { + text: tabwriter.Write([ + for x in objects { + "\(x.kind) \t\(x.metadata.labels.component) \t\(x.metadata.name)" + }, + ]) + } + + task: write: file.Create & { + filename: "foo.txt" + contents: task.print.text + } +} +``` + + +ПРИМЕЧАНИЕ: API ОПРЕДЕЛЕНИЙ ЗАДАЧ БУДЕТ ИЗМЕНЕН. +Хотя мы можем продолжать поддерживать эту форму при необходимости. + +#### :arrow_right: Протестируйте команду рабочего процесса `ls` + +Проверьте, что команда рабочего процесса `ls` теперь доступна в инструменте `cue`, запустив: + +:computer: `terminal` +```sh +cue cmd ls ./frontend/maitred +``` + +Это выведет: + +``` +Service frontend maitred +Deployment frontend maitred +``` + +Если выбрано более одного экземпляра, инструмент `cue` может либо работать +с ними по одному, либо объединять их. +По умолчанию они объединяются. +Разные экземпляры пакета обычно несовместимы: +разные подкаталоги могут иметь разные специализации. +Объединение предварительно расширяет шаблоны каждого экземпляра, а затем объединяет их корневые +значения. +Результат может содержать конфликты, такие как наше поле верхнего уровня `#Component`, +но наши карты объектов Kubernetes по типам должны быть свободны от конфликтов +(если есть, у нас проблемы с Kubernetes в дальнейшем). +Таким образом, объединение дает нам унифицированное представление всех объектов. + +:computer: `terminal` +```sh +cue cmd ls ./... +``` + +Это покажет что-то похожее на ... + +``` +Service frontend bartender +Service frontend breaddispatcher +Service frontend host +Service frontend maitred +Service frontend valeter +Service frontend waiter +Service frontend waterdispatcher +Service infra download +Service infra etcd +Service infra events +[ ... усечено ... ] +``` + +### Сброс потока YAML + +#### :arrow_right: Определите команду рабочего процесса `dump` + +Следующее добавляет команду рабочего процесса для сброса выбранных объектов как потока YAML. + + + +:floppy_disk: `dump_tool.cue` +```cue +package kube + +import ( + "encoding/yaml" + "tool/cli" +) + +command: dump: { + task: print: cli.Print & { + text: yaml.MarshalStream(objects) + } +} +``` + + + +Функция `MarshalStream` преобразует список объектов в поток значений YAML, +разделенных `---`. + +### Создание объектов + +#### :arrow_right: Определите команду рабочего процесса `create` + +Команда рабочего процесса `create` отправляет список объектов в `kubectl create`. + +:floppy_disk: `create_tool.cue` +```cue +package kube + +import ( + "encoding/yaml" + "tool/exec" + "tool/cli" +) + +command: create: { + task: kube: exec.Run & { + cmd: "kubectl create --dry-run=client -f -" + stdin: yaml.MarshalStream(objects) + stdout: string + } + + task: display: cli.Print & { + text: task.kube.stdout + } +} +``` + +Эта команда рабочего процесса имеет две задачи с именами `kube` и `display`. +Задача `display` зависит от вывода задачи `kube`. +Инструмент `cue` выполняет статический анализ зависимостей и запускает все +задачи, зависимости которых удовлетворены, параллельно, блокируя задачи, +для которых отсутствует ввод. + +#### :arrow_right: Протестируйте команду рабочего процесса `create` + +Запуск: + +:computer: `terminal` +```sh +cue cmd create ./frontend/... +``` + +... покажет: + +``` +service/bartender created (dry run) +service/breaddispatcher created (dry run) +service/host created (dry run) +service/maitred created (dry run) +service/valeter created (dry run) +service/waiter created (dry run) +service/waterdispatcher created (dry run) +deployment.apps/bartender created (dry run) +deployment.apps/breaddispatcher created (dry run) +deployment.apps/host created (dry run) +[ ... усечено ... ] +``` + +Реальная версия этой команды в производстве, конечно, должна опустить флаг `--dry-run=client`. + +### Извлечение шаблонов CUE непосредственно из исходного кода Kubernetes Go + +#### :arrow_right: Генерация схем CUE Kubernetes + +Для того чтобы `cue get go` генерировала шаблоны CUE из исходных кодов Go, исходники +должны присутствовать локально заранее. + +:computer: `terminal` +```sh +go get k8s.io/api/apps/v1@v0.23.4 +cue get go k8s.io/api/apps/v1 +``` + +#### :arrow_right: Использование схем CUE Kubernetes + +Теперь, когда у нас есть определения Kubernetes в нашем модуле, мы можем импортировать и +использовать их. Создайте файл `k8s_defs.cue`: + +:floppy_disk: `k8s_defs.cue` +```cue +package kube + +import ( + "k8s.io/api/core/v1" + apps_v1 "k8s.io/api/apps/v1" +) + +service: [string]: v1.#Service +deployment: [string]: apps_v1.#Deployment +daemonSet: [string]: apps_v1.#DaemonSet +statefulSet: [string]: apps_v1.#StatefulSet +``` + +Наконец, мы отформатируем наши файлы CUE: + +:computer: `terminal` +```sh +cue fmt +``` + +## Ручная настройка конфигурации + +В разделе "Быстро и грязно" мы показали, как быстро начать работу с CUE. +С немного большей обдуманностью можно еще больше сократить конфигурации. +Также мы хотели бы определить конфигурацию, которая более общая и менее привязана +к Kubernetes. + +Мы будем активно использовать независимость от порядка CUE, которая позволяет легко +объединять две конфигурации одного и того же объекта определенным образом. +Это упрощает, например, размещение часто используемых полей в одном файле +и более экзотических в другом и затем объединять их без страха, что один +переопределит другой. +Мы применим этот подход в этом разделе. + +Конечный результат этого руководства находится в каталоге верхнего уровня `manual`. +В следующих разделах мы покажем, как туда попасть. + +### Структура + +Основное предположение нашей конфигурации - поддерживать две конфигурации, +простую и абстрактную, и одну, совместимую с Kubernetes. +Версия Kubernetes автоматически генерируется из простой конфигурации. +Каждый упрощенный объект имеет раздел `kubernetes`, который объединяется с +объектом Kubernetes при преобразовании. + +Мы определяем один файл верхнего уровня с нашими общими определениями. + +```cue +package cloud + +service: [Name=_]: { + name: *Name | string // имя сервиса + + ... + + // Специфичные для Kubernetes опции, которые объединяются при преобразовании + // в Kubernetes. + kubernetes: { + } +} + +deployment: [Name=_]: { + name: *Name | string + ... +} +``` + +Файл, специфичный для Kubernetes, затем содержит определения для +преобразования общих объектов в Kubernetes. + +В целом, код, моделирующий наши сервисы, и код, генерирующий код kubernetes, +разделены, но при этом позволяют вводить специфичные для Kubernetes данные +в нашу общую модель. +В то же время мы можем добавлять дополнительную информацию в нашу модель без +того, чтобы она попадала в определения Kubernetes, вызывая сбой. + +### Определение развертывания + +Для нашего дизайна мы предполагаем, что все производные от Kubernetes Pod определяют только один +контейнер. +Это, очевидно, не так в общем случае, но часто бывает так, и это хорошая +практика. +Удобно, что это также упрощает нашу модель. + +Мы основываем модель на общих шаблонах, которые мы вывели в +разделе "Быстро и грязно". +Первым шагом было устранение `statefulSet` и `daemonSet` и +лучше просто иметь `deployment`, позволяющий разные виды. + +```cue +deployment: [Name=_]: _base & { + name: *Name | string + ... +``` + +Вид нужно указывать только если развертывание является набором с сохранением состояния или +демон-набором. +Это также устраняет необходимость в `_spec`. + +Следующий шаг - вынести общие поля, такие как `image`, на верхний уровень. + +Аргументы могут быть указаны как карта. +```cue + arg: [string]: string + args: [ for k, v in arg { "-\(k)=\(v)" } ] | [...string] +``` + +Если порядок важен, пользователи могут явно указать список. + +Для портов мы определяем две простые карты от имени к номеру порта: + +```cue + // expose port определяет именованные порты, которые открыты в сервисе + expose: port: [string]: int + + // port определяет именованный порт, который не открыт в сервисе. + port: [string]: int +``` +Обе карты определяются в определении контейнера, но только `port` включается +в определение сервиса. +Это может быть не лучшей моделью и не поддерживает все функции, +но показывает, как можно выбрать другое представление. + +Аналогичная история и с переменными окружения. +В большинстве случаев сопоставление строк со строками достаточно. +Тестовые данные используют другие опции. +Мы определяем простую карту `env` и `envSpec` для более сложных случаев: + +```cue + env: [string]: string + + envSpec: [string]: {} + envSpec: { + for k, v in env { + "\(k)" value: v + } + } +``` +Простая карта автоматически сопоставляется в более сложную карту, +которая затем представляет полную картину. + +Наконец, наше предположение, что есть один контейнер на развертывание, позволяет нам +создать одно определение для томов, объединяя информацию для +спецификации тома и монтирования тома. + +```cue + volume: [Name=_]: { + name: *Name | string + mountPath: string + subPath: null | string + readOnly: bool + kubernetes: {} + } +``` + +Все другие поля, которые мы хотим определить, могут попасть в общую структуру kubernetes, +которая объединяется со всеми другими сгенерированными данными kubernetes. +Это даже позволяет нам дополнять сгенерированные данные, например, добавляя дополнительные +поля в контейнер. + +### Определение сервиса + +Определение сервиса простое. +Поскольку мы устранили наборы с сохранением состояния и демон-наборы, понимание поля для +автоматического вывода сервиса теперь немного проще: + +```cue +// определяем сервисы, подразумеваемые развертываниями +service: { + for k, spec in deployment { + "\(k)": { + // Копируем все порты, открытые из контейнеров. + for Name, Port in spec.expose.port { + port: "\(Name)": { + port: *Port | int + targetPort: *Port | int + } + } + + // Копируем метки + label: spec.label + } + } +} +``` + +Полные определения модели верхнего уровня можно найти по адресу +[doc/tutorial/kubernetes/manual/services/cloud.cue](https://review.gerrithub.io/plugins/gitiles/cue-lang/cue/+/refs/heads/master/doc/tutorial/kubernetes/manual/services/cloud.cue). + +Настройки для этого конкретного проекта (метки) определены +[здесь](https://review.gerrithub.io/plugins/gitiles/cue-lang/cue/+/refs/heads/master/doc/tutorial/kubernetes/manual/services/kube.cue). + +### Преобразование в Kubernetes + +Преобразование сервисов довольно простое. + +```cue +kubernetes: services: { + for k, x in service { + "\(k)": x.kubernetes & { + apiVersion: "v1" + kind: "Service" + + metadata: name: x.name + metadata: labels: x.label + spec: selector: x.label + + spec: ports: [ for p in x.port { p } ] + } + } +} +``` + +Мы добавляем шаблонный код Kubernetes, сопоставляем поля верхнего уровня и смешиваем +сырые поля `kubernetes` для каждого сервиса. + +Преобразование развертываний немного сложнее, хотя аналогично. +Полные определения для преобразований Kubernetes можно найти по адресу +[doc/tutorial/kubernetes/manual/services/k8s.cue](https://review.gerrithub.io/plugins/gitiles/cue-lang/cue/+/refs/heads/master/doc/tutorial/kubernetes/manual/services/k8s.cue). + +Преобразование определений верхнего уровня в конкретный код Kubernetes - самая сложная +часть этого упражнения. +Тем не менее, большинство пользователей CUE никогда не будут прибегать к такому уровню CUE +для написания конфигураций. +Например, ни один из файлов в подкаталогах не содержит пониманий, +даже файлы шаблонов в этих каталогах (такие как `kitchen/kube.cue`). +Кроме того, ни один из файлов конфигурации в +листовых каталогах не содержит интерполяции строк. + +### Метрики + +Полностью написанная ручная конфигурация может быть найдена в каталоге `manual`. + +Проверьте общее количество строк в каталоге `manual`: + +:computer: `terminal` +```sh +find manual | grep kube.cue | xargs wc -l | tail -1 +``` + +Сообщенные 542 строки не включают наши шаблоны преобразования. +Предполагая, что шаблоны верхнего уровня многоразовые, и если мы не учитываем их +для обоих подходов, ручной подход экономит еще около 150 строк. +Если учитывать шаблоны тоже, два подхода примерно равны. + +### Ручная конфигурация: выводы + +Мы показали, что можно еще больше сжать конфигурацию, вручную +оптимизируя файлы шаблонов. +Однако мы также показали, что ручная оптимизация дает +незначительную выгоду по сравнению с полуавтоматическим быстрым и грязным сокращением. +Преимущества ручного определения в основном лежат в организационной +гибкости. + +Ручная настройка ваших конфигураций позволяет создать абстрактный слой +между логическими определениями и специфичными для Kubernetes определениями. +В то же время независимость от порядка CUE +облегчает смешивание низкоуровневой конфигурации Kubernetes везде, где это +удобно и применимо. + +Ручная настройка также позволяет нам добавлять наши собственные определения без нарушения +Kubernetes. +Это критично для определения информации, относящейся к определениям, +но не связанной с Kubernetes, где они принадлежат. + +Разделение абстрактной и конкретной конфигурации также позволяет нам создавать +различные адаптеры для одной и той же конфигурации. + + \ No newline at end of file diff --git a/005_gitlab_ci/README_RU.md b/005_gitlab_ci/README_RU.md new file mode 100644 index 0000000..64d744d --- /dev/null +++ b/005_gitlab_ci/README_RU.md @@ -0,0 +1,343 @@ +# Управление конвейерами GitLab CI/CD с помощью CUE +от [Jonathan Matthews](https://jonathanmatthews.com) + +Это руководство объясняет, как преобразовать файл конвейера GitLab CI/CD из YAML в +CUE, проверить правильность его содержимого, а затем использовать инструментарий CUE для +повторной генерации YAML. + +Это полезно, потому что позволяет переключиться на CUE как на источник истины для +конвейеров GitLab и выполнять клиентскую проверку, без необходимости GitLab знать, +что вы управляете своими конвейерами с помощью CUE. + +| :exclamation: ПРЕДУПРЕЖДЕНИЕ :exclamation: | +|:---------------------------------------------- | +| Это руководство требует, чтобы вы использовали `cue` версии `v0.11.0-alpha.4` или выше. **Процесс, описанный ниже, не будет работать с более ранними версиями**. Проверьте версию вашей команды `cue`, выполнив `cue version`, и [обновите её](https://cuelang.org/dl), если это необходимо. + +## Предварительные требования + +- У вас есть файл конвейера GitLab. + - Пример, показанный в этом руководстве, использует файл конвейера из конкретного + коммита в репозитории + [`gitlab-org/gitlab`](https://gitlab.com/gitlab-org/gitlab/-/blob/3308936efcd70839cc61e0545dcb780756e4ec28/.gitlab-ci.yml) + на gitlab.com, как указано на + [страницах документации CI GitLab](https://docs.gitlab.com/ee/ci/yaml/), + но **вам не нужно использовать этот репозиторий каким-либо образом**. Он используется как + пример в этом руководстве только потому, что это достаточно сложный файл + конвейера GitLab. +- У вас установлен [`cue`](https://cuelang.org/docs/install/). + - У вас должна быть установлена версия `v0.11.0-alpha.4` или выше. Использование + более ранней версии приведет к сбою определенных команд в этом руководстве. +- У вас установлен [`git`](https://git-scm.com/downloads). +- У вас установлен [`curl`](https://curl.se/dlwiz/), или вы можете каким-то другим способом получить удаленный + файл. + +## Шаги + +### Преобразование конвейера YAML в CUE + +#### :arrow_right: Начните с чистого состояния git + +Перейдите в корневую директорию репозитория, содержащего ваш файл конвейера GitLab, +и убедитесь, что вы начинаете этот процесс с чистого состояния git, без +измененных файлов. Например: + +:computer: `terminal` +```sh +cd gitlab # наш пример репозитория +git status # должен сообщить "working tree clean" +``` + +#### :arrow_right: Инициализируйте модуль CUE + +Инициализируйте модуль CUE с именем организации и репозитория, с которыми вы +работаете, но содержащим только строчные буквы и цифры. Например: + +:computer: `terminal` +```sh +cue mod init gitlab.com/gitlab-org/gitlab +``` + +#### :arrow_right: Импортируйте конвейер YAML + +Используйте `cue` для импорта вашего файла конвейера YAML: + +:computer: `terminal` +```sh +cue import .gitlab-ci.yml --with-context -p gitlab -f -l pipelines: \ + -l 'strings.TrimSuffix(path.Base(filename),path.Ext(filename))' -o gitlab-ci.cue +``` + +Если в вашем проекте используется другое имя для файла конвейера, используйте это имя +в приведенной выше команде и во всем этом руководстве. + +Проверьте, что файл CUE был создан из вашего файла конвейера. Например: + +:computer: `terminal` +```sh +ls {,.}*gitlab-ci* +``` + +Ваш вывод должен выглядеть примерно так, с соответствующими файлами YAML и CUE: + +```text +.gitlab-ci.yml +gitlab-ci.cue +``` +Обратите внимание, что ваш файл был импортирован в структуру `pipelines` в +местоположение, полученное из исходного имени файла, запустив: + +:computer: `terminal` +```sh +head -9 gitlab-ci.cue +``` + +Вывод должен отражать ваш конвейер. В нашем примере: + +```text +package gitlab + +pipelines: ".gitlab-ci": { + stages: [ + "sync", + "preflight", + "prepare", + "build-images", + "fixtures", +``` +#### :arrow_right: Сохраните конвейеры CUE в отдельной директории + +Создайте директорию с именем `gitlab` для хранения ваших файлов конвейеров GitLab на основе CUE. +Например: + +:computer: `terminal` +```sh +mkdir -p internal/ci/gitlab +``` + +Вы можете изменить иерархию и именование **родительских** директорий `gitlab` в +соответствии с макетом вашего репозитория. Если вы это сделаете, вам нужно будет адаптировать +некоторые команды и код CUE при следовании этому руководству. + +Переместите вновь созданный файл конвейера CUE в его выделенную директорию. Например: + +:computer: `terminal` +```sh +mv gitlab-ci.cue internal/ci/gitlab +``` + +### Проверка конвейера + +#### :arrow_right: Создайте схему конвейера + +Получите схему для конвейеров GitLab, определенную проектом GitLab, и +поместите её в директорию `internal/ci/gitlab`: + +:computer: `terminal` +```sh +curl -sSo internal/ci/gitlab/gitlab.cicd.pipeline.schema.json https://gitlab.com/gitlab-org/gitlab/-/raw/277c9f6b643c92d00101aca0f2b4b874a144f7c5/app/assets/javascripts/editor/schema/ci.json +``` + +Мы используем конкретный коммит из репозитория источника, чтобы убедиться, что этот +процесс воспроизводим. + +Преобразуйте схему GitLab из JSON Schema в CUE: + +:computer: `terminal` +```sh +cue import -p gitlab -l '#Pipeline:' \ + internal/ci/gitlab/gitlab.cicd.pipeline.schema.json +``` + +Эта команда создаст файл `internal/ci/gitlab/gitlab.cicd.pipeline.schema.cue` +в пакете `gitlab`, с содержимым схемы источника, помещенным в +поле `#Pipeline`. + +#### :arrow_right: Примените схему + +Нам нужно сказать CUE, чтобы она применила схему к конвейеру. + +Для этого мы создадим файл по адресу `internal/ci/gitlab/pipelines.cue` в нашем +примере. Однако, если ваш импорт конвейера ранее *уже* создал файл с +тем же путем и именем, просто выберите другое имя файла CUE, которое +*еще не существует*. + +Создайте файл в директории `internal/ci/gitlab/` и добавьте этот CUE: + +:floppy_disk: `internal/ci/gitlab/pipelines.cue` + +```cue +package gitlab + +// каждый член структуры pipelines должен быть допустимым #Pipeline +pipelines: [_]: #Pipeline +``` + +#### :arrow_right: Проверьте свои конвейеры + +:computer: `terminal` +```sh +cue vet ./internal/ci/gitlab +``` + +Если эта команда завершается неудачей и выдает какой-либо вывод, то CUE считает, что по крайней мере +один из ваших конвейеров недействителен. Вам нужно будет решить эту проблему перед +продолжением, обновив свои конвейеры внутри новых файлов CUE. Если у вас +возникают трудности с их исправлением, пожалуйста, приходите и попросите помощи в дружелюбном рабочем пространстве CUE +[Slack](https://cuelang.org/s/slack) или +[Discord сервере](https://cuelang.org/s/discord)! + +### Генерация YAML из CUE + +#### :arrow_right: Создайте команду рабочего процесса CUE + +Создайте файл CUE в `internal/ci/gitlab/`, содержащий следующую команду рабочего процесса. +Адаптируйте элемент с комментарием `TODO`: + +:floppy_disk: `internal/ci/gitlab/ci_tool.cue` +```cue +package gitlab + +import ( + "path" + "encoding/yaml" + "tool/file" +) + +_goos: string @tag(os,var=os) + +// Восстановить файлы конвейеров +command: regenerate: { + pipeline_files: { + // TODO: обновите _toolFile, чтобы отразить иерархию директорий, содержащую этот файл. + // TODO: обновите _pipelineDir, чтобы отразить директорию, содержащую ваш файл конвейера. + let _toolFile = "internal/ci/gitlab/ci_tool.cue" + let _pipelineDir = path.FromSlash(".", path.Unix) + let _donotedit = "Code generated by \(_toolFile); DO NOT EDIT." + + for _pipelineName, _pipelineConfig in pipelines + let _pipelineFile = _pipelineName + ".yml" + let _pipelinePath = path.Join([_pipelineDir, _pipelineFile]) { + let delete = { + "Delete \(_pipelinePath)": file.RemoveAll & {path: _pipelinePath} + } + delete + create: file.Create & { + $after: delete + filename: _pipelinePath + contents: "# \(_donotedit)\n\n\(yaml.Marshal(_pipelineConfig))" + } + } + } +} +``` + +Внесите изменения, указанные в комментариях `TODO`. + +Команда рабочего процесса `regenerate` будет экспортировать ваш конвейер на основе CUE обратно в требуемый файл YAML, +по запросу. + +#### :arrow_right: Протестируйте команду рабочего процесса CUE + +При наличии измененного файла `ci_tool.cue` проверьте, что команда рабочего процесса `regenerate` +доступна **из оболочки, находящейся в корне репозитория**. Например: + +:computer: `terminal` +```sh +cd $(git rev-parse --show-toplevel) # убедитесь, что мы находимся в корне репозитория +cue help cmd regenerate ./internal/ci/gitlab # префикс "./" обязателен +``` + +Вывод команды `cue help` **должен** начинаться со следующего: + +```text +Regenerate pipeline files + +Usage: + cue cmd regenerate [flags] +``` +| :exclamation: ПРЕДУПРЕЖДЕНИЕ :exclamation: | +|:---------------------------------------------- | +| Если вы *не* видите объяснение использования команды рабочего процесса `regenerate` (или если вы получаете сообщение об ошибке), то **либо** ваша команда рабочего процесса не настроена так, как требует CUE, **либо** вы используете версию CUE старше `v0.11.0-alpha.4`. Если вы [обновились как минимум до этой версии](https://cuelang.org/dl), но объяснение использования все еще не отображается, то: (1) дважды проверьте содержимое файла `ci_tool.cue` и внесенные в него изменения; (2) убедитесь, что его местоположение в репозитории точно такое же, как указано в этом руководстве; (3) убедитесь, что имя файла *в точности* `ci_tool.cue`; (4) выполните `cue vet ./internal/ci/gitlab` и проверьте, что ваши конвейеры действительно успешно проходят проверку - другими словами: были ли они действительно действительны до того, как вы вообще начали этот процесс? Наконец, убедитесь, что вы выполнили все шаги в этом руководстве и что вы вызвали команду `cue help` из корневой директории репозитория. Если вы действительно застряли, пожалуйста, присоединяйтесь к [сообществу CUE](https://cuelang.org/community/) и попросите помощи! + +#### :arrow_right: Восстановите файл конвейера YAML + +Выполните команду рабочего процесса `regenerate` для создания файла конвейера YAML из CUE. Например: + +:computer: `terminal` +```sh +cue cmd regenerate ./internal/ci/gitlab # префикс "./" обязателен +``` + +#### :arrow_right: Проверьте изменения в файле конвейера YAML + +Проверьте, что ваш файл конвейера YAML имеет одно *существенное* изменение по сравнению с +оригиналом: + +:computer: `terminal` +```sh +git diff .gitlab-ci.yml +``` + +Ваш вывод должен выглядеть примерно как следующий пример: + +```diff +diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml +--- a/.gitlab-ci.yml ++++ b/.gitlab-ci.yml +@@ -1,3 +1,5 @@ ++# Code generated by internal/ci/gitlab/ci_tool.cue; DO NOT EDIT. ++ + stages: + - sync + - preflight +``` +Основное изменение в каждом файле YAML - это добавление заголовка, который предупреждает +читателя не редактировать файл напрямую. + +Ваш diff также может содержать некоторое переформатирование YAML (с изменением количества ведущих +пробелов в вложенных структурах), но это не повлияет на +смысловое содержание файла. + +Кроме того, любые комментарии в оригинальном файле YAML теперь будут находиться *только* +в исходном файле CUE - что важно, поскольку это единственный файл, который вы будете +вручную изменять в дальнейшем. + +#### :arrow_right: Добавьте и зафиксируйте файлы в git + +Добавьте свои файлы в git. Например: + +:computer: `terminal` +```sh +git add .gitlab-ci.yml internal/ci/gitlab/ cue.mod/module.cue +``` + +Обязательно включите немного измененный файл конвейера YAML, где бы вы его ни +храните, вместе со всеми новыми файлами в `internal/ci/gitlab/` и вашим +файлом `cue.mod/module.cue`. + +Зафиксируйте свои файлы в git с соответствующим комментарием к коммиту: + +:computer: `terminal` +```sh +git commit -m "ci: create CUE sources for GitLab CI/CD pipelines" +``` + +## Заключение + +**Отлично - ваш файл конвейера GitLab CI/CD был импортирован в CUE!** + +Теперь им можно управлять с помощью CUE, что приведет к более безопасным и предсказуемым изменениям. +Использование схемы для проверки вашего конвейера означает, что вы будете обнаруживать и исправлять +определенные типы ошибок раньше, чем раньше, без ожидания медленного цикла "git +add/commit/push; проверить, не провалился ли CI". + +Отныне каждый раз, когда вы вносите изменения в файл конвейера CUE, немедленно +восстанавливайте файлы YAML, требуемые GitLab CI/CD, и фиксируйте свои изменения во +всех файлах CUE и YAML. Например: + +:computer: `terminal` +```sh +cue cmd regenerate ./internal/ci/gitlab/ # префикс "./" обязателен +git add .gitlab-ci.yml internal/ci/gitlab/ +git commit -m "ci: added new release pipeline" # пример сообщения +``` \ No newline at end of file