將 AWS CloudWatch Alarms 發佈到 Slack

Luyo
verybuy-dev
Published in
19 min readAug 10, 2017

一直以來我們的 CloudWatch 的 alarms 都是直接用 AWS 預設的介面:透過 SNS 發送通知到 email。這兩天 email 被某個 alarm 洗了一輪後,突然想到如果可以直接推發到 Slack 的話好像會蠻方便的,一來不用管理一堆 email 群組,二來出了什麼問題別人也看得到,甚至要直接轉發到其他 Slack channel 通知相關的人也很方便,想起來還實用的,馬上 google “cloudwatch to slack”,找到這篇官方部落格的文章 New — Slack Integration Blueprints for AWS Lambda,就馬上來實作看看吧!

1 — 新增 Lambda function

1-1 選擇 blueprint

進入 AWS Lambda 的 console 介面後,找到 Blueprints 的搜尋框,輸入 slack

在 Blueprints 的搜尋框中輸入 slack

按下 enter 後會搜尋出相關的 Blueprints,截至目前 (2017/08/09) 以 cloudwatch-alarm-to- 開頭的共有三個:

  • cloudwatch-alarm-to-slack (nodejs)
  • cloudwatch-alarm-to-slack-python (python2.7)
  • cloudwatch-alarm-to-slack-python3 (python3.6)

nodejs 跟 python 對我來說都是半生不熟,就先選 cloudwatch-alarm-to-slack-python3 這支吧,反正應該是不太會需要改動到程式碼,差別不大吧?

NOTE:先爆雷,目前這支 python3.6 的 function 有個小 bug,需要手動修改程式碼,後面會講方法。不想修的話請直接改選 Nodejs 或 Python2.7 版本的 blueprint

選擇 cloudwatch-alarm-to-slack-python3

1–2 新增 SNS topic (optional)

接下來這個步驟是要選擇 trigger,下拉選單中會看到現有的 SNS topic,但我決定開一個新的 SNS topic 來試做,如果你想用現有的 topic 可以直接跳過本節。

先另外開啟一個 SNS console 的網頁,點擊 Create topic,輸入 Topic nameDisplay name 後按下 Create topic 按鈕即可,如圖:

在 SNS console 的 SNS dashboard 中點擊 Create topic
輸入 Topic name 後點擊 Create topic

新增成功後先不要關掉這個視窗,等會兒測試時還會用到。

1–3 設定 Trigger

接下來回到 Lambda console,因為剛 topic 是剛剛才建立的,下拉選單自然不會有,重新整理頁面就會出現了。選好之後往下看,有個 Enable trigger 的選項,上面建議測試期間先不勾選,那就先不勾,按下 Next

在 Lambda 的 Create function 介面中選擇欲整合的 SNS topic

1–4 輸入 function 名稱及資訊

接下來要來設定 function 本身了,先填 function 名稱:

填入自訂的 function 名稱

Description 的部分我是直接用預設的,要不要改就看個人囉。

1–5 新增 Slack 的 WebHook

再拉下拉就會看到 python 的程式碼區塊了,從第二行開始的註解是很重要的提示,告訴我們如何設定 Slack 的 webhook 及如何設定這個 Lambda function 的環境變數。

提示分成兩段,第一段如下:

Follow these steps to configure the webhook in Slack:

1. Navigate to https://<your-team-domain>.slack.com/services/new

2. Search for and select “Incoming WebHooks”.

3. Choose the default channel where messages will be sent and click “Add Incoming WebHooks Integration”.

4. Copy the webhook URL from the setup instructions and use it in the next section.

一步一步照著做做看:

  1. 開啟 https://<your-team-domain>.slack.com/services/new (目前這個網址會被重導至 https://<your-team-domain>.slack.com/apps)
  2. 在中間搜尋框中輸入 webhook,並點擊 Incoming WebHooks
到 Slack 的 app 首頁,搜尋到 Incoming WebHooks 並點擊進去

3. 點擊左側的 Add Configuration

點擊 Add Configuration

4. 選擇要把訊息同步到哪個 channel 後按下 Add Incoming WebHooks integration 按鈕,就新增完成了。把頁面往下拉,找到 Webhook URL 的區塊,連擊 Copy URL

點擊 Copy URL 將 Webhook URL 複製起來

