Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ELB support to Dokku stack #29

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 9 additions & 16 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -325,11 +325,18 @@ The CloudFormation stack creation should not finish until Dokku is fully install
<http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-signal.html>`_ is used in the
template to signal CloudFormation once the installation is complete.

Load Balancer
~~~~~~~~~~~~~

The Dokku stack includes a load balancer and a free SSL certificate via AWS Certificate Manager, so
HTTPS needs to be configured on the Dokku instance only if you wish to encrypt traffic between the
load balancer and the Dokku instance.

DNS
~~~

After the stack is created, you'll want to inspect the Outputs for the PublicIP of the instance and
create a DNS ``A`` record (possibly including a wildcard record, if you're using vhost-based apps)
After the stack is created, you'll want to inspect the Outputs for the `LoadBalancerDNSName` and
create a DNS ``CNAME`` record (possibly including a wildcard record, if you're using vhost-based apps)
for your chosen domain.

For help creating a DNS record, please refer to the `Dokku DNS documentation
Expand Down Expand Up @@ -370,20 +377,6 @@ http://python-sample.your.domain/
For additional help deploying to your new instance, please refer to the `Dokku documentation
<http://dokku.viewdocs.io/dokku/deployment/application-deployment/>`_.

Let's Encrypt
~~~~~~~~~~~~~

The Dokku stack does not create a load balancer and hence does not include a free SSL certificate
via Amazon Certificate Manager, so let's create one with the Let's Encrypt plugin, and add a cron
job to automatically renew the cert as needed::

ssh ubuntu@<your domain or IP> sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
ssh dokku@<your domain or IP> config:set --no-restart python-sample [email protected]
ssh dokku@<your domain or IP> letsencrypt python-sample
ssh dokku@<your domain or IP> letsencrypt:cron-job --add python-sample

The Python sample app should now be accessible over HTTPS at https://python-sample.your.domain/

Contributing
------------

Expand Down
63 changes: 31 additions & 32 deletions stack/dokku.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import troposphere.autoscaling as autoscaling
import troposphere.cloudformation as cloudformation
import troposphere.ec2 as ec2
import troposphere.iam as iam
from troposphere import Base64, FindInMap, Join, Output, Parameter, Ref, Tags
from troposphere import AWS_STACK_NAME, Base64, FindInMap, Join, Parameter, Ref
from troposphere.policies import CreationPolicy, ResourceSignal

from .assets import assets_management_policy
from .common import container_instance_type
from .domain import domain_name
from .environment import environment_variables
from .load_balancer import load_balancer
from .logs import logging_policy
from .template import template
from .vpc import container_a_subnet, vpc
from .vpc import container_a_subnet, container_b_subnet, vpc

key_name = template.add_parameter(
Parameter(
Expand Down Expand Up @@ -150,20 +152,15 @@
]
))

# Elastic IP for EC2 instance
eip = template.add_resource(ec2.EIP("Eip"))


# The Dokku EC2 instance
ec2_instance_name = 'Ec2Instance'
ec2_instance = template.add_resource(ec2.Instance(
ec2_instance_name,
launch_configuration_name = 'LaunchConfiguration'
launch_configuration = template.add_resource(autoscaling.LaunchConfiguration(
launch_configuration_name,
ImageId=FindInMap("RegionMap", Ref("AWS::Region"), "AMI"),
InstanceType=container_instance_type,
KeyName=Ref(key_name),
SecurityGroupIds=[Ref(security_group)],
SecurityGroups=[Ref(security_group)],
IamInstanceProfile=Ref(instance_profile),
SubnetId=Ref(container_a_subnet),
BlockDeviceMappings=[
ec2.BlockDeviceMapping(
DeviceName="/dev/sda1",
Expand All @@ -190,10 +187,10 @@
'update-rc.d cfn-hup defaults\n',
# call our "on_first_boot" configset (defined below):
'cfn-init --stack="', Ref('AWS::StackName'), '" --region=', Ref('AWS::Region'),
' -r %s -c on_first_boot\n' % ec2_instance_name,
' -r %s -c on_first_boot\n' % launch_configuration_name,
# send the exit code from cfn-init to our CreationPolicy:
'cfn-signal -e $? --stack="', Ref('AWS::StackName'), '" --region=', Ref('AWS::Region'),
' --resource %s\n' % ec2_instance_name,
' --resource %s\n' % launch_configuration_name,
])),
Metadata=cloudformation.Metadata(
cloudformation.Init(
Expand Down Expand Up @@ -264,10 +261,10 @@
# trigger the on_metadata_update configset on any changes to Ec2Instance metadata
'[cfn-auto-reloader-hook]\n',
'triggers=post.update\n',
'path=Resources.%s.Metadata\n' % ec2_instance_name,
'path=Resources.%s.Metadata\n' % launch_configuration_name,
'action=/usr/local/bin/cfn-init',
' --stack=', Ref('AWS::StackName'),
' --resource=%s' % ec2_instance_name,
' --resource=%s' % launch_configuration_name,
' --configsets=on_metadata_update',
' --region=', Ref('AWS::Region'), '\n',
'runas=root\n',
Expand All @@ -280,22 +277,24 @@
),
),
),
Tags=Tags(
Name=Ref("AWS::StackName"),
),
))

# Associate the Elastic IP separately, so it doesn't change when the instance changes.
eip_assoc = template.add_resource(ec2.EIPAssociation(
"EipAssociation",
InstanceId=Ref(ec2_instance),
EIP=Ref(eip),
))

template.add_output([
Output(
"PublicIP",
Description="Public IP address of Elastic IP associated with the Dokku instance",
Value=Ref(eip),
),
])
autoscaling_group = autoscaling.AutoScalingGroup(
"AutoScalingGroup",
template=template,
VPCZoneIdentifier=[Ref(container_a_subnet), Ref(container_b_subnet)],
MinSize=1,
MaxSize=1,
DesiredCapacity=1,
LaunchConfigurationName=Ref(launch_configuration),
LoadBalancerNames=[Ref(load_balancer)],
HealthCheckType="EC2",
HealthCheckGracePeriod=300,
Tags=[
{
"Key": "Name",
"Value": Join("-", [Ref(AWS_STACK_NAME), "dokku_instance"]),
"PropagateAtLaunch": True,
}
],
)
28 changes: 17 additions & 11 deletions stack/load_balancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
group="Load Balancer",
label="Web Worker Port",
))
elif os.environ.get('USE_DOKKU') == 'on':
# TODO: optionally support both port 80 and port 443
web_worker_port = 80
else:
# default to port 80 for EC2 and Elastic Beanstalk options
web_worker_port = Ref(template.add_parameter(
Expand All @@ -43,17 +46,20 @@
label="Web Worker Port",
))

web_worker_protocol = Ref(template.add_parameter(
Parameter(
"WebWorkerProtocol",
Description="Web worker instance protocol",
Type="String",
Default="HTTP",
AllowedValues=["HTTP", "HTTPS"],
),
group="Load Balancer",
label="Web Worker Protocol",
))
if os.environ.get('USE_DOKKU') == 'on':
web_worker_protocol = 'HTTP'
else:
web_worker_protocol = Ref(template.add_parameter(
Parameter(
"WebWorkerProtocol",
Description="Web worker instance protocol",
Type="String",
Default="HTTP",
AllowedValues=["HTTP", "HTTPS"],
),
group="Load Balancer",
label="Web Worker Protocol",
))

tcp_health_check_condition = "TcpHealthCheck"
template.add_condition(
Expand Down
59 changes: 29 additions & 30 deletions stack/vpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,39 +117,38 @@
)


if not USE_DOKKU:
# Holds load balancer
loadbalancer_a_subnet_cidr = "10.0.2.0/24"
loadbalancer_a_subnet = Subnet(
"LoadbalancerASubnet",
template=template,
VpcId=Ref(vpc),
CidrBlock=loadbalancer_a_subnet_cidr,
AvailabilityZone=Ref(primary_az),
)
# Holds load balancer
loadbalancer_a_subnet_cidr = "10.0.2.0/24"
loadbalancer_a_subnet = Subnet(
"LoadbalancerASubnet",
template=template,
VpcId=Ref(vpc),
CidrBlock=loadbalancer_a_subnet_cidr,
AvailabilityZone=Ref(primary_az),
)

SubnetRouteTableAssociation(
"LoadbalancerASubnetRouteTableAssociation",
template=template,
RouteTableId=Ref(public_route_table),
SubnetId=Ref(loadbalancer_a_subnet),
)
SubnetRouteTableAssociation(
"LoadbalancerASubnetRouteTableAssociation",
template=template,
RouteTableId=Ref(public_route_table),
SubnetId=Ref(loadbalancer_a_subnet),
)

loadbalancer_b_subnet_cidr = "10.0.3.0/24"
loadbalancer_b_subnet = Subnet(
"LoadbalancerBSubnet",
template=template,
VpcId=Ref(vpc),
CidrBlock=loadbalancer_b_subnet_cidr,
AvailabilityZone=Ref(secondary_az),
)
loadbalancer_b_subnet_cidr = "10.0.3.0/24"
loadbalancer_b_subnet = Subnet(
"LoadbalancerBSubnet",
template=template,
VpcId=Ref(vpc),
CidrBlock=loadbalancer_b_subnet_cidr,
AvailabilityZone=Ref(secondary_az),
)

SubnetRouteTableAssociation(
"LoadbalancerBSubnetRouteTableAssociation",
template=template,
RouteTableId=Ref(public_route_table),
SubnetId=Ref(loadbalancer_b_subnet),
)
SubnetRouteTableAssociation(
"LoadbalancerBSubnetRouteTableAssociation",
template=template,
RouteTableId=Ref(public_route_table),
SubnetId=Ref(loadbalancer_b_subnet),
)


if USE_NAT_GATEWAY:
Expand Down