Packerで、EC2にrootでsshログインしてプロビジョニングする方法

## 概要
[Packer](https://www.packer.io/)は、EC2のAMIを決められた設定・手順でプロビジョニングして作成することができ、ベースとなるAMIの作成手順をコードに残すことができるため、AMI管理において便利なツールである。

PackerでEC2のAMIを作成するときは以下の流れで処理される。（※communicatorにsshを選択している場合）

1. `packer build`コマンドでJSONで記述された設定ファイルを元にAMI作成が始まる
2. 与えたAWS_ACCESS_KEYを使ってAWS上に「Packer Builder」というEC2インスタンス（Builder）を作成する
3. Builderが指定されたベースのAMIでEC2インスタンスを立ち上げる
4. そのインスタンスに対してプロビジョニングを行う
5. 完了したらそのインスタンスをAMIに保存する
6. Builderインスタンスがterminateされる

4の手順では、BuilderインスタンスからベースのAMIにsshログインを行って、AMI上でシェルスクリプトやAnsibleを実行する。
特にSSHログインの対象ユーザの情報を何も指定せずビルドする場合は、AWS EC2だと一般ユーザ（Amazon Linuxであればec2-user）に対して、Packerが自動でkey-pairを発行してそのkey-pair情報を元に一般ユーザに対してsshログインを行う。

## 困りポイント
一般ユーザでsshログインしてプロビジョニングが行う、のところで困るケースが発生した。

`provisoners`でシェルスクリプト（`"type": "shell"`）によるプロビジョニングを行う場合、シェルスクリプト内で`sudo`を指定しておけばrootユーザでコマンドを実行してくれるので問題にはならない。

しかし、ファイルの転送（`"type": "file"`）の場合ではrootユーザで実行するようなオプションを指定できず、一般ユーザで処理を行うことになる。
例えば、Nginxの設定ファイル（`/etc/nginx/nginx.conf`）等を扱う場合はrootユーザで実行しないと、Not Permissionでエラーになるケースが発生する。そもそもプロビジョニングはrootユーザで処理したいことが大半だと思われるので、基本的にrootユーザでログインしてプロビジョニング処理を進めたいという気持ちになる。


## 対応
rootユーザでsshログインしてAMIをビルドするには以下の流れで対応する。

1. 適切なOSなどを選択したEC2インスタンスを用意する
2. そのインスタンス上で、rootユーザの`authorized_keys`に公開鍵を登録する
3. そのインスタンスのAMIを保存する
4. packerの設定ファイルに`builders.ssh_username`と`ssh_private_key_file`を記述する

簡単に言うと、1~3の手順でrootユーザにsshログインできる鍵のペアを用意し、Builderインスタンスがプロビジョニング時にsshログインするときにその鍵を使ってrootユーザにsshログインする、という算段である。

### 手順4
手順1~3については特別なことを行うわけではないので割愛し、手順4がミソなので具体的に説明する。

[Amazon EC2の`builders`](https://www.packer.io/docs/builders/amazon.html)では、

- [`ssh_username`](https://www.packer.io/docs/templates/communicator.html#ssh_username): Builderインスタンスからsshログインする対象のユーザ
- [`ssh_private_key_file`](https://www.packer.io/docs/templates/communicator.html#ssh_private_key_file): Builderインスタンスからsshログインする際に用いる暗号鍵のパス

を設定することができる。
例えば暗号鍵`private_key`と公開鍵`public_key.pub`があり、1~3の手順で作成したAMIには`public_key.pub`を登録しておいたとした場合、

```json
{
  ...
  "builders": [{
    ...
    "communicator": "ssh",
    "source_ami": "<1~3の手順で作成したAMI ID>",
    "ssh_username": "root",
    "ssh_private_key_file": "/home/some_user/.ssh/private_key",
    ...
  }],
  ...
}
```

とすればrootユーザでsshログインするようになる。

### 設定例
以上を総括して、設定例としては以下のようなものになる（あくまで一例ということで）。

```json
{
  "variables": {
    "aws_access_key": "{{env `AWS_ACCESS_KEY_ID`}}",
    "aws_secret_key": "{{env `AWS_SECRET_ACCESS_KEY`}}",
    "ssh_private_key_file": "{{env `SSH_PRIVATE_KEY_FILE`}}"
  },
  "builders": [{
    "type": "amazon-ebs",
    "access_key": "{{user `aws_access_key`}}",
    "secret_key": "{{user `aws_secret_key`}}",
    "region": "ap-northeast-1",
    "source_ami": "ami-b5ff24d3",
    "instance_type": "t2.micro",
    "ssh_username": "root",
    "ssh_private_key_file": "{{user `ssh_private_key_file`}}",
    "ssh_timeout": "3m",
    "ami_name": "my_ami {{timestamp}}"
  }],
  "provisioners": [
    {
      "type": "shell",
      "execute_command": "chmod +x {{ .Path }}; sudo {{ .Vars }} {{ .Path }}",
      "scripts": [
        "scripts/app.sh"
      ]
    },
    {
      "type": "file",
      "source": "nginx.conf",
      "destination": "/etc/nginx/nginx.conf"
    }
  ]
}

```

この設定ファイル（`sample.json`という名前で保存されているとする）を使い、ビルドする。

```
$ export SSH_PRIVATE_KEY_FILE=/user/some_user/.ssh/private_key; export AWS_...; packer build sample.json
```

これで`/etc/nginx/nginx.conf`が設置できた。