Gestion de paquets et DevOps avec Nix, tour d’horizon sur un cas concret

26
24
jan.
2020
Administration système

Nix et GNU Guix sont des gestionnaires de paquets « fonctionnels », au sens de la programmation fonctionnelle. Cette approche de la gestion de paquets est très différente de l’approche habituellement utilisée par les systèmes GNU/Linux ou BSD, à base de collections de ports ou de dépôts de paquets.

Cette approche fonctionnelle apporte de nombreux avantages. Non seulement, elle permet de fournir une gestion de paquets fiable, reproductible, multi‑version et multi‑utilisateur, mais elle apporte également de nombreuses fonctionnalités supplémentaires : gestion d’un environnement de développement, empaquetage décentralisé, construction d’images Docker ou de machines virtuelles, personnalisation de tout l’environnement logiciel, etc.

Cet article part d’un projet de code (un serveur Web) et illustre progressivement différentes fonctionnalités de Nix intéressantes pour le développeur, l’empaqueteur et l’administrateur système. Les exemples sont présentés ici sous NixOS (la distribution GNU/Linux basée sur Nix), mais devraient être utilisables également avec l’outil Nix installé sur une distribution GNU/Linux classique, ou avec GNU Guix.

Sommaire

Voir aussi : le projet d’exemple, la vidéo YouTube et la vidéo PeerTube

Projet d’exemple : mymathserver

Le projet mymathserver est un serveur Web. La route / retourne un texte d’accueil et la route /mul2 permet de multiplier un entier par deux.

URL de mymathserver

Le projet est codé en C++. Il contient une bibliothèque de base src/mymath.hpp :

#pragma once

int mul2(int x) {
    return 2*x;
}

Un exécutable de tests unitaires src/mymathtest.cpp (on ne sait jamais) :

#include <gtest/gtest.h>

#include "mymath.hpp"

TEST(Mymath, mul2_1) {
    ASSERT_EQ(0, mul2(0));
}

// ...

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

Et enfin, le serveur Web src/mymathserver.cpp :

#include "mymath.hpp"
#include <cpprest/http_listener.h>
// ...

class App : public web::http::experimental::listener::http_listener {
    private:
        void handleGet(web::http::http_request req) {
            // ...
            if (path == "/") {
                // ...
            }
            else if (splitPath.size() == 2 and splitPath[0] == "mul2") {
                // ...
            }
            else {
                req.reply(web::http::status_codes::NotFound);
            }
        }

    public:
        App(std::string url) : web::http::experimental::listener::http_listener(url) {
            support(web::http::methods::GET, 
                    bind(&App::handleGet, this, std::placeholders::_1));
        }
};

int main() {
    const char * portEnv = std::getenv("PORT");
    const std::string port = portEnv == nullptr ? "3000" : portEnv;
    const std::string address = "http://0.0.0.0:" + port;
    App app(address);
    // ...
    return 0;
}

Le tout est configuré de façon très classique avec un CMakeLists.txt :

cmake_minimum_required( VERSION 3.0 )
project( mymathserver )

find_package( GTest REQUIRED )
add_executable( mymathtest src/mymathtest.cpp )
target_include_directories( mymathtest PRIVATE ${GTEST_INCLUDE_DIRS} )
target_link_libraries( mymathtest
    ${GTEST_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} )

find_package( Boost REQUIRED system )
find_package ( Threads REQUIRED )
find_package ( OpenSSL REQUIRED )
add_executable( mymathserver src/mymathserver.cpp )
target_link_libraries( mymathserver PRIVATE
   cpprest ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} )

install( TARGETS mymathserver mymathtest DESTINATION bin )

Empaqueter une dépendance manquante

On a donc le programme de tests unitaires mymathtest, qui utilise la bibliothèque gtest, et le programme serveur mymathserver qui utilise boost, openssl et cpprestsdk. Toutes ces dépendances sont classiques et donc présentes dans la plupart des logithèques GNU/Linux. Cependant, cpprestsdk est beaucoup moins connue que les autres dépendances et risque de manquer lors de la compilation de notre projet. Pour éviter ce problème, on peut laisser l’utilisateur installer ou compiler cpprestsdk lui‑même (ce qui peut être compliqué ou fastidieux), ou intégrer cpprestsdk directement à notre projet via les sous‑modules git (ce qui est peu efficace et rend le projet dépendant de git).

Avec Nix, on peut créer nous‑mêmes un paquet pour cpprestsdk. Certaines bibliothèques peuvent être complexes à empaqueter mais, généralement, cela se résume à indiquer l’adresse du code source et la liste des dépendances (nix/cpprestsdk.nix) :

{ stdenv, fetchFromGitHub, cmake, boost, openssl, websocketpp, zlib }:

stdenv.mkDerivation {

  name = "cpprestsdk";

  src = fetchFromGitHub {
    owner = "Microsoft";
    repo = "cpprestsdk";
    rev = "v2.10.14";
    sha256 = "0z1yblqszs7ig79l6lky02jmrs8zmpi7pnzns237p0w59pipzrvs";
  };

  buildInputs = [ boost cmake openssl websocketpp zlib ];
}

On remarquera qu’on n’a écrit aucune directive de compilation. En effet, comme on a indiqué cmake dans les dépendances de cpprestsdk, Nix sait qu’il faut compiler avec les commandes cmake classiques.

Empaqueter le projet

De la même façon que l’on a empaqueté cpprestsdk, on peut créer un fichier nix/mymathserver.nix pour empaqueter notre projet :

{ stdenv, cpprestsdk, boost, cmake, gtest, openssl }:

stdenv.mkDerivation {
  name = "mymathserver";
  version = "0.1";
  src = ../.;
  buildInputs = [ boost cmake cpprestsdk gtest openssl ];
}

De même que pour cpprestsdk, comme on a mis cmake dans les dépendances, Nix sait qu’il faut utiliser cmake et notre fichier CMakeLists.txt pour compiler le projet.

Pour l’instant, Nix ne connaît pas le code exact des dépendances. Il connaît juste les noms, et ce sont des paramètres du paquet (la première ligne du fichier). D’où la fameuse « approche fonctionnelle » de Nix.

Pour terminer, on écrit un fichier default.nix, qui est le point d’entrée de notre configuration de projet et fait le lien avec les fichiers précédents :

{ pkgs ? import <nixpkgs> {} }:

let
  cpprestsdk = pkgs.callPackage ./nix/cpprestsdk.nix {};
  mymathserver = pkgs.callPackage ./nix/mymathserver.nix { inherit cpprestsdk; };

in mymathserver

Ce fichier a également un paramètre (pkgs) mais avec une valeur par défaut (la logithèque système nixpkgs). Il suffit alors d’appeler nos paquets cpprestsdk et mymathserver sur la « logithèque » pkgs. Les paramètres des paquets sont fixés aux valeurs présentes dans pkgs, sauf pour le paramètre cpprestsdk dans mymathserver où c’est le paquet créé juste avant qui est utilisé (grâce au inherit). Ainsi, le default.nix permet de fournir concrètement le code à utiliser à travers les paramètres des descriptions de paquets précédentes, et donc de construire réellement les paquets correspondants.

On notera qu’on aurait pu se passer du fichier nix/mymathserver.nix et tout mettre dans le fichier default.nix. L’avantage d’utiliser deux fichiers est de rendre notre empaquetage plus modulaire. Par exemple, on pourrait quasiment intégrer directement nix/mymathserver.nix dans le dépôt nixpkgs et notre projet serait disponible dans la logithèque Nix officielle !

Lancer un environnement virtuel

À partir de notre fichier default.nix, on peut lancer un environnement virtuel :

nix-shell

Ceci installe les dépendances, compile cpprestsdk et initialise l’environnement. On peut alors travailler sur le code du projet et le compiler avec les commandes cmake classiques :

mkdir mybuild
cd mybuild
cmake ..
make
./mymathserver

lancement du projet

Construire et installer le paquet du projet

Le fichier default.nix permet également de construire automatiquement le paquet du projet :

nix-build
./result/bin/mymathserver

On peut également installer notre projet comme un logiciel classique de la logithèque système :

nix-env -i f .
mymathserver

Et on peut même l’installer en récupérant directement une archive du dépôt Git distant :

nix-env -if "https://gitlab.com/nokomprendo/mymathserver/-/archive/master/mymathserver-master.tar.gz"

Fixer une version

Le fichier default.nix construit le projet à partir de la logithèque système, qui peut varier dans le temps ou selon les utilisateurs. Nix permet de fixer une logithèque précise, et donc d’avoir un paquet reproductible. Par exemple, avec le fichier nix/release.nix :

let
  rev = "1c92cdaf7414261b4a0e0753ca9383137e6fef06";

  pkgs-src = fetchTarball {
    url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
    sha256 = "0d3fqa1aqralj290n007j477av54knc48y1bf1wrzzjic99cykwh";
  };

  pkgs = import pkgs-src {};

in pkgs.callPackage ../default.nix {}

Encore une fois, c’est l’approche fonctionnelle de Nix qui permet de composer les fichiers de configuration entre eux. Ici, nix/release.nix se contente de récupérer un pkgs particulier et s’en sert comme paramètre de default.nix pour construire tout le projet et ses dépendances à partir de ce pkgs particulier.

On peut alors construire ce paquet reproductible avec la commande :

nix-build nix/release.nix

Intégration continue

Comme notre configuration Nix décrit complètement l’environnement et la construction de notre projet, il est très pratique de l’utiliser dans un processus d’intégration continue. Par exemple, avec gitlab-ci, on peut ajouter le fichier .gitlab-ci.yml suivant pour lancer la compilation du projet et les tests unitaires :

build:
    image: nixos/nix
    script:
        - nix-build nix/release.nix
        - ./result/bin/mymathtest

Compilation depuis GitLab

Cache binaire

Nix permet d’utiliser des paquets de binaires pour éviter d’avoir à tout compiler soi‑même. Ainsi, la logithèque nixpkgs fournit un cache binaire. Cependant, cpprestsdk n’est pas disponible dans ce cache car on l’a empaqueté nous‑mêmes. Nix propose des outils pour installer des serveurs d’intégration continue et de cache mais ceci est assez lourd à mettre en place.

Une autre possibilité est d’utiliser le service de cache cachix, qui permet de téléverser des paquets Nix compilés puis de les télécharger sur un autre système. L’utilisation de cachix est très simple. Il suffit de créer un compte et un dépôt de cache. On peut ensuite envoyer des paquets binaires avec la commande cachix push et activer le téléchargement depuis un dépôt de cache avec la commande cachix use.

Par exemple, pour accélérer l’intégration continue précédente, on peut construire cpprestsdk avec notre configuration release, téléverser les paquets binaires correspondants dans le dépôt nokomprendo, puis utiliser ce cache dans le processus d’intégration continue (.gitlab-ci.yml) :

build:
    image: nixos/nix
    script:
        - nix-env -iA nixpkgs.cachix
        - cachix use nokomprendo
        - nix-build nix/release.nix
        - ./result/bin/mymathtest

Ceci fait passer l’exécution d’un processus d’intégration continue de vingt minutes à moins de deux minutes.

Comparaison d’intégration continue avec et sans cachix

Construire et déployer une image Docker

Nix permet de construire des images Docker. Par exemple, le fichier nix/docker.nix suivant définit une image Docker à partir de notre configuration release :

{ pkgs ? import <nixpkgs> {} }:

let
  mymathserver = import ./release.nix;

  entrypoint = pkgs.writeScript "entrypoint.sh" ''
    #!${pkgs.stdenv.shell}
    $@
  '';

