DockerだけでTerraformをLocalStackにデプロイしてみた

# 1. はじめに

下記2つの条件で、[Terraform](https://www.terraform.io/)を[LocalStack](https://github.com/localstack/localstack)へデプロイする記事が見当たらなかったため、今回の記事を作成しました。

- Docker (+ Docker Compose) だけを使用していること。
- 「[Integrations / Terraform / Endpoint Configuration](https://docs.localstack.cloud/integrations/terraform/#endpoint-configuration)」の`apigateway = "http://localhost:4566"`ようにサービスごとにエンドポイントを指定するのを避けるため、[tflocal](https://github.com/localstack/terraform-local)を使用していること。

## 1.1. 動作環境

Bashなどのコマンドが利用でき、DockerとDocker Composeがインストール済みの環境で実行できます。

## 1.2. 各種のバージョン

2022年5月3日時点で動作確認済のバージョンは以下の通りです。

- docker
    - Docker version 20.10.0, build 7287ab3
- docker-compose
    - docker-compose version 1.26.0, build unknown
- localstack
    - 0.14.2
- terraform
    - Terraform v1.1.9
- tflocal
    - 0.2
- aws-cli
    - aws-cli/2.6.1 Python/3.9.11 Linux/4.4.0-210-generic docker/x86_64.amzn.2 prompt/off


# 2. やってみた

## 2.1. 最終的なファイル構成

```bash
$ tree
.
├── localstack
│   └── docker-compose.yml
├── terraform-example
│   ├── main.tf
│   └── terraform.tfstate
└── tflocal
    └── Dockerfile
```

## 2.2. LocalStackの起動

### 2.2.1. LocalStackのdocker-compose.ymlを作成する

```bash
$ mkdir localstack

$ vi localstack/docker-compose.yml
```

```yaml:localstack/docker-compose.yml
version: "3.8"

services:
  localstack:
    container_name: "${LOCALSTACK_DOCKER_NAME-localstack_main}"
    image: localstack/localstack:0.14.2
    ports:
      - "127.0.0.1:53:53"                # only required for Pro (DNS)
      - "127.0.0.1:53:53/udp"            # only required for Pro (DNS)
      - "127.0.0.1:443:443"              # only required for Pro (LocalStack HTTPS Edge Proxy)
      - "127.0.0.1:4510-4559:4510-4559"  # external service port range
      - "127.0.0.1:4566:4566"            # LocalStack Edge Proxy
    environment:
      - DEBUG=${DEBUG-}
      - DATA_DIR=${DATA_DIR-}
      - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR-}
      - LOCALSTACK_API_KEY=${LOCALSTACK_API_KEY-}  # only required for Pro
      - HOST_TMP_FOLDER=${TMPDIR:-/tmp/}localstack
      - DOCKER_HOST=unix:///var/run/docker.sock
    volumes:
      - "${TMPDIR:-/tmp}/localstack:/tmp/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"
```

[localstackのdocker-compose.yml](https://github.com/localstack/localstack/blob/d29ea3eb7d56e37a838f980b4478c4deae40f3c6/docker-compose.yml)を参考にしておりますが、
下記2点を変更しています。

- イメージタグの指定 (`image: localstack/localstack` -> `image: localstack/localstack:0.14.2`) 。
- `network_mode: bridge`を削除。

### 2.2.2. LocalStackを起動する

```bash
$ docker-compose -f localstack/docker-compose.yml up -d
Creating network "localstack_default" with the default driver
・・・(中略)
Creating localstack_main ... done
```

## 2.3. tflocalのセットアップ

### 2.3.1. tflocalのDockerfileを作成する

```bash
$ mkdir tflocal

$ vi tflocal/Dockerfile
```

```Dockerfile:tflocal/Dockerfile
FROM python:3.8.13-slim
RUN apt-get update -y && \
    apt-get install -y gnupg software-properties-common curl && \
    curl -fsSL https://apt.releases.hashicorp.com/gpg | apt-key add - && \
    apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main" && \
    apt-get update -y && \
    apt-get install -y terraform=1.1.9 && \
    pip install terraform-local==0.2
WORKDIR /app
ENV LOCALSTACK_HOSTNAME=localstack \
    S3_HOSTNAME=localstack \
    TF_VAR_s3_use_path_style=true \
    AWS_DEFAULT_REGION=ap-northeast-1
ENTRYPOINT ["tflocal"]
```

`ENV`で指定している環境変数は[こちら](https://github.com/localstack/terraform-local#configurations)を参考にしています。

### 2.3.2. tflocalのDockerイメージを作成する

```bash
$ docker build -t tflocal:0.2 ./tflocal
Sending build context to Docker daemon   2.56kB
・・・(中略)
Successfully tagged tflocal:0.2
```

### 2.3.3. tflocalのaliasを設定する

```bash
$ alias tflocal='docker run --rm -it -v "$(pwd):/app" --net=localstack_default tflocal:0.2'
```

毎回Dockerコマンドを入力するのを省略するために、aliasを設定しています。
`-v "$(pwd):/app"`と指定しているため、`tflocal`コマンドはTerraform定義のファイルがあるディレクトリで実行することを想定しています。
また、`--net=localstack_default`と指定しているため、LocalStackコンテナが`localstack`で名前解決できるようにしています。

## 2.4. Terraformサンプルの作成とLocalStackへのデプロイ

### 2.4.1. Terraform定義ファイルを作成する

```bash
$ mkdir terraform-sample

$ cd terraform-sample

$ vi main.tf
```

```hcl:terraform-sample/main.tf
variable "s3_use_path_style" {
  default = "false"
}

provider "aws" {
  s3_use_path_style = var.s3_use_path_style
}

resource "aws_s3_bucket" "sample" {
  bucket = "sample-bucket"
}

resource "aws_sns_topic" "sample" {
  name = "sample-topic"
}
```

作成するリソースはS3バケットとSNSトピックの2つです。
`variable`の`s3_use_path_style`はデフォルトでは`false`になりますが、`tflocal`コマンドを実行すると、`tflocal/Dockerfile`で設定した`TF_VAR_s3_use_path_style=true`で`true`に上書きされるようにしています。

### 2.4.2. terraform initする

```bash
$ tflocal init

Initializing the backend...
・・・(中略)
commands will detect it and remind you to do so if necessary.
```

### 2.4.3. LocalStackにデプロイする

```bash
$ tflocal apply -auto-approve

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated
with the following symbols:
・・・(中略)
Plan: 2 to add, 0 to change, 0 to destroy.
aws_sns_topic.sample: Creating...
aws_s3_bucket.sample: Creating...
aws_sns_topic.sample: Creation complete after 1s [id=arn:aws:sns:ap-northeast-1:000000000000:sample-topic]
aws_s3_bucket.sample: Creation complete after 1s [id=sample-bucket]
```

## 2.5. LocalStackへデプロイされたリソースの確認

### 2.5.1. LocalStack用のAWS CLIをaliasで設定する

```bash
$ alias awslocal='docker run --rm -it -v $(pwd):/aws --net=localstack_default --env AWS_ACCESS_KEY_ID=dummy --env AWS_SECRET_ACCESS_KEY=dummy --env AWS_DEFAULT_REGION=ap-northeast-1 amazon/aws-cli:2.6.1 --endpoint-url http://localstack:4566'

$ awslocal --version
Unable to find image 'amazon/aws-cli:2.6.1' locally
・・・(中略)
aws-cli/2.6.1 Python/3.9.11 Linux/4.4.0-210-generic docker/x86_64.amzn.2 prompt/off
```

aliasの`awslocal`にAWS CLIのDocker版を諸々のパラメータとともに設定しています。
このaliasも`--net=localstack_default`と指定しているため、LocalStackコンテナが`localstack`で名前解決できるようにしています。

### 2.5.2. S3バケット一覧を取得する

```bash
$ awslocal s3 ls
2022-05-02 13:32:25 sample-bucket
```

### 2.5.3. SNSトピック一覧を取得する

```bash
$ awslocal sns list-topics
{
    "Topics": [
        {
            "TopicArn": "arn:aws:sns:ap-northeast-1:000000000000:sample-topic"
        }
    ]
}
```

無事に作成できていそうですね！