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.
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.
Linux Ops Smart Journey
The operations journey never stops—pursuing excellence endlessly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
