Skip to content

Commit 20aef69

Browse files
authored
Merge pull request #1036 from jsturtevant/windows-support
Windows support via kubeadm
2 parents e22855d + 507da8b commit 20aef69

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4334
-66
lines changed

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,8 +455,10 @@ create-management-cluster: $(KUSTOMIZE) $(ENVSUBST)
455455
# apply CNI ClusterResourceSets
456456
kubectl create configmap calico-addon --from-file=templates/addons/calico.yaml
457457
kubectl create configmap calico-ipv6-addon --from-file=templates/addons/calico-ipv6.yaml
458+
kubectl create configmap flannel-windows-addon --from-file=templates/addons/windows/
458459
kubectl apply -f templates/addons/calico-resource-set.yaml
459-
460+
kubectl apply -f templates/addons/flannel-resource-set.yaml
461+
460462
# Wait for CAPZ deployments
461463
kubectl wait --for=condition=Available --timeout=5m -n capz-system deployment -l cluster.x-k8s.io/provider=infrastructure-azure
462464

@@ -478,6 +480,9 @@ create-workload-cluster: $(ENVSUBST)
478480
@if [[ "${CLUSTER_TEMPLATE}" == *prow* ]]; then \
479481
if [[ "${CLUSTER_TEMPLATE}" == *ipv6* ]]; then \
480482
kubectl --kubeconfig=./kubeconfig apply -f templates/addons/calico-ipv6.yaml; \
483+
elif [[ "${CLUSTER_TEMPLATE}" == *windows* ]]; then \
484+
echo "windows being applied" \
485+
kubectl --kubeconfig=./kubeconfig apply -f templates/addons/windows; \
481486
else \
482487
kubectl --kubeconfig=./kubeconfig apply -f templates/addons/calico.yaml; \
483488
fi \

Tiltfile

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,24 @@ def capz():
196196

197197
k8s_yaml(blob(yaml))
198198

199-
def calico_crs():
199+
def create_crs():
200+
# create config maps
200201
local("kubectl delete configmaps calico-addon --ignore-not-found=true")
201202
local("kubectl create configmap calico-addon --from-file=templates/addons/calico.yaml")
202203
local("kubectl delete configmaps calico-ipv6-addon --ignore-not-found=true")
203204
local("kubectl create configmap calico-ipv6-addon --from-file=templates/addons/calico-ipv6.yaml")
205+
local("kubectl delete configmaps flannel-windows-addon --ignore-not-found=true")
206+
207+
# need to set version for kube-proxy on windows.
208+
# This file is processed then reapply \\ due to the named pipes which need to be escaped for a bug in envsubst library
209+
# https://github.com/kubernetes-sigs/cluster-api/issues/4016
210+
os.putenv("KUBERNETES_VERSION", settings.get("kubernetes_version", {}))
211+
local("kubectl create configmap flannel-windows-addon --from-file=templates/addons/windows/ --dry-run=client -o yaml | " + envsubst_cmd + " | sed -e 's/\\\\/\\\\\\\\/' | kubectl apply -f -")
212+
213+
# set up crs
204214
local("kubectl wait --for=condition=Available --timeout=300s -n capi-webhook-system deployment/capi-controller-manager")
205215
local("kubectl apply -f templates/addons/calico-resource-set.yaml")
216+
local("kubectl apply -f templates/addons/flannel-resource-set.yaml")
206217

207218
# run worker clusters specified from 'tilt up' or in 'tilt_config.json'
208219
def flavors():
@@ -264,8 +275,9 @@ def deploy_worker_templates(template, substitutions):
264275
yaml = yaml.replace("${" + substitution + "}", value)
265276

266277
# programmatically define any remaining vars
278+
# "windows" can not be for cluster name because it sets the dns to trademarked name during reconciliation
267279
substitutions = {
268-
"CLUSTER_NAME": flavor + "-template",
280+
"CLUSTER_NAME": flavor.replace("windows", "win") + "-template",
269281
"AZURE_LOCATION": "eastus",
270282
"AZURE_VNET_NAME": flavor + "-template-vnet",
271283
"AZURE_RESOURCE_GROUP": flavor + "-template-rg",
@@ -334,6 +346,6 @@ deploy_capi()
334346

335347
capz()
336348

337-
calico_crs()
349+
create_crs()
338350

339351
flavors()

cloud/defaults.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,19 @@ const (
3535
const (
3636
// DefaultImageOfferID is the default Azure Marketplace offer ID
3737
DefaultImageOfferID = "capi"
38+
// DefaultWindowsImageOfferID is the default Azure Marketplace offer ID for Windows
39+
DefaultWindowsImageOfferID = "capi-windows"
3840
// DefaultImagePublisherID is the default Azure Marketplace publisher ID
3941
DefaultImagePublisherID = "cncf-upstream"
4042
// LatestVersion is the image version latest
4143
LatestVersion = "latest"
4244
)
4345

46+
const (
47+
// WindowsOS is Windows OS value for OSDisk
48+
WindowsOS = "Windows"
49+
)
50+
4451
const (
4552
// Global is the Azure global location value.
4653
Global = "global"
@@ -177,17 +184,17 @@ func NATRuleID(subscriptionID, resourceGroup, loadBalancerName, natRuleName stri
177184
}
178185

179186
// GetDefaultImageSKUID gets the SKU ID of the image to use for the provided version of Kubernetes.
180-
func getDefaultImageSKUID(k8sVersion string) (string, error) {
187+
func getDefaultImageSKUID(k8sVersion, os, osVersion string) (string, error) {
181188
version, err := semver.ParseTolerant(k8sVersion)
182189
if err != nil {
183190
return "", errors.Wrapf(err, "unable to parse Kubernetes version \"%s\" in spec, expected valid SemVer string", k8sVersion)
184191
}
185-
return fmt.Sprintf("k8s-%ddot%ddot%d-ubuntu-1804", version.Major, version.Minor, version.Patch), nil
192+
return fmt.Sprintf("k8s-%ddot%ddot%d-%s-%s", version.Major, version.Minor, version.Patch, os, osVersion), nil
186193
}
187194

188195
// GetDefaultUbuntuImage returns the default image spec for Ubuntu.
189196
func GetDefaultUbuntuImage(k8sVersion string) (*infrav1.Image, error) {
190-
skuID, err := getDefaultImageSKUID(k8sVersion)
197+
skuID, err := getDefaultImageSKUID(k8sVersion, "ubuntu", "1804")
191198
if err != nil {
192199
return nil, errors.Wrapf(err, "failed to get default image")
193200
}
@@ -204,6 +211,25 @@ func GetDefaultUbuntuImage(k8sVersion string) (*infrav1.Image, error) {
204211
return defaultImage, nil
205212
}
206213

214+
// GetDefaultWindowsImage returns the default image spec for Windows.
215+
func GetDefaultWindowsImage(k8sVersion string) (*infrav1.Image, error) {
216+
skuID, err := getDefaultImageSKUID(k8sVersion, "windows", "2019")
217+
if err != nil {
218+
return nil, errors.Wrapf(err, "failed to get default image")
219+
}
220+
221+
defaultImage := &infrav1.Image{
222+
Marketplace: &infrav1.AzureMarketplaceImage{
223+
Publisher: DefaultImagePublisherID,
224+
Offer: DefaultWindowsImageOfferID,
225+
SKU: skuID,
226+
Version: LatestVersion,
227+
},
228+
}
229+
230+
return defaultImage, nil
231+
}
232+
207233
// UserAgent specifies a string to append to the agent identifier.
208234
func UserAgent() string {
209235
return fmt.Sprintf("cluster-api-provider-azure/%s", version.Get().String())

cloud/defaults_test.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,54 +29,77 @@ func TestGetDefaultImageSKUID(t *testing.T) {
2929

3030
var tests = []struct {
3131
k8sVersion string
32+
os string
33+
osVersion string
3234
expectedResult string
3335
expectedError bool
3436
}{
3537
{
3638
k8sVersion: "v1.14.9",
3739
expectedResult: "k8s-1dot14dot9-ubuntu-1804",
3840
expectedError: false,
41+
os: "ubuntu",
42+
osVersion: "1804",
3943
},
4044
{
4145
k8sVersion: "v1.14.10",
4246
expectedResult: "k8s-1dot14dot10-ubuntu-1804",
4347
expectedError: false,
48+
os: "ubuntu",
49+
osVersion: "1804",
4450
},
4551
{
4652
k8sVersion: "v1.15.6",
4753
expectedResult: "k8s-1dot15dot6-ubuntu-1804",
4854
expectedError: false,
55+
os: "ubuntu",
56+
osVersion: "1804",
4957
},
5058
{
5159
k8sVersion: "v1.15.7",
5260
expectedResult: "k8s-1dot15dot7-ubuntu-1804",
5361
expectedError: false,
62+
os: "ubuntu",
63+
osVersion: "1804",
5464
},
5565
{
5666
k8sVersion: "v1.16.3",
5767
expectedResult: "k8s-1dot16dot3-ubuntu-1804",
5868
expectedError: false,
69+
os: "ubuntu",
70+
osVersion: "1804",
5971
},
6072
{
6173
k8sVersion: "v1.16.4",
6274
expectedResult: "k8s-1dot16dot4-ubuntu-1804",
6375
expectedError: false,
76+
os: "ubuntu",
77+
osVersion: "1804",
6478
},
6579
{
6680
k8sVersion: "1.12.0",
6781
expectedResult: "k8s-1dot12dot0-ubuntu-1804",
6882
expectedError: false,
83+
os: "ubuntu",
84+
osVersion: "1804",
6985
},
7086
{
7187
k8sVersion: "1.1.notvalid.semver",
7288
expectedResult: "",
7389
expectedError: true,
7490
},
91+
{
92+
k8sVersion: "v1.19.3",
93+
expectedResult: "k8s-1dot19dot3-windows-2019",
94+
expectedError: false,
95+
os: "windows",
96+
osVersion: "2019",
97+
},
7598
}
7699

77100
for _, test := range tests {
78101
t.Run(test.k8sVersion, func(t *testing.T) {
79-
id, err := getDefaultImageSKUID(test.k8sVersion)
102+
id, err := getDefaultImageSKUID(test.k8sVersion, test.os, test.osVersion)
80103

81104
if test.expectedError {
82105
g.Expect(err).To(HaveOccurred())

cloud/scope/machine.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"encoding/base64"
2222
"encoding/json"
23+
"strings"
2324

2425
"github.com/Azure/go-autorest/autorest/to"
2526
"github.com/go-logr/logr"
@@ -260,6 +261,15 @@ func (m *MachineScope) AvailabilityZone() string {
260261

261262
// Name returns the AzureMachine name.
262263
func (m *MachineScope) Name() string {
264+
// Windows Machine names cannot be longer than 15 chars
265+
if m.AzureMachine.Spec.OSDisk.OSType == azure.WindowsOS && len(m.AzureMachine.Name) > 15 {
266+
clustername := m.ClusterName()
267+
if len(m.ClusterName()) > 9 {
268+
clustername = strings.TrimSuffix(clustername[0:9], "-")
269+
}
270+
271+
return clustername + "-" + m.AzureMachine.Name[len(m.AzureMachine.Name)-5:]
272+
}
263273
return m.AzureMachine.Name
264274
}
265275

@@ -440,6 +450,12 @@ func (m *MachineScope) GetVMImage() (*infrav1.Image, error) {
440450
if m.AzureMachine.Spec.Image != nil {
441451
return m.AzureMachine.Spec.Image, nil
442452
}
443-
m.Info("No image specified for machine, using default", "machine", m.AzureMachine.GetName())
453+
454+
if m.AzureMachine.Spec.OSDisk.OSType == azure.WindowsOS {
455+
m.Info("No image specified for machine, using default Windows Image", "machine", m.AzureMachine.GetName())
456+
return azure.GetDefaultWindowsImage(to.String(m.Machine.Spec.Version))
457+
}
458+
459+
m.Info("No image specified for machine, using default Linux Image", "machine", m.AzureMachine.GetName())
444460
return azure.GetDefaultUbuntuImage(to.String(m.Machine.Spec.Version))
445461
}

cloud/scope/machine_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package scope
18+
19+
import (
20+
"testing"
21+
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
24+
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha3"
25+
azure "sigs.k8s.io/cluster-api-provider-azure/cloud"
26+
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
27+
)
28+
29+
func TestMachineScope_Name(t *testing.T) {
30+
type fields struct {
31+
ClusterScoper azure.ClusterScoper
32+
AzureMachine *infrav1.AzureMachine
33+
}
34+
tests := []struct {
35+
name string
36+
machineScope MachineScope
37+
want string
38+
testLength bool
39+
}{
40+
{
41+
name: "linux can be any length",
42+
machineScope: MachineScope{
43+
AzureMachine: &infrav1.AzureMachine{
44+
ObjectMeta: metav1.ObjectMeta{
45+
Name: "machine-with-really-really-long-name",
46+
},
47+
Spec: infrav1.AzureMachineSpec{
48+
OSDisk: infrav1.OSDisk{
49+
OSType: "Linux",
50+
},
51+
},
52+
},
53+
},
54+
want: "machine-with-really-really-long-name",
55+
},
56+
{
57+
name: "Windows name with long MachineName and short cluster name",
58+
machineScope: MachineScope{
59+
ClusterScoper: &ClusterScope{
60+
Cluster: &clusterv1.Cluster{
61+
ObjectMeta: metav1.ObjectMeta{
62+
Name: "cluster",
63+
},
64+
},
65+
},
66+
AzureMachine: &infrav1.AzureMachine{
67+
TypeMeta: metav1.TypeMeta{},
68+
ObjectMeta: metav1.ObjectMeta{
69+
Name: "machine-90123456",
70+
},
71+
Spec: infrav1.AzureMachineSpec{
72+
OSDisk: infrav1.OSDisk{
73+
OSType: "Windows",
74+
},
75+
},
76+
Status: infrav1.AzureMachineStatus{},
77+
},
78+
},
79+
want: "cluster-23456",
80+
testLength: true,
81+
},
82+
{
83+
name: "Windows name with long MachineName and long cluster name",
84+
machineScope: MachineScope{
85+
ClusterScoper: &ClusterScope{
86+
Cluster: &clusterv1.Cluster{
87+
ObjectMeta: metav1.ObjectMeta{
88+
Name: "cluster8901234",
89+
},
90+
},
91+
},
92+
AzureMachine: &infrav1.AzureMachine{
93+
TypeMeta: metav1.TypeMeta{},
94+
ObjectMeta: metav1.ObjectMeta{
95+
Name: "machine-90123456",
96+
},
97+
Spec: infrav1.AzureMachineSpec{
98+
OSDisk: infrav1.OSDisk{
99+
OSType: "Windows",
100+
},
101+
},
102+
Status: infrav1.AzureMachineStatus{},
103+
},
104+
},
105+
want: "cluster89-23456",
106+
testLength: true,
107+
},
108+
}
109+
for _, tt := range tests {
110+
t.Run(tt.name, func(t *testing.T) {
111+
112+
got := tt.machineScope.Name()
113+
if got != tt.want {
114+
t.Errorf("MachineScope.Name() = %v, want %v", got, tt.want)
115+
}
116+
117+
if tt.testLength && len(got) > 15 {
118+
t.Errorf("Length of MachineScope.Name() = %v, want less than %v", len(got), 15)
119+
}
120+
})
121+
}
122+
}

cloud/scope/machinepool.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ func (m *MachinePoolScope) ScaleSetSpec() azure.ScaleSetSpec {
124124

125125
// Name returns the Azure Machine Pool Name.
126126
func (m *MachinePoolScope) Name() string {
127+
// Windows Machine pools names cannot be longer than 9 chars
128+
if m.AzureMachinePool.Spec.Template.OSDisk.OSType == azure.WindowsOS && len(m.AzureMachinePool.Name) > 9 {
129+
return "win-" + m.AzureMachinePool.Name[len(m.AzureMachinePool.Name)-5:]
130+
}
127131
return m.AzureMachinePool.Name
128132
}
129133

@@ -310,6 +314,12 @@ func (m *MachinePoolScope) GetVMImage() (*infrav1.Image, error) {
310314
if m.AzureMachinePool.Spec.Template.Image != nil {
311315
return m.AzureMachinePool.Spec.Template.Image, nil
312316
}
317+
318+
if m.AzureMachinePool.Spec.Template.OSDisk.OSType == azure.WindowsOS {
319+
m.Info("No image specified for machine, using default Windows Image", "machine", m.MachinePool.GetName())
320+
return azure.GetDefaultWindowsImage(to.String(m.MachinePool.Spec.Template.Spec.Version))
321+
}
322+
313323
m.Info("No image specified for machine, using default", "machine", m.MachinePool.GetName())
314324
return azure.GetDefaultUbuntuImage(to.String(m.MachinePool.Spec.Template.Spec.Version))
315325
}

0 commit comments

Comments
 (0)