Back to BlogDocker Swarm · docker · docker-healthchecks

High Availability Patterns: Manager Quorum and Anti-Affinity

2025-12-29

High availability means your applications continue operating despite failures. In Docker Swarm, achieving high availability requires careful planning around two critical concepts: manager quorum for control plane resilience and anti-affinity for workload distribution. This article explores patterns and strategies for building highly available Swarm deployments that withstand node failures, maintain service continuity, and deliver reliable application experiences.

Understanding High Availability

What is High Availability?

High availability refers to systems designed to remain operational for extended periods with minimal downtime. Rather than preventing failures—which is impossible—high availability systems are designed to handle failures gracefully, continuing to function even when components fail.

In Swarm contexts, high availability addresses:

Control Plane Availability: Cluster management remains operational Workload Availability: Application services continue running Data Availability: Stateful applications maintain data access Network Availability: Communication paths remain functional

Measuring Availability

Availability is typically measured as uptime percentage:

99% availability: 87.6 hours downtime per year 99.9% availability: 8.76 hours downtime per year 99.99% availability: 52.56 minutes downtime per year 99.999% availability: 5.26 minutes downtime per year

Each additional "nine" requires exponentially more effort and cost.

Failure Domains

Failure domains are boundaries within which a single failure can affect multiple components:

Server Failure Domain: Single physical machine fails Rack Failure Domain: Power or network loss affects entire rack Datacenter Failure Domain: Entire datacenter becomes unavailable Region Failure Domain: Geographic region experiences outage

Distributing resources across failure domains increases availability.

Manager Quorum and Control Plane HA

What is Quorum?

Quorum is the minimum number of managers required to make cluster decisions. For a cluster with N managers, quorum is (N/2) + 1. If quorum is lost, the cluster cannot process management operations, though existing workloads continue running.

Quorum Examples

3 managers: Quorum is 2 (can tolerate 1 manager failure) 5 managers: Quorum is 3 (can tolerate 2 manager failures) 7 managers: Quorum is 4 (can tolerate 3 manager failures)

Why Odd Numbers?

Always use an odd number of managers. Even numbers provide no additional fault tolerance:

2 managers: Quorum is 2 (cannot tolerate any failures) 3 managers: Quorum is 2 (can tolerate 1 failure) 4 managers: Quorum is 3 (can tolerate 1 failure—same as 3) 5 managers: Quorum is 3 (can tolerate 2 failures)

Adding a fourth manager provides no benefit over three managers but increases coordination overhead.

Recommended Manager Count

Choose manager count based on availability requirements:

Single Manager: Development and testing only. No fault tolerance.

Three Managers: Small production deployments. Tolerates one manager failure. Suitable for clusters with 5-10 nodes.

Five Managers: Standard production deployments. Tolerates two manager failures. Recommended for most production clusters.

Seven Managers: Large-scale critical deployments. Tolerates three manager failures. For clusters with 50+ nodes.

Beyond seven managers, coordination overhead increases significantly with diminishing availability returns.

Distributing Managers Across Failure Domains

Place managers in different failure domains:

Rack Distribution:

# Label nodes by rack
docker node update --label-add rack=rack-1 manager-1
docker node update --label-add rack=rack-2 manager-2
docker node update --label-add rack=rack-3 manager-3

Availability Zone Distribution:

# Label nodes by zone
docker node update --label-add zone=us-east-1a manager-1
docker node update --label-add zone=us-east-1b manager-2
docker node update --label-add zone=us-east-1c manager-3

Datacenter Distribution:

# Label nodes by datacenter
docker node update --label-add datacenter=dc1 manager-1
docker node update --label-add datacenter=dc2 manager-2
docker node update --label-add datacenter=dc3 manager-3

This ensures a single infrastructure failure doesn't take down multiple managers.

Quorum Loss Scenarios

Partial Quorum Loss

With 5 managers, losing 2 leaves 3 remaining—quorum is maintained:

  • Cluster continues accepting management commands
  • Services continue operating normally
  • New services can be created
  • Existing services can be updated

The cluster remains fully functional.

Complete Quorum Loss

With 5 managers, losing 3 leaves 2 remaining—quorum is lost:

  • Existing services continue running
  • Existing tasks are rescheduled if they fail
  • New management commands are rejected
  • Service updates cannot be processed
  • New services cannot be created

The cluster operates in read-only mode for the control plane.

Recovering from Quorum Loss

If quorum is lost but managers are recoverable:

  1. Restore failed managers to bring cluster above quorum
  2. Quorum is automatically reestablished
  3. Normal operations resume

If quorum is permanently lost:

  1. Force one remaining manager to operate solo
  2. Rebuild cluster state
  3. Add new managers

Force a single manager to become the cluster:

docker swarm init --force-new-cluster

This creates a new single-manager cluster using the state from the current manager. Data newer than this manager's last state is lost.

Manager Network Requirements

Managers must maintain network connectivity:

Low Latency: Keep round-trip times under 50ms Stable Connections: Avoid frequent network partitions Sufficient Bandwidth: Manager-to-manager traffic needs bandwidth for state replication

High latency or unstable connections between managers cause:

  • Frequent leader elections
  • Slow cluster operations
  • Potential quorum loss during network issues

Manager Hardware Requirements

Managers need sufficient resources:

CPU: 2-4 cores minimum, more for large clusters Memory: 2-8GB RAM depending on cluster size Disk: Fast SSD storage for Raft log writes Network: Low-latency, high-bandwidth connections

Manager performance directly impacts cluster responsiveness.

Service Replica Distribution Patterns

The Need for Distribution

Running all replicas on a single node creates a single point of failure. If that node fails, the entire service becomes unavailable.

Example of poor distribution:

docker service create \
  --name webapp \
  --replicas 5 \
  webapp-image

# All 5 replicas might end up on the same node
docker service ps webapp

If this node fails, all replicas fail simultaneously.

Spread by Node

Distribute replicas across different nodes:

docker service create \
  --name webapp \
  --replicas 5 \
  --placement-pref 'spread=node.id' \
  webapp-image

This spreads replicas evenly across available nodes. With 5 nodes and 5 replicas, each node gets one replica.

Spread by Availability Zone

Distribute across availability zones:

# Label nodes by zone
docker node update --label-add zone=us-east-1a node-1
docker node update --label-add zone=us-east-1a node-2
docker node update --label-add zone=us-east-1b node-3
docker node update --label-add zone=us-east-1b node-4
docker node update --label-add zone=us-east-1c node-5
docker node update --label-add zone=us-east-1c node-6

# Create service with zone spreading
docker service create \
  --name webapp \
  --replicas 9 \
  --placement-pref 'spread=node.labels.zone' \
  webapp-image

With 3 zones and 9 replicas, each zone receives 3 replicas.

Spread by Rack

Distribute across server racks:

docker service create \
  --name database \
  --replicas 3 \
  --placement-pref 'spread=node.labels.rack' \
  postgres

This ensures replicas are distributed across physical racks, protecting against rack-level failures.

Multi-Level Spreading

Combine multiple spread preferences:

docker service create \
  --name api \
  --replicas 12 \
  --placement-pref 'spread=node.labels.datacenter' \
  --placement-pref 'spread=node.labels.rack' \
  --placement-pref 'spread=node.id' \
  api-image

This creates a hierarchy: first spread across datacenters, then within each datacenter spread across racks, then across nodes.

Anti-Affinity Patterns

What is Anti-Affinity?

Anti-affinity ensures tasks or services avoid running together on the same node. This pattern prevents correlated failures where multiple critical components fail simultaneously.

Service-Level Anti-Affinity

While Swarm doesn't provide explicit anti-affinity rules, you can achieve it through constraints and labels:

# Deploy first service on specific nodes
docker service create \
  --name primary-db \
  --constraint 'node.labels.db-type==primary' \
  --replicas 1 \
  postgres

# Deploy backup service on different nodes
docker service create \
  --name backup-db \
  --constraint 'node.labels.db-type==backup' \
  --replicas 1 \
  postgres

By using exclusive labels, services cannot coexist on the same nodes.

Dedic Nodes for Critical Services

Reserve specific nodes for critical services:

# Label dedicated nodes
docker node update --label-add critical-service=payment node-1
docker node update --label-add critical-service=payment node-2

# Deploy critical service on dedicated nodes only
docker service create \
  --name payment-processor \
  --constraint 'node.labels.critical-service==payment' \
  --replicas 2 \
  payment-image

# Prevent other services from using these nodes
docker service create \
  --name general-app \
  --constraint 'node.labels.critical-service!=payment' \
  --replicas 10 \
  app-image

This isolation ensures critical services have dedicated resources.

Geographic Anti-Affinity

Distribute services across geographic regions:

# Create services in different regions
docker service create \
  --name webapp-east \
  --constraint 'node.labels.region==us-east' \
  --replicas 5 \
  webapp-image

docker service create \
  --name webapp-west \
  --constraint 'node.labels.region==us-west' \
  --replicas 5 \
  webapp-image

If one region becomes unavailable, the other continues serving traffic.

Instance Anti-Affinity Through Global Mode

Use global mode to ensure exactly one instance per node:

docker service create \
  --name monitoring-agent \
  --mode global \
  monitoring-image

Global mode automatically provides anti-affinity—each node gets exactly one task.

Achieving Quorum with Geographic Distribution

Three-Datacenter Quorum

Deploy managers across three datacenters:

DC1: 2 managers DC2: 2 managers DC3: 1 manager

Total: 5 managers, quorum of 3

Benefits:

  • Single datacenter failure: Quorum maintained (3+ managers remain)
  • Two datacenter failures: Quorum lost but recoverable

Two-Datacenter Quorum Challenges

Distributing managers across two datacenters creates challenges:

3 managers (2 in DC1, 1 in DC2):

  • DC1 failure: Only 1 manager remains, quorum lost
  • DC2 failure: 2 managers remain, quorum maintained
  • Asymmetric failure tolerance

5 managers (3 in DC1, 2 in DC2):

  • DC1 failure: 2 managers remain, quorum lost
  • DC2 failure: 3 managers remain, quorum maintained
  • Still asymmetric

For two-datacenter deployments, prefer 3-2 or 4-3 splits, accepting that one datacenter's loss causes quorum loss.

Cloud Availability Zone Distribution

In cloud environments, use availability zones:

# AWS example: 3 AZs in one region
# Deploy managers across zones

# Zone A: 2 managers
# Zone B: 2 managers
# Zone C: 1 manager

This provides high availability within a region while maintaining low latency between managers.

Network Latency Considerations

Manager-to-manager latency affects cluster performance. Keep managers within:

Same Datacenter: <1ms latency (ideal) Same Region: <10ms latency (acceptable) Cross-Region: >50ms latency (problematic)

High latency causes:

  • Slow consensus operations
  • Frequent leadership changes
  • Poor cluster responsiveness

For geographically distributed clusters, carefully balance availability with performance.

Replica Count for High Availability

Minimum Replicas for Availability

Different replica counts provide different availability levels:

1 Replica: No availability. Single failure causes outage.

2 Replicas: Minimal availability. Can survive one failure but no redundancy during recovery.

3 Replicas: Good availability. Can survive one failure with redundancy remaining.

5+ Replicas: High availability. Can survive multiple failures and provide capacity during recovery.

Calculating Required Replicas

Consider your failure tolerance needs:

minimum_replicas = failures_to_tolerate + required_capacity_during_failure + 1

Example:

  • Want to tolerate 1 node failure
  • Need 3 replicas for capacity during failure
  • Minimum replicas: 1 + 3 + 1 = 5

Balancing Replicas with Resources

More replicas increase availability but consume more resources:

Resource Constrained: Use minimum replicas needed for availability Resource Available: Over-provision for higher availability and performance

Odd vs Even Replica Counts

Unlike managers, worker replica counts don't benefit from being odd. Choose based on capacity needs and node count.

Health Checks for High Availability

Defining Health Checks

Health checks determine if replicas are functioning correctly:

docker service create \
  --name webapp \
  --replicas 5 \
  --health-cmd "curl -f http://localhost:80/health || exit 1" \
  --health-interval 30s \
  --health-timeout 5s \
  --health-retries 3 \
  webapp-image

Failed health checks trigger task rescheduling.

Health Check Parameters

health-cmd: Command to execute for health check health-interval: Time between checks (default: 30s) health-timeout: Maximum time for check to complete health-retries: Consecutive failures before marking unhealthy health-start-period: Grace period before starting checks

Liveness vs Readiness

Implement different check types:

Liveness Check: Is the application alive?

--health-cmd "ps aux | grep -v grep | grep myapp"

Readiness Check: Can the application serve traffic?

--health-cmd "curl -f http://localhost/ready"

Combine both for comprehensive health monitoring.

