24

在软件开发过程中,如果我们每一次提交的代码都能够进行一次完整的编译、测试、打包、发布,就能及早发现问题、及早修复,在保证代码质量的同时让产品快速迭代。这就是持续集成(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 用在日常的开发中了。

点击查看博客原文


xnng
224 声望16 粉丝