Skip to content

Instantly share code, notes, and snippets.

@vfarcic
Created March 27, 2025 23:51
Show Gist options
  • Save vfarcic/a750b2e2fa2ac0c59d00cb65c4a43895 to your computer and use it in GitHub Desktop.
Save vfarcic/a750b2e2fa2ac0c59d00cb65c4a43895 to your computer and use it in GitHub Desktop.

Introduction

Imagine if you could create, for people in your company, a platform that would provide them with the same experience they have when working with AWS, Google Cloud, Azure, or any other public Cloud provider. Imagine if there would be a service for everything they do.

Do you need a database that works exactly as we expect it to work in this company with all the security, backup, compliance, and other policies we have?

Well...

There is a service for that.

Do you need to run a backend application in Kubernetes? There is a service for that as well.

Do you need a Kubernetes cluster itself? There is yet another service for that.

All you have to do is define a simple manifest that contains only the things you care about and abstracts all the unavoidable complexity. From there on, all you have to do is submit that manifest, the desired state, to the control plane API and observe the actual state. Even better, you can forget about the API and just push it to Git.

Wouldn't that be awesome?

Wouldn't it be great if we could replicate the experience of using a public Cloud provider but made specifically for our needs? Wouldn't it be great if there were a clearly defined API and a clear separation between the tasks end-users need to perform and the tasks that are the responsibility of the platform itself?

TODO: Lower-third: Crossplane, https://crossplane.io, crossplane.png

If that sounds like something you might need, then Crossplane is just the project that will get you there. It enables us with capabilities of creating control planes based on the same principles public cloud providers have. It democratizes technology that was previously reserved mostly for big Cloud providers like AWS, Azure, and Google Cloud. It enables us to create internal developer platforms.

Let me give you a sneak peek into some of the capabilities we'll build throughout this tutorial.

But, before I do that, I need to give you an early warning so that you can decide whether this tutorial is for you or not. I will not use a typical teaching approach where there is a long introduction, followed by an even longer theory, followed by an explanation of each of the components, followed by a bunch of diagrams, and so on and so forth. This is a tutorial that reflects the way I learn. I learn by doing. I learn by having my hands on the keyboard at all times. I learn by making mistakes and fixing them. I learn by experimenting. That's the approach I'll take with this tutorial. I'll explain theory and concepts, and I'll show diagrams, but only through hands-on exercises. I'll explain what we did rather than do what I explained.

Does that make sense?

If it does, you're in the right place. Otherwise... Go away, while you still can.

With that warning out of the way, let's take a quick glimpse at some of the things we'll build throughout this tutorial. That should give you a good idea of what you can expect and let you decide whether Crossplane is something you want to learn and adopt.

Chapter Setup

I lied when I said that we'll take "a quick glimpse at some of the things we'll build throughout this tutorial". We will, but not right away. First, we need to create a control plane cluster with Crossplane, Compositions, Argo CD, and a few other tools that will be required to show you what we're building.

Now, to make things easy, instead of providing step-by-step setup instructions for this and all other chapters, I created scripts that will do that for us. You can run them and follow the instructions they present through questions, or you can choose to inspect the scripts if you prefer setting up everything manually.

However, we have a bit of a problem. To run those setup scripts as well as the instructions that follow in the hands-on parts of the tutorial, we'll need tools. We'll need quite a few CLIs like, for example, kubectl, crossplane, gum, gh, hyperscaler-specific CLIs, and so on and so forth.

One option would be for me to give you the instructions on how to install all the CLIs we'll need. That, however, might result in you spending considerable time reading those instructions and installing those CLIs. We'll do something else. We'll run everything in Nix. It allows us to create ad-hoc Shell environments with all the tools we might need. It's awesome and it will help us streamline this tutorial and avoid complications that might arise from using different operating systems.

TODO: Lower-third: Nix, https://nix.dev/install-nix, nix.png

TODO: Thumbnail: 0ulldVwZiKA

FIXME: Install Nix by following the instructions at https://nix.dev/install-nix

If you are not familiar with Nix, you might want to check out this video, or, simply install it.

TODO: Lower-third: Docker, https://docs.docker.com/engine/install, docker.png

FIXME: Install Docker by following the instructions at https://docs.docker.com/engine/install

Apart from Nix, we'll need to install one more thing. I don't think we should run Docker in Nix, so we'll need it on the host machine. You probably already have it. If you don't, please install it by following the install instructions.

TODO: Lower-third: GitHub CLI, https://github.com/cli/cli?tab=readme-ov-file#installation, github.png

FIXME: Install GitHub CLI by following the instructions at https://github.com/cli/cli?tab=readme-ov-file#installation

Finally, we'll need gh (GitHub CLI) to fork the repository with examples we'll use throughout this tutorial, including the shell.nix file that will bring in all the tools we'll need. Please install it if you do not have it already.

You can find additional information about GitHub CLI in this video.

Finally, each chapter has an associated Gist that contains all the commands we'll execute. You can use it to copy and paste commands instead of pausing videos in an attempt to read what's on the screen and type the same in your terminal. The Gist is in the description of the video.

That's it. Now we're ready to set up everything required for me to show you a glimpse of the future.

First, we'll fork the repo,...

TODO: Overlay: screen-01

gh repo fork vfarcic/crossplane-tutorial --clone --remote

...then enter the crossplane-tutorial directory, ...

cd crossplane-tutorial

...and select the fork as the default repository.

gh repo set-default

Next, we are going to start a Nix Shell with all the tools we'll need in this chapter.

nix-shell --run $SHELL

Executing nix-shell with all the required packages might take a while since there are quite a few CLIs we'll need install. It takes longer the first time. The packages it's downloading are cached so subsequent executions will be almost instant. Be patient. It will be worth it. Once it's done, we'll get a Shell with everything we'll need.

As an alternative, you can skip running nix-shell but, in that case, you'll need to ensure that all the requirements are fulfilled. You'll see what those are in a moment when we execute the setup script.

TODO: Fast-forward to the end of the nix-shell execution.

Next, we'll make the setup script executable...

chmod +x setup/00-intro.sh

...and execute it.

./setup/00-intro.sh

Confirm that you are ready to start unless you prefer to inspect the script and set up everything manually.

Next, double-check that you meet all the requirements. If you are running the script from inside a Nix Shell, as I recommended, you do meet all the requirements, except for Docker. Nix took care of that. So, if you do meet all the requirements, just say... Yes!

I'll fast-forward parts that are too boring to watch and only comment on key parts of what the script does.

The script starts by creating a kind cluster with NGINX Ingress. Once it's up and running, it installs Crossplane and a few Configurations. Don't worry if you don't know what they are. We'll dive into configurations and everything else related to Crossplane and the ecosystem later. Right now, we're just fast-forwarding through all that so that you can see some of the features we'll be learning throughout this tutorial.

Now, those configurations will, ultimately, create hundreds of custom resource definitions. That takes time so expect to wait for a while until all the providers are up and running.

Once all the providers are ready, we're presented with a choice of a hyperscaler.

I'll choose AWS for this chapter, and you can choose any of the "big three". To keep things interesting, and for me not to be labeled as biased, I'll pick a different one in each chapter so the outputs you'll see in the video might differ from those you'll see on your terminal. The logic for all the providers is the same so that should not pose a problem, as long as you're aware that what you see in this video might not be exactly the same as what you'll see in your terminal, unless you happen to pick the same hyperscaler which, as I mentioned, is AWS in this chapter.

I assumed that the downside of having a discrepancy between what you see in this video and what you'll experience by, potentially, choosing a different hyperscaler, is justified by you being able to pick a hyperscaler that best suits your needs.

Once the hyperscaler is selected, the script sets up everything required for Crossplane to work with it.

Finally, the script installed and configured Argo CD.

Throughout the execution of the script, a few environment variables were placed into the .env file. We'll need those, so let's source it.

source .env

That's it. Now we're ready to take a look at some of the features we might expect to learn through this tutorial.

A Glimpse Into the Future

Here's what the future looks like.

cat examples/$HYPERSCALER-intro.yaml

The output is as follows.

---
apiVersion: devopstoolkitseries.com/v1alpha1
kind: ClusterClaim
metadata:
  name: cluster-01
spec:
  id: cluster-01
  compositionSelector:
    matchLabels:
      provider: aws
      cluster: eks
  parameters:
    nodeSize: small
    minNodeCount: 3
---
apiVersion: v1
kind: Secret
metadata:
  name: silly-demo-db-password
data:
  password: cG9zdGdyZXM=
---
apiVersion: devopstoolkitseries.com/v1alpha1
kind: SQLClaim
metadata:
  name: silly-demo-db
spec:
  id: silly-demo-db
  compositionSelector:
    matchLabels:
      provider: aws
      db: postgresql
  parameters:
    version: "13"
    size: small
