Hacker News new | ask | show | jobs
by Esau 3446 days ago
I don't care for SystemD for the same reasons that I don't like MacOS's init system: it an opaque, confusing mess.
4 comments

As are most Bash init files with InitV... At some point we should consider that the init system as a whole (including the configuration scripts for each daemon) _is_ complicated and often confusing. In this regard, writing something like SystemD to elegantly handle most of the usecases is actually a good idea.
Many comments seem to only compare systemd with SysV init. But there is a whole world of process supervisors out there, particularly in the daemontools[1] family.

systemd is a process supervisor plus additional features; sysv init is not a process supervisor (though the "init" program as configured in /etc/inittab is a supervisor). At a minimum, a process supervisor starts processes and restarts them should they exit.

Some modern daemontools-family systems with additional features beyond basic supervision that put them in the same category as systemd are nosh[2] and s6-rc[3]. These two systems (and all the daemontools family), in contrast to systemd, are not monolithic, but composed of multiple smaller programs.

Notably, they depend on the shell (any script or executable, but traditionally small shell scripts) to perform actions like setting up environment variables, switching to a less privileged user, redirecting output, etc. while systemd has created a large and growing set of directives[4] to accomplish these. This set of directives is one of my annoyances with systemd. As someone familiar with the shell, I find it annoying to learn yet another language (systemd directives) for expressing the same thing, though I recognize not everyone is familiar with shell.

[1] http://cr.yp.to/daemontools.html

[2] http://jdebp.info./Softwares/nosh/

[3] http://skarnet.org/software/s6-rc/

[4] https://www.freedesktop.org/software/systemd/man/systemd.dir...

Binary logs, hex logs, there's enough stupidity to go around!
Bash init files may not be particularly feature rich, but they are hardly "complicated".

90% of every file is boilerplate built around: stop, start, status (after all restart is just stop+start).

Proponents of SystemD can make some good points, but trying to claim init.d is complicated comes across as desperate.

You forget that sysvinit also required fun extras like supervisord to run simple executables because it relied on applications demonizing themselves.

Writing systemd unit files for my applications is a breeze compared to the shit I had to do for sysvinit - especially when I factor in apps I DIDN'T write that I need unit files for (teamcity, etc) and there's less surprise with SELinux policy transitions (and unlike most setenforce 0 is not the first thing I run on a server, I work in healthcare and a solid MAC layer is important to me).

> Bash init files may not be particularly feature rich, but they are hardly "complicated".

Are you trying to make the argument that shell scripts are simple, easy to read, and maintainable?

Permit me to disagree.

Yep, they are. Shell is lingua franca for system administrators for whole history of UNIX.
Which one? ash? csh? tcsh? ksh?

I don't think you have as much experience with Unix as you would like us to think you do.

What do you mean which one? It's /bin/sh. /bin/sh has always been the shell used, because it's the only one guaranteed to be on all unixen. No system ships their init scripts written in ash, csh, tcsh, ksh, or anything besides /bin/sh (except Linux, which may have used /bin/bash, since /bin/sh was symlinked to /bin/bash)

You might have made your personal init scripts in some other shell (until you learned about design failures of csh), but I'm willing to bet your Unix distributor did not.

I rarely see a bash script more than five lines long which doesn't have at least one bug.

If you disagree with me then post an example and I'll show you the bug in it.

Also, it's not the 90% that matters; it's the 9% that is difficult and the 1% that is crap.

There's this example for your approval: https://news.ycombinator.com/item?id=13388322
The reply from bazurbat covered it well.
And 90% of that 90% has that tiny modification that is a nightmare to debug.
What he wanted to say: you can debug since context is very local. (not taking sides)
Use `set -x` or `bash -x script.sh` to trace shell script execution. Shell scripts are easy to debug.
I call this to drop into an immediate repl at the point of invocation in a language that doesn't hate me (Ruby):

     require 'pry'; binding.pry
This is, in fact, 2017. One might call it the current year. The stone knives and bearskins of shell scripting have not kept apace with, like...anything else in our industry. You might be comfortable with them, but that doesn't make it easy, it means you have frayed countless synapses learning it.
What about runit?

Unlike all of the above, runit isn't complicated. You write a tiny, often one-line script and it executes, if it exits, it waits a reasonable amount of time and restarts it.

Not only is it not complicated, but it runs beautifully on top of an existing init system. So now wherever I go I have one tiny script that will start my python based web services and similar. I run the same basic script on freebsd rc, ubuntu upstart and arch systemd boxes.

Watch the special case directives grow until you can use systemd to implement Conway's Game of Life...
I hated systemd when I had to start using it. It tries to do way too much. The antithesis of the the Unix philosophy. Journalctl grew on me though.
Who said the unit philosophy was the be-all, end-all? Unix doesn't even believe it's own philosophy because it's too damn painful. Why does ls do sorting? Why does grep do -R recursive searching? How is that "Do one thing and do it well"?

Unix is just a collection of random decisions made by various people over the years. The Unix philosophy is really more like "I've always done it that way so don't you dare change it".

fork/exec is garbage. Signals are garbage. You basically can't make any API calls after fork or in a signal handler because threads came after. The interaction of fork and threads is bananas and many hacks have been required over he years to paper over the problems.

The layout of /usr/bin, /usr/sbin, /usr/local/bin, et al isn't a good design. It's dogshit but it was necessary because early Unix file systems couldn't span multiple volumes and early disk systems were small.

The C compilation model of separate header files is not a good design. People have retroactively determined some of the side-effects are not only good but The One True Way. In reality the design was a result of extremely limited RAM and slow CPUs. The preprocessor itself was never designed, just grafted on ad-hoc.

Unix file permissions are shit. Every unique combination of permissions requires a group. Owners are by simple integers. NFS legitimately gives people nightmares.

Let's not even get into everything is a file, except when it's not, and some files are more equal than others.

What about dependency hell? How's that "simple" model working out?

The "Unix philosophy" can piss right off.

"Who said the Unix philosophy was the be-all, end-all?" I didn't and I don't know anyone that has. I was comparing systemd and SysV.

"Unix is just a collection of random decisions made by various people over the years."

"Linux has never been about quality. There are so many parts of the system that are just these cheap little hacks, and it happens to run." - Theo de Raadt

"The layout of /usr/bin, /usr/sbin, /usr/local/bin" I don't agree with you. Having base in a directory and having user installed bins in another makes sense. I don't understand why most modern Linux distros install on only one partition by default. You lose the ability to mount with flags ie noexec,nosuid,nodev.

NFS is horrible. Unix/Linux is a multi-user operating system why would you not want to have groups?

KISS

> NFS is horrible. Unix/Linux is a multi-user operating system why would you not want to have groups?

Now that's backwards. He obviously mean a better permission system. Do you seriously think groups is the best way?

Access Control Lists are a better way.

"The C compilation model of separate header files is not a good design"

This is probably just your lack of experience not having worked on 50+ million LOC compiling for 12 hours and not having anything else as a better option. There is a reason these things exist.

I've worked on a project that combined C++ and C# in approximately equal amounts (say 2MLOC each). The C++ project compiled and linked for 20 minutes , C# compiled and linked in under 2 minutes. Go figure.

I agree: C and C++ compilation model is not a good design. It's a patch for not having a decent module system. Heck, even Borland Pascal compiled faster in 90-ies than C++ does now on an orders of magnitude faster machine.

For as long as C has existed, other languages have provided alternatives where symbol information is extracted from the main source files automatically by the compiler and optionally cached for next time, or if you don't want to ship users of a library source, for example. In other words: This has been solved in a better way since the 70's.
> There is a reason these things exist.

Enlighten me. And while you're at it, explain why this is better than, say, Rust's module system, where we don't need separate header files.

Isn't the limitations of the header file system the reason why the C++ module system is being developed?

Why wouldn't something like the C++ module system be of benefit in C also?

One of the reasons it seems.

It would, but by the look of it, C++ evolves faster these days.

??? Header files is the problem, not the solution.
Do you want to clarify that, because I think you may have missed a "not" in there.
Yes, yes, yes! Never has someone articulated what I believe so damn well. There is so much blind worship of tradition and heroes in the UNIX world, so glad to see someone else believes what I've always believed.
Most of this comment is incorrect.
I think xenadu02 raises some valid criticisms, but I think those criticisms would have been better received if they were expressed more politely.

I'd love to see a rebuttal of the specific points made as opposed to just "Most of this comment is incorrect".

Most of the statements aren't really conducive to rebuttals because they are lacking substance.

But I can imagine what xenadu02 might have meant, if you like, and provide some counter arguments.

Signals aren't "garbage" (whatever that means).

Signals can call APIs (the set of async-signal-safe APIs). They can't call non-async-signal-safe APIs not because of threads, but because signals can interrupt a routine at any point (necessary for asynchronous notification of certain events which must be handled before the normal instruction control flow can be resumed) and that interrupted routine may not have been written to be reentrant.

This is true even without threads in the picture.

The fork/exec model is not "garbage". It is actually a fairly nice alternative to the "provide one API to start a child process and give it a large number of parameters for all possible situations". And you can call plenty of APIs between fork and exec in the child safely, just like from signal handlers.

I haven't dealt with dependency hell ever since shared libraries got sonames.

The rest of the comment doesn't list anything of substance. If you want rebuttals for "the file system layout is a bad design" or "the C compilation is a bad design" or anything else, provide some reasons why those are bad designs; some of those reasons may be valid criticism, and some may not be, but one can't just make vacuous statements like that and expect a reasonable discussion to follow.

Unix signals have been called garbage by some and "unfixable" by others [1]. The article [1] explains the evolution of signal handling, from sigvec(), sigaction(), to signalfd() -- a rocky history fraught with problems, an article in the series "Unfixable designs".

> So while signal handlers are perfectly workable for some of the early use cases (e.g. SIGSEGV) it seems that they were pushed beyond their competence very early, thus producing a broken design for which there have been repeated attempts at repair. While it may now be possible to write code that handles signal delivery reliably, it is still very easy to get it wrong. The replacement that we find in signalfd() promises to make event handling significantly easier and so more reliable.

Another critic makes the case that "signalfd is [also] useless" [2]:

> "UNIX[] signals are probably one of the worst parts of the UNIX API, and that’s a relatively high bar."

Signals came up recently on HN when someone remarked that not even memset() is signal-safe! [3]

All in all, working with signals correctly requires mastering a tremendous degree of complexity. Other platforms have provided simpler APIs, such as Structure Event Handling (SEH) [4].

[1] https://lwn.net/Articles/414618/

[2] article link from https://news.ycombinator.com/item?id=9564975

[3] https://news.ycombinator.com/item?id=13313563

[4] An HN comment describing how it's simpler: https://news.ycombinator.com/item?id=13323870

P.S. Please note that the views quoted above are not necessarily my views.

I'm not going to defend everything xenadu02 said, but I think there were some points that resonated with me even though I agree they could be expressed more constructively.

> Why does ls do sorting? Why does grep do -R recursive searching? How is that "Do one thing and do it well"?

I think these are valid examples of how Unix itself fails to follow the "Unix philosophy" of "Do One Thing and Do It Well".

> The fork/exec model is not "garbage". It is actually a fairly nice alternative to the "provide one API to start a child process and give it a large number of parameters for all possible situations". And you can call plenty of APIs between fork and exec in the child safely, just like from signal handlers.

fork-exec complicates the implementation of threads (see atfork handlers). Rather than "a large number of parameters for all possible situations", another alternative would be to have (1) a call which given executable name and arguments returns an opaque handle (or file descriptor) representing the process to be started (2) a bunch of further calls to set attributes on that handle – new features could add new APIs acting on the handle, or an extensible API like ioctl could be used – if there is a handle to represent the current process, then you only need one API call to set it for the current process or a child to be started (3) finally, a start call which turns the process-to-be-started handle into a running process handle.

> Unix file permissions are shit

The user-group-other model is arguably too limiting. ACLs are a better idea, but then should you use POSIX ACLs or NFSv4 ACLs?

The distinction between primary group ID and supplementary group IDs is silly.

Why must every file have both a UID and a GID? For files owned by a single user, you end up creating a dummy group like "staff" or so on just to obey the rule that every file must have a GID. For shared files, e.g. project files, files generally end up owned by their creator, even though in a business sense they really belong to the project not to whoever created them. It would make more sense if the owner could be either a user or a group, and then also have zero or more non-owning groups associated with it.

In most cases permissions should only exist on the directory, and then automatically apply to any files in the directory. (In most cases every file in the same directory should have the same permission; Unix bases its design on the exception rather than the rule.) Of course, hard links make this impossible, but I think hard links were a mistake.

The executable permission bits actually do double duty as a file type indicator. That's rather ugly. If Unix had explicit file types (rather than just a naming convention of file extensions), then certain file types could be declared to be executable. Executable permission would then mean "you are allowed to execute this if it is an executable" instead of "this is an executable". Stuff like the +x vs +X distinction in chmod would never have been necessary.

> Let's not even get into everything is a file

Unix would have been much better if everything were a file descriptor, rather than having stuff like pid_t. Linux at least is evolving in this direction. Plan9 does it better. Even the WindowsNT philosophy of "everything is a handle" is better than the traditional Unix approach.

