อธิบาย Crossplane Concept พร้อมตัวอย่าง

Explain Crossplane Concepts with Examples

Nopnithi Khaokaew (Game)
13 min readJul 9, 2023

บทความนี้เป็นเวอร์ชั่นเก่าแล้ว เวอร์ชั่นที่ถูกปรับปรุงใหม่จะถูกย้ายไปที่ Crossplane คืออะไร? อธิบาย Concept ด้วยภาพและตัวอย่าง ครับ

และหลังจากนี้ผมจะเขียนบทความที่ nopnithi.com เป็นหลักครับ

[ENGLISH VERSION IS AT THE BOTTOM]

ก่อนจะเริ่ม…รบกวนกด Like/Follow/Subscribe ให้ผมด้วยนะครับ 🙏

Crossplane Concept

1. Managed Resources (MRs)

อย่างที่เกริ่นไปตอนต้น Crossplane ได้ช่วยสร้าง CRD (และ custom controller) สำหรับ cloud resource ต่าง ๆ ไว้ให้แล้ว

โดย Crossplane จะเรียก CRD และ custom controller รวมเข้าด้วยกันว่า managed resource (MR) ซึ่งแต่ละ MR จะ map 1–1 กับ cloud resource หนึ่งตัวตามภาพด้านล่าง

ตัวอย่าง Manifest ของ Managed Resource (MR) สำหรับ S3 Bucket

apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
name: nopnithi-crossplane-bucket
spec:
forProvider:
region: ap-southeast-1
tags:
Name: nopnithi-crossplane-bucket

ซึ่งผมสามารถใช้ kubectlเพื่อสร้าง S3 bucket ขึ้นมาได้แบบนี้

cat <<EOF | kubectl apply -f -
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
name: nopnithi-crossplane-demo-bucket
spec:
forProvider:
region: ap-southeast-1
tags:
Name: nopnithi-crossplane-demo-bucket
EOF

จากนั้น Kubernetes (Crossplane) ก็จะไปสร้าง S3 bucket ให้กับผมและคอยเช็ค status ของ bucket เอาไว้ให้ตลอดเวลา ถ้ามีคนไปแก้ไขหรือลบ bucket มันก็สร้างหรือแก้ไขกลับมาตาม manifest ข้างต้น (เรียกว่าการ reconcile)

2. Providers

เป็นเหมือน plugin ที่ช่วยให้ Crossplane สามารถคุยกับ platform ต่าง ๆ เพื่อสร้าง resource ขึ้นมาบนนั้นได้ (concept คล้ายกับ provider ของ Terraform)

ซึ่งใน provider แต่ละตัวก็จะรวบรวม MR สำหรับ resource ต่าง ๆ ของ platform นั้น ๆ เอาไว้สำหรับใช้สร้างและจัดการ resource เช่น AWS, Azure, GCP หรือ Helm

ตัวอย่าง Manifest ของ Provider และ ProviderConfig สำหรับ AWS

apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: upbound-provider-aws
spec:
package: xpkg.upbound.io/upbound/provider-aws:v0.27.0
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: aws-secret
key: creds

3. Composite Resources (XRs)

ที่จริงเราสามารถใช้ kubectlกับ manifest ของ MR เพื่อสร้าง resource ขึ้นมาได้โดยตรง แต่โดย practice แล้วเราจะไม่ทำแบบนั้นครับ เพราะนอกจากคนสร้างจะต้องเข้าใจ infrastucture แล้วก็ต้องเข้าใจ Crossplane อีกด้วย (ซึ่งไม่ใช่จุดประสงค์ของการสร้าง self-service platform เลย)

ดังนั้นเราจะรวบรวม MR เป็นก้อนแล้วสร้าง resource ผ่านไอ้เจ้าก้อนนี้ ซึ่งเราจะเรียกมันว่า Composite Resource (XR)

