Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kubeadm: add support for patching a "kubeletconfiguration" target #110405

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/kubeadm/app/apis/kubeadm/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,8 +421,8 @@ type HostPathMount struct {
type Patches struct {
// Directory is a path to a directory that contains files named "target[suffix][+patchtype].extension".
// For example, "kube-apiserver0+merge.yaml" or just "etcd.json". "target" can be one of
// "kube-apiserver", "kube-controller-manager", "kube-scheduler", "etcd". "patchtype" can be one
// of "strategic" "merge" or "json" and they match the patch formats supported by kubectl.
// "kube-apiserver", "kube-controller-manager", "kube-scheduler", "etcd", "kubeletconfiguration".
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason that we don't use kubelet-configuration?

Copy link
Member Author

Choose a reason for hiding this comment

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

tried to keep the name consistent with the original "kind" KubeletConfiguration -> kubeletconfiguration.

// "patchtype" can be one of "strategic" "merge" or "json" and they match the patch formats supported by kubectl.
// The default "patchtype" is "strategic". "extension" must be either "json" or "yaml".
// "suffix" is an optional string that can be used to determine which patches are applied
// first alpha-numerically.
Expand Down
4 changes: 2 additions & 2 deletions cmd/kubeadm/app/apis/kubeadm/v1beta3/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,8 +435,8 @@ type HostPathMount struct {
type Patches struct {
// Directory is a path to a directory that contains files named "target[suffix][+patchtype].extension".
// For example, "kube-apiserver0+merge.yaml" or just "etcd.json". "target" can be one of
// "kube-apiserver", "kube-controller-manager", "kube-scheduler", "etcd". "patchtype" can be one
// of "strategic" "merge" or "json" and they match the patch formats supported by kubectl.
// "kube-apiserver", "kube-controller-manager", "kube-scheduler", "etcd", "kubeletconfiguration".
// "patchtype" can be one of "strategic" "merge" or "json" and they match the patch formats supported by kubectl.
// The default "patchtype" is "strategic". "extension" must be either "json" or "yaml".
// "suffix" is an optional string that can be used to determine which patches are applied
// first alpha-numerically.
Expand Down
2 changes: 1 addition & 1 deletion cmd/kubeadm/app/cmd/options/generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func AddPatchesFlag(fs *pflag.FlagSet, patchesDir *string) {
const usage = `Path to a directory that contains files named ` +
`"target[suffix][+patchtype].extension". For example, ` +
`"kube-apiserver0+merge.yaml" or just "etcd.json". ` +
`"target" can be one of "kube-apiserver", "kube-controller-manager", "kube-scheduler", "etcd". ` +
`"target" can be one of "kube-apiserver", "kube-controller-manager", "kube-scheduler", "etcd", "kubeletconfiguration". ` +
`"patchtype" can be one of "strategic", "merge" or "json" and they match the patch formats ` +
`supported by kubectl. The default "patchtype" is "strategic". "extension" must be either ` +
`"json" or "yaml". "suffix" is an optional string that can be used to determine ` +
Expand Down
3 changes: 2 additions & 1 deletion cmd/kubeadm/app/cmd/phases/init/kubelet.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func NewKubeletStartPhase() workflow.Phase {
options.CfgPath,
options.NodeCRISocket,
options.NodeName,
options.Patches,
},
}
}
Expand All @@ -74,7 +75,7 @@ func runKubeletStart(c workflow.RunData) error {
}

// Write the kubelet configuration file to disk.
if err := kubeletphase.WriteConfigToDisk(&data.Cfg().ClusterConfiguration, data.KubeletDir()); err != nil {
if err := kubeletphase.WriteConfigToDisk(&data.Cfg().ClusterConfiguration, data.KubeletDir(), data.PatchesDir(), data.OutputWriter()); err != nil {
return errors.Wrap(err, "error writing kubelet configuration to disk")
}

Expand Down
3 changes: 2 additions & 1 deletion cmd/kubeadm/app/cmd/phases/join/kubelet.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func NewKubeletStartPhase() workflow.Phase {
options.TokenDiscoverySkipCAHash,
options.TLSBootstrapToken,
options.TokenStr,
options.Patches,
},
}
}
Expand Down Expand Up @@ -174,7 +175,7 @@ func runKubeletStartJoinPhase(c workflow.RunData) (returnErr error) {
}

// Write the configuration for the kubelet (using the bootstrap token credentials) to disk so the kubelet can start
if err := kubeletphase.WriteConfigToDisk(&initCfg.ClusterConfiguration, data.KubeletDir()); err != nil {
if err := kubeletphase.WriteConfigToDisk(&initCfg.ClusterConfiguration, data.KubeletDir(), data.PatchesDir(), data.OutputWriter()); err != nil {
return err
}

Expand Down
3 changes: 3 additions & 0 deletions cmd/kubeadm/app/cmd/phases/upgrade/node/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package node

import (
"io"

"k8s.io/apimachinery/pkg/util/sets"
clientset "k8s.io/client-go/kubernetes"

Expand All @@ -35,4 +37,5 @@ type Data interface {
IgnorePreflightErrors() sets.String
PatchesDir() string
KubeConfigPath() string
OutputWriter() io.Writer
}
3 changes: 2 additions & 1 deletion cmd/kubeadm/app/cmd/phases/upgrade/node/kubeletconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func NewKubeletConfigPhase() workflow.Phase {
InheritFlags: []string{
options.DryRun,
options.KubeconfigPath,
options.Patches,
},
}
return phase
Expand All @@ -73,7 +74,7 @@ func runKubeletConfigPhase() func(c workflow.RunData) error {
// TODO: Checkpoint the current configuration first so that if something goes wrong it can be recovered

// Store the kubelet component configuration.
if err = kubeletphase.WriteConfigToDisk(&cfg.ClusterConfiguration, kubeletDir); err != nil {
if err = kubeletphase.WriteConfigToDisk(&cfg.ClusterConfiguration, kubeletDir, data.PatchesDir(), data.OutputWriter()); err != nil {
return err
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/kubeadm/app/cmd/upgrade/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func runApply(flags *applyFlags, args []string) error {

// Upgrade RBAC rules and addons.
klog.V(1).Infoln("[upgrade/postupgrade] upgrading RBAC rules and addons")
if err := upgrade.PerformPostUpgradeTasks(client, cfg, flags.dryRun); err != nil {
if err := upgrade.PerformPostUpgradeTasks(client, cfg, flags.patchesDir, flags.dryRun, flags.applyPlanFlags.out); err != nil {
return errors.Wrap(err, "[upgrade/postupgrade] FATAL post-upgrade error")
}

Expand Down
13 changes: 10 additions & 3 deletions cmd/kubeadm/app/cmd/upgrade/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package upgrade

import (
"io"
"os"

"github.com/pkg/errors"
Expand Down Expand Up @@ -63,10 +64,11 @@ type nodeData struct {
patchesDir string
ignorePreflightErrors sets.String
kubeConfigPath string
outputWriter io.Writer
}

// newCmdNode returns the cobra command for `kubeadm upgrade node`
func newCmdNode() *cobra.Command {
func newCmdNode(out io.Writer) *cobra.Command {
nodeOptions := newNodeOptions()
nodeRunner := workflow.NewRunner()

Expand All @@ -92,7 +94,7 @@ func newCmdNode() *cobra.Command {
// sets the data builder function, that will be used by the runner
// both when running the entire workflow or single phases
nodeRunner.SetDataInitializer(func(cmd *cobra.Command, args []string) (workflow.RunData, error) {
return newNodeData(cmd, args, nodeOptions)
return newNodeData(cmd, args, nodeOptions, out)
})

// binds the Runner to kubeadm upgrade node command by altering
Expand Down Expand Up @@ -123,7 +125,7 @@ func addUpgradeNodeFlags(flagSet *flag.FlagSet, nodeOptions *nodeOptions) {
// newNodeData returns a new nodeData struct to be used for the execution of the kubeadm upgrade node workflow.
// This func takes care of validating nodeOptions passed to the command, and then it converts
// options into the internal InitConfiguration type that is used as input all the phases in the kubeadm upgrade node workflow
func newNodeData(cmd *cobra.Command, args []string, options *nodeOptions) (*nodeData, error) {
func newNodeData(cmd *cobra.Command, args []string, options *nodeOptions, out io.Writer) (*nodeData, error) {
client, err := getClient(options.kubeConfigPath, options.dryRun)
if err != nil {
return nil, errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", options.kubeConfigPath)
Expand Down Expand Up @@ -168,6 +170,7 @@ func newNodeData(cmd *cobra.Command, args []string, options *nodeOptions) (*node
patchesDir: options.patchesDir,
ignorePreflightErrors: ignorePreflightErrorsSet,
kubeConfigPath: options.kubeConfigPath,
outputWriter: out,
}, nil
}

Expand Down Expand Up @@ -215,3 +218,7 @@ func (d *nodeData) IgnorePreflightErrors() sets.String {
func (d *nodeData) KubeConfigPath() string {
return d.kubeConfigPath
}

func (d *nodeData) OutputWriter() io.Writer {
return d.outputWriter
}
2 changes: 1 addition & 1 deletion cmd/kubeadm/app/cmd/upgrade/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func NewCmdUpgrade(out io.Writer) *cobra.Command {
cmd.AddCommand(newCmdApply(flags))
cmd.AddCommand(newCmdPlan(flags))
cmd.AddCommand(newCmdDiff(out))
cmd.AddCommand(newCmdNode())
cmd.AddCommand(newCmdNode(out))
return cmd
}

Expand Down
24 changes: 23 additions & 1 deletion cmd/kubeadm/app/phases/kubelet/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package kubelet

import (
"fmt"
"io"
"os"
"path/filepath"

Expand All @@ -28,17 +29,19 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/version"
clientset "k8s.io/client-go/kubernetes"
kubeletconfig "k8s.io/kubelet/config/v1beta1"

kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/features"
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
"k8s.io/kubernetes/cmd/kubeadm/app/util/patches"
)

// WriteConfigToDisk writes the kubelet config object down to a file
// Used at "kubeadm init" and "kubeadm upgrade" time
func WriteConfigToDisk(cfg *kubeadmapi.ClusterConfiguration, kubeletDir string) error {
func WriteConfigToDisk(cfg *kubeadmapi.ClusterConfiguration, kubeletDir, patchesDir string, output io.Writer) error {
kubeletCfg, ok := cfg.ComponentConfigs[componentconfigs.KubeletGroup]
if !ok {
return errors.New("no kubelet component config found")
Expand All @@ -53,6 +56,25 @@ func WriteConfigToDisk(cfg *kubeadmapi.ClusterConfiguration, kubeletDir string)
return err
}

// Apply patches to the KubeletConfiguration
if len(patchesDir) != 0 {
target := "kubeletconfiguration"
knownTargets := []string{target}
patchManager, err := patches.GetPatchManagerForPath(patchesDir, knownTargets, output)
if err != nil {
return err
}
patchTarget := &patches.PatchTarget{
Name: target,
StrategicMergePatchObject: kubeletconfig.KubeletConfiguration{},
Data: kubeletBytes,
}
if err := patchManager.ApplyPatchesToTarget(patchTarget); err != nil {
return err
}
kubeletBytes = patchTarget.Data
}

return writeConfigBytesToDisk(kubeletBytes, kubeletDir)
}

Expand Down
9 changes: 5 additions & 4 deletions cmd/kubeadm/app/phases/upgrade/postupgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package upgrade
import (
"context"
"fmt"
"io"
"os"
"strings"

Expand Down Expand Up @@ -48,7 +49,7 @@ import (

// PerformPostUpgradeTasks runs nearly the same functions as 'kubeadm init' would do
// Note that the mark-control-plane phase is left out, not needed, and no token is created as that doesn't belong to the upgrade
func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.InitConfiguration, dryRun bool) error {
func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.InitConfiguration, patchesDir string, dryRun bool, out io.Writer) error {
errs := []error{}

// Upload currently used configuration to the cluster
Expand All @@ -64,7 +65,7 @@ func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.InitCon
}

// Write the new kubelet config down to disk and the env file if needed
if err := writeKubeletConfigFiles(client, cfg, dryRun); err != nil {
if err := writeKubeletConfigFiles(client, cfg, patchesDir, dryRun, out); err != nil {
errs = append(errs, err)
}

Expand Down Expand Up @@ -158,15 +159,15 @@ func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.InitCon
return errorsutil.NewAggregate(errs)
}

func writeKubeletConfigFiles(client clientset.Interface, cfg *kubeadmapi.InitConfiguration, dryRun bool) error {
func writeKubeletConfigFiles(client clientset.Interface, cfg *kubeadmapi.InitConfiguration, patchesDir string, dryRun bool, out io.Writer) error {
kubeletDir, err := GetKubeletDir(dryRun)
if err != nil {
// The error here should never occur in reality, would only be thrown if /tmp doesn't exist on the machine.
return err
}
errs := []error{}
// Write the configuration for the kubelet down to disk so the upgraded kubelet can start with fresh config
if err := kubeletphase.WriteConfigToDisk(&cfg.ClusterConfiguration, kubeletDir); err != nil {
if err := kubeletphase.WriteConfigToDisk(&cfg.ClusterConfiguration, kubeletDir, patchesDir, out); err != nil {
errs = append(errs, errors.Wrap(err, "error writing kubelet configuration to file"))
}

Expand Down
18 changes: 17 additions & 1 deletion cmd/kubeadm/app/util/patches/patches_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (

"github.com/pkg/errors"

"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
)

Expand All @@ -35,6 +35,7 @@ var testKnownTargets = []string{
"kube-apiserver",
"kube-controller-manager",
"kube-scheduler",
"kubeletconfiguration",
}

const testDirPattern = "patch-files"
Expand Down Expand Up @@ -312,6 +313,21 @@ func TestGetPatchManagerForPath(t *testing.T) {
},
},
},
{
name: "valid: kubeletconfiguration target is patched with json patch",
patchTarget: &PatchTarget{
Name: "kubeletconfiguration",
StrategicMergePatchObject: nil,
Data: []byte("foo: bar\n"),
},
expectedData: []byte(`{"foo":"zzz"}`),
files: []*file{
{
name: "kubeletconfiguration+json.json",
data: `[{"op": "replace", "path": "/foo", "value": "zzz"}]`,
},
},
},
{
name: "valid: kube-apiserver target is patched with strategic merge patch",
patchTarget: &PatchTarget{
Expand Down