大家好,我是 Yubin
今天要介紹 Fastify App 部屬的相關實踐及注意事項。
NodeJS 的標準函式庫中有內建著 Web Server 的框架 (http module)。
不像 PHP 或 Python 等語言需要特定支援該語言的 Web Server 或設定 CGI Gateway。
使用 NodeJS 可以直接寫處理 HTTP Request 的程式,可以很容易的在程式裡面處理多個 domain 來的 Request 或聽多個 Port (http/https)。
然而,Fastify 官方團隊 強烈不建議 這樣的作法。
因為會增加不必要的複雜性,也會讓應用程式的功能失焦,更可能會妨礙到水平擴展的進行。
所以使用 Reverse Proxy 來搭配部屬的工作。
不清楚反向代理 (Reverse Proxy) 的朋友可以參考這篇。
或 Nginx 官方網站的文件: What Is a Reverse Proxy Server?。
這篇文章也闡述了為什麼 NodeJS 伺服器應該搭配反向代理。
使用 Reverse Proxy,特別是當你有下列的需求:
要架設 Reverse Proxy 有多種選擇,這邊使用 nginx 來操作。
nginx 的安裝可以參考官方文件。
編輯 nginx config:
upstream fastify_app {
server 10.0.0.1:80;
server 10.0.0.2:80;
server 10.0.0.3:80;
}
server {
listen 80;
listen [::]:80;
server_name yubin.tw;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name yubin.tw;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/private.pem;
}
location / {
# more info: https://nginx.org/en/docs/http/ngx_http_proxy_module.html
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://fastify_app;
}
上述 config 可以參考以下架構閱讀。
Nginx 作為 Proxy Server,把來自 http 且 Request 的目標 host 是 yubin.tw
的都 redirect 到 https。迫使 client 端使用 https 來與我們進行互動。
https 用的 SSL 證書也是定義在這邊。
然後把所有 request 都送進我們定義的 fastify_app
,也就是內部三台 Fastify Server,IP 位址是 10.0.0.1, 10.0.0.2, 10.0.0.3。
預設的分流模式為 Round Robin。如果想要更改預設的分流模式,可以參考這篇。
根據 The Twelve Factors 的描述,我們應該把我們的設定以環境變數的方式提供給程式做讀取。
Store config in the environment
在 NodeJS 中,要讀取環境變數可以透過 process.env
。
const port = process.env.FASTIFY_PORT
但是每個 config 都從環境變數讀,要設定或更改環境變數可能很麻煩。
這邊可以使用 dotenv 這個工具來協助我們進行開發。
dotenv 是 NodeJS 的 Module,透過 npm 就可以安裝:
npm install -D dotenv
接著我們新增一個叫做 .env
的檔案:
FASTIFY_APP=8888
.env
的格式為一行一筆環境變數,由等號(=
)分隔,前面是環境變數的名稱,後面是值。
定義好後,在程式中使用 dotenv 的 .config()
方法:
import * as dotenv from 'dotenv'
dotenv.config()
const port = process.env.FASTIFY_PORT
dotenv.config()
這個方法會去找你有沒有 .env
或相關檔案,有的話會把 .env
中讀到的內容,放進 process.env
中,所以程式內就可以透過 process.env.XXX
的方式拿到環境變數的值。
要注意的是,dotenv 並不會真的去修改環境變數,他只是把 .env
描述的東西,放進 process.env
這個物件裡面。
更重要的是,每個利用 process.env
讀到的環境變數,他的型態都是 string | undefined
。
使用上別忘了進行環境變數的型態轉換以及資料驗證。
程式部屬上去之後,就會有維運、監控的需求。
Grafana + Prometheus + Alertmanager,是常見的 monitor stack 之一。
使用 Prometheus 蒐集 metrics,可以安裝 fastify-metrics 這個 Plugin。
安裝 fastify-metrics
npm i fastify-metrics
透過 server.register()
進行註冊,並設定 endpoint 為 /metrics
(Prometheus scrape job 預設 path)。
import fastifyMetric from 'fastify-metrics'
server.register(fastifyMetric, { endpoint: '/metrics' })
這個 plugin 會幫我們把 App 的 CPU、Memory 等資訊轉換為 Prometheus 的資料格式。
Log 常見的是使用 Elasticsearch + Fluentd + Kibana,合稱 EFK。
Logging 的動作,不要使用 console.log
來進行,而是使用內建的 Pino。
可以參考 Fastify101: Logging。
Trace 常見的是使用 Open Telemetry 這個 Trace 的標準。
Open Telemetry 也對 NodeJS 有良好的支援,可以參考官網的教學。
或這個 GitHub Repo 的完整範例。
程式執行的過程中,要如何知道現在的健康狀態。
絕對不是程式還在執行就是健康,可能 Database 的連線已經斷了,也可能某個重要的外部服務已經存取不到。
我們可以準備一個 Endpoint,來檢查目前應用程式的狀態。
這邊可以使用 fastify-custom-healthcheck 這個 plugin。
使用 npm 進行安裝。
npm i fastify-custom-healthcheck
接著透過 server.register()
進行註冊,並定義 Endpoint。
import customHealthCheck from 'fastify-custom-healthcheck'
server.register(customHealthCheck, {
path: '/health'
})
就可以多一個 Health Check 用的 Endpoint。
當你需要在 Health Check 的時候一並執行其他檢查項目,可以利用 .addHealthCheck
來操作。
server.register(customHealthCheck, {
path: '/health'
}).after(() => {
server.addHealthCheck('mongo', async () => {
return checkMongoConnection() // return Promise
})
})
在 /health 上就會多一個欄位:
如果有多個項目需要做檢查,只要透過 .addHealthCheck
新增檢查的 function 就可以了。
如果有一個檢查沒有通過,Endpoint 就會回傳 500
的狀態碼,所有檢查都通過則回傳 200
。
在現在這個 Cloud Native 當道的年代,應用程式部屬到 Kubernetes 是非常常見的。
我們在撰寫 deployment.yaml
的時候,有沒有良好的定義 Probe 也是非常重要的。
Kubernetes 有三個 Probe:
Liveness Probe
告訴 Kubernetes 什麼時候要重啟 Container。Readiness Probe
告訴 Kubernetes 能不能把流量送進該 Container。Startup Probe
告訴 Kubernetes 這個 Container 是不是已經啟動完成。Probe 的操作,可以使用 cmd 或 http 或 tcp 或 gRPC 的方式來戳。
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
readinessProbe:
httpGet:
path: /health
port: 8888
initialDelaySeconds: 5
periodSeconds: 5
使用 tcp、gRPC 可以參考官方文件。
良好設計這些 Probe,告訴 Kubernetes 現在應用程式的狀態是非常重要的。
本文介紹了一些部屬相關的東西,由於我們的程式都會以 Container 的形式存在,所以怎樣把 image 包的好包的洽當也是很重要的。如果不清楚的朋友可以參考 Fastify101: Containerization。
開發是一回事,部屬之後又是另一個世界。也是你寫的東西真正開始產生價值的時候,千萬不得馬虎。