imds: expose instance metadata as an io.systemd.Metrics provider

When systemd-imds is invoked as a Varlink service (via the new
systemd-imds-metrics.socket), it now acts as an io.systemd.Metrics
provider for systemd-report. It connects to systemd-imdsd over the
existing io.systemd.InstanceMetadata interface to acquire the real
data and re-exposes the detected cloud vendor plus the well-known
hostname, region, zone and public IPv4/IPv6 fields as metrics in the
io.systemd.InstanceMetadata.* namespace.

The metrics logic lives entirely on the client side
(imds-tool-metrics.c); systemd-imdsd is unchanged. Each metric is
acquired on demand with a blocking call to the daemon, benefiting from
its local cache. Fields that are unset or unsupported by the vendor are
simply omitted.

The metrics socket is statically enabled into sockets.target.wants/.
This commit is contained in:
Lennart Poettering
2026-06-22 22:03:12 +02:00
parent d90ae91ef3
commit 55db8dc12b
10 changed files with 322 additions and 15 deletions
+12
View File
@@ -69,6 +69,17 @@
fields are imported into the local credential store, where they are used to configure and parameterize
the system. For details see below.</para></listitem>
</itemizedlist>
<para>In addition, when invoked as a Varlink service (i.e. via socket activation through
<filename>systemd-imds-metrics.socket</filename>), <command>systemd-imds</command> acts as an
<constant>io.systemd.Metrics</constant> provider for
<citerefentry><refentrytitle>systemd-report</refentrytitle><manvolnum>1</manvolnum></citerefentry>. It
exposes the detected cloud vendor and the well-known <literal>hostname</literal>,
<literal>region</literal>, <literal>zone</literal>, <literal>ipv4-public</literal> and
<literal>ipv6-public</literal> fields as metrics in the <literal>io.systemd.InstanceMetadata.</literal>
namespace. The data is acquired on demand from
<citerefentry><refentrytitle>systemd-imdsd@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
benefiting from its local cache.</para>
</refsect1>
<refsect1>
@@ -167,6 +178,7 @@
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-imdsd@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-imds-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-report</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.system-credentials</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
+5
View File
@@ -189,6 +189,11 @@ static int run(const char *dest, const char *dest_early, const char *dest_late)
return log_error_errno(r, "Failed to hook in systemd-imds-import.service: %m");
}
/* Enable IMDS metrics in case IMDS is supported */
r = generator_add_symlink(dest_early, SPECIAL_SOCKETS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-imds-metrics.socket");
if (r < 0)
return log_error_errno(r, "Failed to hook in systemd-imds-metrics.socket: %m");
return 0;
}
+164
View File
@@ -0,0 +1,164 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-json.h"
#include "sd-varlink.h"
#include "imds-tool.h"
#include "imds-tool-metrics.h"
#include "imds-util.h"
#include "log.h"
#include "metrics.h"
#include "string-util.h"
#include "varlink-io.systemd.Metrics.h"
#include "varlink-util.h"
#define METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "io.systemd.InstanceMetadata."
static int metric_vendor_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
sd_varlink *imds = userdata; /* The live systemd-imdsd connection, or NULL if unavailable */
_cleanup_free_ char *vendor = NULL;
int r;
assert(mf && mf->name);
assert(vl);
if (!imds)
return 0;
r = acquire_imds_vendor(imds, &vendor);
if (r <= 0)
return 0; /* On error or absence simply omit the metric */
return metric_build_send_string(mf, vl, /* object= */ NULL, vendor, /* fields= */ NULL);
}
static int metric_well_known_build_json(
const MetricFamily *mf,
sd_varlink *vl,
void *userdata,
ImdsWellKnown wk) {
sd_varlink *imds = userdata;
_cleanup_free_ char *value = NULL;
int r;
assert(mf && mf->name);
assert(vl);
if (!imds)
return 0;
r = acquire_imds_key_as_string(imds, wk, /* key= */ NULL, &value);
if (r <= 0 || isempty(value))
return 0; /* Field not supported/set, or error: omit the metric */
return metric_build_send_string(mf, vl, /* object= */ NULL, value, /* fields= */ NULL);
}
static int metric_hostname_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
return metric_well_known_build_json(mf, vl, userdata, IMDS_HOSTNAME);
}
static int metric_ipv4_public_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
return metric_well_known_build_json(mf, vl, userdata, IMDS_IPV4_PUBLIC);
}
static int metric_ipv6_public_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
return metric_well_known_build_json(mf, vl, userdata, IMDS_IPV6_PUBLIC);
}
static int metric_region_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
return metric_well_known_build_json(mf, vl, userdata, IMDS_REGION);
}
static int metric_zone_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
return metric_well_known_build_json(mf, vl, userdata, IMDS_ZONE);
}
/* Keep metrics ordered alphabetically */
static const MetricFamily imds_metric_family_table[] = {
{
.name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "Hostname",
.description = "Instance hostname reported by IMDS",
.type = METRIC_FAMILY_TYPE_STRING,
.generate = metric_hostname_build_json,
},
{
.name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "IPv4Public",
.description = "Public IPv4 address reported by IMDS",
.type = METRIC_FAMILY_TYPE_STRING,
.generate = metric_ipv4_public_build_json,
},
{
.name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "IPv6Public",
.description = "Public IPv6 address reported by IMDS",
.type = METRIC_FAMILY_TYPE_STRING,
.generate = metric_ipv6_public_build_json,
},
{
.name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "Region",
.description = "Cloud region reported by IMDS",
.type = METRIC_FAMILY_TYPE_STRING,
.generate = metric_region_build_json,
},
{
.name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "Vendor",
.description = "Detected cloud vendor",
.type = METRIC_FAMILY_TYPE_STRING,
.generate = metric_vendor_build_json,
},
{
.name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "Zone",
.description = "Cloud availability zone reported by IMDS",
.type = METRIC_FAMILY_TYPE_STRING,
.generate = metric_zone_build_json,
},
{}
};
static int vl_method_metrics_describe(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
return metrics_method_describe(imds_metric_family_table, link, parameters, flags, userdata);
}
static int vl_method_metrics_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
int r;
/* Acquire a single connection to systemd-imdsd, shared by all metric generators (so they benefit
* from the daemon's token/data cache). If the daemon is unreachable we still serve an empty list
* rather than failing the whole request. */
_cleanup_(sd_varlink_unrefp) sd_varlink *imds = NULL;
r = connect_imdsd(&imds);
if (r < 0)
log_debug_errno(r, "Failed to connect to systemd-imdsd, serving empty metrics list: %m");
return metrics_method_list(imds_metric_family_table, link, parameters, flags, imds);
}
int imds_metrics_run(void) {
int r;
/* Invocation as a Varlink metrics provider (io.systemd.Metrics), typically socket-activated via
* /run/systemd/report/io.systemd.InstanceMetadata. */
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *server = NULL;
r = varlink_server_new(&server, /* flags= */ 0, /* userdata= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to allocate Varlink server: %m");
r = sd_varlink_server_add_interface(server, &vl_interface_io_systemd_Metrics);
if (r < 0)
return log_error_errno(r, "Failed to add Varlink interface: %m");
r = sd_varlink_server_bind_method_many(
server,
"io.systemd.Metrics.List", vl_method_metrics_list,
"io.systemd.Metrics.Describe", vl_method_metrics_describe);
if (r < 0)
return log_error_errno(r, "Failed to bind Varlink methods: %m");
r = sd_varlink_server_loop_auto(server);
if (r < 0)
return log_error_errno(r, "Failed to run Varlink event loop: %m");
return 0;
}
+5
View File
@@ -0,0 +1,5 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
/* Runs systemd-imds as an io.systemd.Metrics provider (socket-activated). */
int imds_metrics_run(void);
+67 -14
View File
@@ -18,6 +18,8 @@
#include "format-util.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "imds-tool.h"
#include "imds-tool-metrics.h"
#include "imds-util.h"
#include "in-addr-util.h"
#include "io-util.h"
@@ -232,7 +234,7 @@ static int acquire_imds_key(
return 1;
}
static int acquire_imds_key_as_string(
int acquire_imds_key_as_string(
sd_varlink *link,
ImdsWellKnown wk,
const char *key,
@@ -292,14 +294,11 @@ static int acquire_imds_key_as_ip_address(
return 1;
}
static int action_summary(sd_varlink *link) {
int acquire_imds_vendor(sd_varlink *link, char **ret) {
int r;
assert(link);
_cleanup_(table_unrefp) Table *table = table_new_vertical();
if (!table)
return log_oom();
assert(ret);
const char *error_id = NULL;
sd_json_variant *reply = NULL;
@@ -311,17 +310,48 @@ static int action_summary(sd_varlink *link) {
&error_id);
if (r < 0)
return log_error_errno(r, "Failed to issue io.systemd.InstanceMetadata.GetVendorInfo(): %m");
if (error_id)
if (error_id) {
if (streq(error_id, "io.systemd.InstanceMetadata.NotSupported")) {
*ret = NULL;
return 0;
}
return log_error_errno(sd_varlink_error_to_errno(error_id, reply), "Failed to issue io.systemd.InstanceMetadata.GetVendorInfo(): %s", error_id);
}
const char *vendor = NULL;
static const sd_json_dispatch_field dispatch_table[] = {
{ "vendor", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 },
{ "vendor", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 },
{}
};
r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG, &vendor);
if (r < 0)
return r;
if (isempty(vendor)) {
*ret = NULL;
return 0;
}
if (strdup_to_full(ret, vendor) < 0)
return log_oom();
return 1;
}
static int action_summary(sd_varlink *link) {
int r;
assert(link);
_cleanup_(table_unrefp) Table *table = table_new_vertical();
if (!table)
return log_oom();
_cleanup_free_ char *vendor = NULL;
r = acquire_imds_vendor(link, &vendor);
if (r < 0)
return r;
if (vendor) {
r = table_add_many(table,
TABLE_FIELD, "Vendor",
@@ -820,14 +850,10 @@ static int action_import(sd_varlink *link) {
return RET_GATHER(ret, import_credentials(data.iov_base));
}
static int run(int argc, char* argv[]) {
int connect_imdsd(sd_varlink **ret) {
int r;
log_setup();
r = parse_argv(argc, argv);
if (r <= 0)
return r;
assert(ret);
_cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL;
r = sd_varlink_connect_address(&link, "/run/systemd/io.systemd.InstanceMetadata");
@@ -850,6 +876,33 @@ static int run(int argc, char* argv[]) {
return log_error_errno(r, "Failed to connect to imdsd service: %m");
}
*ret = TAKE_PTR(link);
return 0;
}
static int run(int argc, char* argv[]) {
int r;
log_setup();
/* When invoked as a Varlink service (socket activation) we act as an io.systemd.Metrics provider
* and run a server loop. The metric generators connect to systemd-imdsd on demand, so we never open
* a one-shot connection here. */
r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
if (r < 0)
return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
if (r > 0)
return imds_metrics_run();
r = parse_argv(argc, argv);
if (r <= 0)
return r;
_cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL;
r = connect_imdsd(&link);
if (r < 0)
return r;
switch (arg_action) {
case ACTION_SUMMARY:
+11
View File
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "imds-util.h"
#include "shared-forward.h"
/* Helpers shared between imds-tool.c and imds-tool-metrics.c for talking to systemd-imdsd. */
int connect_imdsd(sd_varlink **ret);
int acquire_imds_key_as_string(sd_varlink *link, ImdsWellKnown wk, const char *key, char **ret);
int acquire_imds_vendor(sd_varlink *link, char **ret);
+3 -1
View File
@@ -14,7 +14,9 @@ executables += [
libexec_template + {
'name' : 'systemd-imds',
'public' : true,
'sources' : files('imds-tool.c'),
'sources' : files(
'imds-tool.c',
'imds-tool-metrics.c'),
'objects' : ['systemd-imdsd'],
},
generator_template + {
+8
View File
@@ -413,6 +413,14 @@ units = [
'file' : 'systemd-imds-import.service.in',
'conditions' : ['ENABLE_IMDS'],
},
{
'file' : 'systemd-imds-metrics.socket',
'conditions' : ['ENABLE_IMDS'],
},
{
'file' : 'systemd-imds-metrics@.service.in',
'conditions' : ['ENABLE_IMDS'],
},
{
'file' : 'systemd-importd.service.in',
'conditions' : ['ENABLE_IMPORTD'],
+22
View File
@@ -0,0 +1,22 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Cloud Instance Metadata Metrics
Documentation=man:systemd-imds(1)
DefaultDependencies=no
Before=sockets.target
[Socket]
ListenStream=/run/systemd/report/io.systemd.InstanceMetadata
FileDescriptorName=varlink
SocketMode=0666
Accept=yes
MaxConnectionsPerSource=16
RemoveOnStop=yes
+25
View File
@@ -0,0 +1,25 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Cloud Instance Metadata Metrics
Documentation=man:systemd-imds(1)
DefaultDependencies=no
Conflicts=shutdown.target initrd-switch-root.target
Before=shutdown.target initrd-switch-root.target
After=systemd-imdsd.socket
[Service]
# Runs systemd-imds as an io.systemd.Metrics provider. It connects to
# systemd-imdsd to acquire the actual data. Runs as root so the daemon's Polkit
# check on io.systemd.InstanceMetadata.Get() passes.
ExecStart={{LIBEXECDIR}}/systemd-imds
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes