在 Kubernetes 中使用 Liquibase
1. 概述
在本教程中,我們將探索如何使用Liquibase 、Spring Boot 和 Kubernetes。我們可以使用這些技術構建在啟動時配置數據庫的應用程序。這很強大,但是當我們大規模運行它時,它會產生問題。
2. 服務啟動時會發生什麼
當我們在 Kubernetes 上部署時,我們可以指定我們想要的副本數量。如果我們處理用戶請求,我們應該有多個應用程序實例。
當我們使用 Spring Boot Liquibase 啟動器時,它會在應用程序啟動時嘗試運行數據庫遷移。該服務將等待遷移完成,然後才能準備好處理請求。當 Liquibase 運行遷移時,它首先向數據庫寫入一行以創建鎖。同時運行兩個遷移會導致錯誤,因此 Liquibase 通過只允許一次運行一個遷移來保護架構。
如果我們同時使用 Liquibase 啟動兩個 Spring Boot 應用程序,我們將遇到競爭條件來確定哪一個將運行遷移。其中之一將獲取鎖並運行遷移。另一個將等待,直到鎖可用。遷移完成後,第二個實例將獲取鎖,它會看到遷移已經完成,釋放鎖並繼續。
通常,這不是一個大問題。如果沒有要運行的遷移,服務將快速啟動,因此即使我們啟動許多副本,它們仍然會快速啟動。
問題在於 Spring Boot、Liquibase 和 Kubernetes 的組合。 Kubernetes 旨在管理服務器集群並管理容器內的服務。它將使用探測器來確保一切正常,在節點之間移動 Pod 來管理負載,並替換失敗的服務。此行為非常強大,但在運行數據庫遷移等較長過程時會增加風險。
3. Kubernetes 探針
在 Kubernetes 中,建議使用 Readiness 和 Liveiness 探針來測試服務是否正常工作。最常見的方法是使用 Spring Boot Actuator Health 端點。這可確保服務在準備就緒之前不會接收請求,如果出現問題,新實例將被停止。
我們將這樣定義探針:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: api
template:
metadata:
labels:
app.kubernetes.io/name: api
spec:
containers:
- image: myapp:latest
name: api
livenessProbe:
httpGet:
path: /health
port: http
failureThreshold: 1
periodSeconds: 10
startupProbe:
httpGet:
path: /health
port: http
failureThreshold: 12
periodSeconds: 5
ports:
- containerPort: 8080
name: http
在此示例中,我們希望服務在啟動後 60 秒內準備就緒。如果不是,部署將會失敗,並且新的 Pod 將被終止。通常,這很好,但請記住,在遷移完成之前,Spring Boot 不會報告其運行狀況良好。
大多數遷移速度很快,但有些遷移可能需要很長時間。在大型數據庫中添加大量數據或修改索引可能會很慢。如果在就緒探測時間到期後遷移仍在運行,則服務將被終止而不釋放鎖。
啟動新實例將會失敗,因為鎖從未被釋放。每個實例都會等待獲取數據庫鎖,直到就緒探測時間到期,並且它也會被殺死。我們可以手動刪除數據庫中的鎖,因為我們在遷移運行時可能會遇到問題。遷移已停止,再次啟動可能會導致錯誤或損壞架構,這對生產數據庫不利。
4. 今後如何避免這個問題?
在大多數情況下,刪除鎖可以解決問題,但我們應該小心生產數據庫。我們希望確保遷移能夠完全完成,沒有錯誤或中斷。
首先,必須在類似生產的數據庫上測試遷移。了解遷移可能需要多長時間的唯一方法是在類似的數據庫上運行它。理想情況下,我們希望擁有一個與生產環境類似的臨時數據庫。它應該具有大致相同的數據量、處理能力和模擬流量。顯然,這並不總是可用,並且對於許多組織來說是一種奢侈。
生產數據通常受到嚴密保護,因此我們不能簡單地在臨時數據庫中使用它。這意味著我們需要創建測試數據,因此會有差異。通常,暫存數據更加統一和一致,因為它是由腳本生成的。我們也不可能在臨時數據庫上有相同的負載。我們的生產數據庫處理用戶請求和更新。這意味著臨時數據庫的遷移可能比生產數據庫更快。
儘管我們沒有完美的測試環境,但測試我們的遷移仍然很重要。該測試將告訴我們遷移是否有效以及完成所需的最短時間。然後,我們可以查看分階段製作之間的差異,以估計我們應該考慮多長時間。通常,我們應該假設在生產系統上至少需要兩倍的時間。
5. 遷移選項
我們有多種選擇來降低遷移超時的風險。有些實施起來比較困難,並且會使整體架構更加複雜,但它們增加了遷移成功的安全性。
5.1.延長時間表
最直接的方法就是延長服務中啟動探測的時間。這種方法只需要我們修改啟動探測時間即可完成遷移。啟動探針旨在保護需要一段時間才能準備就緒的服務。
完全移除探針並不是一個好主意,因為如果沒有探針,可能會存在無法檢測到的不同問題。
在此示例中,我們將給服務 10 分鐘的啟動時間。
startupProbe:
httpGet:
path: /health
port: http
failureThreshold: 30
periodSeconds: 20
Kubernetes 將每 20 秒檢查一次。遷移完成後,探測就會成功。如果遷移能夠及時完成,該解決方案將發揮作用。如果遷移測試顯示它應該在 3 分鐘內完成,那麼給它 10 分鐘應該是安全的。
這種方法的優點是可以很容易地實現。存在遷移時間仍可能過長並且過程被中斷的風險。還存在導致服務無法準備就緒的其他問題的風險。長時間超時意味著我們必須等待部署失敗。
5.2.生命週期掛鉤
可以選擇在容器退出時調用一些代碼。每當容器啟動或退出時都可以運行生命週期掛鉤,以提供對流程的細粒度控制。使用生命週期鉤子,我們可以確保釋放鎖,以便下一個容器可以獲取它並繼續。
仍然存在遷移在重要步驟期間終止並在重新啟動時導致錯誤的危險,但它消除了所有未來容器將卡住等待容器的風險。
生命週期掛鉤可以是腳本或 HTTP 請求。我們可以實現一個簡單的鉤子來在退出之前移除鎖。
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
# Replicas, selectors and metadata omitted
template:
spec:
containers:
- image: myapp:latest
name: api
# Probes and ports omitted
lifecycle:
preStop:
exec:
command: ["/bin/sh","-c","/stopservice.sh"]
這種方法將確保鎖被釋放並且下一個容器可以啟動。下一個容器應該能夠從最後完成的步驟開始遷移並正常繼續。
確保鉤子經過充分測試非常重要。該鉤子將至少執行一次,但可以被調用多次。只要容器存在,它也會被調用,所以這可能不是錯誤情況。如果鉤子出現故障,容器將陷入終止狀態並需要手動干預。
5.3.分離容器
如果遷移需要很長時間,則可能值得將數據庫遷移與應用程序代碼分開。此方法的優點是遷移可以花費任意時間來完成,而不會影響服務。
為了分離容器,我們需要修改管道以生成兩個容器,一個包含構建的應用程序“jar”,另一個包含遷移。 Liquibase 有多種運行遷移的方法,包括 CLI、Maven 等。通過創建包含遷移腳本的容器,我們可以將其作為獨立任務運行。
apiVersion: v1
kind: Pod
metadata:
name: db-migrationn
spec:
containers:
- name: mymigration:latest
image: migration
resources:
limits:
memory: "200Mi"
cpu: "700m"
requests:
memory: "200Mi"
cpu: "700m"
這個解決方案比較複雜,因為現在有兩個容器正在運行,但它可以讓我們避免在遷移過程中探針殺死服務的風險。遷移可能需要很長時間才能完成,並且 Pod 將在完成後退出。我們還添加了資源限制,以確保 Kubernetes 在遷移完成之前不會嘗試移動 Pod(請參閱服務質量文檔)
遷移可能需要在服務之前完成,因此我們需要規劃更改的順序。也許可以先發布服務,但這取決於我們的用例。
5.4.使用初始化容器
從單獨的容器運行遷移的另一個選項是使用 InitContainer。 InitContainer 在主容器之前運行,並且不受探測器的影響。我們仍然需要管理QoS,但不需要單獨部署容器。
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
# Replicas, selectors and metadata omitted
template:
spec:
containers:
- image: myapp:latest
name: api
# Probes and ports omitted
initContainers:
- name: mymigration:latest
image: migration
這種方法的優點是 InitContainer 將在我們啟動後立即運行。缺點是我們將遷移耦合到了服務中。這意味著部署需要等待遷移完成。
5.5.單獨運行遷移
最後一個選擇是將數據庫遷移與 Kubernetes 環境完全分離。由於 QoS、節點故障或任何其他需要干預的原因,Kubernetes 始終存在需要將 Pod 從一個節點移動到另一個節點的風險。無法確保遷移始終能夠完成。
為了避免這個問題,我們可以從單獨的服務器運行遷移。它可以是允許遷移長期運行的 CI 服務器。或者,我們可以啟動一個專用服務器,特別是在雲中,然後再將其拆除。
這種方法將涉及更多的規劃和基礎設施,但它確保遷移有時間完全完成。插入大量數據、更改索引或限製表等長任務可能是一個緩慢的過程,並且中斷它很困難,因此為大型任務配備單獨的服務器可能是有意義的。
六,結論
在本文中,我們了解了在 Kubernetes 環境中處理數據庫遷移的幾種方法。在 Kubernetes 中運行數據庫遷移通常沒問題,但有時我們需要更詳細地規劃它。了解遷移大約需要多長時間是了解如何解決問題的關鍵。
有一系列的解決方案,從最簡單的到更複雜的選擇,關鍵因素始終是時間。