Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2076495de5 | ||
|
|
0259df47ef | ||
|
|
23ce117661 |
@@ -1,11 +1,11 @@
|
||||
# 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, ...).
|
||||
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.
|
||||
@@ -40,33 +40,8 @@ docker run --name demo \
|
||||
| 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_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
-1
@@ -1,6 +1,6 @@
|
||||
# :: Header
|
||||
FROM 11notes/node:stable
|
||||
ENV APP_VERSION=0.1.0
|
||||
ENV APP_VERSION=0.1.1
|
||||
ENV APP_ROOT=/labels
|
||||
|
||||
# :: Run
|
||||
|
||||
+56
-24
@@ -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,76 @@ class Labels{
|
||||
|
||||
this.#redis.connect();
|
||||
this.#redis.on('ready', ()=>{
|
||||
this.#log('connected to Redis');
|
||||
(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) => {
|
||||
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)){
|
||||
this.dockerInspect(event.id, event.status);
|
||||
this.#log(`new docker event for container ${event.id}`);
|
||||
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, 'start');
|
||||
});
|
||||
});
|
||||
}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) => {
|
||||
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)){
|
||||
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);
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ 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_INTERVAL:parseInt(process.env?.LABELS_INTERVAL || 300),
|
||||
LABELS_TIMEOUT:parseInt(process.env?.LABELS_TIMEOUT|| 30),
|
||||
}
|
||||
});
|
||||
child.on('error', (error) =>{
|
||||
|
||||
Reference in New Issue
Block a user