Skip to content

Commit 4c36f90

Browse files
committed
First pass at Operations docs
Signed-off-by: Nic Cope <[email protected]>
1 parent bb454e8 commit 4c36f90

File tree

9 files changed

+1833
-0
lines changed

9 files changed

+1833
-0
lines changed

content/master/_index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Crossplane organizes its documentation into the following sections:
2323

2424
* [Composition]({{<ref "composition">}}) covers the key concepts of composition.
2525

26+
* [Operations]({{<ref "operations">}}) covers the key concepts of operations.
27+
2628
* [Managed Resources]({{<ref "managed-resources">}}) covers the key concepts of
2729
managed resources.
2830

Lines changed: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,367 @@
1+
---
2+
title: Get Started With Operations
3+
weight: 300
4+
state: alpha
5+
alphaVersion: 2.0
6+
---
7+
8+
This guide shows how to use Crossplane Operations to automate day-two
9+
operational tasks. You create an Operation that checks SSL certificate
10+
expiry for a website.
11+
12+
**Crossplane calls this _Operations_.** Operations run function pipelines to
13+
perform tasks that don't fit the typical resource creation pattern - like
14+
certificate monitoring, rolling upgrades, or scheduled maintenance.
15+
16+
An Operation looks like this:
17+
18+
```yaml
19+
apiVersion: ops.crossplane.io/v1alpha1
20+
kind: Operation
21+
metadata:
22+
name: check-cert-expiry
23+
spec:
24+
mode: Pipeline
25+
pipeline:
26+
- step: check-certificate
27+
functionRef:
28+
name: crossplane-contrib-function-python
29+
input:
30+
apiVersion: python.fn.crossplane.io/v1beta1
31+
kind: Script
32+
script: |
33+
import ssl
34+
import socket
35+
from datetime import datetime
36+
37+
from crossplane.function import request, response
38+
39+
def operate(req, rsp):
40+
hostname = "google.com"
41+
port = 443
42+
43+
# Get SSL certificate info
44+
context = ssl.create_default_context()
45+
with socket.create_connection((hostname, port)) as sock:
46+
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
47+
cert = ssock.getpeercert()
48+
49+
# Parse expiration date
50+
expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z')
51+
days_until_expiry = (expiry_date - datetime.now()).days
52+
53+
# Return results in operation output
54+
response.set_output(rsp, {
55+
"hostname": hostname,
56+
"certificateExpires": cert['notAfter'],
57+
"daysUntilExpiry": days_until_expiry,
58+
"status": "warning" if days_until_expiry < 30 else "ok"
59+
})
60+
```
61+
62+
<!-- vale Crossplane.Spelling = NO -->
63+
**The Operation runs once to completion, like a Kubernetes Job.**
64+
<!-- vale Crossplane.Spelling = YES -->
65+
66+
When you create the Operation, Crossplane runs the function pipeline. The
67+
function checks SSL certificate expiry for google.com and returns the results
68+
in the operation's output.
69+
70+
This basic example shows the concept. In the walkthrough below, you create
71+
a more realistic Operation that reads Kubernetes Ingress resources and
72+
annotates them with certificate expiry information for monitoring tools.
73+
74+
## Prerequisites
75+
76+
This guide requires:
77+
78+
* A Kubernetes cluster with at least 2 GB of RAM
79+
* The Crossplane v2 preview [installed on the Kubernetes cluster]({{<ref "install">}}) with Operations enabled
80+
81+
{{<hint "tip">}}
82+
Enable Operations by adding `--enable-operations` to Crossplane's startup
83+
arguments. If using Helm:
84+
85+
```shell
86+
helm upgrade --install crossplane crossplane-stable/crossplane \
87+
--namespace crossplane-system \
88+
--set args='{"--enable-operations"}'
89+
```
90+
{{</hint>}}
91+
92+
## Create an operation
93+
94+
Follow these steps to create your first Operation:
95+
96+
1. [Create a sample Ingress](#create-a-sample-ingress) for certificate checking
97+
1. [Install the function](#install-the-function) you want to use for the
98+
operation
99+
1. [Create the Operation](#create-the-operation) that checks the Ingress
100+
1. [Check the Operation](#check-the-operation) as it runs
101+
102+
### Create a sample Ingress
103+
104+
Create an Ingress that references a real hostname but doesn't route actual
105+
traffic:
106+
107+
```yaml
108+
apiVersion: networking.k8s.io/v1
109+
kind: Ingress
110+
metadata:
111+
name: example-app
112+
namespace: default
113+
spec:
114+
rules:
115+
- host: google.com
116+
http:
117+
paths:
118+
- path: /
119+
pathType: Prefix
120+
backend:
121+
service:
122+
name: nonexistent-service
123+
port:
124+
number: 80
125+
```
126+
127+
Save as `ingress.yaml` and apply it:
128+
129+
```shell
130+
kubectl apply -f ingress.yaml
131+
```
132+
133+
### Grant Ingress permissions
134+
135+
Operations need permission to access and change Ingresses. Create a ClusterRole
136+
that grants Crossplane access to Ingresses:
137+
138+
```yaml
139+
apiVersion: rbac.authorization.k8s.io/v1
140+
kind: ClusterRole
141+
metadata:
142+
name: operations-ingress-access
143+
labels:
144+
rbac.crossplane.io/aggregate-to-crossplane: "true"
145+
rules:
146+
- apiGroups: ["networking.k8s.io"]
147+
resources: ["ingresses"]
148+
verbs: ["get", "list", "watch", "patch", "update"]
149+
```
150+
151+
Save as `ingress-rbac.yaml` and apply it:
152+
153+
```shell
154+
kubectl apply -f ingress-rbac.yaml
155+
```
156+
157+
### Install the function
158+
159+
Operations use operation functions to implement their logic. Use the Python
160+
function, which supports both composition and operations.
161+
162+
Create this function to install Python support:
163+
164+
```yaml
165+
apiVersion: pkg.crossplane.io/v1
166+
kind: Function
167+
metadata:
168+
name: crossplane-contrib-function-python
169+
spec:
170+
package: xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0
171+
```
172+
173+
Save the function as `function.yaml` and apply it:
174+
175+
```shell
176+
kubectl apply -f function.yaml
177+
```
178+
179+
Check that Crossplane installed the function:
180+
181+
```shell {copy-lines="1"}
182+
kubectl get -f function.yaml
183+
NAME INSTALLED HEALTHY PACKAGE AGE
184+
crossplane-contrib-function-python True True xpkg.crossplane.io/crossplane-contrib/function-python:v0.2.0 12s
185+
```
186+
187+
### Create the operation
188+
189+
Create this Operation that monitors the Ingress certificate:
190+
191+
```yaml
192+
apiVersion: ops.crossplane.io/v1alpha1
193+
kind: Operation
194+
metadata:
195+
name: ingress-cert-monitor
196+
spec:
197+
mode: Pipeline
198+
pipeline:
199+
- step: check-ingress-certificate
200+
functionRef:
201+
name: crossplane-contrib-function-python
202+
requirements:
203+
requiredResources:
204+
- requirementName: ingress
205+
apiVersion: networking.k8s.io/v1
206+
kind: Ingress
207+
name: example-app
208+
namespace: default
209+
input:
210+
apiVersion: python.fn.crossplane.io/v1beta1
211+
kind: Script
212+
script: |
213+
import ssl
214+
import socket
215+
from datetime import datetime
216+
217+
from crossplane.function import request, response
218+
219+
def operate(req, rsp):
220+
# Get the Ingress resource
221+
ingress = request.get_required_resource(req, "ingress")
222+
if not ingress:
223+
response.set_output(rsp, {"error": "No ingress resource found"})
224+
return
225+
226+
# Extract hostname from Ingress rules
227+
hostname = ingress["spec"]["rules"][0]["host"]
228+
port = 443
229+
230+
# Get SSL certificate info
231+
context = ssl.create_default_context()
232+
with socket.create_connection((hostname, port)) as sock:
233+
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
234+
cert = ssock.getpeercert()
235+
236+
# Parse expiration date
237+
expiry_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z')
238+
days_until_expiry = (expiry_date - datetime.now()).days
239+
240+
# Add warning if certificate expires soon
241+
if days_until_expiry < 30:
242+
response.warning(rsp, f"Certificate for {hostname} expires in {days_until_expiry} days")
243+
244+
# Annotate the Ingress with certificate expiry info
245+
rsp.desired.resources["ingress"].resource.update({
246+
"apiVersion": "networking.k8s.io/v1",
247+
"kind": "Ingress",
248+
"metadata": {
249+
"name": ingress["metadata"]["name"],
250+
"namespace": ingress["metadata"]["namespace"],
251+
"annotations": {
252+
"cert-monitor.crossplane.io/expires": cert['notAfter'],
253+
"cert-monitor.crossplane.io/days-until-expiry": str(days_until_expiry),
254+
"cert-monitor.crossplane.io/status": "warning" if days_until_expiry < 30 else "ok"
255+
}
256+
}
257+
})
258+
259+
# Return results in operation output for monitoring
260+
response.set_output(rsp, {
261+
"ingressName": ingress["metadata"]["name"],
262+
"hostname": hostname,
263+
"certificateExpires": cert['notAfter'],
264+
"daysUntilExpiry": days_until_expiry,
265+
"status": "warning" if days_until_expiry < 30 else "ok"
266+
})
267+
```
268+
269+
270+
Save the operation as `operation.yaml` and apply it:
271+
272+
```shell
273+
kubectl apply -f operation.yaml
274+
```
275+
276+
### Check the operation
277+
278+
Check that the Operation runs successfully:
279+
280+
```shell {copy-lines="1"}
281+
kubectl get -f operation.yaml
282+
NAME SYNCED SUCCEEDED AGE
283+
ingress-cert-monitor True True 15s
284+
```
285+
286+
{{<hint "tip">}}
287+
Operations show `SUCCEEDED=True` when they complete successfully.
288+
{{</hint>}}
289+
290+
Check the Operation's detailed status:
291+
292+
```shell {copy-lines="1"}
293+
kubectl describe operation ingress-cert-monitor
294+
# ... metadata ...
295+
Status:
296+
Conditions:
297+
Last Transition Time: 2024-01-15T10:30:15Z
298+
Reason: PipelineSuccess
299+
Status: True
300+
Type: Succeeded
301+
Last Transition Time: 2024-01-15T10:30:15Z
302+
Reason: ValidPipeline
303+
Status: True
304+
Type: ValidPipeline
305+
Pipeline:
306+
Output:
307+
Certificate Expires: Sep 29 08:34:02 2025 GMT
308+
Days Until Expiry: 54
309+
Hostname: google.com
310+
Ingress Name: example-app
311+
Status: ok
312+
Step: check-ingress-certificate
313+
```
314+
315+
{{<hint "tip">}}
316+
The `status.pipeline` field shows the output returned by each function step.
317+
Use this field for tracking what the operation accomplished.
318+
{{</hint>}}
319+
320+
Check that the Operation annotated the Ingress with certificate information:
321+
322+
```shell {copy-lines="1"}
323+
kubectl get ingress example-app -o yaml
324+
apiVersion: networking.k8s.io/v1
325+
kind: Ingress
326+
metadata:
327+
annotations:
328+
cert-monitor.crossplane.io/days-until-expiry: "54"
329+
cert-monitor.crossplane.io/expires: Sep 29 08:34:02 2025 GMT
330+
cert-monitor.crossplane.io/status: ok
331+
name: example-app
332+
namespace: default
333+
spec:
334+
# ... ingress spec ...
335+
```
336+
337+
{{<hint "tip">}}
338+
This pattern shows how Operations can both read and change existing Kubernetes
339+
resources. The Operation annotated the Ingress with certificate expiry
340+
information that other tools can use for monitoring and alerting.
341+
{{</hint>}}
342+
343+
## Clean up
344+
345+
Delete the resources you created:
346+
347+
```shell
348+
kubectl delete -f operation.yaml
349+
kubectl delete -f ingress.yaml
350+
kubectl delete -f ingress-rbac.yaml
351+
kubectl delete -f function.yaml
352+
```
353+
354+
## Next steps
355+
356+
Operations are powerful building blocks for operational workflows. Learn more
357+
about:
358+
359+
* [**Operation concepts**]({{<ref "../operations/operation">}}) - Core
360+
Operation features and best practices
361+
* [**CronOperation**]({{<ref "../operations/cronoperation">}}) - Schedule
362+
operations to run automatically
363+
* [**WatchOperation**]({{<ref "../operations/watchoperation">}}) - Trigger
364+
operations when resources change
365+
366+
Explore the complete [Operations documentation]({{<ref "../operations">}}) for
367+
advanced features and examples.

content/master/operations/_index.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: Operations
3+
weight: 52
4+
state: alpha
5+
alphaVersion: v2.0-preview
6+
description: Understand Crossplane's Operations feature
7+
---

0 commit comments

Comments
 (0)