009_管理Elasticsearch集群

[toc]

安全

集群身份认证与用户鉴权

Elasticsearch 在默认安装后,不提供任何形式的安全防护。如果有些管理员为了配置方便,在 elasticsearch.yml 中配置 server.host 为0.0.0.0,此时如果该台 ES 服务器是拥有一个公网 ip 的网络的接口的,那么外部就可以直接通过这个接口访问的 ES,而此时 ES 又是默认状态下没有任何的安全防护,此时外部就可以直接使用 ES api 随意获取数据。这种情况我们从两个角度讨论:

  • 在 ES 服务器拥有一个公网 ip 的情况下避免配置 server.host 为0.0.0.0;避免 ES 服务器拥有公网 ip,将其部署在内网环境中,并只分配内网 ip;nignx 反向代理。
  • 为 ES 开启它的 security 功能。在本节中我们重点讨论这一项。

数据安全性的基本要求

  1. 身份认证,鉴定用户是否合法
  2. 用户鉴权:指定哪个用户可以访问哪个索引并可以做哪些操作
  3. 传输信息的加密
  4. 需要拥有日志审计的机制,帮助我们了解过去 ES 集群中发生了什么

以下时解决数据安全性的一些免费方案:

Authentication:身份认证

Authentication 主要分为两个体系:

  1. 提供用户名和密码
  2. 提供密钥或者 Kerberos

在 ES 中,X-Pack 的认证服务功能称为 Realms。ES 中的 Realms 分为两种形式:

  • 一种是免费的,这种是 native(内置 Realms)的, 用户名和密码保存在 Elasticsearch 的索引当中的。
  • 另一种是通过和 LDAP、Active Directory、PKI、SAML、Kerberos 进行集成的外部(认证信息存储在外部) Realms,这种是收费的。

RBAC:用户鉴权

Role Based Access Control:定义一个角色,并分配一组权限。权限包括索引级、字段级、集群级的不同操作。然后通过将角色分配给用户,使得用户拥有这些权限。

  • User:The authenticated user
  • Role:A named set of permissions
  • Permission:A set of ne or more privileges against a secured resouce
  • Privilege:A named group of 1 or more actions that user may execute against a secured resource

在 ES 中创建了以下 Privileges:

  1. Cluster Privileges:all、monitor、manager、manage_index、manage_index_template、manage_rollup
  2. Indices Privileges:all、create、create_index、delete、delete_index、index、manage、read、write、view_index_metadata

另外 Elastic X-Pack 内置了一些用户和角色:

image-20200430135926838

当 ES 打开了 security 的功能之后,还可以使用 security 的功能去创建用户创建不同的角色:

image-20200430140139471

开启并配置 X-Pack 的认证与鉴权

  1. 通过设置配置项xpack.security.enabled为 true即可启动 X-Pack 的认证与鉴权功能,可以在命令启动的时候通过-E xpack.security.enbaled=true选项来设置;也可以通过修改 elasticsearch.yml 中设置xpack.security.enabled: true,在本例中,我们对 elasticsearch.yml 中进行如下配置:

    network.host: 0.0.0.0
    http.port: 9800
    transport.port: 9900
    discovery.seed_hosts: ["0.0.0.0", "[::1]"]
    cluster.initial_master_nodes: ["node0"]
    xpack.security.enabled: true
    xpack.security.transport.ssl.enabled: true
    

    然后启动 ES:

    [elasticsearch@izwz920kp0myp15p982vp4z elasticsearch-7.6.2]$ ./bin/elasticsearch -E node.name=node0 -E cluster.name=john -E path.data=node0_data
    

    此时我们直接访问 ES 可以看到需要输入用户名:

    image-20200430143013548

  2. 通过bin/elasticsearch-setup-passwords interactive设置在前面提到的 Xpack 中默认内置的用户密码,这里全部设置为.ABCD45.。(原来好像的命令是是bin/elasticsearch-password interactive

    [root@izwz920kp0myp15p982vp4z elasticsearch-7.6.2]# ./bin/elasticsearch-setup-passwords interactive
    future versions of Elasticsearch will require Java 11; your Java version from [/usr/local/software/java/jdk1.8.0_231/jre] does not meet this requirement
    
    Failed to determine the health of the cluster running at http://172.18.93.184:9800
    Unexpected response code [503] from calling GET http://172.18.93.184:9800/_cluster/health?pretty
    Cause: master_not_discovered_exception
    
    It is recommended that you resolve the issues with your cluster before running elasticsearch-setup-passwords.
    It is very likely that the password changes will fail when run against an unhealthy cluster.
    
    Do you want to continue with the password setup process [y/N]y
    
    Initiating the setup of passwords for reserved users elastic,apm_system,kibana,logstash_system,beats_system,remote_monitoring_user.
    You will be prompted to enter passwords as the process progresses.
    Please confirm that you would like to continue [y/N]y
    
    
    Enter password for [elastic]:
    Reenter password for [elastic]:
    Enter password for [apm_system]:
    Reenter password for [apm_system]:
    Enter password for [kibana]:
    Reenter password for [kibana]:
    Enter password for [logstash_system]:
    Reenter password for [logstash_system]:
    Enter password for [beats_system]:
    Reenter password for [beats_system]:
    Enter password for [remote_monitoring_user]:
    Reenter password for [remote_monitoring_user]:
    
  3. 此时直接连接 elasticsearch 并输入账号名密码即可:

    image-20200501093848208

    或者

    curl -u elastic 'myecs.com:9800/_cat/nodes?pretty'
    
  4. 然后我们进行 Kibana 的设置

    server.port: 5602
    server.host: "0.0.0.0"
    elasticsearch.hosts: ["http://localhost:9800"]
    elasticsearch.preserveHost: true
    elasticsearch.username: "kibana"
    elasticsearch.password: ".ABCD45."
    

    启动 Kibana

    [elasticsearch@izwz920kp0myp15p982vp4z kibana-7.6.2-linux-x86_64]$ ./bin/kibana
    

    此时我们再来尝试访问 Kibana 的时候,发现需要登录了。因为我们在上面配置了 kibana 读取的 elasticsearch 的信息,而 elasticsearch 启动了 X-pack 的安全功能。所以这里 kibana 应该也是跟着一起启动了 X-pack 的安全功能,并且之前 elasticsearch 配置的 X-pack 的一些信息也是通用的,包括我们配置的内置用户密码,所以这里要输入的也是 X-pack 中的提供的一些内置用户。而我们上面在 kibana 中配置的 elasticsearch.username 则是 kibana 在访问 elasticsearch 的时候需要提供的用户名密码。

    image-20200501094744680

  5. 下面我们用这个"elastic"超级用户(拥有所有权限)来创建一些需要被保护的数据,以下是一些信用卡信息:

    POST orders/_bulk
    {"index":{}}
    {"product" : "1","price" : 18,"payment" : "master","card" : "9876543210123456","name" : "jack"}
    {"index":{}}
    {"product" : "2","price" : 99,"payment" : "visa","card" : "1234567890123456","name" : "bob"}
    
  6. 创建数据之后,我们为这个索引创建一些角色

    image-20200501101341679

    然后我们进入了创建角色的界面,分别有关于 elasticsearch 和 kibana 的设置界面,我们下面先看 elasticsearch 的设置界面:

    image-20200501104004169

    下面是 Kibana 的设置界面,我们需要给这个用户拥有读取 Kibana 数据的权限,点击"Add space privilege",然后选择相关权限

    image-20200501102455240

    image-20200501102821056

    点击创建角色,完成角色创建。

    image-20200501102920006

  7. 创建角色之后,我们创建一个用户

    image-20200501104353218

    填写相关信息,并选择我们创建的"read_orders"角色,然后创建用户

    image-20200501104536497

  8. 然后我们登出 elstic 用户并登入新建的 demo 用户

    image-20200501104742250

    对前面创建的orders 索引进行以下操作,发现读数据是没问题的,但是写权限就会报错:

    #验证读权限,可以执行
    POST orders/_search
    {}
    

    image-20200501104906811

    #验证写权限,报错
    POST orders/_bulk
    {"index":{}}
    {"product" : "1","price" : 18,"payment" : "master","card" : "9876543210123456","name" : "jack"}
    {"index":{}}
    {"product" : "2","price" : 99,"payment" : "visa","card" : "1234567890123456","name" : "bob"}
    

    image-20200501104929598

相关阅读

https://www.elastic.co/guide/en/elasticsearch/reference/7.1/configuring-security.html

基于TCP协议通信的安全

上面讲了如何给对于 Elasticsearch 的访问操作加上权限控制,下面我们要讲的是更深层次一点的安全,通讯安全。ES 的通信有两种:TCP 和 HTTP 协议。集群内部通信都是使用 TCP 通信;集群与外部的通信可以使用 TCP,也可以使用 HTTP(推荐是 HTTP 的 RESTful API)。 在本节我们主要介绍 ES 默认提供的保障 TCP 通信安全的方式。

在本节我们介绍集群内部安全通信,Elasticsearch 默认提供了两种通讯方式,一种是 HTTP,默认在9200端口,另一种是 TCP,默认在9300端口。集群的内部通信是基于9300端口进行的,默认情况下,集群的内部通信是没有任何加密的,另外,集群对于一个新节点的加入也没有做任何校验。那么一些非法之徒在可以访问得到我们的集群的情况下就有可能通过以下两张方式获取我们的数据:

  • 对数据进行抓包,获取敏感信息
  • 自己起一个 ES 节点加入我们的集群

针对以上两点,我们就需要对我们集群的内部通信启用通讯加密并且对新加入的节点进行身份验证。X-pack 中就为我们提供这样的方案,使用 TLS 协议进行通信,TLS 协议中通信双方是需要一个 CA(Certification Authority,在 ES 叫做 Trusted Certificate Authority) 签发的证书的,ES 中需要一个 X.509 的证书。我们可以使用ES 提供的以下工具来为集群节点分别创建证书:

# 为您的Elasticearch集群创建一个证书颁发机构。例如,使用elasticsearch-certutil ca命令:
bin/elasticsearch-certutil ca

#为群集中的每个节点生成证书和私钥。例如,使用elasticsearch-certutil cert 命令:
bin/elasticsearch-certutil cert --ca elastic-stack-ca.p12

#将证书拷贝到节点的 config/certs目录下
elastic-certificates.p12

然后在配置文件中或者在启动集群节点的时候指定以下参数:

# 启动 ssl 协议通信
xpack.security.transport.ssl.enabled: true
# 指定 ssl 通信的级别
xpack.security.transport.ssl.verification_mode: certificate
# 指定证书位置
xpack.security.transport.ssl.keystore.path: certs/elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: certs/elastic-certificates.p12

上面提到通过xpack.security.transport.ssl.verification_mode来指定 ES 对于证书的校验级别,一共有三个级别可配置,默认情况是 none:

  • full:节点加入集群需要相同 CA 签发的证书,还需要验证 Host name 或者 IP 地址
  • certificate:节点加入需要使用相同 CA 签发的证书即可
  • none:任何节点都可以加入,开发环境中用于诊断目的

下面我们来演示一下:

  1. 在其中一个节点中创建 CA( TLS 中 CA 签发的证书是用来识别一个网络上的一个终端的,所以对于这个终端里面的所有 ES 节点来说使用的都是同一个证书。真实情况中应该是在多台服务器中包含多个 ES 程序,选择其中一个作为创建 CA 作为证书签发机构进行证书签发然后拷贝证书到其他服务器上其他节点的指定目录即可。我们的演示就是在一个程序下启动多个节点,所以就在当前ES 下创建 CA 并只签发一个证书就行拉)。

    然后下面需要我们输入 CA 文件的名称,直接回车使用默认名称"elastic-stack-ca.p12"。然后需要我们输入这个 CA 的密码,直接回车使用空密码。

    [elasticsearch@izwz920kp0myp15p982vp4z elasticsearch-7.6.2]$ ./bin/elasticsearch-certutil ca
    future versions of Elasticsearch will require Java 11; your Java version from [/usr/local/software/java/jdk1.8.0_231/jre] does not meet this requirement
    This tool assists you in the generation of X.509 certificates and certificate
    signing requests for use with SSL/TLS in the Elastic stack.
    
    The 'ca' mode generates a new 'certificate authority'
    This will create a new X.509 certificate and private key that can be used
    to sign certificate when running in 'cert' mode.
    
    Use the 'ca-dn' option if you wish to configure the 'distinguished name'
    of the certificate authority
    
    By default the 'ca' mode produces a single PKCS#12 output file which holds:
        * The CA certificate
        * The CA's private key
    
    If you elect to generate PEM format certificates (the -pem option), then the output will
    be a zip file containing individual files for the CA certificate and private key
    
    Please enter the desired output file [elastic-stack-ca.p12]:
    Enter password for elastic-stack-ca.p12 :
    
  2. 为节点签发证书。下面会需要我们输入 CA的密码,直接回车输入空密码;然后是需要我们输入证书的名称,直接回车使用默认名称"elastic-certificates.p12";最后会让我们输入证书的密码,直接回车使用空密码。

    [elasticsearch@izwz920kp0myp15p982vp4z elasticsearch-7.6.2]$ ./bin/elasticsearch-certutil cert --ca elastic-stack-ca.p12
    future versions of Elasticsearch will require Java 11; your Java version from [/usr/local/software/java/jdk1.8.0_231/jre] does not meet this requirement
    This tool assists you in the generation of X.509 certificates and certificate
    signing requests for use with SSL/TLS in the Elastic stack.
    
    The 'cert' mode generates X.509 certificate and private keys.
        * By default, this generates a single certificate and key for use
           on a single instance.
        * The '-multiple' option will prompt you to enter details for multiple
           instances and will generate a certificate and key for each one
        * The '-in' option allows for the certificate generation to be automated by describing
           the details of each instance in a YAML file
    
        * An instance is any piece of the Elastic Stack that requires an SSL certificate.
          Depending on your configuration, Elasticsearch, Logstash, Kibana, and Beats
          may all require a certificate and private key.
        * The minimum required value for each instance is a name. This can simply be the
          hostname, which will be used as the Common Name of the certificate. A full
          distinguished name may also be used.
        * A filename value may be required for each instance. This is necessary when the
          name would result in an invalid file or directory name. The name provided here
          is used as the directory name (within the zip) and the prefix for the key and
          certificate files. The filename is required if you are prompted and the name
          is not displayed in the prompt.
        * IP addresses and DNS names are optional. Multiple values can be specified as a
          comma separated string. If no IP addresses or DNS names are provided, you may
          disable hostname verification in your SSL configuration.
    
        * All certificates generated by this tool will be signed by a certificate authority (CA).
        * The tool can automatically generate a new CA for you, or you can provide your own with the
             -ca or -ca-cert command line options.
    
    By default the 'cert' mode produces a single PKCS#12 output file which holds:
        * The instance certificate
        * The private key for the instance certificate
        * The CA certificate
    
    If you specify any of the following options:
        * -pem (PEM formatted output)
        * -keep-ca-key (retain generated CA key)
        * -multiple (generate multiple certificates)
        * -in (generate certificates from an input file)
    then the output will be be a zip file containing individual certificate/key files
    
    Enter password for CA (elastic-stack-ca.p12) :
    Please enter the desired output file [elastic-certificates.p12]:
    Enter password for elastic-certificates.p12 :
    
    Certificates written to /usr/local/software/elasticsearch/elasticsearch-7.6.2/elastic-certificates.p12
    
    This file should be properly secured as it contains the private key for
    your instance.
    
    This file is a self contained file and can be copied and used 'as is'
    For each Elastic product that you wish to configure, you should copy
    this '.p12' file to the relevant configuration directory
    and then follow the SSL configuration instructions in the product guide.
    
    For client applications, you may only need to copy the CA certificate and
    configure the client to trust this certificate.
    

    此时可以看到当前目录下证书已经签发了,我们为证书创建一个 certs 目录来专门存放证书,然后将刚刚创建的证书移动到该目录下。需要注意的是,ES 强制证书只能存放在 config 目录下面,不然会报错,而配置的值如果指定的是相对路径,也就是相对于 config 的路径

    [elasticsearch@izwz920kp0myp15p982vp4z elasticsearch-7.6.2]$ ll
    总用量 580
    drwxrwxrwx  2 elasticsearch root            4096 3月  26 14:36 bin
    drwxrwxrwx  2 elasticsearch root            4096 5月   1 09:34 config
    drwxrwxrwx  3 elasticsearch elasticsearch   4096 4月  23 17:53 data
    -rw-------  1 elasticsearch elasticsearch   3443 5月   1 11:48 elastic-certificates.p12
    -rw-------  1 elasticsearch elasticsearch   2527 5月   1 11:45 elastic-stack-ca.p12
    drwxrwxrwx  9 elasticsearch root            4096 3月  26 14:36 jdk
    drwxrwxrwx  3 elasticsearch root            4096 3月  26 14:36 lib
    -rwxrwxrwx  1 elasticsearch root           13675 3月  26 14:28 LICENSE.txt
    drwxrwxrwx  2 elasticsearch root            4096 5月   1 09:27 logs
    drwxrwxrwx 38 elasticsearch root            4096 3月  26 14:37 modules
    drwxrwxr-x  3 elasticsearch elasticsearch   4096 5月   1 09:27 node0_data
    -rwxrwxrwx  1 elasticsearch root          523209 3月  26 14:36 NOTICE.txt
    drwxrwxrwx  3 elasticsearch root            4096 4月  23 19:21 plugins
    -rwxrwxrwx  1 elasticsearch root            8164 3月  26 14:28 README.asciidoc
    [elasticsearch@izwz920kp0myp15p982vp4z elasticsearch-7.6.2]$ mkdir ./config/certs
    [elasticsearch@izwz920kp0myp15p982vp4z elasticsearch-7.6.2]$ mv elastic-certificates.p12 ./config/certs/
    
  3. 配置节点使用该证书进行 tls 通信,可以在 elasticsearch.yml 中进行配置,也可以在启动的时候通过-E配置,这里我们通过后者配置:

    elasticsearch.yml:

    network.host: 0.0.0.0
    discovery.seed_hosts: ["0.0.0.0:9900"]
    cluster.initial_master_nodes: ["node0"]
    

    通过在启动的时候设置参数,以下两个节点是可以启动成功并形成以 node0 为 master 的集群的

    #提供证书的节点
    bin/elasticsearch -E node.name=node0 -E cluster.name=john -E path.data=node0_data -E http.port=9800 -E transport.port=9900 -E xpack.security.transport.ssl.enabled=true -E xpack.security.transport.ssl.verification_mode=certificate -E xpack.security.transport.ssl.keystore.path=certs/elastic-certificates.p12 -E xpack.security.transport.ssl.truststore.path=certs/elastic-certificates.p12
    
    bin/elasticsearch -E node.name=node1 -E cluster.name=john -E path.data=node1_data -E http.port=9801 -E transport.port=9901 -E xpack.security.transport.ssl.enabled=true -E xpack.security.transport.ssl.verification_mode=certificate -E xpack.security.transport.ssl.keystore.path=certs/elastic-certificates.p12 -E xpack.security.transport.ssl.truststore.path=certs/elastic-certificates.p12
    

    image-20200501132144591

    image-20200501132200368

    此后一个没有启用 ssl 协议通信的节点企图加入集群,一直在尝试寻找 master 节点,但是根本就建立不了通信。

    #不使用 TSL 的节点,无法加入(注意配置文件中也要把相关 ssl 参数关闭)
    bin/elasticsearch -E node.name=node2 -E cluster.name=john -E path.data=node2_data -E http.port=9802 -E transport.port=9902
    

    image-20200501131550736

    然后是一个启用了 ssl 协议通信,但是没有提供 CA 签发证书的节点妄图加入集群,也能启动,并且可以 i 访问,但是在尝试寻找 master 节点的时候一直在报没有证书的错误。

    #或者不提供证书的节点,无法加入
    bin/elasticsearch -E node.name=node2 -E cluster.name=john -E path.data=node2_data -E http.port=9802 -E transport.port=9902 -E xpack.security.transport.ssl.enabled=true -E xpack.security.transport.ssl.verification_mode=certificate
    

    image-20200501132825243

    image-20200501131837571

相关阅读

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

基于HTTP协议通信的安全(外部)

在前面的内容中我们介绍了如何通过设置让 ES 基于 TCP 的通信使用 TLS 通信。在本节中我们将介绍如何让 ES 启用基于 SSL 的 HTTP 通信(HTTPS)。

Elasticsearch 推荐大家都是用 RESTful 的方式与其进行外部通信,

image-20200501154540265

我们看到,从浏览器到 Kibana 的访问、Kibana 到 Elasticsearch 的访问、Logstash 到 Elasticsearch 的访问,我们的 java 应用程序到 Elasticsearch 的访问都是经过 http 协议进行通信的,所以为了我们的一些重要数据更加安全,所以我们需要让它们变成 HTTPS 的通信协议。

启用 Elasticsearch 的 HTTPS

那么我们怎样才能让 ES 启用 HTTPS 通信呢?我们需要进行以下的配置即可:

# http 启用 ssl 通信
xpack.security.http.ssl.enabled: true
# ssl 需要的证书
xpack.security.http.ssl.keystore.path: certs/elastic-certificates.p12
xpack.security.http.ssl.truststore.path: certs/elastic-certificates.p12

下面我们演示一下:

  1. 重启 ES,并且启动 HTTPS 通信

    bin/elasticsearch -E node.name=node0 -E cluster.name=john -E path.data=node0_data -E http.port=9800 -E transport.port=9900 -E xpack.security.http.ssl.enabled=true -E xpack.security.http.ssl.keystore.path=certs/elastic-certificates.p12 -E xpack.security.http.ssl.truststore.path=certs/elastic-certificates.p12
    
  2. 此时在通过http://myecs.com:9800的 http 协议进行通信,无法访问

    image-20200501155641320

  3. 换成https://myecs.com:9800,可以访问到服务器,但是显示如下。

    image-20200501155759849

    为什么会这样呢?我们来看下 HTTPS 通信的过程:

    • 用户在浏览器输入 URL 并按下回车

    • 浏览器从 DNS (在本例中是本地 DNS)解析得到域名对应的 ip

    • 浏览器作为本地 PC 中的一个应用程序向其所在的本地 PC 的网络接口发出一个 HTTP 请求报文

    • 本地 PC 的网络接口将 HTTP 请求报文拆开后发现是一个 HTTPS 请求,便在建立 TCP 通道的时候使用 SSL 协议建立连接。

      所谓 SSL 通信就是基于 TCP 协议通信的双方对于每个 TCP 数据报文的传输都要使用 RSA 算法进行加密。而 RSA 算法中的公私钥当然的就是来自于服务方,服务方基于 RSA 算法生成公钥和私钥,私钥自己保存在服务器,公钥在 TCP 通道建立的初始下发到客户端(这里就是浏览器所在PC)。

      从此服务方向客户端发送信息的时候使用私钥进行 RSA 算法对数据报文进行加密,客户端接收到之后使用公钥进行解密。客户端向服务方发送数据的时候使用公钥进行加密,服务方使用私钥进行解密。这个过程使得数据的安全性得到保障。(RSA 算法的特性是关键,不熟悉的需要自己再了解下)

    • 上面提到 SSL 通信在 TCP 通道建立的时候由服务方下发 RSA 公钥,但是这个过程有可能是有风险的。因为这个公钥是可能伪造的。假设一个用户在第一次向一个银行服务器发起存款的请求,但是在他使用正确的银行服务器的URL 发出 HTTPS 请求之后,在建立 SSL 通信的时候,服务方返回给客户端的报文被截取到了,非法之徒篡改了其中下发给客户端的 RSA 公钥,换成自己的然后保存真正服务方的公钥到本地,然后将报文返回给客户端,客户端收到响应之后将被替换了的公钥保存在本地,从此一直使用该公钥和服务端进行通信,而非法之徒一直截取客户端的报文,用自己的私钥解密出报文然后修改之后再用真正的服务方公钥进行对修改后的报文进行加密返回到服务方,这样客户端和服务方之间的通信就是不安全的了。

      所以这时候就需要一个具有权威的机构(Certificated Authority)来作为一个证书颁发机构,它将所有服务方的**唯一标识(可以直接使用ip)**和公钥生成一个标准(规范)的格式保存到自己的档案中,然后将这个证书颁发给服务方。服务方在和客户端建立 SSL 通信的时候将 RSA 公钥和证书的一些相关信息封装起来一起响应给客户端(包含证书的唯一标识),客户端在收到响应之后会将证书中的服务方的唯一标识和服务方的公钥(或者证书的唯一标识)对 CA 发起请求,CA 校验证书的有效性(在自己的档案中看是否存在这个证书,公钥是否一致)。浏览器收到 CA 的校验通过的响应之后就会正常保存该证书到本地然后和服务方建立通信。但是很明显,我们这里的证书是在上一节内容中自己使用 ES 的一个工具生成(签发)的,Safari 浏览器向 CA 发出校验后当然校验不通过。所以此时就发出了这样的提示,(因为像我们现在这样的开发场景确实有自己生成证书的需求):

      image-20200501163532027

      我们点击访问此网站即可:

      image-20200501163626248

      image-20200501163644515

  4. 上面演示了启用 ES 的 HTTPS 通信的方式以及简单介绍了 HTTPS 的原理。因为我们的 ES 服务器通常都是自己的内部使用的,不会开发给第三方用户,当我们有新的客户端要连接到 ES 服务器的时候,我们再使用一些通用工具(openssl)按照 CA 证书规范解析出证书中的公钥然后复制到客户端的指定目录即可,不需要向去像 CA 申请证书。

配置 Kibana 和 ES 建立 HTTPS 通信

我们在上面配置了 ES 的 HTTPS 通信,现在我们要用 Kibana来访问它,所以也要配置 Kibana 来和它进行 HTTPS 通信。

首先我们需要使用openssl这个工具来解析我们之前使用 ES 签发的那个证书并生成一个 Kibana 需要的公钥格式然后保存到一个专门保存安全信息相关的目录(和 ES 一样我们在${Kibana_Home}/config 下面创建一个 certs 目录)。(我们前面提到,对于浏览器来说,它是向服务端发起建立 SSL 的请求后,服务端按照标准的规范在数据报文中填写自己的公钥和证书相关信息,浏览器也按照规范来取出这些信息,之后可以按照自己的格式存放这些信息到自己本地,也可以按照规范来存储,而这里对于 Kibana 来说,它应该也是按照某种规范来的,毕竟 openssl 是一个通用标准工具,它输出的信息也应该是标准的,但是是不是和浏览器的一样就不清楚了)

过程中可能要我们输入elastic-certificates.p12的密码,我们前面设置了是空密码,回车即可。

[elasticsearch@izwz920kp0myp15p982vp4z kibana-7.6.2-linux-x86_64]$ mkdir ./config/certs
[elasticsearch@izwz920kp0myp15p982vp4z kibana-7.6.2-linux-x86_64]$ openssl pkcs12 -in ../elasticsearch-7.6.2/config/certs/elastic-certificates.p12 -cacerts -nokeys -out ./config/certs/elastic-ca.pem
Enter Import Password:
MAC verified OK

然后我们来修改 Kibana 的配置文件:

# 配置使用 https 协议通信
elasticsearch.hosts: ["https://localhost:9800"]
# 配置公钥文件目录,这里使用绝对路径,kibana 好像不支持相对路径
elasticsearch.ssl.certificateAuthorities: [ "/usr/local/software/elasticsearch/kibana-7.6.2-linux-x86_64/config/certs/elastic-ca.pem" ]
# 配置 ssl 校验等级
elasticsearch.ssl.verificationMode: certificate

启动 kibana

[elasticsearch@izwz920kp0myp15p982vp4z kibana-7.6.2-linux-x86_64]$ ./bin/kibana

可以看到是可以正常访问 ES 的

image-20200501171621353

配置 Kibana 使用 HTTPS

前面我们介绍了 ES 使用 HTTPS,现在我们介绍开启 KIbana 的 HTTPS 通信。 我们再使用 ES 的证书工具elsaticsearch-certutil生成一个压缩包elastic-stack-ca.zip,里面包含了一个证书实体ca.crt和一个私钥ca.key,我们将它解压之后将证书实体和私钥拷贝到 Kibana 的 config/certs 目录下

[elasticsearch@izwz920kp0myp15p982vp4z elasticsearch-7.6.2]$ bin/elasticsearch-certutil ca --pem
future versions of Elasticsearch will require Java 11; your Java version from [/usr/local/software/java/jdk1.8.0_231/jre] does not meet this requirement
This tool assists you in the generation of X.509 certificates and certificate
signing requests for use with SSL/TLS in the Elastic stack.

The 'ca' mode generates a new 'certificate authority'
This will create a new X.509 certificate and private key that can be used
to sign certificate when running in 'cert' mode.

Use the 'ca-dn' option if you wish to configure the 'distinguished name'
of the certificate authority

By default the 'ca' mode produces a single PKCS#12 output file which holds:
    * The CA certificate
    * The CA's private key

If you elect to generate PEM format certificates (the -pem option), then the output will
be a zip file containing individual files for the CA certificate and private key

Please enter the desired output file [elastic-stack-ca.zip]:
[elasticsearch@izwz920kp0myp15p982vp4z elasticsearch-7.6.2]$ mv ./elastic-stack-ca.zip ../kibana-7.6.2-linux-x86_64/config/certs/
[elasticsearch@izwz920kp0myp15p982vp4z elasticsearch-7.6.2]$ cd ../kibana-7.6.2-linux-x86_64
[elasticsearch@izwz920kp0myp15p982vp4z kibana-7.6.2-linux-x86_64]$ unzip ./config/certs/elastic-stack-ca.zip
Archive:  ./config/certs/elastic-stack-ca.zip
   creating: ca/
  inflating: ca/ca.crt
  inflating: ca/ca.key
[elasticsearch@izwz920kp0myp15p982vp4z kibana-7.6.2-linux-x86_64]$ mv -f ./ca/* ./config/certs/
[elasticsearch@izwz920kp0myp15p982vp4z kibana-7.6.2-linux-x86_64]$ ll ./config/certs/
总用量 16
-rw-rw-r-- 1 elasticsearch elasticsearch 1200 5月   1 17:19 ca.crt
-rw-rw-r-- 1 elasticsearch elasticsearch 1679 5月   1 17:19 ca.key
-rw-rw-r-- 1 elasticsearch elasticsearch 1397 5月   1 17:02 elastic-ca.pem
-rw------- 1 elasticsearch elasticsearch 2514 5月   1 17:19 elastic-stack-ca.zip

然后修改 kibana 配置文件

# 启用 https
server.ssl.enabled: true
# 设置证书实体
server.ssl.certificate: /usr/local/software/elasticsearch/kibana-7.6.2-linux-x86_64/config/certs/ca.crt
# 设置私钥
server.ssl.key: /usr/local/software/elasticsearch/kibana-7.6.2-linux-x86_64/config/certs/ca.key

然后启动 kibana

[elasticsearch@izwz920kp0myp15p982vp4z kibana-7.6.2-linux-x86_64]$ ./bin/kibana

此时使用 HTTP 已经无法访问了。

image-20200501173722220

使用 https

image-20200501173807997

成功

image-20200501173846340

相关阅读

https://www.elastic.co/guide/en/elasticsearch/reference/current/configuring-tls.html#tls-http

水平扩展 Elasticsearch 集群

在 ES 中根据不同功能聚合成了不同的节点类型:Master eligible、data、ingest、coordinating、machine learning。

在开发环境中,通常是一个 ES 实例承担多种角色。而在生产环境中,我们往往需要根据数据量,写入和查询的吞吐量,选择合适的部署方式,建议设置ES 实例为一个单一角色的节点(dedicated node)。这样做我们可以使得分配硬件资源的时候粒度更细,充分提高资源利用率 :

  • Dedicated master eligible node:负责集群状态(cluster state)的管理

    使用低配置的 CPU、RAM 和磁盘

  • Dedicated data nodes:负责数据存储及处理客户端请求

    使用高配置的 CPU、RAM 和磁盘啊

  • Dedicated ingest nodes:负责数据处理

    使用高配置 CPU、中等配置的 RAM、低配置的磁盘

  • Dedicated coordinating nodes:负责请求转发以及结果汇总(聚合)

    使用中\高配 CPU、中\高配 RAM、低配置的磁盘

节点参数配置

一个节点在默认情况下会同时扮演:mastetr eligible、data node 和 ingest node

image-20200501180720169

我们可以通过以下配置实现让节点职责单一化

image-20200501180758254

Dedicated Coordinating Only Node(Client Node)

将 master、data、ingest 都配置成 false。

在生产环境中,建议为一些大的集群配置 coordinating only nodes

  • 扮演 Load Balancers;同时降低 Master 和 Data Ndoes 的负载。
  • 负责搜索结果的 Gather 和 Reduce。
  • 有时候无法阈值客户端会发送怎样的请求,例如大量占用内存的聚合操作,一个深度聚合可能会引发 OOM,导致 data node 或者 master node 这样的重要节点宕机。

Dedicated Master (Eligible) Node

从高可用以及避免脑裂的角度出发

  • 一般在生产环境中配置3台,当 master 节点丢失的时候,其他 eligible 节点顶上,保证集群高可用
  • 一个集群只有1台活跃的主节点,负责分片管理、索引创建、集群管理等操作

如果和数据节点或者 Coordinating 节点混合部署,可能会有以下问题:

  • 数据节点相对有比较大的内存占用
  • coordinating 节点有时候可能会有开销很高的查询,导致 OOM
  • 这些都有可能影响 Master 节点,导致集群的不稳定

几种集群部署方式

下面我们将介绍几种 ES 集群的部署方式

1、基本部署:增加节点,水平扩展

当磁盘容量无法满足需求的时候,可以增加数据节点;磁盘读写压力大的时候,增加数据节点。

image-20200501182108857

2、水平扩展:Coordinating Only Node

当系统中有大量的复杂查询及聚合查询的时候,增加 Coordinating 节点,增加查询的性能。另外我们可以在 coordinating nodes 前面部署一个 Load Balancer,这个 Load Balancer 可以由硬件或者软件来实现。

image-20200501182157034

3、读写分离

有一些场景下,需要对写入 ES 的数据做一些比较频繁或者比较复杂的预处理工作,我们需要单独设置一些 Ingest 节点来做这样的工作,将读和写分离开来,根据各自的流量的情况来配置不同的硬件。

image-20200501182436402

4、在集群中部署 Kibana

当我们需要部署 Kibana 来访问 ES ,官方建议我们将 Kibana 直接部署在 Coordinating Nodes 上,在 Coordinating nodes 前面不是一个 LB,即可实现 Kibana集群的高可用。

image-20200501182451714

5、异地多活的部署

在某些情况下,我们拥有多个数据中心,有多个 ES 集群分别分散在这些数据中心上,为了保障更高层面(影响面更大)可能出现的问题(例如交换机、路由器出问题)下,当一个数据中心发生不可用的时候,其他数据中心还能正常的工作。

  • 一方面,我们可以通过数据多写(即写入数据的时候同时写入到多个中心集群中)实现各中心数据一致性。(这种一般是各中心无法通信的情况)
  • 另一方面,可以使用 ES 提供的 Cross-cluster replication来实现数据一致性,不过它是 X-Pack 中的内容,不知道是否需要收费。

对于应用层的读取操作,可以部署一个负载均衡设备到应用层和各数据中心的 ES 之间进行读取操作的 Load Balance。

GTM(Global Traffic Manager):一种负载均衡

image-20200501182509269

Hot & Warm 架构与 Shard Filtering

上一节介绍到,我们通常可以对 ES 实例设置为单一职责的节点来降低部署的成本。一般情况下,我们对于数据节点的配置都是一样的:

image-20200501185422635

Hot & Warm Architecture

有一些索引数据是 Time based(生命周期管理:随着时间的流逝,旧索引一般不会有更新操作,查询也会变少),同时整体的数据量比较大的场景。这时候我们就会引入 Hot & Warm 架构。

image-20200501185708786

注意,我们本节讨论的 Hot & Warm 架构方案仅仅基于 Time based 类型的索引的场景,即索引的访问流量随时间的流逝而越来越少,它是有一个变化过程的。以下两种情况不适合使用本节讨论的方案:

  1. 如果索引一直都是高负载或者一直是低负载是不需要这种架构的,直接一直使用高配置机器或者一直使用低配置机器即可。

  2. Hot & Warm 架构的使用场景其实可以抽象成数据的访问和操作流量会呈现出一些比较有特征的差异,这时候我们可以根据这些特征来配置不同的硬件来部署不同流量程度的数据节点以存放不同的数据,从而进一步提高资源的利用率。这里的有特征的差异主要分两种:

    • 非 time based:这些差异是一直存在的,例如订单数据是肯定访问流量很大的,而一些配置项数据访问流量肯定是很少的,那么我们可以肯定的对订单数据节点使用高配置的(Hot)节点,对配置项数据使用低配置的(Warm)节点,并且这样的配置是不会变的。
    • time based:随着时间流逝才会显示出差异,即我们在一开始的时候需要对这些数据用 Hot 节点,随着时间的流逝使用 Warm 节点。例如最近3个月内的订单访问量肯定很大,所以我们对这3个月内的订单数据在一开始使用配置很高的节点存储,到了3个月之后,这些订单的访问量慢慢下来了,此时我们需要将这些订单数据转移到低配置的节点上进行存储,并将之后的对于这些订单数据的操作都路由到该低配节点上。本节主要讨论的是这种情况下的 Hot & Warm 架构。

这种架构将数据节点分为两类,分别使用不同的硬件配置:

  • Hot 节点(通常使用 SSD):索引有不断的新文档写入和读取
  • Warm 节点(通常使用大容量的 HDD):索引不存在新数据的写入,同时也不存在大量的数据查询

Hot Nodes

主要用于处于 Time based 索引数据的早期阶段存在大量文档写入(以及读取)的场景,对 CPU 和 IO 都有很高的要求,所以需要使用高配置的机器(SSD)。

image-20200501192633355

Warm Nodes

主要用于处于 Time based 索引数据的晚期阶段有很少的数据更新(或者仅仅只读)的情况(俗称比较旧/老的数据),通常使用大容量的磁盘(通常是 Spinning Disks)

image-20200501192959006

Hot & Warm Architecture 示例

ES 对于 Hot & Warm 架构的实现主要依赖于它的"shard filtering",步骤分为以下几步:

  1. 预先分别为 Hot & Warm 节点分配好硬件资源,部署 ES 节点的时候根据其是 Hot节点还是 Warm 节点进行标记(Tagging)
  2. 在 Time based 索引的早期阶段,我们配置该索引的访问操作都路由到 Hot 节点
  3. 到了 Time based 索引的晚期阶段,我们配置该索引搬迁到 Warm 节点上,并配置转移对于该索引的访问操作到 Warm 节点上

下面我们来看一个演示。

1、标记 Hot & Warm 节点

ES 对于节点的配置提供了一个"node.attr"来允许我们灵活地为一个 ES 节点配置attributes,可以是一个任意的 key/value。可以通过 elasticsearch.yml 配置或者通过-E命令选项来指定。在这里我们就利用这个特性来对节点进行 Hot 和 Warm 的标记,如以下命令所示,我们指定了一个"my_node_type"的属性来标记一个节点是 Hot 还是 Warm 节点:

# 标记一个 Hot 节点
bin/elasticsearch  -E node.name=hotnode -E cluster.name=geektime -E path.data=hot_data -E node.attr.my_node_type=hot -E http.port=9800 -E transport.port=9900

# 标记一个 warm 节点
bin/elasticsearch  -E node.name=warmnode -E cluster.name=geektime -E path.data=warm_data -E node.attr.my_node_type=warm -E http.port=9801 -E transport.port=9901

elasticsearch.yml 配置

network.host: 0.0.0.0
#  config discovery for master node
discovery.seed_hosts: ["0.0.0.0:9900"]
cluster.initial_master_nodes: ["hotnode"]

在启动 Hot & Warm 节点之后,我们可以通过以下命令查询所有节点上的属性键值

# 查看节点
GET /_cat/nodeattrs?v

image-20200501200950243

2、Time based 索引的早期将其路由到 Hot 节点

我们通过设置索引settings 中的index.routing.allocation.require指向我们配置的属性和属性值,那么 ES 在为该索引分配分片的时候,会根据当前分片的所属节点是否拥有"my_node_type"的属性并且值为"hot"对待路由的节点进行过滤,如果没有找到拥有属性对"my_node_type=hot"的节点,那么分配分片失败,集群状态为"red"

# 配置到 Hot节点
PUT logs-2019-06-27
{
  "settings":{
    "number_of_shards":2,
    "number_of_replicas":0,
    "index.routing.allocation.require.my_node_type":"hot"
  }
}

往索引中写入数据

PUT logs-2019-06-27/_doc/1
{
  "key":"value"
}

查看分片情况:可以看到索引logs-2019-06-27虽然在 settings 中被我们设置了主分片数量是2,并且在集群中存在两个节点的情况下,依然都分配到了 hotnode 这个节点上,而我们刚刚写入的文档数据被路由到了hotnode 节点的分片0上

GET _cat/shards?v

image-20200501203448182

3、Time based 索引晚期将其转移到 Warm 节点

随着时间的推移,现在索引logs-2019-06-27的访问量慢慢下来了(日志当然也不会有更新操作),我们将它转移到 warm 节点上。

可以看到,我们再次对index.routing.allocation.require.my_node_type设置为"warm",ES 就会为我们在"标签"为 warm 的节点上为索引logs-2019-06-27创建两个分片并将索引中的现有数据进行迁移到 warmnode 上,然后删除 hotnode 上的相关分片信息

# 配置到 warm 节点
PUT logs-2019-06-27/_settings
{  
  "index.routing.allocation.require.my_node_type":"warm"
}
# 查看转移后的分片信息
GET _cat/shards?v

image-20200501204427674

可以看到,上面的 index routing reallocate 其实分为在其他节点上创建分片、迁移索引、删除当前节点上分片等过程,那么这个过程如果会持续比较久会不会影响用户对于该索引的访问呢?这个待研究。

另外,以上提到的 Hot & Warm 架构是对基于 TIme based 数据的方案,一般需要对这些数据按照不同时间段建立索引(如果所有数据都建立在一个索引上,以上的方案就不适合了,需要自己通过reIndex API把"老"的数据进行迁移然后通过deleteByQuery进行当前节点上的数据删除。会比较繁琐,同时不高效),然后在当前时间到达索引的期限后对索引进行迁移到 warm 节点上,并灵活结合 alias (一个 alias 映射到所有时间区间的索引即可让外部访问不受影响)一起使用。

Rack Awareness

ES 的节点在默认情况是可能分配在不同的机架上,也可能分配在同一个机架上的,没有限制。如果ES 的一个主分片及其所有的副本分片都被分配到了同一个机架上的时候,一旦这个机架断电,就会可能导致整个分片数据丢失。(例如下面的P0和 R0主副分片都在同一个机架 Rack1上)

image-20200501210235020

我们可以通过 Rack Awareness 的机器,可以尽可能避免将同一个索引的主副分片同时分配在一个机架上。其实现和前面的 Hot & Warm 的实现有异曲同工的地方。

  1. 我们在启动 ES 节点的时候通过给它自定义一个属性为机架编号,并给它设置相应的机架编号值进行打标。

    # 标记在 rack1 机架
    bin/elasticsearch  -E node.name=hotnode -E cluster.name=geektime -E path.data=hot_data -E node.attr.my_rack_id=rack1 -E http.port=9800 -E transport.port=9900
    
    # 标记在 rack2 机架
    bin/elasticsearch  -E node.name=warmnode -E cluster.name=geektime -E path.data=warm_data -E node.attr.my_rack_id=rack2 -E http.port=9801 -E transport.port=9901
    
  2. 然后通过设置 _cluster 层级的 settings 中的persistent.cluster.routing.allocation.awaerness.attributes为我们前面设置的"机架属性",那么 ES 在后面对索引进行分片分配的时候就会避免将同一个索引的主副分片都分配到属性"my_rack_id"是同一个值的节点上(即同一个机架上)

    # 设置 ES 在给索引分配分片的时候要将主副分片按照节点的"my_rack_id"的值进行隔离
    PUT _cluster/settings
    {
      "persistent": {
        "cluster.routing.allocation.awareness.attributes": "my_rack_id"
      }
    }
    # 设置索引为两个主分片,一个副本分片
    PUT my_index1
    {
      "settings":{
        "number_of_shards":2,
        "number_of_replicas":1
      }
    }
    # 查看分片信息,可以看到同一个索引的主副本分片被分配到了位于不同 rack 的节点上
    get _cat/shards?v
    

    image-20200501212831365

  3. 以上的设置和配置项中的字面(awaerness)意思一样,它不是强制的,仅仅是让 ES 留意到这一点,如果集群中存在有不同"my_rack_id"属性值的节点,ES 会尽量将主副分片散步到不同 rack 上。所以当我们只启动两个"my_rack_id"为"rack1"的节点的时候,其实分片还是可以分配成功的(当然,如果我们只启动一个节点,副本分片就根本无法分配了,因为没有其他节点可备份了,集群显黄色),都分配到了机架 rack1的节点上。

image-20200501214501354

如果我们实在是想强制 ES 不能将主副分片都分配到同一个机架上来对管理员起到一个警告作用,那么可以通过增加一个cluster.routing.allocation.awareness.force.my_rack_id.values设定来实现:

# 覆盖新设置
PUT _cluster/settings
{
  "persistent": {
    "cluster.routing.allocation.awareness.attributes": "my_rack_id",
    "cluster.routing.allocation.awareness.force.my_rack_id.values": "rack1,rack2"
  }
}
# 删除索引
DELETE my_index1
# 重新建立索引
PUT my_index1
{
  "settings":{
    "number_of_shards":2,
    "number_of_replicas":1
  }
}
# 查看分片情况
get _cat/shards?v

可以看到下面的截图中虽然集群中存在两个节点,但是因为都是"rack1"中的节点,所以副本分片都是无法分配的状态,集群显黄色

image-20200501214926484

#  查看集群状态颜色
GET _cluster/health

image-20200501213701284

通过以下命令可以看到详细的信息

GET _cluster/allocation/explain?pretty

image-20200501215007128

Shard Filtering

可以看到我们通过 ES 提供的 Shard Filtering 可以实现对索引进行节点分配以及对分片进行节点分配的前置筛选动作。下面是 Shard Filtering 的一些总结:

  • 通过"node.attr"来标记节点
  • 通过"index.routing.allocation"指定索引分配到哪些节点

image-20200501215420918

相关阅读

https://www.elastic.co/cn/blog/sizing-hot-warm-architectures-for-logging-and-metrics-in-the-elasticsearch-service-on-elastic-cloud

https://www.elastic.co/cn/blog/deploying-a-hot-warm-logging-cluster-on-the-elasticsearch-service

分片设定及管理

单个分片

在7.0开始,ES 在新创建一个索引的时候,默认只有一个主分片,单个分片对于查询算分,聚合不准的问题都可以得以避免。但是如果随着索引中的数据量越来越大的时候,节点压力就会越来越大,这时候即使我们加入一个新的节点,也不能分担压力,因为只有一个分片,此时我们可能需要创建新的索引重新设置分片数进行 reindex。

image-20200501220732045

两个分片

但是如果我们在新建索引的时候,指定了两个及以上的分片,那么在索引数据越来越大之后,我们加入一个新的节点,那么 Elasticsearch 会自动进行分片的移动,也叫 Shard Rebalancing。

image-20200501220840158

如何设计分片数

当分片数大于节点数的时候,一旦集群中有新的数据节点加入,分片就可以自动进行分配。分片在重新分配的时候,系统也不会有 downtime。同时,及时是在数据量不是很大的情况,也给索引设置不同的分片并且分片分布在不同的机器节点上就可以使得查询可以并行执行,数据写入也可以分布到多个机器上。下面是一些设计分片的例子:

  • 案例1:每天1GB 的数据,一个索引一个主分片,一个副本分片,需要保留半年的数据,接近360GB 的数据量。
  • 案例2:5个不同的日志,每天创建一个日志索引,每个日志索引创建10个主分片,保留半年的数据,5 * 10 * 30 * 6 = 9000 个分片。

分片过多所带来的副作用

每个分片是一个 Lucene 的索引,会使用机器的资源。过多的分片会大致额外的性能开销。

  • Lucene Indices、File descriptors、RAM、CPU
  • 每次搜索的请求,需要从每个分片上获取数据
  • 分片的 Meta 信息由Master 节点维护。过多的分片会增加管理的负担。经验值是空值分片总数在10W 内。

如何确定主分片数

从存储的物理角度看

  • 日志类应用,单个分片不要大于50GB
  • 搜索类应用,单个分片不要超过20GB

为什么要控制分片存储大小

  • 提高update 的性能
  • merge 时,减少所需的资源
  • 丢失节点后,具备更快的恢复速度,便于分片在集群内 rebalancing

如何确定副本分片数

副本时主分片的拷贝,它能提高系统可用性,防止数据丢失,但是需要占用和主分片一样的资源。

对性能的影响:

  • 副本会降低数据的索引速度,有几份副本就会有几倍的 CPU 资源消耗在索引上
  • 会减缓对主分片的查询压力,但是会消耗同样的内存资源。

如果机器资源充分,提高副本数,可以提高整体的 QPS。

调整分片总数设定避免分配不均衡

ES 的分片策略会尽量保证节点上的分片数量大致相同,举个例子,某些情况下,现有的所有节点的负载几乎都满了,当我们加入一个新的节点之后,ES 开始 rebalancing,但是有有可能都是基于各个节点上的分片数来进行 rebalancing 的,不考虑分片上的数据量,如果有几个分片的数据量特别大,刚好都分布在同一个节点上,那么虽然 rebalancing 之后分片数量是均匀的,但是实际上某些节点上的数据量是很大的;或者说有些分片上存储的是热点数据,即使是 rebalancing 之后,如果 ES 继续按照节点上的分片数进行 rebalancing,后续还是可能会出现热点数据集中在某些节点上的问题。此时我们可以通过以下一些设定来进行干预:

image-20200501222848669

相关阅读

https://www.elastic.co/guide/en/elasticsearch/reference/7.1/cluster-reroute.html

https://www.elastic.co/guide/en/elasticsearch/reference/7.1/indices-forcemerge.html

https://www.elastic.co/guide/en/elasticsearch/reference/current/allocation-total-shards.html

如何对集群进行容量规划

容量规划

首先需要考虑保持一定的余量,当负载出现波动,节点出现丢失的时候,还能正常运行。在做容量规划的时候,一些需要考虑的因素:

  • 机器的软硬件配置
  • 单条文档的尺寸、文档的总数据量、索引的总数据量(Time based 数据保留的时间)、副本分片数
  • 文档是如何写入的(build 的尺寸)
  • 文档的复杂度、文档是如何进行读取的(查询和聚合的复杂度)

评估业务的性能需求

数据吞吐及性能需求

  • 数据写入的吞吐量,每秒要求写入多少数据
  • 查询的吞吐量
  • 单条查询可接收的最大返回时间

了解你的数据

  • 数据的格式和数据的 Mapping
  • 实际的查询和聚合长的是什么样的

常见用例

简单的来说我们可以将 ES 的应用分为两大类:

  • 搜索类应用:固定大小的数据集,搜索的数据集增长相对比较缓慢
  • 日志类应用:基于时间序列的数据。使用 ES 存放日志与性能指标。数据每天不断写入,增长数据较快。结合 Warm Node 做数据的老化处理

硬件配置

所有的数据节点尽可能使用 SSD:

  • 在搜索类应用或者性能要求高的场景,使用 SSD,按照1:10的比例配置内存和硬盘
  • 日志类和查询并发低的场景,可以考虑使用机械硬盘存储,按照1:50的比例配置内存和硬盘

单节点数据建议控制在2TB 以内,最大不建议超过5TB

JVM 只占机器内存的一般,JVM 内存配置不建议超过32G

部署方式

前面章节提到了多种部署方式,我们需要根据实际场景进行部署方式的选择:

  • 如果对于写入的数据进行一些大量的 pipeline 处理,可以配置一些 dedicated 的 ingest node,并为其配置相应的写操作 LB。
  • 如果数据节点超过了3台,一般建议配置 dedicated 的 master 节点
  • 如果需要考虑高可用,在使用了 dedicated 的 Master 节点的情况下一般部署3台 dedicated mater nodes(防止脑裂)
  • 如果有复杂的查询和聚合,建议设置 coordinating 节点

容量规划案例1:固定大小的数据集

一些案例:唱片信息库/产品信息

一些特性:

  • 被搜索的数据集很大,但是增长相对比较慢(不会有突然大量的写入)。更关心搜索和聚合的读取性能
  • 数据的重要性和时间范围无关,关注的是搜索相关度

估算索引的数据量,然后确定分片的大小

  • 前面提到,搜索类应用单个分片的数据不要超过20GB
  • 可以通过增加副本分片,提高查询的吞吐量

拆分索引

如果业务上有大量的查询是基于一个字段进行 filter,该字段又是一个数据有限的枚举值,存在大量的数据在该字段有着重复的枚举值,例如订单所在的地区,可以考虑将索引按照该字段的枚举值拆分成多个索引,使得这些数据可以分配在更多的分片上,这样查询性能可以得到提高;如果要对多个索引进行查询,可以在查询中指定多个索引来实现。

如果业务上大量的查询是基于一个字段进行 filter,该字段数值并不固定,可以启用 routing 功能,按照 filter 字段的值分布到集群不同的 shard,降低查询时相关的 shard,提高 CPU 利用率。

容量规划案例2:基于时间序列的数据

相关的案例:日志、指标、安全相关的 Events;舆情分析

一些特性:

  • 每条数据都有时间戳,文档基本不会被更新(日志和指标数据)
  • 用户更多的会查询近期的数据,对旧数据查询比较少
  • 对数据的写入性能要求比较高

创建基于时间序列的索引

  • 在索引的名字中增加时间信息
  • 按照每天、每周、每月的方式进行划分

这使得更加合理的组织索引,例如随着时间的推移,便于利用 Hot & Warm Architecture 对索引做老化处理,备份和删除的效率高。(因为在 ES 中如果想删除整个索引速度时较快的,而Delete By Query 执行速度慢,底层的 lucene 的 segment 也不会立即释放空间,而 Merge 时又很消耗资源)

  1. 基于ES 提供的 Date Math 的方式写入时间序列的数据:Date Math 是 ES 提供的日期运算表达式,容易使用,但是如果时间的规则发生变化,就要重新部署代码。

    image-20200501231625802

    我们使用的时候记得对里面的特殊符号进行 URI 编码:

    下面是获取当前日期

    image-20200501232252328

    下面是获取本周第一天

    image-20200501232300143

    详情可以参考官方文档。

  2. 基于 Index Alias 写入时间序列的数据:每当有新的时间索引创建的时候,将它加入到对应的 alias 映射列表中

    image-20200501232010939

集群监控和扩容

即使我们做了容量规划,但是还是需要在上线之后监控集群的使用状况。

对于 Coordinating 和 Ingest nodes,主要监控 CPU 和内存开销的问题。

对于 Data nodes,都需要监控,硬盘比较主要。需要及时解决发现的存储容量问题,为避免分片不均的问题,要提前监控磁盘空间,提前清理数据或者增加节点。(70%)

相关阅读

https://www.elastic.co/guide/en/elasticsearch/guide/current/capacity-planning.html

https://yq.aliyun.com/articles/670118

在私有云上管理 Elasticsearch 集群

Elasticsearch 的安装是很方便的,但是如果在生产环境中管理一个集群,其实还是要做很多事情的。首先我们要对集群进行监控,当集群发现集群容量不够的时候,需要手工增加节点。在云环境中,节点的丢失很常见的,如果有节点丢失时,手工修复或者更换节点。同时我们可能还要留意 Rack Awareness 或者其他的 Shard Filtering,那么就需要为我们的节点分别打上不同的标签。Elasticsearch 的更新也比较频繁,对于集群版本的升级、数据备份、滚动升级,如果都手工操作,管理成本高,无法实现统一管理,例如整合变更管理等。

image-20200502080339509

ECE,帮助你管理多个 ELasticsearch 集群

Elasticsearch 其实有一款产品,叫做 ECE(Elastic Cloud Enterprise)。提供了一个 UI 界面,实现单个控制台,管理多个集群:

  • 支持不同方式的集群部署(支持各类部署)、跨数据中心、部署 Anti Affinity
  • 统一监控所有集群的状态
  • 图形化操作:
    • 增加删除节点
    • 集群升级、滚动更新、自动数据备份

image-20200502080934782

image-20200502080949174

image-20200502081007384

image-20200502081019722

基于Kubernetes 的方案

近些年容器化的技术越来越热门了,很多公司也将自己的基础设施往容器上迁移。ELasticsearch 也在今年退出了基于 K8S 管理的方案。它同时提供了基础级和企业级,前者是免费下载的。

  • 基于容器技术,使用 Operator 模式进行编排管理
  • 配置,管理,监控多个集群
  • 支持Hot & Warm
  • 数据快照和恢复

image-20200502081137159

Kubernertes CRD

其实基于 K8S 的部署,简单来说,就是为ES 定义一个K8S 的配置文件,只需要在这个 yml 文件里面定义所需 ES 的版本,所需 Nodes 的总数,ES Operator 就会帮助你把集群部署起来了。

image-20200502081653827

构建自己的管理系统

上面是 ELastic公司提供的容器管理方案,如果自己在公司中开发一套实现容器编排的系统,大概的实现思路:

  1. 基于虚拟机的编排管理方式:Elasticsearch 还开源了一个 Puppet mdule,利用这个 Puppert module 可以基于 Puppert Infrastructure (Puppet / Elasticsearch Puppert Module / Foreman) 构建一个 Wrokflow based Provision & Management。
  2. 基于 Kubernetes 的容器化编排管理方式:K8S 提供了一种 Operator 的模式,帮助对 K8S 实现一些扩展。我们可以基于 Operator 模式,通过Kubernetes - Customer Resource Definition来实现容器化编排管理。

以下是一个将 Elasticsearch 部署在 Kubernetes 上的一个示意图:

首先看一下我们需要在 Kubernetes 上做一个 Elasticsearch 做一个部署,应该怎么做呢?首先我们需要为 master 节点创建一个 Statefulset,为什么需要创建一个 Statefulset 呢?因为在7.0开始我们需要指定节点的个数,所以我们需要利用Statefulset 中的 pod 是有序的特性,来实现 Provision 的一个功能。同时我们会创建一个 Headless Service,实现集群内部的通信,同时对于 data 节点,我们会为他创建一个 Statefulset 的 Deployment。对于 Coordinating nodes,可以为他们创建 Stateless 的 Deployment。同时我们哈可以使用 K8S 的Service 为集群创建 Read 和 Write 的 LB。

image-20200502082800507

什么是 Kubernetes Operator

我们看一下怎么利用 K8S 的 Operator 管理一个集群,简单来说,我们需要先创建一个"spec",在这个 spec 里面描述了我们需要节点的数量,以及我们需要对集群进行怎样的部署。

image-20200502083332535

社区上的 Operator SDK

社区上也提供了很多的 Operator SDK,方便我们编写自己的 operator,实现在 K8S上对集群的管理:https://github.com/operator-framework/operator-sdk

image-20200502083611096

相关阅读

https://www.elastic.co/cn/blog/introducing-elastic-cloud-on-kubernetes-the-elasticsearch-operator-and-beyond?elektra=products&storm=sub1

https://www.elastic.co/blog/introducing-elastic-cloud-on-kubernetes-the-elasticsearch-operator-and-beyond

https://github.com/operator-framework

https://github.com/upmc-enterprises/elasticsearch-operator

在公有云上管理与部署 Elasticsearch

Elastic 本身就有一个云服务 Elastic Cloud,另外它在国内和阿里云、腾讯云都有合作,我们可以有需要可以尝试在这些云上部署我们的 Elasticsearch 集群。

Elastic Cloud

只要提供一个邮箱,在验证并登录之后即可进行一个免费的试用。

image-20200502084627149

image-20200502084750561

image-20200502084951100

点击 quick deployment

image-20200502085106456

image-20200502085206492

编辑集群

image-20200502085326546

image-20200502085404205

image-20200502085519162

点击 Activity 查看集群的所有变更

image-20200502085607888

还可以通过 performance 监控集群的一些指标

image-20200502085823159

阿里云

https://www.aliyun.com/product/bigdata/product/elasticsearch

https://www.elastic.co/cn/blog/elasticsearch-service-on-elastic-cloud-introduces-new-pricing-with-reduced-costs

腾讯云

根据客户是国外还是国内选择不同的云

生产环境常用配置与上线清单

从 ES5.0开始,支持 Development 和 Production 两种运行模式。ES 是通过 http.port 和 transport.bind_host (这个现在好像是 transport.host?反正就是检测 ES 绑定的地址是否是本地回环地址)两个参数来判断是什么模式:

  • 开发模式

    image-20200502091542377

  • 生产模式

    image-20200502091602629

Bootstrap Checks

一个集群在 Production Mode 的时候,启动时必须通过所有的 Boostrap 检测,否则会启动失败。Bootstrap checks 可以分为两类:JVM & Linux Checks(前提是在 linux 系统上运行)。

image-20200502091741488

boostrap 检查清单:https://www.elastic.co/guide/en/elasticsearch/reference/7.1/bootstrap-checks.html

JVM 设定

从 ES 6 开始,只支持64位的 JVM,通过配置${ES_HOME}/config/jvm.options实现 JVM 参数配置。避免修改默认配置:

  • 将内存 Xms 和 Xmx 设置成一样,避免 heap resize 时引发停顿。
  • Xmx 设置不要超过物理内存的50%(剩下的50%交给 lucene 实现全文检索);单个节点上,最大内存建议不要超过32G内存(因为 JVM 在小于32G 的时候,会使用指针压缩提高性能)(https://www.elastic.co/blog/a-heap-of-trouble
  • 生产环境,JVM 必须使用 Server 模式
  • 关闭 JVM Swapping

集群的 API

静态设置和动态设定:

  • 静态配置文件elasticsearch.yml 尽量简洁:按照文档设置所有相关系统参数,配置文件中尽量只写必备参数。
  • 其他的设置项可以通过 API 动态进行设定。动态设定分transient 和 persistent 两种,都会覆盖 elasticsearch.yml 中的设置。前者在集群重启后会丢失,后者不会。

image-20200502092804094

系统设定

前面提到,ES 在生产环境的 boostraps 的时候除了对 JVM 进行一些检查,如果是在 linux 上启动还会对 linux 系统做一些检查。(如:disable swapping、increase file descrriptor、虚拟内存、number of thread 等等的设定)

系统设置参考文档:“Setup Elasticsearch > Important System Configuration

boostrap 检查清单:https://www.elastic.co/guide/en/elasticsearch/reference/7.1/bootstrap-checks.html

最佳实践:网络

  • 单个集群不要跨数据中心进行部署(不要使用 WAN)

  • 节点之间的 hops (时延)越少越好

  • 如果有多块网卡,最好将 transport 和 http 绑定到不同的网卡,并设置不同的防火墙 rules

  • 按需为 coordinating node 或 ingest node 配置负载均衡

最佳实践:内存设定计算实例

内存大小除了首先要预留50%之外,还要根据 Node 需要存储的数据(和磁盘的比例)来进行估算

  • 搜索类的比例建议:1:16
  • 日志类:1:48-1:96之间

假设现有总数据量1T,设置一个副本=2T 总数据量

  • 如果是搜索类的项目,一个节点设置31G 内存,那么每个节点的磁盘存储就是31 * 16 = 496 G,(加上预留给 lucene 的31G空间 ),即每个节点最多400G 数据,至少需要5个数据节点才能使得磁盘达到2T 存储量。这时单节点内存总共需要是每个节点62G,5个数据节点为310G。
  • 如果是日志类项目,每个节点31 * 50 = 1550GB,2个数据节点即可达到磁盘容量。所以单节点内存为62G,整个集群2个节点为124G。

最佳实践:存储

  • 推荐使用 SSD,使用本地存储(Local Disk),避免使用 SAN NFS、AWS、Azure filesystem等网络存储。

  • 可以在本地指定多个"path.data",以支持使用多块磁盘

  • ES 本身提供了很好的 HA 机制,无需使用 RAID 1/5/10

  • 可以在 Warm 节点上使用 Spinning DIsk(机械硬盘),但是需要关闭 Concurent Mergs(Index.merge.scheduler.max_thread_count:1

  • Trem 你的 SSD(对于 SSD 的优化):https://www.elastic.co/blog/is-your-elasticsearch-trimmed

最佳实践:服务器硬件

  • 建议使用中等配置的机器,不建议使用过于强劲的硬件配置:Medium machine over large machine

  • 不建议在一台服务器上运行多个节点

最佳实践:Throttles 限流

为 Relocation 和 Recovery 设置限流,避免过多任务对集群产生的影响

  • Recovery:Cluster.routing.allocation.node_concurrent_recoveries:2
  • Relocation:Cluster.routing.allocation.cluster.cluster_concurrent_rebalance:2

集群设置:关闭 Dynamic Indexes

考虑关闭动态索引创建的功能,避免无意或者非法的跳过 mapping 建模进行过多索引创建

image-20200502095244011

或者通过模板设置白名单

image-20200502095301146

集群安全设定

  • 为 Elasticsearch 和 Kibana 配置安全功能

    • 打开 Authentication 和 Authorazatin
    • 实现索引和字段级的安全控制
  • 节点间通信加密

  • Enable HTTPS

  • Audit logs(日志审计)

相关阅读

https://www.elastic.co/guide/en/elasticsearch/reference/master/bootstrap-checks.html

https://www.elastic.co/blog/a-heap-of-trouble

https://www.elastic.co/guide/en/elasticsearch/reference/7.1/system-config.html

https://www.elastic.co/blog/is-your-elasticsearch-trimmed

监控 Elasticsearch 集群

Elasticsearch 提供了多个监控相关的 api,可以通过这些 api 来查看节点级别、集群级别、索引级别的一些指标:

  • node stat:_node/stats
  • cluster stat:_cluster/stats
  • index stats:index_name/stats

同时还提供了相关的 Task API:

  1. 查看 Task 相关的 API:
    • Pending Cluster Tasks API:GET _cluster/pending_tasks
    • Task Management API:GET _tasks(可以用来 Cancel 一个 Task)
  2. 监控 Thread Pools
    • GET _nodes/thread_pool
    • GET _node/stats/thread_pool
    • GET _cat/threaad_pool?v
    • GET _nodes/hot_threads

The Index & Query Slow Log(慢查询日志):

  • 支持将分片上,search 和 fetch 阶段的慢查询写入文件
  • 支持为 Query 和 Fetch 分别定义阈值(超过阈值的查询会写入日志文件)
  • 索引级的动态设置,可以按需设置,或者通过 iindex template 统一设定
  • Slow log 文件通过 log4j2.properties 配置

image-20200502100436437

如何创建监控 Dashboard

  • 开发 Elasticsearch plugin,通过读取相关的监控 API,将数据发送到 ES,或者 TSDB。
  • 使用 Metricbeats 搜索相关指标
  • 使用 Kibana 或者 Grafana 创建 Dashboard
  • 可以开发 Elasticsearch Exproter,通过 Prometheus 监控 Elasticsearch 集群

Kibana 相关请求

# Node Stats:
GET _nodes/stats

#Cluster Stats:
GET _cluster/stats

#Index Stats:
GET kibana_sample_data_ecommerce/_stats

#Pending Cluster Tasks API:
GET _cluster/pending_tasks

# 查看所有的 tasks,也支持 cancel task
GET _tasks


GET _nodes/thread_pool
GET _nodes/stats/thread_pool
GET _cat/thread_pool?v
GET _nodes/hot_threads
GET _nodes/stats/thread_pool


# 设置 Index Slowlogs
# the first 1000 characters of the doc's source will be logged
PUT my_index/_settings
{
  "index.indexing.slowlog":{
    "threshold.index":{
      "warn":"10s",
      "info": "4s",
      "debug":"2s",
      "trace":"0s"
    },
    "level":"trace",
    "source":1000  
  }
}

# 设置查询
DELETE my_index
//"0" logs all queries
PUT my_index/
{
  "settings": {
    "index.search.slowlog.threshold": {
      "query.warn": "10s",
      "query.info": "3s",
      "query.debug": "2s",
      "query.trace": "0s",
      "fetch.warn": "1s",
      "fetch.info": "600ms",
      "fetch.debug": "400ms",
      "fetch.trace": "0s"
    }
  }
}

GET my_index

诊断集群的潜在问题

集群运维所面临的挑战

  • 用户集群数量多,业务场景差异大

  • 存在使用与配置不当,优化不够的问题

    • 如何让用户更加高效和正确的使用 ES
    • 如何让用户更全面的了解自己的集群的使用状况
  • 发现问题之后,需要防患于未然

    • 需要"有迹可循",做到"有则改之,无则加勉"
    • Elastic 有提供 Support Diaagnostics Tool:https://github.com/elastic/support-diagnostics。完全基于 Java 语言开发的工具,当集群出现问题的时候,可以运行这个工具,收集集群的一些指标,帮助 ES 公司对一些问题作出诊断。
  • 监控指标多并且分散,指标啊的含义不够明确直观(集群的绿色状态只是其中一项指标,并意味着集群足够好了,其仅表示分片是否都已经正常分配)

  • 问题分析定位的门槛较高,需要具备非常多的专业知识

为什么要诊断集群的潜在问题

即便 ES 是在绿色状态,但是我们依然要经常对 ES 做一些指标的分析,防患于未然,避免集群奔溃

  • Master 节点或者数据节点宕机;负载过高,导致节点失联,都需要提前介入
  • 副本丢失,导致数据可靠性受损
  • 集群压力过大,数据写入失败

提升集群性能

  • 数据节点负载不均衡(避免单节点瓶颈)的时候需要优化分片(例如segments数量过多了,手动出发合并)
  • 规范操作方式(利用别名、避免 Dynamic Mapping 引发过多字段,对索引的合理性进行管控)

eBay Diagnostic Tools

  • 集群健康状态,是否有节点丢失
  • 索引合理性:索引总数不能过大、副本分片尽量不要设置为0、主分片尺寸检测、索引的字段总数(Dynamic Mapping 关闭)、索引是否分配不均衡、索引 Segment 大小诊断分析、数据节点之间的负载偏差是否过大、冷热数据分配是否正确(例如,Cold 节点上的索引是否设置成只读)
  • 资源使用合理性:CPU 内存和磁盘的使用状况分析、是否存在节点负载不平衡、是否需要增加节点
  • 业务操作合理性:集群状态变更频率,是否在业务高峰期有频繁操作;慢查询监控与分析

image-20200502102215609

阿里云:EYOU 智能运维工具

每天凌晨定时诊断,也可以自主诊断。每次诊断耗时3分钟。(https://help.aliyun.com/document_detail/90391.html

image-20200502102632163

诊断 Shard 数

image-20200502102738786

磁盘容量估算

image-20200502102754737

多维度检测,构建自己的诊断工具

image-20200502102814615

相关阅读

https://elasticsearch.cn/slides/162

https://yq.aliyun.com/articles/657712

https://yq.aliyun.com/articles/657108

https://help.aliyun.com/document_detail/90391.html

解决集群 Yellow 与 Red 的问题

分片健康:

  • 红:至少有一个主分片没有分配
  • 黄:至少有一个副本分片没有分配
  • 绿:主副本分片全部正常分配

image-20200502103351675

Health 相关的 API

API 描述
GET _cluster/health 集群的状态(检查节点数量)
GET _cluster/health?level=indices 所有索引的健康状态(查看有问题的索引)
GET _cluster/health/my_index 单个索引的健康状态(查看具体的索引)
GET _cluster/heaalth?level=shards 分片级的索引
GET _cluster/allocation/explain 返回第一个未分配 shard 的原因

image-20200502103808261

案例1:集群变红

我们在索引的创建初期可能因为一些错误的设置,导致集群无法进行分片分配。这个时候可以通过explain api 找到真正的原因,然后将刚创建的索引进行简单的删除,重新正确设置之后再创建即可。

  1. 集群初始状态为绿色

    image-20200502192747153

    image-20200502192806534

    集群上一共有3个节点

    image-20200502192857735

    image-20200502192904871

    查看节点属性,可以看到我们自定义了属性未 box_type,三个节点的值分别为:hot、warm、cold

    image-20200502192940381

    image-20200502193029343

  2. 我们现在来创建一个 index,创建之后好像 index 没有得到任何的返回

    image-20200502193209472

    image-20200502193247086

  3. 这时候再次尝试去查看集群状态,发现集群为红色

    image-20200502193320202

    image-20200502193332717

  4. 查看是哪一个索引导致集群变红。可以看到,就是刚刚创建的"mytest"索引

    image-20200502193421517

    image-20200502193444128

  5. 尝试使用 explain api 看一下为什么集群会变红,可以看到是因为没有节点是匹配"index.routing.allocation.require"一项的,因为 box_type 的值是"hott",设置索引 settings 的时候拼写错误了。

    image-20200502193546327

    image-20200502193657352

  6. 我们先删除索引

    image-20200502193809038

    再查看集群状况,可以看到变回绿色了

    image-20200502193834375

    image-20200502193842477

  7. 重新创建索引,并设置正确的属性值"hot"

    image-20200502193937703

    再次查询集群状况,可以看到是绿色的了。

    image-20200502194000876

    image-20200502194019636

案例2:集群变黄

  1. 先将前面创建的 mytest 索引删除,然后再重新设定

image-20200502194255230

  1. 查看集群状态,发现返回的是 yellow

image-20200502194323886

image-20200502194342547

  1. 再通过 explain api 查看具体的原因,可以看到是 mytest 的索引存在一个未分配的情况,通过查看具体的原因发现还是因为没有节点匹配 box_type 为 hot 的问题。但是这一次我们的配置是对的了。然后我们再查看集群的节点属性发现"hot"节点其实只有一个,但是我们设置索引 settings 的时候是为索引指定了一个副本分片的,而我们又设置了"require"box_type 为 hot 才能分配,那么这时候副本分片就找不到其他 hot 节点做备份了。所以处于无法分配的状态。

image-20200502194430079

image-20200502194510603

image-20200502194858284

image-20200502194536979

image-20200502194947659

image-20200502195002218

  1. 那么此时我们有两个解决方案:一个是增加一个 hot 节点使得可以正确分配一个副本分片;另外一个是重新设置索引的 settings 文件使得副本分片数量为0。

分片没有被分配的一些原因

  • index_create:在创建索引的时候,在索引的全部分片分配完成之前,会有短暂的 red,不一定代表有问题
  • cluster_recover:集群重启阶段,会有这个问题
  • index_reopen:open 一个之前 close 的索引
  • dangling_index_imported:一个节点离开集群期间,有索引被删除。这个节点重新返回时,会导致 Dangling 的问题,此时我们再对这个索引进行删除操作,就可以解决了。

常见问题与解决方法

  • 集群变红,需要检查是否有节点离线。如果有,通常通过重启离线的节点可以解决问题。

  • 由于配置导致的问题,需要修复相关的配置。如果是测试的索引,可以直接删除

  • 因为磁盘空间限制,分片规则(Shard Filtering)引发的,需要调整规则或者增加节点

  • 对于节点返回集群,导致的 dangling 变红,可以直接删除 dangling 索引

集群 Red & Yellow 问题的总结

Red & Yellow 是集群运维中常见的问题。除了集群故障,一些创建,增加副本等操作,都会导致集群短暂的 red 和 yellow,所以监控和报警时需要设置一定的时延。通过检查节点数,使用 ES 提供的相关 API,找到真正的原因。

同时根据情况也可以指定 Move 或者 Reallocate 分片:

image-20200502200149661

相关阅读

https://www.elastic.co/guide/en/elasticsearch/reference/7.1/cat-shards.html

Kibana 请求测试

#案例1
DELETE mytest
PUT mytest
{
  "settings":{
    "number_of_shards":3,
    "number_of_replicas":0,
    "index.routing.allocation.require.box_type":"hott"
  }
}

# 检查集群状态,查看是否有节点丢失,有多少分片无法分配
GET /_cluster/health/

# 查看索引级别,找到红色的索引
GET /_cluster/health?level=indices


#查看索引的分片
GET _cluster/health?level=shards

# Explain 变红的原因
GET /_cluster/allocation/explain

GET /_cat/shards/mytest
GET _cat/nodeattrs

DELETE mytest
GET /_cluster/health/

PUT mytest
{
  "settings":{
    "number_of_shards":3,
    "number_of_replicas":0,
    "index.routing.allocation.require.box_type":"hot"
  }
}

GET /_cluster/health/

#案例2, Explain 看 hot 上的 explain
DELETE mytest
PUT mytest
{
  "settings":{
    "number_of_shards":2,
    "number_of_replicas":1,
    "index.routing.allocation.require.box_type":"hot"
  }
}

GET _cluster/health
GET _cat/shards/mytest
GET /_cluster/allocation/explain

PUT mytest/_settings
{
    "number_of_replicas": 0
}

集群写性能优化

写性能的优化目标是增大写吞吐量(Events Per Second),越高越好。性能优化主要分为两个方向,客户端和服务端:

  • 客户端:使用多线程,buik API 批量写
    • 可以通过性能测试,确定一个 bulk api 的最佳文档数量是多少
    • 多线程写入的情况下,需要观察是否有 HTTP 429的返回,如果有,说明服务端已经无法处理目前的请求量了,这时候客户端需要对返回429的请求有一个重试并自动调节线程数的机制
  • 服务器端:单个性能问题,往往是多个因素造成的。需要先分解问题,在单个节点上进行调整并结合测试,尽可能压榨硬件资源,以达到最高吞吐量
    • 使用更好的硬件,观察 CPU、IO Block
    • 观察线程切换、堆栈情况

服务端优化写入性能的一些手段

  1. 降低 IO 操作。例如使用 ES 自动生成的文档 id,如果我们自己生成 id,ES会有一个 GET操作,对性能有一定开销。还有其他一些相关的 ES 配置,例如 Refresh Interval
  2. 降低 CPU 和存储开销:减少不必要的分词、避免不需要的 doc_value 文档、文档的字段尽量保证相同的顺序,可以提供文档的压缩率
  3. 尽可能做到文档写入请求(Write Load Balancer)和分片(Shard Filtering)的负载均衡,实现水平扩展
  4. 调整 Build 线程池和队列

高质量数据建模

ES 的默认配置已经总和考虑了数据可靠性,搜索的实时性质,写入速度,一般不要盲目修改。一切优化,都要基于当前的数据建模是高质量的。以下是建模的一些建议:

  • 只需要聚合不需要搜索,index 设置成 false

  • 不需要算分,norms 设置成 false

  • 不要对字符串使用默认的 dynamic mapping,因为默认的 mapping 会对 text 类型数据自动生成一个 keyword 子字段,我们按需自己手动设置。另外一般也不建议是有那个 dynamic mapping,因为可能会导致字段数量过多,会对性能产生较大的影响

  • Index_optiions 控制在创建倒排索引的时,哪些内容会被添加到倒排索引中。优化这些设置,一定程度可以节约 CPU

  • 关闭 _source,减少 IO 操作(适合指标型数据)

image-20200502202412916

牺牲一定可靠性和实时性增加写入性能

如果需要追求极致的写入速度,可以牺牲数据可靠性及搜索实时性以换取性能。

  • 牺牲可靠性:将副本分片设置为0,写入完毕再调整回去
  • 牺牲搜索实时性:增加 Refresh Interval 的时间
  • 牺牲可靠性:修改 Translog 的配置

下面我们来回顾一下数据写入的过程,来看一下有哪些点可以优化提高写入性能:

  1. refresh:将文档先保存在 Index Buffer 中,以refresh_interval 为间隔时间,定期清空 buffer,生成 segments,借助文件系统缓存的特性,先将 segments 刷到文件系统缓存中,并开放查询,以提升搜索的实时性
  2. Translog:Segments 没有写入磁盘,即便发生了宕机,重启后,数据也能恢复,默认配置是每次请求都会落盘
  3. Flush:请求一次 refresh 生成 Segments cache 并连着 cache 中之前生成的 segments 一起写入磁盘,更新 commit point 并写入磁盘,删除旧的 translog 文件。ES 自动完成,可优化点不多。

Refresh Interval的优化

我们可以通过降低 Refresh 的频率,减少生成 segments cache 的动作以及生成过多的 segment 文件,空出更多的单位时间系统资源以供写入操作的使用(但是会降低搜索的实时性):

  • 增加 refresh_interval 的数值。默认值是1s,如果设置成-1,会禁止自动 refresh
  • 增大静态配置参数indices.memory.index_buffer_size,默认是10%,不然即使我们设置了 refresh_interval,但是在 index_buffer 满了情况下还是会导致自动出发 refresh

Translog 的优化

另外我们还可以通过以下方式降低 ES 写入 Translog 的频率和实时性,但是会降低容灾能力:

  • index.translog.durability:默认是 request,每个请求都会同步地进行 translog 落盘操作之后才会返回客户端,设置为 async,异步写入
  • index.translog.sync_interval:设置为60s,每分钟执行一次 translog 的磁盘写入而不是每一次请求都会导致落盘(配合上面参数使用)
  • index.translog.flush_threshod_size:默认512mb,可以适当调大。当 translog 超过该值,会触发 flush

分片设定

  • 副本在写入的时候设定为0,完成后再增加。

  • 合理设置主分片数,确保均匀分配在所有数据节点上:

    index.routing.allocation.total_share_per_node:限定每个索引在每个节点上可以分配的主副本分片数(这样可以避免一些热点索引数据集中在一个节点上)

    一个例子:5个节点的集群。索引有5个主分片,1个副本分片,应该如何设置?

    • (5+5) / 5 = 2 ( 设置以上参数值)
    • 生产环境中要适当调大这个数字,避免有节点下线,分片无法正常迁移

Bulk 线程池和队列大小

客户端:

  • 单个 bulk 请求体的数据量不要太大,官方建议5-15mb
  • 写入端的 bulk 请求超时需要足够长,需要60s 以上
  • 写入端尽量将数据轮询打到不同节点(LB)

服务器端:

  • 索引创建属于计算密集型任务,应该使用固定大小的线程池来配置。来不及处理的放入队列,线程数应该配置成 CPU 核心数+1,避免过多的上下文切换
  • 队列大小可以适当增加,不要过大,否则占用的内存会成为 GC 的负担

一个索引设定的例子

image-20200502205658949

{
    "template":"logs-*",
    "settings":{
        "index.indexing.slowlog.threshold.index.debug":"2s",
        "index.indexing.slowlog.threshold.index.info":"5s",
        "index.indexing.slowlog.threshold.index.trace":"500ms",
        "index.indexing.slowlog.threshold.index.warn":"10s",
        "index.merge.policy.max_merged_segment":"2gb",
        "index.merge.policy.segments_per_tier":"24",
        "index.number_of_replicas":"1",
        "index.number_of_shards":"12",
        "index.optimize_auto_generated_id":"true",
        "index.refresh_interval":"600s",
        "index.routing.allocation.total_shards_per_node":"-1",
        "index.search.slowlog.threshold.fetch.debug":"500ms",
        "index.search.slowlog.threshold.fetch.info":"800ms",
        "index.search.slowlog.threshold.fetch.trace":"200ms",
        "index.search.slowlog.threshold.fetch.warn":"1s",
        "index.search.slowlog.threshold.query.debug":"2s",
        "index.search.slowlog.threshold.query.info":"5s",
        "index.search.slowlog.threshold.query.trace":"500ms",
        "index.search.slowlog.threshold.query.warn":"10s",
        "index.translog.durability":"async",
        "index.translog.flush_threshold_size":"5000mb",
        "index.translog.sync_interval":"120s",
        "index.unassigned.node_left.delayed_timeout":"7200m"
    },
    "mappings":{
        "_default_":{
            "_all":{
                "store":"false"
            }
        },
        "typename":{
            "dynamic":false,
            "properties":{
                "full_name":{
                    "type":"text"
                }
            }
        }
    }
}

相关阅读

https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html

集群读性能优化

  1. 首先我们不能将 ES 当做一个普通的关系型数据库来对待,我们要尽量对数据做Denomalization。从而获取最佳的性能:
  • 使用 nested 类型的数据。查询速度会慢几倍
  • 使用 Parent / Child 关系。查询速度会慢几百倍

所以可以避免使用以上数据类型就尽量避免。

  1. 对于需要进行Script 计算的数据,可以在 index 文档的时候,使用 ingest pipeline 计算然后写入ES。尽量避免查询时使用 Script 计算。

    image-20200502210602653

  2. 尽量使用 Filter Context,利用缓存机制,减少不必要的算分

    image-20200502210650530

  3. 结合 profile,expalin API 分析慢查询的问题,持续优化数据模型

  4. 严禁使用 * 开头通配符 Terms 查询

    image-20200502210932397

  5. 聚合文档消耗内存,特别时针对很大的数据集进行聚合运算,如果可以控制聚合的数量,就能减少内存的开销;当需要使用不同的 Query Scope,可以使用 Filter Bucket(和上面提到的一样,使用 Filter 替代 Query)

    image-20200502210818880

  6. 尽量提高 filesystem cache的大小,前面我们提到过,内存一半空间分给 JVM 一半空间留给lucene 的 segments。ES 检索索引的时候底层都是查询的 lucene 的一个个 segments,会先查os cache 后查 file system,如果在缓存能命中,会大大提高效率。

  7. 分页能用 scoll api 的方式就经量使用,尽量避免深度分页。

优化分片

  • 避免 Over Sharing(过多分片),它会导致一个查询需要访问每一个分片,分片过多,会导致不必要的查询开销。
  • 结合应用场景,控制单个分片的尺寸:
    • search:20GB
    • Logging:40GB
  • 基于时间序列的索引,我们要及时地对这些数据做老化处理,将它们设置为 read-only 然后对它们做一个 force merge 操作,减少 segments 数量

相关阅读

https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-search-speed.html

集群压力测试

在我们在做一些容量规划和性能调优的时候,都需要进行一些实际的测试,看一下实际的效果。

压力测试的方法与步骤

  • 测试计划(确定测试场景和测试数据集)
  • 脚本开发
  • 测试环境搭建(不同的软硬件配置)& 运行测试
  • 分析比较结果

测试目标和测试数据

  • 测试目标
    • 测试集群的读写性能、做集群容量规划
    • 性能问题诊断及优化:
      • 对 ES 配置参数进行修改,评估优化效果
      • 修改 Mapping 和 Setting,对数据建模进行优化,并测试评估性能改进
    • 测试 ES 新版本,结合实际场景和老版本进行比较,评估是否进行升级
    • 确定系统稳定性,考察系统功能极限和隐患
  • 测试数据集对于压测结果的影响也非常大,我们主要从数据量和数据分布两个方面进行考虑

测试脚本

ES 本身提供了 REST API,所以,可以通过很多传统的性能测试工具:

  • Load Runner(商业软件,支持录制+重放+DSL)
  • JMeter(Apache 开源,Record & Play)
  • Gatling(开源,支持写 Scala 代码+DSL)

另外,还有一些专门为 Elasticsearch 设计的工具

  • ES Pref & Elasticsearch-stress-test
  • Elastic Rally

ES Rally

Elastic 官方开源,基于 Python3的压力测试工具:

功能介绍:

  • 自动创建、配置、运行测试、并且销毁 ES 集群
  • 支持不同的测试数据(自带一些数据集)的比较,也支持将数据导入 ES集群,进行二次分析
  • 支持测试时指标数据的搜索,方便对测试结果进行深度的分析

安装以及入门

  1. 安装
    • Python3.4+和 pip3、JDK 8、git 1.9+
    • 运行 pip3 install esrally
    • 运行 esrally configure
  2. 运行
    • 运行 esrally -distribution-version=7.1.0(会使用默认的数据集进行测试,数据量很大)
    • 运行1000条测试数据:esrally -distribution-version=7.1.0 --test-mdoe(使用 test mode 默认使用前1000条数据进行测试)

Rally 基本概念讲解

Rally 使用了汽车拉力赛中的概念对压力测试进行类比以及引用了相关的术语进行相关模块的命名:

  • Tournament(锦标赛):定义测试目标,由多个 race 组成

  • Race(锦标赛中的一次比赛):其实就是一次压力测试操作(esrally list race)

  • [Track](https://github.com/elastic/rally- tracks)(赛道):测试数据和测试场景与策略(esrally list tracks)

  • Car(赛车):代表不同的 ES 实例(来执行测试方案)

  • Award(颁奖):测试结果和报告

压测流程

Rally 中pipeline 指的是压测的一个流程(通过 esrally list pipelines命令查看有什么 pipeline),表示Rally 默认有以下流程供使用:

  • From-source-complete:从源码编译构建出一个 ES并搭建集群,然后在这个集群上运行一个基准(benchmark)测试并报告结果。
  • From-source-skip-build:使用已经构建好的一个 ES 搭建集群,然后在这个集群上运行一个基准(benchmark)测试并报告结果。
  • From-distribution:下载一个已经发布的 ES,搭建集群,然后在这个集群上运行一个基准(benchmark)测试并报告结果。
  • Benchmark-only:在一个已经在运行的 ES 实例上执行一个基准测试并报告结果。

自定义测试&分布式测试

使用不同的 car(不同配置的 ES 集群):

使用不同的 Track(不同测试数据):

当需要对一个特别大的 ES 集群进行测试的时候,Rally 还支持分布式测试的模式,可以在不同的机器上分别搭建 Rally,进行一个分布式测试:[https://esrally.readthedocs.io/en/latest/recipes.html#recipe-distributed-load- driver](https://esrally.readthedocs.io/en/latest/recipes.html#recipe-distributed-load- driver)

实例:比较不同版本的性能

对 ES6.0和7.1进行测试,使用相同的测试数据集 nyc_taxis:

# 6.0
esrally race --distribution-version=6.0.0 --track=nyc_taxis --challenge=append-no-conflicts --user-tag="version:6.0.0”
# 7.0
esrally race --distribution-version=7.1.0 --track=nyc_taxis --challenge=append-no-conflicts --user-tag="version:7.1.0"

比较结果

esrally list races
esrally compare --baseline=[6.0.0 race] --contender=[7.1.0 race]

实例:比较不同 Mapping 的性能

测试 mapping 文件中 _source 是否 enabled 的性能差异

# 优化前测试
esrally race --distribution-version=7.1.0 --track=nyc_taxis --challenge=append-no-conflicts --user-tag="enableSource:true" --include-tasks="type:index”

# 修改优化:benchmarks/tracks/default/nyc_taxis/mappings.json,修改 _source.enabled 为 false

# 修改后在测试
esrally race --distribution-version=7.1.0 --track=nyc_taxis --challenge=append-no-conflicts --user-tag="enableSource:false" --include-tasks="type:index

比较

esrally compare --baseline=[enableAll race] --contender=[disableAll race]

实例:测试现有集群的性能

# 指定 pipeline 为 benchmark-only 并指定现有集群的连接信息
esrally race --pipeline=benchmark-only --target-hosts=127.0.0.1:9200 --track=geonames --challenge=append-no-conflicts

相关阅读

https://github.com/elastic/rally

https://github.com/elastic/rally-tracks

https://logz.io/blog/rally/

https://elasticsearch-benchmarks.elastic.co

https://esrally.readthedocs.io/en/stable/tournament.html

https://esrally.readthedocs.io/en/latest/car.html

https://github.com/elastic/rally-tracks

[https://esrally.readthedocs.io/en/latest/recipes.html#recipe-distributed-load- driver](https://esrally.readthedocs.io/en/latest/recipes.html#recipe-distributed-load- driver)

段合并优化及注意事项

Lucene Index 原理回顾

image-20200502222356402

Merge 优化

ES 和 Lucene 默认情况会自动进行 merge 操作,但是 merge 操作相对比较重,需要优化,降低对系统的影响。

  1. 优化点1:降低分段产生的数量/频率

    可以将 Refresh Interval 调整到分钟级别、indices.memory.index_buffer_size调大(默认10%)、尽量避免文档的更新操作

  2. 优化点2:降低最大分段大小,避免较大的分段继续参与 merge,节省系统资源。(这时候一个 ES 分片一个 Lucene 索引下面会一直存在多个分段,检索文档数据就需要分别从各个分段进行检索)

    • index.merge.policy.segments_per_tier参数修改,默认为10,越小需要越多的合并操作。(ES会定时检查分片中segments 的个数发现超过阈值就立马进行 merge?)
    • index.merge.policy.max_merged_segment参数修改,默认5GB,经过不断的 segments 合并,当合并后的 segment 到达这个大小的时候,后续即使有新的 segments 生成,该 segment 也不再参与合并
  3. 优化点3:force marge

    当 index 中不再有写入操作的时候,建议将其改成 read-only,并对其进行 force merge,merged 之后提升查询速度,较少内存开销

    image-20200502223311927

    最终分成几个 segments 比较合适?越少越好,最好可以 force merge 成1个,但是 force merge 会占用大量的网络IO、磁盘IO和 CPU。如果不能在业务高峰期之前做完,就需要考虑增大最终的分段数或者通过一些配置来优化提高 force merge 的效率。(也可以降低一个分片的大小)

缓存及使用 Breaker 限制内存使用

本节来讨论 ES 的缓存结构及一些缓存相关的问题。ES 的缓存主要分为三大类:

  • Node Query Cache(Filter Context)
  • Shard Query Cache(Cache Query的结果)
  • Fielddata Cache

image-20200502225126155

Node Query Cache

每一个节点都有一个 Node Query 缓存

  • 由该节点的所有 Shard 共享,只缓存 Filter Context 相关内容。
  • 采用 LRU 算法

通过在每个 Data Node 的静态文件中进行全局配置以下参数:

  • Node Level:indices.queries.cache.size: “10%”(默认值10%)
  • Index Level:index.queries.cache.enabled: true

Shard Request cache

缓存每个分片上的查询结果,只会缓存设置了 “size=0” 的查询对应的结果,不会缓存 hits。但是会缓存 Aggregations 和 Suggestions。

这个 Cache 也是使用 LRU 算法,将整个 JSON 查询字符串作为 Key,所以我们需要保证查询的 JSON 字符串中的 JSON 属性顺序是一致的了保证缓存命中。

通过在数据节点上配置参数:

  • indices.requests.cache.size:“1%”

  • index.requests.cache.enable: false

一般来说,第一个参数可以在静态文件中进行全局配置,第二个参数可以通过以下 API 进行索引级别或者请求级别的动态设置

image-20200502225950623

Fielddata Cache

除了 Text 类型,其他类型默认都采用 doc_values(排序、聚合),它和 Aggregation 的 Global Ordinals 都保存在 Fielddata cache 中。

Text 类型的字段需要打开 fielddata 才能对其进行聚合和排序。text 经过分词,排序和聚合效果不佳,建议不要轻易使用。

可以通过持续监控 fielddata cache,然后根据情况调整indices.fielddata.cache.size参数来控制 fielddata cache 的大小,避免占用内存过大而产生 GC(默认无限制)

缓存失效

Node Query Cache:保存的是 Segment 级缓存命中的结果,Segments 被合并后,缓存会失效。

Shard Request Cache:分片 Refresh 的时候,Shard Request Cache 会失效。如果 Shard 对应的数据频繁发生变化,该缓存的效率会很差。

Fielddata Cache:Segment 被合并后,会失效

管理内存的重要性

Elasticsearch 高效运维依赖于内存的合理分配,服务器的用内存一半分配给 JVM,另一半留给操作系统,缓存索引文件。

内存相关的一些问题:

  • JVM 内存不足,频繁或者长时间的 GC,影响节点,导致集群响应缓慢
  • OOM,导致节点宕机丢失

我们可以通过以下 API 来查看各个节点的内存状况:

GET _cat/nodes?v
GET _nodes/stats/indices?pretty
GET _cat/nodes?v&h=name,queryCacheMemory,queryCacheEvictions,requestCacheMemory,reques tCacheHitCount,request_cache.miss_count
GET _cat/nodes?h=name,port,segments.memory,segments.index_writer_memory,fielddata.memo ry_size,query_cache.memory_size,request_cache.memory_size&v

一些常见的内存问题举例

  1. Segments 个数过多,导致 full GC
    • 现象:集群整体响应缓慢,也没有特别多的数据读写。但是发现节点在持续进行 full gc。
    • 分析:查看 Elasticsearch 的内存使用,发现 segments.memory 占用很大空间
    • 解决:通过 force merge,把 segments 合并成一个
    • 建议:对于不在写入和更新的索引,可以将其设置成只读。同时,进行 force merge 操作。如果问题依然存在,则需要考虑扩容。此外,对索引进行 force merge,还可以减少 global_ordinals 数据结构的构建,减少对 fielddata cache 的开销
  2. fielddata cache过大,导致 full gc
    • 现象:集群整体响应缓慢,也没有特别多的数据读写。但是发现节点在持续进行 full gc
    • 分析:查看 Elasticsearch 的内存使用,发现 fielddata.memory.size 占用很大空间。同时,数据不存在写入和更新,也执行过 segments merge。
    • 解决:将 indices.fielddata.cache.size 设小,重启节点,堆内存恢复正常
    • 建议:Field data cache 的构建比较重,Elasticsearch 不会主动释放,所以这个值应该设置得保守一些。如果业务上确实有所需要,可以通过增加节点,扩容解决
  3. 复杂的嵌套聚合,导致集群 full gc
    • 现象:节点响应缓慢,持续进行 full gc
    • 分析:导出 Dump 分析,发现内存中有大量 bucket 对象,查看日志,发现复杂的嵌套聚合
    • 解决:优化聚合
    • 建议:在大量数据集上进行嵌套聚合查询,需要很大的堆内存来完成。如果业务场景确实需要。则需要增加硬件进行扩展。同时,为了避免这类查询影响整个集群,需要设置 Circuit Breaker 和search.max_buckets的数值

Circuit Breaker

ES 提供了多种断路器(熔断),避免不合理操作引发的 OOM,每个断路器可以指定内存使用的限制(熔断的阈值)。

  • Parent circuit breaker:设置所有的熔断器可以使用的内存的总量
  • Fielddata circuit breaker:加载 fielddata 所需要的内存
  • Request circuit breaker:防止每个请求级数据结构超过一定的内存(例如聚合计算的内存一般会比较大)
  • In fight circuit breaker:Request 中的断路器
  • Accounting request circuit breaker:请求结束后不能释放的对象所占用的内存

通过 GET /_nodes/stats/breaker?查询当前熔断器的工作状态:

  • Tripped 大于0,说明有过熔断
  • Limit size 与 estimated size 越接近,越可能引发熔断

image-20200502232458039

千万不要触发了熔断,就盲目调大参数,有可能会导致集群出现问题,也不应该盲目调小,需要进行评估(通过压测等)。建议将集群升级到7.x,更好的 Circuit Breaker 实现机制,增加了indices.breaker.total.use_real_memory配置项,可以更加精准的分析内存状况,避免 OOM。

相关阅读

https://www.elastic.co/blog/improving-node-resiliency-with-the-real-memory-circuit-breaker

一些运维的建议

需要引入集群的生命周期管理的概念:

  1. 预上线

    评估用户的需求及使用场景:数据建模、容量规划、选择适合的部署架构、性能测试

  2. 上线

    • 监控流量、定期检查潜在问题(防患于未然,发现错误的使用方式,及时增加机器)
    • 对索引进行优化(index lifecycle management),检测是否存在不均衡而导致有部分节点过热
    • 定期数据备份、滚动升级
  3. 下架前监控流量,实现 Stage Decommission

部署的建议

根据实际场景,选择合适的部署方式,选择合理的硬件配置

  • 搜索类
  • 日志/指标

部署要考虑反亲和性(Anti-Affinity)

  • 尽量将机器分散在不同的机架。
  • 善用 Shard Filtering 进行配置

使用要遵循一定的规范

  • 生产环境中索引应考虑禁用 Dynamic Index Mapping,避免过多字段导致 Cluster State 占用过多

  • 禁止索引自动创建的功能,创建时必须提供 Mapping 或者通过 Index Template 进行设定

    image-20200502235305598

  • 开启慢查询日志,发现一些性能不好,甚至是错误的使用。例如:错误的将网址映射成 keyword,然后用通配符查询,应该使用 Text,结合 URL 分词器;严禁一切"*"开头的通配符查询

对重要的数据进行备份

如果是在公有云上使用云厂商提供的服务,一般会有数据备份的服务包。如果是在自己公司的服务器上搭建的集群,参考如何ES 提供的快照管理功能或者极客时间阮一鸣老师的课程介绍

定期更新到新版本

ES 在新版本中会持续对性能作出优化,提供更多的新功能,例如最近的 Circuit breaker 有大量的改进;并且也会修复一些已知的 bug 和安全隐患。

  1. ES 的版本:

    Elasticsearch 的版本格式是:x.y.z。x 是 Major 版本;y 是 Minor;z 是 Patch。ES 可以使用上一个主版本的索引,例如 7.x 可以使用 6.x;7.x 不支持使用 5.x;5.x 的上一个版本是2.x,所以可以使用2.x 的索引。

  2. Rolling Upgrade v.s. Full Cluster Restart

    • Rolling Upgrade:没有 downtime。详细可以参考:https://www.elastic.co/guide/en/elasticsearch/reference/7.1/rolling-upgrades.html

    • Full Cluster Restart:集群在更新期间不可用,但是升级更快。

      步骤:

      1. 停止索引数据,同时备份集群

      2. Disable Shard Allocationo(Persistent)

        image-20200503001306358

      3. 执行 Synced Flush

        image-20200503001317514

      4. 关闭所有节点,然后进行节点更新

      5. 先运行所有 master 节点、在运行其他节点

      6. 等集群变黄后打开 Shard Allocation

运维 Cheat Sheet

  1. 移动分片:从一个节点移动分片到另外一个节点,使用场景是当一个数据节点上过多 Hot Shards,可以通过手动分配分片到特定的节点解决

    image-20200503001454447

  2. 从集群中移除一个节点:当你想移除一个节点,或者对一个机器进行维护。同时你又不希望导致集群的颜色变黄或者变红。执行以下操作,ES 会将指定 ip 的节点上的数据进行转移,在转移完毕之后,直接移除该节点即可。image-20200503001546359

  3. 控制 Allocation 和 Recovery

    image-20200503001810882

  4. Synced Flush:当需要重启一个节点的时候,我们要先将 cache 中的 segments 进行一次 flush 到磁盘,保证数据不丢失。(通过 synced flush,可以在索引上防止一个 sync id,这样可以提高这些分片的 recovery 的时间?)

    image-20200503001956169

  5. 清空节点上的缓存:使用场景是节点上出现了高内存占用。可以执行清除缓存的操作,这样是会影响集群的性能的,但是会避免集群出现 OOM 的问题

    image-20200503002140844

  6. 控制搜索的队列:使用场景是当搜索的响应时间过长,看到有"reject"指标的增加,都可以适当增加该数值

    image-20200503002247557

  7. 根据场景设置各类的 Circuit Breaker:避免 OOM 的发生

    image-20200503002309835

相关阅读

https://www.elastic.co/guide/en/elasticsearch/reference/7.1/rolling-upgrades.html

close、shrink、split、rollover、rollup API

本节我们主要介绍一些相关的索引管理的 API。

open / close index

索引关闭后无法进行读写搜索,对集群的相关开销基本降低为0。但是索引数据不会被删除,所以如果有一些索引我们是暂时不需要进行读取的,可以先将它们关闭,当需要的时候再打开。

# 删除索引的话,用 HEAD RESTful 方法是查询不到这个索引的
DELETE test
#查看索引是否存在
HEAD test

image-20200503092210451

PUT test/_doc/1
{
  "key":"value"
}

#关闭索引,用 HEAD 方法是可以检测得到索引的存在的
POST /test/_close
#索引存在
HEAD test

image-20200503092310404

# 但是无法查询,会报索引已经关闭的异常
POST test/_count

image-20200503092355083

Shrink Index

这是 ES 5.x 后推出的一个新功能,可以将索引的主分片数搜索到较小的值。其使用场景是有以下

  • 索引保存的数据量比较小,需要重新设定主分片数。
  • 索引从 Hot 移动到 Warm 后,需要降低主分片数

这个 API 会使用和源索引相同的配置创建一个新索引,仅仅降低主分片数:

  • 源分片数必须是目标分片数的倍数。如果源分片数是素数,目标分片数只能为1。
  • 如果文件系统支持硬链接,会将 Segments 硬链接到目标索引,所以(和 reindex 相比)性能好。
  • 新索引创建完成后,可以删除源索引。

在使用 Shrink API 的时候,ES 会做以下校验:

image-20200503093926617

Demo

  1. 在一个节点拥有热、温、冷三个节点的集群上进行测试

image-20200503094600253

image-20200503094643119

  1. 查看节点属性

    image-20200503094657587

    image-20200503094723133

  2. 源索引初始化,一共有4个主分片,然后写入一个文档,再查看文档的分片情况。可以看到文档落到了位于 es7_cold 的分片0上了。

    image-20200503094858455

    image-20200503094922446

  3. 尝试执行 Shrink API,设置分片数为3,会报出一个错误:源索引的分片数必须是目标索引3的的倍数

image-20200503095042980

image-20200503095051271

  1. 尝试将分片数设置成2,看是否能执行成功。可以看到,又报出一个源索引必须是只读的错误。

image-20200503095302081

image-20200503095326463

  1. 将源索引设置为只读,再尝试 Shrink。可以看到,再次报错:所有分片必须都在一个节点上,前面看到示例中的源索引的4个分片是分布在3个节点上的。

image-20200503095516344

image-20200503095545810

  1. 现在我们将源索引删除,并设置选项index.routing.all.allocation.include.box_type为 hot,让索引分片数据只会分到 hot 节点上,而我们当前集群中只有一个 hot 节点。然后重新写入文档,查看分片情况,可以看到4个分片都在 es7_hot 节点上,刚刚写入的文档存在了分片0上。

    image-20200503095834837

    image-20200503095812884

  2. 然后设置为只读并再次执行 Shrink,这次就可以成功了。

    image-20200503100129305

    查看新索引的分片情况,被收缩成了两个分片,因为是通过硬链接的方式,所以也都存在于原来的同一个节点上:

    image-20200503100225608

    image-20200503100231713

  3. 现在我们对新索引尝试写入数据,发现它也是只读的,因为也拷贝了源索引的配置。我们需要重新设置为可写。

    image-20200503100401539

    image-20200503100406518

Split API

可以扩大主分片个数。它和 Shrink API 是一个相反的操作:

image-20200503100602106

同样的,它也需要满足一定的规则才能执行:

image-20200503100631605

Demo

  1. 源索引初始化,设置主分片为4,可以看到分片分布在4个节点上。

image-20200503100733597

image-20200503100756398

  1. 尝试 Split API 将新的索引的分片数设置成10。会报一个目标索引分片数必须是源索引的倍数的错误

    image-20200503100908503

    image-20200503100915494

  2. 设置成8之后又提示源索引必须是只读

    image-20200503100954473

    image-20200503101000980

  3. 设置只读再执行 Split API,就成功了。Split API 不要求所有分片都在一个节点上。基于源索引的各个分片原本所在的节点上进行 Split。

    image-20200503101104379

    image-20200503101128821

  4. 尝试写入数据,和 Shrink 一样,都是拷贝源索引配置,无法写入

    image-20200503101249777

    image-20200503101255482

Rollover Index

类似 Log4J 记录日志的方式,索引尺寸或者当前时间超过一定值后,创建新的索引。

下面我们来看一个场景:我们有一个基于时间序列的索引,我们是按照每天进行新索引的创建并对前一天的索引做老化的,如左图所示,在第一天的时候写入的索引的数据是90GB,但是到了第二天的时候,写入索引暴增到520GB,而第3天的数据暴增到230GB,这种情况下我们应该将这些数据分隔成多个索引存储而不是只存储在一个索引上,从而缓解 ES 服务器压力。如右图所示,我们可以设定一定的规则(按照索引中文档的数量、某个时间阈值、索引大小)来自动创建新的索引,在图中是按照索引大小到达200GB 之后就创建新索引的规则进行的。

image-20200503102035890

而 Rollover API 就是这样一个 API,当我们调用它的时候, 给它设定一定的条件,它会检测这些条件,如果这些条件都满足了,会按照规则创建一个新索引并Alias指向它。Rollover APi 一般和Index Lifecycle Manament Policies 一起使用,它单独作为一个 API 的时候,只有调用它,它才会去做相应的检测,并不会自动监控这些索引。

Demo1:默认情况下 rollover 之后通过别名只能查询到最新索引
  1. 首先我们初始化一个 ngnix-logs-000001的索引,并通过索引属性aliases为其指向了一个别名"nginx_logs_write",然后写入6个文档。

    image-20200503102901500

  2. 现在我们来调用 rollover api 做一个 rollover 的操作。rollover api 是基于一个别名进行操作的,这里我们指定前面设置 nginx-logs-000001索引的时候设置的别名 nginx_logs_write。并设置 rollover 的条件是:当前索引存在时间已经达到了一天、文档数量已经超过了5个、索引大小已经超过了5GB。

    image-20200503103209779

    rollover 成功。返回了 rollover 之前当前别名指向的索引名称"nginx-logs-000001"以及自动新创建的索引名称"nginx-logs-000002",并通过"rolled_over=true"告诉我们是否触发了 rollover 并执行 成功了,以及通过 conditions 告诉我们是哪些条件触发了 rollover。

    image-20200503103352536

  3. 尝试通过 alias 查看索引文档个数。发现只有一个文档:

image-20200503103803045

image-20200503103817404

查看 Alias 信息, 其实这个 alias 现在就是指向了一个索引,就是我们刚刚创建的索引,老索引没有包含,所以老索引中的数据通过别名是访问不到的。

image-20200503103842746

image-20200503103850138

直接通过老索引的名称进行访问,可以访问到老索引中存在6个文档

image-20200503104130892

image-20200503104136054

Demo2:通过is_write_index设定使得 rollover 之后通过别名可以访问所有 index
  1. 针对上面的情况我们再重新做一个案例,别名会映射到所有经过 rollover 的索引,包含全部的新旧索引。我们先再初始化一个索引 apache-logs1。此时设置别名中的一个属性is_write_index为 true。

    image-20200503104605854

  2. 多次写入文档

    image-20200503104619848

  3. 此时我们再做一个 rollover 的操作,这一次我们为这个 rollover 指定了新索引的名称"apache-logs2",因为 ES 默认情况下如果用户不指定新索引的名称,要求别名现在指向的老索引的名称必须是横杠加数字结尾(“xxxx-00001”)才会基于横杠后面的数字加1自动创建新索引并命名该索引。我们前面的例子就是"nginx-logs-000001",而这里的例子是"apache_logs1",没有横杠,所以需要自己指定新索引名称

    image-20200503104726817

    可以看到,再次 rollover 成功。

    image-20200503105019974

  4. 再写入一个文档

image-20200503105058813

再做一次 rollover

image-20200503105118574

image-20200503105127370

  1. 这一次同样对别名"apache_logs"做一次 count 操作,发现和一开始的 demo 不一样,返回的数量不是1而是9。似乎包含了所有索引的文档数。

    image-20200503105235050

    image-20200503105240397

  2. 再次查看 alias 信息可以发现这个索引确实是包含了之前的所有索引"apache-logs1"、“apache-logs2"和"apache-logs3”。另外我们还看到只有"apache-logs3"的"apache_logs"属性中的"is_write_index"是 true。所以可以看到如果我们在为一个需要 rollover 的索引对其别名属性"is_write_index"设置为 true的时候,是可以使得在 rollover 操作的时候会将别名包含所有的索引,使得我们可以访问到所有的索引,但是只会写入数据到最新的索引中。

    image-20200503105530743

    image-20200503105546717

    image-20200503105510258

Rollup Index

对数据进行处理后,重新写入到新的索引,减少单个索引的数据量。

相关阅读

https://www.elastic.co/guide/en/elasticsearch/reference/7.1/indices-shrink-index.html

https://www.elastic.co/guide/en/elasticsearch/reference/7.1/indices-rollover-index.html

索引全生命周期管理及工具介绍

对于 Time based 的索引,它们的特点是索引中的数据随着时间的增长,访问流量越来越低。前面提到我们可以按照时间序列对这种类型的数据进行索引划分:

  • 好处:按照时间进行索引划分,会使得管理更加简单。例如,完整删除一个索引,性能比 delete by query 好。
  • 挑战:如何实现自动化管理,减少人工操作,例如我们需要定期将 Hot 索引迁移为 Warm、定期关闭或者删除索引等。

索引生命周期常见的阶段

image-20200503112649257

  • Hot:索引还存在着大量的读写操作
  • Warm:索引不存在写操作,还有被查询的需要
  • Cold:数据不存在写操作,读操作也不多
  • Delete:索引不再需要,可以被安全删除

一些索引声明周期管理工具

[Elasticsearch Curator](https://www.elastic.co/guide/en/elasticsearch/client/curator/current/index.htm l)

这是 ES 官方推出的工具,基于 python 的命令行工具。它内置了10多种 Index 相关的操作(Actions),每个动作可以顺序执行。 另外它还支持各种条件,过滤出需要操作的索引(Filters)。 但是它仅仅是一个命令行的用来执行索引管理的工具,并不具有自动检测索引状态+对索引进行相关动作的功能,它仅仅是把索引检查的一些动作聚合成了一个命令行,我们仍然需要定期人工去使用这些命令行来检查索引+操作索引。

image-20200503112959682

详细可查看:[https://www.elastic.co/guide/en/elasticsearch/client/curator/current/index.htm l](https://www.elastic.co/guide/en/elasticsearch/client/curator/current/index.htm l)

eBay Lifecycle Management Tool

这是 eBay Pronto team 自研图形化工具:

  • 支持 Curator 的功能
  • 基于图形化配置
  • 配置 Job 定时触发,无需到时间了再手工操作
  • 一个界面,管理多个 ES 集群
  • 支持不同的 ES 版本
  • 系统高可用,保证一些节点的宕机导致一些操作没有被执行

image-20200503113526070

eBay Lifecycle Management Tool 和 Curator 和 rollover api 区别:

image-20200503113611695

  • 在对索引的操作上,eBay LMT 和 Curator 都是支持所有动作的,而 rollover api 仅仅包含了一个新索引创建和别名 rotation 的动作。
  • Curator 和 rollover 并不支持全版本的 ES,后续版本才有的功能
  • 同时 Curator 和 rollover 也没有自动执行、图形化界面、保证系统高可用、多集群索引操作的支持

Index Lifecycle Management

这是ES 6.6推出的一个新功能,基于 X-Pack Basic License,可免费试用。以下是 ILM 的一些相关概念:

  • Policy:定义了一个管理索引的方式,它包含了多个 Phases 和 Actions
  • Phase:定义了索引的各个阶段,并定义了该阶段中需要对索引执行该阶段包含的 Actions 的条件(索引大小最大值、索引文档数量最大个数、索引创建之后的最大时间),其中每个 Phase 都包含了几个固定的 Actions
  • Action:在各个 Phase 中可以对索引进行的操作的集合。

image-20200503114713234

ILM 通过 Kibana Management 进行图形化界面管理的。"watch-history-ilm-policy"是一个自带的策略,它在Hot Phase没有任何动作,同时还定义了一个delete phase,即表示索引创建之后就从Hot Phase过渡到了 Delete Phase,并启动了 Delete Phase 的 Actions:7天后删除索引。也就是说如果对某个索引使用了这个策略,那么它将在创建7天后被删除。

image-20200503115049619

image-20200503115308657

点击"创建策略",其中 Hot Phase 是必须的,每个索引创建之后都是一个 Hot 状态,除非启动了其他Phase,并支持触发索引从 Hot 去到其他 Phase 的条件。另外我们可以启动该阶段的操作(rollover)。其他 Phase 按需设定。:

image-20200503115106680

ILM API 调用 Demo

ILM 其实也是提供了对外的 API 调用的。以下是对于Kibana 的请求Demo

# 运行三个节点,分片 将box_type设置成 hot,warm和cold
# 具体参考 github下,docker-hot-warm-cold 下的docker-compose 文件

# 删除所有索引
DELETE *

# 设置 lifecycle 的定时检测时间间隔为1秒,即每秒都会进行一次检测是否有 有索引满足其匹配的 policy 中满足相关 phase 的actions 条件,如果有就执行 actions;生产环境10分种刷新一次
PUT _cluster/settings
{
  "persistent": {
    "indices.lifecycle.poll_interval":"1s"
  }
}

# 创建一个 Policy
PUT /_ilm/policy/log_ilm_policy
{
	# 包含4个 phases
  "policy": {
    "phases": {
      "hot": {
        # hot 只有一个 rollover 动作
        "actions": {
          "rollover": {
            # 当文档数超过5个就执行 actions
            "max_docs": 5
          }
        }
      },
      "warm": {
        # 设置进入 warm phrase 的条件,10s 后从 hot 进入 warm 阶段,并执行以下 actions
        "min_age": "10s",
        # warm 也只有一个 allocate 动作
        "actions": {
          # (re)allocate 索引到一个 warm 标签节点上
          "allocate": {
            "include": {
              "box_type": "warm"
            }
          }
        }
      },
      "cold": {
        # 15s 后从 warm 进入 cold
        "min_age": "15s",
        # 同 warm
        "actions": {
          "allocate": {
            "include": {
              "box_type": "cold"
            }
          }
        }
      },
      "delete": {
      # 20s 后从 cold 进入 delete
        "min_age": "20s",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}



# 设置索引模版
PUT /_template/log_ilm_template
{
  # 指定可以使用当前模板的索引的规则
  "index_patterns" : [
      "ilm_index-*"
  ],
  "settings" : {
    "index" : {
      # 指定索引管理生命周期的策略为我们前面创建的策略
      "lifecycle" : {
        "name" : "log_ilm_policy",
        "rollover_alias" : "ilm_alias"
      },
      # 索引路由到 hot 节点
      "routing" : {
        "allocation" : {
          "include" : {
            "box_type" : "hot"
          }
        }
      },
      # 一个主分片
      "number_of_shards" : "1",
      "number_of_replicas" : "0"
    }
  },
  "mappings" : { },
  "aliases" : { }
}



#创建索引
PUT ilm_index-000001
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0,
    # 指定了索引生命周期管理策略为前面创建的"log_ilm_policy"
    "index.lifecycle.name": "log_ilm_policy",
    # 指定对该索引执行 rollober api动作的时候指定的别名为"ilm_alias",因为一个索引可能有多个别名,ES 不知道哪个才是用来做 rollover 的
    "index.lifecycle.rollover_alias": "ilm_alias",
    # 指定该索引路由到 hot 节点
    "index.routing.allocation.include.box_type":"hot"
  },
  #  同时为该索引设置 rollover 的别名并设置"is_write_index"为 true,使得rollover 之后的老索引也可以被通过别名查询
  "aliases": {
    "ilm_alias": {
      "is_write_index": true
    }
  }
}

# 对 Alias 写入文档
POST ilm_alias/_doc
{
  "dfd":"dfdsf"
}


   转载规则


《009_管理Elasticsearch集群》 阿钟 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录