AMI作成のPackerプロジェクトのワタシ的ベストプラクティス!
様々なプロジェクトで仕事をするにあたって、AWSのAMI(Amazon Machine Image)を多くつくるようになりました。 今回はPackerプロジェクトの個人的なベストプラクティスをまとめました。
作成したリポジトリ
はじめに、作成したリポジトリを以下に晒しておきます。
また、前提条件は以下とします。
PackerやAnsible、Sererspec自体の解説は割愛します。
Vagrantを使ってローカル環境でデバッグできるようにしておく
Ansible Playbookの書き始めの頃は、可能であればローカル環境上に Vagrant と Virtual Box を使ってインスタンスを起動して、それに対してプロビジョニングするようにしました。
記述したAnsible PlaybookをいきなりAWS環境上のEC2へプロビジョニングをすることはやめました。 それは、プロビジョニング以外の処理(インスタンスの起動/停止処理)もあって、デバッグに時間がかかるためです。
Vagrantfileの準備
まずは、以下のような Vagrantfile
を準備します。とりあえず centos/7
を指定しています。
1Vagrant.configure("2") do |config|
2 config.vm.box = "centos/7"
3
4 config.vm.network :forwarded_port, id: "ssh", guest: 22, host: 2222
5
6 config.vm.provider "virtualbox" do |vb|
7 vb.memory = "1024"
8 end
9 config.vm.provision "shell", inline: <<-SHELL
10 #yum -y update
11 # add user centos
12 useradd centos
13 passwd -f -u centos
14 echo "centos ALL = NOPASSWD: ALL" >> /etc/sudoers.d/centos
15 echo "centos ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/centos
16 # setup SSH
17 mkdir -p /home/centos/.ssh; chown centos /home/centos/.ssh; chmod 700 /home/centos/.ssh
18 cp /home/vagrant/.ssh/authorized_keys /home/centos/.ssh/authorized_keys
19 chown centos /home/centos/.ssh/authorized_keys
20 chmod 600 /home/centos/.ssh/authorized_keys
21 # setup sshd config
22 sed -ri 's/#PermitRootLogin yes/PermitRootLogin yes/g' /etc/ssh/sshd_config
23 sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config
24 sed -ri 's/#UsePAM no/UsePAM no/g' /etc/ssh/sshd_config
25 systemctl restart sshd
26 SHELL
27end
公開されているAMIとVagentの centos/7
には、インストール済みのモジュールや、設定に差分があるため、
可能なかぎり config.vm.provision
ブロックで差分を吸収しています。ここはハマると若干時間を取られます。
ここではAnsibleがSSHしてプロビジョニングするための centos
ユーザの追加と sudo
権限の追加、sshd
への設定を追加しています。
Null BuilderでVagrantに対してプロビジョニングする
vagrant up
コマンドを使って自前で起動する場合は ssh
コマンドだけできれば良いので、 Null Builder を使います。
Packerのtemplateファイルの builders
を抜粋します。
1"builders":[
2 {
3 "type": "null",
4 "communicator": "ssh",
5 "ssh_pty": true,
6 "ssh_timeout" : "1m",
7 "ssh_host": "{{user `ssh_host`}}",
8 "ssh_port": "{{user `ssh_port`}}" ,
9 "ssh_username": "{{user `ssh_user`}}",
10 "ssh_private_key_file": "{{user `ssh_key`}}"
11 }
12]
templateファイル上で展開される変数は以下を与えます。
1{
2 "ssh_host": "127.0.0.1",
3 "ssh_user": "centos",
4 "ssh_key": "../.vagrant/machines/default/virtualbox/private_key",
5 "ssh_port": "2222"
6}
なお、ポート 2222
は Vagrantfile
内で 22
番と既にポートフォワードする設定を追加済みです。
Vagrantのスナップショット機能を使う
Vagrant はバージョン 1.8
から vagrant snapshot
サブコマンドが使用可能になっています。
これは、インスタンスの状態を保存&復元するための機能です。
Ansible Playbook が何度でも流せるように、初期化時に Vagrantインスタンスの状態を一度スナップショットとして取得し、
Packer実行前に毎回 restore
するようにします。
Ansibleの後にServerSpecで検査する
Ansible Playbookが実行された後は Serverspec を実行したいですよね。
残念ながら、PakcerにはServerspecのprovisionerがついていないので、 シェルを実行するためのprovisionerを使ってServerspecを実行します。
provisionerは ansible や shell-local を使う
Packerのtemplateファイルで記載するprovisionerには ansible や shell-local を使います。
ansible-local や shellを使いません。
理由として、ansible-local
や shell
は Packerでのプロビジョニング時に Ansible や Serverspec を プロビジョニング先のインスタンスにインストールする必要がでてきてしまうため です。実際のサービスで稼働させるインスタンスに不要なライブラリがインストールされているのは何とも気持ちの悪いものです。
必ず、ローカルマシンやCIサーバといったプロビジョニングする側にモジュールはインストールします。
ここでは rake
コマンドをラップした run_spec.sh
を呼び出していて、SSHするために必要な情報を引数として渡しています。
1 "provisioners": [
2 {
3 "type": "ansible",
4 "playbook_file" : "ansible/playbook-{{user `provision_target`}}.yml",
5 "user": "centos",
6 "ssh_host_key_file": "{{user `ssh_key`}}",
7 "ansible_env_vars": [
8 "ANSIBLE_HOST_KEY_CHECKING=False",
9 "ANSIBLE_NOCOLOR=True"
10 ]
11 },
12 {
13 "type": "shell-local",
14 "command": "cd serverspec && sh ./run_spec.sh {{user `ssh_host`}} {{user `ssh_port`}} {{user `ssh_key`}} {{user `provision_target`}} {{user `ssh_user`}}"
15 }
16 ]
設定ファイルは、roleごと、環境ごとに準備する
Packerのtemplateファイルでは多くの変数を必要とします。 それらの依存関係を理解して、適切な設定ファイルに分割するのは割と重要だと思っています。
個人的には以下の3種類を作成し、Packer実行時に引数としてこれらの3種類を組合せて渡します。
プラットフォームに依存するPacker template
Packerのtemplateファイルはプロビジョニングするプラットフォーム毎に作成します。 ローカルなのか、AWSなのか、Azureなのか、で1つずつ作るイメージです。
ファイル名 | 用途 |
---|---|
ami-aws-template.json | AWSにプロビジョニングするために使うPacker template |
ami-local-template.json | ローカルのVagrantにプロビジョニングするために使うPacker template |
環境に依存する設定ファイル
プラットフォームの環境に依存する設定ファイルです。 ここで言う「環境」とは、開発環境、ステージング環境、商用環境のような、システムを管理する単位一式のことを指しています。
ざっくり以下のようなイメージです。
ファイル名 | 用途 |
---|---|
env-A-variables.json | AWSアカウント Aにプロビジョニングするときに使う設定 |
env-B-variables.json | AWSアカウント Bにプロビジョニングするときに使う設定 |
env-local-variables.json | ローカルのVagrantにプロビジョニングするために使う設定 |
1{
2 "aws_region": "your-region",
3 "aws_vpc_id": "vpc-xxxxxxxxxxxxxx",
4 "aws_subnet_id": "subnet-xxxxxxxxxxx",
5 "ssh_user": "centos",
6 "use_profile": "your-profile",
7 "aws_instance_role": "your-packer-role",
8 "aws_keypair_name": "your-keypair-name"
9}
AWSアカウント単位でVPCのIDやキーペアの情報は変わってくるので、そのような情報はここにもたせます。
Ansible Role(システムコンポーネント)に依存する設定ファイル
ファイル名 | 用途 |
---|---|
role-hoge-variables.json | Role hoge をプロビジョニングするときに使う設定 |
AnsibleのRole(システムコンポーネント)に依存する設定ファイルです。 ベースとするAMIのインスタンスIDや、作成したAMI名のprefixなど置いておくといいと思います。
1{
2 "packer_tag_prefix": "Packer-Hoge-AMI",
3 "instance_tag_prefix": "HOGE_OPTIMIZED",
4 "aws_source_ami": "ami-xxxxxxxx"
5}
実行コマンドは別ファイルでラップしておく
一連のPacker実行コマンドの量が増えてしまうため、 コマンドをラップします。
シェルでもなんでもいいですが、私は Makefile
にしています。
実行引数は最低限以下の2つで済みます。
- Ansibleのプロビジョニング対象のRole
- AnsibleやServerspecがEC2へSSHするためのSSH鍵
1PACKER = cd packer
2# AnsibleのRole
3ROLE = $1
4# EC2へのSSH鍵(絶対パス)
5AWS_KEY_FILE = $2
6
7# Vagrant初期化時に make init-vagrant を実行します。 initial-saveという名前でスナップショットを保存
8init-vagrant:
9 vagrant halt && vagrant destroy -f && vagrant up --provision && \
10 vagrant snapshot save initial-save
11
12# Vagrantに対して先程のNull Builderでプロビジョニングします
13test-local:
14 @${PACKER} && \
15 vagrant snapshot restore initial-save && \
16 packer build -var-file=env-local-variables.json \
17 -var 'ssh_key=$(CURDIR)/.vagrant/machines/default/virtualbox/private_key' \
18 -var 'provision_target=${ROLE}' \
19 ami-local-template.json
20
21# AWSのAMIを作成します
22create-ami:
23 @${PACKER} && \
24 packer build \
25 -var-file=env-aws-variables.json \
26 -var-file=role-${ROLE}-variables.json \
27 -var 'aws_key_file=${AWS_KEY_FILE}' \
28 -var 'provision_target=${ROLE}' \
29 ami-aws-template.json
まとめ
今回はPackerでAMIを作成するときの個人的ベストプラクティスを紹介しました。
- ローカル環境でVagrantインスタンスを使って、Ansible PlaybookとServerspecの動作確認をする
Vagrantfile
ではconfig.vm.provision
ブロックでAMIとの差分を減らす努力は必要
- Ansibleの後にServerspecを流す
- provisoner は
ansible
とshell-local
を使う
- provisoner は
- 変数ファイルは依存関係ごとに分ける
- 実行コマンドをラップしたファイルを作る