繼續往下拉的話還有一些選項,例如我把名稱改自訂為 “AWS-Bot”,並傳了一張圖上去當頭像,這些部分就看自己要不要設定,要的話設定完後記得拉到最底下按 Save Settings 按鈕。

到這邊我們就拿到了 Slack 的 WebHook URL。接下來就是要把這個 WebHook 回去告訴 Lambda function。

1–6 新增 KMS Key

回到剛剛 Lambda function 的 python 程式碼註解中的第二段提示:

To encrypt your secrets use the following steps:

1. Create or use an existing KMS Key — http://docs.aws.amazon.com/kms/latest/developerguide/create-keys.html

2. Click the “Enable Encryption Helpers” checkbox

3. Paste <SLACK_CHANNEL> into the slackChannel environment variable

Note: The Slack channel does not contain private info, so do NOT click encrypt

4. Paste <SLACK_HOOK_URL> into the kmsEncryptedHookUrl environment variable and click encrypt

Note: You must exclude the protocol from the URL (e.g. “hooks.slack.com/services/abc123”).

5. Give your function’s role permission for the kms:Decrypt action.

Example:

(下略)

首先按照 1. 的指示來打開連結 http://docs.aws.amazon.com/kms/latest/developerguide/create-keys.html ,找到 console 網址 https://console.aws.amazon.com/iam/home#encryptionKeys ,點進去後就會進入 IAM 介面裡的 Encryption keys 管理介面。

這裡很重要的一點是在按下 Create key 之前請務必記得選擇跟你的 Lambda function 相同的 Region!否則你回到 Lambda 那邊會找不到這組 key。

按下 Create key 之前記得先選 Region

再來按下 Create key,輸入自訂的 key 名稱:

輸入 key 的自訂資訊

接下來 tags 可以不用設定,直接再下一步,勾選哪個 IAM 可以管理這個 key。選好後再按下一步,勾選哪個 IAM 可以使用這組 key,但因為 Lambda function 可以幫我們自動產生 IAM role,所以這裡就先不選。再來就一路按下一步直到 Finish 即可。

1–7 設定 Encryption key

回到 Lambda function 這邊,拉到程式碼區塊下面有個 Enable encryption helpers 的核取方塊,將它打勾,會出現下拉選單,找到剛剛新增的 KMS key 並點擊選取 (若找不到就先重新整理頁面):

回到 Lambda console 點擊 Enable encryption helpers,選擇剛剛在 IAM 新產生的 key

1–8 設定環境變數 (Environment variables)

再來要填入 slackChannelkmsEncryptedHookUrl 這兩個環境變數。兩個要點:

  1. 切記 slackChannel 的內容開頭要有井字號 #

2. kmsEncryptedHookUrl 填入 Slack 那邊複製下來的 WebHook URL,並且務必要把開頭的 https:// 這段去掉。

填好之後,請在 kmsEncryptedHookUrl 那行按下 Encrypt 按鈕:

貼上 WebHook URL 並刪掉開頭的 “https://” 後按下 Encrypt

按下之後應該會變成這樣:

按下 Encrypt 之後 Webhook URL 會被加密

注意在 1–7 設定的 Encryption key 一定要用自己去 IAM console 生成的 key,如果偷懶用系統內建的 aws/lambda 這個 key,那麼按下 Encrypt 鈕按後是會加密失敗的,我幫大家試過了 (我就是那個偷懶的人)。

1–9 設定 IAM role

接下來要設定 IAM role。如果想要用已經有的 IAM role 就在下拉選單裡找,但這樣會需要再自己手動設定權限,比較麻煩,所以建議選預設的 Create new role from template(s),系統會自動幫你處理好權限的事情,只要自己填上 Role name 就可以了:

填入自訂的 Role name

下面的 Policy templates 如果沒有其他需求也不用再多選。

1–10 新增 function 完成

function 這部分終於設定完了,按下 Next 接著下一步驟的 Create function 就完成 function 的新增了。新增成功後的頁面長這樣:

Lambda function 新增完成後畫面

2 — 測試 function

2–1 以 SNS template 做測試

接下來測試 function 能不能用,點擊右上角 Test 後,會出現 Input test event 的 templates 可供選擇,下拉選單裡可以找到 SNS 的選項:

在 Input test event 中找到 SNS 的 template

點下去後會出現 SNS 的 template,按下 Save and test 後會出現失敗的畫面及錯誤訊息:

