204 lines
7.4 KiB
Go
204 lines
7.4 KiB
Go
/*
|
|
Copyright 2023 The Kubernetes Authors.
|
|
|
|
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 defaultservicecidr
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"reflect"
|
|
"time"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
networkingapiv1beta1 "k8s.io/api/networking/v1beta1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/fields"
|
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
metav1apply "k8s.io/client-go/applyconfigurations/meta/v1"
|
|
networkingapiv1beta1apply "k8s.io/client-go/applyconfigurations/networking/v1beta1"
|
|
networkingv1beta1informers "k8s.io/client-go/informers/networking/v1beta1"
|
|
clientset "k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/kubernetes/scheme"
|
|
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
|
networkingv1beta1listers "k8s.io/client-go/listers/networking/v1beta1"
|
|
"k8s.io/client-go/tools/cache"
|
|
"k8s.io/client-go/tools/record"
|
|
"k8s.io/klog/v2"
|
|
)
|
|
|
|
const (
|
|
controllerName = "kubernetes-service-cidr-controller"
|
|
DefaultServiceCIDRName = "kubernetes"
|
|
)
|
|
|
|
// NewController returns a new *Controller that generates the default ServiceCIDR
|
|
// from the `--service-cluster-ip-range` flag and recreates it if necessary,
|
|
// but doesn't update it if is different.
|
|
// It follows the same logic that the kubernetes.default Service.
|
|
func NewController(
|
|
primaryRange net.IPNet,
|
|
secondaryRange net.IPNet,
|
|
client clientset.Interface,
|
|
) *Controller {
|
|
c := &Controller{
|
|
client: client,
|
|
interval: 10 * time.Second, // same as DefaultEndpointReconcilerInterval
|
|
}
|
|
|
|
// obtain configuration from flags
|
|
c.cidrs = append(c.cidrs, primaryRange.String())
|
|
if secondaryRange.IP != nil {
|
|
c.cidrs = append(c.cidrs, secondaryRange.String())
|
|
}
|
|
// instead of using the shared informers from the controlplane instance, we construct our own informer
|
|
// because we need such a small subset of the information available, only the kubernetes.default ServiceCIDR
|
|
c.serviceCIDRInformer = networkingv1beta1informers.NewFilteredServiceCIDRInformer(client, 12*time.Hour,
|
|
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
|
func(options *metav1.ListOptions) {
|
|
options.FieldSelector = fields.OneTermEqualSelector("metadata.name", DefaultServiceCIDRName).String()
|
|
})
|
|
|
|
c.serviceCIDRLister = networkingv1beta1listers.NewServiceCIDRLister(c.serviceCIDRInformer.GetIndexer())
|
|
c.serviceCIDRsSynced = c.serviceCIDRInformer.HasSynced
|
|
|
|
return c
|
|
}
|
|
|
|
// Controller manages selector-based service ipAddress.
|
|
type Controller struct {
|
|
cidrs []string // order matters, first cidr defines the default IP family
|
|
|
|
client clientset.Interface
|
|
eventBroadcaster record.EventBroadcaster
|
|
eventRecorder record.EventRecorder
|
|
|
|
serviceCIDRInformer cache.SharedIndexInformer
|
|
serviceCIDRLister networkingv1beta1listers.ServiceCIDRLister
|
|
serviceCIDRsSynced cache.InformerSynced
|
|
|
|
interval time.Duration
|
|
}
|
|
|
|
// Start will not return until the default ServiceCIDR exists or stopCh is closed.
|
|
func (c *Controller) Start(ctx context.Context) {
|
|
defer utilruntime.HandleCrash()
|
|
stopCh := ctx.Done()
|
|
|
|
c.eventBroadcaster = record.NewBroadcaster(record.WithContext(ctx))
|
|
c.eventRecorder = c.eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: controllerName})
|
|
c.eventBroadcaster.StartStructuredLogging(0)
|
|
c.eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: c.client.CoreV1().Events("")})
|
|
defer c.eventBroadcaster.Shutdown()
|
|
|
|
klog.Infof("Starting %s", controllerName)
|
|
defer klog.Infof("Shutting down %s", controllerName)
|
|
|
|
go c.serviceCIDRInformer.Run(stopCh)
|
|
if !cache.WaitForNamedCacheSync(controllerName, stopCh, c.serviceCIDRsSynced) {
|
|
return
|
|
}
|
|
|
|
// wait until first successfully sync
|
|
// this blocks apiserver startup so poll with a short interval
|
|
err := wait.PollUntilContextCancel(ctx, 100*time.Millisecond, true, func(ctx context.Context) (bool, error) {
|
|
syncErr := c.sync()
|
|
return syncErr == nil, nil
|
|
})
|
|
if err != nil {
|
|
klog.Infof("error initializing the default ServiceCIDR: %v", err)
|
|
|
|
}
|
|
|
|
// run the sync loop in the background with the defined interval
|
|
go wait.Until(func() {
|
|
err := c.sync()
|
|
if err != nil {
|
|
klog.Infof("error trying to sync the default ServiceCIDR: %v", err)
|
|
}
|
|
}, c.interval, stopCh)
|
|
}
|
|
|
|
func (c *Controller) sync() error {
|
|
// check if the default ServiceCIDR already exist
|
|
serviceCIDR, err := c.serviceCIDRLister.Get(DefaultServiceCIDRName)
|
|
// if exists
|
|
if err == nil {
|
|
c.syncStatus(serviceCIDR)
|
|
return nil
|
|
}
|
|
|
|
// unknown error
|
|
if !apierrors.IsNotFound(err) {
|
|
return err
|
|
}
|
|
|
|
// default ServiceCIDR does not exist
|
|
klog.Infof("Creating default ServiceCIDR with CIDRs: %v", c.cidrs)
|
|
serviceCIDR = &networkingapiv1beta1.ServiceCIDR{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: DefaultServiceCIDRName,
|
|
},
|
|
Spec: networkingapiv1beta1.ServiceCIDRSpec{
|
|
CIDRs: c.cidrs,
|
|
},
|
|
}
|
|
serviceCIDR, err = c.client.NetworkingV1beta1().ServiceCIDRs().Create(context.Background(), serviceCIDR, metav1.CreateOptions{})
|
|
if err != nil && !apierrors.IsAlreadyExists(err) {
|
|
c.eventRecorder.Eventf(serviceCIDR, v1.EventTypeWarning, "KubernetesDefaultServiceCIDRError", "The default ServiceCIDR can not be created")
|
|
return err
|
|
}
|
|
c.syncStatus(serviceCIDR)
|
|
return nil
|
|
}
|
|
|
|
func (c *Controller) syncStatus(serviceCIDR *networkingapiv1beta1.ServiceCIDR) {
|
|
// don't sync the status of the ServiceCIDR if is being deleted,
|
|
// deletion must be handled by the controller-manager
|
|
if !serviceCIDR.GetDeletionTimestamp().IsZero() {
|
|
return
|
|
}
|
|
|
|
// This controller will set the Ready condition to true if the Ready condition
|
|
// does not exist and the CIDR values match this controller CIDR values.
|
|
for _, condition := range serviceCIDR.Status.Conditions {
|
|
if condition.Type == networkingapiv1beta1.ServiceCIDRConditionReady {
|
|
if condition.Status == metav1.ConditionTrue {
|
|
return
|
|
}
|
|
klog.Infof("default ServiceCIDR condition Ready is not True: %v", condition.Status)
|
|
c.eventRecorder.Eventf(serviceCIDR, v1.EventTypeWarning, condition.Reason, condition.Message)
|
|
return
|
|
}
|
|
}
|
|
// set status to ready if the ServiceCIDR matches this configuration
|
|
if reflect.DeepEqual(c.cidrs, serviceCIDR.Spec.CIDRs) {
|
|
klog.Infof("Setting default ServiceCIDR condition Ready to True")
|
|
svcApplyStatus := networkingapiv1beta1apply.ServiceCIDRStatus().WithConditions(
|
|
metav1apply.Condition().
|
|
WithType(networkingapiv1beta1.ServiceCIDRConditionReady).
|
|
WithStatus(metav1.ConditionTrue).
|
|
WithMessage("Kubernetes default Service CIDR is ready").
|
|
WithLastTransitionTime(metav1.Now()))
|
|
svcApply := networkingapiv1beta1apply.ServiceCIDR(DefaultServiceCIDRName).WithStatus(svcApplyStatus)
|
|
if _, errApply := c.client.NetworkingV1beta1().ServiceCIDRs().ApplyStatus(context.Background(), svcApply, metav1.ApplyOptions{FieldManager: controllerName, Force: true}); errApply != nil {
|
|
klog.Infof("error updating default ServiceCIDR status: %v", errApply)
|
|
c.eventRecorder.Eventf(serviceCIDR, v1.EventTypeWarning, "KubernetesDefaultServiceCIDRError", "The default ServiceCIDR Status can not be set to Ready=True")
|
|
}
|
|
}
|
|
}
|