forked from github-starred/docker-traefik-labels
init
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
maintain/
|
||||
/build
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 11notes
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
89
README.md
Normal file
89
README.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Alpine :: Traefik Labels
|
||||
    
|
||||
|
||||
Run Traefik Labels based on Alpine Linux. Small, lightweight, secure and fast 🏔️
|
||||
|
||||
What can I do with this? Simply put: It will export any traefik labels on a container on the same host as this image runs to a Redis instance. This makes it possible for a centralized Traefik load balancer to update endpoints dynamically by utilizing the docker labels, just like you would on a local installation of Traefik with labels. It is meant as an alternative and simple way to proxy requests from a Traefik load balanacer to multiple docker nodes running in different networks and locations.
|
||||
|
||||
In order to use this image, you need to setup Traefik with a Redis provider and then point this image via REDIS_URL to the same Redis instance. Each entry will have an expire timer set in Redis, so that if a container is removed by a server crashing, Redis will automatically remove stale entries as well. Entries are refreshed every 60 seconds or an all docker container events (create, run, kill, stop, restart, ...).
|
||||
|
||||
## Run
|
||||
This will export all labels from all containers to the Redis instance specified in LABELS_REDIS_URL from the same host this container is running on.
|
||||
```shell
|
||||
docker run --name traefik-labels \
|
||||
-v /run/docker.sock:/run/docker.sock \
|
||||
-e LABELS_REDIS_URL="rediss://foo:bar@10.127.198.254:6379/0" \
|
||||
-d 11notes/traefik-labels:[tag]
|
||||
```
|
||||
|
||||
This is a demo webserver that will start on :8080, all the traefik labels will be exportet to the Redis instance. They follow the exact same [syntax](https://doc.traefik.io/traefik/routing/providers/kv/) as for normal Redis and Traefik, just as labels.
|
||||
```shell
|
||||
docker run --name demo \
|
||||
-p 8080:8080 \
|
||||
-l "traefik/http/routers/demo.domain.com/service=demo.domain.com" \
|
||||
-l "traefik/http/routers/demo.domain.com/rule=Host(`demo.domain.com`)" \
|
||||
-l "traefik/http/routers/demo.domain.com/tls=true" \
|
||||
-l "traefik/http/routers/demo.domain.com/entrypoints=https" \
|
||||
-l "traefik/http/services/demo.domain.com/loadbalancer/servers/0/url=http://fqdn-of-docker-node:8080" \
|
||||
-d 11notes/nginx:stable
|
||||
```
|
||||
|
||||
## Defaults
|
||||
| Parameter | Value | Description |
|
||||
| --- | --- | --- |
|
||||
| `user` | docker | user docker |
|
||||
| `uid` | 1000 | user id 1000 |
|
||||
| `gid` | 1000 | group id 1000 |
|
||||
| `home` | /labels | home directory of user docker |
|
||||
|
||||
## Environment
|
||||
| Parameter | Value | Default |
|
||||
| --- | --- | --- |
|
||||
| `LABELS_REDIS_URL` | the redis URL to connect, use rediss:// for SSL | redis:://localhost:6379/0 |
|
||||
| `LABELS_INTERVAL` | in what interval container information is pulled | 60 |
|
||||
| `LABELS_TIMEOUT` | how many seconds after an interval the keys should stay till they expire | 15 |
|
||||
|
||||
## Example
|
||||
```mermaid
|
||||
flowchart TB
|
||||
|
||||
subgraph Edge
|
||||
A[WAN]:::WAN -->|:8443| B(keepalived VIP):::KEEPALIVED
|
||||
end
|
||||
|
||||
subgraph Domain_B
|
||||
B -->|:8443| C(Traefik):::TRAEFIK
|
||||
C -->|:6379| E(Redis):::REDIS
|
||||
end
|
||||
subgraph Domain_A
|
||||
B -->|:8443| D(Traefik):::TRAEFIK
|
||||
D -->|:6379| F(Redis):::REDIS
|
||||
end
|
||||
subgraph Docker_Nodes
|
||||
id1[Node 1]
|
||||
id2[Node 2]
|
||||
idn[Node n]
|
||||
end
|
||||
|
||||
Domain_A -->|:8443| Docker_Nodes
|
||||
Domain_B --> |:8443|Docker_Nodes
|
||||
|
||||
classDef WAN fill:#000000,stroke:none,color#FFF
|
||||
classDef KEEPALIVED fill:#CC9933,stroke:none,color#000
|
||||
classDef TRAEFIK fill:#3399CC,stroke:none,color:#FFF
|
||||
classDef REDIS fill:#AA0000,stroke:none,color:#FFF
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Parent image
|
||||
* [11notes/node:stable](https://github.com/11notes/docker-node)
|
||||
|
||||
## Built with and thanks to
|
||||
* [nodejs](https://nodejs.org/en)
|
||||
* [Alpine Linux](https://alpinelinux.org)
|
||||
|
||||
## Tips
|
||||
* Only use rootless container runtime (podman, rootless docker)
|
||||
* Don't bind to ports < 1024 (requires root), use NAT/reverse proxy (haproxy, traefik, nginx)
|
||||
* Do not access docker.sock as root
|
||||
33
amd64.dockerfile
Normal file
33
amd64.dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
# :: Header
|
||||
FROM 11notes/node:stable
|
||||
ENV APP_VERSION=0.1.0
|
||||
ENV APP_ROOT=/labels
|
||||
|
||||
# :: Run
|
||||
USER root
|
||||
|
||||
# :: prepare image
|
||||
RUN set -ex; \
|
||||
mkdir -p ${APP_ROOT};
|
||||
|
||||
# :: install
|
||||
RUN set -ex; \
|
||||
cd ${APP_ROOT}; \
|
||||
npm --save install \
|
||||
redis@4.6.11 \
|
||||
dockerode@4.0.0;
|
||||
|
||||
# :: update image
|
||||
RUN set -ex; \
|
||||
apk --no-cache upgrade;
|
||||
|
||||
# :: copy root filesystem changes and set correct permissions
|
||||
COPY ./rootfs /
|
||||
RUN set -ex; \
|
||||
chmod +x -R /usr/local/bin; \
|
||||
chown -R 1000:1000 \
|
||||
${APP_ROOT};
|
||||
|
||||
# :: Start
|
||||
USER docker
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
74
rootfs/labels/app.js
Normal file
74
rootfs/labels/app.js
Normal file
@@ -0,0 +1,74 @@
|
||||
const Docker = require('dockerode');
|
||||
const redis = require('redis');
|
||||
|
||||
class Labels{
|
||||
#docker;
|
||||
#redis;
|
||||
|
||||
constructor(){
|
||||
this.#docker = new Docker({socketPath: '/run/docker.sock'});
|
||||
}
|
||||
|
||||
async watch(){
|
||||
this.#redis = await redis.createClient({
|
||||
url:process.env.LABELS_REDIS_URL,
|
||||
pingInterval:30000,
|
||||
socket:{
|
||||
rejectUnauthorized: false,
|
||||
}
|
||||
});
|
||||
|
||||
this.#redis.connect();
|
||||
this.#redis.on('ready', ()=>{
|
||||
this.dockerEvents();
|
||||
});
|
||||
|
||||
this.#redis.on('error', error =>{
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
this.dockerPoll();
|
||||
}, parseInt(process.env.LABELS_INTERVAL)*1000);
|
||||
}
|
||||
|
||||
dockerEvents(){
|
||||
this.#docker.getEvents({}, (error, data) => {
|
||||
data.on('data', (chunk) => {
|
||||
const event = JSON.parse(chunk.toString('utf8'));
|
||||
if(/Container/i.test(event?.Type) && /start|stop|restart|kill|die|destroy/i.test(event?.status)){
|
||||
this.dockerInspect(event.id, event.status);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
dockerPoll(){
|
||||
this.#docker.listContainers((error, containers) => {
|
||||
containers.forEach(container => {
|
||||
this.dockerInspect(container.Id, 'start');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
dockerInspect(id, status = null){
|
||||
const container = this.#docker.getContainer(id);
|
||||
container.inspect(async(error, data) => {
|
||||
for(const label in data?.Config?.Labels){
|
||||
if(/traefik\//i.test(label)){
|
||||
switch(true){
|
||||
case /start|restart/i.test(status):
|
||||
await this.#redis.set(label, data?.Config?.Labels[label], {EX:parseInt(process.env.LABELS_INTERVAL) + parseInt(process.env.LABELS_TIMEOUT)});
|
||||
break;
|
||||
|
||||
case /stop|kill|die|destroy/i.test(status):
|
||||
await this.#redis.del(label);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
new Labels().watch();
|
||||
18
rootfs/labels/main.js
Normal file
18
rootfs/labels/main.js
Normal file
@@ -0,0 +1,18 @@
|
||||
process.once('SIGTERM', () => process.exit(0));
|
||||
process.once('SIGINT', () => process.exit(0));
|
||||
const { fork } = require('node:child_process');
|
||||
const child = fork(`${__dirname}/app.js`, [], {
|
||||
env:{
|
||||
LABELS_REDIS_URL:process.env?.LABELS_REDIS_URL || 'redis:://localhost:6379/0',
|
||||
LABELS_INTERVAL:parseInt(process.env?.LABELS_INTERVAL || 60),
|
||||
LABELS_TIMEOUT:parseInt(process.env?.LABELS_TIMEOUT|| 15),
|
||||
}
|
||||
});
|
||||
child.on('error', (error) =>{
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
child.on('close', (code) =>{
|
||||
console.warn(`child process closed with exit code ${code}`);
|
||||
process.exit(code);
|
||||
});
|
||||
6
rootfs/usr/local/bin/entrypoint.sh
Normal file
6
rootfs/usr/local/bin/entrypoint.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/ash
|
||||
if [ -z "${1}" ]; then
|
||||
set -- "node" ${APP_ROOT}/main.js
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
Reference in New Issue
Block a user