个性化阅读
专注于IT技术分析

使用Ansible Playbook自动更新Elastic Stack

本文概述

在决定使用Elastic Stack作为集中式日志记录解决方案之前, 对由数千个设备组成的网络进行日志分析曾经是一项复杂, 耗时且乏味的任务。事实证明, 这是一个非常明智的决定。我不仅可以在一个地方搜索所有日志, 还可以在搜索中获得几乎即时的结果, 强大的可视化功能, 这些功能对于分析和故障排除非常有用, 而漂亮的仪表板则可以使我对网络有一个有用的概览。

Elastic Stack不断发布令人惊叹的新功能, 以保持非常活跃的开发速度, 它通常每个月提供两个新版本。我喜欢始终保持环境最新, 以确保可以利用新功能和改进。还可以使其免受错误和安全问题的侵扰。但这要求我不断更新环境。

即使Elastic网站维护着清晰详细的文档(包括产品升级过程), 但手动升级还是一项复杂的任务, 尤其是Elasticsearch集群。其中涉及许多步骤, 需要遵循非常特定的顺序。这就是为什么我很久以前决定使用Ansible Playbooks使整个过程自动化的原因。

在本Ansible教程中, 我将引导我们完成一系列Ansible Playbooks, 它们是为自动升级我的Elastic Stack安装而开发的。

什么是弹性堆栈

Elastic Stack(以前称为ELK堆栈)由开源公司Elastic的Elasticsearch, Logstash和Kibana组成, 它们共同为索引, 搜索和分析数据提供了强大的平台。它可以用于广泛的应用程序。从日志记录和安全分析到应用程序性能管理和站点搜索。

  • Elasticsearch是堆栈的核心。它是一个分布式搜索和分析引擎, 即使对大量存储的数据也能够提供近乎实时的搜索结果。

  • Logstash是一个处理管道, 可以从许多不同的源(我正在编写的50个官方输入插件)中获取或接收数据, 对其进行解析, 过滤和转换并将其发送到一个或多个可能的输出中。在我们的案例中, 我们对Elasticsearch输出插件感兴趣。

  • Kibana是你的用户和操作前端。它使你可以可视化, 搜索, 导航数据并创建仪表板, 从而为你提供有关数据的惊人见解。

什么是Ansible

Ansible是一个IT自动化平台, 可用于配置系统, 部署或升级软件以及协调复杂的IT任务。它的主要目标是简单和易用。我最喜欢的Ansible功能是它没有代理, 这意味着我不需要在要管理的主机和设备上安装和管理任何其他软件。我们将使用Ansible自动化的功能来自动升级我们的Elastic Stack。

免责声明和警告语

我将在此处分享的Playbook基于官方产品文档中描述的步骤。它只能用于相同主要版本的升级。例如:5.x→5.y或6.x→6.y, 其中x> y。在主要版本之间进行升级通常需要采取额外的步骤, 而这些Playbook将不适用于这些情况。

无论如何, 在使用Playbook进行升级之前, 请务必阅读发行说明, 尤其是重大更改部分。确保你了解Playbook中执行的任务, 并始终查看升级说明以确保没有重要的更改。

话虽如此, 我从2.2版本的Elasticsearch开始使用这些Playbook(或更早的版本)没有任何问题。当时, 我对每种产品都有完全不同的Playbook, 因为它们没有共享他们所知道的相同版本号。

话虽如此, 我对你使用本文中包含的信息概不负责。

我们的虚拟环境

我们的Playbook将在其上运行的环境将由6个CentOS 7服务器组成:

  • 1个Logstash服务器
  • 1 x Kibana服务器
  • 4个Elasticsearch节点

环境是否具有不同数量的服务器都没有关系。你可以将其相应地反映在清单文件中, 并且Playbook应该可以正常运行。但是, 如果你没有使用基于RHEL的发行版, 那么我将作为练习来更改一些特定于发行版的任务(主要是软件包管理器的内容)

我们的Ansible Playbook将部署的示例Elastic Stack

存货

Ansible需要一个清单, 以了解应针对哪些主机运行Playbook。在我们的四种假想场景中, 我们将使用以下清单文件:

[logstash]
server01 ansible_host=10.0.0.1

[kibana]
server02 ansible_host=10.0.0.2

[elasticsearch]
server03 ansible_host=10.0.0.3
server04 ansible_host=10.0.0.4
server05 ansible_host=10.0.0.5
server06 ansible_host=10.0.0.6

在Ansible清单文件中, 任何[section]代表一组主机。我们的清单包含3组主机:logstash, kibana和elasticsearch。你会注意到, 我只在Playbook中使用组名。这意味着清单中的主机数量无关紧要, 只要组是正确的即可运行Playbook。

升级过程

升级过程将包括以下步骤:

1)预下载软件包

2)Logstash升级

3)Elasticsearch集群的滚动升级

4)Kibana升级

主要目标是最大程度地减少停机时间。大多数情况下, 用户甚至不会注意到。有时Kibana可能几秒钟不可用。我可以接受。

主要Ansible Playbook

升级过程由一组不同的Playbook组成。我将使用Ansible的import_playbook功能将所有Playbook组织在一个主Playbook文件中, 可以调用该文件来完成整个过程。

- name: pre-download
  import_playbook: pre-download.yml

- name: logstash-upgrade
  import_playbook: logstash-upgrade.yml

- name: elasticsearch-rolling-upgrade
  import_playbook: elasticsearch-rolling-upgrade.yml

- name: kibana-upgrade
  import_playbook: kibana-upgrade.yml

很简单。这只是一种按特定顺序组织执行Playbook的方法。

现在, 让我们考虑如何使用上面的Ansible Playbook示例。我将在稍后说明如何实现它, 但这是升级到6.5.4版时要执行的命令:

$ ansible-playbook -i inventory -e elk_version=6.5.4 main.yml

预下载软件包

第一步实际上是可选的。我之所以使用它, 是因为我认为通常的良好做法是在升级服务之前先停止正在运行的服务。现在, 如果你具有快速的Internet连接, 则包管理器下载包的时间可以忽略不计。但这并非总是如此, 我想最大程度地减少任何服务中断的时间。这样, 我的第一本Playbook将使用yum来预下载所有软件包。这样, 当升级时间到来时, 就已经完成了下载步骤。

- hosts: logstash
  gather_facts: no

  tasks:
  - name: Validate logstash Version
    fail: msg="Invalid ELK Version"
    when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+")
  - name: Get logstash current version
    command: rpm -q logstash --qf %{VERSION}
    args:
      warn: no
    changed_when: False
    register: version_found

  - name: Pre-download logstash install package
    yum:
      name: logstash-{{ elk_version }}
      download_only: yes
    when: version_found.stdout is version_compare(elk_version, '<')

第一行表示此播放仅适用于logstash组。第二行告诉Ansible不要打扰收集有关主机的事实。这样可以加快播放速度, 但是请确保播放中的所有任务都不需要有关主机的任何事实。

剧中的第一个任务将验证elk_version变量。此变量表示我们要升级到的Elastic Stack的版本。当你调用ansible-playbook命令时, 将通过该命令。如果未传递变量或格式无效, 则播放将立即退出。该任务实际上将是所有Playbook中的第一个任务。这样做的原因是, 如果需要或必要, 则允许隔离播放Playbook。

第二个任务将使用rpm命令获取Logstash的当前版本, 并在变量version_found中注册。该信息将在下一个任务中使用。 args:, warn:no和changed_when:False行使ansible-lint感到高兴, 但并非绝对必要。

最后的任务将执行实际预下载软件包的命令。但仅当Logstash的安装版本早于目标版本时。如果不使用, 则不要指向下载和较旧版本或相同版本。

其他两个Playbook基本上相同, 除了它们将代替Logstash预先下载Elasticsearch和Kibana:

- hosts: elasticsearch
  gather_facts: no

  tasks:
  - name: Validate elasticsearch Version
    fail: msg="Invalid ELK Version"
    when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+")

  - name: Get elasticsearch current version
    command: rpm -q elasticsearch --qf %{VERSION}
    args:
      warn: no
    changed_when: False
    register: version_found

  - name: Pre-download elasticsearch install package
    yum:
      name: elasticsearch-{{ elk_version }}
      download_only: yes
    when: version_found.stdout is version_compare(elk_version, '<')

- hosts: kibana
  gather_facts: no

  tasks:
  - name: Validate kibana Version
    fail: msg="Invalid ELK Version"
    when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+")

  - name: Get kibana current version
    command: rpm -q kibana --qf %{VERSION}
    args:
      warn: no
    changed_when: False
    register: version_found

  - name: Pre-download kibana install package
    yum:
      name: kibana-{{ elk_version }}
      download_only: yes
    when: version_found.stdout is version_compare(elk_version, '<')

Logstash升级

Logstash应该是第一个要升级的组件。这是因为保证Logstash可与旧版本的Elasticsearch一起使用。

该剧的第一个任务与下载前的任务相同:

- name: Upgrade logstash
  hosts: logstash
  gather_facts: no

  tasks:
  - name: Validate ELK Version
    fail: msg="Invalid ELK Version"
    when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+")

  - name: Get logstash current version
    command: rpm -q logstash --qf %{VERSION}
    changed_when: False
    register: version_found

最后两个任务包含在一个块中:

  - block:
    - name: Update logstash
      yum:
        name: logstash-{{ elk_version }}
        state: present

    - name: Restart logstash
      systemd:
        name: logstash
        state: restarted
        enabled: yes
        daemon_reload: yes
    when: version_found.stdout is version_compare(elk_version, '<')

条件何时保证仅当目标版本比当前版本新时, 才执行块中的任务。该块中的第一个任务执行Logstash升级, 第二个任务重新启动服务。

Elasticsearch集群滚动升级

为了确保Elasticsearch集群不会停机, 我们必须执行滚动升级。这意味着我们将一次升级一个节点, 只有在确保集群处于绿色状态(完全正常)后才开始升级任何节点。

从Playbook开始, 你会发现一些不同的地方:

- name: Elasticsearch rolling upgrade
  hosts: elasticsearch
  gather_facts: no
  serial: 1

这是串行行:1. Ansible的默认行为是对多个主机并行执行播放, 即在配置中定义的并发主机数。这行代码可确保一次只能对一个主机执行播放。

接下来, 我们定义一些在播放过程中使用的变量:

vars:
  es_disable_allocation: '{"transient":{"cluster.routing.allocation.enable":"none"}}'
  es_enable_allocation: '{"transient":{"cluster.routing.allocation.enable": "all", "cluster.routing.allocation.node_concurrent_recoveries": 5, "indices.recovery.max_bytes_per_sec": "500mb"}}'
  es_http_port: 9200
  es_transport_port: 9300

每个变量的含义在Playbook中都会很清楚。

与往常一样, 第一个任务是验证目标版本:

 tasks:
  - name: Validate ELK Version
    fail: msg="Invalid ELK Version"
    when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+")

以下许多任务将包括对Elasticsearch集群执行REST调用。可以针对任何节点执行该调用。你可以简单地在播放中针对当前主机执行它, 但是某些命令将在当前主机的Elasticsearch服务关闭时执行。因此, 在接下来的任务中, 我们确保选择其他主机来运行REST调用。为此, 我们将使用set_fact模块和Ansible库存中的groups变量。

 - name: Set the es_host for the first host
    set_fact:
      es_host: "{{ groups.elasticsearch[1] }}"
    when: "inventory_hostname == groups.elasticsearch[0]"

  - name: Set the es_host for the remaining hosts
    set_fact:
      es_host: "{{ groups.elasticsearch[0] }}"
    when: "inventory_hostname != groups.elasticsearch[0]"

接下来, 我们在继续操作之前确保服务在当前节点中已启动:

  - name: Ensure elasticsearch service is running
    systemd:
      name: elasticsearch
      enabled: yes
      state: started
    register: response

  - name: Wait for elasticsearch node to come back up if it was stopped
    wait_for:
      port: "{{ es_transport_port }}"
      delay: 45
    when: response.changed == true

像以前的Playbook一样, 我们将检查当前版本。除了这次, 我们将使用Elasticsearch REST API而不是运行rpm。我们也可以使用rpm命令, 但是我想展示这种选择。

  - name: Check current version
    uri:
      url: http://localhost:{{ es_http_port }}
      method: GET
    register: version_found
    retries: 10
    delay: 10

其余任务位于一个块内, 仅当当前版本早于目标版本时才执行:

 - block:
    - name: Enable shard allocation for the cluster
      uri:
        url: http://localhost:{{ es_http_port }}/_cluster/settings
        method: PUT
        body_format: json
        body: "{{ es_enable_allocation }}"

现在, 如果你遵循我的建议并阅读了文档, 你将注意到此步骤应该相反:禁用分片分配。我希望首先将这项任务放在此处, 以防以前由于某种原因而禁用了分片。这很重要, 因为下一个任务将等待群集变为绿色。如果禁用分片分配, 群集将保持黄色, 并且任务将挂起, 直到超时。

因此, 在确保启用分片分配之后, 我们确保集群处于绿色状态:

    - name: Wait for cluster health to return to green
      uri:
        url: http://localhost:{{ es_http_port }}/_cluster/health
        method: GET
      register: response
      until: "response.json.status == 'green'"
      retries: 500
      delay: 15

节点服务重启后, 群集可能需要很长时间才能恢复为绿色。这就是线路重试的原因:500, 然后重试:15。这意味着我们将等待125分钟(500 x 15秒), 使群集恢复为绿色。如果你的节点包含大量数据, 则可能需要进行调整。在大多数情况下, 这远远不够。

现在我们可以禁用分片分配:

    - name: Disable shard allocation for the cluster
      uri:
        url: http://localhost:{{ es_http_port }}/_cluster/settings
        method: PUT
        body_format: json
        body: {{ es_disable_allocation }}

在关闭服务之前, 我们执行可选的但仍推荐的同步刷新。当执行同步刷新时, 某些索引会出现409错误并不罕见。由于这是可以忽略的, 因此我在成功状态代码列表中添加了409。

    - name: Perform a synced flush
      uri:
        url: http://localhost:{{ es_http_port }}/_flush/synced
        method: POST
        status_code: "200, 409"

现在, 该节点已准备好进行升级:

    - name: Shutdown elasticsearch node
      systemd:
        name: elasticsearch
        state: stopped

    - name: Update elasticsearch
      yum:
        name: elasticsearch-{{ elk_version }}
        state: present

服务停止后, 我们等待所有分片被分配, 然后再次启动节点:

- name: Wait for all shards to be reallocated
  uri: url=http://{{ es_host }}:{{ es_http_port }}/_cluster/health method=GET
  register: response
  until: "response.json.relocating_shards == 0"
  retries: 20
  delay: 15

重新分配分片后, 我们重新启动Elasticsearch服务, 并等待其完全准备就绪:

    - name: Start elasticsearch
      systemd:
        name: elasticsearch
        state: restarted
        enabled: yes
        daemon_reload: yes

    - name: Wait for elasticsearch node to come back up
      wait_for:
        port: "{{ es_transport_port }}"
        delay: 35

    - name: Wait for elasticsearch http to come back up
      wait_for:
        port: "{{ es_http_port }}"
        delay: 5

现在, 在重新启用分片分配之前, 请确保集群为黄色或绿色:

    - name: Wait for cluster health to return to yellow or green
      uri:
        url: http://localhost:{{ es_http_port }}/_cluster/health
        method: GET
      register: response
      until: "response.json.status == 'yellow' or response.json.status == 'green'"
      retries: 500
      delay: 15

    - name: Enable shard allocation for the cluster
      uri:
        url: http://localhost:{{ es_http_port }}/_cluster/settings
        method: PUT
        body_format: json
        body: "{{ es_enable_allocation }}"
      register: response
      until: "response.json.acknowledged == true"
      retries: 10
      delay: 15

然后, 我们等待节点完全恢复, 然后再处理下一个节点:

    - name: Wait for the node to recover
      uri:
        url: http://localhost:{{ es_http_port }}/_cat/health
        method: GET
        return_content: yes
      register: response
      until: "'green' in response.content"
      retries: 500
      delay: 15

当然, 正如我之前说过的, 仅当我们真正升级版本时, 才应执行此块:

  when: version_found.json.version.number is version_compare(elk_version, '<')

Kibana升级

最后要升级的组件是Kibana。

如你所料, 第一个任务与Logstash升级或预下载过程没有什么不同。除了一个变量的定义:

- name: Upgrade kibana
  hosts: kibana
  gather_facts: no
  vars:
    set_default_index: '{"changes":{"defaultIndex":"syslog"}}'

  tasks:
  - name: Validate ELK Version
    fail: msg="Invalid ELK Version"
    when: elk_version is undefined or not elk_version is match("\d+\.\d+\.\d+")

  - name: Get kibana current version
    command: rpm -q kibana --qf %{VERSION}
    args:
      warn: no
    changed_when: False
    register: version_found

当我们进入使用set_default_index变量的任务时, 我将对其进行解释。

其余任务将位于一个块中, 该块仅在安装的Kibana版本比目标版本旧时才执行。前两个任务将更新并重新启动Kibana:

    - name: Update kibana
      yum:
        name: kibana-{{ elk_version }}
        state: present

    - name: Restart kibana
      systemd:
        name: kibana
        state: restarted
        enabled: yes
        daemon_reload: yes

对于基巴纳来说, 这应该就足够了。不幸的是, 由于某些原因, 升级后, Kibana失去了对默认索引模式的引用。这导致它要求升级后的第一个用户访问以定义默认的索引模式, 这可能会引起混乱。为了避免这种情况, 请确保包含一个任务来重置默认索引模式。在下面的示例中, 它是syslog, 但是你应该将其更改为你使用的任何名称。不过, 在设置索引之前, 我们必须确保Kibana已启动并准备好处理请求:

    - name: Wait for kibana to start listening
      wait_for:
        port: 5601
        delay: 5

    - name: Wait for kibana to be ready
      uri:
        url: http://localhost:5601/api/kibana/settings
        method: GET
      register: response
      until: "'kbn_name' in response and response.status == 200"
      retries: 30
      delay: 5

    - name: Set Default Index
      uri:
        url: http://localhost:5601/api/kibana/settings
        method: POST
        body_format: json
        body: "{{ set_default_index }}"
        headers:
          "kbn-version": "{{ elk_version }}"

总结

Elastic Stack是一个有价值的工具, 如果你还没有使用它, 我绝对建议你看看。它的现状非常好, 而且还在不断改进, 以至于很难跟上不断升级的步伐。我希望这些Ansible Playbook对你和我一样有用。

我在GitHub上提供了它们, 网址为https://github.com/orgito/elk-upgrade。我建议你在非生产环境中对其进行测试。

如果你是Ruby on Rails开发人员, 希望将Elasticsearch纳入你的应用程序, 请查看Core srcmini软件工程师Arkadiy Zabazhanov的Elasticsearch for Ruby on Rails:”耐嚼宝石教程”。

赞(0)
未经允许不得转载:srcmini » 使用Ansible Playbook自动更新Elastic Stack

评论 抢沙发

评论前必须登录!