如何構建一個高可用的node環境

2020-11-13 15:00:25

如何構建一個高可用的node環境

主要解決問題:

  • 故障恢復
  • 多核利用
  • 多程序共用埠

專案原始碼和更多案例放在github上面,歡迎star.


cluster (叢集)

cluster可以多核監聽同一個埠。實現多程序共用埠,這個在node底層已經做好了。

folk(child_process.fork)方式不能實現多程序共用埠,還需要nginx去做多個埠的負載均衡,一般來說用cluster要好點,folk方式適用與多個程式之間。

建立一個cluster.js檔案

var cluster = require('cluster'); //cluster庫
var os = require('os'); // 獲取CPU 的數量
var numCPUs = os.cpus().length;
var process = require('process') // 管理程序用的

console.log('numCPUs:', numCPUs) // 列印cpu數量 ①
var workers = {};
if (cluster.isMaster) { // 這裡是進入主程序,第一次啟動的時候,執行這裡
    // 主程序分支
    cluster.on('death', function (worker) { ②
        // 當一個工作程序結束時,重新啟動工作程序 delete workers[worker.pid]; 這裡主要是為了讓程式碼即使報錯,也不會影響伺服器執行。故障恢復
        worker = cluster.fork();
        workers[worker.pid] = worker;
    });
    // 初始開啟與CPU 數量相同的工作程序  多核利用 ③
    for (var i = 0; i < numCPUs; i++) {
        var worker = cluster.fork(); // 複製程序,有多少個核,複製多少個子程序,複製的過程會重新執行一遍該檔案(因為是複製程序,程式碼也會複製份在子程序執行)。
        workers[worker.pid] = worker;
    } 
} else { // 這裡是子程序開啟的時候,就是主程序folk之後,會走到這裡。所以這裡會啟動與cpu相同數量的子程序服務。
    // 子程序啟動伺服器,多程序共用3000埠 ④
    var app = require('./app');
    app.use(async (ctx, next) => {
        console.log('worker' + cluster.worker.id + ',PID:' + process.pid)
        next()
    })
    app.listen(3000);
}

// 當主程序被終止時,關閉所有工作程序
process.on('SIGTERM', function () { ⑤
    for (var pid in workers) {
        process.kill(pid);
    }
    process.exit(0);
});


直接看程式碼,這樣看可能看不太懂。我們用一個流程圖來展示。我在上面程式碼標記了①-⑤ ,5個程式碼塊。

在這裡插入圖片描述
這裡看執行情況,啟動後,列印了5次cpu數量(主程序一次,子程序4次),①這段程式碼執行了5次。

然後我們通過存取localhost:3000,得到當前存取的是第三子程序。

在這裡插入圖片描述


更優雅的部署node (pm2)

  • 內建負載均衡(使⽤用Node cluster 叢集模組、子程序)
  • 執行緒守護,keep alive
  • 0秒停機過載,維護升級的時候不不需要停機.
  • 現在 Linux (stable) & MacOSx (stable) & Windows (stable).多平臺⽀支援
  • 停⽌止不不穩定的程序(避免⽆無限迴圈)
  • 控制檯檢測 https://id.keymetrics.io/api/oauth/login#/register
  • 提供 HTTP API

命令列部署方法:

 
npm install -g pm2
pm2 start app.js --watch -i 2 // watch 監聽⽂檔案變化
// -i 啟動多少個範例例
pm2 stop all
pm2 list
pm2 start app.js -i max # 根據機器器CPU核數,開啟對應數⽬目的程序
 

在這裡插入圖片描述

process.yml檔案部署方法:

 
apps:
  - script : app.js
    instances: 2
    watch  : true
    env    :
      NODE_ENV: production
      

執行pm2 start process.yml

在這裡插入圖片描述
pm2設定為開機啟動 pm2 startup

可以看到兩種方式的效果是一樣的,但是大多會選擇以yml檔案來啟動。


docker概念

  • Docker 屬於 Linux 容器的一種封裝,提供簡單易用的容器使用介面
  • 1)提供一次性的環境。比如,本地測試他人的軟體、持續整合的時候提供單元測試和構建的環境。
  • 2)提供彈性的雲服務。因為 Docker 容器可以隨開隨關,很適合動態擴容和縮容。
  • 3)組建微服務架構。通過多個容器,一臺機器可以跑多個服務,因此在本機就可以模擬出微服務架構。
  • 4)image可以建立容器,每個容器有自己的容器埠,我們需要把它對映到主機埠
  • 5)Docker Compose是 docker 提供的一個命令列工具,用來定義和執行由多個容器組成的應用。使用 compose,我們可以通過 YAML 檔案宣告式的定義應用程式的各個服務,並由單個命令完成應用的建立和啟動。

特點

  • ⾼高效的利利⽤用系統資源
  • 快速的啟動時間
  • 一致的運⾏行行環境
  • 持續交付和部署
  • 更更輕鬆的遷移

對⽐傳統虛擬機器器總結

特性容器器虛擬機器器
啟動秒級分鐘級
硬碟使⽤一般為 MB一般為 GB
效能接近原⽣弱於
系統支援量單機⽀持上千個容器一般⼏十個

三個核⼼心概念

  • 映象
  • 容器器
  • 倉庫

和pm2類似,docker也有兩種方式啟動,一種是命令列方式,一種是Dockerfile客製化映象方式。

DockerFile引數 :

FROMMAINTAINERRUNADD&COPYWORKDIRVOLUMEEXPOSE
它依賴什麼映象維護者資訊執行命令列命令複製檔案到指定路徑(ADD能解壓)指定工作目錄目錄掛載容器的埠

常用的doker命令:

  • 檢視docker版本:docker version

  • 顯示docker系統的資訊:docker info

  • 檢索image:docker search image_name

  • 下載image : docker pull image_name

  • 已下載映象列表: docker images

  • 刪除映象: docker rmi image_name

  • 啟動容器:docker run image_name

docker構建一個nginx伺服器

  1. 拉取官⽅方映象
   拉取官⽅方映象 
   docker pull nginx
   檢視映象
   docker images nginx
   啟動映象
   docker run -p 80:80  -d nginx
   檢視程序
   docker ps
   docker ps -a // 檢視全部
   停⽌
   docker stop id
   刪除映象
   docker rm id
  1. Dockerfile客製化映象
#Dockerfile
FROM nginx:latest
RUN echo '<h1>Hello, docker</h1>' > /usr/share/nginx/html/index.html
 
# 客製化映象
docker build -t mynginx .
# 運⾏
# -d 守護態運⾏
docker run -p 80:80 -d mynginx

Docker-Compose

Docker-Compose專案是Docker官方的開源專案,負責實現對Docker容器叢集的快速編排。
Docker-Compose將所管理的容器分為三層,分別是工程(project),服務(service)以及容器(container)。Docker-Compose執行目錄下的所有檔案(docker-compose.yml,extends檔案或環境變數檔案等)組成一個工程,若無特殊指定工程名即為當前目錄名

docker-compose主要是可以集合多個服務,一起執行。比如一個專案有(前端、後臺、資料庫、nginx)4個服務需要去啟動,如果單獨去啟動的話,我們需要執行4次docker。這裡我們能通過docker-compose,一起執行。

案例:nginx+node+pm2後臺

nginx:

在nginx資料夾裡,裡面建立個conf.d資料夾。新增一個docker.conf檔案

## nginx/conf.d/docker.conf

server {
    listen       80;
    location / {
        root   /var/www/html;
        index  index.html index.htm;
    }
    location ~ \.(gif|jpg|png)$ {
        root /static;
        index index.html index.htm;
    }
    location /api {
            proxy_pass  http://127.0.0.1:3000;
            proxy_redirect     off;
            proxy_set_header   Host             $host;
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
    }
}

node:

 // app.js
const Koa = require('koa')
const app = new Koa()
app.use(ctx => {
    ctx.body = 'Hello Docker'
})
app.listen(3000, () => {
    console.log('app started at http://localhost:3000/')
})

// process.yml

apps:
  - script : server.js
    instances: 2
    watch  : true
    env    :
      NODE_ENV: production


// Dockerfile

#Dockerfile
#制定node映象的版本
FROM node:10-alpine
#移動當前目錄下面的檔案到app目錄下
ADD . /app/
#進入到app目錄下面,類似cd
WORKDIR /app
#安裝依賴
RUN npm install
#對外暴露的埠
EXPOSE 3000
#程式啟動指令碼
CMD ["pm2-runtime", "start",  "process.yml"]

然後構建docker-compose.yml

## docker-compose.yml

version: '3.1'
services:
  app-pm2:
      container_name: app-pm2
      #構建容器
      build: ./node 
      ports:
        - "3000:3000"
  nginx:
    restart: always
    image: nginx
    ports:
      - 80:80
    volumes:
      - ./nginx/conf.d/:/etc/nginx/conf.d/  #本地組態檔寫入到nginx設定目錄
      - ./www/:/var/www/html/ 

然後建立一個www資料夾裡面放一個靜態html檔案

//index.html

hello web!! 

可以看到在docker-compose檔案裡面,我們執行了兩個映象,一個是打包後的node名為app-pm2的映象,一個是nginx的映象。
同時我們把nginx的組態檔從本地寫到了docker執行的nginx目錄裡面。現在我們來看執行效果:

輸入:docker-compose up -d 後臺啟動命令。

在這裡插入圖片描述
可以看到,兩個容器都被建立。現在我們先存取80埠(nginx對映在80埠)。

在這裡插入圖片描述
存取成功,成功存取到www/index.html

然後我們存取/api路徑,看是否能存取到node伺服器。

在這裡插入圖片描述
存取成功。這裡一個docker-compose的案例就成功執行了。裡面用了nginx反向代理3000埠介面到80埠/api路徑,同時用了pm2去啟動node介面伺服器。

專案搭建好之後,就需要持續整合了,這裡具體請參考我之前寫的文章。Gitlab-CI/CD雲端構建釋出