# 喬叔的 Elastic Stack 專業教育訓練

提供 Elasticsearch、ELK (Elasticsearch, Logstash, Kibana)、Elastic Stack (Elasticsearch, Kibana, Beats, Logstash) 技術的教育訓練與企業顧問服務。

## 教學特色

### 📚 系統化的學習方式

經過統整與更新的教學資訊，確保學員獲得全面又完整的知識與訊息。

### 🔎 深入了解運作原理

重視探究運作原理，從源頭了解問題的根本，掌握技術的基礎知識，進而減少摸索與踩坑所浪費的時間。

### 🦾 學習業界實務案例

喬叔擁有二十幾年的軟體開發與多年的 Elasticsearch 實戰與顧問經驗，課堂上將會帶入些業界實務案例，使學員學習到書本知識以外的經驗。

### 👨🏻‍🏫 課堂上即時詢問解惑

在課前我們都會收集學員們對課程的期待，再做範圍內的調整。並且在課堂上學習，可即時發問與解惑，講師會依據豐富的經驗為您找到正確的方向。


# 關於喬叔 (Joe Wu)

## Elastic 相關經驗

* 超過 20 年的軟體開發經驗
* 超過 10 年 Elasticsearch 的實戰經驗與多年 Elasticsearch 專業授課及企業培訓經驗。
* 曾擔任美國某新創公司 Distributed & Search Solution Consultant。
* 協助多間知名企業提供 Elastic 相關的技術支援及顧問服務。
* 曾於工研院、台x電、台灣xx大、鈦x科技、HxC、LxxE…等多間知名企業進行 Elastic 教育訓練。
* 現任 Facebook [Elasticsearch Taiwan - ELK 台灣臉書群](https://www.facebook.com/groups/elastictaiwan) 版主之一。

## 獲獎與認證紀錄

* 2023年 榮獲 2023 Elastic Contributor Program 亞太區銀牌。
* 2022年 榮獲 2022 Elastic Contributor Program 亞太區銀牌。
* 2021年 獲得 Elastic Certified Observability Engineer 認證。
* 2021年 IT邦幫忙 第13屆鐵人賽 DevOps 組冠軍。
* 2021年 榮獲 2021 Elastic Contributor Program 亞太區銀牌。
* 2020年 IT邦幫忙 第12屆鐵人賽 Elastic Stack on Cloud 組冠軍。
* 2018年 成為台灣第一位 Elastic Certified Engineer 認證。

## 其他經歷

* 2015年 創業並於新創公司大量使用 Elastic Stack 於新創產品中的功能開發、數據分析、運維監控。
* 2013年 於舊金山參加 Elastic 原廠開設的 Core Elasticsearch Training。
* 2013年 導入 Elasticsearch 於千萬級使用者的跨國產品中，提供多國語言的搜尋功能。

## 著作

* 喬叔帶你上手 Elastic Stack : Elasticsearch 的最佳實踐與最佳化技巧 (博碩文化) ：[天瓏書局](https://www.tenlong.com.tw/products/9789864348572)
* IT邦幫忙 第 12 屆鐵人賽 Elastic Stack on Cloud 分組**冠軍**作品： [喬叔帶你上手 Elastic Stack](https://ithelp.ithome.com.tw/users/20129543/ironman/3148) ([GitBook連結](/tech-sharing/uncle-joe-teach-es-elasticsearch))
* IT邦幫忙 第 13 屆鐵人賽 DevOps 分組**冠軍**作品： [喬叔帶你上手 Elastic Stack - 探索與實踐 Observability 系列](https://ithelp.ithome.com.tw/users/20129543/ironman/4841) ([GitBook連結](/tech-sharing/uncle-joe-teach-es-elastc-observability))

## 其他參考資訊

* 社群直播分享，超過萬人觀看：[喬叔 Elasticsearch Index 管理與效能優化技巧](https://www.facebook.com/watch/live/?v=871207100098404\&ref=watch_permalink)
* 其餘相關課程、書籍與 Elastic 相關討論都會公告在粉絲頁：[喬叔 - Elastic Stack 技術交流](https://www.facebook.com/Joe.ElasticStack)
* 想了解喬叔的更多專業背景，歡迎參考 [LinkedIn](https://www.linkedin.com/in/joe623/)。


# Elasticsearch 基礎實務班

這門課會帶學員從零開始了解 Elasticsearch 基礎的認識、底層運作原理、以及最新版本的改動與應用，並分享實務上可能會踩坑的地方、業界實務的使用技巧等。課程特色會使用大量的圖解設計來講解，讓學員更有效的吸收與學習，此門課程乾貨滿滿，保證學員們都能打好基礎、學會核心知識，無論是曾經使用、正在使用或是考慮使用 Elasticsearch 的學員都適合來上這門課程

## 課程介紹

喬叔在軟體業界已有二十幾年資歷，從軟體開發至中高階管理並有三次創業的經驗。相當熟悉在軟體開發、系統管理、整合與運維會碰到的所有難題，而在十年的 Elastic Stack 實戰與教學的經驗中，更發現了解運作原理、最佳化設定、正確規劃與使用的對於開發與維運人員相當重要，甚至影響企業的開發效率與核心競爭力。

**Elasticsearch 的教學在網路上的資訊及官網文件已經相當豐富，為什麼還需要這門這門課程？**&#x5728;喬叔多年的實戰與顧問經驗中，幫助企業解決了許多重要又緊急的技術問題，即使有這麼多年的經驗，在每一次的協助解決的過程中還是淬煉出許多知識與技巧，而這些重要的知識都是網路文章找不到或是不夠被系統化整理好的。喬叔開設這門課程除了本身的教學熱情外，自己也希望能將多年的努力與所學最大化，利用知識的傳播與顧問的協助，幫助更多的企業與IT技術人員能避掉許多技術坑洞與避免技術債產生，期盼能盡自己一己之力讓企業與技術人員們都有更好、更正向的發展。

## 報名梯次

* Elasticsearch 基礎實務班 2025/05/18 (<mark style="color:red;">**日**</mark>) 與 05/24 (六) 共二天
* Elasticsearch 基礎實務班 2025/09/27 (六) 與 10/04 (六) 共二天
* 2026 年梯次，將在後續公告，請關注 FB 粉絲頁。

## 時間與地點

* **時間：**&#x39;:00 - 17:00，中午休息 1 小時。總共二天，共計 14 小時。\
  (若討論熱烈通常會延遲下課，建議多預留半小時至一小時的彈性時間)
* **上課地點：**<mark style="color:red;">**線上遠端授課**</mark>，詳細連線軟體、資訊與規範會再課前一周以 Email 通知。

## 課程費用與優惠辦法

* 費用：**NTD  14,500 元** (含稅)。
* 同時與 **Elasticsearch 進階運維班** 一起報名可享兩門課均 **9折**，兩門課~~原價 NTD 42,850~~。優惠價 **NTD 38,565** (含稅)。(此優惠限同一人報名)
* **四人以上**團報，可享 **9折**，優惠價每人 **NTD 13,050 元** (含稅)。

## 課程內容

* Elasticsearch 快速上手
  * Elasticsearch 集群的安裝
  * Kibana 的安裝及 Dev Tools 的使用介紹
  * Elasticsearch 的基本存取操作
  * Elasticsearch Indexing 與 Mapping 的簡介
* Elasticsearch 基礎入門
  * Elasticsearch 發展介紹
  * Elastic Stack 家族成員簡介
  * Elasticsearch 集群架構與名詞定義
  * Apache Lucene 術語與架構簡介
  * Elasticsearch 及 Lucene 的資料儲存概念
* Data In/Out
  * 如何建立 Index
  * Index 各種 CRUD (新增、讀取、更新、刪除) 的 REST API 操作方法
  * 批次處理的 REST API 操作方法
  * Search API 與 Query DSL 的基本介紹
  * Index Alias 的使用方式與實務技巧
* Elasticsearch 的底層運作原理
  * Indexing 底層運作原理
  * Searching 底層運作原理
  * Elasticsearch 資料儲存時的底層運作方式
  * Transaction Log 的設計機制
  * Lucene 與 Segment Files 的運作方式
* Text Analysis
  * 文字處理的入門介紹
  * Analyzer, Tokenizer, Char Filter, Token Filter 的使用方式
  * Analysis API 使用介紹
  * 中文搜尋的處理方式
* Mapping 配置方式
  * Elasticsearch 的 Dynamic Mapping
  * Mapping 的設定
  * Elasticsearch 的資料型態與設定配置
  * Index Template 與 Component Template
* Search
  * 常用的 Query 與 Filter 的方法與技巧
  * Highlight 的使用方法
  * 排序、分頁的使用方式
  * 常用的 Aggregation 的方法與技巧
  * Field Data 與 Doc Values 的原理
  * 使用 Search Profiler 分析搜尋效率
* Operation & Configuration & Conclusion
  * 基本的設定介紹
  * 佈署至正式環境時的重要設定與注意事項
  * 效能優化的注意事項
* Elastic Stack 簡介

## 適合對象

* 具有資訊相關科系背景，或是具備相關知識，你需要稍微懂 REST API、能執行基本的 Linux shell 或是 Windows command line 指令。
* 你可以完全沒使用過 Elasticsearch、或是有使用過但對於基礎及底層運作原理想獲得扎實的學習。
* 適合開發人員、SRE 工程師、IT 資訊人員、資料科學家、IT 經理、系統架構師、系統網路部門主管…等任何會使用到 Elasticsearch 的資訊相關人員。

## 開課資訊及退款規則

1. 開課門檻 : 報名達人數達 8 人即確定開班，20 人即額滿。
2. 退費辦法 : 當期若沒有成功開課，將全額退費或是可選擇延至下一梯課程。
3. 已完成報名與繳費之學員，將於開課一周前以 E-mail 方式寄發上課通知函；若課程因故取消或延期，亦將以 E-mail 與手機簡訊方式通知。
4. 已完成繳費之學員如欲取消報名，於實際上課八日前聯繫主辦單位辦理退課，主辦單位將退還 90% 課程費用。實際上課時間七日內辦理退課，則退還 50% 課程費用。
5. 已完成繳費之學員，可以轉讓上課資格予其他人，請在開課三日前與主辦單位聯繫並完成轉讓程序。
6. 如遇不可抗拒之因素，課程主辦單位保留修訂課程日期及取消課程的權利。

## 其他注意事項與資訊

1. 此課程含有大量實機操作練習，請準備可操作的電腦 (Windows、Mac、Linux 皆可，需能安裝並操作 Elasticsearch)。
2. 若有任何關於課程內容、企業報班與顧問服務需求請聯繫 <training@onedoggo.com> 王小姐
3. 提供企業報帳發票與個人發票
4. 優惠價擇一使用
5. 主辦單位保留因應上課成員能力調整內容、日期、時間與進行方式之權利。
6. 主辦單位擁有決定是否接受報名之權利。
7. 相關課程、書籍與 Elastic 相關討論都會公告在這邊，歡迎追蹤並且一起學習成長！\
   喬叔 - Elastic Stack 技術交流 粉絲頁：\
   <https://www.facebook.com/Joe.ElasticStack>


# 學員課後回饋

### 2021 十月份 Elasticsearch 基礎實務班 課後問卷調查結果

使用嚴格的 淨推薦分數 (NPS, Net Promoter Score) 來分析課程問券的結果，得到蠻不錯的分數！

{% hint style="warning" %}
這期課程中討論太熱烈拖到下課時間，最後下課後才請大家填問卷，問卷回覆率較低。
{% endhint %}

![2021 十月份 Elasticsearch 基礎實務班 課後問卷調查結果](/files/zs5eFYX0Vu5JRJES3kfH)

同學們的回饋，都是我們課程持續改進的重要意見，同學們的感謝，也是我們最大的鼓勵。

![](/files/tK0WNpMi8Bs9xUbrUDAB)

### 2021 七月份 Elasticsearch 基礎實務班 課後問卷調查結果

使用嚴格的 淨推薦分數 (NPS, Net Promoter Score) 來分析課程問券的結果，得到蠻不錯的分數！

![2021 七月份 Elasticsearch 基礎實務班 課後問卷調查結果](/files/nvNH9EYqAS8cvcdVvJ17)

同學們的回饋，都是我們課程持續改進的重要意見，同學們的感謝，也是我們最大的鼓勵。

![2021 七月份 Elasticsearch 基礎實務班 課後問卷調查結果](/files/EZ9MkjU9IrEzZwnDmqGr)


# Elasticsearch 進階運維班

學習如何從零開始規劃 Elasticsearch 集群、正確的管理 Elasticsearch 集群、有效率的管理隨著時間不斷增長的大量資料、確保資料的安全性及可靠性、更深入了解底層運作的原理、各種最佳化技巧、例外狀況發生時的處理技巧。

{% hint style="info" %}
2024 年，Elasticsearch 進階運維班進行大幅調整，更重視學員們的實際學習成果，加入更多案例的實作練習，課程時間拉長到三天，絕對要讓上課的同學們收穫滿滿並且具備實戰能力。
{% endhint %}

## 報名梯次

<details>

<summary><mark style="color:red;">我有用過 Elasticsearch，我是否還要上基礎班? 還是直接報名進階班? (請點參考以下評估方式)</mark></summary>

可以先參考下面幾點事項，如果你都有一定程度的掌握，才建議直接上進階班!

* 能獨立安裝 Elasticsearch Cluster，並且熟悉基本的設定。
* 了解 Elasticsearch Index, Mapping 的基本觀念。
* 了解 Elasticsearch Cluster, 什麼是 Primary Shard, Replica Shard，Elasticsearch Node 共有哪些角色。
* 知道如何建立 Index、設定 Index Settings、設定 Mapping、單一文件的 CRUD、批次處理的 mget, bulk 的操作。
* 知道 Indexing 一份文件時，文字欄位如何被解析、Analyzer 的處理方式、Inverted Index 如何儲存。
* 能清楚的分辨什麼是 Elasticsearch refresh, Lucene flush, Elasticsearch flush, Lucene commit, Segment file, Field data, Doc values。
* 能解釋 Query, Filter 的差異、並且知道如何做選擇。
* 掌握基本的 Search API 的使用、並且使用 Query DSL 與 Aggregation。
* 處理 indexing & searching 的請求時，Coordinator 是什麼樣的角色，這些請求在執行時，背後做了哪些事? 什麼是 query then fetch? 和 DFS query then fetch 的差異?
* 知道以下功能或設定使用的時機或要注意的事項，以及可以避掉什麼樣的坑：Nested Object, Terms Aggregation, max\_result\_window。
* Dynamic Mapping 是做什麼用的? fields 這個欄位型態又是做什麼用的?
* 知道在進入 Production 時，一些基本的 Elasticsearch 的設定要如何設置。
* Cluster 紅燈、黃燈、綠燈，分別代表什麼樣的狀態? 對於資料的存取會有什麼樣的影響?

</details>

* Elasticsearch 進階運維班 2025/06/28 (六)、07/05 (六)、07/12 (六) 共三天
* Elasticsearch 進階運維班 2025/11/01 (六)、11/08 (六)、11/15 (六) 共三天
* 2026 年梯次，將在後續公告，請關注 FB 粉絲頁。

## 課程費用與優惠辦法

* 費用：**NTD  28,350 元** (含稅)。
* 同時與 **Elasticsearch 進階運維班** 一起報名可享兩門課均 **9折**，兩門課~~原價 NTD 42,850~~。優惠價 **NTD 38,565** (含稅)。(此優惠限同一人報名)
* **四人以上**團報，可享 **9折**，優惠價每人 **NTD 25,515** 元 (含稅)。

## 時間與地點

* **時間：**&#x39;:00 - 17:00，中午休息 1 小時。總共三天，共計 21 小時。\
  (若討論熱烈通常會延遲下課，建議多預留半小時至一小時的彈性時間)
* **上課地點：**<mark style="color:orange;">**線上遠端授課**</mark>，詳細連線軟體、資訊與規範會再課前以 Email 通知大家。

## 課程內容

* 深入 Elasticsearch 分散式架構
  * Elasticsearch Cluster 概述、形成與維護機制
  * Cluster 腦裂及例外狀況發生時的運作方式
  * Indexing/Searching/Updating/Deleting/Bulk Request 的運作原理與例外狀況處理
  * Shard Allocation 的相關設定與客製化 filtering 配置
  * Routing 的運用方式
  * 分散式架構的分頁處理 - Search After & Scroll API
  * Cross Cluster Search 與 Cross Cluster Replication
* 效能最佳化原理與技巧
  * Elasticsearch JVM Heap 的使用方式
  * Filter Cache 的運作機制
  * Indexing 的優化技巧
  * Searching 的優化技巧
  * Storage 的管理技巧
  * Shard 的管理技巧
* 進階資料塑模 (Data Modeling) 與存取方式
  * 關聯式資料的儲存方式
  * Schema on-read 的資料管理方式
  * Schema on-write 的資料管理方式
  * Async Search + Runtime Fields
  * 資料的选代演進式管理
  * Elastic Common Schema
* 資料生命週期管理 (Data Lifecycle Management)
  * Elasticsearch 的資料管理總覽
  * ILM (Index Lifecycle Management)
  * Data Stream 與 Time Series Data Stream
  * Rollup
  * Transform
* 資料安全性 (Data Security)
  * Elastic X-Pack Security
  * RBAC (Role-based Access Control)
  * Kibana User/Role/Spaces 操作介紹
  * Snapshot/Restore
* 資料擷取 (Data Ingestion)
  * Ingest Pipeline
  * 常用的 Ingest Processors
  * Ingest Pipeline Enrich
  * Ingest Pipeline 例外狀況處理
* 正式環境的運維及管理技巧
  * Capacity Planning
  * 監控 Elastic Stack
  * Circuit Breaker
  * Cluster 的常見問題與解決方式
  * Rolling Upgrade 的執行方式

## 適合對象

* 強烈建議先上過 Elasticsearch 基礎實務班，或是已確認過基礎實務班的課程內容都有一定的掌握，"<mark style="color:red;">**不適合**</mark>"沒使用過 Elasticsearch 的新手。
* 這門課是運維 Elasticsearch 的 IT 資訊人員、SRE 工程師的必修，但建議開發人員也必須學習這門課，良好的設計與正確的使用，會是整體效能最佳化與正式運維複雜度的重要關鍵。
* 適合開發人員、SRE 工程師、IT 資訊人員、資料科學家、IT 經理、系統架構師、系統網路部門主管…等需要深入掌握 Elasticsearch，並能讓 Elasticsearch Cluster 安穩運行的資訊相關人員。

## 開課資訊及退款規則

1. 開課門檻 : 報名達人數達 6 人即確定開班，16 人即額滿。
2. 退費辦法 : 當期若沒有成功開課，將全額退費或是可選擇延至下一梯課程。
3. 已完成報名與繳費之學員，將於開課一周前以 E-mail 方式寄發上課通知函；若課程因故取消或延期，亦將以 E-mail 與簡訊方式通知。
4. 已完成繳費之學員如欲取消報名，請於實際上課八日前聯繫主辦單位辦理退課，主辦單位將退還 90% 課程費用。實際上課時間七日內辦理退課，則退還 50% 課程費用。
5. 已完成繳費之學員，可以轉讓上課資格予其他人，請在開課三日前與主辦單位聯繫並完成轉讓程序。
6. 如遇不可抗拒之因素，課程主辦單位保留修訂課程日期及取消課程的權利。

## 其他注意事項

1. 此課程含有大量實機操作練習，請準備可操作的電腦 (Windows、Mac、Linux 皆可，需能安裝並操作 Elasticsearch)。
2. 若有任何關於課程內容、企業報班與顧問服務需求請聯繫 <training@onedoggo.com> 王小姐
3. 提供企業報帳發票與個人發票
4. 優惠價擇一使用
5. 主辦單位保留因應上課成員能力調整內容、日期、時間與進行方式之權利。
6. 主辦單位擁有決定是否接受報名之權利。
7. 相關課程、書籍與 Elastic 相關討論都會公告在這邊，歡迎追蹤並且一起學習成長！\
   喬叔 - Elastic Stack 技術交流 粉絲頁：\
   <https://www.facebook.com/Joe.ElasticStack>


# 學員課後回饋

### 2022 四月份 Elasticsearch 進階運維班 課後問卷調查結果

使用嚴格的 淨推薦分數 (NPS, Net Promoter Score) 來分析課程問券的結果，得到蠻不錯的分數！

![2022 四月份 Elasticsearch 進階運維班 課後問卷調查結果](/files/3ZU3G0AS89E4zybMLvIK)

同學們的回饋，都是我們課程持續改進的重要意見，同學們的感謝，也是我們最大的鼓勵。

![2022 四月份 Elasticsearch 進階運維班 課後問卷調查結果](/files/h5j6Ft8bKTHMKKNX2bia)

{% hint style="success" %}
大家的意見我們有收到了，因此下一期開始改成隔週上課，也增加一小時的上課時數。
{% endhint %}


# Elasticsearch 進階開發班

專門針對 Developer 所設計的 Elasticsearch 進階開發班。

:warning: <mark style="color:yellow;">**這門課程尚在規劃中。**</mark>

如果您對這門課有興趣，我們邀請您留下 Email 以及對於這門課程的期待，喬叔在備課時，不但會認真的考量您的期待，並且在課程完成上線時，優先透過 Email 通知您，並且提供報名上課的一些優惠，以感激您的等待。

{% embed url="<https://forms.gle/rDG4F92zWNbWUZ9X6>" %}


# Elastic Stack 基礎實務班

如何使用 Elastic Stack (Elasticsearch, Logstash, Kibana, Beats, Elastic Agent)，早期又稱作 ELK (Elasticsearch Logstash Kibana) 來收集各種 Logs 並且進且 ETL (Extract, Transform, Load) 的資料前處理，進一步的將所收集的料透過 Kibana 建立視覺化的報表。

:warning: <mark style="color:yellow;">**這門課程尚在規劃中。**</mark>

如果您對這門課有興趣，我們邀請您留下 Email 以及對於這門課程的期待，喬叔在備課時，不但會認真的考量您的期待，並且在課程完成上線時，優先透過 Email 通知您，並且提供報名上課的一些優惠，以感激您的等待。

{% embed url="<https://forms.gle/rDG4F92zWNbWUZ9X6>" %}


# Elastic Observability 基礎實務班

從零上手，學習如何使用 Elastic Observability 來有效的掌握及管理產品或企業系統的穩定度及例外狀況的盤查。

:warning: <mark style="color:yellow;">**這門課程尚在規劃中。**</mark>

如果您對這門課有興趣，我們邀請您留下 Email 以及對於這門課程的期待，喬叔在備課時，不但會認真的考量您的期待，並且在課程完成上線時，優先透過 Email 通知您，並且提供報名上課的一些優惠，以感激您的等待。

{% embed url="<https://forms.gle/rDG4F92zWNbWUZ9X6>" %}


# 課程許願池

請留下你期待的課程內容以及聯絡資訊，我們會仔細評估需求進課程規劃中，並且在課程推出時第一時間通知您。

{% embed url="<https://docs.google.com/forms/d/e/1FAIpQLSeoC3W804sAg-MoMlY_4RgPjPr3e246XutvUjX0I71uLYo0Tg/viewform?usp=sf_link>" %}


# 喬叔帶你上手 Elastic Stack

{% hint style="info" %}
這系列文章是在 iThome 2020 年 IT邦幫忙 鐵人賽 時所撰寫，參加 Elastic Stack on Cloud 分組主題並得到冠軍的肯定。
{% endhint %}

{% hint style="danger" %}
此系列文章已重新整理編寫成書 ([天瓏書局](https://www.tenlong.com.tw/products/9789864348572))，當中有許多內容有在書中修訂，並且也針對 2021 年書籍發表時依照當時 Elasticsearch 的版本 7.16 進行更新，但是在這邊的文章僅提供鐵人賽 2020 年當時的版本，並未同步修訂，在此特別說明。
{% endhint %}

### 喬叔教 Elastic 文章總整理

在前言裡，有描述到這次參賽的原由、喬叔在 Elastic 的背景、這次文章撰寫的主要方向的概念介紹。

* [前言](/tech-sharing/uncle-joe-teach-es-elasticsearch/qian-yan)

#### Elastic Cloud 如何建立 Deployment

這個系列文章主要介紹使用 Elastic Cloud 時，在選擇 Deployment 的時候，你應該要先知道的知識、以及如何進行選擇。

* [(1/2) - ES Node 的種類](/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-ru-he-jian-li-deployment/es-node-de-zhong-lei#jian-li-elastic-cloud-ec-deployment-shi-de-xuan-ze)
* [(2/2) - 配置的選擇](/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-ru-he-jian-li-deployment/pei-zhi-de-xuan-ze#elastic-cloud-deployment-de-pei-zhi-fang-an)

#### Index 建立前你該知道的

當你架起了 Elasticsearch Cluster 後，要把資料正式的放入 Elasticsearch 來使用之前，你應該要知道的一些進階知識。

* [(1/5) ES Index 如何被建立](/tech-sharing/uncle-joe-teach-es-elasticsearch/index-jian-li-qian-ni-gai-zhi-dao-de/es-index-ru-he-bei-jian-li)
* [(2/5) ES 的超前佈署 - Dynamic Mapping](/tech-sharing/uncle-joe-teach-es-elasticsearch/index-jian-li-qian-ni-gai-zhi-dao-de/es-de-chao-qian-bu-shu-dynamic-mapping)
* [(3/5) ES 的超前佈署 - Index Template](/tech-sharing/uncle-joe-teach-es-elasticsearch/index-jian-li-qian-ni-gai-zhi-dao-de/es-de-chao-qian-bu-shu-index-template)
* [(4/5) ES Index 的別名 (Alias)](/tech-sharing/uncle-joe-teach-es-elasticsearch/index-jian-li-qian-ni-gai-zhi-dao-de/es-index-de-bie-ming-alias)
* [(5/5) ES 管理你的 Index - Kibana Index](/tech-sharing/uncle-joe-teach-es-elasticsearch/index-jian-li-qian-ni-gai-zhi-dao-de/guan-li-ni-de-index-kibana-index)

#### 管理 Index 的 Best Practices

Index 建立起來之後，如何管理你的 Index、也就是如何管理你在 Elasticsearch 中的資料，這裡介紹了各種推薦的工具與實踐的技巧。

* [(1/7) - Shard 的數量與 Rollover & Shrink API](/tech-sharing/uncle-joe-teach-es-elasticsearch/guan-li-index-de-best-practices/shard-de-shu-liang-yu-rollover-shrink-api)
* [(2/7) - 三溫暖架構 - Hot Warm Cold Architecture](/tech-sharing/uncle-joe-teach-es-elasticsearch/guan-li-index-de-best-practices/san-wen-nuan-jia-gou-hot-warm-cold-architecture)
* [(3/7) - Index Lifecycle Management (ILM)](/tech-sharing/uncle-joe-teach-es-elasticsearch/guan-li-index-de-best-practices/index-lifecycle-management-ilm)
* [(4/7) - Rollup](/tech-sharing/uncle-joe-teach-es-elasticsearch/guan-li-index-de-best-practices/rollup)
* [(5/7) - Transform](/tech-sharing/uncle-joe-teach-es-elasticsearch/guan-li-index-de-best-practices/transform)
* [(6/7) - Snapshot Lifecycle Management (SLM)](/tech-sharing/uncle-joe-teach-es-elasticsearch/guan-li-index-de-best-practices/snapshot-lifecycle-management-slm)
* [(7/7) - 總結](/tech-sharing/uncle-joe-teach-es-elasticsearch/guan-li-index-de-best-practices/zong-jie)

#### Elastic Cloud 比免費版還多的功能

Elastic Stack 包含了各種的功能，針對 SaaS 服務中 Standard 版本的功能，以及自己架設 (on-premise) 的 Basic 版本，有什麼差異? 如果你用 Elastic 官方代管的 SaaS 服務，最基本的版本就能得到自行架設要花大錢買進階 License 才能得到的功能有哪些？

* [(1/6) Elastic Stack 的方案比較與銷售方式](/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-bi-mian-fei-ban-huan-duo-de-gong-neng/elastic-stack-de-fang-an-bi-jiao-yu-xiao-shou-fang-shi)
* [(2/6) Centralized Beats Management](/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-bi-mian-fei-ban-huan-duo-de-gong-neng/centralized-beats-management)
* [(3/6) Centralized Pipeline Management](/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-bi-mian-fei-ban-huan-duo-de-gong-neng/centralized-pipeline-management)
* [(4/6) Watcher](/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-bi-mian-fei-ban-huan-duo-de-gong-neng/watcher)
* [(5/6) Elasticsearch Token Service](/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-bi-mian-fei-ban-huan-duo-de-gong-neng/elasticsearch-token-service)
* [(6/6) Multi-stack monitoring & Automatic stack issue alerts](/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-bi-mian-fei-ban-huan-duo-de-gong-neng/multi-stack-monitoring-and-automatic-stack-issue-alerts)

#### 向 App Search 學習怎麼用 Elasticsearch

App Search 是使用 Elasticsearch 做成的產品，這個產品的目的是幫你配置好一般搜尋功能需求的基本最佳方案，讓 一般網站 或 App 能直接簡單的就拿來使用，想知道 Elasticsearch 可以怎麼被使用，當然就是從剖析 App Search 怎麼使用 Elasticsearch 來學習。

* [(1/5) - 揭開 App Search 的面紗](/tech-sharing/uncle-joe-teach-es-elasticsearch/xiang-app-search-xue-xi-zen-mo-yong-elasticsearch/jie-kai-app-search-de-mian-sha)
* [(2/5) - Engine 的 Index Settings 篇](/tech-sharing/uncle-joe-teach-es-elasticsearch/xiang-app-search-xue-xi-zen-mo-yong-elasticsearch/engine-de-index-settings-pian)
* [(3/5) - Engine 的 Mapping 篇](/tech-sharing/uncle-joe-teach-es-elasticsearch/xiang-app-search-xue-xi-zen-mo-yong-elasticsearch/engine-de-mapping-pian)
* [(4/5) - Engine 的 Search 基礎剖析篇](/tech-sharing/uncle-joe-teach-es-elasticsearch/xiang-app-search-xue-xi-zen-mo-yong-elasticsearch/engine-de-search-ji-chu-pou-xi-pian)
* [(5/5) - Engine 的 Search 進階剖析篇](/tech-sharing/uncle-joe-teach-es-elasticsearch/xiang-app-search-xue-xi-zen-mo-yong-elasticsearch/engine-de-search-jin-jie-pou-xi-pian)

#### Elasticsearch 的優化技巧

使用 Elasticsearch 時，是否對於效能不滿意？對於硬體資源的成本想進一步優化？這個主題就帶大家來探討，最佳化 Elasticsearch 的各種技巧及注意事項。

* [(1/4) - Indexing 索引效能優化](/tech-sharing/uncle-joe-teach-es-elasticsearch/elasticsearch-de-you-hua-ji-qiao/indexing-suo-yin-xiao-neng-you-hua)
* [(2/4) - Searching 搜尋效能優化](/tech-sharing/uncle-joe-teach-es-elasticsearch/elasticsearch-de-you-hua-ji-qiao/searching-sou-xun-xiao-neng-you-hua)
* [(3/4) - Index 的儲存空間最佳化](/tech-sharing/uncle-joe-teach-es-elasticsearch/elasticsearch-de-you-hua-ji-qiao/index-de-chu-cun-kong-jian-zui-jia-hua)
* [(4/4) - Shard 的最佳化管理](/tech-sharing/uncle-joe-teach-es-elasticsearch/elasticsearch-de-you-hua-ji-qiao/shard-de-zui-jia-hua-guan-li)


# 前言

這次會參賽是被老婆推坑，明明平常工作已忙到不可開交，抱持者不確定是否能完賽的心情、要死也要拖個人一起下水的心態，硬是拉了個同事來組隊參賽，希望籍此機會，將我曾經花過不少時間累積的心法，及多年教學相長累積的經驗能分享給大家。

## 喬叔的 Elastic 經歷

在開始閱讀這系列的文章之前，和大家介紹喬叔在 Elastic 的相關背景

* 超過20年的程式開發經驗，7年以上 Elasticsearch 的使用經驗。
* 2013年 導入 Elasticsearch 於千萬級使用者的跨國產品中，提供多國語言的搜尋功能。
* 2013年 於舊金山參加原廠開設的 Core Elasticsearch Training。
* 2015年 創業時大量使用 Elastic Stack 於新創產品中的功能開發、大數據分析、運維監控。
* 2018年 成為台灣第一位 Elastic Certified Engineer
* 超過五年的 Elasticsearch 專業課程及企業內部培訓經驗。
* 曾擔任美國某新創公司 Distributed & Search Solution Consultant、並協助多間企業提供 Elastic 相關的技術支援及服務。

## 這系列文章的主題方向

由於網路上已經有許多基礎的入門文章，我也不想多花時間寫同樣的介紹，因此這次主題的文章不會專注在太基礎的操作介紹，若這部份還沒有經驗的朋友們，建議可以先閱讀這次鐵人賽 [Elastic Stack on Cloud 的系列文章](https://ithelp.ithome.com.tw/2020-12th-ironman/elastic)、[官方的說明文件(英文為主，少部份中文)](https://www.elastic.co/guide/index.html)、[官方的免費訓練課程(英文)](https://www.elastic.co/training/free)，裡面都有許多不錯的基礎介紹，或是也可以參加我在外開設的基礎實務培訓班(肯定是親切的中文)。

回想過往我在學習 Elastic Stack 的過程中，很容易找到入門的文章，但往往過程中某些設定背後的原理不清楚、一些需注意的事項沒注意到，而導致誤用或是繞了遠路，因此在這次的文章中，我會從 Elastic Cloud 的使用情境來出發，在過程中深入介紹基本的原理、重要的功能、某個設置的背後代表什麼意思、甚至有一些某些 **SaaS 版的 Elastic Cloud** 才有免費提供而 **自架設且免費授權的 Elastic Cloud Enterprise** 沒有的功能…等。

連續寫30篇文章是個鐵人的挑戰，閱讀完30篇文章並學習吸收也是件挺花時間的挑戰，就讓我們一起接受這挑戰、一起學習與成長，就從開始嘗試14天的 [Elastic Cloud 免費試用](https://cloud.elastic.co/) 吧！(為了這次的鐵人賽的參賽者，Elastic 官方有[另外的入口](https://ela.st/ithome-hackathon)，可以取得30天的免費試用，有興趣的朋友可以進去試試。)

![開始註冊Elastic Cloud](https://i.imgur.com/RyDzlYJ.png)


# Elastic Cloud 如何建立 Deployment

這個系列文章主要介紹使用 Elastic Cloud 時，在選擇 Deployment 的時候，你應該要先知道的知識、以及如何進行選擇。

* [(1/2) - ES Node 的種類](/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-ru-he-jian-li-deployment/es-node-de-zhong-lei#jian-li-elastic-cloud-ec-deployment-shi-de-xuan-ze)
* [(2/2) - 配置的選擇](/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-ru-he-jian-li-deployment/pei-zhi-de-xuan-ze#elastic-cloud-deployment-de-pei-zhi-fang-an)


# ES Node 的種類

### 此章節的重點學習

* 了解 Elasticsearch Node 可扮演的角色有哪些、以及我們常提到的 Node 的種類有哪些，其中扮演的是哪些角色。

***

## 建立 Elastic Cloud (EC) Deployment 時的選擇

![create deployment](https://i.imgur.com/eNDS0vP.png)

當我們在 Elastic Cloud (EC) 上建立一個 Deployment 時，我們會面對下列幾個選擇：

* Deployment 的名稱。
* 指定的 Cloud Platform。
* 這些 Elastic Stack 佈置在的 Region。
* Elastic Stack 的版本。
* 是否要把這個 Deployment 的 Monitor 資料傳到另個統一收集 Monitoring 資料的 Deployment 來分開存放。
* 是否要限制能存取這個 Deployment 的 Traffic Filter 設置：例如只能有特定的 IP 存取、或是只能有特定的 AWS VPC Endpoint 來存取。
* `Optimize your deployment`也就是你的 Deployment infra 上的配置方式。

最後一項 `Optimize your deployment`，一般會是大家思考最久的問題，而我們這篇會來先解釋這個選擇要考慮的項目。

![setup deployment](https://i.imgur.com/So4dWxB.png)

## Elasticsearch Node 可扮演的角色

在進入說明之前，大家需要先知道 Elasticsearch Node 可扮演的角色有以下這些：

* **Master-eligible role:** 是否可以被選擇當作 Cluster 的 Master 節點，以負責以下幾項任務：
  * 維護 Cluster 的狀態，例如 Node 的加入或移除。
  * 負責處理建立或刪除 Index 的請求，並指派給相關的 Node 進行實際操作的執行。
  * 決定 shard 要被分配到哪個 node 身上。
* **Data role:** 是否負責儲存資料，以及執行資料相關的操作 CRUD 或是執行 search、aggregation。
* **Ingest role:** 是否負責執行 [Ingest pipeline](https://www.elastic.co/guide/en/elasticsearch/reference/current/pipeline.html) 所定義的工作 (Ingest pipeline 是在資料匯入至 Elasticsearch 之前，可在進行 Index 前進行資料修改、轉換…等 ETL (Extract, Transform, Load) 操作，也就是提供了部份 Logstash 的功能)。
* **Machine learning role:** 是否負責處理 Machine learning 的工作，這項功能是包含在 xpack 中。通常這個 role 會安排獨立的 Node 來扮演。
* **Transform role:** 是否執行 [Transform](https://www.elastic.co/guide/en/elasticsearch/reference/current/transforms.html) 的任務，這項功能是包含在 xpack 中。
* **Remote cluster client:** 是否能負責存取 Remote Cluster。

## Elasticsearch Node 的種類

通常一個 Node 我們可以同時扮演多種角色，以下的 Node 種類，是依照功能特性與使用情境來特別分類說明：

* **Default node:** 預設開啟一個 Node 的時候，可扮演的角色是 `Master-eligible`, `Data`, `Ingest`, `Remote cluster client node`, `Transform`。
* **Dedicated master-eligible node:** 如果 Cluster 規模較大、或是非常重視 Cluster 的穩定性，可安排某個 Node 只擔任 `Master-eligible` 的角色，這個 Node 就不會因為大量的 indexing, searching, ingesting, transforming 等操作讓資源被吃光而導致 Cluster 不穩定。
* **Voting only node:** 在 [Master Node 的選舉 ](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/modules-discovery-quorums.html)時，有時可能我們的 Master-eligible node 是雙數，可能會造成無法順利完成選舉，因此可以安排只負責投票，但不參與選擇的 `Voting only node`。(在這種模式下，Node 本身也會是 `master-eligible`的角色，因為必須是 `master-eligible`才能參與選舉，而 `Voting only node`會宣告本身不要被選上)
* **Dedicated Data node:** 只單純負責儲存資料，專心的負責 CRUD, search, aggregation 這些很需要 CPU, Memory, I/O 資源的任務，不會讓其他的任務佔用資源。
* **Dedicated coordinating node (Client node):** 不扮演任何的角色，只單純的接受 indexing request 或是 searching request。當有複雜的 sorting、多層的 aggregation 查詢、大量的 bulk request 時，接收到 request 的這個稱之為 Coordinator 的 Node，會需要大量的記憶體來處理這些request，這時可評估安排 Dedicated coordinating node ，並將這類 request 的流量導入到這個 node 身上來進行處理，可以避免因過度消耗資源而影響到 cluster 的穩定性或是其他操作的性能。
* **Dedicated ingest node:** 通常有大量的 ETL 處理的情境下，可以特定安排某個 Node 單純的處理 ingest 的任務，而不讓 ETL 的處理佔用到其他角色的工作執行資源。

下一篇，我們將繼續這個主題，說明 EC 上的預設配置，以及我們如何做選擇。

## 參考資料

* [Elastic 官方文件 - Node 的說明](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html)
* [Elastic 官方文件 - Ingest pipeline](https://www.elastic.co/guide/en/elasticsearch/reference/current/pipeline.html)
* [Elastic 官方文件 - Master Node 的選舉](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/modules-discovery-quorums.html)


# 配置的選擇

### 此章節的重點學習

* 了解 Elastic Cloud 的預設置

***

前一篇我們介紹了 Elasticsearch (ES) Node 的種類，接下來我們回到 Elastic Cloud (EC) 的設置介面。

## Elastic Cloud Deployment 的配置方案

![ECS Deployement Options](https://i.imgur.com/DElQFOg.png)

Elastic Cloud 已經提出了幾個情境的 deployment template 配置方案 ，細節也都有在 [官方文件](https://www.elastic.co/guide/en/cloud/current/ec-getting-started-templates.html) 中解釋，這邊每個 `Default specs` 裡面都有機器規格的列表。

![EC deployment specs](https://i.imgur.com/4VuRPwj.png)

中間有幾個關鍵字，例如 `coordinating`, `data`, `master`, `ml(machine learning)` 這些都是 Elastic 針對這種使用情境的特性，建議可以安排這些功能各自的 Nodes，而我這邊的例子是選擇 AWS 來當我的 Cloud Provider，所以後面的 `m5d`, `i3`, `r5d` 這些都可以對照下表，知道這些機器的安排規格。

### Deployment 的硬體規格參考(以 AWS 為例)

![EC elasticsearch AWS hardward](https://i.imgur.com/WoH45sD.png)

## 進入 Customize Deployment

我們再進一步選擇畫面底下的 `Customize Deployment`，可以針對下列各項主要的功能獨立設置硬體的配置：

* Data: 預設 Elasticsearch 存資料的 nodes
  * `highio`預設配置是: `master-eligiable`, `data`, `coordinating`, `ingest`
  * `highstorage`預設配置是: `data`, `coordinating`, `ingest`
* Machine Learning: 單純執行 Machine Learning 任務的 node。
* Coordinating: 沒有任何的角色，是之前提到的 `dedicated coordinating node`。
* Master: 是 `dedicated master node`。
* Kibana: 運行 Kibana Web Application 的機器。
* APM: 運行 APM 服務的機器。
* Enterprise Search: 是 App Search 和 Workspace Search 的應用程式安裝機。

如下圖紅框，可查看每個配置的角色為何，可針對此角色的 Node 來設定預期的硬體資源配置。

![EC customize deployment](https://i.imgur.com/PjSvD7h.png)

## 安裝 Plugins 及設置 Extensions

Elastic Cloud 預設將常用的一些 plugins 在設定畫面中可直接勾選，若你有要處理 CJK (Chinese, Japanese, Korean) 的語言，通常你至少會要選擇 `analysis-icu` 或甚至是其他的 analysis plugins 。

![EC plugins](https://i.imgur.com/Oxqej4D.png)

若有使用同義字 (Synonym) ，要掛載字典檔的話，要從目錄選單左側的 `Extensions` 進入。

![EC Extensions](https://i.imgur.com/1bOzQFl.png)

## 透過 Create Deployment API 建立 Deployment

當所有設置都決定好之後，我們按下 `Create deployment` 即可進入產生 Deployment 的處理。

![Create Deployment](https://i.imgur.com/E8A3xjG.png)

而其實這個 Elastic Cloud 的網頁設置畫面，只是協助產生 Create Deployment 的 API request，我們從底下的 `Equivalent API request` 可以直接看到透過 Elastic Cloud UI 所建立出來的配置，產生的 API request 細節。

![EC Create deployment API request](https://i.imgur.com/fB0Lf6M.png)

因此若是要特別客製某個 Node 的配置與角色，也可參考官方的 [Deployment CRUD API request](https://www.elastic.co/guide/en/cloud/current/Deployment_-_CRUD.html)，來客製自己的 deployment。

## 完成設置

按下 `Create Deployment` 後，會跳出 Elastic Cloud 在 xpack security 所設置的管理者帳號及密碼，這組帳號將是登入 `Kibana`, `APM`, `Enterprise Search`…等服務時需使用，最後需要約數分鐘的時間等待 deployment 的建置完畢。

![ECS create deploymenet done](https://i.imgur.com/dHBnUIB.png)

## 參考資料

* [官方文件 - EC deployment templates](https://www.elastic.co/guide/en/cloud/current/ec-getting-started-templates.html)
* \[官方文件 - Elasticsearch service hardware]\(<https://www.elastic.co/guide/en/cloud/current/ec-reference-hardware.html>
* [官方文件 - EC deployment CRUD](https://www.elastic.co/guide/en/cloud/current/Deployment_-_CRUD.html)


# Index 建立前你該知道的

當你架起了 Elasticsearch Cluster 後，要把資料正式的放入 Elasticsearch 來使用之前，你應該要知道的一些進階知識。

* [(1/5) ES Index 如何被建立](/tech-sharing/uncle-joe-teach-es-elasticsearch/index-jian-li-qian-ni-gai-zhi-dao-de/es-index-ru-he-bei-jian-li)
* [(2/5) ES 的超前佈署 - Dynamic Mapping](/tech-sharing/uncle-joe-teach-es-elasticsearch/index-jian-li-qian-ni-gai-zhi-dao-de/es-de-chao-qian-bu-shu-dynamic-mapping)
* [(3/5) ES 的超前佈署 - Index Template](/tech-sharing/uncle-joe-teach-es-elasticsearch/index-jian-li-qian-ni-gai-zhi-dao-de/es-de-chao-qian-bu-shu-index-template)
* [(4/5) ES Index 的別名 (Alias)](/tech-sharing/uncle-joe-teach-es-elasticsearch/index-jian-li-qian-ni-gai-zhi-dao-de/es-index-de-bie-ming-alias)
* [(5/5) ES 管理你的 Index - Kibana Index](/tech-sharing/uncle-joe-teach-es-elasticsearch/index-jian-li-qian-ni-gai-zhi-dao-de/guan-li-ni-de-index-kibana-index)


# ES Index 如何被建立

### 進入此章節的先備知識

* 初步了解 Elasticsearch 的 Cluster 與 Shards。

### 此章節的重點學習

* Elasticsearch 的 Index 是如何被建立，在 Cluster 中是如何運作。

***

當我們建立好一個 Elastic Cloud 的 Deployment 之後，下一步就是要建立 Index，但是 Index 建立時的底層是如何運作的呢？

## Elasticsearch Index 的建立

假設有一組擁有 4 個 Nodes 的 Elasticsearch Cluster：

* index: `a`, shard: 2, replica: 1 (有 `a0`, `a1` 兩個深色的 primary shard, 另各有一份白底的 replica shard)
* index: `b`, shard: 3, replica: 1 (有 `b0`, `b1`, `b2` 三個深色的 primary shard, 另各有一份白底的 replica shard)

![es-node](https://i.imgur.com/6IZ7b0c.png)

當我們要建立一個新的 Index `c` 時，Elasticsearch 會執行以下幾個檢查的步驟：

### Shard 分配決策者 (Allocation Deciders)

1. 計算每個 node 身上的 shard 數量，盡可能的**以數量**來平均分配，決定新的 primary shard 要放在哪個 node 身上。
2. 檢查是否有些 filter 條件，例如當 node 有宣告 hot/warm architecture 的 attribute 時，會過濾掉不應該被存放的 nodes，例如新的資料只能被放在有宣告 `hot` attribute 的 node 身上。

![image-20200915024738494](https://i.imgur.com/7dhVeNt.png)

3. 檢查磁碟空間是否充足，如果已達到 `cluster.routing.allocation.disk.watermark.low` 設定的水位，則不會將新的 shard 放在該 node 身上。

<figure><img src="/files/qKNBUEp6xWZJxhin2z4W" alt=""><figcaption></figcaption></figure>

4. 檢查是否有設定 Throttling，例如 `indices.recovery.concurrent_small_file_streams` 和 `indices.recovery.concurrent_file_streams` 的設置是否達到，而是否要暫緩目前 create index 的動作。

### Primary Shard 初始化

若 Shard Allocation Decider 一切順利，將會進行 Primary Shard 的初始化：

1. 在 Cluster 狀態中，標示這個 Index 的 Primary Shard 會被分派到哪個 node 身上，並標示狀態為 `Initializing`。
2. 該 Index 存在的 node 收到動工的通知後，開始建立空白的 Lucene index，並且回報 Cluster master node 處理完成。
3. Cluster master node 收到處理完成時，會將這個 shard 的狀態標示為 `started`，並且通知 cluster 中的大家這個狀態，而這個存在此 pirmary shard 的 node 也收到 master node 的通知時，就會將這個 shard 的狀態設定成 `started`，這時就能提供 indexing 的處理了。

### Replica Initialization

當我們 Primary Shard 正常運作之後，Cluster 會檢查目前 replica 的設定是否滿足，若是有需要執行 replica 的複制則進行下列步驟：

1. 決定 shard 應該放在哪一個 node 身上，通知這個 node 要做事，並且標示這個 shard 為 `Initializing`。
2. 收到通知的 node 為此 shard 建立空白的 Lucene index。
3. 一律從 primary shard 複制資料到 replica shard，並且在完成之後通知 master node。
4. Master node 將這個 shard 的狀態改為 `started`，並且通知 cluster 中的大家。
5. 收到 master node 通知時，存放 replica shard 的 node，將 shard 的狀態開啟提供服務。

到此階段，這個 Index 算是完成了被建立的這個流程，也開始能正常的提供服務了，下一步就是將要 indexing 進 Elasticsearch 的文件準備好吧！

## 參考資料

* [官方 Blog - Every shard deserves a home](https://www.elastic.co/blog/every-shard-deserves-a-home)


# ES 的超前佈署 - Dynamic Mapping

## 前言

一開始使用 Elasticsearch 時，簡單的透過一個 HTTP POST 或 PUT 就能將一個 JSON 文件 indexing 進入 Elasticsearch，然後就可以直接透過 `_search` API endpoint 來進行搜尋，如此的簡單，不像一般 Database 要先定義好 table schema，這背後到底是怎麼運作的？

### 進入此章節的先備知識

* 什麼是 Index 及建立 Index 的一些基本操作知識。
* 什麼是 Mapping 及 Mapping 基本操作的知識。

### 此章節的重點學習

* Indexing 文件進入 Elasticsearch 時，ES 如何針對未事前定義欄位產生其 Mapping 設定值。
* 我們如何自己定義自己的**自動判斷欄位型態**的規則。
* 有什麼實用的案例技巧與要注意的地方。
* 最終透過 `Dynamic Template`的設定，來強化我們設計 `Mapping` 時的技巧。

***

## Dynamic Mapping

這個功能是 Elasticsearch 之所以能宣稱他是 schema-less，也就是不用預先定義好資料的 schema 就能直接將文件 indexing 進入 Elasticsearch 的幕後機制，他的做法就是：

> 當一份被 indexing 進入 Elasticsearch 的文件，若是有一個欄位，沒有在 mapping 中被定義時，Elasticsearch 會自動的判斷他的資料型態，並且依照預設或指定的規則，來產生這個新欄位的 mapping 設定。

Dynamic Mapping 的運作主要依照以下兩種機制及其相關的設定：

* **Dynamic field mappings:** 從 JSON 文件，動態偵測欄位型態的規則。
* **Dynamic templates:** 自訂義的動態判斷欄位型態的規則。

以下我們分別就這兩個部份來說明。

### Dynamic Field Mappings

當一個 JSON 文件 indexing 進入 Elasticsearch 時，Dynamic field mapping 會依照 JSON 欄位原本的資料型態，來分別執行判定的規則：

| JSON 的資料型態        | 判定成為的 Elasticsearch 資料型態                                                                                                                                               |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| null              | 不會產生這個欄位                                                                                                                                                               |
| `true` or `false` | `boolean`                                                                                                                                                              |
| 浮點數               | `float`                                                                                                                                                                |
| 整數                | `long`                                                                                                                                                                 |
| 物件                | `object`                                                                                                                                                               |
| 陣列                | 依照陣列內的資料型態決定                                                                                                                                                           |
| 字串                | <p>1. 判定這個字串是否為日期格式。<br>2. 判定這個字串是否為 <code>double</code> 或是 <code>long</code> 的格式。<br>3. 若都非以上的格式，會直接指派 <code>text</code> 型態，並搭配 <code>keyword</code> 的 sub-field。</p> |

其中日期格式預設的規則為：

```
[ "strict_date_optional_time","yyyy/MM/dd HH:mm:ss Z||yyyy/MM/dd Z"]
```

> strict\_date\_optional\_time 支援的是一般廣泛被使用在 JSON 中表示日期時間的 [ISO8601](https://www.w3.org/TR/NOTE-datetime) 格式。

也可以在宣告 mapping 時自行定義日期格式的規則：

```
PUT my-index-000001
{
  "mappings": {
    "dynamic_date_formats": ["MM/dd/yyyy"]
  }
}
```

### Dynamic Templates

我們可以在 mapping 宣告時，直接指定 `dynamic template` 的規則，從官方的文件可以看到宣告的格式如下：

!\[dynamic templates]\(/Users/joecwu/Google Drive (<joe@onedoggo.com>)/Publication/博碩文化/書籍資料/圖片/2-4.png)

其中 match conditions 代表的定義為：

* `match_mapping_type`: Elasticsearch 根據 JSON 文件的欄位資料內容，所判斷出來的資料型態是什麼。 例如： `boolean`, `date`, `double`, `long`, `object`, `string`.
* `match` 和 `unmatch`: 欄位的名稱，可以支援萬用字元 `*`。例如： `*_text`, `long_*`.
* `match_pattern`: 一樣是針對欄位的名稱進行比對，不過是以 **full Java regular expression** 的比對方式。
* `path_match` 和 `path_unmatch`: 類似 `match` 與 `unmatch` 是針對欄位的名字，不過多包含整體的路徑，例如： `name.*`, `*.middle`。

## 實用的技巧與注意事項

#### 定義好合適的資料型態

* Elasticsearch 預設的 Dynamic Template 會將 `string` 的欄位指定成 `text` 加上包含 `keyword` sub-field 的型態，如果這個 index 的應用場景是 log，而 log 的格式有先定義好，大部份的字串欄位都不用被搜尋，只有特定的字串欄位會是 `text` 的話，可將預設的字串欄位指定成 `keyword`。

```
PUT my-index-000001
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings_as_keywords": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "keyword"
          }
        }
      }
    ]
  }
}
```

* 如果預設會進入的字串資料很明確就是 `text`，不需要使用到 Aggregation, Sorting 或是 Script 的操作 ，因此不必保留 `keyword` 的 sub-field，即可明確指定為 `text`。

```
PUT my-index-000001
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings_as_text": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "text"
          }
        }
      }
    ]
  }
}
```

### 團隊內部的命名規則

指定好團隊寫入特定 Index 的命名規則，可以簡化設定與錯誤發生的機會，例如：

* 若值是日期時間，則必需是 `_datetime` 結尾。
* 若值是整數的次數，則必需是 `_count` 結尾。
* 將特定型態定義在欄位的開頭，例如： `long_`, `double_`, `int_`。

這樣即可以較單純的設定來滿足日後欄位擴充的管理。

```
PUT my-index-000001
{
  "mappings": {
    "dynamic_templates": [
      {
        "long_field": {
          "match":   "long_*",
          "mapping": {
            "type": "long"
          }
        }
      },
      {
        "double_field": {
          "match":   "double_*",
          "mapping": {
            "type": "double"
          }
        }
      }
    ]
  }
}
```

### 必要的嚴謹，以避免意外發生

#### 1. 請小心設定 `Dynamic Template`，特別是修改原先的 `Dynamic Template`時，否則會發生 `Runtime Error`，也就是 indexing 才會發現有錯。

#### 2. 關閉 Dynamic fields mapping

```
PUT /my_index
{
    "mappings": {
        "dynamic": "strict"
    }
}
```

`dynamic`可以設定為：

* `true`: 執行 dynamic mapping。
* `false`: 不執行 dynamic mapping，並在 indexing 時忽略沒有被宣告的欄位。
* `strict`: 不執行 dynamic mapping，並在 indexing 時遇到沒有宣告的欄位會直接拋出 exception。

#### 3. 關閉 日期 或 數值 的自動判斷

```
PUT my-index-000001
{
  "mappings": {
    "date_detection": false,
    "numeric_detection": true
  }
}
```

> 其中數值的部份指的是在字串內容中是否要嘗試判斷是否為數值，預設是關閉的

## 參考資料

* [官方文件 - Dynamic Mapping](https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-mapping.html)
* [Elasticsearch: 權威指南 - 動態映射](https://www.elastic.co/guide/cn/elasticsearch/guide/current/dynamic-mapping.html)


# ES 的超前佈署 - Index Template

## 前言

將 Document indexing 進入 Elasticsearch 時，比較好的做法是先依照合適的 Index settings 及 mapping 來設置，但是若資料量不斷隨著時間增加，Index 也應該要隨著時間產生新的，如何有效的管理這些動態新增的 Index，我們需要的就是 Index Template。

### 進入此章節的先備知識

* Index, Mapping, Alias 的基本設定與操作。
* Dynamic Mapping 的基本認識。

### 此章節的重點學習

* Index Template 的基本使用方式。
* Elasticsearch 7.8 推出的 Component Template 怎麼使用。
* 剖析 Elastic Stack 中預設的幾個 Index Template。
* Index Template Simlate API 的使用方式。
* 建議的 Index Template 設計方式。

***

## Index Template

從 [官方文件 - Index Template](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-templates.html#) 可以知道 Index Template 的基本用法，主要的目的就是預先建立好 `Tempalte`，而當新的 Index 要被建立時，若符合設定好的 `index_patterns`，則使用這個 `Template`裡的設定來建立 Index。

建立一個 Index Template 時，可以設定以下資訊：

* `index_patterns`: 這是指 index 或 data stream 的名字，可以使用萬用字元 `*` 來定義這個 pattern。
* `data_stream`: 這是 **X-Pack** 中的功能 (Basic license就能使用)，主要是針對 time-series 的資料的一整套 aliase, rollover 的管理機制，一般會在 Index Lifecycle Management 中搭配設定及使用，之後會有文章來介紹這種資料的管理方式。\[官方文件 - Data Streams]\[<https://www.elastic.co/guide/en/elasticsearch/reference/7.9/data-streams.html>]
* `template`: 可以包含 Aliases, Mappings, Index Settings 的設定。
* `composed_of`: 這是 Elasticsearch 7.8 新增的功能，可以套用事先定義好的 Component Template，這個可以設定多個 Component Templates，若有重覆的設定值，會以"後面的蓋掉前面的"來進行合併。
* `priority`: 也是 Elasticsearch 7.8 新增的功能，指定 Index Template 的優先順序，數字愈大愈優先。(若沒有指定，會當成`0`，也就是最低優先權來處理)
* `version`: 讓使用者自己編寫的版本號。
* `_meta`: 也是 Elasticsearch 7.8 新增的功能，讓使用者自己存放任意的物件資料。

> 建立 Index Template 時，會檢查是否有同樣的 `priority` 而 `index_patterns` 有重疊的情況，若有衝突會直接拋出錯誤。

以下就是個簡單的基本例子：

```
PUT _index_template/template_1
{
  "index_patterns" : ["te*"],
  "template": {
    "settings" : {
        "number_of_shards" : 1
    },
    "aliases" : {
        "alias1" : {},
        "{index}-alias" : {} 
    },
    "mappings" : {
      "_source" : { "enabled" : false }
    }
  },
  "composed_of": ["template_component_1", "template_component_2"],
  "priority" : 0,
  "version": 5,
  "_meta": {
    "description": "test by Joe",
    "latest_modify_date": "2020-09-20"
  }
}
```

## Component Template

從 [官方文件 - Put Component Template](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-component-template.html) 可以查到基本的用法，主要是建立**可被 Index Template 重覆使用**的一組設定樣版，而可以設定的內容如下：

* `template`: 可以包含 Aliases, Mappings, Index Settings 的設定。
* `version`: 讓使用者自己編寫的版本號。
* `_meta`: 讓使用者自己存放任意的物件資料。

簡單的例子：

```
PUT _component_template/template_1
{
  "template": {
    "settings" : {
        "number_of_shards" : 1
    },
    "aliases" : {
        "alias1" : {},
        "{index}-alias" : {} 
    },
    "mappings" : {
      "_source" : { "enabled" : false }
    }
  },
  "version": 123
}
```

## 剖析 Elastic 官方的內建的 Template

Elasticsearch X-pack 內建有兩個預設的 Index Template, `logs` 和 `metrics`，以下針對 `logs` 來做剖析。

### 內建的 Index Template - `logs`

從 `_index_template` API 可以直接 get 到這個內建的 `logs` index template.

![index template - logs](https://i.imgur.com/l8u94Rm.png)

這是個 Index Template 是 Elastic Agent 產生的 logs 預設會套用的設置，其中幾個重點：

* 會套用在符合 `logs-*-*`格式的index上
* 有 `priority: 100`很高的優先權，所以要注意如果你自己的 index template 其中`index_patterns`有同樣的格式的話，小心會被這個預設的配置給蓋掉。
* 有使用到 `logs-settings`和 `logs-mappings` 的 Component Templates.

以下是 `logs-settings`的 Component Template.

```
{
  "component_templates" : [
    {
      "name" : "logs-settings",
      "component_template" : {
        "template" : {
          "settings" : {
            "index" : {
              "lifecycle" : {
                "name" : "logs"
              },
              "codec" : "best_compression",
              "query" : {
                "default_field" : [
                  "message"
                ]
              }
            }
          }
        },
        "version" : 0,
        "_meta" : {
          "description" : "default settings for the logs index template installed by x-pack",
          "managed" : true
        }
      }
    }
  ]
}
```

主要設置的是 index settings

* 定義了這一系列 index 一致的管理方式 - `lifecycle`。
* `codec` 指定為 `best_compression`。
* 指定 `query` 的 `default_field`。

另外是 `logs-mappings`的內容

```
{
  "component_templates" : [
    {
      "name" : "logs-mappings",
      "component_template" : {
        "template" : {
          "mappings" : {
            "dynamic_templates" : [
              {
                "strings_as_keyword" : {
                  "mapping" : {
                    "ignore_above" : 1024,
                    "type" : "keyword"
                  },
                  "match_mapping_type" : "string"
                }
              }
            ],
            "date_detection" : false,
            "properties" : {
              "@timestamp" : {
                "type" : "date"
              },
              "ecs" : {
                "properties" : {
                  "version" : {
                    "ignore_above" : 1024,
                    "type" : "keyword"
                  }
                }
              },
              "data_stream" : {
                "properties" : {
                  "namespace" : {
                    "type" : "constant_keyword"
                  },
                  "type" : {
                    "type" : "constant_keyword",
                    "value" : "logs"
                  },
                  "dataset" : {
                    "type" : "constant_keyword"
                  }
                }
              },
              "host" : {
                "properties" : {
                  "ip" : {
                    "type" : "ip"
                  }
                }
              },
              "message" : {
                "type" : "text"
              }
            }
          }
        },
        "version" : 0,
        "_meta" : {
          "description" : "default mappings for the logs index template installed by x-pack",
          "managed" : true
        }
      }
    }
  ]
}
```

設置上的主要重點：

* 有使用到我們前一天介紹的 `dynamic_templates`，其中因為這一個 Index Template 的對象是 **Logs**，當有大量的文字的欄位時，不希望使用預設的 `text` + sub-fields `keyword`的配置，所以直接指定預設 `string` 的欄位就用 `keyword`的型態來處理。

  ```
  {
    "strings_as_keyword" : {
      "mapping" : {
        "ignore_above" : 1024,
        "type" : "keyword"
      },
      "match_mapping_type" : "string"
    }
  }
  ```
* `date_detection`有設為 `false`，在這邊採取比較嚴謹的方式，不特別去嘗試判斷字串的欄位是否為日期格式，若要使用日期格式的規則，可由另外的 Component Template 來指定。
* 有一些 Logs general的欄位，直接在 mappings 中先定義出來，例如： `@timestamp`、`ecs`、`host`、`message`...等。

### 相關的 Index Template

我們從 `_index_template` 可以看到 `logs-`開頭的 Index Template 還有蠻多個

![logs related index templates](https://i.imgur.com/FMx2tSW.png)

找其中一個來看，以 `logs-endpoint-events.process`為例：

![image-20200919044815844](https://i.imgur.com/dRxbNSY.png)

* 從 `index_pattern`可以發現，他也符合 `logs-*-*`的格式，但他更針對某一子集來設置 `logs-endpoint.events.process-*`
* 他的 priority 設置到更高的 `200`，代表只要符合這個子集範圍的 Index，就直接以這個設置優先套用。
* 一些基本的 `index settings` 與 `mappings` 都直接在 `template`中有定義。
* 他也有設置 Component Template - `logs-endpoint.events.process-mappings`

  ```
  {
    "component_templates" : [
      {
        "name" : "logs-endpoint.events.process-mappings",
        "component_template" : {
          "template" : {
            "mappings" : {
              "dynamic" : false,
              "properties" : {
                "@timestamp" : {
                  "type" : "date"
                }
              }
            }
          }
        }
      }
    ]
  }
  ```

  這個 Component 很單純的設定了，只要是這種類型的 Logs，就是要把 `dynamic`設成 `false`。

## 使用 Simulate API 來驗證 Index Template 的效果

當 Index Template 及 Component Template 設計的愈複雜，想要驗證結果如何，可以透過兩個 simulate API 來驗證。

### `Simulate Index`

用途：當有個指定名字的 Index 要產生時，最終他被套用的 Template 結果為何，以及是否有其他因同規則發生重疊的 Template。

![simulate index example](https://i.imgur.com/5vfk4aJ.png)

### `Simulate Template`

用途：當建立這個 Template 後，若有一個 Index 因為這個 Index Template 而建立起來時，裡面的設定會長什麼樣子，以及是否有與其他同規則發生重疊的 Template。

![simulate template](https://i.imgur.com/7WnAKjc.png)

## Index Template 使用的建議

* 可以針對 Index Template 的 `index_pattern` 及 `priority` 建立結構化的管理方式，例如 `logs`與 `logs-xxxxx-*`這樣的子、從關係。
* `version` 一定要給，早期沒有 `_meta` 時可以考慮以日期來當版本號，不過有 `_mate` 後，會建議將此 index template 的基本描述及最後更新時間記錄在 `_meta`中，以便於維護及管理。
* 可共用的設置抽出成 `Component Template`，可以當作某種角色的定義，讓管理更容易。
* 在設計好之後，請多使用 `Simulate API` 來驗證這個 Index Template 的產生結果及影響範圍。
* Elastic 官方還有其他的 Index Template 及 Component Template，在使用前可以多參考這些 Template 的設計方式。

## 參考資料

* [官方文件 - Index Template](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-templates.html#)
* [官方文件 - Put Index Template](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-put-template.html)
* [官方文件 - Put Index Template (legacy)](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-templates-v1.html)
* [官方文件 - Put Component Template](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-component-template.html)


# ES Index 的別名 (Alias)

### 進入此章節的先備知識

* 知道什麼是 Index。
* 知道如何使用 Search 以及 Filter。

### 此章節的重點學習

* Index Alias 的基本使用方式。
* 學會 Index Alias 一些最佳實踐的做法。

***

## Index Alias 的使用方式

### 使用 `_cat` API，列出所有的 Aliases

```
GET _cat/aliases?v
```

使用到的參數說明：

* `v`: verbose，在回傳的結果顯示標題。
* `alias`：依 alias name 進行篩選。

![\_cat list aliases](https://i.imgur.com/d9U4Kma.png)

### Add Alias alias API

建立與更新 index alias，這系列 API 都是針對某個 Index 來操作。

```
PUT /<index>/_alias/<alias>
POST /<index>/_alias/<alias>
PUT /<index>/_aliases/<alias>
POST /<index>/_aliases/<alias>
```

直接以官方的例子來看：

1. 建立一個 `2030` 的 alias，並直接指向 `logs_20302801` 這個 index。

```
PUT /logs_20302801/_alias/2030
```

1. 建立時，也可以指定 `routing` 的值，或是 `filter` 的條件，來讓這個 alias 有限定的用途。

```
PUT /users/_alias/user_12
{
  "routing" : "12",
  "filter" : {
    "term" : {
      "user_id" : 12
    }
  }
}
```

> 使用 `alias` 搭配 `filter` 的用法，可以想像是一些 Relational Database 有提供的 `View` 這樣的功能，透過指定的條件，讓這個 `alias` 是具有某些限定的存取行為、或是具有明確使用意義的。例如 Movies 的資料有一個 Index，但可以建立 `Top Movies` 或是 `Action Movies` 這樣的 alias ，配合特定的 `filter` 條件，但是都指向 Movies 的 Index。

### Update index alias API

這個 API 不會綁定從某個 Index 出發，而且是可以同時進行多項 alias 相關的操作，整個請求所包含的動作會被封裝是一個 `atomic` 的操作，也就是成功就會全部都成功，其中有一個失敗就會全部都失敗，不會只有某部份成功而其他部份發生失敗。

```
POST /_aliases
```

API 的部份細節直接參考 [官方文件 - Update index alias API](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html) ，這邊只快速列出幾個例子：

1. 新增一個 alias `alias2` 並指向 index `my-index-000001` 並且套用特定 `filter` 的規則：

```
POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "my-index-000001",
        "alias": "alias2",
        "filter": { "term": { "user.id": "kimchy" } }
      }
    }
  ]
}
```

1. 透過 `atomic` 的特性，幫 alias 改名字，不會在改名的過程中，造成服務中斷。

```
POST /_aliases
{
  "actions" : [
    { "remove" : { "index" : "test1", "alias" : "alias1" } },
    { "add" : { "index" : "test1", "alias" : "alias2" } }
  ]
}
```

1. 建立 alias 並搭配 `filter` 與 `routing` 的設定：

```
POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "test",
        "alias": "alias2",
        "search_routing": "1,2",
        "index_routing": "2"
      }
    }
  ]
}
```

> 指定 `routing` 可以減少讓 request 跑到其他 shard 運作的時間，能直接強制導到某些 shard 身上。

1. 使用 `alias` 時，若會需要將資料透過 `alias` 來寫入，必預要明確的標示哪個 index 是 `is_write_index` ，這部份的設置在建立 `alias` 與 `index` 的關係時，可以一併加上宣告：

```
POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "test",
        "alias": "alias1",
        "is_write_index": true
      }
    },
    {
      "add": {
        "index": "test2",
        "alias": "alias1"
      }
    }
  ]
}
```

可透過 `atomic` 的特性，來把 `write_index` 的能力，從某個 index 移到另個 index 身上：

```
POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "test",
        "alias": "alias1",
        "is_write_index": false
      }
    }, {
      "add": {
        "index": "test2",
        "alias": "alias1",
        "is_write_index": true
      }
    }
  ]
}
```

## Best Practices

* **盡量全面使用 Index Aliases 來存取 index**：因為要修改 mappings 中已存在的設定時，會需要重建 index、並重新 re-index 資料，因此使用 alias 的話，可以先建立新的 index (新的名字可再額外加上版號，例如 `v2` )，資料從 `v1` 搬到 `v2` 後，再把 alias 直向新的 `v2` index，驗證完成後再刪掉舊的 `v1` index，index 的使用者們因為指向 alias ，也就不用全面更改成新的 index 名字了。
* **善用 Index Aliases 搭配 Filter**：
  1. 減少使用端的查詢複雜化，先將適度的 filter 封裝在 alias 中，明確的定義這個 alias 提供的內容，讓使用端不用處理這部份的邏輯。
  2. 權限的管理，限定使用者只能存取特定的子集合的資料、或是限定時間內的資料，而配合 security 的權限處理，可避免使用者存取到不應取得的資料，或是一口氣查詢太多舊資料導致效能的影響。
* **配合 `routing` 來指定資料寫入到特定的 `shard`**：資料在 Indexing 進入 Elasticsearch 時，會依照 routing value 來進行 hash 的運算 (預設是使用 `_id` 當 routing value，並使用 `murmur3` 的 hash 演算法。)，並依照計算出來的值與 primary shard 的數量來進行 mod 運算，以決定資料要寫入到哪個 shard 身上，但如果有指定的 routing value，就可以決定同樣 routing value 的資料會被計算放到同樣的 shard 身上，這樣對於 performance 的優化或是資料的管理上都可以有許多應用的方式，而 alias 就能配合指定 `routing` 的值來達到這類型的運用。
* **配合 index Lifecycle Management (ILM)**：隨著時間增長的資料，使用 ILM 來管理這些資料時，其中就是搭配 Index Alias 來切換寫入時要指定到的實體 Index 在哪邊。

## 參考資料

* [官方文件 - cat aliases API](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/cat-alias.html)
* [官方文件 - Add index alias](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/indices-add-alias.html)
* [官方文件 - Update index alias API](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html)
* [Index Alias - Elasticsearch Best Practice](https://spoon-elastic.com/all-elastic-search-post/elasticsearch-best-practices-index-alias/)


# 管理你的 Index - Kibana Index

## 前言

前幾天我們介紹到 **Index 建立前你該知道的** 的系列文章的前四篇，這些都是要建立 Index 需要知道的管理工具與運作原理，今天我們會回到 Elastic Cloud，如何透過 Kibana 來進行 Index 的管理。

### 此章節的重點學習

* 如何使用 Kibana 的 GUI 介面來進行 Index Management 的操作。

***

## Kibana 的入口

有些第一次使用 Kibana 的朋友，會被左邊落落長的功能選單搞得頭很痛，不知道從何進入我們 Index 的管理，請直接把左邊工作選單展開，拉到最底下的 **Management** 的區塊，選擇 **Stack Management**。

![Kibana Home](https://i.imgur.com/6QUx6TG.png)

再來就是進入到我們今天的主題 **Index Management**。

![Stack Management](https://i.imgur.com/Q7tnNrv.png)

## Index Management

![Index Management](https://i.imgur.com/tY5Tpu2.png)

進入到 Index Management 時，我們可以看到主要的四個 Tab：

* Indices：這就是我們管理 Index 基本的畫面，可以查列我們 Elasticsearch 中所有的 Index，以及進行基本的管理操作。
* Data Streams：這部份是搭配 Ingest Manager 來建立 time-series 類型的資料管理方式，不過因為目前還在 Beta 階段，這次暫時不討論他。
* Index Templates：管理 Index Templates 的畫面。
* Component Templates：管理 Component Template 的畫面。

### Indices

基本上 Index 的建立，還是必須透過 Create Index API，或是真的把一筆 Document indexing 進入 Elasticsearch 中，而由 ES 自動依照 Index Template 來建立 Index，這裡可以列出所有的 Index 及顯示他的基本資料。

![image-20200923024054396](https://i.imgur.com/cHQuboF.png)

這邊列出幾個重點功能：

* 可以同時對多個 Index 進行管理的操作。

![index management - manage indices](https://i.imgur.com/FhucpSA.png)

* 可以簡單明瞭的知道現在 Index 的各種 Settings, Mappings, Stats，也可直接在 UI 上直接操作進行 settings 修改。

![index management edit settings](https://i.imgur.com/HhCt8sq.png)

* 可以過濾掉系統預設、或是 Elastic 家族某些產品產生的 Index，能過濾當然也能取消嘍\~

![include hidden indices](https://i.imgur.com/mGSqLaX.png)

> 所謂 hidden 的定義，就是 Index name 的開頭是否是 `.`，是的話就是屬於隱藏的 Index。

### Index Templates

再來進入的是 Index templates，這部份就是我們先前介紹的東西，透過 Kibana GUI 的畫面來進行管理。

![image-20200923025544596](https://i.imgur.com/5sBot0Q.png)

這裡要分享一個小技巧，在 Kibana 的 UI 上，會顯示這個 Index Template 是否是 Managed，而這邊會發現這個資訊其實是存在 `Metadata` 中，這個 Metadata 在之前的介紹有提到，是個可以自行定義的 JSON object ，不過我們既然能訂、能查出來，同時若前端有些應用，就能直接配合這欄位來設計及使用，例如這部份他們就把這個 `managed` 的屬行直接訂義在 Index Template 裡。

![Screen Shot 2020-09-23 at 2.59.24 AM](https://i.imgur.com/qVvDS0S.png)

在建立一個新的 Index Template 時，我們先前介紹的功能，也都能在畫面上直接操作、設定及產生，有一些部份還是需要告 JSON object 來宣告，例如：`Aliases`

![create template](https://i.imgur.com/0q89Jt2.png)

### Component Template

這部份和 Index Template 差別不大，建立、設定的畫面都差不多，主要是可以針對 `In use` 或是 `Not in use` 來進行過濾，也可以看到 `Usage count`，在管理方便於將根本沒在用的 Component Template 找出，並進行清理的處理。

![component template](https://i.imgur.com/vMygCLQ.png)

## 結論

透過 Kibana 的 Index Management，讓不少要手動操作的 Index 管理處理，方便了非常多，不過回歸到 Production 的管理上，最好將所有的設定都進入**版本控管**(例如：git)，所以如果是 index template, component template, 這些最好也都能用 Configuration Management (例如：Ansible, Puppet) 的工作來設置，並且都進入版控，以確保**管理 Index 的設置**有被良好的管理。

## 參考資料

* [官方文件 - Index Management](https://www.elastic.co/guide/en/kibana/7.9/managing-indices.html)


# 管理 Index 的 Best Practices

Index 建立起來之後，如何管理你的 Index、也就是如何管理你在 Elasticsearch 中的資料，這裡介紹了各種推薦的工具與實踐的技巧。

* [(1/7) - Shard 的數量與 Rollover & Shrink API](/tech-sharing/uncle-joe-teach-es-elasticsearch/guan-li-index-de-best-practices/shard-de-shu-liang-yu-rollover-shrink-api)
* [(2/7) - 三溫暖架構 - Hot Warm Cold Architecture](/tech-sharing/uncle-joe-teach-es-elasticsearch/guan-li-index-de-best-practices/san-wen-nuan-jia-gou-hot-warm-cold-architecture)
* [(3/7) - Index Lifecycle Management (ILM)](/tech-sharing/uncle-joe-teach-es-elasticsearch/guan-li-index-de-best-practices/index-lifecycle-management-ilm)
* [(4/7) - Rollup](/tech-sharing/uncle-joe-teach-es-elasticsearch/guan-li-index-de-best-practices/rollup)
* [(5/7) - Transform](/tech-sharing/uncle-joe-teach-es-elasticsearch/guan-li-index-de-best-practices/transform)
* [(6/7) - Snapshot Lifecycle Management (SLM)](/tech-sharing/uncle-joe-teach-es-elasticsearch/guan-li-index-de-best-practices/snapshot-lifecycle-management-slm)
* [(7/7) - 總結](/tech-sharing/uncle-joe-teach-es-elasticsearch/guan-li-index-de-best-practices/zong-jie)


# Shard 的數量與 Rollover & Shrink API

## 前言

當我們將資料 Indexing 進入 Elasticsearch 後，隨著時光的飛逝，我們 Index 裡的資料通常也會愈來愈多，這時如何有效的管理資料就是一件很重要的事，這個主題的文章會來介紹 Index 管理的 Best Practices，這篇文章會著重在 Index 中 Shard 的數量配置方式及優化的方法。

### 進入此章節的先備知識

* 知道什麼是 Elasticsearch Index、Alias、Index Template。
* 初步了解 Lucene, Shard, Segment Files。

### 此章節的重點學習

* Shard 的數量該怎麼設定。
* 隨著時間的變化，如何透過 Rollover API 及 Shrink API 來優化 Elasticsearch 中的 Indices。

***

## Shard 的重要觀念

![elasticsearch shard](https://i.imgur.com/TNhlKWQ.jpg)

在一開始我們說明一下 Shard 的幾個重要的觀念：

* Shard 是 ES Cluster 中切分資料來儲存的最小單位，裡面是透過 Lucene 管理的一群 Segment Files，也就是 Lucene Index。
* Shard 的大小與數量：
  * **最大多大？：** 沒有一定的限制，主要是依照 Elasticsearch Cluster 硬體的性能來決定，早期官方文件是寫建議 10G，但忘了從哪個版本開始(應該有二、三年前)，文件上已改成 20\~40G，隨著硬體規格的成長、Elasticsearch 版本演進時效能不斷的優化，這個數字會需要實際測試才知道，以我實際的經驗，200G以上的 shard 也有遇到過。
  * **最小多小？：** 如果要設多個 shard 的話，`最好不要小於 1G`，否則這時候切 shard 的好處太少，不如不要切 shard。
  * **數量多的好處？：** 數量愈多的話，好處是資料量非常大時可以分散到多個 Node 去儲存、又或是有大量的 Indexing 的需求可以讓多個 Node 去分擔，若是單一 Shard 的儲存空間足夠，又沒有大量 Indexing 的需求時，應該讓 Shard 大一些，並且數量少一點會比較好。
  * **數量少的好處？：** 數量愈少其實處理搜尋的效能會較好，但取捨是當 Cluster 要 Rebalancing 時，一個巨大的 Shard 要搬移的成本很高。

## Index 的管理方式

若你的資料量是固定的、或是成長非常緩慢的，請直接依照上述 shard 的觀念、硬體規格、成長規劃來設定 shard 的數量，以下著重在介紹隨著時間成長的資料的 Index 管理方式。

隨著時間增長的資料，資料要能分散存取的話，官方建議使用 `time-based indices` 來進行資料的管理，而不是在一開始設定大量的 shard 數量來期待之後資料的成長，因為 shard 的數量在建立 Index 時設定好之後，就不能再修改。

> Elasticsearch 6.x 版之前，預設的 shard 數量是 5，但是從 7.x 版開始，預設的 shard 數量已改成 1，官方建議使用 time-based indices 來處理隨時間成長的資料。

### Index 的數量太多的成本？

Index 的資訊是存在 Cluster stats 中，如果 Index 數量太多 (特別是 Mapping 欄位又很多時)，會讓 Cluster stats 變很大，這會佔用 JVM heap size，也會造成 update 這種需透用到 cluster stats 資訊來確保處理一致性的請求效能變慢。

### 針對 time-based indices 的管理方式 - Rollover Pattern

一般這種資料我們的期待與使用情境如下：

* 在 indexing 大量資料時，為了有較好的效能，我們一開始會將 shard 數量提高。
* 當資料趨近穩定、不太會變動時，我們為了要有更好的查詢效能，我們會希望 shard 的數量愈少愈好，但也不要到太肥大的狀態。
* 有時可能覺得一天一個 Index 會比較好照時間來管理過期的資料，但每天的資料量可能不一定，Shard 的數量又不見得都適合套用同一套規則。

因此這時官方的 Rollover & Shrink 會是很好的解決方案，這個 **Rollover Pattern** 的基本運作如下：

* 定一個 indexing 新文件專用的 Alias ，並將他指到目前 active index。
* 定另一個 searching 用的 Alias，指定所有不論新、舊的 indices。
* active index 可以指定有很多個 shard，讓寫入的效能能最佳。
* 當 active index 資料量達到一個條件、或是時間過太久了，會進行 Rollover，產生一個新的 index 成為 active index，並把 indexing 用的 alias 指向他。
* 舊的 index 被搬到 code node，並觸發 Shrink 的動作，將他轉成單一 shard 的 index，並且觸發 forced-marged 和 compression ，以進行資源的優化。

以下分別針對 Rollover API 及 Shrink API 來示範 Rollover Pattern 的實作：

#### Rollover

建立一個 index - `logs-000001` 與指向他的 alias - `logs_write`：

```
PUT /logs-000001 
{
  "aliases": {
    "logs_write": {}
  }
}
```

使用 Rollover API 建立 `超過7天` 或 `滿1000筆文件` 要觸發自動 rollover：

```
POST /logs_write/_rollover 
{
  "conditions": {
    "max_age":   "7d",
    "max_docs":  1000
  }
}
```

> Rollover 的規則，會依照 index 名字後有 `-` 而且接著數字，就會接受這是要 Rollover 的 index，但長出來新的 index 會依照這邊的定義，會是六位數並且會補 0 的格式。
>
> 例如當筆數滿 1000 後，會產生新的 index `logs-000002`。

若新的 index 不是這個名命的規則，可以明確的指定名字。

```
POST /my_alias/_rollover/my_new_index_name
{
  "conditions": {
    "max_age":   "7d",
    "max_docs":  1000
  }
}
```

#### Shrink

Shrink 的 API 主要的目的就是把 index 的 shard 數量變少，而變少的規則，必需是原來數值的**因數**，例如原本是 8 ，那只能 shrink 成 4 或 2 或 1、原本是 15 的話，只能 shrink 成 5 或 3 或 1。

運作機制如下：

* 建立一個新的 index，其設定會完全照之前的 index，只有 shard 數量是變少的，此時 index 的狀態會先保持 `_close`。
* 透過 OS 的 hard-links 來合併 shards ，如果 OS file system 不支援，會直接用複製的方式將 shard 複製到新的 index 裡。
* 最後再將新的 index 重新 open。

Shrink API 如下：

```
POST my_source_index/_shrink/my_target_index
```

另外也可以宣告一些類似 create index API 能指定的參數：

```
POST my_source_index/_shrink/my_target_index
{
  "settings": {
    "index.number_of_replicas": 1,
    "index.number_of_shards": 1, 
    "index.codec": "best_compression" 
  },
  "aliases": {
    "my_search_indices": {}
  }
}
```

透過這些設定，就能指定 shrink 成特定數量的 shards 了。 (不過要記得必須是原來 shard 數量的**因數**才行。)

## 參考資料

* [官方 Blog - How many shards should I have in my Elasticsearch cluster?](https://www.elastic.co/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster)
* [官方 Blog - Manging Elasticsearch time-based indices efficiency](https://www.elastic.co/blog/managing-time-based-indices-efficiently)
* [官方文件 - Avoid oversharding](https://www.elastic.co/guide/en/elasticsearch/reference/7.x/avoid-oversharding.html)
* [Optimizing Elasticsearch Stronger Better Faster](https://medium.com/analytics-vidhya/optimising-elasticsearch-stronger-better-faster-e56fed2bcc8b)


# 三溫暖架構 - Hot Warm Cold Architecture

## 前言

前一天介紹了 Index 中 Shard 數量對效能的影響以及透過 Rollover 及 Shrink API 來管理 time-based 資料的方法，接下來要介紹 Elastic Stack 6.3 時陸續開始所推出針對這些隨時間增長的資料更全面的整體管理解決方案。

### 進入此章節的先備知識

* Elasticsearch Index, Shard, Segment File 的基本認識。
* Index Template。
* Rollover 與 Shrink 的機制。

### 此章節的重點學習

* Elasticsearch 中的 hot-warm-cold architecture。
* Elasticsearch Freeze API。

***

## 冷熱資料管理的基本觀念

針對 time-based 的資料，Elasticsearch 在 5.x 版的時候，就提出了 Hot-Warm 的架構，主要是針對 **常用的資料** 與 **時間較久也就是不常用的資料** 分開用不同的硬體來存放，以達到資源有效的利用，不過從 Elastic Stack 6.3 開始，陸續在接連的幾個版本中，針對 time-based 資料的管理機制，提供了更全面的解決方案，就像是以下的拼圖，拼湊得更加完整了，從這篇開始，我們會陸續的做相關的介紹。

![hot-warm-features-frozen-indices-transp.png](https://i.imgur.com/96yYghR.png)

## Elastic Cloud Deployment 的入口

當你要建立 Hot-Warm Architecture 時，若是使用 Elastic Cloud，在 Deployment 的階段就有這個配置可以選擇，但是明眼的一看，他只有 **Hot-Warm** 並沒有 **Cold**，這是因為實際上機器的配置，並沒有特別針對 Cold 的配置來安排機器，至少這是目前在 Elastic Cloud 上還沒看到的。

![Elastic Cloud Hot-warm architecture](https://i.imgur.com/nFqQfBL.png)

若是進入到 **Customize deployment** 後，可以看到實際的配置，是只有 **Hot** 與 **Warm** ，沒有 Cold 的配置。

![elastic cloud hot-warm architecture 2](https://i.imgur.com/gDOSrPX.png)

## Hot-Warm-Cold Architecture

這邊提到了 Hot Warm Cold 三種狀態，可想而知，就是除了分出 Hot Warm 之外，還要更細分出 Cold 的存放方式，所以使用上的資料管理順序，也就如下圖所示，新進來的資料就是會配置到 Hot Data Node，再來一段時間後，進入 Warm Data Node，再過一段時間後，最後進入 Code Data Node。

![hot-warm-cold architecture](https://i.imgur.com/Le5JDWC.jpg)

而這三種的定義與差異如下：

* **Hot:** 因為負責最新的資料，所以會負責處理 indexing 的資料，同時新的資料被使用的機率也最高，所以也會處理頻繁的 searching 請求。
* **Warm:** 當一份 Index 的資料成長到一定的量、或是已經過了一段時間，會將這份資料轉到 Warm data node，這時 Warn data node 是 `read-only` 也就是不會需要處理 indexing 的請求，只會專注在處理 searching 的請求。
* **Cold:** 當一份 Index 的資料經過一段較長的時間，判定會較少使用到時，會將他移到 Cold data node，並且針對這些資料進行 `Freeze` 的處理 (下面會介紹)，這時資料會被以最節省系統資源的狀態下被保存，還是可以提供查詢，但速度會較慢。

### Hot Warm Cold 的資源使用狀況

![hot-warm-cold architecture heap usage](https://i.imgur.com/dZueTUH.png)

從上圖可以看到，在這種架構下，各自針對 JVM heap 的使用狀況：

* Hot Node 因為會處理 Indexing 的請求，所以 JVM heap 會有一定比例拿來處理 Indexing。
* Warm Node 不處理 Indexing 所以只會有一半 JVM heap 拿來處理 query request，剩下的會來給 Lucene 用作暫存。
* Cold Node 不會處理 Indexing，而且針對 query request 所產生的 transient cache 也會一用完就盡快的釋放，減少 heap 的使用。

## Freeze Indices

沒錯，所謂的 code data 就是被 `freeze` 的 indices。

這邊解釋一下 Elasticsearch 為什麼要特別建立這個 Freeze 的機制。

一般的 Index，隨著搜尋的用法等不同，JVM heap需要的大小與 Index 存放的資料量比例，大約是 1:8 \~1:100 不等，因此這會限制了某個 Index 能存放的資料量的大小。

但是我們在較少用的舊資料上，我們希望能有更有效的資源使用，也就是一個同樣記憶體大小的 Node 能存放更多的資料、也願意犧牲一些查詢時的反應速度，因此 Elasticsearch 就設計的這個 Freeze 的機制，讓被 Freeze 的 Index 能盡量減少 heap 的使用。

### Freeze API

要把一個 Index freeze 或 unfreeze 使用的方式如下：

```
POST /my_index/_freeze
POST /my_index/_unfreeze
```

> 目前 Freeze Index 時，Elasticsearch 會在背後將 Index close 再重新打開，所以 cluster 的狀態會短暫進入 `紅燈` 直到 index 重新打開時，primary shard 被重新載入。

> 要注意的另一件事是，freezed Index 是 `read-only` ，也就是連 Segment file 的 `_forcemerge` 都不能操作，所以要 Freeze 之前，較好的 practice 是先記得將 Index 進行 segment file 的 force merge。
>
> ```
> POST /sampledata/_forcemerge?max_num_segments=1
> ```

### 搜尋被 Freeze 的 Index

為了避免 Freezed index 在無意識的情況下被存取到， Freezed Index 是會被指定為 `throttled` 的，也就是預設 Elasticsearch 的搜尋，是不會查到 Freezed index 的資料，若是要查詢包含 Freezed index 裡的資料，需要在搜尋時帶上 `ignore_throttled=false` 的參數。

```
GET /sampledata/_search?ignore_throttled=false
{
 "query": {
   "match": {
     "name": "jane"
   }
 }
}
```

### 在 Kibana 搜尋到 Freezed Index 裡的資料

若是在 Kibana 裡，想要搜尋 Freezed Index 的資料的話，需要從以下的地方進入：

左滿 Menu 選單底下的 `Stack Management` > 找到 Kibana 區塊裡的 `Advanced Settings` > 找到 Search 區塊裡的 `Search in frozen indices` 並設定成 `On`。

![kibana search in frozen indices](https://i.imgur.com/DDrHDi3.png)

以上是 Hot-Warm-Cold Architecture 的介紹及原理，下一篇將會介紹如何透過 Index Lifecycle Management 來達到輕鬆設定、並直接利用 Rollover, Shrink, Hot-Warm-Cold Architecture 的機制來有效的管理我們的 Indices。

## 參考資料

* [官方文件 - Index Lifecycle Management](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/index-lifecycle-management.html)
* [官方 Blog - Creating frozen indices with the Elasticsearch Freeze index API](https://www.elastic.co/blog/creating-frozen-indices-with-the-elasticsearch-freeze-index-api)
* [Efficiency in Elasticsearch](https://coggle.it/diagram/XIpfGBc5Ajc-0zoI/t/efficiency-in-elasticsearch)


# Index Lifecycle Management (ILM)

## 前言

先前我們介紹過了 Index, Shard, Segment Files, Hot-Warm-Cold Architecture，知道在不同的時期要有效率的使用 Index 可以進行什麼樣的配置，接下來要介紹的是全面的 Index Lifecycle 的機制，就會使用到之前介紹的各種機制，來進行 Index 生命週期的管理。

### 進入此章節的先備知識

* Elasticsearch Index, Shard, Segment File 的基本認識。
* Index Template 的基本認識。
* Rollover 與 Shrink 的機制。
* Hot-Warm-Cold Architecture。

### 此章節的重點學習

* Index Lifecycle Management (ILM) 的使用方式。
* 如何在 Elastic Cloud 中的 Kibana 來設定 ILM。
* ILM 設定中背後的原理。

***

## Index Lifecycle Management (ILM)

Index Lifecycle Management 顧名思意，就是用來管理 Index 的生命週期，而在 Elasticsearch Index 生命週期主要定義有四個段。

### Index Lifecycle Management 主要的 4 個階段

* **Hot:** 最新的資料，通常是用來放最新的資料。 `可以寫入、可以查詢`
* **Warm:** 資料進來後，不再寫入時，但還是會常常的查用，通常會放在這個階段。 `不能寫入、可以查詢`
* **Cold:** 資料已放蠻久的，不常使用到，但還是希望需要用到時能馬上就能用，但願意接受速度較慢一些，就會放在這個階段。 `不能寫入、可以查詢(但較慢)`
* **Delete:** 資源總是有限，空間也是有限，時間久了總是有些舊資料應該要從 Elasticsearch 中刪掉。

![ilm four phases](https://i.imgur.com/NmmfRFd.png)

上圖可以解釋當資料隨著時間變化，會不斷產新的 Index，並且隨著時間變化 (流水號數字愈大的是愈新的，數字愈小是時間愈久)，會逐漸移到下一個階段中。

在 ILM 中，你可以建立一個 **Policy** 來指定想要設定哪些階段、以及每個階段要進行的 Action (動作) 是什麼，而在 ILM 中可以執行的動作有以下這些。

### Index Lifecycle Management 中可以用的 Action 有哪些

* **Rollover:** 當原 index 達到 **一定的大小**、**資料筆數**、**資料存放一定時間** 時，自動建立新的 Index 來放新進來的資料，不會讓某 Index 一直無限的增長下去。這個動作可以針對 Index Alias 或 Data Stream 進行設定。
* **Shrink:** 將多個 Shard 的 Index 轉成較少 Shard 數量的 Index。
* **Force merge:** 將一個 Shard 中的 Segment Files 進行合併，可以釋放出被刪掉的文件在原先 `read-only` 的 Segment File 所佔用的空間，也能加快查詢的速度。
* **Freeze:** 將很少使用的 Index，以盡量不使用到 heap size 的方式來存放，
* **Delete:** 刪掉 Index。
* **Allocate:** 指定 Index replica 的數量，以及指定 Index 可以被放在哪些 shards 的規則。([Index-level shard allocation filtering](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/shard-allocation-filtering.html#index-allocation-filters))
* **Set Priority:** 指定 Index 的處理優先權，也就是當 node 重新啟動的時候，較高優先權的 Index 會先被 recover 而優先回到可被使用的狀態。
* **Unfollow:** 將 Cross Cluster Replication 機制中，會 follow 的 Index 給取消 follow。在 Rollover, Shrink 處理時會自動執行這個 Action。
* **Wait for snapshot:** 等到 Snapshot 完成後才能刪除 index。

### 每個階段可以進行的 Action 有哪些

| Index Lifecycle Management 的階段 | 此階段中可以執行的操作有什麼                                                               |
| ------------------------------ | ---------------------------------------------------------------------------- |
| **Hot**                        | `Force merge`, `Rollover`, `Set priority`, `Unfollow`                        |
| **Warm**                       | `Allocate`, `Force merge`, `Read only`, `Set priority`, `Shrink`, `Unfollow` |
| **Cold**                       | `Allocate`, `Freeze`, `Set priority`, `Unfollow`                             |
| **Delete**                     | `Delete`, `Wait for snapshot`                                                |

## 透過 Kibana 來設定 Index Lifecycle Policies

我們從 Kibana 左側選單底下的 **Stack Management** 進入後，可以看到以下的畫面，左邊有個 **Index Lifecycle Policies** 可以進入 ILM 的管理畫面。

![kibana ilm](https://i.imgur.com/sFPKPO3.png)

點右上的 **Create Policy** 即可開始建立。

### Hot phase

在這個 Hot phase 的設定中，主要包含了前面介紹的一些設定 `Rollover`, `Index Priority`，不過在 Kibana 的畫可上，並沒有讓你設定 `Force merge`，畢竟這操作並不是適合用在一般的 Hot phase。

![index lifecycle policy - hot](https://i.imgur.com/PUXLup8.png)

### Warm Phase

在 Warm Phase 中，可以指定當 Hot Phase 中的 Index 發生 Rollover 時，是否直接把 Index 移到 Warm Phase 的階段。

若要移的話，會需要指定什麼樣的 attribute 是屬於 Warm Phase。

若使用 Elastic Cloud，他只有兩種 attribute - `data:hot` 和 `data:warm`，這也是依照硬體規格來配置過的。

除此之外可以指定 Shrink, Force merge, Index priority 等設定。

![index lifecycle policy - warm](https://i.imgur.com/DthWuFS.png)

### Cold Phase

在 Code Phase 的階段，也如同 Warm Phase 一樣，可以指定當 Rollover 發生多久之後 (若沒有啟動 Rollover，則是以 Index 建立的時間來判斷)，要把 Index 移到 Rollover，不過因為 Elastic Cloud 目前提供的配置中，沒針對 Cold Phase 規劃的硬體配置，因此還沒辦法直接選擇，相信不久的將來會推出的。

另外可以指定是否要啟動 Freeze，以及是否要指定 Index Priority。

![index lifecycle policy - cold](https://i.imgur.com/fXashHi.png)

### Delete Phase

Delete Phase 的設定很簡單，就是要在 index rollover 發生多久之後 (若沒有啟動 Rollover，則是以 Index 建立的時間來判斷)，要刪除這個 Index。

另外可以指定，刪除之讀是否確保這個 Index 已經有備份過，這個 Snapshot Policy 會在這系列文章的面後有所介紹。

![index lifecycle policy - delete](https://i.imgur.com/0d68Wfc.png)

## Index Lifecycle Policy 與 Index 之間的關係

當我們 Index Lifecycle Policy 設定好之後，在主畫面的 **Actions** 可以看到有兩個設定。

![index lifecycle policy vs index](https://i.imgur.com/xf6nwlU.png)

1. **View indices linked to policy:** 點下就會跳到 Index Management 的頁面，並且過濾出被這個 Indes Lifecycle Policy 所管理的 Indices 有哪些。
2. **Add policy to index template:** 這個可以指定要套用到哪個 Index Template，讓新的 Index 被建立時，自動套用這個管理機制。 ![index lifecycle policy apply index template](https://i.imgur.com/EBtb8rg.png)

### 如何檢視 Index Lifecycle Policy 執行的狀況

當 Index Lifecycle Policy 建立起來之後，若想知道某個 Index, Data stream, Index Alias 所對應的 Index Lifecycle Policy 及執行的狀況、目前在哪個階段…等，可以透過 **Explain lifecycle API** 來查看：

```
GET my-index-000001/_ilm/explain
```

以下是這個 Explain API 回傳的結構：

```
{
  "indices": {
    "my-index-000001": {
      "index": "my-index-000001",
      "managed": true, 
      "policy": "my_policy", 
      "lifecycle_date_millis": 1538475653281, 
      "age": "15s", 
      "phase": "new",
      "phase_time_millis": 1538475653317, 
      "action": "complete",
      "action_time_millis": 1538475653317, 
      "step": "complete",
      "step_time_millis": 1538475653317 
    }
  }
}
```

若是有正在執行中的步驟，也都會有詳細的資訊回傳，若有錯誤發生，也都能看得到。

建議可以上 [官方文件 - Index Lifecycle Management Explain API](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/ilm-explain-lifecycle.html) 看更多詳細的介紹。

若有錯誤時，在排除完成後，也可以直接使用 Retry API 來進行重試。

```
POST /my-index-000001/_ilm/retry
```

## 參考資料

* [官方文件 - Index Lifecycle Management](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/index-lifecycle-management.html)
* [官方文件 - Index-level shard allocation filtering](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/shard-allocation-filtering.html#index-allocation-filters)
* [官方文件 - Index Lifecycle Management Explain API](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/ilm-explain-lifecycle.html)
* [这么简单的ES索引生命周期管理，不了解一下吗～](https://zhuanlan.zhihu.com/p/137810661)


# Rollup

## 前言

Rollup 是 Elasticsearch 6.3 推出的新功能，目前還在 Experimental 階段，官方不建議直接使用在 Production 環境中，不過版本的演進已從 6.3 發展到了到 7.13，相信已經發展到了接近成熟的階段 ，因此這次還是選擇介紹這功能，不過實際在正式環境中，建議還是等官方正式移除 Experimental 的階段後再開始使用。

### 進入此章節的先備知識

* Elasticsearch Query DSL。
* Elasticsearch Aggregation 的基本認識。

### 此章節的重點學習

* Rollup 的使用方式、時機及限制。
* 透過 Kibana 來建立 Rollup Jobs。

***

## 什麼是 Rollup

Rollup 是一個把用來依時間分析的歷史資料的時間 **顆粒度( Granularity )** 變大，以節省空間的 **定期執行** 的機制。

例如我們收集的即時資料 1秒鐘 有 1000筆，每天就會有 86,400,000 筆資料，而一年後就會有 365 \* 86,400,000 筆，這個佔用的儲存空間是很大的，而好處是我們可以隨時查到精確的某一筆原始資料。

但平常在分析上，若我們將時間顆拉度拉最小只能看到 **分鐘** 為單位，這時只需要保存以1分鐘產生一筆彙總的資料，這樣的筆數就只剩下 86,400 筆，只剩下原來的 1/1000。 (當然實際大小可能有所不同，因為彙總的資料的資料大小可能和原始資料有點落差。)

## 使用 Rollup 的好處

* 節省磁碟**儲存空間**
* 節省執行查詢的**記憶體**快取空間
* 節省 JVM heap 載入 Index 的**記憶體**空間
* 查詢的**速度**比較快

## 如何在 Kibana 中建立 Rollup Job

### 建立 Rollup Job

首先，進入 Kibana > Stack Management 後，點選左側 Data 區塊中的 Rollup Jobs。

![kibana rollup job](https://i.imgur.com/O1eIneU.png)

點 Create rollup job 之後，會進入設定頁面：

![create rollup job 1](https://i.imgur.com/f6MEft5.png)

這邊的設定基本上都蠻直覺的，依照旁邊的說明設定即可。

* **Name:** 幫 Rollup Job 取個名字。
* **Data flow:** 指定 Index pattern 以及 Rollup 產生的 Index 名字。
* **Schedule:** 這個 Rollup Job 執行的頻率。
* **Ho manay documents do you want to roll up at a time:** 執行時批次處理的大小，數字愈來處理速度愈快，但記憶體也耗的愈多。
* **How long should the rollup job wait before rolling up new data:** 在執行 Rollup 時，可以設定一個 Latency 執，這個 Latency 指的是資料在 ingest 進入 ES 時，有可能會有一些延遲 ，一但設了這個 Latency，Rollup Job 就會多等到 Latency 的時間過了之後，才會處理這部份的資料。

> 這邊有個要注意的 `Index pattern` 不應該包含到 `Rollup index name` ，上圖就是一個錯誤的例子，這樣會造成處理邏輯上的錯誤，如果你設定了這樣的配置，最終會看到以下這樣的錯誤畫面。
>
> <img src="https://i.imgur.com/YXz6V3I.png" alt="create rollup job error" data-size="original">

接下來要分別設定 Date histogram 的時間顆粒度：

![create rollup job 2](https://i.imgur.com/peJKjTG.png)

設定有哪些欄位會使用到 Term bucketing：

![create rollup job 3](https://i.imgur.com/9wgDGss.png)

哪些欄位可能會進行 Histogram 的 aggregation：

![create rollup job 4](https://i.imgur.com/il4zcuV.png)

哪些欄位會使用到 Metrics，這邊請依照資料的特性來判斷，有些沒必要的就不用勾選了。

![create rollup job 5](https://i.imgur.com/NCAt07w.png)

最後 Review 完沒問題時，就可以直接建立。

![create rollup job 6](https://i.imgur.com/a0vPB1d.png)

### 查看 Rollup Jobs

當建立完成後，在 Rollup Jobs 的選單中可以看到我們建立的這個 Job。

![view rollup jobs](https://i.imgur.com/ouvdDCZ.png)

點開後也可以看到他的 Stats，包含目前已經處理了多少 Documents、Pages、以及執行過多少次。

![image-20200927200414569](https://i.imgur.com/GBRM6zu.png)

這時到 Index Management 來查看 Elasticsearch 中的 Index，就可以看到由這個 Rollup Job 所產生的 Index 了。

> 記得要把上面 `Include rollup indices` 打勾，才會看得到。

![rollup index](https://i.imgur.com/p5Nycc4.png)

這時 Rollup Job 產生的資料已經可以使用了。

而且我們可以看到， `rollup-logstash-daily` 佔用的是 **446mb** 的空間，比起整體 `logstash-1` \~ `logstash-10` 的總量，大約只佔了 **1/10** 。

## 使用 Rollup 的資料

### 先來剖析一下 Rollup 產生出來的資料

首先，這是我們測試用的一筆資料，這是一個 HTTP Request 的 Log：

```
{
        "_index" : "logstash-10",
        "_type" : "_doc",
        "_id" : "VGlRz3QBFWvcdj-NvfL6",
        "_score" : 1.0,
        "_source" : {
          "index" : "logstash-10",
          "@timestamp" : "2020-10-27T04:57:02.344Z",
          "ip" : "148.214.137.20",
          "extension" : "jpg",
          "response" : "200",
          "geo" : {
            "coordinates" : {
              "lat" : 34.48339944,
              "lon" : -104.2171967
            },
            "src" : "AU",
            "dest" : "ID",
            "srcdest" : "AU:ID"
          },
          "@tags" : [
            "warning",
            "info"
          ],
          "utc_time" : "2020-10-27T04:57:02.344Z",
          "referer" : "http://www.slate.com/success/lisa-nowak",
          "agent" : "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)",
          "clientip" : "148.214.137.20",
          "bytes" : 3586,
          "host" : "media-for-the-masses.theacademyofperformingartsandscience.org",
          "request" : "/uploads/konstantin-feoktistov.jpg",
          "url" : "https://media-for-the-masses.theacademyofperformingartsandscience.org/uploads/konstantin-feoktistov.jpg",
          "@message" : "148.214.137.20 - - [2020-10-27T04:57:02.344Z] \"GET /uploads/konstantin-feoktistov.jpg HTTP/1.1\" 200 3586 \"-\" \"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\"",
          "spaces" : "this   is   a   thing    with lots of     spaces       wwwwoooooo",
          "xss" : """<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==" onload="alert('XSS found via img-onload!')"><script>alert("XSS found via script-tag!")</script>""",
          "headings" : [
            "<h3>chris-hadfield</h5>",
            "http://facebook.com/success/christopher-ferguson"
          ],
          "links" : [
            "alexander-poleshchuk@www.slate.com",
            "http://www.slate.com/login/pavel-belyayev",
            "www.twitter.com"
          ],
          "relatedContent" : [ ],
          "machine" : {
            "os" : "osx",
            "ram" : 6442450944
          },
          "longValues" : "efearpjmayxX"
        }
      }
```

而經過 Rollup Job 產生出來的資料，若我們直接用 `_search` API 來查詢：

```
GET rollup-logstash-daily/_search
```

會看到他回傳的結果長這樣子：

```
{
        "_index" : "rollup-logstash-daily",
        "_type" : "_doc",
        "_id" : "rollup-logstash-daily$8LKzd9hsjA2IhasDI3Z8kA",
        "_score" : 1.0,
        "_source" : {
          "@timestamp.date_histogram.time_zone" : "Asia/Taipei",
          "memory.histogram.interval" : 1000,
          "memory.histogram.value" : null,
          "bytes.value_count.value" : 1.0,
          "@timestamp.date_histogram._count" : 1,
          "memory.histogram._count" : 1,
          "geo.src.terms.value" : "SD",
          "geo.srcdest.terms.value" : "SD:AO",
          "machine.ram.max.value" : 2.147483648E9,
          "bytes.histogram.interval" : 1000,
          "bytes.histogram.value" : 0.0,
          "geo.dest.terms.value" : "AO",
          "phpmemory.histogram.value" : null,
          "bytes.sum.value" : 271.0,
          "bytes.min.value" : 271.0,
          "geo.src.terms._count" : 1,
          "geo.dest.terms._count" : 1,
          "_rollup.id" : "rollup-logstash-daily",
          "response.keyword.terms.value" : "200",
          "@timestamp.date_histogram.timestamp" : 1598544000000,
          "referer.terms._count" : 1,
          "referer.terms.value" : "http://www.slate.com/error/sigmund-j-hn",
          "bytes.max.value" : 271.0,
          "machine.os.keyword.terms.value" : "ios",
          "url.keyword.terms._count" : 1,
          "@timestamp.date_histogram.interval" : "24h",
          "bytes.avg.value" : 271.0,
          "host.keyword.terms._count" : 1,
          "machine.ram.min.value" : 2.147483648E9,
          "machine.ram.histogram.value" : 2.147483E9,
          "bytes.avg._count" : 1.0,
          "request.keyword.terms._count" : 1,
          "request.keyword.terms.value" : "/canhaz/yuri-artyukhin.gif",
          "phpmemory.histogram.interval" : 1000,
          "phpmemory.histogram._count" : 1,
          "bytes.histogram._count" : 1,
          "geo.srcdest.terms._count" : 1,
          "url.keyword.terms.value" : "https://motion-media.theacademyofperformingartsandscience.org/canhaz/yuri-artyukhin.gif",
          "_rollup.version" : 2,
          "machine.os.keyword.terms._count" : 1,
          "machine.ram.histogram._count" : 1,
          "host.keyword.terms.value" : "motion-media.theacademyofperformingartsandscience.org",
          "response.keyword.terms._count" : 1,
          "machine.ram.avg._count" : 1.0,
          "machine.ram.histogram.interval" : 1000,
          "machine.ram.avg.value" : 2.147483648E9
        }
      },
```

Rollup 後的資料，已經是使用另外的彙總的格式來儲存，所以他已經沒有原本的資料內容了。

### 查詢 Rollup 的資料

從上面的例子可以看到，使用 `_search` 的結果會是長得不一樣的文件，也因此 Rollup 後的資料，若是要進行 search 時，要使用 `_rollup_search` 的 API。

這邊先從原始的資料來查詢：

![rollup-demo-original-search](https://i.imgur.com/DIYONno.png)

再來使用 `_rollup_search` 來執行一樣的 query：

![rollup-demo-rollup-search](https://i.imgur.com/QhMryy0.png)

可以看到結果是一樣的，通常如果是有一些 metrics 的數值運算的話，有可能會有小誤差。

另外在使用上會有一些限制，以下會做相關的介紹。

> 這邊特別要說一下，使用 `_rollup_search` 若是跨到 Rollup Index 與 原始的資料 時，不會計算重覆的資料，這讓 Rollup 與 Live data 混搭使用非常的方便。

### 使用的限制

Rollup 的資料在使用上有一些限制

* `_rollup_search` 可同時包含多個非 rollup index，但一次只能包含一個 rollup index。
* 在使用 Aggregation 時，只有 rollup job 中有被指定的欄位可以被使用。
* 時間顆粒度只能以 rollup 的基本單位往上的乘數，例如設定的是 `3d` ，就只能用 `3d`, `6d`, `9d` ...以此類推。
* Query 的方式只能使用 `Term`, `Terms`, `Range`, `MatchAll`, 以及 `Boolean`, `ContantScore` 等 compound 查詢。
* 能使用的 Bucketing Aggregation 為：`Date Histogram`, `Histogram`, `Terms。`
* 能使用的 Metrics Aggregation 為：`Min`, `Max`, `Sum`, `Average`, `Value Count`。
* 若是在建立 Rollup Job 時有指定時區的話，在使用 Date Histogram 時也一定要指定同樣的時區。

### 在 Kibana 建立新的 Index Pattern

在 Kibana 中，若要使用 Rollup 的資料，要特別建立一個 `Rollup Index Pattern` ，如下圖：

![image-20200927201453586](https://i.imgur.com/y2iCCdo.png)

建立好這個 Index Pattern 後，就可以在 Discover, Virtual, Dashboard 來使用了。

![image-20200927213635040](https://i.imgur.com/94CK31v.png)

## 使用建議與注意事項

### Rollup 目前還不支援 Rollover

(2020.09.29 更新) Rollup 目前產生出來的 Index 還無法套用 Rollover 的機制，這部份的支援已在官方的討論中，有可能在未來會直接整併成 Index Lifecycle Management 中的其中一個 Action，可參考這篇 [Githib Issue](https://github.com/elastic/elasticsearch/issues/48003)。

### Rollup 的 Interval 支援度

ES 在 Rollup 的支援上，除了讓你整理成較大的時間**顆粒度**來儲存資料，例如以 `1天` 為單位。

如果你在進行 Query 與 Aggregate 時，當然最小的顆粒度就是 1天，但如果你要的單位是比1天還大的顆粒度，如：`1週`、`1個月`、`一季`、`一年`…等，你**不需要**為這每個顆粒度建立獨立的 Rollup job，這些都能在 `_rollup_search` 的計算時，直接以 `1天` 的資料來彙總計算出來。

### 盡量使用 Fixed time interval 而不要用 Calendar time interval

這部份的說明比較複雜，建議直接參考 [官方文件的說明](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/rollup-understanding-groups.html#rollup-understanding-group-intervals) ，主要的概念是，建議少用 Calendar 的"一天"，而是用"24小時"、少用 Calendar 的"一個月"，而是用"30天"，這樣的 Interval 單位，因為 Calendar 時間在查詢的時，會是比較難複雜且使用時的限制較多。

> 另外若使用上是有指定時區的話，記得設定好時區，這樣在時間的切分上才會如使用的預期。

### 參考騰訊的做法

騰訊針對 Metrics 類型的資料：

* 在原始的即時資料監控下，監控的時間顆粒度是 **10秒**。
* 在一個月以前的監控數據，時間顆粒度是 **1小時**。

這樣的顆粒度，是在設計 Rollup Job 時需先考量清楚的，確保使用的情境與需求來製訂規則。

> Rollup Job 一但決定下去，就無法改變，除非重建 Rollup Job。

## 參考資料

* [官方 Blog - Create, Manage, and Visualize Rollup Data in Kibana](https://www.elastic.co/blog/how-to-create-manage-and-visualize-elasticsearch-rollup-data-in-kibana)
* [騰訊萬億級 Elasticsearch 技術解密](https://cloud.tencent.com/developer/article/1598364)


# Transform

## 前言

這一系列的文章分別介紹了隨著時間不斷增長資料的 Index 該如何管理，除了透過 Index Lifecycle Management 移除舊的資料、透過 Rollup 把資料顆粒度變大、這邊要介紹的是透過 Transform 的功能，把資料進行轉換後再儲存。

### 進入此章節的先備知識

* Elasticsearch Query DSL。
* 一定程度的了解 Aggregation 的使用方式。
* 資料分析的基本觀念。

### 此章節的重點學習

* 如何使用 Transform 將 Index 資料轉換成另外的資料檢視維度並儲存至另個 Index 中。
* 如何在 Kibana 上建立 Transform 並使用他的結果。

***

## 什麼是 Transform

Transform 是 **Elasticsearch 7.2** 推出的強大新功能，這是在 `X-pack` 中的 `Basic License` ，所以 Open Source 版是沒有這個功能的，Transform 主要目的是將 Index 資料透過 **Pivot (樞杻)** 分析的方式運算後轉換成另外的資料檢視維度並儲存至另個 Index 中，這個資料轉換與整理的機制，背後就是用 Aggregation 來進行處理，但既然 Aggregation 就能做這這個 Transform 的處理，為什麼還要用 Transform 呢？下面會介紹什麼時候該用 Transform 。

### 何時使用 Transform 而不是用 Aggregation

* 如果你要取得的是 Aggregation 後的所有結果，而不是只有 top-N 的資料時。
* 如果你有使用一些資料，會定期的要撈取 **先前一段時間的訪問量(例如是 Unique User Visit Count)**、**某段時間的各類型的產品總銷售數字**、**某段時間某個商品被瀏覽的次數**…等，這種有特定功能、且每次要查的時候會耗較多運算資源的資料。
* 如果你在進行資料分析時，腦袋裡想到的解法都是要用許多 SQL 語法中的 `Group By ... Having ...`、`Sub Query + Where 或 Order By`…等在 Elasticsearch 要用到大量的 Bucketing + `bucket selector` …甚至你不知道要怎麼寫這個 Aggregation。
* 如果你在使用 `Pipeline Aggregation` 時，卻又要使用到排序，且遇到 Elasticsearch 目前並不支援這樣的操作。
* 如果你想將一些 Summary 的 Aggregation 結果另外存起來，減少每次查詢所要消耗的查詢資源。

## 使用 Kibana 建立 Transform

接下來直接進入 **Kibana** > **Stack Management** > **Transform** 的畫面，來建立一個 Transform。

### Create Transform

建立 Transform 時，第一步會要選擇資料的來源，這邊會需要先在 Kibana 建立好 Index Pattern。

![new transform](https://i.imgur.com/XAQdL8d.png)

接下來會要決定 `Group By` 的欄位會有哪些，以及資料分析時要有哪些 Aggregation 的運算。

![create transform](https://i.imgur.com/9Wxfdld.png)

在資料來源的檢視畫面上，可以切換 `Histogram charts` ，可以看到每一個欄位的資料分步狀況。

![creat transform - histogram](https://i.imgur.com/O2HxgZt.png)

在選擇好欄位後，底下會可以預覽這個 Transform 的 Aggregation 查詢的結果。

![create transform](https://i.imgur.com/Xh8QYqN.png)

確認資料彙總的方式沒問題後，接下來設定 Transform 的基本資訊，包含 Transfor ID, 產出結果存放目的地 Index 名稱，若是有選擇時間的 Group By 條件，也會要指定 Time Filter field name，再來指定 Date field 用來判斷資料是否有更新，最後是 Delay。

![transform details](https://i.imgur.com/gH3BXCL.png)

一切就緒後，就是執行 `Create and start` ，若好奇這個 UI 產生出來的 Create Transform API payload 長什麼樣，也可以選擇 `Copy to clipboard` ，並貼到 Kibana Dev Tools 去執行。

![create tranform](https://i.imgur.com/bv6PITw.png)

執行後，可以到 Transforms 頁面看建立的 Transform Job，或是到 Discover 查詢 Tranform 出來的資料內容。

![image-20200927225028081](https://i.imgur.com/H9yeJdp.png)

### 查看 Transform 的結果

建立好之後，在 Transforms 畫面可以看到這個 Transform job 正在執行，也可以看到他的 Status。

![image-20200927225117950](https://i.imgur.com/vh8aQTh.png)

若我們進入到 Discover 頁面，可以發現從 Kibana 建立的 Transform 自動也建立了 Index Pattern ，所以可以直接選擇這個 Index Pattern 來瀏覽資料。

![image-20200927225404534](https://i.imgur.com/qHw7WSU.png)

若直接從 `_search` API 來查看裡面的資料，每一個 Document 很單純的就是我們所指定彙總的結果。

![search transform data](https://i.imgur.com/yxWMCdu.png)

一但有了這個 Transform 後的資料，也可以直接用 Virtualize 以這個資料來源拉出想要分析的資料圖表。

![virtualize transform data](https://i.imgur.com/s3yY0HQ.png)

接下來也可以把多個 Virtualize 的圖表拉到 Dashboard 中，你會發現這個直接從 Transform 的結果當資料來源，執行速度比原先快上非常的多，而且還可以再進一步使用 Aggregation 來達到更多變化的應用！

## 注意事項

* Transform 是一個會定期執行、而且是使用 Aggregation 這樣很耗資源的查詢處理，所以在建立 Transform 時，要注意系統的資源與執行的資料範圍、適度的使用 `docs_per_second` 的節流設定，避免造成 Elasticsearch Cluster 服務的穩定性。
* 當使用 Transform 時，目的地的 Index 也需要先建立好 Mapping 以免造成 Dynamic Mapping 的結果不如預期，所以記得先 Create Index 或使用 Index Template。
* Transform 在執行時，為了有效的處理所有的 buckets 的分頁，會使 Composite Aggregation 來進行處理，而這個處理在進行中的時候，如果源頭的資料有新增、修改、刪除，不一定會被包含在這次 aggregation 的結果中。
* Transform 使用的是 Composite Aggregation ，因此在 Buckets 的數量限制上、及 terms query 的 `terms` 數量限制上，都可能會需要依照使用的情境來做對應的調整，相關的設定在 `max_page_search_size` 及 `index.max_terms_count`。
* 如果有自己調整 Transform 的 `Frequency` 時，要注意，這個值同時也是 Transform 發生錯誤時，retry 的機制使用的間隔時間。
* 使用 Transform 時，如果執行的當下，資料還在 indexing 的處理中、還無法被 search 出來時，這樣 Transform 執行的結果可能會沒有包含到這些資料，而且會標記成已經處理過，下一次資料沒有異動的話也不會再處理，所以要設定好 `sync.tim.delay` 的值，要有適度的時間等讓 indexing 的資料完成處理，並且可被搜尋到，特別是要注意是否有正確的對應到 `index.refresh_interval`的值。

## 參考資料

* [官方文件 - Transforms](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/transforms.html)


# Snapshot Lifecycle Management (SLM)

## 前言

這個系列的文章前面主要介紹的都是如何優化 Index 的儲存空間、執行效率…等各種優化，最後這一部份是資料的備份，雖然 Elasticsearch Cluster 可以有許多的 Nodes, 也能設置多份的 Replica 來確保資料的可靠性，但是定期的資料備份還是不能少的，能有效在發生災難時能救回資料，例如： [微軟 6.5T 的 Elasticsearch 料被駭客刪了](https://www.ithome.com.tw/news/140115) 這樣的事件。

### 進入此章節的先備知識

* Elasticsearch Index 的相關基本知識。
* 若備份在雲端儲存空間的話 (例如 AWS S3)，會要知道這些雲端儲存空間的基本知識。

### 此章節的重點學習

* Snapshot / Restore 的使用方式。
* 如何在 Kibana 建立 Snapshot Policy，以及使用 AWS S3 Repository。

***

## Snapshot

**Snapshot** 是 Elasticsearch 用來備份的方式，這邊要注意一件事，如果你打算備份 Elasticsearch 的資料，千萬不要自己從磁碟區去備份 Elasticsearch 的 data 資料夾內的資料，因為有很大的機率當你要復原時，Elasticsearch 在啟動的檢查中會告訴你資料是毀損的，因此在 Elasticsearch 要備份資料，請使用 **Snapshot**。

### Repository

使用 Snapshot 的時候，第一個要先決定你備份的資料要存哪邊，所以要先產生 Repository。

Repository 主要支援的類型有下面幾種：

* `fs`: shared file system，要使用 file system 來建立 Repository 的話，要先在 `elasticsearch.yml` 設定檔中指定好 `path.repo` 的路徑。
* `repository-s3`: 以 AWS S3 來當 Repository, 要另外安裝 [官方的 Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/7.9/repository-s3.html)。
* `repository-hdfs`: 以 Hadoop HDFS 來當 Repository, 要另外安裝 [官方的 Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/7.9/repository-hdfs.html)。
* `repository-gcs`: 以 Google Cloud Storage 來當 Repository, 要另外安裝 [官方的 Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/7.9/repository-gcs.html)。
* `repository-azure`: 以 Azure 來當 Repository, 要另外安裝 [官方的 Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/7.9/repository-azure.html)。
* `repository-swift`: 這是 [OpenStack Swift 的 Repository 擴充套件](https://github.com/BigDataBoutique/elasticsearch-repository-swift)，是社群開發、非官方的，也是要另外安裝。

#### 在 Elastic Cloud 中增加其他 Azure 和 GCP 的 Repository 支援

若是在 Elastic Cloud 中要使用其他的 Repository 時，要先到 Deployement 中去安裝 Plugins。

![ec edit deployement](https://i.imgur.com/v6d2xTN.png)

在 **Elasticsearch plugins, extensions, and settings** 的區塊展開後，就可以看到 repository 的 plugins 可以選擇。

![ec install plugins](https://i.imgur.com/CerADUz.png)

#### 建立 Repository

安裝好之後，在 **Kibana** > **Stack Management** 裡 **Data** 區塊的 **Snapshot and Restore** 就可以 **Register a repository**。

![register a repository](https://i.imgur.com/MvuWnTH.png)

這就就可以看到 Azure, GCS, AWS S3 的支援了。

![image-20200929013923134](https://i.imgur.com/CEkT3o5.png)

這邊以 AWS S3 為例，先在自己的 AWS S3 上建立一個 bucket，然後設定好 IAM 權限：

```
{
  "Statement": [
    {
      "Action": [
        "s3:*"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:s3:::bucket-name",
        "arn:aws:s3:::bucket-name/*"
      ]
    }
  ]
}
```

然後再透過 Elastic Cloud 的 Console (不是 Kibana 哦!)，進入 Security 將 IAM 的 Access Key & Secret Key 設定在 Keystore 中。

> 注意，格式上是 `s3.client.{client}.access_key` 和 `s3.client.{client}.secret_key` 。
>
> 這個 `client` 是回到 Kibana Register repository 時要指定的自訂的 client 名字。

![Screen Shot 2020-09-29 at 2.04.10 AM](https://i.imgur.com/EO3WUNM.png)

接下來就繼續將 Register Repository 的步驟走完。

![image-20200929020623597](https://i.imgur.com/Qhoj17v.png)

建立完成後，也可以點擊 Repository ，並選擇右方的 `Verify repository` 確認是否能正常存取。

![repositories](https://i.imgur.com/w4YREEb.png)

### 建立 Snapshot Policy

決定好備份要儲存的 Repository 後，接下來就可以開始建立 Snapshot Policy 了，這是一個可以定時自動備份，並且決定 Snapshot 要保留多少份、保留多久的機制。

![snapshot policy overview](https://i.imgur.com/UVIgOeT.png)

進入 Create Policy 後，設定 policy 的名字、 Snapshot 的名字，以及選擇要用哪一個 Repository，最後是決定定期的週期規則。

> Repository 一但被某一個 Policy 使用後，就不能重覆被另一個 Policy 使用。

![create policy](https://i.imgur.com/MkDhG0t.png)

再來是選擇要備份的 index 或是 Data stream，以及相關的設定。

![image-20200929021107559](https://i.imgur.com/iEfat0g.png)

**Shapshot retention** 是設定備份要保留多久，以及最少保留的份數及最多保留的份數。

![image-20200929021135240](https://i.imgur.com/NO9QZjM.png)

最後確認一切設置正確後，即可建立。

![create snapshot policy review](https://i.imgur.com/D81nPIa.png)

### 查看 Snapshot Policy 的執行狀況

建立完成後，可以直接 **Run Policy**，並且在 Snapshots 分頁中去看執行的狀態。

![taking snapshot](https://i.imgur.com/G7G4qMc.png)

進入 AWS S3 也可以看到 snapshot 的資料被寫入。

![aws s3 repository](https://i.imgur.com/Ob2lblI.png)

## Restore 復原某個版本的資料

在 **Snapshots** 的畫面中，可以找到你想要回復的那份 Snapshot，並點選後面的 **Restore** 按紐。

![Screen Shot 2020-09-29 at 2.36.15 AM](https://i.imgur.com/Jjgc0Rf.png)

**Restore** 時，可以指定要針對哪些特定的 Index，甚至可以改變 restore 之後的名字 (支援 Regular expression group 的方式來取代名字)。

![restore 1](https://i.imgur.com/Po6XjEA.png)

再來 Index Settings 的部份，可以修改 index settings 甚至是清除原先 Snapshot index 中的某些 index settings。

![image-20200929023902608](https://i.imgur.com/0ONInyb.png)

確認沒問題後直接進行 **Restore snapshot**。

![image-20200929024657214](https://i.imgur.com/MBG4BIm.png)

### 檢視 Restore 的結果

在 **Restore Status** 的頁面中，可以看到執行的 Restore 進度與結果。

![restore result](https://i.imgur.com/dxlBkRf.png)

Restore 完成後，我們確認一下這個 Index `restored_logstash-10` 的確已經被復原回來了。

![image-20200929024828659](https://i.imgur.com/C5dD0kp.png)

### Restore 的版本的支援度

這邊要先注意，Restore 是不允許到舊版的 Elasticsearch Cluster 中，也就是 7.6 版的 snapshot 不能 restore 到 7.5 版的環境。

再來要 Restore 到新版本的 Elasticsearch 的話，請參考下面的表格：

![snapshot restore version matrix](https://i.imgur.com/DGclaM1.png)

只有在這表格支援的版本才能進行 restore。

如果真的要 restore 到版本差異較大的環境時，能做的方法是先 restore 到最新支援 restore 的版本，再來透過遠端 reindex 的方式來進行資料的搬移。

## 參考資料

* [官方文件 - Snapshot and restore](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/snapshot-restore.html)
* [官方文件 - Snapshot/Restore Repository Plugins](https://www.elastic.co/guide/en/elasticsearch/plugins/7.9/repository.html)


# 總結

### 前言

這個系列的文章總共介紹了各種 Index Management 的管理方式，最後這篇會融合各種方式，以全貌的方式來看 Elastisearch 的 Index 管理方式，如何應用前面介紹的各種機制，來建立完整的 Index Management。

#### 進入此章節的先備知識

* Elasticsearch Index, Shard, Segment Files 的基本知識。
* 建議可先閱讀此系列文章的前面章節部份，或是也可以將這篇當成 overview，不清楚的部份再回頭去對照前面章節的詳細介紹。

#### 此章節的重點學習

* 在 Elasticsearch 中，如何建立一個完整的 Index 管理機制。

***

### Elasticsearch Index Management Overview

進入 Index 的管理，有以下幾個重點：

* **Segment Files 的數量**：數量愈多對 **查詢的速度** 與 **磁碟的空間** 愈不好。
* **Shard 的數量**：愈多對 **Indexing 的速度** 愈好，但對 **查詢成本較高** ，單一 Shard 愈大對 **Cluster 的 Rebalance** 的成本愈高。
* **Index 的大小**：愈大對於 **查詢效率** 愈好，以 Time-based 資料來看的話，影響的是資料移轉到下一個階段的等待時間。
* **資料的新舊程度**：新的資料通常 **使用頻率** 較頻繁，會給較好的硬體資源、較舊的資料較少使用，可配置較差的硬體資源。
* **時間顆粒度**：當資料量很大時，在觀察過往的資料往往時間顆粒度會抓較大，並且觀察的彙總的結果 (例如：每天的 log 數量、每天的銷售金額、每天的觀看次數…等)。
* **Index 愈來愈多**：資源總是有限，太舊的資料會面臨刪除，可保留的是彙總的結果。
* **資料的安全性**：除了存取控制要妥善的限制之外，資料的備份也是非常重要的機制。

這邊提供一個 Index Lifecycle Management 的概觀圖：

![Elasticsearch Index Management Overview](https://i.imgur.com/Bk35OlJ.png)

隨著資料的進入，以下是主要的管理階段：

1. 使用 **Index Lifecycle Management** 來管理資料的 Hot Warm Cold Architecture：
   1. 新的資料大量寫入 Hot Nodes，所以會配置 **較多的 Primary shards**。
   2. 隨時間及資料量的成長，為了確保 Index 的資料量在有控制的範圍、以及讓 Hot data 進入 Warm data 以確保 Hot node 的硬體資源分配，會透過 **Rollover** 將 Index 進行 rotate ，產生新的 Index 來接新的資料，而原先的 Index 會進入下一個 Warm 的階段。
   3. 進入到 Warm data 的階段，會進入 read-only ，所以會透過 **Force Merge** 與 **Shink** 將 Segment Files 數量 與 Shards 數量進行最佳化，也可同時配合 **Compress** 進行儲存空間的優化。
   4. 時間過更久之後，資料進入 Code data 階段，會將 Index 進行 **Freeze** ，以減少 JVM heap 的使用量，提供較高的延遲反應，但還是能即時使用的服務狀態。
   5. 再更久的資料，可再確認已經被備份過之後進行 **刪除**。
2. 使用 Rollup 將資料以較大的時間顆粒度來儲存：
   1. Rollup 可以從 Hot, Warm, Cold 任何的 Index 當中把資料讀出，並且以較大的 **時間顆粒度** 進行彙總運算，並將結果儲存新的 Index 中。
   2. Rollup 是定時執行，同時 Rollup 的資料在透過 `_rollup_search` 查詢時，可**混搭 Live Data + Rollup Data**，結果會自動合併並去除掉重覆的，也因此當舊的資料被刪除後，存在 Rollup 的資料一樣能提供彙總後的查詢結果。
3. 使用 Transform 將資料以另外的分析結果來儲存：
   1. 將資料以 **Pivot** 的方式進行分析運算、並將結果儲存在獨立的 Index 中。
   2. 適合複雜的 Aggregation 的運算及彙總報表的定期處理工作。
   3. 也可以這個機制從查詢的彙總結果建立成 Index，並當作 Ingest 處理時 Enrich 資料的 Lookup Data Source。
4. 使用 Snapshot Lifecycle Management 來管理資料的備份。
   1. 設定 **定期的備份**。
   2. 設定備份的 **保存時間及數量**，確保備份所佔用的空間不會無限增長。

以上是 Elasticsearch 針對 Index 管理上的各種使用方式的搭配組合，建議搭配前面的文章進行細部的探討，透過這樣的概念進行資料的管理，可以得到較佳的資源使用與配置的規劃安排。

### 參考資料

* 請參考本章節的前面幾篇介紹


# Elastic Cloud 比免費版還多的功能

Elastic Stack 包含了各種的功能，針對 SaaS 服務中 Standard 版本的功能，以及自己架設 (on-premise) 的 Basic 版本，有什麼差異? 如果你用 Elastic 官方代管的 SaaS 服務，最基本的版本就能得到自行架設要花大錢買進階 License 才能得到的功能有哪些？

* [(1/6) Elastic Stack 的方案比較與銷售方式](/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-bi-mian-fei-ban-huan-duo-de-gong-neng/elastic-stack-de-fang-an-bi-jiao-yu-xiao-shou-fang-shi)
* [(2/6) Centralized Beats Management](/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-bi-mian-fei-ban-huan-duo-de-gong-neng/centralized-beats-management)
* [(3/6) Centralized Pipeline Management](/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-bi-mian-fei-ban-huan-duo-de-gong-neng/centralized-pipeline-management)
* [(4/6) Watcher](/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-bi-mian-fei-ban-huan-duo-de-gong-neng/watcher)
* [(5/6) Elasticsearch Token Service](/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-bi-mian-fei-ban-huan-duo-de-gong-neng/elasticsearch-token-service)
* [(6/6) Multi-stack monitoring & Automatic stack issue alerts](/tech-sharing/uncle-joe-teach-es-elasticsearch/elastic-cloud-bi-mian-fei-ban-huan-duo-de-gong-neng/multi-stack-monitoring-and-automatic-stack-issue-alerts)


# Elastic Stack 的方案比較與銷售方式

### 前言

在使用 Elastic Stack 時，總共有哪些方案，而針對 Elastic Cloud 的使用，我們所選擇不同的方案能使用的功能又有哪些差異，這篇文章會先就基本面來做個比較與介紹。

#### 進入此章節的先備知識

* Elastic Stack 整體的基本認識、能大約看懂 Elastic Stack 功能列表中專有名詞指的是什麼。

#### 此章節的重點學習

* Elastic Stack 的 賣法。
* Elastic Pricing 與 Subscription 的比較。
* 使用 Elastic 官方所提供 SaaS Elastic Cloud 最便宜的 Standard 版本，比自己架設的免費 Basic 版本有多出哪些功能。

***

### Elastic Stack 的安裝方式

首先，我們先針對接下來會出現的名詞定義有個解釋：

* **Elastic Stack**：由 Elastic 官方提供使用者即時、迅速、可造的資料分析解決方案的統稱，包含了 Elasticsearch, Kibana, Beats, Logstash…等產品。
* **Elastic Cloud**：Elastic Stack 產品的佈署、配置、管理的一套管理機制，可以自行佈署在 public cloud 或是 private cloud，甚至是直接由 Elastic 官方佈署好，並以 SaaS 的形式來提供使用。

所以，當我們要安裝 Elastic Stack 時，有以下三種方式：

* 使用官方的 SaaS 服務 - **Managed Elastic Cloud**：直接打開 [Elastic Cloud](http://cloud.elastic.co/) 的網頁，信用卡填進去就可以在網頁上開始操作，並產生出佈建在 AWS, GCP 或 Azure 的 Elastic Stack Deployement 了。
* 自己架設 Elastic Cloud - **Self-managed Elastic Cloud**：想要使用像是 Elastic Cloud 這麼方便的管理工具來建置 Elastic Stack，但是又想要有更多的自主控制權、想要佈建在自己的機房、或是使用自己管理的 Cloud Provider、甚至想要有更進階的 Deployement 配置方式，就可以使用 Elastic Cloud Enterprise (ECE) 或是 Elastic Cloud on Kubernetes (ECK) 的方式來架設。
* 什麼都自己來 - **Self-managed Elastic Stack**：可能由手動架設、或是自己用任何容器化的佈署工具、Configuration Management…，總之不使用 Elastic Cloud 佈署機制的方式都算是這類。

### Elastic Stack 的賣法

再來，我們進入官方網站的 Pricing 頁面：

![elastic website - pricing](https://i.imgur.com/3PbRTMD.png)

這邊可以看到有三大類型，也就是對照到上面我們介紹到三種安裝方式。

我們把這三種方式的賣網的網址列出來如下：

* Managed Elastic Cloud: <https://www.elastic.co/pricing/>
* Self-managed Elastic Cloud: <https://www.elastic.co/subscriptions/enterprise>
* Self-managed Elastic Stack: <https://www.elastic.co/subscriptions>

聰明的大家可以看到兩個關鍵字 `Pricing` 和 `Subscriptions` ，這兩個的差別就是：

* **Pricing**： 使用 SaaS 服務時，照用量來收費的價格：
* **Subscription**：使用自己架設 (self-managed) 的方式，但又想使用到 Basic (免費版) 以上的版本時，要與 Elastic 官方購買訂閱制的 License，並將這個 License 登錄到 Elastic Stack 中，來啟用這些進階的功能。

接下來我們針對各種方案進行簡單介紹。

#### Managed Elastic Cloud

![elastic cloud pricing](https://i.imgur.com/r1I4YOO.png)

若是使用 Elastic 官方提供的 SaaS 服務，使用的價格可以直接透過 [Elastic Cloud 價格計算機](https://cloud.elastic.co/pricing) 來評估計算。

總共有四個方案，從網頁的下方有完整的列出每個方案的功能列表與比較表。

![elastic cloud plan comparsion](https://i.imgur.com/3kKXSFk.png)

使用這方案時，主要就是依照你要的功能來決定你要買的是哪個版本，並且網頁上可以使用 **月結** 的方式來支付，若是要長時間使用建議與官方銷售人員聯繫，可以使用 **年結** 的方式來付款，並且可以有一些優惠折扣。

#### Self-managed Elastic Cloud

再來的方案是，自己架設 Elastic Cloud，如前面提到的，這方案有分兩種方式：

**Elastic Cloud Enterprise (ECE)**

這部份基本上就是要錢的，而且授權是使用 `Platinum` 等級，費用的部份就要與官方的銷售人員聯繫。

> 之前詢價時得到的起跳價是 54,000 鎂/年，不同時間點與條件詢到的價格可能有不同，因此僅供參考。

![ece](https://i.imgur.com/SZmpNxF.png)

**Elastic Cloud on Kubernetes (ECK)**

另一個方式就是使用 Kubernetes 來架設，這裡有個 Basic 的免費版，大家可以直接來使用，若是要 Enterprise 的版本，同樣的也是要聯繫銷售窗口取得報價。

![eck](https://i.imgur.com/dgUTuCa.png)

這邊提到的 Basic 或是 Platinum 的版本，都是指 Subscription 的 License ，所以細節的功能比較，都會和下面的 Self-managed Elastic Stack 一樣。

#### Self-managed Elastic Stack

這方案是自己架設了，所以要來看的只有他的各種 Subscription License 版本的差異。

![elastic stack subscriptions](https://i.imgur.com/wjTN2NC.png)

這邊可以看到 **免費版** 有包含 **Open Source** 和 **Basic** 兩種版本，若是要經過再開發、把這樣加上自己開發項目的進階方案再另外拿來賺錢銷售的話，可以使用 Open Source 版，但不能直接使用免費的 Basic 版哦！這邊要特別注意版權的細節，詳細可與官方銷售聯繫及確認授權的問題。

也因此，當我們使用到像是其他提供商提供的 Elasticsearch 服務，例如 AWS Elasticsearch Service，就必然是使用 Open Source 的版本來開發，裡面自然也少了許多 Elastic 官方開發的好用功能。

各版本的功能差異，也可以從網頁下方的比較表查看：

![elastic stack license comprison](https://i.imgur.com/y70HFwM.png)

### Elastic Cloud Standard 版本，比自己架設的 Basic 版本有多出哪些功能

這系列的文章，主要會針對官方提供的 SaaS - Elastic Cloud Standard 的版本 (也就是最便宜的版本)，和自己架設的 Basic 版本 (也就是不用錢的版本)，來比較使用官方的方案有什麼特別的好處。

> 這次比較的方式，都是以不購買到進階的 Gold, Platinum, Enterprise，而是兩種的最基本的方案來比較。

我這邊不會全部完整的比較，但會挑出幾個我覺得差異較大、也特別實用的、也會是這系列文章主要會介紹的功能來比較。

#### Stack Management

下方是 **Self-managed 的 Subscription** 方案：

![stack mgt - subscription](https://i.imgur.com/KiZov5d.png)

而這是 **SaaS 的 Elastic Cloud** 的方案：

![stack mgt - saas](https://i.imgur.com/uNYkzyI.png)

可以看到兩個功能是特別有包含在 SaaS 的 Standard 的方案中

* Centralized Beats management
* Centralized Logstash pipeline management

#### Alerting

下方是 **Self-managed 的 Subscription** 方案：

![image-20201001192839025](https://i.imgur.com/XvodLKI.png)

而這是 **SaaS 的 Elastic Cloud** 的方案：

![Screen Shot 2020-10-01 at 7.29.12 PM](https://i.imgur.com/Kgi62gv.png)

可以看到以下的功能是特別有包含在 SaaS 的 Standard 的方案中：

* Watcher

#### Elastic Stack Security

下方是 **Self-managed 的 Subscription** 方案：

![image-20201001193130239](https://i.imgur.com/tVuQZpr.png)

而這是 **SaaS 的 Elastic Cloud** 的方案：

![Screen Shot 2020-10-01 at 7.31.52 PM](https://i.imgur.com/UJmLbQD.png)

可以看到以下的功能是特別有包含在 SaaS 的 Standard 的方案中：

* Elasticsearch Token Service

#### Stack Monitoring

下方是 **Self-managed 的 Subscription** 方案：

![image-20201001193328221](https://i.imgur.com/Xf8tnbu.png)

而這是 **SaaS 的 Elastic Cloud** 的方案：

![Screen Shot 2020-10-01 at 7.33.50 PM](https://i.imgur.com/ywOCCt2.png)

可以看到以下的功能是特別有包含在 SaaS 的 Standard 的方案中：

* Multi-stack monitoring
* Automatic stack issue alerts

#### 差異比較總結

這邊列出來的差異項目，將會是這系列文章中接下介紹的項目，總結如下：

* Centralized Beats management
* Centralized Logstash pipeline management
* Watcher
* Elasticsearch Token Service
* Multi-stack monitoring
* Automatic stack issue alerts

### 參考資料

* [官方網站 - Pricing](https://www.elastic.co/pricing/)


# Centralized Beats Management

## 前言

在使用 Elastic Cloud 的 SaaS 服務時，Centralized Beats Management 這個功能是在 Standard 的版本上即可使用，不像是自己架設的版本，要買到 Gold License 才能使用，這篇文章主要介紹 Centralized Beats Management 的功能如何使用、以及介紹他在協助管理 Beats 上的便利性。

> 請特別注意， Centralized Beats Management 是在 Elastic Stack 6.5 時推出的 `Beta` 版本的功能，目前已在官方文章上明確註明已 `暫停開發` ，達來會有其他更全面的解決方案來取代這個功能，所以使用上請留意。

### 進入此章節的先備知識

* 知道什麼是 Elastic Stack 中的 Beats。
* 使用過 Filebeat 和 Metricbeat。

### 此章節的重點學習

* 如使在 Elastic Cloud 中的 Kibana 使用 Centralized Beats Management。

***

## Centralized Beats Management 的介紹

顧名思意，**Centralized Beats Management** 的目的就是能使用一個集中化的管理介面，讓我們能輕鬆的管理安裝在各機器上的 Beats，特別是改變一些組態設定時，能直接套用到各 Beats 身上。

這個功能是 Elastic Stack 6.5 時推出的 `Beta` 版本功能，並且是在 **Elastic Gold License** 的級別以上、或是 Elastic Cloud service 的 **Standard License** 版本才能使用。

目前有支援的 Beats 只有以下兩種：

* Filebeat
* Metricbeat

以下是 Centralized Beats Management 的架構圖：

![img](https://i.imgur.com/WddMPrD.png)

## 如何在 Kibana 上設定 Beats Central Management

### Enroll Beat

在 Kibana 上要設定 Beats Central Management時，依照下圖的路線進入這個功能，並點選 Enroll Beat。

![beats central management](https://i.imgur.com/VGPz3eI.png)

進入 Enroll Beat 畫面時，會讓你選擇要 Enroll 的是 Filebeat 還是 Metricbeat，並且選擇 Platform 的類型。

選擇完成後，會產生出 enroll 的 script，從這個 script 可以看到主要是將這個 beat 註冊到我們這個 Elastic Cloud 的路徑上，並且包含了 credential 的資訊，讓這個 client 可以透過這 credential 與 Central Management 互相溝通。

![enroll beats - 1](https://i.imgur.com/k00Owqg.png)

接下來到已安裝好 Filebeat 或 Metricbeat 的機器上，執行 `enroll` 的 command。

![enroll clients](https://i.imgur.com/fpgYDH2.png)

執行完成後，當 Filebeat 或 Metricbeat client 向 server 註冊完成後，畫面會自動帶出已註冊的這台 client。

![enroll beat result](https://i.imgur.com/RG7XU2K.png)

接下來可以進行 tag 的設定，這邊的 tag 可以綁定一種組態設定，而之後可以使用 tag 的方式來套用到各機器上，以便快速的套用不同的組態設定到各機器。

![create tag](https://i.imgur.com/NbZ193b.png)

我們可以幫這個 tag 去定義要給他的 configuration ，也就是針對 Filebeat 或是 Metricbeat 的 config 以及 module 的設定。

![create tag - add config](https://i.imgur.com/Y4lz9Iu.png)

這樣就是一個基本 Enroll 的執行過程。

![image-20201002185555879](https://i.imgur.com/jqGakLc.png)

### Enrolled Beats

回到 Enrolled Beats 的畫面後，可以看到每個已註冊進來的 Beats，並且他們各自的 tag 狀態，這時看到的 Config Status 還是 `Offline`

![image-20201002190053520](https://i.imgur.com/1bIjQEb.png)

### Run Enrolled Beats

接著我們到各 Filebeat 或是 Metricbeat 的機器上，讓他們執行起來

```
./filebeat run
```

或是

```
./metricbeat run
```

回到 Enrolled Beats 的 Config Status 來查看，會發現他們都執行起來了。

![running enrolled beats](https://i.imgur.com/NVpZGad.png)

我們這時可以動態調整每個 beat 他們的 Tags，來調整他們的組態設定。

### Configuration Tags

![image-20201002190006222](https://i.imgur.com/HEqwBO9.png)

每個 Tag 的配置方式，可依照佈署環境的狀態來定義想要的 **組態模組化** 的切割方式，例如：

* web-server: 要安裝 Filebeat apache module 來收集 apache log。
* db-server: 要安裝 Metricbeat mysql module 來收集 mysql 的 metrics、也要安裝 system module 來收集主機的 system metrics、也要安裝 Filebeat mysql module 來收集 mysql 的 logs。
* proxy-server: 要安裝 Filebeat 並且使用自定義的 Paths 到指定的路徑去收集 logs。
* elasticsearch-output: 要有一組 output 到 elasticsearch 的設定。

這時每次有安裝某個 beats 時，可以依照該機器的角色，來分配給他合適的 tags。

如此就能簡單的從 Kibana 做到 Beats 的組態集中化的管理。

## 參考資料

* [官方文件 - Beats Central Management](https://www.elastic.co/guide/en/kibana/7.9/managing-beats.html)
* [官方 Blog - Introducting Beats Central Management in Elastic Stack](https://www.elastic.co/blog/introducing-beats-central-management-in-the-elastic-stack)


# Centralized Pipeline Management

## 前言

Logstash 是一個非常強大的資料 ETL (Extract-Transform-Load) 工具，在具有一定量級的處理環境時，常常會佈署多台的 Logstash，以及配合許多情境而有對應配置的 ETL 設定，這些對於管理及運維上都有一定的複雜度及成本，這篇文章將會介紹，如何透過 X-Pack 中 **Gold License** 以上的授權、或是 Elastic Cloud service **Standard** 以上版本才能使用的 Centralized Pipeline Management ，如個使用這個功能讓 Logstash Pipeline 的管理能集中化的在 Kibana 中被簡單的配置。

### 進入此章節的先備知識

* Logstash 的基本配置與使用方式。
* Logstash 的 Pipeline 配置方式。

### 此章節的重點學習

* 如何透過 Centralized Pipeline Management 來集中化管理 Logstash 的 Pipeline 配置。

***

## Centralized Pipeline Management 基本介紹

Centralized Pipeline Management 是 Elastic Stack 6.0 時以 Beta 版本首次推出的服務，主要的目的就是使用 Kibana 來當作集中化的管理工具、能管理多台 Logstash 身上的 Pipbline 配置，同時將 Elasticsearch 當作 Database ，存放這些 Pipeline 的配置。

### 功能定位

Centralized Pipeline Management 是為集中化的管理多台 Logstash，如果已經有使用其他更強大的 Configuration Management (CM) 工具像是 Puppet, Ansible, Chef 來解決這個問題，這功能可能就不見得需要使用，但如果還沒有使用到這些複雜的 CM 工具、但又有這個 Logstash 管理的需求時、又或者希望能由 Kibana 來提供需要修改 Pipeline 的操作者有統一的 GUI 管理工具，那這個 Centralized Pipeline Management 就會是你的選擇。

### Centralized Pipeline Management 基本運作架構圖

![centralized pipeline management](https://i.imgur.com/fQcvHcD.png)

## 將 Logstash 加入 Centralized Pipeline Management

首先我們要將 Logstash 註冊進入 Centralized Pipeline Management 的託管，我們會需要做幾件事：

#### 1. 先配置好 Logstash 所要使用的 Security User & Role

首先依照官方的建議配置，先建立兩個 Role - `logstash_writer` 和 `logstash_reader` 。

```
POST _xpack/security/role/logstash_writer
{
  "cluster": ["manage_index_templates", "monitor", "manage_ilm"], 
  "indices": [
    {
      "names": [ "logstash-*" ], 
      "privileges": ["write","create","delete","create_index","manage","manage_ilm"]  
    }
  ]
}
```

```
POST _xpack/security/role/logstash_reader
{
  "indices": [
    {
      "names": [ "logstash-*" ], 
      "privileges": ["read","view_index_metadata"]
    }
  ]
}
```

> `logstash_reader` 是給之後要能查閱我們透過 Logstash 傳進來的資料而先定義好的 Role。

建立一個 User ，並且給予 `logstash_admin`, `logstash_system` (這兩個是系統內建的 Role ) 和 `logstash_writer` 的 Role。

```
POST _xpack/security/user/logstash_user
{
  "password" : "t0p.s3cr3t",
  "roles" : ["logstash_admin","logstash_system","logstash_writer"],
  "full_name" : "Logstash User"
}
```

> 以上的操作都能直接在 Kibana > Stack Management > Security 中，使用 GUI 的畫面建立。

#### 2. 決定 Pipeline Id 及修改 Logstash 的設定

我們要在某一台已安裝好 Logstash 的機器上，將配置改成 `xpack.management.enable: true` 也就是將這台 Logstash 註冊透過 Centralized PIpeline Management 來託管。

```
# config/logstash.yml

xpack.management.enabled: true
xpack.management.logstash.poll_interval: 5s
xpack.management.pipeline.id: ["main", "apache_logs", "cloudwatch_logs"]

# 若是自架 Elastic Stack 要設置以下 Elasticsearch 的配置
xpack.management.elasticsearch.hosts: "http://localhost:9200/"
xpack.management.elasticsearch.username: logstash_user
xpack.management.elasticsearch.password: t0p.s3cr3t

# 若是使用 Elastic Cloud 可以直接指定 cloud_id 和 cloud_auth
xpack.management.elasticsearch.cloud_id: uncle-joe:xxxxxxxxxxxx
xpack.management.elasticsearch.cloud_auth: logstash_user:t0p.s3cr3t
```

> 這邊要注意，在 Config 中指派的 pipeline id，也就會是這個 logstash 機器會從 Centralized Pipeline Management 抓取的 Pipeline 配置，所以這部份是要事先定義好的。

另外以下是 X-pack Monitoring 的設定，也建議打開，或是使用 Metricbeat 來另外收集 Logstash Monitoring 的資訊。

```
xpack.monitoring.enabled: true
xpack.monitoring.collection.interval: 10s
xpack.monitoring.elasticsearch.cloud_id: uncle-joe:xxxxxxxxxxxx
xpack.monitoring.elasticsearch.cloud_auth: logstash_system:t0p.s3cr3t
```

#### 3. 註冊 Logstash 進入 Centralized Pipeline Management 中

當配置完成後，不需要做其他特別的執行，只要啟動 (或重新啟動) Logstash

```
bin/logstash
```

![run logstash](https://i.imgur.com/oBvAvka.png)

這時會發現出現找不到 remote config 的錯誤 (因為 Kibana 上的 Pipeline config 我們這時還沒建立好)。

#### 4. 確認 Kibana 使用者的權限

這邊要特別注意，當我們配置好之後，要重新回到 Kibana 來設定 Logstash Pipelines 時，要確保 Kibana 的使用者有以下的權限：

* logstash\_admin
* logstash\_writer

一但以上的步驟完成後，我們接下來就要進入 Kibana 設置 Logstash Pipeline。

## 使用 Kibana 管理 Logstash Pipelines

進入 **Kibana** > **Stack Management** > **Ingest** 中，有一個 **Logstash Pipeline** 的管理介面。

![Screen Shot 2020-10-03 at 10.22.52 PM](https://i.imgur.com/G5o3agQ.png)

點選 **Create Pipeline** 之後，就可以依照一般使用 Pipeline 的配置方式來設定 Pipeline。

![create pipeline](https://i.imgur.com/avCu4u0.png)

最後按下 **Create and deploy** 就會立刻發佈到 Logstash 並生效。

## 透過 Kibana Stack Monitoring 確認 Logstash Pipelines 運作狀況

從 **Kibana** > **Stack Monitoring** > **Logstash** 中，可以看到目前 Logstash 的機器狀況，也可以即時監看 Pipeline 的運作狀態。

![stack monitoring](https://i.imgur.com/HnJj6iz.png)

![stack monitoring pipelines](https://i.imgur.com/WrLJojT.png)

若是有多台機器、多組 Pipeline 的配置，都能透過 Stack Monitoring 即時監看運作狀態，在更新 Pipeline 的配置後，請記得來這邊確認配置的結果是否運作正常。

## 使用建議與注意事項

* Centralized Pipeline Management 的目的是讓 Logstash Pipeline 可以讓多租戶的使用者們自行配置、自主管理，而這邊會配合的用方是 Logstash multiple pipeline 的配置方式，也就可以讓我們 **以 Pipieline 為單位** 來定義各種不同情境的需求，也就是每個租戶、或是每個 data-flow 可以配置屬於自己的 Pipeline。
* Centralized Pipeline Management 所建立的 Pipeline 資料是真接儲存在 Elasticsearch 中，所有的調整與變動都會直接套用到 Logstash，不需要重新開啟動 Logstash，而這些 Pipeline 的配置沒有任何的驗證錯誤的機制，也就是一但修改後就會直接生效，若是配置有錯誤的話，也就會直接反應到實際的環境中，這部份會需要特別小心。
* Logstash 在這樣集中化管理的情況，一但發生錯誤，也會需要看每台 Logstash 的 logs 才能知道狀況，因此也同樣會建議啟用 X-Pack monitoring 的功能，讓 Logstash 的 logs 被收集到 Elasticsearch 來一併監控與管理。
* 為了能控制發生錯誤時的影響，建議配置好 Logstash 的 [Dead Letter Queue](https://www.elastic.co/guide/en/logstash/current/dead-letter-queues.html) ，當有錯誤發生時，也能再次透過 Pipeline 的調整，以重新修復資料。

## 參考資料

* [官方文件 - Centralized Pipeline Management](https://www.elastic.co/guide/en/logstash/current/logstash-centralized-pipeline-management.html)
* [官方文件 - Configuring Centralized Pipeline Management](https://www.elastic.co/guide/en/logstash/current/configuring-centralized-pipelines.html)
* [官方文件 - Dead Letter Queue](https://www.elastic.co/guide/en/logstash/current/dead-letter-queues.html)
* [官方 Blog - Logstash Centralized Pipeline Management](https://www.elastic.co/blog/logstash-centralized-pipeline-management)


# Watcher

## 前言

Elastic Stack 主打的 Observability ，含蓋的範圍包括各種 Logs, Metrics, APM, Uptime 等資訊，這些資訊的收集對於掌握系統運作狀態非常有幫助，這些資訊非常的多，要靠人員主動觀察問題會有些不太實際，因此當有某些異常的條件發生時，能主動通知營運人員的功能也就很重要了，在 Elastic Stack 中，X-pack 的 **Watcher** 就是負責這項任務，而這個功能同樣也是要 **Gold License** 以上、或是 Elastic Cloud service **Standard** 以上版本才有授權使用。

### 進入此章節的先備知識

* Elasticsearch Query DSL。

### 此章節的重點學習

* Watcher 的基本介紹與使用觀念。
* 如何使用 Kibana 設定 Watcher。

***

## Watcher 的基本介紹

Watcher 主要是執行許多事先定義好的 Watch，並且在某個 Watch 滿足條件時，會執行預先所指定的 action 的一個機制。

### Watch 的組成

Watch 的設定當中主要包含下面幾個部份：

* **Trigger:** 決定 Watch 被檢查的時機點，例如：每 5 分鐘、或是使用 Cron schedule 來指定執行的時間。
* **Input:** 決定要將什麼東西放入到 Watch 的 Payload 中當成 Input，可包含四種：
  * `simple`: 固定的值。
  * `search`: 依照 Query DSL 從 Elasticsearch 查詢出來的結果。
  * `http`: 使用某個 http 請求的 response。
  * `chain`: 可將上述三種用法依照不同的順序來組合搭配使用。
* **Condition:** 決定 Watch 是否有達到要執行的條件，有以下幾種用法：
  * `always`: 總是執行。
  * `never`: 總是不執行。
  * `compare`: 能參照 Watch payload 的資料加上判斷的比較條件來決定是否執行。
  * `array_compare`: 針對 Watch payload 中 array 類型的資料來進行比較，並決定是否要執行。
  * `script`: 能使用 Painless script 來自己撰寫比較的邏輯。
* **Transform:** 當 Watch 決定要執行時，能透過另外的方式產生不同的 Watch Payload，讓之後的 Actions 能使用。(有時 Condition 的條件與 Action 要使用的資料會不同，這時就能透過 Transform 來準備 Action 要使用的資料)
* **Actions:** 最後要執行的動作是什麼，可以設定多個 actions，目前有支援的是：`email`, `webhook`, `index`, `logging`, `slack`, `pagerduty`, `Jira` ，目前在 actions 的設定時也能指定節流 (throttle) 的機制，也就是同樣的事件，在多久時間內不要重覆通知。
* **Metadata:** 這是開放自由存放 metadata 的欄位。

### Watch 的執行方式

Watch 在執行時，會先依照 Trigger 的時間定義被呼叫起來，並且依照 Input 的定義將 Watch Payload 準備好，接下來會檢查 Condition 的條件是否滿足，如果滿足的話，再確認是否需要 Throttle ，若需要正常執行的話，就會執行 Transform Payload 並且 執行定義的 Actions。

因為 Watcher 支援 [Acknowledgement Watch](https://www.elastic.co/guide/en/elasticsearch/reference/current/actions.html#actions-ack-throttle) 的機制，也就是另外透過 ack watch API 來標示這個 Watch 不要再執要 Action了，因在 `condition met? == no` 的時候，也因為 condition 改變了，所以會去檢查並清除 acked 的狀態，讓這個 Watch 的狀態重設。

![watch execution](https://i.imgur.com/wbMiTQ7.jpg)

其中 Throttle 的運作方式如下，會檢查是否有 `acked` 或是是否達到 `throttle_period` ，再決定是否要繼續接下來的 Actions。

![action throttling](https://i.imgur.com/FQwoOJU.jpg)

### 指定 Watcher Node

如果我們想讓 watcher 只能運作在 Cluster 中的某些 node 身上的話，我們可以用以下的方式來設定。

如同 hot-warm architecture 中的使用方式，我們可以在特定的 node 的 `elasticsearch.yml` 設定 node attribute，例如：

```
node.attr.role: watcher
```

這時我們要去 `.watches` 的設定進行宣告：

```
PUT .watches/_settings
{
  "index.routing.allocation.include.role": "watcher"
}
```

這樣透過限制 `.watches` 這個 index 的資料 routing 方式，限制只有我們安排的 node 會得到這份資料，也就因此才會去執行 Watcher 的任務。

## 使用 Kibana 設定 Watcher

### 建立新 Watcher

進入 **Kibana** > **Stack Management** > **Alerts and Insights** 中，找到 **Watcher**：

![kibana watcher](https://i.imgur.com/0O50tfU.png)

在 Create 時，可以有兩種方式，一個是直接用 UI 建立，或是直接使用預先建立好的 Json。

![watcher-create](https://i.imgur.com/cAk8F08.png)

在 UI 的設定介面中，有些基本的設定方式可以直接使用，不過一些進階的用法並沒有完全支援，進階的用法還是要透過 JSON 的方式來定義，並且請參考 [官方文件](https://www.elastic.co/guide/en/elasticsearch/reference/current/xpack-alerting.html) 。

![create threshold alert](https://i.imgur.com/NHgba1z.png)

### Elastic Cloud 設定特定 Action 的配置

這邊要注意，如果要使用到 `Slack`, `Jira` 這類型的 Action 時，會需要先在 Elastic Cloud 中定義好這些的帳號設定。

1. 在 Keystore 要先設定好 slack 的 `secure_url` ，請參考 [官方文件](https://www.elastic.co/guide/en/elasticsearch/reference/current/actions-slack.html#configuring-slack)。

![elastic cloud keystore](https://i.imgur.com/1oCpYsM.png)

1. 到 Deployement > Elasticsearch 中，去修改 config。

![elastic cloud slack setting](https://i.imgur.com/BRletRD.png)

這些配置設定完成後，才能在配置中使用 Slack。

### Watcher 執行狀態

我們在 Kibana Watcher 的畫面，可以看到所有定義好的 Watch，也能看到他們最近的執行狀況。

![Screen Shot 2020-10-04 at 4.53.43 PM](https://i.imgur.com/6nuOhPP.png)

也可以進入看每次 Trigger 時間點的執行結果，若是有發生錯誤，也可以進去看到錯誤的原因。

![image-20201004165525240](https://i.imgur.com/OhVgTf5.png)

以上是使用 Kibana 操作 Watcher 的基本使用介紹，建議大家可以從下方的 **參考資料** 進入查閱官方文件的詳細介紹，官方也有一些 [Watcher 的 Examples](https://www.elastic.co/guide/en/elasticsearch/reference/current/example-watches.html) 可以參考，對於了解如何使用 Watcher 會有不少幫助。

## 參考資料

* [官方文件 - How Watcher works](https://www.elastic.co/guide/en/elasticsearch/reference/current/how-watcher-works.html)
* [官方文件 - Watcher](https://www.elastic.co/guide/en/kibana/current/watcher-ui.html)


# Elasticsearch Token Service

## 前言

Elasticsearch 的 X-Pack Security 機制中，Token Based Authentication services 包含了兩種方式， `token-service` 與 `api-key-service` ，而 `api-key-service` 是 Basic License 就能使用的服務，不過 `token-service` 是必需要 Gold License 或是 Elastic Cloud service Standard 以上的授權才能使用的功能，這篇文章主要會介紹 `token-service` 的用法。

### 進入此章節的先備知識

* OAuth2 的運作方式。(可參考這篇：[簡單易懂的 OAuth 2.0](https://speakerdeck.com/chitsaou/jian-dan-yi-dong-de-oauth-2-dot-0))

### 此章節的重點學習

* 如何使用 Elasticsearch Token Service，也就是 Get token API。

***

## Elasticsearch Token Service

進到這篇文章時，已經假設讀者了解 OAuth2 的運作方式、也知道 OAuth2 的好處，而 Elastic Stack 在 X-Pack Security 的套件中，是以 `token-service` 的機制來實作 OAuth2 的 token 核發與更新機制。

### 基本介紹

在 Elastic Stack 中 `token-service` 的運作，主要就是以 [get token API](https://www.elastic.co/guide/en/elasticsearch/reference/master/security-api-get-token.html) 來產生 `access token` 和 `refresh token` ，這個 `access token` 是個短時效性的 token，預設是 20分鐘 過期，最長可以設到 1小時 過期，而核發 `access token` 時會同時提供一個 `refresh token` ，這個 `refresh token` 是當 `access token` 過期時，換發新的 `access token` 用的，預設是 24小時 有效。

### 啟用 Token Service

Elasticsearch 的架設上，如果有開啟 TLS (HTTPS) 的話，預設就會啟用 `token-service` ，或是可以手動在 `elasticsearch.yml` 的 config 中開啟：

```
xpack.security.authc.token.enabled: true
```

> 這邊要注意， 因為安全性的考量， Elasticsearch 在 production 環境提供的 bootstrap 啟動檢查時，會特別檢查如果有開啟 `token-service` 的話，必須要啟用 TLS ，否則 token 在不安全的傳輸過程中，是非常危險的。

如先前提到的，這個 `token-service` 必須是

* **Gold License** Subscription
* Elastic Cloud service **Standard** version

這兩種或是以上的授權等級，才能使用這項服務，如果是自架的 **Basic License** ，在使用 get token API 時，會得到以下的錯誤訊息：

![self-managed elasticsearch - basic license](https://i.imgur.com/byuZwuO.png)

### Get Token API

get token API 提供四種的 `grant_type`，分別的使用情境如下：

* `client_credentials`: 這是實作了 **OAuth2** 中的 **Client Credentials Grant** 的機制，主要是用在 machine to machine 的使用情境中，並不是提供給一般使用者操作的情境使用的，而簽發這個 token 時，會依照本來的 credential 的權限，產生一組 token 並付予完全一樣的權限，而這個機制只會提供 `access token` 不會提供 `refresh token`。
* `_kerberos`: 這是實作 **SPNEGO Kerberos** 機制，要使用的話，需要在 Elasticsearch 中設定好 [Kerberos realm 的配置](https://www.elastic.co/guide/en/elasticsearch/reference/current/kerberos-realm.html)。
* `password`: 這是實作 **Oauth2** 中的 **Resource Owner Password Credentials Grant** 的機制，這個機制是用在某一個已授權的用戶代表另一個授權的用戶來產生 `access token`，白話一點的說法就是，你要 call 這個 get token API 必須是已授權的某個使用者，但你帶入的 `username` 和 `password` 會是另一個人的帳號，並且是為了這個帳號來產生適合他權限的 `access token`，這個機制產生的 `access token` 會同時包含 `refresh token`，適合長時間的使用。
* `refresh_token`: 這是使用 `refresh_token` 來取的新的 `access token` 及 `refresh_token` 的時候所使用的。

以下有幾個例子：

#### Grant Type: `client_credentials`

這時因為在 call 這個 get token API 時，本身就會是以登入的身份 (或是帶某個 token 在 header 中)，所以只要帶這個 `grant_type` ，就會直接以當下使用者的身份權限，來產生出 `access token`。

```
POST /_security/oauth2/token
{
  "grant_type" : "client_credentials"
}
```

以下是回傳的結果，包含了 `access_token` 以及過期的時間，這種 `grant_type` 是不會有 `refresh_token` 的。

```
{
  "access_token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==",
  "type" : "Bearer",
  "expires_in" : 1200
}
```

取得這個 `access token` 後，要使用的時候，在 HTTP Authroization 中帶入這個 `Bearer` 的 `token` 即可：

```
curl -H "Authorization: Bearer dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==" http://localhost:9200/_cluster/health
```

#### Grant Type: `password`

下面是使用 `password` grant type 的例子，需帶入 `username` 與 `pasword`：

```
POST /_security/oauth2/token
{
  "grant_type" : "password",
  "username" : "test_admin",
  "password" : "x-pack-test-password"
}
```

回傳的結果如下：

```
{
  "access_token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==",
  "type" : "Bearer",
  "expires_in" : 1200,
  "refresh_token": "vLBPvmAB6KvwvJZr27cS"
}
```

可以看到這種 grant type 包含了 `refresh_token`，這個 `refresh_token` 的有效時間是 24小時，在這時限內，若 `access token` 過期，都能使用 `refresh_token` 來取得新的 `access_token`。

#### Grant Type: `refresh_token`

當 `access token` 過期時，若還在 `refresh_token` 有效的 24小時 之內，可以直接使用這種方式來取得新的 `access token` 和 `refresh token`。

```
POST /_security/oauth2/token
{
  "grant_type": "refresh_token",
  "refresh_token": "vLBPvmAB6KvwvJZr27cS"
}
```

以下是回傳結果：

```
{
  "access_token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==",
  "type" : "Bearer",
  "expires_in" : 1200,
  "refresh_token": "vLBPvmAB6KvwvJZr27cS"
}
```

### Token Service Settings

上面的例子的 `expires_in` 都是 `1200` ，如果我們要改變 `access token` 的有效時間，可以到 `elasticsearch.yml` 來改變這個時間的設定：

```
xpack.security.authc.token.timeout: 20m
```

預設是 `20m` ，最大可以設定的值是 1小時。

### Invalidate Token API

基本上 `access_token` 和 `refresh_token` 都有各自的過期時限，過期之後就無法再繼續使用。

如果要立即讓某個 token 失效、或某個使用者所有的 token 都失效時，就可以使用 `invalidate Token API`。

#### 讓某個 `access token` 立刻失效

```
DELETE /_security/oauth2/token
{
  "token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ=="
}
```

#### 讓某個 `refresh token` 立刻失效

```
DELETE /_security/oauth2/token
{
  "refresh_token" : "vLBPvmAB6KvwvJZr27cS"
}
```

#### 讓某個使用者的 tokens 立刻失效

```
DELETE /_security/oauth2/token
{
  "username" : "myuser"
}
```

依照官方的範例，回傳結果如下，會告訴我們總共 invalidate 多少個 tokens，以及若遇到錯誤各自的原因為何：

```
{
  "invalidated_tokens":9, 
  "previously_invalidated_tokens":15, 
  "error_count":2, 
  "error_details":[ 
    {
      "type":"exception",
      "reason":"Elasticsearch exception [type=exception, reason=foo]",
      "caused_by":{
        "type":"exception",
        "reason":"Elasticsearch exception [type=illegal_argument_exception, reason=bar]"
      }
    },
    {
      "type":"exception",
      "reason":"Elasticsearch exception [type=exception, reason=boo]",
      "caused_by":{
        "type":"exception",
        "reason":"Elasticsearch exception [type=illegal_argument_exception, reason=far]"
      }
    }
  ]
}
```

> \*\*小插曲：\*\*我在 Elastic Cloud service 試用這個 invlidate by username 時，發生以下的錯誤，看來是踩到某個 bug 了，看到 Github 上好像已經有類似的 issues，相信在不久後的 release 應該就會修掉了吧。
>
> <img src="https://i.imgur.com/rj3dDzq.png" alt="image-20201005054248853" data-size="original">

## 結語

以上是 Elasticsearch Token Service 的介紹，在 Micro-services 的架構之下，若是將 Elasticsearch 當成其中一個微服務來使用時，透過 Token based 的方式來整合與使用其資源，是個蠻好的資源管理方式，這個機制只要在 Elastic Cloud 中就能直接使用，若是 Self-managed 的版本，會需要購買 **Gold License** 才能使用，或是要走向其他 [Open Source 的 solution](https://opendistro.github.io/for-elasticsearch-docs/docs/security/)了。

## 參考資料

* [官方文件 - Token-based authentcation services](https://www.elastic.co/guide/en/elasticsearch/reference/master/token-authentication-services.html)
* [官方文件 - Get token API](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-token.html)
* [官方文件 - Invalidate token API](https://www.elastic.co/guide/en/elasticsearch/reference/master/security-api-invalidate-token.html)
* [官方文件 - Security Settings - Token service settings](https://www.elastic.co/guide/en/elasticsearch/reference/master/security-settings.html#token-service-settings)


# Multi-stack monitoring & Automatic stack issue alerts

## 前言

這篇是 **Elastic Cloud 比免費版還多的功能** 系列的最後一篇，主要是針對 **Stack Monitoring** 中 **Gold License** 才能使用、但已經在 Elastic Cloud **Standard** 版本中開放使用的兩個功能 `Multi-stack monitoring` 和 `Automatic stack issue alerts` 。

### 進入此章節的先備知識

* Elasticsearch 的基本認識
* Kibana 的基本操作

### 此章節的重點學習

* Multi-stack monitoring 的功能介紹。
* Automatic stack issue alerts 的功能介紹。

***

## Multi-stack monitoring

這個功能主要的目的是讓你透過一個 **集中化的監控叢集 (centrlized monitoring cluster)** ，來記錄、追縱、比對來自多個 Elastic Stack deployment 的健康或是效能的狀態，白話就是：把多個 cluster + elastic stack 產品的 log 送到專們 monitoring 用的 cluster 來監管。

> 這樣 Centralized monitoring cluster 也是 Elastic 官方建議的做法，做好角色分工，也避免讓處理其他工作的 Elasticsearch Cluster 若發生異常時，不會影響到 Monitoring 的監控狀態。

以下是在 Elastic Cloud 的 Elasticsearch 設定時，會看到 Monitoring deployment 的設定選項，其中也明確的建議 Production 環境要記得將 Montoring 的任務安排給一組特定的 Deployment 來管理。

![elastic cloud monitoring deployment](https://i.imgur.com/Q7xuQ6n.png)

若是 Monitoring Culster 同時管理多個 Elasticsearch Culster 時，進入 **Kibana** > **Stack Monitoring** 會出現 Clusters 的選單，此時也有所有 Clusters 的 Overview。

![stack monitoring - multi culster](https://i.imgur.com/NY5SXpz.png)

進入某個 Cluster 後，可以查看這組 Deployement 的 Elastic Stack monitor。

![stack monitoring - specific cluster](https://i.imgur.com/hO3c9xO.png)

> Elastic Stack 中，Metricbeat 是被 Elastic 官方推薦用來收集 Monitoring data 的工具，詳細如何使用，請參考 [官方文件 - Collecting monitoring data with metricbeat](https://www.elastic.co/guide/en/elasticsearch/reference/current/configuring-metricbeat.html) 。

## Automatic stack issue alerts

Elastic Stack 中除了先前介紹的 Watcher 之外，Elastic Stack 7.7 時推出全新的 Alerting Framework，將 6.x 版時陸續為了先舖路而推出的各個功能整合起來，完成 Alerting 的大業。詳細的介紹可以從 [官方 Blog - Introducing the new alerting framework for Elastic Observability, Elastic Security, and the Elastic Stack](https://www.elastic.co/blog/introducing-the-new-alerting-framework-for-observability-security-and-the-elastic-stack) 來查閱。

> Watcher 主要是以 Elasticsearch 來當成執行的 Instance，而 Alerting 是以 Kibana 來當執行的 Instance，兩者層級不太一樣，詳細差別官方文件有特別一個章節在描述他們的差異，可以參考 [這邊](https://www.elastic.co/guide/en/kibana/current/alerting-getting-started.html#alerting-concepts-differences)。

在 Elastic Stack 中，這個 Automatic stack issue alerts 有預先針對 Elastic Stack 定義好一些通知的 alerts 機制，我們進入 setup mode 去修改相關的設定。

![stack monitoring edit mode](https://i.imgur.com/QNtlstv.png)

在不同的 Elastic Stack 中，點選 `alerts` 之後，可看到會有不同的 Alerts 的定義。

![Screen Shot 2020-10-05 at 11.55.59 PM](https://i.imgur.com/dMfOTNz.png)

每個 Alerts 都可以進入編輯，並修改一些觸發條件、或是執行的動作。

![image-20201005235701713](https://i.imgur.com/WMwTTBg.png)

除此之外，Alerting Framework 目前強調的是可以在 Kibana 的任何地方都能輕鬆的建立合適的 Alert，也包含在 Machine Learning 時也能用同樣的機制來設定 alert 的 actions，這個 Alerting Framework 還在持續的發展中，期待接下來的版本有更多的功能推出，這次 Alerting Framework 不在此章節的介紹範圍內，有興趣的朋友可以參考下方的參考資料查閱更多的細節。

## 參考資料

* [官方文件 - Monitoring in a production environment](https://www.elastic.co/guide/en/elasticsearch/reference/current/monitoring-production.html)
* [官方 Blog - Alerting in the Elastic Stack](https://www.elastic.co/blog/alerting-in-the-elastic-stack)
* [官方 Blog - Introducing the new alerting framework for Elastic Observability, Elastic Security, and the Elastic Stack](https://www.elastic.co/blog/introducing-the-new-alerting-framework-for-observability-security-and-the-elastic-stack)


# 向 App Search 學習怎麼用 Elasticsearch

App Search 是使用 Elasticsearch 做成的產品，這個產品的目的是幫你配置好一般搜尋功能需求的基本最佳方案，讓 一般網站 或 App 能直接簡單的就拿來使用，想知道 Elasticsearch 可以怎麼被使用，當然就是從剖析 App Search 怎麼使用 Elasticsearch 來學習。

* [(1/5) - 揭開 App Search 的面紗](/tech-sharing/uncle-joe-teach-es-elasticsearch/xiang-app-search-xue-xi-zen-mo-yong-elasticsearch/jie-kai-app-search-de-mian-sha)
* [(2/5) - Engine 的 Index Settings 篇](/tech-sharing/uncle-joe-teach-es-elasticsearch/xiang-app-search-xue-xi-zen-mo-yong-elasticsearch/engine-de-index-settings-pian)
* [(3/5) - Engine 的 Mapping 篇](/tech-sharing/uncle-joe-teach-es-elasticsearch/xiang-app-search-xue-xi-zen-mo-yong-elasticsearch/engine-de-mapping-pian)
* [(4/5) - Engine 的 Search 基礎剖析篇](/tech-sharing/uncle-joe-teach-es-elasticsearch/xiang-app-search-xue-xi-zen-mo-yong-elasticsearch/engine-de-search-ji-chu-pou-xi-pian)
* [(5/5) - Engine 的 Search 進階剖析篇](/tech-sharing/uncle-joe-teach-es-elasticsearch/xiang-app-search-xue-xi-zen-mo-yong-elasticsearch/engine-de-search-jin-jie-pou-xi-pian)


# 揭開 App Search 的面紗

## 前言

這個系列我們針對 Elastic Stack 產品系列 Enterprise Search 中的 App Search 來進行剖析，看看 App Search 是如何使用 Elasticsearch ，從這個探索的過程中，讓我們期待從中學習一些 App Search 值得我們參考的使用方式。

### 進入此章節的先備知識

* Elasticsearch 的基礎使用知識
* App Search 的基礎使用知識

### 此章節的重點學習

* 初探 App Search 是如何把 Elasticsearch 當成 NoSQL Database 來使用。

***

## App Search 簡介

我這篇不會針對 App Search 有太多細節介紹，剛好這次鐵人賽我參加的團隊隊長 - **少女人妻** ，他這次的主題 [少女人妻的30天Elastic](https://ithelp.ithome.com.tw/users/20116811/ironman/3147) 就有蠻詳細的 App Search 介紹，所以對於 App Search 還不太熟悉的朋，推薦大家去拜讀她的文章。

![img](https://i.imgur.com/Z9gLqGq.png)

(圖片來源：[Solutions: Elastic App Search 入門](https://blog.csdn.net/UbuntuTouch/article/details/105392284))

簡單來說，App Search 是提供了一個 Out of Box Experience (OOBE) 使用 Elasticsearch 搜尋服務的產品，讓使用端以 API 的方式將文件透過 App Search 存入，並且提供 API 能將文件搜尋出來，你不需要知道怎麼使用 Elasticsearch、如何下 Query、如何優化 Query 的方式，這些 App Search 都提供了一套預設配置，讓你基本上可以達到一定好用的程度。

而 App Search 底層不僅是使用 Elasticsearch 當作 Document 的 Index engine，同時也把 Elasticsearch 當作 NoSQL database 來存放 App Search 這個應用程式所需要儲存的資料或是設定值。

## 揭開 App Search 的面紗

### App Search System Indices

當我們使用 Elastic Cloud 並啟用 Enterprise Search 、或是自己架設 App Search 並將他指向某一個 Elasticsearch Cluster 時，我們從 Elasticsearch 的 Index 中，可以發現有許多 `.ent-search` 開頭的 Indices，如下圖：

![app search indexes](https://i.imgur.com/7QK4kd2.png)

> 這圖是使用 ElasticSearch Head chrome plugin 的截圖，因為他的排版較適合列出大量的 index name

這些 Index 許多可以從名字猜出用途，另外這邊列幾個比較重要的：

* 包含 `*-app_search_*` 字眼的：這些用途蠻明的，包含 app search settings, accounts, api\_tokens...等，就是存放 App Search 一些 application data 用的。
* `.ent-search-actastic-engine_document_backends_v2`: 這個是存放了 backend level 會用到的 engines 的 meta 資料，有幾個 engines 就有幾筆 document，只有簡單的 engine 建立時間、engine id、是否可讀寫的狀態。
* `.ent.search-actastic-engines_v9`: 這裡存放的是定義每個 engines 的詳細資料。
* `.ent-search-engine-*`: 這個會是每個 engines 產生一個 index，裡面存放的就會是這個 engine 裡面的所有 document。

### 建立 Engine

一開始我們 `.ent-search-actastic-engines_v9` index 會是空的，所以我們透過 App Search UI 來建立一個 Engine。

![image-20201007032436502](https://i.imgur.com/Wsl00pv.png)

建立完成後，我們查詢 `.ent-search-actastic-engines_v9`

```
GET .ent-search-actastic-engines_v9/_search
```

看看裡面有什麼：

```
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : ".ent-search-actastic-engines_v9",
        "_type" : "_doc",
        "_id" : "5f7cc70189dec99f64b0dd35",
        "_score" : 1.0,
        "_source" : {
          "id" : "5f7cc70189dec99f64b0dd35",
          "created_at" : "2020-10-06T19:35:29Z",
          "updated_at" : "2020-10-06T19:42:15Z",
          "type_" : "Engine::IndexedEngine",
          "account_id" : null,
          "cluster_id" : "5f5c740c481a32491fcccccd",
          "key" : "JM-N2oBNNbieV2QXRrh-",
          "loco_moco_account_id" : "5f5c740d481a32491fccccd0",
          "managing_application_id" : null,
          "slug" : "joe-test",
          "name" : "joe-test",
          "api_based" : true,
          "last_touched_at" : null,
          "stub" : false,
          "demo" : false,
          "sample" : false,
          "meta_data" : { },
          "source" : null,
          "queued_for_deletion" : null,
          "frito_pie_content_source_id" : null,
          "page_limit" : null,
          "document_count" : 1,
          "moving" : false,
          "deaggregation_requested" : false,
          "index_settings_override" : { },
          "index_create_settings_override" : { },
          "query_boosted_documents_enabled" : false,
          "query_boosted_queries_enabled_override" : false,
          "language" : "zh",
          "source_engine_ids" : [ ]
        }
      }
    ]
  }
}
```

這個就是我們的 Engine 的資料，包含了 engine 名稱 `joe-test` 、我選擇的語系 `zh` …等資訊，另外 `5f7cc70189dec99f64b0dd35` 這個是我們的 Engine Id，這個要記起來，之後會用到。

### Import document

光是建立 Engine，文件沒有放進去的話，還不會產生對應的 Index ，所以我們再次透過 App Search UI 來新增一份文件。

以下是我從預設的範例精簡後的版本：

```
[
  {
    "id": "park_rocky-mountain",
    "title": "Rocky Mountain",
    "description": "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra. Wildlife including mule deer, bighorn sheep, black bears, and cougars inhabit its igneous mountains and glacial valleys. Longs Peak, a classic Colorado fourteener, and the scenic Bear Lake are popular destinations, as well as the historic Trail Ridge Road, which reaches an elevation of more than 12,000 feet (3,700 m).",
    "visitors": 4517585,
    "location": "40.4,-105.58",
    "date": "1915-01-26T06:00:00Z"
  }
]
```

透過 UI Import 進 App Search 後，會看到 5 個欄位被加入說 Engine's schema 了。

![image-20201007034222461](https://i.imgur.com/GQJo1XO.png)

這時我們去查看 Index

```
GET _cat/indices?v&index=*engine*
```

會發現多了一個 `.ent-search-engine-5f7cc70189dec99f64b0dd35` 的 Index ，也就是我們這個 Engine 的 Index ，並且 `docs.count` 顯示裡面有一份文件。

![image-20201007034849153](https://i.imgur.com/CLY8ZtD.png)

### Query Indexed Document

我們先簡單的查看這個裡面的 Document 長什麼樣子：

```
GET .ent-search-engine-5f7cc70189dec99f64b0dd35/_search
```

以下是回傳的結果：

```
{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : ".ent-search-engine-5f7cc70189dec99f64b0dd35",
        "_type" : "_doc",
        "_id" : "5f7cc89489dec9fc34b0dd38",
        "_score" : 1.0,
        "_source" : {
          "title$string" : "Rocky Mountain",
          "description$string" : "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra. Wildlife including mule deer, bighorn sheep, black bears, and cougars inhabit its igneous mountains and glacial valleys. Longs Peak, a classic Colorado fourteener, and the scenic Bear Lake are popular destinations, as well as the historic Trail Ridge Road, which reaches an elevation of more than 12,000 feet (3,700 m).",
          "visitors$string" : "4517585",
          "location$string" : "40.4,-105.58",
          "date$string" : "1915-01-26T06:00:00Z",
          "id" : "5f7cc89489dec9fc34b0dd38",
          "external_id" : "park_rocky-mountain",
          "engine_id" : "5f7cc70189dec99f64b0dd35",
          "__st_text_summary" : "Rocky Mountain Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra. Wildlife including mule deer, bighorn sheep, black bears, and cougars inhabit its igneous mountains and glacial valleys. Longs Peak, a classic Colorado fourteener, and the scenic Bear Lake are popular destinations, as well as the historic Trail Ridge Road, which reaches an elevation of more than 12,000 feet (3,700 m). 4517585 40.4,-105.58 1915-01-26T06:00:00Z",
          "__st_expires_after" : null
        }
      }
    ]
  }
}
```

我們可以看到，這個 document 有幾部份：

* `id`, `engine_id`: 這邊都是記錄 engine id。
* `__st_text_summary`: 這個欄位把所有值都合併在一起並使用 text 型態來描述。
* 每個我們定義的欄位名稱，後面都被帶上了他的型態 `$string`。

這就是 App Search 使用 Elasticsearch 存放的 Document 外觀的長相。

### Update Engine Schema

![image-20201007040635761](https://i.imgur.com/hIxnlxc.png)

若是我們更新的欄位的型態，再來看看 Document 的變化：

```
{
  "took" : 256,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : ".ent-search-engine-5f7cc70189dec99f64b0dd35",
        "_type" : "_doc",
        "_id" : "5f7cc89489dec9fc34b0dd38",
        "_score" : 1.0,
        "_source" : {
          "title$string" : "Rocky Mountain",
          "description$string" : "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra. Wildlife including mule deer, bighorn sheep, black bears, and cougars inhabit its igneous mountains and glacial valleys. Longs Peak, a classic Colorado fourteener, and the scenic Bear Lake are popular destinations, as well as the historic Trail Ridge Road, which reaches an elevation of more than 12,000 feet (3,700 m).",
          "visitors$float" : 4517585.0,
          "location$location" : "40.4,-105.58",
          "date$date" : "1915-01-26T06:00:00+00:00",
          "id" : "5f7cc89489dec9fc34b0dd38",
          "external_id" : "park_rocky-mountain",
          "engine_id" : "5f7cc70189dec99f64b0dd35",
          "__st_text_summary" : "Rocky Mountain Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra. Wildlife including mule deer, bighorn sheep, black bears, and cougars inhabit its igneous mountains and glacial valleys. Longs Peak, a classic Colorado fourteener, and the scenic Bear Lake are popular destinations, as well as the historic Trail Ridge Road, which reaches an elevation of more than 12,000 feet (3,700 m). 4517585 40.4,-105.58 1915-01-26T06:00:00Z",
          "__st_expires_after" : null
        }
      }
    ]
  }
}
```

一但更新 Schema，這些 Document 都會被 re-indexing，並且變成新的 Document，而且改變型態的欄位名稱也都跟著改變了：

* `visitors$string` -> `visitors$float`
* `location$string` -> `location$location`
* `date$string` -> `date$date`

## 小結

到目前為止，我們了解了 App Search 在建立 Engine 時，是如何使用 Elasticsearch 存放相關的資料，以及 Import 一份文件進入 App Search 他是怎麼存放的，後續的文章將會進一步探索 Index Setting, Mapping...等深入的剖析。

## 參考資料

* [Solutions: Elastic App Search 入門](https://blog.csdn.net/UbuntuTouch/article/details/105392284)


# Engine 的 Index Settings 篇

## 前言

在前一篇介紹中，我們說明了 App Search 是如何使用 Elasticsearch 當成 NoSQL Database 來儲存 App Search 應用程式端的資料，以及我們建立了 Engine 之後，相關的 Index 又是如何被建立出來，這篇文章將會針對每個 App Search Engine 的 Index Settings 進行深入的介紹。

### 進入此章節的先備知識

* Elasticsearch Index Setting 的設定方式。
* Elasticsearch Analysis - Analyzer, Tokenizer, Token Filter 的基本知識。

### 此章節的重點學習

* 剖析 App Search Engine 的 Index Settings。
* 針對 App Search 使用的 Analysis - Analyzer, Tokenizer, Token Filter 進行剖析。

***

## 取得 App Search Engine 的 Index Settings

從上一篇文章的介紹，我們知道要取得 App Searc Engine 的方式如下：

從 `.ent-search-actastic-engines_v9` 找到 Engine 的 id。

```
GET .ent-search-actastic-engines_v9/_search
```

針對 `_source` > `name` 找到指定 Engine 名稱的 Document ，並取得他的 `id` 欄位。

```
{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : ".ent-search-actastic-engines_v9",
        "_type" : "_doc",
        "_id" : "5f7deeaeac761042130bf192",
        "_score" : 1.0,
        "_source" : {
          "id" : "5f7deeaeac761042130bf192",
          "created_at" : "2020-10-07T16:37:02Z",
          "updated_at" : "2020-10-07T16:37:24Z",
          "type_" : "Engine::IndexedEngine",
          "account_id" : null,
          "cluster_id" : "5f7de9caac7610ac216468a8",
          "key" : "ykZuaxzHjycZx6WnDdag",
          "loco_moco_account_id" : "5f7de9caac7610ac216468ab",
          "managing_application_id" : null,
          "slug" : "joe-test",
          "name" : "joe-test",
          "api_based" : true,
          "last_touched_at" : null,
          "stub" : false,
          "demo" : false,
          "sample" : false,
          "meta_data" : { },
          "source" : null,
          "queued_for_deletion" : null,
          "frito_pie_content_source_id" : null,
          "page_limit" : null,
          "document_count" : 1,
          "moving" : false,
          "deaggregation_requested" : false,
          "index_settings_override" : { },
          "index_create_settings_override" : { },
          "query_boosted_documents_enabled" : false,
          "query_boosted_queries_enabled_override" : false,
          "language" : "zh",
          "source_engine_ids" : [ ]
        }
      }
    ]
  }
}
```

以 `joe-test` 為例，Engine id 為 `5f7deeaeac761042130bf192` 。

接下來可以從這個 Engine id 組出這個 Engine 的 Index name： `.ent-search-engine-5f7deeaeac761042130bf192`

我們透過 GET Index Setting API 即可取得這個 Engine 的 Index Settings：

```
GET .ent-search-engine-5f7deeaeac761042130bf192/_settings
```

以下就是這個 Index Settings 的內容：

```
{
  ".ent-search-engine-5f7deeaeac761042130bf192" : {
    "settings" : {
      "index" : {
        "mapping" : {
          "total_fields" : {
            "limit" : "99999999"
          }
        },
        "indexing" : {
          "slowlog" : {
            "threshold" : {
              "index" : {
                "warn" : "10s",
                "trace" : "500ms",
                "debug" : "2s",
                "info" : "5s"
              }
            }
          }
        },
        "auto_expand_replicas" : "0-1",
        "provided_name" : ".ent-search-engine-5f7deeaeac761042130bf192",
        "creation_date" : "1602088641726",
        "analysis" : {
          "filter" : {
            "front_ngram" : {
              "type" : "edge_ngram",
              "min_gram" : "1",
              "max_gram" : "12"
            },
            "bigram_joiner" : {
              "max_shingle_size" : "2",
              "token_separator" : "",
              "output_unigrams" : "false",
              "type" : "shingle"
            },
            "bigram_max_size" : {
              "type" : "length",
              "max" : "16",
              "min" : "0"
            },
            "phrase_shingle" : {
              "max_shingle_size" : "3",
              "min_shingle_size" : "2",
              "output_unigrams" : "true",
              "type" : "shingle"
            },
            "zh-stop-words-filter" : {
              "type" : "stop",
              "stopwords" : "_english_"
            },
            "delimiter" : {
              "split_on_numerics" : "true",
              "generate_word_parts" : "true",
              "preserve_original" : "false",
              "catenate_words" : "true",
              "generate_number_parts" : "true",
              "catenate_all" : "true",
              "split_on_case_change" : "true",
              "type" : "word_delimiter_graph",
              "catenate_numbers" : "true",
              "stem_english_possessive" : "true"
            },
            "zh-stem-filter" : {
              "name" : "light_english",
              "type" : "stemmer"
            }
          },
          "analyzer" : {
            "i_prefix" : {
              "filter" : [
                "icu_folding",
                "front_ngram"
              ],
              "tokenizer" : "icu_tokenizer"
            },
            "iq_intragram" : {
              "filter" : [
                "icu_folding"
              ],
              "tokenizer" : "intragram_tokenizer"
            },
            "iq_phrase_shingle" : {
              "filter" : [
                "icu_folding",
                "phrase_shingle"
              ],
              "tokenizer" : "icu_tokenizer"
            },
            "iq_text_delimiter" : {
              "filter" : [
                "delimiter",
                "icu_folding",
                "zh-stop-words-filter",
                "zh-stem-filter",
                "cjk_bigram"
              ],
              "tokenizer" : "whitespace"
            },
            "q_prefix" : {
              "filter" : [
                "icu_folding"
              ],
              "tokenizer" : "icu_tokenizer"
            },
            "iq_text_base" : {
              "filter" : [
                "icu_folding",
                "zh-stop-words-filter"
              ],
              "tokenizer" : "icu_tokenizer"
            },
            "iq_text_stem" : {
              "filter" : [
                "icu_folding",
                "zh-stop-words-filter",
                "zh-stem-filter",
                "cjk_bigram"
              ],
              "tokenizer" : "icu_tokenizer"
            },
            "iq_text_bigram" : {
              "filter" : [
                "icu_folding",
                "zh-stem-filter",
                "bigram_joiner",
                "bigram_max_size"
              ],
              "tokenizer" : "icu_tokenizer"
            }
          },
          "tokenizer" : {
            "intragram_tokenizer" : {
              "token_chars" : [
                "letter",
                "digit"
              ],
              "min_gram" : "3",
              "type" : "ngram",
              "max_gram" : "4"
            }
          }
        },
        "priority" : "150",
        "number_of_replicas" : "1",
        "uuid" : "ELd3uxHRRlmPbZW3hDEemg",
        "version" : {
          "created" : "7090299"
        },
        "routing" : {
          "allocation" : {
            "require" : {
              "data" : "hot"
            }
          }
        },
        "search" : {
          "slowlog" : {
            "level" : "debug",
            "threshold" : {
              "fetch" : {
                "warn" : "1000ms",
                "trace" : "200ms",
                "debug" : "300ms",
                "info" : "800ms"
              },
              "query" : {
                "warn" : "2000ms",
                "trace" : "500ms",
                "debug" : "1000ms",
                "info" : "1500ms"
              }
            }
          }
        },
        "number_of_shards" : "2",
        "similarity" : {
          "default" : {
            "type" : "BM25"
          }
        }
      }
    }
  }
}
```

## 剖析 Engine's Index Settings

針對整份 Index Settings 我們分成以下幾部份來看：

### Index 基本設定

我將 `slowlog`, `analysis` 這兩個部份另外切出去討論，剩下的基本設定如下圖：

![engine index settings](https://i.imgur.com/7Xfnv2a.png)

* `index.mapping.total_fields.limit: 99999999`: 這個是指定 index 最多的欄位數量，預設是 `1000`，這裡將這個值設提高到 `99999999`。
* `auto_expand_replicas: 0-1`: 這裡設定 replica 的數量依照 cluster 的 node 數量來決定，會配置 `0~1` 之間，也就是如果有超過 1 個node，就會配置 1 份 replica。
* `priority: 150`: 這個數字是決定 node 重啟動時，身上的 index recover 的優先順序，數字愈大會愈先被 recover。
* `routing.allocation.require.data: hot`: 這是因為使用的 Elastic Cloud Deployment 是 Hot-warm architecture，因此這邊會預設將 index 被安排在 `hot` node 身上。
* `number_of_shards: 2`: 在 App Search，其實不確定會進入的資料量有多少，但可以肯定的是並沒有使用 Rollover 的機制，因此這邊的設定不是預設的 `1`，而是較大的 `2`，相信這是 App Search 團隊評估一般使用 App Search 的使用情境後定出較合適的配置。
* `similarity.default.type: BM25`: 這是相關性計分 (score) 時使用的演算法，目的是計算搜尋的關鍵字與找尋的文件的相關性，會多參考到詞頻、文件的長度、所有文件的平均長度，不過 `BM25` 已是 Elasticsearch 的預設使用的 Similarity。

### Slow Log

這個的設定值的目的，是當 `indexing` 或是 `searching` 的處理，慢到某個程度的時候，要把這 request 給寫在 log 中，也就是協助我們分析 **跑太慢的 request** 到底是做了什麼事，App Search 分別依照這兩種類型有不同的設定：

#### Indexing

```
        "indexing" : {
          "slowlog" : {
            "threshold" : {
              "index" : {
                "warn" : "10s",
                "trace" : "500ms",
                "debug" : "2s",
                "info" : "5s"
              }
            }
          }
        },
```

這邊的定義是如果 `index` 的行為，分別依照不同的 Log severity 條件，達到不同的時間限制，就會記錄 Log，例如如果現在 Log 層級是開 `debug` 時，只要 `index` 的處理超過 2秒，就會產生 slowlog 的記錄。

#### Searching

```
        "search" : {
          "slowlog" : {
            "level" : "debug",
            "threshold" : {
              "fetch" : {
                "warn" : "1000ms",
                "trace" : "200ms",
                "debug" : "300ms",
                "info" : "800ms"
              },
              "query" : {
                "warn" : "2000ms",
                "trace" : "500ms",
                "debug" : "1000ms",
                "info" : "1500ms"
              }
            }
          }
        },
```

`searching` 的部份，有分成兩個階段 `query` and `fetch` ，針對這兩個階段有各自的設定值，任一階段超過某個時間，就會觸發 slowlog 的記錄。

### Analysis

這部份是 Index Analayzer, Tokenizer, Token Filters 的自定義設定，由於我這次選擇的語系是 `Chinese` 這當中也會有少部份是針對中文有特別定義的，設定分別如下：

#### Tokenizer

```
          "tokenizer" : {
            "intragram_tokenizer" : {
              "token_chars" : [
                "letter",
                "digit"
              ],
              "min_gram" : "3",
              "type" : "ngram",
              "max_gram" : "4"
            }
          }
```

這裡有定義了 `ngram` 的 tokenizer，將文字的內容依 3\~4 個字元的大小，切成各自的 tokens，定義成 `instagram_tokenizer`。

#### Token Filters

```
          "filter" : {
            "front_ngram" : {
              "type" : "edge_ngram",
              "min_gram" : "1",
              "max_gram" : "12"
            },
            "bigram_joiner" : {
              "max_shingle_size" : "2",
              "token_separator" : "",
              "output_unigrams" : "false",
              "type" : "shingle"
            },
            "bigram_max_size" : {
              "type" : "length",
              "max" : "16",
              "min" : "0"
            },
            "phrase_shingle" : {
              "max_shingle_size" : "3",
              "min_shingle_size" : "2",
              "output_unigrams" : "true",
              "type" : "shingle"
            },
            "zh-stop-words-filter" : {
              "type" : "stop",
              "stopwords" : "_english_"
            },
            "delimiter" : {
              "split_on_numerics" : "true",
              "generate_word_parts" : "true",
              "preserve_original" : "false",
              "catenate_words" : "true",
              "generate_number_parts" : "true",
              "catenate_all" : "true",
              "split_on_case_change" : "true",
              "type" : "word_delimiter_graph",
              "catenate_numbers" : "true",
              "stem_english_possessive" : "true"
            },
            "zh-stem-filter" : {
              "name" : "light_english",
              "type" : "stemmer"
            }
```

App Search 定義了以下幾種 Token Filter：

* `front_ngram`: 使用 `edge_ngram` 切 1\~12 的 gram，這應該是用來做 prefix matching。
* `bigram_joiner`: 這的作用是將 bigram 切出來的小單位的 token ，再兩兩一組的結合在一起成為新的 token。
* `bigram_max_size`: 針對 bigram token 的長度限定最長是 16。
* `phrase_shingle`: 這個定義了 `shingle` 的方式，將小單位的 token 每 2\~3 為一組，組成 phrase 大小的 token。
* `zh-stop-words-filter`: 這雖然命名是 `zh` 的 stop word filter，但是可能是 ES 預設並沒有中文的 stop word，所以這邊還是以 `_english_` 為語系的設定。
* `delimiter`: 這是處理若某個 token 裡面有一些符號，會再依這些非字母或數字的符號將 token 切成斷成新的 tokens。
* `zh-stem-filter`: 這是將 token 轉成子根的 token filter，但在 `zh` 的語系中，一樣是使用 `light_english` 當成設定值。

### Analyzer

```
          "analyzer" : {
            "i_prefix" : {
              "filter" : [
                "icu_folding",
                "front_ngram"
              ],
              "tokenizer" : "icu_tokenizer"
            },
            "iq_intragram" : {
              "filter" : [
                "icu_folding"
              ],
              "tokenizer" : "intragram_tokenizer"
            },
            "iq_phrase_shingle" : {
              "filter" : [
                "icu_folding",
                "phrase_shingle"
              ],
              "tokenizer" : "icu_tokenizer"
            },
            "iq_text_delimiter" : {
              "filter" : [
                "delimiter",
                "icu_folding",
                "zh-stop-words-filter",
                "zh-stem-filter",
                "cjk_bigram"
              ],
              "tokenizer" : "whitespace"
            },
            "q_prefix" : {
              "filter" : [
                "icu_folding"
              ],
              "tokenizer" : "icu_tokenizer"
            },
            "iq_text_base" : {
              "filter" : [
                "icu_folding",
                "zh-stop-words-filter"
              ],
              "tokenizer" : "icu_tokenizer"
            },
            "iq_text_stem" : {
              "filter" : [
                "icu_folding",
                "zh-stop-words-filter",
                "zh-stem-filter",
                "cjk_bigram"
              ],
              "tokenizer" : "icu_tokenizer"
            },
            "iq_text_bigram" : {
              "filter" : [
                "icu_folding",
                "zh-stem-filter",
                "bigram_joiner",
                "bigram_max_size"
              ],
              "tokenizer" : "icu_tokenizer"
            }
          },
```

最後是 Analyzer 的部份，總共定義了以下幾種：

> 從命名規則來看， `i` 的前綴是給 `indexing` 用的，`q` 的前綴是給 `query` 使用。

* `i_prefix`: 使用了 `icu_tokenizer` 及前面介紹的 `front_ngram`，目的應該就是在 `indexing` 時，直接切出 prefix 比對用的 tokens。
* `iq_intragram`: 這裡使用了前面介紹的 `intragram_tokenizer` ，主要的目的應該是 partial match 使用。
* `iq_phrase_shingle`: 這裡使用 `icu_tokenizer` 但搭配前面介紹的 `phrase_shingle` ，目的應該是增強中文字若是有連接在一起的詞在搜尋時，這種 2\~3 個中文字連接在一起的這種 **phrase**，若是有比對到，分數要能拉高。
* `iq_text_delimiter`: 這裡用的是 `whitespace` 最單純的空白切詞的 tokenizer，並搭配前面介紹的 `delimiter` token filter，會依非英數的符號再切詞、不會特別處理文法的部份、會做取字根的轉換、會去除掉 stop words，主要的目的是用來處理字串中有分隔符號的情境。
* `q_prefix`: 這是針對 query 時用的 prefix analyzer，也就是不用把 query 時的查詢字串特別的拆分，而是直接與 indexing 時被拆分的內容來比對即可。
* `iq_text_base`: 這是 text 類型基本使用的 analyzer，單純的使用 `icu_tokenizer`，並配合 `zh-stop-words-filter` 的 token filter，中規中舉。
* `iq_text_stem`: 這邊使用了 `icu_tokenizer` 並配合 `zh-stem-filter` 來將字根取出，主要的目的是讓字根相同的 token 也能互相被找出。
* `iq_text_bigram`: 這邊使用的是 `icu_tokenizer` 配合 `bigram-joiner` 和 `bigram_max_size` ，主要的目的是將相近的 token，兩兩一組的組合在一起，讓相鄰的查詢結果能提高查詢分數，這部份應該是針對 CJK (Chinese, Japenese, Korean) 語系的文字強化的處理。

## 結語

以上針對 App Search Engine 的 Index Settings 進行剖析，除了 Index Settings 上基本設定的參考、 Slowlog 的配置、這篇有一大塊的重點是介紹到 Analysis 的設定，而這些 Analysis 的實際用法，在下一篇的 Mapping 設定上，可以看到更明確的使用方式，這部份就待下一篇進行探討。

## 參考資料

* [官方文件 - Mapping](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/mapping.html)
* [官方文件 - Similarity](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/similarity.html)
* [官方文件 - Analysis](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis.html)


# Engine 的 Mapping 篇

## 前言

在前一篇介紹中，我們剖析了 Engine Index Settings 的各種設定，其中一大塊介紹到了 Analysis 的部份，包含了 Analyzer, Tokenizer, Token Filters 的設定，這篇文章會針對 Index Mapping 的設置來分析，了解 App Search 如用使用這些 Analysis 的客製化設定來定義他的 Mapping，以及 Mapping 中是否有其他的使用技巧。

### 進入此章節的先備知識

* Elasticsearch Mapping 的基本知識。
* 請先閱讀過前一篇文章。

### 此章節的重點學習

* App Search 如何定義 Engine 的 Mapping。
* 若是修改 App Search Engine 的 Schema 後，會發生什麼變化。

***

## 取得 App Search Engine 的 Mapping 設定

第一筆如同前面的介紹，要先取得 Engine 的 id：

```
GET .ent-search-actastic-engines_v9/_search
```

針對 `_source` > `name` 找到指定 Engine 名稱的 Document ，並取得他的 `id` 欄位。

接下來我們針對取得的 Engine Id - `5f7deeaeac761042130bf192` 透過 Get Mapping API 取得 mapping 的設定：

```
GET .ent-search-engine-5f7deeaeac761042130bf192/_mapping
```

以下是回傳結果：

```
{
  ".ent-search-engine-5f7deeaeac761042130bf192" : {
    "mappings" : {
      "dynamic" : "strict",
      "properties" : {
        "__st_expires_after" : {
          "type" : "date"
        },
        "__st_text_summary" : {
          "type" : "text",
          "analyzer" : "iq_phrase_shingle"
        },
        "date$string" : {
          "type" : "text",
          "fields" : {
            "delimiter" : {
              "type" : "text",
              "index_options" : "offsets",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "iq_text_delimiter",
              "position_increment_gap" : 100
            },
            "enum" : {
              "type" : "keyword",
              "ignore_above" : 2048
            },
            "intragram" : {
              "type" : "text",
              "index_options" : "docs",
              "analyzer" : "iq_intragram"
            },
            "joined" : {
              "type" : "text",
              "index_options" : "offsets",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "iq_text_bigram",
              "position_increment_gap" : 100
            },
            "prefix" : {
              "type" : "text",
              "index_options" : "docs",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "i_prefix",
              "search_analyzer" : "q_prefix"
            },
            "stem" : {
              "type" : "text",
              "index_options" : "offsets",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "iq_text_stem",
              "position_increment_gap" : 100
            }
          },
          "index_options" : "offsets",
          "analyzer" : "iq_text_base",
          "position_increment_gap" : 100
        },
        "description$string" : {
          "type" : "text",
          "fields" : {
            "delimiter" : {
              "type" : "text",
              "index_options" : "offsets",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "iq_text_delimiter",
              "position_increment_gap" : 100
            },
            "enum" : {
              "type" : "keyword",
              "ignore_above" : 2048
            },
            "intragram" : {
              "type" : "text",
              "index_options" : "docs",
              "analyzer" : "iq_intragram"
            },
            "joined" : {
              "type" : "text",
              "index_options" : "offsets",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "iq_text_bigram",
              "position_increment_gap" : 100
            },
            "prefix" : {
              "type" : "text",
              "index_options" : "docs",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "i_prefix",
              "search_analyzer" : "q_prefix"
            },
            "stem" : {
              "type" : "text",
              "index_options" : "offsets",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "iq_text_stem",
              "position_increment_gap" : 100
            }
          },
          "index_options" : "offsets",
          "analyzer" : "iq_text_base",
          "position_increment_gap" : 100
        },
        "engine_id" : {
          "type" : "keyword"
        },
        "external_id" : {
          "type" : "keyword"
        },
        "id" : {
          "type" : "keyword"
        },
        "location$string" : {
          "type" : "text",
          "fields" : {
            "delimiter" : {
              "type" : "text",
              "index_options" : "offsets",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "iq_text_delimiter",
              "position_increment_gap" : 100
            },
            "enum" : {
              "type" : "keyword",
              "ignore_above" : 2048
            },
            "intragram" : {
              "type" : "text",
              "index_options" : "docs",
              "analyzer" : "iq_intragram"
            },
            "joined" : {
              "type" : "text",
              "index_options" : "offsets",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "iq_text_bigram",
              "position_increment_gap" : 100
            },
            "prefix" : {
              "type" : "text",
              "index_options" : "docs",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "i_prefix",
              "search_analyzer" : "q_prefix"
            },
            "stem" : {
              "type" : "text",
              "index_options" : "offsets",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "iq_text_stem",
              "position_increment_gap" : 100
            }
          },
          "index_options" : "offsets",
          "analyzer" : "iq_text_base",
          "position_increment_gap" : 100
        },
        "title$string" : {
          "type" : "text",
          "fields" : {
            "delimiter" : {
              "type" : "text",
              "index_options" : "offsets",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "iq_text_delimiter",
              "position_increment_gap" : 100
            },
            "enum" : {
              "type" : "keyword",
              "ignore_above" : 2048
            },
            "intragram" : {
              "type" : "text",
              "index_options" : "docs",
              "analyzer" : "iq_intragram"
            },
            "joined" : {
              "type" : "text",
              "index_options" : "offsets",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "iq_text_bigram",
              "position_increment_gap" : 100
            },
            "prefix" : {
              "type" : "text",
              "index_options" : "docs",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "i_prefix",
              "search_analyzer" : "q_prefix"
            },
            "stem" : {
              "type" : "text",
              "index_options" : "offsets",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "iq_text_stem",
              "position_increment_gap" : 100
            }
          },
          "index_options" : "offsets",
          "analyzer" : "iq_text_base",
          "position_increment_gap" : 100
        },
        "visitors$string" : {
          "type" : "text",
          "fields" : {
            "delimiter" : {
              "type" : "text",
              "index_options" : "offsets",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "iq_text_delimiter",
              "position_increment_gap" : 100
            },
            "enum" : {
              "type" : "keyword",
              "ignore_above" : 2048
            },
            "intragram" : {
              "type" : "text",
              "index_options" : "docs",
              "analyzer" : "iq_intragram"
            },
            "joined" : {
              "type" : "text",
              "index_options" : "offsets",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "iq_text_bigram",
              "position_increment_gap" : 100
            },
            "prefix" : {
              "type" : "text",
              "index_options" : "docs",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "i_prefix",
              "search_analyzer" : "q_prefix"
            },
            "stem" : {
              "type" : "text",
              "index_options" : "offsets",
              "term_vector" : "with_positions_offsets",
              "analyzer" : "iq_text_stem",
              "position_increment_gap" : 100
            }
          },
          "index_options" : "offsets",
          "analyzer" : "iq_text_base",
          "position_increment_gap" : 100
        }
      }
    }
  }
}
```

## 剖析 Engine 的 Mapping

### 基本設定

我們先把一些自訂的欄位收起來，整體設定一覽如下：

![app search engine mapping - overview](https://i.imgur.com/m505WQU.png)

這裡有包含基本的 mapping 設定與一些 App Search 的欄位：

* `dynamic: strict`: 這指的是關掉 dynamic mapping，也就是如果嘗試 indexing 一個 mapping 不存在的欄位時，會回傳錯誤並且 indexing 失敗，這也是一般進入 Production 環境時、而且資料欄位的增長是能控制時，較嚴謹的使用方式。
* 一些 App Search 內部使用的欄位：`engine_id`, `external_id`, `id`, `__st_text_summary` 和 `__st_expires_after`。
* 我們自己新增的欄位：這些欄位會以 `$` 後面帶上這欄位的型態，這邊的例子都是 `string` ，因為我們 import document 後並沒有特別改變 Schema 的設定，所以預設都是 `string`。

### String 類型的 Mapping 設定

接下來我們將自訂的某個 `string` 欄位的細部 Mapping 設定展開：

![app search engine mapping - string](https://i.imgur.com/y2Tim5a.png)

這裡才是這篇文章的重點，也就是 App Search 如何處理這些 **需要被搜尋** 的文字欄位：

* `index_options: offsets`: 這邊使用的 `offsets` 是 `index_options` 最詳細的設定。
* `analyzer`: 預設的 Analyzer 是 `iq_text_base` ，也就是 App Search Analysis 中，基本的文字處理 Analyzer。
* `position_increment_gap`: 這是處理 array 這種類型的多值的資料，拉開每個值之間的距離，提升這種多值的資料查詢的準確性。

接下來是 Fields 的設定，總共包含以下幾種，並且我們使用 `_analyze` API 來示範：

* `delimiter`: 使用的是 `iq_text_delimiter` Analyzer，也就是先前介紹過，使用 `whitespace` 最單純的空白切詞，會依非英數的符號再切詞、不會特別處理文法的部份、會做取字根的轉換、會去除掉 stop words，主要的目的是用來處理字串中有分隔符號的情境。

  ```
  POST .ent-search-engine-5f7deeaeac761042130bf192/_analyze
  {
    "field": "description$string.delimiter",
    "text": "red;yellow;紫;黑色"
  }
  ```

  拆出來的 tokens 如下：

  ```
  redyellow紫黑色, red, yellow, 紫, 黑色
  ```
* `enum`: 使用的是 `keyword` 的 type，也就是保留完整的字串內容成為一個 token。

  ```
  POST .ent-search-engine-5f7deeaeac761042130bf192/_analyze
  {
    "field": "description$string.enum",
    "text": "red;yellow;紫;黑色"
  }
  ```

  拆出來的 tokens 如下：

  ```
  red;yellow;紫;黑色
  ```
* `intragram`: 使用的是 `iq_intragram` Analyzer，也就是以 `ngram` 3\~4 為單位來分詞，不會去處理 CJK 的字元，主要應該是做 partial match 使用。

  ```
  POST .ent-search-engine-5f7deeaeac761042130bf192/_analyze
  {
    "field": "description$string.intragram",
    "text": "red;yellow;紫;黑色"
  }
  ```

  拆出來的 tokens 如下：

  ```
  red, yel, yell, ell, ello, llo, llow, low
  ```
* `joined`: 使用的是 `iq_text_bigram`，這是將 icu 切出來一個個的 token，兩兩一組產生成新的 token，例如，依此讓搜尋時若有臨近的詞，在找尋文件時也相近時，能被找到，可以依此拉高這種相鄰詞的相關性分數。

  ```
  POST .ent-search-engine-5f7deeaeac761042130bf192/_analyze
  {
    "field": "description$string.joined",
    "text": "red;yellow;紫;黑色"
  }
  ```

  拆出來的 tokens 如下：

  ```
  redyellow, yellow紫, 紫黑色
  ```
* `prefix`: 使用的 `indexing` analyzer 是 `i_prefix` ，而 `search` 時的 analyzer 是 `q_prefix`，也就是 `indexing` 時要用 `edge_ngram` 拆分成各種大小一組一組的 tokens，但查詢時要用原來的字去比對，不要另外拆分。

  其中 `i_prefix` 的執行效果：

  ```
  POST .ent-search-engine-5f7deeaeac761042130bf192/_analyze
  {
    "analyzer": "i_prefix", 
    "text": "red;yellow;紫;黑色"
  }
  ```

  拆出來的 tokens 如下：

  ```
  r, re, red, y, ye, yel, yell, yello, yellow, 紫, 黑, 黑色
  ```

  若是使用 `q_prefix` 的執行效果：

  ```
  POST .ent-search-engine-5f7deeaeac761042130bf192/_analyze
  {
    "analyzer": "q_prefix", 
    "text": "red;yellow;紫;黑色"
  }
  ```

  拆出來的 tokens 如下：

  ```
  red, yellow, 紫, 黑色
  ```
* `stem`: 使用的是 `iq_text_stem` Analyzer，主要是有處理過子根的處理，讓 tokens 只保留字根。

  ```
  POST .ent-search-engine-5f7deeaeac761042130bf192/_analyze
  {
    "field": "description$string.stem",
    "text": "quickly;cats;紫;黑色"
  }
  ```

  拆出來的 tokens 如下：

  ```
  quick, cat, 紫, 黑色
  ```

## Engine Schema 改變時 Mapping 的變化

當我們進入 App Search 的 UI，透過 Schema 把原先預設的 `text` 欄位，改成我們實際資料的欄位：

![image-20201009003000700](https://i.imgur.com/sF7SWWm.png)

這時底下到底發生了什麼變化?

首先我們看一下 index 中的 document：

```
GET .ent-search-engine-5f7deeaeac761042130bf192/_search
```

```
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : ".ent-search-engine-5f7deeaeac761042130bf192",
        "_type" : "_doc",
        "_id" : "5f7deec0ac7610cca50bf195",
        "_score" : 1.0,
        "_source" : {
          "title$string" : "Rocky Mountain",
          "description$string" : "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra. Wildlife including mule deer, bighorn sheep, black bears, and cougars inhabit its igneous mountains and glacial valleys. Longs Peak, a classic Colorado fourteener, and the scenic Bear Lake are popular destinations, as well as the historic Trail Ridge Road, which reaches an elevation of more than 12,000 feet (3,700 m).",
          "visitors$float" : 4517585.0,
          "location$location" : "40.4,-105.58",
          "date$date" : "1915-01-26T06:00:00+00:00",
          "id" : "5f7deec0ac7610cca50bf195",
          "external_id" : "park_rocky-mountain",
          "engine_id" : "5f7deeaeac761042130bf192",
          "__st_text_summary" : "Rocky Mountain Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra. Wildlife including mule deer, bighorn sheep, black bears, and cougars inhabit its igneous mountains and glacial valleys. Longs Peak, a classic Colorado fourteener, and the scenic Bear Lake are popular destinations, as well as the historic Trail Ridge Road, which reaches an elevation of more than 12,000 feet (3,700 m). 4517585 40.4,-105.58 1915-01-26T06:00:00Z",
          "__st_expires_after" : null
        }
      }
    ]
  }
}
```

可以發現，這些文件被 reindex 處理過，欄位名稱都改變了，從原本都是 `$string` 結尾的欄位，各自對應有 `$float`, `$location`, `$date` 的型態描述。

再來我們看一下 mapping：

```
GET .ent-search-engine-5f7deeaeac761042130bf192/_mapping
```

![engine mapping after re-index](https://i.imgur.com/GDwdzR0.png)

我們可以發現，原先 `$string` 的這些所有欄位定義都還是存在，代表 index 沒有被重新建立，也因為 Elasticsearch Mapping 新增欄位後就不能刪掉這些定義，所以舊的定義都還是存在，但各自都擁有新的態型的欄位定義了，像是 `date$date`, `location$location`, `visitors$float`。

## 結語

從這篇文章的探討，我們可以知道 App Search Engine 的 Mapping 設定方式，特別是針對 `string` 型態的欄位定義了許多的 `fields` ，另外在修改 Engine Schema 時，會 re-index 文件但會保留原先型態欄位的定義，下一篇將會介紹這些資料被建立起來後，實際的查詢時會如何被使用。

## 參考資料

* [官方文件 - Mapping](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/mapping.html)


# Engine 的 Search 基礎剖析篇

## 前言

前面的章節介紹了 App Search 如何使用 Elasticsearch 來建立 Engine 的 Index 及 Mapping，也介紹各種客制的 Analysis 以及應用在 Mapping 上的各種配置方法，接下來我們要研究 App Search 在執行 Search 時是如何運作的。

### 進入此章節的先備知識

* Elasticsearch Query DSL 的基本知識。
* 請先閱讀本系列先前的文章。

### 此章節的重點學習

* 如何查看 App Search 的 Query 的方式。
* 剖析 App Search Query 的基本使用方式。

***

## 如何取得 App Search 的 Query 內容

App Search 是使用 Elasticsearch 當 Search Engine & Database、並且對外提供 API 的應用程式，他並沒有公開源始碼，所以對我們來說他是一個黑盒子，要知道 App Search 是如何對 Elasticsearch 執行 Query 的，最簡單的方式還是從 Elasticsearch 下手，那就是 - **透過 Elatsicsearch 的 Request Log 來查看 App Search 送了 Request**，但 Elasticsearc 並不會將一般的 Request Log 都記錄下來，所以我們這邊要使用一個小撇步，透過修改 **Slowlog** 的配置，讓我們想辦法得到我們要的東西。

```
PUT .ent-search-engine-5f7deeaeac761042130bf192/_settings
{
  "index.search.slowlog.threshold.query.info": "0ms"
}
```

設定完成之後，再透過 Get Index API 來確認已修改完成：

```
GET .ent-search-engine-5f7deeaeac761042130bf192/_settings
```

結果如下：

![app search sloglog setting](https://i.imgur.com/bylns9W.png)

這時我們透過 App Search 的 Query Tester 進行搜尋，以下以搜尋 `divide` 為例：

![app search query tester](https://i.imgur.com/buCVimg.png)

這時我們去查看 Elasticsearch 的 log，會發現搜尋 `divide` 的 slowlog 的被印出來：

![elastic cloud logs](https://i.imgur.com/vmkSxJS.png)

其中 `source[]` 裡面放的，就是 App Search 執行搜尋的動作時，產生並發送給 Elasticsearch 的 **Search Request**。

## App Search 執行查詢時的 Search Request

我們將 Log 中的 **Search Request** 排版後展開如下：

```
{
  "from": 0,
  "size": 10,
  "timeout": "2000ms",
  "query": {
    "bool": {
      "must": [
        {
          "bool": {
            "must": [
              {
                "bool": {
                  "should": [
                    {
                      "multi_match": {
                        "query": "divide",
                        "fields": [
                          "description$string^1.0",
                          "description$string.delimiter^0.4",
                          "description$string.joined^0.75",
                          "description$string.prefix^0.1",
                          "description$string.stem^0.95",
                          "external_id^1.0",
                          "title$string^1.0",
                          "title$string.delimiter^0.4",
                          "title$string.joined^0.75",
                          "title$string.prefix^0.1",
                          "title$string.stem^0.95"
                        ],
                        "type": "cross_fields",
                        "operator": "OR",
                        "slop": 0,
                        "prefix_length": 0,
                        "max_expansions": 50,
                        "minimum_should_match": "1<-1 3<49%",
                        "zero_terms_query": "NONE",
                        "auto_generate_synonyms_phrase_query": true,
                        "fuzzy_transpositions": true,
                        "boost": 1
                      }
                    },
                    {
                      "constant_score": {
                        "filter": {
                          "multi_match": {
                            "query": "divide",
                            "fields": [
                              "description$string.intragram^0.1",
                              "external_id.intragram^0.1",
                              "title$string.intragram^0.1"
                            ],
                            "type": "best_fields",
                            "operator": "OR",
                            "slop": 0,
                            "prefix_length": 0,
                            "max_expansions": 50,
                            "minimum_should_match": "35%",
                            "zero_terms_query": "NONE",
                            "auto_generate_synonyms_phrase_query": true,
                            "fuzzy_transpositions": true,
                            "boost": 1
                          }
                        },
                        "boost": 0.1
                      }
                    }
                  ],
                  "adjust_pure_negative": true,
                  "boost": 1
                }
              }
            ],
            "adjust_pure_negative": true,
            "boost": 1
          }
        }
      ],
      "adjust_pure_negative": true,
      "boost": 1
    }
  },
  "_source": {
    "includes": [
      "date$date",
      "visitors$float",
      "description$string",
      "external_id",
      "location$location",
      "title$string",
      "engine_id"
    ],
    "excludes": []
  },
  "sort": [
    {
      "_score": {
        "order": "desc"
      }
    },
    {
      "_doc": {
        "order": "desc"
      }
    }
  ],
  "highlight": {
    "fragment_size": 300,
    "number_of_fragments": 1,
    "type": "plain",
    "highlight_query": {
      "multi_match": {
        "query": "divide",
        "fields": [
          "description$string.prefix^1.0",
          "description$string.stem^1.0",
          "title$string.prefix^1.0",
          "title$string.stem^1.0"
        ],
        "type": "best_fields",
        "operator": "OR",
        "slop": 0,
        "prefix_length": 0,
        "max_expansions": 50,
        "zero_terms_query": "NONE",
        "auto_generate_synonyms_phrase_query": true,
        "fuzzy_transpositions": true,
        "boost": 1
      }
    },
    "order": "score",
    "require_field_match": false,
    "encoder": "html",
    "fields": {
      "description$string.stem": {
        "fragment_size": 100,
        "no_match_size": 100
      },
      "description$string.prefix": {
        "fragment_size": 100,
        "no_match_size": 100
      },
      "title$string.stem": {
        "fragment_size": 100,
        "no_match_size": 100
      },
      "title$string.prefix": {
        "fragment_size": 100,
        "no_match_size": 100
      }
    }
  }
}
```

讓我們來剖析這個 Search Request 的使用方式，主要以下面幾個方向來分析。

### 一般查詢設定

* `timeout: 2000ms`: 在執行 search request 時，有明確宣告 2秒 的timeout 限制，避免過長時間等待的查詢。
* `_source`: 這邊有明確的指定 `includes` 的欄位有哪些，也就是回傳結果只有包含這些有宣告的欄位，其他欄位不會回傳。
* `sort`: 排序的方式使用 `_score` 遞減，也就是相關性計分的結果愈相關愈優先，分數同樣時，則不特別指定其他排序的規則，並以 `_doc` 的宣告來排序。

> `_doc` 的用方可參考官方文件的說明：
>
> <img src="https://i.imgur.com/3N3AziY.png" alt="sort _doc" data-size="original">

### 關鍵字查詢

關鍵字的搜尋，只會被套用到 `string` 型態的欄位，其他型態的欄位內容值，不會影響搜尋的結果，其中查詢時透過 `bool query` 的 `should` 也就是 **OR** 的邏輯，將 `multi_match` 與 `constant_score` 的查詢結果組合在一起：

#### `multi_match query`

![multi\_match query](https://i.imgur.com/Dy4Ek66.png)

這邊主要使用 `multi_match query` ，將 `string` 欄位先前定義的各種 `fields` 的特性，透過 `cross_fields` 的方式，分別以不同的比重進行查詢：

* 預設的文字 Analyzer: **1.0**
* delimiter: **0.4**
* joined: **0.75**
* prefix: **0.1**
* stem: **0.95**

此外有透過 `minimum_should_match: 1<-1 3<49%` 的設定，來依照查詢時輸入的關鍵字切出的 terms，決定搜尋時的精確度，這邊的設置是如果是 1\~3 個 term，會需要 1個 term 有 match，如果是超過 3 個 term，會要有 49% 的 match 率。(參考：[官方文件 - minimum\_should\_match](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-minimum-should-match.html))

> 其他有些上面有宣告的設定，但是與預設值相同的，這邊就不特別解釋，需要查詢定義的話，請參考：[官方文件 - Multi-match query](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html)

#### `constant_score query`

這邊查詢主要的目的是將 `intragram` 的查詢結果使用 constant score 的計分方式包含至最終查詢的結果。

而 intragram 的 boost 優先權重是 `0.1` ，此外這邊並沒有跨多欄位合併查詢的需求，所以 multi\_match 的 type 指定使用 `best_fields` 的方式執行。

![constant\_score query](https://i.imgur.com/RFjIwWm.png)

### Highlight 查詢的結果

針對查詢的結果，一併會包含 `highlight` 的標示，這邊使用的是 `plain` 的 highlighting 方式，整體的設置如下：

![image-20201010001024291](https://i.imgur.com/2zbgEZ2.png)

也因為使用 `plain` highlighting 的方式，這邊的 `highlight_query` 只針對 `prefix` 與 `stem` 的 fields 進行查詢，因為在回傳時要顯示時，這兩種分詞的方式會是較能明確顯示關鍵字在原始字串中的比對結果。

## App Search 執行查詢時的 Search Response

上面的 Search Request 執行後，Elasticsearch 的回傳結果如下：

```
{
  "took" : 15,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : ".ent-search-engine-5f7deeaeac761042130bf192",
        "_type" : "_doc",
        "_id" : "5f7deec0ac7610cca50bf195",
        "_score" : 0.38768208,
        "_source" : {
          "title$string" : "Rocky Mountain",
          "engine_id" : "5f7deeaeac761042130bf192",
          "description$string" : "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra. Wildlife including mule deer, bighorn sheep, black bears, and cougars inhabit its igneous mountains and glacial valleys. Longs Peak, a classic Colorado fourteener, and the scenic Bear Lake are popular destinations, as well as the historic Trail Ridge Road, which reaches an elevation of more than 12,000 feet (3,700 m).",
          "date$date" : "1915-01-26T06:00:00+00:00",
          "visitors$float" : 4517585.0,
          "external_id" : "park_rocky-mountain",
          "location$location" : "40.4,-105.58"
        },
        "highlight" : {
          "description$string.prefix" : [
            "Bisected north to south by the Continental <em>Divide</em>, this portion of the Rockies has ecosystems"
          ],
          "title$string.prefix" : [
            "Rocky Mountain"
          ],
          "title$string.stem" : [
            "Rocky Mountain"
          ],
          "description$string.stem" : [
            "Bisected north to south by the Continental <em>Divide</em>, this portion of the Rockies has ecosystems"
          ]
        },
        "sort" : [
          0.38768208,
          0
        ]
      }
    ]
  }
}
```

Search 的結果就是包含我們 `_source` 、 `sort` 以及 `highlight` 宣告的內容。

## 結語

這篇的內容，串接了先前 App Search Engine 的 Index Analysis 設定、Mapping 的設定，了解 App Search 對 Elasticsearch 執行 Search Request 時的使用方式，如何在 indexing 時期先透過 `fields` 的各種 anlaysis 處理特性先將文件進行處理，搜尋時再依照各種權重的組合，將查詢的結果合併在一起，能增加比對的廣泛度卻不用依賴 `searching` 時期非常耗資源的 fuziness 或是 prefix query，同時又會依照各種特性的權重混合計算，能讓**最相關的排在查詢結果的最上方**，不過要注意的是這樣的組合在不少使用情境下、特別是中文的處理中，還是會有缺點，像是可能查詢結果下方會出現非預期的結果，不過這已經是讓 App Search 這種 Out of Box Experience 的產品 **以一招打天下** 的使用配置上很不錯的配置了，值得我們的參考。

## 參考資料

* [官方文件 - Sort search results](https://www.elastic.co/guide/en/elasticsearch/reference/current/sort-search-results.html#sort-search-results)
* [官方文件 - Multi-match query](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html)
* [官方文件 - minimum\_should\_match](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-minimum-should-match.html)
* [官方文件 - Highlighting](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/highlighting.html)


# Engine 的 Search 進階剖析篇

## 前言

前面的章節介紹了 App Search 在執行 Engine 的搜尋時，我們如何取得 App Search 發送給 Elasticsearch 的 Search Request，搭配這系列先前的文章所介紹各種客制的 Analysis 以及應用在 Mapping 上的各種配置方法，我們剖析了 App Search 在執行 Search 時是如何運作的，這篇文章將會進一步探討在使用 App Search 的其他功能、像是 `Synonyms`, `Curations`, `Relevance Tuning` 時，Search Request 會有什麼樣的變化。

### 進入此章節的先備知識

* Elasticsearch Query DSL 的基本知識。
* 請先閱讀本系列先前的文章。

### 此章節的重點學習

* 在使用 App Search 的 `Synonyms`, `Curations`, `Relevance Tuning` 功能時，底層是如何使用 Elasticsearch 的。

***

## 準備測試資料

首先我們先增加幾筆資料，以協助接下來幾個使用案例的說明，這三筆資料請直接使用 App Search Data Importer 匯入即可。

```
[
  {
    "id": "park_rocky-mountain",
    "title": "Rocky Mountain",
    "description": "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra. Wildlife including mule deer, bighorn sheep, black bears, and cougars inhabit its igneous mountains and glacial valleys. Longs Peak, a classic Colorado fourteener, and the scenic Bear Lake are popular destinations, as well as the historic Trail Ridge Road, which reaches an elevation of more than 12,000 feet (3,700 m).",
    "visitors": 4517585,
    "location": "40.4,-105.58",
    "date": "1915-01-26T06:00:00Z"
  },
  {
    "id": "yangming-mountain",
    "title": "Yangming Mountain",
    "description": "Yangmingshan National Park is one of the nine national parks in Taiwan, located between Taipei and New Taipei City. The districts that house parts of the park grounds include Taipei's Beitou and Shilin Districts; and New Taipei's Wanli, Jinshan and Sanzhi Districts. The national park is known for its cherry blossoms, hot springs, sulfur deposits, fumaroles, venomous snakes, and hiking trails, including Taiwan's tallest dormant volcano, Qixing (Seven Star) Mountain (1,120 m).",
    "visitors": 123123,
    "location": "25.17,121.56",
    "date": "1985-09-15T16:00:00Z"
  },
  {
    "id": "Himalaya-mountain",
    "title": "Himalaya",
    "description": "The Himalayas, is a mountain range in Asia separating the plains of the Indian subcontinent from the Tibetan Plateau. The range has many of Earth's highest peaks, including the highest, Mount Everest, at the border between Nepal and China. The Himalayas include over fifty mountains exceeding 7,200 m (23,600 ft) in elevation, including ten of the fourteen 8,000-metre peaks. By contrast, the highest peak outside Asia (Aconcagua, in the Andes) is 6,961 m (22,838 ft) tall.",
    "visitors": 52700000,
    "location": "27.59,86.55",
    "date": "1900-01-01T00:00:00Z"
  }
]
```

## Synonym 同義字的查詢

針對探討 Synonym 同義字的查詢的執行方式，我們先建立一組同義字，這邊使用一個例子，將 `rocky` 和 `yangming` 這兩個字設成同義字：

![synonym setting](https://i.imgur.com/8RUBjmp.png)

接著我們到 Query Tester 執行查詢，並使用 `rocky` 來當查詢的關鍵字：

![synonym query tester](https://i.imgur.com/wvWQMjj.png)

接下來我們來看看 slowlog 幫我們印出來 Elasticsearch 收到的 Search Request 的內容是什麼：

![synonym request slowlog](https://i.imgur.com/dRvJTLR.jpg)

以下是 Formatted Search Request Payload：

```
{
  "from": 0,
  "size": 10,
  "timeout": "2000ms",
  "query": {
    "bool": {
      "must": [
        {
          "bool": {
            "must": [
              {
                "bool": {
                  "should": [
                    {
                      "multi_match": {
                        "query": "rocky",
                        "fields": [
                          "description$string^1.0",
                          "description$string.delimiter^0.4",
                          "description$string.joined^0.75",
                          "description$string.prefix^0.1",
                          "description$string.stem^0.95",
                          "external_id^1.0",
                          "title$string^1.0",
                          "title$string.delimiter^0.4",
                          "title$string.joined^0.75",
                          "title$string.prefix^0.1",
                          "title$string.stem^0.95"
                        ],
                        "type": "cross_fields",
                        "operator": "OR",
                        "slop": 0,
                        "prefix_length": 0,
                        "max_expansions": 50,
                        "minimum_should_match": "1<-1 3<49%",
                        "zero_terms_query": "NONE",
                        "auto_generate_synonyms_phrase_query": true,
                        "fuzzy_transpositions": true,
                        "boost": 1
                      }
                    },
                    {
                      "constant_score": {
                        "filter": {
                          "multi_match": {
                            "query": "rocky",
                            "fields": [
                              "description$string.intragram^0.1",
                              "external_id.intragram^0.1",
                              "title$string.intragram^0.1"
                            ],
                            "type": "best_fields",
                            "operator": "OR",
                            "slop": 0,
                            "prefix_length": 0,
                            "max_expansions": 50,
                            "minimum_should_match": "35%",
                            "zero_terms_query": "NONE",
                            "auto_generate_synonyms_phrase_query": true,
                            "fuzzy_transpositions": true,
                            "boost": 1
                          }
                        },
                        "boost": 0.1
                      }
                    },
                    {
                      "bool": {
                        "should": [
                          {
                            "multi_match": {
                              "query": "rocky yangming",
                              "fields": [
                                "description$string^1.0",
                                "description$string.delimiter^0.4",
                                "description$string.joined^0.75",
                                "description$string.prefix^0.1",
                                "description$string.stem^0.95",
                                "external_id^1.0",
                                "title$string^1.0",
                                "title$string.delimiter^0.4",
                                "title$string.joined^0.75",
                                "title$string.prefix^0.1",
                                "title$string.stem^0.95"
                              ],
                              "type": "cross_fields",
                              "operator": "OR",
                              "slop": 0,
                              "prefix_length": 0,
                              "max_expansions": 50,
                              "zero_terms_query": "NONE",
                              "auto_generate_synonyms_phrase_query": true,
                              "fuzzy_transpositions": true,
                              "boost": 0.75
                            }
                          }
                        ],
                        "adjust_pure_negative": true,
                        "boost": 1
                      }
                    }
                  ],
                  "adjust_pure_negative": true,
                  "boost": 1
                }
              }
            ],
            "adjust_pure_negative": true,
            "boost": 1
          }
        }
      ],
      "adjust_pure_negative": true,
      "boost": 1
    }
  },
  "_source": {
    "includes": [
      "date$date",
      "visitors$float",
      "description$string",
      "external_id",
      "location$location",
      "title$string",
      "engine_id"
    ],
    "excludes": []
  },
  "sort": [
    {
      "_score": {
        "order": "desc"
      }
    },
    {
      "_doc": {
        "order": "desc"
      }
    }
  ],
  "highlight": {
    "fragment_size": 300,
    "number_of_fragments": 1,
    "type": "plain",
    "highlight_query": {
      "multi_match": {
        "query": "rocky",
        "fields": [
          "description$string.prefix^1.0",
          "description$string.stem^1.0",
          "title$string.prefix^1.0",
          "title$string.stem^1.0"
        ],
        "type": "best_fields",
        "operator": "OR",
        "slop": 0,
        "prefix_length": 0,
        "max_expansions": 50,
        "zero_terms_query": "NONE",
        "auto_generate_synonyms_phrase_query": true,
        "fuzzy_transpositions": true,
        "boost": 1
      }
    },
    "order": "score",
    "require_field_match": false,
    "encoder": "html",
    "fields": {
      "description$string.stem": {
        "fragment_size": 100,
        "no_match_size": 100
      },
      "description$string.prefix": {
        "fragment_size": 100,
        "no_match_size": 100
      },
      "title$string.stem": {
        "fragment_size": 100,
        "no_match_size": 100
      },
      "title$string.prefix": {
        "fragment_size": 100,
        "no_match_size": 100
      }
    }
  }
}
```

我們可以發現這個 Request 和先前介紹基本 App Search 執行 Search 時產生的 Request 有一個塊 **新增加** 的一組 `multi_match` 查詢，並且使用 `bool query - should` 和原本的查詢包在一起：

![synonym additioinal request](https://i.imgur.com/0er1XJ3.png)

我們發現這個 query 的關鍵字，直接包含了 `rocky` 和 `yangming` ，這代表了一件事：

App Search 在處理 Synonym 時，**是在 Application 端進行的處理，不是使用 Elasticsearch 內的同義字字典機制**，也就是當 App Search 的 Search API 收到 `rocky` 的關鍵字時，在 Application 端，先發現 `rocky` 是包含在 Synonym 的定義中，所以直接將 Synonym 的 `rocky` 這組同義字設定展開，也就是 `rocky yangming`，並且另外帶入在 Elasticsearch 的查詢中，也就因此產生上面的這個查詢語句，並且由於是同義字的查詢，所以這部份的 boost 值設定為較低的 `0.75`。

> 這種做法的好處是，因為 App Search 的 同義字 是在 App Search 的後台讓使用者靈活的自行設置，所以在 Application 端處理的彈性較高，不用另外維護 Elasticsearch 參照到的同義字字典，同時為了能彈性的調整，所以這邊 Synonym 的執行方式選擇是 `searching` 時機的同義字比對，也就是在搜尋時將關鍵字參考到同義字字典後展開，查詢所有同義字有定義的詞，以查詢出包含這些詞的文件，而不是在 `indexing` 時期先參考好同義字字典，並先將同義字的相關的字詞都包含在 index 中。

## Curation

在使用 Curation 時，又是如何運作的呢？

以下我們透過 `mountain` 這個關鍵字為例，原始的 `mountain` 查詢結果如下：

![curation original request](https://i.imgur.com/TSL2fqg.png)

我們在先 App Search 建立一組新的 Curation 設定，針對 `mountain` 這個關鍵字。

![curation - create](https://i.imgur.com/YIFRdMX.png)

並且將原本分數最低的 `yangming mountain` ，拉到 promoted documents 中。

![curation - manage curation](https://i.imgur.com/y6Sbwh5.png)

這時我們再重新搜尋 `mountain` 時，這個 `yangming mountain` 的 Score 變成了 `1`，並排序在最上面。

![curation - new search](https://i.imgur.com/zpUITH6.png)

我們再透過 slowlog 來查詢底下發生了什麼事，這時我們發現有 **2筆的 logs**。

![curation slowlog](https://i.imgur.com/1lLYpBm.png)

原來 App Search 在處理 Curation 時，會將查詢結果分成兩部份來執行

1. 取得 Curated 項目
2. 執行其他文件的查詢 (不能包含 curated item)

以下我們分別查看各別的查詢內容為何：

### 取得 Curated 項目

```
{
  "from": 0,
  "size": 1,
  "timeout": "2000ms",
  "query": {
    "bool": {
      "must": [
        {
          "match_all": {
            "boost": 1
          }
        }
      ],
      "filter": [
        {
          "bool": {
            "must": [
              {
                "terms": {
                  "external_id": [
                    "yangming-mountain"
                  ],
                  "boost": 1
                }
              }
            ],
            "adjust_pure_negative": true,
            "boost": 1
          }
        }
      ],
      "adjust_pure_negative": true,
      "boost": 1
    }
  },
  "_source": {
    "includes": [
      "date$date",
      "visitors$float",
      "description$string",
      "external_id",
      "location$location",
      "title$string",
      "engine_id"
    ],
    "excludes": []
  },
  "highlight": {
    "fragment_size": 300,
    "number_of_fragments": 1,
    "type": "plain",
    "highlight_query": {
      "multi_match": {
        "query": "Mountain",
        "fields": [
          "description$string.prefix^1.0",
          "description$string.stem^1.0",
          "title$string.prefix^1.0",
          "title$string.stem^1.0"
        ],
        "type": "best_fields",
        "operator": "OR",
        "slop": 0,
        "prefix_length": 0,
        "max_expansions": 50,
        "zero_terms_query": "NONE",
        "auto_generate_synonyms_phrase_query": true,
        "fuzzy_transpositions": true,
        "boost": 1
      }
    },
    "order": "score",
    "require_field_match": false,
    "encoder": "html",
    "fields": {
      "description$string.stem": {
        "fragment_size": 100,
        "no_match_size": 100
      },
      "description$string.prefix": {
        "fragment_size": 100,
        "no_match_size": 100
      },
      "title$string.stem": {
        "fragment_size": 100,
        "no_match_size": 100
      },
      "title$string.prefix": {
        "fragment_size": 100,
        "no_match_size": 100
      }
    }
  }
}
```

仔細看這部份的差異，主要就是以下這個：

![curation must promoted item](https://i.imgur.com/RntQr3y.png)

直接明確的透過 `terms query` 將 `external_id` 是 promoted 的這筆資料查出。

> 這邊雖然只是簡單的把一筆資料查出來，卻還是透過 Query 的方式來執行，我猜測有另個主要的目的就是同樣要使用 Highlighting 的機制，並且簡單的將最後的查詢結果能 combine 在一起。

### 執行其他文件的查詢 (不能包含 curated item)

這部份執行的目的，就是查詢出除了 promoted 的資料之外的資料。

```
{
  "from": 0,
  "size": 9,
  "timeout": "2000ms",
  "query": {
    "bool": {
      "must": [
        {
          "bool": {
            "must": [
              {
                "bool": {
                  "should": [
                    {
                      "multi_match": {
                        "query": "Mountain",
                        "fields": [
                          "description$string^1.0",
                          "description$string.delimiter^0.4",
                          "description$string.joined^0.75",
                          "description$string.prefix^0.1",
                          "description$string.stem^0.95",
                          "external_id^1.0",
                          "title$string^1.0",
                          "title$string.delimiter^0.4",
                          "title$string.joined^0.75",
                          "title$string.prefix^0.1",
                          "title$string.stem^0.95"
                        ],
                        "type": "cross_fields",
                        "operator": "OR",
                        "slop": 0,
                        "prefix_length": 0,
                        "max_expansions": 50,
                        "minimum_should_match": "1<-1 3<49%",
                        "zero_terms_query": "NONE",
                        "auto_generate_synonyms_phrase_query": true,
                        "fuzzy_transpositions": true,
                        "boost": 1
                      }
                    },
                    {
                      "constant_score": {
                        "filter": {
                          "multi_match": {
                            "query": "Mountain",
                            "fields": [
                              "description$string.intragram^0.1",
                              "external_id.intragram^0.1",
                              "title$string.intragram^0.1"
                            ],
                            "type": "best_fields",
                            "operator": "OR",
                            "slop": 0,
                            "prefix_length": 0,
                            "max_expansions": 50,
                            "minimum_should_match": "35%",
                            "zero_terms_query": "NONE",
                            "auto_generate_synonyms_phrase_query": true,
                            "fuzzy_transpositions": true,
                            "boost": 1
                          }
                        },
                        "boost": 0.1
                      }
                    }
                  ],
                  "adjust_pure_negative": true,
                  "boost": 1
                }
              }
            ],
            "adjust_pure_negative": true,
            "boost": 1
          }
        }
      ],
      "filter": [
        {
          "bool": {
            "must_not": [
              {
                "terms": {
                  "external_id": [
                    "yangming-mountain"
                  ],
                  "boost": 1
                }
              }
            ],
            "adjust_pure_negative": true,
            "boost": 1
          }
        }
      ],
      "adjust_pure_negative": true,
      "boost": 1
    }
  },
  "_source": {
    "includes": [
      "date$date",
      "visitors$float",
      "description$string",
      "external_id",
      "location$location",
      "title$string",
      "engine_id"
    ],
    "excludes": []
  },
  "sort": [
    {
      "_score": {
        "order": "desc"
      }
    },
    {
      "_doc": {
        "order": "desc"
      }
    }
  ],
  "highlight": {
    "fragment_size": 300,
    "number_of_fragments": 1,
    "type": "plain",
    "highlight_query": {
      "multi_match": {
        "query": "Mountain",
        "fields": [
          "description$string.prefix^1.0",
          "description$string.stem^1.0",
          "title$string.prefix^1.0",
          "title$string.stem^1.0"
        ],
        "type": "best_fields",
        "operator": "OR",
        "slop": 0,
        "prefix_length": 0,
        "max_expansions": 50,
        "zero_terms_query": "NONE",
        "auto_generate_synonyms_phrase_query": true,
        "fuzzy_transpositions": true,
        "boost": 1
      }
    },
    "order": "score",
    "require_field_match": false,
    "encoder": "html",
    "fields": {
      "description$string.stem": {
        "fragment_size": 100,
        "no_match_size": 100
      },
      "description$string.prefix": {
        "fragment_size": 100,
        "no_match_size": 100
      },
      "title$string.stem": {
        "fragment_size": 100,
        "no_match_size": 100
      },
      "title$string.prefix": {
        "fragment_size": 100,
        "no_match_size": 100
      }
    }
  }
}
```

從這邊產生出來的 Search Request ，可以發現主要是多增加了下面的這個 `filter - must_not` 的查詢。

![curation must\_not promoted item](https://i.imgur.com/9TKboTa.png)

也是明確的宣告不要包含這筆已經另外處理的 promoted item。

最後 App Search 會將這兩個各別查詢的結果合併在一起，這部份就是 App Search Curation 底下運作的方式。

## Relevance Tuning

而 Relevance Tuning 的調整，對於查詢方式的影響是什麼，我們這邊直接來進行的實驗，將 `title` 欄位的 boost 從 `1` 調高到 `3`。

![relevance tuning - setting](https://i.imgur.com/CuphhyI.png)

接下來我們同樣透過 Query Tester 來執行搜尋，可以看到針對 `mountain` 這個關鍵字的回傳結果依照調整 relevanc boost 之後有些不一樣了。

![relevance tuning - query tester](https://i.imgur.com/zZypFHN.png)

直接查看 slowlog 看看看這個查詢的 payload。

```
{
  "from": 0,
  "size": 10,
  "timeout": "2000ms",
  "query": {
    "bool": {
      "must": [
        {
          "bool": {
            "must": [
              {
                "bool": {
                  "should": [
                    {
                      "multi_match": {
                        "query": "mountain",
                        "fields": [
                          "description$string^1.0",
                          "description$string.delimiter^0.4",
                          "description$string.joined^0.75",
                          "description$string.prefix^0.1",
                          "description$string.stem^0.95",
                          "external_id^1.0",
                          "title$string^3.0",
                          "title$string.delimiter^1.2",
                          "title$string.joined^2.25",
                          "title$string.prefix^0.3",
                          "title$string.stem^2.85"
                        ],
                        "type": "cross_fields",
                        "operator": "OR",
                        "slop": 0,
                        "prefix_length": 0,
                        "max_expansions": 50,
                        "minimum_should_match": "1<-1 3<49%",
                        "zero_terms_query": "NONE",
                        "auto_generate_synonyms_phrase_query": true,
                        "fuzzy_transpositions": true,
                        "boost": 1
                      }
                    },
                    {
                      "constant_score": {
                        "filter": {
                          "multi_match": {
                            "query": "mountain",
                            "fields": [
                              "description$string.intragram^0.1",
                              "external_id.intragram^0.1"
                            ],
                            "type": "best_fields",
                            "operator": "OR",
                            "slop": 0,
                            "prefix_length": 0,
                            "max_expansions": 50,
                            "minimum_should_match": "35%",
                            "zero_terms_query": "NONE",
                            "auto_generate_synonyms_phrase_query": true,
                            "fuzzy_transpositions": true,
                            "boost": 1
                          }
                        },
                        "boost": 0.1
                      }
                    },
                    {
                      "constant_score": {
                        "filter": {
                          "multi_match": {
                            "query": "mountain",
                            "fields": [
                              "title$string.intragram^0.3"
                            ],
                            "type": "best_fields",
                            "operator": "OR",
                            "slop": 0,
                            "prefix_length": 0,
                            "max_expansions": 50,
                            "minimum_should_match": "35%",
                            "zero_terms_query": "NONE",
                            "auto_generate_synonyms_phrase_query": true,
                            "fuzzy_transpositions": true,
                            "boost": 1
                          }
                        },
                        "boost": 0.3
                      }
                    }
                  ],
                  "adjust_pure_negative": true,
                  "boost": 1
                }
              }
            ],
            "adjust_pure_negative": true,
            "boost": 1
          }
        }
      ],
      "adjust_pure_negative": true,
      "boost": 1
    }
  },
  "_source": {
    "includes": [
      "date$date",
      "visitors$float",
      "description$string",
      "external_id",
      "location$location",
      "title$string",
      "engine_id"
    ],
    "excludes": []
  },
  "sort": [
    {
      "_score": {
        "order": "desc"
      }
    },
    {
      "_doc": {
        "order": "desc"
      }
    }
  ],
  "highlight": {
    "fragment_size": 300,
    "number_of_fragments": 1,
    "type": "plain",
    "highlight_query": {
      "multi_match": {
        "query": "mountain",
        "fields": [
          "description$string.prefix^1.0",
          "description$string.stem^1.0",
          "title$string.prefix^1.0",
          "title$string.stem^1.0"
        ],
        "type": "best_fields",
        "operator": "OR",
        "slop": 0,
        "prefix_length": 0,
        "max_expansions": 50,
        "zero_terms_query": "NONE",
        "auto_generate_synonyms_phrase_query": true,
        "fuzzy_transpositions": true,
        "boost": 1
      }
    },
    "order": "score",
    "require_field_match": false,
    "encoder": "html",
    "fields": {
      "description$string.stem": {
        "fragment_size": 100,
        "no_match_size": 100
      },
      "description$string.prefix": {
        "fragment_size": 100,
        "no_match_size": 100
      },
      "title$string.stem": {
        "fragment_size": 100,
        "no_match_size": 100
      },
      "title$string.prefix": {
        "fragment_size": 100,
        "no_match_size": 100
      }
    }
  }
}
```

這邊可以看到，針對 `title` 的欄位權重變成 **3倍**，因此 `title` 相關的 boosting 的值，也都對應的變成原本的 **3倍**。

![relevance tuning - normal fields](https://i.imgur.com/b0vjAxS.png)

而 intragram 使用的 `constant_score` 也另外加了另一組 `boost: 0.3` 的查詢。

![relevance tuning - intragram field](https://i.imgur.com/gq1s2CZ.png)

所以在畫面上的 Relevance Tuning 調整，就是直接反應到 Search Request 組成時，每個 `fields` 的 boost 配置。

## 總結

從本篇文章的探索，可以發現 App Search 在實作 `Synonyms`, `Curations`, `Relevance Tuning` 的機制時，是如何使用 Elasticsearch，有些是使用 Elasticsearch 原本就提供的功能、有的是配合一些進階的使用方式，提高 Application 端的管理方便性，也有不一定都會使用到 Elasticsearch 的功能而直接在 Application 端處理掉，這種做法都是有為了達到的好處及對應的取捨，會是我們在使用 Elasticsearch 進行進階的產品搜尋功能開發時很好的參考。


# Elasticsearch 的優化技巧

使用 Elasticsearch 時，是否對於效能不滿意？對於硬體資源的成本想進一步優化？這個主題就帶大家來探討，最佳化 Elasticsearch 的各種技巧及注意事項。

* [(1/4) - Indexing 索引效能優化](/tech-sharing/uncle-joe-teach-es-elasticsearch/elasticsearch-de-you-hua-ji-qiao/indexing-suo-yin-xiao-neng-you-hua)
* [(2/4) - Searching 搜尋效能優化](/tech-sharing/uncle-joe-teach-es-elasticsearch/elasticsearch-de-you-hua-ji-qiao/searching-sou-xun-xiao-neng-you-hua)
* [(3/4) - Index 的儲存空間最佳化](/tech-sharing/uncle-joe-teach-es-elasticsearch/elasticsearch-de-you-hua-ji-qiao/index-de-chu-cun-kong-jian-zui-jia-hua)
* [(4/4) - Shard 的最佳化管理](/tech-sharing/uncle-joe-teach-es-elasticsearch/elasticsearch-de-you-hua-ji-qiao/shard-de-zui-jia-hua-guan-li)


# Indexing 索引效能優化

## 前言

這系列的文章主要的目的在於當我們開始使用 Elastic Stack 時，我們如何優化 Elasticsearch 的使用方式，包含 Indexing, Searching, Disk Usage, Shard Optimization 等四個主題。

### 進入此章節的先備知識

* 已經有在使用 Elasticsearch，並且了解 Elasticsearch 的基本原理與操作方式。

### 此章節的重點學習

* Indexing 的效能優化的各種技巧與建議。

***

## Indexing 索引效能優化

這篇文章主要提供 Indexing 的效能優化的各種技巧與建議：

* 調高你的 Shard 數量，與 Hot Nodes 數量相同
* Indexing 大量資料時，善用 bulk request
* 使用 multi-thread / multi-workers 來 indexing 資料進入 Elasticsearch
* 調低或暫時關閉 `refresh_interval`
* 指定 Routing 的方式，減少 Thread 的數量
* 第一批資料 indexing 進入 Elasticsearch 之前，先不要設定 Replica
* 關閉 java process swapping
* 確保 Filesystem 有足夠的 memory cache
* 使用 auto-generated ids
* 使用更快速的儲存硬體
* 調高 indexing buffer 大小
* 使用 cross-cluster replication 的配置讓 searching 的處理不會佔用 indexing 的資源
* 調整 Translog 的 Flush 設定，減少 Disk I/O

以下會分別針對這些優化項目進行說明。

### 調高你的 Shard 數量，與 Hot Nodes 數量相同

由於 Elasticsearch 在進行資料分散的基本單位是 Shard，而 Indexing 時要能承受大量的資料寫入時，就是同時善用多台硬體資源同時處理這些 Indexing 的請求，Shard 的數量過多會有額外的 overhead，因此最佳的 Shard 數量就是能夠負責處理 Indexing 請求的 Node 的數量，若是使用 Index Lifecycle Management 的規劃方式，這個數量就會是 Hot Nodes 的數量，這樣一來能讓 Elasticseach 平均的將這些 shard 分散在這些 Nodes 身上，並且透過 routing 的機制將大量的 indexing 請求分散到這些機器身上進行處理。

### Indexing 大量資料時，善用 bulk request

大量資料要 Indexing 時，使用 bulk 減少 round-trip overhead，至於 bulk request 要多大才是合適的？這個在不同的 Elasticsearch Cluster 硬體規格、不同的 Indexing 文件大小，所以還是要依照使用情境進行 Benchmark ，找到最合適的批次處理大小，另外官方有建議，bulk request 的資料量太大的話，大量的 bulk 請求同時進入 Elasticsearch 時，可能會吃光 Elasticsearch 的記憶體，所以一般不建議一個 bulk request 處理數十 MB 以上的資料。

### 使用 multi-thread / multi-workers 來 indexing 資料進入 Elasticsearch

使用 multi tread/worker 來處理 indexing 絕對比單一 thraed 能提高處理的效率，不過就像 bulk request 的調效一樣，這個會是依照硬體配置有不同的最佳化配置方式，所以同樣的會建議進行 Benchmark 來保確當下情境下合適的 thread 數量，並注意若是 Elasticsearch 丟出 `TOO_MANY_REQUESTS (429)` 的錯誤時，就已達到上線，應該要調整配置。

### 調低或暫時關閉 `refresh_interval`

在 Elasticsearch 的 Indexing 生命週期中，當不斷的有資料 indexing 進入 Elasticsearch 時，一開始是寫在 memory buffer 中的，而這時還無法被搜尋到 (如下圖)：

![image-20201012074401270](https://i.imgur.com/4ROgeQF.png)

當 Elasticsearch 透過 `refresh` 的機制，將 In-memory buffer 的整理成 segment file，這時的狀態就是能被搜尋到的 (下圖中灰色的那塊，因為還沒進行 `fsync` 所以還沒被 commit)。

![image-20201012074416347](https://i.imgur.com/Q4ZNovs.png)

Elasticsearch 預設的 `refresh_interval` 是 `1s` ，也就是一秒會進行一次處理，若是大量的資料在進行 indexing 時，可以將這個值調大，或甚至先暫時關閉，以提升 indexing 的效率。

> 這個配置的調整，有時能將 indexing 的效能提升一倍，不過一樣是依照情境會有不一樣的效果，但基本上都能有一定的成效。

這個設定的配置在 `index.refresh_interval` ，可參考 [官方文件 - Index Module](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#index-refresh-interval-setting)。

### 指定 Routing 的方式，減少 Thread 的數量

當我們在 indexing 資料進入 Elasticsearch 時，Elasticsearch 的 Routing 機制預設會讓資料平均分配在各個 Shard 身上，以下圖為例，可以想像左邊是我們的 Coordinator node，當我們有兩批資料要 bulk indexing 進入 ES，而在沒有特別指定 Routing 的機制，所以每個資料被分配到不同的 shard 身上，這個例子我們有 4 個 Shards，所以每一批的處理，都會要分配到 4 個 Shards，這時 bulk 的處理就會要 4 個 Threads 來處理各個 Shard 的工作，2 批 bulk request 也就是總共有 8 個 Threads。

![img](https://i.imgur.com/MneJkIR.png)

如果我們指定第一批的資料只會 Routing 到 Shard 1, 2，而第二批的資料只會 Routing 到 3, 4，這樣 bullk 在處理時，就只會用到 2 個 Threads來進行這樣的操作。(如下圖)

![img](https://i.imgur.com/saAjVqk.png)

可以看到這種例子時，Threads 數量就減少一半了，這部份的配置方式，請參考 [官方文件 - Routing](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-routing-field.html)。

### 第一批資料 indexing 進入 Elasticsearch 之前，先不要設定 Replica

建議如果是一次性的 Indexing 一批資料進入 Elasticsearch，先將 `index.number_of_replicas` 設成 `0`，在我們 Indexing 資料時， 讓 Elasticseach 把資源用在處理 Indexing ，而不要在這個階段就去進行 Replica 的處理，等到資料都 indexing 完成之後，再把這個配置改回我們原先的預期配置，再讓 Elasticseach 進行 replication 的處理。

### 關閉 java process swapping

為了避免 JVM heap 被 swap 到 disk，而降低 Elasticsearch 的處理效率，這邊建議關閉 swapping，這部份請直接參考 [官方文件 - Disable swapping](https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration-memory.html)。

### 確保 Filesystem 有足夠的 memory cache

Elasticsearch 使用時，由於使用 Lucene 進行許多 Segment files 的處理，會需要用到大量 filesystem 的 memory buffer，因此官方的配置建議上，會建議 JVM Heap size v.s OS filesystem 各配置 50% 的記憶體大小，因此請確保 Filesystem 擁有足夠的記憶體來處理 indexing 的 request。

### 使用 auto-generated ids

Elasticsearch 在進行 Indexing 的處理時，會檢查文件的 id 是否已存在於目前的 shard 當中，如果是使用 Elasticsearch auto-generated ids 時，由於這個 Id 的組成有包含的時間，所以 Elasticsearch 可以確保他產生時不會與現存的資料有重覆的情況，因此可以省略這個 id checking 的機制，這樣會讓 indexing 的速度有所提升。

### 使用更快速的儲存硬體

Indexing 的處理是屬於 I/O bound，在官方的建議配置上，會建議基本上要使用 SSD 等級的硬碟來當成 Elasticsearch 的儲存硬體規格，而且使用 SSD 的配置，會讓整體的 C/P 值會較高。

若是因資料量太大而有成本的考量，應該進一步再使用 Index Lifecycle Management 將 Indexing 完成的資料、或是較舊的資料，轉移到較便宜的磁碟硬體狀置上。

### 調高 indexing buffer 大小

如果有大量的 indexing 的處理時，適時的調高 [`indices.memory.index_buffer_size`](https://www.elastic.co/guide/en/elasticsearch/reference/current/indexing-buffer.html)，這個值預設是 `10%` 的 JVM head size，這個 index buffer 是所有 active shard 共用的，所以若是有大量 indexing 處理時，也就會互相佔用這個空間，所以確保 indexing 時影響的 shard 數量，並且配置足夠的 index buffer size。

> 這個配置官方的建議是一個 shard 在 **512MB** 之內，若是超過的話效能一般不會有太明顯的改善。

### 使用 cross-cluster replication 的配置讓 searching 的處理不會佔用 indexing 的資源

如果是持續不斷的大量 indexing 資料進入 Elasticsearch 中，可以考慮使用 multi-cluster 的架構，將 indexing 的處理指向一個 Elasticsearch Cluster - A，而透過 cross-cluster replication 將資料 replica 到另一個 Elasticsearch Cluster - B，所有的 search 就指向 Elasticsearch Cluster - B，讓 searching 的各種請求處理，不會佔用到 indexing 的處理資源，確保 indexing 有獨立不受影響的資源配置。

### 調整 Translog 的 Flush 設定，減少 Disk I/O

Elasticsearch 因為避免 process crash 時資料的遺失，會預設在每 `5秒鐘` 、或是 memory size 達到 `512mb` …等條件達到時執行 fsync，而這個處理的 Disk I/O 成本較高，因此在大量 indexing 資料時，而且這時期又允許接受系統 crash 時資料遺失的風險，可以將 `index.translog.interval` 和 `index.translog.flush_threshold_size` 的配置調高，以提升 indexing 的效率。

## 參考資料

* [官方文件 - Tune for indexing speed](https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html)
* [Scaling Elasticsearch Part 1: How to Speed Up Indexing](https://dev.to/molly_struve/scaling-elasticsearch-part-1-how-to-speed-up-indexing-2pel)


# Searching 搜尋效能優化

## 前言

這系列的文章主要的目的在於當我們開始使用 Elastic Stack 時，我們如何優化 Elasticsearch 的使用方式，包含 Indexing, Searching, Disk Usage, Shard Optimization 等四個主題，這篇以是 Searching 為主的介紹。

### 進入此章節的先備知識

* 已經有在使用 Elasticsearch，並且了解 Elasticsearch 的基本原理與操作方式。

### 此章節的重點學習

* Searching 的效能優化的各種技巧與建議。

***

## Searching 搜尋效能優化

這篇文章主要提供 Searching 的效能優化的各種技巧與建議：

* 與相關性計分無關的 Query，都使用 Filter 來處理
* 確保 Filesystem 有足夠的 memory cache
* 使用更快速的儲存硬體
* Document modeled
* 搜尋的欄位愈少愈好
* 依照 Aggregation 的需求 Pre-index 資料
* 盡量使用 `keyword` 來當作 identifiers 的型態
* Scripts 是昂貴的，應該盡量少用
* 使用日期時間當搜尋條件時，可以取整點，增加 Cache 利用率
* 將 filter 條件切割來提高 Cache 利用率
* 將不會再寫入的 Index 進行 Force-merge
* 將常會使用到 Terms Aggregations 的欄位，設定成 Eager Global Ordinals
* 預熱 filesystem cache
* 使用 index sorting 的設定，來加速 conjunctions 的搜尋
* 使用 `preference` 控制 `searching` request 的 routing 來增加 cache 使用率
* Replica 數量愈多不見得對搜尋愈有幫助
* 管理好使用 Elasticsearch 的方式，不要讓使用者擁有太大的彈性
* 使用 `Profile API` 來優化 Search Request
* 在 query 或 aggregation 處理需求量較高的環境中，安排特定的 Coordinating Node

以下會分別針對這些優化項目進行說明。

### 與相關性計分無關的 Query，都使用 Filter 來處理

因為 Filter 的處理不需要去計算 **相關性計分**，所以他的處理會比較快，也因此他的結果是適合被 cache 的，Elasticsearch 也就只會 cache filter 的結果，不會 cache 其他有相關性計分的 query，所以結論就是：預設請使用 filter，只有和相關性計分有關的查詢，才使用 query。

這邊有一點要注意，Query 的 cache 是以 Segment File 為單位，由於 Segment File merge 時會導致 cache 失效，所以 Elasticsearch 預設會檢查 Segment File 裡面至少要包含 10,000 筆資料，並且要擁有超過 3% 以上的 index 的文件數量，才會對這個 segment file 產生 cache，所以並不是所有透過 filter 查詢的結果都會被 cache。

### 確保 Filesystem 有足夠的 memory cache

和 Indexing 時的建議一樣，Elasticsearch 使用時，由於使用 Lucene 進行許多 Segment files 的處理，會需要用到大量 file system 的 memory buffer，因此官方的配置建議上，會建議 JVM Heap size v.s OS filesystem 各配置 50% 的記憶體大小，因此請確保 Filesystem 擁有足夠的記憶體來替較常被使用的資料進行快取。

### 使用更快速的儲存硬體

Search 的處理有可能是 I/O bound 或是 CPU bound，如果你的 Search 是屬於 I/O bound，在官方的建議配置上，會建議基本上要使用 SSD 等級的硬碟來當成 Elasticsearch 的儲存硬體規格，而且使用 SSD 的配置，會讓整體的 C/P 值會較高。

若是你的 Search 是屬於 CPU bound，則應該將 node 配置較高等級的 CPU。

若是因資料量太大而有成本的考量，應該進一步再使用 Index Lifecycle Management 將 Indexing 完成的資料、或是較舊的資料，轉移到較便宜的磁碟硬體狀置上。

### Document modeled

儘量將你的 Document 在 Indexing 進入 Elasticsearch 時，就規劃成是 **針對 Searching 優化的結構**。

例如：避免使用 `join`、`nested` 的資料型態配合 `nested query` 會讓查詢速度慢好幾倍、`parent-child` 會讓查詢速度慢好幾百倍、`fuzzy`、`regex`…等查詢的效能也是非常的慢，所以若是能事先去正規劃、enrich raw log、透過 `ngram` 、 `bigram` 、 `shingle` …等各種 Analysis 套用在 multiple fields 中，能讓 `searching` 階段的處理盡量簡化，並且能達到同樣的效果，這樣搜尋速度會有非常明顯的改善。

### 搜尋的欄位愈少愈好

如果有使用 `query_string` 或 `multi_match` 這類查詢來同時查詢多個欄位時，優化的方式是使用 `copy_to` 在 `indexing` 時期就將這些會同時查詢的欄位合併到一個欄位中，並且 `searching` 時直接針對這個欄位進行搜尋，減少搜尋時的欄位數量，也會優化查詢的效率。

### 依照 Aggregation 的需求 Pre-index 資料

如果你的搜尋應用上常會針對一個欄位進行 `range` aggregation，而且都是一些固定的區間，例如：

```
PUT index/_doc/1
{
  "designation": "spoon",
  "price": 13
}

GET index/_search
{
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 10 },
          { "from": 10, "to": 100 },
          { "from": 100 }
        ]
      }
    }
  }
}
```

針對這種例子， **pre-index** 指的就可以將資料在 `indexing` 時，多增加一個欄位並使用 `keyword` 來存放這個分類的結果，如下：

```
PUT index
{
  "mappings": {
    "properties": {
      "price_range": {
        "type": "keyword"
      }
    }
  }
}

PUT index/_doc/1
{
  "designation": "spoon",
  "price": 13,
  "price_range": "10-100"
}
```

之後在使用時，就可以直接用這個欄位來進行 aggregation。

```
GET index/_search
{
  "aggs": {
    "price_ranges": {
      "terms": {
        "field": "price_range"
      }
    }
  }
}
```

這樣也能有效的提升 aggregation 的效率。

### 盡量使用 `keyword` 來當作 identifiers 的型態

不是所有的數值型態的資料都應該使用 `numeric` 的 data type。

Elasticsearch 針對 `numeric` 型態的欄位特別著重優化 `range` query 或 aggregation，而針對 `keyword` 欄位，會特別優化 `term` 或其他 `term-level` 相關的查詢。

因此如果你的 identifier 這類的資料是數值的型態，而且不需要進行 `range` query，那你應該考慮把他定義成 `keyword` 的型態。

> 如果你不確定會如何使用的話，就用 `multi-field` 把 `keyword` 和 `numeric` 都定義起來，也就是用空間換時間的方式，至少在 `searching` 階段能使用最合適的方式來進行最有效率的搜尋。

### Scripts 是昂貴的，應該盡量少用

不論是 scripts query 或是 scripted fields ，因為使用到 `script` 時，就沒辦法使用 Elasticsearch 的 index structure 或是相關的優化機制，所以如果 scripts 使用到的這些規則，若是能在 `indexing` 時期就先把資料預先算好並準備好，這樣也能有效的增加搜尋的效率。

### 使用日期時間當搜尋條件時，可以取整點，增加 Cache 利用率

這邊的原理，是因為 filter 的 cache 機制會依照 filter 的查詢條件來當成 cache key，一但 filter 的條件改變，這個 cache 自然就不會被 hit，以下面為例：

```
PUT index/_doc/1
{
  "my_date": "2016-05-11T16:30:55.328Z"
}

GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "my_date": {
            "gte": "now-1h",
            "lte": "now"
          }
        }
      }
    }
  }
}
```

如果我們現在的時間是 `16:31:29` ，我們進行了一次 search，過了一秒之後， `16:31:29` 這時再進行一次 search，如果第一次有產生 cache 的話，其實第二次的 filter 是無法利用到第一次的 cache 的，因為時間已經不同了，反之若是使用 rounded date，也就是直接 `/m` 取到分鐘為顆粒度的整數。

```
GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "my_date": {
            "gte": "now-1h/m",
            "lte": "now/m"
          }
        }
      }
    }
  }
}
```

以同樣上述的時間，產生出來的結果就會都是 `16:31:00` ，這樣就能提上 cache hit rate，而這個顆粒度也就取決於應用端可以接受的情境。

### 將 filter 條件切割來提高 Cache 利用率

如果我們的使用情境上有許多顆粒度很細、也就是 Cache hit rate 很低的查詢，例如我們總是要查詢 **最近一小時的資料** ，而且又想要愈即時、也就是顆粒度要很細的 filter，這個一般的查詢方式可能如下：

```
GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "my_date": {
            "gte": "now-1h",
            "lte": "now/"
          }
        }
      }
    }
  }
}
```

可以想像這個 cache hit rate 應該會極低，所以我們可以把這段時間切成三塊，讓其中一大塊的 cache rate 提高，並讓沒辦法 cache 的部份切成小塊而捨棄他的 cache 使用率：

```
GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "bool": {
          "should": [
            {
              "range": {
                "my_date": {
                  "gte": "now-1h",
                  "lte": "now-1h/m"
                }
              }
            },
            {
              "range": {
                "my_date": {
                  "gt": "now-1h/m",
                  "lt": "now/m"
                }
              }
            },
            {
              "range": {
                "my_date": {
                  "gte": "now/m",
                  "lte": "now"
                }
              }
            }
          ]
        }
      }
    }
  }
}
```

這樣三塊分別是：

* 一個很精確的開始時間 \~ 開始時間之後的分鐘整點時間： `now-1h` \~ `now-1h/m`
* 開始時間之後的分鐘整點時間 \~ 最接近目前時間的分鐘整點時間：`now-1h/m` \~ `now/m`
* 最接近目前時間的分鐘整點時間 \~ 目前的時間：`now/m` \~ `now`

這種方式有利有弊，好處是讓中間那段有用分鐘整點來切齊的 cache hit rate 提高，但缺點是將 filter 切成三份，還是會增加一些 overhead，這部份就要依實際的使用情況來評估與調整了。

### 將不會再寫入的 Index 進行 Force-merge

如果是不會再寫入資料的 Index，例如 time-based indices 如果是已經 rotated，那麼不會再被寫入的 Index 應該要進行 segment files 的 force-merge，並且強制 merge 成只剩下一個 segment file，這樣也會提升搜尋的效率。

> 一般情況請不要針對一個還會持續寫入的 index 進行 force-merge ，這樣有可能會讓 performance 更差。

### 將常會使用到 Terms Aggregations 的欄位，設定成 Eager Global Ordinals

Global ordinals 是執行 terms aggregation 時會使用到的資料結構，預設是 lazy loading，因為 Elasticsearch 不知道你會針對哪些 keyword 欄位執行 terms aggregation。因此如果你有某個欄位明確的會頻繁執行 terms aggregation，可以進行以下的設定，將 `eager_global_oridnals` 設成 `true` ：

```
PUT index
{
  "mappings": {
    "properties": {
      "foo": {
        "type": "keyword",
        "eager_global_ordinals": true
      }
    }
  }
}
```

### 預熱 filesystem cache

可以使用 [`index.store.preload`](https://www.elastic.co/guide/en/elasticsearch/reference/current/preload-data-to-file-system-cache.html) 來告訴 OS 在 shard 起動時，要先將哪些 index 預先載入進 memory cache 中，另外載入的設定有以下幾種：

* `nvd` ：norms
* `dvd` ：doc values
* `tim` ：terms dictionaries
* `doc` ：postings lists
* `dim` ：points

例如：

```
PUT /my-index-000001
{
  "settings": {
    "index.store.preload": ["nvd", "dvd"]
  }
}
```

> **注意：** 若是預先載入太多的 indices 而導致 filesystem cache 不夠大來處理這些 indices 的話，`searching` 的效率是會下降的。

### 使用 index sorting 的設定，來加速 conjunctions 的搜尋

conjunctions 搜尋指的像是： `a AND b AND c ...` 這樣的搜尋，由於 conjunctions query 時的處理方式，是會將這些查詢條件，一個個去比對哪些文件"不符合條件"，若是一遇到不符合，就會跳過這個條件的文件，進行下一個條件的找尋。

宣告 Index sorting 的目的，主要是可以讓 符合 與 不符合 的文件先排在一起，不論是 `asc` 或 `desc` 都沒關係，只要讓他們先排在一起，一但使用 conjunctions 搜尋時，有某一個文件的條件不符合時，就能以較快的速度直接跳過這些不符合的文件，進入下一個條件的比較。

> 這邊要注意，這種小技巧只有對於資料內容差異較小的會較有效，也就是重覆的資料愈多愈有效。

### 使用 `preference` 控制 `searching` request 的 routing 來增加 cache 使用率

我們有 filesystem cache, request cache, query cache 等這些 cache 能優化 searching 的效能，不過這些都是 Node level 的 cache，如果我們有多份的 replica，同樣的 search request 若是導到不台，自然就沒辦法利用到另一台的 cache。

所以這邊的優化方式，是依照使用的情境，例如同一個使用者搜尋資料時的查詢條件應該會比較接近、或是相同地區的查詢條件會比較接近…等，我們就可以使用 `preference` 設定為 user id, session id, 甚至是 region id，來讓同樣的使用者或地區，能導到相同的 node，以增加 cache hit rate。

### Replica 數量愈多不見得對搜尋愈有幫助

replica 的數量還是要參考 primary shard 與 node 的數量來一併考量，如果 node 數量 4 個，primary shard 數量也是 4 個，並且 replica 是 0，這時 1 個 node 放 1 個 shard 的資料，這時 filesystem cache 的機制是最好的，如果 replica 設成 1，每一份 shard 都會有一份額外的 replica ，但這時 replica shard 也會佔用到 filesystem cache。

不過 replica 的數量另外一個最重要的目的是 availability，所以這會需要一併考慮，官方有個簡單的公式可做參考：

```
max(max_failures, ceil(num_nodes / num_primaries) - 1)
```

* `max_failures`: 代表 availability，也就是最多同一時間有多少 node 一起壞掉時資料還是需要保留完整性。
* `num_nodes`: cluster node 數量。
* `num_primaries`: cluster 中，所有 primary shard 的數量。

### 管理好使用 Elasticsearch 的方式，不要讓使用者擁有太大的彈性

先前上課時最喜歡舉一個例子，如果大家使用過 Kibana ，可能會有過類似的經驗，在看 dashboard 時，調整時間時一不小心時間拉太長，拉到近1年，整個查詢就要等很久很久，最慘的是有可能把 cluster 的資源耗盡，又或著是我們提供給使用者的是 search box 讓使用者自己輸入 query\_string 的字串，結果使用者輸了個超級複雜的查詢條件…

以上的例子都是我們開放讓使用者產生一些我們無法事先管理的 `searching request`，而這些 requests 是非常耗資源的，而甚至會影響到其他正常使用的狀況，這部份就會是應該要有良好的控管，確認我們適合提供的查詢方式，例如：

* 使用 alias + filter，限制只能查詢最近一段時間的資料，這可搭配 RBAC 來綁定在使用者的帳號上。
* 使用較有侷限的 UI 設計，讓使用者能產生的 `searching request` 都是在我們的掌握之中。

### 使用 `Profile API` 來優化 Search Request

可以使用 [Profile API](https://www.elastic.co/guide/en/elasticsearch/reference/7.9/search-profile.html) 或是 Kibana Dev tools 的 **Search Profiler**，針對 search 底下運作的方式進行分析，也就可以針對某一個執行時間較長的查詢進行剖析或是調整。

![Query Profiler Visualization](https://i.imgur.com/7WRsPCQ.png)

### 在 query 或 aggregation 處理需求量較高的環境中，安排特定的 Coordinating Node

Corrdinator 在處理 aggregation 或是包含較複雜 sorting 處理的 query 時，會需要使用到較大量的 memory，因此將 Node 的身份進行有效的管理與分工，讓處理大量搜尋請求的任務，由專門的 Coordinating Node，有獨立的 memory 與系統資源，讓 Data node 在執行 searching 時，減少系統資源被其他處理佔用的情況。

> 也可以考慮將 Ingest Node 等專門的任務也都與 data node 中獨立出來，以確保處理 search request 的 node 的系統資源不會被其他處理佔用。

## 參考資料

* [官方文件 - Turn for search speed](https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-search-speed.html)
* [Scaling Elasticsearch Part 2: How to Speed Up Search](https://dev.to/molly_struve/scaling-elasticsearch-part-2-how-to-speed-up-search-53of)


# Index 的儲存空間最佳化

## 前言

這系列的文章主要的目的在於當我們開始使用 Elastic Stack 時，我們如何優化 Elasticsearch 的使用方式，包含 Indexing, Searching, Disk Usage, Shard Optimization 等四個主題，這篇以是 Disk Usage 為主的介紹。

### 進入此章節的先備知識

* 已經有在使用 Elasticsearch，並且了解 Elasticsearch 的基本原理與操作方式。

### 此章節的重點學習

* Elasticsearch Cluster 的管理時，針對儲存空間進行優化的各種技巧。

***

## Index 的儲存空間最佳化

儲存空間的利率反應的就是 **錢**，有效的管理資料儲存的方式，有可能可以省下超過一半以上的儲存成本，以下會介紹各種 Elasticsearch Cluster 儲存空間最佳化的技巧：

* 優化 Mapping 的設定 - disable 不需要被搜尋的欄位
* 優化 Mapping 的設定 - 不需計算 score 的欄位可以關掉 `norms`
* 優化 Mapping 的設定 - 調整 `index_options` 到需要的層級即可
* 優化 Mapping 的設定 - 避免使用預設的 dynamic string mapping
* 優化 Mapping 的設定 - 減少 `_source` 裡儲存的資料
* 優化 Mapping 的設定 - 設置 `best_compression` 來提升資料壓縮率
* 優化 Mapping 的設定 - 使用較省空間的資料型態
* 減少 Shard 的數量，增加 Shard 的大小
* 透過 Force Merge 來減少 Segment files 數量太多所佔用的空間
* 將相似的文件透過 index sorting 排在一起以提升壓縮率
* 讓 Document 的欄位順序保持一致以提升壓縮率
* 使用 Rollup 的機制，將歷史資料的儲存顆粒度提升
* 使用 Hot Warm Cold Architecture 來分配合適的儲存硬體

以下會分別針對這些優化項目進行說明。

### 優化 Mapping 的設定 - disable 不需要被搜尋的欄位

```
PUT index
{
  "mappings": {
    "properties": {
      "foo": {
        "type": "integer",
        "index": false
      }
    }
  }
}
```

不論是 `text`, `keyword`, `integer` 等各種型態的欄位，如果這個欄位已經明確是不需要使用 query 的，這時可以把 index 設成 `false` ，這樣 **會節省不少的空間**。

> `index: false` 的欄位，雖然不能用在 query ，但是還是可以使用 aggregation。

### 優化 Mapping 的設定 - 不需計算 score 的欄位可以關掉 `norms`

```
PUT index
{
  "mappings": {
    "properties": {
      "foo": {
        "type": "text",
        "norms": false
      }
    }
  }
}
```

`norms` 是用來處理 query 時，儲存相關性計分所需要使用到的資訊，基本上會一個文件的一個 mapping 宣告的欄位就會佔用 1個 byte (就算某件文件沒有這個欄位還是會佔用)，所以欄位數量多、資料筆數多時，這個儲存空間會很大。( [官方文件 - norms](https://www.elastic.co/guide/en/elasticsearch/reference/current/norms.html) )

所以如果這個欄位 **需要被搜尋，但是不用考慮相關性計分** 時，請把 `norms` 宣告成 `false` ，以節省儲存空間。

> `norms` 的設定可以使用 PUT mapping API 動態的關閉，但是關閉後就像是 Delete 文件一樣，並不會馬上省節空間，會等到下一次 Segment Files merge 時，才會真正節省到磁碟空間。

### 優化 Mapping 的設定 - 調整 `index_options` 到需要的層級即可

`index_options` 是用來控制 inverted index 中要存放哪些資訊的設置，這些資訊會影響到 **相關性計分計** 在計算時參考的資訊，或是某些功能能不能使用，例如 highlighting, proximity or phrase query 。( [官方文件 - index\_options](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-options.html) )

```
PUT index
{
  "mappings": {
    "properties": {
      "foo": {
        "type": "text",
        "index_options": "freqs"
      }
    }
  }
}
```

例如 `freqs` 記錄的是詞頻，是相關性計分在計算是會使用到的，而 `positions` 記錄的是 term 的位置，是 phrase query 需要用的，如果我們確認這個欄位不會需要使用 phrase query，我們可以將他設為 `freqs` ，以減少記錄 `positions` 所使用的空間。

### 優化 Mapping 的設定 - 避免使用預設的 dynamic string mapping

Elasticsearch 針對 `string` 型態的欄位，預設的 dynamic mapping 規則如下：

```
{
  "string_fields": {
    "mapping": {
      "norms": false,
      "type": "text",
      "fields": {
        "keyword": {
          "ignore_above": 256,
          "type": "keyword"
        }
      }
    },
    "match_mapping_type": "string",
    "match": "*"
  }
}
```

也就是只要被判定是 `string` 型態的欄位，除了主要的型態是 `text` 之外，還會建立一個 field 並且指定型態為 `keyword`。

```
  "my_string_field": {
    "type": "text",
    "fields": {
      "keyword": {
        "type": "keyword",
        "ignore_above": 256
      }
    }
  }
```

這樣會產生並儲存兩種不同的 analyzed 結果。

如果很明確知道使用的情境，應該明確的定義好資料的型態及處理的方式，又或著是大部份的欄位不需要進行 text search，只有特定的的欄位才需要，也可以設定 dynamic template，將預設的 `string` 型態先指定成 `keyword` 的資料型態。

```
PUT index
{
  "mappings": {
    "dynamic_templates": [
      {
        "strings": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "keyword"
          }
        }
      }
    ]
  }
}
```

### 優化 Mapping 的設定 - 減少 `_source` 裡儲存的資料

`_source` 儲的是文件的 JSON 原始資料，如果不需要存取原始資料，例如使用 Elasticsearch 對 description 進行搜尋，但搜尋的結果不用回傳 description，只要回傳 document id 或是 titile 等其他欄位，就可以把這些不需要回傳的欄位，從 `_source` 中排除，甚至可以將整份文件的 `_source` 都關閉。

> 這邊要注意，若是沒有存 `_source` ，會無法再使用 **reindex** 或是 **\_update** 的操作。

### 優化 Mapping 的設定 - 設置 `best_compression` 來提升資料壓縮率

`_source` 和 `stored fields` 的儲存資料，可以透過 `index.codec` 的設定來調整資料壓縮的方式，設定成 `best_compression` 可以提高壓縮率，當然成本就是像 `stored fiels` 的處理的效能就會慢一點。 ( [官方文件 - codec](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#index-codec) )

### 優化 Mapping 的設定 - 使用較省空間的資料型態

如果你的資料是整數，而且已經確定資料的範圍大小，應該明確的使用 `byte`, `short`, `integer`, `long`, `unsign_long` ，而不要直接用預設的 dynamic mapping 一律判斷成 `long` 。

同樣的如果是有小數的數值，也應該明確的指定 `half_float`, `float`, `double`, `scaled_float`，而不要直接用預設的 dynamic mapping 一律判斷成 `float` 。

### 減少 Shard 的數量，增加 Shard 的大小

較大的 Shard 對於資料的儲存與查詢效率愈高，要優化這部份的做法有以下幾種：

* 建立 Index 時，就指定數量較少的 primary shard。
* time-series data 在使用 Rollover API 時，建立的 index 數量也要注意不要太多，造成這份資料的總 shard 數量太多。
* 可以搭配 Shrink API 當 indexing 處理完成後，減少 primary shard 的數量。

> 雖然應考慮讓 Shard 數量變少、Size 變大，但 Shard 的數量規劃，還是要注意以下兩點：
>
> 1. 單一 shard 愈大，會讓 cluster rebalancing 時成本較高。
> 2. shard 數量太少，也會限制資料被分散處理的能力。

### 透過 Force Merge 來減少 Segment files 數量太多所佔用的空間

Segment files 的單檔愈大、總數愈少，會對於空間的使用率愈好，甚至像是已刪除的文件，會是透過 **標示為刪除** 的方式來處理，並且會等到 segment files merge 時才會真正的移除，因此當 index 被 rollover 之後，或是不再需要被寫入時，應該將 index 進行 segment files 的 merge，而且可以透過 [\_forcemerge API](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html) 指定 `max_num_segments=1` ，只保留一份 segment file 即可。

### 將相似的文件透過 index sorting 排在一起以提升壓縮率

Elasticsearch 的 `_source` 在保存時，會將一匹文件一起進行壓縮以提升壓縮率，一般使用 Elasticsearch 的情境，像是用來處理 Logs，有蠻多文件其實是有不少欄位是一樣的，因此透過適當的 `index sorting` 設置，讓資料在 Indexing 時就會依照這個規則來排序，除了能提升搜尋時的效率，也可以提高資料保存時的壓縮率進而節省儲存空間。( [官方文件 - Index Sorting](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-index-sorting.html) )

### 讓 Document 的欄位順序保持一致以提升壓縮率

這部份和上一段的原理是一樣的，Elasticsearch 的 `_source` 在保存時，會將一匹文件一起進行壓縮以提升壓縮率，而 JSON 文件的欄位若是會不同的話，就算裡面的值是一樣，但因為 indexing 時欄位的排序不同，會造成這些相似的字串反而是一段一段的散落在不同的位置，也會減少壓縮率，不如排序相同的文件，讓相似的字串有機會比較長的合在一起。

例如：

```
{"a":123,"b":"bbb","c":333,"d":456}
{"a":123,"b":"bbb","c":222,"d":456}
```

這樣的相似字串就會是 `{"a":123,"b":"bbb","c":` 和 `,"d":456}`

而若欄位沒有照一樣的順序：

```
{"a":123,"b":"bbb","c":333,"d":456}
{"b":"bbb","a":123,"c":222,"d":456}
```

這樣的相似字串就會是 `"a":123,`, `"b":"bbb",` `"c":` 和 `,"d":456}` ，而且位置也會比較零散，這樣的壓縮率就會較不好。

### 使用 Rollup 的機制，將歷史資料的儲存顆粒度提升

Rollup 的概念是將原本一筆一筆的資料，透過 aggreate 的方式，以某種顆粒度較大的方式，將資料彙總起來，例如把資料依每分鐘存一筆，並且只保留這分鐘的平均值、加總、最大值、最小值…等彙總資訊，不僅讓檢示這樣的資料的時速度變快，也能將太細顆粒的資料、或是原始資料，隨著時間從 Elasticsearch 中刪除，減少佔用的儲存空間。

詳細的介紹可參考先前的文章 [喬叔教 Elastic - 12 - 管理 Index 的 Best Practice (4/7) - Rollup](https://ithelp.ithome.com.tw/articles/10245259) 。

### 使用 Hot Warm Cold Architecture 來分配合適的儲存硬體

儲存空間的利用，除了節省空間之外，不同的資料配置不同等級的儲存硬體也是一項重要的資源利用管理方式。

透過不同等級的 storage，例如：

* **Hot Data Node** 使用 最高等級的 SSD。
* **Warm Data Node** 使用 次等級的 SSD。
* **Cold Data Node** 使用 HDD。

並且將資料妥善的依照 Hot, Warm ,Cold 的分類方式進行管理，以達到儲存硬體的最佳配置。

詳細的介紹可參考先前的文章 [喬叔教 Elastic - 10 - 管理 Index 的 Best Practices (2/7) - 三溫暖架構 - Hot Warm Cold Architecture](https://ithelp.ithome.com.tw/articles/10243650)

## 參考資料

* [官方文件 - Tune for disk usage](https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-disk-usage.html)
* [官方文件 - index\_options](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-options.html)
* [官方文件 - codec](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#index-codec)
* [官方文件 - forcemerge API](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html)
* [官方文件 - Index Sorting](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-index-sorting.html)


# Shard 的最佳化管理

## 前言

這系列的文章主要的目的在於當我們開始使用 Elastic Stack 時，我們如何優化 Elasticsearch 的使用方式，包含 Indexing, Searching, Disk Usage, Shard Optimization 等四個主題，這篇以是 Shard Optimization 為主的介紹。

### 進入此章節的先備知識

* 已經有在使用 Elasticsearch，並且了解 Elasticsearch 的基本原理與操作方式。

### 此章節的重點學習

* 如何規劃屬於你的 Sharding Strategy 來管理 Elasticsearch Cluster 中的 Shards。
* 當 Elasticsearch Cluster 已經 oversharding 了，要如何修正。

***

## Shard 的最佳化管理方式 - Sharding Strategy

為了避免機器毀損時造成資料的遺失以及提升 indexing 或是 searching 的資料處理能力，Elasticsearch Cluster 在多個 nodes 並包含多個 shards 的分散式資料架構中，儲存 index 資料和這些資料的 replica，但是這些 shards 的數量，以及 replica 的數量，對於 cluster 的健康狀態及效能其實有很大的影響，最常遇到的一個問題就是 `oversharding` (過多的分片)，而這種 shard 數量太多的狀況可能會影響到 cluster 的穩定性，因此在管理 Elasticsearch Cluster 時，最好先了解你的資料，並規劃好 **Sharding Strategy** ，以下是規劃時建議要考量的項目。

* Search 在執行時，一個 Shard 會分配一個 Thread，數量太多反而會拖慢速度
* 每個 Shard 都有基本運作的成本，數量太多成本愈高
* 指定 Shard 在 Cluster 中的分配方式，以優化儲存硬體的資源
* 刪除整批資料時，以 Index 為單位來刪除，而不要從一批 Documents 來刪除
* 建議使用 Data Stream 或 Index Lifecycle Management (ILM) 來管理 time series 資料
* 一般會建議讓單一 Shard 的大小在 10 \~ 50 GB 左右
* 1GB 的 Heap Memory 大約能處理 20 個 Shards
* 一個 Index 有多個 Shard 時，避免大多數的 Shard 都被分配在同一台 Node 身上

底下分別是這些建議的個別說明。

### Search 在執行時，一個 Shard 會分配一個 Thread，數量太多反而會拖慢速度

大多數的 searching 在執行時，查詢的資料是會橫跨多個 shards，而每個 shard 在搜尋時會使用一個 CPU thread 在處理執行，也就是：

* 如果有多個 shard，可以讓查詢的處理並發在多個 shard 身上同時進行，以增加處理的效率。
* shard 數量愈多，node thread pool 的消耗也愈多，因此 thread pool 數量不足的話，反而會影響查詢的執行效率。

> 一個 Node 依照不同的功能分類有多種 Thread pool， 其中 **search** (count/search/suggest) 的 size 是 `int((# of allocated processors * 3) / 2) + 1` 並且 queue\_size 預設是 `1000` ，而 **write** (index/delete/update/bulk) 的 size 是 `# of allocated processors` ，預設的 queue\_size 也是 `1000`。( [官方文件 - Thread pools](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-threadpool.html) )

### 每個 Shard 都有基本運作的成本，數量太多成本愈高

每個 Shard 固定都會佔用一些 CPU 和 Memory 的資源，以同樣的資料量，Shard 數量愈多，overhead 也就愈高，佔用的總系統資源也會較多一些。

Shard 的另外的資源成本有一部份會是裡面的 sgement files，segment files 的 metadata 會被存放在 JVM heap memory 中，以提供搜尋時處理的加速，而 segment files 在經過 merge 後，也會將已標示為刪除的資料給移除，加上 segment files 數量變少，佔用的 heap memeory 也會較少。

### 指定 Shard 在 Cluster 中的分配方式，以優化儲存硬體的資源

預設是自動分配，而 Elasticsearch 也就會盡可能的平均分配，而我們可以使用 [shard allocation awareness](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-cluster.html#shard-allocation-awareness) 的機制來自己決定 shard 被分配的規則，並依照業務的需求來分配不同等級的硬體給不同的資料。

如果是 time-based 的資料，也可以把時間較舊、使用率較低的資料，分配到較次等的硬體上，以優化儲存硬體的成本，這部份也可以參考 Hot-Warm-Cold architecture 的運作機制來處理。

### 刪除整批資料時，以 Index 為單位來刪除，而不要從一批 Documents 來刪除

Document 的刪除，在 Elasticsearch 的運作上，是產生另外一筆 "標示刪除" 的記錄在 segment files 中，所以這些都還是會持續的佔用系統資源，一直到 segment merge 之後，才會真正的被移除。

因此如果會週期性的刪除一批舊資料時，最好能以 Index 為單位來刪除，而不是透過 delete\_by\_query 之類的批次刪除機制。

### 建議使用 Data Stream 或 Index Lifecycle Management (ILM) 來管理 time series 資料

Index Lifecycle Management 裡面可以定義 **自動 Rollover** 的機制，也就是當資料量成長到 **某個數量**、**某個時間**、**某個大小** 時，會自動產生新的 Index 來放新的資料，而太舊的資料也能設定自動刪除。

使用 ILM 是個很好來管理 Shard Strategy 的工具，因為他能很輕易的進行策略的調整：

* 因為 ILM 都會透過 Index Template 來管理 Indices，所以要改變 primary shard 數量時，直接改 index template 即可。
* 想要 shard 總數成長慢一點、單一 shard 的大小要大一點時，只要修改 Rollover 的配置規則。
* 當 shard 數量太多、舊資料要刪掉時，只要修改 delete phase 的規則。

### 一般會建議讓單一 Shard 的大小在 10 \~ 50 GB 左右

Shard 太大時，會讓 Cluster 進行 shard recover 時的成本太高。例如一個 node 死掉時，Cluster 會嘗試進行 rebalance ，這時要將某個 node 身上的 shard 搬到另一個 node 時，這個搬家所消耗的頻寬與處理的系統資源都會是成本。

> 不過這個還是要依照 Cluster 的硬體規格及 Node 的數量來進行全面的評估。

### 1GB 的 Heap Memory 大約能處理 20 個 Shards

一個 node 能處理的 shard 數量大約會和 JVM heap memory 大小成一定的比例，以官方統計的數字，一般是每 GB 的 heap memory 大約可以處理 20 個 shards，也就是 30GB 的 heap memory 可以處理 600 個 shards，不過這同樣的還是要依照 Elasticsearch Cluster 的硬體規格與使用狀況來評估。

若要查看 shard 數量，可以從 \_cat API 來看

```
GET _cat/shards
```

### 一個 Index 有多個 Shard 時，避免大多數的 Shard 都被分配在同一台 Node 身上

當我們將一個 Index 設定有多個 primary shard 時，主要的目的就是為了 indexing 時能有更多的 Nodes 能分擔處理，但一個 Cluster 可能有許多的 Index ，在各種混合分配的情況下，若是 shard allocation 的機制剛好把這個 Index 把 shard 分到同一個 Node 身上的話，這樣就達不到我們要的目的。

因此可以透過 `index.routing.allocation.total_shards_per_node` 的設定，來限制每個 node 可以被分配存放這個 index 多少個 shard，設定方式如下：

```
PUT /my-index-000001/_settings
{
  "index" : {
    "routing.allocation.total_shards_per_node" : 5
  }
}
```

## 修正 oversharging (過多的 shards) Cluster 的方法

當 Cluster 已經因為太多的 shards 導致不太穩定時，我們可以透過以下的方式來進行調整或修正。

* 使用較長時間區間的 time-based indices 配置方式
* 刪掉空的或沒必要的 Indices
* 在系統較不忙的時段，執行 Force Merge 來合併 Segment Files
* 透過 Shrink API 將現有的 Index 的 shard 數量變少
* 透過 reindex API 來合併較小的 indices

底下分別是這些建議的個別說明。

### 使用較長時間區間的 time-based indices 配置方式

針對 time-based 資料，我們可以提高切 Index 的時間顆粒度，例如本來是 1天 切一個 Index，我們可以改成 1個月、或 1年 來切 Index。

如果是使用 Index Lifecycle Management 來處理 time-based indices 時，就可以透過提高 `max_age`, `max_docs`, `max_size` 的這些配置來達到同樣的目的。

### 刪掉空的或沒必要的 Indices

如果是使用 ILM 的 `max_age` 機制來進行 Index 的 rollover 時，有可能會產生出空的 index，這些空的 indices 也會佔用到一些系統資源，這樣的 Index 應該查詢出來並且刪除。

### 在系統較不忙的時段，執行 Force Merge 來合併 Segment Files

透過 Segment Files 的 merge 來減少佔用的系統資源與空間，將資源能提供給其他的任務。

也因為 forcemerge 的執行時很佔用系統資源，所以建議要在系統較不忙的時候來做這個動作。

```
POST /my-index-000001/_forcemerge
```

### 透過 Shrink API 將現有的 Index 的 shard 數量變少

如果 Index 已經不再會寫入新的資料時，可以透過 Shink API 來將 primary shard 的數量減少。詳細的設定方式可以參考先前的文章，或是 [官方文件 - Shrink index API](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-shrink-index.html)。

若是使用 ILM 的話，也可以在 warm phase 時設置 Shrink 的處理。

### 透過 reindex API 來合併較小的 indices

例如原先我們是以 **日** 來當作 time-based indices 的切割單位，若是 index 數量太多，因此造成 shard 數量太多，我們可以透過 reindex 的方式這些 indices 的資料合併到以 **月** 為單位的 index。

```
POST /_reindex
{
  "source": {
    "index": "my-index-2099.10.*"
  },
  "dest": {
    "index": "my-index-2099.10"
  }
}
```

這樣也會是一種減少 shard 數量的方式。

## 參考資料

* [官方文件 - How to size your shards](https://www.elastic.co/guide/en/elasticsearch/reference/current/size-your-shards.html)
* [官方文件 - Thread pools](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-threadpool.html)
* [官方文件 - Shrink index API](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-shrink-index.html)


# 完賽心得

### 完賽感言

如同一位前輩所說的，平常好好的都沒事，偏偏參加鐵人賽的三十天就一定會有各種你意料不到的狀況不斷出現，生病破病超過一週、工作上的各種狀況不斷發生、家裡的狀況、連假滿滿三整天都去上超過11小時的課…，再怎麼苦總之最後熬過來了，就是一個"爽"字。

這次文章幾乎沒有預寫，只有先寫前三篇(含前言)，一開賽馬上就用光，所以幾乎所有文章都是前一晚寫的，一開頭也沒有完整的規劃架構，只有主要的概念，過程中不斷的修改、邊寫邊決定下一個塊主題要寫什麼，也曾經幾次 refactor 前面的文章標題，總之最後產生出來這系列的文章內容，還算滿意，有將我曾經一直想補充進入教材、或是還沒時間摸索的功能，趁這次鐵人賽花時間研究並整理成文章，我自己也在此過程中又學習了不少細節，另外 backlog 中還有不少 topics 最後是沒有寫出來的，這些未來應該會再一併規劃並且開設另外的新課程。

感謝團長的揪團、也感謝小孩沒滿三個月就被我拖下水，一邊餵奶一邊寫文章的 Edward，我們三個人是一個非常棒的互相傷害 (牽制) 組合，就這樣默默的成團、完賽、學習與成長，美中不足的是我們 **搭著ESTC飛上天** 的三個人都沒買 ESTC 啊!!!

希望不只是我們在這過程中自我成長與挑戰，也期待這些文章的知識產出能幫到需要的人，為軟體產業帶來一些貢獻。

(下圖藍框就是我們參加鐵人賽這30天的 ESTC 走勢，嘆\~\~)

![Screen Shot 2020-10-30 at 1.39.54 AM](https://i.imgur.com/kW2sgmx.png)


# 喬叔帶你上手 Elastic Stack - 探索與實踐 Observability 系列

{% hint style="info" %}
這系列文章是在 iThome 2021 年 IT邦幫忙 鐵人賽 時所撰寫，參加 DevOps 分組主題並得到冠軍的肯定，原始文章發佈於 [iT邦幫忙網站](https://ithelp.ithome.com.tw/users/20129543/ironman/4841)。\
(但排版不易閱讀，因此整理到 GitBook 這邊來:smile:)
{% endhint %}

### 喬叔教 Elastic 文章總整理

以下針對這次的 **喬叔帶你上手 Elastic Stack - 探索與實踐 Observability 系列** 進行總覽介紹，方便讀者們掌握系列文章的架構與脈絡。

#### 前言

首先針對 Observability 的定義，以及 Elastic 對於 Observability 的觀點及所提出的解決方案進行介紹，並且在這邊帶出了 Elastic Observability 解進方案中的四大主軸 Uptime、Metrics、Logs、Traces。

* [01 - 前言 & 淺談 Observability](/tech-sharing/uncle-joe-teach-es-elastc-observability/qian-yan-qian-tan-observability)
* [02 - Elastic 的 Observability 解決方案](/tech-sharing/uncle-joe-teach-es-elastc-observability/elastic-de-observability-jie-jue-fang-an)

#### Uptime - 掌握系統的生命徵象 系列文章

針對 Elastic Observability 中的 Uptime 進行介紹 ，如何掌握系統的生命徵象，甚至如何從使用者體驗的角度來驗證服務的運作狀態。

* [01 - 我們要觀測的生命徵象是什麼？](/tech-sharing/uncle-joe-teach-es-elastc-observability/uptime-zhang-wo-xi-tong-de-sheng-ming-zhi-xiang/wo-men-yao-guan-ce-de-sheng-ming-zhi-xiang-shi-shen-mo)
* [02 - 使用 Heartbeat 收集系統生命徵象數據](/tech-sharing/uncle-joe-teach-es-elastc-observability/uptime-zhang-wo-xi-tong-de-sheng-ming-zhi-xiang/shi-yong-heartbeat-shou-ji-xi-tong-sheng-ming-zhi-xiang-shu-ju)
* [03 - 透過 Kibana 觀看心電圖及設定警報](/tech-sharing/uncle-joe-teach-es-elastc-observability/uptime-zhang-wo-xi-tong-de-sheng-ming-zhi-xiang/tou-guo-kibana-guan-kan-xin-dian-tu-ji-she-ding-jing-bao)
* [04 - 使用合成監控 (Synthetics Monitor) 從使用者情境驗證服務的運作狀態](/tech-sharing/uncle-joe-teach-es-elastc-observability/uptime-zhang-wo-xi-tong-de-sheng-ming-zhi-xiang/shi-yong-he-cheng-jian-kong-synthetics-monitor-cong-shi-yong-zhe-qing-jing-yan-zheng-fu-wu-de-yun-zu)

#### Metrics - 觀察系統的健康指標 系列文章

Metrics 是系統 Monitoring 的基礎，在這裡將介紹 Elastic Observability 中的 Metrics 提供了什麼樣的能力，如何實作在自己安裝的機器上、Docker、K8S、甚至是 AWS 的雲端環境，以及如何使用 Metricbeat 來掌握 Elastic Stack 的健康狀態。

* [01 - Metrics 與 Metricbeat 的基本介紹](/tech-sharing/uncle-joe-teach-es-elastc-observability/metrics-guan-cha-xi-tong-de-jian-kang-zhi-biao/metrics-yu-metricbeat-de-ji-ben-jie-shao)
* [02 - 使用 Metricbeat 掌握 Elastic Stack 的健康狀態](/tech-sharing/uncle-joe-teach-es-elastc-observability/metrics-guan-cha-xi-tong-de-jian-kang-zhi-biao/shi-yong-metricbeat-zhang-wo-elastic-stack-de-jian-kang-zhuang-tai)
* [03 - 使用 Metricbeat 掌握 Infrastructure 的健康狀態 Host 篇](/tech-sharing/uncle-joe-teach-es-elastc-observability/metrics-guan-cha-xi-tong-de-jian-kang-zhi-biao/shi-yong-metricbeat-zhang-wo-infrastructure-de-jian-kang-zhuang-tai-host-pian)
* [04 - 使用 Metricbeat 掌握 Infrastructure 的健康狀態 Docker 篇](/tech-sharing/uncle-joe-teach-es-elastc-observability/metrics-guan-cha-xi-tong-de-jian-kang-zhi-biao/shi-yong-metricbeat-zhang-wo-infrastructure-de-jian-kang-zhuang-tai-docker-pian)
* [05 - 使用 Metricbeat 掌握 Infrastructure 的健康狀態 Kubernetes 篇](/tech-sharing/uncle-joe-teach-es-elastc-observability/metrics-guan-cha-xi-tong-de-jian-kang-zhi-biao/shi-yong-metricbeat-zhang-wo-infrastructure-de-jian-kang-zhuang-tai-kubernetes-pian)
* [06 - 使用 Metricbeat 掌握 Infrastructure 的健康狀態 AWS 篇](/tech-sharing/uncle-joe-teach-es-elastc-observability/metrics-guan-cha-xi-tong-de-jian-kang-zhi-biao/shi-yong-metricbeat-zhang-wo-infrastructure-de-jian-kang-zhuang-tai-aws-pian)

#### Logs - 挖掘系統內部發生的狀況 系列文章

Logs 是系統運作細節的記錄，也是我們用來挖掘系統內部運作時發生什麼狀況的重要參考資訊，Elastic Observability 的解決方案之中，使用了 Filebeat 來負責收集散落在四處的 Logs，並且如何將收集到的 Logs 使用 Elastic Observability 來進行查閱。

* [01 - Logs 與 Filebeat 的基本介紹](/tech-sharing/uncle-joe-teach-es-elastc-observability/logs-wa-jue-xi-tong-nei-bu-fa-sheng-de-zhuang-kuang/logs-yu-filebeat-de-ji-ben-jie-shao)
* [02 - 使用 Filebeat 應該要了解的設計細節與原理](/tech-sharing/uncle-joe-teach-es-elastc-observability/logs-wa-jue-xi-tong-nei-bu-fa-sheng-de-zhuang-kuang/shi-yong-filebeat-ying-gai-yao-liao-jie-de-she-ji-xi-jie-yu-yuan-li)
* [03 - 透過 Filebeat 收集 Elastic Stack 中各種服務的細節資訊](/tech-sharing/uncle-joe-teach-es-elastc-observability/logs-wa-jue-xi-tong-nei-bu-fa-sheng-de-zhuang-kuang/tou-guo-filebeat-shou-ji-elastic-stack-zhong-ge-zhong-fu-wu-de-xi-jie-zi-xun)
* [04 - 透過 Filebeat 收集 Infrastructure 中各種服務的細節資訊](/tech-sharing/uncle-joe-teach-es-elastc-observability/logs-wa-jue-xi-tong-nei-bu-fa-sheng-de-zhuang-kuang/tou-guo-filebeat-shou-ji-infrastructure-zhong-ge-zhong-fu-wu-de-xi-jie-zi-xun)

#### Traces - 觀察應用程式的效能瓶頸 系列文章

Observability 的一個核心精神，是讓我們有能力觀察系統運作的狀況，Elastic Observability 當中的 APM (Application Performance Monitoring) 就是實現 Observability 這部份精神的其中一個重要的工具，幫助我們能輕鬆的掌握系統運作的效能分析、發生異常時環節、或是在複雜的多層次架構或是微服務架構之下，服務元件之間的相依性及影響的關連，這樣的工具要如何來使用及應用，將會是這個章節的主軸。

* [01 - Elastic APM 基本介紹](/tech-sharing/uncle-joe-teach-es-elastc-observability/traces-guan-cha-ying-yong-cheng-shi-de-xiao-neng-ping-jing/elastic-apm-ji-ben-jie-shao)
* [02 - 使用 APM-Integratoin-Testing 建立 APM 的模擬環境](/tech-sharing/uncle-joe-teach-es-elastc-observability/traces-guan-cha-ying-yong-cheng-shi-de-xiao-neng-ping-jing/shi-yong-apmintegratointesting-jian-li-elastic-apm-de-mo-ni-huan-jing)
* [03 - 如何在 Kibana 使用 APM UI](/tech-sharing/uncle-joe-teach-es-elastc-observability/traces-guan-cha-ying-yong-cheng-shi-de-xiao-neng-ping-jing/ru-he-zai-kibana-shi-yong-apm-ui)
* [04 - 使用 APM Server 來收集 APM 數據](/tech-sharing/uncle-joe-teach-es-elastc-observability/traces-guan-cha-ying-yong-cheng-shi-de-xiao-neng-ping-jing/shi-yong-apm-server-lai-shou-ji-apm-shu-ju)
* [05 - 透過 APM Agents 收集並傳送後端服務運作的記錄](/tech-sharing/uncle-joe-teach-es-elastc-observability/traces-guan-cha-ying-yong-cheng-shi-de-xiao-neng-ping-jing/tou-guo-apm-agents-shou-ji-bing-chuan-song-hou-duan-fu-wu-yun-zuo-de-ji-lu)
* [06 - 透過真實使用者監控 (RUM, Real User Monitoring) 來改善使用者體驗](/tech-sharing/uncle-joe-teach-es-elastc-observability/traces-guan-cha-ying-yong-cheng-shi-de-xiao-neng-ping-jing/tou-guo-zhen-shi-shi-yong-zhe-jian-kong-rum-real-user-monitoring-lai-gai-shan-shi-yong-zhe-ti-yan)

#### 建立結構化的 Log 系列文章

許多實務上的痛點，常常是收集一堆的 Logs，卻不容易使用，結構化的 Logs 會是 Logs 治理的重要關鍵之一，這個章節介紹了 Elastic Common Schema 的設計規範及準則，可以當作我們自行管理 Logs 的很好的參考，同時也介紹當我們要將 Logs 結構化時，如何使用 Elasticsearch 內建的 Ingest Pipeline。

* [01 - Elastic Common Schema 結構化 Log 的規範](/tech-sharing/uncle-joe-teach-es-elastc-observability/jian-li-jie-gou-hua-de-log/elastic-common-schema-jie-gou-hua-log-de-gui-fan)
* [02 - Elasticsearch Ingest Pipeline 資料 Index 前的轉換好幫手 - 基本介紹](/tech-sharing/uncle-joe-teach-es-elastc-observability/jian-li-jie-gou-hua-de-log/elasticsearch-ingest-pipeline-zi-liao-index-qian-de-zhuan-huan-hao-bang-shou/ji-ben-jie-shao)
* [03 - Elasticsearch Ingest Pipeline 資料 Index 前的轉換好幫手 - 各種常用的 Processor](/tech-sharing/uncle-joe-teach-es-elastc-observability/jian-li-jie-gou-hua-de-log/elasticsearch-ingest-pipeline-zi-liao-index-qian-de-zhuan-huan-hao-bang-shou/ge-zhong-chang-yong-de-processor)
* [04 - Elasticsearch Ingest Pipeline 資料 Index 前的轉換好幫手 - Enrich 資料與例外處理](/tech-sharing/uncle-joe-teach-es-elastc-observability/jian-li-jie-gou-hua-de-log/elasticsearch-ingest-pipeline-zi-liao-index-qian-de-zhuan-huan-hao-bang-shou/enrich-zi-liao-yu-li-wai-chu-li)

#### 有效的使用 Observability 的資料 系列文章

針對前面章節所收集的各種 Observability 資料，說明如何使用進階的 Machine Learning 進行更有效的運用，並且在異常時主動通知的設定方式，以及 Observability 的資料管理，最後將分享實際參加 ElasticOn Observability Workshop 的競賽經歷，以及使用 Elastic Observability 的心得。

* [01 - 透過 Machine Learning 發現異常的問題](/tech-sharing/uncle-joe-teach-es-elastc-observability/you-xiao-de-shi-yong-observability-de-zi-liao/tou-guo-machine-learning-fa-xian-yi-chang-de-wen-ti)
* [02 - 使用 Kibana Alerts 主動通知異常狀況](/tech-sharing/uncle-joe-teach-es-elastc-observability/you-xiao-de-shi-yong-observability-de-zi-liao/shi-yong-kibana-alerts-zhu-dong-tong-zhi-yi-chang-zhuang-kuang)
* [03 - 資料的生命週期管理](/tech-sharing/uncle-joe-teach-es-elastc-observability/you-xiao-de-shi-yong-observability-de-zi-liao/zi-liao-de-sheng-ming-zhou-qi-guan-li)
* [04 - 使用 Elastic Observability 追縱及觀察問題的心得](/tech-sharing/uncle-joe-teach-es-elastc-observability/you-xiao-de-shi-yong-observability-de-zi-liao/shi-yong-elastic-observability-zhui-zong-ji-guan-cha-wen-ti-de-xin-de)

***


# 前言 & 淺談 Observability

## 參賽背景

去年參加第 12 屆的 iT邦幫忙鐵人賽，在飽受煎熬的度過 30 天之後，沒想過會要再參加下一屆，今年在經歷超過 100 次想放棄的念頭之後，在 9/15 最後一天報名截止日，填了報名表單，按下了送出，於是在 9/16 當天開始動筆寫下這篇文章。

(你沒看錯，就是學不會教訓，第二次參賽了還不懂得要先累積一些文章…)

本來少女人妻這次想落跑，今年換我把他推進坑！(不知她是誰的，她是我們上一屆的團長、也是推我入鐵人坑的兇手!)

今年除了我們去年團隊的三個成員，另外多找了三位新成員，總共六人報名，要團體完賽的難度加倍…夥伴們加油！也歡迎大家多鼓勵餵食我們！

## 什麼是 Observability (可觀察性) ?

最近幾年，特別是 2019\~2020 年時，Observability 這個字變得非常的熱門，一方面微服務架構的普及化，傳統的系統服務監控方式開始發現不足以應付這樣複雜的系統，另一方面 DevOps 的理念及各種實踐方法也愈來愈被大家重視，因此如何能有效的掌握系統的運作狀態，也就被受重視。

針對 Observability 字面上可以簡單的解讀為：

> 透過系統外部所揭露資訊的觀察，能有效的掌握到系統內部的運作狀態

有不少針對 Monitoring (監控) 與 Observability (可觀察性) 進行比較的討論，有人覺得根本是一樣的，這個 observability 只是個 buzzword，而我這邊和大家分享一下我自己對於這兩個字的解讀。

Monitoring，可以比喻像是我們透過心電圖、心跳、血壓…等各種方式來掌握一個人的生命狀態，透過身體本身就會產生的各種訊息，來**觀察**我們身體的狀態，用來**解讀**甚至能**監控**我們身體健康情況，一但有異常就能即時發現，甚至是可以當作生病時找尋病源的參考資訊。

Observability，重點是 Observable (**可被觀察的**)，如果今天是一個鋼鐵人，身穿盔甲，我們從外部測不到心跳、心電、血壓，這樣也就缺少了可被觀察的能力。

也就是說，讓身體的數據可以被取得、可以被觀察，也就是 Observability，並且因為有這樣的能力，我們也才有辦法做 Monitoring，因此若只是使用一堆 Monitoring 的工具，把系統的資訊給拉成一個個的 Dashboard，這樣並不適合叫做提升 Observability，而透過工具讓本來很不容易取得的資訊，能更容易的被觀察、分析、監控，甚至在服務或應用程式的設計上，將有被觀察意義的資訊給揭露出來，讓負責維護系統的人能有效的掌握系統狀況、盤查問題，這才是有效的提升系統的 Observability。

另外參考 Google Cloud Architecture Center 中 DevOps Guides \[1] 對針這兩個詞的定義：

**Monitoring** is tooling or a technical solution that allows teams to watch and understand the state of their systems. Monitoring is based on gathering predefined sets of metrics or logs.

**Observability** is tooling or a technical solution that allows teams to actively debug their system. Observability is based on exploring properties and patterns not defined in advance.

有提到一個重點，就是『事先定義』，Observability 也就是擁有能夠探索未事先定義的屬性與模式的能力。

## 此系列文章的目的

這次會以 Observability 當作主題，主要是喬叔自己在軟體領域 20 多年來，在實務中深感 Observability 的重要，因此希望一方面透過這個主題，能將這方面的經驗與觀念整理出來，另一方面也想透過寫這篇文章時，能再次精煉我自己對於 Elastic Stack 的熟練程度，因此這系列會以 Elastic Stack 來當作提升系統 Observability 的解決方案，另外不會去和其他的競品比較，在這邊要先要解釋一下，Elastic Stack 絕對不會是唯一合適的選擇，而我會選擇他，是因為他提一個整合度、生態圈都蠻完整的整體解決方案，最重要的是能較快速的將好的理念實現出來，能實際幫助到團隊、產品、客戶，盡快產生出實際的價值，希望這系列的文章能幫助到有需要的人。

## 參考資料

1. [Google Cloud Architecture Center - DevOps Guides](https://cloud.google.com/architecture/devops/devops-measurement-monitoring-and-observability)


# Elastic 的 Observability 解決方案

### 本篇學習重點

* 了解 Elastic 針對 observability 的觀點
* 初探 Elastic Observability 的解決方案

## Elastic Observability 的觀點

前一篇我稍微淺談了自己對 Observability 的解讀，至於 Elastic 針對 Observability 的觀點，我蠻推薦 Elastic Observability 的 Product VP - Tanya Bragin 在 Observability with the Elastic Stack \[1] 的這篇文章，裡面有提到二個看待 Observability 的面向，分別是：

1. 檢測服務品質的管理面向：透過定義好的 SLIs (Service Level Indicators) 及 SLOs (Service Level Objectives) 來當作服務品質指標的標準，例如一般會使用系統的 uptime (正常運行的時間) 當作 SLI 的這個指標，進而定義可能是 6 個 9 (99.9999%) 的 SLO 這個目標，並且可以將這兩個定義轉換成為 SLAs (Service Level Agreements) 提供給客戶當作服務的保障範圍的依據，這通常會是 observability 的起步，其實也就是以終為始的重要的目的 - 提升客戶的滿意度，而執行的方式其實就是大家都已經有在做的 Monitoring。
2. 提升解決問題效率的方法：如何讓開發、運維的人員，在遇到系統發生問題時，能因為系統具有良好的 Observability，能更有效率的快速發現原因及解決問題，甚至在異常的徵兆發生時，就能觀察到異狀並且避免更大災情的發生。

## Observability 三本柱

針對 Observability 的三個主要的重點支柱，就是 Logs、Metrics、Traces，這個在所有談 Observability 的討論上，已經都成熟的歸納成這三個面向的資訊了，Elastic Observability 也就直接針對這三個面項定義在產品的功能之中。

![three-pillars-of-observability-logs-metrics-tracs-apm](https://i.imgur.com/Gp8qfJz.png)

### Logs

日誌是掌握系統內部發生什麼事情最重要的資訊，而日誌的重點，是從一開始程式開發時日誌如何撰寫，就已經事關一半的成敗，如果源頭的資訊不夠詳細，後續就只能用猜測、或是災情發生後再去埋 Log，這樣就又錯過了救援的黃金時刻，再來就會是如何收集散落各地、各種格式的日誌，以及如何將這麼大量的資料進行有效的分析及使用，最後是如何管理這些資料的生命週期。

### Metrics

Metrics 是以數字化的方式來有效率的解讀系統運作狀態的重要資訊，舉凡 CPU loading, Memory, Disk I/O, Disk free space, Network bandwidth, DB metrics, ... 等各種數字化的指標資訊，透過這些指標能做到最基本的異常的判斷，而這些 Metrics 也會是資料量極多的資訊，如何更有效的保留這些資料也會是管理上的重點。

### Traces (APM, Application Performance Monitoring)

一個交易在系統處理時，經過了哪些環節、哪些子系統、當中做了什麼事、在哪個地方會產生效能的瓶頸、在哪個服務元件之中有發生異常，能夠在複雜的系統架構中協助開發運維的人員一目瞭然的掌握交易運作的細節。

## Elastic Observability 要解決的問題

* 各種裝置、系統、服務的 Metrics 資料如何輕鬆且有效的收集？
* 隨著時間不斷增長的資料，如何在能保留使用價值的情況下，能在儲存效率、使用時的運算效率上，能被最佳化？
* 各種格式的日誌與資料，如何能更容易的分析及解讀？災情發生時有效的減少 MTTR (Mean Time To Recovery)？
* 避免沒有收集到足夠能分析問題的資訊，又要避免收集太多的資訊而造成這些資訊被破碎化的解讀，無法有效的分析及使用。
* 分析後的資料，如何能有效的透過視覺化的方式呈現，協助有價值的資訊能有效的被吸收。
* 主動發現異常的狀態，提早發現問題及減少災情的影響範圍。

## 為什麼是 Elastic Stack？

首先因為『Elasticsearch』，整體 observability 的資料應該要能聚集在同一個地方，在分析及使用上才容易發揮最大效益，舉例來說 Traces 的資料要能進一步和 Logs 串接，追查問題時能一路接起來，甚至在透過 Machine Learning 分析時，能夠將各種資料一併來學習，絕對會比分散在各種不同系統的效果要來得好。

另外要針對巨量非結構化資料進行搜尋，這不只是使用 Apache Lucene 或建個 inverted index 就能處理掉全文檢索的工作，Elasticsearch 使用 Columnar store 方式儲存文字類型的資料、透過 BKD tree, document store, term vectors…等不斷優化 metrics 及 logs 資料分析及彙總的運算能力，資料生命週期的管理機制、分散式架構的擴展能力…這些 Elasticsearch 核心的能力，已經是目前日誌儲存分析的首選工具。

最後是統一的環境，Elastic Stack 產品之間本身的高度整合，這樣高度整合完成的生態圈會讓許多後續維護上較於便利，學習門檻也降低，這部份會是很大的一個好處。

![51072345-1589859310136988\_origin](https://i.imgur.com/HqtzGkM.png)

## Elastic Stack for Observability

* Elasticsearch: 是個 NoSQL DB + 搜尋引擎 + Time-series DB，整體資料分析與運算處理的核心。
* Kibana: 整體方案統一的 UI，不論是 APM, Logs, Metrics, 以及這些 Stack 的管理功能，都可以透過這個入口來進行操作。
* APM: 負責收集 Traces 的資料，包含交易的追縱、分散式架構資訊的追縱、也包含透過網頁端收集 User experience data。
* Beats: 協助收集各種 Logs data, Metrics data, Uptime data, Synthetic data。
* Elastic Agent: 新一代將取代 Beats 收集 Logs, Metrics 的工具。

並透過以上的這些產品及工具，能夠建構出如下圖 Elastic Observability 所提供整體的服務。

![observability](https://i.imgur.com/KiVL7JV.png)

## 參考資料

1. [Observability with the Elastic Stack](https://www.elastic.co/blog/observability-with-the-elastic-stack)


# Uptime - 掌握系統的生命徵象

針對 Elastic Observability 中的 Uptime 進行介紹 ，如何掌握系統的生命徵象，甚至如何從使用者體驗的角度來驗證服務的運作狀態。

* [01 - 我們要觀測的生命徵象是什麼？](/tech-sharing/uncle-joe-teach-es-elastc-observability/uptime-zhang-wo-xi-tong-de-sheng-ming-zhi-xiang/wo-men-yao-guan-ce-de-sheng-ming-zhi-xiang-shi-shen-mo)
* [02 - 使用 Heartbeat 收集系統生命徵象數據](/tech-sharing/uncle-joe-teach-es-elastc-observability/uptime-zhang-wo-xi-tong-de-sheng-ming-zhi-xiang/shi-yong-heartbeat-shou-ji-xi-tong-sheng-ming-zhi-xiang-shu-ju)
* [03 - 透過 Kibana 觀看心電圖及設定警報](/tech-sharing/uncle-joe-teach-es-elastc-observability/uptime-zhang-wo-xi-tong-de-sheng-ming-zhi-xiang/tou-guo-kibana-guan-kan-xin-dian-tu-ji-she-ding-jing-bao)
* [04 - 使用合成監控 (Synthetics Monitor) 從使用者情境驗證服務的運作狀態](/tech-sharing/uncle-joe-teach-es-elastc-observability/uptime-zhang-wo-xi-tong-de-sheng-ming-zhi-xiang/shi-yong-he-cheng-jian-kong-synthetics-monitor-cong-shi-yong-zhe-qing-jing-yan-zheng-fu-wu-de-yun-zu)


# 我們要觀測的生命徵象是什麼？

### 本篇學習重點

* 我們要監控系統的 uptime 時，我們的目的是什麼？什麼是系統的生命徵象？
* 在開始設定 Heartbeat 來設定監控之前，我們要考慮的面向有哪些？

## 系統的生命徵象

當我們要觀察系統的生命徵象時，Elastic Stack 的解決方案中，可以使用 Heartbeat 週期性的檢查系統的可用性 (availability)，不過在這邊我們要先想一下，什麼是系統的生命徵象？代表的意義是什麼？以下一邊透過以"人的生命徵象"來想像，另一邊對應到"系統的生命徵象"來思考：

* **還有沒有心跳：** 也就是系統的主機是不是活著？是不是 ping 了之後能得到回應，當得到回應時，代表系統的主機是活著。
* **心跳的速率是否正常：** ping 系統時，回應的速度如何？是否在我們接受的合理範圍之內？有沒有發生 timeout？
* **還有沒有意識能回話：** 要能回話代表服務要能正常運作，所以我們應該透過應用程式所提供的 health check API 來進行確認，以認服務是否能正常的回應。
* **回話的速度：** 透過 health check API 時，我們可以觀察 response time，也就是系統的回覆速度，是不是符合我們的期待？
* **回話是否正常：** 我們對於 health check API 的回應結果，是否符合我們的預期？如果只是 HTTP 200 的話，其實有機率不代表是我們系統的回覆，(喬叔就曾經發生網路設定被誤改，因此請求被導到別的服務上，拿到的回應是 HTTP 200，但是 response 內容並非我們預期的)，回應的內容應該要是我們期待的結果。
* **其他活著的定義：** 每個人對於活著的定義其實多少會有不同，有些人認為行屍走肉不算活著 XD，這邊提供一個思考的觀點，這個系統提供的是什麼服務，這個服務哪些部份是最重要的，一但這些功能停擺時，代表這個服務是死的了，因此這邊可以想像一下，如果是個有相依於 Database 的服務，如果 DB connection 無法正常連線的時候，這個服務算是活著的嗎？另外也應該要回歸商業面，是否有哪些最重要的功能，如果這些功能無法正常運作時，可以判定服務是死的？例如 auth service，如果他提供 issue token 或是 validate access token 的 API 是死的，是不是代表整個服務其實就是死的了呢？這個議題其實是 **health check API 的設計方式**，你要 check 的東西到底是什麼，是不是要包含到第三方相依服務的狀態？這個題目在這邊不會深入探討，但是絕對會是你們團隊在建立 uptime monitor 之前會要考慮清楚的 (其實是服務開發時就要定義好的)。

## 監控系統生命徵象時，你應該要考慮的面向

### 部署的方式，是不是夠安全可靠？

首先部署 Heartbeat 的最主要原則：**當系統掛掉的時候，負責監測的 Heartbeat 不應該跟著掛掉。**

因此官方文件就有直接提出，不建議使用 sidecar 的這種方式來部署 heartbeat，建議要部署成為獨立的服務，以降低主要的系統發生問題時，Heartbeat 也同時出問題的可能性。\[1]

> 什麼是 sidecar?
>
> sidecar 的中文是『邊車』，也就是把程式以另外的 process 掛載在主要的 process 或是 container 旁邊，避免直接運作在主 process 之中，一方面是可以更好的模組化、佈署的方式也能更容易標準化並重覆利用，另一方面是減少與主程式的相依性，不過實際運作上還是會有一些資源是共用的，例如安裝在同一台主機上、使用同樣的 disk，使用同樣的 network 環境…等。

### 監控不只從最外面的 Internet 來監控，從 client 端一路到服務所運作的伺服器，這條路線中你還要從哪一層切入？

一般我們要監控服務時，最直接會想到從外層，也就是 Internet 去監控，但試想，如果今天外層發現服務不通的時候，但你從服務的 local 端發現是好的時候，中間的這些網路問題，要如何能更有效的盤查與找出問題呢？

我們重新檢視從 Client 端，一路到服務運作的 Server 主機中，網路會經過哪些路徑，對外的服務一般來說會經過 CDN，若是內部的服務也可能跨過多個 VPN，因此在這樣有多段網路環境的路線當中，建議的做法是：CDN 內、外都要架設 heartbeat，不同 VPN 的網段內也要有各自的 heartbeat，透過一層一層的記錄，能夠最快速的幫我們在發生問題的當下，判斷是在網路的哪一層發生問題。

### 是否已盤查清楚系統運作的網路架構，你要從哪些地方監控才足夠？

當我們的服務對外有經過 CDN 時，CDN 的架構上會透過許多的 POPs (points of presence) 也就是散落在世界各地的節點，來處理快取，並且讓 client 能最近、也就會最快速的取得需要的內容，所以如果有使用 CDN 時，一般也就會建議要在多個不同的地區佈建 Heartbeat，收集各個不同地點是否能正常的存取服務的數據。

### 是否有佈署在另一個 Data Center 的備援服務？在多個 Data Center 時，除了監控自己，也應該互相監控。

如果有不同的 Data Center時，除了建議一定要在各個 Data Center 內部佈署自己的 Heartbeat，另外在設定監控時，除了監控自己 Data Center 的服務之外，也應該順便監控另個 Data Center 的服務，這樣跨 Data Center 時的網路環境會多透過 Internet，也會是出問題時能作為交互比對的參考資訊。

以上是建立監控之前，需要先思考及評估的部份，在經過這些評估及規劃之後，下一篇我們將透過 Elastic heartbeat 的設定，來收集這些我們需要取得的資料。

## 參考資料

1. [Elastic 官方文件 - Ingest uptime data](https://www.elastic.co/guide/en/observability/current/ingest-uptime.html#ingest-uptime)


# 使用 Heartbeat 收集系統生命徵象數據

### 本篇學習重點

* Elastic Heartbeat 簡介
* 如何安裝及設定 Heartbeat 來收集系統的生命徵象數據
* 使用 Heartbeat 時有哪些實用的技巧

## 什麼是 Heartbeat

Elastic Beats 是 Elastic Stack 中專門負責收集散落在各地的 Log 及各種資訊的一系列工具，Beats 家族的主要都是基於 `libbeat` framework 所開發，而 Heartbeat 是 Elastic Beats 家族的成員之一，是一個輕量化的常駐程式 (daemon)，可以安裝在遠端的某一台機器上，並且定期的去檢查指定的服務是否還活著 (availability)。

Heartbeat 支援的監控方法有以下幾種：

* **ICMP (v4 or v6)：** 也就是我們很常在使用的 `ping` 這個指令所使用的 protocol，可以提供最基本的驗證，確認機器是否能正常的回應。
* **TCP：** 可以針對特別的 TCP port 去發送指定的請求，並且傳送指定的 payload，也可以驗證回傳的 payload。
* **HTTP：** 這個協定也是 Heartbeat 最常被使用到的通訊協定，像是我們 API 常用的 RESTful 協定，就可以透過 Heartbeat 發送請求，並且驗證 HTTP 回傳的 `status code`、`response header`、`response body` 是不是符合預期。
* **Browser：** 這個功能很強，是目前最新、但還在實驗階段 (Experimental) 的功能，也就是收集 Elastic Observability 當中的 Synthetic Data 的功能，簡單來說他會模擬瀏覽器，來執行一系列我們指定的步驟，我們就可以依照使用者實際的情境，一步一步的模擬、並且驗證執行的結果，同時也會產生螢幕截圖讓我們知道執行的結果為何。這個功能會再下一篇文章進行介紹。

另外針對 `TCP` 和 `HTTP` 都有支援 **SSL/TLS** 的 HTTPS 協定，也能支援 proxy 的設定，最貼心的是，他還能順便記錄 HTTPS 的憑證 (certificate) 的過期時間，我們也就能簡單的透過 Alert 的設定，在憑證過期之前發出警告。

## Heartbeat 的使用情境

以下列出 Heartbeat 可以使用的一些情境，當然能用的範圍不只於此，我們也可以依照他的功能特性來創造合符合需求的應用情境。

1. 從各個不同的地區、Data Center、檢查服務是否活著。
2. 在複雜的網路環境中，從各網段監控服務可否被存取，透過這些數據協助判斷網路是否有狀況，是否哪個網段無法正常存取服務。
3. 除了檢查服務是否活著，同時因為有收集回應的時間，所以也能確認是否有延遲時間更長、甚至導入於過時 (timeout) 的現象。
4. 當我們與客戶有協議 SLA (Service Level Agreement) 時，透過 Heartbeat 收集的資料，透過 Kibana 彙總成報表，可以清楚的驗證我們是否有達到我們所承諾的 SLA。
5. 反向的安全性檢查，例如我們不允許外網能存取到服務，可以透過 Heartbeat 從外網週期的驗證，服務不能被存取到。
6. 針對特定服務存取的 API，定期驗證特定功能的正常狀態。
7. 由於支援 Proxy，若商業服務的目標市場大量依賴於手機端用戶，甚至可以透過 proxy 使用當地的 ISP 的手機網路來當作跳板，驗證指定的 ISP 所提供的手機網路是否能正常存取服務。(以喬叔的經驗，就有發生在日本特定的 ISP 無法存取服務，但其他 ISP 卻可以使用的經驗，這時若是需要舉證，有這些日誌與記錄會非常有幫助)
8. 在協助驗證服務的可用性 (availability) 時，一併協助驗證 HTTPS 的憑證是否快要過期，這部份也是服務可用性很重要的一點，而且從過往的新聞之中，有些很大很厲害的公司，也還是會發生不小心憑證過期忘了更新，而影響到用戶使用的案例。

## 安裝 Heartbeat

要安裝 Heartbeat，官方網站的快速上手文件 \[1]，已經非常的清楚簡單，我這邊就不做細部說明，只大約以 MacOS 環境為例，列出以下的步驟：

1. 先準備好 Elasticsearch Cluster 的環境，你會需要用 Elasticsearch 來儲存與搜尋 Heartbeat 所收集到的資料。
2. 準備好 Kibana 的環境，我們會要透過 Kibana 來檢視這些資料。
3. 下載 Heartbeat 並解壓縮

```
curl -L -O https://artifacts.elastic.co/downloads/beats/heartbeat/heartbeat-7.14.1-darwin-x86_64.tar.gz
tar xzvf heartbeat-7.14.1-darwin-x86_64.tar.gz
```

1. 進入解壓縮後的目錄，修改 `heartbeat.yml`，指定 Elasticsearch 與 Kibana 的位置。

```
output.elasticsearch:
  hosts: ["myEShost:9200"]
  username: "heartbeat_internal"
  password: "YOUR_PASSWORD"
```

```
setup.kibana:
  host: "mykibanahost:5601" 
  username: "my_kibana_user"  
  password: "YOUR_PASSWORD"
```

1. 在 `heartbeat.yml` 裡面，或是在 `monitors.d` 的目錄裡建立自己訂義的 YAML 檔，來設定 Monitors 的規則。

```
heartbeat.monitors:
- type: icmp
  schedule: '*/5 * * * * * *' 
  hosts: ["myhost"]
  id: my-icmp-service
  name: My ICMP Service
- type: tcp
  schedule: '@every 5s' 
  hosts: ["myhost:12345"]
  mode: any 
  id: my-tcp-service
- type: http
  schedule: '@every 5s'
  urls: ["http://example.net"]
  service.name: apm-service-name 
  id: my-http-service
  name: My HTTP Service
```

> 注意：如果在 monitors.d 裡面建立新的 YAML 檔的話，檔案裡是直接放 `heartbeat.monitors` 裡面的設定值的，也就是 `heartbeat.monitors` 這一層是用宣告，直接從 `- type: icmp` 這一層開始宣告即可，請直接參考解壓縮之後，資料夾裡面附的範例。

1. 依照佈署的地理位置，設定好 `processors` 裡的地區設定，這個設定可以協助之後在 Kibana 篩選檢視的範圍。

```
# ============================ Processors ============================

processors:
  - add_observer_metadata:
      # Optional, but recommended geo settings for the location Heartbeat is running in
      geo: 
        # Token describing this location
        name: us-east-1a 
        # Lat, Lon "
        #location: "37.926868, -78.024902" 
```

1. 設置的最後是要透過 `./heartbeat setup -e` 幫我們到 Elasticsearch 建立好 ILM (Index Lifecycle Management) 要使用的 Index Template。

![image-20210919004201201](https://i.imgur.com/703PvmR.jpg)

1. 最後執行 heartbeat。

```
sudo chown root heartbeat.yml 
sudo ./heartbeat -e
```

> 如果你使用 root 的身份執行 heartbeat 時，要記得將 config 的擁有者一併修改成 root。

1. 最後可以就可以從 **Kibana** 的 **Observability** 選單中的 **Uptime** 看到收集的資料了。

![04-kibana-uptime](https://i.imgur.com/C810G7M.png)

## Heartbeat 使用的技巧

以下列出一些在 Heartbeat 在使用上的技巧，可以提供大家參考。

### 針對需求情境環境設定不定的 `tags`

Heartbeat 裡面可以設定 `tags`，並且在 `heartbeat.yml` 裡可以設定 General 的 `tags`，而進入到 `monitors` 裡，可以再設定特定 `monitor` 的 `tag`，這些 `tags` 設定的用途，是在 Kibana 的畫面很可以清楚的分類我們所定義的這一些監示排程的工作，並且可以快速的篩選。

![04-setup\_tags](https://i.imgur.com/aOqnepy.png)

而什麼情境來設定這些 `tags`，建議可以使用最常會用來分類及篩選進行檢示的項目，例如：Service Name、Data Center、Project Name…等。

### 若是有使用 301 或 302 HTTP Redirect 的頁面，要注意的設定

如果今天要透過 Heartbeat 來監測的是有設置 HTTP Redirect 的網頁頁面，首先要注意 `max_redirects` 的設定，預設是 `0` ，也就是不會去執行 redirect，所以會要 Heartbeat 去跟隨 Redirect 回傳的指示進一步的存取下一個 Location 的話，要記得將這個設定打開，另外要注意這個設定打開之後，因為記錄的過程會變得較複雜，有些資訊會無法保留，這部份請直接參考官方文件 \[2]。

另外在這種情境，可以視情況先設定一組專門確認有回傳 `301` 或 `302` 結果、並且驗證 HTTP header 當中的 Location 設定值，另外再建一組是會依照 HTTP redirect 執行到最後頁面的結果，這樣能夠多確認這一步一步的過程是否如預期。

以下是我的一個例子，我會要求先取得 `301` 的 response status，並且最後我會要拿到的是 `404` (雖然你看到 404 好像很怪，但這是我目前需要的情境 :P)。

```
- type: http # monitor type `http`. Connect via HTTP an optionally verify response
  id: onedoggo-web
  name: OneDoggo Web
  schedule: '@every 5s' # every 5 seconds from start of beat
  hosts: ["https://onedoggo.com"]
  ipv4: true
  ipv6: true
  mode: any
  supported_protocols: ["TLSv1.0", "TLSv1.1", "TLSv1.2"]
  # 記錄 Reponse header
  response.include_body: on_error
  check.response:
    status: 301
    headers:
    - location: https://onedoggo.com/98a4eb9cdd8b40ddb4a452af7577295d
  tags: ["onedoggo", "web"]
  fields:
    env: production

- type: http # monitor type `http`. Connect via HTTP an optionally verify response
  id: onedoggo-web
  name: OneDoggo Web
  schedule: '@every 5s' # every 5 seconds from start of beat
  hosts: ["https://onedoggo.com"]
  ipv4: true
  ipv6: true
  mode: any
  supported_protocols: ["TLSv1.0", "TLSv1.1", "TLSv1.2"]
  response.include_body: always
  max_redirects: 5
  check.response:
    status: 404
  tags: ["onedoggo", "web"]
  fields:
    env: production
```

Kibana 也很貼心，會將 **Redirect** 的過程都一步一步的標示出來，讓我們知道過程中走過哪些路徑。

![image-20210919214559939](https://i.imgur.com/JgDGGVX.png)

### 自行定義 `fields` 欄位的資料，一方面在 Kibana 協助搜尋篩選，另外也能延伸自行使用

透過 `fields` 可以存放任何的資料，以下面為例，我們定義了 `env` 的欄位，裡面存了 `production` 的值，另外也定義了 `onedoggo` 的欄位，裡面是存放一整個物件。

```
- type: http # monitor type `http`. Connect via HTTP an optionally verify response
  id: onedoggo-web
  name: OneDoggo Web
  schedule: '@every 5s' # every 5 seconds from start of beat
  hosts: ["https://onedoggo.com"]
  ipv4: true
  ipv6: true
  mode: any
  supported_protocols: ["TLSv1.0", "TLSv1.1", "TLSv1.2"]
  # 記錄 Reponse header
  response.include_body: on_error
  check.response:
    status: 301
    headers:
    - location: https://onedoggo.com/98a4eb9cdd8b40ddb4a452af7577295d
  tags: ["onedoggo", "web"]
  fields:
    env: production
    onedoggo:
      product: homepage
      owner: joe
      tracking:
        user: standard
```

而這些設定，可以在 Kibana Uptime 的畫面協助過濾，另外也可以自行額外延伸使用，例如使用 heartbeat 所收集的資料，另外拉出 dashboard 等檢示的圖表。

![04-kibana-uptime-filter-fields](https://i.imgur.com/OWHYsTs.png)

### 將 Heartbeat 暫存在 Queue 中的資料，寫入 Disk 避免遺失

Heartbeat 所收集到的資料，在送出之前，預設是會存放在 Memory queue 當中，我們可以在 `heartbeat.yml` 中指定 queue 的方式：

```
queue.disk:
  max_size: 10GB
  path: /tmp/heartbeat/diskqueue
  max_retry_interval: 30s
```

透過類似以上的配置，可以讓 heartbeat 的資料寫入到 disk 中，避免 process 中斷時，資料直接遺失的風險。

### 使用 Autodiscover 自動偵測需要被監控的機器

Heartbeat 有支援 Autodiscover 的機制，像是使用 Docker, Kubernetes, AWS ELB，Heartbeat 可以自動幫我們偵測有哪些機器需要被監測，這部份的細節請參考官方文件 - Heartbeat Autodiscovery \[3]。

## 使用時你可能會遇到的坑

如果使用 `ICMP` 並且使用 MacOS 的環境，會發生這種 `Invalid argument` 的錯誤。

![image-20210919194254996](https://i.imgur.com/M1ez6fU.png)

有人已經在 Github 發 Issues ，而官方團隊也有回應 \[4]：

> Unfortunately it's still not our highest priority issue. One workaround would be using the docker image instead of plain heartbeat for now.

所以會需要特別切換成 `root` 來執行，或是先用 Docker 來繞路嘍。

## 參考資料

1. [官方文件 - Heartbeat quick start: installation and configuration](https://www.elastic.co/guide/en/beats/heartbeat/current/heartbeat-installation-configuration.html)
2. [官方文件 - Heartbeat HTTP options](https://www.elastic.co/guide/en/beats/heartbeat/current/monitor-http-options.html#monitor-http-max-redirects)
3. [官方文件 - Heartbeat Autodiscovery](https://www.elastic.co/guide/en/beats/heartbeat/current/configuration-autodiscover.html)
4. [Github Beat Issue - \[Heartbeat\] Rootless ping doesn't work on OSX](https://github.com/elastic/beats/issues/20007)


# 透過 Kibana 觀看心電圖及設定警報

### 本篇學習重點

* 如何使用 Kibana 的 Uptime 功能來觀察 Heartbeat 記錄的資料。
* 當發生問題時，如何透過 Alert 設定主動通知。

## Kibana 的 Uptime 功能介面

當我們透過前一篇所介紹的 Heartbeat 收集資料後，接下來我們要將介紹如何透過 Kibana 的 Uptime 功能，來檢視這些資料。

進入 **Kibana** 的主畫面後，點選主選單，並從 **Observability** 的區塊，可以找到 **Uptime** 的進入點。

![05-kibana-uptime-ui](https://i.imgur.com/h1l6TXB.png)

### Monitors 主畫面

點選進入後，我們可以看到 Uptime 的主要 Dashboard，畫面蠻單純，直接列出監控的彙總結果，包含多少個 Monitors 其中有哪些目前是 `Down` 以及過去指定的時間區間中，`Up` 與 `Down` 的 Histogram 圖表。

![05-kibana-uptime-overview](https://i.imgur.com/vj8Nh65.png)

在底下的**監控總覽**的區塊，會條列出每一項我們所設定的 **Monitor**，並且包含這些 Monitor 的主要狀態資訊。

### 指定 Monitor 的內容畫面

點選其中一個 Monitor 之後，可以進入 **ES Cluster Monitor** 的頁面，如下圖。

![05-kibana-uptime-monitor-detail](https://i.imgur.com/yqSRX5X.png)

這個 Monitor 的畫面會包含幾個重點，以上圖我的情境為例進行說明：

* 這一組 Monitor 項目的主要資訊，包含這組監控在指定時間之內的可用性 (availability) 比例 `100.00%`、監控這組服務的 URL `http://training.onedoggo.com`，監控的類型 `HTTP` 、有哪些 Tags `onedoggo` `training` `web`、以及 TLS Certificate 多久之後會到期 `Expires in 9 monthes`。
* 這一組 Monitor 項目，包含哪些從哪些地區的監控： `taipei` 與 `tokyo`，以及這些地區各自在這段時間的可性用 (availability) 比例及最後檢查的時間。
* **Monitor duration：** 這裡會列出每個地區在監控時，Heartbeat 所收到回應的時間，可以看出有沒有在那個時段或是哪個地區的延遲時間 (latency) 特別久。
* **Pings over time：** 在每個時段之中，總共 Pings 多少次，也就是代表發送了多少次 monitor check 的請求，以及異常與正常的比例 (圖中都是正常，所以都是灰色，有異常會是紅色)。
* **History：** 這部份會詳細的列出每次執行檢查請求的執行結果，包含結果是否正常 `Up` 或 `Down`、檢查的時間、從哪個 Geo Location 執行、HTTP status，花了多少時間 (Duration)、如果有錯誤的話，錯誤的內容是什麼 (如下圖)，另外如果有開啟記錄 HTTP response 的話，包含 HTTP Response Headers 與 Body 都會被詳細的記錄下來，在追查問題時很方便。

![image-20210920134237187](https://i.imgur.com/8V434gl.png)

### TLS Certificates 監控總覽

Kibana Uptime 會協助我們將所有 Monitors 之中有包含 **SSL/TLS** 的 Certificate 彙總起來，在 TLS Certificates 這裡可以看到所有的憑證列表及狀態，這個功能很方便，可以直接一覽所有憑證的狀態。

![05-kibana-uptime-tls-certificates](https://i.imgur.com/1Dm9G2M.png)

另外也能透過 Settings 的畫面，指定 Certificate 的過期通知的期限設定。

![05-kibana-uptime-setting-tls](https://i.imgur.com/Vs0q7eD.png)

### 除了 Kibana Uptime 的功能之外，可以匯入 Elastic 替 Heartbeat 建好的 Dashboard

若要使用 Elastic 替 Uptime 建立的 Dashboard，要另外到 Github (<https://github.com/elastic/uptime-contrib>) 下載，並且使用 Kibana > Stack Management > Saved Objects 的功能，使用 Import 將對應 Github 裡的 `http_dashboard.ndjson` 檔案匯入，匯入完成後，就可以到 Dashboard 選擇開啟 `Heartbeat HTTP monitoring` 的 Dashboard。

![image-20210920224716126](https://i.imgur.com/Yb2Sw5J.png)

## 設定 Alert 讓我們能主動接受到異常的通知

當有異常發生時，我們想要主動收到通知，這時就要使用到 Elastic Alert 的功能，我們首先要建立 Connector 來設定通知發送到哪邊，再透過 Uptime 進行 Alert 的設定。

> 這邊要注意 Alert 的功能有蠻多會是需要使用到進階的授權，不是免費的。

### 初始化 Kibana 的 Alert 設定

如果是第一次使用 Alerting 的功能，在進入 **Stack Management** 的 **Connectors** 設定中要建立 Connectors 時，會看到以下的畫面，提示我們會需要先設定好 `xpack.encryptedSavedObjects.encryptionKey`。

![05-alert-warning](https://i.imgur.com/nMvJ7f8.png)

要產生這個 **EncryptionKey** 可以透過 `kibana-encryption-keys` 的指令來協助產生，這個指令就放在 kibana 的 `bin` 資料夾裡面。\[1]

```
./bin/kibana-encryption-keys generate
```

執行指令後，就會產生 encryption keys。

![05-kibana-generate-encryption-key](https://i.imgur.com/TwBpBnR.png)

這個指令只會協助我們產生 key，我們需要將產生出來的設定，添加到 `config/kibana.yml` 裡面。

一但設定完成、重新啟動 kibana 之後，我們回到 Connectors 的設定頁面，就可以建立新的 Connector 了。

![05-kibana-create-connector](https://i.imgur.com/8bzZXfi.png)

### 建立 Connectors

這邊使用 Slack Connector 為例。

> 請注意：要使用 Slack Connector 會需要擁有 Gold License 以上的授權。

![image-20210920125136535](https://i.imgur.com/yGktEaa.png)

選擇 `Slack` 之後，在建立 Connector 的畫面，會出現 `Create a Slack Webhook URL` 的連結，點選下去之後，會導到 Elastic 的官方文件，裡面有完整的設定教學，當中有提到要進入 Slack WebHook 的設定頁面 <https://my.slack.com/services/new/incoming-webhook> 去建立 WebHook。

![image-20210920125453553](https://i.imgur.com/PqHzKwD.png)

指定 Alert 要通知的 Channel 之後，建立 WebHook integration，就可以拿到 `WebHook URL`。

![image-20210920125647084](https://i.imgur.com/cewo7JW.png)

接著在 Add Connector 的地方，把這些資訊填寫完成，並且建立 Connector。

![image-20210920130533941](https://i.imgur.com/SID4Xyv.png)

### Uptime 設定 Default Connectors

接下來，我們可以在 Uptime 設定 Default Connectors，讓之後 Uptime 有異常發生時，透過這些 Connectors 發送通知。

![05-kibana-uptime-setup-default-connector](https://i.imgur.com/mF0iTRn.png)

### 在 Monitors 中啟用 Alert 的通知

一但我們設定好 Alert 的 Connectors 之後，我們就可以到 Monitors 的頁面，啟動 **Status alert**，接下來有異常發生時，就會通知到我們指定的 Connecters 了。

![image-20210920135724633](https://i.imgur.com/Qhlcf5a.png)

### 設定 TLS Certifcate 快過期時的通知報警

如果我們想在 TLS Certificate 快要過期時收到通知，我們要在 Uptime 設定 Alert。

1. 建立 **Alerts and rules** 。
2. 選擇 **Create rules** 。
3. 選擇 **TLS rule** 。
4. 填寫基本資料後，選擇要使用的 Actions。
5. 按下 **Save**。

![05-kibana-create-tls-expire-alert](https://i.imgur.com/gxYfqcG.png)

### 從 Slack 接收 Alert 的通知

接著從 Slack 的畫面可以看到有發生問題、或是狀態恢復時通知的內容。

![05-slack-alert](https://i.imgur.com/w57y32q.png)

***

以上的介紹，是透過 Kibana 針對 Uptime 所提供的功能，讓我們能迅速的掌握系統的可用性狀態，以及能在異常時發送通知提醒，除了 Kibana Uptime 所提供的畫面之外，我們也能自己透過 Virtualize 等工具，從 Heartbeat 所記錄並存放在 Elasticsearch 裡的資料，建立我們自己想要檢視的圖表與儀表版 (Dashboard)，這部份就不在這裡介紹，有興趣的可以從官方 Kibana 的說明文件參考做法。

## 參考資料

1. [官方文件 - Alerting and action settings in Kibana](https://www.elastic.co/guide/en/kibana/7.14/alert-action-settings-kb.html#general-alert-action-settings)


# 使用合成監控 (Synthetics Monitor) 從使用者情境驗證服務的運作狀態

### 本篇學習重點

* 了解 Uptime 的最新功能合成監控 (Synthetic Monitor) 的能力
* 學習 Elastic Synthetic 的架構與使用方法

## 什麼是合成監控 (Synthetic Monitor)

Elastic Observability Uptime 中 Synthetic Monitor 功能的推出，主要是針對使用者體驗 (User Experience) 的角度所發展出來的功能，設計的目的是『**能夠在使用者遇到問題之前，先發現他們可能將遇到的問題**』，因此透過分析使用者存取網頁的情境，產生並模擬出這些情境的操作步驟，並且將這些步驟記錄下來，當作監控的依據，同時在發生異常時，也因為保留了這些步驟的歷程資料，能夠更即時的協助開發與運維的人員解決問題。

> 注意：Synthetic Monitor 是在 Elastic Observability 7.10 版發佈時推出的，推出的時間是在 2020 年 11 月，寫這篇文章時，已經到 Elastic Observability 7.14 版了，不過 Synthetic 還是在實驗 (Experimental) 的階段，也就是官方不建議直接使用在 Production 上，如果要使用的話，未來正式版推出時不見得會確保向前相容，這部份的風險要自己承擔。

## Elastic Synthetic 的運作架構

Elastic Synthetic 底層是使用 [Playwright](https://github.com/microsoft/playwright) 這個 Node.js 的函式庫，透過 Playwright 能使用簡單的 API 來操作 Chromium, Firefox, Webkit 等瀏覽器的核心，進而模擬出使用者操作瀏覽器的行為，因此 `@elastic/synthetics` 也就是使用 javascript 開發的函式庫，並且與 Heartbeat 所延伸開發出來的 `Heartbeat synthetics module` 進行整合，讓我們能夠直接使用 Heartbeat 來透過 `@elastic/synthetics` 執行指定的腳本，並且將這些結果透過 Heartbeat 記錄回 Elasticsearch。

Elastic Observability 同時也在 Kibana 的 Uptime app 之中深度的整合並支援 Synthetic Monitor，讓透過 Synthetic Monitor 記錄的資訊，也就能和其他 Heartbeat 所接收到的資訊一樣，可以直接在單一的入口網站 Uptime 能查看結果、也能使用 Alert 機制在異常的時候主動通知我們。

![06-synthetics-overview](https://i.imgur.com/dL1gKun.png)

## 使用 Synthetic

這邊建議大家參考 [官方文件 Synthetics Quickstart](https://www.elastic.co/guide/en/observability/current/synthetics-quickstart.html) \[2] 來參考如何進行配置及快速上手，主要的概念我這邊整理了一下，協助大家理解，但細節的請參考官方文件。

### 執行的環境

由於 Synthetic 的使用環境較複雜，因此官方建議使用 Docker 的環境來運作，不過在開發的階段，我們可以直接使用本地端來執行。

> 注意：由於官方提供的 Docker Image 目前喬叔使用 Apple M1 CPU 無法正常使用，應該是 Synthetic 裡所使用到的套件有支援度的問題，所以在花了許多時間嘗試卻失敗之後，我在使用 Docker 環境執行 Synthetic 的時候是使用 Windows + Linux VM 的環境來運作。

要在學會執行的話，最簡單的方式就是直接將官方 [Synthetic Github 的專案](https://github.com/elastic/synthetics/tree/master/) Clone 下來，學習他怎麼做：

```
git clone https://github.com/elastic/synthetics/tree/master/
```

在裡面的 `examples` 路徑下，分別有

* `docker`：使用 `run.sh` 可以運作一個 docker container 並執行路徑下的 `heartbeat.docker.yml` 所定義的腳本，或是使用 `bash.sh` 會運作 docker container 並且進入 docker 環境，可以直接在裡面執行要測試的腳本。
* `e-commerce`：這是一個較完整的電商示範網站，裡面也有一些較完整的腳本，不過目前電商網站掛了，要跑的話要自己架起來。
* `in-line`：裡面有幾個簡單的腳本，可以直接透過下方所教的執行 Synthetic 的方法來使用。
* `todos`：這是一個簡單的 ToDo 的 App，裡面也有包含兩種執行 Synthetic 方式的實作，可以供參考。

### 執行 Synthetic 方式

執行的方式分為兩種：

#### 使用 `@elastic/synthetic` 執行

可以透過

```
npm install @elastic/synthetic
```

的方式安裝，或是在上面提到的 example 的專案中，透過 `npm install` 來安裝 `package.json` 裡面指定好的相依的套件。

再來可以透過

```
npx @elastic/synthetics .
```

執行當下目錄的所有的 `.journey.ts` 或是 `.journey.js` 的腳本。

或是透過以下的指定，使用 `inline` 的方式執行 `xxx.js` 裡面所定義的腳本。

```
cat xxx.js | npx @elastic/synthetics --inline
```

> 請注意！這種方式只是單純的執行腳本的結果，並不會把結果傳送到 Elasticsearch 去，也就是在 Uptime 裡是看不到結果的。

#### 使用 `Heartbeat` 執行

若我們要透過 Heartbeat 執行時，要先定義好 `heartbeat.yml` 的設定，裡面可以使用兩種執行的方式 - `inline` 的腳本以及 `指定腳本檔案下載路徑` 的方式，例如 `todos` 的 examples 裡面的 `heartbeat.yml` 就有包含這兩種的例子。

```
heartbeat.monitors:
- type: browser
  id: elastic-website 
  name: Elastic website
  schedule: "@every 1m"
  source:
    inline:
      script: |- 
        step("load homepage", async () => {
            await page.goto('https://www.elastic.co');
        });
        step("hover over products menu", async () => {
            await page.hover('css=[data-nav-item=products]');
        });
- name: Todos
  id: todos
  type: browser
  schedule: "@every 1m"
  source:
    zip_url: 
      url: "https://github.com/elastic/synthetics/archive/refs/heads/master.zip" 
      folder: "examples/todos" 
      username: "" 
      password: ""
```

### 編寫 Synthetic 腳本

要編寫 Synthetic 腳本，主要是要了解 Playwright 的使用方式，主要編寫測試時會用到的語法如下：

* `journey`：用來測試一個主要的功能情境但擁有多個分散步驟的案例。
* `step`：一個 `journey` 裡面所執行的某個驟步，可以是開啟網頁、滑動到某個元件、點選某個按鈕…等。
* `beforeAll`、`before`、`afterAll`、`after`：這些分別用來定義執行 `journey` 之前、或之後要執行的動作。

一個 `journey` 裡面包含幾個重要的元件：

* `browser`：瀏覽器物件，瀏覽器層級的功能會在這邊可以使用。
* `page`：頁面，可跳轉頁面到指定的 URL，或是執行 screenshot，或是在某些頁面的事件事做事。
* `context`：`browser` 的某個 context，當有多個使用瀏覽器的情境、並且不希望共用 cookie, cache 時，就會建立多個 context。
* `params`：使用者定義的參數。

例如 examples `e-commerce` 裡面的一段範例：

```
journey({ name: 'Delete cart items', tags: ['cart'] }, ({ page, params }) => {
  navigateToProductDetail(page, params);

  step('Add items to cart', async () => {
    await page.selectOption('select[name="quantity"]', '2');
    await Promise.all([
      page.waitForNavigation({
        url: /cart/,
        waitUntil: 'networkidle',
      }),
      page.click('text=Add to Cart'),
    ]);
  });

  step('empty cart items', async () => {
    const headline = await page.$('.container h3');
    expect(await headline.textContent()).toContain(
      '1 items in your Shopping Cart'
    );
    await Promise.all([
      page.waitForNavigation({ waitUntil: 'networkidle' }),
      page.click('text=Empty cart'),
    ]);
  });

  step('verify empty shopping cart', async () => {
    await Promise.all([
      page.waitForNavigation({ url: /cart/, waitUntil: 'networkidle' }),
      page.click('text=View Cart'),
    ]);
    await page.waitForSelector('.container');
    const headline = await page.$('.container h3');
    expect(await headline.textContent()).toContain(
      'Your shopping cart is empty'
    );
  });
});
```

## 透過 Kibana Uptime 檢視 Synthetic Monitor 的結果

我們回到 Kibana 的 Uptime 主畫面，在這個畫面之中，我們可以看到有兩個 `Browser` 類型的 Monitor，這個就是使用 `@elastic/synthetic` 所收集到的資料，也就是專門是以使用者行為模擬出的前端網頁操作情境。

![06-kibana-uptime-browse](https://i.imgur.com/El9m1Gc.png)

### Synthetic Monitor 的內容頁面

接下來我們點選其中一個 **13th iTHome Ironeman OneDoggo - inline** 的 Monitor，觀看裡面的記錄細節，與一般的 Monitor 的結果差不多，不過可以發現在 History 的部份，多了畫面的截圖。

![06-kibana-Browser-Monitor-Overview](https://i.imgur.com/a5OZte3.png)

### 從 Uptime 查看 Synthetic 錄製的 `journey` 結果

點選 History 當中的某一筆記錄之後，我們可以看到這個 `journey` 的結果，總共包含三個部份：

* load OneDoggo team page
* goto Joe's page
* navigate to day 1 article

![06-kibana-monitor-journey](https://i.imgur.com/U0eTPDH.png)

### 可以展開 `step` 的執行細節

任何一個 `journey` 的 `step` 展開後，都可以看到這個 `step` 的執行語法與執行結果，甚至 Console output 也會記錄起來，對於要記錄資訊查問題，非常的方便。

![06-kibana-browser-step-detail](https://i.imgur.com/uY1JPU2.png)

### 針對 `step` 檢視效能的分析

點選某一個 `step` 的 `performance breakdown` 之後，可以查看這個頁面在載入時的細節。

![06-kibana-browser-performance-breakdown](https://i.imgur.com/lrJMDwH.png)

點選任何一個資源請求，可以查看當時完整的細節資訊，雖然不像 **Dev Tools** 這麼強大，但這是定期依照我們要的情境所記錄下來的資訊，能夠還原當時發生了什麼事，是不是某些資源當下載入的時間超過預期。

![06-kibana-browser-step-performance-breakdown-detail](https://i.imgur.com/hiBN9Rn.png)

***

這個 Synthetic 的功能實在是讓人很驚豔，本來想順便做一個監控團員們大家有沒有準時發文的 `journey`，但後來發現我最近花在寫鐵人賽文章的時間太久了，我家的狗一直關在家心情不是很好，所以只好收手，好好當個狗奴才去遛狗，期待 Elastic 正式推出 Synthetics Monitor 的功能！

## 參考資料

1. [Playwright Github](https://github.com/microsoft/playwright)
2. [官方文件 Synthetics Quickstart](https://www.elastic.co/guide/en/observability/current/synthetics-quickstart.html)


# Metrics - 觀察系統的健康指標

Metrics 是系統 Monitoring 的基礎，在這裡將介紹 Elastic Observability 中的 Metrics 提供了什麼樣的能力，如何實作在自己安裝的機器上、Docker、K8S、甚至是 AWS 的雲端環境，以及如何使用 Metricbeat 來掌握 Elastic Stack 的健康狀態。

* [01 - Metrics 與 Metricbeat 的基本介紹](/tech-sharing/uncle-joe-teach-es-elastc-observability/metrics-guan-cha-xi-tong-de-jian-kang-zhi-biao/metrics-yu-metricbeat-de-ji-ben-jie-shao)
* [02 - 使用 Metricbeat 掌握 Elastic Stack 的健康狀態](/tech-sharing/uncle-joe-teach-es-elastc-observability/metrics-guan-cha-xi-tong-de-jian-kang-zhi-biao/shi-yong-metricbeat-zhang-wo-elastic-stack-de-jian-kang-zhuang-tai)
* [03 - 使用 Metricbeat 掌握 Infrastructure 的健康狀態 Host 篇](/tech-sharing/uncle-joe-teach-es-elastc-observability/metrics-guan-cha-xi-tong-de-jian-kang-zhi-biao/shi-yong-metricbeat-zhang-wo-infrastructure-de-jian-kang-zhuang-tai-host-pian)
* [04 - 使用 Metricbeat 掌握 Infrastructure 的健康狀態 Docker 篇](/tech-sharing/uncle-joe-teach-es-elastc-observability/metrics-guan-cha-xi-tong-de-jian-kang-zhi-biao/shi-yong-metricbeat-zhang-wo-infrastructure-de-jian-kang-zhuang-tai-docker-pian)
* [05 - 使用 Metricbeat 掌握 Infrastructure 的健康狀態 Kubernetes 篇](/tech-sharing/uncle-joe-teach-es-elastc-observability/metrics-guan-cha-xi-tong-de-jian-kang-zhi-biao/shi-yong-metricbeat-zhang-wo-infrastructure-de-jian-kang-zhuang-tai-kubernetes-pian)
* [06 - 使用 Metricbeat 掌握 Infrastructure 的健康狀態 AWS 篇](/tech-sharing/uncle-joe-teach-es-elastc-observability/metrics-guan-cha-xi-tong-de-jian-kang-zhi-biao/shi-yong-metricbeat-zhang-wo-infrastructure-de-jian-kang-zhuang-tai-aws-pian)


# Metrics 與 Metricbeat 的基本介紹

### 本篇學習重點

* 什麼是 Metrics
* Metricbeat 的運作架構與設計概念簡介
* 怎麼安裝及設定 Metricbeat 來收集 Metrics 資料

***

## 什麼是 Metrics

Metric 的中文是『指標』，在維基百科的定義為『a measure of some property of a piece of software or its specifications』\[1]，也就是一個對於軟體其中某些屬性的量測方法，這邊的屬性可以像是作業系統的 CPU、Memory、Disk I/O、資料庫的連線數…等。

一般使用 Metrics 所關注的這些資訊，也就是我們用來觀察系統健康指標的重要依據，與先前介紹的 Uptime 相比，Uptime 強調的是『生死』，也就是系統是不是活著的，而 Metrics 強調的是『各種表達系統狀態的指標數字』，讓我們知道系統運作的情況，如果以人的健康來比喻，就好比人的體溫、心率、血壓、血糖、體重…等，這些資訊可以讓我們知道人體的身體健康狀態，能協助我們判斷是否要做出一些生活作息的調整、去看醫生、吃藥…等安排。

收集並監控系統的 Metrics 已經是系統穩定度監控 (Monitoring) 行之有年、也常重要、做法也非常成熟的一件事了， 而 Metrics 也是這系列文章的主軸 - Observability 的三本柱 (Metrics, Traces, Logs) 之一，我們接下來將介紹，如何在 Elastic 的解決方案中，收集 Metrics。

## Metricbeat 的介紹

Elastic 針對 Metrics 的收集，主要是使用 Elastic Beats 家族裡的 Metricbeat，如同先前介紹過的 Heartbeat 一樣，Metricbeat 也是基於 `libbeat` 所開發，並且是一個很輕量的常駐程式，可以安裝在某台主機上，持續的收集這台主機本身的系統指標、或是運作在這台主機上指定服務的指標資訊，也能從這台主機透過網路收集另台遠端主機上指定的某個服務的指標資訊，並且將這些資訊送到指定的地點，例如 Elasticsearch 或是 Logstash，最終能透過 Kibana 的 Dashboard，從單一入口，來觀看這些散落在各地的 Metrics 資料。

![07-metricbeat-system-dashboard](https://i.imgur.com/lTibBKB.png)

### Metricbeat 的運作架構

Metricbeat 本身定義了基本的資料收集、處理、送出的邏輯，而收集的方法，因為每種系統或服務的接口都不一樣、或是要收集的資訊也不同，因此這部份是用模組化 (Modulized) 的設定架構，針對各種不同的服務開發出各種 module，並且在 module 裡面實作對應的處理。

Metricbeat module 本身能收集到的資訊，也必然是該服務有對外揭露的資訊，以下圖 Redis module 為例，Redis module 有提供一個 `info` 的 Metricset，而這個 `info` 的 Metricset 就是透過 Redis 的 `INFO` 指令取得資訊，另外像是 MySQL module 所提供的 `status` 的 Metrics 則是使用 `SHOW GLOBAL STATUS` 的 SQL 語法取得的資訊。

![07-metricsbeat-module-overview](https://i.imgur.com/3OGewD0.png)

圖片來源：[官方文件 - How Metricbeat works](https://www.elastic.co/guide/en/beats/metricbeat/current/how-metricbeat-works.html) \[2]

### Metricbeat 的設計原理

這邊列出幾個 Metricbeat 的重點原理，透過了解 Metricbeat 的運作設計，讓我們知道該如何使用 Metricbeat，減少錯誤的期待。

* Metricbeat 除了收集指定系統或服務的 Metric 之外，如果收集的過程發生錯誤，Metricbeat 會發送 error event，讓我們在之後分析時能知道有發生拿不到 Metrics 的資訊。
* Metricbeat 不支援彙總 (aggregation)，每一個時間點收集到的 event 都是獨立一筆，不支援把多筆 event 的結果進行加總、總計等，有這些需求都需要等資料到 Elasticsearch 之後再進行處理。
* Metricbeat 相較於一般的收集 Metrics 的工具比，他記錄不只是數字的資料，在不同的 module 裡，甚至會包含文字的內容，所以每個 module 裡所定義的 Metricset 在 Elasticsearch 也都會有對應的 Mapping 定義，以優化資料在 Elasticsearch 裡儲存及搜尋的效能。
* Metricbeat 會在將資料傳送到 Elasticsearch 時，將多筆的 events 存在同一筆 document 之中來傳送，以減少資料傳輸的 overhead，並且在 Elasticsearch 裡面使用 `array` 或是 `nested object` 的方式來存取這些資料，如果我們會需要將資料的格式存成像是 [Metrics2.0](http://metrics20.org/) 這種標準格式的話，要注意到這些資料在 Elasticsearch 是被合併儲存的，當然我們也可以另外透過 Logstash 等方式，將這些 raw event 拆開儲存。

## Metricbeat 的使用方式

要使用 Metricbeat 之前，要先另外準備好 Elasticsearch 和 Kibana，接著再進行 Metricbeat 的安裝，以下是使用最簡單的安裝步驟來做介紹，其實與官方的 Quick start 的文件差不多，先大約知道將 Metricbeat 運作起來的流程為何，我將會以 `MacOS` 為例。

1. 下載，並解壓縮 Metricbeat。

```
curl -L -O https://artifacts.elastic.co/downloads/beats/metricbeat/metricbeat-7.14.2-darwin-x86_64.tar.gz
tar xzvf metricbeat-7.14.2-darwin-x86_64.tar.gz
```

1. 在解壓縮目錄下的 `metricbeat.yml` 指定 Elasticsearch 的位置

```
output.elasticsearch:
  hosts: ["myEShost:9200"]
  username: "metricbeat_internal"
  password: "YOUR_PASSWORD" 
```

1. 同樣在 `metricbeats.yml` 裡，也指定 Kibana 的位置，這是接下來要匯入 Dashboard 所使用的。

```
setup.kibana:
  host: "mykibanahost:5601" 
  username: "my_kibana_user"  
  password: "{pwd}"
```

1. 啟動要安裝的模組

```
./metricbeat modules enable {module_name}
```

Metricbeat 提供了非常多內建的模組 (modules)，像是 `Apache`、`HTTP`、`Nginx`、`MySQL`、`PostgreSQL`、`Redis`、`MongoDB`、`HAProxy`、`Zookeeper`...等，詳細可以查看 [官方文件 Metricbeat Modules](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-modules.html) \[3]。

另外針對啟動的模組，通常都會要調整這些模組的 config 檔，檔案的路徑就在 `./modules.d/` 裡面，檔名就會是 module 的名字，副檔名為 `.yml`。

1. 安裝 Metricbeat 內建的 Kibana Dashboard，以及 Elasticsearch 的 Index Template。

```
./metricbeat setup -e
```

1. 啟動 Metricbeat

```
./metricbeat -d
```

若是要以 `root` 執行，要記得把 config 的擁有者也改成 `root`

```
sudo chown root metricbeat.yml 
sudo chown root modules.d/system.yml 
sudo ./metricbeat -e
```

接下來就可以到 Kibana 查看 Metricbeat 所發送的資料，有沒有成功的進入到 Elasticsearch了。

## 參考資料

1. [Wikipedia - Metric](https://en.wikipedia.org/wiki/Metric)
2. [官方文件 - How Metricbeat works](https://www.elastic.co/guide/en/beats/metricbeat/current/how-metricbeat-works.html)
3. [官方文件 - Metricbeat Modules](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-modules.html)


# 使用 Metricbeat 掌握 Elastic Stack 的健康狀態

### 本篇學習重點

* Kibana 的 Elastic Stack Monitoring 簡介
* 監控 Elastic Stack 健康狀態的實踐技巧
* 如何使用 Metricbeat 收集 Elastic Stack 的健康狀態

***

在進入介紹如何使用 Metrics 掌控我們的 Infrastructure 之前，由於我們使用的是 Elastic Stack 這個解決方案來當作我們 Observability 工具，因此在這邊要先介紹如何透過 Metricbeat 來掌控 Elastic Stack 這組工具的健康狀態。

## Kibana 的 Stack Monitoring

身為 Elastic Stack 的最主要的入口 - Kibana，當然也擁有監控整個 Elastic Stack 的能力， 下圖是 Kibana 的 Stack Monitoring 的畫面，在這張圖當中，我們可以看到 Elastic Stack 的主要產品都能在這個 Dashboard 上觀看每個服務的 Metrics 狀態，目前這個畫面所呈現的資訊，除了我截圖當下的 Elastic Stack 還沒有串接 Filebeat，因此 **Logs** 的區塊顯示為黃色的 `No log data found` 也就是還沒有資料，其他所有看到的資訊都是透過 Metricbeat 所收集到的。

![08-kibana-stack-monitoring-overview](https://i.imgur.com/Hdm96dC.png)

進入 **Stack Monitoring** 的功能畫面，可以從 Kibana 左方的功能選單，找到 **Management** > **Stack Monitoring**。

![08-kibana-stack-monitoring-entry](https://i.imgur.com/AHySsHr.png)

Kibana 的 Stack Monitoring 擁有非常詳細的 Elastic Stack 的資訊，以下針對 Observability 相關舉例列出說明 (非全部的功能)：

* **Elasticsearch：** 整個 Cluster 的狀態、Node 的狀態、Index 狀態、即時 indexing 與 searching 存取的數據、shard 的狀態、各種 Metrics…等。
* **Kibana：** 總共有多少個 Kibana Instance、請求的數量、回應的時間、記憶體用量…等。
* **Logstash：** 所有 Nodes 的狀態、即時掌握有多少 events 在處理、Pipeline 的數量與執行狀況、甚至 Pipeline 裡的每個步驟所處理的 event 數都能即時看到。
* **Beats：** 各種 Beats 的 events 處理監控、失敗率、記憶體用量、資料傳輸流量…等。
* **APM Server：** 掌握所有的 APM Server、收了多少 requests、處理了多少的 events、記憶體用量、資料傳輸流量…等。

在這邊就不細節的介紹每個功能，大家可以從官方文件去查閱，另外也可以使用 [Elastic Demo 網站](https://https://demo.elastic.co/) 來試玩。

## 監控 Elastic Stack 的健康狀態的實踐技巧

當我們要來監控 Elastic Stack 的健康狀態時，這邊列了幾個小技巧提供大家參考。

### 盡可能使用獨立的 Monitoring Elasticsearch Cluster

如果在規劃使用 Elasticsearch 的時候，不是單純的當作監控使用，而是有用來提供產品特定的服務，例如：產品的搜尋、當成 NoSQL 資料庫、當作另一個服務的資料來源，在這樣的情況下，就會建議將收集 Monitoring 的資訊這項任務，獨立安排另一個 Elasticsearch Cluster 來處理，以避免監控的資料或是人為的操作，影響到正式服務的運作，另外如果有多個 Elasticsearch Cluster 在運作時，也會建議另外安排獨立的 Monitoring 專用的 Elasticsearch Cluster，來監控這些 Cluster 的狀態。

### 使用 Metricbeat 收集 Elastic Stack 的服務 Metrics，而不要用服務自己本身的 Monitoring 機制來發送 Metrics 資訊

當服務不穩定的時候，有時是因為主機掛掉，又或是 CPU loading 過重、網路有問題、記憶體不夠…等等，一但當這些問題發生時，通常很高的比例服務本身就是無法正常運作，所以如果我們的 Metrics 資訊是由服務本身傳送給 Elasticsearch，代表出問題的時候，我們會拿不到關鍵的數據。

因此會強烈建議使用另外部署的 Metricbeat 來收集服務或系統的 Metrics，不要直接透過 Elasticsearch、Kibana、Beats…等本身所提供的傳送 Monitoring 資訊的功能。

> Elastic 官方也很重視這件事，所以已經開始準備棄用由服務本身傳送 Metric 資訊的功能，建議大家提早全面使用 Metricbeat 來收集這些資訊。

### 使用獨立部署的 Metricbeat 來收集 Metrics

如同前一點所提到的，當問題發生時，很常時候服務所在的主機都有異常，因此不建議直接將 Metricbeat 直接與服務安裝在同一台主機上，應該使用獨立部署與安裝的方式，讓 Metricbeat 透過網路收集這些系統或服務的 Metrics。

## 使用 Metricbeat 監控 Elastic Stack

以下將說明如何使用 Metricbeat 來取得監控 Elastic Stack 的資訊。

### 設定以使用 Stack Monitoring 來觀看 Metrics 數據

要讓 Metricbeat 所收集到的 Elastic Stack Metrics 資訊，出現在 Kibana 的 Stack Monitoring 之中，會需要特別的設定，並且 Metricbeat 僅支援以下幾種 modules 支援這種設定：

* Elasticsearch module
* Beats module
* Kibana module

在這些有支援的 modules 裡，可以有二種設定的選擇：

1. 使用 `-xpack` 結尾的 module name。
2. 使用 `elasticsearch`、`kibana`、`beats` 這種非 `-xpack` 結尾的 module，但是把 `xpack.enabled: true` 開啟，並且移除所有額外設定的 `metricsets`。

> 注意：設定要透過 Stack Monitoring 觀看資訊的這些 module，Metricbeat 會將資料傳送到 Elasticsearch 的 Index `.monitoring-*` 儲存，而非預設 Metricbeat 的 Index `metricbeat-*`，

### 設定 Elastic Stack 與 Metricbeat

接下來將各別介紹 Elasti Stack 的設定方式，在每個服務設定的說明，我們會分成兩個部份，Metricbeat 端與服務端的配置方式：

#### 設定 Elasticsearch

**Elasticsearch 端的配置**

由於 Metricbeat 要收集的 Metrics 資訊，其實都在 Elasticsearch 一般的 RESTful API 裡了，所以並不需要特別開啟另外的 Metrics 專用 API，所以預設的 Elasticsearch 不用特別配置，不過可以特別留意，不要開啟 `xpack.monitoring.collection.enabled` 的設定，以避免 Elasticsearch 自行將 Metrics 資訊傳送進 Elasticsearch 的 Cluster 之中。\[1]

**Metricbeat 端的配置**

首先要開啟 `elasticsearch-xpack module`

```
./metricbeat module enable elasticsearch-xpack
```

接著在 `./modules.d/elasticsearch-xpack.xml` 設定 Elasticsearch 的位置，每個 node 的 host 都要設定好。

```
- module: elasticsearch
  xpack.enabled: true
  period: 10s
  hosts: ["http://localhost:9200","http://localhost:9201"]
```

設定完成後，重新啟動 metricbeat 即可。

#### 設定 Logstash

**Logstash 端的配置**

由於 Metricbeat 要收集的 Metrics 資訊，在 Logstash 會要另外開啟 metrics 資訊的 API，這個部份會要調整 `logstash.yml` 的配置檔。

```
# ------------ HTTP API Settings -------------
# Define settings related to the HTTP API here.
#
# The HTTP API is enabled by default. It can be disabled, but features that rely
# on it will not work as intended.
http.enabled: true
#
# By default, the HTTP API is bound to only the host's local loopback interface,
# ensuring that it is not accessible to the rest of the network. Because the API
# includes neither authentication nor authorization and has not been hardened or
# tested for use as a publicly-reachable API, binding to publicly accessible IPs
# should be avoided where possible.
#
# http.host: 127.0.0.1
#
# The HTTP API web server will listen on an available port from the given range.
# Values can be specified as a single port (e.g., `9600`), or an inclusive range
# of ports (e.g., `9600-9700`).
#
http.port: 9600-9700
```

主要就是把 `http.enabled: true` 打開，並且指定 `http.port`。

**Metricbeat 端的配置**

開啟 `logstash-xpack module`

```
./metricbeat module enable logstash-xpack
```

接著在 `./modules.d/logstash-xpack.xml` 設定 Logstash metrics API 的位置。

```
- module: logstash
  xpack.enabled: true
  period: 10s
  hosts: ["localhost:9600"]
```

設定完成後，重新啟動 metricbeat 即可。

#### 設定 Beats

Beats 的設定在 Elastic Stack 中的各種 Beats： `filebeat`、`metricbeat`、`heartbeat`…等的設定方式都是一樣的，唯一不同的是 `.yml` 的檔名，這部份大家自己去對應一下，以下會以 `filebeat` 為例。

> 小提醒：如果使用同一台主機安裝多種 beats 時，記得 port 不要衝突。

**Beats 端的配置**

由於 Metricbeat 要收集的 Metrics 資訊，在 Beats 也要另外開啟 metrics 資訊的 API，這個部份會要調整 `filebeat.yml`、`heartbeat.yml`、`metricbeat.yml` …等的配置檔。

```
# =============================== HTTP Endpoint ================================

# Each beat can expose internal metrics through a HTTP endpoint. For security
# reasons the endpoint is disabled by default. This feature is currently experimental.
# Stats can be access through http://localhost:5066/stats . For pretty JSON output
# append ?pretty to the URL.

# Defines if the HTTP endpoint is enabled.
http.enabled: true

# The HTTP endpoint will bind to this hostname, IP address, unix socket or named pipe.
# When using IP addresses, it is recommended to only use localhost.
#http.host: localhost

# Port on which the HTTP endpoint will bind. Default is 5066.
http.port: 5066
```

主要就是把 `http.enabled: true` 打開，並且指定 `http.port`。

**Metricbeat 端的配置**

開啟 `beat-xpack module`

```
./metricbeat module enable beat-xpack
```

接著在 `./modules.d/logstash-xpack.xml` 設定 Logstash metrics API 的位置。

```
- module: beat
  xpack.enabled: true
  period: 10s
  hosts: ["http://localhost:5066","http://localhost:5067","http://localhost:5068","http://localhost:5069"]
  #username: "user"
  #password: "secret"
```

設定完成後，重新啟動 metricbeat 即可。

#### 設定 APM Server

**APM Server 端的配置**

APM Server 的設定方式，其實和 Beats 一樣，底層應該是同樣的實作方式，要調整 `apm-server.yml` 配置檔。

```
#=============================== HTTP Endpoint ===============================

# apm-server can expose internal metrics through a HTTP endpoint. For security
# reasons the endpoint is disabled by default. This feature is currently experimental.
# Stats can be access through http://localhost:5066/stats. For pretty JSON output
# append ?pretty to the URL.

# Defines if the HTTP endpoint is enabled.
http.enabled: true

# The HTTP endpoint will bind to this hostname or IP address. It is recommended to use only localhost.
http.host: localhost

# Port on which the HTTP endpoint will bind. Default is 5066.
http.port: 5066
```

主要就是把 `http.enabled: true` 打開，並且指定 `http.port`。

**Metricbeat 端的配置**

開啟 `beat-xpack module`

> 小提醒：如果 Beats 已經有開啟過，就不用再次開啟，只要把對應的 port 加入設定檔即可

```
./metricbeat module enable beat-xpack
```

接著在 `./modules.d/logstash-xpack.xml` 設定 Logstash metrics API 的位置。

```
- module: beat
  xpack.enabled: true
  period: 10s
  hosts: ["http://localhost:5066","http://localhost:5067","http://localhost:5068","http://localhost:5069"]
  #username: "user"
  #password: "secret"
```

設定完成後，重新啟動 metricbeat 即可。

### 完成設定後，開始使用 Kibana Stack Monitoring 吧!

透過以上的設定配置，就可以透過 Metricbeat 收集 Elastic Stack 的服務狀態資訊，接下來打開 Kibana 即可查看 Stack Monitoring。

## 參考資料

1. [官方文件 - Elasticsearch Monitoring Settings](https://www.elastic.co/guide/en/elasticsearch/reference/current/monitoring-settings.html)
2. [官方文件 - Metricbeat Reference](https://www.elastic.co/guide/en/beats/metricbeat/7.14/index.html)


# 使用 Metricbeat 掌握 Infrastructure 的健康狀態 Host 篇

### 本篇學習重點

* 如何設定 Metricbeat 並且以 Host 部署的方式來收集 Elastic Observability 所需的資訊
* 使用 Kibana Observability 掌控 Infrastructure 當中各服務與主機狀態的簡介

## Infrastructure 監控的示範情境

由於我在寫文章的當下，手邊沒有合適的實際環境來當作示範，所以只能以我目前手邊有的機器，來模擬一些情境，主要是介紹配置的方式來說明，實際上若有更複雜的情境，可以以同樣的類推以進行部署。

以下是我目前的進行的環境模擬：

| 服務名稱                                | 主機環境                                       | 情境說明                                                                            |
| ----------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------- |
| `opbeans-node` web server           | Mac mini's Docker container                | 主要只是讓服務運作在 Container 之中                                                         |
| `opbeans-node` PostgreSQL DB server | Mac mini's Docker container                | 主要只是讓服務運作在 Container 之中                                                         |
| `opbeans-node` Redis server         | Mac mini's Docker container                | 主要只是讓服務運作在 Container 之中                                                         |
| Metricbeat                          | Mac mini                                   | 透過 Mac mini 的 Metricbeat 當作是 Infrastructure 當中，專門集中化收集 `opbeans` 這組服務的 Metrics。 |
| Metricbeat                          | web-server-1 @ Mac mini's Docker container | 在 Container 之中，模擬另一台主機，並在裡面安裝獨立的 Metricbeat 收集 System info。                     |
| Metricbeat                          | web-server-2 @ Mac mini's Docker container | 在 Container 之中，模擬另一台主機，並在裡面安裝獨立的 Metricbeat 收集 System info。                     |

### opbeans-node 示範 App

`opbeans-node` 是 Elastic 官方維護的一個 Demo App，他是一個庫存管理的系統，

我們可以從 Github 取得他的 Source Code：<https://github.com/elastic/opbeans-node>

整個 stack 就只有簡單的 Web Server + PostgreSQL + Redis Server，架設起來長相如下：

![09-opbean-web](https://i.imgur.com/MG57lhP.png)

## 如何設定 Metricbeat 來進行監控 Metrics 的收集

由於我們要使用 Mac Mini 當作 Metricbeat 監控的主要收集點，我們首先要先安裝 Metricbeat，這部份請參考先前 [Metricbeat 基本介紹的文章](https://ithelp.ithome.com.tw/articles/10269764)。

接著我們分別針對我們這次要監控的情境進行設置。

### 使用 System module 收集機器的系統 Metrics

收集 System Metrics 是監控 Infrastructure 最基本的要取得的資訊，基本上整個 Infra 之中的每台主機，我們都應該要掌握機器的 System Metrics，不過因為要收到的夠詳細的資訊的話，Metricsbeat 的佈署必須要安裝在本機，無法透過 Remote 的方式直接取得，所以在我們的情境之中，會要在以下三台 Host (其中有兩台是用 docker 模擬的) 都安裝好 Metricbeat。

* Mac Mini
* web-server-1
* web-server-2

接著啟用 System module

```
./metricbeat enable system
```

並且在 `./modules.d/system.yml` 調整相關的配置

```
- module: system
  period: 10s
  metricsets:
    - cpu
    - load
    - memory
    - network
    - process
    - process_summary
    - socket_summary
    # - entropy
    # - core
    # - diskio
    # - socket
    # - service
    # - users
  process.include_top_n:
    by_cpu: 5      # include top 5 processes by CPU
    by_memory: 5   # include top 5 processes by memory
  # Configure the mount point of the host’s filesystem for use in monitoring a host from within a container
  #system.hostfs: "/hostfs"

- module: system
  period: 1m
  metricsets:
    - filesystem
    - fsstat
  processors:
  - drop_event.when.regexp:
      system.filesystem.mount_point: '^/(sys|cgroup|proc|dev|etc|host|lib|snap)($|/)'

- module: system
  period: 15m
  metricsets:
    - uptime
```

我們可以設定不同的 `period` 來以不同的頻率收集不同的 `metricsets` 。

> 注意：由於 System module 要收集許多系統層級的資訊，會需要的權限會較高，官方文件有特別提醒要謹慎的開放權限。\[1]

### 啟用 PostgreSQL module

接著我們要啟用 PostgreSQL module

```
./metricbeat enable postgresql
```

在 `./modules.d/postgresql.yml` 調整相關的配置

```
- module: postgresql
  metricsets:
    # Stats about every PostgreSQL database
    - database
    # Stats about the background writer process's activity
    - bgwriter
    # Stats about every PostgreSQL process
    - activity
  period: 10s
  hosts: ["postgres://localhost:5432?sslmode=disable"]
  username: postgres
  password: _PASSWORD_
```

> 這邊要注意，因為我的示範環境 PostgreSQL 沒有開啟 SSL，所以有特別加上 `sslmode=disable` ，這個不應該在正式環境出現。

### 啟用 Redis module

接著啟用 Redis module

```
./metricbeat enable redis
```

在 `./modules.d/redis.yml` 調整相關的配置

```
- module: redis
  metricsets:
   - info
   - key
   - keyspace
  period: 10s

  # Redis hosts
  hosts: ["127.0.0.1:6379"]

  # Network type to be used for redis connection. Default: tcp
  #network: tcp

  # Max number of concurrent connections. Default: 10
  #maxconn: 10

  # Redis AUTH password. Empty by default.
  #password: foobared
  
  key.patterns:
    - pattern: 'pipeline-*'
      limit: 20
```

在這裡我有啟用 `key` 的資訊收集，所以要設定好 `key.patterns` 設定。

以上三種模組只是個示範，Metricbeat 裡已經整合好 60 多種的模組，可以直接使用，對於想要監控自己 host 的各種服務，可以簡單的開啟即使用。

設定完成後，啟用 Metricbeat 我們就可以到 Kibana Observability 來觀察收集到的資訊。

## 使用 Kibana Observability 來掌握 Infrastructure 的 Metrics

### Inventory 概觀

從 Kibana Observability 的 Metric Inventory 畫面，我們先針對 `Hosts` 的方式來檢示，預設的 Metric 是觀看 `CPU usage` ，這部份我們可以自己依需求調整，甚至可以儲存成不同的 view，方便日後切換檢示。

由於我的情境，只有三台 Host，所以看到的畫面如下圖，三台機器全部列在 Inventory 的列表上，並且即時的顯示 CPU 的使用量，而若是要檢示歷史的數據變化，在最底下有檢示歷史數據的走勢圖。

![09-kibana-obs-metrics-inventory](https://i.imgur.com/28nJXgJ.png)

### Inventory 的分群與搜尋檢示

Inventory 預設的檢示畫面其實很陽春，我們可以多透過 **Group by** 的功能，來使用像是 `Service type` 或甚至自己定義的欄位也可以用來 Group by，只要是 index 裡面有收集到的資訊，都可以使用，所以先前所建議我們可以使用 `Tags`、`Fields` 等自訂義的欄位，就能派上用場。

![09-kibana-obs-inventory-group-by](https://i.imgur.com/5o3Sd0F.png)

使用 `Service type` Group By 之後的結果如下，可以更快速的專注在某一塊要觀察的主題上。

![09-kibana-obs-inventory-service-view](https://i.imgur.com/qajiyFu.png)

### 從 Inventory 的概觀，進入到 Host 的細節，更深入的 Observability

一但我們發現某一台機器有異常，想要多觀察時，這時只要點下這台機器，我們馬上可以進入細節的頁面，而這個細節的頁面，也就是 Elastic Observability 整合好各種資料檢示的入口，讓我們能從 Metrics、Logs、Processes…等各種資訊來盤查問題，也可以查看 Machine Learning 的 Anomalies 的執行狀況，甚至可以直接連接到 APM 與 Uptime 繼續追縱。

![09-kibana-obs-inventory-detail-view](https://i.imgur.com/qqRSA4e.png)

### Kibana 另外內建的 Dashboard

透過 Metriccbeat 所收集的資訊，除了 Kibana Observability 這邊可以看到之外，特別是針對像是 System、PostgreSQL、Redis 的服務，Elastic 也有預先建立好這些服務所專用的各種 Dashboard，非常的豐富，可以從 **Kibana** > **Analytics** > **Dashboard** 去搜尋。

![09-kibana-metricbeat-dashboard](https://i.imgur.com/NU7v9Qi.png)

以下圖為例，就是 **\[Metricbeat System] Host overview ECS** 的 Dashboard。

![image-20210924222311296](https://i.imgur.com/SXDV4sq.png)

## 本章小結

針對 Host 服務所要觀察的 Metrics 資訊，Elastic 透過 Metricbeat 整合好許多的服務，也建立了各種不錯的 Dashboard，這部份對於我們要快速的掌控系統及服務的狀態，能很容易的上手，同時 Elastic Observability 針對 Metrics, Logs, Uptime, Trace 的整合也做得蠻不錯，讓我們要追縱問題時，可以容易的將這些資訊串連在一起，在 Observability 上的確有不錯的幫助。

## 參考資訊

1. [官方文件 - Metricbeat System Module](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-module-system.html)


# 使用 Metricbeat 掌握 Infrastructure 的健康狀態 Docker 篇

### 本篇學習重點

* 如何使用 Docker 來佈署 Metricbeat
* 使用 Metricbeat 在 Docker 環境中，如何取得宿主實體機器或是整體 Docker Containers 的系統 Metrics
* 如何透過 Metricbeat 輕鬆的來掌握 Docker 環境內的各 Containers 運作服務的健康狀態

## 使用 Docker 部署 Metricbeat

這篇要介紹的主題是 **Docker** ，因此這邊我們先來介紹如何使用 Docker 來部署 Metricbeat。\[1]

### Docker Image

Elastic 官方在 [Docker @ Elastic](https://www.docker.elastic.co/) 發佈了兩種版本的 Metricbeat Docker Image，底層是使用 CentOS 7。

* beats/metricbeat
* beats/metricbeat-oss

差別是什麼，老實說我稍微查了一下找不出差異，官方說授權的部份參考 [Subscription](https://www.elastic.co/subscriptions) 頁面，不過裡面針對 Metricbeat 的部份沒有描述到不同，不過大概可以確定的是 X-Pack 基本上是 Elastic License 的功能，所以 OSS 這個 Open Source 的版本應該是沒有 X-Pack 的功能的，至於其他的部份可能要再另外深入挖掘才知道。

不過由於 Elastic License 目前已經很寬鬆了，除了是要提供 SaaS 的服務，不然不太會踩到 License 的問題，接下來會用一般 Elastic License 的版本來操作。

可以透過 `docker pull` 取得 Docker Image，讓這個 image 下載到本機端。

```
docker pull docker.elastic.co/beats/metricbeat:7.15.0
```

### 透過 Docker 環境執行 Metricbeat 的 Setup

由於 Metricbeat 在第一次運行之前，我們會需要執行下面兩個動作：

* 在 Elasticsearch 設定好 Metricbeat 要使用的 Index Template
* 在 Kibana 匯入 Metricbeat 內建好的 Dashboard

這兩動作我們需要執行 `./metricbeat setup` 的使令，所以在 Docker 的環境中，我們也要先單獨執行一次這個指令：

```
docker run \
docker.elastic.co/beats/metricbeat:7.15.0 \
setup -E setup.kibana.host=kibana:5601 \
-E output.elasticsearch.hosts=["elasticsearch:9200"]
```

這邊的 `kibana:5601` 與 `elasticsearch:9200` 的位置，要自己視情況修改成正確的 Kibana 與 Elasticsearch 的位置。

### 使用 Docker 運行 Metricbeat

要開始運作 Metricbeat 也就是執行 `docker run` 並執行 `metricbeat -e` 的 Command：

```
docker run -d \
  --name=metricbeat \
  --user=root \
  --network=testnet \
  --volume="$(pwd)/metricbeat.docker.yml:/usr/share/metricbeat/metricbeat.yml:ro" \
  --volume="/var/run/docker.sock:/var/run/docker.sock:ro" \
  docker.elastic.co/beats/metricbeat:7.15.0 metricbeat -e \
  -E output.elasticsearch.hosts=["elasticsearch:9200"]  
```

啟動時有幾個參數可能會需要設置：

* `--user=root`: 如果有使用 System Module 時，有些 socket 或是 system process 的資訊，會需要有足夠的權限才能存取，這部份的權限管理會需要留意。
* `--volume`: 若是有獨立準備 `metricbeat.yml` 的 config 檔，會需要 mount 到 `/usr/share/metricbeat/metricbeat.yml` 的路徑上。
* `-E coutput.elasticsearch.hosts=`: 如果沒有在 `metricbeat.yml` 特別設定，要使用環境變數指定 Elasticsesarch 的位置時，也要記得加上。
* `--net=`: 如果有要透過 Metricbeat 去收集其他運作在 Docker Container 內服務的 Metrics，要記得 Network 的部份能夠存取得到。

## 在 Docker 環境中，Metricbeat 的設定方式

使用 Metricbeat 運行在 Docker Container 之中時，一般會有三大類要收集的 Metrics：

1. 運行 Container 的實體主機的 System Metrics
2. 每個 Docker Containers 的系統 Metrics
3. 其他 Docker Containers 的服務 Metrics

以下我們分別來說明這些配置上的方式。

### 讓 Metricbeat 在 Docker Container 內取得實體主機的 System Metrics

由於 System Module 針對不同的 Metricset 會從幾個不同的系統位置取得資訊：

* `/proc`: System Module 的許多資訊其實是來自這個 Linux proc filesystem 的位置。
* `/sys/fs/cgroup`: System process 的 metricset 會從這個位置取得 process 的資訊。
* `/proc/net/dev`: System network 的 metricset 會從這個位置取得網路的資訊。

因此我們會需要將這些主機實體位置，mount 到 Docker container 之中，讓運作在 Docker container 內的 Metricbeat 可以存取得到實體主機的這些資訊。

```
ocker run \
	--user root --cap-add sys_ptrace --cap-add dac_read_search \
  --mount type=bind,source=/proc,target=/hostfs/proc,readonly \ 
  --mount type=bind,source=/sys/fs/cgroup,target=/hostfs/sys/fs/cgroup,readonly \ 
  --mount type=bind,source=/,target=/hostfs,readonly \
  docker.elastic.co/beats/metricbeat:7.15.0 -e -system.hostfs=/hostfs
```

上面的例子是把這些路徑都 mount 在 container 內的 `/hostfs` 裡，並且在取後執行 `metricbeat` 時，加上參數指定這個位置 `-system.hostfs=/hostfs`

另外如果有使用 System socket 的 metricset 時，因為需要較高的權限，我們會需要特別加上 `sys_ptrace` 與 `dac_read_search` 的 System capability。

> 這邊要注意，上面提到的 `/proc` 、`/sys`、System capabilies…等設定是針對 Linux 環境，不適用於 Windows 或 MacOS。

### 使用 Metricbeat 取得整體 Docker Containers 的系統 Metrics

要使用 Metricbeat 來取得 Docker Containers 的資訊時，我們要使用的是 Metricbeat 裡的 Docker module。

```
./metricbeat modules enable docker
```

並且在要在 `metricbeat.yml` 裡，設定要收集的 docker metricsets，以及相關的配置。

```
metricbeat.modules:
- module: docker
  metricsets:
    - "container"
    - "cpu"
    - "diskio"
    - "healthcheck"
    - "info"
    #- "image"
    - "memory"
    - "network"
  hosts: ["unix:///var/run/docker.sock"]
  period: 10s
  enabled: true
```

這邊要注意到，由於 docker module 會透過 `/var/run/docker.sock` 與 docker 溝通，所以我們如果是運行在 Docker container 內的 Metricbeat，我們也會需要把實體主機的 `/var/run/docker.sock` mount 到 docker container 內，讓 Metricbeat 可以存取得到。

```
docker run -d \
  --volume="/var/run/docker.sock:/var/run/docker.sock:ro" \
  docker.elastic.co/beats/metricbeat:7.15.0 metricbeat -e \
  -E output.elasticsearch.hosts=["elasticsearch:9200"]  
```

### 使用 Metricbeat 取得其他 Docker Container 身上服務的 Metrics

要使用 Metricbeat 取得其他 Dockre Containre 所運作的服務的 Metrics 時，可以直接指定網路的位置，並且指定在相同的 docker network，讓 Metricbeat 可以存取得到其他服務，或是可以使用 Metricbeat 的 **Autodiscover** 功能。

由於在 Container 的環境之中，機器可能會時常開關，能夠動態的自動監控 Container 會比較實用，因此我們也就會以 Autodiscover 的介紹為主。

#### 設置 Autodiscover 自動找尋需監控的機器 \[2]

Metricbeat 的 Autodiscover 有支援兩種 Provider - `Docker` 與 `Kubernetes` ，這篇會以 Docker 為主要說明。

由於 Docker provider 會監聽 [Docker events](https://docs.docker.com/engine/reference/commandline/events/) \[3]，在 Docker Container `start` 或 `stop` 的時候，去更新需監控機器的列表，因此相關設定上，可以依照 Docker event 的資訊來進行篩選條件的設置，來決定哪些 container 要套用設定。

Docker event 的資訊如下：

```
{
  "host": "10.4.15.9",
  "port": 6379,
  "docker": {
    "container": {
      "id": "382184ecdb385cfd5d1f1a65f78911054c8511ae009635300ac28b4fc357ce51"
      "name": "redis",
      "image": "redis:3.2.11",
      "labels": {
        "io.kubernetes.pod.namespace": "default"
        ...
      }
    }
  }
}
```

這邊可以用來當篩選條件的欄位也就是：

* host
* port
* docker.container.id
* docker.container.image
* docker.container.name
* docker.container.labels

接下來是要在 `metricbeat.yml` 當中，設定 Autodiscover，並且依照上面提到的篩選條件，來設定 `condition`，決定哪些 container 是我們的目標對象。

```
metricbeat.autodiscover:
  providers:
    - type: docker
      labels.dedot: true
      templates:
        - condition:
            contains:
              docker.container.image: redis
          config:
            - module: redis
              metricsets: ["info", "keyspace"]
              hosts: "${data.host}:6379"
```

以上面的例子，目標是只要 Docker Image 是使用 `redis` 的 containers，就會是我們 auto discover 的目標，並且會 Redis module 來收集這些 containers 裡的資訊，相關的 Redis module 的設定，也會在 `config` 裡面去指定。

如此一來，在使用 Docker 動態增加或減少特定服務的 containers 時，我們的 Metricbeat 就會自動的去監控並收集 Metrics 的資訊了。

#### 使用 Docker Autodiscover Provider 的 Hints 讓找機器更容易

Autodiscover 除了上述的使用 Docker event 方式來篩選之外，有提供針對 `Docker Label` 的標示來判定是否要監控的機制。

```
metricbeat.autodiscover:
  providers:
    - type: docker
      hints.enabled: true
```

要啟用這項功能，要把 `hints.enabled: true` 打開。

並且當我們要運作一個 Docker Container 時，就可以指定 Autodiscover 定義好的標籤 `co.elastic.metrics`，讓 Metricbeat 能認得這些 Containers。

以下是 Nginx 的一個例子：

```
  co.elastic.metrics/module: nginx
  co.elastic.metrics/metricsets: stubstatus
  co.elastic.metrics/hosts: '${data.host}:80'
  co.elastic.metrics/period: 10s
```

另外這是 Apache Server 的例子，並且實際用 Docker Run 時，如何加上標籤：

```
docker run \
  --label co.elastic.metrics/module=apache \
  --label co.elastic.metrics/metricsets=status \
  --label co.elastic.metrics/hosts='${data.host}:${data.port}' \
  --detach=true \
  --name my-apache-app \
  -p 8080:80 \
  httpd:2.4
```

## 在 Kibana 的 Metric Monitoring 掌握 Docker 環境的健康狀態

當 Metricbeat 把 Docker Container 相關的資訊開始進行收集後，我們就可以到 Kibana Observability 裡的 Metrics Inventory 頁面，並且選擇 `Docker Containers` 的呈現方式，來檢視 Docker Containers 裡的各項服務的狀態了。

![10-Kibana-Metrics-Inventory-Docker-Container](https://i.imgur.com/czIPkdD.png)

Kibana Metrics 這邊的操作，可以參考前一篇 [09 - Metrics - 觀察系統的健康指標 (3) - 使用 Metricbeat 掌握 Infrastructure 的健康狀態 Host 篇](https://ithelp.ithome.com.tw/articles/10271438) 的介紹，這部份的差異不大，主要是 Docker 會有些特別針對 Docker 環境的數據檢視方式，這部份有興趣的讀者可以再去探索看看。

## 參考資料

1. [官方文件 - Run Metricbeat on Docker](https://www.elastic.co/guide/en/beats/metricbeat/7.14/running-on-docker.html)
2. [官方文件 - Metricbeat Autodiscover](https://www.elastic.co/guide/en/beats/metricbeat/7.14/configuration-autodiscover.html)
3. [Docker events](https://docs.docker.com/engine/reference/commandline/events/)


# 使用 Metricbeat 掌握 Infrastructure 的健康狀態 Kubernetes 篇

### 本篇學習重點

* Metricbeat 能夠收集 Kubernetes 哪些 Metrics
* 如何使用 Metricbeat 來收集 Kubernetes 的 Metrics
* 在 Kibana 上有提供哪些方式，能查看 Metricbeats 所收集的 Metrics

***

前一篇文章介紹了如何使用 Metricbeat 來掌握 Docker 環境 Infrastructure 的健康狀態，這一篇將來說明 Kubernetes 的環境，我們要如何使用 Metricbeat 來掌握各種健康狀態的 Metrics。

## Metricbeat 在 Kubernetes 環境中，能收集哪些 Metrics

這邊我們分成以下三個部份來說明：

* **System Metrics**: 我們在 Kubernetes 環境中的每個 Node，我們都應該要收集這些 Nodes 的系統 Metrics，所以這邊就會要使用 Metricbeat 的 `system module` 來收集。
* **Kubernetes Metrics**: 在 Kubernetes 的環境之中，Kubernetes 本身有許多運作的功能與機制，這部份 Metricbeat 有特別針對 Kubernetes 開發 `kubernetes module` 裡面有收集非常豐富的 Metrics，下面會針對這個 module 獨立介紹。
* **其他運作在 Kubernetes 上的服務的 Metrics**: 這部份就會使用 Metricbeat 所支援的各種服務的 modules，細節可以參考 [官方文件 Metricbeat Modules](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-modules.html) \[1]。

### Metricbeat Kubernetes Module

這邊我們來說明 Metricbeat 針對 Kubernetes 所開發的 Module 有哪些功能。

Kubernetes module 有收集以下 Kubernetes 元件的 Metrics：

* [kubelet](https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/)
* [kube-state-metrics](https://github.com/kubernetes/kube-state-metrics)
* [apiserver](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/)
* [controller-manager](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/)
* [scheduler](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-scheduler/)
* [proxy](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy/)

由於這些元件之中， `kubelet` 和 `proxy` 是運作在 Cluster 當中的每個 Node 身上，所以會是 Node 層級，而其他的元件是運作在 Cluster 層級，因此不同層級也會有不同的佈署與收集方式，以下我整理一個表格，將 Kubernetes module 所支援的 metricset 進行分類說明。

| Metricset 名稱                                                                                                                                                                                                                                                                  | 收集的方式                                                                                                 | 層級      | 適用的佈署方式                  |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------- | ------------------------ |
| `container`, `node`, `pod`, `system`, `volume`                                                                                                                                                                                                                                | kubelet endpoint (Default HTTP port: `10250`)                                                         | Node    | DaemonSet                |
| `state_node`, `state_daemonset`, `state_deployment`, `state_replicaset`, `state_statefulset`, `state_pod`, `state_container`, `state_cronjob`, `state_resourcequota`, `state_service`, `state_persistentvolume`, `state_persistentvolumeclaim`, `state_storageclass`, `event` | kibe-state-metrics 需另外安裝的 Service (Standard Example HTTP port: `8080`)                                | Cluster | DaemonSet 之中的 Leader Pod |
| `apiserver`                                                                                                                                                                                                                                                                   | Kubernetes `/metrics` API                                                                             | Cluster | DaemonSet 之中的 Leader Pod |
| `proxy`                                                                                                                                                                                                                                                                       | proxy endpoint (Default HTTP port: `10249`)                                                           | Node    | DaemonSet                |
| `scheduler`                                                                                                                                                                                                                                                                   | scheduler endpoint 需另外 create `kube-scheduler` service (Default HTTP port: `10259`)                   | Cluster | DaemonSet 之中的 Leader Pod |
| `controller-manager`                                                                                                                                                                                                                                                          | controller-manager endpoint 需另外 create `kube-controller-manager` service (Default HTTP port: `10257`) | Cluster | DaemonSet 之中的 Leader Pod |

## 使用 Metricbeat 監控 Kubernetes 的 Metrics

接下來，我們要來實際說明佈署的方式，在進行實作之前，先介紹在 Kubernetes 上部署 Metricbeat 的方法。

### 在 Kubernetes 上部署 Metricbeat 的方法

首先，要在 Kubernetes 裡佈署 Metricbeat 的方式有兩種，這邊先簡介這兩種的概念，接下來的實例，將帶大家了解如何實作。

#### DaemonSet

由於 Kubernetes DaemonSet 的設計，讓我們能夠輕易的確保 Metricbeat 能運作且常駐在 Kubernetes Cluster 中的每一個 Node 身上，並且可以在這個 Node 上只啟動一個 Instance (一個 Pod)，讓這個 Metricbeat 負責收集這個 Node 的 System Metrics 以及運作在這個 Node 身上其他 Pods 的各種服務的 Metrics。

因此在使用 Metricbeat 來監控 Kubernetes Cluster 的基本配置方式，就是將 Metricbeat 佈署成 DaemonSet。

另外 Metricbeat 有實作 [Leader Election](https://github.com/kubernetes/client-go/tree/master/tools/leaderelection) \[2]，在 Cluster DaemonSet 中只有一個 Metricbeat Pod，會被選為 leader，我們就可以使用這個 leader pod 來負責收集 Cluster 層級的 Metrics，避免 Cluster 中若是有多個 Metricbeat 都在收集 Cluster 層集的 Metrics，會收集到重覆的資料。

#### Deployment

除了使用 DaemonSet 的方式來佈署，我們當然也可以使用 Deployment 的方式來佈署 Metricbeat，但是什麼時候會用到這種方法呢？

主要是當 Cluster 規模愈大時，上面所提到 leader pod 很可能會因為資源不足，無法處理這樣大量級的資料，這時就應該考慮使用獨立的 Instance 來佈署 Metricbeat，以專門收集 Cluster 層級的 Metrics。

### 實際的佈署

我們這邊使用官方提供的一個 [Metricbeat-Kubernetes 範例](https://raw.githubusercontent.com/elastic/beats/7.14/deploy/kubernetes/metricbeat-kubernetes.yaml) \[3] 來進行拆解說明。

#### ConfigMap `metricbeat-deamonset-config`

在這個 ConfigMap 當中，定義了主要運作成為 DaemonSet 的 Metricbeat 的主要設定，也就是 `metricbeat.yml` 的配置。

```
apiVersion: v1
kind: ConfigMap
metadata:
  name: metricbeat-daemonset-config
  namespace: kube-system
  labels:
    k8s-app: metricbeat
data:
  metricbeat.yml: |-
    metricbeat.config.modules:
      # Mounted `metricbeat-daemonset-modules` configmap:
      path: ${path.config}/modules.d/*.yml
      # Reload module configs as they change:
      reload.enabled: false

    metricbeat.autodiscover:
      providers:
        - type: kubernetes
          scope: cluster
          node: ${NODE_NAME}
          unique: true
          templates:
            - config:
                - module: kubernetes
                  hosts: ["kube-state-metrics:8080"]
                  period: 10s
                  add_metadata: true
                  metricsets:
                    - state_node
                    - state_deployment
                    - state_daemonset
                    - state_replicaset
                    - state_pod
                    - state_container
                    - state_cronjob
                    - state_resourcequota
                    - state_statefulset
                    - state_service
                - module: kubernetes
                  metricsets:
                    - apiserver
                  hosts: ["https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}"]
                  bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
                  ssl.certificate_authorities:
                    - /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
                  period: 30s
                # Uncomment this to get k8s events:
                #- module: kubernetes
                #  metricsets:
                #    - event
        # To enable hints based autodiscover uncomment this:
        #- type: kubernetes
        #  node: ${NODE_NAME}
        #  hints.enabled: true

    processors:
      - add_cloud_metadata:

    cloud.id: ${ELASTIC_CLOUD_ID}
    cloud.auth: ${ELASTIC_CLOUD_AUTH}

    output.elasticsearch:
      hosts: ['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
      username: ${ELASTICSEARCH_USERNAME}
      password: ${ELASTICSEARCH_PASSWORD}
```

這裡面的配置我們分成以下來說明：

* 有定義下面另一組 ConfigMap `metricbeat-daemonset-modules` 要載入到 `modules.d` 目錄底的設定。
* 定義 `autodiscover` 的設定，這裡指定到 `scope: cluster` 以及 `unique: true`，代表這是在整個 DaemonSet 當中，只有 leader 這個唯一的 Pod 會運行，也因此裡面所描述的 Metricset 會是 Cluster 等級的，像是 `state_*` 以及 `event` 這樣的 Metricsets。

#### ConfigMap `metricbeat-daemonset-modules`

在這個 ConfigMap 裡，定義了主要的 DaemonSet 要執行的 Metricbeat 的模組有哪些

```
apiVersion: v1
kind: ConfigMap
metadata:
  name: metricbeat-daemonset-modules
  namespace: kube-system
  labels:
    k8s-app: metricbeat
data:
  system.yml: |-
    - module: system
      period: 10s
      metricsets:
        - cpu
        - load
        - memory
        - network
        - process
        - process_summary
        #- core
        #- diskio
        #- socket
      processes: ['.*']
      process.include_top_n:
        by_cpu: 5      # include top 5 processes by CPU
        by_memory: 5   # include top 5 processes by memory

    - module: system
      period: 1m
      metricsets:
        - filesystem
        - fsstat
      processors:
      - drop_event.when.regexp:
          system.filesystem.mount_point: '^/(sys|cgroup|proc|dev|etc|host|lib|snap)($|/)'
  kubernetes.yml: |-
    - module: kubernetes
      metricsets:
        - node
        - system
        - pod
        - container
        - volume
      period: 10s
      host: ${NODE_NAME}
      hosts: ["https://${NODE_NAME}:10250"]
      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      ssl.verification_mode: "none"
      # If there is a CA bundle that contains the issuer of the certificate used in the Kubelet API,
      # remove ssl.verification_mode entry and use the CA, for instance:
      #ssl.certificate_authorities:
        #- /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt
    # Currently `proxy` metricset is not supported on Openshift, comment out section
    - module: kubernetes
      metricsets:
        - proxy
      period: 10s
      host: ${NODE_NAME}
      hosts: ["localhost:10249"]
```

這裡面的配置我們分成以下來說明：

* 由於這會是每個 Node 都會運行一組的 DaemonSet，所以會要收集系統的 Metrics，這邊有定義了 `system.yml` 的 System module。
* 同樣的每個 Node 也都要收集 Node 層級的 Kubernetes Metrics，所以這邊有定義 `kuebernetes.yml` 使用 Kubernetes module，並且宣告 `node`、`system`、`pod`、`container`、`volume`、`proxy` 這些 Metricsets。
* 另外針對不同的 metricset 的來源，會從 `kubelet` 的 port `10250` 及 `proxy` 的 port `10249` 取得 Metrics。

#### DaemoneSet `metricbeat`

接下來就是主要的 DaemonSet - `metricbeat`

```
# Deploy a Metricbeat instance per node for node metrics retrieval
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: metricbeat
  namespace: kube-system
  labels:
    k8s-app: metricbeat
spec:
  selector:
    matchLabels:
      k8s-app: metricbeat
  template:
    metadata:
      labels:
        k8s-app: metricbeat
    spec:
      serviceAccountName: metricbeat
      terminationGracePeriodSeconds: 30
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet
      containers:
      - name: metricbeat
        image: docker.elastic.co/beats/metricbeat:7.14.2
        args: [
          "-c", "/etc/metricbeat.yml",
          "-e",
          "-system.hostfs=/hostfs",
        ]
        env:
        - name: ELASTICSEARCH_HOST
          value: elasticsearch
        - name: ELASTICSEARCH_PORT
          value: "9200"
        - name: ELASTICSEARCH_USERNAME
          value: elastic
        - name: ELASTICSEARCH_PASSWORD
          value: changeme
        - name: ELASTIC_CLOUD_ID
          value:
        - name: ELASTIC_CLOUD_AUTH
          value:
        - name: NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        securityContext:
          runAsUser: 0
          # If using Red Hat OpenShift uncomment this:
          #privileged: true
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 100Mi
        volumeMounts:
        - name: config
          mountPath: /etc/metricbeat.yml
          readOnly: true
          subPath: metricbeat.yml
        - name: data
          mountPath: /usr/share/metricbeat/data
        - name: modules
          mountPath: /usr/share/metricbeat/modules.d
          readOnly: true
        - name: proc
          mountPath: /hostfs/proc
          readOnly: true
        - name: cgroup
          mountPath: /hostfs/sys/fs/cgroup
          readOnly: true
      volumes:
      - name: proc
        hostPath:
          path: /proc
      - name: cgroup
        hostPath:
          path: /sys/fs/cgroup
      - name: config
        configMap:
          defaultMode: 0640
          name: metricbeat-daemonset-config
      - name: modules
        configMap:
          defaultMode: 0640
          name: metricbeat-daemonset-modules
      - name: data
        hostPath:
          # When metricbeat runs as non-root user, this directory needs to be writable by group (g+w)
          path: /var/lib/metricbeat-data
          type: DirectoryOrCreate
```

這裡面的配置我們分成以下來說明：

* 這邊有使用到 DaemonSet 的 `selector` 與 `template` ，並指定 Label `k8s-app: metricbeat`。
* 使用 `serviceAccountName: metricbeat`。
* 將 `/proc`、`/sys/fs/cgroup` 給 mount 到 `/hostfs` 底下，並且在啟動時，指定給 metricbeat: `-system.hostfs=/hostfs`。
* 將 Elasticsearch 相關的環境變數指定預設定。
* mount 前面指定好的 ConfigMap `metricbeat-daemonset-config` 與 `metricbeat-daemonset-modules`。

#### ServiceAccount 與相關的權限設定

這部份就不多解釋，請大家自己從設定來參考。

```
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: metricbeat
subjects:
- kind: ServiceAccount
  name: metricbeat
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: metricbeat
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: metricbeat
  namespace: kube-system
subjects:
  - kind: ServiceAccount
    name: metricbeat
    namespace: kube-system
roleRef:
  kind: Role
  name: metricbeat
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: metricbeat-kubeadm-config
  namespace: kube-system
subjects:
  - kind: ServiceAccount
    name: metricbeat
    namespace: kube-system
roleRef:
  kind: Role
  name: metricbeat-kubeadm-config
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: metricbeat
  labels:
    k8s-app: metricbeat
rules:
- apiGroups: [""]
  resources:
  - nodes
  - namespaces
  - events
  - pods
  - services
  verbs: ["get", "list", "watch"]
# Enable this rule only if planing to use Kubernetes keystore
#- apiGroups: [""]
#  resources:
#  - secrets
#  verbs: ["get"]
- apiGroups: ["extensions"]
  resources:
  - replicasets
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources:
  - statefulsets
  - deployments
  - replicasets
  verbs: ["get", "list", "watch"]
- apiGroups:
  - ""
  resources:
  - nodes/stats
  verbs:
  - get
- nonResourceURLs:
  - "/metrics"
  verbs:
  - get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: metricbeat
  # should be the namespace where metricbeat is running
  namespace: kube-system
  labels:
    k8s-app: metricbeat
rules:
  - apiGroups:
      - coordination.k8s.io
    resources:
      - leases
    verbs: ["get", "create", "update"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: metricbeat-kubeadm-config
  namespace: kube-system
  labels:
    k8s-app: metricbeat
rules:
  - apiGroups: [""]
    resources:
      - configmaps
    resourceNames:
      - kubeadm-config
    verbs: ["get"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: metricbeat
  namespace: kube-system
  labels:
    k8s-app: metricbeat
```

### 安裝 `Kube-state-metrics` 以提供 Metricbeat 所需要的 Metrics

由於前面所提到的 `kube-state-metrics` 並非是 Kubernetes Cluster 預設啟用的功能，這部份會需要透過另外安裝才能啟用這個服務。

以下使用最基本的簡易安裝方式，細節請參考 [GitHub kube-state-metrics](https://github.com/kubernetes/kube-state-metrics#kubernetes-deployment) \[4]。

先抓下 GitHub 上專案中建立好的 examples 配置：

```
git clone https://github.com/kubernetes/kube-state-metrics.git
```

使用 examples 中的 `standard` 配置來建立服務：

```
cd kube-state-metrics
kubectl apply -f examples/standard
```

成功建立之後，就可以在 `kube-system` 的 namespace 中看到 `kube-state-metrics` 的 `pod` 以及對外開啟 `8080` port 的 `service`。

![11-k9s-kube-state-metrics](https://i.imgur.com/NNkYnS3.png)

> 這邊使用的工具是 [k9s](https://github.com/derailed/k9s)，蠻好用的 Kubernetes 視覺化管理工具。

在圖中我們可以看到，由於 `kube-state-metrics` 是指定給 leader 才會執行，所以我們在 leader 這台 pod 的 logs 才會看到這資訊。

### 使用 Autodiscovery 透過 Annotation 自動找尋需監控的 Pods

其他運作在 Kubernetes 上的 Service，若是要使用 Metricbeat 的其他 modules 來取得 Metrics，建議可以使用 Autodiscovery 針對 Kubernetes Annotation 所支援的自動監控的功能。

```
metricbeat.autodiscover:
  providers:
    - type: kubernetes
      templates:
        - condition:
            contains:
              kubernetes.labels.app: "redis"
          config:
            - module: redis
              metricsets: ["info", "keyspace"]
              hosts: "${data.host}:6379"
              password: "${REDIS_PASSWORD}"
```

例如我們可以針對 `kubernetes.labels.app` 為 `redis` 的 pod，設定使用 redis module 來取得 Redis 的 Metrics , 並且套用上面所定義的 config 配置。

## 在 Kibana 檢視 Kubernetes 的 Metrics

在以上設定安裝完成之後，我們可以透過 Kibana 來查看 Metricbeat 所針對 Kubernetes 收集到的 Metrics。

### Kibana Observability Metrics

在 Kibana > Observability > Metrics 裡的 Inventory 中，我們可以針對 Show 指定為 `Kubernetes Pods` ，並且可以透過 Group by 的方式進行分類，結果如下圖。

![11-kibana-observability-metrics-k8s](https://i.imgur.com/rBT17Ct.png)

其他 Inventory 的使用方式，可以參考先前的文章 [09 - Metrics - 觀察系統的健康指標 (3) - 使用 Metricbeat 掌握 Infrastructure 的健康狀態 Host 篇](https://ithelp.ithome.com.tw/articles/10271438) 的介紹。

### Metricbeat 內建的 Dashboard

由於 Inventory 的檢視方式較為是 Infrastructure 整體的健康度，而 Metricbeat 也有為 Kubernetes 建立了四種 Dashboard，可以讓我們直接使用。

(由於我的測試環境有些資訊沒有呈現出來，所以有幾張圖我直接以[官方網站](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-module-kubernetes.html#metricbeat-module-kubernetes)的圖來呈現)

### Cluster Overview

![11-kibana-dashboard-k8s-overview](https://i.imgur.com/lu1pyx1.png)

### Controller Manager

![11-metricbeat-kubernetes-controllermanager](https://i.imgur.com/DY74GiU.png)

### Scheduler

![11-metricbeat\_kubernetes\_scheduler](https://i.imgur.com/ZpNW7k6.png)

### Proxy

![11-metricbeat-kubernetes-proxy](https://i.imgur.com/73DjI4S.png)

***

Metricbeat 針對 Kubernetes 所收集的資訊相當詳細，相信可以對於我們監控管理 Kubernetes Cluster 有很大的幫助，特別是這些資料進入 Elasticsearch 之後，還能和其他 Obersevability 的資訊進行整合使用，應用的空間還很大呢！

## 參考資訊

1. [官方文件 - Metricbeat Modules](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-modules.html)
2. [Kubernetes client-go - Leader Election](https://github.com/kubernetes/client-go/tree/master/tools/leaderelection)
3. [Metricbeat-Kubernetes 範例](https://raw.githubusercontent.com/elastic/beats/7.14/deploy/kubernetes/metricbeat-kubernetes.yaml)
4. [GitHub kube-state-metrics](https://github.com/kubernetes/kube-state-metrics#kubernetes-deployment)


# 使用 Metricbeat 掌握 Infrastructure 的健康狀態 AWS 篇

### 本篇學習重點

* Metricbeat 能夠收集 AWS 哪些 Metrics
* 如何使用 Metricbeat 來收集 AWS 的 Metrics
* 在 Kibana 上有提供哪些方式，能查看 Metricbeats 所收集的 AWS Metrics

***

先前的幾篇文章，我們分別介紹了 Host, Docker, Kubernetes 這幾種不同的佈署環境中，如何透過 Metricbeat 來掌握 Infrastructure 的健康狀態，不過如果是使用像是 AWS 這樣的雲端服務供應商，Metricbeat 也有提供蠻不錯的資訊整合及建立好的 Dashboard，這篇將會針對 AWS 的支援部份來進行介紹。

## Metricbeat 在 AWS 環境中，能收集哪些 Metrics

由於使用 AWS 這樣的雲端服務，大部份我們會使用 SaaS 型式的服務，少部份才會是使用 EC2 當虛擬器，並且自行在裡面架設其他的服務，Metricbeat 因此有提供了 AWS module，針對 AWS 的各種服務進行整合，能取得這些服務的監控資訊，甚至可以從 AWS 的 Cloudwatch 取得所有在 AWS 使用的服務所產生並傳送到 Cloudwatch 的資訊。

我們這邊先列出一些在 AWS 環境中，你可能會要收集 Metrics 的情境，並且對應該用什麼方式來收集：

* **使用 EC2 虛擬主機：** 可以使用 System module 來收進系統 Metrics。
* **使用 EC2 虛擬主機安裝其他的服務：** 可以使用其他 Metricbeat 所支援的各種服務的 module，例如：Tomcat, Nginx, Apache, RabbitMQ, Kafka...等。
* **使用 ECS EC2：** 可以使用 Docker module。
* **使用 ECS Fargate：可** 以使用 AWS Fargate module。
* **使用 EKS：** 可以使用 Kubernetes module。
* **使用 AWS 其他提供的 SaaS 服務，並且是 AWS module 有支援的：** 使用 AWS module。
* **使用 AWS 其他提供的 SaaS 服務，但是 AWS module 沒有支援的：** 使用 AWS module 裡的 Cloudwatch metricset。

對於如何使用 System module、Docker module、Kubernetes module，請參考這系列前面的幾篇文章介紹。

### Metricbeat AWS Module

接下來我們針對 AWS module 來進行介紹，這邊先列出 Metricbeat 有支援的 AWS metricset 有哪些 \[1]：

* [billing](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-billing.html)
* [cloudwatch](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-cloudwatch.html)
* [dynamodb](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-dynamodb.html)
* [ebs](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-ebs.html)
* [ec2](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-ec2.html)
* [elb](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-elb.html)
* [kinesis](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-kinesis.html)
* [lambda](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-lambda.html)
* [natgateway](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-natgateway.html)
* [rds](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-rds.html)
* [s3\_daily\_storage](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-s3_daily_storage.html)
* [s3\_request](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-s3_request.html)
* [sns](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-sns.html)
* [sqs](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-sqs.html)
* [transitgateway](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-transitgateway.html)
* [usage](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-usage.html)
* [vpn](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-metricset-aws-vpn.html)

由於 AWS module 針對以上這些 metricset 所收集到的資訊，大部份都有建立好預設的 Dashboard 可以使用，所以這邊不一個個帶過，請大家有興趣直接點選上面的連結查看。

### Metricbeat AWS Fargate Module

而 AWS Fargate module 所支援的 metricset 就只有單純的 `task_stats` ，裡面其實就是 Docker stats 的回傳資訊，包含以下這些基本的 Metrics：

* CPU metrics
* Disk I/O metrics
* Memory metrics
* Network metrics
* Container metadata

## 使用 Metricbeat 監控 AWS 的 Metrics

接下來我們會舉一個簡單的例子，嘗試使用 Metricbeat 的 AWS module 來收集 metrics。

### Credential 的設定方式

首先要能存取 AWS 的服務，就是要先有權限，我們也會需要將對應的 credential 設定給 Metricbeat，指定的方式有幾種：

1. 什麼都沒有設定時，Metricbeat 會去抓主機上的 [AWS credential profile](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/create-shared-credentials-file.html) \[2] 設定來使用。

一般在電腦上的 AWS credentail 設定的路徑是在 `~/.aws/credentials`，設定如下：

```
# ~/.aws/credentials                                                                                          
[default]
aws_access_key_id = 
aws_secret_access_key = 
```

另外 region 的設定也是一樣，沒有特別指定，會抓機器上的預設配置，路徑在 `~/.aws/config`，設定如下：

```
# ~/.aws/config                                                                                                                                                                                                                        [default]
region = ap-southeast-1
```

1. 直接在 `metricbeat.yml` 設定檔中指定 `access_key_id` 和 `secret_access_key`：

最基本的做法，適合測試時使用，但正式環境不建議這樣用，你總不會想把這段有 credential 的 config 一起進 git 版控吧?

所以真的要在 config 設定，應該抽離出從環境變數設定，這樣的做法也便於使用容器化的佈署方式時，將參數帶入。

```
metricbeat.modules:
- module: aws
  period: 5m
  access_key_id: ${AWS_ACCESS_KEY_ID}
  secret_access_key: ${AWS_SECRET_ACCESS_KEY}
  session_token: ${AWS_SESSION_TOKEN}
  metricsets:
    - ec2
```

> 如果你使用的時有時效性的 Session Token 的話，`AWS_SESSION_TOKEN` 也是在這邊可以進行設定。

例如：使用 docker 佈署 metricbeat 時的 `-e AWS_ACCESS_KEY_ID=abcd` 這種環境變數指定方式。

```
docker run -e AWS_ACCESS_KEY_ID=abcd -e AWS_SECRET_ACCESS_KEY=abcd -d --name=metricbeat --user=root --volume="$(pwd)/metricbeat.aws.yml:/usr/share/metricbeat/metricbeat.yml:ro" docker.elastic.co/beats/metricbeat:7.15.0 metricbeat -e
```

1. 指定 AWS `role_arn`

`role_arn` 是針對 AWS IAM role 產生臨時性的 credentials 所使用的，有使用 `role_arn` 時，也要提供 Access Key，或是配合 AWS credential porfile。

例如下面的例子就是使用指定的 `role_arn` 並且透過 Shared credential file 裡面所定義的 credential 來驗證身份。

```
metricbeat.modules:
- module: aws
  period: 5m
  role_arn: arn:aws:iam::123456789012:role/test-mb
  shared_credential_file: /Users/mb/.aws/credentials_backup
  credential_profile_name: test
  metricsets:
    - ec2
```

這個 `credentials_backup` 就如我們前面介紹的 `~/.aws/credentials` 設定方式是一樣的。

### 啟用 AWS Module

接下來，我們就將 Metricbeat AWS module 啟用：

```
./metricbeat enable aws
```

### 設定 AWS Module 配置

接著針對我們要收集的 AWS Metrics 來進行對應的配置，以下是 Metricbeat 的 AWS module 啟用後的預設配置：

```
# Module: aws
# Docs: https://www.elastic.co/guide/en/beats/metricbeat/7.x/metricbeat-module-aws.html

- module: aws
  period: 1m
  metricsets:
    - elb
    - kinesis
    - natgateway
    - rds
    - transitgateway
    - usage
    - vpn
- module: aws
  period: 5m
  metricsets:
    - cloudwatch
  metrics:
    - namespace: AWS/EC2
      #name: ["CPUUtilization", "DiskWriteOps"]
      resource_type: ec2:instance
      #dimensions:
      #  - name: InstanceId
      #    value: i-0686946e22cf9494a
      #statistic: ["Average", "Maximum"]
- module: aws
  period: 5m
  metricsets:
    - dynamodb
    - ebs
    - ec2
    - lambda
    - rds
    - sns
    - sqs
- module: aws
  period: 24h
  metricsets:
    - billing
  cost_explorer_config:
    group_by_dimension_keys:
      - "AZ"
      - "INSTANCE_TYPE"
      - "SERVICE"
      - "LINKED_ACCOUNT"
    group_by_tag_keys:
      - "aws:createdBy"
- module: aws
  period: 24h
  metricsets:
    - s3_daily_storage
- module: aws
  period: 1m
  latency: 5m
  metricsets:
    - s3_request
```

這邊要注意到，有些 Metrics 的收集頻率會需要較密集，但有些會要較寬鬆，像是下面這幾類 metrics 適合 1 分鐘取得一次數據：

```
- elb
- kinesis
- natgateway
- rds
- transitgateway
- usage
- vpn
```

其他的部份會依照實際的需求，並參考 metricset 裡面的描述說明，像是 `s3_daily_storage` 就會建議 `period` 至少要設定 1 天，因為這個值是一天更新一次，另外這個 daily metrics 一天存取一次也不需要額外的費用。

> 注意：因為 Metricbeat 是透過 CloudWatch API 去取得 AWS 的 Metrics 數據，這部份使用量會要去計算一樣，量大的時候也會是一筆費用。
>
> [官方文件 - Metricbeat AWS Module - AWS API Requests](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-module-aws.html#aws-api-requests) 裡面有個章節就在解釋這部份的算法。

### 啟動 Metricbeat

設置完成後，啟動 Metricbeat：

```
./metricbeat -e
```

接著就可以來查看所收集到的資料了。

## 在 Kibana 檢視 AWS 的 Metrics

### Kibana Observability Metrics

在 Kibana Observability 的 Metrics Inventory 中，我們可以從 **Show** 選擇 AWS，裡面有四種預先定義好所支援的服務：

* EC2 Instances
* S3 Buckets
* RDS Databases
* SQS Queue

![12-kibana-metrics-aws](https://i.imgur.com/HJah3eV.png)

針對這些服務，Kibana 也有先預設定義好這些服務較常會看的 Metrics，例如 RDS 會關注的 `CPU usage`、 `Connections`、`Queries executed`、`Active transactions`，另外也可以自行從 **Add metric** 中，選出我們所指定的 metricset 收集的其他數據。

![12-kibana-inventory-metrics](https://i.imgur.com/MSiVFSA.png)

其他 Inventory 的使用方式，可以參考先前的文章 [09 - Metrics - 觀察系統的健康指標 (3) - 使用 Metricbeat 掌握 Infrastructure 的健康狀態 Host 篇](https://ithelp.ithome.com.tw/articles/10271438) 的介紹。

### Metricbeat 內建的 AWS Dashboard

由於 Metricbeat 預設為 AWS 所建立的 Dashboard 非常的多，如先前介紹 metricset 時所描述，建議大家針對你所指定收集的 metricset，可以先查看是否有預先建立好的 Dashboard，或是從 Kibana Dashbard 中，使用 `Metricbeat AWS` 關鍵字進行搜尋。

![image-20210927235613649](https://i.imgur.com/zkMIU6s.png)

***

以上是『Metrics - 觀察系統的健康指標』系列的介紹，希望能協助大家對於 Elastic Observability 中針對 Metrics 的部份有更多的了解，針對 Metricbeat 的使用上，其實還有不少值得挖掘的功能以及較進階的設定配置，建議有興趣的讀者可以從官方的文件深入了解。

## 參考資料

1. [官方文件 - Metricbeat AWS module](https://www.elastic.co/guide/en/beats/metricbeat/7.14/metricbeat-module-aws.html)
2. [AWS credential profile](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/create-shared-credentials-file.html)


# Logs - 挖掘系統內部發生的狀況

Logs 是系統運作細節的記錄，也是我們用來挖掘系統內部運作時發生什麼狀況的重要參考資訊，Elastic Observability 的解決方案之中，使用了 Filebeat 來負責收集散落在四處的 Logs，並且如何將收集到的 Logs 使用 Elastic Observability 來進行查閱。

* [01 - Logs 與 Filebeat 的基本介紹](/tech-sharing/uncle-joe-teach-es-elastc-observability/logs-wa-jue-xi-tong-nei-bu-fa-sheng-de-zhuang-kuang/logs-yu-filebeat-de-ji-ben-jie-shao)
* [02 - 使用 Filebeat 應該要了解的設計細節與原理](/tech-sharing/uncle-joe-teach-es-elastc-observability/logs-wa-jue-xi-tong-nei-bu-fa-sheng-de-zhuang-kuang/shi-yong-filebeat-ying-gai-yao-liao-jie-de-she-ji-xi-jie-yu-yuan-li)
* [03 - 透過 Filebeat 收集 Elastic Stack 中各種服務的細節資訊](/tech-sharing/uncle-joe-teach-es-elastc-observability/logs-wa-jue-xi-tong-nei-bu-fa-sheng-de-zhuang-kuang/tou-guo-filebeat-shou-ji-elastic-stack-zhong-ge-zhong-fu-wu-de-xi-jie-zi-xun)
* [04 - 透過 Filebeat 收集 Infrastructure 中各種服務的細節資訊](/tech-sharing/uncle-joe-teach-es-elastc-observability/logs-wa-jue-xi-tong-nei-bu-fa-sheng-de-zhuang-kuang/tou-guo-filebeat-shou-ji-infrastructure-zhong-ge-zhong-fu-wu-de-xi-jie-zi-xun)


# Logs 與 Filebeat 的基本介紹

### 本篇學習重點

* Logs 在 Observability 中的基本介紹
* Filebeat 的簡介，以及如何用 Filebeat 來收集 Logs 資料

## Observability Logs 的基本介紹

### Logs 在 Observability 扮演的角色

[Google Cloud Architecture Center - DevOps Guides](https://cloud.google.com/architecture/devops/devops-measurement-monitoring-and-observability) \[1] 對針 Observability 這個詞的定義中，描述到：

> **Observability** is tooling or a technical solution that allows teams to actively debug their system. Observability is based on exploring properties and patterns not defined in advance.

有提到一個重點，就是『**非事先定義**』，Observability 也就是擁有能夠探索未事先定義的屬性與模式的能力，我們在先前介紹的 **Uptime** 與 **Metrics** 的部份，其實都是需要事先定義，就算是 Elastic 已經做好很多預設的 Integration 以及 Dashboard，但也都是先定義好的，如果沒有定義的部份，也就不會被收集到。

而 Observability 當中的 Logs 與 APM，就擁有較多可以觀察到『**非事先定義**』部份的能力，針對 Logs 的部份，源頭當然還是需要系統、應用程式端、或是服務端，有記錄足夠資訊的日誌，而這些日誌，將會讓我們擁有『**當發現系統有異常時，能夠進一步深入挖掘系統內部運作的情況，並且提供分析核心原因及找到解決方案**』的能力。

### Observability 中的 Logs 提供了什麼樣的能力

#### 即時監控不斷產生的 Logs - Streaming

就好比我們在 \*nix 環境中常會針對日誌檔使用的指令 `tail -f`，能讓我們查看最新不斷產生的日誌內容，Logs 也提供了 Streaming 的功能，在 **Kibana** > **Observability** > **Logs** 當中，我們可以啟用 `Stream live` 的按鈕，就可以讓我們即時的查看分散在多台主機的系統、服務、應用程式，所最新產生的日誌內容。

![13-Kibana-O11y-Logs-stream](https://i.imgur.com/kFN8er5.png)

在這個 Logs Streaming 的功能之中，由於資訊量很大，所以 Logs 同時也有提供一些能力，能協助我們找到或是關注在我們所需要的資料上：

* 使用 **KQL (Kibana Query Language)** 定義篩選的規則
* 使用 **Highlights** 在結果當中以顏色突顯、也會在右方的時間軸上呈現出哪些時間有發生 ![13-Kibana-O11t-Stream-Highlight](https://i.imgur.com/BgrRBZk.png)
* 在查詢到指定某一條 log 時，能透過 `View in Context` 的方式來檢視，也就是可以快速的翻查這行 log 的前、後的 logs，這個功能非常的實用。

![13-Kibana-O11t-Stream-view-in-context](https://i.imgur.com/lEKrGTK.png)

### 使用 Machine Learning 來協助分析 Logs

這部份在 Observability 的 Logs 之中，預設在選單上就有列出兩個功能，都是透過 Machine Learning 來協助做到二個類型的處理：

* Anomalies 異常
* Categories 分類

透過機器學習的方式，能針對指定 Logs 的時間啟始點、針對哪些 Index，建立 Machine Learning 的 Job。

![13-Kibana-Create-ML-Job](https://i.imgur.com/FDmNocP.png)

在進入這兩個功能的檢視畫面時，就可以發現 Elastic 已經貼心的幫我們建立好這些基本的學習規則，可以直接查看當下發現的結果。

![13-Kibana-Create-Anomalies](https://i.imgur.com/KXaLKqv.png)

進一步可以從 Anomaly Explorer 查看異常分析的內容。

![13-Kibana-Create-Anomaly-Explorer](https://i.imgur.com/qBWAq5V.png)

甚至可以查看異常判斷的原因。

![13-Kibana-Create-Anomaly-Reason](https://i.imgur.com/drKlsji.png)

## Filebeat 基本介紹

要使用上述介紹到 Elastic 在 Kibana 提供的 Observability Logs 的這些基本能力之前，我們要先將 Logs 收集到 Elasticsearch 之中，Elastic Stack 中負責收集 Logs 資訊的主要角色，就是 Filebeat，如同先前介紹的 Metricbeat 和 Heartbeat 一樣，Filebeat 也是 Beats 家族中的一員，所以也是從 `libbeat` 所發展出來，並且是針對檔案類型的 Logs 進行收集的工具。

### Filebeat 的主要運作架構

如下圖所示，Filebeat 主要是針對機器上的各種檔案，並且會使命必達的負責將指定的目錄中的檔案有新增的 logs，收集起來並且往後傳遞，可以直接送到 Elasticsearch 或是送到 Logstash 進行 ETL (Etract, Transform, Load) 的處理，又或是送到 Kafka 或 Redis 的 Queue 之中，再透過其他的工具進行後續的處理。

![13-Filebeat-Architecture](https://i.imgur.com/BISE7FI.png)

### Filebeat 的安裝方式

安裝的方式如同其他 Beats 家族成員相似，以下是使用最簡單的安裝步驟來做介紹，其實與官方的 Quick start 的文件差不多，先大約知道將 Metricbeat 運作起來的流程為何，我將會以 `MacOS` 為例。

1. 下載，並解壓縮 Filebeat。

```
curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.15.0-darwin-x86_64.tar.gz
tar xzvf filebeat-7.15.0-darwin-x86_64.tar.gz
```

1. 在解壓縮目錄下的 `filebeat.yml` 指定 Elasticsearch 的位置

```
output.elasticsearch:
  hosts: ["myEShost:9200"]
  username: "filebeat_internal"
  password: "YOUR_PASSWORD" 
```

1. 啟動要安裝的模組

```
./filebeat modules enable {module_name}
```

Filebeat 提供了非常多內建的模組 (modules)，像是 `Elasticsearch`、 `Apache`、`Nginx`、`MySQL`、`PostgreSQL`、`Redis`、`MongoDB`...等，詳細可以查看 [官方文件 Filebeat Modules](https://www.elastic.co/guide/en/beats/filebeat/7.15/filebeat-modules.html) \[2]。

另外針對啟動的模組，通常都會要調整這些模組的 config 檔，檔案的路徑就在 `./modules.d/` 裡面，檔名就會是 module 的名字，副檔名為 `.yml`。

1. 安裝 Filebeat 內建的 Kibana Dashboard，以及 Elasticsearch 的 Index Template。

```
./filebeat setup -e
```

1. 啟動 Filebeat

```
./filebeat -d
```

若是要以 `root` 執行，要記得把 config 的擁有者也改成 `root`

```
sudo chown root filebeat.yml 
sudo chown root modules.d/system.yml 
sudo ./filebeat -e
```

接下來就可以到 Kibana 查看 Filebeat 所發送的資料，有沒有成功的進入到 Elasticsearch了。

## 參考資料

1. [Google Cloud Architecture Center - DevOps Guides](https://cloud.google.com/architecture/devops/devops-measurement-monitoring-and-observability)
2. [官方文件 Filebeat Modules](https://www.elastic.co/guide/en/beats/filebeat/7.15/filebeat-modules.html)


# 使用 Filebeat 應該要了解的設計細節與原理

### 本篇學習重點

* Filebeat 的設計架構與底層運作原理
* Filebeat Config 配置的一些建議與需注意的設定

## Filebeat 的運作架構與原理

由於實務上的使用情境有許多種變化，透過了解 Filebeat 運作運作架構與原理，能幫助我們在自己所需要的情境之下，能進行比較好的配置及使用，避免效能的浪費或甚至是因為誤用而造成非預期的結果。

### Filebeat 的運作原理

這是在上一篇文章我們有介紹到的 Filebeat 運作架構，接下來我們先深入的了解裡面的組成元素與運作細節。\[1]

![13-Filebeat-Architecture](https://i.imgur.com/BISE7FI.png)

#### Harvesters (收集器)

Harvester 的職責與運作特性如下：

* 每個檔案都會有專門負責的 Harvester 進行處理，並且將新增加的資料讀出，並且交給 `libbeat` 進行後續處理，最終透過 Outputs 的定義將資料往外傳送。
* 讀取檔案時，以 **行 (Line)** 為單位。
* Harvester 會負責實體檔案的 `open` 與 `close`，也就是說只要 Harvester 還在運作時，所負責讀取的檔案的 File descriptor (檔案描述器) 會一直保持 `open` 的狀態，因此如果 Harvester 在保持運作時，若某個檔案被移動或改名後，這個檔案後續增加的內容，依然會被 Harvester 讀取出來，也因此如果檔案被刪除的話，磁碟空間還是會一直被佔用著，直到 Harvester `close` 檔案之後，才會被釋放。
* 要讓 Harvester `close` 檔案的話，會依照 `close_inactive` 所設定的值，等到檔案持續指定的時間長度沒有寫入新資料之後，才會被關閉，而觸發檢查這個設定值會是依照 `scan_frequency` 所指定的時間週期。
* 被 `close` 的檔案，若是又有新的資料寫入，會等到 `scan_frequency` 下次執行的時間週期檢查到檔案時，才會繼續 `open` 並且讀取檔案。

#### Inputs (輸入端)

而 Inputs 的職責與運作特性如下：

* Inputs 負責管理需要被讀取的檔案或資料來源。
* Inputs 將要讀取的檔案及資料交給 Harvester 進行處理，也就是說 Harvesters 其實就是由 Inputs 在管理的。
* 當有設定多組 Inputs 時，每個 Input 擁有自己的執行緒 (Thread)，會併發進行處理。
* 以 `log` Input 為例，若是有指定多個 `paths` ，每個 `paths` 指定的檔案都會由 Input 負責找出來，並且每一個檔會交給一個 Harvester 去處理。
* 同一種類型的 Input 可以重覆被定義很多次，但要留意同一個檔案不應該被重覆指定，這樣可能會發生非預期的行為。
* Input 目前 Filebeat 有支援 20 幾種服務的實作，有興趣的可以參考 [官方文件 - Filebeat Inputs](https://www.elastic.co/guide/en/beats/filebeat/7.15/configuration-filebeat-options.html) \[2]。

> 早期叫 `prospector` ，6.3 版之後將 `prospector` 改成 `input`

#### Spooler (緩衝處理器)

**Spooler** 其實指的是 **Queue**，也就是 `libbeat` 裡面所實作的機制，由 harvesters 所讀取出的資料，會以 events 的方式，透過 events channel 傳送給 `libbeat` ，並且在 `libbeat` 當中透過各種 Pipeline 的方式進行後續的處理，像是如果有定義 `processors` 就會在 `pipelineProcessors` 面進行指定的處理，而 `libbeat` 裡的 **Publisher** ，就負責將 event 最後透過 Output 的定義所往後傳送的機制，這邊是以 **At Least Once** 的方式來實作，也就是會確保資料有被正確的傳送出去，如果沒有收到正確傳送出去的回覆，就會不斷的重試，最終會透過 **Registrar** 將結果寫入到 `Registry` 檔案之中。

> 有興趣了解 Filebeat 與 libbeat 底層實作的，可以參考 [Filebeat 原始碼淺析](https://www.gushiciku.cn/pl/g0kn/zh-tw) \[3]。

#### Outputs (輸出)

Output 這邊是針對 Filebeat 所支援的對外接口種類有各別的實作，目前 Filebeat 的 Output 有提供 6 種 (Elasticsearch, Logstash, Kafka, Redis, File, Console)，如果有定義要批次處理，例如 Elasticsearch Output 會使用 `bulk` API，這個處理就會實作在 Output 當中，另外如果 Filebeat 在關閉時，還沒有成功取得 Output 所發送出去的回應時，Filebeat 不會等待，會直接關閉，一但 Filebeat 重新啟動時，這個 events 就會被重新傳送。

### Filebeat 如何記錄及處理要傳送的檔案

Filebeat 會將每個處理過的檔案，記錄在他的 `Registry` 檔裡面，預設是存放在 `${path.data}/registry/` 的目錄裡。

裡面會有個 `json` 格式的檔案，記錄每一個曾被讀取過的檔案，而如果是保持在 `open` 的檔案，會另外獨立存在一個 `active` 的檔案之中，同時會記錄 `inode` 等實體 disk 的資訊，Filebeat 會持續在記憶體更新每個檔案被讀取過的位置，並且等到資料成功透過 Output 傳送出去之後，便會將記憶體中的記錄寫到 Disk 中，而如果 Filebeat 程式異常中止的話，也會在重新啟動的時候，從 `Registry` 裡將記錄讀出，就能夠知道要繼續從檔案的哪個位置往後讀取。

由於 log 檔名可能會被修改，檔案也可能會搬位置，因此 Filebeat 會以 Disk `inode` 資訊產生另外的 Unique ID 並記錄在 Registry 之中，用以評估檔案是否在先前有被處理過，以防止檔案改名或搬位置之後，被重覆的讀取。

### Filebeat 如何確保需傳送的資料不會漏掉

Filebeat 能確保資料 `At Least Once` 的使命必達，不會漏掉，是因為他會將傳送的狀態記錄在 `Registry` 檔案裡，所以發生錯誤沒辦法成功的傳送，就會透過前面所介紹的 `libbeat` 裡的 **Registrar** 進行重送，同樣的如果 Filebeat 非正常的關閉，也會在重新啟動的時候，透過 `Registry` 將檔案處理的狀態給恢復。

## Filebeat 的 Config 配置方式

這邊會將一些 Config 配置上，實務上會需要留意及較常會使用到的部份與大家進行介紹。

### 一般設定

這部份是設定在 `filebeat.yml` 當中：

* `retistry.flush` ：預設是 `0s`，也就是只要 output 成功寫出，就會執行 `flush` 。如果有非常多的 Logs 同時在處理很大量的資料的情境下，太過頻繁的寫入 `Registry` 檔案會拖慢整個執行的速度，這時就應該將 `registry.flush` 設定 `>0s`。
* `shutdown_timeout`：預設 Filebeat 在關閉程式時，不會等待 publisher 確認 Output 的結果，這樣在大量資料不斷處理的情況下，會常有機會發生關閉 Filebeat 當下的資料，在重新啟動時重覆發送的情況，我們可以設定這個值，讓關閉的時候稍微多等待 Output 的結果，以減少重送的情況發生。
* `tags` 與 `fields`：這兩個值能夠有效的幫我們分類資料的來源，善用資料來源的標示與分類，可以幫助我們在後續的資料分析。
* `processors`：這個功能能讓我們透過 Filebeat 所收集的資料，在往後送之前，進行一些簡單的加工處理，例如我們想要依照一些條件刪掉不需要的 event、使用我們自訂義的 ID 欄位，讓重覆的資料進入 Elasticsearch 時能夠被 deduplicate (去重覆)、把某個 JSON 的字串解析出來並取得當中的值…等。
* `queue.disk`：如果你透過 Filebeat 在處理的資料量很大，並且希望能夠在 **Spooler** 當中先將較大量的資料進行彙總再往後傳送，預設在 memory 當 queue 的配置，可能承受不了太量的資料彙總，這時就可以考慮將 queue 寫在 disk 中。又或著是我們透過 Filebeat 要往後傳送的資料，非常重視資料的可靠性，同時我們又有指定 `flush.min_events` 和 `flush.timeout` 要使用較大量的 queue，這時也會要考慮將 queue 指定到實體的 disk 之中。\[4]

### 實務上的配置技巧 - 切分設定檔

由於實務佈署上，我們有多台的機器時，相同性質的機器的 filebeat 的配置會是一樣的，這時候我們可以將 config 切分出來，也能夠讓我們在做組態管理時更容易，至於 filebeat 能切分的設定檔主要有以下兩種：

#### Input Config

```
filebeat.config.inputs:
  enabled: true
  path: inputs.d/*.yml
```

存放在 `inputs.d` 裡面的 `yml` 檔案格式，會定義如下方的例子：

```
- type: log
  paths:
    - /var/log/mysql.log
  scan_frequency: 10s

- type: log
  paths:
    - /var/log/apache.log
  scan_frequency: 5s
```

#### Module Config

```
filebeat.config.modules:
  enabled: true
  path: ${path.config}/modules.d/*.yml
```

這裡面的檔案格式如下：

```
- module: apache
  access:
    enabled: true
    var.paths: [/var/log/apache2/access.log*]
  error:
    enabled: true
    var.paths: [/var/log/apache2/error.log*]
```

而建議的 Config 檔案管理方式，一般有二種做法：

* 如預設的配置，以 module 名稱來切分檔案，不同的 module 有各自的 `yml` 設定檔。
* 以服務角色來切分，例如 web server、backend service、DB server…等，並在檔案中定義所有該服務使用到的 modules。

### 其他 Input, Output, Module 的配置

在其他 Input, Output Module 的配置上，在不同的情境下，也都有不同的配置建議設定，由於所支援的部份太多，這邊無法一一細談，會建議大家在使用之前，一定要先閱讀過官方文件的資訊，至少先把有哪些設定值看過一遍，再依照情境去判斷可能會需要調整的部份。

如果你也是使用 Filebeat 在讀取實體的 Log 檔案，至少 [官方文件 Input - Log](https://www.elastic.co/guide/en/beats/filebeat/7.15/filebeat-input-log.html) 這份的設定你應該要看過，要知道 `close_*`、`scan_frequency`、`harvester_limit` 等配置是做什麼用的，對於效能的調效也會有所幫助。

## 參考資料

1. [官方文件 - How Filebeat Works](https://www.elastic.co/guide/en/beats/filebeat/7.15/how-filebeat-works.html)
2. [官方文件 - Filebeat Inputs](https://www.elastic.co/guide/en/beats/filebeat/7.15/configuration-filebeat-options.html)
3. [Filebeat 原始碼淺析](https://www.gushiciku.cn/pl/g0kn/zh-tw)
4. [官方文件 - Filebeat Internal Queue](https://www.elastic.co/guide/en/beats/filebeat/7.15/configuring-internal-queue.html)


# 透過 Filebeat 收集 Elastic Stack 中各種服務的細節資訊

### 本篇學習重點

* 如何使用 Filebeat 收集 Elastic Stack 的各種 Logs
* 在 Kibana 中，如何查看經由 Filebeats 收集到的 Elastic Stack Logs

***

在經過前一篇 Filebeat 的運作原理的介紹後，接下來我們要來說明，如何使用 Filebeat 收集我們 Elastic Stack 之中的各種 Logs。

## 使用 Filebeat 收集 Elastic Stack 的各種 Logs

首先我們針對 Elastic Stack 的 Logs 進行收集，在說明如何配置 Filebeat 之前，我們先解釋一下我們的 Elastic Stack 情境。

### Elastic Stack 情境

在這次示範的情境，我們先使用單純的手動下載安裝，這種方式可以讓我們先專注在 Filebeat 相關的設定之中，不較不會受到其他佈署方式的環境影響。

情境中的 Elastic Stack 有以下這些成員：

* **Elasticsearch Node \* 2：** 都是安裝在本機端，並且分別有各自不同的資料夾路徑
  * `/Users/joecwu/Training/elasticsearch-7.14.1-1`
  * `/Users/joecwu/Training/elasticsearch-7.14.1-2`
* **Kibana \* 1：** 安裝於以下路徑
  * `/Users/joecwu/Training/kibana-7.14.1-darwin-x86_64`
* **Logstash \* 1：** 安裝於以下路徑
  * `/Users/joecwu/Training/logstash-7.14.1`
* **Filebeat \* 1：** 安裝於以下路徑
  * `/Users/joecwu/Training/beats/filebeat-7.14.1-darwin-x86_64`

### 收集 Elasticsearch Logs

#### 設定 Filebeat 的 Elasticsearch Module

收集 Elasticsearch Logs 時，我們會使用到 Filebeat modules 裡的 **Elasticsearch module**，因此我們先啟用 Module：

```
./filebeat modules enable elasticsearch
```

啟用 module 之後，我們到 `./modules.d/` 目錄底下，修改 `elasticsearch.yml` 的設定檔：

```
# Module: elasticsearch
# Docs: https://www.elastic.co/guide/en/beats/filebeat/7.x/filebeat-module-elasticsearch.html

- module: elasticsearch
  # Server log
  server:
    enabled: true

    # Set custom paths for the log files. If left empty,
    # Filebeat will choose the paths depending on your OS.
    var.paths:
      - /Users/joecwu/Training/elasticsearch-7.14.1-1/logs/*_server.json
      - /Users/joecwu/Training/elasticsearch-7.14.1-2/logs/*_server.json

  gc:
    enabled: true
    # Set custom paths for the log files. If left empty,
    # Filebeat will choose the paths depending on your OS.
    var.paths:
      - /Users/joecwu/Training/elasticsearch-7.14.1-1/logs/gc.log.[0-9]*
      - /Users/joecwu/Training/elasticsearch-7.14.1-1/logs/gc.log
      - /Users/joecwu/Training/elasticsearch-7.14.1-2/logs/gc.log.[0-9]*
      - /Users/joecwu/Training/elasticsearch-7.14.1-2/logs/gc.log

  audit:
    enabled: true
    # Set custom paths for the log files. If left empty,
    # Filebeat will choose the paths depending on your OS.
    var.paths:
      - /Users/joecwu/Training/elasticsearch-7.14.1-1/logs/*_audit.json
      - /Users/joecwu/Training/elasticsearch-7.14.1-2/logs/*_audit.json

  slowlog:
    enabled: true
    # Set custom paths for the log files. If left empty,
    # Filebeat will choose the paths depending on your OS.
    var.paths:
      - /Users/joecwu/Training/elasticsearch-7.14.1-1/logs/*_index_search_slowlog.json
      - /Users/joecwu/Training/elasticsearch-7.14.1-1/logs/*_index_indexing_slowlog.json
      - /Users/joecwu/Training/elasticsearch-7.14.1-2/logs/*_index_search_slowlog.json
      - /Users/joecwu/Training/elasticsearch-7.14.1-2/logs/*_index_indexing_slowlog.json

  deprecation:
    enabled: true
    # Set custom paths for the log files. If left empty,
    # Filebeat will choose the paths depending on your OS.
    var.paths:
      - /Users/joecwu/Training/elasticsearch-7.14.1-1/logs/*_deprecation.json
      - /Users/joecwu/Training/elasticsearch-7.14.1-2/logs/*_deprecation.json
```

這邊可以看到，主要有以下五種 Elasticsearch 會寫的 Logs 需要收集：

* server
* gc
* audit
* slowlog
* deprecation

因為是專門針對 Elasticsearch 所提供的 module，所以其實在 module 之中，就已經為這些檔案的格式進行解析了，像是 Elasticsearch server log 在遇到有 error 時，stack trace 是有 `multi-line` 的格式，又或是要將這些 Logs 內容給結構化，整理成 Elastic Common Schema，讓後續查詢及分析能更容易使用，這些都直接在 Elasticsearch module 會直接進行處理。

> **小提醒 1：** 因為在這些 Logs 檔的資訊當中，沒有足夠的 Timezone 的資訊，所以 Filebeat 預設會以主機本機端的 Timezone 來當作處理的依據，所以如果要收集的檔案，他產生時寫入的時區，與運作 Filebeat 的主機的時區是不同的話，可以透過 `event.timezone` 欄位，來修改成想要要指定的時區，這部份就會可以透過 `processors` 來進行操作。

> **小提醒 2：** 由於我的情境中，是直接使用一台主機安裝兩個 Elasticsearch Nodes，但一般 production 環境中，比較不會這樣安裝，所以配置上應該較少會直接針對二組 Elasticsearch 的路徑來進行設定，最好是透過組態管理的工具，或是容器化的佈署方式，會讓這配置檔+路徑更好管理。

### 收集 Kibana Logs

#### 啟用 Kibana Logs

Kibana 的 Logs 有兩種：

1. **Kibana 的 Server Log**

要收集 Kibana server logs 時，需特別注意到一件事，Kibana 預設不會將 logs 寫到檔案，會是直接在啟動時將 logs 輸出到 `stdout`，因此我們會先需要在 Kibana 的設定檔 `./config/kibana.yml` 當中，指定 logs 輸出到檔案：

```
logging.dest: ./logs/kibana.log
```

> 小提醒 1：Kibana 的 log 輸出並沒有支援 log rotate ，所以應該要使用外部的 log rotate 的機制，避免讓 log 檔案無限的長大，吃光硬碟空間。

> 小提醒 2：Kibana 的 log 其實真的很少會用到，要不要記錄下來並且傳送到 Elasticsearch 可以依照大家自己的 Log 管理規範來拿捏，如果 Kibana 平常只是公司內部運維人員自己使用，或許不一定要寫，但如果是有開放給各單位使用，甚至是外部的使用者，這部份就會建議最好還是記錄下來，至於想保留短一點的時間，可以在進入 Elasticsearch 之後再依需要進行 Log 生命週期的管理。

1. **Kibana 的 Security Audit Log**

如果你的 Elasticsearch 是有開啟 Security 的功能時，Kibana 的 `xpack.security.enabled` 會自動啟用，所以這部份不用特別的調整，不過 Security 相關的 Audit logs 預設不會記錄在檔案之中，這些 Audit logs 就是屬於 Kibana 當中的一些與安全性相關的日誌，例如使用者的登入、哪些人存取哪些資源…等，我們首先要先啟用 Audit logs：

```
xpack.security.audit.enabled: true
```

接下來指定 Audit logs `appender` 的配置設定，這邊是有支援 log rotate的：

```
xpack.security.audit.appender: 
  type: rolling-file
  fileName: ./logs/audit.log
  policy:
    type: time-interval
    interval: 24h 
  strategy:
    type: numeric
    max: 10 
  layout:
    type: json
```

在上面的配置，我分別把 Kibana 的 Server Log 產生到 `./logs/kibana.log` 這個檔案，另外把 Audit logs 產生到 `./logs/audit.log` 並且有指定 log rotate。

相關的 Kibana Audit log 的設定可以參考 [官方文件 - Kibana Audit Logging Settings](https://www.elastic.co/guide/en/kibana/current/security-settings-kb.html#audit-logging-settings) \[1]。

#### 設定 Filebeat 的 Kibana Module

有了 Kibana logs 之後，我們要使用到 Filebeat modules 裡的 **Kibana module**，使用以下指令啟用 Module：

```
./filebeat modules enable kibana
```

啟用 module 之後，我們到 `./modules.d/` 目錄底下，修改 `kibana.yml` 的設定檔：

```
# Module: kibana
# Docs: https://www.elastic.co/guide/en/beats/filebeat/7.x/filebeat-module-kibana.html

- module: kibana
  # Server logs
  log:
    enabled: true

    # Set custom paths for the log files. If left empty,
    # Filebeat will choose the paths depending on your OS.
    var.paths:
      - /Users/joecwu/Training/kibana-7.14.1-darwin-x86_64/logs/kibana.log

  # Audit logs
  audit:
    enabled: true

    # Set custom paths for the log files. If left empty,
    # Filebeat will choose the paths depending on your OS.
    var.paths:
      - /Users/joecwu/Training/kibana-7.14.1-darwin-x86_64/logs/audit*.log
```

這邊就分別針對前面所產生的 Kibana server logs 以及 Audit logs 的路徑進行設定。

### 收集 Logstash Logs

#### 設定 Filebeat 的 Logstash Module

收集 Elasticsearch Logs 時，我們會使用到 Filebeat modules 裡的 **Logstash module**，因此我們先啟用 Module：

```
./filebeat modules enable logstash
```

啟用 module 之後，我們到 `./modules.d/` 目錄底下，修改 `logstash.yml` 的設定檔：

```
# Module: logstash
# Docs: https://www.elastic.co/guide/en/beats/filebeat/7.x/filebeat-module-logstash.html

- module: logstash
  # logs
  log:
    enabled: true
    # Set custom paths for the log files. If left empty,
    # Filebeat will choose the paths depending on your OS.
    var.paths: ["/Users/joecwu/Training/logstash-7.14.1/logs/logstash-plain.log*"]

  # Slow logs
  slowlog:
    enabled: true
    # Set custom paths for the log files. If left empty,
    # Filebeat will choose the paths depending on your OS.
    var.paths: ["/Users/joecwu/Training/logstash-7.14.1/logs/logstash-slowlog-plain.log*"]
```

由於 Logstash 運作起來之後，預設會將 Logs 產生到解壓縮目錄下的 `./logs/` 裡頭，並且會產生 `logstash-plain.log` 以及 `logstash-slowlog-plain.log` 兩種 logs 檔，因此我們同在 module 的設定檔中，設定好這兩種 logs 檔的位置即可。

### 收集 Filebeat Logs ?

因為我們主要是透過 Filebeat 來收進檔案類型的 Logs，因此如果透過 Filebeat 本身自己收集自己所寫的 Logs，其實會有點奇怪，也就是如果 Filebeat 自己發生狀況時，他的 Error Logs 可能就沒辦法被收集並往 Elasticsearch 傳送，因此如果要收集 Filebeat 的 logs 時，建議也應該透過另外的 Filebeat 來進行收集。

另外 Filebeat 本身的 modules 之中，也沒有提供 beats 的 module，這部份的一種做法，是直接將 beats 的 Logs 透過 `stderr` 寫到 `journald` ，並且再透過 Beats 家族中的 **Journalbeat** 進行收集。\[2]

當然如果真的要寫成檔案，還是可以透過以下的方式，在 `filebeat.yml` 中進行設置：

```
logging.level: info
logging.to_files: true
logging.files:
  path: /var/log/filebeat
  name: filebeat
  keepfiles: 7
  permissions: 0644
```

## 在 Kibana 中，如何查看經由 Filebeats 收集到的 Elastic Stack Logs

### Observability 的 Logs

首先就是可以使用這一系列文章，前面的 [13 - Logs - 挖掘系統內部發生的狀況 (1) - Logs 與 Filebeat 的基本介紹](https://ithelp.ithome.com.tw/articles/10274299) 所介紹到的 Observability Logs 的使用方式，來進行檢視我們所收集進入 Elasticsearch 裡的 Logs。

![image-20210930234212131](https://i.imgur.com/cIlm4CO.png)

### Kibana Stack Monitoring

再來是進入到 Kibana 專門針對 Elastic Stack 所建立的 **Stack Monitoring**。

這裡面會帶出最近的 Elasticsearch 的 Logs ，進一步的選擇之後，也能自動帶入到 Observability Logs 的畫面之中，並且會自動帶入篩選的條件。

![15-kibana-stack-monitoring](https://i.imgur.com/LSy2dMN.png)

### Kibana Dashboards - Logstash

再來就是 Kibana 所建立好的各種 Dashbards，其中有針對經由 Filebeat 所收集的 Logstash 資訊，建立好兩種 Dashboard。

![image-20210930232324210](https://i.imgur.com/j0BtXJ6.png)

這邊以其中一種 **Logstash Logs ECS** 為例：

![image-20210930232134528](https://i.imgur.com/LnUIDN8.png)

### Elastic Security

最後是 Kibana 的 Audit Logs 在 Elastic Security 的 Solution 之中，也有針對這些安全性的資訊，建立好一些檢視的工具，針對 Kibana Hosts 相關的 Audit Logs，也能在 Hosts 當中查看安全性分析的一些結果，進而可以再追縱到原始的 Logs。

![15-Kibana-Security-Hosts](https://i.imgur.com/6KQvlp5.png)

## 其他注意事項

Filebeat 在 7.15 版的時候，使用的依然是舊版的 Elasticsearch Index Template，也就是 `_template` 這個 endpoint，不是新版的 `_index_template`，有時我們要使用收集到的資料時，會要參考特定欄位的 Mapping 的設定，這時如果要查閱 Filebeat 所建立的 Index Template，記得要從 `_template` 這邊來查詢。

## 參考資訊

1. [官方文件 - Kibana Audit Logging Settings](https://www.elastic.co/guide/en/kibana/current/security-settings-kb.html#audit-logging-settings)
2. [官方文件 - Journalbeat](https://www.elastic.co/guide/en/beats/journalbeat/7.15/journalbeat-overview.html)


# 透過 Filebeat 收集 Infrastructure 中各種服務的細節資訊

### 本篇學習重點

* 使用 Filebeat 收集使用主機 (Host) 所佈署的 Infrastructure 當中 Logs 的方式
* 使用 Filebeat 收集使用容器 (Container) 佈署的 Infrastructure 當中 Logs 的建議做法

## 使用 Filebeat 收集使用主機 (Host) 所佈署的 Infrastructure 當中的 Logs

當我們使用非容器化的佈署方式，也就是直接在各主機上安裝服務或應用程式時，我們要收集這些機器上的 Logs 時，可能會有二種方式：

### 直接在服務的機器上安裝 Filebeat

例如我們在一個單純的三層式服務架構之中，有以下三種服務的主機：

* Apache Web Server
* Java Backend Service
* MySQL Database

而我們要收集這幾台機器上的 Logs 時，我們會建議直接在每台主機上安裝 Filebeat，並且在不同的機器上配置不同的 Inputs 與 Modules 設定值，來收取我們希望取得的 Logs，例如：

| 主機所安裝的服務             | 希望收集的資訊                    | Inputs               | Modules  | 說明                                                                          |
| -------------------- | -------------------------- | -------------------- | -------- | --------------------------------------------------------------------------- |
| Apache Web Server    | Apache access & error logs |                      | `Apache` | 透過 `Apache` module 指定 `access` 與 `error` Logs 的檔案路徑。                        |
| Java Backend Service | Application logs           | `Log` 或 `filestream` |          | 由於 Logs 的格式是自訂義的，直接使用 `filestream` 來指定要 Logs 的檔案來源路徑，甚至要特別處理的 `processors`。 |
| MySQL Database       | MySQL logs                 |                      | `MySQL`  | 透過 `MySQL` module 指定 `error` 與 `slowlog` Logs 的檔案路徑。                        |
| 主機本身                 | 主機本身的系統日誌，或是 auth logs     |                      | `System` | 透過 `System` module 指定 `syslog` 與 `auth` Logs 的檔案路徑。                         |

### 使用 Shared Drive 的方式收集 Logs

如果有使用 Shared drive 的方式，來簡化『集中化收集日誌』這件事的話，基本上就是在專門運行 Filebeat 的機器上，透過 NFS 之類的 Shared drives 取得各服務的日誌。

使用這種方式，在 Filebeat 的 **Inputs** 與 **Modules** 的配置上，沒有特別的差異，只是會一口氣將這些配置設定在同一個 Filebeat 身上，不過有個地方要特別注意，Filebeat 的 `Log` 與 `filestream` input，在使用網路共享或是雲端應商的儲存空間時，因為會使用磁碟機的 `inode` 資訊與 `device id` 當作檔案的唯一識別，有時 shared drive 的這個值會改變，導致於已經處理過的檔案，又重新被判斷成是新的檔案，造成重覆的傳送，這部份會要透過自行產生唯一的識別 ID，並且指定 `inode_maker` 來避免這件事發生，細節請參考 [官方文件 - Filebeat Input - Logs](https://www.elastic.co/guide/en/beats/filebeat/7.15/filebeat-input-log.html#file-identity)。

## 使用 Filebeat 收集使用容器 (Container) 佈署的 Infrastructure 當中的 Logs

### Docker 環境的 Log 產生方式

當我們使用 Docker 來佈署服務時，一般的做法都是在 Container 之中直接把 log 輸出到 `stdout` 或是 `stderr`，並透過 Docker logging driver 去進行處理，而實際的檔案預設會寫在 Docker host 身上，例如在 linux 環境中，會寫到 `/var/lib/docker/containers/` 的路徑底下。

> 注意：因為容器的生命週期可能隨時會中斷，一個有良好 scalability 的容器化架構的設計，會是 stateless 的，也就是當容器被關閉時，存在裡面的 logs 就消失了，所以一般絕對不建議把 logs 直接寫在 container 之中，至少也要使用 volume 的方式將 logs 寫到會持久保存的另外的儲存空間裡。

### 收集所有 Docker Conainer 產生的 Logs

因此我們要使用 Filebeat 來收集所有 Docker Container 產生的 logs 時，我們其實就是針對 Docker host 的這個路徑裡的 logs 下手，這也是 Filebeat **Container Inputs** 所使用的方法。

> 注意：Docker Input 已經在 7.2 版時棄用 (deprecated) 了，之後請直接使用 Container Input。

而 Container Input 的設定方式如下：

```
filebeat.inputs:
- type: container
  paths: 
    - '/var/lib/docker/containers/*/*.log'
```

裡面也可以在 `stream` 屬性去設定只要針對 `stdout` 或是 `stderr` 的內容進行收集，細節可以參考 [官方文件 - Filebeat Input - Container](https://www.elastic.co/guide/en/beats/filebeat/7.15/filebeat-input-container.html)。

> 注意：在這種使用一口氣收集所有 Containers 產生的 logs 時，應該要搭配一些內建的 **Processors**，例如 `add_docker_metadata`、`add_kubernetes_metadata`、`add_cloud_metadata`，將產生 logs 的 container 或是 pod 的資訊增加在 logs 之中，以便於後續使用時能進行分辨。

### 使用 Kubernetes DaemonSet 收集 K8S Nodes 身上各 Pods 的 Logs

如同我們前面 [使用 Metricbeat 掌握 Infrastructure 的健康狀態 Kubernetes 篇](/tech-sharing/uncle-joe-teach-es-elastc-observability/metrics-guan-cha-xi-tong-de-jian-kang-zhi-biao/shi-yong-metricbeat-zhang-wo-infrastructure-de-jian-kang-zhuang-tai-kubernetes-pian) 在介紹 Metricbea 時，提到在 Kubernetes 使用 DaemonSet 的方式同樣的考量，可以確保每個 Node 身上有一個獨立的 Filebeat pod 來進行收集整個 Nodes 上所有 Pods 裡的 logs，並且傳送到 Elasticsearch。

而在 Kubernetes 裡，也如同前面『 Docker 環境的 Log 產生方式』運作的方式一樣，因為 Pods 會是 stateless，因此一般我們也都會直接將 logs 透過 `stdout` 或是 `stderr` 送出，並且由運行這些 Pods 所在的 Node，寫在實體的 disk 路徑 `/var/log/containers/*.log` 之中，因此我們也就可以使用 DaemonSet 的方式來佈署 Filebeat，並且將主機的 `/var/log` mount 到 Filebeat 身上，讓他可以直接使用 **Container Inputs** 的方式，取的這個所有 Pods 產生的 logs。

例如以官方的 [filebeat-kubernetes.yaml](https://raw.githubusercontent.com/elastic/beats/master/deploy/kubernetes/filebeat-kubernetes.yaml) 為例 (以下僅截取 ConfigMap 與 DaemonSet 的主要配置，完整版請參考原始檔案)：

```
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: filebeat-config
  namespace: kube-system
  labels:
    k8s-app: filebeat
data:
  filebeat.yml: |-
    filebeat.inputs:
    - type: container
      paths:
        - /var/log/containers/*.log
      processors:
        - add_kubernetes_metadata:
            host: ${NODE_NAME}
            matchers:
            - logs_path:
                logs_path: "/var/log/containers/"

    # To enable hints based autodiscover, remove `filebeat.inputs` configuration and uncomment this:
    #filebeat.autodiscover:
    #  providers:
    #    - type: kubernetes
    #      node: ${NODE_NAME}
    #      hints.enabled: true
    #      hints.default_config:
    #        type: container
    #        paths:
    #          - /var/log/containers/*${data.kubernetes.container.id}.log

    processors:
      - add_cloud_metadata:
      - add_host_metadata:

    cloud.id: ${ELASTIC_CLOUD_ID}
    cloud.auth: ${ELASTIC_CLOUD_AUTH}

    output.elasticsearch:
      hosts: ['${ELASTICSEARCH_HOST:elasticsearch}:${ELASTICSEARCH_PORT:9200}']
      username: ${ELASTICSEARCH_USERNAME}
      password: ${ELASTICSEARCH_PASSWORD}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: filebeat
  namespace: kube-system
  labels:
    k8s-app: filebeat
spec:
  selector:
    matchLabels:
      k8s-app: filebeat
  template:
    metadata:
      labels:
        k8s-app: filebeat
    spec:
      serviceAccountName: filebeat
      terminationGracePeriodSeconds: 30
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet
      containers:
      - name: filebeat
        image: docker.elastic.co/beats/filebeat:8.0.0
        args: [
          "-c", "/etc/filebeat.yml",
          "-e",
        ]
        env:
        - name: ELASTICSEARCH_HOST
          value: elasticsearch
        - name: ELASTICSEARCH_PORT
          value: "9200"
        - name: ELASTICSEARCH_USERNAME
          value: elastic
        - name: ELASTICSEARCH_PASSWORD
          value: changeme
        - name: ELASTIC_CLOUD_ID
          value:
        - name: ELASTIC_CLOUD_AUTH
          value:
        - name: NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        securityContext:
          runAsUser: 0
          # If using Red Hat OpenShift uncomment this:
          #privileged: true
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 100Mi
        volumeMounts:
        - name: config
          mountPath: /etc/filebeat.yml
          readOnly: true
          subPath: filebeat.yml
        - name: data
          mountPath: /usr/share/filebeat/data
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
        - name: varlog
          mountPath: /var/log
          readOnly: true
      volumes:
      - name: config
        configMap:
          defaultMode: 0640
          name: filebeat-config
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
      - name: varlog
        hostPath:
          path: /var/log
      # data folder stores a registry of read status for all files, so we don't send everything again on a Filebeat pod restart
      - name: data
        hostPath:
          # When filebeat runs as non-root user, this directory needs to be writable by group (g+w).
          path: /var/lib/filebeat-data
          type: DirectoryOrCreate
---
```

* DaemonSet 有宣告 mount `/var/lib/docker/container` 以及 `/var/log`，讓 Filebeat 可以取得 container 的 logs。
* DaemonSet 有另外宣告 `/var/lib/filebeat-data` 當作 **data** 的 volumeMounts 來掛載到 container 內的 `/var/share/filebeat/data` 路徑，讓這個 DaemonSet 的 Filebeat 運作時的檔案，是寫到實體主機的 disk 之中，不會因為重啟而造成資料的遺失。
* 另外有特別使用 `add_kubernetes_metadata` 的 **processor**，讓這些一口氣收集進來的各種 Pod 產生的 logs，加上 `Pod Name`、`Pod UID`、`Namespace`、`Labels` 的資訊，讓我們在後續分析處理時，能分辨得出來是哪邊產生的 log。
* 有一段是註解掉的 autodiscovery 的配置，若是使用 Docker、Kubernetes、或是使用 Nomad 時，可以考慮使用 autodiscovery 的機制，讓 container 或是 pod 動態增加時，也能夠由 DaemonSet 自動開始收集新增加容器產生的 logs。

### 使用 Kubernetes SideCar 的方式收集 K8S Pod 裡服務產生的實體 Logs

如果 Service container 寫的是實體的 Logs 檔案，另一種方式是我們可以使用 SideCar 的方法將 Logs 轉成 `stdout` 與 `stderr` 的方式往外傳送，以使用我們先前建議的容器化收集 logs 的方法。

首先將 Pod 中的 Service container 與 Sidecar container 使用 **Shared Volume**，讓 Sidecar container 能夠直接取得 Service container 產生 的 logs，並且 SideCar container 再負責將這個 logs `tail` 出來，輸出到 `stdout` 或 `stderr`，這樣後續就能同樣的使用 DaemonSet 或是專門收集 log 的 pod 的機制，進行後續的處理。

## 參考資料

1. [官方文件 - Filebeat Input - Logs](https://www.elastic.co/guide/en/beats/filebeat/7.15/filebeat-input-log.html#file-identity)
2. [官方文件 - Filebeat Input - Container](https://www.elastic.co/guide/en/beats/filebeat/7.15/filebeat-input-container.html)


# Traces - 觀察應用程式的效能瓶頸

Observability 的一個核心精神，是讓我們有能力觀察系統運作的狀況，Elastic Observability 當中的 APM (Application Performance Monitoring) 就是實現 Observability 這部份精神的其中一個重要的工具，幫助我們能輕鬆的掌握系統運作的效能分析、發生異常時環節、或是在複雜的多層次架構或是微服務架構之下，服務元件之間的相依性及影響的關連，這樣的工具要如何來使用及應用，將會是這個章節的主軸。

* [01 - Elastic APM 基本介紹](/tech-sharing/uncle-joe-teach-es-elastc-observability/traces-guan-cha-ying-yong-cheng-shi-de-xiao-neng-ping-jing/elastic-apm-ji-ben-jie-shao)
* [02 - 使用 APM-Integratoin-Testing 建立 APM 的模擬環境](/tech-sharing/uncle-joe-teach-es-elastc-observability/traces-guan-cha-ying-yong-cheng-shi-de-xiao-neng-ping-jing/shi-yong-apmintegratointesting-jian-li-elastic-apm-de-mo-ni-huan-jing)
* [03 - 如何在 Kibana 使用 APM UI](/tech-sharing/uncle-joe-teach-es-elastc-observability/traces-guan-cha-ying-yong-cheng-shi-de-xiao-neng-ping-jing/ru-he-zai-kibana-shi-yong-apm-ui)
* [04 - 使用 APM Server 來收集 APM 數據](/tech-sharing/uncle-joe-teach-es-elastc-observability/traces-guan-cha-ying-yong-cheng-shi-de-xiao-neng-ping-jing/shi-yong-apm-server-lai-shou-ji-apm-shu-ju)
* [05 - 透過 APM Agents 收集並傳送後端服務運作的記錄](/tech-sharing/uncle-joe-teach-es-elastc-observability/traces-guan-cha-ying-yong-cheng-shi-de-xiao-neng-ping-jing/tou-guo-apm-agents-shou-ji-bing-chuan-song-hou-duan-fu-wu-yun-zuo-de-ji-lu)
* [06 - 透過真實使用者監控 (RUM, Real User Monitoring) 來改善使用者體驗](/tech-sharing/uncle-joe-teach-es-elastc-observability/traces-guan-cha-ying-yong-cheng-shi-de-xiao-neng-ping-jing/tou-guo-zhen-shi-shi-yong-zhe-jian-kong-rum-real-user-monitoring-lai-gai-shan-shi-yong-zhe-ti-yan)


# Elastic APM 基本介紹

### 本篇學習重點

* APM 的基本介紹與架構
* APM 所收集的資料模型
* APM 的應用場影

***

![17-Traces-APM-Overview](https://i.imgur.com/bBIZywf.png)

## 什麼是 Elastic APM (Application Performance Monitoring) ?

Elastic APM 是一個能讓你『即時』監控、觀察、分析應用程式及服務的工具，透過收集應用程式內元件之間溝通時的記錄，收集包含各種詳細與效能相關的資訊，例如以下的資訊：

* 前端使用者的即時行為資訊
* 前端對應用程式 API 的請求與回應
* 應用程式對內部服務 API 的請求與回應
* 內部服務對 Cache 的存取
* 內部服務對 Database 的存取
* 服務存取外部的第三方服務的請求與回應

除了效能相關的資訊之外，如果處理中有發生例外狀況的錯誤，甚至是程式錯誤發生時的 stacktrace 也都會被 APM 收集，另外也會順便收集某些有支援的系統或運作環境相關的 metrics，例如：JVM metrics, Go runtime metrics。

## Elastic APM 的基本架構

![17-apm-architecture-cloud](https://i.imgur.com/XxfxGWY.png)

Elastic APM 的基本架構如上圖共包含四個主要的元件：

* **APM Agents：** 提供各種語言實作的 Library，能協助開發人員加在應用程式之中，在應用程式執行的過程中負責收集各種效能相關的資訊或是錯誤的資訊，內含許多常用的 framework 或是 library 的整合，像是 cache 或 db 的存取 library，使用這些 library 時就不用額外自己開發要收集的資訊。
* **APM Server：** 以 libbeat 實作的 HTTP server，負責接收各個 APM Agents 所收集到的 APM 資訊，將這些資訊進行驗證及加工處理後，彙整並傳送給 Elasticsearch 進行 Indexing 及儲存。
* **Elasticsearch：** 負責 APM 資料的儲存，提供分析運算、資料生命週期的管理、資料備份等處理核心。
* **Kibana APM UI：** 讓存在 Elasticsearch 裡的 APM 資料能被快速的查閱、追縱、分析的 UI 工具。

## Elastic APM 的資料模型

Elastic APM 的資料，每一筆都會是一個事件 (event)，而這些事件總共有四種的類型：

* Transactions
* Spans
* Errors
* Metrics

除此之外，events 當中都可以在 APM Agent 端自行加上我們自己想收集的額外資訊，並定義在擴充的欄位當中。

以下分別對四種資料類型進行說明。

### Spans

Spans (跨度，可理解成片刻的一小段時間)，表示一個活動的開始到結束的記錄，也就是代表一段程式執行時發生的資訊，並且因為一連串執行與處理的過程中，一個 Span 可能也會與其他的 Span 有上、下層的關係，在 Span 裡面會記錄：

* `transaction.id`：屬於哪一個 Transaction。
* `parent.id`：如果是另個 Transaction 或是 Span 有上、下層關係時，會記錄他的上層。
* Span 的開始及結束時間。
* Span 的 `name`。
* `type`、`subtype`、`action`。
* 過程中有錯誤發生時會包含 `stacktrace` 的資訊。

### Transactions

Transactions (交易)，針對一個相對於 Span 更高層級的事件 (event) 的請求與回應，例如：發送一個外部的 Request、批次的作業處理、背景執行的工作、或是在程式執行中自行定義的一個處理行為…等，都可以是一個 Transaction，在一個 Transaction 之中，可以包含 0 到多個 Spans，Transaction 裡面會記錄：

* 事件發生的 `timestamp`。
* `unique id`、`type`、`name`。
* 事件發生的環境 (Environment) 相關的資訊，例如：
  * Service 的 `environment`、`framework`、`language`。
  * Host 的 `hostname`、`IP`。
  * Process 的 `pid`。
  * URL 的 `domain`、`port`、`query string`。
* 其他特定 APM Agent 所收集到的特定資訊。
* 使用者自行定義的 `labels` 或 `custom` 欄位的資訊。

### Errors

Errors (錯誤)，當 APM Agent 收到 Errors 的資訊時，會產生 Errors 這樣的 event，並且在裡面會記錄發生當時的 `exception` 資訊，或是 Error 發生當下的 `logs`，Error 裡面會記錄：

* 錯誤發生當時 `exception` 或 `error log` 所收集到的 `stacktrace` 資訊。
* `culprit` 記錄錯誤發生的地方。
* 與錯誤發生時相關的 Transaction 的 `transaction.id`。
* 事件發生的環境 (Environment) 相關的資訊，例如：
  * Service 的 `environment`、`framework`、`language`。
  * Host 的 `hostname`、`IP`。
  * Process 的 `pid`。
  * URL 的 `domain`、`port`、`query string`。
* 使用者自行定義的 `labels` 或 `custom` 欄位的資訊。

### Metrics

Metrics (指標)，APM Agent 會收集一些最基本的主機層級 (host-level) 的 metrics，例如：

* System metrics。
* Process 層級的 CPU 和 Memory metrics。
* JVM metrics。
* Go runtime metrics。

## APM 的運用場景

* 分散式追縱 (Distributed Tracing)：分散式追縱是維運微服務架構時不可缺少的重要工具，APM 透過定義 Trace，並在裡面包含了一組的 Transactions 及 Spans，用來代表一個特定的服務請求從到頭尾的過程，並且在跨服務之間透過在 Header 中加入 `trasc-id`、`span-id`、`parent-id`，來實現分散式追縱的功能，讓我們能透過 Kibana 能輕易的觀察一個橫跨多個服務的請求，過程中哪個環節的執行效能較慢、或是在發生錯誤時能快速的查看歷程中發生什麼樣的事。
* 真實使用者監控 (Real User Monitoring)：讓我們透過 Elastic's RUM Agent 收集前端使用者在 web 上的操作，幫助我們從真實的使用者情境，並且關注在使用者體驗來分析效能狀況、進行效能最佳化、或是更即時的發現系統存在的問題。
* 與 Elastic Observability 的整合，像是透過與先前介紹的 Logs 整合，在 Logs 寫入時也加入 APM event 的識別 ID，讓我們使用 APM 進行分析時，在追到某個需要深入探索的地方時，能輕易的接續到 Logs 的部份查閱事件發生上、下文的 Logs。

## 參考資料

1. [官方文件 - APM Overview](https://www.elastic.co/guide/en/apm/get-started/current/index.html)


# 使用 APM-Integratoin-Testing 建立 Elastic APM 的模擬環境

### 本篇學習重點

* 如何使用 Elastic 官方提供的 APM Integration Testing 工具

## 前言

前面的章節介紹了 Elastic APM，由於 Elastic APM 的運作，會需要有實際的應用程式及服務，讓使用者把 APM Agent 埋進去，同時也要有足夠複雜的情境，才能展示 APM 的相關功能，因此在這邊直接使用 Elastic 官方所提供的一個開放原始碼的專案 - APM Integration Testing，讓我們透過這個專案所建立的情境，快速的架設一組實際的 Elastic APM 解決方案及實用實例，從中學習 Elastic APM 的佈署方式與使用方法。

## 簡介 APM Integration Testing

[APM Integration Testing](https://github.com/elastic/apm-integration-testing) 是一個公開在 GitHub 的開放原始碼的專案，這個專案主要語言是用 Python 撰寫，並且使用 Docker 來運作 Elastic Stack 的各種服務以及 opbeans 這個 Demo 專用的庫存管理系統，讓我們能夠擁有一個 Elastic APM 所需要執行的情境，並且能夠將當中的某些元件替換成真實運作的版本，可以協助開發人員進行 debug，或是協助整合測試 (Integration Test) 所需使用的複雜的環境。

### 包含的角色

以下幾種角色，是 APM Integration Testing 的 Docker Containers 運作起來時，裡面有的角色：

* Elastic Stack
  * Elasticsearch
  * Kibana
  * APM Server
  * Heartbeat
  * Filebeat
  * Metricbeat
  * Packetbeat
* opbeans 庫存管理系統的各種語言版本的實作，並且埋入 APM Agent
  * opbeans-go
  * opbeans-java
  * opbeans-ruby
  * opbeans-dotnet
  * opbeans-node
  * opbeans-python
* 針對 opbeans 庫存管理的系統的 node.js 版本，實作 Real User Monitoring
  * opbeans-rum
* opbeans 所使用到的 Database 或是 Cache 等服務
  * PostgreSQL
  * Redis
* 自動模擬存取流量的 opbeans-load-generator
* 專門製造錯誤情況發生，讓壓測能更擬真的 Dyno

APM Integration Testing 就是以這些角色組成一個讓我們可以進行測試及使用的環境，以下畫面，是使用預設配置所建立的環境當中所包含的角色：

![18-apm-tools-service-map](https://i.imgur.com/bR93Z78.png)

## 安裝 APM Integration Testing

接下來說明安裝 APM Integration Testing 的步驟及要注意的事項。

### 準備環境

在執行 APM Integration Testing 的環境，我們會需要準備：

* Docker
* Docker compose
* Python 3

> 注意：Mac M1 目前實測 build 到 RUM 的部份時，會發生 chrome 相關套件的錯誤，所以請不要用 M1 來執行。

### 執行 APM Integration Testing

要執行 APM Integration Testing，首先我們將整個專案從 GitHub 抓下來：

```
git clone https://github.com/elastic/apm-integration-testing.git
```

裡面有一個 Python 程式 `./scripts/compose.py`，會是主要的執行檔，我們先透過這們程式來安裝一個『完整版』的環境：

```
./scripts/compose.py start --all 7.15.0 --release
```

主要的參數如下：

* `start`：初始化並且啟動整個所有的環境。
* `--all`：完整版的各種服務及元件都會進行安裝
* `7.15.0`：Elastic Stack 的指定版本
* `--release`：使用 release 的版本 (如果不指定的話，會使用 snapshot 的版本)。

其他可選用的參數，因為支援的參數非常非常多，我這邊只舉幾個例子：

* `--with-opbeans-java`：當我們不使用 `--all` 時，可以指定我們要安裝哪些版本的 opbeans 環境。
* `--no-apm-server`：不要啟動 APM Server。
* `--no-kibana` 、 `--no-elasticsearch`：不要啟動 Kibana、Elasticsearch，也可以另外指定外部的 Elasticsearch 或 Kibana。
* `--with-dyno`：啟動 Dyno Mode。

一開始執行時，因為會 build 相關的 Docker image，會跑蠻久的，以下是執行的 gif 畫面。

![apm-int-testing-compose](https://i.imgur.com/RLk2eyJ.gif)

### Docker-Compose

當執行完 `start` 之後，會在執行的目錄底下，建立一個 `docker-compose.yml` 的檔案，並且實際執行的運作，就是透過 `docker-compose` 運作起來。

因此我們後續可以使用 `docker-compose stop` 、 `docker-compose up` 等指令來停止或啟動，也可以直接去修改 `docker-compose.yml` 裡面的一些配置。

## 查看 APM Integration Testing 所建立的環境

當安裝及佈署完成之後，我們可以直接從 Kibana 查看，這邊注意預設是有開啟 `X-Pack Security`，並且預設的密碼會是 `changeme`：

![18-kibana-login](https://i.imgur.com/OY9Qmik.jpg)

登入之後，我們就可以在 Observability 的畫面，查看由 APM Integration Testing 這個環境及裡面的流量產生工具，所產生出來的各種服務存取的資訊，我們就有許多 APM 的資料可以來查看 APM 所提供的功能。

以下是使用 gif 檔來錄製操作的畫面：

![18-apm-int-testing-kibana-overview](https://i.imgur.com/7zaYv6s.gif)

## 壓測時的好幫手 - Dyno Mode

APM Dyno 是一個能幫我們在 opbeans 的這些運作的 demo 環境之中，建立出一些情境，這些情境是能協助我們進行壓測時，能模擬出更接近真實情境的環境，

例如：

* 容器裡的 CPU 能力
* 容器裡的 Memory 數量
* 網路的 latency
* 網路頻寬
* 網路不穩的狀況
* 產生壓測請求的 worker 數量
* 產生一定比例的 Error rate

這些功能有透過 UI 的方式讓我們能直接進行調整，並且可以針對某一台 Container 進行指定。

![18-apm-dyno](https://i.imgur.com/YD4u2lp.png)

不過 Dyno mode 只有提供 `opbeans-python` 版本能使用，並不是所有的 opbeans 版本都支援。

> 注意：官方文件寫的指令 `--dyno` 實際使用時發現是寫錯的，是要帶入 `--with-dyno` 才是正確的。

## 使用雲端主機服務 AWS 或 GCP

文件中有特別提到，如果使用的是 AWS、GCP 這種雲端主機服務時，要用 APM Integration Testing 的這個工具，可以透過 port forwarding 的方式來轉接。

例如以下在 `~/.ssh/config` 定義 `gcptunnel` 的這台主機：

```
Host gcptunnel
    HostName <my.gcp.host.ip>
    IdentityFile ~/.ssh/google_compute_engine           <--- yours may differ
    User jamie                                          <--- yours probably differs
    Compression yes
    ExitOnForwardFailure no
    LocalForward 3000 127.0.0.1:3000
    LocalForward 3001 127.0.0.1:3001
    LocalForward 3002 127.0.0.1:3002
    LocalForward 3003 127.0.0.1:3003
    LocalForward 3004 127.0.0.1:80
    LocalForward 5601 127.0.0.1:5601
    LocalForward 8000 127.0.0.1:8000
    LocalForward 9200 127.0.0.1:9200
    LocalForward 9222 127.0.0.1:9222
```

並使用 `ssh gcptunnel` 來執行。

## 參考資料

1. [GitHub: APM Integration Testing](https://github.com/elastic/apm-integration-testing)


# 如何在 Kibana 使用 APM UI

### 本篇學習重點

* 如何透過 Kibana 的 APM UI 來進行效能的分析
* APM UI 中的 Services、Traces、Dependencies、Service Map 的使用時機

## Kibana Observability APM

Kibana Observability 功能中的 APM UI，在主選單中包含了四個選項：

* Services (服務)
* Traces (追縱)
* Dependencies (相依性)
* Service Map (服務地圖)

![19-kibana-apm-menu](https://i.imgur.com/nKS3lGJ.png)

這四個功能貫串了整體 APM 的使用情境，以下我們將會各別介紹這四個功能的主要說明，以及使用的時機。

### Services (服務)

Service，是我們在 APM Agent 安裝時，指定的一個設定值，也是代表我們某個應用程式或某個服務的名稱，而 Kibana APM UI 也將 Service 定義成為一個主要的資料檢視的分類方式，讓我們能以 Service 的角度來檢視 Infrastructure 中各服務的狀態。

#### 使用時機

* 從『服務』或『應用程式』的整體角度，查詢效能狀況。
* 針對某一個『服務』或『應用程式』，裡面有發生哪些 Error？
* 針對某一個『服務』或『應用程式』，裡面所有的 Transaction 之中，執行最慢的是誰？有發生錯誤的比例是多少？發生錯誤的是誰？
* 針對某一個『服務』或『應用程式』，查看與他們相依的其他『服務』或『應用程式』有哪些？哪些其他服務的效能是瓶頸而受到牽連？
* 針對某一個『服務』或『應用程式』，快速查閱 CPU 與 Memory 的 Metrics 資訊。

#### Overview (總覽)

![19-kibana-services-overview](https://i.imgur.com/DszV07g.png)

如上圖在 Services 的 Overview 畫面之中，我們會有以下幾個部份：

1. Environment (環境)：我們可以直接針對指定在 APM Agent 收集資料時，先定義好的 Environment 資訊，例如： `Production` 環境、`Staging` 環境、或是我們自己定義的其他環境，進行籂選。
2. Search Box (搜尋區)：這個功能其實很彈性，讓我們自行依照想要的檢視條件，例如針對跑的特別慢的資料進行分析、或是經由我們自己事先加好的 `tag` 來篩選，可以專注在某種身份的會員、或是某種類型的產品…等。
3. Comparison (比較)：這是針對圖表中一些數據的走勢圖，我們預設要以之前的某段時間來做比較，例如上一週的同一時間、或是上個月的同一時間，這也是在分析異常狀況時常用的技巧。
4. 時間篩選：使用 Kibana 標準的時間篩選器，指定時間的範圍。
5. 服務列表：包含整體檢視時最重要的三個數據『平均延遲時間』、『吞吐量』、『交易失敗率』。

> 在這個畫面預設的排序方式，是照『健康狀態』，把最不健康的排最前面，讓我們優先掌握有問題的服務，而『健康狀態』的判定方式，是依照 Machine Learning 的 anomaly detection (異常偵測) 的功能，所以 ML 的這個功能要設定啟用才會有作用。

#### Service 細部檢視

![19-Kibana-APM-Services](https://i.imgur.com/HykrWE3.gif)

當我們點選某一個 Services 後，會進入這個 Service 自己的 Overview (總覽) 畫面，當中包含這個 Server 與效能、執行錯誤直接相關的

* Latency (延遲)
* Throughput (吞吐量)
* Transaction(交易) 效能較差的前五名，以及他們的效能相關的數據。
* Failed Transaction Rate (交易失敗率)
* Errors (錯誤)
* Time spent by span type：每個 span 所花費的時間比例。
* Depencencies (相依性)：所有與這個 Service 有相依的服務或是其他元件的效能影響數據。
* Instances Latency Distribution (實例延遲的分佈)：也就是這個服務有哪一些實際佈署的 instance，以及這些 instance 這段時間的平均數據的分佈，方便查看問題會不會是出在某一台特定的機器上。
* Instances (實例)：這個服務實際佈署的 instances，以及這些 instance 的效能數據.

> 注意：這邊我們在查閱的 `Metrics` 預設是 `Average`，但有時我們要分析效能狀況時，有時會要去掉極度的數據，這時記得可以用這個功能選擇 `95th percentile` 或是 `99th percentile` 。

至於每個服務細部檢視的畫面，都能再進一步查詢這個服務的 `Transactions`、`Dependencies`、`Errors`、`Metrics`、`Service Map`、`Logs`，這部份可以從上方的 GIF 圖檔的動畫查看。

### Traces (追縱)

Traces，讓我們能檢視某一個業務處理從頭到尾的過程，也就是對應到我們先前介紹到的 Transaction (交易)，中間的處理過程可能橫跨多個 Services，能讓我們做分散式追縱 (Distributed Tracing)，同時也會把相同的 Transaction 給 group 在一起，進行相同行為 Transaction 之間的校能比較與分析。

#### 使用時機

* 分散式追縱 (Distributed Tracing)，想知道某一個 Transaction (交易) 橫跨了哪些服務，中間有經過哪些處理、存取多少次資料庫、存取多少次快取…等。
* 對於某一個業務處理的執行效能不如預期，想要追縱是在哪個環節比較慢，
* 想分析哪一個 Transaction 對於整體系統的使用效能影響最大。

#### Overview (總覽)

Traces 的總覽，會是以整體篩選條件底下，所有符合的 Transaction 全部一起排列出來，在這個畫面預設的排序方式，是依照 Impact (影響程度)，判定方式是依照**最常使用**以及**反應時間最慢**來決定影響的程度。

![19-kibana-apm-traces-overview](https://i.imgur.com/BnaMMoQ.png)

#### Traces 細部檢視

在 Traces 的列表中，點選其中一筆 Trace 的項目之後，其實就會進入到 Service 細部檢視當中的 Transactions (交易) 的畫面。

![19-Kibana-APM-Traces](https://i.imgur.com/VHRTX5x.gif)

在這個畫面中，我們可以分析這個 API 在某段時間 Throughtput 較高時，與前一天、前一週、前一個月的同一段時間相比較是否一樣，也能從 Trace 的 Timeline 當中，查詢 Transaction 底下相關的 Spans，可以看到這個處理橫跨哪些服務，以及每個服務裡面執行的細節，這些細節的處理佔用了多少時間，並且在想要進行進一步調查時，可以透過 Investigate 進入 Elastic Observability 整合好的 Metrics 或是 Logs 的內容進行查看。

### Dependencies (相依性)

#### 使用時機

* 分析在 Infrastructure 中所使用的第三方服務或元件的效能狀況。
* 如果某個 Database (資料庫) 很慢，他的上游有哪些服務使用到他，以及這些服務的效能數據為何？
* 分析使用到外部的第三方服務時 (例如：金流 API)，最近一週每個時段的失敗率為多少?

#### Overview (總覽)

這部份列出的，是『服務』或『應用程式』，所使用到的其他元件或是第三方服務，像是資料庫、外部的 HTTP 服務…等，並且讓我們從這些 dependencies 來分析對效能的影響。

![image-20211004235227398](https://i.imgur.com/VsQAfFN.png)

#### Dependencies 細部檢視

在 Dependencies 的細部檢視的部份，讓我們除了能觀察這個 dependency 的效能數據，也會讓我們查看他的 Upstream (上游) 服候的效能數據，能協助我們判斷前後的影響關係，並且再進一步連結到 Service 細部檢視的頁面，進行查詢所影響的 Transaction 是哪些，甚至查詢實際執行的指令為何。

![image-20211004235427372](https://i.imgur.com/QHP5aBq.png)

### Service Map (服務地圖)

這個 Service Map 的檢視方式，是協助我們能以視覺化的方式，查看整體 Infrastructure 的服務與元件之前的相依關係，能協助我們追縱問題時，更精準的關注在需要注意的路徑上。

#### 使用時機

* 視覺化的掌握在 Infrastructure 中所有的『服務』、『應用程式』與第三方服務及元件之間的相依性。
* 從視覺化的地圖，快速掌握地圖上某個服務是否有發生異常。

#### Overview (總覽)

可以透過 Service Map 以視覺化的圖形，查看整體 Infrastructure。

![19-apm-service-map](https://i.imgur.com/2yn55Vj.png)

#### Service Map 細部檢視

在 Abnormal Detection 有開啟的情況下，有問題的服務，會被標示成紅色，可以進一步進入 Machine Learning 頁面查看。

或是可以針對某個服務進入 Dependency 或是 Service 的細部檢視的頁面，進行進一步的分析。

![19-Kibana-APM-ServiceMap](https://i.imgur.com/u5D6ZPr.gif)

***

以上是使用 Kibana Observability 中的 APM UI 所提供的功能，裡面的資料，是以 [前一篇文章](https://ithelp.ithome.com.tw/articles/10277150) 所介紹的 [APM Integration Testing](https://github.com/elastic/apm-integration-testing) 所產生的示範資料，在了解 Elastic APM 可以做到這些功能之後，下一篇我們將介紹進行自行架設的方式。

## 參考資訊

1. [官方文件 - Kibana APM - Get Started](https://www.elastic.co/guide/en/kibana/current/apm-getting-started.html)


# 使用 APM Server 來收集 APM 數據

### 本篇學習重點

* 更深入的了解 APM Server
* 如何安裝以及設定 APM Server
* APM Server 的校能調校技巧

## APM Server 概觀

![20-apm-architecture-diy](https://i.imgur.com/pXdxEB3.png)

APM Server 的定位，是用來專門收集 APM Agents 所發送的資料，身為同樣是**收集資料**的服務之一，APM Server 被 Elastic 歸類在 Beats 的生態圈之中，因此 APM Server 就是使用 `libbeat` 所開發出來的產品，如此一來 APM Server 就擁有 Beats framework 所開發出來針對資料收集處理的各種能力與機制。

### APM Server 的任務

* 負責收集 APM Agents 所傳送出來的 APM 資料。
* 將收集到的資料，進行基本的驗證。
* 將收集到的資料，進行加工處理，轉換成後續易於分析使用的格式。
* 負責將資料傳送到 Elasticsearch 儲存。
* 負責在 Elasticsearch 安裝 Ingest Pipeline 設定，讓資料匯入至 Elasticsearch 時，能經由 Ingest Pipeline 進行處理。
* 負責向 Elasticsearch 設定 APM 的 Index Template、ILM (Index Lifecycle Management) Policy。
* 如果 Elasticsearch 在某段時間無法承受大量的資料寫入時，APM 擁有的 Beats framework 當中的 queue 機制，會扮演 APM Agents 與 Elasticsearch 之間資料緩存的角色。

### 為何要有 APM Server 這個獨立存在的元件？

* 讓 APM Agents 輕量化，因為 Agents 是要安裝在『服務』或『應用程式』之中，有些功能可以不用存在於 Agents 身上。
* 由於 APM Server 的設計是 stateless (無狀態) 的，所以可以輕易的 scale out (向外擴展)，提升接收及處理大量 APM 資訊的能力。
* Elasticsearch 是架構中的元件，就像是資料庫一樣，不適合直接讓外部存取，APM Server 的存在，避免讓 APM Agents 直接存取到 Elasticsearch，因為有些 APM Agents 例如：RUM (Real User Monitoring) 是以 Javascript 的方式在 Browser 端執行，也就是在一般用戶端執行，若是直接存取 Elasticsearch 會有安全性上的疑慮。
* APM Server 可以掌控資料傳送進 Elasticsearch 的流量，避免 Elasticsearch 被過量的資料在短時間寫入，造成效能影響。
* 如果 Elasticsearch 發生問題，APM Server 可以緩存 APM Agents 送來的資料，避免增加 Agents 端的負擔，因為 Agents 有可能是安裝在我們的『服務』或『應用程式』之中，這樣會影響到『服務』或『應用程式』正常的運作。
* 在使用 RUM (Real User Monitoring) 時，Javascript 在前端有被 minify (縮小)，要在 APM 端能有效的解讀，會需要使用 Source Mapping 的定義，這樣在 APM UI 端時，所看到的資訊才能和原始檔對應起來，這個 Source Mapping 的對應的動作，就會在 APM Server 端處理。
* 經由定義專門接受 APM Data 的 JSON API，並且在 APM Agents 與 Elasticsearch 之中當作緩衝的角色，可以提升不同 Agents 版本與 Elasticsearch 之間的相容性，也就是某些版本有更動時，這些相容性的格式轉換，會在 APM Server 端處理掉。

## 如何安裝及使用 APM Server

### 安裝 APM Server

以下安裝的步驟，以自行安裝於 `MacOS` 環境為例：

1. 下載 APM Server 壓縮檔，並進行解壓縮

```
curl -L -O https://artifacts.elastic.co/downloads/apm-server/apm-server-7.15.0-darwin-x86_64.tar.gz
tar xzvf apm-server-7.15.0-darwin-x86_64.tar.gz
```

1. 進入目錄後，透過 APM Server 向 Elasticsearch 設定 Index Template、ILM (Index Lifecycle Management) Policy、Alias。

```
./apm-server setup --index-management
```

> `apm-server setup --pipelines` 這個指令可以省略，預設 APM Server 啟動時就會執行，當然也可以手動先設定好。

1. 在 `apm-server.yml` 中調整合適的配置設定。
2. 啟動 APM Server

```
./apm-server -e
```

1. 當 APM Server 啟動後，再來就是要安裝 APM Agents，讓 Agents 傳送收集的資料到 APM Server，APM Agents 的部份我們在下一個章節進行介紹。

### APM Server 常用設定

在使用 APM Server 時，在 `apm-server.yml` 有一些設定值可能會需要調整：

* `output.elasticsearch`：指定 Elasticsearch 的主機位置，或是 Security 相關的設定。
* `queue.mem.*`：Queue 的大小，如果 APM 收集的資料量較大、並且 APM Server 也配置較好的硬體規格時，這部份應該要有對應的調整。
* `max_procs`：如果對於 APM Server 能使用的 CPU 數量有要進行限制或調整的話，在此設定。
* `app-server.rum.enable`：如果要開啟 RUM (Real User Monitoring) 的功能，要特別設定啟用。(預設是關閉)
* `apm-server.kibana.*`：如果要透過 Kibana 來控制 APM Agents 的話，會需要設定這些配置。
* `logging.*`：要收集 APM 產生的 Logs 檔的話，要設定啟用寫入檔案，一般建議會啟用並配合 Filebeat 來收集 APM Server 的 Logs。
* `http.*`：如果我們要透過 Metricbeat 收集 APM Server 的 Metrics 時，會需要啟用 HTTP Endpoint，提供 Metricbeat 取得內部 Metrics 的 API。
* `apm-server.auth.anonymous.*`：當 RUM 設定啟用時，這個設定值也會自動被啟用，有些 `rate_limit` 的設定在量級較大的環境可能會需要被重新檢視設定。

## 多了解一點 APM Server

### APM Server 如何接收 APM Agents 的資料

APM Server 定義了一個 Events `Intake` 的 API，而 APM Agents 主要也就是使用這個 API，將我們先前介紹到的以下四種資料傳送給 APM Server：

* Transactions
* Spans
* Errors
* Metrics

`Intake` API 的 Endpoint 如下：

```
http(s)://{hostname}:{port}/intake/v2/events
```

RUM 有另外獨立的 Endpoints：

```
http(s)://{hostname}:{port}/intake/v2/rum/events
```

存取這個 API 使用的是 `HTTP POST`，並且如同 Elasticsearch `_bulk` API 的設計一樣，使用 [newline delimited JSON (NDJSON)](http://ndjson.org/) 的 `Content-Type` 來接收一次多筆 Events 的傳送，同時在回傳結果若有錯誤時，也會回傳每一個 Events 及獨立的錯誤資訊。

[官方文件 - APM Events API](https://www.elastic.co/guide/en/apm/server/current/events-api.html) 裡面有詳細的介紹四種資料各自的 Schema，有興趣的讀者可以參考。

### APM Server 效能調校技巧

針對 APM Server 的效能調校，這邊參考官方文件的介紹，有包含以下幾點：

#### 1. 調整 `output.elasticsearch` 的參數

包含以下三種調整方式：

* 適度的增加 `output.elasticsearch.worker` 的數量。
* 調大 `output.elasticsearch.bulk_max_size` 的數量，預設值 `50` 是蠻小的一個數字，如果硬體規格還不錯，甚至可以調高到 `5120` 來試試。
* 確認 `queue.mem.events` 的數量有被正確的設定是 `output.elasticsearch.worker` \* `output.elasticsearch.bulk_max_size` 的大小。

#### 2. 調整 Queue Size

透過調整 `queue.mem.events` 的大小，在 APM Server 使用更多的記憶體來緩存 APM Agents 所傳送進來的資料，如果為了能承受 Elasticsearch 發生一段時間無法正常運作，又要保持 APM Server 能接受 APM Agents 不斷傳送進來的資料時，可以從這邊下手。

#### 3. 增加 APM Server 的數量

如果發生 request timeouts 的錯誤時，通常是因為 APM Server 處理不了當下的資料量了，這時最簡單且有效的方式，就是增加 APM Server 的數量。

#### 4. 減少傳進 APM Server 資料的 Payload 大小

這部份要從 APM Agents 端下手，如果一次傳送到 APM Server 的資料量太大，有可能會發生 Request timeout，這時可以調小 `flush interval` 的設定，或甚至是降低 `sample rate` (取樣率)。

#### 5. 調整 Anonymous Auth 的 Rate Limit

當 APM Server 處理的量已經消化不完的時候，透過從 `Intake` API 進行節流，設定 `rate_limit.event_limit` 來限制一次能進來的資料量，能幫助 APM Server 有效的處理他能處理的資料量，整體的效能使用率會更佳。

#### 6. 記得刪除舊資料

預設 APM Server 建立的 ILM (Index Lifecycle Management) Policy 沒有包含刪除資料、或移到 Cold phase 等操作，這部份記得在使用 APM 時，也一樣要做好資料管理的規劃，避免 Elasticsearch 的資料隨時間不斷增長，最終導致資料量過多而影響服務的正常使用。

## 參考資料

1. [官方文件 - APM Server](https://www.elastic.co/guide/en/apm/server/current/overview.html)
2. [官方文件 - APM Events API](https://www.elastic.co/guide/en/apm/server/current/events-api.html)


# 透過 APM Agents 收集並傳送後端服務運作的記錄

### 本篇學習重點

* APM Agents 要解決的問題是什麼？
* APM Agents 提供什麼功能？
* 如何使用 APM Agents 的簡介
* 使用 APM Agents 要注意的事項。

## APM Agents 要解決的問題

Elastic APM Agents 的任務主要有兩件事：

* 協助『應用程式』或『服務』收集 Performance (效能) 相關的 Metrics，傳送給 APM Server。
* 當 『應用程式』或『服務』 發生 Error (錯誤) 時，收集 Error Logs，傳送給 APM Server。

要做到以上的兩件事，代表我們需要程式運作時，收集這兩種的資訊，以下分別針對這兩種情境來說明一般的做法。

### 傳統收集 Performance Metrics 的問題

要分析某段程式運作效能時，最簡單與古老的做法，就是在程式開始時，先記錄當下的時間，在程式結束之後，記錄結束的時間，也因為這種需求太普遍，各種語言當中的 Framework 或 Library 也都有支援這種計算 `time elapsed` 的工具，不過當記錄這些 Performance Metrics 時，常會有以下的問題：

* 『只有有埋 `time elapsed` 的地方，才收集得到數據』，但是要埋的地方可能有非常多，一開始不會都埋好，實務上常常會變成『發生問題時，才改 code 來埋 log、重新出 build、deploy、想辦法讓問題再次發生時，取得 log 』，這樣會變成是背動的狀況，甚至遇到不容易 reproduce (重製) 的例外狀況時，也會很不容易收集到能協助判斷及解決問題的資訊。
* 收集到的資訊不足，在不同的情境下，會需要收集的資訊會不同，例如 Database 存取時的執行速度太慢，這時要能進一步盤查原因，可能會需要執行當下的 SQL statement 並且包含執行時帶入的參數，也可能會需要知道是哪一個請求，才產生出這個 DB 的 query，資訊不足時要花更多的時間才能推測與盤查。

### 傳統收集 Error 的問題

使用例如 `try` `catch` 等 Error handling 的方法，並且在錯誤發生時寫 Logs，而這樣的做法常會遇到：

* Logs 沒有結構化的格式，都只純文字的方式在解讀，有時要找尋相關的 Logs 時，只能用 grap 的方式去篩選，但複雜的條件要處理會很花時間。
* 沒有抓到的錯誤，被丟出時，記錄到的資訊也不足，可能只有 stack trace，但是缺少能更進一步判斷的商業資訊，例如使用者 ID、訂單編號…等。
* 收到的 Error，不容易與前、後發生的處理所產生的 Logs 串連在一起，在分析問題的原因時，不容易掌握整體處理流程發生狀況的全貌。

## APM Agents 的功能介紹

Elastic APM Agent 在協助收集 Performance Metrics 時，提供幾種的方式：

### Framework Integration (框架整合)

也可以稱為 Build-in Instrumentation (內建檢測)，不論是哪種程式語言，在開發的時候常會使用各種 Framework，並且在 Framework 透過已經建立好的一些機制，例如：HTTP 請求的處理、Database 的存取，Logging 的機制、Scheduling (排程) 的功能…等。

APM Agents 在各種語言的支援上，都有盡量整合最熱門的一些 Framework，讓使用者簡單的設定後就能直接使用，自動依照 Framework 的功能，收集相關的資訊，並且以結構化的方式，將收集到的數據的格式定義在 **Elastic Common Schema** 之中。

### Instrumentation (檢測)

在沒有使用支援的 Framework 的時候，APM Agents 也有提供 Instrument (檢測) 資料收集的工具，讓使用者能很簡單的將自己程式中要觀察的某個處理行為，能包裝成為 `Transaction` 或 `Span`，並且透過已經定義好的架構與提供的 Utility，能輕易的收集 Performance 相關的 Metrics 並且加上要額外記錄的資訊，並且由 APM 幫我們將這收集到的事件，與其他前後的事件連結在一起。

### Background Collection (背景收集)

APM Agents 在運作時，會在背景定期的收集系統的 Metrics，能夠配合我們所收集的 `Transaction` 或 `Span` 的資訊，來協助掌握某個要觀察的時間點，系統整體的狀況。

### APM Agents 支援的語言

目前有支援的語言，後端相關的如下：

* Golang
* Java
* .Net
* Node.js
* PHP
* Python
* Ruby

前端相關的有兩個

* iOS
* RUM (Real Time Monitoring) Javascript

## APM Agents 使用方式簡介

以下使用 Golang 為例：

1. 安裝 `Elastic APM` 套件

```
go get -u go.elastic.co/apm
```

1. 使用在 Build-in framework 或 Library，例如 **Gin Web Framework**：

```
import (
	"go.elastic.co/apm/module/apmgin"
)

func main() {
	engine := gin.New()
	engine.Use(apmgin.Middleware(engine))
	...
}
```

1. 透過 Environment Variable 定義相關的設定

* `ELASTIC_APM_SERVER_URL`：APM Server 的位置
* `ELASTIC_APM_SERVICE_NAME`：目前的服務名稱，這個會是之後在 APM UI 中用來識別服務的重要設定。
* `ELASTIC_APM_ENVIRONMENT`：目前的 Environment，這也是在 Kibana APM UI 中篩選的主要功能之一。
* `ELASTIC_APM_GLOBAL_LABELS`：有一些自訂的 Labes 是屬於全域型的，也就是在這個服務裡全都要加上的，可以定在這裡。
* ...其它可參考 [官方文件 - APM Go Agents - Configuration](https://www.elastic.co/guide/en/apm/agent/go/current/configuration.html)。

1. 指定自定義的 Instrument

建立自己定義的 `Transaction`：

```
tx := apm.DefaultTracer.StartTransaction("GET /api/v1", "request")
defer tx.End()
...
tx.Result = "HTTP 2xx"
tx.Context.SetLabel("region", "us-east-1")
```

在 `Transaction` 當中加上 `Span`：

```
span, ctx := apm.StartSpan(ctx, "SELECT FROM foo", "db.mysql.query")
defer span.End()
```

由於每一種語言實作的方式都會有些不同，每一種 Framework 的行為有不同時，支援的方式也會有所差異，以上只是最簡短介紹 APM Agents 的使用方式，讓大家有個感覺，詳細的使用方式，請參考 [官方文件 - APM Agents](https://www.elastic.co/guide/en/apm/agent/index.html) \[1] 每個語言的版本。

## 使用 APM Agents 時要注意的事項

### 對於原本服務的影響？

APM Agents 在運作時，一定會佔用到原本服務需使用的系統資源，這些處理會使用到：

* CPU
* Memory
* 頻寬

另外再從兩個部份來分析：

* Latency (延遲)：APM Agents 收集資訊對於原本服務的 latency 影響程度非常的小，一般只有個位數的微秒 (microseconds)，所以單純是考量 Latency 的部份的話，埋的量不是非常多的話，其實不會有太大的影響。
* Background tasks (背景處理)：APM 在收集到 Instrument 資訊之後，會需要序列化 (serialize) 以及壓縮 (gzipping) 的處理，這部份會佔用到 CPU 的運算，如果服務本身是使用 CPU 為主的服務 (CPU bound)，這部份會影響到原本的性能，但如果不是 CPU bound 的話，影響會較少。

另外 APM Server 如果無法正常接受 APM Agents 所傳送的資料時，APM Agents 最終會選擇放棄傳送，設計上也是以不影響原本服務執行為主。

> 特別注意：先前專案的團隊實際在使用 PHP 版本的 APM Agents 時，在壓測時有明確的因為加上 APM Agents 後，原本服務的 QPS 下降將近一半，猜測和 PHP 的實作版本沒有實作 async call 可能有關，若有使用 PHP 版本的話會需要特別留意。

### Sample Rate (取樣率)

既然使用 APM Agents 收集 Instruments 數據時，一定會佔用到系統的資源，也就是多少都會影響到原本服務的效能，這時候 Sample Rate (取樣率) 就是一個很重要的設定，不過這個值沒有一定的標準，但是在量級非常大的系統之中，1 \~ 3% 的取樣率一般已經足夠有一定的代表性，也就是若是有問題發生時，一般都會能取得到樣本，當然這個設定值還是會依照使用的情境與硬體的規格需進行壓測及調整。

### 減少 APM Agents 處理的資料量

* 如果某些資料沒有必要都要收集，例如 `capture_header` 、 `capture_body` ，這個在 Sample Rate 較高、量級較大的環境之中，應該考慮關掉 `capture_body` ，這個會於效能會有明顯的影響。
* 如果一個 Transaction 中包含了過多個 Span，這也會讓整包處理的量非常的大，例如有寫了個跑非常多次的迴圈，裡面產生非常多的 `span`，這種情況應該設定好 `transaction_max_span` 來避免這種意外發生。
* 另外 APM Agents 會將收集到的 Instruments 資料分批的往 Elasticsearch 傳送，這個每次處理的量也會佔用到系統的效能，如果記憶體佔用太多，應該要嘗試調整傳送的間隔 `api_request_time` 、或是每批的大小 `api_request_size`。

## 參考資料

1. [官方文件 - APM Agents](https://www.elastic.co/guide/en/apm/agent/index.html)


# 透過真實使用者監控 (RUM, Real User Monitoring) 來改善使用者體驗

### 本篇學習重點

* 認識 Elastic APM Agent 當中，針對使用者體驗出發而設計的 RUM Agent。
* 如何使用 APM RUM Agent。
* 透過 Kibana 如何使用 RUM Agent 所收集資料的簡介。

## 什麼是 Elastic APM RUM Agent

### 真實使用者監控 (RUM, Real User Monitoring) 要解決的問題

Elastic 推出 RUM，最重要的一個目的，就是從效能的角度改善**使用者體驗**。

而改善使用者體驗的方式，也就是貼近使用者端，了解使用者行為動作中發生的事，其中包含：

* 使用者打開頁面時，分別在 Frontend、Backend 端各佔多少時間？
* 在前端畫面顯示時，第一個 content 被載入的時間、最大的 content 被載入的時間、跑比較久的 tasks 有哪些，並且花費多少時間。
* 開啟頁面的 OS、瀏覽器版本、地理位置…等資訊，能在進行優化時，當成優先順序或是做法上的參考。
* 當有 Error 發生時，能主動收集更多對於盤查問題發生原因有幫助的內容。

### RUM Agent 收集了哪些東西

RUM Agent 使用瀏覽器依照 W3C 提出規範的一些與 Timing 相關 API，來收集 Web 頁面的效能數據，包含以下四種 Timing APIs：

* [Navigation Timeing API](https://w3c.github.io/navigation-timing/) \[1]
* [Resource Timing API](https://w3c.github.io/resource-timing/) \[2]
* [Paint Timing API](https://w3c.github.io/paint-timing/) \[3]
* [User Timing API](https://w3c.github.io/user-timing/) \[4]

當中收集的資訊如下：

* 頁面載入時的各項 Metrics 數據。(DNS 查詢時間、TCP 連線建立時間、TTFB, Time to first byte...等)
* 載入前端頁面資源 (JS, CSS, images, fonts, etc.) 的時間。
* 對後端發送的 API 請求的 Metrics 數據。
* 在 SPA, Single page application 頁面中的瀏覽行為
* 使用者互動的行為，例如會產生網路存取的點擊行為。
* 以使用者為出發點的載入效能 [User-centric Metrics](https://www.elastic.co/guide/en/apm/agent/rum-js/current/supported-technologies.html#user-centric-metrics) \[5]。(例如：LCP、FID、CLS、Long Tasks、User Timing...等)
* 頁面相關的資訊。(URL、Referr)
* 網路連結相關的資訊。
* JavaScript error。
* 支援 [Distributed tracing](https://www.elastic.co/guide/en/apm/agent/rum-js/current/distributed-tracing-guide.html)。
* 針對收集到的數據，能進行細部檢視的 [Breakdown metrics](https://www.elastic.co/guide/en/apm/agent/rum-js/current/breakdown-metrics-docs.html) 資訊。

> 補充：所謂 LCP、FID、CLS 的定義如下 \[6]：
>
> * LCP (Largest Contentful Paint)：最大內容完成繪製的時間，較佳的體驗會是在 2.5s 內完成。
> * FID (First Input Delay)：首次輸入的延遲，最好在 100m 以內。
> * CLS (Cumulative Layout Shift)：累計版面配置轉移，例如動態注入的內容，或是沒定位好的圖片載入，分數應該在 0.1 以內。
>
> <img src="https://i.imgur.com/wiaxP3e.png" alt="21-web-vitals" data-size="original">

## 使用 APM RUM Agent

### 首先在 APM Server 啟用 RUM

首先要在 APM Server 端啟用 RUM，在 `apm-server.yml` 中，定義相關的設定：

```
apm-server.rum.enabled: true
apm-server.auth.anonymous.rate_limit.event_limit: 300
apm-server.auth.anonymous.rate_limit.ip_limit: 1000
apm-server.auth.anonymous.allow_service: [your_service_name]
apm-server.rum.allow_origins: ['*']
apm-server.rum.allow_headers: ["header1", "header2"]
apm-server.rum.library_pattern: "node_modules|bower_components|~"
apm-server.rum.exclude_from_grouping: "^/webpack"
apm-server.rum.source_mapping.enabled: true
apm-server.rum.source_mapping.cache.expiration: 5m
apm-server.rum.source_mapping.index_pattern: "apm-*-sourcemap*"
```

* `apm-server.rum.enabled`：設定為 `true` 以啟用 RUM。
* `apm-server.auth.anonymous.*`：由於 RUM 是從 Client 端直接存取 APM Server，所以要設定相關的 `anonymouse` 設定。
* 以及其他 `apm-server.rum.*` 相關的設定，細節可參考 [APM Server - Configure RUM](https://www.elastic.co/guide/en/apm/server/7.15/configuration-rum.html#configuration-rum) \[7]。

### 在 Web 專案安裝 APM RUM Agent

#### 使用 React、Vue、Angular

如果是使用 React, Vue, Angular 這些前端的框架進行開發，可以直接使用 APM RUM 已經準備好的套件。

* React

```
npm install @elastic/apm-rum-react --save
```

* Vue

```
npm install --save @elastic/apm-rum-vue
```

* Angular

```
npm install @elastic/apm-rum-angular --save
```

並且參考 [官方文件 - Framework-specific integrations](https://www.elastic.co/guide/en/apm/agent/rum-js/current/framework-integrations.html) 的範例說明。

#### 一般安裝

一般安裝 APM RUM Agent 的方式有兩種：

1. 使用 `script` tag 來宣告以及初始化 ( `<version>` 要改成指定的版本)：

```
<script src="https://<your-cdn-host>.com/path/to/elastic-apm-rum.umd.min-<version>.js" crossorigin></script>
<script>
  elasticApm.init({
    serviceName: '<instrumented-app>',
    serverUrl: '<apm-server-url>',
  })
</script>
```

1. 使用 NPM 安裝套件：

```
npm install @elastic/apm-rum --save
```

並且在應用程式中，將 APM 初始化：

```
import { init as initApm } from '@elastic/apm-rum'

const apm = initApm({

  // Set required service name (allowed characters: a-z, A-Z, 0-9, -, _, and space)
  serviceName: '',

  // Set custom APM Server URL (default: http://localhost:8200)
  serverUrl: 'http://localhost:8200',

  // Set service version (required for sourcemap feature)
  serviceVersion: ''
})
```

另外大部份的環境中，APM Server 不會和 APM Agent 安裝的網站應用程式放在相同的網域的位置 (`origin`)，所以要記得設定 CORS (Cross-Origin Resource Sharing) \[8]，避免瀏覽器因為安全性限制而阻擋 APM Agent 對 APM Server 傳送資訊。

```
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Origin: [request-origin]
```

### 產生及設定 SourceMap

要產生 SourceMap 之前，要先取得或先定義好 Web 專案的版本號 `serviceVersion`，這是為了讓不同版本之間的 SourceMap 能有效的對應到正確版本的 `js` 檔，所以會使用 `serviceVersion` 來當作比對的條件之一。

不同的前端打包或自動化工具會有不同的方式，如果是使用 WebPack 的話，會要加入類似於下方的宣告：

```
const webpack = require('webpack')
const serviceVersion = require("./package.json").version 
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
  entry: 'app.js',
  output: {
    filename: 'app.min.js',
    path: './dist'
  },
  devtool: 'source-map',
  plugins: [
    new webpack.DefinePlugin({'serviceVersion': JSON.stringify(serviceVersion)}),
    new TerserPlugin({
      sourceMap: true
    })
  ]
}
```

再針對產生出來的 SourceMap `app.min.js.map` 檔，透過 APM Server 的 `/assets/v1/sourcemaps` API，上傳到 APM Server 中。

以下是 CURL 的範例：

```
SERVICEVERSION=`node -e "console.log(require('./package.json').version);"` && \ 
curl http://localhost:8200/assets/v1/sourcemaps -X POST \
    -F sourcemap="@./dist/app.min.js.map" \
    -F service_version="$SERVICEVERSION" \
    -F bundle_filepath="http://localhost/app.min.js" \
    -F service_name="myService"
    -H "Authorization: ApiKey <token>"
```

另外也可以考慮將 SourceMap 上傳的動作，當作一個標準的 Deployment 步驟，可以透過 Configuration Management 的工具，例如：Ansible、Pupet、Chef...等，或是在 Node.js 裡在啟動時直接上傳。

## 透過 Kibana 來運用 RUM 收集的資訊

當資料透過 APM Agents 收集進入 Elasticsearch 之後，我們可以直接從 **Kibana** \ **Observability** \ **User Experience** 的功能選單進入專門針對 RUM 建立的 Dashboard。

![21-kibana-user-experience](https://i.imgur.com/6PtK56h.png)

這部份主要是針對 Overview 來檢視，若是要觀看詳細的資訊，其實 RUM Agent 所收集的資料，也會是 APM 當中的其中一個 Services，所以我們到 **Kibana** > **Observability** > **APM** > **Services** 中，可以看到以 RUM 收集到的前端服務，進而可以追縱到特定頁面的載入行為，也能查看所有發生的 Errors，以及對應到 Logs。

![21-Kibana-RUM](https://i.imgur.com/y0MyBfv.gif)

同時使用 Elastic Observibility 最優勢的地方就是整合性，RUM 的資料透過和我們的 Distributed Tracing 整合，我們還能看到過程中後端服務執行的內容及每個 Span 處理，甚至是下了哪些 SQL 指令以及所花費的時間。

![21-Kibana-RUM-distributed-tracing](https://i.imgur.com/sxzxRu4.png)

## 參考資料

1. [W3C Navigation Timeing API](https://w3c.github.io/navigation-timing/)
2. [W3C Resource Timing API](https://w3c.github.io/resource-timing/)
3. [W3C Paint Timing API](https://w3c.github.io/paint-timing/)
4. [W3C User Timing API](https://w3c.github.io/user-timing/)
5. [官方文件 - APM Agents - User-centric Metrics](https://www.elastic.co/guide/en/apm/agent/rum-js/current/supported-technologies.html#user-centric-metrics)
6. [Web Vitals](https://web.dev/vitals/)
7. [官方文件 - APM Server - Configure RUM](https://www.elastic.co/guide/en/apm/server/7.15/configuration-rum.html#configuration-rum)
8. [MDN Configure CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)


# 建立結構化的 Log

許多實務上的痛點，常常是收集一堆的 Logs，卻不容易使用，結構化的 Logs 會是 Logs 治理的重要關鍵之一，這個章節介紹了 Elastic Common Schema 的設計規範及準則，可以當作我們自行管理 Logs 的很好的參考，同時也介紹當我們要將 Logs 結構化時，如何使用 Elasticsearch 內建的 Ingest Pipeline。

* [01 - Elastic Common Schema 結構化 Log 的規範](/tech-sharing/uncle-joe-teach-es-elastc-observability/jian-li-jie-gou-hua-de-log/elastic-common-schema-jie-gou-hua-log-de-gui-fan)
* [02 - Elasticsearch Ingest Pipeline 資料 Index 前的轉換好幫手 - 基本介紹](/tech-sharing/uncle-joe-teach-es-elastc-observability/jian-li-jie-gou-hua-de-log/elasticsearch-ingest-pipeline-zi-liao-index-qian-de-zhuan-huan-hao-bang-shou/ji-ben-jie-shao)
* [03 - Elasticsearch Ingest Pipeline 資料 Index 前的轉換好幫手 - 各種常用的 Processor](/tech-sharing/uncle-joe-teach-es-elastc-observability/jian-li-jie-gou-hua-de-log/elasticsearch-ingest-pipeline-zi-liao-index-qian-de-zhuan-huan-hao-bang-shou/ge-zhong-chang-yong-de-processor)
* [04 - Elasticsearch Ingest Pipeline 資料 Index 前的轉換好幫手 - Enrich 資料與例外處理](/tech-sharing/uncle-joe-teach-es-elastc-observability/jian-li-jie-gou-hua-de-log/elasticsearch-ingest-pipeline-zi-liao-index-qian-de-zhuan-huan-hao-bang-shou/enrich-zi-liao-yu-li-wai-chu-li)


# Elastic Common Schema 結構化 Log 的規範

### 本篇學習重點

* Elastic Common Schema (ECS) 的基本介紹
* Elastic Common Schema 的設計重點

***

Elastic Stack 廣泛的被使用在收集 Logs、Metrics、Traces、Uptime 等資料，其中一個最大的目的，就是為了讓散落在各處的資料，能集中化的收集，可以更輕易的存取這些資料，而當資料收集在一起的時候，混亂的格式，就會是遇到的下一個問題，Elastic Common Schema (ECS) 就是被設計出來解決這件事。

## 什麼是 Elastic Common Schema (ECS)

Elastic Common Schema (ECS) 是一個規範 (Specification)，同時這個規範也是 Open Source 的，是在 Elastic 使用者社群支持之下所開發的，主要定義了存放在 Elasticsearch 之中的 Event (事件) 類型資料常用的欄位，這些欄位的型態、描述、使用與呈現方式，而所謂的 Events 即包含就像是 Logs 或是 Metrics 這樣的資料。

ECS 設計的目的，是為了鼓勵使用 Elasticsearch 存放 Event 類型資料的使用者們，能夠將這些 event 的資料『正規化』，透過正規化之後的資料，在後續的資料分析、資料視覺化呈現、顯示與 event 有關聯的資訊上都能更容易的使用。

ECS 的規範橫跨了以下的範圍：

* **Event Sources (事件來源)：** Event 資料的來源，不論是 Elastic 的產品、第三方的產品或工具、甚至是使用者自己開發的應用程式。
* **Ingestion Architectures (資料注入的架構)：** 資料注入 (Ingestion) 的架構，不論有沒有使用 Beats、Logstash、Elasticseach 的 Ingest Pipeline，都有包含在內。
* **Consumers (使用端)：** 不論是透過 API 查出資料、Kibana Query、Dashboard、應用程式等方式來使用。

## Elastic Common Schema 能做到什麼事

### 讓查詢簡單化

透過資料正規化，讓查詢可以變得簡單，舉例來說，一般大型架構中，可能有各種的服務元件、第三方產品或工具，每個產生的 Logs 的格式都不同，同樣是針對 `IP` 的地址，欄位都不一樣，在沒有正規化之前，為了要從 `src`、`client_ip`、`apache.access.remote_ip`、`context.user.ip` 等各種服務所定義的 `IP` 欄位查詢是否有存在 `10.42.42.42` 這個地址，KQL (Kibana Query Language) 的查詢會長成這樣：

```
src:10.42.42.42 OR client_ip:10.42.42.42 OR apache.access.remote_ip:10.42.42.42 OR
context.user.ip:10.42.42.42 OR src_ip:10.42.42.42
```

但透過正規化之後，會將這些欄位全部存放至 `source.ip` 的欄位，查詢就變成：

```
source.ip:10.42.42.42
```

簡單，能加快查詢的速度，也能減少犯錯的機會。

### 統一的視覺化呈現

透過資料正規化，在製作圖表，讓資料以視覺化方式來呈現時，也會變得更簡單，而且在分析上也有更好的能力，例如透過同一個 IP 的位置，在同一個欄位之中，輕易的就能將各種來源 (如：Web Server、IDS/IPS 裝置、防火牆) 所收集到的資料，透過圖表呈現出時間歷程中的變化，又或是能將資料要進行深入的分析時的資料呈現方式，像是樞紐分析。

### 原始資料的轉換

在 Data Ingestion Pipeline 的過程之中，能將原始的資料透過 ECS 的定義，轉換成正規化之後的結果，同時 Elastic Stack 中已經在 Beats、Logstash 等 Data Ingest 的工具，實作了許多第三方產品及工具的整合模組，能將這些 Logs 的格式轉換到 ECS 之中。

例如 Apache Logs 原始的內容如下：

```
10.42.42.42 - - [15/Jul/2020:20:48:32 +0000] "GET /content HTTP/1.1" 200 2571 "-"
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/83.0.4103.106 Safari/537.36"
```

首先會將這些原始的內容轉換到 ECS 定義的欄位之中：

| Field Name                 | Value                                                                                                                                                                                                           |
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| @timestamp                 | `2020-07-15T20:48:32.000Z`                                                                                                                                                                                      |
| event.original             | 10.42.42.42 - - \[15/Jul/2020:20:48:32 +0000] "GET /content HTTP/1.1" 200 2571 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10\_15\_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 |
| http.request.method        | GET                                                                                                                                                                                                             |
| http.response.body.bytes   | 2571                                                                                                                                                                                                            |
| http.response.status\_code | 200                                                                                                                                                                                                             |
| http.version               | 1.1                                                                                                                                                                                                             |
| message                    | GET /content HTTP/1.1" 200 2571 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10\_15\_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36                                                |
| source.address             | 10.42.42.42                                                                                                                                                                                                     |
| source.ip                  | 10.42.42.42                                                                                                                                                                                                     |
| url.original               | `/content`                                                                                                                                                                                                      |
| user\_agent.original       | `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36`                                                                                     |

除此之外，ECS 還有定義其他的欄位，也是在轉換過程中會增加的資訊：

* `ecs.version`：ECS 的版本
* `event.dataset`、`event.module`：記錄這個 event 是從哪裡來的，是透過哪個模組所處理的。
* `event.kind`、`event.cateogry`、`event.type`、`event.outcome`：包含 ECS Categorization Fields (分類欄位)，透過定義好的類別，描述這個 event 是什麼樣的 event。

## Elastic Common Schema 的設計重點

ECS 定義了一些準則與最佳實踐的規範，接下來我們將介紹這些規範。

### ECS Guideline (凖則)

#### ECS 欄位類型

ECS 的欄位定義成以下兩種：

* **Core Fields (核心欄位)：** 指適用於大部份的主要使用情境的欄位，這些欄位也就會在資料分析時、或是製作圖表時，廣泛的被使用在橫跨各種情境的設計之中，這部份的欄位會是比較固定的，不太會隨時間變化。
* **Extended Fileds (延伸欄位)：** 只要不存在 Core Fields 的欄位，就是 Extended Fields，這些欄位只有在特定的使用情境中才會使用，並且隨著時間也較容易發生變化。

#### ECS 一般準則

* 每個文件必須包含 `@timestamp` 欄位。
* 需依照 Elasticsearch 的資料型態來定義每個 ECS 的欄位。
* 必須記錄所使用的 ECS 版本號在 `ecs.version` 的欄位之中。
* 盡可能的將資料對應到 ECS 已定義的欄位之中。

#### 欄位名稱的準則

* 欄位名必須是**小寫**。
* 字與字之間使用**底線**來連接。
* 除了底線之外，**不能使用其他的特殊字元**。
* 英文的文法請使用**現在式**，除非欄位是用來記錄歷史的資料。
* 正確的使用文法的**單、複數**來反映欄位的內容。
* 除了最基礎的欄位之外，所有的欄位應該都要**加上前輟 (Prefix)**，例如：`host` 相關的欄位，都要加上 `host.` 的前綴，當成是 `host` 類的欄位集 (field sets)，進行分類管理。
* **巢狀的資料結構 (Nested JSON objects) 也要使用欄位集 (field sets) 進行分類**，並使用 `.` 而不是使用 `_` 來描述。
* **盡量具體的管理及分類欄位**，如果能具體的安排在某個欄位集 (field sets) 之中，就不要讓他是在一般化的欄位裡，例如 `host.name` 就不要去一般化變成 `name`，這樣太一般化會導致使用時不知如何解讀，甚至會發生許多衝突。
* **避免命名時單字重覆**，例如 `host.host_ip` 應該取名 `host.ip`，不過也有例外，如果 `hostname` 就是一個一般認知的名字，`host.hostname` 就應該保留使用 `hostname`，不要刻意改掉。
* **盡可能的避免縮寫**，為了讓解讀時，清楚的知道欄位的用途，不過也可以有例外，如果縮寫已經非常普遍時，例如 `ip`、`geo`、`os` 等。

### ECS Convension (公約)

#### 整數的數值型態

除非有特別的備註，否則所有的整數的數值型態，應該定義成 `long`。

#### IDs 或是某些編碼 (codes) 應該使用 keywords，而不是數值型態

**IDs** 指的是識別字串，例如 `User Id`、`Product Id`，而 **Codes** 指的是編碼，例如 `Error Code`，這些都應該使用 `keyword` 的資料型態。

不過有一些特別的 **Codes** 只要是大家都共識為數字的，就應該使用數字類型，例如 `HTTP Status Code`，這個就應該使用數字。

#### 字串的預設型態

Elasticsearch 預設的 Dynamic Mapping 會將文字類型的欄位，指定成 `text` 的資料型態，並且包含 `keyword` 的子欄位。

這部份 ECS 和 Elasticsearch 相反，預設的文字類型欄位，會指定成 `keyword` 的資料型態，而另外定義子欄位 `text`。

原因是 ECS 處理的資料大部份都是 Logs 與 Metrics，在這樣的應用情境中，大部份的文字欄位都會較適用 `keyword` 的方式來處理，才能支援較快速的完整比對、Aggregation、Sorting、prefix search…等。

不過也有例外，就是 Logs 當中一般會存放大量文字，要用來做全文檢索的 `message` 與 `error.message` 欄位，這兩個欄位預設就是指定 `text` 並且**不會**另外宣告 `keyword` 的子欄位。

### 客製化欄位

在實務的使用上，我們往往會因為實際的需求與情境，會要在結構化的 Logs 之中定義自己的欄位，由於 ECS 還持續在發展中，以下會有一些自訂欄位的使用建議，減少與未來 ECS 新版本發生衝突的機會。

#### 使用 `labels` 欄位

一些簡單的 `keyword` 類型的資料，可以直接定義在 `labels` 欄位之中，例如：

```
{ "labels": { "foo_id": "beef42", "env": "production" },
  "message": "...",
  "event": { ... }
}
```

`labels` 裡的定義，就是完全依照使用者自己來管理。

#### 了解 ECS 的命名方式

ECS 在命名時，會使盡量使用概念的名字，而不是工具的名字或是專案的名字，一般在資料的整理時，就會先使用通用的方式來歸類，剩下的才會用特定方式來描述，例如 HAProxy 的 log，屬於 HTTP 相關的資訊，就會先定義在 `http` 與 `url` 的 field sets 之中，而剩下的才會放在 `haproxy` 裡：

```
{ "http": { "request": { "method": "get", ... },
            "response": { "status_code": 200, ... } },
  "url": { "original": "/favicon.ico", ... },
  "haproxy": { "frontend_name": "myfrontend", "backend_name": "mybackend_prod",
               "backend_queue": 0, ... }
}
```

#### 使用大寫

如果真的要避免與未來的 ECS 版本發生衝突，有一個做法，雖然醜醜的，但是可以考慮，就是打破 ECS 的命名規則，使用大寫開頭的方式來命名欄位：

```
{ "http": { "request": { "method": "get", ... } },
  "url": { "original": "/favicon.ico", ... },
  "Proxy": { "FrontendName": "myfrontend", "BackendName": "mybackend_prod" },
  "event": { "module": "haproxy" }
}
```

在上述官方文件提出的例子，`Proxy` 就是一個自訂的欄位，並且為了避免未來與 `proxy` 這樣的 ECS 欄位發生衝突，所以使用大寫開頭。

> 備註：我自己是覺得這部份有點醜，如果真的要使用，以上述的子來說，會命名成 `Proxy`，一定是因為想要在自己的領域中定義一個通用的欄位，而剛好這個欄位還沒有被定義在 ECS 之中，如果真的是夠通用，可以接受未來 ECS 推出時再進行轉換，可以這樣考慮，否則是自己領域當中的應用的欄位定義時，最好還是能定義更明確的名字，避免發生衝突。

## 如何使用 ECS

基本上若是使用我們這系列文章前面介紹到的 Heartbeat、Metricbeat、Filebeat 當中的各種 Modules，所收集到的資料就已經是依照 ECS 的格式存入 Elasticsearch 當中，若是有需要增加客製的欄位用，可以使用先前介紹過 Beats 裡的 `properties` 來進行設定，以下會先針對 ECS 產生的欄位進行簡介，若是要想查看 Beats 實際產生哪些 ECS 的欄位，可以直接使用 `_search` 或是 Kibana Discover 的功能來查看。

### ECS 的欄位定義

ECS 欄位的參考，可以參考 [官方文件 - ECS Field Reference](https://www.elastic.co/guide/en/ecs/current/ecs-field-reference.html) 裡的定義，包含非常多已經收錄的各種欄位集 (field sets)，這部份在這邊就不細部說明。

若是想要一覽 ECS 所有欄位的總表的話，可以查看 [ECS GitHub - fields.csv](https://github.com/elastic/ecs/blob/1.12/generated/csv/fields.csv) \[2]。

### ECS Categorization 欄位

ECS Categorization (分類) 欄位，目的是透過一組事先定義的值，用來描述這個欄位是什麼樣的欄位，其中包含了：

* `event.kind`：用來描述這個 event 包含什麼樣的資訊，可使用的值有 `alert`、`enrichement`、`event`、`metric`、`state`、`pipeline_error`、`signal`。
* `event.category`：定義了 ECS 中的主要分類，可使用的值包含 `authentication`、`configuration`、`database`、`driver`、`file`、`host`、`iam`、`intrusion_detection`、`malware`、`network`、`package`、`process`、`registry`、`session`、`threat`、`web`。
* `event.type`：這裡面定義的，是基於 `cateogry` 底下的子分類，可使用的值包含 `access`、`admin`、`allowed`、`change`、`connection`、`creation`、`deletion`、`denied`、`end`、`error`、`group`、`indicator`、`info`、`installation`、`protocol`、`start`、`user`。
* `event.outcome`：這是定義 event 代表的狀態，可使用的值包含 `failure`、`succes`、`unknown`。

透過這些事先定義好的分類，可以協助我們將 events 有效的正規劃，每個欄位可設定的值的說明，可以參考 [官方文件 - ECS Categorization Field](https://www.elastic.co/guide/en/ecs/current/ecs-category-field-values-reference.html) 的細節說明。

***

這篇文章所介紹的 Elastic Common Schema，除了讓我們了解 ECS 的能力以及裡面所包含的定義，更重要的一個參考價值，就是 Elastic 發展出這份 Common Schema 的設計重點，相信在許多領域之中，也應該會有類似定義領域內通用 Schema 的需求，這裡所介紹的做法就非常值得參考。

## 參考資料

1. [官方文件 - Elastic Common Schema](https://www.elastic.co/guide/en/ecs/current/ecs-reference.html)
2. [ECS GitHub - fields.csv](https://github.com/elastic/ecs/blob/1.12/generated/csv/fields.csv)


# Elasticsearch Ingest Pipeline 資料 Index 前的轉換好幫手


# 基本介紹

### 本篇學習重點

* Elasticserach 內建的 Ingest Pipeline 基本介紹
* 如何使用 Ingest Pipeline
* 使用 Ingest Pipeline 時的注意事項

## Ingest Pipeline 的功用

Ingest Pipeline (擷取管道) 是一個內建在 Elasticsearch 中，文件在進入 Index 前的資料轉換 (Transformation) 的工具，主要的任務就是針對透過 Indexing RESTful API 傳入的文件，在真正進入 Elasticsearch Indexing 處理之前，先進行前處理，這個前處理可以像是以下幾種例子：

* 將原始的資料豐富化 (enrich)，透過查找 Elasticsearch 裡存放在別的 Index 裡的相關資料，加入到文件之中，來豐富原有的文件。
* 將日期格式正確的從文件中的某個欄位擷取出來，讓 Elasticsearch 的 `@timestamp` 有正確的時間。
* 將 IP 的欄位，透過反查 GeoIP 的資料庫，加入 GeoLocation 的資訊在文件中，以利於之後能使用地圖檢視資料。
* 將文字內容，依照特定的格式，拆解成結構化的 JSON 欄位與值。
* 將 URL 拆解成包含 `path`、`scheme`、`port`、`domain`、`query`…各個欄位。
* 將 URL 中 Query String 裡的 Key / Value，轉成結構化的 JSON 欄位與值。
* 當某個條件成立時，填加某個固定值。

## Ingest Pipeline 的運作及使用方式

![24-ingest-pipeline-flow](https://i.imgur.com/e5qNlJE.png)

上圖是 Ingest Pipeline 運作時的主要流程，在這過程中，我們分別解釋每個階段的運作及操作方式：

* **Incoming Documents (傳入的文件)：** 在使用 Indexing API、或是 `_bulk` API 將文件傳入至 Elasticsearch 準備進行 Indexing 時，可以指定要使用哪一組預先定義好的 Ingest Pipeline。
* **Ingest Pipeline (擷取管道)：** Elasticsearch 在到 Indexing 的請求時，如果有指定 Ingest Pipeline，Coordinator (協調者) Node 會把這個請求，交給 `ingest` node，透過定義好的 Pipeline 設定，經由當中指定的各種 Processor (處理器) 一步一步的將資料進行處理。
* **Target Index (目標索引)：** Ingest Pipeline 在處理完之後，會將最終的文件，透過 Coordinator 傳送到 Primary Shard 所在的 Node，進行 Indexing 後續的處理。

接下來將會依照實際準備時的步驟，進行說明。

### 定義 Ingest Pipeline

#### 使用 Ingest APIs

在文件進入 Elasticsearch 之前，我們必須先準備好 Ingest Pipeline 的定義，這邊主要是透過 `_ingest` 的 API 進行設定：

```
PUT /_ingest/pipeline/<pipeline>
```

* `<pipeline>`：是自己取的 pipeline 名稱

提供一個實際的範例如下：

```
PUT /_ingest/pipeline/my-pipeline-id
{
  "version": 1,
  "description" : "My optional pipeline description",
  "processors" : [
    {
      "set" : {
        "description" : "My optional processor description",
        "field": "my-keyword-field",
        "value": "foo"
      }
    }
  ],
  "_meta": {
    "reason": "set my-keyword-field to foo",
    "serialization": {
      "class": "MyPipeline",
      "id": 10
    }
  }
}
```

* Pipeline 的名字是 `my-pipeline-id`。
* `version` 是提供使用者自己記錄及參考，與 ingest pipeline 本身的運作功能無關，是選用的欄位。
* `processors` 裡面指定 1 至多個 processors，這部份會是資料 Transform (轉換) 的主要處理，每個 processors 會依照先後順序來執行，一個 processor 做完後會將輸出交給下一個 processor 進行處理，也就是這個功能取名 pipeline 的原因，Elasticsearch 中內建 30 多個 processors，詳細可參考 [官方文件 - Ingest Processor Reference](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/processors.html)。
* `_meta` 是提供給使用者自己存放自己想要額外加入的資訊所使用的，如果 pipeline 是由程式或其他機制在管理時，可以額外記錄一些參考的資訊。

除了建立 pipeline 的這個 API，Ingest API 總共有提供：

* Create or update pipeline
* Get pipeline
* Delete pipeline

可以參考 [官方文件 - Ingest APIs](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/ingest-apis.html) 的使用說明。

#### 使用 Kibana Ingest Node Pipeline

另外 Kibana 也有提供 UI 的設定畫面，可以在 **Kibana** > **Stake Management** > **Ingest** > **Ingest Node Pipelines** 建立或管理 Ingest Pipeline：

![24-kibana-ingest-pipeline](https://i.imgur.com/94I7jOc.png)

在建立 Create Pipeline 時，就能夠使透過 **Add a processor** 進入以下的畫面選擇要使用的 processor 並進行相關的設定。

![24-kibana-ingest-pipeline-create](https://i.imgur.com/d0GeoFq.png)

### 上線前使用 Simulate 模擬一下

當我們依照需求定義好 Ingest Pipeline 之後，我們可以透過 `_simulate` API，並提供測試的文件，確認一下 Ingest Pipeline 的執行結果。

`_simulate` API 有提供兩種模式，第一種是針對還沒有建立 Pipeline 時，一併透過 API 模擬指定的 Pipeline 定義 + 測試文件，另一種是已經建立好 Pipelien 的定義，提供測試文件來試用。

#### Simulate 已建立好的 Pipeline

我們針對 `my-pipeline-id` 這個 pipeline 來測試，並提供兩份文件：

```
POST /_ingest/pipeline/my-pipeline-id/_simulate
{
  "docs": [
    {
      "_index": "index",
      "_id": "id",
      "_source": {
        "foo": "bar"
      }
    },
    {
      "_index": "index",
      "_id": "id",
      "_source": {
        "foo": "rab"
      }
    }
  ]
}
```

可以得到回傳結果：

```
{
   "docs": [
      {
         "doc": {
            "_id": "id",
            "_index": "index",
            "_type": "_doc",
            "_source": {
               "field2": "_value",
               "foo": "bar"
            },
            "_ingest": {
               "timestamp": "2017-05-04T22:30:03.187Z"
            }
         }
      },
      {
         "doc": {
            "_id": "id",
            "_index": "index",
            "_type": "_doc",
            "_source": {
               "field2": "_value",
               "foo": "rab"
            },
            "_ingest": {
               "timestamp": "2017-05-04T22:30:03.188Z"
            }
         }
      }
   ]
}
```

#### Simulate 指定的 Pipeline 規則 + 測試的文件

我們不用指定 Pipeline 名稱，直接使用 `_ingest/pipeline/_simulate` 的 API，並且在 Request Body 中帶入 `pipeline` 的定義：

```
POST /_ingest/pipeline/_simulate
{
  "pipeline" :
  {
    "description": "_description",
    "processors": [
      {
        "set" : {
          "field" : "field2",
          "value" : "_value"
        }
      }
    ]
  },
  "docs": [
    {
      "_index": "index",
      "_id": "id",
      "_source": {
        "foo": "bar"
      }
    },
    {
      "_index": "index",
      "_id": "id",
      "_source": {
        "foo": "rab"
      }
    }
  ]
}
```

以下是 Simulate 的回傳結果：

```
{
   "docs": [
      {
         "doc": {
            "_id": "id",
            "_index": "index",
            "_type": "_doc",
            "_source": {
               "field2": "_value",
               "foo": "bar"
            },
            "_ingest": {
               "timestamp": "2017-05-04T22:30:03.187Z"
            }
         }
      },
      {
         "doc": {
            "_id": "id",
            "_index": "index",
            "_type": "_doc",
            "_source": {
               "field2": "_value",
               "foo": "rab"
            },
            "_ingest": {
               "timestamp": "2017-05-04T22:30:03.188Z"
            }
         }
      }
   ]
}
```

#### 使用 Kibana 的 Test Pipeline

在 Kibana 之中，也有對應的 Test Pipeline 功能，可以直接填入測試的文件，來檢驗 Pipeline 的運作結果。

![24-kibana-ingest-pipeline-test](https://i.imgur.com/z6u6U8H.png)

### Indexing 資料時，使用 Ingest Pipeline 的方法

當上述的方式建立好 Ingest Pipeline 之後，我們將 Document Indexing 進入 Elasticsearch 時，就可以指定要使用 Ingest Pipeline，以下是常用的幾種方式：

#### Index API

使用 Index API 時，指定 `pipeline` 的名字

```
POST my-data-stream/_doc?pipeline=my-pipeline
{
  "@timestamp": "2099-03-07T11:04:05.000Z",
  "my-keyword-field": "foo"
}
```

#### Bulk API

使用 Bulk API 時，同樣也可以指定 `pipeline` 的名字

```
PUT my-data-stream/_bulk?pipeline=my-pipeline
{ "create":{ } }
{ "@timestamp": "2099-03-07T11:04:06.000Z", "my-keyword-field": "foo" }
{ "create":{ } }
{ "@timestamp": "2099-03-07T11:04:07.000Z", "my-keyword-field": "bar" }
```

#### Update By Query

使用 Update by Query 時，也可以指定 `pipeline` 的名字

```
POST my-data-stream/_update_by_query?pipeline=my-pipeline
```

#### Reindex

Reindex 時也可以指定 `pipeline` 的名字

```
POST _reindex
{
  "source": {
    "index": "my-data-stream"
  },
  "dest": {
    "index": "my-new-data-stream",
    "op_type": "create",
    "pipeline": "my-pipeline"
  }
}
```

#### Index Setting 或透過 Index Tempate

在 Index Setting 中，可以透過以下的設定，來決定資料寫入這個 Index 時，要透過 ingest pipeline 來處理：

* `index.default_pipeline`：如果 request 沒有帶入指定的 `pipeline`，就會依照這個設定來執行。
* `index.final_pipeline`：這是不論有沒有其他經由 request 帶入的 `pipeline` 或是 `default_pipeline` 的設定，在最後都一定會執行的的 pipeline 設定。(也就是如果有其他指定的 pipeline，這兩種 pipeline 都會被執行)

因此也可以透過 Index Template 指定 Index setting 中的這兩個設定值。

#### 其他 Elastic Stack

其他 Beats、Logstash，也都會有對應的設定，能夠在資料透過 Index API 或是 Bulk API 寫入時，指定要使用的 Ingest Pipeline，詳細請參考這些產品的官方文件說明。

## 使用 Ingest Pipeline 的注意事項

* Elasticsearch Cluster 中，至少要有一個有啟動 `ingest` 角色的 Node，如果 Ingest 的工作量很繁重的話，建議安排專門處理 Ingest 的 Node 來進行 Ingest 任務的處理，又或是使用 Logstash 等其他 ETL 工具，避免佔用資源而影響 Elasticsearch 其他功能的運作。
* 如果有啟用 Security 的功能，會需要擁有 `manage_pipeline` 的權限，如果要從 Kibana 的 Ingest Node Pipeline 畫面來操作 Ingest Pipeline 的功能的話，另外還會需要 `cluster:monitor/nodes/info` 的權限。
* 在使用 Pipeline 時，版本管理也會是很重要的一件事，為了能更有效率的避免 Pipeline 的定義是舊版，而造成資料 Indexing 時是以非預期的方式處理，善用 `version` 的版本號碼，並且將 Pipeline 的定義進行版控管理，在佈署或除錯時，也能多透過確認 Pipeline 的 `version` 來確保版本的正確性。

***

以上介紹了 Elasticsearch Ingest Pipeline 的基本說明，接下來會以實際的例子進行介紹，說明 Ingest Pipeline 如何協助我們將 Log 結構化。

## 參考資料

1. [官方文件 - Ingest Pipelines](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/ingest.html)
2. [官方文件 - Ingest Processor Reference](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/processors.html)
3. [官方文件 - Ingest APIs](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/ingest-apis.html)


# 各種常用的 Processor

### 本篇學習重點

* Ingest Pipeline 中常用的 Processor 及使用的方式

## Ingest Pipeline 常用的 Processor

這邊會先介紹 Ingest Pipeline 當中，常用到的幾個 Processor，並且說明他們的能力與效果。

### Grok

Grok 怎麼使用我就不多介紹了，如果不知道 Grok 要怎麼使用的，可以上網查詢，有非常多的資料。

Grok 在針對『非結構化的文字資料，整理成結構化的資料』會是非常常用的一個重要工具，這邊要特別介紹的是， Elasticsearch 在 Grok 的支援上，有許多內建好的 Patterns 可以直接拿來使用，至於有支援哪些，請直接從 [GitHub - Elasticsearch Grok Patterns](https://github.com/elastic/elasticsearch/tree/7.15/libs/grok/src/main/resources/patterns) 查看，特別是裡面的 `grok-patterns` 檔案，記錄了一些通用型的 patterns。

至於 Grok processor 的使用方式如下：

```
POST _ingest/pipeline/_simulate
{
  "pipeline": {
  "description" : "parse multiple patterns",
  "processors": [
    {
      "grok": {
        "field": "message",
        "patterns": ["%{FAVORITE_DOG:pet}", "%{FAVORITE_CAT:pet}"],
        "pattern_definitions" : {
          "FAVORITE_DOG" : "beagle",
          "FAVORITE_CAT" : "burmese"
        },
        "trace_match": true
      }
    }
  ]
},
"docs":[
  {
    "_source": {
      "message": "I love burmese cats!"
    }
  }
  ]
}
```

* `field`：從哪個欄位中讀取資料。
* `patterns`：Grok patterns 的描述字串，可以使用已經定義好的 patterns，也可以自己定義。(上例是將比對到的結果，存放到 `pet` 欄位中)
* `pattern_definitions`：可以自行指定相同字串比對的規則，值的宣告中，也可以使用 `|` 來定義多個值。
* `trace_match`：是否要回傳 `_grok_match_index` 這個 grok 執行結果的資訊。

回傳結果為：

```
{
  "docs": [
    {
      "doc": {
        "_type": "_doc",
        "_index": "_index",
        "_id": "_id",
        "_source": {
          "message": "I love burmese cats!",
          "pet": "burmese"
        },
        "_ingest": {
          "_grok_match_index": "1",
          "timestamp": "2016-11-08T19:43:03.850+0000"
        }
      }
    }
  ]
```

> 注意：為了避免某些 Grok 的處理花太久的時間、佔用太多系統資源，Elasticsearch 當中有定義 `ingest.grok.watchdog.interval` 與 `ingest.grok.watchdog.max_execution_time` (預設值都是 1 秒)，來檢查及限制 Grok 任務的處理。

### Dissect

這個是與 Grok processor 類似的功能，但功能較單純一些，可以說在 Elasticsearch Ingest Pipeline 當中，更值得被優先選擇用來處理『非結構化的文字資料，整理成結構化的資料』的 processor，因為處理的方式較單純，不支援正規表示式 (Regular Expression)，也因此執行速度在不少情境下比 Grok 快非常多。

> 注意：效能考量，基本上可以當作如果能使用 Dissect 做到的，就不要用 Grok。

Dissect processor 的使用方式

```
{
  "dissect": {
    "field": "message",
    "pattern" : "%{clientip} %{ident} %{auth} [%{@timestamp}] \"%{verb} %{request} HTTP/%{httpversion}\" %{status} %{size}"
   }
}
```

針對以下的文件內容

```
{
  "message": "1.2.3.4 - - [30/Apr/1998:22:00:52 +0000] \"GET /english/venues/cities/images/montpellier/18.gif HTTP/1.0\" 200 3171"
}
```

可以拆解並得到以下結構化的結果

```
"doc": {
  "_index": "_index",
  "_type": "_type",
  "_id": "_id",
  "_source": {
    "request": "/english/venues/cities/images/montpellier/18.gif",
    "auth": "-",
    "ident": "-",
    "verb": "GET",
    "@timestamp": "30/Apr/1998:22:00:52 +0000",
    "size": "3171",
    "clientip": "1.2.3.4",
    "httpversion": "1.0",
    "status": "200"
  }
}
```

使用 Dissert 時，主要是使用 `%{keyname}` 並在比對到值的時候，將抓到的值存放到對應 `keyname` 的欄位中，

另外可以配合使用以下這些主要的修飾符 (Modifier)：

| 修飾符           | 使用方式                          | 描述                                                                             | 文件                                                                                                                                 |
| ------------- | ----------------------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| `->`          | `%{keyname1->}`               | 右方的字元不論重覆多少次，都忽略它，常用在右方有很多空格時，要一口氣忽略，就可以使用。                                    | [link](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/dissect-processor.html#dissect-modifier-skip-right-padding)    |
| `+`           | `%{+keyname} %{+keyname}`     | 將多個比對到的欄位結果，合併在一起，可以透過 `append_separator` 指定這些值合併時的分隔符號，預設是空格。                 | [link](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/dissect-processor.html#dissect-modifier-append-key)            |
| `+` with `/n` | `%{+keyname/2} %{+keyname/1}` | 和 `+` 一樣是合併多個結果，但多透過 `/n` 來指定這些值合併時的順序，`n` 從 1 開始。                             | [link](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/dissect-processor.html#dissect-modifier-append-key-with-order) |
| `?`           | `%{?ignoreme}`                | 忽略比對到的這個結果，其實效果和 `%{}` 是一樣的，不過指定名字會更利於 Dissert pattern 的閱讀。                    | [link](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/dissect-processor.html#dissect-modifier-named-skip-key)        |
| `*` and `&`   | `%{*r1} %{&r1}`               | 使用 `*` 將從比對到的結果當成欄位的名稱，並且欄位的值是 `&` 比對到的結果，例子中的 `r1` 代表的只是相同名字的 `*` 與 `&` 是同一組。 | [link](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/dissect-processor.html#dissect-modifier-reference-keys)        |

### Date

由於 Elastic Common Schema 的規範之中，每個 event 都會需要有 `@timestamp` 的欄位，並且實務中要將日期的資料使用日期的格式儲存在 Elasticsearch 中也是很常見的需求，因此要先介紹 Date processor。

```
{
  "description" : "...",
  "processors" : [
    {
      "date" : {
        "field" : "initial_date",
        "target_field" : "timestamp",
        "formats" : ["ISO8601"],
        "timezone" : "{{{my_timezone}}}",
        "locale" : "{{{my_locale}}}"
      }
    }
  ]
}
```

Date Processor 擁有幾個重要的設定：

* `field`：從哪個欄位中讀取資料。
* `format`：依照哪一種日期格式來解析資料，支援常見在 JSON 中使用的日期格式 `ISO8601` 或是 timestamp 的數字表示方式 `UNIX`、`UNIX_MS` 或是使用 Java 的 date format 定義時間格式。
* `timezone`：支援指定時區。
* `locale`：支援日期格式中與地區語言相關的表示法，例如日星期、月份有些格式會用英文來表示。

### Convert

如果要將某個欄位的型態，轉換成另種型態，就可以使用 Convert processor。

```
PUT _ingest/pipeline/my-pipeline-id
{
  "description": "converts the content of the id field to an integer",
  "processors" : [
    {
      "convert" : {
        "field" : "id",
        "type": "integer"
      }
    }
  ]
}
```

* `field`：從哪個欄位中讀取資料。
* `type`：轉換成哪個指定的型態。

上述的例子就是將原本可能是字串型態的欄位，指定轉換成為 `integer` 的型態。

### Fingerprint

有時我們放入 Elasticsearch 的文件沒有能當作識別的唯一主鍵 (Primary Key)，但是有多個欄位合併在一起就能代表是唯一的複合鍵 (Composite Key)，當我們想避免資料重覆被 indexing 進入 Elasticsearch 時，又或是進入 Elasticsearch 之後，想要更有效的找出這些重覆的資料時，就是在事前加工，將這些欄位合併在一起產生出一個唯一的 Fingerprint (指紋)，就能當成識別使用。

Finterprint processor 的使用方式：

```
POST _ingest/pipeline/_simulate
{
  "pipeline": {
    "processors": [
      {
        "fingerprint": {
          "fields": ["user"],
          "target_field": "unique_key",
          "method": "SHA-1"
        }
      }
    ]
  },
  "docs": [
    {
      "_source": {
        "user": {
          "last_name": "Smith",
          "first_name": "John",
          "date_of_birth": "1980-01-15",
          "is_active": true
        }
      }
    }
  ]
}
```

* `fields`：主要在這裡提供多個值，甚至如上方的例子能直接給予一個物件。
* `target_field`：產生的 Fingerprint 要存在哪個欄位中，預設是 `fingerprint` 的欄位。
* `method`：使用哪種 Hash Function，有支援 `MD5`、`SHA-1`、`SHA-256`、`SHA-512`、`MurmurHash3`。
* `salt`：有需要的話甚至可以另外指定 `salt` 的值加在 Hash Function 的運算之中。

### GeoIP

當我們有 IP 的資料，想要轉換成地理位置的資訊，在查詢時甚至能使用地圖的方式來呈現時，我們就可以使用 GeoIP processor：

```
{
  "description" : "Add geoip info",
  "processors" : [
    {
      "geoip" : {
        "field" : "ip"
      }
    }
  ]
}
```

當我們指定某個 IP 值，要透過 `geoip` processor 進行處理：

```
PUT my-index-00001/_doc/my_id?pipeline=geoip
{
  "ip": "8.8.8.8"
}
```

產生出來的結果就會是

```
{
  "found": true,
  "_index": "my-index-00001",
  "_type": "_doc",
  "_id": "my_id",
  "_version": 1,
  "_seq_no": 55,
  "_primary_term": 1,
  "_source": {
    "ip": "8.8.8.8",
    "geoip": {
      "continent_name": "North America",
      "country_name": "United States",
      "country_iso_code": "US",
      "location": { "lat": 37.751, "lon": -97.822 }
    }
  }
}
```

GeoIP processor 預設是使用 MaxMind 這間公司所提供免費版的資料庫，如果有要使用其他特定的資料庫來源，可以透過 `database_file` 來指定。

另外針對產生出來的欄位，可以透過 `target_field` 來指定名稱，甚至透過 `properties` 來決定產生出來的物件裡面要包含哪些 Geo 屬性。

### KV

KV 是指 Key/Value，能將在字串中同樣規則的 Key/Value 值給解析出來。

例如最常使用在 URL 的 Query String 當中，如果我們想把 Query String 裡的值都結構化，可以使用：

```
{
  "kv": {
    "field": "query_string",
    "field_split": "&",
    "value_split": "="
  }
}
```

就能將 URL 的格式

```
{
  "query_string": "q=elasticsearch&locale=en"
}
```

轉變成

```
"doc": {
  "_index": "_index",
  "_type": "_type",
  "_id": "_id",
  "_source": {
    "query_string": "q=elasticsearch&locale=en",
    "q": "elasticsearch",
    "locale": "en"
  }
}
```

也能透過 `target_field` 將所以拆解出來的結果收集在某一個欄位之中。

### Pipeline

如果我們定義了許多的 Pipeline，但希望能『模組化』的方式，將某些宣告重覆使用，就可以透過這個 Pipeline Processor 的方式，來組合其他定義好的 Processor。

例如我們先定義一個 `pipelineA`

```
PUT _ingest/pipeline/pipelineA
{
  "description" : "inner pipeline",
  "processors" : [
    {
      "set" : {
        "field": "inner_pipeline_set",
        "value": "inner"
      }
    }
  ]
}
```

在另外的 `pipelineB` 有其他的處理要進行，但又希望使用到 `pipelineA` 所定義的部份，就可以像以下的方試來宣告：

```
PUT _ingest/pipeline/pipelineB
{
  "description" : "outer pipeline",
  "processors" : [
    {
      "pipeline" : {
        "name": "pipelineA"
      }
    },
    {
      "set" : {
        "field": "outer_pipeline_set",
        "value": "outer"
      }
    }
  ]
}
```

### 其他的 Processors

其他還有許多好用的 Processors，例如：

* `drop`：刪除某個欄位。
* `set`：增加一個新欄位並填入指定的值。
* `urldecode`：常常收集到的 log 裡面的 URL 是有 encode 過的，當裡面有非英文的語言、或是特殊符號時，在 Log 的解讀上會很辛苦，可以先透過 `urldecode` processor 進行 URL decode。
* `uri_parts`：直接依照 URI 的標準，拆解出 `scheme`、`domain`、`port`、`path`、`query`、`extension`、`fragment`…等欄位。
* `user_agent`：將 User Agent 的字串，拆解出 `name`、`version`、`os`、`device`…等欄位。
* `script`：透過 painless 的語言，編寫處理的邏輯，很強大的 processor。
* `fail`：配合 `if` 的條件設定，可以在 ingest pipeline 時進行檢查，針對不符合要求的文件，拋出錯誤。

## 參考資料

1. [官方文件 - Elasticsearch Ingest Processor Reference](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/processors.html)


# Enrich 資料與例外處理

### 本篇學習重點

* 使用 Ingest Pipeline 時，想要透過查找其他資訊，將相關的資訊加入到處理的文件中，這個 Enrich 功能是如何運作及要怎麼使用。
* 使用 Ingest Pipeline 時，若發生錯誤，要如何進行例外狀況的處理。

***

## 如何在 Ingest Pipeline 時 Enrich 資料

### 什麼是 Enrich

Enrich (充實、使豐富)，指的是在 Ingest Pipeline 中，透過其他地方取得相關的資料，並加在原來的資料當中，讓資料更為豐富。

這種做法在資料處理 ETL (Extract, Transform, Load) 的過程中蠻常使用，也很重要的一種做法，能讓我們能做到『空間換時間』或是『先苦後甘』這樣的目的。

由於 Elasticsearch 不是關聯式資料庫，而是 Document Based (文件型) 的 NoSQL 資料庫，所以文件在存入 Elasticsearch 之前應該要視情況去正規化，同時為了追求查詢時能有較快的執行速度，會在文件存入時，盡可能將文件查詢時會使用到的資訊先一併寫入在文件之中，避免後續執行時要另外透過 Elasticsearch 的 Join 或是 Application 端另外處理資料查詢及合併等動作。

例如以下幾種情境：

* 在存放銷售訂單的 Document 中，依照訂單裡的 Product ID 將 Product 的詳細資料查詢出來，加在銷售訂單的文件之中。
* 透過已定義好的 IP 位置清單，識別出某一筆處理的請求是來自於客戶或是某個供應商。
* 根據地理坐標，查出地址或是郵遞區號，加在原來的文件之中。
* Web Server 的存取 logs，透過 IP反查出 Geo Location 的坐標資訊並且記錄在 log 之中，讓日後使用時能直接透過地圖呈現地區的分佈。
* 在使用者點擊觀看影片記錄的 log 之中，先將日後分析時會使用到的影片類型、使用者資訊，先反查出來添加在 log 之中。

在 Elasticsearch Ingest Pipeline 的處理過程中，有定義一個 `Enrich Processor` ，就是專門提供資料 Enrich 的處理，接著將介紹這個 Enrich Processor 的運作方式。

### Enrich Processor 的運作方式

先摘錄 Enrich Processor 的運作重點：

* Lookup (查找) 的來源 (Source Index) 只能是 Elasticsearch Index，不支援從 Elasticsearch 的外部讀資料。
* 會依照 Policy 的查找規則，將符合規則的資料轉存在另一個 Enrich Index 中。
* Enrich Processor 在運作時，只會比對 Enrich Index 裡的資料，有找到就會加入到 Document 裡。
* Source Index 的資料更新時，不會反應到 Enrich Index 裡，會需要另外重新執行 Policy，才能重新產生新的 Enrich Index 資料。

接下來我們針對運作的架構與流程進行較細部的說明。

![26-enrich-process](https://i.imgur.com/Bl5vISB.png)

上圖的運作架構，在 Ingest Pipeline 的處理過程中，加上了 `enrich` processor ，這個 `enrich` 的背後，共有三個不同的角色：

#### Enrich Policy

首先 Enrich Policy 是一組需要另外建立的設定，其中定義了 Enrich 的操作應該如何進行，包含

* 定義存放 Enrich 資料的 Source Index。
* `policy_type` 定義找資料時要用哪一種比對方式。
* 指定 `match` 欄位，表示要從 Source Index 中的哪個欄位來進行查尋。
* `enrich_fields` ，要將從 Source Index 中查尋到文件裡的哪些欄位，加入到原來的文件中。

Enrich Policy 是要經過 `Execute` (執行) 的 API 來觸發運作，並不是自動會在背景執行的機制，在執行時，會將 Source Index 裡符合條件的資料找出，並寫入到 Enrich Index 當中進行獨立的儲存。

> 注意，Enrich Policy 建立後不能修改，只能刪除並建立新的 Enrich Policy。

#### Source Index

Enrich 的處理過程中，會透過某個資料的來源進行查詢以取得額外的資料，這個資料來源必須是 Elasticsearch 中的 Index，也就所謂的 Source Index。

Source Index 可以是一個或多個 Elasticsearch 的 Index，而這個 Index 其實就是一般 Elasticsearch 的 Index，並沒有不同，所以能用一般存取的方式進行資料的維護，並且一個 Elasticsearch 的 Index 可以同時當作多個不同 Enrich 處理的 Source Index。

#### Enrich Index

由於每次 Enrich Processor 在處理 Indexing 的文件時，若當下直接從 Source Index 查找資料時，因為較花資源，另外也可能因為查詢條件較複雜會執行較久，所以 Enrich 的運作機制中，有定義了 Enrich Index，讓 Enrich Policy 執行時，透過 Elasticsearch 所建立一個系統層級的 Index，並且會與 Enrich Policy 綁定，裡面存放著在 Source Index 裡找到的文件，也是 Enrich Processor 在處理 Indexing 文件時，實際會用來查找資料的資料來源。

Enrich Index 有以下幾個特性：

* 是由 Elasticsearch 所建立及維護的 Index，因此不應該直接去使用這些系統 Index。
* Enrich Index 的名稱會是 `.enrich-*` 開頭。
* Enrich Index 被建立之後，會執行 Segment files 的 force merged 的，以增加查詢時的效率。
* Enrich Index 是唯讀的，也就是無法修改裡面的內容。

### 使用 Enrich Processor 的完整步驟

在了解 Enrich Processor 的運作方式之後，這邊來介紹要使用時的完整步驟：

1. 準備 Source Index：在 Indexing Document 時，提供 Ingest Pipeline 的 Enrich 查閱的資料，將這些資料存放在 Elasticsearch 的 Index 之中。
2. 建立 Enrich Policy：透過 [create enrich policy API](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/put-enrich-policy-api.html) 來建立 Enrich Policy。
3. 執行 Enrich Policy：使用 [execute enrich policy API](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/execute-enrich-policy-api.html) 針對上面建立好的 Enrich Policy 來觸發執行，並建立出 Enrich Index。
4. 在 Ingest Pipeline 中指定 `enrich` processor：可以將 `enrich` processor 添加到現有的 Ingest Pipeline 之中，或是建立新的 Ingest Pipeline。
5. 將文件 Indexing 到 Elasticsearch 之中，並指定使用上面建立好的 Ingest Pipeline。
6. 如果查閱的資料有異動，先更新到 Source Index 之中，再執行步驟 3 的 execute enrich policy，將 Enrich Index 的資料進行更新，如果先前已經 Ingest 的資料也想要回溯，可以另外透過 \_reindex 或 update\_by\_query 並指定 Ingest Pipeline，以使用新的 Enrich Index 來更新資料。
7. 如果 Enrich Policy 要修改，先建立新的 Enrich Policy，並且修改 `enrich` processor 使用新的 Enrich Policy，再刪除舊的 Enrich Policy。

### 一個實際使用 Enrich Processor 的例子

依照上述的步驟，我們首先準備 Source Index `users`：

```
PUT /users/_doc/1?refresh=wait_for
{
  "email": "mardy.brown@asciidocsmith.com",
  "first_name": "Mardy",
  "last_name": "Brown",
  "city": "New Orleans",
  "county": "Orleans",
  "state": "LA",
  "zip": 70116,
  "web": "mardy.asciidocsmith.com"
}
```

接著我們定義 Enrich Policy - `users-policy`，並且指定使用 `email` 欄位來進行查閱，若有查到，我們要將 `first_name`、`last_name`、`city`、`zip`、`state` 的資料增加到 indexing 的文件中。

```
PUT /_enrich/policy/users-policy
{
  "match": {
    "indices": "users",
    "match_field": "email",
    "enrich_fields": ["first_name", "last_name", "city", "zip", "state"]
  }
}
```

執行 Enrich Policy，以建立 Enrich Index。

```
POST /_enrich/policy/users-policy/_execute
```

這時可以先使用 `_cat/indices` 查看 Enrich Index 是否有正確建立：

```
GET _cat/indices/.enrich-users-policy*?v
```

並使用 `_search` 查看 Enrich Index 裡的內容：

```
GET .enrich-users-policy-*/_search
```

接著我們建立 Ingest Pipeline 並且使用 `enrich` processor

```
PUT /_ingest/pipeline/user_lookup
{
  "processors" : [
    {
      "enrich" : {
        "description": "Add 'user' data based on 'email'",
        "policy_name": "users-policy",
        "field" : "email",
        "target_field": "user",
        "max_matches": "1"
      }
    }
  ]
}
```

我們可以 Indexing 文件，並指定 Ingest Pipeline 來確認是否正常運作

```
PUT /my-index-000001/_doc/my_id?pipeline=user_lookup
{
  "email": "mardy.brown@asciidocsmith.com"
}
```

最後確認 Indexing 進入 Elasticsearch 的文件有正確的如我們的預期被 Enrich。

```
GET /my-index-000001/_doc/my_id
```

### 參考官方 Geo Location 的範例

除了上述的 `term` 查閱的 Enrich 方式，Enrich Processor 也有提供 `geo_shape` 查閱方式，可以參考 [官方文件 - Enrich you data based on geolocation](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/geo-match-enrich-policy-type.html)。

## 使用 Ingest Pipeline 時的例外處理

使用 Ingest Pipeline 時，如果發生錯誤，預設的處理行為會丟出 Exception (例外狀況) 的錯誤，並且停止這筆資料的 Indexing 處理。

如果我們希望在某一個特定 Ingest Processor 的處理發生錯誤時，能忽略這個錯誤，繼續的向下執行，我們可以有三種作法：

1. 在 processor 的設定中，指定 `ignore_failure` 的屬性，並設定成 `true` ，讓錯誤發生時，直接略過當前的 processor，進入下一個 processor 的處理。
2. 在 processor 的設定中，指定 `on_failure` 的設定，讓錯誤發生時，執行另外一系列的 processors。(裡面的 processor 也可以再指定錯誤發生時的 `on_failure`，型成巢狀的設定)

```
PUT _ingest/pipeline/my-pipeline
{
  "processors": [
    {
      "rename": {
        "description": "Rename 'provider' to 'cloud.provider'",
        "field": "provider",
        "target_field": "cloud.provider",
        "on_failure": [
          {
            "set": {
              "description": "Set 'error.message'",
              "field": "error.message",
              "value": "Field 'provider' does not exist. Cannot rename to 'cloud.provider'",
              "override": false
            }
          }
        ]
      }
    }
  ]
}
```

1. 直接在最外層的 pipeline 設定 `on_failure`，將整個 pipeline 最終會發生的錯誤，給抓住。(以下的例子配合 `set` processor，將這筆發生錯誤的資料，另外寫到指定的 index 中。)

```
PUT _ingest/pipeline/my-pipeline
{
  "processors": [ ... ],
  "on_failure": [
    {
      "set": {
        "description": "Index document to 'failed-<index>'",
        "field": "_index",
        "value": "failed-{{{ _index }}}"
      }
    }
  ]
}
```

在使用 `on_failure` 時，也可以使用以下的屬性，取得錯誤相關的資訊：

* `on_failure_message`
* `on_failure_processor_type`
* `on_failure_processor_tag`
* `on_failure_pipeline`

使用方式如下：

```
PUT _ingest/pipeline/my-pipeline
{
  "processors": [ ... ],
  "on_failure": [
    {
      "set": {
        "description": "Record error information",
        "field": "error_information",
        "value": "Processor {{ _ingest.on_failure_processor_type }} with tag {{ _ingest.on_failure_processor_tag }} in pipeline {{ _ingest.on_failure_pipeline }} failed with message {{ _ingest.on_failure_message }}"
      }
    }
  ]
}
```

## 參考資料

1. [官方文件 - Elasticsearch Ingest Enrich Data](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/ingest-enriching-data.html)
2. [官方文件 - Create Enrich Policy API](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/put-enrich-policy-api.html)
3. [官方文件 - Enrich you data based on geolocation](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/geo-match-enrich-policy-type.html)
4. [官方文件 - Elasticsearch Ingest - Handling Pipeline Failures](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/ingest.html#handling-pipeline-failures)


# 有效的使用 Observability 的資料

針對前面章節所收集的各種 Observability 資料，說明如何使用進階的 Machine Learning 進行更有效的運用，並且在異常時主動通知的設定方式，以及 Observability 的資料管理，最後將分享實際參加 ElasticOn Observability Workshop 的競賽經歷，以及使用 Elastic Observability 的心得。

* [01 - 透過 Machine Learning 發現異常的問題](/tech-sharing/uncle-joe-teach-es-elastc-observability/you-xiao-de-shi-yong-observability-de-zi-liao/tou-guo-machine-learning-fa-xian-yi-chang-de-wen-ti)
* [02 - 使用 Kibana Alerts 主動通知異常狀況](/tech-sharing/uncle-joe-teach-es-elastc-observability/you-xiao-de-shi-yong-observability-de-zi-liao/shi-yong-kibana-alerts-zhu-dong-tong-zhi-yi-chang-zhuang-kuang)
* [03 - 資料的生命週期管理](/tech-sharing/uncle-joe-teach-es-elastc-observability/you-xiao-de-shi-yong-observability-de-zi-liao/zi-liao-de-sheng-ming-zhou-qi-guan-li)
* [04 - 使用 Elastic Observability 追縱及觀察問題的心得](/tech-sharing/uncle-joe-teach-es-elastc-observability/you-xiao-de-shi-yong-observability-de-zi-liao/shi-yong-elastic-observability-zhui-zong-ji-guan-cha-wen-ti-de-xin-de)


# 透過 Machine Learning 發現異常的問題

### 本篇學習重點

* 了解 Elastic Observability 中，透過 Machine Learning 可以得到什麼樣的協助

## Elastic Stack 中的 Machine Learning

AIOps 這個議題在近幾年愈來愈火紅，以往我們總是只能透過進行監視、建立好規則觸發警報，來了解系統是否發生異常，但若是我們沒有自己觀察到的部份，或是規則沒有定義清楚的地方，往往很難有效且即時的發現問題，一般都是等到造成更嚴重的影響，嚴重到我們被通知道，我們知道有發生問題，而透過 Machine Learning 的能力，就是希望能在更嚴重的災情發生之前，我們就能主動被提醒是否有異常，進而防止災情的發生。

要說到 Elastic Stack 中所使用到 Machine Learning 的能力，主要有個兩部份：

* Anomaly Detection (異常偵測)：主要是針對隨時間增長的數據，透過不斷收集新的資料來進行非監督學習 (unsupervised learning) 並創建正常行為的模式，使用這個模式比對新進來的資料，判斷是不是有異常的狀況發生。
* Data Frame Analytics (資料框分析)：使用 data frame 的方式進行資料分析，並且提供 outlier detection (異常值分析)、regression (回歸) 演算法、classification (分類) 的三種方式。

由於我自己本身對 Machine Learning 這部份並不熟悉，因此也不便多做說明，有興趣的可以查看 [官方文件 - Machine Learning](https://www.elastic.co/guide/en/machine-learning/7.15/index.html)。

## 在 Elastic Observability 中使用 Machine Learning

雖然我對於 Machine Learning 不熟悉，同時也聽過不同的評論表示 Elastic Stack 裡使用到的 Machine Learning 只是基本的能力，並沒有太過華麗，不過或許是這樣反而更容易讓非 Machine Learning 專業的人上手，同時所關注的點會是在如何能確實的協助我們在 Observability 這件事情上。

接下來我就從 Elastic Observability 當中的四大功能 Logs、Metrics、Uptime、APM ，裡面所使用到 Machine Learning 的部份來進行說明。

### Logs

首先在 Logs 的畫面中，功能表中就有 Anomalies 與 Categories 的選項，這兩個功能都是透過 Machine Learning 的能力所執行的功能，主要能協助我們做到：

* 某種類型的 Logs 數量發生異常 (變多、變少、突然出現)。
* 特定行為的 Logs 數變多，例如某個 IP 突然有大量的存取，並且告訴我們異常的時間區段在哪邊。
* 除了 Machine Learning 本身的機制能通知我們之外，因為有 Categories 將 Logs 進行相似的分類並使用視覺呈現，我們也可以更容易的透過肉眼來檢視是否有異常狀況發生。

#### Anomalies

Logs 的 Anomaly 主要是針對 log entry rates (輸入率) 來進行異常的判斷。

第一次進入時，會需要建立 Machine Learninng 的 job (工作)，只需要選擇起始的時間，以及針對哪個 indes 進行處理即可。

![13-Kibana-Create-ML-Job](https://i.imgur.com/FDmNocP.png)

當 ML job 開始執行後，同時也收集一段時間的資料之後 (一般來說有二週以上的資料會較準確)，我們在 Anomalies 可以看到，針對不同的 dataset (這個欄位是 Elastic Common Schema 所定義的，同時也就是 ECS 正規化的好處)，在不同的時間點，有哪些可能是異常的狀態。

並且會用簡單的分數來協助我們判斷，並且在 Anomaly 可以直接看到異常的描述。

![27-logs-anomalies](https://i.imgur.com/alqbJB8.png)

針對底下的項目展開，可以看到最近幾則判定為異常的 events 的原始 logs 長什麼樣子，從上圖的時間軸，也能快速的針對這段有異常的時間區段來設置時間的篩選，也能進一步跳轉到 Elastic Machine Learning 功能的 Anomaly Explorer 畫面進行深入的分析。

![27-logs-anomalies-detail](https://i.imgur.com/xMZuwbw.png)

#### Categories

進入到 Categories (分類) 的功能畫面時，很單純的依照收集到的 Logs 進行分類，前且顯示總數量、Datasets 的來源、並且借由 Trend (趨勢) 的變化及數量來快速判斷異常的狀況。

![27-logs-categories](https://i.imgur.com/aZPRlkR.png)

### Metrics

Metrics 裡使用 Machine Learning 主要可以協助我們針對 Metrics 數據判斷是否有異常，例如：

* CPU 或 Memory 用量突然增加
* 某個服務的網路存取量異常高
* 某台 host 沒有 inbound 的流量

而 Metrics 裡要啟用 Machine Learning，是在 Inventory 裡面來啟用，並透過右上角的 **Anomaly detection**。

![27-metrics-anomaly](https://i.imgur.com/dQfxwBH.png)

同樣的設定上也相當單純，選擇起始的時間，並且指定是否有要使用哪個欄位值來做 partition。

![27-metrics-anomaly-setting](https://i.imgur.com/liDgBm7.png)

至於檢視異常狀況，也是透過右上角的 Anonmaly detecion 的選項，同時點選 Anomalies 的頁籤，可以看到 Machine Learning 協助判斷出來的異常資訊。

透過 Actions 的選單，可以快速的跳到 Anomaly Explorer 或是在 Metrics 的 Inventory 畫面中檢視。

![27-metrics-anomaly-list](https://i.imgur.com/Dgq3OhF.png)

點選 Show in inventory 之後，會直接帶入篩選值，以及切到時間到異常發生的時段。

![27-metrics-anomaly-inventory](https://i.imgur.com/AzPcO42.png)

### Uptime

在 Uptime 裡，Machine Learning 主要協助的是：

* 某次請求的回應時間，是否與先前相比異常。

設定啟用 Anomaly detection 的方式，是要進入某個定義好的 Monitor 項目之中：

![27-uptime-overview](https://i.imgur.com/DMQR7cY.png)

點選進入之後，在 Monitor duration 的畫面裡，可以看到 **Enable anomaly detection** 的選項。

![27-uptime-anomaly](https://i.imgur.com/4ceAuzy.png)

一旦啟用對後，若有回應時間特別久的異常發生，在 Monitor duration 裡就可以直接看到。

![27-uptime-anomaly-error](https://i.imgur.com/Y9ienSJ.png)

### APM

APM 的部份，與 Machine Learning 有較完整的 APM anomaly detection 的整合，針對

* Span
* Transaction Duration
* Error
* Throughput
* User Agent 的特定屬性行為

上述的各項都有較完整的整合，這部份的資訊可以參考 [官方文件 - Machine Learning - APM anomaly detection integration](https://www.elastic.co/guide/en/machine-learning/7.15/ootb-ml-jobs-apm.html)。

而設定的部份，也是在右上角的 Anomaly detection 進行設定。

![27-apm-anomaly-setting](https://i.imgur.com/p8jAOR3.png)

而異常發生時，在 APM 的相關畫面都能看到標示，Service 頁面會出現 **Hearth** 的欄位：

![27-apm-ml-service](https://i.imgur.com/U5Qdy4T.png)

Transaction 的畫面會將異常 Duration 的區段標示出來：

![27-apm-ml-trans](https://i.imgur.com/pTrLnmM.png)

Service Map 當中，有異常的服務也會變成黃色或紅色，點選下去也會出現異常的分析資訊：

![27-apm-ml-service-map](https://i.imgur.com/KQ6f0nK.png)

***

以上的介紹，是針對 Elastic Observability 中，透過 Machine Learning 在 Logs、Metrics、Uptime、Traces 裡整合的相關功能說明，有興趣的朋友可以進一步的從 [官方文件 - Machine Learning](https://www.elastic.co/guide/en/machine-learning/7.15/index.html) 查閱 Machine Learning 的進階用法，特別是 Anomaly Explorer 裡面擁有更多詳細的功能，對於異常的深入分析會有所幫助。

## 參考資料

1. [官方文件 - Machine Learning](https://www.elastic.co/guide/en/machine-learning/7.15/index.html)
2. [官方文件 - Machine Learning - APM anomaly detection integration](https://www.elastic.co/guide/en/machine-learning/7.15/ootb-ml-jobs-apm.html)


# 使用 Kibana Alerts 主動通知異常狀況

### 本篇學習重點

* 使用 Kibana Alerts 之前的準備工作。
* 了解 Elastic Observability 針對 Kibana Alerts 的整合上有提供哪些 Alert 的設置。

## 使用 Kibana Alerts

在使用 Kibana Alerts 之前，有以下幾點需要注意及準備的項目：

### 準備及設定好加密的密鑰

由於 Kibana 中的敏感資料和 Alerting Rules (警報規則)，儲存時都會進行加密，所以要先準備好加密的密鑰。

密鑰需設定在 `kibana.yml` 中的 `xpack.encryptedSavedObjects.encryptionKey` 設定之中，以下是協助產生密鑰的工具及指令：

```
bin/kibana-encryption-keys generate
```

### 設定好對外開放存取的 Base URL

要讓 Alert 發送通知到外部之後，可以透過連結導回 Kibana，所以會需要設定好外部存取 Kibana 時有效的 Base URL，設定在 `kibana.yml` 中的 `server.publicBaseUrl` 。

### 使用 TLS 安全的連線

如果有啟用 Elastic 的 Security 功能時，必須要設定並啟用 TLS 連線。(可參考 [官方文件 - Setup basic security for the Elastic Stack plus secured HTTPS traffic](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/security-basic-setup-https.html#encrypt-kibana-elasticsearch) )

為了在背景執行的處理程式能安全的存取 Elasticsearch，Kibana alerting 會使用 API key 的安全認證機制，而要使用 API key 的話，就一定要啟用 TLS。

要讓 Elasticsearch 與 Kibana 之間使用 TLS 連線，會有以下幾個設置步驟：

1. 使用 `elasticsearch-certutil` 配合 `http` 參數，來建立 TLS 相關的 certificate。

如果你的環境之中沒有 CA (Certificate Authorities)，又或是先前沒有建立過 CA 的話，會要先透過 `bin/elasticsearch-certutil` 配合 `ca` 參數來建立 CA。

建立完成 http 使用的 certificate 之後，壓縮檔裡面會有以下的內容：

![image-20211013021828881](https://i.imgur.com/q7RdqsS.png)

1. 將 `kibana/elasticsearch-ca.pem` 檔案，複制到 Kibana 的 config 目錄中，並修改 `kibana.yml` 中 的 `elasticsearch.ssl.certificateAuthorities` 設定，以及將 Elasticsearch 的 protocol 改成 HTTPS：

```
elasticsearch.ssl.certificateAuthorities: $KBN_PATH_CONF/elasticsearch-ca.pem
elasticsearch.hosts: ["https://localhost:9200"]
```

1. 同時也要確保 Elasticsearch 也啟用 TLS 連線有正確設定，並且將先前產生的 `http.p12` 複制到 config 目錄底下。

```
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.keystore.path: http.p12
```

1. 重新啟動 Elasticsearch 與 Kibana 。

### 建立好 Connector

Kibana Alerts 有支援多種的 Connectors 如下：

* 寄 Email
* 寫入 Elasticsearch Index
* 建立 Jira incident
* PageDuty
* Slack
* Webhook
* IBM Resilient
* Microsoft Teams
* 寫到 Kibana 的 ServerLog
* 建立 ServiceNow incident
* 建立 Swimlane incident

在 Kibana > Stack Management > Rules and Connectors > Connector 裡可以進行設定，有些與 Security 相關的，可能會要使用到 keystore 來保存密鑰，細節相關的設定，可以參考 [官方文件 - Kibana Action Types](https://www.elastic.co/guide/en/kibana/7.15/action-types.html) 的說明。

這些 Connector 建立好之後，就會是 Alert 最後要發送通知時的接口。

## Elastic Observability 對於 Alerts 的整合與支援

Elastic Observability 的功能之中，有針對 Alerts 進行整合，也就是在 Observability 的 UI 當中，針對該服務的特性，有建立能較快速建立 Alerts Rules (規則) 的功能，以下針對 Alerts 設置的部份進行說明。

### Logs

Log 的部份，能快速針對 Log threshold (門檻) 進行 Alerts 的設置，設置重點如下：

* 在一段時間區間之中，針對一個特定條件下的 logs 數量，或某兩種條件之間的 logs 數量的比例，達到一個門檻時，發出 alert。
* 同時這個計算時，能依照特定欄位的值來進行分群 (group by)，也就是上述的規則可以限定在某些條件的分類上執行，例如每台機器分開計算、或是每種服務分開計算。

![28-logs](https://i.imgur.com/FBymMQy.png)

設定細節可以參考 [官方文件 - Observability - Create a logs threshold rule](https://www.elastic.co/guide/en/observability/current/logs-threshold-alert.html)。

### Metrics

Metrics 在 Inventory 與 Metric Explorer 的頁面當中，分類提供不同的 Alerts 設置。

#### Inventory - Infrastructure Alerts

在 Inventory 當中，關注的重點就是 Infrastructure，不論是 Host、Docker、Kubernetes 或是 AWS 雲端主機或服務。

所以針對 Inventory 的部份，Alerts 的主要設置重點如下：

* 當指定的主機或服務，在最近一段時間內，在任何 metrics 的條件下 (例如：CPU 或 Memory 使用率、網路流量、Log rate、甚至是任何一個欄位的平均數…等) 達到指定的門檻時，就發出 Alert。
* 除了以上的 metrics 條件外，若是在最近一段時間內，沒有任何的 logs 產生，也可以發出 Alert。
* 另外除了 Alert 的報警之外，也可以定義 Warning 層級的條件。
* 與 Metrics 中 Alert 設定的差別，主要在於 Inventory 會指定主機或服務相關的範圍。

![28-metrics-inventory](https://i.imgur.com/lPsDZig.png)

設定細節可以參考 [官方文件 - Observability - Infrastructure threshold rule](https://www.elastic.co/guide/en/observability/current/infrastructure-threshold-alert.html)。

#### Metric Explorer

在 Metric 的部份，主要的設定如下：

* 支援任何所收集到的 Metrics，在最近的一段時間內，若達到一定的門檻，就發送 Alert。
* 若是在最近一段時間內，沒有任何的 logs 產生，也可以發出 Alert。
* 也能支援設定 Warning 的門檻。

![28-metrics-metrics](https://i.imgur.com/QXjcd5c.png)

設定細節可以參考 [官方文件 - Observability - Metrics threshold rule](https://www.elastic.co/guide/en/observability/current/metrics-threshold-alert.html)。

### Uptime

在 Uptime 的部份，主要的 Alert 設定有以下三種：

* Monitor status: 服務的可用性是否正常。
* TLS certificate: 憑證是否快過期。
* Uptime duration anomaly: 服務的回應時間是否異常。

#### Monitor Status

在 Monitor 的頁面之中，Uptime 最主要的任務就是確認服務是否還活著，因此 Alert 的設定重點為：

* Status Check，在一小段時間區間，如果服務沒有依照預期的結果回應並且超過一定的數量，就發出 Alert。
* Availability，在較長的一段時間區間 (例如一個月)，如果服務總共異常超過某個比例 (例如 99.9%)，就發出 Alert，可用作 SLA 的警示。

![28-uptime-monitor](https://i.imgur.com/UUrgmST.png)

設定細節可以參考 [官方文件 - Observability - Monitor status rule](https://www.elastic.co/guide/en/observability/current/monitor-status-alert.html)。

#### TLS Certificate

TLS Certificate 的 Alert 很單純，就只有一個重點：

* 在 Certificate 剩一段時間就要過期時，發出 Alert。

![28-uptime-tls](https://i.imgur.com/eZju8Ou.png)

設定細節可以參考 [官方文件 - Observability - TLS certificate rule](https://www.elastic.co/guide/en/observability/current/tls-certificate-alert.html)。

#### Uptime Duration 異常

在 Uptime Monitor 的頁面中，如我們前一篇文章的介紹，可以開啟 Machine Learning 的 Anomaly detecion 的功能。

一但開始這個功能之後，就可以在 Monitor 的回應時間被判定成異常時，主動發送 Alert。

![28-uptime-abnormal](https://i.imgur.com/61XV4Ve.png)

點選 Enable anomaly alert 後的設定畫面如下：

![28-uptime-abnormal-alert](https://i.imgur.com/C1w3pSL.png)

設定細節可以參考 [官方文件 - Observability - Uptime duration anomaly rule](https://www.elastic.co/guide/en/observability/current/duration-anomaly-alert.html)。

## 參考資料

1. [官方文件 - Kibana Alerting Setup](https://www.elastic.co/guide/en/kibana/7.15/alerting-setup.html)
2. [官方文件 - Setup basic security for the Elastic Stack plus secured HTTPS traffic](https://www.elastic.co/guide/en/elasticsearch/reference/7.15/security-basic-setup-https.html#encrypt-kibana-elasticsearch)
3. [官方文件 - Kibana Action Types](https://www.elastic.co/guide/en/kibana/7.15/action-types.html)


# 資料的生命週期管理

### 本篇學習重點

* Elastic Observability 收集的資料怎麼被儲存
* 如何對 Observability 資料進行生命週期的管理

***

在進入本章節之前，由於這個章節會使用到 Elasticsearch Index Lifecycle Management (ILM) 的功能，如果對於這部份的基礎知識不熟悉的讀者，可以參考我去年鐵人賽的文章 [喬叔教 Elastic - 11 - 管理 Index 的 Best Practices (3/7) - Index Lifecycle Management (ILM)](https://ithelp.ithome.com.tw/articles/10244575)，本文將不會針對 ILM 的部份有太多細節的解釋。

## Elastic Observability 收集的資料怎麼被儲存

首先針對 Elastic Observability 所收集到的各種資料，我們分別來看儲存在哪些地方，我們先從 **Kibana** > **Index Management** > **Indices** 進行查看，可以看這些由 Beats 與 APM 收進來的資料所存放的 Index。

![image-20211014211711713](https://i.imgur.com/zNPjmY8.png)

並且從其中一個 Index 查看 Index Settings，可以發現其實這些 Index 都有指定給 ILM 來進行管理。

![29-index-settings](https://i.imgur.com/hHmEB6B.png)

既然有使用 ILM，也就會有對應的 Index Template 來負責設定新產生 Index 的 Settings、Mappings、Aliases，我們也進入 Index Tempaltes 查看，因為我這次使用的還是 Beats 來收進資料，因此會使用到的是 Legacy index tempaltes，若是使用新版、未來將取代 beats 的 Elastic Agent 時，就會是新版的 Index Template 了。

![29-index-template](https://i.imgur.com/08hWMk2.png)

接著來查看這些 Observability 的資料，所使用的 **Index lifecycle Policies**：

![29-index-lifecycle-policy](https://i.imgur.com/Rg6q68A.png)

Elastic Observability 的這些資料，寫入在 Elasticsearch 中，Index 寫入及管理的方式整理起來如下：

| Service Name | Index Name                            | Index Template Name         | ILM Policy           | ILM Phase |
| ------------ | ------------------------------------- | --------------------------- | -------------------- | --------- |
| **Uptime**   | heartbeat-{version}-{now/d}-#         | heartbeat-{version}         | heartbeat            | Hot       |
| **Metrics**  | metricbeat-{version}-{now/d}-#        | metricbeat-{version}        | metricbeat           | Hot       |
| **Logs**     | filebeat-{version}-{now/d}-#          | filebeat-{version}          | filebeat             | Hot       |
| **Traces**   | apm-{version}-{event\_type}-{now/d}-# | apm-{version}-{event\_type} | api-rollover-30-days | Hot, Warm |

> 注意：這邊使用的是 Beats，還沒使用最新的 Elastic Agent，若是使用 Elastic Agent 的話，Logs 類都會被收進 `logs` 而 Metrics 類都會被收進 `metrics` 之中，並由這些對應的新版 Index Template 來管理。

這邊就發現一件事，就是這些資料寫入到 Elasticsearch 之後，其實並沒有對資料的生命週期進行管理，只有先幫我們將 ILM 建立起來，裡面的 Phase 幾乎都沒有定義，只有 APM 有多設定一個 Warm phase，其他都有只 Hot phase。

## 如何對 Observability 資料進行生命週期的管理

由於不同使用情境、不同的硬體資料，所適用的 ILM 管理方式也會有所差異，以下會先針對 Observability 不同的資料類型，對於管理上的概念提出一些思考的方向，提供大家在規劃時參考。

### Metrics

這類型的數據，其實應該配合 Rollup，在一定時間之後，即保存較大時間顆粒度的彙總結果即可，因此 LIM 當中應該保留在 Hot phase、Warm phase 即可，Cold phase 及 Frozen phase 應該不需要留存，反而是 Rollup 之後的結果其實資料量會小非常多，一般可以留存好幾個月以上的時間，甚至時間更久之後，再次的 Rollup 以更大的時間顆粒度進行彙總，可留存數年。

例如：保留近2週的資料是完整的，2週\~4週使用 1分鐘為單位來 rollup，1個月之後使用 10分鐘為單位來 rollup…等。

### Uptime

Uptime 的資料最主要的目的是為了 SLA，這部份要留意 SLA 在法律上需要確保留存時間的限制，原始資料可能要保存一年以上，而這些資料也可以視情況透過 Rollup 進行瘦身來進行日後查詢時的加速。

### Logs

Logs 的類型就非常多，而且量也非常的大，但是也有留存一定時間的價值，在追查一些很少發生的特殊狀況時，這些資料就會有存在的價值，一般都會每個階段都有定義，最後進入到 Frozen 的階段，使用最便宜的 storage 來進行留存較長的時間。

如果因為資料類型的差異，有不同的 data retention policy，最好在 index 層級就將資料切開存放，這部份可以配合 beats 在收不同源頭的資料時，就指定要寫入的 index 名稱，或是使用 Logstash、Ingest Pipeline 來依照 event.dataset 的類型來開存放，就能使用不同的 ILM Policy 來分開管理。

### Traces

Traces 的資料，主要是協助效能的調校、問題發生時的除錯，一般來說並不太需要保留更久的時間，甚至一開始在收集時都會取樣本比例，因此保留時間會是以一般常會追查問題的狀況來規劃，可能是 2週 或 1個月之後就可以進入 cold phase 或 fronzen phase，並且再保留一段時間後就可刪除。

這邊要注意，首先針對 APM 所收集的資料，因為依照不同的 `event_type` 會存放在不同的 index 之中，所以不是所有 APM 的資料，都完全是屬於 Traces 這部份的定義，裡面也會有 Metrics 或是 Logs 類型的資料，這部份也會要依照資料收集的特性可以做不同的規劃。


# 使用 Elastic Observability 追縱及觀察問題的心得

### 本篇學習重點

* 參加 ElasticOn Global 2021 Observability Workshop 的心得分享
* Elastic Observability 追縱及觀察問題的心得

## ElasticOn Global 2021 Observability Workshop

在參與這次第 13 屆鐵人賽的過程中，剛好遇到了 [ElasticCon Global 2021](https://www.elastic.co/elasticon/global) 的活動 ( Oct 5-7, 2021 )，在最後一天的議程中，有 Workshop 可以參加，我自然就選了最近我投入最深的 Observability 這個主題 - **Capture the Bug with Observability**

![30-elasticon-workshop](https://i.imgur.com/13WzJuf.png)

很驚豔的一場活動，由 Elastic Global Solution Architect (SA) - Dave Moore 主講，總共有 6位 Elastic SA 組成的團隊，以競賽的方式來進行，整場活動我整理成以下幾個部份來描述為什麼我覺得驚豔：

### 有趣的競賽 Workshop

出了十道題目，讓大家來找碴，大家會被指派一名 SA 當作你的指導員，你的角色就是 SRE，要實際操作 Elastic Cloud 上的環境來找問題，當你找到問題時，你要透過 slack 和 SA 回報，並且透過清楚的說明回報，讓 SA 能了解問題的真正原因甚至提出解決方法來得分，找到真正的 root cause 得 2 分，提供正確的 solution 可以再加 1分，最後排名前三的有小獎品。

### 貼近真實情境的出題

活動的環境是使用 GCP 環境，並且使用 GCP 自己提供的一個 [demo project](https://github.com/GoogleCloudPlatform/microservices-demo)，在裡面裝了 Elastic APM Agents，來收集 Observability 相關的資訊，十道題目為了要模擬各種真實發生的情況，會修改原來的 source code 並且打包成不同的 build，再透過 python 控制環境及操作 GKE，要在活動進行的短短時間內快速準備好環境、模擬真實的各種狀況，這部份的技術水準很高，也能真實的體驗到被 Alert 通知有問題時，在摸不著頭緒的情況下，每道題目只有十分鐘的限時，要如何使用手邊有的線索與工具來解決問題。

### 手把手的教學

每一道題目過後，Dave Moore 會一步一步的使用 Elastic Observability 的工具來分析問題的原因，並且說明如何抽絲剝繭的找到真正的 root cause，過程中也有安排雜訊的干擾，所以不是看到 error logs 就代表一定是 root cause，這樣的以摸擬真實情境的環境加上手把手的教學，學習的效果非常的好。

### 競賽時間壓力造成擬真感

每一題的時間只有十分鐘，而且還要等問題浮現，配合時間的壓力，很有真實系統出問題的緊張感，透過這樣的方式，也讓我更真實的驗證我自己對於 Elastic Observability 的熟悉程度與掌控度，有的題目甚至要看 code debug，有很多細節不容易在短時間查覺。

### 有一定難度的題目

有多難? 我舉幾個例子：

* js 寫錯，時間比對的部份，某個地方是文字+數字，型態錯誤，變成非預期的值，造成信用卡的有效時間總是被判定成過期 (對，你要找到 code 寫錯，在 Kibana 就能看到這段 code 哦!…)
* service publish host 設成 127.0.0.1 導入服務無法從外部存取
* capacity 不足，流量變大，某一台機器的 CPU loading 8x% 以上 ，導致某些服務開始會丟錯，latency 變高
* 某個切換頁面造成 302 redirect 無窮迴圈，因為 query string 參數多了個 # (變成 fragment 的宣告…)
* 前端的 code 有 bug，把某個傳入的參數少帶，後端發生 nullPointException

不少都是要有一定的程式開發能力，才能解得出來啊...，如果解不出來，也無法說你找到了 root cause，例如…service publish 為 127.0.0.1，不能只說因為這台服務連不到，要說為什麼… (分數好難拿)

雖然最後沒有拿到前三名的獎品，不過這場活動過後，我也對 Elastic Observability 有深層面應用上的掌握，覺得真的蠻不錯的，也剛好在這次最後鐵人賽的文章來分享我的心得。

## Elastic Observability 追縱及觀察問題的心得

以下幾個方向是我在使用 Elastic Observability 得到的心得：

### 盡可能掌握全局的狀態

當我們透過 slack 收到某一個 alert 時，我們可以直接點 link，就跳到 monitoring 的畫面，直接帶我們進入這個錯誤的地方，快速讓我們進入細節，但很常一個地方丟出的錯誤，這個地方偏偏就不是問題造成的原因，因此可以透過 Service Map 掌握整體服務的運作狀態，有沒有哪段 server 與 server 之間、或是 server 與第三方服務之間的路徑上，有出現異常，或是整條路上發生異常的有哪些，這部份就容易從 dependency map 來掌握上游的狀態。

除此之外 Uptime、Traces、Metrics 也都有 summary 的畫面，透過這些畫面盡可能的掌握全局系統是否還有哪些被忽略的資訊。

### 觀察影響的程度

系統常常會有些不重要的錯誤或警告的通知，有時問題發生時，這些資訊數量可能也不少，在盤查問題時，最好能先從對使用者影響的程度來快速的篩選這些資訊，例如 Boostrap 的 javascript 的錯誤，其實不會是 system down 的 root cuase，在快速判斷後，就可以將這類的訊息設定過濾掉，讓專注力能不要被這些次要的問題給影響。

### 時間的變化

為什麼 Summary 的圖表，總是會使用 histogram 等方式，而 Elastic Observability 的不少 dashboard 上，都會有一個功能是與"前一段時間"的數據進行比對，例如前一天、或前一週的同時段，這樣可以初步的讓我們發現某一段 peak 是不是異常，因為或許每次這個時間點都會有 peak，那可能就只是常態。

另外如果在某個時間點之後，latency 大幅上升、或是 request 量有和先前發生較大的變化，可以切換時間篩選關注這段時間，在時間的前、後觀察有哪些其他的變熟，針對這部份 Elastic Observability 在 APM Services 的 request histogram 畫面上，也會標示出是否有版本的變化，讓我們快速的可以看到，是不是因為更版後才造成的問題。

### 集中化並且結構化的 Log 真的非常重要

一個系統的 Logs 量非常的多，一但要盤查問題的時候，而且問題的方向還不夠明確時，Log 量太大，會讓我們很難的觀察問題，首先是 Logs 沒有收集在一起的話，盤查所花的時間肯定是集中化的數倍以上，集中化之後透過結構化及正規化後的 Log，我們在某些 filter 的條件就可以下得更有效率，例如直接 bypass 某一個 service 的 logs，或是專注在某種篩選條件的相關 logs，例如先專注在某一個有問題的 service 當中的某一台機器，但又可以快速的比對其他台機器，會對找問題非常有幫助。

### Machine Learning 的協助

我先前並沒有在實際專案上使用過 Elastic Stack 的 Machine Learning，在這次的 workshop 時，在盲目探查問題的時候，真的很有幫助，不過在問題浮現之後，倒是真的就比較沒在用到，不過對於還沒浮現的問題，能透過這樣的機制，在問題更被放大、甚至產生連鎖反應之前，就能被警示到，這部份就覺得蠻值得有一定規模的服務投資了。

***

透過以上我參加這次 ElasticOn Global 2021 的經驗分享，以及整體使用 Elastic Observability 的心得來當作這次鐵人賽的結尾。

(完全沒想到這次有夠累，比上一屆參賽還累，之後有空再來補心得! 打完收工!)


# 完賽心得

## 完賽心得

這次是喬叔第二次參加 iT邦幫忙鐵人賽，不過沒有學會上次的教訓，並且比上次還更超過，開賽當天才開始動筆，完全沒先準備，(上次參加有提早三天先寫)，收到 17 封催稿信件，多次 23:59 分才趕完 PO 文的，痛苦指數比去年還高非常多，可能有一部份是去年還可以想說寫不好就算了，今年多了一點去年得獎後的自己給自己的壓力吧！這次寫完還是覺得有許多地方不是那麼的完善、還有改進的空間，不過就繼續抱持著敏捷的精神、小步快跑、持續練習、堅持產出、持續回顧與改進，希望這次的文章能幫助到對於 Observability 有興趣，同時有在使用或是打算要使用 Elastic Stack 的中文資訊領域的讀者們。

(如果有踩線獎的話，我應該要有潛力獲獎…)

![31-alert-mails](https://i.imgur.com/7AtvPJR.jpg)

(不知道怎麼查每天 PO 文的時間，只好土炮從網頁爬…)

![31-deadline](https://i.imgur.com/ecpOrVx.jpg)

想知道我這次如何訂主題的章節嗎？其實第一、二天在寫的時候，還在思考系列文章的架構，最後想說，既然我要寫的是 Elastic Observability，我就以 [Elastic Certified Observability Engineer](https://www.elastic.co/training/elastic-certified-observability-engineer) 當作挑戰吧，所以就參考了認證考試的 Topics：

* Uptime
* Metrics
* Logging
* APM
* Structuring and Processing Data
* Working with Observability Data

完賽前的最後幾天也直接把認證考試放進購物車完成下訂，不過這陣子行程太滿，接下來會找一天把考試完成，之後有機會再來分享考試的心得與結果吧！

![31-certificate-exam](https://i.imgur.com/0b4Dl1t.png)

**10/27 更新** 已順利取得 Elastic Certified Observability Engineer 認證! 不枉這 30 天的鐵人賽文章!

![163535904482](https://i.imgur.com/wTJxUeG.png)


# Elasticsearch 技術分享小品


# Elastic 與 AI


# Elasticsearch Inference API 讓我們直接在 ES 裡運用 OpenAI Completion API

這二年來出來各式各樣 AI 工具與流程自動化的工具，而我自己也常運用這些工具來拼湊出 AI 的應用流程，但是 Elasticsearch 頂多是當個 vector store，還沒有想過要嘗試在 Elasticsearch ETL 的過程中使用 LLM。(去年曾自己用 ChatGPT 寫了個 Logstash OpenAI filter plugin 來呼叫 OpenAI API，做到這件事，但這也是使用外部的 ETL 工具)

這幾天因應某個專案，ETL 的任務是由 ES 的 Ingest Pipeline 負責，而沒用到 Logstash，讓我發現了 Elasticsearch 8.14 在 Inference API 當中加入的 #Completion 的功能 (使用 #OpenAI Completion API)。

這個功能在 Elasticsearch Release Notes 並沒有被特別強調，所以我一直沒注意到這件事，現在找到之後覺得實在是太方便了，必須在這邊分享一下。

這樣能做到什麼事?

1. 可以在 Elasticsearch API 直接下 prompt 取得 OpenAI 的回覆結果。
2. 可以直接運用在 Ingest Pipeline 當中，也就是你的資料寫入到 Elasticsearch 時，可以先透過 LLM 幫你加工或處理資料，再寫入到 Elasticsearch 當中。

我直接在我的圖片分享我這次做的事：

將一篇新聞文章，寫入到 Elasticsearch 時，在 ETL 透過 GPT4o 進行"摘要"以及"擷取重要關鍵字"，並寫入到"summary"與"keywords"的欄位之中。

<figure><img src="/files/QzXovwO3ogx9wsLbkgor" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/lWWHIzOoNbaSGdZ4EBIt" alt=""><figcaption></figcaption></figure>

以下是這次操作 Kibana 的完整步驟：

```java
PUT _inference/completion/openai_chat_completions
{
    "service": "openai",
    "service_settings": {
        "api_key": "${apiKey_openAI}",
        "model_id": "gpt-4o"
    }
}

POST _inference/completion/openai_chat_completions
{
    "input": "我女兒的姊姊是隻叫曲翑的狗，請問我女兒是什麼生物？"
}

PUT _ingest/pipeline/summarization_pipeline
{
  "processors": [
    {
      "script": {
        "source": "ctx.prompt = 'Please summarize the following text in 繁體中文 by using 台灣習慣用語: ' + ctx.content"
      }
    },
    {
      "inference": {
        "model_id": "openai_chat_completions",
        "input_output": {
          "input_field": "prompt",
          "output_field": "summary"
        }
      }
    },
    {
      "remove": {
        "field": "prompt"
      }
    },
    {
      "remove": {
        "field": "model_id"
      }
    }
  ]
}

PUT _ingest/pipeline/keyword_extraction_pipeline
{
  "processors": [
    {
      "script": {
        "source": "ctx.prompt = '你是一個關鍵字的擷取專家，你總是能從一段文字中，依照這段文字最主要的涵意，找到最重要的關鍵字，請直接回覆我關鍵字，並使用|分隔每個關鍵字，不需要額外的說明，直接回覆關鍵字結果即可，以下是你要頡取關鍵字的文章內容：\n\n' + ctx.content + '\n\n以下是關鍵字結果'"
      }
    },
    {
      "inference": {
        "model_id": "openai_chat_completions",
        "input_output": {
          "input_field": "prompt",
          "output_field": "keywords"
        }
      }
    },
    {
      "split": {
        "field": "keywords",
        "separator": "\\|"
      }
    },
    {
      "remove": {
        "field": "prompt"
      }
    },
    {
      "remove": {
        "field": "model_id"
      }
    }
  ]
}

PUT _ingest/pipeline/content_ai_etl
{
  "processors": [
    {
      "pipeline": {
        "name": "summarization_pipeline"
      }
    },
    {
      "pipeline": {
        "name": "keyword_extraction_pipeline"
      }
    }
  ]
}

POST joe-test/_doc/1?pipeline=content_ai_etl
{
  "content": """
「蟹肉棒」可說是火鍋常見配料之一，不過對於其塑膠膜該不該先拆再煮，始終分成兩派說法。一名網友就談到，跟朋友約好一起吃火鍋，但煮了30分鐘才發現「蟹肉棒塑膠膜沒拆」，讓他不禁擔心是否還能吃。貼文曝光後，除了引起兩派網友的看法外，食藥署也對此做出解答了。

一名網友在PTT上談到，「跟朋友約好要來我家吃火鍋，煮了大概30分鐘左右吧，才發現蟹肉棒的塑膠套忘了拆掉，等等要開吃了怎麼辦？」

 

貼文一出，有網友表示「拆三小，你煮餛飩水餃有在拆皮的嗎」、「本來就不用拆，這樣蟹肉棒很完整，超棒」、「根本沒差，我常這樣」。不過也有人認為不健康，並狠酸「吃下去變生化人，郎共讚欸」、「這樣會變塑化劑風味湯底，更好喝」、「塑化劑吃很多了不差這一點」、「不要拉低台灣平均長短」。

事實上，食藥署過去就曾解釋，「肉棒外層的塑膠是種高分子聚合物，裡頭成分耐熱程度不同，將塑膠套放入鍋中長時間煮食，很有可能將有毒物質吃下，因此建議民眾還是先拿下比較保險」，並提醒蟹肉棒的塑膠膜耐熱度從60度至140度都有，在高溫環境下結構相對不穩定，建議民眾不要嫌麻煩，多一個步驟多一分保護。
  """
}

GET joe-test/_doc/1

```

***

統整一下使用這個方式的優缺點：

優點：

* 全在 Elasticsearch 裡做完"運用 LLM 處理 ETL"這件事，Tech stack 的維護成本較低。
* 可直接運用 Ingest Pipeline 的 error handling 機制，發生錯誤時可寫入到別的 index，並方便重試的處理可再寫回原先的 index 中。

缺點：

* Indexing 的整體執行時間被拉長，資料寫入 TPS 較高的服務，可能不適合這樣的運作架構
* CoT (Chain of Thought) 或是過於複雜的 Prompt Chaining，若要加工 LLM 回覆的結果，可能導致處理流程太複雜，複雜的流程放在 No Code 工具來做，往往維護成本比寫 code 高非常多。(最近這半年多的深刻體驗。)

對於想在 ETL 當中簡單應用 LLM 的情境，這個功能真的很方便!


# 演講


# 2025.11 Elastic Days Taiwan

活動網頁：<https://events.elastic.co/unlockthepowerofsearchaitaiwan>

{% embed url="<https://speakerdeck.com/unclejoe/2025-elastic-days-taiwan-uncle-joe-ai-search>" %}

<figure><img src="/files/uebmC11zFNBviok4R18g" alt=""><figcaption><p>credit to Google Nano Banana Pro</p></figcaption></figure>


# 線上分享

收錄喬叔的線上分享：

* [2021.05.26 喬叔 Elasticsearch Index 管理與效能優化技巧](/tech-sharing/online-sharing/2021.05-qiao-shu-elasticsearch-index-guan-li-yu-xiao-neng-you-hua-ji-qiao)
* [2022.05.20 Elastic Certification 認證經驗分享](/tech-sharing/online-sharing/2022.05-elastic-certification-ren-zheng-jing-yan-fen-xiang)


# 2021.05 喬叔 Elasticsearch Index 管理與效能優化技巧

2021.05.26 與 [Will 保哥的技術交流中心](https://www.facebook.com/will.fans/videos/871207100098404/) 合辦的線上技術分享。

{% embed url="<https://www.youtube.com/watch?v=SdrNwxp0GMI>" %}
喬叔 Elasticsearch Index 管理與效能優化技巧
{% endembed %}

簡報 [https://www.slideshare.net/.../elasticsearch-index-248582029](https://www.slideshare.net/joe9991/elasticsearch-index-248582029)


# 2022.05 Elastic Certification 認證經驗分享

2022.05.20 與 [Will 保哥技術交流中心](https://www.facebook.com/will.fans/videos/elastic-certified-engineer-%E8%AA%8D%E8%AD%89%E8%80%83%E8%A9%A6%E7%B6%93%E9%A9%97%E5%88%86%E4%BA%AB/1713104289026103) 合辦的線上分享

{% embed url="<https://www.youtube.com/watch?v=63CQwpLZHs4>" %}
Elastic Certification 認證經驗分享
{% endembed %}

簡報心智圖：<https://www.xmind.net/m/GYHr8b/>


# 2024.05 淺淡 Elasticsearch 的運作原理

2024.05.16 iThome 鐵人講堂 - 淺淡 Elasticsearch 的運作原理

{% embed url="<https://itplus.ithome.com.tw/webinar-page/204>" %}

{% embed url="<https://youtu.be/k-hczeLZJug>" %}


# 工作坊


# 如何在 Elasticsearch 實現敏捷的資料建模與管理 @ DevOpsDays 2023

<figure><img src="/files/5fRq2aBxQynJKfcrxDpw" alt=""><figcaption><p>DevOpsDays Taipei 2023 工作坊滿意度調查結果</p></figcaption></figure>

## **如何在 Elasticsearch 實現敏捷的資料建模與管理**

在面對不斷變化的商業需求，敏捷快速交付價值以及选代的精神已廣泛落實在 DevOps 的實踐之中，但是在使用 Elasticsearch 處理巨量日誌或資料時，一但需求改變或是有新的資料使用需求提出時，我們的應對及管理的策略為何? 如何在 Elasticsearch 來做到最佳的實踐?

工作坊將包含以下的內容：

* 敏捷的資料建模與管理的概念簡介
* Elasticsearch Data Modeling 簡介
* 深入 Elasticsearch Data Modeling 的實作
* Schema on read 與 Schema on write 的差異與實現方式
* 事先定義好的 Data Modeling
* 動態產生的 Data Modeling
* Data Model 的选代

適合已經使用過 Elasticsearch，或至少稍微了解 Elasticsearch 的聽眾。

## **課程目標**

掌握有彈性的敏捷資料建模與管理的方法，並且透過設計好的實作案例，一步一步的學習如何在 Elasticsearch 進行實踐，掌握 Dynamic Field Mapping, Dynamic Template, Runtime Fields, Async Search 以及在 schema on read 至 schema on write 的轉換調整的方式。

## 行前準備

電腦需求

* 建議 **16GB** 以上的 RAM
* MacOS, Linux, Windows 皆可，但請不要用太舊的作業系統

操作環境需求

* Elasticsearch 8.0 以上版本
* Kibana 8.0 以上版本

這次工作坊主要會使用 Kibana 的 Dev Tools 進行操作，你有以下幾種方式，準備好這個環境：

1. **(新手推薦)** 在 <https://cloud.elastic.co> 申請一個新帳號，不用填入信用卡資訊，就可以免費試用 7 天，蠻適合這次工作坊中短期的使用。
2. 手動下載 [Elasticsearch 8.10.2](https://www.elastic.co/downloads/elasticsearch) 與 [Kibana 8.10.2](https://www.elastic.co/downloads/kibana) 壓縮檔，在本機執行。
   1. 解壓縮並啟動 Elasticsearch：執行 Elasticsearch 目錄中的 \
      `./bin/elasticsearch -E xpack.security.enabled=false` \
      `(透過參數強制關閉 Security，避免額外的 Security 設定流程。)`
   2. 解壓縮並啟動 Kibana：執行 Kibana 目錄中的 `./bin/kibana` 執行檔
   3. 請先確保 Elasticsearch 啟動後，再啟動 Kibana。
   4. 透過 <http://localhost:5601> 可以成功存取 Kibana 的網站。
3. 如果你習慣使用 Docker Container 的容器化方式來建構環境，可以使用 <https://github.com/onedoggo/devopsdays-taipei-2023-es-workshop> 準備好的環境。
   1. 先執行 `docker-compose up setup` 初始化 ES 的帳號密碼。
   2. 再執行 `docker-compose up -d` 啟動環境，
   3. 即可以 帳號 `elastic` 密碼 `changeme` 透過 <http://localhost:5601> 存取 kibana。
4. 使用你自己架設好的 Elasticsearch 與 Kibana 環境，可以存取 Kibana Dev Tools 即可。

(請不要使用 AWS OpenSearch 來操作哦! 這次工作坊是以 Elasticsearch 的功能與操作為主。)

## 投影片

{% embed url="<https://speakerdeck.com/unclejoe/ru-he-zai-elasticsearch-shi-xian-min-jie-de-zi-liao-jian-mo-yu-guan-li-at-devopsdays-taipei-2023>" %}


# 工作坊實作內容

DevOpsDays Taipei 2023 工作坊 - 如何在 Elasticsearch 實現敏捷的資料建模與管理

## 事先定義好的 Data Model

### 明確的欄位定義

#### Create Index or Update Mapping

```
PUT my-index
{
  "mappings": {
    "properties": { 
      "name":     { "type": "text"    },
      "age":      { "type": "integer" },
      "birthday": { "type": "date"    },
      "email":    { "type": "keyword" },
      "salary":   { "type": "double"  }
    }
  }
}
```

```
PUT my-index/_mapping
{
  "properties": { 
    "name":     { "type": "text"    },
    "age":      { "type": "integer" },
    "birthday": { "type": "date"    },
    "email":    { "type": "keyword" },
    "salary":   { "type": "double"  }
  }
}
```

#### Sprint 1: 收集電商訂單 Logs

配合的後端工程師告訴你，這個 Sprint 會完成一個訂單系統，右方是訂單的資料結構，每筆訂單完成都會產生一筆 Log 記錄到 Elasticsearch，未來 Data Team 會要用這些訂單資料做數據分析。

```
{
  "currency": "EUR",
  "customer_first_name": "Eddie",
  "customer_gender": "MALE",
  "customer_id": 38,
  "customer_last_name": "Underwood",
  "order_date": "2023-10-09T09:28:48+00:00",
  "order_id": 584677,
  "products": [
    {
      "base_price": 11.99,
      "discount_percentage": 0,
      "quantity": 1,
      "manufacturer": "Elitelligence",
      "tax_amount": 0,
      "product_id": 6283,
      "category": "Men's Clothing",
      "sku": "ZO0549605496",
      "taxless_price": 11.99,
      "unit_discount_amount": 0,
      "min_price": 6.35,
      "_id": "sold_product_584677_6283",
      "discount_amount": 0,
      "created_on": "2016-12-26T09:28:48+00:00",
      "product_name": "Basic T-shirt - dark blue/white",
      "price": 11.99,
      "taxful_price": 11.99,
      "base_unit_price": 11.99
    }
  ],
  "total_quantity": 2,
  "total_unique_products": 2,
  "type": "order",
  "user": "eddie"
}
```

溝通好會將訂單 Logs 資料寫入到 devopsdays-taipei-2023-ec-order 的 index 之中。

```
# 我們要先將這個 Order 的 Data Model 進行定義。
PUT devopsdays-taipei-2023-ec-order
{
  "mappings": {
    "properties": {
      "currency":    { "type": "keyword" },
      "customer_first_name":    { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 }} },    
      "customer_gender":  { "type": "keyword"  }, 
      "customer_id":   { "type": "keyword"  },  
      "customer_last_name":    { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 }} },    
      "order_date":  { "type": "date"  }, 
      "order_id":   { "type": "keyword"  }, 
      "products":   { 
        "properties": {
          "base_price":   { "type": "double"  }, 
          "discount_percentage":   { "type": "float"  }, 
          "quantity":   { "type": "float"  }, 
          "manufacturer":   { "type": "keyword"  }, 
          "tax_amount":   { "type": "float"  }, 
          "product_id":   { "type": "keyword"  }, 
          "category":   { "type": "keyword"  }, 
          "sku":   { "type": "keyword"  }, 
          "taxless_price":   { "type": "double"  }, 
          "unit_discount_amount":   { "type": "float"  }, 
          "min_price":   { "type": "double"  },
          "_id":   { "type": "keyword"  },
          "discount_amount":   { "type": "float"  },
          "created_on":   { "type": "date"  },
          "product_name":   { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 }} }, 
          "price":   { "type": "double"  }, 
          "taxful_price":   { "type": "double"  }, 
          "base_unit_price":   { "type": "double"  } 
        }
      },
      "total_quantity": { "type": "float"  },  
      "total_unique_products": { "type": "float"  },  
      "type":   { "type": "keyword"  }, 
      "user":   { "type": "keyword"  }
    }
  }
}

# 查看 mapping
GET devopsdays-taipei-2023-ec-order/_mapping
```

**Testing Data for Sprint 1**

```
# 第一批資料進來了
PUT devopsdays-taipei-2023-ec-order/_bulk
{ "index" : { "_id" : "584677" } }
{"currency":"EUR","customer_first_name":"Eddie","customer_gender":"MALE","customer_id":38,"customer_last_name":"Underwood","customer_phone":"","order_date":"2023-10-09T09:28:48+00:00","order_id":584677,"products":[{"base_price":11.99,"discount_percentage":0,"quantity":1,"manufacturer":"Elitelligence","tax_amount":0,"product_id":6283,"category":"Men's Clothing","sku":"ZO0549605496","taxless_price":11.99,"unit_discount_amount":0,"min_price":6.35,"_id":"sold_product_584677_6283","discount_amount":0,"created_on":"2016-12-26T09:28:48+00:00","product_name":"Basic T-shirt - dark blue/white","price":12,"taxful_price":12,"base_unit_price":12},{"base_price":24.99,"discount_percentage":0,"quantity":1,"manufacturer":"Oceanavigations","tax_amount":0,"product_id":19400,"category":"Men's Clothing","sku":"ZO0299602996","taxless_price":25,"unit_discount_amount":0,"min_price":11.75,"_id":"sold_product_584677_19400","discount_amount":0,"created_on":"2016-12-26T09:28:48+00:00","product_name":"Sweatshirt - grey multicolor","price":25,"taxful_price":25,"base_unit_price":25}],"taxful_total_price":37,"taxless_total_price":37,"total_quantity":2,"total_unique_products":2,"type":"order","user":"eddie"}
{ "index" : { "_id" : "584021" } }
{"currency":"EUR","customer_first_name":"Mary","customer_gender":"FEMALE","customer_id":20,"customer_last_name":"Bailey","customer_phone":"","order_date":"2023-10-08T21:59:02+00:00","order_id":584021,"products":[{"base_price":24.99,"discount_percentage":0,"quantity":1,"manufacturer":"Champion Arts","tax_amount":0,"product_id":11238,"category":"Women's Clothing","sku":"ZO0489604896","taxless_price":24.99,"unit_discount_amount":0,"min_price":11.75,"_id":"sold_product_584021_11238","discount_amount":0,"created_on":"2016-12-25T21:59:02+00:00","product_name":"Denim dress - black denim","price":24.99,"taxful_price":24.99,"base_unit_price":24.99},{"base_price":28.99,"discount_percentage":0,"quantity":1,"manufacturer":"Pyramidustries","tax_amount":0,"product_id":20149,"category":"Women's Clothing","sku":"ZO0185501855","taxless_price":28.99,"unit_discount_amount":0,"min_price":15.65,"_id":"sold_product_584021_20149","discount_amount":0,"created_on":"2016-12-25T21:59:02+00:00","product_name":"Shorts - black","price":28.99,"taxful_price":28.99,"base_unit_price":28.99}],"taxful_total_price":53.98,"taxless_total_price":53.98,"total_quantity":2,"total_unique_products":2,"type":"order","user":"mary"}
{ "index" : { "_id" : "584058" } }
{"currency":"EUR","customer_first_name":"Gwen","customer_gender":"FEMALE","customer_id":26,"customer_last_name":"Butler","customer_phone":"","order_date":"2023-10-04T22:32:10+00:00","order_id":584058,"products":[{"base_price":99.99,"discount_percentage":0,"quantity":1,"manufacturer":"Low Tide Media","tax_amount":0,"product_id":22794,"category":"Women's Shoes","sku":"ZO0374603746","taxless_price":99.99,"unit_discount_amount":0,"min_price":46.01,"_id":"sold_product_584058_22794","discount_amount":0,"created_on":"2016-12-25T22:32:10+00:00","product_name":"Boots - Midnight Blue","price":99.99,"taxful_price":99.99,"base_unit_price":99.99},{"base_price":99.99,"discount_percentage":0,"quantity":1,"manufacturer":"Oceanavigations","tax_amount":0,"product_id":23386,"category":"Women's Clothing","sku":"ZO0272202722","taxless_price":99.99,"unit_discount_amount":0,"min_price":53.99,"_id":"sold_product_584058_23386","discount_amount":0,"created_on":"2016-12-25T22:32:10+00:00","product_name":"Short coat - white/black","price":99.99,"taxful_price":99.99,"base_unit_price":99.99}],"taxful_total_price":199.98,"taxless_total_price":199.98,"total_quantity":2,"total_unique_products":2,"type":"order","user":"gwen"}


# 工程師反應，查看資料有誤？
GET devopsdays-taipei-2023-ec-order/_search
{
  "query": {
    "range": {
      "taxful_total_price": {
        "lte": 53
      }
    }
  }
}


# 查看 mapping
GET devopsdays-taipei-2023-ec-order/_mapping


# 嘗試修復
PUT devopsdays-taipei-2023-ec-order/_mapping
{
  "properties": {
    "taxful_total_price": { "type": "double" },
    "taxless_total_price": { "type": "double" }
  }
}
```

#### Sprint 2: Index Mapping 的版本选代

```
# 刪除 index
DELETE devopsdays-taipei-2023-ec-order

# 建立 Index Template
# 包含 `_meta` 資料，以及 `aliases`。
PUT _index_template/devopsdays-taipei-2023-ec-order
{
  "index_patterns": [
    "devopsdays-taipei-2023-ec-order*"
  ],
  "template": {
    "aliases": {
      "devopsdays-taipei-2023-ec-order": {}
    },
    "mappings": {
      "_meta": {
        "version": 1,
        "version_creation_date": "2023-09-26"
      },
      "properties": {
        "currency":    { "type": "keyword" },
        "customer_first_name":    { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 }} },    
        "customer_gender":  { "type": "keyword"  }, 
        "customer_id":   { "type": "keyword"  },  
        "customer_last_name":    { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 }} },    
        "order_date":  { "type": "date"  }, 
        "order_id":   { "type": "keyword"  }, 
        "products":   { 
          "properties": {
            "base_price":   { "type": "double"  }, 
            "discount_percentage":   { "type": "float"  }, 
            "quantity":   { "type": "float"  }, 
            "manufacturer":   { "type": "keyword"  }, 
            "tax_amount":   { "type": "float"  }, 
            "product_id":   { "type": "keyword"  }, 
            "category":   { "type": "keyword"  }, 
            "sku":   { "type": "keyword"  }, 
            "taxless_price":   { "type": "double"  }, 
            "unit_discount_amount":   { "type": "float"  }, 
            "min_price":   { "type": "double"  },
            "_id":   { "type": "keyword"  },
            "discount_amount":   { "type": "float"  },
            "created_on":   { "type": "date"  },
            "product_name":   { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 }} }, 
            "price":   { "type": "double"  }, 
            "taxful_price":   { "type": "double"  }, 
            "base_unit_price":   { "type": "double"  } 
          }
        },
        "taxful_total_price": { "type": "double" },
        "taxless_total_price": { "type": "double" },
        "total_quantity": { "type": "float"  },  
        "total_unique_products": { "type": "float"  },  
        "type":   { "type": "keyword"  }, 
        "user":   { "type": "keyword"  }
      }
    }
  },
  "priority": 100,
  "version": 1,
  "_meta": {
    "description": "devopsdays workshop",
    "version_creation_date": "2023-09-26"
  }
}

# 這次我們將 Index 名字後面加上 `_v1`，用來區別這是第一版，重新將資料匯入。
PUT devopsdays-taipei-2023-ec-order_v1/_bulk
{ "index" : { "_id" : "584677" } }
{"currency":"EUR","customer_first_name":"Eddie","customer_gender":"MALE","customer_id":38,"customer_last_name":"Underwood","customer_phone":"","order_date":"2023-10-09T09:28:48+00:00","order_id":584677,"products":[{"base_price":11.99,"discount_percentage":0,"quantity":1,"manufacturer":"Elitelligence","tax_amount":0,"product_id":6283,"category":"Men's Clothing","sku":"ZO0549605496","taxless_price":11.99,"unit_discount_amount":0,"min_price":6.35,"_id":"sold_product_584677_6283","discount_amount":0,"created_on":"2016-12-26T09:28:48+00:00","product_name":"Basic T-shirt - dark blue/white","price":12,"taxful_price":12,"base_unit_price":12},{"base_price":24.99,"discount_percentage":0,"quantity":1,"manufacturer":"Oceanavigations","tax_amount":0,"product_id":19400,"category":"Men's Clothing","sku":"ZO0299602996","taxless_price":25,"unit_discount_amount":0,"min_price":11.75,"_id":"sold_product_584677_19400","discount_amount":0,"created_on":"2016-12-26T09:28:48+00:00","product_name":"Sweatshirt - grey multicolor","price":25,"taxful_price":25,"base_unit_price":25}],"taxful_total_price":37,"taxless_total_price":37,"total_quantity":2,"total_unique_products":2,"type":"order","user":"eddie"}
{ "index" : { "_id" : "584021" } }
{"currency":"EUR","customer_first_name":"Mary","customer_gender":"FEMALE","customer_id":20,"customer_last_name":"Bailey","customer_phone":"","order_date":"2023-10-08T21:59:02+00:00","order_id":584021,"products":[{"base_price":24.99,"discount_percentage":0,"quantity":1,"manufacturer":"Champion Arts","tax_amount":0,"product_id":11238,"category":"Women's Clothing","sku":"ZO0489604896","taxless_price":24.99,"unit_discount_amount":0,"min_price":11.75,"_id":"sold_product_584021_11238","discount_amount":0,"created_on":"2016-12-25T21:59:02+00:00","product_name":"Denim dress - black denim","price":24.99,"taxful_price":24.99,"base_unit_price":24.99},{"base_price":28.99,"discount_percentage":0,"quantity":1,"manufacturer":"Pyramidustries","tax_amount":0,"product_id":20149,"category":"Women's Clothing","sku":"ZO0185501855","taxless_price":28.99,"unit_discount_amount":0,"min_price":15.65,"_id":"sold_product_584021_20149","discount_amount":0,"created_on":"2016-12-25T21:59:02+00:00","product_name":"Shorts - black","price":28.99,"taxful_price":28.99,"base_unit_price":28.99}],"taxful_total_price":53.98,"taxless_total_price":53.98,"total_quantity":2,"total_unique_products":2,"type":"order","user":"mary"}
{ "index" : { "_id" : "584058" } }
{"currency":"EUR","customer_first_name":"Gwen","customer_gender":"FEMALE","customer_id":26,"customer_last_name":"Butler","customer_phone":"","order_date":"2023-10-04T22:32:10+00:00","order_id":584058,"products":[{"base_price":99.99,"discount_percentage":0,"quantity":1,"manufacturer":"Low Tide Media","tax_amount":0,"product_id":22794,"category":"Women's Shoes","sku":"ZO0374603746","taxless_price":99.99,"unit_discount_amount":0,"min_price":46.01,"_id":"sold_product_584058_22794","discount_amount":0,"created_on":"2016-12-25T22:32:10+00:00","product_name":"Boots - Midnight Blue","price":99.99,"taxful_price":99.99,"base_unit_price":99.99},{"base_price":99.99,"discount_percentage":0,"quantity":1,"manufacturer":"Oceanavigations","tax_amount":0,"product_id":23386,"category":"Women's Clothing","sku":"ZO0272202722","taxless_price":99.99,"unit_discount_amount":0,"min_price":53.99,"_id":"sold_product_584058_23386","discount_amount":0,"created_on":"2016-12-25T22:32:10+00:00","product_name":"Short coat - white/black","price":99.99,"taxful_price":99.99,"base_unit_price":99.99}],"taxful_total_price":199.98,"taxless_total_price":199.98,"total_quantity":2,"total_unique_products":2,"type":"order","user":"gwen"}


# 檢查 `taxful_total_price` 與 `taxless_total_price` 的型態
GET devopsdays-taipei-2023-ec-order/_mapping

# 重新查詢，現在已正常
GET devopsdays-taipei-2023-ec-order/_search
{
  "query": {
    "range": {
      "taxful_total_price": {
        "lte": 53
      }
    }
  }
}
```

### 動態新增欄位 Dynamic Mapping

#### Dynamic Template 範例

* 將 String 型態的欄位，定義成 `keyword`，而不是預設的 `text` + `keyword`。

```
PUT my-index
{
  "mappings": {
    "dynamic_templates": [
      {
        "keyword_strings": {
          "match_mapping_type": "string",
          "mapping": {
            "type": "keyword"
          }
        }
      }
    ]
  }
}

```

* 將 String 型態的欄位，且欄位名字是 `long_` 開頭，同時不是 `_text` 結尾，就定義成 `long` 型態。

```
PUT my-index/
{
  "mappings": {
    "dynamic_templates": [
      {
        "string_as_long": {
          "match_mapping_type": "string",
          "match":   "long_*",
          "unmatch": "*_text",
          "mapping": {
            "type": "long"
          }
        }
      }
    ]
  }
}

```

* 將 `name` 物件裡除了 `middle` 之外的所有欄位，都 copy 到 `full_name` 的欄位，並指定為 `text` 型態。

```
PUT my-index
{
  "mappings": {
    "dynamic_templates": [
      {
        "full_name": {
          "path_match":   "name.*",
          "path_unmatch": "*.middle",
          "mapping": {
            "type":       "text",
            "copy_to":    "full_name"
          }
        }
      }
    ]
  }
}

PUT my-index/_doc/1
{
  "name": {
    "first":  "John",
    "middle": "Winston",
    "last":   "Lennon"
  }
}
```

* 先針對字串欄位給予定義，再來針對所有非字串的欄位，關閉 `doc_values`。

```
PUT my-index
{
  "mappings": {
    "dynamic_templates": [
      {
        "named_analyzers": {
          "match_mapping_type": "string",
          "match": "*",
          "mapping": {
            "type": "text",
            "analyzer": "{name}"
          }
        }
      },
      {
        "no_doc_values": {
          "match_mapping_type":"*",
          "mapping": {
            "type": "{dynamic_type}",
            "doc_values": false
          }
        }
      }
    ]
  }
}

PUT my-index/_doc/1
{
  "english": "Some English text", 
  "count":   5 
}
```

#### Sprint 3: 建立適用的 Dynamic Template

```
# 定義好新的 Dynamic template 設定，同時移除不必要的 mapping 宣告，只剩下 `total_unique_products` 這個例外狀況需要特別宣告。
PUT _index_template/devopsdays-taipei-2023-ec-order
{
  "index_patterns": [
    "devopsdays-taipei-2023-ec-order*"
  ],
  "template": {
    "aliases": {
      "devopsdays-taipei-2023-ec-order": {}
    },
    "mappings": {
      "_meta": {
        "version": 1,
        "version_creation_date": "2023-09-26"
      },
      "dynamic_templates": [
        {
          "id": {
            "match": "*_id",
            "mapping": {
              "type": "keyword"
            }
          }
        },
        {
          "number_double": {
            "match": ["*_price", "price"],
            "mapping": {
              "type": "double"
            }
          }
        },
        {
          "number_float": {
            "match": ["*_amount", "*_percentage", "quantity", "*_quantity"],
            "mapping": {
              "type": "float"
            }
          }
        },
        {
          "date": {
            "match": ["*_date", "created_on"],
            "match_mapping_type": "date",
            "mapping": {
              "type": "date"
            }
          }
        },
        {
          "string_as_text": {
            "match": ["*_name"],
            "match_mapping_type": "string",
            "mapping": {
              "type": "text",
              "fields": {
                "keyword": { "type": "keyword", "ignore_above": 256 }
              }
            }
          }
        },
        {
          "string_as_keyword": {
            "match_mapping_type": "string",
            "mapping": {
              "type": "keyword"
            }
          }
        }
      ],
      "properties": {
        "total_unique_products": { "type": "float"  }
      }
    }
  },
  "priority": 100,
  "version": 1,
  "_meta": {
    "description": "devopsdays workshop",
    "version_creation_date": "2023-09-26"
  }
}

# 偷偷刪掉資料
DELETE devopsdays-taipei-2023-ec-order_v1

# 重新匯入資料測試一下
PUT devopsdays-taipei-2023-ec-order_v1/_bulk
{ "index" : { "_id" : "584677" } }
{"currency":"EUR","customer_first_name":"Eddie","customer_gender":"MALE","customer_id":38,"customer_last_name":"Underwood","customer_phone":"","order_date":"2023-10-09T09:28:48+00:00","order_id":584677,"products":[{"base_price":11.99,"discount_percentage":0,"quantity":1,"manufacturer":"Elitelligence","tax_amount":0,"product_id":6283,"category":"Men's Clothing","sku":"ZO0549605496","taxless_price":11.99,"unit_discount_amount":0,"min_price":6.35,"_id":"sold_product_584677_6283","discount_amount":0,"created_on":"2016-12-26T09:28:48+00:00","product_name":"Basic T-shirt - dark blue/white","price":12,"taxful_price":12,"base_unit_price":12},{"base_price":24.99,"discount_percentage":0,"quantity":1,"manufacturer":"Oceanavigations","tax_amount":0,"product_id":19400,"category":"Men's Clothing","sku":"ZO0299602996","taxless_price":25,"unit_discount_amount":0,"min_price":11.75,"_id":"sold_product_584677_19400","discount_amount":0,"created_on":"2016-12-26T09:28:48+00:00","product_name":"Sweatshirt - grey multicolor","price":25,"taxful_price":25,"base_unit_price":25}],"taxful_total_price":37,"taxless_total_price":37,"total_quantity":2,"total_unique_products":2,"type":"order","user":"eddie"}
{ "index" : { "_id" : "584021" } }
{"currency":"EUR","customer_first_name":"Mary","customer_gender":"FEMALE","customer_id":20,"customer_last_name":"Bailey","customer_phone":"","order_date":"2023-10-08T21:59:02+00:00","order_id":584021,"products":[{"base_price":24.99,"discount_percentage":0,"quantity":1,"manufacturer":"Champion Arts","tax_amount":0,"product_id":11238,"category":"Women's Clothing","sku":"ZO0489604896","taxless_price":24.99,"unit_discount_amount":0,"min_price":11.75,"_id":"sold_product_584021_11238","discount_amount":0,"created_on":"2016-12-25T21:59:02+00:00","product_name":"Denim dress - black denim","price":24.99,"taxful_price":24.99,"base_unit_price":24.99},{"base_price":28.99,"discount_percentage":0,"quantity":1,"manufacturer":"Pyramidustries","tax_amount":0,"product_id":20149,"category":"Women's Clothing","sku":"ZO0185501855","taxless_price":28.99,"unit_discount_amount":0,"min_price":15.65,"_id":"sold_product_584021_20149","discount_amount":0,"created_on":"2016-12-25T21:59:02+00:00","product_name":"Shorts - black","price":28.99,"taxful_price":28.99,"base_unit_price":28.99}],"taxful_total_price":53.98,"taxless_total_price":53.98,"total_quantity":2,"total_unique_products":2,"type":"order","user":"mary"}
{ "index" : { "_id" : "584058" } }
{"currency":"EUR","customer_first_name":"Gwen","customer_gender":"FEMALE","customer_id":26,"customer_last_name":"Butler","customer_phone":"","order_date":"2023-10-04T22:32:10+00:00","order_id":584058,"products":[{"base_price":99.99,"discount_percentage":0,"quantity":1,"manufacturer":"Low Tide Media","tax_amount":0,"product_id":22794,"category":"Women's Shoes","sku":"ZO0374603746","taxless_price":99.99,"unit_discount_amount":0,"min_price":46.01,"_id":"sold_product_584058_22794","discount_amount":0,"created_on":"2016-12-25T22:32:10+00:00","product_name":"Boots - Midnight Blue","price":99.99,"taxful_price":99.99,"base_unit_price":99.99},{"base_price":99.99,"discount_percentage":0,"quantity":1,"manufacturer":"Oceanavigations","tax_amount":0,"product_id":23386,"category":"Women's Clothing","sku":"ZO0272202722","taxless_price":99.99,"unit_discount_amount":0,"min_price":53.99,"_id":"sold_product_584058_23386","discount_amount":0,"created_on":"2016-12-25T22:32:10+00:00","product_name":"Short coat - white/black","price":99.99,"taxful_price":99.99,"base_unit_price":99.99}],"taxful_total_price":199.98,"taxless_total_price":199.98,"total_quantity":2,"total_unique_products":2,"type":"order","user":"gwen"}


# 檢查 mapping 的型態
GET devopsdays-taipei-2023-ec-order/_mapping


```

## 無法事先定義好 Data Model

### Runtime Field 的使用方式

#### 在 Searching 時指定 Runtime Field

* Searching 時指定 runtime field

```
# 定義 `@timestamp` 欄位為 date
PUT rf_test
{
  "mappings": {
    "properties": {
      "@timestamp": {
        "type": "date"
      }
    }
  }
}

# Import Test Data
POST rf_test/_bulk?refresh=true
{"index":{}}
{"@timestamp":1516729294000,"model_number":"QVKC92Q","measures":{"voltage":"5.2","start": "300","end":"8675309"}}
{"index":{}}
{"@timestamp":1516642894000,"model_number":"QVKC92Q","measures":{"voltage":"5.8","start": "300","end":"8675309"}}
{"index":{}}
{"@timestamp":1516556494000,"model_number":"QVKC92Q","measures":{"voltage":"5.1","start": "300","end":"8675309"}}
{"index":{}}
{"@timestamp":1516470094000,"model_number":"QVKC92Q","measures":{"voltage":"5.6","start": "300","end":"8675309"}}
{"index":{}}
{"@timestamp":1516383694000,"model_number":"HG537PU","measures":{"voltage":"4.2","start": "400","end":"8625309"}}
{"index":{}}
{"@timestamp":1516297294000,"model_number":"HG537PU","measures":{"voltage":"4.0","start": "400","end":"8625309"}}

GET rf_test

# 定義 `runtime_mapping` 並編寫從 `doc_value` 將資料拿出並進行處理的規則。
GET rf_test/_search
{
  "runtime_mappings": {
    "day_of_week": {
      "type": "keyword",
      "script": {
        "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
      }
    }
  },
  "fields": [
    "day_of_week"
  ]
}

# 定義 `runtime_mapping` 並編寫從 `_source` 將資料拿出並進行處理的規則。
GET rf_test/_search
{
  "runtime_mappings": {
    "mode_number": {
      "type": "keyword",
      "script": {
        "source": "emit(params._source.model_number)"
      }
    }
  },
  "fields": [
    "model_number"
  ]
}

```

#### 在 Mapping 中定義 Runtime Field

```
# 使用 update mapping API 將 runtime fields 定義進 index mapping 之中
PUT rf_test/_mapping
{
  "runtime": {
    "day_of_week": {
      "type": "keyword",
      "script": {
        "source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
      }
    }
  },
  "properties": {
    "@timestamp": {
      "type": "date"
    }
  }
}

# 使用 _search 將 fields 查出
GET rf_test/_search?docvalue_fields=day_of_week

```

#### Sprint 4: 事先所定義的欄位不足

```
# 早期的做法
GET devopsdays-taipei-2023-ec-order/_search
{
  "query": {
    "script": {
      "script": "doc['order_date'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT) == 'Wednesday'"
    }
  }
}

GET devopsdays-taipei-2023-ec-order/_search
{
  "size": 0, 
  "aggs": {
    "day_of_week": {
      "terms": {
        "script": {
          "source": "doc['order_date'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT)"
        }
      }
    }
  }
}

# 現在可以定義成 Runtime Field
GET devopsdays-taipei-2023-ec-order/_search
{
  "runtime_mappings": {
    "day_of_week": {
      "type": "keyword",
      "script": "emit(doc['order_date'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
    }
  },
  "fields": [
    "day_of_week"
  ]
}

# Runtime Field 可以使用在 query
GET devopsdays-taipei-2023-ec-order/_search
{
  "runtime_mappings": {
    "day_of_week": {
      "type": "keyword",
      "script": "emit(doc['order_date'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
    }
  },
  "fields": [
    "day_of_week"
  ],
  "query": {
    "term": {
      "day_of_week": "Wednesday"
    }
  }
}

# Runtime Field 也可以使用在 aggregation
GET devopsdays-taipei-2023-ec-order/_search
{
  "runtime_mappings": {
    "day_of_week": {
      "type": "keyword",
      "script": "emit(doc['order_date'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
    }
  },
  "fields": [
    "day_of_week"
  ],
  "size": 0,
  "aggs": {
    "day_of_week": {
      "terms": {
        "field": "day_of_week"
      }
    }
  }
}

# 確認好用法用，我們可以將 Runtime Field 定義在 Mapping 中，就不用每次 Search 時都要宣告
PUT devopsdays-taipei-2023-ec-order/_mapping
{
  "runtime": {
    "day_of_week": {
      "type": "keyword",
      "script": {
        "source": "emit(doc['order_date'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
      }
    }
  }
}

# 記得也要修改 Index Template，並且讓 Version 進版。
PUT _index_template/devopsdays-taipei-2023-ec-order
{
  "index_patterns": [
    "devopsdays-taipei-2023-ec-order*"
  ],
  "template": {
    "aliases": {
      "devopsdays-taipei-2023-ec-order": {}
    },
    "mappings": {
      "_meta": {
        "version": 2,
        "version_creation_date": "2023-09-26"
      },
      "dynamic_templates": [
        {
          "id": {
            "match": "*_id",
            "mapping": {
              "type": "keyword"
            }
          }
        },
        {
          "number_double": {
            "match": ["*_price", "price"],
            "mapping": {
              "type": "double"
            }
          }
        },
        {
          "number_float": {
            "match": ["*_amount", "*_percentage", "quantity", "*_quantity"],
            "mapping": {
              "type": "float"
            }
          }
        },
        {
          "date": {
            "match": ["*_date", "created_on"],
            "match_mapping_type": "date",
            "mapping": {
              "type": "date"
            }
          }
        },
        {
          "string_as_text": {
            "match": ["*_name"],
            "match_mapping_type": "string",
            "mapping": {
              "type": "text",
              "fields": {
                "keyword": { "type": "keyword", "ignore_above": 256 }
              }
            }
          }
        },
        {
          "string_as_keyword": {
            "match_mapping_type": "string",
            "mapping": {
              "type": "keyword"
            }
          }
        }
      ],
      "runtime": {
        "day_of_week": {
          "type": "keyword",
          "script": {
            "source": "emit(doc['order_date'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
          }
        }
      },
      "properties": {
        "total_unique_products": { "type": "float"  }
      }
    }
  },
  "priority": 100,
  "version": 2,
  "_meta": {
    "description": "devopsdays workshop",
    "version_creation_date": "2023-09-26"
  }
}
```

#### Sprint 5: 當我們想將 Runtime Field 正式定義

```
# 在 Index Template 增加 `day_of_week` 的 schema on-write 欄位宣告，並且進版
PUT _index_template/devopsdays-taipei-2023-ec-order
{
  "index_patterns": [
    "devopsdays-taipei-2023-ec-order*"
  ],
  "template": {
    "aliases": {
      "devopsdays-taipei-2023-ec-order": {}
    },
    "mappings": {
      "_meta": {
        "version": 3,
        "version_creation_date": "2023-09-26"
      },
      "dynamic_templates": [
        {
          "id": {
            "match": "*_id",
            "mapping": {
              "type": "keyword"
            }
          }
        },
        {
          "number_double": {
            "match": ["*_price", "price"],
            "mapping": {
              "type": "double"
            }
          }
        },
        {
          "number_float": {
            "match": ["*_amount", "*_percentage", "quantity", "*_quantity"],
            "mapping": {
              "type": "float"
            }
          }
        },
        {
          "date": {
            "match": ["*_date", "created_on"],
            "match_mapping_type": "date",
            "mapping": {
              "type": "date"
            }
          }
        },
        {
          "string_as_text": {
            "match": ["*_name"],
            "match_mapping_type": "string",
            "mapping": {
              "type": "text",
              "fields": {
                "keyword": { "type": "keyword", "ignore_above": 256 }
              }
            }
          }
        },
        {
          "string_as_keyword": {
            "match_mapping_type": "string",
            "mapping": {
              "type": "keyword"
            }
          }
        }
      ],
      "runtime": {
        "day_of_week": {
          "type": "keyword",
          "script": {
            "source": "emit(doc['order_date'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
          }
        }
      },
      "properties": {
        "total_unique_products": { "type": "float"  },
        "order_date": { "type": "date" },
        "day_of_week": {
          "type": "keyword",
          "on_script_error": "fail",
          "script": {
            "source": "emit(doc['order_date'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
          }
        }
      }
    }
  },
  "priority": 100,
  "version": 3,
  "_meta": {
    "description": "devopsdays workshop",
    "version_creation_date": "2023-09-26"
  }
}


# 將新的資料寫入到新的 index v2 之中
POST devopsdays-taipei-2023-ec-order_v2/_bulk?refresh=true
{ "index" : { "_id" : "584093" } }
{"currency":"EUR","customer_first_name":"Diane","customer_gender":"FEMALE","customer_id":22,"customer_last_name":"Chandler","customer_phone":"","order_date":"2023-10-08T22:58:05+00:00","order_id":584093,"products":[{"base_price":74.99,"discount_percentage":0,"quantity":1,"manufacturer":"Primemaster","tax_amount":0,"product_id":12304,"category":"Women's Shoes","sku":"ZO0360303603","taxless_price":74.99,"unit_discount_amount":0,"min_price":34.5,"_id":"sold_product_584093_12304","discount_amount":0,"created_on":"2016-12-25T22:58:05+00:00","product_name":"High heeled sandals - argento","price":74.99,"taxful_price":74.99,"base_unit_price":74.99},{"base_price":99.99,"discount_percentage":0,"quantity":1,"manufacturer":"Oceanavigations","tax_amount":0,"product_id":19587,"category":"Women's Clothing","sku":"ZO0272002720","taxless_price":99.99,"unit_discount_amount":0,"min_price":47.01,"_id":"sold_product_584093_19587","discount_amount":0,"created_on":"2016-12-25T22:58:05+00:00","product_name":"Classic coat - black","price":99.99,"taxful_price":99.99,"base_unit_price":99.99}],"taxful_total_price":174.98,"taxless_total_price":174.98,"total_quantity":2,"total_unique_products":2,"type":"order","user":"diane"}
{ "index" : { "_id" : "574916" } }
{"currency":"EUR","customer_first_name":"Eddie","customer_gender":"MALE","customer_id":38,"customer_last_name":"Weber","customer_phone":"","order_date":"2023-10-11T03:48:58+00:00","order_id":574916,"products":[{"base_price":59.99,"discount_percentage":0,"quantity":1,"manufacturer":"Elitelligence","tax_amount":0,"product_id":11262,"category":"Men's Clothing","sku":"ZO0542505425","taxless_price":59.99,"unit_discount_amount":0,"min_price":28.2,"_id":"sold_product_574916_11262","discount_amount":0,"created_on":"2016-12-19T03:48:58+00:00","product_name":"Winter jacket - black","price":59.99,"taxful_price":59.99,"base_unit_price":59.99},{"base_price":20.99,"discount_percentage":0,"quantity":1,"manufacturer":"Elitelligence","tax_amount":0,"product_id":15713,"category":"Men's Accessories","sku":"ZO0601306013","taxless_price":20.99,"unit_discount_amount":0,"min_price":10.7,"_id":"sold_product_574916_15713","discount_amount":0,"created_on":"2016-12-19T03:48:58+00:00","product_name":"Watch - green","price":20.99,"taxful_price":20.99,"base_unit_price":20.99}],"taxful_total_price":80.98,"taxless_total_price":80.98,"total_quantity":2,"total_unique_products":2,"type":"order","user":"eddie"}
{ "index" : { "_id" : "574586" } }
{"currency":"EUR","customer_first_name":"Diane","customer_gender":"FEMALE","customer_id":22,"customer_last_name":"Goodwin","customer_phone":"","order_date":"2023-10-04T21:44:38+00:00","order_id":574586,"products":[{"base_price":59.99,"discount_percentage":0,"quantity":1,"manufacturer":"Low Tide Media","tax_amount":0,"product_id":5419,"category":"Women's Shoes","sku":"ZO0376303763","taxless_price":59.99,"unit_discount_amount":0,"min_price":31.79,"_id":"sold_product_574586_5419","discount_amount":0,"created_on":"2016-12-18T21:44:38+00:00","product_name":"Winter boots - brown","price":59.99,"taxful_price":59.99,"base_unit_price":59.99},{"base_price":11.99,"discount_percentage":0,"quantity":1,"manufacturer":"Pyramidustries","tax_amount":0,"product_id":19325,"category":"Women's Clothing","sku":"ZO0212402124","taxless_price":11.99,"unit_discount_amount":0,"min_price":6.47,"_id":"sold_product_574586_19325","discount_amount":0,"created_on":"2016-12-18T21:44:38+00:00","product_name":"Shorts - dark blue/pink/dark green","price":11.99,"taxful_price":11.99,"base_unit_price":11.99}],"taxful_total_price":71.98,"taxless_total_price":71.98,"total_quantity":2,"total_unique_products":2,"type":"order","user":"diane"}

# 同時查詢 v1 與 v2 的資料，使用端沒有任何影響
GET devopsdays-taipei-2023-ec-order/_search
{
  "query": {
    "term": {
      "day_of_week": "Wednesday"
    }
  }
}

# Aggregation 也一樣，使用端沒有任何影響
GET devopsdays-taipei-2023-ec-order/_search
{
  "size": 0,
  "aggs": {
    "day_of_week": {
      "terms": {
        "field": "day_of_week"
      }
    }
  }
}

```

### Async Search

```
# 我們嘗試使用 _async_search 但是發現執行太快，不會觸發 async_search
POST devopsdays-taipei-2023-ec-order/_async_search
{
  "size": 0, 
  "aggs": {
    "test": {
      "date_histogram": {
        "field": "order_date",
        "fixed_interval": "2d"
      }
    }
  }
}

# 強制執行 `wait_for_completion_timeout=0` 讓查詢立刻先返回，即出現 async_search id
POST devopsdays-taipei-2023-ec-order/_async_search?wait_for_completion_timeout=0
{
  "size": 0, 
  "aggs": {
    "test": {
      "date_histogram": {
        "field": "order_date",
        "fixed_interval": "2d"
      }
    }
  }
}

# 使用 id 去查詢執行狀態
GET _async_search/status/<responsed_async_search_id>

# 再使用 id 去取得結果
GET _async_search/<responsed_async_search_id>

# 也可以使用 `keep_on_completion`
POST devopsdays-taipei-2023-ec-order/_async_search?keep_on_completion
{
  "size": 0, 
  "aggs": {
    "test": {
      "date_histogram": {
        "field": "order_date",
        "fixed_interval": "2d"
      }
    }
  }
}

# 用完記得要刪除，不然會留 5 天
DELETE _async_search/<responsed_async_search_id>


# 可使用 .async_search 觀察 docs 數量的變化
GET .async-search/_search?size=0
```

## Data Model 的事後修改

### Update by Query

```
# 加上 `joe` 的 tag
POST kibana_sample_data_logs/_update_by_query
{
  "script": {
    "source": "ctx._source.tags.add(params.tag)",
    "lang": "painless",
    "params": {
      "tag": "joe"
    }
  }, 
  "query": {
    "match_all": {}
  }
}

# 移掉 `joe 的 tag，使用 `wait_for_completion=false` 成為 background task
POST kibana_sample_data_logs/_update_by_query?wait_for_completion=false
{
  "script": {
    "source": "if (ctx._source.tags.contains(params.tag)) { ctx._source.tags.remove(ctx._source.tags.indexOf(params.tag)) }",
    "lang": "painless",
    "params": {
      "tag": "joe"
    }
  }, 
  "query": {
    "match_all": {}
  }
}

# 取得 task 結果
GET /_tasks/<task_id>

```

#### Sprint 6: 將舊資料也轉成 Schema on Write

```
# 針對 `_v1` 的 index，加上 `day_of_week` 的 explicit mapping 宣告
PUT devopsdays-taipei-2023-ec-order_v1/_mapping
{
  "properties": {
    "day_of_week": {
      "type": "keyword",
      "on_script_error": "fail",
      "script": {
        "source": "emit(doc['order_date'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
      }
    }
  }
}

# 接著使用 _update_by_query 將資料重新讀出來並再 indexing 一遍。
POST devopsdays-taipei-2023-ec-order_v1/_update_by_query
{
  "query": {
    "match_all": {}
  }
}

# 刪除先前定義的 runtime field - `day_of_week`
PUT devopsdays-taipei-2023-ec-order_v1/_mapping
{
  "runtime": {
    "day_of_week": null
  }
}

# 我們可以看到最後的 mapping 定義
GET devopsdays-taipei-2023-ec-order_v1/_mapping

# 同時也可以看到查詢出來的結果，是包含 `day_of_week` 欄位的
GET devopsdays-taipei-2023-ec-order_v1/_search?docvalue_fields=day_of_week

```


# Elastic Observability 實作體驗坊 @ DevOpsDays 2022

**這是在 DevOpsDays 2022 舉辦的工作坊內容** [**https://devopsdays.tw/2022/workshops**](https://devopsdays.tw/2022/workshops)

歡迎到 Facebook 粉絲頁進行交流。

<figure><img src="/files/lS4gAypbvsJaBum0mJvi" alt=""><figcaption><p>DevOpsDays Taipei 2022 工作坊滿意度調查結果</p></figcaption></figure>

## 簡介

**Elastic Observability 實作體驗營**

聽到 Observability 這個詞這麼久了，大家都是如何實踐的呢？

這次喬叔將透過一個模擬情境，帶大家使用 Elastic Stack 收集一個軟體產品的 Logs, Metrics 及 Traces 等資訊，從情境中所定義的 SLI (Service Level Indicator) 與 SLO (Service Level Objective) 的配置，了解如何提升系統運作時的可觀測性 (Observability)，並且進一步透過 Elastic Observability 進行例外狀況的盤查。

**預期觀眾收穫：**

1. 學習 Observability 的基本概念。
2. 初探 Elastic Observability 所提供的功能。
3. 如何使用 Elastic Observability 來進行問題的盤查。

請閱讀下一頁的 **行前準備** 與 **工作坊實作內容** 了解實作細節。

## 投影片

{% embed url="<https://speakerdeck.com/unclejoe/elastic-observability-ti-yan-gong-zuo-fang-at-devopsdays-taipei-2022>" %}




---

[Next Page](/llms-full.txt/1)

