mirror of https://github.com/kubernetes/kops.git
Merge pull request #11322 from johngmyers/warmpool-cluster
Add cluster-level warmPool settings
This commit is contained in:
commit
17e46e5a2c
|
|
@ -257,6 +257,9 @@ spec:
|
||||||
maxSize: 10
|
maxSize: 10
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also specify defaults for all instance groups of type Node or APIServer by setting the `warmPool` field in the cluster spec.
|
||||||
|
If warm pools are enabled at the cluster spec level, you can disable them at the instance group level by setting `maxSize: 0`.
|
||||||
|
|
||||||
### Lifecycle hook
|
### Lifecycle hook
|
||||||
|
|
||||||
By default AWS does not guarantee that the kOps configuration will run to completion. Nor that the instance will timely shut down after completion if the instance is allowed to run that long. In order to guarantee this, a lifecycle hook is needed.
|
By default AWS does not guarantee that the kOps configuration will run to completion. Nor that the instance will timely shut down after completion if the instance is allowed to run that long. In order to guarantee this, a lifecycle hook is needed.
|
||||||
|
|
|
||||||
|
|
@ -4141,6 +4141,29 @@ spec:
|
||||||
needed containers. This is needed if some APIs do have self-signed
|
needed containers. This is needed if some APIs do have self-signed
|
||||||
certs
|
certs
|
||||||
type: boolean
|
type: boolean
|
||||||
|
warmPool:
|
||||||
|
description: WarmPool defines the default warm pool settings for instance
|
||||||
|
groups (AWS only).
|
||||||
|
properties:
|
||||||
|
enableLifecycleHook:
|
||||||
|
description: EnableLifecycleHook determines if an ASG lifecycle
|
||||||
|
hook will be added ensuring that nodeup runs to completion.
|
||||||
|
Note that the metadata API must be protected from arbitrary
|
||||||
|
Pods when this is enabled.
|
||||||
|
type: boolean
|
||||||
|
maxSize:
|
||||||
|
description: MaxSize is the maximum size of the warm pool. The
|
||||||
|
desired size of the instance group is subtracted from this number
|
||||||
|
to determine the desired size of the warm pool (unless the resulting
|
||||||
|
number is smaller than MinSize). The default is the instance
|
||||||
|
group's MaxSize.
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
minSize:
|
||||||
|
description: MinSize is the minimum size of the pool
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
served: true
|
served: true
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ go_library(
|
||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = [
|
||||||
|
"cluster_test.go",
|
||||||
"parse_test.go",
|
"parse_test.go",
|
||||||
"semver_test.go",
|
"semver_test.go",
|
||||||
],
|
],
|
||||||
|
|
@ -49,6 +50,7 @@ go_test(
|
||||||
deps = [
|
deps = [
|
||||||
"//upup/pkg/fi/utils:go_default_library",
|
"//upup/pkg/fi/utils:go_default_library",
|
||||||
"//vendor/github.com/blang/semver/v4:go_default_library",
|
"//vendor/github.com/blang/semver/v4:go_default_library",
|
||||||
|
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||||
"//vendor/gopkg.in/inf.v0:go_default_library",
|
"//vendor/gopkg.in/inf.v0:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -201,10 +201,12 @@ type ClusterSpec struct {
|
||||||
// specified, each parameter must follow the form variable=value, the way
|
// specified, each parameter must follow the form variable=value, the way
|
||||||
// it would appear in sysctl.conf.
|
// it would appear in sysctl.conf.
|
||||||
SysctlParameters []string `json:"sysctlParameters,omitempty"`
|
SysctlParameters []string `json:"sysctlParameters,omitempty"`
|
||||||
// RollingUpdate defines the default rolling-update settings for instance groups
|
// RollingUpdate defines the default rolling-update settings for instance groups.
|
||||||
RollingUpdate *RollingUpdate `json:"rollingUpdate,omitempty"`
|
RollingUpdate *RollingUpdate `json:"rollingUpdate,omitempty"`
|
||||||
// ClusterAutoscaler defines the cluster autoscaler configuration.
|
// ClusterAutoscaler defines the cluster autoscaler configuration.
|
||||||
ClusterAutoscaler *ClusterAutoscalerConfig `json:"clusterAutoscaler,omitempty"`
|
ClusterAutoscaler *ClusterAutoscalerConfig `json:"clusterAutoscaler,omitempty"`
|
||||||
|
// WarmPool defines the default warm pool settings for instance groups (AWS only).
|
||||||
|
WarmPool *WarmPoolSpec `json:"warmPool,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeAuthorizationSpec is used to node authorization
|
// NodeAuthorizationSpec is used to node authorization
|
||||||
|
|
@ -843,3 +845,49 @@ type PackagesConfig struct {
|
||||||
// UrlArm64 overrides the URL for the ARM64 package.
|
// UrlArm64 overrides the URL for the ARM64 package.
|
||||||
UrlArm64 *string `json:"urlArm64,omitempty"`
|
UrlArm64 *string `json:"urlArm64,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WarmPoolSpec struct {
|
||||||
|
// MinSize is the minimum size of the warm pool.
|
||||||
|
MinSize int64 `json:"minSize,omitempty"`
|
||||||
|
// MaxSize is the maximum size of the warm pool. The desired size of the instance group
|
||||||
|
// is subtracted from this number to determine the desired size of the warm pool
|
||||||
|
// (unless the resulting number is smaller than MinSize).
|
||||||
|
// The default is the instance group's MaxSize.
|
||||||
|
MaxSize *int64 `json:"maxSize,omitempty"`
|
||||||
|
// EnableLifecyleHook determines if an ASG lifecycle hook will be added ensuring that nodeup runs to completion.
|
||||||
|
// Note that the metadata API must be protected from arbitrary Pods when this is enabled.
|
||||||
|
EnableLifecycleHook bool `json:"enableLifecycleHook,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *WarmPoolSpec) IsEnabled() bool {
|
||||||
|
return in != nil && (in.MaxSize == nil || *in.MaxSize != 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (in *WarmPoolSpec) ResolveDefaults(ig *InstanceGroup) *WarmPoolSpec {
|
||||||
|
igWarmPool := ig.Spec.WarmPool
|
||||||
|
if igWarmPool == nil {
|
||||||
|
if in == nil || (ig.Spec.Role == InstanceGroupRoleMaster || ig.Spec.Role == InstanceGroupRoleBastion) {
|
||||||
|
var zero int64
|
||||||
|
return &WarmPoolSpec{
|
||||||
|
MaxSize: &zero,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
if in == nil || (ig.Spec.Role == InstanceGroupRoleMaster || ig.Spec.Role == InstanceGroupRoleBastion) {
|
||||||
|
return igWarmPool
|
||||||
|
}
|
||||||
|
|
||||||
|
spec := *igWarmPool
|
||||||
|
if spec.MaxSize == nil {
|
||||||
|
spec.MaxSize = in.MaxSize
|
||||||
|
}
|
||||||
|
if spec.MinSize == 0 {
|
||||||
|
spec.MinSize = in.MinSize
|
||||||
|
}
|
||||||
|
if !spec.EnableLifecycleHook {
|
||||||
|
spec.EnableLifecycleHook = in.EnableLifecycleHook
|
||||||
|
}
|
||||||
|
return &spec
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kops
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWarmPoolSpec_IsEnabled(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
spec *WarmPoolSpec
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
spec: nil,
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
spec: &WarmPoolSpec{},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "1",
|
||||||
|
spec: &WarmPoolSpec{
|
||||||
|
MaxSize: int64ptr(1),
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "0",
|
||||||
|
spec: &WarmPoolSpec{
|
||||||
|
MaxSize: int64ptr(0),
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if actual := tc.spec.IsEnabled(); actual != tc.expected {
|
||||||
|
t.Errorf("IsEnabled() = %v, want %v", actual, tc.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func int64ptr(v int64) *int64 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWarmPoolSpec_ResolveDefaults(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
defaultValue interface{}
|
||||||
|
nonDefaultValue interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "MinSize",
|
||||||
|
defaultValue: int64(0),
|
||||||
|
nonDefaultValue: int64(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MaxSize",
|
||||||
|
defaultValue: nil,
|
||||||
|
nonDefaultValue: int64(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EnableLifecycleHook",
|
||||||
|
defaultValue: false,
|
||||||
|
nonDefaultValue: true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
defaultCluster := &WarmPoolSpec{}
|
||||||
|
setFieldValue(defaultCluster, tc.name, tc.defaultValue)
|
||||||
|
|
||||||
|
nonDefaultCluster := &WarmPoolSpec{}
|
||||||
|
setFieldValue(nonDefaultCluster, tc.name, tc.nonDefaultValue)
|
||||||
|
|
||||||
|
defaultGroup := &WarmPoolSpec{}
|
||||||
|
setFieldValue(defaultGroup, tc.name, tc.defaultValue)
|
||||||
|
|
||||||
|
nonDefaultGroup := &WarmPoolSpec{}
|
||||||
|
setFieldValue(nonDefaultGroup, tc.name, tc.nonDefaultValue)
|
||||||
|
|
||||||
|
expectedDefaultValue := tc.defaultValue
|
||||||
|
if expectedDefaultValue == nil {
|
||||||
|
expectedDefaultValue = reflect.Zero(reflect.ValueOf(*defaultGroup).FieldByName(tc.name).Type().Elem()).Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
assertResolvesValue(t, tc.name, expectedDefaultValue, nil, nil, InstanceGroupRoleNode, "nil nil node")
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, &WarmPoolSpec{}, nil, InstanceGroupRoleNode, "{nil} nil node")
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, defaultCluster, nil, InstanceGroupRoleNode, "{default} nil node")
|
||||||
|
assertResolvesValue(t, tc.name, tc.nonDefaultValue, nonDefaultCluster, nil, InstanceGroupRoleNode, "{nonDefault} nil node")
|
||||||
|
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, nil, &WarmPoolSpec{}, InstanceGroupRoleNode, "nil {nil} node")
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, &WarmPoolSpec{}, &WarmPoolSpec{}, InstanceGroupRoleNode, "{nil} {nil} node")
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, defaultCluster, &WarmPoolSpec{}, InstanceGroupRoleNode, "{default} {nil} node")
|
||||||
|
assertResolvesValue(t, tc.name, tc.nonDefaultValue, nonDefaultCluster, &WarmPoolSpec{}, InstanceGroupRoleNode, "{nonDefault} {nil} node")
|
||||||
|
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, nil, defaultGroup, InstanceGroupRoleNode, "nil {default} node")
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, &WarmPoolSpec{}, defaultGroup, InstanceGroupRoleNode, "{nil} {default} node")
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, defaultCluster, defaultGroup, InstanceGroupRoleNode, "{default} {default} node")
|
||||||
|
if reflect.ValueOf(*defaultGroup).FieldByName(tc.name).Type().Kind() == reflect.Ptr && tc.defaultValue != nil {
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, nonDefaultCluster, defaultGroup, InstanceGroupRoleNode, "{nonDefault} {default} node")
|
||||||
|
} else {
|
||||||
|
assertResolvesValue(t, tc.name, tc.nonDefaultValue, nonDefaultCluster, defaultGroup, InstanceGroupRoleNode, "{nonDefault} {default} node")
|
||||||
|
}
|
||||||
|
|
||||||
|
assertResolvesValue(t, tc.name, tc.nonDefaultValue, nil, nonDefaultGroup, InstanceGroupRoleNode, "nil {nonDefault} node")
|
||||||
|
assertResolvesValue(t, tc.name, tc.nonDefaultValue, &WarmPoolSpec{}, nonDefaultGroup, InstanceGroupRoleNode, "{nil} {nonDefault} node")
|
||||||
|
assertResolvesValue(t, tc.name, tc.nonDefaultValue, defaultCluster, nonDefaultGroup, InstanceGroupRoleNode, "{default} {nonDefault} node")
|
||||||
|
assertResolvesValue(t, tc.name, tc.nonDefaultValue, nonDefaultCluster, nonDefaultGroup, InstanceGroupRoleNode, "{nonDefault} {nonDefault} node")
|
||||||
|
|
||||||
|
assertResolvesValue(t, tc.name, expectedDefaultValue, nil, nil, InstanceGroupRoleMaster, "nil nil master")
|
||||||
|
assertResolvesValue(t, tc.name, expectedDefaultValue, &WarmPoolSpec{}, nil, InstanceGroupRoleMaster, "{nil} nil master")
|
||||||
|
assertResolvesValue(t, tc.name, expectedDefaultValue, defaultCluster, nil, InstanceGroupRoleMaster, "{default} nil master")
|
||||||
|
assertResolvesValue(t, tc.name, expectedDefaultValue, nonDefaultCluster, nil, InstanceGroupRoleMaster, "{nonDefault} nil master")
|
||||||
|
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, nil, &WarmPoolSpec{}, InstanceGroupRoleMaster, "nil {nil} master")
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, &WarmPoolSpec{}, &WarmPoolSpec{}, InstanceGroupRoleMaster, "{nil} {nil} master")
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, defaultCluster, &WarmPoolSpec{}, InstanceGroupRoleMaster, "{default} {nil} master")
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, nonDefaultCluster, &WarmPoolSpec{}, InstanceGroupRoleMaster, "{nonDefault} {nil} master")
|
||||||
|
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, nil, defaultGroup, InstanceGroupRoleMaster, "nil {default} master")
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, &WarmPoolSpec{}, defaultGroup, InstanceGroupRoleMaster, "{nil} {default} master")
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, defaultCluster, defaultGroup, InstanceGroupRoleMaster, "{default} {default} master")
|
||||||
|
assertResolvesValue(t, tc.name, tc.defaultValue, nonDefaultCluster, defaultGroup, InstanceGroupRoleMaster, "{nonDefault} {default} master")
|
||||||
|
|
||||||
|
assertResolvesValue(t, tc.name, tc.nonDefaultValue, nil, nonDefaultGroup, InstanceGroupRoleMaster, "nil {nonDefault} master")
|
||||||
|
assertResolvesValue(t, tc.name, tc.nonDefaultValue, &WarmPoolSpec{}, nonDefaultGroup, InstanceGroupRoleMaster, "{nil} {nonDefault} master")
|
||||||
|
assertResolvesValue(t, tc.name, tc.nonDefaultValue, defaultCluster, nonDefaultGroup, InstanceGroupRoleMaster, "{default} {nonDefault} master")
|
||||||
|
assertResolvesValue(t, tc.name, tc.nonDefaultValue, nonDefaultCluster, nonDefaultGroup, InstanceGroupRoleMaster, "{nonDefault} {nonDefault} master")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFieldValue(aStruct interface{}, fieldName string, fieldValue interface{}) {
|
||||||
|
field := reflect.ValueOf(aStruct).Elem().FieldByName(fieldName)
|
||||||
|
fieldType := field.Type()
|
||||||
|
if fieldType.Kind() == reflect.Ptr {
|
||||||
|
if fieldValue != nil {
|
||||||
|
value := reflect.New(fieldType.Elem())
|
||||||
|
value.Elem().Set(reflect.ValueOf(fieldValue))
|
||||||
|
field.Set(value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
field.Set(reflect.ValueOf(fieldValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertResolvesValue(t *testing.T, name string, expected interface{}, warmPoolSpecDefault *WarmPoolSpec, ig *WarmPoolSpec, role InstanceGroupRole, msg interface{}) bool {
|
||||||
|
cluster := Cluster{
|
||||||
|
Spec: ClusterSpec{
|
||||||
|
WarmPool: warmPoolSpecDefault,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
instanceGroup := InstanceGroup{
|
||||||
|
Spec: InstanceGroupSpec{
|
||||||
|
Role: role,
|
||||||
|
WarmPool: ig,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
warmPoolSpecDefaultCopy := warmPoolSpecDefault.DeepCopy()
|
||||||
|
warmPoolSpecCopy := ig.DeepCopy()
|
||||||
|
|
||||||
|
resolved := cluster.Spec.WarmPool.ResolveDefaults(&instanceGroup)
|
||||||
|
value := reflect.ValueOf(*resolved).FieldByName(name)
|
||||||
|
|
||||||
|
assert.Equal(t, warmPoolSpecDefault, cluster.Spec.WarmPool, "cluster not modified")
|
||||||
|
assert.True(t, reflect.DeepEqual(warmPoolSpecDefault, warmPoolSpecDefaultCopy), "WarmPoolSpec not modified")
|
||||||
|
assert.Equal(t, ig, instanceGroup.Spec.WarmPool, "instancegroup not modified")
|
||||||
|
assert.True(t, reflect.DeepEqual(ig, warmPoolSpecCopy), "WarmPoolSpec not modified")
|
||||||
|
|
||||||
|
if expected == nil {
|
||||||
|
return assert.Nil(t, value.Interface(), msg)
|
||||||
|
} else if value.Type().Kind() == reflect.Ptr {
|
||||||
|
return assert.NotNil(t, value.Interface(), msg) &&
|
||||||
|
assert.Equal(t, expected, value.Elem().Interface(), msg)
|
||||||
|
} else {
|
||||||
|
return assert.Equal(t, expected, value.Interface(), msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -186,23 +186,6 @@ type InstanceGroupSpec struct {
|
||||||
WarmPool *WarmPoolSpec `json:"warmPool,omitempty"`
|
WarmPool *WarmPoolSpec `json:"warmPool,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WarmPoolSpec struct {
|
|
||||||
// MinSize is the minimum size of the warm pool.
|
|
||||||
MinSize int64 `json:"minSize,omitempty"`
|
|
||||||
// MaxSize is the maximum size of the warm pool. The desired size of the instance group
|
|
||||||
// is subtracted from this number to determine the desired size of the warm pool
|
|
||||||
// (unless the resulting number is smaller than MinSize).
|
|
||||||
// The default is the instance group's MaxSize.
|
|
||||||
MaxSize *int64 `json:"maxSize,omitempty"`
|
|
||||||
// EnableLifecycleHook determines if an ASG lifecycle hook will be added ensuring that nodeup runs to completion.
|
|
||||||
// Note that the metadata API must be protected from arbitrary Pods when this is enabled.
|
|
||||||
EnableLifecycleHook bool `json:"enableLifecycleHook,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (in *WarmPoolSpec) IsEnabled() bool {
|
|
||||||
return in != nil && (in.MaxSize == nil || *in.MaxSize != 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// SpotAllocationStrategyLowestPrices indicates a lowest-price strategy
|
// SpotAllocationStrategyLowestPrices indicates a lowest-price strategy
|
||||||
SpotAllocationStrategyLowestPrices = "lowest-price"
|
SpotAllocationStrategyLowestPrices = "lowest-price"
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,8 @@ type ClusterSpec struct {
|
||||||
RollingUpdate *RollingUpdate `json:"rollingUpdate,omitempty"`
|
RollingUpdate *RollingUpdate `json:"rollingUpdate,omitempty"`
|
||||||
// ClusterAutoscaler defines the cluaster autoscaler configuration.
|
// ClusterAutoscaler defines the cluaster autoscaler configuration.
|
||||||
ClusterAutoscaler *ClusterAutoscalerConfig `json:"clusterAutoscaler,omitempty"`
|
ClusterAutoscaler *ClusterAutoscalerConfig `json:"clusterAutoscaler,omitempty"`
|
||||||
|
// WarmPool defines the default warm pool settings for instance groups (AWS only).
|
||||||
|
WarmPool *WarmPoolSpec `json:"warmPool,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeAuthorizationSpec is used to node authorization
|
// NodeAuthorizationSpec is used to node authorization
|
||||||
|
|
@ -707,3 +709,16 @@ type PackagesConfig struct {
|
||||||
// UrlArm64 overrides the URL for the ARM64 package.
|
// UrlArm64 overrides the URL for the ARM64 package.
|
||||||
UrlArm64 *string `json:"urlArm64,omitempty"`
|
UrlArm64 *string `json:"urlArm64,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WarmPoolSpec struct {
|
||||||
|
// MinSize is the minimum size of the pool
|
||||||
|
MinSize int64 `json:"minSize,omitempty"`
|
||||||
|
// MaxSize is the maximum size of the warm pool. The desired size of the instance group
|
||||||
|
// is subtracted from this number to determine the desired size of the warm pool
|
||||||
|
// (unless the resulting number is smaller than MinSize).
|
||||||
|
// The default is the instance group's MaxSize.
|
||||||
|
MaxSize *int64 `json:"maxSize,omitempty"`
|
||||||
|
// EnableLifecycleHook determines if an ASG lifecycle hook will be added ensuring that nodeup runs to completion.
|
||||||
|
// Note that the metadata API must be protected from arbitrary Pods when this is enabled.
|
||||||
|
EnableLifecycleHook bool `json:"enableLifecycleHook,omitempty"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -151,18 +151,6 @@ type InstanceGroupSpec struct {
|
||||||
// WarmPool configures an ASG warm pool for the instance group
|
// WarmPool configures an ASG warm pool for the instance group
|
||||||
WarmPool *WarmPoolSpec `json:"warmPool,omitempty"`
|
WarmPool *WarmPoolSpec `json:"warmPool,omitempty"`
|
||||||
}
|
}
|
||||||
type WarmPoolSpec struct {
|
|
||||||
// MinSize is the minimum size of the pool
|
|
||||||
MinSize int64 `json:"minSize,omitempty"`
|
|
||||||
// MaxSize is the maximum size of the warm pool. The desired size of the instance group
|
|
||||||
// is subtracted from this number to determine the desired size of the warm pool
|
|
||||||
// (unless the resulting number is smaller than MinSize).
|
|
||||||
// The default is the instance group's MaxSize.
|
|
||||||
MaxSize *int64 `json:"maxSize,omitempty"`
|
|
||||||
// EnableLifecycleHook determines if an ASG lifecycle hook will be added ensuring that nodeup runs to completion.
|
|
||||||
// Note that the metadata API must be protected from arbitrary Pods when this is enabled.
|
|
||||||
EnableLifecycleHook bool `json:"enableLifecycleHook,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstanceMetadataOptions defines the EC2 instance metadata service options (AWS Only)
|
// InstanceMetadataOptions defines the EC2 instance metadata service options (AWS Only)
|
||||||
type InstanceMetadataOptions struct {
|
type InstanceMetadataOptions struct {
|
||||||
|
|
|
||||||
|
|
@ -2503,6 +2503,15 @@ func autoConvert_v1alpha2_ClusterSpec_To_kops_ClusterSpec(in *ClusterSpec, out *
|
||||||
} else {
|
} else {
|
||||||
out.ClusterAutoscaler = nil
|
out.ClusterAutoscaler = nil
|
||||||
}
|
}
|
||||||
|
if in.WarmPool != nil {
|
||||||
|
in, out := &in.WarmPool, &out.WarmPool
|
||||||
|
*out = new(kops.WarmPoolSpec)
|
||||||
|
if err := Convert_v1alpha2_WarmPoolSpec_To_kops_WarmPoolSpec(*in, *out, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out.WarmPool = nil
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2880,6 +2889,15 @@ func autoConvert_kops_ClusterSpec_To_v1alpha2_ClusterSpec(in *kops.ClusterSpec,
|
||||||
} else {
|
} else {
|
||||||
out.ClusterAutoscaler = nil
|
out.ClusterAutoscaler = nil
|
||||||
}
|
}
|
||||||
|
if in.WarmPool != nil {
|
||||||
|
in, out := &in.WarmPool, &out.WarmPool
|
||||||
|
*out = new(WarmPoolSpec)
|
||||||
|
if err := Convert_kops_WarmPoolSpec_To_v1alpha2_WarmPoolSpec(*in, *out, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out.WarmPool = nil
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1093,6 +1093,11 @@ func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) {
|
||||||
*out = new(ClusterAutoscalerConfig)
|
*out = new(ClusterAutoscalerConfig)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
|
if in.WarmPool != nil {
|
||||||
|
in, out := &in.WarmPool, &out.WarmPool
|
||||||
|
*out = new(WarmPoolSpec)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -143,29 +143,6 @@ func ValidateInstanceGroup(g *kops.InstanceGroup, cloud fi.Cloud) field.ErrorLis
|
||||||
|
|
||||||
allErrs = append(allErrs, IsValidValue(field.NewPath("spec", "updatePolicy"), g.Spec.UpdatePolicy, []string{kops.UpdatePolicyAutomatic, kops.UpdatePolicyExternal})...)
|
allErrs = append(allErrs, IsValidValue(field.NewPath("spec", "updatePolicy"), g.Spec.UpdatePolicy, []string{kops.UpdatePolicyAutomatic, kops.UpdatePolicyExternal})...)
|
||||||
|
|
||||||
warmPool := g.Spec.WarmPool
|
|
||||||
if warmPool != nil {
|
|
||||||
if g.Spec.Role != kops.InstanceGroupRoleNode && g.Spec.Role != kops.InstanceGroupRoleAPIServer {
|
|
||||||
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "warmPool"), "warm pool only allowed on instance groups with role Node or APIServer"))
|
|
||||||
}
|
|
||||||
if g.Spec.MixedInstancesPolicy != nil {
|
|
||||||
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "warmPool"), "warm pool cannot be combined with a mixed instances policy"))
|
|
||||||
}
|
|
||||||
if g.Spec.MaxPrice != nil {
|
|
||||||
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "warmPool"), "warm pool cannot be used with spot instances"))
|
|
||||||
}
|
|
||||||
if warmPool.MaxSize != nil {
|
|
||||||
if *warmPool.MaxSize < 0 {
|
|
||||||
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "warmPool", "maxSize"), *warmPool.MaxSize, "warm pool maxSize cannot be negative"))
|
|
||||||
} else if warmPool.MinSize > *warmPool.MaxSize {
|
|
||||||
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "warmPool", "maxSize"), fi.Int64Value(warmPool.MaxSize), "warm pool maxSize cannot be set to lower than minSize"))
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if warmPool.MinSize < 0 {
|
|
||||||
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "warmPool", "minSize"), warmPool.MinSize, "warm pool minSize cannot be negative"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,6 +215,33 @@ func CrossValidateInstanceGroup(g *kops.InstanceGroup, cluster *kops.Cluster, cl
|
||||||
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "warmPool"), "warm pool only supported on AWS"))
|
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "warmPool"), "warm pool only supported on AWS"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
warmPool := cluster.Spec.WarmPool.ResolveDefaults(g)
|
||||||
|
if warmPool.MaxSize == nil || *warmPool.MaxSize != 0 {
|
||||||
|
if g.Spec.Role != kops.InstanceGroupRoleNode && g.Spec.Role != kops.InstanceGroupRoleAPIServer {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "warmPool"), "warm pool only allowed on instance groups with role Node or APIServer"))
|
||||||
|
}
|
||||||
|
if g.Spec.MixedInstancesPolicy != nil {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "warmPool"), "warm pool cannot be combined with a mixed instances policy"))
|
||||||
|
}
|
||||||
|
if g.Spec.MaxPrice != nil {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "warmPool"), "warm pool cannot be used with spot instances"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if warmPool.MaxSize != nil {
|
||||||
|
if *warmPool.MaxSize < 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "warmPool", "maxSize"), *warmPool.MaxSize, "warm pool maxSize cannot be negative"))
|
||||||
|
} else if warmPool.MinSize > *warmPool.MaxSize {
|
||||||
|
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "warmPool", "maxSize"), *warmPool.MaxSize, "warm pool maxSize cannot be set to lower than minSize"))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if warmPool.MinSize < 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "warmPool", "minSize"), warmPool.MinSize, "warm pool minSize cannot be negative"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -246,6 +246,14 @@ func validateClusterSpec(spec *kops.ClusterSpec, c *kops.Cluster, fieldPath *fie
|
||||||
allErrs = append(allErrs, validateCloudConfiguration(spec.CloudConfig, fieldPath.Child("cloudConfig"))...)
|
allErrs = append(allErrs, validateCloudConfiguration(spec.CloudConfig, fieldPath.Child("cloudConfig"))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if spec.WarmPool != nil {
|
||||||
|
if kops.CloudProviderID(spec.CloudProvider) != kops.CloudProviderAWS {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "warmPool"), "warm pool only supported on AWS"))
|
||||||
|
} else {
|
||||||
|
allErrs = append(allErrs, validateWarmPool(spec.WarmPool, fieldPath.Child("warmPool"))...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1342,3 +1350,17 @@ func validateCloudConfiguration(cloudConfig *kops.CloudConfiguration, fldPath *f
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateWarmPool(warmPool *kops.WarmPoolSpec, fldPath *field.Path) (allErrs field.ErrorList) {
|
||||||
|
if warmPool.MaxSize != nil {
|
||||||
|
if *warmPool.MaxSize < 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("maxSize"), *warmPool.MaxSize, "warm pool maxSize cannot be negative"))
|
||||||
|
} else if warmPool.MinSize > *warmPool.MaxSize {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("maxSize"), *warmPool.MaxSize, "warm pool maxSize cannot be set to lower than minSize"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if warmPool.MinSize < 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("minSize"), warmPool.MinSize, "warm pool minSize cannot be negative"))
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1193,6 +1193,11 @@ func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) {
|
||||||
*out = new(ClusterAutoscalerConfig)
|
*out = new(ClusterAutoscalerConfig)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
|
if in.WarmPool != nil {
|
||||||
|
in, out := &in.WarmPool, &out.WarmPool
|
||||||
|
*out = new(WarmPoolSpec)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ type AutoscalingGroupModelBuilder struct {
|
||||||
BootstrapScriptBuilder *model.BootstrapScriptBuilder
|
BootstrapScriptBuilder *model.BootstrapScriptBuilder
|
||||||
Lifecycle *fi.Lifecycle
|
Lifecycle *fi.Lifecycle
|
||||||
SecurityLifecycle *fi.Lifecycle
|
SecurityLifecycle *fi.Lifecycle
|
||||||
|
Cluster *kops.Cluster
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ fi.ModelBuilder = &AutoscalingGroupModelBuilder{}
|
var _ fi.ModelBuilder = &AutoscalingGroupModelBuilder{}
|
||||||
|
|
@ -87,7 +88,7 @@ func (b *AutoscalingGroupModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
||||||
tsk.LaunchTemplate = task
|
tsk.LaunchTemplate = task
|
||||||
c.AddTask(tsk)
|
c.AddTask(tsk)
|
||||||
|
|
||||||
warmPool := ig.Spec.WarmPool
|
warmPool := b.Cluster.Spec.WarmPool.ResolveDefaults(ig)
|
||||||
{
|
{
|
||||||
enabled := fi.Bool(warmPool.IsEnabled())
|
enabled := fi.Bool(warmPool.IsEnabled())
|
||||||
warmPoolTask := &awstasks.WarmPool{
|
warmPoolTask := &awstasks.WarmPool{
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ func TestRootVolumeOptimizationFlag(t *testing.T) {
|
||||||
InstanceGroups: igs,
|
InstanceGroups: igs,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Cluster: cluster,
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &fi.ModelBuilderContext{
|
c := &fi.ModelBuilderContext{
|
||||||
|
|
@ -153,6 +154,7 @@ func TestAPIServerAdditionalSecurityGroupsWithNLB(t *testing.T) {
|
||||||
InstanceGroups: igs,
|
InstanceGroups: igs,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Cluster: cluster,
|
||||||
}
|
}
|
||||||
|
|
||||||
c := &fi.ModelBuilderContext{
|
c := &fi.ModelBuilderContext{
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ type IAMModelBuilder struct {
|
||||||
*KopsModelContext
|
*KopsModelContext
|
||||||
|
|
||||||
Lifecycle *fi.Lifecycle
|
Lifecycle *fi.Lifecycle
|
||||||
|
Cluster *kops.Cluster
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ fi.ModelBuilder = &IAMModelBuilder{}
|
var _ fi.ModelBuilder = &IAMModelBuilder{}
|
||||||
|
|
@ -76,8 +77,10 @@ func (b *IAMModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
||||||
// Generate IAM tasks for each shared role
|
// Generate IAM tasks for each shared role
|
||||||
for profileARN, igRole := range sharedProfileARNsToIGRole {
|
for profileARN, igRole := range sharedProfileARNsToIGRole {
|
||||||
lchPermissions := false
|
lchPermissions := false
|
||||||
|
defaultWarmPool := b.Cluster.Spec.WarmPool
|
||||||
for _, ig := range b.InstanceGroups {
|
for _, ig := range b.InstanceGroups {
|
||||||
if ig.Spec.Role == igRole && ig.Spec.WarmPool.IsEnabled() && ig.Spec.WarmPool.EnableLifecycleHook {
|
warmPool := defaultWarmPool.ResolveDefaults(ig)
|
||||||
|
if ig.Spec.Role == igRole && warmPool.IsEnabled() && warmPool.EnableLifecycleHook {
|
||||||
lchPermissions = true
|
lchPermissions = true
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
@ -99,16 +102,18 @@ func (b *IAMModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate IAM tasks for each managed role
|
// Generate IAM tasks for each managed role
|
||||||
|
defaultWarmPool := b.Cluster.Spec.WarmPool
|
||||||
for igRole := range managedRoles {
|
for igRole := range managedRoles {
|
||||||
warmPool := false
|
haveWarmPool := false
|
||||||
for _, ig := range b.InstanceGroups {
|
for _, ig := range b.InstanceGroups {
|
||||||
if ig.Spec.Role == igRole && ig.Spec.WarmPool.IsEnabled() && ig.Spec.WarmPool.EnableLifecycleHook {
|
warmPool := defaultWarmPool.ResolveDefaults(ig)
|
||||||
warmPool = true
|
if ig.Spec.Role == igRole && warmPool.IsEnabled() && warmPool.EnableLifecycleHook {
|
||||||
|
haveWarmPool = true
|
||||||
break
|
break
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
role, err := iam.BuildNodeRoleSubject(igRole, warmPool)
|
role, err := iam.BuildNodeRoleSubject(igRole, haveWarmPool)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -554,7 +554,7 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) error {
|
||||||
&model.FirewallModelBuilder{KopsModelContext: modelContext, Lifecycle: &securityLifecycle},
|
&model.FirewallModelBuilder{KopsModelContext: modelContext, Lifecycle: &securityLifecycle},
|
||||||
&model.SSHKeyModelBuilder{KopsModelContext: modelContext, Lifecycle: &securityLifecycle},
|
&model.SSHKeyModelBuilder{KopsModelContext: modelContext, Lifecycle: &securityLifecycle},
|
||||||
&model.NetworkModelBuilder{KopsModelContext: modelContext, Lifecycle: &networkLifecycle},
|
&model.NetworkModelBuilder{KopsModelContext: modelContext, Lifecycle: &networkLifecycle},
|
||||||
&model.IAMModelBuilder{KopsModelContext: modelContext, Lifecycle: &securityLifecycle},
|
&model.IAMModelBuilder{KopsModelContext: modelContext, Lifecycle: &securityLifecycle, Cluster: cluster},
|
||||||
&awsmodel.OIDCProviderBuilder{KopsModelContext: modelContext, Lifecycle: &securityLifecycle, KeyStore: keyStore},
|
&awsmodel.OIDCProviderBuilder{KopsModelContext: modelContext, Lifecycle: &securityLifecycle, KeyStore: keyStore},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -563,6 +563,7 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) error {
|
||||||
BootstrapScriptBuilder: bootstrapScriptBuilder,
|
BootstrapScriptBuilder: bootstrapScriptBuilder,
|
||||||
Lifecycle: &clusterLifecycle,
|
Lifecycle: &clusterLifecycle,
|
||||||
SecurityLifecycle: &securityLifecycle,
|
SecurityLifecycle: &securityLifecycle,
|
||||||
|
Cluster: cluster,
|
||||||
}
|
}
|
||||||
|
|
||||||
if featureflag.Spotinst.Enabled() {
|
if featureflag.Spotinst.Enabled() {
|
||||||
|
|
|
||||||
|
|
@ -424,7 +424,7 @@ func (b *BootstrapChannelBuilder) buildAddons(c *fi.ModelBuilderContext) (*chann
|
||||||
if b.UseServiceAccountIAM() {
|
if b.UseServiceAccountIAM() {
|
||||||
serviceAccountRoles := []iam.Subject{&dnscontroller.ServiceAccount{}}
|
serviceAccountRoles := []iam.Subject{&dnscontroller.ServiceAccount{}}
|
||||||
for _, serviceAccountRole := range serviceAccountRoles {
|
for _, serviceAccountRole := range serviceAccountRoles {
|
||||||
iamModelBuilder := &model.IAMModelBuilder{KopsModelContext: b.KopsModelContext, Lifecycle: b.Lifecycle}
|
iamModelBuilder := &model.IAMModelBuilder{KopsModelContext: b.KopsModelContext, Lifecycle: b.Lifecycle, Cluster: b.Cluster}
|
||||||
|
|
||||||
err := iamModelBuilder.BuildServiceAccountRoleTasks(serviceAccountRole, c)
|
err := iamModelBuilder.BuildServiceAccountRoleTasks(serviceAccountRole, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -582,7 +582,7 @@ func (b *BootstrapChannelBuilder) buildAddons(c *fi.ModelBuilderContext) (*chann
|
||||||
if b.UseServiceAccountIAM() {
|
if b.UseServiceAccountIAM() {
|
||||||
serviceAccountRoles := []iam.Subject{&awsloadbalancercontroller.ServiceAccount{}}
|
serviceAccountRoles := []iam.Subject{&awsloadbalancercontroller.ServiceAccount{}}
|
||||||
for _, serviceAccountRole := range serviceAccountRoles {
|
for _, serviceAccountRole := range serviceAccountRoles {
|
||||||
iamModelBuilder := &model.IAMModelBuilder{KopsModelContext: b.KopsModelContext, Lifecycle: b.Lifecycle}
|
iamModelBuilder := &model.IAMModelBuilder{KopsModelContext: b.KopsModelContext, Lifecycle: b.Lifecycle, Cluster: b.Cluster}
|
||||||
|
|
||||||
err := iamModelBuilder.BuildServiceAccountRoleTasks(serviceAccountRole, c)
|
err := iamModelBuilder.BuildServiceAccountRoleTasks(serviceAccountRole, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -362,7 +362,8 @@ func (c *NodeUpCommand) Run(out io.Writer) error {
|
||||||
klog.Exitf("error closing target: %v", err)
|
klog.Exitf("error closing target: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if modelContext.InstanceGroup.Spec.WarmPool.IsEnabled() && modelContext.InstanceGroup.Spec.WarmPool.EnableLifecycleHook {
|
warmPool := c.cluster.Spec.WarmPool.ResolveDefaults(modelContext.InstanceGroup)
|
||||||
|
if warmPool.IsEnabled() && warmPool.EnableLifecycleHook {
|
||||||
if api.CloudProviderID(c.cluster.Spec.CloudProvider) == api.CloudProviderAWS {
|
if api.CloudProviderID(c.cluster.Spec.CloudProvider) == api.CloudProviderAWS {
|
||||||
err := completeWarmingLifecycleAction(cloud.(awsup.AWSCloud), modelContext)
|
err := completeWarmingLifecycleAction(cloud.(awsup.AWSCloud), modelContext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue