From c6a6be189d52adf4ff5016077a4066f357be0ff4 Mon Sep 17 00:00:00 2001 From: ssongliu Date: Fri, 29 May 2026 17:29:29 +0800 Subject: [PATCH] fix: preserve container IP after upgrade --- agent/app/service/backup_container.go | 77 ++++++++++++++++++++------- agent/app/service/container.go | 44 +++++++++------ 2 files changed, 84 insertions(+), 37 deletions(-) diff --git a/agent/app/service/backup_container.go b/agent/app/service/backup_container.go index 48214605f9b4..100c2860a0ae 100644 --- a/agent/app/service/backup_container.go +++ b/agent/app/service/backup_container.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io/fs" + "net/netip" "os" "path" "sort" @@ -595,35 +596,22 @@ func stepRecreateContainer(recoverCtx *containerRecoverContext, taskItem *task.T return err } - networkConfig, extraNetworks := buildContainerRecoverNetworkConfig(recoverCtx.inspectInfo.NetworkSettings, hostConfig) - removeBridgeDriverIPAM(recoverCtx.client, networkConfig, extraNetworks) - createRes, err := recoverCtx.client.ContainerCreate(ctx, config, hostConfig, networkConfig, nil, recoverCtx.targetName) + createRes, err := createContainerWithOldNetworks(ctx, recoverCtx.client, config, hostConfig, recoverCtx.inspectInfo.NetworkSettings, recoverCtx.targetName) if err != nil { return err } recoverCtx.createdContainerID = createRes.ID - - extraNames := make([]string, 0, len(extraNetworks)) - for name := range extraNetworks { - extraNames = append(extraNames, name) - } - sort.Strings(extraNames) - for _, item := range extraNames { - if err := recoverCtx.client.NetworkConnect(ctx, item, recoverCtx.createdContainerID, extraNetworks[item]); err != nil { - return err - } - } return nil } -func removeBridgeDriverIPAM(cli *client.Client, primary *network.NetworkingConfig, extras map[string]*network.EndpointSettings) { +func removeUnsupportedEndpointStaticIPAM(cli *client.Client, primary *network.NetworkingConfig, extras map[string]*network.EndpointSettings) { if primary != nil { - removeBridgeDriverIPAMFromEndpoints(cli, primary.EndpointsConfig) + removeUnsupportedEndpointStaticIPAMFromEndpoints(cli, primary.EndpointsConfig) } - removeBridgeDriverIPAMFromEndpoints(cli, extras) + removeUnsupportedEndpointStaticIPAMFromEndpoints(cli, extras) } -func removeBridgeDriverIPAMFromEndpoints(cli *client.Client, endpoints map[string]*network.EndpointSettings) { +func removeUnsupportedEndpointStaticIPAMFromEndpoints(cli *client.Client, endpoints map[string]*network.EndpointSettings) { for netName, endpoint := range endpoints { if endpoint == nil || endpoint.IPAMConfig == nil { continue @@ -632,10 +620,59 @@ func removeBridgeDriverIPAMFromEndpoints(cli *client.Client, endpoints map[strin if err != nil { continue } - if info.Driver == "bridge" { - endpoint.IPAMConfig = nil + removeUnsupportedEndpointStaticIP(netName, info, endpoint) + } +} + +func removeUnsupportedEndpointStaticIP(netName string, info network.Inspect, endpoint *network.EndpointSettings) { + if endpoint == nil || endpoint.IPAMConfig == nil { + return + } + if isDefaultBridgeNetwork(netName, info) { + endpoint.IPAMConfig = nil + return + } + + if endpoint.IPAMConfig.IPv4Address != "" && !networkSupportsStaticIP(info, endpoint.IPAMConfig.IPv4Address, false) { + endpoint.IPAMConfig.IPv4Address = "" + } + if endpoint.IPAMConfig.IPv6Address != "" && !networkSupportsStaticIP(info, endpoint.IPAMConfig.IPv6Address, true) { + endpoint.IPAMConfig.IPv6Address = "" + } + if endpoint.IPAMConfig.IPv4Address == "" && endpoint.IPAMConfig.IPv6Address == "" { + endpoint.IPAMConfig = nil + } +} + +func isDefaultBridgeNetwork(netName string, info network.Inspect) bool { + return info.Driver == "bridge" && (netName == "bridge" || info.Name == "bridge") +} + +func networkSupportsStaticIP(info network.Inspect, ip string, isIPv6 bool) bool { + if ip == "" { + return true + } + addr, err := netip.ParseAddr(ip) + if err != nil { + return false + } + if addr.Is6() != isIPv6 { + return false + } + + for _, item := range info.IPAM.Config { + if item.Subnet == "" { + continue + } + subnet, err := netip.ParsePrefix(item.Subnet) + if err != nil || subnet.Addr().Is6() != isIPv6 { + continue + } + if subnet.Contains(addr) { + return true } } + return false } func ensureContainerRecoverNetworks(recoverCtx *containerRecoverContext) error { diff --git a/agent/app/service/container.go b/agent/app/service/container.go index e3fe540b4da7..05a776a4e1b1 100644 --- a/agent/app/service/container.go +++ b/agent/app/service/container.go @@ -527,6 +527,7 @@ func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bo if err != nil { return err } + removeUnsupportedEndpointStaticIPAM(client, networkConf, nil) con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name) if err != nil { taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed")) @@ -675,6 +676,7 @@ func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error { reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings) return err } + removeUnsupportedEndpointStaticIPAM(client, networkConf, nil) con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name) if err != nil { @@ -738,20 +740,13 @@ func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error { config := oldContainer.Config config.Image = req.Image hostConf := oldContainer.HostConfig - var networkConf network.NetworkingConfig - if oldContainer.NetworkSettings != nil { - for networkKey := range oldContainer.NetworkSettings.Networks { - networkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}} - break - } - } err := client.ContainerRemove(ctx, item, container.RemoveOptions{Force: true}) taskItem.LogWithStatus(i18n.GetWithName("ContainerRemoveOld", item), err) if err != nil { return err } - con, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, item) + con, err := createContainerWithOldNetworks(ctx, client, config, hostConf, oldContainer.NetworkSettings, item) if err != nil { taskItem.Log(i18n.GetMsgByKey("ContainerRecreate")) reCreateAfterUpdate(item, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings) @@ -1951,15 +1946,7 @@ func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *conta func reCreateAfterUpdate(name string, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkConf *container.NetworkSettings) { ctx := context.Background() - var oldNetworkConf network.NetworkingConfig - if networkConf != nil { - for networkKey := range networkConf.Networks { - oldNetworkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}} - break - } - } - - oldContainer, err := client.ContainerCreate(ctx, config, hostConf, &oldNetworkConf, &v1.Platform{}, name) + oldContainer, err := createContainerWithOldNetworks(ctx, client, config, hostConf, networkConf, name) if err != nil { global.LOG.Errorf("recreate after container update failed, err: %v", err) return @@ -1970,6 +1957,29 @@ func reCreateAfterUpdate(name string, client *client.Client, config *container.C global.LOG.Info("recreate after container update successful") } +func createContainerWithOldNetworks(ctx context.Context, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkSettings *container.NetworkSettings, name string) (container.CreateResponse, error) { + networkConf, extraNetworks := buildContainerRecoverNetworkConfig(networkSettings, hostConf) + removeUnsupportedEndpointStaticIPAM(client, networkConf, extraNetworks) + + created, err := client.ContainerCreate(ctx, config, hostConf, networkConf, nil, name) + if err != nil { + return created, err + } + + extraNames := make([]string, 0, len(extraNetworks)) + for item := range extraNetworks { + extraNames = append(extraNames, item) + } + sort.Strings(extraNames) + for _, item := range extraNames { + if err := client.NetworkConnect(ctx, item, created.ID, extraNetworks[item]); err != nil { + _ = client.ContainerRemove(ctx, created.ID, container.RemoveOptions{Force: true}) + return created, err + } + } + return created, nil +} + func loadVolumeBinds(binds []container.MountPoint) []dto.VolumeHelper { var datas []dto.VolumeHelper for _, bind := range binds {