J'ai enfin réglé mon problème d'alimentation de mon SheevaPlug. Effectivement, ça faisait maintenant plus de deux ans que j'avais procédé en urgence à un changement un peu sommaire de l'alimentation en utilisant une solution que d'aucuns jugeraient "overkill"...

Après avoir pu regler les affaires courantes de ma todo-list, le sujet de réparer cette alimentation est revenu en haut de mes priorités. La solution de l'alimentation ATX a fait largement ses preuves: je n'ai eu aucun problème pendant plus de deux ans avec. Elle faisait juste un peu de bruit (le ventilateur qui refroidit les condensateurs et le bobinage des transformateurs) et était discrètement rangée dans un meuble dédié (non, ce n'est pas un rack ou toute sorte de chose qui s'y rapporte). Mais, il fallait bien trouver une solution plus élégante au problème.

J'ai donc acquis auprès de NewIt, une nouvelle alimentation dédiée, destinée à remplacer l'ancienne dans le même emplacement. Normalement, sur ce nouveau montage, les condensateurs qui posent problèmes ont été remplacés par des éléments capables de supporter plus facilement le voltage et la chaleur propagée. De plus, l'alimentation dispose d'une couche de plastique molle collée en dessous. C'est censé favoriser la dissipation thermique. Sur ce sujet d'ailleurs, la coque de protection qui était complètement isolante a été remplacée par une simple feuille de plastique épais qu'on plie pour l'adapter au logement de l'alimentation. Je pense que ça doit permettre de gagner quelques degrés car l'intérieur est moins confiné qu'avant.

Voici quelques photos pour voir à quoi ça ressemble:

Photo de la nouvelle alimentation NewIt pour Sheevaplug Photo de la nouvelle alimentation NewIt pour Sheevaplug

L'alimentation est livrée avec la pièce en plastique pour remplacer l'isolation thermique du circuit.

La doc de montage est disponible directement chez newIt.

Après un peu d'efforts de montage (le support de prise de courant est difficile à clipser correctement dans son logement), la machine redevient de dimension réduite. Elle tient à nouveau dans la main. En fonctionnement, elle ne fait plus de bruit du tout.

Photo du sheevaplug complètement fonctionnel avec sa nouvelle alimentation

Espérons que cette nouvelle alimentation fera preuve d'une plus grande longévité. Dans tous les cas, cet article me permettra de faire le point dans le temps, il reste une trace pour que je n'oublie pas cet évènement...

Rendez-vous, le plus tard possible sur ce sujet !

Posted lun. 01 sept. 2014 19:38:21

Introduction

I have used ikiwiki since four years as my blog engine. I like it very much and really enjoy the fact that everything is stored in a git repository. There are lots of plugins to deal with different sorts of directives to publish content more easily. With Ikiwiki I've been more focused on content than on publishing processus.

But there is one point I needed to change and here it is how I achieve it !

What's the problem ?

I really wanted to have a sidebar as a navigation tool in the blog. For me, a true sidebar needs a calendar, or as I said: a way to navigate in the blog using time.

But this has the great drawback to launch a rebuilding of the whole site... I took time to understand what was the problem. The symptom was: whatever was published or modified in the blog part of the site, the blog was always completely rebuilding itself.

I modified the sidebar content to follow advices founded on Ikiwiki web site. But it changed nothing. Then I started to understand: whenever an article is published, the calendar needs to be rebuilded. As there is a calendar directive in sidebar.mdwn, the sidebar is rebuilt, then eveything in the blog is rebuilt as everything in the blog depends on sidebar !

Until now, it was not really a problem because all of my articles were very light with very few pictures. Since I post the Ireland article, the site took about 45 minutres to rebuild completely on my Sheevaplug. It was time to find a solution.

How I solved it

I am not a Perl developer anymore. I used to learn Perl more than ten years ago but now, I have learned Python and I don't want to rediscover the crap of Perl semantic and grammar. My workaround could not been a Perl code. So what about something in HTML+Javascript thing ? After all, ikiwiki is an html compiler !

So I just build a Javascript dynamic calendar. The code is charged everytime you load a page on the blog. It uses the CSS class of the original ikiwiki calendar plugin so there will be few changes on the rendering of the calendar. There is a main object called ikiwiki_cal which just build itself in the render method. It is initialized with the date of the current day or the month of the archive page if you are in a archive page (URL likes ../archives/2014/08). When you click on the next or prev month, calendar content is dynamically changed without reloading the page where you are.

On loading, an XMLHTTPRequest is made on a file named calendar.json. This file stores the list of all the articles published on the blog in a JSON format. With articles come date of publication (like printed in meta directives) and URL of the article. This JSON is parsed when you load the page and its content is used to build the calendar. When an article has been published during the current calendar month, the day by which it has been published becomes a link to the article.

It was my first Javascript/AJAX/JSON code. I just can say that some things were really fast to code and others (AJAX) was a real pain in the ass. To be short, I hate the grammar to declare a class (anonymous functions) and I took some time to really understand callback function mechanism...

Here is the code:

/*
 * Javascript Calendar for Ikiwiki
 * You have to define a JSON file that grab all of the articles date
 * to populate the calendar.
 * The goal of this code is to make ikiwiki sidebar plugin compatible with calendar
 *
 * Copyright 2014 Médéric RIBREUX
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

Date.prototype.isLeap = function() {
    var year = this.getFullYear();
    return year % 400 === 0 || ( year % 4 === 0 && year % 100 !== 0);
}

Date.prototype.getDaysInMonth = function() {
    this.daysInMonth = [31,28,31,30,31,30,31,31,30,31,30,31][this.getMonth()]
    // Feb on leap year
    if (this.isLeap() && this.getMonth() == 1)  {
        this.daysInMonth += 1;
    }
    return this.daysInMonth;
}

//var articles;

var ikiwiki_cal = {
    init: function(json_data) {
    this.days = ["Monday" ,"Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
    this.months = ["January","February","March","April","May","June","July","August","September","October","November","December"];

    //get URL:
    var url = window.location;
    this.month = this.year = null;
    // get arguments of the URL
    if (url.pathname.length > 1) {
        slashes = url.pathname.substr(1)
        if (slashes.charAt(slashes.length-1) == '/')
        slashes = slashes.substr(0,slashes.length-1);

        url_elem = slashes.split('/');
        if (url_elem.length > 1) {
        if (url_elem.indexOf("archives")) {
            month = url_elem[url_elem.indexOf("archives")+2];
            year = url_elem[url_elem.indexOf("archives")+1];
            if (!(isNaN(parseInt(month)) || isNaN(parseInt(year)))) {
            this.month = parseInt(month)-1;
            this.year = parseInt(year);
            }
        }
        }
        }

    if (this.month == null || this.year == null) {
        var today = new Date();
        this.month = today.getMonth();
        this.year = today.getFullYear();
    }

    this.element = document.getElementById('calendar');

    this.articles = json_data;

    },
    change_date: function(month, year) {
    this.month = month;
    this.year = year;
    this.render();
    },
    prev_month: function() {
    if (this.month == 0) {
        this.month = 11;
        this.year--;
    } else
        this.month--;

    this.render();
    },
    next_month: function() {
    if (this.month == 11) {
        this.month = 0;
        this.year++;
    } else
        this.month++;

    this.render();
    },
    articles_from_month: function(json_articles) {
    var lst = [];
    for(var i = 0; i < json_articles.length; i++) {
        var dt = new Date(Date.parse(json_articles[i].date))
        if (dt.getFullYear() == this.year && dt.getMonth() == this.month) {
        lst.push(json_articles[i]);
        }
        }

    return lst;

    },

    render: function() {
    var html = ''
    var articles = this.articles_from_month(this.articles);

    // what is the previous month ?
    var m = (this.month+1).toString();
    if (m.length == 1)
        m = "0"+m;
    if (this.month == 0) {
        pm = 12;
        py = this.year-1;
    } else
        pm = this.month

    // What's the next month ?
    if (this.month == 11) {
        nm = 1;
        ny = this.year+1;
    } else
        nm = this.month+2

    if (pm.toString().length == 1)
        pm = "0"+pm.toString();

    if (nm.toString().length == 1)
        nm = "0"+nm.toString();

    // header
    html = '<table class="month-calendar">\n'
    html += '<tbody><tr>\n'
    html += '<th class="month-calendar-arrow">'
    html += '<a href="#" onclick="ikiwiki_cal.prev_month(); return false;" title="' + this.months[parseInt(pm)-1] +'">←'
    html += '</a></th>'
    html += '<th class="month-calendar-head" colspan="5">'
    html += '<a href="/blog/archives/' + this.year + '/' + m + '/" title="' + this.months[this.month]+'">'+this.months[this.month].substring(0,3)+' '+this.year+'</a></th>'
    html += '<th class="month-calendar-arrow">'
    html += '<a href="#" onclick="ikiwiki_cal.next_month(); return false;" title="' + this.months[parseInt(nm)-1] +'">→'
    html += '</a></th>'
    html += '</tr>'

    // days header:
    html += '<tr>'
    for (var i=0; i < this.days.length; i++) {
        html += '<th class="month-calendar-day-head '+this.days[i]+'" title="'+this.days[i]+'">'+this.days[i].substring(0,1)+'</th>\n'
    }
        html +='</tr><tr>'

    // days body:
    var first_day_date = new Date(this.year, this.month, 1, 0, 0, 0);
    var first_day_num = first_day_date.getDay() == 0 ? 6 : first_day_date.getDay()-1;
    var last_day = first_day_date.getDaysInMonth();
    var last_day_date = new Date(this.year, this.month, last_day, 0, 0, 0, 0);
    var last_day_num = last_day_date.getDay() == 0 ? 6 : last_day_date.getDay()-1;

    for (var i = 0; i < (first_day_num+last_day+(6-last_day_num)); i++) {
        if (i > 1 && i % 7 == 0 && i < (first_day_num + last_day)) {

        html += '</tr><tr>';
        }
        if (i<first_day_num || i>(first_day_num + last_day -1)) {
        html += '<td class="month-calendar-day-noday '+ this.days[i%7] + '">&nbsp;</td>';
        }
        else {
        var article = null;
        for(var j = 0; j < articles.length; j++) {
            var art = articles[j];
            var dt = new Date(Date.parse(art.date))

            if (dt.getDate() == (i-first_day_num+1)) {
            article = art;
            }
        }
        if (article != null)
            html += '<td class="month-calendar-day-link '+ this.days[i%7] + '"><a href="'+article.url+'" title="'+article.title+'">'+(i-first_day_num+1).toString()+'</a></td>';
        else
            html += '<td class="month-calendar-day-nolink '+ this.days[i%7] + '">'+(i-first_day_num+1).toString()+'</td>';


        }

    }
    html +='</tr>';


    // End:

    html += '</tbody></table>\n'

    this.element.innerHTML = html;
    }
}

// JSON Loading function:
function json_request(callback) {
    var req = new XMLHttpRequest();

    req.onreadystatechange = function() {
        if (req.readyState == 4 && (req.status == 200 || req.status == 0)) {
        callback(JSON.parse(req.responseText));
        }
    };

    req.open("GET", "/calendar.json", true);
    req.send();
}

// Callback function to deal with Ajax response:
function json_do(jsondata) {
    //articles = jsondata;
    ikiwiki_cal.init(jsondata);
    ikiwiki_cal.render();
}

// Function to go to a precise month
function go_to_month(month, year) {
    ikiwiki_cal.change_date(month, year);
}

// Main code
window.addEventListener('load', function() {

    // Verify if we have a calendar:
    var calendar = document.getElementById("calendar");
    if (calendar != null) {
    // Just call the JSON request and create our Calendar
    json_request(json_do);
    }
});

How to deploy it ?

First, you have a Javascript file named calendar.js. Put it at the root of your repository. Ikiwiki just compile it by doing nothing. So it will be available at the root of your website.

The second step is to change some templates. The first one is the page template. Because of HTML scrutinization from Ikiwiki, you can't add a '''''' inside a page. Ikiwiki just erase it. The only way I've found to put this balise was to edit the page template. Just create a "templates" directory at the root of your repository and copy the file /usr/share/ikiwiki/templates/page.tmpl inside. Edit the file: I just add a script balise to charge calendar.js in the header only if it is in the blog.

Then you have to modify the archives templates. Just copy /usr/share/ikiwiki/templates/calendarmonth.tmpl in your repository templates directory. I've just modified the template to show a better sidebar for archive pages. Remember that the Javascript code is able to show the good calendar dynamically. I've faced a problem: whatever change you make in the calendar templates, they are never propagated to the corresponding .mdwn files under your archives directory. The solution is quite radical: delete your archives directory and then launch ikiwiki-calendar to regenerate it.

Finally, you have the JSON file generation. I just modified a little bit the git hooks from ikiwiki. I've written a simple Shell script to grep into mdwn files and get the meta directives for title and date of the articles in the blog. Once the code is able to generate JSON content, the git part comes to place.

For those who don't know, there is a special git hook in the bare repository of your ikiwiki instance. It is generated by Ikiwiki when you launch a rebuild of your site. The name of the hook is the content of the git_wrapper variable of your ikiwiki setup file. The content of this hook is a C compiled binary which just update the source directory of your ikiwiki instance and launch a refresh of the site. I just modified the name of the hook (post-update by default) to post-update.ikiwiki and replace post-update file with the content of my JSON generation shell script. At the beginning of this script, it just launches post-update.ikiwiki. When the bare repository is refreshed (pushed in), the script generates calendar.json in the web server directory. So calendar.json is not in any ikiwiki repository, just in the web server served directory.

Here is the code of this hook:

#!/bin/bash

# Script to generate calendar.json file

# make the update by ikiwiki
/path/to/git/bare/repository/post-update.ikiwiki


# global variables
echo "Compiling calendar.json..."
srcdir="/your_ikiwiki_src_repository"
destfile="/var/www/calendar.json"
blogrep="$srcdir/blog"

# Get all the mdwn in blog directory
lst_files=$(find $blogrep -type f \( -iname '*.mdwn' ! -regex '.*/archives/.*' \))

echo "[" > $destfile

# For each mdwn, get:
for file in $lst_files
do
    meta=$(grep '\[\[!meta' $file)
    url=${file#$srcdir}
    url=${url%.mdwn}
    title=""
    echo $meta | grep -q "title"
    if [ "$?" -eq "0" ]
    then
    title=$(echo $meta | sed 's/.*title="\(*\)".*/\1/')
    fi

    date=$(echo $meta | sed 's/.*date="\(*\)".*/\1/')
    date=${date:0:10}

    if [ "${#title}" -ne "0" ]
    then
    cat >> $destfile <<EOF
  { "url" : "$url",
    "title" : "$title",
    "date" : "$date" },
EOF

    fi


done

cat >> $destfile <<EOF
  { "url" : "/",
    "title" : "Start",
    "date" : "1977-01-01" }
]
EOF



exit 0

Conclusion

As a conclusion, this workaround has been very hard to elaborate. I just have to understand very well the internal mechanism of Ikiwiki (for the hook part). Then I just have to learn Javascript a little bit more than what I knew before. But I am proud to find a way to circumvent this real problem in less than a day of work !

Posted sam. 06 sept. 2014 20:38:21

L'article qui suit est ma traduction d'un article du blog de Lennart Poettering, l'auteur de systemd. J'ai réalisé la traduction pour en faire une dépêche sur linuxfr. Je la stocke également ici au cas où et pour me souvenir de ce travail. Je publierai également les autres traductions dans des articles de blog distincts.

Voici un lien vers l'article original.

Trois niveaux de "Off"

Dans Systemd, il y a trois niveaux pour arrêter un service (ou d'autres Units). Voyons voir quels sont-ils:

  1. Vous pouvez arrêter un service, cela termine simplement l'instance du service qui fonctionne et cela ne fait que ça. Si pour d'autres raisons d'activation (telles que le lancement manuel, l'activation par socket, par le bus, par le boot du système ou par un interrupteur machine) le service est demandé après cet arrêt, il sera à nouveau lancé. Arrêter un service est donc une opération simple, temporaire et superficielle. Voici un exemple avec le service NTP:

    $ systemctl stop ntpd.service
    

    C'est globalement équivalent à la commande traditionnelle disponible sur la plupart des systèmes basés sur SysV:

    $ service ntpd stop
    

    Dans les faits, sur Fedora 15, si vous exécutez cette dernière commande, elle sera convertie de manière transparente en la première.

  2. Vous pouvez désactiver un service. Cela détache le service de ses déclencheurs. Cela signifie que, selon votre service, il ne sera plus activé au démarrage de la machine, par socket, par bus ou par un interrupteur machine (ou tout autre déclencheur qui s'y applique). Néanmoins, vous pouvez quand même le lancer manuellement si vous le désirez. S'il y a déjà une instance du service démarrée, désactiver un service ne l'arrêtera pas. Voici un exemple de comment désactiver un service:

    $ systemctl disable ntpd.service
    

    Sur les systèmes traditionnels Fedora, c'est globalement l'équivalent de la commande qui suit:

    $ chkconfig ntpd off
    

    Ici également, sur Fedora 15, la commande précédente sera convertie de manière transparente en la première, si nécessaire.

    Bien souvent, vous souhaitez combiner arrêt et désactivation du service, de manière à supprimer l'instance courante et faire en sorte que le service ne soit pas relancé (sauf par action manuelle):

    $ systemctl disable ntpd.service
    $ systemctl stop ntpd.service
    

    Les commandes précédentes sont lancées par exemple lors de la désinstallation du paquet d'un service systemd sur Fedora.

    Désactiver un service est un changement permanent; tant que vous ne le réactivez pas, ce changement sera conservé même après le redémarrage de la machine.

  3. Vous pouvez masquer un service. C'est comme le désactiver mais en plus fort. Cela fait en sorte d'être sûr que le service ne sera plus jamais démarré automatiquement mais assure également qu'un service ne puisse plus être démarré manuellement. C'est une fonctionnalité cachée dans systemd puisqu'elle n'est pas utilisé couramment et quelle peut être mal interprétée par un utilisateur. Néanmoins, voilà comment vous devriez procéder:

    $ ln -s /dev/null /etc/systemd/system/ntpd.service
    $ systemctl daemon-reload
    

    En créant un lien symbolique d'un fichier de service vers /dev/null, vous indiquez à systemd de ne jamais démarrer le service en question et vous bloquez complètement son exécution. Les fichiers d'Unit stockés dans /etc/systemd.system remplacent ceux qui sont situés dans /lib/systemd/system qui portent le même nom. Le premier répertoire est relève de l'administrateur système, le second relève du responsable de paquet. En installant un lien symbolique dans /etc/systemd/system/ntpd.service, vous pouvez être sûr que systemd ne lira jamais le fichier de service situé dans /lib/systemd/system/ntpd.service.

    Systemd reconnaîtra les Units liées vers /dev/null et les indiquera comme étant masquées. Si vous essayez de lancer de tels services manuellement (via systemctl start par exemple), cela se terminera par une erreur.

    Un mécanisme similaire n'existe pas (officiellement) pour les systèmes SysV. Néanmoins, il existe quelques trucs officieux comme éditer le script d'init et en indiquant un exit 0 au début ou bien encore en enlevant le bit exécutable. Néanmoins ces solutions présentent différents inconvénients comme le fait qu'elles interfèrent avec ce que le responsable de paquet a prévu.

    Masquer un service est un changement permanent, un peu comme désactiver un service.

    Note du traducteur: il existe maintenant les commandes systemctl mask et systemctl unmask pour gérer ces opérations en une seule ligne.

Maintenant que nous savons comment gérer l'arrêt de services sur ces trois niveaux, il reste encore une question: commet les réactiver. Eh bien, c'est assez similaire. Utilisez systemctl start pour revenir en arrière de la commande systemctl stop. Utilisez systemctl enable pour revenir en arrière de la commande systemctl disable et utilisez rm pour supprimer les liens symobliques.

C'est tout pour aujourd'hui, Merci de votre attention !

Posted mar. 30 sept. 2014 19:34:47

L'article qui suit est ma traduction d'un article du blog de Lennart Poettering, l'auteur de systemd. J'ai réalisé la traduction pour en faire une dépêche sur linuxfr. Je la stocke également ici au cas où et pour me souvenir de ce travail. Je publierai également les autres traductions dans des articles de blog distincts.

Voici un lien vers l'article original.

Tuer des services

Tuer un service, c'est simple non ? Ou pas ?

Bien entendu, tant que votre démon est constitué d'un seul processus, c'est généralement vrai. Vous tapez killall rsyslogd et le démon syslog n'est plus. Néanmoins, c'est un plus sale de faire ainsi étant donné que cela tuera tous les démons qui s'appellent ainsi, incluant ceux qu'un utilisateur malchanceux aurait nommé ainsi par accident. Une version plus correcte aurait été de lire le fichier .pid, c'est-à-dire kill \cat /var/run/syslogd.pid``. Cela nous avance un peu mais néanmoins est-ce vraiment ce que nous souhaitons ?

Dans la majorité des cas en fait, cela ne se passera pas ainsi. Considérons des services tels qu'Apache, crond ou atd qui, dans leur fonctionnement courant, engendrent des processus fils. Généralement, il s'agit de processus fils configurables par l'utilisateur tels que des tâches cron ou at ou encore des scripts CGI, y compris des serveurs d'application complets. Si vous tuez le processus principal Apache/crond/atd cela pourra ou non tuer l'ensemble des processus fils également et c'est à chacun de ces processus de savoir s'ils veulent continuer à tourner ou s'ils doivent s'arrêter. Dans la pratique, cela signifie que mettre fin à Apache pourrait très bien laisser ces scripts CGI fonctionner, réaffectés comme enfants du processus init, ce qui est difficile à tracer.

Systemd vient à la rescousse: avec systemctl kill, vous pouvez facilement envoyer un signal à tous les processus d'un service. Par exemple:

# systemctl kill crond.service

Cela assurera que SIGTERM est envoyé à tous les processus du service crond, pas uniquement au processus principal. Bien entendu, vous pouvez également envoyer un signal différent si vous le souhaitez. Par exemple, si vous êtes mal luné, vous aurez peut-être envie d'envoyer SIGKILL de cette manière:

# systemctl kill -s SIGKILL crond.service

Et voilà, le service sera complètement stoppé brutalement, peu importe combien de fois il a été forké ou qu'il essaye d'échapper à la supervision par un fork double ou par un bombardement de forks.

Parfois, vous avez juste besoin d'envoyer un signal spécifique au processus principal d'un service, par exemple par ce que vous voulez déclencher un rechargement du service par le signal SIGHUP. A la place de retrouver le fichier de PID, voici un moyen plus simple d'y parvenir:

# systemctl kill -s HUP --kill-who=main crond.service

Encore une fois, qu'y-a-t-il de nouveau et de fantaisiste dans la manière de tuer des processus avec systemd ? Eh bien, pour la première fois sous Linux, nous pouvons le faire proprement. Les précédentes solutions dépendaient toutes de la bonne coopération des démons pour qu'ils arrêtent tous les processus qu'ils avaient engendrés lorsqu'ils devaient se terminer. Néanmoins si vous voulez employer SIGTERM ou SIGKILL, vous le faîtes parce qu'ils ne coopèrent pas correctement avec vous.

Qu'est-ce-que ça a à voir avec systemctl stop ? kill envoie directement un signal à chacun des processus situés dans le groupe; stop, pour sa part, utilise le moyen officiellement configuré pour arrêter le service, c'est-à-dire qu'il lance la commande configurée avec ExecStop= dans le fichier de service. En général, stop devrait être suffisant. kill reste la méthode forte à utiliser dans les cas ou vous ne voulez pas utiliser la commande shutdown d'un service ou bien lorsque le service est complètement en carafe.

(C'est à vous bien entendu d'indiquer ou non les noms de signaux avec le préfixe SIG avec ou sans l'option -s. Les deux fonctionnent.)

C'est assez surprenant d'être parvenu jusqu'ici sous Linux sans disposer d'un moyen efficace de tuer des services. Systemd permet pour la première fois de le faire proprement.

Posted mar. 30 sept. 2014 19:34:47

L'article qui suit est ma traduction d'un article du blog de Lennart Poettering, l'auteur de systemd. J'ai réalisé la traduction pour en faire une dépêche sur linuxfr. Je la stocke également ici au cas où et pour me souvenir de ce travail. Je publierai également les autres traductions dans des articles de blog distincts.

Voici un lien vers l'article original.

Comment puis-je convertir un script d'init SysV en fichier de service systemd ?

Traditionnellement, les services Unix et Linux (les démons) sont démarrés par des scripts d'init SysV. Il s'agit de scripts pour le shell Bourne qui résident généralement dans un répertoire tel que /etc/rc.d/init.d/ et qui, lorsqu'ils sont appelés avec un des quelques arguments standards (verbes) tels que start, stop ou restart, respectivement démarrent, arrêtent ou relancent le service en question. Pour le démarrage, cela implique généralement d'invoquer le binaire du démon qui forke un processus en tâche de fond (plus précisément, qui devient un démon). Les scripts shell ont tendance à être lents, assez lourds à lire, très verbeux et fragiles. Bien qu'ils soient immensément flexibles (après tout, ce n'est jamais que du code), certaines choses sont difficiles à réaliser avec des scripts shell comme mettre en ordre une exécution en parallèle, superviser correctement des processus ou encore simplement configurer les contextes d'exécution dans leurs moindres détails. Systemd fournit des éléments de compatibilité avec ces scripts shell mais, en raison des points négatifs invoqués précédemment, il est recommandé d'installer des fichiers de service Systemd pour l'ensemble des démons installés. De plus, en contraste avec les scripts d'init SysV qui ont besoin d'être adapté à la distribution, les fichiers de service systemd sont compatible avec n'importe quelle distribution exécutant systemd (ce qui arrive de plus en plus fréquemment ces derniers temps...). Dans ce qui suit, vous trouverez un guide succinct sur comment convertir un script d'init SysV en un fichier de service natif systemd. Dans l'idéal, les projets en amont devraient distribuer et installer des fichiers de service dans leur archives tar. Si vous avez converti avec succès un script SysV en suivant les recommandations, il serait de bon ton de soumettre un patch au projet amont. La préparation d'un tel patch sera discuté lors d'une prochaine session et il suffit de vous informer que la page de manuel de daemon(7), distribuée avec systemd contient de nombreuses informations utiles sur ce sujet.

Plongeons-nous dedans. À titre d’exemple, nous allons convertir le script d'init du démon ABRT en fichier de service systemd. ABRT est un composant standard de toute installation de Fedora et il s'agit d'un acronyme pour Automatic Bug Reporting Tool, ce qui décrit bien mieux ce dont il s'agit, à savoir, un service pour collecter les dumps de crash. Le fichier de script SysV est disponible ici.

La première étape à suivre pour convertir un tel script consiste simplement à le lire (!) et à récupérer l'information essentielle distillée tout au long de ce script volumineux. Dans la majorité des cas, le script est formé d'un ensemble de code qui est assez similaire dans tous les scripts d'init et il est généralement copié-collé d'un script à l'autre. Extrayons maintenant l'information essentielle du script ci-dessus:

  • Une chaîne de caractère qui décrit le service: "Daemon to detect crashing apps". Les commentaires de l'en-tête du script présentent plusieurs chaînes de description, certaines décrivant davantage le script d'init réalisant le démarrage du service que le service en lui-même. Les services Systemd ont également besoin d'une description et ils devraient décrire le service et non le fichier de service.
  • L'en-tête LSB[1] contient les informations de dépendance. Grâce à son architecture basée sur l'activation par socket, systemd n'a généralement pas besoin (ou un tout petit peu) de définir manuellement les dépendances. (Pour plus d'informations sur l’activation par socket lisez l'article originel du blog.) Dans ce cas, la dépendance vis-à-vis de $syslog (qui indique que arbtd a besoin d'un démon syslog) et la seule information d'intérêt. Même si l'en-tête indique une autre dépendance ($local_fs), celle-ci est redondante dans systemd car les services normaux systemd sont toujours démarrés lorsque tous les systèmes de fichiers locaux sont disponibles.
  • L'en-tête LSB suggère que ce service doit être démarré dans les runlevels 3 (multi-utilisateur) et 5 (graphique).
  • Le binaire du démon est /usr/sbin/abrtd.

Et c'est déjà tout ! Le reste de ce script de 115 lignes est simplement du code générique ou redondant: du code qui gère la synchronisation et l'ordonnancement du démarrage (du code qui gère les fichiers de lock) ou qui génère des messages d'état (le code qui appelle echo), ou simplement qui analyse les arguments (le gros bloc du case).

À partir de l'information extraite ci-dessus, nous pouvons maintenant écrire notre fichier de service systemd:

[Unit]
Description=Daemon to detect crashing apps
After=syslog.target

[Service]
ExecStart=/usr/sbin/abrtd
Type=forking

[Install]
WantedBy=multi-user.target

Un peu d'explications du contenu de ce fichier: la section [Unit] contient de l'information générique sur le service. Systemd gère des services systèmes mais également des périphériques, des points de montage, des timers, et d'autres composants du système. Le terme générique pour tous ces objets dans systemd est une Unit. La section [Unit] stocke l'information qui s'applique non seulement aux services mais également à tous les autres types d'Unit systemd. Dans notre cas, nous ajoutons les paramètre d'Unit suivants: nous ajoutons une chaîne de caractère de description et nous configurons que le démon doit être lancé après Syslog[2], de la même manière à ce qui est indiqué dans l'en-tête LSB du script d'init originel. Pour gérer cette dépendance à Syslog, nous créons une dépendance de type After= sur l'Unit systemd nommée syslog.target. Cette dernière est une Unit particulière et elle représente le nom standard pour l'implémentation de syslog dans systemd. Pour plus d'informations sur ces noms standardisés, consultez la page de manuel systemd.special(7). Notez qu'une dépendance du type After= représente seulement une suggestion d'ordre de démarrage; elle ne se traduit pas par le démarrage de syslog lorsque abrtd démarre. C'est exactement ce que nous voulons puisque arbtd fonctionne correctement, même lorsque syslog n'est pas disponible. Néanmoins, si les deux sont démarrés (c'est généralement le cas), alors l'ordre dans lequel ils sont lancés est contrôlé avec cette dépendance.

La section qui suit se nomme [Service] et elle s'occupe de l'information sur le service en lui-même. Elle contient tous les paramètres qui s'appliquent uniquement à des services et non aux autres type d'Unit systemd (points de montage, périphériques, timers, ...). Deux paramètres sont utilisés ici: ExecStart= qui stocke le chemin vers le binaire pour l'exécuter lorsque le service doit être démarré. Avec Type=, on configure la manière dont le service notifie le système d'init qu'il a terminé son démarrage. Étant donné que les démons traditionnels Unix le font en émettant un code de retour au processus parent après avoir forké et initialisé un démon en tâche de fond, nous utilisons le type forking ici. Cela indique à systemd d'attendre jusqu'à ce que le binaire de démarrage envoie un code retour et de gérer les processus qui tournent toujours après ceux du démon.

La dernière section est [Install]. Elle stocke l'information sur comment devrait être l'installation suggérée, c’est-à-dire, dans quelles circonstances et par quels déclencheurs le service devrait être démarré. Dans notre cas, nous indiquons simplement que le service doit être démarré lorsque l'Unit multi-user.target est activée. Il s'agit également d'une Unit spéciale qui prend globalement le rôle du Runlevel 3[3] de SysV. Le paramètre WantedBy= a peu d'effet sur le fonctionnement courant du démon. Il est uniquement lu par la commande systemctl enable qui est le moyen recommandé d'installer un service dans systemd. Cette commande s'assure simplement que notre service est automatiquement activé dès que l'Unit multi-user.target est requise, ce qui est le cas lors des séquences de boot normales[4].

Et c'est tout ! Nous avons maintenant un fichier de service systemd fonctionnel et simple. Pour le tester, il faut le copier dans /etc/systemd/system/abrtd.service et lancer systemctl daemon-reload. Cela permettra à systemd de prendre en compte notre fichier et dès cet instant, nous pouvons démarrer ce service en lançant: systemctl start abrtd.service. Nous pouvons en vérifier l'état grâce à systemctl status abrtd.service. Et nous pouvons arrêter le service via systemctl stop abrtd.service. Finalement, nous pouvons l'installer de manière à ce qu'il soit activé par défaut lors des prochains boots avec la commande systemctl enable abrtd.service.

Le fichier de service ci-dessus, bien que suffisant et représentant une traduction globale du script d'init SysV, peut encore être amélioré. En voici une petite mise à jour:

[Unit]
Description=ABRT Automated Bug Reporting Tool
After=syslog.target

[Service]
Type=dbus
BusName=com.redhat.abrt
ExecStart=/usr/sbin/abrtd -d -s

[Install]
WantedBy=multi-user.target

Voyons ce qui a changé. Deux choses: nous avons amélioré la description et, plus important, nous avons changé le type de service à dbus et configuré le nom D-Bus du service. Pourquoi avoir fait cela ? Comme déjà évoqué précédemment, les services classiques SysV deviennent des démons après le démarrage ce qui implique généralement un double fork et le fait de se détacher de tout terminal. Bien que cette approche soit utile et nécessaire lorsque les démons sont lancés par un script, elle est non nécessaire (et lente) ainsi que contre-productive lorsqu'un système efficace de gestion des processus tel que systemd est employé. La raison à cela est que le processus du démon qui a forké n'a généralement plus de lien avec le processus démarré par systemd (la procédure de transformation en démon consiste à supprimer ce lien), ce qui rend difficile pour systemd de distinguer, après le fork, dans les processus appartenant au service, lequel est le processus principal et quels sont les processus auxiliaires. Cette information est pourtant cruciale pour disposer d'une gestion de processus aux petits oignons, c’est-à-dire pour superviser un processus, le relancer automatiquement lors des arrêts anormaux, collecter l'information de crash et les codes de sortie. Pour faciliter la vie de systemd pour repérer quel est le processus principal, nous avons changé le type de service à dbus. La syntaxe de ce type de service est faîte pour tous les services qui prennent un nom sur le bus système D-Bus comme dernière étape de leur initialisation[5]. ABRT est un de ces services. Avec ce paramètre, systemd lancera le processus ABRT qui ne forkera plus (configuré via les options -d -s du démon) et systemd considérera le service comme complètement démarré dès que com.redhat.abrt apparaîtra sur le bus. Ainsi, le processus lancé par systemd sera le processus principal du démon et systemd dispose d'un moyen fiable pour vérifier si le démon est complètement démarré; systemd pourra le superviser plus facilement.

Et c'est tout ce qu'il y a besoin de faire. Nous avons un simple fichier de service systemd qui fournit plus d'information dans 10 lignes que le script SysV originel en 115 lignes. Dès maintenant, il reste encore une grande marge d'amélioration en utilisant plus de fonctionnalités de systemd. Par exemple, nous pourrions configurer Restart=restart-always pour indiquer à systemd de relancer automatiquement le service lorsque celui-ci échoue. Ou encore, nous pourrions utiliser OOMScoreAdjust=-500 pour demander au noyau de laisser ce processus lorsque OOM killer est employé. Ou bien, nous pourrions utiliser CPUSchedulingPolicy=idle pour s'assurer que les processus abrtd crashent en tâche de fond uniquement, en autorisant le noyau à donner sa préférence à tout ce qui fonctionne et qui a besoin de temps CPU.

Pour plus d'informations sur les options de configuration mentionnées ci-dessus, consultez les pages de manuelles respectives systemd.unit(5), systemd.service(5), systemd.exec(5). Ou bien, consultez toutes les pages de manuel systemd.

Bien sûr, tous les scripts SysV ne se convertissent pas aussi facilement que celui que nous avons étudié. Néanmoins une grande majorité devrait pouvoir l'être.

C'est tout pour aujourd'hui, à bientôt pour la prochaine session de cette série.

Fin de la traduction.

Posted mar. 30 sept. 2014 19:34:47