應用執行時是基礎設施系統的關鍵部分,它將基礎設施層的資源組合起來,提供可以佈署應用的執行時平台。本文探討如何設計和實作應用執行時基礎設施,以支援從傳統應用到雲原生應用的各種需求。
應用執行時的基本概念
設計和實作應用執行時基礎設施的起點是理解將使用它的應用。需要考慮的關鍵問題包括:
- 應用使用什麼語言和執行堆積積疊?
- 應用將被封裝並佈署到伺服器、容器還是作為FaaS無伺服器程式碼?
- 它們是佈署到單一位置的單一應用,還是分佈在叢集中的多個服務?
- 它們的連線性和資料需求是什麼?
這些問題的答案將引導我們理解應用執行時層需要設定和管理哪些基礎設施資源來執行應用。應用執行時層的部分對映到基礎設施平台的部分,包括:
- 根據計算資源的執行環境
- 根據儲存資源的資料管理
- 由網路資源組成的連線性
雲原生與應用驅動的基礎設施
雲原生軟體是設計和實作來利用現代基礎設施動態特性的。與舊一代軟體不同,雲原生應用的例項可以透明地增加、移除和在底層基礎設施之間移動。底層平台動態分配計算和儲存資源,並將流量路由到應用和從應用路由出去。應用與監控、日誌記錄、認證和加密等服務無縫整合。
Heroku提出了在雲基礎設施上構建應用的十二因素方法論。而"雲原生"這個詞通常與Kubernetes生態系統相關聯。
許多組織擁有現有的非雲原生軟體組合。雖然他們可能會轉換或重寫一些軟體以成為雲原生的,但在許多情況下,這樣做的成本無法被收益所證明。應用驅動的基礎設施策略涉及使用現代、動態基礎設施為應用構建應用執行時環境。
團隊為執行為無伺服器程式碼或容器的新應用提供應用執行時。他們還提供基礎設施來支援現有應用。所有基礎設施都被定義、設定和管理為程式碼。應用驅動的基礎設施可以使用抽象層動態提供。
應用執行時目標
實施應用驅動策略始於分析應用組合的執行時需求。然後設計應用執行時解決方案來滿足這些需求,實施可重用的堆積積疊、堆積積疊元件和其他團隊可以用來組裝特定應用環境的元素。
應用的可佈署部分
應用的可佈署發布可能涉及不同的元素,包括:
- 可執行檔案:發布的核心是可執行檔案,無論是二進位檔案還是解釋指令碼。
- 伺服器設定:許多應用佈署包會更改伺服器設定,包括程式將執行的使用者帳戶、資料夾結構和系統設定檔案的更改。
- 資料結構:當應用使用資料函式庫時,其佈署可能會建立或更新模式。
- 參考資料:應用佈署可能會用初始資料集填充資料函式庫或其他儲存。
- 連線性:應用佈署可能會指定網路設定,如網路連線埠。
- 設定引數:應用佈署包可能會設定引數。
你可以在不同的地方劃分應用和基礎設施之間的界限。你可能會將所需的函式庫捆綁到應用佈署包中,或將其作為基礎設施的一部分設定。
例如,容器映像通常包括大部分作業系統以及將在其上執行的應用。不可變伺服器或不可變堆積積疊更進一步,將應用和基礎設施結合為單一實體。
佈署包
應用通常被組織成佈署包,包格式取決於執行時環境的型別。佈署包格式和相關執行時的例子包括:
- 伺服器作業系統:Red Hat RPM檔案、Debian .deb檔案、Windows MSI安裝程式包
- 語言執行時引擎:Ruby gems、Python pip包、Java .jar、.war和.ear檔案
- 容器執行時:Docker映像
- 應用叢集:Kubernetes佈署描述符、Helm圖表
- FaaS無伺服器:Lambda佈署包
佈署包格式是一個標準,使佈署工具或執行時能夠提取應用的部分並將它們放在正確的位置。
將應用佈署到伺服器,無論是物理的還是虛擬的,都是傳統的執行時平台。應用使用作業系統封裝格式(如RPM、.deb檔案或Windows MSI)或語言執行時格式(如Ruby gem或Java .war檔案)封裝。最近,容器映像(如Docker映像)作為封裝和佈署應用到伺服器的格式變得流行。
在容器中封裝應用
容器將依賴從作業系統拉入應用包(容器映像)中。將依賴包含在容器中使其比典型的作業系統或語言包更大,但有幾個優點:
- 容器為執行應用建立了一個更一致的環境。
- 容器化的應用大部分與其執行的伺服器隔離,這為你可以在哪裡執行它提供了靈活性。
- 透過在容器映像中封裝應用的作業系統上下文,你簡化並標準化了主機伺服器的要求。
- 減少應用執行時環境的變異性提高了品質保證。
將應用佈署到伺服器叢集
人們在容器化應用叢集出現之前就已經將應用佈署到伺服器組。通常的模型是有一個伺服器叢集,並在每個伺服器上執行相同的應用集。你以與單個伺服器相同的方式封裝應用,為叢集中的每個伺服器重複佈署過程。
如果你將應用佈署到多個伺服器,你需要決定如何協調佈署。你是否同時將應用佈署到所有伺服器?你是否需要在此期間使整個服務離線?或者你是一次升級一台伺服器?你可以利用對伺服器的增量佈署進行漸進式佈署策略,如藍綠佈署和金絲雀佈署模式。
將應用佈署到應用叢集
應用託管叢集是執行一個或多個應用的伺服器池。與每個伺服器執行相同應用集的伺服器叢集不同,應用叢集中的不同伺服器可能執行不同的應使用案例項組。
當你將應用佈署到叢集時,排程器決定在哪些主機伺服器上執行應用的例項。排程可能會根據不同的演算法和設定更改這種分佈,在主機伺服器之間增加和移除應使用案例項。
佈署應用到叢集的包
現代應用通常涉及多個程式和元件,佈署在複雜的基礎設施上。執行時環境需要知道如何執行這些各種部分:
- 執行的最小和最大例項數是多少?
- 執行時如何知道何時增加或移除例項?
- 執行時如何知道例項是否健康或需要重新啟動?
- 需要為每個例項設定和附加什麼儲存?
- 連線性和安全要求是什麼?
不同的執行時平台提供不同的功能,許多都有自己的封裝和設定格式。通常,這些平台使用佈署清單,參照實際的佈署工件(例如,容器映像),而不是包含所有可佈署部分的存檔檔案。
佈署FaaS無伺服器應用
在編寫FaaS應用時,你的程式碼執行的伺服器或容器的細節對你是隱藏的。但你的程式碼可能需要基礎設施才能工作。例如,你可能需要入站或出站連線的網路由、儲存或訊息佇列。你的FaaS框架可能與底層基礎設施平台整合,自動設定必要的基礎設施。或者你可能需要在單獨的堆積積疊定義工具中定義基礎設施元素。
應用執行時是基礎設施系統的關鍵部分,它將基礎設施層的資源組合起來,提供可以佈署應用的執行時平台。透過理解應用的需求,團隊可以設計和實作適當的應用執行時解決方案,無論是支援傳統應用還是雲原生應用。
隨著技術的發展,應用執行時的設計和實作也在不斷演進。從傳統的伺服器佈署到容器和無伺服器架構,基礎設施團隊需要適應這些變化,並提供能夠支援各種應用需求的靈活解決方案。透過將基礎設施定義為程式碼,團隊可以確保應用執行時環境的一致性、可靠性和可擴充套件性。
應用程式執行環境與伺服器建構
應用程式資料管理:從佈署到遷移的全面策略
資料管理常是應用程式佈署過程中被忽視的環節,但實際上它是系統穩定性和可靠性的關鍵。當我們談論基礎設施時,我們經常關注於資源的設定,但真正的挑戰在於如何管理這些資源的變更。
資料結構與遷移策略
資料儲存可分為嚴格結構化(如SQL資料函式庫)和無結構或無模式的儲存方式。當應用程式版本更新時,資料結構可能需要隨之調整。以常見的案例為例,將單一「姓名」欄位拆分為「姓」、「中間名」和「名」三個獨立欄位時,就需要進行資料結構變更。
這種資料結構變更和資料轉換過程稱為「結構遷移」(schema migration)。目前有多種工具可協助管理這一過程:
# 使用Flyway進行資料函式庫遷移範例
@Component
public class DatabaseMigrationService {
@Autowired
private Flyway flyway;
@PostConstruct
public void migrateDatabases() {
// 執行所有待處理的遷移
flyway.migrate();
// 遷移完成後的日誌記錄
logger.info("資料函式庫遷移完成,當前版本: {}", flyway.info().current().getVersion());
}
}
這段程式碼展示瞭如何使用Flyway自動化資料函式庫遷移過程。當應用程式啟動時,@PostConstruct
註解確保遷移方法會在相依性注入完成後自動執行。Flyway會檢查所有待處理的遷移指令碼並按順序執行,確保資料函式庫結構與應用程式版本保持同步。這種方法使得資料函式庫變更可以作為程式碼管理,並且應用程式版本一起封裝發布。
雲原生應用程式儲存基礎設施
雲原生基礎設施能夠根據需求動態分配資源給應用程式和服務。當系統新增應用程式例項時,它可以自動設定並掛載儲存裝置。以下是一個應用程式佈署清單的範例:
application:
name: db_cluster
compute_instance:
memory: 2 GB
container_image: db_cluster_node_application
storage_volume:
size: 50 GB
volume_image: db_cluster_node_volume
這個佈署清單定義瞭如何建立動態擴充套件資料函式庫叢集的節點。對於每個節點例項,平台會建立一個包含資料函式庫軟體的容器例項,並掛載一個從初始化映像複製的磁碟區。當例項啟動時,它會連線到叢集並將資料同步到本地磁碟區。這種方式讓儲存資源的設定與應用程式佈署緊密結合,實作了真正的基礎設施即程式碼。
應用程式連線性與服務發現
除了計算和儲存資源外,應用程式還需要網路連線能力。雲原生方法是在應用程式佈署清單中定義網路需求,並由應用程式執行環境動態分配資源:
application:
name: nginx
connectivity:
inbound:
id: https_inbound
port: 443
allow_from: $PUBLIC_INTERNET
ssl_cert: $SHOPSPINNER_PUBLIC_SSL_CERT
outbound:
port: 443
allow_to: [ $APPSERVER_APPLICATIONS.https_inbound ]
這個設定義了入站和出站連線,參照了系統的其他部分。它指定了公共網際網路(可能是一個閘道器)和應用程式伺服器的入站HTTPS連線埠,這些應用程式伺服器使用自己的佈署清單佈署到同一叢集。這種方式讓網路設定成為應用程式定義的一部分,而不是獨立的基礎設施設定。
服務發現機制
在動態基礎設施環境中,服務位置經常變動,因此需要更靈活的服務發現方式。常見的服務發現機制包括:
- 硬編碼IP位址 - 為每個服務分配固定IP位址
- 主機檔案條目 - 使用伺服器設定生成/etc/hosts檔案
- DNS (網域名稱系統) - 使用DNS條目將服務名稱對映到當前IP位址
- 資源標籤 - 標記提供特定服務的基礎設施資源
- 設定登入檔 - 在集中式登入檔中維護連線詳情
- 邊車模式(Sidecar) - 在每個應用程式例項旁執行單獨的程式
- API閘道 - 集中式HTTP服務定義路由和端點
當我在設計微服務架構時,發現DNS雖然是最成熟的解決方案,但在高度動態的環境中,邊車模式和服務網格提供了更多的彈性和功能。邊車不僅處理服務發現,還能管理認證、加密、日誌記錄和監控等關鍵功能。
伺服器即程式碼:構建與管理
基礎設施即程式碼最初是作為設定伺服器的方式出現的。系統管理員編寫shell、批處理和Perl指令碼,而CFEngine開創了使用宣告式、冪等DSL來安裝套件和管理伺服器上的設定案的先河,隨後Puppet和Chef也跟進。
伺服器生命週期
伺服器的生命週期可分為三個轉換階段:
- 建立和設定伺服器例項
- 變更現有伺服器例項
- 銷毀伺服器例項
伺服器組成元素
伺服器上的內容可分為三類別:
型別 | 描述 | 組態管理處理方式 |
---|---|---|
軟體 | 應用程式、函式庫和其他程式碼 | 確保在每個相關伺服器上相同;不關心內部內容 |
設定 | 用於控制系統和/或應用程式工作方式的檔案 | 在伺服器上構建檔案內容;確保一致性 |
資料 | 由系統和應用程式生成和更新的檔案 | 自然發生和變化;可能需要儲存,但不會嘗試管理內部內容 |
這些元素可能來自多個來源:
- 基本作業系統
- 作業系統套件函式庫
- 語言、框架和其他平台函式庫
- 非標準套件
- 獨立材料
伺服器設定程式碼
伺服器設定工具(如Ansible、CFEngine、Chef、Puppet和Saltstack)提供了管理伺服器設定的方法。這些工具可以實作推播或提取設定模式:
- 推播模式:從中央伺服器連線到每個受管伺服器
- 提取模式:在每個伺服器上安裝代理,定期檢查和應用設定
伺服器設定程式碼模組
伺服器設定程式碼應組織成模組,每個模組圍繞單一、內聚的關注點設計。常見的方法是為每個應用程式建立單獨的模組。
例如,為Tomcat應用程式伺服器建立的模組可能會:
- 安裝Tomcat軟體
- 建立使用者帳戶和群組
- 設定具有正確許可權的資料夾
- 構建Tomcat設定案
- 整合日誌聚合、監控和程式管理等服務
許多團隊發現根據用途設計不同的模組很有用:
- 函式庫模組:管理可被應用程式模組重用的核心概念
- 應用程式模組:匯入一個或多個函式庫模組,並為特定目的設定引數
伺服器角色
伺服器角色是定義要應用於伺服器的一組設定模組的方式。角色也可以設定一些預設引數:
role: application-server
server_modules:
- tomcat
- monitoring_agent
- logging_agent
- network_hardening
parameters:
- inbound_port: 8443
常見的方法是使用角色繼承。定義包含所有伺服器通用軟體和設定的基本角色,然後更具體的角色繼承基本角色並增加額外的設定:
role: base-role
server_modules:
- monitoring_agent
- logging_agent
- network_hardening
role: application-server
include_roles:
- base_role
server_modules:
- tomcat
parameters:
- inbound_port: 8443
role: shopping-service-server
include_roles:
- application-server
server_modules:
- shopping-service-application
在設計伺服器設定時,玄貓發現角色繼承提供了極佳的平衡點 - 它既保持了設定的模組化和可重用性,又避免了重複設定的維護負擔。特別是在大型組織中,基礎角色可以由平台團隊維護,確保所有伺服器符合基本安全和監控標準,而應用團隊則可以專注於其應用特定的設定需求。
透過這種方式,伺服器設定成為一種可版本化、可測試與可重複的程式碼,而不是手動設定的藝術。這不僅提高了系統的可靠性,還大減少了設定漂移和"特殊雪花"伺服器的出現。
伺服器程式碼化:從建置到佈署的自動化實踐
在現代雲端基礎設施管理中,伺服器程式碼化(Servers as Code)已成為高效維運的關鍵實踐。本文將探討如何透過自動化測試、持續交付和基礎設施程式碼化原則,建立可靠與一致的伺服器佈署流程。玄貓將分享多年實踐經驗,幫助讀者掌握從伺服器角色設計到程式碼測試的完整技術體系。
伺服器程式碼的漸進式測試策略
伺服器程式碼的設計與測試之間存在相互強化的關係。一個結構清晰的程式碼函式庫更容易撰寫和維護測試,而持續執行測試則能幫助團隊保持程式碼的整潔結構。將每次變更推播到執行測試的管道中,有助於團隊養成持續重構的紀律,最小化技術債務和設計債務。
伺服器角色由伺服器設定模組成,這些模組可能進一步組織為函式庫模組和應用程式模組。這種結構非常適合實施漸進式測試策略。我們可以設計一系列管道階段,以遞增的整合程度測試每個模組,如下圖所示:
graph TD C[C] F[F] A[模組變更] --> B[模組測試階段] B --> C{測試透過?} C -->|是| D[角色測試階段] C -->|否| E[修復模組] E --> A D --> F{測試透過?} F -->|是| G[佈署到環境] F -->|否| H[修復角色設定] H --> D
每當有人提交對模組的變更時,獨立的階段會測試該模組。當模組透過測試後,伺服器角色也會有一個測試階段。當角色使用的模組之一發生變更並透過其自身階段時,角色測試階段就會執行。當有人提交對角色程式碼的變更時(例如增加或移除模組,或更改引數),角色測試階段也會執行。
伺服器程式碼測試重點
伺服器程式碼測試的關鍵在於確定測試什麼內容。這涉及到測試宣告式程式碼是否有價值的問題。設計良好的程式碼函式庫中的伺服器程式碼模組通常較小、簡單與專注。例如,安裝Java JVM的模組可能只是一個簡單的陳述式,帶有幾個引數:
package:
name: java-${JAVA_DISTRIBUTION}
version: ${JAVA_VERSION}
這段程式碼定義了一個套件安裝設定,使用變數來指定Java發行版和版本。${JAVA_DISTRIBUTION}
和${JAVA_VERSION}
是變數,會在執行時被實際值替換。這種引數化設計使設定更加靈活,可以根據不同環境需求調整Java版本。
實際上,即使看似簡單的套件安裝模組也可能包含更多程式碼,用於自定義檔案路徑、設定案,甚至可能增加使用者帳戶。但通常沒有太多需要測試的內容,因為測試可能只是重述程式碼本身。
測試應該專注於常見問題、變數結果和程式碼組合:
- 常見問題檢查:針對實踐中容易出錯的地方進行檢查。對於簡單的套件安裝,可能需要檢查命令是否在預設使用者路徑中可用:
given command 'java -version' {
its(exit_status) { should_be 0 }
}
這段測試程式碼使用了一種行為驅動的測試語法,檢查java -version
命令是否能成功執行。exit_status
應為0表示命令執行成功,這驗證了Java確實已正確安裝並可在系統路徑中找到。這種測試確保了安裝過程的實際效果,而不僅是檢查檔案是否存在。
引數變化測試:如果套件可能根據傳遞的引數產生截然不同的結果,應該編寫測試來確保在不同情況下的正確行為。例如,可以使用不同的Java發行版值執行測試,確保無論選擇哪種發行版,java命令都可用。
整合測試:隨著整合更多元素,測試的價值會更高。因此,對於應用程式伺服器角色的測試可能比對角色包含的模組的測試更多。當這些模組相互整合和互動時尤其如此。例如,安全強化模組可能會導致問題,這時可能需要對應用程式伺服器角色進行測試,以確認即使在鎖定連線埠後,應用程式伺服器仍能正常執行並接受請求。
伺服器程式碼測試方法
大多數自動化伺服器程式碼測試透過在伺服器或容器例項上執行命令並檢查結果來工作。這些測試可能斷言資源的存在和狀態,如套件、檔案、使用者帳戶和執行中的程式。測試也可能檢查結果,例如連線到網路連線埠以證明服務是否回傳預期結果。
測試伺服器條件的流行工具包括Inspec、Serverspec和Terratest。測試完整基礎設施堆積積疊的策略包括離線和線上測試。線上測試涉及在基礎設施平台上啟動資源,而離線測試通常使用容器或本地虛擬機器。
本地工作的基礎設施開發人員可以建立具有最小作業系統安裝的容器例項或本地VM,應用伺服器設定程式碼,然後執行測試。測試伺服器程式碼的管道階段可以執行相同操作,在代理上執行容器例項或VM。或者,該階段可以在容器叢集上啟動獨立的容器例項,這在管道協調本身容器化時效果很好。
建立新伺服器例項的生命週期
伺服器的基本生命週期從建立新伺服器例項開始。更完整的生命週期包括建立和更新伺服器映像的步驟,但我們將在後面討論這個主題。現在,我們的生命週期從建立例項開始:
graph LR A[選擇基礎映像] --> B[分配資源] B --> C[安裝作業系統] C --> D[應用設定] D --> E[網路設定] E --> F[服務註冊] F --> G[伺服器就緒]
完整的伺服器建立過程包括佈建伺服器例項,使其完全準備好使用。建立和佈建伺服器例項涉及的一些活動包括:
分配基礎設施資源:從池中選擇物理伺服器或選擇主機伺服器作為虛擬機器執行。對於虛擬機器,主機上的虛擬機器監視器軟體分配記憶體和其他資源,可能還會為伺服器例項分配儲存空間。
安裝作業系統和初始軟體:作業系統可能被複製到磁碟捲上,或者新例項可能執行安裝過程,選擇並複製檔案到新例項。指令碼化OS安裝程式的例子包括Red Hat Kickstart、Solaris JumpStart、Debian Preseed和Windows安裝答案檔案。
應用額外設定:在此步驟中,流程執行伺服器設定工具,遵循"如何應用伺服器設定程式碼"中描述的模式。
設定和附加網路資源與策略:這個過程可以包括將伺服器分配到網路地址區塊、建立路由和增加防火牆規則。
向服務註冊伺服器例項:例如,將新伺服器增加到監控系統。
這些活動並非互斥—伺服器建立過程可能使用一種或多種方法來執行這些不同的活動。
建立伺服器例項的多種方法
手動建立伺服器例項
基礎設施平台提供工具來建立新伺服器,通常是網頁UI、命令列工具,有時還有GUI應用程式。每次建立新伺服器時,都需要選擇所需的選項,包括源映像、要分配的資源和網路詳細訊息:
$ mycloud server new \
--source-image=stock-linux-1.23 \
--memory=2GB \
--vnet=appservers
這是一個命令列範例,用於建立新的雲伺服器例項。它指定了三個關鍵引數:
--source-image=stock-linux-1.23
:使用名為"stock-linux-1.23"的基礎映像--memory=2GB
:為伺服器分配2GB記憶體--vnet=appservers
:將伺服器連線到名為"appservers"的虛擬網路
雖然使用UI和命令列工具實驗平台很有用,但這不是建立人們需要的伺服器的好方法。手動設定選項容易出錯,並導致設定不一致的伺服器、不可預測的系統和過多的維護工作。
使用指令碼建立伺服器
可以編寫指令碼來一致地建立伺服器。指令碼封裝命令列工具或使用基礎設施平台的API來建立伺服器例項,在指令碼程式碼或設定案中設定選項。建立伺服器的指令碼是可重用、一致和透明的:
mycloud server new \
--source-image=stock-linux-1.23 \
--memory=2GB \
--vnet=appservers
這個指令碼與前面的命令列範例相同,但因為它在指令碼中,團隊中的其他人可以看到如何建立伺服器,並且可以建立更多行為相同的伺服器。
在Terraform和其他堆積積疊管理工具出現之前,大多數團隊編寫建立伺服器的指令碼,通常使用設定案使指令碼可設定。但我們花了太多時間改進和修復這些指令碼。
使用堆積積疊管理工具建立伺服器
使用堆積積疊管理工具,可以在其他基礎設施資源的上下文中定義伺服器:
server:
source_image: stock-linux-1.23
memory: 2GB
vnet: ${APPSERVER_VNET}
這段YAML程式碼定義了一個伺服器資源,使用堆積積疊管理工具(如Terraform或CloudFormation)的語法。關鍵在於它使用了變數${APPSERVER_VNET}
來參照可能在堆積積疊程式碼其他部分定義的網路結構。這種方法的優勢在於:
- 工具處理錯誤檢查等邏輯,無需在自定義指令碼中實作
- 堆積積疊處理與其他基礎設施元素的整合,如將伺服器附加到堆積積疊程式碼中定義的網路結構和儲存
- 設定更加宣告式和可維護
使用堆積積疊工具建立伺服器有幾個原因:工具處理錯誤檢查等邏輯;處理與其他基礎設施元素的整合,如將伺服器附加到堆積積疊程式碼中定義的網路結構和儲存。
設定平台自動建立伺服器
大多數基礎設施平台可以在特定情況下自動建立新的伺服器例項。兩種常見情況是自動擴充套件(增加伺服器以處理負載增加)和自動還原(在伺服器失敗時替換例項):
server_cluster:
server_instance:
source_image: stock-linux-1.23
memory: 2GB
vnet: ${APPSERVER_VNET}
scaling_rules:
min_instances: 2
max_instances: 5
scaling_metric: cpu_utilization
scaling_value_target: 40%
health_check:
type: http
port: 8443
path: /health
expected_code: 200
wait: 90s
這段設定義了一個伺服器叢集,具有自動擴充套件和健康檢查功能:
- 伺服器例項基本設定:指定了映像、記憶體和網路設定
- 擴充套件規則:
- 維持2-5個例項
- 根據CPU使用率進行擴充套件
- 目標保持在40%的CPU使用率
- 健康檢查:
- 使用HTTP請求檢查健康狀態
- 檢查連線埠8443上的
/health
路徑 - 預期回傳200狀態碼
- 等待90秒後判定健康狀態
這種自動化設定極大提高了系統的彈性和可靠性,無需人工干預即可應對負載變化和故障還原。
這個例子告訴平台保持至少2個、最多5個伺服器例項執行。平台根據需要增加或移除例項,以保持CPU利用率接近40%。定義還包括健康檢查,如果伺服器在90秒後沒有回傳預期的程式碼,平台會假設伺服器已失敗,並銷毀它並建立新例項。
使用網路佈建工具構建伺服器
裸機雲動態佈建硬體伺服器的過程通常包括以下步驟:
- 選擇未使用的物理伺服器,並觸發它以"網路安裝"模式啟動
- 網路安裝程式從簡單的引導OS映像啟動伺服器以啟動OS安裝
- 下載OS安裝映像並將其複製到主硬碟
- 重新啟動伺服器以在設定模式下啟動OS,執行無人值守的指令碼化OS安裝程式
管理此過程的工具包括Crowbar、Cobbler、FAI、Foreman、MAAS、Rebar和Tinkerbell。
FaaS事件輔助伺服器佈建
FaaS無伺服器程式碼可以在佈建新伺服器中發揮作用。平台可以在不同點執行程式碼,包括在建立新例項之前、期間和之後。例如分配安全策略、向監控服務註冊或執行伺服器設定工具。
預建伺服器的策略
新伺服器的內容來源包括作業系統安裝、從儲存函式庫下載的套件,以及複製到伺服器的自定義設定案和應用程式檔案。雖然可以在建立伺服器時組裝所有這些內容,但也有幾種方法可以提前準備伺服器內容。
熱克隆伺服器
大多數基礎設施平台使複製執行中的伺服器變得簡單。以這種方式熱克隆伺服器既快速又簡單,並提供在克隆點一致的伺服器。
克隆執行中的伺服器在希望探索或排除伺服器故障而不幹擾其操作時很有用。但它帶來一些風險:
- 生產伺服器的副本可能影響生產環境
- 克隆的伺服器包含原始伺服器的歷史資料,如日誌檔案條目,可能導致故障排除混亂
- 克隆的伺服器不是真正可重現的,不同時間點克隆的伺服器會有差異
- 克隆的伺服器是設定漂移的來源
使用伺服器快照
可以拍攝執行中伺服器的快照並從中構建伺服器,而不是直接克隆。大多數基礎設施平台提供命令和API呼叫來輕鬆快照伺服器,建立靜態映像。這樣,可以建立任意數量的伺服器,確信每一個都與起始映像相同。
然而,從活動伺服器拍攝的快照建立伺服器具有熱克隆的許多其他缺點。快照可能被原始伺服器的日誌、設定和其他資料汙染。從頭開始建立乾淨的伺服器映像更有效。
建立乾淨的伺服器映像
伺服器映像是從乾淨、已知來源建立的快照,用於建立多個一致的伺服器例項。可能使用相同的基礎設施平台功能,但原始伺服器例項從未用作整體系統的一部分,確保每個新伺服器都是乾淨的。
構建伺服器映像的典型過程是:
- 從已知來源建立新的伺服器例項,如作業系統供應商的安裝映像
- 在伺服器上應用伺服器設定程式碼或執行其他指令碼,安裝標準套件和代理,強化安全設定,應用最新補丁
- 快照伺服器,建立可用於建立多個伺服器例項的映像,可能涉及標記和版本控制
有時人們稱伺服器映像為黃金映像。雖然一些團隊手動構建這些映像,但自動化此過程並將伺服器映像作為程式碼管理有明顯好處。
設定新伺服器例項的策略
在伺服器生命週期的不同點可以應用設定:
graph LR A[建立伺服器映像] -->|烘焙設定| B[伺服器映像] B -->|建立例項| C[新伺服器例項] C -->|炸制設定| D[設定完成的伺服器] D -->|執行時設定| E[更新的伺服器]
設定可以在建立伺服器映像時應用,在從映像建立伺服器例項時應用,以及在伺服器執行時應用:
設定伺服器映像(烘焙):在構建將用於建立多個伺服器例項的伺服器映像時應用設定。這是一次設定,多次使用。
設定新伺服器例項(炸制):在建立新的伺服器例項時應用設定。這是多次設定。
設定執行中的伺服器例項:對已在使用的伺服器應用設定。常見原因是進行更改(如應用安全補丁)或還原在自動化設定之外進行的任何更改以強制一致性。
炸制伺服器例項
炸制伺服器涉及在建立新伺服器例項時應用設定。可以將其推向極端,將每個伺服器映像保持在最低限度,並將特定於給定伺服器的所有內容構建到活動映像中。
這樣做意味著新伺服器始終具有最新的更改,包括系統補丁、最新的軟體套件版本和最新的設定選項。炸制伺服器是交付時整合的一個例子。
這種方法簡化了映像管理:映像不多,可能只有每種硬體和OS版本組合使用的一個;映像不需要經常更新;可以在佈建每個伺服器時應用最新補丁。
炸制的潛在問題包括:
- 速度:構建伺服器時發生的活動增加了建立時間,特別是對於處理負載峰值或從故障還原而言
- 效率:設定伺服器通常涉及從儲存函式庫透過網路下載套件,可能浪費與緩慢
- 依賴性:設定伺服器通常依賴於成品儲存函式庫和其他系統,如果任何一個離線或無法存取,就無法建立新伺服器
烘焙伺服器映像
另一端是將幾乎所有內容設定到伺服器映像中。構建新伺服器非常快速與簡單,因為只需應用特定於例項的設定。烘焙伺服器映像是構建時整合的一個例子。
烘焙設定到伺服器映像特別適用於使用大量類別似伺服器例項的系統,以及需要能夠頻繁與快速建立伺服器的情況。
烘焙伺服器映像的一個挑戰是需要設定工具和自動化流程,使更新和推出新版本變得容易。例如,如果為作業系統或烘焙到伺服器映像中的關鍵套件發布了重要的安全補丁,希望能夠快速構建新映像並將其推廣到現有伺服器,同時最小化中斷。
烘焙伺服器映像的另一個問題是速度。即使有成熟的自動化流程來更新映像,構建和發布新映像也可能需要許多分鐘(10到60分鐘很常見)。可以透過將更改推廣到執行中的伺服器來緩解這一點,或者使用結合烘焙和炸制的流程。
結合烘焙和炸制
實際上,大多數團隊使用烘焙和炸制的組合來設定新伺服器。可以平衡哪些活動設定到伺服器映像中,哪些在建立伺服器例項時應用。可能在流程的兩個部分都應用一些設定元素。
決定何時是應用特定設定的正確時間的主要考慮因素是所需時間和變更頻率。需要更長時間應用與變更較少的事物是烘焙到伺服器映像中的明確候選者。例如,可以在伺服器映像上安裝應用程式伺服器軟體,使啟動多個伺服器例項變得更快,並在此過程中節省網路頻寬。
相反,安裝更快或變更頻繁的東西更適合炸制。內部開發的應用程式就是一個例子。按需啟動新伺服器的最常見使用案例之一是作為軟體發布流程的一部分進行測試。
高效的開發團隊可能每天推播十幾個或更多的應用程式新構建,依靠CI流程自動佈署和測試每個構建。為每個新的應用程式構建烘焙新的伺服器映像對於這種工作節奏來説太慢,因此在建立測試伺服器時佈署應用程式更有效率。
團隊結合烘焙和炸制的另一種方式是盡可能多地烘焙到伺服器映像上,但炸制更新的版本。團隊可能以較慢的速度烘焙更新的伺服器映像,例如每週或每月一次。當需要更新通常烘焙到映像上的東西時,如安全補丁或設定改進,可以將其放入伺服器建立流程中,在烘焙映像之上炸制。
當到了烘焙更新映像的時候,他們將更新折疊到其中並從建立流程中移除。這種方法讓團隊能夠快速整合新的變更,減少開銷。團隊經常將此與持續應用程式碼結合使用,以更新現有伺服器而無需重建它們。
建立伺服器時應用伺服器設定
大多數用於建立伺服器的工具,無論是命令列工具、平台API呼叫還是堆積積疊管理工具,都提供了應用伺服器設定程式碼的方法。例如,堆積積疊管理工具應該有語法來支援流行的工具,或在新的伺服器例項上執行任意命令:
server:
source_image: stock-linux-1.23
memory: 2GB
vnet: ${APPSERVER_VNET}
configure:
tool: servermaker
code_repo: servermaker.shopspinner.xyz
server_role: appserver
parameters:
app_name: catalog_service
app_version: 1.2.3
這段設定不僅定義了伺服器的基本屬性,還指定了如何設定該伺服器:
- 設定工具:使用名為"servermaker"的工具
- 程式碼來源:從servermaker.shopspinner.xyz儲存函式庫取得設定程式碼
- 伺服器角色:應用"appserver"角色
- 引數傳遞:
- 應用名稱:catalog_service
- 應用版本:1.2.3
這種方法將伺服器建立和設定整合在一個宣告式定義中,使整個過程可重複與一致。工具會自動處理連線到伺服器、執行設定工具和應用適當引數的複雜性。
這段程式碼執行Servermaker工具,傳遞託管伺服器設定程式碼的伺服器主機名、要應用到伺服器的角色(appserver)以及要傳遞給伺服器設定程式碼的一些引數(app_name和app_version)。
一些工具還允許將伺服器設定程式碼直接嵌入到堆積積疊的程式碼中,或執行shell命令。對於簡單的需求,這可能沒問題,但在大多數情況下,這段程式碼會在大小和複雜性上增長,因此最好提取程式碼以保持程式碼函式庫的整潔和可維護性。
伺服器變更管理模式
在管理伺服器變更時,有一種反模式和兩種模式可供選擇。
反模式:僅在變更時應用
也稱為:臨時自動化。
使用僅在變更時應用反模式,設定程式碼只在有特定變更要應用時才應用到伺服器。
例如,一個執行多個Tomcat應用程式伺服器的團隊在建立新的伺服器例項時執行Ansible劇本來安裝和設定Tomcat,但一旦伺服器執行,他們不會執行Ansible,直到需要時才執行。當發布新版本的Tomcat時,他們更新劇本並將其應用到伺服器。
在這種反模式的最極端版本中,只將特定意圖更改的程式碼應用到伺服器。
這種方法的問題在於:
- 如果只在需要特定變更時應用設定程式碼,可能會有很長時間不對特定伺服器例項應用程式碼
- 當最終應用程式碼時,可能會因為伺服器上的其他差異而失敗
- 當只對某些伺服器而非其他伺服器應用變更時,問題會變得更糟
模式:持續設定同步
也稱為:計劃的伺服器設定更新。
持續設定同步涉及重複與頻繁地將設定程式碼應用到伺服器,無論程式碼是否已更改。這樣做可以還原或顯示可能潛入的任何意外差異,無論是對伺服器還是設定程式碼使用的其他資源。
我們希望相信伺服器設定是可預測的。一旦將程式碼應用到伺服器,在下次應用程式碼之前不應該有任何變化。然而,伺服器和伺服器程式碼是狡猾的:
- 有時伺服器因明顯原因而改變,例如有人登入並手動進行更改
- 團隊可能使用不同的工具或流程管理伺服器的某些方面,例如安全補丁
- 即使伺服器沒有變化,多次應用相同的伺服器設定程式碼也可能引入差異,例如使用中央設定登入檔中的引數
- 套件是另一個外部變更來源,可能更新到較新版本
透過定期自動重新應用伺服器設定程式碼,確保所有伺服器設定一致,並確保任何差異都能更快地應用。
大多數基礎設施即程式碼工具(如Ansible、Chef和Puppet)都是考慮到這種模式而設計的。透過更新現有伺服器例項比透過構建新例項應用變更快與幹擾更少。
模式:不可變伺服器
不可變伺服器是一個伺服器例項,其設定永遠不會改變。透過建立具有更改設定的新伺服器例項並用它替換現有伺服器來交付更改。
不可變伺服器減少了進行更改的風險。不是對執行中的伺服器例項應用更改,而是建立一個新的伺服器例項。有機會測試新例項,然後將其與前一個例項交換。然後可以檢查新例項是否正常工作,或者如果出現問題,可以將其換回原位。
需要對伺服器設定進行嚴格控制和一致性的組織可能會發現不可變伺服器很有用。例如,執行數千個伺服器映像的電信公司可能決定不對執行中的伺服器應用更改,而是保證其設定的穩定性。
實施不可變伺服器需要一個強大的自動化流程來構建、測試和更新伺服器映像。系統和應用程式設計必須支援在不中斷服務的情況下交換伺服器例項。
儘管名稱如此,不可變伺服器確實會發生變化。設定漂移可能會潛入,特別是如果人們可以登入到伺服器並手動進行更改,而不是使用設定更改流程來構建新的伺服器例項。因此,使用不可變伺服器的團隊應該小心確保執行例項的新鮮度。
伺服器補丁管理
許多團隊習慣於將伺服器補丁作為一個特殊的、單獨的流程。然而,如果有一個自動化管道來交付對伺服器的更改,並持續將伺服器程式碼同步到現有伺服器,可以使用這個相同的流程來保持伺服器的補丁更新。
玄貓曾與的團隊每週(有時每天)提取、測試和交付作業系統和其他核心套件的最新安全補丁。
這種快速、頻繁的補丁流程曾給一位客戶的CIO留下深刻印象。當商業媒體大肆宣傳核心OS套件中的高調安全漏洞時,CIO要求我們放下一切,制定一個解決漏洞的計劃,並估計需要多長時間以及轉移資源的成本影響。當我們告訴他們修復該問題的補丁已經作為當天例行更新的一部分推出時,他們感到非常驚喜。
如何應用伺服器設定程式碼
對於一個伺服器例項,無論是正在構建的新例項、用於構建映像的臨時例項還是現有例項,有兩種模式可以執行伺服器設定工具來應用程式碼:推播和提取。
模式:推播伺服器設定
使用推播伺服器設定模式,在新伺服器例項外部執行的流程連線到伺服器並執行,下載並應用程式碼。
團隊使用推播來避免需要將伺服器設定工具預安裝到伺服器映像上。當需要對現有伺服器的設定更新時間有更高程度的控制時,推播模式很有用。例如,如果有事件(如軟體佈署)在多個伺服器上有一系列活動,可以使用中央流程來協調流程。
推播設定模式需要能夠連線到伺服器例項並透過網路執行設定流程。這個要求可能會建立安全漏洞,因為它開放了攻擊者可能用來連線並對伺服器進行未授權更改的向量。
推播伺服器設定的一種方法是有人從本地電腦執行伺服器設定工具。然而,最好從中央伺服器或服務執行工具,以確保流程的一致性和控制。
一些伺服器設定工具包括管理與機器例項連線的伺服器應用程式,如Ansible Tower。一些公司提供SaaS服務來遠端設定伺服器例項,儘管許多組織不願給第三方這種級別的基礎設施控制權。
在其他情況下,自己實作中央服務來執行伺服器設定工具。通常使用CI或CD伺服器產品構建這個。他們實作CI作業或管道階段,針對特定的伺服器集執行設定工具。作業根據事件觸發,如對伺服器設定程式碼的更改或新環境的建立。
伺服器設定工具需要能夠連線到伺服器例項。儘管一些工具使用自定義網路協定,但大多數使用SSH。每個伺服器例項必須接受來自伺服器工具的SSH連線,允許工具以足夠的許可權執行以應用其設定更改。
對於這些連線,擁有強大的認證和機密管理至關重要。否則,伺服器設定系統對於您的資產來説是一個巨大的安全漏洞。
模式:提取伺服器設定
提取伺服器設定模式涉及在伺服器例項本身上執行的流程來下載和應用設定程式碼。此流程在建立新的伺服器例項時觸發。對於持續同步模式下的現有例項,該流程通常按計劃執行,定期喚醒並應用當前設定。
根據提取的伺服器設定避免了伺服器例項需要接受來自中央伺服器的傳入連線,因此有助於減少攻擊面。該模式簡化了設定由基礎設施平台自動建立的例項,如自動擴充套件和自動還原。
提取設定透過使用預安裝了伺服器設定工具的伺服器映像來工作。如果為新的伺服器例項提取設定,則設定映像在首次啟動時執行工具。
Cloud-init是一個廣泛使用的工具,用於自動執行這種流程。可以使用基礎設施平台的API將引數傳遞給新的伺服器例項,甚至包括要執行的命令和要傳遞給伺服器設定工具的引數:
server:
source_image: stock-linux-1.23
memory: 2GB
vnet: ${APPSERVER_VNET}
instance_data:
- server_tool: servermaker
- parameter: server_role=appserver
- parameter: code_repo=servermaker.shopspinner.xyz
這段設定展示瞭如何在建立伺服器時傳遞初始化資料:
- 基本伺服器設定:指定了映像、記憶體和網路設定
- 例項資料:這是傳遞給新伺服器的設定訊息
- 指定使用"servermaker"作為設定工具
- 設定伺服器角色為"appserver"
- 指定程式碼儲存函式庫位置
這種方法允許伺服器在首次啟動時自行設定,無需外部系統主動連線到它。Cloud-init會讀取這些資料並執行適當的設定指令碼。
設定指令碼從中央儲存函式庫下載設定程式碼並在啟動時應用它。如果使用持續同步來更新執行中的伺服器,設定流程應該設定這一點,無論是執行伺服器設定工具的後台流程,還是執行工具的cron作業。
即使不構建自己的伺服器映像,大多數公共雲供應商提供的映像也預安裝了cloud-init和流行的伺服器設定工具。
去中心化設定
大多數伺服器設定工具提供中央服務,在機器或叢集上執行以集中控制設定程式碼和引數的分發,並管理其他活動。一些團隊更喜歡在沒有中央服務的情況下執行。
團隊去中心化設定的主要原因是簡化基礎設施管理。設定伺服器是另一組需要管理的部分,可能是單點故障。如果設定伺服器關閉,就無法構建新機器,這使其成為災難還原的依賴項。設定伺服器也可能是效能瓶頸,需要擴充套件以處理來自(可能是到)數百或數千個伺服器例項的連線。
要實作去中心化設定,以離線模式安裝和執行伺服器設定工具,例如使用chef-solo而不是chef-client。可能從指令碼執行工具,該指令碼檢查中央檔案儲存函式庫以下載伺服器設定程式碼的最新版本。程式碼儲存在伺服器例項上,因此即使檔案儲存函式庫不可用,工具仍然可以執行。
中央檔案儲存函式庫可能是單點故障或效能瓶頸,就像設定伺服器一樣。但實際上,有許多簡單、高度可靠和高效能的選項來託管像伺服器設定這樣的靜態檔案。這些包括網頁伺服器、網路檔案伺服器和物件儲存服務,如AWS S3。
團隊實作去中心化模式的另一種方式是將伺服器設定程式碼捆綁到系統套件中,如.rpm或.deb檔案,並將其託管在私有套件儲存函式庫中。定期流程執行yum update或apt-get update,安裝或更新套件,將伺服器設定程式碼複製到本地目錄。
其他伺服器生命週期事件
建立、更改和銷毀伺服器例項構成了伺服器的基本生命週期。但在擴充套件生命週期中還有其他有趣的階段,包括停止和重新啟動伺服器、替換伺服器和還原失敗的伺服器。
停止和重新啟動伺服器例項
當大多數伺服器是插入電源的物理裝置時,通常會關閉它們以升級硬體,或在升級某些作業系統元件時重新啟動它們。
人們仍然停止和重新啟動虛擬伺服器,有時出於相同的原因—重新設定虛擬硬體或升級OS核心。有時關閉伺服器以節省託管成本,例如,一些團隊在晚上或週末關閉開發和測試伺服器,如果沒有人使用它們。
然而,如果伺服器易於重建,許多團隊在不使用時簡單地銷毀伺服器,並在需要時再次建立新伺服器。這樣做部分是因為它很容易,可能比簡單地關閉它們節省更多錢。但銷毀和重建伺服器而不是停止和保留它們也符合將伺服器視為"牛"而不是"寵物"的理念。
團隊可能會停止和重新啟動而不是重建,因為他們無法自信地重建伺服器。通常挑戰是保留和還原應用程式資料。
因此,制定不停止和重新啟動伺服器的政策迫使團隊實施可靠的流程和工具來重建伺服器並保持其正常執行。
停止的伺服器也可能使維護活動複雜化。設定更新和系統補丁不會應用於停止的伺服器。根據管理這些型別更新的方式,伺服器可能在再次啟動時接收它們,或者可能錯過它們。
替換伺服器例項
從物理伺服器轉移到虛擬伺服器的眾多好處之一是構建和替換伺服器例項的容易程度。本文描述的許多模式和實踐,包括不可變伺服器和頻繁更新伺服器映像,都依賴於透過構建新伺服器來替換執行中的伺服器的能力。
替換伺服器例項的基本流程是建立新例項,驗證其準備就緒,重新設定其他基礎設施和系統以使用新例項,測試其是否正常工作,然後銷毀舊例項。
根據使用伺服器例項的應用程式和系統,可能可以在不停機或至少最小停機的情況下進行替換。
一些基礎設施平台具有自動化伺服器替換流程的功能。例如,可能對自動擴充套件伺服器叢集中伺服器的定義應用設定更改,指定它應該使用包含安全補丁的新伺服器映像。平台自動增加新例項,檢查其健康狀況,並移除舊例項。
在其他情況下,可能需要自己做替換伺服器的工作。在根據管道的變更交付系統中做到這一點的一種方法是擴充套件和收縮。首先推播增加新伺服器的變更,然後推播移除舊伺服器的變更。
還原失敗的伺服器
雲基礎設施不一定可靠。一些提供商,包括AWS,明確警告他們可能會在沒有警告的情況下終止伺服器例項,例如當他們決定替換底層硬體時。即使是具有更強可用性保證的提供商也有影響託管系統的硬體故障。
還原失敗伺服器的流程類別似於替換伺服器的流程。一個區別是活動的順序—在銷毀舊伺服器後而不是之前建立新伺服器。另一個區別是通常是有目的地替換伺服器,而故障較少是有意的。
與替換伺服器例項一樣,還原可能需要手動操作,或者可能夠設定平台和其他服務以自動檢測和還原故障。
在現代雲端環境中,伺服器程式碼化已成為基礎設施管理的核心實踐。透過本文的探討,我們瞭解了從伺服器角色設計到程式碼測試的完整技術體系,以及如何透過自動化流程建立可靠與一致的伺服器佈署方法。
我們考慮了何時將設定變更應用到伺服器的幾種模式—在進行變更時、透過持續應用程式碼到執行中的伺服器,或透過建立新例項。我們還研究瞭如何應用變更的模式—推播和提取。最後,我們探討了一些其他伺服器生命週期事件,包括停止、替換和還原伺服器。
這些方法中的許多透過自定義伺服器映像工作,然後使用這些映像建立或更新多個伺服器例項。透過實施可靠的自動化變更流程,可以確保快速與可靠地在整個基礎設施中推出變更,並以最小的努力保持所有伺服器與最新批准的套件和設定同步。
無論是選擇烘焙還是炸制、推播還是提取,關鍵在於建立一個自動化、可重複與可靠的流程,使伺服器管理從繁重的手動任務轉變為可預測的程式碼驅動活動。這不僅提高了效率,還大增強了系統的可靠性和安全性。