Распространенный сценарий: команда разработчиков поддерживает множество 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

Предыдущая статьяРуководство по написанию чистого и читабельного кода для начинающих разработчиков. Часть 2
Следующая статьяОценка производительности нейронной сети Keras с помощью визуализаций Yellowbrick