mirror of
https://github.com/yusing/godoxy.git
synced 2025-07-12 09:04:02 +02:00
v0.5-rc3: update docker port detect mechanism, docker compose file and doc update
This commit is contained in:
parent
1120991019
commit
16b507bc7c
10 changed files with 77 additions and 34 deletions
|
@ -24,7 +24,7 @@ A [lightweight](docs/benchmark_result.md), easy-to-use, and efficient reverse pr
|
||||||
- Auto configuration for docker contaienrs
|
- Auto configuration for docker contaienrs
|
||||||
- Auto hot-reload on container state / config file changes
|
- Auto hot-reload on container state / config file changes
|
||||||
- Support HTTP(s), TCP and UDP
|
- Support HTTP(s), TCP and UDP
|
||||||
- Web UI for configuration and monitoring (See [screenshots](screeenshots))
|
- Web UI for configuration and monitoring (See [screenshots](https://github.com/yusing/go-proxy-frontend?tab=readme-ov-file#screenshots))
|
||||||
- Written in **[Go](https://go.dev)**
|
- Written in **[Go](https://go.dev)**
|
||||||
|
|
||||||
[🔼Back to top](#table-of-content)
|
[🔼Back to top](#table-of-content)
|
||||||
|
|
|
@ -5,7 +5,8 @@ services:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
network_mode: host
|
network_mode: host
|
||||||
labels:
|
labels:
|
||||||
- proxy.*.aliases=gp
|
- proxy.aliases=gp
|
||||||
|
- proxy.gp.port=8888
|
||||||
depends_on:
|
depends_on:
|
||||||
- app
|
- app
|
||||||
app:
|
app:
|
||||||
|
|
|
@ -85,18 +85,18 @@
|
||||||
|
|
||||||
### Syntax
|
### Syntax
|
||||||
|
|
||||||
| Label | Description |
|
| Label | Description | Default |
|
||||||
| ----------------------- | -------------------------------------------------------- |
|
| ----------------------- | -------------------------------------------------------- | ---------------- |
|
||||||
| `proxy.aliases` | comma separated aliases for subdomain and label matching |
|
| `proxy.aliases` | comma separated aliases for subdomain and label matching | `container_name` |
|
||||||
| `proxy.<alias>.<field>` | set field for specific alias |
|
| `proxy.<alias>.<field>` | set field for specific alias | N/A |
|
||||||
| `proxy.*.<field>` | set field for all aliases |
|
| `proxy.*.<field>` | set field for all aliases | N/A |
|
||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
| Field | Description | Default | Allowed Values / Syntax |
|
| Field | Description | Default | Allowed Values / Syntax |
|
||||||
| --------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| --------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `scheme` | proxy protocol | <ul><li>`http` for numeric port</li><li>`tcp` for `x:y` port</li></ul> | `http`, `https`, `tcp`, `udp` |
|
| `scheme` | proxy protocol | <ul><li>`http` for numeric port</li><li>`tcp` for `x:y` port</li></ul> | `http`, `https`, `tcp`, `udp` |
|
||||||
| `host` | proxy host | <ul><li>Docker: `container_name`</li><li>File: `localhost`</li></ul> | IP address, hostname |
|
| `host` | proxy host | <ul><li>Docker: docker client IP / hostname </li><li>File: `localhost`</li></ul> | IP address, hostname |
|
||||||
| `port` | proxy port **(http/s)** | first port in `ports:` | number in range of `1 - 65535` |
|
| `port` | proxy port **(http/s)** | first port in `ports:` | number in range of `1 - 65535` |
|
||||||
| `port` **(required)** | proxy port **(tcp/udp)** | N/A | `x:y` <br><ul><li>x: port for `go-proxy` to listen on</li><li>y: port or [_service name_](../src/common/constants.go#L55) of target container</li></ul> |
|
| `port` **(required)** | proxy port **(tcp/udp)** | N/A | `x:y` <br><ul><li>x: port for `go-proxy` to listen on</li><li>y: port or [_service name_](../src/common/constants.go#L55) of target container</li></ul> |
|
||||||
| `no_tls_verify` | whether skip tls verify **(https only)** | `false` | boolean |
|
| `no_tls_verify` | whether skip tls verify **(https only)** | `false` | boolean |
|
||||||
|
@ -104,6 +104,8 @@
|
||||||
| `set_headers` | header to set **(http/s only)** | empty | yaml style key-value mapping[<sup>2</sup>](#key-value-mapping-example) of header-value pairs |
|
| `set_headers` | header to set **(http/s only)** | empty | yaml style key-value mapping[<sup>2</sup>](#key-value-mapping-example) of header-value pairs |
|
||||||
| `hide_headers` | header to hide **(http/s only)** | empty | yaml style list[<sup>1</sup>](#list-example) of headers |
|
| `hide_headers` | header to hide **(http/s only)** | empty | yaml style list[<sup>1</sup>](#list-example) of headers |
|
||||||
|
|
||||||
|
[🔼Back to top](#table-of-content)
|
||||||
|
|
||||||
#### Key-value mapping example
|
#### Key-value mapping example
|
||||||
|
|
||||||
Docker Compose
|
Docker Compose
|
||||||
|
@ -132,6 +134,8 @@ service_a:
|
||||||
X-Custom-Header2: value3
|
X-Custom-Header2: value3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[🔼Back to top](#table-of-content)
|
||||||
|
|
||||||
#### List example
|
#### List example
|
||||||
|
|
||||||
Docker Compose
|
Docker Compose
|
||||||
|
@ -166,6 +170,23 @@ service_a:
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
- Container not showing up in proxies list
|
||||||
|
|
||||||
|
Please check that either `ports` or label `proxy.<alias>.port` is declared, i.e.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
nginx-1: # Option 1
|
||||||
|
...
|
||||||
|
ports:
|
||||||
|
- 80
|
||||||
|
nginx-2: # Option 2
|
||||||
|
...
|
||||||
|
container_name: nginx-2
|
||||||
|
labels:
|
||||||
|
proxy.nginx-2.port: 80
|
||||||
|
```
|
||||||
|
|
||||||
- Firewall issues
|
- Firewall issues
|
||||||
|
|
||||||
If you are using `ufw` with vpn that drop all inbound traffic except vpn, run below:
|
If you are using `ufw` with vpn that drop all inbound traffic except vpn, run below:
|
||||||
|
@ -237,6 +258,8 @@ services:
|
||||||
container_name: nginx
|
container_name: nginx
|
||||||
volumes:
|
volumes:
|
||||||
- nginx:/usr/share/nginx/html
|
- nginx:/usr/share/nginx/html
|
||||||
|
ports:
|
||||||
|
- 80
|
||||||
go-proxy:
|
go-proxy:
|
||||||
image: ghcr.io/yusing/go-proxy:latest
|
image: ghcr.io/yusing/go-proxy:latest
|
||||||
container_name: go-proxy
|
container_name: go-proxy
|
||||||
|
@ -251,7 +274,8 @@ services:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
network_mode: host
|
network_mode: host
|
||||||
labels:
|
labels:
|
||||||
- proxy.*.aliases=gp
|
- proxy.aliases=gp
|
||||||
|
- proxy.gp.port=8888
|
||||||
depends_on:
|
depends_on:
|
||||||
- go-proxy
|
- go-proxy
|
||||||
```
|
```
|
||||||
|
|
2
frontend
2
frontend
|
@ -1 +1 @@
|
||||||
Subproject commit 8cdf9eaa10728ca808e479ae0144b5f7a62d9a88
|
Subproject commit d0e59630d6e0beb1c22d2f242f556464a5056c1f
|
|
@ -25,7 +25,7 @@ func GetClientInfo(clientHost string) (*ClientInfo, E.NestedError) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
containers, err := E.Check(dockerClient.ContainerList(ctx, container.ListOptions{All: true}))
|
containers, err := E.Check(dockerClient.ContainerList(ctx, container.ListOptions{}))
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
return nil, E.Failure("list containers").With(err)
|
return nil, E.Failure("list containers").With(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,10 @@ func Failure(what string) NestedError {
|
||||||
return errorf("%s %w", what, ErrFailure)
|
return errorf("%s %w", what, ErrFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FailureWhy(what string, why string) NestedError {
|
||||||
|
return errorf("%s %w because %s", what, ErrFailure, why)
|
||||||
|
}
|
||||||
|
|
||||||
func Invalid(subject, what any) NestedError {
|
func Invalid(subject, what any) NestedError {
|
||||||
return errorf("%w %v - %v", ErrInvalid, subject, what)
|
return errorf("%w %v - %v", ErrInvalid, subject, what)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ func NewPort(v string) (Port, E.NestedError) {
|
||||||
return NewPortInt(p)
|
return NewPortInt(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPortInt(v int) (Port, E.NestedError) {
|
func NewPortInt[Int int | uint16](v Int) (Port, E.NestedError) {
|
||||||
pp := Port(v)
|
pp := Port(v)
|
||||||
if err := pp.boundCheck(); err.IsNotNil() {
|
if err := pp.boundCheck(); err.IsNotNil() {
|
||||||
return ErrPort, err
|
return ErrPort, err
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
D "github.com/yusing/go-proxy/docker"
|
D "github.com/yusing/go-proxy/docker"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/error"
|
||||||
M "github.com/yusing/go-proxy/models"
|
M "github.com/yusing/go-proxy/models"
|
||||||
|
@ -39,10 +40,10 @@ func (p DockerProvider) GetProxyEntries() (M.ProxyEntries, E.NestedError) {
|
||||||
|
|
||||||
info, err := D.GetClientInfo(p.dockerHost)
|
info, err := D.GetClientInfo(p.dockerHost)
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
return entries, E.From(err)
|
return entries, err
|
||||||
}
|
}
|
||||||
|
|
||||||
errors := E.NewBuilder("errors when parse docker labels for %q", p.dockerHost)
|
errors := E.NewBuilder("errors when parse docker labels")
|
||||||
|
|
||||||
for _, container := range info.Containers {
|
for _, container := range info.Containers {
|
||||||
en, err := p.getEntriesFromLabels(&container, info.Host)
|
en, err := p.getEntriesFromLabels(&container, info.Host)
|
||||||
|
@ -93,9 +94,9 @@ func (p *DockerProvider) getEntriesFromLabels(container *types.Container, client
|
||||||
entries := M.NewProxyEntries()
|
entries := M.NewProxyEntries()
|
||||||
|
|
||||||
// find first port, return if no port exposed
|
// find first port, return if no port exposed
|
||||||
defaultPort := findFirstPort(container)
|
defaultPort, err := findFirstPort(container)
|
||||||
if defaultPort == PT.NoPort {
|
if err.IsNotNil() {
|
||||||
return entries, E.Nil()
|
logrus.Debug(mainAlias, " ", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// init entries map for all aliases
|
// init entries map for all aliases
|
||||||
|
@ -103,7 +104,7 @@ func (p *DockerProvider) getEntriesFromLabels(container *types.Container, client
|
||||||
entries.Set(string(a), &M.ProxyEntry{
|
entries.Set(string(a), &M.ProxyEntry{
|
||||||
Alias: string(a),
|
Alias: string(a),
|
||||||
Host: clientHost,
|
Host: clientHost,
|
||||||
Port: fmt.Sprint(defaultPort),
|
Port: defaultPort,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -136,15 +137,23 @@ func (p *DockerProvider) getEntriesFromLabels(container *types.Container, client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entries.EachKV(func(a string, e *M.ProxyEntry) {
|
||||||
|
if e.Port == "" {
|
||||||
|
entries.UnsafeDelete(a)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return entries, errors.Build()
|
return entries, errors.Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
func findFirstPort(c *types.Container) (pp PT.Port) {
|
func findFirstPort(c *types.Container) (string, E.NestedError) {
|
||||||
|
if len(c.Ports) == 0 {
|
||||||
|
return "", E.FailureWhy("findFirstPort", "no port exposed")
|
||||||
|
}
|
||||||
for _, p := range c.Ports {
|
for _, p := range c.Ports {
|
||||||
if p.PublicPort != 0 || c.HostConfig.NetworkMode == "host" {
|
if p.PublicPort != 0 {
|
||||||
pp, _ = PT.NewPortInt(int(p.PublicPort))
|
return fmt.Sprint(p.PublicPort), E.Nil()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return PT.NoPort
|
return "", E.Failure("findFirstPort")
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,12 +175,13 @@ func (p *Provider) processReloadRequests() {
|
||||||
select {
|
select {
|
||||||
case p.cooldownCh <- struct{}{}:
|
case p.cooldownCh <- struct{}{}:
|
||||||
p.l.Info("Starting to reload routes")
|
p.l.Info("Starting to reload routes")
|
||||||
|
nRoutes := p.routes.Size()
|
||||||
|
|
||||||
p.StopAllRoutes()
|
p.StopAllRoutes()
|
||||||
p.loadRoutes()
|
p.loadRoutes()
|
||||||
p.StartAllRoutes()
|
p.StartAllRoutes()
|
||||||
|
|
||||||
p.l.Info("Routes reloaded")
|
p.l.Infof("Routes reloaded (%d -> %d)", nRoutes, p.routes.Size())
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(reloadCooldown)
|
time.Sleep(reloadCooldown)
|
||||||
|
@ -212,4 +213,4 @@ func (p *Provider) loadRoutes() E.NestedError {
|
||||||
return errors.Build()
|
return errors.Build()
|
||||||
}
|
}
|
||||||
|
|
||||||
const reloadCooldown = 300 * time.Millisecond
|
const reloadCooldown = 50 * time.Millisecond
|
||||||
|
|
|
@ -135,6 +135,10 @@ func (m *Map[KT, VT]) Delete(key KT) {
|
||||||
m.Unlock()
|
m.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Map[KT, VT]) UnsafeDelete(key KT) {
|
||||||
|
delete(m.m, key)
|
||||||
|
}
|
||||||
|
|
||||||
// MergeWith merges the contents of another Map[KT, VT]
|
// MergeWith merges the contents of another Map[KT, VT]
|
||||||
// into the current Map[KT, VT] and
|
// into the current Map[KT, VT] and
|
||||||
// returns a map that were duplicated.
|
// returns a map that were duplicated.
|
||||||
|
|
Loading…
Add table
Reference in a new issue