From b406214901287246e049aa6b86a2b08b5eef9344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Thu, 27 Jul 2023 17:00:33 +0000 Subject: [PATCH] Initial commit --- .devcontainer/compose.yaml | 11 ++++++ .devcontainer/devcontainer.json | 35 ++++++++++++++++++ Dockerfile | 4 ++ Readme.md | 4 ++ app.py | 65 +++++++++++++++++++++++++++++++++ compose.yaml | 12 ++++++ requirements.txt | 3 ++ 7 files changed, 134 insertions(+) create mode 100644 .devcontainer/compose.yaml create mode 100644 .devcontainer/devcontainer.json create mode 100644 Dockerfile create mode 100644 Readme.md create mode 100644 app.py create mode 100644 compose.yaml create mode 100644 requirements.txt diff --git a/.devcontainer/compose.yaml b/.devcontainer/compose.yaml new file mode 100644 index 0000000..d8f3aa2 --- /dev/null +++ b/.devcontainer/compose.yaml @@ -0,0 +1,11 @@ +version: '3.8' +services: + devcontainer: + image: mcr.microsoft.com/devcontainers/python:1-3.11-bookworm + volumes: + - ../..:/workspaces:cached + - /var/run/docker.sock:/var/run/docker.sock:ro + #network_mode: service:db + command: sleep infinity + ports: + - 9101:9101 \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..759b2ca --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,35 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python +{ + "name": "Python 3", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "dockerComposeFile":"compose.yaml", + "service": "devcontainer", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + //"image": "mcr.microsoft.com/devcontainers/python:1-3.11-bookworm", + "features": { + "ghcr.io/devcontainers-contrib/features/act:1": {} + }, + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "pip install --user -r requirements.txt", + "customizations": { + "vscode": { + "extensions": [ + "ninoseki.vscode-pylens" + ] + } + } + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3f8d13e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM python:3.11-bookworm +RUN pip install -r requirements.txt +COPY app.py /app.py +ENTRYPOINT python /app.py \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..9300573 --- /dev/null +++ b/Readme.md @@ -0,0 +1,4 @@ +openshell-export-docker-status +============================== + +A Prometheus exporter for docker network interface stats diff --git a/app.py b/app.py new file mode 100644 index 0000000..56d6b1d --- /dev/null +++ b/app.py @@ -0,0 +1,65 @@ +import time +from typing import Dict +import docker +import prometheus_client +from prometheus_client.core import REGISTRY, CounterMetricFamily, InfoMetricFamily, StateSetMetricFamily + +class ContainerCollector(object): + docker_client: docker.Client + + def __init__(self, docker_client: docker.Client): + self.docker_client = docker_client + + def collect(self): + nw_counters: Dict[str, CounterMetricFamily] = {} + for stat in ["bytes", "packets", "errors", "dropped"]: + for rt_short in ("rx", "tx"): + rt_long = "receive" if rt_short == "rx" else "transmit" + key = f"{rt_short}_{stat}" + nw_counters[key] = CounterMetricFamily( + f"node_network_{rt_long}_{stat}_total", + f"Docker container stats network.{key}", + labels=["container", "device"], + ) + # "created", "running", "paused", "restarting", "removing", "exited", "dead" + # node_network_receive_packets_total + # node_network_speed_bytes + # node_network_transmit_bytes_total + # node_network_transmit_dropped_total + # node_network_transmit_errors_total + m_container_status = InfoMetricFamily("docker_container_info", "Container info.", labels=["container"]) + + for container in self.docker_client.containers(all=True): + container_name = container.get("Names", [""])[0].strip("/") + container_id = container.get("Id") + container_status = container.get('State','').lower() + + m_container_status.add_metric([container_name], dict(status=container_status)) + + #print(container_name, container_id) + stats = self.docker_client.stats(container_id, stream=False) + # print(stats) + for interface, ifstats in stats.get("networks", {}).items(): + #print(interface, ifstats) + + for stat, value in ifstats.items(): + if stat in nw_counters: + nw_counters[stat].add_metric( + [container_name, interface], + value, + ) + yield m_container_status + for metric in nw_counters.values(): + yield metric + + +REGISTRY.register(ContainerCollector(docker.Client())) +prometheus_client.start_http_server(9101) + + +print("Started") +try: + while True: + time.sleep(10) +except KeyboardInterrupt: + pass diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..75af0da --- /dev/null +++ b/compose.yaml @@ -0,0 +1,12 @@ +version: '2.1' +services: + exporter-docker-stats: + image: local/openshell-export-docker-status + build: . + pull_policy: never + restart: always + container_name: exporter-docker-stats + ports: + - 9101:9101 + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4ad64d3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +docker-py == 1.10.6 +prometheus-client == 0.17.1 +urllib3 >= 1.26.0, < 2.0.0 \ No newline at end of file