In this tutorial, we will run Django SQL migrations on top of IaSQL to deploy an HTTP server within a docker container on your AWS account using ECS, ECR, and ELB. The container image will be hosted as a public repository in ECR and deployed to ECS using Fargate.
IaSQL is an open-source software tool that creates a two-way connection between an unmodified PostgreSQL database and an AWS account so you can manage your infrastructure from a database. The code for this tutorial lives in this part of the repository.
Start managing an AWS account with a PostgreSQL IaSQL db
First, make sure you have an IAM user in AWS or create one with Programmatic access through the console/UI or CLI. Ensure that the IAM role has sufficient permissions to deploy and manage all your infrastructure resources.
There are two parts to each access key, which you’ll see in the IAM console/CLI after you create it, an id and a secret. Input these in the connect account modal:
If you use the AWS CLI, you can look at the credentials configured locally. In macOS and Linux this is as simple as:
$ cat ~/.aws/credentials
[default]
aws_access_key_id = <YOUR_ACCESS_KEY_ID>
aws_secret_access_key = <YOUR_SECRET_ACCESS_KEY>
You will be able to see your PostgreSQL connection information when you press Connect.
Make sure to copy the PostgreSQL connection string as you will not see it again.
Add the necessary cloud services to the PostgreSQL database
Many different clients can be used to connect to a PostgreSQL database. For this tutorial, we'll use the standard
psql
CLI client. If you need to installpsql
, follow the instructions for your corresponding OS here.The first migration calls the
iasql_install
SQL function to install the ECS simplified module into the PostgreSQL database.
SELECT
*
FROM
iasql_install ('aws_ecs_simplified', 'aws_codebuild');
If the function call is successful, it will return a virtual table with a record for each new table in your database under created_table_name
and the number of existing resources or records imported from the account under record_count
.
module_name | created_table_name | record_count
--------------------------+-------------------------------+--------------
aws_cloudwatch | log_group | 0
aws_ecr | public_repository | 0
aws_ecr | repository | 1
aws_ecr | repository_policy | 0
aws_security_group | security_group | 2
aws_security_group | security_group_rule | 0
aws_vpc | vpc | 1
aws_vpc | subnet | 3
aws_elb | load_balancer | 0
aws_elb | target_group | 0
aws_elb | listener | 0
aws_elb | load_balancer_security_groups | 0
aws_ecs_fargate | cluster | 0
aws_ecs_fargate | service | 0
aws_ecs_fargate | task_definition | 0
aws_ecs_fargate | container_definition | 0
aws_ecs_fargate | service_security_groups | 0
Connect to the PostgreSQL db and provision cloud resources in your AWS account
Get a local copy of the ECS Fargate examples
(Optional) Create and activate a virtual environment to install python dependencies
python -m venv <env-name>
source <env-name>/bin/activateInstall the project dependencies under the
django/app
folderpip install -r requirements.txt
Create a
.env
file with the connection parameters provided on db creation. In this case:AWS_REGION=eu-west-2
DB_NAME=_3ba201e349a11daf
DB_USER=qpp3pzqb
DB_PASSWORD=LN6jnHfhRJTBD6ia(Optional) Set the desired project name that your resources will be named after by changing the
IASQL_PROJECT_NAME
in themy_project/app/app/settings.py
. If the name is not changed,quickstart
will be used.noteThe
project-name
can only contain alphanumeric characters and hyphens(-) because it will be used to name the load balancerPer the Django database documentation, to connect to a new database you have to update the
DATABASES
in themy_project/app/app/settings.py
file. This is already configured in the example project.django/app/app/settings.pyDATABASES = {
...
'infra': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': env('DB_NAME'),
'USER': env('DB_USER'),
'PASSWORD': env('DB_PASSWORD'),
'HOST': 'localhost',
'PORT': '5432',
}
}
If you are using the template example go to step 9. The following steps explains how to instrospect an existing DB in Django.
- The second migration corresponds to the Django models introspected from the modules that have been installed in the database. To introspect the schema from your database run the following command. More information here.
python manage.py inspectdb --database=infra > infra/models.py
After running the inspectdb
command you will need to tweak the models Django generated until they work the way you’d like.
In our case you will have to modify the my_project/app/infra/models.py
file as follow:
- Replace
CharField
withTextField
- Remove all
max_length=-1
. Helpful regex for a replacement:[\s,]*max_length=-1[,\s]*
- Add the following import
from django.contrib.postgres.fields import ArrayField
- Replace in the
Service
class thesubnets
property withsubnets = ArrayField(models.TextField())
- Replace in the
Role
class theattached_policies_arns
property withattached_policies_arns = ArrayField(models.TextField())
- Add
related_name
argument to the definition forIasqlDependencies.dependency
. (dependency = models.ForeignKey('IasqlModule', models.DO_NOTHING, db_column='dependency', related_name='module')
) - Add
related_name
argument to the definition forTaskDefinition.execution_role_name
. (execution_role_name = models.ForeignKey(Role, models.DO_NOTHING, db_column='execution_role_name', blank=True, null=True, related_name='execution_role_name')
) - Add
related_name
argument to the definition forTaskDefinition.task_role_name
. (task_role_name = models.ForeignKey(Role, models.DO_NOTHING, db_column='task_role_name', blank=True, null=True, related_name='task_role_name')
)
After instrospecting the db you will need to generate the migration so you can have the
my_project/app/infra/migrations/0002_inspectdb.py
file.python manage.py makemigrations --name inspectdb infra
cautionIf you install or uninstall IaSQL modules the database schema will change and you will need to run steps 7 and 8 to introspect the correct schema once again.
Now you can use IaSQL models to create your resources. Run the existing migrations with:
python manage.py migrate --database infra infra
The operations of the
my_project/app/infra/migrations/0003_initial.py
migration will apply the changes described in the PostgreSQL db to your cloud account which will take a few minutes waiting for AWSmy_project/app/infra/migrations/0003_initial.py...
operations = [
migrations.RunPython(code=quickstart_up, reverse_code=apply),
migrations.RunPython(code=apply, reverse_code=quickstart_down),
]
If the function call is successful, it will return a list of dicts with each cloud resource that has been created, deleted or updated.
[{'action': 'create', 'table_name': 'log_group', 'id': None, 'description': 'quickstart-log-group'}, {'action': 'create', 'table_name': 'repository', 'id': None, 'description': 'quickstart-repository'}, {'action': 'create', 'table_name': 'iam_role', 'id': None, 'description': 'quickstart-ecs-task-exec-role'}, {'action': 'create', 'table_name': 'security_group', 'id': 31, 'description': '31'}, {'action': 'create', 'table_name': 'security_group_rule', 'id': 48, 'description': '48'}, {'action': 'create', 'table_name': 'security_group_rule', 'id': 49, 'description': '49'}, {'action': 'create', 'table_name': 'listener', 'id': 16, 'description': '16'}, {'action': 'create', 'table_name': 'load_balancer', 'id': None, 'description': 'quickstart-load-balancer'}, {'action': 'create', 'table_name': 'target_group', 'id': None, 'description': 'quickstart-target'}, {'action': 'create', 'table_name': 'cluster', 'id': None, 'description': 'quickstart-cluster'}, {'action': 'create', 'table_name': 'task_definition', 'id': 16, 'description': '16'}, {'action': 'create', 'table_name': 'service', 'id': None, 'description': 'quickstart-service'}, {'action': 'delete', 'table_name': 'security_group_rule', 'id': None, 'description': 'sgr-024274a604968919e'}]
Login, build and push your code to the container registry
Previously, you needed to manually build and push your image to the ECR. But recently we've added the high-level ecr_build
SQL function which does all those steps automatically. It will do the following:
- Pull the code from your Github repository
- Build the Docker image in the directory you've specified
- Push the image to the ECR repository you've provided
All of these steps will be done in a CodeBuild project in your AWS account. To use the ecr_build
function, you can run:
SELECT
ecr_build (
'https://github.com/alantech/iasql/', -- replace with your own Github repo if you want to use your own codebase
(
SELECT
id
FROM
repository
WHERE
repository_name = 'quickstart-repository'
)::VARCHAR(255), -- replace quickstart if you've changed the project name
'./examples/ecs-fargate/django/app', -- the sub directory in the Github repo that the image should be built in
'main', -- the Github repo branch name
'' -- replace your github personal access token here if the repo is private
);
After running the above SQL command to completion, you can check the running app using the load balancer DNS name. To grab the name, run:
QUICKSTART_LB_DNS=$(psql -At 'postgres://d0va6ywg:nfdDh#EP4CyzveFr@localhost:5432/_4b2bb09a59a411e4' -c "
SELECT dns_name
FROM load_balancer
WHERE load_balancer_name = '<project-name>-load-balancer';")
And then connect to your service!
curl ${QUICKSTART_LB_DNS}:8088/health
Delete Managed Cloud Resources
Delete the resources created by this tutorial using the following SQL code:
DELETE FROM
repository_image
WHERE
private_repository_id = (
SELECT
id
FROM
repository
WHERE
repository_name = 'quickstart-repository'
);
DELETE FROM
ecs_simplified
WHERE
app_name = 'quickstart';