blob: 639e891d4177c80acfbe03e7fae55215f28f362f [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"
Serge Bazanski71f7a562020-06-22 16:37:28 +020025 "io/ioutil"
Lorenz Brun8e3b8fc2020-05-19 14:29:40 +020026 "net"
Serge Bazanski71f7a562020-06-22 16:37:28 +020027 "os"
Lorenz Brun878f5f92020-05-12 16:15:39 +020028 "os/exec"
29
Serge Bazanskidbfc6382020-06-19 20:35:43 +020030 "go.etcd.io/etcd/clientv3"
31
Serge Bazanskie6030f62020-06-03 17:52:59 +020032 "git.monogon.dev/source/nexantic.git/core/internal/common/supervisor"
Serge Bazanskidbfc6382020-06-19 20:35:43 +020033 "git.monogon.dev/source/nexantic.git/core/internal/kubernetes/pki"
Serge Bazanskie6030f62020-06-03 17:52:59 +020034 "git.monogon.dev/source/nexantic.git/core/internal/kubernetes/reconciler"
35 "git.monogon.dev/source/nexantic.git/core/pkg/fileargs"
36
Lorenz Brun878f5f92020-05-12 16:15:39 +020037 v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Lorenz Brun8e3b8fc2020-05-19 14:29:40 +020038 kubeletconfig "k8s.io/kubelet/config/v1beta1"
Lorenz Brun878f5f92020-05-12 16:15:39 +020039)
40
Serge Bazanskidbfc6382020-06-19 20:35:43 +020041var (
42 kubeletRoot = "/data/kubernetes"
43 kubeletKubeconfig = kubeletRoot + "/kubelet.kubeconfig"
44 kubeletCACert = kubeletRoot + "/ca.crt"
45 kubeletCert = kubeletRoot + "/kubelet.crt"
46 kubeletKey = kubeletRoot + "/kubelet.key"
47)
48
Lorenz Brun878f5f92020-05-12 16:15:39 +020049type KubeletSpec struct {
50 clusterDNS []net.IP
51}
52
Serge Bazanskidbfc6382020-06-19 20:35:43 +020053func createKubeletConfig(ctx context.Context, kv clientv3.KV, kpki *pki.KubernetesPKI, nodeName string) error {
54 identity := fmt.Sprintf("system:node:%s", nodeName)
55
56 ca := kpki.Certificates[pki.IdCA]
57 cacert, _, err := ca.Ensure(ctx, 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 ensure ca certificate: %w", err)
Serge Bazanski71f7a562020-06-22 16:37:28 +020060 }
61
Serge Bazanskidbfc6382020-06-19 20:35:43 +020062 kubeconfig, err := pki.New(ca, "", pki.Client(identity, []string{"system:nodes"})).Kubeconfig(ctx, kv)
Serge Bazanski71f7a562020-06-22 16:37:28 +020063 if err != nil {
Serge Bazanskidbfc6382020-06-19 20:35:43 +020064 return fmt.Errorf("could not create volatile kubelet client cert: %w", err)
Serge Bazanski71f7a562020-06-22 16:37:28 +020065 }
Serge Bazanskidbfc6382020-06-19 20:35:43 +020066
67 cert, key, err := pki.New(ca, "volatile", pki.Server([]string{nodeName}, nil)).Ensure(ctx, kv)
68 if err != nil {
69 return fmt.Errorf("could not create volatile kubelet server cert: %w", err)
Serge Bazanski71f7a562020-06-22 16:37:28 +020070 }
Serge Bazanskidbfc6382020-06-19 20:35:43 +020071
72 if err := os.MkdirAll(kubeletRoot, 0755); err != nil {
73 return fmt.Errorf("could not create kubelet root directory: %w", err)
Serge Bazanski71f7a562020-06-22 16:37:28 +020074 }
Serge Bazanskidbfc6382020-06-19 20:35:43 +020075 // TODO(q3k): this should probably become its own function //core/internal/kubernetes/pki.
76 for _, el := range []struct {
77 target string
78 data []byte
79 }{
80 {kubeletKubeconfig, kubeconfig},
81 {kubeletCACert, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cacert})},
82 {kubeletCert, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert})},
83 {kubeletKey, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: key})},
84 } {
85 if err := ioutil.WriteFile(el.target, el.data, 0400); err != nil {
86 return fmt.Errorf("could not write %q: %w", el.target, err)
87 }
Serge Bazanski71f7a562020-06-22 16:37:28 +020088 }
89
90 return nil
91}
92
Lorenz Brun8e3b8fc2020-05-19 14:29:40 +020093func runKubelet(spec *KubeletSpec, output io.Writer) supervisor.Runnable {
94 return func(ctx context.Context) error {
95 fargs, err := fileargs.New()
96 if err != nil {
97 return err
98 }
99 var clusterDNS []string
100 for _, dnsIP := range spec.clusterDNS {
101 clusterDNS = append(clusterDNS, dnsIP.String())
102 }
Lorenz Brun878f5f92020-05-12 16:15:39 +0200103
Lorenz Brun8e3b8fc2020-05-19 14:29:40 +0200104 kubeletConf := &kubeletconfig.KubeletConfiguration{
105 TypeMeta: v1.TypeMeta{
106 Kind: "KubeletConfiguration",
107 APIVersion: kubeletconfig.GroupName + "/v1beta1",
Lorenz Brun878f5f92020-05-12 16:15:39 +0200108 },
Lorenz Brun8e3b8fc2020-05-19 14:29:40 +0200109 TLSCertFile: "/data/kubernetes/kubelet.crt",
110 TLSPrivateKeyFile: "/data/kubernetes/kubelet.key",
111 TLSMinVersion: "VersionTLS13",
112 ClusterDNS: clusterDNS,
113 Authentication: kubeletconfig.KubeletAuthentication{
114 X509: kubeletconfig.KubeletX509Authentication{
115 ClientCAFile: "/data/kubernetes/ca.crt",
116 },
117 },
Serge Bazanskie6030f62020-06-03 17:52:59 +0200118 // TODO(q3k): move reconciler.False to a generic package, fix the following references.
Lorenz Brun8e3b8fc2020-05-19 14:29:40 +0200119 ClusterDomain: "cluster.local", // cluster.local is hardcoded in the certificate too currently
Serge Bazanskie6030f62020-06-03 17:52:59 +0200120 EnableControllerAttachDetach: reconciler.False(),
Lorenz Brun8e3b8fc2020-05-19 14:29:40 +0200121 HairpinMode: "none",
Serge Bazanskie6030f62020-06-03 17:52:59 +0200122 MakeIPTablesUtilChains: reconciler.False(), // We don't have iptables
123 FailSwapOn: reconciler.False(), // Our kernel doesn't have swap enabled which breaks Kubelet's detection
Lorenz Brun8e3b8fc2020-05-19 14:29:40 +0200124 KubeReserved: map[string]string{
125 "cpu": "200m",
126 "memory": "300Mi",
127 },
Lorenz Brun0db90ba2020-04-06 14:04:52 +0200128
Lorenz Brun8e3b8fc2020-05-19 14:29:40 +0200129 // We're not going to use this, but let's make it point to a known-empty directory in case anybody manages to
130 // trigger it.
131 VolumePluginDir: "/kubernetes/conf/flexvolume-plugins",
132 }
Lorenz Brun878f5f92020-05-12 16:15:39 +0200133
Lorenz Brun8e3b8fc2020-05-19 14:29:40 +0200134 configRaw, err := json.Marshal(kubeletConf)
135 if err != nil {
136 return err
137 }
138 cmd := exec.CommandContext(ctx, "/kubernetes/bin/kube", "kubelet",
139 fargs.FileOpt("--config", "config.json", configRaw),
140 "--container-runtime=remote",
141 "--container-runtime-endpoint=unix:///containerd/run/containerd.sock",
142 "--kubeconfig=/data/kubernetes/kubelet.kubeconfig",
143 "--root-dir=/data/kubernetes/kubelet",
144 )
145 cmd.Env = []string{"PATH=/kubernetes/bin"}
146 cmd.Stdout = output
147 cmd.Stderr = output
148
149 supervisor.Signal(ctx, supervisor.SignalHealthy)
150 err = cmd.Run()
151 fmt.Fprintf(output, "kubelet stopped: %v\n", err)
Lorenz Brun878f5f92020-05-12 16:15:39 +0200152 return err
153 }
Lorenz Brun878f5f92020-05-12 16:15:39 +0200154}