在软件开发过程中,如果我们每一次提交的代码都能够进行一次完整的编译、测试、打包、发布,就能及早发现问题、及早修复,在保证代码质量的同时让产品快速迭代。这就是持续集成(CI)、持续部署(CD)的好处。
目前 CI/CD 的方案有很多,本文将展示一个用 Docker + Jenkins 实现的完整过程。
本文的 CI/CD 流程
开发人员提交代码到自己的分支并 push 到远程仓库 ==>
触发远程仓库(GitHub/GitLab)的 Webhooks ==>
Jenkins 接到通知自动执行之前准备好的一个流程(克隆代码,对代码进行编译、测试、打包,没有问题后会执行 docker 命令进行镜像构建)==>
最终发布到测试服务器中。
环境说明
- 本文选用的测试环境是阿里云的服务器,所以全程也是在服务器上操作的,
无需本地安装 docker
,当然在本地操作也是可以的。 - 本文选用的远程代码库是
GitHub
公有仓库,如果是私有仓库或 GitLab,步骤会略有不同。 - 本文中所用的
Jenkins
也是用的 docker 版,并不是直接安装在宿主机上的。
开始一个 docker 应用
要演示整个过程,就得有一个应用,这里我们用一个 create-react-app
为例,无需 IDE,一个 terminal 即可搞定。
- 首先创建 react-app 和 Dockerfile
4、5 行是增加了一个设置,是关掉 webpack 的 host 检查,如果不加此项,访问绑定域名的服务器就会被 webpack-dev-server 拦截。
$ npm install -g create-react-app
$ create-react-app my-app
$ cd my-app
$ touch .env
$ echo DANGEROUSLY_DISABLE_HOST_CHECK=true > .env
$ touch Dockerfile
- 将以下内容写入 Dockerfile:
为了简单,我们这里直接采用 npm start
的方式启动它,就不 build 了,安装 cnpm 是为了提高依赖的下载速度
FROM node:8.11.1-slim
WORKDIR /home/app
COPY . ${WORKDIR}
RUN npm install -g cnpm --registry=https://registry.npm.taobao.org \
&& cnpm install
EXPOSE 3000
ENTRYPOINT [ "npm", "start" ]
- 到这里就完全好了,单元测试什么的就暂且忽略。
为什么要使用 Jenkins
如果没有 Jenkins,就上面那个例子,我们想要将自己的代码集成并且部署到服务器,可能
要经历以下步骤:
- 1、将代码 push 到仓库
- 2、ssh 登录服务器,克隆代码到宿主机(宿主机还要安装 git)
- 3、执行以下命令完成镜像构建和部署
$ cd repository
$ docker build -t test .
$ docker run -d -p 80:3000 --name my-react test
可以看到上面那个过程需要人工操作,非常繁琐,这还没算上对代码进行测试,如果每次提交了代码都要来一个这样的过程,那是真的没法专心搞开发了。
如果用了 Jenkins,上面的整个过程都可以自动化完成。
初始化 Jenkins
Jenkins 的官网是 jenkins.io
,它有很多种安装方式,例如下载 war 包到宿主机上,然后用 java -jar jenkins.war
命令启动。但是这种安装方式非常不利于管理和服务器的迁移,完全是在给 docker 托后腿。所以我选择用 docker 版的 jenkins。
使用 docker 版的 jenkins 是需要注意很多细节的
- 首先我们要重写官方 jenkins 镜像
$ vi Dockerfile
FROM jenkins/jenkins:lts
USER root
RUN echo deb http://mirrors.aliyun.com/debian wheezy main contrib non-free \
deb-src http://mirrors.aliyun.com/debian wheezy main contrib non-free \
deb http://mirrors.aliyun.com/debian wheezy-updates main contrib non-free \
deb-src http://mirrors.aliyun.com/debian wheezy-updates main contrib non-free \
deb http://mirrors.aliyun.com/debian-security wheezy/updates main contrib non-free \
deb-src http://mirrors.aliyun.com/debian-security wheezy/updates main contrib non-free \
> /etc/apt/sources.list \
&& apt-get update \
&& apt-get install -y libltdl-dev
这里用了 jenkins 最新稳定版最为基础镜像,主要干了两件事:一、将账户改为 root,避免后面不必要的权限问题;二、安装 libltdl-dev ,它是为了解决用 jenkins 调用容器外部 docker 命令时发生以下错误的问题。(第 4~10 行是为了换阿里源提高速度)
docker: error while loading shared libraries: libltdl.so.7: cannot open shared object file: No such file or directory
- 在启动刚刚重写好的 jenkins 镜像的时候还需要挂载三个宿主机的目录到容器内,第一个 jenkins_home 是为了对容器内 jenkins 的所有改动做数据持久化。最后两个目录是为了
能让容器内的 jenkins 调用并操作容器外的 docker
。
$ vi docker-compose.yml
version: "3"
services:
jenkins:
build: .
image: my_jenkins
ports:
- "8090:8080"
- "50000:50000"
container_name: my_jenkins
volumes:
- "/home/jenkins_home:/var/jenkins_home"
- "/var/run/docker.sock:/var/run/docker.sock"
- "/usr/bin/docker:/usr/bin/docker"
为了看起来清晰,我写了一个 docker-compose.yml 文件,将这个文件和之前的 Dockerfile 放在同一个目录中,可以用以下命令快速启动 jenkins,启动之后新构建的镜像和容器都名为 my_jenkins。
$ docker-compose up -d
- 启动 jenkins 后浏览器访问
ip:8090
可看到初始化页面
这里要输入密码,它给出了密码在容器内的位置,我们要将路径改成宿主机上的,然后 cat 一下就能看到密码。
$ cat /home/jenkins_home/secrets/initialAdminPassword
- 将密码粘贴进去然后点继续,下一个页面选择插件,点默认推荐的就好了
- 接着按提示创建一个账户
- 之后就能够使用 jenkins 了
配置 Webhooks
要让 jenkins 操作本机上的 docker 的前提是它收到了我们 push 代码的通知,而这个通知就是由 GitHub 上的 Webhooks 来完成的,所以要将这两者关联起来。
- 配置 Webhooks 之前首先要更改一下安全设置
打开全局安全配置,软后进行如下操作,否则 Webhooks 连接不成功,设置好了别忘了点保存。
- 开始创建任务
点击首页的「开始创建一个新任务」,起个名字,选择流水线。
- 生成身份令牌
点击「构建触发器」,选择「触发远程构建」,然后随便填写一段字符,然后把 URL 复制下来,记得把「JENKINS_URL」和「TOKEN_NAME」替换为相应的值,例如下图最终得到的 URL 就是 111.11.1.1:8090/job/test/build?token=123456
,记下 URL 后点保存。
- 去 GitHub 创建 Webhooks
打开我们要 push 代码的仓库,点击「Add webhook」
然后将刚才记下的回调 URL 填写到这里即可
此时,可以尝试一下 push 代码到仓库,正常情况下,jenkins 就会自动进行构建,虽然没有配置要构建什么,但是它也会进行这个任务,如果构建历史中自动出现了一个颜色是蓝色
的任务则代表整个自动触发的过程是配置成功的。
编写自动任务脚本进行 CI/CD
- 点击上面那个任务的「配置」,切换到流水线这里。
本文不介绍流水线语法
,就用 shell 命令来编写整个过程。但我们首先还是要点击「流水线语法」
- 将示例步骤切换到「sh: Shell Script」,编写好 shell 脚本,熟悉 linux 命令的话,这个过程也应该很容易。写好之后点击「生成流水线脚本」,
之后把生成好的流水线脚本复制下来
。
- 将生成好的 流水线脚本复制到这里就好了,不过要把它复制到一个
node{}
里面才行。
以下是我写的生成好的流水线脚本,记得把定义的四个变量替换一下。
node {
sh '''#!/bin/sh
REPOSITORY_NAME="你的仓库名"
REPOSITORY_URL="你的仓库地址"
IMAGE_NAME="给你要构建的镜像起个名字"
CONTAINER_NAME="给你要构建的容器起个名字"
echo "清除仓库目录"
rm ${REPOSITORY_NAME} -r
echo "克隆远程仓库"
git clone ${REPOSITORY_URL}
echo "删除之前的镜像和容器"
docker stop ${CONTAINER_NAME}
docker rm ${CONTAINER_NAME}
docker rmi ${IMAGE_NAME}
echo "构建镜像"
cd ${REPOSITORY_NAME}
docker build -t ${IMAGE_NAME} .
echo "发布应用"
docker run -d -p 80:3000 --name ${CONTAINER_NAME} ${IMAGE_NAME}'''
}
- 最后可以提交一次代码或者点击「立即构建」,就会自动完成整个过程,在「控制台输出」那里可以看到构建过程。
此时,浏览器访问 ip 就能看到更新过的应用了,这就是一个 CI/CD 过程,整个过程省略的测试环节,可自行加上。
后记
用一个测试服务器来做 CI/CD,能够更及时的发现问题、解决问题,提高代码质量。
但是本文所展示的过程缺陷也很明显,在更新应用时,是会先停掉容器,再启动新容器的,不能做到无宕机更新。而且整个过程也没有服务监控什么的,不能很好地了解无服务的运行状态。
总之,到目前为止,我们已经能够很好地将 docker 用在日常的开发中了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。