Live data from Hacker News

Go, Containers, and the Linux Scheduler

riverphillips.dev

121–130 of 143 posts

Re: Go, Containers, and the Linux Scheduler

#121
post #63
post #59

Earlier quoted context omitted.

It's complicated. I've worked on every kind of application in a container environment: ones that ran at ultra-low priority while declaring zero CPU request and infinite CPU limit . I ran one or a few of these on nearly every machine in Google production for over a year, and could deliver over 1M xeon cores worth of throughput for embarassingly parallel jobs. At other times, I ran jobs that asked for and used precisel…

The problem is your experience involves a hacked up Linux that was far more suitable for doing this than is the upstream. Upstream scheduler can't really deal with running a box hot with mixed batch and latency-sensitive workloads and intentionally abusive ones like yours ;-) That is partly why kubernetes doesn't even really try.

I used Linux for mixed workloads (as in, my desktop that was being used for dev work was also running multi-core molecular dynamics jobs in the background). Not sure I agree completely that the Google linux kernel is significantly better at this.

At work at my new job we run mixed workloads in k8s and I don't really see a problem, but we also don't instrument well enough that I could say for sure. In our case it usually just makes sense to not oversubscribe machines (Google oversubscribed and then paid a cost due to preemptions and random job failures that got masked over by retries) by getting more machines.

Re: Go, Containers, and the Linux Scheduler

#122
post #16

The common problem I see across many languages is: applications detect machine cores by looking at /proc/cpuinfo. However, in a docker container (or other container technology), that file looks the same as the container host (listing all cores, regardless of how few have been assigned to the container). I wondered for a while if docker could make a fake /proc/cpuinfo that apps could parse that just listed "docker cpu…

> I wondered for a while if docker could make a fake /proc/cpuinfo

This exists: https://github.com/lxc/lxcfs

lxcfs is a FUSE filesystem that mocks /proc by inferring cgroup values in a way that makes other applications and libraries work without having to care about whether it runs in a container (to the best of its ability - there are definitely caveats).

One such example is that /proc/uptime should reflect the uptime of the container, not the host; additionally /proc/cpuinfo reflects the number of CPUs as a combination of cpu.max and cpuset.cpus (whichever the lower bound is).

As others also mentioned, inferring the number of CPUs could also be done using the sched_getaffinity syscall - this doesn't depend on /proc/cpuinfo, so depending on the library you're using you might be in a pickle.

Re: Go, Containers, and the Linux Scheduler

#123

Earlier quoted context omitted.

I only use `nproc` and see it used in other containers as well, ie `bundle install -j $(nproc)`. This honors cpu assignment and provides the functionality you're seeking. Whether or not random application software uses nproc if available, idk > Print the number of processing units available to the current process, which may be less than the number of online processors. If this information is not accessible, then prin…

This is not very robust. You probably should use the cgroup cpu limits where present, since `docker --cpus` uses a different way to set quota: if [[ -e /sys/fs/cgroup/cpu/cpu.cfs_quota_us ]] && [[ -e /sys/fs/cgroup/cpu/cpu.cfs_period_us ]]; then GOMAXPROCS=$(perl -e 'use POSIX; printf "%d\n", ceil($ARGV[0] / $ARGV[1])' "$(cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us)" "$(cat /sys/fs/cgroup/cpu/cpu.cfs_period_us)") else GO…

Yeah, I'd also go with cgroups... but... You need to know if it's cgroups v1 or v2, where this filesystem is mounted, how to find your own process. Also, there's this hierarchical stuff going on there... also this can change dynamically while your program is running...

Re: Go, Containers, and the Linux Scheduler

#124
post #102

Earlier quoted context omitted.

Containers are a crappy abstraction and VMware fumbled the bag, is my takeaway from this comment…

> VMware fumbled the bag Oh they did, they're a modern day IBM. > Containers are a crappy abstraction They're one of the best abstractions we have (so far) because they contain only the application and what it needs.

> they contain only the application and what it needs.

Delusion level: over 9900.

I'm yet to find a container that contains only the application and what it needs. Most of the time I find that they contain at least libc and libpthreads (which are already present on the host, so not needed). More often I find metric tonnes of garbage that was not necessary by any metric, but was just too hard to remove, so was allowed to stay.

Re: Go, Containers, and the Linux Scheduler

#125

Earlier quoted context omitted.

And how is that different from worse latency due to cpu throttling from your users’ perspective?

Fixed queue, so it'll only take as many as it can process and reject the rest, which can be used to do scaling, if you have a cluster. With shares it would think it has all the CPU cores available and oversize the queue.

Doesn’t answer my q really. At least in kubernetes scaling is done by measuring usage against the request (shares) not the limit (quota)

Re: Go, Containers, and the Linux Scheduler

#126
post #121
post #63

Earlier quoted context omitted.

The problem is your experience involves a hacked up Linux that was far more suitable for doing this than is the upstream. Upstream scheduler can't really deal with running a box hot with mixed batch and latency-sensitive workloads and intentionally abusive ones like yours ;-) That is partly why kubernetes doesn't even really try.

