﻿# 如何透過 cloud-init 自動化部署 Azure 虛擬機器 (VM)

最近要幫客戶在 Azure 架設一台 Nginx 反向代理伺服器 (Ubuntu 20.04 LTS)，我想透過 [cloud-init](https://cloud-init.io) 將整台 VM 從無到有全自動安裝設定完畢，經過幾個小時的努力，終於達成全自動化建置的目標，實在非常令人振奮。這篇文章我就來說說部署的過程與技術細節。

### 部署方法

以下就是我的最終成果，日後照著 SOP 就可以快速的建立出完全相同需求的 Nginx 反向代理伺服器 VM，而且只要經過一些微調，就可以應用在完全不同的情境上。

1.  先準備好一份 cloud-init 設定檔 (`cloud-init.yml`)
    
    由於我是要架設「反向代理伺服器」，因此其中 `proxy_pass` 指向的網站要先決定好目標網址，以及 `proxy_set_header Host` 的標頭內容也要先依據需求設定好。
    
    ```yaml
    #cloud-config
    apt_update: true
    apt_upgrade: true
    package_upgrade: true
    packages:
      - nginx
    write_files:
      - owner: www-data:www-data
        path: /etc/nginx/sites-available/default
        content: |
          server {
            listen 80;
            location / {
              proxy_pass https://duotify-itblog.azurewebsites.net;
              proxy_http_version 1.1;
              proxy_set_header Upgrade $http_upgrade;
              proxy_set_header Connection keep-alive;
              # proxy_set_header Host $host;
              proxy_set_header Host duotify-itblog.azurewebsites.net;
              proxy_cache_bypass $http_upgrade;
            }
          }
    runcmd:
      - service nginx restart
    ```
    
2.  建立 Azure 資源群組 & 虛擬機器 & 開啟防火牆
    
    執行以下命令之前，要先準備好遠端 SSH 連線所需的 **SSH 公開金鑰檔** (`id_rsa.pub`)！
    
    PowerShell
    
    ```powershell
    az group create -n VM -l japaneast
    
    az vm create -g VM -n FrontNode `
        --image UbuntuLTS `
        --size Standard_DS1_v2 `
        --admin-username azureuser `
        --ssh-key-values '@id_rsa.pub' `
        --custom-data 'cloud-init.yml'
    
    az vm open-port -g VM -n FrontNode --port 80 --priority 900
    
    $IP=(az vm show -d -g VM -n FrontNode --query publicIps -o tsv)
    echo "SSH Login: ssh -l azureuser $IP"
    echo "Nginx URL: http://$IP"
    echo "cURL Test: curl http://$IP -D -"
    ```
    
    Bash
    
    ```powershell
    az group create -n VM -l japaneast
    
    az vm create -g VM -n FrontNode \
        --image UbuntuLTS \
        --size Standard_DS1_v2 \
        --admin-username azureuser \
        --ssh-key-values @id_rsa.pub \
        --custom-data cloud-init.yml
    
    az vm open-port -g VM -n FrontNode --port 80 --priority 900
    
    IP=$(az vm show -d -g VM -n FrontNode --query publicIps -o tsv)
    echo "SSH Login: ssh -l azureuser $IP"
    echo "Nginx URL: http://$IP"
    echo "cURL Test: curl http://$IP -D -"
    ```
    

就醬，兩個步驟就輕鬆完成部署！ 😎

### 技術細節講解: `cloud-init.yml`

1.  該檔案第一行必須為 `#cloud-config` 開頭 (詳見 [Cloud Config Data](https://cloudinit.readthedocs.io/en/latest/topics/format.html#cloud-config-data) 文件)
    
    ```yaml
    #cloud-config
    ```
    
2.  設定 VM 自動更新套件到最新版
    
    ```yaml
    apt_update: true
    apt_upgrade: true
    package_upgrade: true
    ```
    
3.  自動安裝 `nginx` 套件
    
    ```yaml
    packages:
      - nginx
    ```
    
4.  寫入實體檔案並指定檔案擁有者
    
    ```yaml
    write_files:
      - owner: www-data:www-data
        path: /etc/nginx/sites-available/default
        content: |
          server {
            listen 80;
            location / {
              proxy_pass https://duotify-itblog.azurewebsites.net;
              proxy_http_version 1.1;
              proxy_set_header Upgrade $http_upgrade;
              proxy_set_header Connection keep-alive;
              # proxy_set_header Host $host;
              proxy_set_header Host duotify-itblog.azurewebsites.net;
              proxy_cache_bypass $http_upgrade;
            }
          }
    ```
    
5.  部署完成後自動執行命令
    
    ```yaml
    runcmd:
      - service nginx restart
    ```
    

### 技術細節講解: Azure CLI

1.  建立資源群組
    
    ```powershell
    az group create -n VM -l japaneast
    ```
    
2.  建立 `UbuntuLTS` 的虛擬機器
    
    ```powershell
    az vm create -g VM -n FrontNode `
        --image UbuntuLTS `
        --size Standard_DS1_v2 `
        --admin-username azureuser `
        --ssh-key-values '@id_rsa.pub' `
        --custom-data 'cloud-init.yml'
    ```
    
    這裡有好幾個重要的參數：
    
    `--image` 指定你要建立的 VM Image 的名稱，而 `UbuntuLTS` 是一個通用的 Image 名稱，他會自動找到 Ubuntu 最新 LTS 的 Image 來用。以下是查詢完整 VM Image 清單的命令：
    
    ```bash
    # 列出所有 Image 的發行商 (Ubuntu 的發行商為 Canonical)
    az vm image list-publishers --location japaneast --output table
    
    # 列出 VM 發行商的提供方案 (Offers) (UbuntuLTS 來自 0001-com-ubuntu-server-focal 方案)
    az vm image list-offers -l japaneast --publisher Canonical --output table
    
    # 列出 VM 發行商的方案中所有可用的 Image 清單 (輸出結果中的 Urn 欄位，就是可以讓你用來指定建立的 Image 名稱)
    az vm image list -l japaneast --offer 0001-com-ubuntu-server-focal --publisher Canonical --all --output table | grep "2019-Datacenter-with-Containers"
    ```
    
    `--size` 指定你的 VM 規格，想列出完整的 VM 規格清單 (相當多)，可以用以下命令查詢：
    
    ```bash
    az vm list-sizes --location japaneast -o table
    ```
    
    `--admin-username` 指定預設登入的使用者名稱，預設值就是 `azureuser`
    
    `--ssh-key-values` 指定用來 SSH 遠端登入的 SSH Public Key 檔案。不過，在使用 PowerShell 執行時要特別注意 `@` 符號的使用，你必須在 `@id_rsa.pub` 的前後加上**單引號**，確保命令可以正確傳入參數！如果在 Bash 環境的話，就比較沒這個問題。
    
    `--custom-data` 主要用來指定 [cloud-init](https://cloud-init.io) 的設定檔路徑，當 VM 部署後第一次開機，這個 Custom Data 會被自動複製到 `/var/lib/waagent/` 目錄中，並儲存在 `ovf-env.xml` 檔案裡。
    
3.  開啟防火牆設定
    
    以下命令主要用來開放特定 Port 可以讓 Internet 完全公開的存取：
    
    ```bash
    az vm open-port -g VM -n FrontNode --priority 900 --port 80
    ```
    
4.  取得 Azure VM 的實體 IP 地址
    
    ```bash
    az vm show -d -g VM -n FrontNode --query publicIps -o tsv
    ```
    

### 常見問題解答

1.  重新執行 cloud-init 自訂腳本
    
    我剛開始在嘗試 cloud-init 的時候，遇過幾次沒有全自動部署的情況，此時如果你要將 cloud-init 的設定檔重新在已經開機的 VM 上重新執行一遍，可以在 VM 中執行以下命令：
    
    ```bash
    sudo cloud-init clean
    sudo cloud-init init
    ```
    

### 相關連結

-   [Use infrastructure automation tools with virtual machines in Azure](https://learn.microsoft.com/en-us/azure/virtual-machines/infrastructure-automation)
-   [Tutorial - How to use cloud-init to customize a Linux virtual machine in Azure on first boot](https://learn.microsoft.com/en-us/azure/virtual-machines/linux/tutorial-automate-vm-deployment#inject-certificates-from-key-vault)
-   [Cloud Config Data | User-Data Formats — cloud-init 21.4 documentation](https://cloudinit.readthedocs.io/en/latest/topics/format.html#cloud-config-data)
-   [Custom data and cloud-init on Azure Virtual Machines](https://learn.microsoft.com/en-us/azure/virtual-machines/custom-data)
-   [cloud-init - The standard for customising cloud instances](https://cloud-init.io/)
-   [cloud-init Documentation — cloud-init 21.4 documentation](https://cloudinit.readthedocs.io/en/latest/)
-   [How to get public ip address for virtual-machine using Azure CLI command - Stack Overflow](https://stackoverflow.com/a/54980320/910074)
-   [How to re-run cloud-init without reboot - Stack Overflow](https://stackoverflow.com/a/50911376/910074)
