Skip to content

Commit dabe420

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

File tree

9 files changed

+1806
-0
lines changed

9 files changed

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