こんにちは。
インフラチームです。
弊社ではAWS/GCPを使用しており、それぞれのサーバ構築/環境設定はAnsibleで行なっています。
環境の設定の更新を行う場合、AWSであればAMI、GCPであればマシンイメージを更新しておく必要があります。
ゴールデンイメージ(起動してからの設定更新を行わない、最新設定のイメージ)で管理しているのですが、
そのイメージを更新しようとする場合、手動対応では下記手順が必要になります。
一つ一つは大したことない作業ですが、毎回この手順では手間も待ち時間も多く、ミスが発生しやすかったです。
起動対象のイメージを間違えたり、
他の作業している間に起動したことすら忘れてたり、
イメージ取得後のインスタンス消し忘れなんかもよくありました。
この手順を管理し、実行してくれるのがpackerになります。
AMI/GCE image作成の工数を大幅に削減できておりますので、
それぞれどのように継続的に回せるようにしているかをお話しさせていただきます。
今回はAWS(AMI)での設定になります。
packerとは
パプリッククラウドのマシンイメージ、VMイメージ、コンテナイメージまで作成をサポートするツール
安心のhashicorp製
弊社ではAWS,GCPイメージ作成に使用しています。
dockerイメージも作って見たのですが、playbookを同じように入れてしまうと、コンテナイメージが複雑かつ重いものになってしまいます。
role単位で独立して作成すれば良いのですが、従来のplaybook管理と異なってしまうこともあり現在は使用しておりません。
packerがやってくれる事
- AMIからインスタンスを起動
- 起動したインスタンスにAnsibleを適用
- AMIを作成
- 1で起動したインスタンスを削除
※post-processorは今回使用していない為省いています。
継続的に回すために
こちらを毎回指定する必要があります。
- どのAMIから起動させるか( 1. )
- どのplaybookを適用するか( 2. )
もちろん毎回ベタで書くわけにもいかないのでルールとスクリプト化を行い対応しています。
1. どのAMIから起動させるか
ここではどのAMIからインスタンスを起動するかを決めておく必要があります。
AMIの運用の仕方次第ではありますが、弊社では下記のようにルールを設定しています。
AMI運用ルール
- 名前の統一。versionは作成日時で管理。
- ex: hoge-prd-api-base-20180301-1234
- 3世代管理。同じ種類のAMIは3代前までは残し、それ以上古いのは削除。
- 使用するAMIは常に最新の日付のものとする。
packerで起動するインスタンスは最新の日付のAMIと決めています。
最新の日付-時間のAMI IDを取得しpackerのconfigに追加します。
2. どのplaybookを適用するか
どのplaybookを適用するか、というのもルールを設けています。
各環境毎にplaybookを用意(dev環境=dev.yml,本番環境=prd.ymlという感じ)
どの環境のどのカテゴリ(APIや管理画面用等)のAMIを作成するか、という値をparameter.jsonから読み込めるようにしています。
Config
parameter.json/packer.jsonを同じディレクトリにおきます。
それぞれ下記用途で分けています。
- parameter.json : 変数定義
- packer.json : 動作定義
parameter.json( sample_parameter.template )
{
"account": "__ACCOUNT__",
"ami_name": "__AMINAME__",
"source_ami": "__SOURCEAMI__",
"volume_size": "__VOLUMESIZE__",
"ansible_enviroment": "__ENVIROMENT__",
"ansible_group": "__GROUP__",
"ansible_category": "__CATEGORY__",
"playbook_directory" : "__PLAYBOOKDIR__"
}
AMI ID、AMI NAME、適用するansibleのplaybook名等、
対象が毎回変化する値を入れるようにしています。
sample_parameter.templateファイルを用意し、
create_ami.shでこのファイルに値を入れた、parameter.jsonを生成します。
packer.json
{
"builders" : [{
"type" : "amazon-ebs",
"profile" : "{{user `account`}}",
"region" : "ap-northeast-1",
"instance_type" : "t2.nano",
"source_ami" : "{{user `source_ami`}}",
"ssh_username" : "ec2-user",
"ssh_timeout" : "5m",
"ami_name" : "{{user `ami_name`}}",
"ami_block_device_mappings": [
{
"device_name": "/dev/xvda",
"volume_type": "gp2",
"volume_size": "{{user `volume_size`}}",
"delete_on_termination": true
}
]
}],
"provisioners" : [
{
"type" : "ansible",
"extra_arguments": [
"--tags","{{user `ansible_group`}}",
"--extra-vars","group={{user `ansible_group`}}",
"--extra-vars","category={{user `ansible_category`}}",
"--vault-password-file","{{user `playbook_directory`}}/vault.txt"
],
"sftp_command": "/usr/libexec/openssh/sftp-server -e",
"playbook_file" : "{{user `playbook_directory`}}/playbooks/{{user `ansible_enviroment`}}.yml"
}
]
}
packerの動作定義ファイル
シンプルにbuildersとprovisionersだけで構成しています。
- builders
- どこでイメージを作るか、ベースとなるインスタンスはどのような設定で起動させるか、の定義
- AWSのBuilder設定、必要なIAM権限についてはこちらを参照
- profileを使用して~/.aws/credentialsからキーを読み込む設定としています。
- ssh設定
- 特に指定しない場合はdefault VPC,22ポート全開放のセキュリティグループで作成されます。
- IAM Roleやセキュリティグループの指定はできるので必要に応じて
- どこでイメージを作るか、ベースとなるインスタンスはどのような設定で起動させるか、の定義
- provisioners
- インスタンス起動後に適用する操作定義
- type : ansibleで実行環境からansibleを適用する、という設定。
- インスタンス起動後に適用する操作定義
ansibleでのプロビジョニングについて
provisionersにtype:ansibleとして記述いたします。
https://www.packer.io/docs/provisioners/ansible.html
どのように動作するか
まずconfigと実行コマンドを見比べて見ます。
packerは実行時の動作を標準出力で吐いてくれる為、追いやすいです。
{
"type" : "ansible",
"extra_arguments": [
"--tags","{{user `ansible_group`}}",
"--extra-vars","group={{user `ansible_group`}}",
"--extra-vars","category={{user `ansible_category`}}",
"--vault-password-file","{{user `playbook_directory`}}/vault.txt"
],
"sftp_command": "/usr/libexec/openssh/sftp-server -e",
"playbook_file" : "{{user `playbook_directory`}}/playbooks/{{user `ansible_enviroment`}}.yml"
}
↑ 設定
↓ コマンド
※ 実行ログから抜粋
==> amazon-ebs: Executing Ansible: ansible-playbook --extra-vars packer_build_name=amazon-ebs packer_builder_type=amazon-ebs -i /tmp/packer-provisioner-ansible243167728 hoge_playbooks/playbooks/dev.yml -e ansible_ssh_private_key_file=/tmp/ansible-key776005013 --tags dev-api --extra-vars group=dev-api --extra-vars category=env --vault-password-file hoge_playbooks/vault.txt
↓見やすく
ansible-playbook
--extra-vars packer_build_name=amazon-ebs packer_builder_type=amazon-ebs
-i /tmp/packer-provisioner-ansible243167728
hoge_playbooks/playbooks/dev.yml
-e ansible_ssh_private_key_file=/tmp/ansible-key776005013
--tags dev-api
--extra-vars group=dev-api
--extra-vars category=api
--vault-password-file hoge_playbooks/vault.txt
指定した”extra_arguments”のオプション以外にも、何行かあるのがわかります。
ssh
起動したインスタンスに対してsshでコネクションのテストを行います(builders)
どのように対象を指定しているのかを理解する必要があります。
Packer: builders → provisioners流れ
この2行がsshに関わってきます。
-i /tmp/packer-provisioner-ansible243167728
-e ansible_ssh_private_key_file=/tmp/ansible-key776005013
- ポート
- 実行環境からsshできるようにしているか確認
- セキュリティグループを指定していない場合は22ポートが全開放のグループがアタッチされる
- キー
- こちらも特に指定していなければpackerで作成したキーが毎回登録されます
- ex:
-e ansible_ssh_private_key_file=/tmp/ansible-key776005013
- ex:
- sshのキーペアを実行環境内で作成
- AWSへキーペア登録
- 最後までpackerを実行すれば削除されますが、途中で止めると「packer-***」なキーが残り続ける
- 登録したキーペアを起動するインスタンスに設定
- authorized_keysにたまり続けるので、削除の仕組みは入れておきたい
- こちらも特に指定していなければpackerで作成したキーが毎回登録されます
- 対象ホスト(inventry file)
- buildersで起動したインスタンスのIPが記述されている、実行時に作成したもの
- ex:
-i /tmp/packer-provisioner-ansible243167728
- ex:
- hostsのgroup指定が効かない。playbookでhostsを指定している場合は対象が取れないと判断されるので注意。
- buildersで起動したインスタンスのIPが記述されている、実行時に作成したもの
playbook例
NG
hostsの対象不明となる
- hosts: dev-api
user: ec2-user
become: yes
vars_files:
- group_vars/dev.yml
roles:
- common
- nginx
- td-agent
OK
hostsはallとし、対象はtagsで指定
- hosts: all
user: ec2-user
become: yes
vars_files:
- group_vars/dev.yml
roles:
- common
- nginx
- td-agent
tags:
- dev-api
使用しているオプションについて
- extra_arguments
- ansible-playbookコマンド実行時につけるオプションを記述
- playbook内でansible-vaultで暗号化している箇所がある為、”–vault-password-file”で”vault.txt”からパスワードを読み込み実行している
- “sftp_command”: “/usr/libexec/openssh/sftp-server -e”
- Ansibleがファイルの転送に使用するSFTPプロトコルを処理するためにPackerによってプロビジョニングされているマシン上で実行するコマンド
- デフォルトが”/usr/lib/sftp-server -e.”だが、弊社で使用しているAmazonLinux/CentOS6上ではパスが合わない為指定
イメージ作成の流れ(AWS)
では実行に入ります。
最終的にpacker build
を叩くのですが、対象のAMIの取得等があります。
一貫して実行する為にスクリプトファイルとしました。
create_ami.sh
parameter.jsonの作成とpackerの実行(エラー処理なし・・・すいません)
VOLUME_SIZEだけベタ書きというのも修正しないとです。
#!/bin/bash
PLAYBOOK_DIR=`dirname ${0}`
# パラメータ設定
ACCOUNT="$1"
PLAYBOOK_ENV="$2"
PLAYBOOK_CATEGORY="$3"
AMI_PREFIX="${ACCOUNT}-${PLAYBOOK_ENV}-${PLAYBOOK_CATEGORY}-base"
# PLAYBOOK GROUP
PLAYBOOK_GROUP="${PLAYBOOK_ENV}-${PLAYBOOK_CATEGORY}"
# 元となるAMIの情報を取得
SOURCEAMI_INFO=`aws ec2 describe-images
--profile "${ACCOUNT}"
--owners self |
jq -r '.Images | sort_by(.Name)| .[] | .Name + " " + .ImageId' |
grep "${AMI_PREFIX}" |
tail -1`
SOURCEAMI_NAME=`echo ${SOURCEAMI_INFO} | awk '{print $1}'`
SOURCEAMI_ID=`echo ${SOURCEAMI_INFO} | awk '{print $2}'`
echo "SOURCEAMI_NAME : ${SOURCEAMI_NAME}
SOURCEAMI_ID : ${SOURCEAMI_ID}"
# AMIパラメータ設定
AMI_NAME="${AMI_PREFIX}-`date +'%Y%m%d-%H%M'`"
VOLUME_SIZE="50"
# parameterファイルを作成
cat ${PLAYBOOK_DIR}/sample_parameter.template |
sed -e "s/__ACCOUNT__/${ACCOUNT}/g"
-e "s/__AMINAME__/${AMI_NAME}/g"
-e "s/__SOURCEAMI__/${SOURCEAMI_ID}/g"
-e "s/__VOLUMESIZE__/${VOLUME_SIZE}/g"
-e "s/__ENVIROMENT__/${PLAYBOOK_ENV}/g"
-e "s/__GROUP__/${PLAYBOOK_GROUP}/g"
-e "s/__CATEGORY__/${PLAYBOOK_CATEGORY}/g"
-e "s@__PLAYBOOKDIR__@${PLAYBOOK_DIR}@g"
> ${PLAYBOOK_DIR}/parameter.json
cat ${PLAYBOOK_DIR}/parameter.json
# build
packer build
-var-file ${PLAYBOOK_DIR}/parameter.json
${PLAYBOOK_DIR}/packer.json
ディレクトリ構成
$ ~/hoge_playbooks
tree -L 2
.
├── create_ami.sh
├── packer.json
├── parameter.json
├── playbooks
│ ├── dev.yml
│ ├── group_vars
│ ├── prd.yml
│ ├── roles (ansible roles)
│ └── stg.yml
├── sample_parameter.template
└── vault.txt
同じディレクトリに
- packer.json
- parameter.json
- Ansible Playbook
- create_ami.sh
- vault.txt
を置く。
vault.txtにはansible-vaultのパスワードを記載。
実行
sh create_ami.sh hoge dev api
対象の環境を指定し実行致します。
ACCOUNT="$1"
PLAYBOOK_ENV="$2"
PLAYBOOK_CATEGORY="$3"
# AMI名/対象playbookの選択に使用
AMI_PREFIX="${ACCOUNT}-${PLAYBOOK_ENV}-${PLAYBOOK_CATEGORY}-base"
AMI_NAME="${AMI_PREFIX}-`date +'%Y%m%d-%H%M'`"
# PLAYBOOK --tagsに使う
PLAYBOOK_GROUP="${PLAYBOOK_ENV}-${PLAYBOOK_CATEGORY}"
実行環境
EC2でrundeckサーバを立てており、JOBとして実行しています。
rundeckで実行するメリットは下記になります。
- 作業環境の統一
- 実行手順の統一
- 実行履歴の保存
実行履歴が残るので、どこで失敗したか、いつ実行したかを遡って確認できるのは助かります。
実行フローは下記になります。
application | version | 用途 |
---|---|---|
rundeck | 2.6.0 | JOBサーバ |
awscli | 1.14.42 | AWS操作CLI |
packer | 1.2.2 | イメージ作成 |
ansible | 2.3.0.0 | 構成管理 |
packerは最新(2018/4/18現在)で書いてますが、長いこと0.10.2で動いていましたので、最新でなくても問題はありません。
JOBはそれぞれ単体でも動くようにし、連携させたJOBを実行。
- CreateAMI
- playbookをpullし、packerを実行するjob
- CreateLaunchConfig
- 指定したAMIのLaunchConfigを作成し、AutoScalingGroupへ追加する
最新のAMI作成からAutoScalingGroupへの適用まで実行される為、変更の反映をとても楽にできています。
新しい設定に切り替えるにはBlueGreenデプロイで、サーバを入れ替えれば完了になります。
まとめ
AMIをpacker + ansibleで作成する為にやっていることを書かせていただきました。
もともとansibleで構成管理をしていた中に、packerのサイクルを入れられたことで、イメージ作成は格段に楽になっています。
AMIの管理方法、ansibleのロール管理等もそうですが、ベストプラクティスを参考にしつつ、使いやすい形は常に模索中です。
柔軟に変化できるように今のベースを皆で把握しながら変えてけるようにするということを心がけています。
次回はGoogleComputeEngineのイメージ作成についてお話しさせていただきます。