Skip to content

Commit 983dd07

Browse files
authored
Merge pull request kubernetes#128031 from HirazawaUi/kep-4656
[Kubeadm] KEP-4656: Add kubelet instance configuration to configure CRI socket for each node
2 parents 96250d4 + d3ea4d3 commit 983dd07

File tree

16 files changed

+439
-49
lines changed

16 files changed

+439
-49
lines changed

cmd/kubeadm/app/cmd/phases/init/kubelet.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ import (
2323

2424
"k8s.io/klog/v2"
2525

26+
kubeletconfig "k8s.io/kubelet/config/v1beta1"
2627
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
2728
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
2829
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
30+
"k8s.io/kubernetes/cmd/kubeadm/app/features"
2931
kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet"
3032
)
3133

@@ -76,6 +78,16 @@ func runKubeletStart(c workflow.RunData) error {
7678
return errors.Wrap(err, "error writing a dynamic environment file for the kubelet")
7779
}
7880

81+
// Write the instance kubelet configuration file to disk.
82+
if features.Enabled(data.Cfg().FeatureGates, features.NodeLocalCRISocket) {
83+
kubeletConfig := &kubeletconfig.KubeletConfiguration{
84+
ContainerRuntimeEndpoint: data.Cfg().NodeRegistration.CRISocket,
85+
}
86+
if err := kubeletphase.WriteInstanceConfigToDisk(kubeletConfig, data.KubeletDir()); err != nil {
87+
return errors.Wrap(err, "error writing instance kubelet configuration to disk")
88+
}
89+
}
90+
7991
// Write the kubelet configuration file to disk.
8092
if err := kubeletphase.WriteConfigToDisk(&data.Cfg().ClusterConfiguration, data.KubeletDir(), data.PatchesDir(), data.OutputWriter()); err != nil {
8193
return errors.Wrap(err, "error writing kubelet configuration to disk")

cmd/kubeadm/app/cmd/phases/init/uploadconfig.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
3131
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
3232
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
33+
"k8s.io/kubernetes/cmd/kubeadm/app/features"
3334
kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet"
3435
patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode"
3536
"k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig"
@@ -127,9 +128,11 @@ func runUploadKubeletConfig(c workflow.RunData) error {
127128
return errors.Wrap(err, "error creating kubelet configuration ConfigMap")
128129
}
129130

130-
klog.V(1).Infoln("[upload-config] Preserving the CRISocket information for the control-plane node")
131-
if err := patchnodephase.AnnotateCRISocket(client, cfg.NodeRegistration.Name, cfg.NodeRegistration.CRISocket); err != nil {
132-
return errors.Wrap(err, "Error writing Crisocket information for the control-plane node")
131+
if !features.Enabled(cfg.FeatureGates, features.NodeLocalCRISocket) {
132+
klog.V(1).Infoln("[upload-config] Preserving the CRISocket information for the control-plane node")
133+
if err := patchnodephase.AnnotateCRISocket(client, cfg.NodeRegistration.Name, cfg.NodeRegistration.CRISocket); err != nil {
134+
return errors.Wrap(err, "error writing CRISocket for this node")
135+
}
133136
}
134137
return nil
135138
}

cmd/kubeadm/app/cmd/phases/join/kubelet.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,16 @@ func runKubeletStartJoinPhase(c workflow.RunData) (returnErr error) {
232232
fmt.Println("[kubelet-start] Would stop the kubelet")
233233
}
234234

235+
// Write the instance kubelet configuration file to disk.
236+
if features.Enabled(initCfg.FeatureGates, features.NodeLocalCRISocket) {
237+
kubeletConfig := &kubeletconfig.KubeletConfiguration{
238+
ContainerRuntimeEndpoint: data.Cfg().NodeRegistration.CRISocket,
239+
}
240+
if err := kubeletphase.WriteInstanceConfigToDisk(kubeletConfig, data.KubeletDir()); err != nil {
241+
return errors.Wrap(err, "error writing instance kubelet configuration to disk")
242+
}
243+
}
244+
235245
// Write the configuration for the kubelet (using the bootstrap token credentials) to disk so the kubelet can start
236246
if err := kubeletphase.WriteConfigToDisk(&initCfg.ClusterConfiguration, data.KubeletDir(), data.PatchesDir(), data.OutputWriter()); err != nil {
237247
return err
@@ -323,9 +333,11 @@ func runKubeletWaitBootstrapPhase(c workflow.RunData) (returnErr error) {
323333
return err
324334
}
325335

326-
klog.V(1).Infoln("[kubelet-start] preserving the crisocket information for the node")
327-
if err := patchnodephase.AnnotateCRISocket(client, cfg.NodeRegistration.Name, cfg.NodeRegistration.CRISocket); err != nil {
328-
return errors.Wrap(err, "error uploading crisocket")
336+
if !features.Enabled(initCfg.ClusterConfiguration.FeatureGates, features.NodeLocalCRISocket) {
337+
klog.V(1).Infoln("[kubelet-start] preserving the crisocket information for the node")
338+
if err := patchnodephase.AnnotateCRISocket(client, cfg.NodeRegistration.Name, cfg.NodeRegistration.CRISocket); err != nil {
339+
return errors.Wrap(err, "error writing CRISocket for this node")
340+
}
329341
}
330342

331343
return nil

cmd/kubeadm/app/cmd/phases/upgrade/apply/uploadconfig.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
2929
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
3030
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
31+
"k8s.io/kubernetes/cmd/kubeadm/app/features"
3132
kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet"
3233
patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode"
3334
"k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig"
@@ -107,9 +108,11 @@ func runUploadKubeletConfig(c workflow.RunData) error {
107108
return errors.Wrap(err, "error creating kubelet configuration ConfigMap")
108109
}
109110

110-
klog.V(1).Infoln("[upgrade/upload-config] Preserving the CRISocket information for this control-plane node")
111-
if err := patchnodephase.AnnotateCRISocket(client, cfg.NodeRegistration.Name, cfg.NodeRegistration.CRISocket); err != nil {
112-
return errors.Wrap(err, "error writing Crisocket information for the control-plane node")
111+
if !features.Enabled(cfg.ClusterConfiguration.FeatureGates, features.NodeLocalCRISocket) {
112+
klog.V(1).Infoln("[upgrade/upload-config] Preserving the CRISocket information for this control-plane node")
113+
if err := patchnodephase.AnnotateCRISocket(client, cfg.NodeRegistration.Name, cfg.NodeRegistration.CRISocket); err != nil {
114+
return errors.Wrap(err, "error writing CRISocket for this node")
115+
}
113116
}
114117

115118
return nil

cmd/kubeadm/app/componentconfigs/kubelet.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"path/filepath"
2121

2222
"github.com/pkg/errors"
23+
2324
clientset "k8s.io/client-go/kubernetes"
2425
"k8s.io/klog/v2"
2526
kubeletconfig "k8s.io/kubelet/config/v1beta1"

cmd/kubeadm/app/constants/constants.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,10 @@ const (
306306
// This file should exist under KubeletRunDirectory
307307
KubeletConfigurationFileName = "config.yaml"
308308

309+
// KubeletInstanceConfigurationFileName is the name of the kubelet instance configuration file written
310+
// to all nodes. This file should exist under KubeletRunDirectory.
311+
KubeletInstanceConfigurationFileName = "instance-config.yaml"
312+
309313
// KubeletEnvFileName is a file "kubeadm init" writes at runtime. Using that interface, kubeadm can customize certain
310314
// kubelet flags conditionally based on the environment at runtime. Also, parameters given to the configuration file
311315
// might be passed through this file. "kubeadm init" writes one variable, with the name ${KubeletEnvFileVariableName}.

cmd/kubeadm/app/features/features.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ const (
4040
WaitForAllControlPlaneComponents = "WaitForAllControlPlaneComponents"
4141
// ControlPlaneKubeletLocalMode is expected to be in alpha in v1.31, beta in v1.32
4242
ControlPlaneKubeletLocalMode = "ControlPlaneKubeletLocalMode"
43+
// NodeLocalCRISocket is expected to be in alpha in v1.32, beta in v1.33, ga in v1.35
44+
NodeLocalCRISocket = "NodeLocalCRISocket"
4345
)
4446

4547
// InitFeatureGates are the default feature gates for the init command
@@ -56,6 +58,7 @@ var InitFeatureGates = FeatureList{
5658
EtcdLearnerMode: {FeatureSpec: featuregate.FeatureSpec{Default: true, PreRelease: featuregate.GA, LockToDefault: true}},
5759
WaitForAllControlPlaneComponents: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}},
5860
ControlPlaneKubeletLocalMode: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}},
61+
NodeLocalCRISocket: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}},
5962
}
6063

6164
// Feature represents a feature being gated

cmd/kubeadm/app/phases/kubelet/config.go

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@ import (
2727
v1 "k8s.io/api/core/v1"
2828
rbac "k8s.io/api/rbac/v1"
2929
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"k8s.io/apimachinery/pkg/types"
3031
clientset "k8s.io/client-go/kubernetes"
3132
kubeletconfig "k8s.io/kubelet/config/v1beta1"
3233
"sigs.k8s.io/yaml"
3334

3435
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
3536
"k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
3637
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
38+
"k8s.io/kubernetes/cmd/kubeadm/app/features"
3739
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
3840
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
3941
"k8s.io/kubernetes/cmd/kubeadm/app/util/patches"
@@ -66,7 +68,21 @@ func WriteConfigToDisk(cfg *kubeadmapi.ClusterConfiguration, kubeletDir, patches
6668
}
6769
}
6870

69-
return writeConfigBytesToDisk(kubeletBytes, kubeletDir)
71+
if features.Enabled(cfg.FeatureGates, features.NodeLocalCRISocket) {
72+
file := filepath.Join(kubeletDir, kubeadmconstants.KubeletInstanceConfigurationFileName)
73+
kubeletBytes, err = applyKubeletConfigPatchFromFile(kubeletBytes, file, output)
74+
if err != nil {
75+
return errors.Wrapf(err, "could not apply kubelet instance configuration as a patch from %q", file)
76+
}
77+
}
78+
return writeConfigBytesToDisk(kubeletBytes, kubeletDir, kubeadmconstants.KubeletConfigurationFileName)
79+
}
80+
81+
// WriteInstanceConfigToDisk writes the container runtime endpoint configuration
82+
// to the instance configuration file in the specified kubelet directory.
83+
func WriteInstanceConfigToDisk(cfg *kubeletconfig.KubeletConfiguration, kubeletDir string) error {
84+
instanceFileContent := fmt.Sprintf("containerRuntimeEndpoint: %q\n", cfg.ContainerRuntimeEndpoint)
85+
return writeConfigBytesToDisk([]byte(instanceFileContent), kubeletDir, kubeadmconstants.KubeletInstanceConfigurationFileName)
7086
}
7187

7288
// ApplyPatchesToConfig applies the patches located in patchesDir to the KubeletConfiguration stored
@@ -188,8 +204,8 @@ func createConfigMapRBACRules(client clientset.Interface) error {
188204
}
189205

190206
// writeConfigBytesToDisk writes a byte slice down to disk at the specific location of the kubelet config file
191-
func writeConfigBytesToDisk(b []byte, kubeletDir string) error {
192-
configFile := filepath.Join(kubeletDir, kubeadmconstants.KubeletConfigurationFileName)
207+
func writeConfigBytesToDisk(b []byte, kubeletDir, fileName string) error {
208+
configFile := filepath.Join(kubeletDir, fileName)
193209
fmt.Printf("[kubelet-start] Writing kubelet configuration to file %q\n", configFile)
194210

195211
// creates target folder if not already exists
@@ -198,7 +214,7 @@ func writeConfigBytesToDisk(b []byte, kubeletDir string) error {
198214
}
199215

200216
if err := os.WriteFile(configFile, b, 0644); err != nil {
201-
return errors.Wrapf(err, "failed to write kubelet configuration to the file %q", configFile)
217+
return errors.Wrapf(err, "failed to write kubelet configuration file %q", configFile)
202218
}
203219
return nil
204220
}
@@ -225,3 +241,45 @@ func applyKubeletConfigPatches(kubeletBytes []byte, patchesDir string, output io
225241
}
226242
return kubeletBytes, nil
227243
}
244+
245+
// applyKubeletConfigPatchFromFile applies a single patch file to the kubelet configuration bytes.
246+
func applyKubeletConfigPatchFromFile(kubeletConfigBytes []byte, patchFilePath string, output io.Writer) ([]byte, error) {
247+
// Get the patch data from the file.
248+
data, err := os.ReadFile(patchFilePath)
249+
if err != nil {
250+
return nil, errors.Wrapf(err, "could not read patch file %q", patchFilePath)
251+
}
252+
253+
patchSet, err := patches.CreatePatchSet(patches.KubeletConfiguration, types.StrategicMergePatchType, string(data))
254+
if err != nil {
255+
return nil, err
256+
}
257+
258+
patchManager := patches.NewPatchManager([]*patches.PatchSet{patchSet}, []string{patches.KubeletConfiguration}, output)
259+
260+
// Always convert the target data to JSON.
261+
patchData, err := yaml.YAMLToJSON(kubeletConfigBytes)
262+
if err != nil {
263+
return nil, err
264+
}
265+
266+
// Define the patch target.
267+
patchTarget := &patches.PatchTarget{
268+
Name: patches.KubeletConfiguration,
269+
StrategicMergePatchObject: kubeletconfig.KubeletConfiguration{},
270+
Data: patchData,
271+
}
272+
273+
err = patchManager.ApplyPatchesToTarget(patchTarget)
274+
if err != nil {
275+
return nil, err
276+
}
277+
278+
// Convert the patched data back to YAML and return it.
279+
kubeletConfigBytes, err = yaml.JSONToYAML(patchTarget.Data)
280+
if err != nil {
281+
return nil, errors.Wrap(err, "failed to convert patched data to YAML")
282+
}
283+
284+
return kubeletConfigBytes, nil
285+
}

cmd/kubeadm/app/phases/kubelet/config_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
"path/filepath"
2525
"testing"
2626

27+
"github.com/stretchr/testify/assert"
28+
2729
v1 "k8s.io/api/core/v1"
2830
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2931
"k8s.io/apimachinery/pkg/runtime"
@@ -109,6 +111,74 @@ func TestApplyKubeletConfigPatches(t *testing.T) {
109111
}
110112
}
111113

114+
func TestApplyKubeletConfigPatchFromFile(t *testing.T) {
115+
const kubeletConfigGVK = "apiVersion: kubelet.config.k8s.io/v1beta1\nkind: KubeletConfiguration\n"
116+
117+
tests := []struct {
118+
name string
119+
kubeletConfig []byte
120+
patchContent []byte
121+
expectError bool
122+
expectedResult []byte
123+
}{
124+
{
125+
name: "apply new field",
126+
kubeletConfig: []byte(kubeletConfigGVK),
127+
patchContent: []byte("containerRuntimeEndpoint: unix:///run/containerd/containerd.sock"),
128+
expectError: false,
129+
expectedResult: []byte("apiVersion: kubelet.config.k8s.io/v1beta1\ncontainerRuntimeEndpoint: unix:///run/containerd/containerd.sock\nkind: KubeletConfiguration\n"),
130+
},
131+
{
132+
name: "overwrite existing field",
133+
kubeletConfig: []byte(kubeletConfigGVK + "containerRuntimeEndpoint: unix:///run/crio/crio.sock\n"),
134+
patchContent: []byte("containerRuntimeEndpoint: unix:///run/containerd/containerd.sock"),
135+
expectError: false,
136+
expectedResult: []byte("apiVersion: kubelet.config.k8s.io/v1beta1\ncontainerRuntimeEndpoint: unix:///run/containerd/containerd.sock\nkind: KubeletConfiguration\n"),
137+
},
138+
{
139+
name: "invalid patch contents",
140+
kubeletConfig: []byte(kubeletConfigGVK),
141+
patchContent: []byte("invalid-patch-content"),
142+
expectError: true,
143+
},
144+
{
145+
name: "empty patch file",
146+
kubeletConfig: []byte(kubeletConfigGVK),
147+
patchContent: []byte(""),
148+
expectError: false,
149+
expectedResult: []byte(kubeletConfigGVK),
150+
},
151+
}
152+
153+
for _, tt := range tests {
154+
t.Run(tt.name, func(t *testing.T) {
155+
output := io.Discard
156+
157+
// Create a temporary file to store the patch content.
158+
patchFile, err := os.CreateTemp("", "instance-config-*.yml")
159+
if err != nil {
160+
t.Errorf("Error creating temporary file: %v", err)
161+
}
162+
defer func() {
163+
_ = patchFile.Close()
164+
_ = os.Remove(patchFile.Name())
165+
}()
166+
167+
_, err = patchFile.Write(tt.patchContent)
168+
if err != nil {
169+
t.Errorf("Error writing instance config to file: %v", err)
170+
}
171+
172+
// Apply the patch.
173+
result, err := applyKubeletConfigPatchFromFile(tt.kubeletConfig, patchFile.Name(), output)
174+
if !tt.expectError && err != nil {
175+
t.Errorf("Unexpected error: %v", err)
176+
}
177+
assert.Equal(t, tt.expectedResult, result)
178+
})
179+
}
180+
}
181+
112182
func TestApplyPatchesToConfig(t *testing.T) {
113183
const (
114184
expectedAddress = "barfoo"

0 commit comments

Comments
 (0)