Git Product home page Git Product logo

hotel's Introduction

호텔 예약

Winter School 2팀 Workspace입니다.

Table of contents

시나리오

호텔 예약 시스템에서 요구하는 기능/비기능 요구사항은 다음과 같습니다. 사용자가 예약과 함께 결제를 진행하고 나면 객실 관리자가 객실을 배정하는 시스템입니다. 이 과정에 대해서 고객은 진행 상황을 확인할 수 있고, 카카오톡으로 알림을 받을 수 있습니다.

기능적 요구사항

  1. 고객이 원하는 객실을 선택 하여 예약한다.
  2. 고객이 결제 한다.
  3. 예약이 신청 되면 예약 신청 내역이 호텔에 전달 된다.
  4. 호텔이 확인 하여 예약을 확정 한다.
  5. 고객이 예약 신청을 취소할 수 있다.
  6. 예약이 취소 되면 호텔 예약이 취소 된다.
  7. 고객이 예약 진행 상황을 중간 중간 조회 한다.
  8. 예약 상태가 바뀔 때 마다 카카오톡으로 알림을 보낸다.
  9. 고객이 예약 취소를 하면 예약 정보는 삭제 상태로 갱신 된다.

비 기능적 요구사항

  1. 트랜잭션

    • 결제가 되지 않은 예약건은 아예 호텔 예약 신청이 되지 않아야 한다. Sync 호출
  2. 장애격리

    • 객실관리 기능이 수행 되지 않더라도 예약은 365일 24시간 받을 수 있어야 한다. Pub/Sub
    • 결제 시스템이 과중되면 사용자를 잠시동안 받지 않고 결제를 잠시후에 하도록 유도 한다. (장애처리)
  3. 성능

    • 고객이 예약 확인 상태를 마이페이지에서 확인할 수 있어야 한다. CQRS
    • 예약 상태가 바뀔 때 마다 카톡 등으로 알림을 줄 수 있어야 한다.

분석 설계

Event Storming

초기 버전

  1. 잘못된 이벤트 제거 : "호텔선택됨", "룸타입선택됨" 등 UI 이벤트 제거 pp의 Order, store 의 주문처리, 결제의 결제이력은 그와 연결된 command 와 event 들에 의하여 트랜잭션이 유지되어야 하는 단위로 그들 끼리 묶어줌
  2. order의 주문, reservation의 예약과 취소, payment의 결제 이력, 고객관리의 카카오톡 알림 등은 그와 연결된 command 와 event 들에 의하여 트랜잭션이 유지되어야 하는 단위로 그들 끼리 묶어줌(바운디드 컨텍스트)
  3. 도메인 서열 분리
    • Core Domain: Order, Reservation(없어서는 안될 핵심 서비스이며, 연견 Up-time SLA 수준을 99.7% 목표, 배포주기는 1주일 1회 미만)
    • Supporting Domain: customermanagement(경쟁력을 내기위한 서비스이며, SLA 수준은 연간 60% 이상 uptime 목표, 배포주기는 각 팀의 자율)
    • General Domain: payment(결제서비스로 3rd Party 외부 서비스를 사용하는 것이 경쟁력이 높을 것으로 판단. 향후 External로 전환 필요)

screenshot-miro com-2020 12 18-09_30_35

2020.12.21

  • 1차 완성본

screenshot-miro com-2020 12 21-13_50_26

2020.12.22

  • 예약 취소 건에 대한 추가 의견으로, 예약 취소는 이력 정보만 남기고 실제 예약 정보는 삭제하는 것으로 합의
  • 예약 취소 시, 예약 취소에 대한 이력만을 저장하기 때문에 이 이력의 저장에 대한 동기 호출로 결제 취소를 하지 않고, 비동기 호출로 전환.

screenshot-miro com-2020 12 22-10_19_41

2021.01.22

  • 오더 속성 변경(호텔 서비스인 것을 좀 더 명확하게 드러내려고 호텔아이디와 룸타입으로 변경)
  • 호텔이 확인 하여 예약을 확정한다는 기능 요구사항을 표현 하기 위해 액터(호텔리어), 커맨드(컨펌) 추가. 뷰는 기존의 리저베이션 디테일 위치 이동.

miro_20210122

2021.01.25

  • MSAEZ 툴에서 이벤트스토밍 작업
  • 사용자 입장에서 꼭 필요할 때만 카카오톡 알림을 받을 수 있도록 카카오 알림 이벤트 발생 최소화

hotel_msaez_20210125

2021.02.22

  • MSAEZ 툴에서 이벤트스토밍 작업
  • 마이페이지를 customer로

team2hotel

기능 요구사항을 커버하는지 검증

  1. 고객이 원하는 객실을 선택 하여 예약한다.(O)
  2. 고객이 결제 한다.(O)
  3. 예약이 신청 되면 예약 신청 내역이 호텔에 전달 된다.(O)
  4. 호텔이 확인 하여 예약을 확정 한다.(O)
  5. 고객이 예약 신청을 취소할 수 있다.(O)
  6. 예약이 취소 되면 호텔 예약이 취소 된다.(O)
  7. 고객이 예약 진행 상황을 중간 중간 조회 한다.(O)
  8. 예약 상태가 바뀔 때 마다 카카오톡으로 알림을 보낸다.(O)
  9. 고객이 예약 취소를 하면 예약 정보는 삭제 상태로 갱신 된다.(O)

비기능 요구사항을 커버하는지 검증

  1. 트랜잭션

    • 결제가 되지 않은 예약건은 아예 호텔 예약 신청이 되지 않아야 한다. Sync 호출(O)
    • Request-Response 방식 처리
  2. 장애격리

    • 객실관리 기능이 수행 되지 않더라도 예약은 365일 24시간 받을 수 있어야 한다.(O)
    • Eventual Consistency 방식으로 트랜잭션 처리(Pub/Sub)

헥사고날 아키텍처 다이어그램 도출

  • 비지니스 로직은 내부에 순수한 형태로 구현
  • 그 이외의 것을 어댑터 형식으로 설계 하여 해당 비지니스 로직이 어느 환경에서도 잘 도작하도록 설계

event_stream

구현

분석/설계 단계에서 도출된 헥사고날 아키텍처에 따라, 각 BC별로 대변되는 마이크로 서비스들을 스프링부트로 구현하였다. 구현한 각 서비스를 로컬에서 실행하는 방법은 아래와 같다 (각자의 포트넘버는 8081 ~ 8084 이다)

cd /Users/imdongbin/Documents/study/MSA/hotel/app
mvn spring-boot:run

cd /Users/imdongbin/Documents/study/MSA/hotel/hotel
mvn spring-boot:run 

cd /Users/imdongbin/Documents/study/MSA/hotel/pay
mvn spring-boot:run  

cd /Users/imdongbin/Documents/study/MSA/hotel/customer
mvn spring-boot:run 

DDD 의 적용

  • 각 서비스내에 도출된 핵심 Aggregate Root 객체를 Entity 로 선언하였다: (예시는 pay 마이크로 서비스). 이때 가능한 현업에서 사용하는 언어 (유비쿼터스 랭귀지)를 그대로 사용하려고 노력했다. 예를 들어 price, payMethod 등)
package hotel;

import javax.persistence.*;
import org.springframework.beans.BeanUtils;
import java.util.List;

@Entity
@Table(name="Payment_table")
public class Payment {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    private Long orderId;
    private String status;
    private Integer price;
    private String payMethod;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
    public Long getOrderId() {
        return orderId;
    }

    public void setOrderId(Long orderId) {
        this.orderId = orderId;
    }
    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }
    public String getPayMethod() {
        return payMethod;
    }

    public void setPayMethod(String payMethod) {
        this.payMethod = payMethod;
    }

}
  • Entity Pattern 과 Repository Pattern 을 적용하여 JPA 를 통하여 다양한 데이터소스 유형 (RDB or NoSQL) 에 대한 별도의 처리가 없도록 데이터 접근 어댑터를 자동 생성하기 위하여 Spring Data REST 의 RestRepository 를 적용하였다
package hotel;

import org.springframework.data.repository.PagingAndSortingRepository;

public interface PaymentRepository extends PagingAndSortingRepository<Payment, Long>{

}
  • 적용 후 REST API 의 테스트
# app 서비스의 주문처리
http localhost:8081/orders hotelId=4001 roomType=delux

# pay 서비스의 결제처리
http localhost:8083/payments orderId=3 payMethod=card price=100000

# hotel 서비스의 예약처리
http localhost:8082/reservations orderId=3 status="confirmed"

# 주문 상태 확인
http localhost:8081/orders/3

HTTP/1.1 200 
Content-Type: application/hal+json;charset=UTF-8
Date: Tue, 23 Feb 2021 23:56:54 GMT
Transfer-Encoding: chunked

{
    "_links": {
        "order": {
            "href": "http://localhost:8081/orders/3"
        },
        "self": {
            "href": "http://localhost:8081/orders/3"
        }
    },
    "hotelId": "4001",
    "roomType": "delux",
    "status": "confirmed"
}

폴리글랏 퍼시스턴스

폴리그랏 퍼시스턴스 요건을 만족하기 위해 기존 h2를 hsqldb로 변경 https://www.baeldung.com/spring-boot-hsqldb 참고

<!--		<dependency>-->
<!--			<groupId>com.h2database</groupId>-->
<!--			<artifactId>h2</artifactId>-->
<!--			<scope>runtime</scope>-->
<!--		</dependency>-->

		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<version>2.4.0</version>
			<scope>runtime</scope>
		</dependency>

# 변경/재기동 후 예약 주문
http localhost:8081/orders hotelId=2001 roomType=standard

HTTP/1.1 201 
Content-Type: application/json;charset=UTF-8
Date: Mon, 22 Feb 2021 06:11:15 GMT
Location: http://localhost:8081/orders/1
Transfer-Encoding: chunked

{
    "_links": {
        "order": {
            "href": "http://localhost:8081/orders/1"
        },
        "self": {
            "href": "http://localhost:8081/orders/1"
        }
    },
    "hotelId": "2001",
    "roomType": "standard",
    "status": null
}

# 저장이 잘 되었는지 조회
http localhost:8081/orders/1

HTTP/1.1 200 
Content-Type: application/hal+json;charset=UTF-8
Date: Mon, 22 Feb 2021 06:17:40 GMT
Transfer-Encoding: chunked

{
    "_links": {
        "order": {
            "href": "http://localhost:8081/orders/1"
        },
        "self": {
            "href": "http://localhost:8081/orders/1"
        }
    },
    "hotelId": "2001",
    "roomType": "standard",
    "status": null
}

마이페이지

고객의 예약정보를 한 눈에 볼 수 있게 mypage를 구현 한다.(CQRS)

# mypage 호출 
http localhost:8081/mypages/12

HTTP/1.1 200 
Content-Type: application/hal+json;charset=UTF-8
Date: Wed, 24 Feb 2021 00:09:57 GMT
Transfer-Encoding: chunked

{
    "_links": {
        "mypage": {
            "href": "http://localhost:8081/mypages/12"
        },
        "self": {
            "href": "http://localhost:8081/mypages/12"
        }
    },
    "hotelId": "3001",
    "orderId": 11,
    "payMethod": "card",
    "paymentId": null,
    "price": 100000,
    "reservationId": 2,
    "roomType": "suite",
    "status": "Confirming reservation"
}

동기식 호출 과 Fallback 처리

분석단계에서의 조건 중 하나로 주문(app)->결제(pay) 간의 호출은 동기식 일관성을 유지하는 트랜잭션으로 처리하기로 하였다. 호출 프로토콜은 이미 앞서 Rest Repository 에 의해 노출되어있는 REST 서비스를 FeignClient 를 이용하여 호출하도록 한다.

  • 결제서비스를 호출하기 위하여 Stub과 (FeignClient) 를 이용하여 Service 대행 인터페이스 (Proxy) 를 구현
# (app) PaymentService.java

package hotel.external;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.Date;

@FeignClient(name="payment", url="${api.payment.url}")
public interface PaymentService {

    @RequestMapping(method= RequestMethod.POST, path="/payments")
    public void pay(@RequestBody Payment payment);

}
  • 주문을 받은 직후(@PostPersist) 결제를 요청하도록 처리
# Order.java (Entity)

    @PostPersist
    public void onPostPersist(){
        Ordered ordered = new Ordered();
        BeanUtils.copyProperties(this, ordered);
        ordered.publishAfterCommit();

        hotel.external.Payment payment = new hotel.external.Payment();

        AppApplication.applicationContext.getBean(hotel.external.PaymentService.class)
            .pay(payment);

    }
  • 동기식 호출에서는 호출 시간에 따른 타임 커플링이 발생하며, 결제 시스템이 장애가 나면 주문도 못받는다는 것을 확인:
# 결제 (pay) 서비스를 잠시 내려놓음 (ctrl+c)

#주문처리
http localhost:8081/orders hotelId=1002 roomType=delux   

#Fail
HTTP/1.1 500 
Connection: close
Content-Type: application/json;charset=UTF-8
Date: Wed, 24 Feb 2021 00:00:18 GMT
Transfer-Encoding: chunked

{
    "error": "Internal Server Error",
    "message": "Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction",
    "path": "/orders",
    "status": 500,
    "timestamp": "2021-02-24T00:00:18.829+0000"
}

#결제서비스 재기동
cd /Users/imdongbin/Documents/study/MSA/hotel/pay
mvn spring-boot:run

#주문처리
http localhost:8081/orders hotelId=1002 roomType=delux   

#Success
HTTP/1.1 201 
Content-Type: application/json;charset=UTF-8
Date: Wed, 24 Feb 2021 00:01:10 GMT
Location: http://localhost:8081/orders/9
Transfer-Encoding: chunked

{
    "_links": {
        "order": {
            "href": "http://localhost:8081/orders/9"
        },
        "self": {
            "href": "http://localhost:8081/orders/9"
        }
    },
    "hotelId": "1002",
    "roomType": "delux",
    "status": null
}

비동기식 호출 / 시간적 디커플링 / 장애격리 / 최종 (Eventual) 일관성 테스트

결제가 이루어진 후에 호텔 시스템으로 이를 알려주는 행위는 동기식이 아니라 비 동기식으로 처리하여 호텔 시스템의 처리를 위하여 결제주문이 블로킹 되지 않아도록 처리한다.

  • 이를 위하여 결제이력에 기록을 남긴 후에 곧바로 결제승인이 되었다는 도메인 이벤트를 카프카로 송출한다(Publish)
#Payment.java

package hotel;

import javax.persistence.*;
import org.springframework.beans.BeanUtils;
import java.util.List;

@Entity
@Table(name="Payment_table")
public class Payment {

...

@PostPersist
    public void onPostPersist(){
        PayApproved payApproved = new PayApproved();
        BeanUtils.copyProperties(this, payApproved);
        payApproved.publishAfterCommit();
    }
  • 호텔 서비스에서는 결제승인 이벤트에 대해서 이를 수신하여 자신의 정책을 처리하도록 PolicyHandler 를 구현한다.
  • 카톡/이메일 등으로 호텔은 노티를 받고, 예약 상황을 확인 하고, 최종 예약 상태를 UI에 입력할테니, 우선 예약정보를 DB에 받아놓은 후, 이후 처리는 해당 Aggregate 내에서 하면 되겠다.
# PolicyHandler.java

package hotel;

import hotel.config.kafka.KafkaProcessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Service;

@Service
public class PolicyHandler{
    @StreamListener(KafkaProcessor.INPUT)
    public void onStringEventListener(@Payload String eventString){

    }

    @Autowired
    ReservationRepository reservationRepository;