---
apiVersion: devopstoolkitseries.com/v1alpha1
kind: AppClaim
metadata:
  name: silly-demo
spec:
  id: silly-demo
  compositionSelector:
    matchLabels:
      type: backend-db
      location: remote
  parameters:
    namespace: production
    image: c8n.io/vfarcic/silly-demo:1.4.52
    port: 8080
    host: silly-demo.acme.com
    dbSecret:
      name: silly-demo-db
      namespace: a-team
    kubernetesProviderConfigName: cluster-01

There are around fifty lines of YAML. That might look intimidating but, once you see what that YAML creates, you'll realize that it is infinitely simpler than if we tried to accomplish the same result any other way.

It starts with a ClusterClaim that will create a Kubernetes cluster in your favorite hyperscaler. Which hyperscaler it will be depends on the matchLabels. In my case, it will be an AWS EKS cluster. Then there are a few parameters that specify that the nodes of the cluster should be small and that the minimum number of nodes (minNodeCount) should be 3. I don't have to worry about exact node sizes. Crossplane will translate small to whatever that means in AWS.

Please note that if you're using Google Cloud, minNodeCount is set to 1 and not 3 because it will be a regional cluster running in three zones, and GKE will create 1 in each, so there will be 3 in total.

To make things more interesting, ClusterClaim will not only create a managed Kubernetes cluster, but also make it production-ready by setting up Cilium, installing Crossplane, creating a few Namespaces, and quite a few other things.

Further on we have a Secret that contains the encoded password for the database. We will improve on that later when we integrate Crossplane with solutions for managing secrets.

The third definition is SQLClaim which will create a managed PostgreSQL database in the hyperscaler of choice. But that's not all. Not only that a database server will be created, but a new user and a database will be created in that server as well.

Finally, there is AppClaim which will run a backend application in the yet-to-be-created cluster defined through ClusterClaim. That backend app will be automatically connected to the database defined through the SQLClaim.

In other words, those fifty or so lines of YAML will create a production-ready managed Kubernetes cluster, a managed PostgreSQL database, and a backend application. Moreover, those three will be interconnected. The application will be running in that cluster and will be connected to the database.

There are probably a few other things that will happen and we'll comment on them later.

Now, as you'll see soon, creating all that often requires experience in AWS, Google, Azure, Kubernetes, databases, and quite a few other technologies. But, as a user of Crossplane Compositions, we don't need to worry about any of that. Right now, we are consumers of a service created by someone else.

Now, you might expect me to execute a command that will make all that happen, but that's not what we'll do. Instead, we'll treat it as the desired state and push it to Git where it belongs.

So, we'll copy the manifest to the a-team directory in the repo which happens to be monitored by Argo CD,...

cp examples/$HYPERSCALER-intro.yaml a-team/intro.yaml

...and add, commit, and push changes to the Git repo.

git add .

git commit -m "Intro"

git push

That's it. That's the only action I'll do in this chapter. That's all it takes.

FIXME: Open http://argocd.127.0.0.1.nip.io in a browser.

Now, please open http://argocd.127.0.0.1.nip.io in a browser. Use admin as the username and admin123 as the password, and sign in.

There is only one application managed by Argo CD. That's the one that monitors the a-team directory. To see the "real" action, I'll enter into the application and... Lo and behold! Those fifty or so lines of YAML expanded into quite a few resources.

We can also observe what's going on by interacting directly with the control plane cluster through kubectl by retrieving clusterclaims,sqlclaims,appclaims.

FIXME: Create a YT short about crossplane trace.

kubectl --namespace a-team get clusterclaims,sqlclaims,appclaims

The output is as follows (truncated for brevity).

NAME           CLUSTERNAME CONTROLPLANE   NODEPOOL       SYNCED READY ...
.../cluster-01 cluster-01  ReconcileError ReconcileError True   False ...

NAME              SYNCED READY CONNECTION-SECRET AGE
.../silly-demo-db True   False                   7s

NAME           HOST                SYNCED READY CONNECTION-SECRET AGE
.../silly-demo silly-demo.acme.com True   False                   7s

We can see that the clusterclaim, sqlclaim, and appclaim, the resources we defined in YAML, are there. None of them are READY. It'll take a bit of time until the Kubernetes cluster, the database, the application, and everything in between are created and fully operational.

Now, those three resources "expanded" into quite a few lower-level resources which we can observe by retrieving all managed resources.

kubectl get managed

