Шаг 1. Подготовка окружения и загрузка Docker образов
Для этого используем sh скрипт pull‑images‑setup.sh, который выполняет несколько действий: устанавливает k3d и Helm, скачивает необходимые docker образы и создает локальные теги для них, которые будут использоваться через локальный реестр.
Перед запуском скрипта нужно убедиться, что установлены необходимые инструменты:
-
Docker Desktop — понадобится для запуска контейнеров и дальнейшего создания k3s‑кластера через k3d
-
App Installer — предоставляет пакетный менеджер
winget, через который скрипт установит k3d и Helm -
Git Bash — понадобится для запуска
shскриптов в Windows.
#!/bin/bashecho "Установка k3d и Helm..."winget install k3dwinget install Helm.Helmecho "Загрузка Docker образов..."docker pull registry:3docker pull gitea/gitea:1.25docker pull jenkins/jenkins:2.554docker pull maven:3.9.14-eclipse-temurin-21docker pull eclipse-temurin:21.0.10_7-jdk-jammydocker pull jenkins/inbound-agent:jdk21docker pull gcr.io/kaniko-project/executor:v1.24.0echo "Создание тегов для локального registry..."docker tag maven:3.9.14-eclipse-temurin-21 localhost:5000/maven:3.9.14-eclipse-temurin-21docker tag eclipse-temurin:21.0.10_7-jdk-jammy localhost:5000/eclipse-temurin:21.0.10_7-jdk-jammydocker tag gcr.io/kaniko-project/executor:v1.24.0 localhost:5000/kaniko:v1.24.0echo "Проверка загруженных образов:"docker images | grep -E "registry|gitea|jenkins|maven|eclipse-temurin"
-
registry— локальное хранилище Docker‑образов -
gitea— локальный Git‑сервер -
jenkins— CI‑сервер -
mavenиeclipse-temurin— окружение для сборки и запуска Java‑приложения -
jenkins/inbound-agent— базовый образ Jenkins Agent -
kaniko— инструмент для сборки контейнерных образов без использования docker daemon непосредственно в процессе сборки
Шаг 2. Разворачиваем локальный k3s кластер
Теперь можно переходить к созданию самого кластера.
Для этого используется sh скрипт setup‑cluster.sh. Он создаёт docker сеть и необходимые volumes, разворачивает k3s кластер через k3d, устанавливает Argo CD, запускает сервисы из docker compose и подготавливает образы, которые понадобятся Jenkins.
#!/bin/bashecho "========================================="echo "Запуск полной настройки test-prod-cluster"echo "========================================="# Создаем сетьecho "Создание сети test-prod-net..."docker network create test-prod-netsleep 5# Создаём постоянный volume, который будут использовать все агентыecho "Создание volume который будут использовать все агенты..."docker volume create build-workspace# Даём полные права на запись в volume (чтобы jenkins мог писать)docker run --rm -v build-workspace:/workspace alpine sh -c "chmod 777 /workspace"sleep 5# Создаём volumes для кэшаecho "Создание volume для кеша Maven..."docker volume create maven-cache# Даём полные права на запись в volume (Maven будет писать сюда)docker run --rm -v maven-cache:/maven-cache alpine sh -c "chmod 777 /maven-cache"sleep 5# Создание кластера k3decho "Создание кластера test-prod-cluster..."k3d cluster create test-prod-cluster \ --network test-prod-net \ --api-port 6550 \ -p "8081:80@loadbalancer" \ -p "8443:443@loadbalancer" \ --agents 1 \ --registry-config registries.yamlecho "Кластер создан. Ожидание 60 секунд..."sleep 60# Замена URL в kubeconfigecho "Обновление kubeconfig..."KUBECONFIG_PATH="$HOME/.kube/config"sed -i 's|https://host\.docker\.internal:6550|https://localhost:6550|g' "$KUBECONFIG_PATH"# Установка Argo CDecho "Добавление репозитория Argo CD..."helm repo add argo https://argoproj.github.io/argo-helmhelm repo updateecho "Установка Argo CD..."kubectl create namespace argocdhelm install argocd argo/argo-cd -n argocdecho "Установка ArgoCD завершена. Ожидание 60 секунд..."sleep 60# Запуск Docker Composeecho "Запуск Docker Compose..."docker compose up -decho "Запуск Docker Compose завершен. Ожидание 30 секунд..."sleep 30# Применение Applicationecho "Применение Application..."kubectl apply -f application.yamlecho "Применение Application завершено. Ожидание 20 секунд..."sleep 20# Сборка и пуш образа Jenkins агентаecho "Сборка образа Jenkins агента..."docker build -f Dockerfile.agent -t localhost:5000/jenkins-agent:docker .docker push localhost:5000/jenkins-agent:docker# Пуш Maven образа в registryecho "Пуш Maven образа в registry..."docker tag maven:3.9.14-eclipse-temurin-21 localhost:5000/maven:3.9.14-eclipse-temurin-21docker push localhost:5000/maven:3.9.14-eclipse-temurin-21# Пуш eclipse-temurin образа в registryecho "Пуш eclipse-temurin образа в registry..."docker tag eclipse-temurin:21.0.10_7-jdk-jammy localhost:5000/eclipse-temurin:21.0.10_7-jdk-jammydocker push localhost:5000/eclipse-temurin:21.0.10_7-jdk-jammy# Пуш Kanico образа в registryecho "Пуш Kanico образа в registry..."docker tag gcr.io/kaniko-project/executor:v1.24.0 localhost:5000/kaniko:v1.24.0docker push localhost:5000/kaniko:v1.24.0echo "========================================="echo "Настройка завершена!"echo "========================================="echo "Gitea: http://localhost:3000"echo "Jenkins: http://localhost:8082"echo "Argo CD: kubectl get pods -n argocd"
После установки Argo CD применяется файл application.yaml:
kubectl apply -f application.yaml
Этот манифест создаёт в Argo CD объект Application. В нём указывается git репозиторий с Helm чартами, путь к нужному chart, целевой кластер и namespace, куда должно быть развернуто приложение.
После этого Argo CD начинает следить за репозиторием helm-charts. Когда Jenkins обновляет в нём тег docker образа и отправляет изменения в gitea, argo cd обнаруживает изменение и синхронизирует состояние приложения в k3s кластере.
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: myapp namespace: argocdspec: project: default source: repoURL: http://gitea:3000/<gitea_user>/helm-charts.git path: myapp targetRevision: HEAD destination: server: https://kubernetes.default.svc namespace: myapp-prod syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true
Работать с кластером можно полностью через kubectl, но для учебного стенда удобно дополнительно использовать OpenLens — графический интерфейс для Kubernetes.
Шаг 3. Подготавливаем репозитории в Gitea
Теперь подготовим Git репозитории, которые будут использоваться в нашем учебном CI/CD процессе.
В качестве локального Git сервера используется Gitea. Она уже запускается в составе окружения и доступна по адресу:
http://localhost:3000
Для дальнейшей работы нам понадобятся два репозитория:
-
test-java-app— исходный код тестового Java‑приложения; -
helm-charts— helm чарты, которые описывают желаемое состояние приложения в Kubernetes.
Разделение на два репозитория сделано специально. Jenkins будет работать с исходным кодом, собирать docker образ и после успешной сборки обновлять версию образа в helm чарте.
ArgoCD в свою очередь будет следить уже не за репозиторием приложения, а за репозиторием helm-charts. Как только Jenkins изменит там тег образа, ArgoCD увидит изменение и синхронизирует состояние кластера.
После первого запуска Gitea необходимо создать пользователя и затем вручную создать два пустых репозитория:
test-java-apphelm-charts
После этого локальные репозитории можно связать с Gitea и отправить туда код.
Для приложения:
git initgit remote add origin http://localhost:3000/<username>/test-java-app.gitgit branch -M maingit add *git commit -m "first commit"git push -u origin main
Для Helm чартов:
git initgit remote add origin http://localhost:3000/<username>/helm-charts.gitgit branch -M maingit add *git commit -m "first commit"git push -u origin main
На этом этапе Gitea становится точкой хранения исходного кода и конфигурации развертывания, а дальше мы подключим к этим репозиториям Jenkins и ArgoCD.
Шаг 4. Запускаем и настраиваем Jenkins
Следующий компонент нашего стенда Jenkins. В этой схеме он будет отвечать за CI часть процесса:
-
получать исходный код приложения из Gitea
-
запускать сборку
-
собирать docker образ
-
отправлять образ в локальный репозиторий
-
обновлять тег образа в репозитории с Helm чартами
Сам Jenkins уже запускается через docker compose внутри нашего sh скрипта, поэтому после выполнения предыдущих шагов его интерфейс должен быть доступен по адресу:
http://localhost:8082
При первом запуске Jenkins попросит ввести начальный пароль администратора. Получить его можно командой (powershell):
docker exec test-prod-jenkins cat /var/jenkins_home/secrets/initialAdminPassword
После ввода пароля Jenkins предложит установить плагины. Для учебного стенда можно сначала выбрать стандартный набор, а затем дополнительно установить плагины, которые понадобятся для работы с docker:
-
Docker— для создания временных Jenkins Agent через Docker -
CloudBees Docker Build and Publish— для работы со сборкой и публикацией docker образов
После установки плагинов Jenkins можно перезапустить из интерфейса.
Далее проверим базовые параметры Jenkins:
System Configuration → System → Jenkins Location
В поле Jenkins URL для нашего локального стенда используется внутреннее имя контейнера:
http://jenkins:8080
Это важно: из браузера мы открываем Jenkins через localhost:8082, но другие контейнеры внутри Docker сети обращаются к нему по имени сервиса jenkins.
Добавляем полномочия(credentials) для Gitea
Теперь нужно дать Jenkins доступ к нашим репозиториям в Gitea.
В дальнейшем Jenkins будет использовать эти полномочия для двух задач:
-
клонирования репозитория с исходным кодом приложения
-
отправки изменений в репозиторий
helm-charts
В интерфейсе Jenkins переходим:
Настройки Jenkins → Credentials → Global → Add Credentials
В качестве типа credentials выбираем: Username with password
И указываем данные пользователя Gitea:
Username: <ваш пользователь Gitea>Password: <пароль пользователя Gitea>ID: gitea-credentials
Поле ID здесь особенно важно. В нашем pipeline полномочия будут запрашиваться именно по ID.
Настраиваем Docker Cloud в Jenkins
Теперь настроим окружение, в котором Jenkins будет выполнять наши сборки.
В этой конфигурации Jenkins использует Docker Cloud. Его задача — динамически создавать временные Jenkins Agent контейнеры для выполнения pipeline. После завершения сборки такой агент удаляется.
Переходим в: Настройки Jenkins → Clouds → New cloud → Type Docker
Name: my-docker-cloudDocker Cloud details -> Docker Host URI: tcp://host.docker.internal:2375 (включен в Desktop)Enabled: true
Адрес host.docker.internal позволяет контейнеру Jenkins обращаться к Docker Engine, который работает на Windows хосте через Docker Desktop. Порт 2375 используется для подключения к Docker daemon по TCP. Для нашего локального учебного стенда это упрощает взаимодействие Jenkins с Docker.
После этого добавляем шаблон Docker Agent для Kaniko‑сборки.
В разделе Docker Agent templates создаём новый шаблон со следующими параметрами:
Labels: kanikoEnabled: trueName: kaniko-agentDocker Image: localhost:5000/jenkins-agent:docker
Раздел Container settings:
Mounts: type=volume,src=build-workspace,dst=/workspaceNetwork: test-prod-netRemote File System Root: /home/jenkinsConnect method: Connect with JNLPPull strategy: Pull all images every time
Нажми Apply внизу, потом Save
После сохранения шаблона Jenkins сможет динамически создавать временный kaniko-agent для каждой сборки, а после завершения pipeline агент будет удаляться.
Создаём pipeline для Java приложения: сборка через Kaniko
Теперь, когда Jenkins умеет создавать временный агент с лейбломkaniko, можно перейти к самому pipeline.
Создаём новую задачу: New Item → Pipeline
В качестве имени можно использовать: build-java-app-kaniko
Далее выбираем: Definition: Pipeline script
Definition: Pipeline script
Наш pipeline будет выполнять несколько последовательных действий:
-
создавать рабочую директорию
-
клонировать исходный код приложения из Gitea
-
собирать Docker‑образ с помощью Kaniko
-
отправлять образ в локальный registry
-
обновлять тег образа в Helm‑чарте
-
отправлять изменения обратно в Gitea
Важно: перед запуском замените <gitea_user> на имя своего пользователя в Gitea.
Скрипт:
pipeline { agent { label 'kaniko' } environment { IMAGE_TAG = "registry:5000/test-java-app:${BUILD_NUMBER}" GIT_REPO = "http://gitea:3000/<gitea_user>/test-java-app.git" GIT_CREDENTIALS = "gitea-credentials" HELM_REPO = "http://gitea:3000/<gitea_user>/helm-charts.git" } stages { stage('Prepare workspace') { steps { sh """ rm -rf /workspace/repo mkdir -p /workspace/repo """ } } stage('Checkout') { steps { echo "Клонируем репозиторий приложения..." dir('/workspace/repo') { git branch: 'main', url: "${GIT_REPO}", credentialsId: "${GIT_CREDENTIALS}" } } } stage('Build with Kaniko') { steps { sh """ export DOCKER_HOST=tcp://host.docker.internal:2375 docker run \\ -v build-workspace:/workspace \\ -v maven-cache:/root/.m2 \\ --network test-prod-net \\ localhost:5000/kaniko:v1.24.0 \\ --dockerfile /workspace/repo/Dockerfile \\ --destination ${IMAGE_TAG} \\ --context dir:///workspace/repo \\ --insecure \\ --insecure-pull """ } } stage('Update Helm Chart') { steps { echo "Обновляем тег в helm чартах..." withCredentials([usernamePassword( credentialsId: 'gitea-credentials', usernameVariable: 'GIT_USER', passwordVariable: 'GIT_PASS' )]) { sh """ git clone ${HELM_REPO} helm-charts cd helm-charts sed -i "s/tag:.*/tag: ${BUILD_NUMBER}/" myapp/values.yaml git config user.email "jenkins@local" git config user.name "Jenkins CI" git add myapp/values.yaml git commit -m "Update app version to ${BUILD_NUMBER}" git push http://${GIT_USER}:${GIT_PASS}@gitea:3000/<gitea_user>/helm-charts.git main """ } } } stage('Verify') { steps { echo "Готово! Образ ${IMAGE_TAG} запушен, helm чарты обновлены" echo "ArgoCD подхватит изменения автоматически" } } } post { always { echo "Очищаем ресурсы Docker..." script { sh """ export DOCKER_HOST=tcp://host.docker.internal:2375 docker ps -a \ --filter "ancestor=localhost:5000/kaniko:v1.24.0" \ --filter "status=exited" -q | xargs -r docker rm """ } } }}
После сохранения pipeline запускаем его через Build Now.
Если всё настроено правильно, Jenkins соберёт новый образ, отправит его в локальный registry, обновит тег в репозитории helm-charts, а ArgoCD подхватит изменения и применит новую версию приложения в кластере.
Удаление учебного стенда
После экспериментов окружение можно удалить с помощью отдельного скрипта delete-k3d-helm.sh.
Он удаляет установленный Argo CD и его namespace, удаляет Helm‑репозиторий Argo CD, удаляет локальные k3d кластеры, а также деинсталлирует Helm и k3d.
Внимание: скрипт предназначен для полного удаления учебного окружения. Команда k3d cluster delete --all удаляет все k3d‑кластеры на компьютере, а не только созданный в рамках этой статьи. Кроме того, скрипт удаляет установленные через winget Helm и k3d. Если эти инструменты или другие k3d кластеры используются вами для других проектов, не запускайте скрипт целиком.
#!/bin/shecho "=== Удаление Argo CD ==="# Удаление Argo CD (если установлен)helm uninstall argocd -n argocd 2>/dev/null || echo "Argo CD не найден или уже удален"# Удаление namespace Argo CDkubectl delete namespace argocd 2>/dev/null || echo "Namespace argocd не найден"echo "=== Удаление репозитория Helm ==="# Удаление репозитория Argo CD из Helmhelm repo remove argo 2>/dev/null || echo "Репозиторий argo не найден"echo "=== Удаление Helm ==="# Удаление Helm через wingetwinget uninstall Helm.Helm 2>/dev/null || echo "Helm не найден"echo "=== Удаление k3d ==="# Удаление всех кластеров k3dk3d cluster delete --all 2>/dev/null || echo "Нет кластеров k3d для удаления"# Удаление k3d через wingetwinget uninstall k3d 2>/dev/null || echo "k3d не найден"echo "=== Очистка конфигурации kubectl ==="# Очистка контекста kubectl (опционально)kubectl config unset current-context 2>/dev/null || echo "Контекст не установлен"kubectl config delete-cluster k3d-* 2>/dev/null || echo "Кластеры k3d не найдены в конфиге"kubectl config delete-context k3d-* 2>/dev/null || echo "Контексты k3d не найдены"echo "=== Откат завершен ==="
Ссылка на репозиторий: https://github.com/Evgeni-86/LabClusterV1
ссылка на оригинал статьи https://habr.com/ru/articles/1093586/