mirror of
https://github.com/yusing/godoxy.git
synced 2026-02-19 17:07:42 +01:00
434 lines
12 KiB
Markdown
434 lines
12 KiB
Markdown
# Docker Integration
|
|
|
|
Docker container discovery, connection management, and label-based route configuration.
|
|
|
|
## Overview
|
|
|
|
The docker package implements Docker container integration, providing shared client connections, container parsing from Docker API responses, label processing for route configuration, and container filtering capabilities.
|
|
|
|
### Primary consumers
|
|
|
|
- `internal/route/provider` - Creates Docker-based route providers
|
|
- `internal/idlewatcher` - Container idle detection
|
|
- Operators - Configure routes via Docker labels
|
|
|
|
### Non-goals
|
|
|
|
- Docker image building or management
|
|
- Container lifecycle operations (start/stop)
|
|
- Volume management
|
|
- Docker Swarm orchestration
|
|
|
|
### Stability
|
|
|
|
Stable internal package. Public API consists of client management and container parsing functions.
|
|
|
|
## Public API
|
|
|
|
### Exported types
|
|
|
|
```go
|
|
type SharedClient struct {
|
|
*client.Client
|
|
cfg types.DockerProviderConfig
|
|
refCount atomic.Int32
|
|
closedOn atomic.Int64
|
|
key string
|
|
addr string
|
|
dial func(ctx context.Context) (net.Conn, error)
|
|
unique bool
|
|
}
|
|
```
|
|
|
|
```go
|
|
type Container struct {
|
|
DockerCfg types.DockerProviderConfig
|
|
Image Image
|
|
ContainerName string
|
|
ContainerID string
|
|
Labels map[string]string
|
|
ActualLabels map[string]string
|
|
Mounts []Mount
|
|
Network string
|
|
PublicPortMapping map[int]PortSummary
|
|
PrivatePortMapping map[int]PortSummary
|
|
Aliases []string
|
|
IsExcluded bool
|
|
IsExplicit bool
|
|
IsHostNetworkMode bool
|
|
Running bool
|
|
State string
|
|
PublicHostname string
|
|
PrivateHostname string
|
|
Agent *agentpool.Agent
|
|
IdlewatcherConfig *IdlewatcherConfig
|
|
}
|
|
```
|
|
|
|
### Exported functions
|
|
|
|
```go
|
|
func NewClient(cfg types.DockerProviderConfig, unique ...bool) (*SharedClient, error)
|
|
```
|
|
|
|
Creates or returns a Docker client. Reuses existing clients for the same URL. Thread-safe.
|
|
|
|
```go
|
|
func Clients() map[string]*SharedClient
|
|
```
|
|
|
|
Returns all currently connected clients. Callers must close returned clients.
|
|
|
|
```go
|
|
func FromDocker(c *container.Summary, dockerCfg types.DockerProviderConfig) *types.Container
|
|
```
|
|
|
|
Converts Docker API container summary to internal container type. Parses labels for route configuration.
|
|
|
|
```go
|
|
func UpdatePorts(ctx context.Context, c *Container) error
|
|
```
|
|
|
|
Refreshes port mappings from container inspect.
|
|
|
|
```go
|
|
func DockerComposeProject(c *Container) string
|
|
```
|
|
|
|
Returns the Docker Compose project name.
|
|
|
|
```go
|
|
func DockerComposeService(c *Container) string
|
|
```
|
|
|
|
Returns the Docker Compose service name.
|
|
|
|
```go
|
|
func Dependencies(c *Container) []string
|
|
```
|
|
|
|
Returns container dependencies from labels.
|
|
|
|
```go
|
|
func IsBlacklisted(c *Container) bool
|
|
```
|
|
|
|
Checks if container should be excluded from routing.
|
|
|
|
## Architecture
|
|
|
|
### Core components
|
|
|
|
```mermaid
|
|
graph TD
|
|
A[Docker API] --> B[SharedClient Pool]
|
|
B --> C{Client Request}
|
|
C -->|New Client| D[Create Connection]
|
|
C -->|Existing| E[Increment RefCount]
|
|
|
|
F[Container List] --> G[FromDocker Parser]
|
|
G --> H[Container Struct]
|
|
H --> I[Route Builder]
|
|
|
|
J[Container Labels] --> K[Label Parser]
|
|
K --> L[Route Config]
|
|
|
|
subgraph Client Pool
|
|
B --> M[clientMap]
|
|
N[Cleaner Goroutine]
|
|
end
|
|
```
|
|
|
|
### Client lifecycle
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> New: NewClient() called
|
|
New --> Shared: Refcount = 1, stored in pool
|
|
Shared --> Shared: Same URL, increment refcount
|
|
Shared --> Idle: Close() called, refcount = 0
|
|
Idle --> Closed: 10s timeout elapsed
|
|
Idle --> Shared: NewClient() for same URL
|
|
Closed --> [*]: Client closed
|
|
Unique --> [*]: Close() immediately
|
|
```
|
|
|
|
### Container parsing flow
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Provider
|
|
participant SharedClient
|
|
participant DockerAPI
|
|
participant ContainerParser
|
|
participant RouteBuilder
|
|
|
|
Provider->>SharedClient: NewClient(cfg)
|
|
SharedClient->>SharedClient: Check Pool
|
|
alt Existing Client
|
|
SharedClient->>SharedClient: Increment RefCount
|
|
else New Client
|
|
SharedClient->>DockerAPI: Connect
|
|
DockerAPI-->>SharedClient: Client
|
|
end
|
|
|
|
Provider->>SharedClient: ListContainers()
|
|
SharedClient->>DockerAPI: GET /containers/json
|
|
DockerAPI-->>SharedClient: Container List
|
|
SharedClient-->>Provider: Container List
|
|
|
|
loop For Each Container
|
|
Provider->>ContainerParser: FromDocker()
|
|
ContainerParser->>ContainerParser: Parse Labels
|
|
ContainerParser->>ContainerParser: Resolve Hostnames
|
|
ContainerParser-->>Provider: *Container
|
|
end
|
|
|
|
Provider->>RouteBuilder: Create Routes
|
|
RouteBuilder-->>Provider: Routes
|
|
```
|
|
|
|
### Client pool management
|
|
|
|
The docker package maintains a pool of shared clients:
|
|
|
|
```go
|
|
var (
|
|
clientMap = make(map[string]*SharedClient, 10)
|
|
clientMapMu sync.RWMutex
|
|
)
|
|
|
|
func initClientCleaner() {
|
|
cleaner := task.RootTask("docker_clients_cleaner", true)
|
|
go func() {
|
|
ticker := time.NewTicker(cleanInterval)
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
closeTimedOutClients()
|
|
case <-cleaner.Context().Done():
|
|
// Cleanup all clients
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
```
|
|
|
|
## Configuration Surface
|
|
|
|
### Docker provider configuration
|
|
|
|
```yaml
|
|
providers:
|
|
docker:
|
|
local: ${DOCKER_HOST}
|
|
remote1:
|
|
scheme: tcp
|
|
host: docker1.local
|
|
port: 2375
|
|
remote2:
|
|
scheme: tls
|
|
host: docker2.local
|
|
port: 2375
|
|
tls:
|
|
ca_file: /path/to/ca.pem
|
|
cert_file: /path/to/cert.pem
|
|
key_file: /path/to/key.pem
|
|
```
|
|
|
|
### Route configuration labels
|
|
|
|
Route labels use the format `proxy.<alias>.<field>` where `<alias>` is the route alias (or `*` for wildcard). The base labels apply to all routes.
|
|
|
|
| Label | Description | Example |
|
|
| ---------------------- | ------------------------------- | ------------------------------- |
|
|
| `proxy.aliases` | Route aliases (comma-separated) | `proxy.aliases: www,app` |
|
|
| `proxy.exclude` | Exclude from routing | `proxy.exclude: true` |
|
|
| `proxy.network` | Docker network | `proxy.network: frontend` |
|
|
| `proxy.<alias>.host` | Override hostname | `proxy.app.host: 192.168.1.100` |
|
|
| `proxy.<alias>.port` | Target port | `proxy.app.port: 8080` |
|
|
| `proxy.<alias>.scheme` | HTTP scheme | `proxy.app.scheme: https` |
|
|
| `proxy.<alias>.*` | Any route-specific setting | `proxy.app.no_tls_verify: true` |
|
|
|
|
#### Wildcard alias
|
|
|
|
Use `proxy.*.<field>` to apply settings to all routes:
|
|
|
|
```yaml
|
|
labels:
|
|
proxy.aliases: app1,app2
|
|
proxy.*.scheme: https
|
|
proxy.app1.port: 3000 # overrides wildcard
|
|
```
|
|
|
|
### Idle watcher labels
|
|
|
|
| Label | Description | Example |
|
|
| ----------------------- | ------------------------------- | ---------------------------------- |
|
|
| `proxy.idle_timeout` | Idle timeout duration | `proxy.idle_timeout: 30m` |
|
|
| `proxy.wake_timeout` | Max time to wait for wake | `proxy.wake_timeout: 10s` |
|
|
| `proxy.stop_method` | Stop method (pause, stop, kill) | `proxy.stop_method: stop` |
|
|
| `proxy.stop_signal` | Signal to send (e.g., SIGTERM) | `proxy.stop_signal: SIGTERM` |
|
|
| `proxy.stop_timeout` | Stop timeout in seconds | `proxy.stop_timeout: 30` |
|
|
| `proxy.depends_on` | Container dependencies | `proxy.depends_on: database` |
|
|
| `proxy.start_endpoint` | Optional path restriction | `proxy.start_endpoint: /api/ready` |
|
|
| `proxy.no_loading_page` | Skip loading page | `proxy.no_loading_page: true` |
|
|
|
|
### Docker Compose labels
|
|
|
|
Those are created by Docker Compose.
|
|
|
|
| Label | Description |
|
|
| ------------------------------- | -------------------- |
|
|
| `com.docker.compose.project` | Compose project name |
|
|
| `com.docker.compose.service` | Service name |
|
|
| `com.docker.compose.depends_on` | Dependencies |
|
|
|
|
## Dependency and Integration Map
|
|
|
|
### Internal dependencies
|
|
|
|
- `internal/agentpool` - Agent-based Docker host connections
|
|
- `internal/maxmind` - Container geolocation
|
|
- `internal/types` - Container and provider types
|
|
- `internal/task/task.go` - Lifetime management
|
|
|
|
### External dependencies
|
|
|
|
- `github.com/docker/cli/cli/connhelper` - Connection helpers
|
|
- `github.com/moby/moby/client` - Docker API client
|
|
- `github.com/docker/go-connections/nat` - Port parsing
|
|
|
|
### Integration points
|
|
|
|
```go
|
|
// Route provider uses docker for container discovery
|
|
client, err := docker.NewClient(cfg)
|
|
containers, err := client.ContainerList(ctx, container.ListOptions{})
|
|
|
|
for _, c := range containers {
|
|
container := docker.FromDocker(c, cfg)
|
|
// Create routes from container
|
|
}
|
|
```
|
|
|
|
## Observability
|
|
|
|
### Logs
|
|
|
|
- Client initialization and cleanup
|
|
- Connection errors
|
|
- Container parsing errors
|
|
|
|
### Metrics
|
|
|
|
No metrics are currently exposed.
|
|
|
|
## Security Considerations
|
|
|
|
- Docker socket access requires proper permissions
|
|
- TLS certificates for remote connections
|
|
- Agent-based connections are authenticated via TLS
|
|
- Database containers are automatically blacklisted
|
|
|
|
### Blacklist detection
|
|
|
|
Containers are automatically blacklisted if they:
|
|
|
|
- Mount database directories:
|
|
- `/var/lib/postgresql/data`
|
|
- `/var/lib/mysql`
|
|
- `/var/lib/mongodb`
|
|
- `/var/lib/mariadb`
|
|
- `/var/lib/memcached`
|
|
- `/var/lib/rabbitmq`
|
|
- Expose database ports:
|
|
- 5432 (PostgreSQL)
|
|
- 3306 (MySQL/MariaDB)
|
|
- 6379 (Redis)
|
|
- 11211 (Memcached)
|
|
- 27017 (MongoDB)
|
|
|
|
## Failure Modes and Recovery
|
|
|
|
| Failure | Behavior | Recovery |
|
|
| -------------------------- | ---------------------------- | ------------------------ |
|
|
| Docker socket inaccessible | NewClient returns error | Fix socket permissions |
|
|
| Remote connection failed | NewClient returns error | Check network/tls config |
|
|
| Container inspect failed | UpdatePorts returns error | Container may be stopped |
|
|
| Invalid labels | Container created with error | Fix label syntax |
|
|
| Agent not found | Panic during client creation | Add agent to pool |
|
|
|
|
## Performance Characteristics
|
|
|
|
- Client pooling reduces connection overhead
|
|
- Reference counting prevents premature cleanup
|
|
- Background cleaner removes idle clients after 10s
|
|
- O(n) container parsing where n is container count
|
|
|
|
## Usage Examples
|
|
|
|
### Creating a Docker client
|
|
|
|
```go
|
|
dockerCfg := types.DockerProviderConfig{
|
|
URL: "unix:///var/run/docker.sock",
|
|
}
|
|
|
|
client, err := docker.NewClient(dockerCfg)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer client.Close()
|
|
```
|
|
|
|
### Using unique client
|
|
|
|
```go
|
|
// Create a unique client that won't be shared
|
|
client, err := docker.NewClient(cfg, true)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
// Remember to close when done
|
|
client.Close()
|
|
```
|
|
|
|
### Getting all clients
|
|
|
|
```go
|
|
clients := docker.Clients()
|
|
for host, client := range clients {
|
|
log.Printf("Connected to: %s", host)
|
|
}
|
|
// Use clients...
|
|
// Close all clients when done
|
|
for _, client := range clients {
|
|
client.Close()
|
|
}
|
|
```
|
|
|
|
### Parsing containers
|
|
|
|
```go
|
|
containers, err := dockerClient.ContainerList(ctx, container.ListOptions{})
|
|
for _, c := range containers {
|
|
container := docker.FromDocker(c, dockerCfg)
|
|
if container.Errors != nil {
|
|
log.Printf("Container %s has errors: %v", container.ContainerName, container.Errors)
|
|
continue
|
|
}
|
|
log.Printf("Container: %s, Aliases: %v", container.ContainerName, container.Aliases)
|
|
}
|
|
```
|
|
|
|
### Checking if container is blacklisted
|
|
|
|
```go
|
|
container := docker.FromDocker(c, dockerCfg)
|
|
if docker.IsBlacklisted(container) {
|
|
log.Printf("Container %s is blacklisted, skipping", container.ContainerName)
|
|
continue
|
|
}
|
|
```
|