mirror of https://github.com/crossplane/docs.git
Merge pull request #901 from negz/get-started-composition
[v2] Add a new 'Get Started With Composition' guide
This commit is contained in:
commit
017ee79d92
|
@ -3,4 +3,734 @@ title: Get Started With Composition
|
|||
weight: 200
|
||||
---
|
||||
|
||||
TODO
|
||||
This guide shows how to create a new kind of custom resource named `App`. When a
|
||||
user calls the custom resource API to create an `App`, Crossplane creates a
|
||||
`Deployment` and a `Service`.
|
||||
|
||||
**Crossplane calls this _composition_.** The `App` is _composed of_ the
|
||||
`Deployment` and the `Service`.
|
||||
|
||||
|
||||
{{<hint "tip">}}
|
||||
The guide shows how to configure composition using YAML, templated YAML, Python,
|
||||
and KCL. You can pick your preferred language.
|
||||
{{</hint>}}
|
||||
|
||||
An `App` custom resource looks like this:
|
||||
|
||||
```yaml
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: App
|
||||
metadata:
|
||||
namespace: default
|
||||
name: my-app
|
||||
spec:
|
||||
image: nginx
|
||||
status:
|
||||
replicas: 2 # Copied from the Deployment's status
|
||||
address: 10.0.0.1 # Copied from the Service's status
|
||||
```
|
||||
|
||||
**The `App` is the custom API Crossplane users use to configure an app.**
|
||||
|
||||
When users create an `App` Crossplane creates this `Deployment` and `Service`:
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
namespace: default
|
||||
name: my-app-dhj3a
|
||||
labels:
|
||||
example.crossplane.io/app: my-app # Copied from the App's name
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
example.crossplane.io/app: my-app # Copied from the App's name
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
example.crossplane.io/app: my-app # Copied from the App's name
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
image: nginx # Copied from the App's spec
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
namespace: default
|
||||
name: my-app-03mda
|
||||
labels:
|
||||
example.crossplane.io/app: my-app # Copied from the App's name
|
||||
spec:
|
||||
selector:
|
||||
example.crossplane.io/app: my-app # Copied from the App's name
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
targetPort: 80
|
||||
```
|
||||
|
||||
Crossplane builds on Kubernetes, so users can use `kubectl` or any other tool
|
||||
from the Kubernetes ecosystem to work with apps.
|
||||
|
||||
{{<hint "tip">}}
|
||||
Kubernetes custom resources are just JSON REST APIs, so users can use any tool
|
||||
that supports REST APIs to work with apps.
|
||||
{{</hint>}}
|
||||
|
||||
## 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">}})
|
||||
|
||||
## Create the custom resource
|
||||
|
||||
Follow these steps to create a new kind of custom resource using Crossplane:
|
||||
|
||||
1. [Define](#define-the-schema) the schema of the `App` custom resource
|
||||
1. [Install](#install-the-function) the function you want to use to configure
|
||||
how Crossplane composes apps
|
||||
1. [Configure](#configure-the-composition) how Crossplane composes apps
|
||||
|
||||
After you complete these steps you can
|
||||
[use the new `App` custom resource](#use-the-custom-resource).
|
||||
|
||||
### Define the schema
|
||||
|
||||
Crossplane calls a custom resource that's powered by composition a _composite
|
||||
resource_, or XR.
|
||||
|
||||
{{<hint "note">}}
|
||||
Kubernetes calls user-defined API resources _custom resources_.
|
||||
|
||||
Crossplane calls user-defined API resources that use composition _composite
|
||||
resources_.
|
||||
|
||||
A composite resource is a kind of custom resource.
|
||||
{{</hint>}}
|
||||
|
||||
Create this _composite resource definition_ (XRD) to define the schema of the
|
||||
new `App` composite resource (XR).
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v2alpha1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: apps.example.crossplane.io
|
||||
spec:
|
||||
scope: Namespaced
|
||||
group: example.crossplane.io
|
||||
names:
|
||||
kind: App
|
||||
plural: apps
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
referenceable: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
image:
|
||||
description: The app's OCI container image.
|
||||
type: string
|
||||
required:
|
||||
- image
|
||||
status:
|
||||
type: object
|
||||
properties:
|
||||
replicas:
|
||||
description: The number of available app replicas.
|
||||
type: integer
|
||||
address:
|
||||
description: The app's IP address.
|
||||
type: string
|
||||
```
|
||||
|
||||
Save the XRD as `xrd.yaml` and apply it:
|
||||
|
||||
```shell
|
||||
kubectl apply -f xrd.yaml
|
||||
```
|
||||
|
||||
Check that Crossplane has established the XRD:
|
||||
|
||||
``` shell {copy-lines="1"}
|
||||
kubectl get -f xrd.yaml
|
||||
NAME ESTABLISHED OFFERED AGE
|
||||
apps.example.crossplane.io True 21s
|
||||
```
|
||||
|
||||
Now that Crossplane has established the XRD, Kubernetes is serving API requests
|
||||
for the new `App` XR.
|
||||
|
||||
Crossplane now knows it's responsible for the new `App` XR, but it doesn't know
|
||||
what to do when you create or update one. You tell Crossplane what to do by
|
||||
[installing a function](#install-the-function) and
|
||||
[configuring a composition](#configure-the-composition).
|
||||
|
||||
### Install the function
|
||||
|
||||
You can use different _composition functions_ to configure what Crossplane does
|
||||
when someone creates or updates a composite resource (XR). Composition functions
|
||||
are like configuration language plugins.
|
||||
|
||||
Pick what language to use to configure how Crossplane turns an `App` XR into a
|
||||
`Deployment` and a `Service`.
|
||||
|
||||
{{< tabs >}}
|
||||
|
||||
{{< tab "YAML" >}}
|
||||
YAML is a good choice for small, static compositions. It doesn't support loops
|
||||
or conditionals.
|
||||
|
||||
Create this composition function to install YAML support:
|
||||
|
||||
```yaml
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: crossplane-contrib-function-patch-and-transform
|
||||
spec:
|
||||
package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2
|
||||
```
|
||||
|
||||
Save the function as `fn.yaml` and apply it:
|
||||
|
||||
```shell
|
||||
kubectl apply -f fn.yaml
|
||||
```
|
||||
|
||||
Check that Crossplane installed the function:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get -f fn.yaml
|
||||
NAME INSTALLED HEALTHY PACKAGE AGE
|
||||
crossplane-contrib-function-patch-and-transform True True xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.8.2 10s
|
||||
```
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab "Templated YAML" >}}
|
||||
Templated YAML is a good choice if you're used to writing
|
||||
[Helm charts](https://helm.sh).
|
||||
|
||||
Create this composition function to install templated YAML support:
|
||||
|
||||
```yaml
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: crossplane-contrib-function-go-templating
|
||||
spec:
|
||||
package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2
|
||||
```
|
||||
|
||||
Save the function as `fn.yaml` and apply it:
|
||||
|
||||
```shell
|
||||
kubectl apply -f fn.yaml
|
||||
```
|
||||
|
||||
Check that Crossplane installed the function:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get -f fn.yaml
|
||||
NAME INSTALLED HEALTHY PACKAGE AGE
|
||||
crossplane-contrib-function-go-templating True True xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.9.2 9s
|
||||
```
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab "Python" >}}
|
||||
Python is a good choice for compositions with dynamic logic. You can use the
|
||||
full [Python standard library](https://docs.python.org/3/library/index.html).
|
||||
|
||||
Create this composition 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.1.0
|
||||
```
|
||||
|
||||
Save the function as `fn.yaml` and apply it:
|
||||
|
||||
```shell
|
||||
kubectl apply -f fn.yaml
|
||||
```
|
||||
|
||||
Check that Crossplane installed the function:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get -f fn.yaml
|
||||
NAME INSTALLED HEALTHY PACKAGE AGE
|
||||
crossplane-contrib-function-python True True xpkg.crossplane.io/crossplane-contrib/function-python:v0.1.0 12s
|
||||
```
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab "KCL" >}}
|
||||
[KCL](https://kcl-lang.io) is a good choice for compositions with dynamic logic.
|
||||
It's fast and sandboxed.
|
||||
|
||||
Create this composition function to install KCL support:
|
||||
|
||||
```yaml
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: crossplane-contrib-function-kcl
|
||||
spec:
|
||||
package: xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2
|
||||
```
|
||||
|
||||
Save the function as `fn.yaml` and apply it:
|
||||
|
||||
```shell
|
||||
kubectl apply -f fn.yaml
|
||||
```
|
||||
|
||||
Check that Crossplane installed the function:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get -f fn.yaml
|
||||
NAME INSTALLED HEALTHY PACKAGE AGE
|
||||
crossplane-contrib-function-kcl True True xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.2 6s
|
||||
```
|
||||
{{< /tab >}}
|
||||
|
||||
{{</ tabs >}}
|
||||
|
||||
### Configure the composition
|
||||
|
||||
A composition tells Crossplane what functions to call when you create or
|
||||
update a composite resource (XR).
|
||||
|
||||
Create a composition to tell Crossplane what to do when you create or update an
|
||||
`App` XR.
|
||||
|
||||
{{< tabs >}}
|
||||
|
||||
{{< tab "YAML" >}}
|
||||
Create this composition to use YAML to configure Crossplane:
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: app-yaml
|
||||
spec:
|
||||
compositeTypeRef:
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: App
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: create-deployment-and-service
|
||||
functionRef:
|
||||
name: crossplane-contrib-function-patch-and-transform
|
||||
input:
|
||||
apiVersion: pt.fn.crossplane.io/v1beta1
|
||||
kind: Resources
|
||||
resources:
|
||||
- name: deployment
|
||||
base:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
replicas: 2
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
ports:
|
||||
- containerPort: 80
|
||||
patches:
|
||||
- type: FromCompositeFieldPath
|
||||
fromFieldPath: metadata.name
|
||||
toFieldPath: metadata.labels[example.crossplane.io/app]
|
||||
- type: FromCompositeFieldPath
|
||||
fromFieldPath: metadata.name
|
||||
toFieldPath: spec.selector.matchLabels[example.crossplane.io/app]
|
||||
- type: FromCompositeFieldPath
|
||||
fromFieldPath: metadata.name
|
||||
toFieldPath: spec.template.metadata.labels[example.crossplane.io/app]
|
||||
- type: FromCompositeFieldPath
|
||||
fromFieldPath: spec.image
|
||||
toFieldPath: spec.template.spec.containers[0].image
|
||||
- type: ToCompositeFieldPath
|
||||
fromFieldPath: status.availableReplicas
|
||||
toFieldPath: status.replicas
|
||||
readinessChecks:
|
||||
- type: MatchCondition
|
||||
matchCondition:
|
||||
type: Available
|
||||
status: "True"
|
||||
- name: service
|
||||
base:
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
spec:
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
targetPort: 80
|
||||
patches:
|
||||
- type: FromCompositeFieldPath
|
||||
fromFieldPath: metadata.name
|
||||
toFieldPath: metadata.labels[example.crossplane.io/app]
|
||||
- type: FromCompositeFieldPath
|
||||
fromFieldPath: metadata.name
|
||||
toFieldPath: spec.selector[example.crossplane.io/app]
|
||||
- type: ToCompositeFieldPath
|
||||
fromFieldPath: spec.clusterIP
|
||||
toFieldPath: status.address
|
||||
readinessChecks:
|
||||
- type: NonEmpty
|
||||
fieldPath: spec.clusterIP
|
||||
```
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab "Templated YAML" >}}
|
||||
Create this composition to use templated YAML to configure Crossplane:
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: app-templated-yaml
|
||||
spec:
|
||||
compositeTypeRef:
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: App
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: create-deployment-and-service
|
||||
functionRef:
|
||||
name: crossplane-contrib-function-go-templating
|
||||
input:
|
||||
apiVersion: gotemplating.fn.crossplane.io/v1beta1
|
||||
kind: GoTemplate
|
||||
source: Inline
|
||||
inline:
|
||||
template: |
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
gotemplating.fn.crossplane.io/composition-resource-name: deployment
|
||||
{{ if eq (.observed.resources.deployment | getResourceCondition "Available").Status "True" }}
|
||||
gotemplating.fn.crossplane.io/ready: "True"
|
||||
{{ end }}
|
||||
labels:
|
||||
example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }}
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }}
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
image: {{ .observed.composite.resource.spec.image }}
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations:
|
||||
gotemplating.fn.crossplane.io/composition-resource-name: service
|
||||
{{ if (get (getComposedResource . "service").spec "clusterIP") }}
|
||||
gotemplating.fn.crossplane.io/ready: "True"
|
||||
{{ end }}
|
||||
labels:
|
||||
example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }}
|
||||
spec:
|
||||
selector:
|
||||
example.crossplane.io/app: {{ .observed.composite.resource.metadata.name }}
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
targetPort: 80
|
||||
---
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: App
|
||||
status:
|
||||
replicas: {{ get (getComposedResource . "deployment").status "availableReplicas" | default 0 }}
|
||||
address: {{ get (getComposedResource . "service").spec "clusterIP" | default "" | quote }}
|
||||
```
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab "Python" >}}
|
||||
Create this composition to use Python to configure Crossplane:
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: app-python
|
||||
spec:
|
||||
compositeTypeRef:
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: App
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: create-deployment-and-service
|
||||
functionRef:
|
||||
name: crossplane-contrib-function-python
|
||||
input:
|
||||
apiVersion: python.fn.crossplane.io/v1beta1
|
||||
kind: Script
|
||||
script: |
|
||||
def compose(req, rsp):
|
||||
observed_xr = req.observed.composite.resource
|
||||
|
||||
rsp.desired.resources["deployment"].resource.update({
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {
|
||||
"labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]},
|
||||
},
|
||||
"spec": {
|
||||
"replicas": 2,
|
||||
"selector": {"matchLabels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]}},
|
||||
"template": {
|
||||
"metadata": {
|
||||
"labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]},
|
||||
},
|
||||
"spec": {
|
||||
"containers": [{
|
||||
"name": "app",
|
||||
"image": observed_xr["spec"]["image"],
|
||||
"ports": [{"containerPort": 80}]
|
||||
}],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
observed_deployment = req.observed.resources["deployment"].resource
|
||||
if "status" in observed_deployment:
|
||||
if "availableReplicas" in observed_deployment["status"]:
|
||||
rsp.desired.composite.resource.get_or_create_struct("status")["replicas"] = observed_deployment["status"]["availableReplicas"]
|
||||
if "conditions" in observed_deployment["status"]:
|
||||
for condition in observed_deployment["status"]["conditions"]:
|
||||
if condition["type"] == "Available" and condition["status"] == "True":
|
||||
rsp.desired.resources["deployment"].ready = True
|
||||
|
||||
rsp.desired.resources["service"].resource.update({
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": {
|
||||
"labels": {"example.crossplane.io/app": observed_xr["metadata"]["name"]},
|
||||
},
|
||||
"spec": {
|
||||
"selector": {"example.crossplane.io/app": observed_xr["metadata"]["name"]},
|
||||
"ports": [{"protocol": "TCP", "port": 8080, "targetPort": 80}],
|
||||
},
|
||||
})
|
||||
|
||||
observed_service = req.observed.resources["service"].resource
|
||||
if "spec" in observed_service and "clusterIP" in observed_service["spec"]:
|
||||
rsp.desired.composite.resource.get_or_create_struct("status")["address"] = observed_service["spec"]["clusterIP"]
|
||||
rsp.desired.resources["service"].ready = True
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
You can write your own function in Python.
|
||||
|
||||
It's a good idea to write your own function for larger configurations. When you
|
||||
write your own function you can write multiple files of Python. You don't embed
|
||||
the Python in YAML, so it's easier to use a Python IDE.
|
||||
|
||||
Read the [guide to writing a composition function in Python]({{<ref "../guides/write-a-composition-function-in-python">}}).
|
||||
{{</hint>}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab "KCL" >}}
|
||||
Create this composition to use KCL to configure Crossplane:
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: app-kcl
|
||||
spec:
|
||||
compositeTypeRef:
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: App
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: create-deployment-and-service
|
||||
functionRef:
|
||||
name: crossplane-contrib-function-kcl
|
||||
input:
|
||||
apiVersion: krm.kcl.dev/v1alpha1
|
||||
kind: KCLInput
|
||||
spec:
|
||||
source: |
|
||||
observed_xr = option("params").oxr
|
||||
|
||||
_desired_deployment = {
|
||||
apiVersion = "apps/v1"
|
||||
kind = "Deployment"
|
||||
metadata = {
|
||||
annotations = {
|
||||
"krm.kcl.dev/composition-resource-name" = "deployment"
|
||||
}
|
||||
labels = {"example.crossplane.io/app" = observed_xr.metadata.name}
|
||||
}
|
||||
spec = {
|
||||
replicas = 2
|
||||
selector.matchLabels = {"example.crossplane.io/app" = observed_xr.metadata.name}
|
||||
template = {
|
||||
metadata.labels = {"example.crossplane.io/app" = observed_xr.metadata.name}
|
||||
spec.containers = [{
|
||||
name = "app"
|
||||
image = observed_xr.spec.image
|
||||
ports = [{containerPort = 80}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
observed_deployment = option("params").ocds["deployment"]?.Resource
|
||||
if any_true([c.type == "Available" and c.status == "True" for c in observed_deployment?.status?.conditions or []]):
|
||||
_desired_deployment.metadata.annotations["krm.kcl.dev/ready"] = "True"
|
||||
|
||||
_desired_service = {
|
||||
apiVersion = "v1"
|
||||
kind = "Service"
|
||||
metadata = {
|
||||
annotations = {
|
||||
"krm.kcl.dev/composition-resource-name" = "service"
|
||||
}
|
||||
labels = {"example.crossplane.io/app" = observed_xr.metadata.name}
|
||||
}
|
||||
spec = {
|
||||
selector = {"example.crossplane.io/app" = observed_xr.metadata.name}
|
||||
ports = [{protocol = "TCP", port = 8080, targetPort = 80}]
|
||||
}
|
||||
}
|
||||
|
||||
observed_service = option("params").ocds["service"]?.Resource
|
||||
if observed_service?.spec?.clusterIP:
|
||||
_desired_service.metadata.annotations["krm.kcl.dev/ready"] = "True"
|
||||
|
||||
_desired_xr = {
|
||||
**option("params").dxr
|
||||
|
||||
status.address = observed_service?.spec?.clusterIP or ""
|
||||
status.replicas = observed_deployment?.status?.availableReplicas or 0
|
||||
}
|
||||
|
||||
items = [_desired_deployment, _desired_service, _desired_xr]
|
||||
```
|
||||
{{< /tab >}}
|
||||
|
||||
{{</ tabs >}}
|
||||
|
||||
Save the composition as `composition.yaml` and apply it:
|
||||
|
||||
```shell
|
||||
kubectl apply -f composition.yaml
|
||||
```
|
||||
|
||||
{{<hint "note">}}
|
||||
A composition can include multiple functions.
|
||||
|
||||
Functions can change the results of earlier functions in the pipeline.
|
||||
Crossplane uses the result returned by the last function.
|
||||
{{</hint>}}
|
||||
|
||||
## Use the custom resource
|
||||
|
||||
Crossplane now understands `App` custom resources.
|
||||
|
||||
Create an `App`:
|
||||
|
||||
```yaml
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: App
|
||||
metadata:
|
||||
namespace: default
|
||||
name: my-app
|
||||
spec:
|
||||
image: nginx
|
||||
```
|
||||
|
||||
Save the `App` as `app.yaml` and apply it:
|
||||
|
||||
```shell
|
||||
kubectl apply -f app.yaml
|
||||
```
|
||||
|
||||
Check that the `App` is ready:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get -f app.yaml
|
||||
NAME SYNCED READY COMPOSITION AGE
|
||||
my-app True True app-yaml 56s
|
||||
```
|
||||
|
||||
{{<hint "note">}}
|
||||
The `COMPOSITION` column shows what composition the `App` is using.
|
||||
|
||||
You can create multiple compositions for each kind of XR.
|
||||
[Read the XR page]({{<ref "../concepts/composite-resources">}}) to learn how to
|
||||
select which composition Crossplane uses.
|
||||
{{</hint>}}
|
||||
|
||||
Check that Crossplane created a `Deployment` and a `Service`:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get deploy,service -l example.crossplane.io/app=my-app
|
||||
NAME READY UP-TO-DATE AVAILABLE AGE
|
||||
deployment.apps/my-app-2r2rk 2/2 2 2 11m
|
||||
|
||||
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
|
||||
service/my-app-xfkzg ClusterIP 10.96.148.56 <none> 8080/TCP 11m
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
Use `kubectl edit -f app.yaml` to edit the `App`'s image. Crossplane updates
|
||||
the `Deployment`'s image to match.
|
||||
{{</hint>}}
|
||||
|
||||
Delete the `App`.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl delete -f app.yaml
|
||||
```
|
||||
|
||||
When you delete the `App`, Crossplane deletes the `Deployment` and `Service`.
|
||||
|
||||
## Next steps
|
||||
|
||||
Managed resources (MRs) are ready-made Kubernetes custom resources.
|
||||
|
||||
Crossplane has an extensive library of managed resources you can use to manage
|
||||
almost any cloud provider, or cloud native software.
|
||||
|
||||
[Get started with managed resources]({{<ref "get-started-with-managed-resources">}})
|
||||
to learn more about them.
|
||||
|
||||
You can use MRs with composition. Try updating your `App` composition to include
|
||||
an MR.
|
||||
|
|
|
@ -1,86 +1,109 @@
|
|||
---
|
||||
title: Get Started With Managed Resources
|
||||
weight: 200
|
||||
weight: 300
|
||||
---
|
||||
|
||||
Connect Crossplane to AWS to create and manage cloud resources from Kubernetes
|
||||
with [provider-upjet-aws](https://github.com/crossplane-contrib/provider-upjet-aws).
|
||||
This guide shows how to install and use a new kind of custom resource called
|
||||
`Bucket`. When a user calls the custom resource API to create a `Bucket`,
|
||||
Crossplane creates a bucket in AWS S3.
|
||||
|
||||
A _managed resource_ is anything Crossplane creates and manages outside of the
|
||||
control plane.
|
||||
**Crossplane calls this a _managed resource_**. A managed resource is a
|
||||
ready-made custom resource that manages something outside of the control plane.
|
||||
|
||||
This guide creates an AWS S3 bucket with Crossplane. The S3 bucket is a _managed resource_.
|
||||
A `Bucket` managed resource looks like this:
|
||||
|
||||
```yaml
|
||||
apiVersion: s3.aws.m.upbound.io/v1beta1
|
||||
kind: Bucket
|
||||
metadata:
|
||||
namespace: default
|
||||
name: crossplane-bucket-example
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-2
|
||||
```
|
||||
|
||||
{{<hint "note">}}
|
||||
Kubernetes calls third party API resources _custom resources_.
|
||||
{{</hint>}}
|
||||
|
||||
## Prerequisites
|
||||
This quickstart requires:
|
||||
|
||||
This guide requires:
|
||||
|
||||
* A Kubernetes cluster with at least 2 GB of RAM
|
||||
* The Crossplane v2 preview [installed on the Kubernetes cluster]({{<ref "install">}})
|
||||
* An AWS account with permissions to create an S3 storage bucket
|
||||
* AWS [access keys](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-creds)
|
||||
|
||||
## Install the AWS provider
|
||||
Install the AWS S3 provider into the Kubernetes cluster with a Kubernetes
|
||||
configuration file.
|
||||
## Install support for the managed resource
|
||||
|
||||
Follow these steps to install support for the `Bucket` managed resource:
|
||||
|
||||
1. [Install](#install-the-provider) the provider
|
||||
1. [Save](#save-the-providers-credentials) the provider's credentials as a secret
|
||||
1. [Configure](#configure-the-provider) the provider to use the secret
|
||||
|
||||
After you complete these steps you can
|
||||
[use the `Bucket` managed resource](#use-the-managed-resource).
|
||||
|
||||
### Install the provider
|
||||
|
||||
A Crossplane _provider_ installs support for a set of related managed resources.
|
||||
The AWS S3 provider installs support for all the AWS S3 managed resources.
|
||||
|
||||
Create this provider to install the AWS S3 provider:
|
||||
|
||||
```yaml {label="provider",copy-lines="all"}
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: provider-aws-s3
|
||||
name: crossplane-contrib-provider-aws-s3
|
||||
spec:
|
||||
package: xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v1.22.0-crossplane-v2-preview.0
|
||||
```
|
||||
|
||||
Save this to a file called `provider.yaml`, then apply it with:
|
||||
Save this as `provider.yaml` and apply it:
|
||||
|
||||
```shell {label="kube-apply-provider",copy-lines="all"}
|
||||
kubectl apply -f provider.yaml
|
||||
```
|
||||
|
||||
The Crossplane {{< hover label="provider" line="2" >}}Provider{{</hover>}}
|
||||
installs the Kubernetes _Custom Resource Definitions_ (CRDs) representing AWS S3
|
||||
services. These CRDs allow you to create AWS resources directly inside
|
||||
Kubernetes.
|
||||
|
||||
Verify the provider installed with `kubectl get providers`.
|
||||
|
||||
Check that Crossplane installed the provider:
|
||||
|
||||
```shell {copy-lines="1",label="getProvider"}
|
||||
kubectl get providers
|
||||
NAME INSTALLED HEALTHY PACKAGE AGE
|
||||
crossplane-contrib-provider-family-aws True True xpkg.crossplane.io/crossplane-contrib/provider-family-aws:v1.22.0-crossplane-v2-preview.0 27s
|
||||
provider-aws-s3 True True xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v1.22.0-crossplane-v2-preview.0 31s
|
||||
crossplane-contrib-provider-aws-s3 True True xpkg.crossplane.io/crossplane-contrib/provider-aws-s3:v1.22.0-crossplane-v2-preview.0 31s
|
||||
```
|
||||
|
||||
The S3 Provider installs a second Provider, the
|
||||
{{<hint "note">}}
|
||||
The S3 provider installs a second provider, the
|
||||
{{<hover label="getProvider" line="4">}}crossplane-contrib-provider-family-aws{{</hover >}}.
|
||||
The family provider manages authentication to AWS across all AWS family
|
||||
Providers.
|
||||
providers.
|
||||
{{</hint>}}
|
||||
|
||||
You can view the new CRDs with `kubectl get crds`.
|
||||
Every CRD maps to a unique AWS service Crossplane can provision and manage.
|
||||
Crossplane installed the AWS S3 provider. The provider needs credentials to
|
||||
connect to AWS. Before you can use managed resources, you have to
|
||||
[save the provider's credentials](#save-the-providers-credentials) and
|
||||
[configure the provider to use them](#configure-the-provider).
|
||||
|
||||
{{< hint "tip" >}}
|
||||
See details about all the supported CRDs in the
|
||||
[provider examples](https://github.com/crossplane-contrib/provider-upjet-aws/tree/main/examples).
|
||||
{{< /hint >}}
|
||||
### Save the provider's credentials
|
||||
|
||||
## Create a Kubernetes secret for AWS
|
||||
The provider requires credentials to create and manage AWS resources.
|
||||
Providers use a Kubernetes _Secret_ to connect the credentials to the provider.
|
||||
The provider needs credentials to create and manage AWS resources. Providers use
|
||||
a Kubernetes _secret_ to connect the credentials to the provider.
|
||||
|
||||
Generate a Kubernetes _Secret_ from your AWS key-pair and
|
||||
then configure the Provider to use it.
|
||||
Generate a secret from your AWS key-pair.
|
||||
|
||||
### Generate an AWS key-pair file
|
||||
For basic user authentication, use an AWS Access keys key-pair file.
|
||||
|
||||
{{< hint "tip" >}}
|
||||
{{<hint "tip">}}
|
||||
The [AWS documentation](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-creds)
|
||||
provides information on how to generate AWS Access keys.
|
||||
{{< /hint >}}
|
||||
{{</hint>}}
|
||||
|
||||
Create a text file containing the AWS account `aws_access_key_id` and `aws_secret_access_key`.
|
||||
Create a file containing the AWS account `aws_access_key_id` and
|
||||
`aws_secret_access_key`:
|
||||
|
||||
{{< editCode >}}
|
||||
```ini {copy-lines="all"}
|
||||
|
@ -90,32 +113,36 @@ aws_secret_access_key = $@<aws_secret_key>$@
|
|||
```
|
||||
{{< /editCode >}}
|
||||
|
||||
Save this text file as `aws-credentials.txt`.
|
||||
Save the text file as `aws-credentials.ini`.
|
||||
|
||||
{{< hint "note" >}}
|
||||
The [Authentication](https://docs.upbound.io/providers/provider-aws/authentication/) section of the AWS Provider documentation describes other authentication methods.
|
||||
{{< /hint >}}
|
||||
{{<hint "note">}}
|
||||
The [Authentication](https://docs.upbound.io/providers/provider-aws/authentication/)
|
||||
section of the AWS Provider documentation describes other authentication methods.
|
||||
{{</hint>}}
|
||||
|
||||
### Create a Kubernetes secret with the AWS credentials
|
||||
A Kubernetes generic secret has a name and contents.
|
||||
Use
|
||||
{{< hover label="kube-create-secret" line="1">}}kubectl create secret{{</hover >}}
|
||||
to generate the secret object named
|
||||
{{< hover label="kube-create-secret" line="2">}}aws-secret{{< /hover >}}
|
||||
in the {{< hover label="kube-create-secret" line="3">}}crossplane-system{{</ hover >}} namespace.
|
||||
|
||||
Use the {{< hover label="kube-create-secret" line="4">}}--from-file={{</hover>}} argument to set the value to the contents of the {{< hover label="kube-create-secret" line="4">}}aws-credentials.txt{{< /hover >}} file.
|
||||
Create a secret from the text file:
|
||||
|
||||
```shell {label="kube-create-secret",copy-lines="all"}
|
||||
kubectl create secret \
|
||||
generic aws-secret \
|
||||
-n crossplane-system \
|
||||
--from-file=creds=./aws-credentials.txt
|
||||
kubectl create secret generic aws-secret \
|
||||
--namespace=crossplane-system \
|
||||
--from-file=creds=./aws-credentials.ini
|
||||
```
|
||||
|
||||
## Create a ProviderConfig
|
||||
A {{< hover label="providerconfig" line="2">}}ProviderConfig{{</ hover >}}
|
||||
customizes the settings of the AWS Provider:
|
||||
{{<hint "important">}}
|
||||
Crossplane providers don't have to store their credentials in a secret. They
|
||||
can load their credentials from various sources.
|
||||
{{</hint>}}
|
||||
|
||||
Next, [configure the provider](#configure-the-provider) to use the credentials.
|
||||
|
||||
### Configure the provider
|
||||
|
||||
A {{< hover label="providerconfig" line="2">}}provider configuration{{</ hover >}}
|
||||
customizes the settings of the AWS Provider.
|
||||
|
||||
All providers need a configuration to tell them where to load credentials.
|
||||
|
||||
Create this provider configuration:
|
||||
|
||||
```yaml {label="providerconfig",copy-lines="all"}
|
||||
apiVersion: aws.upbound.io/v1beta1
|
||||
|
@ -131,20 +158,21 @@ spec:
|
|||
key: creds
|
||||
```
|
||||
|
||||
Save this to a file called `providerconfig.yaml`, then apply it with:
|
||||
Save the provider configuration as `providerconfig.yaml` and apply it:
|
||||
|
||||
```shell {label="kube-apply-providerconfig",copy-lines="all"}
|
||||
kubectl apply -f providerconfig.yaml
|
||||
```
|
||||
|
||||
This attaches the AWS credentials, saved as a Kubernetes secret, as a
|
||||
{{< hover label="providerconfig" line="8">}}secretRef{{</ hover>}}.
|
||||
This tells the provider to load credentials from
|
||||
[the secret](#save-the-providers-credentials).
|
||||
|
||||
## Create a managed resource
|
||||
{{< hint "note" >}}
|
||||
AWS S3 bucket names must be globally unique. To generate a unique name the example uses a random hash.
|
||||
Any unique name is acceptable.
|
||||
{{< /hint >}}
|
||||
## Use the managed resource
|
||||
|
||||
{{<hint "note">}}
|
||||
AWS S3 bucket names must be globally unique. This example uses `generateName` to
|
||||
generate a random name. Any unique name is acceptable.
|
||||
{{</hint>}}
|
||||
|
||||
```yaml {label="bucket"}
|
||||
apiVersion: s3.aws.m.upbound.io/v1beta1
|
||||
|
@ -155,57 +183,48 @@ metadata:
|
|||
spec:
|
||||
forProvider:
|
||||
region: us-east-2
|
||||
providerConfigRef:
|
||||
name: default
|
||||
```
|
||||
|
||||
Save this to a file called `bucket.yaml`, then apply it with:
|
||||
Save the bucket to `bucket.yaml` and apply it:
|
||||
|
||||
```shell {label="kube-create-bucket",copy-lines="all"}
|
||||
kubectl create -f bucket.yaml
|
||||
```
|
||||
|
||||
The {{< hover label="bucket" line="5">}}metadata.generateName{{< /hover >}} gives a
|
||||
pattern that Kubernetes will use to create a unique name for the bucket in S3.
|
||||
The generated name will look like `crossplane-bucket-<hash>`.
|
||||
|
||||
Use `kubectl -n default get buckets.s3.aws.m.upbound.io` to verify Crossplane created the bucket.
|
||||
|
||||
{{< hint "tip" >}}
|
||||
Crossplane created the bucket when the values `READY` and `SYNCED` are `True`.
|
||||
This may take up to 5 minutes.
|
||||
{{< /hint >}}
|
||||
Check that Crossplane created the bucket:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n default get buckets.s3.aws.m.upbound.io
|
||||
kubectl get buckets.s3.aws.m.upbound.io
|
||||
NAME SYNCED READY EXTERNAL-NAME AGE
|
||||
crossplane-bucket-7tfcj True True crossplane-bucket-7tfcj 3m4s
|
||||
```
|
||||
|
||||
## Delete the managed resource
|
||||
When you are finished with your S3 bucket, use `kubectl -n default
|
||||
delete buckets.s3.aws.m.upbound.io <bucketname>` to remove the bucket.
|
||||
{{<hint "tip">}}
|
||||
Crossplane created the bucket when the values `READY` and `SYNCED` are `True`.
|
||||
{{</hint>}}
|
||||
|
||||
Delete the bucket:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n default delete buckets.s3.aws.m.upbound.io crossplane-bucket-7tfcj
|
||||
kubectl delete buckets.s3.aws.m.upbound.io crossplane-bucket-7tfcj
|
||||
bucket.s3.aws.m.upbound.io "crossplane-bucket-7tfcj" deleted
|
||||
```
|
||||
|
||||
{{< hint "important" >}}
|
||||
When you delete the bucket managed resource, Crossplane deletes the S3 bucket
|
||||
from AWS.
|
||||
|
||||
{{<hint "important">}}
|
||||
Make sure to delete the S3 bucket before uninstalling the provider or shutting
|
||||
down your control plane. If those are no longer running, they can't clean up any
|
||||
managed resources and you would need to do so manually.
|
||||
{{< /hint >}}
|
||||
{{</hint>}}
|
||||
|
||||
## Composing managed resources
|
||||
Crossplane allows you to compose **any type of resource** into custom APIs for
|
||||
## Next steps
|
||||
|
||||
Crossplane allows you to compose **any kind of resource** into custom APIs for
|
||||
your users, which includes managed resources. Enjoy the freedom that Crossplane
|
||||
gives you to compose the diverse set of resources your applications need for
|
||||
their unique environments, scenarios, and requirements.
|
||||
|
||||
Follow [Get Started with Composition]({{<ref "../get-started/get-started-with-composition">}})
|
||||
to learn more about how composition works.
|
||||
|
||||
## Next steps
|
||||
* Join the [Crossplane Slack](https://slack.crossplane.io/) and connect with
|
||||
Crossplane users and contributors.
|
||||
|
|
|
@ -44,7 +44,7 @@ the [Kubebuilder documentation](https://book.kubebuilder.io) to see what's
|
|||
involved in writing a controller.
|
||||
{{</hint>}}
|
||||
|
||||
# Crossplane components
|
||||
## Crossplane components
|
||||
|
||||
Crossplane has three major components:
|
||||
|
||||
|
@ -55,7 +55,7 @@ Crossplane has three major components:
|
|||
You can use all three components to build your control plane, or pick only the
|
||||
ones you need.
|
||||
|
||||
## Composition
|
||||
### Composition
|
||||
|
||||
Composition lets you build custom APIs to control your cloud native software.
|
||||
|
||||
|
@ -66,9 +66,9 @@ extend Kubernetes with new custom resources.
|
|||
controller.** The controller is the software that reacts when a user calls the
|
||||
custom resource API.
|
||||
|
||||
Say you want your control plane to serve an `Application` custom resource API.
|
||||
When someone creates an `Application`, the control plane should create a
|
||||
Kubernetes `Deployment` and a `Service`.
|
||||
Say you want your control plane to serve an `App` custom resource API. When
|
||||
someone creates an `App`, the control plane should create a Kubernetes
|
||||
`Deployment` and a `Service`.
|
||||
|
||||
**If there's not already a controller that does what you want - and exposes the
|
||||
API you want - you have to write the controller yourself.**
|
||||
|
@ -78,8 +78,8 @@ flowchart TD
|
|||
user(User)
|
||||
|
||||
subgraph control [Control Plane]
|
||||
api(Application API)
|
||||
controller[Your Application Controller]
|
||||
api(App API)
|
||||
controller[Your App Controller]
|
||||
deployment(Deployment API)
|
||||
service(Service API)
|
||||
end
|
||||
|
@ -101,7 +101,7 @@ flowchart TD
|
|||
user(User)
|
||||
|
||||
subgraph control [Control Plane]
|
||||
api(Application API)
|
||||
api(App API)
|
||||
|
||||
subgraph crossplane [Composition Engine]
|
||||
fn(Python Function)
|
||||
|
@ -138,7 +138,7 @@ build new custom resource APIs powered by managed resources.
|
|||
Follow [Get Started with Composition]({{<ref "../get-started/get-started-with-composition">}})
|
||||
to see how composition works.
|
||||
|
||||
## Managed resources
|
||||
### Managed resources
|
||||
|
||||
Managed resources (MRs) are ready-made Kubernetes custom resources.
|
||||
|
||||
|
@ -181,7 +181,7 @@ flowchart TD
|
|||
user(User)
|
||||
|
||||
subgraph control [Control Plane]
|
||||
api(Application API)
|
||||
api(App API)
|
||||
|
||||
subgraph crossplane [Composition Engine]
|
||||
fn(Python Function)
|
||||
|
@ -223,7 +223,7 @@ GCP, Terraform, Helm, GitHub, etc to support Crossplane v2 soon.
|
|||
<!-- vale gitlab.FutureTense = YES -->
|
||||
{{</hint>}}
|
||||
|
||||
## Package manager
|
||||
### Package manager
|
||||
|
||||
The Crossplane package manager lets you install new managed resources and
|
||||
composition functions.
|
||||
|
|
|
@ -73,6 +73,7 @@ ReplicaSets
|
|||
RPC
|
||||
RPCs
|
||||
RSS
|
||||
sandboxed
|
||||
SCSS
|
||||
SDK
|
||||
SDKs
|
||||
|
@ -93,6 +94,7 @@ Subnet
|
|||
subnets
|
||||
Substrings
|
||||
syscall
|
||||
templated
|
||||
TLS
|
||||
tolerations
|
||||
UI
|
||||
|
|
|
@ -16,6 +16,7 @@ config
|
|||
CONTRIBUTING.md
|
||||
ControllerConfig
|
||||
ControllerConfigs
|
||||
CRs
|
||||
CRDs
|
||||
Crossplane
|
||||
crossplane-admin
|
||||
|
@ -42,6 +43,7 @@ function-environment-configs
|
|||
function-extra-resources
|
||||
function-go-templating
|
||||
function-patch-and-transform
|
||||
function-template-python
|
||||
HealthyPackageRevision
|
||||
Helm-like
|
||||
ImageConfig
|
||||
|
|
Loading…
Reference in New Issue