summary refs log tree commit diff
path: root/nixos/modules/virtualisation/docker-preloader.nix
blob: 6ab83058dee1d9b50edc75f19eb7ee88af2ac1c0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
{ config, lib, pkgs, ... }:

with lib;
with builtins;

let
  cfg = config.virtualisation;

  sanitizeImageName = image: replaceStrings ["/"] ["-"] image.imageName;
  hash = drv: head (split "-" (baseNameOf drv.outPath));
  # The label of an ext4 FS is limited to 16 bytes
  labelFromImage = image: substring 0 16 (hash image);

  # The Docker image is loaded and some files from /var/lib/docker/
  # are written into a qcow image.
  preload = image: pkgs.vmTools.runInLinuxVM (
    pkgs.runCommand "docker-preload-image-${sanitizeImageName image}" {
      buildInputs = with pkgs; [ docker e2fsprogs utillinux curl kmod ];
      preVM = pkgs.vmTools.createEmptyImage {
        size = cfg.dockerPreloader.qcowSize;
        fullName = "docker-deamon-image.qcow2";
      };
    }
    ''
      mkfs.ext4 /dev/vda
      e2label /dev/vda ${labelFromImage image}
      mkdir -p /var/lib/docker
      mount -t ext4 /dev/vda /var/lib/docker

      modprobe overlay

      # from https://github.com/tianon/cgroupfs-mount/blob/master/cgroupfs-mount
      mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup
      cd /sys/fs/cgroup
      for sys in $(awk '!/^#/ { if ($4 == 1) print $1 }' /proc/cgroups); do
        mkdir -p $sys
        if ! mountpoint -q $sys; then
          if ! mount -n -t cgroup -o $sys cgroup $sys; then
            rmdir $sys || true
          fi
        fi
      done

      dockerd -H tcp://127.0.0.1:5555 -H unix:///var/run/docker.sock &

      until $(curl --output /dev/null --silent --connect-timeout 2 http://127.0.0.1:5555); do
        printf '.'
        sleep 1
      done

      docker load -i ${image}

      kill %1
      find /var/lib/docker/ -maxdepth 1 -mindepth 1 -not -name "image" -not -name "overlay2" | xargs rm -rf
    '');

  preloadedImages = map preload cfg.dockerPreloader.images;

in

{
  options.virtualisation.dockerPreloader = {
    images = mkOption {
      default = [ ];
      type = types.listOf types.package;
      description =
      ''
        A list of Docker images to preload (in the /var/lib/docker directory).
      '';
    };
    qcowSize = mkOption {
      default = 1024;
      type = types.int;
      description =
      ''
        The size (MB) of qcow files.
      '';
    };
  };

  config = mkIf (cfg.dockerPreloader.images != []) {
    assertions = [{
      # If docker.storageDriver is null, Docker choose the storage
      # driver. So, in this case, we cannot be sure overlay2 is used.
      assertion = cfg.docker.storageDriver == "overlay2"
        || cfg.docker.storageDriver == "overlay"
        || cfg.docker.storageDriver == null;
      message = "The Docker image Preloader only works with overlay2 storage driver!";
    }];

    virtualisation.qemu.options =
      map (path: "-drive if=virtio,file=${path}/disk-image.qcow2,readonly,media=cdrom,format=qcow2")
      preloadedImages;


    # All attached QCOW files are mounted and their contents are linked
    # to /var/lib/docker/ in order to make image available.
    systemd.services.docker-preloader = {
      description = "Preloaded Docker images";
      wantedBy = ["docker.service"];
      after = ["network.target"];
      path = with pkgs; [ mount rsync jq ];
      script = ''
        mkdir -p /var/lib/docker/overlay2/l /var/lib/docker/image/overlay2
        echo '{}' > /tmp/repositories.json

        for i in ${concatStringsSep " " (map labelFromImage cfg.dockerPreloader.images)}; do
          mkdir -p /mnt/docker-images/$i

          # The ext4 label is limited to 16 bytes
          mount /dev/disk/by-label/$(echo $i | cut -c1-16) -o ro,noload /mnt/docker-images/$i

          find /mnt/docker-images/$i/overlay2/ -maxdepth 1 -mindepth 1 -not -name l\
             -exec ln -s '{}' /var/lib/docker/overlay2/ \;
          cp -P /mnt/docker-images/$i/overlay2/l/* /var/lib/docker/overlay2/l/

          rsync -a /mnt/docker-images/$i/image/ /var/lib/docker/image/

          # Accumulate image definitions
          cp /tmp/repositories.json /tmp/repositories.json.tmp
          jq -s '.[0] * .[1]' \
            /tmp/repositories.json.tmp \
            /mnt/docker-images/$i/image/overlay2/repositories.json \
            > /tmp/repositories.json
        done

        mv /tmp/repositories.json /var/lib/docker/image/overlay2/repositories.json
      '';
      serviceConfig = {
        Type = "oneshot";
      };
    };
  };
}