Packer初歩的な利用方法

# 00. はじめに
HashiCorpさんの**『Packer』**
コチラの初歩的な使い方を整理してみました。
![Packer_logo.png](https://qiita-image-store.s3.amazonaws.com/0/50310/fa1a5682-8d61-9b57-5a11-38bf99ec8f82.png)
## 00-1. 検証環境
・PackerはMulti Platformに対応しており、それが特徴でもありますが、今回はAWS上での確認のみになります。

■検証環境

```
Packer v1.1.1
Amazon Linux AMI release 2017.09
```

# 01. Packerとは
## 01-1. 特徴
・公式ドキュメントに簡潔に記載されています。
https://www.packer.io/intro/index.html

上記説明をもとに私の理解は下記になります。

1) **各IaaS・仮想化ソフトウェア/コンテナ管理ソフトウェア**(※)にて、**カスタムしたマシンイメージ**を作成できるツール
※AWS、Azure、Google Cloud、VMware、VirtualBoxなど多数のプラットフォームに対応

2) **一度の実行で複数の環境へマシンイメージを作成することが可能**
例: AWSでAMIを作成、 並行してAzureでイメージを作成する　等

3) マシンイメージのプロビジョニングを**コード管理できる**
**「Infrastructure as Code」**の一種

## 01-2. Use Case
・ユースケースに関しても、公式に簡潔な説明があります。
https://www.packer.io/intro/use-cases.html

・主な用途としては**ベースとなるイメージを要件に沿ってプロビジョニング**(サーバセットアップ、コードデプロイ等)した上、**カスタムイメージを作成する**ことが挙げられるかと思います。

※個人的な話ですが、AMIを作成するツールと聞いていたため、触るまでは単なるバックアップツールの一種と思っていました。しかし、上述の特徴およびユースケースからも分かるように起動中のEC2からAMIを作成する機能は無いようです(**バックアップツールではない**のでした)。
→ AWSでしか確認していないため、他platformでも同様のことが言えるか確認できておりませんことご了承ください。

## 01-3. 考慮すべき事項
・例えば、AutoScalingの起動用AMIを継続的に更新したい場合
プロビジョニングのため該当AMIより一旦インスタンスを起動する必要があるため、同一サーバが重複起動することになります。その際の懸念点(例: S3 や Cloudwatch Logsへの自動ログアップデート処理など)を考慮しておく必要がありそうです。具体的にはそういったサービスは実稼働のlaunch時のみ行うようにする等が考えられるかと思います。

# 02. 実装例の前に
## 02-1. 導入はかんたん
・Packerの導入方法はとても簡単ですので、公式の説明を参照下さい。
https://www.packer.io/intro/getting-started/install.html

・また各種コマンドオプションもとてもシンプルになっています。
https://www.packer.io/docs/commands/build.html
https://www.packer.io/docs/commands/validate.html
→ 同HashiCorpさんの**『Terraform』**同様、**構文チェック**があるのはありがたいです。

## 02-2. Tips
### ■ 日付付与
1) 例えば「ami_name」をランダムにすべく、日付を付与する場合

```json
{ "ami_name": "Packer {{isotime | clean_ami_name}}" }
```

 ↓この場合、下記のようなAMI名となります。
**「Packer 2016-07-18T13-22-19Z」**
https://www.packer.io/docs/templates/engine.html

2) 文字列_YYYYMMDD-HHMM としたい場合は下記のように指定

```json
{"文字列_{{isotime \"20060102-0304\"}}" }
```
↓ 下記のような出力結果に
**「文字列_20171120-2008」**

※format指定時の"(ダブルコーテーション)はエスケープする必要がありました。
※年は西暦後半2桁しか出力されないため、上二桁を足しています。
※**TimezoneはUTC固定**。この方法ではJSTなどに変更することは出来ないとのことです。

▽format Referece

| a date     | Numeric |
|:-----------:|------------:|
| Year | 06 |
| Month | 01(or Jan) |
| Date | 02 |
| Hour | 03(or 15) |
| Minute | 04 |
| Second | 05 |

# 03. 実装例
## 03-1. 基本的なセットアップがされているAMIの作成
### ■ 概要
・amzn-linuxのAMIを基に、システム関連の設定、ログインユーザの作成など
サーバ構築時によく行われるセットアップを施したAMIを作成してみようと思います。

### ■ 詳細
1) 公式配布のAmazon Linuxをベースに
2) yum update実施 (amzn-linuxは配布時点で最新になっているので、この場合不要ですが)
3) TimeZoneやlocaleを日本に
4) 第三者のログイン用ユーザ「guest」を作成。公開鍵も配置
5) swap領域も確保

## 03-2. 事前準備
### ■ 03-2-a. ファイル
・packer実行サーバ上に該当ユーザの鍵ペアを作成しておきました。

```bash
/home/guest/.ssh/authorized_keys
/home/guest/.ssh/guest.pem
```

### ■ 03-2-0. シェルスクリプト
・補足
Packerの**Template(JSON形式)**に直接記載することも可能ですが、
**JSONの書式規定により正規表現の利用が難しい**ため、**外部シェルスクリプトを呼び出す**というシンプルな形をとってみました。

### 03-2-1. general.sh
・全般的なものを定義。今回はYum updateのみ

```bash
#!/bin/sh

sudo yum -y update
```
**注意: 起動したインスタンスがインターネットに出られる環境であること**
※起動時にアサインする VPC(NAT、IGW)、Subnet、SecurityGroupに注意
一時的に起動する場所のため**本番と同じNW環境でなくとも問題ありません**


### 03-2-2. system_setup.sh
・TimeZoneなどシステム関連の設定

```bash
#!/bin/sh

sudo sed -i -e 's/ZONE=\"UTC\"/ZONE=\"Asia\/Tokyo\"/g' /etc/sysconfig/clock
sudo cp  -f /usr/share/zoneinfo/Japan /etc/localtime
sudo sed -i -e 's/repo_upgrade: security/repo_upgrade: none/g' /etc/cloud/cloud.cfg
sudo sed -i -e 's/en_US\.UTF-8/ja_JP\.UTF-8/g' /etc/sysconfig/i18n
```

### 03-2-3. create_user.sh
・ユーザ作成

```bash
#!/bin/sh

sudo groupadd -g 1000 guest
sudo useradd -u 1000 -g 1000 guest
sudo echo password1234 | sudo passwd --stdin guest
sudo gpasswd -a guest wheel
sudo sed -i -e "s/^#\(.*NOPASSWD.*\)/\1/" /etc/sudoers
sudo mkdir /home/guest/.ssh
sudo chmod 700 /home/guest/.ssh
```
**秘密鍵をイメージ内に残したくない**ため、ここでは用意していません。


### 03-2-4. create_swap.sh
・Swap領域定義

```bash
#!/bin/sh

sudo dd if=/dev/zero of=/swap bs=1M count=1024
sudo mkswap /swap
sudo chmod 600 /swap
sudo swapon /swap
sudo sed -i -e '$ a /swap       swap        swap    defaults        0   0' /etc/fstab
```
必要があれば

## 03-3. テンプレート

```json:basic-setup.json
{
    "builders":
    [
        {
            "type": "amazon-ebs",
            "ami_name": "mitzi_base_{{isotime \"20060102-0304\"}}",
            "region": "ap-northeast-1",
            "source_ami": "ami-2803ac4e",
            "instance_type": "t2.micro",
            "ssh_username": "ec2-user",

            "security_group_ids": ["sg-962132f1", "sg-d02033b7"],
            "vpc_id": "vpc-505ac034",
            "subnet_id": "subnet-75f7da03",
            "associate_public_ip_address": true,
            "ssh_timeout": "5m",
            "tags": {
              "OS_Version": "Amazon Linux 2017.09.01",
              "BillingType": "RESEARCH"
            }
        }
    ],
    "provisioners": [
      {
        "type": "shell",
        "scripts": [
            "./scripts/general.sh",
            "./scripts/system_setup.sh",
            "./scripts/create_user.sh",
            "./scripts/create_swap.sh"
        ]
      },
      {
         "type": "file",
         "source": "/home/guest/.ssh/authorized_keys",
         "destination": "/tmp/authorized_keys"
      },
      {
        "type": "shell",
        "inline": [
        "sudo mv /tmp/authorized_keys /home/guest/.ssh/authorized_keys",
        "sudo chmod 600 /home/guest/.ssh/authorized_keys",
        "sudo chown -R guest:guest /home/guest"
        ]
      }
    ]
}
```

## 上述したテンプレートのポイント
・**source_ami**から起動したインスタンスへのSSH接続には、Packerが用意する**一時的なキーペア**が利用されます。

・**Credential keyをコード上にもたせたくなかった**ため、packer実行インスタンスに**IAM Roleを付与**しています。

・事前に用意していた公開鍵を起動したインスタンスに配置しています。
→ 「/home/guest/.ssh/」配下に直接配置しようとしましたが**権限無しとエラーになる**ため、一旦 /tmp 配下に配置しています。 (配置先のpermissionを777にしても駄目でした)

## 03-4. 実行結果
・実行すると下記のようなログが標準出力されます。
(日本語設定を盛り込んでから一部文字化けするようになりました。)

## ・構文チェック

```
[root@ip-10-100-5-195 ~]# packer validate basic-setup.json 
Template validated successfully.
```

## ・build実行

