all-kube: create Tailscale Service for HA kube-apiserver ProxyGroup (#16572)
Adds a new reconciler for ProxyGroups of type kube-apiserver that will provision a Tailscale Service for each replica to advertise. Adds two new condition types to the ProxyGroup, TailscaleServiceValid and TailscaleServiceConfigured, to post updates on the state of that reconciler in a way that's consistent with the service-pg reconciler. The created Tailscale Service name is configurable via a new ProxyGroup field spec.kubeAPISserver.ServiceName, which expects a string of the form "svc:<dns-label>". Lots of supporting changes were needed to implement this in a way that's consistent with other operator workflows, including: * Pulled containerboot's ensureServicesUnadvertised and certManager into kube/ libraries to be shared with k8s-proxy. Use those in k8s-proxy to aid Service cert sharing between replicas and graceful Service shutdown. * For certManager, add an initial wait to the cert loop to wait until the domain appears in the devices's netmap to avoid a guaranteed error on the first issue attempt when it's quick to start. * Made several methods in ingress-for-pg.go and svc-for-pg.go into functions to share with the new reconciler * Added a Resource struct to the owner refs stored in Tailscale Service annotations to be able to distinguish between Ingress- and ProxyGroup- based Services that need cleaning up in the Tailscale API. * Added a ListVIPServices method to the internal tailscale client to aid cleaning up orphaned Services * Support for reading config from a kube Secret, and partial support for config reloading, to prevent us having to force Pod restarts when config changes. * Fixed up the zap logger so it's possible to set debug log level. Updates #13358 Change-Id: Ia9607441157dd91fb9b6ecbc318eecbef446e116 Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
This commit is contained in:
@@ -41,7 +41,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
finalizerName = "tailscale.com/service-pg-finalizer"
|
||||
svcPGFinalizerName = "tailscale.com/service-pg-finalizer"
|
||||
|
||||
reasonIngressSvcInvalid = "IngressSvcInvalid"
|
||||
reasonIngressSvcValid = "IngressSvcValid"
|
||||
@@ -174,13 +174,13 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !slices.Contains(svc.Finalizers, finalizerName) {
|
||||
if !slices.Contains(svc.Finalizers, svcPGFinalizerName) {
|
||||
// This log line is printed exactly once during initial provisioning,
|
||||
// because once the finalizer is in place this block gets skipped. So,
|
||||
// this is a nice place to tell the operator that the high level,
|
||||
// multi-reconcile operation is underway.
|
||||
logger.Infof("exposing Service over tailscale")
|
||||
svc.Finalizers = append(svc.Finalizers, finalizerName)
|
||||
svc.Finalizers = append(svc.Finalizers, svcPGFinalizerName)
|
||||
if err := r.Update(ctx, svc); err != nil {
|
||||
return false, fmt.Errorf("failed to add finalizer: %w", err)
|
||||
}
|
||||
@@ -378,7 +378,7 @@ func (r *HAServiceReconciler) maybeProvision(ctx context.Context, hostname strin
|
||||
// corresponding to this Service.
|
||||
func (r *HAServiceReconciler) maybeCleanup(ctx context.Context, hostname string, svc *corev1.Service, logger *zap.SugaredLogger) (svcChanged bool, err error) {
|
||||
logger.Debugf("Ensuring any resources for Service are cleaned up")
|
||||
ix := slices.Index(svc.Finalizers, finalizerName)
|
||||
ix := slices.Index(svc.Finalizers, svcPGFinalizerName)
|
||||
if ix < 0 {
|
||||
logger.Debugf("no finalizer, nothing to do")
|
||||
return false, nil
|
||||
@@ -485,12 +485,12 @@ func (r *HAServiceReconciler) maybeCleanupProxyGroup(ctx context.Context, proxyG
|
||||
|
||||
func (r *HAServiceReconciler) deleteFinalizer(ctx context.Context, svc *corev1.Service, logger *zap.SugaredLogger) error {
|
||||
svc.Finalizers = slices.DeleteFunc(svc.Finalizers, func(f string) bool {
|
||||
return f == finalizerName
|
||||
return f == svcPGFinalizerName
|
||||
})
|
||||
logger.Debugf("ensure %q finalizer is removed", finalizerName)
|
||||
logger.Debugf("ensure %q finalizer is removed", svcPGFinalizerName)
|
||||
|
||||
if err := r.Update(ctx, svc); err != nil {
|
||||
return fmt.Errorf("failed to remove finalizer %q: %w", finalizerName, err)
|
||||
return fmt.Errorf("failed to remove finalizer %q: %w", svcPGFinalizerName, err)
|
||||
}
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
@@ -653,7 +653,7 @@ func (a *HAServiceReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Con
|
||||
// Get all config Secrets for this ProxyGroup.
|
||||
// Get all Pods
|
||||
secrets := &corev1.SecretList{}
|
||||
if err := a.List(ctx, secrets, client.InNamespace(a.tsNamespace), client.MatchingLabels(pgSecretLabels(pgName, "config"))); err != nil {
|
||||
if err := a.List(ctx, secrets, client.InNamespace(a.tsNamespace), client.MatchingLabels(pgSecretLabels(pgName, kubetypes.LabelSecretTypeConfig))); err != nil {
|
||||
return fmt.Errorf("failed to list config Secrets: %w", err)
|
||||
}
|
||||
|
||||
@@ -720,7 +720,7 @@ func (a *HAServiceReconciler) maybeUpdateAdvertiseServicesConfig(ctx context.Con
|
||||
func (a *HAServiceReconciler) numberPodsAdvertising(ctx context.Context, pgName string, serviceName tailcfg.ServiceName) (int, error) {
|
||||
// Get all state Secrets for this ProxyGroup.
|
||||
secrets := &corev1.SecretList{}
|
||||
if err := a.List(ctx, secrets, client.InNamespace(a.tsNamespace), client.MatchingLabels(pgSecretLabels(pgName, "state"))); err != nil {
|
||||
if err := a.List(ctx, secrets, client.InNamespace(a.tsNamespace), client.MatchingLabels(pgSecretLabels(pgName, kubetypes.LabelSecretTypeState))); err != nil {
|
||||
return 0, fmt.Errorf("failed to list ProxyGroup %q state Secrets: %w", pgName, err)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user