弦而時習之

Rails 部署實踐 - 健康檢查

在過去傳統的部署方式中,我們大多是單機的方式部署,因此網站如果發生障礙連不上,那麼就可以視為無法使用。

然而當我們有多個網站節點的時候,大多會使用負載平衡(Load Balancer)來將流量分散到不同的節點上,這個時候哪一個節點可以接受流量,就會仰賴健康檢查(Health Check)來回報狀態。

健康檢查的目的

健康檢查的目標很單純,就是反應目前是否能夠正常提供服務(或者有限的服務)也因此,我們很多時候會把「首頁」作為檢康檢查的查核點,這樣的做法是很容易造成問題的。

以開放原始碼的 GitLab 為例子,在 HealthCheckController 之中,會檢查的項目涵蓋資料庫、Redis以及 Gitaly(跟 Git 功能相關的伺服器)這三個項目剛好涵蓋了 GitLab 要正常提供服務的核心功能,也就是 Web 的正常使用、背景作業(Sidekiq)的運作以及 Git 倉庫的管理。

簡單來說,當上述幾個項目缺少一個 GitLab 就無法提供他該有的功能,那麼這個節點就不應該被存取,即使我們能夠正常開啟 GitLab 的首頁,透過這樣的資訊我們也能夠察覺到系統的異常,畢竟 Redis 連不上這類狀況,我們有很高的機率是還能開啟首頁的。

加入健康檢查

得益於 GitLab 的原始碼,我們可以很簡單的實作健康檢查,最簡單的方式就是要確保資料庫的連線正常,像是我們在使用快取(Cache)機制的時候,即使資料庫斷線也是有可能正常顯示首頁的,然而這個狀態我們是不能正確使用我們的服務,而出現 5xx 類型的錯誤訊息。

先增加 Route 讓我們可以用 /status 或者任何大家偏好的路徑呼叫健康檢查的 Controller(控制器)。

1
2
3
4
5
6
# config/routes.rb

Rails.application.routes.draw do
 get '/status', to: 'health#index'
 # ...
end

接下來實作會檢查資料庫的 HealthController

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class HealthController < ActionController::API
  def index
    return render json: { status: :error }, status: :service_unavailable unless health?

    render json: { status: :ok }
  end

  private

  def health?
    ActiveRecord::Base.connection.execute('SELECT 1 as ping')&.first&.[]('ping')&.to_s == '1'
  end
end

我們參考 GitLab 的方式使用 SELECT 1 來確認連線以及 SQL 操作正常,並且在異常的時候以 503 Service Unavailable 狀態碼進行回應,這樣一來大多數負載平衡的工具,就會知道目前這個節點是無法提供服務的。

我有另外實作一個叫做 Liveness 的 Gem 來幫助我實現這些常見的健康檢查機制,使用上也非常容易只需要透過設定檔定義需要檢查的項目即可,推薦大家使用。

健康檢查的妙用

健康檢查除了可以幫助我們知道服務的狀態、節點是否可以存取之外,在我們使用容器技術的時候可以搭配 Docker 的 HEALTHCHECK 命令,來幫助我們自動重啟。

一般來說,我們在健康檢查發生錯誤時大多數會有外部服務(像是資料庫)和自身兩種狀況,假設我們運行容器的主機正常,然而我們的應用程式(Rails)卡死,此時容器的健康檢查就會持續的出錯,進而可以觸發一些機制「重新啟動」如果不是很嚴重的問題,我們就可以減少需要人工介入的處理。

也因此,透過健康檢查我們可以幫助負載平衡合理的分配流量、避免使用者遭遇無法自行排除的錯誤,以及自動將有問題的容器重新啟動,在部署的規劃上加入這一條也能有助於像是滾動更新這類服務的規劃,因此是非常建議導入使用的。

我在方格子還有一篇關於熔斷機制的文章,也可以跟這篇文章一起閱讀,來思考有了健康檢查之後,要如何保護整個系統的穩定。


如果想在第一時間收到更新,歡迎訂閱弦而時習之在這系列文章更新時收到通知,如果有希望了解的知識,可以利用Rails 部署實踐回饋表單告訴我。

電子報

留言