Résumé : si, au boot d'une machine dont le support de stockage est totalement chiffré, cryptsetup affiche l'erreur « error while loading shared libraries: libcryptsetup.so.4 » (variantes avec libgcrypt.so.20 et libgpg-error.so.0), vérifie que le paquet logiciel cryptsetup-initramfs est bien installé et réinstalle-le le cas échéant.
Mes serveurs perso ont 315 jours d'uptime. Je décide de les redémarrer. Ça permet d'appliquer entièrement les mises à jour de sécurité et de se prémunir contre un redémarrage inopportun / forcé à un moment où on n'aura pas le temps de corriger un éventuel problème.
Sur celui dont tout le disque dur est chiffré (sauf /boot), je teste la passphrase, je vérifie que l'accès VNC distant est fonctionnel, je reboot, et là, c'est le drame : /sbin/cryptsetup: error while loading shared libraries: libcryptsetup.so.4: cannot open shared object file : No such file or directory. Une saisie correcte de la passphrase se termine en « cryptsetup failed, bad password or options? ».
Une recherche sur le web donne rien de satisfaisant.
Il s'agit d'un VPS OVH, donc je tente le mode rescue. Le mot de passe est envoyé par email. L'email de contact de mon compte OVH est géré par le serveur qui ne démarre plus… Néanmoins, le mot de passe du mode rescue est affiché sur la console VNC (KVM). \o/ Le clavier est en qwerty, donc je mets trop de temps à saisir le mot de passe, donc l'écran se rafraîchit et le mot de passe disparaît. Note pour la prochaine fois : prendre une capture d'écran du mot de passe. Je redémarre la machine avec ctrl+alt+suppr. Je me retrouve à nouveau sur le cryptsetup foireux. Je demande à nouveau de démarrer en mode rescue. L'interface web crache « Une erreur est survenue lors de la demande de réinitialisation du mot de passe. ». Plusieurs fois.
Je joue un peu avec l'accès VNC distant. J'atterris dans un initramfs. WTF ?! Je croyais que ce comportement avait pris fin en 2016, quand on avait estimé qu'il s'agit d'une faille de sécurité. Bon ben… tant mieux.
cryptsetup luksOpen /dev/sda2 sda2_crypt crache directement l'erreur sus-mentionnée sans même demander la phrase de passe.
Sur une autre machine Debian 10 avec disque dur chiffré, apt-file search ne trouve pas libcryptsetup.so.4 dans un quelconque paquet logiciel. Un sudo find / -iname 'libcryptsetup.so.4' la trouve dans /usr/src/initramfs/lib/x86_64-linux-gnu/libcryptsetup.so.4. Si tu ne l'as pas, il faudra la récupérer dans le paquet logiciel distribué dans la version Stretch de Debian.
Je configure la pile IP. Le manager d'OVH ne semble pas exposer les paramètres (masque, routeur, etc.) donc je sors /etc/network/interfaces de mes sauvegardes. Je sors sur le net. \o/ Toutefois : pas de /etc/resolv.conf donc pas de résolution DNS.
Je récupère la bibliothèque de fonctions manquante avec wget depuis l'un de mes serveurs web pour la mettre dans /lib/x86_64-linux-gnu. Pas de DNS dans l'initramfs, donc, ça nécessite un serveur web avec un hôte virtuel qui écoute pour tous les noms possibles.
cryptsetup se plaint désormais de l'absence de libgcrypt.so.20. Je la wget. Désormais, l'objet de la plainte est libgpg-error.so.0. Je la récupère.
cryptsetup luksOpen /dev/sda2 sda2_crypt fonctionnee. \o/
Je quitte initramfs (exit), le serveur démarre. \o/
IPv6 ne fonctionne pas. C'est normal : j'ai monté l'interface dans initramfs eet je lui ai affecté une adresse IPv4, donc ifup échoue et ne traite donc pas le bloc « inet6 » du fichier interfaces. Pour m'en assurer, je reboot… tout en comprenant immédiatement ma connerie : je n'ai pas réparé l'initramfs, celui stocké sur le disque dur, donc ça ne va pas démarrer… Je recommence la configuration IP et le téléchargement des bibliothèques manquantes…
Les paquets logiciels libgpg-error0, libgcrypt20 et libcryptsetup12 (nouvelle version de libcryptsetup.so.4 arrivée avec la version Buster de Debian) sont installés (sinon je n'aurais pas pu tester la passphrase avant de redémarrer). Pour rappel : apt-file search permet d'identifier le paquet logiciel qui contient un fichier précis.
Reconstruisons l'initramfs avec update-initramfs -uk 4.19.0-13-amd64. Vérifions avec lsinitramfs /boot/initrd.img-4.19.0-13-amd64 | grep libcryptsetup : aucun résultat, la bibliothèque n'a pas été incorporée à l'initramfs.
Je décide de réinstaller les paquets logiciels de la forme « cryptsetup » : apt-get install --reinstall cryptsetup cryptsetup-bin cryptsetup-run. apt-get m'informe que le paquet cryptsetup-initramfs (dont dépend le paquet cryptsetup) n'est pas installé et qu'il va l'installer. Ooook, tout s'explique !
Après l'installation de cryptsetup-initramfs, lsinitramfs nous confirme que libcryptsetup.so.20 est bien présente dans l'initramfs. \o/
Un reboot fonctionne parfaitement. \o/
Au final, à quelle ocassion le paquet cryptsetup-initramfs a-t-il été désinstallé ? Aucune idée, /var/log/dpkg.log n'en dit pas un mot. libgcrypt20 et libcryptsetup12 ont été installées le 14/02/2020, date à laquelle j'ai mis à jour de Stretch à Buster. libcryptsetup4 a été supprimée ce jour-là. Le système a correctement redémarré le 16/02/2020, c'est /var/log/kern.log qui l'affirme, mais un calcul "28/12/2020 - 315 jours d'uptime" permettait d'avoir une estimation.
Résumé : si t'as un site web de diffusion de vidéos qui utilise Django et videojs, que les vidéos sont longues à charger voire que les longues vidéos sont remplacées par une erreur « The quota has been exceeded », vérifie que ton frontal Apache httpd ne proxifie pas les requêtes HTTP portant sur les vidéos à l'application Django car ce dernier ne prend pas en charge le contenu partiel (entête HTTP « Range »). Profite-en pour surcharger la variable « DEBUG » dans settings_local.py, car elle veut True par défaut…
Nous avons un site web qui diffuse des vidéos. Il s'agit d'un logiciel Python Django derrière un serveur d'applications uwsgi derrière un frontal Apache httpd.
Quand une vidéo dépasse 20 mo (environ), elle n'est pas jouée et l'erreur « The quota has been exceeded » s'affiche à la place. Avec Firefox, car Chromium affiche rien. La console des outils de développement affiche « VIDEOJS: ERROR: (CODE:-3 undefined) The quota has been exceeded. Object { code: -3, type: "APPEND_BUFFER_ERR", message: "The quota has been exceeded.", originalError: DOMException } video.js:128:5 ».
Sur une vidéo plus petite, l'onglet réseau des outils de développement montre que le chargement de la page s'est effectué en 52 secondes, et qu'un fichier vidéo de 16 Mo a été récupéré. La lenteur est le deuxième problème signalé par nos utilisateurs. Forcément, si le navigateur web charge automatiquement l'intégralité de la vidéo en arrière-plan…
Sur le web, on trouve ces erreurs dans des tickets sans solution ici ou là.
La collègue développeuse change le code du logiciel pour que ffmpeg découpe chaque vidéo en fragments (dit autrement : virer l'option « -hls_flags single_file »). Hop, plus d'erreur et la page web se charge en 600 ms. En parallèle, elle interroge les devs de l'application et la communauté : comme d'hab, personne a ce problème.
Mais ce découpage des vidéos n'améliore pas les choses avec Safari qui continue de lire en boucle les deux premières secondes de la vidéo, comme quand les vidéos n'étaient pas découpées.
C'est ici que j'arrive. Je trouve bizarre que les vidéos longues ne fonctionnent pas chez nous alors que ça marche chez les autres utilisateurs du logiciel.
À ce stage on a éliminé l'applicatif des causes potentielles (sa configuration reste une cause potentielle).
Un collègue nous donne une piste : le serveur ne gère pas l'entête HTTP « Range » dans les requêtes, alors que les vidéos sont distribuées sous forme de listes de lecture (playlists) indiquant une série d'intervalles de deux secondes au sein d'un même fichier.
En effet, nos listes de lecture (générées par ffmpeg) ressemblent à ça :
#EXTM3U
#EXT-X-VERSION:4
#EXT-X-TARGETDURATION:2
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:2.000000,
#EXT-X-BYTERANGE:178412@0
360p.ts
#EXTINF:2.000000,
#EXT-X-BYTERANGE:211312@178412
360p.ts
[…]
Fragments de deux secondes (EXTINF). Le premier fragment commence à l'octet 0 du fichier 360p.ts et occupe 178412 octets. Le deuxième commence à l'octet 178412 du fichier 360p.ts et occupe 211312 octets.
Un test avec curl confirme cette analyse :
$ curl -s -o /dev/null -D - -r 1315624-1562279 https://monorganisation.example/media/videos/ef2218bee799524931d2af11aafc3933e5d7413da2989b70fc94a9941fcd50e1/666/720p.ts
HTTP/1.1 200 OK
Date: Wed, 30 Sep 2020 17:10:42 GMT
Server: Apache/2.4.25 (Debian)
Content-Language: fr
Last-Modified: Wed, 16 Sep 2020 14:19:04 GMT
X-Frame-Options: EXEMPT
Vary: Accept-Language,Cookie
Content-Type: video/MP2T
Content-Length: 16929400
Cache-Control: max-age=0, no-store
Accept-Ranges: bytes
Le serveur nous a répondu avec le code HTTP retour 200 au lieu du 216 Partial Content, ce qui signifie qu'il ignore l'entête « Range ». On notera également que la taille annoncée (« Content-Length ») est la totalité du fichier et que l'entête « Content-Range » (qui rappelle la plage demandée sur la taille totale) est absent. On notera que MDN se trompe : la présence de l'entête « Accept-Ranges » ne suffit pas pour garantir qu'un serveur web honore bien l'entête HTTP « Range ».
Je sèche : la prise en charge du contenu partiel par les serveurs web est vieille… C'est grâce à cela que les gestionnaires de téléchargement, qui ouvrent plusieurs téléchargements simultanés d'un même fichier sur des plages d'octets différentes et qui proposent la reprise d'un téléchargement interrompu, fonctionnent. C'est forcément géré nativement par Apache httpd. Une recherche web et un essai sur plusieurs de nos serveurs me confirment cela.
À ce stade, le serveur web est éliminé. Reste sa configuration.
Dans certains résultats de recherche, on préconise d'ajouter l'entête « Accept-Ranges » ou « Range » avec le mod_headers… Ça a aucun sens (à ce stade, le traitement de la requête est terminé, Apache httpd a déjà répondu qu'il ne prenait pas en charge « Range » + « Accept-Ranges » est un entête de requête, pas de réponse, et le navigateur web la positionne déjà et il n'est pas retiré en chemin…), et, de toute façon, ma collègue a déjà tout tenté avec l'ajout d'entêtes, me dit-elle.
Je teste avec curl : nos autres serveurs Apache httpd prennent en charge « Range ». Ils sont en version 2.4.25, comme le serveur problématique. À l'exception de l'hôte virtuel, toute la configuration est celle par défaut de Debian.
Le serveur est récent donc on ne mitige pas la vulnérabilité CVE-2011-3192 puisqu'elle est corrigée dans le code depuis longtemps. De même, Debian n'incorpore pas une directive MaxRanges restrictive.
À ce stade, la configuration du serveur web est éliminée, à l'exception du virtual host.
L'application est livrée avec une configuration (hôte virtuel) nginx. Le chef de mon équipe avait demandé à la collègue (d'une autre équipe) d'utiliser Apache httpd au motif qu'on avait uniquement des httpd, qu'on n'avait pas le temps pour s'éparpiller pour monter en compétence sur un autre logiciel, etc. Je trouve ça comique, le "si tu utilises nginx => pas d'aide de notre part ; si tu utilises apache => débrouille-toi pour faire l'intégration de l'appli". Je teste nginx et la configuration fournie par le projet sur le serveur de test : ça marche directement.
Si Apache httpd gère nativement le contenu partiel et si ça fonctionne de base avec nginx avec la configuration fournie par l'application, ça signifie qu'il y a une erreur dans la configuration de l'hôte virtuel pour Apache httpd conçue à partir de la conf' nginx fournie. Je relis, je compare avec la version nginx, et… bingo ! On a oublié d'exclure le chemin « /media » de la liste des chemins qu'il ne faut pas proxifier vers l'application Django.
Évidemment, il y avait aucun indice dans le journal Django. Quant à lui, le journal uwsgi mentionnait juste une requête reçue, qu'elle a été traitée en tant de temps, etc. Rien qui soit de nature à m'alerter.
Donc les requêtes sur les fichiers vidéos sont proxyfiées vers l'application Django. Le chemin vers lesdites vidéos est routé (on constate cela dans le fichier url.py de l'applicatif)… Mais Django ne prend pas en charge nativement le contenu partiel… Tout s'explique.
Au final, il faut ajouter ces directives dans l'hôte virtuel Apache httpd afin que toutes les requêtes portant sur /media (et en dessous dans l'arborescence) ne soient plus redirigées vers l'application web :
Alias /media /chemin/vers/les/medias
<Location /media>
ProxyPass !
</Location>
<Directory /chemin/vers/les/medias>
Require all granted
</Directory>
On recharge la conf' avec systemctl apache2 reload, et hop, ça fonctionne :
$ curl -s -o /dev/null -D - -r 1315624-1562279 https://monorganisation.example/media/videos/ef2218bee799524931d2af11aafc3933e5d7413da2989b70fc94a9941fcd50e1/666/720p.ts
HTTP/1.1 206 Partial Content
Date: Wed, 30 Sep 2020 17:19:31 GMT
Server: Apache/2.4.25 (Debian)
Last-Modified: Wed, 16 Sep 2020 14:19:04 GMT
ETag: "1025278-5af6ef32e144b"
Accept-Ranges: bytes
Content-Length: 246656
Cache-Control: max-age=0, no-store
Content-Range: bytes 1315624-1562279/16929400
Content-Type: video/MP2T
Pour être exhaustif, /media est routé par Django uniquement si la variable de configuration « DEBUG » a la valeur « True ». C'est effectivement sa valeur par défaut dans le fichier settings.py. Nous ne la surchargeons pas dans le fichier settings_local.py. Il s'agit d'une erreur, désormais corrigée.
Au final, on a migré vers nginx afin d'être dans les pré-requis de l'application, ce qui permet de demander de l'aide plus facilement. De plus, une fonctionnalité de ce logiciel nécessite impérativement nginx (Apache httpd n'a pas d'équivalent), donc autant harmoniser la plateforme en installant nginx partout.
Je retiens assez peu d'éléments techniques. Je retiens surtout un défaut récurrent de notre organisation de travail. Notre équipe d'adminsys et notre équipe de devs auraient dû travailler ensemble sur ce point plutôt que de se renvoyer la balle "c'est ton périmètre, démerde-toi". Au final, c'est ce qu'on a fait car j'ai mis mon nez dedans, mais on a perdu du temps et de l'énergie (tenter de comprendre, modifier le code de l'application pour diviser les vidéos en fragments, ré-encoder les vidéos existantes, comprendre, retirer les patchs du code, ré-encoder les vidéos afin de virer les fragments, etc.). Après ça, la méta-équipe qui regroupe les deux sus-citées peut bien se nommer « devops » et notre DSI peut bien pavoiser partout qu'on est devops et faire des causeries sur le sujet. On n'est même pas au premier stade de la collaboration, celle, élémentaire, qui existe depuis bien avant le bullshit devops. Bien sûr, on me répondra que c'est facile de dégainer sur un seul exemple, ça veut rien dire, ce n'est pas représentatif, tout ça. Mais quand je dégaine plusieurs exemples, on me rétorque que je suis méchant, que j'accable les personnes. :))))
RabbitMQ est une dépendance d'un logiciel que nous utilisons, donc, forcément, j'ai dû regarder koi ke s'est. Ce shaarli a plus vocation à conserver ce que j'ai compris de RabbitMQ que de t'apprendre des trucs.
RabbitMQ est un courtier en messages (message broker). Il permet à des processus et à des logiciels distincts de s'échanger des messages à travers un réseau (oui, ça fonctionne aussi en local, mais bon…). Il permet de temporiser une communication (en attendant d'avoir de quoi la traiter) donc d'absorber des pics d'utilisation grâce une file d'attente, d'avoir plusieurs écrivains et lecteurs dans une même file d'attente, etc.
J'en retiens deux usages :
commande1 | commande2 && commande3, en gros.Dans le passé, j'ai utilisé Apache Kafka (voir mes notes). Je retiens, que, comme d'hab', on peut contorsionner un logiciel afin qu'il fasse le même boulot qu'un autre, mais, en gros : Kafka = file d'attente durable / sécurisée + montée en charge facile par ajout de nœuds + stockage de flux (genre un NetFlow) plus que stockage de messages. RabbitMQ = protocole ouvert et normalisé AMQP dont RabbitMQ n'est qu'une implémentation (Kafka = protocole maison) + simplicité d'utilisation (pour les cas basiques).
J'ai un écrivain / producteur dans une file d'attente, et deux lecteurs / consommateurs de cette file d'attente. RabbitMQ attribue les tâches / messages en round-robin : une tâche pour le premier consommateur, une tâche pour le deuxième, une tâche pour le premier, etc. Cet algorithme ne tient pas compte de la charge effective de chaque consommateur : à certains moments, un consommateur avait X tâches en attente alors que l'autre consommateur faisait rien. J'ai rien trouvé permettant de changer l'algorithme de répartition (dispatching) de RabbitMQ.
Pour compenser, on peut configurer le prefect, c'est-à-dire le nombre max de messages non-acquittés que RabbitMQ envoie à un consommateur (ou à un ensemble de consommateurs). Cela se configure sur le consommateur. Ainsi, tant qu'un consommateur n'aura pas acquitté ses messages afin de retomber sous ce seuil, RabbitMQ distribuera la tâche à un autre consommateur. Si aucun consommateur est disponible, le nombre de messages « ready » (messages pas encore distribués) grossira. La documentation nomme ça « fair dispatch ».
Nos consommateurs sont des nœuds celery, un gestionnaire de tâches distribuées Python. Celery lit la file d'attente RabbitMQ et déclenche la fonction Python KiVaBien dans notre code Django. Pour régler le prefect, le paramètre à utiliser est « worker_prefetch_multiplier » donc la variable « CELERYD_PREFETCH_MULTIPLIER » dans /etc/default/celeryd si l'on utilise le script d'init celery. Par défaut, la valeur est 4. Elle se multiplie par le nombre de threads d'un worker. Comme nous utilisons « worker_concurrency = 1 », le nombre maximal de messages non-acquittés par worker sera 4. Nous avons 2 machine d'encodage, donc 2 celery donc 2 workers, donc un maximum de 8 messages non-acquittés.
Attention : par défaut, celery acquitte le message / la tâche avant d'avoir effectué le boulot. Les tâches réservées (« prefetch ») viennent donc en sus des tâches acquittées qui sont en cours d'exécution (source 1, source 2). Dans mon cas, avec 1 worker, « worker_concurrency = 1 » et « worker_prefetch_multiplier = 4 », 5 tâches seront "en cours" : 1 en cours d'exécution et 4 en attente de traitement et d'acquittement.
Mais, tiens, comment supervise-t-on le nombre de messages dans une file d'attente RabbitMQ ? Cela donne une idée de la charge sur notre service et permet d'anticiper le nombre de consommateurs qu'il convient de lancer afin d'offrir un service acceptable.
Simple : sudo rabbitmqctl -p <virtual_host> list_queues name messages_ready messages_unacknowledged messages.
On formate le résultat de sortie en récupérant le nom des files d'attente, le nombre de messages prêts, le nombre de messages non-acquittés et le nombre total de messages (ready+unack). Pour le reste : voir le manuel.
Les messages non-acquittés sont donc les messages que RabbitMQ a déjà distribué à des consommateurs qui n'ont pas confirmé les avoir traités (dans le cas de celery, par défaut, l'acquittement se fait même avant de bosser).
Les messages prêts sont ceux qui n'ont pas encore été distribués à des consommateurs par RabbitMQ par absence de consommateurs disponibles.
Comment voir le contenu (payload) des messages d'une file d'attente ? Il faut utiliser le plugin management. Il met à disposition une API sur laquelle tapent une interface web (qui affiche de zolis graphes de charge) et l'outil en ligne de commande rabbitmqadmin.
Pour activer le plugin : rabbitmq-plugins enable rabbitmq_management. Pour le désactiver : rabbitmq-plugins disable rabbitmq_management.
Récupérer le contenu des messages :
rabbitmqadmin -V <virtual_host> -u <identifiant> -p <mdp> get queue=<nom_file_attente> count=<nombre_de_messages_qu'on_veut_voir> requeue=true. Si tu as « Access refused: /api/queues/%2F/nom_file/get », c'est que t'as oublié de préciser le nom de l'hôte virtuel ou un couple identifiant/mdp valide (le mieux est de reprendre celui utilisé par ton application ;) ) ;Seuls les messages prêts (ready) sont concernés (les messages unacknowledged sont considérés comme ayant été consommés).
L'affichage se fait du plus ancien (le premier arrivé dans la file d'attente) au plus récent (le dernier arrivé).
Si l'on ne précise pas « requeue=true », les messages sont sortis de la file d'attente, donc ils ne seront pas traités par les consommateurs !
Si l'on ne précise pas « count=X », un seul message est affiché (le plus récent).
on peut cibler les descripteurs de fichier avec
-e write=<numero du fd>
T'es sûr de toi ? Je viens de tester avec un strace -e write=2 dmesg sur un Debian 10 et ça ne fonctionne pas : ça affiche TOUS les appels système. Le manuel dit : 1) il faut utiliser -e write -e write=fd ; 2) ça ne focalise pas strace sur les écritures dans un descripteur de fichier donné, ça dump tout ce qui est écrit via ce descripteur.
mais ça changera probablement rien à ton analyse :p.
Non. Mon problème est que le message d'erreur n'est plus affiché sur stderr dès que je passe l'option « -f » à strace. Et strace voit aucun write(), writev() ou autre.
Est-ce que la conclusion c'est pas un
grep -vxe 'Error: Terminated'à la fin ? :D
J'aimerais bien m'être auto-aveuglé avec un grep -v, mais ce n'est pas le cas. ^^
Résumé : quand elle est chaînée à un head -n 1, la commande rabbitmqctl affiche ce qu'on attend d'elle mais aussi une erreur « Error: Terminated ». Oui, head termine son exécution dès qu'elle a affiché le nombre de ligne qu'on lui a demandé. Se faisant, elle n'écoute plus ce qui se raconte dans le pipe. L'écriture dans un pipe sans lecteur déclenche l'envoi d'un signal SIGPIPE à l'écrivain (rabbitmqctl dans le cas présent).
Ce signal peut fermer le programme (traitement par défaut), déclencher un bout de code maison ou être ignoré (c'est le cas de rabbitmqctl) et/ou déclencher un comportement pas vraiment prévu (c'est le cas de rabbitmqctl).
On profite de l'occasion pour réviser l'utilisation de strace (« -f » et « -e » et la différenciation entre une fonction de la libc et un appel système ‒ fork() versus clone() ‒ sont les pièges habituels), les signaux Linux (leur héritage, leur traitement avec signal / sigaction, la structure de données siginfo_t, etc.), l'appel système write() (qui permet d'écrire dans un fichier, comme la console), les compétitions entre processus (si rabbitmqctl va assez vite, il peut terminer avant que head termine, donc aucune erreur s'affiche), et les limites des outils de diagnostic (un signal émit durant un appel système est annoncé comme venant du processus qui a effectué cet appel, pas du noyau, strace -f fait disparaître l'erreur donc impossible de la tracer…).
J'utilise RabbitMQ.
Pour regarder les entrailles de mon installation, j'utilise l'outil rabbitmqctl. Exemple : rabbitmqctl -p <virtual_host> list_queues | head -n 3.
Une fois sur deux, cette commande me retourne le résultat (la liste des files d'attente) ainsi que l'erreur « Error: Terminated ».
Aucune erreur est affichée si j'utilise grep à la place de head. Si je n'utilise pas la commande head, je n'ai pas de problème.
La réponse est évidente : head se termine (exit()) dès qu'elle a affiché le nombre de lignes qu'on lui demande, donc le fichier « stdin » est fermé… Sauf que ce fichier est toujours le « stdout » de rabbitmqctl. Si rabbitmqctl tente d'écrire dedans avec l'appel système write(), celui-ci lui enverra le signal SIGPIPE qui signifie, en gros "hého, il y a plus personne qui t'écoute !". rabbitmqctl peut intercepter ce signal et afficher un message d'erreur.
Il n'y a pas de problème avec | grep car grep attend la fin du fichier (qu'il reconnaît car read() retourne alors 0, voir man open).
J'en parle à Johndescs qui, évidemment, doute : "ça ferait pareil avec d'autres programmes, genre dmesg, ce qui n'est pas le cas".
Sortons strace afin de tracer les appels système et leurs erreurs.
Évidemment, rabbitmqctl se fork() (et pas qu'un peu…), donc, la première fois, on voit rien. Relançons en demandant de tracer également les appels système des processus enfant : strace -f.
Évidemment, les appels système sont très nombreux, donc on veut les filtrer avec « -e », mais, comme toujours, on ne filtre pas ce qu'il faut : rabbitmqctl utilise writev(), pas write()…
Cette fois, on est bon :
# strace -f -e writev rabbitmqctl -p <virtual_host> list_queues | head -n 1
strace: Process 23884 attached
[…]
strace: Process 23982 attached
[…]
strace: Process 23990 attached
[…]
[pid 23990] writev(45, [{iov_base="\0\0\0+", iov_len=4}, {iov_base="\203D\2\1\0008\204h\4a\23gR\0\0\0\0A\0\0\0\0\1R\1r\0\3R\0\1\0"..., iov_len=43}], 2 <unfinished ...>
[pid 23982] writev(1, [{iov_base="Listing queues ...\n", iov_len=19}], 1 <unfinished ...>
[pid 23990] <... writev resumed> ) = 47
[pid 23982] <... writev resumed> ) = 19
Listing queues ...
[…]
[pid 23990] writev(45, [{iov_base="\0\0\0+", iov_len=4}, {iov_base="\203D\2\1\0008\204h\4a\24gR\0\0\0\0A\0\0\0\0\1R\1r\0\3R\0\1\0"..., iov_len=43}], 2) = 47
[pid 23982] writev(1, [{iov_base="celeryev.42165f0d-b15c-4667-8b57"..., iov_len=51}], 1) = -1 EPIPE (Broken pipe)
[pid 23982] --- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=23899, si_uid=114} ---
[…]
[pid 23982] +++ exited with 0 +++
Le processus numéro 23990 interroge le processus 23982 via un pipe (« writev(45,[…] » => fichier numéro 45 ;) ). Ce dernier réagit à l'ordre en écrivant directement sa réponse sur stdout (writev(1, [{iov_base="Listing queues … » => fichier numéro 1 = stdout, c'est la norme). Et il se mange bien un SIGPIPE.
Même chose pour dmesg | head -n 1 : dmesg reçoit bien un SIGPIPE et se termine (ce qui est le traitement par défaut de la plupart des signaux).
Dans la trace strace ci-dessous, notons que la structure de données « siginfo_t » associée à un signal nous informe que le signal SIGPIPE a été envoyé par un processus (« si_code=SI_USER ») dont le PID est « si_pid=23899 » et que ce dernier tourne avec les droits de l'utilisateur dont l'ID est « si_uid=114 ». 114 = UID de l'utilisateur rabbitmq sur mon système. 23899 = PID de la machine virtuelle Erlang (BEAM/erl, car RabbitMQ est codé en Erlang) qui fait tourner rabbitmqctl, qui, d'ailleurs demande à ignorer le signal SIGPIPE (on verra ça plus bas).
Avec dmesg, on constate que l'UID est celui de root et que le PID est celui du processus dmesg lui-même. Même chose avec un simple programme C qui se fork() et execvp() seq : le PID annoncé dans « si_pid » est celui du processus enfant.
Dans le cas d'Erlang, ce n'est pas étonnant que la machine virtuelle re-émette le signal à l'un de ses enfants, car elle est un intermédiaire entre lui et le noyau.
Dans le cas du programme C, on pourrait s'attendre à ce que le code du signal soit SI_KERNEL vu que write() est un appel système, donc normalement exécuté côté kernel, mais ce n'est pas le cas… C'est un peu comme la structure de données d'un SIGCHLD qui contient le PID du processus enfant qui a pris fin : ce n'est pas directement l'enfant qui envoie le signal (et « si_code » est cohérent puisqu'il vaut « CLD_EXITED »).
Mais, du coup, qui écrit le message d'erreur « Error: Terminated » ?
Impossible de le savoir : dès que j'utilise le paramètre « -f » de strace, il ne s'affiche plus :
rabbitmqctl pour lancer la machinerie Erlang, se termine avec un code retour 70. On le voit car son père (Bash, execvp() par strace afin d'exécuter rabbitmqctl qui s'avère être un script) reçoit un signal SIGCHLD (ton enfant a terminé) avec « si_status=70 » dans la structure de données « siginfo_t »). Bash, le père, conserve ce code retour et termine avec un code retour 70.L'observation avec strace n'est pas si neutre que cela ? Je suis très surpris…
Peut-être que le signal SIGPIPE est traité par rabbitmqctl ? D'ailleurs, si l'on regarde le journal strace ci-dessus, on constate que le processus numéro 23982, qui s'est pourtant mangé le SIGPIPE, termine avec un code retour 0, ce qui n'est pas normal : essaye un kill -13 (SIGPIPE a le numéro de signal 13, voir man 7 signal) sur un cat ou un grep : le code retour est 141. De même, quand un processus termine à cause d'un signal, non-traité strace affiche « killed by
Pour traiter un signal, c'est-à-dire soit l'ignorer, soit déclencher une fonction, un développeur doit utiliser les appels système signal() ou sigaction. Avec un strace -f -e writev,signal rabbitmqctl […] | head -n 1, on constate que plusieurs processus demandent à ignorer SIGPIPE. Mais pas le processus qui écrit sur stdout.
Peu importe : la gestion des signaux s'hérite : si un processus parent a ignoré un signal ou enregistré une fonction pour le gérer, alors toute sa descendance en bénéficie. L'héritage de la fonction de traitement s'arrête uniquement si le code est écrasé avec une fonction de la libc membre de la famille exec (c'est logique : la fonction de traitement ne sera plus disponible puisque le code du programme est écrasé par un autre programme). Un signal ignoré cesse jamais implicitement de l'être. Là encore, c'est le manuel de signal() qui nous le confirme : « A child created via fork(2) inherits a copy of its parent's signal dispositions. During an execve(2), the dispositions of handled signals are reset to the default; the dispositions of ignored signals are left unchanged. »
Comment visualiser la parenté avec strace ? Avec « -f », on a bien le PID qui s'affiche à gauche, mais on ne sait pas quel processus l'a enfanté. On pense à utiliser « -e fork », mais ça produit aucun résultat. Un lutin du web nous rappelle que fork() n'est pas un appel système, mais aussi une fonction de la glibc qui, ces temps-ci, encapsule l'appel système clone(). Debian utilise la glibc. Ça explique pourquoi strace ne voit pas les fork(). Hop, « -e writev,clone,signal » et l'on voit bien que le processus parent de celui qui écrit sur stdout ignore (« SIG_IGN ») le signal SIGPIPE, donc son enfant (celui qui écrit sur stdout et reçoit le SIGPIPE) l'ignorera aussi :
[pid 6686] rt_sigaction(SIGPIPE, {sa_handler=SIG_IGN, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f8a98c2b0e0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
[…]
[pid 6686] clone(child_stack=0x7f8a561b7ff0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f8a561b89d0, tls=0x7f8a561b8700, child_tidptr=0x7f8a561b89d0) = 6767
[…]
[pid 6767] writev(1, [{iov_base="celeryev.42165f0d-b15c-4667-8b57"..., iov_len=51}], 1) = -1 EPIPE (Broken pipe)
Je précise, même si c'est inutile, que le processus enfant n'a pas fait appel à l'appel système execve (ni autre membre de la famille exec).
Donc le processus n'a pas de routine définie lorsqu'il se prend un SIGPIPE, donc ce n'est pas lui qui affiche « Error: Terminated ». Mais cela peut être son parent, la machine virtuelle Erlang ou un autre processus qui discute avec lui et qui se rend compte qu'il n'a pas fait son boulot.
dmesg n'enregistre pas non plus de fonction pour traiter SIGPIPE et ne l'ignore pas non plus, d'où il est donc normal qu'il se termine sans rien afficher quand il se mange un SIGPIPE, car c'est le traitement par défaut de SIGPIPE.
On notera également qu'il y a une notion de vitesse : si rabbitmqctl a le temps d'écrire ce qu'il veut avant que head ne termine, alors aucun signal SIGPIPE est envoyé et donc aucune erreur est affichée.
On vérifie ça avec rabbitmqctl -p <virtual_host> list_queues | (sleep 2 && head -n 1) : aucune erreur, jamais. En revanche, sleep 1 = erreur.
On peut aussi vérifier avec strace -e writev rabbitmqctl -p <virtual_host> list_queues | strace head -n 1 : on verra que head close() stdin avant que rabbitmqctl termine ses writev().
On peut aussi reproduire ça avec seq 1 1040 | head -n 1 : aucune erreur. En revanche, seq 1 1041, vlam, SIGPIPE. Normal : seq écrit les nombres 1 à 1040 ainsi que le retour à la ligne entre chaque nombre dans un buffer. Ce qu'il faut écrire occupe 4093 octets au total, donc write() écrit ça d'un coup d'après sa page de manuel, donc head a encore rien reçu, donc il n'a pas pu fermer stdin et se terminer. En revanche, il faut plus que 4096 octets pour écrire les nombres de 1 à 1041, donc cela se fait en deux appels à write(). Entre les deux appels, head a fermé le fichier et s'est arrêté, donc le deuxième write() déclenche un SIGPIPE.
Cela explique le côté aléatoire de mon problème (tantôt le message s'affiche, tantôt non). rabbitmqctl affiche 4 lignes au total. Avec un head -n 4, le message d'erreur apparaît jamais car rabbitmqctl a toujours le temps d'effectuer ses opérations (demander à son enfant de bosser, l'enfant consulte ses registres et écrit sur stdout, etc.). Avec head -n 3, il y a une race condition : parfois rabbitmqctl arrive à être assez rapide, parfois non. Avec head -n 1, rabbitmqctl a aucune chance de gagner la compétition : il y a trop de boulot et de communication inter-processus à entreprendre.
Cela est confirmé par le manuel de write() qui expose : « If write() is interrupted by a signal before it writes any data, it shall return −1 with errno set to [EINTR] ». Dans la discussion que j'ai déjà mentionnée, on lit également « EPIPE An attempt is made to write to a pipe or FIFO that is not open for reading by any process, or that only has one end open. […] ». Tout correspond : write() n'est pas interrompu, il arrive trop tard.
Au final, c'est très probablement rabbitmqctl (ou la machinerie Erlang) qui affiche le message d'erreur « Error: Terminated » quand l'un des processus enfant ne se termine pas de manière attendue.
Difficile d'aller plus loin (j'aurais voulu savoir quoi, précisément, écrit le message d'erreur) :
su (le script shell rabbitmqctl l'appelle afin de lancer la machinerie Erlang). Sur une machine de bureau Debian 10, si l'on fait un su -s /bin/sh -c cat (comme le fait rabbitmqctl sauf que la commande n'est pas cat, évidemment) et que l'on envoie un signal à cat avec kill, on constate que su consulte /usr/share/locale/<LOCALE>/LC_MESSAGE/libc.mo et write() un message d'erreur qui varie en fonction du signal envoyé. Mais, sur le serveur RabbitMQ Debian 9, ce n'est pas le cas : su write() rien, ni quand on le charge de lancer cat, ni quand on le charge de lancer rabbitmqctl ;gdb permet de jouer avec les signaux, mais on est face à un empilement de scripts shell (sur lesquels gdb est incompétent) qui lancent, in fine, du Erlang, qui, lui, a besoin que des variables d'environnement soient définies (par les scripts) et dont des sous-processus discutent entre eux pour accomplir le boulot…, bref, un joli merdier ;signal() et sigaction() sont des appels système, pas des fonctions de la libc, y'a-t-il moyen d'intercepter ?) ou de faire des dégâts collatéraux (comportement inattendu lié à des signaux qui auraient du être ignorés ou traités et qui ne le sont pas à cause de la surcharge, par exemple) ;Résumé : les erreurs rapportées ci-dessous (et dans le titre) sont rares pour une machine virtuelle (peu de pages web en parle), je n'ai pas de solution à proposer et, dans mon cas, la machine virtuelle a totalement crashé deux mois après les premiers symptômes. Vérifie tes sauvegardes. :)
En juillet 2020, l'une de nos machines virtuelles Proxmox+KVM kernel-panic-ait, parfois plusieurs fois par jour. Puis ce comportement a disparu sans intervention humaine.
Début août 2020, /var passe en lecture seule. Je redémarre la VM, un fsck se déclenche et la partition était à nouveau montée en écriture. Le lendemain, notre supervision m'indique que cette partition est à nouveau en lecture seule. dmesg indique que la transition a eu lieu 256 minutes (4 h) après le démarrage. Les erreurs consignées sont :
kernel: sd 0:0:0:0: [sda] Result: hostbyte=DID_OK driverbyte=DRIVER_TIMEOUT,SUGGEST_OK
kernel: end_request: I/O error, dev sda, sector 29954432
kernel: Aborting journal on device sda7.
kernel: ext3_abort called.
kernel: EXT3-fs error (device sda7): ext3_journal_start_sb: Detected aborted journal
kernel: Remounting filesystem read-only
Pourquoi uniquement /var alors que toutes les partitions de cette machine virtuelle sont stockées dans le même LV LVM sur l'hyperviseur ? Car il s'agit d'un serveur, donc une fois qu'il a démarré et que les principaux binaires sont en RAM, il n'y a quasi plus de lecture et encore moins d'écriture sur /, alors que /var reçoit les journaux systèmes et applicatifs en permanence.
I/O wait côté hyperviseur ? Je n'y crois pas, car les graphes sur la page de résumé de l'hyperviseur dans l'interface web de Proxmox ne le montrent pas.
La VM n'a pas assez de temps CPU pour effectuer ses opérations car ses ressources CPU sont sous-dimensionnées ou que l'hyperviseur est débordé par d'autres VMs ? Je n'y crois pas, car top affichait « 0,0 st » (st = steal time = temps volé). Mais comme je n'étais pas là au moment du passage en lecture seule… De plus, c'était les vacances, donc la charge de ce serveur était très réduite et aucun changement n'a été mis en œuvre par les administrateurs sur l'ensemble du périmètre (cette VM, l'hyperviseur, le réseau, etc.).
Très peu de ressources sur le web mentionnent les erreurs que je rencontre.
Je trouve la première partie des erreurs extraites de mon dmesg dans un ticket chez VirtualBox.
La première piste est un problème d'I/O côté hyperviseur. Comme vu ci-dessus, aucun élément me permet d'affirmer cela.
La deuxième est de désactiver NCQ (optimisation des I/O en laissant le disque dur ordonnancer lui-même les requêtes afin de les traiter dans l'ordre de l'emplacement physique des données). Je n'y crois pas : 1) mes erreurs ne mentionnent pas NCQ ; 2) il y a trop d'abstractions (ext4 sur une partition étendue dans un LV LVM dans un volume RAID matériel) pour que NCQ puisse agir. J'ai quand même désactivé NCQ dans la VM.
Je trouve la deuxième partie des erreurs dans un rapport de bug chez Red Hat. Problème dans Linux censé avoir été corrigé à la fin des années 2000 mais qui est toujours là en 2013 et 2014… La version du système de notre VM date justement de la fin des années 2000, mais la version de notre noyau est ultérieure à celle dans laquelle le correctif est apparu.
Je me laisse convaincre que, peut-être, le journal ext est corrompu, ce qui explique le déclenchement automatique d'un fsck à chaque redémarrage, même quand la partition /var n'a pas été remontée en lecture seule.
La partition /var étant utilisée par plein de processus, je ne peux pas travailler dessus. Je redémarre donc en single user mode (une entrée GRUB me le proposait, sinon il faut éditer la ligne et ajouter « single » à la fin de la ligne « linux » et démarrer en pressant ctrl+x) puis je tape les commandes suivantes proposées dans un commentaire du rapport de bug :
tune2fs -O ^has_journal /dev/sda7
fsck.ext4 -f /dev/sda7
tune2fs -j /dev/sda7
reboot
Vu que fsck n'a pas trouvé d'erreur, je pense que tout cela a été vain.
Quoique… Cela a tenu un mois et demi.
Le serveur a totalement crashé mi-septembre 2020. Impossible de booter car absence de partition… Il semble qu'il ne s'agissait donc pas d'un problème temporaire type I/O wait / temps CPU volé, etc.
Un coup de testdisk depuis un Live CD a permis de remettre la partition / dans le MBR, mais pas /var…
Nous avons monté une nouvelle machine virtuelle. La configuration du service a été récupérée depuis Bacula. Les données des utilisateurs du service étaient stockées dans un partage NFS. Aucune perte de données à déplorer.
Firefox met en cache les redirections HTTP définitives (code HTTP 301).
Ainsi, quand tu configures une redirection sur un serveur web, que tu constates que tu t'es trompé, que tu remets la configuration d'origine et que tu testes, t'es encore redirigé. D'où l'importance de tester avec un outil de diagnostic qui ne conserve pas d'état comme wget ou curl.
Ce qui m'a étonné cette fois-ci, c'est que la mise en cache de la redirection a perduré après le redémarrage de Firefox. Je n'ai pas le souvenir que ça me soit déjà arrivé.
Pour faire oublier à Firefox une redirection sans effacer tout l'historique / cache de la dernière heure depuis les préférences : aller dans l'historique (ctrl + h), chercher la page web précise, cliquer droit, « oublier ce site ».
Nginx prend en charge nativement le rate-limiting, c'est-à-dire la limitation du nombre de requêtes par intervalles de temps sur une page web, sur plusieurs pages web, sur un site web entier. Il est même possible de mettre des adresses IP en liste blanche, de limiter uniquement les requêtes de type POST, de cumuler plusieurs critères de limitation (par hôte virtuel + par adresse IP source, par exemple), etc.
Dans mon cas, le CAPTCHA maison d'un formulaire de contact web est extrêmement faillible. C'est une leçon d'humilité pour les anti-Google dans mon genre : on ne remplace pas reCAPTCHA en un claquement de doigts. Comme dans tout formulaire, un email est envoyé à l'administrateur du site web et un autre à la personne qui a rempli le formulaire. En changeant la valeur du champ HTML « email », il est donc possible d'envoyer un email à n'importe quelle adresse email. Bien sûr, le sujet de l'email ne sera pas au choix du spammeur et le message du spammeur sera entouré de phrases convenues ajoutées par le site web comme « vous avez envoyé le message suivant à l'administrateur du site machin.example », mais cela convient aux spammeurs.
En attendant que les développeurs nous informent qu'ils feront rien et que nos utilisateurs nous valident que ce formulaire est inutile car ils ne souhaitent pas traiter les réclamations par email mais par une demande dans notre système de tickets, une limitation du trafic a été utile pour juguler temporairement le spam sans perdre en fonctionnalité.
D'abord, je déclare un limiteur dans /etc/nginx/conf.d/rate-limiting.conf (mieux vaut ici que de modifier nginx.conf, car ça permet au gestionnaire de paquets du système de mettre à jour nginx.conf sans intervention humaine) : limit_req_zone $binary_remote_addr zone=contact:10m rate=1r/m;. On travaille sur l'adresse IP (stockée, par nginx, dans la variable interne « $binary_remote_addr »). On nomme le limiteur « contact ». Il pourra occuper 10 Mo de mémoire tout au plus (ce qui permet de stocker environ 16 000 états IPv4 ou 8 k états IPv6). Il limitera le trafic à une requête par minute.
Ensuite, j'active le limiteur dans le bloc « server » de l'hôte virtuel :
location /contact {
limit_req zone=contact burst=3 nodelay;
uwsgi_pass django;
}
Je limite les requêtes uniquement sur la page « /contact » du site web.
J'utilise le limiteur nommé « contact ». Au-delà d'une requête par minute, j'autorise un pic jusqu'à 3 requêtes (par minute, toujours). Je dégage au plus vite les requêtes excédentaires (au-delà du pic) afin de ne pas consommer inutilement des ressources (nombre de connexions simultanées, RAM, etc.) plutôt que de mettre en file d'attente les requêtes excédentaires en attendant qu'un slot se libère après l'expiration du temps d'attente (1 minute tout au plus).
« django » fait référence à un upstream de type socket fichier défini en amont. Car, oui, ce nginx est en frontal d'une application Python Django servie par le serveur d'applications uwsgi.
Je recharge la configuration avec systemctl restart nginx, et c'est terminé. \o/
Avec cette configuration, on est passé de 3000 spams en quelques heures à deux spams par heure puis l'emmerdeur a laissé tomber. :)
Si j'avais voulu limiter uniquement les requêtes POST, voici ce que j'aurai mis dans /etc/nginx/conf.d/rate-limiting.conf :
map $request_method $limit_post {
default "";
POST $binary_remote_addr;
}
limit_req_zone $limit_post zone=contact:10m rate=1r/m;
Ça ne fonctionnait pas car notre spammeur avait résolu le CAPTCHA. Dit autrement : il faisait plusieurs requêtes de type GET sur le formulaire web. Dès qu'il reconnaissait l'URL d'une image qu'il avait décodé (exemple : s'il trouvait <img src="/images/captcha_a52b844ab3acbd1169db9a66647285b5fc694284/" […] /> dans le code source de la page web, alors il savait qu'il fallait remplir le champ HTML « captcha » avec la valeur « 7 » car cette URL pointera toujours sur un CAPTCHA qui dit « 5 + 2 = ? », seul le fond, le bruit, changera), il envoyait une requête POST, et, forcément, il tapait dans le mille dès cette première requête, ce qui met en échec toute limitation de trafic.
Au final, puisque le CAPTCHA est faillible, que l'application web envoie l'email destiné à l'administrateur à une adresse email invalide sans tenir compte de la configuration (autre bug signalé), que les développeurs ne corrigeront pas l'application web, et que nos utilisateurs ne veulent pas traiter les réclamations par email, nous avons mis en place une page web qui invite les visiteurs à déposer une demande d'assistance dans notre outil de gestion de ces demandes.
Pour ce faire, voici la nouvelle configuration que l'on trouve dans l'hôte virtuel de nginx :
location /contact {
root /chemin/vers/ma/super/appli/;
try_files /erreurContact.html $uri;
}
La racine du site web (document root) est tel chemin. Lorsqu'une requête arrive, essaye de servir le fichier « erreurContact.html » (qui se trouve dans la racine du site web), sinon sers l'URL qui t'es demandée. Comme la page existera toujours, c'est toujours elle qui sera servie.
En octobre-novembre 2020, et à raison d'une à deux fois par semaine, j'ai aidé une amie à retaper un studio qu'elle propose à la location.
J'ai :
Au final, j'ai pu être force de proposition (lol) sur la partie électrique car j'ai toujours environ pigé la théorie et je comprenais ce qu'il y avait à faire, etc. Parmi les """"nouveautés"""", j'ai préféré poncer la planche de bois (souvenir d'enfance, les gestes ne s'oublient pas). J'ai détesté peindre : je comprenais rien, j'avais l'impression de faire n'importe quoi en permanence, il faut être hyper concentré, etc.
Merci à cette amie de m'avoir donné l'opportunité de bricoler « pour de vrai ». Beaucoup de personnes m'ont dit (et me disent encore…) de varier mes activités de sale geek, de sortir de chez moi gnagnagna, mais très peu de personne se sont bougées le cul pour me proposer des activités, et encore moins pour me laisser tenter des trucs sur leurs propriétés personnelles.
L'implication, c'est toujours pour les autres. Comme les employeurs qui cherchent des employés déjà formés à leurs manières de faire, ou les femmes qui, niveau amour, cherchent des hommes plus vieux car elles les pensent plus matures, raisonnés et expérimentés, ou comme les lieux d'enseignement supérieur qui te refusent en première année avant de te faire la cour en troisième année, quand t'auras prouvé, via des notes, que tu réponds au cahier des charges. Dans tous ces cas, il faut bien comprendre que quelqu'un d'autre a fait le sale boulot en amont : quelqu'un a forcément formé l'employé modèle (lol), l'amoureux parfait (lol), et l'étudiant parfait (lol). On ne devrait jamais oublier ça. Du coup, les moulins à vent qui t'incitent à faire des activités différentes sans rien proposer sont juste des grandes gueules, des flemmards, des gens pour qui tu ne comptes pas assez pour qu'ils t'accordent du temps. Ce n'est pas en restant un mou-mou inactif que l'on fait évoluer les pratiques des gens (si toutefois c'est possible).
Next Inpact est un journal spécialisé dans le numérique, le droit du numérique, et les nouvelles technologies en général. J'y ai un compte, un abonnement, tout ça.
Depuis quelques mois, le site web ne fonctionnait plus correctement avec mon Firefox :
Les cookies sont bien autorisés par uMatrix. Je n'ai pas de filtrage actif sur Next Inpact. J'ai tenté de désactiver toutes mes extensions : ça ne fonctionne pas mieux.
En revanche, tout fonctionne bien depuis un profil Firefox vierge. J'arrive à la conclusion qu'un de mes règlages dans about:config doit foutre la grouille… Identifier lequel s'avère être compliqué.
Au final, j'ai « autorisé » explicitement « nextinpact.com », « www.nextinpact.com », « compte.nextinpact.com » et « api-v1.nextinpact.com » à déposer des cookies "permanents" (qui ne seront pas supprimés à la fin de la session c'est-à-dire à la fermeture de Firefox ‒ car j'ai configuré Firefox pour ce faire) depuis le gestionnaire de cookies de Firefox (Édition, Préférences, Vie privée et sécurité, bouton « Gérer les permissions… » dans la rubrique « Cookies et données de site »). Je ne comprends pas pour quoi cela fonctionne, mais ça fonctionne.
Un script JavaScript pour Greasemonkey (extension Firefox) qui permet de ne pas lire automatiquement la vidéo située sur la page de présentation d'une chaîne YouTube (exemple) lorsque l'on vient du moteur de recherche (la vidéo n'est pas jouée automatiquement quand on arrive directement sur la chaîne). Comme c'est reposant de ne pas se faire emmerder !
À une époque, il fallait faire taire les contenus Flash avec une extension puis avec le paramètre « plugins.click_to_play ». Ensuite, il a fallu tordre le cou aux vidéos natives HTML5 avec une extension puis avec le paramètre « media.autoplay.enabled ». Voir mon historique en la matière. Maintenant, c'est au tour des vidéos HTML5 dont la lecture est forcée avec un bout de JavaScript. Éternel recommencement… et ça fait bien chier…
Un script JavaScript pour Greasemonkey (extension Firefox) qui permet de dégager, pour de vrai, la popup « connectez-vous » de YouTube. Contrairement aux règles de filtrage pour uBlock que l'on trouve partout, le champ de recherche reste fonctionnel et la vidéo n'est pas mise en pause.
Quand on refuse les cookies (et que l'on n'a pas de compte Google YouTube), ce script est indispensable, sinon la lecture de chaque vidéo est interrompue après quelques secondes pour tenter d'afficher la popup et il faut alors cliquer à nouveau sur la vidéo pour que la lecture reprenne. De même, les liens contenant une estampille temporelle (exemple : « watch?v=XXXXXXXXX&t=300 ») ne fonctionnent plus : le site tente de charger sa popup… on clique pour lire la vidéo… qui reprend du début (au lieu de commencer depuis l'estampille temporelle).
Pendant qu'il m'emmerdait avec cette popup, j'ai bien pensé à ne plus utiliser YouTube, le dernier service Google dont je n'arrive pas à me séparer. Mais pour aller où ?
La communauté PeerTube ? Il y a des moteurs de recherche comme Sepia Search et PeerTube index. L'impossibilité de reprendre la lecture d'une vidéo après plusieurs heures me gonfle vraiment. Mais surtout, c'est l'absence de contenus qui me retient de franchir le pas. J'utilise YouTube pour regarder des conneries, pas pour déprimer sur l'état du monde. Or, sur ce point, PeerTube est à YouTube ce que les cinémas Utopia sont au cinéma ou ce que les Mutins de Pangée sont à Netflix ou la FNAC : on y trouve du contenu chiant qui donne à réfléchir ou des contenus niais de bobo gauchiste (genre X est un immigré unijambiste (une mine antipersonnel…) qui se fait tabasser par les flics dans une manif' écolo ; Y, jolie jeune femme, se prend d'amour pour lui, et, ensemble, ils vont changer le monde et pratiquer le sexe féministe). Ça va deux minutes.
J'ai pensé à utiliser une instance Invidious (interface qui fait le relai entre des utilisateurs et YouTube). L'extension Firefox Privacy Redirect permet de rediriger automatiquement toute requête YouTube (ou Twitter ou Google Maps) vers une ou plusieurs instances Invidious. Sauf que :
Bref, rester sur YouTube avec un script Greasemonkey reste le moins mauvais choix. Jusqu'à quand ?
Au taff, on monte un lien fibre 1 Gbps entre deux switchs. Fibre fraîchement posée dans les murs (dans le faux plafond, pour être précis), donc OM2/3.
Pourquoi 1 Gbps seulement ? Car l'une des extrémités est un switch HP 5120 dont les 4 ports fibre de base prennent en charge la norme SFP mais pas la norme SFP+ qui permet, elle, de monter en 10 Gbps.
J'apprécie également que ces ports soient dit « combo » : un tel port fibre fonctionne si le port cuivre auquel il est associé est libre. Le port 49 forme une paire avec le 46, le 52 avec le 47, le 50 avec le 48, etc. Si le port cuivre 48 est utilisé, alors le port SFP 50 est inutilisable : le dernier port « up » remporte la partie et évince l'autre. Amusant, non ?
Notons que nous avons essayé de monter à 10 Gbps en utilisant les fibres qui ont été installées dans les murs il y a 15 ans. OM1 ? Monomode ? Nous n'avons pas retrouvé les spécifications. Ce qui est sûr, c'est que ça ne fonctionne pas.
Plusieurs jours après, je reviens avec un module permettant l'ajout de deux ports SFP+ au HP 5120. Je réutilise les mêmes jarretières et la même fibre optique dans les murs. On monte en 10 Gbps. Mais, au bout d'une minute, la liaison cesse de fonctionner.
Éteindre / allumer le port (shutdown / undo shutdown), vérifier l'absence d'une boucle réseau, acter la désactivation de STP, retirer / remettre les transceivers à chaque extrémité, lire le journal des switchs : aucune des vérifications basiques fonctionnent.
Mon collègue armé de plus de 10 ans d'expérience dans le domaine me dit qu'il faut envoyer promener la théorie, ne pas chercher à comprendre et construire un nouveau circuit. Je pose donc deux jarretières neuves, je prends deux nouvelles fibres dans le tronçon mural, je prends deux autres transceivers dans le pot commun, etc. Et… Ça marche. Depuis plusieurs jours.
Ho, bien sûr, on peut expliquer ça : peut-être qu'un transceiver est mort, prématurément ou non (absence de traçabilité, c'est possible qu'il ait déjà servi) ou peut-être que j'ai mis une poussière sur une fibre quand j'ai installé les transceivers 10 Gbps (sisi, il existe des opérations de nettoyage des extrémités d'une fibre optique) ou peut-être que la chaleur, couplée à une torsion aux extrémités de la fibre, a fait que les deux transceivers n'étaient plus alignés (ainsi, la lumière ne parvenait plus jusqu'à l'autre transceiver). On aurait pu prendre le temps de creuser, mais, parfois, la recherche de l'origine d'un problème prend beaucoup plus de temps que la mise en œuvre de son contournement (cf. les ninjas de l'informatique ne peuvent pas tout).
Ce genre de situations me laisse perplexe : le résultat est atteint, mais c'est l'échec d'un raisonnement et de la méthode scientifique (car on change plusieurs éléments d'un coup) voire l'échec de l'ingénieur (son but est de comprendre, or, là, tout lui échappe). Comme quoi, même les humains payés pour l'être ne sont pas si rationnels ni compétents que cela. Comme d'hab, il faut détecter quand quelque chose sera impossible ou chronophage et contourner.
J'suis mal en campagne, et mal en ville, peut-être un p'tit peu trop fragile. Allô maman bobo.
[…]
Moi je voulais les sorties de port à la voile, la nuit barrer les étoiles. Moi les chevaux le révolver et le chapeau clown, la belle Peggy du saloon.
J'suis mal en homme dur, et mal en p'tit cœur, peut-être un p'tit peu trop rêveur. Allô maman bobo.
Énorme +1. Qui suis-je ? Trop attentionné et pas assez. Trop sensible et pas assez. Trop concerné / impliqué et pas assez. Trop pragmatique et trop rêveur. Comment se trouver ? Comment gérer ses contradictions ? Comment trouver sa place ? Vastes questions pour tant de tristesse. :'(
Un collègue répète souvent la phrase « il faut que tout change pour que rien change » à propos de la politique politicienne, des décisions circulaires (qui tournent en rond) de nos chefs, ou des comportements individuels tout en sourçant le film Le Guépard.
Forcément, j'ai jeté un œil, et le dialogue est encore plus savoureux :
‒ Un Falconeri doit être avec nous, pour le Roi !
‒ Pour le Roi d'accord, mais lequel ? Mais quel roi ? Est-ce que tu peux me le dire ? Pour celui qui veut faire l'unité de l'Italie [ Ferninand ], oui. Mais le petit François [ François II ], Dieu le bénisse, non, mon oncle, non.
‒ Alors, tu t'imagines que le roi Piémontais qu'on appelle l'honnête homme vaut vraiment mieux que l'autre ? Le patois de Turin à la place du Napolitain, c'est tout.
‒ Tu préférerais peut-être la République de Don Peppino Mazzini ?
‒ Bah
‒ Crois-moi mon petit oncle : si nous ne nous mêlons pas de cette affaire, ils vont nous fabriquer la République en deux temps trois mouvements. Si nous voulons que tout reste pareil, il faut que nous changions tout. J'espère que tu me comprends ?
Pour joindre / fusionner plusieurs fichiers PDF en un seul fichier, j'utilise pdftk (paquet logiciel du même nom dans Debian) : pdftk 1.pdf 2.pdf 3.pdf cat output 123.pdf.
Chez moi, pdfjoin / pdfjam fonctionnent jamais : au-delà du premier PDF, les PDF suivants sont orientés selon le format paysage alors qu'ils sont conçus au format portrait, comme le premier document…
Tout devient tellement chaotique que les gens ressentent ce besoin, réclament de l'autorité.
Ha bah oui, bien sûr, c'est une évidence.
Ce qui est bien avec ce genre de propos, c'est que ça marche avec tout. Exemple : les Français réclament une sodomie par semaine. Les Françaises réclament un fist-fuck par mois.
L’ancien chef d’Etat major des armées, qui avait démissionné avec fracas au début du quinquennat d’Emmanuel Macron, est inquiet.
Rappel des faits : il a été poussé à la démission par Macron, son chef, après s'être opposé à lui, devant une commission de l'Assemblée et en des termes fleuris, au sujet du montant du budget 2017 de la Défonce. Macron lui répondra d'ailleurs « Je suis votre chef » devant tout le gratin de la Défonce (source). En plus d'être discrédité devant ses troufions, il n'avait pas d'autre choix que de démissionner, Macron l'aurait viré de toute façon (source).
En gros, Pierre de Villiers a été viré pour insubordination, parce qu'il a refusé l'autorité dont il prétend aujourd'hui que c'est ce que les Français réclament.
Soit il n'est pas français, soit il se moque du monde, soit il est incohérent (syndrome faites ce que je dis, pas ce que je fais). La première hypothèse est fausse. Dans les deux autres cas, une seule réponse s'impose : « casse-toi d'là, pov' con », les Français n'ont pas besoin d'un nouveau De Gaulle (on rappellera que ce mec, DG, a aussi bien bien bien foiré : tranquillement planqué à Londres, coup d'État, Constitution à son image qui nous pose problème aujourd'hui ‒ exécutif fort, institutions faibles et inefficace, personnification-starification du président, etc. ‒, le SAC, la non-gestion de la guerre d'Algérie et plus largement de la colonisation française, mai 68, etc.).
Résumé : impossible de se connecter à un lecteur réseau winwin / CIFS depuis une machine Ubuntu 20.04 ? Ajouter client min protocol=NT1 dans la section [global] du fichier /etc/samba/smb.conf. Attention : cela pose des problèmes de sécurité !
Depuis le gestionnaire de fichiers d'une machine Ubuntu 20.04, impossible de se connecter à un serveur CIFS. L'erreur suivante s'affiche avant même de demander l'identifiant + domaine + mot de passe :
Oups ! Quelque chose s'est mal passé.
Message d'erreur non géré : impossible de monter le partage Windows : Le logiciel a provoqué l'abandon de la connexion.
Si l'on essaye de s'y connecter avec le logiciel smbclient, on obtient l'erreur suivante avant toute demande d'identifiant :
smbclient //serveur.monorganisation.exemple/nom_lecteur -u <identifiant>
protocol negotiation failed: NT_STATUS_CONNECTION_DISCONNECTED
En revanche, cela fonctionne toujours avec mount -t cifs //serveur.monorganisation.exemple/nom_lecteur /point/de/montage -o username=<identifiant>,vers=1.0.
Il ne faut pas aller chercher bien loin : mount d'une part et smbclient et gvfs d'autre part ne doivent pas utiliser le même sous-système, et l'antique version 1 du protocle SMB doit être désactivée dans l'un d'eux. C'est en effet le cas depuis la version 4.11 de Samba… Qui est justement celle distribuée dans Ubuntu 20.04. On notera que le problème se présentera aux Debianeux avec Bullseye. ;)
Un contournement est indiqué dans le journal des changements de Samba : il faut ajouter client min protocol=NT1 dans la section [global] du fichier /etc/samba/smb.conf. Pas besoin de redémarrer quoi que ce soit : la modification est immédiatement effective.
SMBv1 est dépréciée + elle pose des problèmes de sécurité, donc il faut la réactiver avec précaution. Dans notre cas, c'est en attendant la fin de la migration vers notre nouveau NAS.
Depuis quelques mois, l'interface des automates de La Poste (qui permettent de peser / affranchir le courrier et acheter divers produits) a changé. Désormais, à la fin, l'usager est invité à noter le service à l'aide de pictogrammes-smyleys colorés que j'ai toujours trouvé infantilisants.
Noter, encore et toujours, comme si c'était un but en soi. Noter, évaluer, dormir. Frénétiquement. Il faut que le jugement fuse et puisse être exprimé sans contrainte. Point de démarches compliquées, il faut que la sanction tombe rapidement.
Oui, la collecte de retours utilisateurs est intéressante. Néanmoins, cette façon de faire, à l'aide de pictogrammes simplistes, restreint fortement l'utilité desdits retours : La Poste ne saura pas ce qui fonde le mécontentement de l'usager. Qu'est-ce qui n'a pas plu ? L'interface de l'automate ? Une fonctionnalité manquante ? Le fait de devoir utiliser un automate ? Le prix d'un produit ? L'usager a-t-il allumé son cerveau en utilisant l'automate ? A-t-il pris du recul sur son coup de sang ? Bien sûr que non. Rien ne pourra être retiré des retours. Mais, c'est cool, le prolo aura pu s'exprimer, c'est tout ce qui compte, on l'aura soulagé, ouf. Le système ne sera pas amélioré, mais le prolo aura l'impression d'être écouté.
Ce genre de démarche amoindrit la pensée et les processus de contestation. Cela stérilise la critique et l'auto-critique. Simplement car c'est moins engageant de noter avec des smyleys que d'envoyer un courrier de réclamation (tu notes, tu passes à autre chose). On oublie comment contester (comment construire un argumentaire et l'articuler, logique d'une argumentation, forme, etc.) au profit du simpliste « moi content / pas content ». C'est comme cela que l'on éteint des citoyens.
Il sufffit de patienter une petite dizaines de secondes pour que la demande de notation disparaisse. Je vais continuer à faire ça. Ne surtout pas jouer à ce jeu malsain.
La girafe a un long cou car elle prend du recul. Elle a de grandes pattes car elle est orientée résultats, elle veut trouver une solution.
2020, """"formation"""" obligatoire « Bien vivre le télétravail ».
Rigole pas, c'est avec ton pognon qu'on finance ce genre de bullshit. Les charlatans ne perdent pas de temps, et les gaspilleurs qui les financent non plus.
Tu vas me dire « tu prends une phrase dans une formation de plusieurs heures ».
Même pas. Y'avait aussi :
Je suis bien content d'avoir échappé à c'te connerie même si, au fond, je suis l'idiot : mieux vaut être payé à (faire semblant d')écouter c'te blague qu'à travailler, ça demande moins d'efforts.
Allez, je vais être gentil : j'imagine que cette """"formation"""" est déjà plus acceptable (en termes de gaspillage de pognon) que les """"formations"""" sophrologie et autres gabegie en matière de formation professionnelle.
Ça me rappelle l'habilitation électrique ou une formation sur les méthodes agiles : bullshit over bullshit for more bullshitness.
Tant qu'il y aura des gens pour trouver ces """"formations"""" « pas si mal »…