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