Skip to content

Instantly share code, notes, and snippets.

@arsalanses
Created May 28, 2025 06:25
Show Gist options
  • Select an option

  • Save arsalanses/64984ed866987ba4237189b8de9be5dd to your computer and use it in GitHub Desktop.

Select an option

Save arsalanses/64984ed866987ba4237189b8de9be5dd to your computer and use it in GitHub Desktop.
Docker Swarm with Consul Setup for Service Discovery

Docker Swarm with Consul Setup for Service Discovery

As a DevOps engineer, I'll provide a complete setup for integrating Consul with Docker Swarm for service discovery, along with a practical example scenario.

Architecture Overview

[Docker Swarm Cluster]
│
├── [Manager Nodes] - Run Consul server agents
├── [Worker Nodes] - Run Consul client agents
│
└── [Example Services]
    ├── Web Service (Registered in Consul)
    └── API Service (Discovers Web Service via Consul)

1. Consul Cluster Setup for Docker Swarm

docker-compose.consul.yml

version: '3.8'

services:
  consul-server:
    image: consul:1.15
    command: agent -server -bootstrap-expect=3 -ui -client=0.0.0.0 -bind=0.0.0.0 -datacenter=dc1 -node=server-$${NODE_ID}
    environment:
      - NODE_ID={{.Node.ID}}
    volumes:
      - consul_data:/consul/data
    ports:
      - "8500:8500"
      - "8600:8600/tcp"
      - "8600:8600/udp"
    deploy:
      mode: replicated
      replicas: 3
      placement:
        constraints: [node.role == manager]
      restart_policy:
        condition: on-failure
      labels:
        - traefik.enable=true
        - traefik.http.routers.consul.rule=Host(`consul.example.com`)
        - traefik.http.routers.consul.entrypoints=websecure

  consul-client:
    image: consul:1.15
    command: agent -client=0.0.0.0 -retry-join=consul-server -datacenter=dc1 -node=client-$${NODE_ID}
    environment:
      - NODE_ID={{.Node.ID}}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      mode: global
      restart_policy:
        condition: on-failure

volumes:
  consul_data:

Deployment Command

docker stack deploy -c docker-compose.consul.yml consul

2. Example Scenario: Service Registration and Discovery

Scenario: Web Frontend Discovering Backend API

docker-compose.app.yml

version: '3.8'

services:
  webapp:
    image: your-webapp-image:latest
    environment:
      - SERVICE_NAME=webapp
      - SERVICE_TAGS=web,http
      - SERVICE_PORT=8080
    ports:
      - "8080:8080"
    deploy:
      replicas: 3
      labels:
        - com.docker.lb.hosts=webapp.example.com
        - com.docker.lb.port=8080
        - traefik.enable=true
        - traefik.http.routers.webapp.rule=Host(`webapp.example.com`)
    depends_on:
      - api

  api:
    image: your-api-image:latest
    environment:
      - SERVICE_NAME=api
      - SERVICE_TAGS=rest,backend
      - SERVICE_PORT=3000
      - CONSUL_URL=consul-server:8500
    deploy:
      replicas: 2
      labels:
        - com.docker.lb.hosts=api.example.com
        - com.docker.lb.port=3000

  registrator:
    image: gliderlabs/registrator:latest
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock
    command: -internal consul://consul-server:8500
    deploy:
      mode: global
    depends_on:
      - consul-server

3. Service Discovery Implementation

Python Example (API Service)

import requests
import consul

# Initialize Consul client
c = consul.Consul(host='consul-server', port=8500)

def discover_service(service_name):
    """Discover service instances from Consul"""
    index, nodes = c.health.service(service_name)
    if not nodes:
        raise Exception(f"No healthy instances of {service_name} found")
    
    # Get a random healthy instance (for simple load balancing)
    import random
    node = random.choice(nodes)
    return f"http://{node['Service']['Address']}:{node['Service']['Port']}"

def get_webapp_url():
    return discover_service('webapp')

# Example usage
webapp_url = get_webapp_url()
response = requests.get(f"{webapp_url}/some-endpoint")

Node.js Example (Web Service)

const consul = require('consul')({ host: 'consul-server', port: 8500 });

async function registerService() {
  await consul.agent.service.register({
    name: 'webapp',
    address: process.env.HOST_IP || 'webapp',
    port: parseInt(process.env.PORT || 8080),
    check: {
      http: `http://${process.env.HOST_IP || 'webapp'}:${process.env.PORT || 8080}/health`,
      interval: '10s',
      timeout: '5s'
    }
  });
  console.log('Service registered with Consul');
}

async function discoverService(serviceName) {
  const services = await consul.agent.service.list();
  const service = services[serviceName];
  if (!service) {
    throw new Error(`Service ${serviceName} not found`);
  }
  return `http://${service.Address}:${service.Port}`;
}

// Register on startup
registerService();

// Example usage
const apiUrl = await discoverService('api');

4. Health Checks and Monitoring

Enhanced Consul Setup with Health Checks

# Add to your consul-server service
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8500/v1/status/leader"]
  interval: 10s
  timeout: 5s
  retries: 3

# Add to your api service
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
  interval: 30s
  timeout: 10s
  retries: 3

5. Configuration Management with Consul KV

Storing Configuration

# Store DB configuration in Consul KV
docker exec -it $(docker ps -q -f name=consul-server) consul kv put config/api/db_url 'postgres://user:pass@db:5432/api'

Accessing Configuration in Services

import consul

c = consul.Consul(host='consul-server', port=8500)

# Get DB URL from Consul KV
index, data = c.kv.get('config/api/db_url')
db_url = data['Value'].decode('utf-8') if data else None

Deployment Workflow

  1. Deploy Consul Cluster:

    docker stack deploy -c docker-compose.consul.yml consul
  2. Verify Consul Cluster:

    docker exec -it $(docker ps -q -f name=consul-server) consul members
  3. Deploy Application Stack:

    docker stack deploy -c docker-compose.app.yml myapp
  4. Verify Service Registration:

    curl http://consul-server:8500/v1/catalog/services

Key Benefits of This Setup

  1. Service Discovery: Services automatically find each other without hardcoded URLs
  2. Health Monitoring: Unhealthy services are automatically removed from the pool
  3. Configuration Centralization: Store and manage configuration in Consul KV
  4. Multi-Datacenter Support: Consul can span across multiple Swarm clusters
  5. Resilience: Services can gracefully handle failures of dependent services

Production Considerations

  1. Security:

    • Enable ACLs in Consul
    • Use Consul's gossip encryption
    • Secure the Consul UI with authentication
  2. Scaling:

    • Adjust Consul server count based on your cluster size
    • Consider separate Consul server nodes for large clusters
  3. Monitoring:

    • Monitor Consul metrics with Prometheus
    • Set up alerts for Consul cluster health
  4. Backup:

    • Regularly backup Consul's KV store
    • Consider Consul snapshots for disaster recovery

This setup provides a robust foundation for service discovery and configuration management in your Docker Swarm environment while maintaining flexibility for different application architectures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment