鏡像即代碼:基於Packer構建阿里雲鏡像

什麼是Packer

PackerHashiCorp推出的一款工具,旨在提供簡易的方式自動化構建鏡像。通過Packer,你只需要在配置文件中指明鏡像構建所需的基本信息及期望安裝到鏡像中的軟件及配置,即可通過自動化腳本構建所需的鏡像。由於構建鏡像的過程被固化成了配置文件,每一個步驟都清晰可見易於回溯,無需擔心多次構建得到的鏡像存在不一致。且鏡像構建配置化後,將爲測試和更新鏡像帶來極大的便利,大大降低運維和管理鏡像的成本。

在具體介紹Packer的使用方法之前,我們先來看下以前在阿里雲ECS上如何手動創建一個自定義鏡像。如果對這個流程已非常熟悉,可以直接跳到通過Packer構建鏡像一節。

注意:後續操作會創建一些收費資源,請注意釋放和清理,如實例、公網IP、快照等。

實例規格和鏡像會隨着時間的推移不斷更新,本文後續提到的一些規格和鏡像可能會在未來下線,所以具體操作流程可以根據實際情況選擇不同的規格、鏡像或者其他實例相關的資源。

手動創建自定義鏡像

簡單起見,假設我們需要在阿里雲北京地域構建一個CentOS 7.3的鏡像,其中需要安裝redis,其他方面無特定需求,則整個創建步驟如下所示:

  1. 打開ECS售賣頁,從上到下依次選擇按量付費 => 華北2(北京) => ecs.t5-lc1m1.small => 公共鏡像CentOS 7.3 64位,點擊頁面右下方下一步:網絡和安全組
  2. 繼續選擇專有網絡 => 公網帶寬 => 安全組,點擊頁面右下方下一步:系統配置
  3. 繼續選擇祕鑰對,如不存在需要新建祕鑰對,便於後續通過祕鑰連接實例。其餘配置保持默認,點擊頁面右下方確認訂單創建實例
  4. 購買流程完成後,可在ECS控制檯華北2(北京)地域看到新建的實例,稍等片刻待實例狀態變成運行中。
  5. 連接並登陸新創建的實例,通過命令行安裝redis。連接方式可參照使用SSH密鑰對連接Linux實例一文。
  6. 安裝完成後回到控制檯實例列表,點擊對應實例右側更多 => 磁盤和鏡像 => 創建自定義鏡像,等待自定義鏡像創建完成。
  7. 最後清理不需要的資源,釋放實例、公網IP(如果是彈性公網IP)。如果需要,可以進一步刪除VPC、安全組等僅用於測試的資源。

上述過程實際上簡化了鏡像內最爲關鍵的軟件及其配置部分,實際上該過程會隨着鏡像內需預裝的軟件及其配置不斷擴充變得愈發複雜。通過人肉保證每一次操作都準確無誤,和之前毫無偏差,會是一件非常困難的事情,更別提之後的維護和更新了。接下來我們將看到如何通過Packer自動化地完成上述鏡像構建構成。

通過Packer構建鏡像

如上所述,Packer通過配置文件記錄鏡像構建過程中所需的所有細節。如下alicloud.json便是用於完成手動創建自定義鏡像一節需求所需的配置文件。

{
  "variables": {
    "access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
    "secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
  },
  "builders": [{
    "type":"alicloud-ecs",
    "access_key":"{{user `access_key`}}",
    "secret_key":"{{user `secret_key`}}",
    "region":"cn-beijing",
    "image_name":"packer_basic",
    "source_image":"centos_7_03_64_20G_alibase_20170818.vhd",
    "ssh_username":"root",
    "instance_type":"ecs.t5-lc1m1.small",
    "internet_charge_type":"PayByTraffic",
    "io_optimized":"true"
  }],
  "provisioners": [{
    "type": "shell",
    "inline": [
      "sleep 30",
      "yum install redis.x86_64 -y"
    ]
  }]
}

其中:

  1. variables中定義了builders中會用到的兩個變量access_keysecret_key。 這兩個變量的值取自運行時的環境變量,這是爲了防止意外將AK寫到配置文件中造成遺漏。
  2. builders中表明使用的是Alicloud Image Builder。該Builder用於在阿里雲上創建自定義鏡像。其他則爲創建鏡像所需要的一些信息,包括AK、地域、鏡像名稱、源鏡像、登陸名、實例規格、公網計費方式和IO優化。
  3. provisioners定義了需要在實例內執行的操作。這裏用到Shell Provisioner,表示在連接實例後執行一段shell腳本安裝redis。

安裝Packer的過程詳見官網Getting Started,此處不再贅述。假設Packer已經安裝成功,執行packer build alicloud.json完成鏡像構建,整個構建過程需要耗費一些時間,構建過程中產生的日誌如下所示:

alicloud-ecs output will be in this color.

==> alicloud-ecs: Prevalidating image name...
    alicloud-ecs: Found image ID: centos_7_03_64_20G_alibase_20170818.vhd
==> alicloud-ecs: Creating temporary keypair: packer_xxx
==> alicloud-ecs: Creating vpc
==> alicloud-ecs: Creating vswitch...
==> alicloud-ecs: Creating security groups...
==> alicloud-ecs: Creating instance.
==> alicloud-ecs: Allocating eip
==> alicloud-ecs: Allocated eip xxx
    alicloud-ecs: Attach keypair packer_xxx to instance: i-xxx
==> alicloud-ecs: Starting instance: i-xxx
==> alicloud-ecs: Using ssh communicator to connect: ***
==> alicloud-ecs: Waiting for SSH to become available...
==> alicloud-ecs: Connected to SSH!
==> alicloud-ecs: Provisioning with shell script: /var/folders/k_/nv2r4drx3bs08l6tcx06ndb40000gn/T/packer-shell260049331
    alicloud-ecs: Loaded plugins: fastestmirror
    alicloud-ecs: Determining fastest mirrors
    alicloud-ecs: Resolving Dependencies
    alicloud-ecs: --> Running transaction check
    alicloud-ecs: ---> Package redis.x86_64 0:3.2.12-2.el7 will be installed
    alicloud-ecs: --> Processing Dependency: libjemalloc.so.1()(64bit) for package: redis-3.2.12-2.el7.x86_64
    alicloud-ecs: --> Running transaction check
    alicloud-ecs: ---> Package jemalloc.x86_64 0:3.6.0-1.el7 will be installed
    alicloud-ecs: --> Finished Dependency Resolution
    alicloud-ecs:
    alicloud-ecs: Dependencies Resolved
    alicloud-ecs:
    alicloud-ecs: ================================================================================
    alicloud-ecs:  Package           Arch            Version                  Repository     Size
    alicloud-ecs: ================================================================================
    alicloud-ecs: Installing:
    alicloud-ecs:  redis             x86_64          3.2.12-2.el7             epel          544 k
    alicloud-ecs: Installing for dependencies:
    alicloud-ecs:  jemalloc          x86_64          3.6.0-1.el7              epel          105 k
    alicloud-ecs:
    alicloud-ecs: Transaction Summary
    alicloud-ecs: ================================================================================
    alicloud-ecs: Install  1 Package (+1 Dependent package)
    alicloud-ecs:
    alicloud-ecs: Total download size: 648 k
    alicloud-ecs: Installed size: 1.7 M
    alicloud-ecs: Downloading packages:
    alicloud-ecs: --------------------------------------------------------------------------------
    alicloud-ecs: Total                                              2.2 MB/s | 648 kB  00:00
    alicloud-ecs: Running transaction check
    alicloud-ecs: Running transaction test
    alicloud-ecs: Transaction test succeeded
    alicloud-ecs: Running transaction
    alicloud-ecs:   Installing : jemalloc-3.6.0-1.el7.x86_64                                  1/2
    alicloud-ecs:   Installing : redis-3.2.12-2.el7.x86_64                                    2/2
    alicloud-ecs:   Verifying  : redis-3.2.12-2.el7.x86_64                                    1/2
    alicloud-ecs:   Verifying  : jemalloc-3.6.0-1.el7.x86_64                                  2/2
    alicloud-ecs:
    alicloud-ecs: Installed:
    alicloud-ecs:   redis.x86_64 0:3.2.12-2.el7
    alicloud-ecs:
    alicloud-ecs: Dependency Installed:
    alicloud-ecs:   jemalloc.x86_64 0:3.6.0-1.el7
    alicloud-ecs:
    alicloud-ecs: Complete!
==> alicloud-ecs: Stopping instance: i-xxx
==> alicloud-ecs: Waiting instance stopped: i-xxx
==> alicloud-ecs: Creating image: packer_basic
    alicloud-ecs: Detach keypair packer_xxx from instance: i-xxx
==> alicloud-ecs: Cleaning up 'EIP'
==> alicloud-ecs: Cleaning up 'instance'
==> alicloud-ecs: Cleaning up 'security group'
==> alicloud-ecs: Cleaning up 'vSwitch'
==> alicloud-ecs: Cleaning up 'VPC'
==> alicloud-ecs: Deleting temporary keypair...
Build 'alicloud-ecs' finished.

==> Builds finished. The artifacts of successful builds are:
--> alicloud-ecs: Alicloud images were created:

cn-beijing: m-xxx

上述日誌較爲完整得給出了Packer構建過程中執行的每一個步驟:從校驗參數、創建臨時資源、預安裝軟件、創建目標資源到最後的釋放臨時資源,所有的過程一氣呵成,而這僅僅只需預裝好Packer以及定義好相應的配置文件。接下來將針對一些實際DevOps場景會用到的一些配置進行必要的說明以供參考,更多參數和樣例詳見Alicloud Image BuilderExamples

DevOps常用配置

鏡像標籤(tags)

當所要管理的鏡像達到一定的數量時,對鏡像進行適當的標記就變得很有必要,比如記錄鏡像版本號、鏡像包含的應用類型等。通過爲鏡像打上標籤是達到上述目的絕佳手段,阿里雲Builder提供了tags參數以支持此類需求。如下配置文件將爲最終生成的鏡像和對應的快照打上version=v1.0.0app=web兩個標籤。

{
  "variables": {
    "access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
    "secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
  },
  "builders": [{
    "type":"alicloud-ecs",
    "access_key":"{{user `access_key`}}",
    "secret_key":"{{user `secret_key`}}",
    "region":"cn-beijing",
    "image_name":"packer_basic",
    "source_image":"centos_7_03_64_20G_alibase_20170818.vhd",
    "ssh_username":"root",
    "instance_type":"ecs.t5-lc1m1.small",
    "internet_charge_type":"PayByTraffic",
    "io_optimized":"true",
    "tags": {
      "version": "v1.0.0",
      "app": "web"
    }
  }]
}

控制檯鏡像列表頁面和API DescribeImages均支持查詢鏡像時返回標籤以及根據標籤過濾鏡像。但爲鏡像打上標籤真正強大的地方在於能夠和Terraform一起爲標準化的DevOps流程提供支持。Terraform的內容超出了本文討論的範疇,在此不再展開。這裏推薦Alibaba Cloud DevOps tutorials系列教程,其中講解了一些企業DevOps過程中的最佳實踐,其中涉及Terraform和Packer的內容參見Continuous Delivery一節。

讓鏡像只包含系統盤(image_ignore_data_disks)

默認情況下Packer直接從實例創建鏡像,而從實例創建鏡像時如果包含數據盤,則鏡像會同時包含數據盤快照。在構建過程中創建包含數據盤的實例通常有兩種方式:一是通過image_disk_mappings設置數據盤相關參數,二是選擇默認帶有數據盤的實例規格。其中後者涉及的規格包含的數據盤大多爲本地盤,如ecs.d1ne.2xlarge,而本地盤當前並不支持創建快照,進而也無法直接通過此類實例創建鏡像。即便如此,很多場景下爲了滿足某些方面的性能需求,用戶依然會選擇這類實例規格,但實際上數據盤部分並不是必須的。此時就可以在配置文件中加上"image_ignore_data_disks": "true"實現只基於系統盤來創建鏡像。

設置快照超時時間(wait_snapshot_ready_timeout)

鏡像依賴於快照,而快照的創建時間依賴於磁盤大小。當磁盤較大時,創建快照所需要的時間也會相應的增加。默認情況下,輪詢快照的超時時間爲3600s。如果由於磁盤太大導致超時,則可以通過wait_snapshot_ready_timeout調大超時時間。

通過私網IP連接實例(ssh_private_ip)

默認情況下,Packer創建EIP並綁定到實例上,然後通過EIP對應的公網IP連接實例安裝軟件或執行命令。但在一些場景下,用戶可以直接通過私網IP連接實例,此時公網IP就會顯得多餘。此時可以通過設置"ssh_private_ip": "true",該設置下Packer將不會分配EIP或者公網IP,而是直接嘗試通過私網IP連接實例。

停止實例選項(disable_stop_instance)

默認情況下,Packer在執行完provisioners後,會先停止實例然後創建鏡像。但如果設置了"disable_stop_instance": "true",Packer將不會主動去停止實例,而是假設配置中提供的指令會自行停止實例,以滿足一些特殊場景,如Sysprep一個Windows實例。Sysprep的一個使用場景可參照修改Windows實例SID以搭建域環境

通過UserData啓用WinRM

出於安全考慮,Windows鏡像默認關閉了WinRM。但連接Windows實例及之後在實例內部執行命令都依賴於WinRM,所以需要在實例創建時啓用WinRM,而這可以通過UserData來完成。啓用WinRM的Userdata文件內容詳見winrm_enable_userdata.ps1,Packer則通過配置user_data_file指定UserData文件路徑。跟WinRM相關的參數還包括"communicator": "winrm""winrm_port": 5985"winrm_username": "Administrator""winrm_password": "Test1234",分別表示通過WinRM連接實例、連接端口爲5985、連接時使用Administrator賬戶,密碼採用Test1234。以下配置文件提供了基於Windows的一個簡單示例:

{
  "variables": {
    "access_key": "{{env `ALICLOUD_ACCESS_KEY`}}",
    "secret_key": "{{env `ALICLOUD_SECRET_KEY`}}"
  },
  "builders": [{
    "type":"alicloud-ecs",
    "access_key":"{{user `access_key`}}",
    "secret_key":"{{user `secret_key`}}",
    "region":"cn-beijing",
    "image_name":"packer_test",
    "source_image":"win2008r2_64_ent_sp1_zh-cn_40G_alibase_20181220.vhd",
    "instance_type":"ecs.n1.tiny",
    "io_optimized":"true",
    "internet_charge_type":"PayByTraffic",
    "image_force_delete":"true",
    "communicator": "winrm",
    "winrm_port": 5985,
    "winrm_username": "Administrator",
    "winrm_password": "Test1234",
    "user_data_file": "examples/alicloud/basic/winrm_enable_userdata.ps1"
  }],
  "provisioners": [{
    "type": "powershell",
    "inline": ["dir c:\\"]
  }]
}

其中image_force_delete表示如果已存在同名鏡像則先刪除,並假定UserData文件在給定的相對路徑下。provisioners內的指令只用做在實例內調用命令的示例,可根據實際情況填寫。

從ISO到阿里雲鏡像

ISO文件需要在線下虛擬化環境安裝完成後,生成對應格式的鏡像文件再導入到阿里雲(當前支持QCOW2、VHD和RAW三種格式的文件導入阿里雲)。如果線下環境爲qemu,可參照使用Packer創建並導入本地鏡像一文。其中包含兩個重要的部分,首先需要使用對應虛擬化環境或軟件對應的Builder,如上文中使用的是Qemu Builder。其次,通過定義Alicloud Import Post-Processor將前面生成的鏡像文件導入到阿里雲。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章