0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PagerDuty 期限付き抑止ルールの自動削除 実行基盤の比較

0
Posted at

PagerDuty の期限付き抑止ルールを自動で片付ける ― 実行基盤5案の比較と実装例(GitHub Actions / AWS / Azure / PagerDuty)

はじめに

PagerDuty で「この時間だけ特定のアラートを静観したい」とき、イベントオーケストレーションに抑止(Suppress)ルールを追加することがあります。

ただし、このルールには有効期限の機能がなく、期限が過ぎても自動では削除されません。消し忘れたルールが残ると、次のような問題が起きます。

  • 条件次第で再び効いてしまい、必要なアラートまで止めてしまう
  • 不要なルールが増え、どれが有効なのか分かりにくくなる

この記事では、期限切れの抑止ルールだけを定期的に自動削除する仕組みについて、5つの実行基盤の比較と、それぞれの実装例を紹介します。

サービス全体を止めてよい場合は、メンテナンスウィンドウを使えば終了時刻に自動で解除されるため、この記事の仕組みは不要です。「特定のアラートだけ止めたい」ためにイベントオーケストレーションを使う場合が対象です。

静観方法の使い分け

方法 止める範囲 期限が来たら 後片付け
メンテナンスウィンドウ サービス単位で全アラート 自動で解除 不要
イベントオーケストレーション 条件に合うアラートだけ ルールが残る 必要

仕組みの概要

ルールのラベルに期限を書いておき、営業時間外にスクリプトで期限切れのものだけ削除します。

[expire:2026-10-10T09:00+09:00] DB作業のため静観

処理の流れは、実行基盤が何であっても共通です。

実行基盤の比較

定期実行できる環境なら何でも動きますが、代表的な5案を比較しました。

観点 GitHub Actions AWS Lambda + EventBridge PagerDuty Runbook Automation Azure Functions Azure Automation
導入の手軽さ ◎ ファイル2つ △ IAM など設定が多い ○ PD内で完結 ○ 関数1つ ◎ Runbook 1つ
コスト ◎ 無料枠内 ◎ 無料枠内(Parameter Store 利用時) × 有料アドオン ○ ほぼ無料(ストレージ等に少額) ◎ 月500分まで無料
実行時刻の正確さ △ 遅延することがある ◎ ○ ◎ ○ 起動待ちが数分ある場合も
タイムゾーン指定 △ UTC のみ ◎ 直接指定可 ○ △ プランによる ◎ 画面で選択
継続性 △ 公開リポジトリは長期間更新がないと停止 ◎ ◎ ◎ ◎
シークレット管理 ○ Secrets ◎ Parameter Store / Secrets Manager ◎ Key Storage ◎ Key Vault ◎ 暗号化変数 / Key Vault
ログ ○ ◎ CloudWatch ○ ◎ Application Insights ○ ジョブ履歴(Log Analytics で延長)
向いている環境 GitHub 中心 AWS 中心 Runbook Automation 契約済み Azure 中心 Azure 中心・小規模

無料枠・保持期間・プランごとの制約は変わることがあります。採用前に各サービスの公式ドキュメントで最新情報を確認してください。

ざっくりした選び方は次のとおりです。

  • まず試したい → GitHub Actions
  • 確実に動かしたい・AWS を使っている → AWS Lambda + EventBridge Scheduler
  • Azure を使っている → Azure Automation(小規模)か Azure Functions

無料枠で運用できるか

1日1回・数十秒で終わる処理なので、PagerDuty Runbook Automation 以外は無料枠(またはごく少額)で収まります。

案 無料で動くか 補足
GitHub Actions ◎ 公開リポジトリは無料。プライベートでも無料プランの実行時間枠に十分収まる
AWS Lambda + EventBridge ◎ Lambda・EventBridge Scheduler(月1,400万回まで無料)とも無料枠内。Secrets Manager は1シークレットごとに月額課金なので、無料で済ませるなら Systems Manager Parameter Store の SecureString を使う
PagerDuty Runbook Automation × 有料のアドオン。すでに契約している場合のみ選択肢になる
Azure Functions ○ 実行回数は無料枠内。関数アプリに必要なストレージアカウントや Key Vault の操作にごく少額かかる
Azure Automation ◎ ジョブ実行時間は月500分まで無料。ただしサブスクリプション単位で、他の Runbook と合算

料金や無料枠は改定されることがあり、契約形態によっては無料枠が適用されない場合もあります。導入前に各サービスの料金ページで確認してください。

Azure Automation での実装例

構成

  • Automation アカウントの変数に API キーと対象 ID を保存
  • **Runbook(PowerShell 7)**で処理を実行
  • スケジュールで毎日 3:00 JST に起動
  • 失敗時はジョブが「失敗」になるので、Azure Monitor のアラートで通知

事前準備:変数の登録

Automation アカウントの「変数」に次を登録します。

名前 内容 暗号化
PD_API_KEY PagerDuty REST API キー はい
PD_SERVICE_IDS 対象サービス ID(カンマ区切り) いいえ
PD_GLOBAL_ORCH_IDS 対象グローバルオーケストレーション ID(任意) いいえ

Runbook(PowerShell 7)

pd-expired-rule-cleanup.ps1
<#
  PagerDuty 期限切れ抑止ルール削除 Runbook
  実行環境: Azure Automation(PowerShell 7.2 以降)

  期限ラベルの書式(ルールのラベルに付ける):
    [expire:2026-10-10T09:00+09:00] 作業のため静観

  パラメータ:
    DryRun = $true  : 削除対象を表示するだけ(既定)
    DryRun = $false : 実際に削除する
#>
param(
    [bool]$DryRun = $true
)

$ErrorActionPreference = 'Stop'

# ---------- 設定の読み込み ----------
function Get-IdList([string]$name, [bool]$required) {
    try {
        $raw = Get-AutomationVariable -Name $name
    } catch {
        if ($required) { throw "変数 $name が見つかりません" }
        return @()
    }
    return @($raw -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
}

$apiKey   = Get-AutomationVariable -Name 'PD_API_KEY'
$services = Get-IdList 'PD_SERVICE_IDS' $true
$globals  = Get-IdList 'PD_GLOBAL_ORCH_IDS' $false

$headers = @{
    'Authorization' = "Token token=$apiKey"
    'Accept'        = 'application/vnd.pagerduty+json;version=2'
}
$base    = 'https://api.pagerduty.com'
$now     = [DateTimeOffset]::UtcNow
$pattern = '\[expire:([^\]]+)\]'

$targets = @()
foreach ($id in $services) { $targets += "$base/event_orchestrations/services/$id" }
foreach ($id in $globals)  { $targets += "$base/event_orchestrations/$id/global" }

Write-Output "実行時刻(UTC): $now / DryRun: $DryRun / 対象数: $($targets.Count)"

# ---------- メイン処理 ----------
$hasError = $false

foreach ($url in $targets) {
    try {
        Write-Output "=== 対象: $url"

        # 1. 取得
        $res  = Invoke-RestMethod -Method Get -Uri $url -Headers $headers
        $path = $res.orchestration_path

        # 2. バックアップ(ジョブ出力に残る)
        Write-Output '--- バックアップ(変更前)'
        Write-Output ($path | ConvertTo-Json -Depth 100 -Compress)

        # 3. 期限切れルールの除外
        $removed = @()
        foreach ($set in $path.sets) {
            $kept = @()
            foreach ($rule in @($set.rules | Where-Object { $_ })) {
                if ($rule.label -match $pattern) {
                    $expire = [DateTimeOffset]::MinValue
                    $ok = [DateTimeOffset]::TryParse(
                        $Matches[1],
                        [Globalization.CultureInfo]::InvariantCulture,
                        [Globalization.DateTimeStyles]::None,
                        [ref]$expire)

                    if ($ok -and $expire -le $now) {
                        $removed += $rule
                        continue
                    }
                    if (-not $ok) {
                        Write-Warning "期限を読み取れないため削除しません: $($rule.label)"
                    }
                }
                $kept += $rule
            }
            $set.rules = @($kept)
        }

        if ($removed.Count -eq 0) {
            Write-Output '期限切れルールはありません'
            continue
        }
        foreach ($r in $removed) { Write-Output "削除対象: $($r.label)" }

        if ($DryRun) {
            Write-Output 'DryRun のため書き戻しはしません'
            continue
        }

        # 4. 書き戻し(ルール一式の上書き)
        $body = @{
            orchestration_path = @{
                sets      = $path.sets
                catch_all = $path.catch_all
            }
        } | ConvertTo-Json -Depth 100

        Invoke-RestMethod -Method Put -Uri $url -Headers $headers `
            -ContentType 'application/json; charset=utf-8' `
            -Body ([Text.Encoding]::UTF8.GetBytes($body)) | Out-Null

        Write-Output "削除完了: $($removed.Count) 件"
    }
    catch {
        $hasError = $true
        Write-Error "失敗: $url : $($_.Exception.Message)" -ErrorAction Continue
    }
}

# 5. 失敗があればジョブを失敗扱いにする
if ($hasError) {
    throw '一部の対象で失敗しました。ジョブ出力を確認してください。'
}
Write-Output '完了'

ポイント

  • 消すのは期限切れラベルのルールだけ:ラベルなし・期限内のルールには触れません
  • 読めないラベルは消さない:書式ミスは警告を出してスキップします
  • DryRun が既定:初回は手動実行で削除対象を確認してから本番に切り替えます
  • 毎回バックアップ:変更前のルール一式をジョブ出力に残すので、誤削除しても戻せます
  • 失敗を検知できる:1件でも失敗したらジョブを失敗扱いにし、アラートで気づけるようにしています

導入手順

  1. Automation アカウントに上記の変数を登録
  2. Runbook を作成(種類:PowerShell、ランタイム:7.2 以降)し、スクリプトを貼り付けて発行
  3. 手動実行(DryRun = True)で削除対象を確認
  4. スケジュール(タイムゾーン:Japan、毎日 3:00)を作成して Runbook にリンクし、パラメータ DryRun を False に設定
  5. ジョブ失敗時の Azure Monitor アラートを設定

GitHub Actions での実装例

処理内容は Azure Automation 版と同じです。GitHub のランナーには PowerShell 7(pwsh)が入っているので、設定の読み込みを環境変数に変え、バックアップをファイルに保存するようにしただけです。

構成

  • Repository secrets に API キー、Repository variables に対象 ID を保存
  • スケジュール実行(毎日 3:00 JST)と手動実行(DryRun 指定可)に対応
  • バックアップはアーティファクトとして保存
  • 失敗するとワークフローが失敗になり、GitHub から通知が届く

事前準備:シークレットと変数の登録

リポジトリの Settings → Secrets and variables → Actions で登録します。

種類 名前 内容
Secret PD_API_KEY PagerDuty REST API キー
Variable PD_SERVICE_IDS 対象サービス ID(カンマ区切り)
Variable PD_GLOBAL_ORCH_IDS 対象グローバルオーケストレーション ID(任意)

バックアップにはルールの中身が含まれます。リポジトリはプライベートで運用してください。

ディレクトリ構成

.
├── .github/
│   └── workflows/
│       └── pd-expired-rule-cleanup.yml
└── scripts/
    └── pd-expired-rule-cleanup.ps1

ワークフロー

.github/workflows/pd-expired-rule-cleanup.yml
name: PagerDuty expired rule cleanup

on:
  schedule:
    - cron: '0 18 * * *'   # 毎日 3:00 JST(cron は UTC で指定)
  workflow_dispatch:
    inputs:
      dry_run:
        description: '削除せずに対象だけ表示する'
        type: boolean
        default: true

permissions:
  contents: read

jobs:
  cleanup:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4

      - name: Cleanup expired rules
        shell: pwsh
        env:
          PD_API_KEY: ${{ secrets.PD_API_KEY }}
          PD_SERVICE_IDS: ${{ vars.PD_SERVICE_IDS }}
          PD_GLOBAL_ORCH_IDS: ${{ vars.PD_GLOBAL_ORCH_IDS }}
          # 手動実行は入力値、スケジュール実行は本番(false)
          DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || 'false' }}
        run: ./scripts/pd-expired-rule-cleanup.ps1

      - name: Upload backup
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: pd-backup-${{ github.run_id }}
          path: backup/
          retention-days: 30
          if-no-files-found: ignore

スクリプト(PowerShell 7)

scripts/pd-expired-rule-cleanup.ps1
<#
  PagerDuty 期限切れ抑止ルール削除スクリプト(GitHub Actions 用)

  環境変数:
    PD_API_KEY         : PagerDuty REST API キー(Secret)
    PD_SERVICE_IDS     : 対象サービス ID(カンマ区切り)
    PD_GLOBAL_ORCH_IDS : 対象グローバルオーケストレーション ID(任意)
    DRY_RUN            : 'false' のときだけ実際に削除(それ以外は表示のみ)
#>
$ErrorActionPreference = 'Stop'

function Get-IdList([string]$value) {
    if (-not $value) { return @() }
    return @($value -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
}

if (-not $env:PD_API_KEY) { throw 'PD_API_KEY が設定されていません' }

$DryRun   = $env:DRY_RUN -ne 'false'   # 安全側:明示的に false のときだけ削除
$services = Get-IdList $env:PD_SERVICE_IDS
$globals  = Get-IdList $env:PD_GLOBAL_ORCH_IDS
if ($services.Count + $globals.Count -eq 0) { throw '対象 ID が設定されていません' }

$headers = @{
    'Authorization' = "Token token=$($env:PD_API_KEY)"
    'Accept'        = 'application/vnd.pagerduty+json;version=2'
}
$base    = 'https://api.pagerduty.com'
$now     = [DateTimeOffset]::UtcNow
$pattern = '\[expire:([^\]]+)\]'

$targets = @()
foreach ($id in $services) { $targets += @{ name = "service-$id"; url = "$base/event_orchestrations/services/$id" } }
foreach ($id in $globals)  { $targets += @{ name = "global-$id";  url = "$base/event_orchestrations/$id/global" } }

New-Item -ItemType Directory -Force -Path 'backup' | Out-Null
Write-Output "実行時刻(UTC): $now / DryRun: $DryRun / 対象数: $($targets.Count)"

$hasError = $false

foreach ($t in $targets) {
    try {
        Write-Output "=== 対象: $($t.name)"

        # 1. 取得
        $res  = Invoke-RestMethod -Method Get -Uri $t.url -Headers $headers
        $path = $res.orchestration_path

        # 2. バックアップ(アーティファクトとして保存される)
        $path | ConvertTo-Json -Depth 100 |
            Set-Content -Path "backup/$($t.name).json" -Encoding utf8

        # 3. 期限切れルールの除外
        $removed = @()
        foreach ($set in $path.sets) {
            $kept = @()
            foreach ($rule in @($set.rules | Where-Object { $_ })) {
                if ($rule.label -match $pattern) {
                    $expire = [DateTimeOffset]::MinValue
                    $ok = [DateTimeOffset]::TryParse(
                        $Matches[1],
                        [Globalization.CultureInfo]::InvariantCulture,
                        [Globalization.DateTimeStyles]::None,
                        [ref]$expire)

                    if ($ok -and $expire -le $now) {
                        $removed += $rule
                        continue
                    }
                    if (-not $ok) {
                        Write-Output "::warning::期限を読み取れないため削除しません: $($rule.label)"
                    }
                }
                $kept += $rule
            }
            $set.rules = @($kept)
        }

        if ($removed.Count -eq 0) {
            Write-Output '期限切れルールはありません'
            continue
        }
        foreach ($r in $removed) { Write-Output "削除対象: $($r.label)" }

        if ($DryRun) {
            Write-Output 'DryRun のため書き戻しはしません'
            continue
        }

        # 4. 書き戻し(ルール一式の上書き)
        $body = @{
            orchestration_path = @{
                sets      = $path.sets
                catch_all = $path.catch_all
            }
        } | ConvertTo-Json -Depth 100

        Invoke-RestMethod -Method Put -Uri $t.url -Headers $headers `
            -ContentType 'application/json; charset=utf-8' `
            -Body ([Text.Encoding]::UTF8.GetBytes($body)) | Out-Null

        Write-Output "削除完了: $($removed.Count) 件"
    }
    catch {
        $hasError = $true
        Write-Output "::error::失敗: $($t.name) : $($_.Exception.Message)"
    }
}

# 5. 失敗があればワークフローを失敗にする
if ($hasError) { exit 1 }
Write-Output '完了'

導入手順

  1. プライベートリポジトリに上記2ファイルを配置
  2. Secret と Variables を登録
  3. Actions タブから手動実行(dry_run にチェック)で削除対象を確認
  4. 問題なければそのまま運用(スケジュール実行は本番モードで動きます)

GitHub Actions 版の注意点

  • スケジュールの cron は UTC 指定です(0 18 * * * = 3:00 JST)
  • スケジュール実行は混雑時に遅れることがあります。厳密な時刻が必要な用途には向きません
  • 公開リポジトリでは、一定期間アクティビティがないとスケジュール実行が自動で無効になります
  • 使っているアクション(actions/checkout など)のバージョンは、導入時点の最新を確認してください

AWS Lambda + EventBridge Scheduler での実装例

Lambda には PowerShell の標準ランタイムがないため、ここでは **Python(標準ライブラリ+boto3 のみ)**で同じ処理を書いています。

構成

  • API キーは Systems Manager Parameter Store(SecureString)に保存(無料で使える)
  • Lambda(Python 3.12 以降)で処理を実行
  • EventBridge Scheduler で毎日 3:00 JST に起動(タイムゾーンを直接指定できる)
  • バックアップとログは CloudWatch Logs に出力
  • 失敗時は CloudWatch アラーム(Lambda の Errors メトリクス)で通知

Lambda 関数(Python)

lambda_function.py
"""
PagerDuty 期限切れ抑止ルール削除(AWS Lambda 用)

環境変数:
  PD_API_KEY_PARAM   : API キーを保存した Parameter Store の名前(例: /pagerduty/api-key)
  PD_SERVICE_IDS     : 対象サービス ID(カンマ区切り)
  PD_GLOBAL_ORCH_IDS : 対象グローバルオーケストレーション ID(任意)

イベント入力:
  {"dry_run": false} のときだけ実際に削除(それ以外は表示のみ)
"""
import datetime
import json
import os
import re
import urllib.request

import boto3

API = "https://api.pagerduty.com"
PATTERN = re.compile(r"\[expire:([^\]]+)\]")
ssm = boto3.client("ssm")


def get_ids(name):
    return [x.strip() for x in os.environ.get(name, "").split(",") if x.strip()]


def call(method, url, key, body=None):
    data = json.dumps(body, ensure_ascii=False).encode("utf-8") if body is not None else None
    req = urllib.request.Request(
        url,
        data=data,
        method=method,
        headers={
            "Authorization": f"Token token={key}",
            "Accept": "application/vnd.pagerduty+json;version=2",
            "Content-Type": "application/json; charset=utf-8",
        },
    )
    with urllib.request.urlopen(req, timeout=30) as res:
        text = res.read().decode("utf-8")
        return json.loads(text) if text else {}


def parse_expire(value):
    try:
        dt = datetime.datetime.fromisoformat(value)
    except ValueError:
        return None
    return dt if dt.tzinfo else None  # タイムゾーンなしは「読めない」扱い


def lambda_handler(event, context):
    # 安全側:明示的に false のときだけ削除
    dry_run = (event or {}).get("dry_run", True) is not False

    key = ssm.get_parameter(
        Name=os.environ["PD_API_KEY_PARAM"], WithDecryption=True
    )["Parameter"]["Value"]

    targets = [(f"service-{i}", f"{API}/event_orchestrations/services/{i}") for i in get_ids("PD_SERVICE_IDS")]
    targets += [(f"global-{i}", f"{API}/event_orchestrations/{i}/global") for i in get_ids("PD_GLOBAL_ORCH_IDS")]

    now = datetime.datetime.now(datetime.timezone.utc)
    print(f"実行時刻(UTC): {now.isoformat()} / DryRun: {dry_run} / 対象数: {len(targets)}")

    errors = []
    for name, url in targets:
        try:
            print(f"=== 対象: {name}")

            # 1. 取得
            path = call("GET", url, key)["orchestration_path"]

            # 2. バックアップ(CloudWatch Logs に残る)
            print("--- バックアップ(変更前)")
            print(json.dumps(path, ensure_ascii=False))

            # 3. 期限切れルールの除外
            removed = []
            for rule_set in path.get("sets", []):
                kept = []
                for rule in rule_set.get("rules") or []:
                    m = PATTERN.search(rule.get("label") or "")
                    if m:
                        expire = parse_expire(m.group(1))
                        if expire is None:
                            print(f"[WARN] 期限を読み取れないため削除しません: {rule.get('label')}")
                        elif expire <= now:
                            removed.append(rule)
                            continue
                    kept.append(rule)
                rule_set["rules"] = kept

            if not removed:
                print("期限切れルールはありません")
                continue
            for r in removed:
                print(f"削除対象: {r.get('label')}")

            if dry_run:
                print("DryRun のため書き戻しはしません")
                continue

            # 4. 書き戻し(ルール一式の上書き)
            body = {"orchestration_path": {"sets": path["sets"], "catch_all": path["catch_all"]}}
            call("PUT", url, key, body)
            print(f"削除完了: {len(removed)} 件")

        except Exception as e:
            errors.append(name)
            print(f"[ERROR] 失敗: {name} : {e}")

    # 5. 失敗があればエラー終了(CloudWatch アラームで検知)
    if errors:
        raise RuntimeError(f"一部の対象で失敗しました: {', '.join(errors)}")
    return {"checked": len(targets)}

実行ロールの権限

基本の AWSLambdaBasicExecutionRole に加えて、Parameter Store の読み取りを許可します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ssm:GetParameter",
      "Resource": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/pagerduty/api-key"
    }
  ]
}

※ アカウント ID・リージョンはダミーです。独自の KMS キーで暗号化している場合は kms:Decrypt も必要です。

導入手順

  1. Parameter Store に SecureString で API キーを保存(例:/pagerduty/api-key)
  2. Lambda 関数を作成(ランタイム:Python 3.12 以降、タイムアウト:1分)し、コードと環境変数を設定
  3. 実行ロールに上記の権限を追加
  4. テストイベント {} で実行し、削除対象を確認(DryRun)
  5. EventBridge Scheduler でスケジュールを作成
    • スケジュール:cron(0 3 * * ? *)、タイムゾーン:Asia/Tokyo
    • ターゲット:作成した Lambda、入力:{"dry_run": false}
  6. CloudWatch アラームで Lambda の Errors が1以上になったら SNS で通知

Azure Functions での実装例

Azure Functions は PowerShell に対応しているので、GitHub Actions 版のスクリプトをほぼそのまま使えます。

構成

  • 関数アプリ(従量課金プラン、OS:Windows、ランタイム:PowerShell 7)
  • タイマートリガーで毎日 3:00 JST に起動
  • API キーは Key Vault に保存し、アプリ設定から Key Vault 参照で読み込む
  • ログとバックアップは Application Insights に出力
  • 失敗時は Application Insights のアラートで通知

ファイル構成

.
├── host.json
├── profile.ps1
├── requirements.psd1
└── PdExpiredRuleCleanup/
    ├── function.json
    └── run.ps1

host.json などは関数アプリ作成時に生成されるものをそのまま使います。

function.json

PdExpiredRuleCleanup/function.json
{
  "bindings": [
    {
      "name": "Timer",
      "type": "timerTrigger",
      "direction": "in",
      "schedule": "0 0 3 * * *"
    }
  ]
}

スケジュールは「秒 分 時 日 月 曜日」の6項目です。

run.ps1

GitHub Actions 版の scripts/pd-expired-rule-cleanup.ps1 をコピーし、次の4か所だけ変更します。

PdExpiredRuleCleanup/run.ps1(変更点)
# ① 先頭に追加(タイマートリガーの引数)
param($Timer)

# ② バックアップはファイルではなくログに出す
#    New-Item -ItemType Directory ... の行は削除し、
#    Set-Content でファイルに保存している部分を次に置き換え
Write-Host '--- バックアップ(変更前)'
Write-Host ($path | ConvertTo-Json -Depth 100 -Compress)

# ③ GitHub 用の注釈(::warning:: / ::error::)を通常の出力に置き換え
Write-Warning "期限を読み取れないため削除しません: $($rule.label)"
Write-Error "失敗: $($t.name) : $($_.Exception.Message)" -ErrorAction Continue

# ④ 最後の exit 1 を throw に置き換え(関数の失敗として記録させる)
if ($hasError) { throw '一部の対象で失敗しました' }

アプリ設定

名前 値
PD_API_KEY @Microsoft.KeyVault(SecretUri=https://<キーコンテナー名>.vault.azure.net/secrets/pd-api-key/)
PD_SERVICE_IDS 対象サービス ID(カンマ区切り)
PD_GLOBAL_ORCH_IDS 対象グローバルオーケストレーション ID(任意)
DRY_RUN 最初は true、確認後に false
WEBSITE_TIME_ZONE Tokyo Standard Time

導入手順

  1. 関数アプリを作成(従量課金、Windows、PowerShell 7)
  2. システム割り当てマネージド ID を有効にし、Key Vault で「キー コンテナー シークレット ユーザー」ロールを付与
  3. Key Vault に API キーを登録し、アプリ設定を上記のとおり設定
  4. 関数をデプロイし、DRY_RUN=true のまま手動実行して削除対象を確認
  5. DRY_RUN=false に変更して運用開始
  6. Application Insights で関数の失敗時アラートを設定

WEBSITE_TIME_ZONE によるタイムゾーン指定は、OS やホスティングプランによって使えない場合があります。使えない場合は UTC で 0 0 18 * * *(3:00 JST)と指定してください。

PagerDuty Runbook Automation での実装例

PagerDuty の中で完結させたい場合の方法です。有料アドオンのため、すでに契約しているチーム向けです。処理は GitHub Actions 版のスクリプトをそのまま使います。

構成

  • API キーは Runbook Automation の Key Storage に保存
  • ジョブのスクリプトステップで PowerShell スクリプトを実行(実行先に PowerShell 7 が必要)
  • ジョブのスケジュールで毎日 3:00(タイムゾーン Asia/Tokyo)に起動
  • 失敗時はジョブの通知設定で知らせる

導入手順

  1. Key Storage に API キーを保存(例:keys/pagerduty/api-key)
  2. ジョブを作成し、オプションを追加
    • PD_API_KEY:Secure オプション(Key Storage を参照、値をログに出さない)
    • PD_SERVICE_IDS、PD_GLOBAL_ORCH_IDS、DRY_RUN:通常のオプション
  3. ワークフローに「スクリプト」ステップを追加し、GitHub Actions 版スクリプトを貼り付け、インタープリタに pwsh を指定
  4. スクリプトの先頭で、ジョブのオプションを環境変数に読み替える
# Runbook Automation のオプションは RD_OPTION_<名前> で渡される
$env:PD_API_KEY         = $env:RD_OPTION_PD_API_KEY
$env:PD_SERVICE_IDS     = $env:RD_OPTION_PD_SERVICE_IDS
$env:PD_GLOBAL_ORCH_IDS = $env:RD_OPTION_PD_GLOBAL_ORCH_IDS
$env:DRY_RUN            = $env:RD_OPTION_DRY_RUN
  1. DRY_RUN=true で手動実行し、削除対象を確認
  2. スケジュール(毎日 3:00、Asia/Tokyo)を設定し、DRY_RUN の既定値を false に
  3. ジョブの失敗時通知を設定

Secure オプションをスクリプトに渡せるかどうかは、オプションの種類や設定によって異なります。導入時に公式ドキュメントで確認してください。

注意点

  • 書き戻しはルール一式の上書きです。 実行中に誰かがルールを編集すると、その変更が消える可能性があります。人が触らない時間帯に実行してください。
  • API キーは最小限の権限で。 対象サービスを更新できる範囲に絞り、変数は必ず暗号化して保存してください。
  • Terraform などで管理している場合は、スクリプトで直接書き換えるとコードと実環境がずれます。コード側から期限切れルールを外す運用のほうが安全です。
  • 既存の Automation アカウントを流用する場合、古い Run As アカウントや Python 2 の Runbook はすでに廃止されているため、マネージド ID や PowerShell 7 / Python 3 への移行が必要です。
  • 本番に入れる前に、テスト用のサービスで動作確認することをおすすめします。

まとめ

  • イベントオーケストレーションの抑止ルールは期限が来ても残るため、後片付けの仕組みが必要
  • ラベルに期限を書き、定期実行で期限切れだけ消すシンプルな方式で対応できる
  • 実行基盤は手持ちの環境に合わせて選べばよく、PagerDuty Runbook Automation 以外は無料枠(またはごく少額)で運用できる

同じように静観ルールの消し忘れに悩んでいる方の参考になれば幸いです。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?