mirror of https://github.com/crossplane/docs.git
First pass at Operations docs
Signed-off-by: Nic Cope <nicc@rk0n.org>
This commit is contained in:
parent
bb454e8c5a
commit
4c36f9049c
|
|
@ -23,6 +23,8 @@ Crossplane organizes its documentation into the following sections:
|
|||
|
||||
* [Composition]({{<ref "composition">}}) covers the key concepts of composition.
|
||||
|
||||
* [Operations]({{<ref "operations">}}) covers the key concepts of operations.
|
||||
|
||||
* [Managed Resources]({{<ref "managed-resources">}}) covers the key concepts of
|
||||
managed resources.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,367 @@
|
|||
---
|
||||
title: Get Started With Operations
|
||||
weight: 300
|
||||
state: alpha
|
||||
alphaVersion: 2.0
|
||||
---
|
||||
|
||||
This guide shows how to use Crossplane Operations to automate day-two
|
||||
operational tasks. You create an Operation that checks SSL certificate
|
||||
expiry for a website.
|
||||
|
||||
**Crossplane calls this _Operations_.** Operations run function pipelines to
|
||||
perform tasks that don't fit the typical resource creation pattern - like
|
||||
certificate monitoring, rolling upgrades, or scheduled maintenance.
|
||||
|
||||
An Operation looks like this:
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: Operation
|
||||
metadata:
|
||||
name: check-cert-expiry
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: check-certificate
|
||||
functionRef:
|
||||
name: crossplane-contrib-function-python
|
||||
input:
|
||||
apiVersion: python.fn.crossplane.io/v1beta1
|
||||
kind: Script
|
||||
script: |
|
||||
import ssl
|
||||
import socket
|
||||
from datetime import datetime
|
||||
|
||||
from crossplane.function import request, response
|
||||
|
||||
def operate(req, rsp):
|
||||
hostname = "google.com"
|
||||
port = 443
|
||||
|
||||
# Get SSL certificate info
|
||||
context = ssl.create_default_context()
|
||||
with socket.create_connection((hostname, port)) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
|
||||
cert = ssock.getpeercert()
|
||||
|
||||
# Parse expiration date
|
||||
expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z')
|
||||
days_until_expiry = (expiry_date - datetime.now()).days
|
||||
|
||||
# Return results in operation output
|
||||
response.set_output(rsp, {
|
||||
"hostname": hostname,
|
||||
"certificateExpires": cert['notAfter'],
|
||||
"daysUntilExpiry": days_until_expiry,
|
||||
"status": "warning" if days_until_expiry < 30 else "ok"
|
||||
})
|
||||
```
|
||||
|
||||
<!-- vale Crossplane.Spelling = NO -->
|
||||
**The Operation runs once to completion, like a Kubernetes Job.**
|
||||
<!-- vale Crossplane.Spelling = YES -->
|
||||
|
||||
When you create the Operation, Crossplane runs the function pipeline. The
|
||||
function checks SSL certificate expiry for google.com and returns the results
|
||||
in the operation's output.
|
||||
|
||||
This basic example shows the concept. In the walkthrough below, you create
|
||||
a more realistic Operation that reads Kubernetes Ingress resources and
|
||||
annotates them with certificate expiry information for monitoring tools.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This guide requires:
|
||||
|
||||
* A Kubernetes cluster with at least 2 GB of RAM
|
||||
* The Crossplane v2 preview [installed on the Kubernetes cluster]({{<ref "install">}}) with Operations enabled
|
||||
|
||||
{{<hint "tip">}}
|
||||
Enable Operations by adding `--enable-operations` to Crossplane's startup
|
||||
arguments. If using Helm:
|
||||
|
||||
```shell
|
||||
helm upgrade --install crossplane crossplane-stable/crossplane \
|
||||
--namespace crossplane-system \
|
||||
--set args='{"--enable-operations"}'
|
||||
```
|
||||
{{</hint>}}
|
||||
|
||||
## Create an operation
|
||||
|
||||
Follow these steps to create your first Operation:
|
||||
|
||||
1. [Create a sample Ingress](#create-a-sample-ingress) for certificate checking
|
||||
1. [Install the function](#install-the-function) you want to use for the
|
||||
operation
|
||||
1. [Create the Operation](#create-the-operation) that checks the Ingress
|
||||
1. [Check the Operation](#check-the-operation) as it runs
|
||||
|
||||
### Create a sample Ingress
|
||||
|
||||
Create an Ingress that references a real hostname but doesn't route actual
|
||||
traffic:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: example-app
|
||||
namespace: default
|
||||
spec:
|
||||
rules:
|
||||
- host: google.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: nonexistent-service
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
Save as `ingress.yaml` and apply it:
|
||||
|
||||
```shell
|
||||
kubectl apply -f ingress.yaml
|
||||
```
|
||||
|
||||
### Grant Ingress permissions
|
||||
|
||||
Operations need permission to access and change Ingresses. Create a ClusterRole
|
||||
that grants Crossplane access to Ingresses:
|
||||
|
||||
```yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: operations-ingress-access
|
||||
labels:
|
||||
rbac.crossplane.io/aggregate-to-crossplane: "true"
|
||||
rules:
|
||||
- apiGroups: ["networking.k8s.io"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get", "list", "watch", "patch", "update"]
|
||||
```
|
||||
|
||||
Save as `ingress-rbac.yaml` and apply it:
|
||||
|
||||
```shell
|
||||
kubectl apply -f ingress-rbac.yaml
|
||||
```
|
||||
|
||||
### Install the function
|
||||
|
||||
Operations use operation functions to implement their logic. Use the Python
|
||||
function, which supports both composition and operations.
|
||||
|
||||
Create this function to install Python support:
|
||||
|
||||
```yaml
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: crossplane-contrib-function-python
|
||||
spec:
|
||||
package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0
|
||||
```
|
||||
|
||||
Save the function as `function.yaml` and apply it:
|
||||
|
||||
```shell
|
||||
kubectl apply -f function.yaml
|
||||
```
|
||||
|
||||
Check that Crossplane installed the function:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get -f function.yaml
|
||||
NAME INSTALLED HEALTHY PACKAGE AGE
|
||||
crossplane-contrib-function-python True True xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 12s
|
||||
```
|
||||
|
||||
### Create the operation
|
||||
|
||||
Create this Operation that monitors the Ingress certificate:
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: Operation
|
||||
metadata:
|
||||
name: ingress-cert-monitor
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: check-ingress-certificate
|
||||
functionRef:
|
||||
name: crossplane-contrib-function-python
|
||||
requirements:
|
||||
requiredResources:
|
||||
- requirementName: ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
name: example-app
|
||||
namespace: default
|
||||
input:
|
||||
apiVersion: python.fn.crossplane.io/v1beta1
|
||||
kind: Script
|
||||
script: |
|
||||
import ssl
|
||||
import socket
|
||||
from datetime import datetime
|
||||
|
||||
from crossplane.function import request, response
|
||||
|
||||
def operate(req, rsp):
|
||||
# Get the Ingress resource
|
||||
ingress = request.get_required_resource(req, "ingress")
|
||||
if not ingress:
|
||||
response.set_output(rsp, {"error": "No ingress resource found"})
|
||||
return
|
||||
|
||||
# Extract hostname from Ingress rules
|
||||
hostname = ingress["spec"]["rules"][0]["host"]
|
||||
port = 443
|
||||
|
||||
# Get SSL certificate info
|
||||
context = ssl.create_default_context()
|
||||
with socket.create_connection((hostname, port)) as sock:
|
||||
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
|
||||
cert = ssock.getpeercert()
|
||||
|
||||
# Parse expiration date
|
||||
expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z')
|
||||
days_until_expiry = (expiry_date - datetime.now()).days
|
||||
|
||||
# Add warning if certificate expires soon
|
||||
if days_until_expiry < 30:
|
||||
response.warning(rsp, f"Certificate for {hostname} expires in {days_until_expiry} days")
|
||||
|
||||
# Annotate the Ingress with certificate expiry info
|
||||
rsp.desired.resources["ingress"].resource.update({
|
||||
"apiVersion": "networking.k8s.io/v1",
|
||||
"kind": "Ingress",
|
||||
"metadata": {
|
||||
"name": ingress["metadata"]["name"],
|
||||
"namespace": ingress["metadata"]["namespace"],
|
||||
"annotations": {
|
||||
"cert-monitor.crossplane.io/expires": cert['notAfter'],
|
||||
"cert-monitor.crossplane.io/days-until-expiry": str(days_until_expiry),
|
||||
"cert-monitor.crossplane.io/status": "warning" if days_until_expiry < 30 else "ok"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
# Return results in operation output for monitoring
|
||||
response.set_output(rsp, {
|
||||
"ingressName": ingress["metadata"]["name"],
|
||||
"hostname": hostname,
|
||||
"certificateExpires": cert['notAfter'],
|
||||
"daysUntilExpiry": days_until_expiry,
|
||||
"status": "warning" if days_until_expiry < 30 else "ok"
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
Save the operation as `operation.yaml` and apply it:
|
||||
|
||||
```shell
|
||||
kubectl apply -f operation.yaml
|
||||
```
|
||||
|
||||
### Check the operation
|
||||
|
||||
Check that the Operation runs successfully:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get -f operation.yaml
|
||||
NAME SYNCED SUCCEEDED AGE
|
||||
ingress-cert-monitor True True 15s
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
Operations show `SUCCEEDED=True` when they complete successfully.
|
||||
{{</hint>}}
|
||||
|
||||
Check the Operation's detailed status:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl describe operation ingress-cert-monitor
|
||||
# ... metadata ...
|
||||
Status:
|
||||
Conditions:
|
||||
Last Transition Time: 2024-01-15T10:30:15Z
|
||||
Reason: PipelineSuccess
|
||||
Status: True
|
||||
Type: Succeeded
|
||||
Last Transition Time: 2024-01-15T10:30:15Z
|
||||
Reason: ValidPipeline
|
||||
Status: True
|
||||
Type: ValidPipeline
|
||||
Pipeline:
|
||||
Output:
|
||||
Certificate Expires: Sep 29 08:34:02 2025 GMT
|
||||
Days Until Expiry: 54
|
||||
Hostname: google.com
|
||||
Ingress Name: example-app
|
||||
Status: ok
|
||||
Step: check-ingress-certificate
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
The `status.pipeline` field shows the output returned by each function step.
|
||||
Use this field for tracking what the operation accomplished.
|
||||
{{</hint>}}
|
||||
|
||||
Check that the Operation annotated the Ingress with certificate information:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get ingress example-app -o yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
cert-monitor.crossplane.io/days-until-expiry: "54"
|
||||
cert-monitor.crossplane.io/expires: Sep 29 08:34:02 2025 GMT
|
||||
cert-monitor.crossplane.io/status: ok
|
||||
name: example-app
|
||||
namespace: default
|
||||
spec:
|
||||
# ... ingress spec ...
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
This pattern shows how Operations can both read and change existing Kubernetes
|
||||
resources. The Operation annotated the Ingress with certificate expiry
|
||||
information that other tools can use for monitoring and alerting.
|
||||
{{</hint>}}
|
||||
|
||||
## Clean up
|
||||
|
||||
Delete the resources you created:
|
||||
|
||||
```shell
|
||||
kubectl delete -f operation.yaml
|
||||
kubectl delete -f ingress.yaml
|
||||
kubectl delete -f ingress-rbac.yaml
|
||||
kubectl delete -f function.yaml
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
Operations are powerful building blocks for operational workflows. Learn more
|
||||
about:
|
||||
|
||||
* [**Operation concepts**]({{<ref "../operations/operation">}}) - Core
|
||||
Operation features and best practices
|
||||
* [**CronOperation**]({{<ref "../operations/cronoperation">}}) - Schedule
|
||||
operations to run automatically
|
||||
* [**WatchOperation**]({{<ref "../operations/watchoperation">}}) - Trigger
|
||||
operations when resources change
|
||||
|
||||
Explore the complete [Operations documentation]({{<ref "../operations">}}) for
|
||||
advanced features and examples.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title: Operations
|
||||
weight: 52
|
||||
state: alpha
|
||||
alphaVersion: v2.0-preview
|
||||
description: Understand Crossplane's Operations feature
|
||||
---
|
||||
|
|
@ -0,0 +1,345 @@
|
|||
---
|
||||
title: CronOperation
|
||||
weight: 120
|
||||
state: alpha
|
||||
alphaVersion: 2.0
|
||||
description: CronOperations create Operations on a schedule for recurring tasks
|
||||
---
|
||||
|
||||
A `CronOperation` creates [Operations]({{<ref "operation">}}) on a schedule,
|
||||
like Kubernetes CronJobs. Use CronOperations for recurring operational tasks
|
||||
such as database backups, certificate rotation, or periodic maintenance.
|
||||
|
||||
<!-- vale Google.Headings = NO -->
|
||||
## How CronOperations work
|
||||
<!-- vale Google.Headings = YES -->
|
||||
|
||||
CronOperations contain a template for an Operation and create new Operations
|
||||
based on a cron schedule. Each scheduled run creates a new Operation that
|
||||
executes once to completion.
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: CronOperation
|
||||
metadata:
|
||||
name: daily-backup
|
||||
spec:
|
||||
schedule: "0 2 * * *" # Daily at 2 AM
|
||||
concurrencyPolicy: Forbid
|
||||
successfulHistoryLimit: 5
|
||||
failedHistoryLimit: 3
|
||||
operationTemplate:
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: backup
|
||||
functionRef:
|
||||
name: function-database-backup
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: DatabaseBackupInput
|
||||
retentionDays: 7
|
||||
```
|
||||
|
||||
{{<hint "important">}}
|
||||
CronOperations are an alpha feature. You must enable Operations by adding
|
||||
`--enable-operations` to Crossplane's arguments.
|
||||
{{</hint>}}
|
||||
|
||||
## Key features
|
||||
|
||||
- **Standard cron scheduling syntax** - Uses the same format as Kubernetes CronJobs
|
||||
- **Configurable concurrency policies** (Allow, Forbid, Replace)
|
||||
- **Automatic cleanup of old Operations** - Maintains history limits
|
||||
- **Tracks run history and running operations** - Provides visibility into scheduled runs
|
||||
|
||||
## Scheduling
|
||||
|
||||
CronOperations use standard cron syntax:
|
||||
|
||||
```
|
||||
┌───────────── minute (0 - 59)
|
||||
│ ┌───────────── hour (0 - 23)
|
||||
│ │ ┌───────────── day of the month (1 - 31)
|
||||
│ │ │ ┌───────────── month (1 - 12)
|
||||
│ │ │ │ ┌───────────── day of the week (0 - 6) (Sunday to Saturday)
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
* * * * *
|
||||
```
|
||||
|
||||
**Common schedule examples:**
|
||||
- `"0 2 * * *"` - Every day at 2:00 AM
|
||||
- `"0 0 * * 0"` - Every Sunday at midnight
|
||||
- `"0 0 1 * *"` - Every month on the first at midnight
|
||||
- `"*/15 * * * *"` - Every 15 minutes
|
||||
|
||||
## Concurrency policies
|
||||
|
||||
CronOperations support three concurrency policies:
|
||||
|
||||
- **Allow (default)**: Multiple Operations can run simultaneously. Use this
|
||||
when operations don't interfere with each other.
|
||||
- **Forbid**: New Operations don't start if previous ones are still running.
|
||||
Use this for operations that can't run concurrently.
|
||||
- **Replace**: New Operations stop running ones before starting. Use this
|
||||
when you always want the latest operation to run.
|
||||
|
||||
## History management
|
||||
|
||||
Control the number of completed Operations to keep:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
successfulHistoryLimit: 5 # Keep 5 successful operations
|
||||
failedHistoryLimit: 3 # Keep 3 failed operations for debugging
|
||||
```
|
||||
|
||||
This helps balance debugging capabilities with resource usage.
|
||||
|
||||
## Common use cases
|
||||
|
||||
{{<hint "note">}}
|
||||
The following examples use hypothetical functions for illustration. At launch,
|
||||
only function-python supports operations.
|
||||
{{</hint>}}
|
||||
|
||||
### Scheduled database backups
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: CronOperation
|
||||
metadata:
|
||||
name: postgres-backup
|
||||
spec:
|
||||
schedule: "0 3 * * *" # Daily at 3 AM
|
||||
concurrencyPolicy: Forbid # Don't allow overlapping backups
|
||||
operationTemplate:
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: backup
|
||||
functionRef:
|
||||
name: function-postgres-backup
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: PostgresBackupInput
|
||||
instance: production-db
|
||||
s3Bucket: db-backups
|
||||
```
|
||||
|
||||
### Scheduled maintenance
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: CronOperation
|
||||
metadata:
|
||||
name: weekly-maintenance
|
||||
spec:
|
||||
schedule: "0 3 * * 0" # Weekly on Sunday at 3 AM
|
||||
operationTemplate:
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: cleanup-logs
|
||||
functionRef:
|
||||
name: function-log-cleanup
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: LogCleanupInput
|
||||
retentionDays: 30
|
||||
- step: update-certificates
|
||||
functionRef:
|
||||
name: function-cert-renewal
|
||||
```
|
||||
|
||||
### Periodic health checks
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: CronOperation
|
||||
metadata:
|
||||
name: health-check
|
||||
spec:
|
||||
schedule: "*/30 * * * *" # Every 30 minutes
|
||||
operationTemplate:
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: check-cluster-health
|
||||
functionRef:
|
||||
name: function-health-check
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: HealthCheckInput
|
||||
alertThreshold: 80
|
||||
```
|
||||
|
||||
## Advanced configuration
|
||||
|
||||
### Complex scheduling patterns
|
||||
|
||||
Advanced cron schedule examples for specific use cases:
|
||||
|
||||
```yaml
|
||||
# Weekdays only at 9 AM (Monday-Friday)
|
||||
schedule: "0 9 * * 1-5"
|
||||
|
||||
# Every 4 hours during business days
|
||||
schedule: "0 8,12,16 * * 1-5"
|
||||
|
||||
# First and last day of each month
|
||||
schedule: "0 2 1,L * *"
|
||||
|
||||
# Every quarter (1st of Jan, Apr, Jul, Oct)
|
||||
schedule: "0 2 1 1,4,7,10 *"
|
||||
|
||||
# Business hours only, every 2 hours
|
||||
schedule: "0 9-17/2 * * 1-5"
|
||||
```
|
||||
|
||||
### Starting deadline
|
||||
|
||||
CronOperations support a `startingDeadlineSeconds` field that controls how
|
||||
long to wait after the scheduled time before considering it too late to
|
||||
create the Operation:
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: CronOperation
|
||||
metadata:
|
||||
name: deadline-example
|
||||
spec:
|
||||
schedule: "0 9 * * 1-5" # Weekdays at 9 AM
|
||||
startingDeadlineSeconds: 900 # 15 minutes
|
||||
operationTemplate:
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: morning-tasks
|
||||
functionRef:
|
||||
name: function-morning-tasks
|
||||
```
|
||||
|
||||
If the Operation can't start in 15 minutes of 9 AM (due to
|
||||
controller downtime, resource constraints, etc.), the scheduled run is
|
||||
skipped.
|
||||
|
||||
Skip operations for:
|
||||
- **Time-sensitive operations** - Skip operations that become meaningless if delayed
|
||||
- **Resource protection** - Prevent backup Operations piling up during outages
|
||||
- **SLA compliance** - Ensure operations run in acceptable time windows
|
||||
|
||||
### Time zone considerations
|
||||
|
||||
{{<hint "important">}}
|
||||
CronOperations use the cluster's local time zone, same as Kubernetes CronJobs.
|
||||
To ensure consistent scheduling across different environments, consider:
|
||||
|
||||
1. **Standardize cluster time zones** - Use UTC in production clusters
|
||||
2. **Document time zone assumptions** - Note expected time zone in comments
|
||||
3. **Account for DST changes** - Be aware that some schedules may skip or repeat during transitions
|
||||
{{</hint>}}
|
||||
|
||||
## Status and monitoring
|
||||
|
||||
CronOperations provide status information about scheduling:
|
||||
|
||||
```yaml
|
||||
status:
|
||||
conditions:
|
||||
- type: Synced
|
||||
status: "True"
|
||||
reason: ReconcileSuccess
|
||||
- type: Scheduling
|
||||
status: "True"
|
||||
reason: ScheduleActive
|
||||
lastScheduleTime: "2024-01-15T10:00:00Z"
|
||||
lastSuccessfulTime: "2024-01-15T10:02:30Z"
|
||||
runningOperationRefs:
|
||||
- name: daily-backup-1705305600
|
||||
```
|
||||
|
||||
**Key status fields:**
|
||||
- **Conditions**: Standard Crossplane conditions (Synced) and CronOperation-specific conditions:
|
||||
- **Scheduling**: `True` when the CronOperation is actively scheduling operations, `False` when paused or has incorrect schedule syntax
|
||||
- **`lastScheduleTime`**: When the CronOperation last created an Operation
|
||||
- **`lastSuccessfulTime`**: When an Operation last completed successfully
|
||||
- **`runningOperationRefs`**: Running Operations
|
||||
|
||||
### Events
|
||||
|
||||
CronOperations emit events for important activities:
|
||||
- `CreateOperation` (Warning) - Scheduled operation creation failures
|
||||
- `GarbageCollectOperations` (Warning) - Garbage collection failures
|
||||
- `ReplaceRunningOperation` (Warning) - Running operation deletion failures
|
||||
- `InvalidSchedule` (Warning) - Cron schedule parsing errors
|
||||
|
||||
<!-- vale write-good.TooWordy = NO -->
|
||||
### Monitoring
|
||||
<!-- vale write-good.TooWordy = YES -->
|
||||
|
||||
<!-- vale write-good.TooWordy = NO -->
|
||||
Monitor CronOperations using:
|
||||
<!-- vale write-good.TooWordy = YES -->
|
||||
|
||||
```shell
|
||||
# Check CronOperation status
|
||||
kubectl get cronoperation my-cronop
|
||||
|
||||
# View recent Operations created by the CronOperation
|
||||
kubectl get operations -l crossplane.io/cronoperation=my-cronop
|
||||
|
||||
# Check events
|
||||
kubectl get events --field-selector involvedObject.name=my-cronop
|
||||
```
|
||||
|
||||
## Best practices
|
||||
|
||||
### Scheduling considerations
|
||||
|
||||
1. **Consider time zones** - CronOperations use the host's local time
|
||||
(same as Kubernetes CronJobs)
|
||||
1. **Plan for long-running operations** - Ensure operations complete before
|
||||
next scheduled run
|
||||
1. **Set reasonable history limits** - Balance debugging needs with cluster
|
||||
resource usage
|
||||
|
||||
### Concurrency policies
|
||||
|
||||
1. **Choose appropriate concurrency policies**:
|
||||
- **Forbid** for backups, maintenance, or operations that must complete
|
||||
alone
|
||||
- **Replace** for health checks or monitoring where latest data is most
|
||||
important
|
||||
- **Allow** for independent tasks that can run simultaneously
|
||||
|
||||
For general Operations best practices including function development and
|
||||
operational considerations, see [Operation best practices]({{<ref "operation#best-practices">}}).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<!-- vale Google.Headings = NO -->
|
||||
### CronOperation not creating Operations
|
||||
<!-- vale Google.Headings = YES -->
|
||||
|
||||
1. Check the cron schedule syntax
|
||||
1. Verify the CronOperation has `Synced=True` condition
|
||||
1. Look for events indicating schedule parsing errors
|
||||
|
||||
### Operations failing often
|
||||
|
||||
1. Check Operation events and logs
|
||||
1. Verify function capabilities include `operation`
|
||||
1. Review retry limits and adjust as needed
|
||||
|
||||
### Resource cleanup issues
|
||||
|
||||
1. Verify you set history limits appropriately
|
||||
1. Check for events about garbage collection failures
|
||||
|
||||
## Next steps
|
||||
|
||||
- Learn about [Operation]({{<ref "operation">}}) for one-time operational tasks
|
||||
- Learn about [WatchOperation]({{<ref "watchoperation">}}) for reactive operations
|
||||
- [Get started with Operations]({{<ref "../get-started/get-started-with-operations">}}) to try scheduling your first operation
|
||||
|
|
@ -0,0 +1,500 @@
|
|||
---
|
||||
title: Operation
|
||||
weight: 110
|
||||
state: alpha
|
||||
alphaVersion: 2.0
|
||||
description: Operations run function pipelines once to completion for operational tasks
|
||||
---
|
||||
|
||||
An `Operation` runs a function pipeline once to completion to perform operational
|
||||
tasks that don't fit the typical resource creation pattern. Unlike compositions
|
||||
that continuously reconcile desired state, Operations focus on tasks like
|
||||
backups, rolling upgrades, configuration validation, and scheduled maintenance.
|
||||
|
||||
## How operations work
|
||||
|
||||
Operations are like Kubernetes Jobs - they run once to completion rather than
|
||||
continuously reconciling. Like compositions, Operations use function pipelines
|
||||
to implement their logic, but they're designed for operational workflows
|
||||
instead of resource composition.
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: Operation
|
||||
metadata:
|
||||
name: backup-database
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: create-backup
|
||||
functionRef:
|
||||
name: function-database-backup
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: DatabaseBackupInput
|
||||
database: production-db
|
||||
retentionDays: 30
|
||||
```
|
||||
|
||||
When you create this Operation, Crossplane:
|
||||
|
||||
1. **Validates** the operation and its function dependencies
|
||||
2. **Executes** the function pipeline step by step
|
||||
3. **Applies** any resources the functions create or change
|
||||
4. **Updates** the Operation status with results and completion state
|
||||
|
||||
{{<hint "important">}}
|
||||
Operations are an alpha feature. You must enable them by adding
|
||||
`--enable-operations` to Crossplane's arguments.
|
||||
{{</hint>}}
|
||||
|
||||
## Key characteristics
|
||||
|
||||
- **Runs once to completion** (like Kubernetes Jobs)
|
||||
- **Uses function pipelines** (like Compositions)
|
||||
- **Can create or change any Kubernetes resources**
|
||||
- **Provides detailed status and output from each step**
|
||||
- **Supports retry on failure with configurable limits**
|
||||
|
||||
## Operation functions vs composition functions
|
||||
|
||||
Operations and compositions both use function pipelines, but with important
|
||||
differences:
|
||||
|
||||
**Composition Functions:**
|
||||
- **Purpose**: Create and maintain resources
|
||||
- **Lifecycle**: Continuous reconciliation
|
||||
- **Input**: Observed composite resources
|
||||
- **Output**: Desired composed resources
|
||||
- **Ownership**: Creates owner references
|
||||
|
||||
**Operation Functions:**
|
||||
- **Purpose**: Perform operational tasks
|
||||
- **Lifecycle**: Run once to completion
|
||||
- **Input**: Required resources only
|
||||
- **Output**: Any Kubernetes resources
|
||||
- **Ownership**: Force applies without owners
|
||||
|
||||
Functions can support both modes by declaring the appropriate capabilities in
|
||||
their package metadata. Function authors declare this in the `crossplane.yaml`
|
||||
file when building the function package:
|
||||
|
||||
```yaml
|
||||
apiVersion: meta.pkg.crossplane.io/v1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: my-function
|
||||
spec:
|
||||
capabilities:
|
||||
- composition
|
||||
- operation
|
||||
```
|
||||
|
||||
This allows Crossplane to know which modes the function supports and avoid
|
||||
trying to use a composition-only function for operations.
|
||||
|
||||
## Common use cases
|
||||
|
||||
{{<hint "note">}}
|
||||
The following examples use hypothetical functions for illustration. At launch,
|
||||
only function-python supports operations.
|
||||
{{</hint>}}
|
||||
|
||||
### Rolling upgrades
|
||||
|
||||
Use Operations for controlled rolling upgrades:
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: Operation
|
||||
metadata:
|
||||
name: cluster-upgrade
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: rolling-upgrade
|
||||
functionRef:
|
||||
name: function-cluster-upgrade
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: ClusterUpgradeInput
|
||||
targetVersion: "1.28"
|
||||
batches: [0.25, 0.5, 1.0] # 25%, 50%, then 100%
|
||||
healthChecks: [Synced, Ready]
|
||||
```
|
||||
|
||||
### One-time maintenance
|
||||
|
||||
Use Operations for specific maintenance tasks:
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: Operation
|
||||
metadata:
|
||||
name: certificate-rotation
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: rotate-certificates
|
||||
functionRef:
|
||||
name: function-cert-rotation
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: CertRotationInput
|
||||
targetCertificates:
|
||||
matchLabels:
|
||||
rotate: "true"
|
||||
```
|
||||
|
||||
## Advanced configuration
|
||||
|
||||
### Retry behavior
|
||||
|
||||
Operations automatically retry when they fail. Configure the retry limit to
|
||||
control how often attempts occur:
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: Operation
|
||||
metadata:
|
||||
name: resilient-operation
|
||||
spec:
|
||||
retryLimit: 10 # Try up to 10 times before giving up (default: 5)
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: flaky-task
|
||||
functionRef:
|
||||
name: function-flaky-task
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: FlakyTaskInput
|
||||
# Task that might fail due to temporary issues
|
||||
timeout: "30s"
|
||||
```
|
||||
|
||||
**Retry behavior:**
|
||||
- Each retry resets the entire pipeline - if step 2 of 3 fails, the retry
|
||||
starts from step 1
|
||||
- Operations use exponential backoff: 1 s, 2 s, 4 s, 8 s, 16 s, 32 s, then 60 s
|
||||
max
|
||||
- Operations track the number of failures in `status.failures`
|
||||
- After reaching `retryLimit`, the Operation becomes
|
||||
`Succeeded=False`
|
||||
|
||||
### Credentials
|
||||
|
||||
Operations can provide credentials to functions through Secrets:
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: Operation
|
||||
metadata:
|
||||
name: secure-backup
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: backup-with-credentials
|
||||
functionRef:
|
||||
name: function-backup
|
||||
credentials:
|
||||
- name: backup-creds
|
||||
source: Secret
|
||||
secretRef:
|
||||
namespace: crossplane-system
|
||||
name: backup-credentials
|
||||
key: api-key
|
||||
- name: database-creds
|
||||
source: Secret
|
||||
secretRef:
|
||||
namespace: crossplane-system
|
||||
name: database-credentials
|
||||
key: connection-string
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: BackupInput
|
||||
destination: s3://my-backup-bucket
|
||||
```
|
||||
|
||||
### Multiple pipeline steps
|
||||
|
||||
Complex operations can use multiple pipeline steps:
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: Operation
|
||||
metadata:
|
||||
name: multi-step-deployment
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: validate-config
|
||||
functionRef:
|
||||
name: function-validator
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: ValidatorInput
|
||||
configName: app-config
|
||||
- step: backup-current
|
||||
functionRef:
|
||||
name: function-backup
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: BackupInput
|
||||
target: current-deployment
|
||||
- step: deploy-new-version
|
||||
functionRef:
|
||||
name: function-deploy
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: DeployInput
|
||||
image: myapp:v2.0.0
|
||||
strategy: rollingUpdate
|
||||
- step: verify-health
|
||||
functionRef:
|
||||
name: function-health-check
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: HealthCheckInput
|
||||
timeout: 300s
|
||||
healthEndpoint: /health
|
||||
```
|
||||
|
||||
### RBAC permissions
|
||||
|
||||
If your Operation needs to access resources that Crossplane doesn't have
|
||||
permissions for by default, create a ClusterRole that aggregates to
|
||||
Crossplane:
|
||||
|
||||
```yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: operation-additional-permissions
|
||||
labels:
|
||||
rbac.crossplane.io/aggregate-to-crossplane: "true"
|
||||
rules:
|
||||
# Additional permissions for Operations
|
||||
- apiGroups: ["networking.k8s.io"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get", "list", "patch", "update"]
|
||||
- apiGroups: [""]
|
||||
resources: ["persistentvolumes"]
|
||||
verbs: ["get", "list"]
|
||||
# Add other resources your Operations need to access
|
||||
```
|
||||
|
||||
This ClusterRole is automatically aggregated to Crossplane's main
|
||||
ClusterRole, giving the Crossplane service account the permissions needed
|
||||
for your Operations.
|
||||
|
||||
{{<hint "note">}}
|
||||
The [RBAC manager]({{<ref "../guides/pods#rbac-manager-pod">}}) automatically
|
||||
grants Crossplane access to Crossplane resources (MRs, XRs, etc.). You
|
||||
only need to create more ClusterRoles for other Kubernetes resources
|
||||
that your Operations need to access.
|
||||
|
||||
For more details on RBAC configuration, see the
|
||||
[Compositions RBAC documentation]({{<ref "../composition/compositions#grant-access-to-composed-resources">}}).
|
||||
{{</hint>}}
|
||||
|
||||
### Required resources
|
||||
|
||||
Operations can preload resources for functions to access:
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: Operation
|
||||
metadata:
|
||||
name: resource-aware-operation
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: process-deployment
|
||||
functionRef:
|
||||
name: function-processor
|
||||
requirements:
|
||||
requiredResources:
|
||||
- requirementName: app-deployment
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: my-app
|
||||
namespace: production
|
||||
- requirementName: app-service
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
name: my-app-service
|
||||
namespace: production
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: ProcessorInput
|
||||
action: upgrade
|
||||
```
|
||||
|
||||
Functions access these resources through the standard request structure:
|
||||
|
||||
```python
|
||||
from crossplane.function import request, response
|
||||
|
||||
def operate(req, rsp):
|
||||
# Access required resources
|
||||
deployment = request.get_required_resource(req, "app-deployment")
|
||||
service = request.get_required_resource(req, "app-service")
|
||||
|
||||
if not deployment or not service:
|
||||
response.set_output(rsp, {"error": "Required resources not found"})
|
||||
return
|
||||
|
||||
# Process the resources
|
||||
new_replicas = deployment["spec"]["replicas"] * 2
|
||||
|
||||
# Return updated resources with full GVK and metadata for server-side apply
|
||||
rsp.desired.resources["app-deployment"].resource.update({
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {
|
||||
"name": deployment["metadata"]["name"],
|
||||
"namespace": deployment["metadata"]["namespace"]
|
||||
},
|
||||
"spec": {"replicas": new_replicas}
|
||||
})
|
||||
```
|
||||
|
||||
## Status and monitoring
|
||||
|
||||
Operations provide rich status information:
|
||||
|
||||
```yaml
|
||||
status:
|
||||
conditions:
|
||||
- type: Synced
|
||||
status: "True"
|
||||
reason: ReconcileSuccess
|
||||
- type: Succeeded
|
||||
status: "True"
|
||||
reason: PipelineSuccess
|
||||
- type: ValidPipeline
|
||||
status: "True"
|
||||
reason: ValidPipeline
|
||||
failures: 1 # Number of retry attempts
|
||||
pipeline:
|
||||
- step: create-backup
|
||||
output:
|
||||
backupId: "backup-20240115-103000"
|
||||
size: "2.3GB"
|
||||
appliedResourceRefs:
|
||||
- apiVersion: "v1"
|
||||
kind: "Secret"
|
||||
namespace: "production"
|
||||
name: "backup-secret"
|
||||
- apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
name: "updated-deployment"
|
||||
```
|
||||
|
||||
**Key status fields:**
|
||||
- **Conditions**: Standard Crossplane conditions (Synced) and Operation-specific conditions:
|
||||
- **Succeeded**: `True` when the operation completed successfully, `False` when it failed
|
||||
- **ValidPipeline**: `True` when all functions have the required `operation` capability
|
||||
- **Failures**: Number of times the operation has failed and retried
|
||||
- **Pipeline**: Output from each function step for tracking progress
|
||||
- **`AppliedResourceRefs`**: References to all resources the Operation created or modified
|
||||
|
||||
### Events
|
||||
|
||||
Operations emit Kubernetes events for important activities:
|
||||
- Function run results and warnings
|
||||
- Resource apply failures
|
||||
- Operation lifecycle events (creation, completion, failure)
|
||||
|
||||
### Troubleshooting operations
|
||||
|
||||
**Select operation status:**
|
||||
|
||||
```shell
|
||||
kubectl get operation my-operation -o wide
|
||||
kubectl describe operation my-operation
|
||||
```
|
||||
|
||||
**Common failure scenarios:**
|
||||
|
||||
1. **ValidPipeline condition is False** - Function doesn't support operations:
|
||||
```yaml
|
||||
conditions:
|
||||
- type: ValidPipeline
|
||||
status: "False"
|
||||
reason: InvalidFunctionCapability
|
||||
message: "Function function-name doesn't support operations"
|
||||
```
|
||||
*Solution*: use a function that declares `operation` capability.
|
||||
|
||||
2. **Succeeded condition is False** - Function run failed:
|
||||
```yaml
|
||||
conditions:
|
||||
- type: Succeeded
|
||||
status: "False"
|
||||
reason: PipelineFailure
|
||||
message: "Function returned error: connection timeout"
|
||||
```
|
||||
*Solution*: view function logs and fix the underlying issue.
|
||||
|
||||
3. **Resource apply failures** - View events for details:
|
||||
```shell
|
||||
kubectl get events --field-selector involvedObject.name=my-operation
|
||||
```
|
||||
|
||||
**Debug function runs:**
|
||||
|
||||
```shell
|
||||
# View function logs
|
||||
kubectl logs -n crossplane-system deployment/function-python
|
||||
|
||||
# Check operation events
|
||||
kubectl get events --field-selector involvedObject.kind=Operation
|
||||
|
||||
# Inspect operation status in detail
|
||||
kubectl get operation my-operation -o jsonpath='{.status.pipeline}' | jq '.'
|
||||
```
|
||||
|
||||
## Resource management
|
||||
|
||||
Operations can create or change any Kubernetes resources using server-side
|
||||
apply with force ownership. This means:
|
||||
|
||||
**What Operations can do:**
|
||||
- Create new resources of any kind
|
||||
- Change existing resources by taking ownership of specific fields
|
||||
- Apply changes that may conflict with other controllers
|
||||
|
||||
**What Operations can't do:**
|
||||
- Delete resources (current limitation of alpha implementation)
|
||||
- Establish owner references (resources aren't garbage collected)
|
||||
- Continuously maintain desired state (they run once)
|
||||
|
||||
{{<hint "important">}}
|
||||
Use caution with Operations that change resources managed by other controllers.
|
||||
Operations force ownership when applying changes, which can cause conflicts.
|
||||
{{</hint>}}
|
||||
|
||||
## Best practices
|
||||
|
||||
### Operation-specific practices
|
||||
|
||||
1. **Plan for rollback** - Design operations to be reversible when possible,
|
||||
because Operations don't auto rollback like Compositions
|
||||
1. **Make operations idempotent** - Operations should be safe to retry if they
|
||||
fail partway through
|
||||
1. **Use required resources** - Prepopulate functions with needed resources for
|
||||
efficiency rather than requesting them during running
|
||||
|
||||
### Function development
|
||||
|
||||
1. **Declare capabilities** - Explicitly declare `operation` capability in
|
||||
function metadata to enable Operations support
|
||||
1. **Return meaningful output** - Use the output field to track what the
|
||||
operation accomplished for monitoring and debugging
|
||||
|
||||
## Next steps
|
||||
|
||||
- [Get started with Operations]({{<ref "../get-started/get-started-with-operations">}}) to create your first Operation
|
||||
- Learn about [CronOperation]({{<ref "cronoperation">}}) for scheduled operations
|
||||
- Learn about [WatchOperation]({{<ref "watchoperation">}}) for reactive operations
|
||||
|
|
@ -0,0 +1,567 @@
|
|||
---
|
||||
title: WatchOperation
|
||||
weight: 130
|
||||
state: alpha
|
||||
alphaVersion: 2.0
|
||||
description: WatchOperations create Operations when watched resources change
|
||||
---
|
||||
|
||||
A `WatchOperation` creates [Operations]({{<ref "operation">}}) when watched
|
||||
Kubernetes resources change. Use WatchOperations for reactive operational
|
||||
workflows such as backing up databases before deletion, validating
|
||||
configurations after updates, or triggering alerts when resources fail.
|
||||
|
||||
<!-- vale Google.Headings = NO -->
|
||||
## How WatchOperations work
|
||||
<!-- vale Google.Headings = YES -->
|
||||
|
||||
WatchOperations watch specific Kubernetes resources and create new Operations
|
||||
whenever those resources change. The changed resource is automatically injected
|
||||
into the Operation for the function to process.
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: WatchOperation
|
||||
metadata:
|
||||
name: config-validator
|
||||
spec:
|
||||
watch:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
matchLabels:
|
||||
validate: "true"
|
||||
concurrencyPolicy: Allow
|
||||
operationTemplate:
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: validate
|
||||
functionRef:
|
||||
name: function-config-validator
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: ConfigValidatorInput
|
||||
rules:
|
||||
- required: ["database.url", "database.port"]
|
||||
- format: "email"
|
||||
field: "notification.email"
|
||||
- step: notify
|
||||
functionRef:
|
||||
name: function-slack-notifier
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: SlackNotifierInput
|
||||
channel: "#alerts"
|
||||
severity: "warning"
|
||||
```
|
||||
|
||||
{{<hint "important">}}
|
||||
WatchOperations are an alpha feature. You must enable Operations by adding
|
||||
`--enable-operations` to Crossplane's arguments.
|
||||
{{</hint>}}
|
||||
|
||||
## Key features
|
||||
|
||||
- **Watches any Kubernetes resource type** - Not limited to Crossplane resources
|
||||
- **Supports namespace and label filtering** - Target specific resources
|
||||
- **Automatically injects changed resources** - Functions receive the triggering resource
|
||||
- **Configurable concurrency policies** - Control operation creation
|
||||
|
||||
## Resource watching
|
||||
|
||||
WatchOperations can watch any Kubernetes resource with flexible filtering:
|
||||
|
||||
### Watch all resources of a type
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
watch:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
```
|
||||
|
||||
### Watch resources in a specific namespace
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
watch:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
namespace: production
|
||||
```
|
||||
|
||||
### Watch resources with specific labels
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
watch:
|
||||
apiVersion: example.org/v1
|
||||
kind: Database
|
||||
matchLabels:
|
||||
backup: "enabled"
|
||||
environment: "production"
|
||||
```
|
||||
|
||||
### Watch cluster-scoped resources
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
watch:
|
||||
apiVersion: v1
|
||||
kind: Node
|
||||
matchLabels:
|
||||
node-role.kubernetes.io/worker: ""
|
||||
```
|
||||
|
||||
## Resource injection
|
||||
|
||||
When a WatchOperation creates an Operation, it automatically injects the
|
||||
changed
|
||||
<!-- vale write-good.TooWordy = NO -->
|
||||
resource using the special requirement name
|
||||
`ops.crossplane.io/watched-resource`.
|
||||
<!-- vale write-good.TooWordy = YES -->
|
||||
Functions can access this resource without explicitly requesting it.
|
||||
|
||||
For example, when a ConfigMap with label `validate: "true"` changes, the
|
||||
WatchOperation creates an Operation like this:
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: Operation
|
||||
metadata:
|
||||
name: config-validator-abc123
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: validate
|
||||
functionRef:
|
||||
name: function-config-validator
|
||||
requirements:
|
||||
requiredResources:
|
||||
- requirementName: ops.crossplane.io/watched-resource
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
name: my-config
|
||||
namespace: default
|
||||
# ... other pipeline steps from operationTemplate
|
||||
```
|
||||
|
||||
The watched resource is automatically available to functions in
|
||||
`req.required_resources` under the special name
|
||||
`ops.crossplane.io/watched-resource`.
|
||||
|
||||
## Concurrency policies
|
||||
|
||||
WatchOperations support the same concurrency policies as CronOperations:
|
||||
|
||||
- **Allow (default)**: Multiple Operations can run simultaneously. Use this
|
||||
when operations don't interfere with each other.
|
||||
- **Forbid**: New Operations don't start if previous ones are still running.
|
||||
Use this for operations that can't run concurrently.
|
||||
- **Replace**: New Operations stop running ones before starting. Use this
|
||||
when you always want the latest operation to run.
|
||||
|
||||
## Common use cases
|
||||
|
||||
{{<hint "note">}}
|
||||
The following examples use hypothetical functions for illustration. At launch,
|
||||
only function-python supports operations.
|
||||
{{</hint>}}
|
||||
|
||||
### Configuration validation
|
||||
|
||||
Validate ConfigMaps when they change:
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: WatchOperation
|
||||
metadata:
|
||||
name: config-validator
|
||||
spec:
|
||||
watch:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
matchLabels:
|
||||
validate: "true"
|
||||
operationTemplate:
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: validate-config
|
||||
functionRef:
|
||||
name: function-config-validator
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: ConfigValidatorInput
|
||||
rules:
|
||||
- required: ["database.host", "database.port"]
|
||||
- format: "email"
|
||||
field: "notification.email"
|
||||
```
|
||||
|
||||
### Database backup on deletion
|
||||
|
||||
Backup databases before they're deleted:
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: WatchOperation
|
||||
metadata:
|
||||
name: backup-on-deletion
|
||||
spec:
|
||||
watch:
|
||||
apiVersion: rds.aws.crossplane.io/v1alpha1
|
||||
kind: Instance
|
||||
# Note: Watching for deletion requires function logic
|
||||
# to check deletion timestamp
|
||||
operationTemplate:
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: create-backup
|
||||
functionRef:
|
||||
name: function-rds-backup
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: RDSBackupInput
|
||||
retentionDays: 30
|
||||
```
|
||||
|
||||
### Resource failure alerting
|
||||
|
||||
Alert when resources enter a failed state:
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: WatchOperation
|
||||
metadata:
|
||||
name: failure-alerts
|
||||
spec:
|
||||
watch:
|
||||
apiVersion: example.org/v1
|
||||
kind: App
|
||||
matchLabels:
|
||||
alert: "enabled"
|
||||
operationTemplate:
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: check-status
|
||||
functionRef:
|
||||
name: function-status-checker
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: StatusCheckerInput
|
||||
alertConditions:
|
||||
- type: "Ready"
|
||||
status: "False"
|
||||
- step: send-alert
|
||||
functionRef:
|
||||
name: function-alertmanager
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: AlertInput
|
||||
severity: "critical"
|
||||
```
|
||||
|
||||
## Advanced configuration
|
||||
|
||||
### Advanced watch patterns
|
||||
|
||||
Complex resource watching with multiple conditions:
|
||||
|
||||
```yaml
|
||||
# Watch Deployments in specific namespaces with multiple label conditions
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: WatchOperation
|
||||
metadata:
|
||||
name: multi-condition-watcher
|
||||
spec:
|
||||
watch:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
namespace: production # Only production namespace
|
||||
matchLabels:
|
||||
app.kubernetes.io/managed-by: "crossplane"
|
||||
environment: "prod"
|
||||
backup-required: "true"
|
||||
operationTemplate:
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: backup-deployment
|
||||
functionRef:
|
||||
name: function-deployment-backup
|
||||
```
|
||||
|
||||
```yaml
|
||||
# Watch custom resources across all namespaces
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: WatchOperation
|
||||
metadata:
|
||||
name: database-lifecycle-manager
|
||||
spec:
|
||||
watch:
|
||||
apiVersion: database.example.io/v1
|
||||
kind: PostgreSQLInstance
|
||||
# No namespace specified = watch all namespaces
|
||||
matchLabels:
|
||||
lifecycle-management: "enabled"
|
||||
operationTemplate:
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: lifecycle-check
|
||||
functionRef:
|
||||
name: function-database-lifecycle
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: DatabaseLifecycleInput
|
||||
checkDeletionTimestamp: true
|
||||
autoBackup: true
|
||||
```
|
||||
|
||||
|
||||
### Cross-resource workflows
|
||||
|
||||
WatchOperations can watch one resource type and dynamically fetch related
|
||||
resources. Here's a WatchOperation that watches Ingresses and manages
|
||||
certificates:
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: WatchOperation
|
||||
metadata:
|
||||
name: ingress-certificate-manager
|
||||
spec:
|
||||
watch:
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
matchLabels:
|
||||
auto-cert: "enabled"
|
||||
operationTemplate:
|
||||
spec:
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: manage-certificates
|
||||
functionRef:
|
||||
name: function-cert-manager
|
||||
input:
|
||||
apiVersion: fn.crossplane.io/v1beta1
|
||||
kind: CertManagerInput
|
||||
issuer: "letsencrypt-prod"
|
||||
renewBefore: "720h" # 30 days
|
||||
```
|
||||
|
||||
The function examines the watched Ingress and dynamically requests related resources:
|
||||
|
||||
```python
|
||||
from crossplane.function import request, response
|
||||
|
||||
def operate(req, rsp):
|
||||
# Access the watched Ingress resource
|
||||
ingress = request.get_required_resource(req, "ops.crossplane.io/watched-resource")
|
||||
if not ingress:
|
||||
response.set_output(rsp, {"error": "No watched resource found"})
|
||||
return
|
||||
|
||||
# Extract the service name from the Ingress backend
|
||||
rules = ingress.get("spec", {}).get("rules", [])
|
||||
if not rules:
|
||||
return
|
||||
|
||||
backend = rules[0].get("http", {}).get("paths", [{}])[0].get("backend", {})
|
||||
service_name = backend.get("service", {}).get("name")
|
||||
if not service_name:
|
||||
return
|
||||
|
||||
ingress_namespace = ingress.get("metadata", {}).get("namespace", "default")
|
||||
|
||||
# Always declare what resources we need (requirements must be stable across calls)
|
||||
rsp.requirements.resources["related-service"].api_version = "v1"
|
||||
rsp.requirements.resources["related-service"].kind = "Service"
|
||||
rsp.requirements.resources["related-service"].match_name = service_name
|
||||
rsp.requirements.resources["related-service"].namespace = ingress_namespace
|
||||
|
||||
# Process the service if Crossplane fetched it after our previous response
|
||||
service = request.get_required_resource(req, "related-service")
|
||||
if service:
|
||||
create_certificate_for_service(ingress, service, rsp)
|
||||
```
|
||||
|
||||
This pattern allows functions to:
|
||||
1. Examine the watched resource
|
||||
2. Dynamically determine what other resources you need
|
||||
3. Request those resources in the next response
|
||||
4. Process the more resources in next calls
|
||||
|
||||
## Status and monitoring
|
||||
|
||||
WatchOperations provide status information about watching:
|
||||
|
||||
```yaml
|
||||
status:
|
||||
conditions:
|
||||
- type: Synced
|
||||
status: "True"
|
||||
reason: ReconcileSuccess
|
||||
- type: Watching
|
||||
status: "True"
|
||||
reason: WatchActive
|
||||
watchingResources: 12
|
||||
runningOperationRefs:
|
||||
- name: config-validator-anjda
|
||||
- name: config-validator-f0d92
|
||||
```
|
||||
|
||||
**Key status fields:**
|
||||
- **Conditions**: Standard Crossplane conditions (Synced) and WatchOperation-specific conditions:
|
||||
- **Watching**: `True` when the WatchOperation is actively watching resources, `False` when paused or failed
|
||||
- **`watchingResources`**: Number of resources under watch
|
||||
- **`runningOperationRefs`**: Running Operations created by this WatchOperation
|
||||
|
||||
### Events
|
||||
|
||||
WatchOperations emit events for important activities:
|
||||
- `EstablishWatched` (Warning) - Watch establishment failures
|
||||
- `TerminateWatched` (Warning) - Watch termination failures
|
||||
- `GarbageCollectOperations` (Warning) - Operation cleanup failures
|
||||
- `CreateOperation` (Warning) - Operation creation failures
|
||||
- `ReplaceRunningOperation` (Warning) - Operation replacement failures
|
||||
|
||||
<!-- vale write-good.TooWordy = NO -->
|
||||
### Monitoring
|
||||
<!-- vale write-good.TooWordy = YES -->
|
||||
|
||||
<!-- vale write-good.TooWordy = NO -->
|
||||
Monitor WatchOperations using:
|
||||
<!-- vale write-good.TooWordy = YES -->
|
||||
|
||||
```shell
|
||||
# Check WatchOperation status
|
||||
kubectl get watchoperation my-watchop
|
||||
|
||||
# View recent Operations created by the WatchOperation
|
||||
kubectl get operations -l crossplane.io/watchoperation=my-watchop
|
||||
|
||||
# Check watched resource count
|
||||
kubectl describe watchoperation my-watchop
|
||||
|
||||
# Check events
|
||||
kubectl get events --field-selector involvedObject.name=my-watchop
|
||||
```
|
||||
|
||||
|
||||
## Best practices
|
||||
|
||||
### Resource selection
|
||||
|
||||
1. **Use specific label selectors** - Prevent unnecessary Operations with
|
||||
precise filtering
|
||||
1. **Avoid high-churn resources** - Be careful watching frequently changing
|
||||
resources
|
||||
1. **Start small** - Begin with narrow selectors and expand as needed
|
||||
|
||||
### Event handling
|
||||
|
||||
1. **Implement event filtering** - Check generation, deletion timestamp,
|
||||
and status conditions
|
||||
to avoid processing irrelevant changes
|
||||
<!-- vale write-good.TooWordy = NO -->
|
||||
1. **Monitor operation volume** - Popular resources can create numerous
|
||||
Operations
|
||||
<!-- vale write-good.TooWordy = YES -->
|
||||
|
||||
### Concurrency policies
|
||||
|
||||
1. **Choose appropriate concurrency policies**:
|
||||
- **Allow** for independent processing that can run in parallel
|
||||
- **Forbid** for operations that must complete before processing new
|
||||
changes
|
||||
- **Replace** for status-checking or monitoring where only latest state
|
||||
matters
|
||||
|
||||
### History management
|
||||
|
||||
Like CronOperations, WatchOperations automatically clean up completed Operations:
|
||||
|
||||
```yaml
|
||||
apiVersion: ops.crossplane.io/v1alpha1
|
||||
kind: WatchOperation
|
||||
metadata:
|
||||
name: config-validator
|
||||
spec:
|
||||
watch:
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
successfulHistoryLimit: 10 # Keep 10 successful Operations (default: 3)
|
||||
failedHistoryLimit: 5 # Keep 5 failed Operations (default: 1)
|
||||
operationTemplate:
|
||||
# Operation template here
|
||||
```
|
||||
|
||||
### Watched resource injection
|
||||
|
||||
<!-- vale write-good.TooWordy = NO -->
|
||||
WatchOperations automatically inject the changed resource into the created
|
||||
Operation using a special requirement name
|
||||
`ops.crossplane.io/watched-resource`:
|
||||
<!-- vale write-good.TooWordy = YES -->
|
||||
|
||||
```python
|
||||
from crossplane.function import request, response
|
||||
|
||||
def operate(req, rsp):
|
||||
# Access the resource that triggered this Operation
|
||||
watched_resource = request.get_required_resource(req, "ops.crossplane.io/watched-resource")
|
||||
if not watched_resource:
|
||||
response.set_output(rsp, {"error": "No watched resource found"})
|
||||
return
|
||||
|
||||
# Process based on the watched resource
|
||||
if watched_resource["kind"] == "ConfigMap":
|
||||
config_data = watched_resource["data"]
|
||||
# Validate configuration...
|
||||
```
|
||||
|
||||
The watched resource is available in the function's `required_resources` map
|
||||
without needing to declare it in the Operation template.
|
||||
|
||||
For general Operations best practices including function development and
|
||||
operational considerations, see [Operation best practices]({{<ref "operation#best-practices">}}).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<!-- vale Google.Headings = NO -->
|
||||
### WatchOperation not creating Operations
|
||||
<!-- vale Google.Headings = YES -->
|
||||
|
||||
1. Verify the WatchOperation has `Watching=True` condition
|
||||
1. Check that watched resources exist and match the selector
|
||||
1. Ensure resources are actually changing
|
||||
1. Look for events indicating watch establishment failures
|
||||
|
||||
<!-- vale Google.Headings = NO -->
|
||||
<!-- vale write-good.Weasel = NO -->
|
||||
### Too many Operations created
|
||||
<!-- vale write-good.Weasel = YES -->
|
||||
<!-- vale Google.Headings = YES -->
|
||||
|
||||
1. Refine label selectors to match fewer resources
|
||||
1. Consider using `Forbid` or `Replace` concurrency policy
|
||||
1. Check if resources are changing more frequently than expected
|
||||
1. Review function logic to ensure it's not causing resource updates
|
||||
|
||||
### Operations failing to process watched resources
|
||||
|
||||
1. Verify function capabilities include `operation`
|
||||
1. Check that functions handle the `ops.crossplane.io/watched-resource`
|
||||
1. Review function logs for processing errors
|
||||
1. Ensure functions can handle the specific resource types under watch
|
||||
|
||||
## Next steps
|
||||
|
||||
- Learn about [Operation]({{<ref "operation">}}) for one-time operational tasks
|
||||
- Learn about [CronOperation]({{<ref "cronoperation">}}) for scheduled operations
|
||||
- [Get started with Operations]({{<ref "../get-started/get-started-with-operations">}}) to create your first reactive operation
|
||||
|
|
@ -20,7 +20,10 @@ ClusterRole
|
|||
ClusterRoles
|
||||
command-line
|
||||
ConfigMap
|
||||
ConfigMaps
|
||||
CRD
|
||||
cron
|
||||
CronJobs
|
||||
crt
|
||||
CSS
|
||||
CUE
|
||||
|
|
@ -39,6 +42,7 @@ float64
|
|||
GitOps
|
||||
Go
|
||||
gRPC
|
||||
hostname
|
||||
IAM
|
||||
imagePullSecret
|
||||
init.sh
|
||||
|
|
@ -64,6 +68,7 @@ OIDC
|
|||
PersistentVolumeClaim
|
||||
Pre-releases
|
||||
pre-releases
|
||||
Prepopulate
|
||||
PriorityClass
|
||||
proselint
|
||||
protobuf
|
||||
|
|
@ -98,6 +103,7 @@ Substrings
|
|||
syscall
|
||||
templated
|
||||
TLS
|
||||
walkthrough
|
||||
tolerations
|
||||
UI
|
||||
VM
|
||||
|
|
|
|||
|
|
@ -13,12 +13,17 @@ CompositeResourceDefinition
|
|||
CompositeResourceDefinitions
|
||||
CompositionRevision
|
||||
CompositionRevisions
|
||||
composition-only
|
||||
config
|
||||
Configs
|
||||
CONTRIBUTING.md
|
||||
ControllerConfig
|
||||
ControllerConfigs
|
||||
CRDs
|
||||
CronJobs
|
||||
CronOperation
|
||||
CronOperation-specific
|
||||
CronOperations
|
||||
CRs
|
||||
Crossplane
|
||||
crossplane-admin
|
||||
|
|
@ -45,6 +50,7 @@ function-environment-configs
|
|||
function-extra-resources
|
||||
function-go-templating
|
||||
function-patch-and-transform
|
||||
function-python
|
||||
function-template-python
|
||||
HealthyPackageRevision
|
||||
Helm-like
|
||||
|
|
@ -63,9 +69,11 @@ ProviderConfig
|
|||
ProviderConfigs
|
||||
ProviderRevision
|
||||
RunFunctionRequest
|
||||
Operation-specific
|
||||
RunFunctionResponse
|
||||
Sigstore
|
||||
StoreConfig
|
||||
SSL
|
||||
StoreConfigs
|
||||
ToCompositeFieldPath
|
||||
ToEnvironmentFieldPath
|
||||
|
|
@ -74,6 +82,10 @@ TrimPrefix
|
|||
TrimSuffix
|
||||
UnhealthyPackageRevision
|
||||
UnknownPackageRevisionHealth
|
||||
ValidPipeline
|
||||
WatchOperation
|
||||
WatchOperation-specific
|
||||
WatchOperations
|
||||
XCluster
|
||||
XNetwork
|
||||
xpkg
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
backporting
|
||||
built-in
|
||||
call-outs
|
||||
google.com
|
||||
ClusterRoles`
|
||||
comma-separated
|
||||
conformant
|
||||
|
|
@ -10,6 +11,7 @@ cross-reference
|
|||
Cross-resource
|
||||
cross-resource
|
||||
datastore
|
||||
day-two
|
||||
double-check
|
||||
double-checks
|
||||
dry-run
|
||||
|
|
@ -23,12 +25,16 @@ how-to
|
|||
in-depth
|
||||
in-memory
|
||||
left-hand
|
||||
Long-running
|
||||
low-traffic
|
||||
multi-cluster
|
||||
multi-region
|
||||
multi-tenant
|
||||
multi-tenancy
|
||||
non-empty
|
||||
non-Kubernetes
|
||||
one-time
|
||||
One-time
|
||||
per-element
|
||||
per-object
|
||||
per-resource
|
||||
|
|
@ -57,4 +63,25 @@ user-defined
|
|||
v2
|
||||
version-specific
|
||||
Job.
|
||||
e.g.
|
||||
high-churn
|
||||
idempotency
|
||||
least-privilege
|
||||
long-running
|
||||
Multi-step
|
||||
namespace-scoped
|
||||
non-production
|
||||
preload
|
||||
event-driven
|
||||
hardcode
|
||||
low-risk
|
||||
Operation-level
|
||||
performant
|
||||
resource-intensive
|
||||
status-checking
|
||||
System-level
|
||||
Time-sensitive
|
||||
user-provided
|
||||
validators
|
||||
webhook-based
|
||||
backporting
|
||||
Loading…
Reference in New Issue