Composite Resource (XR) คือสิ่งที่มัดรวม MR ตั้งแต่ 1 ตัวขึ้นไปเข้าด้วยกันเป็นก้อนเดียว (XR) และสร้าง API ใหม่ที่ง่ายกว่าสำหรับ user เหตุผลก็เพื่อลดความซับซ้อนในการสร้าง infrastructure ลง

โดย XR หนึ่งตัวจะเป็นอะไรก็ได้ เช่น ในตัวอย่างของผมจะเป็น web application ซึ่งประกอบไปด้วย VPC, subnet, network interface และ EC2 instance และผมจะกำหนด API ให้เรียบง่ายที่สุดในการสร้างมัน

นั่นอาจแปลได้ว่าการเตรียม XR ก็คือหน้าที่ของ platform engineer เพื่อสร้าง self-service platform ให้กับ developer ใช้สร้าง resource ต่าง ๆ โดยที่ developer ไม่จำเป็นต้องเข้าใจ infrastructure มากนัก ในขณะเดียวกัน platform team เองก็สามารถคุม standard หรือ policy ได้

ทีนี้การที่ platform engineer จะเตรียม XR ขึ้นมาได้นั้นมีสิ่งที่จะต้องสร้าง 2 อย่าง คือ Composition และ Composite Resource Definition (XRD)

3.1 Composition คือ template ที่ใช้สำหรับสร้าง XR เป็นตัวกำหนดว่า…

  • XR ของเราประกอบด้วย MR อะไรบ้าง?
  • XR ตัวไหนสามารถใช้ template (composition) นี้ได้?

Tip: หากเทียบกับ Terraform ก็ให้มอง composition เป็นเหมือนกับ Terraform module code ได้เลยครับ

ตัวอย่าง Manifest ของ Composition

อธิบายเฉพาะจุดที่สำคัญในเบื้องต้นก่อนนะครับ

  • ภายใน composition จะประกอบด้วย MR หลาย ๆ ตัว
  • การกำหนด API version จะต้องตรงกับที่กำหนดไว้ใน XRD
  • kind ผมตั้งเป็น NopnithiWebApp ซึ่งจะตรงกับใน XRD
  • patches คือการ transform หรือ map ค่าจาก XR (ในที่นี้คือ NopnithiWebApp) ไปใช้กับ MR แต่ละตัว (VPC, Subnet, NetworkInterface และ Instance)

เมื่อเรียบร้อยแล้ว platform engineer ก็สร้าง composition ขึ้นมาด้วย kubectl

cat <<EOF | kubectl apply -f -
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: webapp
spec:
compositeTypeRef:
apiVersion: custom-api.example.org/v1alpha1
kind: NopnithiWebApp
resources:
- name: vpc
base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: VPC
metadata:
name: nopnithi-crossplane-vpc
labels:
crossplane-demo.nopnithi.com/name: webapp-vpc
spec:
forProvider:
region: ap-southeast-1
cidrBlock: 10.0.0.0/16
tags:
Name: nopnithi-crossplane-vpc
- name: subnet
base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: Subnet
metadata:
name: nopnithi-crossplane-subnet
labels:
crossplane-demo.nopnithi.com/name: webapp-subnet
spec:
forProvider:
region: ap-southeast-1
availabilityZone: ap-southeast-1a
vpcIdSelector:
matchLabels:
crossplane-demo.nopnithi.com/name: webapp-vpc
cidrBlock: 10.0.0.0/24
tags:
Name: nopnithi-crossplane-subnet
- name: interface
base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: NetworkInterface
metadata:
labels:
crossplane-demo.nopnithi.com/name: webapp-interface
name: webapp-interface
spec:
forProvider:
region: ap-southeast-1
subnetIdSelector:
matchLabels:
crossplane-demo.nopnithi.com/name: webapp-subnet
- name: instance
base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: Instance
metadata:
labels:
crossplane-demo.nopnithi.com/name: webapp-instance
name: webapp-instance
spec:
forProvider:
region: ap-southeast-1
ami: ami-0b94777c7d8bfe7e3
instanceType: t3.small
networkInterface:
- deviceIndex: 0
networkInterfaceIdSelector:
matchLabels:
crossplane-demo.nopnithi.com/name: webapp-interface
creditSpecification:
- cpuCredits: unlimited
patches:
- fromFieldPath: "spec.size"
toFieldPath: "spec.forProvider.instanceType"
transforms:
- type: map
map:
small: t3.small
medium: t3.medium
large: t3.large
- fromFieldPath: "spec.appName"
toFieldPath: "spec.forProvider.tags.Name"
EOF

