circleciにlocalstackをterraformで構築する

## 紹介する内容
- circleciにlocalstackをterraformで構築します
  - terraformでlambdaを作ります
    - ユーザーが指定したディレクトリにpython packageをインストールします
    - インストールしたpackageをzipに固めます
- circleciをlocalで動かしてますが、本物のcircleciでも動きます
- circleciの`docker executor`を使います


## 結論
- localstackのdocker-entrypoint-initaws.dが使用できないから、
  - docker-entrypoint-initaws.dの作業内容をPrimaryコンテナに書きます、
  - localstack固有のもろもろ初期化が終わるタイミングを把握できません。愚直に初期化作業に掛かる時間を計測して、その時間分待つようにすることが最善です。
     - circleciをlocal環境でデバックする方法はこちらで確認できます
         - https://qiita.com/cheekykorkind/items/4efc89fd5271bec7ff50
- localstackコンテナの環境変数`HOSTNAME_EXTERNAL`を`localhost`にして、Primaryコンテナからのlocalstackアクセスできるようにします。

## circleciのdocker executorはvolumnsが使えない
- circleciのdocker executorはdocker composeのvolumesを使用できません。
  - これがlocalstack固有のもろもろ初期化が終わるタイミングを把握不可能する原因です。volumesが使えないから、docker-entrypoint-initaws.dに介入が不可能です
     - localstackのportが開くことを基準にするのは意味がありません。なぜなら、portを開けたままもろもろ初期化作業するからです
  - https://circleci.com/docs/2.0/executor-types/#docker-benefits-and-limitations
  - どうしてもdocker composeのvolumesを使う必要があったら、`machine executor`にしたら実装可能だと思います

## circleciのdocker executorはネットワークのhost名がlocalhostになっています
- circleciのdocker executorは一つのネットワーク(`common network`)がありまして、host名は`localhost`です。Primaryコンテナ含むすべてのコンテナが属してます
  - docker executorはコンテナ同士volumnsでやりとりできないから、コンテナ各々違うport番号を貼ってやりとりさせることが無難かと思います。
  - `common network`の原文：https://circleci.com/docs/2.0/executor-types/#using-multiple-docker-images

## (参考)Primaryコンテナ以外のコンテナはできることが限られている印象です
  - docker executorが使えるオプション：https://circleci.com/docs/2.0/configuration-reference/#docker



## 紹介始めます
### localstack紹介
実のAWSを使わずにいろいろ試せるものです。  
バージョン0.11.0から大きな変化がありました。本記事は0.11.0バージョンのlocalstackを使います  
https://github.com/localstack/localstack 

### terraform紹介
コードでインフラを管理できます。
https://www.terraform.io/

### circleci紹介
コードの継続的な統合とテストを助けてくれるものです。
https://circleci.com/


### 構築実験環境
#### Linux
- CentOS Linux release 7.7.1908 (Core)

#### Docker Compose
- VERSION : 1.24.1

#### Docker
- VERSION : 19.03.5

#### local circleciをインストールします
https://circleci.com/docs/2.0/local-cli/#installation


#### ディレクトリ構成
全体ソースコードは https://github.com/cheekykorkind/circleci-localstack で確認できます

- 全体図
![treeD.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247584/27f101d0-527a-34b9-3417-6e7cef348255.png)


- terraformファイルを置く部分
![terraformD.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247584/8a149280-c634-df11-1cb9-363b5a500668.png)


- circleci config部分
![circleciD.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247584/b39b0813-e64b-3453-badf-bea4848a1c60.png)


当記事のプロジェクトは`docker composeでlocalstackを作ってterraformでlambdaを作る` プロジェクトからcircleci設定を追加して調整したバージョンです。docker composeでlocalstackを作る詳しい内容はこちらの記事参考お願いします。
https://qiita.com/cheekykorkind/items/02c896465ddea5c5186f


### circleciのconfig.ymlで構築します
jobの名前はtestです。my-testなどでも設定可能です

```
version: 2
jobs:
  test:
    docker:
      - image: circleci/python:3.7-buster-node
        environment:
          AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE
          AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
          AWS_DEFAULT_REGION: us-east-1

          LAMBDA_ZIP_PATH: /home/circleci/lambda.zip
          HELLO_WORLD_PATH: /home/circleci/project/sam-lambda/hello_world
          TERRAFORM_PATH: /home/circleci/project/terraform

          COMMON_NETWORK: localhost

      - image: localstack/localstack:0.11.0
        environment:
          AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE
          AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
          AWS_DEFAULT_REGION: us-east-1
          SERVICES: lambda,logs
          DEBUG: 1

          HOSTNAME_EXTERNAL: localhost

    steps:
      - checkout

      - run:
          name: Wait for localstack
          command: dockerize -wait tcp://localhost:4566 -timeout 2m

      - run:
          name: install pip packages at packages directory
          command: |
            pip -V
            cd $HELLO_WORLD_PATH
            pip install -t ./packages -r requirements.txt
            zip -r9 $LAMBDA_ZIP_PATH .

      - run:
          name: install terraform 0.12.6
          command: |
            cd ~
            sudo apt-get install unzip
            wget https://releases.hashicorp.com/terraform/0.12.6/terraform_0.12.6_linux_amd64.zip
            sudo unzip -o terraform_0.12.6_linux_amd64.zip
            sudo mv terraform /usr/local/bin/
            terraform --version
            
      - run:
          name: terraform apply
          command: |
            cd $TERRAFORM_PATH
            terraform init
            terraform apply -auto-approve -var="lambda_zip_path=$LAMBDA_ZIP_PATH" -var="endpoint_domain=$COMMON_NETWORK"

      - run:
          name: install aws cli
          command: |
            sudo apt install awscli

      - run:
          name: test lambda
          command: |
            aws lambda invoke --region us-east-1 \
              --endpoint-url http://localhost:4566 \
              --function-name hello_lambda \
              --payload '{ "name": "Bob" }' \
              response.json

```

#### コンテナの環境変数設定
- circleci/python:3.7-buster-nodeコンテナ(Primaryコンテナ)
  - `COMMON_NETWORK` はterraformにcircleciのdocker executorのデフォルトhost名`localhost`を伝えるために作りました

  - `AWS_ACCESS_KEY_ID`、`AWS_SECRET_ACCESS_KEY`、`AWS_DEFAULT_REGION` はaws cliを便利に使うために設定しました
  - `LAMBDA_ZIP_PATH` はlambda生成に使うzipファイルの位置を書きました。コンテナ2つが連携して作業するから管理しやすくするために設定しました
  - `HELLO_WORLD_PATH` は便利にSAMプロジェクトのpythonコードのzip圧縮をするために設定しました
    - `TERRAFORM_PATH` はterraformファイルのパスです。terraformコマンド実行するためにterraformファイルがいるところに移動する必要があります。便利な移動のために事前に指定しておきました

- localstack/localstack:0.11.0コンテナ
  - `SERVICES`、`DEBUG`、`HOSTNAME_EXTERNAL` はlocalstack固有の環境変数です。localstackを調べると理解できると思います
    - `HOSTNAME_EXTERNAL` はcircleciのprimaryコンテナからlocalstackにアクセスできるようにcircleciのデフォルトhost名である`localhost`に合わせます。これでterraformがlocalstackに付けて実行可能になります

  - `AWS_ACCESS_KEY_ID`、`AWS_SECRET_ACCESS_KEY`、`AWS_DEFAULT_REGION` はaws cliを便利に使うために設定しました

#### steps説明
- `name: Wait for localstack`
  - localstackコンテナが起動することを待ちます。一番前に置いた理由はterraform実行する前にlocalstackのもろもろ初期化作業する時間を稼ぐためです。当プロジェクトはlambdaとcloudwatch logs二つのサービスだけ使っていてこれで大丈夫ですが、localstackで使おうとするサービスが多い場合は大目に`sleep 300`などを入れることも考える必要があります
  

#### circleci local execute --job testによる作業の流れ
1. circleci/python:3.7-buster-nodeコンテナ(Primaryコンテナ)が作られます
2. localstackコンテナが作れます
3. Primaryコンテナのstepが実行されます
- `name: Wait for localstack` stepで少なくともportが開けるまで待ちます
- pythonのpackageをディレクトリ`/home/circleci/project/sam-lambda/hello_world/packages` に指定し、インストールします
- `$LAMBDA_ZIP_PATH`に置きます
- terraformをインストールしあます
- terraformファイルがいる `$TERRAFORM_PATH` に移動します
- terraform initします
- terraform applyします。同時にterraformでlambdaのzipパス(`$LAMBDA_ZIP_PATH`)とcircleciのデフォルトhost名を入れたを`$COMMON_NETWORK`を渡します
- terraformによってlambdaが作られます

#### circleci local execute --job testを叩いた結果
circleciの最後のstepに書いたlambdaをinvokeする部分がちゃんと動いてresponseのstatusが200を返したことが確認できます
![circleci-local.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247584/01a52c55-d997-7555-1a3a-e90d74b2919c.gif)