2017-07-15 15:30

(六)Docker 持续集成 - Jenkins

这一节主要介绍一款 CI 开源工具 Jenkins 在 Docker 中的应用部署。Jenkins 是一个可扩展的持续集成引擎,主要用于持续、自动构建/测试,以及监控一些定时任务等。这一节我们使用 Jenkins 工具构建一个测试流水线,使用 Docker-in-Docker 方式。先看我们的构建脚本:

$ cat Dockerfile 
FROM ubuntu:16.04
MAINTAINER leon@weippt.com
ENV REFRESHED_AT 2017-07-15

RUN apt-get -yqq update && apt-get install -yqq curl apt-transport-https
RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
RUN echo deb https://apt.dockerproject.org/repo ubuntu-xenial main > /etc/apt/sources.list.d/docker.list
RUN apt-get update -yqq && apt-get install -yqq iptables ca-certificates openjdk-8-jdk git-core docker-engine

ENV JENKINS_HOME /opt/jenkins/data
ENV JENKINS_MIRROR http://mirrors.jenkins-ci.org

RUN mkdir -p $JENKINS_HOME/plugins
RUN curl -sf -o /opt/jenkins/jenkins.war -L $JENKINS_MIRROR/war-stable/latest/jenkins.war

RUN for plugin in chucknorris greenballs scm-api git-client git wa-cleanp ; \
do curl -sf -o $JENKINS_HOME/plugins/${plugin}.hpi \
-L $JENKINS_MIRROR/plugins/${plugin}/latest/${plugin}.hpi; done

ADD ./dockerjenkins.sh /usr/local/bin/dockerjenkins.sh
RUN cmod +x /usr/local/bin/dockerjenkins.sh
VOLUME /var/lib/docker
EXPOSE 8080
ENTRYPOINT [ "/usr/local/bin/dockerjenkins.sh" ]

上面脚本用到的 dockerjenkins.sh 如下:

#!/bin/bash

# wget https://dockerbook.com/code/5/jenkins/dockerjenkins.sh && chmod 0755 dockerjenkins.sh 
# First, make sure that cgroups are mounted correctly.
CGROUP=/sys/fs/cgroup

[ -d $CGROUP ] ||
  mkdir $CGROUP

mountpoint -q $CGROUP ||
  mount -n -t tmpfs -o uid=0,gid=0,mode=0755 cgroup $CGROUP || {
    echo "Could not make a tmpfs mount. Did you use -privileged?"
    exit 1
  }

# Mount the cgroup hierarchies exactly as they are in the parent system.
for SUBSYS in $(cut -d: -f2 /proc/1/cgroup)
do
  [ -d $CGROUP/$SUBSYS ] || mkdir $CGROUP/$SUBSYS
  mountpoint -q $CGROUP/$SUBSYS ||
    mount -n -t cgroup -o $SUBSYS cgroup $CGROUP/$SUBSYS
done

# Now, close extraneous file descriptors.
pushd /proc/self/fd
for FD in *
do
  case "$FD" in
  # Keep stdin/stdout/stderr
  [012])
    ;;
  # Nuke everything else
  *)
    eval exec "$FD>&-"
    ;;
  esac
done
popd

docker daemon &
exec java -jar /opt/jenkins/jenkins.war

构建这个镜像/容器:

$sudo docker build -t leon/dockerjenkins .
$sudo docker run -p 8080:8080 --name jenkins --privileged  -d leon/dockerjenkins
$sudo docker logs -f jenkins #可以监控容器运行状态
# 注:--privileged 特权模式,可以以宿主(几乎)所有的能力运行容器。但同时对宿主也具有root权限风险!

现在已经可以在浏览器里访问Jenkins服务器了(Jenkins的默认语言同浏览器语言)。

创建Jenkins作业

点击页面左上角创建任务,我们为新创建的 Jenkins 作业命名为:Docker_test_job,并选择 Freestyle project;

接下来在高级选项里选择:使用自定义的工作空间,设置运行 Jenkins 的Directory为:/tmp/jenkins-buildenv/${JOB_NAME}/workspace;在源码管理里选 Git:https://github.com/oooline/docker-jenkins-sample.git  (包含一些基于Ruby的RSpec测试);在构建里点击:增加构建步骤,然后选择执行shell脚本 Execute shell ,脚本如下:

# 构建用于此作业的镜像
IMAGE=$( docker build . | tail -1 | awk '{ print $NF }')
# 构建挂载到 Docker 的目录
MNT="$WORKSPACE/.."
# 在 Docker 里执行编译测试
CONTAINER=$(docker run -d -v "$MNT:/opt/project" $IMAGE /bin/bash -c 'cd /opt/project/workspace && rake spec')
# 进入容器,这样可以看到输出的内容
docker attach $CONTAINER
# 等待程序退出,得到返回码
RC=$(docker wait $CONTAINER)
# 删除刚刚用到的容器
docker rm $CONTAINER
# 使用刚才的返回码退出整个脚本
exit $RC

这个脚本首先使用Git仓库中的Dockerfile创建一个新的Docker镜像,用于测试基于Ruby且使用RSpec测试框架的应用程序,其中 ci_reporter_rspec gem会把 RSpec 的输出转换为JUnit格式的XML输出,并交给Jenkins做解析;接下来创建一个包含Jenkins工作空间(签出Git仓库的地方)的目录,会把这个目录挂载到Docker容器,并在这个目录里执行测试。然后我们从这个镜像创建了容器,并且运行了测试。在容器里,把工作空间挂载到 /opt/project 目录,执行命令切换到这个目录,并执行 rake spec 来运行 RSpec 测试。现在容器启动了并获取到容器ID,现在使用 docker attach,docker wait 会一直阻塞,直到容器里的命令执行完才会返回容器退出时的返回码($RC )。最后清理环境,删除刚刚创建的容器,退出脚本。

接下来依次点击 Add post-build action(增加构建后动作) 、Publish JUint test result report(公布JUint测试结果报告),在Test report XMLs中指定:spec/reports/*.xml (这个目录是 ci_reporter gem的XML输出的位置);

最后点击保存!

运行 Jenkins 作业

点击作业主面板的 Build Now,将会看到有个作业出现在 Build History 里。通过Console Output控制台可以查看构建过程,构建顺利结束之后可以在 Test Result里查看构建结果。

自动构建

通过SCM轮询,它会在Git仓库有改动时,触发自动构建。也可以通过Git钩子,或GitHub钩子来实现。

多配置的Jenkins

现在我们创建一个新的项目:Docker_matrix_job,并选择:Muti-configuration project;

接下来,源码设置同样使用上一个案例中的 git 地址;然后点击 Add Axis(维度)并选择:User-defined- Axis(用户自定义维度),指定这个维度的名字为:OS,并设置3个值:centos debian ubuntu ( 当执行多配置作业时,Jenkins会查找这个维度并生成3个对应的作业 );接着在 Build Environment 中选择:Delete workspace before build starts(保证一些列作业初始化之前删除仓库清理构建环境);接着我们在增加构建步骤 Execute shell :

cd $OS && IMAGE=$( docker build . | tail -1 | awk '{ print $NF }')MNT="$WORKSPACE/.."
CONTAINER=$(docker run -d -v "$MNT:/opt/project" $IMAGE /bin/bash -c "cd /opt/project/$OS && rake spec")
docker attach $CONTAINER
RC=$(docker wait $CONTAINER)
docker rm $CONTAINER
exit $RC

最后,再加入一个构建后动作 Publish JUnit test result report 并指定XML输出位置为:spec/reports/*.xml;

现在我们已经可以开始测试这个多配置作业了:Build Now!

其他CI/CD

我们还有一些其他的持续集成和持续部署工具可以选择,比如Drone、Shippable等......