Docker : can’t create socket (must run as root?) : Permission denied

Dans le cadre de la dockerisation de proxy Zabbix, je suis tombé sur un problème étrange.

Je fais les commandes suivantes :

docker run -it zabbix/zabbix-proxy-sqlite3:ubuntu-3.4-latest /bin/bash
[...]
root@220fab01aae4:/var/lib/zabbix# fping palc.fr
palc.fr is alive
root@220fab01aae4:/var/lib/zabbix# su - zabbix -s /bin/bash
zabbix@220fab01aae4:~$ fping palc.fr
(null): can't create socket (must run as root?) : Permission denied

Note : l’option -it /bin/bash me permets d’avoir directement un bash pour faire mes tests. Lors d’un lancement de Docker en prod, il faudrait utiliser les options -d -t à la place.

Note 2 : vous pouvez également avoir une erreur du type socket: Operation not permitted.

Dans le container Docker, la commande fping fonctionne en root mais pas avec l’utilisateur Zabbix. Pourtant sur le système host, qui utilise le même OS, ça fonctionne bien.

Pour corriger le problème il faut simplement ajouter l’option --sysctl net.ipv4.ping_group_range="0 65535" à la commande lançant le container docker. Ça permets à n’importe quel utilisateur d’accéder directement au réseau de l’hôte, ce qui est indispensable pour effectuer des requêtes ICMP, comme le ping. Dans le cas contraire, seul l’utilisateur root serait autorisé à le faire.

TeamViewer : Pas prêt. Veuillez vérifier votre connexion.

J’’ai récemment dû utiliser TeamViewer dans le cadre de mon travail. Mais il restait bloqué indéfiniment sur l’erreur « Pas prêt. Veuillez vérifier votre connexion. » :

Impossible d’entrer un ID de connexion, ou quoi que ce soit.

La cause de ce problème est simple. Au démarrage TeamViewer fait un test de connexion sur un DNS du type masterX.teamviewer.com (X étant un nombre). Mais comme les DNS sont configurés avec les pieds, si vous êtres en IPv6 il se peut que la résolution se fasse n’importe comment et échoue. La seule solution est de désactiver IPv6 sur votre ordinateur.

Sur un Ubuntu ça se fait de la manière suivante :

  • Éditez le fichier /etc/sysctl.d/99-sysctl.conf
  • Rajoutez les trois lignes suivantes à la fin :
    net.ipv6.conf.all.disable_ipv6 = 1
    net.ipv6.conf.default.disable_ipv6 = 1
    net.ipv6.conf.lo.disable_ipv6 = 1
  • Rechargez la configuration avec sudo sysctl -p
  • Relancez TeamViewer

Notez que ça désactivera complètement IPv6 pour le système d’exploitation entier.

Zabbix : invalid field name « items.jmx_endpoint »

Si vous rencontrez l’erreur suivante dans les logs d’un proxy Zabbix :

failed to update local proxy configuration copy: invalid field name "items.jmx_endpoint"

Ca veut dire que le master est en version 3.4 et que le proxy est en version 3.0. Ces deux versions ne peuvent pas travailler ensemble. Il faut donc mettre à jour le proxy en version 3.4.

Mais cette erreur à une conséquence inattendue. En apparence le proxy fonctionne normalement. Il continue à collecter des données et à les envoyer au master. Par contre il ne peut plus mettre à jour sa configuration (ajout ou modification de métriques). Celle-ci reste figée. Mais si vous relancez le service zabbix_proxy, c’est la cata et même la collecte ne fonctionne plus.

SELinux : détecter et corriger les problèmes de droits

Le problème

Dans le cadre de la supervision MySQL, l’agent Zabbix est censé exécuter cette commande en local pour s’assurer que le serveur accepte bien les connexion :

mysqladmin -h 127.0.0.1 ping

Si je lance la commande en local :

zabbix@1.2.3.4:~$ mysqladmin -h 127.0.0.1 ping
mysqld is alive

Mais quand c’est l’agent Zabbix lui-même qui lance la commande :

mysqladmin: connect to server at '127.0.0.1' failed
error: 'Can't connect to MySQL server on '127.0.0.1' (110)'
Check that mysqld is running on 127.0.0.1 and that the port is 3306.
You can check this by doing 'telnet 127.0.0.1 3306'

Pourtant les deux tests-ci-dessus ont un fonctionnement identique. C’est la même commande qui est utilisée dans les deux cas, avec les même droits. Après une heure à faire des tests et à s’arracher les cheveux pour comprendre d’où peut venir le problème, j’ai pensé à SELinux. Pour voir si SELinux est activé :

root@1.2.3.4:~# getenforce
Enforcing

Là c’est activé, sinon ça m’afficherait Permissive ou Disabled.

Je désactive temporairement SELinux pour faire des tests avec setenforce 0. Comme la supervision Zabbix se mets à fonctionner, je sais que le problème vient de là. Je réactive SELinux avec setenforce 1.

La solution

Pour désactiver définitivement SELinux il faut faire echo "0" > /selinux/enforce. Mais comme je suis sur une plateforme demandant un niveau de sécurité élevé, je ne peux pas le désactive. Il va donc falloir autoriser l’agent Zabbix à utiliser MySQL.

On installe d’abord un utilitaire qui va bien nous aider :

apt-get install policycoreutils

Ensuite on surveille le fichier audit.log :

tail -f -n 0 /var/log/audit/audit.log | audit2allow

À chaque fois que l’agent Zabbix tente de se connecter au MySQL, ca affiche quelque chose qui ressemble à ça :

#============= zabbix_agent_t ==============
allow zabbix_agent_t mysqld_etc_t:file { open read };
#!!!! The file '/var/lib/mysql/mysql.sock' is mislabeled on your system.
#!!!! Fix with $ restorecon -R -v /var/lib/mysql/mysql.sock
#!!!! This avc can be allowed using the boolean 'daemons_enable_cluster_mode'
allow zabbix_agent_t mysqld_t:unix_stream_socket connectto;

On peut voir la cause du problème. L’agent Zabbix ne peut pas accéder à /var/lib/mysql/mysql.sock. Ça nous donne même l’option à activer : daemons_enable_cluster_mode.

Je lance donc les commandes suivantes :

setsebool -P daemons_enable_cluster_mode 1setsebool daemons_enable_cluster_mode 1

Il ne reste plus qu’à redémarrer l’agent Zabbix (systemctl restart zabbix-agent.service), et le problème est résolu.

Linux : créer une règle firewalld

Récemment j’ai dû créer une règle sur firewalld pour autoriser un agent Zabbix à communiquer. Comme je n’avais jamais utilisé cette solution jusque là j’ai un peu galéré. Voici ma procédure.

Faire un firewall-cmd --get-services. Ça va afficher la liste de tous les services gérés par firewalld. Normalement zabbix-agent n’est pas dedans. On va l’ajouter.

Créez le fichier /etc/firewalld/services/zabbix-agent.xml avec pour contenu :

<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>ZABBIX-AGENT</short>
  <description>Flux for zabbix-agent service.</description>
  <port protocol="tcp" port="10050"/>
</service>

Ensuite il faut recharger la configuration en lançant ces quatres commandes :

firewall-cmd --reload
firewall-cmd --add-service=zabbix-agent
firewall-cmd --permanent --add-service=zabbix-agent
firewall-cmd --reload

Si l’option --permanent n’est pas présente, cette configuration ne sera pas conservée après un redémarrage. Si l’option est présente, la configuration ne sera appliquée qu’à partir du redémarrage.

Je ne sais pas pourquoi il faut faire deux fois le --reload. Mais j’ai constaté que s’il n’est fait qu’une seule fois (quelque soit son emplacement), ça ne marche pas.

Zabbix : superviser les tablespaces Oracle

Dans l’article précédent, nous avons vu comment exécuter des requêtes Oracle depuis Linux. Maintenant nous allons nous en servir pour superviser des tablespaces avec Zabbix.

La supervision va se faire en deux parties : une découverte automatique des tablespaces, et la supervision de l’espace libre.

Un export de mon template et des fichiers nécessaires peut être téléchargé à cette adresse : https://palc.fr/wp-content/uploads/template_oracle_tablespace.zip

