├── Dockerfile ├── README.md ├── handler.py └── start /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | RUN apk -Uuv add groff less python py-pip && \ 4 | pip install awscli && \ 5 | pip install boto && \ 6 | apk --purge -v del py-pip && \ 7 | rm /var/cache/apk/* 8 | 9 | ENV CONSUL_VERSION 0.5.2 10 | RUN apk --update add curl ca-certificates && \ 11 | curl -Ls https://circle-artifacts.com/gh/andyshinn/alpine-pkg-glibc/6/artifacts/0/home/ubuntu/alpine-pkg-glibc/packages/x86_64/glibc-2.21-r2.apk > /tmp/glibc-2.21-r2.apk && \ 12 | apk add --allow-untrusted /tmp/glibc-2.21-r2.apk && \ 13 | rm -rf /tmp/glibc-2.21-r2.apk /var/cache/apk/* 14 | 15 | ADD https://dl.bintray.com/mitchellh/consul/${CONSUL_VERSION}_linux_amd64.zip /tmp/consul.zip 16 | RUN unzip /tmp/consul.zip \ 17 | && chmod +x consul \ 18 | && rm /tmp/consul.zip 19 | 20 | ADD handler.py / 21 | ADD start / 22 | 23 | ENTRYPOINT ["/start"] 24 | CMD [] 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ELB Consul 2 | 3 | This Docker image allows you to (de)register EC2 instances belonging to a specific Consul service with an Amazon Elastic Load Balancer (ELB). 4 | 5 | ## Usage 6 | 7 | The `elb-consul` image takes all of its configuration from environment variables. 8 | 9 | * `AWS_ACCESS_KEY_ID` ... Your AWS [access key](http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSGettingStartedGuide/AWSCredentials.html) 10 | * `AWS_SECRET_ACCESS_KEY` ... Your AWS [secret key](http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSGettingStartedGuide/AWSCredentials.html) 11 | * `AWS_REGION` ... The AWS region that your load balancer is located in 12 | * `ELB_NAME` ... The exact name of your load balancer 13 | * `CONSUL_SERVER` ... The IP:PORT to connect to the Consul server (eg: 172.1.6.1.2:8500) 14 | * `CONSUL_SERVICE` ... The exact name of the Consul service you want to monitor 15 | 16 | You could run only one instance of `elb-consul` per `ELB_NAME` / `CONSUL_SERVICE` pair but for high availability purpose, it is recommanded to run two of them. 17 | Internally the Docker image uses a Consul Lock to make sure that only one `elb-consul` will be active at anytime. 18 | -------------------------------------------------------------------------------- /handler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | import json 6 | import time 7 | import boto.ec2.elb 8 | from subprocess import check_output, CalledProcessError 9 | 10 | AWS_REGION = os.environ['AWS_REGION'] 11 | ELB_NAME = os.environ['ELB_NAME'] 12 | 13 | ELB = boto.ec2.elb.connect_to_region(region_name=AWS_REGION) 14 | 15 | 16 | def elb_register(instances): 17 | ELB.register_instances(ELB_NAME, instances) 18 | 19 | 20 | def elb_deregister(instances): 21 | ELB.deregister_instances(ELB_NAME, instances) 22 | 23 | 24 | def elb_instances(): 25 | ELB.describe_instance_health(ELB_NAME) 26 | 27 | def aws(cmd): 28 | while True: 29 | try: 30 | output = check_output('aws --region %s %s' % (AWS_REGION, cmd), shell=True) 31 | return json.loads(output) 32 | except CalledProcessError: 33 | time.sleep(5) 34 | continue 35 | 36 | 37 | def get_instance_id(ip): 38 | cmd = 'ec2 describe-instances --filter Name=private-ip-address,Values=%s' % (ip) 39 | data = aws(cmd) 40 | try: 41 | return data['Reservations'][0]['Instances'][0]['InstanceId'] 42 | except (KeyError, IndexError): 43 | print '%s not found in %s' % (ip, AWS_REGION) 44 | return None 45 | 46 | 47 | def update_elb(register, deregister): 48 | cmd = "elb describe-load-balancers --load-balancer-name %s" % (ELB_NAME) 49 | data = aws(cmd) 50 | instances = set([i["InstanceId"] for i in data["LoadBalancerDescriptions"][0]["Instances"]]) 51 | print "Instances in %s: %s" % (ELB_NAME, list(instances)) 52 | 53 | register -= instances 54 | if register: 55 | cmd = 'elb register-instances-with-load-balancer --load-balancer-name %s --instances %s' % (ELB_NAME, ' '.join(register)) 56 | data = aws(cmd) 57 | instances = set([i['InstanceId'] for i in data["Instances"]]) 58 | print "Instances in %s: %s" % (ELB_NAME, list(instances)) 59 | 60 | deregister = deregister.intersection(instances) 61 | if deregister: 62 | cmd = 'elb deregister-instances-from-load-balancer --load-balancer-name %s --instances %s' % (ELB_NAME, ' '.join(deregister)) 63 | data = aws(cmd) 64 | instances = set([i['InstanceId'] for i in data["Instances"]]) 65 | print "Instances in %s: %s" % (ELB_NAME, list(instances)) 66 | 67 | 68 | def process_update(): 69 | for update in sys.stdin: 70 | register = set() 71 | deregister = set() 72 | for node in json.loads(update): 73 | ip = node['Node']['Address'] 74 | instance_id = get_instance_id(ip) 75 | if not instance_id: 76 | continue 77 | ok = all([check['Status'] == 'passing' for check in node['Checks']]) 78 | if ok: 79 | register.add(instance_id) 80 | else: 81 | deregister.add(instance_id) 82 | update_elb(register, deregister) 83 | 84 | if __name__ == "__main__": 85 | try: 86 | check_output('aws iam get-user', shell=True) 87 | except CalledProcessError as e: 88 | print "Check your AWS credentials: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY" 89 | sys.exit(1) 90 | process_update() 91 | -------------------------------------------------------------------------------- /start: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec /consul lock -http-addr=$CONSUL_SERVER lock/elb-consul/$AWS_REGION/$ELB_NAME /consul watch -http-addr=$CONSUL_SERVER -type=service -service=$CONSUL_SERVICE ./handler.py 4 | --------------------------------------------------------------------------------