Распространенный сценарий: команда разработчиков поддерживает множество Jenkinsfile, каждый из которых соответствует требованиям проекта. Однако эти сценарии непрерывной интеграции (CI) непригодны для повторного использования в другом хранилище. Причина заключается в том, что каждый проект обладает собственным технологическим стеком, версией, зависимостями для инструментов и т. д.
В таком случае многоэтапные сборки в docker— идеальное решение.
Эта технология уже давно известна, однако многие пользователи не осознают мощности ее потенциала.
Идея заключается в объединении нескольких Dockerfiles в один Dockerfile. Каждый файл будет выполнять определенную задачу в процессе разработки.
Рассмотрим на примере. Для демонстрации передовых концепций я добавил несколько сложных структур. Для начала ознакомьтесь с официальной документацией, чтобы узнать о возможностях CI.
Начнем с тестирования sonar:
FROM newtmitch/sonar-scanner AS sonar
COPY src src
RUN sonar-scanner
Затем устанавливаем зависимости и создаем приложение:
FROM node:11 AS build
WORKDIR /usr/src/app
COPY . .
RUN yarn install \
yarn run lint \
yarn run build \
yarn run generate-docs
LABEL stage=build
Затем выполняем модульное тестирование:
FROM build AS unit-tests
RUN yarn run unit-tests
LABEL stage=unit-tests
Теперь переместите документацию в S3 с помощью метода push:
FROM containerlabs/aws-sdk AS push-docs
ARG push-docs=false
COPY --from=build docs docs
RUN [[ "$push-docs" == true ]] && aws s3 cp -r docs s3://my-docs-bucket/
Наконец переходим к последней стадии, которая будет отражена в результирующем образе. На этой стадии используется базовый образ и присутствуют лишь необходимые артефакты:
FROM node:11-slim
EXPOSE 8080
WORKDIR /usr/src/app
COPY --from=build /usr/src/app/node_modules node_modules
COPY --from=build /usr/src/app/dist dist
USER node
CMD ["node", "./dist/server/index.js"]
Jenkinsfile значительно упрощается (пример для Jenkins на Kubernetes):
#!/usr/bin/env groovy
podTemplate(label: "example", name: "example",
containers: [
containerTemplate(name: 'dind',
privileged: true, image: 'docker:18.06-dind', command: 'dockerd-entrypoint.sh'),
containerTemplate(name: 'docker', image: 'docker:18.06', command: 'cat', ttyEnabled: true)
],
envVars: [
envVar(key: 'DOCKER_HOST', value: 'tcp://localhost:2375')
]
){
node('example'){
container('docker'){
stage('checkout'){
checkout scm
}
stage('Docker build + push'){
sh """#!/bin/bash
docker build -t test --build-arg push-docs=true .
docker push test
"""
}
stage('deploy'){
.......
}
}
}
}
Этот способ можно использовать практически для каждого проекта!
Основные преимущества:
- Возможность повторного использования из одной системы CI в другую (например, переход с Jenkins на действия GitHub). Идеально подходит для open source проектов.
- Тестирование можно выполнить локально с помощью команды docker build.
- Все, что относится к созданию и тестированию исходного кода, находится в Dockerfile. Следовательно, сценарии CI хранятся отдельно от исходного кода.
- Меньше возможностей для совершения ошибок: каждый шаг осуществляется в непривилегированном контейнере docker. Избежать docker daemon можно с помощью таких инструментов, как Kaniko.
Таким образом, все детали проекта изолированы, а сценарии CI можно использовать повторно в разных хранилищах, что упрощает инфраструктуру и повышает поддерживаемость.
Примечания
Пропуск шагов
К примеру, я использовал стадию переноса сгенерированной документации в области памяти (bucket) на S3. Этот шаг необходим в том случае, если сборка выполняется в системе CI, в которой предоставлены учетные данные для записи в этой области памяти.
Для этого устанавливаем аргумент build с командой ARG. По умолчанию установлено значение false, однако на своем CI-сервере я запустил docker build --build-arg push-docs=true
, после которого выполняется команда aws s3 cp
.
Экспорт отчета о тестировании и других артефактов
Главное условие при работе в docker build заключается в том, что артефакты хранятся в промежуточных образах docker. Например, результаты тестирования удобно хранить в рабочем пространстве Jenkins для генерирования статистики.
Любой артефакт можно с легкостью извлечь из любой промежуточной стадии с помощью меток.
Я отметил вторую стадию с помощью stage=test
. Таким образом, после docker build можно запустить небольшой сценарий для получения файла test-results.xml
docker cp $(docker image ls --filter label=stage=test -q | head -1):/usr/src/app/tests-results.xml .
docker image ls
используется для получения ID образа этой стадии, а docker cp
для копирования файла.
Лучшее решение — это использование большого количества МЕТОК для фильтрации определенной сборки от других подобных сборок.
И последний совет: используйте BuildKit ?
Перевод статьи Ignacio Millán: Shift your CI scripts to docker build