點擊 Save and test 按鈕後出現失敗畫面及錯誤訊息

仔細看一下,發現是因為 Message 的格式跟這個 Lambda function 預期的不一樣,所以 JSON decode 失敗。但我們目前不知道實際上 CloudWatch alaram 丟給 SNS 的訊息格式到底長怎麼,所以就先不管它了。

2–2 實際發送 SNS message 做測試

若要做進一步的測試,就只能到 SNS console 實際送 notification 出來,才知道 function 有沒有正常運作。

要接收 SNS 的訊息,應該就要先將 trigger 啟用吧?

往下拉找到 Trigger 的標籤頁,再找到 Enable 按鈕並點擊:

在 Trigger 頁籤中點擊 Enable 按鈕

出現對話框之後再點擊 Enable 按鈕就可以啟用這個 Trigger 了。

回去之前打開的 SNS console 頁面,在 Topics 中找到剛才新增的 CloudWatch-to-Slack,將它左邊的核取方塊打勾,然後按下上方的 Publish to topic 按鈕:

在 SNS console 中選取 SNS topic 並點擊 Publish to topic

接著隨便填入 SubjectMessage 然後按下 Publish message

輸入 Message 並按下 Publish message

滿心期待 Slack 會推播成功,等了好一陣子都沒反應,期待落空了。

NOTE:如果你一開始選的是 Nodejs 或 Python2.7 版本,這邊應該就要有反應了請直接跳到 “5 — 確認訊息格式”

試了一些方法都沒有用,Slack 依然沒反應。到底哪裡做錯了,怎麼會失敗呢?

先考慮是不是 IAM 的權限問題吧,打開 IAM console 的 Roles 頁籤,找到 CloudWatch-to-Slack 這個 role,看了一下 policy,都是 Lambda 自動生成的,也有 kms:Decrypt 的權限,這部分應該不會有什麼問題。

再來檢查 Encryption keys 裡的 CloudWatch-to-Slack 這個 key (記得選 Region) 的權限是否正常,發現 Key Users 沒有加入任何 IAM,按 Add 把 /service-role/CloudWatch-to-Slack 這個 role 加進去,再次測試,還是不行,崩潰。

3 — 檢查 Slack WebHook

先來確認一下 Slack 的 Webhook 是不是正常的好了。開啟 Chrome 的外掛小工具 RESTED,貼上 Webhook URL,Method 選擇 POST,並輸入 JSON 參數,按下 Send request 按鈕,結果馬上收到 Slack 推播通知了:

$用 RESTED 套件確認 Slack WebHook 可正常運作

好,確認不是 Slack 的問題,那應該就是 AWS 這邊的問題了。

4 — 重新建立 function — Nodejs 版本

會不會是 cloudwatch-alarm-to-slack-python3 這支 function 有問題?我們還有另外兩個選擇,就重新開一支 cloudwatch-alarm-to-slack 這個 nodejs 的版本來試試看吧。

基本上流程都跟前面一模一樣,只有 function 的 NameRole name 我換成了另一個名字 test-slack-nodejs-1;然後這次我一開始就勾選 Enable trigger 這個選項。

新增完成後按下 Test 按鈕,依然是噴錯。沒關係我們直接到 SNS 去送訊息好了,同樣做之前發送 Message 的動作,勾選 CloudWatch-to-Slack 然後按 Publish to topic,輸入跟之前一樣的訊息:

按下 Publish Message,Slack 居然成功收到訊息了!

Slack 成功接收訊息但格式有問題

雖然訊息內容很怪,但至少是終於有反應了。

5 — 確認訊息格式

那麼該怎麼讓訊息內容變正常呢?研究一下程式碼內容,會找到這段:

const message = JSON.parse(event.Records[0].Sns.Message);const alarmName = message.AlarmName;
//var oldState = message.OldStateValue;
const newState = message.NewStateValue;
const reason = message.NewStateReason;
const slackMessage = {
channel: slackChannel,
text: `${alarmName} state is now ${newState}: ${reason}`,
};

看起來是訊息必須是特定的 JSON 格式,需要有 AlarmName , NewStateValue , NewStateReason 這幾個 key。那就生一個新的訊息:

{
"AlarmName": "地獄警鐘",
"NewStateValue": "我好餓",
"NewStateReason": "五百年沒吃酒肉了"
}

然後貼上 SNS 重新發布一次:

貼上 JSON 訊息格式

成功收到正常格式的訊息囉!

Slack 成功接收格式正確的訊息

確定了訊息的格式之後,我們就可以回到 Lambda function 這邊更新 Test 內的訊息,點擊 Action 裡的 Configure test event 按鈕:

回到 Lambda console 點擊 Action 中的 Configure test event

把 Message 這個 key 裡的內容換成 escape 過的 JSON 字串,然後按下 Save and test

更新 Message 的值為 escape 過的 JSON 字串

測試成功通過了!

Nodejs 版本測試成功

既然 Nodejs 版本是正常的,那麼我們就可以把矛頭指向 python 版本的 Lambda function 了。python3 的版本到底出了什麼問題?來開一個 python2.7 的版本 cloudwatch-alarm-to-slack-python 看看,結果也是正常可運作的。

6 — 修正 cloudwatch-alarm-to-slack-python3 的問題

NOTE:若一開始就選擇 Nodejs 或 Python2.6 版本可跳過本章直接到 “7 —更新 CloudWatch Alarms 設定“。

現在可以很確定是 cloudwatch-alarm-to-slack-python3 的問題了。雖然我們大可直接用 Nodejs 或 Python2.7 的版本,但我還是想知道為什麼 python3 的版本不能用。

回到最早新增的這個 function,同前面步驟,點擊 Action 裡的 Configure test event 按鈕,把正確的 JSON 字串貼進 Message,然後送出,依然產生錯誤訊息:

Python3.6 版本的測試失敗錯誤訊息

但這次的錯誤訊息跟剛才已經不一樣了,因為我們已經找出可用的 JSON 格式,而這次顯然是 POST data 有問題。直接複製貼上這段錯誤訊息到 google 上,找到這篇文章,似乎是 python3.2 之後的版本會遇到這個問題。先在程式碼中找出有問題的這幾行:

slack_message = {
'channel': SLACK_CHANNEL,
'text': "%s state is now %s: %s" % (alarm_name, new_state, reason)
}
req = Request(HOOK_URL, json.dumps(slack_message))
try:
response = urlopen(req)

按照下面的解答,在 Request()的第二個參數裡補上一段 .encode() 的處理:

req = Request(HOOK_URL, json.dumps(slack_message).encode('utf-8'))

再拉回上面按下 Save and test 按鈕,測試成功,同時也收到訊息推播囉!

Python3.6 版本測試成功

7 — 更新 CloudWatch Alarms 設定

最後的最後,我們總算可以回到 CloudWatch 來更新 Alarms 的通知設定了!

NOTE:如果你在 1–2 選擇使用既有的 SNS topic,理論上就已經全部設定完成可以運作了,不需要再做本章的修改。

先勾選要同步至 Slack 的 alarm,然後按下 Modify 按鈕:

選擇欲發布到 Slack 的 Alaram

接著會跳出編輯框,在底下的 Actions 裡的 Send notification to 這個下拉選單,找到剛剛之前新增的 SNS topic — CloudWatch-to-Slack,選好後按下 Save Changes:

選擇新的 SNS topic

之後只要警鈴響起,就會將訊息同步到 Slack 囉!

Alarm 成功發布到 Slack

8 — 結語

兩點心得及一件本文未提及的事情:

  1. CloudWatch Alarms — SNS — Lambda — Slack 這一串說穿了雖然是只要設定一個 Webhook 而已,但實作上還真是處處有陷阱啊。這也是 AWS 教人又愛又恨之處:功能都很齊全,但使用上需要花不少學習成本。希望這篇記錄可以幫助有需要的人節省一些寶貴的時間。
  2. 想要追新就是要付出代價。這次一開始為了追新而選了有瑕疵的 cloudwatch-alarm-to-slack-python3 就是血淋淋的教訓。
  3. 一開始提到的這篇文章 New — Slack Integration Blueprints for AWS Lambda 還有後續:你可以進一步整合 AWS API Gateway 來實踐 “ChatOps” 的概念,只要在 Slack 的聊天室裡下指令,就可以讓 AWS 去做對應的動作!這功能實在有點酷炫,但因為目前還沒有這個需求就不實作了,以後再說,有興趣的人請自行研究 Slack 的 Slash Commands 吧。

--

--