diff --git a/Dockerfile b/Dockerfile index 85e75af..d0f4d0e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,6 +19,6 @@ WORKDIR /docker-discover COPY package.json package-lock.json ./ RUN npm install --only=prod -COPY index.js config.json rproxy.handlebars ./ +COPY index.js config.yml rproxy.handlebars ./ CMD ["pm2-runtime", "index.js"] diff --git a/config.json b/config.json deleted file mode 100644 index 8220550..0000000 --- a/config.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "docker": { - "socketPath": "/var/run/docker.sock" - }, - "rproxies": [ - "rproxy1.local", - "rproxy2.local" - ], - "sshAuth": { - "username": "root", - "privateKey": "/root/.ssh/id_rsa" - }, - "proxyConf": "/etc/haproxy/haproxy.cfg", - "reloadCmd": "service haproxy reload", - "delay": 4, - "debug": false, - "events": ["service", "node"] -} \ No newline at end of file diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..f010ec6 --- /dev/null +++ b/config.yml @@ -0,0 +1,14 @@ +delay: 4 +debug: true +events: [service, node] +docker: + socketPath: /var/run/docker.sock +rproxy: + hosts: + - rproxy1.local + - rproxy2.local + auth: + username: root, + privateKey: /root/.ssh/id_rsa + confPath: /etc/haproxy/haproxy.cfg + reloadCmd: service haproxy reload \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 240c51b..721331d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,15 @@ version: '3.7' services: - discover: + main: image: registry.verdnatura.es/docker-discover build: . environment: - - NODE_ENV=production + NODE_ENV: production volumes: - /var/run/docker.sock:/var/run/docker.sock configs: - source: config - target: /docker-discover/config.json + target: /docker-discover/config.yml - source: template target: /docker-discover/rproxy.handlebars - source: ssh diff --git a/index.js b/index.js index 27f8a0c..f2a4258 100644 --- a/index.js +++ b/index.js @@ -1,30 +1,60 @@ +require('require-yaml'); let Docker = require('dockerode'); let handlebars = require('handlebars'); let ssh = require('node-ssh'); let fs = require('fs'); -let conf = require('./config.json'); +let conf = require('./config.yml'); let docker; let template; +let isProduction = process.env.NODE_ENV === 'production'; async function updateProxy() { - console.log('Updating proxy configuration.'); - let data; + console.log('Updating reverse proxy configuration.'); + let info; - if (process.env.NODE_ENV != 'production') { - data = require('./test.json'); + if (!isProduction) { + info = require('./test.json'); } else { - data = { + info = { services: await docker.listServices(), nodes: await docker.listNodes() }; } + let services = []; + for (let serviceInfo of info.services) { + let ports = serviceInfo.Endpoint.Ports; + if (!Array.isArray(ports) || !ports.length) continue; + + let name = serviceInfo.Spec.Name; + let match = name.match(/(.+)_main$/); + if (match) name = match[1]; + + let service = { + name, + port: ports[0].PublishedPort, + nodes: [] + }; + services.push(service); + + for (let node of info.nodes) { + let address = node.ManagerStatus + ? node.ManagerStatus.Addr.split(':')[0] + : node.Status.Addr; + + service.nodes.push({ + name: node.Description.Hostname, + endpoint: `${address}:${service.port}` + }); + } + } + let tmpConf = `/tmp/rproxy.${new Date().getTime()}`; - let configString = template(data); + let configString = template({info, services}); fs.writeFileSync(tmpConf, configString); - if (conf.debug || process.env.NODE_ENV != 'production') { + if (conf.debug || !isProduction) { let delimiter = '#'.repeat(80); console.log(delimiter); console.log(configString); @@ -33,15 +63,18 @@ async function updateProxy() { let files = { local: tmpConf, - remote: conf.proxyConf + remote: conf.rproxy.confPath }; - for (let host of conf.rproxies) { + for (let host of conf.rproxy.hosts) { console.log(`Updating host: ${host}`); + if (!isProduction) continue; + let sshClient = new ssh(); - await sshClient.connect(Object.assign({host}, conf.sshAuth)); + await sshClient.connect(Object.assign({host}, conf.rproxy.auth)); await sshClient.putFiles([files]); - //await ssh.exec(conf.reloadCmd); + if (conf.rproxy.reloadCmd) + await ssh.exec(conf.rproxy.reloadCmd); await sshClient.dispose(); } @@ -53,7 +86,7 @@ async function updateProxy() { let timeoutId; docker = new Docker(conf.docker); template = handlebars.compile(fs.readFileSync('rproxy.handlebars', 'utf8')); - updateProxy(); + await updateProxy(); console.log('Listenig for events.') docker.getEvents({}, (err, stream) => { @@ -68,9 +101,9 @@ async function updateProxy() { console.log(`Event: ${event.Type}: ${event.Action}`); if (timeoutId) return; - timeoutId = setTimeout(() => { + timeoutId = setTimeout(async () => { timeoutId = null; - updateProxy(); + await updateProxy(); }, conf.delay * 1000); }) }); diff --git a/package-lock.json b/package-lock.json index 6661f77..5a2bbde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,4 +1,5 @@ { + "name": "docker-discover", "requires": true, "lockfileVersion": 1, "dependencies": { @@ -20,6 +21,14 @@ "indent-string": "^4.0.0" } }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -137,6 +146,11 @@ "once": "^1.4.0" } }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -168,6 +182,15 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -252,6 +275,14 @@ "util-deprecate": "^1.0.1" } }, + "require-yaml": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/require-yaml/-/require-yaml-0.0.1.tgz", + "integrity": "sha1-LhsY2RPDuqcqWk03O28Tjd0sMr0=", + "requires": { + "js-yaml": "^3.13.1" + } + }, "safe-buffer": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", @@ -298,6 +329,11 @@ "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", "integrity": "sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY=" }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, "ssh2": { "version": "0.8.7", "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-0.8.7.tgz", diff --git a/package.json b/package.json index b7efc70..38fbbcd 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,11 @@ "repository": { "type": "git", "url": "https://gitea.verdnatura.es/verdnatura/docker-discover" -}, - + }, "dependencies": { "dockerode": "^3.0.2", "handlebars": "^4.7.2", - "node-ssh": "^7.0.0" + "node-ssh": "^7.0.0", + "require-yaml": "0.0.1" } } diff --git a/rproxy.handlebars b/rproxy.handlebars index 59a8219..4278892 100644 --- a/rproxy.handlebars +++ b/rproxy.handlebars @@ -2,10 +2,8 @@ # Auto-generated backends {{#each services}} -{{#if Endpoint.Ports.[0]}} -backend {{Spec.Name}} - {{#each ../nodes}} - server {{Description.Hostname}}({{Status.Addr}}):{{../Endpoint.Ports.[0].PublishedPort}} {{Status.Addr}}:{{../Endpoint.Ports.[0].PublishedPort}} check +backend {{name}} + {{#each nodes}} + server {{name}}({{endpoint}}) {{endpoint}} check {{/each}} -{{/if}} {{/each}} diff --git a/test.json b/test.json index 7129126..fdbf11b 100644 --- a/test.json +++ b/test.json @@ -1,33 +1,43 @@ { "services": [ { - "Spec": {"Name": "service1"}, + "Spec": {"Name": "foo"}, "Endpoint": { "Ports": [ {"PublishedPort": 10001} ] } }, { - "Spec": {"Name": "service2"}, - "Endpoint": { - "Ports": [] - } - }, { - "Spec": {"Name": "service3"}, + "Spec": {"Name": "bar_main"}, "Endpoint": { "Ports": [ {"PublishedPort": 10003} ] } + }, { + "Spec": {"Name": "bar_foo"}, + "Endpoint": { + "Ports": [ + {"PublishedPort": 10003} + ] + } + }, { + "Spec": {"Name": "baz"}, + "Endpoint": {} } ], "nodes": [ { - "Description": {"Hostname": "node1"}, - "Status": {"Addr": "10.0.0.1"} + "Description": {"Hostname": "fooNode"}, + "ManagerStatus": {"Addr": "10.0.0.1:2377"}, + "Status": {"Addr": "0.0.0.0"} }, { - "Description": {"Hostname": "node2"}, + "Description": {"Hostname": "barNode"}, + "ManagerStatus": {"Addr": "10.0.0.2:2377"}, "Status": {"Addr": "10.0.0.2"} + }, { + "Description": {"Hostname": "bazNode"}, + "Status": {"Addr": "10.0.0.3"} } ] } \ No newline at end of file