OCIの監視を計画的に抑止

# 概要
[Oracle Cloud Infrastructure（OCI）](https://www.oracle.com/jp/cloud/)のリソースやサービスを監視するためモニタリングを使用します。運用時は定期停止やメンテナンス作業のため監視を抑止したいことがあると思われます。
コンソールから監視を有効/無効にする方法がありますが、作業省力化を目的に指示ファイルを作成しておき指定した時間になったら監視を一時停止/再開する仕組みを作成しました。監視ステータス変更は日次抑止および臨時抑止に対応しています。

# 仕組み
指示ファイルを取り込んで監視ステータスを更新するFunctionsを定期実行します
- 指示するCSVファイルを作成・アップロード
- Resource Schedulerより1時間ごとにFunctionsを定期実行
- FunctionsはCSVファイルをダウンロードし、指示した時間が一致した監視のステータスを変更
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/170ed7ed-908f-1738-aeae-48e719eff5fb.png">

例：
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/e359cec0-bd4b-8669-21d4-66a3b836273a.png">


# 前提
以下のサービスを作成したことがあることを前提としています
- Monitoringを使用してリソース監視を行っている
- Functionsを作成したことがある
- ObjectStorage利用したことがある
- ResourceScheduleを利用したことがある

# 準備
## OCI Policy
ポリシーは以下を設定します
- Functionsからアラーム定義を更新
*Allow dynamic-group [functionsリソースを含むグループ] to manage alarms in compartment [コンパートメント名]*

- FunctionsからObjectStorageの読み取り
*Allow dynamic-group [functionsリソースを含むグループ] to read object-family in compartment [コンパートメント名]*

- RsourceSchedulerからFunctionsを起動
  [Resource SchedulerのOCID]はResource Scheduler設定後にOCIDを取得して設定します
*Allow any-user to manage functions-family in compartment [コンパートメント名] where all {request.principal.type = 'resourceschedule', request.principal.id='[Resource SchedulerのOCID]'}*


## Functions
Object Storageの[NAMESPACE]および[BUCKET]はご利用の環境の値を利用します
```func.py
import io
import json
import logging
import oci
import csv
from datetime import datetime, timedelta, timezone

def handler(ctx, data: io.BytesIO = None):
    try:
        # Resource principal
        signer = oci.auth.signers.get_resource_principals_signer()
        
        # Download object from Object storage using Resource principal
        object_storage_client = oci.object_storage.ObjectStorageClient(config={},signer=signer,service_endpoint="https://objectstorage.ap-tokyo-1.oraclecloud.com")
        
        get_object_response = object_storage_client.get_object(
            namespace_name="[NAMESPACE]",
            bucket_name="[BUCKET]",
            object_name="monitor/chgmonitorstatus.csv"
        )
        
        downloaded_file_data = get_object_response.data.content
        
        # Write CSV file
        with open('/tmp/chgmonitorstatus.csv', 'w') as f:
            print(downloaded_file_data.decode('utf8'), file=f)
        
        
        # Monitoring using Resource principal
        monitoring_client = oci.monitoring.MonitoringClient(config={},signer=signer,service_endpoint="https://telemetry.ap-tokyo-1.oraclecloud.com")
        
        # Get the current time
        current_time = datetime.now(timezone(timedelta(hours=9)))

        # Read CSV file
        with open('/tmp/chgmonitorstatus.csv', 'r', newline='') as csvf:
            csvreader = csv.reader(csvf)
            
            # Skip header row
            next(csvreader)
            
            for row in csvreader:
                # Check line length before getting data
                if len(row) > 1:
                    # Read the first data (date and time)
                    if row[0] == "Y":
                        record_time = datetime.strptime(row[1], '%H%M').replace(
                            year=current_time.year, month=current_time.month, day=current_time.day, tzinfo=timezone(timedelta(hours=9))
                        )
                    else:
                        record_time = datetime.strptime(row[1], '%Y%m%d%H%M').replace(tzinfo=timezone(timedelta(hours=9)))
                    
                    # Compare to current time
                    time_difference = current_time - record_time
                    if abs(time_difference) <= timedelta(minutes=10):
                        # Read the second and third data
                        second_data = row[2]
                        second_data_boolean = second_data.lower() == "true"
                        third_data = row[3]
                        
                        # Update monitoring
                        update_alarm_response = monitoring_client.update_alarm(
                            alarm_id=third_data,
                            update_alarm_details=oci.monitoring.models.UpdateAlarmDetails(
                                is_enabled=second_data_boolean
                            )
                        )
    
    except Exception as e:
        print('ERROR: bad Event!', flush=True)
        raise
```

```func.yaml
schema_version: 20180708
name: chgmonitorstatus
version: 0.0.1
runtime: python
build_image: fnproject/python:3.11-dev
run_image: fnproject/python:3.11
entrypoint: /python/bin/fdk /function/func.py handler
memory: 256
```

```requirements.txt
fdk>=0.1.83
oci
```

既存アプリケーションを指定してFunctionsをデプロイします
```
fn -v deploy --app [App名]
```
## Resource Scheduler
OCIコンソールから作成します
- ナビゲーションメニューからガバナンスと管理>>リソーススケジューラを選択して[スケジュールの作成]を押下
- スケジュール名、アクション：起動を選択して[次]を押下
- 作成したFunctionsを選択して[次]を押下
- スケジュールを間隔：毎時、時間を初回の時間を入力して[次]を押下
- 内容を確認して[スケジュールの作成]を押下

# 実行
## 指示ファイル作成
以下のCSVファイルを作成してObject Storageにアップロードします
- Daily:日次(Y), 臨時(N)
- Date:起動時刻、日次の場合はHHMM, 臨時の場合はYYYYMMDDHHMM
- IsEnabled:監視停止=False,再開=True
- OCID:アラーム定義OCID
```chgmonitorstatus.csv
Daily,Date,IsEnabled,OCID
Y,2300,False,[アラーム定義OCID ocid1.alarm.oc1...]
N,202411162300,True,[アラーム定義OCID ocid1.alarm.oc1...]
```

## 起動
ResourceSchedulerで指定した時間になるとFunctionsが起動して以下の処理を行います
- ObjectStorageからCSVファイルを読み取り一時ファイルとし保存
- 一時ファイルを読み取り、起動時刻と現在時刻との差が10分以内の場合は以下の処理を実施
    - CSVのIsEnabledとOCIDを読み取り
    - アラーム定義を変更