Upgrade CNFs from 2.2.1 to 2.3.0 using F5 Lifecycle Operator¶
This document describes how to upgrade the F5 Lifecycle Operator (FLO), migrate CPCL ConfigMaps to Secrets, upgrade the CNE Instance, and apply the License custom resource.
Important
Ensure that license content (JWT values) are removed from any values files before sharing or committing them.
Upgrade the F5 Lifecycle Operator (FLO)¶
Create or update the FLO values file.
Example:
flo-values_2.3.0.yamlglobal: imagePullSecrets: - name: far-secret certmgr: clusterIssuer: arm-ca-cluster-issuer image: repository: repo.f5.com/images pullPolicy: IfNotPresent f5-spk-crds-common: versionValidator: image: repository: repo.f5.com/images f5-spk-crds-service-proxy: versionValidator: image: repository: repo.f5.com/images f5-ipam-operator: image: repository: repo.f5.com/images pullPolicy: IfNotPresent namespace: default nameOverride: f5-ipam-operator fullnameOverride: f5-ipam-operator sharedComponentNamespace: f5-utils
Execute the
Helmupgrade using the values file.$ helm upgrade f5-lifecycle-operator \ tar/f5-lifecycle-operator-v2.21.13-0.0.26.tgz \ -f arm/tmp/flo-values_2.3.0.yaml
Verify the Upgrade.
$ helm list
Run the script to migrate cpcl configuration from ConfigMaps to Kubernetes Secrets¶
Before upgrading the CWC, run the below script to migrate cpcl-config-cm and cpcl-key-cm ConfigMaps to Secrets. This is required as part of the upgrade process since the new version of CWC uses Secrets instead of ConfigMaps to store CPCL configuration and key.
Copy and save the script file as
migrate-cpcl-to-secrets.shin the release directory.$ cat migrate-cpcl-to-secrets.sh
#!/bin/bash # Migration script: Populate cpcl-config-secret and cpcl-key-secret from ConfigMaps # Usage: ./migrate-cpcl-to-secrets.sh [-n NAMESPACE] [--dry-run] set -euo pipefail NAMESPACE="f5-utils" DRY_RUN="" while [[ $# -gt 0 ]]; do case $1 in -n|--namespace) NAMESPACE="$2"; shift 2 ;; --dry-run) DRY_RUN="--dry-run=client"; shift ;; -h|--help) echo "Usage: $0 [-n NAMESPACE] [--dry-run]" echo " -n, --namespace Kubernetes namespace (default: cwc)" echo " --dry-run Preview without applying" exit 0 ;; *) echo "Unknown option: $1"; exit 1 ;; esac done JWT_KEY_RAW=$(kubectl get cm cpcl-key-cm -n "${NAMESPACE}" -o jsonpath='{.data.jwt\.key}' 2>/dev/null) if [ -z "${JWT_KEY_RAW}" ]; then echo "ERROR: Could not read jwt.key from cpcl-key-cm in namespace ${NAMESPACE}" exit 1 fi # Base64 encode the jwt.key value (no line wrapping) JWT_KEY_B64=$(echo -n "${JWT_KEY_RAW}" | base64 -w0) echo " jwt.key length (raw): ${#JWT_KEY_RAW} chars" echo " jwt.key length (b64): ${#JWT_KEY_B64} chars" cat <<EOF | kubectl apply ${DRY_RUN} -f - apiVersion: v1 kind: Secret metadata: name: cpcl-key-secret namespace: ${NAMESPACE} labels: app.kubernetes.io/managed-by: cwc-license-controller type: Opaque data: jwt.key: ${JWT_KEY_B64} EOF echo " ✓ cpcl-key-secret created/updated" echo "" # List of all fields in cpcl-config-cm to migrate CONFIG_FIELDS=( "custom_id" "digital_asset_version" "jwt" "licenseserver_root_ca_path" "log_level" "operation_mode" "signed_verify_cert_path" "teem_cert_url" "teem_entitlement_url" "teem_initial_config_url" ) # Build the data section dynamically DATA_SECTION="" for field in "${CONFIG_FIELDS[@]}"; do VALUE=$(kubectl get cm cpcl-config-cm -n "${NAMESPACE}" -o jsonpath="{.data.${field}}" 2>/dev/null || true) if [ -z "${VALUE}" ]; then echo " WARNING: Field '${field}' is empty or not found in cpcl-config-cm, encoding empty string" fi VALUE_B64=$(echo -n "${VALUE}" | base64 -w0) DATA_SECTION="${DATA_SECTION} ${field}: ${VALUE_B64}"$'\n' echo " ${field}: $(echo -n "${VALUE}" | head -c 60)... → (b64 ${#VALUE_B64} chars)" done cat <<EOF | kubectl apply ${DRY_RUN} -f - apiVersion: v1 kind: Secret metadata: name: cpcl-config-secret namespace: ${NAMESPACE} labels: app.kubernetes.io/managed-by: cwc-license-controller type: Opaque data: ${DATA_SECTION} EOF echo " ✓ cpcl-config-secret created/updated" echo "" if [ -z "${DRY_RUN}" ]; then echo "Verifying cpcl-key-secret..." VERIFY_KEY=$(kubectl get secret cpcl-key-secret -n "${NAMESPACE}" -o jsonpath='{.data.jwt\.key}' | base64 -d | head -c 80) echo " jwt.key starts with: ${VERIFY_KEY}..." echo "" echo "Verifying cpcl-config-secret..." for field in "${CONFIG_FIELDS[@]}"; do VERIFY_VAL=$(kubectl get secret cpcl-config-secret -n "${NAMESPACE}" -o jsonpath="{.data.${field}}" | base64 -d | head -c 60) echo " ${field}: ${VERIFY_VAL}..." done else echo " Skipped (dry-run mode)" fi echo "" echo "=== Migration Complete ==="
Note
This script reads the
cpcl-key-cmandcpcl-config-cmConfigMaps, encodes the values in base64 format, and creates/updates thecpcl-key-secretandcpcl-config-secretSecrets in the specified namespace. It also includes a dry-run option to preview the changes without applying them.Using
chmod +xmake the file executable.$ chmod +x migrate-cpcl-to-secrets.sh
Execute the script in the
f5-utilsnamespace where the ConfigMaps are available.$ ./migrate-cpcl-to-secrets.sh -n f5-utils
Sample Output
jwt.key length (raw): 8618 chars jwt.key length (b64): 11492 chars secret/cpcl-key-secret created ✓ cpcl-key-secret created/updated custom_id: SPK Cluster... → (b64 16 chars) debug_telemetry: enabled... → (b64 12 chars) digital_asset_version: 1.2.0... → (b64 8 chars) jwt: eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InYxIiwiamt1Ijoi... → (b64 1436 chars) WARNING: Field 'licenseserver_root_ca_path' is empty or not found in cpcl-config-cm, encoding empty string licenseserver_root_ca_path: ... → (b64 0 chars) log_level: debug... → (b64 8 chars) operation_mode: disconnected... → (b64 16 chars) signed_verify_cert_path: /etc/cm20/cpcl/private/key/jwt.key... → (b64 48 chars) teem_cert_url: https://product-tst.apis.f5networks.net/ee/v1... → (b64 60 chars) teem_entitlement_url: https://product-s-tst.apis.f5networks.net/ee/v1... → (b64 64 chars) teem_initial_config_url: https://product-s-tst.apis.f5networks.net/ee/v1... → (b64 64 chars) secret/cpcl-config-secret created ✓ cpcl-config-secret created/updated Verifying cpcl-key-secret...
Apply the CNE Instance¶
Create the CNE Instance custom resource after the FLO upgrade completes.
Example:
cnegatewayclass-cr.yamlapiVersion: k8s.f5.com/v1 kind: CNEInstance metadata: labels: app.kubernetes.io/name: f5-lifecycle-operator app.kubernetes.io/managed-by: kustomize name: svs-alpha-cneinstcr namespace: svs-alpha spec: wholeCluster: false product: type: CNF gatewayAPI: false manifestVersion: "2.3.0-3.2598.3-0.0.170" telemetry: loggingSubsystem: enabled: true metricSubsystem: enabled: true certificate: clusterIssuer: oss-issuer deploymentSize: "Small" registry: uri: "devrepo.f5.com" imagePullSecrets: - name: farimagesecret imagePullPolicy: Always networkAttachments: - "svs-alpha-cnf-intel710-external1-sriov" - "svs-alpha-cnf-intel710-internal-sriov" storageClassName: managed-nfs-storage # Features # CSRC Egress pseudoCNI: enabled: true # BGP dynamicRouting: enabled: true # AFM firewallACL: enabled: true # Core dump files coreCollection: enabled: true # CGNAT cgnat: enabled: true # IPSD intrusionPrevention: enabled: true # IP Intelligence ipIntelligence: enabled: true # Intelligent Load Balancer intelligentLB: enabled: false # DPU dpu: enabled: false # TMM Replicas (when not whole cluster or DPU mode) tmmReplicas: 2 # Watch Namespaces # # watchNamespaces: # #- "tcpapp" # #- "udpapp" # # # Advanced Configuration advanced: maintenanceMode: enabled: false demoMode: enabled: false tmm: env: - name: SESSIONDB_EXTERNAL_SERVICE value: "f5-dssm-sentinel.svs-alpha.svc.cluster.local" - name: SESSIONDB_DISCOVERY_SENTINEL value: "true" - name: SSL_SERVERSIDE_STORE value: "/tls/tmm/mds/clt" - name: SSL_TRUSTED_CA_STORE value: "/tls/tmm/mds/clt" - name: TMM_MAPRES_VERBOSITY value: "debug" - name: TMM_MAPRES_USE_VETH_NAME # Set to false to resolve the issue (BZ 1407137) value: "FALSE" - name: CONFIG_VIEWER_ENABLE value: "TRUE" - name: TMM_MAPRES_ADDL_VETHS_ON_DP value: "TRUE" - name: OPENSHIFT_VFIO_RESOURCE_1 value: "ens25f0IntelE710Vfio" - name: OPENSHIFT_VFIO_RESOURCE_2 value: "ens25f1IntelE710Vfio" - name: EXPORT_TMROUTED_LOGS value: "true" - name: EXPORT_BLOBD_LOGS value: "true" cneController: env: - name: ICNI20_ENABLED value: "true" envDiscovery: enabled: false
Apply the resource:
$ kubectl apply -f arm/tmp/cnegatewayclass-cr.yaml -n f5-alpha
Verify CNE Pods¶
Verify Pods in
f5-utils$ kubectl get pods -n f5-utils
Verify Pods in
f5-alpha$ kubectl get pods -n f5-alpha
Apply the License Custom Resource¶
Apply the License custom resource after the CNE instance upgrade completes.
Example:
license-cr.yamlapiVersion: k8s.f5net.com/v1 kind: License metadata: name: bnk-license spec: jwt: <REDACTED_JWT_VALUE> operationMode: connected
Apply the license.
$ kubectl apply -f license-cr.yaml