Skip to content

Instantly share code, notes, and snippets.

@negz
Last active March 21, 2021 17:06
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save negz/cb9ee4236e0f1dfff514617584a1c8c6 to your computer and use it in GitHub Desktop.
Save negz/cb9ee4236e0f1dfff514617584a1c8c6 to your computer and use it in GitHub Desktop.
Modelling a simple Wordpress application
# In this YAML an ApplicationDefinition defines the schema of a
# Wordpress, while a Composition specifies what resources the
# Wordpress will be composed of, and how they'll be created.
---
apiVersion: crossplane.io/v1alpha1
kind: ApplicationDefinition
metadata:
name: wordpresses.apps.example.org
spec:
crdSpecTemplate:
group: apps.example.org
version: v1alpha1
names:
kind: Wordpress
listKind: WordpressList
plural: wordpresses
singular: wordpress
validation:
openAPIV3Schema:
properties:
image:
type: string
type: object
serviceAccountRef:
namespace: crossplane-system
name: wordpresses.apps.example.org
---
apiVersion: crossplane.io/v1alpha1
kind: Composition
metadata:
name: wordpresses.apps.example.org
annotations:
crossplane.io/default: "true"
spec:
from:
apiVersion: apps.example.org/v1alpha1
kind: Wordpress
to:
- base:
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: wordpress
image: wordpress:4.6.1-apache
env:
- name: WORDPRESS_DB_HOST
valueFrom:
secretKeyRef:
key: endpoint
- name: WORDPRESS_DB_USER
valueFrom:
secretKeyRef:
key: username
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
key: password
ports:
- containerPort: 80
name: wordpress
patches:
- fromFieldPath: "spec.image"
toFieldPath: "spec.containers[0].image"
- fromFieldPath: "metadata.name"
toFieldPath: "metadata.name"
- fromFieldPath: "metadata.name"
toFieldPath: "spec.selector.matchLabels[wordpress]"
- fromFieldPath: "metadata.name"
toFieldPath: "spec.template.metadata.labels[wordpress]"
- fromFieldPath: "metadata.name"
toFieldPath: "spec.containers[0].env[0].valueFrom.secretKeyRef.name"
- fromFieldPath: "metadata.name"
toFieldPath: "spec.containers[0].env[1].valueFrom.secretKeyRef.name"
- fromFieldPath: "metadata.name"
toFieldPath: "spec.containers[0].env[2].valueFrom.secretKeyRef.name"
- base:
apiVersion: v1
kind: Service
spec:
ports:
- port: 80
type: LoadBalancer
patches:
- fromFieldPath: "metadata.name"
toFieldPath: "metadata.name"
- fromFieldPath: "metadata.name"
toFieldPath: "spec.selector[wordpress]"
- base:
apiVersion: database.example.org/v1alpha1
kind: MySQLInstanceBinding
spec:
engineVersion: "5.7"
patches:
- fromFieldPath: "metadata.name"
toFieldPath: "metadata.name"
- fromFieldPath: "metadata.name"
toFieldPath: "spec.infrastructure.writeConnectionSecretToRef.name"
# In this YAML an ApplicationDefinition declares the resources it
# should produce inline. This implies that a Wordpress can only
# ever render to a Deployment and a Service. It also implies that
# the person who defines the schema of a Wordpress must always be
# the same person who defines the template used to render it.
---
apiVersion: crossplane.io/v1alpha1
kind: ApplicationDefinition
metadata:
name: wordpresses.apps.example.org
spec:
crdSpecTemplate:
group: apps.example.org
version: v1alpha1
names:
kind: Wordpress
listKind: WordpressList
plural: wordpresses
singular: wordpress
validation:
openAPIV3Schema:
properties:
image:
type: string
type: object
serviceAccountRef:
namespace: crossplane-system
name: wordpresses.apps.example.org
resources:
- base:
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: wordpress
image: wordpress:4.6.1-apache
env:
- name: WORDPRESS_DB_HOST
valueFrom:
secretKeyRef:
key: endpoint
- name: WORDPRESS_DB_USER
valueFrom:
secretKeyRef:
key: username
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
key: password
ports:
- containerPort: 80
name: wordpress
patches:
- fromFieldPath: "spec.image"
toFieldPath: "spec.containers[0].image"
- fromFieldPath: "metadata.name"
toFieldPath: "metadata.name"
- fromFieldPath: "metadata.name"
toFieldPath: "spec.selector.matchLabels[wordpress]"
- fromFieldPath: "metadata.name"
toFieldPath: "spec.template.metadata.labels[wordpress]"
- fromFieldPath: "metadata.name"
toFieldPath: "spec.containers[0].env[0].valueFrom.secretKeyRef.name"
- fromFieldPath: "metadata.name"
toFieldPath: "spec.containers[0].env[1].valueFrom.secretKeyRef.name"
- fromFieldPath: "metadata.name"
toFieldPath: "spec.containers[0].env[2].valueFrom.secretKeyRef.name"
- base:
apiVersion: v1
kind: Service
spec:
ports:
- port: 80
type: LoadBalancer
patches:
- fromFieldPath: "metadata.name"
toFieldPath: "metadata.name"
- fromFieldPath: "metadata.name"
toFieldPath: "spec.selector[wordpress]"
- base:
apiVersion: database.example.org/v1alpha1
kind: MySQLInstanceBinding
spec:
engineVersion: "5.7"
patches:
- fromFieldPath: "metadata.name"
toFieldPath: "metadata.name"
- fromFieldPath: "metadata.name"
toFieldPath: "spec.infrastructure.writeConnectionSecretToRef.name"
# The application operator authors a wordpress CR
# for either of the above cases. When a composition
# is annotated as the default the app operator need
# not be aware of the concept of compositions.
---
apiVersion: apps.example.org/v1alpha1
kind: Wordpress
metadata:
namespace: default
name: my-cool-wordpress
spec:
image: 5.3.2-php7.2-apache
@negz
Copy link
Author

negz commented Mar 24, 2020

This gist models the case in which an application developer wants to define a kind: Wordpress application custom resource that will always render to exactly one kind: Deployment and one kind: Service.

Some observations:

  • one-to-one.yaml is more succinct by 12 lines of YAML.
  • one-to-one.yaml has one fewer concept; kind: Composition.
  • one-to-one.yaml constrains a kind: Wordpress to rendering a kind: Deployment and a kind: Service. If a use case arose where we instead wanted to render a kind: KubernetesApplication we'd need to introduce a kind: RemoteWordpress or similar to do so.
  • one-to-one.yaml eliminates the concept of kind: Composition from applications, but not from Crossplane. The concept (or something like it) must still be learned by infrastructure operators who wish to publish infrastructure, and application operators who wish to consume that infrastructure.
  • one-to-one.yaml means that only the person who defines that a kind: Wordpress exists may define how a kind: Wordpress should be composed.

@negz
Copy link
Author

negz commented Mar 24, 2020

https://github.com/crossplane/app-wordpress/blob/b51da8/helm-chart/templates/app.yaml#L13

I derived this example scenario from our app-wordpress example template stack. While doing so I noticed that even our basic template stack example has a use case for supporting multiple application compositions; its Helm template branches based on the setting of spec.provisionPolicy and will either provision a new KubernetesCluster to deploy Wordpress to, or try to use an existing one.

@negz
Copy link
Author

negz commented Mar 24, 2020

one-to-one.yaml means that only the person who defines that a kind: Wordpress exists may define how a kind: Wordpress should be composed.

This constraint has interesting implications around how an application consumes infrastructure.

In our current app-wordpress template stack kind: Wordpress provisions a kind: MySQLInstance claim for Wordpress to use as its database. If we presume an ApplicationDefinition will usually be written by an application developer (the developers of Wordpress presumably author the Wordpress Helm chart), then in one-to-one.yaml those application developers must define how a kind: Wordpress should be composed. Presumably this means they'll be limited to rendering well known (core?) resource claim kinds? If ExampleCorp Ltd would rather their kind: Wordpress produced a kind: VerySecureMySQLInstanceBinding they must fork and maintain a variant of the upstream ApplicationDefinition. Presumably they'd have to change the API group or kind to avoid conflicts with upstream. Maybe they now have kind: VerySecureWordpress, or kind: Wordpress, apiVersion: example.corp.org/v1alpha.

Conversely in one-to-many.yaml, the infrastructure operator at ExampleCorp could introduce a new kind: Composition that was mostly the same as the official upstream one, but rendered a kind: VerySecureMySQLInstanceBinding instead of a kind: MySQLInstanceBinding and annotate it as the default. The application operators wouldn't need to care about any of this; they'd author a kind: Wordpress just as they would have if their infrastructure operators hadn't provided a new default composition to override the database binding.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment