首頁 > 科技

針對資料庫變更的持續整合與交付

2021-06-04 10:05:53

近年來,一種被稱為DevOps的軟體工程文化已悄然在許多組織中流行起來。它旨在統一軟體開發(Dev)和IT運營(Ops),並且通過持續整合(CI)和持續交付(CD)兩個主要概念,在軟體工程的實踐中倡導自動化。如今,許多應用開發團隊都能夠從此類敏捷開發的實踐中實現:頻繁的軟體交付,儘早地收到客戶的反饋,擁有組織內跨職能的團隊,更快地讓產品面市,以及保持客戶的滿意度。

不過傳統的資料庫手動變更管理過程,正在逐漸成為持續交付的瓶頸。對此,本文將重點討論如何將其簡化到應用程式碼的統一交付管道中。

持續整合

作為敏捷開發過程的核心原則之一,持續整合強調的是確保由團隊內多個成員所開發出的程式碼,能夠順暢實現整合,進而避免出現各自為政的「整合地獄」。它主要涉及到獨立且自動化的構建、以及自動化的測試。可以說,持續整合促進了以測試為驅動的開發,以及對版本控制系統的基線、主分支、主幹(trunk)的頻繁「原子性」提交的實踐。

圖1:典型的持續整合過程

如上圖所示,開發人員一旦將程式碼簽入源控制系統,就會觸發在持續整合伺服器中的配置構建作業。該作業將從版本控制系統中籤出程式碼,進行構建,執行測試,並將生成的工件(如jar檔案),部署到工件儲存庫(artifact repository)中。一部分定時觸發的CI作業,則會將程式碼部署到開發環境中,將詳細資訊推送到靜態分析工具中,對已部署的程式碼、或團隊認為實用的自動化過程進行系統測試,進而確保程式碼庫的運行狀況良好。同時,敏捷團隊有責任確保上述自動化流程在出現任何失敗時,能夠暫緩程式碼的提交,直至自動化的構建被修復。

持續交付

持續交付除了需要確保軟體系統中的不同模組能夠被始終整合之外,還要確保程式碼能夠始終被部署到生產環境中。這意味著,系統除了擁有自動化的構建和測試套件之外,還具有自動化的交付過程。通常,我們只需單擊按鈕,便可在幾分鐘之內完成軟體的部署。同樣作為DevOps的核心原則,持續交付的優勢包括:可預測的部署,降低引入新功能的風險,縮短客戶反饋的週期,以及提高軟體的總體質量。

圖2:典型的連續交付過程

「持續交付」的過程往往基於「持續整合」過程之上。上圖包含了使用者驗收測試(UAT)和生產兩種環境。不過,在軟體進入生產環境之前,不同的組織可能會設有諸如:質量保證(QA)、負載測試、預生產等多個staging(模擬)環境。當然,所有staging環境和生產環境的部署,都是通過相同的自動化過程來執行的,並且採用的是不同環境的相同版本程式碼庫。我們可以使用多種工具來實現配置的自動化、受控、可重複、可靠、可稽核、以及可逆(或稱可回滾)。

資料庫變更管理的瓶頸問題

不可否認,幾乎所有的項目除了交付已開發的應用程式碼,也會涉及到諸如schema(結構模式)變更等與資料庫相關的工作。目前,我們認為在資料庫的開發領域尚未採用敏捷原則,或實現持續整合。因此,此類資料庫的相關工作會或多或少地拖慢整個軟體產品的交付程序。

讓我來看一個真實的案例。某開發團隊通過遵循Scrum的敏捷方式,進行了2周的sprint(迭代)。當前的一條story(故事線)是在文件中新增一個能與下游系統互動的新欄位。開發團隊估計:就程式碼開發而言,業務事件觸發應用會將文件傳送到下游系統,以及後期的檢索系統,這些僅涉及到資料訪問層中的微小變更。因此,如果不涉及資料庫(本例為關係資料庫管理系統)的變更,這個僅向現有資料表中新增新列的story,很容易在當前的sprint中被實現。但是,正是因為涉及到資料庫的修改,開發團隊可能會對此類迭代的可行性缺乏信心。

這是為什麼呢?其原因在於,他們需要將架構的變更請求傳送給資料庫管理員(DBA)。而DBA將會花時間去確定該變更請求的優先順序,並將它與從其他開發團隊處收到的變更請求進行比較。而在開發資料庫完成了變更以後,DBA則會通知開發人員,並等待他們的反饋,以便將變更推廣到QA或其他階段的環境中。同時,開發人員將測試新架構中程式碼的變更。最後,通過開發團隊與DBA的緊密協調,將應用的變更和資料庫的變更共同交付到生產環境中。

圖3:交付資料庫變更的手動與半自動過程

值得注意的是,在上圖中,該過程並非由開發人員檢入程式碼而觸發的,而是需要兩個團隊之間的互動。也就是說,即使資料庫側的部署過程是自動化的,也無法與應用程式碼的交付管道整合在一起。雖說應用程式碼的變更在某種程度上直接取決於資料庫的變更,但是這兩個變更的生命週期是完全相互獨立的。

下面,讓我們來討論如何將那些與資料庫變更相關的工作(包括資料建模和schema變更等)置於CI/CD過程的範疇之內。

DBA應該成為跨職能敏捷團隊的一部分

許多組織會根據:協助建立應用開發的資料庫,以及維護生產環境的資料庫,來區分DBA的角色。其中,服務於生產環境的DBA的主要職責是:通過監控資料庫,處置升級與補丁,分配儲存空間,執行備份與恢復等,以確保生產環境中資料庫的可用性。而開發類DBA需要與應用開發團隊緊密合作,估計儲存需求,並協助他們進行資料模型的設計,將邏輯模型轉換為資料庫的物理schema等。

可見,為了將資料庫的工作和應用開發工作整合到一個交付管道中,我們勢必讓開發類DBA成為開發團隊的一部分,並由具有良好資料庫知識的全棧開發人員來擔任。

資料庫作為程式碼(Database As Code)

為了實現將資料庫變更和應用程式碼整合到單個管道中,我們需要對資料庫中的每一項變更編寫指令碼,並對其予以版本控制,進而按需通過指令碼自動建立一個新的資料庫例項。如果我們必須將資料庫的物件捕獲為程式碼,那麼需要根據指令碼(即程式碼)的類型,對資料庫進行評估與分類。具體區別標準如下:

資料庫結構:

就是我們經常提到的schema(模式),它定義了資料庫儲存資料的結構,其中包括針對表、檢視、約束、索引和類型的定義。資料字典也可以被視為資料庫結構的一部分。

已儲存的程式碼:

它們與應用程式碼非常相似,其不同之處在於,它們被儲存在資料庫中,並由資料庫引擎來執行。它們包括:儲存過程、函數、程式包、以及觸發器等。

參考資料:

它們通常儲存著被其他業務資料表所引用的一組允許值(permissible values)。在理想情況下,參考資料表中幾乎沒有資料記錄。它們只有在某些業務流程發生變更時,才可能跟著變化,而在正常業務過程中是不會發生變更的。

應用資料或業務資料:

它們是應用程式在正常業務過程中產生的資料記錄,這是任何資料庫被加入到應用系統的主要目的。

總的說來,在以上四種類型的資料庫物件中,前三種可以並且應該被捕獲為指令碼,進而被儲存在版本控制系統中。

表1:是否可被編寫為指令碼的資料庫物件類型

如上表所示,業務或應用資料是唯一不能被指令碼化、或儲存為程式碼的類型。所有回滾、修改、歸檔等都是由資料庫本身來執行的。唯一例外的是,當schema變更導致資料發生遷移時(例如,填充了新的列,或將資料從基表移至規範化表中),遷移指令碼將被視為程式碼,並且應當遵循與schema變更相同的生命週期。

下面,讓我們以一個簡單的資料模型為例(您可以認為資料建模的「Hello World」),來說明如何將指令碼儲存為程式碼。

圖4:該示例模型中包含了業務資料表和參考資料表

在上述模型中,客戶可能與諸如:帳單地址、送貨地址等多個地址相關聯。AddressType表儲存了諸如:帳單、送貨、住所、工作等不同類型的地址。儲存在AddressType中的資料可以被視為參考資料,畢竟它們在日常業務運營中不會有激增。而其他包含著業務資料的表,會隨著客戶的增多,而繼續增長。

下面是各種示例指令碼:

表:

限制條件:

參考資料:

由上述示例可知,除了業務資料,其他所有的資料庫物件都能夠被捕獲到SQL指令碼中。

與應用程式碼位於同一儲存庫中的版本控制資料庫工件:

將資料庫工件與應用程式碼儲存到版本控制系統的同一儲存庫中,有著諸多好處。由於在大多數情況下,資料庫schema的變更往往會涉及到應用程式碼的變更,因此將它們標記為共同釋出,可以避免應用程式碼和資料庫出現不同步的狀況。同時,由於與項目相關的所有內容都放在了一處,因此新的團隊成員可以更加輕鬆方便的獲悉與查閱,進而加快了工作效率。

圖5:包含了資料庫程式碼的Java Maven項目的結構示例

上面的目錄結構展示瞭如何在Java Maven項目中,將資料庫指令碼與應用程式碼一起儲存。當然,這對於Ruby或.Net等應用,也是通用的。CI/CD自動化工具可以在同一處找到它們,並對其執行諸如:從頭開始構建架構、生成遷移、以及產生部署指令碼等必要的操作。

將資料庫工件整合到構建指令碼中:

為了確保資料庫的變更能夠與同一交付管道中的應用程式碼「齊頭並進」,我們在構建的過程中包含資料庫指令碼是非常必要的。通常,資料庫工件是某種形式的SQL指令碼,而且大多數主流構建工具都能夠支援本地、或通過插件的方式執行SQL指令碼。

在此,讓我們先討論在本地環境、或CI伺服器中的構建,稍後再涉及到暫存環境。其中包括的典型任務包括:

  • 刪除Schema。

  • 創建Schema。

  • 創建資料庫結構(或Schema物件),包括表、約束、索引、序列和同義詞。

  • 部署已儲存的程式碼,包括過程、函數、以及包等。

  • 載入參考資料。

  • 載入測試資料。

構建工具可以確保資料庫在載入已知資料集時處於穩定的狀態,並且通過充分的整合測試,以避免出現應用程式碼與資料模型的不同步。這是在資料庫變更管理過程中,實現持續交付模型的第一步。

圖6:該程式碼片段展示了用於運行資料庫指令碼的Maven構建

上面的程式碼截圖說明了如何使用Maven插件來運行SQL指令碼。它能夠刪除與重建schema,並通過運行所有的DDL指令碼,來創建表、約束、索引、序列和同義詞。接著,它將所有已儲存的程式碼部署到資料庫中,並最後載入所有的參考資料和測試資料。

避免共享資料庫

讓多個應用共享一個數據庫schema並非一個好主意。除非資料庫真正屬於某個應用、且不被其他應用所共享,否則將應用程式碼和資料庫變更置於同一交付管道下,將難以達到預期的效果。同時,共享資料庫還會導致應用之間的緊密耦合、以及許多其他問題。

讓每個提交和CI伺服器都能專享Schema

開發人員總希望能夠在自己的「沙箱」中工作,而不必擔心諸如開發資料庫例項之類通用環境等問題。而CI伺服器就是這樣的沙箱,它遵循瞭如何開發應用程式碼的模式。開發人員可以執行各種變更,在本地運行構建,並且在構建成功且測試通過後,再提交變更。通常,此類沙盒既可以是在開發人員本地電腦上、已安裝的獨立資料庫例項,也可以是共享資料庫例項中的其他架構。

圖7:開發人員在其本地環境中進行頻繁的變更與提交

如上圖所示,每個開發人員都擁有自己的schema副本。在執行完成構建之後,除了構建應用,它還會從頭開始構建資料庫的schema。其中包括:刪除與重建schema,執行DDL指令碼,以載入所有的schema物件(如:表、檢視、序列、約束和索引)。同時,它會創建代表著已儲存的程式碼物件,其中包括:函數、過程、包和觸發器。最後,它將載入所有的參考資料和測試資料。而自動化的測試則可確保應用程式碼和資料庫物件始終同步。值得注意的是,由於資料模型的變更不如應用程式碼那樣頻繁,因此出於構建效能的考慮,我們應當讓構建指令碼應具有跳過資料庫構建的選項。

其實,CI構建作業也應當被設定為帶有自己的資料庫沙箱。畢竟,構建指令碼會執行完整的構建,其中就包括了構建應用,以及從頭開始構建資料庫的schema。而且,它會運行一整套的自動化測試,以確保應用本身、及其與之互動的資料庫能夠保持同步。

圖8:修改後的CI流程,集成了資料庫構建和應用程式碼的構建

