Live data from Hacker News

Linux Namespaces and Go Don't Mix

weave.works

121–130 of 175 posts

Re: Linux Namespaces and Go Don't Mix

#121
post #111

Earlier quoted context omitted.

I agree. Bestowing certain permissions/properties on a thread doesn't make sense. The thread shouldn't "enter the namespace", an API should retrieve a handle to that namespace that should then be usable from any thread in the process via some appropriate calls.

On the other hand having individual threads entering namespaces means your broker process does not have to constantly switch namespaces, it can use shared memory between differently privileged threads to do the brokering.

I know nothing of the implementation here but presumably these namespaces can exist side by side in a way that doesn't require any "switching". If switching is expensive that would make context switching between a thread in one namespace and a thread in another namespace just as expensive?

If you have one thread in one namespace and another in another you now have to worry about what you can do in the context of a callback. This asymmetry just makes any multi-threaded program more complicated than it needs to be (and already is).

Re: Linux Namespaces and Go Don't Mix

#122
post #84

Earlier quoted context omitted.

You can keep using Go. Its behaviour here is actually usually what you want. To avoid the authors' issue, you can write some functions in C. CGo has a very high level of integration (you can mix languages in the same source file) and would be quite simple for the case of a setns/execve wrapper.

> CGo has a very high level of integration No, no it doesn't. CGo is slow as balls to call into and return from. Types can't fully be shared. It's interacted with via comments. You can't use cgo to control the threading of the Go runtime itself, which is the real problem here. The behavior you want is to be able to call linux syscalls that operate on threads and not be utterly fucked. That behavior cannot be accompli…

> You can't use cgo to control the threading of the Go runtime itself, which is the real problem here.

Go is deliberately opinionated about threading of the runtime. I think it's unlikely Go will offer much more control over these internals (beyond GOMAXPROCS), given the philosophy around e.g. GC tuning.

Cgo is a beefed-up runtime.LockOSThread() that could be used to avoid having the author resort to a helper process.

> CGo is slow as balls to call into and return from.

They're slower than Go function calls, but they still take only nanoseconds. This is negligible on the authors' scale of "launching an entire container".

For their case of "it is not possible to guarantee that a new OS process ... will run in a given namespace", you're not even returning from Cgo after exec.

> It's interacted with via comments.

Do you use build tags? Or go:generate? Like it or not, it's idiomatic.

I'm with you on the types, though! `go tool cgo -godefs` helps, but it would be great to see improvements especially in the reverse case of exporting Go buildmode=c-shared for C consumption. Still, a little marshaling seems tidier than a whole helper process.

Re: Linux Namespaces and Go Don't Mix

#123
post #5
post #2

This leads to go code being roughly as messy/clumsy as C (or whatever else) code in the sections that need concurrency and also need to change namespace. That's unfortunate, but I don't know that it really "raises a few eyebrows". I mean, what's the better alternative to Go for this work? Maybe Rust? It is, at least more controllable at a lower level...but, not as easy to pick up for people coming from a C/Python/Per…

Better alternative to C with the same low level control? Why not C++?

I know this one.

C++ looks good on paper. It has containers and types and generics. You come to find out that it's all based on meta-programming which is an interesting idea: Basically you're writing code to write code, and that code writes code. The whole system is macros all the way down. There's no native language support for anything but macros and the macros implement everything.

The student's experience is that he can quickly solve a problem using a list of stacks of strings (vector > > in the parlance.) Which is fine, almost like typed-python until you make a mistake. Then, the compiler, who knows nothing about those types which were all built by expanding macros, is not your friend.

Miss a minor `*` and a single line of code will fully expand its underlying macros giving a 10-page, indecipherable error message.

Should you make it to runtime, no debugger can tell you the contents of a vector, string or stack. They're just blobs of buffers and pointers with mangled (and yeah, that's the word that they chose: mangled) names to make them extra unreadable.

This is when most people ask if they can just have C back.

Re: Linux Namespaces and Go Don't Mix

#124
post #109

Earlier quoted context omitted.

Because this isn't really a language issue. It's an OS issue. Go assumes that all threads are equal and therefore any Goroutine can run on any thread and threads are all equal. Linux got late to the thread party and its threads are kind of like processes that haven't decided if they want to be processes or threads. It's true the Go runtime could give users more control over goroutine and thread scheduling but that wo…

But linux threads predate go, so go is built on assumptions they knew were not true.

It's possible they didn't know different Linux threads can be in different namespaces, it's not exactly something everyone knows. Namespaces in Linux aren't that new (15 years or so). It's possible they knew but decided this wasn't an important enough use case to warrant language features.

I've never needed to have half my process in one namespace and half in another. It's a niche application. Green threads/goroutines is something I use extensively and I wouldn't give it up for the ability to have half my process in another namespace. There's probably some middle ground there in giving Go users more control over which thread pools run which goroutines...

Someone should file a Go bug and see what the response is...

Re: Linux Namespaces and Go Don't Mix

#125
post #121

Earlier quoted context omitted.

On the other hand having individual threads entering namespaces means your broker process does not have to constantly switch namespaces, it can use shared memory between differently privileged threads to do the brokering.

I know nothing of the implementation here but presumably these namespaces can exist side by side in a way that doesn't require any "switching". If switching is expensive that would make context switching between a thread in one namespace and a thread in another namespace just as expensive? If you have one thread in one namespace and another in another you now have to worry about what you can do in the context of a ca…

Switching is a system call (setns), in principle shared-memory IPC does not involve context switches, just lock-free data structures. I'm not sure how common this is in practice since shared memory also has some downsides if you're doing this for security.

But there also are non-security applications of namespaces.

And it's not like namespaces are the only per-thread thing in linux. Capabilities, uid and signal handlers come to mind.

Re: Linux Namespaces and Go Don't Mix

#126
post #120

Earlier quoted context omitted.

>Go assumes that all threads are equal and therefore any Goroutine can run on any thread and threads are all equal This isn't true in Linux or Windows so I don't see why Go would make this assumption other than poor design. >The property should be accessible via a handle that can be shared amongst all threads. It literally is. The namespace information is shared with all threads in a PID group globally available in t…

What's an example of it not being true in Windows? I think it's mostly true in Linux and the situations where it isn't true are subtle and require expert knowledge in some specific areas. It's not like there's a big sticker on Linux that says threads are generally not symmetric. I get the bit of half the process being in one space and half in another. I just find it odd. Can half the process run as root and half the…

Maybe your assumption of symmetry is just backwards? If you look at the clone syscall then threads are more akin to processes that just happen to share virtual memory and some other things such as IO priorities, file desciptor tables, cgroups, .... Many of those things can be shared individually. In other words they are considered orthogonal features that, when taken together, happen to function as threads.

Re: Linux Namespaces and Go Don't Mix

#127
post #12

Earlier quoted context omitted.

Rust is much better for this (though I still feel some of the fork/exec interface has similar warts to Go). However, you're wrong in saying that it's "roughly as messy/clumsy" as C. Let me tell you how runc works. runc is written in Go, and we take an OCI configuration file. Because we can't just fork and set up all of the namespaces in Go, we have a C function called nsexec which is specified as __attribute__((const…

Thanks for explaining how it's done in runc. That does sound pretty awful. So, even though the initialization can be outsourced to a C function, you still would prefer to be working entirely in C? Are there no advantages to Go for runc? And, would it be possible for someone to write a somewhat standardized Go library for doing this grunt work? Is it merely fear of C that keeps so much of the container infrastructure…

> you still would prefer to be working entirely in C?

No, but there are more options than just Go and C. Rust is an option that I'm shilling at the moment (though I've only started learning it, so take that with a grain of salt). The main reason I would want to write it in C is because there's a lot of string parsing code you have to write in order to make container runtimes work -- and as we all know that's probably the #1 source of security vulnerabilities.

> Are there no advantages to Go for runc?

There are, mainly due to network effect (everything else is written in Go) and getting contributions from the community (Go is easy to pick up). Unfortunately there are also disadvantages, and quite a few of those disadvantages are present in Go but are not present in other memory-safe and low-level languages (Rust is a good example because to be quite honest it's the only player in this space that doesn't try to do more than necessary).

Go is a good language for it's designed for (Web servers and similar things), but from my experience it's not the best choice for low-level tasks. We've seen cases where long-running container daemons (not naming any names) will crash if you run more than 1000 containers on a single system. They don't crash because of the actual daemon code but because of issues with Go's GC (it doesn't actually free memory sanely, it uses MADV_DONTNEED which inflates RSS and causes OOM to kill your daemon).

> And, would it be possible for someone to write a somewhat standardized Go library for doing this grunt work?

Of course (and you could argue that we have done that in runc with github.com/opencontainers/runc/libcontainer/nsenter), but the thing to note is that in order to get around problems in the Go runtime you have to split out a single piece of code into separate processes and have to now redesign how a single function would work. So moving it to a separate library means that development is even more frustrating (you've created an API around the internal implementation of whatever thing you're working on).

> Is it merely fear of C that keeps so much of the container infrastructure on Go?

I think the network effect is the main reason. Most of the people I've worked with know quite a lot of C (we do kernel work sometimes) so writing a runtime in C would be frustrating but entirely doable. The problem is that you couldn't just import it into a Go project (and people don't like cgo because it makes binaries harder to build in certain cases).

> I've only spent a couple of weeks looking peripherally at Go, and I already like it better than C

Go definitely has it's uses, and I still use it for new projects. For example I recently wrote a tool for dealing with OCI container images in Go[1]. The standard library of Go is quite nice (though I found some bugs in archive/tar but let's not go there) and I'm always quite amazed just how much you can do before you have to import external libraries.

But I recently started learning Rust and I am _really_ enjoying being able to understand what my program will actually look like when compiled. If you've ever had to strace a Go program, you'll know exactly what I mean. Debugging Go programs is basically fucking impossible.

[1]: https://github.com/openSUSE/umoci

Re: Linux Namespaces and Go Don't Mix

#128

Earlier quoted context omitted.

* Coercion: large down vote/propaganda community against any negative rust idea..evidenced here. * IP Holders, well this is speculative. I can't understand anyone who would compare Rust against C in a positive way without possibly profiting from it. Rust is painful to write, painful to learn and makes you ask yourself: Can I not write a well designed and correct program in C? Of course I can. Why Am I Learning Rust?…

> Can I not write a well designed and correct program in C? We have decades of coredumps and exploits to convince us that nobody can. How many well-known apps can you name that have never blown up randomly? I can't think of a single one. How much more failure until it's reasonable to think that C requires an inhuman level of perfection and almost any alternative would be an improvement? I hear good things about seL4…

> I hear good things about seL4 but that wasn't written so much as translated from Haskell during a formal process that makes Rust look like finger painting.

It's actually more fun than that. They have two implementations of seL4, and they use formal proofs to show that the Haskell model is identical to the C implementation. How much fun is that! /s

Re: Linux Namespaces and Go Don't Mix

#129
post #12

Earlier quoted context omitted.

Rust is much better for this (though I still feel some of the fork/exec interface has similar warts to Go). However, you're wrong in saying that it's "roughly as messy/clumsy" as C. Let me tell you how runc works. runc is written in Go, and we take an OCI configuration file. Because we can't just fork and set up all of the namespaces in Go, we have a C function called nsexec which is specified as __attribute__((const…

What a clumsy, terrible, crappy workaround. Congrats.

I never said it was a nice hack. Personally I think this would be infinitely better in C or Rust.

Oh, and I haven't even mentioned the absolute shitfest that is the cgroup namespace and how you have to set up cgroups before you unshare it because its behaviour changes based on what cgroups you were in when you unshared it.

Re: Linux Namespaces and Go Don't Mix

#130
post #122

Earlier quoted context omitted.

> CGo has a very high level of integration No, no it doesn't. CGo is slow as balls to call into and return from. Types can't fully be shared. It's interacted with via comments. You can't use cgo to control the threading of the Go runtime itself, which is the real problem here. The behavior you want is to be able to call linux syscalls that operate on threads and not be utterly fucked. That behavior cannot be accompli…

> You can't use cgo to control the threading of the Go runtime itself, which is the real problem here. Go is deliberately opinionated about threading of the runtime. I think it's unlikely Go will offer much more control over these internals (beyond GOMAXPROCS), given the philosophy around e.g. GC tuning. Cgo is a beefed-up runtime.LockOSThread() that could be used to avoid having the author resort to a helper process…

> I think it's unlikely Go will offer much more control over these internals (beyond GOMAXPROCS)

GOMAXPROCS is actually a lie. If you set it to 1, the runtime will still create threads.

Post reply on HN