1# Systemd services for lxd. 2 3{ config, lib, pkgs, ... }: 4 5let 6 cfg = config.virtualisation.lxd; 7 preseedFormat = pkgs.formats.yaml {}; 8in { 9 imports = [ 10 (lib.mkRemovedOptionModule [ "virtualisation" "lxd" "zfsPackage" ] "Override zfs in an overlay instead to override it globally") 11 ]; 12 13 ###### interface 14 15 options = { 16 virtualisation.lxd = { 17 enable = lib.mkOption { 18 type = lib.types.bool; 19 default = false; 20 description = lib.mdDoc '' 21 This option enables lxd, a daemon that manages 22 containers. Users in the "lxd" group can interact with 23 the daemon (e.g. to start or stop containers) using the 24 {command}`lxc` command line tool, among others. 25 26 Most of the time, you'll also want to start lxcfs, so 27 that containers can "see" the limits: 28 ``` 29 virtualisation.lxc.lxcfs.enable = true; 30 ``` 31 ''; 32 }; 33 34 package = lib.mkOption { 35 type = lib.types.package; 36 default = pkgs.lxd; 37 defaultText = lib.literalExpression "pkgs.lxd"; 38 description = lib.mdDoc '' 39 The LXD package to use. 40 ''; 41 }; 42 43 lxcPackage = lib.mkOption { 44 type = lib.types.package; 45 default = pkgs.lxc; 46 defaultText = lib.literalExpression "pkgs.lxc"; 47 description = lib.mdDoc '' 48 The LXC package to use with LXD (required for AppArmor profiles). 49 ''; 50 }; 51 52 zfsSupport = lib.mkOption { 53 type = lib.types.bool; 54 default = config.boot.zfs.enabled; 55 defaultText = lib.literalExpression "config.boot.zfs.enabled"; 56 description = lib.mdDoc '' 57 Enables lxd to use zfs as a storage for containers. 58 59 This option is enabled by default if a zfs pool is configured 60 with nixos. 61 ''; 62 }; 63 64 recommendedSysctlSettings = lib.mkOption { 65 type = lib.types.bool; 66 default = false; 67 description = lib.mdDoc '' 68 Enables various settings to avoid common pitfalls when 69 running containers requiring many file operations. 70 Fixes errors like "Too many open files" or 71 "neighbour: ndisc_cache: neighbor table overflow!". 72 See https://lxd.readthedocs.io/en/latest/production-setup/ 73 for details. 74 ''; 75 }; 76 77 preseed = lib.mkOption { 78 type = lib.types.nullOr (lib.types.submodule { 79 freeformType = preseedFormat.type; 80 }); 81 82 default = null; 83 84 description = lib.mdDoc '' 85 Configuration for LXD preseed, see 86 <https://documentation.ubuntu.com/lxd/en/latest/howto/initialize/#initialize-preseed> 87 for supported values. 88 89 Changes to this will be re-applied to LXD which will overwrite existing entities or create missing ones, 90 but entities will *not* be removed by preseed. 91 ''; 92 93 example = lib.literalExpression '' 94 { 95 networks = [ 96 { 97 name = "lxdbr0"; 98 type = "bridge"; 99 config = { 100 "ipv4.address" = "10.0.100.1/24"; 101 "ipv4.nat" = "true"; 102 }; 103 } 104 ]; 105 profiles = [ 106 { 107 name = "default"; 108 devices = { 109 eth0 = { 110 name = "eth0"; 111 network = "lxdbr0"; 112 type = "nic"; 113 }; 114 root = { 115 path = "/"; 116 pool = "default"; 117 size = "35GiB"; 118 type = "disk"; 119 }; 120 }; 121 } 122 ]; 123 storage_pools = [ 124 { 125 name = "default"; 126 driver = "dir"; 127 config = { 128 source = "/var/lib/lxd/storage-pools/default"; 129 }; 130 } 131 ]; 132 } 133 ''; 134 }; 135 136 startTimeout = lib.mkOption { 137 type = lib.types.int; 138 default = 600; 139 apply = toString; 140 description = lib.mdDoc '' 141 Time to wait (in seconds) for LXD to become ready to process requests. 142 If LXD does not reply within the configured time, lxd.service will be 143 considered failed and systemd will attempt to restart it. 144 ''; 145 }; 146 147 ui = { 148 enable = lib.mkEnableOption (lib.mdDoc "(experimental) LXD UI"); 149 150 package = lib.mkPackageOption pkgs.lxd-unwrapped "ui" { }; 151 }; 152 }; 153 }; 154 155 ###### implementation 156 config = lib.mkIf cfg.enable { 157 environment.systemPackages = [ cfg.package ]; 158 159 # Note: the following options are also declared in virtualisation.lxc, but 160 # the latter can't be simply enabled to reuse the formers, because it 161 # does a bunch of unrelated things. 162 systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ]; 163 164 security.apparmor = { 165 packages = [ cfg.lxcPackage ]; 166 policies = { 167 "bin.lxc-start".profile = '' 168 include ${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start 169 ''; 170 "lxc-containers".profile = '' 171 include ${cfg.lxcPackage}/etc/apparmor.d/lxc-containers 172 ''; 173 }; 174 }; 175 176 # TODO: remove once LXD gets proper support for cgroupsv2 177 # (currently most of the e.g. CPU accounting stuff doesn't work) 178 systemd.enableUnifiedCgroupHierarchy = false; 179 180 systemd.sockets.lxd = { 181 description = "LXD UNIX socket"; 182 wantedBy = [ "sockets.target" ]; 183 184 socketConfig = { 185 ListenStream = "/var/lib/lxd/unix.socket"; 186 SocketMode = "0660"; 187 SocketGroup = "lxd"; 188 Service = "lxd.service"; 189 }; 190 }; 191 192 systemd.services.lxd = { 193 description = "LXD Container Management Daemon"; 194 195 wantedBy = [ "multi-user.target" ]; 196 after = [ 197 "network-online.target" 198 (lib.mkIf config.virtualisation.lxc.lxcfs.enable "lxcfs.service") 199 ]; 200 requires = [ 201 "network-online.target" 202 "lxd.socket" 203 (lib.mkIf config.virtualisation.lxc.lxcfs.enable "lxcfs.service") 204 ]; 205 documentation = [ "man:lxd(1)" ]; 206 207 path = [ pkgs.util-linux ] 208 ++ lib.optional cfg.zfsSupport config.boot.zfs.package; 209 210 environment = lib.mkIf (cfg.ui.enable) { 211 "LXD_UI" = cfg.ui.package; 212 }; 213 214 serviceConfig = { 215 ExecStart = "@${cfg.package}/bin/lxd lxd --group lxd"; 216 ExecStartPost = "${cfg.package}/bin/lxd waitready --timeout=${cfg.startTimeout}"; 217 ExecStop = "${cfg.package}/bin/lxd shutdown"; 218 219 KillMode = "process"; # when stopping, leave the containers alone 220 LimitMEMLOCK = "infinity"; 221 LimitNOFILE = "1048576"; 222 LimitNPROC = "infinity"; 223 TasksMax = "infinity"; 224 225 Restart = "on-failure"; 226 TimeoutStartSec = "${cfg.startTimeout}s"; 227 TimeoutStopSec = "30s"; 228 229 # By default, `lxd` loads configuration files from hard-coded 230 # `/usr/share/lxc/config` - since this is a no-go for us, we have to 231 # explicitly tell it where the actual configuration files are 232 Environment = lib.mkIf (config.virtualisation.lxc.lxcfs.enable) 233 "LXD_LXC_TEMPLATE_CONFIG=${pkgs.lxcfs}/share/lxc/config"; 234 }; 235 }; 236 237 systemd.services.lxd-preseed = lib.mkIf (cfg.preseed != null) { 238 description = "LXD initialization with preseed file"; 239 wantedBy = ["multi-user.target"]; 240 requires = ["lxd.service"]; 241 after = ["lxd.service"]; 242 243 script = '' 244 ${pkgs.coreutils}/bin/cat ${preseedFormat.generate "lxd-preseed.yaml" cfg.preseed} | ${cfg.package}/bin/lxd init --preseed 245 ''; 246 247 serviceConfig = { 248 Type = "oneshot"; 249 }; 250 }; 251 252 users.groups.lxd = {}; 253 254 users.users.root = { 255 subUidRanges = [ { startUid = 1000000; count = 65536; } ]; 256 subGidRanges = [ { startGid = 1000000; count = 65536; } ]; 257 }; 258 259 boot.kernel.sysctl = lib.mkIf cfg.recommendedSysctlSettings { 260 "fs.inotify.max_queued_events" = 1048576; 261 "fs.inotify.max_user_instances" = 1048576; 262 "fs.inotify.max_user_watches" = 1048576; 263 "vm.max_map_count" = 262144; # TODO: Default vm.max_map_count has been increased system-wide 264 "kernel.dmesg_restrict" = 1; 265 "net.ipv4.neigh.default.gc_thresh3" = 8192; 266 "net.ipv6.neigh.default.gc_thresh3" = 8192; 267 "kernel.keys.maxkeys" = 2000; 268 }; 269 270 boot.kernelModules = [ "veth" "xt_comment" "xt_CHECKSUM" "xt_MASQUERADE" "vhost_vsock" ] 271 ++ lib.optionals (!config.networking.nftables.enable) [ "iptable_mangle" ]; 272 }; 273}