Android GitLab CI + Docker 工程实践

3,401 阅读5分钟

关键词:Android 、CI、Runner、GitLab、Docker、macOS

最新版 GitLab 已经集成 GitLab CI,可以通过 GitLab Runner 执行相关任务并将执行结果返回给 GitLab。

GitLab CI 与 Runner 的关系?

GitLab 每一个项目都自带一个 GitLab CI 系统,而 GitLab Runner 就是配合 GItLab CI 执行任务存在的。例如一个项目默认分支 push 了 commit,GitLab CI 就会收到通知,然后根据配置下发任务给指定的 Runner,Runner 执行完毕再将结果反馈给 CI。一个项目对应一个 CI,一个 CI 可对应多个 Runner。

配置 GitLab Runner

GitLab Runner | GitLab

安装 GitLab Runner(macOS)

  1. 下载二进制文件
sudo curl --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64
  1. 赋予执行权限
sudo chmod +x /usr/local/bin/gitlab-runner

注册 GitLab Runner(macOS)

Registering Runners | GitLab

在注册一个 Runner 之前,先要获得 token。

两种类型 Runner 的区别?

GitLab Runner 有专用 Runner 和共享 Runner 两种类型。共享 Runner 是 GitLab 提供的,无需自行安装注册就能使用,但可能需要排队等待,而且有使用限制。专用 Runner 是由用户自行安装注册的,可以项目专用,也可以多项目共享使用,可以看作私有 Runner。

在对应 Project -> Settings -> CI / CD -> Runners -> Specific Runners 里可以找到

下面注册流程要用

  1. 执行一下命令
gitlab-runner register
  1. 输入 GitLab 实例 URL
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com )
https://gitlab.com
  1. 输入刚刚获取的 token
Please enter the gitlab-ci token for this runner
xxx
  1. 输入 Runner 的描述
Please enter the gitlab-ci description for this runner
[hostame] android-runner
  1. 输入 Runner tags

Tags 主要用于区别不同任务,不同环境等

Please enter the gitlab-ci tags for this runner (comma separated):
my-tag,another-tag
  1. 输入 Runner 执行方式
Please enter the executor: ssh, docker+machine, docker-ssh+machine, kubernetes, docker, parallels, virtualbox, docker-ssh, shell:
docker

具体怎么选择,可以看看官网文档 Executors | GitLab
选择 Docker 的话,注意先安装 Get Started with Docker | Docker 环境。

  1. 如果上面选的 Docker 作为执行方式,还需指定 Docker image
Please enter the Docker image (eg. ruby:2.1):
alpine:latest

这里可以先填个默认的,后面 GitLab CI 配置文件里还可以自定义。

安装并启动服务

cd ~
gitlab-runner install
gitlab-runner start

Runner 已经安装完成,在系统重启之后将会运行。

运行之后就能在刚刚获得 token 的页面下方看到 Runner 实例了。

制作 Android Build Docker Image

这里只需要编写一个 Dockerfile 就定制所需的 image。

# FROM 指令用于指定后续构建 image 所使用的基础 image
# 这里指定 openjdk 作为基础 image,便不再需要配置 JDK 环境了
FROM openjdk:8-jdk

# ARG 用于指定传递给构建运行时的变量
# sdk-tools 的版本,版本号在 https://developer.android.com/studio/index.html 页面查看
ARG SDK_TOOLS_VERSION=4333796
# build-tools 的版本
ARG BUILD_TOOLS_VERSION=27.0.3
# 指定 compileSdkVersion 的值
ARG COMPILE_SDK_VERSION=27

# WORDDIR 用于在容器内设置一个工作目录,之后的一些命令都会在这个目录下执行
WORKDIR /project

# ENV 用于设置一个环境变量,之后的命令可以使用
# 指定 Android SDK 的路径
ENV ANDROID_HOME /project/sdk

# RUN 用于在容器中执行命令
# 安装 Android SDK 和后续构建所需的工具
RUN mkdir sdk && \
    wget https://dl.google.com/android/repository/sdk-tools-linux-${SDK_TOOLS_VERSION}.zip && \
    unzip -qd sdk sdk-tools-linux-${SDK_TOOLS_VERSION}.zip && \
    rm -f sdk-tools-linux-${SDK_TOOLS_VERSION}.zip && \
    (yes | ./sdk/tools/bin/sdkmanager --no_https --update) && \
    (yes | ./sdk/tools/bin/sdkmanager --no_https "build-tools;${BUILD_TOOLS_VERSION}") && \
    (yes | ./sdk/tools/bin/sdkmanager --no_https "platform-tools") && \
    (yes | ./sdk/tools/bin/sdkmanager --no_https "platforms;android-${COMPILE_SDK_VERSION}") && \
    (yes | ./sdk/tools/bin/sdkmanager --no_https "extras;google;m2repository") && \
    (yes | ./sdk/tools/bin/sdkmanager --no_https "extras;android;m2repository") && \
    (yes | ./sdk/tools/bin/sdkmanager --no_https "extras;m2repository;com;android;support;constraint;constraint-layout-solver;1.0.2") && \
    (yes | ./sdk/tools/bin/sdkmanager --no_https "extras;m2repository;com;android;support;constraint;constraint-layout;1.0.2")
    apt-get update -y && apt-get install -y bash git openssh-client && \
    rm -rf /var/lib/apt/lists/* && \

最好新建一个空文件夹,将 Dockerfile 放到里面,然后进到该文件夹,执行 build。

docker build -t yourself.namespace/android-build:latest -f Dockerfile --build-arg http_proxy="http://docker.for.mac.localhost:port" --build-arg https_proxy="http://docker.for.mac.localhost:port" .

--build-arg 用来指定代理服务器,如果代理服务在 Docker 宿主机上,可以使用 docker.for.mac.localhost 从 Docker 环境访问宿主机。

由于没有将构建的 image push 到仓库,而后续使用到该 image 的时候,需要更新 Runner 的配置,否则会找不到 image。当然,也可以选择将 image push 到仓库。(The Docker executor | GitLab

GItLab Runner 支持 Docker Volume,这样可以把 Android 和 Gradle 的缓存目录作为 Volume,避免了每次运行都要重新下载。(Advanced configuration | GitLab

更新 Runner 配置,Runner 默认的配置文件路径是 ~/.gitlab-runner/config.toml

concurrent = 1
check_interval = 0

[[runners]]
  name = "android-runner"
  url = "https://gitlab.com/"
  token = "xxxxxxxxxxxxxxxxxxxxxxx"
  executor = "docker"
  [runners.docker]
    tls_verify = false
    image = "maven"
    privileged = false
    disable_cache = false
    volumes = ["/cache", "/root/.gradle", "/root/.android"]
    shm_size = 0
    pull_policy = "if-not-present"
  [runners.cache]

[runners.docker] 下新增 pull_policy = "if-not-present" ,这样会优先在本地查找 image,本地没有找到再尝试 pull。

[runners.docker]volumes 里新增 /root/.gradle/root/.android 即可缓存。

修改之后重启 GItLab Runner,即可生效

cd ~
gitlab-runner restart

配置 GitLab CI

Configuration of your jobs with .gitlab-ci.yml | GitLab

GItLab CI 的行为由配置文件 .gitlab-ci.yml 控制,该文件位于项目根目录下。

# 指定全局 job 使用的 Docker Image
image: yourself.namespace/android-build:latest

# 定义全局变量
# 指定网络代理
variables:
  HTTP_PROXY: "http://docker.for.mac.localhost:port"
  HTTPS_PROXY: "http://docker.for.mac.localhost:port"

# 预先执行脚本
# 写入 gradle 配置,具体配置根据项目配置来
before_script:
  - echo -e "systemProp.https.proxyHost=docker.for.mac.localhost\nsystemProp.https.proxyPort=port\nsystemProp.http.proxyHost=docker.for.mac.localhost\nsystemProp.http.proxyPort= port\nandroid.useDeprecatedNdk=true\norg.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8" > gradle.properties

# 定义任务执行阶段
# 在这里仅执行 build 任务
stages:
  - build

# build job
build:
  stage: build # 指定任务阶段
  tags: # 指定执行任务的 Runner
    - my-tag
  only: # 仅 git tag 更新时执行任务
    - tags
  script: # 任务执行脚本,执行构建脚本
    - ./gradlew --stacktrace assembleRelease
  artifacts: # 任务构建输出,指定需要保留的文件,之后可以在 GitLab Web 端下载
    paths:
      - app/build/outputs/

配置文件编写完后,推送到 GitLab,从 GitLab Web 端创建一个 tag,CI 就会自动运行了。

测试效果

  1. 新建一个 Tag

  2. CI 就会自动运行了

  3. 等待任务执行完毕,就可以下载到构建输出文件了