docker+webhook自動化部署實踐

自動化部署是一門最大限度簡化不必要工作的工程藝術,通過自動化部署,可以實現互聯網業務的產品直達DevOps一站式服務,極大的解放了生產力。

本文將使用docker+webhook以及shell實現一個小型的單機自動化部署系統。

業務需求

一切技術都離不開業務,先看下我們的系統架構:

整個系統採用前後端分離,前後胎通過RESTful API交互,並依此,衍生出了2個項目:

1、api(由koa編寫的RESTful API)

2、core(Web前端)

由於前端為SPA(Single Page Application,單頁面應用),所以還需要一個項目專門用來存儲core經過打包之後的文件,這些文件最終將會同步到伺服器上,這個項目叫:core-dist

此外,我們還需要一個負責處理自動化的腳本項目,我們叫它:deploy

最後,我們還需要負責同步開發機和伺服器代碼的webhook,我們就叫它:webhook

將這些項目依次在Github 上創建,最終結果如下:

目前的部署具體需求是:

  1. 啟動三個docker,一個docker運行web前端頁面,一個docker運行api,另一個運行mongodb資料庫;
  2. 向github提交代碼後伺服器自動拉取最新代碼,實時更新到各個容器,包括webhook和deploy自身;
  3. 域名全部自動部署至nginx配置中;
  4. 伺服器環境自動配置。

現在我們開始吧。

構建docker

核心業務在docker上,所以我們從docker開始,我們將所有部署相關shell寫在deploy中,最終deploy的文件結構如下:

.n├── README.mdn├── apin ├── Dockerfilen └── deploy.shn├── deploy.shn├── domain.shn├── domain_api_deploy.shn├── domain_pix_deploy.shn├── domain_tpls_deploy.shn├── mongodbn └── deploy.shn└── coren ├── Dockerfilen └── deploy.shn

可以看到deploy項目下有三個文件夾;api、core和mongodb,這三個文件夾代表了三個核心業務docker,每個文件夾下,先從最簡單的core開始吧,它放置的是打包之後的web前端靜態資源。

core

Dockerfile

FROM nginxnMAINTAINER ivy [email protected] rm -rf /usr/share/nginx/htmlnENTRYPOINT cd /usr/share/nginx/html && nginx && /bin/bashn

這個Dockerfile非常簡單,就是一個最基礎的nginx容器。

deploy.sh

這個文件用來停止、刪除、構建和啟動容器。

docker stop core && docker rm corendocker build -t core .ndocker run -itd -p 7777:80 -v /mnt/pix/core-dist:/usr/share/nginx/html -w /usr/share/nginx/html --name="core" coren

可以看到我們掛載了宿主機的/mnt/pix/core-dist的目錄到容器內的nginx目錄內。/mnt/pix/core-dist內是打包好的Web前端靜態資源,我們使用docker部署遵循一個原則:

docker內不存儲任何數據,所有數據都在宿主機上,這些數據包括程序代碼和用戶數據。

待會我們會說到怎麼往宿主機上同步代碼。

接下來是api和資料庫docker,這兩個需要放在一起說。

先看api:

Dockerfile

FROM node:7.10nnMAINTAINER ivy [email protected]nnRUN apt-get updatenRUN apt-get install zip -ynn#use cnpmnRUN npm install -g cnpm --registry=https://registry.npm.taobao.orgnnRUN cnpm install -g supervisorn# 修改時區nRUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtimennENTRYPOINT cd /var/www/api && supervisor index.js && /bin/bashn

我們用到了zip這個命令和supervisor來運行node程序(沒用pm2是因為覺得暫時沒必要),其它都是基本的node 7.0容器信息。

deploy.sh

docker stop api && docker rm apindocker build -t api .ndocker run -itd -p 7776:3000 -v /mnt/pix/api:/var/www/api/ -v /mnt/pix/templates:/var/www/api/templates -w /var/www/api --name="api" --link mongodb:mongo apin

可以看到我們將宿主機上的/mnt/pix/api(api源代碼)暴露至了容器中的/var/www/api文件夾,還用到了--link命令,link命令可以連接我們的mongodb。

mongodb

mongodb不需要重新構建鏡像,直接使用官方的mongodb鏡像即可。

deploy.sh

docker rmi mongodbndocker run -v /mnt/pix/mongodb/db:/data/db -d --name="mongodb" mongo:3.2n

我們掛載了一個目錄,將容器內的數據目錄掛載到了宿主機上,保證了數據的安全性(docker本身是無狀態的)。

至此,我們就完成了docker容器的自動化構建,第一個需求完成了。

運行順序中,必須先啟動mongodb再啟動api,core可以隨意。

初始化伺服器環境

初始化伺服器環境非常容易,只要寫一個shell腳本即可,以下是這個項目的初始化腳本(處於項目安全考慮,敏感內容已***代替):

