Upgrade to GoodData.CN 4.0

This guide describes the one-time migration steps and breaking changes you need to handle when upgrading to GoodData.CN 4.0. For the general upgrade procedure and version-specific upgrade notes, see Upgrade GoodData.CN.

Etcd Configuration Updates

GoodData.CN exposes additional Helm values to control how etcd is deployed and used by platform services. This is useful when migrating an existing deployment between etcd configurations.

Deprecation Notice

useInternalQuiverEtcd has been removed. Use useInternalGDEtcd with customEtcd instead.

New Values

useInternalGDEtcd

useInternalGDEtcd: false
# If set to true, Quiver will use the internal custom etcd if customEtcd.enabled is also true.
# If customEtcd.enabled is false, Quiver tries to use external etcd instead.

customEtcd

customEtcd deploys a GoodData-managed etcd StatefulSet. The annotated example below shows the available keys; for the authoritative current defaults, see the Helm Chart Values Reference.

customEtcd:
  # Enable the custom etcd StatefulSet deployment
  enabled: false
  # Number of etcd replicas (should be an odd number for quorum)
  replicaCount: 3
  image:
    # etcd image name (uses repositoryPrefix from global/cascade settings)
    name: etcd
    # etcd image tag
    tag: <ETCD_IMAGE_TAG>
  service:
    # Client port for etcd connections
    clientPort: 2379
    # Peer port for etcd cluster communication
    peerPort: 2380
    # Metrics port for Prometheus scraping
    metricsPort: 8080
  # Pod annotations
  podAnnotations: {}
  # Pod labels
  podLabels: {}
  # Resources for etcd container
  resources:
    requests:
      cpu: 200m
      memory: 768Mi
    limits:
      cpu: 400m
      memory: 1Gi
  podSecurityContext:
    fsGroupChangePolicy: Always
    fsGroup: 1001
  containerSecurityContext:
    runAsNonRoot: true
    runAsUser: 1001
    runAsGroup: 1001
    readOnlyRootFilesystem: true
  # Persistence configuration for etcd data
  persistence:
    # Storage size for etcd data PVC
    size: 8Gi
    # Storage class (empty uses cluster default)
    storageClassName: ""
  # Pod anti-affinity ensures etcd pods are spread across different nodes
  antiAffinity:
    enabled: true
    type: requiredDuringSchedulingIgnoredDuringExecution
    topologyKey: kubernetes.io/hostname
  # Liveness probe configuration
  livenessProbe:
    path: /livez
    port: 8080
    initialDelaySeconds: 30
    periodSeconds: 30
    timeoutSeconds: 5
    successThreshold: 1
    failureThreshold: 5
  # Readiness probe configuration
  readinessProbe:
    path: /readyz
    port: 8080
    initialDelaySeconds: 30
    periodSeconds: 10
    timeoutSeconds: 5
    successThreshold: 1
    failureThreshold: 5
  # Auto compaction configuration
  autoCompaction:
    mode: periodic
    retention: 5m
  # Snapshot count for compaction trigger
  snapshotCount: 5000
  # Defragmentation CronJob configuration
  defrag:
    enabled: true
    schedule: "15 3 * * *"
    pauseDuration: "1m"
    commandTimeout: "20s"
    resources:
      limits:
        cpu: 50m
        memory: 50Mi
  # PodMonitor configuration for Prometheus Operator
  podMonitor:
    enabled: true
    interval: 20s
    scrapeTimeout: 10s
    path: /metrics
    port: etcd-metrics

How to Decide What to Use

  • If you run external etcd, set useInternalGDEtcd: false and customEtcd.enabled: false, and configure service.etcd.*.
  • If you want to run internal etcd managed by GoodData, set:
    • customEtcd.enabled: true
    • useInternalGDEtcd: true

PostgreSQL Configuration Updates

GoodData.CN provides Helm values for controlling PostgreSQL deployment and connectivity used by platform services. The Bitnami postgresql-ha subchart has been removed from the chart, so these values are important if you still need to migrate an existing deployment to an externally managed PostgreSQL instance.

External PostgreSQL Configuration

In GoodData.CN 4.0, configure your external PostgreSQL connection with the following Helm values:

service:
  postgres:
    host: "<POSTGRES_HOST>"
    port: 5432
    username: postgres
    password: "<POSTGRES_PASSWORD>"
    # Alternative: reference an existing Kubernetes secret
    # existingSecret: "my-postgres-secret"

If you use an existing Kubernetes secret, it must contain the postgresql-password key.

deployPostgresHA removed in 4.0

The deployPostgresHA value was removed together with the Bitnami postgresql-ha subchart. Delete it from your Helm values file before you upgrade. Only service.postgres.* configures the database connection in 4.0.

Databases Used by GoodData.CN

Depending on your deployment, GoodData.CN uses the following databases:

DatabaseServiceRequired When
mdmetadata-apiAlways
automationautomationAlways
genaiautomationIf deployGenAIService: true
dexdexIf deployDexIdP: true
gwgateway-forgeAlways (as of 4.0, gateway-forge is a core service)

How to Decide What to Use

  • If you already use external PostgreSQL, your service.postgres.* settings carry over unchanged. Remove the now-unrecognized deployPostgresHA key from your Helm values file.
  • If you are still running the Bitnami postgresql-ha subchart on a pre-4.0 GoodData.CN version, migrate to an external PostgreSQL instance before you upgrade to 4.0, where the subchart is no longer available.

PostgreSQL Migration Guide

This procedure describes how to migrate from the Bitnami postgresql-ha subchart to an external PostgreSQL instance by using pg_dump and pg_restore. The migration requires planned downtime. Complete this migration before upgrading to a GoodData.CN version where the postgresql-ha subchart is no longer available.

Prerequisites

  • Access to the Kubernetes cluster where GoodData.CN is deployed
  • A provisioned external PostgreSQL instance
  • Network connectivity between your Kubernetes namespace and the new PostgreSQL instance
  • kubectl configured with access to the target namespace
  • Sufficient storage for database dumps

Step 1: Prepare the External PostgreSQL Instance

Create the required databases in your external PostgreSQL instance:

CREATE DATABASE md;
CREATE DATABASE automation;
CREATE DATABASE genai; -- skip if deployGenAIService is false
CREATE DATABASE dex; -- skip if deployDexIdP is false
CREATE DATABASE gw;

Create only the optional databases that you need for your deployment.

Step 2: Verify Connectivity

Create a temporary pod with PostgreSQL client tools:

kubectl run pg-debug --rm -it \
  --image=postgres:16 \
  --restart=Never \
  --namespace <NAMESPACE> \
  -- bash

Inside the pod, verify access to the current and new PostgreSQL instances:

export PGPASSWORD='<POSTGRES_PASSWORD>'

psql -h <RELEASE>-db-pgpool -p 5432 -U postgres -c "SELECT version();"
psql -h <NEW_POSTGRES_HOST> -p 5432 -U postgres -c "SELECT version();"

Step 3: Scale Down Services

Scale down all services that write to PostgreSQL:

NAMESPACE=<NAMESPACE>
RELEASE=<RELEASE>

kubectl scale deployment ${RELEASE}-metadata-api --replicas=0 -n $NAMESPACE
kubectl scale deployment ${RELEASE}-automation --replicas=0 -n $NAMESPACE
kubectl scale deployment ${RELEASE}-genai --replicas=0 -n $NAMESPACE # skip if deployGenAIService is false
kubectl scale deployment ${RELEASE}-dex --replicas=0 -n $NAMESPACE 2>/dev/null || true # skip if deployDexIdP is false
kubectl scale deployment ${RELEASE}-gateway-md-sink --replicas=0 -n $NAMESPACE 2>/dev/null || true

Step 4: Dump the Databases

export PGPASSWORD='<POSTGRES_PASSWORD>'
SOURCE_HOST=${RELEASE}-db-pgpool

pg_dump -h $SOURCE_HOST -p 5432 -U postgres -d md -Fc -v > /tmp/md.dump
pg_dump -h $SOURCE_HOST -p 5432 -U postgres -d automation -Fc -v > /tmp/automation.dump
pg_dump -h $SOURCE_HOST -p 5432 -U postgres -d genai -Fc -v > /tmp/genai.dump # skip if deployGenAIService is false
pg_dump -h $SOURCE_HOST -p 5432 -U postgres -d dex -Fc -v > /tmp/dex.dump # skip if deployDexIdP is false
pg_dump -h $SOURCE_HOST -p 5432 -U postgres -d gw -Fc -v > /tmp/gw.dump

Skip the optional databases that are not used in your deployment.

Step 5: Restore the Databases

export PGPASSWORD='<POSTGRES_PASSWORD>'
TARGET_HOST=<NEW_POSTGRES_HOST>

