Compare commits

...
Author SHA1 Message Date
ElevenNotes 69f14549c6 rediss://localhost:6379/0 2023-12-04 15:16:53 +01:00
ElevenNotes 6ab10f6b2a sanitize container name 2023-12-04 14:43:43 +01:00
ElevenNotes 35fee8e82b status:null for poll 2023-12-04 14:42:57 +01:00
ElevenNotes 8686ba58c0 log for container event only if traefik label 2023-12-04 14:28:23 +01:00
ElevenNotes 437a3e92d3 event?.status 2023-12-04 14:09:20 +01:00
ElevenNotes 4796cc1aa1 update 0.1.3 2023-12-04 14:03:26 +01:00
ElevenNotes 2076495de5 update 0.1.2 2023-12-04 13:47:27 +01:00
ElevenNotes 0259df47ef update 0.1.1 2023-12-04 13:32:33 +01:00
ElevenNotes 23ce117661 drop mermaid 2023-11-28 14:46:15 +01:00
4 changed files with 78 additions and 62 deletions
+5 -30
View File
@@ -1,11 +1,11 @@
# Alpine :: Traefik Labels
![size](https://img.shields.io/docker/image-size/11notes/traefik-labels/0.1.0?color=0eb305) ![version](https://img.shields.io/docker/v/11notes/traefik-labels?color=eb7a09) ![pulls](https://img.shields.io/docker/pulls/11notes/traefik-labels?color=2b75d6) ![activity](https://img.shields.io/github/commit-activity/m/11notes/docker-traefik-labels?color=c91cb8) ![commit-last](https://img.shields.io/github/last-commit/11notes/docker-traefik-labels?color=c91cb8)
![size](https://img.shields.io/docker/image-size/11notes/traefik-labels/0.1.3?color=0eb305) ![version](https://img.shields.io/docker/v/11notes/traefik-labels?color=eb7a09) ![pulls](https://img.shields.io/docker/pulls/11notes/traefik-labels?color=2b75d6) ![activity](https://img.shields.io/github/commit-activity/m/11notes/docker-traefik-labels?color=c91cb8) ![commit-last](https://img.shields.io/github/last-commit/11notes/docker-traefik-labels?color=c91cb8)
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, ...).
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 on 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.
@@ -39,34 +39,9 @@ docker run --name demo \
## 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
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
```
| `LABELS_REDIS_URL` | the redis URL to connect, use rediss:// for SSL | rediss://localhost:6379/0 |
| `LABELS_INTERVAL` | in what interval container information is pulled | 300 |
| `LABELS_TIMEOUT` | how many seconds after an interval the keys should stay till they expire | 30 |
## Parent image
* [11notes/node:stable](https://github.com/11notes/docker-node)
-1
View File
@@ -1,6 +1,5 @@
# :: Header
FROM 11notes/node:stable
ENV APP_VERSION=0.1.0
ENV APP_ROOT=/labels
# :: Run
+70 -28
View File
@@ -1,14 +1,24 @@
const Docker = require('dockerode');
const redis = require('redis');
const ERROR = 1;
class Labels{
#docker;
#redis;
#poll = false;
constructor(){
this.#docker = new Docker({socketPath: '/run/docker.sock'});
}
#log(message, error){
console.log(JSON.stringify({
time:new Date().toISOString(),
type:(error === ERROR) ? 'ERROR' : 'INFO',
message:message,
}));
}
async watch(){
this.#redis = await redis.createClient({
url:process.env.LABELS_REDIS_URL,
@@ -20,54 +30,86 @@ class Labels{
this.#redis.connect();
this.#redis.on('ready', ()=>{
this.#log('successfully connected to redis database');
(async() => {
await this.dockerPoll();
})();
this.dockerEvents();
});
this.#redis.on('error', error =>{
console.error(error);
this.#log(error, ERROR);
});
setInterval(() => {
this.dockerPoll();
setInterval(async() => {
await 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);
}
});
if(error){
this.#log(error, ERROR);
}else{
data.on('data', async(chunk) => {
const event = JSON.parse(chunk.toString('utf8'));
if(/Container/i.test(event?.Type) && /^(start|stop|restart|kill|die|destroy)$/i.test(event?.status)){
await this.dockerInspect(event.id, event.status);
}
});
}
});
}
dockerPoll(){
this.#docker.listContainers((error, containers) => {
containers.forEach(container => {
this.dockerInspect(container.Id, 'start');
});
});
async dockerPoll(){
if(!this.#poll){
try{
this.#poll = true;
this.#log(`poll start (interval:${process.env.LABELS_INTERVAL})}`);
this.#docker.listContainers((error, containers) => {
containers.forEach(async(container) => {
await this.dockerInspect(container.Id);
});
});
}catch(e){
this.#log(e, ERROR);
}finally{
this.#log('poll end');
this.#poll = false;
}
}
}
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;
return(new Promise((resolve, reject) => {
let log = false;
const container = this.#docker.getContainer(id);
container.inspect(async(error, data) => {
if(error){
reject(error);
}
for(const label in data?.Config?.Labels){
if(/traefik\//i.test(label)){
if(!log){
this.#log(`inspect container ${data.Name.replace(/^\//i, '')}${(
(null === status) ? '' : ` event[${status}]`
)}`);
log = true;
}
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;
case /stop|kill|die|destroy/i.test(status):
await this.#redis.del(label);
break;
}
}
}
}
});
resolve(true);
});
}));
}
}
+3 -3
View File
@@ -3,9 +3,9 @@ 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),
LABELS_REDIS_URL:process.env?.LABELS_REDIS_URL || 'rediss://localhost:6379/0',
LABELS_INTERVAL:parseInt(process.env?.LABELS_INTERVAL || 300),
LABELS_TIMEOUT:parseInt(process.env?.LABELS_TIMEOUT|| 30),
}
});
child.on('error', (error) =>{