1
0
Fork 0

Merge pull request #28 from byjg/kubernetes

Kubernetes
This commit is contained in:
Joao M 2022-08-31 23:42:57 -05:00 committed by GitHub
commit af1fb73c13
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
116 changed files with 3339 additions and 815 deletions

4
.dockerignore Normal file
View file

@ -0,0 +1,4 @@
__pycache__
.pytest_cache
*.pyc
.*

View file

@ -6,6 +6,6 @@ updates:
schedule: schedule:
interval: "daily" interval: "daily"
- package-ecosystem: "pip" - package-ecosystem: "pip"
directory: "/" directory: "/src"
schedule: schedule:
interval: "daily" interval: "daily"

View file

@ -26,9 +26,14 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v2
- run: | - name: Install requirements
run: |
cd src/ cd src/
pip install -r requirements.txt pip install -r requirements.txt
- name: Run tests
run: |
cd src/
pytest -s tests/ -vv pytest -s tests/ -vv
Build: Build:
@ -87,7 +92,7 @@ jobs:
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: . context: .
file: Dockerfile file: build/Dockerfile
build-args: | build-args: |
RELEASE_VERSION_ARG="${{ join(steps.tags.outputs.result, ',') }}" RELEASE_VERSION_ARG="${{ join(steps.tags.outputs.result, ',') }}"
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
@ -102,6 +107,16 @@ jobs:
# chmod +x $HOME/.docker/cli-plugins/docker-pushrm # chmod +x $HOME/.docker/cli-plugins/docker-pushrm
# docker pushrm ${{ env.IMAGE_NAME }} # docker pushrm ${{ env.IMAGE_NAME }}
Helm:
runs-on: 'ubuntu-latest'
needs: Build
if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')
env:
DOC_GITHUB_TOKEN: '${{ secrets.DOC_TOKEN }}'
steps:
- uses: actions/checkout@v2
- run: curl https://opensource.byjg.com/add-helm.sh | bash /dev/stdin helm easyhaproxy
Documentation: Documentation:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
needs: Build needs: Build
@ -110,4 +125,4 @@ jobs:
DOC_GITHUB_TOKEN: '${{ secrets.DOC_TOKEN }}' DOC_GITHUB_TOKEN: '${{ secrets.DOC_TOKEN }}'
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: curl https://opensource.byjg.com/add-doc.sh | bash /dev/stdin devops docker-easy-haproxy - run: curl https://opensource.byjg.com/add-doc.sh | bash /dev/stdin devops docker-easy-haproxy docs

3
.gitignore vendored
View file

@ -4,4 +4,5 @@ venv
.docker_data .docker_data
__pycache__ __pycache__
.pytest_cache .pytest_cache
*.pyc *.pyc
.env

View file

@ -6,4 +6,4 @@ tasks:
- command: | - command: |
virtualenv -p /usr/bin/python3 venv virtualenv -p /usr/bin/python3 venv
source venv/bin/activate source venv/bin/activate
pip install -r requirements.txt pip install -r src/requirements.txt

View file

@ -1,26 +0,0 @@
language: python
services:
- docker
jobs:
include:
- stage: test
if: (type IN (pull_request))
install:
- pip install -r requirements.txt
script:
- pytest -s tests/
- stage: build docker
if: (branch = master) AND (NOT (type IN (pull_request)))
install:
- docker pull byjg/k8s-ci
script:
- docker run --privileged -v /tmp/z:/var/lib/containers -it --rm -v $PWD:/work -w /work -e DOCKER_USERNAME=$DOCKER_USERNAME -e DOCKER_PASSWORD=$DOCKER_PASSWORD -e DOCKER_REGISTRY=$DOCKER_REGISTRY byjg/k8s-ci /work/build-multiarch.sh
- stage: documentation
if: (branch = master) AND (NOT (type IN (pull_request)))
install: skip
script: "curl https://opensource.byjg.com/add-doc.sh | bash /dev/stdin devops docker-easy-haproxy"

4
.vscode/launch.json vendored
View file

@ -10,9 +10,9 @@
"request": "launch", "request": "launch",
"program": "${file}", "program": "${file}",
"console": "integratedTerminal", "console": "integratedTerminal",
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}/src",
"env": { "env": {
"PYTHONPATH": "${cwd}" "PYTHONPATH": "${cwd}/src"
} }
} }
] ]

View file

@ -2,7 +2,7 @@ VERSION := $(shell git rev-parse --short HEAD)
.PHONY: build .PHONY: build
build: build:
docker build -t byjg/easy-haproxy --build-arg RELEASE_VERSION_ARG="$(VERSION)" -t byjg/easy-haproxy:local . docker build -t byjg/easy-haproxy --build-arg RELEASE_VERSION_ARG="$(VERSION)" -t byjg/easy-haproxy:local -f build/Dockerfile .
.PHONY: test .PHONY: test
test: test:

378
README.md
View file

