Docker Infos abrufen und als Variablen speichern

Hi,
um das Script auch im ContainerManager/Docker auf einer Synology zum laufen zu bringen benötigt man Zugriff auf

'/var/run/docker.sock'

Vielleicht geht es wenn man Symcon privilegiert startet oder die erweiterten Möglichkeiten des ContainerManagers benutzt. Ich habe einen anderen Weg gewählt. Ich biege docker.sock auf einen normalen TCP-Port um. Vorteil 1 es arbeitet auch in nichtprivilegierten Images und Vorteil 2 man kann Docker auch von anderen Rechnern steuern. Wenn man mutig ist auch per Freigabe aus dem Internet.

Um den docker.sock umzubiegen benötigt man zuerst einen SSH Zugang zur Synology. Man logged sich ein und dann

cd /
sudo find -name dockerd.json

Bei mir befindet sich die Datei in /volume1/@appconf/ContainerManager/dockerd.json. Diese Datei kopiert man in eine Freigabe auf der Synology mit

sudo cp /volume1/@appconf/ContainerManager/dockerd.json /volume1/public/

Diese Datei sieht bei mir so aus

{"data-root":"/var/packages/ContainerManager/var/docker","log-driver":"db","registry-mirrors":[],"storage-driver":"btrfs"}

und ist ein json String.

In diesen String muss

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

hinzugefügt werden damit docker.sock uf einen TCP Port umgeleitet wird. Der String muss dann in etwa so aussehen

{"data-root":"/var/packages/ContainerManager/var/docker","hosts":["tcp://192.168.178.2:2375","unix:///var/run/docker.sock"],"log-driver":"db","registry-mirrors":[],"storage-driver":"btrfs"}

192.168.178.2 ist meine Synology und 2375 ist der Port den ich gewählt habe. Diese Datei muss man dann wieder zurück kopieren mit

sudo cp /volume1/public/dockerd.json /volume1/@appconf/ContainerManager/

Nachdem man den Containermanager neu gestartet hat sollte die Steuerung von docker über 192.168.178.2:2375 gehen und kann im eigenen Netz von jedem Rechner gemacht werden. Das Script von BlackOrca muss an 2 Stellen angepasst werden damit es funktioniert. Hier mein geändertes Script das zusätzlich auch noch den Speicher und die IDs der Container ausliest. Mit der ID lassen sich so schöne Sachen wie Start/Stop machen. Dank des sehr übersichtlich geschriebenen Original Scripts ist eine Erweiterung ein Klacks. Danke dafür Dennis.

<?php
$DummyInstanceId = "{485D0419-BE97-4548-AA9C-C083EB82E61E}";
$ParentFolder = 35944;
$client = new DockerClient('http://192.168.178.2:2375');

//$dockerContainers  = $client->dispatchCommand('/containers/json?all=1&size=1');
$dockerContainers  = $client->dispatchCommand('/containers/json?size=1');
var_dump($dockerContainers);

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']);

$Children = IPS_GetChildrenIDs($ParentFolder);
foreach ($Children as $Child) {
    DockerCheck($Child);
}

/* Functions */

function DockerCheck($ParentID){
    
    $Object = IPS_GetObject($ParentID);
    if ($Object['ObjectType'] != 1) return;
    $StatusID = IPS_GetObjectIDByName('Status', $ParentID);
    $Variable = IPS_GetVariable($StatusID);
    if ((time() - $Variable['VariableUpdated']) > 86400){
        $Children = IPS_GetChildrenIDs($ParentID);
        foreach ($Children as $Child) {
            IPS_DeleteVariable($Child);
        }
        IPS_DeleteInstance($ParentID);
    }
}

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

function AddOrUpdateContainerInfos($container)
{ 
    $instanceId = AddOrUpdateInstance($container);
    //var_dump($container);
    AddOrUpdateCreated($instanceId, $container);
    AddOrUpdateState($instanceId, $container);
    AddOrUpdateStatus($instanceId, $container);
    AddOrUpdateImage($instanceId, $container); 
    AddOrUpdateSizeRootFs ($instanceId, $container);
    AddOrUpdateSizeRw($instanceId, $container);
    AddOrUpdateSizeId($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 AddOrUpdateSizeRw($instanceId, $container)
{
    if (isset($container['SizeRw']))AddOrUpdate($instanceId, $container, "verändert", VARIABLETYPE_FLOAT, "MByte", floatval($container['SizeRw']) / 1048576);
}

function AddOrUpdateSizeRootFs ($instanceId, $container)
{
    AddOrUpdate($instanceId, $container, "Größe", VARIABLETYPE_FLOAT, "MByte", floatval($container['SizeRootFs']) / 1048576);
}

function AddOrUpdateSizeId ($instanceId, $container)
{
    AddOrUpdate($instanceId, $container, "ID", VARIABLETYPE_STRING, "", $container['Id']);
}

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_PORT, 2375);
        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://192.168.178.2:2375%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;
    }
}

Ralf

So ich kann jetzt auch Container manipulieren :grin:

@Dennis ich habe dein Script mal als Vorlage genommen und um CURLOPT_POST erweitert damit ich restart benutzen kann. Mit folgendem Script kann man einen Container mit dem Namen „Zigbee2MQTT-240205“ auf dem Rechner mit der IP 192.168.178.2 und dem Port 2375 neu starten.

<?php
$DummyInstanceId = "{485D0419-BE97-4548-AA9C-C083EB82E61E}";
$ParentFolder = 35944;
$client = new DockerClient('http://192.168.178.2:2375');
var_dump($client);

$result = $client->dispatchCommand('/containers/Zigbee2MQTT-240205/restart');
var_dump($result);

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_PORT, 2375);
        curl_setopt($this->curlClient, CURLOPT_POST, true);
        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://192.168.178.2:2375%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;
    }
}

starten, stoppen usw. sollte damit auch gehen.

Ralf

Sehr sehr cool :hugs:
Gruß Dennis

Moin,
ich benutze den Neustart durch IPS jetzt sogar schon „produktiv“. Meine Docker Zigbe2MQTT und Harmony2MQTT haben z.Z. die Unart sich eine Pause zu gönnen und das gewöhne ich denen durch einen Neustart ab :rofl:

Ralf

Ja das kann man dann wunderbar machen :+1: :+1: :+1: