스프링을 계속 공부하던 도중, CI/CD 자동화를 겨우겨우 했음에도 불구하고 https 적용에 계속 실패했었다. 그러다가 후배의 도움을 통해 최종적으로 완료가 되었기에, 두고두고 활용하기 위해 벨로그 글로 남겨두려고 한다. (아마 가장 최신판이지 않을까 싶다)
최종 구조는 다음과 같다.
프로젝트에 쓰인 환경은 다음과 같다.
우선 데이터베이스도 사용할 것이기 때문에, AWS RDS를 생성하도록 하자.
RDS에 대한 보안 그룹이 필요하다. 보안 그룹은 다음과 같이 생성한다. (EC2 > 네트워크 및 보안 > 보안 그룹 > 보안 그룹 생성
)
MySQL 8.0.33
)db.t3.micro
)스토리지 자동 조정 활성화: 비활성화
(활성화 할 경우 과금의 원인)생성 후 RDS의 엔드포인트를 확인할 수 있다.
이제 CI/CD를 적용할 프로젝트를 만들어보자.
간단하게 멤버를 등록하고, 멤버를 조회할 수 있는 프로젝트를 만들었다.
작성한 스프링부트 환경은 아래 사진과 같다.
.gitignore
에 application.yml
과 .DS_Store
(맥 환경이기 때문에)를 추가로 붙였다.
### MAC ###
.DS_Store
### YAML ###
*.yml
기본적으로 생성된 application.properties
를 application.yml
로 바꾸고, 다음과 같이 작성하였다.
spring:
datasource:
url: jdbc:mysql://{RDS 엔드포인트}:{RDS 포트}/{DB 이름}?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
username: {관리자 이름}
password: {관리자 비번}
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: create # 처음에는 DDL이 만들어져야 하므로 create
맥 기준으로 터미널에서 RDS에 접속하는 방법은 아래 코드를 입력하면 된다.
mysql -u {관리자 이름} -p -h {RDS 엔드포인트}
Enter password: {비밀번호 입력}
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 16
Server version: 8.0.33 Source distribution
Copyright (c) 2000, 2023, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> create database {데이터베이스 이름};
Query OK, 1 row affected (0.01 sec)
임시로 작성해 본 코드이기 때문에 효율적이라고 할 수는 없을 것 같습니다. 이 점 참고 바랍니다.
package devholic.devops;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private Long id;
private String name;
public static Member from(String name) {
return new Member(name);
}
private Member (String name) {
this.name = name;
}
}
package devholic.devops;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/")
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
@GetMapping("/")
public String hello() {
return "hello";
}
@GetMapping("/members/{id}")
public ResponseEntity<?> find(@PathVariable Long id) {
try {
return ResponseEntity.ok(memberService.find(id));
} catch (IllegalStateException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
@PostMapping("/members")
public ResponseEntity<MemberResponse> create(@RequestBody MemberRequest request) {
return ResponseEntity.ok(memberService.create(request));
}
}
package devholic.devops;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MemberRepository extends JpaRepository<Member, Long> {
}
package devholic.devops;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
public MemberResponse create(MemberRequest request) {
Member newMember = Member.from(request.getName());
memberRepository.save(newMember);
return MemberResponse.from(newMember.getId(), newMember.getName());
}
public MemberResponse find(Long id) {
Member findMember = memberRepository.findById(id)
.orElseThrow(() -> new IllegalStateException("멤버가 없습니다."));
return MemberResponse.from(findMember.getId(), findMember.getName());
}
}
package devholic.devops;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
public class MemberRequest {
private String name;
}
package devholic.devops;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class MemberResponse {
private Long id;
private String name;
public static MemberResponse from(Long id, String name) {
return new MemberResponse(id, name);
}
private MemberResponse (Long id, String name) {
this.id = id;
this.name = name;
}
}
이제 다음 편에서는 Docker 연결, Elasticbeanstalk 설정 등 CI/CD에 관한 작업을 해 보겠다.
부족하거나 보완할 점이 있다면 댓글 부탁드립니다 😃