Elasticsearch:反向代理及负载均衡在 Elasticsearch 中的应用

在我们配置 Elasticsearh 安全的时候,我们可以考虑三个方面的东西:

  • firewalls
  • 反向代理 (reverse proxies)
  • Elastic Security

我们可以利用 firewall 已经  IP filtering 来限制或允许特定的 IP 地址来访问我们的 Elasticsearch。对于 OSS 的用户来说,他们可以使用 reverse proxy 的方法来实现最基本的安全配置。当然对于 Baisic license 以上的用户来说,我们建议使用 Elastic Security

       

在今天的文章中,我将来讲述如何使用 Nginx 来实现 reverse proxy 已经负载均衡。

 

我的配置

我的配置如下:

                                

我在 MacOS 的机器上安装了 Elasticsearch 及 Kibana。我在 Ubuntu 的机器上安装了 Nginx。如果你还没有安装自己的 Elasticsearch 及 Kibana,请参阅我之前的文章 “Elastic:菜鸟上手指南” 进行安装。特别值得指出的是,为了能够使得我们的 Elasticsearch 能够被 Niginx 所访问,那么请修改 config/elasticsearch.yml 中的配置:

network.host: 0.0.0.0
discovery.type: single-node

 

一点点背景

Elasticsearch 的定义性功能之一是通过基于 HTTP的(宽松的)RESTful 服务公开。

当然,好处很容易阐明:API 对所有Web开发人员都是熟悉且可预测的。 通过 curl 命令或在浏览器中即可轻松使用。 使用各种编程语言编写 API 包装器很容易。

尽管如此,Elasticsearch 基于 HTTP 的本质的重要性已根深蒂固:以其适合于软件开发和体系结构的现有范例的方式。

HTTP作为架构范例

典型的现代软件或信息系统通常是松散耦合服务的集合,这些服务通过网络(通常通过 HTTP)进行通信。在设计方面,此方法的重要方面是,你始终可以“撕裂”服务链,并将另一个添加或更改功能的组件插入“堆栈”。在过去,传统上将其称为“中间件”,但在 RESTful Web 服务的背景下又重新出现,例如 Rack中间件,特别是在 Ruby on Rails 框架中使用。

HTTP 特别适合于此类体系结构,因为它的明显缺点(缺乏状态,基于文本的表示形式,以 URI 为中心的语义……)变成了一个优势:中间件都不必适应“链”中的特定内容,并仅传递状态代码,标题和正文。从这个意义上讲,HTTP 在功能上是透明的–没关系,例如,是否从原始 Web 服务器或其他大陆的缓存中获取图像。仍然是相同的“资源”。

缓存是 HTTP 这方面的一个主要示例,Roy Fielding 在 RESTful 体系结构的开创性工作中已经介绍过。 (有关该主题的详细信息,请参阅Ryan Tomayko的《事物缓存》和Mark Nottingham的《缓存教程》。)

从技术上讲,缓存在这里充当代理–它“代表”堆栈中的其他组件。

但是代理可以做的更多。认证和授权就是一个很好的例子:代理可以拦截对服务的请求,执行认证和/或授权例程,并允许或拒绝对客户端的访问。

这种代理通常称为反向代理。当你认为传统代理将从本地网络“转发”流量到远程网络(互联网)时,此名称有意义,因为在这里反向转发是因为“反向”代理将来自 Internet 的请求转发到“本地”后端。可以使用 Java 或 Go 之类的编程语言或诸如 Node.js 之类的框架来实现这种代理。或者,我们可以使用可配置的网络服务器,例如 Nginx。

Nginx

Nginx 是最初由 Igor Sysoev 编写的开源 Web 服务器,专注于高性能,并发性和低内存占用。 (有关详细的技术概述,请参见《开源应用程序体系结构》一书的相关章节。)

Nginx 从一开始就考虑到代理角色,并且支持许多相关的配置指令和选项。 在 Django 应用程序的 Ruby on Rails 前面将 Nginx 作为负载平衡器运行是相当普遍的。 许多大型 PHP 应用程序甚至将 Nginx 放在运行 mod_php 的 Apache 前面,以加速静态内容的提供和扩展应用程序。 本文的大多数部分都假定安装了标准的 Nginx,但是高级部分依赖于 Lua 模块来安装 Nginx。如果你还没有安装自己的 Nginx,请参考我之前的文章 “Beats:使用Elastic Stack对Nginx Web服务器监控” 来进行安装。

要将 Nginx 用作 Elasticsearch 的 “100%透明”代理,我们需要一个非常小的配置。我们直接修改 /etc/nginx/nginx.conf 文件,并修改 http 的部分如下:

/etc/nginx/nginx.conf

http {
  server {
    listen 8080;
    location / {
      proxy_pass http://192.168.0.3:9200;
    }
  }
}

 请注意上面的 192.168.0.3 是我的 MacOS 的 IP 地址。经过这样的设置,我们重新启动 Niginx 服务:

sudo service nginx restart

我们可以通过如下的命令来检查服务是否正常运行:

sudo service nginx status
$ sudo service nginx status
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset:>
     Active: active (running) since Wed 2020-09-02 17:52:33 CST; 14min ago
       Docs: man:nginx(8)
    Process: 25616 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_proc>
    Process: 25634 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (>
   Main PID: 25635 (nginx)
      Tasks: 9 (limit: 18985)
     Memory: 8.2M
     CGroup: /system.slice/nginx.service
             ├─25635 nginx: master process /usr/sbin/nginx -g daemon on; master>
             ├─25636 nginx: worker process
             ├─25637 nginx: worker process
             ├─25638 nginx: worker process
             ├─25639 nginx: worker process
             ├─25640 nginx: worker process
             ├─25641 nginx: worker process
             ├─25642 nginx: worker process
             └─25643 nginx: worker process

经过上面的改造后,我们可以通过如下的命令来检查反向代理是否已经起作用了:

curl -XGET http://192.168.0.4:8080

我们在 terminal 中打入上面的命令:

curl -XGET http://192.168.0.4:8080
{
  "name" : "liuxg",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "u8tKdzFjQaOiJ5sF_d15oQ",
  "version" : {
    "number" : "7.9.0",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "a479a2a7fce0389512d6a9361301708b92dff667",
    "build_date" : "2020-08-11T21:36:48.204330Z",
    "build_snapshot" : false,
    "lucene_version" : "8.6.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

显然当我们向 Ubuntu 的 IP 地址 192.168.0.4 发送请求时,我们可以看到相应的输出。这个请求的结果来自安装于 IP 192.168.0.3 的 MacOS 上的 Elasticsearch。

这个代理当然是毫无用处的,它只是在客户端和 Elasticsearch 之间移交数据。 尽管精明的读者可能已经猜到它已经在“堆栈”中添加了一些内容,即记录每个请求。

 

使用案例

在本文中,我们将介绍 Nginx 作为 Elasticsearch 的反向代理的一些更有趣的用例。

持久 HTTP 连接

让我们从一个非常简单的示例开始:使用 Nginx 作为代理,以保持与 Elasticsearch 的持久(“keep-alive”)连接。 为什么我们要这样做? 主要原因是为了减轻 Elasticsearch 在使用不支持持久连接的客户端时为每个请求打开和关闭连接的压力。 Elasticsearch 不仅仅是处理网络,还有更多的责任,打开/关闭连接会浪费宝贵的时间和资源(例如打开文件的限制)。

像本文中的所有示例一样,本链接提供了完整的配置。我们创建如下的文件:

/etc/nginx/nginx_keep_alive.conf

events {
    worker_connections  1024;
}
http {
  upstream elasticsearch {
    server 127.0.0.1:9200;
    keepalive 15;
  }
  server {
    listen 8080;
    location / {
      proxy_pass http://192.168.0.3:9200;
      proxy_http_version 1.1;
      proxy_set_header Connection "Keep-Alive";
      proxy_set_header Proxy-Connection "Keep-Alive";
    }
  }
}

让我们以如下的方法来启动 Nginx:

$ pwd
/etc/nginx

$ sudo service nginx stop
$ sudo nginx -p $PWD/nginx/ -c $PWD/nginx_keep_alive.conf

当你直接向 Elasticsearch 执行请求时,你会注意到打开的连接数一直在增加:

liuxg@liuxgu:/etc/nginx$ curl 'mac:9200/_nodes/stats/http?pretty' | grep total_opened
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   753  100   753    0     0   245k      0 --:--:-- --:--:-- --:--:--  245k
        "total_opened" : 94
liuxg@liuxgu:/etc/nginx$ curl 'mac:9200/_nodes/stats/http?pretty' | grep total_opened
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   753  100   753    0     0   245k      0 --:--:-- --:--:-- --:--:--  245k
        "total_opened" : 95
liuxg@liuxgu:/etc/nginx$ curl 'mac:9200/_nodes/stats/http?pretty' | grep total_opened
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   753  100   753    0     0   367k      0 --:--:-- --:--:-- --:--:--  367k
        "total_opened" : 96

我们可以看到向 Elasticsearch 直接发送请求的时候,这个 total_opened 的数值是一直在增加的。但是使用 Nginx 时则完全不同,打开的连接数保持不变 (在 Ubuntu 机器发送的请求):

liuxg@liuxgu:/etc/nginx$ curl 'localhost:8080/_nodes/stats/http?pretty' | grep total_opened
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   753  100   753    0     0   245k      0 --:--:-- --:--:-- --:--:--  245k
        "total_opened" : 97
liuxg@liuxgu:/etc/nginx$ curl 'localhost:8080/_nodes/stats/http?pretty' | grep total_opened
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   753  100   753    0     0   367k      0 --:--:-- --:--:-- --:--:--  367k
        "total_opened" : 97
liuxg@liuxgu:/etc/nginx$ curl 'localhost:8080/_nodes/stats/http?pretty' | grep total_opened
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   753  100   753    0     0   367k      0 --:--:-- --:--:-- --:--:--  367k
        "total_opened" : 97

我们可以看到连接数是保持不变的。

 

简单的负载均衡器

只需很小的配置更改,我们就可以使用它将请求传递到多个 Elasticsearch 节点,并将 Nginx 用作轻量级负载均衡器:

/etc/nginx/nginx_keep_alive.conf

events {
    worker_connections  1024;
}
http {
  upstream elasticsearch {
    server 192.168.0.3:9200;
    server 192.168.0.5:9200;
    server 192.168.0.6:9200;
    server 192.168.0.7:9200;
    keepalive 15;
  }
  server {
    listen 8080;
    location / {
      proxy_pass http://elasticsearch;
      proxy_http_version 1.1;
      proxy_set_header Connection "Keep-Alive";
      proxy_set_header Proxy-Connection "Keep-Alive";
    }
  }
}

如你所见,我们在upstream directive 指令中添加了其它的附加节点。 Nginx 现在将以循环方式自动在这些服务器之间分配请求,从而将Elasticsearch 集群上的负载平均分配到各个节点上。在实际的使用中,我们可以以如下的方式来查询当前使用的 Elasticsearch 节点的名称:

curl localhost:8080 | grep name
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   530  100   530    0     0   172k      0 --:--:-- --:--:-- --:--:--  172k
  "name" : "liuxg",
  "cluster_name" : "elasticsearch",

上面显示我的节点的名称为 liuxg。如果我们有多个节点,我们可以重复使用上面的命令,我们将看到有不同的节点返回结果,就像下面的显示一样:

$ curl localhost:8080 | grep name
# "name" : "Silver Fox",
$ curl localhost:8080 | grep name
# "name" : "G-Force",
$ curl localhost:8080 | grep name
# "name" : "Helleyes",
$ curl localhost:8080 | grep name
# "name" : "Silver Fox",
# ...

这是一种理想的行为,因为它防止命中单个“热”节点,该节点必须执行常规节点职责,还路由所有流量并执行所有其他关联的操作。 要更改配置,我们只需要更新 upstream directive 中的服务器列表,并向 Nginx 发送一个重载信号即可。

有关 Nginx 负载平衡功能的更多信息,包括不同的平衡策略,为不同的节点设置“权重”,运行状况检查和实时监控,请参阅使用NGINX和NGINX Plus进行负载平衡

(请注意,正式的 Elasticsearch 客户端可以自行执行这种负载平衡,并具有自动重新加载集群中节点列表,在另一个节点上重试请求等功能)。

 

结论

在本文中,我们充分利用了 Elasticsearch 是通过 HTTP 公开的服务这一事实。 我们已经看到 HTTP 在概念上如何很好地适合于将软件体系结构设计为独立的,分离的服务的当前范例,以及如何将 Nginx 用作高性能,可定制的代理。

参考

【1】https://sysadmins.co.za/secure-your-elasticsearch-cluster-with-basic-auth-using-nginx-and-ssl-from-letsencrypt/

【2】https://www.elastic.co/blog/tips-to-secure-elasticsearch-clusters-for-free-with-encryption-users-and-more