Jenkins в Docker: проверка запуска и пересоздания контейнера
Запустите Jenkins с именованным домашним томом, проверьте HTTP-эндпоинт и пересоздайте контейнер с тем же томом. Экран настройки подтверждает запуск контроллера; повторный запуск проверяет сохранение домашних данных при замене контейнера.
Плавающий тег позже может указывать на другой образ. Один раз определите образ для эксперимента и сохраните начальный пароль в тайне. Временная лаборатория сообщает результаты сравнения без печати пароля.
Закрепите образ для одного эксперимента
Нужны Python3 и Docker engine с Linux-контейнерами. Инструкция образа Jenkins описывает jenkins/jenkins и домашний каталог. Лаборатория начинает с jenkins/jenkins:lts-jdk21, загружает образ и определяет digest репозитория. Каждый контроллер запуска использует digest без повторного поиска плавающего тега. При воспроизведении сохраните digest и полученную версию Jenkins; тег не задаёт постоянный выпуск.
Публикация портов Docker определяет доступ к сопоставленному порту. Скрипт запрашивает 127.0.0.1::8080: Docker выбирает порт хоста на loopback, затем скрипт проверяет привязку. Он не публикует порт агента и не монтирует Docker socket. Это локальная лаборатория контроллера, а не установка Docker build agents или удалённого HTTPS-сервиса.
Выполните полную лабораторию
Сохраните полный файл как jenkins_lab.py. Он создаёт собственные том и контейнеры с уникальными именами. Менеджер контекста удаляет эти ресурсы после теста, в том числе при ошибке готовности или проверки данных. Не подставляйте производственный том в этот временный пример.
jenkins_lab.py
import http.client
import json
import os
import subprocess
import time
import urllib.error
import urllib.request
import uuid
class DockerLab:
def __init__(self):
self.prefix = 'jenkins-proof-' + uuid.uuid4().hex[:12]
self.containers, self.volumes = [], []
def cli(self, *args):
return subprocess.check_output(['docker', *args], text=True, stderr=subprocess.PIPE).strip()
def volume(self):
name = f'{self.prefix}-home-{len(self.volumes)}'
existing = subprocess.run(['docker', 'volume', 'inspect', name], capture_output=True)
if existing.returncode == 0:
raise RuntimeError('Refusing an existing volume')
self.cli('volume', 'create', name)
self.volumes.append(name)
return name
def __enter__(self):
self.cli('info')
requested = os.environ.get('JENKINS_IMAGE', 'jenkins/jenkins:lts-jdk21')
self.cli('pull', requested)
self.image = self.cli('image', 'inspect', requested, '--format', '{{index .RepoDigests 0}}')
if '@sha256:' not in self.image:
raise RuntimeError('Expected a pulled repository digest')
self.home = self.volume()
return self
def start(self, volume=None):
name = f'{self.prefix}-controller-{len(self.containers)}'
self.cli('run', '-d', '--name', name, '-p', '127.0.0.1::8080',
'--mount', f'type=volume,source={volume or self.home},target=/var/jenkins_home', self.image)
self.containers.append(name)
port = self.cli('port', name, '8080/tcp')
if not port.startswith('127.0.0.1:'):
raise RuntimeError('Expected a loopback binding')
deadline = time.monotonic() + 180
while time.monotonic() < deadline:
try:
with urllib.request.urlopen(f'http://{port}/login', timeout=5) as response:
self.version = response.headers.get('X-Jenkins')
if response.status == 200 and self.version and b'Jenkins' in response.read():
return name
except (urllib.error.URLError, TimeoutError, ConnectionError, http.client.HTTPException):
pass
time.sleep(2)
raise RuntimeError('Jenkins login was not ready within 180 seconds')
def password(self, name):
return self.cli('exec', name, 'cat', '/var/jenkins_home/secrets/initialAdminPassword')
def marker(self, name):
return self.cli('exec', name, 'cat', '/var/jenkins_home/lab-marker')
def __exit__(self, *_):
for name in reversed(self.containers):
subprocess.run(['docker', 'rm', '-f', name], capture_output=True)
for name in reversed(self.volumes):
subprocess.run(['docker', 'volume', 'rm', name], capture_output=True)
if __name__ == '__main__':
with DockerLab() as lab:
first = lab.start()
password = lab.password(first)
assert password, 'Missing initial password'
lab.cli('exec', first, 'sh', '-c', 'printf kept > /var/jenkins_home/lab-marker')
lab.cli('rm', '-f', first)
second = lab.start()
assert lab.marker(second) == 'kept'
assert lab.password(second) == password
print(json.dumps({'image': lab.image, 'jenkins': lab.version, 'recreated': True,
'markerPersisted': True, 'passwordPersisted': True, 'result': 'passed'}))
Запустите с работающим Docker daemon:
python3 jenkins_lab.py
Каждый из двух запусков ждёт до 180 секунд HTTP200 на /login, заголовок X-Jenkins и тело с Jenkins. Сбросы соединений, временные HTTP-ошибки и незавершённые HTTP-handshake повторяются в пределах срока. Тест записывает маркер и приватно читает начальный пароль, заменяет первый контейнер и снова сверяет оба значения. Результат с recreated, markerPersisted, passwordPersisted и result: passed означает прохождение выбранных проверок. JSON также фиксирует digest и фактическую версию Jenkins; вымышленная расшифровка загрузки не нужна.
Лаборатория не разблокирует Jenkins, не ставит плагины, не завершает создание администратора и не запускает задания. Готовность проверяется по HTTP-ответу входа, а не завершённой настройке. На Mac без работающего Docker engine сначала запустите выбранный движок; одного Docker CLI недостаточно.
Разделяйте установку и сохранность данных
Для интерактивной установки следуйте официальному мастеру и создайте администратора. Читайте начальный пароль из /var/jenkins_home/secrets/initialAdminPassword своего контроллера; не используйте пример пароля и не публикуйте журналы с ним. Скрипт намеренно удаляет лабораторию и не подходит для сохранения интерактивной установки.
Именованный том Docker переживает удаление контейнера, но не является резервной копией: потеря тома уничтожает данные. Лаборатория резервирования и восстановления остановленного тома продолжает работу с тем же образом и другим целевым томом. Для процесса публикации блога см. Astro Draft PR workflow; пример Jenkins его не заменяет.
Обсуждение