mirror of
https://github.com/cookiecutter/cookiecutter-django.git
synced 2025-02-16 19:41:03 +03:00
Add Experimental AWS Elastic Beanstalk support (#817)
Includes: * First pass at Elastic Beanstalk integration * Gets code and elasticache working * Very rudimentary documentation * Includes post hook cleanup
This commit is contained in:
parent
3d25befb3f
commit
cac685d909
|
@ -42,6 +42,7 @@ Features
|
||||||
* Works with Python 2.7.x or 3.5.x
|
* Works with Python 2.7.x or 3.5.x
|
||||||
* Run tests with unittest or py.test
|
* Run tests with unittest or py.test
|
||||||
* Customizable PostgreSQL version
|
* Customizable PostgreSQL version
|
||||||
|
* Experimental support for Amazon Elastic Beanstalk
|
||||||
|
|
||||||
|
|
||||||
Optional Integrations
|
Optional Integrations
|
||||||
|
@ -146,6 +147,7 @@ Answer the prompts with your own desired options_. For example::
|
||||||
4 - Apache Software License 2.0
|
4 - Apache Software License 2.0
|
||||||
5 - Not open source
|
5 - Not open source
|
||||||
Choose from 1, 2, 3, 4, 5 [1]: 1
|
Choose from 1, 2, 3, 4, 5 [1]: 1
|
||||||
|
use_elasticbeanstalk_experimental: n
|
||||||
|
|
||||||
Enter the project and take a look around::
|
Enter the project and take a look around::
|
||||||
|
|
||||||
|
|
|
@ -21,5 +21,6 @@
|
||||||
"postgresql_version": ["9.5", "9.4", "9.3", "9.2"],
|
"postgresql_version": ["9.5", "9.4", "9.3", "9.2"],
|
||||||
"js_task_runner": ["Gulp", "Grunt", "None"],
|
"js_task_runner": ["Gulp", "Grunt", "None"],
|
||||||
"use_lets_encrypt": "n",
|
"use_lets_encrypt": "n",
|
||||||
"open_source_license": ["MIT", "BSD", "GPLv3", "Apache Software License 2.0", "Not open source"]
|
"open_source_license": ["MIT", "BSD", "GPLv3", "Apache Software License 2.0", "Not open source"],
|
||||||
|
"use_elasticbeanstalk_experimental": "n"
|
||||||
}
|
}
|
||||||
|
|
54
docs/deployment-with-elastic-beanstalk.rst
Normal file
54
docs/deployment-with-elastic-beanstalk.rst
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
Deployment with Elastic Beanstalk
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
.. index:: Elastic Beanstalk
|
||||||
|
|
||||||
|
Warning: Experimental
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
This is experimental. For the time being there will be bugs and issues. If you've never used Elastic Beanstalk before, please hold off before trying this option.
|
||||||
|
|
||||||
|
On the other hand, we need help cleaning this up. If you do have knowledge of Elastic Beanstalk, we would appreciate the help. :)
|
||||||
|
|
||||||
|
Prerequisites
|
||||||
|
-------------
|
||||||
|
|
||||||
|
* awsebcli
|
||||||
|
|
||||||
|
Instructions
|
||||||
|
-------------
|
||||||
|
|
||||||
|
```
|
||||||
|
# creates the directory of environments (servers)
|
||||||
|
eb init -p python3.4 {{ cookiecutter.project_slug }}
|
||||||
|
|
||||||
|
# Creates the environment (server) where the app will run
|
||||||
|
eb create {{ cookiecutter.project_slug }}
|
||||||
|
# Note: This will fail on a postgres error, because postgres doesn't exist yet
|
||||||
|
|
||||||
|
# Make sure you are in the right environment
|
||||||
|
eb list
|
||||||
|
|
||||||
|
# If you are not in the right environment
|
||||||
|
eb use {{ cookiecutter.project_slug }}
|
||||||
|
|
||||||
|
# Set the environment variables
|
||||||
|
python ebsetenv.py
|
||||||
|
|
||||||
|
# Go to EB AWS config. Create new RDS database (postgres, 9.4.9, db.t2.micro)
|
||||||
|
# Get some coffee, this is going to take a while
|
||||||
|
|
||||||
|
# Deploy again
|
||||||
|
eb deploy
|
||||||
|
|
||||||
|
# Take a look
|
||||||
|
eb open
|
||||||
|
```
|
||||||
|
|
||||||
|
FAQ
|
||||||
|
-----
|
||||||
|
|
||||||
|
Why Not Use Docker on Elastic Beanstalk?
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Because I didn't want to add an abstraction (Docker) on top of an abstraction (Elastic Beanstalk).
|
|
@ -25,6 +25,7 @@ Contents:
|
||||||
faq
|
faq
|
||||||
troubleshooting
|
troubleshooting
|
||||||
my-favorite-cookie
|
my-favorite-cookie
|
||||||
|
deployment-with-elastic-beanstalk
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
==================
|
==================
|
||||||
|
|
|
@ -115,7 +115,10 @@ def remove_heroku_files():
|
||||||
"""
|
"""
|
||||||
Removes files needed for heroku if it isn't going to be used
|
Removes files needed for heroku if it isn't going to be used
|
||||||
"""
|
"""
|
||||||
for filename in ["app.json", "Procfile", "requirements.txt", "runtime.txt"]:
|
filenames = ["app.json", "Procfile", "runtime.txt"]
|
||||||
|
if '{{ cookiecutter.use_elasticbeanstalk_experimental }}'.lower() != 'y':
|
||||||
|
filenames.append("requirements.txt")
|
||||||
|
for filename in ["app.json", "Procfile", "runtime.txt"]:
|
||||||
file_name = os.path.join(PROJECT_DIRECTORY, filename)
|
file_name = os.path.join(PROJECT_DIRECTORY, filename)
|
||||||
remove_file(file_name)
|
remove_file(file_name)
|
||||||
|
|
||||||
|
@ -179,6 +182,22 @@ def remove_copying_files():
|
||||||
PROJECT_DIRECTORY, filename
|
PROJECT_DIRECTORY, filename
|
||||||
))
|
))
|
||||||
|
|
||||||
|
def remove_elasticbeanstalk():
|
||||||
|
"""
|
||||||
|
Removes elastic beanstalk components
|
||||||
|
"""
|
||||||
|
docs_dir_location = os.path.join(PROJECT_DIRECTORY, '.ebextensions')
|
||||||
|
if os.path.exists(docs_dir_location):
|
||||||
|
shutil.rmtree(docs_dir_location)
|
||||||
|
|
||||||
|
filenames = ["ebsetenv.py", ]
|
||||||
|
if '{{ cookiecutter.use_heroku }}'.lower() != 'y':
|
||||||
|
filenames.append("requirements.txt")
|
||||||
|
for filename in filenames:
|
||||||
|
os.remove(os.path.join(
|
||||||
|
PROJECT_DIRECTORY, filename
|
||||||
|
))
|
||||||
|
|
||||||
# IN PROGRESS
|
# IN PROGRESS
|
||||||
# def copy_doc_files(project_directory):
|
# def copy_doc_files(project_directory):
|
||||||
# cookiecutters_dir = DEFAULT_CONFIG['cookiecutters_dir']
|
# cookiecutters_dir = DEFAULT_CONFIG['cookiecutters_dir']
|
||||||
|
@ -258,5 +277,6 @@ if '{{ cookiecutter.use_lets_encrypt }}'.lower() == 'y' and '{{ cookiecutter.use
|
||||||
if '{{ cookiecutter.open_source_license}}' != 'GPLv3':
|
if '{{ cookiecutter.open_source_license}}' != 'GPLv3':
|
||||||
remove_copying_files()
|
remove_copying_files()
|
||||||
|
|
||||||
# 4. Copy files from /docs/ to {{ cookiecutter.project_slug }}/docs/
|
# 12. Remove Elastic Beanstalk files
|
||||||
# copy_doc_files(PROJECT_DIRECTORY)
|
if '{{ cookiecutter.use_elasticbeanstalk_experimental }}'.lower() != 'y':
|
||||||
|
remove_elasticbeanstalk()
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
packages:
|
||||||
|
yum:
|
||||||
|
git: []
|
||||||
|
postgresql94-devel: []
|
||||||
|
libjpeg-turbo-devel: []
|
|
@ -0,0 +1,46 @@
|
||||||
|
#This sample requires you to create a separate configuration file that defines the custom
|
||||||
|
# option settings for CacheCluster properties.
|
||||||
|
|
||||||
|
Resources:
|
||||||
|
MyCacheSecurityGroup:
|
||||||
|
Type: "AWS::EC2::SecurityGroup"
|
||||||
|
Properties:
|
||||||
|
GroupDescription: "Lock cache down to webserver access only"
|
||||||
|
SecurityGroupIngress :
|
||||||
|
- IpProtocol : "tcp"
|
||||||
|
FromPort :
|
||||||
|
Fn::GetOptionSetting:
|
||||||
|
OptionName : "CachePort"
|
||||||
|
DefaultValue: "6379"
|
||||||
|
ToPort :
|
||||||
|
Fn::GetOptionSetting:
|
||||||
|
OptionName : "CachePort"
|
||||||
|
DefaultValue: "6379"
|
||||||
|
SourceSecurityGroupName:
|
||||||
|
Ref: "AWSEBSecurityGroup"
|
||||||
|
MyElastiCache:
|
||||||
|
Type: "AWS::ElastiCache::CacheCluster"
|
||||||
|
Properties:
|
||||||
|
CacheNodeType:
|
||||||
|
Fn::GetOptionSetting:
|
||||||
|
OptionName : "CacheNodeType"
|
||||||
|
DefaultValue : "cache.t1.micro"
|
||||||
|
NumCacheNodes:
|
||||||
|
Fn::GetOptionSetting:
|
||||||
|
OptionName : "NumCacheNodes"
|
||||||
|
DefaultValue : "1"
|
||||||
|
Engine:
|
||||||
|
Fn::GetOptionSetting:
|
||||||
|
OptionName : "Engine"
|
||||||
|
DefaultValue : "redis"
|
||||||
|
VpcSecurityGroupIds:
|
||||||
|
-
|
||||||
|
Fn::GetAtt:
|
||||||
|
- MyCacheSecurityGroup
|
||||||
|
- GroupId
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
ElastiCache:
|
||||||
|
Description : "ID of ElastiCache Cache Cluster with Redis Engine"
|
||||||
|
Value :
|
||||||
|
Ref : "MyElastiCache"
|
|
@ -0,0 +1,6 @@
|
||||||
|
option_settings:
|
||||||
|
"aws:elasticbeanstalk:customoption":
|
||||||
|
CacheNodeType : cache.t1.micro
|
||||||
|
NumCacheNodes : 1
|
||||||
|
Engine : redis
|
||||||
|
CachePort : 6379
|
17
{{cookiecutter.project_slug}}/.ebextensions/40_python.config
Normal file
17
{{cookiecutter.project_slug}}/.ebextensions/40_python.config
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
container_commands:
|
||||||
|
01_migrate:
|
||||||
|
command: "source /opt/python/run/venv/bin/activate && python manage.py migrate"
|
||||||
|
leader_only: True
|
||||||
|
02_collectstatic:
|
||||||
|
command: "source /opt/python/run/venv/bin/activate && python manage.py collectstatic --noinput"
|
||||||
|
option_settings:
|
||||||
|
"aws:elasticbeanstalk:application:environment":
|
||||||
|
DJANGO_SETTINGS_MODULE: "config.settings.production"
|
||||||
|
REDIS_ENDPOINT_ADDRESS: '`{ "Fn::GetAtt" : [ "MyElastiCache", "RedisEndpoint.Address"]}`'
|
||||||
|
REDIS_PORT: '`{ "Fn::GetAtt" : [ "MyElastiCache", "RedisEndpoint.Port"]}`'
|
||||||
|
"aws:elasticbeanstalk:container:python":
|
||||||
|
WSGIPath: "config/wsgi.py"
|
||||||
|
NumProcesses: 3
|
||||||
|
NumThreads: 20
|
||||||
|
"aws:elasticbeanstalk:container:python:staticfiles":
|
||||||
|
"/static/": "www/static/"
|
|
@ -138,3 +138,13 @@ See detailed `cookiecutter-django Docker documentation`_.
|
||||||
|
|
||||||
.. _`cookiecutter-django Docker documentation`: http://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html
|
.. _`cookiecutter-django Docker documentation`: http://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if cookiecutter.use_elasticbeanstalk_experimental %}
|
||||||
|
|
||||||
|
Elastic Beanstalk
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
See detailed `cookiecutter-django Elastic Beanstalk documentation`_.
|
||||||
|
|
||||||
|
.. _`cookiecutter-django Docker documentation`: http://cookiecutter-django.readthedocs.io/en/latest/deployment-with-elastic-beanstalk.html
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
|
@ -178,16 +178,39 @@ TEMPLATES[0]['OPTIONS']['loaders'] = [
|
||||||
|
|
||||||
# DATABASE CONFIGURATION
|
# DATABASE CONFIGURATION
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
{% if cookiecutter.use_elasticbeanstalk_experimental -%}
|
||||||
|
# Uses Amazon RDS for database hosting, which doesn't follow the Heroku-style spec
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||||
|
'NAME': env('RDS_DB_NAME'),
|
||||||
|
'USER': env('RDS_USERNAME'),
|
||||||
|
'PASSWORD': env('RDS_PASSWORD'),
|
||||||
|
'HOST': env('RDS_HOSTNAME'),
|
||||||
|
'PORT': env('RDS_PORT'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{% else %}
|
||||||
|
# Use the Heroku-style specification
|
||||||
# Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
|
# Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
|
||||||
DATABASES['default'] = env.db('DATABASE_URL')
|
DATABASES['default'] = env.db('DATABASE_URL')
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
# CACHING
|
# CACHING
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
{% if cookiecutter.use_elasticbeanstalk_experimental -%}
|
||||||
|
REDIS_LOCATION = "redis://{}:{}/0".format(
|
||||||
|
env('REDIS_ENDPOINT_ADDRESS'),
|
||||||
|
env('REDIS_PORT')
|
||||||
|
)
|
||||||
|
{% else %}
|
||||||
|
REDIS_LOCATION = '{0}/{1}'.format(env('REDIS_URL', default='redis://127.0.0.1:6379'), 0)
|
||||||
|
{%- endif %}
|
||||||
# Heroku URL does not pass the DB number, so we parse it in
|
# Heroku URL does not pass the DB number, so we parse it in
|
||||||
CACHES = {
|
CACHES = {
|
||||||
'default': {
|
'default': {
|
||||||
'BACKEND': 'django_redis.cache.RedisCache',
|
'BACKEND': 'django_redis.cache.RedisCache',
|
||||||
'LOCATION': '{0}/{1}'.format(env('REDIS_URL', default='redis://127.0.0.1:6379'), 0),
|
'LOCATION': REDIS_LOCATION,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
|
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
|
||||||
'IGNORE_EXCEPTIONS': True, # mimics memcache behavior.
|
'IGNORE_EXCEPTIONS': True, # mimics memcache behavior.
|
||||||
|
|
31
{{cookiecutter.project_slug}}/ebsetenv.py
Normal file
31
{{cookiecutter.project_slug}}/ebsetenv.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
"""Converts a .env file to Elastic Beanstalk environment variables"""
|
||||||
|
|
||||||
|
from sys import exit
|
||||||
|
from subprocess import check_call
|
||||||
|
|
||||||
|
try:
|
||||||
|
import dotenv
|
||||||
|
except ImportError:
|
||||||
|
print("Please install the 'dotenv' library: 'pip install dotenv'")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
command = ['eb', 'setenv']
|
||||||
|
failures = []
|
||||||
|
for key, value in dotenv.Dotenv('.env').items():
|
||||||
|
if key.startswith('POSTGRES'):
|
||||||
|
continue
|
||||||
|
if value:
|
||||||
|
command.append("{}={}".format(key, value))
|
||||||
|
else:
|
||||||
|
failures.append(key)
|
||||||
|
if failures:
|
||||||
|
for failure in failures:
|
||||||
|
print("{} requires a value".format(failure))
|
||||||
|
else:
|
||||||
|
print(' '.join(command))
|
||||||
|
check_call(command)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
# PostgreSQL
|
# PostgreSQL
|
||||||
POSTGRES_PASSWORD=mysecretpass
|
POSTGRES_PASSWORD=mysecretpass
|
||||||
POSTGRES_USER=postgresuser
|
POSTGRES_USER=postgresuser
|
||||||
|
|
Loading…
Reference in New Issue
Block a user