天天看點

Pulumi 使用體驗 - 基礎設施代碼化

Pulumi 使用體驗 - 基礎設施代碼化
原文: https://ryan4yin.space/posts/expirence-of-pulumi/

Pulumi 是一個基礎設施的自動管理工具,使用 Python/TypeScript/Go/Dotnet 編寫好聲明式的資源配置,就能實作一鍵建立/修改/銷毀各類資源,這裡的資源可以是:

  • AWS/阿裡雲等雲上的負載均衡、雲伺服器、TLS 證書、DNS、CDN、OSS、資料庫...幾乎所有的雲上資源
  • 本地自建的 vSphere/Kubernetes/ProxmoxVE/libvirt 環境中的虛拟機、容器等資源

相比直接調用 AWS/阿裡雲/Kubernetes 的 API,使用 pulumi 的好處有:

  • 聲明式配置:你隻需要聲明你的資源屬性就 OK,所有的狀态管理、異常處理都由 pulumi 完成。
  • 統一的配置方式:提供統一的配置方法,來聲明式的配置所有 AWS/阿裡雲/Kubernetes 資源。
  • 聲明式配置的可讀性更好,更便于維護

試想一下,通過傳統的手段去從零搭建一個雲上測試環境、或者本地開發環境,需要手工做多少繁瑣的工作。

而依靠 Pulumi 這類「基礎設施即代碼」的工具,隻需要一行指令就能搭建好一個可複現的雲上測試環境或本地開發環境。

比如我們的阿裡雲測試環境,包括兩個 kubernetes 叢集、負載均衡、VPC 網絡、資料庫、雲監控告警/日志告警、RAM賬号權限體系等等,是一個比較複雜的體系。

人工去配置這麼多東西,想要複現是很困難的,非常繁瑣而且容易出錯。

但是使用 pulumi,隻需要一行指令,就能建立并配置好這五花八門一大堆的玩意兒。

銷毀整個測試環境也隻需要一行指令。

實際使用體驗:我們使用 Pulumi 自動化了阿裡雲測試環境搭建 95%+ 的操作,這個比例随着阿裡雲的 pulumi provider 的完善,還可以進一步提高!

Pulumi vs Terraform

有一個「基礎設施即代碼」的工具比 Pulumi 更流行,它就是 Terraform.

實際上我們一開始使用的也是 Terraform,但是後來使用 Pulumi 完全重寫了一遍。

主要原因是,Pulumi 解決了 Terraform 配置的一個痛點:配置文法太過簡單,導緻配置繁瑣。而且還要額外學習一門 DSL - HCL

Terraform 雖然應用廣泛,但是它預設使用的 HCL 語言太簡單,表現力不夠強。

這就導緻在一些場景下使用 Terraform,會出現大量的重複配置。

一個典型的場景是「批量建立資源,動态生成資源參數」。比如批量建立一批名稱類似的 ECS 伺服器/VPC交換機。如果使用 terraform,就會出現大量的重複配置。

改用 terraform 提供的 module 能在一定程度上實作配置的複用,但是它還是解決不了問題。

要使用 module,你需要付出時間去學習 module 的概念,為了拼接參數,你還需要學習 HCL 的一些進階用法。

但是付出了這麼多,最後寫出的 module 還是不夠靈活——它被 HCL 局限住了。

為了實作如此的參數化動态化,我們不得不引入 Python 等其他程式設計語言。于是建構流程就變成了:

  1. 借助 Python 等其他語言先生成出 HCL 配置
  2. 通過

    terraform

    指令行進行 plan 與 apply
  3. 通過 Python 代碼解析

    terraform.tfstat

    ,擷取 apply 結果,再進行進一步操作。

這顯然非常繁瑣,主要困難就在于 Python 和 Terraform 之間的互動。

進一步思考,既然其他程式設計語言如 Python/Go 的引入不可避免,那是不是能使用它們徹底替代掉 HCL 呢?能不能直接使用 Python/Go 編寫配置?如果 Terraform 原生就支援 Python/Go 來編寫配置,那就不存在互動問題了。

相比于使用領域特定語言 HCL,使用通用程式設計語言編寫配置,好處有:

  1. Python/Go/TypeScript 等通用的程式設計語言,能滿足你的一切需求。
  2. 作為一個開發人員/DevOps,你應該對 Python/Go 等語言相當熟悉,可以直接利用上已有的經驗。
  3. 更友善測試:可以使用各程式設計語言中流行的測試架構來測試 pulumi 配置!

于是 Pulumi 橫空出世。

另一個和 Pulumi 功能類似的工具,是剛出爐沒多久的 terraform-cdk,但是目前它還很不成熟。

Pulumi 特點介紹

  1. 原生支援通過 Python/Go/TypeScript/Dotnet 等語言編寫配置,也就完全解決了上述的 terraform 和 python 的互動問題。
  2. pulumi 是目前最流行的 真-IaaS 工具,對各語言的支援都很成熟。
  3. 相容 terraform 的所有 provider,隻是需要自行使用 pulumi-tf-provider-boilerplate 重新打包,有些麻煩。
    1. pulumi 官方的 provider 幾乎全都是封裝的 terraform provider,包括 aws/azure/alicloud,目前隻發現 kubernetes 是原生的(獨苗啊)。
  4. 狀态管理和 secrets 管理有如下幾種選擇:
    1. 使用 app.pulumi.com(預設):免費版提供 stack 曆史管理,可以看到所有的曆史記錄。另外還提供一個資源關系的可視化面闆。總之很友善,但是多人合作就需要收費。
    2. 本地檔案存儲:

      pulumi login file:///app/data

    3. 雲端對象存儲,目前貌似隻支援 aws-s3/gcp/azure 三種。
    4. gitlab 13 支援 Terraform HTTP State 協定,等這個 pr 合并,pulumi 也能以 gitlab 為 backend 了。
    5. 使用 pulumi 企業版(自建服務):比 app.pulumi.com 提供更多的特性,但是顯然是收費的。。

總之,非常香,強烈推薦各位 DevOps 試用。

以下内容是我對 pulumi 的一些思考,以及使用 pulumi 遇到的各種問題+解決方法,适合對 pulumi 有一定了解的同學閱讀。
如果你剛接觸 Pulumi 而且有興趣學習,建議先移步 pulumi get started 入個門,再接着看下面的内容。

使用建議

  1. 建議檢視對應的 terraform provider 文檔:pulumi 的 provider 基本都是封裝的 terraform 版本,而且文檔是自動生成的,比(簡)較(直)難(一)看(坨)懂(shi),examples 也少。
  2. stack: pulumi 官方提供了兩種 stack 用法:「單體」和「微-stack」
    1. 單體: one stack rule them all,通過 stack 參數來控制步驟。stack 用來區分環境 dev/pro 等。
    2. 微-stack: 每一個 stack 是一個步驟,所有 stack 組成一個完整的項目。
    3. 實際使用中,我發現「微-stack」模式需要使用到 pulumi 的 inter-stack dependencies,報一堆的錯,而且不夠靈活。是以目前更推薦「單體」模式。

我們最近使用 pulumi 完全重寫了以前用 terraform 編寫的雲上配置,簡化了很多繁瑣的配置,也降低了我們 Python 運維代碼和 terraform 之間的互動難度。

另外我們還充分利用上了 Python 的類型檢查和文法檢查,很多錯誤 IDE 都能直接給出提示,強化了配置的一緻性和可維護性。

不過由于阿裡雲 provider 暫時還:

  1. 不支援管理 ASM 服務網格、DTS 資料傳輸等資源
  2. OSS 等産品的部分參數也暫時不支援配置(比如 OSS 不支援配置圖檔樣式、ElasticSearch 暫時不支援自動建立 7.x 版本)
  3. 不支援建立 ElasticSearch 7.x

這些問題,導緻我們仍然有部配置設定置需要手動處理,另外一些耗時長的資源,需要單獨去建立。

是以還不能實作完全的「一鍵」。

常見問題

1.

Output

的用法

  1. pulumi 通過資源之間的屬性引用(

    Output[str]

    )來确定依賴關系,如果你通過自定義的屬性(

    str

    )解耦了資源依賴,會導緻資源建立順序錯誤而建立失敗。
  2. Output[str]

    是一個異步屬性,類似 Future,不能被用在 pulumi 參數之外的地方!
  3. Output[str]

    提供兩種方法能直接對

    Output[str]

    進行一些操作:
    1. Output.concat("http://", domain, "/", path)

      : 此方法将 str 與

      Output[str]

      拼接起來,傳回一個新的

      Output[str]

      對象,可用做 pulumi 屬性。
    2. domain.apply(lambda it: print(it))

      :

      Output[str]

      apply

      方法接收一個函數。在異步擷取到資料後,pulumi 會調用這個函數,把具體的資料作為參數傳入。
      • 另外

        apply

        也會将傳入函數的傳回值包裝成

        Output

        類型傳回出來。
      • 可用于:在擷取到資料後,将資料列印出來/發送到郵箱/調用某個 API 上傳資料等等。
    3. Output.all(output1, output2, ...).apply(lambda it: print(it))

      可用于将多個

      output

      值,拼接成一個

      Output

      類型,其内部的 raw 值為一個 tuple 對象

      (str1, str2, ...)

      .
      1. 官方舉例:

        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。

通過這個

opts

,我們可以實作在一個 pulumi 項目中,使用多個雲賬号,或者管理多個 k8s 叢集。

示例:

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

被中斷,或者對資源做了手動修改,會發生什麼?

  1. 強行中斷

    pulumi up

    ,會導緻資源進入

    pending

    狀态,必須手動修複。
    1. 修複方法:

      pulumi stack export

      ,删除 pending 資源,再

      pulumi stack import

  2. 手動删除了雲上資源,或者修改了一些對資源管理無影響的參數,對

    pulumi

    沒有影響,它能正确檢測到這種情況。
    1. 可以通過

      pulumi refresh

      手動從雲上拉取最新的資源狀态。
  3. 手動更改了資源之間的依賴關系(比如綁定 EIP 之類的),很可能導緻 pulumi 無法正确管理資源之間的依賴。
    • 這種情況必須先手動還原依賴關系(或者把相關資源全部手動删除掉),然後才能繼續使用 pulumi。

5. pulumi-kubernetes?

pulumi-kubernetes 是一條龍服務:

  1. 在 yaml 配置生成這一步,它能結合/替代掉 helm/kustomize,或者你高度自定義的 Python 腳本。
  2. 在 yaml 部署這一步,它能替代掉 argo-cd 這類 gitops 工具。
  3. 強大的狀态管理,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 要靈活非常多。

總之它非常靈活,既可以和 helm/kustomize 結合使用,替代掉 argocd/kubectl。

也可以和 argocd/kubectl 使用,替代掉 helm/kustomize。

具體怎麼使用好?我也還在研究。

6. 阿裡雲資源 replace 報錯?

阿裡雲有部分資源,隻能建立删除,不允許修改,比如「資源組」。

對這類資源做變更時,pulumi 會直接報錯:「Resources aleardy exists」,

這類資源,通常都有一個「force」參數,訓示是否強制修改——即先删除再重建。

7. 有些資源屬性無法使用 pulumi 配置?

這得看各雲服務提供商的支援情況。

比如阿裡雲很多資源的屬性,pulumi 都無法完全配置,因為 alicloud provider 的功能還不夠全面。

目前我們生産環境,大概 95%+ 的東西,都可以使用 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. 資源狀态被破壞時,修複起來非常麻煩

在很多情況下,都可能發生資源狀态被破壞的問題:

  1. 在建立資源 A,因為參數是已知的,你直接使用了常量而不是

    Output

    。這會導緻 pulumi 無法識别到依賴關系!進而建立失敗,或者删除時資源狀态被破壞!
  2. 有一個 pulumi stack 一次在三台實體機上建立資源。你白天建立資源晚上删除資源,但是某一台實體機晚上會關機。這将導緻 pulumi 無法查詢到這台實體機上的資源狀态,這個 pulumi stack 在晚上就無法使用,它會一直報錯!

常用 Provider

  • pulumi-alicloud: 管理阿裡雲資源
  • pulumi-vault: 我這邊用它來快速初始化 vault,建立與管理 vault 的所有配置。

我建立維護的 Provider

由于 Pulumi 生态還比較小,有些 provider 隻有 terraform 才有。

我為了造(方)福(便)大(自)衆(己),建立并維護了兩個本地虛拟機相關的 Providers:

  • ryan4yin/pulumi-proxmox: 目前隻用來自動建立 PVE 虛拟機
    • 可以考慮結合 kubespray/kubeadm 快速建立 k8s 叢集
  • ryan4yin/pulumi-libvirt: 快速建立 kvm 虛拟機

繼續閱讀