Skip to content

Latest commit

 

History

History
707 lines (568 loc) · 31.1 KB

File metadata and controls

707 lines (568 loc) · 31.1 KB

Configuration

It is highly recommended to read the Design document to understand the components that make up Backstage.

Table of Contents

Default Configuration

The Default Configuration defines the structure of all Backstage instances within a cluster. It consists of a set of YAML manifests that define Kubernetes resources for a Backstage instance. This configuration is located in the *-default-config ConfigMap in the Backstage operator namespace (usually called backstage-system or backstage-operator) and mounted to the /default-config directory of the Backstage controller container.

Backstage Default ConfigMap and CR

Default Configuration Files

Key/File Name Object Kind Object Name Mandatory Multi Version Notes
deployment.yaml appsv1.Deployment or appsv1.StatefulSet backstage- Yes No >=0.1.x* Backstage deployment, StatefulSet available from 0.9
service.yaml corev1.Service backstage- Yes No >=0.1.x Backstage Service
db-statefulset.yaml appsv1.StatefulSet backstage-psql- For local DB No >=0.1.x PostgreSQL StatefulSet
db-service.yaml corev1.Service backstage-psql- For local DB No >=0.1.x PostgreSQL Service
db-secret.yaml corev1.Secret backstage-psql-secret- For local DB No >=0.1.x Secret to connect Backstage to PGSQL
route.yaml openshift.Route backstage- No (for OCP) No >=0.1.x Route exposing Backstage service
app-config.yaml corev1.ConfigMap backstage-appconfig- No Yes >=0.2.x* Backstage app-config.yaml, multi-object 0.10
configmap-files.yaml corev1.ConfigMap backstage-files- No Yes >=0.2.x* File from configMap, multi-object from 0.10
configmap-envs.yaml corev1.ConfigMap backstage-envs- No Yes >=0.2.x Env vars from ConfigMap, multi-object from 0.10
secret-files.yaml []corev1.Secret backstage-files- No Yes >=0.2.x Backstage config file inclusions from Secret
secret-envs.yaml []corev1.Secret backstage-envs- No Yes >=0.2.x Backstage environment variables from Secret
dynamic-plugins.yaml corev1.ConfigMap backstage-dynamic-plugins- No No >=0.2.x Dynamic plugins configuration
pvcs.yaml []corev1.PersistentVolumeClaim backstage-- No Yes >=0.4.x List of PVC objects to be mounted to containers

Meanings of "Mandatory" Column:

  • Yes - Must be configured; deployment will fail otherwise.
  • For local DB - Has to be configured if spec.enableLocalDb is true (or unset) in the Backstage CR.
  • No - Optional configuration.
  • No (for OCP) - Optional configuration, working in Openshift only.

You can see examples of default configurations as part of the Operator Profiles in the default-config directory.

Default mount path

Some objects, such as: app-config, configmap-files, secret-files, dynamic-plugins, pvcs, are mounted to the Backstage Container as files or directories. Default mount path is Container's WorkingDir, if not defined it falls to "/opt/app-root/src".

Annotations

We use annotations to configure some objects. The following annotations are supported:

rhdh.redhat.com/mount-path to configure mount path.

If specified, the object will be mounted to the specified path, otherwise Default mount path will ve used. It is possible to specify relative path, which will be appended to the default mount path.

Supported objects: pvcs, secret-files, configmap-files.

Examples:

pvcs.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
  annotations:
    rhdh.redhat.com/mount-path: /mount/path/from/annotation
...

In the example above the PVC called myclaim will be mounted to /mount/path/from/annotation directory

secret-files.yaml

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
  annotations:
    rhdh.redhat.com/mount-path: /mount/path/from/annotation
...

In the example above the Secret called mysecret will be mounted to /mount/path/from/annotation directory

rhdh.redhat.com/containers for mounting volume to specific container(s)

Supported objects: pvcs, secret-files, secret-envs.

Options:

  • No or empty annotation: the volume will be mounted to the Backstage container only
  • * (asterisk): the volume will be mounted to all the containers
  • Otherwise, container names separated by commas will be used

Examples:

pvcs.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
  annotations:
    rhdh.redhat.com/containers: "init-dynamic-plugins,backstage-backend"
...

In the example above the PVC called myclaim will be mounted to init-dynamic-plugins and backstage-backend containers

secret-envs.yaml

apiVersion: v1
kind: Secret
metadata:
  name: mysecret
  annotations:
    rhdh.redhat.com/containers: "*"
...

In the example above the PVC called myclaim will be mounted to all the containers

Metadata Generation

For Backstage to function consistently at runtime, certain metadata values need to be predictable. Therefore, the Operator generates values according to the following rules. Any value for these fields specified in either Default or Raw Configuration will be replaced by the generated values.

For All the objects metadata.name is generated according to the rules defined in the Default Configuration files, column Object name. means a Name of Backstage Custom Resource owning this configuration. For example, Backstage CR named mybackstage will create K8s Deployment resource called backstage-mybackstage. Specific, per-object generated metadata described below.

  • deployment.yaml
    • spec.selector.matchLabels[rhdh.redhat.com/app] = backstage-<cr-name>
    • spec.template.metadata.labels[rhdh.redhat.com/app] = backstage-<cr-name>
  • service.yaml
    • spec.selector[rhdh.redhat.com/app] = backstage-<cr-name>
  • db-statefulset.yaml
    • spec.selector.matchLabels[rhdh.redhat.com/app] = backstage-psql-<cr-name>
    • spec.template.metadata.labels[rhdh.redhat.com/app] = backstage-psql-<cr-name>
  • db-service.yaml
    • spec.selector[rhdh.redhat.com/app] = backstage-psql-<cr-name>

Multi objects

Since version 0.4.0, Operator supports multi objects which mean the object type(s) marked as Multi=true in the table above can be declared and added to the model as the list of objects of certain type. To do so multiple objects are added to the yaml file using "---" delimiter.

For example, adding the following code snip to pvcs.yaml will cause creating 2 PVCs called backstage-<cr-name>-myclaim1 and backstage-<cr-name>-myclaim2 and mounting them to Backstage container accordingly.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim1
...
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim2
...

Default base URLs

Since version 0.6.0, the Operator may set the base URLs fields in the default app-config ConfigMap (named backstage-appconfig-<CR_name>) created per CR, based on the Route parameters and the OpenShift cluster ingress domain.

Below are the rules currently governing this behavior:

  • No change if the cluster is not OpenShift.
  • No change if spec.application.route.enabled is explicitly set to false in the CR
  • The base URLs are set to https://<spec.application.route.host> if spec.application.route.host is set in the Backstage CR.
  • The base URLs are set to https://<spec.application.route.subdomain>.<cluster_ingress_domain> if spec.application.route.subdomain is set in the Backstage CR.
  • The base URLs are set to https://backstage-<CR_name>-<namespace>.<cluster_ingress_domain>, which is the domain set by default for the Route object created by the Operator.

The following app-config fields might be updated in this default app-config ConfigMap:

  • app.baseUrl
  • backend.baseUrl
  • backend.cors.origin

Note that this behavior is done on a best-effort basis and only on OpenShift.

In any case (error or on non-OpenShift clusters), users still have the ability to override such defaults by providing custom app-config ConfigMap(s), as depicted in the app-config section.

Deployment kind

Starting from version 0.9.0, the Backstage Operator supports StatefulSet as an alternative to Deployment for Backstage deployments. To use StatefulSet, modify the deployment.yaml file in the Default Configuration to define a StatefulSet object instead of a Deployment. Ensure that you adjust any necessary fields specific to StatefulSets, such as volume claims and service names.

Raw Configuration

Raw Configuration consists of the same YAML manifests as the Default configuration, but is specific to each Custom Resource (CR). You can override any or all Default configuration keys (e.g., for deployment.yaml) or add new keys not defined in the Default configuration by specifying them in ConfigMaps.

Here’s a fragment of the Backstage spec containing Raw configuration:

spec:
  rawRuntimeConfig:
    backstageConfig: <configMap-name>  # to use for all manifests except db-*.yaml
    localDbConfig: <configMap-name>    # to use for db-*.yaml manifests

NOTE: While the Backstage Application config is separated from Database Configuration, it makes no difference which ConfigMap you use for which object; they are ultimately merged into one structure. Just avoid using the same keys in both.

Custom Resource Spec

The desired state of resources created by the Backstage Operator is defined in the Backstage Custom Resource Spec. Here’s an example of a simple Backstage CR:

apiVersion: rhdh.redhat.com/v1alpha6
kind: Backstage
metadata:
  name: mybackstage
spec:
  application:
    appConfig:
      configMaps:
        - name: my-app-config
    extraEnvs:
      secrets:
        - name: my-secrets

This Custom Resource defines a Backstage instance called mybackstage and also:

  • Adds additional app-config stored in the my-app-config ConfigMap.
  • Adds some extra environment variables stored (as key-value pairs) in the Secret called my-secrets.

The Backstage CR Spec contains the following top-level elements:

Application Configuration

This section explains how the Backstage Application is configured inside the container.

app-config

As documented in the Backstage documentation, the Backstage application is configured with one or more app-config files, which are merged from the first to the last. The Operator can contribute to the app-config list by mounting the ConfigMap defined in default-config/app-config.yaml, as specified in the Default Configuration section. Additionally, it is possible to define an array of external, user-created ConfigMaps located in the same namespace as the Backstage CR using spec.application.appConfig.

For example, consider the following ConfigMaps containing Backstage app-config configuration:

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-app-config
data:
  my-app-config.yaml: |
    # Some fragment of app-config here

This app-config file can be mounted to the Backstage container as follows:

spec:
  application:
    appConfig:
      mountPath: /my/path
      configMaps:
        - name: my-app-config

In this example, additional app-config is defined in the ConfigMaps called my-app-config, and it is mounted as YAML files to the /my/path directory in the Backstage container:

/my/path/my-app-config.yaml

The ConfigMap key/value defines the file name and content, and this app-config will be applied as the last in the -app-config arguments sequence. Therefore, as per this example, the ConfigMap should be created in the namespace as a prerequisite. Then the Operator will create the file /my/path/my-app-config.yaml and add it to the end of the Backstage command-line arguments as follows:

--config /my/path/my-app-config.yaml

Note: It is possible to define several app-config files inside one ConfigMap (even if there are no visible reasons for it) but since it is a Map, the order of how they are applied is not guaranteed. On the other hand, Backstage application merges the chain of app-config files from first to last, so order is important. Taking this into account, keeping several app-config files inside one ConfigMap is NOT recommended. For this case consider defining several one-entry ConfigMaps instead.

Includes and Dynamic Data (including extra files and extra environment variables) support configuring additional ConfigMaps and Secrets.

Extra Files

Extra files can be mounted from pre-created ConfigMaps or Secrets. By default, they are mounted to the Backstage (backstage-backend) container, but starting from v1alpha4 (v0.8) it is possible to specify the container(s) and initContainer(s) names in the containers field. For example, consider the following objects in the namespace:

apiVersion: v1
kind: ConfigMap
metadata:
  name: cm1
data:
  file11.txt: |
    My file11 content
  file12.txt: |
    My file12 content

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: cm2
data:
  file21.txt: |
    My file21 content
  file22.txt: |
    My file22 content

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: cm3
data:
  file31.txt: |
    My file31 content
  file32.txt: |
    My file32 content
---
apiVersion: v1
kind: Secret
metadata:
  name: secret1
stringData:
  secret11.txt: |
    secret-content
---
apiVersion: v1
kind: Secret
metadata:
  name: secret2
stringData:
  secret21.txt: |
    secret-content
  secret22.txt: |
    secret-content

These objects can be mounted to the Backstage container as follows:

spec:
  application:
    extraFiles:
      mountPath: /my/path
      configMaps:
        - name: cm1
        - name: cm2
          key: file21.txt
          containers:
            - "*"
        - name: cm3
          mountPath: /my/cm3/path
          containers:
            - backstage-backend
            - install-dynamic-plugins
      secrets:
        - name: secret1
          key: file3.txt
          containers:
            - install-dynamic-plugins
        - name: secret2
          mountPath: /my/secret2/path

The Operator will either get all entries from the specified object (if no key is specified) or will pick the specified one, creating volumes per object and mounting the files to the Backstage container. Backstage CRD introduces mountPath field which allows to mount ConfigMap or Secret to specified path. A combination of key/mountPath fields impacts on whether the Volume will be mounted with or without subPath as following:

  • If nothing specified: each key/values will be mounted as filename/content with subPath
  • If key specified, with or without mountPath: the specified key/value will be mounted with subPath
  • If only mountPath specified: a directory containing all the key/value will be mounted without subPath

Since v1alpha4 (v0.8) it is possible to specify the container(s) and initContainer(s) names in the containers field. If not specified, the volume will be mounted to the Backstage container only. The following options are supported:

  • No or empty field: the volume will be mounted to the Backstage container only
  • * (asterisk) as the first and only array element: the volume will be mounted to all the containers
  • explicit container names as the array elements

Note: A volume mounted with subPath is not automatically updated by Kubernetes. So, by default, the Operator watches such ConfigMaps/Secrets and refreshes the Backstage Pod when they change.

Note: To limit read access to Secrets by the Operator Service Account (for security reasons), we do not support mounting files from Secrets without mountPath and key specified.

In our example, the following files will be mounted:

// Each file is mounted individually (related resources are watched by Operator):
/my/path/
  file11.txt - to backstage-backend container only
  file12.txt - to backstage-backend container only
  file21.txt - to all containers
  file3.txt - to install-dynamic-plugins container only
// Directory mounted (related resources are auto-updated):
/my/cm3/path/ - to backstage-backend and install-dynamic-plugins containers
  file31.txt 
  file32.txt 
/my/secret2/path/ - to backstage-backend container only
  file1 
  file2  
PersistentVolumeClaims

It possible to mount directory from pre-created PersistentVolumeClaim using spec.application.extraFiles.pvcs field. PersistentVolumeClaims are mounted as a directory to the container's path defined as following:

  • spec.application.extraFiles.pvcs[].mountPath if defined
  • Then spec.application.extraFiles.mountPath/ if defined
  • Then Backstage container's WorkingDir if defined
  • And if nothing defined it falls to default path (/opt/app-root/src)

Same as for ConfigMaps and Secrets, since v1alpha4 (v0.8) it is possible to specify the container(s) and initContainer(s) names in the containers field. If not specified, the volume will be mounted to the Backstage container only. The following options are supported:

  • No or empty field: the volume will be mounted to the Backstage container only
  • * (asterisk) as the first and only array element: the volume will be mounted to all the containers
  • explicit container names as the array elements

For example, consider the following objects in the namespace:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim1
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim2
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 2Gi

These objects can be mounted to the Backstage container as follows:

spec:
  application:
    extraFiles:
      mountPath: /my/path
      pvcs:
        - name: myclaim1
        - name: myclaim2
          mountPath: /vol/my/claim

As a result, the following directories will be mounted:

/my/path/myclaim1
/vol/my/claim

Extra Environment Variables

Extra environment variables can be injected into the Backstage container from pre-created ConfigMaps or Secrets, as well as specified directly in the Custom Resource.

