Packer 入門として AWS Systems Manager エージェントがインストール済みの RHEL 7 AMI を作成してみた

Packer で ぱっかーん。

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

コンバンハ、千葉(幸)です。

最近、RHEL 7.7 の EC2 インスタンスを触りたい機会がありました。 Systems Manager を使って操作する気まんまんですが、Red Hat 社から標準で提供されている AMI ではエージェントがプリインストールされていません

「インスタンスを作成してからインストールする」でもいいのですが、せっかくなので前々から触ってみたいと思っていた Packer を使用してエージェントインストール済みの AMI を作ってみることにします。

パカっていきましょう。

Packer とは

Terraform などでお馴染みの HashiCorp 社が提供している、マシンイメージの作成・管理を行うコマンドラインツールです。 AWS に限らず様々な環境で使用できます。

今回やりたいことは AMI の作成ですので、そこに限定し、かつ今回の構成にあわせてイメージ図を書くとこのようになります。

テンプレート形式でもろもろの値を定義し、それを指定してビルドすることで大まかに以下の流れが実行されます。

  • ソース AMI からテンポラリなインスタンスの作成
  • インスタンスに操作端末から SSH 接続しシェルの実行
  • 設定が完了したインスタンスから AMI の作成
  • インスタンスや関連するテンポラリなリソースの削除

前提条件

インスタンスや AMI などのリソースの作成を伴いますので、なにがしかの形で IAM の認証情報が必要となります。

今回はAdministratorAccessを持つ IAM ユーザーのクレデンシャルを用意しました。AWS CLI のプロファイルで指定済みです。

.aws/config

[default]
region = ap-northeast-1

.aws/credentials

[default]
aws_access_key_id = AKIAQ3BIIHXXXXXXXXXX
aws_secret_access_key = nb4QLVBkQRXXXXXXXXXXXXXXXXXXXXXXXXXXXX

IAM ロールを引き受ける形でも実行可能です。他の手段については以下を参照してください。

Packer のインストール

筆者の環境は mac のため、以下でインストールを行います。

% brew install packer

バージョンは以下の通りです。

% packer --version
1.7.2

テンプレートファイルの作成

以下を作成しました。

rhel7_ssmagent.json

{
  "builders": [
    {
      "type": "amazon-ebs",
      "region": "ap-northeast-1",
      "instance_type": "t2.micro",
      "ssh_username": "ec2-user",
      "source_ami_filter": {
        "filters": {
          "name": "RHEL-7.7_HVM-*-x86_64-1-Hourly2-GP2"
        },
        "owners": ["309956199498"],
        "most_recent": true
      },
      "ami_name": "RHEL-7.7-{{isotime | clean_resource_name}}",
      "tags": {
        "Base_AMI_ID": "{{ .SourceAMI }}",
        "Base_AMI_NAME": "{{ .SourceAMIName }}"
      }
    }
  ],
    "provisioners": [
      {
        "type":  "shell",
        "inline": [
          "sudo yum install -y https://s3.ap-northeast-1.amazonaws.com/amazon-ssm-ap-northeast-1/latest/linux_amd64/amazon-ssm-agent.rpm"
        ]
      }
    ]
}

ハイライト部を補足します。

source_ami_filter により最新の AMI を指定

こちらのエントリを参考にしました。今回は RHEL 7.7 の最新の AMI を指定する形式としています。

対象の AMI の所有者は以下画面から ami-id で検索して確認しました。

ami-rhe7_7

Systems Manager エージェントのインストールコマンド

以下ページに記載のコマンドを指定しています。

余談ですが、取得先が S3 なので、プライベートサブネットのインスタンスでも VPC エンドポイントを経由すれば問題なくインストールできそうですね。

テンプレート構文の検証

packer validate <テンプレートファイル名>で確認を行います。特に何も出力されなければ問題ありません。

% packer validate rhel7_ssmagent.json
%

誤った構文の場合には例えば以下のように表示されます。

% packer validate rhel7_ssmagent.json
Failed to parse file as legacy JSON template: if you are using an HCL template, check your file extensions; they should be either *.pkr.hcl or *.pkr.json; see the docs for more details: https://www.packer.io/docs/templates/hcl_templates.
Original error: Error parsing JSON: invalid character '"' after object key:value pair
At line 25, column 10 (offset 579):
   24:         "type":  "shell"
   25:         "
               ^

使用可能なオプションなどは以下を参照してください。

% packer validate
Usage: packer validate [options] TEMPLATE

  Checks the template is valid by parsing the template and also
  checking the configuration with the various builders, provisioners, etc.

  If it is not valid, the errors will be shown and the command will exit
  with a non-zero exit status. If it is valid, it will exit with a zero
  exit status.

Options:

  -syntax-only           Only check syntax. Do not verify config of the template.
  -except=foo,bar,baz    Validate all builds other than these.
  -machine-readable      Produce machine-readable output.
  -only=foo,bar,baz      Validate only these builds.
  -var 'key=value'       Variable for templates, can be used multiple times.
  -var-file=path         JSON or HCL2 file containing user variables.

ビルドの実行

早速実行していきます。

packer build <テンプレートファイル名>で実行できます。

失敗1:デフォルト VPC がない

幸先よく失敗します。

% packer build rhel7_ssmagent.json
amazon-ebs: output will be in this color.

==> amazon-ebs: Prevalidating any provided VPC information
==> amazon-ebs: Prevalidating AMI Name: RHEL-7.7-2021-06-04T13-20-35Z
    amazon-ebs: Found Image ID: ami-04a12c0fbaeac005e
==> amazon-ebs: Creating temporary keypair: packer_60ba28a3-5792-7fa7-c47c-02568ac89612
==> amazon-ebs: Creating temporary security group for this instance: packer_60ba28a4-013a-cdab-ca76-03188bc630f2
==> amazon-ebs: VPCIdNotSpecified: No default VPC for this user
==> amazon-ebs: 	status code: 400, request id: cdeeff9c-f34a-4c8b-a71c-b39c62bfe56a
==> amazon-ebs: Deleting temporary keypair...
Build 'amazon-ebs' errored after 2 seconds 48 milliseconds: VPCIdNotSpecified: No default VPC for this user
	status code: 400, request id: cdeeff9c-f34a-4c8b-a71c-b39c62bfe56a

==> Wait completed after 2 seconds 49 milliseconds

==> Some builds didn't complete successfully and had errors:
--> amazon-ebs: VPCIdNotSpecified: No default VPC for this user
	status code: 400, request id: cdeeff9c-f34a-4c8b-a71c-b39c62bfe56a

==> Builds finished but no artifacts were created.

AMI のベースとなるテンポラリなインスタンスは、配置先の指定がないとデフォルト VPC に作成が試みられます。私の環境ではデフォルト VPC が削除済みであるためにエラーが発生しました。

ということで、テンプレートファイルを以下に修正しました。

rhel7_ssmagent_kai.json

