WindowsのVirtualbox上にAWS localstackを自動構築

## 目的
- WindowsPC上にAWS localstack環境を構築する。
- 構築したlocalstackは、ホストマシンであるWindowsPCや、
VirtualBox上の別の仮想マシンからもアクセスできるようにする。

## 作業環境
- Windows 10
- Virtualbox 6.1.14
- Vagrant 2.3.1

ここに、Vagrantとansible_localを利用して
【Virtualbox + Ubuntu + Docker Engine + localstack】
の環境を自動構築する。

仮想マシンのネットワークは、NAT(eth0)+ホストオンリーアダプタ(eth1)で構成する。
ネットワークの説明は [こちらの記事](https://qiita.com/feifo/items/0fde474005589afcff68) がわかりやすかった。
・仮想マシン⇒インターネットの通信はNAT(eth0)
・ホストマシン⇔仮想マシン、仮想マシン⇔仮想マシン間の通信はホストオンリーアダプタ(eth1)
を通ることになる。
localstackを利用する全てのホストマシンと仮想マシンが、
同じホストオンリーアダプタ(今回は192.168.56.0/24)に繋がるように構成する必要がある。

## Virtualbox, Vagrant のインストール
各公式サイトからダウンロードしてインストールする。
- [Virtualbox download](https://www.oracle.com/jp/virtualization/technologies/vm/downloads/virtualbox-downloads.html)
- [Vagrant download](https://www.vagrantup.com/downloads)

##### Vagrant plugin のインストール
PowerShellから以下のコマンドでプラグインをインストール。
vbguestは必須ではないがVirtualboxとのバージョン差分を解消してくれるらしいので入れておく。
```
> vagrant plugin install vagrant-vbguest
```

今回は使わないが、プロキシ環境で利用する場合はproxyconfが必要。
```
> vagrant plugin install vagrant-proxyconf
```

## 作業フォルダと設定ファイルの準備
### 作業フォルダ作成
今回は以下のフォルダ構成にしています。
```
C:\users\(ユーザ名)\documents\vagrant\ubuntu22
├───Vagrantfile
└───provision
    └───ansible-playbook.yml
```

### Vagrantfile作成
作業フォルダの直下に「Vagrantfile」というファイルを作成。

```ruby:Vagrantfileサンプル
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  # 仮想マシンにインストールするOSの指定(Vagrant Cloudから取得される)
  config.vm.box = "bento/ubuntu-22.04"

  # 仮想マシンのホスト名(hostname)。省略可。
  config.vm.hostname = "awslocalstack"

  # Virtualboxのホストオンリーアダプタのネットワーク追加と固定IP設定。
  # デフォルトでホストマシン(WindowsPC)に付与されるアドレスが192.168.56.1のため同じセグメントのIPを指定。
  # Virtualboxのネットワーク設定を見たところ、192.168.56.100以降はDHCP用のようなので、～99の範囲で好きな数字を付ければOK。
  config.vm.network "private_network", ip: "192.168.56.10"

  # 共有フォルダの設定と作成。省略可。
  config.vm.synced_folder "./shared", "/vagrant_data", create:"true"

  # ansible_playbook.ymlを仮想マシン側で読み込むための共有フォルダの設定。必須。
  config.vm.synced_folder "./provision", "/home/vagrant/provision"

  # Virtualboxに対する設定。不要なものは省略可。
  config.vm.provider "virtualbox" do |vb|
    #vb.gui = true
    #vb.memory = "1024"
    vb.name = "ubuntu22-localstack"
    vb.customize ["modifyvm", :id, "--nictype1",  "virtio"]
    vb.customize ["modifyvm", :id, "--nictype2",  "virtio"]
  end

  # vbguestプラグインの設定。省略可。
  if Vagrant.has_plugin?("vagrant-vbguest")
    # set auto_update to false, if you do NOT want to check the correct
    config.vbguest.auto_update = true
  end

  # proxyプラグインの設定はこの辺りに記載すればいいはず。(試してない)

  # ansible_localの起動。必須。
  # 初回のvagrant up時に読み込まれる。(2回目以降は明示的にprovisionの指定が必要)
  config.vm.provision "ansible_local" do |ansible|
    ansible.playbook = "provision/ansible-playbook.yml"
  end

  # ansible_local後の仮想マシン再起動。必須。
  # 初回のvagrant up時に読み込まれる。(2回目以降は明示的にprovisionの指定が必要)
  config.vm.provision "shell" do |shell|
    # reboot for docker reload
    shell.privileged = true
    shell.inline = 'echo rebooting'
    shell.reboot = true
  end

  # localstackのコンテナ起動。必須。
  # vagrant up, vagrant reload時は毎回実行される。(run: "always") 
  # コンテナ起動直後はlocalstackが上がりきっていない場合があるためsleepを入れている。
  config.vm.provision "shell", run: "always", privileged: false, inline: <<-SHELL
    localstack start -d
    # wait localstack service ready
    timeout 60 bash -c 'if [ $? == 0 ]; then until ( curl -s localhost:4566 ); do sleep 5; done; fi'
    localstack status services
  SHELL

end
```

### ansible-playbook作成
作業フォルダの直下に「provision」というフォルダを作成し、「ansible-playbook.yml」というファイルを作成。

```yml:ansible-playbook.ymlサンプル
---
- hosts: all
  become: yes
  user: vagrant

  tasks:
    ### install pkg
    - name: apt-get install packages
      become: yes
      apt:
        name: "{{ item }}"
        state: present
        update_cache: yes
      with_items:
        - python3-pip

    ### install docker
    - name: apt-get install packages for docker
      become: yes
      apt:
        name: "{{ item }}"
        state: present
        update_cache: yes
      with_items:
        - ca-certificates
        - curl
        - gnupg
        - lsb-release

    - name: set dockers official gpg key
      become: yes
      apt_key:
        url: "https://download.docker.com/linux/ubuntu/gpg"
        state: present

    - name: set dockers stable repository
      become: yes
      apt_repository:
        repo: deb [arch=amd64] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable
        state: present

    - name: install docker
      become: yes
      apt:
        name: "{{ item }}"
        state: present
        update_cache: yes
      with_items:
        - docker-ce
        - docker-ce-cli
        - containerd.io
        - docker-compose-plugin

    - name: user add docker group
      become: yes
      user:
        name: "{{ ansible_user }}"
        groups: docker
        append: yes
        state: present

    ### install awscli, awscli-local, localstack
    - name: apt-get install pkg for localstack
      become: yes
      apt:
        name: "{{ item }}"
        state: present
        update_cache: yes
      with_items:
        - awscli

    - name: pip install for localstack
      become: no
      pip: 
        name: "{{ item }}"
        state: present
        executable: pip3
      with_items:
         - docker==5.0.0
         - localstack
         - awscli-local

    - name: set env localstack bind host
      become: no
      lineinfile:
        dest: ~/.profile
        line: export EDGE_BIND_HOST=0.0.0.0
        state: present

```

## VagrantでのUbuntuインストール～localstackの起動の自動実行
### 作業フォルダに移動してVagrantを起動
Powershellで作業フォルダの直下に移動してコマンド投入することで、仮想マシンのインストール～localstackの起動までが自動で完了する。
初回はダウンロードするものが多いのでかなり時間がかかる。(ネットワーク環境にもよるが1時間程度？)

```
> cd C:\users\(ユーザ名)\documents\vagrant\ubuntu22
> vagrant up
...
...

    default: ┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
    default: ┃ Service                  ┃ Status      ┃
    default: ┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩
    default: │ acm                      │ ✔ available │
    default: │ apigateway               │ ✔ available │
    default: │ cloudformation           │ ✔ available │
    default: │ cloudwatch               │ ✔ available │
    default: │ config                   │ ✔ available │
    default: │ dynamodb                 │ ✔ available │

～～～～～～～～～～～～ 略 ～～～～～～～～～～～～

    default: │ swf                      │ ✔ available │
    default: │ transcribe               │ ✔ available │
    default: └──────────────────────────┴─────────────┘
```

最後にlocalstackの各サービスの状態が表示されれば正常終了。

### localstackの動作確認(任意)
ホストマシン(WindowsPC)のPowerShellから、仮想マシン(Ubuntu)上のlocalstackに向けてaws cliコマンドを打ってみる。
ホストマシンにaws-cliがインストールされていない場合はインストールとconfigure設定が必要。([aws cli インストール手順はこちらを参照](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/getting-started-install.html))

```
> aws s3 mb s3://testbucket --endpoint-url=http://192.168.56.10:4566
make_bucket: testbucket
```

次回以降も作業フォルダでvagrant upコマンドで起動すれば、仮想マシンと同時にlocalstackが起動するようになっている。
(VirtualboxのGUIから起動する場合はlocalstackは手動で立ち上げる必要がある)

ちなみにvagrant sshで仮想マシンの中に入れる。
もちろんTeraTerm等を使って設定した固定IPアドレスにログインしてもOK。
仮想マシンの終了はvagrant halt。

## 苦労した点
- **仮想マシンのOSの選定**
最初はCentOS7を使おうとしていたが、デフォルトのPythonのバージョンが古く(※)、またEOL後の扱いも気になるためUbuntuに変更。
※localstackの動作要件としてPython3.7以上が必要だが、CentOS7.9のPythonは3.6だった。Ubuntu22だと3.10が入っていた。

- **Pythonの追加インストール**
[OS標準のPythonを使うべきではないという記事](https://www.python.jp/install/ubuntu/index.html)を見つけたので、Pythonの構築から実行するようにplaybookを修正してみたのだが、なぜかlocalstackが正常に立ち上がらなくなってしまったため今回は断念した。
そのうちリベンジしたい。

- **ansibleでのpip3の実行**
ansibleのモジュールがpipしかなかったため、pip3での実行方法がわからず苦労した。
途中まで~/.bashrcにalias追記する方法をとっていたが、最終的に[公式ドキュメント](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/pip_module.html#parameter-executable)でexecutableオプションを見つけたためそちらを採用。

- **ansible_localでの仮想マシンの再起動**
dockerインストール後に再起動して色々設定を読み込み直したかったのだが、ansible_localだとコントローラーがいないためansible_playbookの中で再起動ができないようだった。(当然といえば当然)
結局Vagrantfile側でrebootコマンドを実行させた。

- **WinsdowsPC(ホストマシン)や他のVirtualbox上の仮想マシンから、localstackにアクセスできない。**
・ping⇒通る。
・awscliコマンドのエンドポイントが`--endpoint-url=http://localhost:4566`⇒通る。
・awscliコマンドのエンドポイントが`--endpoint-url=http://192.168.56.10:4566`⇒通らない。
という状況だったため、ポート4566がlocalhost以外でLISTENできていない点が問題だった模様。
[公式ドキュメント](https://docs.localstack.cloud/localstack/configuration/#core)にある環境変数`EDGE_BIND_HOST`を変更したら通るようになった。

## 参考
- [Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/)
- [LocalStack](https://github.com/localstack/localstack)
- [Vagrant+AnsibleでUbuntu16.04にDocker環境を構築](https://qiita.com/osho2751/items/01957467ddb72d486ed1)
- [【Ubuntu 18.04】Ansible で Docker と docker-compose をインストールする](https://qiita.com/yukin01/items/dd1cfc07abefd6a13aa5)
- [VirtualBoxのネットワーク設定を絵で説明する](https://qiita.com/feifo/items/0fde474005589afcff68)
- [Ubuntu環境のPython](https://www.python.jp/install/ubuntu/index.html)