Health Check Impact on Availability

Aggressive health checks:

  • Detect failures quickly
  • May cause false positives
  • Can lead to unnecessary task churn

Conservative health checks:

  • Reduce false positives
  • May allow unhealthy tasks to serve traffic longer
  • Slower failure recovery

Balance sensitivity with stability.

Graceful Shutdown for High Availability

Stop Grace Period

Configure how long tasks have to shutdown gracefully:

docker service create \
  --name webapp \
  --replicas 3 \
  --stop-grace-period 30s \
  webapp-image

During shutdown:

  1. Task receives SIGTERM signal
  2. Application has 30 seconds to finish in-flight requests
  3. After 30 seconds, SIGKILL forcefully terminates the container

Implementing Graceful Shutdown

Application code should handle SIGTERM:

Python Example:

import signal
import sys

def sigterm_handler(signal, frame):
    print("Gracefully shutting down...")
    # Finish in-flight requests
    # Close database connections
    # Clean up resources
    sys.exit(0)

signal.signal(signal.SIGTERM, sigterm_handler)

Node.js Example:

process.on('SIGTERM', () => {
  console.log('Gracefully shutting down...');
  // Stop accepting new connections
  server.close(() => {
    // Clean up
    process.exit(0);
  });
});

Zero-Downtime Updates

Configure update behavior for zero downtime:

docker service create \
  --name webapp \
  --replicas 5 \
  --update-parallelism 1 \
  --update-delay 10s \
  --update-order start-first \
  --stop-grace-period 30s \
  webapp-image

With start-first order:

  1. New task starts
  2. New task passes health checks
  3. Old task receives shutdown signal
  4. Old task has grace period to finish
  5. Next task update begins

This ensures capacity is always available.

Resource Reservations for Availability

Why Reserve Resources?

Resource reservations ensure tasks have guaranteed capacity:

docker service create \
  --name critical-app \
  --replicas 3 \
  --reserve-cpu 1.0 \
  --reserve-memory 2G \
  critical-app-image

Without reservations, a node might accept too many tasks and become resource-constrained, affecting all tasks.

Preventing Node Overcommitment

Calculate node capacity:

Node capacity: 8 CPU cores, 16GB RAM
Reserved for OS: 1 CPU, 2GB RAM
Available: 7 CPU, 14GB RAM

With tasks reserving resources:

Task reservation: 0.5 CPU, 1GB RAM
Maximum tasks: min(14, 14) = 14 tasks per node

The scheduler automatically respects these limits.

High-Priority Task Resources

Reserve more resources for critical services:

# Critical service with generous reservations
docker service create \
  --name payment-api \
  --replicas 5 \
  --reserve-cpu 2.0 \
  --reserve-memory 4G \
  payment-api-image

# Standard service with minimal reservations
docker service create \
  --name logging \
  --replicas 10 \
  --reserve-cpu 0.25 \
  --reserve-memory 512M \
  logging-image

Critical services get guaranteed resources even under load.

Update Strategies for High Availability

Rolling Updates

Update a few replicas at a time:

docker service create \
  --name webapp \
  --replicas 10 \
  --update-parallelism 2 \
  --update-delay 15s \
  --update-failure-action pause \
  --update-max-failure-ratio 0.2 \
  webapp-image

If more than 20% of tasks fail during update, the update pauses automatically.

Update Rollback

Configure automatic rollback on failure:

docker service create \
  --name api \
  --replicas 5 \
  --update-parallelism 1 \
  --update-failure-action rollback \
  --rollback-parallelism 2 \
  --rollback-delay 5s \
  api-image

Failed updates automatically roll back to previous version.

Start-First Update Order

Maintain capacity during updates:

docker service create \
  --name webapp \
  --replicas 5 \
  --update-order start-first \
  webapp-image

New tasks start before old tasks stop, temporarily increasing replica count but ensuring capacity.

Testing High Availability

Chaos Engineering

Deliberately introduce failures to test HA:

Kill Random Tasks:

# Kill random replica
TASK=$(docker service ps myapp -q | shuf -n 1)
CONTAINER=$(docker ps -q --filter "label=com.docker.swarm.task.id=$TASK")
docker kill $CONTAINER

Verify that Swarm reschedules the task automatically.

Drain Random Node:

# Drain random worker
NODE=$(docker node ls -q --filter "role=worker" | shuf -n 1)
docker node update --availability drain $NODE

Verify that tasks are rescheduled to other nodes.

Partition Network: Simulate network partitions between nodes using firewall rules or network emulation tools.

Load Testing During Failures

Test system behavior under combined load and failures:

  1. Start load test generating realistic traffic
  2. Introduce failures (kill tasks, drain nodes)
  3. Monitor error rates and response times
  4. Verify recovery time and behavior

Planned Failure Drills

Regularly conduct failure drills:

Manager Failure Drill:

  1. Identify current leader manager
  2. Stop Docker daemon on leader
  3. Verify new leader election
  4. Verify cluster continues operating
  5. Restart stopped manager

Worker Failure Drill:

  1. Drain worker node
  2. Verify task rescheduling
  3. Monitor application performance
  4. Reactivate node

Zone Failure Drill:

  1. Drain all nodes in one availability zone
  2. Verify tasks reschedule to other zones
  3. Monitor application availability
  4. Reactivate zone

Monitoring for High Availability

Key Metrics to Monitor

Service Availability: Percentage of time service is responding correctly

Task Churn Rate: Frequency of task restarts (high churn indicates problems)

Node Health: CPU, memory, disk utilization per node

Health Check Success Rate: Percentage of health checks passing

Response Times: Service latency and percentiles

Error Rates: Application errors and failures

Alerting Thresholds

Set alerts for availability issues:

Quorum Risk: Alert when manager count drops close to quorum Low Replica Count: Alert when service has fewer than minimum replicas High Task Churn: Alert when restart rate exceeds normal levels Unscheduled Tasks: Alert when tasks remain in pending state Node Resource Exhaustion: Alert when nodes approach capacity limits

Health Check Monitoring

Monitor health check results:

# View task health status
docker service ps myapp --format "{{.Name}}: {{.CurrentState}}"

# Check task inspect for health details
docker inspect task_id | grep -A 10 Health

Track health check failure patterns to identify systemic issues.

Capacity Planning for High Availability

N+1 Redundancy

Maintain one extra node beyond minimum capacity:

Required capacity: 10 nodes
Deploy: 11 nodes (10 + 1 redundant)

If one node fails, remaining nodes handle the load without degradation.

N+2 Redundancy

For critical systems, maintain two extra nodes:

Required capacity: 10 nodes
Deploy: 12 nodes (10 + 2 redundant)

This tolerates two simultaneous node failures.

Autoscaling for Availability

Configure autoscaling with headroom:

Minimum replicas: 5 (even at zero load)
Target utilization: 60% (leaving 40% headroom)
Maximum replicas: 20

The headroom ensures capacity during traffic spikes and node failures.

Capacity Buffer Zones

Maintain buffers at multiple levels:

Node Level: Reserve 20% CPU and memory for overhead Cluster Level: Maintain 20% extra nodes beyond minimum Replica Level: Run 20% more replicas than minimum needed

These buffers absorb failures without impacting availability.

Best Practices for High Availability

Manager Configuration

Use 5 managers for production: Balances availability with performance Distribute across failure domains: Place managers in different zones/racks Use fast storage for managers: SSDs for Raft log performance Monitor manager health: Alert on manager failures immediately

Service Configuration

Minimum 3 replicas per service: Ensures basic redundancy Enable health checks: Detect and replace unhealthy tasks Configure graceful shutdown: Prevent request failures during updates Use start-first updates: Maintain capacity during deployments

Infrastructure Design

Multiple availability zones: Distribute nodes across zones Over-provision capacity: Maintain buffer for failures Separate failure domains: Avoid single points of failure Plan for complete zone loss: Ensure cluster operates with zone failures

Testing and Validation

Regular failure drills: Test HA mechanisms routinely Chaos engineering: Introduce random failures Load test with failures: Verify behavior under combined stress Document recovery procedures: Clear runbooks for incident response

High availability in Docker Swarm requires careful attention to manager quorum configuration, thoughtful workload distribution through anti-affinity patterns, and comprehensive failure planning. By maintaining sufficient manager count across failure domains, distributing service replicas intelligently, implementing health checks and graceful shutdown procedures, and regularly testing failure scenarios, you build resilient systems that maintain service continuity despite inevitable infrastructure failures.

We use cookies to improve your experience and analyse site traffic. See our Privacy Policy.