| // Copyright 2020 The Monogon Project Authors. |
| // |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package kubernetes |
| |
| import ( |
| "context" |
| "fmt" |
| "net" |
| "os" |
| "time" |
| |
| "google.golang.org/grpc/codes" |
| "google.golang.org/grpc/status" |
| "k8s.io/client-go/informers" |
| "k8s.io/client-go/kubernetes" |
| "k8s.io/client-go/tools/clientcmd" |
| |
| "source.monogon.dev/metropolis/node/core/localstorage" |
| "source.monogon.dev/metropolis/node/core/network/dns" |
| "source.monogon.dev/metropolis/node/kubernetes/clusternet" |
| "source.monogon.dev/metropolis/node/kubernetes/nfproxy" |
| "source.monogon.dev/metropolis/node/kubernetes/pki" |
| "source.monogon.dev/metropolis/node/kubernetes/reconciler" |
| "source.monogon.dev/metropolis/pkg/supervisor" |
| apb "source.monogon.dev/metropolis/proto/api" |
| ) |
| |
| type Config struct { |
| AdvertiseAddress net.IP |
| ServiceIPRange net.IPNet |
| ClusterNet net.IPNet |
| |
| KPKI *pki.KubernetesPKI |
| Root *localstorage.Root |
| CorednsRegistrationChan chan *dns.ExtraDirective |
| } |
| |
| type Service struct { |
| c Config |
| } |
| |
| func New(c Config) *Service { |
| s := &Service{ |
| c: c, |
| } |
| return s |
| } |
| |
| func (s *Service) Run(ctx context.Context) error { |
| controllerManagerConfig, err := getPKIControllerManagerConfig(ctx, s.c.KPKI) |
| if err != nil { |
| return fmt.Errorf("could not generate controller manager pki config: %w", err) |
| } |
| controllerManagerConfig.clusterNet = s.c.ClusterNet |
| schedulerConfig, err := getPKISchedulerConfig(ctx, s.c.KPKI) |
| if err != nil { |
| return fmt.Errorf("could not generate scheduler pki config: %w", err) |
| } |
| |
| masterKubeconfig, err := s.c.KPKI.Kubeconfig(ctx, pki.Master) |
| if err != nil { |
| return fmt.Errorf("could not generate master kubeconfig: %w", err) |
| } |
| |
| rawClientConfig, err := clientcmd.NewClientConfigFromBytes(masterKubeconfig) |
| if err != nil { |
| return fmt.Errorf("could not generate kubernetes client config: %w", err) |
| } |
| |
| clientConfig, err := rawClientConfig.ClientConfig() |
| clientSet, err := kubernetes.NewForConfig(clientConfig) |
| if err != nil { |
| return fmt.Errorf("could not generate kubernetes client: %w", err) |
| } |
| |
| informerFactory := informers.NewSharedInformerFactory(clientSet, 5*time.Minute) |
| |
| hostname, err := os.Hostname() |
| if err != nil { |
| return fmt.Errorf("failed to get hostname: %w", err) |
| } |
| |
| dnsHostIP := s.c.AdvertiseAddress // TODO: Which IP to use |
| |
| apiserver := &apiserverService{ |
| KPKI: s.c.KPKI, |
| AdvertiseAddress: s.c.AdvertiseAddress, |
| ServiceIPRange: s.c.ServiceIPRange, |
| EphemeralConsensusDirectory: &s.c.Root.Ephemeral.Consensus, |
| } |
| |
| kubelet := kubeletService{ |
| NodeName: hostname, |
| ClusterDNS: []net.IP{dnsHostIP}, |
| KubeletDirectory: &s.c.Root.Data.Kubernetes.Kubelet, |
| EphemeralDirectory: &s.c.Root.Ephemeral, |
| KPKI: s.c.KPKI, |
| } |
| |
| csiPlugin := csiPluginServer{ |
| KubeletDirectory: &s.c.Root.Data.Kubernetes.Kubelet, |
| VolumesDirectory: &s.c.Root.Data.Volumes, |
| } |
| |
| csiProvisioner := csiProvisionerServer{ |
| NodeName: hostname, |
| Kubernetes: clientSet, |
| InformerFactory: informerFactory, |
| VolumesDirectory: &s.c.Root.Data.Volumes, |
| } |
| |
| clusternet := clusternet.Service{ |
| NodeName: hostname, |
| Kubernetes: clientSet, |
| ClusterNet: s.c.ClusterNet, |
| InformerFactory: informerFactory, |
| DataDirectory: &s.c.Root.Data.Kubernetes.ClusterNetworking, |
| } |
| |
| nfproxy := nfproxy.Service{ |
| ClusterCIDR: s.c.ClusterNet, |
| ClientSet: clientSet, |
| } |
| |
| for _, sub := range []struct { |
| name string |
| runnable supervisor.Runnable |
| }{ |
| {"apiserver", apiserver.Run}, |
| {"controller-manager", runControllerManager(*controllerManagerConfig)}, |
| {"scheduler", runScheduler(*schedulerConfig)}, |
| {"kubelet", kubelet.Run}, |
| {"reconciler", reconciler.Run(clientSet)}, |
| {"csi-plugin", csiPlugin.Run}, |
| {"csi-provisioner", csiProvisioner.Run}, |
| {"clusternet", clusternet.Run}, |
| {"nfproxy", nfproxy.Run}, |
| } { |
| err := supervisor.Run(ctx, sub.name, sub.runnable) |
| if err != nil { |
| return fmt.Errorf("could not run sub-service %q: %w", sub.name, err) |
| } |
| } |
| |
| supervisor.Logger(ctx).Info("Registering K8s CoreDNS") |
| clusterDNSDirective := dns.NewKubernetesDirective("cluster.local", masterKubeconfig) |
| s.c.CorednsRegistrationChan <- clusterDNSDirective |
| |
| supervisor.Signal(ctx, supervisor.SignalHealthy) |
| <-ctx.Done() |
| s.c.CorednsRegistrationChan <- dns.CancelDirective(clusterDNSDirective) |
| return nil |
| } |
| |
| // GetDebugKubeconfig issues a kubeconfig for an arbitrary given identity. Useful for debugging and testing. |
| func (s *Service) GetDebugKubeconfig(ctx context.Context, request *apb.GetDebugKubeconfigRequest) (*apb.GetDebugKubeconfigResponse, error) { |
| ca := s.c.KPKI.Certificates[pki.IdCA] |
| debugKubeconfig, err := pki.New(ca, "", pki.Client(request.Id, request.Groups)).Kubeconfig(ctx, s.c.KPKI.KV) |
| if err != nil { |
| return nil, status.Errorf(codes.Unavailable, "Failed to generate kubeconfig: %v", err) |
| } |
| return &apb.GetDebugKubeconfigResponse{DebugKubeconfig: string(debugKubeconfig)}, nil |
| } |