Prometheus Exporter for Koyeb
- 4 minutes read - 747 wordsYet another cloud platform exporter for resource|cost management. This time for Koyeb with Koyeb Exporter.
Deploying resources to cloud platforms generally incurs cost based on the number of resources deployed, the time each resource is deployed and the cost (per period of time) that the resource is deployed. It is useful to be able to automatically measure and alert on all the resources deployed on all the platforms that you’re using and this is an intent of these exporters.
Here’s the set for those playing along at home:
- Prometheus Exporter for Azure
- Prometheus Exporter for Fly.io
- Prometheus Exporter for GCP
- Prometheus Exporter for Koyeb <—
- Prometheus Exporter for Linode
- Prometheus Exporter for Vultr
The exporter’s documentation is definitive and provides the list of metrics:
Name | Type | Description |
---|---|---|
apps_up |
Gauge | 1 if the App is up, 0 otherwise |
credentials_up |
Gauge | 1 if the Credential is up, 0 otherwise |
deployments_up |
Gauge | 1 if the Deployment is up, 0 otherwise |
domains_up |
Gauge | 1 if the Domain is up, 0 otherwise |
exporter_build_info |
Counter | A metric with a constant ‘1’ value labeled by OS version, Go version, and the Git commit of the exporter |
exporter_start_time |
Gauge | Exporter start time in Unix epoch seconds |
instances_up |
Gauge | 1 if the instance is up, 0 otherwise |
secrets_up |
Gauge | 1 if the Secret is up, 0 otherwise |
services_up |
Gauge | 1 if the Service is up, 0 otherwise |
The exporters follow the same patterns although my current preference differs (e.g. not /main.go
) and I continue to struggle on metric naming (one of two hard things). In earlier exporters, I prematurely summarized resources but the more recent exporters, more correctly leave aggregation to the user.
As with all of the other cloud platforms (that I’m using), Koyeb provides good developer documentation (API and a Golang SDK).
Koyeb’s SDK is auto-generated from a swagger|OpenAPI spec and I found it slightly more difficult to navigate than some others. Fortunately, the Koyeb CLI is open too, uses the SDK and was helpful to me in inferring best practices e.g.:
token := os.Getenv("TOKEN")
if token == "" {
log.Fatal("unable to get TOKEN")
}
cfg := koyeb.NewConfiguration()
client := koyeb.NewAPIClient(cfg)
ctx := context.Background()
ctx = context.WithValue(ctx, koyeb.ContextAccessToken, token)
NOTE To authenticate API requests this way, using an OAuth2 Access Token, you’ll need to get an API Access Token in the Koyeb Dashboard
To make a request e.g. to list Apps, you simply combine the client
and ctx
with the relevant method:
rqst := client.AppsApi.ListApps(ctx)
resp, _, err := rqst.Execute()
For simplicity, the exporter, doesn’t correctly page through the results. This is left to the reader as an exercise (see the CLI for examples e.g. AppHandler.List
).
The exporter implements metrics for most of Koyeb’s resources (Apps,Deployments,Domains,Instances,Secrets,Services). Each resource type is defined as a distinct collector
and each follows a similiar pattern.
Continuing with the Apps
example, the implementation defines a type (e.g. AppsCollector
) that encapsulates the Koyeb API client, context and the Prometheus metrics (e.g. up
) that will be recorded.
Prometheus’ Collector interface comprises two methods (Collect
and Describe
). Collect
is where the Koyeb list method is called for the resource and where, for each result, an instance of the metric (a measurment) is made labeled with some (hopefully relevant) subset of the resource’s metadata (e.g. IDs, regions etc.).
// Collect implements Prometheus' Collector interface and is used to collect metrics
func (c *AppsCollector) Collect(ch chan<- prometheus.Metric) {
rqst := c.Client.AppsApi.ListApps(c.Ctx)
resp, _, err := rqst.Execute()
if err != nil {
msg := "unable to list Apps"
log.Printf(msg, err)
return
}
for _, app := range resp.Apps {
ch <- prometheus.MustNewConstMetric(
c.Up,
prometheus.GaugeValue,
1.0,
[]string{
app.GetId(),
app.GetName(),
app.GetOrganizationId(),
string(app.GetStatus()),
}...,
)
}
}
The only complexity in the code is ensuring that the resource properties are converted to strings as required by Prometheus (label values).
In Golang, scalar types have default values but are never nil
. Composite types (arrays|slices, maps, structs) default to nil
until initialized. This is generally a good thing but it make it difficult to discern whether a scalar type is explicitly or implicitly set (if this is important). It is a somewhat common practice in Golang APIs to use pointers to scalar types to add the possibility of nil
and Koyeb’s API uses this mechanism.
The result of AppsApi.ListApps
includes a slice of AppListItem
and every field in this struct is a “pointer to” something. So, rather than the customary e.g. app.Id
, it’s better to use app.GetId()
as this method handles the possibility that the value is nil
.
That’s all!