Jenkins+Docker实现部署你的Vue应用

3,468 阅读18分钟

前言

在平时开发中,我们可能经常听到几个词汇,比如:自动化CI/CDDevOps等。有时候我其实是很抵触的,但是有时候又好奇,这些个玩意到底是啥,了解它们对我一个前端开发人员有什么好处?下面我将从理论到实践实现一个简单的CI/CD流程,带大家一起来看看这其中的奥妙之处。

1. 什么是自动化

我总结就是:为了“偷懒”。😄

在没有工具的时代,有很多重复性的工作都是人手工完成的,比如在田地里收割果实、在流水线上加工等。而有了工具后,我们解放了双手,从而把精力放在更重要的事情上,让工具帮我们去完成那些重复性的工作,来提高效率,这就是我理解的自动化。在软件开发中更是如此,大家为了“变懒”,使自动化测试、自动化部署、自动化构建等类型的工具层出不穷,从而让我们更加专注软件的质量,提高软件的生命周期。

2. CI/CD

在这里插入图片描述

2.1 基本概念

CI/CD包含了Continuous Integration(持续集成) Continuous Delivery(持续交付) Continuous Deployment(持续部署)三个核心流程,也可以称为CI/CD的pipeline(管线)。它是敏捷开发中的一个最佳实践,使软件开发团队能够专注于满足业务需求,代码质量和安全性。

2.2 使用场景

下面我来描述一个简单工作场景让大家理解这个流程:

A和B是某项目组的开发人员,D是运维人员。A开发完成了一个登录功能,完成后就推送到远程的dev分支,与此同时B也开发完了一个购物车功能也推送了远程的dev分支,A和B想看看线上环境功能是否正常,于是让D去发布一个线上的测试版本,于是他按项目要求安装环境然后进行一些配置发布,这下A和B终于看到了线上的代码了,于是它们决定放到线上环境,然后D同学又开始一顿环境配置,结果很不幸出现了Bug,然后A和B改Bug,交给D继续重复流程...😭

后来甲方爸爸催得紧需要查看功能,不得已项目工期只能缩短了,而大家开发进度实在是太忙了,A和B没办法开发“CV”操作,bug层出不穷...后来他们决定换一个方式:

A和B继续开发功能,但是开始使用自动化测试工具来测试它们的功能。而D先在公司内部用搭建了Jenkins平台,然后加了一些运维脚本把要干的操作全写好了,也为不同环境编写了不同的脚本。A和B推送后,Jenkins这边自动拉取仓库代码,然后执行脚本开始构建docker镜像并创建容器来运行代码,过了一会儿,A和B刷新页面一看终于看到了功能,这时候甲方爸爸也看到了功能,终于算是放心了,今后A和B更加专注功能和业务实现了,bug也少了,而D自从用了Docker再也不用担心环境问题了,也不用手动去做哪些操作了,从而增加了“摸鱼”时间...(以上纯属瞎造)😂

从上面的过程我们可以看到,A和B一直给项目集成功能,然后用自动化测试并且持续推送远程仓库,这个过程就是Continuous Integration(持续集成),更加准确点说应该是这样:

持续集成(CI)可以帮助开发人员更加频繁地(有时甚至每天)将代码更改合并到共享分支或“主干”中。一旦开发人员对应用所做的更改被合并,系统就会通过自动构建应用并运行不同级别的自动化测试(通常是单元测试和集成测试)来验证这些更改,确保这些更改没有对应用造成破坏。这意味着测试内容涵盖了从类和函数到构成整个应用的不同模块。如果自动化测试发现新代码和现有代码之间存在冲突,CI 可以更加轻松地快速修复这些错误。

而D用Jenkins发布的测试环境版本,其实就是一个Continuous Delivery(持续交付)过程,将A和B开发的功能和自动化测试案例发布在测试环境中进行软件质量评估。这个流程可以验证项目不同阶段的成果,并且降低软件风险。

Continuous Deployment(持续部署)在前一个的流程上发布的生成环境版本,甲方爸爸以及普通用户可以看到你的成果,可以体验你的功能。

实际上从开发完成到部署上线这一个流程时间花的很少很少,这就是CI/CD的魅力所在,在如今产品疯狂迭代的今天,谁的功能发布的越快越好越能吸引用户,所以站在用户的角度来说,增多了用户的留存率,而对于开发和运维来说这更是一个敏捷开发的最佳实践。

总结来说,docker解决了环境不统以及手动配置的效率低下的问题。

纸上得来终觉浅,我们了解了基本概念后,下面当然要实践一波,下面我带大家看看如何使用Jenkins和Docker来部署一个Vue应用。

3. 实践:部署一个Vue应用

3.1 环境准备

下面是我个人的环境准备:

  • 开发环境:VSCode、Win10 OS
  • 安装了Docker和Docker Compose的Linux云服务
  • 一个具有项目读写权限的Github账号

安装DockerDocker Compose工具不是本文重点,大家可以自行上网查找相关文章。有一点要注意的是,Github需要通过外网访问Jenkins,所以必须把Jenkins部署在外网环境(也可以部署在内网,需要配合Gitlab)。

3.2. Docker部署Jenkins服务

Jenkins是具有跨平台特性的Java语言写的,可以直接在操作系统中运行.war文件来安装Jenkins,但是必须安装好Java环境,所以我更喜欢Docker来部署一个Jenkins服务。

3.2.1 安装和运行Jenkins镜像

下面是我编写的docker-compose.yml文件,更多详细配置可以参考Jenkins官方文档

version: '3' 
services:
  jenkins:
    container_name: 'jenkins'
    image: jenkins/jenkins:lts
    restart: always
    user: jenkins:995
    ports:
      - "8081:8080"
      # 下面两个端口可以根据业务情况是否添加
      -"50000:50000"
      -"10051:10051" # zabbix server 默认端口
    volumes:
      - /home/jenkins_data:/var/jenkins_home # 数据持久化
      # 将宿主机docker命令映射到Jenkins,这样在Jenkins容器里面可以使用docker命令了
      - /usr/bin/docker:/usr/bin/docker 
      - /var/run/docker.sock:/var/run/docker.sock
      # 映射时间
      - /etc/localtime:/etc/localtime 
      # 需要提前在宿主机设置 echo 'Asia/Shanghai' > /etc/timezone
      # 否则会报错: timezone is a directory
      - /etc/timezone:/etc/timezone

接着就可以使用docker-compose up -d命令将Jenkins服务在后台跑起来了。

关于user的说明:

jenkins容器中的默认uid1000,用户名是jenkins,我们可以在容器内部查看:

# 进入Jenkins容器
[root@centos7 ~]# docker exec -it jenkins bash
# 查看uid和groupid
jenkins@ee70639cddd8:/$ id
uid=1000(jenkins) gid=995 groups=995
jenkins@ee70639cddd8:/$ whoami
jenkins

Jenkin它默认是没有宿主机操作权限的,但是将来需要在jenkins中来进行容器的构建和创建等一系列操作,而宿主上的docker daemon才有这些权限,所以需要在宿主机找到docker daemongroupid分配给uid1000jenkins用户,给jenkins这个用户提供一个995所在用户组权限,这样jenkins就有了操作docker daemon的权限了。

在运行docker-compose之前我们可以在宿主机查看docker的groupid:

[root@centos7 ~]# cat /etc/group | grep docker
[root@centos7 ~]# docker:x:995:

另外还有一个很重要的地方就是,我宿主机的/home/jenkins_data目录用户是rootjenkins用户是没有读写权限的,可以添加下面命令:

 sudo chown -R 1000:1000 /home/jenkins_data

运行成功后,如果Linux的防火墙是打开的话,我们需要添加端口放行:

# --permanent 永久放行
firewall-cmd --add-port=8080/tcp --permanent

# 重新加载防火墙
firewall-cmd --reload
3.2.2 解锁Jenkins

接下来我们打开浏览器输入ip+端口号就能访问Jenkins了。(如果你发现页面一直提示:Jenkins正在准备中...那么多等等吧😂...) 在这里插入图片描述 我们可以看到,初始进入的时候,我们需要输入管理员密码来解锁(jenkins默认有个超级管理员admin),有两种方式查看密码。

使用日志查看

docker logs -f jenkins

在这里插入图片描述

在Jenkins容器中查看

# 进入Jenkins容器内部, 使用bash终端
 docker exec -it jenkins  /bin/bash
 
# 输出密码
cat /var/jenkins_home/secrets/initialAdminPassword

输入密码后,如果正常应该会显示这个页面: 在这里插入图片描述

3.2.3 安装插件

推荐安装

点击推荐的插件,接下来耐心等待即可。如果中途出现您以离线,请使用代理或离线安装的消息提示的话,这是因为有很多插件依赖Google的服务所以下载不了,我们可以选择下面的离线安装和清华源的方式下载。

离线安装和使用清华源下载

一般插件安装后(不管失败还是成功)都会进入另外一个页面设置管理员用户,然后进行实例配置之后就会看到Jenkins的主页面了。我们第一步要做的就是配置清华源。

我们进入插件管理,按照下面的图示操作:

在这里插入图片描述 找到并点击一个绿色的图标,这个是管理插件的选项:

在这里插入图片描述 点击高级,找到升级站点,把URL替换成清华源提交即可:https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json

如果选择离线安装,先打开官方的插件,搜索相关的插件下载到本地,然后在刚刚配置清华源的页面有一个上传插件的功能,我们只要把在本地下载的插件上传提交就可以了。

在这里插入图片描述 接下来就可以安装你想用的插件啦~ 下面我也会介绍到跟GitHub有关的插件,常见插件推荐如下:

  • git相关:git client、github等
  • ssh相关的
  • buid timeout 构建超时处理
  • dashboard view 用户面板
  • Folders
  • thinbackup 备份
  • AnsiColor 高亮
  • build with parameters 构建时传入参数

注意:最新版本的中文社区提供了速度比清华源还快的源,可以更换为https://updates.jenkins-zh.cn/update-center.json

3.2.4 如何手动备份Jenkins数据

其实我们刚开始在创建docker-compose的时候就已经设置了数据卷的映射,不用再备份数据了,但是有时候会有手动备份的场景,下面我来告诉大家如何手动备份数据。

方式一:commit 镜像

先使用以下命令找到Jenkins容器ID,并复制:

docker ps -f name=jenkins

从该容器创建一个新的镜像:

docker commit 8ea38ac038af jenkins:v1.0

使用该镜像创建一个新容器并运行:

# -itd 启动交互式终端并在后台运行
# -v 将容器的tmp目录映射到宿主机的tmp目录
docker run -itd -v /tmp:/tmp jenkins:v1.0

如果不指定容器名字,docker默认会随机产生一个容器名称,我们可以通过docker ps命令查看。我这里产生的容器名称是:hungry_villani

接下来我们进入该容器,将数据目录jenkins_home拷贝到tmp目录下:

# 使用exec进入容器
# 在容器中使用cp命令将容器中的jenkins_home目录拷贝到容器中的tmp目录下
docker exec -it hungry_villani cp -r /var/jenkins_home /tmp

由于上面又做了数据卷的映射,我们可以在宿主机的tmp目录下找到这个jenkins_home目录了: 在这里插入图片描述 接下来可以使用linux中的mv命令,可以移动到你想备份的地方(tmp目录重启就会清空)。

方式二:使用--volumes-from命令

docker文档中有说明:可以使用--volumes-from标志来创建一个安装该卷的新容器的方法。我们可以来看看如何使用:

# --volumes-from从jenkins容器再复制一个出来,完成拷贝任务再删除
# 使用ubuntu中的tar命令将数据压缩
docker run --rm --volumes-from jenkins -v /tmp/backup:/backup ubuntu tar cvf /backup/backup.tar /var/jenkins_home 

此时我们可以在宿主机中找到这个backup目录,发现有一个backup.tar文件,我们可以解压就可以看到备份的数据目录了。

tar xvf backup.tar

这种方式对比第一种不会产生新容器占用内存了,而且很方便。大家如果想要了解详细,可以看看官方文档

方式三:使用cp命令一步到位

话不多说一句命令:

 docker cp 8ea38ac038af:/var/jenkins_home /tmp 

cp命令用于容器与主机之间的数据拷贝,上面的命令是将容器内部的jenkins_home目录拷贝到了宿主机的tmp目录(我们先得找到容器的id),这种方式可以说比上面两种来说要方便太多了,而且理解起来也比上面两种要轻松,所以十分推荐这种方式。

3.2.5 Jenkins的授权策略

