With provider-terraform we have a new Crossplane provider that runs Terraform configurations and enables platform teams to include custom Terraform code within a Crossplane powered architecture. This provider enables many integration scenarios and allows teams who have invested in Terraform to leverage a powerful control plane architecture. Please read the announcement for more information. Furthermore, if you haven’t consider watching the webinar: Kubernetes Called and it Wants Your IaC Back: Using Control Planes to Modernize Your IaC Tech Stack.
In this blog post, we would like to explore a phased approach on how to include existing Terraform code and start migrating towards a Crossplane native solution.
Example: Let developers create their own subnet
Let’s say we are a platform team and are tasked to create AWS Subnets for developers. We have already spent a significant amount of time into a Terraform module, and we want to re-use that code, so developers can self-serve subnets for their application. Let’s see on how can use Crossplane to do so and port our custom code to Crossplane and possibly migrate fully to Crossplane if we'd like to.
We will do this in four steps:
The beauty of this approach is that we have a running platform after step one and can test our platform and do the subsequent steps 3 and 4 when the time is right.
The following steps will contain short abbreviated code examples. See the upbound/provider-terraform repository for the complete example.
With Crossplane, a platform team offers their users a way to create a subnet by simply creating a specific Kubernetes resource. e.g. something like:
apiVersion: aws.platformref.upbound.io/v1alpha1
kind: XSubnet
metadata:
name: my-subnet
spec:
vpcName: my-vpc-name
In order for this to materialize, we first need to create a Composite Resource Definition (or short XRD). This is Kubernetes resources that Crossplane understands. See Composite Resources · Docs for more information. The XRD contains a spec where we can describe the interface for the developer. In this case, we want to make it easy for the developer and make him only specify the vpcName.
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
spec:
group: aws.platformref.upbound.io
names:
kind: XSubnet
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
vpcName:
type: string
(Abbreviated. See definition.yaml for the complete version.)
After we have created the interface, we need to create an implementation, which is called a Composition in Crossplane. In this step, we want to run the full Terraform module in this composition by using provider-terraform. As you might guess, we are creating another Kubernetes resource with the type Composition. We start by just specifying one Crossplane resource by the kind Workspace which includes the Terraform code and a patches to convert the necessary information to Kubernetes. In this example, we have in-lined all the Terraform code, but we can also specify remote resources (see examples/workspace-remote.yaml for an example.)
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
compositeTypeRef:
apiVersion: aws.platformref.upbound.io/v1alpha1
kind: XSubnet
resources:
- name: tf-vpc-and-subnet
base:
apiVersion: tf.upbound.io/v1beta1
kind: Workspace
metadata:
name: observe-only-vpc
spec:
forProvider:
source: Inline
module: |
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
}
output "vpc_id" {
value = aws_vpc.main.id
}
variable "vpcName" {
description = "VPC name"
type = string
}
vars:
- key: vpcName
patches:
- fromFieldPath: spec.vpcName
toFieldPath: spec.forProvider.vars[0].value
- type: ToCompositeFieldPath
fromFieldPath: status.atProvider.outputs.vpc_id
toFieldPath: status.share.vpcId
(Abbreviated. See 01-composition-tf-only/composition.yaml for the complete version.)
🎉 Success! We now have a fully running control plane with our custom Terraform logic. You can start testing it with your team.
To fully transition to Crossplane, we now want to refactor the Subnet resource to use provider-aws. For this, we adapt our composition to now have two resources of kind Workspace and Subnet and use patching to combine these two.
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
resources:
- name: vpc
base:
apiVersion: tf.upbound.io/v1beta1
kind: Workspace
spec:
forProvider:
source: Inline
module: |
resource "aws_vpc" "main" {
}
… // No "aws_subnet" resource anymore!
patches:
- fromFieldPath: spec.vpcName
toFieldPath: spec.forProvider.vars[0].value
- type: ToCompositeFieldPath
fromFieldPath: status.atProvider.outputs.vpc_id
toFieldPath: status.share.vpcId
- name: subnet
base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: Subnet
spec:
forProvider:
region: eu-central-1
cidrBlock: 10.0.1.0/24
patches:
- fromFieldPath: status.share.vpcId
toFieldPath: spec.forProvider.vpcId
(Abbreviated. See 02-tf-and-native/composition.yaml for the complete version.)
You can see on how this enables us to transition to Crossplane native providers gradually. And depending on the complexity of the resources you have been developing, you can repeat this step.
In our last step, we want to remove provider-terraform from our composition and fully use the native aws provider. The outcome is for folks familiar with Crossplane straightforward. One composition that contains the two resource VPC and Subnet:
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
spec:
resources:
- name: vpc
base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: VPC
spec:
forProvider:...
patches:
- fromFieldPath: spec.vpcName
toFieldPath: spec.forProvider.tags.Name
- name: subnet
base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: Subnet
spec:
forProvider: …
(Abbreviated. See 03-native-only/composition.yaml for the complete version.)
Summary
Crossplane is a great way to build a developer platform powered by Kubernetes. Terraform has a great ecosystem and existing investments from platform teams. With provider-terraform we can combine both ecosystems and enable integration scenarios. This blogpost showed a quick get started guide on how to take a step-by-step guide behind a stable developer facing API. If you want to learn more and see the above in action, please view our webinar.
Subscribe to the Upbound Newsletter