blob: 741dba88650adc5e814bcbb9fc3b4cc1d2155eb3 [file] [log] [blame]
Lorenz Brun878f5f92020-05-12 16:15:39 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package kubernetes
18
19import (
20 "context"
Lorenz Brun878f5f92020-05-12 16:15:39 +020021 "encoding/json"
Serge Bazanski71f7a562020-06-22 16:37:28 +020022 "encoding/pem"
Lorenz Brun878f5f92020-05-12 16:15:39 +020023 "fmt"
Lorenz Brun8e3b8fc2020-05-19 14:29:40 +020024 "io"
Lorenz Brun8e3b8fc2020-05-19 14:29:40 +020025 "net"
Lorenz Brun878f5f92020-05-12 16:15:39 +020026 "os/exec"
27
Lorenz Brun878f5f92020-05-12 16:15:39 +020028 v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Lorenz Brun8e3b8fc2020-05-19 14:29:40 +020029 kubeletconfig "k8s.io/kubelet/config/v1beta1"
Serge Bazanski77cb6c52020-12-19 00:09:22 +010030
Serge Bazanski31370b02021-01-07 16:31:14 +010031 "source.monogon.dev/metropolis/node/core/localstorage"
32 "source.monogon.dev/metropolis/node/core/localstorage/declarative"
33 "source.monogon.dev/metropolis/node/kubernetes/pki"
34 "source.monogon.dev/metropolis/node/kubernetes/reconciler"
35 "source.monogon.dev/metropolis/pkg/fileargs"
36 "source.monogon.dev/metropolis/pkg/supervisor"
Lorenz Brun878f5f92020-05-12 16:15:39 +020037)
38
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020039type kubeletService struct {
40 NodeName string
41 ClusterDNS []net.IP
42 KubeletDirectory *localstorage.DataKubernetesKubeletDirectory
43 EphemeralDirectory *localstorage.EphemeralDirectory
44 Output io.Writer
45 KPKI *pki.KubernetesPKI
Lorenz Brun878f5f92020-05-12 16:15:39 +020046}
47
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020048func (s *kubeletService) createCertificates(ctx context.Context) error {
49 identity := fmt.Sprintf("system:node:%s", s.NodeName)
Serge Bazanskidbfc6382020-06-19 20:35:43 +020050
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020051 ca := s.KPKI.Certificates[pki.IdCA]
52 cacert, _, err := ca.Ensure(ctx, s.KPKI.KV)
Serge Bazanski71f7a562020-06-22 16:37:28 +020053 if err != nil {
Serge Bazanskidbfc6382020-06-19 20:35:43 +020054 return fmt.Errorf("could not ensure ca certificate: %w", err)
Serge Bazanski71f7a562020-06-22 16:37:28 +020055 }
56
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020057 kubeconfig, err := pki.New(ca, "", pki.Client(identity, []string{"system:nodes"})).Kubeconfig(ctx, s.KPKI.KV)
Serge Bazanski71f7a562020-06-22 16:37:28 +020058 if err != nil {
Serge Bazanskidbfc6382020-06-19 20:35:43 +020059 return fmt.Errorf("could not create volatile kubelet client cert: %w", err)
Serge Bazanski71f7a562020-06-22 16:37:28 +020060 }
Serge Bazanskidbfc6382020-06-19 20:35:43 +020061
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020062 cert, key, err := pki.New(ca, "", pki.Server([]string{s.NodeName}, nil)).Ensure(ctx, s.KPKI.KV)
Serge Bazanskidbfc6382020-06-19 20:35:43 +020063 if err != nil {
64 return fmt.Errorf("could not create volatile kubelet server cert: %w", err)
Serge Bazanski71f7a562020-06-22 16:37:28 +020065 }
Serge Bazanskidbfc6382020-06-19 20:35:43 +020066
Serge Bazanski77cb6c52020-12-19 00:09:22 +010067 // TODO(q3k): this should probably become its own function //metropolis/node/kubernetes/pki.
Serge Bazanskidbfc6382020-06-19 20:35:43 +020068 for _, el := range []struct {
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020069 target declarative.FilePlacement
Serge Bazanskidbfc6382020-06-19 20:35:43 +020070 data []byte
71 }{
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020072 {s.KubeletDirectory.Kubeconfig, kubeconfig},
73 {s.KubeletDirectory.PKI.CACertificate, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cacert})},
74 {s.KubeletDirectory.PKI.Certificate, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert})},
75 {s.KubeletDirectory.PKI.Key, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: key})},
Serge Bazanskidbfc6382020-06-19 20:35:43 +020076 } {
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020077 if err := el.target.Write(el.data, 0400); err != nil {
78 return fmt.Errorf("could not write %v: %w", el.target, err)
Serge Bazanskidbfc6382020-06-19 20:35:43 +020079 }
Serge Bazanski71f7a562020-06-22 16:37:28 +020080 }
81
82 return nil
83}
84
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020085func (s *kubeletService) configure() *kubeletconfig.KubeletConfiguration {
86 var clusterDNS []string
87 for _, dnsIP := range s.ClusterDNS {
88 clusterDNS = append(clusterDNS, dnsIP.String())
89 }
Lorenz Brun878f5f92020-05-12 16:15:39 +020090
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020091 return &kubeletconfig.KubeletConfiguration{
92 TypeMeta: v1.TypeMeta{
93 Kind: "KubeletConfiguration",
94 APIVersion: kubeletconfig.GroupName + "/v1beta1",
95 },
96 TLSCertFile: s.KubeletDirectory.PKI.Certificate.FullPath(),
97 TLSPrivateKeyFile: s.KubeletDirectory.PKI.Key.FullPath(),
98 TLSMinVersion: "VersionTLS13",
99 ClusterDNS: clusterDNS,
100 Authentication: kubeletconfig.KubeletAuthentication{
101 X509: kubeletconfig.KubeletX509Authentication{
102 ClientCAFile: s.KubeletDirectory.PKI.CACertificate.FullPath(),
Lorenz Brun878f5f92020-05-12 16:15:39 +0200103 },
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200104 },
105 // TODO(q3k): move reconciler.False to a generic package, fix the following references.
106 ClusterDomain: "cluster.local", // cluster.local is hardcoded in the certificate too currently
107 EnableControllerAttachDetach: reconciler.False(),
108 HairpinMode: "none",
109 MakeIPTablesUtilChains: reconciler.False(), // We don't have iptables
110 FailSwapOn: reconciler.False(), // Our kernel doesn't have swap enabled which breaks Kubelet's detection
111 KubeReserved: map[string]string{
112 "cpu": "200m",
113 "memory": "300Mi",
114 },
Lorenz Brun0db90ba2020-04-06 14:04:52 +0200115
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200116 // We're not going to use this, but let's make it point to a known-empty directory in case anybody manages to
117 // trigger it.
118 VolumePluginDir: s.EphemeralDirectory.FlexvolumePlugins.FullPath(),
119 }
120}
Lorenz Brun878f5f92020-05-12 16:15:39 +0200121
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200122func (s *kubeletService) Run(ctx context.Context) error {
123 if err := s.createCertificates(ctx); err != nil {
124 return fmt.Errorf("when creating certificates: %w", err)
125 }
Lorenz Brun8e3b8fc2020-05-19 14:29:40 +0200126
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200127 configRaw, err := json.Marshal(s.configure())
128 if err != nil {
129 return fmt.Errorf("when marshaling kubelet configuration: %w", err)
130 }
131
132 fargs, err := fileargs.New()
133 if err != nil {
Lorenz Brun878f5f92020-05-12 16:15:39 +0200134 return err
135 }
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200136 cmd := exec.CommandContext(ctx, "/kubernetes/bin/kube", "kubelet",
137 fargs.FileOpt("--config", "config.json", configRaw),
138 "--container-runtime=remote",
139 fmt.Sprintf("--container-runtime-endpoint=unix://%s", s.EphemeralDirectory.Containerd.ClientSocket.FullPath()),
140 fmt.Sprintf("--kubeconfig=%s", s.KubeletDirectory.Kubeconfig.FullPath()),
141 fmt.Sprintf("--root-dir=%s", s.KubeletDirectory.FullPath()),
142 )
143 cmd.Env = []string{"PATH=/kubernetes/bin"}
Serge Bazanski967be212020-11-02 11:26:59 +0100144 return supervisor.RunCommand(ctx, cmd)
Lorenz Brun878f5f92020-05-12 16:15:39 +0200145}