如何实现前端工程的CI/CD?
虽然现在大部分前端开发不需要进行项目运维,但是前端项目如何在线上环境跑起来的,大家也还是需要了解一下的。本文将介绍一些前端项目部署及CI/CD常见的解决方案。
GitHub Page
-
我们先创建个项目,命名用
账号名称.github.io
的后缀,例如我的waltiu.github.io
记得把github设置中的worrkflow读写权限打开,要不然会报
git denied to github-actions[bot]`或`Process completed with exit code 128
-
代码中新建
.github\workflows\deploy.github.yml
内容如下:name: GitHub Pages on: push: branches: - master jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: submodules: true - name: Setup Node uses: actions/setup-node@v2 with: node-version: "14.x" # 使用缓存依赖 - name: Get yarn cache id: yarn-cache run: echo "::set-output name=dir::$(yarn cache dir)" - uses: actions/cache@v1 with: path: ${{ steps.yarn-cache.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn- - name: Install Dependencies run: yarn - name: Build run: yarn run build - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} # github 会在workflw中自动生成 publish_dir: "dist" publish_brach: "gh-pages" # 静态资源的分支 force_orphan: true
我们修改下代码,在actions中就看到github自动走了该流程。
会发现分支里多了一个我们上面配置的
publish_brach: "gh-pages"
,里面的内容正是我们构建的产物publish_dir: "dist"
,主界面也多了一个github-pages的选项,我们可以点进入查看我们部署的版本。需要保证我们这里的路径和构建分支是同一个!
这样我们就可以看到我们部署的项目,github默认是只允许搭建一个网站,不允许创建多个github page。
-
创建多个github page
我们修改下我们的项目名称为test,并等待github重新部署github page后访问
waltiu.github.io/test 这是修改后的github page地址,但是发现访问后报错了,由于我们访问的路径是/test,但是静态js和css资源还是在访问跟项目根路径,我们应该调整下静态资源的访问路径也是/test。
那我修改我们构建工具中的静态资源路径,以vite为例(webpack类似),我们先修改下packjson的名字为test:
如果我们有使用路由的话,可以根据自己的项目实际情况修改下路由前缀;
-
如何实现个性化域名:
我们先去阿里云的域名控制台添加一条域名解析,配置可以参数如下,替换记录为你自己的github page
这时我们通过域名直接访问,会出现这个错误,那么我们需要到github上也配置下我们刚才解析的域名
netlify
我们先登陆下官网,可以使用Github账号登陆, 按照推荐的流程创建一个team;
新建site,从github中导入一个项目,可以在第三步的时候根据自己项目修改下构建命令
我们后续可以在设置中,修改我们的构建参数,比如修改打包后的静态资源地址等
使用netlify如何个性化域名呢
先配置下我们要个性化的域名:
然后和Github就类似了,我们去阿里云为域名添加一条解析记录
自己的服务器(docker + nginx)
下面以阿里云为例,带大家从0到1部署项目,我们先选择一个linux服务器:
-
接下来我们安装一下我们需要的依赖:
- Nvm + Node + Npm + Pm2
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
nvm install 18.16.0 npm install pm2@latest -g
2. Nginx
yum install nginx
3. Docker
# 普通linux镜像 curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun # 或者 curl -sSL https://get.daocloud.io/docker | sh
# Alibaba Cloud Linux镜像 # https://help.aliyun.com/document_detail/264695.html yum -y install dnf dnf -y install docker
4. Git
yum install git
最后检查下各个依赖是否安装成功,分别执行-v:
-
将前端工程部署到docker里,那我们会有2种方法来实现,下面分别介绍:
-
docker内部静态资源替换
-
创建nginx容器并运行
执行以下命令后,在浏览器中输入
ip:8080
测试容器是否生效优化下,容器内外做个映射
-
gitub workflows 自动构建并替换文件
先来领取个阿里云服务器的密钥
绑定到自己的服务器上,然后记得重启项目
github上添加刚才阿里云下载的密钥
最后在前端代码库中新建一个
.github/workflows/deploy-page.yml
: -
ps: 遇到的一些问题
- Alibaba Cloud Linux镜像 镜像需要使用dnf按照docker
- 密钥绑定机器后需要重启下
- ssh私钥配置到github上时需要给----END RSA PRIVATE KEY----- 后加个空格换行,要不然会报
Load key "/home/runner/.ssh/deploy_key": error in libcrypto
- 需要在主机上执行
yum install rsync
,要不然会报rsync: connection unexpectedly closed (0 bytes received so far) [sender]
- nginx如果访问不通,可以排查下端口是否开放了
-
-
docker容器更新,重启
-
github workflow 创建docker镜像并推到阿里云镜像服务
新建一个Dockerfile用来创建镜像
我们去阿里云申请一个镜像仓库,先申请命名空间
创建镜像仓库,也可以绑定我们的git账号
接下来我们完善我们的github workflows:
先在GitHub密钥中配一下我们的docker容器账号密码(也就是阿里云的账号密码)
前端项目中创建
.github\workflows\docker-image.yml
,大家要根据自己阿里云镜像服务配置来替换圈来的这些内容哦!然后看下github action是否顺利执行和阿里云镜像是否被创建
-
新建watch-images服务,并设置阿里云的触发器
我们先创建一个简单的node服务,具体内容后面再补
const http = require("http"); http .createServer((req, res) => { if (req.method === "get" && req.url === "/") { console.log(req.url); res.end(req.url); //... } if (req.method === "post" && req.url === "/") { console.log("post"); //... } }) .listen(3000, () => { console.log("server is ready"); });
我们去服务器上运行一下,并测试下访问下
http://ip/?a=1
把
http://ip/?a=1
设置到阿里云的触发器上,ip替换成真实ip我们更新下我们的服务监听脚本,然后打一些日志
const http = require("http"); const url = require("url"); const TYPE_POST = "POST"; const Type_GET = "GET"; const resolvePost = (req) => { return new Promise((resolve) => { let chunk = ""; req.on("data", (data) => { chunk += data; }); req.on("end", () => { resolve(JSON.parse(chunk)); }); }); }; http .createServer(async (req, res) => { res.writeHead(200, { "Content-Type": "application/json" }); if (req.method === Type_GET) { const { pathname, query } = url.parse(req.url, true); const response = { data: "Hello World!", url: req.url, method: req.method, pathname, query, }; console.log(new Date().toLocaleString(),response) res.end(JSON.stringify(response)); } if (req.method === TYPE_POST) { const data = await resolvePost(req); const response= { data, url: req.url, method: req.method, } console.log(new Date().toLocaleString(),response) res.end( JSON.stringify(response) ); } }) .listen(3000, () => { console.log("server is ready"); })
我们可以使用pm2替换下node,来守护进程,并查看下日志
Pm2和阿里云镜像服务触发器的日志都有,一切ok了
-
优化Watch-image服务,在请求中更新并重启容器
优化watch.js,get走前端模拟测试,post走阿里云镜像的触发器
/** * 请求url参数 * nameSpace 阿里云容器命名空间 * name 阿里云实例名称 * version 版本 * port 服务端口号 * containerName 容器名称 */ const { exec, execSync } = require("child_process"); const http = require("http"); const url = require("url"); const TYPE_POST = "POST"; const Type_GET = "GET"; const resolvePost = (req) => { return new Promise((resolve) => { let chunk = ""; req.on("data", (data) => { chunk += data; }); req.on("end", () => { resolve(JSON.parse(chunk)); }); }); }; http .createServer(async (req, res) => { res.writeHead(200, { "Content-Type": "application/json" }); let shellUrl = ""; // 测试时可以去掉sh,或者在git bash上运行 const { pathname, query } = url.parse(req.url, true); if (req.method === Type_GET) { const response = { result: "Hello World!", url: req.url, method: req.method, pathname, query, }; console.log(new Date().toLocaleString(), response); const path = `${query.nameSpace}/${query.name}:${query.version}`; shellUrl = `sh update.sh ${path} ${query.port} ${query.containerName}`; console.log(shellUrl, "shell"); if ( query.nameSpace && query.name && query.version && query.port && query.containerName ) { execSync(shellUrl); } res.end(JSON.stringify(response)); } if (req.method === TYPE_POST) { const result = await resolvePost(req); const response = { result, url: req.url, method: req.method, port: query.port || 8888, containerName: query.containerName, }; console.log(new Date().toLocaleString(), response); const path = `${result.repository.namespace}/${result.repository.name}:${result.push_data.tag}`; shellUrl = `sh update.sh ${path} ${response.port} ${query.containerName}`; console.log(shellUrl, "shell"); execSync(shellUrl); res.end(JSON.stringify(response)); } }) .listen(3000, () => { console.log("server is ready"); });
添加update.sh脚本,每次接收请求后删除容器->更新镜像->重启容器。同时又预留一些参数变量,可以通过执行脚本的时候传进来。可以使用阿里云触发器传过来的镜像参数,也可以自己模拟。我们将脚本的日志放到log.text中,日志进行叠加。
#!/bin/bash path=$1 port=$2 containerName=$3 username=$4 password=$5 imagepath=registry.cn-beijing.aliyuncs.com/$path # 这里大家可以提前在安装依赖的时候就执行一遍 docker login ,就不用输入密码(docker login --username=waltiu registry.cn-beijing.aliyuncs.com) # 或者在执行脚本的时候输入下密码 # echo -e "---------docker Login--------" # docker login --username=$username --password=$password # 你docker的用户名和密码 echo $(date +%F%n%T) >> log.txt echo 路径:$path 容器名称: $containerName 端口: $port >> log.txt echo -e "---------docker restart--------" >> log.txt echo -e "---------docker Stop--------" >> log.txt docker stop $containerName # 停止容器 echo -e "---------docker Rm--------" >> log.txt docker rm $containerName # 删除容器 echo -e "---------docker Pull--------" >> log.txt docker pull $imagepath # 更新镜像 echo -e "---------docker Create and Start--------" >> log.txt docker run --rm -d -p $port:80 --name $containerName $imagepath # 重启容器 echo -e "---------deploy Success--------" >> log.txt # 本地调试可以打开 # sleep 1000 # echo -e "---------deploy end--------"
下面尝试改下代码,查看后台日志,检测下更改是否生效!
后台日志正常,阿里云和github的构建记录也是有的。这只是重新构建的一种方法,也可以全部流程都在github workflows里完成,比如打包完镜像后,直接访问服务,在workflow里把脚本放到服务器中并执行。
-
ps: 遇到的一些问题
- 创建dockerfile的时候要指定工作区,要不然会报
npm ERR! Tracker "idealTree" already exists
- 服务器执行sh脚本默认时没有权限的,需要添加sh前缀,
sh update.sh
- 像上面这样执行sh脚本是没有日志,我们手动加个日志,echo的内容后加个
>> log.txt
或者>log.txt
- 创建dockerfile的时候要指定工作区,要不然会报
-
-
-
总结:前端项目自动化部署的方法很多,我这里只做个抛砖引玉,大家有其他方法也可以一起沟通,项目中遇到的一些工具放到下面这个github里,欢迎大家查看。 github.com/waltiu/serv…
转载自:https://juejin.cn/post/7232550589964042298