pg_restore -h $TARGET_HOST -p 5432 -U postgres -d md -v /tmp/md.dump
pg_restore -h $TARGET_HOST -p 5432 -U postgres -d automation -v /tmp/automation.dump
pg_restore -h $TARGET_HOST -p 5432 -U postgres -d genai -v /tmp/genai.dump # skip if deployGenAIService is false
pg_restore -h $TARGET_HOST -p 5432 -U postgres -d dex -v /tmp/dex.dump # skip if deployDexIdP is false
pg_restore -h $TARGET_HOST -p 5432 -U postgres -d gw -v /tmp/gw.dump

Step 6: Update Helm Values and Upgrade the Release

You still run this step on your current pre-4.0 chart version, so the values file combines two things: deployPostgresHA: false turns off the Bitnami subchart you are migrating away from, and service.postgres.* points the services at the new instance.

# Pre-4.0 chart only. This key no longer exists in 4.0, so delete
# it from your values file before you upgrade to 4.0.
deployPostgresHA: false

service:
  postgres:
    host: "<NEW_POSTGRES_HOST>"
    port: 5432
    username: postgres
    password: "<POSTGRES_PASSWORD>"
    # Alternative: use an existing Kubernetes secret
    # existingSecret: "my-postgres-secret"
helm upgrade <RELEASE> gooddata/gooddata-cn \
  --namespace <NAMESPACE> \
  --version <CURRENT_PRE_4_0_CHART_VERSION> \
  -f values.yaml \
  --reuse-values

Step 7: Verify the Deployment

After the upgrade, verify that the affected services start successfully and connect to the new database.

API Gateway Authentication Updates

Starting with version 4.5, the API Gateway handles OIDC sign-in, JWT bearer authentication, and API-token validation by default.

Users who are already signed in stay signed in after the upgrade. You do not need to reconfigure your identity provider, change client_id, or ask users to sign in again.

Session Cookies

The API Gateway now uses a server-side session model.

CookieDescription
GD_SESSION_IDPrimary opaque session cookie that points to a server-side session.
GD_OAUTH_FLOWShort-lived cookie used during the OIDC redirect step. It is removed after sign-in completes.

The following legacy cookies are still accepted during the migration window:

  • SPRING_SEC_SECURITY_CONTEXT
  • SPRING_SEC_OAUTH2_AUTHZ_CLIENT

On the first request after the upgrade, legacy cookies are exchanged for GD_SESSION_ID and cleared automatically.

Cookies are HttpOnly, Secure over HTTPS, use Path=/, and use SameSite=Lax by default. Use the chart-level cookiePolicy value if you need to change the default cookie policy.

Action Required

If a WAF, CDN, reverse proxy, or any other component between the browser and API Gateway filters cookies by name, allow the following cookies:

  • GD_SESSION_ID
  • GD_OAUTH_FLOW

After you confirm that no users rely on stale legacy sessions, you can remove allow-list rules for the legacy SPRING_SEC_* cookies.

Helm Values

Gateway-side authentication is enabled by default through the following Helm values:

enableGatewayOauth: true
enableGatewayJwt: true
enableGatewayApiTokens: true
apiGw:
  oauth:
    enabled: true
  jwt:
    enabled: true

Through version 4.8.x, you could temporarily opt out by setting these values to false in your Helm values file. The opt-out was removed in 4.9.0, where the API Gateway always handles this authentication. If you set any of these values to false while on 4.0 through 4.8.x, remove the overrides before you upgrade to 4.9.0 or later. See Version 4.9.0 in the upgrade notes.

Other Breaking Changes in GoodData CN 4.0

Network Policy Helm Values Renamed

The following Helm values were renamed:

  • nginxIngressNamespaceLabelSelector to ingressControllerNamespaceLabelSelector
  • nginxIngressPodLabelSelector to ingressControllerPodLabelSelector

If you use either of the old values in your Helm values file, replace them with the new names before you upgrade.

Deprecated Organization Fields Removed

The ingressClass and gatewayType fields were removed from the Organization custom resource because they were no longer used. They must be removed from the organization’s specification.

Geo Chart Base Map Configuration

In GoodData.CN 4.0, do not use extraEnvVars to configure the base map provider for geo charts and geo area charts. Configure the base map provider through the new apiGw.locationService settings in your custom Helm values instead:

apiGw:
  locationService:
    plugin: "MapboxLocation"
    mapbox:
      token: "..."

For full details, see Configure Geo Charts and the 4.0 release note.