이전 글에서 HTTP 방식으로 클러스터 1개 노드 1개 로 구성하여

데이터 하나를 활용 해보는 시간을 가졌다

 

이번 글에서는 HTTPS 방식으로 SSL 인증서도 다운받고

클러스터 1개에 노드 2개 로 구성하여

데이터 하나를 활용 해보는 시간을 가지겠다

 

1. 개발 환경

Java 17

SpringBoot 2.7.13

Maven

centOS 7.9.2009

docker 24.0.2

elasticsearch:7.17.11

kibana:7.17.11

Intellij

mobaxterm

 

2. 참고한 공식문서

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/configuring-tls-docker.html

 

Encrypting communications in an Elasticsearch Docker Container | Elasticsearch Guide [7.17] | Elastic

Windows users not running PowerShell will need to remove \ and join lines in the snippet below.

www.elastic.co

 

공식 문서에서는 로컬환경에서 구축하는 방법을 다루고

자세하게 설명을 해놓지 않는다

중간중간 알아서 필요한걸 가져다 붙이고

리눅스 환경에서 폴더 배치라던지 명령어를 알아야

이해를 할 수 있는 부분이 있어서 그것도 정리 해보고자 한다

 

3. 공식문서 따라 해보기 (도커)

 

먼저 파일을 4개(instances.yml, .env, create-cert.yml, docker-compose.yml)

생성해서 사용해야 하는데 각 파일이 무엇인지 설명하겠다

 

instances.yml

- 인증서를 생성해야하는 인스턴스를 식별하기 위한 파일

 

.env

- 엘라스틱 버전과 인증서가 생성될 위치를 지정한 환경변수가 세팅되어 있는 파일

 

create-cert.yml

- 도커 컴포즈 파일로써 엘라스틱과 키바나를 위한 인증서를

생성할 컨테이너를 생성하는 명령어가 있는 파일

 

docker-compose.yml

- 도커 컴포즈 파일로써 SSL/TLS 가 설정된 엘라스틱 및 키바나를 생성하고 정의하는 파일

 

위의 4개의 파일을 사용중인 os 환경에 폴더하나를 만들고

같은 폴더에 다 넣어주면 된다

나는 centOS 에서 /project/elasticsearch 라는 위치에 저장을 했다

 

1) instances.yml 생성하기

공식문서에서는 dns: 에다가 es01 이랑 localhost 이렇게 적어주는데

나는 따로 dns 네임이 없기에 ip 주소를 동일하게 적어주었다

 

instances :

- 인스턴스파일임을 선언하는것

 

name :

- 나중에 docker-compose 파일에 각 컨테이너별 이름이 있는데

그 컨테이너와 동일하게 이름을 작성해 주면된다

 

dns :

- 도메인 이름 선언해주는 곳이다

- 도메인 네임 서버 라는것을 알고있을텐데

네이버에서 naver 라는 도메인 명으로 url 창에 적어도 접속이 되지만

사실 그건 192.168. 뭐시기뭐시기 하면서 ip로 접속하는 것이다

모른다면 구글에 dns 검색해서 공부해보면 좋을것 같다

 

ip :

- 접속 하려는 ip 주소를 적어주면 된다

 

instances:
  - name: es01
    dns:
      - 192.168.10.11111
    ip:
      - 192.168.10.11111

  - name: es02
    dns:
      - 192.168.10.11111
    ip:
      - 192.168.10.11111

  - name: kib01
    dns:
      - 192.168.10.11111
    ip:
      - 192.168.10.11111

 

2) .env 파일 생성하기

COMPOSE_PROJECT_NAME=

- 프로젝트 이름을 적어주면되는데 보통 볼륨 및 네트워크에 접두사를 사용한다

 

CERTS_DIR=

- 인증서를 찾을 것으로 예상되는 도커 이미지 내부의 경로이다

 

ELASTIC_PASSWORD=

- 엘라스틱 비밀번호를 지정해준다

 

COMPOSE_PROJECT_NAME=es 
CERTS_DIR=/usr/share/elasticsearch/config/certificates 
ELASTIC_PASSWORD=

 

나중에 설명하겠지만 .env 파일을 만들었으면

해당 파일 위치에서 source .env 로 실행을 시켜줘야한다

 

3) create-certs.yml 생성하기

version :

- 도커컴포즈 파일 만들때 처럼 버전을 지정해준다

 

services :

- 어떠한 컨테이너를 생성할지 지정해주는 것이다

 

create_certs :

- 해당 컨테이너를 생성한다고 선언하는 것이다

 

image :

- 도커에서 어떤 이미지를 받을지 지정하는 것이다

 

container_name :

- 컨테이너 이름을 지정하는 것이다

 

command :

- 해당 파일이 실행될때 입력되는 커멘드를 적는 곳이다

 

working_dir :

- 동작이 일어나는 디렉토리 위치이다

 

volumes :

- 도커를 띄운 os의 위치와 도커에 있는 위치를 바인드 마운트 하는 것이다

 

networks :

- 통신하고자 하는 네트워크 지정하는 곳이다

 

driver :

- 도커 네트워크 방법을 적는 곳인데 default 값이 bridge 이다

이 외에도 host, none, container, overlay 등이 있다

 

version: '3'

services:
  create_certs:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.11
    container_name: create_certs
    command: >
      bash -c '
        yum install -y -q -e 0 unzip;
        if [[ ! -f /certs/elastic-certificates.p12 ]]; then
          bin/elasticsearch-certutil cert --silent --pem --in config/certificates/instances.yml -out /certs/bundle.zip;
          unzip /certs/bundle.zip -d /certs;
        fi;
        chown -R 1000:0 /certs
      '
    working_dir: /usr/share/elasticsearch
    volumes:
      - /project/elasticsearch/certs:/certs
      - .:/usr/share/elasticsearch/config/certificates
    networks:
      - elastic

networks:
  elastic:
    driver: bridge

 

4) docker-compose.yml 생성하기

위에서 create-certs.yml 인 도커 컴포즈 파일을 만들면서

version, services, container_name, image 등 설명 했기에

여기서는 environment 안에 해당하는 내용과

healthcheck 에 대해서만 설명하도록 하겠다

 

environment :

- 환경설정을 하는거라고 보면된다

 

node.name =

- 노드의 이름을 지정하는 곳

 

discovery.seed_hosts =

- 클러스터를 구성할 노드에 대한 정의를 하는 곳

 

cluster.initial_master_nodes =

- 마스터로 선출될 후보 노드를 초기 세팅하는 곳

 

ELASTIC_PASSWORD =

- 엘라스틱서치 접속 패스워드를 적는곳

- .env 파일에서 설정한 값을 사용하려고

$ELASTIC_PASSWORD 이렇게 적으면 된다

 

ingest.geoip.downloader.enabled=

- geoip 인덱싱 다운로드를 비활성화 하는 곳

- 공식 문서에는 없는데 엘라스틱서치를 돌릴때

오류가 나서 해당 내용을 추가했다

 

ES_JAVA_OPTS=

- JVM이 사용 가능한 힙 사이즈 결정하는 곳

 

xpack.license.self_generated.type =

- xpack 라이센스의 종류를 지정하는 곳

 

xpack.security.enabled =

- xpack 활성화 여부를 지정하는 곳

 

xpack.security.http.ssl.enabled =

- xpack ssl 활성화 여부를 지정하는 곳

 

xpack.security.http.ssl.key =

- xpack key 위치를 지정하는 곳

 

xpack.security.http.ssl.certificate_authorities =

- xpack 권한 파일 위치를 지정하는 곳

 

xpack.security.http.ssl.certificate =

- xpack 증명서 위치를 지정하는 곳

 

위 과정으로 security 모드가 활성화 되면

transport ssl 설정 또한 활성화 되어야 한다고 한다

Transport SSL must be enabled if security is enabled on a [basic] license. 
Please set [xpack.security.transport.ssl.enabled] to [true] or disable security by setting [xpack.security.enabled]

 

xpack.security.transport.ssl.enabled =

- ssl 활성화 시 같이 활성화 해줘야하는 곳

 

xpack.security.transport.ssl.verification_mode =

- 검증을 무엇으로 할지 지정하는 곳

 

xpack.security.transport.ssl.certificate_authorities =

- xpack 권한 파일 위치를 지정하는 곳

 

xpack.security.transport.ssl.certificate =

- xpack 증명서 위치를 지정하는 곳

 

xpack.security.transport.ssl.key =

- xpack key 위치를 지정하는 곳

 

healthcheck :

- 도커에서 컨테이너 안의 프로세스가 정상적으로 작동하는지 체크할때 사용

 

test :

- 테스트를 수행하는 명령어를 적는 곳

 

interval :

- 테스트 간의 시간 간격을 지정하는 곳

 

timeout :

- 테스트의 최대 실행 시간을 지정하는 곳

 

retries :

테스트가 실패할 경우 재시도 횟수를 지정하는 곳

 

depends_on :

- 해당 컨테이너가 생성되기 이전에 먼저 생성되야하는 도커 지정

 

version: '2.2'

services:
  es01:
    container_name: es01
    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.11
    environment:
      - node.name=es01
      - discovery.seed_hosts=es01,es02
      - cluster.initial_master_nodes=es01,es02
      - ELASTIC_PASSWORD=$ELASTIC_PASSWORD 
      - ingest.geoip.downloader.enabled=false
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
      - xpack.license.self_generated.type=basic 
      - xpack.security.enabled=true
      - xpack.security.http.ssl.enabled=true
      - xpack.security.http.ssl.key=$CERTS_DIR/es01/es01.key
      - xpack.security.http.ssl.certificate_authorities=$CERTS_DIR/ca/ca.crt
      - xpack.security.http.ssl.certificate=$CERTS_DIR/es01/es01.crt
      - xpack.security.transport.ssl.enabled=true
      - xpack.security.transport.ssl.verification_mode=certificate 
      - xpack.security.transport.ssl.certificate_authorities=$CERTS_DIR/ca/ca.crt
      - xpack.security.transport.ssl.certificate=$CERTS_DIR/es01/es01.crt
      - xpack.security.transport.ssl.key=$CERTS_DIR/es01/es01.key
    volumes: ['data01:/usr/share/elasticsearch/data', '/project/elasticsearch/certs:$CERTS_DIR']
    ports:
      - 29200:9200
    healthcheck:
      test: curl --cacert $CERTS_DIR/ca/ca.crt -s https://192.168.10.11111:29200 >/dev/null; if [[ $$? == 52 ]]; then echo 0; else echo 1; fi
      interval: 30s
      timeout: 10s
      retries: 5

  es02:
    container_name: es02
    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.11
    environment:
      - node.name=es02
      - discovery.seed_hosts=es01,es02
      - cluster.initial_master_nodes=es01,es02
      - ELASTIC_PASSWORD=$ELASTIC_PASSWORD
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
      - ingest.geoip.downloader.enabled=false
      - xpack.license.self_generated.type=trial
      - xpack.security.enabled=true
      - xpack.security.http.ssl.enabled=true
      - xpack.security.http.ssl.key=$CERTS_DIR/es02/es02.key
      - xpack.security.http.ssl.certificate_authorities=$CERTS_DIR/ca/ca.crt
      - xpack.security.http.ssl.certificate=$CERTS_DIR/es02/es02.crt
      - xpack.security.transport.ssl.enabled=true
      - xpack.security.transport.ssl.verification_mode=certificate 
      - xpack.security.transport.ssl.certificate_authorities=$CERTS_DIR/ca/ca.crt
      - xpack.security.transport.ssl.certificate=$CERTS_DIR/es02/es02.crt
      - xpack.security.transport.ssl.key=$CERTS_DIR/es02/es02.key
    volumes: ['data02:/usr/share/elasticsearch/data', '/project/elasticsearch/certs:$CERTS_DIR']

  kib01:
    image: docker.elastic.co/kibana/kibana:7.17.11
    container_name: kib01
    depends_on: {"es01": {"condition": "service_healthy"}}
    ports:
      - 25601:5601
    environment:
      SERVERNAME: kibana
      ELASTICSEARCH_URL: https://192.168.10.11111:29200
      ELASTICSEARCH_HOSTS: https://192.168.10.11111:29200
      ELASTICSEARCH_USERNAME: 
      ELASTICSEARCH_PASSWORD: 
      ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES: $CERTS_DIR/ca/ca.crt
      SERVER_SSL_ENABLED: "true"
      SERVER_SSL_KEY: $CERTS_DIR/kib01/kib01.key
      SERVER_SSL_CERTIFICATE: $CERTS_DIR/kib01/kib01.crt
    volumes:
      - /project/elasticsearch/certs:$CERTS_DIR
    networks:
      - elastic

volumes: {"data01", "data02"}

networks:
  elastic:
    driver: bridge

 

5) 도커 실행 해보기

1) 가장 먼저 도커 컴포즈 파일중 create-cert.yml 을 실행 해 인증서를 생성 해준다

이 과정은 최초 한번만 실행 해주면 된다

docker-compose -f create-certs.yml run --rm create_certs

 

--rm 옵션은 create-certs 서비스가 실행되고 종료되면

자동으로 컨테이너가 삭제되는 옵션이다

 

2) 그 다음 .env 파일을 실행해 준다

아래 코드를 쳐서 .env를 실행해 환경설정을 해준다

source .env

 

3) docker-compose.yml 을 실행해 준다

그리고는 docker-compose.yml 파일을 돌려주면 된다

docker-compose up -d

 

4. 공식문서 따라 해보기 (자바)

참고로 공식문서에서 high level 로 설명이 나와있어서

자바 코드를 짜는데 RestHighLevelClient 를 하면 Deprecated 가 나온다

그래서 찾아보니 LowLevel 로 마이그레이션을 진행하라고 하는데

해당 내용이 공식문서에 엄청 부실하게 나온다

그래서 대충 포멧이 어떤지 파악하고 이렇게하면 되겠는데?

하는 방법으로 몇개는 내 마음데로 코드를 작성했다

 

공식문서

https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/7.17/_basic_authentication.html

 

Basic authentication | Elasticsearch Java API Client [7.17] | Elastic

Configuring basic authentication can be done by providing an HttpClientConfigCallback while building the RestClient through its builder. The interface has one method that receives an instance of org.apache.http.impl.nio.client.HttpAsyncClientBuilder as an

www.elastic.co

 

공식 깃허브

https://github.com/elastic/elasticsearch-java/tree/8.8/java-client/src/test/java/co/elastic/clients/documentation/usage

 

GitHub - elastic/elasticsearch-java: Official Elasticsearch Java Client

Official Elasticsearch Java Client. Contribute to elastic/elasticsearch-java development by creating an account on GitHub.

github.com

 

1) 엘라스틱서치 접속 해보기

/** 호스트 IP */
static String hostName = "192.168.10.11111";

/** 호스트 포트 */
static int hostPort = 29200;

/** 호스트 프로토콜 */
static String hostProtocol = "https";

/** 엘라스틱 서치 아이디 */
static String userName = "";

/** 엘라스틱 서치 비밀번호 */
static String password = "";

/** restClient 객체 생성 */
static RestClient restClient;

/** low level 담을 객체 생성 */
static ElasticsearchClient esClient;

/** 엘라스틱서치 접속 정보 생성 */
static void init() throws IOException {
    final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();

    credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName,password));

    restClient = RestClient.builder(new HttpHost(hostName, hostPort, hostProtocol))
        .setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
            @Override
            public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpAsyncClientBuilder) {
                return httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
            }
        }).build();

    RestHighLevelClient hlrc = new RestHighLevelClientBuilder(restClient)
        .setApiCompatibilityMode(true)
        .build();

    ElasticsearchTransport transport = new RestClientTransport(
        restClient,
        new JacksonJsonpMapper()
    );

    esClient = new ElasticsearchClient(transport);
}

 

여기서 접속을 시도하면 오류가 발생한다

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: 
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

 

위와 비슷한 내용으로 4가지 정도 오류가 발생하는데

로컬에서 키바나로 접근할때 인증서가 없기 때문이다

 

그래서 로컬환경에서도 도커에서 인증서를 받았듯이

똑같이 받아 주면된다

 

나는 파일질라를 이용해서 원격 os 환경에 있는 파일을

로컬인 window 환경으로 certs 파일을 옮겨와서 사용했다

 

윈도우 관리자 권한으로 cmd를 켜서

certs 파일이 있는 경로로 이동한 뒤에

아래 명령어를 작성해주면 된다

 

// 양식
keytool -import -alias "인증서 별칭" -keystore "클라이언트 keystore 경로" -file root-ca.pem

// 적용 사례
C:\Program Files\Java\jdk-17>keytool -import -alias rootCA -keystore .\lib\security\cacerts -file C:\\certs\ca\ca.crt

 

그러면 아래와 같이 화면이 나오고

 

keystore password 를 입력해주고

certificate 를 실뢰 하냐에 y 를 해주면 완료이다

 

 

2) 인덱스 생성 해보기

/** 인덱스 생성 */
static void crateIndex() throws IOException {

    Settings settings = Settings.builder()
        .put("index.number_of_shards",1)
        .put("index.number_of_replicas", 0)
        .build();

    CreateIndexRequest createIndexRequest = new CreateIndexRequest("member");
    createIndexRequest.settings(settings);

    CreateIndexResponse createIndexResponse = esClient.indices().create(c -> c
        .index("member")
    );

    if(createIndexResponse.acknowledged() == true) {
        System.out.println("인덱스 생성 성공");
    } else {
        System.out.println("인덱스 생성 실패");
    }
}

 

3) 도큐먼트 생성 및 수정 해보기

/** Document 생성 및 수정 */
@NoArgsConstructor
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
static class Member {
    private String name;
    private String message;

    Member(String name, String message) {
        this.name = name;
        this.message = message;
    }
}

static void createDocument() throws IOException {
    Member member = new Member("cho", "안녕하세요");

    IndexResponse createResponse = esClient.index(i -> i
        .index("member")
        .id("1")
        .document(member)
    );

    System.out.println(createResponse.version());

}

 

4) 도큐먼트 삭제 해보기

/** Document 삭제 */
@NoArgsConstructor
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
static class Member {
    private String name;
    private String message;

    Member(String name, String message) {
        this.name = name;
        this.message = message;
    }
}

static void delete() throws IOException {

    DeleteResponse deleteResponse = esClient.delete(d -> d
        .index("member")
        .id("1")
    );

    System.out.println("Delete 값 보기 : " + deleteResponse);
}

 

5) 도큐먼트 출력 해보기

/** Document 출력 */
@NoArgsConstructor
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
static class Member {
    private String name;
    private String message;

    Member(String name, String message) {
        this.name = name;
        this.message = message;
    }
}

static void read() throws IOException {

    GetResponse<Member> getResponse = esClient.get(g -> g
        .index("member")
        .id("1"),
        Member.class
    );

    if(getResponse.found()) {
        Member json = getResponse.source();
        String name = json.name;
        System.out.println("name : " + name);
    } else {
        System.out.println("not found");
    }
}

 

5. 마무리

기존 http 방식에서 https 로 넘어오니 설정을 해줘야 하는 방식이나

high level 방식이 deprecated 되다 보니 이를 마이그레이션 하는 과정

추가로 그에 따라 api 문법이 조금 달라지는 것이 문제가 있었고

공식문서에서 이를 자세히 다뤄주지않아 어려움이 있었다

 

그래도 무사히 테스트가 잘 작동하기에

여기서 글을 마쳐보고 나중에는 bulk 방식과

다양한 검색 관련 쿼리를 작성해보고

내가 놓친 부분이 있다면 추가해서 글을 써보도록 하겠다