Code change:
Related: On Windows, kubelet reports only the cumulative for cpu usage. On Linux, kubelet reports both and the instantaneous . Although In the future, most components/monitoring pipelines will switch to using the cumulative metrics, a few features still depend on the instantaneous metrics today. Examples are: , HPA/VPA. Kubelet should report this metrics on Windows to support the existing features. /cc who explained to me what features depend on the cpu metrics. /cc /cc
- is this something that's clear enough in the CRI layer that you can implement it, or do we need to fix that first?
/assign
do you have any notes or a doc you can point to that better describes what features depend on which metrics? It would be good for us to share and see if anything other than is broken
The horizontal and vertical pod autoscalers also depend on the
Documentation on metrics API and metrics server (i.e., the aggregator): The metrics-server pod keeps spewing errors like this: Running : I haven't verified whether pod autoscaling works in my cluster, but I assume they won't since metrics server seems to discard the metrics from the windows pods. I believe the implementation is in kubelet (for the docker integration), so this should not need CRI changes.
I think this is generally an issue for non-Docker CRIs (not necessarily Windows-exclusive). The kubelet source code suggests that these metrics will be gathered from the CRI (if the CRI is not Docker). The struct that kubelet expects returned from the ContainerStats RPC does not include usageNanoCores. Does this mean that, until this is resolved, custom CRIs are unable to satisfactorily report metrics to the 'metrics-server'?
- are you able to work on this?
that is a good point. For CRI runtimes, the kubelet should calculate the average over 10s to produce this metric.
Good point. We designed CRI around core metrics API, but some CRI runtimes have integrated with cadvisor directly to fill the gap while we wait for core metrics API. I think it's reasonable for kubelet to do this on behalf of all CRI runtimes. I spoke to offline, and this would probably require no more than 2 days of work. If sig-windows (e.g., can pick this up, that'd be great. has agreed to review the changes (thanks!) If not, we should clearly document that this is not supported in Windows GA for 1.14.
thanks for the update. we will wait to hear back from
Currently, is got from docker's stats API on Windows. From docker stats API , only is available, hence is not set on Windows: Do you know any way to get instantaneous for Windows containers?
I have no idea. any idea?
I think this is broader than just a Windows issue. Officially supported tools such as the kubernetes metrics-server expect the field, but custom CRIs that conform to the interfaces defined in the package are not given the liberty to return this value.
suggested previously to cache the stats temporarily and compute the stats using the diff. David, could you chime in on how this could be done? Yes, this was mentioned in the previous comments too. There are plans to transition those components off this field, but it's WIP. We are looking for a workaround in kubelet for now.
my biggest worry is that if we say this impacts the horizonal POD autoscaler, how is that passing in our end2end and conformance tests?
Do you test HPA? It's not part of conformance, and I doubt this is included in windows testing.
As to how this could be done, in and we could just add a in the struct, which we assign the previous CpuUsage metric for each container. Then, when we need to calculate the rate, we pass the previous CpuUsage along with the current one to , and calculate a rate based on the timestamp and values.
ListPodCPUAndMemoryStats() we could just add a map[string]*runtimeapi.CpuUsage in the criStatsProvider struct I've done some tinkering on this myself and I originally thought that it would be as simple as that. It gets much more complicated when you have to work in between the existing cAdvisor-based logic. I assume the intent is to cut it out entirely eventually, but the information passed through the CRI doesn't even come close to being so comprehensive. So far I've found that this approach fixes the problem for the "/stats/summary" api, but not necessarily for the other apis. In particular, "/stats/container" seems to be completely based on cAdvisor, being unimpacted by the changes we made to those two functions. Some monitoring tools and metrics scrapers rely on the "/stats/container" api for their metrics, which means it is insufficient to update one api without doing the other.
We don't need to fill in everything in the summary API, just "usageNanoCores". The "/stats/summary" api is the one used by the metrics server, which is why we care about it in this case. The "/stats/container" isn't something we need to implement to support this.
we a couple of tickets to validate if HPA for Windows containers works. My guess is that it works for memory and not for CPU metrics because of this issue. If this is complicated to fix, i wonder if waiting for the CRI containerD changes in windows would be the best option for supporting this.
containerd and cri-o bypass this issue by leveraging cadvisor as a temporary solution. I don't think that'd work for windows.
Sorry the for late response. HPA is actually working for Windows containers. e.g. in my cluster (v1.12.5), cpu-based HPA is working as expected: Which version of kubernetes are you referring for HPA issues?
are you running heapster, or the metrics server to serve HPA? Heapster uses the cumulative metric, but the metrics-server uses the 10 average rate metric.
metrics-server is used in my cluster.
See .
/milestone v1.14

Related discussion:
text: m.InodesUsed = resource.NewQuantity(int64(seed+offsetInodeUsage), resource.BinarySI) return m } <ins> func TestGetContainerUsageNanoCores(t *testing.T) { var value0 uint64 var value1 uint64 = 10000000000 tests := []struct { desc          string cpuUsageCache map[string]*runtimeapi.CpuUsage stats         *runtimeapi.ContainerStats expected      *uint64 }{ { desc:          "should return nil if stats is nil", cpuUsageCache: map[string]*runtimeapi.CpuUsage{}, }, { desc:          "should return nil if cpu stats is nil", cpuUsageCache: map[string]*runtimeapi.CpuUsage{}, stats: &runtimeapi.ContainerStats{ Attributes: &runtimeapi.ContainerAttributes{ Id: "1", }, Cpu: nil, }, }, { desc:          "should return nil if usageCoreNanoSeconds is nil", cpuUsageCache: map[string]*runtimeapi.CpuUsage{}, stats: &runtimeapi.ContainerStats{ Attributes: &runtimeapi.ContainerAttributes{ Id: "1", }, Cpu: &runtimeapi.CpuUsage{ Timestamp:            1, UsageCoreNanoSeconds: nil, }, }, }, { desc:          "should return nil if cpu stats is not cached yet", cpuUsageCache: map[string]*runtimeapi.CpuUsage{}, stats: &runtimeapi.ContainerStats{ Attributes: &runtimeapi.ContainerAttributes{ Id: "1", }, Cpu: &runtimeapi.CpuUsage{ Timestamp: 1, UsageCoreNanoSeconds: &runtimeapi.UInt64Value{ Value: 10000000000, }, }, }, }, { desc: "should return zero value if cached cpu stats is equal to current value", stats: &runtimeapi.ContainerStats{ Attributes: &runtimeapi.ContainerAttributes{ Id: "1", }, Cpu: &runtimeapi.CpuUsage{ Timestamp: 1, UsageCoreNanoSeconds: &runtimeapi.UInt64Value{ Value: 10000000000, }, }, }, cpuUsageCache: map[string]*runtimeapi.CpuUsage{ "1": { Timestamp: 0, UsageCoreNanoSeconds: &runtimeapi.UInt64Value{ Value: 10000000000, }, }, }, expected: &value0, }, { desc: "should return correct value if cached cpu stats is not equal to current value", stats: &runtimeapi.ContainerStats{ Attributes: &runtimeapi.ContainerAttributes{ Id: "1", }, Cpu: &runtimeapi.CpuUsage{ Timestamp: int64(time.Second / time.Nanosecond), UsageCoreNanoSeconds: &runtimeapi.UInt64Value{ Value: 20000000000, }, }, }, cpuUsageCache: map[string]*runtimeapi.CpuUsage{ "1": { Timestamp: 0, UsageCoreNanoSeconds: &runtimeapi.UInt64Value{ Value: 10000000000, }, }, }, expected: &value1, }, } for _, test := range tests { provider := &criStatsProvider{cpuUsageCache: test.cpuUsageCache} real := provider.getContainerUsageNanoCores(test.stats) assert.Equal(t, test.expected, real, test.desc) } } </ins>