Привет, Хабр!
Сегодня рассмотрим, как настроить CI/CD пайплайн для Docker‑образов: от сборки (с docker buildx и buildah) до пуша с версионированием и автоматической очистки через GitLab API.
Сборка Docker-образа: Buildah или Docker Buildx?
Начнём с основ. Перед вами выбор: использовать buildah или docker buildx. Лично я предпочитаю docker buildx — он отлично вписывается в экосистему Docker и позволяет легко билдить образы под разные архитектуры. Но если важна безопасность и работа в rootless‑режиме, то, возможно, buildah для вас.
Пример сборки через docker buildx:
.build_template: image: docker:latest services: - docker:dind variables: DOCKER_DRIVER: overlay2 DOCKER_TLS_CERTDIR: "" before_script: - docker info - docker buildx create --use script: - docker buildx build --platform linux/amd64 -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
docker:dind
запускается Docker‑in‑Docker, чтобы можно было собирать образы прямо в CI. DOCKER_DRIVER: overlay2
: здесь задаётся драйвер для хранения слоёв.
docker buildx create --use
— создаём билдера, чтобы команда buildx знала, куда направлять запросы. --platform linux/amd64
— явно указываем архитектуру.
buildx позволяет параллельно собирать образы для разных архитектур и использовать кэширование слоёв.
Tagging: как правильно присваивать теги
Допустим, вы собрали десяток образов, и теперь нет ни малейшего представления, какой из них — последний успешный билд. Для решения этой проблемы будем использовать переменные окружения GitLab:
-
CI_COMMIT_SHORT_SHA
— короткий хэш коммита -
CI_COMMIT_REF_NAME
— имя ветки
Пример присвоения тегов:
docker buildx build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME .
Первый тег позволяет однозначно идентифицировать образ по конкретному коммиту. Второй тег позволяет вориентироваться в том, что было собрано из определённой ветки.
При желании можно добавить тег latest, но только для основной ветки (например, main или master), чтобы всегда был актуальный образ.
Чтобы автоматизировать добавление тега latest только для главной ветки, можно сделать так:
script: - | TAGS="-t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME" if [ "$CI_COMMIT_REF_NAME" = "main" ]; then TAGS="$TAGS -t $CI_REGISTRY_IMAGE:latest" fi docker buildx build --platform linux/amd64 $TAGS .
Используем условное выражение в bash, чтобы проверить, является ли текущая ветка основной. Если да — добавляем тег latest.
Push в реестры: GitLab Registry, DockerHub, GHCR
После сборки образы нужно куда‑то отправить. GitLab Registry — хороший выбор, поскольку интеграция происходит из коробки. Но если хочется использовать DockerHub или GitHub Container Registry, всё не так сложно.
Пример авторизации и пуша в GitLab Registry:
docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
docker login — процесс авторизации проходит автоматически, используя переменные, которые GitLab задаёт для вас. Пушим образ сразу с двумя тегами: для коммита и для ветки. Это даёт нам гибкость при развертывании: можно выбрать конкретный билд или просто latest.
Пример для DockerHub:
variables: DOCKERHUB_IMAGE: yourdockerhubuser/yourproject DOCKERHUB_USERNAME: yourdockerhubuser DOCKERHUB_PASSWORD: $DOCKERHUB_PASSWORD script: - echo "$DOCKERHUB_PASSWORD" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA $DOCKERHUB_IMAGE:$CI_COMMIT_SHORT_SHA - docker push $DOCKERHUB_IMAGE:$CI_COMMIT_SHORT_SHA
DockerHub проще в настройке для небольших проектов, но лимиты по количеству запросов могут сыграть злую шутку на масштабных CI/CD системах.
Если вы выбираете GHCR — всё почти аналогично DockerHub, только аутентификация через GitHub Personal Access Token.
Полный .gitlab-ci.yml
Объединяем всё: кеш, артефакты, тесты, деплой. Этот файл можно сразу положить в корень репозитория.
stages: - build - test - deploy variables: IMAGE_TAG: $CI_COMMIT_SHORT_SHA DOCKER_DRIVER: overlay2 DOCKER_TLS_CERTDIR: "" build: stage: build image: docker:latest services: - docker:dind before_script: - docker info - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker buildx create --use script: - | TAGS="-t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME" if [ "$CI_COMMIT_REF_NAME" = "main" ]; then TAGS="$TAGS -t $CI_REGISTRY_IMAGE:latest" fi docker buildx build --platform linux/amd64 $TAGS --push . artifacts: expire_in: 1 hour paths: - docker-compose.yml cache: key: ${CI_COMMIT_REF_SLUG} paths: - .cache test: stage: test image: docker:latest services: - docker:dind script: - echo "Запускаем тесты..." - docker pull $CI_REGISTRY_IMAGE:$IMAGE_TAG - docker run --rm $CI_REGISTRY_IMAGE:$IMAGE_TAG pytest tests/ artifacts: paths: - test-reports/ when: always deploy: stage: deploy image: alpine:latest script: - echo "Запуск деплоя..." - apk add --no-cache curl - curl -X POST "https://my-deploy-endpoint/internal/redeploy?image=$CI_REGISTRY_IMAGE:$IMAGE_TAG" environment: name: production url: https://my-production-app.example.com
stages: определены три стадии — build, test и deploy. Это классическая схема, которая помогает разделить ответственность и ускоряет сборку за счёт параллелизма.
artifacts: после сборки и тестов сохраняем полезные файлы (например, docker‑compose.yml или тестовые отчёты), чтобы можно было анализировать результаты или повторно использовать в последующих job»ах.
cache: кеширование позволяет сохранить промежуточные данные между сборками.
environment: параметр для деплоя, позволяющий GitLab отслеживать, какое окружение используется, и предоставлять удобные ссылки для мониторинга.
Cleanup
Со временем в реестре накопится куча устаревших образов, и они начнут занимать место. Решение — автоматизированная очистка через GitLab API.
Пример скрипта на Python для удаления старых образов:
import requests # Задайте свои значения GITLAB_TOKEN = "your_token" PROJECT_ID = 12345 REGISTRY_URL = f"https://gitlab.com/api/v4/projects/{PROJECT_ID}/registry/repositories" headers = {"PRIVATE-TOKEN": GITLAB_TOKEN} def get_repositories(): response = requests.get(REGISTRY_URL, headers=headers) response.raise_for_status() return response.json() def get_tags(repo_id): tags_url = f"{REGISTRY_URL}/{repo_id}/tags" response = requests.get(tags_url, headers=headers) response.raise_for_status() return response.json() def delete_tag(repo_id, tag_name): delete_url = f"{REGISTRY_URL}/{repo_id}/tags/{tag_name}" response = requests.delete(delete_url, headers=headers) if response.status_code == 204: print(f"Тег {tag_name} успешно удалён из репозитория {repo_id}") else: print(f"Ошибка при удалении тега {tag_name} из репозитория {repo_id}: {response.text}") def cleanup_old_tags(): repositories = get_repositories() for repo in repositories: repo_id = repo['id'] tags = get_tags(repo_id) # Удаляем все теги, кроме 'latest' и 'main' tags_to_delete = [tag['name'] for tag in tags if tag['name'] not in ('latest', 'main')] for tag in tags_to_delete: delete_tag(repo_id, tag) if __name__ == "__main__": cleanup_old_tags()
Получаем список всех репозиториев контейнерного реестра в проекте с помощью API. Затем для каждого репозитория извлекаем список тегов и отфильтровываем те, которые не являются основными. После этого последовательно удаляем лишние теги.
Если у вас есть идеи, вопросы или хотите поделиться своими кейсами — пишите в комментариях.
Если вы хотите углубиться в автоматизацию и сделать свой CI/CD ещё мощнее — приходите на открытые уроки Otus. Будем разбирать реальные кейсы и инструменты, которые отлично дополняют связку GitLab + Docker:
ссылка на оригинал статьи https://habr.com/ru/articles/896010/
Добавить комментарий