Skip to content

Commit

Permalink
Merge pull request #7 from Ingensi/develop
Browse files Browse the repository at this point in the history
Release of version 2.1.0
  • Loading branch information
HipsterWhale committed Jan 16, 2016
2 parents e03976f + 2eb1425 commit 9178289
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 110 deletions.
24 changes: 11 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
![SuperWhale Logo](https://raw.githubusercontent.com/Ingensi/superwhale/develop/doc/superwhale.png)

*Logo by Camille BRIZARD*

### Superwhale
*This project is licensed under the terms of the MIT license.*

**Last version : 2.1.0**

#### Aim
Docker introduced Docker Networks with version [1.9](https://github.com/docker/docker/blob/master/CHANGELOG.md#190-2015-11-03). With that update you can now have multiple web servers linked to the same network and a reverse proxy in front of them all without having to manage links manually. First, I was using HAProxy to do this role but HAProxy wasn't flexible enough : I was facing an issue, sometimes my containers weren't running while I was starting up my reverse and because HAProxy force name resolving at startup it was crashing almost every time. So I decided to create a system that add a smart layer upon HAProxy using a simple ruby script.

#### How it works
It uses alpine as a base distribution to provide a lightweight image. It includes ruby (with 1 gem : [FileWatcher](https://github.com/thomasfl/filewatcher)) and [HAProxy](http://www.haproxy.org/), that's all. At startup, SuperWhale will launch `/bin/superwhale` : the main process of the container.

`superwhale` will search for services inside the `/etc/superwhale.d` folder, create HAProxies configurations and then start HAProxies. It will also watch for file modifications inside `/etc/hosts` or `/etc/superwhale.d` and will gracefully reload HAProxies if there is any.
`superwhale` will search for services inside the `/etc/superwhale.d` folder, create HAProxy configuration and then start HAProxy. It will also watch for file modifications inside `/etc/hosts` or `/etc/superwhale.d` and will semi-gracefully reload HAProxy if there is any.

#### How to use it

Expand All @@ -30,7 +33,7 @@ git:
- http-request set-header Host git.mydomain.tld
```

Will output this inside HAProxies configuration ONLY if a `git_container` is present on the relevant `docker network` :
Will output this inside HAProxy configuration ONLY if a `git_container` is present on the relevant `docker network` :

```
[...]
Expand Down Expand Up @@ -78,16 +81,11 @@ force_ssl: false
# Change the log level : debug, info (default) and warning
log_level: info
# Uncomment this to add some directive to the dispatcher frontend declaration :
#dispatcher_frontend:
# - myoption true
# - [...]
```

##### Gracefull reload
##### Semi-gracefull reload

When you modify services if we restart HAProxy on-the-fly it will interrupt all active connections, breaking current downloads, streamings etc... To avoid this, there is not one HAProxy, but three. There is one in the front named `dispatcher`, and 2 behinds : `master` and `slave`. When configuration is changed, slave is restarted, then master is. Using the capability of HAProxy to exit the process only when all connections are closed, there is no lost of connections.
When reloading, Superwhale will do a soft-stop before restarting the process. There is a little time at startup when connections will be refused. That's why I called it "Semi-gracefull" reload.

Here is what HAProxy documentation says about soft-stop :
```
Expand All @@ -104,7 +102,7 @@ is failing, while still doing the job during the time it needs to detect it.

##### Setting `haproxy.cfg` header

You can modify `header.cfg` or `dispatcher_header.cfg` (depending with instance you want to tune) file in `/etc/superwhale.d`.
You can modify the `header.cfg` file in `/etc/superwhale.d`.

##### Using HTTPS

Expand All @@ -126,15 +124,15 @@ While launching the container, use the `-v` argument :
```
docker run -d \
-v /mnt/volumes/superwhale/:/etc/superwhale.d \
-p 80:80 -p 443:443 --net=dockernet bahaika/whale-haproxy
-p 80:80 -p 443:443 --net=dockernet ingensi/superwhale
```

###### With inheritance

Create a `Dockerfile` and inherits from `bahaika/whale-haproxy` :
Create a `Dockerfile` and inherits from `ingensi/superwhale` :

```
FROM bahaika/whale-haproxy:latest
FROM ingensi/superwhale:latest
COPY ./service1.yml /etc/superwhale.d/service1.yml
```
Expand Down
116 changes: 30 additions & 86 deletions bin/superwhale
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,19 @@ PATHS = {

# SuperWhale constants
SUPERWHALE = {
version: '2.0.0',
version: '2.1.0',
config: YAML.load_file("#{PATHS[:superwhale]}/configs/superwhale.yml"),
services: "#{PATHS[:superwhale]}/services/*.yml",
https_cert: "#{PATHS[:superwhale]}/configs/https.pem",
failed_services: []
}

# HAProxies constants
# HAProxy constants
HAPROXY = {
binary: '/usr/sbin/haproxy',
debug_flag: '-d',
proxies_header: "#{PATHS[:superwhale]}/configs/header.cfg",
dispatcher: {
header: "#{PATHS[:superwhale]}/configs/dispatcher_header.cfg",
config: "#{PATHS[:haproxy]}/haproxy_dispatcher.cfg"
},
master: {
config: "#{PATHS[:haproxy]}/haproxy_master.cfg",
port: 10080
},
slave: {
config: "#{PATHS[:haproxy]}/haproxy_slave.cfg",
port: 11080
}
header: "#{PATHS[:superwhale]}/configs/header.cfg",
config: "#{PATHS[:haproxy]}/haproxy.cfg"
}

# Logging helper
Expand All @@ -59,14 +48,14 @@ def hosts_exists?(hostname)
hosts.include? "\t#{hostname}\n"
end

def compile_haproxy_config(frontend_directives, backend_blocks, instance_port)
def compile_haproxy_config(frontend_directives, backend_blocks)
# Loading header of haproxy configuration file
haproxy_cfg = File.read HAPROXY[:proxies_header]
haproxy_cfg = File.read HAPROXY[:header]

# Compiling frontend block
haproxy_cfg << "\n\nfrontend public\n"
frontend_directives.each do |directive|
haproxy_cfg << " #{directive}\n".gsub('%INSTANCE_PORT%', instance_port.to_s)
haproxy_cfg << " #{directive}\n"
end

# Compiling backend blocks
Expand All @@ -80,18 +69,19 @@ def compile_haproxy_config(frontend_directives, backend_blocks, instance_port)
haproxy_cfg
end

def create_dispatcher_config
# Dispatcher defaults
dispatcher_config = File.read HAPROXY[:dispatcher][:header]

# Dispatcher frontend
dispatcher_config << "frontend public\n"
dispatcher_config << " bind *:80\n"
# Create HAProxy configuration from superwhale services
def create_haproxy_config

LOGGER.info 'Parsing configurations'

# Frontend block
frontend_directives = []
frontend_directives << 'bind *:80'
# Frontend HTTPS management
if File.exist? SUPERWHALE[:https_cert]
LOGGER.info 'Certificate found, activating SSL'
dispatcher_config << " bind *:443 ssl crt #{SUPERWHALE[:https_cert]}\n"
frontend_directives << "bind *:443 ssl crt #{SUPERWHALE[:https_cert]}"

if SUPERWHALE[:config]['force_ssl']

Expand All @@ -103,36 +93,9 @@ def create_dispatcher_config
LOGGER.info "Excluding #{domain} from HTTPS forcing"
end if SUPERWHALE[:config].has_key? 'ssl_noforce_domain'

dispatcher_config << " redirect scheme https if !{ ssl_fc } #{https_domain_exclusion}\n"
frontend_directives << "redirect scheme https if !{ ssl_fc } #{https_domain_exclusion}"
end


SUPERWHALE[:config]['dispatcher_frontend'].each do |directive|
dispatcher_config << " #{directive}\n"
end if SUPERWHALE[:config].has_key? 'dispatcher_frontend'
else
LOGGER.warn 'No certificate found, disabling SSL capabilities'
end
dispatcher_config << " default_backend dispatched\n\n"

# Dispatcher backend
dispatcher_config << "backend dispatched\n"
dispatcher_config << " server master 127.0.0.1:#{HAPROXY[:master][:port]} check\n"
dispatcher_config << " server slave 127.0.0.1:#{HAPROXY[:slave][:port]} check backup\n"

LOGGER.debug "Dispatcher configuration:\n\n#{dispatcher_config}"

File.write HAPROXY[:dispatcher][:config], dispatcher_config
end

# Create HAProxies configuration from superwhale services
def create_haproxies_config

LOGGER.info 'Parsing configurations'

# Frontend block
frontend_directives = []
frontend_directives << 'bind *:%INSTANCE_PORT%'

# Backend blocks
backend_blocks = {}
Expand Down Expand Up @@ -213,14 +176,10 @@ def create_haproxies_config
end

# Generating and writing final configuration files, for master...
master_config = compile_haproxy_config frontend_directives, backend_blocks, HAPROXY[:master][:port]
File.write HAPROXY[:master][:config], master_config
# ...and for slave
slave_config = compile_haproxy_config frontend_directives, backend_blocks, HAPROXY[:slave][:port]
File.write HAPROXY[:slave][:config], slave_config
haproxy_config = compile_haproxy_config frontend_directives, backend_blocks
File.write HAPROXY[:config], haproxy_config

LOGGER.debug "Master HAProxy configuration file generated :\n\n#{master_config}\n"
LOGGER.debug "Slave HAProxy configuration file generated :\n\n#{slave_config}\n"
LOGGER.debug "Master HAProxy configuration file generated :\n\n#{haproxy_config}\n"

LOGGER.info 'HAProxy configuration file created'
end
Expand All @@ -239,46 +198,31 @@ end
def main
LOGGER.warn "Starting up SuperWhale, version #{SUPERWHALE[:version]} !"

# Creating haproxy configuration file including dispatcher config
create_dispatcher_config
create_haproxies_config
# Creating haproxy configuration file
create_haproxy_config

# Starting master HAProxy process...
haproxy_master_pid = start_haproxy HAPROXY[:master][:config]
haproxy_slave_pid = start_haproxy HAPROXY[:slave][:config]
haproxy_pid = start_haproxy HAPROXY[:config]

# Wait 2 second for haproxies to start up
# Wait 2 second for HAProxy to start up
sleep 2

# Starting dispatcher HAProxy process...
start_haproxy HAPROXY[:dispatcher][:config]

# Watching for file changes, (blocking)
FileWatcher.new([PATHS[:hosts], SUPERWHALE[:services]]).watch do
LOGGER.warn 'File modification detected !'

# Recreating haproxy configuration file without dispatcher config
create_haproxies_config

# Killing slave
LOGGER.info 'Killing slave'
Process.kill 'USR1', haproxy_slave_pid
Process.wait haproxy_slave_pid
LOGGER.info 'Killing slave done'

# Restarting slave with new configuration
LOGGER.info 'Restarting slave'
haproxy_slave_pid = start_haproxy HAPROXY[:slave][:config]
# Creating haproxy configuration file
create_haproxy_config

# Killing master
LOGGER.info 'Killing master'
Process.kill 'USR1', haproxy_master_pid
Process.wait haproxy_master_pid
LOGGER.info 'Killing master done'
LOGGER.info 'Killing haproxy'
Process.kill 'USR1', haproxy_pid
Process.wait haproxy_pid
LOGGER.info 'Killing haproxy done'

# Restarting master with new configuration
LOGGER.info 'Restarting master'
haproxy_master_pid = start_haproxy HAPROXY[:master][:config]
haproxy_pid = start_haproxy HAPROXY[:config]
end
end

Expand Down
11 changes: 0 additions & 11 deletions lib/dispatcher_header.cfg

This file was deleted.

3 changes: 3 additions & 0 deletions lib/header.cfg
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
global
tune.ssl.default-dh-param 2048

defaults
maxconn 4096
log global
Expand Down

0 comments on commit 9178289

Please sign in to comment.