Skip to content

Commit

Permalink
Merge pull request #1 from byjg/2.0
Browse files Browse the repository at this point in the history
Version 2.0
  • Loading branch information
byjg authored Jul 24, 2019
2 parents 7f6f30b + 57f3363 commit d01cbd5
Show file tree
Hide file tree
Showing 25 changed files with 480 additions and 195 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.idea
*~
venv
venv
.docker_data
__pycache__
22 changes: 15 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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.* /
COPY conf.d /etc/haproxy/conf.d
COPY assets/errors-custom/* /etc/haproxy/errors-custom/
RUN apk add --no-cache bash python3 py-yaml supervisor docker

ENTRYPOINT [ "/entrypoint.sh" ]
CMD ["haproxy", "-f", "/etc/haproxy/haproxy.cfg"]
COPY requirements.txt /scripts
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" ]
203 changes: 156 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,141 @@
# 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

- Enable or disable Stats on port 1936 with custom password
- Simple mapping host => host:port
- Simple redirect host => host:port
- Discover and setup haproxy from Docker Tag
- Discover and setup haproxy redirect from Docker Tag
- Setup HAProxy CFG from a Yaml file.


# 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
stats:
Expand All @@ -30,7 +154,7 @@ easymapping:
www.host1.com.br: http://host1.com.br

- port: 443
ssl_cert: /etc/easyconfig/mycert.pem
ssl_cert: BASE64_PEM_CERTIFICATE
hosts:
host1.com.br: container:80

Expand All @@ -39,24 +163,15 @@ easymapping:
host3.com.br: domain:8181
```
Then run (remember to enable the proper ports):
Running:
```bash
docker run \
-v /path/to/local:/etc/easyconfig \
-p 80:80 \
-p 8080:8080 \
-p 1936:1936 \
--name easy-haproxy-instance \
-d byjg/easy-haproxy
docker run -v /my/config.yml:/etc/haproxy/easyconfig.yml .... byjg/easyhaproxy
```



# Mapping custom .cfg files

Just create a folder and put files with the extension .cfg. and map the volume to the container.
This will concatenate your config into the main haproxy.cfg
Map a folder containing valid HAProxy `.cfg` files to `/etc/haproxy/conf.d`. It will be concatenated to your HAProxy CFG.

```bash
docker run \
Expand All @@ -65,52 +180,46 @@ docker run \
-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
```
# Handling SSL

# Docker Compose
You can attach a valid SSL certificate to the request.

```yaml
version: "3.4"
services:
front:
image: byjg/easy-haproxy
volume:
- /path/to/local:/etc/easyconfig
ports:
- 80:80
- 8080:8080
- 1936:1936
```
1. First Create a single PEM file including CA.

# Handling SSL
```bash
cat example.com.crt example.com.key > single.pem

HaProxy can handle SSL for you. in this case add the parameter pointing to file containing
the pem of certificates and key in only one file:
cat single.pem

-----BEGIN CERTIFICATE-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5ZheHqmBnEJP+
U9r1gxYWKLzdqrMrcxtQN6M1hIH9n0peuJeIrybdcV7sMbStMXI=
-----END CERTIFICATE-----

-----BEGIN PRIVATE KEY-----
MIIEojCCA4qgAwIBAgIUegW2BimwuL4RzRZ2WYkHA6U5nkAwDQYJKoZIhvcNAQEL
3j4wz8/I5fdsk090j4s5KA==
-----END PRIVATE KEY-----
```
- port: 443
ssl_cert: /etc/easyconfig/mycert.pem
hosts:
host1.com.br: container:80

2. Convert it to BASE64 in a single line:

```bash
cat single.pem | base64 -w0
```

Important: Different certificates need to be handled in different entries.
3. Use this string to define the label `com.byjg.easyhaproxy.sslcert.[definition]`

# Setting Custom Errors

Map the volume : `/etc/haproxy/errors-custom/` and put a file named `ERROR_NUMBER.http` where ERROR_NUMBER
is the http error code (e.g. 503.http)
If enabled, map the volume : `/etc/haproxy/errors-custom/` to your container and put a file named `ERROR_NUMBER.http`
where ERROR_NUMBER is the http error code (e.g. 503.http)

# Build

```
docker build -t byjg/easy-haproxy .
```


9 changes: 9 additions & 0 deletions assets/etc/crontabs/root
Original file line number Diff line number Diff line change
@@ -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 changes: 3 additions & 0 deletions assets/etc/haproxy/certs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Certs Folder

Docker Easy HAProxy will save the SSL Certs here.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
45 changes: 45 additions & 0 deletions assets/etc/supervisord.conf
Original file line number Diff line number Diff line change
@@ -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 changes: 18 additions & 0 deletions assets/scripts/exit-event-listener.py
Original file line number Diff line number Diff line change
@@ -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()
Loading

0 comments on commit d01cbd5

Please sign in to comment.