上圖中描述的過程與圖1中的過程較為相似。CI伺服器包含了在對儲存庫提交時觸發的構建作業。它所執行的構建包含了應用和資料庫的構建。至此,資料庫指令碼就能夠被整合為應用程式碼了。

處置遷移

我們在前文中已經討論瞭如何針對持續整合和本地環境,從頭開始構建資料庫的schema物件、已儲存的程式碼、參考資料和測試資料。那麼對於生產環境中的資料庫、以及QA或UAT環境又該如何處理呢?

鑑於資料庫的本質就是為了支援業務資料,我們不可能對當前正在運行的業務交易資料庫,採取刪除schema、或從指令碼中重建等操作。因此,我們需要編寫增量指令碼,也就是將資料庫的結構從已知的狀態(所謂「軟體定製版」)變更過渡,或將資料遷移到所需的狀態。例如,為了標準化,我們可能需要通過指令碼將一個表中的資料遷至一到多個子表中。而schema的變更,則可以在原始碼儲存庫中,通過指令碼的編寫,使其成為構建的一部分。這些指令碼既可以在主動開發的過程中手工被編寫,也可以由一些自動化工具來完成。其中的一種工具是Flyway,它可以生成遷移指令碼,將資料結構從一種狀態轉換為另一種狀態。Schema的變更可以在原始碼儲存庫中被編寫指令碼並進行維護,以使它們成為構建的一部分。

圖9:schema遷移與回滾的自動化

在上圖中,左側顯示了與應用先前版本(1.0.1)同步的資料庫狀態。右側顯示了資料庫所需的下一版本狀態。我們在版本控制系統中既可以捕獲並標記左側的狀態,又可以將捕獲到的右側狀態作為基線、主分支或主幹。兩者之間的區別正是我們需要讓資料庫在staging環境和生產環境中保持不同的狀態。上圖展示了Flyway工具通過創建遷移指令碼,將資料庫從先前的版本過渡到新的版本;以及通過回滾指令碼,將資料庫過渡回先前版本的自動化過程。這些生成的指令碼將會被標記,並與其他部署工件一起被儲存。因此,我們通過將該自動化過程與持續交付的過程相整合,以確保可重複、可靠、且可逆(通過回滾)的資料庫變更。

將資料庫變更併入連續交付

現在,我們可以將上述各部分整合到一起了,即:通過一個現有的持續整合過程,來重建資料庫和應用程式碼;通過一個併入部署工件的過程,為資料庫生成遷移指令碼。DevOps工具將使用這些已釋出的工件,來構建任何staging環境或生產環境。該部署工件還將包含回滾指令碼,以便在出現任何問題時,我們都可以重新部署應用的先前版本,通過運行資料庫的回滾指令碼,將資料庫的schema轉換為與應用程式碼的先前版本相同步的狀態。

圖10:包含了資料庫變更的持續交付

上圖描述了將資料庫變更管理併入持續交付的過程。此處假設已經存在一個持續整合過程。在啟動了UAT(或測試、QA等其他staging環境)的部署後,自動化流程將負責在原始碼控制儲存庫中創建標籤,並從帶標籤的程式碼庫中構建可部署的應用工件,生成資料庫遷移指令碼,組裝工件,並執行部署。整個部署的過程包括:應用的部署,以及將遷移指令碼應用到資料庫中。按照審批流程,應用程式會通過相同的工件被部署到生產環境中。若要回滾至先前版本,則需重新部署應用程式,並運行資料庫的回滾指令碼。

市場上的可用工具

前面我們主要介紹瞭如何在涉及資料庫變更的項目中,實現CI/CD的過程。而在實踐中,我們往往會根據不同的需求,使用不同的工具,例如:針對構建自動化的Maven或Gradle,針對持續整合的Jenkins或TravisCI,以及針對配置管理的Chef或Puppet等本地解決方案。下面,我為您羅列出針對資料庫DevOps的自動化通用工具:

  • Datical

  • Redgate

  • Liquibase

  • Flyway

小結

誠然,持續整合和持續交付的流程為組織帶來了諸如:縮短產品的面市時間,可靠的釋出,以及提高軟體整體質量等巨大的好處。鑑於手動執行資料庫變更管理會帶來的交付瓶頸,本文和您討論瞭如何將資料庫的變更,帶入與應用程式碼相同的交付管道中,以及市場上能夠配合此類實踐的各種實用工具。


IT145.com E-mail:sddin#qq.com