docker自動化部署前端項目實戰一


docker自動化部署前端項目實戰一

本文適用於個人項目,如博客、靜態文檔,不涉及后台數據交互,以部署文檔為例。

思路

利用服務器node腳本,監聽github倉庫webhook push事件觸發post請求,自動拉取最新代碼,再用docker接管項目編譯、部署。

環境

本文使用雲服務器搭建,環境版本:

  • OS:CentOS Linux release 8.2.2004
  • docker:19.03.12
  • node:14.5.0
  • git:2.18.4

雲服務器如果沒有安裝以下環境,需要安裝。

  • docker
  • node
  • pm2
  • git

docker

# 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安裝成功

git

從代碼倉庫拉取最新代碼

yum install git

node

創建js腳本。使用nvm管理node版本,先安裝nvm

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash

將nvm設置環境變量

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 安裝最新版 node

nvm install node

PM2

安裝pm2,服務器后台運行js腳本

npm i pm2 -g

webhook

github 的 webhook 會在當前倉庫觸發某些事件時,發送一個 post 形式的 http 請求

創建webhook

進入github項目倉庫,按下圖順序操作

驗證webhook配置成功,點擊紅色感嘆號右側內容,出現如下請求信息

docker部署

創建Dockfile

在這里,將拉取的項目存放在app目錄下,Dockerfile內容如下,放到服務器根目錄(/root/Dockerfile)

FROM nginx
COPY /app/docsify /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

創建 http 服務器

創建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 
  • build:創建 docker 鏡像
  • .:使用當前目錄下的 Dockerfile 文件,這里在根目錄(/root/)執行
  • -t:使用 tag 標記版本
  • docsify-image:latest:創建名為 docsify-image 的鏡像,並標記為 latest(最新)版本

創建docker容器

docker run -d -p 88:80 --name docsify-container docsify-image:latest
  • run:創建並運行 docker 容器
  • -d: 后台運行容器
    88:80:將當前服務器的 88 端口(冒號前的 88),映射到容器的 80 端口(冒號后的 80)
  • --name:給容器命名,便於之后定位容器
  • docsify-image:latest:基於 docsify-image 最新版本的鏡像創建容器

運行node腳本

pm2 start index.js

test

服務器運行pm2 logs查看index.js打印日志

pm2 logs

本地倉庫修改文件內容,提交遠程倉庫,日志出現deploy success,自動化部署成功。
img

訪問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

這里創建容器省略了容器名,

  • -e:設置環境變量

這時,域名已經配置好了,訪問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:本文中使用的端口號都需要在雲服務器平台創建安全組策略,放開端口

參考

docker + webhook 從零實現前端自動化部署


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM