Skip to content

Commit b3e3c8a

Browse files
Merge pull request #5382 from dkhater-redhat/mco-1940-safer-layered-image-serving
MCO-1940: Enhance MCS layered image serving safety during node scale-up by requiring node validation
2 parents 661e30e + f9536e2 commit b3e3c8a

File tree

5 files changed

+690
-79
lines changed

5 files changed

+690
-79
lines changed

pkg/server/bootstrap_server.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,11 @@ func (bsc *bootstrapServer) GetConfig(cr poolRequest) (*runtime.RawExtension, er
134134

135135
addDataAndMaybeAppendToIgnition(caBundleFilePath, cc.Spec.KubeAPIServerServingCAData, &ignConf)
136136
addDataAndMaybeAppendToIgnition(cloudProviderCAPath, cc.Spec.CloudProviderCAData, &ignConf)
137-
appenders := getAppenders(currConf, nil, bsc.kubeconfigFunc, bsc.certs, bsc.serverBaseDir)
137+
138+
appenders := newAppendersBuilder(nil, bsc.kubeconfigFunc, bsc.certs, bsc.serverBaseDir).
139+
WithNodeAnnotations(currConf, "").
140+
build()
141+
138142
for _, a := range appenders {
139143
if err := a(&ignConf, mc); err != nil {
140144
return nil, err

pkg/server/cluster_server.go

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -185,24 +185,11 @@ func (cs *clusterServer) GetConfig(cr poolRequest) (*runtime.RawExtension, error
185185
addDataAndMaybeAppendToIgnition(cloudProviderCAPath, cc.Spec.CloudProviderCAData, &ignConf)
186186

187187
desiredImage := cs.resolveDesiredImageForPool(mp)
188-
klog.Infof("desiredImage is found to be %s", desiredImage)
189188

190-
appenders := []appenderFunc{
191-
func(cfg *ign3types.Config, _ *mcfgv1.MachineConfig) error {
192-
return appendNodeAnnotations(cfg, currConf, desiredImage)
193-
},
194-
func(cfg *ign3types.Config, _ *mcfgv1.MachineConfig) error {
195-
return appendKubeConfig(cfg, cs.kubeconfigFunc)
196-
},
197-
appendInitialMachineConfig,
198-
func(cfg *ign3types.Config, _ *mcfgv1.MachineConfig) error { return appendCerts(cfg, []string{}, "") },
199-
// Inject desired image into the MC
200-
appendDesiredOSImage(desiredImage),
201-
// Must be last
202-
func(cfg *ign3types.Config, mc *mcfgv1.MachineConfig) error {
203-
return appendEncapsulated(cfg, mc, cr.version)
204-
},
205-
}
189+
appenders := newAppendersBuilder(cr.version, cs.kubeconfigFunc, []string{}, "").
190+
WithNodeAnnotations(currConf, desiredImage).
191+
WithCustomAppender(appendDesiredOSImage(desiredImage)).
192+
build()
206193

207194
for _, a := range appenders {
208195
if err := a(&ignConf, mc); err != nil {
@@ -308,20 +295,6 @@ func (cs *clusterServer) resolveDesiredImageForPool(pool *mcfgv1.MachineConfigPo
308295
return ""
309296
}
310297

311-
// Also verify the corresponding MOSB is successful to ensure we don't serve an image that hasn't been built yet
312-
//
313-
// Note: We cannot directly use the MOSB reference from mosc.Status.MachineOSBuild here because
314-
// there can be multiple MOSBs for a single MOSC (one per rendered MachineConfig). The MOSC status
315-
// points to the latest successful build, but we need the MOSB matching the pool's rollout state.
316-
// We use pool.Status.Configuration.Name (old config) when UpdatedMachineCount == 0, and
317-
// pool.Spec.Configuration.Name (new config) when UpdatedMachineCount > 0. This matches the existing
318-
// MCS behavior for non-layered nodes and ensures we serve the correct image during rollouts.
319-
//
320-
// TODO(dkhater): For added safety during node scale-up, we should only serve the new build if it's
321-
// both successful AND at least one node has already updated to it. This would more closely match
322-
// the existing MCS rollout behavior and provide better safety, though it would result in additional
323-
// reboots when scaling nodes during an update.
324-
// See https://issues.redhat.com/browse/MCO-1940 for tracking this improvement.
325298
mosbList, err := cs.machineOSBuildLister.List(labels.Everything())
326299
if err != nil {
327300
klog.Infof("Could not list MachineOSBuilds for pool %s: %v", pool.Name, err)
@@ -356,6 +329,12 @@ func (cs *clusterServer) resolveDesiredImageForPool(pool *mcfgv1.MachineConfigPo
356329
return ""
357330
}
358331

332+
// Only serve layered images if at least one node has completed the update.
333+
if pool.Status.UpdatedMachineCount == 0 {
334+
klog.Infof("Pool %s has successful MOSB %s but no nodes have completed update yet (UpdatedMachineCount=0), will not serve layered image during bootstrap", pool.Name, mosb.Name)
335+
return ""
336+
}
337+
359338
imageSpec := moscState.GetOSImage()
360339

361340
// Don't serve internal registry images during bootstrap because cluster DNS is not available yet

pkg/server/server.go

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,23 +46,72 @@ type Server interface {
4646
GetConfig(poolRequest) (*runtime.RawExtension, error)
4747
}
4848

49-
func getAppenders(currMachineConfig string, version *semver.Version, f kubeconfigFunc, certs []string, serverDir string) []appenderFunc {
50-
appenders := []appenderFunc{
51-
// append machine annotations file.
49+
// appendersBuilder builds a slice of appenderFunc with guaranteed ordering.
50+
// The builder ensures that appendEncapsulated is always added last.
51+
type appendersBuilder struct {
52+
version *semver.Version
53+
kubeconfigFn kubeconfigFunc
54+
certs []string
55+
serverDir string
56+
customAppenders []appenderFunc
57+
}
58+
59+
// newAppendersBuilder creates a new appendersBuilder.
60+
// Common appenders (kubeconfig, initial machine config, certs) are added automatically in build().
61+
func newAppendersBuilder(version *semver.Version, kubeconfigFn kubeconfigFunc, certs []string, serverDir string) *appendersBuilder {
62+
return &appendersBuilder{
63+
version: version,
64+
kubeconfigFn: kubeconfigFn,
65+
certs: certs,
66+
serverDir: serverDir,
67+
customAppenders: make([]appenderFunc, 0),
68+
}
69+
}
70+
71+
// WithNodeAnnotations adds the node annotations appender with the specified config and image.
72+
func (ab *appendersBuilder) WithNodeAnnotations(currMachineConfig, image string) *appendersBuilder {
73+
ab.customAppenders = append(ab.customAppenders, func(cfg *ign3types.Config, _ *mcfgv1.MachineConfig) error {
74+
return appendNodeAnnotations(cfg, currMachineConfig, image)
75+
})
76+
return ab
77+
}
78+
79+
// WithCustomAppender adds a custom appender function.
80+
// Custom appenders are inserted before the common appenders and appendEncapsulated.
81+
func (ab *appendersBuilder) WithCustomAppender(appender appenderFunc) *appendersBuilder {
82+
ab.customAppenders = append(ab.customAppenders, appender)
83+
return ab
84+
}
85+
86+
// build creates the final slice of appenderFunc with the correct ordering:
87+
// 1. Custom appenders (node annotations, desired image, etc.)
88+
// 2. Common appenders (kubeconfig, initial machine config, certs)
89+
// 3. appendEncapsulated (always last)
90+
func (ab *appendersBuilder) build() []appenderFunc {
91+
result := make([]appenderFunc, 0, len(ab.customAppenders)+4)
92+
93+
// Add custom appenders first
94+
result = append(result, ab.customAppenders...)
95+
96+
// Add common appenders and appendEncapsulated
97+
result = append(result,
98+
// append kubeconfig
5299
func(cfg *ign3types.Config, _ *mcfgv1.MachineConfig) error {
53-
return appendNodeAnnotations(cfg, currMachineConfig, "")
100+
return appendKubeConfig(cfg, ab.kubeconfigFn)
54101
},
55-
// append kubeconfig.
56-
func(cfg *ign3types.Config, _ *mcfgv1.MachineConfig) error { return appendKubeConfig(cfg, f) },
57102
// append the machineconfig content
58103
appendInitialMachineConfig,
59-
func(cfg *ign3types.Config, _ *mcfgv1.MachineConfig) error { return appendCerts(cfg, certs, serverDir) },
60-
// This has to come last!!!
104+
// append certs
105+
func(cfg *ign3types.Config, _ *mcfgv1.MachineConfig) error {
106+
return appendCerts(cfg, ab.certs, ab.serverDir)
107+
},
108+
// appendEncapsulated must always be last
61109
func(cfg *ign3types.Config, mc *mcfgv1.MachineConfig) error {
62-
return appendEncapsulated(cfg, mc, version)
110+
return appendEncapsulated(cfg, mc, ab.version)
63111
},
64-
}
65-
return appenders
112+
)
113+
114+
return result
66115
}
67116

68117
func appendCerts(cfg *ign3types.Config, certs []string, serverDir string) error {

0 commit comments

Comments
 (0)