本文適用於我的項目,如博客、靜態文檔,不涉及後臺數據交互,以部署文檔爲例。html
利用服務器node腳本,監聽github倉庫webhook push事件觸發post請求,自動拉取最新代碼,再用docker接管項目編譯、部署。前端
本文使用雲服務器搭建,環境版本:node
雲服務器若是沒有安裝如下環境,須要安裝。linux
# Step 1: 安裝必要的一些系統工具 sudo yum install -y yum-utils # Step 2: 添加軟件源信息,使用阿里雲鏡像 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # Step 3: 安裝 docker-ce sudo yum install docker-ce docker-ce-cli containerd.io # Step 4: 開啓 docker服務 sudo systemctl start docker # Step 5: 運行 hello-world 項目 sudo docker run hello-world
不出意外,出現==hello world==,docker安裝成功nginx
從代碼倉庫拉取最新代碼git
yum install git
建立js腳本。使用nvm管理node版本,先安裝nvmgithub
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
將nvm設置環境變量web
export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
經過 nvm 安裝最新版 nodedocker
nvm install node
安裝pm2,服務器後臺運行js腳本npm
npm i pm2 -g
github 的 webhook 會在當前倉庫觸發某些事件時,發送一個 post 形式的 http 請求
進入github項目倉庫,按下圖順序操做
驗證webhook配置成功,點擊紅色感嘆號右側內容,出現以下請求信息
在這裏,將拉取的項目存放在app目錄下,Dockerfile內容以下,放到服務器根目錄(/root/Dockerfile)
FROM nginx COPY /app/docsify /usr/share/nginx/html/ EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
建立index.js,放到服務器根目錄(/root/index.js)
const http = require("http") const { execSync } = require("child_process") const fs = require("fs") const path = require("path") // 遞歸刪除目錄 function deleteFolderRecursive(path) { if (fs.existsSync(path)) { fs.readdirSync(path).forEach(function (file) { const curPath = path + "/" + file; if (fs.statSync(curPath).isDirectory()) { // recurse deleteFolderRecursive(curPath); } else { // delete file fs.unlinkSync(curPath); } }); fs.rmdirSync(path); } } const resolvePost = req => new Promise(resolve => { let chunk = ""; req.on("data", data => { chunk += data; }); req.on("end", () => { resolve(JSON.parse(chunk)); }); }); http.createServer(async (req, res) => { console.log('receive request') console.log(req.url) if (req.method === 'POST' && req.url === '/') { const data = await resolvePost(req); const projectDir = path.resolve(`./app/${data.repository.name}`) deleteFolderRecursive(projectDir) // 拉取倉庫最新代碼 execSync(`git clone https://github.com/BKHole/${data.repository.name}.git ${projectDir}`, { stdio: 'inherit', }) // 建立 docker 鏡像 execSync(`docker build . -t ${data.repository.name}-image:latest`, { stdio: 'inherit', }) // 銷燬 docker 容器 execSync(`docker ps -a -f "name=^${data.repository.name}-container" --format="{{.Names}}" | xargs -r docker stop | xargs -r docker rm`, { stdio: 'inherit', }) // 建立 docker 容器 execSync(`docker run -d -p 88:80 --name ${data.repository.name}-container ${data.repository.name}-image:latest`, { stdio: 'inherit', }) console.log('deploy success') res.end('ok') } }).listen(3000, () => { console.log('server is ready') })
解析,
建立docker鏡像
docker build . -t docsify-image:latest
建立docker容器
docker run -d -p 88:80 --name docsify-container docsify-image:latest
88:80:將當前服務器的 88 端口(冒號前的 88),映射到容器的 80 端口(冒號後的 80)
pm2 start index.js
服務器運行pm2 logs查看index.js打印日誌
pm2 logs
本地倉庫修改文件內容,提交遠程倉庫,日誌出現deploy success,自動化部署成功。
訪問http://47.108.82.91:88,記得在雲服務器上放開訪問端口號
在擁有域名的前提下,優先使用域名訪問。爲何?域名固然比IP+端口號好記,且美觀。
這裏爲了方便控制,使用nginx-proxy鏡像來操做,以下操做docker會自動去鏡像倉庫拉取,建議服務器80端口給nginx使用,方便之後增長域名和訪問端口監聽。
docker run -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock:ro jwilder/nginx-proxy
而後綁定域名到新建容器,這裏使用個人二級域名。
docker run -e VIRTUAL_HOST=libotao.nofoo.cn docsify-image
這裏建立容器省略了容器名,
這時,域名已經配置好了,訪問http://libotao.nofoo.cn能夠看到效果。
前面每次提交內容到github,服務器都會從新拉取最新代碼,新建image,銷燬container,新建container,訪問內容纔會更新,爲了實現自動化,須要改造一下上面的index.js腳本,
const http = require("http") const { execSync } = require("child_process") const fs = require("fs") const path = require("path") // 遞歸刪除目錄 function deleteFolderRecursive(path) { if (fs.existsSync(path)) { fs.readdirSync(path).forEach(function (file) { const curPath = path + "/" + file; if (fs.statSync(curPath).isDirectory()) { // recurse deleteFolderRecursive(curPath); } else { // delete file fs.unlinkSync(curPath); } }); fs.rmdirSync(path); } } const resolvePost = req => new Promise(resolve => { let chunk = ""; req.on("data", data => { chunk += data; }); req.on("end", () => { resolve(JSON.parse(chunk)); }); }); http.createServer(async (req, res) => { console.log('receive request') console.log(req.url) if (req.method === 'POST' && req.url === '/') { const data = await resolvePost(req); // 項目放在服務器app目錄下 const projectDir = path.resolve(`./app/${data.repository.name}`) deleteFolderRecursive(projectDir) // 拉取倉庫最新代碼 execSync(`git clone https://github.com/BKHole/${data.repository.name}.git ${projectDir}`, { stdio: 'inherit', }) // 建立 docker 鏡像 execSync(`docker build . -t ${data.repository.name}-image:latest `, { stdio: 'inherit', }) // 銷燬 docker 容器 execSync(`docker ps -a -f "name=^${data.repository.name}-container" --format="{{.Names}}" | xargs -r docker stop | xargs -r docker rm`, { stdio: 'inherit', }) // 建立 docker 容器 execSync(`docker run --name ${data.repository.name}-container -e VIRTUAL_HOST=libotao.nofoo.cn ${data.repository.name}-image:latest`, { stdio: 'inherit', }) console.log('deploy success') res.end('ok') } }).listen(3000, () => { console.log('server is ready') })
修改後覆蓋以前存放的index.js,而後重啓腳本。
pm2 restart index.js
配置完成後,之後每次提交github,都會自動更新,訪問域名就會看到最新的內容。
note:本文中使用的端口號都須要在雲服務器平臺建立安全組策略,放開端口