当前位置: 首页 > 工具软件 > Crossplane > 使用案例 >

Crossplane 详解1——基础设施即代码

方昊英
2023-12-01

假设一个场景

贵司的开发团队的开发者A,需要在代码里使用AWS的S3服务,如果没有Crossplane 的话。他需要:

  1. 自己注册一个AWS账户,或者拿到贵司的AWS账户
  2. 去AWS上,买一个S3服务,自己填一大堆的配置。
  3. 把这个S3的服务的配置放在一个secret 里,并挂载到了pod里。

过了几天,贵司的另外一个开发团队开发者B,需要在代码里使用AWS的MySQL服务,他需要走完上一个同学一样的历程。

又过了几天,贵司的运维同学C,想要一个虚拟机,再来一遍。。

这?没有任何声明式!一大堆的莫名其妙的配置,直接默认就行了!这太不“K8S"了。

解决

crossplane是k8s下的一个管理应用和基础设施的k8s operator,通过隔离云服务提供商的基础设施实现k8s声明式API,将云服务提供商的资源和服务标准化和自动化,方便地实现IaC(基础设施即代码)。

它提供了多种抽象,使得我们的平台开发者在使用云资源时,尤其是公有云服务无比的便利。

刚才的描述,如果你还是一头雾水的话,可以想象成,服务目录,我认为crossplane 非常像Service catalog服务目录。

Crossplane详解

