# 配置独享型负载均衡
在大数据处理场景中，需要通过负载均衡器来分发流量，以提高Elasticsearch集群的可用性和性能。然而，默认的共享型负载均衡器在功能和性能上存在局限性，无法满足高并发和复杂业务需求。如何配置独享型负载均衡器，以充分利用其更丰富的功能和更强的性能，成为了一个亟待解决的问题。本文将详细介绍如何配置独享型负载均衡器，以实现更高效、更安全的集群访问能力。
#### 配置方案介绍
使用独享型负载均衡器接入集群具有如下优点：
- 非安全模式集群也支持集成弹性负载均衡服务的能力。
- 支持用户使用自定义证书进行HTTPS双向认证。
- 支持7层流量监控及告警配置，方便用户随时查看监控情况。
 
不同安全模式的集群对接独享型负载均衡器共有8种不同的服务形态，不同服务形态对应的ELB能力如[表1]所示，8种组合的配置概览如[表2]所示。
 表1不同集群对应的ELB能力 
| 集群安全模式       | ELB最终对外提供的服务形态                                                                                   | ELB负载均衡 | ELB流量监控 | ELB双向认证 |
|:---|:---|:---|:---|:---|
| 非安全        | 无认证                                                                                             | 支持      | 支持      | 不支持      |
| 非安全        | 单向认证 双向认证            | 支持        | 支持        | 支持        |
| 安全+HTTP    | 密码认证                                                                                              | 支持        | 支持        | 不支持        |
| 安全+HTTP    | 单向认证+密码认证 双向认证+密码认证  | 支持        | 支持       | 支持        |
| 安全+HTTPS | 单向认证+密码认证 双向认证+密码认证 | 支持       | 支持     | 支持         |
   
 表2不同集群对接ELB的配置概览 
| **集群安全模式**                          | **ELB最终对外提供的服务形态**                  | **ELB监听器**                         | **ELB监听器**                         | **ELB监听器**                       | **后端服务器组**                       | **后端服务器组**                           | **后端服务器组**                                               |
| **集群安全模式**                          | **ELB最终对外提供的服务形态**                  | **前端协议**                           | **前端端口**                           | **SSL解析方式**                         | **后端协议**                           | **健康检查端口**                        | **健康检查路径**                                              |
|:---|:---|:---|:---|:---|:---|:---|:---|
| 非安全      | 无认证        | HTTP       | 9200    | 无认证     | HTTP     | 9200     | /                             |
| 非安全      | 单向认证     | HTTPS  | 9200   | 单向认证 | HTTP  | 9200 | /                             |
| 非安全      | 双向认证       | HTTPS   | 9200    | 双向认证    | HTTP    | 9200      | /                             |
| 安全+HTTP | 密码认证     | HTTP     | 9200     | 无认证      | HTTP   | 9200      | /_opendistro/_security/health |
| 安全+HTTP | 单向认证+密码认证 | HTTPS  | 9200 | 单向认证   | HTTP  | 9200 | /_opendistro/_security/health |
| 安全+HTTP | 双向认证+密码认证  | HTTPS   | 9200   | 双向认证   | HTTP    | 9200      | /_opendistro/_security/health |
| 安全+HTTPS   | 单向认证+密码认证  | HTTPS    | 9200     | 单向认证   | HTTPS   | 9200     | /_opendistro/_security/health |
| 安全+HTTPS   | 双向认证+密码认证  | HTTPS | 9200  | 双向认证    | HTTPS | 9200     | /_opendistro/_security/health |
   
#### 约束限制
- 如果ELB绑定了公网，则不推荐接入非安全模式的集群。由于非安全模式的集群使用HTTP协议通信，且不需要安全认证即可访问，如果对接公网访问会存在安全风险。
- 开启了HTTPS访问的安全模式的集群不支持HTTP类型的前端协议认证，如果需要使用HTTP类型的前端协议，需要将集群的"安全模式"变更为"非安全模式"，具体操作请参见[安全模式变更](https://support.huaweicloud.com/usermanual-css/css_01_0158.html)。在变更安全模式前，请先关闭"负载均衡"，安全模式变更完成后再开启"负载均衡"。
 
#### 前提条件
- 已经创建独享型负载均衡器，操作指导请参见[创建独享型负载均衡器](https://support.huaweicloud.com/usermanual-elb/elb_lb_000006.html)。负载均衡器需要满足如下条件：
  - "所属VPC"和CSS集群的虚拟私有云保持一致。确保两者之间网络互通。
  
  - 开启"IP类型后端"。只有开启IP类型后端，独享型负载均衡器才支持接入CSS集群。
  
  - 根据业务需求，选择是否配置"弹性公网IP"。只有配置了弹性公网IP，CSS集群对接的负载均衡实例才会显示"公网IP"，才支持通过负载均衡实现公网访问CSS集群。
   
- 如果规划ELB监听器的前端协议是HTTPS，则需要将服务器证书或CA证书上传至ELB管理控制台。操作指导请参见[创建证书](https://support.huaweicloud.com/usermanual-elb/elb_ug_szs_0003.html)。
  - 监听器的SSL解析方式是单向认证，则需要上传服务器证书。
  
  - 监听器的SSL解析方式是双向认证，则需要上传服务器证书和CA证书。
   
 
#### CSS集群对接负载均衡器
1. 登录[云搜索服务管理控制台](https://console.huaweicloud.com/elasticsearch/)。
2. 在左侧导航栏，选择"集群管理 \> Elasticsearch"。
3. 在集群列表，单击目标集群名称，进入集群详情页。
4. 选择"访问配置 \> 负载均衡"，在"Elasticsearch"页签，打开"负载均衡开关"右侧的开关，在弹窗中完成基础配置。
   表3负载均衡基础配置 
   | 参数   | 说明                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
   |:---|:---|
   | 负载均衡器 | 选择准备好的独享型负载均衡器。 如果没有合适的ELB，可以单击创建负载均衡器前往创建页面，操作步骤请参见[创建独享型负载均衡器](https://support.huaweicloud.com/usermanual-elb/elb_lb_000006.html)。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
   | 委托   | 配置负载均衡器需要当前账号具备ELB资源的使用权限。通过配置IAM委托，使当前账号授权CSS服务访问其拥有的ELB资源。 - 当首次配置委托时，可以单击"自动创建委托"新建委托"css_elb_agency"直接使用。  - 当已有自动创建的委托时，可以单击"委托一键授权"，自动删除委托中"ELB Administrator"系统角色或"ELB FullAccess"系统策略的权限，并自动新增如下自定义策略授权委托到最小化权限。 ``` "elb:loadbalancers:list", "elb:loadbalancers:get", "elb:certificates:list", "elb:healthmonitors:*", "elb:members:*", "elb:pools:*", "elb:listeners:*" ```   - 执行"自动创建委托"和"委托一键授权"的用户需要如下最小权限。 ``` "iam:agencies:listAgencies", "iam:roles:listRoles", "iam:agencies:getAgency", "iam:agencies:createAgency", "iam:permissions:listRolesForAgency", "iam:permissions:grantRoleToAgency", "iam:permissions:listRolesForAgencyOnProject", "iam:permissions:revokeRoleFromAgency", "iam:roles:createRole" ```   - 使用委托的用户需要如下最小权限。 ``` "iam:agencies:listAgencies", "iam:agencies:getAgency", "iam:permissions:listRolesForAgencyOnProject", "iam:permissions:listRolesForAgency" ```    |
      
   图1开启负载均衡   
   ![](https://support.huaweicloud.com/usermanual-css/figure/zh-cn_image_0000001951397478.png "点击放大") 
5. 单击"确定"，开启负载均衡。 开启后，页面会显示负载均衡实例的信息。
   
6. 在"监听器配置"区域，您可以单击右侧![](https://support.huaweicloud.com/usermanual-css/figure/zh-cn_image_0000001983636885.png)配置监听器以确保客户端能够正确访问CSS集群。
   表4配置监听器 
   | 参数     | 说明                                                                                                                                                                                                                               |
   |:---|:---|
   | 前端协议      | 客户端与负载均衡监听器建立流量分发连接的协议。 支持选择"HTTP"或"HTTPS"。 建议根据实际业务需要选择协议。                                                                       |
   | 前端端口    | 客户端与负载均衡监听器建立流量分发连接的端口。 建议根据实际业务需要自定义。                                                                                                                                              |
   | SSL解析方式 | 客户端到服务器端认证方式。仅"前端协议"选择"HTTPS"才需要配置。 支持"单向认证"或"双向认证"。 建议根据实际业务需要选择解析方式。                                                           |
   | 服务器证书   | 服务器证书用于SSL握手协商，需提供证书内容和私钥。仅"前端协议"选择"HTTPS"才需要配置服务器证书。 选择在ELB创建的服务器证书。                                                                                                            |
   | CA证书  | CA证书又称客户端CA公钥证书，用于验证客户端证书的签发者。仅"SSL解析方式"选择"双向认证"时，才需要配置CA证书。 选择在ELB创建的CA证书。 在开启HTTPS双向认证功能时，只有当客户端能够出具指定CA签发的证书时，HTTPS连接才能成功。 |
      
   图2配置监听器   
   ![](https://support.huaweicloud.com/usermanual-css/figure/zh-cn_image_0000001951401518.png "点击放大") 
7. （可选）在监听器配置区域，单击"访问控制"后的"设置"跳转到负载均衡器的监听器列表，单击监听器访问控制列的"设置"，配置监听器的访问控制以限制访问CSS集群的IP地址，提高安全性。设置访问控制的操作指导请参见[访问控制策略](https://support.huaweicloud.com/usermanual-elb/elb_03_0003.html)。
   ![](https://support.huaweicloud.com/usermanual-css/public_sys-resources/warning_3.0-zh-cn.png)
   不设置的话默认允许所有的IP地址通过负载均衡实例访问CSS集群，此时存在安全风险。
   
8. 在健康检查区域，您可以查看各个节点IP的健康检查结果，确保集群各个节点正常运行。
   表5健康检查结果状态说明 
   | 健康检查结果 | 说明        |
   |:---|:---|
   | 正常     | 节点IP连接正常。 |
   | 异常    | 节点IP连接不可用 |
      
   
9. 当集群无需使用负载均衡访问时，可以解绑独享型负载均衡器，释放资源。 在"负载均衡 \> Elasticsearch"页面，关闭"负载均衡开关"右侧的开关，在弹框中单击"确定"，解绑ELB。
   ![](https://support.huaweicloud.com/usermanual-css/public_sys-resources/caution_3.0-zh-cn.png)
   关闭负载均衡后，监听器及后端服务器组配置将被删除，且不可恢复。
   
 
#### 通过Curl命令使用负载均衡接入集群
1. 在云搜索服务管理控制台，单击左侧导航栏的"集群管理"。
2. 登录[云搜索服务管理控制台](https://console.huaweicloud.com/elasticsearch/)。
3. 在左侧导航栏，选择"集群管理 \> Elasticsearch"。
4. 在集群列表，单击目标集群名称，进入集群详情页。
5. 选择"访问配置 \> 负载均衡"，在"Elasticsearch"页签，获取并记录负载均衡实例的"私有IP"、"IPv6地址"或"公网IP"，以及监听器配置的"前端协议/端口"。
   ![](https://support.huaweicloud.com/usermanual-css/public_sys-resources/caution_3.0-zh-cn.png)
   如果ELB绑定了公网，则不推荐接入非安全模式的集群。非安全模式的集群使用HTTP通信且不需要安全认证即可访问，如果对接公网访问存在安全风险。
   
6. 在ECS服务器中执行如下Curl命令测试能否通过独享型负载均衡实例接入集群。
   表6不同集群的接入命令 
   | 集群安全模式       | ELB最终对外提供的服务形态 | 接入集群的Curl命令           |
   |:---|:---|:---|
   | 非安全        | 无认证           | ``` curl http://IP:port ```                                                                       |
   | 非安全        | 单向认证            | ``` curl --cacert ./ca.crt https://IP:port ```                                                    |
   | 非安全        | 双向认证           | ``` curl --cacert ./ca.crt --cert ./client.crt --key ./client.key https://IP:port ```             |
   | 安全+HTTP    | 密码认证            | ``` curl http://IP:port -u user:pwd ```                                                           |
   | 安全+HTTP    | 单向认证+密码认证     | ``` curl --cacert ./ca.crt https://IP:port -u user:pwd ```                                        |
   | 安全+HTTP    | 双向认证+密码认证      | ``` curl --cacert ./ca.crt --cert ./client.crt --key ./client.key https://IP:port -u user:pwd ``` |
   | 安全+HTTPS | 单向认证+密码认证     | ``` curl --cacert ./ca.crt https://IP:port -u user:pwd ```                                        |
   | 安全+HTTPS | 双向认证+密码认证     | ``` curl --cacert ./ca.crt --cert ./client.crt --key ./client.key https://IP:port -u user:pwd ``` |
      
   表7变量说明 
   | 变量名 | 说明                 |
   |:---|:---|
   | IP  | 负载均衡实例的IP地址。        |
   | port                              | 监听器配置的"前端协议/端口"。                              |
   | user    | 集群的用户名，仅安全集群需要配置。  |
   | pwd    | 用户名对应的密码，仅安全集群需要配置。 |
      
   当可以正常返回集群信息时，表示连接成功。
   
本文还提供了通过独享型负载均衡实例接入集群的Java客户端代码示例：[通过负载均衡器的HTTPS双向认证方式接入集群的代码示例（Java）]
 #### 通过负载均衡器的HTTPS双向认证方式接入集群的代码示例（Java）
在Java客户端，通过负载均衡器的HTTPS双向认证方式接入集群的代码示例。
#### ESSecuredClientWithCerDemo代码示例
```
import org.apache.commons.io.IOUtils;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.HttpHost;
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.SecureRandom;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManagerFactory;
public class ESSecuredClientWithCerDemo {
    private static final String KEY_STORE_PWD = "";
    private static final String TRUST_KEY_STORE_PWD = "";
    private static final String CA_JKS_PATH = "ca.jks";
    private static final String CLIENT_JKS_PATH = "client.jks";
    private static final String ELB_ADDRESS = "127.0.0.1";
    private static final int ELB_PORT = 9200;
    private static final String CSS_USERNAME = "user";
    private static final String CSS_PWD = "";
    public static void main(String[] args) {
        // 建立客户端
        RestHighLevelClient client = initESClient(ELB_ADDRESS, CSS_USERNAME, CSS_PWD);
        try {
            // 查询 match_all，相当于 {\"query\": {\"match_all\": {}}}
            SearchRequest searchRequest = new SearchRequest();
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchSourceBuilder.query(QueryBuilders.matchAllQuery());
            searchRequest.source(searchSourceBuilder);
            // query
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            System.out.println("query result: " + searchResponse.toString());
            SearchHits hits = searchResponse.getHits();
            for (SearchHit hit : hits) {
                System.out.println(hit.getSourceAsString());
            }
            System.out.println("query success");
            Thread.sleep(2000L);
        } catch (InterruptedException | IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(client);
        }
    }
    private static RestHighLevelClient initESClient(String clusterAddress, String userName, String password) {
        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, password));
        SSLContext ctx = null;
        try {
            KeyStore ks = getKeyStore(CLIENT_JKS_PATH, KEY_STORE_PWD, "JKS");
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
            kmf.init(ks, KEY_STORE_PWD.toCharArray());
            KeyStore tks = getKeyStore(CA_JKS_PATH, TRUST_KEY_STORE_PWD, "JKS");
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
            tmf.init(tks);
            ctx = SSLContext.getInstance("SSL", "SunJSSE");
            ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
        } catch (Exception e) {
            e.printStackTrace();
        }
        SSLIOSessionStrategy sessionStrategy = new SSLIOSessionStrategy(ctx, new HostnameVerifier() {
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }
        });
        SecuredHttpClientConfigCallback httpClientConfigCallback = new SecuredHttpClientConfigCallback(sessionStrategy,
            credentialsProvider);
        RestClientBuilder builder = RestClient.builder(new HttpHost(clusterAddress, ELB_PORT, "https"))
            .setHttpClientConfigCallback(httpClientConfigCallback);
        RestHighLevelClient client = new RestHighLevelClient(builder);
        return client;
    }
    private static KeyStore getKeyStore(String path, String pwd, String type) {
        KeyStore keyStore = null;
        FileInputStream is = null;
        try {
            is = new FileInputStream(path);
            keyStore = KeyStore.getInstance(type);
            keyStore.load(is, pwd.toCharArray());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(is);
        }
        return keyStore;
    }
}
```
#### SecuredHttpClientConfigCallback代码示例
```
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.common.Nullable;
import java.util.Objects;
class SecuredHttpClientConfigCallback implements RestClientBuilder.HttpClientConfigCallback {
    @Nullable
    private final CredentialsProvider credentialsProvider;
    /**
     * The {@link SSLIOSessionStrategy} for all requests to enable SSL / TLS encryption.
     */
    private final SSLIOSessionStrategy sslStrategy;
    /**
     * Create a new {@link SecuredHttpClientConfigCallback}.
     *
     * @param credentialsProvider The credential provider, if a username/password have been supplied
     * @param sslStrategy         The SSL strategy, if SSL / TLS have been supplied
     * @throws NullPointerException if {@code sslStrategy} is {@code null}
     */
    SecuredHttpClientConfigCallback(final SSLIOSessionStrategy sslStrategy,
        @Nullable final CredentialsProvider credentialsProvider) {
        this.sslStrategy = Objects.requireNonNull(sslStrategy);
        this.credentialsProvider = credentialsProvider;
    }
    /**
     * Get the {@link CredentialsProvider} that will be added to the HTTP client.
     *
     * @return Can be {@code null}.
     */
    @Nullable
    CredentialsProvider getCredentialsProvider() {
        return credentialsProvider;
    }
    /**
     * Get the {@link SSLIOSessionStrategy} that will be added to the HTTP client.
     *
     * @return Never {@code null}.
     */
    SSLIOSessionStrategy getSSLStrategy() {
        return sslStrategy;
    }
    /**
     * Sets the {@linkplain HttpAsyncClientBuilder#setDefaultCredentialsProvider(CredentialsProvider) credential provider},
     *
     * @param httpClientBuilder The client to configure.
     * @return Always {@code httpClientBuilder}.
     */
    @Override
    public HttpAsyncClientBuilder customizeHttpClient(final HttpAsyncClientBuilder httpClientBuilder) {
        // enable SSL / TLS
        httpClientBuilder.setSSLStrategy(sslStrategy);
        // enable user authentication
        if (credentialsProvider != null) {
            httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
        }
        return httpClientBuilder;
    }
}
```
#### pom.xml代码示例
```
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>1</groupId>
    <artifactId>ESClient</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>ESClient</name>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <elasticsearch.version>7.10.2</elasticsearch.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>transport</artifactId>
            <version>${elasticsearch.version}</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>${elasticsearch.version}</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>${elasticsearch.version}</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.11.0</version>
        </dependency>
    </dependencies>
</project>
```
