Files
moby/daemon/libnetwork/service.go
Mads Jon Nielsen 1143f413f6 libnetwork: ref-count service aliases for VIP DNS records
During rolling updates, VIP DNS records for service aliases were never
cleaned up when aliases were removed from the service spec. VIP alias
records were only added when the first backend joined a network
(addService=true) and only removed when the last backend left
(rmService=true). During rolling updates there is always at least one
backend, so neither condition triggered and stale aliases persisted.

Each container has its own service config, so there is no single
canonical alias list per service while a rolling update is in flight.
Instead, ref-count aliases per (network, alias) on each loadBalancer:
add the VIP DNS record on the 0->1 transition (first task on that
network claiming the alias) and remove it on the 1->0 transition (last
task referencing it has left). Ref counts are per-network because a
Swarm service can attach to multiple networks with different alias
sets, and a VIP DNS record only makes sense on the network that
actually has the alias configured.

Aliases now survive a rolling update for as long as any task -- old or
new -- still references them on that network, and new aliases register
as soon as the first task carrying them starts.

Signed-off-by: Mads Jon Nielsen <madsjon@gmail.com>
2026-05-12 14:37:09 +02:00

111 lines
3.1 KiB
Go

package libnetwork
import (
"fmt"
"net"
"sync"
"github.com/moby/moby/v2/daemon/libnetwork/internal/setmatrix"
)
var (
// A global monotonic counter to assign firewall marks to
// services.
fwMarkCtr uint32 = 256
fwMarkCtrMu sync.Mutex
)
type portConfigs []*PortConfig
func (p portConfigs) String() string {
if len(p) == 0 {
return ""
}
pc := p[0]
str := fmt.Sprintf("%d:%d/%s", pc.PublishedPort, pc.TargetPort, PortConfig_Protocol_name[int32(pc.Protocol)])
for _, pc := range p[1:] {
str = str + fmt.Sprintf(",%d:%d/%s", pc.PublishedPort, pc.TargetPort, PortConfig_Protocol_name[int32(pc.Protocol)])
}
return str
}
type serviceKey struct {
id string
ports string
}
type service struct {
name string // Service Name
id string // Service ID
// Map of loadbalancers for the service one-per attached
// network. It is keyed with network ID.
loadBalancers map[string]*loadBalancer
// List of ingress ports exposed by the service
ingressPorts portConfigs
// This maps tracks for each IP address the list of endpoints ID
// associated with it. At stable state the endpoint ID expected is 1
// but during transition and service change it is possible to have
// temporary more than 1
ipToEndpoint setmatrix.SetMatrix[string, string]
deleted bool
sync.Mutex
}
// assignIPToEndpoint inserts the mapping between the IP and the endpoint identifier
// returns true if the mapping was not present, false otherwise
// returns also the number of endpoints associated to the IP
func (s *service) assignIPToEndpoint(ip, eID string) (bool, int) {
return s.ipToEndpoint.Insert(ip, eID)
}
// removeIPToEndpoint removes the mapping between the IP and the endpoint identifier
// returns true if the mapping was deleted, false otherwise
// returns also the number of endpoints associated to the IP
func (s *service) removeIPToEndpoint(ip, eID string) (bool, int) {
return s.ipToEndpoint.Remove(ip, eID)
}
func (s *service) printIPToEndpoint(ip string) (string, bool) {
return s.ipToEndpoint.String(ip)
}
type lbBackend struct {
ip net.IP
disabled bool
// aliases is the per-task service alias list this backend was registered
// with. Stored so rmServiceBinding can decrement the matching counts in
// service.aliasRefs even when the caller-supplied list has drifted (e.g.
// during cleanupServiceBindings).
aliases []string
}
type loadBalancer struct {
vip net.IP
fwMark uint32
// Map of backend IPs backing this loadbalancer on this
// network. It is keyed with endpoint ID.
backEnds map[string]*lbBackend
// aliasRefs counts how many backends reference each service alias on
// this network. A VIP DNS record for an alias is created on the 0→1
// transition and removed on the 1→0 transition, so aliases survive
// rolling updates as long as any old or new task still claims them.
//
// The map is keyed by alias. It lives on loadBalancer (one per network
// the service is attached to) because a service can configure different
// alias sets on each of its networks.
aliasRefs map[string]int
// Back pointer to service to which the loadbalancer belongs.
service *service
sync.Mutex
}