Make ControllerManager more useful
This changes `ControllerManager` to `Node`. `Node` is created from a client where the VK lib is responsible for creating all the things except the client (unless client is nil, then we use the env client). This should be a good replacement for node-cli. It offers a simpler API. *It only works with leases enabled* since this seems always desired, however an option could be added to disable if needed. The intent of this is to provide a simpler way to get a vk node up and running while also being extensible. We can slowly add options, but they should be focussed on a use-case rather than trying to support every possible scenario... in which case the user can just use the controllers directly.
This commit is contained in:
@@ -59,7 +59,11 @@ func (mv mapVar) Type() string {
|
|||||||
|
|
||||||
func installFlags(flags *pflag.FlagSet, c *Opts) {
|
func installFlags(flags *pflag.FlagSet, c *Opts) {
|
||||||
flags.StringVar(&c.KubeConfigPath, "kubeconfig", c.KubeConfigPath, "kube config file to use for connecting to the Kubernetes API server")
|
flags.StringVar(&c.KubeConfigPath, "kubeconfig", c.KubeConfigPath, "kube config file to use for connecting to the Kubernetes API server")
|
||||||
|
|
||||||
flags.StringVar(&c.KubeNamespace, "namespace", c.KubeNamespace, "kubernetes namespace (default is 'all')")
|
flags.StringVar(&c.KubeNamespace, "namespace", c.KubeNamespace, "kubernetes namespace (default is 'all')")
|
||||||
|
flags.MarkDeprecated("namespace", "Nodes must watch for pods in all namespaces. This option is now ignored.") //nolint:errcheck
|
||||||
|
flags.MarkHidden("namespace") //nolint:errcheck
|
||||||
|
|
||||||
flags.StringVar(&c.KubeClusterDomain, "cluster-domain", c.KubeClusterDomain, "kubernetes cluster-domain (default is 'cluster.local')")
|
flags.StringVar(&c.KubeClusterDomain, "cluster-domain", c.KubeClusterDomain, "kubernetes cluster-domain (default is 'cluster.local')")
|
||||||
flags.StringVar(&c.NodeName, "nodename", c.NodeName, "kubernetes node name")
|
flags.StringVar(&c.NodeName, "nodename", c.NodeName, "kubernetes node name")
|
||||||
flags.StringVar(&c.OperatingSystem, "os", c.OperatingSystem, "Operating System (Linux/Windows)")
|
flags.StringVar(&c.OperatingSystem, "os", c.OperatingSystem, "Operating System (Linux/Windows)")
|
||||||
@@ -68,11 +72,15 @@ func installFlags(flags *pflag.FlagSet, c *Opts) {
|
|||||||
flags.StringVar(&c.MetricsAddr, "metrics-addr", c.MetricsAddr, "address to listen for metrics/stats requests")
|
flags.StringVar(&c.MetricsAddr, "metrics-addr", c.MetricsAddr, "address to listen for metrics/stats requests")
|
||||||
|
|
||||||
flags.StringVar(&c.TaintKey, "taint", c.TaintKey, "Set node taint key")
|
flags.StringVar(&c.TaintKey, "taint", c.TaintKey, "Set node taint key")
|
||||||
|
|
||||||
flags.BoolVar(&c.DisableTaint, "disable-taint", c.DisableTaint, "disable the virtual-kubelet node taint")
|
flags.BoolVar(&c.DisableTaint, "disable-taint", c.DisableTaint, "disable the virtual-kubelet node taint")
|
||||||
flags.MarkDeprecated("taint", "Taint key should now be configured using the VK_TAINT_KEY environment variable") //nolint:errcheck
|
flags.MarkDeprecated("taint", "Taint key should now be configured using the VK_TAINT_KEY environment variable") //nolint:errcheck
|
||||||
|
|
||||||
flags.IntVar(&c.PodSyncWorkers, "pod-sync-workers", c.PodSyncWorkers, `set the number of pod synchronization workers`)
|
flags.IntVar(&c.PodSyncWorkers, "pod-sync-workers", c.PodSyncWorkers, `set the number of pod synchronization workers`)
|
||||||
|
|
||||||
flags.BoolVar(&c.EnableNodeLease, "enable-node-lease", c.EnableNodeLease, `use node leases (1.13) for node heartbeats`)
|
flags.BoolVar(&c.EnableNodeLease, "enable-node-lease", c.EnableNodeLease, `use node leases (1.13) for node heartbeats`)
|
||||||
|
flags.MarkDeprecated("enable-node-lease", "leases are always enabled") //nolint:errcheck
|
||||||
|
flags.MarkHidden("enable-node-lease") //nolint:errcheck
|
||||||
|
|
||||||
flags.StringSliceVar(&c.TraceExporters, "trace-exporter", c.TraceExporters, fmt.Sprintf("sets the tracing exporter to use, available exporters: %s", AvailableTraceExporters()))
|
flags.StringSliceVar(&c.TraceExporters, "trace-exporter", c.TraceExporters, fmt.Sprintf("sets the tracing exporter to use, available exporters: %s", AvailableTraceExporters()))
|
||||||
flags.StringVar(&c.TraceConfig.ServiceName, "trace-service-name", c.TraceConfig.ServiceName, "sets the name of the service used to register with the trace exporter")
|
flags.StringVar(&c.TraceConfig.ServiceName, "trace-service-name", c.TraceConfig.ServiceName, "sets the name of the service used to register with the trace exporter")
|
||||||
|
|||||||
@@ -15,54 +15,10 @@
|
|||||||
package root
|
package root
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/virtual-kubelet/virtual-kubelet/cmd/virtual-kubelet/internal/provider"
|
|
||||||
"github.com/virtual-kubelet/virtual-kubelet/errdefs"
|
"github.com/virtual-kubelet/virtual-kubelet/errdefs"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const osLabel = "beta.kubernetes.io/os"
|
|
||||||
|
|
||||||
// NodeFromProvider builds a kubernetes node object from a provider
|
|
||||||
// This is a temporary solution until node stuff actually split off from the provider interface itself.
|
|
||||||
func NodeFromProvider(ctx context.Context, name string, taint *v1.Taint, p provider.Provider, version string) *v1.Node {
|
|
||||||
taints := make([]v1.Taint, 0)
|
|
||||||
|
|
||||||
if taint != nil {
|
|
||||||
taints = append(taints, *taint)
|
|
||||||
}
|
|
||||||
|
|
||||||
node := &v1.Node{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: name,
|
|
||||||
Labels: map[string]string{
|
|
||||||
"type": "virtual-kubelet",
|
|
||||||
"kubernetes.io/role": "agent",
|
|
||||||
"kubernetes.io/hostname": name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Spec: v1.NodeSpec{
|
|
||||||
Taints: taints,
|
|
||||||
},
|
|
||||||
Status: v1.NodeStatus{
|
|
||||||
NodeInfo: v1.NodeSystemInfo{
|
|
||||||
Architecture: "amd64",
|
|
||||||
KubeletVersion: version,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
p.ConfigureNode(ctx, node)
|
|
||||||
if _, ok := node.ObjectMeta.Labels[osLabel]; !ok {
|
|
||||||
node.ObjectMeta.Labels[osLabel] = strings.ToLower(node.Status.NodeInfo.OperatingSystem)
|
|
||||||
}
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
// getTaint creates a taint using the provided key/value.
|
// getTaint creates a taint using the provided key/value.
|
||||||
// Taint effect is read from the environment
|
// Taint effect is read from the environment
|
||||||
// The taint key/value may be overwritten by the environment.
|
// The taint key/value may be overwritten by the environment.
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import (
|
|||||||
// Defaults for root command options
|
// Defaults for root command options
|
||||||
const (
|
const (
|
||||||
DefaultNodeName = "virtual-kubelet"
|
DefaultNodeName = "virtual-kubelet"
|
||||||
DefaultOperatingSystem = "Linux"
|
DefaultOperatingSystem = "linux"
|
||||||
DefaultInformerResyncPeriod = 1 * time.Minute
|
DefaultInformerResyncPeriod = 1 * time.Minute
|
||||||
DefaultMetricsAddr = ":10255"
|
DefaultMetricsAddr = ":10255"
|
||||||
DefaultListenPort = 10250 // TODO(cpuguy83)(VK1.0): Change this to an addr instead of just a port.. we should not be listening on all interfaces.
|
DefaultListenPort = 10250 // TODO(cpuguy83)(VK1.0): Change this to an addr instead of just a port.. we should not be listening on all interfaces.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ package root
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"runtime"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -28,12 +28,6 @@ import (
|
|||||||
"github.com/virtual-kubelet/virtual-kubelet/node"
|
"github.com/virtual-kubelet/virtual-kubelet/node"
|
||||||
"github.com/virtual-kubelet/virtual-kubelet/node/nodeutil"
|
"github.com/virtual-kubelet/virtual-kubelet/node/nodeutil"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
kubeinformers "k8s.io/client-go/informers"
|
|
||||||
"k8s.io/client-go/kubernetes/scheme"
|
|
||||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
|
||||||
"k8s.io/client-go/tools/record"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewCommand creates a new top-level command.
|
// NewCommand creates a new top-level command.
|
||||||
@@ -80,36 +74,19 @@ func runRootCommand(ctx context.Context, s *provider.Store, c Opts) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a shared informer factory for Kubernetes pods in the current namespace (if specified) and scheduled to the current node.
|
cancelHTTP := func() {}
|
||||||
podInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(
|
defer func() {
|
||||||
client,
|
// note: this is purposefully using a closure so that when this is actually set the correct function will be called
|
||||||
c.InformerResyncPeriod,
|
if cancelHTTP != nil {
|
||||||
kubeinformers.WithNamespace(c.KubeNamespace),
|
cancelHTTP()
|
||||||
nodeutil.PodInformerFilter(c.NodeName),
|
}
|
||||||
)
|
}()
|
||||||
podInformer := podInformerFactory.Core().V1().Pods()
|
newProvider := func(cfg nodeutil.ProviderConfig) (node.PodLifecycleHandler, node.NodeProvider, error) {
|
||||||
|
var err error
|
||||||
// Create another shared informer factory for Kubernetes secrets and configmaps (not subject to any selectors).
|
rm, err := manager.NewResourceManager(cfg.Pods, cfg.Secrets, cfg.ConfigMaps, cfg.Services)
|
||||||
scmInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(client, c.InformerResyncPeriod)
|
|
||||||
// Create a secret informer and a config map informer so we can pass their listers to the resource manager.
|
|
||||||
secretInformer := scmInformerFactory.Core().V1().Secrets()
|
|
||||||
configMapInformer := scmInformerFactory.Core().V1().ConfigMaps()
|
|
||||||
serviceInformer := scmInformerFactory.Core().V1().Services()
|
|
||||||
|
|
||||||
rm, err := manager.NewResourceManager(podInformer.Lister(), secretInformer.Lister(), configMapInformer.Lister(), serviceInformer.Lister())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not create resource manager")
|
return nil, nil, errors.Wrap(err, "could not create resource manager")
|
||||||
}
|
}
|
||||||
|
|
||||||
apiConfig, err := getAPIConfig(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := setupTracing(ctx, c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
initConfig := provider.InitConfig{
|
initConfig := provider.InitConfig{
|
||||||
ConfigPath: c.ProviderConfigPath,
|
ConfigPath: c.ProviderConfigPath,
|
||||||
NodeName: c.NodeName,
|
NodeName: c.NodeName,
|
||||||
@@ -119,15 +96,49 @@ func runRootCommand(ctx context.Context, s *provider.Store, c Opts) error {
|
|||||||
InternalIP: os.Getenv("VKUBELET_POD_IP"),
|
InternalIP: os.Getenv("VKUBELET_POD_IP"),
|
||||||
KubeClusterDomain: c.KubeClusterDomain,
|
KubeClusterDomain: c.KubeClusterDomain,
|
||||||
}
|
}
|
||||||
|
|
||||||
pInit := s.Get(c.Provider)
|
pInit := s.Get(c.Provider)
|
||||||
if pInit == nil {
|
if pInit == nil {
|
||||||
return errors.Errorf("provider %q not found", c.Provider)
|
return nil, nil, errors.Errorf("provider %q not found", c.Provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
p, err := pInit(initConfig)
|
p, err := pInit(initConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "error initializing provider %s", c.Provider)
|
return nil, nil, errors.Wrapf(err, "error initializing provider %s", c.Provider)
|
||||||
|
}
|
||||||
|
p.ConfigureNode(ctx, cfg.Node)
|
||||||
|
|
||||||
|
apiConfig, err := getAPIConfig(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelHTTP, err = setupHTTPServer(ctx, p, apiConfig, func(context.Context) ([]*corev1.Pod, error) {
|
||||||
|
return rm.GetPods(), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cm, err := nodeutil.NewNodeFromClient(client, c.NodeName, newProvider, func(cfg *nodeutil.NodeConfig) error {
|
||||||
|
cfg.InformerResyncPeriod = c.InformerResyncPeriod
|
||||||
|
|
||||||
|
if taint != nil {
|
||||||
|
cfg.NodeSpec.Spec.Taints = append(cfg.NodeSpec.Spec.Taints, *taint)
|
||||||
|
}
|
||||||
|
cfg.NodeSpec.Status.NodeInfo.Architecture = runtime.GOARCH
|
||||||
|
cfg.NodeSpec.Status.NodeInfo.OperatingSystem = c.OperatingSystem
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := setupTracing(ctx, c); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = log.WithLogger(ctx, log.G(ctx).WithFields(log.Fields{
|
ctx = log.WithLogger(ctx, log.G(ctx).WithFields(log.Fields{
|
||||||
@@ -137,69 +148,8 @@ func runRootCommand(ctx context.Context, s *provider.Store, c Opts) error {
|
|||||||
"watchedNamespace": c.KubeNamespace,
|
"watchedNamespace": c.KubeNamespace,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
pNode := NodeFromProvider(ctx, c.NodeName, taint, p, c.Version)
|
|
||||||
np := node.NewNaiveNodeProvider()
|
|
||||||
additionalOptions := []node.NodeControllerOpt{
|
|
||||||
node.WithNodeStatusUpdateErrorHandler(func(ctx context.Context, err error) error {
|
|
||||||
if !k8serrors.IsNotFound(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.G(ctx).Debug("node not found")
|
|
||||||
newNode := pNode.DeepCopy()
|
|
||||||
newNode.ResourceVersion = ""
|
|
||||||
_, err = client.CoreV1().Nodes().Create(ctx, newNode, metav1.CreateOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.G(ctx).Debug("created new node")
|
|
||||||
return nil
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
if c.EnableNodeLease {
|
|
||||||
leaseClient := nodeutil.NodeLeaseV1Client(client)
|
|
||||||
// 40 seconds is the default lease time in upstream kubelet
|
|
||||||
additionalOptions = append(additionalOptions, node.WithNodeEnableLeaseV1(leaseClient, 40))
|
|
||||||
}
|
|
||||||
nodeRunner, err := node.NewNodeController(
|
|
||||||
np,
|
|
||||||
pNode,
|
|
||||||
client.CoreV1().Nodes(),
|
|
||||||
additionalOptions...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.G(ctx).Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
eb := record.NewBroadcaster()
|
|
||||||
eb.StartLogging(log.G(ctx).Infof)
|
|
||||||
eb.StartRecordingToSink(&corev1client.EventSinkImpl{Interface: client.CoreV1().Events(c.KubeNamespace)})
|
|
||||||
|
|
||||||
pc, err := node.NewPodController(node.PodControllerConfig{
|
|
||||||
PodClient: client.CoreV1(),
|
|
||||||
PodInformer: podInformer,
|
|
||||||
EventRecorder: eb.NewRecorder(scheme.Scheme, corev1.EventSource{Component: path.Join(pNode.Name, "pod-controller")}),
|
|
||||||
Provider: p,
|
|
||||||
SecretInformer: secretInformer,
|
|
||||||
ConfigMapInformer: configMapInformer,
|
|
||||||
ServiceInformer: serviceInformer,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "error setting up pod controller")
|
|
||||||
}
|
|
||||||
|
|
||||||
go podInformerFactory.Start(ctx.Done())
|
|
||||||
go scmInformerFactory.Start(ctx.Done())
|
|
||||||
|
|
||||||
cancelHTTP, err := setupHTTPServer(ctx, p, apiConfig, func(context.Context) ([]*corev1.Pod, error) {
|
|
||||||
return rm.GetPods(), nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer cancelHTTP()
|
defer cancelHTTP()
|
||||||
|
|
||||||
cm := nodeutil.NewControllerManager(nodeRunner, pc)
|
|
||||||
go cm.Run(ctx, c.PodSyncWorkers) // nolint:errcheck
|
go cm.Run(ctx, c.PodSyncWorkers) // nolint:errcheck
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -213,12 +163,6 @@ func runRootCommand(ctx context.Context, s *provider.Store, c Opts) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
setNodeReady(pNode)
|
|
||||||
if err := np.UpdateStatus(ctx, pNode); err != nil {
|
|
||||||
return errors.Wrap(err, "error marking the node as ready")
|
|
||||||
}
|
|
||||||
log.G(ctx).Info("Initialized")
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
case <-cm.Done():
|
case <-cm.Done():
|
||||||
@@ -226,19 +170,3 @@ func runRootCommand(ctx context.Context, s *provider.Store, c Opts) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setNodeReady(n *corev1.Node) {
|
|
||||||
for i, c := range n.Status.Conditions {
|
|
||||||
if c.Type != "Ready" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Message = "Kubelet is ready"
|
|
||||||
c.Reason = "KubeletReady"
|
|
||||||
c.Status = corev1.ConditionTrue
|
|
||||||
c.LastHeartbeatTime = metav1.Now()
|
|
||||||
c.LastTransitionTime = metav1.Now()
|
|
||||||
n.Status.Conditions[i] = c
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -339,7 +339,7 @@ func (p *MockProvider) ConfigureNode(ctx context.Context, n *v1.Node) { // nolin
|
|||||||
n.Status.DaemonEndpoints = p.nodeDaemonEndpoints()
|
n.Status.DaemonEndpoints = p.nodeDaemonEndpoints()
|
||||||
os := p.operatingSystem
|
os := p.operatingSystem
|
||||||
if os == "" {
|
if os == "" {
|
||||||
os = "Linux"
|
os = "linux"
|
||||||
}
|
}
|
||||||
n.Status.NodeInfo.OperatingSystem = os
|
n.Status.NodeInfo.OperatingSystem = os
|
||||||
n.Status.NodeInfo.Architecture = "amd64"
|
n.Status.NodeInfo.Architecture = "amd64"
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package provider
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// OperatingSystemLinux is the configuration value for defining Linux.
|
// OperatingSystemLinux is the configuration value for defining Linux.
|
||||||
OperatingSystemLinux = "Linux"
|
OperatingSystemLinux = "linux"
|
||||||
// OperatingSystemWindows is the configuration value for defining Windows.
|
// OperatingSystemWindows is the configuration value for defining Windows.
|
||||||
OperatingSystemWindows = "Windows"
|
OperatingSystemWindows = "windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OperatingSystems map[string]bool // nolint:golint
|
type OperatingSystems map[string]bool // nolint:golint
|
||||||
|
|||||||
@@ -3,100 +3,127 @@ package nodeutil
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/virtual-kubelet/virtual-kubelet/log"
|
||||||
"github.com/virtual-kubelet/virtual-kubelet/node"
|
"github.com/virtual-kubelet/virtual-kubelet/node"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
|
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
|
corev1listers "k8s.io/client-go/listers/core/v1"
|
||||||
|
"k8s.io/client-go/tools/record"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ControllerManager helps manage the startup/shutdown procedure for other controllers.
|
// Node helps manage the startup/shutdown procedure for other controllers.
|
||||||
// It is intended as a convenience to reduce boiler plate code for starting up controllers.
|
// It is intended as a convenience to reduce boiler plate code for starting up controllers.
|
||||||
//
|
//
|
||||||
// Must be created with constructor `NewControllerManager`.
|
// Must be created with constructor `NewNode`.
|
||||||
type ControllerManager struct {
|
type Node struct {
|
||||||
nc *node.NodeController
|
nc *node.NodeController
|
||||||
pc *node.PodController
|
pc *node.PodController
|
||||||
|
|
||||||
|
readyCb func(context.Context) error
|
||||||
|
|
||||||
ready chan struct{}
|
ready chan struct{}
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
err error
|
err error
|
||||||
}
|
|
||||||
|
|
||||||
// NewControllerManager creates a new ControllerManager.
|
podInformerFactory informers.SharedInformerFactory
|
||||||
func NewControllerManager(nc *node.NodeController, pc *node.PodController) *ControllerManager {
|
scmInformerFactory informers.SharedInformerFactory
|
||||||
return &ControllerManager{
|
client kubernetes.Interface
|
||||||
nc: nc,
|
|
||||||
pc: pc,
|
eb record.EventBroadcaster
|
||||||
ready: make(chan struct{}),
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeController returns the configured node controller.
|
// NodeController returns the configured node controller.
|
||||||
func (c *ControllerManager) NodeController() *node.NodeController {
|
func (n *Node) NodeController() *node.NodeController {
|
||||||
return c.nc
|
return n.nc
|
||||||
}
|
}
|
||||||
|
|
||||||
// PodController returns the configured pod controller.
|
// PodController returns the configured pod controller.
|
||||||
func (c *ControllerManager) PodController() *node.PodController {
|
func (n *Node) PodController() *node.PodController {
|
||||||
return c.pc
|
return n.pc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run starts all the underlying controllers
|
// Run starts all the underlying controllers
|
||||||
func (c *ControllerManager) Run(ctx context.Context, workers int) (retErr error) {
|
func (n *Node) Run(ctx context.Context, workers int) (retErr error) {
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
go c.pc.Run(ctx, workers) // nolint:errcheck
|
if n.podInformerFactory != nil {
|
||||||
|
go n.podInformerFactory.Start(ctx.Done())
|
||||||
|
}
|
||||||
|
if n.scmInformerFactory != nil {
|
||||||
|
go n.scmInformerFactory.Start(ctx.Done())
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.eb != nil {
|
||||||
|
n.eb.StartLogging(log.G(ctx).Infof)
|
||||||
|
n.eb.StartRecordingToSink(&corev1client.EventSinkImpl{Interface: n.client.CoreV1().Events(v1.NamespaceAll)})
|
||||||
|
}
|
||||||
|
|
||||||
|
go n.pc.Run(ctx, workers) // nolint:errcheck
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
cancel()
|
cancel()
|
||||||
|
|
||||||
<-c.pc.Done()
|
<-n.pc.Done()
|
||||||
|
|
||||||
c.err = retErr
|
n.err = retErr
|
||||||
close(c.done)
|
close(n.done)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return c.err
|
return n.err
|
||||||
case <-c.pc.Ready():
|
case <-n.pc.Ready():
|
||||||
case <-c.pc.Done():
|
case <-n.pc.Done():
|
||||||
return c.pc.Err()
|
return n.pc.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
go c.nc.Run(ctx) // nolint:errcheck
|
go n.nc.Run(ctx) // nolint:errcheck
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
cancel()
|
cancel()
|
||||||
<-c.nc.Done()
|
<-n.nc.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
c.err = ctx.Err()
|
n.err = ctx.Err()
|
||||||
return c.err
|
return n.err
|
||||||
case <-c.nc.Ready():
|
case <-n.nc.Ready():
|
||||||
case <-c.nc.Done():
|
case <-n.nc.Done():
|
||||||
return c.nc.Err()
|
return n.nc.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
close(c.ready)
|
if n.readyCb != nil {
|
||||||
|
if err := n.readyCb(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(n.ready)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-c.nc.Done():
|
case <-n.nc.Done():
|
||||||
cancel()
|
cancel()
|
||||||
return c.nc.Err()
|
return n.nc.Err()
|
||||||
case <-c.pc.Done():
|
case <-n.pc.Done():
|
||||||
cancel()
|
cancel()
|
||||||
return c.pc.Err()
|
return n.pc.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitReady waits for the specified timeout for the controller to be ready.
|
// WaitReady waits for the specified timeout for the controller to be ready.
|
||||||
//
|
//
|
||||||
// The timeout is for convenience so the caller doesn't have to juggle an extra context.
|
// The timeout is for convenience so the caller doesn't have to juggle an extra context.
|
||||||
func (c *ControllerManager) WaitReady(ctx context.Context, timeout time.Duration) error {
|
func (n *Node) WaitReady(ctx context.Context, timeout time.Duration) error {
|
||||||
if timeout > 0 {
|
if timeout > 0 {
|
||||||
var cancel func()
|
var cancel func()
|
||||||
ctx, cancel = context.WithTimeout(ctx, timeout)
|
ctx, cancel = context.WithTimeout(ctx, timeout)
|
||||||
@@ -104,33 +131,218 @@ func (c *ControllerManager) WaitReady(ctx context.Context, timeout time.Duration
|
|||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-c.ready:
|
case <-n.ready:
|
||||||
return nil
|
return nil
|
||||||
case <-c.done:
|
case <-n.done:
|
||||||
return fmt.Errorf("controller exited before ready: %w", c.err)
|
return fmt.Errorf("controller exited before ready: %w", n.err)
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ready returns a channel that will be closed after the controller is ready.
|
// Ready returns a channel that will be closed after the controller is ready.
|
||||||
func (c *ControllerManager) Ready() <-chan struct{} {
|
func (n *Node) Ready() <-chan struct{} {
|
||||||
return c.ready
|
return n.ready
|
||||||
}
|
}
|
||||||
|
|
||||||
// Done returns a channel that will be closed when the controller has exited.
|
// Done returns a channel that will be closed when the controller has exited.
|
||||||
func (c *ControllerManager) Done() <-chan struct{} {
|
func (n *Node) Done() <-chan struct{} {
|
||||||
return c.done
|
return n.done
|
||||||
}
|
}
|
||||||
|
|
||||||
// Err returns any error that occurred with the controller.
|
// Err returns any error that occurred with the controller.
|
||||||
//
|
//
|
||||||
// This always return nil before `<-Done()`.
|
// This always return nil before `<-Done()`.
|
||||||
func (c *ControllerManager) Err() error {
|
func (n *Node) Err() error {
|
||||||
select {
|
select {
|
||||||
case <-c.Done():
|
case <-n.Done():
|
||||||
return c.err
|
return n.err
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProviderConfig holds objects created by NewNodeFromClient that a provider may need to bootstrap itself.
|
||||||
|
type ProviderConfig struct {
|
||||||
|
Pods corev1listers.PodLister
|
||||||
|
ConfigMaps corev1listers.ConfigMapLister
|
||||||
|
Secrets corev1listers.SecretLister
|
||||||
|
Services corev1listers.ServiceLister
|
||||||
|
// Hack to allow the provider to set things on the node
|
||||||
|
// Since the provider is bootstrapped after the node object is configured
|
||||||
|
// Primarily this is due to carry-over from the pre-1.0 interfaces that expect the provider to configure the node.
|
||||||
|
Node *v1.Node
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeOpt is used as functional options when configuring a new node in NewNodeFromClient
|
||||||
|
type NodeOpt func(c *NodeConfig) error
|
||||||
|
|
||||||
|
// NodeConfig is used to hold configuration items for a Node.
|
||||||
|
// It gets used in conjection with NodeOpt in NewNodeFromClient
|
||||||
|
type NodeConfig struct {
|
||||||
|
// Set the node spec to register with Kubernetes
|
||||||
|
NodeSpec v1.Node
|
||||||
|
// Set the path to read a kubeconfig from for creating a client.
|
||||||
|
// This is ignored when a client is provided to NewNodeFromClient
|
||||||
|
KubeconfigPath string
|
||||||
|
// Set the period for a full resync for generated client-go informers
|
||||||
|
InformerResyncPeriod time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProviderFunc is used from NewNodeFromClient to bootstrap a provider using the client/listers/etc created there.
|
||||||
|
// If a nil node provider is returned a default one will be used.
|
||||||
|
type NewProviderFunc func(ProviderConfig) (node.PodLifecycleHandler, node.NodeProvider, error)
|
||||||
|
|
||||||
|
// WithNodeConfig returns a NodeOpt which replaces the NodeConfig with the passed in value.
|
||||||
|
func WithNodeConfig(c NodeConfig) NodeOpt {
|
||||||
|
return func(orig *NodeConfig) error {
|
||||||
|
*orig = c
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNode calls NewNodeFromClient with a nil client
|
||||||
|
func NewNode(name string, newProvider NewProviderFunc, opts ...NodeOpt) (*Node, error) {
|
||||||
|
return NewNodeFromClient(nil, name, newProvider, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNodeFromClient creates a new node using the provided client and name.
|
||||||
|
// This is intended for high-level/low boiler-plate usage.
|
||||||
|
// Use the constructors in the `node` package for lower level configuration.
|
||||||
|
//
|
||||||
|
// Some basic values are set for node status, you'll almost certainly want to modify it.
|
||||||
|
//
|
||||||
|
// If client is nil, this will construct a client using ClientsetFromEnv
|
||||||
|
func NewNodeFromClient(client kubernetes.Interface, name string, newProvider NewProviderFunc, opts ...NodeOpt) (*Node, error) {
|
||||||
|
cfg := NodeConfig{
|
||||||
|
// TODO: this is what was set in the cli code... its not clear what a good value actually is.
|
||||||
|
InformerResyncPeriod: time.Minute,
|
||||||
|
KubeconfigPath: os.Getenv("KUBECONFIG"),
|
||||||
|
NodeSpec: v1.Node{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
Labels: map[string]string{
|
||||||
|
"type": "virtual-kubelet",
|
||||||
|
"kubernetes.io/role": "agent",
|
||||||
|
"kubernetes.io/hostname": name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: v1.NodeStatus{
|
||||||
|
Phase: v1.NodePending,
|
||||||
|
Conditions: []v1.NodeCondition{
|
||||||
|
{Type: v1.NodeReady},
|
||||||
|
{Type: v1.NodeDiskPressure},
|
||||||
|
{Type: v1.NodeMemoryPressure},
|
||||||
|
{Type: v1.NodePIDPressure},
|
||||||
|
{Type: v1.NodeNetworkUnavailable},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
if err := o(&cfg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if client == nil {
|
||||||
|
var err error
|
||||||
|
client, err = ClientsetFromEnv(cfg.KubeconfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error creating clientset from env")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
podInformerFactory := informers.NewSharedInformerFactoryWithOptions(
|
||||||
|
client,
|
||||||
|
cfg.InformerResyncPeriod,
|
||||||
|
PodInformerFilter(name),
|
||||||
|
)
|
||||||
|
|
||||||
|
scmInformerFactory := informers.NewSharedInformerFactoryWithOptions(
|
||||||
|
client,
|
||||||
|
cfg.InformerResyncPeriod,
|
||||||
|
)
|
||||||
|
|
||||||
|
podInformer := podInformerFactory.Core().V1().Pods()
|
||||||
|
secretInformer := scmInformerFactory.Core().V1().Secrets()
|
||||||
|
configMapInformer := scmInformerFactory.Core().V1().ConfigMaps()
|
||||||
|
serviceInformer := scmInformerFactory.Core().V1().Services()
|
||||||
|
|
||||||
|
p, np, err := newProvider(ProviderConfig{
|
||||||
|
Pods: podInformer.Lister(),
|
||||||
|
ConfigMaps: configMapInformer.Lister(),
|
||||||
|
Secrets: secretInformer.Lister(),
|
||||||
|
Services: serviceInformer.Lister(),
|
||||||
|
Node: &cfg.NodeSpec,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error creating provider")
|
||||||
|
}
|
||||||
|
|
||||||
|
var readyCb func(context.Context) error
|
||||||
|
if np == nil {
|
||||||
|
nnp := node.NewNaiveNodeProvider()
|
||||||
|
np = nnp
|
||||||
|
|
||||||
|
readyCb = func(ctx context.Context) error {
|
||||||
|
setNodeReady(&cfg.NodeSpec)
|
||||||
|
err := nnp.UpdateStatus(ctx, &cfg.NodeSpec)
|
||||||
|
return errors.Wrap(err, "error marking node as ready")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nc, err := node.NewNodeController(
|
||||||
|
np,
|
||||||
|
&cfg.NodeSpec,
|
||||||
|
client.CoreV1().Nodes(),
|
||||||
|
node.WithNodeEnableLeaseV1(NodeLeaseV1Client(client), node.DefaultLeaseDuration),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error creating node controller")
|
||||||
|
}
|
||||||
|
|
||||||
|
eb := record.NewBroadcaster()
|
||||||
|
|
||||||
|
pc, err := node.NewPodController(node.PodControllerConfig{
|
||||||
|
PodClient: client.CoreV1(),
|
||||||
|
EventRecorder: eb.NewRecorder(scheme.Scheme, v1.EventSource{Component: path.Join(name, "pod-controller")}),
|
||||||
|
Provider: p,
|
||||||
|
PodInformer: podInformer,
|
||||||
|
SecretInformer: secretInformer,
|
||||||
|
ConfigMapInformer: configMapInformer,
|
||||||
|
ServiceInformer: serviceInformer,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error creating pod controller")
|
||||||
|
}
|
||||||
|
return &Node{
|
||||||
|
nc: nc,
|
||||||
|
pc: pc,
|
||||||
|
readyCb: readyCb,
|
||||||
|
ready: make(chan struct{}),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
eb: eb,
|
||||||
|
podInformerFactory: podInformerFactory,
|
||||||
|
scmInformerFactory: scmInformerFactory,
|
||||||
|
client: client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setNodeReady(n *v1.Node) {
|
||||||
|
n.Status.Phase = v1.NodeRunning
|
||||||
|
for i, c := range n.Status.Conditions {
|
||||||
|
if c.Type != "Ready" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Message = "Kubelet is ready"
|
||||||
|
c.Reason = "KubeletReady"
|
||||||
|
c.Status = v1.ConditionTrue
|
||||||
|
c.LastHeartbeatTime = metav1.Now()
|
||||||
|
c.LastTransitionTime = metav1.Now()
|
||||||
|
n.Status.Conditions[i] = c
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ flags:
|
|||||||
default: virtual-kubelet
|
default: virtual-kubelet
|
||||||
- name: --os
|
- name: --os
|
||||||
arg: string
|
arg: string
|
||||||
description: The operating system (must be `Linux` or `Windows`)
|
description: The operating system (must be `linux` or `windows`)
|
||||||
default: Linux
|
default: Linux
|
||||||
- name: --pod-sync-workers
|
- name: --pod-sync-workers
|
||||||
arg: int
|
arg: int
|
||||||
|
|||||||
Reference in New Issue
Block a user