Consul doesn’t actually "register" services in the way you might think; it’s more about announcing their existence and health.
Let’s watch this happen. Imagine we have a simple web service running on 192.168.1.100:8080. We want Consul to know about it.
First, we need Consul itself running. A minimal setup might look like this:
docker run -d --name=consul -p 8500:8500 consul:latest
This spins up a single Consul agent in server mode, accessible on http://localhost:8500.
Now, to tell Consul about our web service, we create a configuration file. This is a JSON file, let’s call it web-service.json, placed in Consul’s configuration directory (or passed via CLI).
{
"service": {
"name": "web-app",
"port": 8080,
"check": {
"http": "http://192.168.1.100:8080/health",
"interval": "10s",
"timeout": "1s"
}
}
}
When the Consul agent (running on 192.168.1.100 in this scenario) loads this configuration, it starts announcing the web-app service. The port tells other services where to find it. The check is crucial: Consul will periodically GET http://192.168.1.100:8080/health. If it gets a 2xx or 3xx response within 1 second, the service is considered healthy. If it fails repeatedly, Consul marks it as critical.
To see this in action, you can query the Consul API:
curl http://localhost:8500/v1/catalog/service/web-app
You’ll see output detailing the web-app service, including its address and current health status. If you stop the web-app process, the health check will fail, and subsequent API calls will reflect that the service is unhealthy.
The real magic happens when other services want to find web-app. They don’t need to know its IP address beforehand. They can query Consul:
curl http://localhost:8500/v1/catalog/service/web-app
This returns the registered service’s details, including its address and port. A client application can then use this information to connect directly. If the service is unhealthy, Consul won’t return it, or it will return it with a status indicating it’s unhealthy, allowing clients to avoid it.
You can also register services and checks via the HTTP API directly, without dropping config files into the agent’s directory. For example, to register our web-app service:
curl --request PUT --data '{"Name": "web-app", "Port": 8080}' http://localhost:8500/v1/agent/service/register
And to register the health check:
curl --request PUT --data '{"Name": "web-app", "ServiceID": "web-app-check", "HTTP": "http://192.168.1.100:8080/health", "Interval": "10s", "Timeout": "1s"}' http://localhost:8500/v1/agent/check/register
This approach is more dynamic and suitable for services that might start and stop frequently, like in a containerized environment. The agent running on the same host as the service is typically responsible for registering it, simplifying network configuration.
Consul’s service discovery isn’t just about knowing where a service is, but also if it’s available to handle requests. This health checking mechanism is fundamental. Without it, you’d be relying on client-side load balancers or DNS round-robin, which don’t account for service failures.
The most surprising true thing about Consul’s service registration is that the agent itself is the primary driver; you don’t typically talk to the Consul server directly to register a service. You talk to the agent on the host where the service is running, and that agent communicates with the Consul server cluster. This distributed nature is key to its scalability and resilience.
The next step is often understanding how to use Consul’s built-in features like KV store or its distributed coordination primitives.