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.
[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)
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:docker stack deploy -c docker-compose.consul.yml consulversion: '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-serverimport 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")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');# 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# 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'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-
Deploy Consul Cluster:
docker stack deploy -c docker-compose.consul.yml consul
-
Verify Consul Cluster:
docker exec -it $(docker ps -q -f name=consul-server) consul members
-
Deploy Application Stack:
docker stack deploy -c docker-compose.app.yml myapp
-
Verify Service Registration:
curl http://consul-server:8500/v1/catalog/services
- Service Discovery: Services automatically find each other without hardcoded URLs
- Health Monitoring: Unhealthy services are automatically removed from the pool
- Configuration Centralization: Store and manage configuration in Consul KV
- Multi-Datacenter Support: Consul can span across multiple Swarm clusters
- Resilience: Services can gracefully handle failures of dependent services
-
Security:
- Enable ACLs in Consul
- Use Consul's gossip encryption
- Secure the Consul UI with authentication
-
Scaling:
- Adjust Consul server count based on your cluster size
- Consider separate Consul server nodes for large clusters
-
Monitoring:
- Monitor Consul metrics with Prometheus
- Set up alerts for Consul cluster health
-
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.