简介

Crossplane 是一种开源 Kubernetes 扩展,可将 Kubernetes 集群转换为通用控制平面。借助 Crossplane,平台团队可以利用 Kubernetes 策略、命名空间、基于角色的访问控制等的全部功能创建新的抽象和自定义 API, Crossplane 将所有非 Kubernetes 资源集中管理。

控制平面创建和管理资源的生命周期。控制平面不断检查预期资源是否存在,当预期状态与现实不符时报告并采取行动纠正错误。Crossplane 将 Kubernetes 控制平面扩展为通用控制平面,可以在任何地方检查、报告和处理任何资源。

基本概念

  • Package:OCI 镜像标准的包,包含 Provider CRDs、Provider 镜像、ServiceAccount 权限等信息。
  • Provider:云提供商插件,由一组 CRD 和 Controller 组成,CRD 定义了资源信息,Controller 通过 SDK 连接外部云服务,根据资源字段变更,执行相关操作。
  • Managed Resource:Provider 内部 CRD 定义的资源,创建并由 Crossplane 托管之后,称为 Managed Resource。
  • Compositions:Managed Resource 集合的模板,用于需要多个资源组合使用的场景,类似 Terraform Module。如 ECS + VPC + EBS。
  • Composite Resource(XR):使用 Composition 模板创建的一组 Managed Resource。
  • Claims:开发人员与 Crossplane 交互的主要方式,开发人员通过 Claim 声明由 SRE 团队定义的 XRD 中的自定义字段值,最终在 XR 定义中引用。

和 Terraform 对比,XRD 比较类似 Terraform Module 中的 variables块;Composition 是模块的 HCL 代码的其余部分,它描述了如何使用这些变量来创建一堆资源;XR 和 Claim 类似为模块提供输入的 tfvars 文件。

Crossplane vs Terraform

  • 相同点

    • 都属于 IaC 工具,通过声明式配置管理基础设施
    • 通过 Provider 插件管理各种各样的基础设施
  • 不同点

    • Crossplane 是一个控制平面,能够不断检查资源状态,管理资源全生命周期。
    • Terraform 是一个命令行工具,而不是控制平面,需要通过 CLI 手动或自动触发资源生命周期管理。
    • Terraform 没有暴露 API,只能通过 HCL + 命令行操作与 Terraform 集成;Crossplane 基于 Kubernetes API 构建,除了 API,也支持使用命令行工具 kubectl,方便应用集成。

Provider 接入 & 实践

Provider 实现方式

  1. Crossplane 原生实现:Crossplane 运行时定义了 ExternalClient 接口,接口中定义了资源的 CURD 操作。Provider 中的自定义 Controller 中通过 OpenAPI 实现 ExternalClient 接口,最终由 Crossplane 触发 Provider 执行资源 CURD 操作。这种方法类似 Terraform。

  2. Upjet:Crossplane 商业公司 Upbound 开发的工具,基于 Terraform Provider 自动生成 Crossplane Provider,内部实际是 Terraform 的封装,使用 Terraform CLI 实现了 ExternalClient 接口。

Upjet 接入流程

构建生成

  1. 使用 upjet-provider-template 模板创建仓库,仓库名为 provider-[provider_name]
  2. 克隆代码,进入仓库目录,拉取 upbound/build 模块
make submodules
  1. 替换模板内容

提前注释掉 prepare.sh 最后一行 rm -rf apis/null,这个是示例资源,这里没有删除干净,如果不加上注释,最后构建时会编译失败。

./hack/prepare.sh
  1. Makefile 环境变量配置
export TERRAFORM_VERSION ?= 1.2.1

export TERRAFORM_PROVIDER_SOURCE ?= volcengine/volcengine
export TERRAFORM_PROVIDER_REPO ?= https://github.com/volcengine/terraform-provider-volcengine
export TERRAFORM_PROVIDER_VERSION ?= 0.0.72
export TERRAFORM_PROVIDER_DOWNLOAD_NAME ?= terraform-provider-volcengine
export TERRAFORM_NATIVE_PROVIDER_BINARY ?= terraform-provider-volcengine_v0.0.72
export TERRAFORM_DOCS_PATH ?= website/docs/r

TERRAFORM_VERSION 使用 1.2.1,upjet 实现不兼容 0.12.xx 版本。

  1. 在 internal/clients/volcengine.go 中,实现 ProviderConfig 逻辑,加入 Terraform Provider 相关配置信息
const (
    keyAccessKey = "access_key"
    keySecretKey = "secret_key"
    keyRegion    = "region"
    keyEndpoint  = "endpoint"
)

func TerraformSetupBuilder(version, providerSource, providerVersion string) terraform.SetupFn {
    ...
    // set provider configuration
    ps.Configuration = map[string]any{}
    ps.Configuration = map[string]any{}
    if v, ok := creds[keyAccessKey]; ok {
        ps.Configuration[keyAccessKey] = v
    }
    if v, ok := creds[keySecretKey]; ok {
        ps.Configuration[keySecretKey] = v
    }
    if v, ok := creds[keyRegion]; ok {
        ps.Configuration[keyRegion] = v
    }
    if v, ok := creds[keyEndpoint]; ok {
        ps.Configuration[keyEndpoint] = v
    }
    return ps, nil
}
  1. config/external_name.go 中添加资源信息(以 VPC + Subnet 为例)
// ExternalNameConfigs contains all external name configurations for this
// provider.
var ExternalNameConfigs = map[string]config.ExternalName{
    // Name is a parameter and it is also used to import the resource.
    "volcengine_vpc": config.IdentifierFromProvider,
    "volcengine_subnet": config.IdentifierFromProvider,
}
  1. 添加资源自定义配置
mkdir config/vpc
cat <<EOF > config/vpc/config.go
package vpc

import "github.com/upbound/upjet/pkg/config"

// Configure configures individual resources by adding custom ResourceConfigurators.
func Configure(p *config.Provider) {
    p.AddResourceConfigurator("volcengine_vpc", func(r *config.Resource) {
       // We need to override the default group that upjet generated for
       r.ShortGroup = "vpc"
       r.Kind = "VPC"
    })
    
    p.AddResourceConfigurator("volcengine_subnet", func(r *config.Resource) {
       // We need to override the default group that upjet generated for
       r.ShortGroup = "subnet"
       r.References["vpc_id"] = config.Reference{
          TerraformName: "volcengine_vpc",
       }
    })
}
EOF

在 config/provider.go 中注册自定义配置

import (
    ...

    tjconfig "github.com/upbound/upjet/pkg/config"
    "github.com/upbound/upjet/pkg/types/conversion/cli"
    "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

+   "github.com/myorg/provider-volcengine/config/vpc"
 )

 func GetProvider() *tjconfig.Provider {
    ...
    for _, configure := range []func(provider *tjconfig.Provider){
            // add custom config functions
-           null.Configure,
+           vpc.Configure,
    } {
            configure(pc)
    }
  1. 生成 Upjet Provider
make generate

随后会自动下载 Terraform、Terraform Provider 包,生成 VPC 相关资源信息。

运行测试

需要依赖 Kubernetes 运行时,可以提前在本机安装 minikube 用于测试。

  1. 在 examples 目录中,创建资源定义 yaml 配置
mkdir examples/vpc

创建 Provider 配置 Secret:

cat <<EOF > examples/providerconfig/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: example-creds
  namespace: crossplane-system
type: Opaque
stringData:
  credentials: |
    {
      "access_key": "demoak",
      "secret_key": "demosk",
      "region": "cn-beijing",
      "endpoint": "open.volcengineapi.com"
    }
EOF

使用前把 demoak & demosk 替换为真实 AK。

创建 VPC 配置:

cat <<EOF > examples/vpc/vpc.yaml
apiVersion: vpc.volcengine.upbound.io/v1alpha1
kind: VPC
metadata:
  name: demo-vpc
spec:
  forProvider:
    description: "Managed with Crossplane."
    cidrBlock: 192.168.0.0/16
    vpcName: crossplane-demo
  providerConfigRef:
    name: default
EOF

创建 Subnet 配置:

cat <<EOF > examples/vpc/subnet.yaml
apiVersion: vpc.volcengine.upbound.io/v1alpha1
kind: Subnet
metadata:
  name: demo-subnet
spec:
  forProvider:
    description: "Managed with Crossplane."
    cidrBlock: 192.168.1.0/24
    subnetName: crossplane-subnet-1
    zoneId: cn-beijing-a
    # vpcId: vpc-xxx
    vpcIdRef:
      name: demo-vpc
  providerConfigRef:
    name: default
EOF
  1. 安装 CRD
kubectl apply -f package/crds
  1. 本地运行 Provider
make run
  1. 安装 ProviderConfig 和示例 VPC 配置:
# Create "crossplane-system" namespace if not exists
kubectl create namespace crossplane-system --dry-run=client -o yaml | kubectl apply -f -

kubectl apply -f examples/providerconfig/
kubectl apply -f examples/vpc/vpc.yaml
kubectl apply -f examples/vpc/subnet.yaml
  1. 查看资源
kubectl get managed
  1. 清理资源
kubectl delete -f examples/vpc/subnet.yaml
kubectl delete -f examples/vpc/vpc.yaml

发布