grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / network-filesystems / ceph.nix
blob5961731dbf446a14fdd4c806fed373408d282282
1 { config, lib, pkgs, ... }:
2 let
3   cfg = config.services.ceph;
5   # function that translates "camelCaseOptions" to "camel case options", credits to tilpner in #nixos@freenode
6   expandCamelCase = lib.replaceStrings lib.upperChars (map (s: " ${s}") lib.lowerChars);
7   expandCamelCaseAttrs = lib.mapAttrs' (name: value: lib.nameValuePair (expandCamelCase name) value);
9   makeServices = daemonType: daemonIds:
10     lib.mkMerge (map (daemonId:
11       { "ceph-${daemonType}-${daemonId}" = makeService daemonType daemonId cfg.global.clusterName cfg.${daemonType}.package; })
12       daemonIds);
14   makeService = daemonType: daemonId: clusterName: ceph:
15     let
16       stateDirectory = "ceph/${if daemonType == "rgw" then "radosgw" else daemonType}/${clusterName}-${daemonId}"; in {
17     enable = true;
18     description = "Ceph ${builtins.replaceStrings lib.lowerChars lib.upperChars daemonType} daemon ${daemonId}";
19     after = [ "network-online.target" "time-sync.target" ] ++ lib.optional (daemonType == "osd") "ceph-mon.target";
20     wants = [ "network-online.target" "time-sync.target" ];
21     partOf = [ "ceph-${daemonType}.target" ];
22     wantedBy = [ "ceph-${daemonType}.target" ];
24     path = [ pkgs.getopt ];
26     # Don't start services that are not yet initialized
27     unitConfig.ConditionPathExists = "/var/lib/${stateDirectory}/keyring";
28     startLimitBurst =
29       if daemonType == "osd" then 30 else if lib.elem daemonType ["mgr" "mds"] then 3 else 5;
30     startLimitIntervalSec = 60 * 30;  # 30 mins
32     serviceConfig = {
33       LimitNOFILE = 1048576;
34       LimitNPROC = 1048576;
35       Environment = "CLUSTER=${clusterName}";
36       ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
37       PrivateDevices = "yes";
38       PrivateTmp = "true";
39       ProtectHome = "true";
40       ProtectSystem = "full";
41       Restart = "on-failure";
42       StateDirectory = stateDirectory;
43       User = "ceph";
44       Group = if daemonType == "osd" then "disk" else "ceph";
45       ExecStart = ''${ceph.out}/bin/${if daemonType == "rgw" then "radosgw" else "ceph-${daemonType}"} \
46                     -f --cluster ${clusterName} --id ${daemonId}'';
47     } // lib.optionalAttrs (daemonType == "osd") {
48       ExecStartPre = "${ceph.lib}/libexec/ceph/ceph-osd-prestart.sh --id ${daemonId} --cluster ${clusterName}";
49       RestartSec = "20s";
50       PrivateDevices = "no"; # osd needs disk access
51     } // lib.optionalAttrs ( daemonType == "mon") {
52       RestartSec = "10";
53     };
54   };
56   makeTarget = daemonType:
57     {
58       "ceph-${daemonType}" = {
59         description = "Ceph target allowing to start/stop all ceph-${daemonType} services at once";
60         partOf = [ "ceph.target" ];
61         wantedBy = [ "ceph.target" ];
62         before = [ "ceph.target" ];
63         unitConfig.StopWhenUnneeded = true;
64       };
65     };
68   options.services.ceph = {
69     # Ceph has a monolithic configuration file but different sections for
70     # each daemon, a separate client section and a global section
71     enable = lib.mkEnableOption "Ceph global configuration";
73     global = {
74       fsid = lib.mkOption {
75         type = lib.types.str;
76         example = ''
77           433a2193-4f8a-47a0-95d2-209d7ca2cca5
78         '';
79         description = ''
80           Filesystem ID, a generated uuid, its must be generated and set before
81           attempting to start a cluster
82         '';
83       };
85       clusterName = lib.mkOption {
86         type = lib.types.str;
87         default = "ceph";
88         description = ''
89           Name of cluster
90         '';
91       };
93       mgrModulePath = lib.mkOption {
94         type = lib.types.path;
95         default = "${pkgs.ceph.lib}/lib/ceph/mgr";
96         defaultText = lib.literalExpression ''"''${pkgs.ceph.lib}/lib/ceph/mgr"'';
97         description = ''
98           Path at which to find ceph-mgr modules.
99         '';
100       };
102       monInitialMembers = lib.mkOption {
103         type = with lib.types; nullOr commas;
104         default = null;
105         example = ''
106           node0, node1, node2
107         '';
108         description = ''
109           List of hosts that will be used as monitors at startup.
110         '';
111       };
113       monHost = lib.mkOption {
114         type = with lib.types; nullOr commas;
115         default = null;
116         example = ''
117           10.10.0.1, 10.10.0.2, 10.10.0.3
118         '';
119         description = ''
120           List of hostname shortnames/IP addresses of the initial monitors.
121         '';
122       };
124       maxOpenFiles = lib.mkOption {
125         type = lib.types.int;
126         default = 131072;
127         description = ''
128           Max open files for each OSD daemon.
129         '';
130       };
132       authClusterRequired = lib.mkOption {
133         type = lib.types.enum [ "cephx" "none" ];
134         default = "cephx";
135         description = ''
136           Enables requiring daemons to authenticate with eachother in the cluster.
137         '';
138       };
140       authServiceRequired = lib.mkOption {
141         type = lib.types.enum [ "cephx" "none" ];
142         default = "cephx";
143         description = ''
144           Enables requiring clients to authenticate with the cluster to access services in the cluster (e.g. radosgw, mds or osd).
145         '';
146       };
148       authClientRequired = lib.mkOption {
149         type = lib.types.enum [ "cephx" "none" ];
150         default = "cephx";
151         description = ''
152           Enables requiring the cluster to authenticate itself to the client.
153         '';
154       };
156       publicNetwork = lib.mkOption {
157         type = with lib.types; nullOr commas;
158         default = null;
159         example = ''
160           10.20.0.0/24, 192.168.1.0/24
161         '';
162         description = ''
163           A comma-separated list of subnets that will be used as public networks in the cluster.
164         '';
165       };
167       clusterNetwork = lib.mkOption {
168         type = with lib.types; nullOr commas;
169         default = null;
170         example = ''
171           10.10.0.0/24, 192.168.0.0/24
172         '';
173         description = ''
174           A comma-separated list of subnets that will be used as cluster networks in the cluster.
175         '';
176       };
178       rgwMimeTypesFile = lib.mkOption {
179         type = with lib.types; nullOr path;
180         default = "${pkgs.mailcap}/etc/mime.types";
181         defaultText = lib.literalExpression ''"''${pkgs.mailcap}/etc/mime.types"'';
182         description = ''
183           Path to mime types used by radosgw.
184         '';
185       };
186     };
188     extraConfig = lib.mkOption {
189       type = with lib.types; attrsOf str;
190       default = {};
191       example = {
192         "ms bind ipv6" = "true";
193       };
194       description = ''
195         Extra configuration to add to the global section. Use for setting values that are common for all daemons in the cluster.
196       '';
197     };
199     mgr = {
200       enable = lib.mkEnableOption "Ceph MGR daemon";
201       daemons = lib.mkOption {
202         type = with lib.types; listOf str;
203         default = [];
204         example = [ "name1" "name2" ];
205         description = ''
206           A list of names for manager daemons that should have a service created. The names correspond
207           to the id part in ceph i.e. [ "name1" ] would result in mgr.name1
208         '';
209       };
210       package = lib.mkPackageOption pkgs "ceph" { };
211       extraConfig = lib.mkOption {
212         type = with lib.types; attrsOf str;
213         default = {};
214         description = ''
215           Extra configuration to add to the global section for manager daemons.
216         '';
217       };
218     };
220     mon = {
221       enable = lib.mkEnableOption "Ceph MON daemon";
222       daemons = lib.mkOption {
223         type = with lib.types; listOf str;
224         default = [];
225         example = [ "name1" "name2" ];
226         description = ''
227           A list of monitor daemons that should have a service created. The names correspond
228           to the id part in ceph i.e. [ "name1" ] would result in mon.name1
229         '';
230       };
231       package = lib.mkPackageOption pkgs "ceph" { };
232       extraConfig = lib.mkOption {
233         type = with lib.types; attrsOf str;
234         default = {};
235         description = ''
236           Extra configuration to add to the monitor section.
237         '';
238       };
239     };
241     osd = {
242       enable = lib.mkEnableOption "Ceph OSD daemon";
243       daemons = lib.mkOption {
244         type = with lib.types; listOf str;
245         default = [];
246         example = [ "name1" "name2" ];
247         description = ''
248           A list of OSD daemons that should have a service created. The names correspond
249           to the id part in ceph i.e. [ "name1" ] would result in osd.name1
250         '';
251       };
252       package = lib.mkPackageOption pkgs "ceph" { };
253       extraConfig = lib.mkOption {
254         type = with lib.types; attrsOf str;
255         default = {
256           "osd journal size" = "10000";
257           "osd pool default size" = "3";
258           "osd pool default min size" = "2";
259           "osd pool default pg num" = "200";
260           "osd pool default pgp num" = "200";
261           "osd crush chooseleaf type" = "1";
262         };
263         description = ''
264           Extra configuration to add to the OSD section.
265         '';
266       };
267     };
269     mds = {
270       enable = lib.mkEnableOption "Ceph MDS daemon";
271       daemons = lib.mkOption {
272         type = with lib.types; listOf str;
273         default = [];
274         example = [ "name1" "name2" ];
275         description = ''
276           A list of metadata service daemons that should have a service created. The names correspond
277           to the id part in ceph i.e. [ "name1" ] would result in mds.name1
278         '';
279       };
280       package = lib.mkPackageOption pkgs "ceph" { };
281       extraConfig = lib.mkOption {
282         type = with lib.types; attrsOf str;
283         default = {};
284         description = ''
285           Extra configuration to add to the MDS section.
286         '';
287       };
288     };
290     rgw = {
291       enable = lib.mkEnableOption "Ceph RadosGW daemon";
292       package = lib.mkPackageOption pkgs "ceph" { };
293       daemons = lib.mkOption {
294         type = with lib.types; listOf str;
295         default = [];
296         example = [ "name1" "name2" ];
297         description = ''
298           A list of rados gateway daemons that should have a service created. The names correspond
299           to the id part in ceph i.e. [ "name1" ] would result in client.name1, radosgw daemons
300           aren't daemons to cluster in the sense that OSD, MGR or MON daemons are. They are simply
301           daemons, from ceph, that uses the cluster as a backend.
302         '';
303       };
304     };
306     client = {
307       enable = lib.mkEnableOption "Ceph client configuration";
308       extraConfig = lib.mkOption {
309         type = with lib.types; attrsOf (attrsOf str);
310         default = {};
311         example = lib.literalExpression ''
312           {
313             # This would create a section for a radosgw daemon named node0 and related
314             # configuration for it
315             "client.radosgw.node0" = { "some config option" = "true"; };
316           };
317         '';
318         description = ''
319           Extra configuration to add to the client section. Configuration for rados gateways
320           would be added here, with their own sections, see example.
321         '';
322       };
323     };
324   };
326   config = lib.mkIf config.services.ceph.enable {
327     assertions = [
328       { assertion = cfg.global.fsid != "";
329         message = "fsid has to be set to a valid uuid for the cluster to function";
330       }
331       { assertion = cfg.mon.enable -> cfg.mon.daemons != [];
332         message = "have to set id of atleast one MON if you're going to enable Monitor";
333       }
334       { assertion = cfg.mds.enable -> cfg.mds.daemons != [];
335         message = "have to set id of atleast one MDS if you're going to enable Metadata Service";
336       }
337       { assertion = cfg.osd.enable -> cfg.osd.daemons != [];
338         message = "have to set id of atleast one OSD if you're going to enable OSD";
339       }
340       { assertion = cfg.mgr.enable -> cfg.mgr.daemons != [];
341         message = "have to set id of atleast one MGR if you're going to enable MGR";
342       }
343     ];
345     warnings = lib.optional (cfg.global.monInitialMembers == null)
346       "Not setting up a list of members in monInitialMembers requires that you set the host variable for each mon daemon or else the cluster won't function";
348     environment.etc."ceph/ceph.conf".text = let
349       # Merge the extraConfig set for mgr daemons, as mgr don't have their own section
350       globalSection = expandCamelCaseAttrs (cfg.global // cfg.extraConfig // lib.optionalAttrs cfg.mgr.enable cfg.mgr.extraConfig);
351       # Remove all name-value pairs with null values from the attribute set to avoid making empty sections in the ceph.conf
352       globalSection' = lib.filterAttrs (name: value: value != null) globalSection;
353       totalConfig = {
354           global = globalSection';
355         } // lib.optionalAttrs (cfg.mon.enable && cfg.mon.extraConfig != {}) { mon = cfg.mon.extraConfig; }
356           // lib.optionalAttrs (cfg.mds.enable && cfg.mds.extraConfig != {}) { mds = cfg.mds.extraConfig; }
357           // lib.optionalAttrs (cfg.osd.enable && cfg.osd.extraConfig != {}) { osd = cfg.osd.extraConfig; }
358           // lib.optionalAttrs (cfg.client.enable && cfg.client.extraConfig != {})  cfg.client.extraConfig;
359       in
360         lib.generators.toINI {} totalConfig;
362     users.users.ceph = {
363       uid = config.ids.uids.ceph;
364       description = "Ceph daemon user";
365       group = "ceph";
366       extraGroups = [ "disk" ];
367     };
369     users.groups.ceph = {
370       gid = config.ids.gids.ceph;
371     };
373     systemd.services = let
374       services = []
375         ++ lib.optional cfg.mon.enable (makeServices "mon" cfg.mon.daemons)
376         ++ lib.optional cfg.mds.enable (makeServices "mds" cfg.mds.daemons)
377         ++ lib.optional cfg.osd.enable (makeServices "osd" cfg.osd.daemons)
378         ++ lib.optional cfg.rgw.enable (makeServices "rgw" cfg.rgw.daemons)
379         ++ lib.optional cfg.mgr.enable (makeServices "mgr" cfg.mgr.daemons);
380       in
381         lib.mkMerge services;
383     systemd.targets = let
384       targets = [
385         { ceph = {
386           description = "Ceph target allowing to start/stop all ceph service instances at once";
387           wantedBy = [ "multi-user.target" ];
388           unitConfig.StopWhenUnneeded = true;
389         }; } ]
390         ++ lib.optional cfg.mon.enable (makeTarget "mon")
391         ++ lib.optional cfg.mds.enable (makeTarget "mds")
392         ++ lib.optional cfg.osd.enable (makeTarget "osd")
393         ++ lib.optional cfg.rgw.enable (makeTarget "rgw")
394         ++ lib.optional cfg.mgr.enable (makeTarget "mgr");
395       in
396         lib.mkMerge targets;
398     systemd.tmpfiles.settings."10-ceph" = let
399       defaultConfig = {
400         user = "ceph";
401         group = "ceph";
402       };
403     in {
404       "/etc/ceph".d = defaultConfig;
405       "/run/ceph".d = defaultConfig // { mode = "0770"; };
406       "/var/lib/ceph".d = defaultConfig;
407       "/var/lib/ceph/mgr".d = lib.mkIf (cfg.mgr.enable) defaultConfig;
408       "/var/lib/ceph/mon".d = lib.mkIf (cfg.mon.enable) defaultConfig;
409       "/var/lib/ceph/osd".d = lib.mkIf (cfg.osd.enable) defaultConfig;
410     };
411   };