インフラが苦手な人の個人開発(ECS、terraform)


はじめに

本記事は 個人開発 Advent Calendar 2019 25日目の記事です。

最近connpassに掲載されている勉強会を地図を見ながら検索できるようなサイトを作りました。
(もともと同僚が勉強会でこんな感じのアプリを作っておりそれに触発された)

https://connpass.net/

使った技術としては
フロント: typescript、react、redux、circleci、cloudfront、s3、terraform
バックエンド: go、gin、docker、nginx、circleci、ecs、terraform
な感じです。

仕事ではあまりインフラまわりを触ることがなく、CI/CD環境の知見があまりないが、それっぽいものを自分で作ってみようとしました。

フロント

アプリケーション

アプリケーションの土台はcreate-react-appを使いtypescriptで書きました。
stateの管理にはredux、ミドルウェアにredux sagaを使いました。
また、reduxの型定義にtypescript-fsa、reducerでのオブジェクトのマージにimmerを使っています。
SSRをせずに、動的なOGPに対応するためにprerender-spa-pluginを使ったため、customize-crareact-app-rewiredを使ってCRAのデフォルトのwebpack設定を上書きしたりもしました。prerender-spa-pluginのことは、別記事にも書いています
また、apiのfetch中に出てくるアニメーションはlottieにあったものを使いました。

環境構築

cloudfront、s3の環境をterraformで作成しました。
別記事に詳しく書いています。

CI/CDの流れ

  • circleci
    • pushされるたびにビルド
    • masterへのコミットならビルドしたファイルをs3にあげる

バックエンド

アプリケーション

golangを使いました。フレームワークにgin、cuiにcobraを使っています。
apiでもcuiでも共通のロジックを使うようにしています。クリーンアーキテクチャを意識はしましたができてるかはよくわからないです。

docker

マルチステージビルドを使って、ローカルで開発する時に利用するイメージと、ECRにあげる本番用イメージを別にしています。

Dockerfile
# 開発用
FROM golang:alpine as builder

RUN apk update \
  && apk add --no-cache git \
  && go get github.com/oxequa/realize

WORKDIR /app
COPY go.mod .
COPY go.sum .

RUN go mod download
COPY . .

RUN GOOS=linux GOARCH=amd64 go build -o /main

# 本番用
FROM alpine:3.9

COPY --from=builder /main .

ENTRYPOINT ["/main"]
EXPOSE 7777

開発用のdocker-composeです。
realizeを使ってホットリロードができるようにしています。

docker-compose.dev.yml
version: "3.5"

services:
  app:
    build:
      context: .
      target: builder
    volumes:
      - ./:/app
    command: realize start --server
    environment:
      - API_VERSION=development
    ports:
      - 7777:7777
  nginx:
    build: nginx
    ports:
      - 80:80
    depends_on:
      - app

本番用のdocker-composeです。ECRにあげたイメージを使っています。
また、現在のリリースバージョンの確認ができるようにコミットのSHA-1を環境変数(API_VERSION)として渡しています。ALBのヘルスチェック用に用意しているAPIを叩くと、このSHA-1が返ってくるようにしてます。
このdocker-composeはecs-cliに引数として渡すものになるため、ecs-cliが対応しているversionである必要があります。(3.5とかだと無理でした)

docker-compose.prod.yml
version: "3"

services:
  app:
    image: "882275384674.dkr.ecr.ap-northeast-1.amazonaws.com/connpass-map-api-app:${SHA1}"
    environment:
      - API_VERSION="${SHA1}"
    ports:
      - 7777:7777
  nginx:
    image: "882275384674.dkr.ecr.ap-northeast-1.amazonaws.com/connpass-map-api-nginx:${SHA1}"
    ports:
      - 80:80
    depends_on:
      - app

環境構築

vpc、サブネットなどのNW環境とALBやターゲットグループ、ECSのクラスターなどをterraformで作成しました。
別記事に詳しく書いています。

CI/CD

  • circleci

    • featureやmasterブランチにpushがされる
    • そのコミットのSHA-1をタグにしてアプリのイメージをビルド
    • ECRにイメージをpush
  • デプロイするスクリプト(ローカルで実行)

    • どのブランチでリリースをしたいか選択
    • ecs-cliを使ってecsのサービス、タスクをリリース

近道

上記の環境を作るに当たってインフラまわりが苦手な人がひっかかりそうな所としては

  • ecs
  • terraform
  • circleci
  • デプロイするシェルスクリプト

な気がしてます。これらは僕自身も今も苦手ですが、

ecsならリリースにecs-cliを使えば、docker-compose.ymlをそのまま流用する形でecsのタスクが作れる。(dockerは勉強する必要あるがecsの独自のタスク定義の書き方とかは勉強しなくてよくなる)

terraformならいい本があるのでベーシックな環境ならこの本の写経でなんとかなる。

circleciなら便利な処理をパッケージにしてくれているorbsを使えば結構楽できる。

シェルスクリプトは勉強すればいい。(これは近道がない気がしている)

という感じでどれも今なら便利なツールや本が揃っているので個人開発でそれっぽい環境を作るハードルはかなり低くなってると思います。