Consul’s export services feature allows you to expose services registered in one Consul cluster to other Consul clusters, even if they are not directly connected. This is particularly useful in multi-region or multi-cloud deployments where you might have separate Consul clusters managing services within their respective environments. The export feature acts as a bridge, enabling services in one cluster to be discoverable and reachable from another.
Let’s imagine a scenario where you have two Consul clusters: dc1 (e.g., in AWS us-east-1) and dc2 (e.g., in GCP us-central1). You have a web service registered in dc1 named webapp on port 8080. You want services in dc2 to be able to discover and connect to this webapp service.
Here’s how you would set this up using Consul’s configuration files.
First, on a Consul agent in dc1, you’d configure the export of the webapp service. This is done within the agent’s configuration directory (e.g., /etc/consul.d/).
# /etc/consul.d/export-webapp.hcl
service {
name = "webapp"
port = 8080
# This block tells Consul to export this service
export {
# The name of the service to export
service = "webapp"
# The datacenter from which to export the service
datacenter = "dc1"
# The name of the Consul cluster that will *receive* the export.
# This is crucial for security and routing.
# In this case, we are exporting to dc2.
consumer_datacenter = "dc2"
}
}
This configuration tells the dc1 Consul agent to make the webapp service (which is registered locally in dc1) available for export. The consumer_datacenter = "dc2" directive specifies that this export is intended for Consul agents operating within the dc2 datacenter.
Next, on a Consul agent in dc2, you need to configure it to receive the exported service from dc1. This is done by configuring a service-defaults block that references the remote datacenter.
# /etc/consul.d/import-webapp.hcl
# This block tells Consul to import services from another datacenter
# For exported services, this effectively "pulls" the remote service definition
# into the local catalog.
service_defaults {
# The name of the datacenter from which to import services.
# This should match the 'consumer_datacenter' in the export configuration.
datacenter = "dc1"
# The name of the service to import.
# This must match the 'service' name in the export configuration.
service = "webapp"
}
With these configurations in place, you would restart the Consul agents on both dc1 and dc2.
After the agents have restarted and established peering (assuming peering is already configured between dc1 and dc2 via WAN gossip or other means), you can verify the service discovery.
On an agent in dc2, you can run:
consul services --dc=dc2
You should see webapp listed. Crucially, if you query its details:
consul services --dc=dc2 -filter 'ServiceName:webapp' -node-meta 'consul-version=1.9.0' -service-meta 'consul-version=1.9.0'
You’ll notice that the service is listed as being provided by a remote node. Consul will create a synthetic service entry in dc2’s catalog that points back to the actual webapp service in dc1. When a client in dc2 queries for webapp, Consul will return the address and port of the webapp service in dc1.
The most surprising thing about Consul’s export services is that it doesn’t actually move or replicate the service. Instead, it creates a proxy or a stub entry in the consumer datacenter’s catalog that intelligently routes requests back to the originating datacenter. This means the service definition, health checks, and actual instances remain solely within the exporting datacenter, simplifying management and avoiding data synchronization issues.
To understand the mental model, think of it as a DNS SRV record that’s managed by Consul. When dc2 asks for webapp, Consul in dc2 looks up its catalog. It finds the synthetic entry for webapp which, internally, contains the information about which remote datacenter (dc1) and which Consul agent in that datacenter to query for the actual service endpoint. Consul then forwards the query to dc1, gets the real service address, and returns it to the client in dc2. This entire process is transparent to the client application.
The export and service_defaults blocks are the primary levers. The service and datacenter fields in service_defaults must precisely match the service and consumer_datacenter fields in the export block on the remote cluster. If there’s a mismatch, the service won’t be imported.
A common point of confusion is that service_defaults in the consumer datacenter is used for both setting default configurations for locally registered services and for importing remote exported services. The presence of the datacenter field within service_defaults is what signals to Consul that this block is intended for remote service import.
The next concept you’ll likely encounter is how to manage health checks for exported services.