The output is as follows (truncated for brevity).

NAME                             READY SYNCED EXTERNAL-NAME AGE
internetgateway.../cluster-01    False True                 18s
internetgateway.../silly-demo-db       False                18s

NAME                                       READY SYNCED EXTERNAL-NAME AGE
mainroutetableassociation.../cluster-01          False                18s
mainroutetableassociation.../silly-demo-db       False                18s

NAME                   READY SYNCED EXTERNAL-NAME AGE
route.../cluster-01          False                18s
route.../silly-demo-db       False                18s

NAME                                      READY SYNCED EXTERNAL-NAME AGE
routetableassociation.../cluster-01-1a          False                18s
routetableassociation.../cluster-01-1b          False                18s
routetableassociation.../cluster-01-1c          False                18s
routetableassociation.../silly-demo-db-1a       False                18s
routetableassociation.../silly-demo-db-1b       False                18s
routetableassociation.../silly-demo-db-1c       False                18s

NAME                        READY SYNCED EXTERNAL-NAME AGE
routetable.../cluster-01    False True                 18s
routetable.../silly-demo-db       False                18s
...

That output shows AWS resources (and a few others). If you chose Google Cloud or Azure, your output will differ.

In my case, I got AWS internet gateways, route table associations, main route table associations, routes, route table associations, route tables, security group rules, security groups, subnets, VPCs, an addon, a cluster auth, a cluster, a node group, role policy attachments, roles, an RDS instance, and a subnet group. Those are the AWS resources that Crossplane is managing. Further on, there are some Helm releases and Kubernetes objects that will be created in the control plane and the new cluster. Finally, there is a database that will be created in the managed database server.

If you're using Google Cloud or Azure, the number of managed resources will be smaller. Nevertheless, no matter what you chose, it's clear that those fifty or so lines of YAML are very simple to define and understand when compared to everything they produce.

An easier way to explore resources managed by Crossplane is through the crossplane CLI. Among other features, it contains the trace command which is, at the time of this recording, a beta release. By the time you watch this, it might become GA so you might need to remove beta from the command that follows.

crossplane beta trace clusterclaim cluster-01 --namespace a-team

The output is as follows (truncated for brevity).

NAME                                       SYNCED READY STATUS                      
ClusterClaim/cluster-01 (a-team)           True   False Waiting: ...
└─ CompositeCluster/cluster-01-lkm96       True   False Creating...
   ├─ InternetGateway/cluster-01           True   True  Available
   ├─ MainRouteTableAssociation/cluster-01 True   True  Available
   ├─ RouteTableAssociation/cluster-01-1a  True   False Creating
   ├─ RouteTableAssociation/cluster-01-1b  False  -     ReconcileError...
   ├─ RouteTableAssociation/cluster-01-1c  True   False Creating
   ├─ RouteTable/cluster-01                True   True  Available
   ├─ Route/cluster-01                     False  -     ReconcileError...
   ...

That output shows a tree-like structure of all the resources managed by the ClusterClaim resource (one of the resources we defined in YAML).

We can see that ClusterClaim claim created the CompositeCluster composite resource which, in turn, created a bunch of managed resources. We'll go through claims, Composite Resources, and Managed Resources later. What matters, for now, is that we can see what are all the resources that are managed by the ClusterClaim we defined earlier.

Another important note is that some of those resources are synced while others are not. Some are ready, and others are not. Unlike some other tools, resources managed by Crossplane are eventually consistent. Some cannot be synchronized because they miss some information from other resources and some are not ready because their preconditions were not met or they are in the process of being created.

As you'll see later, everything will be eventually consistent.

We can observe a similar output if we trace the SQLClaim, the second Crossplane resource we defined.

crossplane beta trace sqlclaim silly-demo-db --namespace a-team

The output is as follows (truncated for brevity).

NAME                                          SYNCED READY STATUS
SQLClaim/silly-demo-db (a-team)               True   False Waiting:...
└─ SQL/silly-demo-db-lp9xm                    True   False Creating...
   ├─ VPC/silly-demo-db                       True   True  Available
   ├─ Subnet/silly-demo-db-a                  True   False Creating
   ├─ Subnet/silly-demo-db-b                  True   True  Available
   ├─ Subnet/silly-demo-db-c                  True   False Creating
   ├─ SubnetGroup/silly-demo-db               False  -     ReconcileError...
   ├─ InternetGateway/silly-demo-db           True   True  Available
   ├─ RouteTable/silly-demo-db                True   True  Available
   ├─ Route/silly-demo-db                     False  -     ReconcileError...
   ├─ MainRouteTableAssociation/silly-demo-db False  -     ReconcileError...
   ├─ RouteTableAssociation/silly-demo-db-1a  False  -     ReconcileError...
   ├─ RouteTableAssociation/silly-demo-db-1b  False  -     ReconcileError...
   ├─ RouteTableAssociation/silly-demo-db-1c  False  -     ReconcileError...
   ├─ SecurityGroup/silly-demo-db             True   True  Available
   ├─ SecurityGroupRule/silly-demo-db         False  -     ReconcileError...
   ├─ Instance/silly-demo-db                  False  -     ReconcileError...
   ├─ ProviderConfig/silly-demo-db            -      -
   ├─ Database/silly-demo-db                  False  -     ReconcileError...
   └─ ProviderConfig/silly-demo-db-sql        -      -

The database server and everything else required to run the database are also not yet ready. It will be ready soon.

Finally, we can trace the application itself defined as the AppClaim.

crossplane beta trace appclaim silly-demo --namespace a-team

The output is as follows (truncated for brevity).

NAME                               SYNCED READY STATUS
AppClaim/silly-demo (a-team)       True   False Waiting...
└─ App/silly-demo-9pfkj            True   False Creating...
   ├─ Object/silly-demo-deployment False  -     ReconcileError...
   ├─ Object/silly-demo-service    False  -     ReconcileError...
   ├─ Object/silly-demo-ingress    False  -     ReconcileError...
   └─ Object/silly-demo-secret     False  -     ReconcileError...

It's "normal" that the application is not yet running. It should run on the new cluster and until the cluster is created, there is no place for it to run. As with the other two, all I can say is that it will become eventually consistent.

Here's what you should do next.

Take a short break. Get a coffee.

Once you're back, we can trace the SQLClaim again.

crossplane beta trace sqlclaim silly-demo-db --namespace a-team

The output is as follows.

NAME                                          SYNCED READY STATUS
SQLClaim/silly-demo-db (a-team)               True   True  Available
└─ SQL/silly-demo-db-lp9xm                    True   True  Available
   ├─ VPC/silly-demo-db                       True   True  Available
   ├─ Subnet/silly-demo-db-a                  True   True  Available
   ├─ Subnet/silly-demo-db-b                  True   True  Available
   ├─ Subnet/silly-demo-db-c                  True   True  Available
   ├─ SubnetGroup/silly-demo-db               True   True  Available
   ├─ InternetGateway/silly-demo-db           True   True  Available
   ├─ RouteTable/silly-demo-db                True   True  Available
   ├─ Route/silly-demo-db                     True   True  Available
   ├─ MainRouteTableAssociation/silly-demo-db True   True  Available
   ├─ RouteTableAssociation/silly-demo-db-1a  True   True  Available
   ├─ RouteTableAssociation/silly-demo-db-1b  True   True  Available
   ├─ RouteTableAssociation/silly-demo-db-1c  True   True  Available
   ├─ SecurityGroup/silly-demo-db             True   True  Available
   ├─ SecurityGroupRule/silly-demo-db         True   True  Available
   ├─ Instance/silly-demo-db                  True   True  Available
   ├─ ProviderConfig/silly-demo-db            -      -
   ├─ Database/silly-demo-db                  True   True  Available
   ├─ ProviderConfig/silly-demo-db-sql        -      -
   └─ Object/silly-demo-db                    True   True  Available

This time, all the resources required to run PostgreSQL are Available. The managed database is up and running.

Let's see what's going on with the cluster.

crossplane beta trace clusterclaim cluster-01 --namespace a-team

The output is as follows (truncated for brevity).

NAME                                               SYNCED READY STATUS
ClusterClaim/cluster-01 (a-team)                   True   True  Available
└─ CompositeCluster/cluster-01-lkm96               True   True  Available
   ├─ InternetGateway/cluster-01                   True   True  Available
   ├─ MainRouteTableAssociation/cluster-01         True   True  Available
   ├─ RouteTableAssociation/cluster-01-1a          True   True  Available
   ├─ RouteTableAssociation/cluster-01-1b          True   True  Available
   ├─ RouteTableAssociation/cluster-01-1c          True   True  Available
   ├─ RouteTable/cluster-01                        True   True  Available
   ├─ Route/cluster-01                             True   True  Available
   ...

All the resources required to run a cluster are up and running as well. That probably means that Crossplane could apply resources related to the backend application. It should run inside that cluster and it should be connected to the database. Since both of those are fully operational now, the application should be running as well.

Let's check it out.

crossplane beta trace appclaim silly-demo --namespace a-team

The output is as follows.

NAME                               SYNCED READY STATUS      
AppClaim/silly-demo (a-team)       True   True  Available   
└─ App/silly-demo-9pfkj            True   True  Available   
   ├─ Object/silly-demo-deployment True   True  Available   
   ├─ Object/silly-demo-service    True   True  Available   
   ├─ Object/silly-demo-ingress    True   True  Available   
   └─ Object/silly-demo-secret     True   True  Available   

If you chose Azure, there is a bug in the CompositeResourceDefinition that results in the silly-demo-secret resource not being created. Please ignore it until I fix the issue. It should not affect the rest of the tutorial.

Everything is up and running. Those three resources we defined at the beginning expanded into dozens of other resources. Some of them are resources in a hyperscaler while others are resources in the new Kubernetes cluster, with a few others sprinkled for good taste.

We are almost done. The last thing we'll do is feed my paranoia. I need to confirm that everything is indeed working as expected. We'll do that by entering into the newly created cluster and taking a peek to see whether the backend application is indeed running. To do that, we'll export the KUBECONFIG variable...

export KUBECONFIG=$PWD/kubeconfig.yaml

...and retrieve the Kube config from the hyperscaler of choice.

# Execute only if using Google Cloud
gcloud container clusters get-credentials cluster-01 \
    --region us-east1 --project $PROJECT_ID

# Execute only if using AWS
aws eks update-kubeconfig --region us-east-1 \
    --name cluster-01 --kubeconfig $KUBECONFIG

# Execute only if using Azure
az aks get-credentials --resource-group cluster01 \
    --name cluster-01 --file $KUBECONFIG

This was one of the cases when instructions differed depending on the hyperscaler you chose. You'll find the instructions for your choice in the Gist.

Now that the cluster config is set, we can check whether it indeed has three nodes I specified.

kubectl get nodes

The output is as follows.

NAME                       STATUS ROLES  AGE VERSION
ip-10-0-0-241.ec2.internal Ready  <none> 30s v1.27.7-eks-e71965b
ip-10-0-1-82.ec2.internal  Ready  <none> 26s v1.27.7-eks-e71965b
ip-10-0-2-216.ec2.internal Ready  <none> 32s v1.27.7-eks-e71965b

The nodes are there.

How about the application? It should be running inside the production Namespace.

kubectl --namespace production get all,ingresses

The output is as follows.

NAME                           READY STATUS  RESTARTS AGE
pod/silly-demo-998c464fc-8j9jj 1/1   Running 0        17m

NAME               TYPE      CLUSTER-IP    EXTERNAL-IP PORT(S)  AGE
service/silly-demo ClusterIP 172.20.219.47 <none>      8080/TCP 17m

NAME                       READY UP-TO-DATE AVAILABLE AGE
deployment.apps/silly-demo 1/1   1          1         17m

NAME                                 DESIRED CURRENT READY AGE
replicaset.apps/silly-demo-998c464fc 1       1       1     17m

NAME                                 CLASS  HOSTS               ADDRESS PORTS AGE
ingress.networking.k8s.io/silly-demo <none> silly-demo.acme.com         80    17m

The application is indeed up and running and it is connected to the PostgreSQL database.

As I mentioned earlier, this was only a glimpse of some of the features Crossplane offers. There's much more to it and I did not want to prolong this section more than necessary.

The goal was not to teach you how to use Crossplane and all its nitty-gritty details. That's coming next. So far, the objective was for you to see some of the things it can do so that you can decide whether that's something you might be interested in. I hope that the answer is a resounding YES.

Crossplane is unique. It's special. It changes the way we manage resources of any kind. It enables us to create control planes. It enables us to do things that were, in the past, reserved for only a handful of companies.

So... What do you say? Shall we continue?

Destroy Everything

Each section starts with instructions on how to create everything needed for that section and ends with instructions on how to destroy everything we created. That way you can explore each section independently from others. You can take a break without having to run and pay for, all the resources we created. I wanted to make it easy, but also cheap. No one should spend more time and more money than necessary.

In that spirit, here are the instructions on how to destroy everything we created.

unset KUBECONFIG

chmod +x destroy/00-intro.sh

./destroy/00-intro.sh

exit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment