Docker Infos abrufen und als Variablen speichern

Hallo Zusammen,
habe per Suche nichts gefunden ob das Thema schon jemand angegangen ist.
Falls Interesse besteht, ich habe ein Skript geschrieben womit man die Docker Infos abrufen kann (Laufende Container mit Infos, Docker Infos).
Das Skript nimmt den weg über die Unix Socket.
Bei mir läuft alles auf einem Raspberry Pi 4 in Containern (IP Symcon, PiHole, Zigbee2Mqtt, Mosquitto, …)
Wichtig ist das IP Symcon unter Linux oder im Container auf das Verzeichnis /var/run/docker.sock zugreifen kann. Unter Windows gibt es da glaube ich einen anderen Weg. Mangels Aufbau kann ich das nicht testen. Aber das sollte mit paar anpassungen auch gehen.
In meiner Docker-Compose File habe ich unter Volumes einen weiteren Eintrag erstellt.

Hier das momentane Skript. Da ich eher im C# Umfeld unterwegs bin muss ich mich noch bisschen an das PHP Kauderwelsch gewöhnen.
Gibt noch paar Sachen die man schöner machen kann und es gibt definitiv noch mehr Daten zu sammeln und zu überwachen.

<?php

$DummyInstanceId = "{485D0419-BE97-4548-AA9C-C083EB82E61E}";
$ParentFolder = ID-FOLDER;

$client = new DockerClient('/var/run/docker.sock');

$dockerContainers  = $client->dispatchCommand('/containers/json');
foreach ($dockerContainers as $container) 
{
    $instanceId = AddOrUpdateContainerInfos($container);

    //$containerDetails = $client->dispatchCommand('/containers/'.$container['Id'].'/json');
    //AddOrUpdateContainerDetails($instanceId, $containerDetails);
}

$dockerInfo = $client->dispatchCommand('/info');
if($dockerInfo === false) return;

AddOrUpdateDockerInfo("Container gesamt", VARIABLETYPE_INTEGER, $ParentFolder, $dockerInfo['Containers']);
AddOrUpdateDockerInfo("Container werden Ausgeführt", VARIABLETYPE_INTEGER, $ParentFolder, $dockerInfo['ContainersRunning']);
AddOrUpdateDockerInfo("Container die Pausiert sind", VARIABLETYPE_INTEGER, $ParentFolder, $dockerInfo['ContainersPaused']);
AddOrUpdateDockerInfo("Container die Gestoppt sind", VARIABLETYPE_INTEGER, $ParentFolder, $dockerInfo['ContainersStopped']);
AddOrUpdateDockerInfo("Vorhandene Images", VARIABLETYPE_INTEGER, $ParentFolder, $dockerInfo['Images']);
AddOrUpdateDockerInfo("Kernel Version", VARIABLETYPE_STRING, $ParentFolder, $dockerInfo['KernelVersion']);
AddOrUpdateDockerInfo("Betriebssystem", VARIABLETYPE_STRING, $ParentFolder, $dockerInfo['OperatingSystem']);
AddOrUpdateDockerInfo("Betriebsystem Version", VARIABLETYPE_STRING, $ParentFolder, $dockerInfo['OSVersion']);
AddOrUpdateDockerInfo("Docker Version", VARIABLETYPE_STRING, $ParentFolder, $dockerInfo['ServerVersion']);

/* Functions */

function AddOrUpdateContainerDetails($instanceId, $containerDetails)
{
    //AddOrUpdateRestartCount($instanceId, $containerDetails);
}

function AddOrUpdateContainerInfos($container)
{ 
    $instanceId = AddOrUpdateInstance($container);
    AddOrUpdateCreated($instanceId, $container);
    AddOrUpdateState($instanceId, $container);
    AddOrUpdateStatus($instanceId, $container);
    AddOrUpdateImage($instanceId, $container); 
    return $instanceId;   
}

function AddOrUpdateDockerInfo($propertyName, $type, $parentId, $value)
{
    $propertyId = @IPS_GetVariableIDByName($propertyName, $parentId);
    if($propertyId === false)
    {
        $propertyId = IPS_CreateVariable($type);
        IPS_SetName($propertyId, $propertyName);
        IPS_SetParent($propertyId, $parentId);
    }

    $oldValue = GetValue($propertyId);
    if($oldValue != $value)
      SetValue($propertyId, $value);
}

function AddOrUpdateRestartCount($instanceId, $containerDetails)
{
    AddOrUpdate($instanceId, $containerDetails, "Neustarts", VARIABLETYPE_INTEGER, "", $containerDetails['RestartCount']);
}

function AddOrUpdateImage($instanceId, $container)
{
    AddOrUpdate($instanceId, $container, "Container Image", VARIABLETYPE_STRING, "", $container['Image']);
}

function AddOrUpdateStatus($instanceId, $container)
{
    AddOrUpdate($instanceId, $container, "Status", VARIABLETYPE_STRING, "", $container['Status']);
}

function AddOrUpdateState($instanceId, $container)
{
    AddOrUpdate($instanceId, $container, "Ausführung", VARIABLETYPE_STRING, "", $container['State']);
}

function AddOrUpdateCreated($instanceId, $container)
{
    AddOrUpdate($instanceId, $container, "Erstellt", VARIABLETYPE_INTEGER, "~UnixTimestamp", $container['Created']);
}

function AddOrUpdate($instanceId, $container, $varName, $varType, $varProfile, $arrayProperty)
{
    $createdId = @IPS_GetVariableIDByName($varName, $instanceId);
    if($createdId === false)
    {
        $createdId = IPS_CreateVariable($varType);
        IPS_SetParent($createdId, $instanceId);
        IPS_SetName($createdId, $varName);
        IPS_SetVariableCustomProfile($createdId, $varProfile);
    }

    if(GetValue($createdId) != $arrayProperty)
    {
        SetValue($createdId, $arrayProperty);
    }
}

function GetName($container)
{
    return substr($container['Names'][0], 1);
}

function AddOrUpdateInstance($container)
{
    global $ParentFolder;
    global $DummyInstanceId;

    $containerName = GetName($container);
    $instanceId = @IPS_GetInstanceIDByName($containerName, $ParentFolder);
    if($instanceId === false)
    {
        $instanceId = IPS_CreateInstance($DummyInstanceId);
        IPS_SetParent($instanceId, $ParentFolder);
        IPS_SetName($instanceId, $containerName);
    }

    return $instanceId;
}

/*Credits https://gist.github.com/FergusInLondon/c3eb96f1f6565a81e509ece6b14b9a78 */

class DockerClient {

    /** @param resource */
    private $curlClient;

    /** @param string */
    private $socketPath;

    /** @param string|null */
    private $curlError = null;

    /**
     * Constructor: Initialises the Curl Resource, making it usable for subsequent
     *  API requests.
     *
     * @param string
     */
    public function __construct(string $socketPath)
    {
        $this->curlClient = curl_init();
        $this->socketPath = $socketPath;

        curl_setopt($this->curlClient, CURLOPT_UNIX_SOCKET_PATH, $socketPath);
        curl_setopt($this->curlClient, CURLOPT_RETURNTRANSFER, true);
    }

    /**
     * Deconstructor: Ensure the Curl Resource is correctly closed.
     */
    public function __destruct()
    {
        curl_close($this->curlClient);
    }

    private function generateRequestUri(string $requestPath)
    {
        /* Please note that Curl doesn't use http+unix:// or any other mechanism for
         *  specifying Unix Sockets; once the CURLOPT_UNIX_SOCKET_PATH option is set,
         *  Curl will simply ignore the domain of the request. Hence why this works,
         *  despite looking as though it should attempt to connect to a host found at
         *  the domain "unixsocket". See L14 where this is set.
         *
         *  @see Client.php:L14
         *  @see https://github.com/curl/curl/issues/1338
         */
        return sprintf("http://unixsocket%s", $requestPath);
    }


    /**
     * Dispatches a command - via Curl - to Commander's Unix Socket.
     *
     * @param  string Docker Engine endpoint to hit.
     * @param  array  Data to post to $endpoint.
     * @return array  JSON decoded response from Commander.
     */
    public function dispatchCommand(string $endpoint, array $parameters = null): array 
    {
        curl_setopt($this->curlClient, CURLOPT_URL, $this->generateRequestUri($endpoint));

        if (!is_null($parameters)) {
          $payload = json_encode($parameters);
          curl_setopt($this->curlClient, CURLOPT_POSTFIELDS, $payload);          
        }

        $result = curl_exec($this->curlClient);

        if ($result === FALSE) {
            $this->curlError = curl_error($this->curlClient);
            return array();
        }

        return json_decode($result, true);
    }


    /**
     * Returns a human readable string from Curl in the event of an error.
     *
     * @return bool|string 
     */
    public function getCurlError()
    {
        return is_null($this->curlError) ? false : $this->curlError;
    }
}

Das Resultat sieht ungefähr so aus

Man könnte das natürlich weiter spinnen und Container über IPS Starten, Beenden und Neu starten. Theoretisch könnte man so auch IPS Neu starten. Das wären die nächsten Schritte.

Gruß
Dennis

Hi,
ich benutze auf meiner Synology und QNap den jeweiligen Container-Manager. Leider wird bei den Volumes nur die Dateiauswahlbox benutzt und dort sich OS-Verzeichnisse/-Dateien wie /var/run/docker.sock ausgeklammert. Gibt es da eine Möglichkeit? Ich habe es bei Synology mit Export, Edit und Import versucht aber beim Import gab es immer einen Fehler. Kann man es auch mit einem Shell-Script nachträglich anlegen?

btw. für ein benutztes Symcon-Docker gibt es das Docker-Modul. Dort kommt man aber nicht an die anderen Docker ran.

Ralf

Moin Ralf,
seit dem Container Manager gibt es ja die Möglichkeit im Projekt Bereich das ganze mit Docker-Compose Dateien zu machen. Es kann aber durchaus sein das synology generell den Zugriff auf den Docker Socket unterbindet.
Vermutlich ginge dann nur über ssh und dort per docker compose oder docker run außerhalb. Ansonsten habe ich hier wohl ein workaround gefunden…

Gruß Dennis

Moin Dennis,
mit dem Manager arbeite ich seit 6-7 Jahren und hab mich dran gewöhnt. Das Synology da jetzt auch Projekte unterstützt habe ich gesehen. Bevor ich mit IPS da was umstelle spiele ich erstmal mit anderen Projekten rum.

Die selbe Idee hatte ich gestern auch nachdem ich meinen PC verlassen hatte. Am Wochenende bin ich normalerweise nur einmal Morgens am PC.

Synology hat da wohl die Sicherheit auch erhöht. Es sieht so aus als wenn sie die Links verfolgen und auf die Rechte achten. Weder in /volume2/docker (bei mir) noch in /volume1/public wird mir die Datei zur Auswahl angeboten.

Ich schau mal weiter.

Ralf

Moin,
ich habe bei mir dockerd.json jetzt um

, "hosts" : [ "tcp:192.168.178.2:2375", "unix:///var/run/docker.sock" ], "registry-mirrors" : []

erweitert und so vermutlich den Unix-Socket auf Port 2375 frei gegeben. Netstat meldet auch das dockerd auf 2375 lauscht. Ich habe schon einige Curl-Options wie z.B.

curl_setopt($this->curlClient, CURLOPT_URL, $socketPath);

ausprobiert und das liefert z.B. für var_dump($client)

object(DockerClient)#1 (3) {
  ["curlClient":"DockerClient":private]=>
  object(CurlHandle)#2 (0) {
  }
  ["socketPath":"DockerClient":private]=>
  string(25) "http://192.168.178.2:2375"
  ["curlError":"DockerClient":private]=>
  NULL
}

Für mich sieht es aus als wenn es keinen Curl-Fehler gab aber das array $dockerContainers ist leer.

Hast Du eine Idee wie man das über einen normalen Port machen kann?

Ralf

Moin Ralf,
wenn ich ein var_dump auf den $client mache bekomme ich

object(DockerClient)#1 (3) {
  ["curlClient":"DockerClient":private]=>
  resource(2) of type (curl)
  ["socketPath":"DockerClient":private]=>
  string(20) "/var/run/docker.sock"
  ["curlError":"DockerClient":private]=>
  NULL
}

Bin da sonst nicht so im Thema.

Kannst Du denn von einem PC über folgenden Aufruf die Laufenden Container abrufen?

  • docker -H tcp://192.168.178.2:2375 ps ?
  • eventuell schon den Docker Service auf der Synology neu gestartet?

Gruß
Dennis

Moin Dennis,

sieht nicht schlecht aus:

rb@RB-DS:~$ docker -H tcp://192.168.178.2:2375 ps
CONTAINER ID   IMAGE                                       COMMAND                  CREATED         STATUS                 PORTS                                                                                  NAMES
75159b7d778b   symcon/symcon:7.0-414-amd64                 "/usr/bin/symcon"        2 days ago      Up 3 hours                                                                                                    Symcon-7.0-414
38173930ed8f   koenkk/zigbee2mqtt:latest-dev               "docker-entrypoint.s…"   5 days ago      Up 3 hours                                                                                                    Zigbee2MQTT-231005
81af2ec33717   jonmaddox/harmony-api:testing               "npm start"              6 weeks ago     Up 3 hours                                                                                                    Harmony2MQTT
f3eb56c695b5   mukowman/roku_dummy:latest                  "python ./roku.py"       6 weeks ago     Up 3 hours                                                                                                    Roku
c8430d0706c0   pihole/pihole:2023.05.2                     "/s6-init"               8 weeks ago     Up 3 hours (healthy)                                                                                          PiHole-23-5-2
a019659f848f   typhonragewind/meshcentral:1.1.5            "docker-entrypoint.s…"   5 months ago    Up 3 hours             0.0.0.0:8087->80/tcp, :::8087->80/tcp, 0.0.0.0:8086->443/tcp, :::8086->443/tcp         MeshCentral-1.1.5
315c091fe056   linuxserver/nextcloud:php8-version-24.0.4   "/init"                  14 months ago   Up 3 hours             0.0.0.0:5088->80/tcp, :::5088->80/tcp, 0.0.0.0:5081->443/tcp, :::5081->443/tcp         NextCloud-24.0.4
81cbe8ccd189   zappiti/zappiti-server:latest               "sh start.sh"            14 months ago   Up 3 hours             0.0.0.0:8777->8777/tcp, 0.0.0.0:8777->8777/udp, :::8777->8777/tcp, :::8777->8777/udp   Zappiti

Damit die dockerd.json übernommen wird musst ich ja neu starten. Ich denke es liegt nur daran das es kein Unix-Port sondern ein normaler TCP-Port ist.

Ralf

Eventuell liegt hier der Hund vergraben…


Hier muss warscheinlich die IP und Port rein + requestPath…

Moin,
der Kandidat hat 100 Punkte :grinning:

So sieht es aus:

Das heißt mit ein paar kleinen Änderungen (Script und dockerd.json) geht es auch aus Docker heraus und es müsste auch auf Docker von anderen Rechnern zugreifen können.

Ralf

Ja sehr schön :slight_smile: das freut mich, das ich dazu auch was beitragen konnte.
Ich bin leider (noch) nicht im Stande ein Modul zu schreiben. Aber das Script ist ja schon mal ein Anfang.
Mal schauen ob ich dazu komme da noch eine bool vorzuschalten ob per unix socket oder per tcp zugegriffen werden soll um dann entweder den Pfad zu nutzen oder die URL.
Wie gesagt Ideen hätte ich da ja noch paar.
Neustart Variable in den jeweiligen Containern, bis hin zu einen neuen Container erstellen/starten und das am besten über Docker Compose.
Dann bräuchte man im Idealfall keine andere „Docker UI“ ala Portainer oder Synology Container Manager und wäre damit unabhängiger.

Gruß
Dennis

Moin Dennis,
ich bin recht fit was Scripte angeht aber an Modul habe ich mich auch noch nicht rangetraut und ich habe noch keine Anwendung gefunden die Sinn macht.

Ich habe mir auch schon Gedanken gemacht und werde wohl eine ~htmlBox aus den Infos erstellen um sie in der Visu abrufen zu können.

Ralf

1 „Gefällt mir“

Moin,
eins habe ich noch vergessen falls es ein Docker-Fan nachmachen möchte. Die Zeile:

habe ich durch:

        curl_setopt($this->curlClient, CURLOPT_PORT, 2375);

ersetzt.

Ralf

1 „Gefällt mir“

Hi BlackOrca,
ein Problemchen habe ich noch beim Script gefunden. Es werden nur laufende Docker bei mir aktualisiert, d.h. auch bei gestoppten Images steht immer noch „running“.

Edit das war einfach :grinning: statt /containers/json einfach /containers/json?all=1 benutzen.

btw. man könnte noch viel mehr Informationen extrahieren wie ich durch var_dump($container) gesehen habe. Mal schauen ob es noch andere interessante Werte gibt.

Ralf

Moin Ralf,
ja es gibt noch eine menge. Die Volumes und Networks etc. Da muss ich mich aber noch rantasten, auch weil PHP halt immer noch eine Fremdsprache für mich ist. Wie gesagt könnte ich mir auch Actions vorstellen das man Container neu starten könnte oder Stoppen wenn man wollte. Bin gerade dabei mich in die Modul Entwicklung ein zu arbeiten. Und dann würde ich als ersten ein Einbruchmelde Modul erstellen. Als Alarmtechniker wurmt es mich das es noch kein Komplette EMA Modul gibt mit alles faceten. :smiley:

Gruß
Dennis

Hi,
bei mir klappt auf einmal all=1 oder all=true nicht mehr :rage:

Ich habe mir die ID noch hinzugefügt weil man die braucht wenn man Image start/stoppen will.

Ralf

Huhu ihr zwei. Ich habe auch eine Syno und würde gerne ein paar Infos meiner Docker abfragen. Könnte ihr mal eine kleines „How2“ schreiben, Quasi Schritt und Schritt. Habe etwas Angst auf meiner Syno die falschen Dateien zu erwischen… :roll_eyes:

1 „Gefällt mir“

Das mit der EMA nervt mich auch, die habe ich noch immer in meiner SPS abgewickelt. Dort habe ich nämlich eine EMA nachgestellt. Ich leite quasi meine HomeMatic Sensoren über IPS via ModBusIP an die SPS und gibt mir via ModBusIP die Funktionen zurück… :rofl: :stuck_out_tongue_winking_eye:

Hmmm,
lt. Docker Engine API v1.24 | Docker Docs ist der Query String all=1 immer noch gültig… Bekommst Du denn einen Error zurück oder bekommst Du nur noch die Aktive Container?

Ja und da PHP nicht meine 2. Muttersprache ist und einige dinge als (Hobby) C# Entwickler nicht ganz auf der Hand liegt in der Modul Entwicklung brauche ich da auch noch etwas ruhe für. Zumal meine Gedanke auch noch Richtung eines „Raum Controller“ geht in dem man nur noch Lampen, Schalter, Fenster, Türen, etc. angeben muss und die ganzen Regelungen (Schimmelerkennung, Lüfungs Empfehlung, Heizungsregelung, etc) in einem Modul gesteuert wird und nicht in zich Einzelscripten/Einzel Modulen pro Raum. Es würde auch ein Austausch von z.B. Fensterkontakten oder Temperatur Sensoren vereinfachen da nicht mehr jedes Element Archve führt sondern das Raum Modul für jede Informationsart eine Sammelvariable anlegt und diese Archiviert wird.
Ach ich schweife ab :smiley:

Gruß
Dennis

Ich kann im fall der Syno gar nicht groß helfen außer auf die Infos von Ralf zu verweisen. Er hat das ja auf seiner Syno verwirklicht. Da Syno ja paar Barrieren aufgebaut hat um sowas nicht einfach zuzulassen.

Gruß
Dennis