feat: agent as docker provider, drop / reload routes when docker connection state changed, refactor

This commit is contained in:
yusing
2025-03-28 08:02:29 +08:00
parent 8c9a2b022b
commit c6f65ba69f
7 changed files with 183 additions and 114 deletions

View File

@@ -1,7 +1,10 @@
package docker
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"sync"
"sync/atomic"
@@ -9,7 +12,9 @@ import (
"github.com/docker/cli/cli/connhelper"
"github.com/docker/docker/client"
"github.com/yusing/go-proxy/agent/pkg/agent"
"github.com/yusing/go-proxy/internal/common"
config "github.com/yusing/go-proxy/internal/config/types"
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/task"
)
@@ -21,11 +26,14 @@ type (
key string
refCount uint32
closedOn int64
addr string
dial func(ctx context.Context) (net.Conn, error)
}
)
var (
clientMap = make(map[string]*SharedClient, 5)
clientMap = make(map[string]*SharedClient, 10)
clientMapMu sync.RWMutex
clientOptEnvHost = []client.Opt{
@@ -74,10 +82,7 @@ func closeTimedOutClients() {
now := time.Now().Unix()
for _, c := range clientMap {
if c.closedOn == 0 {
continue
}
if c.refCount == 0 && now-c.closedOn > clientTTLSecs {
if atomic.LoadUint32(&c.refCount) == 0 && now-atomic.LoadInt64(&c.closedOn) > clientTTLSecs {
delete(clientMap, c.key)
c.Client.Close()
logging.Debug().Str("host", c.key).Msg("docker client closed")
@@ -85,13 +90,26 @@ func closeTimedOutClients() {
}
}
func (c *SharedClient) Address() string {
return c.addr
}
func (c *SharedClient) CheckConnection(ctx context.Context) error {
conn, err := c.dial(ctx)
if err != nil {
return err
}
conn.Close()
return nil
}
// if the client is still referenced, this is no-op.
func (c *SharedClient) Close() {
atomic.StoreInt64(&c.closedOn, time.Now().Unix())
atomic.AddUint32(&c.refCount, ^uint32(0))
}
// ConnectClient creates a new Docker client connection to the specified host.
// NewClient creates a new Docker client connection to the specified host.
//
// Returns existing client if available.
//
@@ -101,7 +119,7 @@ func (c *SharedClient) Close() {
// Returns:
// - Client: the Docker client connection.
// - error: an error if the connection failed.
func ConnectClient(host string) (*SharedClient, error) {
func NewClient(host string) (*SharedClient, error) {
clientMapMu.Lock()
defer clientMapMu.Unlock()
@@ -113,33 +131,49 @@ func ConnectClient(host string) (*SharedClient, error) {
// create client
var opt []client.Opt
var addr string
var dial func(ctx context.Context) (net.Conn, error)
switch host {
case "":
return nil, errors.New("empty docker host")
case common.DockerHostFromEnv:
opt = clientOptEnvHost
default:
helper, err := connhelper.GetConnectionHelper(host)
if err != nil {
logging.Panic().Err(err).Msg("failed to get connection helper")
if agent.IsDockerHostAgent(host) {
cfg, ok := config.GetInstance().GetAgent(host)
if !ok {
panic(fmt.Errorf("agent %q not found", host))
}
if helper != nil {
httpClient := &http.Client{
Transport: &http.Transport{
DialContext: helper.Dialer,
},
opt = []client.Opt{
client.WithHost(agent.DockerHost),
client.WithHTTPClient(cfg.NewHTTPClient()),
client.WithAPIVersionNegotiation(),
}
addr = "tcp://" + cfg.Addr
dial = cfg.DialContext
} else {
switch host {
case "":
return nil, errors.New("empty docker host")
case common.DockerHostFromEnv:
opt = clientOptEnvHost
default:
helper, err := connhelper.GetConnectionHelper(host)
if err != nil {
logging.Panic().Err(err).Msg("failed to get connection helper")
}
opt = []client.Opt{
client.WithHTTPClient(httpClient),
client.WithHost(helper.Host),
client.WithAPIVersionNegotiation(),
client.WithDialContext(helper.Dialer),
}
} else {
opt = []client.Opt{
client.WithHost(host),
client.WithAPIVersionNegotiation(),
if helper != nil {
httpClient := &http.Client{
Transport: &http.Transport{
DialContext: helper.Dialer,
},
}
opt = []client.Opt{
client.WithHTTPClient(httpClient),
client.WithHost(helper.Host),
client.WithAPIVersionNegotiation(),
client.WithDialContext(helper.Dialer),
}
} else {
opt = []client.Opt{
client.WithHost(host),
client.WithAPIVersionNegotiation(),
}
}
}
}
@@ -153,9 +187,16 @@ func ConnectClient(host string) (*SharedClient, error) {
Client: client,
key: host,
refCount: 1,
addr: addr,
dial: dial,
}
defer logging.Debug().Str("host", host).Msg("docker client connected")
// non-agent client
if c.dial == nil {
c.dial = client.Dialer()
}
defer logging.Debug().Str("host", host).Msg("docker client initialized")
clientMap[c.key] = c
return c, nil

View File

@@ -5,7 +5,6 @@ import (
"errors"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
)
@@ -22,8 +21,8 @@ var listOptions = container.ListOptions{
All: true,
}
func ListContainers(clientHost string) ([]types.Container, error) {
dockerClient, err := ConnectClient(clientHost)
func ListContainers(clientHost string) ([]container.Summary, error) {
dockerClient, err := NewClient(clientHost)
if err != nil {
return nil, err
}