pulumi - 基礎設施程式碼化
pulumi - 基礎設施程式碼化
本文不是一篇 pulumi 入門文件!文章主要內容是我對 pulumi 的一些思考,以及使用 pulumi 遇到的各種問題+解決方法。
pulumi 和 terraform 一樣,都是自動化管理基礎設施的工具,但是它解決了 terraform 配置的一個痛點:配置語法太過簡單,導致配置繁瑣,而且還要額外學習一門 DSL - hcl。
terraform 雖然應用廣泛,但是它預設使用的 HCL 語言太簡單,表現力不夠強。
這導致在更復雜的場景下,我們無法更自動化地進行基礎設施配置,而需要更復雜的步驟:
- 藉助 Python 等其他語言先生成出 HCL 配置
- 通過
terraform
命令列進行 plan 與 apply - 通過 Python 程式碼解析
terraform.tfstat
,獲取 apply 結果,再進行進一步操作。
這顯然是一個很麻煩的過程。其中最主要的原因,是 terraform 只做到了「基礎設施即配置」,而「配置」過於簡單。
這種情況下,就需要用到真正的「基礎設施即程式碼」工具 - Pulumi 了。它的優勢如下:
- pulumi 是目前最流行的 真-IaaS 工具(另一個是剛出爐沒多久的 terraform-cdk),對各語言的支援最為成熟。
- 相容 terraform 的所有 provider,只是需要自行使用 pulumi-tf-provider-boilerplate
- pulumi 官方的 provider 幾乎全都是封裝的 terraform provider,包括 aws/azure/alicloud,目前只發現 kubernetes 是原生的(獨苗啊)。
- 狀態管理和 secrets 管理有如下幾種選擇:
- 使用 app.pulumi.com(預設):免費版提供 stack 歷史管理,可以看到所有的歷史記錄。另外還提供一個資源關係的視覺化面板。總之很方便,但是多人合作就需要收費。
- 本地檔案儲存:
pulumi login file:///app/data
- 雲端物件儲存,目前貌似只支援 aws-s3/gcp/azure 三種。
- gitlab 13 支援 Terraform HTTP State 協議
- 使用 pulumi 企業版(自建服務):比 app.pulumi.com 提供更多的特性,但是顯然是收費的。。
上述工具支援通過 Python/TypeScript 等語言來描述配置。好處有:
- 批量建立資源,動態生成資源引數。
- 比如批量建立一批名稱類似的 ECS 伺服器/VPC交換機。如果使用 terraform,你需要編寫 module 來實現配置的複用,然後使用 hcl 的特殊語法來動態拼接出資源名稱,因為語法限制,這種 HCL 能實現的功能也很有限。
- 而使用 pulumi,Python/TypeScript 這類通用的程式語言,能滿足你的一切需求,而且作為一個開發人員/DevOps,你應該對它們相當熟悉。
- 更方便測試:可以使用各程式語言中流行的測試框架來測試 pulumi 配置!
- 使用程式碼編寫 Kubernetes 配置,no-yaml
- yaml 也存在和 HCL 一樣的問題,配置太死板,導致我們現在需要通過 helm/kustomize + python 來生成 yaml ...
使用建議
- 建議檢視對應的 terraform provider 文件:pulumi 的 provider 基本都是封裝的 terraform 版本,而且文件是自動生成的,比(簡)較(直)難(一)看(坨)懂(shi),examples 也少。
- stack: pulumi 官方提供了兩種 stack 用法:「單體」和「微-stack」
- 單體: one stack hold them all,通過 stack 引數來控制步驟。stack 用來區分環境 dev/pro 等。
- 微-stack: 每一個 stack 是一個步驟,所有 stack 組成一個完整的專案。
- 實際使用中,我發現「微-stack」模式需要使用到 pulumi 的 inter-stack dependencies,報一堆的錯,而且不夠靈活。因此目前更推薦「單體」模式。
我們最近使用 pulumi 完全重寫了以前用 terraform 編寫的雲上配置,簡化了很多繁瑣的配置,也降低了我們 Python 運維程式碼和 terraform 之間的互動難度。
另外我們還充分利用上了 Python 的型別檢查和語法檢查,很多錯誤 IDE 都能直接給出提示,強化了配置的一致性和可維護性。
體驗上,terraform 只是配置編寫方式,以及狀態管理有些不同。實際上都是通過同樣的 provider 管理雲上資源。
目前我們使用 pulumi/terraform,實現了雲上環境(資源組、VPC專有網路、k8s叢集、資料庫、賬號許可權系統、負載均衡等等)的一鍵搭建與銷燬。
不過由於阿里雲 provider 暫時還:
- 不支援管理 ASM 服務網格、DTS 資料傳輸等資源
- OSS 等產品的部分引數也暫時不支援配置(比如 OSS 不支援配置圖片樣式、ElasticSearch 暫時不支援自動建立 7.x 版本)
- 不支援建立 ElasticSearch 7.x
這些問題,導致我們仍然有部分配置需要手動處理,另外一些耗時長的資源,需要單獨去建立。
因此還不能實現完全的「一鍵」。
常見問題
1. pulumi 的 Output
常見問題
- pulumi 通過資源之間的屬性引用(
Output[str]
)來確定依賴關係,如果你通過自定義的屬性(str
)解耦了資源依賴,會導致資源建立順序錯誤而建立失敗。 Output[str]
是一個非同步屬性,類似 Future,不能被用在 pulumi 引數之外的地方!Output[str]
提供兩種方法能直接對Output[str]
進行一些操作:Output.concat("http://", domain, "/", path)
: 此方法將 str 與Output[str]
拼接起來,返回一個新的Output[str]
物件,可用做 pulumi 屬性。domain.apply(lambda it: print(it))
:Output[str]
的apply
方法接收一個函式。在非同步獲取到資料後,pulumi 會呼叫這個函式,把具體的資料作為引數傳入。- 另外
apply
也會將傳入函式的返回值包裝成Output
型別返回出來。 - 可用於:在獲取到資料後,將資料打印出來/傳送到郵箱/呼叫某個 API 上傳資料等等。
- 另外
Output.all(output1, output2, ...).apply(lambda it: print(it))
可用於將多個output
值,拼接成一個Output
型別,其內部的 raw 值為一個 tuple 物件(str1, str2, ...)
.- 官方舉例:
connection_string = Output.all(sql_server.name, database.name).apply(lambda args: f"Server=tcp:{args[0]}.database.windows.net;initial catalog={args[1]}...")
- 官方舉例:
2. 如果使用多個雲賬號/多個k8s叢集?
預設情況下 pulumi 使用預設的 provider,但是 pulumi 所有的資源都有一個額外的 opts
引數,可用於設定其他 provider。
示例:
from pulumi import get_stack, ResourceOptions, StackReference
from pulumi_alicloud import Provider, oss
# 自定義 provider,key/secret 通過引數設定,而不是從預設的環境變數讀取。
# 可以自定義很多個 providers
provider = pulumi_alicloud.Provider(
"custom-alicloud-provider",
region="cn-hangzhou",
access_key="xxx",
secret_key="jjj",
)
# 通過 opts,讓 pulumi 使用自定義的 provider(替換掉預設的)
bucket = oss.Bucket(..., opts=ResourceOptions(provider=provider))
3. inter-stack 屬性傳遞
這東西還沒搞透,待研究。
多個 stack 之間要互相傳遞引數,需要通過 pulumi.export
匯出屬性,通過 stack.require_xxx
獲取屬性。
從另一個 stack 讀取屬性的示例:
from pulumi import StackReference
cfg = pulumi.Config()
stack_name = pulumi.get_stack() # stack 名稱
project = pulumi.get_project()
infra = StackReference(f"ryan4yin/{project}/{stack_name}")
# 這個屬性在上一個 stack 中被 export 出來
vpc_id = infra.require("resources.vpc.id")
4. pulumi up
被中斷,或者對資源做了手動修改,會發生什麼?
- 強行中斷
pulumi up
,會導致資源進入pending
狀態,必須手動修復。- 修復方法:
pulumi stack export
,刪除 pending 資源,再pulumi stack import
- 修復方法:
- 手動刪除了雲上資源,或者修改了一些對資源管理無影響的引數,對
pulumi
沒有影響,它能正確檢測到這種情況。- 可以通過
pulumi refresh
手動從雲上拉取最新的資源狀態。
- 可以通過
- 手動更改了資源之間的關係(比如繫結 EIP 之類的),很可能導致 pulumi 無法正確管理資源之間的依賴。
5. pulumi-kubernetes?
pulumi-kubernetes 是一條龍服務:
- 在 yaml 配置生成這一步,它能結合/替代掉 helm/kustomize,或者你高度自定義的 Python 指令碼。
- 在 yaml 部署這一步,它能替代掉 argo-cd 這類 gitops 工具。
- 強大的狀態管理,argo-cd 也有狀態管理,可以對比看看。
也可以僅通過 kubernetes_pulumi 生成 yaml,再通過 argo-cd 部署,這樣 pulumi_kubernetes 就僅用來簡化 yaml 的編寫,仍然通過 gitops 工具/kubectl 來部署。
使用 pulumi-kubernetes 寫配置,要警惕邏輯和資料的混合程度。
因為 kubernetes 的配置複雜度比較高,如果動態配置比較多,很容易就會寫出難以維護的 python 程式碼來。
渲染 yaml 的示例:
from pulumi import get_stack, ResourceOptions, StackReference
from pulumi_kubernetes import Provider
from pulumi_kubernetes.apps.v1 import Deployment, DeploymentSpecArgs
from pulumi_kubernetes.core.v1 import (
ContainerArgs,
ContainerPortArgs,
EnvVarArgs,
PodSpecArgs,
PodTemplateSpecArgs,
ResourceRequirementsArgs,
Service,
ServicePortArgs,
ServiceSpecArgs,
)
from pulumi_kubernetes.meta.v1 import LabelSelectorArgs, ObjectMetaArgs
provider = Provider(
"render-yaml",
render_yaml_to_directory="rendered",
)
deployment = Deployment(
"redis",
spec=DeploymentSpecArgs(...),
opts=ResourceOptions(provider=provider),
)
如示例所示,pulumi-kubernetes 的配置是完全結構化的,比 yaml/helm/kustomize 要靈活非常多。
6. 阿里雲資源 replace 報錯?
部分只能建立刪除,不允許修改的資源,做變更時會報錯:「Resources aleardy exists」,
這類資源,通常都有一個「force」引數,指示是否強制修改——即先刪除再重建。
7. 有些資源屬性無法使用 pulumi 配置?
這得看各雲服務提供商的支援情況。
比如阿里雲很多資源的屬性,pulumi 都無法完全配置,因為 alicloud provider 的功能還不夠全面。
目前我們生產環境,大概 90%+ 的東西,都可以使用 pulumi 實現自動化配置。
而其他 OSS 的高階引數、新出的 ASM 服務網格、kubernetes 的授權管理、ElasticSearch7 等資源,還是需要手動配置。
這個沒辦法,只能等阿里雲提供支援。
8. CI/CD 中如何使 pulumi 將狀態儲存到檔案?
CI/CD 中我們可能會希望 pulumi 將狀態儲存到本地,避免連線 pulumi 中心伺服器。
這一方面能加快速度,另一方面一些臨時狀態我們可能根本不想儲存,可以直接丟棄。
方法:
# 指定狀態檔案路徑
pulumi login file://<file-path>
# 儲存到預設位置: ~/.pulumi/credentials.json
pulumi login --local
# 儲存到遠端 S3 儲存(minio/ceph 或者各類雲物件儲存服務,都相容 aws 的 s3 協議)
pulumi login s3://<bucket-path>
登入完成後,再進行 pulumi up
操作,資料就會直接儲存到你設定的路徑下。
缺點
1. 報錯資訊不直觀
pulumi 和 terraform 都有一個缺點,就是封裝層次太高了。
封裝的層次很高,優點是方便了我們使用,可以使用很統一很簡潔的宣告式語法編寫配置。
而缺點,則是出了 bug,報錯資訊往往不夠直觀,導致問題不好排查。
2. 資源狀態被破壞時,修復起來非常麻煩
在很多情況下,都可能發生資源狀態被破壞的問題:
- 在建立資源 A,因為引數是已知的,你直接使用了常量而不是 output。這會導致 pulumi 無法識別到依賴關係!從而建立失敗,或者刪除時資源狀態被破壞!
- 有一個 pulumi stack 一次在三臺物理機上建立資源。你白天建立資源晚上刪除資源,但是某一臺物理機晚上會關機。這將導致 pulumi 無法查詢到這臺物理機上的資源狀態,這個 pulumi stack 在晚上就無法使用,它會一直報錯!
常用 Provider
我建立的 provider:
我正打算建立的 provider: