[DEV] 7주차. AWS 클라우드(4)
종합실습
- 구성도
1. 백엔드 모듈 구성 (SpringBoot)
- build.gradle.kts
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web:2.6.3")
testImplementation(platform("org.junit:junit-bom:5.9.1"))
testImplementation("org.junit.jupiter:junit-jupiter")
implementation("io.springfox:springfox-swagger2:2.9.2")
implementation("io.springfox:springfox-swagger-ui:2.9.2")
}
- CitronServiceApplication.java
package com.citron.cit;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CitronServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CitronServiceApplication.class, args);
}
}
- config/SwaggerConfig.java
package com.citron.cit.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.citron.cit"))
.paths(PathSelectors.any())
.build();
}
public ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Citron project")
.version("1.8.0")
.description("Citron Project swagger api")
.build();
}
}
- controller/MemberService.java
package com.citron.cit.controller;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MemberService {
@GetMapping("/members")
@ApiOperation("회원조회")
public void get() {
}
@PostMapping("/members")
@ApiOperation("회원등록")
public void register() {
}
@DeleteMapping("/members")
@ApiOperation("회원삭제")
public void delect() {
}
}
서버 실행 확인
beanstalk에서도 이렇게 뜨는지 확인할 예정!
github 리포지토리와 연동
2. VPC 구성
NAT 게이트웨이 생성!
3. beanstalk 생성
환경 생성
vpc는 꼭 이 단계에서 설정해주어야 함!
private 선택
EC2 인스턴스 자동 생성
4. baston host 역할의 EC2 생성
public 영역 선택 & 퍼블릭 IP 자동 할당 활성화
bastonhost 인스턴스 연결
- 다운받은 키페어를 ~/.ssh 로 이동
- 터미널에서 ~/.ssh 로 이동
ssh -i bk.pem ec2-user@[public IP 주소]
Permissions 0644 for 'key.pem' are too open.
오류
chmod 400 key.pem
명령어로 권한 변경!
5. citron-application-env 연결
- private 서브넷에 연결했기 때문에 바로는 접속할 수 없음
- public에 있는 bastonhost를 통해서 private에 있는 beanstalk 연결!
bastonhost에 접속하여 키 복사
ssh -i bk.pem ec2-user@[public IP 주소]
# 루트로 접속
sudo su -
cd /home/ec2-user
cd .ssh
vi bk.pem
# 복사한 키 붙여넣기
chmod 400 bk.pem
beanstalk에서 키 설정
- [beanstalk] - [Citron-application-env] - [구성] - [서비스 액세스 구성] - [EC2 키 페어] 등록
ssh 연결
- 프라이빗 IPv4 로 연결!!
- bastonhost 안에서 연결!!
# 위에서 키를 생성한 곳에서 실행
# /home/ec2-user/.ssh
# ls 로 bk.pem 있는지 확인하고
ssh -i bk.pem ec2-user@[private IP 주소]
연결 확인
- elastic beanstalk은 기본적으로 5000번 포트 사용
- 앞단에 nginx 80 포트가 5000번으로 연결
netstat -an | grep 5000
# tcp6 0 0 :::5000 :::* LISTEN
로그
/var/log
에서 확인 가능- beanstalk 페이지 - [로그] 에서도 요청 가능
6. RDS 연결
DB 서브넷 그룹 생성
private 서브넷 선택
데이터베이스 생성
MySQL 8.0.35 프리티어 버전
보안그룹 - 인바운드 규칙에 3306 포트가 있어야 함! 없으면 추가!
데이터베이스 연결
- IntelliJ에서 beanstalk 애플리케이션 - 데이터베이스 연결
host: DB 엔드포인트
- private에 연결했기 때문에 중간에 터널링 필요
- [SSH/SSL] - [SSH 터널 사용] 체크 - [ssh 구성] 추가
- bastonhost public IP 주소를 host로
연결 성공
intelliJ 내에서 스키마, 테이블, 열 등등 생성 가능
7. S3 정적 웹 호스팅
S3 버킷 생성
버킷 - 속성 - 정적 웹 호스팅 활성화
기본적인 웹 생성
# npx 설치
npm install npx -g
# 앱 생성
npx create-react-app citron-app
citron-app 폴더가 생성됨! open 하기
- 기본적인 리액트 페이지 생성됨
npm start
- 빌드
npm run build
- 생성된 build 폴더 내의
index.html
파일을 S3에 올려주면 됨!
serve
로 실행
# serve 설치
npm install -g serve
# 실행
serve -s build
- build 된 것을 띄우는 것!
S3에 업로드
- 프로젝트 내 build 폴더 내의 모든 파일을 객체에 업로드!
권한 수정
-
버킷 - [권한] - [퍼블릭 액세스 차단] - [비활성] 으로 수정
-
정책 추가
웹 확인
[속성] - [정적 웹 사이트 호스팅] - [엔드포인트] 링크로 확인
S3 정적 웹호스팅 Access Denied 오류
버킷 정책에서 resources를 "Resource": "arn:aws:s3:::citron124.com"
에서 ` “Resource”: “arn:aws:s3:::citron124.com/*”` 으로 변경!
8. CI/CD
- CodePipeline으로 Spring Boot 배포
github 연동
code build
- build 명령 삽입
build:
commands:
- ./gradlew build
artifacts:
files:
- "**/*"
code deploy
-
public에 있다면 beanstalk으로 바로 배포가 가능하지만, private에 만들었기 때문에 Code Deploy로 ELB를 묶어서 배포할 것
- 환경 구성 -
Amazon EC2 Auto Scaling 그룹
선택 - 로드밸런서 선택
- Beanstalk에 대한 ELB 생성 후 선택
- ELB를 통해 private 서브넷 안에 있는 인스턴스와 연결하여 배포
codedeploy-agent 설치
-
참고하여 설치!
-
설치하면
/var/log/aws/codedeploy-agent
에서 로그 파일 확인할 수 있음
DownloadBundle 단계에서 AccessDenied 오류
- IAM 사용자에 S3FullAccess 권한 부여!
BeforeInstall 단계에서 오류
CodeDeploy agent was not able to receive the lifecycle event. Check the CodeDeploy agent logs on your host and make sure the agent is running and can connect to the CodeDeploy server.
오류 발생/var/log/aws/codedeploy-agent
에서 codedeploy-agent.log 를 확인했더니 Appspec.yml 파일에서 version을 0.2 -> 0.0으로 수정하라고 나와있음- 수정하여 푸시했더니 해결!
AllowTraffic 단계에서 시간이 너무 오래걸리는 오류
- code deploy의 보안 그룹의 인바운드 규칙에서 HTTP:80 을 허용하지 않고 있었음
유형: HTTP, 포트는 자동으로 80, 모든 IPv4에 대해 허용
으로 규칙 생성하여 해결!
code pipeline
8. Route53
- Route53에서 도메인 구입하여 등록 가능
- 외부 업체에서 구매했다면 [DNS 관리] - [호스팅 영역 생성]
CloudFront
- CDN을 이용하는 경우
Route53
- S3를 이용하는 경우
- 구매한 도메인이 S3 엔드포인트에 연결됨!