La découverte automatique

La découverte des tablespace a été abordée dans l’article précédent, mais je vais revenir dessus.

Je vais utiliser un externalscript. Chez moi ils se trouvent dans le dossier /usr/lib/zabbix/externalscripts. Je vais donc dedans et je créé le fichier oracle_tablespace_discovery.ext :

SET heading OFF;
SET feedback OFF;

SELECT
a.tablespace_name
FROM
dba_data_files a
GROUP BY
a.tablespace_name;

EXIT;

On notera la désactivation des en-tête et autres infos inutiles. Cette commande retourne uniquement les tablespaces, et rien d’autre.

Ensuite je créé un script oracle_tablespace_discovery.sh dans le même répertoire :

#!/bin/bash

LD_LIBRARY_PATH=/usr/lib/oracle/12.1/client64/lib

echo '{
  "data":['

for tablespace in `/usr/lib/oracle/12.1/client64/bin/sqlplus -s "$3/$4@ (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = $1)(PORT = $2))) (CONNECT_DATA = (SERVICE_NAME = $5)))" @/usr/lib/zabbix/externalscripts/oracle_tablespace_discovery.ext`
do
  echo -n '    {"{#ORACLE_TABLESPACE_NAME}":"'
  echo -n $tablespace
  echo '"},'
done

echo '    {}'
echo '  ]
}'

Ce script lance la requête Oracle vue dans l’article précédent, et mets le résultat sous un format JSON pour que Zabbix puisse l’interpréter.

À noter le echo ' {}' à la fin. C’est pas très propre, mais c’est une méthode efficace et rapide pour éviter les erreurs JSON sur le dernier résultat retourné.

Maintenant on lance la commande à la main, pour tester :

root@SV-TCA-ZPXSCP01+~# /usr/lib/zabbix/externalscripts/oracle_tablespace_discovery.sh IP 1521 user password NOM_DU_SERVICE
{
  "data":[
    {"{#ORACLE_TABLESPACE_NAME}":"ZABBIX_TS"},
    {"{#ORACLE_TABLESPACE_NAME}":"UNDOTBS1"},
    {"{#ORACLE_TABLESPACE_NAME}":"SYSAUX"},
    {"{#ORACLE_TABLESPACE_NAME}":"USERS"},
    {"{#ORACLE_TABLESPACE_NAME}":"SYSTEM"},
    {"{#ORACLE_TABLESPACE_NAME}":"WF_TABLE"},
    {}
  ]
}

C’est exactement ce que l’ont veut. Il ne reste plus qu’à créer la règle de découverte correspondante. Elle doit juste avoir pour clé oracle_tablespace_discovery.sh[{HOST.IP},{$ORACLE_PORT},{$ORACLE_USER},{$ORACLE_PASSWORD},{$ORACLE_SERVICENAME}] :

On notera l’utilisation des macros, cette règle de découverte étant destinée à finir dans un template.

La supervision de l’espace libre

Là encore je vais utiliser un externalscript. D’abord la requête SQL, dans le fichier oracle_tablespace_stat.ext :

SET heading OFF;
SET feedback OFF;
SET LINE 250;

SELECT
    a.tablespace_name,
    TO_CHAR(SUM(a.bytes)) "Curb",
    TO_CHAR(SUM(decode(b.maxextend, NULL, A.BYTES, b.maxextend*8192))) "Maxb",
    TO_CHAR((SUM(a.bytes) - ROUND(c."Free"))) "TotalUsed",
    TO_CHAR(SUM(decode(b.maxextend, NULL, A.BYTES, b.maxextend*8192)) - (SUM(a.bytes) - ROUND(c."Free"))) "TotalFree",
    100*(SUM(a.bytes) - ROUND(c."Free"))/(SUM(DECODE(b.maxextend, NULL, A.BYTES, b.maxextend*8192))) "UPercent"
FROM
    dba_data_files a,
    sys.filext$ b,
    (SELECT d.tablespace_name , SUM(nvl(c.bytes,0)) "Free"
    FROM dba_tablespaces d, DBA_FREE_SPACE c
    WHERE d.tablespace_name = c.tablespace_name(+) GROUP BY d.tablespace_name
    ) c
WHERE a.file_id = b.file#(+)
    AND a.tablespace_name = c.tablespace_name
GROUP BY
    a.tablespace_name, c."Free"
ORDER BY
    ROUND(100*(SUM(a.bytes) - ROUND(c."Free"))/(SUM(decode(b.maxextend, NULL, A.BYTES, b.maxextend*8192)))) DESC;

EXIT;

Cette requête retourne le nom du tablespace, deux colonnes dont je ne comprends pas trop l’utilité, puis la taille totale et l’espace libre en octets, et enfin le pourcentage d’utilisation. Comme la requête doit obligatoirement être stockée dans un fichier, je suis obligé de tout retourner à chaque fois, sinon ça impliquerait de créer un fichier à la volée et ça créerait de la complexité.

LD_LIBRARY_PATH=/opt/oracle/instantclient_12_2 /opt/oracle/instantclient_12_2/sqlplus -s "user/password@ (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = 1.2.3.4)(PORT = 1521))) (CONNECT_DATA = (SERVICE_NAME = NOM_DU_SERVICE)))" @/usr/lib/zabbix/externalscripts/oracle_tablespace_stat.ext

TABLESPACE_NAME Curb Maxb TotalUsed TotalFree UPercent
------------------------------ ---------------------------------------- ---------------------------------------- ---------------------------------------- ---------------------------------------- ----------
WF_TABLE 89120571392 93415538688 83884572672 9530966016 89.797237
SYSAUX 933232640 34359721984 879296512 33480425472 2.55909088
SYSTEM 807403520 34359721984 801243136 33558478848 2.33192555
ZABBIX_TS 104857600 104857600 1048576 103809024 1
USERS 5242880 34359721984 1376256 34358345728 .004005434
UNDOTBS1 728760320 34359721984 14548992 34345172992 .04234316

(ici j’ai réactivé l’affichage de l’en-tête des colonnes, pour que le résultat soit plus facile à comprendre)

Maintenant il reste à exploiter le résultat, via le script oracle_tablespace_stat.sh :

#!/bin/bash

LD_LIBRARY_PATH=/usr/lib/oracle/12.1/client64/lib

/usr/lib/oracle/12.1/client64/bin/sqlplus -s "$3/$4@ (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = $1)(PORT = $2))) (CONNECT_DATA = (SERVICE_NAME = $5)))" @/usr/lib/zabbix/externalscripts/oracle_tablespace_stat.ext | grep -E "^$6\W" | awk "{print \$$7}"

Ce script est assez simple. Il prends les mêmes paramètres que le script de découverte mais en rajouter deux autres : le nom du tablespace, et le numéro de la colonne dont on veut retrouver la donnée. J’ai choisis de faire le filtrage sur le numéro de colonne plutôt que sur son nom pour simplifier la commande. La clé Zabbix est moins clair, mais le script est beaucoup plus simple. C’est un compromis.

Testons le script à la main. Je vais demander le pourcentage d’espace utilisé sur le tablespace WF_TABLE :

root@SV-TCA-ZPXSCP01+~# /usr/lib/zabbix/externalscripts/stat.sh IP 1521 user password NOM_DU_SERVICE WF_TABLE 6
89.797237

Ce tablespace est bien utilisé à 89%. Le script est donc fonctionnel. Il ne reste plus qu’à créer les items.

Un exemple de clé : oracle_tablespace_stat.sh[{HOST.IP},{$ORACLE_PORT},{$ORACLE_USER},{$ORACLE_PASSWORD},{$ORACLE_SERVICENAME},{#ORACLE_TABLESPACE_NAME},2]

La configuration d’un des items :

Tous les items :

Il ne reste plus qu’à créer le trigger, avec l’expression : {Template Oracle:oracle_tablespace_stat.sh[{HOST.IP},{$ORACLE_PORT},{$ORACLE_USER},{$ORACLE_PASSWORD},{$ORACLE_SERVICENAME},{#ORACLE_TABLESPACE_NAME},6].min(#3)}>90

Linux : se connecter à un serveur Oracle

L’installation

Dans le cadre de mon travail, j’ai dû lancer des requêtes Oracles depuis un serveur sous Linux. Et quand on n’a jamais travaillé avec Oracle c’est une grosse galère. Voici ma procédure, testée sur Ubuntu 18.04. Elle est très largement inspirée de https://manjaro.site/how-to-install-sqlplus-utility-on-ubuntu-18-04-and-ubuntu-18-10/.

D’abord il faut aller sur le site officiel d’Oracle pour télécharger le client SQL*Plus. Les deux fichiers à télécharger sont instantclient-basic-linux.x64-12.2.0.1.0.zip et instantclient-sqlplus-linux.x64-12.2.0.1.0.zip. Le téléchargement nécessite un compte Oracle. J’en ai créé un pour l’occasion.

Ensuite on mets les deux fichiers dans le dossier /opt/oracle, et on les extrait :

unzip instantclient-basic-linux.x64-12.2.0.1.0.zip
unzip instantclient-sqlplus-linux.x64-12.2.0.1.0.zip

Puis on corrige quelques liens :

cd /opt/oracle/instantclient_12_2
ln -s libclntsh.so.12.1 libclntsh.so
ln -s libocci.so.12.1 libocci.so

Et pour terminer on installe libaio :

apt-get install libaio1

Il ne reste plus qu’à lancer la commande sqlplus pour tester :

LD_LIBRARY_PATH=/opt/oracle/instantclient_12_2 /opt/oracle/instantclient_12_2/sqlplus

Il est obligatoire de renseigner la variable d’environnement LD_LIBRARY_PATH, sinon SQL*Plus ne retrouve pas ses bibliothèques.

L’utilisation

L’utilisation de SQL*Plus est particulière. Je pense que c’est dû au fonctionnement d’Oracle. Il n’est pas possible de passer la requête en paramètre, comme avec MySQL ou PostgreSQL. J’ai d’ailleurs bien galéré à trouver comment spécifier les paramètres de connexion en ligne de commande.

Donc, il faut créer un fichier /usr/lib/oracle/tablespace.ext (par exemple), contenant :

SELECT
    tablespace_name
FROM
    dba_data_files
GROUP BY
    tablespace_name;

EXIT;

Notez le EXIT; à la fin. C’est indispensable sinon SQL*Plus ne rends pas la main.

Cette commande doit afficher la liste des tablespaces sur la base Oracle. Ça me servira plus tard pour mettre en place la supervision.

Pour lancer la requête, ça se passe avec la commande suivante :

LD_LIBRARY_PATH=/opt/oracle/instantclient_12_2 /opt/oracle/instantclient_12_2/sqlplus -s "user/password@ (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = 1.2.3.4)(PORT = 1521))) (CONNECT_DATA = (SERVICE_NAME = NOM_DU_SERVICE)))" @/usr/lib/oracle/tablespace.ext

Je ne pourrais pas détailler les paramètres de cette commande, c’est un gros sac de nœuds. C’est un admin Oracle qui m’a fourni le tout, je me contente d’appliquer. Pensez juste à remplacer les bons paramètres (nom d’utilisateur, mot de passe, adresse IP, nom du service et éventuellement port).

Chez moi ça donne :

root@SV-TCA-ZPXSCP01+~# LD_LIBRARY_PATH=/opt/oracle/instantclient_12_2 /opt/oracle/instantclient_12_2/sqlplus -s "user/password@ (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = 1.2.3.4)(PORT = 1521))) (CONNECT_DATA = (SERVICE_NAME = NOM_DU_SERVICE)))" @/usr/lib/oracle/tablespace.ext

TABLESPACE_NAME
------------------------------
ZABBIX_TS
UNDOTBS1
SYSAUX
USERS
SYSTEM
WF_TABLE

6 rows selected

C’est gagné, j’ai bien récupéré la liste des tablespaces. Ma requête a fonctionné.

Dans le prochain article je vais détailler comment mettre en place une supervision des tablespaces Oracle avec Zabbix, en utilisant SQL*Plus.