Asko Soukka

Developing service mesh with Nomad and Nix

Nomad is a very special service orchestrator, because it can be used without any 3rd party container runtime. Of course, Nomad supports the usual suspects like Docker and Podman, but its pluggable architecture supports also many other ways to run applications. My immediate favorite has been its Isolated Fork/Exec Driver, which is a perfect fit for Nix packaged applications.

Nomad’s Exec driver can download a compressed tar archive (.tar.gz), extract its contents, and execute any path from the extracted archive. The catch, of course, is that the downloaded archive should be self-containing enough to run successfully in the defined chroot configuration. And the smaller the chroot is, the faster and more lean the deployment is.

Nix is able to package anything into a fully self-contained Linux or MaxOS archive, which makes it perfect pair for Nomad’s Exec driver. I crafted a simple example demonstrating my current approach, which uses Nix for development and packaging, but requires Nix knowledge from our Nomad nodes: https://github.com/datakurre/nomad-with-nix/.

Below are some of the highlights:

Bootstrapping development

Domen Kožar’s awesome nix.dev guide for getting things done using the Nix ecosystem, pushed me over to stop worrying and love Niv and direnv for seamless developer experience with Nix managed projects.

With only having Nix pre-installed, this is how I bootstrapped my example project:

At first, I initialized the project with Niv, which created ./nix folder to contain version pins to external dependencies like Nix Packages collection (nixpkgs).

$ nix-shell -p niv --run "niv init"
$ nix-shell -p niv --run "niv update nixpkgs -b nixos-20.09"

Next, I made ./nix easily importable with extensible default nixpkgs set.

$ cat > nix/default.nix << EOF
{ sources ? import ./sources.nix }:

let

  overlay = _: pkgs: {
   # custom packages
  };

  pkgs = import sources.nixpkgs {
    overlays = [ overlay ];
    config = {};
  };

in pkgs
EOF

Then, I crafted a simple ./shell.nix expression to define my initial development shell from Niv managed dependencies.

$ cat > shell.nix << EOF
{ pkgs ? import ./nix {}
}:

pkgs.mkShell {
  buildInputs = with pkgs; [
    niv
    nomad_0_12
  ];
  shellHook = ''
    export NOMAD_ADDR=http://127.0.0.1:4646
  '';
}
EOF

Finally, because I had already configured direnv on my system, I could easily active dirvenv for the project.

$ echo "use nix" > .envrc && direnv allow
$ nix-shell