Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 679afb7

Browse files
committedFeb 24, 2025
chore : s3 버킷 생성 및 이미지 업르도, 삭제 기능 테스트 (#6)
1 parent ef668fb commit 679afb7

File tree

8 files changed

+571
-3
lines changed

8 files changed

+571
-3
lines changed
 

‎.gitignore

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,92 @@ docker-compose.yaml
7777
db/
7878

7979
### secret 프로필
80-
application-secret.yml
80+
application-secret.yml
81+
82+
### Terraform ###
83+
# Terraform 상태 파일
84+
*.tfstate
85+
*.tfstate.*
86+
87+
# Terraform 변수 파일
88+
*.tfvars
89+
*.tfvars.json
90+
91+
# 로컬 Terraform 디렉토리
92+
**/.terraform/*
93+
94+
# 충돌 로그 파일
95+
crash.log
96+
crash.*.log
97+
98+
# Terraform 재정의 파일
99+
override.tf
100+
override.tf.json
101+
*_override.tf
102+
*_override.tf.json
103+
104+
# Terraform CLI 설정 파일
105+
.terraformrc
106+
terraform.rc
107+
108+
#
109+
.terraform.lock.hcl
110+
111+
### macOS ###
112+
# General
113+
.DS_Store
114+
.AppleDouble
115+
.LSOverride
116+
117+
# Icon must end with two \r
118+
Icon
119+
120+
121+
# Thumbnails
122+
._*
123+
124+
# Files that might appear in the root of a volume
125+
.DocumentRevisions-V100
126+
.fseventsd
127+
.Spotlight-V100
128+
.TemporaryItems
129+
.Trashes
130+
.VolumeIcon.icns
131+
.com.apple.timemachine.donotpresent
132+
133+
# Directories potentially created on remote AFP share
134+
.AppleDB
135+
.AppleDesktop
136+
Network Trash Folder
137+
Temporary Items
138+
.apdisk
139+
140+
### macOS Patch ###
141+
# iCloud generated files
142+
*.icloud
143+
144+
### Windows ###
145+
# Windows thumbnail cache files
146+
Thumbs.db
147+
Thumbs.db:encryptable
148+
ehthumbs.db
149+
ehthumbs_vista.db
150+
151+
# Dump file
152+
*.stackdump
153+
154+
# Folder config file
155+
[Dd]esktop.ini
156+
157+
# Recycle Bin used on file shares
158+
$RECYCLE.BIN/
159+
160+
# Windows Installer files
161+
*.cab
162+
*.msi
163+
*.msix
164+
*.msm
165+
*.msp
166+
167+
# Windows shortcuts
168+
*.lnk

‎build.gradle.kts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ dependencies {
4040
compileOnly("org.projectlombok:lombok")
4141
annotationProcessor("org.projectlombok:lombok")
4242

43-
//DB
43+
// DB
4444
runtimeOnly("com.h2database:h2")
4545
runtimeOnly("com.mysql:mysql-connector-j")
4646
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
4747

48-
//redis
48+
// redis
4949
implementation("org.springframework.boot:spring-boot-starter-data-redis")
5050
implementation("org.springframework.session:spring-session-data-redis")
5151

@@ -68,6 +68,10 @@ dependencies {
6868

6969
// WebClient
7070
implementation ("org.springframework.boot:spring-boot-starter-webflux")
71+
72+
// aws
73+
implementation(platform("software.amazon.awssdk:bom:2.24.0"))
74+
implementation("software.amazon.awssdk:s3")
7175
}
7276

7377
tasks.withType<Test> {

‎ec2/main.tf

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
terraform {
2+
// aws 라이브러리 불러옴
3+
required_providers {
4+
aws = {
5+
source = "hashicorp/aws"
6+
version = "~> 4.0"
7+
}
8+
}
9+
}
10+
11+
# AWS 설정 시작
12+
provider "aws" {
13+
region = var.region
14+
}
15+
# AWS 설정 끝
16+
17+
# VPC 설정 시작
18+
resource "aws_vpc" "vpc_1" {
19+
cidr_block = "10.0.0.0/16"
20+
21+
# 무조건 켜세요.
22+
enable_dns_support = true
23+
# 무조건 켜세요.
24+
enable_dns_hostnames = true
25+
26+
tags = {
27+
Name = "${var.prefix}-vpc-1"
28+
}
29+
}
30+
31+
resource "aws_subnet" "subnet_1" {
32+
vpc_id = aws_vpc.vpc_1.id
33+
cidr_block = "10.0.1.0/24"
34+
availability_zone = "${var.region}a"
35+
map_public_ip_on_launch = true
36+
37+
tags = {
38+
Name = "${var.prefix}-subnet-1"
39+
}
40+
}
41+
42+
resource "aws_subnet" "subnet_2" {
43+
vpc_id = aws_vpc.vpc_1.id
44+
cidr_block = "10.0.2.0/24"
45+
availability_zone = "${var.region}b"
46+
map_public_ip_on_launch = true
47+
48+
tags = {
49+
Name = "${var.prefix}-subnet-2"
50+
}
51+
}
52+
53+
resource "aws_subnet" "subnet_3" {
54+
vpc_id = aws_vpc.vpc_1.id
55+
cidr_block = "10.0.3.0/24"
56+
availability_zone = "${var.region}c"
57+
map_public_ip_on_launch = true
58+
59+
tags = {
60+
Name = "${var.prefix}-subnet-3"
61+
}
62+
}
63+
64+
resource "aws_internet_gateway" "igw_1" {
65+
vpc_id = aws_vpc.vpc_1.id
66+
67+
tags = {
68+
Name = "${var.prefix}-igw-1"
69+
}
70+
}
71+
72+
resource "aws_route_table" "rt_1" {
73+
vpc_id = aws_vpc.vpc_1.id
74+
75+
route {
76+
cidr_block = "0.0.0.0/0"
77+
gateway_id = aws_internet_gateway.igw_1.id
78+
}
79+
80+
tags = {
81+
Name = "${var.prefix}-rt-1"
82+
}
83+
}
84+
85+
resource "aws_route_table_association" "association_1" {
86+
subnet_id = aws_subnet.subnet_1.id
87+
route_table_id = aws_route_table.rt_1.id
88+
}
89+
90+
resource "aws_route_table_association" "association_2" {
91+
subnet_id = aws_subnet.subnet_2.id
92+
route_table_id = aws_route_table.rt_1.id
93+
}
94+
95+
resource "aws_route_table_association" "association_3" {
96+
subnet_id = aws_subnet.subnet_3.id
97+
route_table_id = aws_route_table.rt_1.id
98+
}
99+
100+
resource "aws_security_group" "sg_1" {
101+
name = "${var.prefix}-sg-1"
102+
103+
ingress {
104+
from_port = 0
105+
to_port = 0
106+
protocol = "all"
107+
cidr_blocks = ["0.0.0.0/0"]
108+
}
109+
110+
egress {
111+
from_port = 0
112+
to_port = 0
113+
protocol = "all"
114+
cidr_blocks = ["0.0.0.0/0"]
115+
}
116+
117+
vpc_id = aws_vpc.vpc_1.id
118+
119+
tags = {
120+
Name = "${var.prefix}-sg-1"
121+
}
122+
}
123+
124+
# EC2 설정 시작
125+
126+
# EC2 역할 생성
127+
resource "aws_iam_role" "ec2_role_1" {
128+
name = "${var.prefix}-ec2-role-1"
129+
130+
# 이 역할에 대한 신뢰 정책 설정. EC2 서비스가 이 역할을 가정할 수 있도록 설정
131+
assume_role_policy = <<EOF
132+
{
133+
"Version": "2012-10-17",
134+
"Statement": [
135+
{
136+
"Sid": "",
137+
"Action": "sts:AssumeRole",
138+
"Principal": {
139+
"Service": "ec2.amazonaws.com"
140+
},
141+
"Effect": "Allow"
142+
}
143+
]
144+
}
145+
EOF
146+
}
147+
148+
# EC2 역할에 AmazonS3FullAccess 정책을 부착
149+
resource "aws_iam_role_policy_attachment" "s3_full_access" {
150+
role = aws_iam_role.ec2_role_1.name
151+
policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
152+
}
153+
154+
# EC2 역할에 AmazonEC2RoleforSSM 정책을 부착
155+
resource "aws_iam_role_policy_attachment" "ec2_ssm" {
156+
role = aws_iam_role.ec2_role_1.name
157+
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM"
158+
}
159+
160+
# IAM 인스턴스 프로파일 생성
161+
resource "aws_iam_instance_profile" "instance_profile_1" {
162+
name = "${var.prefix}-instance-profile-1"
163+
role = aws_iam_role.ec2_role_1.name
164+
}
165+
166+
locals {
167+
ec2_user_data_base = <<-END_OF_FILE
168+
#!/bin/bash
169+
yum install socat -y
170+
yum install python -y
171+
172+
yum install docker -y
173+
systemctl enable docker
174+
systemctl start docker
175+
176+
curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
177+
chmod +x /usr/local/bin/docker-compose
178+
179+
exec 1> >(tee -a "/var/log/user-data.log") 2>&1
180+
181+
182+
# cmf 디렉터리 생성
183+
mkdir -p /dockerProjects/cmf
184+
cd /dockerProjects/cmf
185+
186+
# docker-compose.yml 생성
187+
echo "[INFO] Creating docker-compose.yml..."
188+
cat << 'DOCKEREOF' > docker-compose.yml
189+
services:
190+
mysql:
191+
image: mysql:9.2.0
192+
container_name: mysql-cmf
193+
environment:
194+
MYSQL_ROOT_PASSWORD: ${var.mysql_root_password}
195+
MYSQL_DATABASE: ${var.mysql_database_name}
196+
ports:
197+
- "3306:3306"
198+
volumes:
199+
- ./db/:/var/lib/mysql
200+
201+
redis:
202+
image: redis:latest
203+
container_name: redis_1
204+
restart: unless-stopped
205+
ports:
206+
- "6379:6379"
207+
environment:
208+
- TZ=Asia/Seoul
209+
command: ["redis-server", "--requirepass", "${var.redis_password}"] # 비밀번호 강제 설정
210+
211+
npm:
212+
image: jc21/nginx-proxy-manager:latest
213+
container_name: npm_1
214+
restart: unless-stopped
215+
ports:
216+
- "80:80"
217+
- "443:443"
218+
- "81:81"
219+
environment:
220+
- TZ=Asia/Seoul
221+
volumes:
222+
- /dockerProjects/npm_1/volumes/data:/data
223+
- /dockerProjects/npm_1/volumes/etc/letsencrypt:/etc/letsencrypt
224+
DOCKEREOF
225+
226+
# 권한 설정 (소유권을 현재 사용자로 변경)
227+
chown -R $(whoami):$(whoami) /dockerProjects/cmf
228+
229+
# docker-compose 실행
230+
echo "[INFO] Starting CMF stack..."
231+
docker-compose up -d
232+
233+
# 컨테이너 상태 확인
234+
docker ps
235+
docker-compose logs
236+
237+
echo "[INFO] Installation completed!"
238+
239+
yum install git -y
240+
241+
sudo dd if=/dev/zero of=/swapfile bs=128M count=32
242+
sudo chmod 600 /swapfile
243+
sudo mkswap /swapfile
244+
sudo swapon /swapfile
245+
sudo swapon -s
246+
sudo sh -c 'echo "/swapfile swap swap defaults 0 0" >> /etc/fstab'
247+
248+
END_OF_FILE
249+
}
250+
251+
# EC2 인스턴스 생성
252+
resource "aws_instance" "ec2_1" {
253+
# 사용할 AMI ID
254+
ami = "ami-04c596dcf23eb98d8"
255+
# EC2 인스턴스 유형
256+
instance_type = "t2.micro"
257+
# 사용할 서브넷 ID
258+
subnet_id = aws_subnet.subnet_1.id
259+
# 적용할 보안 그룹 ID
260+
vpc_security_group_ids = [aws_security_group.sg_1.id]
261+
# 퍼블릭 IP 연결 설정
262+
associate_public_ip_address = true
263+
264+
# 인스턴스에 IAM 역할 연결
265+
iam_instance_profile = aws_iam_instance_profile.instance_profile_1.name
266+
267+
# 인스턴스에 태그 설정
268+
tags = {
269+
Name = "${var.prefix}-ec2-1"
270+
}
271+
272+
# 루트 볼륨 설정
273+
root_block_device {
274+
volume_type = "gp3"
275+
volume_size = 30 # 볼륨 크기를 30GB로 설정
276+
}
277+
278+
# User data script for ec2_1
279+
user_data = <<-EOF
280+
${local.ec2_user_data_base}
281+
EOF
282+
}

‎ec2/variables.tf

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
variable "prefix" {
2+
description = "Prefix for all resources"
3+
default = "cmf"
4+
}
5+
6+
variable "region" {
7+
description = "region"
8+
default = "ap-northeast-2"
9+
}
10+
11+
variable "nickname" {
12+
description = "nickname"
13+
default = "dev-seoyeon"
14+
}
15+
16+
variable "mysql_root_password" {
17+
description = "MySQL root password"
18+
type = string
19+
sensitive = true
20+
}
21+
22+
variable "mysql_database_name" {
23+
description = "MySQL database name"
24+
type = string
25+
}
26+
27+
variable "redis_password" {
28+
description = "Redis password"
29+
type = string
30+
sensitive = true
31+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package cmf.commitField.domain.admin.controller;
2+
3+
import cmf.commitField.global.aws.s3.S3Service;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.web.bind.annotation.*;
6+
import org.springframework.web.multipart.MultipartFile;
7+
import software.amazon.awssdk.services.s3.S3Client;
8+
import software.amazon.awssdk.services.s3.model.Bucket;
9+
10+
import java.io.IOException;
11+
import java.util.List;
12+
import java.util.stream.Collectors;
13+
14+
@RestController
15+
@RequestMapping("/api/v1/admin/pet")
16+
@RequiredArgsConstructor
17+
public class ApiV1PetImgController {
18+
private final S3Client s3Client;
19+
private final S3Service s3Service;
20+
21+
@GetMapping("/test")
22+
public List<String> home() {
23+
List<Bucket> bucketList = s3Client.listBuckets().buckets();
24+
return bucketList.stream().map(Bucket::name).collect(Collectors.toList());
25+
}
26+
27+
@GetMapping("/upload")
28+
public String upload() {
29+
return """
30+
<form action="/api/v1/upload" method="post" enctype="multipart/form-data">
31+
<input type="file" name="file" accept="image/*">
32+
<input type="submit" value="Upload">
33+
</form>
34+
""";
35+
}
36+
37+
@PostMapping("/upload")
38+
@ResponseBody
39+
public String handleFileUpload(@RequestParam("file") MultipartFile file) throws IOException {
40+
String img = s3Service.uploadFile(file,"pet");
41+
42+
return """
43+
<img src="%s">
44+
<hr>
45+
<div>업로드 완료</div>
46+
""".formatted(img);
47+
}
48+
49+
@GetMapping("/deleteFile")
50+
public String showDeleteFile() {
51+
return """
52+
<form action="/api/v1/deleteFile" method="post">
53+
<input type="text" name="fileName">
54+
<input type="submit" value="delete">
55+
</form>
56+
""";
57+
}
58+
59+
@PostMapping("/deleteFile")
60+
@ResponseBody
61+
public String deleteFile(String fileName) {
62+
s3Service.deleteFile(fileName);
63+
return "파일이 삭제되었습니다.";
64+
}
65+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package cmf.commitField.global.aws.s3;
2+
3+
import jakarta.annotation.PreDestroy;
4+
import org.springframework.stereotype.Component;
5+
import software.amazon.awssdk.services.s3.S3Client;
6+
import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
7+
import software.amazon.awssdk.services.s3.model.ListObjectsV2Response;
8+
import software.amazon.awssdk.services.s3.model.S3Object;
9+
10+
@Component
11+
public class S3Cleaner {
12+
13+
private final S3Client s3Client;
14+
private final String bucketName = "cmf-bucket-dev-seoyeon-1"; // S3 버킷 이름
15+
16+
public S3Cleaner(S3Client s3Client) {
17+
this.s3Client = s3Client;
18+
}
19+
20+
@PreDestroy
21+
public void cleanup() {
22+
// S3에서 테스트 파일 삭제
23+
ListObjectsV2Request listObjects = ListObjectsV2Request.builder()
24+
.bucket(bucketName)
25+
.build();
26+
27+
// 파일 목록을 가져옵니다.
28+
ListObjectsV2Response listObjectsResponse = s3Client.listObjectsV2(listObjects);
29+
30+
// 각 파일을 삭제합니다.
31+
for (S3Object s3Object : listObjectsResponse.contents()) {
32+
s3Client.deleteObject(builder -> builder.bucket(bucketName).key(s3Object.key()));
33+
}
34+
35+
System.out.println("S3에 있는 테스트 파일들을 삭제했습니다.");
36+
}
37+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package cmf.commitField.global.aws.s3;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import software.amazon.awssdk.regions.Region;
6+
import software.amazon.awssdk.services.s3.S3Client;
7+
8+
@Configuration
9+
public class S3Config {
10+
@Bean
11+
public S3Client s3Client() {
12+
return S3Client.builder()
13+
.region(Region.AP_NORTHEAST_2)
14+
.build();
15+
}
16+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package cmf.commitField.global.aws.s3;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.stereotype.Service;
5+
import org.springframework.web.multipart.MultipartFile;
6+
import software.amazon.awssdk.core.sync.RequestBody;
7+
import software.amazon.awssdk.services.s3.S3Client;
8+
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
9+
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
10+
11+
import java.io.IOException;
12+
import java.util.UUID;
13+
14+
@Service
15+
@RequiredArgsConstructor
16+
public class S3Service {
17+
private final S3Client s3Client;
18+
private static final String BUCKET_NAME = "cmf-bucket-dev-seoyeon-1";
19+
private static final String REGION = "ap-northeast-2";
20+
21+
// 파일 업로드 기능
22+
public String uploadFile(MultipartFile file, String dirName) throws IOException {
23+
String fileName = dirName + "/" + UUID.randomUUID() + "_" + file.getOriginalFilename();
24+
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
25+
.bucket(BUCKET_NAME)
26+
.key(fileName)
27+
.contentType(file.getContentType())
28+
.build();
29+
30+
s3Client.putObject(putObjectRequest,
31+
RequestBody.fromInputStream(file.getInputStream(), file.getSize()));
32+
33+
return "https://" + BUCKET_NAME + ".s3." + REGION + ".amazonaws.com/" + fileName;
34+
}
35+
36+
// 파일 삭제 기능
37+
public void deleteFile(String fileName) {
38+
DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
39+
.bucket(BUCKET_NAME)
40+
.key(fileName)
41+
.build();
42+
43+
s3Client.deleteObject(deleteObjectRequest);
44+
}
45+
}

0 commit comments

Comments
 (0)
Please sign in to comment.