Skip to content

Commit

Permalink
Allow containerd on Windows 11 to use Windows Server 2022 images
Browse files Browse the repository at this point in the history
Windows 11 clients support running process-isolated containers of 10.0.20348 and above. Or to put it another way, Windows 11 clients can run Windows Server 2022 containers even when their build numbers don't match. Docker Engine already supports this scenario; this change updates containerd to make it also support this scenario.

The [container/OS version compatibility page](https://learn.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/version-compatibility?tabs=windows-server-2022%2Cwindows-11#windows-client-host-os-compatibility) documents this level of compatibility between the client OS and server OS versions.

I didn't explicitly add support for Windows 10 clients as the current version of Windows 10 is no longer compatible with the LTSC2019 container images, and there's no way to downgrade a Windows 10 install to the required version. Windows 11 compatibility with LTSC2022 is much more stable in practice than Windows 10, and Microsoft is committed to permitting any combination of Windows Server 2022 images and Windows 11 clients as outlined in the [Windows Server 2022 and beyond for containers blog post](https://techcommunity.microsoft.com/t5/containers/windows-server-2022-and-beyond-for-containers/ba-p/2712487).
  • Loading branch information
hach-que committed Feb 19, 2023
1 parent 887395a commit 672dd76
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 1 deletion.
47 changes: 46 additions & 1 deletion platforms/defaults_windows.go
Expand Up @@ -42,6 +42,7 @@ type windowsmatcher struct {
specs.Platform
osVersionPrefix string
defaultMatcher Matcher
isClientOS bool
}

// Match matches platform with the same windows major, minor
Expand All @@ -53,6 +54,31 @@ func (m windowsmatcher) Match(p specs.Platform) bool {
if strings.HasPrefix(p.OSVersion, m.osVersionPrefix) {
return true
}
if m.isClientOS && p.OSVersion != "" {
split := strings.Split(p.OSVersion, ".")
if len(split) >= 3 {
major, err := strconv.Atoi(split[0])
if err != nil {
return false
}
minor, err := strconv.Atoi(split[1])
if err != nil {
return false
}
build, err := strconv.Atoi(split[2])
if err != nil {
return false
}

if (major == 10 && minor == 0 && build >= 20348) ||
(major == 10 && minor > 0) {
// Windows 11 client machines are implicitly
// compatible with 10.0.20348 (LTSC2022), even
// when the client has a newer build version.
return true
}
}
}
return p.OSVersion == ""
}

Expand All @@ -61,16 +87,35 @@ func (m windowsmatcher) Match(p specs.Platform) bool {

// Less sorts matched platforms in front of other platforms.
// For matched platforms, it puts platforms with larger revision
// number in front.
// number in front (and for Win 11 clients, larger build numbers).
func (m windowsmatcher) Less(p1, p2 specs.Platform) bool {
m1, m2 := m.Match(p1), m.Match(p2)
if m1 && m2 {
// Build number comparison will only be used for clients
// where differing build versions can match.
b1, b2 := build(p1.OSVersion), build(p2.OSVersion)
if b1 != b2 {
return b1 > b2
}

r1, r2 := revision(p1.OSVersion), revision(p2.OSVersion)
return r1 > r2
}
return m1 && !m2
}

func build(v string) int {
parts := strings.Split(v, ".")
if len(parts) < 3 {
return 0
}
r, err := strconv.Atoi(parts[2])
if err != nil {
return 0
}
return r
}

func revision(v string) int {
parts := strings.Split(v, ".")
if len(parts) < 4 {
Expand Down
98 changes: 98 additions & 0 deletions platforms/defaults_windows_test.go
Expand Up @@ -80,6 +80,7 @@ func TestMatchComparerMatch_WCOW(t *testing.T) {
defaultMatcher: &matcher{
Platform: Normalize(DefaultSpec()),
},
isClientOS: false,
}
for _, test := range []struct {
platform imagespec.Platform
Expand Down Expand Up @@ -158,6 +159,7 @@ func TestMatchComparerMatch_LCOW(t *testing.T) {
},
),
},
isClientOS: false,
}
for _, test := range []struct {
platform imagespec.Platform
Expand Down Expand Up @@ -210,6 +212,7 @@ func TestMatchComparerLess(t *testing.T) {
defaultMatcher: &matcher{
Platform: Normalize(DefaultSpec()),
},
isClientOS: false,
}
platforms := []imagespec.Platform{
{
Expand Down Expand Up @@ -268,3 +271,98 @@ func TestMatchComparerLess(t *testing.T) {
})
assert.Equal(t, expected, platforms)
}

func TestMatchComparerClientOSMatch(t *testing.T) {
m := windowsmatcher{
Platform: DefaultSpec(),
osVersionPrefix: "10.0.22000",
defaultMatcher: &matcher{
Platform: Normalize(DefaultSpec()),
},
isClientOS: true,
}
for _, test := range []struct {
platform imagespec.Platform
match bool
}{
{
platform: imagespec.Platform{
Architecture: "amd64",
OS: "windows",
OSVersion: "10.0.17763.2114",
},
match: false,
},
{
platform: imagespec.Platform{
Architecture: "amd64",
OS: "windows",
OSVersion: "10.0.20348.169",
},
match: true,
},
{
platform: imagespec.Platform{
Architecture: "amd64",
OS: "windows",
OSVersion: "10.0.22000",
},
match: true,
},
{
platform: imagespec.Platform{
Architecture: "amd64",
OS: "windows",
},
match: true,
},
{
platform: imagespec.Platform{
Architecture: "amd64",
OS: "linux",
},
match: false,
},
} {
assert.Equal(t, test.match, m.Match(test.platform), "should match %b, %s to %s", test.match, m.Platform, test.platform)
}
}

func TestMatchComparerClientOSPriority(t *testing.T) {
m := windowsmatcher{
Platform: DefaultSpec(),
osVersionPrefix: "10.0.22000",
defaultMatcher: &matcher{
Platform: Normalize(DefaultSpec()),
},
isClientOS: true,
}
platforms := []imagespec.Platform{
{
Architecture: "amd64",
OS: "windows",
OSVersion: "10.0.20348.169",
},
{
Architecture: "amd64",
OS: "windows",
OSVersion: "10.0.22000",
},
}
expected := []imagespec.Platform{
{
Architecture: "amd64",
OS: "windows",
OSVersion: "10.0.22000",
},
{
Architecture: "amd64",
OS: "windows",
OSVersion: "10.0.20348.169",
},
}
sort.SliceStable(platforms, func(i, j int) bool {
return m.Less(platforms[i], platforms[j])
})
assert.Equal(t, expected, platforms)
}
17 changes: 17 additions & 0 deletions platforms/platforms_windows.go
Expand Up @@ -18,8 +18,24 @@ package platforms

import (
specs "github.com/opencontainers/image-spec/specs-go/v1"
"golang.org/x/sys/windows/registry"
)

func isClientOS() bool {
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
if err != nil {
return false
}
defer k.Close()

installationType, _, err := k.GetStringValue("InstallationType")
if err != nil {
return false
}

return installationType == "Client"
}

// NewMatcher returns a Windows matcher that will match on osVersionPrefix if
// the platform is Windows otherwise use the default matcher
func newDefaultMatcher(platform specs.Platform) Matcher {
Expand All @@ -30,5 +46,6 @@ func newDefaultMatcher(platform specs.Platform) Matcher {
defaultMatcher: &matcher{
Platform: Normalize(platform),
},
isClientOS: isClientOS(),
}
}

0 comments on commit 672dd76

Please sign in to comment.