mirror of https://github.com/crossplane/docs.git
369 lines
11 KiB
Markdown
369 lines
11 KiB
Markdown
---
|
|
title: Get Started With Operations
|
|
weight: 300
|
|
state: alpha
|
|
alphaVersion: 2.0
|
|
description: "Run operational tasks with Crossplane operations"
|
|
---
|
|
|
|
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.
|