Переменные: контекст пайплайна и секреты
Встроенные CI_-переменные, свои значения в .gitlab-ci.yml и masked-секреты в настройках проекта. Плюс двоеточие, которое ломает YAML.
Откуда job знает, где он запущен
Раннер получает от GitLab несколько десятков переменных: кто запустил пайплайн, из какой ветки, какой коммит, номер job. Объявлять их не нужно, они уже есть.
Материалы этого урока лежат в учебном репозитории. Заведите второй
удалённый адрес: origin указывает на ваш проект, а обновления уроков
приходят из учебного.
git remote add upstream https://gitlab.com/izidevops-learn/ci-start.git
git pull --no-rebase upstream main
cp lesson-06-variables/pipeline.yml .gitlab-ci.yml
Флаг --no-rebase обязателен, и вот почему. К этому моменту ваша история
разошлась с учебной: вы уже сделали свои коммиты с пайплайном. На
разошедшихся ветках git отказывается угадывать, чего вы хотите:
fatal: Need to specify how to reconcile divergent branches.
--no-rebase говорит «сделай обычный merge-коммит». Это же поведение
можно включить один раз навсегда: git config pull.rebase false.
Встроенные переменные
show-context:
stage: info
script:
- echo "Проект $CI_PROJECT_NAME, ветка $CI_COMMIT_BRANCH"
- echo "Коммит $CI_COMMIT_SHORT_SHA, автор $GITLAB_USER_LOGIN"
- echo "Пайплайн номер $CI_PIPELINE_ID, job номер $CI_JOB_ID"
В логе получите примерно такое:
Проект my-ci-site, ветка main
Коммит 9f09955b, автор vladislav.makhankov
Пайплайн номер 2782918422, job номер 16052273647
Эти значения и превращают пайплайн из скрипта в инструмент. Тег образа по
CI_COMMIT_SHORT_SHA, имя окружения по CI_COMMIT_BRANCH, ссылка на job
по CI_JOB_ID в уведомлении.
Полный список открывается по env в любом job. Их правда много, больше
сотни, и набор зависит от того, чем запущен пайплайн.
Свои переменные
Объявляются блоком variables в корне файла или внутри job:
variables:
SITE_TITLE: "Собрано пайплайном"
BUILD_DIR: "public"
pages:
stage: build
script:
- mkdir -p "$BUILD_DIR"
- cp style.css "$BUILD_DIR/"
- sed -e "s|<h1>.*</h1>|<h1>$SITE_TITLE</h1>|"
-e "s|<dd id=\"commit\">.*</dd>|<dd id=\"commit\">$CI_COMMIT_SHORT_SHA</dd>|"
-e "s|<dd id=\"branch\">.*</dd>|<dd id=\"branch\">$CI_COMMIT_BRANCH</dd>|"
index.html > "$BUILD_DIR/index.html"
artifacts:
paths:
- $BUILD_DIR
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Тут переменные впервые дают видимый результат. На странице есть пустые
поля «коммит» и «ветка», и sed подставляет в них значения из окружения
job. Запушьте, дождитесь деплоя и обновите свой сайт: в карточке появится
короткий хеш коммита, из которого она собрана.
Переменные раскрываются и в artifacts.paths, не только в script.
Переменная внутри job перекрывает одноимённую из корня.
Кавычки вокруг "$BUILD_DIR" не украшение. Без них путь с пробелом
развалится на два аргумента, и mkdir создаст не то, что вы ждали.
Секреты живут в настройках, не в файле
.gitlab-ci.yml лежит в git и виден всем, у кого есть доступ к репозиторию
(а у публичного проекта — вообще всем). Токены и пароли туда не пишут.
Идите в Settings -> CI/CD -> Variables -> Add variable.
Ключ DEPLOY_TOKEN, значение произвольное, и главное: тип переменной.
GitLab предлагает три варианта, причём новые переменные с версии 18.3
создаются сразу как Masked.
Visible — обычная переменная, значение видно в настройках и в логах. Для флагов и путей.
Masked — значение заменяется на [MASKED] везде, где оно попадёт в
лог. В настройках его по-прежнему можно подсмотреть.
Masked and hidden — то же самое плюс запрет на просмотр в интерфейсе после сохранения. Для боевых токенов берите этот режим: даже вы сами значение больше не увидите, только перезапишете.
Важная деталь: включить скрытие можно только при создании переменной. Перевести в этот режим уже существующую нельзя, придётся удалить и завести заново.
Что маскировка ловит, а что нет
Проверьте на живом job:
show-secret:
stage: info
script:
- 'echo "Длина токена: ${#DEPLOY_TOKEN}"'
- 'echo "Сам токен: $DEPLOY_TOKEN"'
В логе:
Длина токена: 22
Сам токен: [MASKED]
Значение скрыто, а длина утекла. Маскировка ищет в выводе точное
совпадение со строкой секрета и заменяет его. Всё, что вы посчитали от
секрета сами (длина, хеш, первые символы через cut), пройдёт в лог
спокойно.
Отсюда практическое правило: секрет не печатают вообще, даже частично. Передавайте его сразу в команду, лучше через stdin:
- echo "$DEPLOY_TOKEN" | docker login -u ci --password-stdin registry.example.com
У маскировки есть требования к значению: минимум 8 символов, одна строка без пробелов, и значение не должно совпадать с именем другой переменной. Если GitLab не даёт включить Masked, причина обычно в этом списке.
Многострочный секрет: приём с base64
Маскировка требует значение без переводов строки, а секреты часто многострочные: kubeconfig, приватный SSH-ключ, JSON сервисного аккаунта. Положить их в переменную как есть не выйдет, Masked включить не дадут.
Обходят это кодированием в base64. На своей машине:
base64 < kubeconfig.yaml | tr -d '\n'
tr -d '\n' обязателен: на Linux base64 переносит строку каждые 76
символов, и без него вы получите то же многострочное значение, от которого
уходили.
Полученную строку кладёте в переменную KUBECONFIG_B64 с типом Masked, а в
пайплайне разворачиваете обратно в файл:
deploy:
script:
- echo "$KUBECONFIG_B64" | base64 -d > /tmp/kubeconfig
- export KUBECONFIG=/tmp/kubeconfig
- kubectl get pods
Файл ложится в /tmp, а не в рабочую папку: иначе он рискует уехать в
артефакт вместе с остальной сборкой.
Чего base64 не делает
Это кодирование, а не шифрование. base64 -d раскодирует кто угодно, так
что смысл приёма ровно один: уложить многострочное значение в одну строку.
И тут есть ловушка, которую видно на живом job:
$ echo "Сырая переменная: $KUBECONFIG_B64"
Сырая переменная: [MASKED]
$ cat /tmp/kubeconfig
apiVersion: v1
clusters:
- cluster:
server: https://k8s.example.com
name: prod
Сама переменная замаскирована. А содержимое файла после декодирования вылилось в лог целиком, потому что маскировка сравнивает вывод с base64 -строкой, и раскодированный текст под неё не подходит.
В этом примере строка token: всё-таки скрылась, но по отдельной причине:
её значение заведено вторым секретом в настройках проекта. Само по себе
декодирование ничего не прячет.
Отсюда правило: раскодировали в файл — файл не печатаем. Ни cat, ни
kubectl config view --raw (без флага секреты как раз прячутся, с ним
показываются), ни отладочный вывод «на минутку».
Двоеточие, которое ломает пайплайн
Обратите внимание на кавычки вокруг всей строки:
- 'echo "Длина токена: ${#DEPLOY_TOKEN}"'
Без внешних кавычек GitLab отвечает так:
jobs:show-secret:script config should be a string
or a nested array of strings up to 10 levels deep
Причина в YAML, а не в GitLab. Последовательность : (двоеточие с
пробелом) внутри незакавыченной строки читается как разделитель ключа и
значения, и элемент списка превращается в словарь. Любой echo с
двоеточием в тексте нужно брать в одинарные кавычки целиком.
Что важно запомнить
Встроенные CI_* переменные дают job контекст: проект, ветку, коммит,
номера пайплайна и job.
Свои переменные объявляются в variables, работают и в script, и в
artifacts.paths; значение внутри job перекрывает корневое.
Секреты идут в Settings -> CI/CD -> Variables с типом Masked, а для боевых токенов Masked and hidden.
Маскировка прячет только точное совпадение. Длину, хеш и обрезок секрета она не поймает, поэтому не печатайте его совсем.
Многострочные секреты кладут в переменную через base64 и разворачивают в
файл в /tmp. Это упаковка, а не защита: раскодированное содержимое
маскировка уже не узнаёт.
Двоеточие с пробелом внутри команды требует одинарных кавычек вокруг всей строки, иначе YAML разберёт её как словарь.
Дальше собираем сайт настоящим сборщиком в контейнере с Node.
Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс