- PVSM.RU - https://www.pvsm.ru -

Jenkins для Android сборки, с помощью Docker

Всем привет!

Я работаю андроид разработчиком, и не так давно мы столкнулись с некоторыми рутинными задачами на своем проекте, которые хотелось бы автоматизировать. Например у нас 5 разных flavor, для каждого из которых требуется загружать свой билд на fabric, иногда для разных тасок по несколько раз в день. Да эту задачу можно сделать и с помощью gradle таски, но хотелось бы не запускать этот процесс на машине разработчика, а делать это как-то централизовано. Или например автоматически заливать билд в google play в бету. Ну и просто хотелось поковырять CI систему. Что из этого получилось, и как мы это настраивали, зачем там Docker, далее в статье.

Jenkins для Android сборки, с помощью Docker - 1

В моем понимании вся задача делилась примерно на два этапа:

  1. Установить и настроить сам Jenkins совместно с Android SDK
  2. Настроить задачи уже внутри Jenkins

В этой статье я хочу затронуть именно первый пункт, а если это все будет кому-то интересно, то в следующей статье опишу и процесс настройки задач по сборке в самом Jenkins.

Итак, пункт первый установка и настройка Jenkins системы

На Хабре уже есть замечательная статья [1] на эту тему, но ей уже пару лет, и некоторые вещи в ней слегка устарели (например sdkmanager), хотя она мне сильно помогла разобраться на начальных этапах что и как делать.

Если посмотреть официальную документацию [2] по установке Jenkins то увидим три разных способа как это сделать: запустить готовый docker образ, скачать и запустить war файле, а также по старинке просто установить jenkins в систему (например apt-get install jenkins на примере ubuntu). Первый вариант самый правильный, поскольку он не несет никаких лишних настроек и зависимостей в нашу хост систему, и в любой момент даже если что-то пойдет не так, легко и просто все удалить и начать заново. Но стандартный docker образ для jenkins содержит часть данных которые нам не нужны (например blueocean плагин ) и не содержат того что нам обязательно понадобиться (например android sdk). Было принято решение создать собственный docker образ который внутри себя будет качать и запускать war файл, качать и устанавливать android sdk, а также настраивать все остальные настройки которые нам будут нужны. Для того чтоб его потом запустить, нам потребуеться хостовая система с установленным docker. Я предлагаю тут не изобретать велосипед и воспользоваться DigitalOcean.

Создание и настройка виртуальной машины

Для начала если там еще кто не зарегистрирован то предлагаю зарегистрироваться [3](тут в момент написания статьи была реферальная ссылка, но почитав правила я выкинул ее). После регистрации можно на просторах инета погуглить тот или иной промокод, и получить примерно баксов 10 для старта.

После нам потребуется завести новый дроплет. Выберем пункт Droplets, и далее Create Droplet.

image

Хостовой системой оставим Ubuntu 18.04. Можно было бы выбрать образ с уже установленным и настроенным Docker, но мы все сделаем самостоятельно. Поскольку сборка андроид билдов дело все же ресурсоемкое, нам нужно выбрать конфигурацию как минимум за 20 баксов, чтоб билды собирались нормально и относительно быстро.

image

Выберем расположение к себе поближе (например в Германии). Дальше два варианта как мы будем подключаться к нашему виртуальному серверу. Мы можем добавить ssh ключ или обойтись без него. Если в этом месте мы не укажем какой ключ использовать, то нам на почту придет пароль для юзера root.

image

Тут можем изменить имя сервера, и завершаем создание нажатием кнопки Create.

image

Теперь заходим в созданный дроплет, и копируем себе ip адрес, для дальнейшего подключения и настройки.

image

Нам нужен ssh клиент. Если вы работает из под мака, то можно воспользоваться стандартным терминалом, если из под винды то мы можем взять для работты putty [4] или воспользоваться подсистемой Linux [5] (только для Windows 10). Я лично использую последний вариант, и он меня полностью устраивает.

Подключаемся к нашему серверу с помощью следующей команды

ssh root@YOUR_IP_ADDRESS

Консоль вам предложить сохранить ключ, соглашаемся с этим. После подключения создадим себе нового пользователя, добавим его в суперпользователи (и дадим ему возможность пользоваться sudo без пароля), копируем ему ключ для доступа по ssh и скажем что он владелец этих файлов (иначе не заработает). Имя username меняем на любое удобное для вас.


useradd -m -s /bin/bash username 
&& echo 'username ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers 
&& mkdir /home/username/.ssh 
&& cp /root/.ssh/authorized_keys /home/username/.ssh/authorized_keys 
&& chown username:username -R /home/username/.ssh

Отключаемся от пользователя root с помощью команды

exit

И подключимся заново уже с помощью нами созданного нового пользователя

ssh username@YOUR_IP_ADDRESS

После обновим систему, и перезагружаем наш сервер (если в процессе обновления система у вас будет что-то спрашивать, достаточно в этом случае всегда выбирать дефолтные значения).

sudo apt update && sudo apt full-upgrade -y && sudo apt autoremove -y && sudo reboot

Базовая настройка закончена. С точки зрения боевой системы она не очень секьюрна, но в рамках этой статью полностью подойдет.

Установка Docker.

Чтоб установить Docker в нашу систему воспользуемся официальной документацией [6]. Поскольку у нас заново установленная система, мы пропустим этот пункт, а если у вас система на которой уже давно что-то бежит, по рекомендации ребят из Docker удалите возможные старые версии

sudo apt-get remove docker docker-engine docker.io containerd runc

Не забудьте вначале обратно подключиться по ssh к нашему серверу. Сама установка Docker расписана очень детально в документации, я приведу общие команды, чтоб вам было проще. Что они делают можно почитать там. Сначала добавим репозиторий.


sudo apt update 
&& sudo apt install -y apt-transport-https ca-certificates 
curl gnupg-agent software-properties-common 
&& curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 
&& sudo apt-key fingerprint 0EBFCD88 
&& sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

И после установим сам Docker:

sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io

Чтоб в дальнейшем мы могли вызывать команды docker без приставки sudo выполним следующую команду (котрая тоже заботливо описана в инструкции [7]).

sudo usermod -aG docker username

После нужно перезайти (с помощью команды exit и повторного подключения к серверу) для того чтоб изменения закрепились.

Сам Docker установлен, что мы можем проверить командой

docker run hello-world

Она загружает тестовый образ, запускает его в контейнере. Контейнер после запуска печатает информационное сообщение и завершает работу.

Поздравляю, этап подготовки сервера к работе мы закончили!

Создание своего Docker образа

Создавать Docker образ будем с помощью написания собственного Dockerfile. Примеров как это сделать правильно в интернете вагон и маленькая тележка, я покажу свой [8] уже готовый вариант, и постараюсь его максимально прокомментировать. Существует также от самого docker статья инструкция [9] с примерами по правильному и каноническому написание dockerfile.

Создадим и откроем для редактирования свой Dockerfile

touch Dockerfile && nano Dockerfile

В него для примера, поместим содержимое моего Dockerfile

Мой Dockerfile целиком с комментариями


# базовая система для образа.
FROM ubuntu:18.04

# тут должно быть все понятно
LABEL author="osipovaleks"
LABEL maintainer="osipov.aleks.kr@gmail.com"
LABEL version="1.0"
LABEL description="Docker image for Jenkins with Android SDK"

# устанавливаем таймзону, чтоб Jenkins показывал локальное время
ENV TZ=Europe/Kiev
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

#добавляем i386 архитектуру для установки  ia32-libs
RUN dpkg --add-architecture i386

# обновляем пакеты и устанавливаем нужное
RUN apt-get update && apt-get install -y git 
 wget 
 unzip 
 sudo 
 tzdata 
 locales
 openjdk-8-jdk 
 libncurses5:i386 
 libstdc++6:i386 
 zlib1g:i386

#чистим после себя, чтоб размер образа был немного поменьше
RUN apt-get clean && rm -rf /var/lib/apt/lists /var/cache/apt

#устанавливаем локали
RUN locale-gen en_US.UTF-8  
ENV LANG en_US.UTF-8  
ENV LANGUAGE en_US:en  
ENV LC_ALL en_US.UTF-8

