這篇是很久以前答應 hSATAC 大人說要寫的,由於一直有更有趣的事情可以做所以就拖稿拖到現在,主要講的是如何使用 Packer 來快快樂樂建 AWS EC2 AMI,將這件事情給它 infrastructure as code 這樣。
範例檔案放在 [GitHub] 這裡,這篇舉的例子,是建出一個把 Drupal 8 裝好的 AMI,先看 packer.json:
{ "variables": { "drupal_version": "8.2.5" }, "builders": [ { "type": "amazon-ebs", "name": "drupal-8-ami-us-east-1", "ami_description": "My Drupal 8 barebone AMI ({{user `drupal_version`}}) (us-east-1)", "region": "us-east-1", "source_ami": "ami-e13739f6", "instance_type": "t2.micro", "ssh_username": "ubuntu", "ami_name": "drupal-8-ami-{{timestamp}}", "tags": { "Name": "drupal-8-ami-{{timestamp}}", "Distribution": "Ubuntu Server 16.04 LTS (HVM), SSD Volume Type", "Drupal": "({{user `drupal_version`}})" }, "ami_block_device_mappings": [ { "device_name": "/dev/sda1", "volume_size": 8, "volume_type": "gp2", "delete_on_termination": true } ], "launch_block_device_mappings": [ { "device_name": "/dev/sda1", "volume_size": 8, "volume_type": "gp2", "delete_on_termination": true } ], "vpc_id": "vpc-cc9aeba9", "subnet_id": "subnet-940f76ae", "associate_public_ip_address": true, "security_group_ids": [ "sg-15a50671" ], "communicator": "ssh", "ssh_pty": true }, { "type": "amazon-ebs", "name": "drupal-8-ami-us-east-2", "ami_description": "My Drupal 8 barebone AMI ({{user `drupal_version`}}) (us-east-2)", "region": "us-east-2", "source_ami": "ami-d1cb91b4", "instance_type": "t2.micro", "ssh_username": "ubuntu", "ami_name": "drupal-8-ami-{{timestamp}}", "tags": { "Name": "drupal-8-ami-{{timestamp}}", "Distribution": "Ubuntu Server 16.04 LTS (HVM), SSD Volume Type", "Drupal": "({{user `drupal_version`}})" }, "ami_block_device_mappings": [ { "device_name": "/dev/sda1", "volume_size": 8, "volume_type": "gp2", "delete_on_termination": true } ], "launch_block_device_mappings": [ { "device_name": "/dev/sda1", "volume_size": 8, "volume_type": "gp2", "delete_on_termination": true } ], "vpc_id": "sg-789c2d11", "subnet_id": "subnet-60e84409", "associate_public_ip_address": true, "security_group_ids": [ "sg-789c2d11" ], "communicator": "ssh", "ssh_pty": true } ], "provisioners": [ { "type": "file", "source": "./nginx/default", "destination": "/tmp/nginx-default" }, { "type": "shell", "inline": "wget https://ftp.drupal.org/files/projects/drupal-{{user `drupal_version`}}.tar.gz -O /tmp/drupal.tar.gz" }, { "type": "shell", "script": "./provision.sh" } ] }
這份 Packer template 裡頭很多地方都可以望文生義,我就不一一逐條解說了。只講一些概念上的、可能會遇到坑的地方。
首先,一份 Packer template 分為好幾個部份,我這個例子裡,就分成了 variables
, builders
以及 provisioners
三塊。
其中,在 variables
裡我們可以宣告接下來會用到的變數,在我這個例子裡,我把想裝的 Drupal 版本寫在這裡,之後很多地方都可以用 {{user `drupal_version`}}
來引用。
然後看到 builders
這塊,我分別想在 AWS 兩個美東 region 建立需要的 AMI。這裡有個小 tricky 的地方,就是如果我們不特別寫 name
這個屬性的話,Packer 是不會允許我們建立多個同一 type 的 images 的(這裡就是指 amazon-ebs
),如果我們需要在複數個 AWS region 建立 AMI,就要特別寫明 name
。
還有要注意的一個概念是,Packer template 裡面寫的各種屬性,是在「建立 image 當下」使用的,AMI 建完後,要怎麼使用是你家的事,Packer 並不負責這塊。所以呢,為了 Packer 方便作業(在這裡是透過 SSH 連進一台為了做 AMI 而臨時開出的 EC2 instance),像是 vpc_id
, subnet_id
, associate_public_ip_address
, security_group_ids
這些設定,是 Packer 呼叫 AWS API 開一台 EC2 instance 時使用的,特別是 security_group_ids
,注意這些設定要能夠保證 Packer 可以 SSH 登進去機器,不然就等著 timeout 執行失敗。
provisioners
這裡我們可以用很多方式來對 AMI 做 provisioning,Packer 官方提供了很多常見的 provisioners,除了基本的丟 file、跑 shell script 以外,還有一些很潮很夯或不潮不夯但有用的自動組態工具像是 Ansible, Chef, Puppet。搭配這些自動組態工具就有更多花樣可以玩,可能也比 shell script 更好處理一些更需要彈性的東西。
最後提一個小技巧,就是如果我們跑 packer build packer.json 時遇到問題,那麼用 packer build -debug packer.json 進除錯模式,Packer 會把它為了這次 building 動態產生的 SSH key 寫在檔案系統裡,讓您可以手動 SSH 進去這台機器,方便診斷問題所在。