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
nixos/digital-ocean-image: init (rebase) #66978
Conversation
Please feel free to squash and rebase your commit to make it tidy. Also, is it possible to include a test with it? |
No idea how to do this, I guess we could build the image - is there somewhere that's appropriate to introduce that? All the services require DO's internal network environment so I imagine testing any other parts would be a pretty involved process. |
i dont think a test is needed. I also don't know what to test here in the first place; we're just building an image. I don't think any of the other images have tests either. If they do, they're probably in the NixOps repo and not the Nixos one |
To be fair, there are some valid points to testing here:
So I imagine something that builds the image (or at least computing the Testing the services themselves via mocking the metadata network service or something similar seems like overkill, however, so yes; I agree that integration tests make more sense to run against DO via nixops. |
No worries, was just asking. :) |
networking = { | ||
firewall.allowedTCPPorts = [ 22 ]; | ||
hostName = mkDefault ""; # use Digital Ocean metadata server | ||
useNetworkd = mkDefault true; |
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.
Why have networkd
by default? This is an experimental feature that still has some known bugs. I'd prefer not enabling it by default on installations. Especially because it behaves differently from what people expect
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.
Turns out the answer is: Because I added it there. Which was a stupid mistake on my side! :D. we should remove it
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.
Hm, it's been working well for me... From initial testing turning it off it causes problems for the metadata init services and neither hostname nor ssh keys are making it to the VM :(
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 sounds like a (serious) bug in the NixOS networking module. By default networking
module should listen to DHCP requests on all interfaces. investigating...
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.
New theory network-online.target
works well on systemd-networkd
but maybe not with the default networking module. Workaround: add a Restart=always
to digitalocean-init
such that it keeps restarting until the network is available instead of it failing immediately...
This is me purely speculating though; I'll spin up a digitalocean box and do some actual debugging later tonight
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 just did this on my digitalocean image and it seems to work fine! curl will then do the retries with exponential backoff and whatnot
Weird, depending on which network target (link a diff if possible)? I tried connrefused but it did not work for me because curl seems to give up if the error specifically isn't ECONNREFUSED, which is generally the case when the network isn't available or the metadata server isn't reachable/routable.
Perhaps we could use it to unify all our cloud image metadata fetching steps? (Just a thought; and probably for a later PR).
Definitely worth looking into! But yeah, not necessarily for this PR.
They have a service called coreos-metadata.service
Cool, yeah, I think in general that's the way forward here. Whether built on top of afterburn or a bash loop around curl or anything else, it gives us a central point to place the init/detection logic. I'll at least restructure the units around this approach, then even if we kept the network-online dependency for now, it should be easy to make follow-up improvements. I'll also look into writing a retry loop, I'm just slightly wary about making sure that I choose the appropriate retry logic.
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.
Oh I'm running this on networkd
I just realised. That's probably why it works.
If you're wary about the retry-loop, we can also let systemd handle that, which is maybe more elegant:
[Service]
Restart=on-failure
RestartSec=zzz
StartLimitIntervalSec=yyy
StartLimitBurst=xxx
I think either way works. Systemd doesn't do exponential backoff though, but im not sure we require that. the link-local server isn't gonna crash from hammering. Restarting every 2 seconds for like 10 times is probably sufficient.
I'll see if I can package afterburner tonight
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.
If you're wary about the retry-loop, we can also let systemd handle that, which is maybe more elegant.
Though I wanted to, it requires that the service type be changed to simple
, which then makes it unusable as a dependency for other units. It does work okay for the individual units, but yeah the lack of backoff is slightly annoying... A loop will work fine.
the link-local server isn't gonna crash from hammering. Restarting every 2 seconds for like 10 times is probably sufficient
Probably, my concern is more that we don't want it to spam journald forever, but we do want to make sure it doesn't give up too early if the network is slow to start or whatever, because these services are important for making sure you can access the machine! While also being fairly responsive because if you wait 2 seconds, a bunch of services will start in the meantime.
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.
yeh not having it a oneshot is annoying. I didn't realise oneshot
didn't support Restart=
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.
So, pushed a few things:
- metadata.service added with a retry loop that can be used as a dependency so that services that need metadata can just wait on it before doing their thing. The service is ordered After dhcpcd or systemd-networkd (if you have them enabled) and thus the retry loop isn't likely to be needed except in unusual circumstances, as the service should just start as soon as an ip/route is assigned to the interface.
- sethostname and entropy services are wantedBy and Before network.target, so that any services that are ordered After network or network-online will wait for them. This does have the effect of delaying the startup of certain services until the droplet has an ip, but that's also kind of the point. networkd is pretty instantaneous, dhcpcd takes about 6 seconds.
- Some fixes to the userdata init service. It remains part of / After network-online.
- the password/sshkey services are just part of mult-user but ordered to take place before sshd so you can't have racy scenarios where the server is online and reachable but are unable to login.
- Some changes to image/init to allow them to share the config file because it seemed kind of silly to duplicate?
02d54ba
to
20877e1
Compare
options.virtualisation.digitalOcean = with types; { | ||
setRootPassword = mkOption { | ||
type = bool; | ||
default = true; |
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'm considering changing this default to false
because:
- It's a pretty insecure option, setting the password to something accessible from the metadata server to any unauthenticated user on the system that can access the network.
- It currently resets the root password on every boot rather than first boot. I should maybe add a path condition to make sure it only ever runs once?
- DO always tells me my custom images don't support root password setting, so I can't even get it to email me the password to test this. Maybe I'm using the wrong Distribution setting when uploading it?
|
||
} | ||
/* Give nice error messages if a user tries to turn off mutable users and enable | ||
* root password or SSH key fetching from Digital Ocean |
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.
Want to remove these defaults+assertions because mutableUsers=false
does not imply that /root/.ssh
is readonly, nor even /etc/shadow
. In fact, enabling these settings is a likely motivation/reason for a server to use mutableUsers=false
in the first place, so that it can provide auth details in some way other than via the nixos config.
4701648
to
07dcf84
Compare
script = '' | ||
set -eu | ||
DO_DELAY_ATTEMPTS=0 | ||
while ! curl -fsSLI http://169.254.169.254/metadata/v1.json > /dev/null; 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.
Should I be worried that this isn't using https? This is used for root passwords and such..
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.
Also it would be nice to download all files in this unit and store them somewhere to be accessed by the others (in a location only readable by root).
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'm more worried that unless you explicitly firewall it, any process can retrieve the same data. Though, the root password service in particular I think needs some changes and should probably also be disabled by default.
Also it would be nice to download all files in this unit and store them somewhere to be accessed by the others (in a location only readable by root).
I'd considered that but decided against it just because I didn't feel like deciding on a proper path for it or dealing with the corner cases:
- The hostname can be changed while the server is running, so you can restart that service to update it. Making it depend on data from the first makes that dependency relationship (and the meaning of the systemd service states) a bit more complicated?
- Each service was interested in a different endpoint, so downloading them all when most services never even run seemed like an unnecessary extra second of boot time.
Though, I believe the json blob contains everything... so each service can probably just use jq to pick out the pieces they need from that one file. I believe the hostname one is the only mutable piece of data so it could just continue to use the network to simplify things.
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.
Nope this is a Link-Local address so should be safe.
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.
"should be safe" doesn't make me feel safe! Can we check that it's a link-local address?
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 is by definition a link local address because it's in the link-local private ip range. Ipv4 mandates that the IP-range isn't next-hopped https://tools.ietf.org/html/rfc3927. Only people on the same physical link can talk over the link-local ip range
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.
Oh nice! I feel safe now
set -eo pipefail | ||
TEMPDIR=$(mktemp -d) | ||
curl -fsSL http://169.254.169.254/metadata/v1/vendor-data | munpack -tC $TEMPDIR | ||
ENTROPY_SEED=$(grep -rl "DigitalOcean Entropy Seed script" $TEMPDIR) |
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'd be interested to see an example of all these downloaded files to see what they look like and what other info they contain.
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'd be interested to see an example of all these downloaded files to see what they look like and what other info they contain.
if [[ -s $userData ]]; then | ||
# If the user-data looks like it could be a nix expression, | ||
# copy it over. Also, look for a magic three-hash comment and set | ||
# that as the channel. |
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.
How about doing something like nix-instantiate --eval '<nixpkgs/nixos>' -A config.system.stateVersion
to find the desired channel too?
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'm a bit wary of making significant changes to the logic here, and it comes straight from other modules. I guess ideally there should some be some common module or place for this to go where cleanup can be applied to nixos on all relevant cloud providers?
Very cool PR! |
4cd97ee
to
954b082
Compare
I just merged #69186, so you could rebase this PR ontop of master and enable |
Nice, done, but unfortunately master has been broken for a week due to vm test failures which means this is now semi-broken. Anyone looking to try image generation now will currently need to rebase this onto nixos-unstable instead and cherry-pick the #69186 merge on top of it. EDIT: or apply #70027 which unblocks this and nixos-unstable. |
#69302 seems to be relevant in some way, particularly if you want to use networkd for faster droplet boot. I'm not quite sure how or what needs to change though? |
It means you have to explicitly set for which network interfaces you want DHCP if networkd is denabled. I think this is a thing we can fix after merging to be honest, as it's only a problem for now if networkd is enabled. Lets file a new issue for it. I'm pretty familiar with the networkd changes and can probably create a PR for it. but it's not worth blocking what we already have. |
Yeah, fair, it's easy enough to add (also I'm at least not confident enough in including a change like that without going with a more flexible EDIT: just pushed a rebase because the last time I did so master was broken |
Is this ready for testing? How do I build the image? |
Yeah it works and can be built with something like: nix build '(with import <nixpkgs> { }; nixos { imports = [<nixpkgs/nixos/modules/virtualisation/digital-ocean-image.nix>]; }).digitalOceanImage' Then the resulting file can be uploaded as a custom image via the DO web UI and then used to create a droplet. Though beware that if you plan on using the userdata feature to configure the image (or just in general doing a rebuild/switch), you will need to point it to a channel URL that has these changes merged in, otherwise it will fail when it tries to use nixpkgs without the DO modules. I'd recommend managing the resulting droplets with nixops or something that makes it easier to pin the channel, but other than that complication it should work fine. |
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.
Looking pretty good, I tested it and it works very well (except the do-agent which didn't start for some reason). Some final remarks and this is ready to merge imo
}; | ||
services.do-agent.enable = mkDefault true; | ||
networking = { | ||
firewall.allowedTCPPorts = [ 22 ]; |
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.
This should be done by default already, or is there another reason this is added? In addition it would have to be services.openssh.listenAddresses.*.port
A path to a configuration file which will be placed at `/etc/nixos/configuration.nix` | ||
and be used when switching to a new configuration. | ||
If set to `null`, a default configuration is used that imports | ||
`(modulesPath + "/virtualisation/digital-ocean-config.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.
This needs to use the xml syntax <literal>...</literal>
, same for other option descriptions
maintainers/maintainer-list.nix
Outdated
arianvp = { | ||
email = "arian.vanputten@gmail.com"; | ||
name = "Arian van Putten"; | ||
github = "arianvp"; |
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.
Needs githubId
since recently
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.
LGTM! Can you squash commits a bit?
I tried. |
Personally I'd like it if every commit is one that won't trip up anybody when bisecting. So that in all of them eval still succeeds, the manual build successfully and things like that. So I'd do one commit for adding maintainer, one for the |
|
||
with lib; | ||
let | ||
cfg = config.virtualisation.digitalOceanImage; |
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.
Hi! @arcnmx, I am planning to use this custom image. But I am confused about where is this digitalOceanImage
located? Because in the file digital-ocean-image.nix
, I only found digitalOcean
.
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.
You'll have to manually import the module file before you can use it.
This seems to be a convention all images use. Not sure if it's a good one.
E.g.. https://github.com/nix-community/nixos-generators/blob/master/formats/gce.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.
@arianvp thank you! Yes, I think I have misunderstanding about the use of cfg = config.virtualisation.digitalOceanImage
here.
That does kind of just sound like a single commit for the whole thing I guess, though I can't quite make sense of applying that logic here. If someone isn't using the module, it won't trip them up. If they are, they would necessarily start bisecting with a known good commit that's at least from the head of this branch (or point at which this is merged) regardless of whether it's one commit or many, because it wouldn't exist in a state they could have used it otherwise.
To my knowledge they are currently like that, though each commit adds a particular feature (excluding the commits from the prior PR).
That does seem simplest, though I'm not very comfortable with messing with someone else's history... |
I would honestly just squash everything in one commit; and keep the descriptions of each of the original commits. I'm also OK getting it merged as is... |
Please rebase on latest master, and squash into two commits (one editing maintainer-list, the other one adding the other files). |
a74a353
to
8bba282
Compare
Thank you for this work! I came across it via this very nice tutorial. What would it take to get this image built on a MacOS device? I want to spin up a DigitalOcean instance of NixOS so I can use a remote builder to build a Docker image with Nix... and that has landed me here! :D |
Feel free to ignore my above question. I found a workaround. |
Motivation for this change
A fork of the stalled #58464 made at @arianvp's suggestion, containing various fixes and changes that were brought up in review of the original PR (is a merge preferred here or is rebase fine?)
Things done
sandbox
innix.conf
on non-NixOS)nix-shell -p nix-review --run "nix-review wip"
./result/bin/
)nix path-info -S
before and after)Notify maintainers
cc @eamsden @infinisil