blob: 136bc28c1305a3386224a406b8ef27c94cddaa35 [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"
Serge Bazanski2cfafc92023-03-21 16:42:47 +010021 "crypto/ed25519"
Lorenz Brun878f5f92020-05-12 16:15:39 +020022 "encoding/json"
Serge Bazanski2cfafc92023-03-21 16:42:47 +010023 "encoding/pem"
Lorenz Brun878f5f92020-05-12 16:15:39 +020024 "fmt"
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 Bazanski2cfafc92023-03-21 16:42:47 +010031 ipb "source.monogon.dev/metropolis/node/core/curator/proto/api"
Tim Windelschmidt93020d72024-02-13 18:13:07 +010032
Serge Bazanski31370b02021-01-07 16:31:14 +010033 "source.monogon.dev/metropolis/node/core/localstorage"
Serge Bazanski31370b02021-01-07 16:31:14 +010034 "source.monogon.dev/metropolis/node/kubernetes/pki"
35 "source.monogon.dev/metropolis/node/kubernetes/reconciler"
36 "source.monogon.dev/metropolis/pkg/fileargs"
37 "source.monogon.dev/metropolis/pkg/supervisor"
Lorenz Brun878f5f92020-05-12 16:15:39 +020038)
39
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020040type kubeletService struct {
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020041 ClusterDNS []net.IP
Lorenz Brun78cefca2022-06-20 12:59:55 +000042 ClusterDomain string
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020043 KubeletDirectory *localstorage.DataKubernetesKubeletDirectory
44 EphemeralDirectory *localstorage.EphemeralDirectory
Serge Bazanski9411f7c2021-03-10 13:12:53 +010045
Serge Bazanski2cfafc92023-03-21 16:42:47 +010046 kubeconfig []byte
47 serverCACert []byte
48 serverCert []byte
Lorenz Brun878f5f92020-05-12 16:15:39 +020049}
50
Serge Bazanski2cfafc92023-03-21 16:42:47 +010051func (s *kubeletService) getPubkey(ctx context.Context) (ed25519.PublicKey, error) {
52 // First make sure we have a local ED25519 private key, and generate one if not.
53 if err := s.KubeletDirectory.PKI.GeneratePrivateKey(); err != nil {
54 return nil, fmt.Errorf("failed to generate private key: %w", err)
55 }
56 priv, err := s.KubeletDirectory.PKI.ReadPrivateKey()
Serge Bazanski71f7a562020-06-22 16:37:28 +020057 if err != nil {
Serge Bazanski2cfafc92023-03-21 16:42:47 +010058 return nil, fmt.Errorf("could not read keypair: %w", err)
59 }
60 pubkey := priv.Public().(ed25519.PublicKey)
61 return pubkey, nil
62}
63
64func (s *kubeletService) setCertificates(kw *ipb.IssueCertificateResponse_KubernetesWorker) error {
65 key, err := s.KubeletDirectory.PKI.ReadPrivateKey()
66 if err != nil {
67 return fmt.Errorf("could not read private key from disk: %w", err)
Serge Bazanski71f7a562020-06-22 16:37:28 +020068 }
69
Serge Bazanski2cfafc92023-03-21 16:42:47 +010070 s.kubeconfig, err = pki.KubeconfigRaw(kw.IdentityCaCertificate, kw.KubeletClientCertificate, key, pki.KubernetesAPIEndpointForWorker)
Serge Bazanski71f7a562020-06-22 16:37:28 +020071 if err != nil {
Serge Bazanski9411f7c2021-03-10 13:12:53 +010072 return fmt.Errorf("when generating kubeconfig: %w", err)
Serge Bazanski71f7a562020-06-22 16:37:28 +020073 }
Serge Bazanski2cfafc92023-03-21 16:42:47 +010074 s.serverCACert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: kw.IdentityCaCertificate})
75 s.serverCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: kw.KubeletServerCertificate})
Serge Bazanski71f7a562020-06-22 16:37:28 +020076 return nil
77}
78
Serge Bazanski2cfafc92023-03-21 16:42:47 +010079func (s *kubeletService) configure(fargs *fileargs.FileArgs) *kubeletconfig.KubeletConfiguration {
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020080 var clusterDNS []string
81 for _, dnsIP := range s.ClusterDNS {
82 clusterDNS = append(clusterDNS, dnsIP.String())
83 }
Lorenz Brun878f5f92020-05-12 16:15:39 +020084
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020085 return &kubeletconfig.KubeletConfiguration{
86 TypeMeta: v1.TypeMeta{
87 Kind: "KubeletConfiguration",
88 APIVersion: kubeletconfig.GroupName + "/v1beta1",
89 },
Serge Bazanski2cfafc92023-03-21 16:42:47 +010090 TLSCertFile: fargs.ArgPath("server.crt", s.serverCert),
91 TLSPrivateKeyFile: s.KubeletDirectory.PKI.Key.FullPath(),
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020092 TLSMinVersion: "VersionTLS13",
93 ClusterDNS: clusterDNS,
94 Authentication: kubeletconfig.KubeletAuthentication{
95 X509: kubeletconfig.KubeletX509Authentication{
Serge Bazanski2cfafc92023-03-21 16:42:47 +010096 ClientCAFile: fargs.ArgPath("ca.crt", s.serverCACert),
Lorenz Brun878f5f92020-05-12 16:15:39 +020097 },
Serge Bazanskic2c7ad92020-07-13 17:20:09 +020098 },
99 // TODO(q3k): move reconciler.False to a generic package, fix the following references.
Lorenz Brun78cefca2022-06-20 12:59:55 +0000100 ClusterDomain: s.ClusterDomain,
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200101 EnableControllerAttachDetach: reconciler.False(),
102 HairpinMode: "none",
103 MakeIPTablesUtilChains: reconciler.False(), // We don't have iptables
104 FailSwapOn: reconciler.False(), // Our kernel doesn't have swap enabled which breaks Kubelet's detection
Lorenz Brun98206b92022-06-22 16:21:50 +0000105 CgroupRoot: "/",
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200106 KubeReserved: map[string]string{
107 "cpu": "200m",
108 "memory": "300Mi",
109 },
Lorenz Brun0db90ba2020-04-06 14:04:52 +0200110
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200111 // We're not going to use this, but let's make it point to a
112 // known-empty directory in case anybody manages to trigger it.
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200113 VolumePluginDir: s.EphemeralDirectory.FlexvolumePlugins.FullPath(),
Lorenz Brun2f7e0a22023-06-22 16:56:13 +0200114 // Currently we allocate a /24 per node, so we can have a maximum of
115 // 253 pods per node.
116 MaxPods: 253,
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200117 }
118}
Lorenz Brun878f5f92020-05-12 16:15:39 +0200119
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200120func (s *kubeletService) Run(ctx context.Context) error {
Serge Bazanski2cfafc92023-03-21 16:42:47 +0100121 if len(s.serverCert) == 0 || len(s.serverCACert) == 0 || len(s.kubeconfig) == 0 {
122 return fmt.Errorf("setCertificates was not called")
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200123 }
124
125 fargs, err := fileargs.New()
126 if err != nil {
Lorenz Brun878f5f92020-05-12 16:15:39 +0200127 return err
128 }
Serge Bazanski2cfafc92023-03-21 16:42:47 +0100129 defer fargs.Close()
130
131 configRaw, err := json.Marshal(s.configure(fargs))
132 if err != nil {
133 return fmt.Errorf("when marshaling kubelet configuration: %w", err)
134 }
135
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200136 cmd := exec.CommandContext(ctx, "/kubernetes/bin/kube", "kubelet",
137 fargs.FileOpt("--config", "config.json", configRaw),
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200138 fmt.Sprintf("--container-runtime-endpoint=unix://%s", s.EphemeralDirectory.Containerd.ClientSocket.FullPath()),
Tim Windelschmidt93020d72024-02-13 18:13:07 +0100139 //TODO: Remove with k8s 1.29 (https://github.com/kubernetes/kubernetes/pull/118544)
140 "--pod-infra-container-image", "preseed.metropolis.internal/node/kubernetes/pause:latest",
Serge Bazanski2cfafc92023-03-21 16:42:47 +0100141 fargs.FileOpt("--kubeconfig", "kubeconfig", s.kubeconfig),
Serge Bazanskic2c7ad92020-07-13 17:20:09 +0200142 fmt.Sprintf("--root-dir=%s", s.KubeletDirectory.FullPath()),
143 )
144 cmd.Env = []string{"PATH=/kubernetes/bin"}
Serge Bazanski05604292021-03-12 17:47:21 +0100145 return supervisor.RunCommand(ctx, cmd, supervisor.ParseKLog())
Lorenz Brun878f5f92020-05-12 16:15:39 +0200146}