Контроллер: мозг, а не рабочая лошадь
Центральный процесс называется контроллер (controller). Раньше его звали master, но термин ушёл в прошлое вместе с slave - современная документация говорит только controller и agent, и ты тоже привыкай так писать и думать. Что делает jenkins контроллер? Он держит конфигурацию, хранит историю сборок и пайплайнов, отдаёт веб-интерфейс и REST API, ставит задачи в очередь и решает, куда их отправить. По сути это диспетчер: он не копает, он командует.
Ключевая мысль урока: на контроллере НЕ собирают. Никогда в проде. Причин несколько. Во-первых, безопасность - код сборки (а это произвольные скрипты из репозитория) не должен исполняться там, где лежат секреты, креды и конфиг всего CI. Один зловредный Jenkinsfile - и злоумышленник внутри сердца системы. Во-вторых, стабильность: тяжёлая компиляция или прожорливый тест съедают память и CPU, и тогда падает не одна сборка, а вообще всё. Поэтому у контроллера должно быть ноль исполнителей.
Сделать это можно руками через "Manage Jenkins" -> "Nodes", а правильнее - декларативно через Configuration as Code (JCasC), чтобы настройка жила в git, а не в чьей-то голове:
Код: Выделить всё
jenkins:
numExecutors: 0
mode: EXCLUSIVE
labelString: "controller"
Код: Выделить всё
numExecutors: 0Код: Выделить всё
mode: EXCLUSIVE
Узлы, агенты и executor: где реально идёт работа
Теперь к рабочей части. Здесь три термина, которые новички путают, поэтому разложим по полочкам.
Узел (node) - это абстракция "машины", которая может выполнять задачи. Jenkins node - общий термин: сам контроллер тоже формально является узлом (built-in node), но рабочие узлы - это отдельные машины. Когда в Jenkinsfile пишут шаг
Код: Выделить всё
node {}Агент (jenkins agent) - это процесс, который запущен на узле и подключён к контроллеру; именно он принимает команды и выполняет шаги сборки. На практике "узел" и "агент" в речи часто сливаются в одно, и это нормально: агент - это узел, готовый к работе.
Исполнитель (jenkins executor) - это слот параллельного выполнения на узле. Один executor = одна задача в один момент времени. Если у агента четыре исполнителя, он тянет до четырёх сборок одновременно. Сколько ставить? Грубое правило - примерно по числу ядер, но смотри на профиль нагрузки: тяжёлые сборки душат друг друга, и иногда два executor дают больше реальной пропускной способности, чем восемь. Jenkins executor - это ровно та единица, которой ты управляешь пропускной способностью своего CI.
Схема такая: контроллер ставит задачу в очередь -> ищет свободный executor на подходящем узле -> отдаёт работу агенту -> агент выполняет шаги и шлёт логи обратно.
Как агент подключается к контроллеру
Способов три, и выбор зависит от инфраструктуры.
1. SSH. Контроллер сам заходит по SSH на удалённую машину, кладёт туда agent.jar и запускает. Удобно для статических Linux-серверов: ничего не надо ставить заранее, нужен только Java и SSH-доступ. Это исходящее соединение от контроллера к агенту.
2. Inbound (бывший JNLP). Здесь наоборот: агент сам стучится на контроллер. Подходит, когда агент за NAT или фаерволом и контроллер до него не достучится (типичный случай - агенты в DMZ или Windows-машины). Запускается примерно так:
Код: Выделить всё
java -jar agent.jar \
-url https://jenkins.example.ru/ \
-secret @secret-file \
-name "win-build-01" \
-workDir /home/jenkins/agent
Важная деталь про версии: агенту нужна совместимая Java. Текущая LTS на середину 2026 - Jenkins 2.541.1 (январь 2026), линейка 2.541.x - последняя с поддержкой Java 11, а начиная с 2.555.1 контроллер и агенты требуют уже Java 21 (или 25), Java 17 там не поддерживается. Так что планируя парк агентов, держи Java в актуальном состоянии, иначе свежий контроллер просто откажется с ними работать.
Метки (labels): как направить задачу на нужный узел
У тебя есть Linux-агенты, Windows-агенты, машина с GPU и узел с доступом в прод-сеть. Как сказать сборке "иди именно на Linux с Docker"? Через метки. Каждому узлу вешаешь метки (linux, docker, gpu, arm64), а в пайплайне указываешь, что тебе нужно:
Код: Выделить всё
pipeline {
agent { label 'linux && docker' }
stages {
stage('Build') {
steps {
sh 'docker build -t myapp:${BUILD_NUMBER} .'
}
}
stage('Test') {
agent { label 'gpu' }
steps {
sh './run-gpu-tests.sh'
}
}
}
}
Код: Выделить всё
agentКод: Выделить всё
&&Код: Выделить всё
||Код: Выделить всё
linux && dockerСтатические и эфемерные агенты: куда всё идёт
Статический агент - это всегда работающая машина (VM или физический сервер), которая постоянно подключена. Минусы очевидны: она простаивает и жрёт деньги, когда сборок нет; на ней накапливается мусор от предыдущих билдов (грязный кеш, чужие зависимости), и "у меня собралось, а у тебя нет" - классика именно отсюда.
Эфемерный агент решает обе беды. Под каждую сборку поднимается чистый одноразовый pod в Kubernetes, выполняет работу и уничтожается. Никакого мусора, оплата только за время сборки, идеальная воспроизводимость. Это стандарт 2026 года для динамического CI. В declarative-пайплайне это выглядит так:
Код: Выделить всё
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.9-eclipse-temurin-21
command: ["sleep"]
args: ["infinity"]
'''
retries 2
}
}
stages {
stage('Build') {
steps {
container('maven') {
sh 'mvn -B clean package'
}
}
}
}
}
Код: Выделить всё
retries 2Код: Выделить всё
container('maven')Раз уж актуализируем: интерфейс Blue Ocean, который во многих туториалах преподносят как современный вид Jenkins, сегодня в режиме поддержки - новых фич не получает, только критичные багфиксы. Не строй на нём ничего нового, это наследие. И ещё к слову о выборе инструмента: у GitLab CI и GitHub Actions агенты (runner-ы) и эфемерность встроены из коробки, конфиг лежит в YAML рядом с кодом. Jenkins же даёт максимальную гибкость и контроль над парком узлов ценой того, что эту архитектуру - контроллер, агенты, метки - надо понимать и настраивать руками. Зато теперь ты её понимаешь.
Типичные грабли
- Оставили исполнителей на контроллере. Сборки идут на нём же, секреты под угрозой, сервер падает под нагрузкой. Лечится .
Код: Выделить всё
numExecutors: 0 - Перекрутили число executor "по числу ядер x2" и удивляются, почему всё тормозит. Тяжёлые сборки конкурируют за память и диск - меньше иногда быстрее.
- Жёстко прописали имя узла вместо метки. Машину пересоздали - пайплайны посыпались. Всегда через label.
- Несовместимая Java на агенте. Обновили контроллер до новой LTS - старые агенты с Java 11/17 отвалились.
- Думают, что эфемерный pod сохранит кеш между сборками. Нет, он одноразовый. Кеш зависимостей выноси в persistent volume или внешний кеш.
- Зайди в "Manage Jenkins" -> "Nodes" и убедись, что у built-in node стоит 0 исполнителей. Если нет - поставь.
- Подними один статический агент по SSH: дай ему метки linux и docker, выставь 2 executor.
- Напиши минимальный declarative-пайплайн с и шагом
Код: Выделить всё
agent { label 'linux && docker' }. Запусти, посмотри в логе, на каком узле он отработал.Код: Выделить всё
sh 'echo $NODE_NAME && nproc' - Поставь в очередь три сборки разом и посмотри в "Build Executor Status", как они разбираются по исполнителям.
- Почему на контроллере в проде не должно быть исполнителей - назови две причины (безопасность и что ещё)?
- Чем узел (node) отличается от исполнителя (executor)? Сколько задач тянет один executor?
- Когда выбирать inbound-подключение агента вместо SSH?
- В чём практическая разница между статическим и эфемерным агентом и почему второй воспроизводимее?
Контроллер оркеструет и хранит состояние, но сам не собирает. Агенты выполняют работу на узлах, а число исполнителей на узле задаёт, сколько задач идёт параллельно. Подключение бывает SSH, inbound или по требованию через Docker/Kubernetes. Метки маршрутизируют задачи по возможностям, а не по именам машин. И главный вектор 2026 года - эфемерные одноразовые агенты в Kubernetes: чисто, дёшево, воспроизводимо. Дальше в курсе именно к ним и идём.