From 3e1fbd14e2652f156fe4e2953fc57c4abee6f52f Mon Sep 17 00:00:00 2001 From: Hyun Sim Date: Sat, 2 Mar 2024 11:58:21 +0000 Subject: [PATCH] still testing --- .gitignore | 1 + cloudfront.tf | 88 +++++++++++++++++++++++++++ ec2.tf | 132 +++++++++++++++++++++++++++++++++++++++++ elb.tf | 10 ++++ iam.tf | 27 +++++++++ providers.tf | 14 +++++ rds.tf | 12 ++++ resizer/app.js | 90 ++++++++++++++++++++++++++++ resizer/package.json | 12 ++++ route53.tf | 27 +++++++++ s3.tf | 36 +++++++++++ sqs.tf | 5 ++ vpc.tf | 68 +++++++++++++++++++++ webserver/app.js | 87 +++++++++++++++++++++++++++ webserver/index.html | 51 ++++++++++++++++ webserver/package.json | 13 ++++ 16 files changed, 673 insertions(+) create mode 100644 .gitignore create mode 100644 cloudfront.tf create mode 100644 ec2.tf create mode 100644 elb.tf create mode 100644 iam.tf create mode 100644 providers.tf create mode 100644 rds.tf create mode 100644 resizer/app.js create mode 100644 resizer/package.json create mode 100644 route53.tf create mode 100644 s3.tf create mode 100644 sqs.tf create mode 100644 vpc.tf create mode 100644 webserver/app.js create mode 100644 webserver/index.html create mode 100644 webserver/package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..66df410 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.terraform* \ No newline at end of file diff --git a/cloudfront.tf b/cloudfront.tf new file mode 100644 index 0000000..7e06744 --- /dev/null +++ b/cloudfront.tf @@ -0,0 +1,88 @@ +resource "aws_cloudfront_distribution" "photogram_CF" { + origin { + domain_name = aws_elb.photogram_ELB.dns_name + origin_id = "photogram_ELB_origin" + } + + default_cache_behavior { + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + target_origin_id = "photogram_ELB_origin" + + forwarded_values { + query_string = false + cookies { + forward = "none" + } + } + + viewer_protocol_policy = "redirect-to-https" + cached_methods = ["GET", "HEAD"] + } + + viewer_certificate { + cloudfront_default_certificate = true + } + + restrictions { + geo_restriction { + restriction_type = "none" + } + } + + aliases = ["photogram.0x0.kr"] + + enabled = true + is_ipv6_enabled = true + price_class = "PriceClass_100" + default_root_object = "index.html" +} + + + + +resource "aws_cloudfront_origin_access_identity" "photogram_oai" { + comment = "Allows CloudFront to access the S3 bucket" +} + +resource "aws_cloudfront_distribution" "photogram_image_CF" { + origin { + domain_name = aws_s3_bucket.photogram_image.bucket_regional_domain_name + origin_id = "photogram_image_bucket_origin" + + s3_origin_config { + origin_access_identity = aws_cloudfront_origin_access_identity.photogram_oai.cloudfront_access_identity_path + } + } + + default_cache_behavior { + allowed_methods = ["GET", "HEAD"] + cached_methods = ["GET", "HEAD"] + target_origin_id = "photogram_image_bucket_origin" + + forwarded_values { + query_string = false + cookies { + forward = "none" + } + } + + viewer_protocol_policy = "redirect-to-https" + } + + viewer_certificate { + cloudfront_default_certificate = true + } + + restrictions { + geo_restriction { + restriction_type = "none" + } + } + + aliases = ["photogram-image.0x0.kr"] + + enabled = true + is_ipv6_enabled = true + price_class = "PriceClass_100" + default_root_object = "index.html" +} \ No newline at end of file diff --git a/ec2.tf b/ec2.tf new file mode 100644 index 0000000..7ebe8eb --- /dev/null +++ b/ec2.tf @@ -0,0 +1,132 @@ +//webserver ec2 instance +resource "aws_instance" "photogram_webserver" { + ami = "ami-0440d3b780d96b29d" # Replace with your desired AMI ID + instance_type = "t2.micro" # Or any instance type you prefer + + provisioner "remote-exec" { + inline = [ + "sudo yum install -y nodejs npm --enablerepo=epel", + "sudo npm install -g forever", + "mkdir web", + "aws s3 sync --region=ap-northeast-1 s3://photogram.src/web web", + "cd web", + "npm install" + ] + } +} + +resource "aws_security_group" "photogram_webserver_sg" { + name = "photogram-webserver-sg" + description = "Security group for Photogram web servers" + vpc_id = aws_vpc.photogram_vpc.id + + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 65535 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_security_group" "photogram_asg_sg" { + name = "photogram-asg-sg" + description = "Security group for Photogram auto scaling groups" + vpc_id = aws_vpc.photogram_vpc.id + + ingress { + from_port = 22 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 65535 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_ami_from_instance" "photogram_webserver_ami" { + name = "photogram_webserver_ami" + source_instance_id = aws_instance.photogram_webserver.id +} + +resource "aws_launch_configuration" "photogram_webserver_lc" { + name = "photogram_webserver_lc" + image_id = aws_ami_from_instance.photogram_webserver_ami.id + instance_type = "t2.micro" + security_groups = [aws_security_group.photogram_webserver_sg.id] # Specify your security group ID + key_name = "your_key_pair" # Specify your key pair name + user_data = <<-EOF + #!/bin/bash + cd /home/ec2-user + aws s3 sync --region=ap-northeast-1 \ + s3://examplephoto.src/ExamplePhotoWebServer ExamplePhotoWebServer + cd ExamplePhotoWebServer + npm install + forever start -w app.js + EOF +} + +resource "aws_autoscaling_group" "photogram_webserver_asg" { + name = "photogram_webserver_asg" + launch_configuration = aws_launch_configuration.photogram_webserver_lc.name + min_size = 1 # Minimum number of instances + max_size = 5 # Maximum number of instances + desired_capacity = 2 # Desired number of instances + vpc_zone_identifier = [aws_subnet.photogram_subnet.id] # Specify your subnet IDs # Specify your target group ARN if using ALB/NLB + load_balancers = [aws_elb.photogram_ELB.name] + enabled_metrics = ["GroupMinSize", "GroupMaxSize", "GroupDesiredCapacity", "GroupInServiceInstances", "GroupPendingInstances", "GroupStandbyInstances", "GroupTerminatingInstances", "GroupTotalInstances"] + metrics_granularity = "1Minute" +} + + +resource "aws_autoscaling_policy" "cpu_scaling_policy" { + name = "cpu_scaling_policy" + scaling_adjustment = 1 + adjustment_type = "ChangeInCapacity" + cooldown = 300 + autoscaling_group_name = aws_autoscaling_group.photogram_webserver_asg.name + + target_tracking_configuration { + predefined_metric_specification { + predefined_metric_type = "ASGAverageCPUUtilization" + } + target_value = 80 # Set the desired target value (e.g., 50 for 50% CPU utilization) + } + + +} + +//resizer ec2 instance +resource "aws_instance" "photogram_resizer" { + ami = "ami-0440d3b780d96b29d" # Replace with your desired AMI ID + instance_type = "t2.micro" # Or any instance type you prefer + iam_instance_profile = aws_iam_instance_profile.photogram_resizer_profile.name + + provisioner "remote-exec" { + inline = [ + "sudo yum install -y nodejs npm --enablerepo=epel", + "sudo yum install -y ImageMagick", + "sudo npm install -g forever", + "mkdir resize", + "aws s3 sync --region=ap-northeast-1 s3://photogram.src/resize resize", + "cd resize", + "npm install", + "forever start -w app.js" + ] + } +} +resource "aws_iam_instance_profile" "photogram_resizer_profile" { + name = "photogram_resizer_profile" + role = aws_iam_role.photogram_Role.name +} \ No newline at end of file diff --git a/elb.tf b/elb.tf new file mode 100644 index 0000000..a534da2 --- /dev/null +++ b/elb.tf @@ -0,0 +1,10 @@ +resource "aws_elb" "photogram_ELB" { + name = "photogram-ELB" + availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"] // Add appropriate availability zones + listener { + instance_port = 80 + instance_protocol = "HTTP" + lb_port = 80 + lb_protocol = "HTTP" + } +} diff --git a/iam.tf b/iam.tf new file mode 100644 index 0000000..d93a95b --- /dev/null +++ b/iam.tf @@ -0,0 +1,27 @@ +resource "aws_iam_role" "photogram_Role" { + name = "photogram_Role" + assume_role_policy = jsonencode({ + "Version" : "2012-10-17", + "Statement" : [ + { + "Effect" : "Allow", + "Principal" : { + "Service" : "ec2.amazonaws.com" + }, + "Action" : "sts:AssumeRole" + } + ] + }) +} + +resource "aws_iam_policy_attachment" "s3_full_access" { + name = "photogram_S3_Full_Access" + roles = [aws_iam_role.photogram_Role.name] + policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess" +} + +resource "aws_iam_policy_attachment" "sqs_full_access" { + name = "photogram_SQS_Full_Access" + roles = [aws_iam_role.photogram_Role.name] + policy_arn = "arn:aws:iam::aws:policy/AmazonSQSFullAccess" +} diff --git a/providers.tf b/providers.tf new file mode 100644 index 0000000..435e2b6 --- /dev/null +++ b/providers.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + } + } +} + +# Configure the AWS Provider +provider "aws" { + region = "us-east-1" + shared_credentials_files = ["~/.aws/credentials"] + profile = "terra" +} diff --git a/rds.tf b/rds.tf new file mode 100644 index 0000000..7f1d1bb --- /dev/null +++ b/rds.tf @@ -0,0 +1,12 @@ +resource "aws_db_instance" "photogram_DB" { + allocated_storage = 10 + db_name = "photogram" + engine = "mysql" + engine_version = "5.7" + instance_class = "db.t3.micro" + username = "admin" + password = "Qwer1234**" + parameter_group_name = "default.mysql5.7" + vpc_security_group_ids = [aws_security_group.allow_mysql.id] + skip_final_snapshot = true +} \ No newline at end of file diff --git a/resizer/app.js b/resizer/app.js new file mode 100644 index 0000000..a7c034f --- /dev/null +++ b/resizer/app.js @@ -0,0 +1,90 @@ +var AWS = require('aws-sdk') + , Sequelize = require('sequelize') + , im = require('imagemagick') + , mime = require('mime') + , s3 = new AWS.S3({ region: 'ap-northeast-1' }) + , sqs = new AWS.SQS({ region: 'ap-northeast-1' }); + +var s3Bucket = 'examplephoto.image'; +var sqsQueueUrl = 'https://sqs.ap-northeast-1.amazonaws.com/232075047203/ExamplePhotoQueue'; +var rdsEndpoint = { + host: 'examplephoto.cnlconsezo7y.ap-northeast-1.rds.amazonaws.com', + port: 3306 +}; + +// MySQL DB 이름, 계정, 암호 +var sequelize = new Sequelize('examplephoto', 'admin', 'adminpassword', { + host: rdsEndpoint.host, + port: rdsEndpoint.port +}); + +// MySQL DB 테이블 정의 +var Photo = sequelize.define('Photo', { + filename: { type: Sequelize.STRING, allowNull: false, unique: true } +}); + +// SQS 메시지 삭제 +function deleteMessage(ReceiptHandle) { + sqs.deleteMessage({ + QueueUrl: sqsQueueUrl, + ReceiptHandle: ReceiptHandle + }, function (err, data) { + if (err) + console.log(err, err.stack); + else + console.log(data); + }); +} + +// MySQL에 데이터 저장 +function insertPhoto(filename) { + sequelize.sync().success(function () { + Photo.create({ + filename: filename + }); + }); +} + +// SQS 메시지 받기 +function receiveMessage() { + sqs.receiveMessage({ + QueueUrl: sqsQueueUrl, + MaxNumberOfMessages: 1, + VisibilityTimeout: 10, + WaitTimeSeconds: 10 + }, function (err, data) { + if (!err && data.Messages && data.Messages.length > 0) + resizeImage(data.Messages[0]); + else if (err) + console.log(err, err.stack); + receiveMessage(); + }); +} + +// 이미지 해상도 변환 +function resizeImage(Message) { + var filename = Message.Body; + s3.getObject({ + Bucket: s3Bucket, + Key: 'original/' + filename + }, function (err, data) { + im.resize({ + srcData: data.Body, + width: 800 + }, function (err, stdout, stderr) { + s3.putObject({ + Bucket: s3Bucket, + Key: 'resized/' + filename, + Body: new Buffer(stdout, 'binary'), + ACL: 'public-read', + ContentType: mime.lookup(filename) + }, function (err, data) { + console.log('Complete resize ' + filename); + deleteMessage(Message.ReceiptHandle); + insertPhoto(filename); + }); + }); + }); +} + +receiveMessage(); \ No newline at end of file diff --git a/resizer/package.json b/resizer/package.json new file mode 100644 index 0000000..c1275d3 --- /dev/null +++ b/resizer/package.json @@ -0,0 +1,12 @@ +{ + "name": "ExamplePhotoResizeServer", + "version": "0.0.1", + "description": "ExamplePhotoResizeServer", + "dependencies": { + "aws-sdk": "2.0.x", + "mime": "1.2.x", + "sequelize": "1.7.x", + "mysql": "2.3.2", + "imagemagick": "0.1.x" + } +} \ No newline at end of file diff --git a/route53.tf b/route53.tf new file mode 100644 index 0000000..c5a94e2 --- /dev/null +++ b/route53.tf @@ -0,0 +1,27 @@ +resource "aws_route53_zone" "photogram_zone" { + name = "0x0.kr" +} + +resource "aws_route53_record" "photogram_alias" { + zone_id = aws_route53_zone.photogram_zone.zone_id + name = "photogram.0x0.kr" + type = "A" + + alias { + name = aws_cloudfront_distribution.photogram_CF.domain_name + zone_id = aws_cloudfront_distribution.photogram_CF.hosted_zone_id + evaluate_target_health = false + } +} + +resource "aws_route53_record" "photogram_image_alias" { + zone_id = aws_route53_zone.photogram_zone.zone_id + name = "photogram-image.0x0.kr" + type = "A" + + alias { + name = aws_cloudfront_distribution.photogram_image_CF.domain_name + zone_id = aws_cloudfront_distribution.photogram_image_CF.hosted_zone_id + evaluate_target_health = false + } +} diff --git a/s3.tf b/s3.tf new file mode 100644 index 0000000..245a020 --- /dev/null +++ b/s3.tf @@ -0,0 +1,36 @@ +resource "aws_s3_bucket" "photogram_image" { + bucket = "photogram.image" + + tags = { + Name = "photogram_image" + } +} + +resource "aws_s3_bucket" "photogram_src" { + bucket = "photogram.src" + + tags = { + Name = "photogram_src" + } +} + +locals { + web_files = ["./webserver/app.js", "./webserver/index.html", "./webserver/package.json"] + resize_files = ["./resizer/app.js", "./resizer/package.json"] +} + +resource "aws_s3_object" "web_files" { + for_each = { for idx, file in local.web_files : idx => file } + + bucket = aws_s3_bucket.photogram_src.id + key = "web/${each.value}" + source = each.value +} + +resource "aws_s3_object" "resize_files" { + for_each = { for idx, file in local.resize_files : idx => file } + + bucket = aws_s3_bucket.photogram_src.id + key = "resize/${each.value}" + source = each.value +} diff --git a/sqs.tf b/sqs.tf new file mode 100644 index 0000000..7cff83e --- /dev/null +++ b/sqs.tf @@ -0,0 +1,5 @@ +resource "aws_sqs_queue" "photogram_SQS" { + name = "photogram_SQS.fifo" + fifo_queue = true + content_based_deduplication = true +} \ No newline at end of file diff --git a/vpc.tf b/vpc.tf new file mode 100644 index 0000000..5e3e7fe --- /dev/null +++ b/vpc.tf @@ -0,0 +1,68 @@ +resource "aws_vpc" "photogram_vpc" { + cidr_block = "10.0.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = "photogram_vpc" + } +} + +resource "aws_subnet" "photogram_subnet" { + vpc_id = aws_vpc.photogram_vpc.id + cidr_block = "10.0.1.0/24" + map_public_ip_on_launch = true + availability_zone = "us-east-1a" + tags = { + Name = "photogram_subnet" + } +} + +resource "aws_internet_gateway" "photogram_IG" { + vpc_id = aws_vpc.photogram_vpc.id + tags = { + Name = "photogram_IG" + } +} + +resource "aws_route_table" "photogram_RT" { + vpc_id = aws_vpc.photogram_vpc.id + tags = { + Name = "photogram_RT" + } +} + +resource "aws_route" "photogram_Route" { + route_table_id = aws_route_table.photogram_RT.id + destination_cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.photogram_IG.id +} + +resource "aws_route_table_association" "name" { + subnet_id = aws_subnet.photogram_subnet.id + route_table_id = aws_route.photogram_Route.id +} + +resource "aws_security_group" "allow_mysql" { + name = "allow_mysql" + description = "Allow MySQL inbound traffic and all outbound traffic" + vpc_id = aws_vpc.photogram_vpc.id + + tags = { + Name = "allow_mysql" + } +} + +resource "aws_vpc_security_group_ingress_rule" "allow_mysql_ipv4" { + security_group_id = aws_security_group.allow_mysql.id + cidr_ipv4 = aws_vpc.photogram_vpc.cidr_block + from_port = 3306 + ip_protocol = "tcp" + to_port = 3306 +} + +resource "aws_vpc_security_group_egress_rule" "allow_all_traffic_ipv4" { + security_group_id = aws_security_group.allow_mysql.id + cidr_ipv4 = "0.0.0.0/0" + ip_protocol = "-1" # semantically equivalent to all ports +} diff --git a/webserver/app.js b/webserver/app.js new file mode 100644 index 0000000..dd9ec75 --- /dev/null +++ b/webserver/app.js @@ -0,0 +1,87 @@ +var express = require('express') + , multer = require('multer') + , AWS = require('aws-sdk') + , Sequelize = require('sequelize') + , mime = require('mime') + , http = require('http') + , fs = require('fs') + , app = express() + , server = http.createServer(app) + , s3 = new AWS.S3({ region: 'ap-northeast-1' }) + , sqs = new AWS.SQS({ region: 'ap-northeast-1' }); + +var s3Bucket = 'examplephoto.image'; +var sqsQueueUrl = 'https://sqs.ap-northeast-1.amazonaws.com/232075047203/ExamplePhotoQueue'; +var rdsEndpoint = { + host: 'examplephoto.cnlconsezo7y.ap-northeast-1.rds.amazonaws.com', + port: 3306 +}; + +// MySQL DB 이름, 계정, 암호 +var sequelize = new Sequelize('examplephoto', 'admin', 'adminpassword', { + host: rdsEndpoint.host, + port: rdsEndpoint.port +}); + +// MySQL DB 테이블 정의 +var Photo = sequelize.define('Photo', { + filename: { type: Sequelize.STRING, allowNull: false, unique: true } +}); + +// MySQL DB 테이블 생성 +sequelize.sync(); + +app.use(multer({ dest: './uploads/' })); + +app.get(['/', '/index.html'], function (req, res) { + fs.readFile('./index.html', function (err, data) { + res.contentType('text/html'); + res.send(data); + }); +}); + +// 이미지 목록 출력 +app.get('/images', function (req, res) { + Photo.findAll().success(function (photoes) { + var data = []; + photoes.map(function (photo) { return photo.values; }).forEach(function (e) { + data.push(e.filename); + }); + + res.header('Cache-Control', 'max-age=0, s-maxage=0, public'); + res.send(data); + }); +}); + +// 웹 브라우저에서 이미지 받기 +app.post('/images', function (req, res) { + fs.readFile(req.files.images.path, function (err, data) { + var filename = req.files.images.name; + s3.putObject({ + Bucket: s3Bucket, + Key: 'original/' + filename, + Body: data, + ContentType: mime.lookup(filename) + }, function (err, data) { + if (err) + console.log(err, err.stack); + else { + console.log(data); + + sqs.sendMessage({ + MessageBody: filename, + QueueUrl: sqsQueueUrl + }, function (err, data) { + if (err) + console.log(err, err.stack); + else + console.log(data); + }); + } + }); + }); + + res.send(); +}); + +server.listen(80); \ No newline at end of file diff --git a/webserver/index.html b/webserver/index.html new file mode 100644 index 0000000..9539a10 --- /dev/null +++ b/webserver/index.html @@ -0,0 +1,51 @@ + + + + ExamplePhoto + + + + + + + + + + + Select files... + + + +
+
+
+ +
+ + + + \ No newline at end of file diff --git a/webserver/package.json b/webserver/package.json new file mode 100644 index 0000000..793549f --- /dev/null +++ b/webserver/package.json @@ -0,0 +1,13 @@ +{ + "name": "ExamplePhotoWebServer", + "version": "0.0.1", + "description": "ExamplePhotoWebServer", + "dependencies": { + "express": "4.4.x", + "multer": "0.1.x", + "aws-sdk": "2.0.x", + "mime": "1.2.x", + "sequelize": "1.7.x", + "mysql": "2.3.2" + } +} \ No newline at end of file