Operations 6 min read

How to Build a Nacos‑Prometheus Adapter for Dynamic Service Discovery in Go

This article walks through the core code of a Nacos‑Prometheus adapter, explaining how it connects to Nacos, retrieves service and instance data, formats it into Prometheus http_sd JSON, and serves it via an HTTP endpoint, enabling dynamic service discovery for monitoring.

Linux Ops Smart Journey
Linux Ops Smart Journey
Linux Ops Smart Journey
How to Build a Nacos‑Prometheus Adapter for Dynamic Service Discovery in Go

In the previous article "From Static to Dynamic Monitoring: Prometheus + Nacos" the author introduced using Nacos for dynamic service discovery in Prometheus. This article dives into the core code of the Nacos‑Prometheus adapter, showing how it interacts with Nacos, generates Prometheus‑compatible metrics, and fits into the overall monitoring system.

Project Background and Design Idea

Background: The original project used Consul for service registration; switching to Nacos required changing Prometheus service discovery. Since Prometheus lacks native Nacos support, the generic http_sd mechanism is used.

Design:

Fetch Nacos service instance information.

Assemble data into Prometheus http_sd format.

Expose the data via an HTTP service to Prometheus.

Prometheus http_sd Format Requirements

1. Respond with HTTP 200 and UTF‑8 JSON.

2. Set the "Content‑Type: application/json" header.

3. If no targets exist, still return 200 with an empty array [] .

[
  {
    "targets": ["<host>", ...],
    "labels": {
      "<labelname>": "<labelvalue>", ...
    }
  },
  ...
]

Connecting to Nacos and Fetching Service Information

Code to create a Nacos client, retrieve all service names, and obtain healthy instances.

// Get configuration
nacoscfg := global.Config.Nacos

serverConfigs := []constant.ServerConfig{
    {
        IpAddr:      nacoscfg.Address,
        ContextPath: nacoscfg.ContextPath,
        Port:        uint64(nacoscfg.Port),
        Scheme:      "http",
    },
}
clientConfig := constant.ClientConfig{
    NamespaceId:         nacoscfg.NamespaceID,
    Username:            nacoscfg.Username,
    Password:            nacoscfg.Password,
    TimeoutMs:           10000,
    NotLoadCacheAtStart: false,
    LogDir:               "/tmp/nacos/logs",
    CacheDir:             "/tmp/nacos/cache",
    LogLevel:             "debug",
}
client, err := clients.NewNamingClient(vo.NacosClientParam{
    ClientConfig:  &clientConfig,
    ServerConfigs: serverConfigs,
})
if err != nil {
    return err
}
namingClient = client

// Get all services
var allServices []string
pageNo := 1
for {
    serviceInfos, err := namingClient.GetAllServicesInfo(vo.GetAllServiceInfoParam{
        NameSpace: global.Config.Nacos.NamespaceID,
        PageNo:    uint32(pageNo),
    })
    if err != nil {
        return nil, err
    }
    allServices = append(allServices, serviceInfos.Doms...)
    if len(allServices) == int(serviceInfos.Count) {
        break
    }
    pageNo++
}

// Get healthy instances
instances, err := namingClient.SelectInstances(vo.SelectInstancesParam{
    ServiceName: serviceName,
    HealthyOnly: true,
})
if err != nil {
    slog.Error("Get instances failed", "serviceName", serviceName, "cause", err.Error())
    break
}
slog.Debug("Get instances info", "serviceName", serviceName, "instances", instances)

Assembling Data in Prometheus http_sd Format

Define a struct and populate it with target addresses and Nacos metadata.

type Object struct {
    Targets []string          `json:"targets"`
    Labels  map[string]string `json:"labels"`
}
objs := []Object{}
for _, i := range instances {
    r := Object{
        Targets: []string{},
        Labels:  make(map[string]string),
    }
    r.Targets = []string{i.Ip + ":" + strconv.Itoa(int(i.Port))}
    r.Labels["__meta_nacos_cluster_name"] = i.ClusterName
    r.Labels["__meta_nacos_group_name"] = "DEFAULT_GROUP"
    r.Labels["__meta_nacos_service_name"] = strings.Split(i.ServiceName, "@@")[1]
    r.Labels["__meta_nacos_instance_address"] = i.Ip
    r.Labels["__meta_nacos_instance_port"] = strconv.Itoa(int(i.Port))
    for k, v := range i.Metadata {
        r.Labels["__meta_nacos_metadata_"+k] = v
    }
    objs = append(objs, r)
}

Returning the JSON via HTTP

w.Header().Set("Content-Type", "application/json")
v, _ := json.Marshal(objs)
if _, err := fmt.Fprintln(w, string(v)); err != nil {
    slog.Error("Response Writer failed", "error", err.Error())
}

Conclusion

By dissecting the Nacos‑Prometheus adapter code, we understand its internal workflow and learn how to extend or optimize it, gaining the ability to build highly available and scalable monitoring systems.

Monitoringservice discoveryGoNacosPrometheushttp_sd
Linux Ops Smart Journey
Written by

Linux Ops Smart Journey

The operations journey never stops—pursuing excellence endlessly.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.