1
16
17 package dns
18
19 import (
20 "context"
21 "fmt"
22 "io"
23 "strings"
24
25 "github.com/coredns/corefile-migration/migration"
26 "github.com/pkg/errors"
27
28 apps "k8s.io/api/apps/v1"
29 v1 "k8s.io/api/core/v1"
30 rbac "k8s.io/api/rbac/v1"
31 apierrors "k8s.io/apimachinery/pkg/api/errors"
32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33 kuberuntime "k8s.io/apimachinery/pkg/runtime"
34 "k8s.io/apimachinery/pkg/types"
35 clientset "k8s.io/client-go/kubernetes"
36 clientsetscheme "k8s.io/client-go/kubernetes/scheme"
37 "k8s.io/klog/v2"
38
39 kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
40 kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
41 "k8s.io/kubernetes/cmd/kubeadm/app/images"
42 kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
43 "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
44 "k8s.io/kubernetes/cmd/kubeadm/app/util/image"
45 )
46
47 const (
48 unableToDecodeCoreDNS = "unable to decode CoreDNS"
49 coreDNSReplicas = 2
50 )
51
52
53 func DeployedDNSAddon(client clientset.Interface) (string, error) {
54 deploymentsClient := client.AppsV1().Deployments(metav1.NamespaceSystem)
55 deployments, err := deploymentsClient.List(context.TODO(), metav1.ListOptions{LabelSelector: "k8s-app=kube-dns"})
56 if err != nil {
57 return "", errors.Wrap(err, "couldn't retrieve DNS addon deployments")
58 }
59
60 switch len(deployments.Items) {
61 case 0:
62 return "", nil
63 case 1:
64 return image.TagFromImage(deployments.Items[0].Spec.Template.Spec.Containers[0].Image), nil
65 default:
66 return "", errors.Errorf("multiple DNS addon deployments found: %v", deployments.Items)
67 }
68 }
69
70
71 func deployedDNSReplicas(client clientset.Interface, replicas int32) (*int32, error) {
72 deploymentsClient := client.AppsV1().Deployments(metav1.NamespaceSystem)
73 deployments, err := deploymentsClient.List(context.TODO(), metav1.ListOptions{LabelSelector: "k8s-app=kube-dns"})
74 if err != nil {
75 return &replicas, errors.Wrap(err, "couldn't retrieve DNS addon deployments")
76 }
77 switch len(deployments.Items) {
78 case 0:
79 return &replicas, nil
80 case 1:
81 return deployments.Items[0].Spec.Replicas, nil
82 default:
83 return &replicas, errors.Errorf("multiple DNS addon deployments found: %v", deployments.Items)
84 }
85 }
86
87
88 func EnsureDNSAddon(cfg *kubeadmapi.ClusterConfiguration, client clientset.Interface, out io.Writer, printManifest bool) error {
89 var replicas *int32
90 var err error
91 if !printManifest {
92 replicas, err = deployedDNSReplicas(client, coreDNSReplicas)
93 if err != nil {
94 return err
95 }
96 } else {
97 var defaultReplicas int32 = coreDNSReplicas
98 replicas = &defaultReplicas
99 }
100 return coreDNSAddon(cfg, client, replicas, out, printManifest)
101 }
102
103 func coreDNSAddon(cfg *kubeadmapi.ClusterConfiguration, client clientset.Interface, replicas *int32, out io.Writer, printManifest bool) error {
104
105 coreDNSDeploymentBytes, err := kubeadmutil.ParseTemplate(CoreDNSDeployment, struct {
106 DeploymentName, Image, ControlPlaneTaintKey string
107 Replicas *int32
108 }{
109 DeploymentName: kubeadmconstants.CoreDNSDeploymentName,
110 Image: images.GetDNSImage(cfg),
111 ControlPlaneTaintKey: kubeadmconstants.LabelNodeRoleControlPlane,
112 Replicas: replicas,
113 })
114 if err != nil {
115 return errors.Wrap(err, "error when parsing CoreDNS deployment template")
116 }
117
118
119 coreDNSConfigMapBytes, err := kubeadmutil.ParseTemplate(CoreDNSConfigMap, struct{ DNSDomain, UpstreamNameserver, StubDomain string }{
120 DNSDomain: cfg.Networking.DNSDomain,
121 })
122 if err != nil {
123 return errors.Wrap(err, "error when parsing CoreDNS configMap template")
124 }
125
126 dnsip, err := kubeadmconstants.GetDNSIP(cfg.Networking.ServiceSubnet)
127 if err != nil {
128 return err
129 }
130
131 coreDNSServiceBytes, err := kubeadmutil.ParseTemplate(CoreDNSService, struct{ DNSIP string }{
132 DNSIP: dnsip.String(),
133 })
134
135 if err != nil {
136 return errors.Wrap(err, "error when parsing CoreDNS service template")
137 }
138
139 if printManifest {
140 fmt.Fprint(out, "---")
141 fmt.Fprintf(out, "%s", coreDNSDeploymentBytes)
142 fmt.Fprint(out, "---")
143 fmt.Fprintf(out, "%s", coreDNSConfigMapBytes)
144 fmt.Fprint(out, "---")
145 fmt.Fprintf(out, "%s", coreDNSServiceBytes)
146 fmt.Fprint(out, "---")
147 fmt.Fprintf(out, "%s", []byte(CoreDNSClusterRole))
148 fmt.Fprint(out, "---")
149 fmt.Fprintf(out, "%s", []byte(CoreDNSClusterRoleBinding))
150 fmt.Fprint(out, "---")
151 fmt.Fprintf(out, "%s", []byte(CoreDNSServiceAccount))
152 return nil
153 }
154
155 if err := createCoreDNSAddon(coreDNSDeploymentBytes, coreDNSServiceBytes, coreDNSConfigMapBytes, client); err != nil {
156 return err
157 }
158 fmt.Fprintln(out, "[addons] Applied essential addon: CoreDNS")
159 return nil
160 }
161
162 func createCoreDNSAddon(deploymentBytes, serviceBytes, configBytes []byte, client clientset.Interface) error {
163 coreDNSConfigMap := &v1.ConfigMap{}
164 if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), configBytes, coreDNSConfigMap); err != nil {
165 return errors.Wrapf(err, "%s ConfigMap", unableToDecodeCoreDNS)
166 }
167
168
169 _, corefile, currentInstalledCoreDNSVersion, err := GetCoreDNSInfo(client)
170 if err != nil {
171 return errors.Wrap(err, "unable to fetch CoreDNS current installed version and ConfigMap.")
172 }
173
174 corefileMigrationRequired, err := isCoreDNSConfigMapMigrationRequired(corefile, currentInstalledCoreDNSVersion)
175 if err != nil {
176 return err
177 }
178
179
180 canMigrateCorefile := true
181
182 if corefile == "" || migration.Default("", corefile) {
183
184 if err := apiclient.CreateOrUpdateConfigMap(client, coreDNSConfigMap); err != nil {
185 return err
186 }
187 } else if corefileMigrationRequired {
188
189 if err := migrateCoreDNSCorefile(client, coreDNSConfigMap, corefile, currentInstalledCoreDNSVersion); err != nil {
190
191
192 canMigrateCorefile = false
193 klog.Warningf("the CoreDNS Configuration was not migrated: %v. The existing CoreDNS Corefile configuration has been retained.", err)
194 if err := apiclient.CreateOrRetainConfigMap(client, coreDNSConfigMap, kubeadmconstants.CoreDNSConfigMap); err != nil {
195 return err
196 }
197 }
198 } else {
199
200 if err := apiclient.CreateOrRetainConfigMap(client, coreDNSConfigMap, kubeadmconstants.CoreDNSConfigMap); err != nil {
201 return err
202 }
203 }
204
205 coreDNSClusterRoles := &rbac.ClusterRole{}
206 if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), []byte(CoreDNSClusterRole), coreDNSClusterRoles); err != nil {
207 return errors.Wrapf(err, "%s ClusterRole", unableToDecodeCoreDNS)
208 }
209
210
211 if err := apiclient.CreateOrUpdateClusterRole(client, coreDNSClusterRoles); err != nil {
212 return err
213 }
214
215 coreDNSClusterRolesBinding := &rbac.ClusterRoleBinding{}
216 if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), []byte(CoreDNSClusterRoleBinding), coreDNSClusterRolesBinding); err != nil {
217 return errors.Wrapf(err, "%s ClusterRoleBinding", unableToDecodeCoreDNS)
218 }
219
220
221 if err := apiclient.CreateOrUpdateClusterRoleBinding(client, coreDNSClusterRolesBinding); err != nil {
222 return err
223 }
224
225 coreDNSServiceAccount := &v1.ServiceAccount{}
226 if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), []byte(CoreDNSServiceAccount), coreDNSServiceAccount); err != nil {
227 return errors.Wrapf(err, "%s ServiceAccount", unableToDecodeCoreDNS)
228 }
229
230
231 if err := apiclient.CreateOrUpdateServiceAccount(client, coreDNSServiceAccount); err != nil {
232 return err
233 }
234
235 coreDNSDeployment := &apps.Deployment{}
236 if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), deploymentBytes, coreDNSDeployment); err != nil {
237 return errors.Wrapf(err, "%s Deployment", unableToDecodeCoreDNS)
238 }
239
240
241 if !canMigrateCorefile {
242 if err := apiclient.CreateOrRetainDeployment(client, coreDNSDeployment, kubeadmconstants.CoreDNSDeploymentName); err != nil {
243 return err
244 }
245 } else {
246
247 if err := apiclient.CreateOrUpdateDeployment(client, coreDNSDeployment); err != nil {
248 return err
249 }
250 }
251
252 coreDNSService := &v1.Service{}
253 return createDNSService(coreDNSService, serviceBytes, client)
254 }
255
256 func createDNSService(dnsService *v1.Service, serviceBytes []byte, client clientset.Interface) error {
257 if err := kuberuntime.DecodeInto(clientsetscheme.Codecs.UniversalDecoder(), serviceBytes, dnsService); err != nil {
258 return errors.Wrap(err, "unable to decode the DNS service")
259 }
260
261
262 if _, err := client.CoreV1().Services(metav1.NamespaceSystem).Create(context.TODO(), dnsService, metav1.CreateOptions{}); err != nil {
263
264
265
266 if !apierrors.IsAlreadyExists(err) && !apierrors.IsInvalid(err) {
267 return errors.Wrap(err, "unable to create a new DNS service")
268 }
269
270 if _, err := client.CoreV1().Services(metav1.NamespaceSystem).Update(context.TODO(), dnsService, metav1.UpdateOptions{}); err != nil {
271 return errors.Wrap(err, "unable to create/update the DNS service")
272 }
273 }
274 return nil
275 }
276
277
278 func isCoreDNSConfigMapMigrationRequired(corefile, currentInstalledCoreDNSVersion string) (bool, error) {
279 var isMigrationRequired bool
280
281
282 if currentInstalledCoreDNSVersion == "" {
283 return isMigrationRequired, nil
284 }
285 currentInstalledCoreDNSVersion = strings.TrimLeft(currentInstalledCoreDNSVersion, "v")
286 targetCoreDNSVersion := strings.TrimLeft(kubeadmconstants.CoreDNSVersion, "v")
287 if currentInstalledCoreDNSVersion == targetCoreDNSVersion {
288 return isMigrationRequired, nil
289 }
290 deprecated, err := migration.Deprecated(currentInstalledCoreDNSVersion, targetCoreDNSVersion, corefile)
291 if err != nil {
292 return isMigrationRequired, errors.Wrap(err, "unable to get list of changes to the configuration.")
293 }
294
295
296 for _, dep := range deprecated {
297 if dep.Severity == "removed" || dep.Severity == "newdefault" {
298 isMigrationRequired = true
299 }
300 }
301
302 return isMigrationRequired, nil
303 }
304
305 func migrateCoreDNSCorefile(client clientset.Interface, cm *v1.ConfigMap, corefile, currentInstalledCoreDNSVersion string) error {
306
307 updatedCorefile, err := migration.Migrate(currentInstalledCoreDNSVersion, strings.TrimLeft(kubeadmconstants.CoreDNSVersion, "v"), corefile, false)
308 if err != nil {
309 return errors.Wrap(err, "unable to migrate CoreDNS ConfigMap")
310 }
311
312
313 if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Update(context.TODO(), &v1.ConfigMap{
314 ObjectMeta: metav1.ObjectMeta{
315 Name: kubeadmconstants.CoreDNSConfigMap,
316 Namespace: metav1.NamespaceSystem,
317 },
318 Data: map[string]string{
319 "Corefile": updatedCorefile,
320 "Corefile-backup": corefile,
321 },
322 }, metav1.UpdateOptions{}); err != nil {
323 return errors.Wrap(err, "unable to update the CoreDNS ConfigMap")
324 }
325
326
327 if err := setCorefile(client, "Corefile-backup"); err != nil {
328 return err
329 }
330
331 fmt.Println("[addons] Migrating CoreDNS Corefile")
332 changes, err := migration.Deprecated(currentInstalledCoreDNSVersion, strings.TrimLeft(kubeadmconstants.CoreDNSVersion, "v"), corefile)
333 if err != nil {
334 return errors.Wrap(err, "unable to get list of changes to the configuration.")
335 }
336
337 klog.V(2).Infof("the CoreDNS configuration has been migrated and applied: %v.", updatedCorefile)
338 klog.V(2).Infoln("the old migration has been saved in the CoreDNS ConfigMap under the name [Corefile-backup]")
339 klog.V(2).Infoln("The changes in the new CoreDNS Configuration are as follows:")
340 for _, change := range changes {
341 klog.V(2).Infof("%v", change.ToString())
342 }
343 return nil
344 }
345
346
347 func GetCoreDNSInfo(client clientset.Interface) (*v1.ConfigMap, string, string, error) {
348 coreDNSConfigMap, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(context.TODO(), kubeadmconstants.CoreDNSConfigMap, metav1.GetOptions{})
349 if err != nil {
350 if apierrors.IsNotFound(err) {
351 return nil, "", "", nil
352 }
353 return nil, "", "", err
354 }
355 corefile, ok := coreDNSConfigMap.Data["Corefile"]
356 if !ok {
357 return nil, "", "", errors.New("unable to find the CoreDNS Corefile data")
358 }
359
360 currentCoreDNSversion, err := DeployedDNSAddon(client)
361 if err != nil {
362 return nil, "", "", err
363 }
364
365 return coreDNSConfigMap, corefile, currentCoreDNSversion, nil
366 }
367
368 func setCorefile(client clientset.Interface, coreDNSCorefileName string) error {
369 dnsDeployment, err := client.AppsV1().Deployments(metav1.NamespaceSystem).Get(context.TODO(), kubeadmconstants.CoreDNSDeploymentName, metav1.GetOptions{})
370 if err != nil {
371 return err
372 }
373 patch := fmt.Sprintf(`{"spec":{"template":{"spec":{"volumes":[{"name": "config-volume", "configMap":{"name": "coredns", "items":[{"key": "%s", "path": "Corefile"}]}}]}}}}`, coreDNSCorefileName)
374
375 if _, err := client.AppsV1().Deployments(dnsDeployment.ObjectMeta.Namespace).Patch(context.TODO(), dnsDeployment.Name, types.StrategicMergePatchType, []byte(patch), metav1.PatchOptions{}); err != nil {
376 return errors.Wrap(err, "unable to patch the CoreDNS deployment")
377 }
378 return nil
379 }
380
View as plain text