mkdir /mnt/pixnmkdir /mnt/pix/mongodbnmkdir /mnt/pix/mongodb/dbnn#templatesngit clone *** /mnt/pix/templatesnn#pix-distngit clone *** /mnt/pix/pix-distnn#pix-apingit clone *** /mnt/pix/apincd /mnt/pix/api && npm installnn#docker imagesnsh ./mongodb/deploy.shnsh ./api/deploy.shnsh ./pix/deploy.shnn#webhookngit clone *** /mnt/pix/webhookncd /mnt/pix/webhook && npm installnpm2 start /mnt/pix/webhook/index.js --name pix_webhooknn#配置域名nsh ./domain.shn

還隱掉了很多軟體包的安裝,這些視項目決定,就沒必要寫出來了。

至此,我們完成了伺服器環境的初始化,注意上面的代碼中有一個webhook,這個是用來做自動化同步代碼的。

webhook

webhook是github提供的功能,這個功能可以在github伺服器接收到客戶端發來的push消息後,再向伺服器發送一個api,這個api由用戶自定義,代碼同步也在這個api里做。

本項目的webhook由nodejs寫成,非常短,代碼如下:

var http = require(http);nvar exec = require("shelljs").exec;nnconst PORT = 6474;nnconsole.log(listening port at: + PORT);nnconst cmds = { n /api/build: cd /mnt/pix/api && git pull,n /pixweb: cd /mnt/pix/pix-dist && git pull,n /webhook: cd /mnt/pix/webhook && git pull && pm2 restart pix_webhook,n /deploy: cd ~/pix/deploy && git pull,n /templates: cd /mnt/pix/templates && git pulln}nnvar deployServer = http.createServer(function(request, response) {nn var inCMDs = false,n cmd = ; nn for(var key in cmds) {n if(key == request.url) {n inCMDs = true;n cmd = cmds[key];n break;n } n }nn if(inCMDs) {nn exec(cmd, function(err, out, code) {n if (err instanceof Error) {n response.writeHead(500);n response.end(Server Internal Error.);n throw err n } n process.stderr.write(err.toString());n process.stdout.write(out.toString());n response.writeHead(200);n response.end(Deploy Done.);n }) n

主要看第8-14行即可,鍵是url,後面的值是要執行的shell命令。

比如這個項目運行在localhost:6474埠,那麼假如我要將api的代碼同步至伺服器,我只要發送一個post請求到localhost:6474/api/dev即可,注意在實際使用中,localhost應使用外網ip,因為這個請求是github發送的。

再回過頭來看我們部署webhook的shell代碼:

#webhookngit clone *** /mnt/pix/webhookncd /mnt/pix/webhook && npm installnpm2 start /mnt/pix/webhook/index.js --name pix_webhookn

使用了pm2啟動webhook,至此,還沒完,還差最後一步,我們還未將url配置到github上。

打開任意的項目,按照如圖所示配置即可。

配置完成後,我們推送到github上的每一行代碼,都將自動同步到伺服器上。

至此,我們完成了第二個需求。

現在只剩第三個需求,即nginx域名需求。

配置nginx域名

這個就非常簡單了,隨便舉個例子即可:

sudo tee /etc/nginx/conf.d/api.****.cn.conf <<- EOFnupstream nodejs__upstream_pxi_core {n server 127.0.0.1:7476;n keepalive 64;n}nnserver {n listen 80;n server_name www.api.****.cn api.****.cn;n #access_log /var/log/nginx/moiveme.log;n location / {n proxy_set_header X-Real-IP $remote_addr;n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;n proxy_set_header Host $http_host;n proxy_set_header X-NginX-Proxy true;n proxy_set_header Connection "";n proxy_http_version 1.1;n proxy_pass http://nodejs__upstream_pxi_core;n }n}nEOFn

通過upstream代理到對應的docker埠即可。

還有最後一項工作要做,記得嗎?前端的部署是分開的,源代碼同步到github上,打包之後的代碼同步到伺服器上。

前端單獨部署

npm run buildncp favicon.icon distnzip dist.zip -r distnrm -rf /var/www/pix/pix-dist/*nmv dist.zip /var/www/pix/pix-distncd /var/www/pix/pix-dist && git pull && unzip dist.zip && cd dist && mv * ../ && cd ../ && rm -rf dist.zip && rm -rf dist && git add . && git commit -a -m constrution && git push -u origin mastern

單獨部署也很簡單,思路就是寫一個shell將打包之後的代碼放置到core-dist項目下然後push到github中,上面的代碼就乾的這事,體力活,沒啥技術含量。

至此,我們完成了所有需求,一套簡單的自動化部署系統也完成了,接下來,盡情的寫代碼吧。

自動化部署真的是一門藝術。


推薦閱讀:

面試之Event Loop,nextTick()和setImmediate()區別分析
深入 Promise(三)——命名 Promise
libuv漫談之線程
Egg.js 中 GraphQL 小試牛刀
Node.js 實現 Hot Reload

TAG:Docker | 运维 | Nodejs |