Kuma's Curious Paradise
[이룸] 검색 기능 고도화 3 - kibana / spring boot와 es 연동하기 본문
1. Kibana 연동하기
1) Kibana란?
Kibana는 Elastic Stack의 핵심 구성 요소 중 하나로, 데이터를 시각화해서 보여주는 도구이다. Elasticsearch에 저장된 데이터를 기반으로 Dashboard(여러 그래프와 차트를 결합한 페이지)를 만들고, 시각화하여 분석할 수 있도록 돕는다. 쉽게 말해, 데이터들이 어떻게 들어가 있는지, 어떤 검색어가 많이 나왔는지, 핵심 키워드와 같은 데이터를 분석하여 그래프와 차트 형태로 시각화한다. 이 데이터를 기반으로 사용자의 관심사와 시장의 변화를 실시간으로 모니터링할 수 있다.
2) elasticsearch부터 kibana까지 설치하기
- 'elastic'이라는 이름의 도커 네트워크를 만든다. 도커 네트워크는 해당 네트워크에 있는 컨테이너들끼리 통신하도록 돕는다.
docker network create elastic
- Elastic의 공식 Docker 레지스트리인 docker.elastic.co에서 elasticsearch 이미지의 8.13.2 버전을 당겨온다.
docker pull docker.elastic.co/elasticsearch/elasticsearch:8.13.2
- 다운받은 도커 이미지를 실행한다. 컨테이너의 이름은 'es01'로 하며 호스트의 9200 포트(elasticsearch 기본 포트)와 컨테이너의 9200 포트를 매핑한다. 이를 통해 외부에서 컨테이너의 Elasticsearch 서비스에 접근할 수 있게 된다. '-m 1GB'는 컨테이너의 메모리 사용을 1GB로 제한하는데, 꼭 써주지 않아도 된다.
docker run --name es01 --net elastic -p 9200:9200 -it -m 1GB docker.elastic.co/elasticsearch/elasticsearch:8.13.2
- elasticsearch가 성공적으로 설치되면 다음의 정보가 뜬다. 어딘가에 저장해두자.
✅ Elasticsearch security features have been automatically configured!
✅ Authentication is enabled and cluster connections are encrypted.
ℹ️ Password for the elastic user (reset with `bin/elasticsearch-reset-password -u elastic`):
(이곳에 elastic password가 들어올 것)
ℹ️ HTTP CA certificate SHA-256 fingerprint:
(이곳에 elasticsearch가 발급한 CA 인증서의 fingerprint가 들어올 것)
ℹ️ Configure Kibana to use this cluster:
• Run Kibana and click the configuration link in the terminal when Kibana starts.
• Copy the following enrollment token and paste it into Kibana in your browser (valid for the next 30 minutes):
(이곳에 kibana 연동을 위한 token이 들어올 것)
ℹ️ Configure other nodes to join this cluster:
• Copy the following enrollment token and start new Elasticsearch nodes with `bin/elasticsearch --enrollment-token <token>` (valid for the next 30 minutes):
(이곳에 elasticsearch의 노드 확장을 위한 enrollment token이 들어올 것)
If you're running in Docker, copy the enrollment token and run:
`docker run -e "ENROLLMENT_TOKEN=<token>" docker.elastic.co/elasticsearch/elasticsearch:8.13.2`
- elastic password를 환경변수에 등록한다.
export ELASTIC_PASSWORD=${elastic password}
- 실행 중인 Docker 컨테이너인 es01 컨테이너의 /usr/share/elasticsearch/config/certs 디렉토리 안에 있는 http_ca.crt 파일을 현재 작업 중인 디렉토리(.)로 복사한다. http_ca.crt 파일은 elasticsearch가 자체적으로 서명한 https 통신용 인증서다.
docker cp es01:/usr/share/elasticsearch/config/certs/http_ca.crt .
- 서버에서 제공하는 SSL 인증서가 자체 서명(self-signed)되었거나 특정한 CA에 의해 발급된 경우, 클라이언트인 curl은 기본적으로 이를 거부할 수 있다. 이 때 --cacert 옵션으로 연결할 url에 인증서를 지정해 주어 안전하게 연결될 수 있도록 한다. '-u elastic:$ELASTIC_PASSWORD'에서 elastic은 username이고, $ELASTIC_PASSWORD는 앞에서 지정한 환경변수이니, 지정하지 않았다면 그냥 하드코딩해도 괜찮다.
curl --cacert http_ca.crt -u elastic:$ELASTIC_PASSWORD https://localhost:9200
- Elastic의 공식 Docker 레지스트리인 docker.elastic.co에서 kibana 이미지의 8.13.2 버전을 당겨온다. (elasticsearch와 버전을 맞출 것!)
docker pull docker.elastic.co/kibana/kibana:8.13.2
- kibana 컨테이너를 실행한다.
docker run --name kib01 --net elastic -p 5601:5601 docker.elastic.co/kibana/kibana:8.13.2
- kibana가 성공적으로 실행되었다면 5601포트로 지정된 특정 url을 준다. 해당 url로 들어가 앞에서 받았던 kibana용 토큰을 입력하자.
- kibana와 elasticsearch가 성공적으로 연결되었다!
2. Spring boot와 Elasticsearch 연동하기
1) build.gradle에 elasticsearch 라이브러리를 임포트한다. (elasticsearch도 스프링부트 스타터 중에 하나였다...!)
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
2) Elasticsearch 설정
@Configuration
@EnableElasticsearchRepositories(basePackageClasses = com.sparta.elasticsearch_practice.BoardDocumentRepository.class)
public class ElasticSearchConfig extends ElasticsearchConfiguration {
String fingerprint = ${fingerprint}
SSLContext sslContext = TransportUtils.sslContextFromCaFingerprint(fingerprint);
@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder()
.connectedTo("localhost:9200")
.usingSsl(sslContext) // SSL 사용 설정
.withBasicAuth("elastic", "FJW3vh6q6QyUpvYC8j2B") // 기본 인증을 사용하는 경우
.build();
}
}
3) JPA 엔티티 클래스와 ElasticSearch에 사용될 Document 클래스
@Entity
@Getter
@Setter
public class Board extends Timestamped{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long boardId;
@Setter
@Column(nullable = false)
private Long memberId;
@Column(nullable = false)
private String title;
@Column(nullable = false)
private String content;
}
@Document(indexName = "board")
@NoArgsConstructor
@Getter
public class BoardDocument {
@Id
private Long id;
@Field(type = FieldType.Long)
private Long boardId;
@Field(type = FieldType.Text)
private String title;
@Field(type = FieldType.Text)
private String content;
@Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second_millis)
private LocalDateTime registerDt;
public BoardDocument(Board board) {
this.id = board.getBoardId();
this.boardId = board.getBoardId();
this.title = board.getTitle();
this.content = board.getContent();
this.registerDt = board.getRegisterDt();
}
}
@Document (indexName = "board") 로 해당 인스턴스가 Elasticsearch의 어떤 인덱스에 저장될지 지정한다. 이때 꼭 소문자 사용할 것!
@Field 는 해당 필드가 Elasticsearch에서 어떻게 색인되어야 하는지 설정하는 데 도움을 준다. 보통 String은 FieldType.Text와 FieldType.Keyword로 지정하는데, Text는 분석기를 사용하여 색인화한다. 예를 들어, "아버지가 가방에 들어가신다."라는 문자가 들어오면 "아버지", "가방", "들어가신다" 같은 키워드(토큰)을 추출하여 색인으로 만든다. 반면 Keyword는 이메일처럼 '정확히 그것'을 검색하고 싶을 때 사용한다. "naver@google.com"이라는 문자는 naver, google, com으로 따로 색인하지 않고 사용자가 쓴 그 형태 그대로 저장한다.
4) 레포지토리 생성 (JpaRepository / ElasticsearchRepository를 상속받는 각각의 레포지토리 클래스가 있어야 한다.)
public interface BoardRepository extends JpaRepository<Board, Long> {
List<Board> findAll();
}
public interface BoardDocumentRepository extends ElasticsearchRepository<BoardDocument, Long> {
}
5) Controller와 Service 클래스
@RestController
@RequiredArgsConstructor
public class BoardController {
private final BoardService boardService;
@PostMapping("/board")
public String migrateDataFromMySQLToElasticsearch(){
boardService.migrateDataFromMySQLToElasticsearch();
return "엘라스틱서치에 성공적으로 저장하였습니다.";
}
@GetMapping("/board")
public List<Board> searchBy(@RequestParam String keyword){
return boardService.searchBy(keyword);
}
}
@Service
@AllArgsConstructor
public class BoardService {
private final ElasticsearchOperations elasticsearchOperations;
private final BoardRepository boardRepository;
private final BoardDocumentRepository boardDocumentRepository;
// MySQL 데이터를 읽어와서 Elasticsearch에 저장하는 메서드
@Transactional
public void migrateDataFromMySQLToElasticsearch() {
List<Board> boards = boardRepository.findAll();
for (Board board : boards) {
BoardDocument boardDocument = new BoardDocument(board);
boardDocumentRepository.save(boardDocument);
}
}
// 검색 메서드
public List<Board> searchBy(String keyword) {
Criteria criteria = new Criteria("title").contains(keyword)
.or(new Criteria("content").contains(keyword));
Query query = new CriteriaQuery(criteria).setPageable(PageRequest.of(0, 10));
SearchHits<BoardDocument> searchHits = elasticsearchOperations.search(query, BoardDocument.class);
return searchHits.stream()
.map(SearchHit::getContent)
.map(this::convertToBoard)
.collect(Collectors.toList());
}
private Board convertToBoard(BoardDocument boardDocument) {
Board board = new Board();
board.setBoardId(boardDocument.getBoardId());
board.setMemberId(1L);
board.setTitle(boardDocument.getTitle());
board.setContent(boardDocument.getContent());
return board;
}
elasticsearch 연습 프로젝트이므로, 다소 비효율적이지만(...) 엘라스틱서치와 mysql의 싱크를 맞추는 메서드 하나, 검색 메서드 하나를 작성한다.
3. 실행하기
- 엘라스틱에 접속해 "board"라는 이름의 인덱스를 생성한다. (아래는 이미 생성한 모습. 오른쪽 상단 'Create a new index'를 누르면 5초만에 생성이 가능하다.)
- db에 더미 데이터를 넣는다.
- 이후 포스트맨으로 연동 및 검색이 잘되는지 확인한다. (kibana의 devTool로도 확인 가능!)
'이룸 프로젝트' 카테고리의 다른 글
[이룸] Redis 저장을 위한 직렬화 / 역직렬화 (1) | 2024.04.23 |
---|---|
[이룸] elasticsearch 관련 트러블슈팅 1 - config에 ssl 관련 설정 삽입 (0) | 2024.04.17 |
[이룸] 도커Docker가 궁금해! - 가상 머신(VM) vs. 컨테이너(Container) (1) | 2024.04.10 |
[이룸] 검색 기능 고도화 2 - 엘라스틱 서치란 무엇인가? + 설치하기 (0) | 2024.04.09 |
[이룸] 검색 기능 고도화 1 - 현재 상황 점검 및 전문 검색 기능 도입 (0) | 2024.04.03 |