﻿# 如何利用 AzCopy 將 Azure 第一代的儲存體帳戶同步到 StorageV2 的儲存體帳戶

有些事情看起來很複雜，但想通了就會很簡單；不理解的時候很抽象，理解的時候就很直覺。最近在幫客戶搬遷 Azure Storage 儲存體帳戶，照理說透過 AzCopy 應該要非常簡單才是，但是偏偏就是這個客戶的訂用帳戶的儲存體帳戶無法複製檔案過去，而且錯誤訊息相當難以理解，搞了很久才釐清真相。

[AzCopy](https://github.com/Azure/azure-storage-azcopy) v10 的檔案複製功能真的無比強大，真的比舊版還強太多，相對的使用參數也非常多，非常複雜。

> 使用方法可參考我的 [使用新版 AzCopy v10 的注意事項與使用教學](/post/2021/08/22/Using-AzCopy-v10) 文章教學。

基本上使用 `azcopy cp` 複製檔案，支援以下幾種情境：

-   local <-> Azure Blob (SAS or OAuth authentication)
-   local <-> Azure Files (Share/directory SAS authentication)
-   local <-> ADLS Gen 2 (SAS, OAuth, or SharedKey authentication)
-   **Azure Blob (SAS or public)** -> **Azure Blob (SAS or OAuth authentication)**
-   Azure Blob (SAS or public) -> Azure Files (SAS)
-   Azure Files (SAS) -> Azure Files (SAS)
-   Azure Files (SAS) -> Azure Blob (SAS or OAuth authentication)
-   AWS S3 (Access Key) -> Azure Block Blob (SAS or OAuth authentication)
-   Google Cloud Storage (Service Account Key) -> Azure Block Blob (SAS or OAuth authentication) \[Preview\]

這篇文章所涵蓋的範圍，就是從 Azure Blob 複製到 Azure Blob 的某種特殊情況才會出問題，以就讓我娓娓道來這個問題是怎樣發生的。

### 問題描述

我先來說幾個官方文件上會提到的用法：

1.  Azure Blob (public) -> Azure Blob (OAuth authentication)
    
    ```bash
    # 先登入 Azure 透過 OAuth 取得 Access Token
    azcopy login --tenant-id "$tenantId"
    
    # 若是透過 Service Principal 取得 Access Token 則要用以下命令
    # azcopy login --tenant-id "$tenantId" --service-principal --application-id "$clientId"
    
    # 來源 Azure Blob 是公開的 Blob Container 並且具有 List 權限才能進行完整複製
    azcopy cp "https://[storageaccount1].blob.core.windows.net/[container]" "https://[storageaccount2].blob.core.windows.net/[container]" --recursive=true
    ```
    
2.  Azure Blob (SAS) -> Azure Blob (OAuth authentication)
    
    ```bash
    # 先登入 Azure 透過 OAuth 取得 Access Token
    azcopy login --tenant-id "$tenantId"
    
    # 若是透過 Service Principal 取得 Access Token 則要用以下命令
    # azcopy login --tenant-id "$tenantId" --service-principal --application-id "$clientId"
    
    # 來源 Azure Blob 是公開的 Blob Container 並且具有 List 權限才能進行完整複製
    azcopy cp "https://[storageaccount1].blob.core.windows.net/[container]?[SASToken]" "https://[storageaccount2].blob.core.windows.net/[container]" --recursive=true
    ```
    
3.  Azure Blob (public) -> Azure Blob (SAS)
    
    ```powershell
    # 來源 Azure Blob 是公開的 Blob Container 並且具有 List 權限才能進行完整複製
    azcopy cp "https://[storageaccount1].blob.core.windows.net/[container]" "https://[storageaccount2].blob.core.windows.net/[container]?[SASToken]" --recursive=true
    ```
    
4.  Azure Blob (SAS) -> Azure Blob (SAS)
    
    ```bash
    # 來源 Azure Blob 是公開的 Blob Container 並且具有 List 權限才能進行完整複製
    azcopy cp "https://[storageaccount1].blob.core.windows.net/[container]?[SASToken]" "https://[storageaccount2].blob.core.windows.net/[container]?[SASToken]" --recursive=true
    ```
    
    以下是產生 SAS Token 的方法：
    
    PowerShell
    
    ```powershell
    # 取得 SAS Token 到期時間 ( 30 分鐘後 )
    $expiry = (Get-Date).ToUniversalTime().AddMinutes(30).ToString("yyyy-MM-ddTHH:mmZ")
    
    # 產生 SAS Token 的命令
    $SASToken  = az storage container generate-sas `
                   --account-name $storageAccountName `
                   --name $containerName `
                   --permissions acdlrw `
                   --expiry $expiry `
                   --auth-mode login `
                   --as-user `
                   -o tsv
    ```
    
    Bash
    
    ```powershell
    # 取得 SAS Token 到期時間 ( 30 分鐘後 )
    expiry = `date -u -d "30 minutes" '+%Y-%m-%dT%H:%MZ'`
    
    # 產生 SAS Token 的命令
    SASToken  = az storage container generate-sas \
                   --account-name $storageAccountName \
                   --name $containerName \
                   --permissions acdlrw \
                   --expiry $expiry \
                   --auth-mode login \
                   --as-user \
                   -o tsv
    ```
    

其實這些用法都「感覺」很簡單，但是 Azure 儲存體這幾年推出了許多功能，但是這些新功能都被放在 StorageV2 版本下，早期的 Storage (V1) 版本雖然沒有明確的退役時間，但是在 Azure Portal 上其實已經不太容易找到建立的方法，所以大部分的人，只要是新建立的儲存體，通常都會是 StorageV2 的版本。

由於 StorageV2 有支援不同的**存取等級** ([Access Tier](https://learn.microsoft.com/en-us/azure/storage/blobs/access-tiers-overview?WT.mc_id=DT-MVP-4015686))，有分 `Hot`、`Cool` 與 `Archive` 三個等級：

1.  `Hot tier` (預設值)
    
    An online tier optimized for storing data that is accessed or modified frequently. The Hot tier has the highest storage costs, but the lowest access costs.
    
2.  `Cool tier`
    
    An online tier optimized for storing data that is infrequently accessed or modified. Data in the Cool tier should be stored for a minimum of 30 days. The Cool tier has lower storage costs and higher access costs compared to the Hot tier.
    
3.  `Archive tier`
    
    An offline tier optimized for storing data that is rarely accessed, and that has flexible latency requirements, on the order of hours. Data in the Archive tier should be stored for a minimum of 180 days.
    

簡言之，當 [AzCopy](https://github.com/Azure/azure-storage-azcopy) 在兩個儲存體帳戶搬移檔案時，預設會想要讓複製檔案時可以保留來源端的 **存取等級** 屬性，但是早期的 Storage (V1) 版本並沒有 **存取等級** 的概念，所以在複製檔案時，就會遇到錯誤。我從記錄檔中發現的錯誤訊息是：

> RESPONSE Status: 400 Blob access tier is not supported on this storage account type.

完整的訊息如下：

```
2022/01/12 11:27:38 JobID=b9aecf80-b157-ad49-7c75-b3abf6fb1fdf, credential type: Anonymous
2022/01/12 11:27:38 scheduling JobID=b9aecf80-b157-ad49-7c75-b3abf6fb1fdf, Part#=0, Transfer#=0, priority=0
2022/01/12 11:27:38 Final job part has been scheduled
2022/01/12 11:27:38 INFO: [P#0-T#0] has worker 13 which is processing TRANSFER 0
2022/01/12 11:27:38 INFO: [P#0-T#0] Starting transfer: Source "https://[storage1].blob.core.windows.net/[container]?[SAS]" Destination "https://[storage2].blob.core.windows.net/[container]?[SAS]". Specified chunk size 8388608
2022/01/12 11:27:38 ==> REQUEST/RESPONSE (Try=1/65.3928ms, OpTime=260.8969ms) -- RESPONSE SUCCESSFULLY RECEIVED
   GET https://[storage2].blob.core.windows.net/[container]?[SAS]&timeout=901
   X-Ms-Request-Id: [499ae245-001e-0074-29a7-07777d000000]

2022/01/12 11:27:38 ==> REQUEST/RESPONSE (Try=1/63.6402ms, OpTime=63.6402ms) -- RESPONSE STATUS CODE ERROR
   PUT https://[storage2].blob.core.windows.net/[container]?[SAS]&timeout=901
   Content-Length: [0]
   User-Agent: [AzCopy/10.13.0 Azure-Storage/0.14 (go1.16; Windows_NT)]
   X-Ms-Access-Tier: [Hot]
   X-Ms-Blob-Cache-Control: []
   X-Ms-Blob-Content-Disposition: []
   X-Ms-Blob-Content-Encoding: []
   X-Ms-Blob-Content-Language: []
   X-Ms-Blob-Content-Type: [application/octet-stream]
   X-Ms-Blob-Type: [BlockBlob]
   X-Ms-Client-Request-Id: [6adcf20f-29f8-4997-77f6-3d3149068148]
   X-Ms-Copy-Source: [https://[storage1].blob.core.windows.net/[container]?[SAS]]
   X-Ms-Version: [2020-08-04]
   --------------------------------------------------------------------------------
   RESPONSE Status: 400 Blob access tier is not supported on this storage account type.
   Content-Length: [272]
   Content-Type: [application/xml]
   Date: [Wed, 12 Jan 2022 11:27:38 GMT]
   Server: [Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0]
   X-Ms-Client-Request-Id: [6adcf20f-29f8-4997-77f6-3d3149068148]
   X-Ms-Error-Code: [BlobAccessTierNotSupportedForAccountType]
   X-Ms-Request-Id: [499ae25d-001e-0074-3fa7-07777d000000]
   X-Ms-Version: [2020-08-04]
Response Details: <Code>BlobAccessTierNotSupportedForAccountType</Code><Message>Blob access tier is not supported on this storage account type. </Message>

2022/01/12 11:27:38 ERR: [P#0-T#0] COPYFAILED: https://[storage1].blob.core.windows.net/[container]?[SAS] : 400 : 400 Blob access tier is not supported on this storage account type.. When Put Blob from URL. X-Ms-Request-Id: 499ae25d-001e-0074-3fa7-07777d000000

   Dst: https://[storage2].blob.core.windows.net/[container]?[SAS]
```

基本上這個錯誤訊息跟權限沒甚麼關係，主要還是因為 AzCopy 預設會保留來源儲存體帳戶的 Access-Tier 資訊，當帶給目的端的 `PUT` 操作時，就發生了問題。

由於訊息還算清楚，所以順藤摸瓜還是可以找到蛛絲馬跡，我就依循這個線索，透過 `azcopy cp -h` 找到了 `--s2s-preserve-access-tier` 這個參數選項，想不到一試就通，檔案可以順利且快速的複製了！ 😃

完整的命令如下：

```bash
azcopy cp "https://[storageaccount1].blob.core.windows.net/[container]?[SASToken]" "https://[storageaccount2].blob.core.windows.net/[container]?[SASToken]" --recursive=true --s2s-preserve-access-tier=false
```

### 總結

當你想從 Storage (V1) 的儲存體帳號，透過 AzCopy\](https://github.com/Azure/azure-storage-azcopy) v10 複製 Blob 容器或檔案到 StorageV2 的儲存體帳戶，記得要加上 `--s2s-preserve-access-tier=false` 參數！

### 相關連結

-   [Copy blobs between Azure storage accounts by using AzCopy](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azcopy-blobs-copy)
-   [Azure/azure-storage-azcopy: The new Azure Storage data transfer utility - AzCopy v10](https://github.com/Azure/azure-storage-azcopy)
-   [Hot, Cool, and Archive access tiers for blob data](https://learn.microsoft.com/en-us/azure/storage/blobs/access-tiers-overview?WT.mc_id=DT-MVP-4015686)
-   [使用新版 AzCopy v10 的注意事項與使用教學](/post/2021/08/22/Using-AzCopy-v10)
-   [Cannot upload files to Azure General purpose V1 type storage account - Stack Overflow](https://stackoverflow.com/a/70680905/910074)