#качаем и распаковываем Android Sdk в заранее подготовленную папку
ARG android_home_dir=/var/lib/android-sdk/
ARG sdk_tools_zip_file=sdk-tools-linux-4333796.zip
RUN mkdir $android_home_dir
RUN wget https://dl.google.com/android/repository/$sdk_tools_zip_file -P $android_home_dir -nv
RUN unzip $android_home_dir$sdk_tools_zip_file -d $android_home_dir
RUN rm $android_home_dir$sdk_tools_zip_file && chmod 777 -R $android_home_dir

#устанавливаем environment в наш образ
ENV ANDROID_HOME=$android_home_dir
ENV PATH="${PATH}:$android_home_dir/tools/bin:$android_home_dir/platform-tools"
ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/

#соглашаемся с лицензиями Android SDK
RUN yes | sdkmanager --licenses

#создаем рабочую директорию для Jenkins
ENV JENKINS_HOME=/var/lib/jenkins
RUN mkdir $JENKINS_HOME && chmod 777 $JENKINS_HOME

#заводим нового юзера с именем jenkins, сделаем его суперпользователем, переключимся на него и перейдем в рабочую директорию
RUN useradd -m jenkins && echo 'jenkins ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
USER jenkins
WORKDIR /home/jenkins

#загрузим и запустим war файл с последней версией Jenkins
RUN wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war -nv
CMD java -jar jenkins.war

#сообщим какой порт нам требуется слушать
EXPOSE 8080/tcp

Несколько уточнений:

  • В началае было желание использовать более легковесный alpine вместо ubuntu, но него нету поддержки ia32-libs [10], что требуется для сборки проектов с помощью Android SDK.
  • Мы устанавливаем openjdk-8-jdk, а не более легковесную openjdk-8-jdk-headless по причине того, что некоторым функциям Jenkins нужна именно полная система (например отображение результатов unit тестов).
  • Установить локали нужно обязательно, по причине того, что на некоторых проектах, без них сборка gradle крашится без внятных ошибок и логов, и я потратил несколько дней чтоб докопаться до этой причины (на обычной ubuntu которая не в docker, все локали заполнены по умолчанию).
  • Нам нужно сразу принять все лицензии у Android SDK, для того чтоб в процессе сборки Jenkins мог самостоятельно установить нужные ему компоненты (например нужные ему SDK под разные версии api). Если потребуется, то в дальнейшем внутри docker контейнера можно будет управлять SDK с помощью sdkmanager, например sdkmanager --list позволяет просмотреть все доступные и все установленные компоненты, а sdkmanager --install "platforms;android-26" установит SDK для 26 версии api.
  • В целом можно было не заводить юзера jenkins, и остаться с юзером root, но это как-то не совсем правильно, так же можно было ему не давать права суперпользователя, но это сделано в плане удобства, если вдруг что-то вам нужно будет установить на этапе настройки и дебага.
  • Базовый размер образа получился немаленький (почти 800 мб), но в целом я пришел к выводу что для меня это не сильно критично, и мне легче его скачать в таком виде, чем тратить время на поиск и удаление пакетов которые мне не нужны.

После окончания написания Dockerfile нам нужно его превратить в готовый образ для Docker, на основании которого и будут создаваться контейнеры. Делается это просто командой

docker build -t jenkins-image

где параметр -t jenkins-image отвечает за имя вашего образа, а точка в конце команды, говорит о том что Dockerfile для сборки нужно искать внутри данного каталога. Сам процесс сборки занимает какое-то время, и после сборки в консоли должно быть примерно подобное сообщение.

Successfully built 9fd8f5545c27
Successfully tagged jenkins-image:latest

Которое нам говорит о том что наш образ успешно собран, и мы можем приступать к следующему шагу, а именно запуску нашего контейнера

Docker Hub и готовые образы

Да конечно, мы можем использовать наш готовый образ для запуска контейнера, но если нам потребуется это сделать больше чем на нескольких устройствах, каждый раз создавать Dockerfile и собирать из него готовый образ будет не совсем удобно. А если мы еще и обновим содержимое нашего Dockerfile, то раскатывать изменения по всем нодам будет вообще не удобно. Для этих целей и существует публичный репозиторий образов Docker Hub [11]. Он позволяет не собирать каждый раз образ, на каждой ноде, а просто скачать его себе с публичного хранилища, и использовать одинаково на всех машинах. Например образ который послужил примером для этой статьи, доступен в репозитории по имени osipovaleks/docker-jenkins-android [12], и дальше в статье мы будем работать именно с ним.

