mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-02 06:22:24 +00:00
cmd/k8s-operator,k8s-operator: enable HA Ingress again. (#15453)
Re-enable HA Ingress again that was disabled for 1.82 release. This reverts commit fea74a60d529bcccbc8ded74644256bb6f6c7727. Updates tailscale/corp#24795 Signed-off-by: Irbe Krumina <irbe@tailscale.com>
This commit is contained in:
parent
4c5112eba6
commit
bf8c8e9e89
cmd/k8s-operator
k8s-operator
@ -103,7 +103,7 @@ spec:
|
||||
pattern: ^tag:[a-zA-Z][a-zA-Z0-9-]*$
|
||||
type:
|
||||
description: |-
|
||||
Type of the ProxyGroup proxies. Currently the only supported type is egress.
|
||||
Type of the ProxyGroup proxies. Supported types are egress and ingress.
|
||||
Type is immutable once a ProxyGroup is created.
|
||||
type: string
|
||||
enum:
|
||||
|
@ -2876,7 +2876,7 @@ spec:
|
||||
type: array
|
||||
type:
|
||||
description: |-
|
||||
Type of the ProxyGroup proxies. Currently the only supported type is egress.
|
||||
Type of the ProxyGroup proxies. Supported types are egress and ingress.
|
||||
Type is immutable once a ProxyGroup is created.
|
||||
enum:
|
||||
- egress
|
||||
|
@ -49,6 +49,7 @@ const (
|
||||
// FinalizerNamePG is the finalizer used by the IngressPGReconciler
|
||||
FinalizerNamePG = "tailscale.com/ingress-pg-finalizer"
|
||||
|
||||
indexIngressProxyGroup = ".metadata.annotations.ingress-proxy-group"
|
||||
// annotationHTTPEndpoint can be used to configure the Ingress to expose an HTTP endpoint to tailnet (as
|
||||
// well as the default HTTPS endpoint).
|
||||
annotationHTTPEndpoint = "tailscale.com/http-endpoint"
|
||||
|
@ -9,6 +9,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
@ -39,6 +40,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"tailscale.com/client/local"
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/hostinfo"
|
||||
"tailscale.com/ipn"
|
||||
@ -331,6 +333,40 @@ func runReconcilers(opts reconcilerOpts) {
|
||||
if err != nil {
|
||||
startlog.Fatalf("could not create ingress reconciler: %v", err)
|
||||
}
|
||||
lc, err := opts.tsServer.LocalClient()
|
||||
if err != nil {
|
||||
startlog.Fatalf("could not get local client: %v", err)
|
||||
}
|
||||
id, err := id(context.Background(), lc)
|
||||
if err != nil {
|
||||
startlog.Fatalf("error determining stable ID of the operator's Tailscale device: %v", err)
|
||||
}
|
||||
ingressProxyGroupFilter := handler.EnqueueRequestsFromMapFunc(ingressesFromIngressProxyGroup(mgr.GetClient(), opts.log))
|
||||
err = builder.
|
||||
ControllerManagedBy(mgr).
|
||||
For(&networkingv1.Ingress{}).
|
||||
Named("ingress-pg-reconciler").
|
||||
Watches(&corev1.Service{}, handler.EnqueueRequestsFromMapFunc(serviceHandlerForIngressPG(mgr.GetClient(), startlog))).
|
||||
Watches(&corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(HAIngressesFromSecret(mgr.GetClient(), startlog))).
|
||||
Watches(&tsapi.ProxyGroup{}, ingressProxyGroupFilter).
|
||||
Complete(&HAIngressReconciler{
|
||||
recorder: eventRecorder,
|
||||
tsClient: opts.tsClient,
|
||||
tsnetServer: opts.tsServer,
|
||||
defaultTags: strings.Split(opts.proxyTags, ","),
|
||||
Client: mgr.GetClient(),
|
||||
logger: opts.log.Named("ingress-pg-reconciler"),
|
||||
lc: lc,
|
||||
operatorID: id,
|
||||
tsNamespace: opts.tailscaleNamespace,
|
||||
})
|
||||
if err != nil {
|
||||
startlog.Fatalf("could not create ingress-pg-reconciler: %v", err)
|
||||
}
|
||||
if err := mgr.GetFieldIndexer().IndexField(context.Background(), new(networkingv1.Ingress), indexIngressProxyGroup, indexPGIngresses); err != nil {
|
||||
startlog.Fatalf("failed setting up indexer for HA Ingresses: %v", err)
|
||||
}
|
||||
|
||||
connectorFilter := handler.EnqueueRequestsFromMapFunc(managedResourceHandlerForType("connector"))
|
||||
// If a ProxyClassChanges, enqueue all Connectors that have
|
||||
// .spec.proxyClass set to the name of this ProxyClass.
|
||||
@ -1003,6 +1039,65 @@ func reconcileRequestsForPG(pg string, cl client.Client, ns string) []reconcile.
|
||||
return reqs
|
||||
}
|
||||
|
||||
func isTLSSecret(secret *corev1.Secret) bool {
|
||||
return secret.Type == corev1.SecretTypeTLS &&
|
||||
secret.ObjectMeta.Labels[kubetypes.LabelManaged] == "true" &&
|
||||
secret.ObjectMeta.Labels[kubetypes.LabelSecretType] == "certs" &&
|
||||
secret.ObjectMeta.Labels[labelDomain] != "" &&
|
||||
secret.ObjectMeta.Labels[labelProxyGroup] != ""
|
||||
}
|
||||
|
||||
func isPGStateSecret(secret *corev1.Secret) bool {
|
||||
return secret.ObjectMeta.Labels[kubetypes.LabelManaged] == "true" &&
|
||||
secret.ObjectMeta.Labels[LabelParentType] == "proxygroup" &&
|
||||
secret.ObjectMeta.Labels[kubetypes.LabelSecretType] == "state"
|
||||
}
|
||||
|
||||
// HAIngressesFromSecret returns a handler that returns reconcile requests for
|
||||
// all HA Ingresses that should be reconciled in response to a Secret event.
|
||||
func HAIngressesFromSecret(cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
|
||||
return func(ctx context.Context, o client.Object) []reconcile.Request {
|
||||
secret, ok := o.(*corev1.Secret)
|
||||
if !ok {
|
||||
logger.Infof("[unexpected] Secret handler triggered for an object that is not a Secret")
|
||||
return nil
|
||||
}
|
||||
if isTLSSecret(secret) {
|
||||
return []reconcile.Request{
|
||||
{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: secret.ObjectMeta.Labels[LabelParentNamespace],
|
||||
Name: secret.ObjectMeta.Labels[LabelParentName],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if !isPGStateSecret(secret) {
|
||||
return nil
|
||||
}
|
||||
pgName, ok := secret.ObjectMeta.Labels[LabelParentName]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
ingList := &networkingv1.IngressList{}
|
||||
if err := cl.List(ctx, ingList, client.MatchingFields{indexIngressProxyGroup: pgName}); err != nil {
|
||||
logger.Infof("error listing Ingresses, skipping a reconcile for event on Secret %s: %v", secret.Name, err)
|
||||
return nil
|
||||
}
|
||||
reqs := make([]reconcile.Request, 0)
|
||||
for _, ing := range ingList.Items {
|
||||
reqs = append(reqs, reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: ing.Namespace,
|
||||
Name: ing.Name,
|
||||
},
|
||||
})
|
||||
}
|
||||
return reqs
|
||||
}
|
||||
}
|
||||
|
||||
// egressSvcsFromEgressProxyGroup is an event handler for egress ProxyGroups. It returns reconcile requests for all
|
||||
// user-created ExternalName Services that should be exposed on this ProxyGroup.
|
||||
func egressSvcsFromEgressProxyGroup(cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
|
||||
@ -1033,6 +1128,36 @@ func egressSvcsFromEgressProxyGroup(cl client.Client, logger *zap.SugaredLogger)
|
||||
}
|
||||
}
|
||||
|
||||
// ingressesFromIngressProxyGroup is an event handler for ingress ProxyGroups. It returns reconcile requests for all
|
||||
// user-created Ingresses that should be exposed on this ProxyGroup.
|
||||
func ingressesFromIngressProxyGroup(cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
|
||||
return func(ctx context.Context, o client.Object) []reconcile.Request {
|
||||
pg, ok := o.(*tsapi.ProxyGroup)
|
||||
if !ok {
|
||||
logger.Infof("[unexpected] ProxyGroup handler triggered for an object that is not a ProxyGroup")
|
||||
return nil
|
||||
}
|
||||
if pg.Spec.Type != tsapi.ProxyGroupTypeIngress {
|
||||
return nil
|
||||
}
|
||||
ingList := &networkingv1.IngressList{}
|
||||
if err := cl.List(ctx, ingList, client.MatchingFields{indexIngressProxyGroup: pg.Name}); err != nil {
|
||||
logger.Infof("error listing Ingresses: %v, skipping a reconcile for event on ProxyGroup %s", err, pg.Name)
|
||||
return nil
|
||||
}
|
||||
reqs := make([]reconcile.Request, 0)
|
||||
for _, svc := range ingList.Items {
|
||||
reqs = append(reqs, reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Namespace: svc.Namespace,
|
||||
Name: svc.Name,
|
||||
},
|
||||
})
|
||||
}
|
||||
return reqs
|
||||
}
|
||||
}
|
||||
|
||||
// epsFromExternalNameService is an event handler for ExternalName Services that define a Tailscale egress service that
|
||||
// should be exposed on a ProxyGroup. It returns reconcile requests for EndpointSlices created for this Service.
|
||||
func epsFromExternalNameService(cl client.Client, logger *zap.SugaredLogger, ns string) handler.MapFunc {
|
||||
@ -1153,7 +1278,63 @@ func indexEgressServices(o client.Object) []string {
|
||||
return []string{o.GetAnnotations()[AnnotationProxyGroup]}
|
||||
}
|
||||
|
||||
// indexPGIngresses adds a local index to a cached Tailscale Ingresses meant to be exposed on a ProxyGroup. The index is
|
||||
// used a list filter.
|
||||
func indexPGIngresses(o client.Object) []string {
|
||||
if !hasProxyGroupAnnotation(o) {
|
||||
return nil
|
||||
}
|
||||
return []string{o.GetAnnotations()[AnnotationProxyGroup]}
|
||||
}
|
||||
|
||||
// serviceHandlerForIngressPG returns a handler for Service events that ensures that if the Service
|
||||
// associated with an event is a backend Service for a tailscale Ingress with ProxyGroup annotation,
|
||||
// the associated Ingress gets reconciled.
|
||||
func serviceHandlerForIngressPG(cl client.Client, logger *zap.SugaredLogger) handler.MapFunc {
|
||||
return func(ctx context.Context, o client.Object) []reconcile.Request {
|
||||
ingList := networkingv1.IngressList{}
|
||||
if err := cl.List(ctx, &ingList, client.InNamespace(o.GetNamespace())); err != nil {
|
||||
logger.Debugf("error listing Ingresses: %v", err)
|
||||
return nil
|
||||
}
|
||||
reqs := make([]reconcile.Request, 0)
|
||||
for _, ing := range ingList.Items {
|
||||
if ing.Spec.IngressClassName == nil || *ing.Spec.IngressClassName != tailscaleIngressClassName {
|
||||
continue
|
||||
}
|
||||
if !hasProxyGroupAnnotation(&ing) {
|
||||
continue
|
||||
}
|
||||
if ing.Spec.DefaultBackend != nil && ing.Spec.DefaultBackend.Service != nil && ing.Spec.DefaultBackend.Service.Name == o.GetName() {
|
||||
reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&ing)})
|
||||
}
|
||||
for _, rule := range ing.Spec.Rules {
|
||||
if rule.HTTP == nil {
|
||||
continue
|
||||
}
|
||||
for _, path := range rule.HTTP.Paths {
|
||||
if path.Backend.Service != nil && path.Backend.Service.Name == o.GetName() {
|
||||
reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&ing)})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return reqs
|
||||
}
|
||||
}
|
||||
|
||||
func hasProxyGroupAnnotation(obj client.Object) bool {
|
||||
ing := obj.(*networkingv1.Ingress)
|
||||
return ing.Annotations[AnnotationProxyGroup] != ""
|
||||
}
|
||||
|
||||
func id(ctx context.Context, lc *local.Client) (string, error) {
|
||||
st, err := lc.StatusWithoutPeers(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error getting tailscale status: %w", err)
|
||||
}
|
||||
if st.Self == nil {
|
||||
return "", fmt.Errorf("unexpected: device's status does not contain self status")
|
||||
}
|
||||
return string(st.Self.ID), nil
|
||||
}
|
||||
|
@ -600,7 +600,7 @@ _Appears in:_
|
||||
|
||||
| Field | Description | Default | Validation |
|
||||
| --- | --- | --- | --- |
|
||||
| `type` _[ProxyGroupType](#proxygrouptype)_ | Type of the ProxyGroup proxies. Currently the only supported type is egress.<br />Type is immutable once a ProxyGroup is created. | | Enum: [egress ingress] <br />Type: string <br /> |
|
||||
| `type` _[ProxyGroupType](#proxygrouptype)_ | Type of the ProxyGroup proxies. Supported types are egress and ingress.<br />Type is immutable once a ProxyGroup is created. | | Enum: [egress ingress] <br />Type: string <br /> |
|
||||
| `tags` _[Tags](#tags)_ | Tags that the Tailscale devices will be tagged with. Defaults to [tag:k8s].<br />If you specify custom tags here, make sure you also make the operator<br />an owner of these tags.<br />See https://tailscale.com/kb/1236/kubernetes-operator/#setting-up-the-kubernetes-operator.<br />Tags cannot be changed once a ProxyGroup device has been created.<br />Tag values must be in form ^tag:[a-zA-Z][a-zA-Z0-9-]*$. | | Pattern: `^tag:[a-zA-Z][a-zA-Z0-9-]*$` <br />Type: string <br /> |
|
||||
| `replicas` _integer_ | Replicas specifies how many replicas to create the StatefulSet with.<br />Defaults to 2. | | Minimum: 0 <br /> |
|
||||
| `hostnamePrefix` _[HostnamePrefix](#hostnameprefix)_ | HostnamePrefix is the hostname prefix to use for tailnet devices created<br />by the ProxyGroup. Each device will have the integer number from its<br />StatefulSet pod appended to this prefix to form the full hostname.<br />HostnamePrefix can contain lower case letters, numbers and dashes, it<br />must not start with a dash and must be between 1 and 62 characters long. | | Pattern: `^[a-z0-9][a-z0-9-]{0,61}$` <br />Type: string <br /> |
|
||||
|
@ -48,7 +48,7 @@ type ProxyGroupList struct {
|
||||
}
|
||||
|
||||
type ProxyGroupSpec struct {
|
||||
// Type of the ProxyGroup proxies. Currently the only supported type is egress.
|
||||
// Type of the ProxyGroup proxies. Supported types are egress and ingress.
|
||||
// Type is immutable once a ProxyGroup is created.
|
||||
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="ProxyGroup type is immutable"
|
||||
Type ProxyGroupType `json:"type"`
|
||||
|
Loading…
x
Reference in New Issue
Block a user