從 Kubernetes API 伺服器請求流程的變異與驗證 Webhook 機製出發,本文延伸探討如何在基礎設施即程式碼(IaC)中實作類別似的預防性控制。不同於 Kubernetes 原生提供的 Webhook,IaC 的預防性控制大多仰賴外部工具與整合。本文聚焦於 AWS 環境,並以不可變性作為 IaC 的核心最佳實踐,說明如何避免基礎設施漂移,並提升佈署效率與可靠性。同時,文章也比較了指令式與宣告式 IaC 的差異,並輔以程式碼範例說明如何運用 AWS SDK 和工具如 eksctl 管理雲端資源。最後,本文介紹瞭如何在 IaC 中整合 PaC,並運用偵測性、預防性和反應性控制,強化基礎設施的安全性和合規性。
策略即程式碼(Policy as Code)與基礎設施即程式碼(Infrastructure as Code)
在前幾章中,我們探討了策略即程式碼(PaC)在Kubernetes中的應用,並根據第一章介紹的PaC選擇標準評估了多種解決方案。我們的目標是提供多種可供評估的解決方案,讓讀者能夠根據自身需求選擇最合適的工具。每種解決方案都有其優缺點,筆者認為某些解決方案比其他的更成熟和功能更完善。不過,最終的選擇權在讀者手中,我們已提供了一個良好的起點來做出這些決定。
在本章中,我們將把Kubernetes放在一邊,轉而關注基礎設施即程式碼(IaC)以及PaC如何改善IaC的實踐。本章將主要關注AWS中的IaC。
重溫Kubernetes API伺服器請求流程
在完全放棄Kubernetes之前,我們需要確保讀者記住Kubernetes PaC解決方案中最重要的部分:API伺服器請求流程。第四章介紹的Kubernetes API伺服器請求流程(如圖11-1所示)是Kubernetes PaC解決方案中最強大的方面,因為它提供了一種原生方法,將預防性控制應用於Kubernetes叢集。
圖11-1:Kubernetes API伺服器請求流程
圖表翻譯: 此圖示展示了 Kubernetes API 伺服器請求流程,客戶端傳送請求後,API 伺服器將請求轉發至 Webhook 進行變異與驗證處理,最終將處理結果持久化至控制器。
透過將變異和驗證webhook整合到PaC解決方案中,Kubernetes API伺服器請求流程應用了眾所周知的資料輸入轉換和驗證模式,在任何資料被持久化之前進行處理。這些模式至少與1980年代的原始客戶端-伺服器模型一樣古老;此外,筆者在1990年代和2000年代初建立Lotus Notes和Domino應用程式時也使用了它們。
雖然筆者不想過分強調輸入轉換和驗證的重要性,但筆者希望明確指出此功能的重要性,以及它對於構建預防性(而不是偵測性或反應性)控制的重要性。這種預防性立場在各個平台和系統中相對獨特。當我們向前探索IaC以及PaC如何應用於IaC時,我們將討論預防性、偵測性和反應性控制,以及與Kubernetes不同的是,IaC中的預防性控制大多透過非原生實作來完成,在資源被IaC應用之前進行。
基礎設施即程式碼(Infrastructure as Code)
在第一章中,筆者介紹了「一切皆程式碼」(EaC)的概念,並描述了這個概念如何改變我們定義和應用資源和策略的方式,將其轉化為機器可讀的程式碼構件。在此過程中,我們重用了多年來成功使用的程式碼管理工具和技術,例如組態管理和版本控制系統(如Git),就像處理其他「即程式碼」構件一樣。
為什麼基礎設施即程式碼如此重要?
基礎設施即程式碼(IaC)是現代基礎設施佈署和管理的關鍵。它允許我們透過程式碼定義和管理基礎設施,從而實作自動化、可重複性和版本控制。這不僅提高了效率,還減少了人為錯誤,並增強了基礎設施的一致性和可預測性。
# IaC 示例:使用 Terraform 定義 AWS EC2 例項
provider "aws" {
region = "us-west-2"
}
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
# 為例項新增標籤
tags = {
Name = "example-instance"
}
}
#### 內容解密:
此 Terraform 程式碼定義了一個 AWS EC2 例項。它首先指定了要使用的 AWS 提供者及其區域。然後,它定義了一個名為 `example` 的 EC2 例項資源,指定了要使用的 AMI 和例項型別,並為例項增加了一個名稱標籤。這展示瞭如何透過 IaC 管理雲資源。
PaC 在 IaC 中的應用
將 PaC 與 IaC 結合使用,可以進一步提高基礎設施的安全性和合規性。透過在 IaC 中嵌入 PaC,我們可以在基礎設施佈署之前對其進行驗證和策略檢查,從而確保基礎設施符合組織的安全和合規要求。
# PaC 示例:使用 Open Policy Agent (OPA) 驗證 Terraform 組態
package terraform
deny[msg] {
input.resource_type == "aws_instance"
not input.instance_type == "t2.micro"
msg := "Instance type must be t2.micro"
}
#### 內容解密:
此 OPA 策略檢查 Terraform 組態中的 `aws_instance` 資源。如果例項型別不是 `t2.micro`,則觸發拒絕並傳回錯誤訊息。這展示瞭如何使用 PaC 在 IaC 中實施策略控制。
基礎設施即程式碼(IaC)與不可變性最佳實踐
基礎設施即程式碼(IaC)使我們能夠以可測試、可稽核和可重現的方式來組態和管理資料中心型別的資源,就像佈署其他應用程式堆積疊一樣。IaC之所以受到歡迎,是因為它既可供人類閱讀,也可供機器讀取,並且專注於特定的平台和生態系統。儘管IaC通常與雲端運算和雲端服務提供商(CSPs)相關聯,但它也適用於裸機資源。如同第一章所述,有一些工具,如Tinkerbell,能夠促進裸機伺服器的組態。
現在,讓我們來探討IaC的最佳實踐之一:不可變性。
不可變性
IaC顛覆並改變了我們管理基礎設施的方式,特別是在雲端運算基礎設施方面。我們現在普遍使用自動化的CI/CD和GitOps管道及工作流程來管理我們的基礎設施。這種轉變使我們能夠採用不可變基礎設施的已知最佳實踐,即不可變性。
當遵循不可變基礎設施的最佳實踐時,一旦資源被佈署,就不會對其進行更改。這並不意味著資源永遠不能被修改——只是不應該在原地修改。如果資源需要更改,您需要更改資源的底層IaC工件,從而產生資源的新版本。然後,您使用新版本替換現有版本。這與之前的基礎設施管理操作(如在原地修補伺服器)相反。
在雲原生運算中,我們使用不可變基礎設施的概念來檢測和防止不想要或未經授權的更改,例如漂移,這主要是由手動更改引起的。例如,在IaC之前,我們曾經從頭開始構建伺服器,從作業系統(OS)開始。然後,我們會組態OS,安裝和組態實用程式和函式庫,並新增應用程式堆積疊。接著,我們專注於保持伺服器的執行,並根據需要進行修補。
過去的手動管理與自動化演進
在IaC之前,大部分工作都是手動完成的。隨著時間的推移,我們使用指令碼自動化了一些任務;然後,我們轉向了像Puppet和Chef這樣的工具,在DevOps管道和伺服器端排程(如cron)的協調下,疊加了額外的自動化。自動化過程會使用像Chef和Puppet這樣的工具,或自定義指令碼,將當前的伺服器組態與先前定義的狀態進行比較。伺服器程式會定期“喚醒”並檢查組態。如果當前的組態與最初定義的組態有偏差,工具會糾正這種情況,修復漂移,然後進入休眠狀態,直到下一次檢查間隔。
圖表翻譯: 此圖示展示了從手動組態到自動化管理的演進過程。首先是手動組態,然後透過指令碼實作初步自動化,接著採用Puppet或Chef等工具進行更進階的自動化組態管理,最後透過DevOps管道進行協調並實作定期檢查與漂移修正。
漂移的成因與影響
漂移不僅僅是由於手動更改所致。漂移也可能來自於應用錯誤的IaC工件。隨著時間的推移,IaC工件和工具會老化甚至腐化。工具會發生變化,程式碼會變得過時。錯誤的更改以過時和可能衝突的程式碼形式出現,可能會導致不想要的可變性,從而導致漂移。
環境的複雜性也可能導致漂移。這些環境不僅包括目標基礎設施的堆積疊,還包括用於交付IaC更改的自動化工具。如果複雜性沒有被適當地管理和分割或隔離,那麼不想要的更改可能會跨越環境邊界洩露並導致漂移。
烘焙 vs. 煎炒
「烘焙」與「煎炒」的概念簡化了在IaC中從已知良好且完整的組態(如映像或IaC檔案)開始構建新資源,與建立不完整的資源然後更改它直到達到所需的組態狀態之間的區別。「烘焙」代表從一開始就具備所需的一切,而「煎炒」則是從最小組態開始,不斷新增直到滿意為止。這兩個術語主要適用於計算和容器資源。
隨著我們逐漸成熟並記錄我們的實踐,我們開始使用已知良好且完整的映像和組態,從而減少了「煎炒」的次數,更多地依賴「烘焙」。我們消除得越多「煎炒」(本地組態),就越能避免漂移和錯誤,也就越接近不可變性。當我們減少「煎炒」時,我們意識到計算資源的重要性降低了,至少比整個應用程式堆積疊的重要性要低。過去,我們透過伺服器執行時間指標來衡量成功;現在,我們透過應用程式可用性、基礎設施和應用程式效能、安全性以及漂移和漏洞的減少來衡量成功,所有這些都帶來了更好的使用者經驗。
烘焙 vs. 煎炒的實踐意義
def bake_image(config):
# 從已知良好的基礎映像開始
base_image = get_base_image()
# 新增必要的組態和應用程式
for component in config['components']:
add_component(base_image, component)
# 傳回最終的映像
return base_image
def fry_server(server):
# 從最小組態的伺服器開始
minimal_config = get_minimal_config()
apply_config(server, minimal_config)
# 逐步新增組態直到滿足需求
while not is_config_satisfied(server):
next_config = get_next_config_step(server)
apply_config(server, next_config)
return server
內容解密:
此段程式碼展示了「烘焙」與「煎炒」兩種不同的資源組態方法。在bake_image函式中,我們從一個已知的良好基礎映像開始,逐步新增所需的元件以建立最終映像。這種方法確保了環境的一致性和可預測性。在fry_server函式中,我們則是從一個最小組態的伺服器開始,然後逐步新增組態直到達到所需的狀態。這種方法容易引入漂移和錯誤,因此在現代IaC實踐中較少被採用。
IaC 的兩種型別:命令式與宣告式
探討完IaC的不可變性後,讓我們來看看IaC的兩種型別:命令式(imperative)和宣告式(declarative)。
命令式 IaC
命令式IaC關注的是實作特定狀態所需的步驟。它描述瞭如何達到預期的組態狀態,通常涉及一系列命令或操作。這種方法類別似於編寫指令碼來一步步地組態系統。
宣告式 IaC
宣告式IaC則是描述系統應該達到的最終狀態,而不關心具體實作步驟。它定義了預期的組態狀態,讓IaC工具自行決定如何達到該狀態。這種方法更符合不可變性的原則,因為它關注的是結果而非過程。
# 宣告式 IaC 示例
resources:
- type: server
properties:
cpu: 2
memory: 4GB
os: ubuntu:latest
內容解密:
此YAML檔案定義了一個宣告式的IaC組態。它描述了一個伺服器資源應該具備的屬性,如CPU數量、記憶體大小和作業系統版本。這種宣告式的方法讓IaC工具負責實作這些屬性,而無需指定具體的操作步驟。
隨著技術的不斷進步,IaC工具和方法論將繼續演化,以更好地支援雲原生應用程式和微服務架構。未來,我們可以期待看到更多自動化和智慧化的IaC解決方案,這些方案將進一步簡化基礎設施的管理,提高佈署的速度和可靠性,並增強整體的IT營運效率。此外,隨著安全性和合規性要求的提高,IaC也將在確保基礎設施安全和符合監管要求方面發揮更加重要的作用。
指令式與宣告式基礎設施即程式碼(IaC)
在定義程式碼變更程式狀態的步驟以達成預期目標時,我們採用的是指令式程式設計。在基礎設施即程式碼(IaC)的背景下,我們使用 Java、Go、Python 和 TypeScript 等程式語言,並藉助 SDK 來進行指令式程式設計。在雲端服務提供商(CSP)的環境中,SDK 提供特定語言的函式庫,呼叫 API 以佈建和查詢 CSP 基礎設施並構建應用程式。
以下程式碼範例展示瞭如何使用 AWS Golang SDK 取得 Amazon Elastic Container Registry(ECR)客戶端物件,並列出來源 ECR 儲存函式庫中的映像:
// Go SDK ECR 操作
ecrClient := ecr.NewFromConfig(cfg)
input := &ecr.ListImagesInput{
RepositoryName: aws.String(REPO),
}
resp, err := ecrClient.ListImages(context.TODO(), input)
if err != nil {
log.Fatal(err)
return
}
fmt.Println("Listing tags in ", aws.String(REPO))
for _, img := range resp.ImageIds {
fmt.Println("Digest: ", *img.ImageDigest)
if img.ImageTag != nil {
// 避免對未標記的映像進行空指標解參照
fmt.Println("Tag: ", *img.ImageTag)
}
}
內容解密:
這段 Go 程式碼使用 AWS SDK 與 Amazon ECR 互動。首先,它根據組態建立一個 ECR 客戶端。接著,它準備一個 ListImagesInput 結構,指定要列出映像的儲存函式庫名稱。然後,它呼叫 ListImages 方法來檢索映像 ID。如果發生錯誤,程式將記錄錯誤並離開。成功時,它會遍歷回應中的映像 ID,列印每個映像的摘要和標籤(如果存在)。這段程式碼展示瞭如何使用 AWS SDK 以程式設計方式管理 ECR 中的映像。
SDK 在需要使用特定程式語言構建應用程式,並且該應用程式需要與 CSP 的受管服務互動時非常有用。
宣告式程式設計常用於管理資源狀態。當你使用結構化資料語言(如 JSON 或 YAML)編寫程式碼,並定義你希望管理的資源的期望狀態時,你就是在進行宣告式程式設計。宣告式程式設計需要一個處理層來應用程式碼中定義的變更。
以下 YAML 範例展示了宣告式程式設計,使用程式碼定義根據 eksctl YAML 語法的 Amazon Elastic Kubernetes Service(EKS)叢集狀態:
# eksctl 叢集定義
---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: test-cluster
region: us-west-2
version: "1.23"
tags:
owner: jimmy
env: dev
billing: lob-cc
iam:
withOIDC: true
secretsEncryption:
keyARN: "arn:aws:kms:us-west-2:123456789012:key/..." # eks-secrets-uw2
vpc:
id: "vpc-..."
cidr: "192.168.0.0/16"
subnets:
private:
private-1:
id: "subnet-..."
cidr: "192.168.0.0/20"
az: us-west-2a
private-2:
id: "subnet-..."
cidr: "192.168.16.0/20"
az: us-west-2b
private-3:
id: "subnet-..."
cidr: "192.168.32.0/20"
az: us-west-2c
public:
public-1:
id: "subnet-..."
cidr: "192.168.48.0/20"
az: us-west-2a
public-2:
id: "subnet-..."
cidr: "192.168.64.0/20"
az: us-west-2b
public-3:
id: "subnet-..."
cidr: "192.168.80.0/20"
az: us-west-2c
clusterEndpoints:
publicAccess: true
privateAccess: true
cloudWatch:
clusterLogging:
enableTypes: ["*"]
addons:
- name: vpc-cni
version: latest
attachPolicyARNs:
- arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
- name: kube-proxy
version: latest
- name: coredns
version: latest
內容解密:
這段 YAML 程式碼定義了一個 EKS 叢集的組態,包括叢集名稱、區域、版本、標籤、IAM 設定、金鑰加密、VPC 和子網路組態,以及叢集端點和日誌記錄設定。它還指定了要安裝的附加元件,如 vpc-cni、kube-proxy 和 coredns。這段 YAML 將被 eksctl CLI 處理,以建立具有特定組態的 EKS 叢集。
本文介紹了指令式和宣告式 IaC 程式設計之間的主要區別。指令式程式設計透過定義程式碼執行的步驟來管理程式狀態,而宣告式程式設計則定義資源的期望狀態,並由處理器(如 eksctl 和 AWS CloudFormation)進行處理。
值得注意的是,指令式和宣告式程式設計模型並非總是相互排斥的。AWS Cloud Development Kit(CDK)結合了這兩種模型,你可以編寫指令式程式碼,然後輸出宣告式程式碼,由 AWS CloudFormation 服務處理。
將政策即程式碼(PaC)應用於 IaC
我們使用 PaC 對 IaC 資源和流程實施控制,包括安全、合規、治理、財務營運和最佳實踐。這些控制措施通常分為三類別:
- 偵測性控制:記錄和目錄不符合規範的問題,通常用於通知和進一步處置。
- 預防性控制:用作「護欄」,嵌入被控制的系統或平台中,直接由系統事件觸發,防止變更發生。
- 反應性控制:類別似於偵測性和預防性控制,用於避免或記錄不符合規範的變更或行為,其應用由內部系統事件觸發,但發生在系統變更之後。反應性控制的有效性取決於其對系統事件的反應速度。
將 PaC 控制型別應用於 IaC,可以確保基礎設施組態符合組織的政策要求。
IaC 與 PaC 的整合
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title IaC 策略程式碼整合與最佳實踐
package "安全架構" {
package "網路安全" {
component [防火牆] as firewall
component [WAF] as waf
component [DDoS 防護] as ddos
}
package "身份認證" {
component [OAuth 2.0] as oauth
component [JWT Token] as jwt
component [MFA] as mfa
}
package "資料安全" {
component [加密傳輸 TLS] as tls
component [資料加密] as encrypt
component [金鑰管理] as kms
}
package "監控審計" {
component [日誌收集] as log
component [威脅偵測] as threat
component [合規審計] as audit
}
}
firewall --> waf : 過濾流量
waf --> oauth : 驗證身份
oauth --> jwt : 簽發憑證
jwt --> tls : 加密傳輸
tls --> encrypt : 資料保護
log --> threat : 異常分析
threat --> audit : 報告生成
@enduml圖表翻譯:
此圖表展示了指令式 IaC 和宣告式 IaC 如何與雲端資源互動,以及 PaC 控制如何確保這些資源符合規範。指令式 IaC 使用 SDK 和 API 直接操作雲端資源,而宣告式 IaC 定義資源的期望狀態,由處理器(如 eksctl)負責建立或更新資源。PaC 控制透過偵測、預防和反應機制,確保資源組態符合組織的政策要求,從而實作合規與治理。