First Kubernetes implementation
This commit is contained in:
parent
eb5f7e313f
commit
bbf3dca6fd
5 changed files with 183 additions and 22 deletions
45
README.md
45
README.md
|
|
@ -47,7 +47,7 @@ The environment variables will setup the HAProxy.
|
||||||
|
|
||||||
| Environment Variable | Description |
|
| Environment Variable | Description |
|
||||||
|-------------------------------|---------------------------------------------------------------------------------------------------------------|
|
|-------------------------------|---------------------------------------------------------------------------------------------------------------|
|
||||||
| EASYHAPROXY_DISCOVER | How `haproxy.cfg` will be created: `static`, `docker` or `swarm` |
|
| EASYHAPROXY_DISCOVER | How `haproxy.cfg` will be created: `static`, `docker`, `swarm` or `kubernetes` |
|
||||||
| EASYHAPROXY_LABEL_PREFIX | (Optional) The key will search for matching resources. Default: `easyhaproxy`. |
|
| 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_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_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). |
|
||||||
|
|
@ -100,7 +100,48 @@ The discovery will occur every minute.
|
||||||
|
|
||||||
Important: easyhaproxy needs to be in the same network of the containers or otherwise will not access.
|
Important: easyhaproxy needs to be in the same network of the containers or otherwise will not access.
|
||||||
|
|
||||||
### Docker Container (Swarm or Docker) tags:
|
### EASYHAPROXY_DISCOVER: kubernetes (experimental and limited)
|
||||||
|
|
||||||
|
This will query all `ingress` in the kubernetes cluster and check the annotation `kubernetes.io/ingress.class: easyhaproxy-ingress`.
|
||||||
|
|
||||||
|
e.g.:
|
||||||
|
```
|
||||||
|
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
|
||||||
|
- host: www.example.org
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
service:
|
||||||
|
name: example-service
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
|
pathType: ImplementationSpecific
|
||||||
|
```
|
||||||
|
|
||||||
|
At this point the implementation is very limited and doesn't support all ingress properties nor wildcard domains.
|
||||||
|
|
||||||
|
The system will read only `host` and `port.number`
|
||||||
|
|
||||||
|
There is no necessary to add labels or annotations.
|
||||||
|
|
||||||
|
|
||||||
|
### Container (Docker or Swarm) labels:
|
||||||
|
|
||||||
| Tag | Description | Example |
|
| Tag | Description | Example |
|
||||||
|---------------------------------------|---------------------------------------------------------------------------------------------------------|--------------|
|
|---------------------------------------|---------------------------------------------------------------------------------------------------------|--------------|
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,6 @@ cd /scripts
|
||||||
|
|
||||||
RELOAD="true"
|
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
|
if [[ "$EASYHAPROXY_DISCOVER" == "static" ]]; then
|
||||||
CONTROL_FILE="/etc/haproxy/haproxy.cfg"
|
CONTROL_FILE="/etc/haproxy/haproxy.cfg"
|
||||||
touch ${CONTROL_FILE}
|
touch ${CONTROL_FILE}
|
||||||
|
|
@ -22,27 +17,40 @@ else
|
||||||
mv ${CONTROL_FILE} ${CONTROL_FILE}.old
|
mv ${CONTROL_FILE} ${CONTROL_FILE}.old
|
||||||
touch ${CONTROL_FILE}
|
touch ${CONTROL_FILE}
|
||||||
|
|
||||||
if [[ "$EASYHAPROXY_DISCOVER" == "docker" ]]; then
|
case "$EASYHAPROXY_DISCOVER" in
|
||||||
CONTAINERS=$(docker ps -q | sort | uniq)
|
docker)
|
||||||
LABEL_PATH=".Config.Labels"
|
CONTAINERS=$(docker ps -q | sort | uniq)
|
||||||
|
LABEL_PATH=".Config.Labels"
|
||||||
|
|
||||||
for container in ${CONTAINERS}; do
|
for container in ${CONTAINERS}; do
|
||||||
docker inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE}
|
docker inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE}
|
||||||
done
|
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
|
swarm)
|
||||||
docker service inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE}
|
CONTAINERS=$(docker node ps $(docker node ls -q) --format "{{ .Name }}" --filter desired-state=running | cut -d. -f1 | sort | uniq)
|
||||||
done
|
LABEL_PATH=".Spec.Labels"
|
||||||
fi
|
|
||||||
|
for container in ${CONTAINERS}; do
|
||||||
|
docker service inspect --format "{{ json $LABEL_PATH }}" ${container} | xargs -I % echo ${container}=% >> ${CONTROL_FILE}
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
|
||||||
|
kubernetes)
|
||||||
|
python3 /scripts/k8s.py > ${CONTROL_FILE}
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
log "error" "CONF_CHECK" "EASYHAPROXY_DISCOVER should be 'static', 'docker', 'swarm' or kubernetes. I got '$EASYHAPROXY_DISCOVER' instead."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
if cmp -s ${CONTROL_FILE} ${CONTROL_FILE}.old ; then
|
if cmp -s ${CONTROL_FILE} ${CONTROL_FILE}.old ; then
|
||||||
RELOAD="false"
|
RELOAD="false"
|
||||||
else
|
else
|
||||||
python3 swarm.py > /etc/haproxy/haproxy.cfg
|
python3 swarm.py > /etc/haproxy/haproxy.cfg
|
||||||
log "info" "CONF_CHECK" "New configuration found"
|
log "info" "CONF_CHECK" "New configuration found"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
50
assets/scripts/k8s.py
Normal file
50
assets/scripts/k8s.py
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
from kubernetes import client, config
|
||||||
|
from kubernetes.client.rest import ApiException
|
||||||
|
import json
|
||||||
|
|
||||||
|
# https://github.com/kubernetes-client/python/tree/master/kubernetes/docs
|
||||||
|
|
||||||
|
def main():
|
||||||
|
config.load_incluster_config()
|
||||||
|
|
||||||
|
api_instance = client.CoreV1Api()
|
||||||
|
v1 = client.NetworkingV1Api()
|
||||||
|
|
||||||
|
ret = v1.list_ingress_for_all_namespaces(watch=False)
|
||||||
|
|
||||||
|
discover = {}
|
||||||
|
for i in ret.items:
|
||||||
|
if i.metadata.annotations['kubernetes.io/ingress.class'] != "easyhaproxy-ingress":
|
||||||
|
continue
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
#ingress_name = i.metadata.name
|
||||||
|
data["creation_timestamp"] = i.metadata.creation_timestamp.strftime("%x %X")
|
||||||
|
data["resource_version"] = i.metadata.resource_version
|
||||||
|
data["namespace"] = i.metadata.namespace
|
||||||
|
for rule in i.spec.rules:
|
||||||
|
rule_data = {}
|
||||||
|
port_number = rule.http.paths[0].backend.service.port.number
|
||||||
|
definition = rule.host.replace(".", "-")
|
||||||
|
rule_data["easyhaproxy.%s_%s.host" % (definition, port_number)] = rule.host
|
||||||
|
rule_data["easyhaproxy.%s_%s.port" % (definition, port_number)] = "80"
|
||||||
|
rule_data["easyhaproxy.%s_%s.localport" % (definition, port_number)] = port_number
|
||||||
|
service_name = rule.http.paths[0].backend.service.name
|
||||||
|
try:
|
||||||
|
api_response = api_instance.read_namespaced_service(service_name, i.metadata.namespace)
|
||||||
|
cluster_ip = api_response.spec.cluster_ip
|
||||||
|
except ApiException as e:
|
||||||
|
cluster_ip = None
|
||||||
|
# print("Exception when calling CoreV1Api->read_namespaced_service: %s\n" % e)
|
||||||
|
|
||||||
|
if cluster_ip is not None:
|
||||||
|
if cluster_ip not in discover.keys():
|
||||||
|
discover[cluster_ip] = data
|
||||||
|
discover[cluster_ip].update(rule_data)
|
||||||
|
|
||||||
|
for k in discover.keys():
|
||||||
|
print("%s=%s" % (k, json.dumps(discover[k])))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
||||||
61
kubernetes/easyhaproxy.yml
Normal file
61
kubernetes/easyhaproxy.yml
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: easyhaproxy
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
name: easyhaproxy-ingress
|
||||||
|
namespace: easyhaproxy
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: easyhaproxy-ingress
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: easyhaproxy-ingress
|
||||||
|
spec:
|
||||||
|
serviceAccountName: easyhaproxy-ingress
|
||||||
|
containers:
|
||||||
|
- image: byjg/easy-haproxy:kubernetes
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
name: easyhaproxy-ingress
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 80
|
||||||
|
hostPort: 80
|
||||||
|
- name: https
|
||||||
|
containerPort: 443
|
||||||
|
hostPort: 443
|
||||||
|
- name: stats
|
||||||
|
containerPort: 1943
|
||||||
|
hostPort: 1943
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "100m"
|
||||||
|
memory: "128Mi"
|
||||||
|
#limits:
|
||||||
|
# cpu: "1"
|
||||||
|
# memory: "1Gi"
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: true
|
||||||
|
#runAsUser: 101 #nginx
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
add:
|
||||||
|
- NET_BIND_SERVICE
|
||||||
|
env:
|
||||||
|
- name: EASYHAPROXY_DISCOVER
|
||||||
|
value: kubernetes
|
||||||
|
- name: HAPROXY_USERNAME
|
||||||
|
value: admin
|
||||||
|
- name: HAPROXY_PASSWORD
|
||||||
|
value: password
|
||||||
|
- name: EASYHAPROXY_REFRESH_CONF
|
||||||
|
value: 10
|
||||||
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
pyyaml
|
pyyaml
|
||||||
docker
|
docker
|
||||||
jinja2
|
jinja2
|
||||||
pytest
|
pytest
|
||||||
|
kubernetes
|
||||||
Loading…
Add table
Add a link
Reference in a new issue