mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-20 04:42:33 +02:00
Initial commit
This commit is contained in:
commit
acad8dc5ba
9 changed files with 540 additions and 0 deletions
2
.gitignore
vendored
Executable file
2
.gitignore
vendored
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
bin/
|
||||||
|
compose.yml
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"go.inferGopath": false
|
||||||
|
}
|
13
Dockerfile
Executable file
13
Dockerfile
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
MAINTAINER yusing@6uo.me
|
||||||
|
|
||||||
|
COPY bin/go-proxy /usr/bin
|
||||||
|
|
||||||
|
RUN chmod +rx /usr/bin/go-proxy
|
||||||
|
ENV DOCKER_HOST unix:///var/run/docker.sock
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
EXPOSE 443
|
||||||
|
|
||||||
|
CMD ["go-proxy"]
|
157
README.md
Normal file
157
README.md
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
# go-proxy
|
||||||
|
|
||||||
|
A simple auto docker reverse proxy for home use.
|
||||||
|
|
||||||
|
Written in **Go** with *~180 loc*.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- subdomain matching **(domain name doesn't matter)**
|
||||||
|
- path matching
|
||||||
|
- Auto hot-reload when container start / die / stop.
|
||||||
|
|
||||||
|
## Why am I making this
|
||||||
|
|
||||||
|
I have tried different reverse proxy services, i.e. [nginx proxy manager](https://nginxproxymanager.com/), [traefik](https://github.com/traefik/traefik), [nginx-proxy](https://github.com/nginx-proxy/nginx-proxy). I have found that `traefik` is not easy to use, and I don't want to click buttons every time I spin up a new container (`nginx proxy manager`). For `nginx-proxy` I found it buggy and quite unusable.
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
1. Clone the repo `git clone https://github.com/yusing/go-proxy`
|
||||||
|
|
||||||
|
2. Copy [compose.example.yml](compose.example.yml) to `compose.yml`
|
||||||
|
|
||||||
|
3. add networks to make sure it is in the same network with other containers, or make sure `proxy.<alias>.host` is reachable
|
||||||
|
|
||||||
|
4. modify the path to your SSL certs. See [Getting SSL Certs](#getting-ssl-certs)
|
||||||
|
|
||||||
|
5. start `go-proxy` with `docker compose up -d`.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
With a proper container name, no label needs to be added.
|
||||||
|
|
||||||
|
However, there are some labels you can manipulate with:
|
||||||
|
|
||||||
|
- `proxy.aliases`: comma separated aliases for subdomain matching
|
||||||
|
- defaults to `container_name`
|
||||||
|
- `proxy.<alias>.scheme`: container port protocol (`http` or `https`)
|
||||||
|
- defaults to `http`
|
||||||
|
- `proxy.<alias>.host`: proxy host
|
||||||
|
- defaults to `container_name`
|
||||||
|
- `proxy.<alias>.port`: proxy port
|
||||||
|
- defaults to first expose port (declared in `Dockerfile` or `docker-compose.yml`)
|
||||||
|
- `proxy.<alias>.path`: path matching
|
||||||
|
- defaults to empty
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
whoami:
|
||||||
|
image: traefik/whoami
|
||||||
|
container_name: whoami
|
||||||
|
# visit https://whoami.yourdomain.com to access
|
||||||
|
|
||||||
|
# enable both subdomain and path matching:
|
||||||
|
whoami:
|
||||||
|
image: traefik/whoami
|
||||||
|
container_name: whoami
|
||||||
|
labels:
|
||||||
|
- proxy.aliases=whoami,apps
|
||||||
|
- proxy.apps.path=/whoami
|
||||||
|
# 1. visit https://whoami.yourdomain.com to access
|
||||||
|
# 2. visit https://apps.yourdomain.com/whoami to access
|
||||||
|
```
|
||||||
|
|
||||||
|
For multiple port container (i.e. minio)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
minio:
|
||||||
|
image: quay.io/minio/minio
|
||||||
|
container_name: minio
|
||||||
|
command:
|
||||||
|
- server
|
||||||
|
- /data
|
||||||
|
- --console-address
|
||||||
|
- "9001"
|
||||||
|
env_file: minio.env
|
||||||
|
expose:
|
||||||
|
- 9000
|
||||||
|
- 9001
|
||||||
|
volumes:
|
||||||
|
- ./data/minio/data:/data
|
||||||
|
labels:
|
||||||
|
proxy.aliases: minio,minio-console
|
||||||
|
proxy.minio.port: 9000
|
||||||
|
proxy.minio-console.port: 9001
|
||||||
|
|
||||||
|
# visit https://minio.yourdomain.com to access minio
|
||||||
|
# visit https://minio-console.yourdomain.com/whoami to access minio console
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
Q: How to fix when it shows "no matching route for subdomain \<subdomain>"?
|
||||||
|
|
||||||
|
A: Make sure the container is running, and \<subdomain> matches any container name / alias
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
Benchmarked with `wrk` connecting `traefik/whoami`'s `/bench` endpoint
|
||||||
|
|
||||||
|
Direct connection
|
||||||
|
|
||||||
|
```shell
|
||||||
|
% wrk -t20 -c100 -d10s --latency http://homelab:4999/bench
|
||||||
|
Running 10s test @ http://homelab:4999/bench
|
||||||
|
20 threads and 100 connections
|
||||||
|
Thread Stats Avg Stdev Max +/- Stdev
|
||||||
|
Latency 3.71ms 2.26ms 48.10ms 94.95%
|
||||||
|
Req/Sec 1.41k 179.01 2.11k 69.97%
|
||||||
|
Latency Distribution
|
||||||
|
50% 3.32ms
|
||||||
|
75% 3.98ms
|
||||||
|
90% 4.97ms
|
||||||
|
99% 11.36ms
|
||||||
|
282804 requests in 10.10s, 33.98MB read
|
||||||
|
Requests/sec: 27998.62
|
||||||
|
Transfer/sec: 3.36MB
|
||||||
|
```
|
||||||
|
|
||||||
|
With **go-proxy** reverse proxy
|
||||||
|
|
||||||
|
```shell
|
||||||
|
% wrk -t20 -c100 -d10s --latency https://whoami.mydomain.com/bench
|
||||||
|
Running 10s test @ https://whoami.mydomain.com/bench
|
||||||
|
20 threads and 100 connections
|
||||||
|
Thread Stats Avg Stdev Max +/- Stdev
|
||||||
|
Latency 4.41ms 2.56ms 77.80ms 95.38%
|
||||||
|
Req/Sec 1.18k 156.44 1.63k 86.51%
|
||||||
|
Latency Distribution
|
||||||
|
50% 3.93ms
|
||||||
|
75% 4.76ms
|
||||||
|
90% 5.92ms
|
||||||
|
99% 10.46ms
|
||||||
|
235374 requests in 10.10s, 22.90MB read
|
||||||
|
Requests/sec: 23302.42
|
||||||
|
Transfer/sec: 2.27MB
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build it yourself
|
||||||
|
|
||||||
|
1. [Install go](https://go.dev/doc/install) if not already
|
||||||
|
|
||||||
|
2. Get dependencies with `go get`
|
||||||
|
|
||||||
|
3. build image with following commands
|
||||||
|
|
||||||
|
```shell
|
||||||
|
mkdir -p bin
|
||||||
|
CGO_ENABLED=0 GOOS=<platform> go build -o bin/go-proxy
|
||||||
|
docker build -t <tag> .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting SSL certs
|
||||||
|
|
||||||
|
I personally use `nginx-proxy-manager` to get SSL certs with auto renewal by Cloudflare DNS challenge. You may symlink the certs from `nginx-proxy-manager` to somewhere else, and mount them to `go-proxy`'s `/certs`
|
8
build.sh
Executable file
8
build.sh
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/sh
|
||||||
|
mkdir -p bin
|
||||||
|
CGO_ENABLED=0 GOOS=linux go build -o bin/go-proxy || exit 1
|
||||||
|
|
||||||
|
if [ "$1" = "up" ]; then
|
||||||
|
docker compose up -d --build app && \
|
||||||
|
docker compose logs -f
|
||||||
|
fi
|
14
compose.example.yml
Executable file
14
compose.example.yml
Executable file
|
@ -0,0 +1,14 @@
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
container_name: go-proxy
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
- 443:443
|
||||||
|
volumes:
|
||||||
|
- /path/to/cert.pem:/certs/cert.crt:ro
|
||||||
|
- /path/to/privkey.pem:/certs/priv.key:ro
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
extra_hosts:
|
||||||
|
- host.docker.internal:host-gateway
|
40
go.mod
Executable file
40
go.mod
Executable file
|
@ -0,0 +1,40 @@
|
||||||
|
module go-proxy
|
||||||
|
|
||||||
|
go 1.21.7
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/docker/docker v25.0.3+incompatible
|
||||||
|
github.com/jellydator/ttlcache/v3 v3.2.0
|
||||||
|
golang.org/x/text v0.14.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
|
github.com/moby/term v0.5.0 // indirect
|
||||||
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
|
||||||
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
|
golang.org/x/time v0.5.0 // indirect
|
||||||
|
gotest.tools/v3 v3.5.1 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Microsoft/go-winio v0.4.14 // indirect
|
||||||
|
github.com/distribution/reference v0.5.0 // indirect
|
||||||
|
github.com/docker/go-connections v0.5.0 // indirect
|
||||||
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.1 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||||
|
golang.org/x/net v0.21.0
|
||||||
|
golang.org/x/sys v0.17.0 // indirect
|
||||||
|
)
|
125
go.sum
Executable file
125
go.sum
Executable file
|
@ -0,0 +1,125 @@
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
|
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
||||||
|
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
|
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
|
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
||||||
|
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
|
github.com/docker/docker v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ=
|
||||||
|
github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||||
|
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
|
||||||
|
github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++uW8a3LE=
|
||||||
|
github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
|
||||||
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||||
|
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||||
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
|
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||||
|
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||||
|
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
|
||||||
|
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||||
|
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
|
||||||
|
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||||
|
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||||
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
|
||||||
|
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
|
||||||
|
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
||||||
|
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||||
|
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||||
|
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
178
main.go
Executable file
178
main.go
Executable file
|
@ -0,0 +1,178 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Scheme string
|
||||||
|
Host string
|
||||||
|
Port string
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Route struct {
|
||||||
|
Url url.URL
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
var dockerClient *client.Client
|
||||||
|
var subdomainRouteMap map[string][]Route // subdomain -> path
|
||||||
|
|
||||||
|
func NewConfig() Config {
|
||||||
|
return Config{Scheme: "http", Host: "", Port: "", Path: ""}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var err error
|
||||||
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
|
||||||
|
dockerClient, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
filter := filters.NewArgs(
|
||||||
|
filters.Arg("type", "container"),
|
||||||
|
filters.Arg("event", "start"),
|
||||||
|
filters.Arg("event", "die"), // stop seems like triggering die
|
||||||
|
// filters.Arg("event", "stop"),
|
||||||
|
)
|
||||||
|
msgs, _ := dockerClient.Events(context.Background(), types.EventsOptions{Filters: filter})
|
||||||
|
|
||||||
|
for msg := range msgs {
|
||||||
|
// TODO: handle actor only
|
||||||
|
log.Printf("[Event] %s %s caused rebuild", msg.Action, msg.Actor.Attributes["name"])
|
||||||
|
buildRoutes()
|
||||||
|
log.Printf("[Build] rebuilt %v reverse proxies", len(subdomainRouteMap))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
buildRoutes()
|
||||||
|
log.Printf("[Build] built %v reverse proxies", len(subdomainRouteMap))
|
||||||
|
|
||||||
|
http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = 100
|
||||||
|
|
||||||
|
http.HandleFunc("/", handler)
|
||||||
|
go func() {
|
||||||
|
log.Println("Starting HTTP server on port 80")
|
||||||
|
err := http.ListenAndServe(":80", nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("HTTP server error", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
log.Println("Starting HTTPS server on port 443")
|
||||||
|
err = http.ListenAndServeTLS(":443", "/certs/cert.crt", "/certs/priv.key", nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("HTTPS Server error: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func redirectTLS(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Redirect to the same host but with HTTPS
|
||||||
|
http.Redirect(w, r, "https://"+r.Host+r.URL.Path, http.StatusMovedPermanently)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.TLS == nil {
|
||||||
|
redirectTLS(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subdomain := strings.Split(r.Host, ".")[0]
|
||||||
|
// log.Printf("[Request] %s%s\n", r.Host, r.URL)
|
||||||
|
|
||||||
|
routeMap, ok := subdomainRouteMap[subdomain]
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, fmt.Sprintf("no matching route for subdomain %s", subdomain), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, route := range routeMap {
|
||||||
|
if strings.HasPrefix(r.URL.Path, route.Path) {
|
||||||
|
r.URL.Path = strings.TrimPrefix(r.URL.Path, route.Path)
|
||||||
|
// log.Printf("[Route] %s", route.Url.String())
|
||||||
|
|
||||||
|
proxy := httputil.NewSingleHostReverseProxy(&route.Url)
|
||||||
|
proxy.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.Error(w, fmt.Sprintf("no matching route for path %s for subdomain %s", r.URL.Path, subdomain), http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildContainerCfg(container types.Container) {
|
||||||
|
var aliases []string
|
||||||
|
|
||||||
|
container_name := strings.TrimPrefix(container.Names[0], "/")
|
||||||
|
aliases_label, ok := container.Labels["proxy.aliases"]
|
||||||
|
if !ok {
|
||||||
|
aliases = []string{container_name}
|
||||||
|
} else {
|
||||||
|
aliases = strings.Split(aliases_label, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, alias := range aliases {
|
||||||
|
config := NewConfig()
|
||||||
|
prefix := fmt.Sprintf("proxy.%s.", alias)
|
||||||
|
for label, value := range container.Labels {
|
||||||
|
if strings.HasPrefix(label, prefix) {
|
||||||
|
field := strings.TrimPrefix(label, prefix)
|
||||||
|
field = cases.Title(language.Und, cases.NoLower).String(field)
|
||||||
|
prop := reflect.ValueOf(&config).Elem().FieldByName(field)
|
||||||
|
prop.Set(reflect.ValueOf(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if config.Scheme != "http" && config.Scheme != "https" {
|
||||||
|
log.Printf("%s: unsupported scheme: %s, using http", container_name, config.Scheme)
|
||||||
|
config.Scheme = "http"
|
||||||
|
}
|
||||||
|
if config.Port == "" {
|
||||||
|
for _, port := range container.Ports {
|
||||||
|
config.Port = fmt.Sprintf("%d", port.PrivatePort)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if config.Port == "" {
|
||||||
|
// no ports exposed or specified
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if config.Host == "" {
|
||||||
|
if container.HostConfig.NetworkMode != "host" {
|
||||||
|
config.Host = container_name
|
||||||
|
} else {
|
||||||
|
config.Host = "host.docker.internal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, inMap := subdomainRouteMap[alias]
|
||||||
|
if !inMap {
|
||||||
|
subdomainRouteMap[alias] = make([]Route, 0)
|
||||||
|
}
|
||||||
|
route := Route{Url: url.URL{Scheme: config.Scheme, Host: fmt.Sprintf("%s:%s", config.Host, config.Port)}, Path: config.Path}
|
||||||
|
subdomainRouteMap[alias] = append(subdomainRouteMap[alias], route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func buildRoutes() {
|
||||||
|
subdomainRouteMap = make(map[string][]Route)
|
||||||
|
containerSlice, err := dockerClient.ContainerList(context.Background(), container.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, container := range containerSlice {
|
||||||
|
buildContainerCfg(container)
|
||||||
|
}
|
||||||
|
// log.Println(subdomainRouteMap)
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue