Getting Started
This page walks through every step from an empty cluster to a working Azure landing zone managed by Helm + Crossplane. No steps are skipped.
Prerequisites
| Tool | Minimum Version | Purpose |
|---|---|---|
kubectl | v1.28+ | Cluster access |
helm | v3.12+ | Chart rendering and installation |
az (Azure CLI) | v2.50+ | Service Principal creation |
| A Kubernetes cluster | v1.28+ | kind, AKS, or any conformant cluster |
| An Azure subscription | — | Resources will be created and billed |
Step 1: Create a Cluster (if needed)
If you already have a cluster, skip to Step 2.
For local development with kind:
# kind-cluster.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: workerkind create cluster --name crossplane-lab --config kind-cluster.yaml
kubectl cluster-info --context kind-crossplane-labStep 2: Install Crossplane
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update
helm install crossplane crossplane-stable/crossplane \
--namespace crossplane-system \
--create-namespace \
--waitVerify:
kubectl get pods -n crossplane-systemExpected output:
NAME READY STATUS RESTARTS AGE
crossplane-7f8d5b4c9-xxxxx 1/1 Running 0 30s
crossplane-rbac-manager-6c9b7d8f4-xxxxx 1/1 Running 0 30sStep 3: Install Azure Providers
Apply the provider manifests:
kubectl apply -f setup/providers.yamlThis installs exactly four sub-providers:
Wait for all providers to become healthy:
kubectl get providers -wExpected output (all should show HEALTHY: True):
NAME INSTALLED HEALTHY PACKAGE AGE
provider-family-azure True True xpkg.upbound.io/upbound/provider-family-azure:v2.3.0 60s
provider-azure-network True True xpkg.upbound.io/upbound/provider-azure-network:v2.3.0 60s
provider-azure-storage True True xpkg.upbound.io/upbound/provider-azure-storage:v2.3.0 60s
provider-azure-keyvault True True xpkg.upbound.io/upbound/provider-azure-keyvault:v2.3.0 60sWARNING
If a provider stays HEALTHY: False, check its pod logs:
kubectl get pods -n crossplane-system
kubectl logs -n crossplane-system <provider-pod-name>Common issue: insufficient RBAC or CRD installation failures on undersized clusters.
Step 4: Configure Azure Credentials
Create a Service Principal
# Replace with your actual subscription ID
export AZURE_SUBSCRIPTION_ID="your-subscription-id"
az ad sp create-for-rbac \
--name "crossplane-sp" \
--role Contributor \
--scopes /subscriptions/$AZURE_SUBSCRIPTION_ID \
-o json > /tmp/azure-creds.jsonThe output file contains:
{
"appId": "...",
"displayName": "crossplane-sp",
"password": "...",
"tenant": "..."
}TIP
Note the tenant value — you'll need it for keyvault.tenantId in your values files.
Create the Kubernetes Secret
kubectl create secret generic azure-creds \
-n crossplane-system \
--from-file=creds=/tmp/azure-creds.json
# Remove credentials from disk immediately
rm /tmp/azure-creds.jsonApply ProviderConfig
kubectl apply -f setup/provider-config.yamlVerify:
kubectl get providerconfigNAME AGE
default 10sStep 5: Deploy the Dev Environment
First, update the tenant ID in your values file:
# Edit charts/azure-base/values-dev.yaml
# Replace REPLACE-WITH-YOUR-TENANT-ID with your actual Azure AD tenant IDThen deploy:
helm install azure-base-dev ./charts/azure-base \
-f ./charts/azure-base/values-dev.yamlStep 6: Watch Resources Provision
# Watch all managed resources
kubectl get managed -wYou'll see resources transition through states:
NAME SYNCED READY AGE
myapp-dev-rg True True 30s
myapp-dev-vnet True False 25s ← Still creating
myapp-dev-subnet-app False False 20s ← Waiting for VNet
myapp-dev-subnet-data False False 20s ← Waiting for VNet
myapp-dev-nsg True False 20s
myapp-dev-nsg-assoc-app False False 15s ← Waiting for Subnet + NSG
myapp-dev-sa True False 20s
myapp-dev-container-data False False 15s ← Waiting for Storage Account
myapp-dev-kv True False 20sTIP
Resources that depend on others (Subnet depends on VNet, Container depends on Account) will show SYNCED: False until their dependency is READY: True. This is Crossplane's *Ref resolution working — it automatically retries until the referenced resource exists.
Check a specific resource:
kubectl describe virtualnetwork myapp-dev-vnetLook for Status.Conditions:
Status:
Conditions:
- Type: Ready
Status: "True"
- Type: Synced
Status: "True"Step 7: Verify in Azure
# List resource groups
az group list --query "[?tags.project=='myapp']" -o table
# List resources in the group
az resource list --resource-group myapp-dev-rg -o tableDry Run (No Azure Account Needed)
You can render the chart without applying anything:
helm template azure-base-dev ./charts/azure-base \
-f ./charts/azure-base/values-dev.yamlThis outputs the exact YAML that helm install would apply. Use this for:
- Reviewing resources before deploying
- CI/CD validation
- Understanding what each template produces
- Debugging value injection issues
Teardown
# Remove infrastructure
helm uninstall azure-base-dev
# Watch resources being deleted
kubectl get managed -wCrossplane will delete the Azure resources in reverse dependency order. This usually completes in 2-5 minutes depending on resource types.
DANGER
By default, deletionPolicy: Delete means uninstalling the Helm release deletes the actual Azure resources. This includes data in Storage Accounts and secrets in Key Vaults. If you want to keep Azure resources after Helm uninstall, add deletionPolicy: Orphan to your templates.