```
[root@ip-10-100-5-195 ~]# packer build basic-setup.json 
amazon-ebs output will be in this color.

==> amazon-ebs: Prevalidating AMI Name: mitzi_base_20171120-0731
    amazon-ebs: Found Image ID: ami-2803ac4e
==> amazon-ebs: Creating temporary keypair: packer_5a1284ed-27b6-4c78-e7aa-9b86380a5e56
==> amazon-ebs: Launching a source AWS instance...
==> amazon-ebs: Adding tags to source instance
    amazon-ebs: Adding tag: "Name": "Packer Builder"
    amazon-ebs: Instance ID: i-01a24d283513a107a
==> amazon-ebs: Waiting for instance (i-01a24d283513a107a) to become ready...
==> amazon-ebs: Waiting for SSH to become available...
==> amazon-ebs: Connected to SSH!
==> amazon-ebs: Provisioning with shell script: ./scripts/general.sh
    amazon-ebs: Loaded plugins: priorities, update-motd, upgrade-helper
    amazon-ebs: No packages marked for update
==> amazon-ebs: Provisioning with shell script: ./scripts/system_setup.sh
==> amazon-ebs: Provisioning with shell script: ./scripts/create_user.sh
    amazon-ebs: 繝ｦ繝ｼ繧ｶ繝ｼ guest 縺ｮ繝代せ繝ｯ繝ｼ繝峨ｒ螟画峩縲
    amazon-ebs: passwd: 縺吶∋縺ｦ縺ｮ隱崎ｨｼ繝医□繧ｯ繝ｳ縺梧ｭ｣縺励￥譖ｴ譁ｰ縺ｧ縺阪∪縺励◆縲
    amazon-ebs: Adding user guest to group wheel
==> amazon-ebs: Provisioning with shell script: ./scripts/create_swap.sh
    amazon-ebs: 1024+0 繝ｬ繧ｳ繝ｼ繝牙□蜉
    amazon-ebs: 1024+0 繝ｬ繧ｳ繝ｼ繝牙□蜉
    amazon-ebs: 1073741824 繝舌う繝 (1.1 GB) 繧ｳ繝斐□縺輔ｌ縺ｾ縺励◆縲 14.094 遘偵 76.2 MB/遘
    amazon-ebs: 繧ｹ繝ｯ繝□□遨ｺ髢薙ヰ繝ｼ繧ｸ繝ｧ繝ｳ1繧定ｨｭ螳壹＠縺ｾ縺吶√し繧､繧ｺ = 1048572 KiB
    amazon-ebs: 繝ｩ繝吶Ν縺ｯ縺ゅｊ縺ｾ縺帙ｓ, UUID=7ee19a25-bb14-40da-8115-74eaf0d0dbe5
==> amazon-ebs: Uploading /home/guest/.ssh/authorized_keys => /tmp/authorized_keys
==> amazon-ebs: Provisioning with shell script: /tmp/packer-shell459700274
==> amazon-ebs: Stopping the source instance...
    amazon-ebs: Stopping instance, attempt 1
==> amazon-ebs: Waiting for the instance to stop...
==> amazon-ebs: Creating the AMI: mitzi_base_20171120-0731
    amazon-ebs: AMI: ami-ba2291dc
==> amazon-ebs: Waiting for AMI to become ready...
==> amazon-ebs: Adding tags to AMI (ami-ba2291dc)...
==> amazon-ebs: Tagging snapshot: snap-03cf7a7d44094beb7
==> amazon-ebs: Creating AMI tags
    amazon-ebs: Adding tag: "BillingType": "RESEARCH"
    amazon-ebs: Adding tag: "OS_Version": "Amazon Linux 2017.09.01"
==> amazon-ebs: Creating snapshot tags
==> amazon-ebs: Terminating the source AWS instance...
==> amazon-ebs: Cleaning up any extra volumes...
==> amazon-ebs: No volumes to clean up, skipping
==> amazon-ebs: Deleting temporary keypair...
Build 'amazon-ebs' finished.

==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:
ap-northeast-1: ami-ba2291dc
```
・AMI作成が完了すると、一時的に起動したインスタンス(名称は「Packer Builder」)は自動で削除されます。

## 03-5. 確認
・意図したとおりのAMIが作成されたか、作成したAMIからインスタンスを起動して確認。

1) guestユーザの秘密鍵でSSH接続出来ることを確認
2) ユーザがsudo権限を持っていることを確認
3) TimeZoneや文字設定が意図したとおりに設定されていることを確認
4) Swapが有効になっていることを確認
5) 作成されたAMIに意図したTagが付与されていることを確認

# 04. 備考
## 04-1. 必要なPolicy
・今回はIAM Roleを利用しましたが、Packer実行に最低限必要なAWS権限は下記公式のとおりになります。
https://www.packer.io/docs/builders/amazon.html#using-an-iam-task-or-instance-role

## 04-2. 独自key pairをアサインしているAMIを利用した場合
・build時にssh接続出来ない問題が発生しました。
同問題は他の方も経験しているようで下記の方が仰るように公開鍵を削除する処理を盛り込みましたが、私の場合はそれでも起動しませんでした。
https://qiita.com/ikuyamada/items/97c286990adfe1770acf

そこで、下記の方のコメント欄のやりとりにあるようにPackerの独自keyを使用せず、
「key pair」及び接続時の「秘密鍵」を指定する方法にて問題解決しています。
https://qiita.com/soeda_jp/items/e9f759e2db63637e8549


# おわり
今回はここまでになります。