Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 1 | // 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 | |
| 17 | package kubernetes |
| 18 | |
| 19 | import ( |
Lorenz Brun | 878f5f9 | 2020-05-12 16:15:39 +0200 | [diff] [blame] | 20 | "context" |
Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 21 | "errors" |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 22 | "fmt" |
Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 23 | "net" |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 24 | "os" |
Lorenz Brun | b15abad | 2020-04-16 11:17:12 +0200 | [diff] [blame] | 25 | "time" |
| 26 | |
| 27 | "k8s.io/client-go/informers" |
| 28 | "k8s.io/client-go/tools/clientcmd" |
Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 29 | |
Lorenz Brun | 8e3b8fc | 2020-05-19 14:29:40 +0200 | [diff] [blame] | 30 | "go.etcd.io/etcd/clientv3" |
| 31 | "go.uber.org/zap" |
Lorenz Brun | 878f5f9 | 2020-05-12 16:15:39 +0200 | [diff] [blame] | 32 | "google.golang.org/grpc/codes" |
| 33 | "google.golang.org/grpc/status" |
Lorenz Brun | b15abad | 2020-04-16 11:17:12 +0200 | [diff] [blame] | 34 | "k8s.io/client-go/kubernetes" |
Lorenz Brun | 878f5f9 | 2020-05-12 16:15:39 +0200 | [diff] [blame] | 35 | |
| 36 | schema "git.monogon.dev/source/nexantic.git/core/generated/api" |
Lorenz Brun | 8e3b8fc | 2020-05-19 14:29:40 +0200 | [diff] [blame] | 37 | "git.monogon.dev/source/nexantic.git/core/internal/common/supervisor" |
Hendrik Hofstadt | 8efe51e | 2020-02-28 12:53:41 +0100 | [diff] [blame] | 38 | "git.monogon.dev/source/nexantic.git/core/internal/consensus" |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 39 | "git.monogon.dev/source/nexantic.git/core/internal/kubernetes/pki" |
Serge Bazanski | e6030f6 | 2020-06-03 17:52:59 +0200 | [diff] [blame] | 40 | "git.monogon.dev/source/nexantic.git/core/internal/kubernetes/reconciler" |
Lorenz Brun | 0db90ba | 2020-04-06 14:04:52 +0200 | [diff] [blame] | 41 | "git.monogon.dev/source/nexantic.git/core/internal/storage" |
Lorenz Brun | 8e3b8fc | 2020-05-19 14:29:40 +0200 | [diff] [blame] | 42 | "git.monogon.dev/source/nexantic.git/core/pkg/logbuffer" |
Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 43 | ) |
| 44 | |
| 45 | type Config struct { |
| 46 | AdvertiseAddress net.IP |
| 47 | ServiceIPRange net.IPNet |
| 48 | ClusterNet net.IPNet |
| 49 | } |
| 50 | |
| 51 | type Service struct { |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 52 | consensusService *consensus.Service |
| 53 | storageService *storage.Manager |
| 54 | logger *zap.Logger |
| 55 | |
Lorenz Brun | 878f5f9 | 2020-05-12 16:15:39 +0200 | [diff] [blame] | 56 | apiserverLogs *logbuffer.LogBuffer |
| 57 | controllerManagerLogs *logbuffer.LogBuffer |
| 58 | schedulerLogs *logbuffer.LogBuffer |
| 59 | kubeletLogs *logbuffer.LogBuffer |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 60 | |
| 61 | kpki *pki.KubernetesPKI |
Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 62 | } |
| 63 | |
Lorenz Brun | 0db90ba | 2020-04-06 14:04:52 +0200 | [diff] [blame] | 64 | func New(logger *zap.Logger, consensusService *consensus.Service, storageService *storage.Manager) *Service { |
Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 65 | s := &Service{ |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 66 | consensusService: consensusService, |
| 67 | storageService: storageService, |
| 68 | logger: logger, |
| 69 | |
Lorenz Brun | 878f5f9 | 2020-05-12 16:15:39 +0200 | [diff] [blame] | 70 | apiserverLogs: logbuffer.New(5000, 16384), |
| 71 | controllerManagerLogs: logbuffer.New(5000, 16384), |
| 72 | schedulerLogs: logbuffer.New(5000, 16384), |
| 73 | kubeletLogs: logbuffer.New(5000, 16384), |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 74 | |
| 75 | kpki: pki.NewKubernetes(logger.Named("pki")), |
Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 76 | } |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 77 | |
Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 78 | return s |
| 79 | } |
| 80 | |
| 81 | func (s *Service) getKV() clientv3.KV { |
| 82 | return s.consensusService.GetStore("kubernetes", "") |
| 83 | } |
| 84 | |
| 85 | func (s *Service) NewCluster() error { |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 86 | // TODO(q3k): this needs to be passed by the caller. |
| 87 | ctx := context.TODO() |
| 88 | return s.kpki.EnsureAll(ctx, s.getKV()) |
Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 89 | } |
| 90 | |
Lorenz Brun | 878f5f9 | 2020-05-12 16:15:39 +0200 | [diff] [blame] | 91 | // GetComponentLogs grabs logs from various Kubernetes binaries |
| 92 | func (s *Service) GetComponentLogs(component string, n int) ([]string, error) { |
| 93 | switch component { |
| 94 | case "apiserver": |
| 95 | return s.apiserverLogs.ReadLinesTruncated(n, "..."), nil |
| 96 | case "controller-manager": |
| 97 | return s.controllerManagerLogs.ReadLinesTruncated(n, "..."), nil |
| 98 | case "scheduler": |
| 99 | return s.schedulerLogs.ReadLinesTruncated(n, "..."), nil |
| 100 | case "kubelet": |
| 101 | return s.kubeletLogs.ReadLinesTruncated(n, "..."), nil |
| 102 | default: |
| 103 | return []string{}, errors.New("component not available") |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | // GetDebugKubeconfig issues a kubeconfig for an arbitrary given identity. Useful for debugging and testing. |
| 108 | func (s *Service) GetDebugKubeconfig(ctx context.Context, request *schema.GetDebugKubeconfigRequest) (*schema.GetDebugKubeconfigResponse, error) { |
Lorenz Brun | fc5dbc6 | 2020-05-28 12:18:07 +0200 | [diff] [blame] | 109 | if !s.consensusService.IsReady() { |
| 110 | return nil, status.Error(codes.Unavailable, "Consensus not ready yet") |
| 111 | } |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 112 | ca := s.kpki.Certificates[pki.IdCA] |
| 113 | debugKubeconfig, err := pki.New(ca, "", pki.Client(request.Id, request.Groups)).Kubeconfig(ctx, s.getKV()) |
Lorenz Brun | 878f5f9 | 2020-05-12 16:15:39 +0200 | [diff] [blame] | 114 | if err != nil { |
| 115 | return nil, status.Errorf(codes.Unavailable, "Failed to generate kubeconfig: %v", err) |
| 116 | } |
| 117 | return &schema.GetDebugKubeconfigResponse{DebugKubeconfig: string(debugKubeconfig)}, nil |
| 118 | } |
| 119 | |
Lorenz Brun | 8e3b8fc | 2020-05-19 14:29:40 +0200 | [diff] [blame] | 120 | func (s *Service) Start() error { |
| 121 | // TODO(lorenz): This creates a new supervision tree, it should instead attach to the root one. But for that SmalltownNode needs |
| 122 | // to be ported to supervisor. |
| 123 | supervisor.New(context.TODO(), s.logger, s.Run()) |
Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 124 | return nil |
| 125 | } |
| 126 | |
Lorenz Brun | 8e3b8fc | 2020-05-19 14:29:40 +0200 | [diff] [blame] | 127 | func (s *Service) Run() supervisor.Runnable { |
| 128 | return func(ctx context.Context) error { |
| 129 | config := Config{ |
| 130 | AdvertiseAddress: net.IP{10, 0, 2, 15}, // Depends on networking |
| 131 | ServiceIPRange: net.IPNet{ // TODO: Decide if configurable / final value |
| 132 | IP: net.IP{192, 168, 188, 0}, |
| 133 | Mask: net.IPMask{0xff, 0xff, 0xff, 0x00}, // /24, but Go stores as a literal mask |
| 134 | }, |
| 135 | ClusterNet: net.IPNet{ |
| 136 | IP: net.IP{192, 168, 188, 0}, |
| 137 | Mask: net.IPMask{0xff, 0xff, 0xfd, 0x00}, // /22 |
| 138 | }, |
| 139 | } |
| 140 | consensusKV := s.getKV() |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 141 | apiserverConfig, err := getPKIApiserverConfig(ctx, consensusKV, s.kpki) |
Lorenz Brun | 8e3b8fc | 2020-05-19 14:29:40 +0200 | [diff] [blame] | 142 | if err != nil { |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 143 | return fmt.Errorf("could not generate apiserver pki config: %w", err) |
Lorenz Brun | 8e3b8fc | 2020-05-19 14:29:40 +0200 | [diff] [blame] | 144 | } |
| 145 | apiserverConfig.advertiseAddress = config.AdvertiseAddress |
| 146 | apiserverConfig.serviceIPRange = config.ServiceIPRange |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 147 | controllerManagerConfig, err := getPKIControllerManagerConfig(ctx, consensusKV, s.kpki) |
Lorenz Brun | 8e3b8fc | 2020-05-19 14:29:40 +0200 | [diff] [blame] | 148 | if err != nil { |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 149 | return fmt.Errorf("could not generate controller manager pki config: %w", err) |
Lorenz Brun | 8e3b8fc | 2020-05-19 14:29:40 +0200 | [diff] [blame] | 150 | } |
| 151 | controllerManagerConfig.clusterNet = config.ClusterNet |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 152 | schedulerConfig, err := getPKISchedulerConfig(ctx, consensusKV, s.kpki) |
Lorenz Brun | 8e3b8fc | 2020-05-19 14:29:40 +0200 | [diff] [blame] | 153 | if err != nil { |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 154 | return fmt.Errorf("could not generate scheduler pki config: %w", err) |
Lorenz Brun | 8e3b8fc | 2020-05-19 14:29:40 +0200 | [diff] [blame] | 155 | } |
| 156 | |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 157 | masterKubeconfig, err := s.kpki.Kubeconfig(ctx, pki.Master, consensusKV) |
Lorenz Brun | 8e3b8fc | 2020-05-19 14:29:40 +0200 | [diff] [blame] | 158 | if err != nil { |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 159 | return fmt.Errorf("could not generate master kubeconfig: %w", err) |
Lorenz Brun | 8e3b8fc | 2020-05-19 14:29:40 +0200 | [diff] [blame] | 160 | } |
| 161 | |
Lorenz Brun | b15abad | 2020-04-16 11:17:12 +0200 | [diff] [blame] | 162 | rawClientConfig, err := clientcmd.NewClientConfigFromBytes(masterKubeconfig) |
| 163 | if err != nil { |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 164 | return fmt.Errorf("could not generate kubernetes client config: %w", err) |
Lorenz Brun | b15abad | 2020-04-16 11:17:12 +0200 | [diff] [blame] | 165 | } |
| 166 | |
| 167 | clientConfig, err := rawClientConfig.ClientConfig() |
| 168 | clientSet, err := kubernetes.NewForConfig(clientConfig) |
| 169 | if err != nil { |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 170 | return fmt.Errorf("could not generate kubernetes client: %w", err) |
Lorenz Brun | b15abad | 2020-04-16 11:17:12 +0200 | [diff] [blame] | 171 | } |
| 172 | |
| 173 | informerFactory := informers.NewSharedInformerFactory(clientSet, 5*time.Minute) |
| 174 | |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 175 | hostname, err := os.Hostname() |
| 176 | if err != nil { |
| 177 | return fmt.Errorf("failed to get hostname: %w", err) |
Lorenz Brun | 8e3b8fc | 2020-05-19 14:29:40 +0200 | [diff] [blame] | 178 | } |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 179 | err = createKubeletConfig(ctx, consensusKV, s.kpki, hostname) |
| 180 | if err != nil { |
| 181 | return fmt.Errorf("could not created kubelet config: %w", err) |
Lorenz Brun | 8e3b8fc | 2020-05-19 14:29:40 +0200 | [diff] [blame] | 182 | } |
Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame^] | 183 | |
| 184 | for _, sub := range []struct { |
| 185 | name string |
| 186 | runnable supervisor.Runnable |
| 187 | }{ |
| 188 | {"apiserver", runAPIServer(*apiserverConfig, s.apiserverLogs)}, |
| 189 | {"controller-manager", runControllerManager(*controllerManagerConfig, s.controllerManagerLogs)}, |
| 190 | {"scheduler", runScheduler(*schedulerConfig, s.schedulerLogs)}, |
| 191 | {"kubelet", runKubelet(&KubeletSpec{}, s.kubeletLogs)}, |
| 192 | {"reconciler", reconciler.Run(clientSet)}, |
| 193 | {"csi-plugin", runCSIPlugin(s.storageService)}, |
| 194 | {"pv-provisioner", runCSIProvisioner(s.storageService, clientSet, informerFactory)}, |
| 195 | } { |
| 196 | err := supervisor.Run(ctx, sub.name, sub.runnable) |
| 197 | if err != nil { |
| 198 | return fmt.Errorf("could not run sub-service %q: %w", sub.name, err) |
| 199 | } |
Lorenz Brun | b15abad | 2020-04-16 11:17:12 +0200 | [diff] [blame] | 200 | } |
| 201 | |
Lorenz Brun | 8e3b8fc | 2020-05-19 14:29:40 +0200 | [diff] [blame] | 202 | supervisor.Signal(ctx, supervisor.SignalHealthy) |
| 203 | supervisor.Signal(ctx, supervisor.SignalDone) |
| 204 | return nil |
| 205 | } |
Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 206 | } |