mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-29 22:46:12 +00:00
Merge pull request #64232 from luxas/kubeadm_config_migrate
Automatic merge from submit-queue (batch tested with PRs 64322, 64210, 64458, 64232, 64370). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. kubeadm: Add a 'kubeadm config migrate' command **What this PR does / why we need it**: This is an UX improvement so users may easier "upgrade" their **configuration files** from the an old version (e.g. `v1alpha1`) version to a new one (e.g. `v1alpha2`), can do this **locally and seamlessly without touching a cluster**. We talked about this in the SIG meeting; getting the users to be able to convert their checked-in configuration files to new API versions will be crucial. **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: Fixes # **Special notes for your reviewer**: **Release note**: ```release-note kubeadm: Add a 'kubeadm config migrate' command to convert old API types to their newer counterparts in the new, supported API types. This is just a client-side tool, it just executes locally without requiring a cluster to be running. You can think about this as an Unix pipe that upgrades config files. ``` @kubernetes/sig-cluster-lifecycle-pr-reviews @liztio
This commit is contained in:
commit
897a4b4968
@ -22,6 +22,7 @@ go_library(
|
|||||||
deps = [
|
deps = [
|
||||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||||
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
|
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
|
||||||
|
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
||||||
"//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library",
|
"//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library",
|
||||||
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
|
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
|
||||||
"//cmd/kubeadm/app/cmd/phases:go_default_library",
|
"//cmd/kubeadm/app/cmd/phases:go_default_library",
|
||||||
|
@ -19,6 +19,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
@ -30,6 +31,7 @@ import (
|
|||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
||||||
|
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||||
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
|
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
|
||||||
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
|
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
@ -43,6 +45,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// TODO: Figure out how to get these constants from the API machinery
|
||||||
masterConfig = "MasterConfiguration"
|
masterConfig = "MasterConfiguration"
|
||||||
nodeConfig = "NodeConfiguration"
|
nodeConfig = "NodeConfiguration"
|
||||||
sillyToken = "abcdef.0123456789abcdef"
|
sillyToken = "abcdef.0123456789abcdef"
|
||||||
@ -74,6 +77,7 @@ func NewCmdConfig(out io.Writer) *cobra.Command {
|
|||||||
cmd.PersistentFlags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use when talking to the cluster.")
|
cmd.PersistentFlags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use when talking to the cluster.")
|
||||||
|
|
||||||
cmd.AddCommand(NewCmdConfigPrintDefault(out))
|
cmd.AddCommand(NewCmdConfigPrintDefault(out))
|
||||||
|
cmd.AddCommand(NewCmdConfigMigrate(out))
|
||||||
cmd.AddCommand(NewCmdConfigUpload(out, &kubeConfigFile))
|
cmd.AddCommand(NewCmdConfigUpload(out, &kubeConfigFile))
|
||||||
cmd.AddCommand(NewCmdConfigView(out, &kubeConfigFile))
|
cmd.AddCommand(NewCmdConfigView(out, &kubeConfigFile))
|
||||||
cmd.AddCommand(NewCmdConfigImages(out))
|
cmd.AddCommand(NewCmdConfigImages(out))
|
||||||
@ -138,6 +142,71 @@ func getDefaultAPIObjectBytes(apiObject string) ([]byte, error) {
|
|||||||
return []byte{}, fmt.Errorf("--api-object needs to be one of %v", availableAPIObjects)
|
return []byte{}, fmt.Errorf("--api-object needs to be one of %v", availableAPIObjects)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewCmdConfigMigrate returns cobra.Command for "kubeadm config migrate" command
|
||||||
|
func NewCmdConfigMigrate(out io.Writer) *cobra.Command {
|
||||||
|
var oldCfgPath, newCfgPath string
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "migrate",
|
||||||
|
Short: "Read an older version of the kubeadm configuration API types from a file, and output the similar config object for the newer version.",
|
||||||
|
Long: fmt.Sprintf(dedent.Dedent(`
|
||||||
|
This command lets you convert configuration objects of older versions to the latest supported version,
|
||||||
|
locally in the CLI tool without ever touching anything in the cluster.
|
||||||
|
In this version of kubeadm, the following API versions are supported:
|
||||||
|
- %s
|
||||||
|
- %s
|
||||||
|
|
||||||
|
Further, kubeadm can only write out config of version %q, but read both types.
|
||||||
|
So regardless of what version you pass to the --old-config parameter here, the API object will be
|
||||||
|
read, deserialized, defaulted, converted, validated, and re-serialized when written to stdout or
|
||||||
|
--new-config if specified.
|
||||||
|
|
||||||
|
In other words, the output of this command is what kubeadm actually would read internally if you
|
||||||
|
submitted this file to "kubeadm init"
|
||||||
|
`), kubeadmapiv1alpha2.SchemeGroupVersion.String(), kubeadmapiv1alpha1.SchemeGroupVersion.String(), kubeadmapiv1alpha2.SchemeGroupVersion.String()),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(oldCfgPath) == 0 {
|
||||||
|
kubeadmutil.CheckErr(fmt.Errorf("The --old-config flag is mandatory"))
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadFile(oldCfgPath)
|
||||||
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
|
var outputBytes []byte
|
||||||
|
gvk, err := kubeadmutil.GroupVersionKindFromBytes(b, kubeadmscheme.Codecs)
|
||||||
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
|
switch gvk.Kind {
|
||||||
|
case masterConfig:
|
||||||
|
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(oldCfgPath, &kubeadmapiv1alpha2.MasterConfiguration{})
|
||||||
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
|
outputBytes, err = kubeadmutil.MarshalToYamlForCodecs(internalcfg, kubeadmapiv1alpha2.SchemeGroupVersion, kubeadmscheme.Codecs)
|
||||||
|
kubeadmutil.CheckErr(err)
|
||||||
|
case nodeConfig:
|
||||||
|
internalcfg, err := configutil.NodeConfigFileAndDefaultsToInternalConfig(oldCfgPath, &kubeadmapiv1alpha2.NodeConfiguration{})
|
||||||
|
kubeadmutil.CheckErr(err)
|
||||||
|
|
||||||
|
// TODO: In the future we might not want to duplicate these two lines of code for every case here.
|
||||||
|
outputBytes, err = kubeadmutil.MarshalToYamlForCodecs(internalcfg, kubeadmapiv1alpha2.SchemeGroupVersion, kubeadmscheme.Codecs)
|
||||||
|
kubeadmutil.CheckErr(err)
|
||||||
|
default:
|
||||||
|
kubeadmutil.CheckErr(fmt.Errorf("Didn't recognize type with GroupVersionKind: %v", gvk))
|
||||||
|
}
|
||||||
|
|
||||||
|
if newCfgPath == "" {
|
||||||
|
fmt.Fprintf(out, string(outputBytes))
|
||||||
|
} else {
|
||||||
|
if err := ioutil.WriteFile(newCfgPath, outputBytes, 0644); err != nil {
|
||||||
|
kubeadmutil.CheckErr(fmt.Errorf("failed to write the new configuration to the file %q: %v", newCfgPath, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cmd.Flags().StringVar(&oldCfgPath, "old-config", "", "Path to the kubeadm config file that is using an old API version and should be converted. This flag is mandatory.")
|
||||||
|
cmd.Flags().StringVar(&newCfgPath, "new-config", "", "Path to the resulting equivalent kubeadm config file using the new API version. Optional, if not specified output will be sent to STDOUT.")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
// NewCmdConfigUpload returns cobra.Command for "kubeadm config upload" command
|
// NewCmdConfigUpload returns cobra.Command for "kubeadm config upload" command
|
||||||
func NewCmdConfigUpload(out io.Writer, kubeConfigFile *string) *cobra.Command {
|
func NewCmdConfigUpload(out io.Writer, kubeConfigFile *string) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
|
@ -20,6 +20,7 @@ go_library(
|
|||||||
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util",
|
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util",
|
||||||
deps = [
|
deps = [
|
||||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||||
|
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
||||||
"//vendor/gopkg.in/yaml.v2:go_default_library",
|
"//vendor/gopkg.in/yaml.v2:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||||
|
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MarshalToYaml marshals an object into yaml.
|
// MarshalToYaml marshals an object into yaml.
|
||||||
@ -34,6 +35,8 @@ func MarshalToYaml(obj runtime.Object, gv schema.GroupVersion) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MarshalToYamlForCodecs marshals an object into yaml using the specified codec
|
// MarshalToYamlForCodecs marshals an object into yaml using the specified codec
|
||||||
|
// TODO: Is specifying the gv really needed here?
|
||||||
|
// TODO: Can we support json out of the box easily here?
|
||||||
func MarshalToYamlForCodecs(obj runtime.Object, gv schema.GroupVersion, codecs serializer.CodecFactory) ([]byte, error) {
|
func MarshalToYamlForCodecs(obj runtime.Object, gv schema.GroupVersion, codecs serializer.CodecFactory) ([]byte, error) {
|
||||||
mediaType := "application/yaml"
|
mediaType := "application/yaml"
|
||||||
info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
|
info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
|
||||||
@ -51,6 +54,8 @@ func UnmarshalFromYaml(buffer []byte, gv schema.GroupVersion) (runtime.Object, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalFromYamlForCodecs unmarshals yaml into an object using the specified codec
|
// UnmarshalFromYamlForCodecs unmarshals yaml into an object using the specified codec
|
||||||
|
// TODO: Is specifying the gv really needed here?
|
||||||
|
// TODO: Can we support json out of the box easily here?
|
||||||
func UnmarshalFromYamlForCodecs(buffer []byte, gv schema.GroupVersion, codecs serializer.CodecFactory) (runtime.Object, error) {
|
func UnmarshalFromYamlForCodecs(buffer []byte, gv schema.GroupVersion, codecs serializer.CodecFactory) (runtime.Object, error) {
|
||||||
mediaType := "application/yaml"
|
mediaType := "application/yaml"
|
||||||
info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
|
info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
|
||||||
@ -62,6 +67,40 @@ func UnmarshalFromYamlForCodecs(buffer []byte, gv schema.GroupVersion, codecs se
|
|||||||
return runtime.Decode(decoder, buffer)
|
return runtime.Decode(decoder, buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GroupVersionKindFromBytes parses the bytes and returns the gvk
|
||||||
|
func GroupVersionKindFromBytes(buffer []byte, codecs serializer.CodecFactory) (schema.GroupVersionKind, error) {
|
||||||
|
|
||||||
|
decoded, err := LoadYAML(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return schema.EmptyObjectKind.GroupVersionKind(), fmt.Errorf("unable to decode config from bytes: %v", err)
|
||||||
|
}
|
||||||
|
kindStr, apiVersionStr := "", ""
|
||||||
|
|
||||||
|
// As there was a bug in kubeadm v1.10 and earlier that made the YAML uploaded to the cluster configmap NOT have metav1.TypeMeta information
|
||||||
|
// we need to populate this here manually. If kind or apiVersion is empty, we know the apiVersion is v1alpha1, as by the time kubeadm had this bug,
|
||||||
|
// it could only write
|
||||||
|
// TODO: Remove this "hack" in v1.12 when we know the ConfigMap always contains v1alpha2 content written by kubeadm v1.11. Also, we will drop support for
|
||||||
|
// v1alpha1 in v1.12
|
||||||
|
kind := decoded["kind"]
|
||||||
|
apiVersion := decoded["apiVersion"]
|
||||||
|
if kind == nil || len(kind.(string)) == 0 {
|
||||||
|
kindStr = "MasterConfiguration"
|
||||||
|
} else {
|
||||||
|
kindStr = kind.(string)
|
||||||
|
}
|
||||||
|
if apiVersion == nil || len(apiVersion.(string)) == 0 {
|
||||||
|
apiVersionStr = kubeadmapiv1alpha1.SchemeGroupVersion.String()
|
||||||
|
} else {
|
||||||
|
apiVersionStr = apiVersion.(string)
|
||||||
|
}
|
||||||
|
gv, err := schema.ParseGroupVersion(apiVersionStr)
|
||||||
|
if err != nil {
|
||||||
|
return schema.EmptyObjectKind.GroupVersionKind(), fmt.Errorf("unable to parse apiVersion: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gv.WithKind(kindStr), nil
|
||||||
|
}
|
||||||
|
|
||||||
// LoadYAML is a small wrapper around go-yaml that ensures all nested structs are map[string]interface{} instead of map[interface{}]interface{}.
|
// LoadYAML is a small wrapper around go-yaml that ensures all nested structs are map[string]interface{} instead of map[interface{}]interface{}.
|
||||||
func LoadYAML(bytes []byte) (map[string]interface{}, error) {
|
func LoadYAML(bytes []byte) (map[string]interface{}, error) {
|
||||||
var decoded map[interface{}]interface{}
|
var decoded map[interface{}]interface{}
|
||||||
|
@ -61,6 +61,7 @@ docs/admin/kubeadm_config.md
|
|||||||
docs/admin/kubeadm_config_images.md
|
docs/admin/kubeadm_config_images.md
|
||||||
docs/admin/kubeadm_config_images_list.md
|
docs/admin/kubeadm_config_images_list.md
|
||||||
docs/admin/kubeadm_config_images_pull.md
|
docs/admin/kubeadm_config_images_pull.md
|
||||||
|
docs/admin/kubeadm_config_migrate.md
|
||||||
docs/admin/kubeadm_config_print-default.md
|
docs/admin/kubeadm_config_print-default.md
|
||||||
docs/admin/kubeadm_config_upload.md
|
docs/admin/kubeadm_config_upload.md
|
||||||
docs/admin/kubeadm_config_upload_from-file.md
|
docs/admin/kubeadm_config_upload_from-file.md
|
||||||
@ -140,6 +141,7 @@ docs/man/man1/kubeadm-completion.1
|
|||||||
docs/man/man1/kubeadm-config-images-list.1
|
docs/man/man1/kubeadm-config-images-list.1
|
||||||
docs/man/man1/kubeadm-config-images-pull.1
|
docs/man/man1/kubeadm-config-images-pull.1
|
||||||
docs/man/man1/kubeadm-config-images.1
|
docs/man/man1/kubeadm-config-images.1
|
||||||
|
docs/man/man1/kubeadm-config-migrate.1
|
||||||
docs/man/man1/kubeadm-config-print-default.1
|
docs/man/man1/kubeadm-config-print-default.1
|
||||||
docs/man/man1/kubeadm-config-upload-from-file.1
|
docs/man/man1/kubeadm-config-upload-from-file.1
|
||||||
docs/man/man1/kubeadm-config-upload-from-flags.1
|
docs/man/man1/kubeadm-config-upload-from-flags.1
|
||||||
|
3
docs/admin/kubeadm_config_migrate.md
Normal file
3
docs/admin/kubeadm_config_migrate.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
This file is autogenerated, but we've stopped checking such files into the
|
||||||
|
repository to reduce the need for rebases. Please run hack/generate-docs.sh to
|
||||||
|
populate this file.
|
3
docs/man/man1/kubeadm-config-migrate.1
Normal file
3
docs/man/man1/kubeadm-config-migrate.1
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
This file is autogenerated, but we've stopped checking such files into the
|
||||||
|
repository to reduce the need for rebases. Please run hack/generate-docs.sh to
|
||||||
|
populate this file.
|
Loading…
Reference in New Issue
Block a user