Jenkins for Android build using docker

Hello!

I work as an android developer, and not so long ago we ran into some routine tasks on our project that we would like to automate. For example, we have 5 different flavors, for each of which we need to upload our build to fabric, sometimes for different carts several times a day. Yes, this task can also be done using gradle task, but I would like not to start this process on the developer's machine, but to do it somehow centrally. Or for example, automatically upload the build in google play to beta. Well, I just wanted to pick the CI system. What came of this, and how we set it up, why is there Docker, later in the article.



In my understanding, the whole task was divided into approximately two stages:

  1. Install and configure Jenkins itself with the Android SDK
  2. Set up tasks already inside Jenkins

In this article, I want to touch on the first point, and if it will be interesting to anyone, then in the next article I will describe the process of setting up assembly tasks in Jenkins itself.

So, the first point is the installation and configuration of the Jenkins system


Habré already has a wonderful article on this topic, but she’s already a couple of years, and some things in it are slightly outdated (for example sdkmanager), although she helped me a lot to figure out what and how to do at the initial stages.

If you look at the official documentation for installing Jenkins, we will see three different ways to do this: launch a ready-made docker image, download and run a war file, and also just install jenkins in the system in the old fashion (for exampleapt-get install jenkinsubuntu as an example). The first option is the most correct, because it does not carry any unnecessary settings and dependencies to our host system, and at any time even if something goes wrong, it is easy and simple to delete everything and start again. But the standard docker image for jenkins contains some of the data that we do not need (for example, the blueocean plugin) and do not contain what we will definitely need (for example, android sdk). It was decided to create our own docker image that inside it will download and run the war file, download and install android sdk, as well as configure all other settings that we need. In order to start it later, we need a host system with docker installed. I suggest here not to reinvent the wheel and use DigitalOcean.

Create and configure a virtual machine


To begin with, if someone else is not registered there, I suggest registering (here at the time of writing the article there was a referral link, but after reading the rules I threw it out). After registration, you can google one or another promotional code on the Internet, and get about 10 bucks to start.

After we need to get a new droplet. Select the item Droplets, and then Create Droplet .

image

The host system is Ubuntu 18.04. You could choose an image with Docker already installed and configured, but we will do everything on our own. Since the assembly of android builds is still resource-intensive, we need to choose a configuration for at least 20 bucks so that the builds are collected normally and relatively quickly.

image

We’ll choose a closer location (for example, in Germany). Then there are two options for how we will connect to our virtual server. We can add an ssh key or do without it. If in this place we do not indicate which key to use, then the password for the root user will be sent to our mail.

image

Here we can change the server name, and complete the creation by clicking the Create button .

image

Now we go into the created droplet, and copy the ip address for ourselves, for further connection and configuration.

image

We need an ssh client. If you work from under a poppy, then you can use the standard terminal, if from under Windows we can use putty to work or use the Linux subsystem(Windows 10 only). I personally use the latter option, and it completely suits me.

Connect to our server using the following command

ssh root@YOUR_IP_ADDRESS

The console will offer you to save the key, we agree with this. After connecting, we will create a new user for ourselves, add it to superusers (and give him the opportunity to use sudo without a password), copy him the key for access via ssh and say that he is the owner of these files (otherwise it will not work). The username is changed to any convenient for you.


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

Disconnect from root with the command

exit

And we will reconnect already with the help of the created new user

ssh username@YOUR_IP_ADDRESS

After we update the system, and reboot our server (if during the update process the system will ask you something, it is enough to always choose the default values ​​in this case).

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

Basic setup is complete. From the point of view of the combat system, it is not very secure, but within the framework of this article it is completely suitable.

Install Docker.


To install Docker in our system, we will use the official documentation . Since we have a newly installed system, we will skip this point, and if you have a system on which something has been running for a long time, on the recommendation of the guys from Docker, delete possible old versions

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

Do not forget to first connect back via ssh to our server. The installation of Docker itself is described in great detail in the documentation, I will give general commands to make it easier for you. What they do can be read there. First add the repository.


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"

And then install Docker itself:

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

So that in the future we could call docker commands without the sudo prefix, execute the following command (which is also carefully described in the instructions ).

sudo usermod -aG docker username

After that, you need to re-login (using the exit command and reconnecting to the server) in order for this to work.

Docker itself is installed, which we can check with the command

docker run hello-world

She downloads the test image, runs it in the container. The container, after starting, prints an informational message and exits.

Congratulations, we’ve finished the stage of preparing the server for work!

Creating Your Docker Image


We will create the Docker image by writing our own Dockerfile. Examples of how to do this correctly on the Internet a wagon and a small cart, I will show my ready-made version, and I will try to comment on it as much as possible. There is also an instruction manual from docker itself with examples on the correct and canonical spelling of dockerfile.

Create and open your Dockerfile for editing

touch Dockerfile && nano Dockerfile

In it, for example, we’ll put the contents of my Dockerfile

My entire dockerfile with comments

# базовая система для образа.
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


A few clarifications:

  • In the beginning, there was a desire to use a lighter alpine instead of ubuntu, but it does not have ia32-libs support , which is required for building projects using the Android SDK.
  • We install openjdk-8-jdk, not the more lightweight openjdk-8-jdk-headless, because some Jenkins functions need a complete system (for example, displaying unit test results).
  • It is necessary to install locales, due to the fact that on some projects, without them, the gradle assembly crashes without clear errors and logs, and I spent several days to get to the bottom of this reason (on regular ubuntu which is not in docker, all locales are filled by default) .
  • We need to immediately accept all the licenses for the Android SDK, so that during the build process Jenkins can independently install the components it needs (for example, the SDKs it needs for different versions of api). If necessary, in the future inside the docker container it will be possible to manage the SDK using sdkmanager, for example, sdkmanager --listit allows you to view all available and all installed components, and sdkmanager --install "platforms;android-26"install the SDK for version 26 of the api.
  • In general, it was possible not to start the user jenkins, and stay with the root user, but somehow it’s not quite right, you could also not give him superuser rights, but this was done in terms of convenience, if something needs to be installed at the setup and debug stage.
  • The basic size of the image turned out to be rather big (almost 800 mb), but overall I came to the conclusion that for me this is not very critical, and it’s easier for me to download it in this form than to spend time searching and removing packages that I don’t need.

After writing Dockerfile, we need to turn it into a ready-made image for Docker, on the basis of which containers will be created. This is done simply by a team

docker build -t jenkins-image

where the parameter -t jenkins-imageis responsible for the name of your image, and the dot at the end of the command indicates that the Dockerfile for assembly needs to be searched inside this directory. The assembly process itself takes some time, and after the assembly there should be something like this in the console.
Successfully built 9fd8f5545c27
Successfully tagged jenkins-image: latest
Which tells us that our image has been successfully assembled, and we can proceed to the next step, namely the launch of our container

Docker Hub and ready-made images


Yes, of course, we can use our ready-made image to launch the container, but if we need to do this on more than several devices, creating a Dockerfile each time and building a finished image from it will not be very convenient. And if we also update the contents of our Dockerfile, then rolling out the changes across all the nodes will not be convenient at all. For these purposes, there is a public repository of Docker Hub images . It allows you to not collect an image every time, on each node, but simply download it to yourself from the public repository, and use it equally on all machines. For example, the image that served as an example for this article is available in the repository named osipovaleks / docker-jenkins-android , and later in the article we will work with it.

This article does not imply a detailed study of the Docker Hub, we will not understand how to upload our images there (although this is not very difficult) and what can be done with them there, we will not understand that there may still be your own personal public or private repositories, In this all can be sorted out independently if necessary.

Container launch


There are two ways to start a container.

  1. The first way, just with a command docker run, allows you to do this quickly and easily in the following

    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

    where the command runhas the following parameters

    • --name jenkins - name of the future container
    • -d - launch of the container in the background
    • -it - flags for working with STDIN and tty
    • -v jenkins-data:/var/lib/jenkinsand -v jenkins-home:/home/jenkins- create (if not created) and map to the internal sections of the container special volume files that will allow us to save our customized Jenkins even after reconstituting the container
    • -p 8080:8080 - map the host port to the container port, so that we have access to the web interface (yes this is the port that we specified in the Dockerfile)
    • --restart unless-stopped - the option determines the autorun policy of the container after rebooting the host (in this case, autostart if the container was not manually turned off)
    • osipovaleks/docker-jenkins-android - image for deployment.

    At the exit to the Docker console, we should get the id of the created container, and also show information about how the image is loaded into the system (of course, if it is not already loaded), something like
    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
    ef9e5512581da66d6610bbdbe4b4e4f4b6ebdb6bbb7bf4e4db6b6bfb7be4fb4dbfd
  2. The second way to start involves writing a special compose file, where the run command is simply described using the YAML language , and is launched using the Docker Compose.

    To do this, we need to install it:

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

    Next, create a directory for the project (this is important if you care what the automatically created volumes for the container will be called) and go to it

    mkdir jenkinsProject && cd jenkinsProject

    and inside we create the compose file itself and go into edit mode

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

    and put the following contents in it

    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:

    In it, perhaps, only the first line raises questions ( version: '3') which indicates the version of the compose file 's capabilities, as well as a section with a block volumesthat lists those that are used in this container.

    Run your container with the command:

    docker-compose up -d

    where the flag -dalso indicates that the creation and launch of the container will be done in the background. As a result, Docker should show something like the following:
    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
    9870888888888888 : Pull the complete
    b4624b3efe06: Pull the complete
    d42beb8ded59: Pull the complete
    b3896048bb8c: Pull the complete
    8eeace4c3d64: Pull the complete
    d9b74624442c: Pull the complete
    36bb3b7da419: Pull the complete
    31361bd508cb: Pull the complete
    cee49ae4c825: Pull the complete
    868ddf54d4c1: Pull the complete
    361bd7573dd0: Pull the complete
    bb7b15e36ae8: Pull the 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


Which startup option to use? Here you can choose for yourself, it is believed that Docker Compose is rather a tool for launching several containers at once, which are tied to each other, and if you need to run only one container, then you can use just a command docker run.

Now after these sub-steps for starting and configuring the server, as well as starting the container with Jenkins, we can proceed to its initial configuration

Initial Jenkins setup


Take the ip address of our server, add the port 8080 indicated by us to it and follow this link in the browser.

http://YOUR_IP_ADDRESS:8080/

If before that everything was configured and started correctly, then here we will see the following picture.



For the first setup, we need to enter the password that the system generated during installation. To do this, we just need to look at the contents of the file /var/lib/jenkins/secrets/initialAdminPassword. But this file is inside our running container, and in order to read it, we need to connect to the container using the following command:

docker exec -it jenkins /bin/bash

where the parameter is the -itsame as at startup docker run, jenkinsthis is the name of our container, and it /bin/bashwill run bash for us in the container and give it access. After that, we can see the initial password for Jenkins:

cat /var/lib/jenkins/secrets/initialAdminPassword

the following appears in the console
91092b18d6ca4492a2759b1903241d2a
This is the password.

User ALexhha suggested a simpler option to read this password, without connecting to the container itself. The fact is that at the time of launching Jenkins itself, this password is shown in the logs. It turns out that all we need is to read the container logs. In our case, this is done with the following command:

docker logs jenkins


where is the jenkinsname of our container, and in the logs you can see the following:

*************************************************************
*************************************************************
*************************************************************

Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:

91092b18d6ca4492a2759b1903241d2a

This may also be found at: /var/lib/jenkins/secrets/initialAdminPassword

*************************************************************
*************************************************************

***************************************************** ***********

This option is a bit simpler and faster.

Copy it, paste it into the Administrator password field in the web interface and click Continue . On the next screen, select Install suggested plugins and install a set of default plugins.





After installing the plugins, create a user for ourselves and click on Save and Finish We



agree with the Instance Configuration section, where we are prompted to fill in the URL on which Jenkins will work (in our case, leave everything as it is)



And on the next screen click on the cherished Start using Jenkins



So, we installed and launched Jenkins!



It is already quite possible to work with it, but in order to collect our Android builds, you will need to configure a few more points. The localization of Jenkins is related to the selected language of your browser, and of course the translation into Russian is not completely finished, and we get a hellish mixture of Russian and English. If you succeeded in exactly the same way, and it infuriates you, then you can use the special plug-in and set the default interface language. Well, or switch your browser to the English interface.

Let's go to the Jenkins settings and select the item System Configuration.



Check the box for Environment variables and enter the name ANDROID_HOME in the field and specify / var / lib / android-sdk / in the field .(this is exactly the data we indicated in the Dockerfile as the home directory for storing the Android SDK).



Click on the Save button , exit this settings section and go to the section called Global Tools Configuration .



Set up the JDK partition (where the JAVA_HOME variable was also populated by us in the Dockerfile, and we can use its value / usr / lib / jvm / java-8-openjdk-amd64 / here ).



Also here we still need to fill out the Gradle section. We select and install the version of Gradle that is used in those projects that you will build using this CI system. You can have several versions. You can also not start the Gradle variable at all if you have gradlew in the repository, for example, and you can build it using it.



With this we can end our first stage. The Jenkins system is fully operational and we can move on to customizing the build tasks themselves. Please note that the system was customized to our needs and may not provide what you need - for example, there are no android emulators for testing and NDK.

If this article interests anyone, then I will continue in the second part with an example of one or two hassles, I will describe the integration of Jenkins and Bitbucket (it is he, not Github, because it’s easier with free private repositories and articles on the Internet about it’s smaller, but perhaps more fun), I’ll tell you how to make friends with the ssh key of our container with the repository, about email notifications, as well as several other chips. In general, about everything that we have configured.

I ask you not to kick much, this is my first article on Habr. Good to all!

Also popular now: