cmd/k8s-operator,k8s-opeerator: include Connector's MagicDNS name and tailnet IPs in status (#12359)

Add new fields TailnetIPs and Hostname to Connector Status. These
contain the addresses of the Tailscale node that the operator created
for the Connector to aid debugging.

Fixes #12214

Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
pull/12412/head
Tom Proctor 5 months ago committed by GitHub
parent 3a6d3f1a5b
commit 23e26e589f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -108,7 +108,7 @@ func (a *ConnectorReconciler) Reconcile(ctx context.Context, req reconcile.Reque
} }
oldCnStatus := cn.Status.DeepCopy() oldCnStatus := cn.Status.DeepCopy()
setStatus := func(cn *tsapi.Connector, conditionType tsapi.ConnectorConditionType, status metav1.ConditionStatus, reason, message string) (reconcile.Result, error) { setStatus := func(cn *tsapi.Connector, _ tsapi.ConnectorConditionType, status metav1.ConditionStatus, reason, message string) (reconcile.Result, error) {
tsoperator.SetConnectorCondition(cn, tsapi.ConnectorReady, status, reason, message, cn.Generation, a.clock, logger) tsoperator.SetConnectorCondition(cn, tsapi.ConnectorReady, status, reason, message, cn.Generation, a.clock, logger)
if !apiequality.Semantic.DeepEqual(oldCnStatus, cn.Status) { if !apiequality.Semantic.DeepEqual(oldCnStatus, cn.Status) {
// An error encountered here should get returned by the Reconcile function. // An error encountered here should get returned by the Reconcile function.
@ -211,7 +211,27 @@ func (a *ConnectorReconciler) maybeProvisionConnector(ctx context.Context, logge
gaugeConnectorResources.Set(int64(connectors.Len())) gaugeConnectorResources.Set(int64(connectors.Len()))
_, err := a.ssr.Provision(ctx, logger, sts) _, err := a.ssr.Provision(ctx, logger, sts)
return err if err != nil {
return err
}
_, tsHost, ips, err := a.ssr.DeviceInfo(ctx, crl)
if err != nil {
return err
}
if tsHost == "" {
logger.Debugf("no Tailscale hostname known yet, waiting for connector pod to finish auth")
// No hostname yet. Wait for the connector pod to auth.
cn.Status.TailnetIPs = nil
cn.Status.Hostname = ""
return nil
}
cn.Status.TailnetIPs = ips
cn.Status.Hostname = tsHost
return nil
} }
func (a *ConnectorReconciler) maybeCleanupConnector(ctx context.Context, logger *zap.SugaredLogger, cn *tsapi.Connector) (bool, error) { func (a *ConnectorReconciler) maybeCleanupConnector(ctx context.Context, logger *zap.SugaredLogger, cn *tsapi.Connector) (bool, error) {

@ -17,6 +17,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/client/fake"
tsapi "tailscale.com/k8s-operator/apis/v1alpha1" tsapi "tailscale.com/k8s-operator/apis/v1alpha1"
"tailscale.com/tstest" "tailscale.com/tstest"
"tailscale.com/util/mak"
) )
func TestConnector(t *testing.T) { func TestConnector(t *testing.T) {
@ -29,7 +30,7 @@ func TestConnector(t *testing.T) {
}, },
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
Kind: tsapi.ConnectorKind, Kind: tsapi.ConnectorKind,
APIVersion: "tailscale.io/v1alpha1", APIVersion: "tailscale.com/v1alpha1",
}, },
Spec: tsapi.ConnectorSpec{ Spec: tsapi.ConnectorSpec{
SubnetRouter: &tsapi.SubnetRouter{ SubnetRouter: &tsapi.SubnetRouter{
@ -77,6 +78,23 @@ func TestConnector(t *testing.T) {
expectEqual(t, fc, expectedSecret(t, opts), nil) expectEqual(t, fc, expectedSecret(t, opts), nil)
expectEqual(t, fc, expectedSTS(t, fc, opts), removeHashAnnotation) expectEqual(t, fc, expectedSTS(t, fc, opts), removeHashAnnotation)
// Connector status should get updated with the IP/hostname info when available.
const hostname = "foo.tailnetxyz.ts.net"
mustUpdate(t, fc, "operator-ns", opts.secretName, func(secret *corev1.Secret) {
mak.Set(&secret.Data, "device_id", []byte("1234"))
mak.Set(&secret.Data, "device_fqdn", []byte(hostname))
mak.Set(&secret.Data, "device_ips", []byte(`["127.0.0.1", "::1"]`))
})
expectReconciled(t, cr, "", "test")
cn.Finalizers = append(cn.Finalizers, "tailscale.com/finalizer")
cn.Status.IsExitNode = cn.Spec.ExitNode
cn.Status.SubnetRoutes = cn.Spec.SubnetRouter.AdvertiseRoutes.Stringify()
cn.Status.Hostname = hostname
cn.Status.TailnetIPs = []string{"127.0.0.1", "::1"}
expectEqual(t, fc, cn, func(o *tsapi.Connector) {
o.Status.Conditions = nil
})
// Add another route to be advertised. // Add another route to be advertised.
mustUpdate[tsapi.Connector](t, fc, "", "test", func(conn *tsapi.Connector) { mustUpdate[tsapi.Connector](t, fc, "", "test", func(conn *tsapi.Connector) {
conn.Spec.SubnetRouter.AdvertiseRoutes = []tsapi.Route{"10.40.0.0/14", "10.44.0.0/20"} conn.Spec.SubnetRouter.AdvertiseRoutes = []tsapi.Route{"10.40.0.0/14", "10.44.0.0/20"}

@ -117,12 +117,20 @@ spec:
x-kubernetes-list-map-keys: x-kubernetes-list-map-keys:
- type - type
x-kubernetes-list-type: map x-kubernetes-list-type: map
hostname:
description: Hostname is the fully qualified domain name of the Connector node. If MagicDNS is enabled in your tailnet, it is the MagicDNS name of the node.
type: string
isExitNode: isExitNode:
description: IsExitNode is set to true if the Connector acts as an exit node. description: IsExitNode is set to true if the Connector acts as an exit node.
type: boolean type: boolean
subnetRoutes: subnetRoutes:
description: SubnetRoutes are the routes currently exposed to tailnet via this Connector instance. description: SubnetRoutes are the routes currently exposed to tailnet via this Connector instance.
type: string type: string
tailnetIPs:
description: TailnetIPs is the set of tailnet IP addresses (both IPv4 and IPv6) assigned to the Connector node.
type: array
items:
type: string
served: true served: true
storage: true storage: true
subresources: subresources:

@ -142,12 +142,20 @@ spec:
x-kubernetes-list-map-keys: x-kubernetes-list-map-keys:
- type - type
x-kubernetes-list-type: map x-kubernetes-list-type: map
hostname:
description: Hostname is the fully qualified domain name of the Connector node. If MagicDNS is enabled in your tailnet, it is the MagicDNS name of the node.
type: string
isExitNode: isExitNode:
description: IsExitNode is set to true if the Connector acts as an exit node. description: IsExitNode is set to true if the Connector acts as an exit node.
type: boolean type: boolean
subnetRoutes: subnetRoutes:
description: SubnetRoutes are the routes currently exposed to tailnet via this Connector instance. description: SubnetRoutes are the routes currently exposed to tailnet via this Connector instance.
type: string type: string
tailnetIPs:
description: TailnetIPs is the set of tailnet IP addresses (both IPv4 and IPv6) assigned to the Connector node.
items:
type: string
type: array
type: object type: object
required: required:
- spec - spec

@ -178,6 +178,13 @@ ConnectorStatus describes the status of the Connector. This is set and managed b
List of status conditions to indicate the status of the Connector. Known condition types are `ConnectorReady`.<br/> List of status conditions to indicate the status of the Connector. Known condition types are `ConnectorReady`.<br/>
</td> </td>
<td>false</td> <td>false</td>
</tr><tr>
<td><b>hostname</b></td>
<td>string</td>
<td>
Hostname is the fully qualified domain name of the Connector node. If MagicDNS is enabled in your tailnet, it is the MagicDNS name of the node.<br/>
</td>
<td>false</td>
</tr><tr> </tr><tr>
<td><b>isExitNode</b></td> <td><b>isExitNode</b></td>
<td>boolean</td> <td>boolean</td>
@ -192,6 +199,13 @@ ConnectorStatus describes the status of the Connector. This is set and managed b
SubnetRoutes are the routes currently exposed to tailnet via this Connector instance.<br/> SubnetRoutes are the routes currently exposed to tailnet via this Connector instance.<br/>
</td> </td>
<td>false</td> <td>false</td>
</tr><tr>
<td><b>tailnetIPs</b></td>
<td>[]string</td>
<td>
TailnetIPs is the set of tailnet IP addresses (both IPv4 and IPv6) assigned to the Connector node.<br/>
</td>
<td>false</td>
</tr></tbody> </tr></tbody>
</table> </table>

@ -156,6 +156,15 @@ type ConnectorStatus struct {
// IsExitNode is set to true if the Connector acts as an exit node. // IsExitNode is set to true if the Connector acts as an exit node.
// +optional // +optional
IsExitNode bool `json:"isExitNode"` IsExitNode bool `json:"isExitNode"`
// TailnetIPs is the set of tailnet IP addresses (both IPv4 and IPv6)
// assigned to the Connector node.
// +optional
TailnetIPs []string `json:"tailnetIPs,omitempty"`
// Hostname is the fully qualified domain name of the Connector node.
// If MagicDNS is enabled in your tailnet, it is the MagicDNS name of the
// node.
// +optional
Hostname string `json:"hostname,omitempty"`
} }
// ConnectorCondition contains condition information for a Connector. // ConnectorCondition contains condition information for a Connector.

@ -125,6 +125,11 @@ func (in *ConnectorStatus) DeepCopyInto(out *ConnectorStatus) {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
} }
if in.TailnetIPs != nil {
in, out := &in.TailnetIPs, &out.TailnetIPs
*out = make([]string, len(*in))
copy(*out, *in)
}
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectorStatus. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConnectorStatus.

Loading…
Cancel
Save