Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for running controller outside of cluster #102

Merged
merged 1 commit into from
Jan 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ require (
github.com/jonboulle/clockwork v0.1.0 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/onsi/ginkgo v1.10.2
github.com/onsi/gomega v1.7.0
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
Expand Down
12 changes: 10 additions & 2 deletions pkg/csi/service/cns/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,23 @@ func (nodes *Nodes) Initialize() error {
}
nodes.cnsNodeManager.SetKubernetesClient(k8sclient)
nodes.informMgr = k8s.NewInformer(k8sclient)
nodes.informMgr.AddNodeListener(nodes.nodeAdd, nil, nodes.nodeDelete)
nodes.informMgr.AddNodeListener(nodes.nodeAdd, nodes.nodeUpdate, nodes.nodeDelete)
nodes.informMgr.Listen()
return nil
}

func (nodes *Nodes) nodeAdd(obj interface{}) {
nodes.nodeRegister(obj)
}

func (nodes *Nodes) nodeUpdate(oldObj interface{}, newObj interface{}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you done any testing to validate if nodeUpdate works as expected? I think here that you'd need to unregister the old node object and register the new one if that passes (or vice versa). Otherwise we'd have a long list of nodes registered representing the same node object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A node update typically updates its status fields. Especially it is forbidden to change the providerID of the spec or its name. Therefore the node registration happens with the same values and the node manager is hopefully idempotent. I'm not an expert here, but removing the node registration on an update of its status looks to me like a very dangerous operation.

Copy link

@mandelsoft mandelsoft Dec 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem here is a timing behaviour. If the CSI controller is already running if new nodes join the cluster
the node object is created first without attributes required by the CSI controller. They are later added by the cloud controller.
But this is then no create event anymore, but an update event. Therefore it is not possible to ignore update events in the CSI controller.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mandelsoft exactly! Not just in the case of new nodes, but also if CSI is initialized before CPI adds ProviderID to all nodes, we have a situation where not all nodes are registered with the ProviderID set. This is the use case that i see for nodeUpdate.
@MartinWeindel it seems that nodeRegister will modify the existing node entry and not add a new one. But we still need some testing to verify

nodes.nodeRegister(newObj)
}

func (nodes *Nodes) nodeRegister(obj interface{}) {
node, ok := obj.(*v1.Node)
if node == nil || !ok {
klog.Warningf("nodeAdd: unrecognized object %+v", obj)
klog.Warningf("nodeRegister: unrecognized object %+v", obj)
return
}
err := nodes.cnsNodeManager.RegisterNode(common.GetUUIDFromProviderID(node.Spec.ProviderID), node.Name)
Expand Down
32 changes: 27 additions & 5 deletions pkg/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ limitations under the License.
package kubernetes

import (
"flag"
"os"

"k8s.io/klog"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -27,16 +30,35 @@ import (
"sigs.k8s.io/vsphere-csi-driver/pkg/csi/service/common"
)

var kubeconfig *string

func init() {
kubeconfig = flag.String("kubeconfig", "", "path to the kubeconfig file")
}

// NewClient creates a newk8s client based on a service account
func NewClient() (clientset.Interface, error) {
kubecfgPath := os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
if *kubeconfig != "" {
kubecfgPath = *kubeconfig
}

var config *restclient.Config
var err error
klog.V(2).Info("k8s client using in-cluster config")
config, err = restclient.InClusterConfig()
if err != nil {
klog.Errorf("InClusterConfig failed %q", err)
return nil, err
if kubecfgPath != "" {
klog.V(2).Infof("k8s client using kubeconfig from %s", kubecfgPath)
config, err = clientcmd.BuildConfigFromFlags("", kubecfgPath)
if err != nil {
klog.Errorf("BuildConfigFromFlags failed %q", err)
return nil, err
}
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about defaulting to an in cluster config if BuildConfigFromFlags fails?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be very confusing. You specify a kubeconfig, but if something fails the controller suddenly tries to control the cluster it is running on.
Better to fail early and report it. If you want to use in-cluster config, just do not provide a kubeconfig.
BTW, your colleges of the vSphere cloud manager are using exactly the same logic, see https:/kubernetes/cloud-provider-vsphere/blob/master/pkg/common/kubernetes/kubernetes.go#L38-L44

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CPI have different use cases from CSI. However, on second thought, i agree with the benefits of failing early.

klog.V(2).Info("k8s client using in-cluster config")
config, err = restclient.InClusterConfig()
if err != nil {
klog.Errorf("InClusterConfig failed %q", err)
return nil, err
}
}

return clientset.NewForConfig(config)
Expand Down