

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 使用 Data Lifecycle Manager 自動化應用程式一致性快照
<a name="automate-app-consistent-backups"></a>

您可以在以執行個體為目標的快照生命週期政策中啟用前置和後置指令碼，以使用 Amazon Data Lifecycle Manager 自動執行應用程式一致快照。

Amazon Data Lifecycle Manager 與 AWS Systems Manager (Systems Manager) 整合，以支援應用程式一致的快照。Amazon Data Lifecycle Manager 使用 Systems Manager (SSM) 命令文件，其中包含前置和後置指令碼，可自動執行完成應用程式一致快照所需的動作。Amazon Data Lifecycle Manager 在起始快照建立作業之前，會先執行前置指令碼中的命令以凍結和清除 I/O。Amazon Data Lifecycle Manager 起始快照建立作業後，會執行後置指令碼中的命令以解凍 I/O。

使用 Amazon Data Lifecycle Manager，您可以自動執行下列應用程式一致快照：
+ 使用磁碟區陰影複製服務 (VSS) 的 Windows 應用程式
+ 使用 AWS 受管 SSDM 文件的 SAP HANA。如需更多資訊，請參閱 [SAP HANA 的 Amazon EBS 快照](https://docs.aws.amazon.com/sap/latest/sap-hana/ebs-sap-hana.html)。
+ 使用 SSM 文件範本的自我管理資料庫，例如 MySQL、PostgreSQL 或 InterSystems IRIS

**Topics**
+ [使用前置和後置指令碼的需求](#app-consistent-prereqs)
+ [開始使用應用程式一致快照](#app-consistent-get-started)
+ [使用 Amazon Data Lifecycle Manager 搭配 VSS 備份的考量事項](#app-consistent-vss)
+ [應用程式一致快照的共同責任](#shared-responsibility)

## 使用前置和後置指令碼的需求
<a name="app-consistent-prereqs"></a>

下表概述在 Amazon Data Lifecycle Manager 中使用前置和後置指令碼的需求。


|  | 應用程式一致性快照 |  | 
| --- |--- |--- |
| 需求 | VSS 備份 | 自訂 SSM 文件 | 其他使用案例 | 
| --- |--- |--- |--- |
| SSM Agent installed and running on target instances | ✓ | ✓ | ✓ | 
| VSS system requirements met on target instances | ✓ |  |  | 
| VSS enabled instance profile associated with target instances | ✓ |  |  | 
| VSS components installed on target instances | ✓ |  |  | 
| Prepare SSM document with pre and post script commands |  | ✓ | ✓ | 
| Prepare Amazon Data Lifecycle Manager IAM role run pre and post scripts | ✓ | ✓ | ✓ | 
| Create snapshot policy that targets instances and is configured for pre and post scripts | ✓ | ✓ | ✓ | 

## 開始使用應用程式一致快照
<a name="app-consistent-get-started"></a>

本節說明使用 Amazon Data Lifecycle Manager 自動執行應用程式一致快照需遵循的步驟。

### 步驟 1：準備目標執行個體
<a name="prep-instances"></a>

您需要使用 Amazon Data Lifecycle Manager 來準備目標執行個體，以取得應用程式一致快照。根據使用案例執行以下其中一項操作。

------
#### [ Prepare for VSS Backups ]

**準備用於 VSS 備份的目標執行個體**

1. 如果目標執行個體上尚未安裝 SSM 代理程式，請安裝。如果目標執行個體上已安裝 SSM 代理程式，請跳過此步驟。

   如需詳細資訊，請參閱[在 Windows 伺服器的 EC2 執行個體上使用 SSM Agent](https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent-windows.html)。

1. 確定 SSM 代理程式執行中。如需詳細資訊，請參閱[檢查 SSM 代理程式狀態和啟動代理程式](https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent-status-and-restart.html)。

1. 設定 Amazon EC2 執行個體的 Systems Manager。如需詳細資訊，請參閱《AWS Systems Manager 使用者指南》**中的[為 Amazon EC2 執行個體設定 Systems Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-setting-up-ec2.html)。

1. [確認符合 VSS 備份的系統需求](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/application-consistent-snapshots-prereqs.html)。

1. [將啟用 VSS 的執行個體設定檔連接至目標執行個體](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/vss-iam-reqs.html)。

1. [安裝 VSS 元件](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/application-consistent-snapshots-getting-started.html)。

------
#### [ Prepare for SAP HANA backups ]

**準備用於 SAP HANA 備份的目標執行個體**

1. 在目標執行個體上準備 SAP HANA 環境。

   1. 使用 SAP HANA 設定執行個體。如果您還沒有現有的 SAP HANA 環境，可以參考 [https://docs.aws.amazon.com/sap/latest/sap-hana/std-sap-hana-environment-setup.html](https://docs.aws.amazon.com/sap/latest/sap-hana/std-sap-hana-environment-setup.html) 上的 SAP HANA 環境設定 AWS。

   1. 以合適的系統管理員使用者身分登入 SystemDB。

   1. 建立要搭配 Amazon Data Lifecycle Manager 使用的資料庫備份使用者。

      ```
      CREATE USER {{username}} PASSWORD {{password}} NO FORCE_FIRST_PASSWORD_CHANGE;
      ```

      例如，以下命令會建立名為 `dlm_user` 且密碼為 `password` 的使用者。

      ```
      CREATE USER dlm_user PASSWORD password NO FORCE_FIRST_PASSWORD_CHANGE;
      ```

   1. 將 `BACKUP OPERATOR` 角色指派給您在先前步驟中建立的資料庫備份使用者。

      ```
      GRANT BACKUP OPERATOR TO {{username}}
      ```

      例如，下列命令會將角色指派給名為 `dlm_user` 的使用者。

      ```
      GRANT BACKUP OPERATOR TO dlm_user
      ```

   1. 以管理員身分登入作業系統，例如 `{{sid}}adm`。

   1. 建立 `hdbuserstore` 項目以儲存連線資訊，讓使用者不需要輸入資訊就能將 SAP HANA SSM 文件連線至 SAP HANA。

      ```
      hdbuserstore set DLM_HANADB_SNAPSHOT_USER localhost:3{{hana_instance_number}}13 {{username}} {{password}}
      ```

      例如：

      ```
      hdbuserstore set DLM_HANADB_SNAPSHOT_USER localhost:30013 dlm_user password
      ```

   1. 測試連線。

      ```
      hdbsql -U DLM_HANADB_SNAPSHOT_USER "select * from dummy"
      ```

1. 如果目標執行個體上尚未安裝 SSM 代理程式，請安裝。如果目標執行個體上已安裝 SSM 代理程式，請跳過此步驟。

   如需詳細資訊，請參閱在 [ Linux 的 EC2 執行個體上手動安裝 SSM Agent](https://docs.aws.amazon.com/systems-manager/latest/userguide/manually-install-ssm-agent-linux.html)。

1. 確定 SSM 代理程式執行中。如需詳細資訊，請參閱[檢查 SSM 代理程式狀態和啟動代理程式](https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent-status-and-restart.html)。

1. 設定 Amazon EC2 執行個體的 Systems Manager。如需詳細資訊，請參閱《AWS Systems Manager 使用者指南》**中的[為 Amazon EC2 執行個體設定 Systems Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-setting-up-ec2.html)。

------
#### [ Prepare for custom SSM documents ]

**準備目標執行個體自訂 SSM 文件**

1. 如果目標執行個體上尚未安裝 SSM 代理程式，請安裝。如果目標執行個體上已安裝 SSM 代理程式，請跳過此步驟。
   + (Linux 執行個體） 在適用於 [ Linux 的 EC2 執行個體上手動安裝 SSM 代理](https://docs.aws.amazon.com/systems-manager/latest/userguide/manually-install-ssm-agent-linux.html)程式
   + (Windows 執行個體） [ 在 Windows Server 的 EC2 執行個體上使用 SSM Agent](https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent-windows.html)

1. 確定 SSM 代理程式執行中。如需詳細資訊，請參閱[檢查 SSM 代理程式狀態和啟動代理程式](https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent-status-and-restart.html)。

1. 設定 Amazon EC2 執行個體的 Systems Manager。如需詳細資訊，請參閱《AWS Systems Manager 使用者指南》**中的[為 Amazon EC2 執行個體設定 Systems Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-setting-up-ec2.html)。

------

### 步驟 2：準備 SSM 文件
<a name="prep-ssm-doc"></a>

**注意**  
只有自訂 SSM 文件需要執行此步驟。使用 VSS 備份或 SAP HANA 不需要此步驟。對於 VSS Backups 和 SAP HANA，Amazon Data Lifecycle Manager 會使用 AWS 受管 SSM 文件。

如果您要自動化自我管理資料庫的應用程式一致性快照，例如 MySQL、PostgreSQL 或 InterSystems IRIS，則必須建立 SSM 命令文件，其中包含在建立快照之前凍結和排清 I/O 的預先指令碼，以及在建立快照之後解凍 I/O 的後置指令碼。

如果您的 MySQL、PostgreSQL 或 InterSystems IRIS 資料庫使用標準組態，您可以使用以下範例 SSM 文件內容建立 SSM 命令文件。如果您的 MySQL、PostgreSQL 或 InterSystems IRIS 資料庫使用非標準組態，您可以使用以下範例內容做為 SSM 命令文件的起點，然後自訂它以符合您的需求。或者，如果您想要從頭開始建立新的 SSM 文件，可以使用以下空白 SSM 文件範本，並在適當的文件區段中新增前置和後置命令。

**注意下列事項：**  
您有責任確保 SSM 文件會針對資料庫組態執行正確且必要的動作。
只有當 SSM 文件中的前置和後置指令碼可以成功凍結、清除和解凍 I/O 時，才能保證快照保持應用程式一致。
SSM 文件必須包含 `allowedValues` 的必要欄位，包括 `pre-script`、`post-script` 和 `dry-run`。Amazon Data Lifecycle Manager 會根據這些區段的內容，在執行個體上執行命令。如果您的 SSM 文件沒有這些區段，Amazon Data Lifecycle Manager 便會將其視為執行失敗。

------
#### [ MySQL sample document content ]

```
###===============================================================================###
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

# Permission is hereby granted, free of charge, to any person obtaining a copy of this
# software and associated documentation files (the "Software"), to deal in the Software
# without restriction, including without limitation the rights to use, copy, modify,
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
###===============================================================================###
schemaVersion: '2.2'
description: Amazon Data Lifecycle Manager Pre/Post script for MySQL databases
parameters:
  executionId:
    type: String
    default: None
    description: (Required) Specifies the unique identifier associated with a pre and/or post execution
    allowedPattern: ^(None|[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})$
  command:
  # Data Lifecycle Manager will trigger the pre-script and post-script actions during policy execution. 
  # 'dry-run' option is intended for validating the document execution without triggering any commands
  # on the instance. The following allowedValues will allow Data Lifecycle Manager to successfully 
  # trigger pre and post script actions.
    type: String
    default: 'dry-run'
    description: (Required) Specifies whether pre-script and/or post-script should be executed.
    allowedValues:
    - pre-script
    - post-script
    - dry-run

mainSteps:
- action: aws:runShellScript
  description: Run MySQL Database freeze/thaw commands
  name: run_pre_post_scripts
  precondition:
    StringEquals:
    - platformType
    - Linux
  inputs:
    runCommand:
    - |
      #!/bin/bash

      ###===============================================================================###
      ### Error Codes
      ###===============================================================================###
      # The following Error codes will inform Data Lifecycle Manager of the type of error 
      # and help guide handling of the error. 
      # The Error code will also be emitted via AWS Eventbridge events in the 'cause' field.
      # 1 Pre-script failed during execution - 201
      # 2 Post-script failed during execution - 202
      # 3 Auto thaw occurred before post-script was initiated - 203
      # 4 Pre-script initiated while post-script was expected - 204
      # 5 Post-script initiated while pre-script was expected - 205
      # 6 Application not ready for pre or post-script initiation - 206

      ###=================================================================###
      ### Global variables
      ###=================================================================###
      START=$(date +%s)
      # For testing this script locally, replace the below with OPERATION=$1.
      OPERATION={{ command }}
      FS_ALREADY_FROZEN_ERROR='freeze failed: Device or resource busy'
      FS_ALREADY_THAWED_ERROR='unfreeze failed: Invalid argument'
      FS_BUSY_ERROR='mount point is busy'

      # Auto thaw is a fail safe mechanism to automatically unfreeze the application after the 
      # duration specified in the global variable below. Choose the duration based on your
      # database application's tolerance to freeze.
      export AUTO_THAW_DURATION_SECS="60"

      # Add all pre-script actions to be performed within the function below
      execute_pre_script() {
          echo "INFO: Start execution of pre-script"
          # Check if filesystem is already frozen. No error code indicates that filesystem 
          # is not currently frozen and that the pre-script can proceed with freezing the filesystem.
          check_fs_freeze
          # Execute the DB commands to flush the DB in preparation for snapshot
          snap_db
          # Freeze the filesystem. No error code indicates that filesystem was succefully frozen
          freeze_fs

          echo "INFO: Schedule Auto Thaw to execute in ${AUTO_THAW_DURATION_SECS} seconds."
          $(nohup bash -c execute_schedule_auto_thaw  >/dev/null 2>&1 &)
      }

      # Add all post-script actions to be performed within the function below
      execute_post_script() {
          echo "INFO: Start execution of post-script"
          # Unfreeze the filesystem. No error code indicates that filesystem was successfully unfrozen.
          unfreeze_fs
          thaw_db
      }

      # Execute Auto Thaw to automatically unfreeze the application after the duration configured 
      # in the AUTO_THAW_DURATION_SECS global variable.
      execute_schedule_auto_thaw() {
          sleep ${AUTO_THAW_DURATION_SECS}
          execute_post_script
      }

      # Disable Auto Thaw if it is still enabled
      execute_disable_auto_thaw() {
          echo "INFO: Attempting to disable auto thaw if enabled"
          auto_thaw_pgid=$(pgrep -f execute_schedule_auto_thaw | xargs -i ps -hp {} -o pgid)
          if [ -n "${auto_thaw_pgid}" ]; then
              echo "INFO: execute_schedule_auto_thaw process found with pgid ${auto_thaw_pgid}"
              sudo pkill -g ${auto_thaw_pgid}
              rc=$?
              if [ ${rc} != 0 ]; then
                  echo "ERROR: Unable to kill execute_schedule_auto_thaw process. retval=${rc}"
              else
                  echo "INFO: Auto Thaw  has been disabled"
              fi
          fi
      }

      # Iterate over all the mountpoints and check if filesystem is already in freeze state.
      # Return error code 204 if any of the mount points are already frozen.
      check_fs_freeze() {
          for target in $(lsblk -nlo MOUNTPOINTS)
          do
              # Freeze of the root and boot filesystems is dangerous and pre-script does not freeze these filesystems.
              # Hence, we will skip the root and boot mountpoints while checking if filesystem is in freeze state.
              if [ $target == '/' ]; then continue; fi
              if [[ "$target" == *"/boot"* ]]; then continue; fi

              error_message=$(sudo mount -o remount,noatime $target 2>&1)
              # Remount will be a no-op without a error message if the filesystem is unfrozen.
              # However, if filesystem is already frozen, remount will fail with busy error message.
              if [ $? -ne 0 ];then
                  # If the filesystem is already in frozen, return error code 204
                  if [[ "$error_message" == *"$FS_BUSY_ERROR"* ]];then
                      echo "ERROR: Filesystem ${target} already frozen. Return Error Code: 204"
                      exit 204
                  fi
                  # If the check filesystem freeze failed due to any reason other than the filesystem already frozen, return 201
                  echo "ERROR: Failed to check_fs_freeze on mountpoint $target due to error - $errormessage"
                  exit 201
              fi
          done
      } 

      # Iterate over all the mountpoints and freeze the filesystem.
      freeze_fs() {
          for target in $(lsblk -nlo MOUNTPOINTS)
          do
              # Freeze of the root and boot filesystems is dangerous. Hence, skip filesystem freeze 
              # operations for root and boot mountpoints.
              if [ $target == '/' ]; then continue; fi
              if [[ "$target" == *"/boot"* ]]; then continue; fi
              echo "INFO: Freezing $target"
              error_message=$(sudo fsfreeze -f $target 2>&1)
              if [ $? -ne 0 ];then
                  # If the filesystem is already in frozen, return error code 204
                  if [[ "$error_message" == *"$FS_ALREADY_FROZEN_ERROR"* ]]; then
                      echo "ERROR: Filesystem ${target} already frozen. Return Error Code: 204"
                      sudo mysql -e 'UNLOCK TABLES;'
                      exit 204
                  fi
                  # If the filesystem freeze failed due to any reason other than the filesystem already frozen, return 201
                  echo "ERROR: Failed to freeze mountpoint $targetdue due to error - $errormessage"
                  thaw_db
                  exit 201
              fi
              echo "INFO: Freezing complete on $target"
          done
      }

      # Iterate over all the mountpoints and unfreeze the filesystem.
      unfreeze_fs() {
          for target in $(lsblk -nlo MOUNTPOINTS)
          do
              # Freeze of the root and boot filesystems is dangerous and pre-script does not freeze these filesystems.
              # Hence, will skip the root and boot mountpoints during unfreeze as well.
              if [ $target == '/' ]; then continue; fi
              if [[ "$target" == *"/boot"* ]]; then continue; fi
              echo "INFO: Thawing $target"
              error_message=$(sudo fsfreeze -u $target 2>&1)
              # Check if filesystem is already unfrozen (thawed). Return error code 204 if filesystem is already unfrozen.
              if [ $? -ne 0 ]; then
                  if [[ "$error_message" == *"$FS_ALREADY_THAWED_ERROR"* ]]; then
                      echo "ERROR: Filesystem ${target} is already in thaw state. Return Error Code: 205"
                      exit 205
                  fi
                  # If the filesystem unfreeze failed due to any reason other than the filesystem already unfrozen, return 202
                  echo "ERROR: Failed to unfreeze mountpoint $targetdue due to error - $errormessage"
                  exit 202
              fi
              echo "INFO: Thaw complete on $target"
          done    
      }

      snap_db() {
          # Run the flush command only when MySQL DB service is up and running
          sudo systemctl is-active --quiet mysqld.service
          if [ $? -eq 0 ]; then
              echo "INFO: Execute MySQL Flush and Lock command."
              sudo mysql -e 'FLUSH TABLES WITH READ LOCK;'
              # If the MySQL Flush and Lock command did not succeed, return error code 201 to indicate pre-script failure
              if [ $? -ne 0 ]; then
                  echo "ERROR: MySQL FLUSH TABLES WITH READ LOCK command failed."
                  exit 201
              fi
              sync
          else 
              echo "INFO: MySQL service is inactive. Skipping execution of MySQL Flush and Lock command."
          fi
      }

      thaw_db() {
          # Run the unlock command only when MySQL DB service is up and running
          sudo systemctl is-active --quiet mysqld.service
          if [ $? -eq 0 ]; then
              echo "INFO: Execute MySQL Unlock"
              sudo mysql -e 'UNLOCK TABLES;'
          else 
              echo "INFO: MySQL service is inactive. Skipping execution of MySQL Unlock command."
          fi
      }

      export -f execute_schedule_auto_thaw
      export -f execute_post_script
      export -f unfreeze_fs
      export -f thaw_db

      # Debug logging for parameters passed to the SSM document
      echo "INFO: ${OPERATION} starting at $(date) with executionId: ${EXECUTION_ID}"

      # Based on the command parameter value execute the function that supports 
      # pre-script/post-script operation
      case ${OPERATION} in
          pre-script)
              execute_pre_script
              ;;
          post-script)
              execute_post_script
              execute_disable_auto_thaw
              ;;
          dry-run)
              echo "INFO: dry-run option invoked - taking no action"
              ;;
          *)
              echo "ERROR: Invalid command parameter passed. Please use either pre-script, post-script, dry-run."
              exit 1 # return failure
              ;;
      esac

      END=$(date +%s)
      # Debug Log for profiling the script time
      echo "INFO: ${OPERATION} completed at $(date). Total runtime: $((${END} - ${START})) seconds."
```

------
#### [ PostgreSQL sample document content ]

```
###===============================================================================###
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

# Permission is hereby granted, free of charge, to any person obtaining a copy of this
# software and associated documentation files (the "Software"), to deal in the Software
# without restriction, including without limitation the rights to use, copy, modify,
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
###===============================================================================###
schemaVersion: '2.2'
description: Amazon Data Lifecycle Manager Pre/Post script for PostgreSQL databases
parameters:
  executionId:
    type: String
    default: None
    description: (Required) Specifies the unique identifier associated with a pre and/or post execution
    allowedPattern: ^(None|[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})$
  command:
  # Data Lifecycle Manager will trigger the pre-script and post-script actions during policy execution. 
  # 'dry-run' option is intended for validating the document execution without triggering any commands
  # on the instance. The following allowedValues will allow Data Lifecycle Manager to successfully 
  # trigger pre and post script actions.
    type: String
    default: 'dry-run'
    description: (Required) Specifies whether pre-script and/or post-script should be executed.
    allowedValues:
    - pre-script
    - post-script
    - dry-run

mainSteps:
- action: aws:runShellScript
  description: Run PostgreSQL Database freeze/thaw commands
  name: run_pre_post_scripts
  precondition:
    StringEquals:
    - platformType
    - Linux
  inputs:
    runCommand:
    - |
      #!/bin/bash

      ###===============================================================================###
      ### Error Codes
      ###===============================================================================###
      # The following Error codes will inform Data Lifecycle Manager of the type of error 
      # and help guide handling of the error. 
      # The Error code will also be emitted via AWS Eventbridge events in the 'cause' field.
      # 1 Pre-script failed during execution - 201
      # 2 Post-script failed during execution - 202
      # 3 Auto thaw occurred before post-script was initiated - 203
      # 4 Pre-script initiated while post-script was expected - 204
      # 5 Post-script initiated while pre-script was expected - 205
      # 6 Application not ready for pre or post-script initiation - 206

      ###===============================================================================###
      ### Global variables
      ###===============================================================================###
      START=$(date +%s)
      OPERATION={{ command }}
      FS_ALREADY_FROZEN_ERROR='freeze failed: Device or resource busy'
      FS_ALREADY_THAWED_ERROR='unfreeze failed: Invalid argument'
      FS_BUSY_ERROR='mount point is busy'

      # Auto thaw is a fail safe mechanism to automatically unfreeze the application after the 
      # duration specified in the global variable below. Choose the duration based on your
      # database application's tolerance to freeze.
      export AUTO_THAW_DURATION_SECS="60"

      # Add all pre-script actions to be performed within the function below
      execute_pre_script() {
          echo "INFO: Start execution of pre-script"
          # Check if filesystem is already frozen. No error code indicates that filesystem 
          # is not currently frozen and that the pre-script can proceed with freezing the filesystem.
          check_fs_freeze
          # Execute the DB commands to flush the DB in preparation for snapshot
          snap_db
          # Freeze the filesystem. No error code indicates that filesystem was succefully frozen
          freeze_fs

          echo "INFO: Schedule Auto Thaw to execute in ${AUTO_THAW_DURATION_SECS} seconds."
          $(nohup bash -c execute_schedule_auto_thaw  >/dev/null 2>&1 &)
      }

      # Add all post-script actions to be performed within the function below
      execute_post_script() {
          echo "INFO: Start execution of post-script"
          # Unfreeze the filesystem. No error code indicates that filesystem was successfully unfrozen
          unfreeze_fs
      }

      # Execute Auto Thaw to automatically unfreeze the application after the duration configured 
      # in the AUTO_THAW_DURATION_SECS global variable.
      execute_schedule_auto_thaw() {
          sleep ${AUTO_THAW_DURATION_SECS}
          execute_post_script
      }

      # Disable Auto Thaw if it is still enabled
      execute_disable_auto_thaw() {
          echo "INFO: Attempting to disable auto thaw if enabled"
          auto_thaw_pgid=$(pgrep -f execute_schedule_auto_thaw | xargs -i ps -hp {} -o pgid)
          if [ -n "${auto_thaw_pgid}" ]; then
              echo "INFO: execute_schedule_auto_thaw process found with pgid ${auto_thaw_pgid}"
              sudo pkill -g ${auto_thaw_pgid}
              rc=$?
              if [ ${rc} != 0 ]; then
                  echo "ERROR: Unable to kill execute_schedule_auto_thaw process. retval=${rc}"
              else
                  echo "INFO: Auto Thaw  has been disabled"
              fi
          fi
      }

      # Iterate over all the mountpoints and check if filesystem is already in freeze state.
      # Return error code 204 if any of the mount points are already frozen.
      check_fs_freeze() {
          for target in $(lsblk -nlo MOUNTPOINTS)
          do
              # Freeze of the root and boot filesystems is dangerous and pre-script does not freeze these filesystems.
              # Hence, we will skip the root and boot mountpoints while checking if filesystem is in freeze state.
              if [ $target == '/' ]; then continue; fi
              if [[ "$target" == *"/boot"* ]]; then continue; fi

              error_message=$(sudo mount -o remount,noatime $target 2>&1)
              # Remount will be a no-op without a error message if the filesystem is unfrozen.
              # However, if filesystem is already frozen, remount will fail with busy error message.
              if [ $? -ne 0 ];then
                  # If the filesystem is already in frozen, return error code 204
                  if [[ "$error_message" == *"$FS_BUSY_ERROR"* ]];then
                      echo "ERROR: Filesystem ${target} already frozen. Return Error Code: 204"
                      exit 204
                  fi
                  # If the check filesystem freeze failed due to any reason other than the filesystem already frozen, return 201
                  echo "ERROR: Failed to check_fs_freeze on mountpoint $target due to error - $errormessage"
                  exit 201
              fi
          done
      } 

      # Iterate over all the mountpoints and freeze the filesystem.
      freeze_fs() {
          for target in $(lsblk -nlo MOUNTPOINTS)
          do
              # Freeze of the root and boot filesystems is dangerous. Hence, skip filesystem freeze 
              # operations for root and boot mountpoints.
              if [ $target == '/' ]; then continue; fi
              if [[ "$target" == *"/boot"* ]]; then continue; fi
              echo "INFO: Freezing $target"
              error_message=$(sudo fsfreeze -f $target 2>&1)
              if [ $? -ne 0 ];then
                  # If the filesystem is already in frozen, return error code 204
                  if [[ "$error_message" == *"$FS_ALREADY_FROZEN_ERROR"* ]]; then
                      echo "ERROR: Filesystem ${target} already frozen. Return Error Code: 204"
                      exit 204
                  fi
                  # If the filesystem freeze failed due to any reason other than the filesystem already frozen, return 201
                  echo "ERROR: Failed to freeze mountpoint $targetdue due to error - $errormessage"
                  exit 201
              fi
              echo "INFO: Freezing complete on $target"
          done
      }

      # Iterate over all the mountpoints and unfreeze the filesystem.
      unfreeze_fs() {
          for target in $(lsblk -nlo MOUNTPOINTS)
          do
              # Freeze of the root and boot filesystems is dangerous and pre-script does not freeze these filesystems.
              # Hence, will skip the root and boot mountpoints during unfreeze as well.
              if [ $target == '/' ]; then continue; fi
              if [[ "$target" == *"/boot"* ]]; then continue; fi
              echo "INFO: Thawing $target"
              error_message=$(sudo fsfreeze -u $target 2>&1)
              # Check if filesystem is already unfrozen (thawed). Return error code 204 if filesystem is already unfrozen.
              if [ $? -ne 0 ]; then
                  if [[ "$error_message" == *"$FS_ALREADY_THAWED_ERROR"* ]]; then
                      echo "ERROR: Filesystem ${target} is already in thaw state. Return Error Code: 205"
                      exit 205
                  fi
                  # If the filesystem unfreeze failed due to any reason other than the filesystem already unfrozen, return 202
                  echo "ERROR: Failed to unfreeze mountpoint $targetdue due to error - $errormessage"
                  exit 202
              fi
              echo "INFO: Thaw complete on $target"
          done
      }

      snap_db() {
          # Run the flush command only when PostgreSQL DB service is up and running
          sudo systemctl is-active --quiet postgresql
          if [ $? -eq 0 ]; then
              echo "INFO: Execute Postgres CHECKPOINT"
              # PostgreSQL command to flush the transactions in memory to disk
              sudo -u postgres psql -c 'CHECKPOINT;'
              # If the PostgreSQL Command did not succeed, return error code 201 to indicate pre-script failure
              if [ $? -ne 0 ]; then
                  echo "ERROR: Postgres CHECKPOINT command failed."
                  exit 201
              fi
              sync
          else 
              echo "INFO: PostgreSQL service is inactive. Skipping execution of CHECKPOINT command."
          fi
      }

      export -f execute_schedule_auto_thaw
      export -f execute_post_script
      export -f unfreeze_fs

      # Debug logging for parameters passed to the SSM document
      echo "INFO: ${OPERATION} starting at $(date) with executionId: ${EXECUTION_ID}"

      # Based on the command parameter value execute the function that supports 
      # pre-script/post-script operation
      case ${OPERATION} in
          pre-script)
              execute_pre_script
              ;;
          post-script)
              execute_post_script
              execute_disable_auto_thaw
              ;;
          dry-run)
              echo "INFO: dry-run option invoked - taking no action"
              ;;
          *)
              echo "ERROR: Invalid command parameter passed. Please use either pre-script, post-script, dry-run."
              exit 1 # return failure
              ;;
      esac

      END=$(date +%s)
      # Debug Log for profiling the script time
      echo "INFO: ${OPERATION} completed at $(date). Total runtime: $((${END} - ${START})) seconds."
```

------
#### [ InterSystems IRIS sample document content ]

```
###===============================================================================###
# MIT License
# 
# Copyright (c) 2024 InterSystems
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
###===============================================================================###
schemaVersion: '2.2'
description: SSM Document Template for Amazon Data Lifecycle Manager Pre/Post script feature for InterSystems IRIS.
parameters:
  executionId:
    type: String
    default: None
    description: Specifies the unique identifier associated with a pre and/or post execution
    allowedPattern: ^(None|[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})$
  command:
    type: String
    # Data Lifecycle Manager will trigger the pre-script and post-script actions. You can also use this SSM document with 'dry-run' for manual testing purposes.
    default: 'dry-run'
    description: (Required) Specifies whether pre-script and/or post-script should be executed.
    #The following allowedValues will allow Data Lifecycle Manager to successfully trigger pre and post script actions.
    allowedValues:
    - pre-script
    - post-script
    - dry-run

mainSteps:
- action: aws:runShellScript
  description: Run InterSystems IRIS Database freeze/thaw commands
  name: run_pre_post_scripts
  precondition:
    StringEquals:
    - platformType
    - Linux
  inputs:
    runCommand:
    - |
      #!/bin/bash
      ###===============================================================================###
      ### Global variables
      ###===============================================================================###
      DOCKER_NAME=iris
      LOGDIR=./
      EXIT_CODE=0
      OPERATION={{ command }}
      START=$(date +%s)
      
      # Check if Docker is installed
      # By default if Docker is present, script assumes that InterSystems IRIS is running in Docker
      # Leave only the else block DOCKER_EXEC line, if you run InterSystems IRIS non-containerised (and Docker is present).
      # Script assumes irissys user has OS auth enabled, change the OS user or supply login/password depending on your configuration.
      if command -v docker &> /dev/null
      then
        DOCKER_EXEC="docker exec $DOCKER_NAME"
      else
        DOCKER_EXEC="sudo -i -u irissys"
      fi
      
                    
      # Add all pre-script actions to be performed within the function below
      execute_pre_script() {
        echo "INFO: Start execution of pre-script"
        
        # find all iris running instances
        iris_instances=$($DOCKER_EXEC iris qall 2>/dev/null | tail -n +3 | grep '^up' | cut -c5-  | awk '{print $1}')
        echo "`date`: Running iris instances $iris_instances"
      
        # Only for running instances
        for INST in $iris_instances; do
      
          echo "`date`: Attempting to freeze $INST"
      
          # Detailed instances specific log
          LOGFILE=$LOGDIR/$INST-pre_post.log
          
          #check Freeze status before starting
          $DOCKER_EXEC irissession $INST -U '%SYS' "##Class(Backup.General).IsWDSuspendedExt()"
          freeze_status=$?
          if [ $freeze_status -eq 5 ]; then
            echo "`date`:   ERROR: $INST IS already FROZEN"
            EXIT_CODE=204
          else
            echo "`date`:   $INST is not frozen"
            # Freeze
            # Docs: https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=Backup.General#ExternalFreeze
            $DOCKER_EXEC irissession $INST -U '%SYS' "##Class(Backup.General).ExternalFreeze(\"$LOGFILE\",,,,,,600,,,300)"
            status=$?
      
            case $status in
              5) echo "`date`:   $INST IS FROZEN"
                ;;
              3) echo "`date`:   $INST FREEZE FAILED"
                EXIT_CODE=201
                ;;
              *) echo "`date`:   ERROR: Unknown status code: $status"
                EXIT_CODE=201
                ;;
            esac
            echo "`date`:   Completed freeze of $INST"
          fi
        done
        echo "`date`: Pre freeze script finished"
      }
                    
      # Add all post-script actions to be performed within the function below
      execute_post_script() {
        echo "INFO: Start execution of post-script"
      
        # find all iris running instances
        iris_instances=$($DOCKER_EXEC iris qall 2>/dev/null | tail -n +3 | grep '^up' | cut -c5-  | awk '{print $1}')
        echo "`date`: Running iris instances $iris_instances"
      
        # Only for running instances
        for INST in $iris_instances; do
      
          echo "`date`: Attempting to thaw $INST"
      
          # Detailed instances specific log
          LOGFILE=$LOGDIR/$INST-pre_post.log
      
          #check Freeze status befor starting
          $DOCKER_EXEC irissession $INST -U '%SYS' "##Class(Backup.General).IsWDSuspendedExt()"
          freeze_status=$?
          if [ $freeze_status -eq 5 ]; then
            echo "`date`:  $INST is in frozen state"
            # Thaw
            # Docs: https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=Backup.General#ExternalFreeze
            $DOCKER_EXEC irissession $INST -U%SYS "##Class(Backup.General).ExternalThaw(\"$LOGFILE\")"
            status=$?
      
            case $status in
              5) echo "`date`:   $INST IS THAWED"
                  $DOCKER_EXEC irissession $INST -U%SYS "##Class(Backup.General).ExternalSetHistory(\"$LOGFILE\")"
                ;;
              3) echo "`date`:   $INST THAW FAILED"
                  EXIT_CODE=202
                ;;
              *) echo "`date`:   ERROR: Unknown status code: $status"
                  EXIT_CODE=202
                ;;
            esac
            echo "`date`:   Completed thaw of $INST"
          else
            echo "`date`:   ERROR: $INST IS already THAWED"
            EXIT_CODE=205
          fi
        done
        echo "`date`: Post thaw script finished"
      }
      
      # Debug logging for parameters passed to the SSM document
        echo "INFO: ${OPERATION} starting at $(date) with executionId: ${EXECUTION_ID}"
                    
      # Based on the command parameter value execute the function that supports 
      # pre-script/post-script operation
      case ${OPERATION} in
        pre-script)
          execute_pre_script
          ;;
        post-script)
          execute_post_script
            ;;
        dry-run)
          echo "INFO: dry-run option invoked - taking no action"
          ;;
        *)
          echo "ERROR: Invalid command parameter passed. Please use either pre-script, post-script, dry-run."
          # return failure
          EXIT_CODE=1
          ;;
      esac
                    
      END=$(date +%s)
      # Debug Log for profiling the script time
      echo "INFO: ${OPERATION} completed at $(date). Total runtime: $((${END} - ${START})) seconds."
      exit $EXIT_CODE
```

如需更多資訊，請參閱 [GitHub 儲存庫](https://github.com/intersystems-community/aws/blob/master/README.md)。

------
#### [ Empty document template ]

```
###===============================================================================###
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

# Permission is hereby granted, free of charge, to any person obtaining a copy of this
# software and associated documentation files (the "Software"), to deal in the Software
# without restriction, including without limitation the rights to use, copy, modify,
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
###===============================================================================###
schemaVersion: '2.2'
description: SSM Document Template for Amazon Data Lifecycle Manager Pre/Post script feature
parameters:
  executionId:
    type: String
    default: None
    description: (Required) Specifies the unique identifier associated with a pre and/or post execution
    allowedPattern: ^(None|[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})$
  command:
  # Data Lifecycle Manager will trigger the pre-script and post-script actions during policy execution. 
  # 'dry-run' option is intended for validating the document execution without triggering any commands
  # on the instance. The following allowedValues will allow Data Lifecycle Manager to successfully 
  # trigger pre and post script actions.
    type: String
    default: 'dry-run'
    description: (Required) Specifies whether pre-script and/or post-script should be executed.
    allowedValues:
    - pre-script
    - post-script
    - dry-run

mainSteps:
- action: aws:runShellScript
  description: Run Database freeze/thaw commands
  name: run_pre_post_scripts
  precondition:
    StringEquals:
    - platformType
    - Linux
  inputs:
    runCommand:
    - |
      #!/bin/bash

      ###===============================================================================###
      ### Error Codes
      ###===============================================================================###
      # The following Error codes will inform Data Lifecycle Manager of the type of error 
      # and help guide handling of the error. 
      # The Error code will also be emitted via AWS Eventbridge events in the 'cause' field.
      # 1 Pre-script failed during execution - 201
      # 2 Post-script failed during execution - 202
      # 3 Auto thaw occurred before post-script was initiated - 203
      # 4 Pre-script initiated while post-script was expected - 204
      # 5 Post-script initiated while pre-script was expected - 205
      # 6 Application not ready for pre or post-script initiation - 206

      ###===============================================================================###
      ### Global variables
      ###===============================================================================###
      START=$(date +%s)
      # For testing this script locally, replace the below with OPERATION=$1.
      OPERATION={{ command }}

      # Add all pre-script actions to be performed within the function below
      execute_pre_script() {
          echo "INFO: Start execution of pre-script"
      }

      # Add all post-script actions to be performed within the function below
      execute_post_script() {
          echo "INFO: Start execution of post-script"
      }

      # Debug logging for parameters passed to the SSM document
      echo "INFO: ${OPERATION} starting at $(date) with executionId: ${EXECUTION_ID}"

      # Based on the command parameter value execute the function that supports 
      # pre-script/post-script operation
      case ${OPERATION} in
          pre-script)
              execute_pre_script
              ;;
          post-script)
              execute_post_script
              ;;
          dry-run)
              echo "INFO: dry-run option invoked - taking no action"
              ;;
          *)
              echo "ERROR: Invalid command parameter passed. Please use either pre-script, post-script, dry-run."
              exit 1 # return failure
              ;;
      esac

      END=$(date +%s)
      # Debug Log for profiling the script time
      echo "INFO: ${OPERATION} completed at $(date). Total runtime: $((${END} - ${START})) seconds."
```

------

SSM 文件內容就緒後，請使用下列其中一個程序建立自訂 SSM 文件。

------
#### [ Console ]

**建立 SSM 命令文件**

1. 在 https：//[https://console.aws.amazon.com//systems-manager/ ](https://console.aws.amazon.com//systems-manager/)開啟 AWS Systems Manager 主控台。

1. 在導覽窗格中，選擇**文件**，然後選擇**建立文件**、**命令或工作階段**。

1. 對於 **Name** (名稱)，輸入文件的描述性名稱。

1. 在**目標類型**選取 **/AWS::EC2::Instance**。

1. 在**文件類型**選取**命令**。

1. 在**內容**欄位選取 **YAML**，然後貼上文件內容。

1. 在**文件標籤**區段中新增一個標籤，其標籤索引鍵為 `DLMScriptsAccess` 而標籤值為`true`。
**重要**  
在*步驟 3：準備 Amazon Data Lifecycle Manager IAM 角色*中使用的 **AWSDataLifecycleManagerSSMFullAccess** AWS 受管政策需要 `DLMScriptsAccess:true` 標籤。此政策使用 `aws:ResourceTag` 條件索引鍵來限制具有此標籤之 SSM 文件的存取權限。

1. 選擇 **Create document (建立文件)**。

------
#### [ AWS CLI ]

**建立 SSM 命令文件**  
使用 [create-document](https://docs.aws.amazon.com/cli/latest/reference/ssm/create-document.html) 命令。在 `--name` 輸入文件的描述性名稱。對於 `--document-type`，請指定 `Command`。在 `--content` 指定具有 SSM 文件內容之 .yaml 檔案的路徑。對於 `--tags`，請指定 `"Key=DLMScriptsAccess,Value=true"`。

```
$ aws ssm create-document \
--content file:{{//path/to/file/documentContent.yaml}} \
--name "{{document_name}}" \
--document-type "Command" \
--document-format YAML \
--tags "Key=DLMScriptsAccess,Value=true"
```

------

### 步驟 3：準備 Amazon Data Lifecycle Manager IAM 角色
<a name="prep-iam-role"></a>

**注意**  
在下列情況中，需執行此步驟：  
您可以建立或更新使用自訂 IAM 角色且已啟用前置/後置指令碼的快照政策。
您可以使用命令列建立或更新使用預設值且已啟用前置/後置指令碼的快照政策。
如果您是使用主控台建立或更新使用預設角色 (**AWSDataLifecycleManagerDefaultRole**) 來管理快照，且已啟用前置/後置指令碼的快照政策，請略過此步驟。在這種情況下，系統會自動連接 **AWSDataLifecycleManagerSSMFullAccess** 政策至該角色。

您必須確保用於政策的 IAM 角色會授予 Amazon Data Lifecycle Manager 許可，才能在政策鎖定為目標的執行個體上執行前置和後置指令碼所需的 SSM 動作。

Amazon Data Lifecycle Manager 提供包含必要許可的受管政策 (**AWSDataLifecycleManagerSSMFullAccess**)。您可以將此政策連接到 IAM 角色以管理快照，確保其中包含許可。

**重要**  
使用前置和後置指令碼時，AWSDataLifecycleManagerSSMFullAccess 受管政策會使用 `aws:ResourceTag` 條件索引鍵來限制特定 SSM 文件的存取權限。若要允許 Amazon Data Lifecycle Manager 存取 SSM 文件，您必須確保 SSM 文件已用 `DLMScriptsAccess:true` 標記。

或者，您可以手動建立自訂政策，或直接將所需許可指派給您使用的 IAM 角色。您可以使用在 AWSDataLifecycleManagerSSMFullAccess 受管政策中定義的相同許可，不過 `aws:ResourceTag` 條件索引鍵是選用的。如果您決定不包含該條件索引鍵，就不需要使用 `DLMScriptsAccess:true` 來標記 SSM 文件。

使用下列其中一種方法，將 **AWSDataLifecycleManagerSSMFullAccess** 政策新增至您的 IAM 角色。

------
#### [ Console ]

**將受管政策連接至自訂角色**

1. 前往 [https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/) 開啟 IAM 主控台。

1. 在導覽窗格中，選擇 **Roles** (角色)。

1. 搜尋並選取您要管理快照的自訂角色。

1. 在**許可**索引標籤上，依序選擇**新增許可**、**連接政策**。

1. 搜尋並選取 **AWSDataLifecycleManagerSSMFullAccess** 受管政策，然後選擇**新增權限**。

------
#### [ AWS CLI ]

**將受管政策連接至自訂角色**  
使用 [attach-role-policy](https://docs.aws.amazon.com/cli/latest/reference/iam/attach-role-policy.html) 命令。在 `---role-name` 指定自訂角色的名稱。對於 `--policy-arn`，請指定 `arn:aws:iam::aws:policy/AWSDataLifecycleManagerSSMFullAccess`。

```
$ aws iam attach-role-policy \
--policy-arn arn:aws:iam::aws:policy/AWSDataLifecycleManagerSSMFullAccess \
--role-name {{your_role_name}}
```

------

### 步驟 4：建立快照生命週期政策
<a name="prep-policy"></a>

若要自動執行應用程式一致快照，您必須建立以執行個體為目標的快照生命週期政策，並為該政策設定前置和後置指令碼。

------
#### [ Console ]

**建立快照生命週期政策**

1. 前往 [https://console.aws.amazon.com/ec2/](https://console.aws.amazon.com/ec2/) 開啟 Amazon EC2 主控台。

1. 在導覽窗格中，選擇 **Elastic Block Store**、**Lifecycle Manager (生命週期管理員)**，然後選擇 **Create lifecycle policy (建立生命週期政策)**。

1. 在 **Select policy type** (選取政策類型) 畫面中，選取 **EBS snapshot policy** (EBS 快照政策)，然後選取 **Next** (下一步)。

1. 在 **Target resources** (目標資源) 區段中，執行下列動作：

   1. 針對**目標資源類型**，選擇 `Instance`。

   1. 在**目標資源標籤**，指定用來識別待備份磁碟區或執行個體的資源標籤。系統只會備份具有指定標籤的資源。

1. 在 **IAM 角色**選擇 ** AWSDataLifecycleManagerDefaultRole** (管理快照的預設角色)，或選擇您為前置和後置指令碼建立並準備好的自訂角色。

1. 根據需要設定排程和其他選項。建議您針對配合工作負載的期間 (例如維護時段) 排程快照建立時間。

   若使用 SAP HANA，建議您啟用快速快照還原。
**注意**  
如果您為 VSS 備份啟用排程，就無法啟用**排除特定資料磁碟區**或**從來源複製標籤**。

1. 在**前置和後置指令碼**區段中，選取**啟用前置和後置指令碼**，然後根據您的工作負載執行下列操作：
   + 若要建立 Windows 應用程式的應用程式一致快照集，請選取 **VSS 備份**。
   + 若要建立 SAP HANA 工作負載的應用程式一致快照，請選取 **SAP HANA**。
   + 若要使用自訂 SSM 文件建立所有其他資料庫和工作負載的應用程式一致性快照，包括自我管理的 MySQL、PostgreSQL 或 InterSystems IRIS 資料庫，請選取**自訂 SSM 文件**。

     1. 在**自動執行選項**選擇**前置和後置指令碼**。

     1. 在 **SSM 文件**選取您準備好的 SSM 文件。

1. 根據您選取的選項，設定以下其他選項：
   + **指令碼逾時**：(*僅限自訂 SSM 文件*) 此逾時期間過後，如果指令碼未完成，Amazon Data Lifecycle Manager 的指令碼執行嘗試就會失敗。如果指令碼沒有在逾時期間內完成，Amazon Data Lifecycle Manager 的嘗試就會失敗。逾時期限會分別套用至前置和後置指令碼。最低和預設逾時期間為 10 秒。最大逾時期間為 120 秒。
   + **重試失敗的指令碼**：選取此選項可重試在逾時期間內未完成的指令碼。如果前置指令碼失敗，Amazon Data Lifecycle Manager 會重試整個快照建立程序，包括執行前置和後置指令碼。如果後置指令碼失敗，Amazon Data Lifecycle Manager 只會重試後置指令碼；在此情況下，前置指令碼應該已完成，而且快照可能已建立。
   + **預設為當機一致快照**：選取此選項可在前置指令碼執行失敗時，預設為當機一致快照。如果未啟用前置和後置指令碼，這是 Amazon Data Lifecycle Manager 的預設快照建立行為。如果您啟用重試功能，Amazon Data Lifecycle Manager 只會在用盡所有重試嘗試次數之後，才會預設為當機一致快照。如果前置指令碼失敗，且您沒有預設為當機一致快照，Amazon Data Lifecycle Manager 就不會在該排程執行期間為執行個體建立快照。
**注意**  
如果要為 SAP HANA 建立快照，您可能需停用此選項。SAP HANA 工作負載的當機一致快照無法以相同方式還原。

1. 選擇**建立預設政策**。
**注意**  
如果出現 `Role with name AWSDataLifecycleManagerDefaultRole already exists` 錯誤，請參閱 [對 Amazon Data Lifecycle Manager 問題進行故障診斷](dlm-troubleshooting.md) 以取得更多資訊。

------
#### [ AWS CLI ]

**建立快照生命週期政策**  
使用 [create-lifecycle-policy](https://docs.aws.amazon.com/cli/latest/reference/dlm/create-lifecycle-policy.html) 命令，並在 `CreateRule` 中包含 `Scripts` 參數。如需有關參數的詳細資訊，請參閱 [https://docs.aws.amazon.com/dlm/latest/APIReference/API_Script.html](https://docs.aws.amazon.com/dlm/latest/APIReference/API_Script.html)。

```
$ aws dlm create-lifecycle-policy \
--description "{{policy_description}}" \
--state ENABLED \
--execution-role-arn {{iam_role_arn}} \
--policy-details file:{{//policyDetails.json}}
```

根據使用案例而定，`policyDetails.json` 中會包含以下其中一項：
+ **VSS 備份**

  ```
  {
      "PolicyType": "EBS_SNAPSHOT_MANAGEMENT",
      "ResourceTypes": [
          "INSTANCE"
      ],
      "TargetTags": [{
          "Key": "{{tag_key}}",
          "Value": "{{tag_value}}"
      }],
      "Schedules": [{
          "Name": "{{schedule_name}}",
          "CreateRule": {
              "CronExpression": "{{cron_for_creation_frequency}}", 
              "Scripts": [{ 
                  "ExecutionHandler":"AWS_VSS_BACKUP",
                  "ExecuteOperationOnScriptFailure":{{true|false}},
                  "MaximumRetryCount":{{retries (0-3)}}
              }]
          },
          "RetainRule": {
              "Count": {{retention_count}}
          }
      }]
  }
  ```
+ **SAP HANA 備份**

  ```
  {
      "PolicyType": "EBS_SNAPSHOT_MANAGEMENT",
      "ResourceTypes": [
          "INSTANCE"
      ],
      "TargetTags": [{
          "Key": "{{tag_key}}",
          "Value": "{{tag_value}}"
      }],
      "Schedules": [{
          "Name": "{{schedule_name}}",
          "CreateRule": {
              "CronExpression": "{{cron_for_creation_frequency}}", 
              "Scripts": [{ 
                  "Stages": ["PRE","POST"],
                  "ExecutionHandlerService":"AWS_SYSTEMS_MANAGER",
                  "ExecutionHandler":"AWSSystemsManagerSAP-CreateDLMSnapshotForSAPHANA",
                  "ExecuteOperationOnScriptFailure":{{true|false}},
                  "ExecutionTimeout":{{timeout_in_seconds (10-120)}}, 
                  "MaximumRetryCount":{{retries (0-3)}}
              }]
          },
          "RetainRule": {
              "Count": {{retention_count}}
          }
      }]
  }
  ```
+ **自訂 SSM 文件**

  ```
  {
      "PolicyType": "EBS_SNAPSHOT_MANAGEMENT",
      "ResourceTypes": [
          "INSTANCE"
      ],
      "TargetTags": [{
          "Key": "{{tag_key}}",
          "Value": "{{tag_value}}"
      }],
      "Schedules": [{
          "Name": "{{schedule_name}}",
          "CreateRule": {
              "CronExpression": "{{cron_for_creation_frequency}}", 
              "Scripts": [{ 
                  "Stages": ["PRE","POST"],
                  "ExecutionHandlerService":"AWS_SYSTEMS_MANAGER",
                  "ExecutionHandler":"{{ssm_document_name|arn}}",
                  "ExecuteOperationOnScriptFailure":{{true|false}},
                  "ExecutionTimeout":{{timeout_in_seconds (10-120)}}, 
                  "MaximumRetryCount":{{retries (0-3)}}
              }]
          },
          "RetainRule": {
              "Count": {{retention_count}}
          }
      }]
  }
  ```

------

## 使用 Amazon Data Lifecycle Manager 搭配 VSS 備份的考量事項
<a name="app-consistent-vss"></a>

使用 Amazon Data Lifecycle Manager，您可以備份和還原在 Amazon EC2 執行個體上執行且啟用 VSS (磁碟區陰影複製服務) 的 Windows 應用程式。如果應用程式已向 Windows VSS 註冊 VSS 寫入器，Amazon Data Lifecycle Manager 就會為該應用程式建立應用程式一致快照。

**注意**  
Amazon Data Lifecycle Manager 目前僅支援在 Amazon EC2 上執行的資源之應用程式一致快照，尤其是針對使用從備份建立的新執行個體取代現有執行個體來還原應用程式資料的備份案例。並非所有執行個體類型或應用程式都能使用 VSS 備份。如需詳細資訊，請參閱《*Amazon EC2 使用者指南*》中的[應用程式一致 Windows VSS 快照](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/application-consistent-snapshots.html)。

**不支援的執行個體類型**  
VSS 備份不支援下列 Amazon EC2 執行個體類型。如果您的政策是以下列其中一種執行個體類型為目標，Amazon Data Lifecycle Manager 可能仍會建立 VSS 備份，但快照可能不會標記所需的系統標籤。如果沒有這些標籤，Amazon Data Lifecycle Manager 建立快照後將不會管理快照。您可能須手動刪除這些快照。
+ T3：`t3.nano` \| `t3.micro`
+ T3a：`t3a.nano` \| `t3a.micro`
+ T2：`t2.nano` \| `t2.micro`

## 應用程式一致快照的共同責任
<a name="shared-responsibility"></a>

**您必須確保：**
+ SSM 代理程式已安裝、為最新版本，且正在目標執行個體上執行
+ Systems Manager 具有在目標執行個體上執行必要動作的許可
+ Amazon Data Lifecycle Manager 具有在目標執行個體上執行前置和後置指令碼所需的 Systems Manager 動作執行許可。
+ 對於自訂工作負載，例如自我管理的 MySQL、PostgreSQL 或 InterSystems IRIS 資料庫，您使用的 SSM 文件包含凍結、清除和解凍資料庫組態 I/O 的正確和必要動作。
+ 快照建立時間與工作負載排程一致。例如，嘗試在排定的維護時段時段排程快照建立作業。

**Amazon Data Lifecycle Manager 需確保：**
+ 快照建立作業會在排定快照建立時間的 60 分鐘內起始。
+ 快照建立作業起始之前會執行前置指令碼。
+ 後置指令碼會在前置指令碼成功且快照建立作業已起始之後執行。只有在前置指令碼成功時，Amazon Data Lifecycle Manager 才會執行後置指令碼。如果前置指令碼失敗，Amazon Data Lifecycle Manager 將不會執行後置指令碼。
+ 建立快照時會使用適當的標籤來標記快照。
+ CloudWatch 指標和事件會在指令碼起始以及失敗或成功時發出。