1
16
17 package config
18
19 import (
20 "context"
21 "crypto/x509"
22 "fmt"
23 "path/filepath"
24 "strings"
25 "time"
26
27 "github.com/pkg/errors"
28
29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30 "k8s.io/apimachinery/pkg/runtime"
31 errorsutil "k8s.io/apimachinery/pkg/util/errors"
32 "k8s.io/apimachinery/pkg/util/wait"
33 clientset "k8s.io/client-go/kubernetes"
34 "k8s.io/client-go/tools/clientcmd"
35 certutil "k8s.io/client-go/util/cert"
36 "k8s.io/klog/v2"
37
38 kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
39 kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
40 kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
41 "k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
42 "k8s.io/kubernetes/cmd/kubeadm/app/constants"
43 "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
44 "k8s.io/kubernetes/cmd/kubeadm/app/util/config/strict"
45 "k8s.io/kubernetes/cmd/kubeadm/app/util/output"
46 )
47
48
49 func FetchInitConfigurationFromCluster(client clientset.Interface, printer output.Printer, logPrefix string, newControlPlane, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) {
50 if printer == nil {
51 printer = &output.TextPrinter{}
52 }
53 printer.Printf("[%s] Reading configuration from the cluster...\n", logPrefix)
54 printer.Printf("[%s] FYI: You can look at this config file with 'kubectl -n %s get cm %s -o yaml'\n", logPrefix, metav1.NamespaceSystem, constants.KubeadmConfigConfigMap)
55
56
57 cfg, err := getInitConfigurationFromCluster(constants.KubernetesDir, client, newControlPlane, skipComponentConfigs)
58 if err != nil {
59 return nil, err
60 }
61
62
63
64 if err := SetInitDynamicDefaults(cfg, true); err != nil {
65 return nil, err
66 }
67
68 return cfg, nil
69 }
70
71
72 func getInitConfigurationFromCluster(kubeconfigDir string, client clientset.Interface, newControlPlane, skipComponentConfigs bool) (*kubeadmapi.InitConfiguration, error) {
73
74 configMap, err := apiclient.GetConfigMapWithShortRetry(client, metav1.NamespaceSystem, constants.KubeadmConfigConfigMap)
75 if err != nil {
76 return nil, errors.Wrap(err, "failed to get config map")
77 }
78
79
80 versionedInitcfg := &kubeadmapiv1.InitConfiguration{}
81 kubeadmscheme.Scheme.Default(versionedInitcfg)
82 initcfg := &kubeadmapi.InitConfiguration{}
83 if err := kubeadmscheme.Scheme.Convert(versionedInitcfg, initcfg, nil); err != nil {
84 return nil, errors.Wrap(err, "could not prepare a defaulted InitConfiguration")
85 }
86
87
88 clusterConfigurationData, ok := configMap.Data[constants.ClusterConfigurationConfigMapKey]
89 if !ok {
90 return nil, errors.Errorf("unexpected error when reading kubeadm-config ConfigMap: %s key value pair missing", constants.ClusterConfigurationConfigMapKey)
91 }
92
93 if err := strict.VerifyUnmarshalStrict([]*runtime.Scheme{kubeadmscheme.Scheme},
94 kubeadmapiv1.SchemeGroupVersion.WithKind(constants.ClusterConfigurationKind),
95 []byte(clusterConfigurationData)); err != nil {
96 klog.Warning(err.Error())
97 }
98 if err := runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(), []byte(clusterConfigurationData), &initcfg.ClusterConfiguration); err != nil {
99 return nil, errors.Wrap(err, "failed to decode cluster configuration data")
100 }
101
102 if !skipComponentConfigs {
103
104 if err := componentconfigs.FetchFromCluster(&initcfg.ClusterConfiguration, client); err != nil {
105 return nil, errors.Wrap(err, "failed to get component configs")
106 }
107 }
108
109
110
111 if !newControlPlane {
112
113 kubeconfigFile := filepath.Join(kubeconfigDir, constants.KubeletKubeConfigFileName)
114 if err := GetNodeRegistration(kubeconfigFile, client, &initcfg.NodeRegistration); err != nil {
115 return nil, errors.Wrap(err, "failed to get node registration")
116 }
117
118 if err := getAPIEndpoint(client, initcfg.NodeRegistration.Name, &initcfg.LocalAPIEndpoint); err != nil {
119 return nil, errors.Wrap(err, "failed to getAPIEndpoint")
120 }
121 }
122 return initcfg, nil
123 }
124
125
126 func GetNodeRegistration(kubeconfigFile string, client clientset.Interface, nodeRegistration *kubeadmapi.NodeRegistrationOptions) error {
127
128 nodeName, err := getNodeNameFromKubeletConfig(kubeconfigFile)
129 if err != nil {
130 return errors.Wrap(err, "failed to get node name from kubelet config")
131 }
132
133
134 node, err := client.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
135 if err != nil {
136 return errors.Wrap(err, "failed to get corresponding node")
137 }
138
139 criSocket, ok := node.ObjectMeta.Annotations[constants.AnnotationKubeadmCRISocket]
140 if !ok {
141 return errors.Errorf("node %s doesn't have %s annotation", nodeName, constants.AnnotationKubeadmCRISocket)
142 }
143
144
145 nodeRegistration.Name = nodeName
146 nodeRegistration.CRISocket = criSocket
147 nodeRegistration.Taints = node.Spec.Taints
148
149
150
151 return nil
152 }
153
154
155
156
157 func getNodeNameFromKubeletConfig(fileName string) (string, error) {
158
159 config, err := clientcmd.LoadFromFile(fileName)
160 if err != nil {
161 return "", err
162 }
163
164
165 currentContext, exists := config.Contexts[config.CurrentContext]
166 if !exists {
167 return "", errors.Errorf("invalid kubeconfig file %s: missing context %s", fileName, config.CurrentContext)
168 }
169 authInfo, exists := config.AuthInfos[currentContext.AuthInfo]
170 if !exists {
171 return "", errors.Errorf("invalid kubeconfig file %s: missing AuthInfo %s", fileName, currentContext.AuthInfo)
172 }
173
174
175 var certs []*x509.Certificate
176 if len(authInfo.ClientCertificateData) > 0 {
177
178 if certs, err = certutil.ParseCertsPEM(authInfo.ClientCertificateData); err != nil {
179 return "", err
180 }
181 } else if len(authInfo.ClientCertificate) > 0 {
182
183 if certs, err = certutil.CertsFromFile(authInfo.ClientCertificate); err != nil {
184 return "", err
185 }
186 } else {
187 return "", errors.Errorf("invalid kubeconfig file %s. x509 certificate expected", fileName)
188 }
189
190
191
192 cert := certs[0]
193
194
195 return strings.TrimPrefix(cert.Subject.CommonName, constants.NodesUserPrefix), nil
196 }
197
198 func getAPIEndpoint(client clientset.Interface, nodeName string, apiEndpoint *kubeadmapi.APIEndpoint) error {
199 return getAPIEndpointWithRetry(client, nodeName, apiEndpoint,
200 constants.KubernetesAPICallRetryInterval, kubeadmapi.GetActiveTimeouts().KubernetesAPICall.Duration)
201 }
202
203 func getAPIEndpointWithRetry(client clientset.Interface, nodeName string, apiEndpoint *kubeadmapi.APIEndpoint,
204 interval, timeout time.Duration) error {
205 var err error
206 var errs []error
207
208 if err = getAPIEndpointFromPodAnnotation(client, nodeName, apiEndpoint, interval, timeout); err == nil {
209 return nil
210 }
211 errs = append(errs, errors.WithMessagef(err, "could not retrieve API endpoints for node %q using pod annotations", nodeName))
212 return errorsutil.NewAggregate(errs)
213 }
214
215 func getAPIEndpointFromPodAnnotation(client clientset.Interface, nodeName string, apiEndpoint *kubeadmapi.APIEndpoint,
216 interval, timeout time.Duration) error {
217 var rawAPIEndpoint string
218 var lastErr error
219
220
221 err := wait.PollUntilContextTimeout(context.Background(), interval, timeout, true,
222 func(ctx context.Context) (bool, error) {
223 rawAPIEndpoint, lastErr = getRawAPIEndpointFromPodAnnotationWithoutRetry(ctx, client, nodeName)
224 return lastErr == nil, nil
225 })
226 if err != nil {
227 return err
228 }
229 parsedAPIEndpoint, err := kubeadmapi.APIEndpointFromString(rawAPIEndpoint)
230 if err != nil {
231 return errors.Wrapf(err, "could not parse API endpoint for node %q", nodeName)
232 }
233 *apiEndpoint = parsedAPIEndpoint
234 return nil
235 }
236
237 func getRawAPIEndpointFromPodAnnotationWithoutRetry(ctx context.Context, client clientset.Interface, nodeName string) (string, error) {
238 podList, err := client.CoreV1().Pods(metav1.NamespaceSystem).List(
239 ctx,
240 metav1.ListOptions{
241 FieldSelector: fmt.Sprintf("spec.nodeName=%s", nodeName),
242 LabelSelector: fmt.Sprintf("component=%s,tier=%s", constants.KubeAPIServer, constants.ControlPlaneTier),
243 },
244 )
245 if err != nil {
246 return "", errors.Wrap(err, "could not retrieve list of pods to determine api server endpoints")
247 }
248 if len(podList.Items) != 1 {
249 return "", errors.Errorf("API server pod for node name %q has %d entries, only one was expected", nodeName, len(podList.Items))
250 }
251 if apiServerEndpoint, ok := podList.Items[0].Annotations[constants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey]; ok {
252 return apiServerEndpoint, nil
253 }
254 return "", errors.Errorf("API server pod for node name %q hasn't got a %q annotation, cannot retrieve API endpoint", nodeName, constants.KubeAPIServerAdvertiseAddressEndpointAnnotationKey)
255 }
256
View as plain text