Change:
If a LoadBalancer cannot be created (e.g. quota limit hit), there is currently no way to know why other than reviewing the logs (?). We could put an status message / object into LoadBalancerStatus.
We could make an event for this too Justin Santa Barbara < :
+1 for event
I think that is reasonable, but we need to be careful how we define it. Event also sounds good. , Justin Santa Barbara < :
I'm in favor of status as well as an event. The pattern I recommend is an array of conditions, similar to NodeCondition, where each contains at least a Status, Reason, and Message.
+1 for an event. I'd argue this is important enough for usability that we should get it into v1.
How often does this failure mode occur?
I unfortunately don't have stats, but it can happen for a handful of reasons. I think these are the most likely: Cloud provider unavailability/errors Ran out of quota from the cloud provider The cluster is using a cloud provider that doesn't support external load balancers I expect the third one is most common by far. We could perhaps move that into the API object validation rather than writing events to handle that particularly case, but using events would solve all the cases together.

Discussion:
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache" <ins> "github.com/GoogleCloudPlatform/kubernetes/pkg/client/record" </ins> "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"

} type ServiceController struct { <del> cloud       cloudprovider.Interface kubeClient  client.Interface clusterName string balancer    cloudprovider.TCPLoadBalancer zone        cloudprovider.Zone cache       *serviceCache </del> <ins> cloud            cloudprovider.Interface kubeClient       client.Interface clusterName      string balancer         cloudprovider.TCPLoadBalancer zone             cloudprovider.Zone cache            *serviceCache eventBroadcaster record.EventBroadcaster eventRecorder    record.EventRecorder </ins> } // New returns a new service controller to keep cloud provider service resources // (like external load balancers) in sync with the registry. func New(cloud cloudprovider.Interface, kubeClient client.Interface, clusterName string) *ServiceController { <ins> broadcaster := record.NewBroadcaster() broadcaster.StartRecordingToSink(kubeClient.Events("")) recorder := broadcaster.NewRecorder(api.EventSource{Component: "service-controller"}) </ins> return &ServiceController{ <del> cloud:       cloud, kubeClient:  kubeClient, clusterName: clusterName, cache:       &serviceCache{serviceMap: make(map[string]*cachedService)}, </del> <ins> cloud:            cloud, kubeClient:       kubeClient, clusterName:      clusterName, cache:            &serviceCache{serviceMap: make(map[string]*cachedService)}, eventBroadcaster: broadcaster, eventRecorder:    recorder, </ins> } }

case cache.Sync: err, retry := s.createLoadBalancerIfNeeded(namespacedName, service, cachedService.service) if err != nil { <ins> s.eventRecorder.Event(service, "creating loadbalancer failed", err.Error()) </ins> return err, retry } // Always update the cache upon success.

case cache.Deleted: err := s.balancer.EnsureTCPLoadBalancerDeleted(s.loadBalancerName(service), s.zone.Region) if err != nil { <ins> s.eventRecorder.Event(service, "deleting loadbalancer failed", err.Error()) </ins> return err, retryable } s.cache.delete(namespacedName.String())