常见的Jenkins权限控制有以下方式,可以通过插件安装它们:

  • Matrix Authorization Strategy(基于矩阵)
  • Role-Based Authorization Strategy(基于角色控制)
  • Github Authorization Strategy(github授权)

下面我来讲一下基于角色的控制,这个很常用,安装插件后可以在系统设置=>全局安全配置中勾选就可以开启基于角色的授权策略了。

在这里插入图片描述 接下来回到系统设置,拉到底部就可以看到这个功能已经打开了:

在这里插入图片描述 接下来的操作跟gitlab分配项目权限很像了,我就不细说了。

3.2.6 如何升级Jenkins版本

(1)先停止容器的运行

docker stop jenkins

(2)下载清华源war包:

 wget http://mirrors.tuna.tsinghua.edu.cn/jenkins/war-stable/latest/jenkins.war 

(3)将这个war包替换Jenkins容器内部的war包:

docker cp jenkins.war <jenkins container_id>:/usr/share/jenkins/jenkins.war

(4)重新运行

docker start jenkins

3.3 给Vue项目编写Dockerfile

下面进到了非常关键的步骤了,在我们已有的Vue项目上如何编写Dockerfile来构建镜像呢,这里Vue官方文档中的CookBook是有案例的,我稍微改动了一下: Dockerfile

# build stage
FROM node:lts-alpine as build-stage
# 镜像的元信息,maintainer项目维护者
LABEL maintainer="vuefans@gmail.com"
# 工作目录
WORKDIR /app
# 将git仓库下所有文件拷贝到工作目录
COPY . .
# 安装cnpm
RUN npm install cnpm -g --no-progress --registry=https://registry.npm.taobao.org
# 安装项目依赖并打包
RUN cnpm install --no-progress && cnpm run build

# production stage
# 生产环境基础nginx镜像(上面的镜像已经打包为了静态文件)
FROM nginx:stable-alpine as production-stage
# 使用--from把上面产生的静态文件复制到nginx的运行目录
COPY --from=build-stage /app/dist /usr/share/nginx/html
# nginx容器内部暴露的端口
EXPOSE 80
# 运行的命令
CMD ["nginx", "-g", "daemon off;"]

为了防止工作目录过大,可以添加dockerignore文件防止像node_modules这种文件被拷贝进来。另外还要提一嘴就是,不要进行太多冗余的操作,docker镜像是一层层叠起来的(非专业解释),会使镜像变得很大。

.dockerignore

# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
.DS_Store
dist

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
.dockerignore
Dockerfile
*docker-compose*

# Logs
logs
*.log

# Runtime data
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
pids
*.pid
*.seed
.hg
.svn

注意:上面两个文件一定要放在项目根目录下面。关于dockerfile更多信息,可以查看官方文档

3.4 配置Jenkins任务

接下来需要配置Jenkins的任务,这里带大家一步一步来完成。

3.4.1 配置与GitHub的SSH连接

代码从本地推送到GitHub后,我们需要利用Github的webkook功能来通知Jenkins开始自动化任务,但是Jenkins是需要拉取代码的。我这里的项目是一个私有项目,需要公私钥,我们可以利用SSH来拉取代码,所以需要在运行Jenkins的宿主机上添加私钥,在github的项目中添加公钥。

生成密钥对

# 4096 安全性更高 
ssh-keygen -t rsa -b 4096 -C "youremail"

将私钥添加到Jenkins凭据

在系统设置可以找到,记得勾选类型勾选SSH。

在这里插入图片描述 把公钥添加到项目的Deploy keys

在这里插入图片描述在仓库中配置webhook

配置webhook的目的是当收到推送事件后,通知Jenkins拉取代码。

webhook

3.4.2 Jenkins任务配置

有了前面的SSH配置基础后,现在我们可以进行项目的流程配置了,首先我们需要创建一个自由风格的项目,然后对项目进行一些基本说明。

General

在这里插入图片描述 源码管理

在这里插入图片描述 注意:如果Credentials那里出现一大堆红色的错误,说明你公私钥没配置对,你需要检查一下公私钥是否正常。

构建触发器 在这里插入图片描述 后面的构建环境、构建、构建后根据你的需求来自己决定,比如我在本地构建过程中对构建环境和构建完成后没啥要求(我使用docker自己配环境),一般构建完成后你可以发通知大家,或者干点别的。

构建主要是执行shell

#!/bin/bash

# 定义变量
CONTAINER=${container_name}
PORT=${port}

# build docker image 每次构建不产生缓存
docker build --no-cache -t ${image_name}:${tag} .

# 检查是否有重名和端口占用情况
checkDocker() {
  RUNNING=$(docker inspect --format="{{ .State.Running }}" $CONTAINER 2>/dev/null)
  if [ -z $RUNNING ]; then
    echo "$CONTAINER does not exist."
    return 1
  fi

  if [ "$RUNNING" == "false" ]; then
    matching=$(docker ps -a --filter="name=$CONTAINER" -q | xargs)
    if [ -n $matching ]; then
      docker rm $matching
    fi
    return 2
  else
    echo "$CONTAINER is running."
    matchingStarted=$(docker ps --filter="name=$CONTAINER" -q | xargs)
    if [ -n $matchingStarted ]; then
      docker stop $matchingStarted
      docker rm ${container_name}
    fi
  fi
}

checkDocker

# 运行容器
docker run -itd --name $CONTAINER -p $PORT:80 ${image_name}:${tag}

这里的参数传递可以通过Build with Parameters插件来设置,如果安装过可以在前面勾选上:

在这里插入图片描述 我们需要传递四个字符参数: container_name(容器名)image_name(镜像名)tag(标签)port(要映射的端口),配置完后记得保存。

到这一步算是完成了任务的简单配置了,下面可以从本地推送一个提交来测试,或者点击项目面板中的Build With Parameters来构建。 在这里插入图片描述

如果不出意外的话,可以看到左下角的球变为蓝色,就说明成功构建了:

在这里插入图片描述

3.4.3 测试是否成功部署

我们可以回到宿主机查看刚刚创建的容器是否在运行:

[root@centos7 ~]# docker ps | grep vue-demo
ffbc893cd1e3        vue-demo:1.0          "nginx -g 'daemon of…"   5 minutes ago       Up 5 minutes        0.0.0.0:8888->80/tcp                                                         vue-demo

此时你在URL输入你服务器ip+8888就可以查看到线上的Vue应用了:

在这里插入图片描述 大功告成!

如果你发现进不去,大部分可能是防火墙没有放行8888端口,那么你可以进行下面的操作:

# 可以先查看所有端口信息
firewall-cmd --list-all

# 永久放行8888端口
firewall-cmd -add-port=8888/tcp --permanent

# 重启防火墙
firewall-cmd --reload

3.5 常见问题

3.5.1 运行Jenkins后出现permission denied

我们通过docker-compose启动容器后,输入docker logs -f jenkins,发现一堆这样的错误: 在这里插入图片描述 说明,Jenkins没有宿主机上文件的读写和执行权限,可以给你映射的目录添加:

# 假如/home/jenkins_data是你宿主机映射目录
sudo chown -R 1000:1000 /home/jenkins_data
# 或者
sudo chmod 777 -R /home/jenkins_data
3.5.2 给防火墙添加了放行端口还是访问不了

很有可能是服务器的安全组没有打开放行,你需要到你对应的云服务器提供网站设置。

3.5.3 Jenkins构建中:docker: command not found

在这里插入图片描述 出现这种情况是因为我们让Jenkins操作docker,Jenkins没有操作docker的权限,你需要给Jenkins分配一个权限,具体可以看我前面Jenkins中docker-compose的配置。

4 总结

本篇文章给大家简单介绍了CI/CD的流程和使用场景,然后利用部署一个Vue应用来简单实践CI/CD流程。在这个过程中,我们可以了解Linux基本操作、Docker部署、Jenkins工作流程等知识,我虽然是一名前端开发人员,但是有了这些知识的了解,我对整个项目的流程有一个整体的认知,虽然配置不是很复杂,但是对于敏捷开发流程有了更多认识。

在这里插入图片描述

最后:十分感谢您看完了这篇文章,本人是一个目前还是一个菜鸟,如果文章有错误,欢迎您的指正!

5 参考

【1】Docker安装Jenkins官方文档

【2】CI/CD是什么?如何理解持续集成、持续交付和持续部署