Crossplane 作為 Kubernetes 的擴充套件,簡化了雲端資源的管理。然而,傳統的 Crossplane 組合根據靜態 CRD 定義,缺乏彈性。組合函式的出現,為 Crossplane 帶來了新的可能性,允許開發者以更靈活的方式組態資源。本文將著重於 Go 範本函式的應用,透過一個動態建立資料函式庫的案例,展現其強大之處。過往,我們可能需要為每個資料函式庫建立一個獨立的 CRD,而現在,藉由組合函式和 Go 範本,我們可以根據使用者輸入,動態生成所需數量的資料函式庫資源。這不僅簡化了組態流程,也提升了資源管理的效率。更進一步地,我們可以將這個概念延伸到其他型別的資源,例如虛擬機器、網路等等,實作真正的 Infrastructure as Code。

在 Crossplane 的 Pipeline 模式下,組合不再直接定義資源,而是定義一系列步驟,每個步驟由一個函式處理。每個函式接收輸入,執行特定邏輯,然後產生輸出。這樣的設計賦予了 Crossplane 極大的靈活性,因為函式內部可以執行任何邏輯,只要它能夠接收輸入並傳回資源作為輸出。本文的案例中,我們使用 Go 範本函式來根據使用者提供的資料函式庫名稱列表,動態生成多個資料函式庫資源。Go 範本的語法簡潔易懂,可以方便地操作資料,生成所需的 YAML 組態。透過 observed 欄位,函式可以存取 Composite Resource 的所有資訊,包括使用者輸入的引數,以及先前步驟生成的資源。這使得函式之間可以相互協作,構建更複雜的資源組態邏輯。

擁抱函式式組合:Crossplane 的技術進化之路

在 Crossplane 的世界裡,我們不斷探索如何更靈活、更強大地管理雲端資源。本文將探討 Composition Functions,一種讓資源組合更具彈性的技術。玄貓將帶領大家瞭解如何運用 Function,以及為何 Crossplane 要朝這個方向發展。

Function 的本質:資源組合的新起點

Function 在 Crossplane 中扮演著核心角色,它本質上是一個獨立的運算單元,專門處理資源的產生、修改和轉換。Function 的定義方式與 Provider 和 Configuration 類別似,都是透過 YAML 檔案來描述。

apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
  name: crossplane-contrib-function-patch-and-transform
spec:
  package: xpkg.upbound.io/crossplane-contrib/function-patch-and-transform:v0.1.4

Function 的關鍵在於 kind: Function,這告訴 Crossplane 這是一個 Function 定義。雖然現在為了簡化流程,我們直接套用 Function,但實際上,Function 應該被封裝到 Configuration Packages 中,以便更好地管理和佈署。

Pipeline 模式:開發資源協調Pipeline

過去,我們的 Composition 執行在預設的 Resources 模式下,直接在 Composition 內部定義資源。現在,透過 Pipeline 模式,我們可以將資源的產生過程拆解為一系列步驟,每個步驟都由一個 Function 處理。

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: azure-postgresql
  labels:
    provider: azure
    db: postgresql
spec:
  compositeTypeRef:
    apiVersion: devopstoolkitseries.com/v1alpha1
    kind: SQL
  mode: Pipeline
  pipeline:
  - functionRef:
      name: crossplane-contrib-function-patch-and-transform
    step: patch-and-transform
    input:
      apiVersion: pt.fn.crossplane.io/v1beta1
      kind: Resources
      patchSets:
      - name: metadata
        patches:
        - fromFieldPath: metadata.annotations
          toFieldPath: metadata.annotations
        - fromFieldPath: spec.id
          toFieldPath: metadata.name
      resources:
      - name: resourcegroup
        base:
          apiVersion: azure.upbound.io/v1beta1
          kind: ResourceGroup
          spec:
            forProvider:
              location: eastus
            patches:
            - type: PatchSet
              patchSetName: metadata
      - name: server
        base:
          ...

在 Pipeline 模式下,spec.mode 被設定為 Pipelinespec.pipeline 是一個步驟陣列,每個步驟都透過 functionRef.name 參照一個 Function。Function 的輸入則定義在 input 欄位中,這個 input 實際上就是過去我們在 Resources 模式下定義的資源。

Patch and Transform Function:功能轉移還是進化?

你可能會問,Patch and Transform Function 的作用是什麼?它不就是把原本內建在 Composition 裡的 Patch 和 Transform 功能搬出來而已嗎?

沒錯,表面上看起來是這樣。但實際上,Crossplane 正在將資源組合的邏輯從核心功能轉移到 Function 中。

為何要擁抱 Function?Crossplane 的未來藍圖

或許你會疑惑,如果沒有明顯的好處,為何要大費周章地改寫 Composition?玄貓認為有兩個主要原因:

  1. Pipeline 模式的強制要求:Pipeline 模式不支援內建的 Patch 和 Transform 功能,因此必須使用 Function。
  2. Crossplane 的發展方向:Crossplane 很有可能逐步淘汰 Resources 模式下內建的 Patch 和 Transform 功能,轉而全面擁抱 Function。

這意味著,Crossplane 不太可能在 Composition 中增加更多內建功能,而是會鼓勵開發者透過 Function 來擴充套件和客製化資源組合。

實戰演練:佈署與追蹤

讓我們實際套用新的 Composition,並追蹤 Claim 的狀態。

kubectl apply --filename compositions/sql-v8/$HYPERSCALER.yaml
crossplane beta trace sqlclaim my-db --namespace a-team

透過 crossplane beta trace 命令,我們可以清楚地看到資源的產生過程,以及 Function 在其中扮演的角色。

總而言之,Composition Functions 是 Crossplane 在資源組合方面的重要一步。透過擁抱 Function,我們可以開發更靈活、更可擴充套件的雲端資源管理平台。雖然目前看起來只是功能的轉移,但實際上,Crossplane 正在為我們描繪一個更具彈性的未來。

利用 Crossplane 組合函式實作資料函式庫靈活組態:玄貓的實戰分享

在雲原生時代,基礎設施即程式碼(Infrastructure as Code, IaC)已成為常態。Crossplane 作為一個 Kubernetes 的擴充套件,讓我們能夠使用 Kubernetes 的 API 來管理各種雲端資源。本文中,玄貓將分享如何利用 Crossplane 的組合函式(Composition Functions)來實作更靈活的資料函式庫組態。

為何要使用組合函式?擺脫傳統 CRD 的束縛

傳統的 Crossplane 組合依賴於靜態的 CRD(Custom Resource Definition)定義,缺乏彈性。若要實作更複雜的邏輯,例如根據使用者輸入動態建立多個資料函式庫,傳統方式就顯得捉襟見肘。這時,組合函式就派上用場了。

組合函式:賦予 Crossplane 無限可能

組合函式本質上是一個獨立的程式,它接收輸入,產生輸出。在 Crossplane 的 Pipeline 模式下,組合不再直接定義資源,而是定義一系列步驟,每個步驟將輸入傳遞給函式,函式再將資源傳回給組合。

這種模式的強大之處在於,函式內部可以執行任何邏輯。它可以新增、修改或刪除資源,只要能接收輸入並傳回資源作為輸出,Crossplane 就不會限制函式的行為。

案例:使用 Go 範本函式動態建立資料函式庫

假設我們希望使用者能夠在一個 PostgreSQL 伺服器中請求任意數量的資料函式庫,並且可以自訂資料函式庫名稱。傳統的組合方式難以實作這種需求,但透過 Go 範本函式,我們可以輕鬆解決。

首先,我們需要修改 Composite Resource Definition,新增一個 databases 欄位,允許使用者傳入一個資料函式庫名稱列表:

apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
  name: sqls.devopstoolkitseries.com
spec:
  versions:
    - name: v1alpha1
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                parameters:
                  type: object
                  properties:
                    databases:
                      description: 資料函式庫名稱列表
                      type: array
                      items:
                        type: string

接下來,我們需要安裝 Go 範本函式。玄貓這裡使用 Upbound 提供的 function-go-templating

apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
  name: upbound-function-go-templating
spec:
  package: xpkg.upbound.io/crossplane-contrib/function-go-templating:v0.4.0

然後,在組閤中新增一個步驟,使用 Go 範本函式根據使用者輸入的資料函式庫名稱列表生成相應的 Database 資源:

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: azure-postgresql
spec:
  pipeline:
    - functionRef:
        name: crossplane-contrib-function-patch-and-transform
    - functionRef:
        name: upbound-function-go-templating
      step: sql-db
      input:
        apiVersion: gotemplating.fn.crossplane.io/v1beta1
        kind: GoTemplate
        source: Inline
        inline: |
          {{ range .observed.composite.resource.spec.parameters.databases }}
          ---
          apiVersion: postgresql.sql.crossplane.io/v1alpha1
          kind: Database
          metadata:
            name: {{ $.observed.composite.resource.spec.id }}-{{ . }}
            annotations:
              crossplane.io/external-name: {{ . }}
              gotemplating.fn.crossplane.io/composition-resource-name: {{ $.observed.composite.resource.spec.id }}-{{ . }}
          spec:
            providerConfigRef:
              name: azure-provider-config
            forProvider:
              name: {{ . }}
          {{ end }}

在這個範例中,我們使用 Go 範本的 range 函式遍歷 databases 列表,為每個資料函式庫名稱生成一個 Database 資源。

玄貓解密:Go 範本程式碼

以下是 Go 範本程式碼的逐項解說:

  • {{ range .observed.composite.resource.spec.parameters.databases }}:這行程式碼使用 range 函式來遍歷 databases 列表。.observed.composite.resource.spec.parameters.databases 表示我們從 Composite Resource 的 spec.parameters.databases 欄位中取得資料。
  • {{ $.observed.composite.resource.spec.id }}-{{ . }}:這行程式碼用於生成資料函式庫的名稱。$.observed.composite.resource.spec.id 表示 Composite Resource 的 ID,. 表示當前遍歷到的資料函式庫名稱。
  • crossplane.io/external-name: {{ . }}:這行程式碼設定 external-name annotation,用於指定資料函式庫的外部名稱。
  • gotemplating.fn.crossplane.io/composition-resource-name: {{ $.observed.composite.resource.spec.id }}-{{ . }}:這行程式碼設定 composition-resource-name annotation,用於追蹤資源的來源。
  • spec.forProvider.name: {{ . }}:這行程式碼設定 Database 資源的 name 欄位,用於指定資料函式庫的名稱。

玄貓(BlackCat)談 Crossplane Composition Functions:如何用 Go 範本靈活組態資源

在之前的文章中,玄貓(BlackCat)向大家介紹了 Crossplane Composition 的基本概念。今天,我們將更深入地探討 Composition Functions,特別是如何使用 Go 範本來靈活地組態資源。

從 Database 消失說起:Composition Function 的奧秘

讓我們回顧一下之前的 Composition 範例。如果你夠仔細,會發現原本在資源清單中的 Database 消失了!取而代之的是一個新的步驟,這個步驟使用 Go 範本函式。

34 name: {{ $.observed.composite.resource.spec.id }}
35 forProvider: {}
36 {{ end }}

這個範例展示了 Composition Function 的強大之處。它允許我們在 Composition 中呼叫函式,並將輸入內容傳遞給這些函式。更重要的是,它還會傳送 Composite Resource 的當前狀態。這就是 observed 欄位的作用。

observed 欄位包含了所有資訊,包括 Claim 建立的 Composite Resource 定義,以及先前步驟中執行的 Function 所組裝的所有資源。雖然 Function 的開發者可能更關心先前組裝的資源,但作為 Function 的使用者,我們更關注從 Composite Resource 中存取欄位的能力。

具體來說,我們關心 spec.parameters.databasesspec.id 欄位。這些欄位,以及所有其他 Composite Resource 欄位,都可以在 observed.composite.resource 欄位中找到。

Go 範本:靈活組態的根本

有了以上概念,讓我們快速瀏覽一下範本本身。

我們正在迭代 spec.parameters.databases 欄位的值,這是一個陣列,並為每個值產生一個 Database。

如果你熟悉 Helm 範本,應該對 Go 範本的語法不會陌生。關鍵在於理解所有內容都可以在 observed 欄位中存取。

值得一提的是 metadata.namemetadata.annotations.crossplane.io/external-name 欄位的值。

我們可以將 metadata.name 留空,讓 Crossplane 自動產生一個唯一的名稱。但玄貓(BlackCat)不喜歡隨機名稱,所以我們使用 spec.id 和資料函式庫名稱({{ . }},來自 range 迴圈)的組合。這樣,我們就能產生一個結合了這兩者的唯一資源名稱。

然而,雖然這適用於 Managed Resource 的名稱,但玄貓(BlackCat)不希望資料函式庫的「真實」名稱與 Claim 的 spec.parameters.databases 欄位中指定的名稱不同。

預設情況下,資源的「真實」名稱(在 Azure 中最終會變成某個東西的名稱)與 Kubernetes 資源名稱相同,除非名稱是自動產生的,就像 AWS VPC 的情況一樣。因此,有時它們相同,有時則不同。Crossplane 透過 crossplane.io/external-name annotation 來解決這個問題。它將資源的「真實」名稱儲存在這裡,無論它是否與 Managed Resource 的名稱比對。

但正如玄貓(BlackCat)之前提到的,我們不希望這兩者相同,因此我們自己指定 crossplane.io/external-name annotation,並將其設定為 spec.parameters.databases[] 的值({{ . }})。

剩下的部分都與 Go 範本的工作方式有關,這不是本文的重點。

實戰演練:新增和驗證資料函式庫

讓我們套用 Composition…

kubectl apply --filename compositions/sql-v9/$HYPERSCALER.yaml

…並檢索所有資料函式庫。

kubectl get databases.postgresql.sql.crossplane.io

如果沒有出現任何資源,別擔心,這並不是錯誤!

我們套用了一個範本,該範本迭代 spec.parameters.databases 欄位中的值,但我們沒有將該欄位新增到 Claim 中。因此,我們沒有任何資料函式庫。這也證明瞭其中一個要求:不指定任何資料函式庫的使用者將不會獲得任何資料函式庫。這是一種選擇加入的模型。

讓我們新增一些資料函式庫。

apiVersion: devopstoolkitseries.com/v1alpha1
kind: SQLClaim
metadata:
  name: my-db
spec:
  parameters:
    databases:
    - db-01
    - db-02

更新後的 manifest 新增了兩個資料函式庫:db-01db-02

讓我們套用它…

kubectl --namespace a-team apply \
--filename examples/$HYPERSCALER-sql-v9.yaml

…並檢索所有資料函式庫。

kubectl get databases.postgresql.sql.crossplane.io

現在,我們可以看到兩個新的資料函式庫。

NAME             READY   SYNCED   AGE
my-db-...-db-01   True    True     7s
my-db-...-db-02   True    True     7s

它們需要幾秒鐘才能在伺服器內部啟動並執行,所以讓我們利用這段時間來仔細檢查一切是否按預期工作。玄貓(BlackCat)不相信自己,所以你也不應該相信我。

因此,讓我們仔細檢查資料函式庫是否確實在伺服器內部建立。我們已經完成了取得身份驗證資料和執行帶有 psql 的容器的難關,因此我們可以不用太多解釋地做到這一點。

首先,檢索 a-team Namespace 中的 Secrets…

kubectl --namespace a-team get secrets

…如果它是 Azure(並且堅持使用唯一名稱),則將伺服器名稱(與 Secret 名稱相同)儲存在 DB_NAME 變數中。對於其他人,它將是 my-db

export DB_NAME=my-db

接下來,我們將檢索使用者…

export PGUSER=$(kubectl --namespace a-team \
get secret $DB_NAME --output jsonpath="{.data.username}" \
| base64 -d)

…密碼…

export PGPASSWORD=$(kubectl --namespace a-team \
get secret $DB_NAME --output jsonpath="{.data.password}" \
| base64 -d)

…和主機。

export PGHOST=$(kubectl --namespace a-team \
get secret $DB_NAME --output jsonpath="{.data.endpoint}" \
| base64 -d)

現在我們可以執行一個帶有 psql 的 Pod…

kubectl run postgresql-client --rm -ti --restart='Never' \
--image docker.io/bitnami/postgresql:16 \
--env PGPASSWORD=$PGPASSWORD --env PGHOST=$PGHOST \
--env PGUSER=$PGUSER --command -- sh

…連線到伺服器…

psql --host $PGHOST -U $PGUSER -d postgres -p 5432

…並列出所有資料函式庫。

\l

輸出結果應該包含我們剛剛建立的 db-01db-02 資料函式庫。