@ -1,4 +1,4 @@
# Easy HAProxy # EasyHAProxy
[![Opensource ByJG](https://img.shields.io/badge/opensource-byjg-success.svg)](http://opensource.byjg.com) [![Opensource ByJG](https://img.shields.io/badge/opensource-byjg-success.svg)](http://opensource.byjg.com)
[![Build Status](https://github.com/byjg/docker-easy-haproxy/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/byjg/docker-easy-haproxy/actions/workflows/build.yml) [![Build Status](https://github.com/byjg/docker-easy-haproxy/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/byjg/docker-easy-haproxy/actions/workflows/build.yml)
@ -6,378 +6,48 @@
[![GitHub license](https://img.shields.io/github/license/byjg/docker-easy-haproxy.svg)](https://opensource.byjg.com/opensource/licensing.html) [![GitHub license](https://img.shields.io/github/license/byjg/docker-easy-haproxy.svg)](https://opensource.byjg.com/opensource/licensing.html)
[![GitHub release](https://img.shields.io/github/release/byjg/docker-easy-haproxy.svg)](https://github.com/byjg/docker-easy-haproxy/releases/) [![GitHub release](https://img.shields.io/github/release/byjg/docker-easy-haproxy.svg)](https://github.com/byjg/docker-easy-haproxy/releases/)
Service discovery for HAProxy. ![EasyHAProxy](easyhaproxy_logo.png)
This Docker image will dynamically create the `haproxy.cfg` based on the labels defined in docker containers or from ## Service discovery for HAProxy
a simple Yaml.
The main objective of EasyHAProxy is dynamically create the `haproxy.cfg` based on the labels defined in docker containers.
EasyHAProxy can detect and configure automatically HAProxy on the folowing platforms:
- Docker
- Docker Swarm
- Kubernetes
## Features ## Features
EasyHAProxy will discover the services based on the Docker Tags of the running containers in a Docker host or Docker Swarm cluster and dynamically set up the `haproxy.cfg`. Below, EasyHAProxy main features:: EasyHAProxy will discover the services based on the Docker Tags of the running containers in a Docker host or Docker Swarm cluster and dynamically set up the `haproxy.cfg`. Below, EasyHAProxy main features:
- Use Letsencrypt with HAProxy. - Use Letsencrypt with HAProxy.
- Set your custom SSL certificates
- Balance traffic between multiple replicas - Balance traffic between multiple replicas
- Set SSL with three different levels of validations and according to the most recent definitions. - Set SSL with three different levels of validations and according to the most recent definitions.
- Include your SSL certificate.
- Setup HAProxy to listen to TCP. - Setup HAProxy to listen to TCP.
- Add redirects. - Add redirects.
- Enable/disable Stats on port 1936 with a custom password. - Enable/disable Stats on port 1936 with a custom password.
- Enable/disable custom errors. - Enable/disable custom errors.
Also, it is possible to set up HAProxy from a simple Yaml file instead of creating `haproxy.cfg` file. Also, it is possible to set up HAProxy from a simple Yaml file instead of creating `haproxy.cfg` file.
## Basic Usage ## How It Works?
The Easy HAProxy will automatically create the `haproxy.cfg` file based on the containers or a YAML provided. You don't need to change your current infrastructure and don't need to learn the HAProxy configuration.
The basic command line to run is: You need run the EasyHAProxy container, add some labels to your existing container and EasyHAProxy will
automatically detect them and setup HAProxy for you.
```bash ## Detailed Instructions
docker run -d \
--name easy-haproxy-container \
-v /var/run/docker.sock:/var/run/docker.sock \
-e EASYHAPROXY_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; For detailed instructions on how to use EasyHAProxy follow the instructions for the platform you want to use:
The environment variables will setup the HAProxy. | Kubernetes | Docker Swarm | Docker | Static
|:----------:|:------------:|:------:|:-------:
| [![Kubernetes](easyhaproxy_kubernetes.png)](docs/kubernetes.md) | [![Docker Swarm](easyhaproxy_swarm.png)](docs/swarm.md) | [![Docker](easyhaproxy_docker.png)](docs/docker.md) | [![Static](easyhaproxy_static.png)](docs/static.md)
| Environment Variable | Description |
|-------------------------------|---------------------------------------------------------------------------------------------------------------|
| EASYHAPROXY_DISCOVER | How `haproxy.cfg` will be created: `static`, `docker` or `swarm` |
| EASYHAPROXY_LABEL_PREFIX | (Optional) The key will search for matching resources. Default: `easyhaproxy`. |
| EASYHAPROXY_LETSENCRYPT_EMAIL | (Optional) The email will be used to request the certificate to Letsencrypt |
| EASYHAPROXY_SSL_MODE | (Optional) `STRICT` supports only the most recent TLS version; `DEFAULT` good SSL integration with recent browsers; `LOOSE` supports all old SSL protocols for old browsers (not recommended). |
| EASYHAPROXY_REFRESH_CONF | (Optional) Check configuration every N seconds. Default: 10 |
| HAPROXY_USERNAME | (Optional) The HAProxy username to the statistics. Default: `admin` |
| HAPROXY_PASSWORD | (Optional) The HAProxy password to the statistics. If not set, statistics will be available with no password |
| HAPROXY_STATS_PORT | (Optional) The HAProxy port to the statistics. Default: `1936`. If set to `false`, disable statistics |
| HAPROXY_CUSTOMERRORS | (Optional) If HAProxy will use custom HTML errors. true/false. Default: `false` |
The environment variable `EASYHAPROXY_DISCOVER` will define where is located your containers (see below for more details):
- docker
- swarm
- static
## Automatic Discover Services
Easy HAProxy can automatically discover the container services running in the same network of Docker or in a Docker Swarm cluster.
### EASYHAPROXY_DISCOVER: docker
This method will use a standard 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 discovery will occur every minute.
e.g.:
```bash
docker create networkd easyhaproxy
docker run --network easyhaproxy byjg/easyhaproxy
docker run --network easyhaproxy myimage
```
or, if the container is already created you can join it using the command:
```
docker network connect easyhaproxy mycontainer
```
### EASYHAPROXY_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 discovery will occur every minute.
Important: easyhaproxy needs to be in the same network of the containers or otherwise will not access.
### Docker Container (Swarm or Docker) tags:
| Tag | Description | Example |
|---------------------------------------|---------------------------------------------------------------------------------------------------------|--------------|
| easyhaproxy.[definition].host | Host(s) HAProxy is listening. More than one host use comma as delimiter | somehost.com OR host1.com,host2.com |
| easyhaproxy.[definition].mode | (Optional) Is this `http` or `tcp` mode in HAProxy. (Defaults to http) | http |
| easyhaproxy.[definition].port | (Optional) Port HAProxy will listen for the host. (Defaults to 80) | 80 |
| easyhaproxy.[definition].localport | (Optional) Port container is listening. (Defaults to 80) | 8080 |
| easyhaproxy.[definition].redirect | (Optional) JSON containing key/value pair from host/to URL redirect. | {"foo.com":"https://bla.com", "bar.com":"https://bar.org"} |
| easyhaproxy.[definition].sslcert | (Optional) Cert PEM Base64 encoded. Do not use this if letsencrypt is enabled. | |
| easyhaproxy.[definition].ssl | (Optional) If `true` you need to provide certificate as a file. See below. Do not use with `sslcert`. | true |
| easyhaproxy.[definition].health-check | (Optional) `ssl`, enable health check via SSL in `mode tcp` (Defaults to "empty") | ssl |
| easyhaproxy.[definition].letsencrypt | (Optional) Generate certificate with letsencrypt. Do not use with `sslcert`. | true OR yes OR false OR no |
| easyhaproxy.[definition].redirect_ssl | (Optional) Redirect all requests to https | true OR yes OR false OR no |
### Defining the labels in Docker Swarm
if you are deploying a stack in a Docker Swarm cluster, set labels at the `deploy` level:
```yaml
services:
foo:
deploy:
labels:
easyhaproxy.my.host: "www.example.org"
easyhaproxy.my.localport: 8080
...
```
### Single Definition
```bash
docker run \
-l easyhaproxy.webapi.port=80\
-l easyhaproxy.webapi.host=byjg.com.br \
....
```
### Multiples Definitions on the same container
```bash
docker run \
-l easyhaproxy.express.port=80 \
-l easyhaproxy.express.localport=3000 \
-l easyhaproxy.express.host=express.byjg.com.br \
-l easyhaproxy.admin.port=80 \
-l easyhaproxy.admin.localport=3001 \
-l easyhaproxy.admin.host=admin.byjg.com.br \
.... \
some/myimage
```
### Multiples hosts on the same container
```bash
docker run \
-l easyhaproxy.express.port=80 \
-l easyhaproxy.express.localport=3000 \
-l easyhaproxy.express.host=express.byjg.com.br,admin.byjg.com.br \
.... \
some/myimage
```
If you are using docker-compose you can use this way:
```yaml
version: "3"
services:
mycontainer:
image: some/myimage
labels:
easyhaproxy.express.port: 80
easyhaproxy.express.localport: 3000
easyhaproxy.express.host: >-
express.byjg.com.br,
admin.byjg.com.br
```
### TLS passthrough
Used to pass on SSL termination to a backend. Alternatively, you can enable health-check via SSL on the backend with the optional `health-check` label:
```bash
docker run \
-l easyhaproxy.example.mode=tcp \
-l easyhaproxy.example.health-check=ssl \
-l easyhaproxy.example.port=443
.... \
some/tcp-service
```
### Redirect Example
```bash
docker run \
-l easyhaproxy.[definition].redirect='{"www.byjg.com.br":"http://byjg.com.br","byjg.com":"http://byjg.com.br"}'
```
## EASYHAPROXY_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:
username: admin
password: password
port: 1936 # Optional (default 1936)
customerrors: true # Optional (default false)
ssl_mode: default
letsencrypt: {
"email": "acme@example.org"
}
easymapping:
- port: 80
hosts:
host1.com.br:
containers:
- container:5000
letsencrypt: true
redirect_ssl: true
host2.com.br:
containers:
- other:3000
redirect:
www.host1.com.br: http://host1.com.br
- port: 443
hosts:
host1.com.br:
containers:
- container:80
redirect_ssl: false
ssl: true
- port: 8080
hosts:
host3.com.br:
containers:
- domain:8181
```
Running:
```bash
docker run -v /my/config.yml:/etc/haproxy/easyconfig.yml .... byjg/easyhaproxy
```
## Letsencrypt
This HAProxy can issue a letsencrypt certificate. The command is as below:
Run the EasyHAProxy:
```bash
docker run \
-e EASYHAPROXY_LETSENCRYPT_EMAIL=john@doe.com
.... \
byjg/easy-haproxy
```
Run your container:
```bash
docker run \
-l easyhaproxy.express.port=80 \
-l easyhaproxy.express.localport=3000 \
-l easyhaproxy.express.host=example.org \
-l easyhaproxy.express.letsencrypt=true \
.... \
some/myimage
```
Caveats:
- Your container **must** listen to the port 80. Besides no error, the certificate won't be issued if in a different port.
- The port 2080 is reserved for the certbot and should not be exposed.
- You cannot set the port 443 for the container with the Letsencrypt because EasyHAProxy will handle this automatically once the certificate is issued.
- If you don't run the EasyHAProxy with the parameter `EASYHAPROXY_LETSENCRYPT_EMAIL` no certificate will be issued.
- Be aware of Letsencrypt issue limits - https://letsencrypt.org/docs/duplicate-certificate-limit/ and https://letsencrypt.org/docs/rate-limits/
## Exposing Ports
You must expose some ports on the EasyHAProxy container and in the firewall. However, you don't need to expose the other container ports because EasyHAProxy will handle that.
- The ports `80` and `443`.
- If you enable the HAProxy statistics, you must also expose the port defined in `HAPROXY_STATS_PORT` environment variable (default 1936). Be aware that statististics are enabled by default with no password.
- Every port defined in `easyhaproxy.[definitions].port` also should be exposed.
e.g.
```bash
docker run \
/* other parameters */
-p 80:80 \
-p 443:443 \
-p 1936:1936 \
-d byjg/easy-haproxy
```
## Mapping custom .cfg files
You can concatenate valid HAProxy `.cfg` files to the dynamically generated `haproxy.cfg` by mapping the folder `/etc/haproxy/conf.d`.
```bash
docker run \
/* other parameters */
-v /your/local/conf.d:/etc/haproxy/conf.d \
-d byjg/easy-haproxy
```
## Mapping SSL certificates volumes
EasyHAProxy stores the certificates inside the folder `/certs/haproxy` and `/certs/letsencrypt`.
- If you want to preserve the letsencrypt certificates between reloads, map the folder `/certs/letsencrypt` to your volume.
- If you want to provide your certificates as a file instead of a Base64 parameter, map the folder `/certs/haproxy` to your volume, and instead of use `easyhaproxy.[definition].sslcert`, use `easyhaproxy.[definition].ssl: true`
```bash
docker run \
/* other parameters */
-v /your/certs/letsencrypt:/certs/letsencrypt \
-d byjg/easy-haproxy
```
## Handling SSL
You can attach a valid SSL certificate to the request.
1. First, Create a single PEM file including CA.
```bash
cat example.com.crt example.com.key > single.pem
cat single.pem
-----BEGIN CERTIFICATE-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5ZheHqmBnEJP+
U9r1gxYWKLzdqrMrcxtQN6M1hIH9n0peuJeIrybdcV7sMbStMXI=
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEojCCA4qgAwIBAgIUegW2BimwuL4RzRZ2WYkHA6U5nkAwDQYJKoZIhvcNAQEL
3j4wz8/I5fdsk090j4s5KA==
-----END PRIVATE KEY-----
```
2. Convert it to BASE64 in a single line:
```bash
cat single.pem | base64 -w0
```
3. Use this string to define the label `easyhaproxy.[definition].sslcert`
## Setting Custom Errors
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
```bash
docker build -t byjg/easy-haproxy .
```
## Limitations
EasyHAProxy has some limitations when there is more than one easy-haproxy container running:
- Replicas can be out-of-sync for a few seconds because each replica will discover the pods separately.
- Each replica will request a Letsencrypt certificate and can fail because the letsencrypt challenge can be directed to the other replica.
---- ----
[Open source ByJG](http://opensource.byjg.com) [Open source ByJG](http://opensource.byjg.com)

View file

@ -1,66 +0,0 @@
name: docker-easy-haproxy
project:
version: 1.0.0
download_url: https://github.com/byjg/docker-easy-haproxy/releases
license:
software: MIT
software_url: https://opensource.org/licenses/MIT
docs: MIT
docs_url: https://opensource.org/licenses/MIT
git_edit_address: https://github.com/byjg/docker-easy-haproxy/blob/master/
links:
header:
- title: GitHub
url: https://github.com/byjg/docker-easy-haproxy
- title: ByJG
url: https://opensource.byjg.com/
footer:
- title: GitHub
url: https://github.com/byjg/docker-easy-haproxy
- title: Issues
url: https://github.com/byjg/docker-easy-haproxy/issues
ui:
header:
color1: "#080331"
color2: "#0033cc"
trianglify: true
social:
github:
user: byjg
repo: docker-easy-haproxy
twitter:
enabled: false
via:
hash: opensourcebyjg
account:
facebook:
enabled: true
url: https://opensource.byjg.com/
profileUrl:
author:
twitter: byjg
twitter:
card: summary
username: byjg
logo: https://opensource.byjg.com/images/logo_byjg.png
analytics:
google: UA-130014324-1
plugins:
- jekyll-seo-tag
# Build settings
markdown: kramdown
remote_theme: byjg/jekyll-docs-theme

View file

@ -1,5 +0,0 @@
_
___ __ _ ____ _ ___| |_ __ _ _ __ _ _ _____ ___ _
/ -_) _` (_-< || |___| ' \/ _` | '_ \ '_/ _ \ \ / || |
\___\__,_/__/\_, | |_||_\__,_| .__/_| \___/_\_\\_, |
|__/ |_| |__/

View file

@ -1,55 +0,0 @@
#!/usr/bin/env bash
source /scripts/functions.sh
if [ ! -f /scripts/letsencrypt_hosts.txt ]; then
exit 0
fi
# Semaphore
if [ -f /tmp/certbot-lock ]; then
log "notice" "CERTBOT_JOB" "Another process is running"
exit 0
fi
touch /tmp/certbot-lock
mkdir -p /var/log/letsencrypt
ln -sf /dev/stdout /var/log/letsencrypt/letsencrypt.log
REQUEST_CERTS=""
RENEW_CERTS=""
for domain in $(cat /scripts/letsencrypt_hosts.txt); do
if [ ! -f "/certs/letsencrypt/$domain.pem" ]; then
REQUEST_CERTS="$REQUES_CERTS -d $domain"
continue
fi
if [[ $(find "/certs/letsencrypt/$domain.pem" -mtime +30 -print) ]]; then
RENEW_CERTS="$RENEW_CERTS -d $domain"
fi
done
if [ -n "$REQUEST_CERTS" ]; then
log "info" "CERTBOT_JOB" "Requesting certificates for $REQUEST_CERTS"
certbot certonly \
--standalone \
--preferred-challenges http \
--http-01-port 2080 \
--agree-tos \
--issuance-timeout 90 \
--no-eff-email \
--non-interactive \
--max-log-backups=0 \
--post-hook "/scripts/certbot_to_haproxy.sh" \
$REQUEST_CERTS --email $EASYHAPROXY_LETSENCRYPT_EMAIL
fi
if [ -n "$RENEW_CERTS" ]; then
log "info" "CERTBOT_JOB" "Resquesting renew certificated fort $RENEW_CERTS"
certbot renew --post-hook "/scripts/certbot_to_haproxy.sh"
fi
# Release semaphore
rm /tmp/certbot-lock

View file

@ -1,14 +0,0 @@
#!/bin/bash
source /scripts/functions.sh
# Loop through all Let's Encrypt certificates
for CERTIFICATE in `find /etc/letsencrypt/live/* -type d`; do
CERTIFICATE=`basename $CERTIFICATE`
# Combine certificate and private key to single file
cat /etc/letsencrypt/live/$CERTIFICATE/fullchain.pem /etc/letsencrypt/live/$CERTIFICATE/privkey.pem > /certs/letsencrypt/$CERTIFICATE.pem
done
# It will be checked on haproxy-reload.sh
touch /tmp/force-reload

View file

@ -1,9 +0,0 @@
#!/bin/bash
function log() {
# ARGS:
# - loglevel
# - app
# - message
echo [$2] $(date +"$EASYHAPROXY_DATEFORMAT") [$1]: $3
}

View file

@ -1,73 +0,0 @@
#!/usr/bin/env bash
source /scripts/functions.sh
cd /scripts
RELOAD="true"
if [[ "static|docker|swarm" != *"$EASYHAPROXY_DISCOVER"* ]];then
log "error" "CONF_CHECK" "EASYHAPROXY_DISCOVER should be 'static', 'docker', or 'swarm'. I got '$EASYHAPROXY_DISCOVER' instead."
exit 1
fi
if [[ "$EASYHAPROXY_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 [[ "$EASYHAPROXY_DISCOVER" == "docker" ]]; then
CONTAINERS=$(docker ps -q | sort | uniq)
LABEL_PATH=".Config.Labels"
for container in ${CONTAINERS}; do
docker inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE}
done
else
CONTAINERS=$(docker node ps $(docker node ls -q) --format "{{ .Name }}" --filter desired-state=running | cut -d. -f1 | sort | uniq)
LABEL_PATH=".Spec.Labels"
for container in ${CONTAINERS}; do
docker service inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE}
done
fi
if cmp -s ${CONTROL_FILE} ${CONTROL_FILE}.old ; then
RELOAD="false"
else
python3 swarm.py > /etc/haproxy/haproxy.cfg
log "info" "CONF_CHECK" "New configuration found"
fi
fi
if cmp -s ${CONTROL_FILE} ${CONTROL_FILE}.old ; then
RELOAD="false"
fi
if [[ ! -z "$1" ]]; then
log "info" "CONF_CHECK" "Initial configuration. Skip certbot."
else
/scripts/certbot.sh
fi
# If Certbot reloads successfully will create the file /tmp/force-reload
if [ -f /tmp/force-reload ]; then
log "info" "CONF_CHECK" "New certificates found..."
RELOAD="true"
rm /tmp/force-reload
fi
if [[ ! -z "$1" ]]; then
log "info" "CONF_CHECK" "Starting haproxy..."
/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg $(ls /etc/haproxy/conf.d/*.cfg 2>/dev/null | xargs -I{} echo -f {}) -p /run/haproxy.pid -S /var/run/haproxy.sock &
elif [[ "$RELOAD" == "true" ]]; then
log "info" "CONF_CHECK" "Reloading..."
/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg $(ls /etc/haproxy/conf.d/*.cfg 2>/dev/null | xargs -I{} echo -f {}) -p /run/haproxy.pid -x /var/run/haproxy.sock -sf $(cat /run/haproxy.pid) &
fi

View file

@ -1,29 +0,0 @@
#!/usr/bin/env bash
source /scripts/functions.sh
/usr/sbin/haproxy -v
if [ -z "$EASYHAPROXY_DATEFORMAT" ]; then
export EASYHAPROXY_DATEFORMAT="%Y-%m-%d %H:%M:%S %Z"
fi
if [ -z "$EASYHAPROXY_REFRESH_CONF" ]; then
export EASYHAPROXY_REFRESH_CONF=10
fi
cat banner.txt
echo Release: $RELEASE_VERSION
echo
echo "Environment"
env | sort | grep 'HAPROXY' | xargs -I{} echo " - {} "
echo
/scripts/haproxy-reload.sh initial
while true; do
sleep $EASYHAPROXY_REFRESH_CONF
log "info" "CONF_CHECK" "Heartbeat."
/scripts/haproxy-reload.sh
done

View file

@ -1,22 +0,0 @@
import yaml
import sys
import os
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())
path = os.path.dirname(os.path.realpath(__file__))
with open(path + "/letsencrypt_hosts.txt", 'w') as fp:
fp.write('\n'.join(cfg.letsencrypt_hosts))
exit(0)

View file

@ -1,34 +0,0 @@
import os
from easymapping import HaproxyConfigGenerator
with open("/tmp/.docker_data", 'r') as content_file:
line_list = content_file.readlines()
result = {
"customerrors": True if os.getenv("HAPROXY_CUSTOMERRORS") == "true" else False,
"ssl_mode": os.getenv("EASYHAPROXY_SSL_MODE", "default")
}
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",
}
result["lookup_label"] = os.getenv("EASYHAPROXY_LABEL_PREFIX") if os.getenv("EASYHAPROXY_LABEL_PREFIX") else "easyhaproxy"
if (os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL")):
result["letsencrypt"] = {
"email": os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL")
}
cfg = HaproxyConfigGenerator(result)
print(cfg.generate(line_list))
path = os.path.dirname(os.path.realpath(__file__))
with open(path + "/letsencrypt_hosts.txt", 'w') as fp:
fp.write('\n'.join(cfg.letsencrypt_hosts))
# print(jsonStr)

View file

@ -5,9 +5,9 @@ set -e
# Start k8s-ci before run this command # Start k8s-ci before run this command
# docker run --privileged -v /tmp/z:/var/lib/containers -it --rm -v $PWD:/work -w /work byjg/k8s-ci # docker run --privileged -v /tmp/z:/var/lib/containers -it --rm -v $PWD:/work -w /work byjg/k8s-ci
if [ -z "$DOCKER_USERNAME" ] || [ -z "$DOCKER_PASSWORD" ] || [ -z "$DOCKER_REGISTRY" ] if [ -z "$DOCKER_USERNAME" ] || [ -z "$DOCKER_PASSWORD" ] || [ -z "$DOCKER_REGISTRY" ] || [ -z "$VERSIONS" ]
then then
echo You need to setup \$DOCKER_USERNAME, \$DOCKER_PASSWORD and \$DOCKER_REGISTRY before run this command. echo You need to setup \$DOCKER_USERNAME, \$DOCKER_PASSWORD, \$DOCKER_REGISTRY and \$VERSIONS before run this command.
exit 1 exit 1
fi fi
@ -15,19 +15,17 @@ buildah login --username $DOCKER_USERNAME --password $DOCKER_PASSWORD $DOCKER_RE
podman run --rm --events-backend=file --cgroup-manager=cgroupfs --privileged docker://multiarch/qemu-user-static --reset -p yes podman run --rm --events-backend=file --cgroup-manager=cgroupfs --privileged docker://multiarch/qemu-user-static --reset -p yes
VERSIONS="latest $TRAVIS_TAG"
for VERSION in $VERSIONS for VERSION in $VERSIONS
do do
DOCKERFILE=Dockerfile DOCKERFILE=build/Dockerfile
buildah manifest create byjg/easy-haproxy:$VERSION buildah manifest create byjg/easy-haproxy:$VERSION
buildah bud --arch arm64 --os linux --iidfile /tmp/iid-arm64 -f $DOCKERFILE -t byjg/easy-haproxy:$VERSION-arm64 . buildah bud --arch arm64 --os linux --iidfile /tmp/iid-arm64 -f $DOCKERFILE --build-arg=RELEASE_VERSION_ARG="$VERSION-manual" -t byjg/easy-haproxy:$VERSION-arm64 .
buildah bud --arch amd64 --os linux --iidfile /tmp/iid-amd64 -f $DOCKERFILE -t byjg/easy-haproxy:$VERSION-amd64 . buildah bud --arch amd64 --os linux --iidfile /tmp/iid-amd64 -f $DOCKERFILE --build-arg=RELEASE_VERSION_ARG="$VERSION-manual" -t byjg/easy-haproxy:$VERSION-amd64 .
buildah manifest add byjg/easy-haproxy:$VERSION --arch arm64 --os linux --variant v8 $(cat /tmp/iid-arm64) buildah manifest add byjg/easy-haproxy:$VERSION --arch arm64 --os linux --variant v8 $(cat /tmp/iid-arm64)
buildah manifest add byjg/easy-haproxy:$VERSION --arch amd64 --os linux --os=linux $(cat /tmp/iid-amd64) buildah manifest add byjg/easy-haproxy:$VERSION --arch amd64 --os linux --os=linux $(cat /tmp/iid-amd64)
buildah manifest push --all --format v2s2 byjg/easy-haproxy:$VERSION docker://byjg/easy-haproxy:$VERSION buildah manifest push --all --format v2s2 byjg/easy-haproxy:$VERSION docker://byjg/easy-haproxy:$VERSION
done done

View file

@ -3,21 +3,19 @@ FROM alpine:3.16
ARG RELEASE_VERSION_ARG ARG RELEASE_VERSION_ARG
ENV RELEASE_VERSION=$RELEASE_VERSION_ARG ENV RELEASE_VERSION=$RELEASE_VERSION_ARG
ENV TZ="Etc/UTC"
WORKDIR /scripts WORKDIR /scripts
COPY requirements.txt /scripts COPY src/ /scripts/
COPY templates /scripts/templates/ COPY build/assets /
COPY easymapping /scripts/easymapping/
COPY tests/ /scripts/tests/
COPY assets /
RUN apk add --no-cache haproxy bash python3 py3-pip py-yaml docker certbot openssl \ RUN apk add --no-cache haproxy bash python3 py3-pip py-yaml certbot openssl \
&& ln -s /usr/bin/python3 /usr/bin/python \ && ln -s /usr/bin/python3 /usr/bin/python \
&& pip3 install --upgrade pip \ && pip3 install --upgrade pip \
&& pip install -r requirements.txt \ && pip install -r requirements.txt \
&& pytest -s tests/ \ && pytest -s -vv tests/ \
&& openssl dhparam -out /etc/haproxy/dhparam 2048 \ && openssl dhparam -out /etc/haproxy/dhparam 2048 \
&& openssl dhparam -out /etc/haproxy/dhparam-1024 1024 && openssl dhparam -out /etc/haproxy/dhparam-1024 1024
CMD ["/bin/bash", "-c", "/scripts/haproxy.sh" ] CMD ["/usr/bin/python", "-u", "/scripts/main.py" ]

View file

@ -2,7 +2,7 @@ version: "3"
services: services:
easyhaproxy: easyhaproxy:
image: byjg/easy-haproxy:4.1.0 image: byjg/easy-haproxy:master
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- certs_letsencrypt:/certs/letsencrypt - certs_letsencrypt:/certs/letsencrypt
@ -23,10 +23,15 @@ services:
- "443:443/tcp" - "443:443/tcp"
- "1936:1936/tcp" - "1936:1936/tcp"
networks:
- easyhaproxy
volumes: volumes:
certs_letsencrypt: certs_letsencrypt:
external: true
certs_haproxy: certs_haproxy:
external: true
networks: networks:
easyhaproxy: easyhaproxy:
driver: bridge external: true

29
deploy/docker/install.sh Executable file
View file

@ -0,0 +1,29 @@
#!/bin/bash
ASSETS_DIR="$(dirname "${BASH_SOURCE[0]}")"/../../build/assets/certs/haproxy
docker network create easyhaproxy
docker volume create certs_letsencrypt
docker volume create certs_haproxy
docker run -d --rm --name easyhaproxy_install -v certs_haproxy:/certs alpine tail -f /dev/null
docker cp $ASSETS_DIR/.place_holder_cert.pem easyhaproxy_install:/certs/.place_holder_cert.pem
docker stop easyhaproxy_install
echo
echo
echo make sure to add to all of your containers:
echo
echo docker-compose
echo ==============
echo "networks:"
echo " default:"
echo " name: easyhaproxy"
echo " external: true"
echo
echo
echo docker run
echo ==============
echo docker run ... --network easyhaproxy ... your_image:tag
echo

View file

@ -0,0 +1,208 @@
---
# Source: easyhaproxy/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: ingress-easyhaproxy
namespace: easyhaproxy
labels:
helm.sh/chart: easyhaproxy-0.1.3
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
app.kubernetes.io/version: "master"
app.kubernetes.io/managed-by: Helm
---
# Source: easyhaproxy/templates/clusterrole.yaml
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: ingress-easyhaproxy
namespace: easyhaproxy
labels:
helm.sh/chart: easyhaproxy-0.1.3
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
app.kubernetes.io/version: "master"
app.kubernetes.io/managed-by: Helm
rules:
- apiGroups:
- ""
resources:
# - configmaps
# - endpoints
# - nodes
- pods
- services
- namespaces
# - events
- serviceaccounts
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses
# - ingresses/status
# - ingressclasses
verbs:
- get
- list
- watch
# - apiGroups:
# - "extensions"
# - "networking.k8s.io"
# resources:
# - ingresses/status
# verbs:
# - update
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
# - watch
# - create
# - patch
# - update
# - apiGroups:
# - "discovery.k8s.io"
# resources:
# - endpointslices
# verbs:
# - get
# - list
# - watch
---
# Source: easyhaproxy/templates/clusterrolebinding.yaml
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: ingress-easyhaproxy
namespace: easyhaproxy
labels:
helm.sh/chart: easyhaproxy-0.1.3
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
app.kubernetes.io/version: "master"
app.kubernetes.io/managed-by: Helm
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ingress-easyhaproxy
subjects:
- kind: ServiceAccount
name: ingress-easyhaproxy
namespace: easyhaproxy
---
# Source: easyhaproxy/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
metadata:
name: ingress-easyhaproxy
namespace: easyhaproxy
labels:
helm.sh/chart: easyhaproxy-0.1.3
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
app.kubernetes.io/version: "master"
app.kubernetes.io/managed-by: Helm
annotations:
{}
spec:
type: ClusterIP
ports:
- name: http
port: 80
- name: https
port: 443
- name: stats
port: 1936
selector:
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
---
# Source: easyhaproxy/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingress-easyhaproxy
namespace: easyhaproxy
labels:
helm.sh/chart: easyhaproxy-0.1.3
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
app.kubernetes.io/version: "master"
app.kubernetes.io/managed-by: Helm
spec:
selector:
matchLabels:
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
template:
metadata:
labels:
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: easyhaproxy/node
operator: In
values:
- master
serviceAccountName: ingress-easyhaproxy
securityContext:
{}
containers:
- name: easyhaproxy
securityContext:
{}
image: "byjg/easy-haproxy:master"
imagePullPolicy: Always
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
- name: stats
containerPort: 1936
resources:
requests:
cpu: 100m
memory: 128Mi
env:
- name: EASYHAPROXY_DISCOVER
value: kubernetes
- name: HAPROXY_USERNAME
value: admin
- name: HAPROXY_PASSWORD
value: password
- name: EASYHAPROXY_REFRESH_CONF
value: "10"
- name: HAPROXY_CUSTOMERRORS
value: "true"
- name: EASYHAPROXY_SSL_MODE
value: loose
- name: EASYHAPROXY_LOG_LEVEL
value: DEBUG
- name: HAPROXY_LOG_LEVEL
value: DEBUG
- name: CERTBOT_LOG_LEVEL
value: DEBUG

View file

@ -0,0 +1,175 @@
---
# Source: easyhaproxy/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: ingress-easyhaproxy
namespace: easyhaproxy
labels:
helm.sh/chart: easyhaproxy-0.1.3
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
app.kubernetes.io/version: "master"
app.kubernetes.io/managed-by: Helm
---
# Source: easyhaproxy/templates/clusterrole.yaml
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: ingress-easyhaproxy
namespace: easyhaproxy
labels:
helm.sh/chart: easyhaproxy-0.1.3
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
app.kubernetes.io/version: "master"
app.kubernetes.io/managed-by: Helm
rules:
- apiGroups:
- ""
resources:
# - configmaps
# - endpoints
# - nodes
- pods
- services
- namespaces
# - events
- serviceaccounts
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses
# - ingresses/status
# - ingressclasses
verbs:
- get
- list
- watch
# - apiGroups:
# - "extensions"
# - "networking.k8s.io"
# resources:
# - ingresses/status
# verbs:
# - update
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
# - watch
# - create
# - patch
# - update
# - apiGroups:
# - "discovery.k8s.io"
# resources:
# - endpointslices
# verbs:
# - get
# - list
# - watch
---
# Source: easyhaproxy/templates/clusterrolebinding.yaml
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: ingress-easyhaproxy
namespace: easyhaproxy
labels:
helm.sh/chart: easyhaproxy-0.1.3
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
app.kubernetes.io/version: "master"
app.kubernetes.io/managed-by: Helm
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ingress-easyhaproxy
subjects:
- kind: ServiceAccount
name: ingress-easyhaproxy
namespace: easyhaproxy
---
# Source: easyhaproxy/templates/deployment.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: ingress-easyhaproxy
namespace: easyhaproxy
labels:
helm.sh/chart: easyhaproxy-0.1.3
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
app.kubernetes.io/version: "master"
app.kubernetes.io/managed-by: Helm
spec:
selector:
matchLabels:
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
template:
metadata:
labels:
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: easyhaproxy/node
operator: In
values:
- master
serviceAccountName: ingress-easyhaproxy
securityContext:
{}
containers:
- name: easyhaproxy
securityContext:
{}
image: "byjg/easy-haproxy:master"
imagePullPolicy: Always
ports:
- name: http
containerPort: 80
hostPort: 80
- name: https
containerPort: 443
hostPort: 443
- name: stats
containerPort: 1936
hostPort: 1936
resources:
requests:
cpu: 100m
memory: 128Mi
env:
- name: EASYHAPROXY_DISCOVER
value: kubernetes
- name: HAPROXY_USERNAME
value: admin
- name: HAPROXY_PASSWORD
value: password
- name: EASYHAPROXY_REFRESH_CONF
value: "10"
- name: HAPROXY_CUSTOMERRORS
value: "true"
- name: EASYHAPROXY_SSL_MODE
value: loose
- name: EASYHAPROXY_LOG_LEVEL
value: DEBUG
- name: HAPROXY_LOG_LEVEL
value: DEBUG
- name: CERTBOT_LOG_LEVEL
value: DEBUG

View file

@ -0,0 +1,208 @@
---
# Source: easyhaproxy/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: ingress-easyhaproxy
namespace: easyhaproxy
labels:
helm.sh/chart: easyhaproxy-0.1.3
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
app.kubernetes.io/version: "master"
app.kubernetes.io/managed-by: Helm
---
# Source: easyhaproxy/templates/clusterrole.yaml
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: ingress-easyhaproxy
namespace: easyhaproxy
labels:
helm.sh/chart: easyhaproxy-0.1.3
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
app.kubernetes.io/version: "master"
app.kubernetes.io/managed-by: Helm
rules:
- apiGroups:
- ""
resources:
# - configmaps
# - endpoints
# - nodes
- pods
- services
- namespaces
# - events
- serviceaccounts
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses
# - ingresses/status
# - ingressclasses
verbs:
- get
- list
- watch
# - apiGroups:
# - "extensions"
# - "networking.k8s.io"
# resources:
# - ingresses/status
# verbs:
# - update
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
# - watch
# - create
# - patch
# - update
# - apiGroups:
# - "discovery.k8s.io"
# resources:
# - endpointslices
# verbs:
# - get
# - list
# - watch
---
# Source: easyhaproxy/templates/clusterrolebinding.yaml
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: ingress-easyhaproxy
namespace: easyhaproxy
labels:
helm.sh/chart: easyhaproxy-0.1.3
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
app.kubernetes.io/version: "master"
app.kubernetes.io/managed-by: Helm
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: ingress-easyhaproxy
subjects:
- kind: ServiceAccount
name: ingress-easyhaproxy
namespace: easyhaproxy
---
# Source: easyhaproxy/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
metadata:
name: ingress-easyhaproxy
namespace: easyhaproxy
labels:
helm.sh/chart: easyhaproxy-0.1.3
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
app.kubernetes.io/version: "master"
app.kubernetes.io/managed-by: Helm
annotations:
{}
spec:
type: NodePort
ports:
- name: http
port: 80
nodePort: 31080
- name: https
port: 443
nodePort: 31443
- name: stats
port: 1936
nodePort: 31936
selector:
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
---
# Source: easyhaproxy/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: ingress-easyhaproxy
namespace: easyhaproxy
labels:
helm.sh/chart: easyhaproxy-0.1.3
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
app.kubernetes.io/version: "master"
app.kubernetes.io/managed-by: Helm
spec:
selector:
matchLabels:
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
template:
metadata:
labels:
app.kubernetes.io/name: easyhaproxy
app.kubernetes.io/instance: ingress
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: easyhaproxy/node
operator: In
values:
- master
serviceAccountName: ingress-easyhaproxy
securityContext:
{}
containers:
- name: easyhaproxy
securityContext:
{}
image: "byjg/easy-haproxy:master"
imagePullPolicy: Always
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
- name: stats
containerPort: 1936
resources:
requests:
cpu: 100m
memory: 128Mi
env:
- name: EASYHAPROXY_DISCOVER
value: kubernetes
- name: HAPROXY_USERNAME
value: admin
- name: HAPROXY_PASSWORD
value: password
- name: EASYHAPROXY_REFRESH_CONF
value: "10"
- name: HAPROXY_CUSTOMERRORS
value: "true"
- name: EASYHAPROXY_SSL_MODE
value: loose
- name: EASYHAPROXY_LOG_LEVEL
value: DEBUG
- name: HAPROXY_LOG_LEVEL
value: DEBUG
- name: CERTBOT_LOG_LEVEL
value: DEBUG

97
docs/container-labels.md Normal file
View file

@ -0,0 +1,97 @@
# Container Labels
## Container (Docker or Swarm) labels
| Tag | Description | Default | Example |
|---------------------------------------|-------------------------------------------------------------------------------------------------------|----------------|--------------|
| easyhaproxy.[definition].host | Host(s) HAProxy is listening. More than one host use comma as delimiter | **required** | somehost.com OR host1.com,host2.com |
| easyhaproxy.[definition].mode | (Optional) Is this `http` or `tcp` mode in HAProxy. | http | http or tcp |
| easyhaproxy.[definition].port | (Optional) Port HAProxy will listen for the host. | 80 | 3000 |
| easyhaproxy.[definition].localport | (Optional) Port container is listening. | 80 | 8080 |
| easyhaproxy.[definition].redirect | (Optional) JSON containing key/value pair from host/to URL redirect. | *empty* | {"foo.com":"https://bla.com", "bar.com":"https://bar.org"} |
| easyhaproxy.[definition].sslcert | (Optional) Cert PEM Base64 encoded. Do not use this if `letsencrypt` is enabled. | *empty* | base64 cert + key |
| easyhaproxy.[definition].ssl | (Optional) If `true` you need to provide certificate as a file. See below. Do not use with `sslcert`. | false | true or false |
| easyhaproxy.[definition].health-check | (Optional) `ssl`, enable health check via SSL in `mode tcp` | *empty* | ssl |
| easyhaproxy.[definition].letsencrypt | (Optional) Generate certificate with letsencrypt. Do not use with `sslcert` parameter. | false | true OR false |
| easyhaproxy.[definition].redirect_ssl | (Optional) Redirect all requests to https | false | true OR false |
| easyhaproxy.[definition].clone_to_ssl | (Optional) It copies the configuration to HTTPS(443) and disable SSL from the current config. **Do not use* this with `ssl` or `letsencrypt` parameters | false | true OR false |
The `definition` is a string that will group all configurations togethers. Different `definition` will create different configurations.
The container can have more than one defintion.
## Configuations
### Single Definition
```bash
docker run \
-l easyhaproxy.webapi.port=80\
-l easyhaproxy.webapi.host=byjg.com.br \
....
```
### Multiples Definitions on the same container
```bash
docker run \
-l easyhaproxy.express.port=80 \
-l easyhaproxy.express.localport=3000 \
-l easyhaproxy.express.host=express.byjg.com.br \
-l easyhaproxy.admin.port=80 \
-l easyhaproxy.admin.localport=3001 \
-l easyhaproxy.admin.host=admin.byjg.com.br \
.... \
some/myimage
```
### Multiples hosts on the same container
```bash
docker run \
-l easyhaproxy.express.port=80 \
-l easyhaproxy.express.localport=3000 \
-l easyhaproxy.express.host=express.byjg.com.br,admin.byjg.com.br \
.... \
some/myimage
```
If you are using docker-compose you can use this way:
```yaml
version: "3"
services:
mycontainer:
image: some/myimage
labels:
easyhaproxy.express.port: 80
easyhaproxy.express.localport: 3000
easyhaproxy.express.host: >-
express.byjg.com.br,
admin.byjg.com.br
```
### TCP Mode
Set `easyhaproxy.[definition].mode=tcp` if your application uses TCP protocol instead of HTTP.
```bash
docker run \
-l easyhaproxy.example.mode=tcp \
-l easyhaproxy.example.port=3306
-l easyhaproxy.example.localport=3306
.... \
some/tcp-service
```
### Redirect Domains
```bash
docker run \
-l easyhaproxy.[definition].redirect='{"www.byjg.com.br":"http://byjg.com.br","byjg.com":"http://byjg.com.br"}'
```
----
[Open source ByJG](http://opensource.byjg.com)

View file

@ -0,0 +1,21 @@
# Docker environment variables
| Environment Variable | Description | Default |
|-------------------------------|-------------------------------------------------------------------------------------------------|------------------|
| EASYHAPROXY_DISCOVER | How the services will be discovered to create `haproxy.cfg`: `static`, `docker`, `swarm` or `kubernetes` | **required** |
| EASYHAPROXY_LABEL_PREFIX | (Optional) The key will search for matching resources. | `easyhaproxy` |
| EASYHAPROXY_LETSENCRYPT_EMAIL | (Optional) The email will be used to request the certificate to Letsencrypt | *empty* |
| EASYHAPROXY_SSL_MODE | (Optional) `strict` supports only the most recent TLS version; `default` good SSL integration with recent browsers; `loose` supports all old SSL protocols for old browsers (not recommended). | `default`|
| EASYHAPROXY_REFRESH_CONF | (Optional) Check configuration every N seconds. | 10 |
| EASYHAPROXY_LOG_LEVEL | (Optional) The log level for EasyHAproxy messages. Available: TRACE,DEBUG,INFO,WARN,ERROR,FATAL | DEBUG |
| CERTBOT_LOG_LEVEL | (Optional) The log level for Certbot messages. Available: TRACE,DEBUG,INFO,WARN,ERROR,FATAL | DEBUG |
| HAPROXY_LOG_LEVEL | (Optional) The log level for HAProxy messages. Available: TRACE,DEBUG,INFO,WARN,ERROR,FATAL | DEBUG |
| HAPROXY_USERNAME | (Optional) The HAProxy username to the statistics. | `admin` |
| HAPROXY_PASSWORD | (Optional) The HAProxy password to the statistics. If not set, statistics will be available with no password | *empty* |
| HAPROXY_STATS_PORT | (Optional) The HAProxy port to the statistics. If set to `false`, disable statistics | `1936` |
| HAPROXY_CUSTOMERRORS | (Optional) If HAProxy will use custom HTML errors. true/false. | `false` |
----
[Open source ByJG](http://opensource.byjg.com)

65
docs/docker.md Normal file
View file

@ -0,0 +1,65 @@
# Docker
## Setup Docker EasyHAProxy
This method will use a docker standalone installation to discover the containers and configure the HAProxy.
The only requirement is that containers and EasyHAProxy must be in the same docker network in order to HAProxy be able direct the traffic to the containers.
e.g.:
```bash
docker create network easyhaproxy
```
And then run the EasyHAProxy
```bash
docker run -d \
--name easy-haproxy-container \
-v /var/run/docker.sock:/var/run/docker.sock \
-e EASYHAPROXY_DISCOVER="docker" \
# + Environment Variables \
-p 80:80 \
-p 443:443 \
-p 1936:1936 \
--network easyhaproxy
byjg/easy-haproxy
```
The mapping to `/var/run/docker.sock` is necessary to discover the docker containers and get the labels;
## Running containers
To make your containers "discoverable" by EasyHAProxy that is minimum configuration you need:
```bash
docker run -d \
-e easyhaproxy.http.host=example.org \
-e easyhaproxy.http.port=80 \
-e easyhaproxy.http.localport=8080 \
--network easyhaproxy
my/image:tag
```
Once the container is running EasyHAProxy will detect automatically and start to redirect all traffic from `example.org:80` to your container.
You don't need to expose any port in your container.
There a list of other parameters you can to configure your container. Please follow the [docker label configuration](container-labels.md)
## Setup the EasyHAProxy container
You can configure the behavior of the EasyHAProxy by setup specific environment variables. To get a list of the variables please follow the [docker container environment](docker-environment.md)
## Setup certificates with Letsencrypt
Follow [this link](letsencrypt.md)
## Setup your own certificates
Follow [this link](ssl.md)
----
[Open source ByJG](http://opensource.byjg.com)

BIN
docs/icons.xcf Normal file

Binary file not shown.

198
docs/kubernetes.md Normal file
View file

@ -0,0 +1,198 @@
# Kubernetes
## Setup Kubernetes EasyHAProxy
EasyHAProxy query all ingress definitions with the annotation `kubernetes.io/ingress.class: easyhaproxy-ingress`.Once find the annotation, it will immediatelly setup HAProxy and start to serve it.
There are three installation modes:
- DaemonSet: It will expose the ports 80, 443 and 1936
- NodePort: It will expose the ports 31080, 31443 and 31936
- ClusterIP it will node expose any port. The HAProxy will be accessible only inside the cluster.
To install the daemonset in your cluster follow these steps:
### 1) Identify the node where your EasyHAProxy container will run.
Doesn't matter if you choose DaemonSet or ClusterIP, EasyHAProxy will be limited to a single node. To understand that see [limitations](limitations.md) page.
```bash
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
node-01 Ready <none> 561d v1.21.13-3
node-02 Ready <none> 561d v1.21.13-3
```
Add the EasyHAProxy label to the node
```bash
kubectl label nodes node-01 "easyhaproxy/node=master"
```
### 2) Install EasyHAProxy
There are two ways to install EasyHAProxy in a Kubernetes cluster. You can use Kubernetes Manifest or Helm 3.
#### 2.1.) Using Kubernetes Manifest
```bash
kubectl create namespace easyhaproxy
kubectl apply -f \
https://raw.githubusercontent.com/byjg/docker-easy-haproxy/kubernetes/deploy/kubernetes/easyhaproxy-daemonset.yml
```
You can configure the behavior of the EasyHAProxy by setup specific environment variables. To get a list of the variables please follow the [docker container environment](docker-environment.md)
#### 2.2) Using HELM 3
Minimal configuration
```bash
helm repo add byjg https://opensource.byjg.com/helm
helm repo update byjg
kubectl create namespace easyhaproxy
helm upgrade --install ingress byjg/easyhaproxy \
--namespace easyhaproxy \
--set resources.requests.cpu=100m \
--set resources.requests.memory=128Mi
```
Customizing Helm Values:
```yaml
easyhaproxy:
stats:
username: admin
password: password
refresh: "10"
customErrors: "true"
sslMode: loose
logLevel:
certbot: DEBUG
easyhaproxy: DEBUG
haproxy: DEBUG
service:
create: false # If false, it will create a Daemonset with hostPort. The easiest.
type: ClusterIP # or NodePort
annotations: {}
binding:
ports:
http: 80
https: 443
stats: 1936
additionalPorts: []
# Make sure to create this
masterNode:
label: easyhaproxy/node
values:
- master
```
## Running containers
The only requirement is that you have an ingress properly setup and with the annotation `kubernetes.io/ingress.class: easyhaproxy-ingress`.
e.g.
```yaml
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: easyhaproxy-ingress
name: example-ingress
namespace: example
spec:
rules:
- host: example.org
http:
paths:
- backend:
service:
name: example-service
port:
number: 8080
pathType: ImplementationSpecific
```
Once the container is running EasyHAProxy will detect automatically and start to redirect all traffic from `example.org:80` to your container.
You don't need to expose any port in your container.
Caveats:
- At this point, the implementation don't support all ingress properties nor wildcard domains.
- The ingress will publish externally only the ports 80 and 443, plus 1936 if stats is enable.
- EasyHAProxy will read all `spec.rules[].host` spec, however it will parse only the first path `spec.rules[].http.paths[0].port.number` for each rule, and ignore the other paths.
## Kubernetes annotations
| annotation | Description | Default | Example |
|-----------------------------|-----------------------------------------------------------------------------------------|--------------|--------------|
| kubernetes.io/ingress.class | (required) Activate EasyHAProxy. | **required** | easyhaproxy-ingress
| easyhaproxy.redirect_ssl | (optional) Boolean. Force redirect all endpoints to https. | false | true or false
| easyhaproxy.letsencrypt | (optional) Boolean. It will request letsencript certificates for the ingresses domains. | false | true or false
| easyhaproxy.redirect | (optional) Json. Specific a domain and its destination. | *empty* | {"domain":"redirect_url"}
| easyhaproxy.mode | (optional) Set the HTTP mode for that connection. | http | http or tcp
| easyhaproxy.listen_port | (optional) Set the an additional port for that ingress | http | http or tcp
**Important**: The annotations are per ingress and applied to all hosts in that ingress configuration.
## Letsencrypt
It is necessary add the annotation `easyhaproxy.letsencrypt` to the ingress configuration:
```yaml
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: easyhaproxy-ingress
easyhaproxy.letsencrypt: 'true'
name: example-ingress
namespace: example
spec:
....
```
Make sure your cluster is accessible both through ports 80 and 443.
## Custom SSL Certificates
You need to create a secret with your certificate and key, and associate them in your ingress.
```yaml
---
apiVersion: v1
kind: Secret
metadata:
name: host2-tls
namespace: default
data:
tls.crt: base64 of your certificate
tls.key: base64 of your certificate private key
type: kubernetes.io/tls
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: easyhaproxy-ingress
name: tls-example
namespace: default
spec:
tls:
- hosts:
- host2.local
secretName: host2-tls
rules:
...
```
----
[Open source ByJG](http://opensource.byjg.com)

52
docs/letsencrypt.md Normal file
View file

@ -0,0 +1,52 @@
# Letsencrypt
EasyHAProxy can issue a letsencrypt certificate. The command is as below:
Run the EasyHAProxy:
```bash
docker run \
-e EASYHAPROXY_LETSENCRYPT_EMAIL=john@doe.com
.... \
byjg/easy-haproxy
```
Run your container:
```bash
docker run \
-l easyhaproxy.express.port=80 \
-l easyhaproxy.express.localport=3000 \
-l easyhaproxy.express.host=example.org \
-l easyhaproxy.express.letsencrypt=true \
.... \
some/myimage
```
Requirements:
- Your container **must** listen to the port 80. Besides no error, the certificate won't be issued if in a different port.
- You cannot set the port 443 for the container with the Letsencrypt because EasyHAProxy will handle this automatically once the certificate is issued.
- You have to setup the `EASYHAPROXY_LETSENCRYPT_EMAIL` environment variable on EasyHAProxy. If you don't setup, EasyHAProxy **will not request** a certificate.
Be aware of Letsencrypt issue limits - https://letsencrypt.org/docs/duplicate-certificate-limit/ and https://letsencrypt.org/docs/rate-limits/
## Persist your Letsencrypt certificates
It is a good idea to store the letsencrypt certificate in a persistent storage, even you knowing you can issue again in case your lost the certificate.
However, there is a limit in how many certificates can be issue for the same domain in a period of time.
To avoid this, map the folder `/certs/letsencrypt` to a docker volume.
```bash
docker volume create certs_letsencrypt
docker run \
/* other parameters */
-v certs_letsencrypt:/certs/letsencrypt \
-d byjg/easy-haproxy
```
----
[Open source ByJG](http://opensource.byjg.com)

16
docs/limitations.md Normal file
View file

@ -0,0 +1,16 @@
# Limitations
EasyHAProxy currently expects to work in a single replica.
If more than one replica is running, EasyHAProxy will continue to work, however each replica will discover
the services independently.
It means replicas can be out-of-sync for a few seconds because each replica will discover the pods
separately.
For Letsencrypt this is worse because each replica will a Letsencrypt certificate and can fail because the
letsencrypt challenge can be directed to the other replica. Also, you can hit the certificate issue limit.
So if you intend to run multiple replicas **do not** activate letsencrypt.
----
[Open source ByJG](http://opensource.byjg.com)

BIN
docs/logo.xcf Normal file

Binary file not shown.

39
docs/other.md Normal file
View file

@ -0,0 +1,39 @@
# Other configurations
## Exposing Ports
You must expose some ports on the EasyHAProxy container and in the firewall. However, you don't need to expose the other container ports because EasyHAProxy will handle that.
- The ports `80` and `443`.
- If you enable the HAProxy statistics, you must also expose the port defined in `HAPROXY_STATS_PORT` environment variable (default 1936). Be aware that statististics are enabled by default with no password.
- Every port defined in `easyhaproxy.[definitions].port` also should be exposed.
e.g.
```bash
docker run \
/* other parameters */
-p 80:80 \
-p 443:443 \
-p 1936:1936 \
-d byjg/easy-haproxy
```
## Mapping custom .cfg files
You can concatenate valid HAProxy `.cfg` files to the dynamically generated `haproxy.cfg` by mapping the folder `/etc/haproxy/conf.d`.
```bash
docker run \
/* other parameters */
-v /your/local/conf.d:/etc/haproxy/conf.d \
-d byjg/easy-haproxy
```
## Setting Custom Errors
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`)
----
[Open source ByJG](http://opensource.byjg.com)

75
docs/ssl.md Normal file
View file

@ -0,0 +1,75 @@
# Setup your own certificates
You can use your certificates with EasyHAProxy.
There is two ways to do that.
## Setup certificate as a label definition in docker container
1. First, Create a single PEM from the certificate and the key.
```bash
cat example.com.crt example.com.key > single.pem
cat single.pem
-----BEGIN CERTIFICATE-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5ZheHqmBnEJP+
U9r1gxYWKLzdqrMrcxtQN6M1hIH9n0peuJeIrybdcV7sMbStMXI=
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEojCCA4qgAwIBAgIUegW2BimwuL4RzRZ2WYkHA6U5nkAwDQYJKoZIhvcNAQEL
3j4wz8/I5fdsk090j4s5KA==
-----END PRIVATE KEY-----
```
2. Convert the `single.pem` to BASE64 in a single line:
```bash
cat single.pem | base64 -w0
```
3. Use this string to define the label `easyhaproxy.[definition].sslcert`
## Map the certificate as docker volume
EasyHAProxy stores the certificates inside the folder `/certs/haproxy`.
1. Run EasyHAProxy with the volume for the certificates:
```bash
docker volume create certs_haproxy
docker run \
/* other parameters */
-v certs_haproxy:/certs/haproxy \
-d byjg/easy-haproxy
```
2. Create a single PEM from the certificate and the key.
```bash
cat example.com.crt example.com.key > single.pem
cat single.pem
-----BEGIN CERTIFICATE-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5ZheHqmBnEJP+
U9r1gxYWKLzdqrMrcxtQN6M1hIH9n0peuJeIrybdcV7sMbStMXI=
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEojCCA4qgAwIBAgIUegW2BimwuL4RzRZ2WYkHA6U5nkAwDQYJKoZIhvcNAQEL
3j4wz8/I5fdsk090j4s5KA==
-----END PRIVATE KEY-----
```
3. Copy this certificate to EasyHAProxy volume
```bash
docker cp single.pem easyhaproxy:/certs/haproxy
```
----
[Open source ByJG](http://opensource.byjg.com)

109
docs/static.md Normal file
View file

@ -0,0 +1,109 @@
# Docker
## Setup Docker EasyHAProxy
This method will use a static configuration, simpler and easier than HAProxy to create the `haproxy.cfg`
You can use this configuration to setup external servers not related to docker or kubernetes.
Another advantage is that EasyHAProxy will monitor for changes in this file and automatically reconfigure HAProxy and changes are detected.
First, Create a YAML:
```yaml
stats:
username: admin
password: password
port: 1936 # Optional (default 1936)
customerrors: true # Optional (default false)
ssl_mode: default
letsencrypt: {
"email": "acme@example.org"
}
easymapping:
- port: 80
hosts:
host1.com.br:
containers:
- container:5000
letsencrypt: true
redirect_ssl: true
host2.com.br:
containers:
- other:3000
redirect:
www.host1.com.br: http://host1.com.br
- port: 443
hosts:
host1.com.br:
containers:
- container:80
redirect_ssl: false
ssl: true
- port: 8080
hosts:
host3.com.br:
containers:
- domain:8181
```
Then map this file to `/etc/haproxy/easyconfig.yml` in your EasyHAProxy container as:
```bash
docker run -d \
--name easy-haproxy-container \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /my/config.yml:/etc/haproxy/easyconfig.yml
-e EASYHAPROXY_DISCOVER="static" \
# + Environment Variables \
-p 80:80 \
-p 443:443 \
-p 1936:1936 \
--network easyhaproxy
byjg/easy-haproxy
```
You can find other informations on [docker label configuration](container-labels.md) and [docker container environment](docker-environment.md)
## Yaml Definition
```yaml
stats:
username: admin # Optional (default "admin")
password: password # If stats or stats.password is omitted, stats will be public with no password
port: 1936 # Optional (default 1936)
customerrors: true # Optional (default false)
ssl_mode: default # Optional
letsencrypt: { # Optional. If you enable `letsencrypt` will need to setu0p this,
# otherwise the certificate will be issued
"email": "acme@example.org"
}
easymapping:
- port: 80 # Listen port
mode: http # Optional. Default `http`. Can be http or tcp
hosts:
host1.com.br: # Hostname
containers:
- container:5000 # Endpoints of the hostname above (ip, dns, container, etc)
letsencrypt: true # Optional. it will request a letsencrypt certiticate
redirect_ssl: true # Optional. It will redirect this site to it SSL.
ssl: true # Optional. Inform this port will listen to SSL, instead of HTTP
clone_to_ssl: true # Optional. Default False. You clone these hosts to it equivalent SSL.
redirect:
www.host1.com.br: http://host1.com.br
```
*Note*: The only way to pass SSL certificates is to map the certificates to EasyHAProxy as a docker volume. Refer to the [SSL documentation](ssl.md) to learn how to do it.
----
[Open source ByJG](http://opensource.byjg.com)

96
docs/swarm.md Normal file
View file

@ -0,0 +1,96 @@
# Swarm
## Setup Docker EasyHAProxy
This method will use a docker swarm installation to discover the containers and configure the HAProxy.
The advantage of this method is that you can discover container in other nodes from cluster.
The only requirement is that containers and EasyHAProxy must be in the same docker swarm network in order to HAProxy be able direct the traffic to the containers.
e.g.:
```bash
docker create network easyhaproxy
```
And then deploy the EasyHAProxy stack:
```yaml
version: "3"
services:
haproxy:
image: byjg/easy-haproxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
deploy:
replicas: 1
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"
networks:
- easyhaproxy
networks:
easyhaproxy:
external: true
```
and then:
```bash
docker stack deploy --compose-file docker-compose.yml easyhaproxy
```
The mapping to `/var/run/docker.sock` is necessary to discover the docker containers and get the labels;
**Do not** add more than one replica for EasyHAProxy. To understand that see [limitations](limitations.md) page.
## Running containers
To make your containers "discoverable" by EasyHAProxy that is minimum configuration you need:
```yaml
version: "3"
services:
container:
image: my/image:tag
deploy:
replicas: 1
labels:
easyhaproxy.http.host: host1.local
easyhaproxy.http.port: 80
easyhaproxy.http.localport: 8080
networks:
- easyhaproxy
networks:
easyhaproxy:
external: true
```
Once the container is running EasyHAProxy will detect automatically and start to redirect all traffic from `example.org:80` to your container.
You don't need to expose any port in your container.
There a list of other parameters you can to configure your container. Please follow the [docker label configuration](container-labels.md)
## Setup the EasyHAProxy container
You can configure the behavior of the EasyHAProxy by setup specific environment variables. To get a list of the variables please follow the [docker container environment](docker-environment.md)
## More information
You can refer the [Docker Documentation](docker.md) to get other detailed instructions.
----
[Open source ByJG](http://opensource.byjg.com)

BIN
easyhaproxy_docker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
easyhaproxy_kubernetes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
easyhaproxy_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
easyhaproxy_static.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
easyhaproxy_swarm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -0,0 +1,80 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: easyhaproxy-ingress
name: container-example
namespace: default
spec:
rules:
- host: example.org
http:
paths:
- backend:
service:
name: container-example
port:
number: 8080
pathType: ImplementationSpecific
- host: www.example.org
http:
paths:
- backend:
service:
name: container-example
port:
number: 8080
pathType: ImplementationSpecific
---
apiVersion: v1
kind: Service
metadata:
name: container-example
namespace: default
spec:
ports:
- name: http
port: 8080
selector:
app: container-example
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: container-example
namespace: default
spec:
replicas: 1
revisionHistoryLimit: 10
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
selector:
matchLabels:
app: container-example
template:
metadata:
labels:
app: container-example
spec:
containers:
- name: container-example
image: byjg/static-httpserver
ports:
- containerPort: 8080
resources:
limits:
cpu: '0.05'
memory: '20Mi'
requests:
cpu: '0.05'
memory: '20Mi'
env:
- name: TITLE
value: "My Host Example"

View file

@ -0,0 +1,86 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: easyhaproxy-ingress
name: tls-example
namespace: default
spec:
tls:
- hosts:
- host2.local
secretName: host2-tls
rules:
- host: host2.local
http:
paths:
- backend:
service:
name: tls-example
port:
number: 8080
pathType: ImplementationSpecific
---
apiVersion: v1
kind: Secret
metadata:
name: host2-tls
namespace: default
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxVENDQXBHZ0F3SUJBZ0lVSWQ1Yjl0OXVxSDc4ZzAyRXpiV0Y2RktWdzNnd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1pERUxNQWtHQTFVRUJoTUNRbEl4RnpBVkJnTlZCQWdNRGxKcGJ5QmtaU0JLWVc1bGFYSnZNUmN3RlFZRApWUVFIREE1U2FXOGdaR1VnU21GdVpXbHliekVOTUFzR0ExVUVDZ3dFUVVOTlJURVVNQklHQTFVRUF3d0xhRzl6CmRESXViRzlqWVd3d0hoY05Nakl3T0RFMU1EUXlOekExV2hjTk1qTXdPREUxTURReU56QTFXakJrTVFzd0NRWUQKVlFRR0V3SkNVakVYTUJVR0ExVUVDQXdPVW1sdklHUmxJRXBoYm1WcGNtOHhGekFWQmdOVkJBY01EbEpwYnlCawpaU0JLWVc1bGFYSnZNUTB3Q3dZRFZRUUtEQVJCUTAxRk1SUXdFZ1lEVlFRRERBdG9iM04wTWk1c2IyTmhiRENDCkFTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTVNLdnJPYWhhdkNYbnZTRjUxMzFocG8KNms2NUM1N2pnUlE4NEZhRGo1TWJKT1ZsWVFWRnRNRzBYT2s3YStoaDV2MWZlNHdIMFI3STZGRG8wVjlzUytzcwprbzVic0VsYzF4WWxnNUhidUtxODl2UlNLZzZFRGx6dHgzQktiaTkxMlBtdDV2RkdOSjE2emN3NzdEVXJRSVhvCjRJL2I0YTNwbUJpV2o0M05vVElybVNXSHRzR3d3T2ozaUR2U3dlcWRZWEpJcjNocEhINXU2cG9oakRvUXZxRHoKSzZNdThwNm1oQ1VLTnM3S0ZKbk5Jbk5HMjVvUVQ2TzBuNE9HdG1nUmpMV29wZEVuT2hNa0tzZklvSTFYdGxYQgpMQkR2N2h1SUNrM3Q1eXd0ZkNReU8wOWtYN2xGSWdkNXJuNytNandINVdOZXFiUUp4dWFxam9YUW5OWlVnVXNDCkF3RUFBYU5UTUZFd0hRWURWUjBPQkJZRUZOaE1CRzhxNmEraUsybkVDd1ZUbjZCOUVYWk9NQjhHQTFVZEl3UVkKTUJhQUZOaE1CRzhxNmEraUsybkVDd1ZUbjZCOUVYWk9NQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEUVlKS29aSQpodmNOQVFFTEJRQURnZ0VCQUptdWR2eDgrcDVpSVVzVDhmbS9mYlZNMERBNnFXQUxEWVVKblRuM2o2THE0dnBmClBGQytxMUxtdVdmQlFNeXFLckhyUDNlNDkzRWN0WG9pU0taTzZpTjVkVkpJdXIwMk9qR3VpQUVjc1l1WTFuTG4KczlwaWlJK1VFd3hINnV4MU5hSFVuenNXYXVvQnZSaHpqWHZPNlNBVlNaSllhOWRZNW1pelhrbER5RE51RzVVMApsWHY5ZWdNR0JzeTBkRzZlRlhrVTVDUGR4V1U1NDB5STJzQ3RTQWo3eitXUlVENWs3Z0o3dFZvWTMvL2pIUVpHCjVTVFRtbTV0OWtwSVpUV2twdHlKb3M5b1pKRllNSVhxVzJGYzZ0eUxacFJwMzFSNzh0RHM2RVRJa1RvRGMwUlIKano2NnRoNkhJK1psZ0lCUWh3MDkraFlBaEJEZTkrRG1kL1N6UVpjPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0t
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2d0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktrd2dnU2xBZ0VBQW9JQkFRREVpcjZ6bW9XcndsNTcKMGhlZGQ5WWFhT3BPdVF1ZTQ0RVVQT0JXZzQrVEd5VGxaV0VGUmJUQnRGenBPMnZvWWViOVgzdU1COUVleU9oUQo2TkZmYkV2ckxKS09XN0JKWE5jV0pZT1IyN2lxdlBiMFVpb09oQTVjN2Nkd1NtNHZkZGo1cmVieFJqU2RlczNNCk8rdzFLMENGNk9DUDIrR3Q2WmdZbG8rTnphRXlLNWtsaDdiQnNNRG85NGc3MHNIcW5XRnlTSzk0YVJ4K2J1cWEKSVl3NkVMNmc4eXVqTHZLZXBvUWxDamJPeWhTWnpTSnpSdHVhRUUranRKK0RoclpvRVl5MXFLWFJKem9USkNySAp5S0NOVjdaVndTd1E3KzRiaUFwTjdlY3NMWHdrTWp0UFpGKzVSU0lIZWE1Ky9qSThCK1ZqWHFtMENjYm1xbzZGCjBKeldWSUZMQWdNQkFBRUNnZ0VCQUtjZWl0VlJPUVE1ZS9teFJSOUNmSzFzTkgvSDNOZTMvMVBrQjZYSXJGYWIKcUIzZXZFYXRaT3VvbjdBNk5LRWVUamwzN1NlK3BkU1ZaT1VYY3FDL0J6YnJhWnJlMytFaHJrcElqNzJBcFYrWQoyaXdaaVdWYWFKUWdJNHVaM21OQXc4UmFXSnNqNVMxYTlJOExET2lRNUlaNDVDbXZBQkRQSmVNU2N2SlN2UlJZCmU1TjBMNnN0cVM3WitJb3lHVktVZnAxaU5PMFl5eXdPVWlTa0lSWGdzY3VSWFpHWXBpR1BvbXNKK0pzMWVqelcKanlTdGxaSkVyNEwxMjg1ckdQcm1IcWpUd0ZkK2hHODBXYzQxNzl4TCtXUkU2SEJFVVpTaXk5NWZlNmtjUEhYWApCZ2lWWXRjRkttaUJpMmRUYnhsNGU5NHV0MjM5aTBIdGxKMVpKdExoK0JFQ2dZRUE4YTI5OEsyelhrSG9zeGhOCnRSckg3WGZNUFRrSEREZDNyeE0yMUxUK2ZJWHFpbkdVcDlMWWFEY2JqdVRQczhlMzN1S01kN1I2UTQweDg5eVcKSVhOa2EvVkwwUFhVZVY2N2FDVkxMcWdYRExHdWRsdUppbkgwWHZtSTBDbUJlY0ZTTXFJRm1RbGdxRVJvR0dzMwpVTWFjMHA4NzZUNFhrR1FRSmRmNjJiRnBFNFVDZ1lFQTBEQkgwUERsT3B3WGNjRGdYck1mYXlyOEhBaEkrRzVSCnlXUS8vOWlpcnRVODNjaHdJV3draDUzZUxMTXpMZ2RxSm5QaVd5YVVXNUJxem1ZdUQyM25oeGNRN1BOZElxT08KSDFzRTZ6cUxOc2h2NDZ0NVFLbGgxUTRxamQ3VXF0Z3JTclk2M1JYSkNNV1R3bk5NZURMdGo4Z2FLYmprckczUgpCTTJpbGR0NlVvOENnWUJYN05EVWxpMVNsb0gxWGxzdkQwNDdTOEZIYU03eWw5OTRGM0owVW1EZnBzemNqMVA0CjlwRjY0TW1xNC8zWXQwbGkwbU11VGIvSmdiM3hyWWdGSlhrY2VjS2FoRVZIM3JvcHVwK3Vtc0xBQUlpclVNUXEKVlNrRndKMFF0bmovZGVEVXdQTnVhT1g4Y2Q2NU81Q0ZWNnpJUjl4QkVERDhmQnNQMlpMT3ptZWZEUUtCZ1FERgptMjR2VnRoZC8xY0pkQ2dEKzBWeE5ZWERIZUlWWExGbzFTMGlMWUNOTG4zdGpabFJRQktVWHpaSmUzYXkwL3JmCnNOTkQ3YVNZSE1Za1R6eWRESmJjMVBvTnp4bXlEVWlUWHBPV3F5VUV4TS9mYkIxVlVQRTVoNDdBeHFkWjJvR04KRXRkZ2pwTVpMbUNJQzJTa0dzTCszTkpvazhVS0hkcHVFcm1tUUlNazVRS0JnUUNnRVdjWXRMWEMzWURZTUZkSQpVZ2NUZWJGcVNzM21MWWd1YjF4ZWtXM0lYUjJ5b200VjVmUVRMaUY3WWZuMmRwRFc0SWNNVTBVSkZZVnNVbGhLCmFHdGV0NFZtNU5uOCtNZ2hvdDV5QWpxTzl5QVVhdWI3d2dpZktJZTk5dFFLZDh1WnlDdkowaGh2bUREU2Z4NG0KQi9URWlGQU85OXlGNDlpU3hFVlNBUzZwcVE9PQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0t
type: kubernetes.io/tls
---
apiVersion: v1
kind: Service
metadata:
name: tls-example
namespace: default
spec:
ports:
- name: http
port: 8080
selector:
app: tls-example
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tls-example
namespace: default
spec:
replicas: 1
revisionHistoryLimit: 10
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
selector:
matchLabels:
app: tls-example
template:
metadata:
labels:
app: tls-example
spec:
containers:
- name: tls-example
image: byjg/static-httpserver
ports:
- containerPort: 8080
resources:
limits:
cpu: '0.05'
memory: '20Mi'
requests:
cpu: '0.05'
memory: '20Mi'
env:
- name: TITLE
value: "My Host Example"

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,50 @@
-----BEGIN CERTIFICATE-----
MIIDqTCCApGgAwIBAgIUId5b9t9uqH78g02EzbWF6FKVw3gwDQYJKoZIhvcNAQEL
BQAwZDELMAkGA1UEBhMCQlIxFzAVBgNVBAgMDlJpbyBkZSBKYW5laXJvMRcwFQYD
VQQHDA5SaW8gZGUgSmFuZWlybzENMAsGA1UECgwEQUNNRTEUMBIGA1UEAwwLaG9z
dDIubG9jYWwwHhcNMjIwODE1MDQyNzA1WhcNMjMwODE1MDQyNzA1WjBkMQswCQYD
VQQGEwJCUjEXMBUGA1UECAwOUmlvIGRlIEphbmVpcm8xFzAVBgNVBAcMDlJpbyBk
ZSBKYW5laXJvMQ0wCwYDVQQKDARBQ01FMRQwEgYDVQQDDAtob3N0Mi5sb2NhbDCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMSKvrOahavCXnvSF5131hpo
6k65C57jgRQ84FaDj5MbJOVlYQVFtMG0XOk7a+hh5v1fe4wH0R7I6FDo0V9sS+ss
ko5bsElc1xYlg5HbuKq89vRSKg6EDlztx3BKbi912Pmt5vFGNJ16zcw77DUrQIXo
4I/b4a3pmBiWj43NoTIrmSWHtsGwwOj3iDvSweqdYXJIr3hpHH5u6pohjDoQvqDz
K6Mu8p6mhCUKNs7KFJnNInNG25oQT6O0n4OGtmgRjLWopdEnOhMkKsfIoI1XtlXB
LBDv7huICk3t5ywtfCQyO09kX7lFIgd5rn7+MjwH5WNeqbQJxuaqjoXQnNZUgUsC
AwEAAaNTMFEwHQYDVR0OBBYEFNhMBG8q6a+iK2nECwVTn6B9EXZOMB8GA1UdIwQY
MBaAFNhMBG8q6a+iK2nECwVTn6B9EXZOMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
hvcNAQELBQADggEBAJmudvx8+p5iIUsT8fm/fbVM0DA6qWALDYUJnTn3j6Lq4vpf
PFC+q1LmuWfBQMyqKrHrP3e493EctXoiSKZO6iN5dVJIur02OjGuiAEcsYuY1nLn
s9piiI+UEwxH6ux1NaHUnzsWauoBvRhzjXvO6SAVSZJYa9dY5mizXklDyDNuG5U0
lXv9egMGBsy0dG6eFXkU5CPdxWU540yI2sCtSAj7z+WRUD5k7gJ7tVoY3//jHQZG
5STTmm5t9kpIZTWkptyJos9oZJFYMIXqW2Fc6tyLZpRp31R78tDs6ETIkToDc0RR
jz66th6HI+ZlgIBQhw09+hYAhBDe9+Dmd/SzQZc=
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDEir6zmoWrwl57
0hedd9YaaOpOuQue44EUPOBWg4+TGyTlZWEFRbTBtFzpO2voYeb9X3uMB9EeyOhQ
6NFfbEvrLJKOW7BJXNcWJYOR27iqvPb0UioOhA5c7cdwSm4vddj5rebxRjSdes3M
O+w1K0CF6OCP2+Gt6ZgYlo+NzaEyK5klh7bBsMDo94g70sHqnWFySK94aRx+buqa
IYw6EL6g8yujLvKepoQlCjbOyhSZzSJzRtuaEE+jtJ+DhrZoEYy1qKXRJzoTJCrH
yKCNV7ZVwSwQ7+4biApN7ecsLXwkMjtPZF+5RSIHea5+/jI8B+VjXqm0Ccbmqo6F
0JzWVIFLAgMBAAECggEBAKceitVROQQ5e/mxRR9CfK1sNH/H3Ne3/1PkB6XIrFab
qB3evEatZOuon7A6NKEeTjl37Se+pdSVZOUXcqC/BzbraZre3+EhrkpIj72ApV+Y
2iwZiWVaaJQgI4uZ3mNAw8RaWJsj5S1a9I8LDOiQ5IZ45CmvABDPJeMScvJSvRRY
e5N0L6stqS7Z+IoyGVKUfp1iNO0YyywOUiSkIRXgscuRXZGYpiGPomsJ+Js1ejzW
jyStlZJEr4L1285rGPrmHqjTwFd+hG80Wc4179xL+WRE6HBEUZSiy95fe6kcPHXX
BgiVYtcFKmiBi2dTbxl4e94ut239i0HtlJ1ZJtLh+BECgYEA8a298K2zXkHosxhN
tRrH7XfMPTkHDDd3rxM21LT+fIXqinGUp9LYaDcbjuTPs8e33uKMd7R6Q40x89yW
IXNka/VL0PXUeV67aCVLLqgXDLGudluJinH0XvmI0CmBecFSMqIFmQlgqERoGGs3
UMac0p876T4XkGQQJdf62bFpE4UCgYEA0DBH0PDlOpwXccDgXrMfayr8HAhI+G5R
yWQ//9iirtU83chwIWwkh53eLLMzLgdqJnPiWyaUW5BqzmYuD23nhxcQ7PNdIqOO
H1sE6zqLNshv46t5QKlh1Q4qjd7UqtgrSrY63RXJCMWTwnNMeDLtj8gaKbjkrG3R
BM2ildt6Uo8CgYBX7NDUli1SloH1XlsvD047S8FHaM7yl994F3J0UmDfpszcj1P4
9pF64Mmq4/3Yt0li0mMuTb/Jgb3xrYgFJXkcecKahEVH3ropup+umsLAAIirUMQq
VSkFwJ0Qtnj/deDUwPNuaOX8cd65O5CFV6zIR9xBEDD8fBsP2ZLOzmefDQKBgQDF
m24vVthd/1cJdCgD+0VxNYXDHeIVXLFo1S0iLYCNLn3tjZlRQBKUXzZJe3ay0/rf
sNND7aSYHMYkTzydDJbc1PoNzxmyDUiTXpOWqyUExM/fbB1VUPE5h47AxqdZ2oGN
EtdgjpMZLmCIC2SkGsL+3NJok8UKHdpuErmmQIMk5QKBgQCgEWcYtLXC3YDYMFdI
UgcTebFqSs3mLYgub1xekW3IXR2yom4V5fQTLiF7Yfn2dpDW4IcMU0UJFYVsUlhK
aGtet4Vm5Nn8+Mghot5yAjqO9yAUaub7wgifKIe99tQKd8uZyCvJ0hhvmDDSfx4m
B/TEiFAO99yF49iSxEVSAS6pqQ==
-----END PRIVATE KEY-----

View file

@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View file

@ -0,0 +1,24 @@
apiVersion: v2
name: easyhaproxy
description: "EasyHAProxy - A service discovery backed on HAProxy"
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.3
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "master"

View file

@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "easyhaproxy.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "easyhaproxy.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "easyhaproxy.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "easyhaproxy.labels" -}}
helm.sh/chart: {{ include "easyhaproxy.chart" . }}
{{ include "easyhaproxy.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "easyhaproxy.selectorLabels" -}}
app.kubernetes.io/name: {{ include "easyhaproxy.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "easyhaproxy.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "easyhaproxy.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View file

@ -0,0 +1,67 @@
{{- if .Values.serviceAccount.create -}}
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ include "easyhaproxy.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "easyhaproxy.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
- apiGroups:
- ""
resources:
# - configmaps
# - endpoints
# - nodes
- pods
- services
- namespaces
# - events
- serviceaccounts
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses
# - ingresses/status
# - ingressclasses
verbs:
- get
- list
- watch
# - apiGroups:
# - "extensions"
# - "networking.k8s.io"
# resources:
# - ingresses/status
# verbs:
# - update
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- list
# - watch
# - create
# - patch
# - update
# - apiGroups:
# - "discovery.k8s.io"
# resources:
# - endpointslices
# verbs:
# - get
# - list
# - watch
{{- end }}

View file

@ -0,0 +1,22 @@
{{- if .Values.serviceAccount.create -}}
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: {{ include "easyhaproxy.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "easyhaproxy.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "easyhaproxy.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ include "easyhaproxy.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}

View file

@ -0,0 +1,71 @@
---
apiVersion: apps/v1
kind: {{ ternary "Deployment" "DaemonSet" .Values.service.create }}
metadata:
name: {{ include "easyhaproxy.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "easyhaproxy.labels" . | nindent 4 }}
spec:
selector:
matchLabels:
{{- include "easyhaproxy.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "easyhaproxy.selectorLabels" . | nindent 8 }}
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: {{ .Values.masterNode.label }}
operator: In
values:
{{- toYaml .Values.masterNode.values | nindent 18 }}
serviceAccountName: {{ include "easyhaproxy.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 80
{{ if not .Values.service.create }}hostPort: {{ .Values.binding.ports.http }}{{ end }}
- name: https
containerPort: 443
{{ if not .Values.service.create }}hostPort: {{ .Values.binding.ports.https }}{{ end }}
- name: stats
containerPort: 1936
{{ if not .Values.service.create }}hostPort: {{ .Values.binding.ports.stats }}{{ end }}
{{- range $port := .Values.binding.additionalPorts }}
- name: extra{{ $port }}
containerPort: {{ $port }}
{{ if not $.Values.service.create }}hostPort: {{ $port }}{{ end }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
env:
- name: EASYHAPROXY_DISCOVER
value: kubernetes
- name: HAPROXY_USERNAME
value: {{ .Values.easyhaproxy.stats.username }}
- name: HAPROXY_PASSWORD
value: {{ .Values.easyhaproxy.stats.password }}
- name: EASYHAPROXY_REFRESH_CONF
value: {{ .Values.easyhaproxy.refresh | quote }}
- name: HAPROXY_CUSTOMERRORS
value: {{ .Values.easyhaproxy.customErrors | quote}}
- name: EASYHAPROXY_SSL_MODE
value: {{ .Values.easyhaproxy.sslMode }}
- name: EASYHAPROXY_LOG_LEVEL
value: {{ .Values.easyhaproxy.logLevel.easyhaproxy }}
- name: HAPROXY_LOG_LEVEL
value: {{ .Values.easyhaproxy.logLevel.haproxy }}
- name: CERTBOT_LOG_LEVEL
value: {{ .Values.easyhaproxy.logLevel.certbot }}

View file

@ -0,0 +1,34 @@
{{ if .Values.service.create }}
---
apiVersion: v1
kind: Service
metadata:
metadata:
name: {{ include "easyhaproxy.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "easyhaproxy.labels" . | nindent 4 }}
annotations:
{{- toYaml .Values.service.annotations | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- name: http
port: 80
{{ if eq .Values.service.type "NodePort" }}nodePort: {{ .Values.binding.ports.http }}{{ end }}
- name: https
port: 443
{{ if eq .Values.service.type "NodePort" }}nodePort: {{ .Values.binding.ports.https }}{{ end }}
- name: stats
port: 1936
{{ if eq .Values.service.type "NodePort" }}nodePort: {{ .Values.binding.ports.stats }}{{ end }}
{{- range $port := .Values.binding.additionalPorts }}
- name: extra{{ $port }}
port: {{ $port }}
{{ if eq $.Values.service.type "NodePort" }}nodePort: {{ $port }}{{ end }}
{{- end }}
selector:
{{- include "easyhaproxy.selectorLabels" . | nindent 4 }}
{{ end }}

View file

@ -0,0 +1,13 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "easyhaproxy.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "easyhaproxy.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View file

@ -0,0 +1,19 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"required": [],
"properties": {
"service": {
"type": "object",
"required": [],
"properties": {
"create": {
"type": "boolean"
},
"type": {
"type": "string",
"enum": ["ClusterIP", "NodePort"]
}
}
}
}
}

View file

@ -0,0 +1,77 @@
# Default values for easyhaproxy.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: byjg/easy-haproxy
pullPolicy: Always # IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
service:
create: false # If false, it will create a Daemonset with hostPort. The easiest.
type: ClusterIP # or NodePort
annotations: {}
binding:
ports:
http: 80
https: 443
stats: 1936
additionalPorts: []
serviceAccount:
create: true
annotations: {}
name: ""
podAnnotations: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
resources: {}
# requests:
# cpu: "100m"
# memory: "128Mi"
# limits:
# cpu: 100m
# memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}
easyhaproxy:
stats:
username: admin
password: password
refresh: "10"
customErrors: "true"
sslMode: loose
logLevel:
certbot: DEBUG
easyhaproxy: DEBUG
haproxy: DEBUG
# Make sure to create this
masterNode:
label: easyhaproxy/node
values:
- master

View file

@ -1,4 +0,0 @@
pyyaml
docker
jinja2
pytest

View file

@ -27,7 +27,7 @@ class DockerLabelHandler:
def get_bool(self, label, default_value = False): def get_bool(self, label, default_value = False):
if self.has_label(label): if self.has_label(label):
return self.__data[label].lower() in ["True", "true", "1", "yes"] return self.__data[label].lower() in ["true", "1", "yes"]
return default_value return default_value
def get_json(self, label, default_value = {}): def get_json(self, label, default_value = {}):
@ -46,24 +46,21 @@ class DockerLabelHandler:
class HaproxyConfigGenerator: class HaproxyConfigGenerator:
def __init__(self, mapping, ssl_cert_folder="/certs"): def __init__(self, mapping):
self.mapping = mapping self.mapping = mapping
self.mapping.setdefault("ssl_mode", 'default') self.mapping.setdefault("ssl_mode", 'default')
self.mapping.setdefault("letsencrypt", {"email": ""}) self.mapping.setdefault("letsencrypt", {"email": ""})
self.mapping["ssl_mode"] = self.mapping["ssl_mode"].lower() self.mapping["ssl_mode"] = self.mapping["ssl_mode"].lower()
self.label = DockerLabelHandler(mapping['lookup_label'] if 'lookup_label' in mapping else "easyhaproxy") self.label = DockerLabelHandler(mapping['lookup_label'] if 'lookup_label' in mapping else "easyhaproxy")
self.ssl_cert_haproxy = ssl_cert_folder + "/haproxy"
self.ssl_cert_letsecncrypt = ssl_cert_folder + "/letsencrypt"
self.letsencrypt_hosts = [] self.letsencrypt_hosts = []
os.makedirs(self.ssl_cert_haproxy, exist_ok=True) self.serving_hosts = []
os.makedirs(self.ssl_cert_letsecncrypt, exist_ok=True) self.certs = {}
def generate(self, line_list = []): def generate(self, container_metadata = {}):
self.mapping.setdefault("easymapping", []) self.mapping.setdefault("easymapping", [])
# static? if container_metadata != {}:
if len(line_list) > 0: self.mapping["easymapping"] = self.parse(container_metadata)
self.mapping["easymapping"] = self.parse(line_list)
file_loader = FileSystemLoader('templates') file_loader = FileSystemLoader('templates')
env = Environment(loader=file_loader) env = Environment(loader=file_loader)
@ -74,15 +71,11 @@ class HaproxyConfigGenerator:
return template.render(data=self.mapping) return template.render(data=self.mapping)
def parse(self, line_list): def parse(self, container_metadata):
easymapping = dict() easymapping = dict()
for line in line_list: for container in container_metadata:
line = line.strip() d = container_metadata[container]
i = line.find("=")
container = line[:i]
json_str = line[i+1:]
d = json.loads(json_str)
# Extract the definitions dynamically # Extract the definitions dynamically
definitions = {} definitions = {}
@ -97,7 +90,7 @@ class HaproxyConfigGenerator:
self.label.set_data(d) self.label.set_data(d)
# Parse each definition found. # Parse each definition found.
for definition in definitions.keys(): for definition in sorted(definitions.keys()):
mode = self.label.get( mode = self.label.get(
self.label.create([definition, "mode"]), self.label.create([definition, "mode"]),
"http" "http"
@ -117,6 +110,9 @@ class HaproxyConfigGenerator:
self.label.create([definition, "letsencrypt"]), self.label.create([definition, "letsencrypt"]),
False False
) and self.mapping["letsencrypt"]["email"] != "" ) and self.mapping["letsencrypt"]["email"] != ""
clone_to_ssl = self.label.get_bool(
self.label.create([definition, "clone_to_ssl"])
)
if port not in easymapping: if port not in easymapping:
easymapping[port] = { easymapping[port] = {
@ -138,8 +134,9 @@ class HaproxyConfigGenerator:
"" ""
) )
for hostname in d[host_label].split(","): for hostname in sorted(d[host_label].split(",")):
hostname = hostname.strip() hostname = hostname.strip()
self.serving_hosts.append("%s:%s" % (hostname, port))
easymapping[port]["hosts"].setdefault(hostname, {}) easymapping[port]["hosts"].setdefault(hostname, {})
easymapping[port]["hosts"][hostname].setdefault("containers", []) easymapping[port]["hosts"][hostname].setdefault("containers", [])
easymapping[port]["hosts"][hostname].setdefault("letsencrypt", False) easymapping[port]["hosts"][hostname].setdefault("letsencrypt", False)
@ -153,7 +150,7 @@ class HaproxyConfigGenerator:
self.label.create([definition, "redirect"]) self.label.create([definition, "redirect"])
) )
if letsencrypt: if letsencrypt or clone_to_ssl:
if "443" not in easymapping: if "443" not in easymapping:
easymapping["443"] = { easymapping["443"] = {
"mode": "http", "mode": "http",
@ -166,21 +163,17 @@ class HaproxyConfigGenerator:
easymapping["443"]["hosts"][hostname]["letsencrypt"] = False easymapping["443"]["hosts"][hostname]["letsencrypt"] = False
easymapping["443"]["hosts"][hostname]["redirect_ssl"] = False easymapping["443"]["hosts"][hostname]["redirect_ssl"] = False
easymapping["443"]["ssl"] = True easymapping["443"]["ssl"] = True
self.letsencrypt_hosts.append(hostname) if hostname not in self.letsencrypt_hosts else self.letsencrypt_hosts self.letsencrypt_hosts.append(hostname) if letsencrypt and hostname not in self.letsencrypt_hosts else self.letsencrypt_hosts
# handle SSL # handle SSL
ssl_label = self.label.create([definition, "sslcert"]) ssl_label = self.label.create([definition, "sslcert"])
if self.label.has_label(ssl_label): if self.label.has_label(ssl_label):
filename = "{}/{}.pem".format( filename = "{}.pem".format(d[host_label])
self.ssl_cert_haproxy, d[host_label] easymapping[port]["ssl"] = True if not clone_to_ssl else False
) self.certs[filename] = base64.b64decode(d[ssl_label]).decode('ascii')
easymapping[port]["ssl"] = True
with open(filename, 'wb') as file:
file.write(
base64.b64decode(d[ssl_label])
)
if self.label.get_bool(self.label.create([definition, "ssl"])): if self.label.get_bool(self.label.create([definition, "ssl"])):
easymapping[port]["ssl"] = True easymapping[port]["ssl"] = True if not clone_to_ssl else False
return easymapping.values() return easymapping.values()

232
src/functions/__init__.py Normal file
View file

@ -0,0 +1,232 @@
from datetime import datetime
from multiprocessing import Process, Lock
import subprocess
import shlex
import time
import os
class Functions:
HAPROXY_LOG="HAPROXY"
EASYHAPROXY_LOG="EASYHAPROXY"
CERTBOT_LOG="CERTBOT"
INIT_LOG="INIT"
TRACE = "TRACE"
DEBUG = "DEBUG"
INFO = "INFO"
WARN = "WARN"
ERROR = "ERROR"
FATAL = "FATAL"
debug_log = None
@staticmethod
def skip_log(source, log_level_str):
level = os.getenv("%s_LOG_LEVEL" % (source.upper()), "").upper()
level_importance = {
Functions.TRACE: 0,
Functions.DEBUG: 1,
Functions.INFO: 2,
Functions.WARN: 3,
Functions.ERROR: 4,
Functions.FATAL: 5
}
level_required = 1 if level not in level_importance else level_importance[level]
level_asked = 1 if log_level_str.upper() not in level_importance else level_importance[log_level_str.upper()]
return level_asked < level_required
@staticmethod
def load(filename):
with open(filename, 'r') as content_file:
return content_file.read()
@staticmethod
def save(filename, contents):
with open(filename, 'w') as file:
file.write(contents)
@staticmethod
def log(source, level, message):
if message is None or message == "":
return
if Functions.skip_log(source, level):
return
if not isinstance(message, (list, tuple)):
message = [message]
for line in message:
log = "[%s] %s [%s]: %s" % (source, datetime.now().strftime("%x %X"), level, line.rstrip())
print(log)
if Functions.debug_log is not None:
Functions.debug_log.append(log)
@staticmethod
def run_bash(source, command, log_output=True, return_result=True):
if not isinstance(command, (list, tuple)):
command = shlex.split(command)
try:
process = subprocess.Popen(command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
output = []
while True:
line = process.stdout.readline().rstrip()
output.append(line) if return_result else None
Functions.log(source, Functions.INFO, line) if log_output else None
Functions.log(source, Functions.WARN, process.stderr.readline())
return_code = process.poll()
if return_code is not None:
lines = []
for line in process.stdout.readlines():
output.append(line.rstrip()) if return_result else None
lines.append(line.rstrip())
Functions.log(source, Functions.INFO, lines) if log_output else None
Functions.log(source, Functions.WARN, process.stderr.readlines())
break
return output
except Exception as e:
Functions.log(source, Functions.ERROR, "%s" % (e))
class Consts:
easyhaproxy_config = "/etc/haproxy/easyconfig.yml"
haproxy_config = "/etc/haproxy/haproxy.cfg"
certs_letsencrypt = "/certs/letsencrypt"
certs_haproxy = "/certs/haproxy"
class DaemonizeHAProxy:
def __init__(self):
self.process = None
self.thread = None
def haproxy(self, action):
if action == "start":
self.__prepare("/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /var/run/haproxy.sock")
else:
pid = "".join(Functions().run_bash(Functions.HAPROXY_LOG, "cat /run/haproxy.pid", log_output=False))
self.__prepare("/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -x /var/run/haproxy.sock -sf %s" % (pid))
if self.process is None:
return
self.thread = Process(target=self.__start, args=())
self.thread.start()
def __prepare(self, command):
source = Functions.HAPROXY_LOG
if not isinstance(command, (list, tuple)):
command = shlex.split(command)
try:
self.process = subprocess.Popen(command,
shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
bufsize=-1,
universal_newlines=True)
except Exception as e:
Functions.log(source, Functions.ERROR, "%s" % (e))
def __start(self):
source = Functions.HAPROXY_LOG
try:
with self.process.stdout:
for line in iter(self.process.stdout.readline, b''):
Functions.log(source, Functions.INFO, line)
returncode = self.process.wait()
Functions.log(source, Functions.DEBUG, "Return code %s" % (returncode))
except Exception as e:
Functions.log(source, Functions.ERROR, "%s" % (e))
def is_alive(self):
return self.thread.is_alive()
def kill(self):
self.process.kill()
self.thread.kill()
def terminate(self):
self.process.terminate()
self.thread.terminate()
class Certbot:
def __init__(self, certs, email):
self.certs = certs
self.email = email
def check_certificates(self, hosts):
if self.email == "" or len(hosts) == 0:
return False
try:
request_certs = []
renew_certs = []
current_time = time.time()
for host in hosts:
filename = "%s/%s.pem" % (self.certs, host)
host_arg = '-d %s' % (host)
if not os.path.exists(filename):
Functions.log(Functions.CERTBOT_LOG, Functions.DEBUG, "Request new certificate for %s" % (host))
request_certs.append(host_arg)
else:
creation_time = os.path.getctime(filename)
if (current_time - creation_time) // (24 * 3600) > 90:
Functions.log(Functions.CERTBOT_LOG, Functions.DEBUG, "Request expired certificate for %s" % (host))
request_certs.append(host_arg)
if (current_time - creation_time) // (24 * 3600) >= 45:
Functions.log(Functions.CERTBOT_LOG, Functions.DEBUG, "Renew certificate for %s" % (host))
renew_certs.append(host_arg)
certbot_certonly = ('/usr/bin/certbot certonly '
' --standalone'
' --preferred-challenges http'
' --http-01-port 2080'
' --agree-tos'
' --issuance-timeout 90'
' --no-eff-email'
' --non-interactive'
' --max-log-backups=0'
' %s --email %s' % (' '.join(request_certs), self.email)
)
ret_reload = False
if len(request_certs) > 0:
Functions.run_bash(Functions.CERTBOT_LOG, certbot_certonly, return_result=False)
ret_reload = True
if len(renew_certs) > 0:
Functions.run_bash(Functions.CERTBOT_LOG, "/usb/bin/certbot renew", return_result=False)
ret_reload = True
if ret_reload:
self.find_live_certificates()
return ret_reload
except Exception as e:
Functions.log(Functions.CERTBOT_LOG, Functions.ERROR, "%s" % (e))
return False
def merge_certificate(self, cert, key, filename):
Functions.save(filename, cert + key)
def find_live_certificates(self):
letsencrypt_certs = "/etc/letsencrypt/live/"
for item in os.listdir(letsencrypt_certs):
path = os.path.join(letsencrypt_certs, item)
if os.path.isdir(path):
cert = Functions.load(os.path.join(path, "fullchain.pem"))
key = Functions.load(os.path.join(path, "privkey.pem"))
filename = "%s/%s.pem" % (self.certs, item)
self.merge_certificate(cert, key, filename)

73
src/main.py Normal file
View file

@ -0,0 +1,73 @@
from functions import Functions, DaemonizeHAProxy, Certbot, Consts
from processor import ProcessorInterface
import os
import time
from deepdiff import DeepDiff
def start():
processor_obj = ProcessorInterface.factory(os.getenv("EASYHAPROXY_DISCOVER"))
if processor_obj is None:
exit(1)
os.makedirs(Consts.certs_letsencrypt, exist_ok=True)
os.makedirs(Consts.certs_haproxy, exist_ok=True)
processor_obj.save_config(Consts.haproxy_config)
processor_obj.save_certs(Consts.certs_haproxy)
letsencrypt_certs_found = processor_obj.get_letsencrypt_hosts()
Functions.log(Functions.EASYHAPROXY_LOG, Functions.DEBUG, 'Found hosts: %s' % ", ".join(processor_obj.get_hosts())) # Needs to after save_config
Functions.log(Functions.EASYHAPROXY_LOG, Functions.TRACE, 'Object Found: %s' % (processor_obj.get_parsed_object()))
old_haproxy = None
haproxy = DaemonizeHAProxy()
haproxy.haproxy("start")
certbot = Certbot(Consts.certs_letsencrypt, os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL"))
while True:
time.sleep(10)
if old_haproxy is not None:
old_haproxy.kill()
old_haproxy = None
try:
old_parsed = processor_obj.get_parsed_object()
processor_obj.refresh()
if DeepDiff(old_parsed, processor_obj.get_parsed_object()) != {} or not haproxy.is_alive():
Functions.log(Functions.EASYHAPROXY_LOG, Functions.DEBUG, 'New configuration found. Reloading...')
Functions.log(Functions.EASYHAPROXY_LOG, Functions.TRACE, 'Object Found: %s' % (processor_obj.get_parsed_object()))
processor_obj.save_config(Consts.haproxy_config)
processor_obj.save_certs(Consts.certs_haproxy)
letsencrypt_certs_found = processor_obj.get_letsencrypt_hosts()
Functions.log(Functions.EASYHAPROXY_LOG, Functions.DEBUG, 'Found hosts: %s' % ", ".join(processor_obj.get_hosts())) # Needs to after save_config
old_haproxy = haproxy
haproxy = DaemonizeHAProxy()
haproxy.haproxy("reload")
old_haproxy.terminate()
certbot.check_certificates(letsencrypt_certs_found)
except Exception as e:
Functions.log(Functions.EASYHAPROXY_LOG, Functions.FATAL, "Err: %s" % (e))
Functions.log(Functions.EASYHAPROXY_LOG, Functions.DEBUG, 'Heartbeat')
def main():
Functions.run_bash(Functions.INIT_LOG, '/usr/sbin/haproxy -v')
Functions.log(Functions.INIT_LOG, Functions.INFO, " _ ")
Functions.log(Functions.INIT_LOG, Functions.INFO, " ___ __ _ ____ _ ___| |_ __ _ _ __ _ _ _____ ___ _ ")
Functions.log(Functions.INIT_LOG, Functions.INFO, "/ -_) _` (_-< || |___| ' \/ _` | '_ \ '_/ _ \ \ / || |")
Functions.log(Functions.INIT_LOG, Functions.INFO, "\___\__,_/__/\_, | |_||_\__,_| .__/_| \___/_\_\\_, |")
Functions.log(Functions.INIT_LOG, Functions.INFO, " |__/ |_| |__/ ")
Functions.log(Functions.INIT_LOG, Functions.INFO, "Release: %s" % (os.getenv("RELEASE_VERSION")))
Functions.log(Functions.INIT_LOG, Functions.DEBUG, 'Environment:')
for name, value in os.environ.items():
if "HAPROXY" in name:
Functions.log(Functions.INIT_LOG, Functions.DEBUG, "- {0}: {1}".format(name, value))
start()
if __name__ == '__main__':
main()

236
src/processor/__init__.py Normal file
View file

@ -0,0 +1,236 @@
from easymapping import HaproxyConfigGenerator
from functions import Functions, Consts
import yaml
import sys
import os
import json
import base64
import docker
from kubernetes import client, config
from kubernetes.client.rest import ApiException
class ContainerEnv:
@staticmethod
def read():
env_vars = {
"customerrors": True if os.getenv("HAPROXY_CUSTOMERRORS") == "true" else False,
"ssl_mode": os.getenv("EASYHAPROXY_SSL_MODE").lower() if os.getenv("EASYHAPROXY_SSL_MODE") else 'default'
}
if os.getenv("HAPROXY_PASSWORD"):
env_vars["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",
}
env_vars["lookup_label"] = os.getenv("EASYHAPROXY_LABEL_PREFIX") if os.getenv("EASYHAPROXY_LABEL_PREFIX") else "easyhaproxy"
if (os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL")):
env_vars["letsencrypt"] = {
"email": os.getenv("EASYHAPROXY_LETSENCRYPT_EMAIL")
}
return env_vars
class ProcessorInterface:
static_file = "/etc/haproxy/easyconfig.yml"
def __init__(self, filename = None):
self.filename = filename
self.refresh()
@staticmethod
def factory(mode):
if mode == "static":
return Static(ProcessorInterface.static_file)
elif mode == "docker":
return Docker()
elif mode == "swarm":
return Swarm()
elif mode == "kubernetes":
return Kubernetes()
else:
Functions.log("EASYHAPROXY", Functions.FATAL, "Expected mode to be 'static', 'docker', 'swarm' or 'kubernetes'. I got '%s'" % (mode))
return None
def refresh(self):
self.letsencrypt_hosts = None
self.parsed_object = None
self.cfg = None
self.hosts = None
self.inspect_network()
self.parse()
def inspect_network(self):
#Abstract
pass
def parse(self):
self.cfg = HaproxyConfigGenerator(ContainerEnv.read())
def get_letsencrypt_hosts(self):
return self.letsencrypt_hosts
def get_hosts(self):
return self.hosts
def get_parsed_object(self):
return self.parsed_object
def get_certs(self, key = None):
if key is None:
return self.cfg.certs
else:
return None if key not in self.cfg.certs else self.cfg.certs[key]
def get_haproxy_conf(self):
conf = self.cfg.generate(self.parsed_object)
self.letsencrypt_hosts = self.cfg.letsencrypt_hosts
self.hosts = self.cfg.serving_hosts
return conf
def save_config(self, filename):
Functions.save(filename, self.get_haproxy_conf())
def save_certs(self, path):
for cert in self.get_certs():
Functions.save("{0}/{1}".format(path, cert), self.get_certs(cert))
class Static(ProcessorInterface):
def inspect_network(self):
self.parsed_object = {}
self.static_content = None
def get_parsed_object(self):
return self.static_content["easymapping"] if "easymapping" in self.static_content else []
def get_hosts(self):
hosts = []
for object in self.get_parsed_object():
for host in object["hosts"].keys():
hosts.append("%s:%s" % (host, object["port"]))
return hosts
def parse(self):
self.static_content = yaml.load(Functions.load(self.filename), Loader=yaml.FullLoader)
self.cfg = HaproxyConfigGenerator(self.static_content)
class Docker(ProcessorInterface):
def __init__(self, filename = None):
self.client = docker.from_env()
super().__init__()
def inspect_network(self):
self.parsed_object = {}
for container in self.client.containers.list():
self.parsed_object[container.name] = container.labels
class Swarm(ProcessorInterface):
def __init__(self, filename = None):
self.client = docker.from_env()
super().__init__()
def inspect_network(self):
self.parsed_object = {}
for container in self.client.services.list():
self.parsed_object[container.attrs["Spec"]["Name"]] = container.attrs["Spec"]["Labels"]
class Kubernetes(ProcessorInterface):
def __init__(self, filename = None):
config.load_incluster_config()
config.verify_ssl = False
self.api_instance = client.CoreV1Api()
self.v1 = client.NetworkingV1Api()
self.cert_cache = {}
super().__init__()
def _check_annotation(self, annotations, key):
if key not in annotations:
return None
return annotations[key]
def inspect_network(self):
ret = self.v1.list_ingress_for_all_namespaces(watch=False)
self.parsed_object = {}
for ingress in ret.items:
if ingress.metadata.annotations['kubernetes.io/ingress.class'] != "easyhaproxy-ingress":
continue
ssl_hosts = []
letsencrypt = self._check_annotation(ingress.metadata.annotations, "easyhaproxy.letsencrypt")
redirect_ssl = self._check_annotation(ingress.metadata.annotations, "easyhaproxy.redirect_ssl")
redirect = self._check_annotation(ingress.metadata.annotations, "easyhaproxy.redirect")
mode = self._check_annotation(ingress.metadata.annotations, "easyhaproxy.mode")
listen_port = self._check_annotation(ingress.metadata.annotations, "easyhaproxy.listen_port")
if listen_port is None:
listen_port = 80
data = {}
data["creation_timestamp"] = ingress.metadata.creation_timestamp.strftime("%x %X")
data["resource_version"] = ingress.metadata.resource_version
data["namespace"] = ingress.metadata.namespace
ingress_name = ingress.metadata.namespace
if ingress.spec.tls is not None:
for tls in ingress.spec.tls:
try:
secret = self.api_instance.read_namespaced_secret(tls.secret_name, ingress.metadata.namespace)
if "tls.crt" not in secret.data or "tls.key" not in secret.data:
continue
if tls.secret_name not in self.cert_cache or self.cert_cache[tls.secret_name] != secret.data:
self.cert_cache[tls.secret_name] = secret.data
Functions.save(
"{0}/{1}.pem".format(Consts.certs_haproxy, tls.secret_name),
base64.b64decode(secret.data["tls.crt"]).decode('ascii') + "\n" + base64.b64decode(secret.data["tls.key"]).decode('ascii')
)
ssl_hosts.extend(tls.hosts)
except Exception as e:
Functions.log("EASYHAPROXY", Functions.WARN, "Ingress %s - Get secret failed: '%s'" % (ingress_name, e))
Functions.log("EASYHAPROXY", Functions.TRACE, "Ingress %s - SSL Hosts found '%s'" % (ingress_name, ssl_hosts))
for rule in ingress.spec.rules:
rule_data = {}
port_number = rule.http.paths[0].backend.service.port.number
definition = "easyhaproxy.%s_%s" % (rule.host.replace(".", "-"), port_number)
rule_data["%s.host" % (definition)] = rule.host
rule_data["%s.port" % (definition)] = listen_port
rule_data["%s.localport" % (definition)] = port_number
if rule.host in ssl_hosts:
rule_data["%s.clone_to_ssl" % (definition)] = 'true'
if redirect_ssl is not None:
rule_data["%s.redirect_ssl" % (definition)] = redirect_ssl
if letsencrypt is not None:
rule_data["%s.letsencrypt" % (definition)] = letsencrypt
if redirect is not None:
rule_data["%s.redirect" % (definition)] = redirect
if mode is not None:
rule_data["%s.mode" % (definition)] = mode
service_name = rule.http.paths[0].backend.service.name
try:
api_response = self.api_instance.read_namespaced_service(service_name, ingress.metadata.namespace)
cluster_ip = api_response.spec.cluster_ip
except ApiException as e:
cluster_ip = None
Functions.log("EASYHAPROXY", Functions.WARN, "Ingress %s - Service %s - Failed: '%s'" % (ingress_name, service_name, e))
if cluster_ip is not None:
if cluster_ip not in self.parsed_object.keys():
self.parsed_object[cluster_ip] = data
self.parsed_object[cluster_ip].update(rule_data)

7
src/requirements.txt Normal file
View file

@ -0,0 +1,7 @@
pyyaml
docker
jinja2
pytest
docker
kubernetes
deepdiff

View file

@ -78,3 +78,4 @@ backend srv_{{ host }}
backend letsencrypt_backend backend letsencrypt_backend
mode http mode http
server certbot 127.0.0.1:2080 server certbot 127.0.0.1:2080

View file

@ -0,0 +1,101 @@
global
log stdout format raw local0 info
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
frontend http_in_443
bind *:443 ssl crt /certs/letsencrypt/ alpn http/1.1 crt /certs/haproxy/ alpn http/1.1
mode http
acl is_rule_hostssl_local_443_1 hdr(host) -i hostssl.local
acl is_rule_hostssl_local_443_2 hdr(host) -i hostssl.local:443
use_backend srv_hostssl_local_443 if is_rule_hostssl_local_443_1 OR is_rule_hostssl_local_443_2
acl is_rule_host2_local_443_1 hdr(host) -i host2.local
acl is_rule_host2_local_443_2 hdr(host) -i host2.local:443
use_backend srv_host2_local_443 if is_rule_host2_local_443_1 OR is_rule_host2_local_443_2
backend srv_hostssl_local_443
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-0 test2_processor_docker:8080 check weight 1
backend srv_host2_local_443
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-0 test_processor_docker:9000 check weight 1
frontend http_in_80
bind *:80
mode http
acl is_rule_host1_local_80_1 hdr(host) -i host1.local
acl is_rule_host1_local_80_2 hdr(host) -i host1.local:80
use_backend srv_host1_local_80 if is_rule_host1_local_80_1 OR is_rule_host1_local_80_2
backend srv_host1_local_80
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-0 test_processor_docker:8080 check weight 1
frontend http_in_90
bind *:90
mode http
acl is_rule_host2_local_90_1 hdr(host) -i host2.local
acl is_rule_host2_local_90_2 hdr(host) -i host2.local:90
acl is_letsencrypt_host2_local_90 path_beg /.well-known/acme-challenge/
use_backend letsencrypt_backend if is_letsencrypt_host2_local_90 is_rule_host2_local_90_1 OR is_letsencrypt_host2_local_90 is_rule_host2_local_90_2
use_backend srv_host2_local_90 if is_rule_host2_local_90_1 OR is_rule_host2_local_90_2
backend srv_host2_local_90
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-0 test_processor_docker:9000 check weight 1
backend letsencrypt_backend
mode http
server certbot 127.0.0.1:2080

View file

@ -25,4 +25,4 @@ defaults
backend letsencrypt_backend backend letsencrypt_backend
mode http mode http
server certbot 127.0.0.1:2080 server certbot 127.0.0.1:2080

View file

@ -92,4 +92,4 @@ backend srv_test_example_org_443
backend letsencrypt_backend backend letsencrypt_backend
mode http mode http
server certbot 127.0.0.1:2080 server certbot 127.0.0.1:2080

View file

@ -42,4 +42,4 @@ backend srv_www_helloworld_com_19901
backend letsencrypt_backend backend letsencrypt_backend
mode http mode http
server certbot 127.0.0.1:2080 server certbot 127.0.0.1:2080

View file

@ -48,15 +48,15 @@ frontend http_in_19901
mode http mode http
redirect prefix www.google.com code 301 if { hdr(host) -i google.helloworld.com } redirect prefix www.google.com code 301 if { hdr(host) -i google.helloworld.com }
acl is_rule_hello_com_19901_1 hdr(host) -i hello.com
acl is_rule_hello_com_19901_2 hdr(host) -i hello.com:19901
use_backend srv_hello_com_19901 if is_rule_hello_com_19901_1 OR is_rule_hello_com_19901_2
acl is_rule_www_helloworld_com_19901_1 hdr(host) -i www.helloworld.com acl is_rule_www_helloworld_com_19901_1 hdr(host) -i www.helloworld.com
acl is_rule_www_helloworld_com_19901_2 hdr(host) -i www.helloworld.com:19901 acl is_rule_www_helloworld_com_19901_2 hdr(host) -i www.helloworld.com:19901
use_backend srv_www_helloworld_com_19901 if is_rule_www_helloworld_com_19901_1 OR is_rule_www_helloworld_com_19901_2 use_backend srv_www_helloworld_com_19901 if is_rule_www_helloworld_com_19901_1 OR is_rule_www_helloworld_com_19901_2
backend srv_hello_com_19901 acl is_rule_hello_com_19901_1 hdr(host) -i hello.com
acl is_rule_hello_com_19901_2 hdr(host) -i hello.com:19901
use_backend srv_hello_com_19901 if is_rule_hello_com_19901_1 OR is_rule_hello_com_19901_2
backend srv_www_helloworld_com_19901
balance roundrobin balance roundrobin
mode http mode http
option forwardfor option forwardfor
@ -64,7 +64,7 @@ backend srv_hello_com_19901
http-request add-header X-Forwarded-Proto https if { ssl_fc } http-request add-header X-Forwarded-Proto https if { ssl_fc }
server srv-0 3e63154954b0:80 check weight 1 server srv-0 3e63154954b0:80 check weight 1
server srv-1 eb294c110eb1:80 check weight 1 server srv-1 eb294c110eb1:80 check weight 1
backend srv_www_helloworld_com_19901 backend srv_hello_com_19901
balance roundrobin balance roundrobin
mode http mode http
option forwardfor option forwardfor
@ -75,4 +75,4 @@ backend srv_www_helloworld_com_19901
backend letsencrypt_backend backend letsencrypt_backend
mode http mode http
server certbot 127.0.0.1:2080 server certbot 127.0.0.1:2080

View file

@ -77,4 +77,4 @@ backend srv_host1_local_443
backend letsencrypt_backend backend letsencrypt_backend
mode http mode http
server certbot 127.0.0.1:2080 server certbot 127.0.0.1:2080

View file

@ -39,4 +39,4 @@ backend srv_agent_quantum_local_31339
backend letsencrypt_backend backend letsencrypt_backend
mode http mode http
server certbot 127.0.0.1:2080 server certbot 127.0.0.1:2080

View file

@ -121,4 +121,4 @@ backend srv_www_somehost_com_br_80
backend letsencrypt_backend backend letsencrypt_backend
mode http mode http
server certbot 127.0.0.1:2080 server certbot 127.0.0.1:2080

View file

@ -35,4 +35,4 @@ backend srv_stats
backend letsencrypt_backend backend letsencrypt_backend
mode http mode http
server certbot 127.0.0.1:2080 server certbot 127.0.0.1:2080

View file

@ -20,4 +20,4 @@ defaults
backend letsencrypt_backend backend letsencrypt_backend
mode http mode http
server certbot 127.0.0.1:2080 server certbot 127.0.0.1:2080

View file

@ -107,4 +107,4 @@ backend srv_host3_com_br_8080
backend letsencrypt_backend backend letsencrypt_backend
mode http mode http
server certbot 127.0.0.1:2080 server certbot 127.0.0.1:2080

5
src/tests/fixtures/no-services vendored Normal file
View file

@ -0,0 +1,5 @@
{"swarm-prom_caddy": {"com.docker.stack.image":"stefanprodan/caddy","com.docker.stack.namespace":"swarm-prom"},
"swarm-prom_cadvisor": {"com.docker.stack.image":"google/cadvisor","com.docker.stack.namespace":"swarm-prom"},
"swarm-prom_dockerd-exporter": {"com.docker.stack.image":"stefanprodan/caddy","com.docker.stack.namespace":"swarm-prom"},
"swarm-prom_unsee": {"com.docker.stack.image":"cloudflare/unsee:v0.8.0","com.docker.stack.namespace":"swarm-prom"},
"test_proxy": {"com.docker.stack.image":"byjg/easy-haproxy","com.docker.stack.namespace":"test"}}

6
src/tests/fixtures/services vendored Normal file
View file

@ -0,0 +1,6 @@
{"portainer-agent_agent": {"com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"portainer-agent"},
"my-stack_agent": {"easyhaproxy.agent.host":"agent.quantum.example.org","easyhaproxy.agent.localport":"9001","easyhaproxy.agent.mode":"tcp","easyhaproxy.agent.port":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"},
"my-stack_cadvisor": {"easyhaproxy.cadvisor.host":"cadvisor.quantum.example.org","easyhaproxy.cadvisor.localport":"8080","easyhaproxy.cadvisor.port":"31337","com.docker.stack.image":"gcr.io/google-containers/cadvisor:v0.34.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"},
"my-stack_node-exporter": {"easyhaproxy.exp.host":"node-exporter.quantum.example.org","easyhaproxy.exp.localport":"9100","easyhaproxy.exp.port":"31337","com.docker.stack.image":"stefanprodan/swarmprom-node-exporter:v0.16.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring","easyhaproxy.exp.letsencrypt":"true"},
"my-stack_reverse-proxy": {"com.docker.stack.image":"quay.io/pngmbh/easy-haproxy:tcp-mode","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"},
"some-service": {"easyhaproxy.http.port":"80","easyhaproxy.http.host":"www.somehost.com.br","easyhaproxy.http.localport":"80","easyhaproxy.http.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","easyhaproxy.https.port":"443","easyhaproxy.https.host":"www.somehost.com.br","easyhaproxy.https.localport":"80","easyhaproxy.https.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","easyhaproxy.https.sslcert":"U29tZSBQRU0gQ2VydGlmaWNhdGU="}}

View file

@ -0,0 +1,6 @@
{"portainer-agent_agent": {"com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"portainer-agent"},
"my-stack_agent": {"haproxy.agent.host":"agent.quantum.example.org","haproxy.agent.localport":"9001","haproxy.agent.mode":"tcp","haproxy.agent.port":"31339","com.docker.stack.image":"portainer/agent:1.5.1","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"},
"my-stack_cadvisor": {"haproxy.cadvisor.host":"cadvisor.quantum.example.org","haproxy.cadvisor.localport":"8080","haproxy.cadvisor.port":"31337","com.docker.stack.image":"gcr.io/google-containers/cadvisor:v0.34.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"},
"my-stack_node-exporter": {"haproxy.exp.host":"node-exporter.quantum.example.org","haproxy.exp.localport":"9100","haproxy.exp.port":"31337","com.docker.stack.image":"stefanprodan/swarmprom-node-exporter:v0.16.0","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring","haproxy.exp.letsencrypt":"yes"},
"my-stack_reverse-proxy": {"com.docker.stack.image":"quay.io/pngmbh/easy-haproxy:tcp-mode","com.docker.stack.namespace":"my-stack","com.planetary-quantum":"monitoring"},
"some-service": {"haproxy.http.port":"80","haproxy.http.host":"www.somehost.com.br","haproxy.http.localport":"80","haproxy.http.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","haproxy.https.port":"443","haproxy.https.host":"www.somehost.com.br","haproxy.https.localport":"80","haproxy.https.redirect":"{\"somehost.com.br\":\"https://www.somehost.com.br\",\"somehost.com\":\"https://www.somehost.com.br\",\"www.somehost.com\":\"https://www.somehost.com.br\",\"byjg.ca\":\"https://www.somehost.com.br\",\"www.byjg.ca\":\"https://www.somehost.com.br\"}","haproxy.https.sslcert":"U29tZSBQRU0gQ2VydGlmaWNhdGU="}}

View file

@ -0,0 +1,2 @@
{"10.152.183.62": {"creation_timestamp": "08/24/22 02:59:44", "resource_version": "72517156", "namespace": "parking", "easyhaproxy.valida-me_8080.host": "valida.me", "easyhaproxy.valida-me_8080.port": "80", "easyhaproxy.valida-me_8080.localport": 8080, "easyhaproxy.valida-me_8080.redirect": "{\"www.valida.me\": \"https://valida.me\"}", "easyhaproxy.www-valida-me_8080.host": "www.valida.me", "easyhaproxy.www-valida-me_8080.port": "80", "easyhaproxy.www-valida-me_8080.localport": 8080, "easyhaproxy.www-valida-me_8080.redirect": "{\"www.valida.me\": \"https://valida.me\"}"},
"10.152.183.215": {"creation_timestamp": "08/26/22 03:06:01", "resource_version": "72522999", "namespace": "default", "easyhaproxy.host2-local_8080.host": "host2.local", "easyhaproxy.host2-local_8080.port": "80", "easyhaproxy.host2-local_8080.localport": 8080, "easyhaproxy.host2-local_8080.clone_to_ssl": "true"}}

View file

@ -0,0 +1,4 @@
{"f5c645a0dfc6": {"com.docker.compose.config-hash":"b95ebc27d0e61caa418cdfa632e05a656da9bbc3ea0d4603651971015f10a1f0","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test.example.org","easyhaproxy.http.letsencrypt":"true","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"80","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}","easyhaproxy.http.redirect_ssl":"true"},
"bbd4d1854155": {"com.docker.compose.config-hash":"3dc790bf2bea944359c75a40c45655bd868f1d85beb599d1ca797e8ea2c95ee4","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:0fd95b1512c207048ab3fcc74032354f38143fbb8235ac2a47da903c98a58205","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"haproxy","com.docker.compose.version":"2.8.0"},
"b63438410b6a": {"com.docker.compose.config-hash":"b95ebc27d0e61caa418cdfa632e05a656da9bbc3ea0d4603651971015f10a1f0","com.docker.compose.container-number":"2","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test.example.org","easyhaproxy.http.letsencrypt":"true","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"80","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}","easyhaproxy.http.redirect_ssl":"true"},
"83d57d592e26": {"com.docker.compose.config-hash":"8c5871144f1e8a3aeca037207c02f011ab2c6e6c311a3773602b63541762dab5","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:c4232396c715f3d568816c666e6d9b4a68ef6c36f6243b4007c4ee1d8335fd65","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-test.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"static","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"test2.example.org","easyhaproxy.http.localport":"8080","easyhaproxy.http.port":"80","io.buildah.version":"1.21.0"}}

View file

@ -0,0 +1,2 @@
{"test_nginx.2.t5r94mjlced7m3t5orfjbowmm": {"easyhaproxy.http.host":"www.helloworld.com","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","com.docker.stack.image":"stenote/nginx-hostname","com.docker.stack.namespace":"test"},
"test_nginx.1.p552hqxkdx88narjrp5kouwb2": {"easyhaproxy.http.host":"www.helloworld.com","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","com.docker.stack.image":"stenote/nginx-hostname","com.docker.stack.namespace":"test"}}

View file

@ -0,0 +1,3 @@
{"db79d3a910f4": {"com.docker.compose.config-hash":"5bde40f52451521ad201e70de1291397376a0498a7c955624a609da3b60e7e8e","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:ea39067705590557dd0cd951664a10970ceefcb725a3c1f43690d6d6d4ed5fce","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-multi-containers.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"haproxy","com.docker.compose.version":"2.8.0"},
"3e63154954b0": {"com.docker.compose.config-hash":"4e0cbdd8372c6779863799e5021ed8178f74b55bd8e070abcdffaf87eb7baa36","com.docker.compose.container-number":"1","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-multi-containers.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"hello.com\n, www.helloworld.com\n","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}"},
"eb294c110eb1": {"com.docker.compose.config-hash":"4e0cbdd8372c6779863799e5021ed8178f74b55bd8e070abcdffaf87eb7baa36","com.docker.compose.container-number":"2","com.docker.compose.depends_on":"","com.docker.compose.image":"sha256:bea3509d6fdc8d7f9ec95563a5a226dc977ee74fb3e980e0de70e892c2d38dde","com.docker.compose.oneoff":"False","com.docker.compose.project":"docker","com.docker.compose.project.config_files":"/workspace/docker-easy-haproxy/examples/docker/docker-compose-multi-containers.yml","com.docker.compose.project.working_dir":"/workspace/docker-easy-haproxy/examples/docker","com.docker.compose.service":"nginx","com.docker.compose.version":"2.8.0","easyhaproxy.http.host":"hello.com\n, www.helloworld.com\n","easyhaproxy.http.localport":"80","easyhaproxy.http.port":"19901","easyhaproxy.http.redirect":"{\"google.helloworld.com\": \"www.google.com\"}"}}

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show more