Данная статья не подразумевает детального изучения Docker Hub, мы не будем разбираться как залить туда свои образы (хотя это очень не сложно) и что с ними можно делать дальше там, мы не будем разбираться что существуют еще могут быть свои личные публичные или приватные репозитории, в этом всем можно будет разобраться самостоятельно если понадобиться.

Запуск контейнера

Запустить контейнер можно двумя способами.

  1. Первый способ просто с помощью команды docker run, позволяет это сделать легко и быстро следующим способом
    docker run --name jenkins -d -it -v jenkins-data:/var/lib/jenkins -v jenkins-home:/home/jenkins -p 8080:8080 --restart unless-stopped osipovaleks/docker-jenkins-android

    где команда run имеет следующие параметры

    • --name jenkins — имя будущего контейнера
    • -d — запуск контейнера в фоне
    • -it — флаги для работы с STDIN и tty
    • -v jenkins-data:/var/lib/jenkins и -v jenkins-home:/home/jenkins — создаем (если не созданы) и замапим на внутренние разделы контейнера специальный файлы тома, которые позволят нам сохранить наш настроенный Jenkins даже после пересоздания контейнера
    • -p 8080:8080 — замапим порт хоста на порт контейнера, для того чтоб у нас был доступ к веб интерфейсу (да это именно тот порт который мы указывали в Dockerfile)
    • --restart unless-stopped — опция определяет политику автозапуска контейнера после перезагрузки хоста (в данном случае, автостарт если контейнер не был выключен вручную)
    • osipovaleks/docker-jenkins-android — образ для развертывания.

    На выходе в консоль Docker нам должен вывести id созданного контейнера, а также показать информацию о том как образ загружается в систему (конечно если он еще не загружен), примерно так

    Unable to find image 'osipovaleks/docker-jenkins-android:latest' locally
    latest: Pulling from osipovaleks/docker-jenkins-android
    6cf436f81810: Pull complete
    987088a85b96: Pull complete
    b4624b3efe06: Pull complete
    d42beb8ded59: Pull complete
    b3896048bb8c: Pull complete
    8eeace4c3d64: Pull complete
    d9b74624442c: Pull complete
    36bb3b7da419: Pull complete
    31361bd508cb: Pull complete
    cee49ae4c825: Pull complete
    868ddf54d4c1: Pull complete
    361bd7573dd0: Pull complete
    bb7b15e36ae8: Pull complete
    97f19daace79: Pull complete
    1f5eb3850f3e: Pull complete
    651e7bbedad2: Pull complete
    a52705a2ded7: Pull complete
    Digest: sha256:321453e2f2142e433817cc9559443387e9f680bb091d6369bbcbc1e0201be1c5
    Status: Downloaded newer image for osipovaleks/docker-jenkins-android:latest
    ef9e5512581da66d66103d9f6ea6ccd74e5bdb3776747441ce6a88a98a12b5a4

  2. Второй способ запуска подразумевает написание специального compose файла, где команда run просто описана с помощью языка YAML [13], и запускается с помощью Docker Compose.

    Для этого нам потребуется установить его:

    sudo apt update && sudo apt install -y docker-compose

    Далее создаем директорию для проекта (это важно в том случае, если вам не все равно, как будут называться автоматически созданные volumes для контейнера) и перейдем в нее

    mkdir jenkinsProject && cd jenkinsProject

    а внутри создаем сам compose файл и заходим в режим редактирования

    touch docker-compose.yml && nano docker-compose.yml

    и поместим в него следующее содержимое

    version: '3'
    services:
      jenkins:
        container_name: jenkins
        image: osipovaleks/docker-jenkins-android
        ports:
          - "8080:8080"
        restart: unless-stopped
        volumes:
          - "jenkins-data:/var/lib/jenkins"
          - "jenkins-home:/home/jenkins"
    volumes:
      jenkins-data:
      jenkins-home:

    В нем, пожалуй, только первая строчка вызывает вопросы (version: '3') которая указывает на версию [14] возможностей compose файла, а также раздел с блоком volumes в котором перечислены те, которые используються в данном контейнере

    Запустим свой контейнер командой:

    docker-compose up -d

    где флаг -d также указывает на то что создание и запуск контейнера будет производиться в фоне. В итоге Docker должен показать примерно следующее:

    Creating volume «jenkinsproject_jenkins-data» with default driver
    Creating volume «jenkinsproject_jenkins-home» with default driver
    Pulling jenkins (osipovaleks/docker-jenkins-android:latest)…
    latest: Pulling from osipovaleks/docker-jenkins-android
    6cf436f81810: Pull complete
    987088a85b96: Pull complete
    b4624b3efe06: Pull complete
    d42beb8ded59: Pull complete
    b3896048bb8c: Pull complete
    8eeace4c3d64: Pull complete
    d9b74624442c: Pull complete
    36bb3b7da419: Pull complete
    31361bd508cb: Pull complete
    cee49ae4c825: Pull complete
    868ddf54d4c1: Pull complete
    361bd7573dd0: Pull complete
    bb7b15e36ae8: Pull complete
    97f19daace79: Pull complete
    1f5eb3850f3e: Pull complete
    651e7bbedad2: Pull complete
    a52705a2ded7: Pull complete
    Digest: sha256:321453e2f2142e433817cc9559443387e9f680bb091d6369bbcbc1e0201be1c5
    Status: Downloaded newer image for osipovaleks/docker-jenkins-android:latest
    Creating jenkins…
    Creating jenkins… done

    Помните я говорил что от имени проекта будет зависеть имя созданных volumes? Выполним команду:

    docker volume ls

    и получим на выходе такое

    DRIVER VOLUME NAME
    local jenkinsproject_jenkins-data
    local jenkinsproject_jenkins-home

    где и увидим, что несмотря на то что имя для volume было выбрано jenkins-home, в реальности к нему прилепился префикс из имени проекта и имя volume получилось jenkinsproject_jenkins-home

Какой из вариантов запуска использовать? Тут вы можете выбрать самостоятельно, считается что Docker Compose это скорее инструмент, для запуска нескольких контейнеров сразу, которые завязаны друг на друга, и если вам нужно запустить всего один контейнер, то можно воспользоваться просто командой docker run.

Теперь после этих подэтапов по запуску и настройке сервера, а так же запуске контейнера с Jenkins мы можем перейти к его первоначальной настройке

Первоначальная настройка Jenkins

Возьмем ip адрес нашего сервера, добавим к нему указанный нами порт 8080 и перейдем по этой ссылке в браузере.

http://YOUR_IP_ADDRESS:8080/

Если до этого все было настроено и запущено правильно, то тут увидим следующую картинку

Jenkins для Android сборки, с помощью Docker - 7

Для первой настройки нам нужно ввести пароль который сгенерировала система при установке. Для этого нам всего лишь нужно посмотреть содержимое файла /var/lib/jenkins/secrets/initialAdminPassword. Но этот файл находиться внутри нашего запущенного контейнера, и для того чтоб его прочесть, нам понадобиться приконектиться к контейнеру, с помощью следующей команды:

docker exec -it jenkins /bin/bash

где параметр -it аналогичен как и при запуске docker run, jenkins это имя нашего контейнера, а /bin/bash запустит для нас bash в контейнере и даст к нему доступ. После этого мы можем посмотреть начальный пароль для Jenkins:

cat /var/lib/jenkins/secrets/initialAdminPassword

в консоли покажеться примерно следующее

91092b18d6ca4492a2759b1903241d2a

Это и есть пароль. Копируем его, вставляем в поле Administrator password в веб интерфейсе и нажимаем Continue. На следующем экране выбираем пункт Install suggested plugins и устанавливает набор дефолтных плагинов.

Jenkins для Android сборки, с помощью Docker - 8

Jenkins для Android сборки, с помощью Docker - 9

После установки плагинов, создаем себе пользователя и нажимаем на Save and Finish

Jenkins для Android сборки, с помощью Docker - 10

Соглашаемся с разделом Instance Configuration, где нам предлагают заполнить URL на котором будет работать Jenkins (в нашем случае оставляем все как есть)

Jenkins для Android сборки, с помощью Docker - 11

И на следующем экране нажимаем заветное Start using Jenkins

Jenkins для Android сборки, с помощью Docker - 12

Итак, мы установили и запустили Jenkins!

Jenkins для Android сборки, с помощью Docker - 13

С ним уже вполне можно работать, но для того чтоб собирать наши Android билды нужно будет настроить еще несколько пунктов. Локализация Jenkins связана с выбранным языком вашего браузера, и естественно перевод на русский язык закончен не до конца, и мы получаем адовую смесь из русского и английского языков. Если у вас получилось точно также, и вас это бесит, то можно воспользоваться специальным плагином [15] и установить дефолтный язык интерфейса. Ну или переключить свой браузер на англоязычный интерфейс.

Перейдем в настройки Jenkins и выберем пункт Конфигурация системы

Jenkins для Android сборки, с помощью Docker - 14

Отметим галочкой пункт Environment variables, и впишем в поле имя ANDROID_HOME, а в поле значение укажем /var/lib/android-sdk/ (именно эти данные мы указывали еще в Dockerfile как домашнюю директорию для хранения Android SDK).

Jenkins для Android сборки, с помощью Docker - 15

Нажмем на кнопку Сохранить, выйдем из данного раздела настроек и зайдем в раздел под названием Конфигурация глобальных инструментов.

Jenkins для Android сборки, с помощью Docker - 16

Настроем раздел с JDK (где переменная JAVA_HOME так же заполнялась нами в Dockerfile, и мы можем тут использовать ее значение /usr/lib/jvm/java-8-openjdk-amd64/).

Jenkins для Android сборки, с помощью Docker - 17

Так же тут нам еще нужно заполнить раздел с Gradle. Версию Gradle мы выбираем и устанавливаем ту, которая используется в тех проектах, которые вы будете собирать с помощью этой CI системы. Можно завести несколько версий. Можно так же вообще не заводить Gradle переменную, если у вас в репозитории например есть gradlew, и собирать можно с его помощью.

Jenkins для Android сборки, с помощью Docker - 18

На этом мы можем заканчивать наш первый этап. Система Jenkins полностью готова к работе и мы можем переходить к настройке самих задач по сборке. Обратите внимание, система настраивалась под наши нужды и тут может не оказать того что нужно именно вам — например тут нету андроид эмуляторов для тестирования и NDK.

Если эта статья кого-то заинтересует, то я продолжу и во второй части на примере одной или двух тасок, опишу интеграцию Jenkins и Bitbucket (именно он, а не Github, потому что там и с бесплатными приватными репозиториями попроще, и статей в интернете про него поменьше, а приколов пожалуй побольше), расскажу как ssh ключ нашего контейнера подружить с репозиторием, про email уведомления, а также несколько других фишек. В общем примерно про все, что у нас и настроено.

Прошу сильно не пинать, это моя первая статья на Хабр. Всем добра!

Автор: osipovaleks

Источник [16]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/android/311512

Ссылки в тексте:

[1] статья: https://habr.com/ru/post/315804/

[2] документацию: https://jenkins.io/doc/book/installing/

[3] зарегистрироваться : https://cloud.digitalocean.com/registrations/new

[4] putty: https://www.putty.org/

[5] подсистемой Linux: https://ru.wikipedia.org/wiki/Windows_Subsystem_for_Linux

[6] официальной документацией: https://docs.docker.com/install/linux/docker-ce/ubuntu/

[7] инструкции: https://docs.docker.com/install/linux/linux-postinstall/

[8] свой: https://github.com/osipovaleks/docker-jenkins-android/blob/master/Dockerfile

[9] инструкция: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

[10] ia32-libs: https://github.com/gliderlabs/docker-alpine/issues/169

[11] Docker Hub: https://hub.docker.com

[12] osipovaleks/docker-jenkins-android: https://cloud.docker.com/u/osipovaleks/repository/docker/osipovaleks/docker-jenkins-android

[13] YAML: https://ru.wikipedia.org/wiki/YAML

[14] версию: https://docs.docker.com/compose/compose-file/compose-versioning/

[15] плагином: https://wiki.jenkins.io/display/JENKINS/Locale+Plugin

[16] Источник: https://habr.com/ru/post/443606/?utm_campaign=443606