Deploying Apicurio Registry for high availability
This chapter explains how to configure Apicurio Registry for high availability in a single Kubernetes cluster.
High availability architecture overview
A highly available Apicurio Registry deployment consists of the following components:
- Application (backend) pods
-
Multiple stateless replicas that process REST API requests. These pods can scale horizontally and are distributed across cluster nodes for fault tolerance.
- UI pods
-
Multiple stateless replicas serving the web console (static files). Typically fewer replicas are needed compared to the backend.
- Storage layer
-
The stateful component requiring HA configuration. The strategy depends on whether you use SQL database or KafkaSQL storage.
The application and UI components are stateless and can be scaled horizontally. High availability is achieved by running multiple replicas distributed across failure domains (availability zones) and implementing a highly available storage layer.
Configuring application component high availability
The Apicurio Registry backend and UI components are stateless and can scale horizontally to provide high availability and increased throughput.
-
Configure the number of replicas for each component in the
ApicurioRegistry3custom resource:apiVersion: registry.apicur.io/v1 kind: ApicurioRegistry3 metadata: name: example-registry-ha spec: app: replicas: 3 storage: type: postgresql sql: dataSource: url: jdbc:postgresql://postgresql-ha.my-project.svc:5432/registry username: registry_user password: name: postgresql-credentials key: password ingress: host: registry.example.com ui: replicas: 2 ingress: host: registry-ui.example.comRunning multiple replicas requires a production-ready storage backend (PostgreSQL, MySQL, or KafkaSQL with persistent Kafka). Do not use the default embedded H2 configuration with multiple replicas. -
Distribute Apicurio Registry pods across different nodes and availability zones:
spec: app: replicas: 3 podTemplateSpec: spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app: example-registry app.kubernetes.io/component: app app.kubernetes.io/part-of: apicurio-registry topologyKey: kubernetes.io/hostname - weight: 50 podAffinityTerm: labelSelector: matchLabels: app: example-registry app.kubernetes.io/component: app app.kubernetes.io/part-of: apicurio-registry topologyKey: topology.kubernetes.io/zoneThis configuration spreads pods across different nodes (hostname) and availability zones when possible.
-
Set appropriate resource requests and limits to ensure pod scheduling and prevent resource contention:
spec: app: podTemplateSpec: spec: containers: - name: apicurio-registry-app resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "1Gi" cpu: "1000m" -
Ensure minimum availability during voluntary disruptions such as node drains or cluster upgrades. The operator creates a PodDisruptionBudget with
maxUnavailable: 1by default, but you can customize it by creating your own:spec: app: replicas: 3 podDisruptionBudget: enabled: falseapiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: example-registry-app-poddisruptionbudget spec: minAvailable: 1 selector: matchLabels: app: example-registry app.kubernetes.io/component: app app.kubernetes.io/part-of: apicurio-registry
Configuring SQL database storage for high availability
When using PostgreSQL or MySQL storage, high availability depends on your database configuration. The database must be configured with replication and automatic failover.
| MySQL storage is not yet supported by the operator and requires manual configuration using environment variables. |
-
Configure the Agroal connection pool to handle database failover gracefully. Add these environment variables to the
appcomponent:spec: app: env: # Connection pool sizing - name: APICURIO_DATASOURCE_JDBC_INITIAL-SIZE value: "10" - name: APICURIO_DATASOURCE_JDBC_MIN-SIZE value: "10" - name: APICURIO_DATASOURCE_JDBC_MAX-SIZE value: "50" # Connection acquisition timeout (5 seconds) - name: QUARKUS_DATASOURCE_JDBC_ACQUISITION-TIMEOUT value: "5S" # Background validation to detect stale connections (every 2 minutes) - name: QUARKUS_DATASOURCE_JDBC_BACKGROUND-VALIDATION-INTERVAL value: "2M" # Foreground validation before use (every 1 minute) - name: QUARKUS_DATASOURCE_JDBC_FOREGROUND-VALIDATION-INTERVAL value: "1M" # Maximum connection lifetime (30 minutes) - name: QUARKUS_DATASOURCE_JDBC_MAX-LIFETIME value: "30M"These settings ensure that:
-
Connections are validated regularly to detect database failovers
-
Stale connections are removed and recreated
-
Connection acquisition times out rather than hanging indefinitely
SQL database high availability options
When using SQL database storage, consider these database high availability strategies.
- PostgreSQL with streaming replication
-
Primary-replica configuration with automatic failover using tools such as Patroni, CloudNativePG, or Crunchy PostgreSQL Operator.
- PostgreSQL with synchronous replication
-
Ensures zero data loss but might impact performance.
- MySQL with Group Replication
-
Multi-primary or single-primary mode with automatic failover.
- Managed database services
-
Cloud provider managed databases (RDS, Cloud SQL, Azure Database) with built-in HA.
Example: PostgreSQL high availability with CloudNativePG
When using the CloudNativePG operator for PostgreSQL HA, create a Cluster resource and then reference it from
your ApicurioRegistry3 custom resource.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: registry-db-cluster
spec:
instances: 3
storage:
size: 20Gi
storageClass: standard
postgresql:
parameters:
max_connections: "200"
backup:
barmanObjectStore:
# Configure backup storage
Then reference the cluster in your ApicurioRegistry3 CR:
spec:
app:
storage:
type: postgresql
sql:
dataSource:
url: jdbc:postgresql://registry-db-cluster-rw:5432/app
username: app
password:
name: registry-db-cluster-app
key: password
Configuring KafkaSQL storage for high availability
When using KafkaSQL storage, high availability depends on the Kafka cluster configuration. Each Apicurio Registry replica independently consumes all messages from the Kafka journal topic.
-
Configure your Kafka cluster for high availability:
apiVersion: kafka.strimzi.io/v1beta2 kind: Kafka metadata: name: registry-kafka spec: kafka: version: 3.5.0 replicas: 3 config: # Replication settings for HA offsets.topic.replication.factor: 3 transaction.state.log.replication.factor: 3 transaction.state.log.min.isr: 2 default.replication.factor: 3 min.insync.replicas: 2 storage: type: persistent-claim size: 100Gi zookeeper: replicas: 3 storage: type: persistent-claim size: 10GiKey configuration for HA:
- Kafka replicas set to 3
-
Provides fault tolerance for broker failures.
- Replication factor set to 3
-
Each partition has three copies.
min.insync.replicasset to 2-
Requires at least two replicas to acknowledge writes.
KafkaSQL topic configuration
Apicurio Registry uses three Kafka topics for various purposes:
- Journal topic
-
Stores all changes to the registry data. Named
kafkasql-journalby default and is the most important topic for data durability. - Snapshots topic
-
Stores periodic snapshots of the registry state for faster startup, if the snapshotting feature is used. It’s named
kafkasql-snapshotsby default and should be configured with similar replication settings as the journal topic. - Events topic
-
Stores events to support Kafka-based Registry eventing feature. Named
registry-eventsby default, its configuration depends on your event processing needs. Currently, Apicurio Registry sends messages to only 1 partition of this topic.
Configure the Kafka topics used by Apicurio Registry with appropriate replication:
apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaTopic
metadata:
name: kafkasql-journal
labels:
strimzi.io/cluster: registry-kafka
spec:
partitions: 3
replicas: 3
config:
cleanup.policy: delete
min.insync.replicas: 2
retention.ms: -1 # Infinite retention
retention.bytes: -1 # Infinite retention
The journal topic and the snapshots topic must use cleanup.policy: delete with infinite retention (retention.ms: -1 and retention.bytes: -1) to prevent accidental data loss. As of the latest version, Apicurio Registry will check these settings on startup and refuse to start if they are not configured correctly.
|
Apicurio Registry automatically creates the journal, snapshots, and events topics on startup by default, if they do not exist. While this is not recommended in a high-availability production scenario, if you want the topics to be created automatically, the following environment variables provide the equivalent configuration:
APICURIO_KAFKASQL_TOPIC_PARTITIONS=3
APICURIO_KAFKASQL_TOPIC_REPLICATION_FACTOR=3
APICURIO_KAFKASQL_TOPIC_MIN_INSYNC_REPLICAS=2
APICURIO_KAFKASQL_SNAPSHOTS_TOPIC_PARTITIONS=3
APICURIO_KAFKASQL_SNAPSHOTS_TOPIC_REPLICATION_FACTOR=3
APICURIO_KAFKASQL_SNAPSHOTS_TOPIC_MIN_INSYNC_REPLICAS=2
KafkaSQL startup verification
At startup, Apicurio Registry verifies the KafkaSQL topic configuration and the contents of the journal topic before it serves requests. Verification protects against accidental data loss from misconfigured topics or from data that an earlier major version wrote.
Apicurio Registry performs the following checks:
-
Topic configuration: every KafkaSQL topic must use the
deletecleanup policy, and the journal and snapshots topics must use unlimited retention (retention.ms=-1andretention.bytes=-1). When a check fails, Apicurio Registry refuses to start, and the error message names the topic, the effective value, and where the value was configured. -
Journal contents: Apicurio Registry inspects recent messages in the journal topic. When the journal contains only Apicurio Registry version 2.x records, Apicurio Registry refuses to start to prevent data loss. When the journal contains a mix of version 2.x and 3.x records, Apicurio Registry logs an error and continues.
The journal inspection is bounded: Apicurio Registry captures the topic end offsets before it reads, so messages that other replicas produce concurrently do not delay startup.
To relax the retention verification, for example, when you use the snapshotting feature with automatic cleanup, set apicurio.kafkasql.topic-configuration-verification-override-enabled=true. With this setting, Apicurio Registry does not enforce unlimited retention, so ensure that snapshots are created more frequently than messages are deleted.
Consumer behavior with multiple replicas
When running multiple Apicurio Registry replicas with KafkaSQL storage, each replica independently consumes the full Kafka journal topic to build its own local state.
-
Each replica uses a unique consumer group ID (automatically generated using UUID)
-
Each replica independently consumes all messages from the journal topic
-
There is no consumer group rebalancing between replicas
-
All replicas build the same local state from the Kafka topic (Apicurio Registry replays data into an embedded H2 database)
This design ensures that:
-
New replicas can be added without affecting existing replicas
-
Pod restarts only affect the restarting pod, not others
-
Each replica maintains a consistent view of the data
Do not configure a fixed Kafka consumer group ID (for example, by setting
APICURIO_KAFKASQL_CONSUMER_GROUP_ID). KafkaSQL uses an embedded H2 database that is empty on every
restart, so the full journal topic must be replayed from the beginning each time. A fixed consumer
group ID causes Kafka to resume from previously committed offsets, skipping the journal replay and
resulting in the registry appearing empty after a restart. If you need a recognizable consumer group
name for monitoring, use the APICURIO_KAFKASQL_CONSUMER_GROUP_PREFIX environment variable instead,
which sets a prefix while still generating a unique group ID on each startup.
|
Operator leader election
When you run more than one replica of the Apicurio Registry Operator, Kubernetes Lease-based leader election ensures that only one replica reconciles resources at a time. Leader election is enabled by default.
You can configure leader election with the following environment variables on the operator deployment:
| Environment variable | Default | Description |
|---|---|---|
|
|
Enables Lease-based leader election. When the lease namespace cannot be resolved, leader election is disabled automatically. |
|
|
Name of the Lease resource. |
|
The operator pod namespace |
Namespace of the Lease resource. |
High availability monitoring
Apicurio Registry exposes Prometheus metrics for monitoring application health and performance.
Metrics are enabled by default. Access the metrics endpoint at /metrics on the management port (default 9000).
Configure alerts for:
-
Pod restarts or crash loops
-
High error rates (5xx responses)
-
Storage operation timeouts
-
Database connection pool exhaustion
-
Kafka consumer lag (for KafkaSQL storage)
Key metrics to monitor
Monitor the following categories of metrics for high availability deployments of Apicurio Registry.
-
REST API metrics:
-
http_server_requests_seconds- Request latency -
http_server_active_requests- Concurrent requests -
http_server_requests_total- Total request count
-
-
Storage metrics:
-
apicurio_storage_operation_seconds- Storage operation latency -
apicurio_storage_concurrent_operations- Concurrent storage operations -
apicurio_storage_operation_total- Total storage operations
-
-
Health check metrics:
-
Readiness probe:
/health/ready(on management port 9000) -
Liveness probe:
/health/live(on management port 9000)
-
-
JVM metrics:
-
jvm_memory_used_bytes- Memory usage -
jvm_gc_pause_seconds- Garbage collection pauses
-
Configuring a ServiceMonitor for Prometheus Operator
If you use the Prometheus Operator, create a ServiceMonitor resource to scrape metrics from your Apicurio Registry deployment.
-
The Prometheus Operator is installed in your cluster.
-
Create a
ServiceMonitorresource that selects your Apicurio Registry pods:apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: apicurio-registry-metrics labels: app: apicurio-registry spec: selector: matchLabels: app: apicurio-registry endpoints: - port: management path: /metrics interval: 30s
Performing rolling updates
When updating Apicurio Registry to a new version, use rolling updates to minimize downtime.
-
Update Apicurio Registry Operator to update Apicurio Registry to a new version. Apicurio Registry and Apicurio Registry Operator are versioned together, so updating Apicurio Registry Operator automatically updates the application. For production environments, we strongly recommend using Operator Lifecycle Manager (OLM) with manual install plan confirmation enabled, so you have time to review release notes and prepare for the update.
If you need to update the application without updating Apicurio Registry Operator (not recommended except as a workaround for critical issues), you can override the application image in your pod template, as shown in the following example. spec: app: podTemplateSpec: spec: containers: - name: apicurio-registry-app image: quay.io/apicurio/apicurio-registry:3.3.0
Operator rolling update strategy
The operator performs rolling updates automatically when you update the ApicurioRegistry3 CR. The default
strategy ensures the following.
-
Pods are updated one at a time
-
Each new pod must pass readiness checks before the next pod is updated
-
PodDisruptionBudget prevents too many pods being unavailable
Safe update practices
Follow these practices to update your Apicurio Registry deployment safely and minimize risk.
- Test in non-production first
-
Validate the new version in a test environment.
- Monitor during rollout
-
Watch metrics and logs during the update.
- Maintain minimum replicas
-
Keep at least two replicas to ensure availability during updates.
- Review release notes
-
Check for breaking changes or migration steps.
| For patch releases (for example, 3.1.0 to 3.1.1), rolling updates typically complete without issues. For major or minor version updates, always review the migration guide. |
Upgrading to the management interface (port 9000)
Starting with Apicurio Registry 3.x, health check and metrics endpoints are served on a dedicated management port (9000) instead of the main application port (8080). This is a breaking change that requires updates to your deployment configuration when upgrading from earlier versions.
-
Update Kubernetes liveness and readiness probes to target port
9000instead of port8080. The endpoint paths (/health/readyand/health/live) remain the same.# Before (old configuration) livenessProbe: httpGet: path: /health/live port: 8080 # After (new configuration) livenessProbe: httpGet: path: /health/live port: 9000 # After (with TLS enabled) livenessProbe: httpGet: path: /health/live port: 9000 scheme: HTTPS -
Update Prometheus scrape targets: update your Prometheus configuration or
ServiceMonitorresources to scrape metrics from port9000at path/metrics. -
If you use Kubernetes
NetworkPolicyresources, ensure that ingress traffic on port9000is allowed from your monitoring infrastructure and the kubelet (for health probes). -
Review TLS considerations. The management interface inherits the default TLS configuration from the Quarkus TLS registry. When TLS is enabled, the management interface on port
9000also serves HTTPS, and health probes must usescheme: HTTPS. The Apicurio Registry Operator handles this automatically. -
If you are using the Apicurio Registry Operator, confirm that no manual changes are required: it automatically configures the correct probe ports for operator-managed deployments.
Backup and restore procedures
Regular backups are essential for disaster recovery and data protection.
Apicurio Registry supports backup and restore for both SQL database storage and KafkaSQL storage. Apicurio Registry also
exposes a health endpoint (/health/ready) and a metrics endpoint (/metrics) on the management port (9000),
which you can use to monitor deployment health during backup and restore operations.
Backing up and restoring SQL database storage
For SQL storage (PostgreSQL or MySQL), backup and restore strategies depend on your database setup.
-
Back up a PostgreSQL database using one of the following options:
-
Create a logical backup with
pg_dump:pg_dump -h postgresql-host -U registry_user -d registry > registry-backup.sql -
Use continuous archiving with WAL for point-in-time recovery.
-
Use operator-managed backups if you use CloudNativePG or Crunchy PostgreSQL Operator:
spec: backup: barmanObjectStore: destinationPath: s3://my-backups/registry-db s3Credentials: accessKeyId: name: backup-credentials key: ACCESS_KEY_ID secretAccessKey: name: backup-credentials key: ACCESS_SECRET_KEY wal: compression: gzip retentionPolicy: "30d"
-
-
Back up a MySQL database using one of the following options:
-
Create a logical backup with
mysqldump:mysqldump -h mysql-host -u registry_user -p registry > registry-backup.sql -
Create a binary backup using tools like Percona XtraBackup or MySQL Enterprise Backup.
-
-
Restore from a logical backup:
# PostgreSQL psql -h postgresql-host -U registry_user -d registry < registry-backup.sql # MySQL mysql -h mysql-host -u registry_user -p registry < registry-backup.sql
Backing up KafkaSQL storage
For KafkaSQL storage, back up the Kafka journal topic and snapshots topic.
-
Use Kafka’s MirrorMaker 2 or Strimzi Mirror Maker for topic replication (recommended):
apiVersion: kafka.strimzi.io/v1beta2 kind: KafkaMirrorMaker2 metadata: name: registry-backup-mirror spec: version: 3.5.0 replicas: 1 connectCluster: "backup-cluster" clusters: - alias: "source-cluster" bootstrapServers: registry-kafka-bootstrap:9092 - alias: "backup-cluster" bootstrapServers: backup-kafka-bootstrap:9092 mirrors: - sourceCluster: "source-cluster" targetCluster: "backup-cluster" topicsPattern: "kafkasql-.*" groupsPattern: ".*"Alternatively, export and import topics using Kafka’s console consumer or CLI tools like
kcat/kafkacat. Since the topics might contain binary data, which might be affected by text file encoding, make sure they are exported correctly when using CLI tools.
Testing backup and restore
Regularly test your backup and restore procedures to confirm that they work when you need them.
-
Create a test Apicurio Registry deployment in a separate namespace.
-
Restore from backup to the test deployment.
-
Verify that all artifacts and metadata are present.
-
Test API functionality to ensure data integrity.