Since v1alpha4 (v0.8) it is possible to specify the container(s) and initContainer(s) names in the containers field. If not specified, the environment variables will be injected to the Backstage container only. The following options are supported:

  • No or empty field: the volume will be mounted to the Backstage container only
  • * (asterisk) as the first and only array element: the volume will be mounted to all the containers
  • explicit container names as the array elements

For instance, consider the following objects in the namespace:

apiVersion: v1
kind: ConfigMap
metadata:
  name: cm1
data:
  ENV_VAR1: "1"
  ENV_VAR2: "2"

---
apiVersion: v1
kind: Secret
metadata:
  name: secret1
data:
  ENV_VAR3: "base64encoded3"
  ENV_VAR4: "base64encoded4"

In addition, we may want to create the environment variable MY_VAR=my-value by declaring it directly in the Custom Resource:

spec:
  application:
    extraEnvs:
      configMaps:
        - name: cm1
          key: ENV_VAR1
          containers:
            - "*" 
      secrets:
        - name: secret1
      envs:
        - name: MY_VAR
          value: "my-value"
          containers:
           - install-dynamic-plugins

Similar to extraFiles, you can specify a key name to inject only a particular environment variable. In our example, the following environment variables will be injected:

ENV_VAR1 = 1 - to all containers
ENV_VAR3 = 3 - to backstage-backend container only
ENV_VAR4 = 4 - to backstage-backend container only
MY_VAR = my-value - to install-dynamic-plugins container only

Dynamic Plugins

The Operator can configure Dynamic Plugins. To support Dynamic Plugins, the Backstage deployment should contain a dedicated initContainer called install-dynamic-plugins (see RHDH deployment.yaml). To enable the Operator to configure Dynamic Plugins for a specific Backstage instance (CR), the user must create a ConfigMap with an entry called dynamic-plugins.yaml.

For example, the dynamic-plugins-config ConfigMap contains a simple Dynamic Plugins configuration, which includes predefined default plugins in dynamic-plugins.default.yaml and the GitHub plugin provided in the package located at ./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-github-dynamic.

kind: ConfigMap
apiVersion: v1
metadata:
  name: dynamic-plugins-config
data:
  dynamic-plugins.yaml: |
    includes:
      - dynamic-plugins.default.yaml
    plugins:
      - package: './dynamic-plugins/dist/backstage-plugin-catalog-backend-module-github-dynamic'
        disabled: false
        pluginConfig:
          catalog:
            providers:
              github:
                organization: "${GITHUB_ORG}"
                schedule:
                  frequency: { minutes: 1 }
                  timeout: { minutes: 1 }
                  initialDelay: { seconds: 100 }

To configure it with the Backstage CR, the following spec should be included:

spec:
  application:
    dynamicPluginsConfigMapName: "dynamic-plugins-config"

In order to configure plugins without defaults, initialize includes with empty array:

...
data:
  dynamic-plugins.yaml: |
    includes: []

NOTE: Before version 0.8.0, the Operator overrode the default Dynamic Plugins configuration with the one specified in Custom Resource. This meant that the user had to specify all the default plugins in the Custom Resource. From version 0.8.0, the Operator merges the default Dynamic Plugins configuration with the one specified in the Custom Resource. This allows users to override only the parts they want to change, while still keeping the default plugins. Note, merging is performed on plugins top-level fields only, so the complex fields like 'pluginConfig' or 'dependencies' are not merged deeply and will be replaced by the ones specified in the Custom Resource.

Starting from version 0.7.0, the Operator supports dynamic plugins dependencies. For more details, refer to Dynamic Plugins Dependencies.

Route

To support Backstage service routing on OpenShift, the Operator can create a route.openshift.io resource, as specified in the spec.application.route field. Here’s an example:

spec:
  application:
    route:
      enabled: true
      host: my.host
      subdomain: mysubdomain
      tls:
        certificate: "certificatecontent"
        externalCertificateSecretName: "my-certificate"
        key: "keycontent"
        caCertificate: "caCertificatecontent"

Here, the user can specify some of the OpenShift Route specifications fields. The names of the Backstage spec.application.route fields correspond to the names of Route specification fields and follow the same default rules if not specified.

Also note that securing Routes with external certificates in TLS secrets (via the spec.application.route.tls.externalCertificateSecretName CR field) is a Technology Preview feature in OpenShift. It requires enabling the RouteExternalCertificate OpenShift Feature Gate and might not be functionally complete. See Creating a route with externally managed certificate for more details.

Deployment Configuration

The Backstage CRD contains spec.deployment field, allowing to change the shape of the Backstage Deployment resource

Deployment Kind

By default, the Operator creates a resource of Kind, defined in default configuration, (usually 'Deployment') for Backstage instances. Starting from version 0.9.0, it is possible to choose between Deployment and StatefulSet. To use StatefulSet, set the following field in the Backstage CR::

spec:
  deployment:
    kind: StatefulSet

The motivation for this feature is to allow using StatefulSet-specific capabilities, such as Pod Management Policies or Volume Claim Templates.

Deployment Patching

spec.deployment.patch field contains a fragment of the apps.Deployment or StatefulSet resource. This patching is performed via strategic merge patch using Kustomize's library.

For example, the following specification fragment will:

  • Set an additional volume named my-volume and mount it to /my/path of the Backstage container.
  • Replace existed dynamic-plugins-root volume
  • Set the CPU request for Backstage containers to 250m.
  • Set the label my=true for the Backstage Pod.
  • Adds another my-sidecar container

Note: It is assumed that the Backstage container name as defined in the default configuration is backstage-backend.

spec:
  deployment:
    patch:
      spec:
        template:
          metadata:
            labels:
              my: true
          spec:
            containers:
              - name: backstage-backend
                volumeMounts:
                  - mountPath: /my/path
                    name: my-volume
                resources:
                  requests:
                    cpu: 250m
              - name: my-sidecar
                image: quay.io/my-org/my-sidecar:latest
            volumes:
              - ephemeral:
                  volumeClaimTemplate:
                    spec:
                      storageClassName: "special"
                name: my-volume
              - $patch: replace
                name: dynamic-plugins-root
                persistentVolumeClaim:
                  claimName: dynamic-plugins-root
Handling Discriminated Unions

When patching Kubernetes resources that contain discriminated unions (fields where one field determines which other fields are valid), you may need to use the $patch: delete directive to remove conflicting fields.

A common example is changing the Deployment strategy from RollingUpdate to Recreate. The strategy.type field acts as a discriminator:

  • When type: RollingUpdate, the rollingUpdate field is valid
  • When type: Recreate, the rollingUpdate field must not be present

To change from RollingUpdate to Recreate strategy, use the $patch: delete directive:

spec:
  deployment:
    patch:
      spec:
        strategy:
          type: Recreate
          rollingUpdate:
            $patch: delete

Database Configuration

Backstage uses PostgreSQL as a storage solution. The Operator can:

  • Pre-create and manage a local database (i.e., created as a StatefulSet in the same namespace as the Backstage Deployment).
  • Allow Backstage to use an external PostgreSQL database.

This is dictated by the following configuration:

spec:
  database:
    enableLocalDb: [true] or false

If local DB is enabled (which is simpler but not recommended for production), the Operator will:

  • Create a StatefulSet according to the template defined in either the default or raw db-statefulset.yaml.
  • Create a Service for accessing the DB.
  • Generate a password for this DB.
  • Inject corresponding environment variables containing DB connection information (host, port, username, password) into the Backstage container.

If local DB is disabled (enableLocalDb: false), then the secret with DB connection information must be created manually and specified in either spec.database.authSecretName or one of spec.application.extraEnvs.secrets.

For more information, refer to the External DB Integration manual.