cmd/k8s-operator: allow VIP ownership reclaim after operator recreation

Fixes #18466

Signed-off-by: Raj Singh <raj@tailscale.com>
This commit is contained in:
Raj Singh 2026-01-20 13:27:55 -05:00
parent 1a79abf5fb
commit b870902c14
3 changed files with 24 additions and 5 deletions

View File

@ -446,8 +446,8 @@ func exclusiveOwnerAnnotations(pg *tsapi.ProxyGroup, operatorID string, svc *tai
if o == nil || len(o.OwnerRefs) == 0 {
return nil, fmt.Errorf("Tailscale Service %s exists, but does not contain owner annotation with owner references; not proceeding as this is likely a resource created by something other than the Tailscale Kubernetes operator", svc.Name)
}
if len(o.OwnerRefs) > 1 || o.OwnerRefs[0].OperatorID != operatorID {
return nil, fmt.Errorf("Tailscale Service %s is already owned by other operator(s) and cannot be shared across multiple clusters; configure a difference Service name to continue", svc.Name)
if len(o.OwnerRefs) > 1 {
return nil, fmt.Errorf("Tailscale Service %s is already owned by multiple operators and cannot be shared across multiple clusters; configure a difference Service name to continue", svc.Name)
}
if o.OwnerRefs[0].Resource == nil {
return nil, fmt.Errorf("Tailscale Service %s exists, but does not reference an owning resource; not proceeding as this is likely a Service already owned by an Ingress", svc.Name)
@ -455,6 +455,11 @@ func exclusiveOwnerAnnotations(pg *tsapi.ProxyGroup, operatorID string, svc *tai
if o.OwnerRefs[0].Resource.Kind != "ProxyGroup" || o.OwnerRefs[0].Resource.UID != string(pg.UID) {
return nil, fmt.Errorf("Tailscale Service %s is already owned by another resource: %#v; configure a difference Service name to continue", svc.Name, o.OwnerRefs[0].Resource)
}
// Allow operator pod recreation: if the ProxyGroup UID matches but operatorID differs,
// the operator was recreated and should reclaim ownership.
if o.OwnerRefs[0].OperatorID != operatorID {
o.OwnerRefs[0].OperatorID = operatorID
}
if o.OwnerRefs[0].Resource.Name != pg.Name {
// ProxyGroup name can be updated in place.
o.OwnerRefs[0].Resource.Name = pg.Name

View File

@ -323,13 +323,22 @@ func TestExclusiveOwnerAnnotations(t *testing.T) {
},
},
},
"owned_by_another_operator": {
"owned_by_another_operator_no_resource": {
svc: &tailscale.VIPService{
Annotations: map[string]string{
ownerAnnotation: `{"ownerRefs":[{"operatorID":"operator-2"}]}`,
},
},
wantErr: "already owned by other operator(s)",
wantErr: "does not reference an owning resource",
},
"operator_recreated_same_pg": {
// Different operatorID but same ProxyGroup UID - operator pod was recreated.
// Should allow reclaiming ownership.
svc: &tailscale.VIPService{
Annotations: map[string]string{
ownerAnnotation: `{"ownerRefs":[{"operatorID":"old-operator-id","resource":{"kind":"ProxyGroup","name":"pg1","uid":"pg1-uid"}}]}`,
},
},
},
"owned_by_an_ingress": {
svc: &tailscale.VIPService{

View File

@ -114,7 +114,12 @@ $bootstrapScriptBlock = {
New-Item -Force -Path $toolchain -ItemType Directory | Out-Null
Start-ChildScope -ScriptBlock {
Set-Location -LiteralPath $toolchain
tar --strip-components=1 -xf "$toolchain.tar.gz"
# Using an absolute path to the tar that ships with Windows
# to avoid conflicts with others (eg msys2).
$system32 = [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::System)
$tar = Join-Path $system32 'tar.exe' -Resolve
& $tar --strip-components=1 -xf "$toolchain.tar.gz"
if ($LASTEXITCODE -ne 0) {
throw "tar failed with exit code $LASTEXITCODE"
}