Git Product home page Git Product logo

byjg / docker-easy-haproxy Goto Github PK

View Code? Open in Web Editor NEW
50.0 5.0 12.0 1.7 MB

Discover services and create dynamically the haproxy.cfg based on the labels defined in docker containers or from a simple static Yaml

License: MIT License

Dockerfile 0.55% Shell 2.33% Python 86.54% Makefile 0.26% Jinja 8.33% Smarty 2.00%
haproxy haproxy-configuration haproxy-docker ssl discovery-service dynamic-configuration

docker-easy-haproxy's People

Contributors

byjg avatar dtenenba avatar josegonzalez avatar ox0400 avatar till avatar zasdaym avatar zhipeng-stratifyd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

docker-easy-haproxy's Issues

Docker Swarm error: Service cannot be explicitly attached to the ingress network "ingress"

When using the default yml for Docker Swarm, I am getting the following error during start:

requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: http+docker://localhost/v1.41/services/9gffebl2qqovhlyf8nuit3kkb/update?version=9790
[...]
docker.errors.APIError: 400 Client Error for http+docker://localhost/v1.41/services/9gffebl2qqovhlyf8nuit3kkb/update?version=9790: Bad Request ("rpc error: code = InvalidArgument desc = Service cannot be explicitly attached to the ingress network "ingress"")
[...]
  File "/scripts/processor/__init__.py", line 176, in inspect_network
    service.update(networks = network_list)
  File "/scripts/processor/__init__.py", line 64, in refresh
    self.inspect_network()
    self.refresh()
  File "/scripts/processor/__init__.py", line 43, in __init__
  File "/scripts/main.py", line 7, in start
    processor_obj = ProcessorInterface.factory(os.getenv("EASYHAPROXY_DISCOVER"))
  File "/scripts/processor/__init__.py", line 52, in factory
    return Swarm()
  File "/scripts/processor/__init__.py", line 155, in __init__
    super().__init__()

My yml looks like this:

version: "3"

services:
  haproxy:
    image: byjg/easy-haproxy
    environment:
      EASYHAPROXY_DISCOVER: swarm
      EASYHAPROXY_SSL_MODE: "loose"
      HAPROXY_CUSTOMERRORS: "true"
      HAPROXY_USERNAME: admin
      HAPROXY_PASSWORD: password
      HAPROXY_STATS_PORT: 1936
    ports:
      - "80:80/tcp"
      - "443:443/tcp"
      - "1936:1936/tcp"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      replicas: 1
    networks:
      - proxy_public
networks:
  proxy_public:
    external: true

Enable HTTP/2 over HTTPS

bind *:{{ o["port"] }} ssl crt /certs/letsencrypt/ alpn http/1.1 crt /certs/haproxy/ alpn http/1.1

You can enable HTTP/2 to make better use of network resources and decrease perceived latency by enabling multiple concurrent requests/responses to be multiplexed over a single TCP connection. This can be done by replacing alpn http/1.1 with alpn h2,http/1.1.

What this new order does is it uses HTTP/2 if supported, otherwise switch back to HTTP/1.1.

Source: https://www.haproxy.com/documentation/hapee/latest/load-balancing/protocols/http-2/

Using labels to configure haproxy running on a different host

Is it possible to run easy-haproxy in docker to read container labels, but then actually configure haproxy on a different host? I have haproxy running on my opnsense router and I have multiple servers at home. At the moment I'm using traefik but if I could have a single instance of haproxy on the router and it would automatically update when I bring containers up on various hosts, that would be the ideal setup.
If it's not possible, what would be the steps needed to implement something like that?

Crash when a container with `network_mode: host` is running

Whenever a container is running in network host mode, docker-easy-haproxy refuses to start. Log is below:

Traceback (most recent call last):
  File "/usr/lib/python3.10/site-packages/docker/api/client.py", line 268, in _raise_for_status
    response.raise_for_status()
  File "/usr/lib/python3.10/site-packages/requests/models.py", line 960, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 400 Client Error: Bad Request for url: http+docker://localhost/v1.43/networks/ebf21d2da2ef49b72f40147ca4ea6180b5dde83622981664ae107d9491ed22e2/connect

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/scripts/main.py", line 73, in <module>
    main()
  File "/scripts/main.py", line 70, in main
    start()
  File "/scripts/main.py", line 7, in start
    processor_obj = ProcessorInterface.factory(os.getenv("EASYHAPROXY_DISCOVER"))
  File "/scripts/processor/__init__.py", line 50, in factory
    return Docker()
  File "/scripts/processor/__init__.py", line 128, in __init__
    super().__init__()
  File "/scripts/processor/__init__.py", line 43, in __init__
    self.refresh()
  File "/scripts/processor/__init__.py", line 64, in refresh
    self.inspect_network()
  File "/scripts/processor/__init__.py", line 145, in inspect_network
    ha_proxy_network.connect(container.name)
  File "/usr/lib/python3.10/site-packages/docker/models/networks.py", line 58, in connect
    return self.client.api.connect_container_to_network(
  File "/usr/lib/python3.10/site-packages/docker/utils/decorators.py", line 19, in wrapped
    return f(self, resource_id, *args, **kwargs)
  File "/usr/lib/python3.10/site-packages/docker/api/network.py", line 254, in connect_container_to_network
    self._raise_for_status(res)
  File "/usr/lib/python3.10/site-packages/docker/api/client.py", line 270, in _raise_for_status
    raise create_api_error_from_http_exception(e) from e
  File "/usr/lib/python3.10/site-packages/docker/errors.py", line 39, in create_api_error_from_http_exception
    raise cls(e, response=response, explanation=explanation) from e
docker.errors.APIError: 400 Client Error for http+docker://localhost/v1.43/networks/ebf21d2da2ef49b72f40147ca4ea6180b5dde83622981664ae107d9491ed22e2/connect: Bad Request ("container sharing network namespace with another container or host cannot be connected to any other network")

Maybe the host network containers can be excluded, or at least this error be ignored?

RFC: ACL

I have another proposal, so here it comes... If you have time, I'd like your input. I'll build everything, etc..

Proposal

I want a basic service firewall so I can stop dealing with iptables/firewalld on the docker host system.

So, my "proposal" is: I wanted a way to set ACL on frontends, via service/container labels.

Example 1

A simple ACL to ensure a service can be accessed only from 1.1.1.1:

labels:
 - com.byjg.easyhaproxy.definitions=service
 - com.byjg.easyhaproxy.mode.service=tcp
 - com.byjg.easyhaproxy.port.service=443
 - com.byjg.easyhaproxy.host.service=dns.service.name
 # ACL here
 - com.byjg.easyhaproxy.acl-name.0.service=service-fw
 - com.byjg.easyhaproxy.acl-value.0.service="src 1.1.1.1"

It would render the following config block:

frontend service_in_443_1
    bind *:443
    mode tcp
    acl service-fw src 1.1.1.1
    tcp-request connection reject if !service-fw

    default_backend my_backend_server

Do you think this is over-complicating it?

Example 2

Here is another example — I have an internal API server, I want to ensure that requests originate from 10.0.1.0/24 or 10.0.2.2 and each request must start with /api/v2:

labels:
 - com.byjg.easyhaproxy.definitions=web
 - com.byjg.easyhaproxy.port.web=80
 - com.byjg.easyhaproxy.host.web=dns
 # ACL here
 - com.byjg.easyhaproxy.acl-name.0.web=service-fw
 - com.byjg.easyhaproxy.acl-value.0.web="src 10.0.1.0/24 10.0.2.2"
 - com.byjg.easyhaproxy.acl-name.1.web=service-fw
 - com.byjg.easyhaproxy.acl-value.1.web="path_beg -i /api/v2"

It would render to:

frontend http_in_80_1
    bind *:80
    mode http

    # same ACL name, I think combines them — both must be true
    acl service-fw src 10.0.1.0/24 10.0.2.2
    acl service-fw path_beg -i /api/v2
    http-request deny if !service-sw

    acl is_rule_dns_1_1 hdr(host) -i dns
    acl is_rule_dns_1_2 hdr(host) -i dns:80
    use_backend srv_dns_80_1 if is_rule_dns_1_1 OR is_rule_dns_1_2 

Different acl-names would render another http-request deny if statement.

Thoughts?

CI?

I refactored the code and added some tests. Would you enable CI for this repo? I don't know if you have any preference. :)

How do we handle multiple domains?

Some of the other proxy plugins I have in Dokku use comma-separated lists. Is that the method here, or should we have 1 definition per domain?

question about redirects

Hi, this is not an issue but a question -

If a request comes in for a host (say foo.com) on port 80 I want to redirect it to port 443, so I want to redirect

http://foo.com
to
https://foo.com

How can I do this with easy-haproxy? I will be using a stack and so setting up my labels in my yml file.
Thanks for this nice tool!

Which networks are scanned?

The documentation doesn't say which labels get scanned for containers. Is it possible to specify a network? For instance, I'd want to run this in the bridge network as that is where all Dokku apps would otherwise be.

Use container ip instead of container name when using the default bridge network

After a bit of testing, it looks like if the containers aren't both in a custom network, they can't route to each other because the routing happens via the assumed network alias.

For the default bridge network:

If you run the same application stack on the default bridge network, you need to manually create links between the containers (using the legacy --link flag). These links need to be created in both directions, so you can see this gets complex with more than two containers which need to communicate. Alternatively, you can manipulate the /etc/hosts files within the containers, but this creates problems that are difficult to debug.

I think in this case, we want the internal ip address if the container is on the bridge network. Maybe we can have a "shadow" label that gets injected into the labels here and then pick it off later when we set the upstreams here?

[FEATURE REQUEST] Custom defaults, frontend and backend configuration

I use your setup for docker containers. Looks respectable but since I rely on haproxy for my other projects I'd like to have my "own" common logging setup. Currently, there's no way to do it.

My request is to add extra configuration for:

  • options,
  • defaults,
  • frontend,
  • backend.,

Here's my proposal for addind extra configuration to those configuration sections.

      easyhaproxy.wiki.host: wiki.example.org,wiki.example.org:1080
      easyhaproxy.wiki.options.extra: |
        stats socket /sockets/hadmin.sock mode 660 level admin
        nbthread 16
      easyhaproxy.wiki.defaults.extra: |
        timeout connect 5s
        timeout client  50s
        timeout server  5s
        maxconn 1000000
        timeout client-fin 5s
        unique-id-format %{+X}o\ %cp-%ci-%Ts-%H-%rt-%ms
        unique-id-header X-Unique-ID
        log-format "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r %ID"
      easyhaproxy.wiki.frontend.extra: |
        acl wirki_blacklist src 10.20.30.0/24
        tcp-request connection reject if !wiki_blacklist
      easyhaproxy.wiki.backend.extra: |
        acl wiki_burstable src 192.168.0.0/16
        acl wiki_burstable src 10.0.0.0/8
        acl wiki_burstable src 172.16.0.0/12
        http-request track-sc0 src
        http-request deny deny_status 429 if { sc_http_req_rate(0) gt 33 } !wiki_burstable
        http-response add-header Connection close

Create Plugins for EasyHAProxy

Description

Create the possibility to attach plugins to EasyHAProxy processing.

How it works?

Every time a new site is discovery the plugin will be invoked and process some feature.

There are two types of plugin and it is configured during the plugin creation

  • Global: It will be invoked only one time
  • Per domain: it will be invoked for every domain.

Some plugin examples

Add ability to customize the label prefix

Hi! I'm looking at ways to add new proxy implementations for Dokku, and this repo is in-line with what I'm doing around Caddy and Traefik.

One thing I'd like to do is change the label prefix to haproxy instead of easyhaproxy. I believe this will make it easier for developers to integrate with (since they'll know what haproxy is).