จะได้

composition.apiextensions.crossplane.io/webapp created
➜  crossplane-demo kubectl get composition webapp
NAME XR-KIND XR-APIVERSION AGE
webapp NopnithiWebApp custom-api.example.org/v1alpha1 41s

ซึ่งปกติแล้วเราอาจจะมี composition (template) หลายตัวต่อหนึ่ง XRD (ที่กำลังจะพูดถึงต่อไป) เช่น composition สำหรับ environment ต่าง ๆ อย่าง dev, test หรือ prod อะไรแบบนั้น

3.2 Composite Resource Definition (XRD) คือตัวกำหนด type ของ XR และกำหนด API schema ว่าการจะสร้าง XR ดังกล่าวขึ้นมาจะต้องใช้ API หน้าตาแบบไหน มี property อะไรบ้าง (เรียก field ก็ได้)

Tip: หากเทียบกับ Terraform ก็ให้มอง XRD เป็นเหมือนกับ variable block หลาย ๆ ตัวที่เรา define ไว้ใน Terraform module ก็พอได้ครับ

ตัวอย่าง Manifest ของ Composite Resource Definition (XRD)

  • kind คือ type ของ XR ที่เรากำหนดขึ้น ในตัวอย่างผมตั้งเป็น NopnithiWebApp
  • API Schema ผมกำหนดว่าถ้าจะสร้าง XR ตัวนี้จะต้องส่ง property/field เข้ามา 2 ตัว คือ appName ซึ่งผมจะเอาตัวนี้ไปใช้ใส่ที่ tag:Name ของ EC2 instance ส่วน size ผมจะเอาไปใช้เป็น instance type ของ EC2 instance

และ platform engineer ก็สร้าง XRD ขึ้นมาด้วย kubectl เช่นเคย

cat <<EOF | kubectl apply -f -
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: nopnithiwebapps.custom-api.example.org
spec:
group: custom-api.example.org
names:
kind: NopnithiWebApp
plural: nopnithiwebapps
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
appName:
type: string
description: "The name of the application."
size:
type: string
description: "The size of the instance."
enum:
- small
- medium
- large
required:
- size
- appName
claimNames:
kind: CustomNopnithiWebApp
plural: customnopnithiwebapps
EOF

จะได้

compositeresourcedefinition.apiextensions.crossplane.io/nopnithiwebapps.custom-api.example.org created
➜  crossplane-demo kubectl get xrd nopnithiwebapps.custom-api.example.org 
NAME ESTABLISHED OFFERED AGE
nopnithiwebapps.custom-api.example.org True True 2m1s

สุดท้ายเมื่อเราจะสร้าง web app stack นี้ขึ้น เราไม่จำเป็นต้องรู้อะไรเกี่ยวกับ VPC หรือ EC2 สักเท่าไร แค่รู้ว่าจะเขียน manifest ยังไงแบบนี้

cat <<EOF | kubectl apply -f -
apiVersion: custom-api.example.org/v1alpha1
kind: NopnithiWebApp
metadata:
name: mywebapp
spec:
size: small
appName: MySmallWebApp
EOF

หรือถ้าอยากได้ size ใหญ่สุดก็เปลี่ยนที่ size แบบนี้

cat <<EOF | kubectl apply -f -
apiVersion: custom-api.example.org/v1alpha1
kind: NopnithiWebApp
metadata:
name: mywebapp
spec:
size: large
appName: MyLargeWebApp
EOF

จะได้ XR ขึ้นมาชื่อว่า mywebapp ตามที่เรากำหนดไปตอนสร้าง

nopnithiwebapp.custom-api.example.org/mywebapp created
➜  crossplane-demo kubectl get composite mywebapp
NAME SYNCED READY COMPOSITION AGE
mywebapp True True webapp 2m7s

ซึ่ง XR มันก็จะไปสร้าง MR ตาม composition ไงครับ ลองเช็ค MR ดู

➜  crossplane-demo kubectl get vpcs                    
NAME READY SYNCED EXTERNAL-NAME AGE
mywebapp-8c7lf True True vpc-0e713e72ed7573c9c 3m8s
➜  crossplane-demo kubectl get subnets
NAME READY SYNCED EXTERNAL-NAME AGE
mywebapp-qtg2c True True subnet-071f6295086c8567d 3m13s
➜  crossplane-demo kubectl get networkinterfaces
NAME READY SYNCED EXTERNAL-NAME AGE
mywebapp-9qftq True True eni-04ae8b011f7e16788 3m19s
➜  crossplane-demo kubectl get instances.ec2                
NAME READY SYNCED EXTERNAL-NAME AGE
mywebapp-vjn4f True True i-0dea862b9eee6d9c1 3m30s

ผมลองเอาหน้า console ของ EC2 มาให้ดูสัก resource นึงแล้วกันเนอะ จะเห็นว่ามันไปสร้าง EC2 instance ให้ (มี VPC, subnet และ ENI ด้วย) และชื่อ instance (tag:Name) ก็มาจาก appName ที่กำหนดใน XRD และถูก map ไปใช้ตาม composition

ทีนี้ผมลอง terminate instance ทิ้งไป แปปเดียว Kubernetes มันก็สร้างกลับมาให้เหมือนเดิมแล้วครับ 😄

4. Claims

Claim (หรือชื่อเต็มคือ Composite Resource Claim — XRC) ถ้าให้แปลเป็นไทยก็คงจะเป็นการอ้างสิทธิ์แหละครับ ซึ่ง claim ถือเป็น interface หลักที่ developer ใช้สำหรับติดต่อกับ Crossplane เพื่อสร้าง XR ขึ้นมา

แม้ว่าการสร้าง XR โดยตรง กับ การสร้าง claim นั้นจะให้ผลลัพธ์ที่เหมือนกัน คือทั้งสองวิธีจะทำให้เราได้ MR หรือก็คือ resource ต่าง ๆ ขึ้นมาแบบรูปข้างล่างนี้

แต่ความต่างก็คือ…

  • claim อยู่ใน namespace scope
  • ขณะที่ XR อยู่ใน cluster scope

ซึ่ง claim จะช่วยให้เราสามารถแยกแยะ resource ของแต่ละทีม developer ได้ (ที่จริงยังมีเรื่องของการจัดการสิทธิ์ต่าง ๆ ในการสร้าง resource ด้วยแหละ)

ดังนั้นหากเราสร้าง claim เราจะสามารถกำหนด namespace ได้แบบนี้

cat <<EOF | kubectl apply -f -
apiVersion: custom-api.example.org/v1alpha1
kind: CustomNopnithiWebApp
metadata:
name: claimed-mywebapp
namespace: test
spec:
size: small
appName: MyWebAppTest1
EOF

สังเกตว่า kind เปลี่ยนจาก NopnithiWebApp เป็น CustomNopnithiWebApp ตามที่กำหนดไว้ใน XRD แต่อย่างอื่นแทบไม่มีอะไรต่างกันเลย และจะได้

customnopnithiwebapp.custom-api.example.org/claimed-mywebapp created
➜  crossplane-demo kubectl get claim -n test
NAME SYNCED READY CONNECTION-SECRET AGE
claimed-mywebapp True True 4m

เมื่อสร้าง claim เราก็จะได้ XR (และ MR ทั้งหมด) เหมือนเดิมนั่นแหละครับ และทุกอย่างจะยังคงอยู่ใน cluster scope เช่นกัน เพียงแต่เจ้าตัว claim เองที่จะอยู่ใน namespace ซึ่งตรงนี้ทำให้เราสามารถแยกแยะ resource ของแต่ละทีมได้

สำหรับ concept ของ Crossplane ผมคิดว่าเอาไว้ประมาณนี้แล้วกันครับ คิดว่าน่าจะเพียงพอสำหรับการต่อยอดได้ละ ไว้โอกาสหน้ามาลองทำ hands-on project เกี่ยวกับ Crossplane กันเลยดีกว่า

ถ้าคิดว่าบทความนี้มีประโยชน์ อย่าลืมกด clap และ follow เพื่ออัพเดทเรื่องราวใหม่ ๆ จากผมได้นะครับ

[ENGLISH VERSION]

Continuing from the previous article “What is Crossplane?”, this article proceeds with the concept of Crossplane.

Crossplane Concepts

1. Managed Resources (MRs)

As mentioned in earlier article, Crossplane helps in creating CRDs (and custom controllers) for various cloud resources.

Crossplane refers to the combination of CRDs and custom controllers as managed resources (MR), with each MR mapping 1:1 with a cloud resource.

Managed Resource (MR) manifest for an S3 Bucket:

apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
name: nopnithi-crossplane-bucket
spec:
forProvider:
region: ap-southeast-1
tags:
Name: nopnithi-crossplane-bucket

So I can use kubectl to create an S3 bucket like this:

cat <<EOF | kubectl apply -f -
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
name: nopnithi-crossplane-demo-bucket
spec:
forProvider:
region: ap-southeast-1
tags:
Name: nopnithi-crossplane-demo-bucket
EOF

Then Kubernetes (Crossplane) will create an S3 bucket for me and constantly check the status of the bucket. If someone modifies or deletes the bucket, it will recreate or adjust it according to the above manifest (referred to as reconciliation loop).

2. Providers

These act like plugins that allow Crossplane to communicate with different platforms to create resources on them (a concept similar to Terraform provider).

Each provider collects MRs for different resources of its platform for creating and managing resources such as AWS, Azure, GCP, or Helm.

An example of a Provider and ProviderConfig manifest for AWS:

apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: upbound-provider-aws
spec:
package: xpkg.upbound.io/upbound/provider-aws:v0.27.0
apiVersion: aws.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: aws-secret
key: creds

3. Composite Resources (XRs)

Actually, we can directly use kubectl with MR’s manifest to create a resource. However, in practice, we usually won’t do this because the user creating the resource needs to understand both the infrastructure and Crossplane (which is not the purpose of creating a self-service platform).

Therefore, we will bundle MRs into one object and create resources via this object, which we will call this object as a Composite Resource (XR).

An XR is something that bundles one or more MRs into a single entity (XR), creating a new API that is simpler for users. The reason being to reduce complexity when creating infrastructure.

A single XR can be anything, such as a web application, which consists of VPC, subnet, network interface, and EC2 instance, and I will determine the simplest possible API to create it.

Preparing XR could be seen as the role of a platform engineer to create a self-service platform for developers to create various resources. Developers don’t need to understand the infrastructure too much while the platform team can control the standards or policies.

To prepare an XR, a platform engineer needs to create two things: a Composition and a Composite Resource Definition (XRD).

3.1 Composition is a template used to create XR and determines…

  • What MRs our XR consists of?
  • Which XRs can use this template?

Tip: If compared to Terraform, you can think the composition as similar to the Terraform module code.

An example of a Composition manifest:

  • A composition consists of several MRs
  • The API version must match what is specified in the XRD
  • I set kind as NopnithiWebApp to match the XRD
  • patches are the transformation or mapping of values from XR (in this case, NopnithiWebApp) for use with each MR (VPC, Subnet, NetworkInterface, and Instance)

Once completed, the platform engineer can create the composition using kubectl.

cat <<EOF | kubectl apply -f -
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: webapp
spec:
compositeTypeRef:
apiVersion: custom-api.example.org/v1alpha1
kind: NopnithiWebApp
resources:
- name: vpc
base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: VPC
metadata:
name: nopnithi-crossplane-vpc
labels:
crossplane-demo.nopnithi.com/name: webapp-vpc
spec:
forProvider:
region: ap-southeast-1
cidrBlock: 10.0.0.0/16
tags:
Name: nopnithi-crossplane-vpc
- name: subnet
base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: Subnet
metadata:
name: nopnithi-crossplane-subnet
labels:
crossplane-demo.nopnithi.com/name: webapp-subnet
spec:
forProvider:
region: ap-southeast-1
availabilityZone: ap-southeast-1a
vpcIdSelector:
matchLabels:
crossplane-demo.nopnithi.com/name: webapp-vpc
cidrBlock: 10.0.0.0/24
tags:
Name: nopnithi-crossplane-subnet
- name: interface
base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: NetworkInterface
metadata:
labels:
crossplane-demo.nopnithi.com/name: webapp-interface
name: webapp-interface
spec:
forProvider:
region: ap-southeast-1
subnetIdSelector:
matchLabels:
crossplane-demo.nopnithi.com/name: webapp-subnet
- name: instance
base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: Instance
metadata:
labels:
crossplane-demo.nopnithi.com/name: webapp-instance
name: webapp-instance
spec:
forProvider:
region: ap-southeast-1
ami: ami-0b94777c7d8bfe7e3
instanceType: t3.small
networkInterface:
- deviceIndex: 0
networkInterfaceIdSelector:
matchLabels:
crossplane-demo.nopnithi.com/name: webapp-interface
creditSpecification:
- cpuCredits: unlimited
patches:
- fromFieldPath: "spec.size"
toFieldPath: "spec.forProvider.instanceType"
transforms:
- type: map
map:
small: t3.small
medium: t3.medium
large: t3.large
- fromFieldPath: "spec.appName"
toFieldPath: "spec.forProvider.tags.Name"
EOF

It will be like this.

composition.apiextensions.crossplane.io/webapp created
➜  crossplane-demo kubectl get composition webapp
NAME XR-KIND XR-APIVERSION AGE
webapp NopnithiWebApp custom-api.example.org/v1alpha1 41s

We may have several compositions (templates) for one XRD, such as compositions for different environments like dev, test, or prod.

3.2 Composite Resource Definition (XRD) is the entity that defines the type of XR and determines the API schema. This schema dictates what API properties (fields) it contains when creating an XR.

Tip: If compared to Terraform, you can see XRD as similar to multiple variable blocks that we define in the Terraform module.

An example of a Composite Resource Definition (XRD) manifest:

  • kind is the type of XR that we determine. In this example, I set it as NopnithiWebApp.
  • In the API Schema, I specify that to create this XR, you need to submit two properties: appName, which I will use to put in the tag:Name of the EC2 instance, and size, which I will use as the instance type of the EC2 instance.

And the platform engineer can also create the XRD using kubectl.

cat <<EOF | kubectl apply -f -
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: nopnithiwebapps.custom-api.example.org
spec:
group: custom-api.example.org
names:
kind: NopnithiWebApp
plural: nopnithiwebapps
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
appName:
type: string
description: "The name of the application."
size:
type: string
description: "The size of the instance."
enum:
- small
- medium
- large
required:
- size
- appName
claimNames:
kind: CustomNopnithiWebApp
plural: customnopnithiwebapps
EOF

It will be like this.

compositeresourcedefinition.apiextensions.crossplane.io/nopnithiwebapps.custom-api.example.org created
➜  crossplane-demo kubectl get xrd nopnithiwebapps.custom-api.example.org 
NAME ESTABLISHED OFFERED AGE
nopnithiwebapps.custom-api.example.org True True 2m1s

Finally, when users want to create this web app stack, they don’t need to know much about VPC or EC2. They just need to know how to write a manifest like this.

cat <<EOF | kubectl apply -f -
apiVersion: custom-api.example.org/v1alpha1
kind: NopnithiWebApp
metadata:
name: mywebapp
spec:
size: small
appName: MySmallWebApp
EOF

To get the largest size, you can change the size like this.

cat <<EOF | kubectl apply -f -
apiVersion: custom-api.example.org/v1alpha1
kind: NopnithiWebApp
metadata:
name: mywebapp
spec:
size: large
appName: MyLargeWebApp
EOF

You’ll get an XR called mywebapp as defined in manifest.

nopnithiwebapp.custom-api.example.org/mywebapp created
➜  crossplane-demo kubectl get composite mywebapp
NAME SYNCED READY COMPOSITION AGE
mywebapp True True webapp 2m7s

The XR will then create MRs as per the composition.

➜  crossplane-demo kubectl get vpcs                    
NAME READY SYNCED EXTERNAL-NAME AGE
mywebapp-8c7lf True True vpc-0e713e72ed7573c9c 3m8s
➜  crossplane-demo kubectl get subnets
NAME READY SYNCED EXTERNAL-NAME AGE
mywebapp-qtg2c True True subnet-071f6295086c8567d 3m13s
➜  crossplane-demo kubectl get networkinterfaces
NAME READY SYNCED EXTERNAL-NAME AGE
mywebapp-9qftq True True eni-04ae8b011f7e16788 3m19s
➜  crossplane-demo kubectl get instances.ec2                
NAME READY SYNCED EXTERNAL-NAME AGE
mywebapp-vjn4f True True i-0dea862b9eee6d9c1 3m30s

You can check the MRs to see if the EC2 instance has been created (along with VPC, subnet, and ENI) and the instance name (tag:Name) comes from the appName specified in the XRD and mapped according to the composition.

When I tried to terminate the instance, Kubernetes recreated it exactly as before 😄.

4. Claims

A Claim (or Composite Resource Claim — XRC) is the primary interface that developers use to interact with Crossplane to create an XR.

Even though creating an XR directly and creating a claim result in the same outcome, i.e., both methods lead to the creation of MRs or various resources like the image below.

But the difference is…

  • Claims are in the namespace scope
  • While XRs are in the cluster scope

Claims help us isolate resources for each developer team (in fact, there are also issues regarding permissions in creating resources).

So, if we create a claim, we can specify a namespace like this:

cat <<EOF | kubectl apply -f -
apiVersion: custom-api.example.org/v1alpha1
kind: CustomNopnithiWebApp
metadata:
name: claimed-mywebapp
namespace: test
spec:
size: small
appName: MyWebAppTest1
EOF

Notice that the kind has changed from NopnithiWebApp to CustomNopnithiWebApp as defined in the XRD, but almost everything else remains the same. And we get:

customnopnithiwebapp.custom-api.example.org/claimed-mywebapp created
➜  crossplane-demo kubectl get claim -n test
NAME SYNCED READY CONNECTION-SECRET AGE
claimed-mywebapp True True 4m

When we create a claim, we get an XR (and all MRs) just like before. And everything will still be in the cluster scope, only the claim itself will be in the namespace. This allows us to isolate resources for each team.

For the concept of Crossplane, I think this should be enough for now. I believe it’s sufficient to get started. Next time, let’s try a hands-on project related to Crossplane.

ก่อนจะไป…รบกวนกด Like/Follow/Subscribe ให้ผมด้วยนะครับ 🙏

--

--

Nopnithi Khaokaew (Game)

Cloud Solutions Architect & Hobbyist Developer | 6x AWS Certified, CKA, CKAD, 2x HashiCorp Certified (Terraform, Vault), etc.