This PR fixes issue #55031 where kubelet.exe crashes on Windows Server Core. The root cause is that kubelet.exe depends on package lxn/win pdh and kernel32 wrapper for node metrics. However, opengl32.dll is not available in Server Core and lxn/win requires the presence of all win32 DLLs.

This PR uses a slim win32 package JeffAshton/win_pdh since most win32 APIs needed are PDH API. Also this PR makes own implementation of GetPhysicallyInstalledSystemMemory until golang Windows syscall has it or lxn/win fixes opengl32 issue. Also this PR modifies the way to get Windows version.
This commit is contained in:
Jiangtian Li 2017-11-10 14:50:44 -08:00
parent fe399bda1a
commit b91e030fcf
5 changed files with 48 additions and 44 deletions

View File

@ -34,8 +34,8 @@ go_library(
"//vendor/github.com/google/cadvisor/info/v2:go_default_library", "//vendor/github.com/google/cadvisor/info/v2:go_default_library",
] + select({ ] + select({
"@io_bazel_rules_go//go/platform:windows_amd64": [ "@io_bazel_rules_go//go/platform:windows_amd64": [
"//vendor/github.com/JeffAshton/win_pdh:go_default_library",
"//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/lxn/win:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
], ],
"//conditions:default": [], "//conditions:default": [],

View File

@ -25,14 +25,20 @@ import (
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
"syscall"
"time" "time"
"unsafe"
"github.com/golang/glog" "github.com/golang/glog"
cadvisorapi "github.com/google/cadvisor/info/v1" cadvisorapi "github.com/google/cadvisor/info/v1"
"github.com/lxn/win"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
) )
var (
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
procGetPhysicallyInstalledSystemMemory = modkernel32.NewProc("GetPhysicallyInstalledSystemMemory")
)
// NewPerfCounterClient creates a client using perf counters // NewPerfCounterClient creates a client using perf counters
func NewPerfCounterClient() (Client, error) { func NewPerfCounterClient() (Client, error) {
return newClient(&perfCounterNodeStatsClient{}) return newClient(&perfCounterNodeStatsClient{})
@ -51,13 +57,18 @@ func (p *perfCounterNodeStatsClient) startMonitoring() error {
return err return err
} }
version, err := exec.Command("cmd", "/C", "ver").Output() iv, err := exec.Command("powershell", "-command", "(Get-CimInstance Win32_OperatingSystem).Caption").Output()
if err != nil { if err != nil {
return err return err
} }
osImageVersion := strings.TrimSpace(string(iv))
kv, err := exec.Command("powershell", "-command", "[System.Environment]::OSVersion.Version.ToString()").Output()
if err != nil {
return err
}
kernelVersion := strings.TrimSpace(string(kv))
osImageVersion := strings.TrimSpace(string(version))
kernelVersion := extractVersionNumber(osImageVersion)
p.nodeInfo = nodeInfo{ p.nodeInfo = nodeInfo{
kernelVersion: kernelVersion, kernelVersion: kernelVersion,
osImageVersion: osImageVersion, osImageVersion: osImageVersion,
@ -158,9 +169,18 @@ func (p *perfCounterNodeStatsClient) convertCPUValue(cpuValue uint64) uint64 {
func getPhysicallyInstalledSystemMemoryBytes() (uint64, error) { func getPhysicallyInstalledSystemMemoryBytes() (uint64, error) {
var physicalMemoryKiloBytes uint64 var physicalMemoryKiloBytes uint64
if ok := win.GetPhysicallyInstalledSystemMemory(&physicalMemoryKiloBytes); !ok { if ok := getPhysicallyInstalledSystemMemory(&physicalMemoryKiloBytes); !ok {
return 0, errors.New("unable to read physical memory") return 0, errors.New("unable to read physical memory")
} }
return physicalMemoryKiloBytes * 1024, nil // convert kilobytes to bytes return physicalMemoryKiloBytes * 1024, nil // convert kilobytes to bytes
} }
func getPhysicallyInstalledSystemMemory(totalMemoryInKilobytes *uint64) bool {
ret, _, _ := syscall.Syscall(procGetPhysicallyInstalledSystemMemory.Addr(), 1,
uintptr(unsafe.Pointer(totalMemoryInKilobytes)),
0,
0)
return ret != 0
}

View File

@ -24,7 +24,7 @@ import (
"time" "time"
"unsafe" "unsafe"
"github.com/lxn/win" "github.com/JeffAshton/win_pdh"
) )
const ( const (
@ -37,31 +37,31 @@ const (
) )
type perfCounter struct { type perfCounter struct {
queryHandle win.PDH_HQUERY queryHandle win_pdh.PDH_HQUERY
counterHandle win.PDH_HCOUNTER counterHandle win_pdh.PDH_HCOUNTER
} }
func newPerfCounter(counter string) (*perfCounter, error) { func newPerfCounter(counter string) (*perfCounter, error) {
var queryHandle win.PDH_HQUERY var queryHandle win_pdh.PDH_HQUERY
var counterHandle win.PDH_HCOUNTER var counterHandle win_pdh.PDH_HCOUNTER
ret := win.PdhOpenQuery(0, 0, &queryHandle) ret := win_pdh.PdhOpenQuery(0, 0, &queryHandle)
if ret != win.ERROR_SUCCESS { if ret != win_pdh.ERROR_SUCCESS {
return nil, errors.New("unable to open query through DLL call") return nil, errors.New("unable to open query through DLL call")
} }
ret = win.PdhValidatePath(counter) ret = win_pdh.PdhValidatePath(counter)
if ret != win.ERROR_SUCCESS { if ret != win_pdh.ERROR_SUCCESS {
return nil, fmt.Errorf("unable to valid path to counter. Error code is %x", ret) return nil, fmt.Errorf("unable to valid path to counter. Error code is %x", ret)
} }
ret = win.PdhAddEnglishCounter(queryHandle, counter, 0, &counterHandle) ret = win_pdh.PdhAddEnglishCounter(queryHandle, counter, 0, &counterHandle)
if ret != win.ERROR_SUCCESS { if ret != win_pdh.ERROR_SUCCESS {
return nil, fmt.Errorf("unable to add process counter. Error code is %x", ret) return nil, fmt.Errorf("unable to add process counter. Error code is %x", ret)
} }
ret = win.PdhCollectQueryData(queryHandle) ret = win_pdh.PdhCollectQueryData(queryHandle)
if ret != win.ERROR_SUCCESS { if ret != win_pdh.ERROR_SUCCESS {
return nil, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) return nil, fmt.Errorf("unable to collect data from counter. Error code is %x", ret)
} }
@ -72,24 +72,24 @@ func newPerfCounter(counter string) (*perfCounter, error) {
} }
func (p *perfCounter) getData() (uint64, error) { func (p *perfCounter) getData() (uint64, error) {
ret := win.PdhCollectQueryData(p.queryHandle) ret := win_pdh.PdhCollectQueryData(p.queryHandle)
if ret != win.ERROR_SUCCESS { if ret != win_pdh.ERROR_SUCCESS {
return 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) return 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret)
} }
var bufSize, bufCount uint32 var bufSize, bufCount uint32
var size = uint32(unsafe.Sizeof(win.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE{})) var size = uint32(unsafe.Sizeof(win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE{}))
var emptyBuf [1]win.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE // need at least 1 addressable null ptr. var emptyBuf [1]win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE // need at least 1 addressable null ptr.
var data uint64 var data uint64
ret = win.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &emptyBuf[0]) ret = win_pdh.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &emptyBuf[0])
if ret != win.PDH_MORE_DATA { if ret != win_pdh.PDH_MORE_DATA {
return 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) return 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret)
} }
filledBuf := make([]win.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, bufCount*size) filledBuf := make([]win_pdh.PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, bufCount*size)
ret = win.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &filledBuf[0]) ret = win_pdh.PdhGetFormattedCounterArrayDouble(p.counterHandle, &bufSize, &bufCount, &filledBuf[0])
if ret != win.ERROR_SUCCESS { if ret != win_pdh.ERROR_SUCCESS {
return 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret) return 0, fmt.Errorf("unable to collect data from counter. Error code is %x", ret)
} }

View File

@ -18,7 +18,6 @@ limitations under the License.
package winstats package winstats
import ( import (
"regexp"
"time" "time"
cadvisorapi "github.com/google/cadvisor/info/v1" cadvisorapi "github.com/google/cadvisor/info/v1"
@ -135,11 +134,3 @@ func (c *statsClient) createRootContainerInfo() (*cadvisorapiv2.ContainerInfo, e
return &rootInfo, nil return &rootInfo, nil
} }
// extractVersionNumber gets the version number from the full version string on Windows
// e.g. extracts "10.0.14393" from "Microsoft Windows [Version 10.0.14393]"
func extractVersionNumber(fullVersion string) string {
re := regexp.MustCompile("[^0-9.]")
version := re.ReplaceAllString(fullVersion, "")
return version
}

View File

@ -116,13 +116,6 @@ func TestWinVersionInfo(t *testing.T) {
KernelVersion: "v42"}) KernelVersion: "v42"})
} }
func TestExtractVersionNumber(t *testing.T) {
fullVersion := "Microsoft Windows [Version 10.0.14393]"
versionNumber := extractVersionNumber(fullVersion)
expected := "10.0.14393"
assert.Equal(t, expected, versionNumber)
}
func getClient(t *testing.T) Client { func getClient(t *testing.T) Client {
f := fakeWinNodeStatsClient{} f := fakeWinNodeStatsClient{}
c, err := newClient(f) c, err := newClient(f)