Would it be possible to expose an environment variable for customizing the label prefix?

No configuration generated when using docker discovery

Hi, I'm trying to use the standalone docker discovery but can't make it work.

root@z-srv-1:~# docker inspect haproxy | jq '.[].Config.Env'
[
  "EASYHAPROXY_DISCOVER=docker",
  "EASYHAPROXY_LOG_LEVEL=DEBUG",
  "HAPROXY_LOG_LEVEL=ERROR",
  "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  "RELEASE_VERSION=\"4.4.0\"",
  "TZ=Etc/UTC"
]

root@z-srv-1:~# docker inspect haproxy | jq '.[].Mounts'
[
  {
    "Type": "bind",
    "Source": "/var/run/docker.sock",
    "Destination": "/var/run/docker.sock",
    "Mode": "ro",
    "RW": false,
    "Propagation": "rprivate"
  }
]

root@z-srv-1:~# docker inspect grafana | jq '.[].Config.Labels'
{
  "easyhaproxy.grafana.host": "grafana.zasdaym.my.id",
  "easyhaproxy.grafana.localport": "3000",
  "maintainer": "Grafana Labs <[email protected]>"
}

root@z-srv-1:~# docker exec haproxy cat /etc/haproxy/haproxy.cfg
global
    log stdout  format raw  local0  err
    maxconn 2000
    tune.ssl.default-dh-param 2048

    # intermediate configuration
    ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets

    ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
    ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets

    ssl-dh-param-file /etc/haproxy/dhparam

defaults
    log global
    option httplog

    timeout connect    3s
    timeout client    10s
    timeout server    10m

frontend stats
    bind *:1936
    mode http
    stats enable
    stats hide-version
    stats realm Haproxy\ Statistics
    stats uri /
    default_backend srv_stats

backend srv_stats
    mode http
    server Local 127.0.0.1:1936

backend certbot_backend
    mode http
    server certbot 127.0.0.1:2080

Certbot Continues Requests Despite Hitting Let's Encrypt Certificate Rate Limit


Screenshot 2023-06-29 at 8 16 53 AM

Upon hitting the Let's Encrypt quota of issuing 5 certificates for the exact set of domains within a span of 168 hours, the Certbot process is rendered unable to generate any new certificate orders until a specific timeframe set by Let's Encrypt. Nevertheless, Certbot persists in transmitting requests, leading to an increase in unnecessary network traffic and potentially causing strain on system resources. This condition warrants optimization to prevent Certbot from making redundant certificate requests once the rate limit is reached, thereby enhancing overall system performance and stability.


Comprehensive Strategies to Address Let's Encrypt Rate Limit Issue in Certbot:

  1. Rate Limit Check: Implement a check within Certbot to ascertain if the rate limit has been reached before attempting to request a new certificate.

  2. Backoff and Retry Logic: Introduce an exponential backoff and retry logic to Certbot, reducing the frequency of requests to Let's Encrypt when the rate limit has been reached.

  3. Configurable Rate Limit Alerts: Add a feature in Certbot that alerts the administrator when the rate limit is close to being reached for proactive manual intervention.

  4. Rate Limit Documentation: Improve the project's documentation to clearly explain Let's Encrypt's rate limits, helping users understand and potentially adjust their certificate issuance strategies.

  5. Adjust Certificate Request Strategy: Consider adjusting the certificate request strategy to prevent hitting the rate limit by grouping multiple domains under fewer certificates or adjusting the timing of certificate requests.

  6. Next Eligible Time Retry or Notification: Implement a feature to notify the admin when the next eligible time for certificate issuance arrives, or program Certbot to automatically attempt a new request at this given time.

  7. Switching Certificate Provider (Let's Encrypt vs ZeroSSL): Consider switching to ZeroSSL, which offers unlimited certificates without rate limits, and provides a user-friendly web interface for certificate management.

  8. Automated Certificate Provider Switch: Implement an automated solution where Let's Encrypt is the primary choice, and the system automatically switches to ZeroSSL when the rate limit is reached on Let's Encrypt.

These solutions aim to mitigate the issue of hitting rate limits, enhance system performance and stability, and provide flexibility in handling SSL certificates. As always, any changes should be thoroughly tested to ensure they do not introduce new issues or conflicts with existing functionality.

Add letsencrypt integration

It would be cool to have automatic letsencrypt support for this codebase. While haproxy doesn't support it directly (yet? ever?), I found this repo from the official Github account, documented here on their blog.

Might be a cool addition to add somehow - maybe something like easyproxy.letsencrypt=true? The config file could be templated based on env vars for the easyhaproxy container :)

Add a way to configure log-level

It would be great to somehow be able to configure the log-level of haproxy, as well as where app request logs go (assuming we can write them to a file per backend or something).

Certbot not working: connection refused

Trying to enable certbot, but I get this error (on LetsEncrypt, but ZeroSSL has the same issue):

[CERTBOT] 08/15/23 14:09:07 [DEBUG]: [not_found] Request new certificate for -DOMAIN-
[CERTBOT] 08/15/23 14:09:08 [INFO]: Account registered.
[CERTBOT] 08/15/23 14:09:08 [WARN]: Saving debug log to /var/log/letsencrypt/letsencrypt.log
[CERTBOT] 08/15/23 14:09:08 [INFO]: Requesting a certificate for -DOMAIN-
[CERTBOT] 08/15/23 14:09:10 [WARN]: Some challenges have failed.
[CERTBOT] 08/15/23 14:09:10 [WARN]: Ask for help or search for solutions at https://community.letsencrypt.org. See the logfile /var/log/letsencrypt/letsencrypt.log or re-run Certbot with -v for more details.
[CERTBOT] 08/15/23 14:09:10 [INFO]: Certbot failed to authenticate some domains (authenticator: standalone). The Certificate Authority reported these problems:
[CERTBOT] 08/15/23 14:09:10 [INFO]:   Domain: -DOMAIN-
[CERTBOT] 08/15/23 14:09:10 [INFO]:   Type:   connection
[CERTBOT] 08/15/23 14:09:10 [INFO]:   Detail: 49.13.73.162: Fetching http://-DOMAIN-/.well-known/acme-challenge/fXpTY0iMRtl5GuLhg07-uBv75L9NTJrCSDUfJr82zL8: Connection refused
[CERTBOT] 08/15/23 14:09:10 [INFO]:
[CERTBOT] 08/15/23 14:09:10 [INFO]: Hint: The Certificate Authority failed to download the challenge files from the temporary standalone webserver started by Certbot on port 2080. Ensure that the listed domains point to this machine and that it can accept inbound connections from the internet.
[CERTBOT] 08/15/23 14:09:10 [INFO]:
[CERTBOT] 08/15/23 14:09:10 [DEBUG]: Freeze issuing ssl for -DOMAIN- due failure. The certificate is not_found

(domain redacted to -DOMAIN-)

This is when running :master, :latest does not seem to spawn port 443 at all.

HTTP/3

Now that haproxy supports "HTTP/3" with a docker image, perhaps support could be added to this project? Only thing holding me back from using it at the moment.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.