Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
822 changes: 822 additions & 0 deletions LAB06.md

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ Workshop for Platform Engineering in a day
- Convert traditional IaC workflows to GitOps with Terranetes
- Implement platform engineering patterns for self-service infrastructure

### [LAB06: Deploying with Crossplane](LAB06.md)
- Install Crossplane in your Kind cluster and configure the Azure provider
- Deploy Azure resources directly as Managed Resources
- Create platform abstractions using XRDs and Compositions
- Enable developer self-service via Claims synced through ArgoCD
- (Stretch) Provision GitHub repositories with the GitHub provider

#### Comments or feedback how to improve let us know before you go home or connect here:
- https://www.linkedin.com/in/geertvandercruijsen/
- https://www.linkedin.com/in/cvs79/
Expand Down
139 changes: 139 additions & 0 deletions lab06/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# LAB06: Deploying with Crossplane

This directory contains all the configuration files needed for LAB06.

## Directory Structure

```
lab06/
└── crossplane/
├── provider-family-azure.yaml # Install Azure provider family
├── providerconfig-azure.yaml # Azure ProviderConfig (credentials reference)
├── managed-resources/ # Direct Managed Resource approach (Part 3)
│ ├── resourcegroup.yaml # Azure ResourceGroup MR
│ └── storageaccount.yaml # Azure StorageAccount MR
├── composition/ # Abstraction layer (Part 4)
│ ├── xrd.yaml # CompositeResourceDefinition (XRD)
│ └── composition.yaml # Composition: XAppStorage → RG + SA
├── claims/ # Developer-facing API (Part 5)
│ └── app-storage-claim.yaml # Example AppStorageClaim
├── argocd/ # GitOps integration (Part 5)
│ └── argocd-application.yaml # ArgoCD Application for Claims
└── stretch/ # Stretch goal (Part 6)
└── provider-github.yaml # GitHub provider + example Repository MR
```

## Quick Start

### Install Crossplane

```bash
# Add the Crossplane Helm repo
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update

# Install Crossplane
helm install crossplane \
--namespace crossplane-system \
--create-namespace \
crossplane-stable/crossplane

# Verify pods
kubectl get pods -n crossplane-system
```

### Install Azure Provider

```bash
kubectl apply -f lab06/crossplane/provider-family-azure.yaml

# Wait until providers are healthy
kubectl get providers -w
```

### Configure Azure Credentials

```bash
# Create the Service Principal credentials secret (reuse from LAB03)
kubectl create secret generic azure-sp-creds \
--from-literal=credentials="$(cat azure-sp.json)" \
--namespace crossplane-system

# Apply the ProviderConfig
kubectl apply -f lab06/crossplane/providerconfig-azure.yaml
```

### Deploy Managed Resources Directly (Part 3)

```bash
# Edit resourcegroup.yaml and storageaccount.yaml to set unique names first
kubectl apply -f lab06/crossplane/managed-resources/resourcegroup.yaml
kubectl get resourcegroup -w # Wait for READY=True

kubectl apply -f lab06/crossplane/managed-resources/storageaccount.yaml
kubectl get accounts -w
```

### Deploy via Composition (Parts 4-5)

```bash
# Install the XRD and Composition
kubectl apply -f lab06/crossplane/composition/xrd.yaml
kubectl apply -f lab06/crossplane/composition/composition.yaml

# Edit app-storage-claim.yaml to set unique names, then:
kubectl apply -f lab06/crossplane/claims/app-storage-claim.yaml
kubectl get appstorageclaims -n team-alpha -w
```

### GitOps with ArgoCD (Part 5)

```bash
# Edit argocd-application.yaml to point at your Git repository, then:
kubectl apply -f lab06/crossplane/argocd/argocd-application.yaml -n argocd

# Check the application in ArgoCD
argocd app get crossplane-claims
```

## Prerequisites

- Completed LAB01 (Kind cluster + ArgoCD running)
- Completed LAB02 (GitOps repo + ArgoCD ApplicationSets)
- Completed LAB03 (Azure Service Principal + Azure CLI configured)

## What Gets Created

| Resource | Type | Description |
|----------|------|-------------|
| Azure Resource Group | Managed Resource | Logical container for Azure resources |
| Azure Storage Account | Managed Resource | Blob/file storage with LRS replication |
| XAppStorage / AppStorageClaim | Custom CRD | Platform abstraction over RG + SA |
| GitHub Repository (stretch) | Managed Resource | Private GitHub repo via provider-github |

## Cleanup

```bash
# Delete claims first — Crossplane cascades to Managed Resources and Azure
kubectl delete appstorageclaims --all -n team-alpha

# Delete direct Managed Resources (if created in Part 3)
kubectl delete accounts workshop-sa
kubectl delete resourcegroup workshop-rg

# Uninstall providers
kubectl delete provider upbound-provider-azure-storage
kubectl delete provider upbound-provider-family-azure

# Uninstall Crossplane
helm uninstall crossplane -n crossplane-system
```

## Documentation

See [LAB06.md](../LAB06.md) for the full lab instructions with detailed explanations and verification steps.
40 changes: 40 additions & 0 deletions lab06/crossplane/argocd/argocd-application.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# ArgoCD Application - Self-Service Claims via GitOps
# This ArgoCD Application watches a Git directory for AppStorageClaim
# manifests. When a developer commits a new Claim file, ArgoCD syncs it
# to the cluster and Crossplane takes over to provision the Azure resources.
#
# This is the same GitOps pattern used in LAB02, extended to cloud infrastructure.
#
# Instructions:
# 1. Replace YOUR_GITHUB_ORG/YOUR_REPO with your repository details
# 2. Ensure the path exists in your repository (e.g., crossplane-claims/)
# 3. Apply with: kubectl apply -f argocd-application.yaml -n argocd

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: crossplane-claims
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default

source:
repoURL: https://github.com/YOUR_GITHUB_ORG/YOUR_REPO.git
targetRevision: HEAD
# Directory in your repo where Claim YAMLs live
path: crossplane-claims

destination:
server: https://kubernetes.default.svc
# Claims are namespace-scoped; ArgoCD creates the namespace if needed
namespace: team-alpha

syncPolicy:
automated:
prune: true # Remove claims that are deleted from Git
selfHeal: true # Re-sync if someone edits the cluster manually
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
34 changes: 34 additions & 0 deletions lab06/crossplane/claims/app-storage-claim.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# AppStorageClaim - Example Claim for application teams
# A Claim is the developer-facing API. It is namespace-scoped and exposes
# only the parameters that application teams need to know about.
# The platform team's Composition handles all the complexity behind the scenes.
#
# Instructions:
# 1. Replace "yourname" placeholders with your own identifiers
# 2. Storage account names must be globally unique, lowercase, 3-24 chars
# 3. Apply with: kubectl apply -f app-storage-claim.yaml
# 4. Watch: kubectl get appstorageclaims -n team-alpha
# 5. List MRs: kubectl get resourcegroups,accounts

apiVersion: v1
kind: Namespace
metadata:
name: team-alpha

---
apiVersion: platform.workshop.io/v1alpha1
kind: AppStorageClaim
metadata:
name: myapp-storage
namespace: team-alpha
spec:
parameters:
# Must be unique in your Azure subscription
resourceGroupName: rg-yourname-crossplane-claim
# Must be globally unique, lowercase, 3-24 chars
storageAccountName: styournamecpclaim
location: swedencentral
environment: workshop
# Bind this claim to the composition
compositionRef:
name: appstorage-composition
89 changes: 89 additions & 0 deletions lab06/crossplane/composition/composition.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Composition for AppStorage
# The Composition tells Crossplane how to satisfy an AppStorage claim:
# it maps one XAppStorage → one ResourceGroup + one StorageAccount.
# This is the "Crossplane superpower" — platform engineering abstractions.
#
# Apply with: kubectl apply -f composition.yaml
# Check: kubectl get compositions

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: appstorage-composition
labels:
provider: azure
type: storage
spec:
# Which XRD this Composition satisfies
compositeTypeRef:
apiVersion: platform.workshop.io/v1alpha1
kind: XAppStorage

# The resources that will be created for every XAppStorage/Claim
resources:

# --- Resource 1: Azure Resource Group ---
- name: resource-group
base:
apiVersion: azure.upbound.io/v1beta1
kind: ResourceGroup
spec:
forProvider:
location: swedencentral # Patched below
tags:
managed-by: crossplane
providerConfigRef:
name: default
patches:
# Patch the external (Azure) name from the claim parameter
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.resourceGroupName
toFieldPath: metadata.annotations["crossplane.io/external-name"]
# Patch location from the claim parameter
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.location
toFieldPath: spec.forProvider.location
# Patch environment tag
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.environment
toFieldPath: spec.forProvider.tags.environment
# Write the Azure Resource ID back to the XR status
- type: ToCompositeFieldPath
fromFieldPath: status.atProvider.id
toFieldPath: status.resourceGroupId

# --- Resource 2: Azure Storage Account ---
- name: storage-account
base:
apiVersion: storage.azure.upbound.io/v1beta2
kind: Account
spec:
forProvider:
location: swedencentral # Patched below
resourceGroupNameSelector:
matchControllerRef: true # Auto-select the RG created above
accountTier: Standard
accountReplicationType: LRS
enableHttpsTrafficOnly: true
minTlsVersion: TLS1_2
tags:
managed-by: crossplane
providerConfigRef:
name: default
patches:
# Patch the globally unique storage account name
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.storageAccountName
toFieldPath: metadata.annotations["crossplane.io/external-name"]
# Patch location
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.location
toFieldPath: spec.forProvider.location
# Patch environment tag
- type: FromCompositeFieldPath
fromFieldPath: spec.parameters.environment
toFieldPath: spec.forProvider.tags.environment
# Write the Azure Resource ID back to the XR status
- type: ToCompositeFieldPath
fromFieldPath: status.atProvider.id
toFieldPath: status.storageAccountId
Loading