daybreak6 / spring-boot-reactive

0 stars 0 forks source link

스터디 5회차 인증하기 #5

Open LangVE opened 2 years ago

LangVE commented 2 years ago

스터디 5회차

kaiwind7 commented 2 years ago

5장. 스프링 부트 운영

애플리케이션 배포

우버 JAR 배포

$ ./mvnw package
$ java -jar target/hacking-spring-boot-ch5-reactive-0.0.1-SNAPSHOT.jar

도커 배포

FROM adoptopenjdk/openjdk8:latest

ARG JAR_FILE=target/*.jar

COPY ${JAR_FILE} app.jar

ENTRYPOINT ["java", "-jar", "/app.jar"]

애플리케이션을 여러 부분으로 분할해서 여러 계층에 나눠 담고 도커의 캐시 시스템을 활용하는 것이 더 효율적.

하지만 스프링부트 메이븐 플러그인에 내장된 도커 지원 기능을 활용하는 편이 더 낫다. 이를 위해 pom.xml 파일에 계층화 방식을 사용한다고 지정한다.

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <layers>
            <enabled>true</enabled>
        </layers>
    </configuration>
</plugin>
$ ./mvnw clean package
$ java -Djarmode=layertools -jar target/hacking-spring-boot-ch5-reactive-0..1-SNAPSHOT.jar list

dependencies
spring-boot-loader
snapshot-dependencies
application

스프링 부트가 jarmode-layertools 파라미터를 인식해서 list 명령을 만나면 JAR 파일에 내장된 모든 계층을 보여준다

FROM adoptopenjdk/openjdk8:latest as builder
WORKDIR application
ARG JAR FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Diarmode=layertools -jar application.jar extract

FROM adoptopenjdk/openjdk8:latest
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
#COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
  1. 빌더(builder)로 사용할 컨테이너를 만든다.
  2. extract 명령으로 레이어를 추출한다.
  3. 두 번째 컨테이너를 만든다.
  4. 빌더 컨테이너에서 추출한 여러 레이어를 두 번째 컨테이너에 복사한다. COPY 명령에는 도커의 계층 캐시 알고리즘이 적용된다. 그래서 서드파티 라이브러리는 캐시될 수 있다.
  5. java -jar가 아니라 스프링 부트의 커스텀 런처로 애플리케이션을 실행한다. 이 런처는 애플리케이션 시작 시 불필요한 JAR 파일 압축 해제를 하지 않으므로 효율적이다.
$ docker build . --tag hacking-with-spring-boot
$ docker run -it -p 8080:8080 hacking-with-sprint-boot:latest
$ ./mvnw spring-boot:build-image

spring-boot:build-image 명령을 실행하면 스프링 부트가 페이카토 빌드팩 프로젝트에서 빌드펙을 가져와서 도커 컨테이너 이미지를 빌드한다.

$ docker run -it -p 8080:8080 docker.io/library/hacking-spring-boot-ch5-reactive:0.0.1-SNPSHOP
방식 장점 단점
계층 기반 Dockerfile 이미지 - Dockerfile을 직접 작성하므로 이미지 빌드 전체 과정 제어 가능.
- 스프링 부트에서 계층화를 제공하므로 빌드 과정 최적화 가능
- 컨테이너 직접 관리
- 컨테이너를 빌드 과정이 완전하지 않으면 보안에 취약한 계층 존재 위험
페이키토 빌드팩 기반 이미지 - Dockerfile을 직접 다룰 필요 없음.
- 최신 패치와 SSL을 포함한 업계표준 컨테이너 기술이 빌드 과정에 포함
- 개발에 더 집중 가능
- DOckerfile에 비해 제어할 수 있는 것이 적음

운영 애플리케이션 관리

애플리케이션 정상상태 점검: /actuator/health

management.endpoint.health.show-details=always

애플리케이션 상세정보: /actuator/info

info.project.version=@project.version@
info.java.version=@java.version@
info.spring.framework.version=@spring-framework.version@
info.spring.data.version=@spring-data-bom.version@
<plugin>
    <groupId>pl.project13.maven</groupId>
    <artifactId>git-commit-id-plugin</artifactId>
</plugin>

다양한 액추에이터 앤드포인트

management.endpoints.web.exposure.include=*

management.endpoints.web.exposure.include=aduitevents,beans,caches, ...

로깅 정보 앤드포인트: /actuator/loggers

$ curl -v -H 'Content-Type: application/json' -d '{"configuraedLevel": "TRACE"}' http://localhost:8080/actuator/loggers/com.greglturnquist/

다양한 운영 데이터 확인

스레드 정보 확인:/actuator/threaddump

힙 정보 확인: /actuator/heapdump

HTTP 호출 트레이싱: /actuator/httptrace

@Bean
HttpTraceRepository traceRepository(){
    return new InMemoryHttpTraceRepository();
}
  1. @Bean 애너테이션이 붙은 메소드가 반환하는 객체는 애플리케이션 컨텍스트에 스프링 빈으로 등록된다
  2. traceRepository() 메소드는 HttpTraceRepository 타입의 빈을 반환한다.
  3. 스프링 부트 액추에이터의 InmemoryHttpTraceRepository 인스턴스를 생성해서 반환한다.
public class HttpTraceWrapper {
    private @Id String id;

    private HttpTrace httpTrace;

    public HttpTraceWrapper(HttpTrace httpTrace) {
        this.httpTrace = httpTrace;
    }
    public HttpTrace getHttpTrace() {
        return httpTrace;
    }    
}
public interface HttpTraceWrapperRepository extends Repository<HttpTraceWrapper, String> {
    Stream<HttpTraceWrapper> findAll();
    void save(HttpTraceWrapper trace);
}
public class SpringDateHttpTraceRepository implements HtttpTraceRepository {
    private final HttpTraceWrapperRepository repositoty;

    public SpringDataHttpTraceRepository(HttpTraceWrapperRepository repository) {
        this.repositoty = repositoty;
    }

    @Override
    public List<HttpTrace> findAll() {
        return repository.findAll()
            .map(HttpTraceWrapper::getHttpTrace)
            .collect(Collectors.toList());
    }

    @Override
    public void add(HttpTrace trace) {
        repository.save(new HttpTraceWrapper(trace));
    }
}
@Bean
HttpTraceRepository springDataTraceRepository(HttpTraceWrapperRepository repository) {
    return new SpringDateHttpTraceRepository(repositoty)
}
static Converter<Document, HttpTraceWrapper> CONVERTER = new Converter<Document, HttpTraceWrapper>() {
   @Overrid
    public HttpTraceWrapper convert(Document document) {
        Document httpTrace = document.get("httpTrace", Document.class);
        Document request = httpTrace.get("request", Document.class);
        Document response = httpTrace.get("response", Document.class);

        return new HttpTraceWrapper(new HttpTrace(
            new HttpTrace.Request(request.getString("method"), 
                                 URI.create(request.getStirng("uri")),
                                 request.get("headers", Map.class),
                                 null),
            new Httptrace.Response(response.getInterger("status"),
                                  response.get("headers", Map.class)),
            httpTrace.getDate("timestamp").toInstant(),
            null,
            null,
            httpTrace.getLong("timeTaken")));
    }
}
@Bean
public MappingMongoConverter mappingMongoConverter(MongoMappingContext context) {
    MappingMongoConverter mappingConverter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context);

    mappingConverter.setCustomConversions(new MongoCustomConversions(Collections.singletonList(Converter)));

    return mappingConverter;
}
  1. 몽고디비의 DBRef 값에 해석이 필요할 때 UnsupportedOperationException을 던지는 NoOpDbRefResolver를 사용해서 MappingMongoConverter 객체를 생성한다. HttpTrace에는 DBRef 객체가 없으므로 DBRef 값 해석이 발생하지 않으며, NoOpDbRefResolver에 의해 예외가 발생할 일은 없다.단지 MappingMongoConverter의 생성자가 DbRefResolver 타입의 인자를 받기 떄문에 NoOpDbRefResolver를 전달하는 것이다.
  2. MongoCustomConversions 객체를 mappingConverter에 설정한다.
  3. 커스텀 컨버터 한 개로 구성된 리스트를 추가해서 MongoCustomConversions를 생성한다.

그 밖의 엔드포인트

관리 서비스 경로 수정

management.endpoints.web.base-path=?manage

management.endpoints.web.base-path=/
management.endpoints.web.path-mapping.loggers=logs

정리