Skip to content

Commit 1318492

Browse files
author
orion
committed
adding repo files
1 parent 4ddec43 commit 1318492

31 files changed

+1139
-2
lines changed

.env

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
DJANGO_SETTINGS_MODULE=settings.dev
2+
SECRET_KEY='django-insecure-django-insecure-django-insecure-django-insecure'
3+
DEBUG=True
4+
DB_NAME=postgres
5+
DB_USER=postgres
6+
DB_PASSWORD=postgres
7+
DB_HOST=db
8+
DB_PORT=5432

Dockerfile

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
FROM python:3.8.5-alpine
2+
3+
RUN pip install --upgrade pip
4+
5+
COPY ./requirements.txt .
6+
7+
RUN \
8+
apk add --no-cache postgresql-libs && \
9+
apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev && \
10+
python3 -m pip install -r requirements.txt --no-cache-dir && \
11+
apk --purge del .build-deps
12+
13+
COPY ./myproject /myproject
14+
15+
COPY ./entrypoint.sh /myproject
16+
17+
WORKDIR /myproject
18+
19+
ENTRYPOINT ["sh", "./entrypoint.sh"]

README.md

+144-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,144 @@
1-
# djaws
2-
Deploy a Django container to AWS.
1+
# DJAWS (DJango on AWS)
2+
3+
### Description
4+
5+
Use **DJAWS** to deploy a Django container on AWS. This repo contains a small Django project called _myproject_ that needs access to a Postgres backend and an S3 bucket. The code in this repo will:
6+
7+
* deploy _myproject_ **locally** with Docker Compose using 3 containers (Gunicorn, Nginx and Postgres)
8+
* deploy _myproject_ in **production** on AWS Elastic Container Service (ECS)
9+
10+
Deploying on ECS is done via Cloudformation YAML files in the _infra/_ directory. The Django app would be reached via the load balancer URL.
11+
12+
13+
### Installing AWS CLI
14+
15+
It is advised that the root user of your AWS account not to be used during deployment or most other activities. It is better to create a new group and a user belonging to that group. The user would have programmatic but limited access and you can store the user's credentials locally in `~/.aws/credentials`.
16+
17+
```
18+
## install aws cli
19+
virtualenv venv -p python
20+
source venv/bin/activate
21+
python -m pip install awscli
22+
aws --version
23+
```
24+
25+
### Deployment to AWS
26+
27+
Deployment can be carried out from your local machine and it consists of 7 steps:
28+
29+
1. Build the Docker image for _myproject_ and push it to the Elastic Container Registry (ECR)
30+
2. Create the VPC stack (includes public subnets, private subnets, internet gateway, elastic IPs, Nat gateways, route tables)
31+
3. Create the execution role for the ECS task
32+
4. Create the load balancer, security group and task definition
33+
5. Create the Postgres RDS, S3 bucket and their secrets and parameters
34+
6. Update the Django SECRET_KEY as a SecureString type on the parameter store and add its value
35+
7. Launch the ECS Service.
36+
37+
38+
### The AWS Services Used:
39+
40+
* Elastic Container Registry
41+
* Elastic Container Service
42+
* Fargate Cluster
43+
* Application Load Balancer
44+
* Elastic IP address
45+
* Postgres RDS
46+
* S3 bucket
47+
* Secrets Manager
48+
* Parameter Store
49+
* CloudWatch
50+
51+
52+
## Local Deployment
53+
54+
Docker Compose is used to locally spin up a Django container with Gunicorn, Nginx container and a Postgres container.
55+
Enviroment variables are found in the **.env** file.
56+
57+
The Docker image for the Django project _myproject_ will be built locally.
58+
59+
#### Start
60+
61+
```docker-compose up -d```
62+
63+
#### Stop
64+
65+
```docker-compose down```
66+
67+
68+
## Deployment in Production with AWS
69+
70+
1. Build and push Docker image to ECR
71+
72+
```
73+
## build docker image
74+
docker build -t myproject .
75+
76+
## create a repo on ECR
77+
aws ecr create-repository --repository-name myproject
78+
79+
## authenticate your local Docker daemon against the ECR
80+
$(aws ecr get-login --registry-ids <your registry ID> --no-include-email)
81+
82+
## tag the Docker image
83+
docker tag myproject <your registry ID>.dkr.ecr.<your region>.amazonaws.com/myproject:0.0.1
84+
85+
## push to ECR
86+
docker push <your registry ID>.dkr.ecr.<your region>.amazonaws.com/myproject:0.0.1
87+
88+
## list your container images on ECR
89+
aws ecr list-images --repository-name myproject
90+
91+
## delete image on registry
92+
aws ecr batch-delete-image --repository-name myproject --image-ids imageDigest=<the image digest>
93+
```
94+
95+
2. The VPC stack
96+
97+
```
98+
cd infra
99+
aws cloudformation create-stack --stack-name vpc --template-body file://vpc.yaml --capabilities CAPABILITY_NAMED_IAM
100+
```
101+
102+
3. The Execution role
103+
104+
```
105+
aws cloudformation create-stack --stack-name roles --template-body file://roles.yaml --capabilities CAPABILITY_NAMED_IAM
106+
```
107+
108+
4. The Load balancer, security group and task definition
109+
110+
```
111+
aws cloudformation create-stack --stack-name lb-sg-task --template-body file://lb_sg_task.yaml --capabilities CAPABILITY_NAMED_IAM --parameters ParameterKey=ImageUrl,ParameterValue='<your registry ID>.dkr.ecr.<your region>.amazonaws.com/myproject:0.0.1'
112+
```
113+
114+
5. The Postgres DB, S3 bucket and Secrets
115+
116+
```
117+
aws cloudformation create-stack --stack-name rds-s3 --template-body file://rds_s3.yaml --capabilities CAPABILITY_NAMED_IAM --parameters ParameterKey=BucketName,ParameterValue=$(head /dev/urandom | tr -dc a-z0-9 | head -c10)
118+
```
119+
120+
6. Update SECRET_KEY in the Parameter Store:
121+
122+
```
123+
aws ssm put-parameter --overwrite --name /Prod/DjangoSecret --type SecureString --value <SECRET_KEY value>
124+
```
125+
126+
7. Launching the ECS Service
127+
128+
```
129+
aws cloudformation create-stack --stack-name service --template-body file://service.yaml --capabilities CAPABILITY_NAMED_IAM
130+
```
131+
132+
### Teardown
133+
134+
Teardown in reverse order:
135+
136+
service stack -> rds-s3 stack -> lb-sg-task stack -> roles stack -> vpc stack
137+
138+
```
139+
## deleting a stack
140+
aws cloudformation delete-stack --stack-name <stack name>
141+
142+
## updating a stack
143+
aws cloudformation update-stack --stack-name <stack name> --template-body file://<stack yaml>
144+
```

docker-compose.yaml

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
version: '3.7'
2+
3+
services:
4+
db:
5+
image: postgres
6+
container_name: db
7+
ports:
8+
- '5438:5432'
9+
volumes:
10+
- ./postgres-data:/var/lib/postgresql/data
11+
environment:
12+
- POSTGRES_DB=postgres
13+
- POSTGRES_USER=postgres
14+
- POSTGRES_PASSWORD=postgres
15+
16+
fl_gunicorn:
17+
container_name: fl_gunicorn
18+
volumes:
19+
- ./myproject/static:/static
20+
env_file:
21+
- .env
22+
build:
23+
context: .
24+
dockerfile: ./Dockerfile
25+
ports:
26+
- "8000:8000"
27+
depends_on:
28+
- db
29+
30+
nginx:
31+
build: ./nginx
32+
volumes:
33+
- ./myproject/static:/static
34+
ports:
35+
- "80:80"
36+
depends_on:
37+
- fl_gunicorn

entrypoint.sh

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
python manage.py makemigrations
2+
python manage.py migrate --no-input
3+
python manage.py collectstatic --no-input
4+
5+
gunicorn myproject.wsgi:application --bind 0.0.0.0:8000

infra/lb_sg_task.yaml

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
AWSTemplateFormatVersion: 2010-09-09
2+
Description: Create load balancer, security groups and task definition.
3+
Parameters:
4+
EnvName:
5+
Description: The environment name.
6+
Type: String
7+
Default: 'PROD'
8+
9+
ContainerPort:
10+
Description: Container port.
11+
Type: Number
12+
Default: 8000
13+
14+
ImageUrl:
15+
Description: Container image.
16+
Type: String
17+
Default: ""
18+
19+
Resources:
20+
PublicLoadBalancerSecurityGroup:
21+
Type: AWS::EC2::SecurityGroup
22+
Properties:
23+
GroupDescription: Access to the public facing load balancer.
24+
VpcId:
25+
'Fn::ImportValue': !Sub "${EnvName}-VPC"
26+
SecurityGroupIngress:
27+
- CidrIp: 0.0.0.0/0
28+
IpProtocol: -1
29+
30+
PublicLoadBalancer:
31+
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
32+
Properties:
33+
Scheme: internet-facing
34+
Subnets:
35+
- 'Fn::ImportValue': !Sub "${EnvName}-PublicSubnet1"
36+
- 'Fn::ImportValue': !Sub "${EnvName}-PublicSubnet2"
37+
SecurityGroups:
38+
- !Ref PublicLoadBalancerSecurityGroup
39+
40+
ECSCluster:
41+
Type: AWS::ECS::Cluster
42+
43+
ECSSecurityGroup:
44+
Type: AWS::EC2::SecurityGroup
45+
Properties:
46+
GroupDescription: Access to the ECS containers.
47+
VpcId:
48+
'Fn::ImportValue': !Sub "${EnvName}-VPC"
49+
50+
ECSSecurityGroupIngressFromPublicALB:
51+
Type: AWS::EC2::SecurityGroupIngress
52+
Properties:
53+
Description: Ingress from the public ALB.
54+
GroupId: !Ref ECSSecurityGroup
55+
IpProtocol: -1
56+
SourceSecurityGroupId: !Ref PublicLoadBalancerSecurityGroup
57+
58+
ECSSecurityGroupIngressFromSelf:
59+
Type: AWS::EC2::SecurityGroupIngress
60+
Properties:
61+
Description: Ingress from other containers in the same security group
62+
GroupId: !Ref ECSSecurityGroup
63+
IpProtocol: -1
64+
SourceSecurityGroupId: !Ref ECSSecurityGroup
65+
66+
67+
TargetGroup:
68+
Type: AWS::ElasticLoadBalancingV2::TargetGroup
69+
Properties:
70+
HealthCheckIntervalSeconds: 10
71+
HealthCheckPath: /
72+
HealthCheckProtocol: HTTP
73+
HealthCheckTimeoutSeconds: 5
74+
HealthyThresholdCount: 2
75+
TargetType: ip
76+
Name: !Join ['-', [!Ref EnvName, 'Service']]
77+
Port: !Ref ContainerPort
78+
Name: "target-group"
79+
Protocol: HTTP
80+
UnhealthyThresholdCount: 2
81+
VpcId:
82+
'Fn::ImportValue': !Sub "${EnvName}-VPC"
83+
84+
PublicLoadBalancerListener:
85+
Type: AWS::ElasticLoadBalancingV2::Listener
86+
DependsOn:
87+
- PublicLoadBalancer
88+
- TargetGroup
89+
Properties:
90+
DefaultActions:
91+
- TargetGroupArn: !Ref TargetGroup
92+
Type: 'forward'
93+
LoadBalancerArn: !Ref PublicLoadBalancer
94+
Port: 80
95+
Protocol: HTTP
96+
97+
TaskDefinition:
98+
Type: AWS::ECS::TaskDefinition
99+
Properties:
100+
Family: !Ref EnvName
101+
Cpu: 512
102+
Memory: "2GB"
103+
NetworkMode: awsvpc
104+
RequiresCompatibilities:
105+
- FARGATE
106+
ExecutionRoleArn:
107+
'Fn::ImportValue': !Sub "${EnvName}-ECSTaskExecutionRole"
108+
TaskRoleArn:
109+
'Fn::ImportValue': !Sub "${EnvName}-ECSTaskExecutionRole"
110+
ContainerDefinitions:
111+
- Name: !Ref EnvName
112+
Image: !Ref ImageUrl
113+
PortMappings:
114+
- ContainerPort: !Ref ContainerPort
115+
Environment:
116+
- Name: 'DB_PORT'
117+
Value: 5432
118+
- Name: 'DJANGO_SETTINGS_MODULE'
119+
Value: 'settings.prod'
120+
LogConfiguration:
121+
LogDriver: awslogs
122+
Options:
123+
awslogs-group: !Ref CloudWatchLogsGroup
124+
awslogs-region: !Ref AWS::Region
125+
awslogs-stream-prefix: !Ref EnvName
126+
127+
CloudWatchLogsGroup:
128+
Type: AWS::Logs::LogGroup
129+
Properties:
130+
LogGroupName: !Sub "${EnvName}-Logs"
131+
RetentionInDays: 1
132+
133+
134+
Outputs:
135+
136+
PublicLoadBalancer:
137+
Description: Public Load Balancer
138+
Value: !Ref PublicLoadBalancer
139+
Export:
140+
Name: !Join ['-', [!Ref EnvName, 'PublicLoadBalancer']]
141+
142+
ECSSecurityGroup:
143+
Description: ECS Security Group
144+
Value: !Ref ECSSecurityGroup
145+
Export:
146+
Name: !Join ['-', [!Ref EnvName, 'ECSSecurityGroup']]
147+
148+
ECSCluster:
149+
Description: ECS Cluster
150+
Value: !Ref ECSCluster
151+
Export:
152+
Name: !Join ['-', [!Ref EnvName, 'ECSCluster']]
153+
154+
TargetGroup:
155+
Description: Target Group
156+
Value: !Ref TargetGroup
157+
Export:
158+
Name: !Join ['-', [!Ref EnvName, 'TargetGroup']]
159+
160+
TaskDefinition:
161+
Description: Task Definition
162+
Value: !Ref TaskDefinition
163+
Export:
164+
Name: !Join ['-', [!Ref EnvName, 'TaskDefinition']]

0 commit comments

Comments
 (0)