commit
d01cbd5a3d
25 changed files with 483 additions and 198 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,3 +1,5 @@
|
||||||
.idea
|
.idea
|
||||||
*~
|
*~
|
||||||
venv
|
venv
|
||||||
|
.docker_data
|
||||||
|
__pycache__
|
||||||
|
|
|
||||||
22
Dockerfile
22
Dockerfile
|
|
@ -1,10 +1,18 @@
|
||||||
FROM haproxy:1.9-alpine
|
FROM haproxy:2.0.2-alpine
|
||||||
|
|
||||||
RUN apk add --no-cache bash python3 py-yaml
|
WORKDIR /scripts
|
||||||
|
|
||||||
COPY entrypoint.* /
|
RUN apk add --no-cache bash python3 py-yaml supervisor docker
|
||||||
COPY conf.d /etc/haproxy/conf.d
|
|
||||||
COPY assets/errors-custom/* /etc/haproxy/errors-custom/
|
|
||||||
|
|
||||||
ENTRYPOINT [ "/entrypoint.sh" ]
|
COPY requirements.txt /scripts
|
||||||
CMD ["haproxy", "-f", "/etc/haproxy/haproxy.cfg"]
|
RUN pip3 install --upgrade pip \
|
||||||
|
&& pip install -r requirements.txt
|
||||||
|
|
||||||
|
COPY swarm.* /scripts/
|
||||||
|
COPY static.* /scripts/
|
||||||
|
COPY templates /scripts/templates/
|
||||||
|
COPY easymapping /scripts/easymapping/
|
||||||
|
|
||||||
|
COPY assets /
|
||||||
|
|
||||||
|
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisord.conf" ]
|
||||||
209
README.md
209
README.md
|
|
@ -1,17 +1,141 @@
|
||||||
# Easy HAProxy
|
# Easy HAProxy
|
||||||
|
|
||||||
This Docker image will create dynamically the `haproxy.cfg` based on very simple Yaml.
|
This Docker image will create dynamically the `haproxy.cfg` based on the labels defined in docker containers or from
|
||||||
|
a simple Yaml instead docker
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
- Enable or disable Stats on port 1936 with custom password
|
- Enable or disable Stats on port 1936 with custom password
|
||||||
- Simple mapping host => host:port
|
- Discover and setup haproxy from Docker Tag
|
||||||
- Simple redirect host => host:port
|
- Discover and setup haproxy redirect from Docker Tag
|
||||||
|
- Setup HAProxy CFG from a Yaml file.
|
||||||
|
|
||||||
|
|
||||||
# Basic Usage
|
# Basic Usage
|
||||||
|
|
||||||
Create a yaml file in your machine called `easyconfig.cfg` and put the contents:
|
The Easy HAProxy will create the `haproxy.cfg` automatically based on the containers or from a YAML provided.
|
||||||
|
|
||||||
|
The basic command line to run is:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name easy-haproxy-container \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-e DISCOVER="swarm|docker|static" \
|
||||||
|
# + Environment Variables \
|
||||||
|
# + ports mapped to the host \
|
||||||
|
byjg/easy-haproxy
|
||||||
|
```
|
||||||
|
|
||||||
|
The mapping to `/var/run/docker.sock` is necessary to discover the docker containers and get the labels;
|
||||||
|
|
||||||
|
The environment variables will setup the HAProxy.
|
||||||
|
|
||||||
|
| Environment Variable | Description |
|
||||||
|
|----------------------|-------------------------------------------------------------------------------|
|
||||||
|
| DISCOVER | How `haproxy.cfg` will be created: `static`, `docker` or `swarm` |
|
||||||
|
| HAPROXY_USERNAME | (Optional) The HAProxy username to the statistics. Default: `admin` |
|
||||||
|
| HAPROXY_PASSWORD | The HAProxy password to the statistics. If not set disable stats. |
|
||||||
|
| HAPROXY_STATS_PORT | (Optional) The HAProxy port to the statistics. Default: `1936` |
|
||||||
|
| HAPROXY_CUSTOMERRORS | (Optional) If HAProxy will use custom HTML errors. true/false. Default: false |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
The environment variable `DISCOVER` will define where is located your containers (see below more details):
|
||||||
|
- docker
|
||||||
|
- swarm
|
||||||
|
- static
|
||||||
|
|
||||||
|
# DISCOVER: `docker`
|
||||||
|
|
||||||
|
This method will use a regular docker installation to discover the containers and configure the HAProxy.
|
||||||
|
|
||||||
|
The only requirement is that containers and easy-haproxy must be in the same docker network.
|
||||||
|
|
||||||
|
The discover will occur every minute.
|
||||||
|
|
||||||
|
e.g.:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker create networkd easyhaproxy
|
||||||
|
|
||||||
|
docker run --network easyhaproxy byjg/easyhaproxy
|
||||||
|
|
||||||
|
docker run --network easyhaproxy myimage
|
||||||
|
```
|
||||||
|
|
||||||
|
# DISCOVER: `swarm`
|
||||||
|
|
||||||
|
This method requires a functional Docker Swarm Cluster. The system will search for the labels in all containers on all
|
||||||
|
swarm nodes.
|
||||||
|
|
||||||
|
The discover will occur every minute.
|
||||||
|
|
||||||
|
Important: easyhaproxy needs to be in the same network of the containers or otherwise will not access.
|
||||||
|
|
||||||
|
## Tags to be attached in the Docker Container
|
||||||
|
|
||||||
|
| Tag | Description |
|
||||||
|
|---------------------------------------------|---------------------------------------------------------------------------------------------------------|
|
||||||
|
| com.byjg.easyhaproxy.definitions | A Comma delimited list with the definitions. Each name requires the definition of the parameters below. |
|
||||||
|
| com.byjg.easyhaproxy.port.[definition] | (Optional) What is the port that the HAProxy will listen to. (Defaults to 80) |
|
||||||
|
| com.byjg.easyhaproxy.localport.[definition] | (Optional) What is the port that the container is listening. (Defaults to 80) |
|
||||||
|
| com.byjg.easyhaproxy.host.[definition] | What is the host that the HAProxy will listen to. |
|
||||||
|
| com.byjg.easyhaproxy.redirect.[definition] | (Optional) Host redirects from connections in the port defined above. |
|
||||||
|
| com.byjg.easyhaproxy.sslcert.[definition] | (Optional) Cert PEM Base64 encoded. |
|
||||||
|
|
||||||
|
|
||||||
|
Note: if you are deploying a stack set labels at the `deploy` level:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
foo:
|
||||||
|
deploy:
|
||||||
|
labels:
|
||||||
|
com.byjg.easyhaproxy.definitions: "http,https"
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Single Definition:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run \
|
||||||
|
-l com.byjg.easyhaproxy.definitions=http \
|
||||||
|
-l com.byjg.easyhaproxy.port.http=80\
|
||||||
|
-l com.byjg.easyhaproxy.host.http=byjg.com.br \
|
||||||
|
....
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiples Definitions on the same container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run \
|
||||||
|
-l com.byjg.easyhaproxy.definitions=express,admin \
|
||||||
|
|
||||||
|
-l com.byjg.easyhaproxy.port.express=80 \
|
||||||
|
-l com.byjg.easyhaproxy.localport.express=3000 \
|
||||||
|
-l com.byjg.easyhaproxy.host.express=express.byjg.com.br \
|
||||||
|
|
||||||
|
-l com.byjg.easyhaproxy.port.admin=80 \
|
||||||
|
-l com.byjg.easyhaproxy.localport.admin=3001 \
|
||||||
|
-l com.byjg.easyhaproxy.host.admin=admin.byjg.com.br \
|
||||||
|
.... \
|
||||||
|
some/myimage
|
||||||
|
```
|
||||||
|
|
||||||
|
### Redirect Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run \
|
||||||
|
-l com.byjg.easyhaproxy.redirect.<defintion>=www.byjg.com.br--http://byjg.com.br,byjg.com--http://byjg.com.br
|
||||||
|
```
|
||||||
|
|
||||||
|
# DISCOVER: `static`
|
||||||
|
|
||||||
|
This method expects a YAML file to setup the `haproxy.cfg`
|
||||||
|
|
||||||
|
Create a YAML file and map to `/etc/haproxy/easyconfig.yml`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
stats:
|
stats:
|
||||||
|
|
@ -30,7 +154,7 @@ easymapping:
|
||||||
www.host1.com.br: http://host1.com.br
|
www.host1.com.br: http://host1.com.br
|
||||||
|
|
||||||
- port: 443
|
- port: 443
|
||||||
ssl_cert: /etc/easyconfig/mycert.pem
|
ssl_cert: BASE64_PEM_CERTIFICATE
|
||||||
hosts:
|
hosts:
|
||||||
host1.com.br: container:80
|
host1.com.br: container:80
|
||||||
|
|
||||||
|
|
@ -39,24 +163,15 @@ easymapping:
|
||||||
host3.com.br: domain:8181
|
host3.com.br: domain:8181
|
||||||
```
|
```
|
||||||
|
|
||||||
Then run (remember to enable the proper ports):
|
Running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run \
|
docker run -v /my/config.yml:/etc/haproxy/easyconfig.yml .... byjg/easyhaproxy
|
||||||
-v /path/to/local:/etc/easyconfig \
|
|
||||||
-p 80:80 \
|
|
||||||
-p 8080:8080 \
|
|
||||||
-p 1936:1936 \
|
|
||||||
--name easy-haproxy-instance \
|
|
||||||
-d byjg/easy-haproxy
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Mapping custom .cfg files
|
# Mapping custom .cfg files
|
||||||
|
|
||||||
Just create a folder and put files with the extension .cfg. and map the volume to the container.
|
Map a folder containing valid HAProxy `.cfg` files to `/etc/haproxy/conf.d`. It will be concatenated to your HAProxy CFG.
|
||||||
This will concatenate your config into the main haproxy.cfg
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run \
|
docker run \
|
||||||
|
|
@ -65,48 +180,41 @@ docker run \
|
||||||
-d byjg/easy-haproxy
|
-d byjg/easy-haproxy
|
||||||
```
|
```
|
||||||
|
|
||||||
Check if your config is ok:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run \
|
|
||||||
/* other parameters */
|
|
||||||
-v /your/local/conf.d:/etc/haproxy/conf.d \
|
|
||||||
-byjg/easy-haproxy -c -f /etc/haproxy/haproxy.cfg
|
|
||||||
```
|
|
||||||
|
|
||||||
# Docker Compose
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: "3.4"
|
|
||||||
services:
|
|
||||||
front:
|
|
||||||
image: byjg/easy-haproxy
|
|
||||||
volume:
|
|
||||||
- /path/to/local:/etc/easyconfig
|
|
||||||
ports:
|
|
||||||
- 80:80
|
|
||||||
- 8080:8080
|
|
||||||
- 1936:1936
|
|
||||||
```
|
|
||||||
|
|
||||||
# Handling SSL
|
# Handling SSL
|
||||||
|
|
||||||
HaProxy can handle SSL for you. in this case add the parameter pointing to file containing
|
You can attach a valid SSL certificate to the request.
|
||||||
the pem of certificates and key in only one file:
|
|
||||||
|
|
||||||
```
|
1. First Create a single PEM file including CA.
|
||||||
- port: 443
|
|
||||||
ssl_cert: /etc/easyconfig/mycert.pem
|
```bash
|
||||||
hosts:
|
cat example.com.crt example.com.key > single.pem
|
||||||
host1.com.br: container:80
|
|
||||||
|
cat single.pem
|
||||||
|
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5ZheHqmBnEJP+
|
||||||
|
U9r1gxYWKLzdqrMrcxtQN6M1hIH9n0peuJeIrybdcV7sMbStMXI=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEojCCA4qgAwIBAgIUegW2BimwuL4RzRZ2WYkHA6U5nkAwDQYJKoZIhvcNAQEL
|
||||||
|
3j4wz8/I5fdsk090j4s5KA==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
```
|
```
|
||||||
|
|
||||||
Important: Different certificates need to be handled in different entries.
|
2. Convert it to BASE64 in a single line:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat single.pem | base64 -w0
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Use this string to define the label `com.byjg.easyhaproxy.sslcert.[definition]`
|
||||||
|
|
||||||
# Setting Custom Errors
|
# Setting Custom Errors
|
||||||
|
|
||||||
Map the volume : `/etc/haproxy/errors-custom/` and put a file named `ERROR_NUMBER.http` where ERROR_NUMBER
|
If enabled, map the volume : `/etc/haproxy/errors-custom/` to your container and put a file named `ERROR_NUMBER.http`
|
||||||
is the http error code (e.g. 503.http)
|
where ERROR_NUMBER is the http error code (e.g. 503.http)
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
|
|
||||||
|
|
@ -114,3 +222,4 @@ is the http error code (e.g. 503.http)
|
||||||
docker build -t byjg/easy-haproxy .
|
docker build -t byjg/easy-haproxy .
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
9
assets/etc/crontabs/root
Normal file
9
assets/etc/crontabs/root
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# do daily/weekly/monthly maintenance
|
||||||
|
# min hour day month weekday command
|
||||||
|
*/15 * * * * run-parts /etc/periodic/15min
|
||||||
|
0 * * * * run-parts /etc/periodic/hourly
|
||||||
|
0 2 * * * run-parts /etc/periodic/daily
|
||||||
|
0 3 * * 6 run-parts /etc/periodic/weekly
|
||||||
|
0 5 1 * * run-parts /etc/periodic/monthly
|
||||||
|
* * * * * /scripts/haproxy-reload.sh
|
||||||
|
|
||||||
3
assets/etc/haproxy/certs/README.md
Normal file
3
assets/etc/haproxy/certs/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Certs Folder
|
||||||
|
|
||||||
|
Docker Easy HAProxy will save the SSL Certs here.
|
||||||
45
assets/etc/supervisord.conf
Normal file
45
assets/etc/supervisord.conf
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
[unix_http_server]
|
||||||
|
file=/dev/shm/supervisor.sock ; (the path to the socket file)
|
||||||
|
|
||||||
|
[supervisord]
|
||||||
|
logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log)
|
||||||
|
logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)
|
||||||
|
logfile_backups=10 ; (num of main logfile rotation backups;default 10)
|
||||||
|
loglevel=info ; (log level;default info; others: debug,warn,trace)
|
||||||
|
pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
|
||||||
|
nodaemon=false ; (start in foreground if true;default false)
|
||||||
|
minfds=1024 ; (min. avail startup file descriptors;default 1024)
|
||||||
|
minprocs=200 ; (min. avail process descriptors;default 200)
|
||||||
|
user=root ;
|
||||||
|
|
||||||
|
; the below section must remain in the config file for RPC
|
||||||
|
; (supervisorctl/web interface) to work, additional interfaces may be
|
||||||
|
; added by defining them in separate rpcinterface: sections
|
||||||
|
[rpcinterface:supervisor]
|
||||||
|
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||||
|
|
||||||
|
[supervisorctl]
|
||||||
|
serverurl=unix:///dev/shm/supervisor.sock ; use a unix:// URL for a unix socket
|
||||||
|
|
||||||
|
[program:haproxy]
|
||||||
|
command = /scripts/haproxy.sh
|
||||||
|
autostart=true
|
||||||
|
autorestart=false
|
||||||
|
priority=5
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
|
||||||
|
[program:crond]
|
||||||
|
command=/usr/sbin/crond -f
|
||||||
|
autostart=true
|
||||||
|
autorestart=false
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
|
||||||
|
[eventlistener:exit_on_any_fatal]
|
||||||
|
command=/scripts/exit-event-listener.py
|
||||||
|
events=PROCESS_STATE_FATAL,PROCESS_STATE_EXITED,PROCESS_STATE_STOPPED
|
||||||
18
assets/scripts/exit-event-listener.py
Executable file
18
assets/scripts/exit-event-listener.py
Executable file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
|
||||||
|
from supervisor import childutils
|
||||||
|
|
||||||
|
def main():
|
||||||
|
while True:
|
||||||
|
headers, payload = childutils.listener.wait()
|
||||||
|
childutils.listener.ok()
|
||||||
|
events = ['PROCESS_STATE_FATAL', 'PROCESS_STATE_EXITED', 'PROCESS_STATE_STOPPED']
|
||||||
|
if not (headers['eventname'] in events):
|
||||||
|
continue
|
||||||
|
os.kill(os.getppid(), signal.SIGTERM)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
49
assets/scripts/haproxy-reload.sh
Executable file
49
assets/scripts/haproxy-reload.sh
Executable file
|
|
@ -0,0 +1,49 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
cd /scripts
|
||||||
|
|
||||||
|
RELOAD="true"
|
||||||
|
|
||||||
|
if [[ "$DISCOVER" == "static" ]]; then
|
||||||
|
CONTROL_FILE="/etc/haproxy/haproxy.cfg"
|
||||||
|
touch ${CONTROL_FILE}
|
||||||
|
cp ${CONTROL_FILE} ${CONTROL_FILE}.old
|
||||||
|
python3 static.py /etc/haproxy/easyconfig.yml > ${CONTROL_FILE}
|
||||||
|
else
|
||||||
|
CONTROL_FILE="/tmp/.docker_data"
|
||||||
|
touch ${CONTROL_FILE}
|
||||||
|
mv ${CONTROL_FILE} ${CONTROL_FILE}.old
|
||||||
|
touch ${CONTROL_FILE}
|
||||||
|
|
||||||
|
if [[ "$DISCOVER" == "docker" ]]; then
|
||||||
|
CONTAINERS=$(docker ps -q)
|
||||||
|
LABEL_PATH=".Config.Labels"
|
||||||
|
else
|
||||||
|
CONTAINERS=$(docker node ps $(docker node ls -q) --format "{{ .Name }}" --filter desired-state=running | cut -d. -f1 | sort | uniq)
|
||||||
|
LABEL_PATH=".Spec.Labels"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for container in ${CONTAINERS}; do
|
||||||
|
docker inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE}
|
||||||
|
done
|
||||||
|
|
||||||
|
if cmp -s ${CONTROL_FILE} ${CONTROL_FILE}.old ; then
|
||||||
|
RELOAD="false"
|
||||||
|
else
|
||||||
|
python3 swarm.py > /etc/haproxy/haproxy.cfg
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if cmp -s ${CONTROL_FILE} ${CONTROL_FILE}.old ; then
|
||||||
|
RELOAD="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -z "$1" ]]; then
|
||||||
|
echo "Initial configuration"
|
||||||
|
RELOAD="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$RELOAD" == "true" ]]; then
|
||||||
|
echo "Reloading..."
|
||||||
|
/usr/local/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -x /var/run/haproxy.sock -sf $(cat /run/haproxy.pid) &
|
||||||
|
fi
|
||||||
9
assets/scripts/haproxy.sh
Executable file
9
assets/scripts/haproxy.sh
Executable file
|
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
source /scripts/haproxy-reload.sh initial
|
||||||
|
|
||||||
|
/usr/local/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /var/run/haproxy.sock
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
sleep 60
|
||||||
|
done
|
||||||
12
easymapping/__init__.py
Normal file
12
easymapping/__init__.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
|
|
||||||
|
class HaproxyConfigGenerator:
|
||||||
|
def __init__(self, mapping):
|
||||||
|
self.mapping = mapping
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
file_loader = FileSystemLoader('templates')
|
||||||
|
env = Environment(loader=file_loader)
|
||||||
|
template = env.get_template('haproxy.cfg.j2')
|
||||||
|
return template.render(data=self.mapping)
|
||||||
109
entrypoint.py
109
entrypoint.py
|
|
@ -1,109 +0,0 @@
|
||||||
import yaml
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if len(sys.argv) != 2:
|
|
||||||
print("You need to pass the easyconfig.cfg path")
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
def defaults(custom):
|
|
||||||
result = """
|
|
||||||
defaults
|
|
||||||
log global
|
|
||||||
|
|
||||||
timeout connect 3s
|
|
||||||
timeout client 10s
|
|
||||||
timeout server 10m
|
|
||||||
"""
|
|
||||||
if custom:
|
|
||||||
result += """
|
|
||||||
errorfile 400 /etc/haproxy/errors-custom/400.http
|
|
||||||
errorfile 403 /etc/haproxy/errors-custom/403.http
|
|
||||||
errorfile 408 /etc/haproxy/errors-custom/408.http
|
|
||||||
errorfile 500 /etc/haproxy/errors-custom/500.http
|
|
||||||
errorfile 502 /etc/haproxy/errors-custom/502.http
|
|
||||||
errorfile 503 /etc/haproxy/errors-custom/503.http
|
|
||||||
errorfile 504 /etc/haproxy/errors-custom/504.http
|
|
||||||
"""
|
|
||||||
result += """
|
|
||||||
global
|
|
||||||
log /dev/log local0
|
|
||||||
maxconn 2000
|
|
||||||
tune.ssl.default-dh-param 2048
|
|
||||||
"""
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def stats(map):
|
|
||||||
return """
|
|
||||||
frontend stats
|
|
||||||
bind *:{2}
|
|
||||||
mode http
|
|
||||||
stats enable
|
|
||||||
stats hide-version
|
|
||||||
stats realm Haproxy\ Statistics
|
|
||||||
stats uri /
|
|
||||||
stats auth {0}:{1}
|
|
||||||
# acl is_proxystats hdr(host) -i some.host.com
|
|
||||||
# default_backend srv_stats
|
|
||||||
# use_backend srv_stats if is_proxystats
|
|
||||||
default_backend srv_stats
|
|
||||||
|
|
||||||
backend srv_stats
|
|
||||||
mode http
|
|
||||||
server Local 127.0.0.1:{2}
|
|
||||||
""".format(map["username"], map["password"], map["port"] if "port" in map else 1936)
|
|
||||||
|
|
||||||
|
|
||||||
def easymapping(o, salt):
|
|
||||||
port = o["port"]
|
|
||||||
ssl = " ssl crt " + o["ssl_cert"] if "ssl_cert" in o else ""
|
|
||||||
hosts = o["hosts"] if "hosts" in o else dict()
|
|
||||||
redir = o["redirect"] if "redirect" in o else dict()
|
|
||||||
|
|
||||||
result = """
|
|
||||||
frontend http_in_{0}_{1}
|
|
||||||
bind *:{0} {2}
|
|
||||||
mode http
|
|
||||||
|
|
||||||
""".format(port, salt, ssl)
|
|
||||||
|
|
||||||
for k in redir:
|
|
||||||
result += " redirect prefix " + redir[k] + " code 301 if { hdr(host) -i " + k + " }\n"
|
|
||||||
|
|
||||||
result += "\n"
|
|
||||||
for k in hosts:
|
|
||||||
host = k.replace(".", "_") + "_{0}_{1}".format(port, salt)
|
|
||||||
result += " acl is_rule_{0}_1 hdr(host) -i {1}\n".format(host, k)
|
|
||||||
result += " acl is_rule_{0}_2 hdr(host) -i {1}:{2}\n".format(host, k, port)
|
|
||||||
result += " use_backend srv_{0} if is_rule_{0}_1 OR is_rule_{0}_2\n\n".format(host)
|
|
||||||
|
|
||||||
for k in hosts:
|
|
||||||
host = k.replace(".", "_") + "_{0}_{1}".format(port, salt)
|
|
||||||
result += """
|
|
||||||
backend srv_{0}
|
|
||||||
balance roundrobin
|
|
||||||
mode http
|
|
||||||
option forwardfor
|
|
||||||
http-request set-header X-Forwarded-Port %[dst_port]""".format(host) + """
|
|
||||||
http-request add-header X-Forwarded-Proto https if { ssl_fc }""" + """
|
|
||||||
server srv {0} check weight 1
|
|
||||||
""".format(hosts[k])
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
with open(sys.argv[1], 'r') as content_file:
|
|
||||||
parsed = yaml.load(content_file.read())
|
|
||||||
|
|
||||||
n = 0
|
|
||||||
|
|
||||||
print(defaults(parsed["customerrors"] if "customerrors" in parsed else False))
|
|
||||||
if "stats" in parsed:
|
|
||||||
print(stats(parsed["stats"]))
|
|
||||||
if "easymapping" in parsed:
|
|
||||||
for k in parsed["easymapping"]:
|
|
||||||
n = n + 1
|
|
||||||
print(easymapping(k, n))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo " ______ _ _ _____ "
|
|
||||||
echo "| ____| | | | | /\ | __ \ "
|
|
||||||
echo "| |__ __ _ ___ _ _ | |__| | / \ | |__) | __ _____ ___ _ "
|
|
||||||
echo "| __| / _\` / __| | | | | __ | / /\ \ | ___/ '__/ _ \ \/ / | | |"
|
|
||||||
echo "| |___| (_| \__ \ |_| | | | | |/ ____ \| | | | | (_) > <| |_| |"
|
|
||||||
echo "|______\__,_|___/\__, | |_| |_/_/ \_\_| |_| \___/_/\_\\__, |"
|
|
||||||
echo " __/ | __/ |"
|
|
||||||
echo " |___/ |___/ "
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
HAPROXY_CFG="/etc/haproxy/haproxy.cfg"
|
|
||||||
EASYCONFIG_CFG="/etc/easyconfig/easyconfig.cfg"
|
|
||||||
|
|
||||||
if [ ! -f "$EASYCONFIG_CFG" ]
|
|
||||||
then
|
|
||||||
echo "File '$EASYCONFIG_CFG' does not exist"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
python3 /entrypoint.py "$EASYCONFIG_CFG" > $HAPROXY_CFG
|
|
||||||
|
|
||||||
|
|
||||||
/sbin/syslogd -O /proc/1/fd/1
|
|
||||||
/docker-entrypoint.sh "$@"
|
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
pyyaml
|
pyyaml
|
||||||
|
docker
|
||||||
|
jinja2
|
||||||
16
static.py
Normal file
16
static.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import yaml
|
||||||
|
import sys
|
||||||
|
from easymapping import HaproxyConfigGenerator
|
||||||
|
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print("You need to pass the easyconfig.yml path")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
with open(sys.argv[1], 'r') as content_file:
|
||||||
|
parsed = yaml.load(content_file.read(), Loader=yaml.FullLoader)
|
||||||
|
|
||||||
|
cfg = HaproxyConfigGenerator(parsed)
|
||||||
|
print(cfg.generate())
|
||||||
|
|
||||||
|
exit(0)
|
||||||
74
swarm.py
Normal file
74
swarm.py
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
from easymapping import HaproxyConfigGenerator
|
||||||
|
|
||||||
|
# path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
with open("/tmp/.docker_data", 'r') as content_file:
|
||||||
|
lineList = content_file.readlines()
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"easymapping": [],
|
||||||
|
"customerrors": True if os.getenv("HAPROXY_CUSTOMERRORS") == "true" else False
|
||||||
|
}
|
||||||
|
easymapping = dict()
|
||||||
|
|
||||||
|
if os.getenv("HAPROXY_PASSWORD"):
|
||||||
|
result["stats"] = {
|
||||||
|
"username": os.getenv("HAPROXY_USERNAME") if os.getenv("HAPROXY_USERNAME") else "admin",
|
||||||
|
"password": os.getenv("HAPROXY_PASSWORD"),
|
||||||
|
"port": os.getenv("HAPROXY_STATS_PORT") if os.getenv("HAPROXY_STATS_PORT") else "1936",
|
||||||
|
}
|
||||||
|
|
||||||
|
for line in lineList:
|
||||||
|
line = line.strip()
|
||||||
|
i = line.find("=")
|
||||||
|
container = line[:i]
|
||||||
|
jsonStr = line[i+1:]
|
||||||
|
d = json.loads(jsonStr)
|
||||||
|
|
||||||
|
if "com.byjg.easyhaproxy.definitions" in d.keys():
|
||||||
|
definitions = d["com.byjg.easyhaproxy.definitions"].split(",")
|
||||||
|
|
||||||
|
for definition in definitions:
|
||||||
|
if "com.byjg.easyhaproxy.host." + definition not in d:
|
||||||
|
continue
|
||||||
|
|
||||||
|
port = d["com.byjg.easyhaproxy.port." + definition] if "com.byjg.easyhaproxy.port." + definition in d else "80"
|
||||||
|
hash = hashlib.md5(d["com.byjg.easyhaproxy.sslcert." + definition].encode('utf-8')).hexdigest() if "com.byjg.easyhaproxy.sslcert." + definition in d else ""
|
||||||
|
|
||||||
|
key = port+hash
|
||||||
|
|
||||||
|
if key not in easymapping:
|
||||||
|
easymapping[key] = {
|
||||||
|
"port": port,
|
||||||
|
"hosts": dict(),
|
||||||
|
"redirect": dict(),
|
||||||
|
# "ssl_cert": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
easymapping[key]["hosts"][d["com.byjg.easyhaproxy.host." + definition]] = container + ":" + (d["com.byjg.easyhaproxy.localport." + definition] if "com.byjg.easyhaproxy.localport." + definition in d else "80")
|
||||||
|
|
||||||
|
if "com.byjg.easyhaproxy.sslcert." + definition in d:
|
||||||
|
filename = '/etc/haproxy/certs/' + d["com.byjg.easyhaproxy.host." + definition] + "." + str(time.time()) + ".pem"
|
||||||
|
easymapping[key]["ssl_cert"] = filename
|
||||||
|
with open(filename, 'wb') as file:
|
||||||
|
file.write(base64.b64decode(d["com.byjg.easyhaproxy.sslcert." + definition]))
|
||||||
|
|
||||||
|
if "com.byjg.easyhaproxy.redirect." + definition in d:
|
||||||
|
redirect = d["com.byjg.easyhaproxy.redirect." + definition] if "com.byjg.easyhaproxy.redirect." + definition in d else ""
|
||||||
|
for r in redirect.split(","):
|
||||||
|
r_parts = r.split("--")
|
||||||
|
easymapping[key]["redirect"][r_parts[0]] = r_parts[1]
|
||||||
|
|
||||||
|
result["easymapping"] = easymapping.values()
|
||||||
|
|
||||||
|
|
||||||
|
cfg = HaproxyConfigGenerator(result)
|
||||||
|
print(cfg.generate())
|
||||||
|
|
||||||
|
# print(jsonStr)
|
||||||
|
|
||||||
|
|
||||||
68
templates/haproxy.cfg.j2
Normal file
68
templates/haproxy.cfg.j2
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
defaults
|
||||||
|
log global
|
||||||
|
|
||||||
|
timeout connect 3s
|
||||||
|
timeout client 10s
|
||||||
|
timeout server 10m
|
||||||
|
{% if data["customerrors"] %}
|
||||||
|
errorfile 400 /etc/haproxy/errors-custom/400.http
|
||||||
|
errorfile 403 /etc/haproxy/errors-custom/403.http
|
||||||
|
errorfile 408 /etc/haproxy/errors-custom/408.http
|
||||||
|
errorfile 500 /etc/haproxy/errors-custom/500.http
|
||||||
|
errorfile 502 /etc/haproxy/errors-custom/502.http
|
||||||
|
errorfile 503 /etc/haproxy/errors-custom/503.http
|
||||||
|
errorfile 504 /etc/haproxy/errors-custom/504.http
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
global
|
||||||
|
log /dev/log local0
|
||||||
|
maxconn 2000
|
||||||
|
tune.ssl.default-dh-param 2048
|
||||||
|
|
||||||
|
{% if "stats" in data %}
|
||||||
|
frontend stats
|
||||||
|
bind *:{{ data["stats"]["port"] | default(1936) }}
|
||||||
|
mode http
|
||||||
|
stats enable
|
||||||
|
stats hide-version
|
||||||
|
stats realm Haproxy\ Statistics
|
||||||
|
stats uri /
|
||||||
|
stats auth {{ data["stats"]["username"] }}:{{ data["stats"]["password"] }}
|
||||||
|
# acl is_proxystats hdr(host) -i some.host.com
|
||||||
|
# default_backend srv_stats
|
||||||
|
# use_backend srv_stats if is_proxystats
|
||||||
|
default_backend srv_stats
|
||||||
|
|
||||||
|
backend srv_stats
|
||||||
|
mode http
|
||||||
|
server Local 127.0.0.1:{{ data["stats"]["port"] | default(1936) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for o in data["easymapping"] %}
|
||||||
|
{% set salt = loop.index %}
|
||||||
|
frontend http_in_{{ o["port"] }}_{{ salt }}
|
||||||
|
bind *:{{ o["port"] }} {{ " ssl crt " + o["ssl_cert"] if "ssl_cert" in o else "" }}
|
||||||
|
mode http
|
||||||
|
|
||||||
|
{% for k in o["redirect"] -%}
|
||||||
|
redirect prefix {{ o["redirect"][k] }} code 301 if { hdr(host) -i {{ k }} }
|
||||||
|
{% endfor -%}
|
||||||
|
|
||||||
|
{% for k in o["hosts"] %}
|
||||||
|
{% set host = k.replace(".", "_") + "_{0}_{1}".format(o["port"], salt) %}
|
||||||
|
acl is_rule_{{ host }}_1 hdr(host) -i {{ k }}
|
||||||
|
acl is_rule_{{ host }}_2 hdr(host) -i {{ k }}:{{ o["port"] }}
|
||||||
|
use_backend srv_{{ host }} if is_rule_{{ host }}_1 OR is_rule_{{ host }}_2
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for k in o["hosts"] %}
|
||||||
|
{% set host = k.replace(".", "_") + "_{0}_{1}".format(o["port"], salt) %}
|
||||||
|
backend srv_{{ host }}
|
||||||
|
balance roundrobin
|
||||||
|
mode http
|
||||||
|
option forwardfor
|
||||||
|
http-request set-header X-Forwarded-Port %[dst_port]
|
||||||
|
http-request add-header X-Forwarded-Proto https if { ssl_fc }
|
||||||
|
server srv {{ o["hosts"][k] }} check weight 1
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue