Amazon Linux 2をローカル環境にAnsibleテスト用途で作成する。

# はじめに
先日仕事で`Ansible`の`Playbook`を作成・テストするためにローカル環境で`Amazon Linux 2`の仮想マシン環境を作ったので残しておこうと思います。

# Amazon Linux 2をローカルで動かそうと思ったきっかけ
仕事でよく`Ansible`を使ってAWSの`EC2`向けのコードを書いているのですが、今までは適当な`EC2`をスポットインスタンスで作成して動作チェック、もとに戻したい場合は新しく`EC2`を作っていました。

ただ、いちいち新しい`EC2`を作るのも時間とお金がかかるので、だったら`VirtualBox`上で`EC2`（`Amazon Linux 2`）を実行して、戻したい場合は`VirtualBox`のスナップショットでもとに戻せばよいのでは？と思ったのがきっかけです。

# 環境
今回は`VirtualBox`がインストールされている`Linux`で仮想マシンを作ってみようと思います。

`Windows`で行う場合でも手順はあまり変わりませんが、`Windows`の`VirtualBox`で行う場合は適宜読み替えてください。

以下、`VirtualBox`がインストールされている`Linux`ホストマシンの構成。
※OSアップグレードしないとなぁ・・・

|構成|種別|
|:--|:--|
|OS|Ubuntu 20.04|
|仮想化ソフト|VirtualBox 6.1.40|

# やりかた
そもそもAWSの公式ドキュメントに書いてありました。
- [Amazon Linux 2 を仮想マシンとしたオンプレミスでの実行](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/amazon-linux-2-virtual-machine.html)

上記の公式ドキュメントを参考に以下の流れで進めていきたいと思います。

