Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/KubeletConfigDesign.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,17 @@ kind: KubeletConfig
metadata:
name: set-max-pods
spec:
logLevel: 5
machineConfigPoolSelector:
matchLabels:
pools.operator.machineconfiguration.openshift.io/worker: ""
kubeletConfig:
maxPods: 100
```

The logLevel attribute is optional and will default to level 2. Increasing the logLevel
uses more IO, CPU, and resources on all the machines within the pool.

Save your `kubeletconfig` locally, for example as maxpods.yaml

The label in the above example corresponds to the worker MachineConfigPool. By default the master/worker
Expand All @@ -88,6 +92,7 @@ kind: KubeletConfig
metadata:
name: set-max-pods
spec:
logLevel: 5
machineConfigPoolSelector:
matchLabels:
custom-kubelet: small-pods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ spec:
description: KubeletConfigSpec defines the desired state of KubeletConfig
type: object
properties:
logLevel:
description: logLevel defines the log level of the Kubelet
type: integer
format: int64
minimum: 1
maximum: 10
kubeletConfig:
type: object
x-kubernetes-preserve-unknown-fields: true
Expand Down
1 change: 1 addition & 0 deletions pkg/apis/machineconfiguration.openshift.io/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ type KubeletConfig struct {

// KubeletConfigSpec defines the desired state of KubeletConfig
type KubeletConfigSpec struct {
LogLevel *int32 `json:"logLevel,omitempty"`
MachineConfigPoolSelector *metav1.LabelSelector `json:"machineConfigPoolSelector,omitempty"`
KubeletConfig *runtime.RawExtension `json:"kubeletConfig,omitempty"`
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 30 additions & 6 deletions pkg/controller/kubelet-config/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,30 @@ import (
mcfgclientset "github.com/openshift/machine-config-operator/pkg/generated/clientset/versioned"
)

func createNewKubeletIgnition(jsonConfig []byte) ign3types.Config {
func createNewKubeletLogLevelIgnition(level int32) *ign3types.File {
config := fmt.Sprintf("[Service]\nEnvironment=\"KUBELET_LOG_LEVEL=%d\"\n", level)

mode := 0644
overwrite := true
du := dataurl.New([]byte(config), "text/plain")
du.Encoding = dataurl.EncodingASCII
duStr := du.String()

return &ign3types.File{
Node: ign3types.Node{
Path: "/etc/systemd/system/kubelet.service.d/20-logging.conf",
Overwrite: &overwrite,
},
FileEmbedded1: ign3types.FileEmbedded1{
Mode: &mode,
Contents: ign3types.Resource{
Source: &(duStr),
},
},
}
}

func createNewKubeletIgnition(jsonConfig []byte) *ign3types.File {
// Want the kubelet.conf file to have the pretty JSON formatting
buf := new(bytes.Buffer)
json.Indent(buf, jsonConfig, "", " ")
Expand All @@ -32,7 +55,8 @@ func createNewKubeletIgnition(jsonConfig []byte) ign3types.Config {
du := dataurl.New(buf.Bytes(), "text/plain")
du.Encoding = dataurl.EncodingASCII
duStr := du.String()
tempFile := ign3types.File{

return &ign3types.File{
Node: ign3types.Node{
Path: "/etc/kubernetes/kubelet.conf",
Overwrite: &overwrite,
Expand All @@ -44,9 +68,6 @@ func createNewKubeletIgnition(jsonConfig []byte) ign3types.Config {
},
},
}
tempIgnConfig := ctrlcommon.NewIgnConfig()
tempIgnConfig.Storage.Files = append(tempIgnConfig.Storage.Files, tempFile)
return tempIgnConfig
}

func createNewDefaultFeatureGate() *osev1.FeatureGate {
Expand Down Expand Up @@ -93,7 +114,10 @@ func getManagedKubeletConfigKeyDeprecated(pool *mcfgv1.MachineConfigPool) string
// validates a KubeletConfig and returns an error if invalid
// nolint:gocyclo
func validateUserKubeletConfig(cfg *mcfgv1.KubeletConfig) error {
if cfg.Spec.KubeletConfig.Raw == nil {
if cfg.Spec.LogLevel != nil && (*cfg.Spec.LogLevel < 1 || *cfg.Spec.LogLevel > 10) {
return fmt.Errorf("KubeletConfig's LogLevel is not valid [1,10]: %v", cfg.Spec.LogLevel)
}
if cfg.Spec.KubeletConfig == nil || cfg.Spec.KubeletConfig.Raw == nil {
return nil
}
kcDecoded, err := decodeKubeletConfig(cfg.Spec.KubeletConfig.Raw)
Expand Down
92 changes: 56 additions & 36 deletions pkg/controller/kubelet-config/kubelet_config_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,41 +444,49 @@ func (ctrl *Controller) syncKubeletConfig(key string) error {
return ctrl.syncStatusOnly(cfg, err, "could not find MachineConfig: %v", managedKey)
}
isNotFound := macherrors.IsNotFound(err)
// Generate the original KubeletConfig
originalKubeletIgn, err := ctrl.generateOriginalKubeletConfig(role)
if err != nil {
return ctrl.syncStatusOnly(cfg, err, "could not generate the original Kubelet config: %v", err)
}
if originalKubeletIgn.Contents.Source == nil {
return ctrl.syncStatusOnly(cfg, err, "the original Kubelet source string is empty: %v", err)
}
dataURL, err := dataurl.DecodeString(*originalKubeletIgn.Contents.Source)
if err != nil {
return ctrl.syncStatusOnly(cfg, err, "could not decode the original Kubelet source string: %v", err)
}
originalKubeConfig, err := decodeKubeletConfig(dataURL.Data)
if err != nil {
return ctrl.syncStatusOnly(cfg, err, "could not deserialize the Kubelet source: %v", err)
}
specKubeletConfig, err := decodeKubeletConfig(cfg.Spec.KubeletConfig.Raw)
if err != nil {
return ctrl.syncStatusOnly(cfg, err, "could not deserialize the new Kubelet config: %v", err)
}
// Merge the Old and New
err = mergo.Merge(originalKubeConfig, specKubeletConfig, mergo.WithOverride)
if err != nil {
return ctrl.syncStatusOnly(cfg, err, "could not merge original config and new config: %v", err)
}
// Merge in Feature Gates
err = mergo.Merge(&originalKubeConfig.FeatureGates, featureGates, mergo.WithOverride)
if err != nil {
return ctrl.syncStatusOnly(cfg, err, "could not merge FeatureGates: %v", err)
}
// Encode the new config into raw JSON
cfgJSON, err := encodeKubeletConfig(originalKubeConfig, kubeletconfigv1beta1.SchemeGroupVersion)
if err != nil {
return ctrl.syncStatusOnly(cfg, err, "could not encode JSON: %v", err)

var kubeletIgnition *ign3types.File
var logLevelIgnition *ign3types.File

if cfg.Spec.KubeletConfig != nil && cfg.Spec.KubeletConfig.Raw != nil {
// Generate the original KubeletConfig
originalKubeletIgn, err := ctrl.generateOriginalKubeletConfig(role)
if err != nil {
return ctrl.syncStatusOnly(cfg, err, "could not generate the original Kubelet config: %v", err)
}
if originalKubeletIgn.Contents.Source == nil {
return ctrl.syncStatusOnly(cfg, err, "the original Kubelet source string is empty: %v", err)
}
dataURL, err := dataurl.DecodeString(*originalKubeletIgn.Contents.Source)
if err != nil {
return ctrl.syncStatusOnly(cfg, err, "could not decode the original Kubelet source string: %v", err)
}
originalKubeConfig, err := decodeKubeletConfig(dataURL.Data)
if err != nil {
return ctrl.syncStatusOnly(cfg, err, "could not deserialize the Kubelet source: %v", err)
}
specKubeletConfig, err := decodeKubeletConfig(cfg.Spec.KubeletConfig.Raw)
if err != nil {
return ctrl.syncStatusOnly(cfg, err, "could not deserialize the new Kubelet config: %v", err)
}
// Merge the Old and New
err = mergo.Merge(originalKubeConfig, specKubeletConfig, mergo.WithOverride)
if err != nil {
return ctrl.syncStatusOnly(cfg, err, "could not merge original config and new config: %v", err)
}
// Merge in Feature Gates
err = mergo.Merge(&originalKubeConfig.FeatureGates, featureGates, mergo.WithOverride)
if err != nil {
return ctrl.syncStatusOnly(cfg, err, "could not merge FeatureGates: %v", err)
}
// Encode the new config into raw JSON
cfgJSON, err := encodeKubeletConfig(originalKubeConfig, kubeletconfigv1beta1.SchemeGroupVersion)
if err != nil {
return ctrl.syncStatusOnly(cfg, err, "could not encode JSON: %v", err)
}
kubeletIgnition = createNewKubeletIgnition(cfgJSON)
}

if isNotFound {
ignConfig := ctrlcommon.NewIgnConfig()
mc, err = ctrlcommon.MachineConfigFromIgnConfig(role, managedKey, ignConfig)
Expand All @@ -487,8 +495,20 @@ func (ctrl *Controller) syncKubeletConfig(key string) error {
}
mc.ObjectMeta.UID = uuid.NewUUID()
}
cfgIgn := createNewKubeletIgnition(cfgJSON)
rawIgn, err := json.Marshal(cfgIgn)

if cfg.Spec.LogLevel != nil {
logLevelIgnition = createNewKubeletLogLevelIgnition(*cfg.Spec.LogLevel)
}

tempIgnConfig := ctrlcommon.NewIgnConfig()
if logLevelIgnition != nil {
tempIgnConfig.Storage.Files = append(tempIgnConfig.Storage.Files, *logLevelIgnition)
}
if kubeletIgnition != nil {
tempIgnConfig.Storage.Files = append(tempIgnConfig.Storage.Files, *kubeletIgnition)
}

rawIgn, err := json.Marshal(tempIgnConfig)
if err != nil {
return ctrl.syncStatusOnly(cfg, err, "could not marshal kubelet config Ignition: %v", err)
}
Expand Down
42 changes: 42 additions & 0 deletions pkg/controller/kubelet-config/kubelet_config_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1"
"k8s.io/utils/pointer"

mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
Expand Down Expand Up @@ -123,6 +124,7 @@ func newKubeletConfig(name string, kubeconf *kubeletconfigv1beta1.KubeletConfigu
TypeMeta: metav1.TypeMeta{APIVersion: mcfgv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{Name: name, UID: types.UID(utilrand.String(5)), Generation: 1},
Spec: mcfgv1.KubeletConfigSpec{
LogLevel: pointer.Int32Ptr(2),
KubeletConfig: &runtime.RawExtension{
Raw: kcRaw,
},
Expand Down Expand Up @@ -343,6 +345,46 @@ func TestKubeletConfigCreate(t *testing.T) {
}
}

func TestKubeletConfigLogFile(t *testing.T) {
for _, platform := range []osev1.PlatformType{osev1.AWSPlatformType, osev1.NonePlatformType, "unrecognized"} {
t.Run(string(platform), func(t *testing.T) {
f := newFixture(t)

cc := newControllerConfig(ctrlcommon.ControllerConfigName, platform)
mcp := helpers.NewMachineConfigPool("master", nil, helpers.MasterSelector, "v0")
mcp2 := helpers.NewMachineConfigPool("worker", nil, helpers.WorkerSelector, "v0")
kc1 := &mcfgv1.KubeletConfig{
TypeMeta: metav1.TypeMeta{APIVersion: mcfgv1.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{Name: "kubulet-log", UID: types.UID(utilrand.String(5)), Generation: 1},
Spec: mcfgv1.KubeletConfigSpec{
LogLevel: pointer.Int32Ptr(5),
MachineConfigPoolSelector: metav1.AddLabelToSelector(&metav1.LabelSelector{}, "pools.operator.machineconfiguration.openshift.io/master", ""),
},
Status: mcfgv1.KubeletConfigStatus{},
}
kubeletConfigKey, _ := getManagedKubeletConfigKey(mcp, nil)
mcs := helpers.NewMachineConfig(kubeletConfigKey, map[string]string{"node-role/master": ""}, "dummy://", []ign3types.File{{}})
mcsDeprecated := mcs.DeepCopy()
mcsDeprecated.Name = getManagedKubeletConfigKeyDeprecated(mcp)

f.ccLister = append(f.ccLister, cc)
f.mcpLister = append(f.mcpLister, mcp)
f.mcpLister = append(f.mcpLister, mcp2)
f.mckLister = append(f.mckLister, kc1)
f.objects = append(f.objects, kc1)

f.expectGetMachineConfigAction(mcs)
f.expectGetMachineConfigAction(mcsDeprecated)
f.expectGetMachineConfigAction(mcs)
f.expectCreateMachineConfigAction(mcs)
f.expectPatchKubeletConfig(kc1, []uint8{0x7b, 0x22, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x3a, 0x7b, 0x22, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x39, 0x39, 0x2d, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2d, 0x68, 0x35, 0x35, 0x32, 0x6d, 0x2d, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x2d, 0x6d, 0x61, 0x78, 0x2d, 0x70, 0x6f, 0x64, 0x73, 0x2d, 0x6b, 0x75, 0x62, 0x65, 0x6c, 0x65, 0x74, 0x22, 0x5d, 0x7d, 0x7d})
f.expectUpdateKubeletConfig(kc1)

f.run(getKey(kc1, t))
})
}
}

func TestKubeletConfigUpdates(t *testing.T) {
for _, platform := range []osev1.PlatformType{osev1.AWSPlatformType, osev1.NonePlatformType, "unrecognized"} {
t.Run(string(platform), func(t *testing.T) {
Expand Down
6 changes: 6 additions & 0 deletions templates/common/_base/files/kubelet-log-level.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
mode: 0644
path: "/etc/systemd/system/kubelet.service.d/20-logging.conf"
contents:
inline: |
[Service]
Environment="KUBELET_LOG_LEVEL=2"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i thought level 2 was not sufficient for debugging?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem is that level 3 is logging dead pod logs into the kubelet log. Instead of chasing all level 3 logs... I'm proposing just setting it to level 2. This new approach with a kubelog.conf would allow us to manage the kubelet log level within the kubeletconfig perhaps.

Log Line

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to changing the log line to be 4, but it seems like whack-a-mole.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's consensus that level work works between the experts (ie you and @sjenning) I'm ok with it, just want to make sure this isn't going to hurt us later.

@sjenning wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kikisdeliveryservice I'm on board with this

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ contents: |
Type=notify
ExecStartPre=/bin/mkdir --parents /etc/kubernetes/manifests
ExecStartPre=/bin/rm -f /var/lib/kubelet/cpu_manager_state
Environment="KUBELET_LOG_LEVEL=3"
{{- if .KubeletIPv6}}
Environment="KUBELET_NODE_IP=::"
{{- end}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ contents: |
Type=notify
ExecStartPre=/bin/mkdir --parents /etc/kubernetes/manifests
ExecStartPre=/bin/rm -f /var/lib/kubelet/cpu_manager_state
Environment="KUBELET_LOG_LEVEL=4"
EnvironmentFile=/etc/os-release
EnvironmentFile=-/etc/kubernetes/kubelet-workaround
EnvironmentFile=-/etc/kubernetes/kubelet-env
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ contents: |
Type=notify
ExecStartPre=/bin/mkdir --parents /etc/kubernetes/manifests
ExecStartPre=/bin/rm -f /var/lib/kubelet/cpu_manager_state
Environment="KUBELET_LOG_LEVEL=3"
{{- if .KubeletIPv6}}
Environment="KUBELET_NODE_IP=::"
{{- end}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ contents: |
Type=notify
ExecStartPre=/bin/mkdir --parents /etc/kubernetes/manifests
ExecStartPre=/bin/rm -f /var/lib/kubelet/cpu_manager_state
Environment="KUBELET_LOG_LEVEL=4"
EnvironmentFile=/etc/os-release
EnvironmentFile=-/etc/kubernetes/kubelet-workaround
EnvironmentFile=-/etc/kubernetes/kubelet-env
Expand Down