-
-
Notifications
You must be signed in to change notification settings - Fork 17.5k
Modular services #372170
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
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.
|
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 |
|
Does this take any position on configuration files (like a recommended way of integrating with NixOS/rfcs#42 for example)? |
|
The proof of concept uses an option tree that starts with the name of the software, so you'd have options in a service like
We could pick a fixed prefix instead, e.g.
42 is great in many contexts, so definitely worth a mention. As for integrating, if we go with |
|
My experience reusing NixOS modules as «modular» services on non-NixOS is that sometimes the config generation is put into a |
|
@7c6f434c We could make it a convention to put config file stuff in a separate module that doesn't have any process-related options or definitions. This module is then responsible for declaring options like The main module imports the config file module and adds a generic We don't have something for symlinked config files yet ("
Having second thoughts.
|
infinisil
left a comment
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.
Me and @hsjobeki looked at this together and only had some minor nits. We approve of this direction overall!
We believe this can be merged without an RFC after resolving these discussion points that were brought up:
- Perhaps having a brief guideline to describe best practices for services depending on other services (#372170 (review))
- A better name for
system.servicesthat won't be confused withsystemd.services(#372170 (comment))
We believe these points don't need to be addressed in this PR, but can be follow-up work:
- Automated docs for the service-specific options (brought up in discussion with @hsjobeki)
- Establishing best practices for config generation, "/etc", etc. (#372170 (comment))
| ```nix | ||
| { | ||
| system.services.httpd = { | ||
| imports = [ nixpkgs.modules.services.foo ]; |
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.
| imports = [ nixpkgs.modules.services.foo ]; | |
| imports = [ pkgs.foo.services.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.
That would only make sense if we finally invert the relationship between derivations and attribute sets: Currently derivations have attrs attached, but an application (package) actually has service modules and derivations attached. See ngi-nix/ngipkgs#507 for an explicit implementation of that notion (where we call applications "projects", but that will likely change).
Yes, it's not strictly required to make sense, but it would help a lot.
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 also makes sense for a broadly defined package, which has Nix-specific behaviors like outPath, as well as attributes like tests and services that are about using the outputs.
I can not make the introduction of ngipkgs-style applications a prerequisite for this PR, but we could change our conventions away from passthru, into applications when such a change materializes.
| types.package | ||
| // { | ||
| # require mainProgram for this conversion | ||
| check = v: v.type or null == "derivation" && v ? meta.mainProgram; | ||
| } |
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.
Style nit: Putting this in a variable would improve readability (though also negligibly decrease performance)
|
I like this change overall pretty much. I have the concern that services are now more difficult to discover since they have (even) more ways to be enabled now. So i hope we understand the significance that is going to be added with this PR and thinking that it really presses on the need of improved discoverability. Also adding those services opens a new opportunity of abstracting away from systemd and getting closer to the actual user via the options that each specific service defines. Layered approach ontop of systemd or alternatives. While systemd might be a good interface for computer systems it is not always the best language towards configuring more high level or user oriented services. |
Without a proper introduction it's really really hard to make sense of the examples, and where values come from; which are arbitrary, which are conventional, which are hard-coded into some part of the framework. Co-authored-by: Valentin Gagarin <[email protected]>
This reverts commit 121c0d9efad7cf74ccc70ce3e69763e9a8822f3b.
This unblocks modular services while providing opportunity to improve this when a solution is agreed on.
d86dec7 to
b51a6c3
Compare
I think we should backport it nonetheless.
|
@aanderse Even without the features you've listed, modular services can be used by adding the necessary NixOS configuration by hand. We should improve on that, but I consider them to be fairly independent additions that will each require some design and implementation work, and they don't block this feature.
Yes, this would fit. We'd have to work out some details, but I'd propose that a service module has something like { lib, config, user, ... }: {
config.allocateUser = true;
}The Other designs are possible. It depends a bit. Do we want the generic interface to be lean, or should it make it as easy as possible to deal with environment differences. For example, a Lean interface, worse DX: config = { } // optionalAttrs (options?allocateUser) {
allocateUser = true;
};or we could have Support for multiple users in one (leaf) service is probably not needed. I'd expect services that run as multiple uids to be composite services, where each of the leaf services can request their own user as needed.
Wouldn't be inherently modular if they can be placed in arbitrary places.
I'm not sure this is desirable, but we could at least add an option to enable a service to check the suitability of its host NixOS. This means adding a new option to {
nixos.assertions = nixos@{config, ...}:
[ /* usual assertion format, referencing nixos and any local configs from the service */ ];
}and this would then be picked up, with some context added to the message. If there's things in sysctl that we can enable safely, we could add options to cause that to happen, but all of sysctl seems too much.
This is also trouble if you want to run multiple instances of a service.
Could be done. Would be good to have a check that no more than on service opens the same port. That's a broader set of ports than "public" ports though, so that will require differentiation in the service modules, if we want such a feature. I think a networked service could look like: { ports, ... }:
{
ports.http = { mustBe = 80; zone = "public"; };
ports.ipc = { zone = "localhost"; type = "udp"; }; # arbitrary local port, deterministically allocated, usually the same between generations
foo.settings.httpPort = ports.http;
foo.settings.ipcPort = ports.ipc;
# ...
}Whether to automatically open ports in the I am confident that we'll land on good solutions for most of these things, and it's more than ok for a service to be a non-modular NixOS service. |
aanderse
left a comment
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.
@roberth thanks for your work and comments
at this point i agree that we should go ahead and merge this with the idea that we can iterate - i would likely contribute (to at least a non nixos backend) and i know others who would as well 👍
|
Let's get this going! |
|
Backport failed for Please cherry-pick the changes locally and resolve any conflicts. git fetch origin release-25.05
git worktree add -d .worktree/backport-372170-to-release-25.05 origin/release-25.05
cd .worktree/backport-372170-to-release-25.05
git switch --create backport-372170-to-release-25.05
git cherry-pick -x b915f0c5c0307c82f7834d683cf81414ce73d01b 1acabeebed483fd8a682eb6b7ef538f5b99d35e2 109a6a9d1e13013a4d3e4220d79d0f599a10ac97 53f97deb26d3defe67bbe1223efd2c5c4b9def1e b7ded190104f8b8af63d10355377b094dbc032f1 7d7b678ed6664f42223e05113e596e0947bb1a04 03c4d4bc664963b488ad575f1993890d8cd9a650 b9e4118e6dd800509784b16234f43fd8e8fb3470 af04a80c7627a83e2c0eaf71b44c4a71397a6201 d20a5749486886032aedec736311e4c3d29a48c7 b51a6c3531c669c744778eb8c986bd7e9b19dcee 1f4e47626931468baeff906a38122af052d1e53a |
|
This should really have removed the redundant revert and squashed the nixos release change before merge. |
|
Ok. I'll adjust my dials to capture less history in Nixpkgs. |
|
I love this! Some notes: Composability brings to mind infuse.nix, a library that builds, for example, I thought of an API like this, could it work? I guess it has the problem of not being able to configure dependencies but I was thinking of having non-NixOS-managed service depenencies. Maybe DAGs or attrnames would work better? { config, lib, ... }:
# { _type = "systemd"; perMachineUuid = "..."; ... exports.url = ""; }
serviceInstances.database = modules.postgres {};
# { _type = "external"; exports.url = "redis://10.0.0.2:1234"; }
serviceInstances.cache = modules.redisExternal { host = "10.0.0.2"; port = 1234; auth = "?"; };
# produces dependency on `database`, automatically doing permissioning and provisioning secrets
serviceInstances.webapp = modules.webapp { inherit (config.serviceInstances) database cache; };
modules.webapp = { database, cache }: let
# resolve/fix/etc; just look for it in `config`
database = config.resolveServiceInstanceOrThrow database;
cache = config.resolveServiceInstanceOrThrow cache;
in {
env.DATABASE_URL = database.exports.url;
env.CACHE_URL = cache.exports.url;
require_units_at_startup = [
database # resolves to systemd.Requites = [ database.exports.systemd.unitName ];
cache # resolves to ??? (see below)
];
};We really need a contract system here, as specified in the Discourse thread in the issue text. For example, how do you specify dependencies on things that aren't managed by the current NixOS instance? With Seeing as modifying global state is disallowed (service instances can't set
Could this be expanded to support systemd's Also let's use abstractions and FHS as much as we can: e.g. systemd's |
|
Hi @axelkar
It is possible to add a translation layer like that on top, but for now I would recommend to stick to the normal module syntax, so that it's a bit easier to reason about potential changes we want to make to the implementation. A crucial aspect of this new usage pattern of the module system is more extensive use of I didn't spot something for
Currently we don't. Three possible solutions:
👍 That is definitely an important aspect of this system. I assume you mean host-global, and that would be the domain of normal NixOS modules; those model a whole host.
We might want to make that the default setting, perhaps?
SGTM. If other process manager integrations can also provide a degree of compatibility with features like that, we could even make them part of the portable interface. |
It's designed for derivations and overlays, but there's apparently an operating system named based on it named sixos. I couldn't find any examples earlier, but here's one. I do agree that it's probably not a good suit here, but I found it interesting. Both the NixOS module system and overlays/Nixpkgs do (in my mind at least) use fixed points, but the NixOS module system has better syntax and looks more beginner-friendly. One downside is that there is only a number representing ordering and AFAIK you can't update a value like you would with overlays (e.g. override a package based on the old version and copy the old version into another attribute). Eval costs and verbose error messages are another thing.
If I was being unclear, global state was referring to NixOS system-wide configuration. Submodules can't by themselves affect anything outside the submodules. Modularity in services here could mean that you can run them without the static global state. See services-flake and Devenv services. There's also wrapper-manager that takes everything even further by putting configuration into wrappers. I personally wouldn't use it with daily desktop programs because they wouldn't be able to see configuration changes. Or.. I guess I do use Nixvim with its wrapper so I have to restart Neovim when I make changes... I think we might want to keep this reloadabilty in mind, because some services like Nginx support it too.
I agree! For most services, there are no downsides and without service users everything is even more modular.
I'm thinking we could even make this the default and give modules parameters with the state, cache (if enabled), etc. directories. |
|
Hey all, just a reminder that this is just a start! We now have tracking issue and a Matrix room for discussions |
I invite you to collaborate on modular services. (EDIT: and you can make PRs or push to this branch)
A lot is described in
nixos/doc/manual/development/modular-services.md(see PR files), but in a nutshell:system.servicestree where arbitrary services can be added by means ofimportsAs an example, I've ported one service,
ghostunnelto 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 = relaxedsandbox = truenix-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.