    @StreamListener(KafkaProcessor.INPUT)
    public void wheneverPayApproved_(@Payload PayApproved payApproved){

        if(payApproved.isMe()){
            System.out.println("##### listener  : " + payApproved.toJson());
            // 결제 승인 되었으니 호텔에 예약 확인 하라고 카톡 알림 처리 필요
            Reservation reservation = new Reservation();
            reservation.setOrderId(payApproved.getOrderId());
            reservation.setStatus("Confirming reservation");

            reservationRepository.save(reservation);
        }
    }

}

호텔 시스템은 주문/결제와 완전히 분리되어있으며, 이벤트 수신에 따라 처리되기 때문에, 호텔 시스템이 유지보수로 인해 잠시 내려간 상태라도 예약 주문을 받는데 문제가 없어야 한다.

# 호텔 서비스 (hotel) 를 잠시 내려놓음 (ctrl+c)

# 주문처리
http localhost:8081/orders hotelId=3001 roomType=suite   #Success

# 결제처리
http localhost:8083/payments orderId=11 price=100000 payMethod=card   #Success

# 주문 상태 확인
http localhost:8081/orders/11     

# 주문상태 안바뀜 확인
HTTP/1.1 200 
Content-Type: application/hal+json;charset=UTF-8
Date: Wed, 24 Feb 2021 00:03:23 GMT
Transfer-Encoding: chunked

{
    "_links": {
        "order": {
            "href": "http://localhost:8081/orders/11"
        },
        "self": {
            "href": "http://localhost:8081/orders/11"
        }
    },
    "hotelId": "3001",
    "roomType": "suite",
    "status": null
}

# hotel 서비스 기동
cd /Users/imdongbin/Documents/study/MSA/hotel/hotel
mvn spring-boot:run

# 주문상태 확인
http localhost:8081/orders/11

# 주문 상태가 "Confirming reservation"으로 확인
HTTP/1.1 200 
Content-Type: application/hal+json;charset=UTF-8
Date: Wed, 24 Feb 2021 00:04:03 GMT
Transfer-Encoding: chunked

{
    "_links": {
        "order": {
            "href": "http://localhost:8081/orders/11"
        },
        "self": {
            "href": "http://localhost:8081/orders/11"
        }
    },
    "hotelId": "3001",
    "roomType": "suite",
    "status": "Confirming reservation"
}

API 게이트웨이(gateway)

API gateway 를 통해 MSA 진입점을 통일 시킨다.

# gateway 기동(8088 포트)
cd gateway
mvn spring-boot:run

# api gateway를 통한 3001 호텔 standard룸 예약 주문
http localhost:8088/orders hotelId=3001 roomType=standard

HTTP/1.1 201 Created
Content-Type: application/json;charset=UTF-8
Date: Mon, 22 Feb 2021 07:36:34 GMT
Location: http://localhost:8081/orders/13
transfer-encoding: chunked

{
    "_links": {
        "order": {
            "href": "http://localhost:8081/orders/13"
        },
        "self": {
            "href": "http://localhost:8081/orders/13"
        }
    },
    "hotelId": "3001",
    "roomType": "standard",
    "status": null
}
application.yml

server:
  port: 8080

---

spring:
  profiles: default
  cloud:
    gateway:
      routes:
        - id: order
          uri: http://localhost:8081
          predicates:
            - Path=/orders/** 
        - id: reservation
          uri: http://localhost:8082
          predicates:
            - Path=/reservations/**,/cancellations/** 
        - id: payment
          uri: http://localhost:8083
          predicates:
            - Path=/paymentHistories/** 
        - id: customer
          uri: http://localhost:8084
          predicates:
            - Path= /mypages/**
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins:
              - "*"
            allowedMethods:
              - "*"
            allowedHeaders:
              - "*"
            allowCredentials: true


---

spring:
  profiles: docker
  cloud:
    gateway:
      routes:
        - id: order
          uri: http://order:8080
          predicates:
            - Path=/orders/** 
        - id: reservation
          uri: http://reservation:8080
          predicates:
            - Path=/reservations/**,/cancellations/** 
        - id: payment
          uri: http://payment:8080
          predicates:
            - Path=/paymentHistories/** 
        - id: customer
          uri: http://customer:8080
          predicates:
            - Path= /mypages/**
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins:
              - "*"
            allowedMethods:
              - "*"
            allowedHeaders:
              - "*"
            allowCredentials: true
            
logging:
  level:
    root: debug

server:
  port: 8080

spring-boot-starter-security 인증을 활용한 MSA 보호

  • gateway > pom.xml 에 spring-boot-starter-security 추가
		<!-- 인증 추가-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
  • gateway 재기동 하여 콘솔 로그에서 비밀번호 확인
...
2021-02-22 18:49:49.393  INFO 56079 --- [           main] ctiveUserDetailsServiceAutoConfiguration : 

Using generated security password: 4c9b4f9b-ba47-4ad9-b5c0-9b4a12a89a39

2021-02-22 18:49:49.402  INFO 56079 --- [           main] o.s.c.g.r.RouteDefinitionRouteLocator    : Loaded RoutePredicateFactory [After]
...
  • 포스트맨을 통해 인증 없이 orders/19 상세 조회

스크린샷 2021-02-22 오후 6 55 40

  • 포스트맨에서 앞서 확인한 비밀번호 "4c9b4f9b-ba47-4ad9-b5c0-9b4a12a89a39"를 입력 하여 동일한 orders/19 상세 조회

스크린샷 2021-02-22 오후 6 57 46

SAGA CQRS 동작 결과

  1. 호텔 예약 발생 order1

  2. 예약 KAFKA 메시지 확인 order_kafka

  3. 예약 내역 Mypage 확인 order_mypage

  4. 취소 발생 cancel

  5. 취소 KAFKA 메시지 확인 cancel_kafka

  6. 취소 상태 Mypage 확인 cancel_mypage

운영

CI/CD 설정

  • 환경변수 준비
환경변수 설정 접기/펼치기 AWS_ACCOUNT_ID KUBE URL : EKS -> 클러스터 -> 구성 "세부정보"의 "API 엔드포인트 URL" CodeBuild 와 EKS 연결
1. eks-admin-service-account.yaml 파일 생성하여 sa 생성
apiVersion: v1
kind: ServiceAccount
metadata:
  name: eks-admin
  namespace: kube-system
  
2. kubectl apply -f eks-admin-service-account.yaml
혹은, 바로 적용도 가능함
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: eks-admin
  namespace: kube-system
EOF

3. eks-admin-cluster-role-binding.yaml 파일 생성하여 롤바인딩
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: eks-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: eks-admin
  namespace: kube-system
  
4. kubectl apply -f eks-admin-cluster-role-binding.yaml
혹은, 바로 적용도 가능함
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: eks-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: eks-admin
  namespace: kube-system
EOF

만들어진 eks-admin SA 의 토큰 가져오기 kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep eks-admin | awk '{print $1}') KUBE TOKEN 가져오기 :

Code build와 ECR 연결 정책 설정 : code build -> 빌드 프로젝트 생성

그리고 다시 뒷 내용은 "3. CICD-Pipeline_AWS_v2" pdf 자료 39페이지부터 (이미지가 많은 관계로, buildspec.yml 작성하기)

환경 변수

{ "Action": [
      "ecr:BatchCheckLayerAvailability",
      "ecr:CompleteLayerUpload",
      "ecr:GetAuthorizationToken",
      "ecr:InitiateLayerUpload",
      "ecr:PutImage",
      "ecr:UploadLayerPart"
    ],
    "Resource": "*",
    "Effect": "Allow"
}

Codebuild cache 적용 : CICD PDF p.45, S3 만들고 설정해야 함 buildspec.yml에 aws eks --region $AWS_DEFAULT_REGION update-kubeconfig --name $_EKS 이거 넣어줘야 하는데 권한 에러 날 경우

https://stackoverflow.com/questions/56011492/accessdeniedexception-creating-eks-cluster-user-is-not-authorized-to-perform 상세 내용은 buildspec.yml과 코드빌드의 환경변수 확인하면 됨

  • CI/CD 적용 및 빌드 성공 결과

- Buildspec.yml
version: 0.2

env: 
  variables:
    _PROJECT_NAME: "teamtwohotel2-order"
    _DIR_NAME: "order"
    _EKS: "teamtwohotel2"
    _NAMESPACE: "teamtwohotel"

phases:
  install:
    runtime-versions:
      java: openjdk8
      docker: 18
    commands:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - echo $_PROJECT_NAME
      - echo $AWS_ACCOUNT_ID
      - echo $AWS_DEFAULT_REGION
      - echo $CODEBUILD_RESOLVED_SOURCE_VERSION
      - echo start command
      - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)
  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - cd $_DIR_NAME && mvn package -Dmaven.test.skip=true
      - ls -al
      - pwd
      - docker build -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$_PROJECT_NAME:$IMAGE_TAG .
  post_build:
    commands:
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$_PROJECT_NAME:$IMAGE_TAG
      - echo connect kubectl
      - kubectl config set-cluster k8s --server="$KUBE_URL" --insecure-skip-tls-verify=true
      - kubectl config set-credentials admin --token="$KUBE_TOKEN"
      - kubectl config set-context default --cluster=k8s --user=admin
      - kubectl config use-context default
      - |
          cat <<EOF | kubectl apply -f -
          apiVersion: v1
          kind: Service
          metadata:
            name: $_DIR_NAME
            namespace: $_NAMESPACE
            labels:
              app: $_DIR_NAME
          spec:
            ports:
              - port: 8080
                targetPort: 8080
            selector:
              app: $_DIR_NAME
          EOF
      - |
          cat  <<EOF | kubectl apply -f -
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: $_DIR_NAME
            namespace: $_NAMESPACE
            labels:
              app: $_DIR_NAME
          spec:
            replicas: 1
            selector:
              matchLabels:
                app: $_DIR_NAME
            template:
              metadata:
                labels:
                  app: $_DIR_NAME
              spec:
                containers:
                  - name: $_DIR_NAME
                    image: $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$_PROJECT_NAME:$IMAGE_TAG
                    imagePullPolicy: Always
                    ports:
                      - containerPort: 8080
                    readinessProbe:
                      httpGet:
                        path: /actuator/health
                        port: 8080
                      initialDelaySeconds: 10
                      timeoutSeconds: 2
                      periodSeconds: 5
                      failureThreshold: 10
                    livenessProbe:
                      httpGet:
                        path: /actuator/health
                        port: 8080
                      initialDelaySeconds: 120
                      timeoutSeconds: 2
                      periodSeconds: 5
                      failureThreshold: 5
          EOF
cache:
  paths:
    - '/root/.m2/**/*'

동기식 호출 / 서킷 브레이킹 / 장애격리

  • 서킷 브레이킹 프레임워크의 선택: Spring FeignClient + Hystrix 옵션을 사용하여 구현함

시나리오는 단말앱(app)-->결제(pay) 시의 연결을 RESTful Request/Response 로 연동하여 구현이 되어있고, 결제 요청이 과도할 경우 CB 를 통하여 장애격리.

  • Hystrix 를 설정: 요청처리 쓰레드에서 처리시간이 610 밀리가 넘어서기 시작하여 어느정도 유지되면 CB 회로가 닫히도록 (요청을 빠르게 실패처리, 차단) 설정
# app 서비스, application.yml

feign:
  hystrix:
    enabled: true

# To set thread isolation to SEMAPHORE
#hystrix:
#  command:
#    default:
#      execution:
#        isolation:
#          strategy: SEMAPHORE

hystrix:
  command:
    # 전역설정
    default:
      execution.isolation.thread.timeoutInMilliseconds: 610

  • 피호출 서비스(결제:pay) 의 임의 부하 처리 - 400 밀리에서 증감 220 밀리 정도 왔다갔다 하게
# (pay) Payment.java (Entity)

    @PrePersist
    public void onPrePersist(){

        if("cancle".equals(payMethod)) {
            // 예시 푸드 딜리버리처럼 행위 필드를 하나 더 추가 하려다가 payMethod에 cancle 들어오면 취소 요청인 것으로 정의
            PayCanceled payCanceled = new PayCanceled();
            BeanUtils.copyProperties(this, payCanceled);
            payCanceled.publish();
        } else {
            PayApproved payApproved = new PayApproved();
            BeanUtils.copyProperties(this, payApproved);

            // 바로 이벤트를 보내버리면 주문정보가 커밋되기도 전에 예약 상태 변경 이벤트가 발송되어 주문테이블의 상태가 바뀌지 않을 수 있다.
            // TX 리스너는 커밋이 완료된 후에 이벤트를 발생하도록 만들어준다.
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
                @Override
                public void beforeCommit(boolean readOnly) {
                    payApproved.publish();
                }
            });

            try { // 피호출 서비스(결제:pay) 의 임의 부하 처리 - 400 밀리에서 증감 220 밀리 정도 왔다갔다 하게
                Thread.currentThread().sleep((long) (400 + Math.random() * 220));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
  • 부하테스터 siege 툴을 통한 서킷 브레이커 동작 확인:
  • 동시사용자 100명
  • 60초 동안 실시
siege -c100 -t60S -r10 --content-type "application/json" 'http://localhost:8081/orders POST {"hotelId": "4001", "roomType": "standard"}'

defaulting to time-based testing: 60 seconds

{	"transactions":			        1054,
	"availability":			       80.34,
	"elapsed_time":			       59.74,
	"data_transferred":		        0.30,
	"response_time":		        5.47,
	"transaction_rate":		       17.64,
	"throughput":			        0.00,
	"concurrency":			       96.58,
	"successful_transactions":	        1054,
	"failed_transactions":		         258,
	"longest_transaction":		        8.41,
	"shortest_transaction":		        0.44
}

  • 80.34% 성공, 19.66% 실패

오토스케일 아웃

사전 작업

  1. metric server 설치 - kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.3.7/components.yaml
  2. Resource Request/Limit 설정 image
  3. HPA 설정 - kubectl autoscale deployment payment --cpu-percent=50 --min=1 --max=10 cpu-percent=50 -n teamtwohotel

Pod 들의 요청 대비 평균 CPU 사용율 (여기서는 요청이 200 milli-cores이므로, 모든 Pod의 평균 CPU 사용율이 100 milli-cores(50%)를 넘게되면 HPA 발생)"

Siege 도구 활용한 부하(Stress) 주기

  1. siege 설치 - kubectl create -f siege.yaml
  2. siege 접속 - kubectl exec -it siege -- /bin/bash image

부하에 따른 오토스케일 아웃 모니터링

image

무정지 배포

무정지 배포 전 replica 3 scale up

image

Readiness 설정

image

Rolling Update

kubectl set image deploy order order=새로운 이미지 버전 image

siege로 무중단 확인

image

Configmap

  • configmap 생성

    kubectl create configmap my-config --from-literal=key1=value1 --from-literal=key2=value2

  • configmap 정보 가져오기

    kubectl get configmaps my-config -o yaml

  • 파일로부터 configmap 생성 (configmap.yml 생성)

apiVersion: v1
kind: ConfigMap
metadata:
  name: customer1
data:
  TEXT1: Customer1_Company
  TEXT2: Welcomes You
  COMPANY: Customer1 Company Technology Pct. Ltd.

kubectl create -f configmap.yml

  • configmap
  • 출력하는 소스는 아래의 secret에서 함께

Secret

  • 시크릿 생성
kubectl create secret generic my-password --from-literal=password=mysqlpassword --namespace teamtwohotel

secret

  • 시크릿 확인
kubectl get secret my-password -o yaml

확인

  • 시크릿 buildspec.yml

    소스

  • 시크릿 자바 출력

    결과출력

참고

개발 환경 구성

  1. 도커 설치 https://whitepaek.tistory.com/38 위에 가면 도커 관련 명령어들도 있음

  2. 카프카 설치

https://dev-jj.tistory.com/entry/MAC-Kafka-%EB%A7%A5%EC%97%90-Kafka-%EC%84%A4%EC%B9%98-%ED%95%98%EA%B8%B0-Docker-homebrew-Apache
https://jdm.kr/blog/208
경로 이동 /Users/jinhyeonbak/intensive/kafka_2.12-2.3.0/bin
주키퍼 실행
./zookeeper-server-start.sh ../config/zookeeper.properties &
카프카 broker 실행
./kafka-server-start.sh ../config/server.properties ( 4 ~ 5 는 건너뛰어도 됨 )
카프카 topic 만들기
./kafka-topic.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic teamtwohotel
카프카 producer 실행
./kafka-console-poducer.sh --broker-list localhost:9092 --topic teamtwohotel
카프카 consumer 실행
./kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic teamtwohotel --from-beginning
카프카 토픽 삭제 ./kafka-topics.sh --zookeeper localhost:2181 --delete --topic DummyTopic
카프카 토픽 리스트 ./kafka-topics.sh --list --zookeeper localhost:2181
카프카가 비정상일 때 sudo lsof -i :2181 한뒤
kill -9 pid 하고 다시 띄워준다
  1. httpie 설치

  2. aws cli 설치 https://docs.aws.amazon.com/ko_kr/cli/latest/userguide/install-cliv2-mac.html aws configure 로 액세스 ID 등 입력

  3. eksctl 설치 https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/getting-started-eksctl.html

  4. IAM 생성 https://www.44bits.io/ko/post/publishing_and_managing_aws_user_access_key

  5. eksctl 생성 ( 시간이 좀 걸림 ) 클러스터 생성 eksctl create cluster --name admin-eks --version 1.17 --nodegroup-name standard-workers --node-type t3.medium --nodes 4 --nodes-min 1 --nodes-max 4

  6. Local EKS 클러스터 토큰가져오기 ( CI/CD 할때 필요한건데, 앞에 설정해줘야 할 게 더 있으니 아래 쪽 CI/CD 다시 참고 ) aws eks --region ap-northeast-2 update-kubeconfig --name admin-eks

  7. 아마존 컨테이너 레지스트리 아마존 > ecr (elastic container registry) > ecr 레파지터리 : ECR은 각 배포될 이미지 대상과 이름을 맞춰준다 aws ecr create-repository --repository-name admin-eks --region ap-northeast-2 aws ecr put-image-scanning-configuration --repository-name admin-eks --image-scanning-configuration scanOnPush=true --region ap-northeast-2

  8. AWS 컨테이너 레지스트리 로그인 aws ecr get-login-password --region (Region-Code) | docker login --username AWS --password-stdin (Account-Id).dkr.ecr.(Region-Code).amazonaws.com

  9. AWS 레지스트리에 도커 이미지 푸시하기 (이건 위에서 한 거랑 좀 겹치는듯) aws ecr create-repository --repository-name (IMAGE_NAME) --region ap-northeast-2 docker push (Account-Id).dkr.ecr.ap-northeast-2.amazonaws.com/(IMAGE_NAME):latest

hotel's People

Contributors

jinhyun-park avatar imdongbin avatar daehyeokyou avatar simpl-ify avatar chjun000 avatar

Watchers

James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.