前言
在平时开发中,我们可能经常听到几个词汇,比如:自动化
、CI/CD
、DevOps
等。有时候我其实是很抵触的,但是有时候又好奇,这些个玩意到底是啥,了解它们对我一个前端开发人员有什么好处?下面我将从理论到实践实现一个简单的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账号
安装Docker
和Docker 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
容器中的默认uid
是1000
,用户名是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 daemon
的groupid
分配给uid
为1000
的jenkins
用户,给jenkins
这个用户提供一个995
所在用户组权限,这样jenkins就有了操作docker daemon
的权限了。
在运行docker-compose
之前我们可以在宿主机查看docker的groupid:
[root@centos7 ~]# cat /etc/group | grep docker
[root@centos7 ~]# docker:x:995:
另外还有一个很重要的地方就是,我宿主机的/home/jenkins_data
目录用户是root
,jenkins
用户是没有读写权限的,可以添加下面命令:
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拉取代码。
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工作流程等知识,我虽然是一名前端开发人员,但是有了这些知识的了解,我对整个项目的流程有一个整体的认知,虽然配置不是很复杂,但是对于敏捷开发流程有了更多认识。
最后:十分感谢您看完了这篇文章,本人是一个目前还是一个菜鸟,如果文章有错误,欢迎您的指正!