«Foo is garbage» is not a valid criticism.
I think systemd needs to do most of the things it does. Russ Allbery's analysis of systemd [1], written as part of Debian's evaluation of whether to switch to systemd, explains the benefits. Journal integration is something that Russ was also skeptical about, until he realized its value:

  * Integrated daemon status.  This one caught me by surprise, since the
    systemd journal was functionality that I expected to dislike.  But I was
    surprised at how well-implemented it is, and systemctl status blew me
    away.  I think any systems administrator who has tried to debug a
    running service will be immediately struck by the differences between
    upstart:
  
    lbcd start/running, process 32294
  
    and systemd:
  
      lbcd.service - responder for load balancing
       Loaded: loaded (/lib/systemd/system/lbcd.service; enabled)
       Active: active (running) since Sun 2013-12-29 13:01:24 PST; 1h 11min ago
         Docs: man:lbcd(8)
               http://www.eyrie.org/~eagle/software/lbcd/
     Main PID: 25290 (lbcd)
       CGroup: name=systemd:/system/lbcd.service
               └─25290 /usr/sbin/lbcd -f -l
  
    Dec 29 13:01:24 wanderer systemd[1]: Starting responder for load balancing...
    Dec 29 13:01:24 wanderer systemd[1]: Started responder for load balancing.
    Dec 29 13:01:24 wanderer lbcd[25290]: ready to accept requests
    Dec 29 13:01:43 wanderer lbcd[25290]: request from ::1 (version 3)
  
    Both are clearly superior to sysvinit, which bails on the problem
    entirely and forces reimplementation in every init script, but the
    systemd approach takes this to another level.  And this is not an easy
    change for upstart.  While some more data could be added, like the
    command line taken from ps, the most useful addition in systemd is the
    log summary.  And that relies on the journal, which is a fundamental
    design decision of systemd.
  
    And yes, all of those log messages are also in the syslog files where
    one would expect to find them.  And systemd can also capture standard
    output and standard error from daemons and drop that in the journal and
    from there into syslog, which makes it much easier to uncover daemon
    startup problems that resulted in complaints to standard error instead
    of syslog.  This cannot even be easily replaced with something that
    might parse the syslog files, even given output forwarding to syslog
    (something upstart currently doesn't have), since the journal will
    continue to work properly even if all syslog messages are forwarded off
    the host, stored in some other format, or stored in some other file.
    systemd is agnostic to the underlying syslog implementation.
I wrote another comment recently [2] to explain why I value systemd's approach and appreciate its declarative style. I can launch my service at the appropriate time during boot with configuration as simple as:

  [Unit]
  Description=Demo service

  [Service]
  Type=forking
  ExecStart=/usr/sbin/my-daemon
Now let's say that I didn't author this daemon, but I'd like to run it with a private network, private temp folder, or a private /dev namespace. Or perhaps the daemon needs to run as root, but I want to drop all capabilities it doesn't need. It's as simple as adding these lines to the service's configuration:

  PrivateTmp=yes
  PrivateDevices=yes
  PrivateNetwork=yes
  CapabilityBoundingSet=CAP_NET_BIND_SERVICE
The fact that systemd supports these configuration options means that there's a simple and standard way to employ them with any service. The service itself doesn't need to support them, and needn't complicate its own daemonization logic to do so correctly. Indeed, I don't need to trust the service to daemonize or drop capabilities, since I can tell the init system do that before launching the service.

I can drop capabilities with CapabilityBoundingSet=, or limit resource usage with CPUSchedulingPriority=, IOSchedulingPriority=, etc. I could even tell systemd to open the listening socket for me so the service doesn't need CAP_NET_BIND_SERVICE! Moving these options into the init system makes a ton of sense, because it gives administrators the ability to employ these features from outside applications, not just by enabling them within applications that bother to explicitly support them via command line arguments. Systemd better encourages the principle of least privilege: if a system daemon does not need the ability to "ptrace" other processes, or bind to ports <1024, then as the administrator I can take those away with CapabilityBoundingSet= in the unit file. Chrooting the service is as easy as RootDirectory=. This is a huge step forward compared to the world where every service must be relied upon to expose these settings, and must be trusted to implement them correctly.

[1] https://lists.debian.org/debian-ctte/2013/12/msg00234.html

[2] https://news.ycombinator.com/item?id=13359519

I thought that was the part of systemd that was universally liked? The best arguments against systemd seem to be that everything becomes implemented in or tightly bound to it.
This is also the rationale behind uselessd, which keeps the service management part and cuts out everything else.
Pid 1 runs its own DNS server now. It also launders kernel calls for non-setuid xorg (breaking rootless x on non-systemd boxes, since the kernel can't be bothered to consistently check process uid or gids, apparently). In turn, that means it must have some baroque authentication subsystem too. There is no way you need that in init. The amount of ancillary damage systemd causes far outweighs any possible benefits there are to improving init.

Also, I have never seen a systemd box emit log lines like that for a failed service. It invariably points at some useless logfile with obscure systemd messages in it instead of the stderr of the failed process. This is on clean ubuntu and debian installs. Maybe it is user error, but I doubt it. (Though there is no command line in the examples...)

Anyway, I'm happy to cleanse with fire instead of RTFM at this point. On a related note, I just learned the solaris init system and started using openbsd's again.

I prefer them both to systemd. They are at opposite ends of a spectrum. The openbsd approach is well curated shell scripts. I think systemd was heavily inspired by the solaris thing.

> Pid 1 runs its own DNS server now.

It doesn't. Systemd has its own resolver (systemd-resolved), which has other issues, but it does not run in PID 1. It's a completely separate process.

// I'm enjoying watching the points bouncing up and down, but if you disagree in some way, please comment :)

> PrivateTmp=yes > PrivateUsers=yes > PrivateNetwork=yes >The fact that systemd supports these configuration options means that there's a simple and standard way to employ them with any service.

What exactly does 'PrivateUsers' do? What uid do I have? When I write that uidin a db, what value does it keep? Between invocations, does the uid change or is it per unit? If a file is owned by a private uid, what do other processes on the system see? Is PrivateUsers for this unit file only, for the unit files in this group of unit files, across the entire system, across the entire cluster? If I want two different programs to share this PrivateUsers concept, how do I do that?

It turns out that gluing random shit to the side of a monolith gives you the illusion of convenience, but since the monolith will not do that thing well -- for example, identity management -- you will end up with some programs that adopt the half-assed solution, and some programs that are forced to do things a different way because their use case is complex. Now you have two problems.

PrivateUsers= is documented in the manual page for systemd.exec [1]. I hope the section I linked will answer your question, but for the sake of simplicity I edited my comment to use PrivateDevices= as the example instead.

https://www.freedesktop.org/software/systemd/man/systemd.exe...

You missed the point completely, I'm afraid. The point was that it's a leaky abstraction composed of half implemented concepts that devs have to add to their brain. It does not replace the existing functionality or improve it.
Respectfully, actually, I think you missed the point. Linux itself offers a number of powerful features such as namespaces to isolate programs, capabilities that can be dropped, etc., including the User Namespace feature we're discussing presently [1].

Systemd's job and goal is to provide a simple configuration file format that makes it easy to enable these features with installed system daemons.

You may overlooking the parameters supported by systemd and their benefits. Admins get a single way to manage their services and dependencies, and a way to do that that works consistently across all services. These features can be enabled even if the services were not designed for it (e.g., chroot). With systemd you can employ these settings from the outside with any service, and that's a big advance. Difficult to achieve otherwise.

> The point was that it's a leaky abstraction composed of half implemented concepts

I am unclear what part you want to criticize. The Linux kernel is what provides the User Namespace feature. Systemd helps users take advantage of it. What part do you consider half-implemented or a leaky abstraction?

Please also consider whether you might have the wrong mental model for the feature or its employment. In particular, the documentation for PrivateUsers= says: "This is useful to securely detach the user and group databases used by the unit from the rest of the system, and thus to create an effective sandbox environment." The usage you had in mind when you wrote your comment may not be compatible with the purpose of PrivateUsers. I recommend reading up systemd.exec params [1] before criticism. PrivateUsers= is intended for scenarios like transient sandboxed environments, so I'd suggest we discuss a simpler example like PrivateDevices=

[1] http://man7.org/linux/man-pages/man7/user_namespaces.7.html

>The point was that it's a leaky abstraction composed of half implemented concepts that devs have to add to their brain.

As opposed to a multitude of implementations of varying quality and functionality for the same concept in each init script that needs it?.

You've copied and pasted that text multiple times on this page alone. Once, or at the very least once per Hacker News page, is surely enough.
http://www.soma-zone.com/LaunchControl/

Disclosure: I liked this so much that I gave the developer money.

Poettering and crew have written an enormous number of excellent blog articles and a great amount of very good manual pages.

So, not sure what you have to do to not be 'opaque'