31 | {% block content %}
32 |
33 | {% endblock content %}
34 |
35 |
36 |
37 |
40 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/blog/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.contrib.auth.models import User
3 |
4 |
5 | def user_directory_path(instance, filename):
6 | return 'blog/{0}/{1}'.format(instance.author.id, filename)
7 |
8 |
9 | # class Category(models.Model):
10 | # name = models.CharField(max_length=100)
11 |
12 | # class Meta:
13 | # verbose_name_plural = "Categories"
14 |
15 | # def __str__(self):
16 | # return self.name
17 |
18 |
19 | class Post(models.Model):
20 | OPTIONS = (
21 | ('d', 'Draft'),
22 | ('p', 'Published')
23 | )
24 | CATEGORY_OPT = (
25 | ('e', 'Entertainment'),
26 | ('m', 'Music'),
27 | ('i', 'IT')
28 | )
29 | title = models.CharField(max_length=100)
30 | content = models.TextField()
31 | image = models.ImageField(
32 | upload_to=user_directory_path, default='django.jpg')
33 | category = models.CharField(max_length=15, choices=CATEGORY_OPT, default='e')
34 | publish_date = models.DateTimeField(auto_now_add=True)
35 | last_updated = models.DateTimeField(auto_now=True)
36 | author = models.ForeignKey(User, on_delete=models.CASCADE)
37 | status = models.CharField(max_length=10, choices=OPTIONS, default='d')
38 | slug = models.SlugField(blank=True, unique=True) # how-to-learn-django
39 |
40 | def __str__(self):
41 | return self.title
42 |
43 | def comment_count(self):
44 | return self.comment_set.all().count()
45 |
46 | def view_count(self):
47 | return self.postview_set.all().count()
48 |
49 | def like_count(self):
50 | return self.like_set.all().count()
51 |
52 | def comments(self):
53 | return self.comment_set.all()
54 |
55 |
56 | class Comment(models.Model):
57 | user = models.ForeignKey(User, on_delete=models.CASCADE)
58 | post = models.ForeignKey(Post, on_delete=models.CASCADE)
59 | time_stamp = models.DateTimeField(auto_now_add=True)
60 | content = models.TextField()
61 |
62 | def __str__(self):
63 | return self.user.username
64 |
65 |
66 | class Like(models.Model):
67 | user = models.ForeignKey(User, on_delete=models.CASCADE)
68 | post = models.ForeignKey(Post, on_delete=models.CASCADE)
69 |
70 | def __str__(self):
71 | return self.user.username
72 |
73 |
74 | class PostView(models.Model):
75 | user = models.ForeignKey(User, on_delete=models.CASCADE)
76 | post = models.ForeignKey(Post, on_delete=models.CASCADE)
77 | time_stamp = models.DateTimeField(auto_now_add=True)
78 |
79 | def __str__(self):
80 | return self.user.username
81 |
--------------------------------------------------------------------------------
/src/blog/templates/blog/post_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load crispy_forms_tags %}
3 | {% block content %}
4 |
5 |

6 |
7 |
{{ object.title }}
8 |
9 |
10 | {{ object.comment_count }}
11 | {{ object.view_count }}
12 | {{ object.like_count }}
13 | Posted {{ object.publish_date|timesince }} ago.
14 |
15 |
16 |
{{ object.content }}.
17 |
18 |
19 |
Enjoy this post? Give it a LIKE!!
20 |
21 |
22 |
23 |
33 |
34 |
35 |
Leave a comment below
36 |
41 |
42 |
Comments
43 | {% for comment in object.comments %}
44 |
45 |
46 | Comment by {{user.username}} - {{ comment.time_stamp|timesince }} ago.
47 |
48 |
49 |
50 | {{ comment.content }}
51 |
52 |
53 |
54 |
55 | {% endfor %}
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | {% if user.id == object.author.id %}
65 |
Edit
66 |
Delete
67 | {% endif %}
68 |
69 | {% endblock content %}
--------------------------------------------------------------------------------
/src/blog/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.4 on 2021-02-28 14:30
2 |
3 | import blog.models
4 | from django.conf import settings
5 | from django.db import migrations, models
6 | import django.db.models.deletion
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | initial = True
12 |
13 | dependencies = [
14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
15 | ]
16 |
17 | operations = [
18 | migrations.CreateModel(
19 | name='Category',
20 | fields=[
21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22 | ('name', models.CharField(max_length=100)),
23 | ],
24 | options={
25 | 'verbose_name_plural': 'Categories',
26 | },
27 | ),
28 | migrations.CreateModel(
29 | name='Post',
30 | fields=[
31 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
32 | ('title', models.CharField(max_length=100)),
33 | ('content', models.TextField()),
34 | ('image', models.ImageField(default='django.jpg', upload_to=blog.models.user_directory_path)),
35 | ('publish_date', models.DateTimeField(auto_now_add=True)),
36 | ('last_updated', models.DateTimeField(auto_now=True)),
37 | ('status', models.CharField(choices=[('d', 'Draft'), ('p', 'Published')], default='d', max_length=10)),
38 | ('slug', models.SlugField(blank=True, unique=True)),
39 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
40 | ('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='cats', to='blog.category')),
41 | ],
42 | ),
43 | migrations.CreateModel(
44 | name='PostView',
45 | fields=[
46 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
47 | ('time_stamp', models.DateTimeField(auto_now_add=True)),
48 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.post')),
49 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
50 | ],
51 | ),
52 | migrations.CreateModel(
53 | name='Like',
54 | fields=[
55 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
56 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.post')),
57 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
58 | ],
59 | ),
60 | migrations.CreateModel(
61 | name='Comment',
62 | fields=[
63 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
64 | ('time_stamp', models.DateTimeField(auto_now_add=True)),
65 | ('content', models.TextField()),
66 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.post')),
67 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
68 | ],
69 | ),
70 | ]
71 |
--------------------------------------------------------------------------------
/src/blog/views.py:
--------------------------------------------------------------------------------
1 | # from django.http.response import HttpResponse
2 | from django.shortcuts import get_object_or_404, redirect, render
3 | from .models import Post, Like, PostView
4 | from .forms import CommentForm, PostForm
5 | from django.contrib.auth.decorators import login_required
6 | from django.contrib import messages
7 |
8 |
9 | def post_list(request):
10 | qs = Post.objects.filter(status='p')
11 | context = {
12 | "object_list": qs
13 | }
14 | return render(request, "blog/post_list.html", context)
15 |
16 |
17 | @login_required()
18 | def post_create(request):
19 | # form = PostForm(request.POST or None, request.FILES or None)
20 |
21 | form = PostForm()
22 | if request.method == "POST":
23 | form = PostForm(request.POST, request.FILES)
24 | if form.is_valid():
25 | post = form.save(commit=False)
26 | post.author = request.user
27 | post.save()
28 | messages.success(request, "Post created succesfully!")
29 | return redirect("blog:list")
30 | context = {
31 | 'form': form
32 | }
33 | return render(request, "blog/post_create.html", context)
34 |
35 |
36 | def post_detail(request, slug):
37 | # print(request.get_host())
38 | # print(slug)
39 | # Post.objects.get(slug=learn-drf-3c78be2186)
40 | # slug = learn-drf-3c78be2186
41 | form = CommentForm()
42 | obj = get_object_or_404(Post, slug=slug)
43 | if request.user.is_authenticated:
44 | PostView.objects.create(user=request.user, post=obj)
45 | if request.method == "POST":
46 | form = CommentForm(request.POST)
47 | if form.is_valid:
48 | comment = form.save(commit=False)
49 | comment.user = request.user
50 | comment.post = obj
51 | comment.save()
52 | return redirect("blog:detail", slug=slug)
53 | # return redirect(request.path)
54 | context = {
55 | "object": obj,
56 | "form": form
57 | }
58 | return render(request, "blog/post_detail.html", context)
59 |
60 |
61 | @login_required()
62 | def post_update(request, slug):
63 | obj = get_object_or_404(Post, slug=slug)
64 | form = PostForm(request.POST or None, request.FILES or None, instance=obj)
65 | if request.user != obj.author:
66 | messages.warning(request, "You're not a writer of this post")
67 | return redirect('blog:list')
68 | if form.is_valid():
69 | form.save()
70 | messages.success(request, "Post updated!!")
71 | return redirect("blog:list")
72 |
73 | context = {
74 | "object": obj,
75 | "form": form
76 | }
77 | return render(request, "blog/post_update.html", context)
78 |
79 |
80 | @login_required()
81 | def post_delete(request, slug):
82 | obj = get_object_or_404(Post, slug=slug)
83 |
84 | if request.user.id != obj.author.id:
85 | messages.warning(request, "You're not a writer of this post")
86 | return redirect('blog:list')
87 | if request.method == "POST":
88 | obj.delete()
89 | messages.success(request, "Post deleted!!")
90 | return redirect("blog:list")
91 | context = {
92 | "object": obj
93 | }
94 | return render(request, "blog/post_delete.html", context)
95 |
96 |
97 | @login_required()
98 | def like(request, slug):
99 | if request.method == "POST":
100 | obj = get_object_or_404(Post, slug=slug)
101 | like_qs = Like.objects.filter(user=request.user, post=obj)
102 | if like_qs:
103 | like_qs.delete()
104 | else:
105 | Like.objects.create(user=request.user, post=obj)
106 | return redirect('blog:detail', slug=slug)
107 | return redirect('blog:detail', slug=slug)
108 |
--------------------------------------------------------------------------------
/src/cblog/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for cblog project.
3 |
4 | Generated by 'django-admin startproject' using Django 3.1.4.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.1/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/3.1/ref/settings/
11 | """
12 |
13 | from pathlib import Path
14 | from decouple import config
15 | import os
16 |
17 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
18 | BASE_DIR = Path(__file__).resolve().parent.parent
19 |
20 |
21 | # Quick-start development settings - unsuitable for production
22 | # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
23 |
24 | # SECURITY WARNING: keep the secret key used in production secret!
25 | SECRET_KEY = config('SECRET_KEY')
26 |
27 | # SECURITY WARNING: don't run with debug turned on in production!
28 | DEBUG = False
29 |
30 | ALLOWED_HOSTS = ['*']
31 |
32 |
33 | # Application definition
34 |
35 | INSTALLED_APPS = [
36 | 'django.contrib.admin',
37 | 'django.contrib.auth',
38 | 'django.contrib.contenttypes',
39 | 'django.contrib.sessions',
40 | 'django.contrib.messages',
41 | 'django.contrib.staticfiles',
42 | # my_apps
43 | 'blog.apps.BlogConfig',
44 | 'users.apps.UsersConfig',
45 |
46 | # third party
47 | 'crispy_forms',
48 | 'storages'
49 | ]
50 |
51 | MIDDLEWARE = [
52 | 'django.middleware.security.SecurityMiddleware',
53 | 'django.contrib.sessions.middleware.SessionMiddleware',
54 | 'django.middleware.common.CommonMiddleware',
55 | 'django.middleware.csrf.CsrfViewMiddleware',
56 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
57 | 'django.contrib.messages.middleware.MessageMiddleware',
58 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
59 | ]
60 |
61 | ROOT_URLCONF = 'cblog.urls'
62 |
63 | TEMPLATES = [
64 | {
65 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
66 | 'DIRS': [BASE_DIR, "templates"],
67 | 'APP_DIRS': True,
68 | 'OPTIONS': {
69 | 'context_processors': [
70 | 'django.template.context_processors.debug',
71 | 'django.template.context_processors.request',
72 | 'django.contrib.auth.context_processors.auth',
73 | 'django.contrib.messages.context_processors.messages',
74 | ],
75 | },
76 | },
77 | ]
78 |
79 | WSGI_APPLICATION = 'cblog.wsgi.application'
80 |
81 |
82 | # Database
83 | # https://docs.djangoproject.com/en/3.1/ref/settings/#databases
84 |
85 | DATABASES = {
86 | 'default': {
87 | 'ENGINE': 'django.db.backends.mysql',
88 | 'NAME': 'database1', # database name in RDS is written here
89 | 'USER': 'admin', # database master username in RDS is written here
90 | 'PASSWORD': config('PASSWORD'),
91 | # database endpoint is written here
92 | 'HOST': 'aws-capstone-rds.cukd79ofsohr.us-east-1.rds.amazonaws.com',
93 | 'PORT': '3306' # database port number is written here
94 | }
95 | }
96 |
97 |
98 | # Password validation
99 | # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
100 |
101 | AUTH_PASSWORD_VALIDATORS = [
102 | {
103 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
104 | },
105 | {
106 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
107 | },
108 | {
109 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
110 | },
111 | {
112 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
113 | },
114 | ]
115 |
116 |
117 | # Internationalization
118 | # https://docs.djangoproject.com/en/3.1/topics/i18n/
119 |
120 | LANGUAGE_CODE = 'en-us'
121 |
122 | TIME_ZONE = 'UTC'
123 |
124 | USE_I18N = True
125 |
126 | USE_L10N = True
127 |
128 | USE_TZ = True
129 |
130 |
131 | # Static files (CSS, JavaScript, Images)
132 | # https://docs.djangoproject.com/en/3.1/howto/static-files/
133 |
134 | STATIC_URL = '/static/'
135 | MEDIA_URL = '/media/'
136 | STATICFILES_DIRS = [BASE_DIR / "static"]
137 |
138 | MEDIA_ROOT = BASE_DIR / "media_root"
139 |
140 | CRISPY_TEMPLATE_PACK = 'bootstrap4'
141 |
142 | LOGIN_REDIRECT_URL = "blog:list"
143 |
144 | LOGIN_URL = "login"
145 |
146 |
147 | AWS_STORAGE_BUCKET_NAME = 'enesblog' # please enter your s3 bucket name
148 | AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
149 | AWS_S3_REGION_NAME = "us-east-1" # please enter your s3 region
150 | AWS_DEFAULT_ACL = 'public-read'
151 |
152 | AWS_LOCATION = 'static'
153 | STATICFILES_DIRS = [
154 | os.path.join(BASE_DIR, 'static'),
155 | ]
156 |
157 | STATIC_URL = 'https://%s/%s/' % (AWS_S3_CUSTOM_DOMAIN, AWS_LOCATION)
158 | STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
159 | DEFAULT_FILE_STORAGE = 'cblog.storages.MediaStore'
160 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Django Blog Page Application deployed on AWS Application Load Balancer with Auto Scaling, S3, Relational Database Service(RDS), VPC's Components, Lambda, DynamoDB and CloudFront with Route 53
2 |
3 | ## Description
4 |
5 | The Blog Page Application aims to deploy blog application as a web application written Django Framework on AWS Cloud Infrastructure. This infrastructure has Application Load Balancer with Auto Scaling Group of Elastic Compute Cloud (EC2) Instances and Relational Database Service (RDS) on defined VPC. Also, The CloudFront and Route 53 services are located in front of the architecture and manage the traffic in secure. User is able to upload pictures and videos on own blog page and these are kept on S3 Bucket.
6 |
7 | ## Project Details
8 |
9 | 
10 |
11 | - Your company has recently ended up a project that aims to serve as Blog web application on isolated VPC environment. You and your colleagues have started to work on the project. Your Developer team has developed the application and you are going to deploy the app in production environment.
12 |
13 | - Application is coded by Fullstack development team and given you as DevOps team. App allows users to write their own blog page to whom user registration data should be kept in separate MySQL database in AWS RDS service and pictures or videos should be kept in S3 bucket. The object list of S3 Bucket containing movies and videos is recorded on DynamoDB table.
14 |
15 | - The web application will be deployed using Django framework.
16 |
17 | - The Web Application should be accessible via web browser from anywhere in secure.
18 |
19 | - You are requested to push your program to the project repository on the Github. You are going to pull it into the webservers in the production environment on AWS Cloud.
20 |
21 | In the architecture, you can configure your infrastructure using the followings,
22 |
23 | - The application stack should be created with new AWS resources.
24 |
25 | - Specifications of VPC:
26 |
27 | - VPC has two AZs and every AZ has 1 public and 1 private subnets.
28 |
29 | - VPC has Internet Gateway
30 |
31 | - One of public subnets has NAT Instance.
32 |
33 | - You might create new instance as Bastion host on Public subnet or you can use NAT instance as Bastion host.
34 |
35 | - There should be managed private and public route tables.
36 |
37 | - Route tables should be arranged regarding of routing policies and subnet associations based on public and private subnets.
38 |
39 | - You should create Application Load Balancer with Auto Scaling Group of Ubuntu 18.04 EC2 Instances within created VPC.
40 |
41 | - You should create RDS instance within one of private subnets on created VPC and configure it on application.
42 |
43 | - The Auto Scaling Group should use a Launch Template in order to launch instances needed and should be configured to;
44 |
45 | - use all Availability Zones on created VPC.
46 |
47 | - set desired capacity of instances to ` 2`
48 |
49 | - set minimum size of instances to ` 2`
50 |
51 | - set maximum size of instances to ` 4`
52 |
53 | - set health check grace period to ` 90 seconds`
54 |
55 | - set health check type to ` ELB`
56 |
57 | - Scaling Policy --> Target Tracking Policy
58 |
59 | - Average CPU utilization (set Target Value ` %70`)
60 |
61 | - seconds warm up before including in metric ---> `200`
62 |
63 | - Set notification to your email address for launch, terminate, fail to launch, fail to terminate instance situations
64 |
65 | - ALB configuration;
66 |
67 | - Application Load Balancer should be placed within a security group which allows HTTP (80) and HTTPS (443) connections from anywhere.
68 |
69 | - Certification should be created for secure connection (HTTPS)
70 |
71 | - To create certificate, AWS Certificate Manager can be utilized.
72 |
73 | - ALB redirects to traffic from HTTP to HTTPS
74 |
75 | - Target Group
76 | - Health Check Protocol is going to be HTTP
77 |
78 | - The Launch Template should be configured to;
79 |
80 | - Prepare Django environment on EC2 instance based on Developer Notes,
81 |
82 | - Deploy the Django application on port 80.
83 |
84 | - Launch Template only allows HTTP (80) and HTTPS (443) ports coming from ALB Security Group and SSH (22) connections from anywhere.
85 |
86 | - EC2 Instances type can be configured as `t2.micro`.
87 |
88 | - Instance launched should be tagged `AWS Capstone Project`
89 |
90 | - Since Django App needs to talk with S3, S3 full access role must be attached EC2s.
91 |
92 | - For RDS Database Instance;
93 |
94 | - Instance type can be configured as `db.t2.micro`
95 |
96 | - Database engine can be `MySQL` with version of `8.0.20`.
97 |
98 | - RDS endpoint should be addressed within settings file of blog application that is explained developer notes.
99 |
100 | - Please read carefully "Developer notes" to manage RDS sub settings.
101 |
102 | - CloudFront should be set as a cache server which points to Application Load Balance with following configurations;
103 |
104 | - The CloudFront distribution should communicate with ALB securely.
105 |
106 | - Origin Protocol policy can be selected as `HTTPS only`.
107 |
108 | - Viewer Protocol Policy can be selected as `Redirect HTTP to HTTPS`
109 |
110 | - As cache behavior;
111 |
112 | - GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE methods should be allowed.
113 |
114 | - Forward Cookies must be selected All.
115 |
116 | - Newly created ACM Certificate should be used for securing connections. (You can use same certificate with ALB)
117 |
118 | - Route 53
119 |
120 | - Connection must be secure (HTTPS).
121 |
122 | - Your hostname can be used to publish website.
123 |
124 | - Failover routing policy should be set while publishing application
125 |
126 | - Primary connection is going to be CloudFormation
127 |
128 | - Secondary connection is going to be a static website placed another S3 bucket. This S3 bucket has just basic static website that has a picture said "the page is under construction" given files within S3_static_Website folder
129 |
130 | - Healthcheck should check If CloudFront is healthy or not.
131 |
132 | - As S3 Bucket
133 |
134 | - First S3 Bucket
135 |
136 | - It should be created within the Region that you created VPC
137 |
138 | - Since development team doesn't prefer to expose traffic between S3 and EC2s on internet, Endpoint should be set on created VPC.
139 |
140 | - S3 Bucket name should be addressed within configuration file of blog application that is explained developer notes.
141 |
142 | - Second S3 Bucket
143 |
144 | - This Bucket is going to be used for failover scenario. It has just a basic static website that has a picture said "the page is under construction"
145 |
146 | - To write the objects of S3 on DynamoDB table
147 |
148 | - Lambda Function
149 |
150 | - Lambda function is going to be Python 3.8
151 |
152 | - Python Function can be found in github repo
153 |
154 | - S3 event is set as trigger
155 |
156 | - Since Lambda needs to talk S3 and DynamoDB and to run on created VPC, S3, DynamoDB full access policies and NetworkAdministrator policy must be attached it
157 |
158 | - `S3 Event` must be created first S3 Bucket to trigger Lambda function
159 |
160 | - DynamoDB Table
161 |
162 | - Create a DynamoDB table which has primary key that is `id`
163 |
164 | - Created DynamoDB table's name should be placed on Lambda function.
165 |
166 | ## Expected Outcome
167 |
168 | 
169 |
170 | ### The following topics will be used at the end of the project:
171 |
172 | - Bash scripting
173 |
174 | - AWS EC2 Launch Template Configuration
175 |
176 | - AWS VPC Configuration
177 |
178 | - VPC
179 | - Private and Public Subnets
180 | - Private and Public Route Tables
181 | - Managing routes
182 | - Subnet Associations
183 | - Internet Gateway
184 | - NAT Gateway
185 | - Bastion Host
186 | - Endpoint
187 |
188 | - AWS EC2 Application Load Balancer Configuration
189 |
190 | - AWS EC2 ALB Target Group Configuration
191 |
192 | - AWS EC2 ALB Listener Configuration
193 |
194 | - AWS EC2 Auto Scaling Group Configuration
195 |
196 | - AWS Relational Database Service Configuration
197 |
198 | - AWS EC2, RDS, ALB Security Groups Configuration
199 |
200 | - IAM Roles configuration
201 |
202 | - S3 configuration
203 |
204 | - Static website configuration on S3
205 |
206 | - DynamoDB Table configuration
207 |
208 | - Lambda Function configuration
209 |
210 | - Get Certificate with AWS Certification Manager Configuration
211 |
212 | - AWS CloudFront Configuration
213 |
214 | - Route 53 Configuration
215 |
216 | - Git & Github for Version Control System
217 |
218 | ### At the end of the project, you will be able to;
219 |
220 | - Construct VPC environment with whole components like public and private subnets, route tables and managing their routes, internet Gateway, NAT Instance.
221 |
222 | - Apply web programming skills, importing packages within Python Django Framework
223 |
224 | - Configure connection to the `MySQL` database.
225 |
226 | - Demonstrate bash scripting skills using `user data` section within launch template to install and setup Blog web application on EC2 Instance.
227 |
228 | - Create a Lambda function using S3, Lambda and DynamoDB table.
229 |
230 | - Demonstrate their configuration skills of AWS VPC, EC2 Launch Templates, Application Load Balancer, ALB Target Group, ALB Listener, Auto Scaling Group, S3, RDS, CloudFront, Route 53.
231 |
232 | - Apply git commands (push, pull, commit, add etc.) and Github as Version Control System.
233 |
234 | ## Solution Steps
235 |
236 | - Step 1: Create dedicated VPC and whole components
237 |
238 | - Step 2: Create Security Groups (ALB ---> EC2 ---> RDS)
239 |
240 | - Step 3: Create RDS
241 |
242 | - Step 4: Create two S3 Buckets and set one of these as static website
243 |
244 | - Step 5: Download or clone project definition
245 |
246 | - Step 6: Prepare your Github repository
247 |
248 | - Step 7: Prepare a userdata to be utilized in Launch Template
249 |
250 | - Step 8: Write RDS, S3 in settings file given by Fullstack Developer team
251 |
252 | - Step 9: Create NAT Instance in Public Subnet
253 |
254 | - Step 10: Create Launch Template and IAM role for it
255 |
256 | - Step 11: Create certification for secure connection
257 |
258 | - Step 12: Create ALB and Target Group
259 |
260 | - Step 13: Create Autoscaling Group with Launch Template
261 |
262 | - Step 14: Create CloudFront in front of ALB
263 |
264 | - Step 15: Create Route 53 with Failover settings
265 |
266 | - Step 16: Create DynamoDB Table
267 |
268 | - Step 17-18: Create Lambda function
269 |
270 | - Step 17-18: Create S3 Event and set it as trigger for Lambda Function
271 |
272 | ## Notes
273 |
274 | - RDS database should be located in private subnet. just EC2 machines that has ALB security group can talk with RDS.
275 |
276 | - RDS is located on private groups and only EC2s can talk with it on port 3306
277 |
278 | - ALB is located public subnet and it redirects traffic from http to https
279 |
280 | - EC2's are located in private subnets and only ALB can talk with them
281 |
282 | ## Resources
283 |
284 | - [Python Django Framework](https://www.djangoproject.com/)
285 |
286 | - [Python Django Example](https://realpython.com/get-started-with-django-1/)
287 |
288 | - [AWS CLI Command Reference](https://docs.aws.amazon.com/cli/latest/index.html)
289 |
--------------------------------------------------------------------------------