diff --git a/examples/14-windows-nodes.yaml b/examples/14-windows-nodes.yaml index 698247685c..59dcb25f5f 100644 --- a/examples/14-windows-nodes.yaml +++ b/examples/14-windows-nodes.yaml @@ -1,5 +1,5 @@ # An example of ClusterConfig containing Windows and Linux node groups to support Windows workloads -# This example should be run with `eksctl create cluster -f 14-windows-nodes.yaml` +# This example should be run with `eksctl create cluster -f 14-windows-nodes.yaml --install-vpc-controllers` --- apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig @@ -13,8 +13,6 @@ nodeGroups: amiFamily: WindowsServer2019FullContainer minSize: 2 maxSize: 3 - -managedNodeGroups: - name: linux-ng instanceType: t2.large minSize: 2 diff --git a/integration/tests/windows/windows_test.go b/integration/tests/windows/windows_test.go index 298726b343..67b08e1994 100644 --- a/integration/tests/windows/windows_test.go +++ b/integration/tests/windows/windows_test.go @@ -70,6 +70,7 @@ var _ = Describe("(Integration) [Windows Nodegroups]", func() { "--config-file", "-", "--verbose", "4", "--kubeconfig", params.KubeconfigPath, + "--install-vpc-controllers", ). WithoutArg("--region", params.Region). WithStdin(bytes.NewReader(data)) diff --git a/pkg/actions/nodegroup/create.go b/pkg/actions/nodegroup/create.go index 77cc8bbb75..57205cc409 100644 --- a/pkg/actions/nodegroup/create.go +++ b/pkg/actions/nodegroup/create.go @@ -160,15 +160,6 @@ func (m *Manager) nodeCreationTasks(options CreateOpts, nodegroupFilter filter.N taskTree.Append(m.stackManager.NewClusterCompatTask()) } - if m.cfg.HasWindowsNodeGroup() { - taskTree.Append(&eks.WindowsIPAMTask{ - Info: "enable Windows IPAM", - ClientsetFunc: func() (kubernetes.Interface, error) { - return m.ctl.NewStdClientSet(m.cfg) - }, - }) - } - awsNodeUsesIRSA, err := init.DoesAWSNodeUseIRSA(m.ctl.Provider, m.clientSet) if err != nil { return errors.Wrap(err, "couldn't check aws-node for annotation") diff --git a/pkg/addons/assets.go b/pkg/addons/assets.go index 8acdc6f886..5baaf0a456 100644 --- a/pkg/addons/assets.go +++ b/pkg/addons/assets.go @@ -3,7 +3,12 @@ // assets/efa-device-plugin.yaml (3.084kB) // assets/neuron-device-plugin.yaml (3.623kB) // assets/nvidia-device-plugin.yaml (2.369kB) -// assets/vpc-controller-metadata.yaml (924B) +// assets/vpc-admission-webhook-config.yaml (524B) +// assets/vpc-admission-webhook-csr.yaml (234B) +// assets/vpc-admission-webhook-dep.yaml (1.675kB) +// assets/vpc-admission-webhook.yaml (231B) +// assets/vpc-resource-controller-dep.yaml (1.679kB) +// assets/vpc-resource-controller.yaml (565B) package addons @@ -132,23 +137,123 @@ func nvidiaDevicePluginYaml() (*asset, error) { return a, nil } -var _vpcControllerMetadataYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xa4\x93\xc1\x6e\xc2\x40\x0c\x44\xef\xf9\x8a\xfc\xc0\x06\x71\xab\x72\x2c\xbd\xf6\x02\x52\x7b\x76\x36\x66\xb1\x92\xd8\xa9\xed\x4d\x45\xbf\xbe\x42\x14\x5a\x21\xa8\x82\x38\x27\xe3\x79\x9e\xf1\x86\x10\x0a\x18\xe9\x0d\xd5\x48\xb8\x2e\xa7\x65\xd1\x11\xb7\x75\xb9\x41\x9d\x28\x62\x31\xa0\x43\x0b\x0e\x75\x51\x96\x0c\x03\xd6\xe5\x34\xc6\x00\xed\x40\x76\x50\x84\x4f\x6c\x76\x22\xdd\xcf\x57\x1b\x21\x62\x5d\x76\xb9\xc1\x60\x7b\x73\x1c\x8a\xe2\xd2\xe2\xac\x55\x4c\x64\xae\xe0\x24\x5c\x75\x4f\x56\x91\x2c\xa6\x65\x83\x0e\x27\x88\xd7\xec\xe0\xc4\xe9\xfd\x68\xb2\x12\xde\x52\xca\x47\xc5\x5c\xb2\x10\xb7\xe9\x0e\xba\x88\xea\xb4\xa5\x08\x8e\x76\x1d\x6a\xf5\xfb\xc7\x86\x12\x13\xa7\x35\x7e\x64\x34\x9f\x4b\x54\xfd\x1f\xcf\x38\xda\xe2\x5c\xc3\x0b\x8e\xbd\xec\x07\xe4\xd9\xd3\xef\xd8\x55\x1b\x88\x15\x64\xdf\x89\xd2\xd7\x45\x0d\xa7\x65\xfb\x6c\x8e\xba\x96\xfe\xd6\x29\x28\x9a\x64\x8d\x18\xa2\xb0\xab\xf4\x3d\xea\xa3\x4e\xcf\xc4\x2d\x71\x7a\xc4\xf0\xce\x14\xaf\xcd\x9c\x9f\xe3\x9f\x47\x13\x15\x67\x37\x15\x0e\xb7\x66\xb7\x7d\xbe\x03\x00\x00\xff\xff\xad\xa6\xf6\xbd\x9c\x03\x00\x00") +var _vpcAdmissionWebhookConfigYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\x91\x4f\x6b\xf3\x30\x0c\xc6\xef\xf9\x14\x22\xf7\xa4\xf4\xf6\xe2\xdb\x4b\x29\x63\x87\xc1\x18\x63\x3b\x8c\x1d\x14\x47\x4d\x45\x62\xcb\x58\x76\x4a\xf7\xe9\x47\xfe\xb4\xac\xb0\xd5\x17\xdb\x7a\xa4\xdf\x63\xc9\x18\xf8\x8d\xa2\xb2\x78\x03\xd8\x3a\xd6\xe9\x18\xa9\x63\x4d\x11\x13\x8b\xaf\xfb\x7f\x5a\xb3\x6c\xc6\x6d\x43\x09\xb7\x45\xcf\xbe\x35\xf0\x94\x13\x26\xf6\xdd\x3b\x35\x47\x91\x7e\x27\xfe\xc0\x5d\x5e\x2a\x0a\x47\x09\x5b\x4c\x68\x0a\x00\x8f\x8e\x0c\x8c\xc1\x56\x57\x7a\x75\x5a\x8a\x2a\x7b\xe8\xd6\x0c\x0d\x68\xc9\x40\x9f\x1b\xaa\xf4\xac\x89\x5c\x01\x30\x60\x43\x83\x4e\x10\x00\x0c\xe1\x0f\x4a\xb1\xee\x73\x62\x75\xcf\xaf\x46\x87\x5f\xe2\xf1\xa4\xb5\x15\x37\x63\xed\xc0\xe4\xd3\xf2\xfa\xc5\x08\x40\x29\x8e\x6c\xe9\x72\xbd\xdb\xc2\x4d\xce\xaf\x4d\x2c\x2b\x60\x3a\x1a\x28\x37\x6e\x1a\x1b\x95\x73\x3c\xe6\x81\xf4\xe2\x52\x81\x04\x5a\xc6\xa7\x06\x3e\xa0\xdc\xbd\xec\xff\xbf\xee\x4b\xf8\xbc\x32\x30\xf0\x43\x94\x1c\x26\xbd\x2c\x6f\xe2\xeb\x0f\xce\xca\xb8\xfd\xa1\x45\x52\xc9\xd1\xd2\xac\x04\x69\x75\xd5\x0e\xc8\x43\x8e\xf4\x2c\x03\xdb\xb3\x81\xc7\xce\x4b\xa4\xe2\x3b\x00\x00\xff\xff\x49\xee\x9e\x02\x0c\x02\x00\x00") -func vpcControllerMetadataYamlBytes() ([]byte, error) { +func vpcAdmissionWebhookConfigYamlBytes() ([]byte, error) { return bindataRead( - _vpcControllerMetadataYaml, - "vpc-controller-metadata.yaml", + _vpcAdmissionWebhookConfigYaml, + "vpc-admission-webhook-config.yaml", ) } -func vpcControllerMetadataYaml() (*asset, error) { - bytes, err := vpcControllerMetadataYamlBytes() +func vpcAdmissionWebhookConfigYaml() (*asset, error) { + bytes, err := vpcAdmissionWebhookConfigYamlBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "vpc-controller-metadata.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xa6, 0x89, 0x6b, 0xa9, 0x33, 0x90, 0x94, 0x63, 0xb2, 0xaf, 0x3a, 0x5c, 0x22, 0x5b, 0x19, 0xc7, 0xb9, 0x22, 0xd6, 0x5a, 0xbb, 0x68, 0xac, 0xf8, 0x24, 0x68, 0x4d, 0x2c, 0x54, 0x12, 0x18, 0x57}} + info := bindataFileInfo{name: "vpc-admission-webhook-config.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x7d, 0x11, 0x23, 0x17, 0x2f, 0xdc, 0x4e, 0x18, 0xaa, 0xc8, 0x66, 0xee, 0xf3, 0xc1, 0x85, 0x63, 0xb1, 0xe3, 0x53, 0x57, 0x80, 0x96, 0xe6, 0x54, 0x26, 0x46, 0x6b, 0x3f, 0x17, 0x20, 0x31, 0x8}} + return a, nil +} + +var _vpcAdmissionWebhookCsrYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x44\x8d\x4b\x4e\xc4\x30\x10\x44\xf7\x3e\x45\x5f\x20\x41\xb3\x43\xde\x72\x03\x90\xd8\x77\xec\xc2\x69\x19\x7f\x70\xb7\x83\xe6\xf6\x28\x13\xa4\xd9\x95\xaa\x9e\x5e\x71\x97\x4f\x0c\x95\x56\x3d\x05\x0c\x93\x2f\x09\x6c\xd0\x35\xbf\xea\x2a\xed\xe5\xb8\x6d\x30\xbe\xb9\x2c\x35\x7a\x7a\x7b\x12\x1f\x92\xaa\xd4\xf4\x8e\x9f\x09\x35\x57\x60\x1c\xd9\xd8\x3b\xa2\xca\x05\x9e\x8e\x1e\x16\x8e\x45\xf4\x94\x2f\xbf\xd8\xf6\xd6\xf2\x9a\xe7\x86\x45\xef\x6a\x28\x4e\x3b\xc2\xc9\xa7\xd1\x66\xd7\x33\x2d\x74\x4d\x9e\xa7\xed\xa8\xf6\x78\x8a\x8e\x68\x2a\x27\xfc\x23\x51\x92\x18\x7f\x93\x4a\xaa\x6c\x73\xe0\xd1\x66\xdc\x09\x35\x48\xdf\x31\x0a\xaa\x5d\x36\x8c\x03\x83\x4e\x9b\xfb\x0b\x00\x00\xff\xff\xf1\x7d\x42\x97\xea\x00\x00\x00") + +func vpcAdmissionWebhookCsrYamlBytes() ([]byte, error) { + return bindataRead( + _vpcAdmissionWebhookCsrYaml, + "vpc-admission-webhook-csr.yaml", + ) +} + +func vpcAdmissionWebhookCsrYaml() (*asset, error) { + bytes, err := vpcAdmissionWebhookCsrYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "vpc-admission-webhook-csr.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x56, 0xf4, 0x54, 0x57, 0xf8, 0xbd, 0x8a, 0x1a, 0x67, 0xb2, 0x29, 0x18, 0xe2, 0x44, 0x8a, 0xc2, 0x7f, 0x44, 0x26, 0x4c, 0x52, 0xe0, 0x70, 0xe0, 0xc8, 0x5a, 0x74, 0x76, 0x2, 0xcd, 0x3e, 0xa}} + return a, nil +} + +var _vpcAdmissionWebhookDepYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x54\xc1\x6e\xdb\x3a\x10\xbc\xfb\x2b\xf6\x92\x97\x93\xa5\x97\xa0\xc8\x81\x40\x0a\x04\x4d\x0a\x04\x6d\x93\xa0\x29\x7a\x5f\x53\x13\x99\x10\x45\xb2\xe4\x4a\x8e\xfe\xbe\xa0\xad\xa4\xb6\x1c\x37\x39\x14\xad\x0e\x3e\xec\xec\xcc\x2c\x87\x5e\x72\x30\xdf\x11\x93\xf1\x4e\x11\x87\x90\xca\xfe\x64\xd6\x18\x57\x29\xba\x44\xb0\x7e\x68\xe1\x64\xd6\x42\xb8\x62\x61\x35\x23\x72\xdc\x42\x51\x1f\xf4\x9c\xab\xd6\xa4\xcc\x9c\xaf\xb0\x58\x7a\xdf\x8c\x68\x0a\xac\xa1\xa8\xe9\x16\x98\xa7\x21\x09\xda\x19\x91\xe5\x05\x6c\xca\x02\x94\x7d\x0e\x29\xa4\x00\x9d\x9b\x22\x82\x35\x9a\x93\xa2\x93\x19\x51\x92\xc8\x82\x7a\xd8\xd0\x65\x08\x50\xf4\x15\x3a\x82\x05\x19\x86\x85\x16\x1f\x37\x70\xcb\xa2\x97\x9f\xb7\xec\x7e\x6b\x48\x24\x68\x83\x65\xc1\xc8\xde\x3a\x6a\xfe\xec\x8e\xd0\x2b\x52\x44\x4f\xf3\xe7\x4f\x7b\x27\x6c\x1c\xe2\x16\x7d\xfe\x4a\x7e\xcf\x36\xb1\xde\x62\x6d\x98\x73\xb1\xe9\x03\xa2\x7c\x34\x16\xe7\x25\x44\x97\x23\xaf\xd4\x88\x92\xd6\xbf\x45\x58\xa7\x3d\xa5\x7d\xc2\x70\x88\xd5\x60\x78\x89\x74\x7b\xbf\x8e\xf0\x7e\x8c\xf6\xb6\x47\x8c\xa6\xc2\xf9\xca\xb8\xca\xaf\xd2\xb4\x9d\x6d\xf2\xd6\xd7\xe2\x93\x54\x88\x71\x0a\xf7\xe7\xef\x26\xa5\xd3\xf7\xff\x9d\x6c\x95\x4c\xcb\x35\x14\x1d\x1f\xa5\xa2\x6a\x62\x01\x1d\x8b\xa3\x54\x1c\xa5\x12\x4d\x2a\x5f\x0c\x4b\xf5\xff\x17\xa7\xc5\xd9\xf1\x54\xe4\xae\xb3\xf6\xce\x5b\xa3\x07\x45\x17\x76\xc5\xc3\xf6\xac\xbd\xb7\x5d\x8b\x2f\xbe\x73\xb2\x17\xef\xe6\x62\x46\xf5\xf9\x3a\x9c\x9d\x0e\xa2\x36\xf3\xee\x58\x96\x8a\xf6\x83\x9c\xf4\x46\x70\x75\xeb\xec\xa0\x48\x62\x87\x11\x5c\xfa\x24\x37\x90\x95\x8f\xcd\x4e\x9d\x1f\x1e\x8c\x33\x32\xfc\x1a\xc9\xf9\x0a\x17\x7b\xd5\x2c\xfb\xa3\x33\x11\xd5\x65\x17\x8d\xab\xef\xf5\x12\x55\x67\x8d\xab\xaf\x6b\xe7\x9f\xcb\x57\x8f\xd0\x9d\xe4\x95\xde\x19\x2a\x6b\x3e\xdd\xe7\x37\xc4\x76\x2f\x81\xf5\xe6\x5c\x3d\x86\x88\x75\xd2\x13\x3c\x77\x34\x18\x14\x2d\x20\x5c\xe4\xed\x8e\x0e\x82\x54\x18\x5f\xfa\xe9\xf1\x89\x7c\x40\xe4\xbc\x94\x74\xed\xf6\xc0\x9e\x6d\x87\x3d\xfd\xec\x60\x8d\xeb\x1e\xdf\xec\xcb\x51\x2f\xff\x94\x33\xb7\xd5\xd9\xf4\x5f\xfa\xc6\x44\xfe\x41\x18\x7f\x27\x87\xcd\xbe\xbc\xf0\x7e\x1d\x5a\x93\x94\x5f\x65\xd9\x95\xdd\xd4\x6e\x0e\xbf\x7b\xa3\xca\xcf\x00\x00\x00\xff\xff\x52\xd4\x1b\x58\x8b\x06\x00\x00") + +func vpcAdmissionWebhookDepYamlBytes() ([]byte, error) { + return bindataRead( + _vpcAdmissionWebhookDepYaml, + "vpc-admission-webhook-dep.yaml", + ) +} + +func vpcAdmissionWebhookDepYaml() (*asset, error) { + bytes, err := vpcAdmissionWebhookDepYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "vpc-admission-webhook-dep.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x3b, 0x48, 0x71, 0x63, 0xbb, 0x59, 0xcc, 0xd5, 0x1b, 0x30, 0xc7, 0x1c, 0x5e, 0xf2, 0x65, 0x72, 0xff, 0xa9, 0x4d, 0x14, 0x6c, 0xce, 0x49, 0xa8, 0x6f, 0x4d, 0x8e, 0x19, 0xf7, 0xee, 0xfa, 0x8c}} + return a, nil +} + +var _vpcAdmissionWebhookYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x84\x8c\x31\xae\xc2\x40\x0c\x05\xfb\x3d\x85\x2f\xe0\xe2\xeb\xa7\xf2\x29\x90\x90\xe8\x9d\xcd\x13\xac\x92\xcd\x5a\x6b\x13\xc4\xed\x51\x22\x0a\x1a\x44\x67\xf9\xcd\x0c\x33\x27\xb5\x72\x41\xf7\xd2\x56\xa1\xed\x2f\xcd\x65\x9d\x84\xce\xe8\x5b\xc9\x48\x15\xa1\x93\x86\x4a\x22\x5a\xb5\x42\x68\xb3\xcc\x3a\xd5\xe2\xbb\xc1\x0f\x8c\xb7\xd6\xe6\xf7\xea\xa6\x19\x42\xf3\x7d\x04\xfb\xd3\x03\x35\x11\x2d\x3a\x62\xf1\x3d\x40\xa4\x66\xdf\x0a\x6e\xc8\x3b\x64\xad\xc7\x41\xf3\x71\x0a\x0d\xc3\xff\xe1\x86\xf6\x2b\xe2\xf4\xf1\x73\x2c\xc8\xd1\xfa\xcf\xf6\x2b\x00\x00\xff\xff\xbc\xa7\x78\x3e\xe7\x00\x00\x00") + +func vpcAdmissionWebhookYamlBytes() ([]byte, error) { + return bindataRead( + _vpcAdmissionWebhookYaml, + "vpc-admission-webhook.yaml", + ) +} + +func vpcAdmissionWebhookYaml() (*asset, error) { + bytes, err := vpcAdmissionWebhookYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "vpc-admission-webhook.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xc0, 0x14, 0x3e, 0x68, 0x1c, 0x57, 0x26, 0x7e, 0x3b, 0xab, 0xf1, 0x55, 0x21, 0x61, 0xc3, 0xe1, 0xaf, 0x46, 0xb6, 0xf7, 0xdd, 0x11, 0x29, 0x41, 0x64, 0x57, 0xc8, 0xd4, 0xea, 0x97, 0xba, 0xff}} + return a, nil +} + +var _vpcResourceControllerDepYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x94\x4f\x6f\xdb\x3c\x0c\xc6\xef\xfe\x14\xbc\x14\x3d\xd9\x49\xfa\xbe\x6b\x31\x01\x3b\x14\x6b\xb1\x15\x18\x86\x00\x0d\x76\x67\x24\x26\x16\x2c\x4b\x1a\x45\x3b\xf1\x3e\xfd\xa0\xb5\xf9\xe3\xa6\x5b\x8b\x61\xc3\x7c\x32\x9e\x9f\xc4\x87\x24\x44\x96\x65\x59\x60\xb4\x5f\x88\x93\x0d\x5e\x01\xc6\x98\x26\xfd\xac\x68\xac\x37\x0a\x6e\x28\xba\x30\xb4\xe4\xa5\x68\x49\xd0\xa0\xa0\x2a\x00\x3c\xb6\xa4\xa0\x8f\xba\x64\x4a\xa1\x63\x4d\xa5\x0e\x5e\x38\x38\x47\xfc\xc8\x53\x44\x4d\x0a\x9a\x6e\x49\x65\x1a\x92\x50\x5b\xa4\x48\x3a\x5f\x67\x8a\xce\x6a\x4c\x0a\x66\x05\x40\x22\x47\x5a\x02\x67\x02\xd0\xa2\xe8\xfa\x13\x2e\xc9\xa5\x07\x01\x72\x4a\xbf\x32\xcb\x9f\x58\x62\x05\x4b\xd4\x0d\x79\xb3\xd3\x18\x75\xa3\x20\x09\x2e\x1d\x15\x00\x42\x6d\x74\x28\xf4\xe8\x73\x54\x4e\xfe\xdc\xc8\xf2\x55\xa6\xcf\xdb\x9e\x1a\x03\xec\x0a\xff\xf1\x4f\xdc\x5b\x4d\xd7\x5a\x87\xce\xcb\x4b\x1e\x59\x40\xeb\x89\xf7\xa9\x95\xa0\x43\xdb\xa2\x37\x87\x5c\x4b\x98\xbc\x94\x29\xf2\x3a\x1d\x5f\x28\x93\x18\x62\x96\x9a\x29\xd5\xc1\x99\x77\xd6\xaf\xc2\x9e\xdb\x16\xd7\xa4\xe0\xfc\x2c\x55\xa6\xe1\x8a\x34\x57\x67\xa9\x3a\x4b\x13\x6a\xd2\x64\x63\xbd\x09\x9b\x54\xfe\xc4\x52\xf5\xd3\xea\xa2\xba\x3c\x1f\x07\x9b\x77\xce\xcd\x83\xb3\x7a\x50\x70\xed\x36\x38\xa4\x3d\x77\xb6\x27\x4f\x29\xcd\x39\x2c\xe9\x90\x23\xc0\x0a\xad\xeb\x98\x16\xbb\x1c\x15\xbc\x39\xa2\xb5\x48\xfc\x40\x72\x7c\x01\xa0\x0e\x49\x14\xcc\x2e\xae\xaa\x69\x35\xad\x66\x23\x16\x51\x6a\x05\x93\x9a\xd0\x49\xfd\x6d\x8c\x02\x8b\x82\xcb\xd9\xd5\xd5\xdb\x91\x9e\x74\x4d\xf9\xa5\x7f\x5c\x2c\xe6\x47\xc0\x7a\x2b\x16\xdd\x0d\x39\x1c\xee\x49\x07\x6f\x92\x82\xff\xa6\x47\x27\x22\xb1\x0d\xe6\x79\x26\xb6\xa5\xd0\xc9\x1e\x1e\x8a\x7a\x69\xac\x76\x0f\x48\x77\x6c\x65\x78\x1f\xbc\xd0\x76\xd4\x80\xc8\xb6\xb7\x8e\xd6\x64\x14\x08\x77\x54\x1c\xba\xf2\x99\x64\x13\xb8\x19\xe9\xb8\x5a\xe5\x52\x86\x43\x08\x1f\x0c\x5d\x9f\xa8\x79\x62\xbf\x76\x96\xc9\xdc\x74\x6c\xfd\xfa\x5e\xd7\x64\x3a\x67\xfd\xfa\x6e\xed\xc3\x5e\xbe\xdd\x92\xee\x24\x6f\x91\x51\x13\x73\xcc\xfb\xc7\x19\x5f\x10\xb7\x69\x8c\xcb\x87\x91\xbf\xdd\x46\xa6\x94\x77\xd0\x13\x9e\x4f\x34\x34\x28\x58\x92\x60\x95\xd7\x09\x7b\x12\x4a\x95\x0d\x93\x90\x9e\x1c\x05\x08\x91\x18\xf3\x36\x81\x3b\x7f\x02\x7b\x74\x1d\x9d\xc4\xcf\x0e\xce\xfa\x6e\xfb\x6a\x5f\x64\x5d\xff\x29\x67\x6c\xcd\xe5\xff\xbf\xd7\x91\x7f\xd0\x8c\xbf\xdd\x87\xef\x01\x00\x00\xff\xff\xec\x16\x91\xef\x8f\x06\x00\x00") + +func vpcResourceControllerDepYamlBytes() ([]byte, error) { + return bindataRead( + _vpcResourceControllerDepYaml, + "vpc-resource-controller-dep.yaml", + ) +} + +func vpcResourceControllerDepYaml() (*asset, error) { + bytes, err := vpcResourceControllerDepYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "vpc-resource-controller-dep.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x0, 0xbf, 0x11, 0xf3, 0x5f, 0x76, 0xa3, 0xb2, 0x2e, 0x2f, 0x80, 0xa7, 0x86, 0xae, 0x3f, 0xb7, 0x7e, 0x76, 0xed, 0x68, 0xfe, 0x23, 0x5c, 0x76, 0xb9, 0xd0, 0xd8, 0xed, 0xf9, 0x9d, 0x96, 0x93}} + return a, nil +} + +var _vpcResourceControllerYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xa4\x90\x31\x4f\x33\x31\x0c\x86\xf7\xfc\x8a\xa8\x7b\x5a\x7d\xdb\xa7\xdb\x80\x81\xbd\x48\xec\x3e\x9f\xdb\x9a\xe6\xe2\xc8\x76\x0e\xc1\xaf\x47\x77\xd7\xc2\x80\x44\x85\x98\xf2\xc4\xf6\x6b\x4b\x4f\x4a\x29\x40\xe5\x67\x52\x63\x29\x5d\xd4\x1e\x70\x0b\xcd\x4f\xa2\xfc\x0e\xce\x52\xb6\xe7\xff\xb6\x65\xd9\x4d\xff\xc2\x99\xcb\xd0\xc5\x87\xdc\xcc\x49\xf7\x92\x29\x8c\xe4\x30\x80\x43\x17\x62\x2c\x30\x52\x17\xa7\x8a\x49\xc9\xa4\x29\x52\x42\x29\xae\x92\x33\x69\xd0\x96\xc9\xba\x90\x22\x54\x7e\x54\x69\xd5\xe6\x4c\x8a\x9b\x4d\x88\xf1\x1a\xb8\xd4\x8a\x0c\x64\x5f\xb4\x33\x07\x6f\x6b\xa1\xca\xb0\x02\x4a\x39\xf0\x71\x84\x3a\x7f\x27\xd2\xfe\x92\x6d\x75\x00\xa7\x05\x8f\xe4\xcb\x9b\xd9\x56\x78\x05\xc7\xd3\xba\xe6\x93\x50\x69\x9e\xff\x9b\x87\x7b\x2e\x03\x97\xe3\x6f\x74\x48\xa6\x3d\x1d\xe6\xc1\xab\x90\x1f\x8e\x86\x18\xbf\xbb\xbf\x75\xc2\x5a\xff\x42\xe8\x8b\xf4\x35\xfd\x44\x3a\x31\xd2\x1d\xa2\xb4\xe2\x37\x17\xac\x7d\xab\x80\xd4\xc5\x73\xeb\x29\xd9\x9b\x39\x8d\xe1\x23\x00\x00\xff\xff\x83\x2e\x8d\x64\x35\x02\x00\x00") + +func vpcResourceControllerYamlBytes() ([]byte, error) { + return bindataRead( + _vpcResourceControllerYaml, + "vpc-resource-controller.yaml", + ) +} + +func vpcResourceControllerYaml() (*asset, error) { + bytes, err := vpcResourceControllerYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "vpc-resource-controller.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xa8, 0xff, 0xd2, 0xc, 0x46, 0xfb, 0xe4, 0x4e, 0xb, 0x26, 0x4a, 0xa0, 0x7d, 0x73, 0x5e, 0xaf, 0x6d, 0xab, 0xfd, 0xe5, 0xc2, 0x4f, 0xed, 0xae, 0xb6, 0xd7, 0x17, 0x38, 0x68, 0xb0, 0xe8, 0x2d}} return a, nil } @@ -243,10 +348,15 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ - "efa-device-plugin.yaml": efaDevicePluginYaml, - "neuron-device-plugin.yaml": neuronDevicePluginYaml, - "nvidia-device-plugin.yaml": nvidiaDevicePluginYaml, - "vpc-controller-metadata.yaml": vpcControllerMetadataYaml, + "efa-device-plugin.yaml": efaDevicePluginYaml, + "neuron-device-plugin.yaml": neuronDevicePluginYaml, + "nvidia-device-plugin.yaml": nvidiaDevicePluginYaml, + "vpc-admission-webhook-config.yaml": vpcAdmissionWebhookConfigYaml, + "vpc-admission-webhook-csr.yaml": vpcAdmissionWebhookCsrYaml, + "vpc-admission-webhook-dep.yaml": vpcAdmissionWebhookDepYaml, + "vpc-admission-webhook.yaml": vpcAdmissionWebhookYaml, + "vpc-resource-controller-dep.yaml": vpcResourceControllerDepYaml, + "vpc-resource-controller.yaml": vpcResourceControllerYaml, } // AssetDebug is true if the assets were built with the debug flag enabled. @@ -296,7 +406,12 @@ var _bintree = &bintree{nil, map[string]*bintree{ "efa-device-plugin.yaml": {efaDevicePluginYaml, map[string]*bintree{}}, "neuron-device-plugin.yaml": {neuronDevicePluginYaml, map[string]*bintree{}}, "nvidia-device-plugin.yaml": {nvidiaDevicePluginYaml, map[string]*bintree{}}, - "vpc-controller-metadata.yaml": {vpcControllerMetadataYaml, map[string]*bintree{}}, + "vpc-admission-webhook-config.yaml": {vpcAdmissionWebhookConfigYaml, map[string]*bintree{}}, + "vpc-admission-webhook-csr.yaml": {vpcAdmissionWebhookCsrYaml, map[string]*bintree{}}, + "vpc-admission-webhook-dep.yaml": {vpcAdmissionWebhookDepYaml, map[string]*bintree{}}, + "vpc-admission-webhook.yaml": {vpcAdmissionWebhookYaml, map[string]*bintree{}}, + "vpc-resource-controller-dep.yaml": {vpcResourceControllerDepYaml, map[string]*bintree{}}, + "vpc-resource-controller.yaml": {vpcResourceControllerYaml, map[string]*bintree{}}, }} // RestoreAsset restores an asset under the given directory. diff --git a/pkg/addons/assets/vpc-admission-webhook-config.yaml b/pkg/addons/assets/vpc-admission-webhook-config.yaml new file mode 100644 index 0000000000..652a257203 --- /dev/null +++ b/pkg/addons/assets/vpc-admission-webhook-config.yaml @@ -0,0 +1,20 @@ +apiVersion: admissionregistration.k8s.io/v1beta1 +kind: MutatingWebhookConfiguration +metadata: + name: vpc-admission-webhook-cfg + namespace: kube-system + labels: + app: vpc-admission-webhook +webhooks: + - name: vpc-admission-webhook.amazonaws.com + clientConfig: + service: + name: vpc-admission-webhook + namespace: kube-system + path: "/mutate" + rules: + - operations: [ "CREATE" ] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] + failurePolicy: Ignore diff --git a/pkg/addons/assets/vpc-admission-webhook-csr.yaml b/pkg/addons/assets/vpc-admission-webhook-csr.yaml new file mode 100644 index 0000000000..09e4f77a6d --- /dev/null +++ b/pkg/addons/assets/vpc-admission-webhook-csr.yaml @@ -0,0 +1,11 @@ +apiVersion: certificates.k8s.io/v1beta1 +kind: CertificateSigningRequest +metadata: + name: vpc-admission-webhook.kube-system +spec: + groups: + - system:authenticated + usages: + - digital signature + - key encipherment + - server auth diff --git a/pkg/addons/assets/vpc-admission-webhook-dep.yaml b/pkg/addons/assets/vpc-admission-webhook-dep.yaml new file mode 100644 index 0000000000..2c08acab80 --- /dev/null +++ b/pkg/addons/assets/vpc-admission-webhook-dep.yaml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: vpc-admission-webhook + namespace: kube-system + labels: + app: vpc-admission-webhook +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: vpc-admission-webhook + template: + metadata: + labels: + app: vpc-admission-webhook + spec: + containers: + - name: vpc-admission-webhook + args: + - -tlsCertFile=/etc/webhook/certs/cert.pem + - -tlsKeyFile=/etc/webhook/certs/key.pem + - -OSLabelSelectorOverride=windows + - -alsologtostderr + - -v=4 + - 2>&1 + image: '%s.dkr.ecr.%s.%s/eks/vpc-admission-webhook:v0.2.6' + imagePullPolicy: Always + volumeMounts: + - name: webhook-certs + mountPath: /etc/webhook/certs + readOnly: true + hostNetwork: true + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: beta.kubernetes.io/os + operator: In + values: + - linux + - key: beta.kubernetes.io/arch + operator: In + values: + - amd64 + - matchExpressions: + - key: kubernetes.io/os + operator: In + values: + - linux + - key: kubernetes.io/arch + operator: In + values: + - amd64 + volumes: + - name: webhook-certs + secret: + secretName: vpc-admission-webhook-certs diff --git a/pkg/addons/assets/vpc-admission-webhook.yaml b/pkg/addons/assets/vpc-admission-webhook.yaml new file mode 100644 index 0000000000..b8f351fb12 --- /dev/null +++ b/pkg/addons/assets/vpc-admission-webhook.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: vpc-admission-webhook + namespace: kube-system + labels: + app: vpc-admission-webhook +spec: + ports: + - port: 443 + targetPort: 443 + selector: + app: vpc-admission-webhook diff --git a/pkg/addons/assets/vpc-controller-metadata.yaml b/pkg/addons/assets/vpc-controller-metadata.yaml deleted file mode 100644 index 93bfd78257..0000000000 --- a/pkg/addons/assets/vpc-controller-metadata.yaml +++ /dev/null @@ -1,52 +0,0 @@ ---- -apiVersion: v1 -kind: Service -metadata: - name: vpc-admission-webhook - namespace: kube-system - ---- -apiVersion: admissionregistration.k8s.io/v1beta1 -kind: MutatingWebhookConfiguration -metadata: - name: vpc-admission-webhook-cfg - namespace: kube-system - ---- -apiVersion: certificates.k8s.io/v1beta1 -kind: CertificateSigningRequest -metadata: - name: vpc-admission-webhook.kube-system - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: vpc-admission-webhook - namespace: kube-system - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: vpc-resource-controller - ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: vpc-resource-controller - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: vpc-resource-controller - namespace: kube-system - ---- -apiVersion: v1 -kind: Secret -metadata: - name: vpc-admission-webhook-certs - namespace: kube-system diff --git a/pkg/addons/assets/vpc-resource-controller-dep.yaml b/pkg/addons/assets/vpc-resource-controller-dep.yaml new file mode 100644 index 0000000000..3966657e1b --- /dev/null +++ b/pkg/addons/assets/vpc-resource-controller-dep.yaml @@ -0,0 +1,64 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: vpc-resource-controller + namespace: kube-system +spec: + replicas: 1 + selector: + matchLabels: + app: vpc-resource-controller + tier: backend + track: stable + template: + metadata: + labels: + app: vpc-resource-controller + tier: backend + track: stable + spec: + serviceAccount: vpc-resource-controller + containers: + - command: + - /vpc-resource-controller + args: + - -stderrthreshold=info + image: '%s.dkr.ecr.%s.%s/eks/windows-vpc-resource-controller:v0.2.6' + imagePullPolicy: Always + livenessProbe: + failureThreshold: 5 + httpGet: + host: 127.0.0.1 + path: /healthz + port: 61779 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 5 + name: vpc-resource-controller + securityContext: + privileged: true + hostNetwork: true + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: beta.kubernetes.io/os + operator: In + values: + - linux + - key: beta.kubernetes.io/arch + operator: In + values: + - amd64 + - matchExpressions: + - key: kubernetes.io/os + operator: In + values: + - linux + - key: kubernetes.io/arch + operator: In + values: + - amd64 diff --git a/pkg/addons/assets/vpc-resource-controller.yaml b/pkg/addons/assets/vpc-resource-controller.yaml new file mode 100644 index 0000000000..91b1c9f085 --- /dev/null +++ b/pkg/addons/assets/vpc-resource-controller.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: vpc-resource-controller +rules: +- apiGroups: + - "" + resources: + - nodes + - nodes/status + - pods + - configmaps + verbs: + - update + - get + - list + - watch + - patch + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: vpc-resource-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: vpc-resource-controller +subjects: +- kind: ServiceAccount + name: vpc-resource-controller + namespace: kube-system diff --git a/pkg/addons/device_plugin.go b/pkg/addons/device_plugin.go index 70683fe99d..694668ad8b 100644 --- a/pkg/addons/device_plugin.go +++ b/pkg/addons/device_plugin.go @@ -71,15 +71,6 @@ type DevicePlugin interface { Deploy() error } -type typeAssertionError struct { - expected interface{} - got interface{} -} - -func (t *typeAssertionError) Error() string { - return fmt.Sprintf("expected type to be %T; got %T", t.expected, t.got) -} - func applyDevicePlugin(dp DevicePlugin) error { list, err := kubernetes.NewList(dp.Manifest()) if err != nil { diff --git a/pkg/addons/vpc_controller.go b/pkg/addons/vpc_controller.go index e6f9e059a3..4af6c3ab57 100644 --- a/pkg/addons/vpc_controller.go +++ b/pkg/addons/vpc_controller.go @@ -1,56 +1,410 @@ package addons import ( + "context" + "fmt" + "time" + + "github.com/cloudflare/cfssl/csr" "github.com/kris-nova/logger" "github.com/pkg/errors" + api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5" + "github.com/weaveworks/eksctl/pkg/assetutil" "github.com/weaveworks/eksctl/pkg/kubernetes" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/kubernetes/typed/certificates/v1beta1" + admv1beta1 "k8s.io/api/admissionregistration/v1beta1" + appsv1 "k8s.io/api/apps/v1" + certsv1beta1 "k8s.io/api/certificates/v1beta1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) const ( - VPCControllerInfoMessage = "you no longer need to install the VPC resource controller on Linux worker nodes to run " + - "Windows workloads in EKS clusters. You can enable Windows IP address management on the EKS control plane via " + - "a ConfigMap setting (see https://todo.com for details). eksctl will automatically patch the ConfigMap to enable " + - "Windows IP address management when a Windows nodegroup is created. For existing clusters, you can enable it manually " + - "and run `eksctl utils install-vpc-controllers` with the --delete flag to remove the worker node installation of the VPC resource controller" + vpcControllerNamespace = metav1.NamespaceSystem + vpcControllerName = "vpc-resource-controller" + webhookServiceName = "vpc-admission-webhook" + + certWaitTimeout = 45 * time.Second ) -// VPCController deletes an existing installation of VPC controller from worker nodes. +// NewVPCController creates a new VPCController +func NewVPCController(rawClient kubernetes.RawClientInterface, irsa IRSAHelper, clusterStatus *api.ClusterStatus, region string, planMode bool) *VPCController { + return &VPCController{ + rawClient: rawClient, + irsa: irsa, + clusterStatus: clusterStatus, + region: region, + planMode: planMode, + } +} + +// A VPCController deploys Windows VPC controller to a cluster type VPCController struct { - RawClient *kubernetes.RawClient - PlanMode bool + rawClient kubernetes.RawClientInterface + irsa IRSAHelper + clusterStatus *api.ClusterStatus + region string + planMode bool +} + +// Deploy deploys VPC controller to the specified cluster +func (v *VPCController) Deploy() (err error) { + defer func() { + if r := recover(); r != nil { + if ae, ok := r.(*assetutil.Error); ok { + err = ae + } else { + panic(r) + } + } + }() + + if err := v.deployVPCResourceController(); err != nil { + return err + } + + if err := v.generateCert(); err != nil { + return err + } + + return v.deployVPCWebhook() +} + +type typeAssertionError struct { + expected interface{} + got interface{} } -// Delete deletes the resources for VPC controller. -func (v *VPCController) Delete() error { - vpcControllerMetadata, err := vpcControllerMetadataYamlBytes() +func (t *typeAssertionError) Error() string { + return fmt.Sprintf("expected type to be %T; got %T", t.expected, t.got) +} + +func (v *VPCController) generateCert() error { + var ( + csrName = fmt.Sprintf("%s.%s", webhookServiceName, vpcControllerNamespace) + csrClientSet = v.rawClient.ClientSet().CertificatesV1beta1().CertificateSigningRequests() + ) + + hasApprovedCert, err := v.hasApprovedCert() + if err != nil { + return err + } + if hasApprovedCert { + // Delete existing CSR if the secret is missing + _, err := v.rawClient.ClientSet().CoreV1().Secrets(vpcControllerNamespace).Get(context.TODO(), "vpc-admission-webhook-certs", metav1.GetOptions{}) + if err != nil { + if !apierrors.IsNotFound(err) { + return err + } + if err := csrClientSet.Delete(context.TODO(), csrName, metav1.DeleteOptions{}); err != nil { + return err + } + } + return nil + } + + csrPEM, privateKey, err := generateCertReq(webhookServiceName, vpcControllerNamespace) if err != nil { - return errors.Wrap(err, "unexpected error loading manifests") + return errors.Wrap(err, "generating CSR") } - list, err := kubernetes.NewList(vpcControllerMetadata) + + manifest := assetutil.MustLoad(vpcAdmissionWebhookCsrYamlBytes) + rawExtension, err := kubernetes.NewRawExtension(manifest) if err != nil { - return errors.Wrap(err, "unexpected error parsing manifests") + return err + } + + certificateSigningRequest, ok := rawExtension.Object.(*certsv1beta1.CertificateSigningRequest) + if !ok { + return &typeAssertionError{&certsv1beta1.CertificateSigningRequest{}, rawExtension.Object} + } + + certificateSigningRequest.Spec.Request = csrPEM + certificateSigningRequest.Name = csrName + + if err := v.applyRawResource(certificateSigningRequest); err != nil { + return errors.Wrap(err, "creating CertificateSigningRequest") } + + certificateSigningRequest.Status.Conditions = []certsv1beta1.CertificateSigningRequestCondition{ + { + Type: certsv1beta1.CertificateApproved, + LastUpdateTime: metav1.NewTime(time.Now()), + Message: "This CSR was approved by eksctl", + Reason: "eksctl-approve", + }, + } + + if _, err := csrClientSet.UpdateApproval(context.TODO(), certificateSigningRequest, metav1.UpdateOptions{}); err != nil { + return errors.Wrap(err, "updating approval") + } + + logger.Info("waiting for certificate to be available") + + cert, err := watchCSRApproval(csrClientSet, csrName, certWaitTimeout) + if err != nil { + return err + } + + return v.createCertSecrets(privateKey, cert) +} + +func watchCSRApproval(csrClientSet v1beta1.CertificateSigningRequestInterface, csrName string, timeout time.Duration) ([]byte, error) { + watcher, err := csrClientSet.Watch(context.TODO(), metav1.ListOptions{ + FieldSelector: fmt.Sprintf("metadata.name=%s", csrName), + }) + + if err != nil { + return nil, err + } + + defer watcher.Stop() + + timer := time.NewTimer(timeout) + defer timer.Stop() + + for { + select { + case event, ok := <-watcher.ResultChan(): + if !ok { + return nil, errors.New("failed waiting for certificate: unexpected close of ResultChan") + } + switch event.Type { + case watch.Added, watch.Modified: + req := event.Object.(*certsv1beta1.CertificateSigningRequest) + if cert := req.Status.Certificate; cert != nil { + return cert, nil + } + logger.Warning("certificate not yet available (event: %s)", event.Type) + } + case <-timer.C: + return nil, fmt.Errorf("timed out (after %v) waiting for certificate", timeout) + } + + } +} + +func (v *VPCController) createCertSecrets(key, cert []byte) error { + secret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "vpc-admission-webhook-certs", + Namespace: vpcControllerNamespace, + }, + Data: map[string][]byte{ + "key.pem": key, + "cert.pem": cert, + }, + } + + err := v.applyRawResource(secret) + if err != nil { + return errors.Wrap(err, "error creating secret") + } + return err +} + +func makePolicyDocument() map[string]interface{} { + return map[string]interface{}{ + "Version": "2012-10-17", + "Statement": []map[string]interface{}{ + { + "Effect": "Allow", + "Action": []string{ + "ec2:AssignPrivateIpAddresses", + "ec2:DescribeInstances", + "ec2:DescribeNetworkInterfaces", + "ec2:UnassignPrivateIpAddresses", + "ec2:DescribeRouteTables", + "ec2:DescribeSubnets", + }, + "Resource": "*", + }, + }, + } +} + +func (v *VPCController) deployVPCResourceController() error { + irsaEnabled, err := v.irsa.IsSupported() + if err != nil { + return err + } + if irsaEnabled { + sa := &api.ClusterIAMServiceAccount{ + ClusterIAMMeta: api.ClusterIAMMeta{ + Name: vpcControllerName, + Namespace: vpcControllerNamespace, + }, + AttachPolicy: makePolicyDocument(), + } + if err := v.irsa.CreateOrUpdate(sa); err != nil { + return errors.Wrap(err, "error enabling IRSA") + } + } else { + // If an OIDC provider isn't associated with the cluster, the VPC controller relies on the managed policy + // attached to the node role for the AWS VPC CNI plugin. + sa := kubernetes.NewServiceAccount(metav1.ObjectMeta{ + Name: vpcControllerName, + Namespace: vpcControllerNamespace, + }) + if err := v.applyRawResource(sa); err != nil { + return err + } + } + if err := v.applyResources(assetutil.MustLoad(vpcResourceControllerYamlBytes)); err != nil { + return err + } + + return v.applyDeployment(assetutil.MustLoad(vpcResourceControllerDepYamlBytes)) +} + +func (v *VPCController) deployVPCWebhook() error { + if err := v.applyResources(assetutil.MustLoad(vpcAdmissionWebhookYamlBytes)); err != nil { + return err + } + if err := v.applyDeployment(assetutil.MustLoad(vpcAdmissionWebhookDepYamlBytes)); err != nil { + return err + } + + manifest := assetutil.MustLoad(vpcAdmissionWebhookConfigYamlBytes) + rawExtension, err := kubernetes.NewRawExtension(manifest) + if err != nil { + return err + } + + mutatingWebhook, ok := rawExtension.Object.(*admv1beta1.MutatingWebhookConfiguration) + if !ok { + return &typeAssertionError{&admv1beta1.MutatingWebhookConfiguration{}, rawExtension.Object} + } + + mutatingWebhook.Webhooks[0].ClientConfig.CABundle = v.clusterStatus.CertificateAuthorityData + return v.applyRawResource(rawExtension.Object) +} + +func (v *VPCController) hasApprovedCert() (bool, error) { + csrClientSet := v.rawClient.ClientSet().CertificatesV1beta1().CertificateSigningRequests() + request, err := csrClientSet.Get(context.TODO(), fmt.Sprintf("%s.%s", webhookServiceName, vpcControllerNamespace), metav1.GetOptions{}) + if err != nil { + if !apierrors.IsNotFound(err) { + return false, err + } + return false, nil + } + + conditions := request.Status.Conditions + switch len(conditions) { + case 1: + if conditions[0].Type == certsv1beta1.CertificateApproved { + return true, nil + } + return false, fmt.Errorf("expected certificate to be approved; got %q", conditions[0].Type) + + case 0: + return false, nil + default: + return false, fmt.Errorf("unexpected number of request conditions: %d", len(conditions)) + } +} + +func (v *VPCController) applyResources(manifests []byte) error { + list, err := kubernetes.NewList(manifests) + if err != nil { + return err + } + for _, item := range list.Items { - if err := v.deleteResource(item.Object); err != nil { + if err := v.applyRawResource(item.Object); err != nil { return err } } return nil } -func (v *VPCController) deleteResource(o runtime.Object) error { - r, err := v.RawClient.NewRawResource(o) +func (v *VPCController) applyDeployment(manifests []byte) error { + rawExtension, err := kubernetes.NewRawExtension(manifests) if err != nil { - return errors.Wrap(err, "unexpected error creating raw resource") + return err + } + + deployment, ok := rawExtension.Object.(*appsv1.Deployment) + if !ok { + return &typeAssertionError{&appsv1.Deployment{}, rawExtension.Object} + } + if err := UseRegionalImage(&deployment.Spec.Template, v.region); err != nil { + return err } - msg, err := r.DeleteSync(v.PlanMode) + return v.applyRawResource(rawExtension.Object) +} + +func (v *VPCController) applyRawResource(object runtime.Object) error { + rawResource, err := v.rawClient.NewRawResource(object) if err != nil { - return errors.Wrapf(err, "error deleting resource %q", r.Info.String()) + return err + } + + switch newObject := object.(type) { + case *corev1.Service: + r, found, err := rawResource.Get() + if err != nil { + return err + } + if found { + service, ok := r.(*corev1.Service) + if !ok { + return &typeAssertionError{&corev1.Service{}, r} + } + newObject.Spec.ClusterIP = service.Spec.ClusterIP + newObject.SetResourceVersion(service.GetResourceVersion()) + } + case *admv1beta1.MutatingWebhookConfiguration: + r, found, err := rawResource.Get() + if err != nil { + return err + } + if found { + mwc, ok := r.(*admv1beta1.MutatingWebhookConfiguration) + if !ok { + return &typeAssertionError{&admv1beta1.MutatingWebhookConfiguration{}, r} + } + newObject.SetResourceVersion(mwc.GetResourceVersion()) + } } - if msg != "" { - logger.Info(msg) + + msg, err := rawResource.CreateOrReplace(v.planMode) + if err != nil { + return err } + logger.Info(msg) return nil } + +func generateCertReq(service, namespace string) ([]byte, []byte, error) { + generator := csr.Generator{ + Validator: func(request *csr.CertificateRequest) error { + // ignore validation as all required fields are being set + return nil + }, + } + + serviceCN := fmt.Sprintf("%s.%s.svc", service, namespace) + + return generator.ProcessRequest(&csr.CertificateRequest{ + KeyRequest: &csr.KeyRequest{ + A: "rsa", + S: 2048, + }, + CN: serviceCN, + Hosts: []string{ + service, + fmt.Sprintf("%s.%s", service, namespace), + serviceCN, + }, + }) +} diff --git a/pkg/apis/eksctl.io/v1alpha5/nodegroups.go b/pkg/apis/eksctl.io/v1alpha5/nodegroups.go index 548c6d5928..00382c7ca2 100644 --- a/pkg/apis/eksctl.io/v1alpha5/nodegroups.go +++ b/pkg/apis/eksctl.io/v1alpha5/nodegroups.go @@ -78,13 +78,3 @@ func (c *ClusterConfig) AllNodeGroups() []*NodeGroupBase { } return baseNodeGroups } - -// HasWindowsNodeGroup returns true if an unmanaged Windows nodegroup exists. -func (c *ClusterConfig) HasWindowsNodeGroup() bool { - for _, ng := range c.NodeGroups { - if IsWindowsImage(ng.AMIFamily) { - return true - } - } - return false -} diff --git a/pkg/ctl/create/cluster.go b/pkg/ctl/create/cluster.go index 45fbb32e77..f3d96d66f1 100644 --- a/pkg/ctl/create/cluster.go +++ b/pkg/ctl/create/cluster.go @@ -6,8 +6,6 @@ import ( "os" "strings" - "github.com/weaveworks/eksctl/pkg/addons" - "github.com/aws/amazon-ec2-instance-selector/v2/pkg/selector" "github.com/weaveworks/eksctl/pkg/kops" "github.com/weaveworks/eksctl/pkg/utils" @@ -73,8 +71,6 @@ func createClusterCmdWithRunFunc(cmd *cmdutils.Cmd, runFunc func(cmd *cmdutils.C fs.BoolVarP(¶ms.InstallWindowsVPCController, "install-vpc-controllers", "", false, "Install VPC controller that's required for Windows workloads") fs.BoolVarP(¶ms.Fargate, "fargate", "", false, "Create a Fargate profile scheduling pods in the default and kube-system namespaces onto Fargate") fs.BoolVarP(¶ms.DryRun, "dry-run", "", false, "Dry-run mode that skips cluster creation and outputs a ClusterConfig") - - _ = fs.MarkDeprecated("install-vpc-controllers", addons.VPCControllerInfoMessage) }) cmd.FlagSetGroup.InFlagSet("Initial nodegroup", func(fs *pflag.FlagSet) { @@ -194,7 +190,6 @@ func doCreateCluster(cmd *cmdutils.Cmd, ngFilter *filter.NodeGroupFilter, params if !eks.SupportsWindowsWorkloads(kubeNodeGroups) { return errors.New("running Windows workloads requires having both Windows and Linux (AmazonLinux2) node groups") } - logger.Warning(addons.VPCControllerInfoMessage) } else { eks.LogWindowsCompatibility(kubeNodeGroups, cfg.Metadata) } @@ -253,7 +248,7 @@ func doCreateCluster(cmd *cmdutils.Cmd, ngFilter *filter.NodeGroupFilter, params if err != nil { return err } - postClusterCreationTasks := ctl.CreateExtraClusterConfigTasks(cfg) + postClusterCreationTasks := ctl.CreateExtraClusterConfigTasks(cfg, params.InstallWindowsVPCController) supported, err := utils.IsMinVersion(api.Version1_18, cfg.Metadata.Version) if err != nil { diff --git a/pkg/ctl/utils/install_vpc_controllers.go b/pkg/ctl/utils/install_vpc_controllers.go index 1306f64e5e..79e3893844 100644 --- a/pkg/ctl/utils/install_vpc_controllers.go +++ b/pkg/ctl/utils/install_vpc_controllers.go @@ -4,7 +4,6 @@ import ( "github.com/kris-nova/logger" "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/weaveworks/eksctl/pkg/addons" "github.com/weaveworks/eksctl/pkg/eks" "github.com/weaveworks/eksctl/pkg/utils/tasks" @@ -17,17 +16,13 @@ func installWindowsVPCController(cmd *cmdutils.Cmd) { cmd.ClusterConfig = cfg cmd.SetDescription("install-vpc-controllers", "Install Windows VPC controller to support running Windows workloads", "") - cmd.CobraCommand.Deprecated = addons.VPCControllerInfoMessage - - var deleteController bool cmd.CobraCommand.RunE = func(_ *cobra.Command, args []string) error { cmd.NameArg = cmdutils.GetNameArg(args) - return doInstallWindowsVPCController(cmd, deleteController) + return doInstallWindowsVPCController(cmd) } cmd.FlagSetGroup.InFlagSet("General", func(fs *pflag.FlagSet) { - fs.BoolVar(&deleteController, "delete", false, "Deletes VPC resource controller from worker nodes") cmdutils.AddClusterFlagWithDeprecated(fs, cfg.Metadata) cmdutils.AddRegionFlag(fs, &cmd.ProviderConfig) cmdutils.AddConfigFileFlag(fs, &cmd.ClusterConfigFile) @@ -38,33 +33,33 @@ func installWindowsVPCController(cmd *cmdutils.Cmd) { cmdutils.AddCommonFlagsForAWS(cmd.FlagSetGroup, &cmd.ProviderConfig, false) } -func doInstallWindowsVPCController(cmd *cmdutils.Cmd, deleteController bool) error { - if !deleteController { - logger.Warning(addons.VPCControllerInfoMessage) - return nil - } - +func doInstallWindowsVPCController(cmd *cmdutils.Cmd) error { if err := cmdutils.NewMetadataLoader(cmd).Load(); err != nil { return err } + cfg := cmd.ClusterConfig + meta := cmd.ClusterConfig.Metadata + ctl, err := cmd.NewProviderForExistingCluster() if err != nil { return err } - logger.Info("using region %s", cmd.ClusterConfig.Metadata.Region) - rawClient, err := ctl.NewRawClient(cmd.ClusterConfig) - if err != nil { + logger.Info("using region %s", meta.Region) + + if ok, err := ctl.CanUpdate(cfg); !ok { return err } - deleteControllerTask := &eks.DeleteVPCControllerTask{ - Info: "delete Windows VPC controller", - PlanMode: cmd.Plan, - RawClient: rawClient, + + vpcControllerTask := &eks.VPCControllerTask{ + Info: "install Windows VPC controller", + ClusterConfig: cfg, + ClusterProvider: ctl, + PlanMode: cmd.Plan, } taskTree := &tasks.TaskTree{ - Tasks: []tasks.Task{deleteControllerTask}, + Tasks: []tasks.Task{vpcControllerTask}, } if errs := taskTree.DoAllSync(); len(errs) > 0 { diff --git a/pkg/eks/tasks.go b/pkg/eks/tasks.go index b749d07170..b457a8b22a 100644 --- a/pkg/eks/tasks.go +++ b/pkg/eks/tasks.go @@ -6,10 +6,11 @@ import ( "time" "github.com/weaveworks/eksctl/pkg/actions/identityproviders" - "github.com/weaveworks/eksctl/pkg/windows" + "github.com/weaveworks/eksctl/pkg/actions/irsa" "github.com/kris-nova/logger" "github.com/pkg/errors" + "github.com/weaveworks/eksctl/pkg/cfn/manager" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -39,52 +40,44 @@ func (t *clusterConfigTask) Do(errs chan error) error { return err } -// WindowsIPAMTask is a task for enabling Windows IPAM. -type WindowsIPAMTask struct { - Info string - ClientsetFunc func() (kubernetes.Interface, error) +// VPCControllerTask represents a task to install the VPC controller +type VPCControllerTask struct { + Info string + ClusterProvider *ClusterProvider + ClusterConfig *api.ClusterConfig + PlanMode bool } -// Do implements Task. -func (w *WindowsIPAMTask) Do(errCh chan error) error { - defer close(errCh) +// Describe implements Task +func (v *VPCControllerTask) Describe() string { return v.Info } - clientset, err := w.ClientsetFunc() +// Do implements Task +func (v *VPCControllerTask) Do(errCh chan error) error { + defer close(errCh) + rawClient, err := v.ClusterProvider.NewRawClient(v.ClusterConfig) if err != nil { return err } - windowsIPAM := windows.IPAM{ - Clientset: clientset, + oidc, err := v.ClusterProvider.NewOpenIDConnectManager(v.ClusterConfig) + if err != nil { + return err } - return windowsIPAM.Enable(context.TODO()) -} - -// Describe implements Task. -func (w *WindowsIPAMTask) Describe() string { - return w.Info -} -// DeleteVPCControllerTask is a task for deleting VPC controller resources. -type DeleteVPCControllerTask struct { - RawClient *kubernetes.RawClient - PlanMode bool - Info string -} + stackCollection := manager.NewStackCollection(v.ClusterProvider.Provider, v.ClusterConfig) -// Do implements Task. -func (v *DeleteVPCControllerTask) Do(errCh chan error) error { - defer close(errCh) - - vpcController := &addons.VPCController{ - RawClient: v.RawClient, - PlanMode: v.PlanMode, + clientSet, err := v.ClusterProvider.NewStdClientSet(v.ClusterConfig) + if err != nil { + return err } - return vpcController.Delete() -} + irsaManager := irsa.New(v.ClusterConfig.Metadata.Name, stackCollection, oidc, clientSet) + irsa := addons.NewIRSAHelper(oidc, stackCollection, irsaManager, v.ClusterConfig.Metadata.Name) -// Describe implements Task. -func (v *DeleteVPCControllerTask) Describe() string { - return v.Info + // TODO PlanMode doesn't work as intended + vpcController := addons.NewVPCController(rawClient, irsa, v.ClusterConfig.Status, v.ClusterProvider.Provider.Region(), v.PlanMode) + if err := vpcController.Deploy(); err != nil { + return errors.Wrap(err, "error installing VPC controller") + } + return nil } type devicePluginTask struct { @@ -195,7 +188,7 @@ func (t *restartDaemonsetTask) Do(errCh chan error) error { } // CreateExtraClusterConfigTasks returns all tasks for updating cluster configuration not depending on the control plane availability -func (c *ClusterProvider) CreateExtraClusterConfigTasks(cfg *api.ClusterConfig) *tasks.TaskTree { +func (c *ClusterProvider) CreateExtraClusterConfigTasks(cfg *api.ClusterConfig, installVPCController bool) *tasks.TaskTree { newTasks := &tasks.TaskTree{ Parallel: false, IsSubTask: true, @@ -261,12 +254,11 @@ func (c *ClusterProvider) CreateExtraClusterConfigTasks(cfg *api.ClusterConfig) newTasks.Append(identityproviders.NewAssociateProvidersTask(*cfg.Metadata, cfg.IdentityProviders, c.Provider.EKS())) } - if cfg.HasWindowsNodeGroup() { - newTasks.Append(&WindowsIPAMTask{ - Info: "enable Windows IP address management", - ClientsetFunc: func() (kubernetes.Interface, error) { - return c.NewStdClientSet(cfg) - }, + if installVPCController { + newTasks.Append(&VPCControllerTask{ + Info: "install Windows VPC controller", + ClusterConfig: cfg, + ClusterProvider: c, }) } diff --git a/pkg/kubernetes/client.go b/pkg/kubernetes/client.go index cd6494264a..10b7bf2a3e 100644 --- a/pkg/kubernetes/client.go +++ b/pkg/kubernetes/client.go @@ -245,7 +245,7 @@ func (c *RawClient) deleteObject(object runtime.RawExtension) error { if err != nil { return err } - status, err := resource.DeleteSync(false) + status, err := resource.DeleteSync() if err != nil { return err } @@ -415,7 +415,7 @@ func (r *RawResource) CreatePatchOrReplace() error { // DeleteSync attempts to delete this Kubernetes resource, or returns doing // nothing if it does not exist. It blocks until the resource has been deleted. -func (r *RawResource) DeleteSync(plan bool) (string, error) { +func (r *RawResource) DeleteSync() (string, error) { _, exists, err := r.Get() if err != nil { return "", err @@ -423,9 +423,6 @@ func (r *RawResource) DeleteSync(plan bool) (string, error) { if !exists { return "", nil } - if plan { - return r.LogAction(true, "deleted"), nil - } propagationPolicy := metav1.DeletePropagationForeground if _, err := r.Helper.DeleteWithOptions(r.Info.Namespace, r.Info.Name, &metav1.DeleteOptions{ PropagationPolicy: &propagationPolicy, diff --git a/pkg/kubernetes/client_test.go b/pkg/kubernetes/client_test.go index 5a553a9a77..aa037e04ef 100644 --- a/pkg/kubernetes/client_test.go +++ b/pkg/kubernetes/client_test.go @@ -243,7 +243,7 @@ var _ = Describe("Kubernetes client wrappers", func() { Expect(err).ToNot(HaveOccurred()) Expect(exists).To(BeTrue()) // The Kubernetes resource already exists. - status, err := rc.DeleteSync(false) + status, err := rc.DeleteSync() Expect(err).ToNot(HaveOccurred()) Expect(status).To(Equal(fmt.Sprintf("deleted %q", rc))) Expect(track).ToNot(BeNil()) diff --git a/pkg/windows/ipam.go b/pkg/windows/ipam.go deleted file mode 100644 index a2390e0028..0000000000 --- a/pkg/windows/ipam.go +++ /dev/null @@ -1,86 +0,0 @@ -package windows - -import ( - "context" - "encoding/json" - - jsonpatch "github.com/evanphx/json-patch/v5" - "github.com/kris-nova/logger" - "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" - corev1client "k8s.io/client-go/kubernetes/typed/core/v1" -) - -const ( - vpcCNIName = "amazon-vpc-cni" - vpcCNINamespace = metav1.NamespaceSystem - windowsIPAMField = "enable-windows-ipam" -) - -// IPAM enables Windows IPAM in the VPC CNI ConfigMap. -type IPAM struct { - Clientset kubernetes.Interface -} - -// Enable enables Windows IPAM in the VPC CNI ConfigMap. -func (w *IPAM) Enable(ctx context.Context) error { - configMaps := w.Clientset.CoreV1().ConfigMaps(metav1.NamespaceSystem) - vpcCNIConfig, err := configMaps.Get(ctx, vpcCNIName, metav1.GetOptions{}) - if err != nil { - if !apierrors.IsNotFound(err) { - return errors.Wrapf(err, "error getting ConfigMap %q", vpcCNIName) - } - return createConfigMap(ctx, configMaps) - } - - if val, ok := vpcCNIConfig.Data[windowsIPAMField]; ok && val == "true" { - logger.Info("Windows IPAM is already enabled") - return nil - } - - patch, err := createPatch(vpcCNIConfig) - if err != nil { - return errors.Wrap(err, "error creating merge patch") - } - - _, err = configMaps.Patch(ctx, vpcCNIName, types.StrategicMergePatchType, patch, metav1.PatchOptions{}) - if err != nil { - return errors.Wrapf(err, "failed to patch resource %q", vpcCNIName) - } - return nil -} - -func createPatch(cm *corev1.ConfigMap) ([]byte, error) { - oldData, err := json.Marshal(cm) - if err != nil { - return nil, err - } - cm.Data[windowsIPAMField] = "true" - modifiedData, err := json.Marshal(cm) - if err != nil { - return nil, err - } - return jsonpatch.CreateMergePatch(oldData, modifiedData) -} - -func createConfigMap(ctx context.Context, configMaps corev1client.ConfigMapInterface) error { - cm := &corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: vpcCNIName, - Namespace: vpcCNINamespace, - }, - Data: map[string]string{ - windowsIPAMField: "true", - }, - } - _, err := configMaps.Create(ctx, cm, metav1.CreateOptions{}) - return err -} diff --git a/pkg/windows/ipam_test.go b/pkg/windows/ipam_test.go deleted file mode 100644 index 97ced2e57c..0000000000 --- a/pkg/windows/ipam_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package windows_test - -import ( - "context" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - . "github.com/onsi/gomega" - - . "github.com/onsi/ginkgo/extensions/table" - "github.com/weaveworks/eksctl/pkg/windows" - "k8s.io/client-go/kubernetes/fake" -) - -type ipamEntry struct { - existingConfigMapData map[string]string - - expectedConfigMapData map[string]string -} - -var _ = DescribeTable("Windows IPAM", func(e ipamEntry) { - var clientset *fake.Clientset - if e.existingConfigMapData != nil { - clientset = fake.NewSimpleClientset(&v1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "amazon-vpc-cni", - Namespace: "kube-system", - }, - Data: e.existingConfigMapData, - }) - } else { - clientset = fake.NewSimpleClientset() - } - - ipam := &windows.IPAM{ - Clientset: clientset, - } - ctx := context.Background() - err := ipam.Enable(ctx) - Expect(err).ToNot(HaveOccurred()) - - cm, err := clientset.CoreV1().ConfigMaps("kube-system").Get(ctx, "amazon-vpc-cni", metav1.GetOptions{}) - Expect(err).ToNot(HaveOccurred()) - Expect(cm.Data).To(Equal(e.expectedConfigMapData)) - -}, - Entry("VPC CNI ConfigMap is missing", ipamEntry{ - expectedConfigMapData: map[string]string{ - "enable-windows-ipam": "true", - }, - }), - - Entry("VPC CNI ConfigMap has data", ipamEntry{ - existingConfigMapData: map[string]string{ - "VPC_CNI_1": "yes", - "VPC_CNI_2": "no", - "other": "true", - }, - expectedConfigMapData: map[string]string{ - "VPC_CNI_1": "yes", - "VPC_CNI_2": "no", - "other": "true", - "enable-windows-ipam": "true", - }, - }), - - Entry("VPC CNI ConfigMap has Windows IPAM already enabled", ipamEntry{ - existingConfigMapData: map[string]string{ - "VPC_CNI_1": "yes", - "VPC_CNI_2": "no", - "enable-windows-ipam": "true", - }, - expectedConfigMapData: map[string]string{ - "VPC_CNI_1": "yes", - "VPC_CNI_2": "no", - "enable-windows-ipam": "true", - }, - }), - - Entry("VPC CNI ConfigMap has Windows IPAM explicitly disabled", ipamEntry{ - existingConfigMapData: map[string]string{ - "VPC_CNI_1": "yes", - "VPC_CNI_2": "no", - "enable-windows-ipam": "false", - }, - expectedConfigMapData: map[string]string{ - "VPC_CNI_1": "yes", - "VPC_CNI_2": "no", - "enable-windows-ipam": "true", - }, - }), -) diff --git a/pkg/windows/windows_suite_test.go b/pkg/windows/windows_suite_test.go deleted file mode 100644 index aa7fa2b4bf..0000000000 --- a/pkg/windows/windows_suite_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package windows_test - -import ( - "testing" - - "github.com/weaveworks/eksctl/pkg/testutils" -) - -func TestWindows(t *testing.T) { - testutils.RegisterAndRun(t) -} diff --git a/userdocs/src/usage/windows-worker-nodes.md b/userdocs/src/usage/windows-worker-nodes.md index 3c5998e872..96bd63ba20 100644 --- a/userdocs/src/usage/windows-worker-nodes.md +++ b/userdocs/src/usage/windows-worker-nodes.md @@ -1,15 +1,10 @@ # Windows Worker Nodes From version 1.14, Amazon EKS supports [Windows Nodes][eks-user-guide] that allow running Windows containers. -In addition to having Windows nodes, a Linux node in the cluster is required to run CoreDNS, as Microsoft doesn't support host-networking mode yet. Thus, a Windows EKS cluster will be a mixture of Windows nodes and at least one Linux node. +In addition to having Windows nodes, a Linux node in the cluster is required to run the VPC resource controller and CoreDNS, as Microsoft doesn't support host-networking mode yet. Thus, a Windows EKS cluster will be a mixed-mode cluster containing Windows nodes and at least one Linux node. The Linux nodes are critical to the functioning of the cluster, and thus, for a production-grade cluster, it's recommended to have at least two `t2.large` Linux nodes for HA. -!!!note - You no longer need to install the VPC resource controller on Linux worker nodes to run Windows workloads in EKS clusters. - You can enable Windows IP address management on the EKS control plane via a ConfigMap setting (see https://todo.com for details). - eksctl will automatically patch the ConfigMap to enable Windows IP address management when a Windows nodegroup is created. - For existing clusters, you can enable it manually, and run `eksctl utils install-vpc-controllers` with the `--delete` flag - to remove the worker node installation of the VPC resource controller. +`eksctl` provides a flag to install the VPC resource controller as part of cluster creation, and a command to install it after a cluster has been created. ## Creating a new Windows cluster @@ -31,8 +26,6 @@ nodeGroups: amiFamily: WindowsServer2019FullContainer minSize: 2 maxSize: 3 - -managedNodeGroups: - name: linux-ng instanceType: t2.large minSize: 2 @@ -40,7 +33,7 @@ managedNodeGroups: ``` ```console -eksctl create cluster -f cluster.yaml +eksctl create cluster -f cluster.yaml --install-vpc-controllers ``` @@ -49,6 +42,7 @@ To create a new cluster without using a config file, issue the following command ```console eksctl create cluster --managed=false --name=windows-cluster --node-ami-family=WindowsServer2019CoreContainer eksctl create nodegroup --cluster=windows-cluster --node-ami-family=AmazonLinux2 --nodes-min=2 --node-type=t2.large +eksctl utils install-vpc-controllers --cluster=windows-cluster --approve ``` !!!note @@ -56,10 +50,11 @@ eksctl create nodegroup --cluster=windows-cluster --node-ami-family=AmazonLinux2 ## Adding Windows support to an existing Linux cluster -To enable running Windows workloads on an existing cluster with Linux nodes (`AmazonLinux2` AMI family), you need to add a Windows nodegroup. +To enable running Windows workloads on an existing cluster with Linux nodes (`AmazonLinux2` AMI family), you need to add a Windows node group and install the Windows VPC controller: ```console eksctl create nodegroup --managed=false --cluster=existing-cluster --node-ami-family=WindowsServer2019CoreContainer +eksctl utils install-vpc-controllers --cluster=existing-cluster --approve ``` To ensure workloads are scheduled on the right OS, they must have a `nodeSelector` targeting the OS it must run on: