为什么标题这么奇怪?
之前都是使用Gocd来作为CI的工具,但是Jenkins是目前使用最火的CI工具,所以想研究研究。作为一个有梦想的咸鱼,不想简简单单地走一遍官方教程,就设定了一个target,Pipeline的每一步都使用Docker来做。所以标题的意思就是,用Jenkins做CI工具,用Docker来测试,用Docker来Build,用Docker来Deploy。
简介
- 这次会用Spring项目来做示范,进行全套的CI流程。这是本次项目使用的代码库
- 所有的需求就是装好docker,能使用git的。
1. Set up Jenkins
1. Install Jenkins in docker
docker run \
--restart=always \
-d \
-u root \
-p 8888:8080 \
-v jenkins-data:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$HOME":/home \
jenkinsci/blueocean
解释一下几个重要的参数:
- -u root 用root作为容器的用户
- -p 8888:8080 将本机8888端口映射到容器内部的8080端口
- -v jenkins-data:/var/jenkins_home 将jenkins-data volume映射到/var/jenkins_home。 这是做持久化用的
- (很重要)/var/run/docker.sock:/var/run/docker.sock 这个参数将本机的docker.sock映射到容器内的docker .sock,这样就可以在容器内部使用docker来在本机启动一个容器了。
- -v “$HOME”:/home (暂时还没发现有什么用)
- jenkinsci/blueocean 这是带blueocean的Jenkins容器。blueocean提供了更漂亮的界面。
2. Login Jenkins
装完后,可以访问http://localhost:8888看到:
按照提示找到密码然后填入
3. Initialize Jenkins
登录后,可以看到初始化界面:
这里选择Install suggested plugins
4. Register
装完后就出现了注册界面,可以注册第一个admin账号:
2. Set up repository
好的,Jenkins的环境准备完毕,来准备一下代码库(我们默认使用github来做代码库)。 目前代码库只需要一个README就行了。
3. Set up pipeline
- 打开blue ocean:
- 点击Create a new Pipeline。
- 点击GitHub
- 点击”Create an access key here.”来创建Jenkins用于访问GitHub的Access token
- 输入github密码
- 取个token名
- 将token填入Jenkins并点击connect
- 选好组织
- 选好repository
- 点击”Create Pipeline”。
- 设置好pipeline:
- 将Start的agent设置为none,因为我们之后要在不同的stage使用不同的agent(也就是不同的docker image):
- 添加一个hello world的stage:
- 点击页面左边的加号
- 在页面右边输入stage名字
- 点击add step
- 选择Shell Script
- 输入: echo ‘hello world’:
- 点击Settings
- 选择agent为docker
- image填busybox:
- 点击save
- 点击Save&run:
- 等一小会儿,我们的第一个pipeline就变绿了。再check一下我们的git仓库,里面多了一条commit,以及Jenkinsfile:
- 现在我们的pipeline还没法自动触发,我们可以点击小齿轮,进入pipeline的设置,然后设置Scan Repository Triggers为1分钟:
4. Add Test Stage
在准备好代码库和pipeline后,我们可以加入我们的第一个test stage了。
-
在Jenkinsfile中加入新的stage:
stage('Test') { agent { docker { image 'java:8-jdk-alpine' } } steps { sh './gradlew clean test' } }
因为我们的代码库里面什么都没有,所以pipeline肯定会红。
- 在代码库中引入spring,并确保本地
./gradlew clean test
可以通过。 - push代码,等待pipeline被trigger。
-
打开jenkins,发现pipeline已经过了:
-
仔细看一下Test stage的log,发现了各种download依赖,我们需要想个办法保存这些依赖,作为gradle项目,直接将~/.gradle映射到容器中去就行了:
stage('Test') { agent { docker { image 'java:8-jdk-alpine' args '-v /home/jenkins/.gradle:/root/.gradle' } } steps { sh './gradlew clean test' } }
-
但是我们现在点击Pipeline里的Test,发现: 所以现在我们加上Test报告:
stage('Test') { agent { docker { image 'java:8-jdk-alpine' args '-v /home/jenkins/.gradle:/root/.gradle' } } steps { sh './gradlew clean test' } post { always { junit 'build/test-results/**/*.xml' } } }
-
赶紧写一个Fail Test来试试我们的报告效果:
@Test public void shouldFail() { assertEquals("expected value","wrong value"); }
- 把我们的这个fail test删掉~~~~~
5. add build stage
在我们的Jenkinsfile里加上build stage:
stage('Build') {
agent {
docker {
image 'java:8-jdk-alpine'
args '-v /home/jenkins/.gradle:/root/.gradle'
}
}
steps {
sh './gradlew clean build'
}
post {
success {
archiveArtifacts artifacts: 'build/libs/*.jar', fingerprint: true
}
}
}
post块里的代码会让我们将build出来的包给保存下来。
补充说明:其实gradle build的时候会跑test,所以可以只留一个stage。
6. add build docker image stage
- 在这个stage我们需要把之前的jar包copy出来,所以需要Copy Artifact插件:
- 依次打开:Manage Jenkins -> Manage Plugins
- 使用filter 搜索Copy Artifact并安装
- 添加Dockerfile:
FROM java:8-jdk-alpine VOLUME /tmp COPY entrypoint.sh entrypoint.sh RUN chmod +x entrypoint.sh COPY app.jar app.jar ENTRYPOINT ["./entrypoint.sh"]
entrypoint.sh:
#!/bin/sh java -jar -Dspring.profiles.active=$springProfiles /app.jar
- 添加build的脚本:
build.sh
#!/usr/bin/env sh dockerRegistry='192.168.42.10:5000' imageName=jenkins_docker_spring cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0)) set -x docker build -t "$dockerRegistry/$imageName" . docker push "$dockerRegistry/$imageName" set +x cd -
-
在Jenkinsfile里添加新的stage:
stage('Build Docker') { agent { docker { image 'docker:stable' args '-v /var/run/docker.sock:/var/run/docker.sock' } } steps { step([$class : 'CopyArtifact', filter : 'build/libs/*.jar', fingerprintArtifacts: true, projectName : '${JOB_NAME}', selector : [$class: 'SpecificBuildSelector', buildNumber: '${BUILD_NUMBER}'] ]) sh 'cp build/libs/*.jar docker/app.jar' sh 'docker/build.sh app.jar' } }
一些重要的解释:
-
在docker容器中使用docker的时候,需要加上这个映射
args '-v /var/run/docker.sock:/var/run/docker.sock'
-
这个地方是在调用jenkins的插件,主要作用就是将上个stage的jar拷贝到这个stage中去。
step([$class : 'CopyArtifact', filter : 'build/libs/*.jar', fingerprintArtifacts: true, projectName : '${JOB_NAME}', selector : [$class: 'SpecificBuildSelector', buildNumber: '${BUILD_NUMBER}'] ])
7. add deploy stage
最后就是deploy了,一般的部署方式都很简单k8s和rancher都可以有http的方式来部署,这里我们使用ssh的方式登录到目标机器来部署(虽然不是好的实践,但是可以了解一下这样的方式)
- 我们需要在Jenkins里安装一个插件:
- 依次打开:Manage Jenkins -> Manage Plugins
- 使用filter 搜索Publish Over SSH并安装
- 给Publish Over SSH设置我们的ssh private key。
- 依次打开:Manage Jenkins -> Configure System
- 在Publish over SSHsection进行配置
-
在Jenkinsfile里面新加一个Deploy stage:
stage('Deploy') { agent { docker { image 'busybox' } } steps { sshPublisher(publishers: [sshPublisherDesc( configName: 'configuration1', transfers: [sshTransfer(execCommand: 'echo 111')])]) } }
sshPublisher相当于调用Publish Over SSH的函数: 1. configName: 对应插件配置里的Name。 2. execCommand: 登录目标机器后想执行的命令。这里我只是echo了一下。
8. 结束语
学好docker,走遍天下都不怕。只需要一台装有docker的机器,我们的jenkins就能完美运行了。