-
-
Notifications
You must be signed in to change notification settings - Fork 14.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Modular services #372170
base: master
Are you sure you want to change the base?
Modular services #372170
Conversation
9b8c13c
to
1ee1ac4
Compare
let's elaborate on that so people who may not be as familiar with the history of this PR up to this point stand a chance at understanding this the new
the above is just a list of the most common/useful patterns that many nixos service oriented modules use, but anyone else should feel free to edit this comment and add to it if they feel it useful to do so - as well as check off the functionality if they contribute it to this PR thanks for getting the ball rolling on this one @roberth 🙇♂️ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like the idea and the way services can be imported in the top-level attrsOf
. I'm already looking forward to having multiple instances of a database!
From a theoretical point of view, I understand the nesting of services but I'm not sure to understand the use in practice. Would this be used to provide opinionated coupling between services? For example there would be a service grouping Nextcloud and Postgres and others to provide an out-of-the-box experience? And this would allow to split that big module into multiple ones?
You mentioned the pre-RFC on decoupling services and I'm the one that wrote it. There are some common goals here and I'd love to collaborate. One way would be to write the systemd implementation of the generic service using the structural typing I propose in the pre-RFC. But there are still questions to be elucidated there so not (yet) sure it's the ideal solution. And also, I don't want to pull the rug on "my" side for no good reason. I know you're way more proficient in the module system than I am so I'd love to get your input on how we could work together (us - the community) so that both RFCs can cooperate, merge? At least not hinder one another.
I had also a few noob questions about modules. I've been using modules a lot but not that deep.
Thanks for progressing on this!
|
||
- No `daemon.*` options. https://github.com/NixOS/nixpkgs/pull/267111/files#r1723206521 | ||
|
||
- For now, do not add an `enable` option, because it's ambiguous. Does it disable at the Nix level (not generate anything) or at the systemd level (generate a service that is disabled)? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to keep the distinction. I had cases where I wanted one or the other.
options.services = mkOption { | ||
type = types.attrsOf ( | ||
types.submoduleWith { | ||
class = "service"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noob question: what does class
do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's very basic "type checking" so that you don't load a normal NixOS module where a service is expected or vice versa.
The typing is "gradual" - if you don't specify it in a module or submodule, that counts as a match. In practice, this means modules have to specify _class
to get this benefit. It's not so widely used yet. I think it's a 2 or 3 year old feature, but limited usefulness and not widely advertized. Flake-parts flake.modules
does set it, but that's more recent.
See also evalModules
class
in the manual.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense now. The manual helped also, specifically:
If the class attribute is set and non-null, the module system will reject imports with a different _class declaration.
''; | ||
type = types.lazyAttrsOf types.deferredModule; | ||
default = { }; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose there will also be systemd.timers
and the other?
}; | ||
config = { | ||
# Note that this is the systemd.services option above, not the system one. | ||
systemd.services."" = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the point of this empty service?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They're prefixed by the service attribute path, or you could say ""
is the suffix.
system.services."foo".systemd.service."bar"
=> systemd.services.foo-bar
system.services."foo".systemd.service.""
=> systemd.services.foo
system.services."foo".services."db".systemd.service."autovacuum"
=> systemd.services.foo-db-autovacuum
types.submoduleWith { | ||
class = "service"; | ||
modules = [ | ||
./service.nix |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just checking I'm understanding this correctly, this is importing itself? It's to support sub-services, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. This constructs a self-referential type - see visible = "shallow";
which makes that practical. It makes arbitrarily nested services possible. "Infinite types" are ok in the module system, with no noteworthy disadvantages.
In practice, 4 is already very deep, I think, but we don't need to enforce anything here.
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/pre-rfc-decouple-services-using-structured-typing/58257/16 |
This can be used for groupings of multiple "off-the-shelf" services into a common configuration, or for non-trivial services that consist of multiple executables, such as apache cassandra, or even a mix, such as pulsar. I think the distinguishing factor is that when a sub-service is an off the shelf one, such as a database, admins may prefer to have a single db service with multiple apps that consume it. @ibizaman this is where I think the ideas you've worked out apply.
Of course! I will have a look soon-ish.
Happy to help :) |
Agreed. The end user should have the choice of either an instance per service or one instance for all of them.
For non-trivial packages with multiple binaries, I indeed don't see how contracts can apply. But if we're talking about multiple services working together, this is where our work could be merged and become even more powerful. If one write a group of services, like you allow, and the link between those services are contracts, then we can easily swap one of those services for another one. |
|
Perhaps Adding any extra words is probably a net negative. Explaining extra wordsSystemIt's the place where service modules are connected to the system (as in e.g. ModulePutting the word module or modular in there would be wrong, because that option is where the modules are consumed and turned into a concrete configuration, such that the modules become irrelevant. { serviceModules, ... }:
{
systemServices.etcd = { imports = [ serviceModules.etcd ]; };
} PortablePortability is similarly irrelevant, because it is a property of a module, and it is not even a required property for these services. NixOSPutting NixOS in the name, besides being redundant, would seem to imply that the modules are not portable, which is also not necessarily the case. (though I did shift the goalposts a bit for that second argument) An intriguing thought is that "subsystem" could replace "service", which seems appropriate when these things encompass more than a process and its execution settings, while also giving space for multiple services to exist within it. |
About naming, it seems you haven't considered changing the 'services' part 🤔 Systemd base resource names are So in our case we could have Although this might easily increase the coverage support for types of manageable systemd resources, it might be too much for this proposal 🤔 |
Similar problem because we do have |
systemUnits then? |
This feature isn't really about systemd units. For instance, the |
I invite you to collaborate on modular services.
A lot is described in
nixos/doc/manual/development/modular-services.md
(see PR files), but in a nutshell:system.services
tree where arbitrary services can be added by means ofimports
As an example, I've ported one service,
ghostunnel
to have a modular service.Some services will be more complicated, requiring more than the current facilities. These can be added.
This PR does not require that all services become modular services.
Things done
nix.conf
? (See Nix manual)sandbox = relaxed
sandbox = true
nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD"
. Note: all changes have to be committed, also see nixpkgs-review usage./result/bin/
)Add a 👍 reaction to pull requests you find important.