diff --git "a/2\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\231\251\354\261\204\354\233\220_2\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/2\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\231\251\354\261\204\354\233\220_2\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..59d30ac --- /dev/null +++ "b/2\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\231\251\354\261\204\354\233\220_2\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,59 @@ +# 2주차 과제 + +## 1. MobaXterm에서 Bastion 접속하기 + +![Bastion](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/b0280fba-c5d3-4a14-8f68-d4b12de1f701) + +Bastion 서버의 ip 주소는 위 사진과 같다. + +![MobaXterm_Bastion](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/c2f84134-a6bb-488c-b4c3-9f860a97da0a) + + +MobaXterm에서 Bastion 서버로 접속한 사진 + + +## 2. Bastion에서 private 서버 접속하기 + +키 권한 수정없이 바로 private 서버에 접속하려고 하면 접속이 거부되므로, 키 권한 설정을 600으로 바꿔주어야 한다. + +리눅스 명령어 `sudo chmod 600 [키 이름]`을 써주면 되는데... + +*어디에 쓰지?* + +윈도우 cmd 창에서는 해당 명령어를 사용할 수 없기 때문에 번거롭게 보안 설정을 편집해주어야 한다. 개발자의 자아가 거부하는 일이다. + +구글링 결과 키의 사용을 위해서는 세 가지 시도를 해볼 수 있다는 것을 알 수 있었다. + +### 1. 로컬에서 키 권한 수정하기 + +위에서 언급했다시피 chmod는 윈도우 cmd에서 사용할 수 없고, 보안 설정을 하나하나 건드리는 것은 개발자의 자아가 거부하는 일이었다. + +### 2. 바스티온 호스트에서의 SSH 키 사용 + +바스티온 서버에서 다른 인스턴스로 접속하기 위해 키를 사용해야 하는 경우, 키 파일을 로컬에서 바스티온 서버로 전송하여 사용할 수 있다. + +그러나 이는 민감한 키 파일이 바스티온 서버에 전송되는 것이므로, 일반적으로 권장되지 않는 보안관행이라고 한다. + +### 3. SSH Agent Forwarding 사용 + +보안을 위해 키 파일을 바스티온 서버에 올리는 것 대신 SSH Agent Forwarding을 사용할 수 있다고 한다. 이 기능은 로컬의 SSH 키를 바스티온 서버를 거쳐 다른 EC2 인스턴스에 사용할 수 있도록 하는 기능이다. + +![allow_agent_forwarding](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/fd78b2d0-e6af-40dd-ae67-869f5bef2944) + + +이에 따라 세션을 생성할 때 Allow agent forwarding을 체크하고 생성해보았다. + +![nosuchfileordir](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/58c36964-fcd0-4504-a419-18264ace01a2) + + +그럼에도 키 파일의 권한을 수정할 수는 없었다. + +**그렇다면 무슨 방법으로 키 권한을 수정해서 private 인스턴스에 접속할 수 있는거지?** + +김 빠지게도 답은 2번이었다. + +로컬의 키를 바스티온 서버에 올리고, mobaXterm에서 chmod을 사용하여 키의 권한을 변경해준다. +그리고 private 서버의 ip주소로 접속해주면 된다. + +![Instance1](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/448e94f2-e3de-4314-8623-55cfe161b0b6) +![MobaXterm_Instance1](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/cbb7e3e4-9814-4175-a45c-59f20c718229) diff --git "a/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/img1.png" "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/img1.png" new file mode 100644 index 0000000..3c32398 Binary files /dev/null and "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/img1.png" differ diff --git "a/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/img2.png" "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/img2.png" new file mode 100644 index 0000000..cc6c160 Binary files /dev/null and "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/img2.png" differ diff --git "a/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\352\271\200\354\244\221\355\230\204_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\352\271\200\354\244\221\355\230\204_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..7ab0ceb --- /dev/null +++ "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\352\271\200\354\244\221\355\230\204_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,52 @@ +## 3주차 서버 세션 과제 +```md +1. Bastion Host로 Web EC2에 접근하기 +2. Private 서브넷은 NAT와,Public 서브넷은 IGW와 연결하기 +3. Web EC2에는 nginx를 설치하고, 각 서버에 서로 다른 정적 파일 넣기 +4. ALB의 DNS를 통해서 Web EC2에 접근한 후, 새로고침을 할 때마다 페이지가 달라지는 것을 확인하기 +``` +→ 결과적으로, 아래 이미지와 같은 아키텍쳐를 구성한다.
+스크린샷 2023-11-03 오후 1 10 58 + +
+ +## 과제 인증 +1. Bastion 통해 Private IP를 가진 EC2에 SSH 연결 + 스크린샷 2023-11-03 오후 3 07 00 + +2. 들어간 Web EC2에서 nginx 설치 + 스크린샷 2023-11-03 오후 3 29 53 + +3. 새로고침 하면 서로 다른 HTTP 웹 페이지가 보이는 모습
+ [영상 인증](https://drive.google.com/file/d/1V4kFMS2ts3NRBcdclOURHFO3iaroivK_/view?usp=sharing) +
+ +## 과제 해결 과정 +1. 2개의 가용영역(2a, 2c)에 `public subnet`, `private subnet`을 각각 생성한다. +2. `IGW`를 생성해 `VPC`, `public subnet`과 연결한다. +3. `public RT`, `private RT`를 생성해 각각 `public subnet(2개)`, `private subnet(2개)`과 연결한다. +4. 2개의 private subnet에 EC2 instance(web1, web2)를 각각 생성한다. +5. 네트워크 매핑을 public subnet 두 개로 해 ALB를 생성한다. +6. NAT gateway를 생성하고, private RT에 0.0.0.0/0 → nat-gw를 향하도록 설정한다.
+ → 주의! 실습 종료 후 반드시 Elastic IP release해주기! +7. `Bastion host`를 통해 `web1`, `web2`에 private IP로 ssh 접속한 후, `nginx`를 설치한다. + ```sh + sudo yum update -y # 업데이트 실시 + sudo yum install nginx # yum을 이용한 Apache 설치 + sudo systemctl start nginx # nginx 시작 + sudo systemctl enable nginx # nginx 웹 서버가 시스템이 부팅할 때마다 시작되도록 함 + sudo systemctl status nginx # 상태 확인 + ``` +8. 두 개의 index.html 파일을 `local` → `bastion host`로 전송하고, `bastion host`→ `web1`과 `web2`로 각각 전송한다. 그 후, `web1`과 `web2`의 `/usr/share/nginx/html/` 경로에 있는 `index.html` 파일을 대체한다. + ```sh + sudo mv index_1.html /usr/share/nginx/html/index.html + ``` + ```sh + sudo mv index_2.html /usr/share/nginx/html/index.html + ``` +9. `Load Balancer`의 DNS를 통해 `Web EC2`에 접근한다. +
+ +## 참고 +- [Your requested instance type is not supported in your requested Availability Zone(요청한 인스턴스 유형이 요청된 가용 영역에서 지원되지 않습니다)](https://repost.aws/ko/knowledge-center/ec2-instance-type-not-supported-az-error) +- [로드 밸런서 권장 보안그룹 설정](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-update-security-groups.html) diff --git "a/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\204\234\354\247\200\354\233\220_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\204\234\354\247\200\354\233\220_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..6b8ffcc --- /dev/null +++ "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\204\234\354\247\200\354\233\220_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,6 @@ +## SampleController.class의 코드 +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/63919973/9096bb56-75c3-48fa-88a6-1fa090c37ffb) +- spring initializr로 프로젝트 생성 +- index.html, members.html, newMember.html을 resources/templates에 넣음 +- @GetMapping, @RequestParam 어노테이션 사용해서 컨트롤러 작성 +- 동영상 주소: https://drive.google.com/file/d/1J_KLi7vz8SaGJqjZWxo-KK3agNBR98_2/view?usp=sharing diff --git "a/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\226\221\353\217\231\354\204\240_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\226\221\353\217\231\354\204\240_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..3d1ce46 --- /dev/null +++ "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\226\221\353\217\231\354\204\240_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,111 @@ +# Spring 과제 + +## 과정 1 : 인텔리제이 설치 및 jdk 17 설치, spring initializer를 이용하여 프로젝트 다운 +- https://gymdev.tistory.com/72를 참고하여 환경변수 설정함. +#### 난관 : JAVAVIRTUALMACHINE 폴더에 jdk폴더를 넣고 싶어서 맥의 '폴더로 이동'기능을 사용! +- https://support.apple.com/ko-kr/guide/mac-help/mchlp1236/mac +- 이런 것도 있구나. 맥이란 해도해도 신기해요! 언제쯤 맥OS를 점령할지... + + + +## 과정 2 : 코드 작성 +### 난관 1 : symbol not find 문제 +--> 필요한 라이브러리를 import했더니 'web'이라는 symbol를 찾지 못했다고 뜬다. +==> 해결 : https://www.goodsource.co.kr/125 참고 +- build.grandle의 + implementation 'org.springframework.boot:spring-boot-starter'를 + implementation 'org.springframework.boot:spring-boot-starter-web'으로 수정하고 빌딩하고 실행! + + +### 난관 2 : java: cannot find symbol +이번엔 email symbol을 찾지 못하고 에러를 낸다. +스크린샷 2023-11-03 오후 11 37 56 + +==> 해결 : 당연함... 내가 email 선언 안해줌... String email을 추가해줬다! ㅎㅎ +해결~ + + +### 난관 3 : localhost:8080 을 실행했으나 메인 페이지가 나타나지 않았다. +스크린샷 2023-11-03 오후 11 44 20 + +- 참고 : https://devmango.tistory.com/97 + + +- 해결 과정 :난관4 & 난관5 + + +### 난관 4 : 분명 환경변수 설정을 다 해줬었는데 $java --version 을 해보니 jdk 21버전으로 뜬다. +- 해결: jdk 21 버전을 삭제해줬다! +- 삭제하지 않고 default jdk를 17로 바꿀 수는 없을까 해서 vim ~/.zshrc으로 zshrc 한 번 건드렸다가 터미널이 고장나길래 취소! + +스크린샷 2023-11-07 오후 2 34 55 + +``` +$ sudo rm -rf temurin-21.jdk +``` + +-참고 : https://ifuwanna.tistory.com/247 + + + +### 난관 5 : 갑자기 application이 안돌아간다! 8080포트가 이미 사용 중.. +스크린샷 2023-11-07 오후 2 36 10 + +오케이 해결해주마 + +스크린샷 2023-11-07 오후 2 41 51 + +- 8080포트를 죽여준다~ 해결! + + +스크린샷 2023-11-07 오후 2 42 37 + + +드디어 되는구나~~ʕ”̮ॽु⋆⁺₊⋆ ♡̷̷̷ + + + + +--- +SampleController.java 소스 코드 : + +``` + package mvcstudy.mvcstudy.controller; + + import org.springframework.stereotype.Controller; + import org.springframework.ui.Model; + import org.springframework.web.bind.annotation.GetMapping; + import org.springframework.web.bind.annotation.RequestParam; + + + @Controller + public class SampleController { + @GetMapping("/") + public String sample(Model model) { + model.addAttribute("description", + "메인 페이지 입니다."); + return "index"; + } + @GetMapping("/members") + public String members(Model model) { + model.addAttribute("member1", "Yang"); + model.addAttribute("member2", "Dong"); + model.addAttribute("member3", "Seon"); + return "members"; + } + + @GetMapping("/members/new") + public String showNewMember(@RequestParam(name= + "name", defaultValue = "Guest")String name, String email, + Model model){ + model.addAttribute("name", name); + model.addAttribute("email", email); + return "newMember"; + } + } +``` + +스크린샷 2023-11-04 오전 12 13 34 + + + diff --git "a/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\354\204\234\355\230\204_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\354\204\234\355\230\204_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..4da3815 --- /dev/null +++ "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\354\204\234\355\230\204_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,47 @@ +### 3주차 Server S-Day 과제 - 이서현 +> 과제(spring): 스프링 프로젝트 생성, 주어진 HTML(index.html, members.html, newMember.html)을 프로젝트에 포함시키기, SampleController 파일을 만들고 코드 작성 + +* SampleController.class의 소스 코드 : +``` +package gdscstudy.serverstudy3.controllers; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@Controller +public class SampleController { + @GetMapping("/") //root url 경로 + public String home(Model model){ + model.addAttribute("description", "메인 페이지입니다."); + //key-value 쌍을 모델에 추가 (attributeName "des...", attributeValue "메인...") + return "index"; //html 파일명 + } + + @GetMapping("/members") //url 경로 + public String showMembers(Model model){ //Model은 데이터를 View로 전달하기 위해 사용됨 + model.addAttribute("member1", "Seohyun Lee"); + model.addAttribute("member2", "Haeseung Jeon"); + model.addAttribute("member3", "Hyuna Kim"); + return "members"; //html 파일명 + } + + @GetMapping("/members/new") //url 경로 + public String showNewMember(@RequestParam(name = "name", defaultValue = "guest") String name, + //@RequestParam은 쿼리 스트링 방식으로 url을 통해 파라미터로 값을 받아옴 + //"?name=value1&email=value2": query string(쿼리 파라미터), '&' 연산자 사용해 쿼리 스트링 여러개 + //"name"은 쿼리 파라미터의 key, name 매개변수에 value 받아옴. "guest"은 value 없을 경우 기본값. + String email, //RequestParam 생략 (자동으로 데이터 파싱), email 매개변수에 value 받아옴 + Model model){ + model.addAttribute("name", name); //"name"은 View의 변수 이름(key), name에 value 있음. 모델에 추가 + model.addAttribute("email", email); //"email"은 View의 변수 이름(key), email에 value 있음. 모델에 추가 + return "newMember"; //html 파일명 + } +} +//동영상 : https://drive.google.com/file/d/1kaPrxyZM4b8yTSnxNVRAOqSYwahvLMST/view?usp=sharing +//참고자료 : +// [Spring] @RequestParam 사용법 https://dangdangee.tistory.com/entry/Spring-RequestParam-%EC%82%AC%EC%9A%A9%EB%B2%95 +// [Spring] @RequestParam - 요청 파라미터 데이터 파싱하기 https://amy-it.tistory.com/108 +// [Spring] Spring MVC: Controller에서 parameter를 받아오는 방법 https://ooeunz.tistory.com/99 +``` \ No newline at end of file diff --git "a/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..991dba4 --- /dev/null +++ "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,87 @@ +## 📒 3주차 과제 + +1. [Cloud 과제](#cloud-과제) + +2. [Spring 과제](#spring-과제) + +--- +## Cloud 과제 +> 1. Web EC2 2개에 `nginx1`를 설치하고, 로드 밸런서의 DNS를 통해서 웹 서버에 접근 +> 2. 새로고침을 할 때마다 페이지가 달라지는 것을 확인하기 + +### 과제 인증 +1. `Bastion`을 통해서 Private IP를 가진 `EC2`에 SSH 연결 +

+ +

+ +2. 1에서 접속한 Web EC2에서 `nginx`를 설치 +

+ +

+ +3. [영상 인증](https://drive.google.com/file/d/1722uMrXEpy8Oira_eiIBXLjRk4TJNUqR/view?usp=sharing) + + +### 과제를 해결한 방법 +1. 서로 다른 AZ 2개에 `public subnet`, `private subnet`을 각각 하나씩 생성한다.
+ (💡 public subnet은 **IGW가 연결**된 서브넷, private subnet은 **IGW가 연결되지 않은** 서브넷) +2. 1에서 생성한 public subnet 2개는 `Public RT` 라우팅 테이블에 연결하고, private subnet 2개는 `Private RT` 라우팅 테이블에 연결한다. +3. 각 private subnet에 (서로 다른 AZ) `t2.micro spot` 타입 `EC2`를 생성한다. (총 2개)
+ → `t2.micro` 타입이 지원되는 가용 영역은 다음 코드를 AWS EC2 콘솔에 입력하면 알 수 있다. + ```bash + aws ec2 describe-instance-type-offerings --location-type availability-zone --filters Name=instance-type,Values=t2.micro --region ap-northeast-2 --output table + ``` +4. 보안그룹 규칙에서 **HTTP 프로토콜을 허용**한다. +5. `Load Balancer`를 public subnet에 생성한다. +6. `NAT gateway`를 public subnet에 생성 후, `Private RT`에서 `0.0.0.0/0` 트래픽이 `nat`로 향하도록 설정한다. +7. `Bastion`을 통해서 `web1`과 `web2` EC2에 Private IP로 SSH 연결한 후, `nginx`를 설치한다. + ```bash + sudo yum install nginx + sudo systemctl start nginx + sudo systemctl enable nginx + sudo systemctl status nginx + ``` +8. `web1`과 `web2`의 `/usr/share/nginx/html/` 경로에 있는 `index.html` 파일을 수정한다. +9. `Load Balancer`의 DNS를 통해 `Web EC2`에 접근한다. +10. NAT gateway 삭제 후에는 꼭! **Elastic IPs를 릴리스**해야 한다 ‼️ +--- +## Spring 과제 +> 1. 사용자는 `/(루트)` 경로, `/members` 경로, `/members/new` 경로로 접근 가능 +> 2. 각 경로로 접근했을 때 view에 각각 데이터가 전달되는 것을 확인하기 + +### 과제 인증 +```java +/* 영상 인증 : https://drive.google.com/file/d/1FsOlrfv4vV6PJr_hAyv43tuQ2Zbzpc6j/view?usp=sharing */ +package GDSCsever.springstudy.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@Controller +public class SampleController { + @GetMapping("/") + public String sample(Model model) { + model.addAttribute("description", "메인 페이지 입니다."); // key:value 구조 + return "index"; // resources:templates/ + {viewname} + .html 실행 + } + + @GetMapping("/members") + public String members(Model model) { + model.addAttribute("member1", "Haewon Lee"); // key:value 구조 + model.addAttribute("member2", "GDSC"); + model.addAttribute("member3", "EWHA"); + return "members"; // resources:templates/ + {viewname} + .html 실행 + } + + @GetMapping("/members/new") + public String showNewMember(@RequestParam(name="name", defaultValue="Guest") String name, @RequestParam("email") String email,Model model) { + model.addAttribute("name", name); // 쿼리 파라미터로 name 입력 + model.addAttribute("email", email); // 쿼리 파라미터로 email 입력 + return "newMember"; // resources:templates/ + {viewname} + .html 실행 + } + +} +``` \ No newline at end of file diff --git "a/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\236\204\354\230\201\354\204\234_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\236\204\354\230\201\354\204\234_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..809555b --- /dev/null +++ "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\236\204\354\230\201\354\204\234_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,39 @@ +###인증 사진 + +1.Bastion을 통해서 Private IP를 가진 EC2애 SSH 연결 +![img1.png](img1.png) + +2.nginx 설치 +![img2.png](img2.png) + +3.HTTP 웹페이지 영상 +[Web link](https://drive.google.com/file/d/1PNJNKNafx8-ueO8sSb9dCqRkRn_RzLbS/view?usp=sharing) + + +###과제를 해결한 방법 + +1.Bastion Host로 Private IP를 가진 EC2 2개(가용영역이 다름) 각각 접근 후 SSH 연결 + -> ssh -i [Key name].pem user_name@[instance1 private ip] + + *EC2 인바운드 규칙편집: HTTP 프로토콜 허용 + +2.nginx 설치 +->sudo yum update -y +->sudo yum install nginx +->sudo systemctl start nginx +->sudo systemctl enable nginx +->sudo systemctl status nginx + +3.nginx에 정적 파일 설정 +->sudo nano /usr/share/nginx/html/index.html +위 명령어를 이용해 정적파일 교체 + +4.ALB 생성 + +5.Nat Gateway 생성, 탄력적 IP할당 + +6.Private 라우팅 테이블에서 0.0.0.0/0트래픽울 Nat을 향하도록 연결 = private 서브넷과 NAT연결 + +7.ALB의 DNS를 통해서 Web EC2 접근 + + diff --git "a/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..4f9df0b --- /dev/null +++ "b/3\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_3\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,39 @@ +## 📖3주차 과제 +1. vpc안에 가용영역 2개 생성 +2. 각 가용영역에 bastion, private EC2 생성 +3. 각 private server에 nginx 설치 후 서로 다른 정적 파일 넣기 +4. 2개의 private sever를 로드밸런서로 연결 +5. private subnet은 NAT와, public subnet은 IGW와 연결할 것 + + +## 🤓해결 과정 +1. vpc 생성 +2. 2개의 가용영역에 public, private subnet 생성 -> 총 4개의 subnet (2 public, 2 private) +3. private rt, public rt 생성 후 서브넷 연결 +4. igw 생성후 vpc, public rt와 연결 +5. 각 subnet에 EC2 instance 생성 -> 총 4개의 instance (2 public, 2 private) +6. 로드밸런서 생성 (이때, 로드밸런서의 보안그룹은 ec2 bastion instance의 보안그룹 2개 지정해줌) +7. NAT gateway 생성 (1개의 public subnet에 생성) 후 private rt에 0.0.0.0/0 트래픽이 NAT를 향하도록 설정 + +image + + +## ✌️과제 인증 + +1. bastion을 통해서 private EC2에 ssh 연결 +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/67634926/e6180b24-d920-4288-bba0-a2bb1225ab1d) +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/67634926/3d8f1b88-822c-4f60-b13e-67213deb7a6b) + +2. private EC2에 nginx 설치 +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/67634926/442919c0-926d-423c-9dc4-e98b35c09471) +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/67634926/628e996c-c2f5-4063-8900-92734dc39e9c) + + +3. 새로고침 시 서로 다른 HTTP 웹 페이지 보임 +
+https://drive.google.com/file/d/19b0rq6WKRF9GL-BstKf14QnNKAFVArk8/view?usp=sharing + +## ☠️어려웠던 점 +1. nginx 설치 후에도 unhealthy 해결x, 로드밸런서 주소로 접속 시 504 gateway timeout +
+ 원인: 아마 타겟그룹 설정과 로드밸런서 보안그룹에서 문제가 있었던 것으로 추측됨 diff --git "a/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234.md" "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234.md" new file mode 100644 index 0000000..fce3d8f --- /dev/null +++ "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234.md" @@ -0,0 +1,25 @@ +## 과정1 : 프로젝트를 생성하고 build.gradle을 실행 +- 그러나 실습을 따라가던 도중 yml 파일이 보이지 않았습니다. +스크린샷 2023-11-10 오후 10 10 24 + +yml 파일 내용을 보고 데이터 베이스 연결이 필요하다고 판단했습니다. + + + + +## 과정2 : Database 연결 +그래서 환경변수 영상에서 오류가 있었나 하여 환경변수 설정영상을 (노션에 있던 Spring 환경변수 설정을 저번 실습 때 완료하여 보지 않았음) 보고 Database를 연결하던 도중, 연결에 실패하고 막혀서 과제를 완료하지 못했습니다.
+yml 파일을 직접 만들어서 내용을 추가하고 시도해보았으나 계속 오류가 발생했습니다.(yml이 원래 없었는데 제가 직접 만들어도 되는 걸까요?)
+이후에 하려고 두었으나 오늘 다시 해결하려고 해도 하지 못하여 막혔습니다..
+추후 수정하고 늦게라도 제출하겠습니다..
+결석처리하시면 될 것 같습니다...ㅠㅠㅠ 과제 미완 정말 죄송합니다..! + + +스크린샷 2023-11-10 오후 10 08 50 + +스크린샷 2023-11-10 오후 10 08 58 + +### 참고 : +- https://ifuwanna.tistory.com/261(맥에서 폴더 내에서 터미널을 여는 방법을 몰라서 참고함) +- https://devkingdom.tistory.com/148 (참고했으나 이미 내가 embedded를 사용하고 있어서 실패) +- https://primary.tistory.com/11 diff --git "a/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/classDiagram_leeseohyun.PNG" "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/classDiagram_leeseohyun.PNG" new file mode 100644 index 0000000..3cb7cdb Binary files /dev/null and "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/classDiagram_leeseohyun.PNG" differ diff --git "a/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/domain_leeseohyun/Blogpost.java" "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/domain_leeseohyun/Blogpost.java" new file mode 100644 index 0000000..060dcd4 --- /dev/null +++ "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/domain_leeseohyun/Blogpost.java" @@ -0,0 +1,28 @@ +package com.example.serverstudy4.domain_leeseohyun; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Entity +@Getter @Setter +public class Blogpost { + @Id + @GeneratedValue + @Column(name="blogpost_id") + private Long id; //id + private String title; //제목 + private String content; //내용 + private LocalDateTime timestamp; //포스팅된 당시의 시각 + + //Many 쪽에 외래키를 둔다. User, Category둘 다 대해 Many쪽임 + @ManyToOne + @JoinColumn(name="user_id") + private User user; + + @ManyToOne + @JoinColumn(name = "category_id") + private Category category; +} diff --git "a/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/domain_leeseohyun/Category.java" "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/domain_leeseohyun/Category.java" new file mode 100644 index 0000000..05299dd --- /dev/null +++ "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/domain_leeseohyun/Category.java" @@ -0,0 +1,21 @@ +package com.example.serverstudy4.domain_leeseohyun; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter @Setter +public class Category { + @Id + @GeneratedValue + @Column(name="category_id") + private Long id; //id + private String category_name; //카테고리명 + + @OneToMany(mappedBy="category") //one 쪽이다 + private List blogposts = new ArrayList<>(); +} diff --git "a/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/domain_leeseohyun/Resume.java" "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/domain_leeseohyun/Resume.java" new file mode 100644 index 0000000..4b7b9ba --- /dev/null +++ "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/domain_leeseohyun/Resume.java" @@ -0,0 +1,20 @@ +package com.example.serverstudy4.domain_leeseohyun; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Getter @Setter +public class Resume { + @Id + @GeneratedValue + @JoinColumn(name="resume_id") + private Long id; + private String title; //제목 + private String content; //내용 + + @ManyToOne //Many 쪽임 + @JoinColumn(name="user_id") + private User user; +} diff --git "a/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/domain_leeseohyun/User.java" "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/domain_leeseohyun/User.java" new file mode 100644 index 0000000..b4eb313 --- /dev/null +++ "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/domain_leeseohyun/User.java" @@ -0,0 +1,27 @@ +package com.example.serverstudy4.domain_leeseohyun; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Setter +public class User { + + @Id + @GeneratedValue + @Column(name="user_id") + private Long id; //id + private String name; //이름 + private int age; //나이 + private String country; //국가 + private String city; //도시 + private String postal_code; //우편번호 + + @OneToMany(mappedBy="user") //one 쪽이다 + private List blogposts = new ArrayList<>(); +} diff --git "a/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/img1.png" "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/img1.png" new file mode 100644 index 0000000..9e39a83 Binary files /dev/null and "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/img1.png" differ diff --git "a/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/img2.png" "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/img2.png" new file mode 100644 index 0000000..098034a Binary files /dev/null and "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/img2.png" differ diff --git "a/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\352\271\200\353\246\254\353\202\230_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\352\271\200\353\246\254\353\202\230_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..3fdf70c --- /dev/null +++ "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\352\271\200\353\246\254\353\202\230_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,19 @@ +## 4주차 Server session 기본 과제 +- `http:www.도메인이름` 으로 접근하여 사용자용 Nginx 웹 서버 보여주기 +- `http:dev.도메인이름` 으로 접근하여 사용자용 Nginx 웹 서버 보여주기 + +

+ +## 해결과정 + +1. route 53 호스팅 영역에서 레코드 추가
+ `dev.cloud-ri.shop` 이름으로 레코드 추가 +image + +2. 로드밸런서 리스너 규칙 추가
+ `dev.cloud-ri.shop`을 호스트헤더로 해서 web-dev로 전달하는 규칙 추가 +스크린샷 2023-11-09 022938 +

+ +## 과제 인증 +https://drive.google.com/file/d/1YwZSKx8XbI9Q_pZIN6fIK_rqyVQvWDbd/view?usp=sharing diff --git "a/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\352\271\200\354\244\221\355\230\204_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\352\271\200\354\244\221\355\230\204_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..294a498 --- /dev/null +++ "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\352\271\200\354\244\221\355\230\204_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,74 @@ +# 4주차 서버 세션 +## ✏️ 기본 과제 +```markdown +1. Bastion Host 없이 모든 서버에 퍼블릭 IP 부여하기 +2. `http://www.도메인이름`으로 접근하면 사용자용 Nginx 웹 서버가 보이고, `http://dev.도메인이름`으로 접근하면 관리자용 웹 서버가 보인다. +``` +
+ +## 기본 과제 인증 +1. ALB 리스너 규칙 + 스크린샷 2023-11-10 오후 12 12 15 + +2. DNS 접근
+ → [영상 인증](https://drive.google.com/file/d/18HRALxm2ToXLiYP-37LGisrY-CT0X97g/view?usp=sharing) +
+ +## 기본 과제 해결 과정 +1. `ALB`에 dev용 리스너 규칙 추가 + - 호스트 헤더값 `dev.joong.store`, 대상 그룹 `web-dev`로 전달되도록 설정한다. + - 규칙 우선수위 1순위는 user용으로 사용했기 때문에 2순위로 설정한다. + - cf) 규칙은 가장 낮은 값에서 가장 높은 값에 이르기까지 우선 순위에 따라 평가된다. 기본 규칙은 마지막에 평가된다. +
+ +2. `joongh.store` 도메인에 `dev.joongh.store`를 name으로 하는 A 레코드를 생성한다. + 스크린샷 2023-11-10 오후 12 16 35 + +
+ +3. 로컬에서 `web-dev`와 `web-user` 서버에 각 index.html 파일을 전송하고, `/usr/share/nginx/html` 경로에 있는 index.html를 대체한다. + 스크린샷 2023-11-10 오후 12 29 21 + + 스크린샷 2023-11-10 오후 12 36 35 + +
+ +## 📚 심화 과제 +```markdown +1. 기본 과제를 통해 구축한 서버를 활용해 ACM 발급 및 적용하기 +2. ALB는 "https://도메인이름"으로 접근하면 Nginx 웹 서버가 보이고, "http://도메인이름"으로 접근하면 자동으로 HTTPS 트래픽으로 리다이렉트된다. +``` +
+ +## 심화 과제 인증 +- `https://도메인이름`으로 접근 시 Nginx 웹 서버가 보이고, `http://도메인이름`으로 접근 시 HTTPS로 리다이렉션이 일어나는 모습
+→ [영상 인증](https://drive.google.com/file/d/1N_jYWKbYMFhBppjxn2_1AzpSe4fhRJsf/view?usp=sharing) + +
+ +## 심화 과제 해결 과정 +1. ACM 퍼블릭 인증서 요청 + - 루트 도메인뿐만 아니라, 모든 서브 도메인에 대해서 인증서를 발급받기 위해 `joongh.store`, `*.joongh.store`로 도메인 이름을 설정한다. + 스크린샷 2023-11-10 오후 12 56 15 + +2. 생성된 인증서를 클릭해 모든 도메인에 대해 `Route53에서 레코드 생성`한다. +3. ALB의 리스너 규칙 중 `HTTP`를 `HTTPS`로 수정하고, 보안 리스너 설정 파트에서 위에서 생성한 ACM 인증서를 선택해준다. + 스크린샷 2023-11-10 오후 1 05 49 + +
+ +4. HTTPS 트래픽을 허용하도록 ALB 보안그룹의 인바운드 규칙을 수정한다. + 스크린샷 2023-11-10 오후 1 09 19 + 스크린샷 2023-11-10 오후 1 11 01 + +
+
+ +5. HTTP로 들어오는 트래픽을 HTTPS로 리디렉션 해주는 HTTP 리스너를 생성한다. + 스크린샷 2023-11-10 오후 1 12 48 + +
+ +## 참고 +- ALB 리스너 규칙 + https://docs.aws.amazon.com/ko_kr/elasticloadbalancing/latest/application/listener-update-rules.html diff --git "a/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..dd40901 --- /dev/null +++ "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,40 @@ +## 📒 Cloud 4주차 과제 +> 1. `http://www.도메인이름`에 접근하면 사용자용 Nginx 웹 서버가 보임 +> 2. `http://dev.도메인이름`에 접근하면 관리자용 Nginx 웹 서버가 보임 + +### 과제 인증 +1. 리스너 규칙 3개 설정한 모습 (`기본값`, `www.`, `dev.`) +

+ +

+ +2. [영상 인증 - DNS 접근](https://drive.google.com/file/d/1A8cTJ0KN9mDw0APXvZUpGBvzOLS2ckQ6/view?usp=sharing) + + +### 과제를 해결한 방법 +1. 서로 다른 AZ 2개에 `public subnet`을 생성한다. +2. `dev` EC2와 `user` EC2를 생성한다.
+ public IP를 활성화 하고 보안그룹을 생성하여 `ssh`, `HTTP` 모두 위치 무관(모든 IP 허용)으로 규칙을 생성한다. +3. 대상 그룹을 생성한다.
+ 3-1. `web-dev` 대상 그룹을 생성하여 `dev` EC2를 대상 등록한다.
+ 3-2. `web-user` 대상 그룹을 생성하여 `user` EC2를 대상 등록한다. +4. `dev` EC2와 `user` EC2에 연결 후, `NGINX`를 설치한다. + ```bash + sudo yum update -y # 업데이트 실시 + sudo yum install nginx # yum을 이용한 Apache 설치 + sudo systemctl start nginx # nginx 시작 + sudo systemctl enable nginx # nginx 웹 서버가 시스템이 부팅할 때마다 시작되도록 함 + sudo systemctl status nginx # 상태 확인 + ``` +5. 로드밸런서를 생성하고, `public subnet`에 매핑한다.
+ 5-1. `alb-all` 보안그룹을 생성하고, 인바운드 규칙(HTTP를 모든 IP에 대해 허용)과 아웃바운드 규칙(모든 트래픽에 대해 전부 허용)을 등록한다.
+ 5-2. 대상그룹은 `web-user`를 선택한다. +6. 구입한 도메인에 대한 호스팅 영역을 생성한다. +7. [가비아](https://www.gabia.com/)에서 NS 레코드를 참고하여 네임 서버를 설정한다. +8. `alb`와 `Route53` 연결을 위해 레코드를 생성한다.
+ 8-1. 루트 도메인(`haewonlee.site`)에 대해 A 레코드를 생성한다.
+ 8-2. 서브 도메인 `www.haewonlee.site` 와 `dev.haewonlee.site`에 대해 A 레코드를 생성한다. +9. `alb`의 리스너 규칙을 추가한다.
+ 9-1. 루트 도메인과 `www.haewonlee.site` 는 `web-user` 대상 그룹으로 전달한다.
+ 9-2. `dev.haewonlee.site` 는 `web-dev` 대상 그룹으로 전달한다. +10. `dev` EC2와 `user` EC2의 `/usr/share/nginx/html/index.html` 파일을 서로 다른 내용으로 수정한다. diff --git "a/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\236\204\354\230\201\354\204\234_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\236\204\354\230\201\354\204\234_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..58f8942 --- /dev/null +++ "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\236\204\354\230\201\354\204\234_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,35 @@ +###인증 사진 + +1.Bastion을 통해서 Private IP를 가진 EC2애 SSH 연결 +![img1.png](img1.png) + +2.nginx 설치 +![img2.png](img2.png) + +3.HTTP 웹페이지 영상 +[Web link]() + + +###과제를 해결한 방법 + +1.서로 다른 가용영역에 있는 퍼블릭 서브넷 2개를 생성한다.(퍼블릭 서브넷은 라우팅 테이블에 연결되어 있어야한다) +2.dev EC2와 user EC2를 생성한다 +3.대상 그룹을 생성하여 EC2를 등록한다. + -dev는 web-dev대상 그룹에 + -user은 web-user대상그룹에 +4.dev와 user를 EC2에 연결하고 NGINX를 설치한다. + sudo yum update -y # 업데이트 실시 + sudo yum install nginx # yum을 이용한 Apache 설치 + sudo systemctl start nginx # nginx 시작 + sudo systemctl enable nginx # nginx 웹 서버가 시스템이 부팅할 때마다 시작되도록 함 + sudo systemctl status nginx # 상태 확인 +5.가비아에서 도메인 구매 후 퍼블릭 호스팅 영역을 생성한다. + -AWS Route53에서 할당받은 네임서버를 가비아>구입도메인>관리 탭에 넣어준다. +6.ALB와 Route53이 연결되도록 레코드를 생성한다. + -루트도메인에 대해 A레코드 생성 + -서브도메인(www,dev)에 대해 A레코드 생성 +7.alb 리스너 규칙 추가 + -루트도메인(xohalox.com)과 서브도메인(www.xohlox.com)은 web-user대상그룹으로 + -서브도메인(dev.xohalox.com)은 wev-dev대상 그룹으로 +8.dev EC2와 userEC2의 /usr/share/nginx/html/index.html파일을 수정한다 + -sudo nano /usr/share/nginx/html/index.html 명령어 이용 \ No newline at end of file diff --git "a/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..16364a7 --- /dev/null +++ "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,37 @@ +## 📖4주차 기본 과제 +1. http://www.도메인이름 으로 접근시 사용자용 nginx 서버 접속 +2. http://dev.도메인이름 으로 접근시 관리자용 nginx 서버 접속 + +## 🤓해결 과정 +1. dev.도메인이름 로 A유형 레코드 생성 +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/67634926/ed8a77be-3848-4425-aa31-a6c96e090501) +2. 로드밸런서 리스너 규칙 수정 + dev.도메인이름 를 호스트헤더로 하고 web-dev로 전달하는 2순위 규칙 추가 +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/67634926/e146aca6-f30b-4b9c-8b0d-491ae3a0d888) + +## ✌️과제 인증 +https://drive.google.com/file/d/1tqpxE1VgiuDXDRUeasbTvpfGmYKrQIMh/view?usp=sharing + +

+## 📖4주차 심화 과제 +1. ACM 인증서 발급 +2. 로드 밸런서 리스너 편집 및 추가를 통해 http접속 시 https로 리다이렉트 적용 + +## 🤓해결 과정 +1. ACM 인증서 요청 + 이때, 서브 도메인까지 전부 적용하기 위해 도메인이름*.도메인이름 모두 등록하기 +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/67634926/da8da0da-f332-4bd2-a05a-793cdc0846c8) + +2. 생성한 인증서 클릭 -> Route53에서 레코드 생성 버튼 클릭 +3. EC2, 로드밸런서 인바운드 규칙 수정 - HTTPS 443 허용 +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/67634926/2e6b51a2-bad9-4889-8ee7-92fde7d53d4e) +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/67634926/9d20ea67-8510-486e-82d1-a9c5c28564ad) +4. 리스너 규칙 편집 + 기존에 HTTP로 되어있던걸 HTTPS로 변경 (대상 그룹으로 전달 유지) +6. 리스너 규칙 추가 + HTTP를 HTTPS로 연결하도록 'URL로 리다이렉션' 등록 + ![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/67634926/d749b63e-3116-4527-80af-980755ec31a5) + +## ✌️과제 인증 +https://drive.google.com/file/d/1lqdPPQWXkG8bn_cXgfI8LcKdKHSTq8De/view?usp=sharing + diff --git "a/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\231\251\354\261\204\354\233\220_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\231\251\354\261\204\354\233\220_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..bbc9102 --- /dev/null +++ "b/4\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\231\251\354\261\204\354\233\220_4\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,54 @@ +# 황채원_4주차_과제 + +### 과제 영상 + +https://drive.google.com/file/d/1h_hNfldFO7nl_rnm0w7MuTLQekavfASQ/view?usp=drive_link + + +### 해결 과정 + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/937b7d44-c78f-4833-8fca-e8bf9ede3d22) + +dev.cloud-chaiwon.store 에 대한 레코드를 생성해준다. + + + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/2ced2eec-c4af-45f1-861d-c449ed3ea368) + +HTTP 호스트 헤더는 dev.cloud-chaiwon.store 인 경우에 web-dev 로 전달해주도록 alb 규칙을 설정해준다. + + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/4d7bdae5-c6e9-4345-8800-d97b1eea754c) + +web-user 서버에 frontend-user.html 업로드 + + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/e144258f-5a00-4ac9-9d56-a8d3c0d7eeeb) + +frontend-user.html로 index.html을 덮어쓰기 해준다. + + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/81b6534b-e71a-4b0e-9824-50a5b45d8c26) + +해당 파일이 정상적으로 보인다. + + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/9efc0364-c5c5-4926-9e12-f8aeb7ed68e0) + +같은 방식에도 서버에도 파일을 업로드 해준다. + + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/8899395d-e127-471b-acc2-a52b9e547851) + +dev로 접속했을 때 해당 파일이 정상적으로 보인다. + + + +### 서버에 파일을 업로드 하는 과정 + +1. `sudo chmod 600 [key name].pem` 으로 키 권한 바꿔주기 +2. `ssh -i [key name].pem ec2-user@[public ip]` ssh 접속하기 +3. `sudo mv [local file] /usr/share/nginx/html/` 서버에 파일 올려주기 +4. (optional) `ls -l /usr/share/nginx/html/` 잘 올라갔는지 확인 +5. `sudo systemctl restart nginx` + diff --git "a/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller.java" "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller.java" new file mode 100644 index 0000000..bca173a --- /dev/null +++ "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller.java" @@ -0,0 +1,86 @@ + +package ServerStudy5Cloud.ServerStudy5Cloud.Controller; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.ObjectListing; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.S3ObjectSummary; +import lombok.RequiredArgsConstructor; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + + +@Controller +@RequiredArgsConstructor +public class S3Controller { + + private final AmazonS3 amazonS3; + + @Value("${cloud.aws.s3.bucket}") + private String bucketName; + + @GetMapping("/") + public String listFiles(Model model) { + //getUrl로 객체 URL 가져온 후, List에 넣어 index.html에 반환하기 + + //업로드할 URL 파일 목록을 저장할 리스트 생성 + List fileUrls = new ArrayList<>(); + + //S3 내의 객체 목록 가져오기 + ObjectListing objectListing = amazonS3.listObjects(bucketName); + List objectSummaries = objectListing.getObjectSummaries(); + + // 객체 URL 가져와서 리스트에 추가 + for (S3ObjectSummary objectSummary : objectSummaries) { + String fileUrl = amazonS3.getUrl(bucketName, objectSummary.getKey()).toString(); + fileUrls.add(fileUrl); + } + + // 모델에 URL 리스트 추가 + model.addAttribute("fileUrls", fileUrls); + + return "index"; + } + + + + @PostMapping("/upload") + public String uploadFile(@RequestParam("file") MultipartFile file) throws IOException{ + + //putObject와 setObjectAcl로 이미지 업로드하고 ACL 퍼블릭으로 만들기 + //파일 이름가져오기 + String originalFilename = file.getOriginalFilename(); + + //메타데이터 설정 + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(file.getSize()); + metadata.setContentType(file.getContentType()); + + // putObject로 s3에 파일 업로드 + amazonS3.putObject(bucketName, originalFilename, file.getInputStream(),metadata); + + //업로드한 객체에 ACL퍼블릭 설정 + amazonS3.setObjectAcl(bucketName, originalFilename, CannedAccessControlList.PublicRead); + + return "redirect:/"; + + + } +} + +//https://drive.google.com/file/d/1o9uRjxExZHYJOCKX6aTS8y8KKc-vic0d/view?usp=sharing + diff --git "a/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_HaYunji.java" "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_HaYunji.java" new file mode 100644 index 0000000..5c55634 --- /dev/null +++ "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_HaYunji.java" @@ -0,0 +1,70 @@ +package ServerStudy5Cloud.ServerStudy5Cloud.Controller; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.S3ObjectSummary; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + + +@Controller +@RequiredArgsConstructor +public class S3Controller { + + private final AmazonS3 amazonS3; + + @Value("${cloud.aws.s3.bucket}") + private String bucketName; + + @GetMapping("/") + public String listFiles(Model model) { + //getUrl로 객체 URL 가져온 후, List에 넣어 index.html에 반환하기 + List imageUrls = new ArrayList<>(); + + //S3 버킷 내 모든 객체 리스트 가져오기 + List objectSummaries = amazonS3.listObjects(bucketName).getObjectSummaries(); + + //각 객체의 URL을 가져와서 리스트에 추가하기 + for(S3ObjectSummary ob : objectSummaries){ //반복문을 돌면서 리스트의 모든 객체에 접근 + String objectKey = ob.getKey(); //객체의 key값 가져오기 + String objectUrl = amazonS3.getUrl(bucketName, objectKey).toString(); //key값으로 url가져오기 + imageUrls.add(objectUrl); //list에 저장 + } + + //모델에 객체 URL 리스트 추가 + //index.html은 ${fileUrls}를 이용하여 객체에 접근 + model.addAttribute("fileUrls", imageUrls); + + return "index"; + } + + @PostMapping("/upload") + public String uploadFile(@RequestParam("file") MultipartFile file) { + + //putObject와 setObjectAcl로 이미지 업로드하고 ACL 퍼블릭으로 만들기 + + try{ + //s3에 파일 업로드 + amazonS3.putObject(bucketName, file.getOriginalFilename(), file.getInputStream(), null); + + //업로드한 객체에 대해 ACL 설정 + amazonS3.setObjectAcl(bucketName, file.getOriginalFilename(), CannedAccessControlList.PublicRead); + + } catch (IOException e) { + e.printStackTrace(); + } + + return "redirect:/"; + + } +} diff --git "a/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_JoongHyunKim.java" "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_JoongHyunKim.java" new file mode 100644 index 0000000..212689d --- /dev/null +++ "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_JoongHyunKim.java" @@ -0,0 +1,56 @@ +package ServerStudy5Cloud.ServerStudy5Cloud.Controller; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.*; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 영상 인증: https://drive.google.com/file/d/1aoKdLbbNA8EHkJmxeYoLGfrx0gwYSfdg/view?usp=sharing + */ +@Controller +@RequiredArgsConstructor +public class S3Controller { + + private final AmazonS3 amazonS3; + + @Value("${cloud.aws.s3.bucket}") + private String bucketName; + + @GetMapping("/") + public String listFiles(Model model) { + // 버킷의 object list 조회 + ObjectListing objectListing = amazonS3.listObjects(new ListObjectsRequest().withBucketName(bucketName)); + + // object list에서 각 object의 url을 조회해 list 생성 + List urls = objectListing.getObjectSummaries().stream() + .map(os -> amazonS3.getUrl(bucketName, os.getKey()).toString()) + .collect(Collectors.toList()); + model.addAttribute("urls", urls); // 모델에 추가해 뷰로 전달 + + return "index"; + } + + @PostMapping("/upload") + public String uploadFile(@RequestParam("file") MultipartFile file) throws IOException { + // 전송한 파일 이름 조회 + String fileName = file.getOriginalFilename(); + + // 버킷에 파일 업로드 + amazonS3.putObject(new PutObjectRequest(bucketName, fileName, file.getInputStream(), null)); + // 파일의 접근 권한을 public으로 설정 + amazonS3.setObjectAcl(bucketName, fileName, CannedAccessControlList.PublicRead); + + return "redirect:/"; + } +} diff --git "a/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_LeeHaewon.java" "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_LeeHaewon.java" new file mode 100644 index 0000000..2055cfd --- /dev/null +++ "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_LeeHaewon.java" @@ -0,0 +1,75 @@ +package GDSC.ServerStudyCloud5.Controller; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.ObjectMetadata; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +/* + + [Cloud 5주차 과제] + 1. S3에 객체 업로드가 가능한 @PostMapping 구현 + 2. S3 객체 조회가 가능한 @GetMapping 구현 + + 영상 인증 : https://drive.google.com/file/d/1oLtqv3YUAEVZu-bkpyFeafFT2Jj8jysW/view?usp=sharing + +*/ + +@Controller +@RequiredArgsConstructor +public class S3Controller { + + private final AmazonS3 amazonS3; + + @Value("${cloud.aws.s3.bucket}") + private String bucketName; + + @GetMapping("/") + public String listFiles(Model model) { + List imageUrls = getImageUrls(); + model.addAttribute("fileUrls", imageUrls); // fileUrls 값 + + return "index"; // index.html 반환 + } + + private List getImageUrls() { + + // S3 버킷 내의 모든 객체의 키(파일 경로)를 가져오기 + List objectKeys = amazonS3.listObjects(bucketName).getObjectSummaries() + .stream() + .map(s3ObjectSummary -> s3ObjectSummary.getKey()) + .collect(Collectors.toList()); + + // 이미지 URL로 변환 + return objectKeys.stream() + .map(objectKey -> amazonS3.getUrl(bucketName, objectKey).toString()) // 객체 URL 가져오기 + .collect(Collectors.toList()); + } + + @PostMapping("/upload") + public String uploadFile(@RequestParam("file") MultipartFile file) throws IOException{ + + String originalFilename = file.getOriginalFilename(); + + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(file.getSize()); // 메타데이터 설정 + metadata.setContentType(file.getContentType()); // 메타데이터 설정 + + amazonS3.putObject(bucketName, originalFilename, file.getInputStream(), metadata); // 이미지 업로드 + amazonS3.setObjectAcl(bucketName, originalFilename, CannedAccessControlList.PublicRead); // ACL 퍼블릭으로 만들기 + + return "redirect:/"; + + } +} \ No newline at end of file diff --git "a/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_LeeYeji.java" "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_LeeYeji.java" new file mode 100644 index 0000000..1bd7ef0 --- /dev/null +++ "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_LeeYeji.java" @@ -0,0 +1,76 @@ +/* + ## 과제 수행 인증 동영상 + https://drive.google.com/file/d/10d7Rp4JSPHYUbRt4dOeozRHmkafxJ32R/view?usp=sharing + + ##메서드별 기능 생각해보기 + 1. GetMapping("/"): + - 이 메서드는 웹 어플리케이션의 루트 경로("/")에 대한 GET 요청을 처리 + - **`listFiles(Model model)`** 메서드는 S3 버킷 내의 파일 목록을 가져와서 **`index.html`**에 반환 + - 여기서 **`Model`** 객체를 사용하여 파일 목록을 뷰로 전달 + - **`index.html`**은 파일 목록을 화면에 표시할 템플릿 파일, 파일 목록을 가져와 화면에 표시하도록 해당 템플릿을 구성 + + 2. PostMapping("/upload"): + - 이 메서드는 **`/upload`** 엔드포인트에 POST 요청을 처리 + - **`uploadFile(@RequestParam("file") MultipartFile file)`** 메서드는 클라이언트로부터 업로드된 파일을 처리 + - **`MultipartFile`**은 HTTP 요청에서 전송된 파일을 나타내며, 해당 파일을 Amazon S3에 업로드하는 작업을 수행 + - **`amazonS3.putObject()`** 또는 관련 메서드를 사용하여 파일을 S3 버킷에 업로드하고, 업로드된 파일에 대한 ACL(Access Control List)을 설정하여 해당 파일을 퍼블릭으로 만들 수 있음 + - 이후에는 **`redirect:/`**를 사용하여 파일 업로드가 완료된 후에는 루트 경로로 리디렉션하여 홈 화면으로 이동하도록 설정 + + 위 내용 바탕으로 아래에 코드 채워넣기 +*/ + +package ServerStudy5Cloud.ServerStudy5Cloud.Controller; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.ListObjectsV2Result; +import com.amazonaws.services.s3.model.S3ObjectSummary; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@Controller +@RequiredArgsConstructor +public class S3Controller { + + private final AmazonS3 amazonS3; + + @Value("${cloud.aws.s3.bucket}") + private String bucketName; + + @GetMapping("/") + public String listFiles(Model model) { + List fileUrls = new ArrayList<>(); + + ListObjectsV2Result objects = amazonS3.listObjectsV2(bucketName); + for (S3ObjectSummary objectSummary : objects.getObjectSummaries()) { + String fileUrl = amazonS3.getUrl(bucketName, objectSummary.getKey()).toString(); + fileUrls.add(fileUrl); + } + + model.addAttribute("fileUrls", fileUrls); + return "index"; + } + + @PostMapping("/upload") + public String uploadFile(@RequestParam("file") MultipartFile file) { + try { + amazonS3.putObject(bucketName, file.getOriginalFilename(), file.getInputStream(), null); + amazonS3.setObjectAcl(bucketName, file.getOriginalFilename(), CannedAccessControlList.PublicRead); + } catch (IOException e) { + // 에러 처리 로직 추가 + e.printStackTrace(); + } + + return "redirect:/"; + } +} \ No newline at end of file diff --git "a/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_LimYoungseo.java" "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_LimYoungseo.java" new file mode 100644 index 0000000..283468e --- /dev/null +++ "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_LimYoungseo.java" @@ -0,0 +1,79 @@ +//package ServerStudy5Cloud.ServerStudy5Cloud.Controller; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.ObjectListing; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.S3ObjectSummary; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + + +@Controller +@RequiredArgsConstructor +public class S3Controller { + + private final AmazonS3 amazonS3; + + @Value("${cloud.aws.s3.bucket}") + private String bucketName; + + @GetMapping("/") + public String listFiles(Model model) { + //getUrl로 객체 URL 가져온 후, List에 넣어 index.html에 반환하기 + //업로드할 URL 파일 목록을 저장할 리스트 생성 + List fileUrls = new ArrayList<>(); + + //S3 내의 객체 목록 가져오기 + ObjectListing objectListing = amazonS3.listObjects(bucketName); + List objectSummaries = objectListing.getObjectSummaries(); + + // 객체 URL 가져와서 리스트에 추가 + for (S3ObjectSummary objectSummary : objectSummaries) { + String fileUrl = amazonS3.getUrl(bucketName, objectSummary.getKey()).toString(); + fileUrls.add(fileUrl); + } + + // 모델에 URL 리스트 추가 + model.addAttribute("fileUrls", fileUrls); + + return "index"; + } + + + + @PostMapping("/upload") + public String uploadFile(@RequestParam("file") MultipartFile file) throws IOException{ + + //putObject와 setObjectAcl로 이미지 업로드하고 ACL 퍼블릭으로 만들기 + //파일 이름가져오기 + String originalFilename = file.getOriginalFilename(); + + //메타데이터 설정 + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(file.getSize()); + metadata.setContentType(file.getContentType()); + + // putObject로 s3에 파일 업로드 + amazonS3.putObject(bucketName, originalFilename, file.getInputStream(),metadata); + + //업로드한 객체에 ACL퍼블릭 설정 + amazonS3.setObjectAcl(bucketName, originalFilename, CannedAccessControlList.PublicRead); + + return "redirect:/"; + + + } +} + +//https://drive.google.com/file/d/1o9uRjxExZHYJOCKX6aTS8y8KKc-vic0d/view?usp=sharing \ No newline at end of file diff --git "a/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_chaiwonHwang.java" "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_chaiwonHwang.java" new file mode 100644 index 0000000..dbe65ff --- /dev/null +++ "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/S3Controller_chaiwonHwang.java" @@ -0,0 +1,64 @@ +package ServerStudy5Cloud.ServerStudy5Cloud.Controller; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.*; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.server.ResponseStatusException; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +// https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/AmazonS3.html +// https://growth-coder.tistory.com/116 +@Controller +@RequiredArgsConstructor +public class S3Controller { + + private final AmazonS3 amazonS3; + + @Value("${cloud.aws.s3.bucket}") + private String bucketName; + + @GetMapping("/") + public String listFiles(Model model) { + //getUrl로 객체 URL 가져온 후, List에 넣어 index.html에 반환하기 + List fileUrls = new ArrayList<>(); + List objectSummaries = amazonS3.listObjects(bucketName).getObjectSummaries(); + for (S3ObjectSummary os : objectSummaries) { + String url = "https://" + bucketName + ".s3.amazonaws.com/" + os.getKey(); + fileUrls.add(url); + } + model.addAttribute("fileUrls", fileUrls); + return "index"; + } + + @PostMapping("/upload") + public String uploadFile(@RequestParam("file") MultipartFile file) { + //putObject와 setObjectAcl로 이미지 업로드하고 ACL 퍼블릭으로 만들기 + // 파일 업로드 로직 + String fileName = file.getOriginalFilename(); + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(file.getSize()); + metadata.setContentType(file.getContentType()); + + try { + amazonS3.putObject(bucketName, fileName, file.getInputStream(), metadata); + } catch (IOException e) { + throw new RuntimeException(e); + } + amazonS3.setObjectAcl(bucketName, fileName, CannedAccessControlList.PublicRead); + return "redirect:/"; + + } +} \ No newline at end of file diff --git "a/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/application_JoongHyunKim.yml" "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/application_JoongHyunKim.yml" new file mode 100644 index 0000000..5d08cb2 --- /dev/null +++ "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/application_JoongHyunKim.yml" @@ -0,0 +1,9 @@ +cloud: + aws: + s3: + bucket: gdsc-server-prac + stack.auto: false + region.static: ap-northeast-2 + credentials: + access-key: ${AWS_ACCESS_KEY} + secret-key: ${AWS_SECRET_KEY} diff --git "a/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/files_leeseohyun/S3Controller.java" "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/files_leeseohyun/S3Controller.java" new file mode 100644 index 0000000..c08ae6e --- /dev/null +++ "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/files_leeseohyun/S3Controller.java" @@ -0,0 +1,53 @@ +package ServerStudy5Cloud.ServerStudy5Cloud.Controller; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.S3ObjectSummary; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + + +@Controller +@RequiredArgsConstructor +public class S3Controller { + + private final AmazonS3 amazonS3; + + @Value("${cloud.aws.s3.bucket}") + private String bucketName; + + @GetMapping("/") + public String listFiles(Model model) { + // S3 버킷의 객체 목록 가져오기 + List objectSummaries = amazonS3.listObjectsV2(bucketName).getObjectSummaries(); + // getUrl로 객체 URL 가져온 후, List에 넣어 index.html에 반환 + List fileUrls = new ArrayList<>(); + for (S3ObjectSummary os : objectSummaries) { + String url = amazonS3.getUrl(bucketName, os.getKey()).toString(); + fileUrls.add(url); + } + model.addAttribute("fileUrls", fileUrls); + return "index"; + } + + @PostMapping("/upload") + public String uploadFile(@RequestParam("file") MultipartFile file) throws IOException { + String filename = file.getOriginalFilename(); + // putObject로 파일을 S3 버킷에 업로드 + amazonS3.putObject(bucketName, filename, file.getInputStream(), null); + // ACL 퍼블릭으로 설정 + amazonS3.setObjectAcl(bucketName, filename, CannedAccessControlList.PublicRead); + return "redirect:/"; + } +} +// 동작 영상 : https://drive.google.com/file/d/10wPjsBbnbIR5E6pKbwmr2sZ-GsLY-FWP/view?usp=sharing \ No newline at end of file diff --git "a/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/files_leeseohyun/application.yml" "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/files_leeseohyun/application.yml" new file mode 100644 index 0000000..4b7489c --- /dev/null +++ "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/files_leeseohyun/application.yml" @@ -0,0 +1,15 @@ +spring: + config: + import: optional:file:.env[.properties] +# 이렇게 .env 파일을 사용하려고 했지만 잘 동작하지 않아서... 빌드 Edit Configuration에서 Environment variables로 설정해서 했습니다...! +cloud: + aws: + s3: + bucket: ${S3_BUCKET} + stack.auto: false + region.static: ap-northeast-2 + credentials: + access-key: ${CREDENTIALS_ACCESS_KEY} + secret-key: ${CREDENTIALS_SECRET_KEY} +server: + port: 8080 \ No newline at end of file diff --git "a/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/index_JoongHyunKim.html" "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/index_JoongHyunKim.html" new file mode 100644 index 0000000..592fda0 --- /dev/null +++ "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/index_JoongHyunKim.html" @@ -0,0 +1,29 @@ + + + + + + + GDSC Server + + + +

AWS S3에 파일을 업로드해봐요!

+ +
+ + +
+ +

AWS S3의 파일을 확인해봐요!

+ + + + + +
+ +
+ + + diff --git "a/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\204\234\354\247\200\354\233\220_5\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\204\234\354\247\200\354\233\220_5\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..a41906a --- /dev/null +++ "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\204\234\354\247\200\354\233\220_5\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,19 @@ +1. IAM에서 AmazonS3FullAccess 권한을 가진 사용자 생성 -> application.yml의 accessKey와 secretKey에 입력
+2. ```getUrl```로 객체 URL 가져온 후, List에 넣어 index.html에 반환하기
+ a. ```listObjects```메서드로 버킷의 ```ObjectListing```객체에 대한 정보를 제공하는 객체를 반환
+ b. ```getObjectSummaries``` 메서드를 사용하여 각 객체가 버킷의 단일 ```ObjectSummary``` 객체를 나타내는 S3 객체 목록을 가져옴
+ c . 반복문을 돌며 summary에서 key 가져와 url을 list에 저장
+
+⚠️발생한 문제 + +![사진 안뜸](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/63919973/0191904f-9bb4-4e96-97c3-51ba5a0cc96b) +- 사진이 안뜸->버킷 읽기 권한이 없기 때문이었음 + ![access denied](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/63919973/eb333e7d-5f10-46d3-94b4-8b128a0af747) +- 버킷 권한 설정 + ![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/63919973/9abcb391-b84c-4880-b698-a75374f8b235) + +------>해결
+3. putObject와 setObjectAcl로 이미지 업로드하고 ACL 퍼블릭으로 만들기 + +[화면] +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/63919973/062ed883-ecde-4f1c-9d98-e014b7eb3dca) diff --git "a/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\354\204\234\355\230\204_5\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\354\204\234\355\230\204_5\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..ffedd12 --- /dev/null +++ "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\354\204\234\355\230\204_5\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,11 @@ +### 5주차 Server S-Day 과제 - 이서현 +> 과제: S3에 객체 업로드 및 조회 + +> 심화 과제: application.yml 변경 + +## 과제 +### 사진 +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/32611398/305cc543-d0df-46bc-8c28-d615a45a70fa) + +### 참고 자료 +* [Spring] 설정 파일 환경변수를 통해 숨기기 — Shin._.Mallang https://ttl-blog.tistory.com/1125 diff --git "a/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_5\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_5\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..883e047 --- /dev/null +++ "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_5\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,19 @@ +## 📖5주차 기본 과제 +1. S3 bucket에 사진 업로드 controller 작성 +2. S3 bucket 사진 리스트 가져오기 controller 작성 + +## 🤓해결 과정 +### 1️⃣ 사진 리스트 가져오기 +1. amazonS3.listObjects(bucketName).getObjectSummaries() 를 이용해 버킷 내 모든 객체 리스트 가져오기 +2. 반복문으로 각 객체의 url에 접근 +3. 객체의 key값을 이용해 amazonS3.getUrl(bucketName, objectKey).toString() 함수로 객체의 url 가져오기 +4. 리스트에 저장 +5. thymeleaf 적용을 위해 모델에 객체 url 리스트 추가 + +### 2️⃣ 사진 업로드하기 +1. amazonS3.putObject(bucketName, file.getOriginalFilename(), file.getInputStream(), null); 로 S3에 파일 업로드 +2. amazonS3.setObjectAcl(bucketName, file.getOriginalFilename(), CannedAccessControlList.PublicRead);로 ACL객체 권한을 Public read로 설정 + + +## ✌️과제 인증 +https://drive.google.com/file/d/1ZP0zgfgus9n5NOU51p686daHZSkrR84E/view?usp=sharing diff --git "a/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\231\251\354\261\204\354\233\220_5\354\243\274\354\262\264_\352\263\274\354\240\234.md" "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\231\251\354\261\204\354\233\220_5\354\243\274\354\262\264_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..2a90b32 --- /dev/null +++ "b/5\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\231\251\354\261\204\354\233\220_5\354\243\274\354\262\264_\352\263\274\354\240\234.md" @@ -0,0 +1,66 @@ +### 과제링크 + +https://drive.google.com/file/d/11ZL6ydk9WMiko-jp4E0RmoXrEZvhr-PN/view?usp=sharing + +--- + +### 파일 보기 구현 +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/e49f5e4f-b867-4ec2-8ed1-8132648b18ff) + +--- +### 파일 업로드 구현 + +``` +@PostMapping("/upload") + public String uploadFile(@RequestParam("file") MultipartFile file, Model model) { + //putObject와 setObjectAcl로 이미지 업로드하고 ACL 퍼블릭으로 만들기 + if (file.isEmpty()) { + model.addAttribute("message", "업로드할 파일이 없습니다."); + return "errorPage"; // 에러 메시지를 표시할 뷰 이름 + } + + try { + // 파일 업로드 로직 + String fileName = file.getOriginalFilename(); + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(file.getSize()); + metadata.setContentType(file.getContentType()); + + amazonS3.putObject(bucketName, fileName, file.getInputStream(), metadata); + amazonS3.setObjectAcl(bucketName, fileName, CannedAccessControlList.PublicRead); + return "redirect:/"; + + } catch (Exception e) { + // 예외 메시지를 모델에 추가 + model.addAttribute("message", "업로드 중 오류 발생: " + e.getMessage()); + return "errorPage"; // 에러 메시지를 표시할 뷰 이름 + } + + } +``` + + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/b85ab488-db69-4f2b-8ad8-f9ff94fb9d9d) + +에러 확인을 위해 errorPage.html을 추가하고 웹페이지에 에러가 출력되도록 설정해보았지만 + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/e046ee55-5af4-4338-b4a4-ab0ab6ecec55) + +여전히 같은 white label 에러가 나는 상황이다. + + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/0f9261c6-b74b-4935-afa7-4733eb024b9b) + +이것저것 시도해보았는데 허무하게도(?) 사진 사이즈가 너무 컸다는 것을 알게되었다... + +인터넷에서 적당히 작은 사진을 찾아 업로드해보았더니 아주 잘 올라간다... + + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/c6347889-4f61-4f91-bff1-82bcaae76ed1) + +--- +**참고** + +- https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/s3/AmazonS3.html + +- https://growth-coder.tistory.com/116 diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/Book.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/Book.java" new file mode 100644 index 0000000..23ddc33 --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/Book.java" @@ -0,0 +1,18 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Domain; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Setter @Getter +@Table(name = "book_info") +public class Book { + @Id + @GeneratedValue + @Column(name = "book_id") + private Long id; + + private String name;//책 이름 + private String reason;//해당 책을 좋아하는 이유 +} diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/BookForm.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/BookForm.java" new file mode 100644 index 0000000..2623542 --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/BookForm.java" @@ -0,0 +1,11 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Controller; + +import lombok.Getter; +import lombok.Setter; + +@Getter @Setter +public class BookForm { //폼에서 입력받은 필드를 그대로 적음 + //책 이름, 책 좋아하는 이유 + private String name; + private String reason; +} diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/Book.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/Book.java" new file mode 100644 index 0000000..88c4df8 --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/Book.java" @@ -0,0 +1,16 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Domain; + +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter @Setter +@Table(name = "book_info") +public class Book { + @Id + @GeneratedValue + @Column(name = "book_id") + private Long id; + private String name; + private String reason; +} diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/BookForm.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/BookForm.java" new file mode 100644 index 0000000..657a170 --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/BookForm.java" @@ -0,0 +1,9 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Controller; +import lombok.*; + +@Getter +@Setter +public class BookForm { + private String name; + private String reason; +} diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/RdsController.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/RdsController.java" new file mode 100644 index 0000000..05003b0 --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/RdsController.java" @@ -0,0 +1,34 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Controller; + +import ServerStudy6Cloud.ServerStudy6Cloud.Domain.Book; +import ServerStudy6Cloud.ServerStudy6Cloud.Service.RdsService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class RdsController { + private final RdsService rdsService; + + @GetMapping("/") + public ResponseEntity> readDB(){ + List bookList = rdsService.findBooks(); + return new ResponseEntity<>(bookList, HttpStatus.OK); + } + + @PostMapping("/upload") + public ResponseEntity updateDB(BookForm form){ + Book book = new Book(); + book.setName(form.getName()); + book.setReason(form.getReason()); + rdsService.saveBook(book); + return new ResponseEntity<>(HttpStatus.CREATED); + } +} diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/RdsRepository.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/RdsRepository.java" new file mode 100644 index 0000000..098961b --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/RdsRepository.java" @@ -0,0 +1,21 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Repository; + +import ServerStudy6Cloud.ServerStudy6Cloud.Domain.Book; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class RdsRepository { + private final EntityManager em; + public void save(Book book){ + em.persist(book); + } + + public List findAll(){ + return em.createQuery("select b from Book b", Book.class).getResultList(); + } +} diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/RdsService.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/RdsService.java" new file mode 100644 index 0000000..03ad612 --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/RdsService.java" @@ -0,0 +1,26 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Service; + +import ServerStudy6Cloud.ServerStudy6Cloud.Domain.Book; +import ServerStudy6Cloud.ServerStudy6Cloud.Repository.RdsRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class RdsService { + private final RdsRepository rdsRepository; + + @Transactional(readOnly = true) + public List findBooks(){ + return rdsRepository.findAll(); + } + + public Long saveBook(Book book){ + rdsRepository.save(book); + return book.getId(); + } +} diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/application.yml" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/application.yml" new file mode 100644 index 0000000..bb3c12f --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/JoongHyun-Kim/application.yml" @@ -0,0 +1,13 @@ +# RDS +spring: + datasource: + url: jdbc:mysql://gdsc-rds.cuzvt5d8qznc.ap-northeast-2.rds.amazonaws.com:3306/study6 + username: admin + password: [password] + driver-class-name: com.mysql.cj.jdbc.Driver + # hibernate + jpa: + show-sql: true + hibernate: + ddl-auto: update + dialect: org.hibernate.dialect.MySQL8Dialect diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/Book.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/Book.java" new file mode 100644 index 0000000..6add2b2 --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/Book.java" @@ -0,0 +1,18 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Domain; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Getter @Setter +@Table(name= "book_info") +public class Book { + @Id + @GeneratedValue + @Column(name="book_id") + private Long id; + + private String name; //책 이름 + private String reason; //해당 책을 좋아하는 이유 +} diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/BookForm.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/BookForm.java" new file mode 100644 index 0000000..100c259 --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/BookForm.java" @@ -0,0 +1,12 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Controller; + +import lombok.Getter; +import lombok.Setter; + +//BookForm: 폼에서 입력받은 내용과 entity에 넣을 내용 분리 위해 만든 클래스 +@Getter @Setter +public class BookForm { + //책 이름, 책 좋아하는 이유 + private String name; + private String reason; +} \ No newline at end of file diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/RdsController.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/RdsController.java" new file mode 100644 index 0000000..0cfdc22 --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/RdsController.java" @@ -0,0 +1,54 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Controller; + +import ServerStudy6Cloud.ServerStudy6Cloud.Domain.Book; +import ServerStudy6Cloud.ServerStudy6Cloud.Service.RdsService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController //API 만들기 +@RequiredArgsConstructor +public class RdsController { + private final RdsService rdsService; + //AWS RDS에서 Book list를 가져오는 GetMapping + + @GetMapping("/") + public ResponseEntity> readDB(){ + List bookList = rdsService.findBooks(); + return new ResponseEntity<>(bookList, HttpStatus.OK); + } + + @PostMapping("/upload") + public ResponseEntity updateDB(BookForm form){ + Book book = new Book(); + book.setName(form.getName()); + book.setReason(form.getReason()); + rdsService.saveBook(book); + return new ResponseEntity<>(HttpStatus.CREATED); + } + + + //AWS RDS에 Book 객체를 저장하는 PostMapping +} + +//@Controller에서 view에 리턴할 때의 컨트롤러 코드 + /*@GetMapping("/") + public String readDB(Model model){ + model.addAttribute("bookForm", new BookForm()); //BookForm 객체 넘기기 + model.addAttribute("books", rdsService.findBooks()); + return "index"; + } + @PostMapping("/upload") + public String updateDB(BookForm form){ + Book book = new Book(); + book.setName(form.getName()); + book.setReason(form.getReason()); + rdsService.saveBook(book); + return "redirect:/"; + } + */ \ No newline at end of file diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/RdsRepository.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/RdsRepository.java" new file mode 100644 index 0000000..86e3ea5 --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/RdsRepository.java" @@ -0,0 +1,23 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Repository; + +import ServerStudy6Cloud.ServerStudy6Cloud.Domain.Book; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class RdsRepository { + private final EntityManager em; + //DB에 새로운 책 저장하는 메서드 + public void save(Book book){ + em.persist(book); + } + //DB에서 모든 책 리스트 가져오는 메서드 + public List findAll() { + return em.createQuery("select b from Book b", Book.class) //JPQL 쿼리와 조회할 class + .getResultList(); + } +} diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/RdsService.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/RdsService.java" new file mode 100644 index 0000000..ac9007e --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/RdsService.java" @@ -0,0 +1,27 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Service; + +import ServerStudy6Cloud.ServerStudy6Cloud.Domain.Book; +import ServerStudy6Cloud.ServerStudy6Cloud.Repository.RdsRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class RdsService { + //RdsRepository를 사용해 DB에 저장하는 로직 + private final RdsRepository rdsRepository; //레포지토리 주입받음 + + @Transactional(readOnly=true) //조회에 최적화되게 Transaction 사용가능 + public List findBooks() { + return rdsRepository.findAll(); + } + + public Long saveBook(Book book) { + rdsRepository.save(book); + return book.getId(); //값 저장되었는지 확인하는 용도 + } +} diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/application.yml" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/application.yml" new file mode 100644 index 0000000..9fd0aad --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/LeeSeohyun/application.yml" @@ -0,0 +1,13 @@ +# RDS +spring: + datasource: + url: jdbc:mysql://gdsc-rds2.cf0i42aimwar.ap-northeast-2.rds.amazonaws.com:3306/study_6 + username: admin + password: [password] + driver-class-name: com.mysql.cj.jdbc.Driver + # hibernate + jpa: + show-sql: true + hibernate: + ddl-auto: update + dialect: org.hibernate.dialect.MySQL8Dialect \ No newline at end of file diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/RdsController.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/RdsController.java" new file mode 100644 index 0000000..845b7cf --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/RdsController.java" @@ -0,0 +1,52 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Controller; + +import ServerStudy6Cloud.ServerStudy6Cloud.Domain.Book; +import ServerStudy6Cloud.ServerStudy6Cloud.Service.RdsService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class RdsController { + private final RdsService rdsService; + //AWS RDS에서 Book list를 가져오는 GetMapping +// @GetMapping("/") +// public String readDB(Model model){ +// model.addAttribute("bookForm", new BookForm()); +// model.addAttribute("books", rdsService.findBooks()); +// return "index"; +// } + @GetMapping("/") + public ResponseEntity> readDB(){ + List bookList = rdsService.findBooks(); + return new ResponseEntity<>(bookList, HttpStatus.OK); + } + + //AWS RDS에 Book 객체를 저장하는 PostMapping +// @PostMapping("/upload") +// public String updateDB(BookForm form){ +// Book book = new Book(); +// book.setName(form.getName()); +// book.setReason(form.getReason()); +// rdsService.saveBook(book); +// return "redirect:/"; +// } + @PostMapping("/upload") + public ResponseEntity updateDB(BookForm form){ + Book book = new Book(); + book.setName(form.getName()); + book.setReason(form.getReason()); + rdsService.saveBook(book); + return new ResponseEntity<>(HttpStatus.CREATED); + + } + +} diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/RdsRepository.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/RdsRepository.java" new file mode 100644 index 0000000..b675bef --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/RdsRepository.java" @@ -0,0 +1,24 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Repository; + +import ServerStudy6Cloud.ServerStudy6Cloud.Domain.Book; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class RdsRepository { + private final EntityManager em; + //DB에 새로운 책 저장하는 메서드 + public void save(Book book){ + em.persist(book); // book 객체 저장 + } + + //DB에서 모든 책 리스트 가져오는 메서드 + public List findAll(){ + return em.createQuery("select b from Book b", Book.class) //JPQL 쿼리와 조회할 class + .getResultList(); + } +} diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/RdsService.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/RdsService.java" new file mode 100644 index 0000000..41fee18 --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/RdsService.java" @@ -0,0 +1,28 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Service; + +import ServerStudy6Cloud.ServerStudy6Cloud.Domain.Book; +import ServerStudy6Cloud.ServerStudy6Cloud.Repository.RdsRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor //final이 있는 argument의 생성자를 자동으로 만들어준다. +public class RdsService { + //RdsRepository를 사용해 DB에 저장하는 로직 + private final RdsRepository rdsRepository; + + @Transactional(readOnly = true) //위의 transactional을 override + public List findBooks(){ + return rdsRepository.findAll(); + } + + public Long saveBook(Book book){ + rdsRepository.save(book); + return book.getId(); //값이 저장되었는지 확인하는 용도 + } + +} diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/applicaion.yml" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/applicaion.yml" new file mode 100644 index 0000000..6a7c45c --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/applicaion.yml" @@ -0,0 +1,13 @@ +# RDS +spring: + datasource: + url: jdbc:mysql://gdsc-rds.cbsa892orvdn.ap-northeast-2.rds.amazonaws.com:3306/study6 + username: admin + password: [password] + driver-class-name: com.mysql.cj.jdbc.Driver + # hibernate + jpa: + show-sql: true + hibernate: + ddl-auto: update + dialect: org.hibernate.dialect.MySQL8Dialect diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220 Server S-Day \352\263\274\354\240\234/BookForm.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220 Server S-Day \352\263\274\354\240\234/BookForm.java" new file mode 100644 index 0000000..e9eb5fb --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220 Server S-Day \352\263\274\354\240\234/BookForm.java" @@ -0,0 +1,11 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Controller; + +import lombok.Getter; +import lombok.Setter; + +@Getter @Setter +public class BookForm { + // 이름, 책 좋아하는 이유 + private String name; + private String reason; +} \ No newline at end of file diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220 Server S-Day \352\263\274\354\240\234/RdsController.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220 Server S-Day \352\263\274\354\240\234/RdsController.java" new file mode 100644 index 0000000..c3c37fe --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220 Server S-Day \352\263\274\354\240\234/RdsController.java" @@ -0,0 +1,39 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Controller; + +import ServerStudy6Cloud.ServerStudy6Cloud.Domain.Book; +import ServerStudy6Cloud.ServerStudy6Cloud.Service.RdsService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class RdsController { + private final RdsService rdsService; + + // AWS RDS에서 Book list를 가져오는 GetMapping + @GetMapping("/") + public ResponseEntity> readDB() { + List bookList = rdsService.findBooks(); + + return new ResponseEntity<>(bookList, HttpStatus.OK); + } + + // AWS RDS에 Book 객체를 저장하는 PostMapping + @PostMapping("/upload") + public ResponseEntity updateDB(BookForm form) { + Book book = new Book(); + book.setName(form.getName()); + book.setReason(form.getReason()); + + rdsService.saveBook(book); + + return new ResponseEntity<>(HttpStatus.CREATED); + } +} diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220 Server S-Day \352\263\274\354\240\234/RdsRepository.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220 Server S-Day \352\263\274\354\240\234/RdsRepository.java" new file mode 100644 index 0000000..e45fd8d --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220 Server S-Day \352\263\274\354\240\234/RdsRepository.java" @@ -0,0 +1,25 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Repository; + +import ServerStudy6Cloud.ServerStudy6Cloud.Domain.Book; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class RdsRepository { + private final EntityManager em; + + // DB에 새로운 책 저장하는 메서드 + public void save(Book book) { + em.persist(book); // book 객체 저장 + } + + // DB에서 모든 책 리스트 가져오는 메서드 + public List findAll() { + return em.createQuery("select b from Book b", Book.class) // JPQL 쿼리 & 조회할 class + .getResultList(); + } +} diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220 Server S-Day \352\263\274\354\240\234/RdsService.java" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220 Server S-Day \352\263\274\354\240\234/RdsService.java" new file mode 100644 index 0000000..1f0eb9d --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220 Server S-Day \352\263\274\354\240\234/RdsService.java" @@ -0,0 +1,29 @@ +package ServerStudy6Cloud.ServerStudy6Cloud.Service; + +import ServerStudy6Cloud.ServerStudy6Cloud.Domain.Book; +import ServerStudy6Cloud.ServerStudy6Cloud.Repository.RdsRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class RdsService { + // RdsRepository를 사용해 DB에 저장하는 로직 + private final RdsRepository rdsRepository; + + @Transactional(readOnly = true) // 데이터 조회 + public List findBooks() { + return rdsRepository.findAll(); + } + + public Long saveBook(Book book) { + rdsRepository.save(book); + return book.getId(); // 값 저장되었는지 확인 용도 + } + + +} diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220 Server S-Day \352\263\274\354\240\234/application.yml" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220 Server S-Day \352\263\274\354\240\234/application.yml" new file mode 100644 index 0000000..52913a4 --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\355\225\264\354\233\220 Server S-Day \352\263\274\354\240\234/application.yml" @@ -0,0 +1,13 @@ +# RDS +spring: + datasource: + url: jdbc:mysql://gdsc-rds.c6tjoxms8dv5.ap-northeast-2.rds.amazonaws.com:3306/study6 + username: admin + password: [password] + driver-class-name: com.mysql.cj.jdbc.Driver + # hibernate + jpa: + show-sql: true + hibernate: + ddl-auto: update + dialect: org.hibernate.dialect.MySQL8Dialect \ No newline at end of file diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_6\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_6\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..527a992 --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_6\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,8 @@ +## 📖6주차 과제 +좋아하는 책의 이름, 좋아하는 이유 입력 받고 DB에 저장하기 및 데이터 불러오기 +1. Amazon RDS 환경 구축 +2. 강의 영상 대로 코드 작성하기 +3. postman을 이용해 API 테스트 + +## ✌️과제 인증 +https://drive.google.com/file/d/1yfBd62XxBBkL7_C4YVLGPu3woH7yTbZY/view?usp=sharing diff --git "a/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\231\251\354\261\204\354\233\220_6\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\231\251\354\261\204\354\233\220_6\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..4c09618 --- /dev/null +++ "b/6\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\231\251\354\261\204\354\233\220_6\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,33 @@ +## 6주차 과제 + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/c1f8d221-6595-4483-a1c2-2836b7ddaf2f) + +`Caused by: com.mysql.cj.exceptions.CJCommunicationsException: Communications link failure` 이라는 오류가 계속 발생했다. +main을 실행하면 아래 사진과 같이 `application.yml` 파일이 리셋된다. + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/377ca9d8-cd72-4379-bdd5-0c68ce27c55b) + + + +**사용한 VPC** + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/565eab2b-dc54-478e-bdc4-f32349dc8406) + +**사용한 라우팅 테이블** + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/f5d8a420-e408-49b2-94a4-945e5a42d459) + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/a402f4d6-6d2b-4eae-8bc8-ceaa1dfead6b) + +**보안그룹 설정** + +인바운드 규칙 설정 + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/3deda922-c8e0-4951-bc30-cce1e7cab9f0) + +아웃바운드 규칙 설정 + +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/90598552/4261324e-df98-4151-aecd-760336c35004) + + + diff --git "a/7\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\354\204\234\355\230\204_7\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/7\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\354\204\234\355\230\204_7\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..cef5206 --- /dev/null +++ "b/7\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\354\204\234\355\230\204_7\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,4 @@ +## 과제 Article + +* 개발 블로그(티스토리) 링크 +https://hereishyun.tistory.com/105 \ No newline at end of file diff --git "a/7\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_7\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/7\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_7\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..6e316b4 --- /dev/null +++ "b/7\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_7\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,66 @@ +# 7주차 과제 Article + +## 📝강의 정리 +### ✨컨테이너 +- 어떤 환경에서 실행하기 위해 필요한 모든 요소를 포함하는 소프트웨어 패키지 +- 코드와 그에 필요한 모든 종속성을 패키징하여 응용 프로그램이 한 컴퓨팅 환경에서 빠르고 신뢰성 있게 다른 환경으로 실행될 수 있도록 함 -> 애플리케이션을 환경에 구애받지 않고 실행하는 기술! + + +### ✨도커 +- 컨테이너를 관리하기 위한 기술 + +### ✨도커 vs VM + - VM은 Host OS위에 하이퍼바이저가 올라가고 그 위에 Guest OS가 올라가는 구조. + - 하지만 Docker는 Host OS 위에 바로 어플리케이션을 패키징한 컨테이너를 올림 -> VM에 비해 종속성 격리가 간편하고 오버헤드가 적음 + - 도커는 각 컨테이너는 격리된 실행 환경을 제공 + - 도커는 호스트의 리눅스 커널을 공유 (도커가 리눅스 기술 기반이기 때문) + +### ✨도커는 하나의 프로세스다 + - 도커 컨테이너는 프로세스 ID를 격리하는 PID 네임스페이스에 의해 호스트 시스템(리눅스)가 보기에는 하나의 프로세스처럼 보임 + - 도커 컨테이너가 보기에는 하나의 가상머신처럼 관리된다 +
+ -> 도커는 가상머신 보다는 훨씬 더 가벼우면서도 어플리케이션을 위한 독립된 환경을 제공해 줄 수 있음 + +### ✨Docker image +- 소스 코드, 라이브러리, 종속성, 도구 및 응용 프로그램을 실행하는데 필요한 기타 파일을 포함하는 변경이 불가능한 파일(템플릿) +- 도커 컨테이너를 생성하기 위한 모든 파일과 설정을 가지고 있음 -> 도커 컨테이너의 설계도! + + +### ✨Dockerfile +- 도커 이미지를 정의한 파일 +- 컨테이너 내부에 설치할 소프트웨어, 설정값, 실행 명령 등을 명시하는 스크립트 형태의 파일 + +Dockerfile을 작성 후 빌드 -> 도커 이미지 생성 -> 이미지를 사용하여 도커 컨테이너 실행 + +### ✨쿠버네티스(Kubernetes, K8s) +- 컨테이너로 이루어진 워크로드를 자동화하거나 관리하기 위한 기술 +- 쿠버네티스는 컨테이너 그 자체를 다루진 않고 서로 밀접하게 연관된 컨테이너들의 집합인 Pod를 관리함 (컨테이너 관리 기술은 도커) + +### ✨Pod +- 하나의 포드 내의 모든 컨테이너는 네트워킹과 스토리지를 공유함 -> ip주소, 네크워크 포트, 네트워크 네임스페이스도 공유 + +### ✨쿠버네티스의 아키텍쳐 +- Master Node(=컨트롤 플레인): 어떤 컨테이너를 실행할지, 얼만큼의 컨테이너를 실행할지 결정 +- Worker Node: 각자 컨테이너를 가지고 있음 + +### ✨GKE 클러스터 (Google Kubernetes Engine(GKE)) +- 클러스터는 1개 이상의 클러스터 master 머신과 여러 worker 머신으로 구성됨. 그리고 각 머신들은 VM 인스턴스로 구현되어야함 +
+ -> GKE 클러스터를 사용하면 자동으로 해당 작업이 완료됨. +- 사용자는 kubectl 명령어를 통해 컨트롤 플레인에 접근 가능함 + +## ✌️과제 인증 +1. Dockerfile로 Node 서버 만들기 + ![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/67634926/dad1f423-0c6b-4d6f-aa7f-04139f339ced) + +
+ +2. GKE 클러스터 생성 후 클러스터에 애플리케이션 배포 + ![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/67634926/eb7936be-5d82-4357-b3ca-11b07dfff611) + + +### 🚨문제상황 +애플리케이션 배포 생성, 서비스 노출, 서비스 확인 명령어 실행 후 사진과 같은 경고 문구 발생 +하지만 애플리케이션은 정상적으로 동작하였다... +![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/67634926/68639b7f-48be-4e55-9685-341202cb2a04) + diff --git "a/8\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\354\204\234\355\230\204_8\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/8\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\354\204\234\355\230\204_8\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..c045afa --- /dev/null +++ "b/8\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\354\235\264\354\204\234\355\230\204_8\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,4 @@ +## 과제 Article + +* 개발 블로그(티스토리) 링크 +https://hereishyun.tistory.com/106 \ No newline at end of file diff --git "a/8\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_8\354\243\274\354\260\250_\352\263\274\354\240\234.md" "b/8\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_8\354\243\274\354\260\250_\352\263\274\354\240\234.md" new file mode 100644 index 0000000..3ee8212 --- /dev/null +++ "b/8\354\243\274\354\260\250 Server S-Day \352\263\274\354\240\234/\355\225\230\354\234\244\354\247\200_8\354\243\274\354\260\250_\352\263\274\354\240\234.md" @@ -0,0 +1,53 @@ +# 8주차 과제 Article + +## 📝강의 정리 +### ✨Service +- Pod를 위한 영구적인 엔드포인트 +- private IP(=cluster IP) 제공: 클러스터 내부에서만 사용됨. pod 내에서는 접근 가능 +- external IP 제공: 외부에서 접근 가능 +- 여러 pod로 백엔드 서버를 띄우고 이를 service가 제공하는 LoadBalancer로 연결한 다음 LoadBalancer의 IP로 접근하는 것이 가능함 + +### ✨쿠버네티스의 객체 +- 스펙(spec)과 상태(status) +1. Spec: 원하는 상태 +2. Status: 현재 상태 + +쿠버네티스의 control plane은 spec과 status를 계속 비교한다. 필요한 경우 status를 수정함으로써 spec과 status를 일치시키려고 한다! + +### ✨배포(Deployment) +- 내용은 같고 이름만 다른 pod 여러개를 만들고 싶은 경우, 배포를 사용하면 파드 관리가 편해진다! +- 배포 파일에 실행하고 유지할 pod의 수와 각 pod의 스펙을 정의한다 +- pod를 삭제하기 위해서는 배포를 수정해야함 -> 원하는 상태 자체를 변경해야한다! + - 배포를 통해 만든 선언적 명령을 만족시키기 위해 계속 원상복구 시켜두기 때문 + +### ✨배포 업데이트 방식 +1. 순차적(Rolling) 업데이트
+ a. 배포가 업데이트 되면 새로운 ReplicaSet이 생성됨
+ b. 이전 ReplicaSet의 복제본은 서서히 감소 (기존 pod들이 하나씩 삭제됨)
+ c. 새 ReplicaSet의 복제본은 서서히 증가 (새로운 pod들이 하나씩 늘어남)
+ - 장점: 최소한의 downtime(중단시간)
+ - 단점: 업데이트 시간이 짧진 않음
+ - 쿠버네티스는 롤백을 새로운 리비전으로 처리함. 롤백된 배포의 이전 리비전은 표시하지 않음 + +2. 카나리아 업데이트
+ a. 카나리아 배포용 yaml파일 작성 후 apply하여 파드 생성
+ b. 기존 파드와 카나리아 파드 모두를 다루도록 service 변경
+ - 일부 사용자에게만 신버전을 업데이트하는 방식
+ - 카나리아 배포를 통해 신버전이 정상적으로 동작하는 것을 확인하면 기존 카나리아 배포를 삭제하고 rolling update 함 + +3. 블루/그린 업데이트
+ a. 구버전(blue)와 동일한 신버전(green)을 구축
+ b. 구 버전을 가리켰던 서비스가 한번에 신버전을 가리키도록 업데이트
+ - 장점: 신버전을 배포하기 전 동일한 리소스를 사용해서 프로덕션 환경을 구축한 다음 테스트를 진행할 수 있다 + - 단점: 일시적으로 시스템 자원이 2배로 필요함 + + +## ✌️과제 인증 +실습 진행 과정: yaml 파일 생성 -> apply 명령어로 파드/서비스 만들기 + +1. service의 외부 IP를 이용해 접속 + ![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/67634926/501175d7-6641-404f-bc68-4aed1a7c8363) + +2. 카나리아 배포 실습 + ![image](https://github.com/GDSC-Ewha-5th/GDSC-Server-5th/assets/67634926/c97b8b6e-1ee7-473d-9ca9-ac0f1207445f) +