# How Kratos Integrates with Service Discovery and Registry Patterns (etcd, Consul)

> Learn how Kratos uses pluggable components for service discovery and registry integration with etcd and Consul. Discover seamless backend implementation.

- Repository: [Kratos/kratos](https://github.com/go-kratos/kratos)
- Tags: deep-dive
- Published: 2026-03-02

---

**Kratos treats service discovery and registration as pluggable components that implement the `registry.Registrar` and `registry.Discovery` interfaces, allowing seamless integration with etcd, Consul, or any custom backend via constructor injection.**

Kratos is a popular open-source Go microservices framework that abstracts service discovery through clean interface contracts. The framework defines core registry contracts in [`registry/registry.go`](https://github.com/go-kratos/kratos/blob/main/registry/registry.go) that decouple your application from specific infrastructure backends. This architecture enables you to swap between etcd, Consul, or other service registries without changing application logic.

## Core Registry Interfaces

The foundation of Kratos service discovery lives in **[registry/registry.go](https://github.com/go-kratos/kratos/blob/main/registry/registry.go)**. This file defines two primary contracts:

- **Registrar**: Handles service lifecycle with `Register` and `Deregister` methods.
- **Discovery**: Handles service resolution with `GetService` and `Watch` methods.

Any implementation satisfying these interfaces can be injected into the application via the `Registrar` option defined in **[options.go](https://github.com/go-kratos/kratos/blob/main/options.go)**. The `ServiceInstance` struct carries metadata including service name, version, endpoints, and metadata map.

## etcd Integration

The etcd implementation resides in `contrib/registry/etcd` and provides a production-ready backend using etcd leases for TTL-based service health.

### Constructor and Configuration

The **[etcd/registry.go](https://github.com/go-kratos/kratos/blob/main/contrib/registry/etcd/registry.go)** file defines the `Registry` struct that implements both `Registrar` and `Discovery`. Construction occurs through `etcd.New` (lines 66‑78), which accepts a `clientv3.Client` and functional options:

```go
etcdreg.New(cli,
    etcdreg.Namespace("/myservices"),
    etcdreg.RegisterTTL(10*time.Second),
)

```

### Registration Mechanism

Registration creates a lease against the etcd cluster and stores the service payload under the key pattern `/<namespace>/<service>/<id>`. A background goroutine maintains the lease heartbeat to prevent expiration during healthy operation. Deregistration revokes the lease, causing immediate key deletion.

### Discovery and Watching

The `GetService` method performs a prefix query against the etcd keyspace using the configured namespace. For real-time updates, **[etcd/watcher.go](https://github.com/go-kratos/kratos/blob/main/contrib/registry/etcd/watcher.go)** (lines 26‑44) implements the `Watcher` interface using `clientv3.Watcher` to stream etcd events and translate them into service instance updates.

## Consul Integration

The Consul implementation lives in `contrib/registry/consul` and integrates with HashiCorp Consul's agent API for service registration and health checking.

### Constructor and Client Wrapper

The **[consul/registry.go](https://github.com/go-kratos/kratos/blob/main/contrib/registry/consul/registry.go)** constructor `consul.New` (lines 12‑32) wraps a `consul.Client` provided by **[consul/client.go](https://github.com/go-kratos/kratos/blob/main/contrib/registry/consul/client.go)**. The constructor accepts options to enable health checks, heartbeats, and automatic deregistration:

```go
consulreg.New(cli,
    consulreg.WithHealthCheck(true),
    consulreg.WithHeartbeat(true),
    consulreg.WithDeregisterCriticalServiceAfter(30),
)

```

### Registration with Health Checks

Registration uses Consul's `AgentServiceRegistration` API with optional TCP or script-based health checks. The implementation registers a service ID with checks that Consul uses to determine service health. When configured with `WithHeartbeat`, the client sends periodic updates to maintain the "passing" status.

### Discovery and Watching

The `GetService` implementation first checks an in-memory cache; if empty, it queries the Consul Catalog API via `c.cli.Service`. The **[consul/watcher.go](https://github.com/go-kratos/kratos/blob/main/contrib/registry/consul/watcher.go)** forwards change notifications from an internal `serviceSet` that updates when the Consul query returns different service instances.

## Application Integration Flow

Integrating either registry follows a consistent three-step pattern:

1. **Instantiate the registry** using the appropriate contrib package (etcd or Consul).
2. **Pass the registry** to `kratos.New` using the `kratos.Registrar` option.
3. **Run the application**, which automatically invokes `Register` on startup and `Deregister` on graceful shutdown.

Services can discover peers by accessing the registry directly through the `Discovery` interface to call `GetService` or `Watch`.

## Practical Implementation Examples

### etcd Service Registration

```go
package main

import (
	"context"
	"time"

	etcdreg "github.com/go-kratos/kratos/contrib/registry/etcd/v2"
	"github.com/go-kratos/kratos/v2"
	"github.com/go-kratos/kratos/v2/log"
	clientv3 "go.etcd.io/etcd/client/v3"
)

func main() {
	// Build an etcd client
	cli, _ := clientv3.New(clientv3.Config{
		Endpoints:   []string{"localhost:2379"},
		DialTimeout: 5 * time.Second,
	})

	// Create the Kratos etcd registry
	etcdRegistry := etcdreg.New(cli,
		etcdreg.Namespace("/myservices"),
		etcdreg.RegisterTTL(10*time.Second),
	)

	// Build the Kratos application, injecting the registry
	app := kratos.New(
		kratos.ID("order-srv"),
		kratos.Name("order"),
		kratos.Version("v1.0.0"),
		kratos.Registrar(etcdRegistry),
		kratos.Logger(log.NewStdLogger()),
	)

	// Run the app (registrar will auto-register on start)
	if err := app.Run(); err != nil {
		panic(err)
	}
}

```

### Consul Service Registration

```go
package main

import (
	"context"
	"time"

	consulreg "github.com/go-kratos/kratos/contrib/registry/consul/v2"
	"github.com/go-kratos/kratos/v2"
	"github.com/go-kratos/kratos/v2/log"
	consulapi "github.com/hashicorp/consul/api"
)

func main() {
	// Consul API client
	cli, _ := consulapi.NewClient(&consulapi.Config{
		Address: "127.0.0.1:8500",
		Scheme:  "http",
	})

	// Consul registry with health checks
	consulRegistry := consulreg.New(cli,
		consulreg.WithHealthCheck(true),
		consulreg.WithHeartbeat(true),
		consulreg.WithDeregisterCriticalServiceAfter(30),
	)

	// Kratos app with Consul registrar
	app := kratos.New(
		kratos.ID("payment-srv"),
		kratos.Name("payment"),
		kratos.Version("v2.1.0"),
		kratos.Registrar(consulRegistry),
		kratos.Logger(log.NewStdLogger()),
	)

	if err := app.Run(); err != nil {
		panic(err)
	}
}

```

### Watching for Service Changes

```go
func watchOrders(ctx context.Context, r registry.Discovery) {
	w, _ := r.Watch(ctx, "order")
	for {
		services, err := w.Next()
		if err != nil {
			// handle context cancellation or watch errors
			return
		}
		// react to updated service list
		fmt.Printf("order instances: %v\n", services)
	}
}

```

## Summary

- Kratos defines abstract **Registrar** and **Discovery** interfaces in [`registry/registry.go`](https://github.com/go-kratos/kratos/blob/main/registry/registry.go) that decouple applications from specific service registries.
- The **etcd** implementation uses lease-based registration with automatic heartbeats and prefix-based discovery with etcd watchers.
- The **Consul** implementation wraps the Consul agent API, supporting configurable health checks and catalog-based service resolution.
- Both implementations reside in the `contrib/` directory and satisfy the same interfaces, enabling zero-code-change swapping between backends.
- Registration occurs automatically on application startup when using the `kratos.Registrar` option, while discovery uses `GetService` or `Watch` methods.

## Frequently Asked Questions

### How do I switch from etcd to Consul in an existing Kratos application?

Switching registries requires only changing the constructor call and import path. Replace `etcdreg.New` with `consulreg.New`, pass the appropriate client configuration, and inject the new registry using `kratos.Registrar`. The application code that calls `GetService` or `Watch` remains unchanged because both implementations satisfy the same `registry.Discovery` interface.

### What is the difference between the Registrar and Discovery interfaces?

The **Registrar** interface manages a service's own lifecycle in the registry, providing `Register` and `Deregister` methods that Kratos calls during startup and shutdown. The **Discovery** interface enables clients to locate other services, providing `GetService` for immediate lookup and `Watch` for receiving updates when service instances change. A single struct (like `etcd.Registry` or `consul.Registry`) typically implements both interfaces.

### How does Kratos handle service health checking with these registries?

For **etcd**, Kratos creates a lease with a TTL and spawns a heartbeat goroutine that keeps the lease alive; if the process crashes, etcd automatically removes the expired key. For **Consul**, the implementation registers health checks (TCP, HTTP, or TTL) with the Consul agent; if checks fail or the service becomes critical, Consul marks the service as unhealthy and can automatically deregister it after a configurable timeout.

### Can I implement a custom service registry for Kratos?

Yes. Implement the `Registrar` and `Discovery` interfaces defined in [`registry/registry.go`](https://github.com/go-kratos/kratos/blob/main/registry/registry.go), ensuring your type provides thread-safe `Register`, `Deregister`, `GetService`, and `Watch` methods. Place your implementation in your project or a separate module, then inject it using `kratos.Registrar(yourRegistry)` when building the application. This pattern allows integration with proprietary service discovery systems or databases.