← CI/CD: практика
15 мин · средне · Урок 10 из 10

Антипаттерны: пять способов сделать пайплайн бесполезным

Зелёный job, который врёт, пароль в логе, latest вместо версии, монолитная задача и мёртвый only. Каждый разобран на живом прогоне.

Зачем разбирать плохое

Работающий пайплайн собрать несложно. Сложнее заметить, что он работает вхолостую: гоняется, зеленеет, а от проблем не защищает. Ниже пять таких случаев, каждый встречается в чужих конфигах регулярно.

Плохой пример целиком лежит в lesson-10-antipatterns/bad-pipeline.yml, исправленный рядом в pipeline.yml.

1. Зелёный job, который врёт

green-lie:
  image: node:22-alpine
  script:
    - node -e "throw new Error('тест упал')" || true
    - echo "job завершился успешно"

Прогон этого job в логе:

$ node -e "throw new Error('тест упал')" || true
Error: тест упал
    at [eval]:1:7
$ echo "job завершился успешно"
job завершился успешно

Статус — Passed, время — 9 секунд. Тест упал, ошибка в логе, пайплайн зелёный.

|| true дописывают, чтобы «пока не мешало»: тесты флакают, линтер ругается на легаси. Дальше про это забывают, и CI превращается в декорацию, которая всегда показывает зелёный.

Если проверку временно нельзя делать блокирующей, для этого есть честный способ:

lint:
  script:
    - npm run lint
  allow_failure: true

Разница принципиальная. || true прячет падение, allow_failure его показывает: job помечается предупреждением, видно, что именно не прошло, и это можно вернуть в блокирующие, когда почините.

2. Секрет в конфиге

    - 'echo "Пароль от прода: hunter2"'

В логе:

Пароль от прода: hunter2

Тут два отдельных греха. Первый: пароль лежит в .gitlab-ci.yml, то есть в git, то есть в истории навсегда, у всех, кто клонировал репозиторий. Второй: он напечатан в лог, а логи job видны шире, чем кажется, и живут месяцами.

Как правильно, разбирали в уроке про переменные: значение в Settings -> CI/CD -> Variables с типом Masked, в команду через stdin, в лог никогда.

Отдельно про удаление. Убрать пароль коммитом недостаточно: он остаётся в истории. Утёкший секрет надо считать скомпрометированным и менять, а не прятать.

3. latest вместо версии

image: node:latest

Тег latest подвижный. Сегодня за ним Node 22, через месяц Node 24 с другим поведением. Сборка, которая вчера проходила, сегодня падает, коммитов между ними нет, и человек час ищет причину в своём коде.

То же самое с образами, которые собираете вы: myapp:latest в registry означает «неизвестно что», по нему невозможно ни понять, что в проде, ни откатиться.

Ставьте конкретный тег: node:22-alpine для окружения сборки, myapp:$CI_COMMIT_SHORT_SHA для своих образов. Тег, привязанный к коммиту, превращает откат в запуск предыдущего образа.

4. Один job на всё

deploy-everything:
  script:
    - npm ci
    - npm test
    - npm run build
    - echo "деплой на прод"

Формально работает. Проблем три.

Обратной связи нет: в списке видно «job упал», а что именно упало, тесты или деплой, ясно только из лога.

Перезапуск дорогой: чтобы повторить деплой, придётся заново ставить зависимости и гонять тесты.

Параллельность недоступна: линтер и юнит-тесты могли бы идти одновременно, но они внутри одной последовательной команды.

Разнесите по стадиям, как в уроке про стадии: test, build, deploy. Каждый со своим статусом, своим временем и возможностью перезапустить только нужное.

5. only, который живёт рядом с rules

mixed:
  script:
    - echo hi
  only:
    - main
  rules:
    - if: $CI_COMMIT_BRANCH

Пайплайн даже не стартует:

Unable to run pipeline
jobs:mixed config key may not be used with `rules`: only

Это типичный результат правки чужого конфига: человек добавил rules поверх старого only, не убрав его. Работавший пайплайн ломается целиком, а не в одном job: ошибка конфигурации отменяет весь прогон.

only/except и rules несовместимы внутри одного job. Переписываете job — убирайте only полностью.

Как исправленная версия выглядит целиком

stages:
  - test
  - build
  - deploy

test:
  stage: test
  image: node:22-alpine
  script:
    - npm ci
    - npm test

build:
  stage: build
  image: node:22-alpine
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist
    expire_in: 1 day

deploy:
  stage: deploy
  script:
    - echo "Выкат версии $CI_COMMIT_SHORT_SHA"
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      when: manual

Тесты блокируют сборку, сборка отдаёт артефакт с ограниченным сроком жизни, выкат идёт только с основной ветки и по кнопке, версия привязана к коммиту, секретов в файле нет.

Что важно запомнить

|| true делает проверку декоративной; когда падение допустимо осознанно, это allow_failure: true.

Секрет в .gitlab-ci.yml остаётся в истории git навсегда и требует смены, а не удаления коммитом.

latest — подвижная метка: фиксируйте версию образа, свои образы тегайте по CI_COMMIT_SHORT_SHA.

Один job на всё лишает вас понятного статуса, дешёвого перезапуска и параллельности.

only и rules в одном job валят весь пайплайн на этапе разбора конфига.

На этом практический трек закончен. У вас есть пайплайн со стадиями, сборкой в контейнере, кешем, секретами и ручным выкатом — то есть скелет того, что делают в работе.

Прошли материал до конца? Отметьте урок пройденным.