I used Linux for mixed workloads (as in, my desktop that was being used for dev work was also running multi-core molecular dynamics jobs in the background). Not sure I agree completely that the Google linux kernel is significantly better at this. At work at my new job we run mixed workloads in k8s and I don't really see a problem, but we also don't instrument well enough that I could say for sure. In our case it usua…

I think you touch on the key issue which is the upstream scheduler does not have all the stats that you need to have confidence in the solution. You want to know how long threads are waiting to get on a CPU after becoming runnable.

Re: Go, Containers, and the Linux Scheduler

#127
post #113
post #96

Earlier quoted context omitted.

> Memory is different because it is non-compressible - once you give memory you can't take it away without killing the process

Swap (Disk, RDMA, Compression)? Page migration (NUMA, CXL)?

All of this is below K8s scheduler level, the K8s scheduler doesn't know how the underlying kernel handles memory, all it cares about whether it thinks there's enough free memory to give to a pod or not because it just keeps track of other pod requests and limits, the fact that giving this memory, which it thinks is unavailable, would actually not result in any swapping for the pod is unknown.

Re: Go, Containers, and the Linux Scheduler

#128

This sort of tuning isn't necessary if you use CPU reservations instead of limits, as you should: https://home.robusta.dev/blog/stop-using-cpu-limits CPU reservations are limits, just implicit ones and declared as guarantees. So let the Go runtime use all the CPUs available, and let the Linux scheduler throttle according to your declared reservations if the CPU is contended for.

I don't set limits because I'm afraid of how a pod is going to affect other pods. I set limits because I don't want to get used to being able to tap on the excess CPU available because that's not guaranteed to be available. As the node fills up with more and more other pods, it's possible that a pod that was running just fine a moment ago is crawling to a halt. Limits allow me to simulate the same behavior and plan f…

In my experience working with containers -- both on my own behalf and for customers -- I can think of relatively few situations in which CPU limits were necessary or useful for the workload.

When you set CPU reservations properly in your container definitions, they don't "crawl[] to a halt" when other containers demand more CPU than they did before. Every container is guaranteed to get the CPU it requested the moment it needs it, regardless of what other containers are using right up till that moment. So the key is to set the requested CPU to the minimum the application owner needs in the contended case. That's why I say that "requests are limits in reverse" - a request declared by container A effectively creates an implicit limit on container B when container A needs the CPU.

Perhaps you're concerned that not setting limits causes oversubscription on a node. But this is not so: when scheduling pods, the K8S scheduler only considers requests, not limits. If every container is forced to declare a CPU request - as it should - then K8S will not overprovision pods onto a node, and every container will be entitled to all the CPU it requested when needed.

Consider, too, that most applications are memory-bound and concurrency-bound, not CPU bound. And a typical web server is also demand-driven and load-balanced; it doesn't spontaneously consume CPU. For those kinds of applications, resource consumption is roughly consistent across all the containers, and the "pod running just find a moment ago...crawling to a halt" isn't a phenomenon you're going to see, as long as they haven't yet consumed all the CPU they requested. Limits hurt more than help especially for these kinds of workloads, because being able to burst gives them headroom to serve while a horizontal scale-out process (adding more pods) is taking place.

Most CPU-bound workloads tend to be batch workloads (e.g. ETL, map/reduce). Teams who run those on shared nodes usually just want to get whatever CPU they can. These jobs tend not to have strict deadlines and the owners don't usually panic when a worker pod has its CPU throttled on a shared node. And those who do care about getting all the CPU available are frequently putting these workloads on dedicated nodes, often even dedicated clusters. And the nodes tend to be ephemeral - launched just-in-time to run the job and then terminated when the job has completed.

As others have pointed out, putting CPU limits on containers is a contributing cause of nodes having lower CPU utilization than they could otherwise sustain. Every node costs money -- whether you bought it or rent it -- so underutilized nodes represent wasted capital or cloud spend. You want nodes to run as hot as possible to get the most out of your investment. If your CFO/cloud financial team isn't breathing down your neck when they see idle nodes, they ought to be.

Re: Go, Containers, and the Linux Scheduler

#129
post #46

Earlier quoted context omitted.

According to the article, this is not true. The limits become active only when the host cpu is under pressure.

I don't think that's correct. --cpus is the same as --cpu-period which is cpu limit. You can easily check it yourself, just run docker container with --cpus set, run multi-core load there and check your activity monitor.

I just tried it and you are right. Ran

    docker run --cpus 0.1 --rm -it progrium/stress --cpu 16
and the machine's cpu sits idle.

Re: Go, Containers, and the Linux Scheduler

#130
post #119

Earlier quoted context omitted.

A VM provides the first 4 anyway - if you're deploying to a cloud instance then having these in the container is redundant. If you're deploying to bare metal then it's possibly useful, but only if you're deploying multiple containers to the same machine. Go doesn't need a format for packaging - it's one file. It's becoming common practice to embed everything else into the binary. (side note: I haven't done this with…

This goes against everything we've learned about effectively deploying and managing software at runtime. Using the golang binary as a packaging format for your app has the same energy as crafting it exclusively from impenetrable one-liners.

Sorry, I don't understand this. What's the difference between a Docker image made from a script and a Go binary made from a script?
Post reply on HN