1 { config, lib, pkgs, ... }:
6 cfg = config.services.ceph;
8 # function that translates "camelCaseOptions" to "camel case options", credits to tilpner in #nixos@freenode
9 expandCamelCase = replaceStrings upperChars (map (s: " ${s}") lowerChars);
10 expandCamelCaseAttrs = mapAttrs' (name: value: nameValuePair (expandCamelCase name) value);
12 makeServices = daemonType: daemonIds:
13 mkMerge (map (daemonId:
14 { "ceph-${daemonType}-${daemonId}" = makeService daemonType daemonId cfg.global.clusterName cfg.${daemonType}.package; })
17 makeService = daemonType: daemonId: clusterName: ceph:
19 stateDirectory = "ceph/${if daemonType == "rgw" then "radosgw" else daemonType}/${clusterName}-${daemonId}"; in {
21 description = "Ceph ${builtins.replaceStrings lowerChars upperChars daemonType} daemon ${daemonId}";
22 after = [ "network-online.target" "time-sync.target" ] ++ optional (daemonType == "osd") "ceph-mon.target";
23 wants = [ "network-online.target" "time-sync.target" ];
24 partOf = [ "ceph-${daemonType}.target" ];
25 wantedBy = [ "ceph-${daemonType}.target" ];
27 path = [ pkgs.getopt ];
29 # Don't start services that are not yet initialized
30 unitConfig.ConditionPathExists = "/var/lib/${stateDirectory}/keyring";
32 if daemonType == "osd" then 30 else if lib.elem daemonType ["mgr" "mds"] then 3 else 5;
33 startLimitIntervalSec = 60 * 30; # 30 mins
36 LimitNOFILE = 1048576;
38 Environment = "CLUSTER=${clusterName}";
39 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
40 PrivateDevices = "yes";
43 ProtectSystem = "full";
44 Restart = "on-failure";
45 StateDirectory = stateDirectory;
47 Group = if daemonType == "osd" then "disk" else "ceph";
48 ExecStart = ''${ceph.out}/bin/${if daemonType == "rgw" then "radosgw" else "ceph-${daemonType}"} \
49 -f --cluster ${clusterName} --id ${daemonId}'';
50 } // optionalAttrs (daemonType == "osd") {
51 ExecStartPre = "${ceph.lib}/libexec/ceph/ceph-osd-prestart.sh --id ${daemonId} --cluster ${clusterName}";
53 PrivateDevices = "no"; # osd needs disk access
54 } // optionalAttrs ( daemonType == "mon") {
59 makeTarget = daemonType:
61 "ceph-${daemonType}" = {
62 description = "Ceph target allowing to start/stop all ceph-${daemonType} services at once";
63 partOf = [ "ceph.target" ];
64 wantedBy = [ "ceph.target" ];
65 before = [ "ceph.target" ];
66 unitConfig.StopWhenUnneeded = true;
71 options.services.ceph = {
72 # Ceph has a monolithic configuration file but different sections for
73 # each daemon, a separate client section and a global section
74 enable = mkEnableOption (lib.mdDoc "Ceph global configuration");
80 433a2193-4f8a-47a0-95d2-209d7ca2cca5
82 description = lib.mdDoc ''
83 Filesystem ID, a generated uuid, its must be generated and set before
84 attempting to start a cluster
88 clusterName = mkOption {
91 description = lib.mdDoc ''
96 mgrModulePath = mkOption {
98 default = "${pkgs.ceph.lib}/lib/ceph/mgr";
99 defaultText = literalExpression ''"''${pkgs.ceph.lib}/lib/ceph/mgr"'';
100 description = lib.mdDoc ''
101 Path at which to find ceph-mgr modules.
105 monInitialMembers = mkOption {
106 type = with types; nullOr commas;
111 description = lib.mdDoc ''
112 List of hosts that will be used as monitors at startup.
117 type = with types; nullOr commas;
120 10.10.0.1, 10.10.0.2, 10.10.0.3
122 description = lib.mdDoc ''
123 List of hostname shortnames/IP addresses of the initial monitors.
127 maxOpenFiles = mkOption {
130 description = lib.mdDoc ''
131 Max open files for each OSD daemon.
135 authClusterRequired = mkOption {
136 type = types.enum [ "cephx" "none" ];
138 description = lib.mdDoc ''
139 Enables requiring daemons to authenticate with eachother in the cluster.
143 authServiceRequired = mkOption {
144 type = types.enum [ "cephx" "none" ];
146 description = lib.mdDoc ''
147 Enables requiring clients to authenticate with the cluster to access services in the cluster (e.g. radosgw, mds or osd).
151 authClientRequired = mkOption {
152 type = types.enum [ "cephx" "none" ];
154 description = lib.mdDoc ''
155 Enables requiring the cluster to authenticate itself to the client.
159 publicNetwork = mkOption {
160 type = with types; nullOr commas;
163 10.20.0.0/24, 192.168.1.0/24
165 description = lib.mdDoc ''
166 A comma-separated list of subnets that will be used as public networks in the cluster.
170 clusterNetwork = mkOption {
171 type = with types; nullOr commas;
174 10.10.0.0/24, 192.168.0.0/24
176 description = lib.mdDoc ''
177 A comma-separated list of subnets that will be used as cluster networks in the cluster.
181 rgwMimeTypesFile = mkOption {
182 type = with types; nullOr path;
183 default = "${pkgs.mailcap}/etc/mime.types";
184 defaultText = literalExpression ''"''${pkgs.mailcap}/etc/mime.types"'';
185 description = lib.mdDoc ''
186 Path to mime types used by radosgw.
191 extraConfig = mkOption {
192 type = with types; attrsOf str;
195 "ms bind ipv6" = "true";
197 description = lib.mdDoc ''
198 Extra configuration to add to the global section. Use for setting values that are common for all daemons in the cluster.
203 enable = mkEnableOption (lib.mdDoc "Ceph MGR daemon");
205 type = with types; listOf str;
207 example = [ "name1" "name2" ];
208 description = lib.mdDoc ''
209 A list of names for manager daemons that should have a service created. The names correspond
210 to the id part in ceph i.e. [ "name1" ] would result in mgr.name1
213 package = mkPackageOptionMD pkgs "ceph" { };
214 extraConfig = mkOption {
215 type = with types; attrsOf str;
217 description = lib.mdDoc ''
218 Extra configuration to add to the global section for manager daemons.
224 enable = mkEnableOption (lib.mdDoc "Ceph MON daemon");
226 type = with types; listOf str;
228 example = [ "name1" "name2" ];
229 description = lib.mdDoc ''
230 A list of monitor daemons that should have a service created. The names correspond
231 to the id part in ceph i.e. [ "name1" ] would result in mon.name1
234 package = mkPackageOptionMD pkgs "ceph" { };
235 extraConfig = mkOption {
236 type = with types; attrsOf str;
238 description = lib.mdDoc ''
239 Extra configuration to add to the monitor section.
245 enable = mkEnableOption (lib.mdDoc "Ceph OSD daemon");
247 type = with types; listOf str;
249 example = [ "name1" "name2" ];
250 description = lib.mdDoc ''
251 A list of OSD daemons that should have a service created. The names correspond
252 to the id part in ceph i.e. [ "name1" ] would result in osd.name1
255 package = mkPackageOptionMD pkgs "ceph" { };
256 extraConfig = mkOption {
257 type = with types; attrsOf str;
259 "osd journal size" = "10000";
260 "osd pool default size" = "3";
261 "osd pool default min size" = "2";
262 "osd pool default pg num" = "200";
263 "osd pool default pgp num" = "200";
264 "osd crush chooseleaf type" = "1";
266 description = lib.mdDoc ''
267 Extra configuration to add to the OSD section.
273 enable = mkEnableOption (lib.mdDoc "Ceph MDS daemon");
275 type = with types; listOf str;
277 example = [ "name1" "name2" ];
278 description = lib.mdDoc ''
279 A list of metadata service daemons that should have a service created. The names correspond
280 to the id part in ceph i.e. [ "name1" ] would result in mds.name1
283 package = mkPackageOptionMD pkgs "ceph" { };
284 extraConfig = mkOption {
285 type = with types; attrsOf str;
287 description = lib.mdDoc ''
288 Extra configuration to add to the MDS section.
294 enable = mkEnableOption (lib.mdDoc "Ceph RadosGW daemon");
295 package = mkPackageOptionMD pkgs "ceph" { };
297 type = with types; listOf str;
299 example = [ "name1" "name2" ];
300 description = lib.mdDoc ''
301 A list of rados gateway daemons that should have a service created. The names correspond
302 to the id part in ceph i.e. [ "name1" ] would result in client.name1, radosgw daemons
303 aren't daemons to cluster in the sense that OSD, MGR or MON daemons are. They are simply
304 daemons, from ceph, that uses the cluster as a backend.
310 enable = mkEnableOption (lib.mdDoc "Ceph client configuration");
311 extraConfig = mkOption {
312 type = with types; attrsOf (attrsOf str);
314 example = literalExpression ''
316 # This would create a section for a radosgw daemon named node0 and related
317 # configuration for it
318 "client.radosgw.node0" = { "some config option" = "true"; };
321 description = lib.mdDoc ''
322 Extra configuration to add to the client section. Configuration for rados gateways
323 would be added here, with their own sections, see example.
329 config = mkIf config.services.ceph.enable {
331 { assertion = cfg.global.fsid != "";
332 message = "fsid has to be set to a valid uuid for the cluster to function";
334 { assertion = cfg.mon.enable -> cfg.mon.daemons != [];
335 message = "have to set id of atleast one MON if you're going to enable Monitor";
337 { assertion = cfg.mds.enable -> cfg.mds.daemons != [];
338 message = "have to set id of atleast one MDS if you're going to enable Metadata Service";
340 { assertion = cfg.osd.enable -> cfg.osd.daemons != [];
341 message = "have to set id of atleast one OSD if you're going to enable OSD";
343 { assertion = cfg.mgr.enable -> cfg.mgr.daemons != [];
344 message = "have to set id of atleast one MGR if you're going to enable MGR";
348 warnings = optional (cfg.global.monInitialMembers == null)
349 "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";
351 environment.etc."ceph/ceph.conf".text = let
352 # Merge the extraConfig set for mgr daemons, as mgr don't have their own section
353 globalSection = expandCamelCaseAttrs (cfg.global // cfg.extraConfig // optionalAttrs cfg.mgr.enable cfg.mgr.extraConfig);
354 # Remove all name-value pairs with null values from the attribute set to avoid making empty sections in the ceph.conf
355 globalSection' = filterAttrs (name: value: value != null) globalSection;
357 global = globalSection';
358 } // optionalAttrs (cfg.mon.enable && cfg.mon.extraConfig != {}) { mon = cfg.mon.extraConfig; }
359 // optionalAttrs (cfg.mds.enable && cfg.mds.extraConfig != {}) { mds = cfg.mds.extraConfig; }
360 // optionalAttrs (cfg.osd.enable && cfg.osd.extraConfig != {}) { osd = cfg.osd.extraConfig; }
361 // optionalAttrs (cfg.client.enable && cfg.client.extraConfig != {}) cfg.client.extraConfig;
363 generators.toINI {} totalConfig;
366 uid = config.ids.uids.ceph;
367 description = "Ceph daemon user";
369 extraGroups = [ "disk" ];
372 users.groups.ceph = {
373 gid = config.ids.gids.ceph;
376 systemd.services = let
378 ++ optional cfg.mon.enable (makeServices "mon" cfg.mon.daemons)
379 ++ optional cfg.mds.enable (makeServices "mds" cfg.mds.daemons)
380 ++ optional cfg.osd.enable (makeServices "osd" cfg.osd.daemons)
381 ++ optional cfg.rgw.enable (makeServices "rgw" cfg.rgw.daemons)
382 ++ optional cfg.mgr.enable (makeServices "mgr" cfg.mgr.daemons);
386 systemd.targets = let
389 description = "Ceph target allowing to start/stop all ceph service instances at once";
390 wantedBy = [ "multi-user.target" ];
391 unitConfig.StopWhenUnneeded = true;
393 ++ optional cfg.mon.enable (makeTarget "mon")
394 ++ optional cfg.mds.enable (makeTarget "mds")
395 ++ optional cfg.osd.enable (makeTarget "osd")
396 ++ optional cfg.rgw.enable (makeTarget "rgw")
397 ++ optional cfg.mgr.enable (makeTarget "mgr");
401 systemd.tmpfiles.rules = [
402 "d /etc/ceph - ceph ceph - -"
403 "d /run/ceph 0770 ceph ceph -"
404 "d /var/lib/ceph - ceph ceph - -"]
405 ++ optionals cfg.mgr.enable [ "d /var/lib/ceph/mgr - ceph ceph - -"]
406 ++ optionals cfg.mon.enable [ "d /var/lib/ceph/mon - ceph ceph - -"]
407 ++ optionals cfg.osd.enable [ "d /var/lib/ceph/osd - ceph ceph - -"];