{
  "builders": [
    {
      "type": "amazon-ebs",
      "region": "ap-northeast-1",
      "instance_type": "t2.micro",
      "ssh_username": "ec2-user",
      "source_ami_filter": {
        "filters": {
          "name": "RHEL-7.7_HVM-*-x86_64-1-Hourly2-GP2"
        },
        "owners": ["309956199498"],
        "most_recent": true
      },
      "ami_name": "RHEL-7.7-{{isotime | clean_resource_name}}",
      "tags": {
        "Base_AMI_ID": "{{ .SourceAMI }}",
        "Base_AMI_NAME": "{{ .SourceAMIName }}"
      },
      "vpc_id": "vpc-0e4acafc38414468c",
      "subnet_id": "subnet-0caa45223899b4b73"
    }
  ],
    "provisioners": [
      {
        "type":  "shell",
        "inline": [
          "sudo yum install -y https://s3.ap-northeast-1.amazonaws.com/amazon-ssm-ap-northeast-1/latest/linux_amd64/amazon-ssm-agent.rpm"
        ]
      }
    ]

テンポラリなインスタンスの作成先を特定の VPC、サブネット(パブリック)に指定しました。

失敗2:パブリック IP アドレスが付与されない

上記のテンプレートを指定して再度ビルドを行います。

% packer build rhel7_ssmagent_kai.json
amazon-ebs: output will be in this color.

==> amazon-ebs: Prevalidating any provided VPC information
==> amazon-ebs: Prevalidating AMI Name: RHEL-7.7-2021-06-04T13-27-43Z
    amazon-ebs: Found Image ID: ami-04a12c0fbaeac005e
==> amazon-ebs: Creating temporary keypair: packer_60ba2a4f-fc54-7e58-f68c-d44979599175
==> amazon-ebs: Creating temporary security group for this instance: packer_60ba2a51-1c72-0015-9331-d2d157427a1d
==> amazon-ebs: Authorizing access to port 22 from [0.0.0.0/0] in the temporary security groups...
==> 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-079c3c8497366fea9
==> amazon-ebs: Waiting for instance (i-079c3c8497366fea9) to become ready...
==> amazon-ebs: Using ssh communicator to connect: 192.168.0.145
==> amazon-ebs: Waiting for SSH to become available...
==> amazon-ebs: Timeout waiting for SSH.
==> 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 security group...
==> amazon-ebs: Deleting temporary keypair...
Build 'amazon-ebs' errored after 7 minutes 7 seconds: Timeout waiting for SSH.

==> Wait completed after 7 minutes 7 seconds

==> Some builds didn't complete successfully and had errors:
--> amazon-ebs: Timeout waiting for SSH.

==> Builds finished but no artifacts were created.

テンポラリな SecuriryGroup(0.0.0.0/0 で SSHが開いてる……!)、インスタンスの作成が行われます。その後に SSH 接続が試みられていますが、接続先としてインスタンスのプライベート IP アドレスが指定されています。

これでは当然 SSH 接続できませんので、タイムアウトしています。

今回私が指定したサブネットはパブリックサブネットですが、サブネットでのパブリック IP アドレスの自動割り当て設定は無効となっています。テンポラリなインスタンスに対して明示的に割り当ての許可が必要です。

ということで、テンプレートを以下のように修正しました。

rhel7_ssmagent_kaikai.json

{
  "builders": [
    {
      "type": "amazon-ebs",
      "region": "ap-northeast-1",
      "instance_type": "t2.micro",
      "ssh_username": "ec2-user",
      "source_ami_filter": {
        "filters": {
          "name": "RHEL-7.7_HVM-*-x86_64-1-Hourly2-GP2"
        },
        "owners": ["309956199498"],
        "most_recent": true
      },
      "ami_name": "RHEL-7.7-{{isotime | clean_resource_name}}",
      "tags": {
        "Base_AMI_ID": "{{ .SourceAMI }}",
        "Base_AMI_NAME": "{{ .SourceAMIName }}"
      },
      "vpc_id": "vpc-0e4acafc38414468c",
      "subnet_id": "subnet-0caa45223899b4b73",
      "associate_public_ip_address": true,
      "security_group_ids": "sg-0a597e5b2a9a1d86d"
    }
  ],
    "provisioners": [
      {
        "type":  "shell",
        "inline": [
          "sudo yum install -y https://s3.ap-northeast-1.amazonaws.com/amazon-ssm-ap-northeast-1/latest/linux_amd64/amazon-ssm-agent.rpm"
        ]
      }
    ]
}

パブリック IP アドレスの自動割り当てを許可したほか、既存の SecuriryGroup を使用するように変更しました。

この SecuriryGroup では 22 ポートのインバウンドをマイ IP (自身の拠点のグローバル IP )に限定しています。

ようやくビルド成功

上記のテンプレートを指定してビルドを行うことで、ようやく成功しました。

せっかくなのでプロセスを分割して見ていきます。

% packer build rhel7_ssmagent_kaikai.json
amazon-ebs: output will be in this color.

==> amazon-ebs: Prevalidating any provided VPC information
==> amazon-ebs: Prevalidating AMI Name: RHEL-7.7-2021-06-04T13-52-43Z
    amazon-ebs: Found Image ID: ami-04a12c0fbaeac005e
==> amazon-ebs: Creating temporary keypair: packer_60ba302b-54e3-60f5-5218-b9ef9614466c
==> 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-04a7f0a409d3fa99f
==> amazon-ebs: Waiting for instance (i-04a7f0a409d3fa99f) to become ready...
==> amazon-ebs: Using ssh communicator to connect: 54.95.37.143
==> amazon-ebs: Waiting for SSH to become available...
==> amazon-ebs: Connected to SSH!

↑テンポラリなインスタンスが立ち上がり、自端末からグローバル IP アドレスを指定して SSH 接続が成功します。接続に使用するキーペアもテンポラリなものです。ここまで 1 分もかからなかったかと思います。

==> amazon-ebs: Provisioning with shell script: /var/folders/5_/v4p285913wq2scrrq28g14nc0000gq/T/packer-shell417327082
    amazon-ebs: Loaded plugins: amazon-id, search-disabled-repos
    amazon-ebs: Examining /var/tmp/yum-root-GMKzK7/amazon-ssm-agent.rpm: amazon-ssm-agent-3.0.1209.0-1.x86_64
    amazon-ebs: Marking /var/tmp/yum-root-GMKzK7/amazon-ssm-agent.rpm to be installed
    amazon-ebs: Resolving Dependencies
    amazon-ebs: --> Running transaction check
    amazon-ebs: ---> Package amazon-ssm-agent.x86_64 0:3.0.1209.0-1 will be installed
    amazon-ebs: --> Finished Dependency Resolution
    amazon-ebs:
    amazon-ebs: Dependencies Resolved
    amazon-ebs:
    amazon-ebs: ================================================================================
    amazon-ebs:  Package              Arch       Version            Repository             Size
    amazon-ebs: ================================================================================
    amazon-ebs: Installing:
    amazon-ebs:  amazon-ssm-agent     x86_64     3.0.1209.0-1       /amazon-ssm-agent     108 M
    amazon-ebs:
    amazon-ebs: Transaction Summary
    amazon-ebs: ================================================================================
    amazon-ebs: Install  1 Package
    amazon-ebs:
    amazon-ebs: Total size: 108 M
    amazon-ebs: Installed size: 108 M
    amazon-ebs: Downloading packages:
    amazon-ebs: Running transaction check
    amazon-ebs: Running transaction test
    amazon-ebs: Transaction test succeeded
    amazon-ebs: Running transaction
    amazon-ebs:   Installing : amazon-ssm-agent-3.0.1209.0-1.x86_64                         1/1
    amazon-ebs: Created symlink from /etc/systemd/system/multi-user.target.wants/amazon-ssm-agent.service to /etc/systemd/system/amazon-ssm-agent.service.
    amazon-ebs:   Verifying  : amazon-ssm-agent-3.0.1209.0-1.x86_64                         1/1
    amazon-ebs:
    amazon-ebs: Installed:
    amazon-ebs:   amazon-ssm-agent.x86_64 0:3.0.1209.0-1
    amazon-ebs:
    amazon-ebs: Complete!

↑SSM エージェントのインストールがあっという間に終わります。

==> amazon-ebs: Stopping the source instance...
    amazon-ebs: Stopping instance
==> amazon-ebs: Waiting for the instance to stop...
==> amazon-ebs: Creating AMI RHEL-7.7-2021-06-04T13-52-43Z from instance i-04a7f0a409d3fa99f
    amazon-ebs: AMI: ami-063ec89988bc36945
==> amazon-ebs: Waiting for AMI to become ready...
==> amazon-ebs: Adding tags to AMI (ami-063ec89988bc36945)...
==> amazon-ebs: Tagging snapshot: snap-0d806e1437959e2d3
==> amazon-ebs: Creating AMI tags
    amazon-ebs: Adding tag: "Base_AMI_ID": "ami-04a12c0fbaeac005e"
    amazon-ebs: Adding tag: "Base_AMI_NAME": "RHEL-7.7_HVM-20191028-x86_64-1-Hourly2-GP2"
==> 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 after 10 minutes 42 seconds.

==> Wait completed after 10 minutes 42 seconds

==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:
ap-northeast-1: ami-063ec89988bc36945

↑テンポラリなインスタンスはあっという間に停止され、AMI の作成処理が走ります。ビルドの実行から停止まで 1 分強程度の素早い処理でした。 AMI の作成は流石に多少時間がかかり、10 GiB のスナップショットを持つ AMI が 10 分程度で作成されました。

AMI の作成が完了したらテンポラリなインスタンス、キーペアは削除され一通りの処理が完了です。

指定した通りの内容で AMI が作成されました。

packer_rhel7_dekita

作成したAMI からインスタンスの作成

念のためきちんと Systems Manager エージェントが機能するかを確認します。

Packer で作成した AMI から新規インスタンスを立ち上げました。パブリックサブネットに配置し、必要な権限を持つ IAM ロールをアタッチしています。

インスタンスに対して Systems Manager のセッションマネージャー接続を試みると……

Packer_RHEL

Packer_RHEL_SSM

RHEL_SSM

この通り、きちんと接続できました。

やったね!

終わりに

今更ながら Packer に入門してみました。先人のブログを眺めていて「一時的なインスタンスが作成されてそこから AMI が取得される」というところまではイメージがついていたのですが、

  • インスタンスはどこに作成されるの?
    • →(指定なしだとデフォルト VPC。指定も可)
  • インスタンス上での操作はどう行われるの?
    • →(操作端末から、指定した OS ユーザーで SSH して実行)

というのが分かっていなかったので、実際に手を動かしてみてようやく理解できました。

せっかく覚えたので、今後も積極的に使っていきたいと思います。

以上、 チバユキ (@batchicchi) がお送りしました。

参考