- [Amazon Linux 2 ローカル実行の仕組み](#amazon-linux-2-ローカル実行の仕組み)
- [seed.iso起動イメージの準備](#seediso起動イメージの準備)
    - [meta-dataの作成](#meta-dataの作成)
    - [SSH公開鍵の作成](#ssh公開鍵の作成)
    - [user-dataの作成](#user-dataの作成)
        - [SSH公開鍵認証の設定](#ssh公開鍵認証の設定)
        - [DNSの設定](#dnsの設定)
        - [カーネルアップグレード](#カーネルアップグレード)
        - [Python3インストール](#python3インストール)
        - [最終的なuser-data設定](#最終的なuser-data設定)
    - [seed.isoの作成](#seedisoの作成)
- [Amazon Linux 2 VMイメージのダウンロード](#amazon-linux-2-vmイメージのダウンロード)
- [VirtualBox仮想マシンの作成](#virtualbox仮想マシンの作成)
- [SSHでの接続確認](#sshでの接続確認)

## Amazon Linux 2 ローカル実行の仕組み
`Amazon Linux 2`をローカルで実行するためには、`AWS`で`EC2`を作成する場合と同様、初回起動時に`cloud-init`を動かし、ネットワーク等の基本設定と追加設定を行わせる必要があるようです。

`cloud-init`で必要となる`meta-data`と`user-data`をISO形式でまとめたファイルを初回起動時に読み込ませることで`cloud-init`により基本設定が行えるようにしているとのこと。

|ファイル名|説明|
|:--|:--|
|meta-data|ホスト名やネットワーク設定を記載|
|user-data|EC2作成時に「ユーザデータ」に記載する設定やユーザアカウント設定を記載|

仕組みとしてはISOファイルを使った自動インストールと同等の仕組みとなるため、Linuxの自動インストールを行ったことがある方はイメージしやすいかと思います。

## seed.iso起動イメージの準備
初期設定を行う`cloud-init`で必要となる`meta-data`、`user-data`の作成と、その２つのファイルをまとめたISOイメージの作成を行います。

### meta-dataの作成
ホスト名とネットワーク設定を自分のローカル環境に合わせて行います。

```yml:meta-data
local-hostname: vm_hostname
# eth0 is the default network interface enabled in the image. You can configure static network settings with an entry like the following.
network-interfaces: |
  auto eth0
  iface eth0 inet static
  address 192.168.1.10
  network 192.168.1.0
  netmask 255.255.255.0
  broadcast 192.168.1.255
  gateway 192.168.1.254
```

尚、AWSのドキュメントでは、IPアドレスを固定した上記のようなネットワーク設定が書かれていますが、実際の`EC2`の構成になるべく合わせたいのであれば、`meta-data`の設定をDHCP用の設定にすることで、`EC2`で作成される構成に近い構成になると思います。

但し、DHCPなのでどのアドレスが割り振られるかわからなかったり、ローカルにDHCPサーバが必要となったりするため、よほどの理由がなければIPアドレスを固定したほうが良いかと思います。

### SSH公開鍵の作成
今回はAWSで`EC2`接続する際に使っている秘密鍵を使いまわしてローカル接続時にも同じ秘密鍵で接続できるようにしたいので、`EC2`接続する際に使っている秘密鍵から公開鍵を生成しておきます。

```bash:SSH公開鍵の作成
ssh-keygen -y -f [キーペア名].pem
```

上記コマンドを実行して出力される結果を控えておきます。（以下出力例）

```bash:コマンド実行結果例
ssh-rsa AAAAB3NzaC1(以下略)==
```

もし、`Windows`でコマンド実行が難しかったり、生成するのが面倒であれば、`EC2`の`/home/ec2-user/.ssh/authorized_keys`ファイルに記載されている公開鍵情報を控えて使っても問題ありません。

### user-dataの作成
`user-data`もAWSのドキュメントに記載されている内容を基に作っていきます。

但し、ドキュメントに記載されている内容そのままでは使い勝手が悪いため、以下について追加で`user-data`に盛り込んでおきます。

- [SSH公開鍵認証の設定](#ssh公開鍵認証の設定)
- [DNSの設定](#dnsの設定)
- [カーネルアップグレード](#カーネルアップグレード)
- [Python3インストール](#python3インストール)
- [最終的なuser-data設定](#最終的なuser-data設定)

#### SSH公開鍵認証の設定
ドキュメントに記載されている設定では黒いコンソール画面からのログインしか許可されず、リモートからのSSHログインができないため、先程出力させた公開鍵の情報を追加し、SSHログインできるようにしておきます。

`ssh_authorized_keys`の項目を追加し、後述する設定のように行頭にハイフンとスペースを入力した後、`ssh-keygen`コマンドで出力した結果か`EC2`の`authorized_keys`ファイルに記載されている公開鍵をそのままコピペすればOKです。

もし複数`authorized_keys`を登録したい場合は、改行して同じ書き方で記載すれば複数登録できます。

#### DNSの設定
ドキュメントに記載されている設定では`DNS`の設定が行われず、`yum`等によるパッケージインストールも行えないので、以下のQAを参考に`user-data`に盛り込んでおきます。

- [プライベート Amazon EC2 インスタンスが Amazon Linux、Ubuntu、または RHEL で実行中です。再起動中も持続する EC2 インスタンスに静的 DNS サーバーを割り当てる方法を教えてください。](https://aws.amazon.com/jp/premiumsupport/knowledge-center/ec2-static-dns-ubuntu-debian/)

設定方法として2つ紹介されていますが、今回は1ファイルの設定だけで済むオプション2の方法を盛り込み、`ifcfg-ethX`の修正のみで設定したいと思います。

`Amazon Linux 2`は`RHEL`ベースですが`NetworkManager`が使われておらず、`nmcli`コマンドが使えないので`/etc/sysconfig/network-scripts/ifcfg-eth0`ファイルに設定を無理やり追加します。

#### カーネルアップグレード
2023年1月現在、AWSで`Amazon Linux 2`のEC2を作成する際、`Kernel 4.14`と`Kernel 5.10`が選択できますが、後述のダウンロードページからダウンロードできるイメージは`Kernel 4.14`のイメージとなります。

`Kernel 5.10`のイメージはダウンロードできないようなので、`kernel 4.14`で起動した後、`amazon-linux-extras`コマンドで`kernel 5.10`をインストールしたいと思います。

#### Python3インストール
`Kernel 4.14`版の`Amazon Linux 2`には`Python`の2系バージョンしかインストールされておらず、`Ansible`で`Amazon Linux 2`の管理対象マシンに接続すると、`Python3`がインストールされていないため実行できないと言ったエラーが表示されます。

`Kernel 5.10`版の`Amazon Linux 2`の場合は`Python`の2系バージョン、3系バージョンともにインストールされていますが、前述のように`Kernel 4.14`から`amazon-linux-extras`で`Kernel 5.10`をインストールした場合は`Python3`はインストールされません。

そのため、`Python3`のインストールと`python3`に対するシンボリックリンクの作成を行います。

#### 最終的なuser-data設定
上述した設定を盛り込んだ最終的な`user-data`の設定が以下となります。

```yml:user-data
#cloud-config
#vim:syntax=yaml
users:
# A user by the name `ec2-user` is created in the image by default.
  - default
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1(以下略)==
chpasswd:
  list: |
    ec2-user:[コンソール接続用パスワード]
# In the above line, do not add any spaces after 'ec2-user:'.
runcmd:
  - egrep "^PEERDNS=" /etc/sysconfig/network-scripts/ifcfg-eth0 > /dev/null 2>&1 || echo "PEERDNS=yes" >> /etc/sysconfig/network-scripts/ifcfg-eth0
  - egrep "^DNS1=" /etc/sysconfig/network-scripts/ifcfg-eth0 > /dev/null 2>&1 || echo "DNS1=8.8.8.8" >> /etc/sysconfig/network-scripts/ifcfg-eth0
  - egrep "^DNS2=" /etc/sysconfig/network-scripts/ifcfg-eth0 > /dev/null 2>&1 || echo "DNS2=8.8.4.4" >> /etc/sysconfig/network-scripts/ifcfg-eth0
  - systemctl restart network
  - amazon-linux-extras install -y kernel-5.10
  - amazon-linux-extras install -y python3.8
  - cd /usr/bin
  - ls python3 > /dev/null 2>&1 || ln -s python3.8 python3
  - reboot
```

`runcmd`により、上述した以下の操作を行うよう設定しています。

- 行頭に`PEERDNS=`を含む行がなければ`PEERDNS=yes`を追加
- 行頭に`DNS1=`を含む行がなければ`DNS1=8.8.8.8`を追加
- 行頭に`DNS2=`を含む行がなければ`DNS2=8.8.4.4`を追加
- 次手順でリポジトリアクセスできるようにするためにネットワーク再起動
- `amazon-linux-extras`リポジトリから`kernel-5.10`をインストール
- `amazon-linux-extras`リポジトリから`python3.8`をインストール
- 次手順で`python3`のシンボリックリンク作成を行うので`/usr/bin/`に移動
- カレントディレクトリ（`/usr/bin`）に`python3`が存在しなければ`python3`のシンボリックリンクを作成
- 新しいカーネルで立ち上げるため再起動

また、コンソール接続を行わないのであれば`chpasswd`以下の内容は消してしまっても良いですが、SSH接続できなかったときの確認用に設定しておいたほうが良いです。（そもそもテスト目的でガチガチにする必要も無いので）

### seed.isoの作成
先程作成した`meta-data`、`user-data`を含んだISOファイルを作成します。

最終的には`VirtualBox`でISOイメージを指定して読み込ませるだけなので、名前は任意で問題ありませんが、AWSのドキュメントに沿って今回は`seed.iso`という名前で作成します。

ISOイメージの作成は`mkisofs`コマンド等でもできますが、AWSのドキュメントでは`genisoimage`というコマンドで行っているため、そちらで作成します。

もしコマンドがインストールされていなければパッケージインストーラでインストールしてください。

```bash:seed.isoの作成（Linux）
genisoimage -output seed.iso -volid cidata -joliet -rock user-data meta-data
```

## Amazon Linux 2 VMイメージのダウンロード
`Amazon Linux 2`のVMイメージを以下のURLから取得します。

仮想化ソフトごとにディレクトリが分かれているので、今回の場合は`virtualbox`ディレクトリ配下の`amzn2-virtualbox-2.0.YYYYMMDD.0-x86_64.xfs.gpt.vdi`をダウンロードします。

- [Amazon Linux 2 virtual machine images](https://cdn.amazonlinux.com/os-images/latest/)

尚、ここでダウンロードしたVMイメージがそのままローカル仮想マシンの仮想ディスクとして使われるため、`cloud-init`による初期設定がうまく行かなかった場合に備えて、ダウンロード後、別名でバックアップしておくことをお勧めします。

## VirtualBox仮想マシンの作成
準備したファイルを使って、Linuxの`VirtualBox`で`Amazon Linux 2`の仮想マシンを作成していきます。

「**新規**」より任意の名前、マシンフォルダー、タイプ、バージョンを指定します。

タイプは「**Linux**」、バージョンは「**Red Hat (64-bit)**」を指定してください。

![Monosnap_20230102_185120.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/660f8f98-f099-e1f8-b72d-9a56a61787bb.png)

ハードディスクは先程ダウンロードした`vdi`ファイルを指定する必要があるため、「**すでにある仮想ハードディスクファイルを使用する**」にチェックを入れ、右側のフォルダマークを選択します。

![Monosnap_20230102_185237.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/ccc1e8f3-eef8-f6f1-0c93-b63deb49fe23.png)

以下のようにハードディスク選択画面が表示されるため、「**追加**」より先程ダウンロードしたイメージパスを選択すると先程の画面に戻るので「**作成**」を行います。

![Monosnap_20230102_185403.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/64878151-2f71-11e9-ac96-0d642649d47c.png)

作成された仮想マシンの「**設定**」より、「**ストレージ**」→「**ストレージデバイス**」→「**コントローラー: IDE**」の空ディスクから、ディスクマークを選択します。

![Monosnap_20230102_185620.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/08a09252-852e-361e-74d7-70f14df4292d.png)

選択画面より、「**仮想光学ディスクの選択/作成**」を選択し、先程作成した`seed.iso`を選びます。

![Monosnap_20230102_190015.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/f8d04105-c6a5-5a2f-c44c-c37b990e796c.png)

また、ネットワーク設定もデフォルトだと`NAT`となっており、直接接続できないので、「**ネットワーク**」→「**アダプター１**」から割り当てを「**ブリッジアダプター**」にしておきます。

![Monosnap_20230102_185750.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/372b7324-5fd9-b19b-b8f5-6070c1dca56c.png)

これで設定が完了したので、起動してログイン画面が表示されれば成功です。

## SSHでの接続確認
`EC2`への接続でも使用しているSSH鍵ファイル（PEMファイル）を使ってローカルの`Amazon Linux 2`へも接続できるか確認してみます。

初回接続時、フィンガープリントの登録が聞かれますが、`yes`で進みます。

```bash:ローカル仮想マシンへのSSH接続
ssh -l ec2-user -i [秘密鍵ファイル] [Amazon Linux 2のIPアドレス]
```

パスワードが聞かれず接続できれば成功です。

もしSSH接続できなかったりする場合は`VirtualBox`のネットワーク設定やローカルのネットワーク設定を見直してみてください。

SSHのプロンプトは表示されるけど接続できない場合は`user-data`ファイルを確認してみてください。

尚、仮想マシンを作り直す場合、`vdi`ファイルも新しくする必要があるので、再度ダウンロードするか、予めコピーしておいた`vdi`ファイルから戻して再度やり直してください。

# VirtualBoxスナップショットの確認
作成した仮想マシンできちんと`VirtualBox`のスナップショット機能が使えるか確認してみます。

一旦仮想マシンを電源オフして、「**スナップショット**」から「**作成**」で適当なスナップショット名でスナップショットを作成します。

![Monosnap_20230102_193033.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/49f24947-3054-ffbd-cb23-e21c7c9c69f9.png)

再度仮想マシンを起動させ、適当にファイルを作成した後、シャットダウンして、先程作成したスナップショット名を選択し、「**復元**」を行います。

![Monosnap_20230102_193420.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/20532742-ba5c-8de8-7209-12b85e90c40f.png)

改めて仮想マシンを起動させ、先程作成したファイルが無ければスナップショット機能も無事使えることが確認できました。

# おわりに
DNS周りの設定は大変でしたが、AWS公式のドキュメントも用意されているため、比較的簡単にローカル環境に仮想マシンを作成できたのではないかと思います。

AWS上の他サービスと連携が必要な場合は今回の手順だけではできませんが、AWS上で動かしているEC2と全く同じとはいかずとも、今回の構築により`EC2`をローカルで実行できるようになったのは大きかったです。

AWS環境で作ったり壊したりするとお金がかかるので、私のように`EC2`に対する`Ansible`を作成しているような方はぜひともローカル環境に`Amazon Linux 2`の環境を作っておくと良いと思います。