這篇就是要講 AWS RDS 那個「七天自動復活」的都市傳說。身為工程師的你,一定有過類似的體驗:月底看到帳單的瞬間,心裡的 OS 大概就是—「蛤?我明明關掉了啊,怎麼又在跑?這個月又爆預算了啊!」
AWS RDS 本來是個不錯的服務,畢竟 全托管,不必自己顧 patch、備份、Failover,還幫你把 DBA 變成無用武之地(大誤)。但重點來了,它貴。真的貴。尤其對老闆來說,帳單就是比你還懂什麼叫「痛」。
所以聰明的工程師就會想:
「反正只是測試用的 RDS,沒在用的時候關掉就好啦~反正只收 Storage 費用嘛!」
結果,AWS 在你轉身去茶水間倒咖啡的時候,偷偷給你來一招:
RDS 關機超過七天,它自己會重新開機!
對,你沒看錯。七天自動復活,根本殭屍片橋段。
然後月底帳單來了,你才恍然大悟:
「靠北,原來 Server 不是我自己開的,是它自己醒來的!」
AWS 的設計動機是什麼?老實講,我也不知道。可能是怕你資料庫太久沒醒會寂寞吧?
既然 AWS 會幫它自動叫醒,那我們就要幫它「催眠(關機)」。方法很簡單:
聽起來很兇殘,但這就是工程師的日常—「不管黑貓白貓,只要能抓得到老鼠的都是好貓。」,同理「RDS 沒在亂跑就是成功」。
新建一隻 Lambda,取名就叫 ShutdownRDS
,Runtime 選 Python 3.13。
以下提供的程式碼是可以直接複製貼上就能用的,重點是要記得:只有 RDS 狀態是 Available 才能關機,不然就會報錯。所以我們要先檢查一下他的狀態後,確定OK,才進行關機。
我們的做法是先將所有的RDS資料取出來,逐一檢查是不是我們要關機的RDS。找到我們要關機的RDS後,再檢查它的狀態,最後才關機。
以下是AWS Lambda 的程式如下:
import json
import boto3
def lambda_handler(event, context):
# 設定要操作的 RDS 資料庫實例識別碼
dbID = 'testdb123'
# 建立 RDS 服務的 boto3 客戶端
client = boto3.client('rds')
# 取得所有 RDS 資料庫實例的資訊
response = client.describe_db_instances()
# 逐一檢查每一個資料庫實例
for db in response['DBInstances']:
# 判斷目前的資料庫實例是否為指定的 dbID
if db['DBInstanceIdentifier'] == dbID:
# 如果該實例目前狀態為 available(可用)
if db['DBInstanceStatus'] == 'available':
# 執行停止該資料庫實例的動作
client.stop_db_instance(DBInstanceIdentifier=dbID)
print(f'The rds({db['DBInstanceIdentifier']}) is shutting down')
else:
# 若狀態不是 available,則僅顯示目前狀態
print(f'The rds({db['DBInstanceIdentifier']}) is not available')
# Lambda 函式的回傳結果
return {
'statusCode': 200,
'body': json.dumps('execute ok')
}
寫完 Lambda 一跑,結果爆 AccessDenied
。
Log 大概就是長得像下面這樣:
Status: Failed
Test Event Name: test
Response:
{
"errorMessage": "An error occurred (AccessDenied) when calling the DescribeDBInstances operation: User: arn:aws:sts::8105????????:assumed-role/ShutdownRDS-role-bmsaqf6r/ShutdownRDS is not authorized to perform: rds:DescribeDBInstances on resource: arn:aws:rds:us-east-1:8105????????:db:* because no identity-based policy allows the rds:DescribeDBInstances action",
"errorType": "ClientError",
"requestId": "4d892667-3ddf-4513-96c7-7bc21fbf26c6",
"stackTrace": [
" File \"/var/task/lambda_function.py\", line 10, in lambda_handler\n response = client.describe_db_instances()\n",
" File \"/var/lang/lib/python3.13/site-packages/botocore/client.py\", line 602, in _api_call\n return self._make_api_call(operation_name, kwargs)\n",
" File \"/var/lang/lib/python3.13/site-packages/botocore/context.py\", line 123, in wrapper\n return func(*args, **kwargs)\n",
" File \"/var/lang/lib/python3.13/site-packages/botocore/client.py\", line 1078, in _make_api_call\n raise error_class(parsed_response, operation_name)\n"
]
}
Function Logs:
START RequestId: 4d892667-3ddf-4513-96c7-7bc21fbf26c6 Version: $LATEST
[ERROR] ClientError: An error occurred (AccessDenied) when calling the DescribeDBInstances operation: User: arn:aws:sts::8105????????:assumed-role/ShutdownRDS-role-bmsaqf6r/ShutdownRDS is not authorized to perform: rds:DescribeDBInstances on resource: arn:aws:rds:us-east-1:8105????????:db:* because no identity-based policy allows the rds:DescribeDBInstances action
Traceback (most recent call last):
File "/var/task/lambda_function.py", line 10, in lambda_handler
response = client.describe_db_instances()
File "/var/lang/lib/python3.13/site-packages/botocore/client.py", line 602, in _api_call
return self._make_api_call(operation_name, kwargs)
File "/var/lang/lib/python3.13/site-packages/botocore/context.py", line 123, in wrapper
return func(*args, **kwargs)
File "/var/lang/lib/python3.13/site-packages/botocore/client.py", line 1078, in _make_api_call
raise error_class(parsed_response, operation_name)
END RequestId: 4d892667-3ddf-4513-96c7-7bc21fbf26c6
REPORT RequestId: 4d892667-3ddf-4513-96c7-7bc21fbf26c6 Duration: 2465.13 ms Billed Duration: 2769 ms Memory Size: 128 MB Max Memory Used: 88 MB Init Duration: 303.06 ms
Request ID: 4d892667-3ddf-4513-96c7-7bc21fbf26c6
Error Message 超長"errorMessage": "An error occurred (AccessDenied) when calling the DescribeDBInstances operation: User: arn:aws:sts::8105????????:assumed-role/ShutdownRDS-role-bmsaqf6r/ShutdownRDS is not authorized to perform: rds:DescribeDBInstances on resource: arn:aws:rds:us-east-1:8105????????:db:* because no identity-based policy allows the rds:DescribeDBInstances action"
反正重點就是:
「你沒有權限。」
沒錯,Lambda 要操作 RDS,當然要給它 RDS 權限。於是我們走一趟 IAM,幫 Role 加上 AmazonRDSFullAccess
。
設定權限的方式,如下圖的紅框123依照順序點選。先到 Configurations -> Permissions -> Role name ,點擊之後,就可以看到 IAM Role 的設定界面
可以看到現在的Role只有一組自定的Policy,依照下圖紅框的順序點選來加入需要的權限。
接下來會看到可以選擇的權限,現在搜尋欄內輸入RDS。接下來將所有有RDS的三個字的 Policy 列出來,將清單中的 AmazonRDSFullAccess
選起來。
最後點選右下角的 Add permissions
黃色按鈕,就可以完成權限的賦予。
然後你就會心裡想:
「Holly XXXX,這權限也太大了吧!老闆看到一定問:『你是想讓 Lambda 當 DBA 嗎?』」
所以呢,測試完記得要縮小權限,否則以後出事就是「這不是 bug,是 feature」的最佳實例。
完成權限設定後,再看一下 Role 的下方Policy 清單中有出現剛剛選擇的 AmazonRDSFullAccess
後,再回去執行一次 Lambda 看看。
再跑一次,如果成功,你會看到 RDS 被乖乖關掉,Log 顯示任務完成。
如果失敗,理由通常是「Timeout」。解法也很工程師:
「把 Timeout 拉長就好啦!」
這就跟平常 Debug 一樣——「重開機就好了啦。」
如果看到類似以下的結果就表示執行成功了。
Status: Succeeded
Test Event Name: test
Response:
{
"statusCode": 200,
"body": "\"execute ok\""
}
Function Logs:
START RequestId: 01a0ad34-a64e-4661-9d09-ea756994d9a9 Version: $LATEST
The rds(testdb123) is shutting down
END RequestId: 01a0ad34-a64e-4661-9d09-ea756994d9a9
REPORT RequestId: 01a0ad34-a64e-4661-9d09-ea756994d9a9 Duration: 2988.49 ms Billed Duration: 3302 ms Memory Size: 128 MB Max Memory Used: 87 MB Init Duration: 312.65 ms
Request ID: 01a0ad34-a64e-4661-9d09-ea756994d9a9
看到執行成功的訊息之後,可以去看一下RDS有沒有順利被催眠(關機)。如果有順利被催眠的話,之後就不用擔心它每七天自動甦醒。一醒來,每隔十分鐘就可以一棒把他打暈。
那今天就先收工,工程師的人生就是這樣:截圖截到懷疑人生,最後只好跟自己說—「明天再弄啦,反正今天有確定把RDS關機就好了」。
明天再來聊 AWS EventBridge 的設定。