Module 2: Your First Managed Resource¶
Time: 15 minutes
Objective: Deploy a real Azure Resource Group using Crossplane and GitOps workflows
Overview¶
Now that the Azure provider is configured, let's create our first cloud resource! We'll deploy an Azure Resource Group, which is a fundamental building block for organizing Azure resources.
What you'll experience: 1. Create a Managed Resource manifest 2. Set up GitOps deployment 3. Watch Crossplane create real Azure infrastructure 4. Verify the resource in Azure Portal
Prerequisites Check¶
Ensure the previous module completed successfully:
# Verify provider is ready
kubectl get provider provider-family-azure
# Should show INSTALLED: True, HEALTHY: True
# Verify provider config exists
kubectl get providerconfig default
# Should show READY: True
# Verify Azure provider pods are running
kubectl get pods -n crossplane-system | grep azure
# Should show Running pod
If any of these fail, return to Module 1 and complete the setup.
Step 1: Understanding Managed Resources¶
Managed Resources (MRs) are Crossplane's representation of cloud infrastructure resources. Each MR corresponds to exactly one cloud resource (1:1 relationship).
Key characteristics:
- Declarative: Describe desired state, Crossplane handles the implementation
- Cloud-agnostic API: Same patterns work across AWS, Azure, GCP
- Kubernetes native: Use kubectl and GitOps workflows
- Lifecycle managed: Crossplane handles creation, updates, and deletion
Step 2: Create Directory Structure¶
Let's organize our resource deployment:
# Create directory for Azure resources
mkdir -p platform-core/azure/01-resource-group
# Verify structure
tree platform-core/azure/
Step 3: Define Your First Managed Resource¶
Create an Azure Resource Group using Crossplane:
# platform-core/azure/01-resource-group/resource-group.yaml
apiVersion: azure.upbound.io/v1beta1
kind: ResourceGroup
metadata:
name: tutorial-rg-001
namespace: tutorial
labels:
app: crossplane-tutorial
component: resource-group
environment: tutorial
spec:
forProvider:
location: East US
tags:
environment: tutorial
managed-by: crossplane
purpose: learning
created-by: crossplane-tutorial
providerConfigRef:
name: default
Key components explained:
apiVersion: Azure provider's API for Resource Groupskind: ResourceGroup: The specific Azure resource typemetadata.name: Kubernetes resource name (must be unique in namespace)spec.forProvider: Azure-specific configurationlocation: Azure region for the resourcetags: Metadata for resource organization and billingproviderConfigRef: Which provider config to use (we created "default")
Step 4: Create ArgoCD Application¶
Set up GitOps deployment for the resource:
# platform-core/azure/01-resource-group/application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: azure-resource-group-tutorial
namespace: argocd
labels:
app: crossplane-tutorial
component: azure-resources
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: __YOUR_PLATFORM_GITOPS_REPO_URL__
targetRevision: HEAD
path: platform-core/azure/01-resource-group
destination:
server: https://kubernetes.default.svc
namespace: tutorial # v2.0: Match namespace where resources are created
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=true
info:
- name: 'Purpose'
value: 'Deploy tutorial Azure Resource Group via Crossplane'
- name: 'Resource Type'
value: 'Azure Resource Group'
Step 5: Update Repository URL¶
Replace the placeholder with your repository URL:
# Replace placeholder (adjust path if your repo URL env var has different name)
sed -i "s|__YOUR_PLATFORM_GITOPS_REPO_URL__|$PLATFORM_GITOPS_REPO_URL|g" platform-core/azure/01-resource-group/application.yaml
Step 6: Commit and Deploy¶
Let's commit our changes and trigger the GitOps deployment:
# Add the new files
git add platform-core/azure/
# Commit with descriptive message
git commit -m "feat: add first managed resource - Azure Resource Group
- Create Azure Resource Group in East US region
- Deploy via ArgoCD with automated sync
- Include proper labeling and tagging
- Demonstrate Crossplane managed resource lifecycle"
# Push to trigger deployment
git push
Step 7: Deploy the ArgoCD Application¶
Apply the ArgoCD application to start managing the resource:
# Deploy the ArgoCD application
kubectl apply -f platform-core/azure/01-resource-group/application.yaml
# Verify application was created
kubectl get application azure-resource-group-tutorial -n argocd
Step 8: Watch the Magic Happen! ๐¶
Now let's watch Crossplane create real Azure infrastructure:
8.1 Monitor ArgoCD Sync¶
# Watch ArgoCD sync the resource
kubectl get application azure-resource-group-tutorial -n argocd -w
# Or check sync status
argocd app get azure-resource-group-tutorial
8.2 Monitor Resource Creation¶
# Watch resource creation
kubectl get resourcegroup tutorial-rg-001 -n tutorial -w
# Check resource status
kubectl get resourcegroup tutorial-rg-001 -n tutorial -o yaml
Expected progression:
1. Initial state: Resource appears with READY: False
2. Creating: Crossplane calls Azure API to create resource
3. Created: READY: True, SYNCED: True - success! ๐
8.3 Monitor Provider Logs¶
# Watch provider logs to see Azure API calls
kubectl logs -n crossplane-system deployment/upbound-provider-azure -f
You'll see logs like:
Step 9: Verify in Azure Portal¶
Let's confirm the resource was created in Azure:
9.1 Using Azure CLI¶
# List resource groups to find ours
az group list --query "[?contains(name, 'tutorial-rg-001')]" --output table
# Get detailed information
az group show --name tutorial-rg-001 --output table
9.2 Using Azure Portal¶
- Open Azure Portal
- Navigate to "Resource Groups"
- Look for
tutorial-rg-001 - Verify location is "East US"
- Check tags match what we specified
Step 10: Explore Resource Status¶
Let's examine what Crossplane provides for resource management:
10.1 Resource Status Fields¶
Key status fields:
- conditions: Health and sync status
- atProvider: Actual state from Azure
- observedGeneration: Last processed spec version
10.2 Resource Events¶
# Check Kubernetes events for the resource
kubectl describe resourcegroup tutorial-rg-001 -n tutorial | grep -A 10 Events
# Or get events directly
kubectl get events -n tutorial --field-selector involvedObject.name=tutorial-rg-001
10.3 Resource Conditions¶
# Check resource conditions
kubectl get resourcegroup tutorial-rg-001 -n crossplane-system -o jsonpath='{.status.conditions}' | jq '.'
Healthy resource should show:
- Ready: True
- Synced: True
- LastSyncTime: Recent timestamp
Understanding What Happened¶
The GitOps Flow¶
sequenceDiagram
participant Dev as Developer
participant Git as Git Repo
participant ArgoCD as ArgoCD
participant Crossplane as Crossplane
participant Azure as Azure API
Dev->>Git: 1. Commit ResourceGroup YAML
ArgoCD->>Git: 2. Detect changes
ArgoCD->>Crossplane: 3. Apply ResourceGroup
Crossplane->>Azure: 4. Create Resource Group
Azure->>Crossplane: 5. Confirm creation
Crossplane->>ArgoCD: 6. Update status
ArgoCD->>Dev: 7. Sync complete
Crossplane Resource Lifecycle¶
- Creation: Crossplane detects new resource, calls Azure API
- Monitoring: Continuously polls Azure for status changes
- Updates: If spec changes, Crossplane updates Azure resource
- Deletion: If resource deleted from Git, Crossplane removes from Azure
Experiment Time! ๐งช¶
Let's make a change to see the reconciliation in action:
Modify Resource Tags¶
Add a new tag to the spec.forProvider.tags section:
spec:
forProvider:
tags:
environment: tutorial
managed-by: crossplane
purpose: learning
created-by: crossplane-tutorial
modified-at: "$(date -u +%Y-%m-%dT%H:%M:%SZ)" # Add this line
# Commit the change
git add platform-core/azure/01-resource-group/resource-group.yaml
git commit -m "feat: add timestamp tag to resource group"
git push
# Watch Crossplane update the Azure resource
kubectl get resourcegroup tutorial-rg-001 -n tutorial -w
Verify in Azure:
Troubleshooting Common Issues¶
Resource Stuck in Creating State¶
# Check resource events
kubectl describe resourcegroup tutorial-rg-001 -n tutorial
# Check provider logs
kubectl logs -n crossplane-system deployment/upbound-provider-azure --tail=50
# Verify provider config
kubectl describe providerconfig default
ArgoCD Sync Issues¶
# Check application status
argocd app get azure-resource-group-tutorial
# Force sync if needed
argocd app sync azure-resource-group-tutorial
# Check for sync errors
kubectl describe application azure-resource-group-tutorial -n argocd
Permission Issues¶
# Verify service principal has correct permissions
az role assignment list --assignee $CLIENT_ID --output table
# Check if subscription is correct
kubectl get secret azure-secret -n crossplane-system -o jsonpath='{.data.creds}' | base64 -d | jq '.subscriptionId'
Learning Outcomes¶
After completing this module, you should understand:
- โ Managed Resources represent cloud resources in Crossplane
- โ GitOps workflow for infrastructure deployment
- โ Resource lifecycle - creation, monitoring, updates, deletion
- โ Status monitoring and troubleshooting techniques
- โ Real-world impact - Git commits create cloud infrastructure!
Key Concepts Reinforced¶
Declarative Infrastructure¶
- You declared "I want a Resource Group with these properties"
- Crossplane handled all the implementation details
- Changes to declaration trigger automatic reconciliation
GitOps Benefits¶
- Version control: All changes tracked in Git
- Audit trail: Who changed what and when
- Rollback capability: Git revert undoes infrastructure changes
- Collaboration: Team can review infrastructure changes via PRs
Security Integration¶
- Credentials remain encrypted in Git
- Only authorized clusters can decrypt and use credentials
- All API calls use least-privilege service principal
Next Steps¶
Congratulations! You've successfully created your first cloud resource using Crossplane and GitOps. You now have real Azure infrastructure managed by Git commits.
In the next module, we'll explore what happened under the hood and learn more about Crossplane's architecture and troubleshooting techniques.
โก๏ธ Module 3: Understanding What Happened
Quick Reference¶
Resource Status Commands¶
# Quick status check
kubectl get resourcegroups -n azure-resource-group
# Detailed resource info
kubectl describe resourcegroup tutorial-rg-001 -n azure-resource-group
# Watch resource changes
kubectl get resourcegroup tutorial-rg-001 -n azure-resource-group -w
# Get resource YAML
kubectl get resourcegroup tutorial-rg-001 -n azure-resource-group -o yaml
Step 5: Complete Verification Guide¶
Kubernetes Verification Commands¶
# Check if resource is created and ready
kubectl get resourcegroup tutorial-rg-001 -n azure-resource-group
# Expected: STATUS should be "Ready" after 2-3 minutes
# View detailed status and conditions
kubectl describe resourcegroup tutorial-rg-001 -n azure-resource-group
# Look for: Status conditions showing "Ready: True"
# Watch resource creation progress
kubectl get resourcegroup tutorial-rg-001 -n azure-resource-group -w
# Watch until STATUS changes from "Creating" to "Ready"
# Get complete resource YAML with Azure details
kubectl get resourcegroup tutorial-rg-001 -n azure-resource-group -o yaml
# Contains Azure resource ID and connection details
Azure CLI Verification Commands¶
# List all resource groups to confirm creation
az group list --output table
# Look for: tutorial-rg-001 in East US region
# Show specific resource group details
az group show --name tutorial-rg-001 --output table
# Expected: State should be "Succeeded"
# Verify all tags were applied correctly
az group show --name tutorial-rg-001 --query tags
# Expected: Should show environment, managed-by, purpose, created-by tags
# Confirm resource group location
az group show --name tutorial-rg-001 --query location
# Expected: "eastus"
ArgoCD Verification¶
# Check application sync status
kubectl get application azure-resource-group-tutorial -n argocd
# Expected: SYNC STATUS: "Synced", HEALTH STATUS: "Healthy"
# Get detailed application status
argocd app get azure-resource-group-tutorial
# Shows complete dependency tree and sync history
Expected Results Summary¶
โ
Successful deployment indicators:
- kubectl shows ResourceGroup with STATUS: "Ready"
- ArgoCD shows application as "Synced" and "Healthy"
- Azure CLI shows resource group in "Succeeded" state
- All tags are present and correct in Azure
- Location matches specification (East US)
๐ง Troubleshooting common issues:
- STATUS shows "Creating" for >5 minutes โ Check Azure service principal permissions
- STATUS shows "Failed" โ Check Azure quotas and subscription limits
- ArgoCD shows "OutOfSync" โ Resource may have been modified outside Crossplane
- Resource not visible in Azure โ Check subscription context with az account show
Cleanup Resources¶
To remove the resources created in this exercise:
# Delete the managed resource by removing the configuration file
rm platform-core/azure/01-resource-group/resource-group.yaml
git add -A
git commit -m "cleanup: remove tutorial resource group"
git push
# Verify deletion in Azure (after ArgoCD sync)
az group show --name tutorial-rg-001
# Expected: ResourceGroupNotFound error after GitOps cleanup
Cost Note: Resource Groups themselves don't incur charges, but always clean up resources to develop good habits for more expensive resources like databases and compute instances.