crossplane 的概念还是蛮绕的,尤其这个项目里的英文术语,很容易混淆,目前中文里,只有跟OAM团队有关的应用相关的文章,他们并没有突出Crossplane的核心概念,官方文档都是英文,而且它的例子,又臭又长,可以说crossplane 社区在用实力劝退众多中文从业者。

  1. provider
    基础设施资源提供者,它是一组k8s 的CRD和controllers的组合,用于一对一的定义各个provider 提供的资源。官方提供的provider 有:

    • AWS provider
    • GCP provider
    • Azure
    • Alibaba
    • 等等

    provider 主要有两种资源组成,Provider 和 ProviderConfig,Provider大概长这样:

    apiVersion: pkg.crossplane.io/v1
    kind: Provider
    metadata:
      name: provider-aws
    spec:
      package: "crossplane/provider-aws:master"
    

    它主要定义了Provider 的名称和安装包信息。ProviderConfig,长这样:

    apiVersion: aws.crossplane.io/v1beta1
    kind: ProviderConfig
    metadata:
      name: aws-provider
    spec:
      credentials:
        source: Secret
        secretRef:
          namespace: crossplane-system
          name: aws-creds
          key: key
    

    ProviderConfig 对象描述了以何种身份或者用户去使用provider 的资源,ProviderConfig和Provider 显然是“多对一”的关系。也就是说k8s可以使用不同的身份去使用Provider 的资源。

  2. Managed Resource
    Managed Resource 即管理对象,在crossplane里,这是一种provider 提供的,最底层、且无法分割的元资源。Managed Resource 在k8s集群里,可以直接使用,用来创建one-to-one 的云资源。以AWS 的 Redis 服务为例:

    apiVersion: database.aws.crossplane.io/v1beta1
    kind: RDSInstance
    metadata:
      name: foodb
    spec:
      forProvider:
        dbInstanceClass: db.t2.small
        masterUsername: root
        allocatedStorage: 20
        engine: mysql
      writeConnectionSecretToRef:
          name: mysql-secret
          namespace: crossplane-system
      providerConfigRef:
        name: default
      deletionPolicy: Delete
    

    我们看下,这个CRD对象的Group显然只有AWS的Provider Controller 能认识,别家的Provider肯定是不认识的。

    那么AWS家的Provider 认识到这个Managed Controller 对象,也就是RDSInstance以后,会从它spec里指定的Provider Config对象里拿到用户认证信息,接着就去消费AWS这个Cloud Provider 里的云服务资源了。

    之所以称之为low level的“元资源”,是因为对于我们的众多开发者而言,他们不太可能只用到单独一种Managed Resource,通常他们会使用一组这种Managed Resources,那么如果用一组这种资源的时候,我们还是要求开发者,一个个low level 的资源去创建的话,显然开发者要骂娘了,这增加了他们的学习成本,且一旦Provider 的云资源配置修改了,开发者的部署包也得跟着改。
    好在Crossplane 提供了一种组合资源。

  3. Composite Resource Definition
    翻译成组合资源定义,或者XRD,根据社区的解释,XRD的含义和k8s 的CRD意义是一样的,它定义了一种组合资源,为啥叫XRD呢?它明明是CRD,因为如果叫CRD就和k8s 的CRD冲突了。而X,意味着Cross,交叉,也是Crossplane 的前缀。
    它的意义和使用方法跟K8S的CRD一模一样,你可以XRD里定义你希望将来给开发者提供什么样的API 接口,注意,它只是一个接口,并不定义组合资源由那些资源组成。XRD比你熟悉的CRD多了三个字段:

    • connectionSecretKeys
      可选,用于向用户暴露一个连接Secret
    • defaultCompositionRef
      默认的Composition对象,这个字段就是指定,当开发者创建XR的时候,应该用哪种Composition。
    • claimNames
      可选,这个XR的Claim的类型,Claim,去类比下PVC吧。

    因为XRD比较长,我们看下XR:

    apiVersion: example.org/v1alpha1
    kind: CompositeMySQLInstance
    metadata:
      name: example
    spec:
      parameters:
        location: au-east
        storageGB: 20
        version: "5.7"
      compositionRef:
        name: example-azure
      writeConnectionSecretToRef:
        namespace: infra-secrets
        name: example-mysqlinstance
    

    这个资源就是给人看的,给人用的,因为它说人话,开发者A,他就想要一个au-east区的Mysql 实例,存储20G,版本5.7,至于是什么db engine,什么规格,全公司给一个默认的就行了。

    那么开发者A,创建了这个名为:example的CompositeMySQLInstance以后,究竟用的是哪家的云资源呢?是AWS的?是Azure的?还是阿里云的?毕竟每一家,对Mysql 的管理都是不一样的。
    compositionRef,这个字段就是指定了具体哪一个的Composition(组合)。

  4. Composition
    组合,它是XRD的一种实现,它具体指明了,某个XR究竟有哪些Managed Resource组成。它具体有三个部分组成:

    • compositeTypeRef
      指明了这个Composition实现的是哪个XR。
    • patchSets
      这个地方,可以叫补丁,也可以叫overlay,就像面向对象编程,多态的时候,子类有哪些地方可以覆盖的。
    • resouce
      • base
        这个就是基集
      • patch
        这里就是替换或者覆盖的规则。

    用官方的例子展示下,我去掉了注释,否则太长了,这里resource 的部分,就描绘了,这个Compostion 究竟有哪些Managed Resource 组成。

    apiVersion: apiextensions.crossplane.io/v1
    kind: Composition
    metadata:
      name: example-azure
      labels:
        purpose: example
        provider: azure
    spec:
      compositeTypeRef:
        apiVersion: example.org/v1alpha1
        kind: CompositeMySQLInstance
      patchSets:
      - name: metadata
        patches:
        - fromFieldPath: metadata.labels
        - fromFieldPath: metadata.annotations[example.org/app-name]
      - name: external-name
        patches:
        - type: FromCompositeFieldPath
          fromFieldPath: metadata.annotations[crossplane.io/external-name]
          policy:
            fromFieldPath: Required
      resources:
      - name: resourcegroup
        base:
          apiVersion: azure.crossplane.io/v1alpha3
          kind: ResourceGroup
          spec: {}
        patches:
        - type: PatchSet
          patchSetName: metadata
        - fromFieldPath: "spec.parameters.location"
          toFieldPath: "spec.location"
          transforms:
          - type: map
            map:
              us-west: West US
              us-east: East US
              au-east: Australia East
      - name: mysqlserver
        base:
          apiVersion: database.azure.crossplane.io/v1beta1
          kind: MySQLServer
          spec:
            forProvider:
              resourceGroupNameSelector:
                matchControllerRef: true
              sslEnforcement: Disabled
              sku:
                tier: GeneralPurpose
                capacity: 8
                family: Gen5
              storageProfile:
                backupRetentionDays: 7
                geoRedundantBackup: Disabled
            writeConnectionSecretToRef:
              namespace: crossplane-system
        patches:
        - type: PatchSet
          patchSetName: metadata
        - fromFieldPath: "metadata.uid"
          toFieldPath: "spec.writeConnectionSecretToRef.name"
          transforms:
          - type: string
            string:
              fmt: "%s-mysqlserver"
        - fromFieldPath: "spec.parameters.version"
          toFieldPath: "spec.forProvider.version"
        - fromFieldPath: "spec.parameters.location"
          toFieldPath: "spec.forProvider.location"
          transforms:
          - type: map
            map:
              us-west: West US
              us-east: East US
              au-east: Australia East
        - fromFieldPath: "spec.parameters.storageGB"
          toFieldPath: "spec.forProvider.storageProfile.storageMB"
          # Transform the value from the CompositeMySQLInstance by multiplying it by
          # 1024 to convert Gigabytes to Megabytes.
          transforms:
            - type: math
              math:
                multiply: 1024
        - type: CombineFromComposite
          combine:
            variables:
              - fromFieldPath: spec.parameters.location
              - fromFieldPath: metadata.annotations[crossplane.io/claim-name]
            strategy: string
            string:
              fmt: "%s-%s"
          toFieldPath: spec.forProvider.administratorLogin
          policy:
            fromFieldPath: Required
        - type: ToCompositeFieldPath
          fromFieldPath: "status.atProvider.fullyQualifiedDomainName"
          toFieldPath: "status.address"
        - type: CombineToComposite
          combine:
            variables:
              - fromFieldPath: "spec.parameters.administratorLogin"
              - fromFieldPath: "status.atProvider.fullyQualifiedDomainName"
            strategy: string
            string:
              fmt: "mysql://%s@%s:3306/my-database-name"
          toFieldPath: status.adminDSN
          policy:
            fromFieldPath: Optional
        connectionDetails:
        - fromConnectionSecretKey: username
        - fromConnectionSecretKey: password
        - name: hostname
          fromConnectionSecretKey: endpoint
        - type: FromValue
          name: port
          value: "3306"
        readinessChecks:
        - type: MatchString
          fieldPath: "status.atProvider.userVisibleState"
          matchString: "Ready"
      - name: firewallrule
        base:
          apiVersion: database.azure.crossplane.io/v1alpha3
          kind: MySQLServerFirewallRule
          spec:
            forProvider:
              resourceGroupNameSelector:
                matchControllerRef: true
              serverNameSelector:
                matchControllerRef: true
              properties:
                startIpAddress: 10.10.0.0
                endIpAddress: 10.10.255.254
                virtualNetworkSubnetIdSelector:
                  name: sample-subnet
        patches:
        - type: PatchSet
          patchSetName: metadata
      writeConnectionSecretsToNamespace: crossplane-system
    
  5. 使用
    还记得我们的XR对象吧,这个给人用的东西,最终会产生一个Secret,它位于infra-secrets 这个namespace 它叫example-mysqlinstance。这样,平台的消费者,就可以直接访问他渴望的资源了。

    apiVersion: example.org/v1alpha1
    kind: CompositeMySQLInstance
    metadata:
      name: example
    spec:
      parameters:
        location: au-east
        storageGB: 20
        version: "5.7"
      compositionRef:
        name: example-azure
      writeConnectionSecretToRef:
        namespace: infra-secrets
        name: example-mysqlinstance
    

    还有另外一种使用方式,那就是Claim的方式,Claim 是一XR资源的代理,它可以完全无视XRD的定义的变化,你们基础设施部门,或者云计算部门的同学,想怎么改XRD就怎么改,我永远只用XRClaim去访问我想要访问的资源。
    Claim 可以使用一个现成的XR对象,也可以每次在创建Claim 的时候,动态生成一个新的XR对象。就像下面这样:

    1. 用现成的,用的是上一步我们创建的XR对象。
      # The MySQLInstance always has the same API group and version as the
      # resource it requires. Its kind is always suffixed with .
      apiVersion: example.org/v1alpha1
      kind: MySQLInstance
      metadata:
        # Infrastructure claims are namespaced.
        namespace: default
        name: example
      spec:
        # The schema of the spec.parameters object is defined by the earlier example
        # of an CompositeResourceDefinition. The location, storageGB, and version fields
        # are patched onto the ResourceGroup, MySQLServer, and MySQLServerFirewallRule
        # composed by the required MySQLInstance.
        parameters:
          location: au-east
          storageGB: 20
          version: "5.7"
        # Support for a resourceRef is automatically injected into the schema of all
        # resource claims. The resourceRef requests a CompositeMySQLInstance
        # explicitly.
        resourceRef:
          apiVersion: example.org/v1alpha1
          kind: CompositeMySQLInstance
          name: example
        # Support for a writeConnectionSecretToRef is automatically injected into the
        # schema of all published infrastructure claim resources. This allows
        # the resource to write a connection secret containing any details required to
        # connect to it - in this case the hostname, username, and password.
        writeConnectionSecretToRef:
          name: example-mysqlinstance
      
    2. 动态生成,这时候,就得指定,你要用哪个Compostion,也就是用谁家的资源,就得指定Composition了。因为Composition,指明了,用到了哪些具体资源。
      apiVersion: example.org/v1alpha1
      kind: MySQLInstance
      metadata:
        namespace: default
        name: example
      spec:
        parameters:
          location: au-east
          storageGB: 20
          version: "5.7"
        # Support for a compositionSelector is automatically injected into the schema
        # of all published infrastructure claim resources. This selector selects
        # the example-azure composition by its labels.
        compositionSelector:
          matchLabels:
            purpose: example
            provider: azure
        writeConnectionSecretToRef:
          name: example-mysqlinstance
      

平台开发者需要做什么

这时候平台开发者的任务就转变为,利用已有的各自Provider,为了各自公司的开发人员,开发好用,易用,丝滑的XRD和Composition了,对于应用开发者而言,以后自身组件如果有些依赖的话,就直接写Claim了。基础设施的同学又可以跟业务部门愉快地玩耍了。

 类似资料: