CNE: Automatic Environment Licensing
The DXP operator can license Liferay DXP environments automatically through the provisioning service of One Liferay, Liferay’s customer portal. When you enable licensing, the operator activates the environment with an activation code, gets its license, and checks the license again every ten minutes by default. Each check is a heartbeat. You can also turn on add-on delivery, so the Marketplace add-ons in your subscription reach the Liferay DXP pods.
By default, the operator licenses an environment online and needs outbound HTTPS access to https://api.one.liferay.com. When an environment has no direct access to the provisioning service, you can license it offline with an activation bundle instead.
An environment is one logical Liferay DXP cluster in its own Kubernetes namespace. Run only one environment in each namespace, because the operator identifies environments by namespace. The environment’s LiferayEnvironment resource, short name lenv, holds its settings and the status the operator reports.
- To check an environment’s license and fix errors, see CNE: Checking and Troubleshooting Environment Licensing.
- To license an environment manually by storing and mounting
license.xml, see your cloud provider’s license configuration steps in CNE AWS Ready: Managing Secrets and Licenses, CNE Azure Ready: Managing Secrets and Licenses, or CNE GCP Ready: Managing Secrets and Licenses. On Kubernetes Ready, see Configuring External Dependencies.
The examples use the namespace liferay and the LiferayEnvironment name liferay-default. Replace them with your own, including in Secret names such as liferay-default-activation.
Installing the Operator
CNE AWS Ready and CNE GCP Ready install the operator for you with infrastructure provider chart 1.1.0 or later, and CNE Azure Ready installs it with the liferay-platform-components chart, in the dxp-operator-system namespace.
On Kubernetes Ready, install the operator yourself. Install it before enabling licensing on the liferay-default chart, or the chart’s Helm install or upgrade fails. Installing the operator needs cluster-administrator credentials and Kubernetes 1.30 or later.
-
Install the
liferay-dxp-operatorchart:helm upgrade --install dxp-operator \ oci://us-central1-docker.pkg.dev/external-assets-prd/liferay-helm-chart/liferay-dxp-operator \ --create-namespace \ --namespace dxp-operator-system \ --set fullnameOverride=dxp-operator \ --version 0.2.0 -
Confirm the operator is running:
kubectl --namespace dxp-operator-system rollout status deployment/dxp-operator
If you plan to activate environments with the activation workflow instead of kubectl, rerun the helm upgrade --install command with the value specified in Activating with Argo Workflows.
Version 4.0.0 and later of the liferay-default chart need operator chart 0.2.0 or later. Without it, the license never reaches the Liferay DXP pods, so upgrade the operator before you upgrade the liferay-default chart. Operator chart 0.2.0 rejects the gracePeriod value, so if you set it, rename it to provisioningGracePeriod or the install fails.
Turning On the Marketplace Volume
The operator stores Marketplace add-ons and offline activation bundles on its marketplace volume. Without the volume, add-on downloads fail. The AWS and GCP infrastructure provider charts turn it on for you, but CNE Azure Ready 0.3.0 doesn’t provide it, so Marketplace add-on delivery and offline licensing aren’t available there. On Kubernetes Ready, turn it on if your subscription includes Marketplace add-ons or you license offline.
On every platform that provides the volume, the stored add-ons reach the Liferay DXP pods only when you also turn on add-on delivery on the liferay-default chart.
The volume needs a PersistentVolumeClaim with the ReadWriteMany access mode in the dxp-operator-system namespace. Create the claim yourself, or have the operator chart create it on an existing storage volume. For the second option, add --set flags to the command below for marketplace.csi.driver, marketplace.csi.volumeHandle, and any marketplace.csi.volumeAttributes your CSI driver needs.
The operator runs as user and group ID 65532, so the volume’s root must be writable by that ID. If your CSI driver applies fsGroup, add --set podSecurityContext.fsGroup=65532 to the command below. Otherwise, give the volume’s root that owner.
Rerun the operator’s install command with the marketplace values, plus any values you set before. Replace [claim name] with the name of your claim, or with a name for the claim the chart creates:
helm upgrade --install dxp-operator \
oci://us-central1-docker.pkg.dev/external-assets-prd/liferay-helm-chart/liferay-dxp-operator \
--create-namespace \
--namespace dxp-operator-system \
--set fullnameOverride=dxp-operator \
--set marketplace.enabled=true \
--set marketplace.claimName="[claim name]" \
--version 0.2.0
Enabling Licensing
The licensing values need version 2.0.1 or later of the chart you install: liferay-default, liferay-aws, or liferay-gcp. licensing.enabled on the liferay-default chart is false by default. When you set it to true, the chart creates a LiferayEnvironment named liferay-default, or named after the chart’s nameOverride value if you set one.
On Kubernetes Ready, set the licensing values in your liferay-default chart’s values file. If your cluster runs Argo Workflows, or your chart isn’t version 3.0.0, set licensing.enabled to true:
licensing:
enabled: true
If your cluster doesn’t run Argo Workflows and your chart is version 3.0.0, also set licensing.onlineActivationWorkflow.enabled to false, or the Helm install or upgrade fails:
licensing:
enabled: true
onlineActivationWorkflow:
enabled: false
Then upgrade your release with the values file.
On CNE AWS Ready, CNE Azure Ready, and CNE GCP Ready, put the licensing values under the liferay-default key in liferay/projects/[projectId]/base/liferay.yaml, or in liferay/projects/[projectId]/environments/[environmentId]/liferay.yaml to enable licensing in one environment only:
liferay-default:
licensing:
enabled: true
Then commit and push the change to your GitOps repository.
If your Liferay DXP image isn’t named liferay/dxp, set licensing.dxpVersion to the tag of the liferay/dxp image yours is built from.
Licensing Without the liferay-default Chart
If you deploy Liferay DXP without the liferay-default chart, write the LiferayEnvironment yourself. This example names the StatefulSet liferay as the environment’s workload, references the activation code in the Secret liferay-activation, and requests two replicas:
apiVersion: licensing.liferay.com/v1alpha1
kind: LiferayEnvironment
metadata:
name: liferay
namespace: liferay
spec:
activationCodeSecretRef:
name: liferay-activation
key: activationCode
desiredReplicas: 2
workloadRef:
name: liferay
activationCodeSecretRef (the Secret’s name and key) and workloadRef (the StatefulSet’s name) are required. The other fields are optional:
desiredReplicas: The replica count you want, capped at the license’s node limit.
dxpVersion: The DXP version reported to the provisioning service. When it is empty, the operator reads it from the tag of a liferay/dxp image on the StatefulSet.
environmentName: A display name for the environment.
offline and offlineActivationBundle: The offline mode settings.
Without the chart, nothing mounts the license into Liferay DXP. Mount license.xml from the Secret the operator writes, named after the resource plus -entitlements, into Liferay DXP’s deploy folder. Liferay DXP copies files mounted this way into place only at startup, so restart the pods after each license renewal.
Add-on delivery requires the liferay-default chart, so without it, Marketplace add-ons don’t reach Liferay DXP.
Activate this environment with kubectl. The activation workflow can’t activate it, because only the liferay-default chart creates the service account the workflow runs as.
Activating an Environment
Each online environment activates with a single-use activation code, which you get from One Liferay as Getting an Activation Code describes.
The activation code is not license.xml. The operator uses the code to get the license for you.
The operator reads the code from the activation Secret, liferay-default-activation, under the key activationCode. To use a different Secret or key, set licensing.activationCodeSecretName or licensing.activationCodeSecretKey on the liferay-default chart, and use your Secret name and key in Activating with kubectl. Create the Secret with kubectl, or submit the activation workflow to create it for you.
Getting an Activation Code
Each Cloud Native project has one Production activation code and one for UAT. You can generate multiple Non-Production codes. A code counts as used only once an environment activates with it. Until then, generating a code of the same type returns the unused code rather than a new one.
To get an activation code from your project in One Liferay,
-
Open One Liferay, go to My Account → Projects, select your project, and click Activation.
-
Click Generate Activation Key.
-
In the Product field, select Cloud Native. Then choose the environment’s Type (Production, UAT, or Non-Production) and a Version.
-
Click Next, and copy the activation code for the Type you chose. A grayed-out code is already used.
After an environment activates, it appears in the project’s list of activation keys. From then on, the operator gets the license and your subscription’s add-ons on every heartbeat, without reading the code again.
Activating with kubectl
To activate with kubectl,
-
Create the Secret in the environment’s namespace:
kubectl --namespace liferay create secret generic liferay-default-activation \ --from-literal=activationCode="[activation code]" -
Watch the environment until the
ACTIVATEDcolumn readsTrueand theVALID-UNTILcolumn shows a future expiration date:kubectl --namespace liferay get lenv liferay-default --watchIf
ACTIVATEDdoesn’t turnTrue, read theActivatedcondition’s reason withkubectl describe lenvand look it up in Troubleshooting.
Activating with Argo Workflows
The activation workflow writes the activation code into the activation Secret and asks the operator to activate the environment immediately. It needs Argo Workflows in the cluster and these chart values:
onlineActivationWorkflow.enabled on the liferay-dxp-operator chart: Installs the workflow’s cluster template. The value is false by default, except on CNE Azure Ready, which sets it to true.
licensing.onlineActivationWorkflow.enabled on the liferay-default chart: Installs the workflow’s resources in the environment’s namespace. The value is true by default in chart 3.0.0 and false from chart 4.0.0, but on CNE Azure Ready, the platform sets it to true for every environment.
On Kubernetes Ready, rerun the operator’s install command with onlineActivationWorkflow.enabled=true. Include every value you set before, because helm upgrade resets any value the command leaves out:
helm upgrade --install dxp-operator \
oci://us-central1-docker.pkg.dev/external-assets-prd/liferay-helm-chart/liferay-dxp-operator \
--create-namespace \
--namespace dxp-operator-system \
--set fullnameOverride=dxp-operator \
--set onlineActivationWorkflow.enabled=true \
--version 0.2.0
From liferay-default 4.0.0, also set licensing.onlineActivationWorkflow.enabled to true in your liferay-default chart’s values file, and upgrade your release:
licensing:
enabled: true
onlineActivationWorkflow:
enabled: true
On CNE AWS Ready and CNE GCP Ready, the workflow needs infrastructure provider chart 1.1.2 or later. Set onlineActivationWorkflow.enabled under the liferay-dxp-operator key in liferay/system/infrastructure-provider.yaml:
liferay-dxp-operator:
onlineActivationWorkflow:
enabled: true
From liferay-default 4.0.0, also set licensing.onlineActivationWorkflow.enabled to true under the liferay-default key in liferay.yaml.
Then commit and push the change to your GitOps repository.
On CNE Azure Ready, the platform sets both values for you, so the workflow needs no setup.
Charts 3.0.0 and Later
The liferay-aws, liferay-azure, and liferay-gcp charts include the liferay-default chart. From liferay-default 3.0.0, when licensing.onlineActivationWorkflow.enabled is true, the chart adds the namespaced template online-activation-workflow-template to each environment’s namespace, with a login token in the Secret online-activation-user-token.
To activate an environment from the Argo Workflows UI,
-
Using credentials that can read Secrets in the environment’s namespace, read the login token:
echo "Bearer $(kubectl --namespace liferay get secret online-activation-user-token \ --output jsonpath='{.data.token}' | base64 --decode)" -
Forward the Argo Workflows server port. On CNE AWS Ready, CNE Azure Ready, and CNE GCP Ready, the server is
argo-workflows-serverin theargo-workflows-systemnamespace:kubectl port-forward --namespace argo-workflows-system svc/argo-workflows-server 2746:2746On Kubernetes Ready, use your Argo Workflows server’s namespace and service name.
-
Open
http://localhost:2746. On Kubernetes Ready, usehttpsif your Argo Workflows server uses TLS. -
Paste the full output of step 1, including
Bearer, into the client authentication box, and click the Login button below it. -
Open Workflow Templates, and enter the environment’s namespace in the namespace filter.
-
Select
online-activation-workflow-template, and click Submit. -
Enter the activation code in the
activation-codeparameter, and click Submit. The environment is activated when every workflow step succeeds.
To submit from the command line instead, use the argo CLI. Run it without the ARGO_SERVER environment variable set, so it uses your kubeconfig credentials rather than the login token. Those credentials must be able to create workflows in the namespace and read the namespaced template and the cluster template it calls. Submit the namespaced template:
argo submit \
--from workflowtemplate/online-activation-workflow-template \
--namespace liferay \
-p activation-code="[activation code]" \
--watch
Charts Before 3.0.0
Before liferay-default 3.0.0, submit the cluster template with the argo CLI. Use credentials that can read ClusterWorkflowTemplate resources and create workflows in the environment’s namespace, such as cluster-administrator credentials. Pass the LiferayEnvironment’s name in the liferay-environment-name parameter:
argo submit \
--from clusterworkflowtemplate/online-activation-cluster-workflow-template \
--namespace liferay \
-p activation-code="[activation code]" \
-p liferay-environment-name="liferay-default" \
--watch
Delivering Marketplace Add-Ons
When the liferay-default chart’s marketplace.enabled value is true, the Marketplace add-ons in the environment’s entitlement reach each Liferay DXP pod’s deploy folder. The value is false by default and separate from the operator chart’s value of the same name.
Add-on delivery isn’t available on CNE Azure Ready 0.3.0, which doesn’t provide the marketplace volume.
The operator downloads the add-ons to the marketplace volume. In each Liferay DXP pod, a container named liferay-marketplace-sync, the sync sidecar, copies them from the volume into the deploy folder. With the liferay-default chart’s marketplace.enabled at false, the operator still downloads every entitled add-on, and the ADD-ONS column of kubectl get lenv reads True. No add-on reaches Liferay DXP.
Add-on delivery needs licensing.enabled at true on the liferay-default chart. From chart 4.0.0, the sync sidecar runs a script that the operator writes, and the Helm install or upgrade fails without licensing.enabled. An online environment gets its add-ons only after it’s activated.
Turning On Add-On Delivery
Add-on delivery needs the environment’s claim: a ReadWriteMany PersistentVolumeClaim in the environment’s namespace named liferay-default-marketplace, or [nameOverride]-marketplace if you set the chart’s nameOverride value. The environment’s claim must point to the same storage directory as the operator’s claim in dxp-operator-system. That directory holds a folder named after each environment’s namespace, and the claim points to the directory, not to that folder.
These liferay-default chart values control add-on delivery:
| Chart value | Default | Controls |
|---|---|---|
marketplace.enabled | false | Whether each Liferay DXP pod gets the sync sidecar and the environment’s claim |
marketplace.syncInterval | 600 | The seconds between the sync sidecar’s checks for new or changed .lpkg files |
marketplace.csi.driver, marketplace.csi.volumeHandle, marketplace.csi.volumeAttributes | Empty | The storage of the operator’s claim, on which the chart creates the environment’s claim |
Turning on add-on delivery restarts the Liferay DXP pods one at a time. If the environment’s claim doesn’t exist, the first restarted pod stays Pending until you create the claim.
On Kubernetes Ready, have the chart create the environment’s claim or create it yourself. To have the chart create it, read the CSI values of the operator’s claim, replacing [claim name] with the marketplace.claimName you set on the operator chart:
kubectl get pv \
$(kubectl --namespace dxp-operator-system get pvc "[claim name]" --output jsonpath='{.spec.volumeName}') \
--output jsonpath='{.spec.csi}'
Create the environment’s claim yourself if the output is empty or names a secret. Also create it yourself if a PersistentVolume named [namespace]-marketplace, such as liferay-marketplace, already exists, because the chart gives its PersistentVolume that name. Otherwise, in your liferay-default chart’s values file, set marketplace.enabled to true, and set the driver and volume handle from the output:
marketplace:
csi:
driver: "[CSI driver]"
volumeHandle: "[volume handle]"
enabled: true
If the output has volume attributes, also set them in marketplace.csi.volumeAttributes.
If you create the environment’s claim yourself, create it before you upgrade, and in your liferay-default chart’s values file, set only marketplace.enabled to true:
marketplace:
enabled: true
Then upgrade your release with the values file.
On CNE AWS Ready and CNE GCP Ready, the default liferay-aws and liferay-gcp charts and the bootstrap process supply the CSI values, so set only marketplace.enabled. Set it under the liferay-default key in liferay/projects/[projectId]/base/liferay.yaml, or in liferay/projects/[projectId]/environments/[environmentId]/liferay.yaml to turn on add-on delivery in one environment only:
liferay-default:
marketplace:
enabled: true
On CNE GCP Ready, a project can use the liferay-default chart directly if you set liferay_helm_chart_name to liferay-default in your bootstrap configuration. Then set the marketplace values at the top level of liferay.yaml. The bootstrap process sets marketplace.csi.volumeHandle and marketplace.csi.volumeAttributes.ip. This example sets the driver, the other volume attributes, and marketplace.enabled:
marketplace:
csi:
driver: filestore.csi.storage.gke.io
volumeAttributes:
protocol: NFS_V3
volume: marketplace
enabled: true
Then commit and push the change to your GitOps repository.
Checking That an Add-On Arrived
For an online environment, the operator downloads a new or updated add-on on the next heartbeat. The sync sidecar copies it into the deploy folder when the pod starts and then every marketplace.syncInterval seconds.
To confirm that the sync sidecar copied an add-on into a pod’s deploy folder, read the sync sidecar’s log in a Liferay DXP pod, such as liferay-default-0:
kubectl --namespace liferay logs liferay-default-0 --container liferay-marketplace-sync
For each add-on it copies, the sync sidecar writes a JSON line like this one:
{"message": "Content of \"/marketplace/[virtualEntryId].lpkg\" was changed. Copied to \"/opt/liferay/deploy/[virtualEntryId].lpkg\".", "script": "deploy-sync.sh", "severity": "INFO", "timestamp": "[timestamp]"}
[virtualEntryId] is the add-on’s ID in its status.apps entry. This line confirms delivery to the deploy folder, not installation. Liferay DXP completes the installation of a new or updated add-on when the pod restarts. Check the log for this line before you restart, because after a restart the log has no line for an add-on whose file hasn’t changed since the last copy. Then restart the pods:
kubectl --namespace liferay rollout restart statefulset/liferay-default
If an add-on doesn’t arrive, check AddOnsReady. If it’s True, see An Add-On Is Downloaded but Not Installed.
Updated Add-Ons and Ended Entitlements
For an offline environment, new and updated add-ons arrive with a new activation bundle. To deliver them, replace the bundle. In both cases, restart the pods after the sync sidecar copies the file.
When an add-on’s entitlement ends, its .lpkg file stays on the marketplace volume, the add-on stays installed in Liferay DXP, and new replicas still get it.
To remove the add-on, list its virtualEntryId in the liferay-default chart’s portalBundleDenyList value. Liferay DXP names the add-on’s bundle after its file, [virtualEntryId].lpkg, without the extension. Find the ID in the add-on’s status.apps entry, whose state is Orphaned for an online environment. An offline environment’s status has no entry for an ended add-on, so note its ID before you replace the bundle.
The chart writes portalBundleDenyList into a portal property that overrides the bundle blacklist in System Settings. From chart 4.0.0, the chart always writes the property, which also blacklists the App Manager. On Kubernetes Ready, this values file entry removes one add-on:
portalBundleDenyList:
- "[virtualEntryId]"
Then upgrade your release. On CNE AWS Ready and CNE GCP Ready, set the value under the liferay-default key in liferay.yaml.
Applying the change restarts the Liferay DXP pods one at a time, and each pod uninstalls the add-on when it starts.
Node Limits
The license’s node limit is the maximum number of Liferay DXP pods the environment can run, not a count of Kubernetes nodes. kubectl get lenv shows it in the MAX column.
If autoscaling is off and you scale the StatefulSet manually, the operator resets the replica count on the next heartbeat that confirms a valid license. It resets the count to the liferay-default chart’s replicaCount, or to the node limit if replicaCount is higher.
Once the environment has a license, Kubernetes rejects any change that would scale the StatefulSet past the replica ceiling. The ceiling is the node limit unless the license is invalid or has expired, or the provisioning grace period has ended.
Keep replicaCount at or below the limit. If autoscaling.enabled is true, keep the autoscaler’s maximum at or below the limit instead: autoscaling.hpa.maxReplicas when autoscaling.type is hpa, or autoscaling.keda.maxReplicaCount when it is keda. Kubernetes Ready uses hpa by default, and CNE AWS Ready, CNE Azure Ready, and CNE GCP Ready use keda. Both maximums default to 100.
Operator Configuration Reference
These liferay-dxp-operator chart values tune the operator’s timing for every environment it manages. On Kubernetes Ready, add them to the operator’s install command with --set. On CNE AWS Ready and CNE GCP Ready, set them under the liferay-dxp-operator key in liferay/system/infrastructure-provider.yaml. On CNE Azure Ready, set them under platformComponents.values.operatorApplications.dxpOperator.values in your bootstrap’s config.json file, and run the bootstrap again to apply them.
| Chart value | Default | Controls |
|---|---|---|
heartbeatInterval | 10m | How often the operator checks each environment’s license |
provisioningGracePeriod | 168h | How long the provisioning service can stay unreachable before the operator scales the StatefulSet down to one replica |
expirationGracePeriod | 2160h | How long after the license expires the operator caps the StatefulSet at one replica before scaling it to zero |
retry.initialDelay | 30s | The first retry delay after a failed call |
retry.maxDelay | 30m | The longest retry delay |
downloadPollInterval | 15s | How often the operator polls an in-progress add-on download |