in pkgs.dockerTools.buildImage {
  name = "mymathserver";
  tag = "latest";
  config = {
    WorkingDir = "${mymathserver}";
    Entrypoint = [ entrypoint ];
    Cmd = [ "${mymathserver}/bin/mymathserver" ];
  };
}

On peut alors construire l’image, la charger dans Docker et la tester :

nix-build nix/docker.nix
docker load < result
docker run --rm -it -p 3000:3000 mymathserver:latest

Compilation Nix et lancement avec Docker

Cette image Docker peut être déployée, par exemple sur Heroku :

heroku login
heroku container:login
heroku create nokomprendo-mymathserver
docker tag mymathserver:latest registry.heroku.com/nokomprendo-mymathserver/web
docker push registry.heroku.com/nokomprendo-mymathserver/web
heroku container:release web --app nokomprendo-mymathserver
heroku logs --tail

L’application est alors accessible à l’adresse http://nokomprendo-mymathserver.herokuapp.com.

Le projet Nixi en Docker dans Heroku

Construire et déployer une machine virtuelle

Nix permet de définir et de déployer des machines virtuelles, avec nixops. Par exemple, le fichier nix/virtualbox.nix suivant reprend notre programme mymathserver, le lance dans un service systemd et déploie le tout dans une virtualbox :

{
  network.description = "mynetwork";

  myserver = { config, pkgs, ... }: 

  let
    myapp = import ./release.nix;

  in {
    networking.firewall.allowedTCPPorts = [ 3000 ];

    systemd.services.myservice = {
      wantedBy = [ "multi-user.target" ];
      after = [ "network.target" ];
      script = "${myapp}/bin/mymathserver";
    };

    deployment = {
      targetEnv = "virtualbox";
      virtualbox = {
        memorySize = 512; 
        vcpu = 1; 
        headless = true;
      };
    };
  };
}

Les commandes suivantes créent et déploient la machine virtuelle sous le nom myvm :

nixops create -d myvm nix/virtualbox.nix
nixops deploy -d myvm --force-reboot

À l’issue du déploiement, une adresse IP est fournie et permet d’accèder à notre serveur sur la machine virtuelle.

Machine virtuelle avec Nix

Personnaliser des paquets existants

Enfin, une fonctionnalité originale de Nix, liée à son « approche fonctionnelle », est la possibilité de modifier les paramètres des paquets et de répercuter automatiquement et efficacement ces modifications dans l’ensemble de l’environnement logiciel.

Les paquets peuvent être surchargés, c’est‑à‑dire remplacés par des versions modifiées. Par exemple, le paquet de la bibliothèque zlib contient un attribut configureFlags qui contient les options de compilation à utiliser. Si l’on veut utiliser une version de zlib compilée avec l’option --zprefix, on peut utiliser le fichier nix/custom0.nix suivant :

let
  pkgs = import <nixpkgs> {};

  zlib = pkgs.zlib.overrideDerivation (attrs: {
     configureFlags = [ attrs.configureFlags "--zprefix" ];
  });

  cpprestsdk = pkgs.callPackage ./cpprestsdk.nix { inherit zlib; };

  mymathserver = pkgs.callPackage ./mymathserver.nix { inherit cpprestsdk; };

in pkgs.stdenv.mkDerivation rec {
  name = "mymathserver-custom0";
  src = ./.;
  buildPhase = "";
  installPhase = ''
    mkdir -p $out/bin
    cp ${mymathserver}/bin/mymathserver $out/bin/${name}
  '';
}

Les paramètres des paquets Nix peuvent également contenir des options paramétrables. Par exemple, la bibliothèque openssl a une option enableSSL2, qu’on peut spécifier comme dans le fichier nix/custom1.nix suivant :

let
  pkgs = import <nixpkgs> {};

  openssl = pkgs.openssl.override { enableSSL2 = true; };

  cpprestsdk = pkgs.callPackage ./cpprestsdk.nix { inherit openssl; };

  mymathserver = pkgs.callPackage ./mymathserver.nix {
    inherit cpprestsdk;
    inherit openssl;
  };

in pkgs.stdenv.mkDerivation rec {
  name = "mymathserver-custom1";
  src = ./.;
  buildPhase = "";
  installPhase = ''
    mkdir -p $out/bin
    cp ${mymathserver}/bin/mymathserver $out/bin/${name}
  '';
}

Enfin, pour éviter tout risque d’incompatibilité d’ABI, on peut surcharger la configuration lors du chargement de la logithèque. Ainsi, tous les paquets dépendant des paquets surchargés seront recompilés en prenant en compte ces modifications. Par exemple, le fichier nix/custom2.nix suivant surcharge openssl via la configuration de la logithèque (il existe aussi un système d’overlays pour cela) :

let

  config = {
    packageOverrides = pkgs: {
      openssl = pkgs.openssl.override {
        enableSSL2 = true;
      };
    };
  };

  pkgs = import <nixpkgs> { inherit config; };

  mymathserver = pkgs.callPackage ../default.nix {};

in pkgs.stdenv.mkDerivation rec {
  name = "mymathserver-custom2";
  src = ./.;
  buildPhase = "";
  installPhase = ''
    mkdir -p $out/bin
    cp ${mymathserver}/bin/mymathserver $out/bin/${name}
  '';
}

Nix installe chaque paquet dans un dossier spécifique, ce qui signifie qu’on peut utiliser en même temps plusieurs versions ou plusieurs configurations d’un même logiciel. Par exemple, dans les trois fichiers de personnalisation précédents, on a renommé l’exécutable mymathserver produit. On peut donc les installer et les exécuter en même temps dans l’environnement utilisateur courant :

nix-env -i -f nix/custom0.nix
nix-env -i -f nix/custom1.nix
nix-env -i -f nix/custom2.nix
mymathserver-custom0
...

Conclusion

L’approche fonctionnelle de Nix constitue une base solide pour la gestion de paquets. Elle permet de construire des paquets de façon fiable, reproductible et personnalisable. Sur cette base, Nix propose également des fonctionnalités intéressantes pour le développeur, l’empaqueteur et l’administrateur système.

Ainsi, en cent vingt lignes de code Nix, le projet d’exemple mymathserver :

  • empaquette une dépendance manquante (nix/cpprestsdk.nix) ;
  • empaquette le projet de base (nix/mymathserver.nix) ;
  • définit un point d’entrée pour créer des paquets et des environnements virtuels (default.nix) ;
  • définit une publication (nix/release.nix) ;
  • définit une image Docker (nix/docker.nix) ;
  • définit une machine virtuelle (nix/virtualbox.nix) ;
  • définit trois personnalisations différentes de l’environnement logiciel (nix/custom0.nix, etc.) ;
  • permet de réaliser de l’intégration continue, d’utiliser un cache binaire, d’installer le projet depuis une archive GitLab, etc.
mymathserver/
├── CMakeLists.txt
├── default.nix
├── nix
│   ├── cpprestsdk.nix
│   ├── custom0.nix
│   ├── custom1.nix
│   ├── custom2.nix
│   ├── docker.nix
│   ├── mymathserver.nix
│   ├── release.nix
│   └── virtualbox.nix
└── src
    ├── mymath.hpp
    ├── mymathserver.cpp
    └── mymathtest.cpp

Aller plus loin

  • # Superbe dépêche et étonnement !

    Posté par . Évalué à 4 (+3/-0).

    Merci pour cette dépêche complète et limpide !

    Je salue tout le travail accompli pour illustrer ton propos, c'est rare de voir autant d'efforts déployés pour introduire une technologie.

    Par ailleurs, je suis assez étonné de voir que mon article sur Guix plutôt "minable" en termes de contenu ait créé autant d’engouement (de beaux débats de fond et parfois du troll) alors que le tien n’a aucun commentaire…

    Quoiqu'il en soit, ça me donne encore plus envie de tester Nix et NixOS, les concepts sont les mêmes qu'avec Guix et la communauté à l'air bien plus grande (plus de paquets notamment). De plus, la possibilité d'installer un noyau linux avec drivers non libres semble plus évidente qu'avec Guix System, ce qui d'un point de vue de Gnu est mal, mais d'un point de vue pragmatique est plutôt une bonne chose.

    Encore bravo pour tout ce travail !

    • [^] # Re: Superbe dépêche et étonnement !

      Posté par (page perso) . Évalué à 5 (+3/-0).

      Merci pour ce commentaire fort sympathique.
      L'article est un peu long car l'idée était d'illustrer qu'on peut faire pas mal de choses assez facilement avec l'approche fonctionnelle de Nix/Guix. Maintenant, on n'est pas à un concours de l'article le plus long ou le plus commenté; le but est plutôt de partager des infos donc tous les articles qui vont dans ce sens sont intéressants.

      • [^] # Re: Superbe dépêche et étonnement !

        Posté par . Évalué à 2 (+1/-0).

        Maintenant, on n'est pas à un concours de l'article le plus long ou le plus commenté; le but est plutôt de partager des infos donc tous les articles qui vont dans ce sens sont intéressants.

        C'est sûr, mais j'ai été interpellé par le peu de réactions sur un article traitant d'un sujet similaire.

      • [^] # Re: Superbe dépêche et étonnement !

        Posté par . Évalué à 1 (+1/-0).

        Très intéressant. Cela permet de vous un peu les similarités et différences. :-)

        Par exemple, Guix n'est pas supporté par Cachix même s'il y a un ticket d'ouvert depuis longtemps. :-)

        https://github.com/cachix/cachix/issues/85

        En revanche, Guix fournit guix publish qui permet de servir les paquets que l'on a construit localement. Oui cela est moins pratique que Cachix. :-)

        https://guix.gnu.org/manual/en/html_node/Invoking-guix-publish.html

        Il y a un patch pour distribuer via IPFS mais il n'a pas encore été assez testé, je crois.

        https://debbugs.gnu.org/cgi/bugreport.cgi?bug=33899https://ipfs.io/

        A ma connaissance, il n'y a pas d'image officielle pour Gitlab CI. Mais il y a des images sur DockerHub ;-)

        Merci car cela donne du concret par rapport à ce qui a été dit dans l'autre annonce.

        • [^] # Re: Superbe dépêche et étonnement !

          Posté par (page perso) . Évalué à 2 (+0/-0).

          Merci pour ces infos.

          Pour l'image docker, je trouve ça très pratique, et ça ne doit pas demander beaucoup de temps à maintenir.

          Pour cachix, le service est assez jeune, même pour nix. Le ticket que tu mentionnes a été créé par un des développeurs pour voir s'il y a des gens intéressé par le support de guix. Peut-être qu'en relançant un peu le ticket, ça motiverait des gens pour l'implémenter.

  • # Déploiement de machine virtuelle ?

    Posté par . Évalué à 4 (+3/-0).

    Petite question concernant le déploiement de machine virtuelle, tu écris :

    À l’issue du déploiement, une adresse IP est fournie et permet d’accèder à notre serveur sur la machine virtuelle.

    Si je comprends bien, la commande nixops create créer un fichier de machine virtuel compatible virtual box et nixops deploy la déploie automatiquement ?
    La VM est donc automatiquement rajoutée aux machines gérées par Virtual Box ou c'est une étape qui n'est pas montrée ?

    Pour l'adresse IP, n'y a-t-il pas moyen de la préciser ?

    Désolé si mes questions sont simples ou si la réponse est dans la doc, pas pris le temps de regarder.

    Merci

    • [^] # Re: Déploiement de machine virtuelle ?

      Posté par (page perso) . Évalué à 4 (+2/-0).

      Nixops crée la VM et l'intègre directement dans Virtualbox. Pour l'exemple donné dans l'article, j'ai vraiment juste tapé les 2 commandes nixops et la VM est apparue dans l'interface de Virtualbox comme on peut le voir sur la capture d'écran. Pour l'IP, il y a peut-être moyen de la fixer à partir du fichier Nix mais je n'en sais pas plus.
      Nixops permet également de déployer vers des machines NixOS ou vers du Cloud (EC2, GCE, etc) : https://nixos.org/nixops/manual

  • # Versions ?

    Posté par . Évalué à 1 (+1/-0). Dernière modification le 27/01/20 à 20:38.

    Quelles versions avez-vous utilisé ?
    En particulier pour Boost.

    Comme je trouve la comparaison intéressante, j'ai fait quelques tests avec Guix. Mais avec Boost@1.70 j'ai un message d'erreur à la compilation de cpprestsdk. Et comme je ne voudrais pas passer trop de temps à empaqueter cette "chose" pour laquelle je n'ai pas un vif intérêt. :-)

    Merci d'avance.

    • [^] # Re: Versions ?

      Posté par (page perso) . Évalué à 2 (+0/-0).

      J'ai utilisé deux versions de nixpkgs : la release 19.09 et la révision 1c92cdaf7414261b4a0e0753ca9383137e6fef06. Les paquets de la release 19.09 sont consultables facilement ici : https://nixos.org/nixos/packages.html?channel=nixos-19.09 (pour Boost c'est la version 1.67).

      C'est une bonne idée de comparer avec Guix. Si tu as le temps de nous faire un retour, ce serait chouette.

      • [^] # Re: Versions ?

        Posté par . Évalué à 1 (+1/-0).

        C'est une bonne idée de comparer avec Guix. Si tu as le temps de nous faire un retour, ce serait chouette.

        Tout ca va dépendre si Microsoft fait bien le travail pour empaqueter cpprestsdk. Avoir la version de Boost avec laquelle vous avez compilé aide déjà pas mal. :-)

        Merci!

        Je vous tiens au courant ici. :-)

  • # Paquets pour Guix

    Posté par . Évalué à 4 (+4/-0). Dernière modification le 28/01/20 à 19:08.

    Le projet jouet mymathserver dépend de cpprestsdk qui est le point sensible à empaqueter car le code n'est pas très robuste. Pour s'en convaincre, il suffit d'aller faire un tour sur les issues GitHub.

    https://github.com/microsoft/cpprestsdk/issues

    Donc, il faut un peu tricoter avec la version de Boost.

    Du côté de Guix, le paquet websocketpp est dans disponible après la bonne version de Boost. Donc on pourrait s'en sortir avec guix time-machine. Mais beaucoup de substituts ne sont plus disponibles donc il y aurait beaucoup de compilations à faire—ce qui n'est pas un problème en soi mais pas le but ici.

    Bref! en plus du projet jouet mymathserver et de cpprestsdk comme dans l'article initial, j'ai "empaqueté" websocketpp et une vieille version de boost. Empaqueter est un bien grand mot puisque ce sont les vieux paquets dépoussiérés à coup de git show.

    Finalement, on compare des pommes et des oranges puisque le tour d'horizon a été fait avec ce qui était facile pour Nix (2 paquets personnalisés) et donc dans une autre configuration, cela aurait été Nix qui aurait dû fournir plus de paquets personnalisés. Donc il faut s'arrêter après les 2 mêmes paquets, les 2 autres derniers sont juste là pour éviter de recompiler le monde entier.

    Ceci dit, avec ces définitions, on est capable de reproduire la première partie.


    Le fichier disons /tmp/test/linuxfr.scm contient les paquets personnalisés ci-dessous.

    Pour tout construire, il faut faire:

      guix build -L /tmp/test mymathserver
    

    En terme de reproductibilité, si vous utilisez la time-machine :

      guix time-machine --commit=b3e28b5ef5e23e564eeac8823ad2c837f7c40650 \
        -- build -L /tmp/test mymathserver
    

    vous devriez compiler au bit près la même chose que moi. Donc normalement, vous devriez avoir: /gnu/store/hqhsc61v4xpj1ld0dwvf8gabxr3x0dwc-mymathserver-0.1 comme résultat. Sinon, il ya une source de non-déterminisme.

    Je ferai un autre commentaire pour donner l'équivalent des commandes. :-)


    (define-module (linuxfr)
      #:use-module (guix download)
      #:use-module (guix git-download)
      #:use-module (guix packages)
      #:use-module (guix build-system cmake)
    
      ;; boost
      #:use-module (guix build-system gnu)
      #:use-module (gnu packages)
      #:use-module (gnu packages perl)
      #:use-module (gnu packages python)
      #:use-module (gnu packages shells)
      #:use-module (gnu packages icu4c)
    
      #:use-module (gnu packages tls)
      #:use-module (gnu packages compression)
      #:use-module (gnu packages check)
      )
    
    
    (define-public mymathserver
      (package
       (name "mymathserver")
       (version "0.1")
       (source (origin
                (method git-fetch)
                (uri (git-reference
                      (url "https://gitlab.com/nokomprendo/mymathserver")
                      (commit (string-append "v" version))))
                (file-name (git-file-name name version))
                (sha256
                 (base32 "0vvq2z2pjdr7dm6s4d6a3ikp7lw391lk4mdlqd18rgmr2jqfhl0b"))))
       (build-system cmake-build-system)
       (arguments '(#:tests? #f))
       (native-inputs
        `(("boost" ,boost-old)
          ("cpprestsdk" ,cpprestsdk)
          ("gtest" ,googletest)
          ("openssl" ,openssl)))
       (synopsis "Toy example")
       (description "Nothing relevant")
       (home-page "https://gitlab.com/nokomprendo/mymathserver")
       (license #f)))
    
    
    (define-public cpprestsdk
      (package
       (name "cpprestsdk")
       (version "v2.10.14")
       (source (origin
                (method git-fetch)
                (uri (git-reference
                      (url "https://github.com/microsoft/cpprestsdk")
                      (commit version)))
                (file-name (git-file-name name version))
                (sha256
                 (base32 "0z1yblqszs7ig79l6lky02jmrs8zmpi7pnzns237p0w59pipzrvs"))))
       (build-system cmake-build-system)
       (native-inputs
        `(("websocketpp" ,my-websocketpp)
          ("boost" ,boost-old)
          ("openssl" ,openssl)
          ("zlib" ,zlib)))
       (arguments '(#:tests? #f
                    ;; Warnings not always addressed by upstream, see:
                    ;; https://github.com/microsoft/cpprestsdk/issues/724
                    #:configure-flags '("-DWERROR=OFF")))
       (synopsis "C++ REST SDK by Microsoft")
       (description "The C++ REST SDK is a Microsoft project for cloud-based
    client-server communication in native code using a modern asynchronous C++ API
    design.  This project aims to help C++ developers connect to and interact with
    services.")
       (home-page "https://github.com/microsoft/cpprestsdk")
       (license #f)))
    
    
    ;;;
    ;;; End here. 
    ;;; Below hack to build cpprestsdk with the correct versions
    ;;; without rebuilding the world.
    ;;;
    
    (define-public my-websocketpp
      (package
        (name "websocketpp")
        (version "0.8.1")
        (source
         (origin
           (method git-fetch)
           (uri (git-reference
                 (url "https://github.com/zaphoyd/websocketpp.git")
                 (commit version)))
           (file-name (git-file-name name version))
           (sha256
            (base32 "12ffczcrryh74c1xssww35ic6yiy2l2xgdd30lshiq9wnzl2brgy"))))
        (build-system cmake-build-system)
        (native-inputs
         `(("boost" ,boost-old)
           ("openssl" ,openssl)))
        (arguments '(#:configure-flags '("-DBUILD_TESTS=ON")
                     #:phases
                     (modify-phases %standard-phases
                       (add-after 'install 'remove-tests
                         (lambda* (#:key outputs #:allow-other-keys)
                           (let* ((install-dir (assoc-ref outputs "out"))
                                  (bin-dir (string-append install-dir "/bin")))
                             (delete-file-recursively bin-dir)
                             #t))))))
        (home-page "https://www.zaphoyd.com/websocketpp/")
        (synopsis "C++ library implementing the WebSocket protocol")
        (description "WebSocket++ is a C++ library that can be used to implement
    WebSocket functionality.  The goals of the project are to provide a WebSocket
    implementation that is simple, portable, flexible, lightweight, low level, and
    high performance.")
        (license #f)))                      ;license:bsd-3
    
    
    (define-public boost-old
      (package
        (name "boost")
        (version "1.67.0")
        (source (origin
                  (method url-fetch)
                  (uri (string-append
                        "mirror://sourceforge/boost/boost/" version "/boost_"
                        (string-map (lambda (x) (if (eq? x #\.) #\_ x)) version)
                        ".tar.bz2"))
                  (sha256
                   (base32
                    "1fmdlmkzsrd46wwk834jsi2ypxj68w2by0rfcg2pzrafk5rck116"))
                  (patches '("/tmp/test/patches/boost-fix-icu-build.patch"))))
        (build-system gnu-build-system)
        (inputs `(("icu4c" ,icu4c)
                  ("zlib" ,zlib)))
        (native-inputs
         `(("perl" ,perl)
           ("python" ,python-2)
           ("tcsh" ,tcsh)))
        (arguments
         `(#:tests? #f
           #:make-flags
           (list "threading=multi" "link=shared"
    
                 ;; Set the RUNPATH to $libdir so that the libs find each other.
                 (string-append "linkflags=-Wl,-rpath="
                                (assoc-ref %outputs "out") "/lib")
    
                 ;; Boost's 'context' library is not yet supported on mips64, so
                 ;; we disable it.  The 'coroutine' library depends on 'context',
                 ;; so we disable that too.
                 ,@(if (string-prefix? "mips64" (or (%current-target-system)
                                                    (%current-system)))
                       '("--without-context"
                         "--without-coroutine" "--without-coroutine2")
                       '()))
           #:phases
           (modify-phases %standard-phases
             (delete 'bootstrap)
             (replace 'configure
               (lambda* (#:key inputs outputs #:allow-other-keys)
                 (let ((icu (assoc-ref inputs "icu4c"))
                       (out (assoc-ref outputs "out")))
                   (substitute* '("libs/config/configure"
                                  "libs/spirit/classic/phoenix/test/runtest.sh"
                                  "tools/build/doc/bjam.qbk"
                                  "tools/build/src/engine/execunix.c"
                                  "tools/build/src/engine/Jambase"
                                  "tools/build/src/engine/jambase.c")
                     (("/bin/sh") (which "sh")))
    
                   (setenv "SHELL" (which "sh"))
                   (setenv "CONFIG_SHELL" (which "sh"))
    
                   (invoke "./bootstrap.sh"
                           (string-append "--prefix=" out)
                           ;; Auto-detection looks for ICU only in traditional
                           ;; install locations.
                           (string-append "--with-icu=" icu)
                           "--with-toolset=gcc"))))
             (replace 'build
               (lambda* (#:key make-flags #:allow-other-keys)
                 (apply invoke "./b2"
                        (format #f "-j~a" (parallel-job-count))
                        make-flags)))
             (replace 'install
               (lambda* (#:key make-flags #:allow-other-keys)
                 (apply invoke "./b2" "install" make-flags))))))
    
        (home-page "http://www.boost.org")
        (synopsis "Peer-reviewed portable C++ source libraries")
        (description
         "A collection of libraries intended to be widely useful, and usable
    across a broad spectrum of applications.")
        (license #f)))
    ;; (license (license:x11-style "http://www.boost.org/LICENSE_1_0.txt"
    ;;                               "Some components have other similar licences."))
    • [^] # Re: Paquets pour Guix

      Posté par . Évalué à 2 (+0/-0).

      Je ne suis pas utilisateur de Lisp mais je trouve que ton fichier montre bien que le mythe "le Lisp c'est illisible car il y a trop de parenthèses" n'est bien qu'un mythe, ça se lit bien.

      Je suis juste surpris par "#:use-module", c'est quoi ce machin qui ne commence pas par une parenthèse justement?

      • [^] # Re: Paquets pour Guix

        Posté par . Évalué à 1 (+1/-0).

        Pour faire simple, une introduction aux keywords #: est cette page:

        https://www.gnu.org/software/guile/manual/html_node/Why-Use-Keywords_003f.html

        Oui c'est un mythe les histoires de parenthèses et de Lisp. Comme avec le C par exemple, il faut juste faire un effort d'indentation et tout se passe bien.

        Et si on est totalement honnête, la seule chose vraiment pénible est de converser les parenthèses balancées. Mais cela se résout avec tout bon éditeur de texte.


        Notez que les définitions des paquets paraissent plus difficiles qu'avec Nix parce que j'ai fait un peu de zèle. ;-)

  • # Empaqueter une dépendance manquante (avec Guix

    Posté par . Évalué à 3 (+3/-0). Dernière modification le 28/01/20 à 19:14.

    Nix

    { stdenv, fetchFromGitHub, cmake, boost, openssl, websocketpp, zlib }:
    
    stdenv.mkDerivation {
    
      name = "cpprestsdk";
    
      src = fetchFromGitHub {
        owner = "Microsoft";
        repo = "cpprestsdk";
        rev = "v2.10.14";
        sha256 = "0z1yblqszs7ig79l6lky02jmrs8zmpi7pnzns237p0w59pipzrvs";
      };
    
      buildInputs = [ boost cmake openssl websocketpp zlib ];
    }

    Guix

    (define-module (linuxfr)
      #:use-module (guix download)
      #:use-module (guix git-download)
      #:use-module (guix packages)
      #:use-module (guix build-system cmake)
    
      #:use-module (gnu packages tls)
      #:use-module (gnu packages compression))
    
    (define-public cpprestsdk
      (package
       (name "cpprestsdk")
       (version "v2.10.14")
       (source (origin
                (method git-fetch)
                (uri (git-reference
                      (url "https://github.com/microsoft/cpprestsdk")
                      (commit version)))
                (file-name (git-file-name name version))
                (sha256
                 (base32 "0z1yblqszs7ig79l6lky02jmrs8zmpi7pnzns237p0w59pipzrvs"))))
       (build-system cmake-build-system)
       (native-inputs
        `(("websocketpp" ,my-websocketpp)
          ("boost" ,boost-old)
          ("openssl" ,openssl)
          ("zlib" ,zlib)))
       (arguments '(#:tests? #f
                    ;; Warnings not always addressed by upstream, see:
                    ;; https://github.com/microsoft/cpprestsdk/issues/724
                    #:configure-flags '("-DWERROR=OFF")))
       (synopsis "C++ REST SDK by Microsoft")
       (description "The C++ REST SDK is a Microsoft project for cloud-based
    client-server communication in native code using a modern asynchronous C++ API
    design.  This project aims to help C++ developers connect to and interact with
    services.")
       (home-page "https://github.com/microsoft/cpprestsdk")
       (license #f)))
  • # Empaqueter le projet

    Posté par . Évalué à 1 (+1/-0).

    Guix

    (define-module (linuxfr)
      #:use-module (guix download)
      #:use-module (guix git-download)
      #:use-module (guix packages)
      #:use-module (guix build-system cmake)
    
      #:use-module (gnu packages tls)
      #:use-module (gnu packages compression)
      #:use-module (gnu packages check))
    
    (define-public mymathserver
      (package
       (name "mymathserver")
       (version "0.1")
       (source (origin
                (method git-fetch)
                (uri (git-reference
                      (url "https://gitlab.com/nokomprendo/mymathserver")
                      (commit (string-append "v" version))))
                (file-name (git-file-name name version))
                (sha256
                 (base32 "0vvq2z2pjdr7dm6s4d6a3ikp7lw391lk4mdlqd18rgmr2jqfhl0b"))))
       (build-system cmake-build-system)
       (arguments '(#:tests? #f))
       (native-inputs
        `(("boost" ,boost-old)
          ("cpprestsdk" ,cpprestsdk)
          ("gtest" ,googletest)
          ("openssl" ,openssl)))
       (synopsis "Toy example")
       (description "Nothing relevant")
       (home-page "https://gitlab.com/nokomprendo/mymathserver")
       (license #f)))
  • # Lancer un environnement virtuel

    Posté par . Évalué à 1 (+1/-0). Dernière modification le 28/01/20 à 19:37.

    Il n'y a pas vraiment de default.nix. Il y a 2 solutions pour faire la même chose :

    • soit un channel (qui peut être local ou à distance)
    • soit utiliser --load-path pour ajouter (prepend) un dossier dans la recherche des paquets.

    Disons que l'on a cloné le dépôt mymathserver et que l'on se trouve dans le dossier. Et dans ce dossier, il y a aussi un fichier, disons guix.scm qui contient les définitions (voir autres sections).

    Guix

     guix environment --load-path=. mymathserver
    
    mkdir mybuild
    cd mybuild
    cmake ..
    make
    

    Mais ./mymathserver retourne Segmentation fault. A investiguer…

  • # Construire et installer le paquet du projet

    Posté par . Évalué à 1 (+1/-0).

    Idem que dans l'autre section, on suppose que l'on se trouve dans le dossier contenant le fichier guix.scm sinon il faut indiquer ledit chemin ou utiliser un channel.

    Guix

      guix build --load-path=. mymathserver
    
      guix install --load-path . mymathserver
    

    Cependant, je préféres installer dans un profil séparer du profil courant. Par exemple,

      guix install --load-path=. mymathserver --profile=/tmp/my-server-test
    

    Puis il faut "activer" ce profil :

      source /tmp/my-server-test/etc/profile
    

    (il y a d'autres solutions de faire pour mettre à jours tous les chemins :-))

    Finalement, taper mymathserver fonctionne (serveur dans localhost).

  • # Fixer une version

    Posté par . Évalué à 1 (+1/-0).

    Avec Guix, c'est plus facile de fixer une version comme c'est fait.

    Pour ne pas fixer une version fixe, à ma connaissance il n'y a rien de natif et il faut écrire un peu plus qu'avec Nix. Comme là par exemple:

    http://git.savannah.nongnu.org/cgit/mescc-tools.git/tree/guix.scm

    • [^] # Re: Fixer une version

      Posté par (page perso) . Évalué à 2 (+0/-0).

      Merci pour les explications et pour tout le travail de packaging.
      Je vais tester ça dès que j'aurais le temps. Si tu es d'accord, je reprendrai tes fichiers et explications pour les intégrer dans le dépôt git.

      • [^] # Re: Fixer une version

        Posté par . Évalué à 2 (+2/-0).

        Si c'est pour aller un peu plus quand dans un commentaire ici, je voudrais nettoyer un peu les choses alors. :-)

        1. Disons avoir un guix.scm propre pour construire master (quelque soit le commit).
        2. Savoir pourquoi il ya un Seg Fault avec guix environment.
        3. Regarder de plus près pourquoi cpprestsdk ne compile pas avec Boost 1.70. Parce que normalement, ca devrait ! Par exemple, par défaut il ya -Werror et … il y a beaucoup de warnings. :-) Donc je vais re-essayer un peu ce qui évitera de ressortir une vieille version de Boost et simplifiera le brol. Chose que je ne voulais pas faire initialement parce que bon cpprestsdk c'est loin de mes affaires. Mais maintenant au point où j'en suis… :-)
        4. Faire l'équivalent de la dernière partie avec le conteneur, toussa.

        Bon si je ne suis pas sorti du bois d'ici la milieu de semaine prochaine, c'est que j'aurais eu d'autres préoccupations plus urgentes. ;-)
        Dans ce cas, reprenez ce que vous voudrez. :-)

  • # je comprend pas

    Posté par . Évalué à 2 (+2/-0).

    salut,
    désolé je vais poser une question conne, mais quel est l’intérêt au niveau sysadmin/devops/SRE ? Je n'arrive pas à voir se qu'apporte ce type de solution par rapport à d'autre outils de gestion de conf/déploiement/etc, quelqu'un peu me donner un exemple concret dans un environnement de production ?
    Merci

    • [^] # Re: je comprend pas

      Posté par . Évalué à 1 (+1/-0).

      Au niveau sysadmin, pour la gestion des paquets, un intérêt est le rollback et les profils (ce qui est fait par des solutions comme modulefiles ou virtualenv).
      (en passant il n'y a pas besoin d'être superutilisateurs pour installer des paquets et il n'y a pas de problème)
      Ensuite, si on utilise Guix System (ou Nix OS), le rollback s'applique aussi aux services.

      Il y a les fichiers manifest qui permettent de déclarer ses paquets en spécifiant un commit Guix particulier (un channel). Donc sur n'importe quelle machine, il est trivial d'instancier exactement la même configuration.

      DevOps et SRE n'est pas mon coeur de métier donc je ne sais pas.

      Pour vous donner un exemple concret, il faudrait que vous décriviez un problème concret et votre actuelle solution concrète.

      • [^] # Re: je comprend pas

        Posté par . Évalué à 1 (+1/-0).

        par exemple sur mes 500 serveurs de prod je veux déployer la nouvelle version d'apache. Avec un puppet/ansible c'est relativement rapide (quelques minutes pour la modif du code et autres opérations GITesques et hop le serveur puppet (par exemple) pousse la nouvelle version sur tous les serveurs). Et Nix ?

        • [^] # Re: je comprend pas

          Posté par . Évalué à 1 (+0/-0).

          Votre cas d'usage n'est pas le cadre de cet article. :-)
          Et pour être honnête, ce cas d'usage n'est pas mon coeur de métier.

          Concernant Nix, je ne sais pas.

          Concernant Guix, les fonctionnalités pour ce genre de cas d'usage ne sont pas encore totalement mature pour être utilisé en production. Pour faire simple la réponse est guix deploy et voici quelques pointeurs :

          https://guix.gnu.org/manual/devel/en/html_node/Invoking-guix-deploy.html
          https://guix.gnu.org/blog/2019/towards-guix-for-devops/

          roptat qui contribue beaucoup à Guix a écrit cela :

          https://linuxfr.org/news/guix-un-outil-pour-les-remplacer-tous#comment-1797262

        • [^] # Re: je comprend pas

          Posté par . Évalué à 1 (+0/-0).

          Si on doit gérer un parc de cette taille, on va donc partir du principe qu'on utilise soit cachix, soit un serveur au sein de l'infra pour présenter la configuration voulue.

          Je vais isoler deux cas:

          1) la nouvelle version d'apache dont on parle est livrée par la release officielle de Nixpkgs/Nixos

          Dans ce cas, il suffit de faire une mise à jour des canaux des 500 machines (sur nixos, il y a un module pour faire cela régulièrement)

          2) la nouvelle version n'est pas distribuée dans nixpkgs

          Dans ce cas, il faut fournir un channel particulier pour les paquets custom (ça demande un peu plus de travail, mais ça permet de faciliter les personnalisations).

          La logique reste la même, il faut juste prévoir un channel (dans un dépôt git) pour présenter les paquets spécifiques.

          Dans le cas où certaines installations sont longues, on poussera les binaires sur l'espace cachix / le serveur cache de l'infra pour que les déploiements soient juste des téléchargements

          C'est assez proche de l'expérience de puppet. Le seul point à scripter soi-même, serait le provisionnement en mode push pour un hotfix (ssh + nix-channel --update && nixos-rebuild switch, donc rien de mortel) qui n'est pas proposé par défaut.

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.