mirror of https://github.com/crossplane/docs.git
parent
e2603a9cc0
commit
2b598cb79f
|
@ -54,43 +54,6 @@ To create new content create a new markdown file in the appropriate location.
|
|||
To create a new section, create a new directory and an `_index.md` file in the
|
||||
root.
|
||||
|
||||
### Types of content
|
||||
Crossplane documentation has three content sections:
|
||||
* The [Contributing Guide]({{<ref "/contribute/_index.md">}}) with details on
|
||||
how to contribute to the Crossplane documentation.
|
||||
* The [Knowledge Base]({{<ref "/knowledge-base" >}}) is for content related to
|
||||
Crossplane integrations, in-depth articles or how-to guides.
|
||||
* [User documentation]({{<ref "/master" >}}) are for generic documentation,
|
||||
commonly version-specific.
|
||||
|
||||
#### User documentation vs knowledge base articles
|
||||
User documentation includes both _conceptual_ and _procedural_ instructions.
|
||||
|
||||
_Conceptual_ content describes the background and theory behind the technology.
|
||||
Conceptual documents are helpful to explain the "why" of the technology.
|
||||
|
||||
An example of _Conceptual_ content would be describing the role
|
||||
of a Crossplane Provider.
|
||||
|
||||
_Procedural_ content is the step-by-step instructions to do something.
|
||||
Procedural content details the "how" of a piece of technology.
|
||||
|
||||
An example of a _Procedural_ document would be a step-by-step Crossplane
|
||||
installation guide.
|
||||
|
||||
User documentation is more narrowly focused on a single piece or
|
||||
related pieces of technology. For example, installing a Provider and creating a
|
||||
ProviderConfig.
|
||||
|
||||
Knowledge base articles are more "free-form" and can describe more than one
|
||||
piece of technology or provide more opinionated examples.
|
||||
|
||||
{{< hint "tip" >}}
|
||||
Not sure if the content would be better as a knowledge base article or user
|
||||
document? Ask in the `#documentation` channel of the
|
||||
[Crossplane Slack](https://slack.crossplane.io/).
|
||||
{{< /hint >}}
|
||||
|
||||
### Front matter
|
||||
Each page contains metadata called
|
||||
[front matter](https://gohugo.io/content-management/front-matter/). Each page
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
---
|
||||
title: Knowledge Base
|
||||
weight: -1
|
||||
cascade:
|
||||
version: "0"
|
||||
docs: false
|
||||
product: "Knowledge Base"
|
||||
---
|
||||
|
||||
{{< img src="media/banner.png" alt="Crossplane Popsicle Truck" eager=true >}}
|
||||
<br />
|
||||
|
||||
The Crossplane Knowledge Base is a collection of documents covering recommended
|
||||
practices, third-party integrations and Crossplane-related how-tos.
|
||||
|
||||
## Topics
|
||||
* [Configuration Guides]({{<ref "guides" >}}) covers topics related to operating
|
||||
and using Crossplane.
|
||||
* [Integrations]({{<ref "integrations" >}}) are topics using Crossplane with
|
||||
third-party tools like [Vault](https://www.vaultproject.io/).
|
||||
|
||||
## Contribute an article
|
||||
Contributions to the Knowledge Base are always welcome. Knowledge Base articles
|
||||
cover topics in detail or describe using Crossplane with third-party software.
|
||||
|
||||
Some examples of Knowledge Base articles could include:
|
||||
* Troubleshooting Crossplane providers
|
||||
* Migrating to Crossplane (from another tool)
|
||||
* Using Crossplane with [ArgoCD]({{< ref "integrations/argo-cd-crossplane" >}})
|
||||
|
||||
Find more information in the Crossplane
|
||||
[documentation contributor's guide]({{< ref "/contribute" >}}).
|
|
@ -1,12 +0,0 @@
|
|||
---
|
||||
title: Configuration Guides
|
||||
weight: 10
|
||||
description: Guides on using, operating and configuring Crossplane
|
||||
---
|
||||
|
||||
Crossplane Configuration Guides cover topics about using, operating and
|
||||
configuring Crossplane.
|
||||
|
||||
These guides cover specific administrative tasks,
|
||||
complex scenarios or more in-depth examples of Crossplane components.
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
---
|
||||
title: Composition Revisions
|
||||
---
|
||||
|
||||
This guide discusses the use of "Composition Revisions" to safely make and roll
|
||||
back changes to a Crossplane [`Composition`][composition-type]. It assumes
|
||||
familiarity with Crossplane, and particularly with
|
||||
[Compositions].
|
||||
|
||||
A `Composition` configures how Crossplane should reconcile a Composite Resource
|
||||
(XR). Put otherwise, when you create an XR the selected `Composition` determines
|
||||
what managed resources Crossplane will create in response. Let's say for example
|
||||
that you define a `PlatformDB` XR, which represents your organisation's common
|
||||
database configuration of an Azure MySQL Server and a few firewall rules. The
|
||||
`Composition` contains the 'base' configuration for the MySQL server and the
|
||||
firewall rules that is extended by the configuration for the `PlatformDB`.
|
||||
|
||||
There is a one-to-many relationship between a `Composition` and the XRs that use
|
||||
it. You might define a `Composition` named `big-platform-db` that is used by ten
|
||||
different `PlatformDB` XRs. Usually, in the interest of self-service, the
|
||||
`Composition` is managed by a different team from the actual `PlatformDB` XRs.
|
||||
For example the `Composition` may be written and maintained by a platform team
|
||||
member, while individual application teams create `PlatformDB` XRs that use said
|
||||
`Composition`.
|
||||
|
||||
Each `Composition` is mutable - you can update it as your organisation's needs
|
||||
change. However, without Composition Revisions updating a `Composition` can be a
|
||||
risky process. Crossplane constantly uses the `Composition` to ensure that your
|
||||
actual infrastructure - your MySQL Servers and firewall rules - match your
|
||||
desired state. If you have 10 `PlatformDB` XRs all using the `big-platform-db`
|
||||
`Composition`, all 10 of those XRs will be instantly updated in accordance with
|
||||
any updates you make to the `big-platform-db` `Composition`.
|
||||
|
||||
Composition Revisions allow XRs to opt out of automatic updates. Instead you can
|
||||
update your XRs to leverage the latest `Composition` settings at your own pace.
|
||||
This enables you to [canary] changes to your infrastructure, or to roll back
|
||||
some XRs to previous `Composition` settings without rolling back all XRs.
|
||||
|
||||
## Using Composition Revisions
|
||||
|
||||
When you enable Composition Revisions three things happen:
|
||||
|
||||
1. Crossplane creates a `CompositionRevision` for each `Composition` update.
|
||||
1. Composite Resources gain a `spec.compositionRevisionRef` field that specifies
|
||||
which `CompositionRevision` they use.
|
||||
1. Composite Resources gain a `spec.compositionUpdatePolicy` field that
|
||||
specifies how they should be updated to new Composition Revisions.
|
||||
|
||||
Each time you edit a `Composition` Crossplane will automatically create a
|
||||
`CompositionRevision` that represents that 'revision' of the `Composition` -
|
||||
that unique state. Each revision is allocated an increasing revision number.
|
||||
This gives `CompositionRevision` consumers an idea about which revision is
|
||||
'newest'.
|
||||
|
||||
Crossplane distinguishes between the 'newest' and the 'current' revision of a
|
||||
`Composition`. That is, if you revert a `Composition` to a previous state that
|
||||
corresponds to an existing `CompositionRevision` that revision will become
|
||||
'current' even if it is not the 'newest' revision (i.e. the most latest _unique_
|
||||
`Composition` configuration).
|
||||
|
||||
You can discover which revisions exist using `kubectl`:
|
||||
|
||||
```console
|
||||
# Find all revisions of the Composition named 'example'
|
||||
kubectl get compositionrevision -l crossplane.io/composition-name=example
|
||||
```
|
||||
|
||||
This should produce output something like:
|
||||
|
||||
```console
|
||||
NAME REVISION CURRENT AGE
|
||||
example-18pdg 1 False 4m36s
|
||||
example-2bgdr 2 True 73s
|
||||
example-xjrdm 3 False 61s
|
||||
```
|
||||
|
||||
> A `Composition` is a mutable resource that you can update as your needs
|
||||
> change over time. Each `CompositionRevision` is an immutable snapshot of those
|
||||
> needs at a particular point in time.
|
||||
|
||||
Crossplane behaves the same way by default whether Composition Revisions are
|
||||
enabled or not. This is because when you enable Composition Revisions all XRs
|
||||
default to the `Automatic` `compositionUpdatePolicy`. XRs support two update
|
||||
policies:
|
||||
|
||||
* `Automatic`: Automatically use the current `CompositionRevision`. (Default)
|
||||
* `Manual`: Require manual intervention to change `CompositionRevision`.
|
||||
|
||||
The below XR uses the `Manual` policy. When this policy is used the XR will
|
||||
select the current `CompositionRevision` when it is first created, but must
|
||||
manually be updated when you wish it to use another `CompositionRevision`.
|
||||
|
||||
```yaml
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: PlatformDB
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
parameters:
|
||||
storageGB: 20
|
||||
# The Manual policy specifies that you do not want this XR to update to the
|
||||
# current CompositionRevision automatically.
|
||||
compositionUpdatePolicy: Manual
|
||||
compositionRef:
|
||||
name: example
|
||||
writeConnectionSecretToRef:
|
||||
name: db-conn
|
||||
```
|
||||
|
||||
Crossplane sets an XR's `compositionRevisionRef` automatically at creation time
|
||||
regardless of your chosen `compositionUpdatePolicy`. If you choose the `Manual`
|
||||
policy you must edit the `compositionRevisionRef` field when you want your XR to
|
||||
use a different `CompositionRevision`.
|
||||
|
||||
```yaml
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: PlatformDB
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
parameters:
|
||||
storageGB: 20
|
||||
compositionUpdatePolicy: Manual
|
||||
compositionRef:
|
||||
name: example
|
||||
# Update the referenced CompositionRevision if and when you are ready.
|
||||
compositionRevisionRef:
|
||||
name: example-18pdg
|
||||
writeConnectionSecretToRef:
|
||||
name: db-conn
|
||||
```
|
||||
|
||||
[composition-type]: {{<ref "../../master/concepts/compositions" >}}
|
||||
[Compositions]: {{<ref "../../master/concepts/compositions" >}}
|
||||
[canary]: https://martinfowler.com/bliki/CanaryRelease.html
|
||||
[install-guide]: {{<ref "../../master/software/install" >}}
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
title: Integrations
|
||||
weight: 20
|
||||
description: Integrate Crossplane with third-party software
|
||||
---
|
||||
|
||||
This section contains guides for using Crossplane alongside other technologies.
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
title: Crossplane API
|
||||
title: API Reference
|
||||
weight: 400
|
||||
description: "API details for Crossplane's core types"
|
||||
cascade:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
weight: 400
|
||||
title: Crossplane CLI
|
||||
weight: 200
|
||||
title: CLI Reference
|
||||
description: "Documentation for the Crossplane command-line interface"
|
||||
---
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Concepts
|
||||
weight: 100
|
||||
weight: 50
|
||||
description: Understand Crossplane's core components
|
||||
---
|
||||
|
||||
|
|
|
@ -204,4 +204,4 @@ spec:
|
|||
name: my-claim-secret
|
||||
```
|
||||
|
||||
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}).
|
||||
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
|
|
@ -618,7 +618,7 @@ recreate the XRD to change the `connectionSecretKeys`.
|
|||
{{</hint >}}
|
||||
|
||||
For more information on connection secrets read the
|
||||
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}).
|
||||
[Connection Secrets knowledge base article]({{<ref "connection-details">}}).
|
||||
|
||||
### Set composite resource defaults
|
||||
XRDs can set default parameters for composite resources and Claims.
|
||||
|
|
|
@ -189,7 +189,7 @@ spec:
|
|||
### Composition revision policy
|
||||
|
||||
Crossplane tracks changes to Compositions as
|
||||
[Composition revisions]({{<ref "/knowledge-base/guides/composition-revisions">}}) .
|
||||
[Composition revisions]({{<ref "composition-revisions">}}) .
|
||||
|
||||
A composite resource can use
|
||||
a {{<hover label="comprev" line="6">}}compositionUpdatePolicy{{</hover>}} to
|
||||
|
@ -217,7 +217,7 @@ spec:
|
|||
### Composition revision selection
|
||||
|
||||
Crossplane records changes to Compositions as
|
||||
[Composition revisions]({{<ref "/knowledge-base/guides/composition-revisions">}}).
|
||||
[Composition revisions]({{<ref "composition-revisions">}}).
|
||||
A composite resource can
|
||||
select a specific Composition revision.
|
||||
|
||||
|
@ -309,7 +309,7 @@ spec:
|
|||
```
|
||||
|
||||
Composite resources can write connection secrets to an
|
||||
[external secret store]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}),
|
||||
[external secret store]({{<ref "../guides/vault-as-secret-store">}}),
|
||||
like HashiCorp Vault.
|
||||
|
||||
{{<hint "important" >}}
|
||||
|
@ -332,10 +332,10 @@ spec:
|
|||
# Removed for brevity
|
||||
```
|
||||
|
||||
Read the [External Secrets Store]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) documentation for more information on using
|
||||
Read the [External Secrets Store]({{<ref "../guides/vault-as-secret-store">}}) documentation for more information on using
|
||||
external secret stores.
|
||||
|
||||
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}).
|
||||
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
|
||||
|
||||
### Pausing composite resources
|
||||
|
||||
|
|
|
@ -453,7 +453,7 @@ $ crossplane xpkg push -f package/*.xpkg crossplane-contrib/function-example:v0.
|
|||
|
||||
{{<hint "tip">}}
|
||||
Crossplane has
|
||||
[language specific guides]({{<ref "../../knowledge-base/guides">}}) to writing
|
||||
[language specific guides]({{<ref "../guides">}}) to writing
|
||||
a composition function. Refer to the guide for your preferred language for a
|
||||
more detailed guide to writing a function.
|
||||
{{</hint>}}
|
||||
|
|
|
@ -0,0 +1,445 @@
|
|||
---
|
||||
title: Composition Revisions
|
||||
weight: 35
|
||||
---
|
||||
|
||||
This guide discusses the use of "Composition Revisions" to safely make and roll
|
||||
back changes to a Crossplane [`Composition`][composition-type]. It assumes
|
||||
familiarity with Crossplane, and particularly with
|
||||
[Compositions].
|
||||
|
||||
A `Composition` configures how Crossplane should reconcile a Composite Resource
|
||||
(XR). Put otherwise, when you create an XR the selected `Composition` determines
|
||||
what managed resources Crossplane will create in response. Let's say for example
|
||||
that you define a `PlatformDB` XR, which represents your organisation's common
|
||||
database configuration of an Azure MySQL Server and a few firewall rules. The
|
||||
`Composition` contains the 'base' configuration for the MySQL server and the
|
||||
firewall rules that is extended by the configuration for the `PlatformDB`.
|
||||
|
||||
There is a one-to-many relationship between a `Composition` and the XRs that use
|
||||
it. You might define a `Composition` named `big-platform-db` that is used by ten
|
||||
different `PlatformDB` XRs. Usually, in the interest of self-service, the
|
||||
`Composition` is managed by a different team from the actual `PlatformDB` XRs.
|
||||
For example the `Composition` may be written and maintained by a platform team
|
||||
member, while individual application teams create `PlatformDB` XRs that use said
|
||||
`Composition`.
|
||||
|
||||
Each `Composition` is mutable - you can update it as your organisation's needs
|
||||
change. However, without Composition Revisions updating a `Composition` can be a
|
||||
risky process. Crossplane constantly uses the `Composition` to ensure that your
|
||||
actual infrastructure - your MySQL Servers and firewall rules - match your
|
||||
desired state. If you have 10 `PlatformDB` XRs all using the `big-platform-db`
|
||||
`Composition`, all 10 of those XRs will be instantly updated in accordance with
|
||||
any updates you make to the `big-platform-db` `Composition`.
|
||||
|
||||
Composition Revisions allow XRs to opt out of automatic updates. Instead you can
|
||||
update your XRs to leverage the latest `Composition` settings at your own pace.
|
||||
This enables you to [canary] changes to your infrastructure, or to roll back
|
||||
some XRs to previous `Composition` settings without rolling back all XRs.
|
||||
|
||||
## Using Composition Revisions
|
||||
|
||||
When you enable Composition Revisions three things happen:
|
||||
|
||||
1. Crossplane creates a `CompositionRevision` for each `Composition` update.
|
||||
1. Composite Resources gain a `spec.compositionRevisionRef` field that specifies
|
||||
which `CompositionRevision` they use.
|
||||
1. Composite Resources gain a `spec.compositionUpdatePolicy` field that
|
||||
specifies how they should be updated to new Composition Revisions.
|
||||
|
||||
Each time you edit a `Composition` Crossplane will automatically create a
|
||||
`CompositionRevision` that represents that 'revision' of the `Composition` -
|
||||
that unique state. Each revision is allocated an increasing revision number.
|
||||
This gives `CompositionRevision` consumers an idea about which revision is
|
||||
'newest'.
|
||||
|
||||
Crossplane distinguishes between the 'newest' and the 'current' revision of a
|
||||
`Composition`. That is, if you revert a `Composition` to a previous state that
|
||||
corresponds to an existing `CompositionRevision` that revision will become
|
||||
'current' even if it is not the 'newest' revision (i.e. the most latest _unique_
|
||||
`Composition` configuration).
|
||||
|
||||
You can discover which revisions exist using `kubectl`:
|
||||
|
||||
```console
|
||||
# Find all revisions of the Composition named 'example'
|
||||
kubectl get compositionrevision -l crossplane.io/composition-name=example
|
||||
```
|
||||
|
||||
This should produce output something like:
|
||||
|
||||
```console
|
||||
NAME REVISION CURRENT AGE
|
||||
example-18pdg 1 False 4m36s
|
||||
example-2bgdr 2 True 73s
|
||||
example-xjrdm 3 False 61s
|
||||
```
|
||||
|
||||
> A `Composition` is a mutable resource that you can update as your needs
|
||||
> change over time. Each `CompositionRevision` is an immutable snapshot of those
|
||||
> needs at a particular point in time.
|
||||
|
||||
Crossplane behaves the same way by default whether Composition Revisions are
|
||||
enabled or not. This is because when you enable Composition Revisions all XRs
|
||||
default to the `Automatic` `compositionUpdatePolicy`. XRs support two update
|
||||
policies:
|
||||
|
||||
* `Automatic`: Automatically use the current `CompositionRevision`. (Default)
|
||||
* `Manual`: Require manual intervention to change `CompositionRevision`.
|
||||
|
||||
The below XR uses the `Manual` policy. When this policy is used the XR will
|
||||
select the current `CompositionRevision` when it is first created, but must
|
||||
manually be updated when you wish it to use another `CompositionRevision`.
|
||||
|
||||
```yaml
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: PlatformDB
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
parameters:
|
||||
storageGB: 20
|
||||
# The Manual policy specifies that you do not want this XR to update to the
|
||||
# current CompositionRevision automatically.
|
||||
compositionUpdatePolicy: Manual
|
||||
compositionRef:
|
||||
name: example
|
||||
writeConnectionSecretToRef:
|
||||
name: db-conn
|
||||
```
|
||||
|
||||
Crossplane sets an XR's `compositionRevisionRef` automatically at creation time
|
||||
regardless of your chosen `compositionUpdatePolicy`. If you choose the `Manual`
|
||||
policy you must edit the `compositionRevisionRef` field when you want your XR to
|
||||
use a different `CompositionRevision`.
|
||||
|
||||
```yaml
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: PlatformDB
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
parameters:
|
||||
storageGB: 20
|
||||
compositionUpdatePolicy: Manual
|
||||
compositionRef:
|
||||
name: example
|
||||
# Update the referenced CompositionRevision if and when you are ready.
|
||||
compositionRevisionRef:
|
||||
name: example-18pdg
|
||||
writeConnectionSecretToRef:
|
||||
name: db-conn
|
||||
```
|
||||
|
||||
## Complete example
|
||||
|
||||
This tutorial discusses how CompositionRevisions work and how they manage Composite Resource
|
||||
(XR) updates. This starts with a `Composition` and `CompositeResourceDefinition` (XRD) that defines a `MyVPC`
|
||||
resource and continues with creating multiple XRs to observe different upgrade paths. Crossplane will
|
||||
assign different CompositionRevisions to the created composite resources each time the composition is updated.
|
||||
|
||||
### Preparation
|
||||
##### Install Crossplane
|
||||
Install Crossplane v1.11.0 or later and wait until the Crossplane pods are running.
|
||||
```shell
|
||||
kubectl create namespace crossplane-system
|
||||
helm repo add crossplane-master https://charts.crossplane.io/master/
|
||||
helm repo update
|
||||
helm install crossplane --namespace crossplane-system crossplane-master/crossplane --devel --version 1.11.0-rc.0.108.g0521c32e
|
||||
kubectl get pods -n crossplane-system
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
crossplane-7f75ddcc46-f4d2z 1/1 Running 0 9s
|
||||
crossplane-rbac-manager-78bd597746-sdv6w 1/1 Running 0 9s
|
||||
```
|
||||
|
||||
#### Deploy Composition and XRD Examples
|
||||
Apply the example Composition.
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
labels:
|
||||
channel: dev
|
||||
name: myvpcs.aws.example.upbound.io
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: crossplane-system
|
||||
compositeTypeRef:
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
resources:
|
||||
- base:
|
||||
apiVersion: ec2.aws.upbound.io/v1beta1
|
||||
kind: VPC
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-west-1
|
||||
cidrBlock: 192.168.0.0/16
|
||||
enableDnsSupport: true
|
||||
enableDnsHostnames: true
|
||||
name: my-vcp
|
||||
```
|
||||
|
||||
Apply the example XRD.
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: myvpcs.aws.example.upbound.io
|
||||
spec:
|
||||
group: aws.example.upbound.io
|
||||
names:
|
||||
kind: MyVPC
|
||||
plural: myvpcs
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
referenceable: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: ID of this VPC that other objects will use to refer to it.
|
||||
required:
|
||||
- id
|
||||
```
|
||||
|
||||
Verify that Crossplane created the Composition revision
|
||||
```shell
|
||||
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME REVISION CHANNEL
|
||||
myvpcs.aws.example.upbound.io-ad265bc 1 dev
|
||||
```
|
||||
|
||||
{{< hint "note" >}}
|
||||
The label `dev` is automatically created from the Composition.
|
||||
{{< /hint >}}
|
||||
|
||||
|
||||
### Create Composite Resources
|
||||
This tutorial has four composite resources to cover different update policies and composition selection options.
|
||||
The default behavior is updating XRs to the latest revision of the Composition. However, this can be changed by setting
|
||||
`compositionUpdatePolicy: Manual` in the XR. It is also possible to select the latest revision with a specific label
|
||||
with `compositionRevisionSelector.matchLabels` together with `compositionUpdatePolicy: Automatic`.
|
||||
|
||||
#### Default update policy
|
||||
Create an XR without a `compositionUpdatePolicy` defined. The update policy is `Automatic` by default:
|
||||
```yaml
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
metadata:
|
||||
name: vpc-auto
|
||||
spec:
|
||||
id: vpc-auto
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
myvpc.aws.example.upbound.io/vpc-auto created
|
||||
```
|
||||
|
||||
#### Manual update policy
|
||||
Create a Composite Resource with `compositionUpdatePolicy: Manual` and `compositionRevisionRef`.
|
||||
```yaml
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
metadata:
|
||||
name: vpc-man
|
||||
spec:
|
||||
id: vpc-man
|
||||
compositionUpdatePolicy: Manual
|
||||
compositionRevisionRef:
|
||||
name: myvpcs.aws.example.upbound.io-ad265bc
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
```shell
|
||||
myvpc.aws.example.upbound.io/vpc-man created
|
||||
```
|
||||
|
||||
#### Using a selector
|
||||
Create an XR with a `compositionRevisionSelector` of `channel: dev`:
|
||||
```yaml
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
metadata:
|
||||
name: vpc-dev
|
||||
spec:
|
||||
id: vpc-dev
|
||||
compositionRevisionSelector:
|
||||
matchLabels:
|
||||
channel: dev
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
myvpc.aws.example.upbound.io/vpc-dev created
|
||||
```
|
||||
|
||||
Create an XR with a `compositionRevisionSelector` of `channel: staging`:
|
||||
```yaml
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
metadata:
|
||||
name: vpc-staging
|
||||
spec:
|
||||
id: vpc-staging
|
||||
compositionRevisionSelector:
|
||||
matchLabels:
|
||||
channel: staging
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
```shell
|
||||
myvpc.aws.example.upbound.io/vpc-staging created
|
||||
```
|
||||
|
||||
Verify the Composite Resource with the label `channel: staging` doesn't have a `REVISION`.
|
||||
All other XRs have a `REVISION` matching the created Composition Revision.
|
||||
```shell
|
||||
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME SYNCED REVISION POLICY MATCHLABEL
|
||||
vpc-auto True myvpcs.aws.example.upbound.io-ad265bc Automatic <none>
|
||||
vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev]
|
||||
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
|
||||
vpc-staging False <none> Automatic map[channel:staging]
|
||||
```
|
||||
|
||||
{{< hint "note" >}}
|
||||
The `vpc-staging` XR label doesn't match any existing Composition Revisions.
|
||||
{{< /hint >}}
|
||||
|
||||
### Create new Composition revisions
|
||||
Crossplane creates a new CompositionRevision when a Composition is created or updated. Label and annotation changes will
|
||||
also trigger a new CompositionRevision.
|
||||
|
||||
#### Update the Composition label
|
||||
Update the `Composition` label to `channel: staging`:
|
||||
```shell
|
||||
kubectl label composition myvpcs.aws.example.upbound.io channel=staging --overwrite
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
composition.apiextensions.crossplane.io/myvpcs.aws.example.upbound.io labeled
|
||||
```
|
||||
|
||||
Verify that Crossplane creates a new Composition revision:
|
||||
```shell
|
||||
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME REVISION CHANNEL
|
||||
myvpcs.aws.example.upbound.io-727b3c8 2 staging
|
||||
myvpcs.aws.example.upbound.io-ad265bc 1 dev
|
||||
```
|
||||
|
||||
Verify that Crossplane assigns the Composite Resources `vpc-auto` and `vpc-staging` to Composite revision:2.
|
||||
XRs `vpc-man` and `vpc-dev` are still assigned to the original revision:1:
|
||||
|
||||
```shell
|
||||
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME SYNCED REVISION POLICY MATCHLABEL
|
||||
vpc-auto True myvpcs.aws.example.upbound.io-727b3c8 Automatic <none>
|
||||
vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev]
|
||||
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
|
||||
vpc-staging True myvpcs.aws.example.upbound.io-727b3c8 Automatic map[channel:staging]
|
||||
```
|
||||
|
||||
{{< hint "note" >}}
|
||||
`vpc-auto` always use the latest Revision.
|
||||
`vpc-staging` now matches the label applied to Revision revision:2.
|
||||
{{< /hint >}}
|
||||
|
||||
#### Update Composition Spec and Label
|
||||
Update the Composition to disable DNS support in the VPC and change the label from `staging` back to `dev`.
|
||||
|
||||
Apply the following changes to update the `Composition` spec and label:
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
labels:
|
||||
channel: dev
|
||||
name: myvpcs.aws.example.upbound.io
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: crossplane-system
|
||||
compositeTypeRef:
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
resources:
|
||||
- base:
|
||||
apiVersion: ec2.aws.upbound.io/v1beta1
|
||||
kind: VPC
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-west-1
|
||||
cidrBlock: 192.168.0.0/16
|
||||
enableDnsSupport: false
|
||||
enableDnsHostnames: true
|
||||
name: my-vcp
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
```shell
|
||||
composition.apiextensions.crossplane.io/myvpcs.aws.example.upbound.io configured
|
||||
```
|
||||
|
||||
Verify that Crossplane creates a new Composition revision:
|
||||
|
||||
```shell
|
||||
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME REVISION CHANNEL
|
||||
myvpcs.aws.example.upbound.io-727b3c8 2 staging
|
||||
myvpcs.aws.example.upbound.io-ad265bc 1 dev
|
||||
myvpcs.aws.example.upbound.io-f81c553 3 dev
|
||||
```
|
||||
|
||||
{{< hint "note" >}}
|
||||
Changing the label and the spec values simultaneously is critical for deploying new changes to the `dev` channel.
|
||||
{{< /hint >}}
|
||||
|
||||
Verify Crossplane assigns the Composite Resources `vpc-auto` and `vpc-dev` to Composite revision:3.
|
||||
`vpc-staging` is assigned to revision:2, and `vpc-man` is still assigned to the original revision:1:
|
||||
|
||||
```shell
|
||||
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME SYNCED REVISION POLICY MATCHLABEL
|
||||
vpc-auto True myvpcs.aws.example.upbound.io-f81c553 Automatic <none>
|
||||
vpc-dev True myvpcs.aws.example.upbound.io-f81c553 Automatic map[channel:dev]
|
||||
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
|
||||
vpc-staging True myvpcs.aws.example.upbound.io-727b3c8 Automatic map[channel:staging]
|
||||
```
|
||||
|
||||
|
||||
{{< hint "note" >}}
|
||||
`vpc-dev` matches the updated label applied to Revision revision:3.
|
||||
`vpc-staging` matches the label applied to Revision revision:2.
|
||||
{{< /hint >}}
|
||||
|
||||
|
||||
[composition-type]: {{<ref "../../master/concepts/compositions" >}}
|
||||
[Compositions]: {{<ref "../../master/concepts/compositions" >}}
|
||||
[canary]: https://martinfowler.com/bliki/CanaryRelease.html
|
||||
[install-guide]: {{<ref "../../master/software/install" >}}
|
|
@ -748,7 +748,7 @@ details.
|
|||
This section discusses creating Kubernetes secrets.
|
||||
Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/).
|
||||
|
||||
Read the [external secrets store guide]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) for more information on using Crossplane
|
||||
Read the [external secrets store guide]({{<ref "../guides/vault-as-secret-store">}}) for more information on using Crossplane
|
||||
with an external secret store.
|
||||
{{</hint >}}
|
||||
|
||||
|
@ -958,7 +958,7 @@ for more information on restricting secret keys.
|
|||
{{< /hint >}}
|
||||
|
||||
For more information on connection secrets read the
|
||||
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}).
|
||||
[Connection Secrets knowledge base article]({{<ref "connection-details">}}).
|
||||
|
||||
{{<hint "warning">}}
|
||||
You can't change the
|
||||
|
@ -973,7 +973,7 @@ recreate the Composition to change the
|
|||
#### Save connection details to an external secret store
|
||||
|
||||
Crossplane
|
||||
[External Secret Stores]({{<ref "/knowledge-base/integrations/vault-as-secret-store" >}})
|
||||
[External Secret Stores]({{<ref "../guides/vault-as-secret-store" >}})
|
||||
write secrets and connection details to external secret stores like HashiCorp
|
||||
Vault.
|
||||
|
||||
|
@ -1018,7 +1018,7 @@ spec:
|
|||
# Removed for brevity
|
||||
```
|
||||
|
||||
For more details read the [External Secret Stores]({{<ref "/knowledge-base/integrations/vault-as-secret-store" >}})
|
||||
For more details read the [External Secret Stores]({{<ref "../guides/vault-as-secret-store" >}})
|
||||
integration guide.
|
||||
|
||||
### Resource readiness checks
|
||||
|
|
|
@ -0,0 +1,607 @@
|
|||
---
|
||||
title: Connection Details
|
||||
weight: 110
|
||||
description: "How to create and manage connection details across Crossplane managed resources, composite resources, Compositions and Claims"
|
||||
---
|
||||
|
||||
Using connection details in Crossplane requires the following components:
|
||||
* Defining the `writeConnectionSecretToRef.name` in a [Claim]({{<ref "/master/concepts/claims#claim-connection-secrets">}}).
|
||||
* Defining the `writeConnectionSecretsToNamespace` value in the [Composition]({{<ref "/master/concepts/compositions#composite-resource-combined-secret">}}).
|
||||
* Define the `writeConnectionSecretToRef` name and namespace for each resource in the
|
||||
[Composition]({{<ref "/master/concepts/compositions#composed-resource-secrets">}}).
|
||||
* Define the list of secret keys produced by each composed resource with `connectionDetails` in the
|
||||
[Composition]({{<ref "/master/concepts/compositions#define-secret-keys">}}).
|
||||
* Optionally, define the `connectionSecretKeys` in a
|
||||
[CompositeResourceDefinition]({{<ref "/master/concepts/composite-resource-definitions#manage-connection-secrets">}}).
|
||||
|
||||
{{<hint "note">}}
|
||||
This guide discusses creating Kubernetes secrets.
|
||||
Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/).
|
||||
|
||||
Read the [external secrets store guide]({{<ref "../guides/vault-as-secret-store">}}) for more information on using Crossplane
|
||||
with an external secret store.
|
||||
{{</hint >}}
|
||||
|
||||
## Background
|
||||
When a [Provider]({{<ref "/master/concepts/providers">}}) creates a managed
|
||||
resource, the resource may generate resource-specific details. These details can include
|
||||
usernames, passwords or connection details like an IP address.
|
||||
|
||||
Crossplane refers to this information as the _connection details_ or
|
||||
_connection secrets_.
|
||||
|
||||
The Provider
|
||||
defines what information to present as a _connection
|
||||
detail_ from a managed resource.
|
||||
|
||||
<!-- vale gitlab.SentenceLength = NO -->
|
||||
<!-- wordy because of type names -->
|
||||
When a managed resource is part of a
|
||||
[Composition]({{<ref "/master/concepts/compositions">}}), the Composition,
|
||||
[Composite Resource Definition]({{<ref "/master/concepts/composite-resource-definitions">}})
|
||||
and optionally, the
|
||||
[Claim]({{<ref "/master/concepts/claims">}}) define what details are visible
|
||||
and where they're stored.
|
||||
<!-- vale gitlab.SentenceLength = YES -->
|
||||
|
||||
{{<hint "note">}}
|
||||
All the following examples use the same set of Compositions,
|
||||
CompositeResourceDefinitions and Claims.
|
||||
|
||||
All examples rely on
|
||||
[Upbound provider-aws-iam](https://marketplace.upbound.io/providers/upbound/provider-aws-iam/)
|
||||
to create resources.
|
||||
|
||||
{{<expand "Reference Composition" >}}
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: xsecrettest.example.org
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: other-namespace
|
||||
compositeTypeRef:
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: XSecretTest
|
||||
resources:
|
||||
- name: key
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
spec:
|
||||
forProvider:
|
||||
userSelector:
|
||||
matchControllerRef: true
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key1
|
||||
connectionDetails:
|
||||
- fromConnectionSecretKey: username
|
||||
- fromConnectionSecretKey: password
|
||||
- fromConnectionSecretKey: attribute.secret
|
||||
- fromConnectionSecretKey: attribute.ses_smtp_password_v4
|
||||
patches:
|
||||
- fromFieldPath: "metadata.uid"
|
||||
toFieldPath: "spec.writeConnectionSecretToRef.name"
|
||||
transforms:
|
||||
- type: string
|
||||
string:
|
||||
fmt: "%s-secret1"
|
||||
- name: user
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: User
|
||||
spec:
|
||||
forProvider: {}
|
||||
- name: user2
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: User
|
||||
metadata:
|
||||
labels:
|
||||
docs.crossplane.io: user
|
||||
spec:
|
||||
forProvider: {}
|
||||
- name: key2
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
spec:
|
||||
forProvider:
|
||||
userSelector:
|
||||
matchLabels:
|
||||
docs.crossplane.io: user
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key2
|
||||
connectionDetails:
|
||||
- name: key2-user
|
||||
fromConnectionSecretKey: username
|
||||
- name: key2-password
|
||||
fromConnectionSecretKey: password
|
||||
- name: key2-secret
|
||||
fromConnectionSecretKey: attribute.secret
|
||||
- name: key2-smtp
|
||||
fromConnectionSecretKey: attribute.ses_smtp_password_v4
|
||||
patches:
|
||||
- fromFieldPath: "metadata.uid"
|
||||
toFieldPath: "spec.writeConnectionSecretToRef.name"
|
||||
transforms:
|
||||
- type: string
|
||||
string:
|
||||
fmt: "%s-secret2"
|
||||
```
|
||||
{{</expand >}}
|
||||
|
||||
{{<expand "Reference CompositeResourceDefinition" >}}
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: xsecrettests.example.org
|
||||
spec:
|
||||
group: example.org
|
||||
connectionSecretKeys:
|
||||
- username
|
||||
- password
|
||||
- attribute.secret
|
||||
- attribute.ses_smtp_password_v4
|
||||
- key2-user
|
||||
- key2-pass
|
||||
- key2-secret
|
||||
- key2-smtp
|
||||
names:
|
||||
kind: XSecretTest
|
||||
plural: xsecrettests
|
||||
claimNames:
|
||||
kind: SecretTest
|
||||
plural: secrettests
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
referenceable: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
```
|
||||
{{</ expand >}}
|
||||
|
||||
{{<expand "Reference Claim" >}}
|
||||
```yaml
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: SecretTest
|
||||
metadata:
|
||||
name: test-secrets
|
||||
namespace: default
|
||||
spec:
|
||||
writeConnectionSecretToRef:
|
||||
name: my-access-key-secret
|
||||
```
|
||||
{{</expand >}}
|
||||
{{</hint >}}
|
||||
|
||||
## Connection secrets in a managed resource
|
||||
|
||||
<!-- vale gitlab.Substitutions = NO -->
|
||||
<!-- vale gitlab.SentenceLength = NO -->
|
||||
<!-- under 25 words -->
|
||||
When a managed resource creates connection secrets, Crossplane can write the
|
||||
secrets to a
|
||||
[Kubernetes secret]({{<ref "/master/concepts/managed-resources#publish-secrets-to-kubernetes">}})
|
||||
or an
|
||||
[external secret store]({{<ref "/master/concepts/managed-resources#publish-secrets-to-an-external-secrets-store">}}).
|
||||
<!-- vale gitlab.SentenceLength = YES -->
|
||||
<!-- vale gitlab.Substitutions = YES -->
|
||||
|
||||
Creating an individual managed resource shows the connection secrets the
|
||||
resource creates.
|
||||
|
||||
{{<hint "note" >}}
|
||||
Read the [managed resources]({{<ref "/master/concepts/managed-resources">}})
|
||||
documentation for more information on configuring resources and storing
|
||||
connection secrets for individual resources.
|
||||
{{< /hint >}}
|
||||
|
||||
|
||||
For example, create an
|
||||
{{<hover label="mr" line="2">}}AccessKey{{</hover>}} resource and save the
|
||||
connection secrets in a Kubernetes secret named
|
||||
{{<hover label="mr" line="12">}}my-accesskey-secret{{</hover>}}
|
||||
in the
|
||||
{{<hover label="mr" line="11">}}default{{</hover>}} namespace.
|
||||
|
||||
```yaml {label="mr"}
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
metadata:
|
||||
name: test-accesskey
|
||||
spec:
|
||||
forProvider:
|
||||
userSelector:
|
||||
matchLabels:
|
||||
docs.crossplane.io: user
|
||||
writeConnectionSecretToRef:
|
||||
namespace: default
|
||||
name: my-accesskey-secret
|
||||
```
|
||||
|
||||
View the Kubernetes secret to see the connection details from the managed
|
||||
resource.
|
||||
This includes an
|
||||
{{<hover label="mrSecret" line="11">}}attribute.secret{{</hover>}},
|
||||
{{<hover label="mrSecret" line="12">}}attribute.ses_smtp_password_v4{{</hover>}},
|
||||
{{<hover label="mrSecret" line="13">}}password{{</hover>}} and
|
||||
{{<hover label="mrSecret" line="14">}}username{{</hover>}}
|
||||
|
||||
```yaml {label="mrSecret",copy-lines="1"}
|
||||
kubectl describe secret my-accesskey-secret
|
||||
Name: my-accesskey-secret
|
||||
Namespace: default
|
||||
Labels: <none>
|
||||
Annotations: <none>
|
||||
|
||||
Type: connection.crossplane.io/v1alpha1
|
||||
|
||||
Data
|
||||
====
|
||||
attribute.secret: 40 bytes
|
||||
attribute.ses_smtp_password_v4: 44 bytes
|
||||
password: 40 bytes
|
||||
username: 20 bytes
|
||||
```
|
||||
|
||||
Compositions and CompositeResourceDefinitions require the exact names of the
|
||||
secrets generated by a resource.
|
||||
|
||||
## Connection secrets in Compositions
|
||||
|
||||
Resources in a Composition that create connection details still create a
|
||||
secret object containing their connection details.
|
||||
Crossplane also generates
|
||||
another secret object for each composite resource,
|
||||
containing the secrets from all the defined resources.
|
||||
|
||||
For example, a Composition defines two
|
||||
{{<hover label="comp1" line="9">}}AccessKey{{</hover>}}
|
||||
objects.
|
||||
Each {{<hover label="comp1" line="9">}}AccessKey{{</hover>}} writes a
|
||||
connection secrets to the {{<hover label="comp1" line="15">}}name{{</hover>}}
|
||||
inside the {{<hover label="comp1" line="14">}}namespace{{</hover>}} defined by
|
||||
the resource
|
||||
{{<hover label="comp1" line="13">}}writeConnectionSecretToRef{{</hover>}}.
|
||||
|
||||
Crossplane also creates a secret object for the entire Composition
|
||||
saved in the namespace defined by
|
||||
{{<hover label="comp1" line="4">}}writeConnectionSecretsToNamespace{{</hover>}}
|
||||
with a Crossplane generated name.
|
||||
|
||||
```yaml {label="comp1",copy-lines="none"}
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: other-namespace
|
||||
resources:
|
||||
- name: key1
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
spec:
|
||||
forProvider:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key1-secret
|
||||
- name: key2
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
spec:
|
||||
forProvider:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key2-secret
|
||||
# Removed for brevity
|
||||
```
|
||||
|
||||
After applying a Claim, view the Kubernetes secrets to see three secret objects
|
||||
created.
|
||||
|
||||
The secret
|
||||
{{<hover label="compGetSec" line="3">}}key1-secret{{</hover>}} is from the resource
|
||||
{{<hover label="comp1" line="6">}}key1{{</hover>}},
|
||||
{{<hover label="compGetSec" line="4">}}key2-secret{{</hover>}} is from the resource
|
||||
{{<hover label="comp1" line="16">}}key2{{</hover>}}.
|
||||
|
||||
Crossplane creates another secret in the namespace
|
||||
{{<hover label="compGetSec" line="5">}}other-namespace{{</hover>}} with the
|
||||
secrets from resource in the Composition.
|
||||
|
||||
|
||||
```shell {label="compGetSec",copy-lines="1"}
|
||||
kubectl get secrets -A
|
||||
NAMESPACE NAME TYPE DATA AGE
|
||||
docs key1-secret connection.crossplane.io/v1alpha1 4 4s
|
||||
docs key2-secret connection.crossplane.io/v1alpha1 4 4s
|
||||
other-namespace 70975471-c44f-4f6d-bde6-6bbdc9de1eb8 connection.crossplane.io/v1alpha1 0 6s
|
||||
```
|
||||
|
||||
Although Crossplane creates a secret object, by default, Crossplane doesn't add
|
||||
any data to the object.
|
||||
|
||||
```yaml {copy-lines="none"}
|
||||
kubectl describe secret 70975471-c44f-4f6d-bde6-6bbdc9de1eb8 -n other-namespace
|
||||
Name: 70975471-c44f-4f6d-bde6-6bbdc9de1eb8
|
||||
Namespace: other-namespace
|
||||
|
||||
Type: connection.crossplane.io/v1alpha1
|
||||
|
||||
Data
|
||||
====
|
||||
```
|
||||
|
||||
The Composition must list the connection secrets to store for each resource.
|
||||
Use the
|
||||
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}} object under
|
||||
each resource and define the secret keys the resource creates.
|
||||
|
||||
|
||||
{{<hint "warning">}}
|
||||
You can't change the
|
||||
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}
|
||||
of a Composition.
|
||||
You must delete and
|
||||
recreate the Composition to change the
|
||||
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}.
|
||||
{{</hint >}}
|
||||
|
||||
```yaml {label="comp2",copy-lines="16-20"}
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: other-namespace
|
||||
resources:
|
||||
- name: key
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
spec:
|
||||
forProvider:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key1
|
||||
connectionDetails:
|
||||
- fromConnectionSecretKey: username
|
||||
- fromConnectionSecretKey: password
|
||||
- fromConnectionSecretKey: attribute.secret
|
||||
- fromConnectionSecretKey: attribute.ses_smtp_password_v4
|
||||
# Removed for brevity
|
||||
```
|
||||
|
||||
After applying a Claim the composite resource secret object contains the list of
|
||||
keys listed in the
|
||||
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl describe secret -n other-namespace
|
||||
Name: b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a
|
||||
Namespace: other-namespace
|
||||
|
||||
Type: connection.crossplane.io/v1alpha1
|
||||
|
||||
Data
|
||||
====
|
||||
username: 20 bytes
|
||||
attribute.secret: 40 bytes
|
||||
attribute.ses_smtp_password_v4: 44 bytes
|
||||
password: 40 bytes
|
||||
```
|
||||
|
||||
{{<hint "important">}}
|
||||
If a key isn't listed in the
|
||||
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}
|
||||
it isn't stored in the secret object.
|
||||
{{< /hint >}}
|
||||
|
||||
### Managing conflicting secret keys
|
||||
If resources produce conflicting keys, create a unique name with a connection
|
||||
details
|
||||
{{<hover label="comp3" line="25">}}name{{</hover>}}.
|
||||
|
||||
```yaml {label="comp3",copy-lines="none"}
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: other-namespace
|
||||
resources:
|
||||
- name: key
|
||||
base:
|
||||
kind: AccessKey
|
||||
spec:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key1
|
||||
connectionDetails:
|
||||
- fromConnectionSecretKey: username
|
||||
- name: key2
|
||||
base:
|
||||
kind: AccessKey
|
||||
spec:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key2
|
||||
connectionDetails:
|
||||
- name: key2-user
|
||||
fromConnectionSecretKey: username
|
||||
```
|
||||
|
||||
The secret object contains both keys,
|
||||
{{<hover label="comp3Sec" line="9">}}username{{</hover>}}
|
||||
and
|
||||
{{<hover label="comp3Sec" line="10">}}key2-user{{</hover>}}
|
||||
|
||||
```shell {label="comp3Sec",copy-lines="1"}
|
||||
kubectl describe secret -n other-namespace
|
||||
Name: b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a
|
||||
Namespace: other-namespace
|
||||
|
||||
Type: connection.crossplane.io/v1alpha1
|
||||
|
||||
Data
|
||||
====
|
||||
username: 20 bytes
|
||||
key2-user: 20 bytes
|
||||
# Removed for brevity.
|
||||
```
|
||||
|
||||
## Connection secrets in Composite Resource Definitions
|
||||
|
||||
The CompositeResourceDefinition (`XRD`), can restrict which secrets keys are
|
||||
put in the combined secret and provided to a Claim.
|
||||
|
||||
By default an XRD writes all secret keys listed in the composed resource
|
||||
`connectionDetails` to the combined secret object.
|
||||
|
||||
Limit the keys passed to the combined secret object and Claims with a
|
||||
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} object.
|
||||
|
||||
Inside the {{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} list
|
||||
the secret key names to create. Crossplane only adds the keys listed to the
|
||||
combined secret.
|
||||
|
||||
{{<hint "warning">}}
|
||||
You can't change the
|
||||
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} of an XRD.
|
||||
You must delete and
|
||||
recreate the XRD to change the
|
||||
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}}.
|
||||
{{</hint >}}
|
||||
|
||||
For example, an XRD may restrict the secrets to only the
|
||||
{{<hover label="xrd" line="5">}}username{{</hover>}},
|
||||
{{<hover label="xrd" line="6">}}password{{</hover>}} and custom named
|
||||
{{<hover label="xrd" line="7">}}key2-user{{</hover>}} keys.
|
||||
|
||||
```yaml {label="xrd",copy-lines="4-12"}
|
||||
kind: CompositeResourceDefinition
|
||||
spec:
|
||||
# Removed for brevity.
|
||||
connectionSecretKeys:
|
||||
- username
|
||||
- password
|
||||
- key2-user
|
||||
```
|
||||
|
||||
The secret from an individual resource contains all the resources detailed in
|
||||
the Composition's `connectionDetails`.
|
||||
|
||||
```shell {label="xrdSec",copy-lines="1"}
|
||||
kubectl describe secret key1 -n docs
|
||||
Name: key1
|
||||
Namespace: docs
|
||||
|
||||
Data
|
||||
====
|
||||
password: 40 bytes
|
||||
username: 20 bytes
|
||||
attribute.secret: 40 bytes
|
||||
attribute.ses_smtp_password_v4: 44 bytes
|
||||
```
|
||||
|
||||
The Claim's secret only contains the
|
||||
keys allowed by the XRD
|
||||
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}}
|
||||
fields.
|
||||
|
||||
```shell {label="xrdSec2",copy-lines="2"}
|
||||
kubectl describe secret my-access-key-secret
|
||||
Name: my-access-key-secret
|
||||
|
||||
Data
|
||||
====
|
||||
key2-user: 20 bytes
|
||||
password: 40 bytes
|
||||
username: 20 bytes
|
||||
```
|
||||
|
||||
## Secret objects
|
||||
Compositions create a secret object for each resource and an extra secret
|
||||
containing all the secrets from all resources.
|
||||
|
||||
Crossplane saves the resource secret objects in the location defined by the
|
||||
resource's
|
||||
{{<hover label="comp4" line="11">}}writeConnectionSecretToRef{{</hover>}}.
|
||||
|
||||
Crossplane saves the combined secret with a Crossplane generated name in the
|
||||
namespace defined in the Composition's
|
||||
{{<hover label="comp4" line="4">}}writeConnectionSecretsToNamespace{{</hover>}}.
|
||||
|
||||
```yaml {label="comp4",copy-lines="none"}
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: other-namespace
|
||||
resources:
|
||||
- name: key
|
||||
base:
|
||||
kind: AccessKey
|
||||
spec:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key1
|
||||
connectionDetails:
|
||||
- fromConnectionSecretKey: username
|
||||
- name: key2
|
||||
base:
|
||||
kind: AccessKey
|
||||
spec:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key2
|
||||
connectionDetails:
|
||||
- name: key2-user
|
||||
fromConnectionSecretKey: username
|
||||
```
|
||||
|
||||
If a Claim uses a secret, it's stored in the same namespace as the Claim with
|
||||
the name defined in the Claim's
|
||||
{{<hover label="claim3" line="7">}}writeConnectionSecretToRef{{</hover>}}.
|
||||
|
||||
```yaml {label="claim3",copy-lines="none"}
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: SecretTest
|
||||
metadata:
|
||||
name: test-secrets
|
||||
namespace: default
|
||||
spec:
|
||||
writeConnectionSecretToRef:
|
||||
name: my-access-key-secret
|
||||
```
|
||||
|
||||
After applying the Claim Crossplane creates the following secrets:
|
||||
* The Claim's secret, {{<hover label="allSec" line="3">}}my-access-key-secret{{</hover>}}
|
||||
in the Claim's {{<hover label="claim3" line="5">}}namespace{{</hover>}}.
|
||||
* The first resource's secret object, {{<hover label="allSec" line="4">}}key1{{</hover>}}.
|
||||
* The second resource's secret object, {{<hover label="allSec" line="5">}}key2{{</hover>}}.
|
||||
* The composite resource secret object in the
|
||||
{{<hover label="allSec" line="6">}}other-namespace{{</hover>}} defined by the
|
||||
Composition's `writeConnectionSecretsToNamespace`.
|
||||
|
||||
|
||||
```shell {label="allSec",copy-lines="none"}
|
||||
kubectl get secret -A
|
||||
NAMESPACE NAME TYPE DATA AGE
|
||||
default my-access-key-secret connection.crossplane.io/v1alpha1 8 29m
|
||||
docs key1 connection.crossplane.io/v1alpha1 4 31m
|
||||
docs key2 connection.crossplane.io/v1alpha1 4 31m
|
||||
other-namespace b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a connection.crossplane.io/v1alpha1 8 31m
|
||||
```
|
|
@ -50,7 +50,7 @@ kind: Instance
|
|||
|
||||
A managed resource's `deletionPolicy` tells the Provider what to do after
|
||||
deleting the managed resource. If the `deletionPolicy` is `Delete` the Provider
|
||||
deletes the external resource as well. If the `deletionPolicy` is `Orphan` the
|
||||
deletes the external resource as well. If the `deletionPolicy` is `orphan` the
|
||||
Provider deletes the managed resource but doesn't delete the external resource.
|
||||
|
||||
#### Options
|
||||
|
@ -357,7 +357,7 @@ Crossplane supports the following policies:
|
|||
| `Create` | If the external resource doesn't exist, Crossplane creates it based on the managed resource settings. |
|
||||
| `Delete` | Crossplane can delete the external resource when deleting the managed resource. |
|
||||
| `LateInitialize` | Crossplane initializes some external resource settings not defined in the `spec.forProvider` of the managed resource. See [the late initialization]({{<ref "./managed-resources#late-initialization" >}}) section for more details. |
|
||||
| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{<ref "/knowledge-base/guides/import-existing-resources#import-resources-automatically">}}). |
|
||||
| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{<ref "../guides/import-existing-resources#import-resources-automatically">}}). |
|
||||
| `Update` | Crossplane changes the external resource when changing the managed resource. |
|
||||
{{</table >}}
|
||||
|
||||
|
@ -373,7 +373,7 @@ The following is a list of common policy combinations:
|
|||
| {{<check>}} | | {{<check>}} | {{<check>}} | | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't apply changes to the external resource after creation. |
|
||||
| {{<check>}} | | | {{<check>}} | {{<check>}} | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't import any settings from the external resource. |
|
||||
| {{<check>}} | | | {{<check>}} | | Crossplane creates the external resource but doesn't apply any changes to the external resource or managed resource. Crossplane can't delete the resource. |
|
||||
| | | | {{<check>}} | | Crossplane only observes a resource. Used for [observe only resources]({{<ref "/knowledge-base/guides/import-existing-resources#import-resources-automatically">}}). |
|
||||
| | | | {{<check>}} | | Crossplane only observes a resource. Used for [observe only resources]({{<ref "../guides/import-existing-resources#import-resources-automatically">}}). |
|
||||
| | | | | | No policy set. An alternative method for [pausing](#paused) a resource. |
|
||||
{{< /table >}}
|
||||
|
||||
|
@ -567,7 +567,7 @@ metadata:
|
|||
|
||||
{{<hint "tip" >}}
|
||||
Read the
|
||||
[Vault as an External Secrets Store]({{<ref "knowledge-base/integrations/vault-as-secret-store">}})
|
||||
[Vault as an External Secrets Store]({{<ref "../guides/vault-as-secret-store">}})
|
||||
guide for details on using StoreConfig objects.
|
||||
{{< /hint >}}
|
||||
|
||||
|
|
|
@ -404,7 +404,7 @@ If you remove the Provider first, you must manually delete external resources
|
|||
through your cloud provider. Managed resources must be manually deleted by
|
||||
removing their finalizers.
|
||||
|
||||
For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{<ref "/knowledge-base/guides/troubleshoot#deleting-when-a-resource-hangs" >}}).
|
||||
For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{<ref "../guides/troubleshoot-crossplane#deleting-when-a-resource-hangs" >}}).
|
||||
{{< /hint >}}
|
||||
|
||||
## Verify a Provider
|
||||
|
@ -593,12 +593,12 @@ replacement for Controller configuration and is available in v1.14+.
|
|||
|
||||
Applying a Crossplane `ControllerConfig` to a Provider changes the settings of
|
||||
the Provider's pod. The
|
||||
[Crossplane ControllerConfig schema](https://doc.crds.dev/github.com/crossplane/crossplane/pkg.crossplane.io/ControllerConfig/v1alpha1)
|
||||
[Crossplane ControllerConfig schema]({{< ref "../api#ControllerConfig-spec" >}})
|
||||
defines the supported set of ControllerConfig settings.
|
||||
|
||||
The most common use case for ControllerConfigs are providing `args` to a
|
||||
Provider's pod enabling optional services. For example, enabling
|
||||
[external secret stores](https://docs.crossplane.io/knowledge-base/integrations/vault-as-secret-store/#enable-external-secret-stores-in-the-provider)
|
||||
[external secret stores]({{< ref "../guides/vault-as-secret-store#enable-external-secret-stores-in-the-provider" >}})
|
||||
for a Provider.
|
||||
|
||||
Each Provider determines their supported set of `args`.
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Guides
|
||||
weight: 100
|
||||
description: Crossplane integrations and detailed examples.
|
||||
---
|
|
@ -0,0 +1,443 @@
|
|||
---
|
||||
title: Troubleshoot Crossplane
|
||||
weight: 306
|
||||
---
|
||||
## Requested Resource Not Found
|
||||
|
||||
If you use the Crossplane CLI to install a `Provider` or
|
||||
`Configuration` (e.g. `crossplane install provider
|
||||
xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0`) and get `the server
|
||||
could not find the requested resource` error, more often than not, that is an
|
||||
indicator that the Crossplane CLI you're using is outdated. In other words
|
||||
some Crossplane API has been graduated from alpha to beta or stable and the old
|
||||
plugin is not aware of this change.
|
||||
|
||||
|
||||
## Resource Status and Conditions
|
||||
|
||||
Most Crossplane resources have a `status` section that can represent the current
|
||||
state of that particular resource. Running `kubectl describe` against a
|
||||
Crossplane resource will frequently give insightful information about its
|
||||
condition. For example, to determine the status of a GCP `CloudSQLInstance`
|
||||
managed resource use `kubectl describe` for the resource.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl describe cloudsqlinstance my-db
|
||||
Status:
|
||||
Conditions:
|
||||
Last Transition Time: 2019-09-16T13:46:42Z
|
||||
Reason: Creating
|
||||
Status: False
|
||||
Type: Ready
|
||||
```
|
||||
|
||||
Most Crossplane resources set the `Ready` condition. `Ready` represents the
|
||||
availability of the resource - whether it is creating, deleting, available,
|
||||
unavailable, binding, etc.
|
||||
|
||||
## Resource Events
|
||||
|
||||
Most Crossplane resources emit _events_ when something interesting happens. You
|
||||
can see the events associated with a resource by running `kubectl describe` -
|
||||
e.g. `kubectl describe cloudsqlinstance my-db`. You can also see all events in a
|
||||
particular namespace by running `kubectl get events`.
|
||||
|
||||
```console
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Warning CannotConnectToProvider 16s (x4 over 46s) managed/postgresqlserver.database.azure.crossplane.io cannot get referenced ProviderConfig: ProviderConfig.azure.crossplane.io "default" not found
|
||||
```
|
||||
|
||||
> Note that events are namespaced, while many Crossplane resources (XRs, etc)
|
||||
> are cluster scoped. Crossplane emits events for cluster scoped resources to
|
||||
> the 'default' namespace.
|
||||
|
||||
## Crossplane Logs
|
||||
|
||||
The next place to look to get more information or investigate a failure would be
|
||||
in the Crossplane pod logs, which should be running in the `crossplane-system`
|
||||
namespace. To get the current Crossplane logs, run the following:
|
||||
|
||||
```shell
|
||||
kubectl -n crossplane-system logs -lapp=crossplane
|
||||
```
|
||||
|
||||
> Note that Crossplane emits few logs by default - events are typically the best
|
||||
> place to look for information about what Crossplane is doing. You may need to
|
||||
> restart Crossplane with the `--debug` flag if you can't find what you're
|
||||
> looking for.
|
||||
|
||||
## Provider Logs
|
||||
|
||||
Remember that much of Crossplane's functionality is provided by providers. You
|
||||
can use `kubectl logs` to view provider logs too. By convention, they also emit
|
||||
few logs by default.
|
||||
|
||||
```shell
|
||||
kubectl -n crossplane-system logs <name-of-provider-pod>
|
||||
```
|
||||
|
||||
All providers maintained by the Crossplane community mirror Crossplane's support
|
||||
of the `--debug` flag. The easiest way to set flags on a provider is to create a
|
||||
`ControllerConfig` and reference it from the `Provider`:
|
||||
|
||||
```yaml
|
||||
apiVersion: pkg.crossplane.io/v1alpha1
|
||||
kind: ControllerConfig
|
||||
metadata:
|
||||
name: debug-config
|
||||
spec:
|
||||
args:
|
||||
- --debug
|
||||
---
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: provider-aws
|
||||
spec:
|
||||
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0
|
||||
controllerConfigRef:
|
||||
name: debug-config
|
||||
```
|
||||
|
||||
> Note that a reference to a `ControllerConfig` can be added to an already
|
||||
> installed `Provider` and it will update its `Deployment` accordingly.
|
||||
|
||||
## Compositions and composite resource definition
|
||||
|
||||
### General troubleshooting steps
|
||||
|
||||
Crossplane and its providers log most error messages to resources' event fields. Whenever your Composite Resources aren't getting provisioned, follow the following steps:
|
||||
|
||||
1. Get the events for the root resource using `kubectl describe` or `kubectl get event`
|
||||
2. If there are errors in the events, address them.
|
||||
3. If there are no errors, follow its sub-resources.
|
||||
|
||||
`kubectl get <KIND> <NAME> -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq`
|
||||
4. Repeat this process for each resource returned.
|
||||
|
||||
{{< hint "note" >}}
|
||||
The rest of this section show you how to debug issues related to compositions without using external tooling.
|
||||
If you are using ArgoCD or FluxCD with UI, you can visualize object relationships in the UI.
|
||||
You can also use the kube-lineage plugin to visualize object relationships in your terminal.
|
||||
{{< /hint >}}
|
||||
|
||||
### Examples
|
||||
|
||||
#### Composition
|
||||
<!-- vale Google.WordList = NO -->
|
||||
You deployed an example application using a claim. Kind = `ExampleApp`. Name = `example-application`.
|
||||
|
||||
|
||||
The example application never reaches available state as shown below.
|
||||
|
||||
|
||||
1. View the claim.
|
||||
|
||||
```bash
|
||||
kubectl describe exampleapp example-application
|
||||
|
||||
Status:
|
||||
Conditions:
|
||||
Last Transition Time: 2022-03-01T22:57:38Z
|
||||
Reason: Composite resource claim is waiting for composite resource to become Ready
|
||||
Status: False
|
||||
Type: Ready
|
||||
Events: <none>
|
||||
```
|
||||
|
||||
2. If the claim doesn't have errors, inspect the `.spec.resourceRef` field of the claim.
|
||||
|
||||
```bash
|
||||
kubectl get exampleapp example-application -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq
|
||||
|
||||
{
|
||||
"apiVersion": "awsblueprints.io/v1alpha1",
|
||||
"kind": "XExampleApp",
|
||||
"name": "example-application-xqlsz"
|
||||
}
|
||||
```
|
||||
3. In the preceding output, you see the cluster scoped resource for this claim. Kind = `XExampleApp` name = `example-application-xqlsz`
|
||||
4. View the cluster scoped resource's events.
|
||||
|
||||
```bash
|
||||
kubectl describe xexampleapp example-application-xqlsz
|
||||
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Normal PublishConnectionSecret 9s (x2 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully published connection details
|
||||
Normal SelectComposition 6s (x6 over 11s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully selected composition
|
||||
Warning ComposeResources 6s (x6 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io can't render composed resource from resource template at index 3: can't use dry-run create to name composed resource: an empty namespace may not be set during creation
|
||||
Normal ComposeResources 6s (x6 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully composed resources
|
||||
```
|
||||
5. You see errors in the events. it's complaining about not specifying namespace in its compositions. For this particular kind of error, you can get its sub-resources and check which one isn't created.
|
||||
|
||||
```bash
|
||||
kubectl get xexampleapp example-application-xqlsz -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq
|
||||
|
||||
[
|
||||
{
|
||||
"apiVersion": "awsblueprints.io/v1alpha1",
|
||||
"kind": "XDynamoDBTable",
|
||||
"name": "example-application-xqlsz-6j9nm"
|
||||
},
|
||||
{
|
||||
"apiVersion": "awsblueprints.io/v1alpha1",
|
||||
"kind": "XIAMPolicy",
|
||||
"name": "example-application-xqlsz-lp9wt"
|
||||
},
|
||||
{
|
||||
"apiVersion": "awsblueprints.io/v1alpha1",
|
||||
"kind": "XIAMPolicy",
|
||||
"name": "example-application-xqlsz-btwkn"
|
||||
},
|
||||
{
|
||||
"apiVersion": "awsblueprints.io/v1alpha1",
|
||||
"kind": "IRSA"
|
||||
}
|
||||
]
|
||||
```
|
||||
6. Notice the last element in the array doesn't have a name. When a resource in composition fails validation, the resource object isn't created and doesn't have a name. For this particular issue, you must specify the namespace for the IRSA resource.
|
||||
|
||||
#### Composite resource definition
|
||||
|
||||
Debugging Composite Resource Definition (XRD) is like debugging Compositions.
|
||||
|
||||
1. Get the XRD
|
||||
|
||||
```bash
|
||||
kubectl get xrd testing.awsblueprints.io
|
||||
|
||||
NAME ESTABLISHED OFFERED AGE
|
||||
testing.awsblueprints.io 66s
|
||||
```
|
||||
2. Notice its status it not established. You describe this XRD to get its events.
|
||||
|
||||
```bash
|
||||
kubectl describe xrd testing.awsblueprints.io
|
||||
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Normal ApplyClusterRoles 3m19s (x3 over 3m19s) rbac/compositeresourcedefinition.apiextensions.crossplane.io Applied RBAC ClusterRoles
|
||||
Normal RenderCRD 18s (x9 over 3m19s) defined/compositeresourcedefinition.apiextensions.crossplane.io Rendered composite resource CustomResourceDefinition
|
||||
Warning EstablishComposite 18s (x9 over 3m19s) defined/compositeresourcedefinition.apiextensions.crossplane.io can't apply rendered composite resource CustomResourceDefinition: can't create object: CustomResourceDefinition.apiextensions.k8s.io "testing.awsblueprints.io" is invalid: metadata.name: Invalid value: "testing.awsblueprints.io": must be spec.names.plural+"."+spec.group
|
||||
```
|
||||
3. You see in the events that Crossplane can't generate corresponding CRDs for this XRD. In this case, ensure the name is `spec.names.plural+"."+spec.group`
|
||||
|
||||
#### Providers
|
||||
|
||||
You can use install providers in two ways: `configuration.pkg.crossplane.io` and `provider.pkg.crossplane.io`. You can use either one to install providers with no functional differences to providers themselves.
|
||||
If you define a `configuration.pkg.crossplane.io` object, Crossplane creates a
|
||||
`provider.pkg.crossplane.io` object and manages it. Refer to [the Packages
|
||||
documentation]({{<ref "/master/concepts/packages">}})
|
||||
for more information about Crossplane Packages.
|
||||
|
||||
If you are experiencing provider issues, steps below are a good starting point.
|
||||
|
||||
1. Check the status of provider object.
|
||||
```bash
|
||||
kubectl describe provider.pkg.crossplane.io provider-aws
|
||||
|
||||
Status:
|
||||
Conditions:
|
||||
Last Transition Time: 2022-08-04T16:19:44Z
|
||||
Reason: HealthyPackageRevision
|
||||
Status: True
|
||||
Type: Healthy
|
||||
Last Transition Time: 2022-08-04T16:14:29Z
|
||||
Reason: ActivePackageRevision
|
||||
Status: True
|
||||
Type: Installed
|
||||
Current Identifier: crossplane/provider-aws:v0.29.0
|
||||
Current Revision: provider-aws-a2e16ca2fc1a
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Normal InstallPackageRevision 9m49s (x237 over 4d17h) packages/provider.pkg.crossplane.io Successfully installed package revision
|
||||
```
|
||||
In the output above you see that this provider is healthy. To get more information about this provider, you can dig deeper. The `Current Revision` field let you know of your next object to look at.
|
||||
|
||||
|
||||
2. When you create a provider object, Crossplane creates a `ProviderRevision` object based on the contents of the OCI image. In this example, you're specifying the OCI image to be `crossplane/provider-aws:v0.29.0`. This image contains a YAML file which defines Kubernetes objects such as Deployment, ServiceAccount, and CRDs.
|
||||
The `ProviderRevision` object creates resources necessary for a provider to function based on the contents of the YAML file. To inspect what's deployed as part of the provider package, you inspect the ProviderRevision object. The `Current Revision` field above indicates which ProviderRevision object this provider uses.
|
||||
|
||||
```bash
|
||||
kubectl get providerrevision provider-aws-a2e16ca2fc1a
|
||||
|
||||
NAME HEALTHY REVISION IMAGE STATE DEP-FOUND DEP-INSTALLED AGE
|
||||
provider-aws-a2e16ca2fc1a True 1 crossplane/provider-aws:v0.29.0 Active 19d
|
||||
```
|
||||
|
||||
When you describe the object, you find all CRDs managed by this object.
|
||||
|
||||
```bash
|
||||
kubectl describe providerrevision provider-aws-a2e16ca2fc1a
|
||||
|
||||
Status:
|
||||
Controller Ref:
|
||||
Name: provider-aws-a2e16ca2fc1a
|
||||
Object Refs:
|
||||
API Version: apiextensions.k8s.io/v1
|
||||
Kind: CustomResourceDefinition
|
||||
Name: natgateways.ec2.aws.crossplane.io
|
||||
UID: 5c36d1bc-61b8-44f8-bca0-47e368af87a9
|
||||
....
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Normal SyncPackage 22m (x369 over 4d18h) packages/providerrevision.pkg.crossplane.io Successfully configured package revision
|
||||
Normal BindClusterRole 15m (x348 over 4d18h) rbac/providerrevision.pkg.crossplane.io Bound system ClusterRole to provider ServiceAccount
|
||||
Normal ApplyClusterRoles 15m (x364 over 4d18h) rbac/providerrevision.pkg.crossplane.io Applied RBAC ClusterRoles
|
||||
```
|
||||
|
||||
The event field also indicates any issues that may have occurred during this process.
|
||||
<!-- vale Google.WordList = YES -->
|
||||
3. If you don't see any errors in the event field above, you should check if Crossplane provisioned deployments and their status.
|
||||
|
||||
```bash
|
||||
kubectl get deployment -n crossplane-system
|
||||
|
||||
NAME READY UP-TO-DATE AVAILABLE AGE
|
||||
crossplane 1/1 1 1 105d
|
||||
crossplane-rbac-manager 1/1 1 1 105d
|
||||
provider-aws-a2e16ca2fc1a 1/1 1 1 19d
|
||||
|
||||
kubectl get pods -n crossplane-system
|
||||
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
crossplane-54db688c8d-qng6b 2/2 Running 0 4d19h
|
||||
crossplane-rbac-manager-5776c9fbf4-wn5rj 1/1 Running 0 4d19h
|
||||
provider-aws-a2e16ca2fc1a-776769ccbd-4dqml 1/1 Running 0 4d23h
|
||||
```
|
||||
If there are any pods failing, check its logs and remedy the problem.
|
||||
|
||||
|
||||
## Pausing Crossplane
|
||||
|
||||
Sometimes, for example when you encounter a bug, it can be useful to pause
|
||||
Crossplane if you want to stop it from actively attempting to manage your
|
||||
resources. To pause Crossplane without deleting all of its resources, run the
|
||||
following command to simply scale down its deployment:
|
||||
|
||||
```bash
|
||||
kubectl -n crossplane-system scale --replicas=0 deployment/crossplane
|
||||
```
|
||||
|
||||
Once you have been able to rectify the problem or smooth things out, you can
|
||||
unpause Crossplane simply by scaling its deployment back up:
|
||||
|
||||
```bash
|
||||
kubectl -n crossplane-system scale --replicas=1 deployment/crossplane
|
||||
```
|
||||
|
||||
## Pausing Providers
|
||||
|
||||
Providers can also be paused when troubleshooting an issue or orchestrating a
|
||||
complex migration of resources. Creating and referencing a `ControllerConfig` is
|
||||
the easiest way to scale down a provider, and the `ControllerConfig` can be
|
||||
modified or the reference can be removed to scale it back up:
|
||||
|
||||
```yaml
|
||||
apiVersion: pkg.crossplane.io/v1alpha1
|
||||
kind: ControllerConfig
|
||||
metadata:
|
||||
name: scale-config
|
||||
spec:
|
||||
replicas: 0
|
||||
---
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: provider-aws
|
||||
spec:
|
||||
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0
|
||||
controllerConfigRef:
|
||||
name: scale-config
|
||||
```
|
||||
|
||||
> Note that a reference to a `ControllerConfig` can be added to an already
|
||||
> installed `Provider` and it will update its `Deployment` accordingly.
|
||||
|
||||
## Deleting When a Resource Hangs
|
||||
|
||||
The resources that Crossplane manages will automatically be cleaned up so as not
|
||||
to leave anything running behind. This is accomplished by using finalizers, but
|
||||
in certain scenarios the finalizer can prevent the Kubernetes object from
|
||||
getting deleted.
|
||||
|
||||
To deal with this, we essentially want to patch the object to remove its
|
||||
finalizer, which will then allow it to be deleted completely. Note that this
|
||||
won't necessarily delete the external resource that Crossplane was managing, so
|
||||
you will want to go to your cloud provider's console and look there for any
|
||||
lingering resources to clean up.
|
||||
|
||||
In general, a finalizer can be removed from an object with this command:
|
||||
|
||||
```shell
|
||||
kubectl patch <resource-type> <resource-name> -p '{"metadata":{"finalizers": []}}' --type=merge
|
||||
```
|
||||
|
||||
For example, for a `CloudSQLInstance` managed resource (`database.gcp.crossplane.io`) named
|
||||
`my-db`, you can remove its finalizer with:
|
||||
|
||||
```shell
|
||||
kubectl patch cloudsqlinstance my-db -p '{"metadata":{"finalizers": []}}' --type=merge
|
||||
```
|
||||
|
||||
## Tips, Tricks, and Troubleshooting
|
||||
|
||||
In this section we'll cover some common tips, tricks, and troubleshooting steps
|
||||
for working with Composite Resources. If you're trying to track down why your
|
||||
Composite Resources aren't working the [Troubleshooting][trouble-ref] page also
|
||||
has some useful information.
|
||||
|
||||
### Troubleshooting Claims and XRs
|
||||
|
||||
Crossplane relies heavily on status conditions and events for troubleshooting.
|
||||
You can see both using `kubectl describe` - for example:
|
||||
|
||||
```console
|
||||
# Describe the PostgreSQLInstance claim named my-db
|
||||
kubectl describe postgresqlinstance.database.example.org my-db
|
||||
```
|
||||
|
||||
Per Kubernetes convention, Crossplane keeps errors close to the place they
|
||||
happen. This means that if your claim is not becoming ready due to an issue with
|
||||
your `Composition` or with a composed resource you'll need to "follow the
|
||||
references" to find out why. Your claim will only tell you that the XR is not
|
||||
yet ready.
|
||||
|
||||
To follow the references:
|
||||
|
||||
1. Find your XR by running `kubectl describe` on your claim and looking for its
|
||||
"Resource Ref" (aka `spec.resourceRef`).
|
||||
1. Run `kubectl describe` on your XR. This is where you'll find out about issues
|
||||
with the `Composition` you're using, if any.
|
||||
1. If there are no issues but your XR doesn't seem to be becoming ready, take a
|
||||
look for the "Resource Refs" (or `spec.resourceRefs`) to find your composed
|
||||
resources.
|
||||
1. Run `kubectl describe` on each referenced composed resource to determine
|
||||
whether it is ready and what issues, if any, it is encountering.
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Named Links -->
|
||||
[Requested Resource Not Found]: #requested-resource-not-found
|
||||
[install Crossplane CLI]: "../getting-started/install-configure"
|
||||
[Resource Status and Conditions]: #resource-status-and-conditions
|
||||
[Resource Events]: #resource-events
|
||||
[Crossplane Logs]: #crossplane-logs
|
||||
[Provider Logs]: #provider-logs
|
||||
[Pausing Crossplane]: #pausing-crossplane
|
||||
[Pausing Providers]: #pausing-providers
|
||||
[Deleting When a Resource Hangs]: #deleting-when-a-resource-hangs
|
||||
[Installing Crossplane Package]: #installing-crossplane-package
|
||||
[Crossplane package]: /master/concepts/packages/
|
||||
[Handling Crossplane Package Dependency]: #handling-crossplane-package-dependency
|
||||
[semver spec]: https://github.com/Masterminds/semver#basic-comparisons
|
||||
|
||||
|
|
@ -0,0 +1,838 @@
|
|||
---
|
||||
title: Write a Composition Function in Go
|
||||
state: beta
|
||||
alphaVersion: "1.11"
|
||||
betaVersion: "1.14"
|
||||
weight: 80
|
||||
description: "Composition functions allow you to template resources using Go"
|
||||
---
|
||||
|
||||
Composition functions (or just functions, for short) are custom programs that
|
||||
template Crossplane resources. Crossplane calls composition functions to
|
||||
determine what resources it should create when you create a composite resource
|
||||
(XR). Read the
|
||||
[concepts]{{<ref "../concepts/composition-functions" >}}
|
||||
page to learn more about composition functions.
|
||||
|
||||
You can write a function to template resources using a general purpose
|
||||
programming language. Using a general purpose programming language allows a
|
||||
function to use advanced logic to template resources, like loops and
|
||||
conditionals. This guide explains how to write a composition function in
|
||||
[Go](https://go.dev).
|
||||
|
||||
{{< hint "important" >}}
|
||||
It helps to be familiar with
|
||||
[how composition functions work]{{<ref "../concepts/composition-functions#how-composition-functions-work" >}}
|
||||
before following this guide.
|
||||
{{< /hint >}}
|
||||
|
||||
## Understand the steps
|
||||
|
||||
This guide covers writing a composition function for an
|
||||
{{<hover label="xr" line="2">}}XBuckets{{</hover>}} composite resource (XR).
|
||||
|
||||
```yaml {label="xr"}
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBuckets
|
||||
metadata:
|
||||
name: example-buckets
|
||||
spec:
|
||||
region: us-east-2
|
||||
names:
|
||||
- crossplane-functions-example-a
|
||||
- crossplane-functions-example-b
|
||||
- crossplane-functions-example-c
|
||||
```
|
||||
|
||||
<!-- vale gitlab.FutureTense = NO -->
|
||||
<!--
|
||||
This section is setting the stage for future sections. It doesn't make sense to
|
||||
refer to the function in the present tense, because it doesn't exist yet.
|
||||
-->
|
||||
An `XBuckets` XR has a region and an array of bucket names. The function will
|
||||
create an Amazon Web Services (AWS) S3 bucket for each entry in the names array.
|
||||
<!-- vale gitlab.FutureTense = YES -->
|
||||
|
||||
To write a function in Go:
|
||||
|
||||
1. [Install the tools you need to write the function](#install-the-tools-you-need-to-write-the-function)
|
||||
1. [Initialize the function from a template](#initialize-the-function-from-a-template)
|
||||
1. [Edit the template to add the function's logic](#edit-the-template-to-add-the-functions-logic)
|
||||
1. [Test the function end-to-end](#test-the-function-end-to-end)
|
||||
1. [Build and push the function to a package repository](#build-and-push-the-function-to-a-package-registry)
|
||||
|
||||
This guide covers each of these steps in detail.
|
||||
|
||||
## Install the tools you need to write the function
|
||||
|
||||
To write a function in Go you need:
|
||||
|
||||
* [Go](https://go.dev/dl/) v1.21 or newer. The guide uses Go v1.21.
|
||||
* [Docker Engine](https://docs.docker.com/engine/). This guide uses Engine v24.
|
||||
* The [Crossplane CLI]({{<ref "../cli" >}}) v1.14 or newer. This guide uses Crossplane
|
||||
CLI v1.14.
|
||||
|
||||
{{<hint "note">}}
|
||||
You don't need access to a Kubernetes cluster or a Crossplane control plane to
|
||||
build or test a composition function.
|
||||
{{</hint>}}
|
||||
|
||||
## Initialize the function from a template
|
||||
|
||||
Use the `crossplane beta xpkg init` command to initialize a new function. When
|
||||
you run this command it initializes your function using
|
||||
[a GitHub repository](https://github.com/crossplane/function-template-go)
|
||||
as a template.
|
||||
|
||||
```shell {copy-lines=1}
|
||||
crossplane beta xpkg init function-xbuckets function-template-go -d function-xbuckets
|
||||
Initialized package "function-xbuckets" in directory "/home/negz/control/negz/function-xbuckets" from https://github.com/crossplane/function-template-go/tree/91a1a5eed21964ff98966d72cc6db6f089ad63f4 (main)
|
||||
```
|
||||
|
||||
The `crossplane beta init xpkg` command creates a directory named
|
||||
`function-xbuckets`. When you run the command the new directory should look like
|
||||
this:
|
||||
|
||||
```shell {copy-lines=1}
|
||||
ls function-xbuckets
|
||||
Dockerfile fn.go fn_test.go go.mod go.sum input/ LICENSE main.go package/ README.md renovate.json
|
||||
```
|
||||
|
||||
The `fn.go` file is where you add the function's code. It's useful to know about
|
||||
some other files in the template:
|
||||
|
||||
* `main.go` runs the function. You don't need to edit `main.go`.
|
||||
* `Dockerfile` builds the function runtime. You don't need to edit `Dockerfile`.
|
||||
* The `input` directory defines the function's input type.
|
||||
* The `package` directory contains metadata used to build the function package.
|
||||
|
||||
{{<hint "tip">}}
|
||||
<!-- vale gitlab.FutureTense = NO -->
|
||||
<!--
|
||||
This tip talks about future plans for Crossplane.
|
||||
-->
|
||||
In v1.14 of the Crossplane CLI `crossplane beta xpkg init` just clones a
|
||||
template GitHub repository. A future CLI release will automate tasks like
|
||||
replacing the template name with the new function's name. See Crossplane issue
|
||||
[#4941](https://github.com/crossplane/crossplane/issues/4941) for details.
|
||||
<!-- vale gitlab.FutureTense = YES -->
|
||||
{{</hint>}}
|
||||
|
||||
You must make some changes before you start adding code:
|
||||
|
||||
* Edit `package/crossplane.yaml` to change the package's name.
|
||||
* Edit `go.mod` to change the Go module's name.
|
||||
|
||||
Name your package `function-xbuckets`.
|
||||
|
||||
The name of your module depends on where you want to keep your function code. If
|
||||
you push Go code to GitHub, you can use your GitHub username. For example
|
||||
`module github.com/negz/function-xbuckets`.
|
||||
|
||||
The function in this guide doesn't use an input type. For this function you
|
||||
should delete the `input` and `package/input` directories.
|
||||
|
||||
The `input` directory defines a Go struct that a function can use to take input,
|
||||
using the `input` field from a Composition. The
|
||||
[composition functions]{{<ref "../concepts/composition-functions" >}}
|
||||
documentation explains how to pass an input to a composition function.
|
||||
|
||||
The `package/input` directory contains an OpenAPI schema generated from the
|
||||
structs in the `input` directory.
|
||||
|
||||
{{<hint "tip">}}
|
||||
If you're writing a function that uses an input, edit the input to meet your
|
||||
function's requirements.
|
||||
|
||||
Change the input's kind and API group. Don't use `Input` and
|
||||
`template.fn.crossplane.io`. Instead use something meaningful to your function.
|
||||
|
||||
When you edit files under the `input` directory you must update some generated
|
||||
files by running `go generate`. See `input/generate.go` for details.
|
||||
|
||||
```shell
|
||||
go generate ./...
|
||||
```
|
||||
{{</hint>}}
|
||||
|
||||
## Edit the template to add the function's logic
|
||||
|
||||
You add your function's logic to the
|
||||
{{<hover label="hello-world" line="1">}}RunFunction{{</hover>}}
|
||||
method in `fn.go`. When you first open the file it contains a "hello world"
|
||||
function.
|
||||
|
||||
```go {label="hello-world"}
|
||||
func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) {
|
||||
f.log.Info("Running Function", "tag", req.GetMeta().GetTag())
|
||||
|
||||
rsp := response.To(req, response.DefaultTTL)
|
||||
|
||||
in := &v1beta1.Input{}
|
||||
if err := request.GetInput(req, in); err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot get Function input from %T", req))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
response.Normalf(rsp, "I was run with input %q", in.Example)
|
||||
return rsp, nil
|
||||
}
|
||||
```
|
||||
|
||||
All Go composition functions have a `RunFunction` method. Crossplane passes
|
||||
everything the function needs to run in a
|
||||
{{<hover label="hello-world" line="1">}}RunFunctionRequest{{</hover>}} struct.
|
||||
|
||||
The function tells Crossplane what resources it should compose by returning a
|
||||
{{<hover label="hello-world" line="13">}}RunFunctionResponse{{</hover>}} struct.
|
||||
|
||||
{{<hint "tip">}}
|
||||
Crossplane generates the `RunFunctionRequest` and `RunFunctionResponse` structs
|
||||
using [Protocol Buffers](http://protobuf.dev). You can find detailed schemas for
|
||||
`RunFunctionRequest` and `RunFunctionResponse` in the
|
||||
[Buf Schema Registry](https://buf.build/crossplane/crossplane/docs/main:apiextensions.fn.proto.v1beta1).
|
||||
{{</hint>}}
|
||||
|
||||
Edit the `RunFunction` method to replace it with this code.
|
||||
|
||||
```go {hl_lines="4-56"}
|
||||
func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) {
|
||||
rsp := response.To(req, response.DefaultTTL)
|
||||
|
||||
xr, err := request.GetObservedCompositeResource(req)
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot get observed composite resource from %T", req))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
region, err := xr.Resource.GetString("spec.region")
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot read spec.region field of %s", xr.Resource.GetKind()))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
names, err := xr.Resource.GetStringArray("spec.names")
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot read spec.names field of %s", xr.Resource.GetKind()))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
desired, err := request.GetDesiredComposedResources(req)
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot get desired resources from %T", req))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
_ = v1beta1.AddToScheme(composed.Scheme)
|
||||
|
||||
for _, name := range names {
|
||||
b := &v1beta1.Bucket{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"crossplane.io/external-name": name,
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.BucketSpec{
|
||||
ForProvider: v1beta1.BucketParameters{
|
||||
Region: ptr.To[string](region),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cd, err := composed.From(b)
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot convert %T to %T", b, &composed.Unstructured{}))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
desired[resource.Name("xbuckets-"+name)] = &resource.DesiredComposed{Resource: cd}
|
||||
}
|
||||
|
||||
if err := response.SetDesiredComposedResources(rsp, desired); err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot set desired composed resources in %T", rsp))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
return rsp, nil
|
||||
}
|
||||
```
|
||||
|
||||
Expand the below block to view the full `fn.go`, including imports and
|
||||
commentary explaining the function's logic.
|
||||
|
||||
{{<expand "The full fn.go file" >}}
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
"github.com/upbound/provider-aws/apis/s3/v1beta1"
|
||||
|
||||
"github.com/crossplane/function-sdk-go/errors"
|
||||
"github.com/crossplane/function-sdk-go/logging"
|
||||
fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1"
|
||||
"github.com/crossplane/function-sdk-go/request"
|
||||
"github.com/crossplane/function-sdk-go/resource"
|
||||
"github.com/crossplane/function-sdk-go/resource/composed"
|
||||
"github.com/crossplane/function-sdk-go/response"
|
||||
)
|
||||
|
||||
// Function returns whatever response you ask it to.
|
||||
type Function struct {
|
||||
fnv1beta1.UnimplementedFunctionRunnerServiceServer
|
||||
|
||||
log logging.Logger
|
||||
}
|
||||
|
||||
// RunFunction observes an XBuckets composite resource (XR). It adds an S3
|
||||
// bucket to the desired state for every entry in the XR's spec.names array.
|
||||
func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) {
|
||||
f.log.Info("Running Function", "tag", req.GetMeta().GetTag())
|
||||
|
||||
// Create a response to the request. This copies the desired state and
|
||||
// pipeline context from the request to the response.
|
||||
rsp := response.To(req, response.DefaultTTL)
|
||||
|
||||
// Read the observed XR from the request. Most functions use the observed XR
|
||||
// to add desired managed resources.
|
||||
xr, err := request.GetObservedCompositeResource(req)
|
||||
if err != nil {
|
||||
// If the function can't read the XR, the request is malformed. This
|
||||
// should never happen. The function returns a fatal result. This tells
|
||||
// Crossplane to stop running functions and return an error.
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot get observed composite resource from %T", req))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
// Create an updated logger with useful information about the XR.
|
||||
log := f.log.WithValues(
|
||||
"xr-version", xr.Resource.GetAPIVersion(),
|
||||
"xr-kind", xr.Resource.GetKind(),
|
||||
"xr-name", xr.Resource.GetName(),
|
||||
)
|
||||
|
||||
// Get the region from the XR. The XR has getter methods like GetString,
|
||||
// GetBool, etc. You can use them to get values by their field path.
|
||||
region, err := xr.Resource.GetString("spec.region")
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot read spec.region field of %s", xr.Resource.GetKind()))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
// Get the array of bucket names from the XR.
|
||||
names, err := xr.Resource.GetStringArray("spec.names")
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot read spec.names field of %s", xr.Resource.GetKind()))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
// Get all desired composed resources from the request. The function will
|
||||
// update this map of resources, then save it. This get, update, set pattern
|
||||
// ensures the function keeps any resources added by other functions.
|
||||
desired, err := request.GetDesiredComposedResources(req)
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot get desired resources from %T", req))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
// Add v1beta1 types (including Bucket) to the composed resource scheme.
|
||||
// composed.From uses this to automatically set apiVersion and kind.
|
||||
_ = v1beta1.AddToScheme(composed.Scheme)
|
||||
|
||||
// Add a desired S3 bucket for each name.
|
||||
for _, name := range names {
|
||||
// One advantage of writing a function in Go is strong typing. The
|
||||
// function can import and use managed resource types from the provider.
|
||||
b := &v1beta1.Bucket{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
// Set the external name annotation to the desired bucket name.
|
||||
// This controls what the bucket will be named in AWS.
|
||||
Annotations: map[string]string{
|
||||
"crossplane.io/external-name": name,
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.BucketSpec{
|
||||
ForProvider: v1beta1.BucketParameters{
|
||||
// Set the bucket's region to the value read from the XR.
|
||||
Region: ptr.To[string](region),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Convert the bucket to the unstructured resource data format the SDK
|
||||
// uses to store desired composed resources.
|
||||
cd, err := composed.From(b)
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot convert %T to %T", b, &composed.Unstructured{}))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
// Add the bucket to the map of desired composed resources. It's
|
||||
// important that the function adds the same bucket every time it's
|
||||
// called. It's also important that the bucket is added with the same
|
||||
// resource.Name every time it's called. The function prefixes the name
|
||||
// with "xbuckets-" to avoid collisions with any other composed
|
||||
// resources that might be in the desired resources map.
|
||||
desired[resource.Name("xbuckets-"+name)] = &resource.DesiredComposed{Resource: cd}
|
||||
}
|
||||
|
||||
// Finally, save the updated desired composed resources to the response.
|
||||
if err := response.SetDesiredComposedResources(rsp, desired); err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot set desired composed resources in %T", rsp))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
// Log what the function did. This will only appear in the function's pod
|
||||
// logs. A function can use response.Normal and response.Warning to emit
|
||||
// Kubernetes events associated with the XR it's operating on.
|
||||
log.Info("Added desired buckets", "region", region, "count", len(names))
|
||||
|
||||
return rsp, nil
|
||||
}
|
||||
```
|
||||
{{</expand>}}
|
||||
|
||||
This code:
|
||||
|
||||
1. Gets the observed composite resource from the `RunFunctionRequest`.
|
||||
1. Gets the region and bucket names from the observed composite resource.
|
||||
1. Adds one desired S3 bucket for each bucket name.
|
||||
1. Returns the desired S3 buckets in a `RunFunctionResponse`.
|
||||
|
||||
The code uses the `v1beta1.Bucket` type from
|
||||
[Upbound's AWS S3 provider](https://github.com/upbound/provider-aws). One
|
||||
advantage of writing a function in Go is that you can compose resources using
|
||||
the same strongly typed structs Crossplane uses in its providers.
|
||||
|
||||
You must get the AWS Provider Go module to use this type:
|
||||
|
||||
```shell
|
||||
go get github.com/upbound/provider-aws@v0.43.0
|
||||
```
|
||||
|
||||
Crossplane provides a
|
||||
[software development kit](https://github.com/crossplane/function-sdk-go) (SDK)
|
||||
for writing composition functions in [Go](https://go.dev). This function uses
|
||||
utilities from the SDK. In particular the `request` and `response` packages make
|
||||
working with the `RunFunctionRequest` and `RunFunctionResponse` types easier.
|
||||
|
||||
{{<hint "tip">}}
|
||||
Read the
|
||||
[Go package documentation](https://pkg.go.dev/github.com/crossplane/function-sdk-go)
|
||||
for the SDK.
|
||||
{{</hint>}}
|
||||
|
||||
## Test the function end-to-end
|
||||
|
||||
Test your function by adding unit tests, and by using the `crossplane beta
|
||||
render` command.
|
||||
|
||||
Go has rich support for unit testing. When you initialize a function from the
|
||||
template it adds some unit tests to `fn_test.go`. These tests follow Go's
|
||||
[recommendations](https://github.com/golang/go/wiki/TestComments). They use only
|
||||
[`pkg/testing`](https://pkg.go.dev/testing) from the Go standard library and
|
||||
[`google/go-cmp`](https://pkg.go.dev/github.com/google/go-cmp/cmp).
|
||||
|
||||
To add test cases, update the `cases` map in `TestRunFunction`. Expand the below
|
||||
block to view the full `fn_test.go` file for the function.
|
||||
|
||||
{{<expand "The full fn_test.go file" >}}
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/logging"
|
||||
|
||||
fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1"
|
||||
"github.com/crossplane/function-sdk-go/resource"
|
||||
)
|
||||
|
||||
func TestRunFunction(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *fnv1beta1.RunFunctionRequest
|
||||
}
|
||||
type want struct {
|
||||
rsp *fnv1beta1.RunFunctionResponse
|
||||
err error
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
reason string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"AddTwoBuckets": {
|
||||
reason: "The Function should add two buckets to the desired composed resources",
|
||||
args: args{
|
||||
req: &fnv1beta1.RunFunctionRequest{
|
||||
Observed: &fnv1beta1.State{
|
||||
Composite: &fnv1beta1.Resource{
|
||||
// MustStructJSON is a handy way to provide mock
|
||||
// resources.
|
||||
Resource: resource.MustStructJSON(`{
|
||||
"apiVersion": "example.crossplane.io/v1alpha1",
|
||||
"kind": "XBuckets",
|
||||
"metadata": {
|
||||
"name": "test"
|
||||
},
|
||||
"spec": {
|
||||
"region": "us-east-2",
|
||||
"names": [
|
||||
"test-bucket-a",
|
||||
"test-bucket-b"
|
||||
]
|
||||
}
|
||||
}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
rsp: &fnv1beta1.RunFunctionResponse{
|
||||
Meta: &fnv1beta1.ResponseMeta{Ttl: durationpb.New(60 * time.Second)},
|
||||
Desired: &fnv1beta1.State{
|
||||
Resources: map[string]*fnv1beta1.Resource{
|
||||
"xbuckets-test-bucket-a": {Resource: resource.MustStructJSON(`{
|
||||
"apiVersion": "s3.aws.upbound.io/v1beta1",
|
||||
"kind": "Bucket",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"crossplane.io/external-name": "test-bucket-a"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"forProvider": {
|
||||
"region": "us-east-2"
|
||||
}
|
||||
}
|
||||
}`)},
|
||||
"xbuckets-test-bucket-b": {Resource: resource.MustStructJSON(`{
|
||||
"apiVersion": "s3.aws.upbound.io/v1beta1",
|
||||
"kind": "Bucket",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"crossplane.io/external-name": "test-bucket-b"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"forProvider": {
|
||||
"region": "us-east-2"
|
||||
}
|
||||
}
|
||||
}`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
f := &Function{log: logging.NewNopLogger()}
|
||||
rsp, err := f.RunFunction(tc.args.ctx, tc.args.req)
|
||||
|
||||
if diff := cmp.Diff(tc.want.rsp, rsp, protocmp.Transform()); diff != "" {
|
||||
t.Errorf("%s\nf.RunFunction(...): -want rsp, +got rsp:\n%s", tc.reason, diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" {
|
||||
t.Errorf("%s\nf.RunFunction(...): -want err, +got err:\n%s", tc.reason, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
{{</expand>}}
|
||||
|
||||
Run the unit tests using the `go test` command:
|
||||
|
||||
```shell
|
||||
go test -v -cover .
|
||||
=== RUN TestRunFunction
|
||||
=== RUN TestRunFunction/AddTwoBuckets
|
||||
--- PASS: TestRunFunction (0.00s)
|
||||
--- PASS: TestRunFunction/AddTwoBuckets (0.00s)
|
||||
PASS
|
||||
coverage: 52.6% of statements
|
||||
ok github.com/negz/function-xbuckets 0.016s coverage: 52.6% of statements
|
||||
```
|
||||
|
||||
You can preview the output of a Composition that uses this function using
|
||||
the Crossplane CLI. You don't need a Crossplane control plane to do this.
|
||||
|
||||
Create a directory under `function-xbuckets` named `example` and create
|
||||
Composite Resource, Composition and Function YAML files.
|
||||
|
||||
Expand the following block to see example files.
|
||||
|
||||
{{<expand "The xr.yaml, composition.yaml and function.yaml files">}}
|
||||
|
||||
You can recreate the output below using by running `crossplane beta render` with
|
||||
these files.
|
||||
|
||||
The `xr.yaml` file contains the composite resource to render:
|
||||
|
||||
```yaml
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBuckets
|
||||
metadata:
|
||||
name: example-buckets
|
||||
spec:
|
||||
region: us-east-2
|
||||
names:
|
||||
- crossplane-functions-example-a
|
||||
- crossplane-functions-example-b
|
||||
- crossplane-functions-example-c
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
The `composition.yaml` file contains the Composition to use to render the
|
||||
composite resource:
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: create-buckets
|
||||
spec:
|
||||
compositeTypeRef:
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBuckets
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: create-buckets
|
||||
functionRef:
|
||||
name: function-xbuckets
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
The `functions.yaml` file contains the Functions the Composition references in
|
||||
its pipeline steps:
|
||||
|
||||
```yaml
|
||||
apiVersion: pkg.crossplane.io/v1beta1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: function-xbuckets
|
||||
annotations:
|
||||
render.crossplane.io/runtime: Development
|
||||
spec:
|
||||
# The CLI ignores this package when using the Development runtime.
|
||||
# You can set it to any value.
|
||||
package: xpkg.upbound.io/negz/function-xbuckets:v0.1.0
|
||||
```
|
||||
{{</expand>}}
|
||||
|
||||
The Function in `functions.yaml` uses the
|
||||
{{<hover label="development" line="6">}}Development{{</hover>}}
|
||||
runtime. This tells `crossplane beta render` that your function is running
|
||||
locally. It connects to your locally running function instead of using Docker to
|
||||
pull and run the function.
|
||||
|
||||
```yaml {label="development"}
|
||||
apiVersion: pkg.crossplane.io/v1beta1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: function-xbuckets
|
||||
annotations:
|
||||
render.crossplane.io/runtime: Development
|
||||
```
|
||||
|
||||
Use `go run` to run your function locally.
|
||||
|
||||
```shell {label="run"}
|
||||
go run . --insecure --debug
|
||||
```
|
||||
|
||||
{{<hint "warning">}}
|
||||
The {{<hover label="run" line="1">}}insecure{{</hover>}} flag tells the function
|
||||
to run without encryption or authentication. Only use it during testing and
|
||||
development.
|
||||
{{</hint>}}
|
||||
|
||||
In a separate terminal, run `crossplane beta render`.
|
||||
|
||||
```shell
|
||||
crossplane beta render xr.yaml composition.yaml functions.yaml
|
||||
```
|
||||
|
||||
This command calls your function. In the terminal where your function is running
|
||||
you should now see log output:
|
||||
|
||||
```shell
|
||||
go run . --insecure --debug
|
||||
2023-10-31T16:17:32.158-0700 INFO function-xbuckets/fn.go:29 Running Function {"tag": ""}
|
||||
2023-10-31T16:17:32.159-0700 INFO function-xbuckets/fn.go:125 Added desired buckets {"xr-version": "example.crossplane.io/v1", "xr-kind": "XBuckets", "xr-name": "example-buckets", "region": "us-east-2", "count": 3}
|
||||
```
|
||||
|
||||
The `crossplane beta render` command prints the desired resources the function
|
||||
returns.
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBuckets
|
||||
metadata:
|
||||
name: example-buckets
|
||||
---
|
||||
apiVersion: s3.aws.upbound.io/v1beta1
|
||||
kind: Bucket
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-b
|
||||
crossplane.io/external-name: crossplane-functions-example-b
|
||||
generateName: example-buckets-
|
||||
labels:
|
||||
crossplane.io/composite: example-buckets
|
||||
ownerReferences:
|
||||
# Omitted for brevity
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-2
|
||||
---
|
||||
apiVersion: s3.aws.upbound.io/v1beta1
|
||||
kind: Bucket
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-c
|
||||
crossplane.io/external-name: crossplane-functions-example-c
|
||||
generateName: example-buckets-
|
||||
labels:
|
||||
crossplane.io/composite: example-buckets
|
||||
ownerReferences:
|
||||
# Omitted for brevity
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-2
|
||||
---
|
||||
apiVersion: s3.aws.upbound.io/v1beta1
|
||||
kind: Bucket
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-a
|
||||
crossplane.io/external-name: crossplane-functions-example-a
|
||||
generateName: example-buckets-
|
||||
labels:
|
||||
crossplane.io/composite: example-buckets
|
||||
ownerReferences:
|
||||
# Omitted for brevity
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-2
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
Read the composition functions documentation to learn more about
|
||||
[testing composition functions]({{< ref "../concepts/composition-functions#test-a-composition-that-uses-functions" >}}).
|
||||
{{</hint>}}
|
||||
|
||||
## Build and push the function to a package registry
|
||||
|
||||
You build a function in two stages. First you build the function's runtime. This
|
||||
is the Open Container Initiative (OCI) image Crossplane uses to run your
|
||||
function. You then embed that runtime in a package, and push it to a package
|
||||
registry. The Crossplane CLI uses `xpkg.upbound.io` as its default package
|
||||
registry.
|
||||
|
||||
A function supports a single platform, like `linux/amd64`, by default. You can
|
||||
support multiple platforms by building a runtime and package for each platform,
|
||||
then pushing all the packages to a single tag in the registry.
|
||||
|
||||
Pushing your function to a registry allows you to use your function in a
|
||||
Crossplane control plane. See the
|
||||
[composition functions documentation]{{<ref "../concepts/composition-functions" >}}.
|
||||
to learn how to use a function in a control plane.
|
||||
|
||||
Use Docker to build a runtime for each platform.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
docker build . --quiet --platform=linux/amd64 --tag runtime-amd64
|
||||
sha256:fdf40374cc6f0b46191499fbc1dbbb05ddb76aca854f69f2912e580cfe624b4b
|
||||
```
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
docker build . --quiet --platform=linux/arm64 --tag runtime-arm64
|
||||
sha256:cb015ceabf46d2a55ccaeebb11db5659a2fb5e93de36713364efcf6d699069af
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
You can use whatever tag you want. There's no need to push the runtime images to
|
||||
a registry. The tag is only used to tell `crossplane xpkg build` what runtime to
|
||||
embed.
|
||||
{{</hint>}}
|
||||
|
||||
Use the Crossplane CLI to build a package for each platform. Each package embeds
|
||||
a runtime image.
|
||||
|
||||
The {{<hover label="build" line="2">}}--package-root{{</hover>}} flag specifies
|
||||
the `package` directory, which contains `crossplane.yaml`. This includes
|
||||
metadata about the package.
|
||||
|
||||
The {{<hover label="build" line="3">}}--embed-runtime-image{{</hover>}} flag
|
||||
specifies the runtime image tag built using Docker.
|
||||
|
||||
The {{<hover label="build" line="4">}}--package-file{{</hover>}} flag specifies
|
||||
specifies where to write the package file to disk. Crossplane package files use
|
||||
the extension `.xpkg`.
|
||||
|
||||
```shell {label="build"}
|
||||
crossplane xpkg build \
|
||||
--package-root=package \
|
||||
--embed-runtime-image=runtime-amd64 \
|
||||
--package-file=function-amd64.xpkg
|
||||
```
|
||||
|
||||
```shell
|
||||
crossplane xpkg build \
|
||||
--package-root=package \
|
||||
--embed-runtime-image=runtime-arm64 \
|
||||
--package-file=function-arm64.xpkg
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
Crossplane packages are special OCI images. Read more about packages in the
|
||||
[packages documentation]({{< ref "../concepts/packages" >}}).
|
||||
{{</hint>}}
|
||||
|
||||
Push both package files to a registry. Pushing both files to one tag in the
|
||||
registry creates a
|
||||
[multi-platform](https://docs.docker.com/build/building/multi-platform/)
|
||||
package that runs on both `linux/arm64` and `linux/amd64` hosts.
|
||||
|
||||
```shell
|
||||
crossplane xpkg push \
|
||||
--package-files=function-amd64.xpkg,function-arm64.xpkg \
|
||||
negz/function-xbuckets:v0.1.0
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
If you push the function to a GitHub repository the template automatically sets
|
||||
up continuous integration (CI) using
|
||||
[GitHub Actions](https://github.com/features/actions). The CI workflow will
|
||||
lint, test, and build your function. You can see how the template configures CI
|
||||
by reading `.github/workflows/ci.yaml`.
|
||||
|
||||
The CI workflow can automatically push packages to `xpkg.upbound.io`. For this
|
||||
to work you must create a repository at https://marketplace.upbound.io. Give the
|
||||
CI workflow access to push to the Marketplace by creating an API token and
|
||||
[adding it to your repository](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository).
|
||||
Save your API token access ID as a secret named `XPKG_ACCESS_ID` and your API
|
||||
token as a secret named `XPKG_TOKEN`.
|
||||
{{</hint>}}
|
|
@ -0,0 +1,745 @@
|
|||
---
|
||||
title: Write a Composition Function in Python
|
||||
state: beta
|
||||
alphaVersion: "1.11"
|
||||
betaVersion: "1.14"
|
||||
weight: 81
|
||||
description: "Composition functions allow you to template resources using Python"
|
||||
---
|
||||
|
||||
Composition functions (or just functions, for short) are custom programs that
|
||||
template Crossplane resources. Crossplane calls composition functions to
|
||||
determine what resources it should create when you create a composite resource
|
||||
(XR). Read the
|
||||
[concepts]{{<ref "../concepts/composition-functions" >}}
|
||||
page to learn more about composition functions.
|
||||
|
||||
You can write a function to template resources using a general purpose
|
||||
programming language. Using a general purpose programming language allows a
|
||||
function to use advanced logic to template resources, like loops and
|
||||
conditionals. This guide explains how to write a composition function in
|
||||
[Python](https://python.org).
|
||||
|
||||
{{< hint "important" >}}
|
||||
It helps to be familiar with
|
||||
[how composition functions work]{{<ref "../concepts/composition-functions#how-composition-functions-work" >}}
|
||||
before following this guide.
|
||||
{{< /hint >}}
|
||||
|
||||
## Understand the steps
|
||||
|
||||
This guide covers writing a composition function for an
|
||||
{{<hover label="xr" line="2">}}XBuckets{{</hover>}} composite resource (XR).
|
||||
|
||||
```yaml {label="xr"}
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBuckets
|
||||
metadata:
|
||||
name: example-buckets
|
||||
spec:
|
||||
region: us-east-2
|
||||
names:
|
||||
- crossplane-functions-example-a
|
||||
- crossplane-functions-example-b
|
||||
- crossplane-functions-example-c
|
||||
```
|
||||
|
||||
<!-- vale gitlab.FutureTense = NO -->
|
||||
<!--
|
||||
This section is setting the stage for future sections. It doesn't make sense to
|
||||
refer to the function in the present tense, because it doesn't exist yet.
|
||||
-->
|
||||
An `XBuckets` XR has a region and an array of bucket names. The function will
|
||||
create an Amazon Web Services (AWS) S3 bucket for each entry in the names array.
|
||||
<!-- vale gitlab.FutureTense = YES -->
|
||||
|
||||
To write a function in Python:
|
||||
|
||||
1. [Install the tools you need to write the function](#install-the-tools-you-need-to-write-the-function)
|
||||
1. [Initialize the function from a template](#initialize-the-function-from-a-template)
|
||||
1. [Edit the template to add the function's logic](#edit-the-template-to-add-the-functions-logic)
|
||||
1. [Test the function end-to-end](#test-the-function-end-to-end)
|
||||
1. [Build and push the function to a package repository](#build-and-push-the-function-to-a-package-registry)
|
||||
|
||||
This guide covers each of these steps in detail.
|
||||
|
||||
## Install the tools you need to write the function
|
||||
|
||||
To write a function in Python you need:
|
||||
|
||||
* [Python](https://www.python.org/downloads/) v3.11.
|
||||
* [Hatch](https://hatch.pypa.io/), a Python build tool. This guide uses v1.7.
|
||||
* [Docker Engine](https://docs.docker.com/engine/). This guide uses Engine v24.
|
||||
* The [Crossplane CLI]({{<ref "../cli" >}}) v1.14 or newer. This guide uses Crossplane
|
||||
CLI v1.14.
|
||||
|
||||
{{<hint "note">}}
|
||||
You don't need access to a Kubernetes cluster or a Crossplane control plane to
|
||||
build or test a composition function.
|
||||
{{</hint>}}
|
||||
|
||||
## Initialize the function from a template
|
||||
|
||||
Use the `crossplane beta xpkg init` command to initialize a new function. When
|
||||
you run this command it initializes your function using
|
||||
[a GitHub repository](https://github.com/crossplane/function-template-python)
|
||||
as a template.
|
||||
|
||||
```shell {copy-lines=1}
|
||||
crossplane beta xpkg init function-xbuckets https://github.com/crossplane/function-template-python -d function-xbuckets
|
||||
Initialized package "function-xbuckets" in directory "/home/negz/control/negz/function-xbuckets" from https://github.com/crossplane/function-template-python/tree/bfed6923ab4c8e7adeed70f41138645fc7d38111 (main)
|
||||
```
|
||||
|
||||
The `crossplane beta init xpkg` command creates a directory named
|
||||
`function-xbuckets`. When you run the command the new directory should look like
|
||||
this:
|
||||
|
||||
```shell {copy-lines=1}
|
||||
ls function-xbuckets
|
||||
Dockerfile example/ function/ LICENSE package/ pyproject.toml README.md renovate.json tests/
|
||||
```
|
||||
|
||||
Your function's code lives in the `function` directory:
|
||||
|
||||
```shell {copy-lines=1}
|
||||
ls function/
|
||||
__version__.py fn.py main.py
|
||||
```
|
||||
|
||||
The `function/fn.py` file is where you add the function's code. It's useful to
|
||||
know about some other files in the template:
|
||||
|
||||
* `function/main.py` runs the function. You don't need to edit `main.py`.
|
||||
* `Dockerfile` builds the function runtime. You don't need to edit `Dockerfile`.
|
||||
* The `package` directory contains metadata used to build the function package.
|
||||
|
||||
{{<hint "tip">}}
|
||||
<!-- vale gitlab.FutureTense = NO -->
|
||||
<!--
|
||||
This tip talks about future plans for Crossplane.
|
||||
-->
|
||||
In v1.14 of the Crossplane CLI `crossplane beta xpkg init` just clones a
|
||||
template GitHub repository. A future CLI release will automate tasks like
|
||||
replacing the template name with the new function's name. See Crossplane issue
|
||||
[#4941](https://github.com/crossplane/crossplane/issues/4941) for details.
|
||||
<!-- vale gitlab.FutureTense = YES -->
|
||||
{{</hint>}}
|
||||
|
||||
Edit `package/crossplane.yaml` to change the package's name before you start
|
||||
adding code. Name your package `function-xbuckets`.
|
||||
|
||||
The `package/input` directory defines the OpenAPI schema for the a function's
|
||||
input. The function in this guide doesn't accept an input. Delete the
|
||||
`package/input` directory.
|
||||
|
||||
The [composition functions]{{<ref "../concepts/composition-functions" >}}
|
||||
documentation explains composition function inputs.
|
||||
|
||||
{{<hint "tip">}}
|
||||
If you're writing a function that uses an input, edit the input YAML file to
|
||||
meet your function's requirements.
|
||||
|
||||
Change the input's kind and API group. Don't use `Input` and
|
||||
`template.fn.crossplane.io`. Instead use something meaningful to your function.
|
||||
{{</hint>}}
|
||||
|
||||
## Edit the template to add the function's logic
|
||||
|
||||
You add your function's logic to the
|
||||
{{<hover label="hello-world" line="1">}}RunFunction{{</hover>}}
|
||||
method in `function/fn.py`. When you first open the file it contains a "hello
|
||||
world" function.
|
||||
|
||||
```python {label="hello-world"}
|
||||
async def RunFunction(self, req: fnv1beta1.RunFunctionRequest, _: grpc.aio.ServicerContext) -> fnv1beta1.RunFunctionResponse:
|
||||
log = self.log.bind(tag=req.meta.tag)
|
||||
log.info("Running function")
|
||||
|
||||
rsp = response.to(req)
|
||||
|
||||
example = ""
|
||||
if "example" in req.input:
|
||||
example = req.input["example"]
|
||||
|
||||
# TODO: Add your function logic here!
|
||||
response.normal(rsp, f"I was run with input {example}!")
|
||||
log.info("I was run!", input=example)
|
||||
|
||||
return rsp
|
||||
```
|
||||
|
||||
All Python composition functions have a `RunFunction` method. Crossplane passes
|
||||
everything the function needs to run in a
|
||||
{{<hover label="hello-world" line="1">}}RunFunctionRequest{{</hover>}} object.
|
||||
|
||||
The function tells Crossplane what resources it should compose by returning a
|
||||
{{<hover label="hello-world" line="15">}}RunFunctionResponse{{</hover>}} object.
|
||||
|
||||
Edit the `RunFunction` method to replace it with this code.
|
||||
|
||||
```python {hl_lines="7-28"}
|
||||
async def RunFunction(self, req: fnv1beta1.RunFunctionRequest, _: grpc.aio.ServicerContext) -> fnv1beta1.RunFunctionResponse:
|
||||
log = self.log.bind(tag=req.meta.tag)
|
||||
log.info("Running function")
|
||||
|
||||
rsp = response.to(req)
|
||||
|
||||
region = req.observed.composite.resource["spec"]["region"]
|
||||
names = req.observed.composite.resource["spec"]["names"]
|
||||
|
||||
for name in names:
|
||||
rsp.desired.resources[f"xbuckets-{name}"].resource.update(
|
||||
{
|
||||
"apiVersion": "s3.aws.upbound.io/v1beta1",
|
||||
"kind": "Bucket",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"crossplane.io/external-name": name,
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
"forProvider": {
|
||||
"region": region,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
log.info("Added desired buckets", region=region, count=len(names))
|
||||
|
||||
return rsp
|
||||
```
|
||||
|
||||
Expand the below block to view the full `fn.py`, including imports and
|
||||
commentary explaining the function's logic.
|
||||
|
||||
{{<expand "The full fn.py file" >}}
|
||||
```python
|
||||
"""A Crossplane composition function."""
|
||||
|
||||
import grpc
|
||||
from crossplane.function import logging, response
|
||||
from crossplane.function.proto.v1beta1 import run_function_pb2 as fnv1beta1
|
||||
from crossplane.function.proto.v1beta1 import run_function_pb2_grpc as grpcv1beta1
|
||||
|
||||
|
||||
class FunctionRunner(grpcv1beta1.FunctionRunnerService):
|
||||
"""A FunctionRunner handles gRPC RunFunctionRequests."""
|
||||
|
||||
def __init__(self):
|
||||
"""Create a new FunctionRunner."""
|
||||
self.log = logging.get_logger()
|
||||
|
||||
async def RunFunction(
|
||||
self, req: fnv1beta1.RunFunctionRequest, _: grpc.aio.ServicerContext
|
||||
) -> fnv1beta1.RunFunctionResponse:
|
||||
"""Run the function."""
|
||||
# Create a logger for this request.
|
||||
log = self.log.bind(tag=req.meta.tag)
|
||||
log.info("Running function")
|
||||
|
||||
# Create a response to the request. This copies the desired state and
|
||||
# pipeline context from the request to the response.
|
||||
rsp = response.to(req)
|
||||
|
||||
# Get the region and a list of bucket names from the observed composite
|
||||
# resource (XR). Crossplane represents resources using the Struct
|
||||
# well-known protobuf type. The Struct Python object can be accessed
|
||||
# like a dictionary.
|
||||
region = req.observed.composite.resource["spec"]["region"]
|
||||
names = req.observed.composite.resource["spec"]["names"]
|
||||
|
||||
# Add a desired S3 bucket for each name.
|
||||
for name in names:
|
||||
# Crossplane represents desired composed resources using a protobuf
|
||||
# map of messages. This works a little like a Python defaultdict.
|
||||
# Instead of assigning to a new key in the dict-like map, you access
|
||||
# the key and mutate its value as if it did exist.
|
||||
#
|
||||
# The below code works because accessing the xbuckets-{name} key
|
||||
# automatically creates a new, empty fnv1beta1.Resource message. The
|
||||
# Resource message has a resource field containing an empty Struct
|
||||
# object that can be populated from a dictionary by calling update.
|
||||
#
|
||||
# https://protobuf.dev/reference/python/python-generated/#map-fields
|
||||
rsp.desired.resources[f"xbuckets-{name}"].resource.update(
|
||||
{
|
||||
"apiVersion": "s3.aws.upbound.io/v1beta1",
|
||||
"kind": "Bucket",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"crossplane.io/external-name": name,
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
"forProvider": {
|
||||
"region": region,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# Log what the function did. This will only appear in the function's pod
|
||||
# logs. A function can use response.normal() and response.warning() to
|
||||
# emit Kubernetes events associated with the XR it's operating on.
|
||||
log.info("Added desired buckets", region=region, count=len(names))
|
||||
|
||||
return rsp
|
||||
```
|
||||
{{</expand>}}
|
||||
|
||||
This code:
|
||||
|
||||
1. Gets the observed composite resource from the `RunFunctionRequest`.
|
||||
1. Gets the region and bucket names from the observed composite resource.
|
||||
1. Adds one desired S3 bucket for each bucket name.
|
||||
1. Returns the desired S3 buckets in a `RunFunctionResponse`.
|
||||
|
||||
Crossplane provides a
|
||||
[software development kit](https://github.com/crossplane/function-sdk-python)
|
||||
(SDK) for writing composition functions in Python. This function uses utilities
|
||||
from the SDK.
|
||||
|
||||
{{<hint "tip">}}
|
||||
Read [the Python Function SDK documentation](https://crossplane.github.io/function-sdk-python).
|
||||
{{</hint>}}
|
||||
|
||||
{{<hint "important">}}
|
||||
The Python SDK automatically generates the `RunFunctionRequest` and
|
||||
`RunFunctionResponse` Python objects from a
|
||||
[Protocol Buffers](https://protobuf.dev) schema. You can see the schema in the
|
||||
[Buf Schema Registry](https://buf.build/crossplane/crossplane/docs/main:apiextensions.fn.proto.v1beta1).
|
||||
|
||||
The fields of the generated Python objects behave similarly to builtin Python
|
||||
types like dictionaries and lists. Be aware that there are some differences.
|
||||
|
||||
Notably, you access the map of observed and desired resources like a dictionary
|
||||
but you can't add a new desired resource by assigning to a map key. Instead,
|
||||
access and mutate the map key as if it already exists.
|
||||
|
||||
Instead of adding a new resource like this:
|
||||
|
||||
```python
|
||||
resource = {"apiVersion": "example.org/v1", "kind": "Composed", ...}
|
||||
rsp.desired.resources["new-resource"] = fnv1beta1.Resource(resource=resource)
|
||||
```
|
||||
|
||||
Pretend it already exists and mutate it, like this:
|
||||
|
||||
```python
|
||||
resource = {"apiVersion": "example.org/v1", "kind": "Composed", ...}
|
||||
rsp.desired.resources["new-resource"].resource.update(resource)
|
||||
```
|
||||
|
||||
Refer to the Protocol Buffers
|
||||
[Python Generated Code Guide](https://protobuf.dev/reference/python/python-generated/#fields)
|
||||
for further details.
|
||||
{{</hint>}}
|
||||
|
||||
## Test the function end-to-end
|
||||
|
||||
Test your function by adding unit tests, and by using the `crossplane beta
|
||||
render` command.
|
||||
|
||||
When you initialize a function from the
|
||||
template it adds some unit tests to `tests/test_fn.py`. These tests use the
|
||||
[`unittest`](https://docs.python.org/3/library/unittest.html) module from the
|
||||
Python standard library.
|
||||
|
||||
To add test cases, update the `cases` list in `test_run_function`. Expand the
|
||||
below block to view the full `tests/test_fn.py` file for the function.
|
||||
|
||||
{{<expand "The full test_fn.py file" >}}
|
||||
```python
|
||||
import dataclasses
|
||||
import unittest
|
||||
|
||||
from crossplane.function import logging, resource
|
||||
from crossplane.function.proto.v1beta1 import run_function_pb2 as fnv1beta1
|
||||
from google.protobuf import duration_pb2 as durationpb
|
||||
from google.protobuf import json_format
|
||||
from google.protobuf import struct_pb2 as structpb
|
||||
|
||||
from function import fn
|
||||
|
||||
|
||||
class TestFunctionRunner(unittest.IsolatedAsyncioTestCase):
|
||||
def setUp(self) -> None:
|
||||
logging.configure(level=logging.Level.DISABLED)
|
||||
self.maxDiff = 2000
|
||||
|
||||
async def test_run_function(self) -> None:
|
||||
@dataclasses.dataclass
|
||||
class TestCase:
|
||||
reason: str
|
||||
req: fnv1beta1.RunFunctionRequest
|
||||
want: fnv1beta1.RunFunctionResponse
|
||||
|
||||
cases = [
|
||||
TestCase(
|
||||
reason="The function should compose two S3 buckets.",
|
||||
req=fnv1beta1.RunFunctionRequest(
|
||||
observed=fnv1beta1.State(
|
||||
composite=fnv1beta1.Resource(
|
||||
resource=resource.dict_to_struct(
|
||||
{
|
||||
"apiVersion": "example.crossplane.io/v1alpha1",
|
||||
"kind": "XBuckets",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": {
|
||||
"region": "us-east-2",
|
||||
"names": ["test-bucket-a", "test-bucket-b"],
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
want=fnv1beta1.RunFunctionResponse(
|
||||
meta=fnv1beta1.ResponseMeta(ttl=durationpb.Duration(seconds=60)),
|
||||
desired=fnv1beta1.State(
|
||||
resources={
|
||||
"xbuckets-test-bucket-a": fnv1beta1.Resource(
|
||||
resource=resource.dict_to_struct(
|
||||
{
|
||||
"apiVersion": "s3.aws.upbound.io/v1beta1",
|
||||
"kind": "Bucket",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"crossplane.io/external-name": "test-bucket-a"
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
"forProvider": {"region": "us-east-2"}
|
||||
},
|
||||
}
|
||||
)
|
||||
),
|
||||
"xbuckets-test-bucket-b": fnv1beta1.Resource(
|
||||
resource=resource.dict_to_struct(
|
||||
{
|
||||
"apiVersion": "s3.aws.upbound.io/v1beta1",
|
||||
"kind": "Bucket",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"crossplane.io/external-name": "test-bucket-b"
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
"forProvider": {"region": "us-east-2"}
|
||||
},
|
||||
}
|
||||
)
|
||||
),
|
||||
},
|
||||
),
|
||||
context=structpb.Struct(),
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
runner = fn.FunctionRunner()
|
||||
|
||||
for case in cases:
|
||||
got = await runner.RunFunction(case.req, None)
|
||||
self.assertEqual(
|
||||
json_format.MessageToDict(got),
|
||||
json_format.MessageToDict(case.want),
|
||||
"-want, +got",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
```
|
||||
{{</expand>}}
|
||||
|
||||
Run the unit tests using `hatch run`:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
hatch run test:unit
|
||||
.
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.003s
|
||||
|
||||
OK
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
[Hatch](https://hatch.pypa.io/) is a Python build tool. It builds Python
|
||||
artifacts like wheels. It also manages virtual environments, similar
|
||||
to `virtualenv` or `venv`. The `hatch run` command creates a virtual environment
|
||||
and runs a command in that environment.
|
||||
{{</hint>}}
|
||||
|
||||
You can preview the output of a Composition that uses this function using
|
||||
the Crossplane CLI. You don't need a Crossplane control plane to do this.
|
||||
|
||||
Create a directory under `function-xbuckets` named `example` and create
|
||||
Composite Resource, Composition and Function YAML files.
|
||||
|
||||
Expand the following block to see example files.
|
||||
|
||||
{{<expand "The xr.yaml, composition.yaml and function.yaml files">}}
|
||||
|
||||
You can recreate the output below using by running `crossplane beta render` with
|
||||
these files.
|
||||
|
||||
The `xr.yaml` file contains the composite resource to render:
|
||||
|
||||
```yaml
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBuckets
|
||||
metadata:
|
||||
name: example-buckets
|
||||
spec:
|
||||
region: us-east-2
|
||||
names:
|
||||
- crossplane-functions-example-a
|
||||
- crossplane-functions-example-b
|
||||
- crossplane-functions-example-c
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
The `composition.yaml` file contains the Composition to use to render the
|
||||
composite resource:
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: create-buckets
|
||||
spec:
|
||||
compositeTypeRef:
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBuckets
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: create-buckets
|
||||
functionRef:
|
||||
name: function-xbuckets
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
The `functions.yaml` file contains the Functions the Composition references in
|
||||
its pipeline steps:
|
||||
|
||||
```yaml
|
||||
apiVersion: pkg.crossplane.io/v1beta1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: function-xbuckets
|
||||
annotations:
|
||||
render.crossplane.io/runtime: Development
|
||||
spec:
|
||||
# The CLI ignores this package when using the Development runtime.
|
||||
# You can set it to any value.
|
||||
package: xpkg.upbound.io/negz/function-xbuckets:v0.1.0
|
||||
```
|
||||
{{</expand>}}
|
||||
|
||||
The Function in `functions.yaml` uses the
|
||||
{{<hover label="development" line="6">}}Development{{</hover>}}
|
||||
runtime. This tells `crossplane beta render` that your function is running
|
||||
locally. It connects to your locally running function instead of using Docker to
|
||||
pull and run the function.
|
||||
|
||||
```yaml {label="development"}
|
||||
apiVersion: pkg.crossplane.io/v1beta1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: function-xbuckets
|
||||
annotations:
|
||||
render.crossplane.io/runtime: Development
|
||||
```
|
||||
|
||||
Use `hatch run development` to run your function locally.
|
||||
|
||||
```shell {label="run"}
|
||||
hatch run development
|
||||
```
|
||||
|
||||
{{<hint "warning">}}
|
||||
`hatch run development` runs the function without encryption or authentication.
|
||||
Only use it during testing and development.
|
||||
{{</hint>}}
|
||||
|
||||
In a separate terminal, run `crossplane beta render`.
|
||||
|
||||
```shell
|
||||
crossplane beta render xr.yaml composition.yaml functions.yaml
|
||||
```
|
||||
|
||||
This command calls your function. In the terminal where your function is running
|
||||
you should now see log output:
|
||||
|
||||
```shell
|
||||
hatch run development
|
||||
2024-01-11T22:12:58.153572Z [info ] Running function filename=fn.py lineno=22 tag=
|
||||
2024-01-11T22:12:58.153792Z [info ] Added desired buckets count=3 filename=fn.py lineno=68 region=us-east-2 tag=
|
||||
```
|
||||
|
||||
The `crossplane beta render` command prints the desired resources the function
|
||||
returns.
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBuckets
|
||||
metadata:
|
||||
name: example-buckets
|
||||
---
|
||||
apiVersion: s3.aws.upbound.io/v1beta1
|
||||
kind: Bucket
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-b
|
||||
crossplane.io/external-name: crossplane-functions-example-b
|
||||
generateName: example-buckets-
|
||||
labels:
|
||||
crossplane.io/composite: example-buckets
|
||||
ownerReferences:
|
||||
# Omitted for brevity
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-2
|
||||
---
|
||||
apiVersion: s3.aws.upbound.io/v1beta1
|
||||
kind: Bucket
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-c
|
||||
crossplane.io/external-name: crossplane-functions-example-c
|
||||
generateName: example-buckets-
|
||||
labels:
|
||||
crossplane.io/composite: example-buckets
|
||||
ownerReferences:
|
||||
# Omitted for brevity
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-2
|
||||
---
|
||||
apiVersion: s3.aws.upbound.io/v1beta1
|
||||
kind: Bucket
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-a
|
||||
crossplane.io/external-name: crossplane-functions-example-a
|
||||
generateName: example-buckets-
|
||||
labels:
|
||||
crossplane.io/composite: example-buckets
|
||||
ownerReferences:
|
||||
# Omitted for brevity
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-2
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
Read the composition functions documentation to learn more about
|
||||
[testing composition functions]({{< ref "../concepts/composition-functions#test-a-composition-that-uses-functions" >}}).
|
||||
{{</hint>}}
|
||||
|
||||
## Build and push the function to a package registry
|
||||
|
||||
You build a function in two stages. First you build the function's runtime. This
|
||||
is the Open Container Initiative (OCI) image Crossplane uses to run your
|
||||
function. You then embed that runtime in a package, and push it to a package
|
||||
registry. The Crossplane CLI uses `xpkg.upbound.io` as its default package
|
||||
registry.
|
||||
|
||||
A function supports a single platform, like `linux/amd64`, by default. You can
|
||||
support multiple platforms by building a runtime and package for each platform,
|
||||
then pushing all the packages to a single tag in the registry.
|
||||
|
||||
Pushing your function to a registry allows you to use your function in a
|
||||
Crossplane control plane. See the
|
||||
[composition functions documentation]{{<ref "../concepts/composition-functions" >}}.
|
||||
to learn how to use a function in a control plane.
|
||||
|
||||
Use Docker to build a runtime for each platform.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
docker build . --quiet --platform=linux/amd64 --tag runtime-amd64
|
||||
sha256:fdf40374cc6f0b46191499fbc1dbbb05ddb76aca854f69f2912e580cfe624b4b
|
||||
```
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
docker build . --quiet --platform=linux/arm64 --tag runtime-arm64
|
||||
sha256:cb015ceabf46d2a55ccaeebb11db5659a2fb5e93de36713364efcf6d699069af
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
You can use whatever tag you want. There's no need to push the runtime images to
|
||||
a registry. The tag is only used to tell `crossplane xpkg build` what runtime to
|
||||
embed.
|
||||
{{</hint>}}
|
||||
|
||||
{{<hint "important">}}
|
||||
Docker uses emulation to create images for different platforms. If building an
|
||||
image for a different platform fails, make sure you have installed `binfmt`. See
|
||||
the
|
||||
[Docker documentation](https://docs.docker.com/build/building/multi-platform/#qemu)
|
||||
for instructions.
|
||||
{{</hint>}}
|
||||
|
||||
Use the Crossplane CLI to build a package for each platform. Each package embeds
|
||||
a runtime image.
|
||||
|
||||
The {{<hover label="build" line="2">}}--package-root{{</hover>}} flag specifies
|
||||
the `package` directory, which contains `crossplane.yaml`. This includes
|
||||
metadata about the package.
|
||||
|
||||
The {{<hover label="build" line="3">}}--embed-runtime-image{{</hover>}} flag
|
||||
specifies the runtime image tag built using Docker.
|
||||
|
||||
The {{<hover label="build" line="4">}}--package-file{{</hover>}} flag specifies
|
||||
specifies where to write the package file to disk. Crossplane package files use
|
||||
the extension `.xpkg`.
|
||||
|
||||
```shell {label="build"}
|
||||
crossplane xpkg build \
|
||||
--package-root=package \
|
||||
--embed-runtime-image=runtime-amd64 \
|
||||
--package-file=function-amd64.xpkg
|
||||
```
|
||||
|
||||
```shell
|
||||
crossplane xpkg build \
|
||||
--package-root=package \
|
||||
--embed-runtime-image=runtime-arm64 \
|
||||
--package-file=function-arm64.xpkg
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
Crossplane packages are special OCI images. Read more about packages in the
|
||||
[packages documentation]({{< ref "../concepts/packages" >}}).
|
||||
{{</hint>}}
|
||||
|
||||
Push both package files to a registry. Pushing both files to one tag in the
|
||||
registry creates a
|
||||
[multi-platform](https://docs.docker.com/build/building/multi-platform/)
|
||||
package that runs on both `linux/arm64` and `linux/amd64` hosts.
|
||||
|
||||
```shell
|
||||
crossplane xpkg push \
|
||||
--package-files=function-amd64.xpkg,function-arm64.xpkg \
|
||||
negz/function-xbuckets:v0.1.0
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
If you push the function to a GitHub repository the template automatically sets
|
||||
up continuous integration (CI) using
|
||||
[GitHub Actions](https://github.com/features/actions). The CI workflow will
|
||||
lint, test, and build your function. You can see how the template configures CI
|
||||
by reading `.github/workflows/ci.yaml`.
|
||||
|
||||
The CI workflow can automatically push packages to `xpkg.upbound.io`. For this
|
||||
to work you must create a repository at https://marketplace.upbound.io. Give the
|
||||
CI workflow access to push to the Marketplace by creating an API token and
|
||||
[adding it to your repository](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository).
|
||||
Save your API token access ID as a secret named `XPKG_ACCESS_ID` and your API
|
||||
token as a secret named `XPKG_TOKEN`.
|
||||
{{</hint>}}
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
title: Learn More
|
||||
weight: 307
|
||||
description: Learn more about Crossplane.
|
||||
weight: 500
|
||||
---
|
||||
|
||||
If you have any questions, please drop us a note on [Crossplane Slack][join-crossplane-slack] or [contact us][contact-us]!
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Release Notes
|
||||
weight: 20
|
||||
weight: 600
|
||||
description: "Crossplane release notes"
|
||||
product: "Release Notes"
|
||||
cascade:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Install and Uninstall Crossplane
|
||||
weight: 300
|
||||
title: Install, Upgrade and Uninstall
|
||||
weight: 10
|
||||
description: Manage Crossplane installations
|
||||
---
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ For example, in v1.15.0 Crossplane changed the default image registry from
|
|||
before v1.15.0 updates the default package registry.
|
||||
|
||||
Override new defaults by
|
||||
[customizing the Helm chart](https://docs.crossplane.io/v1.15/software/install/#customize-the-crossplane-helm-chart)
|
||||
[customizing the Helm chart]({{<ref "install#customize-the-crossplane-helm-chart" >}})
|
||||
with the upgrade command.
|
||||
|
||||
For example, to maintain the original image registry use
|
||||
|
|
|
@ -205,4 +205,4 @@ spec:
|
|||
```
|
||||
|
||||
For more information on connection secrets read the
|
||||
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}).
|
||||
[Connection Secrets knowledge base article]({{<ref "connection-details">}}).
|
|
@ -623,7 +623,7 @@ recreate the XRD to change the `connectionSecretKeys`.
|
|||
{{</hint >}}
|
||||
|
||||
For more information on connection secrets read the
|
||||
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}).
|
||||
[Connection Secrets knowledge base article]({{<ref "connection-details">}}).
|
||||
|
||||
### Set composite resource defaults
|
||||
XRDs can set default parameters for composite resources and Claims.
|
||||
|
|
|
@ -190,7 +190,7 @@ spec:
|
|||
### Composition revision policy
|
||||
|
||||
Crossplane tracks changes to Compositions as
|
||||
[Composition revisions]({{<ref "/knowledge-base/guides/composition-revisions">}}) .
|
||||
[Composition revisions]({{<ref "composition-revisions">}}) .
|
||||
|
||||
A composite resource can use
|
||||
a {{<hover label="comprev" line="6">}}compositionUpdatePolicy{{</hover>}} to
|
||||
|
@ -218,7 +218,7 @@ spec:
|
|||
### Composition revision selection
|
||||
|
||||
Crossplane records changes to Compositions as
|
||||
[Composition revisions]({{<ref "/knowledge-base/guides/composition-revisions">}}).
|
||||
[Composition revisions]({{<ref "composition-revisions">}}).
|
||||
A composite resource can
|
||||
select a specific Composition revision.
|
||||
|
||||
|
@ -311,7 +311,7 @@ spec:
|
|||
```
|
||||
|
||||
Composite resources can write connection secrets to an
|
||||
[external secret store]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}),
|
||||
[external secret store]({{<ref "../guides/vault-as-secret-store">}}),
|
||||
like HashiCorp Vault.
|
||||
|
||||
{{<hint "important" >}}
|
||||
|
@ -334,11 +334,11 @@ spec:
|
|||
# Removed for brevity
|
||||
```
|
||||
|
||||
Read the [External Secrets Store]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) documentation for more information on using
|
||||
Read the [External Secrets Store]({{<ref "../guides/vault-as-secret-store">}}) documentation for more information on using
|
||||
external secret stores.
|
||||
|
||||
For more information on connection secrets read the
|
||||
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}).
|
||||
[Connection Secrets knowledge base article]({{<ref "connection-details">}}).
|
||||
|
||||
### Pausing composite resources
|
||||
|
||||
|
|
|
@ -1,13 +1,144 @@
|
|||
---
|
||||
title: Composition Revision Example
|
||||
title: Composition Revisions
|
||||
---
|
||||
|
||||
This guide discusses the use of "Composition Revisions" to safely make and roll
|
||||
back changes to a Crossplane [`Composition`][composition-type]. It assumes
|
||||
familiarity with Crossplane, and particularly with
|
||||
[Compositions].
|
||||
|
||||
A `Composition` configures how Crossplane should reconcile a Composite Resource
|
||||
(XR). Put otherwise, when you create an XR the selected `Composition` determines
|
||||
what managed resources Crossplane will create in response. Let's say for example
|
||||
that you define a `PlatformDB` XR, which represents your organisation's common
|
||||
database configuration of an Azure MySQL Server and a few firewall rules. The
|
||||
`Composition` contains the 'base' configuration for the MySQL server and the
|
||||
firewall rules that is extended by the configuration for the `PlatformDB`.
|
||||
|
||||
There is a one-to-many relationship between a `Composition` and the XRs that use
|
||||
it. You might define a `Composition` named `big-platform-db` that is used by ten
|
||||
different `PlatformDB` XRs. Usually, in the interest of self-service, the
|
||||
`Composition` is managed by a different team from the actual `PlatformDB` XRs.
|
||||
For example the `Composition` may be written and maintained by a platform team
|
||||
member, while individual application teams create `PlatformDB` XRs that use said
|
||||
`Composition`.
|
||||
|
||||
Each `Composition` is mutable - you can update it as your organisation's needs
|
||||
change. However, without Composition Revisions updating a `Composition` can be a
|
||||
risky process. Crossplane constantly uses the `Composition` to ensure that your
|
||||
actual infrastructure - your MySQL Servers and firewall rules - match your
|
||||
desired state. If you have 10 `PlatformDB` XRs all using the `big-platform-db`
|
||||
`Composition`, all 10 of those XRs will be instantly updated in accordance with
|
||||
any updates you make to the `big-platform-db` `Composition`.
|
||||
|
||||
Composition Revisions allow XRs to opt out of automatic updates. Instead you can
|
||||
update your XRs to leverage the latest `Composition` settings at your own pace.
|
||||
This enables you to [canary] changes to your infrastructure, or to roll back
|
||||
some XRs to previous `Composition` settings without rolling back all XRs.
|
||||
|
||||
## Using Composition Revisions
|
||||
|
||||
When you enable Composition Revisions three things happen:
|
||||
|
||||
1. Crossplane creates a `CompositionRevision` for each `Composition` update.
|
||||
1. Composite Resources gain a `spec.compositionRevisionRef` field that specifies
|
||||
which `CompositionRevision` they use.
|
||||
1. Composite Resources gain a `spec.compositionUpdatePolicy` field that
|
||||
specifies how they should be updated to new Composition Revisions.
|
||||
|
||||
Each time you edit a `Composition` Crossplane will automatically create a
|
||||
`CompositionRevision` that represents that 'revision' of the `Composition` -
|
||||
that unique state. Each revision is allocated an increasing revision number.
|
||||
This gives `CompositionRevision` consumers an idea about which revision is
|
||||
'newest'.
|
||||
|
||||
Crossplane distinguishes between the 'newest' and the 'current' revision of a
|
||||
`Composition`. That is, if you revert a `Composition` to a previous state that
|
||||
corresponds to an existing `CompositionRevision` that revision will become
|
||||
'current' even if it is not the 'newest' revision (i.e. the most latest _unique_
|
||||
`Composition` configuration).
|
||||
|
||||
You can discover which revisions exist using `kubectl`:
|
||||
|
||||
```console
|
||||
# Find all revisions of the Composition named 'example'
|
||||
kubectl get compositionrevision -l crossplane.io/composition-name=example
|
||||
```
|
||||
|
||||
This should produce output something like:
|
||||
|
||||
```console
|
||||
NAME REVISION CURRENT AGE
|
||||
example-18pdg 1 False 4m36s
|
||||
example-2bgdr 2 True 73s
|
||||
example-xjrdm 3 False 61s
|
||||
```
|
||||
|
||||
> A `Composition` is a mutable resource that you can update as your needs
|
||||
> change over time. Each `CompositionRevision` is an immutable snapshot of those
|
||||
> needs at a particular point in time.
|
||||
|
||||
Crossplane behaves the same way by default whether Composition Revisions are
|
||||
enabled or not. This is because when you enable Composition Revisions all XRs
|
||||
default to the `Automatic` `compositionUpdatePolicy`. XRs support two update
|
||||
policies:
|
||||
|
||||
* `Automatic`: Automatically use the current `CompositionRevision`. (Default)
|
||||
* `Manual`: Require manual intervention to change `CompositionRevision`.
|
||||
|
||||
The below XR uses the `Manual` policy. When this policy is used the XR will
|
||||
select the current `CompositionRevision` when it is first created, but must
|
||||
manually be updated when you wish it to use another `CompositionRevision`.
|
||||
|
||||
```yaml
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: PlatformDB
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
parameters:
|
||||
storageGB: 20
|
||||
# The Manual policy specifies that you do not want this XR to update to the
|
||||
# current CompositionRevision automatically.
|
||||
compositionUpdatePolicy: Manual
|
||||
compositionRef:
|
||||
name: example
|
||||
writeConnectionSecretToRef:
|
||||
name: db-conn
|
||||
```
|
||||
|
||||
Crossplane sets an XR's `compositionRevisionRef` automatically at creation time
|
||||
regardless of your chosen `compositionUpdatePolicy`. If you choose the `Manual`
|
||||
policy you must edit the `compositionRevisionRef` field when you want your XR to
|
||||
use a different `CompositionRevision`.
|
||||
|
||||
```yaml
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: PlatformDB
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
parameters:
|
||||
storageGB: 20
|
||||
compositionUpdatePolicy: Manual
|
||||
compositionRef:
|
||||
name: example
|
||||
# Update the referenced CompositionRevision if and when you are ready.
|
||||
compositionRevisionRef:
|
||||
name: example-18pdg
|
||||
writeConnectionSecretToRef:
|
||||
name: db-conn
|
||||
```
|
||||
|
||||
## Complete example
|
||||
|
||||
This tutorial discusses how CompositionRevisions work and how they manage Composite Resource
|
||||
(XR) updates. This starts with a `Composition` and `CompositeResourceDefinition` (XRD) that defines a `MyVPC`
|
||||
resource and continues with creating multiple XRs to observe different upgrade paths. Crossplane will
|
||||
assign different CompositionRevisions to the created composite resources each time the composition is updated.
|
||||
|
||||
## Preparation
|
||||
### Install Crossplane
|
||||
### Preparation
|
||||
##### Install Crossplane
|
||||
Install Crossplane v1.11.0 or later and wait until the Crossplane pods are running.
|
||||
```shell
|
||||
kubectl create namespace crossplane-system
|
||||
|
@ -23,7 +154,7 @@ crossplane-7f75ddcc46-f4d2z 1/1 Running 0 9s
|
|||
crossplane-rbac-manager-78bd597746-sdv6w 1/1 Running 0 9s
|
||||
```
|
||||
|
||||
### Deploy Composition and XRD Examples
|
||||
#### Deploy Composition and XRD Examples
|
||||
Apply the example Composition.
|
||||
|
||||
```yaml
|
||||
|
@ -95,13 +226,13 @@ The label `dev` is automatically created from the Composition.
|
|||
{{< /hint >}}
|
||||
|
||||
|
||||
## Create Composite Resources
|
||||
### Create Composite Resources
|
||||
This tutorial has four composite resources to cover different update policies and composition selection options.
|
||||
The default behavior is updating XRs to the latest revision of the Composition. However, this can be changed by setting
|
||||
`compositionUpdatePolicy: Manual` in the XR. It is also possible to select the latest revision with a specific label
|
||||
with `compositionRevisionSelector.matchLabels` together with `compositionUpdatePolicy: Automatic`.
|
||||
|
||||
### Default update policy
|
||||
#### Default update policy
|
||||
Create an XR without a `compositionUpdatePolicy` defined. The update policy is `Automatic` by default:
|
||||
```yaml
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
|
@ -116,7 +247,7 @@ Expected Output:
|
|||
myvpc.aws.example.upbound.io/vpc-auto created
|
||||
```
|
||||
|
||||
### Manual update policy
|
||||
#### Manual update policy
|
||||
Create a Composite Resource with `compositionUpdatePolicy: Manual` and `compositionRevisionRef`.
|
||||
```yaml
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
|
@ -135,7 +266,7 @@ Expected Output:
|
|||
myvpc.aws.example.upbound.io/vpc-man created
|
||||
```
|
||||
|
||||
### Using a selector
|
||||
#### Using a selector
|
||||
Create an XR with a `compositionRevisionSelector` of `channel: dev`:
|
||||
```yaml
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
|
@ -189,11 +320,11 @@ vpc-staging False <none> Automatic map[c
|
|||
The `vpc-staging` XR label doesn't match any existing Composition Revisions.
|
||||
{{< /hint >}}
|
||||
|
||||
## Create new Composition revisions
|
||||
### Create new Composition revisions
|
||||
Crossplane creates a new CompositionRevision when a Composition is created or updated. Label and annotation changes will
|
||||
also trigger a new CompositionRevision.
|
||||
|
||||
### Update the Composition label
|
||||
#### Update the Composition label
|
||||
Update the `Composition` label to `channel: staging`:
|
||||
```shell
|
||||
kubectl label composition myvpcs.aws.example.upbound.io channel=staging --overwrite
|
||||
|
@ -234,7 +365,7 @@ vpc-staging True myvpcs.aws.example.upbound.io-727b3c8 Automatic map[c
|
|||
`vpc-staging` now matches the label applied to Revision revision:2.
|
||||
{{< /hint >}}
|
||||
|
||||
### Update Composition Spec and Label
|
||||
#### Update Composition Spec and Label
|
||||
Update the Composition to disable DNS support in the VPC and change the label from `staging` back to `dev`.
|
||||
|
||||
Apply the following changes to update the `Composition` spec and label:
|
||||
|
@ -305,3 +436,9 @@ vpc-staging True myvpcs.aws.example.upbound.io-727b3c8 Automatic map[c
|
|||
`vpc-dev` matches the updated label applied to Revision revision:3.
|
||||
`vpc-staging` matches the label applied to Revision revision:2.
|
||||
{{< /hint >}}
|
||||
|
||||
|
||||
[composition-type]: {{<ref "../../master/concepts/compositions" >}}
|
||||
[Compositions]: {{<ref "../../master/concepts/compositions" >}}
|
||||
[canary]: https://martinfowler.com/bliki/CanaryRelease.html
|
||||
[install-guide]: {{<ref "../../master/software/install" >}}
|
|
@ -716,7 +716,7 @@ details.
|
|||
This section discusses creating Kubernetes secrets.
|
||||
Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/).
|
||||
|
||||
Read the [external secrets store guide]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) for more information on using Crossplane
|
||||
Read the [external secrets store guide]({{<ref "../guides/vault-as-secret-store">}}) for more information on using Crossplane
|
||||
with an external secret store.
|
||||
{{</hint >}}
|
||||
|
||||
|
@ -926,7 +926,7 @@ for more information on restricting secret keys.
|
|||
{{< /hint >}}
|
||||
|
||||
For more information on connection secrets read the
|
||||
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}).
|
||||
[Connection Secrets knowledge base article]({{<ref "connection-details">}}).
|
||||
|
||||
{{<hint "warning">}}
|
||||
You can't change the
|
||||
|
@ -941,7 +941,7 @@ recreate the Composition to change the
|
|||
#### Save connection details to an external secret store
|
||||
|
||||
Crossplane
|
||||
[External Secret Stores]({{<ref "/knowledge-base/integrations/vault-as-secret-store" >}})
|
||||
[External Secret Stores]({{<ref "../guides/vault-as-secret-store" >}})
|
||||
write secrets and connection details to external secret stores like HashiCorp
|
||||
Vault.
|
||||
|
||||
|
@ -986,7 +986,7 @@ spec:
|
|||
# Removed for brevity
|
||||
```
|
||||
|
||||
For more details read the [External Secret Stores]({{<ref "/knowledge-base/integrations/vault-as-secret-store" >}})
|
||||
For more details read the [External Secret Stores]({{<ref "../guides/vault-as-secret-store" >}})
|
||||
integration guide.
|
||||
|
||||
### Resource readiness checks
|
||||
|
|
|
@ -18,7 +18,7 @@ Using connection details in Crossplane requires the following components:
|
|||
This guide discusses creating Kubernetes secrets.
|
||||
Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/).
|
||||
|
||||
Read the [external secrets store guide]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) for more information on using Crossplane
|
||||
Read the [external secrets store guide]({{<ref "../guides/vault-as-secret-store">}}) for more information on using Crossplane
|
||||
with an external secret store.
|
||||
{{</hint >}}
|
||||
|
|
@ -372,7 +372,7 @@ Crossplane supports the following policies:
|
|||
| `Create` | If the external resource doesn't exist, Crossplane creates it based on the managed resource settings. |
|
||||
| `Delete` | Crossplane can delete the external resource when deleting the managed resource. |
|
||||
| `LateInitialize` | Crossplane initializes some external resource settings not defined in the `spec.forProvider` of the managed resource. See [the late initialization]({{<ref "./managed-resources#late-initialization" >}}) section for more details. |
|
||||
| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{<ref "/knowledge-base/guides/import-existing-resources#import-resources-automatically">}}). |
|
||||
| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{<ref "../guides/import-existing-resources#import-resources-automatically">}}). |
|
||||
| `Update` | Crossplane changes the external resource when changing the managed resource. |
|
||||
{{</table >}}
|
||||
|
||||
|
@ -388,7 +388,7 @@ The following is a list of common policy combinations:
|
|||
| {{<check>}} | | {{<check>}} | {{<check>}} | | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't apply changes to the external resource after creation. |
|
||||
| {{<check>}} | | | {{<check>}} | {{<check>}} | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't import any settings from the external resource. |
|
||||
| {{<check>}} | | | {{<check>}} | | Crossplane creates the external resource but doesn't apply any changes to the external resource or managed resource. Crossplane can't delete the resource. |
|
||||
| | | | {{<check>}} | | Crossplane only observes a resource. Used for [observe only resources]({{<ref "/knowledge-base/guides/import-existing-resources#import-resources-automatically">}}). |
|
||||
| | | | {{<check>}} | | Crossplane only observes a resource. Used for [observe only resources]({{<ref "../guides/import-existing-resources#import-resources-automatically">}}). |
|
||||
| | | | | | No policy set. An alternative method for [pausing](#paused) a resource. |
|
||||
{{< /table >}}
|
||||
|
||||
|
@ -583,7 +583,7 @@ metadata:
|
|||
|
||||
{{<hint "tip" >}}
|
||||
Read the
|
||||
[Vault as an External Secrets Store]({{<ref "knowledge-base/integrations/vault-as-secret-store">}})
|
||||
[Vault as an External Secrets Store]({{<ref "../guides/vault-as-secret-store">}})
|
||||
guide for details on using StoreConfig objects.
|
||||
{{< /hint >}}
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@ If you remove the Provider first, you must manually delete external resources
|
|||
through your cloud provider. Managed resources must be manually deleted by
|
||||
removing their finalizers.
|
||||
|
||||
For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{<ref "/knowledge-base/guides/troubleshoot#deleting-when-a-resource-hangs" >}}).
|
||||
For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{<ref "../guides/troubleshoot-crossplane#deleting-when-a-resource-hangs" >}}).
|
||||
{{< /hint >}}
|
||||
|
||||
## Verify a Provider
|
||||
|
@ -324,7 +324,7 @@ defines the supported set of ControllerConfig settings.
|
|||
|
||||
The most common use case for ControllerConfigs are providing `args` to a
|
||||
Provider's pod enabling optional services. For example, enabling
|
||||
[external secret stores](https://docs.crossplane.io/knowledge-base/integrations/vault-as-secret-store/#enable-external-secret-stores-in-the-provider)
|
||||
[external secret stores]({{< ref "../guides/vault-as-secret-store#enable-external-secret-stores-in-the-provider" >}})
|
||||
for a Provider.
|
||||
|
||||
Each Provider determines their supported set of `args`.
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Guides
|
||||
weight: 400
|
||||
description: Crossplane integrations and detailed examples.
|
||||
---
|
|
@ -0,0 +1,216 @@
|
|||
---
|
||||
title: Configuring Crossplane with Argo CD
|
||||
weight: 270
|
||||
---
|
||||
|
||||
[Argo CD](https://argoproj.github.io/cd/) and [Crossplane](https://crossplane.io)
|
||||
are a great combination. Argo CD provides GitOps while Crossplane turns any Kubernetes
|
||||
cluster into a Universal Control Plane for all of your resources. There are
|
||||
configuration details required in order for the two to work together properly.
|
||||
This doc will help you understand these requirements. It is recommended to use
|
||||
Argo CD version 2.4.8 or later with Crossplane.
|
||||
|
||||
Argo CD synchronizes Kubernetes resource manifests stored in a Git repository
|
||||
with those running in a Kubernetes cluster (GitOps). There are different ways to configure
|
||||
how Argo CD tracks resources. With Crossplane, you need to configure Argo CD
|
||||
to use Annotation based resource tracking. See the [Argo CD docs](https://argo-cd.readthedocs.io/en/latest/user-guide/resource_tracking/) for additional detail.
|
||||
|
||||
### Configuring Argo CD with Crossplane
|
||||
|
||||
#### Set Resource Tracking Method
|
||||
|
||||
In order for Argo CD to correctly track Application resources that contain Crossplane related objects it needs
|
||||
to be configured to use the annotation mechanism.
|
||||
|
||||
To configure it, edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace` as such:
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
application.resourceTrackingMethod: annotation
|
||||
```
|
||||
|
||||
#### Set Health Status
|
||||
|
||||
Argo CD has a built-in health assessment for Kubernetes resources. Some checks are supported by the community directly
|
||||
in Argo's [repository](https://github.com/argoproj/argo-cd/tree/master/resource_customizations). For example the `Provider`
|
||||
from `pkg.crossplane.io` has already been declared which means there no further configuration needed.
|
||||
|
||||
Argo CD also enable customising these checks per instance, and that's the mechanism used to provide support
|
||||
of Provider's CRDs.
|
||||
|
||||
To configure it, edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace`.
|
||||
{{<hint "note">}}
|
||||
{{<hover label="argocfg" line="22">}} ProviderConfig{{</hover>}} may have no status or a `status.users` field.
|
||||
{{</hint>}}
|
||||
```yaml {label="argocfg"}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
application.resourceTrackingMethod: annotation
|
||||
resource.customizations: |
|
||||
"*.upbound.io/*":
|
||||
health.lua: |
|
||||
health_status = {
|
||||
status = "Progressing",
|
||||
message = "Provisioning ..."
|
||||
}
|
||||
|
||||
local function contains (table, val)
|
||||
for i, v in ipairs(table) do
|
||||
if v == val then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local has_no_status = {
|
||||
"ProviderConfig",
|
||||
"ProviderConfigUsage"
|
||||
}
|
||||
|
||||
if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is up-to-date."
|
||||
return health_status
|
||||
end
|
||||
|
||||
if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
|
||||
if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is in use."
|
||||
return health_status
|
||||
end
|
||||
return health_status
|
||||
end
|
||||
|
||||
for i, condition in ipairs(obj.status.conditions) do
|
||||
if condition.type == "LastAsyncOperation" then
|
||||
if condition.status == "False" then
|
||||
health_status.status = "Degraded"
|
||||
health_status.message = condition.message
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
|
||||
if condition.type == "Synced" then
|
||||
if condition.status == "False" then
|
||||
health_status.status = "Degraded"
|
||||
health_status.message = condition.message
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
|
||||
if condition.type == "Ready" then
|
||||
if condition.status == "True" then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is up-to-date."
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return health_status
|
||||
|
||||
"*.crossplane.io/*":
|
||||
health.lua: |
|
||||
health_status = {
|
||||
status = "Progressing",
|
||||
message = "Provisioning ..."
|
||||
}
|
||||
|
||||
local function contains (table, val)
|
||||
for i, v in ipairs(table) do
|
||||
if v == val then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local has_no_status = {
|
||||
"Composition",
|
||||
"CompositionRevision",
|
||||
"DeploymentRuntimeConfig",
|
||||
"ControllerConfig",
|
||||
"ProviderConfig",
|
||||
"ProviderConfigUsage"
|
||||
}
|
||||
if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is up-to-date."
|
||||
return health_status
|
||||
end
|
||||
|
||||
if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
|
||||
if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is in use."
|
||||
return health_status
|
||||
end
|
||||
return health_status
|
||||
end
|
||||
|
||||
for i, condition in ipairs(obj.status.conditions) do
|
||||
if condition.type == "LastAsyncOperation" then
|
||||
if condition.status == "False" then
|
||||
health_status.status = "Degraded"
|
||||
health_status.message = condition.message
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
|
||||
if condition.type == "Synced" then
|
||||
if condition.status == "False" then
|
||||
health_status.status = "Degraded"
|
||||
health_status.message = condition.message
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
|
||||
if contains({"Ready", "Healthy", "Offered", "Established"}, condition.type) then
|
||||
if condition.status == "True" then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is up-to-date."
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return health_status
|
||||
```
|
||||
|
||||
#### Set Resource Exclusion
|
||||
|
||||
Crossplane providers generates a `ProviderConfigUsage` for each of the managed resource (MR) it handles. This resource
|
||||
enable representing the relationship between MR and a ProviderConfig so that the controller can use it as finalizer when a
|
||||
ProviderConfig is deleted. End-users of Crossplane are not expected to interact with this resource.
|
||||
|
||||
Argo CD UI reactivity can be impacted as the number of resource and types grow. To help keep this number low we
|
||||
recommend hiding all `ProviderConfigUsage` resources from Argo CD UI.
|
||||
|
||||
To configure resource exclusion edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace` as such:
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
resource.exclusions: |
|
||||
- apiGroups:
|
||||
- "*"
|
||||
kinds:
|
||||
- ProviderConfigUsage
|
||||
```
|
||||
|
||||
The use of `"*"` as apiGroups will enable the mechanism for all Crossplane Providers.
|
||||
|
||||
#### Increase K8s Client QPS
|
||||
|
||||
As the number of CRDs grow on a control plane it will increase the amount of queries Argo CD Application Controller
|
||||
needs to send to the Kubernetes API. If this is the case you can increase the rate limits of the Argo CD Kubernetes client.
|
||||
|
||||
Set the environment variable `ARGOCD_K8S_CLIENT_QPS` to `300` for improved compatibility with a large number of CRDs.
|
||||
|
||||
The default value of `ARGOCD_K8S_CLIENT_QPS` is 50, modifying the value will also update `ARGOCD_K8S_CLIENT_BURST` as it
|
||||
is default to `ARGOCD_K8S_CLIENT_QPS` x 2.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Disaster Recovery with Crossplane
|
||||
weight: 10
|
||||
---
|
||||
|
||||
AWS wrote a guide covering disaster recovery with Crossplane. The guide covers
|
||||
using Crossplane to provision resources and Velero for Kubernetes backup and
|
||||
recovery.
|
||||
|
||||
[Read the guide on AWS](https://aws.amazon.com/blogs/opensource/disaster-recovery-when-using-crossplane-for-infrastructure-provisioning-on-aws/).
|
|
@ -0,0 +1,285 @@
|
|||
---
|
||||
title: Import Existing Resources
|
||||
weight: 200
|
||||
---
|
||||
|
||||
If you have resources that are already provisioned in a Provider,
|
||||
you can import them as managed resources and let Crossplane manage them.
|
||||
A managed resource's [`managementPolicies`]({{<ref "/v1.13/concepts/managed-resources#managementpolicies">}})
|
||||
field enables importing external resources into Crossplane.
|
||||
|
||||
Crossplane can import resources either [manually]({{<ref "#import-resources-manually">}})
|
||||
or [automatically]({{<ref "#import-resources-automatically">}}).
|
||||
|
||||
## Import resources manually
|
||||
|
||||
Crossplane can discover and import existing Provider resources by matching the
|
||||
`crossplane.io/external-name` annotation in a managed resource.
|
||||
|
||||
To import an existing external resource in a Provider, create a new managed
|
||||
resource with the `crossplane.io/external-name` annotation. Set the annotation
|
||||
value to the name of the resource in the Provider.
|
||||
|
||||
For example, to import an existing GCP Network named
|
||||
{{<hover label="annotation" line="5">}}my-existing-network{{</hover>}},
|
||||
create a new managed resource and use the
|
||||
{{<hover label="annotation" line="5">}}my-existing-network{{</hover>}} in the
|
||||
annotation.
|
||||
|
||||
```yaml {label="annotation",copy-lines="none"}
|
||||
apiVersion: compute.gcp.crossplane.io/v1beta1
|
||||
kind: Network
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/external-name: my-existing-network
|
||||
```
|
||||
|
||||
The {{<hover label="name" line="5">}}metadata.name{{</hover>}}
|
||||
field can be anything you want. For example,
|
||||
{{<hover label="name" line="5">}}imported-network{{</hover>}}.
|
||||
|
||||
{{< hint "note" >}}
|
||||
This name is the
|
||||
name of the Kubernetes object. It's not related to the resource name inside the
|
||||
Provider.
|
||||
{{< /hint >}}
|
||||
|
||||
```yaml {label="name",copy-lines="none"}
|
||||
apiVersion: compute.gcp.crossplane.io/v1beta1
|
||||
kind: Network
|
||||
metadata:
|
||||
name: imported-network
|
||||
annotations:
|
||||
crossplane.io/external-name: my-existing-network
|
||||
```
|
||||
|
||||
Leave the
|
||||
{{<hover label="fp" line="8">}}spec.forProvider{{</hover>}} field empty.
|
||||
Crossplane imports the settings and automatically applies them to the managed
|
||||
resource.
|
||||
|
||||
{{< hint "important" >}}
|
||||
If the managed resource has _required_ fields in the
|
||||
{{<hover label="fp" line="8">}}spec.forProvider{{</hover>}} you must add it to
|
||||
the `forProvider` field.
|
||||
|
||||
The values of those fields must match what's inside the Provider or Crossplane
|
||||
overwrites the existing values.
|
||||
{{< /hint >}}
|
||||
|
||||
```yaml {label="fp",copy-lines="all"}
|
||||
apiVersion: compute.gcp.crossplane.io/v1beta1
|
||||
kind: Network
|
||||
metadata:
|
||||
name: imported-network
|
||||
annotations:
|
||||
crossplane.io/external-name: my-existing-network
|
||||
spec:
|
||||
forProvider: {}
|
||||
```
|
||||
|
||||
|
||||
Crossplane now controls and manages this imported resource. Any changes to the
|
||||
managed resource `spec` changes the external resource.
|
||||
|
||||
## Import resources automatically
|
||||
|
||||
Automatically import external resources with an `Observe` [management policy]({{<ref "/v1.13/concepts/managed-resources#managementpolicies">}}).
|
||||
|
||||
Crossplane imports observe only resources but never changes or deletes the
|
||||
resources.
|
||||
|
||||
{{<hint "important" >}}
|
||||
The managed resource `managementPolicies` option is a beta feature.
|
||||
|
||||
The Provider determines support for management policies.
|
||||
Refer to the Provider's documentation to see if the Provider supports
|
||||
management policies.
|
||||
{{< /hint >}}
|
||||
|
||||
<!-- vale off -->
|
||||
### Apply the Observe management policy
|
||||
<!-- vale on -->
|
||||
|
||||
Create a new managed resource matching the
|
||||
{{<hover label="oo-policy" line="1">}}apiVersion{{</hover>}} and
|
||||
{{<hover label="oo-policy" line="2">}}kind{{</hover>}} of the resource
|
||||
to import and add
|
||||
{{<hover label="oo-policy" line="4">}}managementPolicies: ["Observe"]{{</hover>}} to the
|
||||
{{<hover label="oo-policy" line="3">}}spec{{</hover>}}
|
||||
|
||||
For example, to import a GCP SQL DatabaseInstance, create a new resource with
|
||||
the {{<hover label="oo-policy" line="4">}}managementPolicies: ["Observe"]{{</hover>}}
|
||||
set.
|
||||
```yaml {label="oo-policy",copy-lines="none"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
spec:
|
||||
managementPolicies: ["Observe"]
|
||||
```
|
||||
|
||||
### Add the external-name annotation
|
||||
Add the {{<hover label="oo-ex-name" line="5">}}crossplane.io/external-name{{</hover>}}
|
||||
annotation for the resource. This name must match the name inside the Provider.
|
||||
|
||||
For example, for a GCP database named
|
||||
{{<hover label="oo-ex-name" line="5">}}my-external-database{{</hover>}}, apply
|
||||
the
|
||||
{{<hover label="oo-ex-name" line="5">}}crossplane.io/external-name{{</hover>}}
|
||||
annotation with the value
|
||||
{{<hover label="oo-ex-name" line="5">}}my-external-database{{</hover>}}.
|
||||
|
||||
```yaml {label="oo-ex-name",copy-lines="none"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/external-name: my-external-database
|
||||
spec:
|
||||
managementPolicies: ["Observe"]
|
||||
```
|
||||
|
||||
### Create a Kubernetes object name
|
||||
Create a {{<hover label="oo-name" line="4">}}name{{</hover>}} to use for the
|
||||
Kubernetes object.
|
||||
|
||||
For example, name the Kubernetes object
|
||||
{{<hover label="oo-name" line="4">}}my-imported-database{{</hover>}}.
|
||||
|
||||
```yaml {label="oo-name",copy-lines="none"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
metadata:
|
||||
name: my-imported-database
|
||||
annotations:
|
||||
crossplane.io/external-name: my-external-database
|
||||
spec:
|
||||
managementPolicies: ["Observe"]
|
||||
```
|
||||
|
||||
### Identify a specific external resource
|
||||
If more than one resource inside the Provider shares the same name, identify the
|
||||
specific resource with a unique
|
||||
{{<hover line="9" label="oo-region">}}spec.forProvider{{</hover>}} field.
|
||||
|
||||
For example, only import the GCP SQL database in the
|
||||
{{<hover line="10" label="oo-region">}}us-central1{{</hover>}} region.
|
||||
|
||||
```yaml {label="oo-region"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
metadata:
|
||||
name: my-imported-database
|
||||
annotations:
|
||||
crossplane.io/external-name: my-external-database
|
||||
spec:
|
||||
managementPolicies: ["Observe"]
|
||||
forProvider:
|
||||
region: "us-central1"
|
||||
```
|
||||
|
||||
### Apply the managed resource
|
||||
|
||||
Apply the new managed resource. Crossplane syncs the status of the external
|
||||
resource in the cloud with the newly created managed resource.
|
||||
|
||||
### View the discovered resource
|
||||
Crossplane discovers the managed resource and populates the
|
||||
{{<hover label="ooPopulated" line="12">}}status.atProvider{{</hover>}}
|
||||
fields with the values from the external resource.
|
||||
|
||||
```yaml {label="ooPopulated",copy-lines="none"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
metadata:
|
||||
name: my-imported-database
|
||||
annotations:
|
||||
crossplane.io/external-name: my-external-database
|
||||
spec:
|
||||
managementPolicies: ["Observe"]
|
||||
forProvider:
|
||||
region: us-central1
|
||||
status:
|
||||
atProvider:
|
||||
connectionName: crossplane-playground:us-central1:my-external-database
|
||||
databaseVersion: POSTGRES_14
|
||||
deletionProtection: true
|
||||
firstIpAddress: 35.184.74.79
|
||||
id: my-external-database
|
||||
publicIpAddress: 35.184.74.79
|
||||
region: us-central1
|
||||
# Removed for brevity
|
||||
settings:
|
||||
- activationPolicy: ALWAYS
|
||||
availabilityType: REGIONAL
|
||||
diskSize: 100
|
||||
# Removed for brevity
|
||||
pricingPlan: PER_USE
|
||||
tier: db-custom-4-26624
|
||||
version: 4
|
||||
conditions:
|
||||
- lastTransitionTime: "2023-02-22T07:16:51Z"
|
||||
reason: Available
|
||||
status: "True"
|
||||
type: Ready
|
||||
- lastTransitionTime: "2023-02-22T07:16:51Z"
|
||||
reason: ReconcileSuccess
|
||||
status: "True"
|
||||
type: Synced
|
||||
```
|
||||
<!-- vale off -->
|
||||
## Control imported ObserveOnly resources
|
||||
<!-- vale on -->
|
||||
|
||||
Crossplane can take active control of observe only imported resources by
|
||||
changing the `managementPolicies` after import.
|
||||
|
||||
Change the {{<hover label="fc" line="8">}}managementPolicies{{</hover>}} field
|
||||
of the managed resource to
|
||||
{{<hover label="fc" line="8">}}["*"]{{</hover>}}.
|
||||
|
||||
Copy any required parameter values from
|
||||
{{<hover label="fc" line="16">}}status.atProvider{{</hover>}} and provide them
|
||||
in {{<hover label="fc" line="9">}}spec.forProvider{{</hover>}}.
|
||||
|
||||
{{< hint "tip" >}}
|
||||
Manually copy the important `spec.atProvider` values to `spec.forProvider`.
|
||||
{{< /hint >}}
|
||||
|
||||
```yaml {label="fc"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
metadata:
|
||||
name: my-imported-database
|
||||
annotations:
|
||||
crossplane.io/external-name: my-external-database
|
||||
spec:
|
||||
managementPolicies: ["*"]
|
||||
forProvider:
|
||||
databaseVersion: POSTGRES_14
|
||||
region: us-central1
|
||||
settings:
|
||||
- diskSize: 100
|
||||
tier: db-custom-4-26624
|
||||
status:
|
||||
atProvider:
|
||||
databaseVersion: POSTGRES_14
|
||||
region: us-central1
|
||||
# Removed for brevity
|
||||
settings:
|
||||
- diskSize: 100
|
||||
tier: db-custom-4-26624
|
||||
# Removed for brevity
|
||||
conditions:
|
||||
- lastTransitionTime: "2023-02-22T07:16:51Z"
|
||||
reason: Available
|
||||
status: "True"
|
||||
type: Ready
|
||||
- lastTransitionTime: "2023-02-22T11:16:45Z"
|
||||
reason: ReconcileSuccess
|
||||
status: "True"
|
||||
type: Synced
|
||||
```
|
||||
|
||||
Crossplane now fully manages the imported resource. Crossplane applies any
|
||||
changes to the managed resource in the Provider's external resource.
|
|
@ -0,0 +1,323 @@
|
|||
---
|
||||
title: Multi-Tenant Crossplane
|
||||
weight: 240
|
||||
---
|
||||
|
||||
This guide describes how to use Crossplane effectively in multi-tenant
|
||||
environments by utilizing Kubernetes primitives and compatible policy
|
||||
enforcement projects in the cloud-native ecosystem.
|
||||
|
||||
## TL;DR
|
||||
|
||||
Infrastructure operators in multi-tenant Crossplane environments typically
|
||||
utilize composition and Kubernetes RBAC to define lightweight, standardized
|
||||
policies that dictate what level of self-service developers are given when
|
||||
requesting infrastructure. This is primarily achieved through exposing abstract
|
||||
resource types at the namespace scope, defining `Roles` for teams and
|
||||
individuals within that namespace, and patching the `spec.providerConfigRef` of
|
||||
the underlying managed resources so that they use a specific `ProviderConfig`
|
||||
and credentials when provisioned from each namespace. Larger organizations, or
|
||||
those with more complex environments, may choose to incorporate third-party
|
||||
policy engines, or scale to multiple Crossplane clusters. The following sections
|
||||
describe each of these scenarios in greater detail.
|
||||
|
||||
- [TL;DR](#tldr)
|
||||
- [Background](#background)
|
||||
- [Cluster-Scoped Managed Resources](#cluster-scoped-managed-resources)
|
||||
- [Namespace Scoped Claims](#namespace-scoped-claims)
|
||||
- [Single Cluster Multi-Tenancy](#single-cluster-multi-tenancy)
|
||||
- [Composition as an Isolation Mechanism](#composition-as-an-isolation-mechanism)
|
||||
- [Namespaces as an Isolation Mechanism](#namespaces-as-an-isolation-mechanism)
|
||||
- [Policy Enforcement with Open Policy Agent](#policy-enforcement-with-open-policy-agent)
|
||||
- [Multi-Cluster Multi-Tenancy](#multi-cluster-multi-tenancy)
|
||||
- [Reproducible Platforms with Configuration Packages](#reproducible-platforms-with-configuration-packages)
|
||||
- [Control Plane of Control Planes](#control-plane-of-control-planes)
|
||||
|
||||
## Background
|
||||
|
||||
Crossplane is designed to run in multi-tenant environments where many teams are
|
||||
consuming the services and abstractions provided by infrastructure operators in
|
||||
the cluster. This functionality is facilitated by two major design patterns in
|
||||
the Crossplane ecosystem.
|
||||
|
||||
### Cluster-Scoped Managed Resources
|
||||
|
||||
Typically, Crossplane providers, which supply granular [managed resources] that
|
||||
reflect an external API, authenticate by using a `ProviderConfig` object that
|
||||
points to a credentials source (such as a Kubernetes `Secret`, the `Pod`
|
||||
filesystem, or an environment variable). Then, every managed resource references
|
||||
a `ProviderConfig` that points to credentials with sufficient permissions to
|
||||
manage that resource type.
|
||||
|
||||
For example, the following `ProviderConfig` for `provider-aws` points to a
|
||||
Kubernetes `Secret` with AWS credentials.
|
||||
|
||||
```yaml
|
||||
apiVersion: aws.crossplane.io/v1beta1
|
||||
kind: ProviderConfig
|
||||
metadata:
|
||||
name: cool-aws-creds
|
||||
spec:
|
||||
credentials:
|
||||
source: Secret
|
||||
secretRef:
|
||||
namespace: crossplane-system
|
||||
name: aws-creds
|
||||
key: creds
|
||||
```
|
||||
|
||||
If a user desired for these credentials to be used to provision an
|
||||
`RDSInstance`, they would reference the `ProviderConfig` in the object manifest:
|
||||
|
||||
```yaml
|
||||
apiVersion: database.aws.crossplane.io/v1beta1
|
||||
kind: RDSInstance
|
||||
metadata:
|
||||
name: rdsmysql
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-1
|
||||
dbInstanceClass: db.t3.medium
|
||||
masterUsername: masteruser
|
||||
allocatedStorage: 20
|
||||
engine: mysql
|
||||
engineVersion: "5.6.35"
|
||||
skipFinalSnapshotBeforeDeletion: true
|
||||
providerConfigRef:
|
||||
name: cool-aws-creds # name of ProviderConfig above
|
||||
writeConnectionSecretToRef:
|
||||
namespace: crossplane-system
|
||||
name: aws-rdsmysql-conn
|
||||
```
|
||||
|
||||
Since both the `ProviderConfig` and all managed resources are cluster-scoped,
|
||||
the RDS controller in `provider-aws` will resolve this reference by fetching the
|
||||
`ProviderConfig`, obtaining the credentials it points to, and using those
|
||||
credentials to reconcile the `RDSInstance`. This means that anyone who has been
|
||||
given [RBAC] to manage `RDSInstance` objects can use any credentials to do so.
|
||||
In practice, Crossplane assumes that only folks acting as infrastructure
|
||||
administrators or platform builders will interact directly with cluster-scoped
|
||||
resources.
|
||||
|
||||
### Namespace Scoped Claims
|
||||
|
||||
While managed resources exist at the cluster scope, composite resources, which
|
||||
are defined using a **CompositeResourceDefinition (XRD)** may exist at either
|
||||
the cluster or namespace scope. Platform builders define XRDs and
|
||||
**Compositions** that specify what granular managed resources should be created
|
||||
in response to the creation of an instance of the XRD. More information about
|
||||
this architecture can be found in the [Composition] documentation.
|
||||
|
||||
Every XRD is exposed at the cluster scope, but only those with `spec.claimNames`
|
||||
defined will have a namespace-scoped variant.
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: xmysqlinstances.example.org
|
||||
spec:
|
||||
group: example.org
|
||||
names:
|
||||
kind: XMySQLInstance
|
||||
plural: xmysqlinstances
|
||||
claimNames:
|
||||
kind: MySQLInstance
|
||||
plural: mysqlinstances
|
||||
...
|
||||
```
|
||||
|
||||
When the example above is created, Crossplane will produce two
|
||||
[CustomResourceDefinitions]:
|
||||
1. A cluster-scoped type with `kind: XMySQLInstance`. This is referred to as a
|
||||
**Composite Resource (XR)**.
|
||||
2. A namespace-scoped type with `kind: MySQLInstance`. This is referred to as a
|
||||
**Claim (XRC)**.
|
||||
|
||||
Platform builders may choose to define an arbitrary number of Compositions that
|
||||
map to these types, meaning that creating a `MySQLInstance` in a given namespace
|
||||
can result in the creations of any set of managed resources at the cluster
|
||||
scope. For instance, creating a `MySQLInstance` could result in the creation of
|
||||
the `RDSInstance` defined above.
|
||||
|
||||
## Single Cluster Multi-Tenancy
|
||||
|
||||
Depending on the size and scope of an organization, platform teams may choose to
|
||||
run one central Crossplane control plane, or many different ones for each team
|
||||
or business unit. This section will focus on servicing multiple teams within a
|
||||
single cluster, which may or may not be one of many other Crossplane clusters in
|
||||
the organization.
|
||||
|
||||
### Composition as an Isolation Mechanism
|
||||
|
||||
While managed resources always reflect every field that the underlying provider
|
||||
API exposes, XRDs can have any schema that a platform builder chooses. The
|
||||
fields in the XRD schema can then be patched onto fields in the underlying
|
||||
managed resource defined in a Composition, essentially exposing those fields as
|
||||
configurable to the consumer of the XR or XRC.
|
||||
|
||||
This feature serves as a lightweight policy mechanism by only giving the
|
||||
consumer the ability to customize the underlying resources to the extent the
|
||||
platform builder desires. For instance, in the examples above, a platform
|
||||
builder may choose to define a `spec.location` field in the schema of the
|
||||
`XMySQLInstance` that is an enum with options `east` and `west`. In the
|
||||
Composition, those fields could map to the `RDSInstance` `spec.region` field,
|
||||
making the value either `us-east-1` or `us-west-1`. If no other patches were
|
||||
defined for the `RDSInstance`, giving a user the ability (using RBAC) to create
|
||||
a `XMySQLInstance` / `MySQLInstance` would be akin to giving the ability to
|
||||
create a very specifically configured `RDSInstance`, where they can only decide
|
||||
the region where it lives and they are restricted to two options.
|
||||
|
||||
This model is in contrast to many infrastructure as code tools where the end
|
||||
user must have provider credentials to create the underlying resources that are
|
||||
rendered from the abstraction. Crossplane takes a different approach, defining
|
||||
various credentials in the cluster (using the `ProviderConfig`), then giving
|
||||
only the provider controllers the ability to utilize those credentials and
|
||||
provision infrastructure on the users behalf. This creates a consistent
|
||||
permission model, even when using many providers with differing IAM models, by
|
||||
standardizing on Kubernetes RBAC.
|
||||
|
||||
### Namespaces as an Isolation Mechanism
|
||||
|
||||
While the ability to define abstract schemas and patches to concrete resource
|
||||
types using composition is powerful, the ability to define Claim types at the
|
||||
namespace scope enhances the functionality further by enabling RBAC to be
|
||||
applied with namespace restrictions. Most users in a cluster do not have access
|
||||
to cluster-scoped resources as they are considered only relevant to
|
||||
infrastructure admins by both Kubernetes and Crossplane.
|
||||
|
||||
Building on our simple `XMySQLInstance` / `MySQLInstance` example, a platform
|
||||
builder may choose to define permissions on `MySQLInstance` at the namespace
|
||||
scope using a `Role`. This allows for giving users the ability to create and
|
||||
manage `MySQLInstances` in their given namespace, but not the ability to see
|
||||
those defined in other namespaces.
|
||||
|
||||
Furthermore, because the `metadata.namespace` is a field on the XRC, patching can
|
||||
be utilized to configure managed resources based on the namespace in which the
|
||||
corresponding XRC was defined. This is especially useful if a platform builder
|
||||
wants to designate specific credentials or a set of credentials that users in a
|
||||
given namespace can utilize when provisioning infrastructure using an XRC. This
|
||||
can be accomplished today by creating one or more `ProviderConfig` objects that
|
||||
include the name of the namespace in the `ProviderConfig` name. For example, if
|
||||
any `MySQLInstance` created in the `team-1` namespace should use specific AWS
|
||||
credentials when the provider controller creates the underlying `RDSInstance`,
|
||||
the platform builder could:
|
||||
|
||||
1. Define a `ProviderConfig` with name `team-1`.
|
||||
|
||||
```yaml
|
||||
apiVersion: aws.crossplane.io/v1beta1
|
||||
kind: ProviderConfig
|
||||
metadata:
|
||||
name: team-1
|
||||
spec:
|
||||
credentials:
|
||||
source: Secret
|
||||
secretRef:
|
||||
namespace: crossplane-system
|
||||
name: team-1-creds
|
||||
key: creds
|
||||
```
|
||||
|
||||
2. Define a `Composition` that patches the namespace of the Claim reference in the XR
|
||||
to the `providerConfigRef` of the `RDSInstance`.
|
||||
|
||||
```yaml
|
||||
...
|
||||
resources:
|
||||
- base:
|
||||
apiVersion: database.aws.crossplane.io/v1beta1
|
||||
kind: RDSInstance
|
||||
spec:
|
||||
forProvider:
|
||||
...
|
||||
patches:
|
||||
- fromFieldPath: spec.claimRef.namespace
|
||||
toFieldPath: spec.providerConfigRef.name
|
||||
policy:
|
||||
fromFieldPath: Required
|
||||
```
|
||||
|
||||
This would result in the `RDSInstance` using the `ProviderConfig` of whatever
|
||||
namespace the corresponding `MySQLInstance` was created in.
|
||||
|
||||
> Note that this model currently only allows for a single `ProviderConfig` per
|
||||
> namespace. However, future Crossplane releases should allow for defining a set
|
||||
> of `ProviderConfig` that can be selected from using [Multiple Source Field
|
||||
> patching].
|
||||
|
||||
### Policy Enforcement with Open Policy Agent
|
||||
|
||||
In some Crossplane deployment models, only using composition and RBAC to define
|
||||
policy will not be flexible enough. However, because Crossplane brings
|
||||
management of external infrastructure to the Kubernetes API, it is well suited
|
||||
to integrate with other projects in the cloud-native ecosystem. Organizations
|
||||
and individuals that need a more robust policy engine, or just prefer a more
|
||||
general language for defining policy, often turn to [Open Policy Agent] (OPA).
|
||||
OPA allows platform builders to write custom logic in [Rego], a domain-specific
|
||||
language. Writing policy in this manner allows for not only incorporating the
|
||||
information available in the specific resource being evaluated, but also using
|
||||
other state represented in the cluster. Crossplane users typically install OPA's
|
||||
[Gatekeeper] to make policy management as streamlined as possible.
|
||||
|
||||
> A live demo of using OPA with Crossplane can be viewed [here].
|
||||
|
||||
## Multi-Cluster Multi-Tenancy
|
||||
|
||||
Organizations that deploy Crossplane across many clusters typically take
|
||||
advantage of two major features that make managing multiple control planes much
|
||||
simpler.
|
||||
|
||||
### Reproducible Platforms with Configuration Packages
|
||||
|
||||
[Configuration packages] allow platform builders to package their XRDs and
|
||||
Compositions into [OCI images] that can be distributed via any OCI-compliant
|
||||
image registry. These packages can also declare dependencies on providers,
|
||||
meaning that a single package can declare all of the granular managed resources,
|
||||
the controllers that must be deployed to reconcile them, and the abstract types
|
||||
that expose the underlying resources using composition.
|
||||
|
||||
Organizations with many Crossplane deployments utilize Configuration packages to
|
||||
reproduce their platform in each cluster. This can be as simple as installing
|
||||
Crossplane with the flag to automatically install a Configuration package
|
||||
alongside it.
|
||||
|
||||
```
|
||||
helm install crossplane --namespace crossplane-system crossplane-stable/crossplane --set configuration.packages='{"registry.upbound.io/xp/getting-started-with-aws:latest"}'
|
||||
```
|
||||
|
||||
### Control Plane of Control Planes
|
||||
|
||||
Taking the multi-cluster multi-tenancy model one step further, some
|
||||
organizations opt to manage their many Crossplane clusters using a single
|
||||
central Crossplane control plane. This requires setting up the central cluster,
|
||||
then using a provider to spin up new clusters (such as an [EKS Cluster] using
|
||||
[provider-aws]), then using [provider-helm] to install Crossplane into the new
|
||||
remote cluster, potentially bundling a common Configuration package into each
|
||||
install using the method described above.
|
||||
|
||||
This advanced pattern allows for full management of Crossplane clusters using
|
||||
Crossplane itself, and when done properly, is a scalable solution to providing
|
||||
dedicated control planes to many tenants within a single organization.
|
||||
|
||||
|
||||
<!-- Named Links -->
|
||||
[managed resources]: {{<ref "../../master/concepts/managed-resources" >}}
|
||||
[RBAC]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/
|
||||
[Composition]: {{<ref "../../master/concepts/compositions" >}}
|
||||
[CustomResourceDefinitions]: https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/
|
||||
[Open Policy Agent]: https://www.openpolicyagent.org/
|
||||
[Rego]: https://www.openpolicyagent.org/docs/latest/policy-language/
|
||||
[Gatekeeper]: https://open-policy-agent.github.io/gatekeeper/website/docs/
|
||||
[here]: https://youtu.be/TaF0_syejXc
|
||||
[Multiple Source Field patching]: https://github.com/crossplane/crossplane/pull/2093
|
||||
[Configuration packages]: {{<ref "../../master/concepts/packages" >}}
|
||||
[OCI images]: https://github.com/opencontainers/image-spec
|
||||
[EKS Cluster]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws/latest/resources/eks.aws.crossplane.io/Cluster/v1beta1
|
||||
[provider-aws]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws
|
||||
[provider-helm]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-helm/
|
||||
[Open Service Broker API]: https://github.com/openservicebrokerapi/servicebroker
|
||||
[Crossplane Service Broker]: https://github.com/vshn/crossplane-service-broker
|
||||
[Cloudfoundry]: https://www.cloudfoundry.org/
|
||||
[Kubernetes Service Catalog]: https://github.com/kubernetes-sigs/service-catalog
|
||||
[vshn/application-catalog-demo]: https://github.com/vshn/application-catalog-demo
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
title: Self-Signed CA Certs
|
||||
weight: 270
|
||||
---
|
||||
|
||||
> Using self-signed certificates is not advised in production, it is
|
||||
recommended to only use self-signed certificates for testing.
|
||||
|
||||
When Crossplane loads Configuration and Provider Packages from private
|
||||
registries, it must be configured to trust the CA and Intermediate certs.
|
||||
|
||||
Crossplane needs to be installed via the Helm chart with the
|
||||
`registryCaBundleConfig.name` and `registryCaBundleConfig.key` parameters
|
||||
defined. See [Install Crossplane]({{<ref "../../master/software/install" >}}).
|
||||
|
||||
## Configure
|
||||
|
||||
1. Create a CA Bundle (A file containing your Root and Intermediate
|
||||
certificates in a specific order). This can be done with any text editor or
|
||||
from the command line, so long as the resulting file contains all required crt
|
||||
files in the proper order. In many cases, this will be either a single
|
||||
self-signed Root CA crt file, or an Intermediate crt and Root crt file. The
|
||||
order of the crt files should be from lowest to highest in signing order.
|
||||
For example, if you have a chain of two certificates below your Root
|
||||
certificate, you place the bottom level Intermediate cert at the beginning of
|
||||
the file, then the Intermediate cert that singed that cert, then the Root cert
|
||||
that signed that cert.
|
||||
|
||||
2. Save the files as `[yourdomain].ca-bundle`.
|
||||
|
||||
3. Create a Kubernetes ConfigMap in your Crossplane system namespace:
|
||||
|
||||
```
|
||||
kubectl -n [Crossplane system namespace] create cm ca-bundle-config \
|
||||
--from-file=ca-bundle=./[yourdomain].ca-bundle
|
||||
```
|
||||
|
||||
4. Set the `registryCaBundleConfig.name` Helm chart parameter to
|
||||
`ca-bundle-config` and the `registryCaBundleConfig.key` parameter to
|
||||
`ca-bundle`.
|
||||
|
||||
> Providing Helm with parameter values is convered in the Helm docs,
|
||||
[Helm install](https://helm.sh/docs/helm/helm_install/). An example block
|
||||
in an `override.yaml` file would look like this:
|
||||
```
|
||||
registryCaBundleConfig:
|
||||
name: ca-bundle-config
|
||||
key: ca-bundle
|
||||
```
|
|
@ -0,0 +1,627 @@
|
|||
---
|
||||
title: Vault as an External Secret Store
|
||||
weight: 230
|
||||
---
|
||||
|
||||
This guide walks through the steps required to configure Crossplane and
|
||||
its Providers to use [Vault] as an [External Secret Store] (`ESS`) with [ESS Plugin Vault].
|
||||
|
||||
{{<hint "warning" >}}
|
||||
External Secret Stores are an alpha feature.
|
||||
|
||||
They're not recommended for production use. Crossplane disables External Secret
|
||||
Stores by default.
|
||||
{{< /hint >}}
|
||||
|
||||
Crossplane uses sensitive information including Provider credentials, inputs to
|
||||
managed resources and connection details.
|
||||
|
||||
The [Vault credential injection guide]({{<ref "vault-injection" >}}) details
|
||||
using Vault and Crossplane for Provider credentials.
|
||||
|
||||
Crossplane doesn't support for using Vault for managed resources input.
|
||||
[Crossplane issue #2985](https://github.com/crossplane/crossplane/issues/2985)
|
||||
tracks support for this feature.
|
||||
|
||||
Supporting connection details with Vault requires a Crossplane external secret
|
||||
store.
|
||||
|
||||
## Prerequisites
|
||||
This guide requires [Helm](https://helm.sh) version 3.11 or later.
|
||||
|
||||
## Install Vault
|
||||
|
||||
{{<hint "note" >}}
|
||||
Detailed instructions on [installing
|
||||
Vault](https://developer.hashicorp.com/vault/docs/platform/k8s/helm)
|
||||
are available from the Vault documentation.
|
||||
{{< /hint >}}
|
||||
|
||||
### Add the Vault Helm chart
|
||||
|
||||
Add the Helm repository for `hashicorp`.
|
||||
```shell
|
||||
helm repo add hashicorp https://helm.releases.hashicorp.com --force-update
|
||||
```
|
||||
|
||||
Install Vault using Helm.
|
||||
```shell
|
||||
helm -n vault-system upgrade --install vault hashicorp/vault --create-namespace
|
||||
```
|
||||
|
||||
### Unseal Vault
|
||||
|
||||
If Vault is [sealed](https://developer.hashicorp.com/vault/docs/concepts/seal)
|
||||
unseal Vault using the unseal keys.
|
||||
|
||||
Get the Vault keys.
|
||||
```shell
|
||||
kubectl -n vault-system exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json
|
||||
VAULT_UNSEAL_KEY=$(cat cluster-keys.json | jq -r ".unseal_keys_b64[]")
|
||||
```
|
||||
|
||||
Unseal the vault using the keys.
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n vault-system exec vault-0 -- vault operator unseal $VAULT_UNSEAL_KEY
|
||||
Key Value
|
||||
--- -----
|
||||
Seal Type shamir
|
||||
Initialized true
|
||||
Sealed false
|
||||
Total Shares 1
|
||||
Threshold 1
|
||||
Version 1.13.1
|
||||
Build Date 2023-03-23T12:51:35Z
|
||||
Storage Type file
|
||||
Cluster Name vault-cluster-df884357
|
||||
Cluster ID b3145d26-2c1a-a7f2-a364-81753033c0d9
|
||||
HA Enabled false
|
||||
```
|
||||
|
||||
## Configure Vault Kubernetes authentication
|
||||
|
||||
Enable the [Kubernetes auth method] for Vault to authenticate requests based on
|
||||
Kubernetes service accounts.
|
||||
|
||||
### Get the Vault root token
|
||||
|
||||
The Vault root token is inside the JSON file created when
|
||||
[unsealing Vault](#unseal-vault).
|
||||
|
||||
```shell
|
||||
cat cluster-keys.json | jq -r ".root_token"
|
||||
```
|
||||
|
||||
### Enable Kubernetes authentication
|
||||
|
||||
Connect to a shell in the Vault pod.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n vault-system exec -it vault-0 -- /bin/sh
|
||||
/ $
|
||||
```
|
||||
|
||||
From the Vault shell, login to Vault using the _root token_.
|
||||
```shell {copy-lines="1"}
|
||||
vault login # use the root token from above
|
||||
Token (will be hidden):
|
||||
Success! You are now authenticated. The token information displayed below
|
||||
is already stored in the token helper. You do NOT need to run "vault login"
|
||||
again. Future Vault requests will automatically use this token.
|
||||
|
||||
Key Value
|
||||
--- -----
|
||||
token hvs.TSN4SssfMBM0HAtwGrxgARgn
|
||||
token_accessor qodxHrINVlRXKyrGeeDkxnih
|
||||
token_duration ∞
|
||||
token_renewable false
|
||||
token_policies ["root"]
|
||||
identity_policies []
|
||||
policies ["root"]
|
||||
```
|
||||
|
||||
Enable the Kubernetes authentication method in Vault.
|
||||
```shell {copy-lines="1"}
|
||||
vault auth enable kubernetes
|
||||
Success! Enabled kubernetes auth method at: kubernetes/
|
||||
```
|
||||
|
||||
Configure Vault to communicate with Kubernetes and exit the Vault shell
|
||||
|
||||
```shell {copy-lines="1-4"}
|
||||
vault write auth/kubernetes/config \
|
||||
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
|
||||
kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
|
||||
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
Success! Data written to: auth/kubernetes/config
|
||||
/ $ exit
|
||||
```
|
||||
|
||||
## Configure Vault for Crossplane integration
|
||||
|
||||
Crossplane relies on the Vault key-value secrets engine to store information and
|
||||
Vault requires a permissions policy for the Crossplane service account.
|
||||
|
||||
<!-- vale Crossplane.Spelling = NO -->
|
||||
<!-- allow "kv" -->
|
||||
### Enable the Vault kv secrets engine
|
||||
<!-- vale Crossplane.Spelling = YES -->
|
||||
|
||||
Enable the [Vault KV Secrets Engine].
|
||||
|
||||
{{< hint "important" >}}
|
||||
Vault has two versions of the
|
||||
[KV Secrets Engine](https://developer.hashicorp.com/vault/docs/secrets/kv).
|
||||
This example uses version 2.
|
||||
{{</hint >}}
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n vault-system exec -it vault-0 -- vault secrets enable -path=secret kv-v2
|
||||
Success! Enabled the kv-v2 secrets engine at: secret/
|
||||
```
|
||||
|
||||
### Create a Vault policy for Crossplane
|
||||
|
||||
Create the Vault policy to allow Crossplane to read and write data from Vault.
|
||||
```shell {copy-lines="1-8"}
|
||||
kubectl -n vault-system exec -i vault-0 -- vault policy write crossplane - <<EOF
|
||||
path "secret/data/*" {
|
||||
capabilities = ["create", "read", "update", "delete"]
|
||||
}
|
||||
path "secret/metadata/*" {
|
||||
capabilities = ["create", "read", "update", "delete"]
|
||||
}
|
||||
EOF
|
||||
Success! Uploaded policy: crossplane
|
||||
```
|
||||
|
||||
Apply the policy to Vault.
|
||||
```shell {copy-lines="1-5"}
|
||||
kubectl -n vault-system exec -it vault-0 -- vault write auth/kubernetes/role/crossplane \
|
||||
bound_service_account_names="*" \
|
||||
bound_service_account_namespaces=crossplane-system \
|
||||
policies=crossplane \
|
||||
ttl=24h
|
||||
Success! Data written to: auth/kubernetes/role/crossplane
|
||||
```
|
||||
|
||||
## Install Crossplane
|
||||
|
||||
{{<hint "important" >}}
|
||||
Crossplane v1.12 introduced the plugin support. Make sure your version of Crossplane supports plugins.
|
||||
{{< /hint >}}
|
||||
|
||||
Install the Crossplane with the External Secrets Stores feature enabled.
|
||||
|
||||
```shell
|
||||
helm upgrade --install crossplane crossplane-stable/crossplane --namespace crossplane-system --create-namespace --set args='{--enable-external-secret-stores}'
|
||||
```
|
||||
|
||||
## Install the Crossplane Vault plugin
|
||||
|
||||
The Crossplane Vault plugin isn't part of the default Crossplane install.
|
||||
The plugin installs as a unique Pod that uses the [Vault Agent Sidecar
|
||||
Injection] to connect the Vault secret store to Crossplane.
|
||||
|
||||
First, configure annotations for the Vault plugin pod.
|
||||
|
||||
```yaml
|
||||
cat > values.yaml <<EOF
|
||||
podAnnotations:
|
||||
vault.hashicorp.com/agent-inject: "true"
|
||||
vault.hashicorp.com/agent-inject-token: "true"
|
||||
vault.hashicorp.com/role: crossplane
|
||||
vault.hashicorp.com/agent-run-as-user: "65532"
|
||||
EOF
|
||||
```
|
||||
Next, install the Crossplane ESS Plugin pod to the `crossplane-system` namespace
|
||||
and apply the Vault annotations.
|
||||
|
||||
```shell
|
||||
helm upgrade --install ess-plugin-vault oci://xpkg.upbound.io/crossplane-contrib/ess-plugin-vault --namespace crossplane-system -f values.yaml
|
||||
```
|
||||
|
||||
## Configure Crossplane
|
||||
|
||||
Using the Vault plugin requires configuration to connect to the Vault
|
||||
service. The plugin also requires Providers to enable external secret stores.
|
||||
|
||||
With the plugin and providers configured, Crossplane requires two `StoreConfig`
|
||||
objects to describe how Crossplane and the Providers communicate with vault.
|
||||
|
||||
### Enable external secret stores in the Provider
|
||||
|
||||
{{<hint "note">}}
|
||||
This example uses Provider GCP, but the
|
||||
{{<hover label="ControllerConfig" line="2">}}ControllerConfig{{</hover>}} is the
|
||||
same for all Providers.
|
||||
{{</hint >}}
|
||||
|
||||
Create a `ControllerConfig` object to enable external secret stores.
|
||||
|
||||
```yaml {label="ControllerConfig"}
|
||||
echo "apiVersion: pkg.crossplane.io/v1alpha1
|
||||
kind: ControllerConfig
|
||||
metadata:
|
||||
name: vault-config
|
||||
spec:
|
||||
args:
|
||||
- --enable-external-secret-stores" | kubectl apply -f -
|
||||
```
|
||||
|
||||
Install the Provider and apply the ControllerConfig.
|
||||
```yaml
|
||||
echo "apiVersion: pkg.crossplane.io/v1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: provider-gcp
|
||||
spec:
|
||||
package: xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.23.0-rc.0.19.ge9b75ee5
|
||||
controllerConfigRef:
|
||||
name: vault-config" | kubectl apply -f -
|
||||
```
|
||||
|
||||
### Connect the Crossplane plugin to Vault
|
||||
Create a {{<hover label="VaultConfig" line="2">}}VaultConfig{{</hover>}}
|
||||
resource for the plugin to connect to the Vault service:
|
||||
|
||||
```yaml {label="VaultConfig"}
|
||||
echo "apiVersion: secrets.crossplane.io/v1alpha1
|
||||
kind: VaultConfig
|
||||
metadata:
|
||||
name: vault-internal
|
||||
spec:
|
||||
server: http://vault.vault-system:8200
|
||||
mountPath: secret/
|
||||
version: v2
|
||||
auth:
|
||||
method: Token
|
||||
token:
|
||||
source: Filesystem
|
||||
fs:
|
||||
path: /vault/secrets/token" | kubectl apply -f -
|
||||
```
|
||||
|
||||
### Create a Crossplane StoreConfig
|
||||
|
||||
Create a {{<hover label="xp-storeconfig" line="2">}}StoreConfig{{</hover >}}
|
||||
object from the
|
||||
{{<hover label="xp-storeconfig" line="1">}}secrets.crossplane.io{{</hover >}}
|
||||
group. Crossplane uses the StoreConfig to connect to the Vault plugin service.
|
||||
|
||||
The {{<hover label="xp-storeconfig" line="10">}}configRef{{</hover >}} connects
|
||||
the StoreConfig to the specific Vault plugin configuration.
|
||||
|
||||
```yaml {label="xp-storeconfig"}
|
||||
echo "apiVersion: secrets.crossplane.io/v1alpha1
|
||||
kind: StoreConfig
|
||||
metadata:
|
||||
name: vault
|
||||
spec:
|
||||
type: Plugin
|
||||
defaultScope: crossplane-system
|
||||
plugin:
|
||||
endpoint: ess-plugin-vault.crossplane-system:4040
|
||||
configRef:
|
||||
apiVersion: secrets.crossplane.io/v1alpha1
|
||||
kind: VaultConfig
|
||||
name: vault-internal" | kubectl apply -f -
|
||||
```
|
||||
|
||||
|
||||
### Create a Provider StoreConfig
|
||||
Create a {{<hover label="gcp-storeconfig" line="2">}}StoreConfig{{</hover >}}
|
||||
object from the Provider's API group,
|
||||
{{<hover label="gcp-storeconfig" line="1">}}gcp.crossplane.io{{</hover >}}.
|
||||
The Provider uses this StoreConfig to communicate with Vault for
|
||||
Managed Resources.
|
||||
|
||||
The {{<hover label="gcp-storeconfig" line="10">}}configRef{{</hover >}} connects
|
||||
the StoreConfig to the specific Vault plugin configuration.
|
||||
|
||||
```yaml {label="gcp-storeconfig"}
|
||||
echo "apiVersion: gcp.crossplane.io/v1alpha1
|
||||
kind: StoreConfig
|
||||
metadata:
|
||||
name: vault
|
||||
spec:
|
||||
type: Plugin
|
||||
defaultScope: crossplane-system
|
||||
plugin:
|
||||
endpoint: ess-plugin-vault.crossplane-system:4040
|
||||
configRef:
|
||||
apiVersion: secrets.crossplane.io/v1alpha1
|
||||
kind: VaultConfig
|
||||
name: vault-internal" | kubectl apply -f -
|
||||
```
|
||||
|
||||
## Create Provider resources
|
||||
|
||||
Check that Crossplane installed the Provider and the Provider is healthy.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get providers
|
||||
NAME INSTALLED HEALTHY PACKAGE AGE
|
||||
provider-gcp True True xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.23.0-rc.0.19.ge9b75ee5 10m
|
||||
```
|
||||
|
||||
### Create a CompositeResourceDefinition
|
||||
|
||||
Create a `CompositeResourceDefinition` to define a custom API endpoint.
|
||||
|
||||
```yaml
|
||||
echo "apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: compositeessinstances.ess.example.org
|
||||
annotations:
|
||||
feature: ess
|
||||
spec:
|
||||
group: ess.example.org
|
||||
names:
|
||||
kind: CompositeESSInstance
|
||||
plural: compositeessinstances
|
||||
claimNames:
|
||||
kind: ESSInstance
|
||||
plural: essinstances
|
||||
connectionSecretKeys:
|
||||
- publicKey
|
||||
- publicKeyType
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
referenceable: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
serviceAccount:
|
||||
type: string
|
||||
required:
|
||||
- serviceAccount
|
||||
required:
|
||||
- parameters" | kubectl apply -f -
|
||||
```
|
||||
|
||||
### Create a Composition
|
||||
Create a `Composition` to create a Service Account and Service Account Key
|
||||
inside GCP.
|
||||
|
||||
Creating a Service Account Key generates
|
||||
{{<hover label="comp" line="39" >}}connectionDetails{{</hover>}} that the
|
||||
Provider stores in Vault using the
|
||||
{{<hover label="comp" line="31">}}publishConnectionDetailsTo{{</hover>}} details.
|
||||
|
||||
```yaml {label="comp"}
|
||||
echo "apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: essinstances.ess.example.org
|
||||
labels:
|
||||
feature: ess
|
||||
spec:
|
||||
publishConnectionDetailsWithStoreConfigRef:
|
||||
name: vault
|
||||
compositeTypeRef:
|
||||
apiVersion: ess.example.org/v1alpha1
|
||||
kind: CompositeESSInstance
|
||||
resources:
|
||||
- name: serviceaccount
|
||||
base:
|
||||
apiVersion: iam.gcp.crossplane.io/v1alpha1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: ess-test-sa
|
||||
spec:
|
||||
forProvider:
|
||||
displayName: a service account to test ess
|
||||
- name: serviceaccountkey
|
||||
base:
|
||||
apiVersion: iam.gcp.crossplane.io/v1alpha1
|
||||
kind: ServiceAccountKey
|
||||
spec:
|
||||
forProvider:
|
||||
serviceAccountSelector:
|
||||
matchControllerRef: true
|
||||
publishConnectionDetailsTo:
|
||||
name: ess-mr-conn
|
||||
metadata:
|
||||
labels:
|
||||
environment: development
|
||||
team: backend
|
||||
configRef:
|
||||
name: vault
|
||||
connectionDetails:
|
||||
- fromConnectionSecretKey: publicKey
|
||||
- fromConnectionSecretKey: publicKeyType" | kubectl apply -f -
|
||||
```
|
||||
|
||||
### Create a Claim
|
||||
Now create a `Claim` to have Crossplane create the GCP resources and associated
|
||||
secrets.
|
||||
|
||||
Like the Composition, the Claim uses
|
||||
{{<hover label="claim" line="12">}}publishConnectionDetailsTo{{</hover>}} to
|
||||
connect to Vault and store the secrets.
|
||||
|
||||
```yaml {label="claim"}
|
||||
echo "apiVersion: ess.example.org/v1alpha1
|
||||
kind: ESSInstance
|
||||
metadata:
|
||||
name: my-ess
|
||||
namespace: default
|
||||
spec:
|
||||
parameters:
|
||||
serviceAccount: ess-test-sa
|
||||
compositionSelector:
|
||||
matchLabels:
|
||||
feature: ess
|
||||
publishConnectionDetailsTo:
|
||||
name: ess-claim-conn
|
||||
metadata:
|
||||
labels:
|
||||
environment: development
|
||||
team: backend
|
||||
configRef:
|
||||
name: vault" | kubectl apply -f -
|
||||
```
|
||||
|
||||
## Verify the resources
|
||||
|
||||
Verify all resources are `READY` and `SYNCED`:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get managed
|
||||
NAME READY SYNCED DISPLAYNAME EMAIL DISABLED
|
||||
serviceaccount.iam.gcp.crossplane.io/my-ess-zvmkz-vhklg True True a service account to test ess my-ess-zvmkz-vhklg@testingforbugbounty.iam.gserviceaccount.com
|
||||
|
||||
NAME READY SYNCED KEY_ID CREATED_AT EXPIRES_AT
|
||||
serviceaccountkey.iam.gcp.crossplane.io/my-ess-zvmkz-bq8pz True True 5cda49b7c32393254b5abb121b4adc07e140502c 2022-03-23T10:54:50Z
|
||||
```
|
||||
|
||||
View the claims
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n default get claim
|
||||
NAME READY CONNECTION-SECRET AGE
|
||||
my-ess True 19s
|
||||
```
|
||||
|
||||
View the composite resources.
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get composite
|
||||
NAME READY COMPOSITION AGE
|
||||
my-ess-zvmkz True essinstances.ess.example.org 32s
|
||||
```
|
||||
|
||||
## Verify Vault secrets
|
||||
|
||||
Look inside Vault to view the secrets from the managed resources.
|
||||
|
||||
```shell {copy-lines="1",label="vault-key"}
|
||||
kubectl -n vault-system exec -i vault-0 -- vault kv list /secret/default
|
||||
Keys
|
||||
----
|
||||
ess-claim-conn
|
||||
```
|
||||
|
||||
The key {{<hover label="vault-key" line="4">}}ess-claim-conn{{</hover>}}
|
||||
is the name of the Claim's
|
||||
{{<hover label="claim" line="12">}}publishConnectionDetailsTo{{</hover>}}
|
||||
configuration.
|
||||
|
||||
Check connection secrets in the "crossplane-system" Vault scope.
|
||||
```shell {copy-lines="1",label="scope-key"}
|
||||
kubectl -n vault-system exec -i vault-0 -- vault kv list /secret/crossplane-system
|
||||
Keys
|
||||
----
|
||||
d2408335-eb88-4146-927b-8025f405da86
|
||||
ess-mr-conn
|
||||
```
|
||||
|
||||
The key
|
||||
{{<hover label="scope-key"line="4">}}d2408335-eb88-4146-927b-8025f405da86{{</hover>}}
|
||||
comes from
|
||||
|
||||
<!-- ## WHERE DOES IT COME FROM? -->
|
||||
|
||||
and the key
|
||||
{{<hover label="scope-key"line="5">}}ess-mr-conn{{</hover>}}
|
||||
comes from the Composition's
|
||||
{{<hover label="comp" line="31">}}publishConnectionDetailsTo{{</hover>}}
|
||||
configuration.
|
||||
|
||||
|
||||
Check contents of Claim's connection secret `ess-claim-conn` to see the key
|
||||
created by the managed resource.
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n vault-system exec -i vault-0 -- vault kv get /secret/default/ess-claim-conn
|
||||
======= Metadata =======
|
||||
Key Value
|
||||
--- -----
|
||||
created_time 2022-03-18T21:24:07.2085726Z
|
||||
custom_metadata map[environment:development secret.crossplane.io/ner-uid:881cd9a0-6cc6-418f-8e1d-b36062c1e108 team:backend]
|
||||
deletion_time n/a
|
||||
destroyed false
|
||||
version 1
|
||||
|
||||
======== Data ========
|
||||
Key Value
|
||||
--- -----
|
||||
publicKey -----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzsEYCokmYEsZJCc9QN/8
|
||||
Fm1M/kTPp7Gat/MXLTP3zFyCTBFVNLN79MbAKdinWi6ePXEb75vzB79IdZcWj8lo
|
||||
8trnS64QjNB9Vs4Xk5UvDALwleFN/bZeperxivDPwVPvT9Aqy/U9kohoS/LHyE8w
|
||||
uWQb5AuMeVQ1gtCTnCqQZ4d2MSVhQXYVvAWax1spJ9LT7mHub5j95xDdYIcOV3VJ
|
||||
l9CIo4VrWIT8THFN2NnjTrGq9+0TzXY0bV674bjJkfBC6v6yXs5HTetG+Uekq/xf
|
||||
FCjrrDi1+2UR9Mu2WTuvl8qn50be+mbwdJO5wE32jewxdYrVVmj19+PkaEeAwGTc
|
||||
vwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
publicKeyType TYPE_RAW_PUBLIC_KEY
|
||||
```
|
||||
|
||||
Check contents of managed resource connection secret `ess-mr-conn`. The public
|
||||
key is identical to the public key in the Claim since the Claim is using this
|
||||
managed resource.
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n vault-system exec -i vault-0 -- vault kv get /secret/crossplane-system/ess-mr-conn
|
||||
======= Metadata =======
|
||||
Key Value
|
||||
--- -----
|
||||
created_time 2022-03-18T21:21:07.9298076Z
|
||||
custom_metadata map[environment:development secret.crossplane.io/ner-uid:4cd973f8-76fc-45d6-ad45-0b27b5e9252a team:backend]
|
||||
deletion_time n/a
|
||||
destroyed false
|
||||
version 2
|
||||
|
||||
========= Data =========
|
||||
Key Value
|
||||
--- -----
|
||||
privateKey {
|
||||
"type": "service_account",
|
||||
"project_id": "REDACTED",
|
||||
"private_key_id": "REDACTED",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nREDACTED\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "ess-test-sa@REDACTED.iam.gserviceaccount.com",
|
||||
"client_id": "REDACTED",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/ess-test-sa%40REDACTED.iam.gserviceaccount.com"
|
||||
}
|
||||
privateKeyType TYPE_GOOGLE_CREDENTIALS_FILE
|
||||
publicKey -----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzsEYCokmYEsZJCc9QN/8
|
||||
Fm1M/kTPp7Gat/MXLTP3zFyCTBFVNLN79MbAKdinWi6ePXEb75vzB79IdZcWj8lo
|
||||
8trnS64QjNB9Vs4Xk5UvDALwleFN/bZeperxivDPwVPvT9Aqy/U9kohoS/LHyE8w
|
||||
uWQb5AuMeVQ1gtCTnCqQZ4d2MSVhQXYVvAWax1spJ9LT7mHub5j95xDdYIcOV3VJ
|
||||
l9CIo4VrWIT8THFN2NnjTrGq9+0TzXY0bV674bjJkfBC6v6yXs5HTetG+Uekq/xf
|
||||
FCjrrDi1+2UR9Mu2WTuvl8qn50be+mbwdJO5wE32jewxdYrVVmj19+PkaEeAwGTc
|
||||
vwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
publicKeyType TYPE_RAW_PUBLIC_KEY
|
||||
```
|
||||
|
||||
### Remove the resources
|
||||
|
||||
Deleting the Claim removes the managed resources and associated keys from Vault.
|
||||
|
||||
```shell
|
||||
kubectl delete claim my-ess
|
||||
```
|
||||
|
||||
<!-- named links -->
|
||||
|
||||
[Vault]: https://www.vaultproject.io/
|
||||
[External Secret Store]: https://github.com/crossplane/crossplane/blob/master/design/design-doc-external-secret-stores.md
|
||||
[this issue]: https://github.com/crossplane/crossplane/issues/2985
|
||||
[Kubernetes Auth Method]: https://www.vaultproject.io/docs/auth/kubernetes
|
||||
[Unseal]: https://www.vaultproject.io/docs/concepts/seal
|
||||
[Vault KV Secrets Engine]: https://developer.hashicorp.com/vault/docs/secrets/kv
|
||||
[Vault Agent Sidecar Injection]: https://www.vaultproject.io/docs/platform/k8s/injector
|
||||
[ESS Plugin Vault]: https://github.com/crossplane-contrib/ess-plugin-vault
|
|
@ -0,0 +1,506 @@
|
|||
---
|
||||
title: Vault Credential Injection
|
||||
weight: 230
|
||||
---
|
||||
|
||||
|
||||
> This guide is adapted from the [Vault on Minikube] and [Vault Kubernetes
|
||||
> Sidecar] guides.
|
||||
|
||||
Most Crossplane providers support supplying credentials from at least the
|
||||
following sources:
|
||||
- Kubernetes Secret
|
||||
- Environment Variable
|
||||
- Filesystem
|
||||
|
||||
A provider may optionally support additional credentials sources, but the common
|
||||
sources cover a wide variety of use cases. One specific use case that is popular
|
||||
among organizations that use [Vault] for secrets management is using a sidecar
|
||||
to inject credentials into the filesystem. This guide will demonstrate how to
|
||||
use the [Vault Kubernetes Sidecar] to provide credentials for [provider-gcp]
|
||||
and [provider-aws].
|
||||
|
||||
> Note: in this guide we will copy GCP credentials and AWS access keys
|
||||
> into Vault's KV secrets engine. This is a simple generic approach to
|
||||
> managing secrets with Vault, but is not as robust as using Vault's
|
||||
> dedicated cloud provider secrets engines for [AWS], [Azure], and [GCP].
|
||||
|
||||
## Setup
|
||||
|
||||
> Note: this guide walks through setting up Vault running in the same cluster as
|
||||
> Crossplane. You may also choose to use an existing Vault instance that runs
|
||||
> outside the cluster but has Kubernetes authentication enabled.
|
||||
|
||||
Before getting started, you must ensure that you have installed Crossplane and
|
||||
Vault and that they are running in your cluster.
|
||||
|
||||
1. Install Crossplane
|
||||
|
||||
```console
|
||||
kubectl create namespace crossplane-system
|
||||
|
||||
helm repo add crossplane-stable https://charts.crossplane.io/stable
|
||||
helm repo update
|
||||
|
||||
helm install crossplane --namespace crossplane-system crossplane-stable/crossplane
|
||||
```
|
||||
|
||||
2. Install Vault Helm Chart
|
||||
|
||||
```console
|
||||
helm repo add hashicorp https://helm.releases.hashicorp.com
|
||||
helm install vault hashicorp/vault
|
||||
```
|
||||
|
||||
3. Unseal Vault Instance
|
||||
|
||||
In order for Vault to access encrypted data from physical storage, it must be
|
||||
[unsealed].
|
||||
|
||||
```console
|
||||
kubectl exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json
|
||||
VAULT_UNSEAL_KEY=$(cat cluster-keys.json | jq -r ".unseal_keys_b64[]")
|
||||
kubectl exec vault-0 -- vault operator unseal $VAULT_UNSEAL_KEY
|
||||
```
|
||||
|
||||
4. Enable Kubernetes Authentication Method
|
||||
|
||||
In order for Vault to be able to authenticate requests based on Kubernetes
|
||||
service accounts, the [Kubernetes authentication backend] must be enabled. This
|
||||
requires logging in to Vault and configuring it with a service account token,
|
||||
API server address, and certificate. Because we are running Vault in Kubernetes,
|
||||
these values are already available via the container filesystem and environment
|
||||
variables.
|
||||
|
||||
```console
|
||||
cat cluster-keys.json | jq -r ".root_token" # get root token
|
||||
|
||||
kubectl exec -it vault-0 -- /bin/sh
|
||||
vault login # use root token from above
|
||||
vault auth enable kubernetes
|
||||
|
||||
vault write auth/kubernetes/config \
|
||||
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
|
||||
kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
|
||||
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
```
|
||||
|
||||
5. Exit Vault Container
|
||||
|
||||
The next steps will be executed in your local environment.
|
||||
|
||||
```console
|
||||
exit
|
||||
```
|
||||
|
||||
{{< tabs >}}
|
||||
{{< tab "GCP" >}}
|
||||
|
||||
## Create GCP Service Account
|
||||
|
||||
In order to provision infrastructure on GCP, you will need to create a service
|
||||
account with appropriate permissions. In this guide we will only provision a
|
||||
CloudSQL instance, so the service account will be bound to the `cloudsql.admin`
|
||||
role. The following steps will setup a GCP service account, give it the
|
||||
necessary permissions for Crossplane to be able to manage CloudSQL instances,
|
||||
and emit the service account credentials in a JSON file.
|
||||
|
||||
```console
|
||||
# replace this with your own gcp project id and the name of the service account
|
||||
# that will be created.
|
||||
PROJECT_ID=my-project
|
||||
NEW_SA_NAME=test-service-account-name
|
||||
|
||||
# create service account
|
||||
SA="${NEW_SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
|
||||
gcloud iam service-accounts create $NEW_SA_NAME --project $PROJECT_ID
|
||||
|
||||
# enable cloud API
|
||||
SERVICE="sqladmin.googleapis.com"
|
||||
gcloud services enable $SERVICE --project $PROJECT_ID
|
||||
|
||||
# grant access to cloud API
|
||||
ROLE="roles/cloudsql.admin"
|
||||
gcloud projects add-iam-policy-binding --role="$ROLE" $PROJECT_ID --member "serviceAccount:$SA"
|
||||
|
||||
# create service account keyfile
|
||||
gcloud iam service-accounts keys create creds.json --project $PROJECT_ID --iam-account $SA
|
||||
```
|
||||
|
||||
You should now have valid service account credentials in `creds.json`.
|
||||
|
||||
## Store Credentials in Vault
|
||||
|
||||
After setting up Vault, you will need to store your credentials in the [kv
|
||||
secrets engine].
|
||||
|
||||
> Note: the steps below involve copying credentials into the container
|
||||
> filesystem before storing them in Vault. You may also choose to use Vault's
|
||||
> HTTP API or UI by port-forwarding the container to your local environment
|
||||
> (`kubectl port-forward vault-0 8200:8200`).
|
||||
|
||||
1. Copy Credentials File into Vault Container
|
||||
|
||||
Copy your credentials into the container filesystem so that your can store them
|
||||
in Vault.
|
||||
|
||||
```console
|
||||
kubectl cp creds.json vault-0:/tmp/creds.json
|
||||
```
|
||||
|
||||
2. Enable KV Secrets Engine
|
||||
|
||||
Secrets engines must be enabled before they can be used. Enable the `kv-v2`
|
||||
secrets engine at the `secret` path.
|
||||
|
||||
```console
|
||||
kubectl exec -it vault-0 -- /bin/sh
|
||||
|
||||
vault secrets enable -path=secret kv-v2
|
||||
```
|
||||
|
||||
3. Store GCP Credentials in KV Engine
|
||||
|
||||
The path of your GCP credentials is how the secret will be referenced when
|
||||
injecting it into the `provider-gcp` controller `Pod`.
|
||||
|
||||
```console
|
||||
vault kv put secret/provider-creds/gcp-default @tmp/creds.json
|
||||
```
|
||||
|
||||
4. Clean Up Credentials File
|
||||
|
||||
You no longer need our GCP credentials file in the container filesystem, so go
|
||||
ahead and clean it up.
|
||||
|
||||
```console
|
||||
rm tmp/creds.json
|
||||
```
|
||||
|
||||
{{< /tab >}}
|
||||
{{< tab "AWS" >}}
|
||||
|
||||
## Create AWS IAM User
|
||||
|
||||
In order to provision infrastructure on AWS, you will need to use an existing or create a new IAM
|
||||
user with appropriate permissions. The following steps will create an AWS IAM user and give it the necessary
|
||||
permissions.
|
||||
|
||||
> Note: if you have an existing IAM user with appropriate permissions, you can skip this step but you will
|
||||
> still need to provide the values for the `ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables.
|
||||
|
||||
```console
|
||||
# create a new IAM user
|
||||
IAM_USER=test-user
|
||||
aws iam create-user --user-name $IAM_USER
|
||||
|
||||
# grant the IAM user the necessary permissions
|
||||
aws iam attach-user-policy --user-name $IAM_USER --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
|
||||
|
||||
# create a new IAM access key for the user
|
||||
aws iam create-access-key --user-name $IAM_USER > creds.json
|
||||
# assign the access key values to environment variables
|
||||
ACCESS_KEY_ID=$(jq -r .AccessKey.AccessKeyId creds.json)
|
||||
AWS_SECRET_ACCESS_KEY=$(jq -r .AccessKey.SecretAccessKey creds.json)
|
||||
```
|
||||
|
||||
## Store Credentials in Vault
|
||||
|
||||
After setting up Vault, you will need to store your credentials in the [kv
|
||||
secrets engine].
|
||||
|
||||
1. Enable KV Secrets Engine
|
||||
|
||||
Secrets engines must be enabled before they can be used. Enable the `kv-v2`
|
||||
secrets engine at the `secret` path.
|
||||
|
||||
```console
|
||||
kubectl exec -it vault-0 -- env \
|
||||
ACCESS_KEY_ID=${ACCESS_KEY_ID} \
|
||||
AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
|
||||
/bin/sh
|
||||
|
||||
vault secrets enable -path=secret kv-v2
|
||||
```
|
||||
|
||||
2. Store AWS Credentials in KV Engine
|
||||
|
||||
The path of your AWS credentials is how the secret will be referenced when
|
||||
injecting it into the `provider-aws` controller `Pod`.
|
||||
|
||||
```
|
||||
vault kv put secret/provider-creds/aws-default access_key="$ACCESS_KEY_ID" secret_key="$AWS_SECRET_ACCESS_KEY"
|
||||
```
|
||||
|
||||
{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
## Create a Vault Policy for Reading Provider Credentials
|
||||
|
||||
In order for our controllers to have the Vault sidecar inject the credentials
|
||||
into their filesystem, you must associate the `Pod` with a [policy]. This policy
|
||||
will allow for reading and listing all secrets on the `provider-creds` path in
|
||||
the `kv-v2` secrets engine.
|
||||
|
||||
```console
|
||||
vault policy write provider-creds - <<EOF
|
||||
path "secret/data/provider-creds/*" {
|
||||
capabilities = ["read", "list"]
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
## Create a Role for Crossplane Provider Pods
|
||||
|
||||
1. Create Role
|
||||
|
||||
The last step is to create a role that is bound to the policy you created and
|
||||
associate it with a group of Kubernetes service accounts. This role can be
|
||||
assumed by any (`*`) service account in the `crossplane-system` namespace.
|
||||
|
||||
```console
|
||||
vault write auth/kubernetes/role/crossplane-providers \
|
||||
bound_service_account_names="*" \
|
||||
bound_service_account_namespaces=crossplane-system \
|
||||
policies=provider-creds \
|
||||
ttl=24h
|
||||
```
|
||||
|
||||
2. Exit Vault Container
|
||||
|
||||
The next steps will be executed in your local environment.
|
||||
|
||||
```console
|
||||
exit
|
||||
```
|
||||
|
||||
{{< tabs >}}
|
||||
{{< tab "GCP" >}}
|
||||
|
||||
## Install provider-gcp
|
||||
|
||||
You are now ready to install `provider-gcp`. Crossplane provides a
|
||||
`ControllerConfig` type that allows you to customize the deployment of a
|
||||
provider's controller `Pod`. A `ControllerConfig` can be created and referenced
|
||||
by any number of `Provider` objects that wish to use its configuration. In the
|
||||
example below, the `Pod` annotations indicate to the Vault mutating webhook that
|
||||
we want for the secret stored at `secret/provider-creds/gcp-default` to be
|
||||
injected into the container filesystem by assuming role `crossplane-providers`.
|
||||
There is also so template formatting added to make sure the secret data is
|
||||
presented in a form that `provider-gcp` is expecting.
|
||||
|
||||
{% raw %}
|
||||
```console
|
||||
echo "apiVersion: pkg.crossplane.io/v1alpha1
|
||||
kind: ControllerConfig
|
||||
metadata:
|
||||
name: vault-config
|
||||
spec:
|
||||
metadata:
|
||||
annotations:
|
||||
vault.hashicorp.com/agent-inject: \"true\"
|
||||
vault.hashicorp.com/role: "crossplane-providers"
|
||||
vault.hashicorp.com/agent-inject-secret-creds.txt: "secret/provider-creds/gcp-default"
|
||||
vault.hashicorp.com/agent-inject-template-creds.txt: |
|
||||
{{- with secret \"secret/provider-creds/gcp-default\" -}}
|
||||
{{ .Data.data | toJSON }}
|
||||
{{- end -}}
|
||||
---
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: provider-gcp
|
||||
spec:
|
||||
package: xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.22.0
|
||||
controllerConfigRef:
|
||||
name: vault-config" | kubectl apply -f -
|
||||
```
|
||||
{% endraw %}
|
||||
|
||||
## Configure provider-gcp
|
||||
|
||||
One `provider-gcp` is installed and running, you will want to create a
|
||||
`ProviderConfig` that specifies the credentials in the filesystem that should be
|
||||
used to provision managed resources that reference this `ProviderConfig`.
|
||||
Because the name of this `ProviderConfig` is `default` it will be used by any
|
||||
managed resources that do not explicitly reference a `ProviderConfig`.
|
||||
|
||||
> Note: make sure that the `PROJECT_ID` environment variable that was defined
|
||||
> earlier is still set correctly.
|
||||
|
||||
```console
|
||||
echo "apiVersion: gcp.crossplane.io/v1beta1
|
||||
kind: ProviderConfig
|
||||
metadata:
|
||||
name: default
|
||||
spec:
|
||||
projectID: ${PROJECT_ID}
|
||||
credentials:
|
||||
source: Filesystem
|
||||
fs:
|
||||
path: /vault/secrets/creds.txt" | kubectl apply -f -
|
||||
```
|
||||
|
||||
To verify that the GCP credentials are being injected into the container run the
|
||||
following command:
|
||||
|
||||
```console
|
||||
PROVIDER_CONTROLLER_POD=$(kubectl -n crossplane-system get pod -l pkg.crossplane.io/provider=provider-gcp -o name --no-headers=true)
|
||||
kubectl -n crossplane-system exec -it $PROVIDER_CONTROLLER_POD -c provider-gcp -- cat /vault/secrets/creds.txt
|
||||
```
|
||||
|
||||
## Provision Infrastructure
|
||||
|
||||
The final step is to actually provision a `CloudSQLInstance`. Creating the
|
||||
object below will result in the creation of a Cloud SQL Postgres database on
|
||||
GCP.
|
||||
|
||||
```console
|
||||
echo "apiVersion: database.gcp.crossplane.io/v1beta1
|
||||
kind: CloudSQLInstance
|
||||
metadata:
|
||||
name: postgres-vault-demo
|
||||
spec:
|
||||
forProvider:
|
||||
databaseVersion: POSTGRES_12
|
||||
region: us-central1
|
||||
settings:
|
||||
tier: db-custom-1-3840
|
||||
dataDiskType: PD_SSD
|
||||
dataDiskSizeGb: 10
|
||||
writeConnectionSecretToRef:
|
||||
namespace: crossplane-system
|
||||
name: cloudsqlpostgresql-conn" | kubectl apply -f -
|
||||
```
|
||||
|
||||
You can monitor the progress of the database provisioning with the following
|
||||
command:
|
||||
|
||||
```console
|
||||
kubectl get cloudsqlinstance -w
|
||||
```
|
||||
|
||||
{{< /tab >}}
|
||||
{{< tab "AWS" >}}
|
||||
|
||||
## Install provider-aws
|
||||
|
||||
You are now ready to install `provider-aws`. Crossplane provides a
|
||||
`ControllerConfig` type that allows you to customize the deployment of a
|
||||
provider's controller `Pod`. A `ControllerConfig` can be created and referenced
|
||||
by any number of `Provider` objects that wish to use its configuration. In the
|
||||
example below, the `Pod` annotations indicate to the Vault mutating webhook that
|
||||
we want for the secret stored at `secret/provider-creds/aws-default` to be
|
||||
injected into the container filesystem by assuming role `crossplane-providers`.
|
||||
There is also some template formatting added to make sure the secret data is
|
||||
presented in a form that `provider-aws` is expecting.
|
||||
|
||||
{% raw %}
|
||||
```console
|
||||
echo "apiVersion: pkg.crossplane.io/v1alpha1
|
||||
kind: ControllerConfig
|
||||
metadata:
|
||||
name: aws-vault-config
|
||||
spec:
|
||||
args:
|
||||
- --debug
|
||||
metadata:
|
||||
annotations:
|
||||
vault.hashicorp.com/agent-inject: \"true\"
|
||||
vault.hashicorp.com/role: \"crossplane-providers\"
|
||||
vault.hashicorp.com/agent-inject-secret-creds.txt: \"secret/provider-creds/aws-default\"
|
||||
vault.hashicorp.com/agent-inject-template-creds.txt: |
|
||||
{{- with secret \"secret/provider-creds/aws-default\" -}}
|
||||
[default]
|
||||
aws_access_key_id="{{ .Data.data.access_key }}"
|
||||
aws_secret_access_key="{{ .Data.data.secret_key }}"
|
||||
{{- end -}}
|
||||
---
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: provider-aws
|
||||
spec:
|
||||
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0
|
||||
controllerConfigRef:
|
||||
name: aws-vault-config" | kubectl apply -f -
|
||||
```
|
||||
{% endraw %}
|
||||
|
||||
## Configure provider-aws
|
||||
|
||||
Once `provider-aws` is installed and running, you will want to create a
|
||||
`ProviderConfig` that specifies the credentials in the filesystem that should be
|
||||
used to provision managed resources that reference this `ProviderConfig`.
|
||||
Because the name of this `ProviderConfig` is `default` it will be used by any
|
||||
managed resources that do not explicitly reference a `ProviderConfig`.
|
||||
|
||||
```console
|
||||
echo "apiVersion: aws.crossplane.io/v1beta1
|
||||
kind: ProviderConfig
|
||||
metadata:
|
||||
name: default
|
||||
spec:
|
||||
credentials:
|
||||
source: Filesystem
|
||||
fs:
|
||||
path: /vault/secrets/creds.txt" | kubectl apply -f -
|
||||
```
|
||||
|
||||
To verify that the AWS credentials are being injected into the container run the
|
||||
following command:
|
||||
|
||||
```console
|
||||
PROVIDER_CONTROLLER_POD=$(kubectl -n crossplane-system get pod -l pkg.crossplane.io/provider=provider-aws -o name --no-headers=true)
|
||||
kubectl -n crossplane-system exec -it $PROVIDER_CONTROLLER_POD -c provider-aws -- cat /vault/secrets/creds.txt
|
||||
```
|
||||
|
||||
## Provision Infrastructure
|
||||
|
||||
The final step is to actually provision a `Bucket`. Creating the
|
||||
object below will result in the creation of a S3 bucket on AWS.
|
||||
|
||||
```console
|
||||
echo "apiVersion: s3.aws.crossplane.io/v1beta1
|
||||
kind: Bucket
|
||||
metadata:
|
||||
name: s3-vault-demo
|
||||
spec:
|
||||
forProvider:
|
||||
acl: private
|
||||
locationConstraint: us-east-1
|
||||
publicAccessBlockConfiguration:
|
||||
blockPublicPolicy: true
|
||||
tagging:
|
||||
tagSet:
|
||||
- key: Name
|
||||
value: s3-vault-demo
|
||||
providerConfigRef:
|
||||
name: default" | kubectl apply -f -
|
||||
```
|
||||
|
||||
You can monitor the progress of the bucket provisioning with the following
|
||||
command:
|
||||
|
||||
```console
|
||||
kubectl get bucket -w
|
||||
```
|
||||
|
||||
{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
<!-- named links -->
|
||||
|
||||
[Vault on Minikube]: https://learn.hashicorp.com/tutorials/vault/kubernetes-minikube
|
||||
[Vault Kubernetes Sidecar]: https://learn.hashicorp.com/tutorials/vault/kubernetes-sidecar
|
||||
[Vault]: https://www.vaultproject.io/
|
||||
[Vault Kubernetes Sidecar]: https://www.vaultproject.io/docs/platform/k8s/injector
|
||||
[provider-gcp]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-gcp
|
||||
[provider-aws]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws
|
||||
[AWS]: https://www.vaultproject.io/docs/secrets/aws
|
||||
[Azure]: https://www.vaultproject.io/docs/secrets/azure
|
||||
[GCP]: https://www.vaultproject.io/docs/secrets/gcp
|
||||
[unsealed]: https://www.vaultproject.io/docs/concepts/seal
|
||||
[Kubernetes authentication backend]: https://www.vaultproject.io/docs/auth/kubernetes
|
||||
[kv secrets engine]: https://www.vaultproject.io/docs/secrets/kv/kv-v2
|
||||
[policy]: https://www.vaultproject.io/docs/concepts/policies
|
|
@ -11,7 +11,7 @@ Composition functions (or just functions, for short) are custom programs that
|
|||
template Crossplane resources. Crossplane calls composition functions to
|
||||
determine what resources it should create when you create a composite resource
|
||||
(XR). Read the
|
||||
[concepts](https://docs.crossplane.io/latest/concepts/composition-functions)
|
||||
[concepts]{{<ref "../concepts/composition-functions" >}}
|
||||
page to learn more about composition functions.
|
||||
|
||||
You can write a function to template resources using a general purpose
|
||||
|
@ -22,7 +22,7 @@ conditionals. This guide explains how to write a composition function in
|
|||
|
||||
{{< hint "important" >}}
|
||||
It helps to be familiar with
|
||||
[how composition functions work](https://docs.crossplane.io/latest/concepts/composition-functions#how-composition-functions-work)
|
||||
[how composition functions work]{{<ref "../concepts/composition-functions#how-composition-functions-work" >}}
|
||||
before following this guide.
|
||||
{{< /hint >}}
|
||||
|
||||
|
@ -134,7 +134,7 @@ should delete the `input` and `package/input` directories.
|
|||
|
||||
The `input` directory defines a Go struct that a function can use to take input,
|
||||
using the `input` field from a Composition. The
|
||||
[composition functions](https://docs.crossplane.io/latest/concepts/composition-functions)
|
||||
[composition functions]{{<ref "../concepts/composition-functions" >}}
|
||||
documentation explains how to pass an input to a composition function.
|
||||
|
||||
The `package/input` directory contains an OpenAPI schema generated from the
|
||||
|
@ -757,7 +757,7 @@ then pushing all the packages to a single tag in the registry.
|
|||
|
||||
Pushing your function to a registry allows you to use your function in a
|
||||
Crossplane control plane. See the
|
||||
[composition functions documentation](https://docs.crossplane.io/latest/concepts/composition-functions).
|
||||
[composition functions documentation]{{<ref "../concepts/composition-functions" >}}.
|
||||
to learn how to use a function in a control plane.
|
||||
|
||||
Use Docker to build a runtime for each platform.
|
||||
|
@ -808,7 +808,7 @@ crossplane xpkg build \
|
|||
|
||||
{{<hint "tip">}}
|
||||
Crossplane packages are special OCI images. Read more about packages in the
|
||||
[packages documentation](https://docs.crossplane.io/latest/concepts/packages).
|
||||
[packages documentation]({{< ref "../concepts/packages" >}}).
|
||||
{{</hint>}}
|
||||
|
||||
Push both package files to a registry. Pushing both files to one tag in the
|
|
@ -11,7 +11,7 @@ Composition functions (or just functions, for short) are custom programs that
|
|||
template Crossplane resources. Crossplane calls composition functions to
|
||||
determine what resources it should create when you create a composite resource
|
||||
(XR). Read the
|
||||
[concepts](https://docs.crossplane.io/latest/concepts/composition-functions)
|
||||
[concepts]{{<ref "../concepts/composition-functions" >}}
|
||||
page to learn more about composition functions.
|
||||
|
||||
You can write a function to template resources using a general purpose
|
||||
|
@ -22,7 +22,7 @@ conditionals. This guide explains how to write a composition function in
|
|||
|
||||
{{< hint "important" >}}
|
||||
It helps to be familiar with
|
||||
[how composition functions work](https://docs.crossplane.io/latest/concepts/composition-functions#how-composition-functions-work)
|
||||
[how composition functions work]{{<ref "../concepts/composition-functions#how-composition-functions-work" >}}
|
||||
before following this guide.
|
||||
{{< /hint >}}
|
||||
|
||||
|
@ -132,7 +132,7 @@ The `package/input` directory defines the OpenAPI schema for the a function's
|
|||
input. The function in this guide doesn't accept an input. Delete the
|
||||
`package/input` directory.
|
||||
|
||||
The [composition functions](https://docs.crossplane.io/latest/concepts/composition-functions)
|
||||
The [composition functions]{{<ref "../concepts/composition-functions" >}}
|
||||
documentation explains composition function inputs.
|
||||
|
||||
{{<hint "tip">}}
|
||||
|
@ -656,7 +656,7 @@ then pushing all the packages to a single tag in the registry.
|
|||
|
||||
Pushing your function to a registry allows you to use your function in a
|
||||
Crossplane control plane. See the
|
||||
[composition functions documentation](https://docs.crossplane.io/latest/concepts/composition-functions).
|
||||
[composition functions documentation]{{<ref "../concepts/composition-functions" >}}.
|
||||
to learn how to use a function in a control plane.
|
||||
|
||||
Use Docker to build a runtime for each platform.
|
||||
|
@ -715,7 +715,7 @@ crossplane xpkg build \
|
|||
|
||||
{{<hint "tip">}}
|
||||
Crossplane packages are special OCI images. Read more about packages in the
|
||||
[packages documentation](https://docs.crossplane.io/latest/concepts/packages).
|
||||
[packages documentation]({{< ref "../concepts/packages" >}}).
|
||||
{{</hint>}}
|
||||
|
||||
Push both package files to a registry. Pushing both files to one tag in the
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
title: Learn
|
||||
description: Learn more about Crossplane.
|
||||
---
|
||||
|
||||
If you have any questions, please drop us a note on [Crossplane Slack][join-crossplane-slack] or [contact us][contact-us]!
|
||||
|
||||
***Learn more about using Crossplane***
|
||||
- [Latest Design Docs](https://github.com/crossplane/crossplane/tree/master/design)
|
||||
- [Roadmap](https://github.com/crossplane/crossplane/blob/master/ROADMAP.md)
|
||||
- [Crossplane Architecture](https://docs.google.com/document/d/1whncqdUeU2cATGEJhHvzXWC9xdK29Er45NJeoemxebo/edit?usp=sharing)
|
||||
- [GitLab deploys into multiple clouds from kubectl using Crossplane](https://about.gitlab.com/2019/05/20/gitlab-first-deployed-kubernetes-api-to-multiple-clouds/)
|
||||
- [CNCF Talks & Community Presentations](https://www.youtube.com/playlist?list=PL510POnNVaaZJj9OG6PbgsZvgYbhwJRyE)
|
||||
- [Software Engineering Daily - Intro Podcast](https://softwareengineeringdaily.com/2019/01/02/crossplane-multicloud-control-plane-with-bassam-tabbara/)
|
||||
|
||||
***Writing Kubernetes controllers to extend Crossplane***
|
||||
- [Keep the Space Shuttle Flying: Writing Robust Operators](https://www.youtube.com/watch?v=uf97lOApOv8)
|
||||
- [Best practices for building Kubernetes Operators](https://cloud.google.com/blog/products/containers-kubernetes/best-practices-for-building-kubernetes-operators-and-stateful-apps)
|
||||
- [Programming Kubernetes Book](https://www.oreilly.com/library/view/programming-kubernetes/9781492047094/)
|
||||
- [Contributor Guide](https://github.com/crossplane/crossplane/blob/master/CONTRIBUTING.md)
|
||||
|
||||
***Join the growing Crossplane community and get involved!***
|
||||
- Join our [Community Slack](https://slack.crossplane.io/)!
|
||||
- Submit an issue on [GitHub](https://github.com/crossplane/crossplane)
|
||||
- Attend our bi-weekly [Community Meeting](https://github.com/crossplane/crossplane#get-involved)
|
||||
- Join our bi-weekly live stream: [The Binding Status](https://github.com/crossplane/tbs)
|
||||
- Subscribe to our [YouTube Channel](https://www.youtube.com/channel/UC19FgzMBMqBro361HbE46Fw)
|
||||
- Drop us a note on Twitter: [@crossplane_io](https://twitter.com/crossplane_io)
|
||||
- Email us: [info@crossplane.io](mailto:info@crossplane.io)
|
||||
|
||||
<!-- Named links -->
|
||||
|
||||
[join-crossplane-slack]: https://slack.crossplane.io
|
||||
[contact-us]: https://github.com/crossplane/crossplane#contact
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
title: Feature Lifecycle
|
||||
toc: true
|
||||
weight: 309
|
||||
indent: true
|
||||
---
|
||||
|
||||
# Feature Lifecycle
|
||||
|
||||
Crossplane follows a similar feature lifecycle to [upstream
|
||||
Kubernetes][kube-features]. All major new features must be added in alpha. Alpha
|
||||
features are expected to eventually graduate to beta, and then to general
|
||||
availability (GA). Features that languish at alpha or beta may be subject to
|
||||
deprecation.
|
||||
|
||||
## Alpha Features
|
||||
|
||||
Alpha are off by default, and must be enabled by a feature flag, for example
|
||||
`--enable-composition-revisions`. API types pertaining to alpha features use a
|
||||
`vNalphaN` style API version, like `v1alpha`. **Alpha features are subject to
|
||||
removal or breaking changes without notice**, and generally not considered ready
|
||||
for use in production.
|
||||
|
||||
In some cases alpha features require fields be added to existing beta or GA
|
||||
API types. In these cases fields must clearly be marked (i.e in their OpenAPI
|
||||
schema) as alpha and subject to alpha API constraints (or lack thereof).
|
||||
|
||||
All alpha features should have an issue tracking their graduation to beta.
|
||||
|
||||
## Beta Features
|
||||
|
||||
Beta features are on by default, but may be disabled by a feature flag. API
|
||||
types pertaining to beta features use a `vNbetaN` style API version, like
|
||||
`v1beta1`. Beta features are considered to be well tested, and will not be
|
||||
removed completely without being marked deprecated for at least two releases.
|
||||
|
||||
The schema and/or semantics of objects may change in incompatible ways in a
|
||||
subsequent beta or stable release. When this happens, we will provide
|
||||
instructions for migrating to the next version. This may require deleting,
|
||||
editing, and re-creating API objects. The editing process may require some
|
||||
thought. This may require downtime for applications that rely on the feature.
|
||||
|
||||
In some cases beta features require fields be added to existing GA API types. In
|
||||
these cases fields must clearly be marked (i.e in their OpenAPI schema) as beta
|
||||
and subject to beta API constraints (or lack thereof).
|
||||
|
||||
All beta features should have an issue tracking their graduation to GA.
|
||||
|
||||
## GA Features
|
||||
|
||||
GA features are always enabled - they cannot be disabled. API types pertaining
|
||||
to GA features use `vN` style API versions, like `v1`. GA features are widely
|
||||
used and thoroughly tested. They guarantee API stability - only backward
|
||||
compatible changes are allowed.
|
||||
|
||||
[kube-features]: https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-stages
|
|
@ -0,0 +1,100 @@
|
|||
---
|
||||
title: Release Cycle
|
||||
weight: 308
|
||||
---
|
||||
|
||||
Starting with the v1.10.0 release, Crossplane is released on a quarterly (13
|
||||
week) cadence. A cycle is comprised of three general stages:
|
||||
|
||||
- Weeks 1—11: [Active Development]
|
||||
- Week 12: [Feature Freeze]
|
||||
- Week 13: [Code Freeze]
|
||||
|
||||
This results in four releases per year, with the most recent three releases
|
||||
being maintained at any given time. When a new release is cut, the fourth most
|
||||
recent release reaches end of life (EOL). Users can expect any given release to
|
||||
be maintained for nine months.
|
||||
|
||||
### Definition of maintenance
|
||||
|
||||
The Crossplane community defines maintenance in that relevant bug fixes that are
|
||||
merged to the main development branch will be eligible to be back-ported to the
|
||||
release branch of any currently maintained version, and patch releases will be
|
||||
cut appropriately. It's also possible that a fix may be merged directly to the
|
||||
release branch if no longer applicable on the main development branch.
|
||||
Maintenance doesn't indicate any SLA on response time for user support in the
|
||||
form of Slack messages or issues, but problems will be addressed on a best
|
||||
effort basis by maintainers and contributors for currently maintained releases.
|
||||
|
||||
### Patch releases
|
||||
|
||||
_This policy is subject to change in the future._
|
||||
|
||||
Patch releases are cut for currently maintained minor versions on an as-needed
|
||||
basis. Any critical back-ported fixes will be included in a patch release as
|
||||
soon as possible after merge.
|
||||
|
||||
### Pre-releases
|
||||
|
||||
_This policy is subject to change in the future._
|
||||
|
||||
Alpha, Beta, and RC releases are cut for an upcoming release on an as-needed
|
||||
basis. As a policy, at least one pre-release will be cut prior to any minor
|
||||
release. Pre-releases won't be made on release branches.
|
||||
|
||||
### Provider releases
|
||||
|
||||
The Crossplane release cycle isn't required to be adhered to by any other
|
||||
Crossplane projects, but a similar cadence is encouraged. Maintainers listed in
|
||||
each repository's `OWNERS.md` file are responsible for determining and
|
||||
publishing the release cycle for their project.
|
||||
|
||||
## Release stages
|
||||
|
||||
The following stages are the main milestones in a Crossplane release.
|
||||
|
||||
### Active development
|
||||
|
||||
During active development, any code that meets the requisite criteria (i.e.
|
||||
passing appropriate tests, approved by a maintainer, etc.) will be merged into
|
||||
the main development branch. At present, there is no requirement to formally
|
||||
submit an enhancement proposal prior to the start of the release cycle, but
|
||||
contributors are encouraged to open an issue and gather feedback before starting
|
||||
work on a major implementation (see [CONTRIBUTING.md] for more information).
|
||||
|
||||
### Feature freeze
|
||||
|
||||
During feature freeze, no new functionality should be merged into the main
|
||||
development branch. Bug fixes, documentation changes, and non-critical changes
|
||||
may be made. In the case that a new feature is deemed absolutely necessary for a
|
||||
release, the Crossplane maintainers will weigh the impact of the change and make
|
||||
a decision on whether it should be included.
|
||||
|
||||
### Code freeze
|
||||
|
||||
During code freeze, there should be no changes merged to the main development
|
||||
branch with the following exceptions:
|
||||
- Fixes to a failing test that's deemed to be incorrectly testing
|
||||
functionality.
|
||||
- Documentation only changes. It's possible that a documentation freeze will be
|
||||
implemented in the future, but it's not currently enforced.
|
||||
- Fixes to a critical bug that wasn't previously identified. Merging a bug fix
|
||||
during code freeze requires application for and approval of an exception by
|
||||
Crossplane maintainers. This process is currently informal, but may be
|
||||
formalized in the future.
|
||||
|
||||
## Release dates
|
||||
|
||||
Crossplane releases once a quarter (every 13 weeks). Typically, the release
|
||||
happens on the Tuesday of the last week of the quarter, as shown on the
|
||||
[community calendar][community calendar]. Keep in mind that the specific date is
|
||||
**approximate**. A lot of factors can alter the date slightly, such as code
|
||||
reviews, testing, and bug fixing to ensure a quality release.
|
||||
|
||||
<!-- Named links -->
|
||||
|
||||
[Active Development]: #active-development
|
||||
[Feature Freeze]: #feature-freeze
|
||||
[Code Freeze]: #code-freeze
|
||||
[CONTRIBUTING.md]: https://github.com/crossplane/crossplane/blob/master/CONTRIBUTING.md
|
||||
[community calendar]: https://calendar.google.com/calendar/embed?src=c_2cdn0hs9e2m05rrv1233cjoj1k%40group.calendar.google.com
|
|
@ -204,4 +204,4 @@ spec:
|
|||
name: my-claim-secret
|
||||
```
|
||||
|
||||
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}).
|
||||
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
|
|
@ -618,7 +618,7 @@ recreate the XRD to change the `connectionSecretKeys`.
|
|||
{{</hint >}}
|
||||
|
||||
For more information on connection secrets read the
|
||||
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}).
|
||||
[Connection Secrets knowledge base article]({{<ref "connection-details">}}).
|
||||
|
||||
### Set composite resource defaults
|
||||
XRDs can set default parameters for composite resources and Claims.
|
||||
|
|
|
@ -189,7 +189,7 @@ spec:
|
|||
### Composition revision policy
|
||||
|
||||
Crossplane tracks changes to Compositions as
|
||||
[Composition revisions]({{<ref "/knowledge-base/guides/composition-revisions">}}) .
|
||||
[Composition revisions]({{<ref "composition-revisions">}}) .
|
||||
|
||||
A composite resource can use
|
||||
a {{<hover label="comprev" line="6">}}compositionUpdatePolicy{{</hover>}} to
|
||||
|
@ -217,7 +217,7 @@ spec:
|
|||
### Composition revision selection
|
||||
|
||||
Crossplane records changes to Compositions as
|
||||
[Composition revisions]({{<ref "/knowledge-base/guides/composition-revisions">}}).
|
||||
[Composition revisions]({{<ref "composition-revisions">}}).
|
||||
A composite resource can
|
||||
select a specific Composition revision.
|
||||
|
||||
|
@ -309,7 +309,7 @@ spec:
|
|||
```
|
||||
|
||||
Composite resources can write connection secrets to an
|
||||
[external secret store]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}),
|
||||
[external secret store]({{<ref "../guides/vault-as-secret-store">}}),
|
||||
like HashiCorp Vault.
|
||||
|
||||
{{<hint "important" >}}
|
||||
|
@ -332,10 +332,10 @@ spec:
|
|||
# Removed for brevity
|
||||
```
|
||||
|
||||
Read the [External Secrets Store]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) documentation for more information on using
|
||||
Read the [External Secrets Store]({{<ref "../guides/vault-as-secret-store">}}) documentation for more information on using
|
||||
external secret stores.
|
||||
|
||||
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}).
|
||||
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
|
||||
|
||||
### Pausing composite resources
|
||||
|
||||
|
|
|
@ -453,7 +453,7 @@ $ crossplane xpkg push -f package/*.xpkg crossplane-contrib/function-example:v0.
|
|||
|
||||
{{<hint "tip">}}
|
||||
Crossplane has a
|
||||
[guide to writing a composition function in Go]({{<ref "../../knowledge-base/guides/write-a-composition-function-in-go">}}).
|
||||
[guide to writing a composition function in Go]({{<ref "../guides/write-a-composition-function-in-go">}}).
|
||||
{{</hint>}}
|
||||
|
||||
When you're writing a composition function it's useful to know how composition
|
||||
|
|
|
@ -0,0 +1,444 @@
|
|||
---
|
||||
title: Composition Revisions
|
||||
---
|
||||
|
||||
This guide discusses the use of "Composition Revisions" to safely make and roll
|
||||
back changes to a Crossplane [`Composition`][composition-type]. It assumes
|
||||
familiarity with Crossplane, and particularly with
|
||||
[Compositions].
|
||||
|
||||
A `Composition` configures how Crossplane should reconcile a Composite Resource
|
||||
(XR). Put otherwise, when you create an XR the selected `Composition` determines
|
||||
what managed resources Crossplane will create in response. Let's say for example
|
||||
that you define a `PlatformDB` XR, which represents your organisation's common
|
||||
database configuration of an Azure MySQL Server and a few firewall rules. The
|
||||
`Composition` contains the 'base' configuration for the MySQL server and the
|
||||
firewall rules that is extended by the configuration for the `PlatformDB`.
|
||||
|
||||
There is a one-to-many relationship between a `Composition` and the XRs that use
|
||||
it. You might define a `Composition` named `big-platform-db` that is used by ten
|
||||
different `PlatformDB` XRs. Usually, in the interest of self-service, the
|
||||
`Composition` is managed by a different team from the actual `PlatformDB` XRs.
|
||||
For example the `Composition` may be written and maintained by a platform team
|
||||
member, while individual application teams create `PlatformDB` XRs that use said
|
||||
`Composition`.
|
||||
|
||||
Each `Composition` is mutable - you can update it as your organisation's needs
|
||||
change. However, without Composition Revisions updating a `Composition` can be a
|
||||
risky process. Crossplane constantly uses the `Composition` to ensure that your
|
||||
actual infrastructure - your MySQL Servers and firewall rules - match your
|
||||
desired state. If you have 10 `PlatformDB` XRs all using the `big-platform-db`
|
||||
`Composition`, all 10 of those XRs will be instantly updated in accordance with
|
||||
any updates you make to the `big-platform-db` `Composition`.
|
||||
|
||||
Composition Revisions allow XRs to opt out of automatic updates. Instead you can
|
||||
update your XRs to leverage the latest `Composition` settings at your own pace.
|
||||
This enables you to [canary] changes to your infrastructure, or to roll back
|
||||
some XRs to previous `Composition` settings without rolling back all XRs.
|
||||
|
||||
## Using Composition Revisions
|
||||
|
||||
When you enable Composition Revisions three things happen:
|
||||
|
||||
1. Crossplane creates a `CompositionRevision` for each `Composition` update.
|
||||
1. Composite Resources gain a `spec.compositionRevisionRef` field that specifies
|
||||
which `CompositionRevision` they use.
|
||||
1. Composite Resources gain a `spec.compositionUpdatePolicy` field that
|
||||
specifies how they should be updated to new Composition Revisions.
|
||||
|
||||
Each time you edit a `Composition` Crossplane will automatically create a
|
||||
`CompositionRevision` that represents that 'revision' of the `Composition` -
|
||||
that unique state. Each revision is allocated an increasing revision number.
|
||||
This gives `CompositionRevision` consumers an idea about which revision is
|
||||
'newest'.
|
||||
|
||||
Crossplane distinguishes between the 'newest' and the 'current' revision of a
|
||||
`Composition`. That is, if you revert a `Composition` to a previous state that
|
||||
corresponds to an existing `CompositionRevision` that revision will become
|
||||
'current' even if it is not the 'newest' revision (i.e. the most latest _unique_
|
||||
`Composition` configuration).
|
||||
|
||||
You can discover which revisions exist using `kubectl`:
|
||||
|
||||
```console
|
||||
# Find all revisions of the Composition named 'example'
|
||||
kubectl get compositionrevision -l crossplane.io/composition-name=example
|
||||
```
|
||||
|
||||
This should produce output something like:
|
||||
|
||||
```console
|
||||
NAME REVISION CURRENT AGE
|
||||
example-18pdg 1 False 4m36s
|
||||
example-2bgdr 2 True 73s
|
||||
example-xjrdm 3 False 61s
|
||||
```
|
||||
|
||||
> A `Composition` is a mutable resource that you can update as your needs
|
||||
> change over time. Each `CompositionRevision` is an immutable snapshot of those
|
||||
> needs at a particular point in time.
|
||||
|
||||
Crossplane behaves the same way by default whether Composition Revisions are
|
||||
enabled or not. This is because when you enable Composition Revisions all XRs
|
||||
default to the `Automatic` `compositionUpdatePolicy`. XRs support two update
|
||||
policies:
|
||||
|
||||
* `Automatic`: Automatically use the current `CompositionRevision`. (Default)
|
||||
* `Manual`: Require manual intervention to change `CompositionRevision`.
|
||||
|
||||
The below XR uses the `Manual` policy. When this policy is used the XR will
|
||||
select the current `CompositionRevision` when it is first created, but must
|
||||
manually be updated when you wish it to use another `CompositionRevision`.
|
||||
|
||||
```yaml
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: PlatformDB
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
parameters:
|
||||
storageGB: 20
|
||||
# The Manual policy specifies that you do not want this XR to update to the
|
||||
# current CompositionRevision automatically.
|
||||
compositionUpdatePolicy: Manual
|
||||
compositionRef:
|
||||
name: example
|
||||
writeConnectionSecretToRef:
|
||||
name: db-conn
|
||||
```
|
||||
|
||||
Crossplane sets an XR's `compositionRevisionRef` automatically at creation time
|
||||
regardless of your chosen `compositionUpdatePolicy`. If you choose the `Manual`
|
||||
policy you must edit the `compositionRevisionRef` field when you want your XR to
|
||||
use a different `CompositionRevision`.
|
||||
|
||||
```yaml
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: PlatformDB
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
parameters:
|
||||
storageGB: 20
|
||||
compositionUpdatePolicy: Manual
|
||||
compositionRef:
|
||||
name: example
|
||||
# Update the referenced CompositionRevision if and when you are ready.
|
||||
compositionRevisionRef:
|
||||
name: example-18pdg
|
||||
writeConnectionSecretToRef:
|
||||
name: db-conn
|
||||
```
|
||||
|
||||
## Complete example
|
||||
|
||||
This tutorial discusses how CompositionRevisions work and how they manage Composite Resource
|
||||
(XR) updates. This starts with a `Composition` and `CompositeResourceDefinition` (XRD) that defines a `MyVPC`
|
||||
resource and continues with creating multiple XRs to observe different upgrade paths. Crossplane will
|
||||
assign different CompositionRevisions to the created composite resources each time the composition is updated.
|
||||
|
||||
### Preparation
|
||||
##### Install Crossplane
|
||||
Install Crossplane v1.11.0 or later and wait until the Crossplane pods are running.
|
||||
```shell
|
||||
kubectl create namespace crossplane-system
|
||||
helm repo add crossplane-master https://charts.crossplane.io/master/
|
||||
helm repo update
|
||||
helm install crossplane --namespace crossplane-system crossplane-master/crossplane --devel --version 1.11.0-rc.0.108.g0521c32e
|
||||
kubectl get pods -n crossplane-system
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
crossplane-7f75ddcc46-f4d2z 1/1 Running 0 9s
|
||||
crossplane-rbac-manager-78bd597746-sdv6w 1/1 Running 0 9s
|
||||
```
|
||||
|
||||
#### Deploy Composition and XRD Examples
|
||||
Apply the example Composition.
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
labels:
|
||||
channel: dev
|
||||
name: myvpcs.aws.example.upbound.io
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: crossplane-system
|
||||
compositeTypeRef:
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
resources:
|
||||
- base:
|
||||
apiVersion: ec2.aws.upbound.io/v1beta1
|
||||
kind: VPC
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-west-1
|
||||
cidrBlock: 192.168.0.0/16
|
||||
enableDnsSupport: true
|
||||
enableDnsHostnames: true
|
||||
name: my-vcp
|
||||
```
|
||||
|
||||
Apply the example XRD.
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: myvpcs.aws.example.upbound.io
|
||||
spec:
|
||||
group: aws.example.upbound.io
|
||||
names:
|
||||
kind: MyVPC
|
||||
plural: myvpcs
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
referenceable: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: ID of this VPC that other objects will use to refer to it.
|
||||
required:
|
||||
- id
|
||||
```
|
||||
|
||||
Verify that Crossplane created the Composition revision
|
||||
```shell
|
||||
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME REVISION CHANNEL
|
||||
myvpcs.aws.example.upbound.io-ad265bc 1 dev
|
||||
```
|
||||
|
||||
{{< hint "note" >}}
|
||||
The label `dev` is automatically created from the Composition.
|
||||
{{< /hint >}}
|
||||
|
||||
|
||||
### Create Composite Resources
|
||||
This tutorial has four composite resources to cover different update policies and composition selection options.
|
||||
The default behavior is updating XRs to the latest revision of the Composition. However, this can be changed by setting
|
||||
`compositionUpdatePolicy: Manual` in the XR. It is also possible to select the latest revision with a specific label
|
||||
with `compositionRevisionSelector.matchLabels` together with `compositionUpdatePolicy: Automatic`.
|
||||
|
||||
#### Default update policy
|
||||
Create an XR without a `compositionUpdatePolicy` defined. The update policy is `Automatic` by default:
|
||||
```yaml
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
metadata:
|
||||
name: vpc-auto
|
||||
spec:
|
||||
id: vpc-auto
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
myvpc.aws.example.upbound.io/vpc-auto created
|
||||
```
|
||||
|
||||
#### Manual update policy
|
||||
Create a Composite Resource with `compositionUpdatePolicy: Manual` and `compositionRevisionRef`.
|
||||
```yaml
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
metadata:
|
||||
name: vpc-man
|
||||
spec:
|
||||
id: vpc-man
|
||||
compositionUpdatePolicy: Manual
|
||||
compositionRevisionRef:
|
||||
name: myvpcs.aws.example.upbound.io-ad265bc
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
```shell
|
||||
myvpc.aws.example.upbound.io/vpc-man created
|
||||
```
|
||||
|
||||
#### Using a selector
|
||||
Create an XR with a `compositionRevisionSelector` of `channel: dev`:
|
||||
```yaml
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
metadata:
|
||||
name: vpc-dev
|
||||
spec:
|
||||
id: vpc-dev
|
||||
compositionRevisionSelector:
|
||||
matchLabels:
|
||||
channel: dev
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
myvpc.aws.example.upbound.io/vpc-dev created
|
||||
```
|
||||
|
||||
Create an XR with a `compositionRevisionSelector` of `channel: staging`:
|
||||
```yaml
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
metadata:
|
||||
name: vpc-staging
|
||||
spec:
|
||||
id: vpc-staging
|
||||
compositionRevisionSelector:
|
||||
matchLabels:
|
||||
channel: staging
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
```shell
|
||||
myvpc.aws.example.upbound.io/vpc-staging created
|
||||
```
|
||||
|
||||
Verify the Composite Resource with the label `channel: staging` doesn't have a `REVISION`.
|
||||
All other XRs have a `REVISION` matching the created Composition Revision.
|
||||
```shell
|
||||
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME SYNCED REVISION POLICY MATCHLABEL
|
||||
vpc-auto True myvpcs.aws.example.upbound.io-ad265bc Automatic <none>
|
||||
vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev]
|
||||
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
|
||||
vpc-staging False <none> Automatic map[channel:staging]
|
||||
```
|
||||
|
||||
{{< hint "note" >}}
|
||||
The `vpc-staging` XR label doesn't match any existing Composition Revisions.
|
||||
{{< /hint >}}
|
||||
|
||||
### Create new Composition revisions
|
||||
Crossplane creates a new CompositionRevision when a Composition is created or updated. Label and annotation changes will
|
||||
also trigger a new CompositionRevision.
|
||||
|
||||
#### Update the Composition label
|
||||
Update the `Composition` label to `channel: staging`:
|
||||
```shell
|
||||
kubectl label composition myvpcs.aws.example.upbound.io channel=staging --overwrite
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
composition.apiextensions.crossplane.io/myvpcs.aws.example.upbound.io labeled
|
||||
```
|
||||
|
||||
Verify that Crossplane creates a new Composition revision:
|
||||
```shell
|
||||
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME REVISION CHANNEL
|
||||
myvpcs.aws.example.upbound.io-727b3c8 2 staging
|
||||
myvpcs.aws.example.upbound.io-ad265bc 1 dev
|
||||
```
|
||||
|
||||
Verify that Crossplane assigns the Composite Resources `vpc-auto` and `vpc-staging` to Composite revision:2.
|
||||
XRs `vpc-man` and `vpc-dev` are still assigned to the original revision:1:
|
||||
|
||||
```shell
|
||||
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME SYNCED REVISION POLICY MATCHLABEL
|
||||
vpc-auto True myvpcs.aws.example.upbound.io-727b3c8 Automatic <none>
|
||||
vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev]
|
||||
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
|
||||
vpc-staging True myvpcs.aws.example.upbound.io-727b3c8 Automatic map[channel:staging]
|
||||
```
|
||||
|
||||
{{< hint "note" >}}
|
||||
`vpc-auto` always use the latest Revision.
|
||||
`vpc-staging` now matches the label applied to Revision revision:2.
|
||||
{{< /hint >}}
|
||||
|
||||
#### Update Composition Spec and Label
|
||||
Update the Composition to disable DNS support in the VPC and change the label from `staging` back to `dev`.
|
||||
|
||||
Apply the following changes to update the `Composition` spec and label:
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
labels:
|
||||
channel: dev
|
||||
name: myvpcs.aws.example.upbound.io
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: crossplane-system
|
||||
compositeTypeRef:
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
resources:
|
||||
- base:
|
||||
apiVersion: ec2.aws.upbound.io/v1beta1
|
||||
kind: VPC
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-west-1
|
||||
cidrBlock: 192.168.0.0/16
|
||||
enableDnsSupport: false
|
||||
enableDnsHostnames: true
|
||||
name: my-vcp
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
```shell
|
||||
composition.apiextensions.crossplane.io/myvpcs.aws.example.upbound.io configured
|
||||
```
|
||||
|
||||
Verify that Crossplane creates a new Composition revision:
|
||||
|
||||
```shell
|
||||
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME REVISION CHANNEL
|
||||
myvpcs.aws.example.upbound.io-727b3c8 2 staging
|
||||
myvpcs.aws.example.upbound.io-ad265bc 1 dev
|
||||
myvpcs.aws.example.upbound.io-f81c553 3 dev
|
||||
```
|
||||
|
||||
{{< hint "note" >}}
|
||||
Changing the label and the spec values simultaneously is critical for deploying new changes to the `dev` channel.
|
||||
{{< /hint >}}
|
||||
|
||||
Verify Crossplane assigns the Composite Resources `vpc-auto` and `vpc-dev` to Composite revision:3.
|
||||
`vpc-staging` is assigned to revision:2, and `vpc-man` is still assigned to the original revision:1:
|
||||
|
||||
```shell
|
||||
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME SYNCED REVISION POLICY MATCHLABEL
|
||||
vpc-auto True myvpcs.aws.example.upbound.io-f81c553 Automatic <none>
|
||||
vpc-dev True myvpcs.aws.example.upbound.io-f81c553 Automatic map[channel:dev]
|
||||
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
|
||||
vpc-staging True myvpcs.aws.example.upbound.io-727b3c8 Automatic map[channel:staging]
|
||||
```
|
||||
|
||||
|
||||
{{< hint "note" >}}
|
||||
`vpc-dev` matches the updated label applied to Revision revision:3.
|
||||
`vpc-staging` matches the label applied to Revision revision:2.
|
||||
{{< /hint >}}
|
||||
|
||||
|
||||
[composition-type]: {{<ref "../../master/concepts/compositions" >}}
|
||||
[Compositions]: {{<ref "../../master/concepts/compositions" >}}
|
||||
[canary]: https://martinfowler.com/bliki/CanaryRelease.html
|
||||
[install-guide]: {{<ref "../../master/software/install" >}}
|
|
@ -748,7 +748,7 @@ details.
|
|||
This section discusses creating Kubernetes secrets.
|
||||
Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/).
|
||||
|
||||
Read the [external secrets store guide]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) for more information on using Crossplane
|
||||
Read the [external secrets store guide]({{<ref "../guides/vault-as-secret-store">}}) for more information on using Crossplane
|
||||
with an external secret store.
|
||||
{{</hint >}}
|
||||
|
||||
|
@ -958,7 +958,7 @@ for more information on restricting secret keys.
|
|||
{{< /hint >}}
|
||||
|
||||
For more information on connection secrets read the
|
||||
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}).
|
||||
[Connection Secrets knowledge base article]({{<ref "connection-details">}}).
|
||||
|
||||
{{<hint "warning">}}
|
||||
You can't change the
|
||||
|
@ -973,7 +973,7 @@ recreate the Composition to change the
|
|||
#### Save connection details to an external secret store
|
||||
|
||||
Crossplane
|
||||
[External Secret Stores]({{<ref "/knowledge-base/integrations/vault-as-secret-store" >}})
|
||||
[External Secret Stores]({{<ref "../guides/vault-as-secret-store" >}})
|
||||
write secrets and connection details to external secret stores like HashiCorp
|
||||
Vault.
|
||||
|
||||
|
@ -1018,7 +1018,7 @@ spec:
|
|||
# Removed for brevity
|
||||
```
|
||||
|
||||
For more details read the [External Secret Stores]({{<ref "/knowledge-base/integrations/vault-as-secret-store" >}})
|
||||
For more details read the [External Secret Stores]({{<ref "../guides/vault-as-secret-store" >}})
|
||||
integration guide.
|
||||
|
||||
### Resource readiness checks
|
||||
|
|
|
@ -0,0 +1,607 @@
|
|||
---
|
||||
title: Understanding Connection Details
|
||||
weight: 11
|
||||
description: "How to create and manage connection details across Crossplane managed resources, composite resources, Compositions and Claims"
|
||||
---
|
||||
|
||||
Using connection details in Crossplane requires the following components:
|
||||
* Defining the `writeConnectionSecretToRef.name` in a [Claim]({{<ref "/master/concepts/claims#claim-connection-secrets">}}).
|
||||
* Defining the `writeConnectionSecretsToNamespace` value in the [Composition]({{<ref "/master/concepts/compositions#composite-resource-combined-secret">}}).
|
||||
* Define the `writeConnectionSecretToRef` name and namespace for each resource in the
|
||||
[Composition]({{<ref "/master/concepts/compositions#composed-resource-secrets">}}).
|
||||
* Define the list of secret keys produced by each composed resource with `connectionDetails` in the
|
||||
[Composition]({{<ref "/master/concepts/compositions#define-secret-keys">}}).
|
||||
* Optionally, define the `connectionSecretKeys` in a
|
||||
[CompositeResourceDefinition]({{<ref "/master/concepts/composite-resource-definitions#manage-connection-secrets">}}).
|
||||
|
||||
{{<hint "note">}}
|
||||
This guide discusses creating Kubernetes secrets.
|
||||
Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/).
|
||||
|
||||
Read the [external secrets store guide]({{<ref "../guides/vault-as-secret-store">}}) for more information on using Crossplane
|
||||
with an external secret store.
|
||||
{{</hint >}}
|
||||
|
||||
## Background
|
||||
When a [Provider]({{<ref "/master/concepts/providers">}}) creates a managed
|
||||
resource, the resource may generate resource-specific details. These details can include
|
||||
usernames, passwords or connection details like an IP address.
|
||||
|
||||
Crossplane refers to this information as the _connection details_ or
|
||||
_connection secrets_.
|
||||
|
||||
The Provider
|
||||
defines what information to present as a _connection
|
||||
detail_ from a managed resource.
|
||||
|
||||
<!-- vale gitlab.SentenceLength = NO -->
|
||||
<!-- wordy because of type names -->
|
||||
When a managed resource is part of a
|
||||
[Composition]({{<ref "/master/concepts/compositions">}}), the Composition,
|
||||
[Composite Resource Definition]({{<ref "/master/concepts/composite-resource-definitions">}})
|
||||
and optionally, the
|
||||
[Claim]({{<ref "/master/concepts/claims">}}) define what details are visible
|
||||
and where they're stored.
|
||||
<!-- vale gitlab.SentenceLength = YES -->
|
||||
|
||||
{{<hint "note">}}
|
||||
All the following examples use the same set of Compositions,
|
||||
CompositeResourceDefinitions and Claims.
|
||||
|
||||
All examples rely on
|
||||
[Upbound provider-aws-iam](https://marketplace.upbound.io/providers/upbound/provider-aws-iam/)
|
||||
to create resources.
|
||||
|
||||
{{<expand "Reference Composition" >}}
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: xsecrettest.example.org
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: other-namespace
|
||||
compositeTypeRef:
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: XSecretTest
|
||||
resources:
|
||||
- name: key
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
spec:
|
||||
forProvider:
|
||||
userSelector:
|
||||
matchControllerRef: true
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key1
|
||||
connectionDetails:
|
||||
- fromConnectionSecretKey: username
|
||||
- fromConnectionSecretKey: password
|
||||
- fromConnectionSecretKey: attribute.secret
|
||||
- fromConnectionSecretKey: attribute.ses_smtp_password_v4
|
||||
patches:
|
||||
- fromFieldPath: "metadata.uid"
|
||||
toFieldPath: "spec.writeConnectionSecretToRef.name"
|
||||
transforms:
|
||||
- type: string
|
||||
string:
|
||||
fmt: "%s-secret1"
|
||||
- name: user
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: User
|
||||
spec:
|
||||
forProvider: {}
|
||||
- name: user2
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: User
|
||||
metadata:
|
||||
labels:
|
||||
docs.crossplane.io: user
|
||||
spec:
|
||||
forProvider: {}
|
||||
- name: key2
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
spec:
|
||||
forProvider:
|
||||
userSelector:
|
||||
matchLabels:
|
||||
docs.crossplane.io: user
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key2
|
||||
connectionDetails:
|
||||
- name: key2-user
|
||||
fromConnectionSecretKey: username
|
||||
- name: key2-password
|
||||
fromConnectionSecretKey: password
|
||||
- name: key2-secret
|
||||
fromConnectionSecretKey: attribute.secret
|
||||
- name: key2-smtp
|
||||
fromConnectionSecretKey: attribute.ses_smtp_password_v4
|
||||
patches:
|
||||
- fromFieldPath: "metadata.uid"
|
||||
toFieldPath: "spec.writeConnectionSecretToRef.name"
|
||||
transforms:
|
||||
- type: string
|
||||
string:
|
||||
fmt: "%s-secret2"
|
||||
```
|
||||
{{</expand >}}
|
||||
|
||||
{{<expand "Reference CompositeResourceDefinition" >}}
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: xsecrettests.example.org
|
||||
spec:
|
||||
group: example.org
|
||||
connectionSecretKeys:
|
||||
- username
|
||||
- password
|
||||
- attribute.secret
|
||||
- attribute.ses_smtp_password_v4
|
||||
- key2-user
|
||||
- key2-pass
|
||||
- key2-secret
|
||||
- key2-smtp
|
||||
names:
|
||||
kind: XSecretTest
|
||||
plural: xsecrettests
|
||||
claimNames:
|
||||
kind: SecretTest
|
||||
plural: secrettests
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
referenceable: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
```
|
||||
{{</ expand >}}
|
||||
|
||||
{{<expand "Reference Claim" >}}
|
||||
```yaml
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: SecretTest
|
||||
metadata:
|
||||
name: test-secrets
|
||||
namespace: default
|
||||
spec:
|
||||
writeConnectionSecretToRef:
|
||||
name: my-access-key-secret
|
||||
```
|
||||
{{</expand >}}
|
||||
{{</hint >}}
|
||||
|
||||
## Connection secrets in a managed resource
|
||||
|
||||
<!-- vale gitlab.Substitutions = NO -->
|
||||
<!-- vale gitlab.SentenceLength = NO -->
|
||||
<!-- under 25 words -->
|
||||
When a managed resource creates connection secrets, Crossplane can write the
|
||||
secrets to a
|
||||
[Kubernetes secret]({{<ref "/master/concepts/managed-resources#publish-secrets-to-kubernetes">}})
|
||||
or an
|
||||
[external secret store]({{<ref "/master/concepts/managed-resources#publish-secrets-to-an-external-secrets-store">}}).
|
||||
<!-- vale gitlab.SentenceLength = YES -->
|
||||
<!-- vale gitlab.Substitutions = YES -->
|
||||
|
||||
Creating an individual managed resource shows the connection secrets the
|
||||
resource creates.
|
||||
|
||||
{{<hint "note" >}}
|
||||
Read the [managed resources]({{<ref "/master/concepts/managed-resources">}})
|
||||
documentation for more information on configuring resources and storing
|
||||
connection secrets for individual resources.
|
||||
{{< /hint >}}
|
||||
|
||||
|
||||
For example, create an
|
||||
{{<hover label="mr" line="2">}}AccessKey{{</hover>}} resource and save the
|
||||
connection secrets in a Kubernetes secret named
|
||||
{{<hover label="mr" line="12">}}my-accesskey-secret{{</hover>}}
|
||||
in the
|
||||
{{<hover label="mr" line="11">}}default{{</hover>}} namespace.
|
||||
|
||||
```yaml {label="mr"}
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
metadata:
|
||||
name: test-accesskey
|
||||
spec:
|
||||
forProvider:
|
||||
userSelector:
|
||||
matchLabels:
|
||||
docs.crossplane.io: user
|
||||
writeConnectionSecretToRef:
|
||||
namespace: default
|
||||
name: my-accesskey-secret
|
||||
```
|
||||
|
||||
View the Kubernetes secret to see the connection details from the managed
|
||||
resource.
|
||||
This includes an
|
||||
{{<hover label="mrSecret" line="11">}}attribute.secret{{</hover>}},
|
||||
{{<hover label="mrSecret" line="12">}}attribute.ses_smtp_password_v4{{</hover>}},
|
||||
{{<hover label="mrSecret" line="13">}}password{{</hover>}} and
|
||||
{{<hover label="mrSecret" line="14">}}username{{</hover>}}
|
||||
|
||||
```yaml {label="mrSecret",copy-lines="1"}
|
||||
kubectl describe secret my-accesskey-secret
|
||||
Name: my-accesskey-secret
|
||||
Namespace: default
|
||||
Labels: <none>
|
||||
Annotations: <none>
|
||||
|
||||
Type: connection.crossplane.io/v1alpha1
|
||||
|
||||
Data
|
||||
====
|
||||
attribute.secret: 40 bytes
|
||||
attribute.ses_smtp_password_v4: 44 bytes
|
||||
password: 40 bytes
|
||||
username: 20 bytes
|
||||
```
|
||||
|
||||
Compositions and CompositeResourceDefinitions require the exact names of the
|
||||
secrets generated by a resource.
|
||||
|
||||
## Connection secrets in Compositions
|
||||
|
||||
Resources in a Composition that create connection details still create a
|
||||
secret object containing their connection details.
|
||||
Crossplane also generates
|
||||
another secret object for each composite resource,
|
||||
containing the secrets from all the defined resources.
|
||||
|
||||
For example, a Composition defines two
|
||||
{{<hover label="comp1" line="9">}}AccessKey{{</hover>}}
|
||||
objects.
|
||||
Each {{<hover label="comp1" line="9">}}AccessKey{{</hover>}} writes a
|
||||
connection secrets to the {{<hover label="comp1" line="15">}}name{{</hover>}}
|
||||
inside the {{<hover label="comp1" line="14">}}namespace{{</hover>}} defined by
|
||||
the resource
|
||||
{{<hover label="comp1" line="13">}}writeConnectionSecretToRef{{</hover>}}.
|
||||
|
||||
Crossplane also creates a secret object for the entire Composition
|
||||
saved in the namespace defined by
|
||||
{{<hover label="comp1" line="4">}}writeConnectionSecretsToNamespace{{</hover>}}
|
||||
with a Crossplane generated name.
|
||||
|
||||
```yaml {label="comp1",copy-lines="none"}
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: other-namespace
|
||||
resources:
|
||||
- name: key1
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
spec:
|
||||
forProvider:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key1-secret
|
||||
- name: key2
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
spec:
|
||||
forProvider:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key2-secret
|
||||
# Removed for brevity
|
||||
```
|
||||
|
||||
After applying a Claim, view the Kubernetes secrets to see three secret objects
|
||||
created.
|
||||
|
||||
The secret
|
||||
{{<hover label="compGetSec" line="3">}}key1-secret{{</hover>}} is from the resource
|
||||
{{<hover label="comp1" line="6">}}key1{{</hover>}},
|
||||
{{<hover label="compGetSec" line="4">}}key2-secret{{</hover>}} is from the resource
|
||||
{{<hover label="comp1" line="16">}}key2{{</hover>}}.
|
||||
|
||||
Crossplane creates another secret in the namespace
|
||||
{{<hover label="compGetSec" line="5">}}other-namespace{{</hover>}} with the
|
||||
secrets from resource in the Composition.
|
||||
|
||||
|
||||
```shell {label="compGetSec",copy-lines="1"}
|
||||
kubectl get secrets -A
|
||||
NAMESPACE NAME TYPE DATA AGE
|
||||
docs key1-secret connection.crossplane.io/v1alpha1 4 4s
|
||||
docs key2-secret connection.crossplane.io/v1alpha1 4 4s
|
||||
other-namespace 70975471-c44f-4f6d-bde6-6bbdc9de1eb8 connection.crossplane.io/v1alpha1 0 6s
|
||||
```
|
||||
|
||||
Although Crossplane creates a secret object, by default, Crossplane doesn't add
|
||||
any data to the object.
|
||||
|
||||
```yaml {copy-lines="none"}
|
||||
kubectl describe secret 70975471-c44f-4f6d-bde6-6bbdc9de1eb8 -n other-namespace
|
||||
Name: 70975471-c44f-4f6d-bde6-6bbdc9de1eb8
|
||||
Namespace: other-namespace
|
||||
|
||||
Type: connection.crossplane.io/v1alpha1
|
||||
|
||||
Data
|
||||
====
|
||||
```
|
||||
|
||||
The Composition must list the connection secrets to store for each resource.
|
||||
Use the
|
||||
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}} object under
|
||||
each resource and define the secret keys the resource creates.
|
||||
|
||||
|
||||
{{<hint "warning">}}
|
||||
You can't change the
|
||||
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}
|
||||
of a Composition.
|
||||
You must delete and
|
||||
recreate the Composition to change the
|
||||
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}.
|
||||
{{</hint >}}
|
||||
|
||||
```yaml {label="comp2",copy-lines="16-20"}
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: other-namespace
|
||||
resources:
|
||||
- name: key
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
spec:
|
||||
forProvider:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key1
|
||||
connectionDetails:
|
||||
- fromConnectionSecretKey: username
|
||||
- fromConnectionSecretKey: password
|
||||
- fromConnectionSecretKey: attribute.secret
|
||||
- fromConnectionSecretKey: attribute.ses_smtp_password_v4
|
||||
# Removed for brevity
|
||||
```
|
||||
|
||||
After applying a Claim the composite resource secret object contains the list of
|
||||
keys listed in the
|
||||
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl describe secret -n other-namespace
|
||||
Name: b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a
|
||||
Namespace: other-namespace
|
||||
|
||||
Type: connection.crossplane.io/v1alpha1
|
||||
|
||||
Data
|
||||
====
|
||||
username: 20 bytes
|
||||
attribute.secret: 40 bytes
|
||||
attribute.ses_smtp_password_v4: 44 bytes
|
||||
password: 40 bytes
|
||||
```
|
||||
|
||||
{{<hint "important">}}
|
||||
If a key isn't listed in the
|
||||
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}
|
||||
it isn't stored in the secret object.
|
||||
{{< /hint >}}
|
||||
|
||||
### Managing conflicting secret keys
|
||||
If resources produce conflicting keys, create a unique name with a connection
|
||||
details
|
||||
{{<hover label="comp3" line="25">}}name{{</hover>}}.
|
||||
|
||||
```yaml {label="comp3",copy-lines="none"}
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: other-namespace
|
||||
resources:
|
||||
- name: key
|
||||
base:
|
||||
kind: AccessKey
|
||||
spec:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key1
|
||||
connectionDetails:
|
||||
- fromConnectionSecretKey: username
|
||||
- name: key2
|
||||
base:
|
||||
kind: AccessKey
|
||||
spec:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key2
|
||||
connectionDetails:
|
||||
- name: key2-user
|
||||
fromConnectionSecretKey: username
|
||||
```
|
||||
|
||||
The secret object contains both keys,
|
||||
{{<hover label="comp3Sec" line="9">}}username{{</hover>}}
|
||||
and
|
||||
{{<hover label="comp3Sec" line="10">}}key2-user{{</hover>}}
|
||||
|
||||
```shell {label="comp3Sec",copy-lines="1"}
|
||||
kubectl describe secret -n other-namespace
|
||||
Name: b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a
|
||||
Namespace: other-namespace
|
||||
|
||||
Type: connection.crossplane.io/v1alpha1
|
||||
|
||||
Data
|
||||
====
|
||||
username: 20 bytes
|
||||
key2-user: 20 bytes
|
||||
# Removed for brevity.
|
||||
```
|
||||
|
||||
## Connection secrets in Composite Resource Definitions
|
||||
|
||||
The CompositeResourceDefinition (`XRD`), can restrict which secrets keys are
|
||||
put in the combined secret and provided to a Claim.
|
||||
|
||||
By default an XRD writes all secret keys listed in the composed resource
|
||||
`connectionDetails` to the combined secret object.
|
||||
|
||||
Limit the keys passed to the combined secret object and Claims with a
|
||||
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} object.
|
||||
|
||||
Inside the {{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} list
|
||||
the secret key names to create. Crossplane only adds the keys listed to the
|
||||
combined secret.
|
||||
|
||||
{{<hint "warning">}}
|
||||
You can't change the
|
||||
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} of an XRD.
|
||||
You must delete and
|
||||
recreate the XRD to change the
|
||||
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}}.
|
||||
{{</hint >}}
|
||||
|
||||
For example, an XRD may restrict the secrets to only the
|
||||
{{<hover label="xrd" line="5">}}username{{</hover>}},
|
||||
{{<hover label="xrd" line="6">}}password{{</hover>}} and custom named
|
||||
{{<hover label="xrd" line="7">}}key2-user{{</hover>}} keys.
|
||||
|
||||
```yaml {label="xrd",copy-lines="4-12"}
|
||||
kind: CompositeResourceDefinition
|
||||
spec:
|
||||
# Removed for brevity.
|
||||
connectionSecretKeys:
|
||||
- username
|
||||
- password
|
||||
- key2-user
|
||||
```
|
||||
|
||||
The secret from an individual resource contains all the resources detailed in
|
||||
the Composition's `connectionDetails`.
|
||||
|
||||
```shell {label="xrdSec",copy-lines="1"}
|
||||
kubectl describe secret key1 -n docs
|
||||
Name: key1
|
||||
Namespace: docs
|
||||
|
||||
Data
|
||||
====
|
||||
password: 40 bytes
|
||||
username: 20 bytes
|
||||
attribute.secret: 40 bytes
|
||||
attribute.ses_smtp_password_v4: 44 bytes
|
||||
```
|
||||
|
||||
The Claim's secret only contains the
|
||||
keys allowed by the XRD
|
||||
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}}
|
||||
fields.
|
||||
|
||||
```shell {label="xrdSec2",copy-lines="2"}
|
||||
kubectl describe secret my-access-key-secret
|
||||
Name: my-access-key-secret
|
||||
|
||||
Data
|
||||
====
|
||||
key2-user: 20 bytes
|
||||
password: 40 bytes
|
||||
username: 20 bytes
|
||||
```
|
||||
|
||||
## Secret objects
|
||||
Compositions create a secret object for each resource and an extra secret
|
||||
containing all the secrets from all resources.
|
||||
|
||||
Crossplane saves the resource secret objects in the location defined by the
|
||||
resource's
|
||||
{{<hover label="comp4" line="11">}}writeConnectionSecretToRef{{</hover>}}.
|
||||
|
||||
Crossplane saves the combined secret with a Crossplane generated name in the
|
||||
namespace defined in the Composition's
|
||||
{{<hover label="comp4" line="4">}}writeConnectionSecretsToNamespace{{</hover>}}.
|
||||
|
||||
```yaml {label="comp4",copy-lines="none"}
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: other-namespace
|
||||
resources:
|
||||
- name: key
|
||||
base:
|
||||
kind: AccessKey
|
||||
spec:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key1
|
||||
connectionDetails:
|
||||
- fromConnectionSecretKey: username
|
||||
- name: key2
|
||||
base:
|
||||
kind: AccessKey
|
||||
spec:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key2
|
||||
connectionDetails:
|
||||
- name: key2-user
|
||||
fromConnectionSecretKey: username
|
||||
```
|
||||
|
||||
If a Claim uses a secret, it's stored in the same namespace as the Claim with
|
||||
the name defined in the Claim's
|
||||
{{<hover label="claim3" line="7">}}writeConnectionSecretToRef{{</hover>}}.
|
||||
|
||||
```yaml {label="claim3",copy-lines="none"}
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: SecretTest
|
||||
metadata:
|
||||
name: test-secrets
|
||||
namespace: default
|
||||
spec:
|
||||
writeConnectionSecretToRef:
|
||||
name: my-access-key-secret
|
||||
```
|
||||
|
||||
After applying the Claim Crossplane creates the following secrets:
|
||||
* The Claim's secret, {{<hover label="allSec" line="3">}}my-access-key-secret{{</hover>}}
|
||||
in the Claim's {{<hover label="claim3" line="5">}}namespace{{</hover>}}.
|
||||
* The first resource's secret object, {{<hover label="allSec" line="4">}}key1{{</hover>}}.
|
||||
* The second resource's secret object, {{<hover label="allSec" line="5">}}key2{{</hover>}}.
|
||||
* The composite resource secret object in the
|
||||
{{<hover label="allSec" line="6">}}other-namespace{{</hover>}} defined by the
|
||||
Composition's `writeConnectionSecretsToNamespace`.
|
||||
|
||||
|
||||
```shell {label="allSec",copy-lines="none"}
|
||||
kubectl get secret -A
|
||||
NAMESPACE NAME TYPE DATA AGE
|
||||
default my-access-key-secret connection.crossplane.io/v1alpha1 8 29m
|
||||
docs key1 connection.crossplane.io/v1alpha1 4 31m
|
||||
docs key2 connection.crossplane.io/v1alpha1 4 31m
|
||||
other-namespace b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a connection.crossplane.io/v1alpha1 8 31m
|
||||
```
|
|
@ -357,7 +357,7 @@ Crossplane supports the following policies:
|
|||
| `Create` | If the external resource doesn't exist, Crossplane creates it based on the managed resource settings. |
|
||||
| `Delete` | Crossplane can delete the external resource when deleting the managed resource. |
|
||||
| `LateInitialize` | Crossplane initializes some external resource settings not defined in the `spec.forProvider` of the managed resource. See [the late initialization]({{<ref "./managed-resources#late-initialization" >}}) section for more details. |
|
||||
| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{<ref "/knowledge-base/guides/import-existing-resources#import-resources-automatically">}}). |
|
||||
| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{<ref "../guides/import-existing-resources#import-resources-automatically">}}). |
|
||||
| `Update` | Crossplane changes the external resource when changing the managed resource. |
|
||||
{{</table >}}
|
||||
|
||||
|
@ -373,7 +373,7 @@ The following is a list of common policy combinations:
|
|||
| {{<check>}} | | {{<check>}} | {{<check>}} | | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't apply changes to the external resource after creation. |
|
||||
| {{<check>}} | | | {{<check>}} | {{<check>}} | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't import any settings from the external resource. |
|
||||
| {{<check>}} | | | {{<check>}} | | Crossplane creates the external resource but doesn't apply any changes to the external resource or managed resource. Crossplane can't delete the resource. |
|
||||
| | | | {{<check>}} | | Crossplane only observes a resource. Used for [observe only resources]({{<ref "/knowledge-base/guides/import-existing-resources#import-resources-automatically">}}). |
|
||||
| | | | {{<check>}} | | Crossplane only observes a resource. Used for [observe only resources]({{<ref "../guides/import-existing-resources#import-resources-automatically">}}). |
|
||||
| | | | | | No policy set. An alternative method for [pausing](#paused) a resource. |
|
||||
{{< /table >}}
|
||||
|
||||
|
@ -568,7 +568,7 @@ metadata:
|
|||
|
||||
{{<hint "tip" >}}
|
||||
Read the
|
||||
[Vault as an External Secrets Store]({{<ref "knowledge-base/integrations/vault-as-secret-store">}})
|
||||
[Vault as an External Secrets Store]({{<ref "../guides/vault-as-secret-store">}})
|
||||
guide for details on using StoreConfig objects.
|
||||
{{< /hint >}}
|
||||
|
||||
|
|
|
@ -395,7 +395,7 @@ If you remove the Provider first, you must manually delete external resources
|
|||
through your cloud provider. Managed resources must be manually deleted by
|
||||
removing their finalizers.
|
||||
|
||||
For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{<ref "/knowledge-base/guides/troubleshoot#deleting-when-a-resource-hangs" >}}).
|
||||
For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{<ref "../guides/troubleshoot-crossplane#deleting-when-a-resource-hangs" >}}).
|
||||
{{< /hint >}}
|
||||
|
||||
## Verify a Provider
|
||||
|
@ -584,12 +584,12 @@ replacement for Controller configuration and is available in v1.14+.
|
|||
|
||||
Applying a Crossplane `ControllerConfig` to a Provider changes the settings of
|
||||
the Provider's pod. The
|
||||
[Crossplane ControllerConfig schema](https://doc.crds.dev/github.com/crossplane/crossplane/pkg.crossplane.io/ControllerConfig/v1alpha1)
|
||||
[Crossplane ControllerConfig schema]({{< ref "../api#ControllerConfig-spec" >}})
|
||||
defines the supported set of ControllerConfig settings.
|
||||
|
||||
The most common use case for ControllerConfigs are providing `args` to a
|
||||
Provider's pod enabling optional services. For example, enabling
|
||||
[external secret stores](https://docs.crossplane.io/knowledge-base/integrations/vault-as-secret-store/#enable-external-secret-stores-in-the-provider)
|
||||
[external secret stores]({{< ref "../guides/vault-as-secret-store#enable-external-secret-stores-in-the-provider" >}})
|
||||
for a Provider.
|
||||
|
||||
Each Provider determines their supported set of `args`.
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Guides
|
||||
weight: 400
|
||||
description: Crossplane integrations and detailed examples.
|
||||
---
|
|
@ -0,0 +1,216 @@
|
|||
---
|
||||
title: Configuring Crossplane with Argo CD
|
||||
weight: 270
|
||||
---
|
||||
|
||||
[Argo CD](https://argoproj.github.io/cd/) and [Crossplane](https://crossplane.io)
|
||||
are a great combination. Argo CD provides GitOps while Crossplane turns any Kubernetes
|
||||
cluster into a Universal Control Plane for all of your resources. There are
|
||||
configuration details required in order for the two to work together properly.
|
||||
This doc will help you understand these requirements. It is recommended to use
|
||||
Argo CD version 2.4.8 or later with Crossplane.
|
||||
|
||||
Argo CD synchronizes Kubernetes resource manifests stored in a Git repository
|
||||
with those running in a Kubernetes cluster (GitOps). There are different ways to configure
|
||||
how Argo CD tracks resources. With Crossplane, you need to configure Argo CD
|
||||
to use Annotation based resource tracking. See the [Argo CD docs](https://argo-cd.readthedocs.io/en/latest/user-guide/resource_tracking/) for additional detail.
|
||||
|
||||
### Configuring Argo CD with Crossplane
|
||||
|
||||
#### Set Resource Tracking Method
|
||||
|
||||
In order for Argo CD to correctly track Application resources that contain Crossplane related objects it needs
|
||||
to be configured to use the annotation mechanism.
|
||||
|
||||
To configure it, edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace` as such:
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
application.resourceTrackingMethod: annotation
|
||||
```
|
||||
|
||||
#### Set Health Status
|
||||
|
||||
Argo CD has a built-in health assessment for Kubernetes resources. Some checks are supported by the community directly
|
||||
in Argo's [repository](https://github.com/argoproj/argo-cd/tree/master/resource_customizations). For example the `Provider`
|
||||
from `pkg.crossplane.io` has already been declared which means there no further configuration needed.
|
||||
|
||||
Argo CD also enable customising these checks per instance, and that's the mechanism used to provide support
|
||||
of Provider's CRDs.
|
||||
|
||||
To configure it, edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace`.
|
||||
{{<hint "note">}}
|
||||
{{<hover label="argocfg" line="22">}} ProviderConfig{{</hover>}} may have no status or a `status.users` field.
|
||||
{{</hint>}}
|
||||
```yaml {label="argocfg"}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
application.resourceTrackingMethod: annotation
|
||||
resource.customizations: |
|
||||
"*.upbound.io/*":
|
||||
health.lua: |
|
||||
health_status = {
|
||||
status = "Progressing",
|
||||
message = "Provisioning ..."
|
||||
}
|
||||
|
||||
local function contains (table, val)
|
||||
for i, v in ipairs(table) do
|
||||
if v == val then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local has_no_status = {
|
||||
"ProviderConfig",
|
||||
"ProviderConfigUsage"
|
||||
}
|
||||
|
||||
if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is up-to-date."
|
||||
return health_status
|
||||
end
|
||||
|
||||
if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
|
||||
if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is in use."
|
||||
return health_status
|
||||
end
|
||||
return health_status
|
||||
end
|
||||
|
||||
for i, condition in ipairs(obj.status.conditions) do
|
||||
if condition.type == "LastAsyncOperation" then
|
||||
if condition.status == "False" then
|
||||
health_status.status = "Degraded"
|
||||
health_status.message = condition.message
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
|
||||
if condition.type == "Synced" then
|
||||
if condition.status == "False" then
|
||||
health_status.status = "Degraded"
|
||||
health_status.message = condition.message
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
|
||||
if condition.type == "Ready" then
|
||||
if condition.status == "True" then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is up-to-date."
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return health_status
|
||||
|
||||
"*.crossplane.io/*":
|
||||
health.lua: |
|
||||
health_status = {
|
||||
status = "Progressing",
|
||||
message = "Provisioning ..."
|
||||
}
|
||||
|
||||
local function contains (table, val)
|
||||
for i, v in ipairs(table) do
|
||||
if v == val then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local has_no_status = {
|
||||
"Composition",
|
||||
"CompositionRevision",
|
||||
"DeploymentRuntimeConfig",
|
||||
"ControllerConfig",
|
||||
"ProviderConfig",
|
||||
"ProviderConfigUsage"
|
||||
}
|
||||
if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is up-to-date."
|
||||
return health_status
|
||||
end
|
||||
|
||||
if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
|
||||
if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is in use."
|
||||
return health_status
|
||||
end
|
||||
return health_status
|
||||
end
|
||||
|
||||
for i, condition in ipairs(obj.status.conditions) do
|
||||
if condition.type == "LastAsyncOperation" then
|
||||
if condition.status == "False" then
|
||||
health_status.status = "Degraded"
|
||||
health_status.message = condition.message
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
|
||||
if condition.type == "Synced" then
|
||||
if condition.status == "False" then
|
||||
health_status.status = "Degraded"
|
||||
health_status.message = condition.message
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
|
||||
if contains({"Ready", "Healthy", "Offered", "Established"}, condition.type) then
|
||||
if condition.status == "True" then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is up-to-date."
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return health_status
|
||||
```
|
||||
|
||||
#### Set Resource Exclusion
|
||||
|
||||
Crossplane providers generates a `ProviderConfigUsage` for each of the managed resource (MR) it handles. This resource
|
||||
enable representing the relationship between MR and a ProviderConfig so that the controller can use it as finalizer when a
|
||||
ProviderConfig is deleted. End-users of Crossplane are not expected to interact with this resource.
|
||||
|
||||
Argo CD UI reactivity can be impacted as the number of resource and types grow. To help keep this number low we
|
||||
recommend hiding all `ProviderConfigUsage` resources from Argo CD UI.
|
||||
|
||||
To configure resource exclusion edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace` as such:
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
resource.exclusions: |
|
||||
- apiGroups:
|
||||
- "*"
|
||||
kinds:
|
||||
- ProviderConfigUsage
|
||||
```
|
||||
|
||||
The use of `"*"` as apiGroups will enable the mechanism for all Crossplane Providers.
|
||||
|
||||
#### Increase K8s Client QPS
|
||||
|
||||
As the number of CRDs grow on a control plane it will increase the amount of queries Argo CD Application Controller
|
||||
needs to send to the Kubernetes API. If this is the case you can increase the rate limits of the Argo CD Kubernetes client.
|
||||
|
||||
Set the environment variable `ARGOCD_K8S_CLIENT_QPS` to `300` for improved compatibility with a large number of CRDs.
|
||||
|
||||
The default value of `ARGOCD_K8S_CLIENT_QPS` is 50, modifying the value will also update `ARGOCD_K8S_CLIENT_BURST` as it
|
||||
is default to `ARGOCD_K8S_CLIENT_QPS` x 2.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Disaster Recovery with Crossplane
|
||||
weight: 10
|
||||
---
|
||||
|
||||
AWS wrote a guide covering disaster recovery with Crossplane. The guide covers
|
||||
using Crossplane to provision resources and Velero for Kubernetes backup and
|
||||
recovery.
|
||||
|
||||
[Read the guide on AWS](https://aws.amazon.com/blogs/opensource/disaster-recovery-when-using-crossplane-for-infrastructure-provisioning-on-aws/).
|
|
@ -0,0 +1,285 @@
|
|||
---
|
||||
title: Import Existing Resources
|
||||
weight: 200
|
||||
---
|
||||
|
||||
If you have resources that are already provisioned in a Provider,
|
||||
you can import them as managed resources and let Crossplane manage them.
|
||||
A managed resource's [`managementPolicies`]({{<ref "/v1.13/concepts/managed-resources#managementpolicies">}})
|
||||
field enables importing external resources into Crossplane.
|
||||
|
||||
Crossplane can import resources either [manually]({{<ref "#import-resources-manually">}})
|
||||
or [automatically]({{<ref "#import-resources-automatically">}}).
|
||||
|
||||
## Import resources manually
|
||||
|
||||
Crossplane can discover and import existing Provider resources by matching the
|
||||
`crossplane.io/external-name` annotation in a managed resource.
|
||||
|
||||
To import an existing external resource in a Provider, create a new managed
|
||||
resource with the `crossplane.io/external-name` annotation. Set the annotation
|
||||
value to the name of the resource in the Provider.
|
||||
|
||||
For example, to import an existing GCP Network named
|
||||
{{<hover label="annotation" line="5">}}my-existing-network{{</hover>}},
|
||||
create a new managed resource and use the
|
||||
{{<hover label="annotation" line="5">}}my-existing-network{{</hover>}} in the
|
||||
annotation.
|
||||
|
||||
```yaml {label="annotation",copy-lines="none"}
|
||||
apiVersion: compute.gcp.crossplane.io/v1beta1
|
||||
kind: Network
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/external-name: my-existing-network
|
||||
```
|
||||
|
||||
The {{<hover label="name" line="5">}}metadata.name{{</hover>}}
|
||||
field can be anything you want. For example,
|
||||
{{<hover label="name" line="5">}}imported-network{{</hover>}}.
|
||||
|
||||
{{< hint "note" >}}
|
||||
This name is the
|
||||
name of the Kubernetes object. It's not related to the resource name inside the
|
||||
Provider.
|
||||
{{< /hint >}}
|
||||
|
||||
```yaml {label="name",copy-lines="none"}
|
||||
apiVersion: compute.gcp.crossplane.io/v1beta1
|
||||
kind: Network
|
||||
metadata:
|
||||
name: imported-network
|
||||
annotations:
|
||||
crossplane.io/external-name: my-existing-network
|
||||
```
|
||||
|
||||
Leave the
|
||||
{{<hover label="fp" line="8">}}spec.forProvider{{</hover>}} field empty.
|
||||
Crossplane imports the settings and automatically applies them to the managed
|
||||
resource.
|
||||
|
||||
{{< hint "important" >}}
|
||||
If the managed resource has _required_ fields in the
|
||||
{{<hover label="fp" line="8">}}spec.forProvider{{</hover>}} you must add it to
|
||||
the `forProvider` field.
|
||||
|
||||
The values of those fields must match what's inside the Provider or Crossplane
|
||||
overwrites the existing values.
|
||||
{{< /hint >}}
|
||||
|
||||
```yaml {label="fp",copy-lines="all"}
|
||||
apiVersion: compute.gcp.crossplane.io/v1beta1
|
||||
kind: Network
|
||||
metadata:
|
||||
name: imported-network
|
||||
annotations:
|
||||
crossplane.io/external-name: my-existing-network
|
||||
spec:
|
||||
forProvider: {}
|
||||
```
|
||||
|
||||
|
||||
Crossplane now controls and manages this imported resource. Any changes to the
|
||||
managed resource `spec` changes the external resource.
|
||||
|
||||
## Import resources automatically
|
||||
|
||||
Automatically import external resources with an `Observe` [management policy]({{<ref "/v1.13/concepts/managed-resources#managementpolicies">}}).
|
||||
|
||||
Crossplane imports observe only resources but never changes or deletes the
|
||||
resources.
|
||||
|
||||
{{<hint "important" >}}
|
||||
The managed resource `managementPolicies` option is a beta feature.
|
||||
|
||||
The Provider determines support for management policies.
|
||||
Refer to the Provider's documentation to see if the Provider supports
|
||||
management policies.
|
||||
{{< /hint >}}
|
||||
|
||||
<!-- vale off -->
|
||||
### Apply the Observe management policy
|
||||
<!-- vale on -->
|
||||
|
||||
Create a new managed resource matching the
|
||||
{{<hover label="oo-policy" line="1">}}apiVersion{{</hover>}} and
|
||||
{{<hover label="oo-policy" line="2">}}kind{{</hover>}} of the resource
|
||||
to import and add
|
||||
{{<hover label="oo-policy" line="4">}}managementPolicies: ["Observe"]{{</hover>}} to the
|
||||
{{<hover label="oo-policy" line="3">}}spec{{</hover>}}
|
||||
|
||||
For example, to import a GCP SQL DatabaseInstance, create a new resource with
|
||||
the {{<hover label="oo-policy" line="4">}}managementPolicies: ["Observe"]{{</hover>}}
|
||||
set.
|
||||
```yaml {label="oo-policy",copy-lines="none"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
spec:
|
||||
managementPolicies: ["Observe"]
|
||||
```
|
||||
|
||||
### Add the external-name annotation
|
||||
Add the {{<hover label="oo-ex-name" line="5">}}crossplane.io/external-name{{</hover>}}
|
||||
annotation for the resource. This name must match the name inside the Provider.
|
||||
|
||||
For example, for a GCP database named
|
||||
{{<hover label="oo-ex-name" line="5">}}my-external-database{{</hover>}}, apply
|
||||
the
|
||||
{{<hover label="oo-ex-name" line="5">}}crossplane.io/external-name{{</hover>}}
|
||||
annotation with the value
|
||||
{{<hover label="oo-ex-name" line="5">}}my-external-database{{</hover>}}.
|
||||
|
||||
```yaml {label="oo-ex-name",copy-lines="none"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/external-name: my-external-database
|
||||
spec:
|
||||
managementPolicies: ["Observe"]
|
||||
```
|
||||
|
||||
### Create a Kubernetes object name
|
||||
Create a {{<hover label="oo-name" line="4">}}name{{</hover>}} to use for the
|
||||
Kubernetes object.
|
||||
|
||||
For example, name the Kubernetes object
|
||||
{{<hover label="oo-name" line="4">}}my-imported-database{{</hover>}}.
|
||||
|
||||
```yaml {label="oo-name",copy-lines="none"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
metadata:
|
||||
name: my-imported-database
|
||||
annotations:
|
||||
crossplane.io/external-name: my-external-database
|
||||
spec:
|
||||
managementPolicies: ["Observe"]
|
||||
```
|
||||
|
||||
### Identify a specific external resource
|
||||
If more than one resource inside the Provider shares the same name, identify the
|
||||
specific resource with a unique
|
||||
{{<hover line="9" label="oo-region">}}spec.forProvider{{</hover>}} field.
|
||||
|
||||
For example, only import the GCP SQL database in the
|
||||
{{<hover line="10" label="oo-region">}}us-central1{{</hover>}} region.
|
||||
|
||||
```yaml {label="oo-region"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
metadata:
|
||||
name: my-imported-database
|
||||
annotations:
|
||||
crossplane.io/external-name: my-external-database
|
||||
spec:
|
||||
managementPolicies: ["Observe"]
|
||||
forProvider:
|
||||
region: "us-central1"
|
||||
```
|
||||
|
||||
### Apply the managed resource
|
||||
|
||||
Apply the new managed resource. Crossplane syncs the status of the external
|
||||
resource in the cloud with the newly created managed resource.
|
||||
|
||||
### View the discovered resource
|
||||
Crossplane discovers the managed resource and populates the
|
||||
{{<hover label="ooPopulated" line="12">}}status.atProvider{{</hover>}}
|
||||
fields with the values from the external resource.
|
||||
|
||||
```yaml {label="ooPopulated",copy-lines="none"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
metadata:
|
||||
name: my-imported-database
|
||||
annotations:
|
||||
crossplane.io/external-name: my-external-database
|
||||
spec:
|
||||
managementPolicies: ["Observe"]
|
||||
forProvider:
|
||||
region: us-central1
|
||||
status:
|
||||
atProvider:
|
||||
connectionName: crossplane-playground:us-central1:my-external-database
|
||||
databaseVersion: POSTGRES_14
|
||||
deletionProtection: true
|
||||
firstIpAddress: 35.184.74.79
|
||||
id: my-external-database
|
||||
publicIpAddress: 35.184.74.79
|
||||
region: us-central1
|
||||
# Removed for brevity
|
||||
settings:
|
||||
- activationPolicy: ALWAYS
|
||||
availabilityType: REGIONAL
|
||||
diskSize: 100
|
||||
# Removed for brevity
|
||||
pricingPlan: PER_USE
|
||||
tier: db-custom-4-26624
|
||||
version: 4
|
||||
conditions:
|
||||
- lastTransitionTime: "2023-02-22T07:16:51Z"
|
||||
reason: Available
|
||||
status: "True"
|
||||
type: Ready
|
||||
- lastTransitionTime: "2023-02-22T07:16:51Z"
|
||||
reason: ReconcileSuccess
|
||||
status: "True"
|
||||
type: Synced
|
||||
```
|
||||
<!-- vale off -->
|
||||
## Control imported ObserveOnly resources
|
||||
<!-- vale on -->
|
||||
|
||||
Crossplane can take active control of observe only imported resources by
|
||||
changing the `managementPolicies` after import.
|
||||
|
||||
Change the {{<hover label="fc" line="8">}}managementPolicies{{</hover>}} field
|
||||
of the managed resource to
|
||||
{{<hover label="fc" line="8">}}["*"]{{</hover>}}.
|
||||
|
||||
Copy any required parameter values from
|
||||
{{<hover label="fc" line="16">}}status.atProvider{{</hover>}} and provide them
|
||||
in {{<hover label="fc" line="9">}}spec.forProvider{{</hover>}}.
|
||||
|
||||
{{< hint "tip" >}}
|
||||
Manually copy the important `spec.atProvider` values to `spec.forProvider`.
|
||||
{{< /hint >}}
|
||||
|
||||
```yaml {label="fc"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
metadata:
|
||||
name: my-imported-database
|
||||
annotations:
|
||||
crossplane.io/external-name: my-external-database
|
||||
spec:
|
||||
managementPolicies: ["*"]
|
||||
forProvider:
|
||||
databaseVersion: POSTGRES_14
|
||||
region: us-central1
|
||||
settings:
|
||||
- diskSize: 100
|
||||
tier: db-custom-4-26624
|
||||
status:
|
||||
atProvider:
|
||||
databaseVersion: POSTGRES_14
|
||||
region: us-central1
|
||||
# Removed for brevity
|
||||
settings:
|
||||
- diskSize: 100
|
||||
tier: db-custom-4-26624
|
||||
# Removed for brevity
|
||||
conditions:
|
||||
- lastTransitionTime: "2023-02-22T07:16:51Z"
|
||||
reason: Available
|
||||
status: "True"
|
||||
type: Ready
|
||||
- lastTransitionTime: "2023-02-22T11:16:45Z"
|
||||
reason: ReconcileSuccess
|
||||
status: "True"
|
||||
type: Synced
|
||||
```
|
||||
|
||||
Crossplane now fully manages the imported resource. Crossplane applies any
|
||||
changes to the managed resource in the Provider's external resource.
|
|
@ -0,0 +1,323 @@
|
|||
---
|
||||
title: Multi-Tenant Crossplane
|
||||
weight: 240
|
||||
---
|
||||
|
||||
This guide describes how to use Crossplane effectively in multi-tenant
|
||||
environments by utilizing Kubernetes primitives and compatible policy
|
||||
enforcement projects in the cloud-native ecosystem.
|
||||
|
||||
## TL;DR
|
||||
|
||||
Infrastructure operators in multi-tenant Crossplane environments typically
|
||||
utilize composition and Kubernetes RBAC to define lightweight, standardized
|
||||
policies that dictate what level of self-service developers are given when
|
||||
requesting infrastructure. This is primarily achieved through exposing abstract
|
||||
resource types at the namespace scope, defining `Roles` for teams and
|
||||
individuals within that namespace, and patching the `spec.providerConfigRef` of
|
||||
the underlying managed resources so that they use a specific `ProviderConfig`
|
||||
and credentials when provisioned from each namespace. Larger organizations, or
|
||||
those with more complex environments, may choose to incorporate third-party
|
||||
policy engines, or scale to multiple Crossplane clusters. The following sections
|
||||
describe each of these scenarios in greater detail.
|
||||
|
||||
- [TL;DR](#tldr)
|
||||
- [Background](#background)
|
||||
- [Cluster-Scoped Managed Resources](#cluster-scoped-managed-resources)
|
||||
- [Namespace Scoped Claims](#namespace-scoped-claims)
|
||||
- [Single Cluster Multi-Tenancy](#single-cluster-multi-tenancy)
|
||||
- [Composition as an Isolation Mechanism](#composition-as-an-isolation-mechanism)
|
||||
- [Namespaces as an Isolation Mechanism](#namespaces-as-an-isolation-mechanism)
|
||||
- [Policy Enforcement with Open Policy Agent](#policy-enforcement-with-open-policy-agent)
|
||||
- [Multi-Cluster Multi-Tenancy](#multi-cluster-multi-tenancy)
|
||||
- [Reproducible Platforms with Configuration Packages](#reproducible-platforms-with-configuration-packages)
|
||||
- [Control Plane of Control Planes](#control-plane-of-control-planes)
|
||||
|
||||
## Background
|
||||
|
||||
Crossplane is designed to run in multi-tenant environments where many teams are
|
||||
consuming the services and abstractions provided by infrastructure operators in
|
||||
the cluster. This functionality is facilitated by two major design patterns in
|
||||
the Crossplane ecosystem.
|
||||
|
||||
### Cluster-Scoped Managed Resources
|
||||
|
||||
Typically, Crossplane providers, which supply granular [managed resources] that
|
||||
reflect an external API, authenticate by using a `ProviderConfig` object that
|
||||
points to a credentials source (such as a Kubernetes `Secret`, the `Pod`
|
||||
filesystem, or an environment variable). Then, every managed resource references
|
||||
a `ProviderConfig` that points to credentials with sufficient permissions to
|
||||
manage that resource type.
|
||||
|
||||
For example, the following `ProviderConfig` for `provider-aws` points to a
|
||||
Kubernetes `Secret` with AWS credentials.
|
||||
|
||||
```yaml
|
||||
apiVersion: aws.crossplane.io/v1beta1
|
||||
kind: ProviderConfig
|
||||
metadata:
|
||||
name: cool-aws-creds
|
||||
spec:
|
||||
credentials:
|
||||
source: Secret
|
||||
secretRef:
|
||||
namespace: crossplane-system
|
||||
name: aws-creds
|
||||
key: creds
|
||||
```
|
||||
|
||||
If a user desired for these credentials to be used to provision an
|
||||
`RDSInstance`, they would reference the `ProviderConfig` in the object manifest:
|
||||
|
||||
```yaml
|
||||
apiVersion: database.aws.crossplane.io/v1beta1
|
||||
kind: RDSInstance
|
||||
metadata:
|
||||
name: rdsmysql
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-1
|
||||
dbInstanceClass: db.t3.medium
|
||||
masterUsername: masteruser
|
||||
allocatedStorage: 20
|
||||
engine: mysql
|
||||
engineVersion: "5.6.35"
|
||||
skipFinalSnapshotBeforeDeletion: true
|
||||
providerConfigRef:
|
||||
name: cool-aws-creds # name of ProviderConfig above
|
||||
writeConnectionSecretToRef:
|
||||
namespace: crossplane-system
|
||||
name: aws-rdsmysql-conn
|
||||
```
|
||||
|
||||
Since both the `ProviderConfig` and all managed resources are cluster-scoped,
|
||||
the RDS controller in `provider-aws` will resolve this reference by fetching the
|
||||
`ProviderConfig`, obtaining the credentials it points to, and using those
|
||||
credentials to reconcile the `RDSInstance`. This means that anyone who has been
|
||||
given [RBAC] to manage `RDSInstance` objects can use any credentials to do so.
|
||||
In practice, Crossplane assumes that only folks acting as infrastructure
|
||||
administrators or platform builders will interact directly with cluster-scoped
|
||||
resources.
|
||||
|
||||
### Namespace Scoped Claims
|
||||
|
||||
While managed resources exist at the cluster scope, composite resources, which
|
||||
are defined using a **CompositeResourceDefinition (XRD)** may exist at either
|
||||
the cluster or namespace scope. Platform builders define XRDs and
|
||||
**Compositions** that specify what granular managed resources should be created
|
||||
in response to the creation of an instance of the XRD. More information about
|
||||
this architecture can be found in the [Composition] documentation.
|
||||
|
||||
Every XRD is exposed at the cluster scope, but only those with `spec.claimNames`
|
||||
defined will have a namespace-scoped variant.
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: xmysqlinstances.example.org
|
||||
spec:
|
||||
group: example.org
|
||||
names:
|
||||
kind: XMySQLInstance
|
||||
plural: xmysqlinstances
|
||||
claimNames:
|
||||
kind: MySQLInstance
|
||||
plural: mysqlinstances
|
||||
...
|
||||
```
|
||||
|
||||
When the example above is created, Crossplane will produce two
|
||||
[CustomResourceDefinitions]:
|
||||
1. A cluster-scoped type with `kind: XMySQLInstance`. This is referred to as a
|
||||
**Composite Resource (XR)**.
|
||||
2. A namespace-scoped type with `kind: MySQLInstance`. This is referred to as a
|
||||
**Claim (XRC)**.
|
||||
|
||||
Platform builders may choose to define an arbitrary number of Compositions that
|
||||
map to these types, meaning that creating a `MySQLInstance` in a given namespace
|
||||
can result in the creations of any set of managed resources at the cluster
|
||||
scope. For instance, creating a `MySQLInstance` could result in the creation of
|
||||
the `RDSInstance` defined above.
|
||||
|
||||
## Single Cluster Multi-Tenancy
|
||||
|
||||
Depending on the size and scope of an organization, platform teams may choose to
|
||||
run one central Crossplane control plane, or many different ones for each team
|
||||
or business unit. This section will focus on servicing multiple teams within a
|
||||
single cluster, which may or may not be one of many other Crossplane clusters in
|
||||
the organization.
|
||||
|
||||
### Composition as an Isolation Mechanism
|
||||
|
||||
While managed resources always reflect every field that the underlying provider
|
||||
API exposes, XRDs can have any schema that a platform builder chooses. The
|
||||
fields in the XRD schema can then be patched onto fields in the underlying
|
||||
managed resource defined in a Composition, essentially exposing those fields as
|
||||
configurable to the consumer of the XR or XRC.
|
||||
|
||||
This feature serves as a lightweight policy mechanism by only giving the
|
||||
consumer the ability to customize the underlying resources to the extent the
|
||||
platform builder desires. For instance, in the examples above, a platform
|
||||
builder may choose to define a `spec.location` field in the schema of the
|
||||
`XMySQLInstance` that is an enum with options `east` and `west`. In the
|
||||
Composition, those fields could map to the `RDSInstance` `spec.region` field,
|
||||
making the value either `us-east-1` or `us-west-1`. If no other patches were
|
||||
defined for the `RDSInstance`, giving a user the ability (using RBAC) to create
|
||||
a `XMySQLInstance` / `MySQLInstance` would be akin to giving the ability to
|
||||
create a very specifically configured `RDSInstance`, where they can only decide
|
||||
the region where it lives and they are restricted to two options.
|
||||
|
||||
This model is in contrast to many infrastructure as code tools where the end
|
||||
user must have provider credentials to create the underlying resources that are
|
||||
rendered from the abstraction. Crossplane takes a different approach, defining
|
||||
various credentials in the cluster (using the `ProviderConfig`), then giving
|
||||
only the provider controllers the ability to utilize those credentials and
|
||||
provision infrastructure on the users behalf. This creates a consistent
|
||||
permission model, even when using many providers with differing IAM models, by
|
||||
standardizing on Kubernetes RBAC.
|
||||
|
||||
### Namespaces as an Isolation Mechanism
|
||||
|
||||
While the ability to define abstract schemas and patches to concrete resource
|
||||
types using composition is powerful, the ability to define Claim types at the
|
||||
namespace scope enhances the functionality further by enabling RBAC to be
|
||||
applied with namespace restrictions. Most users in a cluster do not have access
|
||||
to cluster-scoped resources as they are considered only relevant to
|
||||
infrastructure admins by both Kubernetes and Crossplane.
|
||||
|
||||
Building on our simple `XMySQLInstance` / `MySQLInstance` example, a platform
|
||||
builder may choose to define permissions on `MySQLInstance` at the namespace
|
||||
scope using a `Role`. This allows for giving users the ability to create and
|
||||
manage `MySQLInstances` in their given namespace, but not the ability to see
|
||||
those defined in other namespaces.
|
||||
|
||||
Furthermore, because the `metadata.namespace` is a field on the XRC, patching can
|
||||
be utilized to configure managed resources based on the namespace in which the
|
||||
corresponding XRC was defined. This is especially useful if a platform builder
|
||||
wants to designate specific credentials or a set of credentials that users in a
|
||||
given namespace can utilize when provisioning infrastructure using an XRC. This
|
||||
can be accomplished today by creating one or more `ProviderConfig` objects that
|
||||
include the name of the namespace in the `ProviderConfig` name. For example, if
|
||||
any `MySQLInstance` created in the `team-1` namespace should use specific AWS
|
||||
credentials when the provider controller creates the underlying `RDSInstance`,
|
||||
the platform builder could:
|
||||
|
||||
1. Define a `ProviderConfig` with name `team-1`.
|
||||
|
||||
```yaml
|
||||
apiVersion: aws.crossplane.io/v1beta1
|
||||
kind: ProviderConfig
|
||||
metadata:
|
||||
name: team-1
|
||||
spec:
|
||||
credentials:
|
||||
source: Secret
|
||||
secretRef:
|
||||
namespace: crossplane-system
|
||||
name: team-1-creds
|
||||
key: creds
|
||||
```
|
||||
|
||||
2. Define a `Composition` that patches the namespace of the Claim reference in the XR
|
||||
to the `providerConfigRef` of the `RDSInstance`.
|
||||
|
||||
```yaml
|
||||
...
|
||||
resources:
|
||||
- base:
|
||||
apiVersion: database.aws.crossplane.io/v1beta1
|
||||
kind: RDSInstance
|
||||
spec:
|
||||
forProvider:
|
||||
...
|
||||
patches:
|
||||
- fromFieldPath: spec.claimRef.namespace
|
||||
toFieldPath: spec.providerConfigRef.name
|
||||
policy:
|
||||
fromFieldPath: Required
|
||||
```
|
||||
|
||||
This would result in the `RDSInstance` using the `ProviderConfig` of whatever
|
||||
namespace the corresponding `MySQLInstance` was created in.
|
||||
|
||||
> Note that this model currently only allows for a single `ProviderConfig` per
|
||||
> namespace. However, future Crossplane releases should allow for defining a set
|
||||
> of `ProviderConfig` that can be selected from using [Multiple Source Field
|
||||
> patching].
|
||||
|
||||
### Policy Enforcement with Open Policy Agent
|
||||
|
||||
In some Crossplane deployment models, only using composition and RBAC to define
|
||||
policy will not be flexible enough. However, because Crossplane brings
|
||||
management of external infrastructure to the Kubernetes API, it is well suited
|
||||
to integrate with other projects in the cloud-native ecosystem. Organizations
|
||||
and individuals that need a more robust policy engine, or just prefer a more
|
||||
general language for defining policy, often turn to [Open Policy Agent] (OPA).
|
||||
OPA allows platform builders to write custom logic in [Rego], a domain-specific
|
||||
language. Writing policy in this manner allows for not only incorporating the
|
||||
information available in the specific resource being evaluated, but also using
|
||||
other state represented in the cluster. Crossplane users typically install OPA's
|
||||
[Gatekeeper] to make policy management as streamlined as possible.
|
||||
|
||||
> A live demo of using OPA with Crossplane can be viewed [here].
|
||||
|
||||
## Multi-Cluster Multi-Tenancy
|
||||
|
||||
Organizations that deploy Crossplane across many clusters typically take
|
||||
advantage of two major features that make managing multiple control planes much
|
||||
simpler.
|
||||
|
||||
### Reproducible Platforms with Configuration Packages
|
||||
|
||||
[Configuration packages] allow platform builders to package their XRDs and
|
||||
Compositions into [OCI images] that can be distributed via any OCI-compliant
|
||||
image registry. These packages can also declare dependencies on providers,
|
||||
meaning that a single package can declare all of the granular managed resources,
|
||||
the controllers that must be deployed to reconcile them, and the abstract types
|
||||
that expose the underlying resources using composition.
|
||||
|
||||
Organizations with many Crossplane deployments utilize Configuration packages to
|
||||
reproduce their platform in each cluster. This can be as simple as installing
|
||||
Crossplane with the flag to automatically install a Configuration package
|
||||
alongside it.
|
||||
|
||||
```
|
||||
helm install crossplane --namespace crossplane-system crossplane-stable/crossplane --set configuration.packages='{"registry.upbound.io/xp/getting-started-with-aws:latest"}'
|
||||
```
|
||||
|
||||
### Control Plane of Control Planes
|
||||
|
||||
Taking the multi-cluster multi-tenancy model one step further, some
|
||||
organizations opt to manage their many Crossplane clusters using a single
|
||||
central Crossplane control plane. This requires setting up the central cluster,
|
||||
then using a provider to spin up new clusters (such as an [EKS Cluster] using
|
||||
[provider-aws]), then using [provider-helm] to install Crossplane into the new
|
||||
remote cluster, potentially bundling a common Configuration package into each
|
||||
install using the method described above.
|
||||
|
||||
This advanced pattern allows for full management of Crossplane clusters using
|
||||
Crossplane itself, and when done properly, is a scalable solution to providing
|
||||
dedicated control planes to many tenants within a single organization.
|
||||
|
||||
|
||||
<!-- Named Links -->
|
||||
[managed resources]: {{<ref "../../master/concepts/managed-resources" >}}
|
||||
[RBAC]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/
|
||||
[Composition]: {{<ref "../../master/concepts/compositions" >}}
|
||||
[CustomResourceDefinitions]: https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/
|
||||
[Open Policy Agent]: https://www.openpolicyagent.org/
|
||||
[Rego]: https://www.openpolicyagent.org/docs/latest/policy-language/
|
||||
[Gatekeeper]: https://open-policy-agent.github.io/gatekeeper/website/docs/
|
||||
[here]: https://youtu.be/TaF0_syejXc
|
||||
[Multiple Source Field patching]: https://github.com/crossplane/crossplane/pull/2093
|
||||
[Configuration packages]: {{<ref "../../master/concepts/packages" >}}
|
||||
[OCI images]: https://github.com/opencontainers/image-spec
|
||||
[EKS Cluster]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws/latest/resources/eks.aws.crossplane.io/Cluster/v1beta1
|
||||
[provider-aws]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws
|
||||
[provider-helm]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-helm/
|
||||
[Open Service Broker API]: https://github.com/openservicebrokerapi/servicebroker
|
||||
[Crossplane Service Broker]: https://github.com/vshn/crossplane-service-broker
|
||||
[Cloudfoundry]: https://www.cloudfoundry.org/
|
||||
[Kubernetes Service Catalog]: https://github.com/kubernetes-sigs/service-catalog
|
||||
[vshn/application-catalog-demo]: https://github.com/vshn/application-catalog-demo
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
title: Self-Signed CA Certs
|
||||
weight: 270
|
||||
---
|
||||
|
||||
> Using self-signed certificates is not advised in production, it is
|
||||
recommended to only use self-signed certificates for testing.
|
||||
|
||||
When Crossplane loads Configuration and Provider Packages from private
|
||||
registries, it must be configured to trust the CA and Intermediate certs.
|
||||
|
||||
Crossplane needs to be installed via the Helm chart with the
|
||||
`registryCaBundleConfig.name` and `registryCaBundleConfig.key` parameters
|
||||
defined. See [Install Crossplane]({{<ref "../../master/software/install" >}}).
|
||||
|
||||
## Configure
|
||||
|
||||
1. Create a CA Bundle (A file containing your Root and Intermediate
|
||||
certificates in a specific order). This can be done with any text editor or
|
||||
from the command line, so long as the resulting file contains all required crt
|
||||
files in the proper order. In many cases, this will be either a single
|
||||
self-signed Root CA crt file, or an Intermediate crt and Root crt file. The
|
||||
order of the crt files should be from lowest to highest in signing order.
|
||||
For example, if you have a chain of two certificates below your Root
|
||||
certificate, you place the bottom level Intermediate cert at the beginning of
|
||||
the file, then the Intermediate cert that singed that cert, then the Root cert
|
||||
that signed that cert.
|
||||
|
||||
2. Save the files as `[yourdomain].ca-bundle`.
|
||||
|
||||
3. Create a Kubernetes ConfigMap in your Crossplane system namespace:
|
||||
|
||||
```
|
||||
kubectl -n [Crossplane system namespace] create cm ca-bundle-config \
|
||||
--from-file=ca-bundle=./[yourdomain].ca-bundle
|
||||
```
|
||||
|
||||
4. Set the `registryCaBundleConfig.name` Helm chart parameter to
|
||||
`ca-bundle-config` and the `registryCaBundleConfig.key` parameter to
|
||||
`ca-bundle`.
|
||||
|
||||
> Providing Helm with parameter values is convered in the Helm docs,
|
||||
[Helm install](https://helm.sh/docs/helm/helm_install/). An example block
|
||||
in an `override.yaml` file would look like this:
|
||||
```
|
||||
registryCaBundleConfig:
|
||||
name: ca-bundle-config
|
||||
key: ca-bundle
|
||||
```
|
|
@ -0,0 +1,443 @@
|
|||
---
|
||||
title: Troubleshoot
|
||||
weight: 306
|
||||
---
|
||||
## Requested Resource Not Found
|
||||
|
||||
If you use the Crossplane CLI to install a `Provider` or
|
||||
`Configuration` (e.g. `crossplane install provider
|
||||
xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0`) and get `the server
|
||||
could not find the requested resource` error, more often than not, that is an
|
||||
indicator that the Crossplane CLI you're using is outdated. In other words
|
||||
some Crossplane API has been graduated from alpha to beta or stable and the old
|
||||
plugin is not aware of this change.
|
||||
|
||||
|
||||
## Resource Status and Conditions
|
||||
|
||||
Most Crossplane resources have a `status` section that can represent the current
|
||||
state of that particular resource. Running `kubectl describe` against a
|
||||
Crossplane resource will frequently give insightful information about its
|
||||
condition. For example, to determine the status of a GCP `CloudSQLInstance`
|
||||
managed resource use `kubectl describe` for the resource.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl describe cloudsqlinstance my-db
|
||||
Status:
|
||||
Conditions:
|
||||
Last Transition Time: 2019-09-16T13:46:42Z
|
||||
Reason: Creating
|
||||
Status: False
|
||||
Type: Ready
|
||||
```
|
||||
|
||||
Most Crossplane resources set the `Ready` condition. `Ready` represents the
|
||||
availability of the resource - whether it is creating, deleting, available,
|
||||
unavailable, binding, etc.
|
||||
|
||||
## Resource Events
|
||||
|
||||
Most Crossplane resources emit _events_ when something interesting happens. You
|
||||
can see the events associated with a resource by running `kubectl describe` -
|
||||
e.g. `kubectl describe cloudsqlinstance my-db`. You can also see all events in a
|
||||
particular namespace by running `kubectl get events`.
|
||||
|
||||
```console
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Warning CannotConnectToProvider 16s (x4 over 46s) managed/postgresqlserver.database.azure.crossplane.io cannot get referenced ProviderConfig: ProviderConfig.azure.crossplane.io "default" not found
|
||||
```
|
||||
|
||||
> Note that events are namespaced, while many Crossplane resources (XRs, etc)
|
||||
> are cluster scoped. Crossplane emits events for cluster scoped resources to
|
||||
> the 'default' namespace.
|
||||
|
||||
## Crossplane Logs
|
||||
|
||||
The next place to look to get more information or investigate a failure would be
|
||||
in the Crossplane pod logs, which should be running in the `crossplane-system`
|
||||
namespace. To get the current Crossplane logs, run the following:
|
||||
|
||||
```shell
|
||||
kubectl -n crossplane-system logs -lapp=crossplane
|
||||
```
|
||||
|
||||
> Note that Crossplane emits few logs by default - events are typically the best
|
||||
> place to look for information about what Crossplane is doing. You may need to
|
||||
> restart Crossplane with the `--debug` flag if you can't find what you're
|
||||
> looking for.
|
||||
|
||||
## Provider Logs
|
||||
|
||||
Remember that much of Crossplane's functionality is provided by providers. You
|
||||
can use `kubectl logs` to view provider logs too. By convention, they also emit
|
||||
few logs by default.
|
||||
|
||||
```shell
|
||||
kubectl -n crossplane-system logs <name-of-provider-pod>
|
||||
```
|
||||
|
||||
All providers maintained by the Crossplane community mirror Crossplane's support
|
||||
of the `--debug` flag. The easiest way to set flags on a provider is to create a
|
||||
`ControllerConfig` and reference it from the `Provider`:
|
||||
|
||||
```yaml
|
||||
apiVersion: pkg.crossplane.io/v1alpha1
|
||||
kind: ControllerConfig
|
||||
metadata:
|
||||
name: debug-config
|
||||
spec:
|
||||
args:
|
||||
- --debug
|
||||
---
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: provider-aws
|
||||
spec:
|
||||
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0
|
||||
controllerConfigRef:
|
||||
name: debug-config
|
||||
```
|
||||
|
||||
> Note that a reference to a `ControllerConfig` can be added to an already
|
||||
> installed `Provider` and it will update its `Deployment` accordingly.
|
||||
|
||||
## Compositions and composite resource definition
|
||||
|
||||
### General troubleshooting steps
|
||||
|
||||
Crossplane and its providers log most error messages to resources' event fields. Whenever your Composite Resources aren't getting provisioned, follow the following steps:
|
||||
|
||||
1. Get the events for the root resource using `kubectl describe` or `kubectl get event`
|
||||
2. If there are errors in the events, address them.
|
||||
3. If there are no errors, follow its sub-resources.
|
||||
|
||||
`kubectl get <KIND> <NAME> -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq`
|
||||
4. Repeat this process for each resource returned.
|
||||
|
||||
{{< hint "note" >}}
|
||||
The rest of this section show you how to debug issues related to compositions without using external tooling.
|
||||
If you are using ArgoCD or FluxCD with UI, you can visualize object relationships in the UI.
|
||||
You can also use the kube-lineage plugin to visualize object relationships in your terminal.
|
||||
{{< /hint >}}
|
||||
|
||||
### Examples
|
||||
|
||||
#### Composition
|
||||
<!-- vale Google.WordList = NO -->
|
||||
You deployed an example application using a claim. Kind = `ExampleApp`. Name = `example-application`.
|
||||
|
||||
|
||||
The example application never reaches available state as shown below.
|
||||
|
||||
|
||||
1. View the claim.
|
||||
|
||||
```bash
|
||||
kubectl describe exampleapp example-application
|
||||
|
||||
Status:
|
||||
Conditions:
|
||||
Last Transition Time: 2022-03-01T22:57:38Z
|
||||
Reason: Composite resource claim is waiting for composite resource to become Ready
|
||||
Status: False
|
||||
Type: Ready
|
||||
Events: <none>
|
||||
```
|
||||
|
||||
2. If the claim doesn't have errors, inspect the `.spec.resourceRef` field of the claim.
|
||||
|
||||
```bash
|
||||
kubectl get exampleapp example-application -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq
|
||||
|
||||
{
|
||||
"apiVersion": "awsblueprints.io/v1alpha1",
|
||||
"kind": "XExampleApp",
|
||||
"name": "example-application-xqlsz"
|
||||
}
|
||||
```
|
||||
3. In the preceding output, you see the cluster scoped resource for this claim. Kind = `XExampleApp` name = `example-application-xqlsz`
|
||||
4. View the cluster scoped resource's events.
|
||||
|
||||
```bash
|
||||
kubectl describe xexampleapp example-application-xqlsz
|
||||
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Normal PublishConnectionSecret 9s (x2 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully published connection details
|
||||
Normal SelectComposition 6s (x6 over 11s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully selected composition
|
||||
Warning ComposeResources 6s (x6 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io can't render composed resource from resource template at index 3: can't use dry-run create to name composed resource: an empty namespace may not be set during creation
|
||||
Normal ComposeResources 6s (x6 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully composed resources
|
||||
```
|
||||
5. You see errors in the events. it's complaining about not specifying namespace in its compositions. For this particular kind of error, you can get its sub-resources and check which one isn't created.
|
||||
|
||||
```bash
|
||||
kubectl get xexampleapp example-application-xqlsz -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq
|
||||
|
||||
[
|
||||
{
|
||||
"apiVersion": "awsblueprints.io/v1alpha1",
|
||||
"kind": "XDynamoDBTable",
|
||||
"name": "example-application-xqlsz-6j9nm"
|
||||
},
|
||||
{
|
||||
"apiVersion": "awsblueprints.io/v1alpha1",
|
||||
"kind": "XIAMPolicy",
|
||||
"name": "example-application-xqlsz-lp9wt"
|
||||
},
|
||||
{
|
||||
"apiVersion": "awsblueprints.io/v1alpha1",
|
||||
"kind": "XIAMPolicy",
|
||||
"name": "example-application-xqlsz-btwkn"
|
||||
},
|
||||
{
|
||||
"apiVersion": "awsblueprints.io/v1alpha1",
|
||||
"kind": "IRSA"
|
||||
}
|
||||
]
|
||||
```
|
||||
6. Notice the last element in the array doesn't have a name. When a resource in composition fails validation, the resource object isn't created and doesn't have a name. For this particular issue, you must specify the namespace for the IRSA resource.
|
||||
|
||||
#### Composite resource definition
|
||||
|
||||
Debugging Composite Resource Definition (XRD) is like debugging Compositions.
|
||||
|
||||
1. Get the XRD
|
||||
|
||||
```bash
|
||||
kubectl get xrd testing.awsblueprints.io
|
||||
|
||||
NAME ESTABLISHED OFFERED AGE
|
||||
testing.awsblueprints.io 66s
|
||||
```
|
||||
2. Notice its status it not established. You describe this XRD to get its events.
|
||||
|
||||
```bash
|
||||
kubectl describe xrd testing.awsblueprints.io
|
||||
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Normal ApplyClusterRoles 3m19s (x3 over 3m19s) rbac/compositeresourcedefinition.apiextensions.crossplane.io Applied RBAC ClusterRoles
|
||||
Normal RenderCRD 18s (x9 over 3m19s) defined/compositeresourcedefinition.apiextensions.crossplane.io Rendered composite resource CustomResourceDefinition
|
||||
Warning EstablishComposite 18s (x9 over 3m19s) defined/compositeresourcedefinition.apiextensions.crossplane.io can't apply rendered composite resource CustomResourceDefinition: can't create object: CustomResourceDefinition.apiextensions.k8s.io "testing.awsblueprints.io" is invalid: metadata.name: Invalid value: "testing.awsblueprints.io": must be spec.names.plural+"."+spec.group
|
||||
```
|
||||
3. You see in the events that Crossplane can't generate corresponding CRDs for this XRD. In this case, ensure the name is `spec.names.plural+"."+spec.group`
|
||||
|
||||
#### Providers
|
||||
|
||||
You can use install providers in two ways: `configuration.pkg.crossplane.io` and `provider.pkg.crossplane.io`. You can use either one to install providers with no functional differences to providers themselves.
|
||||
If you define a `configuration.pkg.crossplane.io` object, Crossplane creates a
|
||||
`provider.pkg.crossplane.io` object and manages it. Refer to [the Packages
|
||||
documentation]({{<ref "/master/concepts/packages">}})
|
||||
for more information about Crossplane Packages.
|
||||
|
||||
If you are experiencing provider issues, steps below are a good starting point.
|
||||
|
||||
1. Check the status of provider object.
|
||||
```bash
|
||||
kubectl describe provider.pkg.crossplane.io provider-aws
|
||||
|
||||
Status:
|
||||
Conditions:
|
||||
Last Transition Time: 2022-08-04T16:19:44Z
|
||||
Reason: HealthyPackageRevision
|
||||
Status: True
|
||||
Type: Healthy
|
||||
Last Transition Time: 2022-08-04T16:14:29Z
|
||||
Reason: ActivePackageRevision
|
||||
Status: True
|
||||
Type: Installed
|
||||
Current Identifier: crossplane/provider-aws:v0.29.0
|
||||
Current Revision: provider-aws-a2e16ca2fc1a
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Normal InstallPackageRevision 9m49s (x237 over 4d17h) packages/provider.pkg.crossplane.io Successfully installed package revision
|
||||
```
|
||||
In the output above you see that this provider is healthy. To get more information about this provider, you can dig deeper. The `Current Revision` field let you know of your next object to look at.
|
||||
|
||||
|
||||
2. When you create a provider object, Crossplane creates a `ProviderRevision` object based on the contents of the OCI image. In this example, you're specifying the OCI image to be `crossplane/provider-aws:v0.29.0`. This image contains a YAML file which defines Kubernetes objects such as Deployment, ServiceAccount, and CRDs.
|
||||
The `ProviderRevision` object creates resources necessary for a provider to function based on the contents of the YAML file. To inspect what's deployed as part of the provider package, you inspect the ProviderRevision object. The `Current Revision` field above indicates which ProviderRevision object this provider uses.
|
||||
|
||||
```bash
|
||||
kubectl get providerrevision provider-aws-a2e16ca2fc1a
|
||||
|
||||
NAME HEALTHY REVISION IMAGE STATE DEP-FOUND DEP-INSTALLED AGE
|
||||
provider-aws-a2e16ca2fc1a True 1 crossplane/provider-aws:v0.29.0 Active 19d
|
||||
```
|
||||
|
||||
When you describe the object, you find all CRDs managed by this object.
|
||||
|
||||
```bash
|
||||
kubectl describe providerrevision provider-aws-a2e16ca2fc1a
|
||||
|
||||
Status:
|
||||
Controller Ref:
|
||||
Name: provider-aws-a2e16ca2fc1a
|
||||
Object Refs:
|
||||
API Version: apiextensions.k8s.io/v1
|
||||
Kind: CustomResourceDefinition
|
||||
Name: natgateways.ec2.aws.crossplane.io
|
||||
UID: 5c36d1bc-61b8-44f8-bca0-47e368af87a9
|
||||
....
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Normal SyncPackage 22m (x369 over 4d18h) packages/providerrevision.pkg.crossplane.io Successfully configured package revision
|
||||
Normal BindClusterRole 15m (x348 over 4d18h) rbac/providerrevision.pkg.crossplane.io Bound system ClusterRole to provider ServiceAccount
|
||||
Normal ApplyClusterRoles 15m (x364 over 4d18h) rbac/providerrevision.pkg.crossplane.io Applied RBAC ClusterRoles
|
||||
```
|
||||
|
||||
The event field also indicates any issues that may have occurred during this process.
|
||||
<!-- vale Google.WordList = YES -->
|
||||
3. If you don't see any errors in the event field above, you should check if Crossplane provisioned deployments and their status.
|
||||
|
||||
```bash
|
||||
kubectl get deployment -n crossplane-system
|
||||
|
||||
NAME READY UP-TO-DATE AVAILABLE AGE
|
||||
crossplane 1/1 1 1 105d
|
||||
crossplane-rbac-manager 1/1 1 1 105d
|
||||
provider-aws-a2e16ca2fc1a 1/1 1 1 19d
|
||||
|
||||
kubectl get pods -n crossplane-system
|
||||
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
crossplane-54db688c8d-qng6b 2/2 Running 0 4d19h
|
||||
crossplane-rbac-manager-5776c9fbf4-wn5rj 1/1 Running 0 4d19h
|
||||
provider-aws-a2e16ca2fc1a-776769ccbd-4dqml 1/1 Running 0 4d23h
|
||||
```
|
||||
If there are any pods failing, check its logs and remedy the problem.
|
||||
|
||||
|
||||
## Pausing Crossplane
|
||||
|
||||
Sometimes, for example when you encounter a bug, it can be useful to pause
|
||||
Crossplane if you want to stop it from actively attempting to manage your
|
||||
resources. To pause Crossplane without deleting all of its resources, run the
|
||||
following command to simply scale down its deployment:
|
||||
|
||||
```bash
|
||||
kubectl -n crossplane-system scale --replicas=0 deployment/crossplane
|
||||
```
|
||||
|
||||
Once you have been able to rectify the problem or smooth things out, you can
|
||||
unpause Crossplane simply by scaling its deployment back up:
|
||||
|
||||
```bash
|
||||
kubectl -n crossplane-system scale --replicas=1 deployment/crossplane
|
||||
```
|
||||
|
||||
## Pausing Providers
|
||||
|
||||
Providers can also be paused when troubleshooting an issue or orchestrating a
|
||||
complex migration of resources. Creating and referencing a `ControllerConfig` is
|
||||
the easiest way to scale down a provider, and the `ControllerConfig` can be
|
||||
modified or the reference can be removed to scale it back up:
|
||||
|
||||
```yaml
|
||||
apiVersion: pkg.crossplane.io/v1alpha1
|
||||
kind: ControllerConfig
|
||||
metadata:
|
||||
name: scale-config
|
||||
spec:
|
||||
replicas: 0
|
||||
---
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: provider-aws
|
||||
spec:
|
||||
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0
|
||||
controllerConfigRef:
|
||||
name: scale-config
|
||||
```
|
||||
|
||||
> Note that a reference to a `ControllerConfig` can be added to an already
|
||||
> installed `Provider` and it will update its `Deployment` accordingly.
|
||||
|
||||
## Deleting When a Resource Hangs
|
||||
|
||||
The resources that Crossplane manages will automatically be cleaned up so as not
|
||||
to leave anything running behind. This is accomplished by using finalizers, but
|
||||
in certain scenarios the finalizer can prevent the Kubernetes object from
|
||||
getting deleted.
|
||||
|
||||
To deal with this, we essentially want to patch the object to remove its
|
||||
finalizer, which will then allow it to be deleted completely. Note that this
|
||||
won't necessarily delete the external resource that Crossplane was managing, so
|
||||
you will want to go to your cloud provider's console and look there for any
|
||||
lingering resources to clean up.
|
||||
|
||||
In general, a finalizer can be removed from an object with this command:
|
||||
|
||||
```shell
|
||||
kubectl patch <resource-type> <resource-name> -p '{"metadata":{"finalizers": []}}' --type=merge
|
||||
```
|
||||
|
||||
For example, for a `CloudSQLInstance` managed resource (`database.gcp.crossplane.io`) named
|
||||
`my-db`, you can remove its finalizer with:
|
||||
|
||||
```shell
|
||||
kubectl patch cloudsqlinstance my-db -p '{"metadata":{"finalizers": []}}' --type=merge
|
||||
```
|
||||
|
||||
## Tips, Tricks, and Troubleshooting
|
||||
|
||||
In this section we'll cover some common tips, tricks, and troubleshooting steps
|
||||
for working with Composite Resources. If you're trying to track down why your
|
||||
Composite Resources aren't working the [Troubleshooting][trouble-ref] page also
|
||||
has some useful information.
|
||||
|
||||
### Troubleshooting Claims and XRs
|
||||
|
||||
Crossplane relies heavily on status conditions and events for troubleshooting.
|
||||
You can see both using `kubectl describe` - for example:
|
||||
|
||||
```console
|
||||
# Describe the PostgreSQLInstance claim named my-db
|
||||
kubectl describe postgresqlinstance.database.example.org my-db
|
||||
```
|
||||
|
||||
Per Kubernetes convention, Crossplane keeps errors close to the place they
|
||||
happen. This means that if your claim is not becoming ready due to an issue with
|
||||
your `Composition` or with a composed resource you'll need to "follow the
|
||||
references" to find out why. Your claim will only tell you that the XR is not
|
||||
yet ready.
|
||||
|
||||
To follow the references:
|
||||
|
||||
1. Find your XR by running `kubectl describe` on your claim and looking for its
|
||||
"Resource Ref" (aka `spec.resourceRef`).
|
||||
1. Run `kubectl describe` on your XR. This is where you'll find out about issues
|
||||
with the `Composition` you're using, if any.
|
||||
1. If there are no issues but your XR doesn't seem to be becoming ready, take a
|
||||
look for the "Resource Refs" (or `spec.resourceRefs`) to find your composed
|
||||
resources.
|
||||
1. Run `kubectl describe` on each referenced composed resource to determine
|
||||
whether it is ready and what issues, if any, it is encountering.
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Named Links -->
|
||||
[Requested Resource Not Found]: #requested-resource-not-found
|
||||
[install Crossplane CLI]: "../getting-started/install-configure"
|
||||
[Resource Status and Conditions]: #resource-status-and-conditions
|
||||
[Resource Events]: #resource-events
|
||||
[Crossplane Logs]: #crossplane-logs
|
||||
[Provider Logs]: #provider-logs
|
||||
[Pausing Crossplane]: #pausing-crossplane
|
||||
[Pausing Providers]: #pausing-providers
|
||||
[Deleting When a Resource Hangs]: #deleting-when-a-resource-hangs
|
||||
[Installing Crossplane Package]: #installing-crossplane-package
|
||||
[Crossplane package]: /master/concepts/packages/
|
||||
[Handling Crossplane Package Dependency]: #handling-crossplane-package-dependency
|
||||
[semver spec]: https://github.com/Masterminds/semver#basic-comparisons
|
||||
|
||||
|
|
@ -0,0 +1,627 @@
|
|||
---
|
||||
title: Vault as an External Secret Store
|
||||
weight: 230
|
||||
---
|
||||
|
||||
This guide walks through the steps required to configure Crossplane and
|
||||
its Providers to use [Vault] as an [External Secret Store] (`ESS`) with [ESS Plugin Vault].
|
||||
|
||||
{{<hint "warning" >}}
|
||||
External Secret Stores are an alpha feature.
|
||||
|
||||
They're not recommended for production use. Crossplane disables External Secret
|
||||
Stores by default.
|
||||
{{< /hint >}}
|
||||
|
||||
Crossplane uses sensitive information including Provider credentials, inputs to
|
||||
managed resources and connection details.
|
||||
|
||||
The [Vault credential injection guide]({{<ref "vault-injection" >}}) details
|
||||
using Vault and Crossplane for Provider credentials.
|
||||
|
||||
Crossplane doesn't support for using Vault for managed resources input.
|
||||
[Crossplane issue #2985](https://github.com/crossplane/crossplane/issues/2985)
|
||||
tracks support for this feature.
|
||||
|
||||
Supporting connection details with Vault requires a Crossplane external secret
|
||||
store.
|
||||
|
||||
## Prerequisites
|
||||
This guide requires [Helm](https://helm.sh) version 3.11 or later.
|
||||
|
||||
## Install Vault
|
||||
|
||||
{{<hint "note" >}}
|
||||
Detailed instructions on [installing
|
||||
Vault](https://developer.hashicorp.com/vault/docs/platform/k8s/helm)
|
||||
are available from the Vault documentation.
|
||||
{{< /hint >}}
|
||||
|
||||
### Add the Vault Helm chart
|
||||
|
||||
Add the Helm repository for `hashicorp`.
|
||||
```shell
|
||||
helm repo add hashicorp https://helm.releases.hashicorp.com --force-update
|
||||
```
|
||||
|
||||
Install Vault using Helm.
|
||||
```shell
|
||||
helm -n vault-system upgrade --install vault hashicorp/vault --create-namespace
|
||||
```
|
||||
|
||||
### Unseal Vault
|
||||
|
||||
If Vault is [sealed](https://developer.hashicorp.com/vault/docs/concepts/seal)
|
||||
unseal Vault using the unseal keys.
|
||||
|
||||
Get the Vault keys.
|
||||
```shell
|
||||
kubectl -n vault-system exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json
|
||||
VAULT_UNSEAL_KEY=$(cat cluster-keys.json | jq -r ".unseal_keys_b64[]")
|
||||
```
|
||||
|
||||
Unseal the vault using the keys.
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n vault-system exec vault-0 -- vault operator unseal $VAULT_UNSEAL_KEY
|
||||
Key Value
|
||||
--- -----
|
||||
Seal Type shamir
|
||||
Initialized true
|
||||
Sealed false
|
||||
Total Shares 1
|
||||
Threshold 1
|
||||
Version 1.13.1
|
||||
Build Date 2023-03-23T12:51:35Z
|
||||
Storage Type file
|
||||
Cluster Name vault-cluster-df884357
|
||||
Cluster ID b3145d26-2c1a-a7f2-a364-81753033c0d9
|
||||
HA Enabled false
|
||||
```
|
||||
|
||||
## Configure Vault Kubernetes authentication
|
||||
|
||||
Enable the [Kubernetes auth method] for Vault to authenticate requests based on
|
||||
Kubernetes service accounts.
|
||||
|
||||
### Get the Vault root token
|
||||
|
||||
The Vault root token is inside the JSON file created when
|
||||
[unsealing Vault](#unseal-vault).
|
||||
|
||||
```shell
|
||||
cat cluster-keys.json | jq -r ".root_token"
|
||||
```
|
||||
|
||||
### Enable Kubernetes authentication
|
||||
|
||||
Connect to a shell in the Vault pod.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n vault-system exec -it vault-0 -- /bin/sh
|
||||
/ $
|
||||
```
|
||||
|
||||
From the Vault shell, login to Vault using the _root token_.
|
||||
```shell {copy-lines="1"}
|
||||
vault login # use the root token from above
|
||||
Token (will be hidden):
|
||||
Success! You are now authenticated. The token information displayed below
|
||||
is already stored in the token helper. You do NOT need to run "vault login"
|
||||
again. Future Vault requests will automatically use this token.
|
||||
|
||||
Key Value
|
||||
--- -----
|
||||
token hvs.TSN4SssfMBM0HAtwGrxgARgn
|
||||
token_accessor qodxHrINVlRXKyrGeeDkxnih
|
||||
token_duration ∞
|
||||
token_renewable false
|
||||
token_policies ["root"]
|
||||
identity_policies []
|
||||
policies ["root"]
|
||||
```
|
||||
|
||||
Enable the Kubernetes authentication method in Vault.
|
||||
```shell {copy-lines="1"}
|
||||
vault auth enable kubernetes
|
||||
Success! Enabled kubernetes auth method at: kubernetes/
|
||||
```
|
||||
|
||||
Configure Vault to communicate with Kubernetes and exit the Vault shell
|
||||
|
||||
```shell {copy-lines="1-4"}
|
||||
vault write auth/kubernetes/config \
|
||||
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
|
||||
kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
|
||||
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
Success! Data written to: auth/kubernetes/config
|
||||
/ $ exit
|
||||
```
|
||||
|
||||
## Configure Vault for Crossplane integration
|
||||
|
||||
Crossplane relies on the Vault key-value secrets engine to store information and
|
||||
Vault requires a permissions policy for the Crossplane service account.
|
||||
|
||||
<!-- vale Crossplane.Spelling = NO -->
|
||||
<!-- allow "kv" -->
|
||||
### Enable the Vault kv secrets engine
|
||||
<!-- vale Crossplane.Spelling = YES -->
|
||||
|
||||
Enable the [Vault KV Secrets Engine].
|
||||
|
||||
{{< hint "important" >}}
|
||||
Vault has two versions of the
|
||||
[KV Secrets Engine](https://developer.hashicorp.com/vault/docs/secrets/kv).
|
||||
This example uses version 2.
|
||||
{{</hint >}}
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n vault-system exec -it vault-0 -- vault secrets enable -path=secret kv-v2
|
||||
Success! Enabled the kv-v2 secrets engine at: secret/
|
||||
```
|
||||
|
||||
### Create a Vault policy for Crossplane
|
||||
|
||||
Create the Vault policy to allow Crossplane to read and write data from Vault.
|
||||
```shell {copy-lines="1-8"}
|
||||
kubectl -n vault-system exec -i vault-0 -- vault policy write crossplane - <<EOF
|
||||
path "secret/data/*" {
|
||||
capabilities = ["create", "read", "update", "delete"]
|
||||
}
|
||||
path "secret/metadata/*" {
|
||||
capabilities = ["create", "read", "update", "delete"]
|
||||
}
|
||||
EOF
|
||||
Success! Uploaded policy: crossplane
|
||||
```
|
||||
|
||||
Apply the policy to Vault.
|
||||
```shell {copy-lines="1-5"}
|
||||
kubectl -n vault-system exec -it vault-0 -- vault write auth/kubernetes/role/crossplane \
|
||||
bound_service_account_names="*" \
|
||||
bound_service_account_namespaces=crossplane-system \
|
||||
policies=crossplane \
|
||||
ttl=24h
|
||||
Success! Data written to: auth/kubernetes/role/crossplane
|
||||
```
|
||||
|
||||
## Install Crossplane
|
||||
|
||||
{{<hint "important" >}}
|
||||
Crossplane v1.12 introduced the plugin support. Make sure your version of Crossplane supports plugins.
|
||||
{{< /hint >}}
|
||||
|
||||
Install the Crossplane with the External Secrets Stores feature enabled.
|
||||
|
||||
```shell
|
||||
helm upgrade --install crossplane crossplane-stable/crossplane --namespace crossplane-system --create-namespace --set args='{--enable-external-secret-stores}'
|
||||
```
|
||||
|
||||
## Install the Crossplane Vault plugin
|
||||
|
||||
The Crossplane Vault plugin isn't part of the default Crossplane install.
|
||||
The plugin installs as a unique Pod that uses the [Vault Agent Sidecar
|
||||
Injection] to connect the Vault secret store to Crossplane.
|
||||
|
||||
First, configure annotations for the Vault plugin pod.
|
||||
|
||||
```yaml
|
||||
cat > values.yaml <<EOF
|
||||
podAnnotations:
|
||||
vault.hashicorp.com/agent-inject: "true"
|
||||
vault.hashicorp.com/agent-inject-token: "true"
|
||||
vault.hashicorp.com/role: crossplane
|
||||
vault.hashicorp.com/agent-run-as-user: "65532"
|
||||
EOF
|
||||
```
|
||||
Next, install the Crossplane ESS Plugin pod to the `crossplane-system` namespace
|
||||
and apply the Vault annotations.
|
||||
|
||||
```shell
|
||||
helm upgrade --install ess-plugin-vault oci://xpkg.upbound.io/crossplane-contrib/ess-plugin-vault --namespace crossplane-system -f values.yaml
|
||||
```
|
||||
|
||||
## Configure Crossplane
|
||||
|
||||
Using the Vault plugin requires configuration to connect to the Vault
|
||||
service. The plugin also requires Providers to enable external secret stores.
|
||||
|
||||
With the plugin and providers configured, Crossplane requires two `StoreConfig`
|
||||
objects to describe how Crossplane and the Providers communicate with vault.
|
||||
|
||||
### Enable external secret stores in the Provider
|
||||
|
||||
{{<hint "note">}}
|
||||
This example uses Provider GCP, but the
|
||||
{{<hover label="ControllerConfig" line="2">}}ControllerConfig{{</hover>}} is the
|
||||
same for all Providers.
|
||||
{{</hint >}}
|
||||
|
||||
Create a `ControllerConfig` object to enable external secret stores.
|
||||
|
||||
```yaml {label="ControllerConfig"}
|
||||
echo "apiVersion: pkg.crossplane.io/v1alpha1
|
||||
kind: ControllerConfig
|
||||
metadata:
|
||||
name: vault-config
|
||||
spec:
|
||||
args:
|
||||
- --enable-external-secret-stores" | kubectl apply -f -
|
||||
```
|
||||
|
||||
Install the Provider and apply the ControllerConfig.
|
||||
```yaml
|
||||
echo "apiVersion: pkg.crossplane.io/v1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: provider-gcp
|
||||
spec:
|
||||
package: xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.23.0-rc.0.19.ge9b75ee5
|
||||
controllerConfigRef:
|
||||
name: vault-config" | kubectl apply -f -
|
||||
```
|
||||
|
||||
### Connect the Crossplane plugin to Vault
|
||||
Create a {{<hover label="VaultConfig" line="2">}}VaultConfig{{</hover>}}
|
||||
resource for the plugin to connect to the Vault service:
|
||||
|
||||
```yaml {label="VaultConfig"}
|
||||
echo "apiVersion: secrets.crossplane.io/v1alpha1
|
||||
kind: VaultConfig
|
||||
metadata:
|
||||
name: vault-internal
|
||||
spec:
|
||||
server: http://vault.vault-system:8200
|
||||
mountPath: secret/
|
||||
version: v2
|
||||
auth:
|
||||
method: Token
|
||||
token:
|
||||
source: Filesystem
|
||||
fs:
|
||||
path: /vault/secrets/token" | kubectl apply -f -
|
||||
```
|
||||
|
||||
### Create a Crossplane StoreConfig
|
||||
|
||||
Create a {{<hover label="xp-storeconfig" line="2">}}StoreConfig{{</hover >}}
|
||||
object from the
|
||||
{{<hover label="xp-storeconfig" line="1">}}secrets.crossplane.io{{</hover >}}
|
||||
group. Crossplane uses the StoreConfig to connect to the Vault plugin service.
|
||||
|
||||
The {{<hover label="xp-storeconfig" line="10">}}configRef{{</hover >}} connects
|
||||
the StoreConfig to the specific Vault plugin configuration.
|
||||
|
||||
```yaml {label="xp-storeconfig"}
|
||||
echo "apiVersion: secrets.crossplane.io/v1alpha1
|
||||
kind: StoreConfig
|
||||
metadata:
|
||||
name: vault
|
||||
spec:
|
||||
type: Plugin
|
||||
defaultScope: crossplane-system
|
||||
plugin:
|
||||
endpoint: ess-plugin-vault.crossplane-system:4040
|
||||
configRef:
|
||||
apiVersion: secrets.crossplane.io/v1alpha1
|
||||
kind: VaultConfig
|
||||
name: vault-internal" | kubectl apply -f -
|
||||
```
|
||||
|
||||
|
||||
### Create a Provider StoreConfig
|
||||
Create a {{<hover label="gcp-storeconfig" line="2">}}StoreConfig{{</hover >}}
|
||||
object from the Provider's API group,
|
||||
{{<hover label="gcp-storeconfig" line="1">}}gcp.crossplane.io{{</hover >}}.
|
||||
The Provider uses this StoreConfig to communicate with Vault for
|
||||
Managed Resources.
|
||||
|
||||
The {{<hover label="gcp-storeconfig" line="10">}}configRef{{</hover >}} connects
|
||||
the StoreConfig to the specific Vault plugin configuration.
|
||||
|
||||
```yaml {label="gcp-storeconfig"}
|
||||
echo "apiVersion: gcp.crossplane.io/v1alpha1
|
||||
kind: StoreConfig
|
||||
metadata:
|
||||
name: vault
|
||||
spec:
|
||||
type: Plugin
|
||||
defaultScope: crossplane-system
|
||||
plugin:
|
||||
endpoint: ess-plugin-vault.crossplane-system:4040
|
||||
configRef:
|
||||
apiVersion: secrets.crossplane.io/v1alpha1
|
||||
kind: VaultConfig
|
||||
name: vault-internal" | kubectl apply -f -
|
||||
```
|
||||
|
||||
## Create Provider resources
|
||||
|
||||
Check that Crossplane installed the Provider and the Provider is healthy.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get providers
|
||||
NAME INSTALLED HEALTHY PACKAGE AGE
|
||||
provider-gcp True True xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.23.0-rc.0.19.ge9b75ee5 10m
|
||||
```
|
||||
|
||||
### Create a CompositeResourceDefinition
|
||||
|
||||
Create a `CompositeResourceDefinition` to define a custom API endpoint.
|
||||
|
||||
```yaml
|
||||
echo "apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: compositeessinstances.ess.example.org
|
||||
annotations:
|
||||
feature: ess
|
||||
spec:
|
||||
group: ess.example.org
|
||||
names:
|
||||
kind: CompositeESSInstance
|
||||
plural: compositeessinstances
|
||||
claimNames:
|
||||
kind: ESSInstance
|
||||
plural: essinstances
|
||||
connectionSecretKeys:
|
||||
- publicKey
|
||||
- publicKeyType
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
referenceable: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
serviceAccount:
|
||||
type: string
|
||||
required:
|
||||
- serviceAccount
|
||||
required:
|
||||
- parameters" | kubectl apply -f -
|
||||
```
|
||||
|
||||
### Create a Composition
|
||||
Create a `Composition` to create a Service Account and Service Account Key
|
||||
inside GCP.
|
||||
|
||||
Creating a Service Account Key generates
|
||||
{{<hover label="comp" line="39" >}}connectionDetails{{</hover>}} that the
|
||||
Provider stores in Vault using the
|
||||
{{<hover label="comp" line="31">}}publishConnectionDetailsTo{{</hover>}} details.
|
||||
|
||||
```yaml {label="comp"}
|
||||
echo "apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: essinstances.ess.example.org
|
||||
labels:
|
||||
feature: ess
|
||||
spec:
|
||||
publishConnectionDetailsWithStoreConfigRef:
|
||||
name: vault
|
||||
compositeTypeRef:
|
||||
apiVersion: ess.example.org/v1alpha1
|
||||
kind: CompositeESSInstance
|
||||
resources:
|
||||
- name: serviceaccount
|
||||
base:
|
||||
apiVersion: iam.gcp.crossplane.io/v1alpha1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: ess-test-sa
|
||||
spec:
|
||||
forProvider:
|
||||
displayName: a service account to test ess
|
||||
- name: serviceaccountkey
|
||||
base:
|
||||
apiVersion: iam.gcp.crossplane.io/v1alpha1
|
||||
kind: ServiceAccountKey
|
||||
spec:
|
||||
forProvider:
|
||||
serviceAccountSelector:
|
||||
matchControllerRef: true
|
||||
publishConnectionDetailsTo:
|
||||
name: ess-mr-conn
|
||||
metadata:
|
||||
labels:
|
||||
environment: development
|
||||
team: backend
|
||||
configRef:
|
||||
name: vault
|
||||
connectionDetails:
|
||||
- fromConnectionSecretKey: publicKey
|
||||
- fromConnectionSecretKey: publicKeyType" | kubectl apply -f -
|
||||
```
|
||||
|
||||
### Create a Claim
|
||||
Now create a `Claim` to have Crossplane create the GCP resources and associated
|
||||
secrets.
|
||||
|
||||
Like the Composition, the Claim uses
|
||||
{{<hover label="claim" line="12">}}publishConnectionDetailsTo{{</hover>}} to
|
||||
connect to Vault and store the secrets.
|
||||
|
||||
```yaml {label="claim"}
|
||||
echo "apiVersion: ess.example.org/v1alpha1
|
||||
kind: ESSInstance
|
||||
metadata:
|
||||
name: my-ess
|
||||
namespace: default
|
||||
spec:
|
||||
parameters:
|
||||
serviceAccount: ess-test-sa
|
||||
compositionSelector:
|
||||
matchLabels:
|
||||
feature: ess
|
||||
publishConnectionDetailsTo:
|
||||
name: ess-claim-conn
|
||||
metadata:
|
||||
labels:
|
||||
environment: development
|
||||
team: backend
|
||||
configRef:
|
||||
name: vault" | kubectl apply -f -
|
||||
```
|
||||
|
||||
## Verify the resources
|
||||
|
||||
Verify all resources are `READY` and `SYNCED`:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get managed
|
||||
NAME READY SYNCED DISPLAYNAME EMAIL DISABLED
|
||||
serviceaccount.iam.gcp.crossplane.io/my-ess-zvmkz-vhklg True True a service account to test ess my-ess-zvmkz-vhklg@testingforbugbounty.iam.gserviceaccount.com
|
||||
|
||||
NAME READY SYNCED KEY_ID CREATED_AT EXPIRES_AT
|
||||
serviceaccountkey.iam.gcp.crossplane.io/my-ess-zvmkz-bq8pz True True 5cda49b7c32393254b5abb121b4adc07e140502c 2022-03-23T10:54:50Z
|
||||
```
|
||||
|
||||
View the claims
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n default get claim
|
||||
NAME READY CONNECTION-SECRET AGE
|
||||
my-ess True 19s
|
||||
```
|
||||
|
||||
View the composite resources.
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get composite
|
||||
NAME READY COMPOSITION AGE
|
||||
my-ess-zvmkz True essinstances.ess.example.org 32s
|
||||
```
|
||||
|
||||
## Verify Vault secrets
|
||||
|
||||
Look inside Vault to view the secrets from the managed resources.
|
||||
|
||||
```shell {copy-lines="1",label="vault-key"}
|
||||
kubectl -n vault-system exec -i vault-0 -- vault kv list /secret/default
|
||||
Keys
|
||||
----
|
||||
ess-claim-conn
|
||||
```
|
||||
|
||||
The key {{<hover label="vault-key" line="4">}}ess-claim-conn{{</hover>}}
|
||||
is the name of the Claim's
|
||||
{{<hover label="claim" line="12">}}publishConnectionDetailsTo{{</hover>}}
|
||||
configuration.
|
||||
|
||||
Check connection secrets in the "crossplane-system" Vault scope.
|
||||
```shell {copy-lines="1",label="scope-key"}
|
||||
kubectl -n vault-system exec -i vault-0 -- vault kv list /secret/crossplane-system
|
||||
Keys
|
||||
----
|
||||
d2408335-eb88-4146-927b-8025f405da86
|
||||
ess-mr-conn
|
||||
```
|
||||
|
||||
The key
|
||||
{{<hover label="scope-key"line="4">}}d2408335-eb88-4146-927b-8025f405da86{{</hover>}}
|
||||
comes from
|
||||
|
||||
<!-- ## WHERE DOES IT COME FROM? -->
|
||||
|
||||
and the key
|
||||
{{<hover label="scope-key"line="5">}}ess-mr-conn{{</hover>}}
|
||||
comes from the Composition's
|
||||
{{<hover label="comp" line="31">}}publishConnectionDetailsTo{{</hover>}}
|
||||
configuration.
|
||||
|
||||
|
||||
Check contents of Claim's connection secret `ess-claim-conn` to see the key
|
||||
created by the managed resource.
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n vault-system exec -i vault-0 -- vault kv get /secret/default/ess-claim-conn
|
||||
======= Metadata =======
|
||||
Key Value
|
||||
--- -----
|
||||
created_time 2022-03-18T21:24:07.2085726Z
|
||||
custom_metadata map[environment:development secret.crossplane.io/ner-uid:881cd9a0-6cc6-418f-8e1d-b36062c1e108 team:backend]
|
||||
deletion_time n/a
|
||||
destroyed false
|
||||
version 1
|
||||
|
||||
======== Data ========
|
||||
Key Value
|
||||
--- -----
|
||||
publicKey -----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzsEYCokmYEsZJCc9QN/8
|
||||
Fm1M/kTPp7Gat/MXLTP3zFyCTBFVNLN79MbAKdinWi6ePXEb75vzB79IdZcWj8lo
|
||||
8trnS64QjNB9Vs4Xk5UvDALwleFN/bZeperxivDPwVPvT9Aqy/U9kohoS/LHyE8w
|
||||
uWQb5AuMeVQ1gtCTnCqQZ4d2MSVhQXYVvAWax1spJ9LT7mHub5j95xDdYIcOV3VJ
|
||||
l9CIo4VrWIT8THFN2NnjTrGq9+0TzXY0bV674bjJkfBC6v6yXs5HTetG+Uekq/xf
|
||||
FCjrrDi1+2UR9Mu2WTuvl8qn50be+mbwdJO5wE32jewxdYrVVmj19+PkaEeAwGTc
|
||||
vwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
publicKeyType TYPE_RAW_PUBLIC_KEY
|
||||
```
|
||||
|
||||
Check contents of managed resource connection secret `ess-mr-conn`. The public
|
||||
key is identical to the public key in the Claim since the Claim is using this
|
||||
managed resource.
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n vault-system exec -i vault-0 -- vault kv get /secret/crossplane-system/ess-mr-conn
|
||||
======= Metadata =======
|
||||
Key Value
|
||||
--- -----
|
||||
created_time 2022-03-18T21:21:07.9298076Z
|
||||
custom_metadata map[environment:development secret.crossplane.io/ner-uid:4cd973f8-76fc-45d6-ad45-0b27b5e9252a team:backend]
|
||||
deletion_time n/a
|
||||
destroyed false
|
||||
version 2
|
||||
|
||||
========= Data =========
|
||||
Key Value
|
||||
--- -----
|
||||
privateKey {
|
||||
"type": "service_account",
|
||||
"project_id": "REDACTED",
|
||||
"private_key_id": "REDACTED",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nREDACTED\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "ess-test-sa@REDACTED.iam.gserviceaccount.com",
|
||||
"client_id": "REDACTED",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/ess-test-sa%40REDACTED.iam.gserviceaccount.com"
|
||||
}
|
||||
privateKeyType TYPE_GOOGLE_CREDENTIALS_FILE
|
||||
publicKey -----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzsEYCokmYEsZJCc9QN/8
|
||||
Fm1M/kTPp7Gat/MXLTP3zFyCTBFVNLN79MbAKdinWi6ePXEb75vzB79IdZcWj8lo
|
||||
8trnS64QjNB9Vs4Xk5UvDALwleFN/bZeperxivDPwVPvT9Aqy/U9kohoS/LHyE8w
|
||||
uWQb5AuMeVQ1gtCTnCqQZ4d2MSVhQXYVvAWax1spJ9LT7mHub5j95xDdYIcOV3VJ
|
||||
l9CIo4VrWIT8THFN2NnjTrGq9+0TzXY0bV674bjJkfBC6v6yXs5HTetG+Uekq/xf
|
||||
FCjrrDi1+2UR9Mu2WTuvl8qn50be+mbwdJO5wE32jewxdYrVVmj19+PkaEeAwGTc
|
||||
vwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
publicKeyType TYPE_RAW_PUBLIC_KEY
|
||||
```
|
||||
|
||||
### Remove the resources
|
||||
|
||||
Deleting the Claim removes the managed resources and associated keys from Vault.
|
||||
|
||||
```shell
|
||||
kubectl delete claim my-ess
|
||||
```
|
||||
|
||||
<!-- named links -->
|
||||
|
||||
[Vault]: https://www.vaultproject.io/
|
||||
[External Secret Store]: https://github.com/crossplane/crossplane/blob/master/design/design-doc-external-secret-stores.md
|
||||
[this issue]: https://github.com/crossplane/crossplane/issues/2985
|
||||
[Kubernetes Auth Method]: https://www.vaultproject.io/docs/auth/kubernetes
|
||||
[Unseal]: https://www.vaultproject.io/docs/concepts/seal
|
||||
[Vault KV Secrets Engine]: https://developer.hashicorp.com/vault/docs/secrets/kv
|
||||
[Vault Agent Sidecar Injection]: https://www.vaultproject.io/docs/platform/k8s/injector
|
||||
[ESS Plugin Vault]: https://github.com/crossplane-contrib/ess-plugin-vault
|
|
@ -0,0 +1,506 @@
|
|||
---
|
||||
title: Vault Credential Injection
|
||||
weight: 230
|
||||
---
|
||||
|
||||
|
||||
> This guide is adapted from the [Vault on Minikube] and [Vault Kubernetes
|
||||
> Sidecar] guides.
|
||||
|
||||
Most Crossplane providers support supplying credentials from at least the
|
||||
following sources:
|
||||
- Kubernetes Secret
|
||||
- Environment Variable
|
||||
- Filesystem
|
||||
|
||||
A provider may optionally support additional credentials sources, but the common
|
||||
sources cover a wide variety of use cases. One specific use case that is popular
|
||||
among organizations that use [Vault] for secrets management is using a sidecar
|
||||
to inject credentials into the filesystem. This guide will demonstrate how to
|
||||
use the [Vault Kubernetes Sidecar] to provide credentials for [provider-gcp]
|
||||
and [provider-aws].
|
||||
|
||||
> Note: in this guide we will copy GCP credentials and AWS access keys
|
||||
> into Vault's KV secrets engine. This is a simple generic approach to
|
||||
> managing secrets with Vault, but is not as robust as using Vault's
|
||||
> dedicated cloud provider secrets engines for [AWS], [Azure], and [GCP].
|
||||
|
||||
## Setup
|
||||
|
||||
> Note: this guide walks through setting up Vault running in the same cluster as
|
||||
> Crossplane. You may also choose to use an existing Vault instance that runs
|
||||
> outside the cluster but has Kubernetes authentication enabled.
|
||||
|
||||
Before getting started, you must ensure that you have installed Crossplane and
|
||||
Vault and that they are running in your cluster.
|
||||
|
||||
1. Install Crossplane
|
||||
|
||||
```console
|
||||
kubectl create namespace crossplane-system
|
||||
|
||||
helm repo add crossplane-stable https://charts.crossplane.io/stable
|
||||
helm repo update
|
||||
|
||||
helm install crossplane --namespace crossplane-system crossplane-stable/crossplane
|
||||
```
|
||||
|
||||
2. Install Vault Helm Chart
|
||||
|
||||
```console
|
||||
helm repo add hashicorp https://helm.releases.hashicorp.com
|
||||
helm install vault hashicorp/vault
|
||||
```
|
||||
|
||||
3. Unseal Vault Instance
|
||||
|
||||
In order for Vault to access encrypted data from physical storage, it must be
|
||||
[unsealed].
|
||||
|
||||
```console
|
||||
kubectl exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json
|
||||
VAULT_UNSEAL_KEY=$(cat cluster-keys.json | jq -r ".unseal_keys_b64[]")
|
||||
kubectl exec vault-0 -- vault operator unseal $VAULT_UNSEAL_KEY
|
||||
```
|
||||
|
||||
4. Enable Kubernetes Authentication Method
|
||||
|
||||
In order for Vault to be able to authenticate requests based on Kubernetes
|
||||
service accounts, the [Kubernetes authentication backend] must be enabled. This
|
||||
requires logging in to Vault and configuring it with a service account token,
|
||||
API server address, and certificate. Because we are running Vault in Kubernetes,
|
||||
these values are already available via the container filesystem and environment
|
||||
variables.
|
||||
|
||||
```console
|
||||
cat cluster-keys.json | jq -r ".root_token" # get root token
|
||||
|
||||
kubectl exec -it vault-0 -- /bin/sh
|
||||
vault login # use root token from above
|
||||
vault auth enable kubernetes
|
||||
|
||||
vault write auth/kubernetes/config \
|
||||
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
|
||||
kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
|
||||
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
```
|
||||
|
||||
5. Exit Vault Container
|
||||
|
||||
The next steps will be executed in your local environment.
|
||||
|
||||
```console
|
||||
exit
|
||||
```
|
||||
|
||||
{{< tabs >}}
|
||||
{{< tab "GCP" >}}
|
||||
|
||||
## Create GCP Service Account
|
||||
|
||||
In order to provision infrastructure on GCP, you will need to create a service
|
||||
account with appropriate permissions. In this guide we will only provision a
|
||||
CloudSQL instance, so the service account will be bound to the `cloudsql.admin`
|
||||
role. The following steps will setup a GCP service account, give it the
|
||||
necessary permissions for Crossplane to be able to manage CloudSQL instances,
|
||||
and emit the service account credentials in a JSON file.
|
||||
|
||||
```console
|
||||
# replace this with your own gcp project id and the name of the service account
|
||||
# that will be created.
|
||||
PROJECT_ID=my-project
|
||||
NEW_SA_NAME=test-service-account-name
|
||||
|
||||
# create service account
|
||||
SA="${NEW_SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
|
||||
gcloud iam service-accounts create $NEW_SA_NAME --project $PROJECT_ID
|
||||
|
||||
# enable cloud API
|
||||
SERVICE="sqladmin.googleapis.com"
|
||||
gcloud services enable $SERVICE --project $PROJECT_ID
|
||||
|
||||
# grant access to cloud API
|
||||
ROLE="roles/cloudsql.admin"
|
||||
gcloud projects add-iam-policy-binding --role="$ROLE" $PROJECT_ID --member "serviceAccount:$SA"
|
||||
|
||||
# create service account keyfile
|
||||
gcloud iam service-accounts keys create creds.json --project $PROJECT_ID --iam-account $SA
|
||||
```
|
||||
|
||||
You should now have valid service account credentials in `creds.json`.
|
||||
|
||||
## Store Credentials in Vault
|
||||
|
||||
After setting up Vault, you will need to store your credentials in the [kv
|
||||
secrets engine].
|
||||
|
||||
> Note: the steps below involve copying credentials into the container
|
||||
> filesystem before storing them in Vault. You may also choose to use Vault's
|
||||
> HTTP API or UI by port-forwarding the container to your local environment
|
||||
> (`kubectl port-forward vault-0 8200:8200`).
|
||||
|
||||
1. Copy Credentials File into Vault Container
|
||||
|
||||
Copy your credentials into the container filesystem so that your can store them
|
||||
in Vault.
|
||||
|
||||
```console
|
||||
kubectl cp creds.json vault-0:/tmp/creds.json
|
||||
```
|
||||
|
||||
2. Enable KV Secrets Engine
|
||||
|
||||
Secrets engines must be enabled before they can be used. Enable the `kv-v2`
|
||||
secrets engine at the `secret` path.
|
||||
|
||||
```console
|
||||
kubectl exec -it vault-0 -- /bin/sh
|
||||
|
||||
vault secrets enable -path=secret kv-v2
|
||||
```
|
||||
|
||||
3. Store GCP Credentials in KV Engine
|
||||
|
||||
The path of your GCP credentials is how the secret will be referenced when
|
||||
injecting it into the `provider-gcp` controller `Pod`.
|
||||
|
||||
```console
|
||||
vault kv put secret/provider-creds/gcp-default @tmp/creds.json
|
||||
```
|
||||
|
||||
4. Clean Up Credentials File
|
||||
|
||||
You no longer need our GCP credentials file in the container filesystem, so go
|
||||
ahead and clean it up.
|
||||
|
||||
```console
|
||||
rm tmp/creds.json
|
||||
```
|
||||
|
||||
{{< /tab >}}
|
||||
{{< tab "AWS" >}}
|
||||
|
||||
## Create AWS IAM User
|
||||
|
||||
In order to provision infrastructure on AWS, you will need to use an existing or create a new IAM
|
||||
user with appropriate permissions. The following steps will create an AWS IAM user and give it the necessary
|
||||
permissions.
|
||||
|
||||
> Note: if you have an existing IAM user with appropriate permissions, you can skip this step but you will
|
||||
> still need to provide the values for the `ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables.
|
||||
|
||||
```console
|
||||
# create a new IAM user
|
||||
IAM_USER=test-user
|
||||
aws iam create-user --user-name $IAM_USER
|
||||
|
||||
# grant the IAM user the necessary permissions
|
||||
aws iam attach-user-policy --user-name $IAM_USER --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
|
||||
|
||||
# create a new IAM access key for the user
|
||||
aws iam create-access-key --user-name $IAM_USER > creds.json
|
||||
# assign the access key values to environment variables
|
||||
ACCESS_KEY_ID=$(jq -r .AccessKey.AccessKeyId creds.json)
|
||||
AWS_SECRET_ACCESS_KEY=$(jq -r .AccessKey.SecretAccessKey creds.json)
|
||||
```
|
||||
|
||||
## Store Credentials in Vault
|
||||
|
||||
After setting up Vault, you will need to store your credentials in the [kv
|
||||
secrets engine].
|
||||
|
||||
1. Enable KV Secrets Engine
|
||||
|
||||
Secrets engines must be enabled before they can be used. Enable the `kv-v2`
|
||||
secrets engine at the `secret` path.
|
||||
|
||||
```console
|
||||
kubectl exec -it vault-0 -- env \
|
||||
ACCESS_KEY_ID=${ACCESS_KEY_ID} \
|
||||
AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
|
||||
/bin/sh
|
||||
|
||||
vault secrets enable -path=secret kv-v2
|
||||
```
|
||||
|
||||
2. Store AWS Credentials in KV Engine
|
||||
|
||||
The path of your AWS credentials is how the secret will be referenced when
|
||||
injecting it into the `provider-aws` controller `Pod`.
|
||||
|
||||
```
|
||||
vault kv put secret/provider-creds/aws-default access_key="$ACCESS_KEY_ID" secret_key="$AWS_SECRET_ACCESS_KEY"
|
||||
```
|
||||
|
||||
{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
## Create a Vault Policy for Reading Provider Credentials
|
||||
|
||||
In order for our controllers to have the Vault sidecar inject the credentials
|
||||
into their filesystem, you must associate the `Pod` with a [policy]. This policy
|
||||
will allow for reading and listing all secrets on the `provider-creds` path in
|
||||
the `kv-v2` secrets engine.
|
||||
|
||||
```console
|
||||
vault policy write provider-creds - <<EOF
|
||||
path "secret/data/provider-creds/*" {
|
||||
capabilities = ["read", "list"]
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
## Create a Role for Crossplane Provider Pods
|
||||
|
||||
1. Create Role
|
||||
|
||||
The last step is to create a role that is bound to the policy you created and
|
||||
associate it with a group of Kubernetes service accounts. This role can be
|
||||
assumed by any (`*`) service account in the `crossplane-system` namespace.
|
||||
|
||||
```console
|
||||
vault write auth/kubernetes/role/crossplane-providers \
|
||||
bound_service_account_names="*" \
|
||||
bound_service_account_namespaces=crossplane-system \
|
||||
policies=provider-creds \
|
||||
ttl=24h
|
||||
```
|
||||
|
||||
2. Exit Vault Container
|
||||
|
||||
The next steps will be executed in your local environment.
|
||||
|
||||
```console
|
||||
exit
|
||||
```
|
||||
|
||||
{{< tabs >}}
|
||||
{{< tab "GCP" >}}
|
||||
|
||||
## Install provider-gcp
|
||||
|
||||
You are now ready to install `provider-gcp`. Crossplane provides a
|
||||
`ControllerConfig` type that allows you to customize the deployment of a
|
||||
provider's controller `Pod`. A `ControllerConfig` can be created and referenced
|
||||
by any number of `Provider` objects that wish to use its configuration. In the
|
||||
example below, the `Pod` annotations indicate to the Vault mutating webhook that
|
||||
we want for the secret stored at `secret/provider-creds/gcp-default` to be
|
||||
injected into the container filesystem by assuming role `crossplane-providers`.
|
||||
There is also so template formatting added to make sure the secret data is
|
||||
presented in a form that `provider-gcp` is expecting.
|
||||
|
||||
{% raw %}
|
||||
```console
|
||||
echo "apiVersion: pkg.crossplane.io/v1alpha1
|
||||
kind: ControllerConfig
|
||||
metadata:
|
||||
name: vault-config
|
||||
spec:
|
||||
metadata:
|
||||
annotations:
|
||||
vault.hashicorp.com/agent-inject: \"true\"
|
||||
vault.hashicorp.com/role: "crossplane-providers"
|
||||
vault.hashicorp.com/agent-inject-secret-creds.txt: "secret/provider-creds/gcp-default"
|
||||
vault.hashicorp.com/agent-inject-template-creds.txt: |
|
||||
{{- with secret \"secret/provider-creds/gcp-default\" -}}
|
||||
{{ .Data.data | toJSON }}
|
||||
{{- end -}}
|
||||
---
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: provider-gcp
|
||||
spec:
|
||||
package: xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.22.0
|
||||
controllerConfigRef:
|
||||
name: vault-config" | kubectl apply -f -
|
||||
```
|
||||
{% endraw %}
|
||||
|
||||
## Configure provider-gcp
|
||||
|
||||
One `provider-gcp` is installed and running, you will want to create a
|
||||
`ProviderConfig` that specifies the credentials in the filesystem that should be
|
||||
used to provision managed resources that reference this `ProviderConfig`.
|
||||
Because the name of this `ProviderConfig` is `default` it will be used by any
|
||||
managed resources that do not explicitly reference a `ProviderConfig`.
|
||||
|
||||
> Note: make sure that the `PROJECT_ID` environment variable that was defined
|
||||
> earlier is still set correctly.
|
||||
|
||||
```console
|
||||
echo "apiVersion: gcp.crossplane.io/v1beta1
|
||||
kind: ProviderConfig
|
||||
metadata:
|
||||
name: default
|
||||
spec:
|
||||
projectID: ${PROJECT_ID}
|
||||
credentials:
|
||||
source: Filesystem
|
||||
fs:
|
||||
path: /vault/secrets/creds.txt" | kubectl apply -f -
|
||||
```
|
||||
|
||||
To verify that the GCP credentials are being injected into the container run the
|
||||
following command:
|
||||
|
||||
```console
|
||||
PROVIDER_CONTROLLER_POD=$(kubectl -n crossplane-system get pod -l pkg.crossplane.io/provider=provider-gcp -o name --no-headers=true)
|
||||
kubectl -n crossplane-system exec -it $PROVIDER_CONTROLLER_POD -c provider-gcp -- cat /vault/secrets/creds.txt
|
||||
```
|
||||
|
||||
## Provision Infrastructure
|
||||
|
||||
The final step is to actually provision a `CloudSQLInstance`. Creating the
|
||||
object below will result in the creation of a Cloud SQL Postgres database on
|
||||
GCP.
|
||||
|
||||
```console
|
||||
echo "apiVersion: database.gcp.crossplane.io/v1beta1
|
||||
kind: CloudSQLInstance
|
||||
metadata:
|
||||
name: postgres-vault-demo
|
||||
spec:
|
||||
forProvider:
|
||||
databaseVersion: POSTGRES_12
|
||||
region: us-central1
|
||||
settings:
|
||||
tier: db-custom-1-3840
|
||||
dataDiskType: PD_SSD
|
||||
dataDiskSizeGb: 10
|
||||
writeConnectionSecretToRef:
|
||||
namespace: crossplane-system
|
||||
name: cloudsqlpostgresql-conn" | kubectl apply -f -
|
||||
```
|
||||
|
||||
You can monitor the progress of the database provisioning with the following
|
||||
command:
|
||||
|
||||
```console
|
||||
kubectl get cloudsqlinstance -w
|
||||
```
|
||||
|
||||
{{< /tab >}}
|
||||
{{< tab "AWS" >}}
|
||||
|
||||
## Install provider-aws
|
||||
|
||||
You are now ready to install `provider-aws`. Crossplane provides a
|
||||
`ControllerConfig` type that allows you to customize the deployment of a
|
||||
provider's controller `Pod`. A `ControllerConfig` can be created and referenced
|
||||
by any number of `Provider` objects that wish to use its configuration. In the
|
||||
example below, the `Pod` annotations indicate to the Vault mutating webhook that
|
||||
we want for the secret stored at `secret/provider-creds/aws-default` to be
|
||||
injected into the container filesystem by assuming role `crossplane-providers`.
|
||||
There is also some template formatting added to make sure the secret data is
|
||||
presented in a form that `provider-aws` is expecting.
|
||||
|
||||
{% raw %}
|
||||
```console
|
||||
echo "apiVersion: pkg.crossplane.io/v1alpha1
|
||||
kind: ControllerConfig
|
||||
metadata:
|
||||
name: aws-vault-config
|
||||
spec:
|
||||
args:
|
||||
- --debug
|
||||
metadata:
|
||||
annotations:
|
||||
vault.hashicorp.com/agent-inject: \"true\"
|
||||
vault.hashicorp.com/role: \"crossplane-providers\"
|
||||
vault.hashicorp.com/agent-inject-secret-creds.txt: \"secret/provider-creds/aws-default\"
|
||||
vault.hashicorp.com/agent-inject-template-creds.txt: |
|
||||
{{- with secret \"secret/provider-creds/aws-default\" -}}
|
||||
[default]
|
||||
aws_access_key_id="{{ .Data.data.access_key }}"
|
||||
aws_secret_access_key="{{ .Data.data.secret_key }}"
|
||||
{{- end -}}
|
||||
---
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: provider-aws
|
||||
spec:
|
||||
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0
|
||||
controllerConfigRef:
|
||||
name: aws-vault-config" | kubectl apply -f -
|
||||
```
|
||||
{% endraw %}
|
||||
|
||||
## Configure provider-aws
|
||||
|
||||
Once `provider-aws` is installed and running, you will want to create a
|
||||
`ProviderConfig` that specifies the credentials in the filesystem that should be
|
||||
used to provision managed resources that reference this `ProviderConfig`.
|
||||
Because the name of this `ProviderConfig` is `default` it will be used by any
|
||||
managed resources that do not explicitly reference a `ProviderConfig`.
|
||||
|
||||
```console
|
||||
echo "apiVersion: aws.crossplane.io/v1beta1
|
||||
kind: ProviderConfig
|
||||
metadata:
|
||||
name: default
|
||||
spec:
|
||||
credentials:
|
||||
source: Filesystem
|
||||
fs:
|
||||
path: /vault/secrets/creds.txt" | kubectl apply -f -
|
||||
```
|
||||
|
||||
To verify that the AWS credentials are being injected into the container run the
|
||||
following command:
|
||||
|
||||
```console
|
||||
PROVIDER_CONTROLLER_POD=$(kubectl -n crossplane-system get pod -l pkg.crossplane.io/provider=provider-aws -o name --no-headers=true)
|
||||
kubectl -n crossplane-system exec -it $PROVIDER_CONTROLLER_POD -c provider-aws -- cat /vault/secrets/creds.txt
|
||||
```
|
||||
|
||||
## Provision Infrastructure
|
||||
|
||||
The final step is to actually provision a `Bucket`. Creating the
|
||||
object below will result in the creation of a S3 bucket on AWS.
|
||||
|
||||
```console
|
||||
echo "apiVersion: s3.aws.crossplane.io/v1beta1
|
||||
kind: Bucket
|
||||
metadata:
|
||||
name: s3-vault-demo
|
||||
spec:
|
||||
forProvider:
|
||||
acl: private
|
||||
locationConstraint: us-east-1
|
||||
publicAccessBlockConfiguration:
|
||||
blockPublicPolicy: true
|
||||
tagging:
|
||||
tagSet:
|
||||
- key: Name
|
||||
value: s3-vault-demo
|
||||
providerConfigRef:
|
||||
name: default" | kubectl apply -f -
|
||||
```
|
||||
|
||||
You can monitor the progress of the bucket provisioning with the following
|
||||
command:
|
||||
|
||||
```console
|
||||
kubectl get bucket -w
|
||||
```
|
||||
|
||||
{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
<!-- named links -->
|
||||
|
||||
[Vault on Minikube]: https://learn.hashicorp.com/tutorials/vault/kubernetes-minikube
|
||||
[Vault Kubernetes Sidecar]: https://learn.hashicorp.com/tutorials/vault/kubernetes-sidecar
|
||||
[Vault]: https://www.vaultproject.io/
|
||||
[Vault Kubernetes Sidecar]: https://www.vaultproject.io/docs/platform/k8s/injector
|
||||
[provider-gcp]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-gcp
|
||||
[provider-aws]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws
|
||||
[AWS]: https://www.vaultproject.io/docs/secrets/aws
|
||||
[Azure]: https://www.vaultproject.io/docs/secrets/azure
|
||||
[GCP]: https://www.vaultproject.io/docs/secrets/gcp
|
||||
[unsealed]: https://www.vaultproject.io/docs/concepts/seal
|
||||
[Kubernetes authentication backend]: https://www.vaultproject.io/docs/auth/kubernetes
|
||||
[kv secrets engine]: https://www.vaultproject.io/docs/secrets/kv/kv-v2
|
||||
[policy]: https://www.vaultproject.io/docs/concepts/policies
|
|
@ -0,0 +1,838 @@
|
|||
---
|
||||
title: Write a Composition Function in Go
|
||||
state: beta
|
||||
alphaVersion: "1.11"
|
||||
betaVersion: "1.14"
|
||||
weight: 80
|
||||
description: "Composition functions allow you to template resources using Go"
|
||||
---
|
||||
|
||||
Composition functions (or just functions, for short) are custom programs that
|
||||
template Crossplane resources. Crossplane calls composition functions to
|
||||
determine what resources it should create when you create a composite resource
|
||||
(XR). Read the
|
||||
[concepts]{{<ref "../concepts/composition-functions" >}}
|
||||
page to learn more about composition functions.
|
||||
|
||||
You can write a function to template resources using a general purpose
|
||||
programming language. Using a general purpose programming language allows a
|
||||
function to use advanced logic to template resources, like loops and
|
||||
conditionals. This guide explains how to write a composition function in
|
||||
[Go](https://go.dev).
|
||||
|
||||
{{< hint "important" >}}
|
||||
It helps to be familiar with
|
||||
[how composition functions work]{{<ref "../concepts/composition-functions#how-composition-functions-work" >}}
|
||||
before following this guide.
|
||||
{{< /hint >}}
|
||||
|
||||
## Understand the steps
|
||||
|
||||
This guide covers writing a composition function for an
|
||||
{{<hover label="xr" line="2">}}XBuckets{{</hover>}} composite resource (XR).
|
||||
|
||||
```yaml {label="xr"}
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBuckets
|
||||
metadata:
|
||||
name: example-buckets
|
||||
spec:
|
||||
region: us-east-2
|
||||
names:
|
||||
- crossplane-functions-example-a
|
||||
- crossplane-functions-example-b
|
||||
- crossplane-functions-example-c
|
||||
```
|
||||
|
||||
<!-- vale gitlab.FutureTense = NO -->
|
||||
<!--
|
||||
This section is setting the stage for future sections. It doesn't make sense to
|
||||
refer to the function in the present tense, because it doesn't exist yet.
|
||||
-->
|
||||
An `XBuckets` XR has a region and an array of bucket names. The function will
|
||||
create an Amazon Web Services (AWS) S3 bucket for each entry in the names array.
|
||||
<!-- vale gitlab.FutureTense = YES -->
|
||||
|
||||
To write a function in Go:
|
||||
|
||||
1. [Install the tools you need to write the function](#install-the-tools-you-need-to-write-the-function)
|
||||
1. [Initialize the function from a template](#initialize-the-function-from-a-template)
|
||||
1. [Edit the template to add the function's logic](#edit-the-template-to-add-the-functions-logic)
|
||||
1. [Test the function end-to-end](#test-the-function-end-to-end)
|
||||
1. [Build and push the function to a package repository](#build-and-push-the-function-to-a-package-registry)
|
||||
|
||||
This guide covers each of these steps in detail.
|
||||
|
||||
## Install the tools you need to write the function
|
||||
|
||||
To write a function in Go you need:
|
||||
|
||||
* [Go](https://go.dev/dl/) v1.21 or newer. The guide uses Go v1.21.
|
||||
* [Docker Engine](https://docs.docker.com/engine/). This guide uses Engine v24.
|
||||
* The [Crossplane CLI]({{<ref "../cli" >}}) v1.14 or newer. This guide uses Crossplane
|
||||
CLI v1.14.
|
||||
|
||||
{{<hint "note">}}
|
||||
You don't need access to a Kubernetes cluster or a Crossplane control plane to
|
||||
build or test a composition function.
|
||||
{{</hint>}}
|
||||
|
||||
## Initialize the function from a template
|
||||
|
||||
Use the `crossplane beta xpkg init` command to initialize a new function. When
|
||||
you run this command it initializes your function using
|
||||
[a GitHub repository](https://github.com/crossplane/function-template-go)
|
||||
as a template.
|
||||
|
||||
```shell {copy-lines=1}
|
||||
crossplane beta xpkg init function-xbuckets function-template-go -d function-xbuckets
|
||||
Initialized package "function-xbuckets" in directory "/home/negz/control/negz/function-xbuckets" from https://github.com/crossplane/function-template-go/tree/91a1a5eed21964ff98966d72cc6db6f089ad63f4 (main)
|
||||
```
|
||||
|
||||
The `crossplane beta init xpkg` command creates a directory named
|
||||
`function-xbuckets`. When you run the command the new directory should look like
|
||||
this:
|
||||
|
||||
```shell {copy-lines=1}
|
||||
ls function-xbuckets
|
||||
Dockerfile fn.go fn_test.go go.mod go.sum input/ LICENSE main.go package/ README.md renovate.json
|
||||
```
|
||||
|
||||
The `fn.go` file is where you add the function's code. It's useful to know about
|
||||
some other files in the template:
|
||||
|
||||
* `main.go` runs the function. You don't need to edit `main.go`.
|
||||
* `Dockerfile` builds the function runtime. You don't need to edit `Dockerfile`.
|
||||
* The `input` directory defines the function's input type.
|
||||
* The `package` directory contains metadata used to build the function package.
|
||||
|
||||
{{<hint "tip">}}
|
||||
<!-- vale gitlab.FutureTense = NO -->
|
||||
<!--
|
||||
This tip talks about future plans for Crossplane.
|
||||
-->
|
||||
In v1.14 of the Crossplane CLI `crossplane beta xpkg init` just clones a
|
||||
template GitHub repository. A future CLI release will automate tasks like
|
||||
replacing the template name with the new function's name. See Crossplane issue
|
||||
[#4941](https://github.com/crossplane/crossplane/issues/4941) for details.
|
||||
<!-- vale gitlab.FutureTense = YES -->
|
||||
{{</hint>}}
|
||||
|
||||
You must make some changes before you start adding code:
|
||||
|
||||
* Edit `package/crossplane.yaml` to change the package's name.
|
||||
* Edit `go.mod` to change the Go module's name.
|
||||
|
||||
Name your package `function-xbuckets`.
|
||||
|
||||
The name of your module depends on where you want to keep your function code. If
|
||||
you push Go code to GitHub, you can use your GitHub username. For example
|
||||
`module github.com/negz/function-xbuckets`.
|
||||
|
||||
The function in this guide doesn't use an input type. For this function you
|
||||
should delete the `input` and `package/input` directories.
|
||||
|
||||
The `input` directory defines a Go struct that a function can use to take input,
|
||||
using the `input` field from a Composition. The
|
||||
[composition functions]{{<ref "../concepts/composition-functions" >}}
|
||||
documentation explains how to pass an input to a composition function.
|
||||
|
||||
The `package/input` directory contains an OpenAPI schema generated from the
|
||||
structs in the `input` directory.
|
||||
|
||||
{{<hint "tip">}}
|
||||
If you're writing a function that uses an input, edit the input to meet your
|
||||
function's requirements.
|
||||
|
||||
Change the input's kind and API group. Don't use `Input` and
|
||||
`template.fn.crossplane.io`. Instead use something meaningful to your function.
|
||||
|
||||
When you edit files under the `input` directory you must update some generated
|
||||
files by running `go generate`. See `input/generate.go` for details.
|
||||
|
||||
```shell
|
||||
go generate ./...
|
||||
```
|
||||
{{</hint>}}
|
||||
|
||||
## Edit the template to add the function's logic
|
||||
|
||||
You add your function's logic to the
|
||||
{{<hover label="hello-world" line="1">}}RunFunction{{</hover>}}
|
||||
method in `fn.go`. When you first open the file it contains a "hello world"
|
||||
function.
|
||||
|
||||
```go {label="hello-world"}
|
||||
func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) {
|
||||
f.log.Info("Running Function", "tag", req.GetMeta().GetTag())
|
||||
|
||||
rsp := response.To(req, response.DefaultTTL)
|
||||
|
||||
in := &v1beta1.Input{}
|
||||
if err := request.GetInput(req, in); err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot get Function input from %T", req))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
response.Normalf(rsp, "I was run with input %q", in.Example)
|
||||
return rsp, nil
|
||||
}
|
||||
```
|
||||
|
||||
All Go composition functions have a `RunFunction` method. Crossplane passes
|
||||
everything the function needs to run in a
|
||||
{{<hover label="hello-world" line="1">}}RunFunctionRequest{{</hover>}} struct.
|
||||
|
||||
The function tells Crossplane what resources it should compose by returning a
|
||||
{{<hover label="hello-world" line="13">}}RunFunctionResponse{{</hover>}} struct.
|
||||
|
||||
{{<hint "tip">}}
|
||||
Crossplane generates the `RunFunctionRequest` and `RunFunctionResponse` structs
|
||||
using [Protocol Buffers](http://protobuf.dev). You can find detailed schemas for
|
||||
`RunFunctionRequest` and `RunFunctionResponse` in the
|
||||
[Buf Schema Registry](https://buf.build/crossplane/crossplane/docs/main:apiextensions.fn.proto.v1beta1).
|
||||
{{</hint>}}
|
||||
|
||||
Edit the `RunFunction` method to replace it with this code.
|
||||
|
||||
```go {hl_lines="4-56"}
|
||||
func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) {
|
||||
rsp := response.To(req, response.DefaultTTL)
|
||||
|
||||
xr, err := request.GetObservedCompositeResource(req)
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot get observed composite resource from %T", req))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
region, err := xr.Resource.GetString("spec.region")
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot read spec.region field of %s", xr.Resource.GetKind()))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
names, err := xr.Resource.GetStringArray("spec.names")
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot read spec.names field of %s", xr.Resource.GetKind()))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
desired, err := request.GetDesiredComposedResources(req)
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot get desired resources from %T", req))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
_ = v1beta1.AddToScheme(composed.Scheme)
|
||||
|
||||
for _, name := range names {
|
||||
b := &v1beta1.Bucket{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"crossplane.io/external-name": name,
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.BucketSpec{
|
||||
ForProvider: v1beta1.BucketParameters{
|
||||
Region: ptr.To[string](region),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cd, err := composed.From(b)
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot convert %T to %T", b, &composed.Unstructured{}))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
desired[resource.Name("xbuckets-"+name)] = &resource.DesiredComposed{Resource: cd}
|
||||
}
|
||||
|
||||
if err := response.SetDesiredComposedResources(rsp, desired); err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot set desired composed resources in %T", rsp))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
return rsp, nil
|
||||
}
|
||||
```
|
||||
|
||||
Expand the below block to view the full `fn.go`, including imports and
|
||||
commentary explaining the function's logic.
|
||||
|
||||
{{<expand "The full fn.go file" >}}
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
"github.com/upbound/provider-aws/apis/s3/v1beta1"
|
||||
|
||||
"github.com/crossplane/function-sdk-go/errors"
|
||||
"github.com/crossplane/function-sdk-go/logging"
|
||||
fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1"
|
||||
"github.com/crossplane/function-sdk-go/request"
|
||||
"github.com/crossplane/function-sdk-go/resource"
|
||||
"github.com/crossplane/function-sdk-go/resource/composed"
|
||||
"github.com/crossplane/function-sdk-go/response"
|
||||
)
|
||||
|
||||
// Function returns whatever response you ask it to.
|
||||
type Function struct {
|
||||
fnv1beta1.UnimplementedFunctionRunnerServiceServer
|
||||
|
||||
log logging.Logger
|
||||
}
|
||||
|
||||
// RunFunction observes an XBuckets composite resource (XR). It adds an S3
|
||||
// bucket to the desired state for every entry in the XR's spec.names array.
|
||||
func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequest) (*fnv1beta1.RunFunctionResponse, error) {
|
||||
f.log.Info("Running Function", "tag", req.GetMeta().GetTag())
|
||||
|
||||
// Create a response to the request. This copies the desired state and
|
||||
// pipeline context from the request to the response.
|
||||
rsp := response.To(req, response.DefaultTTL)
|
||||
|
||||
// Read the observed XR from the request. Most functions use the observed XR
|
||||
// to add desired managed resources.
|
||||
xr, err := request.GetObservedCompositeResource(req)
|
||||
if err != nil {
|
||||
// If the function can't read the XR, the request is malformed. This
|
||||
// should never happen. The function returns a fatal result. This tells
|
||||
// Crossplane to stop running functions and return an error.
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot get observed composite resource from %T", req))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
// Create an updated logger with useful information about the XR.
|
||||
log := f.log.WithValues(
|
||||
"xr-version", xr.Resource.GetAPIVersion(),
|
||||
"xr-kind", xr.Resource.GetKind(),
|
||||
"xr-name", xr.Resource.GetName(),
|
||||
)
|
||||
|
||||
// Get the region from the XR. The XR has getter methods like GetString,
|
||||
// GetBool, etc. You can use them to get values by their field path.
|
||||
region, err := xr.Resource.GetString("spec.region")
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot read spec.region field of %s", xr.Resource.GetKind()))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
// Get the array of bucket names from the XR.
|
||||
names, err := xr.Resource.GetStringArray("spec.names")
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot read spec.names field of %s", xr.Resource.GetKind()))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
// Get all desired composed resources from the request. The function will
|
||||
// update this map of resources, then save it. This get, update, set pattern
|
||||
// ensures the function keeps any resources added by other functions.
|
||||
desired, err := request.GetDesiredComposedResources(req)
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot get desired resources from %T", req))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
// Add v1beta1 types (including Bucket) to the composed resource scheme.
|
||||
// composed.From uses this to automatically set apiVersion and kind.
|
||||
_ = v1beta1.AddToScheme(composed.Scheme)
|
||||
|
||||
// Add a desired S3 bucket for each name.
|
||||
for _, name := range names {
|
||||
// One advantage of writing a function in Go is strong typing. The
|
||||
// function can import and use managed resource types from the provider.
|
||||
b := &v1beta1.Bucket{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
// Set the external name annotation to the desired bucket name.
|
||||
// This controls what the bucket will be named in AWS.
|
||||
Annotations: map[string]string{
|
||||
"crossplane.io/external-name": name,
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.BucketSpec{
|
||||
ForProvider: v1beta1.BucketParameters{
|
||||
// Set the bucket's region to the value read from the XR.
|
||||
Region: ptr.To[string](region),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Convert the bucket to the unstructured resource data format the SDK
|
||||
// uses to store desired composed resources.
|
||||
cd, err := composed.From(b)
|
||||
if err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot convert %T to %T", b, &composed.Unstructured{}))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
// Add the bucket to the map of desired composed resources. It's
|
||||
// important that the function adds the same bucket every time it's
|
||||
// called. It's also important that the bucket is added with the same
|
||||
// resource.Name every time it's called. The function prefixes the name
|
||||
// with "xbuckets-" to avoid collisions with any other composed
|
||||
// resources that might be in the desired resources map.
|
||||
desired[resource.Name("xbuckets-"+name)] = &resource.DesiredComposed{Resource: cd}
|
||||
}
|
||||
|
||||
// Finally, save the updated desired composed resources to the response.
|
||||
if err := response.SetDesiredComposedResources(rsp, desired); err != nil {
|
||||
response.Fatal(rsp, errors.Wrapf(err, "cannot set desired composed resources in %T", rsp))
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
// Log what the function did. This will only appear in the function's pod
|
||||
// logs. A function can use response.Normal and response.Warning to emit
|
||||
// Kubernetes events associated with the XR it's operating on.
|
||||
log.Info("Added desired buckets", "region", region, "count", len(names))
|
||||
|
||||
return rsp, nil
|
||||
}
|
||||
```
|
||||
{{</expand>}}
|
||||
|
||||
This code:
|
||||
|
||||
1. Gets the observed composite resource from the `RunFunctionRequest`.
|
||||
1. Gets the region and bucket names from the observed composite resource.
|
||||
1. Adds one desired S3 bucket for each bucket name.
|
||||
1. Returns the desired S3 buckets in a `RunFunctionResponse`.
|
||||
|
||||
The code uses the `v1beta1.Bucket` type from
|
||||
[Upbound's AWS S3 provider](https://github.com/upbound/provider-aws). One
|
||||
advantage of writing a function in Go is that you can compose resources using
|
||||
the same strongly typed structs Crossplane uses in its providers.
|
||||
|
||||
You must get the AWS Provider Go module to use this type:
|
||||
|
||||
```shell
|
||||
go get github.com/upbound/provider-aws@v0.43.0
|
||||
```
|
||||
|
||||
Crossplane provides a
|
||||
[software development kit](https://github.com/crossplane/function-sdk-go) (SDK)
|
||||
for writing composition functions in [Go](https://go.dev). This function uses
|
||||
utilities from the SDK. In particular the `request` and `response` packages make
|
||||
working with the `RunFunctionRequest` and `RunFunctionResponse` types easier.
|
||||
|
||||
{{<hint "tip">}}
|
||||
Read the
|
||||
[Go package documentation](https://pkg.go.dev/github.com/crossplane/function-sdk-go)
|
||||
for the SDK.
|
||||
{{</hint>}}
|
||||
|
||||
## Test the function end-to-end
|
||||
|
||||
Test your function by adding unit tests, and by using the `crossplane beta
|
||||
render` command.
|
||||
|
||||
Go has rich support for unit testing. When you initialize a function from the
|
||||
template it adds some unit tests to `fn_test.go`. These tests follow Go's
|
||||
[recommendations](https://github.com/golang/go/wiki/TestComments). They use only
|
||||
[`pkg/testing`](https://pkg.go.dev/testing) from the Go standard library and
|
||||
[`google/go-cmp`](https://pkg.go.dev/github.com/google/go-cmp/cmp).
|
||||
|
||||
To add test cases, update the `cases` map in `TestRunFunction`. Expand the below
|
||||
block to view the full `fn_test.go` file for the function.
|
||||
|
||||
{{<expand "The full fn_test.go file" >}}
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/logging"
|
||||
|
||||
fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1"
|
||||
"github.com/crossplane/function-sdk-go/resource"
|
||||
)
|
||||
|
||||
func TestRunFunction(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *fnv1beta1.RunFunctionRequest
|
||||
}
|
||||
type want struct {
|
||||
rsp *fnv1beta1.RunFunctionResponse
|
||||
err error
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
reason string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"AddTwoBuckets": {
|
||||
reason: "The Function should add two buckets to the desired composed resources",
|
||||
args: args{
|
||||
req: &fnv1beta1.RunFunctionRequest{
|
||||
Observed: &fnv1beta1.State{
|
||||
Composite: &fnv1beta1.Resource{
|
||||
// MustStructJSON is a handy way to provide mock
|
||||
// resources.
|
||||
Resource: resource.MustStructJSON(`{
|
||||
"apiVersion": "example.crossplane.io/v1alpha1",
|
||||
"kind": "XBuckets",
|
||||
"metadata": {
|
||||
"name": "test"
|
||||
},
|
||||
"spec": {
|
||||
"region": "us-east-2",
|
||||
"names": [
|
||||
"test-bucket-a",
|
||||
"test-bucket-b"
|
||||
]
|
||||
}
|
||||
}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
rsp: &fnv1beta1.RunFunctionResponse{
|
||||
Meta: &fnv1beta1.ResponseMeta{Ttl: durationpb.New(60 * time.Second)},
|
||||
Desired: &fnv1beta1.State{
|
||||
Resources: map[string]*fnv1beta1.Resource{
|
||||
"xbuckets-test-bucket-a": {Resource: resource.MustStructJSON(`{
|
||||
"apiVersion": "s3.aws.upbound.io/v1beta1",
|
||||
"kind": "Bucket",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"crossplane.io/external-name": "test-bucket-a"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"forProvider": {
|
||||
"region": "us-east-2"
|
||||
}
|
||||
}
|
||||
}`)},
|
||||
"xbuckets-test-bucket-b": {Resource: resource.MustStructJSON(`{
|
||||
"apiVersion": "s3.aws.upbound.io/v1beta1",
|
||||
"kind": "Bucket",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"crossplane.io/external-name": "test-bucket-b"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"forProvider": {
|
||||
"region": "us-east-2"
|
||||
}
|
||||
}
|
||||
}`)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
f := &Function{log: logging.NewNopLogger()}
|
||||
rsp, err := f.RunFunction(tc.args.ctx, tc.args.req)
|
||||
|
||||
if diff := cmp.Diff(tc.want.rsp, rsp, protocmp.Transform()); diff != "" {
|
||||
t.Errorf("%s\nf.RunFunction(...): -want rsp, +got rsp:\n%s", tc.reason, diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.want.err, err, cmpopts.EquateErrors()); diff != "" {
|
||||
t.Errorf("%s\nf.RunFunction(...): -want err, +got err:\n%s", tc.reason, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
{{</expand>}}
|
||||
|
||||
Run the unit tests using the `go test` command:
|
||||
|
||||
```shell
|
||||
go test -v -cover .
|
||||
=== RUN TestRunFunction
|
||||
=== RUN TestRunFunction/AddTwoBuckets
|
||||
--- PASS: TestRunFunction (0.00s)
|
||||
--- PASS: TestRunFunction/AddTwoBuckets (0.00s)
|
||||
PASS
|
||||
coverage: 52.6% of statements
|
||||
ok github.com/negz/function-xbuckets 0.016s coverage: 52.6% of statements
|
||||
```
|
||||
|
||||
You can preview the output of a Composition that uses this function using
|
||||
the Crossplane CLI. You don't need a Crossplane control plane to do this.
|
||||
|
||||
Create a directory under `function-xbuckets` named `example` and create
|
||||
Composite Resource, Composition and Function YAML files.
|
||||
|
||||
Expand the following block to see example files.
|
||||
|
||||
{{<expand "The xr.yaml, composition.yaml and function.yaml files">}}
|
||||
|
||||
You can recreate the output below using by running `crossplane beta render` with
|
||||
these files.
|
||||
|
||||
The `xr.yaml` file contains the composite resource to render:
|
||||
|
||||
```yaml
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBuckets
|
||||
metadata:
|
||||
name: example-buckets
|
||||
spec:
|
||||
region: us-east-2
|
||||
names:
|
||||
- crossplane-functions-example-a
|
||||
- crossplane-functions-example-b
|
||||
- crossplane-functions-example-c
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
The `composition.yaml` file contains the Composition to use to render the
|
||||
composite resource:
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: create-buckets
|
||||
spec:
|
||||
compositeTypeRef:
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBuckets
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: create-buckets
|
||||
functionRef:
|
||||
name: function-xbuckets
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
The `functions.yaml` file contains the Functions the Composition references in
|
||||
its pipeline steps:
|
||||
|
||||
```yaml
|
||||
apiVersion: pkg.crossplane.io/v1beta1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: function-xbuckets
|
||||
annotations:
|
||||
render.crossplane.io/runtime: Development
|
||||
spec:
|
||||
# The CLI ignores this package when using the Development runtime.
|
||||
# You can set it to any value.
|
||||
package: xpkg.upbound.io/negz/function-xbuckets:v0.1.0
|
||||
```
|
||||
{{</expand>}}
|
||||
|
||||
The Function in `functions.yaml` uses the
|
||||
{{<hover label="development" line="6">}}Development{{</hover>}}
|
||||
runtime. This tells `crossplane beta render` that your function is running
|
||||
locally. It connects to your locally running function instead of using Docker to
|
||||
pull and run the function.
|
||||
|
||||
```yaml {label="development"}
|
||||
apiVersion: pkg.crossplane.io/v1beta1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: function-xbuckets
|
||||
annotations:
|
||||
render.crossplane.io/runtime: Development
|
||||
```
|
||||
|
||||
Use `go run` to run your function locally.
|
||||
|
||||
```shell {label="run"}
|
||||
go run . --insecure --debug
|
||||
```
|
||||
|
||||
{{<hint "warning">}}
|
||||
The {{<hover label="run" line="1">}}insecure{{</hover>}} flag tells the function
|
||||
to run without encryption or authentication. Only use it during testing and
|
||||
development.
|
||||
{{</hint>}}
|
||||
|
||||
In a separate terminal, run `crossplane beta render`.
|
||||
|
||||
```shell
|
||||
crossplane beta render xr.yaml composition.yaml functions.yaml
|
||||
```
|
||||
|
||||
This command calls your function. In the terminal where your function is running
|
||||
you should now see log output:
|
||||
|
||||
```shell
|
||||
go run . --insecure --debug
|
||||
2023-10-31T16:17:32.158-0700 INFO function-xbuckets/fn.go:29 Running Function {"tag": ""}
|
||||
2023-10-31T16:17:32.159-0700 INFO function-xbuckets/fn.go:125 Added desired buckets {"xr-version": "example.crossplane.io/v1", "xr-kind": "XBuckets", "xr-name": "example-buckets", "region": "us-east-2", "count": 3}
|
||||
```
|
||||
|
||||
The `crossplane beta render` command prints the desired resources the function
|
||||
returns.
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBuckets
|
||||
metadata:
|
||||
name: example-buckets
|
||||
---
|
||||
apiVersion: s3.aws.upbound.io/v1beta1
|
||||
kind: Bucket
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-b
|
||||
crossplane.io/external-name: crossplane-functions-example-b
|
||||
generateName: example-buckets-
|
||||
labels:
|
||||
crossplane.io/composite: example-buckets
|
||||
ownerReferences:
|
||||
# Omitted for brevity
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-2
|
||||
---
|
||||
apiVersion: s3.aws.upbound.io/v1beta1
|
||||
kind: Bucket
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-c
|
||||
crossplane.io/external-name: crossplane-functions-example-c
|
||||
generateName: example-buckets-
|
||||
labels:
|
||||
crossplane.io/composite: example-buckets
|
||||
ownerReferences:
|
||||
# Omitted for brevity
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-2
|
||||
---
|
||||
apiVersion: s3.aws.upbound.io/v1beta1
|
||||
kind: Bucket
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-a
|
||||
crossplane.io/external-name: crossplane-functions-example-a
|
||||
generateName: example-buckets-
|
||||
labels:
|
||||
crossplane.io/composite: example-buckets
|
||||
ownerReferences:
|
||||
# Omitted for brevity
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-2
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
Read the composition functions documentation to learn more about
|
||||
[testing composition functions]({{< ref "../concepts/composition-functions#test-a-composition-that-uses-functions" >}}).
|
||||
{{</hint>}}
|
||||
|
||||
## Build and push the function to a package registry
|
||||
|
||||
You build a function in two stages. First you build the function's runtime. This
|
||||
is the Open Container Initiative (OCI) image Crossplane uses to run your
|
||||
function. You then embed that runtime in a package, and push it to a package
|
||||
registry. The Crossplane CLI uses `xpkg.upbound.io` as its default package
|
||||
registry.
|
||||
|
||||
A function supports a single platform, like `linux/amd64`, by default. You can
|
||||
support multiple platforms by building a runtime and package for each platform,
|
||||
then pushing all the packages to a single tag in the registry.
|
||||
|
||||
Pushing your function to a registry allows you to use your function in a
|
||||
Crossplane control plane. See the
|
||||
[composition functions documentation]{{<ref "../concepts/composition-functions" >}}.
|
||||
to learn how to use a function in a control plane.
|
||||
|
||||
Use Docker to build a runtime for each platform.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
docker build . --quiet --platform=linux/amd64 --tag runtime-amd64
|
||||
sha256:fdf40374cc6f0b46191499fbc1dbbb05ddb76aca854f69f2912e580cfe624b4b
|
||||
```
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
docker build . --quiet --platform=linux/arm64 --tag runtime-arm64
|
||||
sha256:cb015ceabf46d2a55ccaeebb11db5659a2fb5e93de36713364efcf6d699069af
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
You can use whatever tag you want. There's no need to push the runtime images to
|
||||
a registry. The tag is only used to tell `crossplane xpkg build` what runtime to
|
||||
embed.
|
||||
{{</hint>}}
|
||||
|
||||
Use the Crossplane CLI to build a package for each platform. Each package embeds
|
||||
a runtime image.
|
||||
|
||||
The {{<hover label="build" line="2">}}--package-root{{</hover>}} flag specifies
|
||||
the `package` directory, which contains `crossplane.yaml`. This includes
|
||||
metadata about the package.
|
||||
|
||||
The {{<hover label="build" line="3">}}--embed-runtime-image{{</hover>}} flag
|
||||
specifies the runtime image tag built using Docker.
|
||||
|
||||
The {{<hover label="build" line="4">}}--package-file{{</hover>}} flag specifies
|
||||
specifies where to write the package file to disk. Crossplane package files use
|
||||
the extension `.xpkg`.
|
||||
|
||||
```shell {label="build"}
|
||||
crossplane xpkg build \
|
||||
--package-root=package \
|
||||
--embed-runtime-image=runtime-amd64 \
|
||||
--package-file=function-amd64.xpkg
|
||||
```
|
||||
|
||||
```shell
|
||||
crossplane xpkg build \
|
||||
--package-root=package \
|
||||
--embed-runtime-image=runtime-arm64 \
|
||||
--package-file=function-arm64.xpkg
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
Crossplane packages are special OCI images. Read more about packages in the
|
||||
[packages documentation]({{< ref "../concepts/packages" >}}).
|
||||
{{</hint>}}
|
||||
|
||||
Push both package files to a registry. Pushing both files to one tag in the
|
||||
registry creates a
|
||||
[multi-platform](https://docs.docker.com/build/building/multi-platform/)
|
||||
package that runs on both `linux/arm64` and `linux/amd64` hosts.
|
||||
|
||||
```shell
|
||||
crossplane xpkg push \
|
||||
--package-files=function-amd64.xpkg,function-arm64.xpkg \
|
||||
negz/function-xbuckets:v0.1.0
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
If you push the function to a GitHub repository the template automatically sets
|
||||
up continuous integration (CI) using
|
||||
[GitHub Actions](https://github.com/features/actions). The CI workflow will
|
||||
lint, test, and build your function. You can see how the template configures CI
|
||||
by reading `.github/workflows/ci.yaml`.
|
||||
|
||||
The CI workflow can automatically push packages to `xpkg.upbound.io`. For this
|
||||
to work you must create a repository at https://marketplace.upbound.io. Give the
|
||||
CI workflow access to push to the Marketplace by creating an API token and
|
||||
[adding it to your repository](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository).
|
||||
Save your API token access ID as a secret named `XPKG_ACCESS_ID` and your API
|
||||
token as a secret named `XPKG_TOKEN`.
|
||||
{{</hint>}}
|
|
@ -0,0 +1,745 @@
|
|||
---
|
||||
title: Write a Composition Function in Python
|
||||
state: beta
|
||||
alphaVersion: "1.11"
|
||||
betaVersion: "1.14"
|
||||
weight: 81
|
||||
description: "Composition functions allow you to template resources using Python"
|
||||
---
|
||||
|
||||
Composition functions (or just functions, for short) are custom programs that
|
||||
template Crossplane resources. Crossplane calls composition functions to
|
||||
determine what resources it should create when you create a composite resource
|
||||
(XR). Read the
|
||||
[concepts]{{<ref "../concepts/composition-functions" >}}
|
||||
page to learn more about composition functions.
|
||||
|
||||
You can write a function to template resources using a general purpose
|
||||
programming language. Using a general purpose programming language allows a
|
||||
function to use advanced logic to template resources, like loops and
|
||||
conditionals. This guide explains how to write a composition function in
|
||||
[Python](https://python.org).
|
||||
|
||||
{{< hint "important" >}}
|
||||
It helps to be familiar with
|
||||
[how composition functions work]{{<ref "../concepts/composition-functions#how-composition-functions-work" >}}
|
||||
before following this guide.
|
||||
{{< /hint >}}
|
||||
|
||||
## Understand the steps
|
||||
|
||||
This guide covers writing a composition function for an
|
||||
{{<hover label="xr" line="2">}}XBuckets{{</hover>}} composite resource (XR).
|
||||
|
||||
```yaml {label="xr"}
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBuckets
|
||||
metadata:
|
||||
name: example-buckets
|
||||
spec:
|
||||
region: us-east-2
|
||||
names:
|
||||
- crossplane-functions-example-a
|
||||
- crossplane-functions-example-b
|
||||
- crossplane-functions-example-c
|
||||
```
|
||||
|
||||
<!-- vale gitlab.FutureTense = NO -->
|
||||
<!--
|
||||
This section is setting the stage for future sections. It doesn't make sense to
|
||||
refer to the function in the present tense, because it doesn't exist yet.
|
||||
-->
|
||||
An `XBuckets` XR has a region and an array of bucket names. The function will
|
||||
create an Amazon Web Services (AWS) S3 bucket for each entry in the names array.
|
||||
<!-- vale gitlab.FutureTense = YES -->
|
||||
|
||||
To write a function in Python:
|
||||
|
||||
1. [Install the tools you need to write the function](#install-the-tools-you-need-to-write-the-function)
|
||||
1. [Initialize the function from a template](#initialize-the-function-from-a-template)
|
||||
1. [Edit the template to add the function's logic](#edit-the-template-to-add-the-functions-logic)
|
||||
1. [Test the function end-to-end](#test-the-function-end-to-end)
|
||||
1. [Build and push the function to a package repository](#build-and-push-the-function-to-a-package-registry)
|
||||
|
||||
This guide covers each of these steps in detail.
|
||||
|
||||
## Install the tools you need to write the function
|
||||
|
||||
To write a function in Python you need:
|
||||
|
||||
* [Python](https://www.python.org/downloads/) v3.11.
|
||||
* [Hatch](https://hatch.pypa.io/), a Python build tool. This guide uses v1.7.
|
||||
* [Docker Engine](https://docs.docker.com/engine/). This guide uses Engine v24.
|
||||
* The [Crossplane CLI]({{<ref "../cli" >}}) v1.14 or newer. This guide uses Crossplane
|
||||
CLI v1.14.
|
||||
|
||||
{{<hint "note">}}
|
||||
You don't need access to a Kubernetes cluster or a Crossplane control plane to
|
||||
build or test a composition function.
|
||||
{{</hint>}}
|
||||
|
||||
## Initialize the function from a template
|
||||
|
||||
Use the `crossplane beta xpkg init` command to initialize a new function. When
|
||||
you run this command it initializes your function using
|
||||
[a GitHub repository](https://github.com/crossplane/function-template-python)
|
||||
as a template.
|
||||
|
||||
```shell {copy-lines=1}
|
||||
crossplane beta xpkg init function-xbuckets https://github.com/crossplane/function-template-python -d function-xbuckets
|
||||
Initialized package "function-xbuckets" in directory "/home/negz/control/negz/function-xbuckets" from https://github.com/crossplane/function-template-python/tree/bfed6923ab4c8e7adeed70f41138645fc7d38111 (main)
|
||||
```
|
||||
|
||||
The `crossplane beta init xpkg` command creates a directory named
|
||||
`function-xbuckets`. When you run the command the new directory should look like
|
||||
this:
|
||||
|
||||
```shell {copy-lines=1}
|
||||
ls function-xbuckets
|
||||
Dockerfile example/ function/ LICENSE package/ pyproject.toml README.md renovate.json tests/
|
||||
```
|
||||
|
||||
Your function's code lives in the `function` directory:
|
||||
|
||||
```shell {copy-lines=1}
|
||||
ls function/
|
||||
__version__.py fn.py main.py
|
||||
```
|
||||
|
||||
The `function/fn.py` file is where you add the function's code. It's useful to
|
||||
know about some other files in the template:
|
||||
|
||||
* `function/main.py` runs the function. You don't need to edit `main.py`.
|
||||
* `Dockerfile` builds the function runtime. You don't need to edit `Dockerfile`.
|
||||
* The `package` directory contains metadata used to build the function package.
|
||||
|
||||
{{<hint "tip">}}
|
||||
<!-- vale gitlab.FutureTense = NO -->
|
||||
<!--
|
||||
This tip talks about future plans for Crossplane.
|
||||
-->
|
||||
In v1.14 of the Crossplane CLI `crossplane beta xpkg init` just clones a
|
||||
template GitHub repository. A future CLI release will automate tasks like
|
||||
replacing the template name with the new function's name. See Crossplane issue
|
||||
[#4941](https://github.com/crossplane/crossplane/issues/4941) for details.
|
||||
<!-- vale gitlab.FutureTense = YES -->
|
||||
{{</hint>}}
|
||||
|
||||
Edit `package/crossplane.yaml` to change the package's name before you start
|
||||
adding code. Name your package `function-xbuckets`.
|
||||
|
||||
The `package/input` directory defines the OpenAPI schema for the a function's
|
||||
input. The function in this guide doesn't accept an input. Delete the
|
||||
`package/input` directory.
|
||||
|
||||
The [composition functions]{{<ref "../concepts/composition-functions" >}}
|
||||
documentation explains composition function inputs.
|
||||
|
||||
{{<hint "tip">}}
|
||||
If you're writing a function that uses an input, edit the input YAML file to
|
||||
meet your function's requirements.
|
||||
|
||||
Change the input's kind and API group. Don't use `Input` and
|
||||
`template.fn.crossplane.io`. Instead use something meaningful to your function.
|
||||
{{</hint>}}
|
||||
|
||||
## Edit the template to add the function's logic
|
||||
|
||||
You add your function's logic to the
|
||||
{{<hover label="hello-world" line="1">}}RunFunction{{</hover>}}
|
||||
method in `function/fn.py`. When you first open the file it contains a "hello
|
||||
world" function.
|
||||
|
||||
```python {label="hello-world"}
|
||||
async def RunFunction(self, req: fnv1beta1.RunFunctionRequest, _: grpc.aio.ServicerContext) -> fnv1beta1.RunFunctionResponse:
|
||||
log = self.log.bind(tag=req.meta.tag)
|
||||
log.info("Running function")
|
||||
|
||||
rsp = response.to(req)
|
||||
|
||||
example = ""
|
||||
if "example" in req.input:
|
||||
example = req.input["example"]
|
||||
|
||||
# TODO: Add your function logic here!
|
||||
response.normal(rsp, f"I was run with input {example}!")
|
||||
log.info("I was run!", input=example)
|
||||
|
||||
return rsp
|
||||
```
|
||||
|
||||
All Python composition functions have a `RunFunction` method. Crossplane passes
|
||||
everything the function needs to run in a
|
||||
{{<hover label="hello-world" line="1">}}RunFunctionRequest{{</hover>}} object.
|
||||
|
||||
The function tells Crossplane what resources it should compose by returning a
|
||||
{{<hover label="hello-world" line="15">}}RunFunctionResponse{{</hover>}} object.
|
||||
|
||||
Edit the `RunFunction` method to replace it with this code.
|
||||
|
||||
```python {hl_lines="7-28"}
|
||||
async def RunFunction(self, req: fnv1beta1.RunFunctionRequest, _: grpc.aio.ServicerContext) -> fnv1beta1.RunFunctionResponse:
|
||||
log = self.log.bind(tag=req.meta.tag)
|
||||
log.info("Running function")
|
||||
|
||||
rsp = response.to(req)
|
||||
|
||||
region = req.observed.composite.resource["spec"]["region"]
|
||||
names = req.observed.composite.resource["spec"]["names"]
|
||||
|
||||
for name in names:
|
||||
rsp.desired.resources[f"xbuckets-{name}"].resource.update(
|
||||
{
|
||||
"apiVersion": "s3.aws.upbound.io/v1beta1",
|
||||
"kind": "Bucket",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"crossplane.io/external-name": name,
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
"forProvider": {
|
||||
"region": region,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
log.info("Added desired buckets", region=region, count=len(names))
|
||||
|
||||
return rsp
|
||||
```
|
||||
|
||||
Expand the below block to view the full `fn.py`, including imports and
|
||||
commentary explaining the function's logic.
|
||||
|
||||
{{<expand "The full fn.py file" >}}
|
||||
```python
|
||||
"""A Crossplane composition function."""
|
||||
|
||||
import grpc
|
||||
from crossplane.function import logging, response
|
||||
from crossplane.function.proto.v1beta1 import run_function_pb2 as fnv1beta1
|
||||
from crossplane.function.proto.v1beta1 import run_function_pb2_grpc as grpcv1beta1
|
||||
|
||||
|
||||
class FunctionRunner(grpcv1beta1.FunctionRunnerService):
|
||||
"""A FunctionRunner handles gRPC RunFunctionRequests."""
|
||||
|
||||
def __init__(self):
|
||||
"""Create a new FunctionRunner."""
|
||||
self.log = logging.get_logger()
|
||||
|
||||
async def RunFunction(
|
||||
self, req: fnv1beta1.RunFunctionRequest, _: grpc.aio.ServicerContext
|
||||
) -> fnv1beta1.RunFunctionResponse:
|
||||
"""Run the function."""
|
||||
# Create a logger for this request.
|
||||
log = self.log.bind(tag=req.meta.tag)
|
||||
log.info("Running function")
|
||||
|
||||
# Create a response to the request. This copies the desired state and
|
||||
# pipeline context from the request to the response.
|
||||
rsp = response.to(req)
|
||||
|
||||
# Get the region and a list of bucket names from the observed composite
|
||||
# resource (XR). Crossplane represents resources using the Struct
|
||||
# well-known protobuf type. The Struct Python object can be accessed
|
||||
# like a dictionary.
|
||||
region = req.observed.composite.resource["spec"]["region"]
|
||||
names = req.observed.composite.resource["spec"]["names"]
|
||||
|
||||
# Add a desired S3 bucket for each name.
|
||||
for name in names:
|
||||
# Crossplane represents desired composed resources using a protobuf
|
||||
# map of messages. This works a little like a Python defaultdict.
|
||||
# Instead of assigning to a new key in the dict-like map, you access
|
||||
# the key and mutate its value as if it did exist.
|
||||
#
|
||||
# The below code works because accessing the xbuckets-{name} key
|
||||
# automatically creates a new, empty fnv1beta1.Resource message. The
|
||||
# Resource message has a resource field containing an empty Struct
|
||||
# object that can be populated from a dictionary by calling update.
|
||||
#
|
||||
# https://protobuf.dev/reference/python/python-generated/#map-fields
|
||||
rsp.desired.resources[f"xbuckets-{name}"].resource.update(
|
||||
{
|
||||
"apiVersion": "s3.aws.upbound.io/v1beta1",
|
||||
"kind": "Bucket",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"crossplane.io/external-name": name,
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
"forProvider": {
|
||||
"region": region,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# Log what the function did. This will only appear in the function's pod
|
||||
# logs. A function can use response.normal() and response.warning() to
|
||||
# emit Kubernetes events associated with the XR it's operating on.
|
||||
log.info("Added desired buckets", region=region, count=len(names))
|
||||
|
||||
return rsp
|
||||
```
|
||||
{{</expand>}}
|
||||
|
||||
This code:
|
||||
|
||||
1. Gets the observed composite resource from the `RunFunctionRequest`.
|
||||
1. Gets the region and bucket names from the observed composite resource.
|
||||
1. Adds one desired S3 bucket for each bucket name.
|
||||
1. Returns the desired S3 buckets in a `RunFunctionResponse`.
|
||||
|
||||
Crossplane provides a
|
||||
[software development kit](https://github.com/crossplane/function-sdk-python)
|
||||
(SDK) for writing composition functions in Python. This function uses utilities
|
||||
from the SDK.
|
||||
|
||||
{{<hint "tip">}}
|
||||
Read [the Python Function SDK documentation](https://crossplane.github.io/function-sdk-python).
|
||||
{{</hint>}}
|
||||
|
||||
{{<hint "important">}}
|
||||
The Python SDK automatically generates the `RunFunctionRequest` and
|
||||
`RunFunctionResponse` Python objects from a
|
||||
[Protocol Buffers](https://protobuf.dev) schema. You can see the schema in the
|
||||
[Buf Schema Registry](https://buf.build/crossplane/crossplane/docs/main:apiextensions.fn.proto.v1beta1).
|
||||
|
||||
The fields of the generated Python objects behave similarly to builtin Python
|
||||
types like dictionaries and lists. Be aware that there are some differences.
|
||||
|
||||
Notably, you access the map of observed and desired resources like a dictionary
|
||||
but you can't add a new desired resource by assigning to a map key. Instead,
|
||||
access and mutate the map key as if it already exists.
|
||||
|
||||
Instead of adding a new resource like this:
|
||||
|
||||
```python
|
||||
resource = {"apiVersion": "example.org/v1", "kind": "Composed", ...}
|
||||
rsp.desired.resources["new-resource"] = fnv1beta1.Resource(resource=resource)
|
||||
```
|
||||
|
||||
Pretend it already exists and mutate it, like this:
|
||||
|
||||
```python
|
||||
resource = {"apiVersion": "example.org/v1", "kind": "Composed", ...}
|
||||
rsp.desired.resources["new-resource"].resource.update(resource)
|
||||
```
|
||||
|
||||
Refer to the Protocol Buffers
|
||||
[Python Generated Code Guide](https://protobuf.dev/reference/python/python-generated/#fields)
|
||||
for further details.
|
||||
{{</hint>}}
|
||||
|
||||
## Test the function end-to-end
|
||||
|
||||
Test your function by adding unit tests, and by using the `crossplane beta
|
||||
render` command.
|
||||
|
||||
When you initialize a function from the
|
||||
template it adds some unit tests to `tests/test_fn.py`. These tests use the
|
||||
[`unittest`](https://docs.python.org/3/library/unittest.html) module from the
|
||||
Python standard library.
|
||||
|
||||
To add test cases, update the `cases` list in `test_run_function`. Expand the
|
||||
below block to view the full `tests/test_fn.py` file for the function.
|
||||
|
||||
{{<expand "The full test_fn.py file" >}}
|
||||
```python
|
||||
import dataclasses
|
||||
import unittest
|
||||
|
||||
from crossplane.function import logging, resource
|
||||
from crossplane.function.proto.v1beta1 import run_function_pb2 as fnv1beta1
|
||||
from google.protobuf import duration_pb2 as durationpb
|
||||
from google.protobuf import json_format
|
||||
from google.protobuf import struct_pb2 as structpb
|
||||
|
||||
from function import fn
|
||||
|
||||
|
||||
class TestFunctionRunner(unittest.IsolatedAsyncioTestCase):
|
||||
def setUp(self) -> None:
|
||||
logging.configure(level=logging.Level.DISABLED)
|
||||
self.maxDiff = 2000
|
||||
|
||||
async def test_run_function(self) -> None:
|
||||
@dataclasses.dataclass
|
||||
class TestCase:
|
||||
reason: str
|
||||
req: fnv1beta1.RunFunctionRequest
|
||||
want: fnv1beta1.RunFunctionResponse
|
||||
|
||||
cases = [
|
||||
TestCase(
|
||||
reason="The function should compose two S3 buckets.",
|
||||
req=fnv1beta1.RunFunctionRequest(
|
||||
observed=fnv1beta1.State(
|
||||
composite=fnv1beta1.Resource(
|
||||
resource=resource.dict_to_struct(
|
||||
{
|
||||
"apiVersion": "example.crossplane.io/v1alpha1",
|
||||
"kind": "XBuckets",
|
||||
"metadata": {"name": "test"},
|
||||
"spec": {
|
||||
"region": "us-east-2",
|
||||
"names": ["test-bucket-a", "test-bucket-b"],
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
want=fnv1beta1.RunFunctionResponse(
|
||||
meta=fnv1beta1.ResponseMeta(ttl=durationpb.Duration(seconds=60)),
|
||||
desired=fnv1beta1.State(
|
||||
resources={
|
||||
"xbuckets-test-bucket-a": fnv1beta1.Resource(
|
||||
resource=resource.dict_to_struct(
|
||||
{
|
||||
"apiVersion": "s3.aws.upbound.io/v1beta1",
|
||||
"kind": "Bucket",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"crossplane.io/external-name": "test-bucket-a"
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
"forProvider": {"region": "us-east-2"}
|
||||
},
|
||||
}
|
||||
)
|
||||
),
|
||||
"xbuckets-test-bucket-b": fnv1beta1.Resource(
|
||||
resource=resource.dict_to_struct(
|
||||
{
|
||||
"apiVersion": "s3.aws.upbound.io/v1beta1",
|
||||
"kind": "Bucket",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"crossplane.io/external-name": "test-bucket-b"
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
"forProvider": {"region": "us-east-2"}
|
||||
},
|
||||
}
|
||||
)
|
||||
),
|
||||
},
|
||||
),
|
||||
context=structpb.Struct(),
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
runner = fn.FunctionRunner()
|
||||
|
||||
for case in cases:
|
||||
got = await runner.RunFunction(case.req, None)
|
||||
self.assertEqual(
|
||||
json_format.MessageToDict(got),
|
||||
json_format.MessageToDict(case.want),
|
||||
"-want, +got",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
```
|
||||
{{</expand>}}
|
||||
|
||||
Run the unit tests using `hatch run`:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
hatch run test:unit
|
||||
.
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.003s
|
||||
|
||||
OK
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
[Hatch](https://hatch.pypa.io/) is a Python build tool. It builds Python
|
||||
artifacts like wheels. It also manages virtual environments, similar
|
||||
to `virtualenv` or `venv`. The `hatch run` command creates a virtual environment
|
||||
and runs a command in that environment.
|
||||
{{</hint>}}
|
||||
|
||||
You can preview the output of a Composition that uses this function using
|
||||
the Crossplane CLI. You don't need a Crossplane control plane to do this.
|
||||
|
||||
Create a directory under `function-xbuckets` named `example` and create
|
||||
Composite Resource, Composition and Function YAML files.
|
||||
|
||||
Expand the following block to see example files.
|
||||
|
||||
{{<expand "The xr.yaml, composition.yaml and function.yaml files">}}
|
||||
|
||||
You can recreate the output below using by running `crossplane beta render` with
|
||||
these files.
|
||||
|
||||
The `xr.yaml` file contains the composite resource to render:
|
||||
|
||||
```yaml
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBuckets
|
||||
metadata:
|
||||
name: example-buckets
|
||||
spec:
|
||||
region: us-east-2
|
||||
names:
|
||||
- crossplane-functions-example-a
|
||||
- crossplane-functions-example-b
|
||||
- crossplane-functions-example-c
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
The `composition.yaml` file contains the Composition to use to render the
|
||||
composite resource:
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: create-buckets
|
||||
spec:
|
||||
compositeTypeRef:
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBuckets
|
||||
mode: Pipeline
|
||||
pipeline:
|
||||
- step: create-buckets
|
||||
functionRef:
|
||||
name: function-xbuckets
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
The `functions.yaml` file contains the Functions the Composition references in
|
||||
its pipeline steps:
|
||||
|
||||
```yaml
|
||||
apiVersion: pkg.crossplane.io/v1beta1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: function-xbuckets
|
||||
annotations:
|
||||
render.crossplane.io/runtime: Development
|
||||
spec:
|
||||
# The CLI ignores this package when using the Development runtime.
|
||||
# You can set it to any value.
|
||||
package: xpkg.upbound.io/negz/function-xbuckets:v0.1.0
|
||||
```
|
||||
{{</expand>}}
|
||||
|
||||
The Function in `functions.yaml` uses the
|
||||
{{<hover label="development" line="6">}}Development{{</hover>}}
|
||||
runtime. This tells `crossplane beta render` that your function is running
|
||||
locally. It connects to your locally running function instead of using Docker to
|
||||
pull and run the function.
|
||||
|
||||
```yaml {label="development"}
|
||||
apiVersion: pkg.crossplane.io/v1beta1
|
||||
kind: Function
|
||||
metadata:
|
||||
name: function-xbuckets
|
||||
annotations:
|
||||
render.crossplane.io/runtime: Development
|
||||
```
|
||||
|
||||
Use `hatch run development` to run your function locally.
|
||||
|
||||
```shell {label="run"}
|
||||
hatch run development
|
||||
```
|
||||
|
||||
{{<hint "warning">}}
|
||||
`hatch run development` runs the function without encryption or authentication.
|
||||
Only use it during testing and development.
|
||||
{{</hint>}}
|
||||
|
||||
In a separate terminal, run `crossplane beta render`.
|
||||
|
||||
```shell
|
||||
crossplane beta render xr.yaml composition.yaml functions.yaml
|
||||
```
|
||||
|
||||
This command calls your function. In the terminal where your function is running
|
||||
you should now see log output:
|
||||
|
||||
```shell
|
||||
hatch run development
|
||||
2024-01-11T22:12:58.153572Z [info ] Running function filename=fn.py lineno=22 tag=
|
||||
2024-01-11T22:12:58.153792Z [info ] Added desired buckets count=3 filename=fn.py lineno=68 region=us-east-2 tag=
|
||||
```
|
||||
|
||||
The `crossplane beta render` command prints the desired resources the function
|
||||
returns.
|
||||
|
||||
```yaml
|
||||
---
|
||||
apiVersion: example.crossplane.io/v1
|
||||
kind: XBuckets
|
||||
metadata:
|
||||
name: example-buckets
|
||||
---
|
||||
apiVersion: s3.aws.upbound.io/v1beta1
|
||||
kind: Bucket
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-b
|
||||
crossplane.io/external-name: crossplane-functions-example-b
|
||||
generateName: example-buckets-
|
||||
labels:
|
||||
crossplane.io/composite: example-buckets
|
||||
ownerReferences:
|
||||
# Omitted for brevity
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-2
|
||||
---
|
||||
apiVersion: s3.aws.upbound.io/v1beta1
|
||||
kind: Bucket
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-c
|
||||
crossplane.io/external-name: crossplane-functions-example-c
|
||||
generateName: example-buckets-
|
||||
labels:
|
||||
crossplane.io/composite: example-buckets
|
||||
ownerReferences:
|
||||
# Omitted for brevity
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-2
|
||||
---
|
||||
apiVersion: s3.aws.upbound.io/v1beta1
|
||||
kind: Bucket
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/composition-resource-name: xbuckets-crossplane-functions-example-a
|
||||
crossplane.io/external-name: crossplane-functions-example-a
|
||||
generateName: example-buckets-
|
||||
labels:
|
||||
crossplane.io/composite: example-buckets
|
||||
ownerReferences:
|
||||
# Omitted for brevity
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-2
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
Read the composition functions documentation to learn more about
|
||||
[testing composition functions]({{< ref "../concepts/composition-functions#test-a-composition-that-uses-functions" >}}).
|
||||
{{</hint>}}
|
||||
|
||||
## Build and push the function to a package registry
|
||||
|
||||
You build a function in two stages. First you build the function's runtime. This
|
||||
is the Open Container Initiative (OCI) image Crossplane uses to run your
|
||||
function. You then embed that runtime in a package, and push it to a package
|
||||
registry. The Crossplane CLI uses `xpkg.upbound.io` as its default package
|
||||
registry.
|
||||
|
||||
A function supports a single platform, like `linux/amd64`, by default. You can
|
||||
support multiple platforms by building a runtime and package for each platform,
|
||||
then pushing all the packages to a single tag in the registry.
|
||||
|
||||
Pushing your function to a registry allows you to use your function in a
|
||||
Crossplane control plane. See the
|
||||
[composition functions documentation]{{<ref "../concepts/composition-functions" >}}.
|
||||
to learn how to use a function in a control plane.
|
||||
|
||||
Use Docker to build a runtime for each platform.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
docker build . --quiet --platform=linux/amd64 --tag runtime-amd64
|
||||
sha256:fdf40374cc6f0b46191499fbc1dbbb05ddb76aca854f69f2912e580cfe624b4b
|
||||
```
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
docker build . --quiet --platform=linux/arm64 --tag runtime-arm64
|
||||
sha256:cb015ceabf46d2a55ccaeebb11db5659a2fb5e93de36713364efcf6d699069af
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
You can use whatever tag you want. There's no need to push the runtime images to
|
||||
a registry. The tag is only used to tell `crossplane xpkg build` what runtime to
|
||||
embed.
|
||||
{{</hint>}}
|
||||
|
||||
{{<hint "important">}}
|
||||
Docker uses emulation to create images for different platforms. If building an
|
||||
image for a different platform fails, make sure you have installed `binfmt`. See
|
||||
the
|
||||
[Docker documentation](https://docs.docker.com/build/building/multi-platform/#qemu)
|
||||
for instructions.
|
||||
{{</hint>}}
|
||||
|
||||
Use the Crossplane CLI to build a package for each platform. Each package embeds
|
||||
a runtime image.
|
||||
|
||||
The {{<hover label="build" line="2">}}--package-root{{</hover>}} flag specifies
|
||||
the `package` directory, which contains `crossplane.yaml`. This includes
|
||||
metadata about the package.
|
||||
|
||||
The {{<hover label="build" line="3">}}--embed-runtime-image{{</hover>}} flag
|
||||
specifies the runtime image tag built using Docker.
|
||||
|
||||
The {{<hover label="build" line="4">}}--package-file{{</hover>}} flag specifies
|
||||
specifies where to write the package file to disk. Crossplane package files use
|
||||
the extension `.xpkg`.
|
||||
|
||||
```shell {label="build"}
|
||||
crossplane xpkg build \
|
||||
--package-root=package \
|
||||
--embed-runtime-image=runtime-amd64 \
|
||||
--package-file=function-amd64.xpkg
|
||||
```
|
||||
|
||||
```shell
|
||||
crossplane xpkg build \
|
||||
--package-root=package \
|
||||
--embed-runtime-image=runtime-arm64 \
|
||||
--package-file=function-arm64.xpkg
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
Crossplane packages are special OCI images. Read more about packages in the
|
||||
[packages documentation]({{< ref "../concepts/packages" >}}).
|
||||
{{</hint>}}
|
||||
|
||||
Push both package files to a registry. Pushing both files to one tag in the
|
||||
registry creates a
|
||||
[multi-platform](https://docs.docker.com/build/building/multi-platform/)
|
||||
package that runs on both `linux/arm64` and `linux/amd64` hosts.
|
||||
|
||||
```shell
|
||||
crossplane xpkg push \
|
||||
--package-files=function-amd64.xpkg,function-arm64.xpkg \
|
||||
negz/function-xbuckets:v0.1.0
|
||||
```
|
||||
|
||||
{{<hint "tip">}}
|
||||
If you push the function to a GitHub repository the template automatically sets
|
||||
up continuous integration (CI) using
|
||||
[GitHub Actions](https://github.com/features/actions). The CI workflow will
|
||||
lint, test, and build your function. You can see how the template configures CI
|
||||
by reading `.github/workflows/ci.yaml`.
|
||||
|
||||
The CI workflow can automatically push packages to `xpkg.upbound.io`. For this
|
||||
to work you must create a repository at https://marketplace.upbound.io. Give the
|
||||
CI workflow access to push to the Marketplace by creating an API token and
|
||||
[adding it to your repository](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository).
|
||||
Save your API token access ID as a secret named `XPKG_ACCESS_ID` and your API
|
||||
token as a secret named `XPKG_TOKEN`.
|
||||
{{</hint>}}
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
title: Learn
|
||||
description: Learn more about Crossplane.
|
||||
---
|
||||
|
||||
If you have any questions, please drop us a note on [Crossplane Slack][join-crossplane-slack] or [contact us][contact-us]!
|
||||
|
||||
***Learn more about using Crossplane***
|
||||
- [Latest Design Docs](https://github.com/crossplane/crossplane/tree/master/design)
|
||||
- [Roadmap](https://github.com/crossplane/crossplane/blob/master/ROADMAP.md)
|
||||
- [Crossplane Architecture](https://docs.google.com/document/d/1whncqdUeU2cATGEJhHvzXWC9xdK29Er45NJeoemxebo/edit?usp=sharing)
|
||||
- [GitLab deploys into multiple clouds from kubectl using Crossplane](https://about.gitlab.com/2019/05/20/gitlab-first-deployed-kubernetes-api-to-multiple-clouds/)
|
||||
- [CNCF Talks & Community Presentations](https://www.youtube.com/playlist?list=PL510POnNVaaZJj9OG6PbgsZvgYbhwJRyE)
|
||||
- [Software Engineering Daily - Intro Podcast](https://softwareengineeringdaily.com/2019/01/02/crossplane-multicloud-control-plane-with-bassam-tabbara/)
|
||||
|
||||
***Writing Kubernetes controllers to extend Crossplane***
|
||||
- [Keep the Space Shuttle Flying: Writing Robust Operators](https://www.youtube.com/watch?v=uf97lOApOv8)
|
||||
- [Best practices for building Kubernetes Operators](https://cloud.google.com/blog/products/containers-kubernetes/best-practices-for-building-kubernetes-operators-and-stateful-apps)
|
||||
- [Programming Kubernetes Book](https://www.oreilly.com/library/view/programming-kubernetes/9781492047094/)
|
||||
- [Contributor Guide](https://github.com/crossplane/crossplane/blob/master/CONTRIBUTING.md)
|
||||
|
||||
***Join the growing Crossplane community and get involved!***
|
||||
- Join our [Community Slack](https://slack.crossplane.io/)!
|
||||
- Submit an issue on [GitHub](https://github.com/crossplane/crossplane)
|
||||
- Attend our bi-weekly [Community Meeting](https://github.com/crossplane/crossplane#get-involved)
|
||||
- Join our bi-weekly live stream: [The Binding Status](https://github.com/crossplane/tbs)
|
||||
- Subscribe to our [YouTube Channel](https://www.youtube.com/channel/UC19FgzMBMqBro361HbE46Fw)
|
||||
- Drop us a note on Twitter: [@crossplane_io](https://twitter.com/crossplane_io)
|
||||
- Email us: [info@crossplane.io](mailto:info@crossplane.io)
|
||||
|
||||
<!-- Named links -->
|
||||
|
||||
[join-crossplane-slack]: https://slack.crossplane.io
|
||||
[contact-us]: https://github.com/crossplane/crossplane#contact
|
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
title: Feature Lifecycle
|
||||
toc: true
|
||||
weight: 309
|
||||
indent: true
|
||||
---
|
||||
|
||||
# Feature Lifecycle
|
||||
|
||||
Crossplane follows a similar feature lifecycle to [upstream
|
||||
Kubernetes][kube-features]. All major new features must be added in alpha. Alpha
|
||||
features are expected to eventually graduate to beta, and then to general
|
||||
availability (GA). Features that languish at alpha or beta may be subject to
|
||||
deprecation.
|
||||
|
||||
## Alpha Features
|
||||
|
||||
Alpha are off by default, and must be enabled by a feature flag, for example
|
||||
`--enable-composition-revisions`. API types pertaining to alpha features use a
|
||||
`vNalphaN` style API version, like `v1alpha`. **Alpha features are subject to
|
||||
removal or breaking changes without notice**, and generally not considered ready
|
||||
for use in production.
|
||||
|
||||
In some cases alpha features require fields be added to existing beta or GA
|
||||
API types. In these cases fields must clearly be marked (i.e in their OpenAPI
|
||||
schema) as alpha and subject to alpha API constraints (or lack thereof).
|
||||
|
||||
All alpha features should have an issue tracking their graduation to beta.
|
||||
|
||||
## Beta Features
|
||||
|
||||
Beta features are on by default, but may be disabled by a feature flag. API
|
||||
types pertaining to beta features use a `vNbetaN` style API version, like
|
||||
`v1beta1`. Beta features are considered to be well tested, and will not be
|
||||
removed completely without being marked deprecated for at least two releases.
|
||||
|
||||
The schema and/or semantics of objects may change in incompatible ways in a
|
||||
subsequent beta or stable release. When this happens, we will provide
|
||||
instructions for migrating to the next version. This may require deleting,
|
||||
editing, and re-creating API objects. The editing process may require some
|
||||
thought. This may require downtime for applications that rely on the feature.
|
||||
|
||||
In some cases beta features require fields be added to existing GA API types. In
|
||||
these cases fields must clearly be marked (i.e in their OpenAPI schema) as beta
|
||||
and subject to beta API constraints (or lack thereof).
|
||||
|
||||
All beta features should have an issue tracking their graduation to GA.
|
||||
|
||||
## GA Features
|
||||
|
||||
GA features are always enabled - they cannot be disabled. API types pertaining
|
||||
to GA features use `vN` style API versions, like `v1`. GA features are widely
|
||||
used and thoroughly tested. They guarantee API stability - only backward
|
||||
compatible changes are allowed.
|
||||
|
||||
[kube-features]: https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-stages
|
|
@ -0,0 +1,100 @@
|
|||
---
|
||||
title: Release Cycle
|
||||
weight: 308
|
||||
---
|
||||
|
||||
Starting with the v1.10.0 release, Crossplane is released on a quarterly (13
|
||||
week) cadence. A cycle is comprised of three general stages:
|
||||
|
||||
- Weeks 1—11: [Active Development]
|
||||
- Week 12: [Feature Freeze]
|
||||
- Week 13: [Code Freeze]
|
||||
|
||||
This results in four releases per year, with the most recent three releases
|
||||
being maintained at any given time. When a new release is cut, the fourth most
|
||||
recent release reaches end of life (EOL). Users can expect any given release to
|
||||
be maintained for nine months.
|
||||
|
||||
### Definition of maintenance
|
||||
|
||||
The Crossplane community defines maintenance in that relevant bug fixes that are
|
||||
merged to the main development branch will be eligible to be back-ported to the
|
||||
release branch of any currently maintained version, and patch releases will be
|
||||
cut appropriately. It's also possible that a fix may be merged directly to the
|
||||
release branch if no longer applicable on the main development branch.
|
||||
Maintenance doesn't indicate any SLA on response time for user support in the
|
||||
form of Slack messages or issues, but problems will be addressed on a best
|
||||
effort basis by maintainers and contributors for currently maintained releases.
|
||||
|
||||
### Patch releases
|
||||
|
||||
_This policy is subject to change in the future._
|
||||
|
||||
Patch releases are cut for currently maintained minor versions on an as-needed
|
||||
basis. Any critical back-ported fixes will be included in a patch release as
|
||||
soon as possible after merge.
|
||||
|
||||
### Pre-releases
|
||||
|
||||
_This policy is subject to change in the future._
|
||||
|
||||
Alpha, Beta, and RC releases are cut for an upcoming release on an as-needed
|
||||
basis. As a policy, at least one pre-release will be cut prior to any minor
|
||||
release. Pre-releases won't be made on release branches.
|
||||
|
||||
### Provider releases
|
||||
|
||||
The Crossplane release cycle isn't required to be adhered to by any other
|
||||
Crossplane projects, but a similar cadence is encouraged. Maintainers listed in
|
||||
each repository's `OWNERS.md` file are responsible for determining and
|
||||
publishing the release cycle for their project.
|
||||
|
||||
## Release stages
|
||||
|
||||
The following stages are the main milestones in a Crossplane release.
|
||||
|
||||
### Active development
|
||||
|
||||
During active development, any code that meets the requisite criteria (i.e.
|
||||
passing appropriate tests, approved by a maintainer, etc.) will be merged into
|
||||
the main development branch. At present, there is no requirement to formally
|
||||
submit an enhancement proposal prior to the start of the release cycle, but
|
||||
contributors are encouraged to open an issue and gather feedback before starting
|
||||
work on a major implementation (see [CONTRIBUTING.md] for more information).
|
||||
|
||||
### Feature freeze
|
||||
|
||||
During feature freeze, no new functionality should be merged into the main
|
||||
development branch. Bug fixes, documentation changes, and non-critical changes
|
||||
may be made. In the case that a new feature is deemed absolutely necessary for a
|
||||
release, the Crossplane maintainers will weigh the impact of the change and make
|
||||
a decision on whether it should be included.
|
||||
|
||||
### Code freeze
|
||||
|
||||
During code freeze, there should be no changes merged to the main development
|
||||
branch with the following exceptions:
|
||||
- Fixes to a failing test that's deemed to be incorrectly testing
|
||||
functionality.
|
||||
- Documentation only changes. It's possible that a documentation freeze will be
|
||||
implemented in the future, but it's not currently enforced.
|
||||
- Fixes to a critical bug that wasn't previously identified. Merging a bug fix
|
||||
during code freeze requires application for and approval of an exception by
|
||||
Crossplane maintainers. This process is currently informal, but may be
|
||||
formalized in the future.
|
||||
|
||||
## Release dates
|
||||
|
||||
Crossplane releases once a quarter (every 13 weeks). Typically, the release
|
||||
happens on the Tuesday of the last week of the quarter, as shown on the
|
||||
[community calendar][community calendar]. Keep in mind that the specific date is
|
||||
**approximate**. A lot of factors can alter the date slightly, such as code
|
||||
reviews, testing, and bug fixing to ensure a quality release.
|
||||
|
||||
<!-- Named links -->
|
||||
|
||||
[Active Development]: #active-development
|
||||
[Feature Freeze]: #feature-freeze
|
||||
[Code Freeze]: #code-freeze
|
||||
[CONTRIBUTING.md]: https://github.com/crossplane/crossplane/blob/master/CONTRIBUTING.md
|
||||
[community calendar]: https://calendar.google.com/calendar/embed?src=c_2cdn0hs9e2m05rrv1233cjoj1k%40group.calendar.google.com
|
|
@ -28,7 +28,7 @@ Read the
|
|||
* Changes to TLS certificates. Existing users of external secret stores need to
|
||||
manually update their TLS certificates. Read [Crossplane issue #4565](https://github.com/crossplane/crossplane/pull/4656) for more information.
|
||||
* Removed Vault support for External Secret Stores. Crossplane
|
||||
suggests using the [ESS Plugins]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) as a replacement.
|
||||
suggests using the [ESS Plugins]({{<ref "../guides/vault-as-secret-store">}}) as a replacement.
|
||||
* Removed the `controllerConfigRef` from the Configuration package
|
||||
and package revision APIs.
|
||||
* The introduction of the new [Crossplane CLI]({{<ref "../cli" >}}) deprecates
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
title: Crossplane API
|
||||
title: API Reference
|
||||
weight: 400
|
||||
description: "API details for Crossplane's core types"
|
||||
cascade:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
weight: 400
|
||||
title: Crossplane CLI
|
||||
weight: 200
|
||||
title: CLI Reference
|
||||
description: "Documentation for the Crossplane command-line interface"
|
||||
---
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: Concepts
|
||||
weight: 100
|
||||
weight: 50
|
||||
description: Understand Crossplane's core components
|
||||
---
|
||||
|
||||
|
|
|
@ -204,4 +204,4 @@ spec:
|
|||
name: my-claim-secret
|
||||
```
|
||||
|
||||
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}).
|
||||
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
|
|
@ -618,7 +618,7 @@ recreate the XRD to change the `connectionSecretKeys`.
|
|||
{{</hint >}}
|
||||
|
||||
For more information on connection secrets read the
|
||||
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}).
|
||||
[Connection Secrets knowledge base article]({{<ref "connection-details">}}).
|
||||
|
||||
### Set composite resource defaults
|
||||
XRDs can set default parameters for composite resources and Claims.
|
||||
|
|
|
@ -189,7 +189,7 @@ spec:
|
|||
### Composition revision policy
|
||||
|
||||
Crossplane tracks changes to Compositions as
|
||||
[Composition revisions]({{<ref "/knowledge-base/guides/composition-revisions">}}) .
|
||||
[Composition revisions]({{<ref "composition-revisions">}}) .
|
||||
|
||||
A composite resource can use
|
||||
a {{<hover label="comprev" line="6">}}compositionUpdatePolicy{{</hover>}} to
|
||||
|
@ -217,7 +217,7 @@ spec:
|
|||
### Composition revision selection
|
||||
|
||||
Crossplane records changes to Compositions as
|
||||
[Composition revisions]({{<ref "/knowledge-base/guides/composition-revisions">}}).
|
||||
[Composition revisions]({{<ref "composition-revisions">}}).
|
||||
A composite resource can
|
||||
select a specific Composition revision.
|
||||
|
||||
|
@ -309,7 +309,7 @@ spec:
|
|||
```
|
||||
|
||||
Composite resources can write connection secrets to an
|
||||
[external secret store]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}),
|
||||
[external secret store]({{<ref "../guides/vault-as-secret-store">}}),
|
||||
like HashiCorp Vault.
|
||||
|
||||
{{<hint "important" >}}
|
||||
|
@ -332,10 +332,10 @@ spec:
|
|||
# Removed for brevity
|
||||
```
|
||||
|
||||
Read the [External Secrets Store]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) documentation for more information on using
|
||||
Read the [External Secrets Store]({{<ref "../guides/vault-as-secret-store">}}) documentation for more information on using
|
||||
external secret stores.
|
||||
|
||||
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}).
|
||||
For more information on connection secrets read the [Connection Secrets knowledge base article]({{<ref "connection-details">}}).
|
||||
|
||||
### Pausing composite resources
|
||||
|
||||
|
|
|
@ -453,7 +453,7 @@ $ crossplane xpkg push -f package/*.xpkg crossplane-contrib/function-example:v0.
|
|||
|
||||
{{<hint "tip">}}
|
||||
Crossplane has
|
||||
[language specific guides]({{<ref "../../knowledge-base/guides">}}) to writing
|
||||
[language specific guides]({{<ref "../guides">}}) to writing
|
||||
a composition function. Refer to the guide for your preferred language for a
|
||||
more detailed guide to writing a function.
|
||||
{{</hint>}}
|
||||
|
|
|
@ -0,0 +1,445 @@
|
|||
---
|
||||
title: Composition Revisions
|
||||
weight: 35
|
||||
---
|
||||
|
||||
This guide discusses the use of "Composition Revisions" to safely make and roll
|
||||
back changes to a Crossplane [`Composition`][composition-type]. It assumes
|
||||
familiarity with Crossplane, and particularly with
|
||||
[Compositions].
|
||||
|
||||
A `Composition` configures how Crossplane should reconcile a Composite Resource
|
||||
(XR). Put otherwise, when you create an XR the selected `Composition` determines
|
||||
what managed resources Crossplane will create in response. Let's say for example
|
||||
that you define a `PlatformDB` XR, which represents your organisation's common
|
||||
database configuration of an Azure MySQL Server and a few firewall rules. The
|
||||
`Composition` contains the 'base' configuration for the MySQL server and the
|
||||
firewall rules that is extended by the configuration for the `PlatformDB`.
|
||||
|
||||
There is a one-to-many relationship between a `Composition` and the XRs that use
|
||||
it. You might define a `Composition` named `big-platform-db` that is used by ten
|
||||
different `PlatformDB` XRs. Usually, in the interest of self-service, the
|
||||
`Composition` is managed by a different team from the actual `PlatformDB` XRs.
|
||||
For example the `Composition` may be written and maintained by a platform team
|
||||
member, while individual application teams create `PlatformDB` XRs that use said
|
||||
`Composition`.
|
||||
|
||||
Each `Composition` is mutable - you can update it as your organisation's needs
|
||||
change. However, without Composition Revisions updating a `Composition` can be a
|
||||
risky process. Crossplane constantly uses the `Composition` to ensure that your
|
||||
actual infrastructure - your MySQL Servers and firewall rules - match your
|
||||
desired state. If you have 10 `PlatformDB` XRs all using the `big-platform-db`
|
||||
`Composition`, all 10 of those XRs will be instantly updated in accordance with
|
||||
any updates you make to the `big-platform-db` `Composition`.
|
||||
|
||||
Composition Revisions allow XRs to opt out of automatic updates. Instead you can
|
||||
update your XRs to leverage the latest `Composition` settings at your own pace.
|
||||
This enables you to [canary] changes to your infrastructure, or to roll back
|
||||
some XRs to previous `Composition` settings without rolling back all XRs.
|
||||
|
||||
## Using Composition Revisions
|
||||
|
||||
When you enable Composition Revisions three things happen:
|
||||
|
||||
1. Crossplane creates a `CompositionRevision` for each `Composition` update.
|
||||
1. Composite Resources gain a `spec.compositionRevisionRef` field that specifies
|
||||
which `CompositionRevision` they use.
|
||||
1. Composite Resources gain a `spec.compositionUpdatePolicy` field that
|
||||
specifies how they should be updated to new Composition Revisions.
|
||||
|
||||
Each time you edit a `Composition` Crossplane will automatically create a
|
||||
`CompositionRevision` that represents that 'revision' of the `Composition` -
|
||||
that unique state. Each revision is allocated an increasing revision number.
|
||||
This gives `CompositionRevision` consumers an idea about which revision is
|
||||
'newest'.
|
||||
|
||||
Crossplane distinguishes between the 'newest' and the 'current' revision of a
|
||||
`Composition`. That is, if you revert a `Composition` to a previous state that
|
||||
corresponds to an existing `CompositionRevision` that revision will become
|
||||
'current' even if it is not the 'newest' revision (i.e. the most latest _unique_
|
||||
`Composition` configuration).
|
||||
|
||||
You can discover which revisions exist using `kubectl`:
|
||||
|
||||
```console
|
||||
# Find all revisions of the Composition named 'example'
|
||||
kubectl get compositionrevision -l crossplane.io/composition-name=example
|
||||
```
|
||||
|
||||
This should produce output something like:
|
||||
|
||||
```console
|
||||
NAME REVISION CURRENT AGE
|
||||
example-18pdg 1 False 4m36s
|
||||
example-2bgdr 2 True 73s
|
||||
example-xjrdm 3 False 61s
|
||||
```
|
||||
|
||||
> A `Composition` is a mutable resource that you can update as your needs
|
||||
> change over time. Each `CompositionRevision` is an immutable snapshot of those
|
||||
> needs at a particular point in time.
|
||||
|
||||
Crossplane behaves the same way by default whether Composition Revisions are
|
||||
enabled or not. This is because when you enable Composition Revisions all XRs
|
||||
default to the `Automatic` `compositionUpdatePolicy`. XRs support two update
|
||||
policies:
|
||||
|
||||
* `Automatic`: Automatically use the current `CompositionRevision`. (Default)
|
||||
* `Manual`: Require manual intervention to change `CompositionRevision`.
|
||||
|
||||
The below XR uses the `Manual` policy. When this policy is used the XR will
|
||||
select the current `CompositionRevision` when it is first created, but must
|
||||
manually be updated when you wish it to use another `CompositionRevision`.
|
||||
|
||||
```yaml
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: PlatformDB
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
parameters:
|
||||
storageGB: 20
|
||||
# The Manual policy specifies that you do not want this XR to update to the
|
||||
# current CompositionRevision automatically.
|
||||
compositionUpdatePolicy: Manual
|
||||
compositionRef:
|
||||
name: example
|
||||
writeConnectionSecretToRef:
|
||||
name: db-conn
|
||||
```
|
||||
|
||||
Crossplane sets an XR's `compositionRevisionRef` automatically at creation time
|
||||
regardless of your chosen `compositionUpdatePolicy`. If you choose the `Manual`
|
||||
policy you must edit the `compositionRevisionRef` field when you want your XR to
|
||||
use a different `CompositionRevision`.
|
||||
|
||||
```yaml
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: PlatformDB
|
||||
metadata:
|
||||
name: example
|
||||
spec:
|
||||
parameters:
|
||||
storageGB: 20
|
||||
compositionUpdatePolicy: Manual
|
||||
compositionRef:
|
||||
name: example
|
||||
# Update the referenced CompositionRevision if and when you are ready.
|
||||
compositionRevisionRef:
|
||||
name: example-18pdg
|
||||
writeConnectionSecretToRef:
|
||||
name: db-conn
|
||||
```
|
||||
|
||||
## Complete example
|
||||
|
||||
This tutorial discusses how CompositionRevisions work and how they manage Composite Resource
|
||||
(XR) updates. This starts with a `Composition` and `CompositeResourceDefinition` (XRD) that defines a `MyVPC`
|
||||
resource and continues with creating multiple XRs to observe different upgrade paths. Crossplane will
|
||||
assign different CompositionRevisions to the created composite resources each time the composition is updated.
|
||||
|
||||
### Preparation
|
||||
##### Install Crossplane
|
||||
Install Crossplane v1.11.0 or later and wait until the Crossplane pods are running.
|
||||
```shell
|
||||
kubectl create namespace crossplane-system
|
||||
helm repo add crossplane-master https://charts.crossplane.io/master/
|
||||
helm repo update
|
||||
helm install crossplane --namespace crossplane-system crossplane-master/crossplane --devel --version 1.11.0-rc.0.108.g0521c32e
|
||||
kubectl get pods -n crossplane-system
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
crossplane-7f75ddcc46-f4d2z 1/1 Running 0 9s
|
||||
crossplane-rbac-manager-78bd597746-sdv6w 1/1 Running 0 9s
|
||||
```
|
||||
|
||||
#### Deploy Composition and XRD Examples
|
||||
Apply the example Composition.
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
labels:
|
||||
channel: dev
|
||||
name: myvpcs.aws.example.upbound.io
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: crossplane-system
|
||||
compositeTypeRef:
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
resources:
|
||||
- base:
|
||||
apiVersion: ec2.aws.upbound.io/v1beta1
|
||||
kind: VPC
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-west-1
|
||||
cidrBlock: 192.168.0.0/16
|
||||
enableDnsSupport: true
|
||||
enableDnsHostnames: true
|
||||
name: my-vcp
|
||||
```
|
||||
|
||||
Apply the example XRD.
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: myvpcs.aws.example.upbound.io
|
||||
spec:
|
||||
group: aws.example.upbound.io
|
||||
names:
|
||||
kind: MyVPC
|
||||
plural: myvpcs
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
referenceable: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: ID of this VPC that other objects will use to refer to it.
|
||||
required:
|
||||
- id
|
||||
```
|
||||
|
||||
Verify that Crossplane created the Composition revision
|
||||
```shell
|
||||
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME REVISION CHANNEL
|
||||
myvpcs.aws.example.upbound.io-ad265bc 1 dev
|
||||
```
|
||||
|
||||
{{< hint "note" >}}
|
||||
The label `dev` is automatically created from the Composition.
|
||||
{{< /hint >}}
|
||||
|
||||
|
||||
### Create Composite Resources
|
||||
This tutorial has four composite resources to cover different update policies and composition selection options.
|
||||
The default behavior is updating XRs to the latest revision of the Composition. However, this can be changed by setting
|
||||
`compositionUpdatePolicy: Manual` in the XR. It is also possible to select the latest revision with a specific label
|
||||
with `compositionRevisionSelector.matchLabels` together with `compositionUpdatePolicy: Automatic`.
|
||||
|
||||
#### Default update policy
|
||||
Create an XR without a `compositionUpdatePolicy` defined. The update policy is `Automatic` by default:
|
||||
```yaml
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
metadata:
|
||||
name: vpc-auto
|
||||
spec:
|
||||
id: vpc-auto
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
myvpc.aws.example.upbound.io/vpc-auto created
|
||||
```
|
||||
|
||||
#### Manual update policy
|
||||
Create a Composite Resource with `compositionUpdatePolicy: Manual` and `compositionRevisionRef`.
|
||||
```yaml
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
metadata:
|
||||
name: vpc-man
|
||||
spec:
|
||||
id: vpc-man
|
||||
compositionUpdatePolicy: Manual
|
||||
compositionRevisionRef:
|
||||
name: myvpcs.aws.example.upbound.io-ad265bc
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
```shell
|
||||
myvpc.aws.example.upbound.io/vpc-man created
|
||||
```
|
||||
|
||||
#### Using a selector
|
||||
Create an XR with a `compositionRevisionSelector` of `channel: dev`:
|
||||
```yaml
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
metadata:
|
||||
name: vpc-dev
|
||||
spec:
|
||||
id: vpc-dev
|
||||
compositionRevisionSelector:
|
||||
matchLabels:
|
||||
channel: dev
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
myvpc.aws.example.upbound.io/vpc-dev created
|
||||
```
|
||||
|
||||
Create an XR with a `compositionRevisionSelector` of `channel: staging`:
|
||||
```yaml
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
metadata:
|
||||
name: vpc-staging
|
||||
spec:
|
||||
id: vpc-staging
|
||||
compositionRevisionSelector:
|
||||
matchLabels:
|
||||
channel: staging
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
```shell
|
||||
myvpc.aws.example.upbound.io/vpc-staging created
|
||||
```
|
||||
|
||||
Verify the Composite Resource with the label `channel: staging` doesn't have a `REVISION`.
|
||||
All other XRs have a `REVISION` matching the created Composition Revision.
|
||||
```shell
|
||||
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME SYNCED REVISION POLICY MATCHLABEL
|
||||
vpc-auto True myvpcs.aws.example.upbound.io-ad265bc Automatic <none>
|
||||
vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev]
|
||||
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
|
||||
vpc-staging False <none> Automatic map[channel:staging]
|
||||
```
|
||||
|
||||
{{< hint "note" >}}
|
||||
The `vpc-staging` XR label doesn't match any existing Composition Revisions.
|
||||
{{< /hint >}}
|
||||
|
||||
### Create new Composition revisions
|
||||
Crossplane creates a new CompositionRevision when a Composition is created or updated. Label and annotation changes will
|
||||
also trigger a new CompositionRevision.
|
||||
|
||||
#### Update the Composition label
|
||||
Update the `Composition` label to `channel: staging`:
|
||||
```shell
|
||||
kubectl label composition myvpcs.aws.example.upbound.io channel=staging --overwrite
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
composition.apiextensions.crossplane.io/myvpcs.aws.example.upbound.io labeled
|
||||
```
|
||||
|
||||
Verify that Crossplane creates a new Composition revision:
|
||||
```shell
|
||||
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME REVISION CHANNEL
|
||||
myvpcs.aws.example.upbound.io-727b3c8 2 staging
|
||||
myvpcs.aws.example.upbound.io-ad265bc 1 dev
|
||||
```
|
||||
|
||||
Verify that Crossplane assigns the Composite Resources `vpc-auto` and `vpc-staging` to Composite revision:2.
|
||||
XRs `vpc-man` and `vpc-dev` are still assigned to the original revision:1:
|
||||
|
||||
```shell
|
||||
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME SYNCED REVISION POLICY MATCHLABEL
|
||||
vpc-auto True myvpcs.aws.example.upbound.io-727b3c8 Automatic <none>
|
||||
vpc-dev True myvpcs.aws.example.upbound.io-ad265bc Automatic map[channel:dev]
|
||||
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
|
||||
vpc-staging True myvpcs.aws.example.upbound.io-727b3c8 Automatic map[channel:staging]
|
||||
```
|
||||
|
||||
{{< hint "note" >}}
|
||||
`vpc-auto` always use the latest Revision.
|
||||
`vpc-staging` now matches the label applied to Revision revision:2.
|
||||
{{< /hint >}}
|
||||
|
||||
#### Update Composition Spec and Label
|
||||
Update the Composition to disable DNS support in the VPC and change the label from `staging` back to `dev`.
|
||||
|
||||
Apply the following changes to update the `Composition` spec and label:
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
labels:
|
||||
channel: dev
|
||||
name: myvpcs.aws.example.upbound.io
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: crossplane-system
|
||||
compositeTypeRef:
|
||||
apiVersion: aws.example.upbound.io/v1alpha1
|
||||
kind: MyVPC
|
||||
resources:
|
||||
- base:
|
||||
apiVersion: ec2.aws.upbound.io/v1beta1
|
||||
kind: VPC
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-west-1
|
||||
cidrBlock: 192.168.0.0/16
|
||||
enableDnsSupport: false
|
||||
enableDnsHostnames: true
|
||||
name: my-vcp
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
```shell
|
||||
composition.apiextensions.crossplane.io/myvpcs.aws.example.upbound.io configured
|
||||
```
|
||||
|
||||
Verify that Crossplane creates a new Composition revision:
|
||||
|
||||
```shell
|
||||
kubectl get compositionrevisions -o="custom-columns=NAME:.metadata.name,REVISION:.spec.revision,CHANNEL:.metadata.labels.channel"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME REVISION CHANNEL
|
||||
myvpcs.aws.example.upbound.io-727b3c8 2 staging
|
||||
myvpcs.aws.example.upbound.io-ad265bc 1 dev
|
||||
myvpcs.aws.example.upbound.io-f81c553 3 dev
|
||||
```
|
||||
|
||||
{{< hint "note" >}}
|
||||
Changing the label and the spec values simultaneously is critical for deploying new changes to the `dev` channel.
|
||||
{{< /hint >}}
|
||||
|
||||
Verify Crossplane assigns the Composite Resources `vpc-auto` and `vpc-dev` to Composite revision:3.
|
||||
`vpc-staging` is assigned to revision:2, and `vpc-man` is still assigned to the original revision:1:
|
||||
|
||||
```shell
|
||||
kubectl get composite -o="custom-columns=NAME:.metadata.name,SYNCED:.status.conditions[0].status,REVISION:.spec.compositionRevisionRef.name,POLICY:.spec.compositionUpdatePolicy,MATCHLABEL:.spec.compositionRevisionSelector.matchLabels"
|
||||
```
|
||||
Expected Output:
|
||||
```shell
|
||||
NAME SYNCED REVISION POLICY MATCHLABEL
|
||||
vpc-auto True myvpcs.aws.example.upbound.io-f81c553 Automatic <none>
|
||||
vpc-dev True myvpcs.aws.example.upbound.io-f81c553 Automatic map[channel:dev]
|
||||
vpc-man True myvpcs.aws.example.upbound.io-ad265bc Manual <none>
|
||||
vpc-staging True myvpcs.aws.example.upbound.io-727b3c8 Automatic map[channel:staging]
|
||||
```
|
||||
|
||||
|
||||
{{< hint "note" >}}
|
||||
`vpc-dev` matches the updated label applied to Revision revision:3.
|
||||
`vpc-staging` matches the label applied to Revision revision:2.
|
||||
{{< /hint >}}
|
||||
|
||||
|
||||
[composition-type]: {{<ref "../../master/concepts/compositions" >}}
|
||||
[Compositions]: {{<ref "../../master/concepts/compositions" >}}
|
||||
[canary]: https://martinfowler.com/bliki/CanaryRelease.html
|
||||
[install-guide]: {{<ref "../../master/software/install" >}}
|
|
@ -748,7 +748,7 @@ details.
|
|||
This section discusses creating Kubernetes secrets.
|
||||
Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/).
|
||||
|
||||
Read the [external secrets store guide]({{<ref "/knowledge-base/integrations/vault-as-secret-store">}}) for more information on using Crossplane
|
||||
Read the [external secrets store guide]({{<ref "../guides/vault-as-secret-store">}}) for more information on using Crossplane
|
||||
with an external secret store.
|
||||
{{</hint >}}
|
||||
|
||||
|
@ -958,7 +958,7 @@ for more information on restricting secret keys.
|
|||
{{< /hint >}}
|
||||
|
||||
For more information on connection secrets read the
|
||||
[Connection Secrets knowledge base article]({{<ref "/knowledge-base/guides/connection-details">}}).
|
||||
[Connection Secrets knowledge base article]({{<ref "connection-details">}}).
|
||||
|
||||
{{<hint "warning">}}
|
||||
You can't change the
|
||||
|
@ -973,7 +973,7 @@ recreate the Composition to change the
|
|||
#### Save connection details to an external secret store
|
||||
|
||||
Crossplane
|
||||
[External Secret Stores]({{<ref "/knowledge-base/integrations/vault-as-secret-store" >}})
|
||||
[External Secret Stores]({{<ref "../guides/vault-as-secret-store" >}})
|
||||
write secrets and connection details to external secret stores like HashiCorp
|
||||
Vault.
|
||||
|
||||
|
@ -1018,7 +1018,7 @@ spec:
|
|||
# Removed for brevity
|
||||
```
|
||||
|
||||
For more details read the [External Secret Stores]({{<ref "/knowledge-base/integrations/vault-as-secret-store" >}})
|
||||
For more details read the [External Secret Stores]({{<ref "../guides/vault-as-secret-store" >}})
|
||||
integration guide.
|
||||
|
||||
### Resource readiness checks
|
||||
|
|
|
@ -0,0 +1,607 @@
|
|||
---
|
||||
title: Connection Details
|
||||
weight: 110
|
||||
description: "How to create and manage connection details across Crossplane managed resources, composite resources, Compositions and Claims"
|
||||
---
|
||||
|
||||
Using connection details in Crossplane requires the following components:
|
||||
* Defining the `writeConnectionSecretToRef.name` in a [Claim]({{<ref "/master/concepts/claims#claim-connection-secrets">}}).
|
||||
* Defining the `writeConnectionSecretsToNamespace` value in the [Composition]({{<ref "/master/concepts/compositions#composite-resource-combined-secret">}}).
|
||||
* Define the `writeConnectionSecretToRef` name and namespace for each resource in the
|
||||
[Composition]({{<ref "/master/concepts/compositions#composed-resource-secrets">}}).
|
||||
* Define the list of secret keys produced by each composed resource with `connectionDetails` in the
|
||||
[Composition]({{<ref "/master/concepts/compositions#define-secret-keys">}}).
|
||||
* Optionally, define the `connectionSecretKeys` in a
|
||||
[CompositeResourceDefinition]({{<ref "/master/concepts/composite-resource-definitions#manage-connection-secrets">}}).
|
||||
|
||||
{{<hint "note">}}
|
||||
This guide discusses creating Kubernetes secrets.
|
||||
Crossplane also supports using external secret stores like [HashiCorp Vault](https://www.vaultproject.io/).
|
||||
|
||||
Read the [external secrets store guide]({{<ref "../guides/vault-as-secret-store">}}) for more information on using Crossplane
|
||||
with an external secret store.
|
||||
{{</hint >}}
|
||||
|
||||
## Background
|
||||
When a [Provider]({{<ref "/master/concepts/providers">}}) creates a managed
|
||||
resource, the resource may generate resource-specific details. These details can include
|
||||
usernames, passwords or connection details like an IP address.
|
||||
|
||||
Crossplane refers to this information as the _connection details_ or
|
||||
_connection secrets_.
|
||||
|
||||
The Provider
|
||||
defines what information to present as a _connection
|
||||
detail_ from a managed resource.
|
||||
|
||||
<!-- vale gitlab.SentenceLength = NO -->
|
||||
<!-- wordy because of type names -->
|
||||
When a managed resource is part of a
|
||||
[Composition]({{<ref "/master/concepts/compositions">}}), the Composition,
|
||||
[Composite Resource Definition]({{<ref "/master/concepts/composite-resource-definitions">}})
|
||||
and optionally, the
|
||||
[Claim]({{<ref "/master/concepts/claims">}}) define what details are visible
|
||||
and where they're stored.
|
||||
<!-- vale gitlab.SentenceLength = YES -->
|
||||
|
||||
{{<hint "note">}}
|
||||
All the following examples use the same set of Compositions,
|
||||
CompositeResourceDefinitions and Claims.
|
||||
|
||||
All examples rely on
|
||||
[Upbound provider-aws-iam](https://marketplace.upbound.io/providers/upbound/provider-aws-iam/)
|
||||
to create resources.
|
||||
|
||||
{{<expand "Reference Composition" >}}
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: xsecrettest.example.org
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: other-namespace
|
||||
compositeTypeRef:
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: XSecretTest
|
||||
resources:
|
||||
- name: key
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
spec:
|
||||
forProvider:
|
||||
userSelector:
|
||||
matchControllerRef: true
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key1
|
||||
connectionDetails:
|
||||
- fromConnectionSecretKey: username
|
||||
- fromConnectionSecretKey: password
|
||||
- fromConnectionSecretKey: attribute.secret
|
||||
- fromConnectionSecretKey: attribute.ses_smtp_password_v4
|
||||
patches:
|
||||
- fromFieldPath: "metadata.uid"
|
||||
toFieldPath: "spec.writeConnectionSecretToRef.name"
|
||||
transforms:
|
||||
- type: string
|
||||
string:
|
||||
fmt: "%s-secret1"
|
||||
- name: user
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: User
|
||||
spec:
|
||||
forProvider: {}
|
||||
- name: user2
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: User
|
||||
metadata:
|
||||
labels:
|
||||
docs.crossplane.io: user
|
||||
spec:
|
||||
forProvider: {}
|
||||
- name: key2
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
spec:
|
||||
forProvider:
|
||||
userSelector:
|
||||
matchLabels:
|
||||
docs.crossplane.io: user
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key2
|
||||
connectionDetails:
|
||||
- name: key2-user
|
||||
fromConnectionSecretKey: username
|
||||
- name: key2-password
|
||||
fromConnectionSecretKey: password
|
||||
- name: key2-secret
|
||||
fromConnectionSecretKey: attribute.secret
|
||||
- name: key2-smtp
|
||||
fromConnectionSecretKey: attribute.ses_smtp_password_v4
|
||||
patches:
|
||||
- fromFieldPath: "metadata.uid"
|
||||
toFieldPath: "spec.writeConnectionSecretToRef.name"
|
||||
transforms:
|
||||
- type: string
|
||||
string:
|
||||
fmt: "%s-secret2"
|
||||
```
|
||||
{{</expand >}}
|
||||
|
||||
{{<expand "Reference CompositeResourceDefinition" >}}
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: xsecrettests.example.org
|
||||
spec:
|
||||
group: example.org
|
||||
connectionSecretKeys:
|
||||
- username
|
||||
- password
|
||||
- attribute.secret
|
||||
- attribute.ses_smtp_password_v4
|
||||
- key2-user
|
||||
- key2-pass
|
||||
- key2-secret
|
||||
- key2-smtp
|
||||
names:
|
||||
kind: XSecretTest
|
||||
plural: xsecrettests
|
||||
claimNames:
|
||||
kind: SecretTest
|
||||
plural: secrettests
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
referenceable: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
```
|
||||
{{</ expand >}}
|
||||
|
||||
{{<expand "Reference Claim" >}}
|
||||
```yaml
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: SecretTest
|
||||
metadata:
|
||||
name: test-secrets
|
||||
namespace: default
|
||||
spec:
|
||||
writeConnectionSecretToRef:
|
||||
name: my-access-key-secret
|
||||
```
|
||||
{{</expand >}}
|
||||
{{</hint >}}
|
||||
|
||||
## Connection secrets in a managed resource
|
||||
|
||||
<!-- vale gitlab.Substitutions = NO -->
|
||||
<!-- vale gitlab.SentenceLength = NO -->
|
||||
<!-- under 25 words -->
|
||||
When a managed resource creates connection secrets, Crossplane can write the
|
||||
secrets to a
|
||||
[Kubernetes secret]({{<ref "/master/concepts/managed-resources#publish-secrets-to-kubernetes">}})
|
||||
or an
|
||||
[external secret store]({{<ref "/master/concepts/managed-resources#publish-secrets-to-an-external-secrets-store">}}).
|
||||
<!-- vale gitlab.SentenceLength = YES -->
|
||||
<!-- vale gitlab.Substitutions = YES -->
|
||||
|
||||
Creating an individual managed resource shows the connection secrets the
|
||||
resource creates.
|
||||
|
||||
{{<hint "note" >}}
|
||||
Read the [managed resources]({{<ref "/master/concepts/managed-resources">}})
|
||||
documentation for more information on configuring resources and storing
|
||||
connection secrets for individual resources.
|
||||
{{< /hint >}}
|
||||
|
||||
|
||||
For example, create an
|
||||
{{<hover label="mr" line="2">}}AccessKey{{</hover>}} resource and save the
|
||||
connection secrets in a Kubernetes secret named
|
||||
{{<hover label="mr" line="12">}}my-accesskey-secret{{</hover>}}
|
||||
in the
|
||||
{{<hover label="mr" line="11">}}default{{</hover>}} namespace.
|
||||
|
||||
```yaml {label="mr"}
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
metadata:
|
||||
name: test-accesskey
|
||||
spec:
|
||||
forProvider:
|
||||
userSelector:
|
||||
matchLabels:
|
||||
docs.crossplane.io: user
|
||||
writeConnectionSecretToRef:
|
||||
namespace: default
|
||||
name: my-accesskey-secret
|
||||
```
|
||||
|
||||
View the Kubernetes secret to see the connection details from the managed
|
||||
resource.
|
||||
This includes an
|
||||
{{<hover label="mrSecret" line="11">}}attribute.secret{{</hover>}},
|
||||
{{<hover label="mrSecret" line="12">}}attribute.ses_smtp_password_v4{{</hover>}},
|
||||
{{<hover label="mrSecret" line="13">}}password{{</hover>}} and
|
||||
{{<hover label="mrSecret" line="14">}}username{{</hover>}}
|
||||
|
||||
```yaml {label="mrSecret",copy-lines="1"}
|
||||
kubectl describe secret my-accesskey-secret
|
||||
Name: my-accesskey-secret
|
||||
Namespace: default
|
||||
Labels: <none>
|
||||
Annotations: <none>
|
||||
|
||||
Type: connection.crossplane.io/v1alpha1
|
||||
|
||||
Data
|
||||
====
|
||||
attribute.secret: 40 bytes
|
||||
attribute.ses_smtp_password_v4: 44 bytes
|
||||
password: 40 bytes
|
||||
username: 20 bytes
|
||||
```
|
||||
|
||||
Compositions and CompositeResourceDefinitions require the exact names of the
|
||||
secrets generated by a resource.
|
||||
|
||||
## Connection secrets in Compositions
|
||||
|
||||
Resources in a Composition that create connection details still create a
|
||||
secret object containing their connection details.
|
||||
Crossplane also generates
|
||||
another secret object for each composite resource,
|
||||
containing the secrets from all the defined resources.
|
||||
|
||||
For example, a Composition defines two
|
||||
{{<hover label="comp1" line="9">}}AccessKey{{</hover>}}
|
||||
objects.
|
||||
Each {{<hover label="comp1" line="9">}}AccessKey{{</hover>}} writes a
|
||||
connection secrets to the {{<hover label="comp1" line="15">}}name{{</hover>}}
|
||||
inside the {{<hover label="comp1" line="14">}}namespace{{</hover>}} defined by
|
||||
the resource
|
||||
{{<hover label="comp1" line="13">}}writeConnectionSecretToRef{{</hover>}}.
|
||||
|
||||
Crossplane also creates a secret object for the entire Composition
|
||||
saved in the namespace defined by
|
||||
{{<hover label="comp1" line="4">}}writeConnectionSecretsToNamespace{{</hover>}}
|
||||
with a Crossplane generated name.
|
||||
|
||||
```yaml {label="comp1",copy-lines="none"}
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: other-namespace
|
||||
resources:
|
||||
- name: key1
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
spec:
|
||||
forProvider:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key1-secret
|
||||
- name: key2
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
spec:
|
||||
forProvider:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key2-secret
|
||||
# Removed for brevity
|
||||
```
|
||||
|
||||
After applying a Claim, view the Kubernetes secrets to see three secret objects
|
||||
created.
|
||||
|
||||
The secret
|
||||
{{<hover label="compGetSec" line="3">}}key1-secret{{</hover>}} is from the resource
|
||||
{{<hover label="comp1" line="6">}}key1{{</hover>}},
|
||||
{{<hover label="compGetSec" line="4">}}key2-secret{{</hover>}} is from the resource
|
||||
{{<hover label="comp1" line="16">}}key2{{</hover>}}.
|
||||
|
||||
Crossplane creates another secret in the namespace
|
||||
{{<hover label="compGetSec" line="5">}}other-namespace{{</hover>}} with the
|
||||
secrets from resource in the Composition.
|
||||
|
||||
|
||||
```shell {label="compGetSec",copy-lines="1"}
|
||||
kubectl get secrets -A
|
||||
NAMESPACE NAME TYPE DATA AGE
|
||||
docs key1-secret connection.crossplane.io/v1alpha1 4 4s
|
||||
docs key2-secret connection.crossplane.io/v1alpha1 4 4s
|
||||
other-namespace 70975471-c44f-4f6d-bde6-6bbdc9de1eb8 connection.crossplane.io/v1alpha1 0 6s
|
||||
```
|
||||
|
||||
Although Crossplane creates a secret object, by default, Crossplane doesn't add
|
||||
any data to the object.
|
||||
|
||||
```yaml {copy-lines="none"}
|
||||
kubectl describe secret 70975471-c44f-4f6d-bde6-6bbdc9de1eb8 -n other-namespace
|
||||
Name: 70975471-c44f-4f6d-bde6-6bbdc9de1eb8
|
||||
Namespace: other-namespace
|
||||
|
||||
Type: connection.crossplane.io/v1alpha1
|
||||
|
||||
Data
|
||||
====
|
||||
```
|
||||
|
||||
The Composition must list the connection secrets to store for each resource.
|
||||
Use the
|
||||
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}} object under
|
||||
each resource and define the secret keys the resource creates.
|
||||
|
||||
|
||||
{{<hint "warning">}}
|
||||
You can't change the
|
||||
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}
|
||||
of a Composition.
|
||||
You must delete and
|
||||
recreate the Composition to change the
|
||||
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}.
|
||||
{{</hint >}}
|
||||
|
||||
```yaml {label="comp2",copy-lines="16-20"}
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: other-namespace
|
||||
resources:
|
||||
- name: key
|
||||
base:
|
||||
apiVersion: iam.aws.upbound.io/v1beta1
|
||||
kind: AccessKey
|
||||
spec:
|
||||
forProvider:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key1
|
||||
connectionDetails:
|
||||
- fromConnectionSecretKey: username
|
||||
- fromConnectionSecretKey: password
|
||||
- fromConnectionSecretKey: attribute.secret
|
||||
- fromConnectionSecretKey: attribute.ses_smtp_password_v4
|
||||
# Removed for brevity
|
||||
```
|
||||
|
||||
After applying a Claim the composite resource secret object contains the list of
|
||||
keys listed in the
|
||||
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl describe secret -n other-namespace
|
||||
Name: b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a
|
||||
Namespace: other-namespace
|
||||
|
||||
Type: connection.crossplane.io/v1alpha1
|
||||
|
||||
Data
|
||||
====
|
||||
username: 20 bytes
|
||||
attribute.secret: 40 bytes
|
||||
attribute.ses_smtp_password_v4: 44 bytes
|
||||
password: 40 bytes
|
||||
```
|
||||
|
||||
{{<hint "important">}}
|
||||
If a key isn't listed in the
|
||||
{{<hover label="comp2" line="16">}}connectionDetails{{</hover>}}
|
||||
it isn't stored in the secret object.
|
||||
{{< /hint >}}
|
||||
|
||||
### Managing conflicting secret keys
|
||||
If resources produce conflicting keys, create a unique name with a connection
|
||||
details
|
||||
{{<hover label="comp3" line="25">}}name{{</hover>}}.
|
||||
|
||||
```yaml {label="comp3",copy-lines="none"}
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: other-namespace
|
||||
resources:
|
||||
- name: key
|
||||
base:
|
||||
kind: AccessKey
|
||||
spec:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key1
|
||||
connectionDetails:
|
||||
- fromConnectionSecretKey: username
|
||||
- name: key2
|
||||
base:
|
||||
kind: AccessKey
|
||||
spec:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key2
|
||||
connectionDetails:
|
||||
- name: key2-user
|
||||
fromConnectionSecretKey: username
|
||||
```
|
||||
|
||||
The secret object contains both keys,
|
||||
{{<hover label="comp3Sec" line="9">}}username{{</hover>}}
|
||||
and
|
||||
{{<hover label="comp3Sec" line="10">}}key2-user{{</hover>}}
|
||||
|
||||
```shell {label="comp3Sec",copy-lines="1"}
|
||||
kubectl describe secret -n other-namespace
|
||||
Name: b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a
|
||||
Namespace: other-namespace
|
||||
|
||||
Type: connection.crossplane.io/v1alpha1
|
||||
|
||||
Data
|
||||
====
|
||||
username: 20 bytes
|
||||
key2-user: 20 bytes
|
||||
# Removed for brevity.
|
||||
```
|
||||
|
||||
## Connection secrets in Composite Resource Definitions
|
||||
|
||||
The CompositeResourceDefinition (`XRD`), can restrict which secrets keys are
|
||||
put in the combined secret and provided to a Claim.
|
||||
|
||||
By default an XRD writes all secret keys listed in the composed resource
|
||||
`connectionDetails` to the combined secret object.
|
||||
|
||||
Limit the keys passed to the combined secret object and Claims with a
|
||||
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} object.
|
||||
|
||||
Inside the {{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} list
|
||||
the secret key names to create. Crossplane only adds the keys listed to the
|
||||
combined secret.
|
||||
|
||||
{{<hint "warning">}}
|
||||
You can't change the
|
||||
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}} of an XRD.
|
||||
You must delete and
|
||||
recreate the XRD to change the
|
||||
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}}.
|
||||
{{</hint >}}
|
||||
|
||||
For example, an XRD may restrict the secrets to only the
|
||||
{{<hover label="xrd" line="5">}}username{{</hover>}},
|
||||
{{<hover label="xrd" line="6">}}password{{</hover>}} and custom named
|
||||
{{<hover label="xrd" line="7">}}key2-user{{</hover>}} keys.
|
||||
|
||||
```yaml {label="xrd",copy-lines="4-12"}
|
||||
kind: CompositeResourceDefinition
|
||||
spec:
|
||||
# Removed for brevity.
|
||||
connectionSecretKeys:
|
||||
- username
|
||||
- password
|
||||
- key2-user
|
||||
```
|
||||
|
||||
The secret from an individual resource contains all the resources detailed in
|
||||
the Composition's `connectionDetails`.
|
||||
|
||||
```shell {label="xrdSec",copy-lines="1"}
|
||||
kubectl describe secret key1 -n docs
|
||||
Name: key1
|
||||
Namespace: docs
|
||||
|
||||
Data
|
||||
====
|
||||
password: 40 bytes
|
||||
username: 20 bytes
|
||||
attribute.secret: 40 bytes
|
||||
attribute.ses_smtp_password_v4: 44 bytes
|
||||
```
|
||||
|
||||
The Claim's secret only contains the
|
||||
keys allowed by the XRD
|
||||
{{<hover label="xrd" line="4">}}connectionSecretKeys{{</hover>}}
|
||||
fields.
|
||||
|
||||
```shell {label="xrdSec2",copy-lines="2"}
|
||||
kubectl describe secret my-access-key-secret
|
||||
Name: my-access-key-secret
|
||||
|
||||
Data
|
||||
====
|
||||
key2-user: 20 bytes
|
||||
password: 40 bytes
|
||||
username: 20 bytes
|
||||
```
|
||||
|
||||
## Secret objects
|
||||
Compositions create a secret object for each resource and an extra secret
|
||||
containing all the secrets from all resources.
|
||||
|
||||
Crossplane saves the resource secret objects in the location defined by the
|
||||
resource's
|
||||
{{<hover label="comp4" line="11">}}writeConnectionSecretToRef{{</hover>}}.
|
||||
|
||||
Crossplane saves the combined secret with a Crossplane generated name in the
|
||||
namespace defined in the Composition's
|
||||
{{<hover label="comp4" line="4">}}writeConnectionSecretsToNamespace{{</hover>}}.
|
||||
|
||||
```yaml {label="comp4",copy-lines="none"}
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
spec:
|
||||
writeConnectionSecretsToNamespace: other-namespace
|
||||
resources:
|
||||
- name: key
|
||||
base:
|
||||
kind: AccessKey
|
||||
spec:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key1
|
||||
connectionDetails:
|
||||
- fromConnectionSecretKey: username
|
||||
- name: key2
|
||||
base:
|
||||
kind: AccessKey
|
||||
spec:
|
||||
# Removed for brevity
|
||||
writeConnectionSecretToRef:
|
||||
namespace: docs
|
||||
name: key2
|
||||
connectionDetails:
|
||||
- name: key2-user
|
||||
fromConnectionSecretKey: username
|
||||
```
|
||||
|
||||
If a Claim uses a secret, it's stored in the same namespace as the Claim with
|
||||
the name defined in the Claim's
|
||||
{{<hover label="claim3" line="7">}}writeConnectionSecretToRef{{</hover>}}.
|
||||
|
||||
```yaml {label="claim3",copy-lines="none"}
|
||||
apiVersion: example.org/v1alpha1
|
||||
kind: SecretTest
|
||||
metadata:
|
||||
name: test-secrets
|
||||
namespace: default
|
||||
spec:
|
||||
writeConnectionSecretToRef:
|
||||
name: my-access-key-secret
|
||||
```
|
||||
|
||||
After applying the Claim Crossplane creates the following secrets:
|
||||
* The Claim's secret, {{<hover label="allSec" line="3">}}my-access-key-secret{{</hover>}}
|
||||
in the Claim's {{<hover label="claim3" line="5">}}namespace{{</hover>}}.
|
||||
* The first resource's secret object, {{<hover label="allSec" line="4">}}key1{{</hover>}}.
|
||||
* The second resource's secret object, {{<hover label="allSec" line="5">}}key2{{</hover>}}.
|
||||
* The composite resource secret object in the
|
||||
{{<hover label="allSec" line="6">}}other-namespace{{</hover>}} defined by the
|
||||
Composition's `writeConnectionSecretsToNamespace`.
|
||||
|
||||
|
||||
```shell {label="allSec",copy-lines="none"}
|
||||
kubectl get secret -A
|
||||
NAMESPACE NAME TYPE DATA AGE
|
||||
default my-access-key-secret connection.crossplane.io/v1alpha1 8 29m
|
||||
docs key1 connection.crossplane.io/v1alpha1 4 31m
|
||||
docs key2 connection.crossplane.io/v1alpha1 4 31m
|
||||
other-namespace b0dc71f8-2688-4ebc-818a-bbad6a2c4f9a connection.crossplane.io/v1alpha1 8 31m
|
||||
```
|
|
@ -357,7 +357,7 @@ Crossplane supports the following policies:
|
|||
| `Create` | If the external resource doesn't exist, Crossplane creates it based on the managed resource settings. |
|
||||
| `Delete` | Crossplane can delete the external resource when deleting the managed resource. |
|
||||
| `LateInitialize` | Crossplane initializes some external resource settings not defined in the `spec.forProvider` of the managed resource. See [the late initialization]({{<ref "./managed-resources#late-initialization" >}}) section for more details. |
|
||||
| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{<ref "/knowledge-base/guides/import-existing-resources#import-resources-automatically">}}). |
|
||||
| `Observe` | Crossplane only observes the resource and doesn't make any changes. Used for [observe only resources]({{<ref "../guides/import-existing-resources#import-resources-automatically">}}). |
|
||||
| `Update` | Crossplane changes the external resource when changing the managed resource. |
|
||||
{{</table >}}
|
||||
|
||||
|
@ -373,7 +373,7 @@ The following is a list of common policy combinations:
|
|||
| {{<check>}} | | {{<check>}} | {{<check>}} | | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't apply changes to the external resource after creation. |
|
||||
| {{<check>}} | | | {{<check>}} | {{<check>}} | Crossplane doesn't delete the external resource when deleting the managed resource. Crossplane doesn't import any settings from the external resource. |
|
||||
| {{<check>}} | | | {{<check>}} | | Crossplane creates the external resource but doesn't apply any changes to the external resource or managed resource. Crossplane can't delete the resource. |
|
||||
| | | | {{<check>}} | | Crossplane only observes a resource. Used for [observe only resources]({{<ref "/knowledge-base/guides/import-existing-resources#import-resources-automatically">}}). |
|
||||
| | | | {{<check>}} | | Crossplane only observes a resource. Used for [observe only resources]({{<ref "../guides/import-existing-resources#import-resources-automatically">}}). |
|
||||
| | | | | | No policy set. An alternative method for [pausing](#paused) a resource. |
|
||||
{{< /table >}}
|
||||
|
||||
|
@ -567,7 +567,7 @@ metadata:
|
|||
|
||||
{{<hint "tip" >}}
|
||||
Read the
|
||||
[Vault as an External Secrets Store]({{<ref "knowledge-base/integrations/vault-as-secret-store">}})
|
||||
[Vault as an External Secrets Store]({{<ref "../guides/vault-as-secret-store">}})
|
||||
guide for details on using StoreConfig objects.
|
||||
{{< /hint >}}
|
||||
|
||||
|
|
|
@ -404,7 +404,7 @@ If you remove the Provider first, you must manually delete external resources
|
|||
through your cloud provider. Managed resources must be manually deleted by
|
||||
removing their finalizers.
|
||||
|
||||
For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{<ref "/knowledge-base/guides/troubleshoot#deleting-when-a-resource-hangs" >}}).
|
||||
For more information on deleting abandoned resources read the [Crossplane troubleshooting guide]({{<ref "../guides/troubleshoot-crossplane#deleting-when-a-resource-hangs" >}}).
|
||||
{{< /hint >}}
|
||||
|
||||
## Verify a Provider
|
||||
|
@ -593,12 +593,12 @@ replacement for Controller configuration and is available in v1.14+.
|
|||
|
||||
Applying a Crossplane `ControllerConfig` to a Provider changes the settings of
|
||||
the Provider's pod. The
|
||||
[Crossplane ControllerConfig schema](https://doc.crds.dev/github.com/crossplane/crossplane/pkg.crossplane.io/ControllerConfig/v1alpha1)
|
||||
[Crossplane ControllerConfig schema]({{< ref "../api#ControllerConfig-spec" >}})
|
||||
defines the supported set of ControllerConfig settings.
|
||||
|
||||
The most common use case for ControllerConfigs are providing `args` to a
|
||||
Provider's pod enabling optional services. For example, enabling
|
||||
[external secret stores](https://docs.crossplane.io/knowledge-base/integrations/vault-as-secret-store/#enable-external-secret-stores-in-the-provider)
|
||||
[external secret stores]({{< ref "../guides/vault-as-secret-store#enable-external-secret-stores-in-the-provider" >}})
|
||||
for a Provider.
|
||||
|
||||
Each Provider determines their supported set of `args`.
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Guides
|
||||
weight: 100
|
||||
description: Crossplane integrations and detailed examples.
|
||||
---
|
|
@ -0,0 +1,216 @@
|
|||
---
|
||||
title: Configuring Crossplane with Argo CD
|
||||
weight: 270
|
||||
---
|
||||
|
||||
[Argo CD](https://argoproj.github.io/cd/) and [Crossplane](https://crossplane.io)
|
||||
are a great combination. Argo CD provides GitOps while Crossplane turns any Kubernetes
|
||||
cluster into a Universal Control Plane for all of your resources. There are
|
||||
configuration details required in order for the two to work together properly.
|
||||
This doc will help you understand these requirements. It is recommended to use
|
||||
Argo CD version 2.4.8 or later with Crossplane.
|
||||
|
||||
Argo CD synchronizes Kubernetes resource manifests stored in a Git repository
|
||||
with those running in a Kubernetes cluster (GitOps). There are different ways to configure
|
||||
how Argo CD tracks resources. With Crossplane, you need to configure Argo CD
|
||||
to use Annotation based resource tracking. See the [Argo CD docs](https://argo-cd.readthedocs.io/en/latest/user-guide/resource_tracking/) for additional detail.
|
||||
|
||||
### Configuring Argo CD with Crossplane
|
||||
|
||||
#### Set Resource Tracking Method
|
||||
|
||||
In order for Argo CD to correctly track Application resources that contain Crossplane related objects it needs
|
||||
to be configured to use the annotation mechanism.
|
||||
|
||||
To configure it, edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace` as such:
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
application.resourceTrackingMethod: annotation
|
||||
```
|
||||
|
||||
#### Set Health Status
|
||||
|
||||
Argo CD has a built-in health assessment for Kubernetes resources. Some checks are supported by the community directly
|
||||
in Argo's [repository](https://github.com/argoproj/argo-cd/tree/master/resource_customizations). For example the `Provider`
|
||||
from `pkg.crossplane.io` has already been declared which means there no further configuration needed.
|
||||
|
||||
Argo CD also enable customising these checks per instance, and that's the mechanism used to provide support
|
||||
of Provider's CRDs.
|
||||
|
||||
To configure it, edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace`.
|
||||
{{<hint "note">}}
|
||||
{{<hover label="argocfg" line="22">}} ProviderConfig{{</hover>}} may have no status or a `status.users` field.
|
||||
{{</hint>}}
|
||||
```yaml {label="argocfg"}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
application.resourceTrackingMethod: annotation
|
||||
resource.customizations: |
|
||||
"*.upbound.io/*":
|
||||
health.lua: |
|
||||
health_status = {
|
||||
status = "Progressing",
|
||||
message = "Provisioning ..."
|
||||
}
|
||||
|
||||
local function contains (table, val)
|
||||
for i, v in ipairs(table) do
|
||||
if v == val then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local has_no_status = {
|
||||
"ProviderConfig",
|
||||
"ProviderConfigUsage"
|
||||
}
|
||||
|
||||
if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is up-to-date."
|
||||
return health_status
|
||||
end
|
||||
|
||||
if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
|
||||
if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is in use."
|
||||
return health_status
|
||||
end
|
||||
return health_status
|
||||
end
|
||||
|
||||
for i, condition in ipairs(obj.status.conditions) do
|
||||
if condition.type == "LastAsyncOperation" then
|
||||
if condition.status == "False" then
|
||||
health_status.status = "Degraded"
|
||||
health_status.message = condition.message
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
|
||||
if condition.type == "Synced" then
|
||||
if condition.status == "False" then
|
||||
health_status.status = "Degraded"
|
||||
health_status.message = condition.message
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
|
||||
if condition.type == "Ready" then
|
||||
if condition.status == "True" then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is up-to-date."
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return health_status
|
||||
|
||||
"*.crossplane.io/*":
|
||||
health.lua: |
|
||||
health_status = {
|
||||
status = "Progressing",
|
||||
message = "Provisioning ..."
|
||||
}
|
||||
|
||||
local function contains (table, val)
|
||||
for i, v in ipairs(table) do
|
||||
if v == val then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local has_no_status = {
|
||||
"Composition",
|
||||
"CompositionRevision",
|
||||
"DeploymentRuntimeConfig",
|
||||
"ControllerConfig",
|
||||
"ProviderConfig",
|
||||
"ProviderConfigUsage"
|
||||
}
|
||||
if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is up-to-date."
|
||||
return health_status
|
||||
end
|
||||
|
||||
if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then
|
||||
if obj.kind == "ProviderConfig" and obj.status.users ~= nil then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is in use."
|
||||
return health_status
|
||||
end
|
||||
return health_status
|
||||
end
|
||||
|
||||
for i, condition in ipairs(obj.status.conditions) do
|
||||
if condition.type == "LastAsyncOperation" then
|
||||
if condition.status == "False" then
|
||||
health_status.status = "Degraded"
|
||||
health_status.message = condition.message
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
|
||||
if condition.type == "Synced" then
|
||||
if condition.status == "False" then
|
||||
health_status.status = "Degraded"
|
||||
health_status.message = condition.message
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
|
||||
if contains({"Ready", "Healthy", "Offered", "Established"}, condition.type) then
|
||||
if condition.status == "True" then
|
||||
health_status.status = "Healthy"
|
||||
health_status.message = "Resource is up-to-date."
|
||||
return health_status
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return health_status
|
||||
```
|
||||
|
||||
#### Set Resource Exclusion
|
||||
|
||||
Crossplane providers generates a `ProviderConfigUsage` for each of the managed resource (MR) it handles. This resource
|
||||
enable representing the relationship between MR and a ProviderConfig so that the controller can use it as finalizer when a
|
||||
ProviderConfig is deleted. End-users of Crossplane are not expected to interact with this resource.
|
||||
|
||||
Argo CD UI reactivity can be impacted as the number of resource and types grow. To help keep this number low we
|
||||
recommend hiding all `ProviderConfigUsage` resources from Argo CD UI.
|
||||
|
||||
To configure resource exclusion edit the `argocd-cm` `ConfigMap` in the `argocd` `Namespace` as such:
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
data:
|
||||
resource.exclusions: |
|
||||
- apiGroups:
|
||||
- "*"
|
||||
kinds:
|
||||
- ProviderConfigUsage
|
||||
```
|
||||
|
||||
The use of `"*"` as apiGroups will enable the mechanism for all Crossplane Providers.
|
||||
|
||||
#### Increase K8s Client QPS
|
||||
|
||||
As the number of CRDs grow on a control plane it will increase the amount of queries Argo CD Application Controller
|
||||
needs to send to the Kubernetes API. If this is the case you can increase the rate limits of the Argo CD Kubernetes client.
|
||||
|
||||
Set the environment variable `ARGOCD_K8S_CLIENT_QPS` to `300` for improved compatibility with a large number of CRDs.
|
||||
|
||||
The default value of `ARGOCD_K8S_CLIENT_QPS` is 50, modifying the value will also update `ARGOCD_K8S_CLIENT_BURST` as it
|
||||
is default to `ARGOCD_K8S_CLIENT_QPS` x 2.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Disaster Recovery with Crossplane
|
||||
weight: 10
|
||||
---
|
||||
|
||||
AWS wrote a guide covering disaster recovery with Crossplane. The guide covers
|
||||
using Crossplane to provision resources and Velero for Kubernetes backup and
|
||||
recovery.
|
||||
|
||||
[Read the guide on AWS](https://aws.amazon.com/blogs/opensource/disaster-recovery-when-using-crossplane-for-infrastructure-provisioning-on-aws/).
|
|
@ -0,0 +1,285 @@
|
|||
---
|
||||
title: Import Existing Resources
|
||||
weight: 200
|
||||
---
|
||||
|
||||
If you have resources that are already provisioned in a Provider,
|
||||
you can import them as managed resources and let Crossplane manage them.
|
||||
A managed resource's [`managementPolicies`]({{<ref "/v1.13/concepts/managed-resources#managementpolicies">}})
|
||||
field enables importing external resources into Crossplane.
|
||||
|
||||
Crossplane can import resources either [manually]({{<ref "#import-resources-manually">}})
|
||||
or [automatically]({{<ref "#import-resources-automatically">}}).
|
||||
|
||||
## Import resources manually
|
||||
|
||||
Crossplane can discover and import existing Provider resources by matching the
|
||||
`crossplane.io/external-name` annotation in a managed resource.
|
||||
|
||||
To import an existing external resource in a Provider, create a new managed
|
||||
resource with the `crossplane.io/external-name` annotation. Set the annotation
|
||||
value to the name of the resource in the Provider.
|
||||
|
||||
For example, to import an existing GCP Network named
|
||||
{{<hover label="annotation" line="5">}}my-existing-network{{</hover>}},
|
||||
create a new managed resource and use the
|
||||
{{<hover label="annotation" line="5">}}my-existing-network{{</hover>}} in the
|
||||
annotation.
|
||||
|
||||
```yaml {label="annotation",copy-lines="none"}
|
||||
apiVersion: compute.gcp.crossplane.io/v1beta1
|
||||
kind: Network
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/external-name: my-existing-network
|
||||
```
|
||||
|
||||
The {{<hover label="name" line="5">}}metadata.name{{</hover>}}
|
||||
field can be anything you want. For example,
|
||||
{{<hover label="name" line="5">}}imported-network{{</hover>}}.
|
||||
|
||||
{{< hint "note" >}}
|
||||
This name is the
|
||||
name of the Kubernetes object. It's not related to the resource name inside the
|
||||
Provider.
|
||||
{{< /hint >}}
|
||||
|
||||
```yaml {label="name",copy-lines="none"}
|
||||
apiVersion: compute.gcp.crossplane.io/v1beta1
|
||||
kind: Network
|
||||
metadata:
|
||||
name: imported-network
|
||||
annotations:
|
||||
crossplane.io/external-name: my-existing-network
|
||||
```
|
||||
|
||||
Leave the
|
||||
{{<hover label="fp" line="8">}}spec.forProvider{{</hover>}} field empty.
|
||||
Crossplane imports the settings and automatically applies them to the managed
|
||||
resource.
|
||||
|
||||
{{< hint "important" >}}
|
||||
If the managed resource has _required_ fields in the
|
||||
{{<hover label="fp" line="8">}}spec.forProvider{{</hover>}} you must add it to
|
||||
the `forProvider` field.
|
||||
|
||||
The values of those fields must match what's inside the Provider or Crossplane
|
||||
overwrites the existing values.
|
||||
{{< /hint >}}
|
||||
|
||||
```yaml {label="fp",copy-lines="all"}
|
||||
apiVersion: compute.gcp.crossplane.io/v1beta1
|
||||
kind: Network
|
||||
metadata:
|
||||
name: imported-network
|
||||
annotations:
|
||||
crossplane.io/external-name: my-existing-network
|
||||
spec:
|
||||
forProvider: {}
|
||||
```
|
||||
|
||||
|
||||
Crossplane now controls and manages this imported resource. Any changes to the
|
||||
managed resource `spec` changes the external resource.
|
||||
|
||||
## Import resources automatically
|
||||
|
||||
Automatically import external resources with an `Observe` [management policy]({{<ref "/v1.13/concepts/managed-resources#managementpolicies">}}).
|
||||
|
||||
Crossplane imports observe only resources but never changes or deletes the
|
||||
resources.
|
||||
|
||||
{{<hint "important" >}}
|
||||
The managed resource `managementPolicies` option is a beta feature.
|
||||
|
||||
The Provider determines support for management policies.
|
||||
Refer to the Provider's documentation to see if the Provider supports
|
||||
management policies.
|
||||
{{< /hint >}}
|
||||
|
||||
<!-- vale off -->
|
||||
### Apply the Observe management policy
|
||||
<!-- vale on -->
|
||||
|
||||
Create a new managed resource matching the
|
||||
{{<hover label="oo-policy" line="1">}}apiVersion{{</hover>}} and
|
||||
{{<hover label="oo-policy" line="2">}}kind{{</hover>}} of the resource
|
||||
to import and add
|
||||
{{<hover label="oo-policy" line="4">}}managementPolicies: ["Observe"]{{</hover>}} to the
|
||||
{{<hover label="oo-policy" line="3">}}spec{{</hover>}}
|
||||
|
||||
For example, to import a GCP SQL DatabaseInstance, create a new resource with
|
||||
the {{<hover label="oo-policy" line="4">}}managementPolicies: ["Observe"]{{</hover>}}
|
||||
set.
|
||||
```yaml {label="oo-policy",copy-lines="none"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
spec:
|
||||
managementPolicies: ["Observe"]
|
||||
```
|
||||
|
||||
### Add the external-name annotation
|
||||
Add the {{<hover label="oo-ex-name" line="5">}}crossplane.io/external-name{{</hover>}}
|
||||
annotation for the resource. This name must match the name inside the Provider.
|
||||
|
||||
For example, for a GCP database named
|
||||
{{<hover label="oo-ex-name" line="5">}}my-external-database{{</hover>}}, apply
|
||||
the
|
||||
{{<hover label="oo-ex-name" line="5">}}crossplane.io/external-name{{</hover>}}
|
||||
annotation with the value
|
||||
{{<hover label="oo-ex-name" line="5">}}my-external-database{{</hover>}}.
|
||||
|
||||
```yaml {label="oo-ex-name",copy-lines="none"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
metadata:
|
||||
annotations:
|
||||
crossplane.io/external-name: my-external-database
|
||||
spec:
|
||||
managementPolicies: ["Observe"]
|
||||
```
|
||||
|
||||
### Create a Kubernetes object name
|
||||
Create a {{<hover label="oo-name" line="4">}}name{{</hover>}} to use for the
|
||||
Kubernetes object.
|
||||
|
||||
For example, name the Kubernetes object
|
||||
{{<hover label="oo-name" line="4">}}my-imported-database{{</hover>}}.
|
||||
|
||||
```yaml {label="oo-name",copy-lines="none"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
metadata:
|
||||
name: my-imported-database
|
||||
annotations:
|
||||
crossplane.io/external-name: my-external-database
|
||||
spec:
|
||||
managementPolicies: ["Observe"]
|
||||
```
|
||||
|
||||
### Identify a specific external resource
|
||||
If more than one resource inside the Provider shares the same name, identify the
|
||||
specific resource with a unique
|
||||
{{<hover line="9" label="oo-region">}}spec.forProvider{{</hover>}} field.
|
||||
|
||||
For example, only import the GCP SQL database in the
|
||||
{{<hover line="10" label="oo-region">}}us-central1{{</hover>}} region.
|
||||
|
||||
```yaml {label="oo-region"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
metadata:
|
||||
name: my-imported-database
|
||||
annotations:
|
||||
crossplane.io/external-name: my-external-database
|
||||
spec:
|
||||
managementPolicies: ["Observe"]
|
||||
forProvider:
|
||||
region: "us-central1"
|
||||
```
|
||||
|
||||
### Apply the managed resource
|
||||
|
||||
Apply the new managed resource. Crossplane syncs the status of the external
|
||||
resource in the cloud with the newly created managed resource.
|
||||
|
||||
### View the discovered resource
|
||||
Crossplane discovers the managed resource and populates the
|
||||
{{<hover label="ooPopulated" line="12">}}status.atProvider{{</hover>}}
|
||||
fields with the values from the external resource.
|
||||
|
||||
```yaml {label="ooPopulated",copy-lines="none"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
metadata:
|
||||
name: my-imported-database
|
||||
annotations:
|
||||
crossplane.io/external-name: my-external-database
|
||||
spec:
|
||||
managementPolicies: ["Observe"]
|
||||
forProvider:
|
||||
region: us-central1
|
||||
status:
|
||||
atProvider:
|
||||
connectionName: crossplane-playground:us-central1:my-external-database
|
||||
databaseVersion: POSTGRES_14
|
||||
deletionProtection: true
|
||||
firstIpAddress: 35.184.74.79
|
||||
id: my-external-database
|
||||
publicIpAddress: 35.184.74.79
|
||||
region: us-central1
|
||||
# Removed for brevity
|
||||
settings:
|
||||
- activationPolicy: ALWAYS
|
||||
availabilityType: REGIONAL
|
||||
diskSize: 100
|
||||
# Removed for brevity
|
||||
pricingPlan: PER_USE
|
||||
tier: db-custom-4-26624
|
||||
version: 4
|
||||
conditions:
|
||||
- lastTransitionTime: "2023-02-22T07:16:51Z"
|
||||
reason: Available
|
||||
status: "True"
|
||||
type: Ready
|
||||
- lastTransitionTime: "2023-02-22T07:16:51Z"
|
||||
reason: ReconcileSuccess
|
||||
status: "True"
|
||||
type: Synced
|
||||
```
|
||||
<!-- vale off -->
|
||||
## Control imported ObserveOnly resources
|
||||
<!-- vale on -->
|
||||
|
||||
Crossplane can take active control of observe only imported resources by
|
||||
changing the `managementPolicies` after import.
|
||||
|
||||
Change the {{<hover label="fc" line="8">}}managementPolicies{{</hover>}} field
|
||||
of the managed resource to
|
||||
{{<hover label="fc" line="8">}}["*"]{{</hover>}}.
|
||||
|
||||
Copy any required parameter values from
|
||||
{{<hover label="fc" line="16">}}status.atProvider{{</hover>}} and provide them
|
||||
in {{<hover label="fc" line="9">}}spec.forProvider{{</hover>}}.
|
||||
|
||||
{{< hint "tip" >}}
|
||||
Manually copy the important `spec.atProvider` values to `spec.forProvider`.
|
||||
{{< /hint >}}
|
||||
|
||||
```yaml {label="fc"}
|
||||
apiVersion: sql.gcp.upbound.io/v1beta1
|
||||
kind: DatabaseInstance
|
||||
metadata:
|
||||
name: my-imported-database
|
||||
annotations:
|
||||
crossplane.io/external-name: my-external-database
|
||||
spec:
|
||||
managementPolicies: ["*"]
|
||||
forProvider:
|
||||
databaseVersion: POSTGRES_14
|
||||
region: us-central1
|
||||
settings:
|
||||
- diskSize: 100
|
||||
tier: db-custom-4-26624
|
||||
status:
|
||||
atProvider:
|
||||
databaseVersion: POSTGRES_14
|
||||
region: us-central1
|
||||
# Removed for brevity
|
||||
settings:
|
||||
- diskSize: 100
|
||||
tier: db-custom-4-26624
|
||||
# Removed for brevity
|
||||
conditions:
|
||||
- lastTransitionTime: "2023-02-22T07:16:51Z"
|
||||
reason: Available
|
||||
status: "True"
|
||||
type: Ready
|
||||
- lastTransitionTime: "2023-02-22T11:16:45Z"
|
||||
reason: ReconcileSuccess
|
||||
status: "True"
|
||||
type: Synced
|
||||
```
|
||||
|
||||
Crossplane now fully manages the imported resource. Crossplane applies any
|
||||
changes to the managed resource in the Provider's external resource.
|
|
@ -0,0 +1,323 @@
|
|||
---
|
||||
title: Multi-Tenant Crossplane
|
||||
weight: 240
|
||||
---
|
||||
|
||||
This guide describes how to use Crossplane effectively in multi-tenant
|
||||
environments by utilizing Kubernetes primitives and compatible policy
|
||||
enforcement projects in the cloud-native ecosystem.
|
||||
|
||||
## TL;DR
|
||||
|
||||
Infrastructure operators in multi-tenant Crossplane environments typically
|
||||
utilize composition and Kubernetes RBAC to define lightweight, standardized
|
||||
policies that dictate what level of self-service developers are given when
|
||||
requesting infrastructure. This is primarily achieved through exposing abstract
|
||||
resource types at the namespace scope, defining `Roles` for teams and
|
||||
individuals within that namespace, and patching the `spec.providerConfigRef` of
|
||||
the underlying managed resources so that they use a specific `ProviderConfig`
|
||||
and credentials when provisioned from each namespace. Larger organizations, or
|
||||
those with more complex environments, may choose to incorporate third-party
|
||||
policy engines, or scale to multiple Crossplane clusters. The following sections
|
||||
describe each of these scenarios in greater detail.
|
||||
|
||||
- [TL;DR](#tldr)
|
||||
- [Background](#background)
|
||||
- [Cluster-Scoped Managed Resources](#cluster-scoped-managed-resources)
|
||||
- [Namespace Scoped Claims](#namespace-scoped-claims)
|
||||
- [Single Cluster Multi-Tenancy](#single-cluster-multi-tenancy)
|
||||
- [Composition as an Isolation Mechanism](#composition-as-an-isolation-mechanism)
|
||||
- [Namespaces as an Isolation Mechanism](#namespaces-as-an-isolation-mechanism)
|
||||
- [Policy Enforcement with Open Policy Agent](#policy-enforcement-with-open-policy-agent)
|
||||
- [Multi-Cluster Multi-Tenancy](#multi-cluster-multi-tenancy)
|
||||
- [Reproducible Platforms with Configuration Packages](#reproducible-platforms-with-configuration-packages)
|
||||
- [Control Plane of Control Planes](#control-plane-of-control-planes)
|
||||
|
||||
## Background
|
||||
|
||||
Crossplane is designed to run in multi-tenant environments where many teams are
|
||||
consuming the services and abstractions provided by infrastructure operators in
|
||||
the cluster. This functionality is facilitated by two major design patterns in
|
||||
the Crossplane ecosystem.
|
||||
|
||||
### Cluster-Scoped Managed Resources
|
||||
|
||||
Typically, Crossplane providers, which supply granular [managed resources] that
|
||||
reflect an external API, authenticate by using a `ProviderConfig` object that
|
||||
points to a credentials source (such as a Kubernetes `Secret`, the `Pod`
|
||||
filesystem, or an environment variable). Then, every managed resource references
|
||||
a `ProviderConfig` that points to credentials with sufficient permissions to
|
||||
manage that resource type.
|
||||
|
||||
For example, the following `ProviderConfig` for `provider-aws` points to a
|
||||
Kubernetes `Secret` with AWS credentials.
|
||||
|
||||
```yaml
|
||||
apiVersion: aws.crossplane.io/v1beta1
|
||||
kind: ProviderConfig
|
||||
metadata:
|
||||
name: cool-aws-creds
|
||||
spec:
|
||||
credentials:
|
||||
source: Secret
|
||||
secretRef:
|
||||
namespace: crossplane-system
|
||||
name: aws-creds
|
||||
key: creds
|
||||
```
|
||||
|
||||
If a user desired for these credentials to be used to provision an
|
||||
`RDSInstance`, they would reference the `ProviderConfig` in the object manifest:
|
||||
|
||||
```yaml
|
||||
apiVersion: database.aws.crossplane.io/v1beta1
|
||||
kind: RDSInstance
|
||||
metadata:
|
||||
name: rdsmysql
|
||||
spec:
|
||||
forProvider:
|
||||
region: us-east-1
|
||||
dbInstanceClass: db.t3.medium
|
||||
masterUsername: masteruser
|
||||
allocatedStorage: 20
|
||||
engine: mysql
|
||||
engineVersion: "5.6.35"
|
||||
skipFinalSnapshotBeforeDeletion: true
|
||||
providerConfigRef:
|
||||
name: cool-aws-creds # name of ProviderConfig above
|
||||
writeConnectionSecretToRef:
|
||||
namespace: crossplane-system
|
||||
name: aws-rdsmysql-conn
|
||||
```
|
||||
|
||||
Since both the `ProviderConfig` and all managed resources are cluster-scoped,
|
||||
the RDS controller in `provider-aws` will resolve this reference by fetching the
|
||||
`ProviderConfig`, obtaining the credentials it points to, and using those
|
||||
credentials to reconcile the `RDSInstance`. This means that anyone who has been
|
||||
given [RBAC] to manage `RDSInstance` objects can use any credentials to do so.
|
||||
In practice, Crossplane assumes that only folks acting as infrastructure
|
||||
administrators or platform builders will interact directly with cluster-scoped
|
||||
resources.
|
||||
|
||||
### Namespace Scoped Claims
|
||||
|
||||
While managed resources exist at the cluster scope, composite resources, which
|
||||
are defined using a **CompositeResourceDefinition (XRD)** may exist at either
|
||||
the cluster or namespace scope. Platform builders define XRDs and
|
||||
**Compositions** that specify what granular managed resources should be created
|
||||
in response to the creation of an instance of the XRD. More information about
|
||||
this architecture can be found in the [Composition] documentation.
|
||||
|
||||
Every XRD is exposed at the cluster scope, but only those with `spec.claimNames`
|
||||
defined will have a namespace-scoped variant.
|
||||
|
||||
```yaml
|
||||
apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: xmysqlinstances.example.org
|
||||
spec:
|
||||
group: example.org
|
||||
names:
|
||||
kind: XMySQLInstance
|
||||
plural: xmysqlinstances
|
||||
claimNames:
|
||||
kind: MySQLInstance
|
||||
plural: mysqlinstances
|
||||
...
|
||||
```
|
||||
|
||||
When the example above is created, Crossplane will produce two
|
||||
[CustomResourceDefinitions]:
|
||||
1. A cluster-scoped type with `kind: XMySQLInstance`. This is referred to as a
|
||||
**Composite Resource (XR)**.
|
||||
2. A namespace-scoped type with `kind: MySQLInstance`. This is referred to as a
|
||||
**Claim (XRC)**.
|
||||
|
||||
Platform builders may choose to define an arbitrary number of Compositions that
|
||||
map to these types, meaning that creating a `MySQLInstance` in a given namespace
|
||||
can result in the creations of any set of managed resources at the cluster
|
||||
scope. For instance, creating a `MySQLInstance` could result in the creation of
|
||||
the `RDSInstance` defined above.
|
||||
|
||||
## Single Cluster Multi-Tenancy
|
||||
|
||||
Depending on the size and scope of an organization, platform teams may choose to
|
||||
run one central Crossplane control plane, or many different ones for each team
|
||||
or business unit. This section will focus on servicing multiple teams within a
|
||||
single cluster, which may or may not be one of many other Crossplane clusters in
|
||||
the organization.
|
||||
|
||||
### Composition as an Isolation Mechanism
|
||||
|
||||
While managed resources always reflect every field that the underlying provider
|
||||
API exposes, XRDs can have any schema that a platform builder chooses. The
|
||||
fields in the XRD schema can then be patched onto fields in the underlying
|
||||
managed resource defined in a Composition, essentially exposing those fields as
|
||||
configurable to the consumer of the XR or XRC.
|
||||
|
||||
This feature serves as a lightweight policy mechanism by only giving the
|
||||
consumer the ability to customize the underlying resources to the extent the
|
||||
platform builder desires. For instance, in the examples above, a platform
|
||||
builder may choose to define a `spec.location` field in the schema of the
|
||||
`XMySQLInstance` that is an enum with options `east` and `west`. In the
|
||||
Composition, those fields could map to the `RDSInstance` `spec.region` field,
|
||||
making the value either `us-east-1` or `us-west-1`. If no other patches were
|
||||
defined for the `RDSInstance`, giving a user the ability (using RBAC) to create
|
||||
a `XMySQLInstance` / `MySQLInstance` would be akin to giving the ability to
|
||||
create a very specifically configured `RDSInstance`, where they can only decide
|
||||
the region where it lives and they are restricted to two options.
|
||||
|
||||
This model is in contrast to many infrastructure as code tools where the end
|
||||
user must have provider credentials to create the underlying resources that are
|
||||
rendered from the abstraction. Crossplane takes a different approach, defining
|
||||
various credentials in the cluster (using the `ProviderConfig`), then giving
|
||||
only the provider controllers the ability to utilize those credentials and
|
||||
provision infrastructure on the users behalf. This creates a consistent
|
||||
permission model, even when using many providers with differing IAM models, by
|
||||
standardizing on Kubernetes RBAC.
|
||||
|
||||
### Namespaces as an Isolation Mechanism
|
||||
|
||||
While the ability to define abstract schemas and patches to concrete resource
|
||||
types using composition is powerful, the ability to define Claim types at the
|
||||
namespace scope enhances the functionality further by enabling RBAC to be
|
||||
applied with namespace restrictions. Most users in a cluster do not have access
|
||||
to cluster-scoped resources as they are considered only relevant to
|
||||
infrastructure admins by both Kubernetes and Crossplane.
|
||||
|
||||
Building on our simple `XMySQLInstance` / `MySQLInstance` example, a platform
|
||||
builder may choose to define permissions on `MySQLInstance` at the namespace
|
||||
scope using a `Role`. This allows for giving users the ability to create and
|
||||
manage `MySQLInstances` in their given namespace, but not the ability to see
|
||||
those defined in other namespaces.
|
||||
|
||||
Furthermore, because the `metadata.namespace` is a field on the XRC, patching can
|
||||
be utilized to configure managed resources based on the namespace in which the
|
||||
corresponding XRC was defined. This is especially useful if a platform builder
|
||||
wants to designate specific credentials or a set of credentials that users in a
|
||||
given namespace can utilize when provisioning infrastructure using an XRC. This
|
||||
can be accomplished today by creating one or more `ProviderConfig` objects that
|
||||
include the name of the namespace in the `ProviderConfig` name. For example, if
|
||||
any `MySQLInstance` created in the `team-1` namespace should use specific AWS
|
||||
credentials when the provider controller creates the underlying `RDSInstance`,
|
||||
the platform builder could:
|
||||
|
||||
1. Define a `ProviderConfig` with name `team-1`.
|
||||
|
||||
```yaml
|
||||
apiVersion: aws.crossplane.io/v1beta1
|
||||
kind: ProviderConfig
|
||||
metadata:
|
||||
name: team-1
|
||||
spec:
|
||||
credentials:
|
||||
source: Secret
|
||||
secretRef:
|
||||
namespace: crossplane-system
|
||||
name: team-1-creds
|
||||
key: creds
|
||||
```
|
||||
|
||||
2. Define a `Composition` that patches the namespace of the Claim reference in the XR
|
||||
to the `providerConfigRef` of the `RDSInstance`.
|
||||
|
||||
```yaml
|
||||
...
|
||||
resources:
|
||||
- base:
|
||||
apiVersion: database.aws.crossplane.io/v1beta1
|
||||
kind: RDSInstance
|
||||
spec:
|
||||
forProvider:
|
||||
...
|
||||
patches:
|
||||
- fromFieldPath: spec.claimRef.namespace
|
||||
toFieldPath: spec.providerConfigRef.name
|
||||
policy:
|
||||
fromFieldPath: Required
|
||||
```
|
||||
|
||||
This would result in the `RDSInstance` using the `ProviderConfig` of whatever
|
||||
namespace the corresponding `MySQLInstance` was created in.
|
||||
|
||||
> Note that this model currently only allows for a single `ProviderConfig` per
|
||||
> namespace. However, future Crossplane releases should allow for defining a set
|
||||
> of `ProviderConfig` that can be selected from using [Multiple Source Field
|
||||
> patching].
|
||||
|
||||
### Policy Enforcement with Open Policy Agent
|
||||
|
||||
In some Crossplane deployment models, only using composition and RBAC to define
|
||||
policy will not be flexible enough. However, because Crossplane brings
|
||||
management of external infrastructure to the Kubernetes API, it is well suited
|
||||
to integrate with other projects in the cloud-native ecosystem. Organizations
|
||||
and individuals that need a more robust policy engine, or just prefer a more
|
||||
general language for defining policy, often turn to [Open Policy Agent] (OPA).
|
||||
OPA allows platform builders to write custom logic in [Rego], a domain-specific
|
||||
language. Writing policy in this manner allows for not only incorporating the
|
||||
information available in the specific resource being evaluated, but also using
|
||||
other state represented in the cluster. Crossplane users typically install OPA's
|
||||
[Gatekeeper] to make policy management as streamlined as possible.
|
||||
|
||||
> A live demo of using OPA with Crossplane can be viewed [here].
|
||||
|
||||
## Multi-Cluster Multi-Tenancy
|
||||
|
||||
Organizations that deploy Crossplane across many clusters typically take
|
||||
advantage of two major features that make managing multiple control planes much
|
||||
simpler.
|
||||
|
||||
### Reproducible Platforms with Configuration Packages
|
||||
|
||||
[Configuration packages] allow platform builders to package their XRDs and
|
||||
Compositions into [OCI images] that can be distributed via any OCI-compliant
|
||||
image registry. These packages can also declare dependencies on providers,
|
||||
meaning that a single package can declare all of the granular managed resources,
|
||||
the controllers that must be deployed to reconcile them, and the abstract types
|
||||
that expose the underlying resources using composition.
|
||||
|
||||
Organizations with many Crossplane deployments utilize Configuration packages to
|
||||
reproduce their platform in each cluster. This can be as simple as installing
|
||||
Crossplane with the flag to automatically install a Configuration package
|
||||
alongside it.
|
||||
|
||||
```
|
||||
helm install crossplane --namespace crossplane-system crossplane-stable/crossplane --set configuration.packages='{"registry.upbound.io/xp/getting-started-with-aws:latest"}'
|
||||
```
|
||||
|
||||
### Control Plane of Control Planes
|
||||
|
||||
Taking the multi-cluster multi-tenancy model one step further, some
|
||||
organizations opt to manage their many Crossplane clusters using a single
|
||||
central Crossplane control plane. This requires setting up the central cluster,
|
||||
then using a provider to spin up new clusters (such as an [EKS Cluster] using
|
||||
[provider-aws]), then using [provider-helm] to install Crossplane into the new
|
||||
remote cluster, potentially bundling a common Configuration package into each
|
||||
install using the method described above.
|
||||
|
||||
This advanced pattern allows for full management of Crossplane clusters using
|
||||
Crossplane itself, and when done properly, is a scalable solution to providing
|
||||
dedicated control planes to many tenants within a single organization.
|
||||
|
||||
|
||||
<!-- Named Links -->
|
||||
[managed resources]: {{<ref "../../master/concepts/managed-resources" >}}
|
||||
[RBAC]: https://kubernetes.io/docs/reference/access-authn-authz/rbac/
|
||||
[Composition]: {{<ref "../../master/concepts/compositions" >}}
|
||||
[CustomResourceDefinitions]: https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/
|
||||
[Open Policy Agent]: https://www.openpolicyagent.org/
|
||||
[Rego]: https://www.openpolicyagent.org/docs/latest/policy-language/
|
||||
[Gatekeeper]: https://open-policy-agent.github.io/gatekeeper/website/docs/
|
||||
[here]: https://youtu.be/TaF0_syejXc
|
||||
[Multiple Source Field patching]: https://github.com/crossplane/crossplane/pull/2093
|
||||
[Configuration packages]: {{<ref "../../master/concepts/packages" >}}
|
||||
[OCI images]: https://github.com/opencontainers/image-spec
|
||||
[EKS Cluster]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws/latest/resources/eks.aws.crossplane.io/Cluster/v1beta1
|
||||
[provider-aws]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-aws
|
||||
[provider-helm]: https://marketplace.upbound.io/providers/crossplane-contrib/provider-helm/
|
||||
[Open Service Broker API]: https://github.com/openservicebrokerapi/servicebroker
|
||||
[Crossplane Service Broker]: https://github.com/vshn/crossplane-service-broker
|
||||
[Cloudfoundry]: https://www.cloudfoundry.org/
|
||||
[Kubernetes Service Catalog]: https://github.com/kubernetes-sigs/service-catalog
|
||||
[vshn/application-catalog-demo]: https://github.com/vshn/application-catalog-demo
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
title: Self-Signed CA Certs
|
||||
weight: 270
|
||||
---
|
||||
|
||||
> Using self-signed certificates is not advised in production, it is
|
||||
recommended to only use self-signed certificates for testing.
|
||||
|
||||
When Crossplane loads Configuration and Provider Packages from private
|
||||
registries, it must be configured to trust the CA and Intermediate certs.
|
||||
|
||||
Crossplane needs to be installed via the Helm chart with the
|
||||
`registryCaBundleConfig.name` and `registryCaBundleConfig.key` parameters
|
||||
defined. See [Install Crossplane]({{<ref "../../master/software/install" >}}).
|
||||
|
||||
## Configure
|
||||
|
||||
1. Create a CA Bundle (A file containing your Root and Intermediate
|
||||
certificates in a specific order). This can be done with any text editor or
|
||||
from the command line, so long as the resulting file contains all required crt
|
||||
files in the proper order. In many cases, this will be either a single
|
||||
self-signed Root CA crt file, or an Intermediate crt and Root crt file. The
|
||||
order of the crt files should be from lowest to highest in signing order.
|
||||
For example, if you have a chain of two certificates below your Root
|
||||
certificate, you place the bottom level Intermediate cert at the beginning of
|
||||
the file, then the Intermediate cert that singed that cert, then the Root cert
|
||||
that signed that cert.
|
||||
|
||||
2. Save the files as `[yourdomain].ca-bundle`.
|
||||
|
||||
3. Create a Kubernetes ConfigMap in your Crossplane system namespace:
|
||||
|
||||
```
|
||||
kubectl -n [Crossplane system namespace] create cm ca-bundle-config \
|
||||
--from-file=ca-bundle=./[yourdomain].ca-bundle
|
||||
```
|
||||
|
||||
4. Set the `registryCaBundleConfig.name` Helm chart parameter to
|
||||
`ca-bundle-config` and the `registryCaBundleConfig.key` parameter to
|
||||
`ca-bundle`.
|
||||
|
||||
> Providing Helm with parameter values is convered in the Helm docs,
|
||||
[Helm install](https://helm.sh/docs/helm/helm_install/). An example block
|
||||
in an `override.yaml` file would look like this:
|
||||
```
|
||||
registryCaBundleConfig:
|
||||
name: ca-bundle-config
|
||||
key: ca-bundle
|
||||
```
|
|
@ -0,0 +1,443 @@
|
|||
---
|
||||
title: Troubleshoot Crossplane
|
||||
weight: 306
|
||||
---
|
||||
## Requested Resource Not Found
|
||||
|
||||
If you use the Crossplane CLI to install a `Provider` or
|
||||
`Configuration` (e.g. `crossplane install provider
|
||||
xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0`) and get `the server
|
||||
could not find the requested resource` error, more often than not, that is an
|
||||
indicator that the Crossplane CLI you're using is outdated. In other words
|
||||
some Crossplane API has been graduated from alpha to beta or stable and the old
|
||||
plugin is not aware of this change.
|
||||
|
||||
|
||||
## Resource Status and Conditions
|
||||
|
||||
Most Crossplane resources have a `status` section that can represent the current
|
||||
state of that particular resource. Running `kubectl describe` against a
|
||||
Crossplane resource will frequently give insightful information about its
|
||||
condition. For example, to determine the status of a GCP `CloudSQLInstance`
|
||||
managed resource use `kubectl describe` for the resource.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl describe cloudsqlinstance my-db
|
||||
Status:
|
||||
Conditions:
|
||||
Last Transition Time: 2019-09-16T13:46:42Z
|
||||
Reason: Creating
|
||||
Status: False
|
||||
Type: Ready
|
||||
```
|
||||
|
||||
Most Crossplane resources set the `Ready` condition. `Ready` represents the
|
||||
availability of the resource - whether it is creating, deleting, available,
|
||||
unavailable, binding, etc.
|
||||
|
||||
## Resource Events
|
||||
|
||||
Most Crossplane resources emit _events_ when something interesting happens. You
|
||||
can see the events associated with a resource by running `kubectl describe` -
|
||||
e.g. `kubectl describe cloudsqlinstance my-db`. You can also see all events in a
|
||||
particular namespace by running `kubectl get events`.
|
||||
|
||||
```console
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Warning CannotConnectToProvider 16s (x4 over 46s) managed/postgresqlserver.database.azure.crossplane.io cannot get referenced ProviderConfig: ProviderConfig.azure.crossplane.io "default" not found
|
||||
```
|
||||
|
||||
> Note that events are namespaced, while many Crossplane resources (XRs, etc)
|
||||
> are cluster scoped. Crossplane emits events for cluster scoped resources to
|
||||
> the 'default' namespace.
|
||||
|
||||
## Crossplane Logs
|
||||
|
||||
The next place to look to get more information or investigate a failure would be
|
||||
in the Crossplane pod logs, which should be running in the `crossplane-system`
|
||||
namespace. To get the current Crossplane logs, run the following:
|
||||
|
||||
```shell
|
||||
kubectl -n crossplane-system logs -lapp=crossplane
|
||||
```
|
||||
|
||||
> Note that Crossplane emits few logs by default - events are typically the best
|
||||
> place to look for information about what Crossplane is doing. You may need to
|
||||
> restart Crossplane with the `--debug` flag if you can't find what you're
|
||||
> looking for.
|
||||
|
||||
## Provider Logs
|
||||
|
||||
Remember that much of Crossplane's functionality is provided by providers. You
|
||||
can use `kubectl logs` to view provider logs too. By convention, they also emit
|
||||
few logs by default.
|
||||
|
||||
```shell
|
||||
kubectl -n crossplane-system logs <name-of-provider-pod>
|
||||
```
|
||||
|
||||
All providers maintained by the Crossplane community mirror Crossplane's support
|
||||
of the `--debug` flag. The easiest way to set flags on a provider is to create a
|
||||
`ControllerConfig` and reference it from the `Provider`:
|
||||
|
||||
```yaml
|
||||
apiVersion: pkg.crossplane.io/v1alpha1
|
||||
kind: ControllerConfig
|
||||
metadata:
|
||||
name: debug-config
|
||||
spec:
|
||||
args:
|
||||
- --debug
|
||||
---
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: provider-aws
|
||||
spec:
|
||||
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0
|
||||
controllerConfigRef:
|
||||
name: debug-config
|
||||
```
|
||||
|
||||
> Note that a reference to a `ControllerConfig` can be added to an already
|
||||
> installed `Provider` and it will update its `Deployment` accordingly.
|
||||
|
||||
## Compositions and composite resource definition
|
||||
|
||||
### General troubleshooting steps
|
||||
|
||||
Crossplane and its providers log most error messages to resources' event fields. Whenever your Composite Resources aren't getting provisioned, follow the following steps:
|
||||
|
||||
1. Get the events for the root resource using `kubectl describe` or `kubectl get event`
|
||||
2. If there are errors in the events, address them.
|
||||
3. If there are no errors, follow its sub-resources.
|
||||
|
||||
`kubectl get <KIND> <NAME> -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq`
|
||||
4. Repeat this process for each resource returned.
|
||||
|
||||
{{< hint "note" >}}
|
||||
The rest of this section show you how to debug issues related to compositions without using external tooling.
|
||||
If you are using ArgoCD or FluxCD with UI, you can visualize object relationships in the UI.
|
||||
You can also use the kube-lineage plugin to visualize object relationships in your terminal.
|
||||
{{< /hint >}}
|
||||
|
||||
### Examples
|
||||
|
||||
#### Composition
|
||||
<!-- vale Google.WordList = NO -->
|
||||
You deployed an example application using a claim. Kind = `ExampleApp`. Name = `example-application`.
|
||||
|
||||
|
||||
The example application never reaches available state as shown below.
|
||||
|
||||
|
||||
1. View the claim.
|
||||
|
||||
```bash
|
||||
kubectl describe exampleapp example-application
|
||||
|
||||
Status:
|
||||
Conditions:
|
||||
Last Transition Time: 2022-03-01T22:57:38Z
|
||||
Reason: Composite resource claim is waiting for composite resource to become Ready
|
||||
Status: False
|
||||
Type: Ready
|
||||
Events: <none>
|
||||
```
|
||||
|
||||
2. If the claim doesn't have errors, inspect the `.spec.resourceRef` field of the claim.
|
||||
|
||||
```bash
|
||||
kubectl get exampleapp example-application -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq
|
||||
|
||||
{
|
||||
"apiVersion": "awsblueprints.io/v1alpha1",
|
||||
"kind": "XExampleApp",
|
||||
"name": "example-application-xqlsz"
|
||||
}
|
||||
```
|
||||
3. In the preceding output, you see the cluster scoped resource for this claim. Kind = `XExampleApp` name = `example-application-xqlsz`
|
||||
4. View the cluster scoped resource's events.
|
||||
|
||||
```bash
|
||||
kubectl describe xexampleapp example-application-xqlsz
|
||||
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Normal PublishConnectionSecret 9s (x2 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully published connection details
|
||||
Normal SelectComposition 6s (x6 over 11s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully selected composition
|
||||
Warning ComposeResources 6s (x6 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io can't render composed resource from resource template at index 3: can't use dry-run create to name composed resource: an empty namespace may not be set during creation
|
||||
Normal ComposeResources 6s (x6 over 10s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully composed resources
|
||||
```
|
||||
5. You see errors in the events. it's complaining about not specifying namespace in its compositions. For this particular kind of error, you can get its sub-resources and check which one isn't created.
|
||||
|
||||
```bash
|
||||
kubectl get xexampleapp example-application-xqlsz -o=jsonpath='{.spec.resourceRef}{" "}{.spec.resourceRefs}' | jq
|
||||
|
||||
[
|
||||
{
|
||||
"apiVersion": "awsblueprints.io/v1alpha1",
|
||||
"kind": "XDynamoDBTable",
|
||||
"name": "example-application-xqlsz-6j9nm"
|
||||
},
|
||||
{
|
||||
"apiVersion": "awsblueprints.io/v1alpha1",
|
||||
"kind": "XIAMPolicy",
|
||||
"name": "example-application-xqlsz-lp9wt"
|
||||
},
|
||||
{
|
||||
"apiVersion": "awsblueprints.io/v1alpha1",
|
||||
"kind": "XIAMPolicy",
|
||||
"name": "example-application-xqlsz-btwkn"
|
||||
},
|
||||
{
|
||||
"apiVersion": "awsblueprints.io/v1alpha1",
|
||||
"kind": "IRSA"
|
||||
}
|
||||
]
|
||||
```
|
||||
6. Notice the last element in the array doesn't have a name. When a resource in composition fails validation, the resource object isn't created and doesn't have a name. For this particular issue, you must specify the namespace for the IRSA resource.
|
||||
|
||||
#### Composite resource definition
|
||||
|
||||
Debugging Composite Resource Definition (XRD) is like debugging Compositions.
|
||||
|
||||
1. Get the XRD
|
||||
|
||||
```bash
|
||||
kubectl get xrd testing.awsblueprints.io
|
||||
|
||||
NAME ESTABLISHED OFFERED AGE
|
||||
testing.awsblueprints.io 66s
|
||||
```
|
||||
2. Notice its status it not established. You describe this XRD to get its events.
|
||||
|
||||
```bash
|
||||
kubectl describe xrd testing.awsblueprints.io
|
||||
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Normal ApplyClusterRoles 3m19s (x3 over 3m19s) rbac/compositeresourcedefinition.apiextensions.crossplane.io Applied RBAC ClusterRoles
|
||||
Normal RenderCRD 18s (x9 over 3m19s) defined/compositeresourcedefinition.apiextensions.crossplane.io Rendered composite resource CustomResourceDefinition
|
||||
Warning EstablishComposite 18s (x9 over 3m19s) defined/compositeresourcedefinition.apiextensions.crossplane.io can't apply rendered composite resource CustomResourceDefinition: can't create object: CustomResourceDefinition.apiextensions.k8s.io "testing.awsblueprints.io" is invalid: metadata.name: Invalid value: "testing.awsblueprints.io": must be spec.names.plural+"."+spec.group
|
||||
```
|
||||
3. You see in the events that Crossplane can't generate corresponding CRDs for this XRD. In this case, ensure the name is `spec.names.plural+"."+spec.group`
|
||||
|
||||
#### Providers
|
||||
|
||||
You can use install providers in two ways: `configuration.pkg.crossplane.io` and `provider.pkg.crossplane.io`. You can use either one to install providers with no functional differences to providers themselves.
|
||||
If you define a `configuration.pkg.crossplane.io` object, Crossplane creates a
|
||||
`provider.pkg.crossplane.io` object and manages it. Refer to [the Packages
|
||||
documentation]({{<ref "/master/concepts/packages">}})
|
||||
for more information about Crossplane Packages.
|
||||
|
||||
If you are experiencing provider issues, steps below are a good starting point.
|
||||
|
||||
1. Check the status of provider object.
|
||||
```bash
|
||||
kubectl describe provider.pkg.crossplane.io provider-aws
|
||||
|
||||
Status:
|
||||
Conditions:
|
||||
Last Transition Time: 2022-08-04T16:19:44Z
|
||||
Reason: HealthyPackageRevision
|
||||
Status: True
|
||||
Type: Healthy
|
||||
Last Transition Time: 2022-08-04T16:14:29Z
|
||||
Reason: ActivePackageRevision
|
||||
Status: True
|
||||
Type: Installed
|
||||
Current Identifier: crossplane/provider-aws:v0.29.0
|
||||
Current Revision: provider-aws-a2e16ca2fc1a
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Normal InstallPackageRevision 9m49s (x237 over 4d17h) packages/provider.pkg.crossplane.io Successfully installed package revision
|
||||
```
|
||||
In the output above you see that this provider is healthy. To get more information about this provider, you can dig deeper. The `Current Revision` field let you know of your next object to look at.
|
||||
|
||||
|
||||
2. When you create a provider object, Crossplane creates a `ProviderRevision` object based on the contents of the OCI image. In this example, you're specifying the OCI image to be `crossplane/provider-aws:v0.29.0`. This image contains a YAML file which defines Kubernetes objects such as Deployment, ServiceAccount, and CRDs.
|
||||
The `ProviderRevision` object creates resources necessary for a provider to function based on the contents of the YAML file. To inspect what's deployed as part of the provider package, you inspect the ProviderRevision object. The `Current Revision` field above indicates which ProviderRevision object this provider uses.
|
||||
|
||||
```bash
|
||||
kubectl get providerrevision provider-aws-a2e16ca2fc1a
|
||||
|
||||
NAME HEALTHY REVISION IMAGE STATE DEP-FOUND DEP-INSTALLED AGE
|
||||
provider-aws-a2e16ca2fc1a True 1 crossplane/provider-aws:v0.29.0 Active 19d
|
||||
```
|
||||
|
||||
When you describe the object, you find all CRDs managed by this object.
|
||||
|
||||
```bash
|
||||
kubectl describe providerrevision provider-aws-a2e16ca2fc1a
|
||||
|
||||
Status:
|
||||
Controller Ref:
|
||||
Name: provider-aws-a2e16ca2fc1a
|
||||
Object Refs:
|
||||
API Version: apiextensions.k8s.io/v1
|
||||
Kind: CustomResourceDefinition
|
||||
Name: natgateways.ec2.aws.crossplane.io
|
||||
UID: 5c36d1bc-61b8-44f8-bca0-47e368af87a9
|
||||
....
|
||||
Events:
|
||||
Type Reason Age From Message
|
||||
---- ------ ---- ---- -------
|
||||
Normal SyncPackage 22m (x369 over 4d18h) packages/providerrevision.pkg.crossplane.io Successfully configured package revision
|
||||
Normal BindClusterRole 15m (x348 over 4d18h) rbac/providerrevision.pkg.crossplane.io Bound system ClusterRole to provider ServiceAccount
|
||||
Normal ApplyClusterRoles 15m (x364 over 4d18h) rbac/providerrevision.pkg.crossplane.io Applied RBAC ClusterRoles
|
||||
```
|
||||
|
||||
The event field also indicates any issues that may have occurred during this process.
|
||||
<!-- vale Google.WordList = YES -->
|
||||
3. If you don't see any errors in the event field above, you should check if Crossplane provisioned deployments and their status.
|
||||
|
||||
```bash
|
||||
kubectl get deployment -n crossplane-system
|
||||
|
||||
NAME READY UP-TO-DATE AVAILABLE AGE
|
||||
crossplane 1/1 1 1 105d
|
||||
crossplane-rbac-manager 1/1 1 1 105d
|
||||
provider-aws-a2e16ca2fc1a 1/1 1 1 19d
|
||||
|
||||
kubectl get pods -n crossplane-system
|
||||
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
crossplane-54db688c8d-qng6b 2/2 Running 0 4d19h
|
||||
crossplane-rbac-manager-5776c9fbf4-wn5rj 1/1 Running 0 4d19h
|
||||
provider-aws-a2e16ca2fc1a-776769ccbd-4dqml 1/1 Running 0 4d23h
|
||||
```
|
||||
If there are any pods failing, check its logs and remedy the problem.
|
||||
|
||||
|
||||
## Pausing Crossplane
|
||||
|
||||
Sometimes, for example when you encounter a bug, it can be useful to pause
|
||||
Crossplane if you want to stop it from actively attempting to manage your
|
||||
resources. To pause Crossplane without deleting all of its resources, run the
|
||||
following command to simply scale down its deployment:
|
||||
|
||||
```bash
|
||||
kubectl -n crossplane-system scale --replicas=0 deployment/crossplane
|
||||
```
|
||||
|
||||
Once you have been able to rectify the problem or smooth things out, you can
|
||||
unpause Crossplane simply by scaling its deployment back up:
|
||||
|
||||
```bash
|
||||
kubectl -n crossplane-system scale --replicas=1 deployment/crossplane
|
||||
```
|
||||
|
||||
## Pausing Providers
|
||||
|
||||
Providers can also be paused when troubleshooting an issue or orchestrating a
|
||||
complex migration of resources. Creating and referencing a `ControllerConfig` is
|
||||
the easiest way to scale down a provider, and the `ControllerConfig` can be
|
||||
modified or the reference can be removed to scale it back up:
|
||||
|
||||
```yaml
|
||||
apiVersion: pkg.crossplane.io/v1alpha1
|
||||
kind: ControllerConfig
|
||||
metadata:
|
||||
name: scale-config
|
||||
spec:
|
||||
replicas: 0
|
||||
---
|
||||
apiVersion: pkg.crossplane.io/v1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: provider-aws
|
||||
spec:
|
||||
package: xpkg.upbound.io/crossplane-contrib/provider-aws:v0.33.0
|
||||
controllerConfigRef:
|
||||
name: scale-config
|
||||
```
|
||||
|
||||
> Note that a reference to a `ControllerConfig` can be added to an already
|
||||
> installed `Provider` and it will update its `Deployment` accordingly.
|
||||
|
||||
## Deleting When a Resource Hangs
|
||||
|
||||
The resources that Crossplane manages will automatically be cleaned up so as not
|
||||
to leave anything running behind. This is accomplished by using finalizers, but
|
||||
in certain scenarios the finalizer can prevent the Kubernetes object from
|
||||
getting deleted.
|
||||
|
||||
To deal with this, we essentially want to patch the object to remove its
|
||||
finalizer, which will then allow it to be deleted completely. Note that this
|
||||
won't necessarily delete the external resource that Crossplane was managing, so
|
||||
you will want to go to your cloud provider's console and look there for any
|
||||
lingering resources to clean up.
|
||||
|
||||
In general, a finalizer can be removed from an object with this command:
|
||||
|
||||
```shell
|
||||
kubectl patch <resource-type> <resource-name> -p '{"metadata":{"finalizers": []}}' --type=merge
|
||||
```
|
||||
|
||||
For example, for a `CloudSQLInstance` managed resource (`database.gcp.crossplane.io`) named
|
||||
`my-db`, you can remove its finalizer with:
|
||||
|
||||
```shell
|
||||
kubectl patch cloudsqlinstance my-db -p '{"metadata":{"finalizers": []}}' --type=merge
|
||||
```
|
||||
|
||||
## Tips, Tricks, and Troubleshooting
|
||||
|
||||
In this section we'll cover some common tips, tricks, and troubleshooting steps
|
||||
for working with Composite Resources. If you're trying to track down why your
|
||||
Composite Resources aren't working the [Troubleshooting][trouble-ref] page also
|
||||
has some useful information.
|
||||
|
||||
### Troubleshooting Claims and XRs
|
||||
|
||||
Crossplane relies heavily on status conditions and events for troubleshooting.
|
||||
You can see both using `kubectl describe` - for example:
|
||||
|
||||
```console
|
||||
# Describe the PostgreSQLInstance claim named my-db
|
||||
kubectl describe postgresqlinstance.database.example.org my-db
|
||||
```
|
||||
|
||||
Per Kubernetes convention, Crossplane keeps errors close to the place they
|
||||
happen. This means that if your claim is not becoming ready due to an issue with
|
||||
your `Composition` or with a composed resource you'll need to "follow the
|
||||
references" to find out why. Your claim will only tell you that the XR is not
|
||||
yet ready.
|
||||
|
||||
To follow the references:
|
||||
|
||||
1. Find your XR by running `kubectl describe` on your claim and looking for its
|
||||
"Resource Ref" (aka `spec.resourceRef`).
|
||||
1. Run `kubectl describe` on your XR. This is where you'll find out about issues
|
||||
with the `Composition` you're using, if any.
|
||||
1. If there are no issues but your XR doesn't seem to be becoming ready, take a
|
||||
look for the "Resource Refs" (or `spec.resourceRefs`) to find your composed
|
||||
resources.
|
||||
1. Run `kubectl describe` on each referenced composed resource to determine
|
||||
whether it is ready and what issues, if any, it is encountering.
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Named Links -->
|
||||
[Requested Resource Not Found]: #requested-resource-not-found
|
||||
[install Crossplane CLI]: "../getting-started/install-configure"
|
||||
[Resource Status and Conditions]: #resource-status-and-conditions
|
||||
[Resource Events]: #resource-events
|
||||
[Crossplane Logs]: #crossplane-logs
|
||||
[Provider Logs]: #provider-logs
|
||||
[Pausing Crossplane]: #pausing-crossplane
|
||||
[Pausing Providers]: #pausing-providers
|
||||
[Deleting When a Resource Hangs]: #deleting-when-a-resource-hangs
|
||||
[Installing Crossplane Package]: #installing-crossplane-package
|
||||
[Crossplane package]: /master/concepts/packages/
|
||||
[Handling Crossplane Package Dependency]: #handling-crossplane-package-dependency
|
||||
[semver spec]: https://github.com/Masterminds/semver#basic-comparisons
|
||||
|
||||
|
|
@ -0,0 +1,627 @@
|
|||
---
|
||||
title: Vault as an External Secret Store
|
||||
weight: 230
|
||||
---
|
||||
|
||||
This guide walks through the steps required to configure Crossplane and
|
||||
its Providers to use [Vault] as an [External Secret Store] (`ESS`) with [ESS Plugin Vault].
|
||||
|
||||
{{<hint "warning" >}}
|
||||
External Secret Stores are an alpha feature.
|
||||
|
||||
They're not recommended for production use. Crossplane disables External Secret
|
||||
Stores by default.
|
||||
{{< /hint >}}
|
||||
|
||||
Crossplane uses sensitive information including Provider credentials, inputs to
|
||||
managed resources and connection details.
|
||||
|
||||
The [Vault credential injection guide]({{<ref "vault-injection" >}}) details
|
||||
using Vault and Crossplane for Provider credentials.
|
||||
|
||||
Crossplane doesn't support for using Vault for managed resources input.
|
||||
[Crossplane issue #2985](https://github.com/crossplane/crossplane/issues/2985)
|
||||
tracks support for this feature.
|
||||
|
||||
Supporting connection details with Vault requires a Crossplane external secret
|
||||
store.
|
||||
|
||||
## Prerequisites
|
||||
This guide requires [Helm](https://helm.sh) version 3.11 or later.
|
||||
|
||||
## Install Vault
|
||||
|
||||
{{<hint "note" >}}
|
||||
Detailed instructions on [installing
|
||||
Vault](https://developer.hashicorp.com/vault/docs/platform/k8s/helm)
|
||||
are available from the Vault documentation.
|
||||
{{< /hint >}}
|
||||
|
||||
### Add the Vault Helm chart
|
||||
|
||||
Add the Helm repository for `hashicorp`.
|
||||
```shell
|
||||
helm repo add hashicorp https://helm.releases.hashicorp.com --force-update
|
||||
```
|
||||
|
||||
Install Vault using Helm.
|
||||
```shell
|
||||
helm -n vault-system upgrade --install vault hashicorp/vault --create-namespace
|
||||
```
|
||||
|
||||
### Unseal Vault
|
||||
|
||||
If Vault is [sealed](https://developer.hashicorp.com/vault/docs/concepts/seal)
|
||||
unseal Vault using the unseal keys.
|
||||
|
||||
Get the Vault keys.
|
||||
```shell
|
||||
kubectl -n vault-system exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json
|
||||
VAULT_UNSEAL_KEY=$(cat cluster-keys.json | jq -r ".unseal_keys_b64[]")
|
||||
```
|
||||
|
||||
Unseal the vault using the keys.
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n vault-system exec vault-0 -- vault operator unseal $VAULT_UNSEAL_KEY
|
||||
Key Value
|
||||
--- -----
|
||||
Seal Type shamir
|
||||
Initialized true
|
||||
Sealed false
|
||||
Total Shares 1
|
||||
Threshold 1
|
||||
Version 1.13.1
|
||||
Build Date 2023-03-23T12:51:35Z
|
||||
Storage Type file
|
||||
Cluster Name vault-cluster-df884357
|
||||
Cluster ID b3145d26-2c1a-a7f2-a364-81753033c0d9
|
||||
HA Enabled false
|
||||
```
|
||||
|
||||
## Configure Vault Kubernetes authentication
|
||||
|
||||
Enable the [Kubernetes auth method] for Vault to authenticate requests based on
|
||||
Kubernetes service accounts.
|
||||
|
||||
### Get the Vault root token
|
||||
|
||||
The Vault root token is inside the JSON file created when
|
||||
[unsealing Vault](#unseal-vault).
|
||||
|
||||
```shell
|
||||
cat cluster-keys.json | jq -r ".root_token"
|
||||
```
|
||||
|
||||
### Enable Kubernetes authentication
|
||||
|
||||
Connect to a shell in the Vault pod.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n vault-system exec -it vault-0 -- /bin/sh
|
||||
/ $
|
||||
```
|
||||
|
||||
From the Vault shell, login to Vault using the _root token_.
|
||||
```shell {copy-lines="1"}
|
||||
vault login # use the root token from above
|
||||
Token (will be hidden):
|
||||
Success! You are now authenticated. The token information displayed below
|
||||
is already stored in the token helper. You do NOT need to run "vault login"
|
||||
again. Future Vault requests will automatically use this token.
|
||||
|
||||
Key Value
|
||||
--- -----
|
||||
token hvs.TSN4SssfMBM0HAtwGrxgARgn
|
||||
token_accessor qodxHrINVlRXKyrGeeDkxnih
|
||||
token_duration ∞
|
||||
token_renewable false
|
||||
token_policies ["root"]
|
||||
identity_policies []
|
||||
policies ["root"]
|
||||
```
|
||||
|
||||
Enable the Kubernetes authentication method in Vault.
|
||||
```shell {copy-lines="1"}
|
||||
vault auth enable kubernetes
|
||||
Success! Enabled kubernetes auth method at: kubernetes/
|
||||
```
|
||||
|
||||
Configure Vault to communicate with Kubernetes and exit the Vault shell
|
||||
|
||||
```shell {copy-lines="1-4"}
|
||||
vault write auth/kubernetes/config \
|
||||
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
|
||||
kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
|
||||
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
Success! Data written to: auth/kubernetes/config
|
||||
/ $ exit
|
||||
```
|
||||
|
||||
## Configure Vault for Crossplane integration
|
||||
|
||||
Crossplane relies on the Vault key-value secrets engine to store information and
|
||||
Vault requires a permissions policy for the Crossplane service account.
|
||||
|
||||
<!-- vale Crossplane.Spelling = NO -->
|
||||
<!-- allow "kv" -->
|
||||
### Enable the Vault kv secrets engine
|
||||
<!-- vale Crossplane.Spelling = YES -->
|
||||
|
||||
Enable the [Vault KV Secrets Engine].
|
||||
|
||||
{{< hint "important" >}}
|
||||
Vault has two versions of the
|
||||
[KV Secrets Engine](https://developer.hashicorp.com/vault/docs/secrets/kv).
|
||||
This example uses version 2.
|
||||
{{</hint >}}
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n vault-system exec -it vault-0 -- vault secrets enable -path=secret kv-v2
|
||||
Success! Enabled the kv-v2 secrets engine at: secret/
|
||||
```
|
||||
|
||||
### Create a Vault policy for Crossplane
|
||||
|
||||
Create the Vault policy to allow Crossplane to read and write data from Vault.
|
||||
```shell {copy-lines="1-8"}
|
||||
kubectl -n vault-system exec -i vault-0 -- vault policy write crossplane - <<EOF
|
||||
path "secret/data/*" {
|
||||
capabilities = ["create", "read", "update", "delete"]
|
||||
}
|
||||
path "secret/metadata/*" {
|
||||
capabilities = ["create", "read", "update", "delete"]
|
||||
}
|
||||
EOF
|
||||
Success! Uploaded policy: crossplane
|
||||
```
|
||||
|
||||
Apply the policy to Vault.
|
||||
```shell {copy-lines="1-5"}
|
||||
kubectl -n vault-system exec -it vault-0 -- vault write auth/kubernetes/role/crossplane \
|
||||
bound_service_account_names="*" \
|
||||
bound_service_account_namespaces=crossplane-system \
|
||||
policies=crossplane \
|
||||
ttl=24h
|
||||
Success! Data written to: auth/kubernetes/role/crossplane
|
||||
```
|
||||
|
||||
## Install Crossplane
|
||||
|
||||
{{<hint "important" >}}
|
||||
Crossplane v1.12 introduced the plugin support. Make sure your version of Crossplane supports plugins.
|
||||
{{< /hint >}}
|
||||
|
||||
Install the Crossplane with the External Secrets Stores feature enabled.
|
||||
|
||||
```shell
|
||||
helm upgrade --install crossplane crossplane-stable/crossplane --namespace crossplane-system --create-namespace --set args='{--enable-external-secret-stores}'
|
||||
```
|
||||
|
||||
## Install the Crossplane Vault plugin
|
||||
|
||||
The Crossplane Vault plugin isn't part of the default Crossplane install.
|
||||
The plugin installs as a unique Pod that uses the [Vault Agent Sidecar
|
||||
Injection] to connect the Vault secret store to Crossplane.
|
||||
|
||||
First, configure annotations for the Vault plugin pod.
|
||||
|
||||
```yaml
|
||||
cat > values.yaml <<EOF
|
||||
podAnnotations:
|
||||
vault.hashicorp.com/agent-inject: "true"
|
||||
vault.hashicorp.com/agent-inject-token: "true"
|
||||
vault.hashicorp.com/role: crossplane
|
||||
vault.hashicorp.com/agent-run-as-user: "65532"
|
||||
EOF
|
||||
```
|
||||
Next, install the Crossplane ESS Plugin pod to the `crossplane-system` namespace
|
||||
and apply the Vault annotations.
|
||||
|
||||
```shell
|
||||
helm upgrade --install ess-plugin-vault oci://xpkg.upbound.io/crossplane-contrib/ess-plugin-vault --namespace crossplane-system -f values.yaml
|
||||
```
|
||||
|
||||
## Configure Crossplane
|
||||
|
||||
Using the Vault plugin requires configuration to connect to the Vault
|
||||
service. The plugin also requires Providers to enable external secret stores.
|
||||
|
||||
With the plugin and providers configured, Crossplane requires two `StoreConfig`
|
||||
objects to describe how Crossplane and the Providers communicate with vault.
|
||||
|
||||
### Enable external secret stores in the Provider
|
||||
|
||||
{{<hint "note">}}
|
||||
This example uses Provider GCP, but the
|
||||
{{<hover label="ControllerConfig" line="2">}}ControllerConfig{{</hover>}} is the
|
||||
same for all Providers.
|
||||
{{</hint >}}
|
||||
|
||||
Create a `ControllerConfig` object to enable external secret stores.
|
||||
|
||||
```yaml {label="ControllerConfig"}
|
||||
echo "apiVersion: pkg.crossplane.io/v1alpha1
|
||||
kind: ControllerConfig
|
||||
metadata:
|
||||
name: vault-config
|
||||
spec:
|
||||
args:
|
||||
- --enable-external-secret-stores" | kubectl apply -f -
|
||||
```
|
||||
|
||||
Install the Provider and apply the ControllerConfig.
|
||||
```yaml
|
||||
echo "apiVersion: pkg.crossplane.io/v1
|
||||
kind: Provider
|
||||
metadata:
|
||||
name: provider-gcp
|
||||
spec:
|
||||
package: xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.23.0-rc.0.19.ge9b75ee5
|
||||
controllerConfigRef:
|
||||
name: vault-config" | kubectl apply -f -
|
||||
```
|
||||
|
||||
### Connect the Crossplane plugin to Vault
|
||||
Create a {{<hover label="VaultConfig" line="2">}}VaultConfig{{</hover>}}
|
||||
resource for the plugin to connect to the Vault service:
|
||||
|
||||
```yaml {label="VaultConfig"}
|
||||
echo "apiVersion: secrets.crossplane.io/v1alpha1
|
||||
kind: VaultConfig
|
||||
metadata:
|
||||
name: vault-internal
|
||||
spec:
|
||||
server: http://vault.vault-system:8200
|
||||
mountPath: secret/
|
||||
version: v2
|
||||
auth:
|
||||
method: Token
|
||||
token:
|
||||
source: Filesystem
|
||||
fs:
|
||||
path: /vault/secrets/token" | kubectl apply -f -
|
||||
```
|
||||
|
||||
### Create a Crossplane StoreConfig
|
||||
|
||||
Create a {{<hover label="xp-storeconfig" line="2">}}StoreConfig{{</hover >}}
|
||||
object from the
|
||||
{{<hover label="xp-storeconfig" line="1">}}secrets.crossplane.io{{</hover >}}
|
||||
group. Crossplane uses the StoreConfig to connect to the Vault plugin service.
|
||||
|
||||
The {{<hover label="xp-storeconfig" line="10">}}configRef{{</hover >}} connects
|
||||
the StoreConfig to the specific Vault plugin configuration.
|
||||
|
||||
```yaml {label="xp-storeconfig"}
|
||||
echo "apiVersion: secrets.crossplane.io/v1alpha1
|
||||
kind: StoreConfig
|
||||
metadata:
|
||||
name: vault
|
||||
spec:
|
||||
type: Plugin
|
||||
defaultScope: crossplane-system
|
||||
plugin:
|
||||
endpoint: ess-plugin-vault.crossplane-system:4040
|
||||
configRef:
|
||||
apiVersion: secrets.crossplane.io/v1alpha1
|
||||
kind: VaultConfig
|
||||
name: vault-internal" | kubectl apply -f -
|
||||
```
|
||||
|
||||
|
||||
### Create a Provider StoreConfig
|
||||
Create a {{<hover label="gcp-storeconfig" line="2">}}StoreConfig{{</hover >}}
|
||||
object from the Provider's API group,
|
||||
{{<hover label="gcp-storeconfig" line="1">}}gcp.crossplane.io{{</hover >}}.
|
||||
The Provider uses this StoreConfig to communicate with Vault for
|
||||
Managed Resources.
|
||||
|
||||
The {{<hover label="gcp-storeconfig" line="10">}}configRef{{</hover >}} connects
|
||||
the StoreConfig to the specific Vault plugin configuration.
|
||||
|
||||
```yaml {label="gcp-storeconfig"}
|
||||
echo "apiVersion: gcp.crossplane.io/v1alpha1
|
||||
kind: StoreConfig
|
||||
metadata:
|
||||
name: vault
|
||||
spec:
|
||||
type: Plugin
|
||||
defaultScope: crossplane-system
|
||||
plugin:
|
||||
endpoint: ess-plugin-vault.crossplane-system:4040
|
||||
configRef:
|
||||
apiVersion: secrets.crossplane.io/v1alpha1
|
||||
kind: VaultConfig
|
||||
name: vault-internal" | kubectl apply -f -
|
||||
```
|
||||
|
||||
## Create Provider resources
|
||||
|
||||
Check that Crossplane installed the Provider and the Provider is healthy.
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get providers
|
||||
NAME INSTALLED HEALTHY PACKAGE AGE
|
||||
provider-gcp True True xpkg.upbound.io/crossplane-contrib/provider-gcp:v0.23.0-rc.0.19.ge9b75ee5 10m
|
||||
```
|
||||
|
||||
### Create a CompositeResourceDefinition
|
||||
|
||||
Create a `CompositeResourceDefinition` to define a custom API endpoint.
|
||||
|
||||
```yaml
|
||||
echo "apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: CompositeResourceDefinition
|
||||
metadata:
|
||||
name: compositeessinstances.ess.example.org
|
||||
annotations:
|
||||
feature: ess
|
||||
spec:
|
||||
group: ess.example.org
|
||||
names:
|
||||
kind: CompositeESSInstance
|
||||
plural: compositeessinstances
|
||||
claimNames:
|
||||
kind: ESSInstance
|
||||
plural: essinstances
|
||||
connectionSecretKeys:
|
||||
- publicKey
|
||||
- publicKeyType
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
referenceable: true
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
type: object
|
||||
properties:
|
||||
spec:
|
||||
type: object
|
||||
properties:
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
serviceAccount:
|
||||
type: string
|
||||
required:
|
||||
- serviceAccount
|
||||
required:
|
||||
- parameters" | kubectl apply -f -
|
||||
```
|
||||
|
||||
### Create a Composition
|
||||
Create a `Composition` to create a Service Account and Service Account Key
|
||||
inside GCP.
|
||||
|
||||
Creating a Service Account Key generates
|
||||
{{<hover label="comp" line="39" >}}connectionDetails{{</hover>}} that the
|
||||
Provider stores in Vault using the
|
||||
{{<hover label="comp" line="31">}}publishConnectionDetailsTo{{</hover>}} details.
|
||||
|
||||
```yaml {label="comp"}
|
||||
echo "apiVersion: apiextensions.crossplane.io/v1
|
||||
kind: Composition
|
||||
metadata:
|
||||
name: essinstances.ess.example.org
|
||||
labels:
|
||||
feature: ess
|
||||
spec:
|
||||
publishConnectionDetailsWithStoreConfigRef:
|
||||
name: vault
|
||||
compositeTypeRef:
|
||||
apiVersion: ess.example.org/v1alpha1
|
||||
kind: CompositeESSInstance
|
||||
resources:
|
||||
- name: serviceaccount
|
||||
base:
|
||||
apiVersion: iam.gcp.crossplane.io/v1alpha1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: ess-test-sa
|
||||
spec:
|
||||
forProvider:
|
||||
displayName: a service account to test ess
|
||||
- name: serviceaccountkey
|
||||
base:
|
||||
apiVersion: iam.gcp.crossplane.io/v1alpha1
|
||||
kind: ServiceAccountKey
|
||||
spec:
|
||||
forProvider:
|
||||
serviceAccountSelector:
|
||||
matchControllerRef: true
|
||||
publishConnectionDetailsTo:
|
||||
name: ess-mr-conn
|
||||
metadata:
|
||||
labels:
|
||||
environment: development
|
||||
team: backend
|
||||
configRef:
|
||||
name: vault
|
||||
connectionDetails:
|
||||
- fromConnectionSecretKey: publicKey
|
||||
- fromConnectionSecretKey: publicKeyType" | kubectl apply -f -
|
||||
```
|
||||
|
||||
### Create a Claim
|
||||
Now create a `Claim` to have Crossplane create the GCP resources and associated
|
||||
secrets.
|
||||
|
||||
Like the Composition, the Claim uses
|
||||
{{<hover label="claim" line="12">}}publishConnectionDetailsTo{{</hover>}} to
|
||||
connect to Vault and store the secrets.
|
||||
|
||||
```yaml {label="claim"}
|
||||
echo "apiVersion: ess.example.org/v1alpha1
|
||||
kind: ESSInstance
|
||||
metadata:
|
||||
name: my-ess
|
||||
namespace: default
|
||||
spec:
|
||||
parameters:
|
||||
serviceAccount: ess-test-sa
|
||||
compositionSelector:
|
||||
matchLabels:
|
||||
feature: ess
|
||||
publishConnectionDetailsTo:
|
||||
name: ess-claim-conn
|
||||
metadata:
|
||||
labels:
|
||||
environment: development
|
||||
team: backend
|
||||
configRef:
|
||||
name: vault" | kubectl apply -f -
|
||||
```
|
||||
|
||||
## Verify the resources
|
||||
|
||||
Verify all resources are `READY` and `SYNCED`:
|
||||
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get managed
|
||||
NAME READY SYNCED DISPLAYNAME EMAIL DISABLED
|
||||
serviceaccount.iam.gcp.crossplane.io/my-ess-zvmkz-vhklg True True a service account to test ess my-ess-zvmkz-vhklg@testingforbugbounty.iam.gserviceaccount.com
|
||||
|
||||
NAME READY SYNCED KEY_ID CREATED_AT EXPIRES_AT
|
||||
serviceaccountkey.iam.gcp.crossplane.io/my-ess-zvmkz-bq8pz True True 5cda49b7c32393254b5abb121b4adc07e140502c 2022-03-23T10:54:50Z
|
||||
```
|
||||
|
||||
View the claims
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n default get claim
|
||||
NAME READY CONNECTION-SECRET AGE
|
||||
my-ess True 19s
|
||||
```
|
||||
|
||||
View the composite resources.
|
||||
```shell {copy-lines="1"}
|
||||
kubectl get composite
|
||||
NAME READY COMPOSITION AGE
|
||||
my-ess-zvmkz True essinstances.ess.example.org 32s
|
||||
```
|
||||
|
||||
## Verify Vault secrets
|
||||
|
||||
Look inside Vault to view the secrets from the managed resources.
|
||||
|
||||
```shell {copy-lines="1",label="vault-key"}
|
||||
kubectl -n vault-system exec -i vault-0 -- vault kv list /secret/default
|
||||
Keys
|
||||
----
|
||||
ess-claim-conn
|
||||
```
|
||||
|
||||
The key {{<hover label="vault-key" line="4">}}ess-claim-conn{{</hover>}}
|
||||
is the name of the Claim's
|
||||
{{<hover label="claim" line="12">}}publishConnectionDetailsTo{{</hover>}}
|
||||
configuration.
|
||||
|
||||
Check connection secrets in the "crossplane-system" Vault scope.
|
||||
```shell {copy-lines="1",label="scope-key"}
|
||||
kubectl -n vault-system exec -i vault-0 -- vault kv list /secret/crossplane-system
|
||||
Keys
|
||||
----
|
||||
d2408335-eb88-4146-927b-8025f405da86
|
||||
ess-mr-conn
|
||||
```
|
||||
|
||||
The key
|
||||
{{<hover label="scope-key"line="4">}}d2408335-eb88-4146-927b-8025f405da86{{</hover>}}
|
||||
comes from
|
||||
|
||||
<!-- ## WHERE DOES IT COME FROM? -->
|
||||
|
||||
and the key
|
||||
{{<hover label="scope-key"line="5">}}ess-mr-conn{{</hover>}}
|
||||
comes from the Composition's
|
||||
{{<hover label="comp" line="31">}}publishConnectionDetailsTo{{</hover>}}
|
||||
configuration.
|
||||
|
||||
|
||||
Check contents of Claim's connection secret `ess-claim-conn` to see the key
|
||||
created by the managed resource.
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n vault-system exec -i vault-0 -- vault kv get /secret/default/ess-claim-conn
|
||||
======= Metadata =======
|
||||
Key Value
|
||||
--- -----
|
||||
created_time 2022-03-18T21:24:07.2085726Z
|
||||
custom_metadata map[environment:development secret.crossplane.io/ner-uid:881cd9a0-6cc6-418f-8e1d-b36062c1e108 team:backend]
|
||||
deletion_time n/a
|
||||
destroyed false
|
||||
version 1
|
||||
|
||||
======== Data ========
|
||||
Key Value
|
||||
--- -----
|
||||
publicKey -----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzsEYCokmYEsZJCc9QN/8
|
||||
Fm1M/kTPp7Gat/MXLTP3zFyCTBFVNLN79MbAKdinWi6ePXEb75vzB79IdZcWj8lo
|
||||
8trnS64QjNB9Vs4Xk5UvDALwleFN/bZeperxivDPwVPvT9Aqy/U9kohoS/LHyE8w
|
||||
uWQb5AuMeVQ1gtCTnCqQZ4d2MSVhQXYVvAWax1spJ9LT7mHub5j95xDdYIcOV3VJ
|
||||
l9CIo4VrWIT8THFN2NnjTrGq9+0TzXY0bV674bjJkfBC6v6yXs5HTetG+Uekq/xf
|
||||
FCjrrDi1+2UR9Mu2WTuvl8qn50be+mbwdJO5wE32jewxdYrVVmj19+PkaEeAwGTc
|
||||
vwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
publicKeyType TYPE_RAW_PUBLIC_KEY
|
||||
```
|
||||
|
||||
Check contents of managed resource connection secret `ess-mr-conn`. The public
|
||||
key is identical to the public key in the Claim since the Claim is using this
|
||||
managed resource.
|
||||
```shell {copy-lines="1"}
|
||||
kubectl -n vault-system exec -i vault-0 -- vault kv get /secret/crossplane-system/ess-mr-conn
|
||||
======= Metadata =======
|
||||
Key Value
|
||||
--- -----
|
||||
created_time 2022-03-18T21:21:07.9298076Z
|
||||
custom_metadata map[environment:development secret.crossplane.io/ner-uid:4cd973f8-76fc-45d6-ad45-0b27b5e9252a team:backend]
|
||||
deletion_time n/a
|
||||
destroyed false
|
||||
version 2
|
||||
|
||||
========= Data =========
|
||||
Key Value
|
||||
--- -----
|
||||
privateKey {
|
||||
"type": "service_account",
|
||||
"project_id": "REDACTED",
|
||||
"private_key_id": "REDACTED",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nREDACTED\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "ess-test-sa@REDACTED.iam.gserviceaccount.com",
|
||||
"client_id": "REDACTED",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/ess-test-sa%40REDACTED.iam.gserviceaccount.com"
|
||||
}
|
||||
privateKeyType TYPE_GOOGLE_CREDENTIALS_FILE
|
||||
publicKey -----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzsEYCokmYEsZJCc9QN/8
|
||||
Fm1M/kTPp7Gat/MXLTP3zFyCTBFVNLN79MbAKdinWi6ePXEb75vzB79IdZcWj8lo
|
||||
8trnS64QjNB9Vs4Xk5UvDALwleFN/bZeperxivDPwVPvT9Aqy/U9kohoS/LHyE8w
|
||||
uWQb5AuMeVQ1gtCTnCqQZ4d2MSVhQXYVvAWax1spJ9LT7mHub5j95xDdYIcOV3VJ
|
||||
l9CIo4VrWIT8THFN2NnjTrGq9+0TzXY0bV674bjJkfBC6v6yXs5HTetG+Uekq/xf
|
||||
FCjrrDi1+2UR9Mu2WTuvl8qn50be+mbwdJO5wE32jewxdYrVVmj19+PkaEeAwGTc
|
||||
vwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
publicKeyType TYPE_RAW_PUBLIC_KEY
|
||||
```
|
||||
|
||||
### Remove the resources
|
||||
|
||||
Deleting the Claim removes the managed resources and associated keys from Vault.
|
||||
|
||||
```shell
|
||||
kubectl delete claim my-ess
|
||||
```
|
||||
|
||||
<!-- named links -->
|
||||
|
||||
[Vault]: https://www.vaultproject.io/
|
||||
[External Secret Store]: https://github.com/crossplane/crossplane/blob/master/design/design-doc-external-secret-stores.md
|
||||
[this issue]: https://github.com/crossplane/crossplane/issues/2985
|
||||
[Kubernetes Auth Method]: https://www.vaultproject.io/docs/auth/kubernetes
|
||||
[Unseal]: https://www.vaultproject.io/docs/concepts/seal
|
||||
[Vault KV Secrets Engine]: https://developer.hashicorp.com/vault/docs/secrets/kv
|
||||
[Vault Agent Sidecar Injection]: https://www.vaultproject.io/docs/platform/k8s/injector
|
||||
[ESS Plugin Vault]: https://github.com/crossplane-contrib/ess-plugin-vault
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue