IaC Part 7-配置Stack Instance
使用單一stack code project可以較輕易地維護基礎設施的多個具一致性的 instance。但是,我們通常需要stack中有不同insatnce。 例如,我們通常需要將開發環境中的伺服器數量設置為比生產環境的要少。下面是stack code的範例,它定義了具有可配置的最小和最大伺服器數量的容器託管集群:
container_cluster: web_cluster-${environment}
min_size: ${cluster_minimum}
max_size: ${cluster_maximum}
我們可以將不同的參數值(不同的環境,如下圖)傳入上面stack code的範例。
Terraform 和 CloudFormation 這一類的stack tool支援多種設定參數值的方式。 這些通常包括在CLI上傳遞參數、從檔案中讀取它們以及從key-value store。
管理基礎設施的團隊需要決定如何使用這些功能來管理配置參數值並將其傳遞給他們的IaC工具。 確保參數值是被定義的並能具一致性的應用於每個環境至關重要。
但在參數設計上,我們需要保持簡單。以下是幾點建議:
- 用簡單的參數類型,例如string、number、list和key-value map。 避免傳遞更複雜的資料結構。
- 盡量減少stack中的參數數量。 避免使用“可能”需要的參數。 只有當我們立即需要時才加入參數。
- 避免使用參數成為conditionals,這會在基礎架構中產生顯著差異。 例如,在stack內啟用服務的Boolean(yes/no)參數會增加複雜性。
使用Stack參數來建立 Unique identifiers
如果我們從同一project建立多個stack instances,我們可能會看到需要unique identifiers的基礎設施資源出現失敗。 為什麼呢?請看以下定義應用程序伺服器的偽代碼:
server:
id: webserver
subnet_id: webserver-subnet
雲端平台要求 id 值是唯一的,因此當我們運行 stack 命令建立第二個stack時,它失敗了:
> stack up environment=test --source mystack/src
SUCCESS: stack 'test' created
> stack up environment=staging --source mystack/src
FAILURE: server 'webserver' already exists in another stack
我們可以在stack code中使用參數來避免這些衝突。 更改代碼以取得environment的參數,並使用它來分配給 unique server ID。 然後將伺服器加到每個environemnt中的不同subnet中:
server:
id: webserver-${environment}
subnet_id: webserver-subnet-${environment}"
Stack參數範例
我們將使用一個範例來比較和對比本文中的不同的stack configuration模式。 該範例是一個template stack project,它定義了一個容器集群,由動態主機節點池和一些網路結構組成。 以下顯示了project結構。
├── src/
│ ├── cluster.infra
│ └── networking.infra
└── test/
Cluster stack將下圖中列出的參數用於三個不同的stack instance。 environment是每個環境的 unique ID,可用於命名並建立unique identifiers。 cluster_minimum 和 cluster_maximum 定義容器主機集群的數量範圍。 檔案cluster.infra 中的基礎設施代碼定義了雲端平台上的集群,該集群根據負載擴展主機節點的數量。 test、staging和production這三個環境中的每一個都使用一組不同的值。
配置Stack的參數
我們已經研究了為什麼需要參數化stack以及IaC工具如何實現參數。 現在我們將討論一些用於管理參數並將其傳遞給IaC工具的正確和錯誤模式:
- 手動的stack參數 —
用CLI的方式傳遞參數 - 腳本化參數 —
在scripts中的每個insatnce把參數hardcode在裡面 - Stack configuration file —
在stack code project建立一個configuration file,並在檔案中的每一個instance宣告其參數 - Wrapper Stack—
針對不同的的insatnce個別建立stack project,並把在stack code中把 shared module import進來 - Pipeline stack參數 —
在Pipeline stage的配置中為每個instance定義參數值。 也就是Stack parameter registry模式:將參數值存儲在一個集中的地方。
錯誤模式: 手動的stack參數
這是一種簡單與直覺的方式(用CLI)。如下範例:
> stack up environment=production --source mystck/src
FAILURE: No such directory 'mystck/src'
> stack up environment=production --source mystack/src
SUCCESS: new stack 'production' created
> stack destroy environment=production --source mystack/src
SUCCESS: stack 'production' destroyed
> stack up environment=production --source mystack/src
SUCCESS: existing stack 'production' modified
動機
在CLI上輸參數非常簡單,這在我們學習如何使用工具時很有幫助。 在進行實驗也是有用的。
後果
但在CLI中輸入參數時很容易出錯。 記住要輸入哪些參數也可能很困難。 對於重要的基礎設施,我們可能不希望在進行改進或修復時因錯誤命令而意外破壞重要的東西。 當一堆人在Infra stack上作業時,我們不能指望每個人都記住為每個insatnce輸入的正確參數值。 手動的stack參數不適合自動將基礎設施代碼應用到環境中,例如 CI 或 CD。
相關模式
腳本化參數模式採用我們要輸入的命令並將其放入腳本中。 pipeline stack參數模式執行相同的操作,但將它們放入pipeline configuration而不是腳本中。
模式: Stack Environment Variables
這是將參數值設定為environment variables來讓IaC工具使用。 此模式通常與另一種模式結合來設置environment variables。
environment variables是預先設置的,如下所示。
export STACK_ENVIRONMENT=tproduction
export STACK_CLUSTER_MINIMUM=1
export STACK_CLUSTER_MAXIMUM=10
export STACK_SSL_CERT_PASSPHRASE="cloud first"
它有不同的實做選項,但最基本的一種是讓stack code直接引用它們,如示下範例。
container_cluster: web_cluster-${ENV("STACK_ENVIRONMENT")}
min_size: ${ENV("STACK_CLUSTER_MINIMUM")}
max_size: ${ENV("STACK_CLUSTER_MAXIMUM")}
動機
大部分的雲端平台都支援environment variables,通常它能很容易進行。
適用性
如果我們的團隊已經有在其他的IT系統中使用environment variables並且有合適的機制來管理它們,我們可能會發現將它們用作stack參數很方便。
後果
我們可以從本文中的其他模式來取得要設置的參數值。 但這樣做會增加移動部件,以致於很難追蹤任何特定stack instance的參數值,並且需要更多作業來更改這些參數值。直接在stack code中 hardcode environment variables可能會將stack code與runtime environment耦合得太緊密。在environment variables中設置secrets會有資安疑慮。
實行
同樣的,我們需要設置要使用的environment variables,這意味著從本文中選擇另一個模式。 例如,如果我們希望技術人員在本地環境中設置environment variables來應用stack code,那麼我們正在使用手動stack參數模式。 我們可以在運行IaC工具的腳本中設置它們,或者讓pipeline工具設置它們。另一種方法是將這些參數值放入技術人員或insatnce 的本地環境的腳本中。 這是stack configuration file模式的變體。
或者,我們可以將environment values構建到運作IaC工具的compute instance中。 例如,如果我們配置一個單獨的 CD agent node來運作IaC工具以在每個環境中構建和更新stack,則構建該節點的代碼可以將適當的參數值設置為environment variables。 這些environment variables可用於節點上運行的任何命令,包括IaC工具。
但要做到這一點,我們需要將參數值傳遞給構建agent nodes的代碼。因此,您需要從本文中一個模式來做到這一點。實現此模式的另一方面是IaC工具如何取得environment variables。 我們可以使用stack orchestration script來取得environment variables並將它們傳遞給IaC工具。 orchestration script中的代碼如下所示:
stack up \
environment=${STACK_ENVIRONMENT} \
cluster_minimum=${STACK_CLUSTER_MINIMUM} \
cluster_maximum=${STACK_CLUSTER_MAXIMUM} \
ssl_cert_passphrase="${STACK_SSL_CERT_PASSPHRASE}"
此方法將stack code與其運行的環境解耦(decouples)。
相關模式
本文中的任何其他模式都可以與此模式組合來設定環境參數值。
模式: 腳本化參數
腳本化參數涉及將參數值hardcode到運作IaC的腳本中。 我們可以為每個環境編寫單獨的腳本,也可以編寫包含所有環境的參數值的單一腳本:
if ${ENV} == "test"
stack up cluster_maximum=1 env="test"
elsif ${ENV} == "staging"
stack up cluster_maximum=2 env="staging"
elsif ${ENV} == "production"
stack up cluster_maximum=7 env="production"
end
動機
用腳本取得每個instance的參數值是很簡單的方法,避免了手動輸入參數模式的問題。 我們可以確信每個環境都會一致性地使用這些參數值。 通過將腳本放到版控,我們可以確保追蹤配置值的任何變更。
適用性
當我們有一組不經常更改的固定環境時,stack provisioning script是設置參數的有效方法。 它不需要本文中其他一些模式的額外移動部分。
由於在腳本中hardcode secret是錯誤的,因此此模式不適合有secret的選項。 這並不意味著我們不應該使用此模式,只是您需要實現一個單獨的模式來處理secret。
後果
隨著時間的推移,用於運作IaC工具的命令變得複雜是很常見的。 Provisioning scripts可能會變得混亂不堪。 我們在後面的文章會討論如何使用這些腳本,並概述了保持它們可維護性的陷阱和建議。 我們應該測試Provisioning scripts,因為它們可能是其配置系統出現問題的根源。
實行
此模式有兩種常見的實做。 一種是單一腳本,它將environment作為CLI 參數,並為每個環境提供的參數值(如下範例)。
#!/bin/sh
case $1 in
test)
CLUSTER_MINIMUM=1
CLUSTER_MAXIMUM=1
;;
staging)
CLUSTER_MINIMUM=1
CLUSTER_MAXIMUM=3
;;
production)
CLUSTER_MINIMUM=3
CLUSTER_MAXIMUM=9
;;
*)
echo "Unknown environment $1"
exit 1
;;
esac
stack up \
environment=$1 \
cluster_minimum=${CLUSTER_MINIMUM} \
cluster_maximum=${CLUSTER_MAXIMUM}
另一種是為每一個 stack instance建立個別腳本,如下範例所示。
jason-infra-stack/
├── bin/
│ ├── test.sh
│ ├── staging.sh
│ └── production.sh
├── src/
└── test/
這些腳本中的每一個都是相同的,但其中hardcode了不同的參數值。 腳本較小,因為它們不需要在不同參數值之間有邏輯選擇。 然而,它們需要更多的維護。 如果需要更改命令,則需要在所有腳本中進行更改。 為每個環境建立腳本也會讓技術人員傾向客製不同的環境,進而產生不一致性。
將一個或多個配置腳本放到source control。 腳本放在與其提供的stack相同的project中可確保它與stack code保持同步。 例如,如果加新入參數,請將其加入到source code以及provisioning script中。 我們就可以都知道針對特定版本的stack code運行哪個版本的腳本。
如之前所說的,我們不應該將secret hardcode到腳本中,因此我們需要對它們使用不同的模式。 我們可以使用腳本來支援該模式。 在下面範例中,CLI tool按照parameter registry模式從secret manager取得secret。
...
# (參照之前提及的環境參數範例)
...
SSL_CERT_PASSPHRASE=$(some-tool get-secret id="/ssl_cert_passphrase/${ENV}")
stack up \
environment=${ENV} \
cluster_minimum=${CLUSTER_MINIMUM} \
cluster_maximum=${CLUSTER_MAXIMUM} \
ssl_cert_passphrase="${SSL_CERT_PASSPHRASE}"
CLI連接到secret manager並使用 ID /ssl_cert_passphrase/${ENV} 檢索相關環境的secret。 此範例是假設session被授權使用secret manager。 基礎設施開發人員可以在運行此腳本之前使用該工具啟動session。 或者,運行腳本的compute instance可能被授權用secretless authorization的方式檢索secret。
相關模式
Provisioning script會run CLI tool,因此這是一種超越手動stack 模式的方法。 stack configuration file模式將腳本中的參數值提取到單別的檔案中。
模式:Stack Configuration Files
Stack configuration files在個別的檔案中管理每個instance的參數值,我們可以使用在版控中管理該檔案,如下範例所示。
├── src/
│ ├── cluster.infra
│ ├── host_servers.infra
│ └── networking.infra
├── environments/
│ ├── test.properties
│ ├── staging.properties
│ └── production.properties
└── test/
動機
為stack instance建立configuration file非常簡單且易於理解。 由於該檔案已提交到source code repo,因此很容易:
- 查看任何給特定環境使用哪些參數值(“生產環境的最大集群大小是多少?”)
- 追蹤debugg的歷史記錄(“最大集群大小何時發生變化?”)
- 稽核變更(“誰更改了最大集群大小?”)
Stack configuration file強制將configuration與stack code分離。
合適性
當環境的數量變化很少時,stack configuration files是合適的。 它們要求我們將檔案加到project中以加新入的stack instance。 它們還需要(並幫助強制執行)不同instance的建立和更新方式有一致性的邏輯,因為Configuration file不能包含邏輯。
後果
當我們想要建立新的stack instance時,我們需要在stack project中加入新的configuration file。 這樣做可以防止您我們隨便的建立新環境。 我們在之前的系列文章中有提到一種依賴於自動創建立環境的"測試環境"管理方法。 我們可以on-demand的方式為臨時環境創建立configuration來解決此問題。
參數檔案可能會增加change delivery pipeline中變更下游環境配置的衝突。對stack project code的每次變更都必須經過pipeline的每個階段,然後才能應用於生產環境。 這可能需要一段時間才能完成,並且當configuration chnage只應用於生產環境時不會增加任何參數值。
定義參數值可能會導致配置腳本相當複雜。 後面的系列文章中會詳細討論這一點,但作為一個預告,考慮到團隊通常希望為stack project和環境定義預設參數值,然後需要邏輯將它們組合成不同環境中特定stack的特定instance的參數值。 參數值的繼承模型可能會變得混亂。
source control中的configuration file不應包含secret。 因此,對於secret,需要在本文中提到的附加模式來處理,或在source code control 之外實現一個單獨的secret configuration file。
實行
我們可以在每個環境的個別檔案中定義stack參數值。參數值檔案的範例內容如下:
env = test
cluster_minimum = 1
cluster_maximum = 2
Run “stack” command時傳遞相關參數檔案的路徑:
stack up --source ./src --config ./environments/test.properties
如果系統由多個stack組成,那麼管理所有環境中的configuration可能會變得一團亂。 在這些情況下,有兩種常見的管理參數檔案的方法。 一種是將所有環境的configuration file與每個stack的代碼放在一起:
├── cluster_stack/
│ ├── src/
│ │ ├── cluster.infra
│ │ ├── host_servers.infra
│ │ └── networking.infra
│ └── environments/
│ ├── test.properties
│ ├── staging.properties
│ └── production.properties
│
└── appserver_stack/
├── src/
│ ├── server.infra
│ └── networking.infra
└── environments/
├── test.properties
├── staging.properties
└── production.properties
另一種是將所有的configuration file集中在同一個地方:
├── cluster_stack/
│ ├── cluster.infra
│ ├── host_servers.infra
│ └── networking.infra
│
├── appserver_stack/
│ ├── server.infra
│ └── networking.infra
│
└── environments/
├── test/
│ ├── cluster.properties
│ └── appserver.properties
├── staging/
│ ├── cluster.properties
│ └── appserver.properties
└── production/
├── cluster.properties
└── appserver.properties
每種方法如果沒有好好管理還是會變得一團亂。 當我們需要對環境中的所有內容進行變更時,在數十個stack project中變更configuration file是很痛苦的。 當我們需要在單一stack所處的各種環境中更改其配置時,在充滿配置的tree中搜索數十個其他stack也一樣痛苦。
如果我們想使用configuration file來提供secrets,而不是使用單獨的secret模式,那麼我們應該在簽入source control的project code之外管理這些檔案。對於本地開發環境,我們可以要求開發人員在"設定位置"手動建立檔案。 將檔案位置傳遞給 stack command,如下所示:
stack up --source ./src \
--config ./environments/staging.properties \
--config ../.secrets/staging.properties
在上面的範例中,我們向IaC工具提供兩個 config 參數,它會從這兩個參數讀取參數值。 我們在project folder之外有一個名為 .secrets 的目錄,因此它不在source control的管理中。
從 CD pipeline agent等compute insatnce自動運行IaC工具時,執行此操作可能會比較棘手。 我們還是可以將類似的secret屬性檔案配置到這些compute instance上,但這可能會將secret暴露給在同一agent上運行的其他processes。 我們還需要向agent構建的compute instance的processes提供secrets,因此仍然存在bootstrapping問題。
相關模式
將configuration values放入檔案可以簡化腳本化參數中描述的provisioning scripts。我們可以通過使用stack parameter registry模式來避免環境配置檔案的一些限制。 執行此操作會將參數值從stack project code移出至集中位置,這樣我們就可以使用不同的代碼和配置的工作流程。
模式: Wrapper Stack
Wrapper stack使用Infra stack project為每個instance作為wrapper來import “stack code component”。 每個wrapper project都定義stack的一個insatnce的參數值。 然後它導入一個由所有stack instance共享組件(如下圖)。
動機
Wrapper stack利用IaC工具的module功能或library來支援跨stack insatnce重複使用且共享的代碼。 我們可以使用該工具的module版控、依賴項管理和工件存儲庫功能來實現change delivery pipeline。借助wrapper stack我們可以使用用於定義基礎架構的相同語言編寫用於provisioning和configuring stack的邏輯,而不是像使用provisioning script那樣使用個別的語言。
後果
Component在stack和有代碼的組件之間增加了額外的複雜性。 我們現在有兩個level:wrapper project和stack code的組件。
由於每個stack instance都有一個單獨的code project,因此技術人員可能會想為每個instance加上自定義邏輯。 自定義instance代碼使我們的codebase不一致且難以維護。
由於我們在source control中管理的wrapper project中定義了參數值,因此我們無法使用此模式來管理secrets。 所以需要加上本文中的另一個模式來為stack管理secrets。
實行
每個stack instance都有一個單獨的infra stack project。 例如,每個環境都有一個單獨的 Terraform project。 我們可以像copy-past環境一樣實現此功能,每個環境都位於單獨的repo中。 或者,每個environment project可以是單一個repo中的一個folder:
my_stack/
├── test/
│ └── stack.infra
├── staging/
│ └── stack.infra
└── production/
└── stack.infra
根據工具的實做,將stack的infra code定義為module。 我們可以將module code與wrapper stacks放在同一repo中。 但是,這會妨礙我們利用module版本功能。 也就是說,我們將無法在不同的環境中使用不同版本的Infra code,這對於逐步測試代碼至關重要。
以下範例是一個wrapper stack,它導入名為 container_cluster_module 的module,指定該module的版本以及要傳遞給它的配置參數:
module:
name: container_cluster_module
version: 4.56
parameters:
env: production
cluster_minimum: 3
cluster_maximum: 9
Module的project結構可能看起來會像這樣子:
├── container_cluster_module/
│ ├── cluster.infra
│ └── networking.infra
└── test/
當我們對module code進行變更時,我們可以測試並將其上傳到module repo。 repo的作業方式取決於我們IaC工具。 然後,我們可以更新我們的test stack instance以導入新的module version並將其應用到測試環境。 Terragrunt 是一個stack orchestration tool,它能實現wrapper stack模式。
模式: Pipeline Stack Parameters
使用pipeline stack parameters模式,我們可以在delivery pipeline的配置中為每個insatnce定義參數值(如下圖)。
我們後面會解釋如何使用change delivery pipeline將Infra stack code應用到環境。我們可以使用 Jenkins這一類的工具來實現pipeline。
動機
如果我們正在使用pipeline tool來運作Infra stack tool,它提供了用於存儲參數值並將參數值傳遞的機制。 假設我們的pipeline tool本身是由代碼配置的(也就是自行開發的),那麼這些參數值將被定義為代碼並存儲在版控中。
Configuration vlaues與Infra code分開。 我們可以更改下游環境的configuration values並立即應用它們,而無需從piepline開始就進行infra code的新版本。
適用性
已經使用pipeline將infra code應用到環境的團隊可以輕易地利用它來為每個環境設置stack參數。 但是,如果stack需要多個參數值,則在pipeline configuration中定義這些參數值會產生嚴重的缺點,因此我們應該避免這種情況。
後果
通過在pipeline configuration中定stack insatnce variables,我們可以confoguration vlaues與交付流程結合起來。 但風險在於存在pipeline configuration會變得複雜且難以維護。
在pipeline中定義的configuration vlaues越多,在pipeline外運作IaC工具就越困難。 pipeline可能會成為單點故障 — 在恢復pipeline之前,我們可能無法在緊急情況下進行修復、回復或重建環境。 團隊可能很難發展在pipeline之外測試stack code。
一般來說,最好保持應用stack project的pipeline configuration盡可能小和簡單。 大多數邏輯應該存在於pipeline會呼叫的腳本中,而不是存在於pipeline configuration中。
實行
參數應使用pipeline工具的“as code”configuration來實現。 以下範例代碼是一個pipeline stage configuration。
stage: apply-test-stack
input_artifacts: container_cluster_stack
commands:
unpack ${input_artifacts}
stack up --source ./src environment=test cluster_minimum=1 cluster_maximum=1
stack test environment=test
上面的範例在command line上傳遞參數值。 我們還可以將它們設置為stack code使用的environment variables,如下範例。
stage: apply-test-stack
input_artifacts: container_cluster_stack
environment_vars:
STACK_ENVIRONMENT=stage
STACK_CLUSTER_MINIMUM=1
STACK_CLUSTER_MAXIMUM=3
commands:
unpack ${input_artifacts}
stack up --source ./src
stack test environment=stage
在上面範例中,pipeline tool在run command之前設置這些environment variables。 許多pipeline提供secrets管理功能,我們可以使用這些功能將secrets傳遞給stack command。 我們可以以某種方式在pipeline中設置secrets values,然後可以在pipeline job中引用它們,如以下範例。
stage: apply-test-stack
input_artifacts: container_cluster_stack
commands:
unpack ${input_artifacts}
stack up --source ./src environment=production \
cluster_minimum=3 \
cluster_maximum=9 \
ssl_cert_passphrase=${STACK_SSL_CERT_PASSPHRASE}
相關模式
定義commands和參數以在pipeline configuration中為每個環境應用stack code類似於腳本化參數模式。 區別在於腳本所在的位置 — — pipeline configuration中與腳本檔案中。
模式: Stack Parameter Registry
Stack parameters registry是在一個集中位置管理stack insatnce的參數值,而不是使用stack code。 當IaC工具將stack code應用於特定insatnce時,IaC工具會檢索相關參數值(如下圖)。
動機
將參數值存儲在registry中將configuration與implementation分開。 Registry中的參數可以通過不同的工具、使用不同的語言和技術來設置、使用和查看。 這種靈活性減少了系統不同部分之間的耦合度。 我們可以替換任何使用registry的工具,而不會影響也使用該registry的任何其他工具。
由於stack parameter registry與工具無關,因此可以作為基礎設施甚至system configuration的唯一來源,也就是 CMDB(Configuration Management Database)。 這一類的資料在受監管的環境中非常有用,可以產生稽核報告。
適用性
如果我們將configuration registry用於其他目的,那麼將其用作stack parameter registry也是有意義的。 例如,configuration registry是整合多個stack的有用方法。
後果
Stack parameter registry需要一個configuration registry,它是整個系統的額外可移動的部分。 Registry是stack依dependecny,也是潛在的故障點。 如果Registry掛掉,則在恢復之前可能無法重新配置或更新infra stack。 這種依賴性在災難恢復場景中可能會很痛苦,從而將Registry服務置於關鍵路徑上。
將參數值與使用參數值的stack code分開管理需要權衡。 我們可以更改stack instance的配置,而無需更改stack project。 如果一個團隊維護一個可重複使用的stack project,其他團隊可以使用它來建立自己的stack instance,而無需在stack project本身中加入或更改configuration file。
另一方面,在多個地方(stack project和parameter registry)進行變更會增加複雜性和出包的機會。
實行
這是一種可能是存儲 key/value pair的服務,也可能是包含key-value file或key-value file的目錄結構。 無論哪種方式,參數值通常可以存儲在分層結構中,因此我們可以根據環境和stack以及其他因素(例如應用程序、服務、團隊、地理位置或客戶)來存儲它們。 以下是一個 container cluster的configuration registration entries範例。
└── env/
├── test/
│ └── cluster/
│ ├── min = 1
│ └── max = 1
├── staging/
│ └── cluster/
│ ├── min = 1
│ └── max = 3
└── production/
└── cluster/
├── min = 3
└── max = 9
當我們將Infra stack code變成於instance時,IaC工具使用key來檢索相關vlaue。 我們需要將""environment"參數傳遞給IaC工具,代碼使用它來引用registry中的相關位置:
cluster:
id: container_cluster-${environment}
minimum: ${get_value("/env/${environment}/cluster/min")}
maximum: ${get_value("/env/${environment}/cluster/max")}
代碼中的get_registry_item()函數將會查找value。
這個實作將我們的stack code和configuration registry聯繫起來。 我們需要registry來運行和測試代碼,這可能太煩人了。 我們可以暫時把registry的參數值暫時copy到scripts中,然後將它們作為普通參數傳遞給stack code。 這樣做可以讓我們靈活地以其他方式設置參數值。 對於可重複使用的stack code,這特別有用,為代碼的技術人員提供了更多如何配置其stack instance的選項。
Secrets management service是一種特殊類型的paramater registry。 如果使用得當,它們可以確保secrets只提供給需要它們的人和服務。 某些configuration registry產品和服務可用於存儲secrets和non-secrets。 但重要的是要避免將secret存儲沒有受保護的registry中。
相關模式
我們可能需要向IaC工具傳遞至少一個參數以指示要使用哪個stack instance的參數。 為此,可以使用stack provisionong script或pipeline stack parameters模式。
甚麼是Configuration Registry及功用
企業如果有許多團隊在具有許多移動部件的大型系統上作業通常會發現configuration registry是有效的做法。 它對於配置stack instance很有用。 它對於管理跨不同stack instance、應用程式和其他服務的整合dependency也很有用。
Registry可以提供有關基礎設施的組成和狀態的資訊來源。 我們可以使用它來建立工具、儀表板和報告,以及監控和稽核系統。 因此值得深入研究如何實現和使用Configuration registry。
實做一個Configuration Registry
建立一個configuration registry有多種方法。 可以使用基礎設施自動化工具提供的現有解決方案(開源或商業版)。 大多數雲端提供商也提提拱這一類的服務。 如果團隊人夠多、夠閒,我們可以自己刻一個。
許多基礎設施自動化toolchain都有configuration registry功能。 這些往往是集中式服務的一部分,還可能包括source code管理、監控、儀表板和command orchestration等功能。 這些例子包括:
- Chef Infra Server
- PuppetDB
- Ansible Tower
- Salt Mine
我們也許可以其他開源工具來運用這些服務。 我們可以編寫一個腳本來探索有關由configuration tool管理的基礎設施的現行狀態資訊。 一些基礎設施工具registry是可擴展的,因此我們可以使用它們來存儲來自其他工具的資料。
然而,這會產生對提供registry service的任何toolchain的依賴。 該服務可能不完全支援與第三方工具的集整合。 它可能不提供保證未來的相容性。
因此,如果我們正在考慮使用基礎設施工具的數資料存儲作為通用的configuration registry,請考慮它的支援程度與會被lock-in的類型。
通用的configuration registry產品
在特定自動化工具的toolchain之外,有許多專用的configuration registry和key-vlaue store DB產品可以選擇。 包括:
- Zookeeper
- etcd
- Consul
- doozerd
它們通常與不同的工具、語言和系統相容,因此最好避免將我們被鎖lock-in在任何特定的toolchain中。
然而,定義資料的存儲方式可能需要相當多的作業。 主要的結構應該像environment/service/application或 service/application/environment還是完全不同的東西? 我們可能需要編寫和維護自定義代碼以將不同的系統與registry整合。 configuration registry為團隊提供了另一項部署和運行的解決方案。
Platform registry services
大多數雲端平台都提供key-value store services,例如AWS SSM Parameter Store。 這些為我們提供了通用的configuration registry產品大部分優點,而無需自行建立與管理其底層基礎設施。 但是,我們會被它lock-in。 在某些情況下,我們可能會發現自己在一個雲端上使用registry service來管理在另一個雲端平台上的基礎設施。
DIY自己的configuration registry
一些團隊不是使用configuration regisry server,而是通過將configuration file存儲在一個集中位置或使用分佈式存儲來構建自定義輕量級configuration registry。 他們通常使用現有的file storage service,例如object storage(如 AWS 的 S3 bucket)、版控系統、NAS,甚至 Web server。
另一種作法是將configuration setting打包到system package中,例如 .deb 或 .rpm 檔案,並將它們推送到內部 APT 或 YUM repo。 然後,您可以使用標準的package management tool將configuration file下載到local server。還有一種做法是使用standard relational servers或document store DB。
所有這些方法都利用現有服務,因此可以快速實施,而不需要安裝和運行新伺服器。 但是,當我們有一大堆小細節需要處理時,我們可能會發現自己正在構建和維護可以下架的功能。
將Secrets作為參數來處理
系統需要各種secrets。 我們的IaC工具可能需要密碼或key才能使用平台的 API 來建立和修改基礎設施。 還可能還需要在環境中配置secrets,例如,確保應用程式能有密碼連接到資料庫。
從一開始就以安全的方式處理這些類型的secrets至關重要。 無論我們使用公有雲還是私有雲,密secrets外洩都可能造成可怕的資安事件。 因此,即使我們只是編寫代碼來學習如何使用新工具或平台,也不應該將secrets放入代碼中。 有幾種方法可以處理基礎設施代碼所需的secrets,而無需實際將其放入代碼中。 它們包括加密secrets、secretless authorization、runtime secret injection和一次性secrets。
加密Secrets
不將secrets放入source code的規則的一種例外是在source code中對其進行加密; git-crypt、blackbox、sops 和 transcrypt 是一些可以幫助我們加密repo中的secrets的工具。而解密secrets的key則不應該在repo本身中。
Secretless Authorization
許多服務和系統提供了可以不用secret的方式授權操作的方法。 大多數雲端平台可以將compute instance(例如VM或container instance)標記為已授權執行特權操作。
例如,可以為 AWS EC2 instance分配一個 IAM profile,該profile為instance上的processes提供執行一組 API 命令的權限。 如果我們將IaC管理工具的instance直接配置有這一種特權,則無需管理可能被駭客竊取的secrets。
在某些情況下,可以使用secretless authorization來避免在建立基礎設施時需要在基礎設施上提供secrets。 例如,應用程程式伺服器可能需要訪存取DB instance。 DB server可能被配置成接受來自應用程式伺服器的連接,而不是使用IaC工具向應用程序伺服器提供密碼。
將權限與compute instance綁定只會改變可能的攻擊向量。 任何有權存取該instance的人都可以利用這些特權。 我們需要努力保護對特權instance的存取。 另一方面,獲得instance存取權限的人可能能夠存取存儲在那裡的secrets,因此向instance授予特權可能不會更糟。 而且secrets可能也從其他地方被且竊取,因此完全消除secrets的使用通常是一件好事。
Injecting Secrets at Runtime
當我們無法避免在stack或其他Infra code使用secrets時,我們可以探索在runtime注入secrets的方法。 通常,我們會將其實現為stack參數,這是我們之前提過的。
有兩種不同的runtime情況需要考慮:本地開發和unattended agents。 處理Infra code的人員通常會將secrets保存在沒有版控管理的本地檔案。 IaC工具可以直接讀取該檔案,如果我們使用stack configuration file模式,這尤其合適。 或者該檔案可以是在environment variables中設置secrets的腳本,這與stack environment variables模式配合良好。
這些方法也適用於unattended agents,例如用於 CI 測試或 CD delivery pipeline的agents。但我們需要將secrets存儲在運行agent的server或container上。 或者,我們可以使用代理軟體的secret管理功能把secrets放入stack command,就像pipeline stack parameters模式一樣。 另一種選擇是從secrets管理服務中取得secrets,該服務與stack parameter registry pattern模式保持一致。
一次性secrets
使用動態平台可以做的一件很炫炮的事情是動態建立secrets,並且只在“need-toknow”的基礎上使用它們。 在資料庫密碼示範例中,配置資料庫的代碼自動生成密碼並將其傳遞給配置應用程序伺服器的代碼。 人類不需要看到這個secrets,因此它永遠不會存儲在其他地方。
我們可以根據需要應用該代碼來重置密碼。 如果應用伺服器被重建,我們可以rerun資料庫伺服器代碼為其生成新密碼。
Secrets management services(例如 HashiCorp Vault)還可以在其他系統和服務中動態產生和設置密碼。 然後,它可以在配置基礎設施時將密碼提供給IaC工具,或者直接提供給使用它的服務(例如應用程序伺服器)。 一次性密碼將這種方法發揮到了極致,即每次進行身份驗證時都建立一個新密碼。
結論
重複使用stack project要求我們能夠配置特定stack的不同instance。 Configuration應該要最小化。 如果我們發現需要stack的不同實instance彼此完全不同,則應該將它們定義為不同的stack。
Stack project應定義跨insatnce的一致性stack shape。 有兩個不同的stack project定義表面上相似的東西(例如應用程式賜福器)但具有不同的shape是完全可以的。