Skip to content

Commit 7615a61

Browse files
Merge pull request #2547 from QiWang19/kubecfg-boot
[OCPNODE-673] Add kubeletconfig to bootstrap mode
2 parents 280d029 + a282006 commit 7615a61

7 files changed

Lines changed: 382 additions & 46 deletions

File tree

pkg/controller/bootstrap/bootstrap.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ func (b *Bootstrap) Run(destDir string) error {
7474

7575
var cconfig *mcfgv1.ControllerConfig
7676
var featureGate *apicfgv1.FeatureGate
77+
var kconfigs []*mcfgv1.KubeletConfig
7778
var pools []*mcfgv1.MachineConfigPool
7879
var configs []*mcfgv1.MachineConfig
7980
var icspRules []*apioperatorsv1alpha1.ImageContentSourcePolicy
@@ -112,6 +113,8 @@ func (b *Bootstrap) Run(destDir string) error {
112113
configs = append(configs, obj)
113114
case *mcfgv1.ControllerConfig:
114115
cconfig = obj
116+
case *mcfgv1.KubeletConfig:
117+
kconfigs = append(kconfigs, obj)
115118
case *apioperatorsv1alpha1.ImageContentSourcePolicy:
116119
icspRules = append(icspRules, obj)
117120
case *apicfgv1.Image:
@@ -139,6 +142,7 @@ func (b *Bootstrap) Run(destDir string) error {
139142
if err != nil {
140143
return err
141144
}
145+
142146
configs = append(configs, rconfigs...)
143147

144148
if featureGate != nil {
@@ -148,6 +152,13 @@ func (b *Bootstrap) Run(destDir string) error {
148152
}
149153
configs = append(configs, kConfigs...)
150154
}
155+
if len(kconfigs) > 0 {
156+
kconfigs, err := kubeletconfig.RunKubeletBootstrap(b.templatesDir, kconfigs, cconfig, featureGate, pools)
157+
if err != nil {
158+
return err
159+
}
160+
configs = append(configs, kconfigs...)
161+
}
151162

152163
fpools, gconfigs, err := render.RunBootstrap(pools, configs, cconfig)
153164
if err != nil {

pkg/controller/common/constants.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ const (
1919
// MCNameSuffixAnnotationKey is used to keep track of the machine config name associated with a CR
2020
MCNameSuffixAnnotationKey = "machineconfiguration.openshift.io/mc-name-suffix"
2121

22+
// MaxMCNameSuffix is the maximum value of the name suffix of the machine config associated with kubeletconfig and containerruntime objects
23+
MaxMCNameSuffix int = 9
24+
2225
// ClusterFeatureInstanceName is a singleton name for featureGate configuration
2326
ClusterFeatureInstanceName = "cluster"
2427
)

pkg/controller/kubelet-config/helpers.go

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strconv"
99

1010
ign3types "github.com/coreos/ignition/v2/config/v3_2/types"
11+
"github.com/imdario/mergo"
1112
osev1 "github.com/openshift/api/config/v1"
1213
"github.com/vincent-petithory/dataurl"
1314
corev1 "k8s.io/api/core/v1"
@@ -206,7 +207,7 @@ func getManagedKubeletConfigKey(pool *mcfgv1.MachineConfigPool, client mcfgclien
206207
// then if the user creates a kc-new it will map to mc-3. This is what we want as the latest kubelet config created should be higher in priority
207208
// so that those changes can be rolled out to the nodes. But users will have to be mindful of how many kubelet config CRs they create. Don't think
208209
// anyone should ever have the need to create 10 when they can simply update an existing kubelet config unless it is to apply to another pool.
209-
if suffixNum+1 > 9 {
210+
if suffixNum+1 > ctrlcommon.MaxMCNameSuffix {
210211
return "", fmt.Errorf("max number of supported kubelet config (10) has been reached. Please delete old kubelet configs before retrying")
211212
}
212213
// Return the default MC name with the suffixNum+1 value appended to it
@@ -336,3 +337,57 @@ func kubeletConfigToIgnFile(cfg *kubeletconfigv1beta1.KubeletConfiguration) (*ig
336337
cfgIgn := createNewKubeletIgnition(cfgJSON)
337338
return cfgIgn, nil
338339
}
340+
341+
// generateKubeletIgnFiles generates the Ignition files from the kubelet config
342+
func generateKubeletIgnFiles(kubeletConfig *mcfgv1.KubeletConfig, originalKubeConfig *kubeletconfigv1beta1.KubeletConfiguration, userDefinedSystemReserved map[string]string) (*ign3types.File, *ign3types.File, *ign3types.File, error) {
343+
var (
344+
kubeletIgnition *ign3types.File
345+
logLevelIgnition *ign3types.File
346+
autoSizingReservedIgnition *ign3types.File
347+
)
348+
349+
if kubeletConfig.Spec.KubeletConfig != nil && kubeletConfig.Spec.KubeletConfig.Raw != nil {
350+
specKubeletConfig, err := decodeKubeletConfig(kubeletConfig.Spec.KubeletConfig.Raw)
351+
if err != nil {
352+
return nil, nil, nil, fmt.Errorf("could not deserialize the new Kubelet config: %v", err)
353+
}
354+
355+
if val, ok := specKubeletConfig.SystemReserved["memory"]; ok {
356+
userDefinedSystemReserved["memory"] = val
357+
delete(specKubeletConfig.SystemReserved, "memory")
358+
}
359+
360+
if val, ok := specKubeletConfig.SystemReserved["cpu"]; ok {
361+
userDefinedSystemReserved["cpu"] = val
362+
delete(specKubeletConfig.SystemReserved, "cpu")
363+
}
364+
365+
// FeatureGates must be set from the FeatureGate.
366+
// Remove them here to prevent the specKubeletConfig merge overwriting them.
367+
specKubeletConfig.FeatureGates = nil
368+
369+
// Merge the Old and New
370+
err = mergo.Merge(originalKubeConfig, specKubeletConfig, mergo.WithOverride)
371+
if err != nil {
372+
return nil, nil, nil, fmt.Errorf("could not merge original config and new config: %v", err)
373+
}
374+
}
375+
376+
// Encode the new config into an Ignition File
377+
kubeletIgnition, err := kubeletConfigToIgnFile(originalKubeConfig)
378+
if err != nil {
379+
return nil, nil, nil, fmt.Errorf("could not encode JSON: %v", err)
380+
}
381+
382+
if kubeletConfig.Spec.LogLevel != nil {
383+
logLevelIgnition = createNewKubeletLogLevelIgnition(*kubeletConfig.Spec.LogLevel)
384+
}
385+
if kubeletConfig.Spec.AutoSizingReserved != nil && len(userDefinedSystemReserved) == 0 {
386+
autoSizingReservedIgnition = createNewKubeletDynamicSystemReservedIgnition(kubeletConfig.Spec.AutoSizingReserved, userDefinedSystemReserved)
387+
}
388+
if len(userDefinedSystemReserved) > 0 {
389+
autoSizingReservedIgnition = createNewKubeletDynamicSystemReservedIgnition(nil, userDefinedSystemReserved)
390+
}
391+
392+
return kubeletIgnition, logLevelIgnition, autoSizingReservedIgnition, nil
393+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package kubeletconfig
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
configv1 "github.com/openshift/api/config/v1"
8+
mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
9+
ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
10+
"github.com/openshift/machine-config-operator/pkg/version"
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
"k8s.io/apimachinery/pkg/labels"
13+
)
14+
15+
// RunKubeletBootstrap generates MachineConfig objects for mcpPools that would have been generated by syncKubeletConfig
16+
func RunKubeletBootstrap(templateDir string, kubeletConfigs []*mcfgv1.KubeletConfig, controllerConfig *mcfgv1.ControllerConfig, features *configv1.FeatureGate, mcpPools []*mcfgv1.MachineConfigPool) ([]*mcfgv1.MachineConfig, error) {
17+
var res []*mcfgv1.MachineConfig
18+
managedKeyExist := make(map[string]bool)
19+
userDefinedSystemReserved := make(map[string]string)
20+
// Validate the KubeletConfig CR if exists
21+
for _, kubeletConfig := range kubeletConfigs {
22+
if err := validateUserKubeletConfig(kubeletConfig); err != nil {
23+
return nil, err
24+
}
25+
}
26+
27+
for _, kubeletConfig := range kubeletConfigs {
28+
// use selector since label matching part of a KubeletConfig is not handled during the bootstrap
29+
selector, err := metav1.LabelSelectorAsSelector(kubeletConfig.Spec.MachineConfigPoolSelector)
30+
if err != nil {
31+
return nil, fmt.Errorf("invalid label selector: %v", err)
32+
}
33+
34+
for _, pool := range mcpPools {
35+
// If a pool with a nil or empty selector creeps in, it should match nothing, not everything.
36+
// skip the pool if no matched label for kubeletconfig
37+
if selector.Empty() || !selector.Matches(labels.Set(pool.Labels)) {
38+
continue
39+
}
40+
role := pool.Name
41+
42+
originalKubeConfig, err := generateOriginalKubeletConfigWithFeatureGates(controllerConfig, templateDir, role, features)
43+
if err != nil {
44+
return nil, err
45+
}
46+
if kubeletConfig.Spec.TLSSecurityProfile != nil {
47+
// Inject TLS Options from Spec
48+
observedMinTLSVersion, observedCipherSuites := getSecurityProfileCiphers(kubeletConfig.Spec.TLSSecurityProfile)
49+
originalKubeConfig.TLSMinVersion = observedMinTLSVersion
50+
originalKubeConfig.TLSCipherSuites = observedCipherSuites
51+
}
52+
53+
kubeletIgnition, logLevelIgnition, autoSizingReservedIgnition, err := generateKubeletIgnFiles(kubeletConfig, originalKubeConfig, userDefinedSystemReserved)
54+
if err != nil {
55+
return nil, err
56+
}
57+
58+
tempIgnConfig := ctrlcommon.NewIgnConfig()
59+
if autoSizingReservedIgnition != nil {
60+
tempIgnConfig.Storage.Files = append(tempIgnConfig.Storage.Files, *autoSizingReservedIgnition)
61+
}
62+
if logLevelIgnition != nil {
63+
tempIgnConfig.Storage.Files = append(tempIgnConfig.Storage.Files, *logLevelIgnition)
64+
}
65+
if kubeletIgnition != nil {
66+
tempIgnConfig.Storage.Files = append(tempIgnConfig.Storage.Files, *kubeletIgnition)
67+
}
68+
69+
rawIgn, err := json.Marshal(tempIgnConfig)
70+
if err != nil {
71+
return nil, err
72+
}
73+
managedKey, err := generateBootstrapManagedKeyKubelet(pool, managedKeyExist)
74+
if err != nil {
75+
return nil, err
76+
}
77+
ignConfig := ctrlcommon.NewIgnConfig()
78+
mc, err := ctrlcommon.MachineConfigFromIgnConfig(role, managedKey, ignConfig)
79+
if err != nil {
80+
return nil, fmt.Errorf("could not create MachineConfig from new Ignition config: %v", err)
81+
}
82+
mc.Spec.Config.Raw = rawIgn
83+
mc.SetAnnotations(map[string]string{
84+
ctrlcommon.GeneratedByControllerVersionAnnotationKey: version.Hash,
85+
})
86+
oref := metav1.OwnerReference{
87+
APIVersion: controllerKind.GroupVersion().String(),
88+
Kind: controllerKind.Kind,
89+
}
90+
mc.SetOwnerReferences([]metav1.OwnerReference{oref})
91+
res = append(res, mc)
92+
}
93+
}
94+
return res, nil
95+
}
96+
97+
// generateBootstrapManagedKeyKubelet generates the machine config name for a CR during bootstrap, returns error if there's more than 1 kubeletconfigs fir the same pool
98+
// Note: Only one kubeletconfig manifest per pool is allowed for bootstrap mode for the following reason:
99+
// if you provide multiple per pool, they would overwrite each other and not merge, potentially confusing customers post install;
100+
// we can simplify the logic for the bootstrap generation and avoid some edge cases.
101+
func generateBootstrapManagedKeyKubelet(pool *mcfgv1.MachineConfigPool, managedKeyExist map[string]bool) (string, error) {
102+
if _, ok := managedKeyExist[pool.Name]; ok {
103+
return "", fmt.Errorf("Error found multiple KubeletConfigs targeting MachineConfigPool %v. Please apply only one KubeletConfig manifest for each pool during installation", pool.Name)
104+
}
105+
managedKey, err := ctrlcommon.GetManagedKey(pool, nil, "99", "kubelet", "")
106+
if err != nil {
107+
return "", err
108+
}
109+
managedKeyExist[pool.Name] = true
110+
return managedKey, nil
111+
}

0 commit comments

Comments
 (0)