背景
前几天在掘金刷文章,刷到了自动化CI/CD,于是心血来潮打算耍一耍,于是打开我的阿里云服务器,启动了一直睡觉的服务器实例,准备试试手,一路磕磕绊绊,最终功夫不负有心人,修成正果。
记录一下,也给现在或即将入手的你带来一份保姆级实站教程,这瓜保不保熟你说了算
前置准备
服务器一台(本系统CentOS 7.3 64位)
Gitlab项目一个(本次用Umi4创建了一个项目),提前创建好对应分支dev/sit ...
实现目标
搭建Docker + Jenkins + Nginx 环境,实现同一服务器多环境(dev、sit 、uat ...)通过git提交对应分支 自动部署到服务器上对应的环境,本案例仅考虑linux服务器前端部署自动化
可能用到的linux命令
arduino
名词 CONTAINER ID(容器ID)
名词 IMAGE ID(镜像ID)
cat /etc/os-release //查看系统信息
pwd //查看当前路径
ls //查看当前目录文件
docker -v //查看docker版本
docker-compose up -d //在后台运行(取消-d是在控制台运行)
docker-compose stop //所有容器停止运行
docker ps -a //查看正在运行的容器
docker images //查看安装的镜像
docker stop [CONTAINER ID] //停掉运行的容器
docker rm [CONTAINER ID] //删除运行的容器
systemctl status docker //查看docker状态
systemctl stop docker //停止docker
systemctl start docker //启动docker
systemctl restart docker //重启docker
环境搭建
本示例环境:
CentOS 7.3 64位
Docker version 24.0.5
Nginx: version 1.25.1 latest(最新版)
Jenkins:version 2.401.3 lts(稳定正式版)
Docker Compose: version v2.20.2
连接服务器
可以采用ssh连接(ssh root@服务器公网IP
)、云服务器官网-控制台-远程连接 或者一些连接工具例如 FinalShell
等工具
搭建Docker环境
Docker 是一个开源的应用容器引擎,基于 [Go 语言]并遵从 Apache2.0 协议开源。
Docker 从 17.03 版本之后分为 CE(Community Edition: 社区版) 和 EE(Enterprise Edition: 企业版)
Docker 包括三个基本概念:
镜像(Image) 、容器(Container) 、仓库(Repository)
安装Docker
首先要确保自己的服务器系统满足安装要求,具体要求可去百度查找
Docker安装完毕后
查看docker相关的rpm源文件rpm -qa |grep docker
Docker启动
sql
sudo systemctl start docker
安装Docker-compose
Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。
ruby
curl -L https://get.daocloud.io/docker/compose/releases/download/v2.4.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
Docker-compose权限提升和jenkins目录权限提升
bash
chmod 777 /docker/jenkins_home
sudo chmod +x /usr/local/bin/docker-compose
如果出现操作docker命令权限问题,可用下面提升权限,不出现可忽略
bash
#创建用户组,已存在则无需理会
sudo groupadd docker
#将需要授权docker权限的用户添加到docker用户组内
sudo usermod -aG docker $USER
#-------------上面两步操作完,用户就有docker权限了
#将docker-compose应用分配给docker用户组
sudo chgrp docker /usr/local/bin/docker-compose
#设置docker用户组可执行权限
sudo chmod 750 /usr/local/bin/docker-compose
安装Git
arduino
sudo apt-get update //更新包索引
sudo apt-get install git //安装git
git --version //验证
配置密钥
arduino
ssh-keygen -t rsa -C "root"
一路enter
然后会生成id_rsa(私钥) 、id_rsa_pub(公钥)
输入cd /root/.ssh
添加公钥到gitlab
将公钥添加到Gitlab或其他代码库的SSH Keys
验证一下,如果成功,则可以在服务器上拉取项目代码
安装Nginx和Jenkins
jenkins最新版已停止维护
改用jenkins/jenkins:lts
bash
docker pull nginx
docker pull jenkins/jenkins:lts
安装完毕后配置通过 docker images
查看安装的镜像
然后创建文件目录,可通过远程连接工具手动创建,也可以通过下面命令创建
bash
mkdir /docker
mkdir /docker/compose
mkdir /docker/jenkins_home
mkdir /docker/nginx
mkdir /docker/nginx/conf
mkdir /docker/html
mkdir /docker/html/dev
mkdir /docker/html/sit
mkdir /docker/html/uat
目录创建之后创建两个文件docker-compose.yml、nginx.conf
bash
cd /docker/compose
touch docker-compose.yml
cd /docker/nginx/conf
touch nginx.conf
最后目录结构如下
less
+ docker
+ compose
- docker-compose.yml //docker-compose配置
+ html //各环境代码目录(实际项目可能不在同一目录)
+ dev //dev环境代码目录
+ sit //sit环境代码目录
+ uat //uat环境代码目录
+ jenkins_home //Jenkins工程目录
+ nginx //nginx工程目录
+ conf
- nginx.conf //nginx配置
setting.yml配置
ruby
version: '3'
networks:
frontend:
external: true
services: # 容器
docker_jenkins:
user: root # root权限
restart: always # 重启方式
image: jenkins/jenkins:lts # 使用的镜像
container_name: jenkins # 容器名称
ports: # 对外暴露的端口定义
- 8080:8080
- 50000:50000
volumes: # 卷挂载路径
- /docker/jenkins_home/:/var/jenkins_home # 挂载到容器内的jenkins_home目录
- /usr/local/bin/docker-compose:/usr/local/bin/docker-compose
docker_nginx_dev: # nginx-dev环境
restart: always
image: nginx
container_name: nginx_dev
ports:
- 8001:8001
volumes:
- /docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf
- /docker/html:/usr/share/nginx/html
- /docker/nginx/logs:/var/log/nginx
docker_nginx_sit: # nginx-sit环境
restart: always
image: nginx
container_name: nginx_sit
ports:
- 8002:8002
volumes:
- /docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf
- /docker/html:/usr/share/nginx/html
- /docker/nginx/logs:/var/log/nginx
docker-compose 配置说明
- version:指定Compose文件版本。
- services:定义需要启动的服务及它们所需的镜像、端口映射、环境变量、挂载到主机上的目录等相关信息。
- volumes:定义数据卷,即将容器内部的数据持久化到宿主机的目录中。
- networks:定义网络,使得多个服务可以在同一网络下相互通信。(跨服务器连接时必须要配置)
- environment:指定环境变量,传递给服务容器使用。
- ports:定义端口映射,将容器内部的端口映射到宿主机端口。
- depends_on:定义服务启动的依赖关系,保证依赖的服务先启动。
- build:定义镜像构建的相关信息,包括构建上下文路径、Dockerfile路径以及构建参数等。
nginx.conf配置
ini
# nginx.conf 例:
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
gzip on;
#dev环境
server {
#监听的端口
listen 8001;
server_name localhost;
#设置日志
# access_log logs/dev.access.log main;
#定位到index.html
location / {
#linux下HTML文件夹,就是你的前端项目文件夹
root /usr/share/nginx/html/dev/dist;
# root /home/html/dev/dist;
#输入网址(server_name:port)后,默认的访问页面
index index.html;
try_files $uri $uri/ /index.html;
}
}
#sit环境
server {
#监听的端口
listen 8002;
server_name localhost;
#设置日志
# access_log logs/sit.access.log main;
#定位到index.html
location / {
#linux下HTML文件夹,就是你的前端项目文件夹
root /usr/share/nginx/html/sit/dist;
# root /home/html/dev/dist;
#输入网址(server_name:port)后,默认的访问页面
index index.html;
try_files $uri $uri/ /index.html;
}
}
# include /etc/nginx/conf.d/*.conf;
}
想要更详细的配置可参考:nginx详细配置
开放服务器对应端口
本示例要开放的有3个:
8080:对应jenkins
8001:对应dev环境
8002:对应sit环境
进入对应服务器控制台安全组配置添加
环境启动
按照上面步骤Docker环境就部署好了
执行docker启动命令systemctl start docker
进入到对应目录 cd /docker/compose
执行命令 docker-compose up -d
查看容器运行状态
css
docker ps -a
如下图
此时浏览器输入 IP:端口 ,会自动进入jenkins页面,需要输入密码
密码在 docker/jenkins_home/secrets/initialAdminPassword
可以通过命令:
bash
cat /docker/jenkins_home/secrets/initialAdminPassword
复制密码进jenkins登录页面
等待安装完成配置用户密码
配置实例,自定义,然后可以登录访问了,首页如下
至此环境安装完毕并启动成功
Nginx配置
验证nginx环境
在对应目录/docker/html/dev/dist
、/docker/html/sit/dist
各新建一个index.html
附上代码
xml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>这是一个测试</h1>
</body>
</html>
如果页面正常打开代表nginx配置成功
然后删除对应的dist文件夹,后续会自动生成
Jenkins配置
设置中文
下载 local 插件
选择【Manage Jenkins】-> 【Plugins】-> 【Plugins】->
如已安装,执行重启即可,地址栏输入http://ip:端口/restart
安装插件
安装之后重启
Publish Over SSH
用来连接远程服务器的
进去找到Publish Over SSH,这块有多种连接验证方式,本案例采用密码验证,想使用别的方式请移步百度搜索
找到SSH Server,点击新增
点击高级
往下滑动
测试成功后->应用->保存
Gitlab
用来连接gitlab和jenkins
NodeJS
用来执行前端代码环境
安装之后配置node环境,可以用服务器上的,如果用服务器上的,需要在服务器配置环境变量,我这边配置好后在Jenkinsg构建时一直失败,尝试很久,感兴趣的可以尝试下,最后考虑还是选择用自动安装的,按下图操作,这里node版本要与本地环境node版本保持一致
先应用后保存
添加凭据
添加凭据(账号密码)是为了方便后续使用
添加gitlab账号密码
创建job
源码管理
应用保存后,点击立即构建
无论是否成功,都可以在构建记录控制台查看
如果成功代表基础构建成功,咱们继续完善构建
Gitlab WebHooks配置
登录gitlab,进入偏好设置
这里的URL
和Secret Token
来自下面 构建触发器 那块,仔细找一下,Triger是触发方式,本案例选的全部分支提交都会触发,实际项目根据实际来,SSL可选可不选
往下拉,点击添加
验证webhooks
点击TEST、点击Push events
在此步未出现200的请移步文章后面的踩坑记录
gitlab验证200通过后,测试通过,去jenkins工程看构建记录是否新增一条测试的记录,如正常新增,代表整个流程流通,如未流通,请检查上面创建job -源码管理是否正常成功
构建触发器
点开高级
往下滑
点击应用保存,继续重复之前立即构建,构建成功,此时代码已经同步过来了
前两步构建都需要结合gitlab
Build Steps
在/docker/jenkins_home/workspace/gitlab_web
,每次构建对应代码都会同步更新,由于代码没有在git上传node_moudle文件夹,所以后续需要在服务器重新安装node_moudle
Execute NodeJS script
这里选的node是上面配置的
点击应用后保存,然后点击立即构建,此次时间会长一点 ,此时构建会去自动安装对应的nodejs安装包到jenkins目录并配置好环境变量,注意尽量与本地开发环境的node版本一致,为了保持环境同步,等待构建好后再执行下一步
应用保存并构建成功后,可以进入下一步
Shell命令
在shell命令这块有的执行node -v都报错,此时请检查环境变量是否与服务器的环境变量有差异,执行echo $PATH
第一行代码一般要添加#!/bin/bash
,至于为什么可以去百度查询,考虑时间精力,这里不做扩展
如果环境变量有问题可能要在第二行执行刷新环境变量命令
bash
source ~/.bash_profile
source /etc/profile
上面哪个有效果用哪个
然后执行对应命令,验证环境可用
bash
#!/bin/bash
node -v
npm -v
echo $PATH
上述命令如成功执行,进行下一步
安装node_moule并build打包
如果在之前构建时,git已经提交里node_moudle,此时就不需要执行项目重新装包的操作,可跳过此命令添加,我这边本地用的yarn构建
添加yarn包管理器
ini
npm install -g yarn -registry=https://registry.npm.taobao.org
yarn -v
成功后执行包安装
lua
yarn install --pure-lockfile
#--pure-lockfile 这个参数是在服务器install不生成yarn.lock,防止服务器和本地代码冲突,本地代码也不要提交上去
安装成功后执行build打包
此时执行命令要把安装包命令去掉
继续执行构建操作
此时服务器代码出现dist文件夹
代码自动部署到对应环境项目目录
同一服务器可以用cp
命令,可以参考linux cp命令
之前尝试过cp执行失败,考虑时间精力最后改成压缩包ssh传输,但cp命令确实是可以的,应该是我姿势不对,感兴趣的朋友可以试着用一下,这样同一服务器减少了一步ssh构建
压缩成tar
命令:
bash
rm -rf dist.tar # 每次构建删除已存在的dist压缩包
tar -zcvf dist.tar ./dist #将dist文件压缩成dist.tar
构建成功后多了个dist.tar文件
连接SSH服务器
系统配置已经设置过ssh相关配置再操作下面,如未设置,请往上翻 Publish Over SSH
字段说明:
Source files
:准备发送的文件,该文件是相对于这个项目的workspace
目录。例如要发送/docker/jenkins_home/workspace/gitlab_web/dist.tar
到目标目录,则设置Source files
为dist.tar
Remove prefix
:目标文件前缀添加,例如要操作src下面的某个文件,就设置成src
,本案例是跟目录,无需设置
Remote directory
:目标目录,本案例要复制到dev环境下的dist文件,/docker/html/dev
Exec command
:最后执行的命令,可在这里进行解压,删除,复制等操作
执行构建后,对应目录已经有了一个dist.tar文件
这样肯定是不行的,还需要删除原有dist文件夹,解压dist.tar,再删除dist.tar,最终命令如下
bash
cd /docker/html/dev
rm -rf dist/
tar zxvf dist.tar
rm dist.tar
至此,shell命令执行结束,目前的效果是:
gitlab 项目dev
分支git提交后,触发jenkins自动构建,自动构建会先在服务器从gitlab的dev
分支拉取最新代码,执行build
打包后生成dist.tar
文件,然后通过ssh将对应dist.tar
发送到对应dev
环境的项目目录解压dist.tar
并更新对应文件,实现自动更新dev环境
验证自动更新dev效果
现有dev环境
现有代码:
更改后提交:
此时的gitlab上有了对应的分支提交
此时的jenkins工程构建目录里有了来自gitlab的自动触发
此时的网页http://ip:8001 已经自动更新
至此,dev
环境自动构建已结束,如果需要自动更新sit
环境,需要按照上面的创建job重新建一个指向sit的自动构建,这里不再赘述
踩坑记录
gitlab webhooks 测试 HTTP 403
在Jenkins构建成功后,连接gitlab的webhooks测试时出现http 403
问题原因
网上查了很多文档,经多方排查验证,主要是CSRF配置问题,还有要把Jenkins系统设置中取消勾选"Enable authentication for '/project' end-point" ,因为该案例是用的gitlab官网,不是在服务器上单独搭建的一套gitlab平台,单独搭建没试,感兴趣的可以搭一下看是不是还出现403
在旧版本的jenkins里,有这个配置可控,但自2.2xx版本之后,csrf认证在web界面里已经没法关闭了 本案例现有的配置,
实测勾选启用代理兼容并重启jenkins后无效
解决方案
勾选匿名用户具有可读权限
系统设置里
取消勾选"Enable authentication for '/project' end-point"
系统设置里
关闭CSRF
在网上找到了2个可以关闭csrf的方案
1.命令行临时关闭
输入命令
ini
hudson.security.csrf.GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTION=true
执行命令后在全局安全设置里是这样
优点:有效果
缺点:重启后失效
2.jenkins的启动配置中添加参数
这个我没试成功,感兴趣的可以研究下,我用的方案1
按照上面操作,403会消失,进而是其他报错401或者404
gitlab webhooks 测试 HTTP 404
问题原因
这个问题也排查了一段时间,最后发现是访问不到对应目录文件
我这边工程目录是http://ip:端口/job/gitlab_web/
在地址栏输入webhooks的URL http://ip:8080/jenkins_home/project/gitlab_web 发现是打不开的
本案例最初设置的实例是jenkins_home
最终分析出这个是要设置访问前缀,文章上面的docker-compose.yml最初是没有配置前缀的,所以Jenkins访问是http://ip:端口/
解决方案
在docker-compose.yml里添加前缀,本案例设置的实例是jenkins_home,所以要加前缀jenkins_home
ini
environment:
- TZ=Asia/Shanghai
- "JENKINS_OPTS=--prefix=/jenkins" ## 自定义 jenkins 访问前缀(上下文context)
最终yml效果
修改保存后重启容器,然后jenkins访问路径要变化了,变成http://ip:端口/jenkins_home
重启后再次在gitlab 测试webhooks,发现404没了。
友情提示:
上面我关闭CSRF配置走的方案1,此时随着重启CSRF已经恢复过来了,但,gitlab测试仍然是成功的,所以,CSRF白配了,若问我为什么最初的docker-compose.yml不配置前缀最终版,😄(内心OS:我踩的坑你们不踩怎么行)看到这的朋友是不是要来个赞👍,别的文章有我记忆深刻吗,是不是这个理,其实还有个坑我没放出来,是nginx的配置坑,直接一步到位了
总结
5年前端,第一次在掘金发文,面对当前大环境,也有迷茫,这篇文章算是心血来潮,想留下一点东西吧,之前没接触过CI/CD,属于一路坑踩过来的,最终部署成功后,为了写这篇文章,全部卸载重新安装又走了一遍,jenkins卸载的时候发现卸载不干净,单独卸载docker里的jenkins没多大效果,数据卷还存在,又重新百度最后卸载干净,然后边部署边写文章,所以目录排版有点错乱,请见谅,终于今天完成,亲测保熟可食😄,后续可能完善后台node,对接后台接口,实现前后端均自动化部署。
各位看官如果有收获,请一定要用一键三连砸我,谢谢🙏