Ansible Playbook으로 Elastic Stack 자동 업데이트
게시 됨: 2022-03-11Elastic Stack을 중앙 집중식 로깅 솔루션으로 사용하기로 결정하기 전에는 수천 대의 장치로 구성된 네트워크에 대한 로그 분석이 복잡하고 시간이 많이 걸리며 지루한 작업이었습니다. 그것은 매우 현명한 결정으로 판명되었습니다. 한 곳에서 모든 로그를 검색할 수 있을 뿐만 아니라 검색에 대한 거의 즉각적인 결과, 분석 및 문제 해결에 매우 유용한 강력한 시각화, 네트워크에 대한 유용한 개요를 제공하는 아름다운 대시보드를 얻을 수 있습니다.
Elastic Stack은 지속적으로 새롭고 놀라운 기능을 출시하고 있으며 매우 활발한 개발 속도를 유지하고 있으며 종종 매월 2개의 새로운 릴리스를 제공합니다. 저는 새로운 기능과 개선 사항을 활용할 수 있도록 항상 환경을 최신 상태로 유지하는 것을 좋아합니다. 또한 버그 및 보안 문제로부터 무료로 유지합니다. 하지만 이를 위해서는 환경을 지속적으로 업데이트해야 합니다.
Elastic 웹사이트는 제품의 업그레이드 프로세스를 포함하여 명확하고 자세한 문서를 유지하고 있지만, 수동으로 업그레이드하는 것은 복잡한 작업, 특히 Elasticsearch 클러스터입니다. 관련된 단계가 많이 있으며 매우 구체적인 순서를 따라야 합니다. 그래서 오래전에 Ansible Playbooks를 사용하여 전체 프로세스를 자동화하기로 결정했습니다.
이 Ansible 튜토리얼에서는 Elastic Stack 설치를 자동 업그레이드하기 위해 개발된 일련의 Ansible 플레이북을 살펴보겠습니다.
Elastic Stack이란?
이전에 ELK 스택으로 알려졌던 Elastic Stack은 오픈 소스 회사 Elastic의 Elasticsearch, Logstash 및 Kibana로 구성되며, 함께 데이터 인덱싱, 검색 및 분석을 위한 강력한 플랫폼을 제공합니다. 광범위한 응용 분야에 사용할 수 있습니다. 로깅 및 보안 분석부터 애플리케이션 성능 관리 및 사이트 검색까지.
Elasticsearch 는 스택의 핵심입니다. 방대한 양의 저장된 데이터에 대해서도 거의 실시간에 가까운 검색 결과를 제공할 수 있는 분산 검색 및 분석 엔진입니다.
Logstash 는 다양한 소스(내가 쓰고 있는 50개의 공식 입력 플러그인)에서 데이터를 가져오거나 받고, 이를 구문 분석하고, 필터링 및 변환하고, 가능한 출력 중 하나 이상으로 보내는 처리 파이프라인입니다. 우리의 경우 Elasticsearch 출력 플러그인에 관심이 있습니다.
Kibana 는 사용자 및 작업 프론트엔드입니다. 이를 통해 데이터를 시각화, 검색, 탐색하고 이에 대한 놀라운 통찰력을 제공하는 대시보드를 만들 수 있습니다.
앤서블이란?
Ansible은 시스템 구성, 소프트웨어 배포 또는 업그레이드, 복잡한 IT 작업 조정에 사용할 수 있는 IT 자동화 플랫폼입니다. 주요 목표는 단순성과 사용 용이성입니다. Ansible에서 제가 가장 좋아하는 기능은 에이전트가 없다는 것입니다. 즉, 관리하려는 호스트와 장치에 추가 소프트웨어를 설치하고 관리할 필요가 없습니다. Ansible 자동화의 힘을 사용하여 Elastic Stack을 자동 업그레이드할 것입니다.
면책 조항 및 주의 사항
여기서 공유할 플레이북은 공식 제품 설명서에 설명된 단계를 기반으로 합니다. 동일한 주 버전의 업그레이드에만 사용하도록 되어 있습니다. 예: 5.x
→ 5.y
또는 6.x
→ 6.y
여기서 x
> y
. 주요 버전 간의 업그레이드에는 종종 추가 단계가 필요하며 해당 플레이북은 이러한 경우에 작동하지 않습니다.
그럼에도 불구하고 플레이북을 사용하여 업그레이드하기 전에 릴리스 정보, 특히 주요 변경 사항 섹션을 항상 읽으십시오. 플레이북에서 실행되는 작업을 이해하고 중요한 변경 사항이 없는지 항상 업그레이드 지침을 확인하십시오.
그렇긴 하지만, 저는 Elasticsearch 버전 2.2 이후로 문제 없이 해당 플레이북(또는 이전 버전)을 사용하고 있습니다. 그 당시 나는 그들이 알고 있는 것과 동일한 버전 번호를 공유하지 않았기 때문에 각 제품에 대해 완전히 별도의 플레이북을 가지고 있었습니다.
그러나 나는 이 기사에 포함된 정보의 사용에 대해 어떠한 책임도 지지 않습니다.
우리의 가상 환경
플레이북이 실행될 환경은 6개의 CentOS 7 서버로 구성됩니다.
- 1 x Logstash 서버
- 1 x 키바나 서버
- 4 x Elasticsearch 노드
환경에 다른 수의 서버가 있는지 여부는 중요하지 않습니다. 인벤토리 파일에 적절하게 반영하기만 하면 플레이북이 문제 없이 실행됩니다. 그러나 RHEL 기반 배포를 사용하지 않는 경우 배포와 관련된 몇 가지 작업(주로 패키지 관리자 항목)을 변경하는 연습으로 남겨 둡니다.
인벤토리
Ansible은 플레이북을 실행해야 하는 호스트를 알기 위해 인벤토리가 필요합니다. 네 가지 가상 시나리오에서 다음 인벤토리 파일을 사용할 것입니다.
[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]
은 호스트 그룹을 나타냅니다. 인벤토리에는 logstash
, kibana
및 elasticsearch
의 3가지 호스트 그룹이 있습니다. 플레이북에서는 그룹 이름만 사용한다는 것을 알 수 있습니다. 즉, 그룹이 올바르면 플레이북이 실행되는 한 인벤토리의 호스트 수는 중요하지 않습니다.
업그레이드 프로세스
업그레이드 프로세스는 다음 단계로 구성됩니다.
1) 패키지 사전 다운로드
2) Logstash 업그레이드
3) Elasticsearch 클러스터의 롤링 업그레이드
4) 키바나 업그레이드
주요 목표는 가동 중지 시간을 최소화하는 것입니다. 대부분의 경우 사용자는 알아차리지 못할 것입니다. 때때로 Kibana를 몇 초 동안 사용하지 못할 수 있습니다. 그것은 나에게 받아들여질 수 있다.
주요 Ansible 플레이북
업그레이드 프로세스는 다양한 플레이북 세트로 구성됩니다. Ansible의 import_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
상당히 간단합니다. 이는 플레이북의 실행을 하나의 특정 순서로 구성하는 방법일 뿐입니다.
이제 위의 Ansible 플레이북 예제를 사용하는 방법을 고려해 보겠습니다. 나중에 구현하는 방법을 설명하겠지만 버전 6.5.4로 업그레이드하기 위해 실행할 명령은 다음과 같습니다.
$ ansible-playbook -i inventory -e elk_version=6.5.4 main.yml
패키지 사전 다운로드
그 첫 번째 단계는 실제로 선택 사항입니다. 이것을 사용하는 이유는 일반적으로 실행 중인 서비스를 업그레이드하기 전에 중지하는 것이 좋습니다. 이제 빠른 인터넷 연결이 있는 경우 패키지 관리자가 패키지를 다운로드하는 데 걸리는 시간은 무시할 수 있습니다. 그러나 항상 그런 것은 아니며 서비스가 중단되는 시간을 최소화하고 싶습니다. 이것이 내 첫 번째 플레이북에서 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의 버전을 나타냅니다. 이는sible-playbook 명령을 호출할 때 전달됩니다. 변수가 전달되지 않거나 유효한 형식이 아닌 경우 재생이 즉시 구제됩니다. 그 작업은 실제로 모든 연극의 첫 번째 작업이 될 것입니다. 그 이유는 원하거나 필요한 경우 연극을 격리하여 실행할 수 있도록 하기 위함입니다.
두 번째 작업은 rpm
명령을 사용하여 Logstash의 현재 버전을 가져오고 version_found
변수에 등록합니다. 해당 정보는 다음 작업에서 사용됩니다. args:
, warn: no
및 changed_when: False
행은 sible-lint를 행복하게 만들기 위해 존재하지만 꼭 필요한 것은 아닙니다.
마지막 작업은 실제로 패키지를 미리 다운로드하는 명령을 실행합니다. 그러나 설치된 Logstash 버전이 대상 버전보다 오래된 경우에만 가능합니다. 포인트 다운로드 및 이전 버전 또는 동일한 버전을 사용하지 않을 경우.
다른 두 플레이는 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, '<')
조건부 when
은 대상 버전이 현재 버전보다 최신인 경우에만 블록의 작업이 실행되도록 보장합니다. 블록 내의 첫 번째 작업은 Logstash 업그레이드를 수행하고 두 번째 작업은 서비스를 다시 시작합니다.
Elasticsearch 클러스터 롤링 업그레이드
Elasticsearch 클러스터에 다운타임이 없도록 하려면 롤링 업그레이드를 수행해야 합니다. 즉, 클러스터가 녹색 상태(완전히 정상)인지 확인한 후에만 노드 업그레이드를 시작하여 한 번에 하나의 노드를 업그레이드합니다.
플레이 시작부터 당신은 뭔가 다른 것을 알 수 있을 것입니다:
- name: Elasticsearch rolling upgrade hosts: elasticsearch gather_facts: no serial: 1
여기에 우리는 라인 serial: 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
각 변수의 의미는 극에 등장하는 대로 명확해집니다.
항상 그렇듯이 첫 번째 작업은 대상 버전을 확인하는 것입니다.
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 호출을 실행할 다른 호스트를 선택해야 합니다. 이를 위해 Ansible 인벤토리의 set_fact 모듈과 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
이전 플레이와 마찬가지로 현재 버전을 확인합니다. 이 시간을 제외하고는 rpm을 실행하는 대신 Elasticsearch REST API를 사용합니다. 우리는 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
노드 서비스가 다시 시작된 후 클러스터가 녹색으로 돌아가는 데 오랜 시간이 걸릴 수 있습니다. 이것이 라인 retries: 500
및 delay: 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입니다.
예상할 수 있듯이 첫 번째 작업은 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의 경우 그것으로 충분했습니다. 불행히도 어떤 이유로 업그레이드 후 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 플레이북이 저에게만큼 여러분에게도 유용할 수 있기를 바랍니다.
GitHub(https://github.com/orgito/elk-upgrade)에서 사용할 수 있도록 했습니다. 비프로덕션 환경에서 테스트하는 것이 좋습니다.
앱에 Elasticsearch를 통합하려는 Ruby on Rails 개발자라면 Elasticsearch for Ruby on Rails: A Tutorial to the Chewy Gem by Core Toptal 소프트웨어 엔지니어 Arkadiy Zabazhanov를 확인하십시오.