Anatomy of the example chart (and how you can build your own)

This document explains the mechanisms used in this chart to build a Alfresco platform to deploy on Kubernetes.

Why an example charts when there is already the alfresco-content-services?

With alfresco-content-services chart we tried to provide something that can deploy most of our software components - still not all of them are included - but also serves as a basis for customization for real world scenarios. These two paradigms have actually proven to fight one another and we think this chart is doing way too much things to really be both understandable - and serve the example purpose and to build upon - and to also offer an holistic and straight forward deployment mean: There’s no one size fit all configuration and when it comes to deployment and configuration of a platform which involves so many different components, including third party ones (database, message broker, Identity provider, …).

For that reason we have started creating individual charts for Alfresco components in the alfresco-helm-charts repository (more to come and PR are welcome).

High overview of the example chart

This example chart extensively leverages the concept of chart dependencies in Helm.

One of the goal is to make this chart as simple as possible while still providing way to deploy exactly the platform you. So most of the kubernetes resources are actually provided in the individual component charts we declare as dependencies and this chart is mostly creating the plumbing between each components we want to use. This type of Helm chart is often referred to as an umbrella chart.

Here we will see how to declare dependencies an configure them properly so they work together.

Architecture of the deployment

In the document bellow we discuss an initial basic setup as shown below:

flowchart TB
classDef alf fill:#0c0
classDef thrdP fill:#ffa

subgraph legend
  t[3rd party component]:::thrdP
  a[Alfresco component]:::alf
end

repodb[(Repository\nDatabase)]:::thrdP
amq{{Message\nBroker}}:::thrdP
repo[Alfresco\nRepository]:::alf

repo ==> amq
repo ==> repodb

Note: there is no search component neither is there a transformation service.

This set up will be enriched with more components configured for SSO using vanilla keycloak here

Pre-requisites

Kubernetes

As this chart is meant to deploy on localhost if you want to use it you’ll need a kubernetes distribution setup on your local machine.

We usually use KinD but Docker-desktop, Podman or miinkube should be OK to use if properly configured.

Give your kubernetes setup 8GB of RAM and a few CPUs.

Ingress

You will also need to have the nginx-ingress installed and configured to handle local traffic on port 80.

:warning: If you use KinD, you need to install a patched version of the NGINX ingress as described here If using another kubernetes distribution check it doesn’t come with a default ingress controller different from NGINX and if so, disable it. Once done install the NGINX ingress controller

Make sure the nginx ingress controller have the following settings enabled:

  • allow-snippet-annotations set to true
  • proxy-buffer-size set to 12k

You can do that at installation time using nginx-ingress annotations or after the installation of the ingress, using the command below (e.g. for KinD):

kubectl -n ingress-nginx patch cm ingress-nginx-controller -p \
  '{"data": {"allow-snippet-annotations":"true","proxy-buffer-size":"12k"}}'

Helm

Make sure you have Helm installed on your machine. It is often provided has part of the kubernetes desktop distributions but if you don’t have install it.

Starting building the example (your) chart

Base skeleton

Before we start referencing components’ charts we need to create a chart structure.

To do that, use the command below:

helm create mychart && cd $_

As the chart we want to create is simply an umbrella chart, there are actually too much things this command scaffolds. We’ll continue and cleanup what we don’t need.

rm templates/*.yaml templates/NOTES.txt

Now we can give the chart some metadata, like a version, a description and so on. Find more details in the Helm doc.

Repository Database

To start with, we need to give Alfresco a database. Here we will use the PostgreSQL Bitnami chart. It is simple and lightweight enough for the purpose of this example.

Declaring the dependency

Dependencies are declared in the Chart.yaml file adding the section below:

dependencies:
  - name: postgresql
    repository: oci://registry-1.docker.io/bitnamicharts
    version: 13.4.0
    alias: repository-database

The charts declared as dependencies are also called “subcharts”.

If you’re building your own chart to deploy on a production environment, you may want to use a database service that either lives outside of the kubernetes cluster (e.g. RDS) or use a more “production ready” deployment for Kubernetes (There are some very good PostgreSQL Kubernetes operators such as CrunchyData).

Configuring the database subchart

Now that the dependency is declared we can configure the subchart to match our needs. Each chart should provide its own set of values, which are usually documented in the main README.md file of the source repository. In our case the documentation useful to us is here.

For example we want to:

  • Set the database name
  • Set the database username
  • Set the database password
  • Disable persistence

As per the documentation mentioned above, this translates to the yaml configuration below in the values.yaml file of the umbrella chart:

repository-database:
  nameOverride: repository-database
  auth:
    database: alfresco
    username: alfresco
    password: alfresco
  primary:
    persistence:
      enabled: false

The values documented for the chart we depend on are placed under a YAML key named after the chart name or its declared alias (here repository-database).

Also note we set a nameOverride value. This ensures consistent resource naming when the Helm template is rendered, and in particular when giving the service a name (as this name is needed to tell other components how to connect to the database).

Message Broker

Declaring the broker dependency

For the message broker we will use an Alfresco provided chart. This is because there no official or well maintained chart one can rely on. In a real world scenario the best options remains to use a managed ActiveMQ instance outside of the kubernetes cluster.

Just as for the database chart we declare the activemq chart as a dependency of our umbrella chart.

  - name: activemq
    repository: https://alfresco.github.io/alfresco-helm-charts/
    version: 3.4.1

Configuring the message broker subchart

Again, we start by taking a look at the documented values for the chart we want to use.

For our example purpose we’ll configure the message broker with:

  • a defined username and password
  • persistence disabled

So we’re adding the values below in the values.yamlfile of the umbrella chart:

activemq:
  nameOverride: activemq
  persistence:
    enabled: false
  adminUser:
    user: alfresco
    password: alfresco

Now step back and think

So far w have just setup 2 independent components. They don’t interact together. But at this point we need to wonder what are the key information generated by this chart, that we’ll need to configure other charts?

That’s basically the connection details:

  • database URL
  • database username
  • database password
  • message broker URL
  • message broker username
  • message broker password

The credentials are statically defined in the values file so that’s easy. The URLs on the other hand, are slightly more tricky as they are built dynamically by the each subchart.

All Alfresco components’ charts support 2 ways to provide this type of configuration:

  • through the usual values
  • through configmaps and secrets

For example, if we want to tell the ACS repo where its database is located we can use either of the following YAML data structure:

# value based configuration
...
  database:
    url: url://...
    username: scott
    password: tiger
# resource based configuration
...
  database:
    existingConfigMap:
      name: db-cm
      keys:
        url: DB_URL
    existingSecret:
      name: db-secret
      keys:
        username: DB_USER
        password: DB_PASS

Note: the path to the database key may vary from one Alfreso chart to another, check each chart README page.

The key difference between values based config and resource based config is that values can only take static strings, while resource can be pre-existing configmaps or secrets, or can even be rendered from template in the main umbrella chart, thus allowing for a bit of manipulation. The later is the approach we prefer using to configure Alfresco component charts.

Alfresco Repository

Declaring the repository dependency

  - name: alfresco-repository
    repository: https://alfresco.github.io/alfresco-helm-charts/
    version: 0.1.3

Configuring the repository subchart

The Alfresco repository chart documentation shows how to configure the repo db and message broker using configmaps and secrets:

alfresco-repository:
  nameOverride: alfresco-repository
  # Use Community
  replicaCount: 1
  image:
    repository: alfresco/alfresco-content-repository-community
  # Ensure Alfresco is served on the localhost domain
  ingress:
    hosts:
      - host: localhost
        paths:
          - path: /
            pathType: Prefix
          - path: /api-explorer
            pathType: Prefix
  # Actual component configuration
  configuration:
    db:
      existingConfigMap:
        name: repository-database
      existingSecret:
        name: repository-database
    messageBroker:
      existingConfigMap:
        name: repository-message-broker
      existingSecret:
        name: repository-message-broker

Here we omit the *.keys.* values as we will create both the configmap and the secret and we have the freedom to use the default keys keys as documented in the subchart README.

Creating the config resources

Let’s first create the secrets where we credentials will be available to components which needs them. To avoid spreading credentials where not needed, we’ll have one for each service:

  • the database secret can be defined as follow in `templates/secret-db.yaml

    apiVersion: v1
    kind: Secret
    metadata:
      name: repository-database
      labels: {{- include "acs-sso-example.labels" . | nindent 4 }}
    data:
    {{- with (index .Values "repository-database") }}
      DATABASE_USERNAME: {{ .auth.username | b64enc | quote }}
      DATABASE_PASSWORD: {{ .auth.password | b64enc | quote }}
    {{- end }}
    
  • the message broker secret in `templates/secret-mq.yaml

    apiVersion: v1
    kind: Secret
    metadata:
      name: repository-message-broker
      labels: {{- include "acs-sso-example.labels" . | nindent 4 }}
    data:
    {{- with .Values.activemq.adminUser }}
      BROKER_USERNAME: {{ .user | b64enc | quote }}
      BROKER_PASSWORD: {{ .password | b64enc | quote }}
    {{- end }}
    

Now we can create configmaps to compute the services URL:

  • the database configmap in templates/confgimap-db.yaml

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: repository-database
      labels: {{- include "acs-sso-example.labels" . | nindent 4 }}
    data:
      DATABASE_DRIVER: org.postgres.Driver
      {{- with (index .Values "repository-database") }}
      {{- $dbCtx := dict "Values" . "Chart" $.Chart "Release" $.Release }}
      {{- $dbHost := include "postgresql.v1.primary.fullname" $dbCtx }}
      DATABASE_URL: >-
        {{ printf "jdbc:postgresql://%s:5432/%s" $dbHost .auth.database }}
      {{- end }}
    

The URL is built dynamically using string concatenation and the very same named template that’s used in the postgresql subchart to define the postgresql service name: see the code.

Note we are building a $dbCtx to mimic the context the subchart uses when evaluating postgresql.v1.primary.fullname. This is where it’s important to have a nameOverridevalue defined for each subchart which provides a service that must be accessed by another subchart.

  • message broker configmap in templates/configmap-mq.yaml

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: repository-message-broker
      labels: {{- include "acs-sso-example.labels" . | nindent 4 }}
    data:
      {{- with .Values.activemq }}
      {{- $mqCtx := dict "Values" . "Chart" $.Chart "Release" $.Release }}
      {{- $mqUrl := printf "nio://%s-broker" (include "activemq.fullname" $mqCtx) }}
      BROKER_URL: >-
        {{ include "alfresco-common.activemq.url.withFailover" $mqUrl }}
      {{- end }}
    

We also provide named template one can use to ease creating config resources. Here we’re using alfresco-common.activemq.url.withFailover - which returns a valid failover activemq URL as expected by Alfresco components - but there are some others you may find handy in the alfresco-common chart.

The config resources presented here have been slightly simplified for the sake of readability, so for instance they do not support changing the default port in the postgresql or activemq chart but that doesn’t change the higher level logic.

First deployment

It is now time to test our deployment as we have a system that matches the architecture we wanted and all the configured components should be configured properly to work together.

helm dep build # pull dependencies
helm install --generate-name --atomic .

You can now open your browser on http://localhost/alfresco

Next steps

To discover more ways to configure Alfresco components you can take a look at the SSO part of this chart. It explains how to use a vanilla Keycloak chart with various Alfresco components, thus exposing other means to tweak them for your needs.