執行關機指令碼

建立及執行關機指令碼,在虛擬機器 (VM) 執行個體停止或重新啟動前執行指令。如果您依賴自動指令碼來啟動和關閉執行個體、讓執行個體清理或執行任務 (例如匯出記錄或與其他系統同步),這功能會非常實用。

關機指令碼特別適用於代管執行個體群組中,具有自動配置器的 VM。如果自動配置器關閉群組中的 VM,關機指令碼會在 VM 停止前執行,並執行您定義的任何動作。指令碼會在 VM 停止前的有限關機期間執行。舉例來說,您的關機指令碼可能會將已處理的資料複製到 Cloud Storage 或備份任何記錄檔。

關機指令碼的功能與開機指令碼非常相似。開機指令碼的多數說明文件也適用於關機指令碼。

無論是關機或重新啟動工作,VM 一律會執行關機指令碼,如下所示:

  • 如果是 Linux VM,請使用 root 使用者。
  • 如果是 Windows VM,請使用 System 帳戶。

必要條件

如要執行儲存在 VM 執行個體中繼資料中的指令碼,必須安裝並執行訪客環境。

  • 訪客環境包含訪客代理程式 (例如 Linux 上的 google-guest-agent),可從執行個體的中繼資料讀取指令碼內容或網址,並啟動執行作業。
  • 所有公開的 Compute Engine 映像檔都預先安裝了訪客環境。
  • 如果您建立自訂映像檔,則必須手動安裝 Google 客體環境,確保中繼資料和其他 Google Cloud 功能的指令碼正常運作。

事前準備

  • 瞭解開機指令碼。
  • 瞭解何謂中繼資料伺服器。
  • 如果尚未設定驗證,請先完成設定。驗證程序會確認您的身分,以便授予 Google Cloud 服務和 API 的存取權。如要從本機開發環境執行程式碼或範例,您可以選取下列其中一個選項,向 Compute Engine 進行驗證:

    請根據您打算如何使用本頁範例,選取對應的分頁標籤:

    控制台

    使用 Google Cloud 控制台存取 Google Cloud 服務和 API 時,您不需要設定驗證機制。

    gcloud

    1. 安裝 Google Cloud CLI。 安裝完成後,請執行下列指令,初始化 Google Cloud CLI:

      gcloud init

      若您採用的是外部識別資訊提供者 (IdP),請先 使用聯合身分登入 gcloud CLI。

  • 設定預設區域和可用區。
  • Terraform

    如要在本機開發環境中使用本頁的 Terraform 範例,請安裝並初始化 gcloud CLI,然後使用使用者憑證設定應用程式預設憑證。

    1. 安裝 Google Cloud CLI。

    2. 若您採用的是外部識別資訊提供者 (IdP),請先 使用聯合身分登入 gcloud CLI。

    3. 如果您使用的是本機殼層,請為使用者帳戶建立本機驗證憑證:

      gcloud auth application-default login

      如果您使用的是 Cloud Shell,則不必執行這項操作。

      如果系統傳回驗證錯誤,且您使用的是外部識別資訊提供者 (IdP),請確認您已 使用聯合身分登入 gcloud CLI。

    詳情請參閱「 為本機開發環境設定驗證機制」。

    REST

    如要在本機開發環境中使用本頁的 REST API 範例,請使用您提供給 gcloud CLI 的憑證。

      安裝 Google Cloud CLI。

      若您採用的是外部識別資訊提供者 (IdP),請先 使用聯合身分登入 gcloud CLI。

    詳情請參閱 Google Cloud 驗證說明文件中的「使用 REST 進行驗證」一節。

這項工作需要的權限

如要執行這項工作,您必須具備下列權限:

  • 建立執行個體需要的所有權限
  • 執行個體的 compute.instances.setMetadata 權限

規格

以下各節將說明關機指令碼的規格。

限制

使用關機指令碼時,一些必須注意的限制如下:

  • Compute Engine 會儘可能執行關機指令碼,但不保證能在所有情況下都完全執行關機指令碼 (有極少數情況無法完成)。
  • 在 Windows 上,本機群組原則用於啟動關機指令碼。
    • 安裝套件會設定本機群組原則 Computer Configuration/Windows Setting/Scripts (Startup/Shutdown),在系統關機時啟動指令碼。

關機指令碼叫用

發生下列任一事件後,執行個體關閉時會觸發關機指令碼:

  • 您將執行下列操作:

  • Compute Engine 會在執行程式化停止或刪除作業時,停止執行個體。

  • Compute Engine 會在先占程序中停止 Spot VM 或先占執行個體。

關機指令碼可以是任何檔案類型。如果執行個體有關機指令碼,Compute Engine 會執行下列操作:

  1. 將指令碼複製到執行個體中的本機檔案。
  2. 設定檔案權限,讓指令碼可執行。
  3. 在執行個體關機時執行檔案。

例如,您可以提供 Python 指令碼而不是 Bash 指令碼。請注意,Compute Engine 會確實地執行指令碼,無論其類型為何。

如要執行非 Bash 指令碼,請在檔案頂端新增一個工作行,讓作業系統知道要使用哪個解譯器。舉例來說,您可以針對 Python 指令碼新增如下的工作行:

#!/usr/bin/python

關機指令碼執行時間

Compute Engine 將執行個體狀態設為 STOPPING 時,關機指令碼就會開始執行。指令碼必須在執行個體完全停止 (即狀態變更為 TERMINATED) 之前完成。執行個體進入 TERMINATED 狀態所需的時間,取決於執行個體類型。

如果指令碼執行時間超過執行個體停止所需的時間,Compute Engine 會強制停止指令碼,這可能會導致資料遺失或工作未完成。為避免這種情況,請在客體 OS 中停止執行個體。這種做法會將執行個體狀態保持在 STOPPING,直到關機指令碼執行完畢。

使用本機關機指令碼

本機關機指令碼是存放在本機電腦的指令碼。您可以將本機關機指令碼透過檔案的形式傳入,或是直接將內容傳給 Compute Engine。

關機指令碼可以根據需要執行不限次數的操作,但如果您在本機傳入檔案,則指令碼長度不得超過中繼資料值的上限 256 KB。如要使用長度超過上限的指令碼,請將檔案儲存至 Cloud Storage。詳情請參閱透過 Cloud Storage 使用關機指令碼一節。

提供關機指令碼檔案

您只能透過 gcloud 指令列工具傳入本機關機指令碼檔案。

gcloud

如要傳入本機關機指令碼檔案,請加入 --metadata-from-file 旗標並在後面加上中繼資料鍵值組合 shutdown-script=PATH/TO/FILE,其中 PATH/TO/FILE 是關機指令碼的相對路徑。例如:

gcloud compute instances create example-instance \
    --metadata-from-file shutdown-script=examples/scripts/install.sh

Terraform

如要直接指定關機指令碼,請使用 google_compute_instance 資源,並在中繼資料內提供關機指令碼的路徑。

resource "google_compute_instance" "shutdown_content_from_file" {
  name         = "instance-name-shutdown-content-from-file"
  machine_type = "f1-micro"
  zone         = "us-central1-c"
  metadata = {
    # Shuts down Apache server
    shutdown-script = file("${path.module}/shutdown-script.sh")
  }
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-13"
    }
  }
  network_interface {
    # A default network is created for all Google Cloud projects
    network = "default"
    access_config {
    }
  }
}

如要瞭解如何套用或移除 Terraform 設定,請參閱「基本 Terraform 指令」。

直接提供關機指令碼

您也可以直接傳入關機指令碼內容。

控制台

在 Google Cloud 控制台中,直接使用 shutdown-script 中繼資料鍵指定關機指令碼:

  1. 前往「Create an instance」(建立執行個體) 頁面。

    前往「Create an instance」(建立執行個體)

  2. 指定 VM 詳細資料。

  3. 展開「Advanced options」(進階選項) 區段。

  4. 展開「管理」,然後執行下列操作:

    1. 在「中繼資料」部分,按一下「新增項目」。
    2. 在中繼資料鍵的「Key」(鍵) 欄位中,輸入 shutdown-script。
    3. 在「Value」(值) 欄位中,新增關機指令碼的內容。
  5. 繼續執行 VM 建立程序。

gcloud

使用 Google Cloud CLI 時,請透過 --metadata 旗標提供關機指令碼內容,後面加上 shutdown-script=CONTENTS 金鑰組,其中 CONTENTS 是關機指令碼內容。

gcloud compute instances create example-instance --metadata shutdown-script="#! /bin/bash
> # Shuts down Apache server
> /etc/init.d/apache2 stop"

Terraform

如要直接指定關機指令碼,請使用 google_compute_instance 資源,並在中繼資料中加入關機指令碼。

resource "google_compute_instance" "default" {
  name         = "instance-name-shutdown-content-directly"
  machine_type = "f1-micro"
  zone         = "us-central1-c"
  metadata = {
    # Shuts down Apache server
    shutdown-script = "#! /bin/bash /etc/init.d/apache2 stop"
  }
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-13"
    }
  }
  network_interface {
    # A default network is created for all Google Cloud projects
    network = "default"
    access_config {
    }
  }
}

如要瞭解如何套用或移除 Terraform 設定,請參閱「基本 Terraform 指令」。

REST

在 API 中,建立執行個體時,請在要求中提供做為中繼資料屬性的關機指令碼。使用 shutdown-script 做為中繼資料鍵值:

POST https://compute.googleapis.com/compute/v1/projects/myproject/zones/us-central1-a/instances

{...
  "metadata": {
    "items": [
      {
       "key": "shutdown-script",
       "value": "#! /bin/bash\n\n# Shuts down Apache server\n/etc/init.d/apache2 stop"
      }
    ]
  }...
}

在 Windows 執行個體上提供關機指令碼

使用以下 Windows 專用中繼資料鍵值,在 Windows 執行個體上執行關機指令碼。可從下面列出的所有特殊鍵中加以選擇。每組鍵值都應與您要執行的指令碼類型相符。

您可以透過向執行個體傳入不同的鍵值來指定多個關機指令碼,但每個虛擬機器只能指定一組鍵值。

下列金鑰可與本文件前述相同操作說明的本機關機指令碼搭配使用。

cmd 關機指令碼 bat 關機指令碼 ps1 關機指令碼
windows-shutdown-script-cmd windows-shutdown-script-bat windows-shutdown-script-ps1

透過 Cloud Storage 使用關機指令碼

您可以透過 Cloud Storage 儲存和使用關機指令碼。請按照開機指令碼說明文件中的指示操作,但要將 startup-script-url 替換成 shutdown-script-url。

如果是 Windows 執行個體,請將 windows-startup-script-url 替換成 windows-shutdown-script-url。

為執行中的執行個體套用關機指令碼

如要將關機指令碼新增至執行中的執行個體,請按照將開機指令碼套用至執行中的執行個體說明文件的指示操作,但是要將中繼資料鍵換成下列鍵值:

  • shutdown-script:直接使用這個鍵值提供關機指令碼內容。使用 Google Cloud CLI 時,您可以透過 --metadata-from-file 旗標和 shutdown-script 中繼資料鍵,提供關機指令碼檔案的路徑。
  • shutdown-script-url:使用這個鍵值將 Cloud Storage URL 提供給關機指令碼檔案。

在正常關機期間執行關機指令碼

根據預設,如果運算執行個體已啟用正常關機功能,則您在中繼資料鍵中設定的任何關機指令碼,都會在正常關機結束後執行。

如要在正常關機期間執行指令,您必須使用自訂監控指令碼,在背景持續輪詢中繼資料伺服器,如本節所示。為確保指令碼有足夠時間完成,並避免潛在的資料遺失或工作未完成,建議您修改關機指令碼,將邏輯移至這個監控指令碼。

如要在正常關機期間執行關機指令碼,請根據運算執行個體使用的客體 OS,選取下列其中一個選項:

Linux

  1. 如果您尚未連線至運算執行個體,請先完成這項操作。

  2. 執行監控指令碼,或設定在啟動時執行,例如:在指定位置新增特定關閉邏輯:

    #!/bin/bash
    
    # --------------------------------------------------------
    # Script for graceful shutdown based on the metadata value
    # --------------------------------------------------------
    #
    # This script monitors the metadata key `shutdown-status` and
    # waits for its value to change to `PENDING_STOP`. After the script
    # runs a series of user-defined commands, it runs `sudo shutdown -h now`
    # to signal to Compute Engine that the shutdown process is
    # complete.
    #
    # To run this script, you must have passwordless sudo privileges.
    
    METADATA_URL="http://metadata.google.internal/computeMetadata/v1/instance/attributes/shutdown-status"
    LAST_ETAG="0"
    
    echo "Starting shutdown status watcher"
    
    while true; do
      # Wait for the metadata value to change.
      # The metadata server blocks the request until a change occurs or a timeout is reached.
      RESPONSE=$(curl -H "Metadata-Flavor: Google" -s -i "${METADATA_URL}?wait_for_change=true&last_etag=${LAST_ETAG}")
      CURL_EXIT_STATUS=$?
    
      if [[ ${CURL_EXIT_STATUS} -ne 0 ]]; then
        echo "Error: curl command failed with exit status ${CURL_EXIT_STATUS}. Retrying in 5 seconds..."
        sleep 5
        continue
      fi
    
      # Strip carriage returns (\r) immediately to prevent CRLF line endings from corrupting string comparisons
      RESPONSE=$(echo "$RESPONSE" | tr -d '\r')
    
      # Extract the HTTP status code from the first line of the response
      HTTP_STATUS=$(echo "$RESPONSE" | head -n 1 | awk '{print $2}')
    
      if [[ "${HTTP_STATUS}" -eq 200 ]]; then
        # Read the new etag value to re-subscribe on the next poll.
        NEW_ETAG=$(echo "$RESPONSE" | grep -i "^ETag:" | awk '{print $2}')
        if [[ -n "${NEW_ETAG}" ]]; then
          LAST_ETAG="${NEW_ETAG}"
        fi
    
        # Extract the value of the shutdown status from the last line of the response body.
        SHUTDOWN_STATUS=$(echo "$RESPONSE" | tail -n 1)
    
        echo "Metadata changed. New ETag: ${LAST_ETAG}, shutdown-status: '${SHUTDOWN_STATUS}'"
    
        if [[ "${SHUTDOWN_STATUS}" == "PENDING_STOP" ]]; then
          echo "Shutdown signal 'PENDING_STOP' received. Exiting watcher loop."
          break
        fi
      elif [[ "${HTTP_STATUS}" -eq 503 ]]; then
        # 503 Service Unavailable: wait_for_change timed out with no change. Sleep and loop again.
        echo "Service temporarily unavailable or request timed out. Retrying in 2 seconds..."
        sleep 2
        continue
      else
        # Unexpected HTTP status
        echo "Warning: Unexpected HTTP status ${HTTP_STATUS}. Retrying in 5 seconds..."
        sleep 5
      fi
    done
    
    # --------------------------------------------------------
    # Graceful shutdown commands section
    # --------------------------------------------------------
    echo "Watcher loop exited. Starting graceful shutdown procedures..."
    
    # --- ADD YOUR GRACEFUL SHUTDOWN COMMANDS HERE ---
    
    echo "Graceful shutdown commands executed."
    
    # --------------------------------------------------------
    # Signal OS to stop
    # --------------------------------------------------------
    echo "Shutting down the guest OS to turn off the compute instance..."
    sudo shutdown -h now
    

Windows

  1. 如果尚未連線至運算執行個體,請使用下列其中一種方法:

  2. 執行監控指令碼,或設定在啟動時執行,例如:在指定位置新增特定的關閉邏輯:

    <#
    .SYNOPSIS
    pre-shutdown-script.ps1
    #>
    
    # --------------------------------------------------------
    # Script for graceful shutdown based on the metadata value
    # --------------------------------------------------------
    #
    # This script monitors the metadata key `shutdown-status` and
    # waits for its value to change to `PENDING_STOP`. After the script
    # runs a series of user-defined commands, it runs `Stop-Computer -Force`
    # to signal to Compute Engine that the shutdown process is
    # complete.
    
    $METADATA_URL = "http://metadata.google.internal/computeMetadata/v1/instance/attributes/shutdown-status"
    $LAST_ETAG = "0"
    
    Write-Host "Starting shutdown status watcher"
    
    while ($true) {
        $Response = $null
        $HTTP_STATUS = 0
    
        # Wait for the metadata value to change.
        # The metadata server blocks the request until a change occurs or a timeout is reached.
        try {
            $Response = Invoke-WebRequest `
                -Headers @{"Metadata-Flavor" = "Google"} `
                -Uri "${METADATA_URL}?wait_for_change=true&last_etag=${LAST_ETAG}" `
                -TimeoutSec 320 `
                -ErrorAction Stop
    
            if ($null -ne $Response) {
                $HTTP_STATUS = [int]$Response.StatusCode
            }
        } catch {
            # Invoke-WebRequest throws an error for non-2xx status codes or connection failures.
            $Exception = $_.Exception
            if ($null -ne $Exception -and $null -ne $Exception.Response) {
                $HTTP_STATUS = [int]$Exception.Response.StatusCode
            } else {
                $HTTP_STATUS = 0
            }
        }
    
        if ($HTTP_STATUS -eq 0) {
            Write-Host "Error: Request failed. Retrying in 5 seconds..."
            Start-Sleep -Seconds 5
            continue
        }
    
        if ($HTTP_STATUS -eq 200) {
            # Read the new etag value to re-subscribe on the next poll.
            $NEW_ETAG = $Response.Headers["ETag"]
            if ($null -ne $NEW_ETAG) {
                $LAST_ETAG = $NEW_ETAG
            }
    
            # Extract the value of the shutdown status from the response body.
            $SHUTDOWN_STATUS = $Response.Content.Trim()
    
            Write-Host "Metadata changed. New ETag: ${LAST_ETAG}, shutdown-status: '${SHUTDOWN_STATUS}'"
    
            if ($SHUTDOWN_STATUS -eq "PENDING_STOP") {
                Write-Host "Shutdown signal 'PENDING_STOP' received. Exiting watcher loop."
                break
            }
        } elseif ($HTTP_STATUS -eq 503) {
            # 503 Service Unavailable: wait_for_change timed out with no change. Sleep and loop again.
            Write-Host "Service temporarily unavailable or request timed out. Retrying in 2 seconds..."
            Start-Sleep -Seconds 2
            continue
        } else {
            # Unexpected HTTP status
            Write-Host "Warning: Unexpected HTTP status ${HTTP_STATUS}. Retrying in 5 seconds..."
            Start-Sleep -Seconds 5
        }
    }
    
    # --------------------------------------------------------
    # Graceful shutdown commands section
    # --------------------------------------------------------
    Write-Host "Watcher loop exited. Starting graceful shutdown procedures..."
    
    # --- ADD YOUR GRACEFUL SHUTDOWN COMMANDS HERE ---
    
    Write-Host "Graceful shutdown commands executed."
    
    # --------------------------------------------------------
    # Signal OS to Halt
    # --------------------------------------------------------
    Write-Host "Shutting down the guest OS to turn off the compute instance..."
    Stop-Computer -Force
    

查看關機指令碼的輸出內容

Linux

您可以透過下列任一方式,查看 Linux 關機指令碼的輸出內容:

Windows

如要查看 Windows Server 關機指令碼的輸出內容,請使用下列任一方法,並檢查 GCEMetadataScripts 事件: