description: “本文深入探討如何運用 Packer 自動化建構 Azure 虛擬機映像。內容從準備 Azure 服務主體開始,詳細解析 Packer 模板中的 builders、variables 與 provisioners 區塊,並展示如何使用 shell 配置器安裝軟體及執行通用化。進一步地,文章闡述了整合 Ansible…” 在現代雲端維運與 DevOps 實踐中,標準化且不可變的「黃金映像檔」(Golden Image)是確保環境一致性與加速部署的關鍵基石。傳統手動建構流程不僅耗時,且容易導致組態漂移(Configuration Drift)。本文旨在闡述如何透過基礎架構即程式碼(IaC)工具 Packer,建立一套可重複的映像建構管線。文章從基礎的 Packer 模板結構切入,說明如何定義 Azure 平台上的建構器與變數,並透過 Shell 腳本進行基礎配置。接著,將探討如何整合 Ansible 這類成熟的組態管理工具,將既有的維運腳本無縫融入映像檔的生命週期管理,實現從程式碼到可部署映像的端到端自動化。
Azure 映像構建實踐:Packer 模板詳解
在掌握了 Packer 模板的各個組件後,我們現在將實際構建一個用於在 Azure 環境中創建 Linux 虛擬機映像的模板。這個過程需要先準備 Azure 服務主體 (Service Principal),然後編寫包含 builders、variables 和 provisioners 的 Packer 模板文件。
步驟一:準備 Azure 服務主體 (Service Principal)
為了讓 Packer 能夠在您的 Azure 訂閱中創建資源(如虛擬機、映像),需要一個具有相應權限的服務主體。這個過程與為 Terraform 配置 Azure 環境類似。
- 創建服務主體: 使用 Azure CLI 或 Azure 入口網站創建一個服務主體。建議為其分配最低必要的權限,例如「參與者」(Contributor) 角色,並將其作用域限定在特定的資源組或訂閱。
- 獲取認證資訊: 創建服務主體後,您需要獲取以下關鍵資訊,這些資訊將用於 Packer 模板中的變數:
client_id(應用程式 ID)client_secret(密碼)subscription_id(訂閱 ID)tenant_id(租戶 ID)
步驟二:創建 Packer 模板文件 (JSON 格式)
我們將創建一個名為 azure_linux.json 的文件,並開始編寫 Packer 模板。
1. builders 區塊:定義 Azure 構建目標
此區塊指定了映像構建的目標平台為 Azure,並配置了連接和映像屬性。
{
"builders": [
{
"type": "azure-arm",
"client_id": "{{user 'clientid'}}",
"client_secret": "{{user 'clientsecret'}}",
"subscription_id": "{{user 'subscriptionid'}}",
"tenant_id": "{{user 'tenantid'}}",
"os_type": "Linux",
"image_publisher": "Canonical",
"image_offer": "UbuntuServer",
"image_sku": "18.04-LTS",
"location": "West Europe",
"vm_size": "Standard_DS2_v3",
"managed_image_resource_group_name": "{{user 'resource_group'}}",
"managed_image_name": "{{user 'image_name'}}-{{user 'image_version'}}",
"azure_tags": {
"version": "{{user 'image_version'}}",
"role": "WebServer"
}
}
],
// ... variables 和 provisioners 區塊將在此處添加 ...
}
區塊解析:
type: "azure-arm": 指定使用 Azure Resource Manager 的構建器。- 認證變數:
client_id,client_secret,subscription_id,tenant_id都通過{{user '...'}}引用變數,這些變數將在執行時提供,以確保安全。 - 基礎映像:
os_type,image_publisher,image_offer,image_sku定義了基礎的 Ubuntu 18.04 LTS 映像。 - 部署設定:
location指定了映像創建的 Azure 區域,vm_size設定了臨時虛擬機的大小。 - 映像命名與位置:
managed_image_resource_group_name指定了最終映像將被存儲的資源組,managed_image_name則結合了映像名稱和版本變數來生成唯一的映像名稱。 - 標籤 (
azure_tags): 為創建的映像附加了元數據標籤,便於管理和識別。
2. variables 區塊:定義動態參數
此區塊定義了模板中使用的變數及其預設值或來源。
{
// ... builders 區塊 ...
"variables": {
"subscriptionid": "{{env 'AZURE_SUBSCRIPTION_ID'}}",
"clientid": "{{env 'AZURE_CLIENT_ID'}}",
"clientsecret": "{{env 'AZURE_CLIENT_SECRET'}}",
"tenantid": "{{env 'AZURE_TENANT_ID'}}",
"resource_group": "rg_images",
"image_name": "linuxWeb",
"image_version": "0.0.1"
},
// ... provisioners 區塊 ...
}
變數說明:
- 認證變數:
subscriptionid,clientid,clientsecret,tenantid通過{{env '...'}}引用環境變數。這意味著您需要在運行 Packer 前設置這些環境變數。 - 映像屬性變數:
resource_group: 指定了映像將被創建的 Azure 資源組名稱。image_name: 設定了映像的基本名稱。image_version: 設定了映像的版本號。 這些變數的組合(image_name-image_version)用於動態生成最終的映像名稱,方便版本管理。
3. provisioners 區塊:配置和加固映像
此區塊定義了在臨時虛擬機上執行的腳本,用於安裝必要的軟體並進行系統配置。
{
// ... builders 和 variables 區塊 ...
"provisioners": [
{
"type": "shell",
"execute_command": "sudo sh -c '{{ .Vars }} {{ .Path }}'",
"inline": [
"apt-get update",
"apt-get -y install nginx"
]
},
{
"type": "shell",
"execute_command": "sudo sh -c '{{ .Vars }} {{ .Path }}'",
"inline": [
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
]
}
]
}
配置器說明:
- 第一個
shell配置器:apt-get update: 更新 Ubuntu 的套件列表。apt-get -y install nginx: 自動安裝 Nginx Web 伺服器。
- 第二個
shell配置器:- 執行 Linux 映像的通用化步驟,包括運行
waagent進行去配置和用戶移除,清空命令歷史記錄,並確保數據同步。
- 執行 Linux 映像的通用化步驟,包括運行
執行 Packer 構建
在完成 azure_linux.json 文件的編寫後,您需要設置必要的環境變數,然後執行 Packer 命令來構建映像。
設置環境變數:
export AZURE_SUBSCRIPTION_ID="your_subscription_id" export AZURE_CLIENT_ID="your_client_id" export AZURE_CLIENT_SECRET="your_client_secret" export AZURE_TENANT_ID="your_tenant_id"執行 Packer 命令:
packer init azure_linux.json packer build azure_linux.jsonpacker init會下載所需的插件(例如azure-arm插件)。packer build命令將啟動構建過程。
Packer 會按照模板中的定義,在 Azure 中創建一個臨時虛擬機,執行配置腳本,然後將其轉換為一個名為 linuxWeb-0.0.1(或您指定的其他名稱)的託管映像,並存儲在 rg_images 資源組中。
視覺化模板結構
以下圖示展示了 Packer 模板的主要結構及其關聯性。
@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
start
:Packer 模板 (JSON/HCL); :builders 區塊;
:定義目標平台 (Azure);
:配置認證 (Service Principal);
:指定基礎映像 (Ubuntu);
:設定映像屬性 (位置, 大小, 名稱);
:variables 區塊;
:定義動態參數;
:連結至環境變數或預設值;
:用於 builders 和 provisioners;
:provisioners 區塊;
:執行配置腳本;
:安裝軟體 (Nginx);
:執行通用化 (waagent);
:Packer 執行構建;
:創建臨時 VM;
:應用 provisioners;
:創建最終映像;
stop
@enduml
看圖說話:
此圖示描繪了 Packer 模板的核心結構及其在映像構建過程中的作用。頂部代表了 Packer 模板本身,它被劃分為三個主要部分:builders、variables 和 provisioners。builders 區塊負責定義映像構建的目標環境,例如 Azure 雲端,並配置連接到該平台的必要資訊,同時指定基礎映像的來源和最終映像的屬性。variables 區塊則扮演著參數化的角色,它允許我們定義可變的值,這些值可以從環境變數或命令列參數中獲取,從而使模板更具彈性,並用於動態配置 builders 和 provisioners 中的參數。provisioners 區塊是執行實際配置操作的地方,它包含一系列腳本或命令,用於在臨時虛擬機上安裝軟體、進行系統設置,並執行關鍵的通用化步驟,以確保映像的可重用性。當 Packer 執行時,它會依序讀取這些配置,創建一個臨時的虛擬機,應用 provisioners 中的所有步驟,然後將其轉換為一個最終的、可部署的映像。
Packer 模板整合 Ansible:實現自動化映像構建
在之前的探討中,我們了解了如何使用 Packer 的 shell 和 windows-shell 配置器來執行腳本,從而完成映像的配置和通用化。然而,對於更複雜的配置管理任務,直接使用腳本可能會變得繁瑣。Packer 提供了與 Ansible 等配置管理工具整合的能力,這能極大地簡化映像構建過程,並提高一致性。
為什麼選擇 Ansible?
- 重用現有腳本: 如果您已經在使用 Ansible 來配置虛擬機,那麼您可以直接重用現有的 Ansible Playbook 來創建映像,無需重新編寫腳本,節省了大量時間和精力。
- 聲明式配置: Ansible 以其聲明式的語法,使得配置過程更易於理解和維護。
- 強大的生態系統: Ansible 擁有豐富的模組和龐大的社群支持,能夠處理各種複雜的配置任務。
步驟一:編寫 Ansible Playbook
首先,我們需要一個 Ansible Playbook 來定義映像的配置。這個 Playbook 的目標是安裝 Nginx 並啟動其服務。
Ansible Playbook 範例 (nginx_playbook.yml):
---
- hosts: 127.0.0.1
become: true
connection: local
tasks:
- name: Installing Nginx latest version
apt:
name: nginx
state: latest
- name: Starting Nginx service
service:
name: nginx
state: started
Playbook 說明:
hosts: 127.0.0.1: 指定 Playbook 在本地主機上運行。在 Packer 的上下文中,這意味著 Playbook 將在 Packer 構建過程中創建的臨時虛擬機上執行。become: true: 啟用特權提升(類似於sudo),以便執行需要 root 權限的任務。connection: local: 指示 Ansible 使用本地連接模式。tasks:- 第一個任務使用
apt模組安裝最新版本的 Nginx。 - 第二個任務使用
service模組確保 Nginx 服務正在運行。
- 第一個任務使用
與獨立 VM 配置的差異:
與為獨立虛擬機編寫的 Playbook 相比,用於 Packer 映像構建的 Playbook 通常會有以下變化:
- 無 Inventory: Packer 負責管理臨時虛擬機的生命週期,因此不需要單獨的 Ansible Inventory 文件。
hosts: 127.0.0.1: 如上所述,直接指向本地執行。- 任務精簡: 通常只包含映像構建所需的配置任務,例如軟體安裝和基本配置,而排除與特定部署相關的任務(如數據庫安裝、用戶創建等)。
步驟二:整合 Ansible Playbook 到 Packer 模板
現在,我們將修改 Packer 模板的 provisioners 區塊,使其能夠調用我們編寫的 Ansible Playbook。
Packer 模板 (azure_linux_ansible.json) 的 provisioners 區塊:
{
// ... builders 和 variables 區塊 (與之前 JSON 範例類似) ...
"provisioners": [
{
"type": "shell",
"inline": [
"echo 'Waiting for cloud-init to finish...'",
"while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting...'; sleep 1; done;",
"echo 'Cloud-init finished.'"
]
},
{
"type": "ansible",
"playbook_file": "./nginx_playbook.yml",
"user": "packer", // 指定連接臨時 VM 的用戶
"extra_arguments": [
"--extra-vars", "ANSIBLE_HOST_KEY_CHECKING=False"
]
},
{
"type": "shell",
"inline": [
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
]
}
]
}
provisioners 區塊解析:
shell(等待 cloud-init): 在某些雲端環境中,cloud-init會在首次啟動時執行一些初始化任務。在運行 Ansible 之前,最好確保cloud-init已完成,以避免潛在的衝突。ansible配置器:type: "ansible": 指定使用 Ansible 配置器。playbook_file: "./nginx_playbook.yml": 指向我們編寫的 Ansible Playbook 文件。Packer 會將此 Playbook 上傳到臨時虛擬機並執行。user: "packer": 指定用於 SSH 連接到臨時虛擬機的用戶名。extra_arguments: 可以傳遞額外的參數給 Ansible 命令,例如禁用主機密鑰檢查 (ANSIBLE_HOST_KEY_CHECKING=False),這在自動化構建中很常見。
shell(通用化): 在 Ansible 配置完成後,執行 Linux 映像的通用化步驟,與之前使用純shell配置器時的步驟相同。
HCL 格式的整合
在 HCL 格式的模板中,整合 Ansible 的方式也非常直觀。
# ... source 區塊 ...
build {
sources = ["source.azure-rm.azurevm"]
provisioner "shell" {
inline = [
"echo 'Waiting for cloud-init to finish...'",
"while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting...'; sleep 1; done;",
"echo 'Cloud-init finished.'"
]
}
provisioner "ansible" {
playbook_file = "./nginx_playbook.yml"
user = "packer"
extra_arguments = [
"--extra-vars", "ANSIBLE_HOST_KEY_CHECKING=False"
]
}
provisioner "shell" {
inline = [
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
]
}
}
執行與驗證
完成 Packer 模板和 Ansible Playbook 的編寫後,執行步驟與之前類似:
- 設置環境變數: 確保 Azure 認證的環境變數已設置。
- 執行 Packer 命令:
packer init azure_linux_ansible.json packer build azure_linux.json
Packer 會啟動構建流程,創建臨時虛擬機,運行 cloud-init 等待腳本,然後調用 Ansible Playbook 來安裝和配置 Nginx,最後執行通用化步驟並生成最終的 Azure 映像。
視覺化 Ansible 整合流程
以下圖示展示了 Packer 如何調用 Ansible Playbook 來配置映像。
@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
start
:Packer 啟動構建;
:創建臨時 Azure VM;
:執行 Provisioner 1 (Shell - 等待 cloud-init);
:執行 Provisioner 2 (Ansible);
:Packer 傳遞 Playbook 路徑;
:Ansible 在臨時 VM 上執行;
:安裝 Nginx;
:啟動 Nginx 服務;
:執行 Provisioner 3 (Shell - 通用化);
:移除用戶資訊;
:清空歷史記錄;
:同步磁碟;
:Packer 將 VM 轉換為映像;
:映像部署至 Azure 資源組;
stop
@enduml
看圖說話:
此圖示清晰地描繪了使用 Packer 整合 Ansible 來構建 Azure 映像的流程。整個過程始於 Packer 啟動,並在 Azure 中創建一個臨時虛擬機。接著,Packer 依序執行定義在 provisioners 區塊中的三個步驟。第一步是一個 shell 腳本,用於等待雲端初始化完成,確保系統準備就緒。第二步是核心的 Ansible 配置階段,Packer 將指定的 Ansible Playbook(例如 nginx_playbook.yml)傳遞給 Ansible 執行器,Ansible 在臨時虛擬機上運行,根據 Playbook 的指示安裝 Nginx 並啟動其服務。完成配置後,第三步是另一個 shell 腳本,執行映像的通用化操作,清除臨時用戶資訊和配置,為映像的部署做好準備。最後,Packer 將完成配置的臨時虛擬機轉換為一個可在 Azure 中部署的映像,並將其存儲在指定的資源組中。
縱觀現代管理者的多元挑戰,將映像檔自動化建構的演進路徑,從單純的 shell 腳本提升至整合 Ansible 的模式,其核心價值不僅在於技術效率的提升,更深刻地反映了團隊從「單點執行」邁向「系統化治理」的思維躍遷。相較於 shell 腳本在初期部署的直觀便利,其在複雜環境下容易形成難以維護的技術債;而整合 Ansible 則代表一種策略性突破,成功將「建構機制」與「配置邏輯」解耦。這種分離不僅大幅提升了配置腳本的重用性與可維護性,更實現了基礎設施即程式碼(IaC)實踐的深度整合,將既有的配置管理投資轉化為可持續的競爭優勢。
展望未來 2-3 年,這種以 Packer 為核心、整合專業配置管理工具的「智慧工具鏈」,將成為成熟維運團隊的標準作業模式,並推動基礎設施生命週期管理的典範轉移。
綜合評估後,對於追求長期維運穩定性與規模化效益的組織而言,將 Ansible 整合至 Packer 流程,已是從「能用」邁向「卓越」的關鍵投資,值得納入核心技術戰略。