在本小节中,我们将讨论持续集成。
持续集成指的是: 频繁地(一天多次)将代码集成到主干。这里的集成不止是代码合并,还要保证可以通过编译、单元测试、集成测试。
持续集成的主要优点是:
针对我们当前的架构,持续集成分为如下几个步骤:
在持续构建的过程中,需要迁出代码、编译、打Docker镜像等步骤,如果都放在Jenkins的容器内执行,存在一些缺点:
因此,通常都会新建一个独立于Jenkins的打包机,里面集成编译打包工具。Jenkins将打包机作为Slave添加到系统中,在打包时将调用Slave机器执行打包任务。
Slave打包机可以采用物理机、虚拟机或者容器,这里我们选择容器的方式,主要优点有:
我们首先来构建一个打包机的镜像,Dockerfile如下:
FROM ubuntu:18.04
# apt-add-repostory zip unzip git
RUN apt-get update
RUN apt-get install -y apt-utils software-properties-common zip unzip git
# SSH
RUN apt-get install -y openssh-server \
&& mkdir /var/run/sshd \
&& sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config
# Java
ENV JAVA_HOME /usr/lib/jvm/java-8-oracle
RUN \
echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | debconf-set-selections && \
add-apt-repository -y ppa:webupd8team/java && \
apt-get update && \
apt-get install -y oracle-java8-installer
# Gradle
ENV GRADLE_HOME /opt/gradle
ENV GRADLE_VERSION 4.10
ARG GRADLE_DOWNLOAD_SHA256=248cfd92104ce12c5431ddb8309cf713fe58de8e330c63176543320022f59f18
RUN set -o errexit -o nounset \
&& echo "Downloading Gradle" \
&& wget --no-verbose --output-document=gradle.zip "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" \
\
&& echo "Checking download hash" \
&& echo "${GRADLE_DOWNLOAD_SHA256} *gradle.zip" | sha256sum --check - \
\
&& echo "Installing Gradle" \
&& unzip gradle.zip \
&& rm gradle.zip \
&& mv "gradle-${GRADLE_VERSION}" "${GRADLE_HOME}/" \
&& ln --symbolic "${GRADLE_HOME}/bin/gradle" /usr/bin/gradle
# Create User
RUN useradd -m build
RUN echo "build:build123" | chpasswd
# Docker ce
RUN apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
software-properties-common
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
RUN add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
RUN apt-get install -y docker-ce
RUN usermod -aG docker build
# Clean
RUN apt-get remove -y apt-utils software-properties-common && \
apt-get autoremove -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /var/cache/oracle-jdk8-installer
EXPOSE 22
# SSH Daemon
CMD ["../../../usr/sbin/sshd", "-D"]
如上所示,上述镜像主要完成了以下功能:
有了上述镜像后,我们启动这个slave容器:
#!/bin/bash
# BUILD
docker build -t slave .
NAME="slave"
# submit to local docker node
docker ps -q -a --filter "name=$NAME" | xargs -I {} docker rm -f {}
docker run \
--name $NAME \
-v /var/run/docker.sock:/var/run/docker.sock \
-p 22 \
--detach \
--restart always \
-d slave:latest
接下来,我们在Jenkins中添加这台slave机器。
使用管理员帐号登录,"Manage Jenkins" -> "Manage Nodes" -> "New Node",然后如下图所示配置:
主要的配置为:
配置成功默认是离线的,稍等一会,会提示"slave已经上线"。
本节开篇已经提到,持续集成的第一步即从代码仓库中迁出代码,我们来完成这项工作。
首先在gerrit上准备一个项目,假设为lmsia-xyz,这是一个最简单的Spring Boot项目。
为了能够提交、迁出代码,需要将公钥配置到gerrit上,点击右上角的名字 -> Setting -> SSH Public Keys,填入即可完成。
准备好项目后,我们在Jenkins上新建一个"Freestyle"项目,命名为lmsia-xyz-build。
首先配置代码仓库,如下图所示:
此外,还要限制只能在slave上执行: Restrict where this project can be run中设置"slave"。
完成后点击底部的Save。
配置好后,我们执行第一次Build,在项目左侧菜单选择"Build Now",可以在Log中查看输出如下:
Building remotely on slave in workspace /home/build/workspace/lmsia-xyz-build
Cloning the remote Git repository
Cloning repository ssh://lihy@10.1.64.72:29418/lmsia-xyz
> git init /home/build/workspace/lmsia-xyz-build # timeout=10
Fetching upstream changes from ssh://lihy@10.1.64.72:29418/lmsia-xyz
> git --version # timeout=10
using GIT_SSH to set credentials
> git fetch --tags --progress ssh://lihy@10.1.64.72:29418/lmsia-xyz +refs/heads/*:refs/remotes/origin/*
> git config remote.origin.url ssh://lihy@10.1.64.72:29418/lmsia-xyz # timeout=10
> git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
> git config remote.origin.url ssh://lihy@10.1.64.72:29418/lmsia-xyz # timeout=10
Fetching upstream changes from ssh://lihy@10.1.64.72:29418/lmsia-xyz
using GIT_SSH to set credentials
> git fetch --tags --progress ssh://lihy@10.1.64.72:29418/lmsia-xyz +refs/heads/*:refs/remotes/origin/*
> git rev-parse refs/remotes/origin/master^{commit} # timeout=10
> git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10
Checking out Revision eab8a79ff6cde375c017b6f9eec29dff02a0bb85 (refs/remotes/origin/master)
> git config core.sparsecheckout # timeout=10
> git checkout -f eab8a79ff6cde375c017b6f9eec29dff02a0bb85
Commit message: "MOD: init commit"
First time build. Skipping changelog.
Finished: SUCCESS
如上所示,我们成功地从代码仓库迁出了代码,第一步顺利完成!
在迁出代码后,我们需要进行编译,回到lmsia-xyz-build项目的配置中,找到Build选项,新增一个"Execute shell"步骤,命令输入"gradle build",点击底部"Save"。
再次执行"Build Now",发现项目依然执行成功,查看日志,可以发现编译也成功地执行了!
至此,我们已经完成了代码的迁出和编译。
在lmsia-xyz-build项目中新建一个Shell步骤,内容如下:
$HOME/ms2docker.sh
上述脚本需要添加到slave的镜像中,直接COPY即可,这里不再赘述。
脚本内容如下:
#!/bin/bash
set -e
# Const
DOCKER_REGISTRY="10.1.64.72"
PROJECT_VERSION=${BUILD_NUMBER:-1}
PROJECT_NAME=$(basename `pwd`| sed -r 's/-build$//g')
SERVER_NAME="$PROJECT_NAME-server"
JAR_NAME="$SERVER_NAME.jar"
DOCKER_FULLNAME="$PROJECT_NAME:$PROJECT_VERSION"
# Copy Jar
find . -name "$SERVER_NAME*.jar" -exec cp {} ./$JAR_NAME \;
# Generate Dockerfile
cat > ./Dockerfile <<EOF
FROM anapsix/alpine-java:8_server-jre
VOLUME /tmp /app
WORKDIR /app
EXPOSE 8080 3000
COPY ${JAR_NAME} /app
CMD ["java", "-jar", "../../../app/${JAR_NAME}"]
EOF
# Build
docker build .
docker build -t $PROJECT_NAME .
docker tag $PROJECT_NAME $DOCKER_REGISTRY/$DOCKER_FULLNAME
docker push $DOCKER_REGISTRY/$DOCKER_FULLNAME
简单解释一下:
在Jenkins配置好后,点击保存,重新打包。
看一下结果:
....
The push refers to repository [10.1.64.72/lmsia-xyz]
ce4c6e84ae9a: Preparing
c24b758e34d0: Preparing
c28e906f67c9: Preparing
cd7100a72410: Preparing
c28e906f67c9: Layer already exists
c24b758e34d0: Layer already exists
cd7100a72410: Layer already exists
ce4c6e84ae9a: Pushed
6: digest: sha256:86ccfb07945bdaf61c90470e3302774cde31d41b6c1a26647ea92fdb681536b3 size: 1158
Finished: SUCCESS
如上所述,已经成功地打好Docker镜像并上传到私有仓库中。
至此,我们经完成了持续集成的所有步骤,它包含: