Honor OS update policy at InstanceGroup level too

As with the Cluster-level "spec.updatePolicy" field, add a similar
field at the InstanceGroup level, allowing overriding of the
cluster-level choice in each InstanceGroup.

Introduce a new value for the field ("automatic") as equivalent to the
default value applied when the field is absent. Honoring this new
value allows disabling automatic updates at the cluster level, but
then enabling them again for particular InstanceGroups. Without such a
positive affirmation, it's not possible to override a cluster-level
"external" policy at the InstanceGroup level, as there's no way to
specify positively that you want to recover the default
value. Instead, expressing the explicit "automatic" value is clear and
unambiguous.
This commit is contained in:
Steven E. Harris 2021-02-23 11:24:16 -05:00
parent 89fdd16c4d
commit e39c985ee7
15 changed files with 151 additions and 20 deletions

View File

@ -4070,10 +4070,10 @@ spec:
type: object
updatePolicy:
description: 'UpdatePolicy determines the policy for applying upgrades
automatically. Valid values: ''external'' do not apply updates
automatically - they are applied manually or by an external system missing:
default policy (currently OS security upgrades that do not require
a reboot)'
automatically. Valid values: ''automatic'' (default): apply updates
automatically (apply OS security upgrades, avoiding rebooting when
possible) ''external'': do not apply updates automatically; they
are applied manually or by an external system'
type: string
useHostCertificates:
description: UseHostCertificates will mount /etc/ssl/certs to inside

View File

@ -825,6 +825,13 @@ spec:
description: Describes the tenancy of the instance group. Can be either
default or dedicated. Currently only applies to AWS.
type: string
updatePolicy:
description: 'UpdatePolicy determines the policy for applying upgrades
automatically. Valid values: ''automatic'' (default): apply updates
automatically (apply OS security upgrades, avoiding rebooting when
possible) ''external'': do not apply updates automatically; they
are applied manually or by an external system'
type: string
volumeMounts:
description: VolumeMounts a collection of volume mounts
items:

View File

@ -49,8 +49,16 @@ func (b *UpdateServiceBuilder) Build(c *fi.ModelBuilderContext) error {
}
func (b *UpdateServiceBuilder) buildFlatcarSystemdService(c *fi.ModelBuilderContext) {
if b.Cluster.Spec.UpdatePolicy == nil || *b.Cluster.Spec.UpdatePolicy != kops.UpdatePolicyExternal {
klog.Infof("UpdatePolicy not set in Cluster Spec; skipping creation of %s", flatcarServiceName)
if b.InstanceGroup.Spec.UpdatePolicy != nil {
switch *b.InstanceGroup.Spec.UpdatePolicy {
case kops.UpdatePolicyAutomatic:
klog.Infof("UpdatePolicy set in InstanceGroup %q spec requests automatic updates; skipping creation of systemd unit %q", b.InstanceGroup.GetName(), flatcarServiceName)
return
case kops.UpdatePolicyExternal:
// Carry on with creating this systemd unit.
}
} else if fi.StringValue(b.Cluster.Spec.UpdatePolicy) != kops.UpdatePolicyExternal {
klog.Infof("UpdatePolicy in Cluster spec requests automatic updates; skipping creation of systemd unit %q", flatcarServiceName)
return
}
@ -85,8 +93,16 @@ func (b *UpdateServiceBuilder) buildFlatcarSystemdService(c *fi.ModelBuilderCont
}
func (b *UpdateServiceBuilder) buildDebianPackage(c *fi.ModelBuilderContext) {
if b.Cluster.Spec.UpdatePolicy != nil && *b.Cluster.Spec.UpdatePolicy == kops.UpdatePolicyExternal {
klog.Infof("UpdatePolicy is External; skipping installation of %s", debianPackageName)
if b.InstanceGroup.Spec.UpdatePolicy != nil {
switch *b.InstanceGroup.Spec.UpdatePolicy {
case kops.UpdatePolicyAutomatic:
klog.Infof("UpdatePolicy set in InstanceGroup %q spec requests automatic updates; skipping installation of packagk %q", b.InstanceGroup.GetName(), debianPackageName)
return
case kops.UpdatePolicyExternal:
// Carry on with creating this systemd unit.
}
} else if fi.StringValue(b.Cluster.Spec.UpdatePolicy) != kops.UpdatePolicyExternal {
klog.Infof("UpdatePolicy in Cluster spec requests automatic updates; skipping installation of package %q", debianPackageName)
return
}

View File

@ -133,8 +133,8 @@ type ClusterSpec struct {
IsolateMasters *bool `json:"isolateMasters,omitempty"`
// UpdatePolicy determines the policy for applying upgrades automatically.
// Valid values:
// 'external' do not apply updates automatically - they are applied manually or by an external system
// missing: default policy (currently OS security upgrades that do not require a reboot)
// 'automatic' (default): apply updates automatically (apply OS security upgrades, avoiding rebooting when possible)
// 'external': do not apply updates automatically; they are applied manually or by an external system
UpdatePolicy *string `json:"updatePolicy,omitempty"`
// ExternalPolicies allows the insertion of pre-existing managed policies on IG Roles
ExternalPolicies *map[string][]string `json:"externalPolicies,omitempty"`

View File

@ -176,6 +176,11 @@ type InstanceGroupSpec struct {
CompressUserData *bool `json:"compressUserData,omitempty"`
// InstanceMetadata defines the EC2 instance metadata service options (AWS Only)
InstanceMetadata *InstanceMetadataOptions `json:"instanceMetadata,omitempty"`
// UpdatePolicy determines the policy for applying upgrades automatically.
// Valid values:
// 'automatic' (default): apply updates automatically (apply OS security upgrades, avoiding rebooting when possible)
// 'external': do not apply updates automatically; they are applied manually or by an external system
UpdatePolicy *string `json:"updatePolicy,omitempty"`
}
const (
@ -190,7 +195,7 @@ const (
// SpotAllocationStrategies is a collection of supported strategies
var SpotAllocationStrategies = []string{SpotAllocationStrategyLowestPrices, SpotAllocationStrategyDiversified, SpotAllocationStrategyCapacityOptimized}
// InstanceMetadata defines the EC2 instance metadata service options (AWS Only)
// InstanceMetadataOptions defines the EC2 instance metadata service options (AWS Only)
type InstanceMetadataOptions struct {
// HTTPPutResponseHopLimit is the desired HTTP PUT response hop limit for instance metadata requests.
// The larger the number, the further instance metadata requests can travel. The default value is 1.

View File

@ -23,6 +23,9 @@ const (
// AnnotationValueManagementImported is the annotation value that indicates a cluster was imported, typically as part of an upgrade
AnnotationValueManagementImported = "imported"
// UpdatePolicyExternal is a value for ClusterSpec.UpdatePolicy indicating that upgrades are done externally, and we should disable automatic upgrades
// UpdatePolicyAutomatic is a value for ClusterSpec.UpdatePolicy and InstanceGroup.UpdatePolicy indicating that upgrades are performed automatically
UpdatePolicyAutomatic = "automatic"
// UpdatePolicyExternal is a value for ClusterSpec.UpdatePolicy and InstanceGroup.UpdatePolicy indicating that upgrades are done externally, and we should disable automatic upgrades
UpdatePolicyExternal = "external"
)

View File

@ -132,8 +132,8 @@ type ClusterSpec struct {
IsolateMasters *bool `json:"isolateMasters,omitempty"`
// UpdatePolicy determines the policy for applying upgrades automatically.
// Valid values:
// 'external' do not apply updates automatically - they are applied manually or by an external system
// missing: default policy (currently OS security upgrades that do not require a reboot)
// 'automatic' (default): apply updates automatically (apply OS security upgrades, avoiding rebooting when possible)
// 'external': do not apply updates automatically; they are applied manually or by an external system
UpdatePolicy *string `json:"updatePolicy,omitempty"`
// ExternalPolicies allows the insertion of pre-existing managed policies on IG Roles
ExternalPolicies *map[string][]string `json:"externalPolicies,omitempty"`

View File

@ -174,6 +174,11 @@ type InstanceGroupSpec struct {
CompressUserData *bool `json:"compressUserData,omitempty"`
// InstanceMetadata defines the EC2 instance metadata service options (AWS Only)
InstanceMetadata *InstanceMetadataOptions `json:"instanceMetadata,omitempty"`
// UpdatePolicy determines the policy for applying upgrades automatically.
// Valid values:
// 'automatic' (default): apply updates automatically (apply OS security upgrades, avoiding rebooting when possible)
// 'external': do not apply updates automatically; they are applied manually or by an external system
UpdatePolicy *string `json:"updatePolicy,omitempty"`
}
const (
@ -188,7 +193,7 @@ const (
// SpotAllocationStrategies is a collection of supported strategies
var SpotAllocationStrategies = []string{SpotAllocationStrategyLowestPrices, SpotAllocationStrategyDiversified, SpotAllocationStrategyCapacityOptimized}
// InstanceMetadata defines the EC2 instance metadata service options (AWS Only)
// InstanceMetadataOptions defines the EC2 instance metadata service options (AWS Only)
type InstanceMetadataOptions struct {
// HTTPPutResponseHopLimit is the desired HTTP PUT response hop limit for instance metadata requests.
// The larger the number, the further instance metadata requests can travel. The default value is 1.

View File

@ -3952,6 +3952,7 @@ func autoConvert_v1alpha2_InstanceGroupSpec_To_kops_InstanceGroupSpec(in *Instan
} else {
out.InstanceMetadata = nil
}
out.UpdatePolicy = in.UpdatePolicy
return nil
}
@ -4104,6 +4105,7 @@ func autoConvert_kops_InstanceGroupSpec_To_v1alpha2_InstanceGroupSpec(in *kops.I
} else {
out.InstanceMetadata = nil
}
out.UpdatePolicy = in.UpdatePolicy
return nil
}

View File

@ -2174,6 +2174,11 @@ func (in *InstanceGroupSpec) DeepCopyInto(out *InstanceGroupSpec) {
*out = new(InstanceMetadataOptions)
(*in).DeepCopyInto(*out)
}
if in.UpdatePolicy != nil {
in, out := &in.UpdatePolicy, &out.UpdatePolicy
*out = new(string)
**out = **in
}
return
}

View File

@ -140,6 +140,8 @@ func ValidateInstanceGroup(g *kops.InstanceGroup, cloud fi.Cloud) field.ErrorLis
allErrs = append(allErrs, validateExternalLoadBalancer(&lb, path)...)
}
allErrs = append(allErrs, IsValidValue(field.NewPath("spec", "updatePolicy"), g.Spec.UpdatePolicy, []string{kops.UpdatePolicyAutomatic, kops.UpdatePolicyExternal})...)
return allErrs
}

View File

@ -338,3 +338,49 @@ func TestIGCloudLabelIsIGName(t *testing.T) {
testErrors(t, g.label, errs, g.expected)
}
}
func TestIGUpdatePolicy(t *testing.T) {
const unsupportedValueError = "Unsupported value::spec.updatePolicy"
for _, test := range []struct {
label string
policy *string
expected []string
}{
{
label: "missing",
},
{
label: "automatic",
policy: fi.String(kops.UpdatePolicyAutomatic),
},
{
label: "external",
policy: fi.String(kops.UpdatePolicyExternal),
},
{
label: "empty",
policy: fi.String(""),
expected: []string{unsupportedValueError},
},
{
label: "unknown",
policy: fi.String("something-else"),
expected: []string{unsupportedValueError},
},
} {
ig := kops.InstanceGroup{
ObjectMeta: v1.ObjectMeta{
Name: "some-ig",
},
Spec: kops.InstanceGroupSpec{
Role: "Node",
CloudLabels: make(map[string]string),
},
}
t.Run(test.label, func(t *testing.T) {
ig.Spec.UpdatePolicy = test.policy
errs := ValidateInstanceGroup(&ig, nil)
testErrors(t, test.label, errs, test.expected)
})
}
}

View File

@ -105,7 +105,7 @@ func validateClusterSpec(spec *kops.ClusterSpec, c *kops.Cluster, fieldPath *fie
}
// UpdatePolicy
allErrs = append(allErrs, IsValidValue(fieldPath.Child("updatePolicy"), spec.UpdatePolicy, []string{kops.UpdatePolicyExternal})...)
allErrs = append(allErrs, IsValidValue(fieldPath.Child("updatePolicy"), spec.UpdatePolicy, []string{kops.UpdatePolicyAutomatic, kops.UpdatePolicyExternal})...)
// Hooks
for i := range spec.Hooks {

View File

@ -2340,6 +2340,11 @@ func (in *InstanceGroupSpec) DeepCopyInto(out *InstanceGroupSpec) {
*out = new(InstanceMetadataOptions)
(*in).DeepCopyInto(*out)
}
if in.UpdatePolicy != nil {
in, out := &in.UpdatePolicy, &out.UpdatePolicy
*out = new(string)
**out = **in
}
return
}

View File

@ -112,14 +112,49 @@ func TestValidateFull_ClusterName_Required(t *testing.T) {
func TestValidateFull_UpdatePolicy_Valid(t *testing.T) {
c := buildDefaultCluster(t)
c.Spec.UpdatePolicy = fi.String(api.UpdatePolicyExternal)
expectNoErrorFromValidate(t, c)
for _, test := range []struct {
label string
policy *string
}{
{
label: "missing",
},
{
label: "automatic",
policy: fi.String(api.UpdatePolicyAutomatic),
},
{
label: "external",
policy: fi.String(api.UpdatePolicyExternal),
},
} {
t.Run(test.label, func(t *testing.T) {
c.Spec.UpdatePolicy = test.policy
expectNoErrorFromValidate(t, c)
})
}
}
func TestValidateFull_UpdatePolicy_Invalid(t *testing.T) {
c := buildDefaultCluster(t)
c.Spec.UpdatePolicy = fi.String("not-a-real-value")
expectErrorFromValidate(t, c, "spec.updatePolicy")
for _, test := range []struct {
label string
policy string
}{
{
label: "empty",
policy: "",
},
{
label: "populated",
policy: "not-a-real-value",
},
} {
t.Run(test.label, func(t *testing.T) {
c.Spec.UpdatePolicy = &test.policy
expectErrorFromValidate(t, c, "spec.updatePolicy")
})
}
}
func Test_Validate_Kubenet_With_14(t *testing.T) {