<a href="https://opensource.org/licenses/MIT" alt="MIT License">
<img alt="License MIT" src="https://img.shields.io/badge/License-MIT-yellow.svg" /></a>
<a href="https://enix.io/fr/blog/" alt="Brought to you by ENIX">
<img alt="Brought to you by ENIX" src="https://img.shields.io/badge/Brought%20to%20you%20by-ENIX-%23377dff?labelColor=888&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAQAAAC1QeVaAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElNRQfkBAkQIg/iouK/AAABZ0lEQVQY0yXBPU8TYQDA8f/zcu1RSDltKliD0BKNECYZmpjgIAOLiYtubn4EJxI/AImzg3E1+AGcYDIMJA7lxQQQQRAiSSFG2l457+655x4Gfz8B45zwipWJ8rPCQ0g3+p9Pj+AlHxHjnLHAbvPW2+GmLoBN+9/+vNlfGeU2Auokd8Y+VeYk/zk6O2fP9fcO8hGpN/TUbxpiUhJiEorTgy+6hUlU5N1flK+9oIJHiKNCkb5wMyOFw3V9o+zN69o0Exg6ePh4/GKr6s0H72Tc67YsdXbZ5gENNjmigaXbMj0tzEWrZNtqigva5NxjhFP6Wfw1N1pjqpFaZQ7FAY6An6zxTzHs0BGqY/NQSnxSBD6WkDRTf3O0wG2Ztl/7jaQEnGNxZMdy2yET/B2xfGlDagQE1OgRRvL93UOHqhLnesPKqJ4NxLLn2unJgVka/HBpbiIARlHFq1n/cWlMZMne1ZfyD5M/Aa4BiyGSwP4Jl3UAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjAtMDQtMDlUMTQ6MzQ6MTUrMDI6MDDBq8/nAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIwLTA0LTA5VDE0OjM0OjE1KzAyOjAwsPZ3WwAAAABJRU5ErkJggg==" /></a>
<img alt="Topomatik logo" src="https://raw.githubusercontent.com/enix/topomatik/refs/heads/main/artworks/topomatik.png" />
Topomatik automatically reflects your underlying infrastructure in Kubernetes node topology labels, because manually updating topology is about as fun as untangling holiday lights 🎄
Learn more about topology in Kubernetes:
We were at the Cloud Native Days France 2026 to present topomatik. The video and slides are in French, but the video also has English subtitles.
<a href="https://www.youtube.com/watch?v=CBrj0YF6KzE">
<img alt="Topomatik talk at CND France 2026" src="https://img.youtube.com/vi/TCLYktup5TU/maxresdefault.jpg" width="640" />
</a>
<a href="https://www.youtube.com/watch?v=CBrj0YF6KzE">▶️ Video (French with English subtitles)</a> · <a href="https://drive.google.com/file/d/1avsxPE0UAZ0y49dM4Hs4eP0oafd3C2lw/view">📑 Slides (French)</a>
topology.kubernetes.io/zone and topology.kubernetes.io/region node labels based on autodiscovered infrastructurehelm install my-release oci://quay.io/enix/charts/topomatik
When using Flux, you can use this HelmRepository object as repository for all ENIX projects:
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: enix
namespace: default
spec:
interval: 24h
type: oci
url: oci://quay.io/enix/charts
Then, you can create a HelmRelease object pointing to this repository:
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: topomatik
namespace: topomatik
spec:
interval: 1m0s
chart:
spec:
chart: topomatik
sourceRef:
kind: HelmRepository
name: enix
namespace: default
version: 1.*
Topomatik is configured using a YAML file. Here's an example configuration:
minimumReconciliationInterval: 1s
labelTemplates:
topology.kubernetes.io/zone: "{{ .files.zone }}-{{ .lldp.hostname }}"
topology.kubernetes.io/region: "{{ .files.region }}-{{ .lldp.description | regexp.Find "location: [^ ]" }}
lldp:
enabled: true
interface: auto
files:
zone:
path: /etc/zone
region:
path: /etc/region
interval: 5s
The labelsTemplates section defines which Kubernetes labels Topomatik will manage. Each value is a Go template that will be rendered to determine the label value.
The Sprig library is available for advanced template operations, giving you access to string manipulation, regular expressions, and more.
[!NOTE] Labels must be an empty string or consist of alphanumeric characters, "-", "_" or ".", and must start and end with an alphanumeric character. Invalid characters will be replaced by "_" and leading or trailing non alpha-numeric characters will be removed.
Example: "@@@foo+bar.foobar----." will be rendered as "foo_bar.foobar".
The taintTemplates section defines which Kubernetes taints Topomatik will manage. The map is keyed by the taint key; each entry has a value and an effect, both Go templates (same engine and Sprig functions as label templates).
taintTemplates:
topology.plaffitt.kubernetes.io/specialized:
value: "{{ .hostname.zone }}"
effect: NoSchedule
Allowed rendered effects: NoSchedule, PreferNoSchedule, NoExecute. An effect that renders to an unsupported value preserves the existing taint on the node and logs a warning.
Rendered values are sanitized using the same rules as label values (see the note above).
If effect renders to an empty string, the taint is not applied; if it already exists on the node it will be removed. This lets you conditionally manage a taint, e.g.:
taintTemplates:
topology.plaffitt.kubernetes.io/specialized:
value: "{{ .hostname.zone }}"
effect: '{{ if eq .hostname.zone "eu-west" }}NoSchedule{{ end }}'
[!NOTE] Conditional management of labels is driven by an empty rendered
value, but for taints it is driven by an empty renderedeffect. A taint with an emptyvalueis still a valid Kubernetes taint, whereas an emptyeffectis the natural signal that the taint should not exist.[!NOTE] Topomatik only manages taints whose key appears in
taintTemplates. Taints set by other controllers or by the user (e.g.kubectl taint) on different keys are preserved on every reconciliation. Removing an entry fromtaintTemplatesdoes not remove the corresponding taint from the node; remove it manually withkubectl taint nodes <name> <key>-or render theeffectto an empty string.
Each auto-discovery engine has its own configuration section.
All engines share a common enabled key that allows you to enable or disable the engine. The remaining configuration keys are specific to each engine.
Below you'll find detailed information about each supported engine
LLDP (Link Layer Discovery Protocol) is a vendor-neutral Layer 2 protocol that enables network devices to automatically discover and share information about their identity, capabilities, and neighboring device on a local network link (physical cable or veth pair).
It can be used in both bare-metal and virtualised environments to inform nodes about the underlying topology (eg: Proxmox PVE). In bare-metal environments, it must be enabled at the network device level (e.g., switches). In virtualised environments, you'll need to install the lldpd service on your hypervisors.
| Name | Description | Default value |
|---|---|---|
| enabled | Enable or disable this auto discovery engine | true |
| interface | Interface name to use for listen to LLDP frames or auto to use any interface with default gateway | auto |
| Name | Description |
|---|---|
| lldp.hostname | The SysName TLV |
| lldp.description | The SysDescription |
Topomatik can be used with Proxmox PVE using the lldpd. Just install it using apt install lldpd. The default configuration should be sufficient to advertise the TLV handled by Topomatik.
Reads the contents of a file. It supports reading files over http(s). It supports either watching or polling files. However, watch is supported for local files only using inotify.
| Name | Description | Default value |
|---|---|---|
| path | Path or url of the file to watch | <required> |
| interval | Use polling instead of watching (using inotify) | <required_for_remote_files> |
Example:
files:
# watching a local file
zone:
path: /etc/zone
# polling a local file
region:
path: /etc/region
interval: 5s
# polling a remote file
rack:
path: http://localhost:8080/rackname
interval: 1m # this is required for remote files
Template variables are defined by the name of each file entry in the configuration. For instance, with the above configuration, this engine exposes .files.zone, .files.region and .files.rack.
This engine allows to read information about the hardware (equivalent to dmidecode on Linux, but it also supports MacOS and Windows).
| Name | Description | Default value |
|---|---|---|
| enabled | Enable or disable this auto discovery engine | false |
| interval | Polling interval of the hardware information | <required> |
| Name | Description |
|---|---|
| hardware.chassis_serial | The chassis serial number |
| hardware.chassis_asset_tag | The chassis asset tag |
This engine allows to read the name of the host and parse it using regexps.
Example parsing the hostname EU-R1-42
hostname:
enabled: true
pattern: "(?P<zone>[A-Z]{2})-(?P<region>R[0-9])-(?P<node>[0-9]+)$"
| Name | Description | Default value |
|---|---|---|
| enabled | Enable or disable this auto discovery engine | false |
| interval | Polling interval of the hostname information | <required> |
| pattern | Regexp with named groups | - |
Template variables are defined by the name of an file entry in the configuration. For instance, with the above configuration, this engine exposes .hostname.zone, .hostname.region and .hostname.node, but since the pattern in the configuration is optional, it also exposes the whole hostname as .hostname.value.
Reads network information of the device in order to group machines by subnet. IPv4-only.
| Name | Description | Default value |
|---|---|---|
| enabled | Enable or disable this auto discovery engine | false |
| interface | Interface name to get the subnet from, or auto (or empty) to use the default route |
auto |
The only variable available here is network.subnet, which is in CIDR form, minus the fact that / is replace by _ since Kubernetes labels doesn't support / character (eg. 192.168.1.0_24).
| Name | Description |
|---|---|
| network.subnet | The subnet associated with the defined interface |
Found a bug? Want to add support for another discovery engine? We welcome contributions! Just be sure your code is as clean as your commit messages.
Topomatik is open-source software licensed under MIT. Use it, modify it, love it!
$ claude mcp add topomatik \
-- python -m otcore.mcp_server <graph>