はじめに
こんにちは!山内です。最近マイクロサービスについて勉強していまして、ついにECSを触る機会がありました。(遅いって言わないで!)
API Gateway + Lambda構成と違うところが結構ありましたので、備忘録も兼ねて記事にします!※スクリーンショットは2021/03時点のものです。
環境準備
AWSリソースの作成
まずは下記のAWSリソースを作成します。
- CodeCommit
- ECR
- ECS(Fargate)
- CodePipeline
- CodeBuild
CodeCommit
まず、CodeCommitのリポジトリを作成します。特に注意点はありませんが、ローカルで開発するので、CloneとFirst Commitを済ませておきましょう。その際に、IAMユーザの「認証情報」タブから「AWS CodeCommitのHTTPS Git認証情報」を発行する必要があります。
ECR
- ECRのマネジメントコンソールを開き、「リポジトリを作成」をクリック
- 下記設定を行い、「リポジトリを作成」をクリック
- 可視設定 : プライベート
- リポジトリ名: 任意
- プライベートリポジトリ一覧から、作成したリポジトリをクリックし、「プッシュコマンドの表示」をクリック
- AWS CLIが叩ける環境にて、Node.jsプロジェクトおよびDockerfile(下記を参考)を用意し、コピーしたコマンドを実行
123456789101112131415161718192021222324# FROM node:12FROM amazon/aws-lambda-nodejs:12# アプリケーションディレクトリを作成するWORKDIR /usr/src/app# アプリケーションの依存関係をインストールする# ワイルドカードを使用して、package.json と package-lock.json の両方が確実にコピーされるようにします。# 可能であれば (npm@5+)COPY package*.json ./RUN npm install# 本番用にコードを作成している場合# RUN npm install --only=production# アプリケーションのソースをバンドルするCOPY . .# ポート待ち受けEXPOSE 8080# アプリケーションの起動コマンド実行ENTRYPOINT [ "node" ]CMD [ "server.js" ]12345678# Node.jsプロジェクトの作成npm init # 各質問は任意でOK# DockerイメージをECRへプッシュaws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin <<AWSアカウントID>>.dkr.ecr.ap-northeast-1.amazonaws.comdocker build -t <<ECRのリポジトリ名>> .docker tag <<ECRのリポジトリ名>>:latest <<AWSアカウントID>>.dkr.ecr.ap-northeast-1.amazonaws.com/<<ECRのリポジトリ名>>:latestdocker push <<AWSアカウントID>>.dkr.ecr.ap-northeast-1.amazonaws.com/<<ECRのリポジトリ名>>:latest
- AWSコンソールで、対象のリポジトリにイメージが登録されていることを確認
※後で使うので、「イメージのURI」>「URIのコピー」からイメージURIをコピーし、メモしておくこと
ECS(Fargate)
- ECSのマネジメントコンソールを開く ※ECSの機能の一部がFargate
- 左サイドメニューから「クラスター」をクリック
- 「クラスターの作成」をクリック
- 「ネットワーキングのみ」を選択し、「次のステップ」をクリック
- 下記設定を行い、「作成」をクリック
- クラスター名: 任意
- その他: デフォルト
- 「クラスターの表示」をクリック
- 左サイドメニューの「タスク定義」をクリック
- 「新しいタスク定義の作成」をクリック
- Fargateを選択し、「次のステップ」をクリック
- 下記設定を行い、「作成」をクリック
- タスク定義名: 任意
- タスクメモリ (GB): 0.5GB
- タスクCPU (vCPU): 0.25vCPU
- 「コンテナの追加」から下記を設定し、「追加」をクリック
- コンテナ名: 任意
- イメージ: ECR設定時にコピーしたイメージのURI
- ポートマッピング: 8080/tcp ※API用
- その他: デフォルト
- 「タスク定義の表示」をクリック
- 「アクション」>「サービスの作成」をクリック
- 下記サービスの設定を行い、「次のステップ」をクリック
- 起動タイプ: FARGATE
- クラスター: 先ほど作成したクラスター
- サービス名: 任意
- タスクの数: 1
- その他: デフォルト
- 下記ネットワーク構成の設定を行い、「次のステップ」をクリック
- クラスターVPC: 任意のVPC(必要に応じて新規作成してもOK)
- サブネット: 任意のサブネット(今回はパブリックサブネット2つを指定)
- セキュリティグループ: 任意(8080ポート/TCPへのアクセスを許可してあるもの)
- その他: デフォルト
- Auto Scaling設定はデフォルトのまま、「次のステップ」をクリック
- 設定を確認し、「サービスの作成」をクリック
- 「サービスの表示」をクリックし、作成されていることを確認
CodePipeline
CodePipelineを作成しつつ、一緒にECRとCodeBuildを作成します。なお、API Gateway + Lambda構成のときとほとんど変わらなかったので、スクショは割愛します。
- CodePipelineコンソール上で「パイプラインを作成する」をクリック
- パイプライン名とサービスロールを選択して、「次に」をクリック
- 下記ソースステージの設定を行い、「次に」をクリック
- ソースプロバイダー: AWS CodeCommit
- リポジトリ名: 先ほど作ったCodeCommitのリポジトリ名
- ブランチ名: 任意(今回は簡単のため「master」のまま)
- その他: デフォルト
- 下記ビルドステージの設定を行い、「次に」をクリック
- プロバイダー: AWS CodeBuild
- リージョン: アジアパシフィック(東京)
- プロジェクト名: 「プロジェクトを作成する」から作成
- 下記設定を行い、「CodePipelineに進む」をクリック
- プロジェクト名: 任意
- 説明: 任意
- 環境イメージ: マネージド型イメージ
- オペレーティングシステム: Amazon Linux 2
- ランタイム: Standard
- イメージ: aws/codebuild/amazonlinux2-x86_64-standard:3.0
- 特権付与: チェックを入れる
- その他: デフォルト
- その他: デフォルト
- 下記デプロイステージの設定を行い、「次に」をクリック
- プロバイダー: AWS ECS
- リージョン: アジアパシフィック(東京)
- クラスター名: 先ほど作成したECSのクラスター名
- サービス名: 先ほど作成したECSのサービス名
- その他: デフォルト
- 「パイプラインを作成する」をクリック
自動デプロイの設定
下記buildspec.ymlファイルをプロジェクトのルートに配置します。後述しますが、5、6行目でDockerHubのアカウント情報を使っています。実際の開発ではハードコーディングだと問題なので、SecretManagerなどを活用しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
version: 0.2 env: secrets-manager: DOCKERHUB_USER: <<DockerHubアカウントのユーザ名>> DOCKERHUB_PASS: <<DockerHubアカウントのパスワード>>; phases: install: runtime-versions: docker: 19 commands: - echo Installing modules... - npm install pre_build: commands: - echo Logging in to Amazon ECR... - aws --version - aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin <<AWSアカウントID>>.dkr.ecr.ap-northeast-1.amazonaws.com - echo Logging in to Docker Hub... - echo $DOCKERHUB_PASS | docker login -u $DOCKERHUB_USER --password-stdin - REPOSITORY_URI=<<AWSアカウントID>>.dkr.ecr.ap-northeast-1.amazonaws.com/<<ECRのリポジトリ名>> - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7) - IMAGE_TAG=${COMMIT_HASH:=latest} build: commands: - echo Build started on `date` - echo Building the Docker image... - docker build -t $REPOSITORY_URI:latest . - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:latest post_build: commands: - echo Build completed on `date` - echo Pushing the Docker images... - docker push $REPOSITORY_URI:latest - echo Writing image definitions file... - echo '[{"name":"<<ECSのタスク定義で設定したコンテナ名>>","imageUri":"<<AWSアカウントID>>.dkr.ecr.ap-northeast-1.amazonaws.com/<<ECRのリポジトリ名>>:latest"}]' > imagedefinitions.json - cat imagedefinitions.json artifacts: files: imagedefinitions.json |
APIの実装
いよいよ、「server.js」ファイルにAPIを実装します。今回は「Hello MISO」と返すAPIにします。 「express」というモジュールを使っているので、npm install expressして、package.jsonの依存関係に追加しておきましょう。Gitにモジュールを載せたくないので、「.gitignore」ファイルの設定も忘れずに!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
"use strict"; const express = require("express"); // Constants const PORT = 8080; const HOST = "0.0.0.0"; // App const app = express(); app.use((req, res, next) => { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Credentials", true); res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); next(); }); app.get("/", (req, res) => { res.send("Hello MISO"); }); app.listen(PORT, HOST); |
デプロイ
APIの実装が終わったら、GitコマンドでGitリポジトリへ変更をpushします。 GitへのpushをトリガーにCodePipelineが動いて、ECSへのデプロイまでの流れが全て成功するまで待ちます。
動作確認①
デプロイまで成功したら、ECSの「サービス」から作成したサービスを選びます。「設定とタスク」タブを選択すると、タスクが1件表示されているので、そのタスク名をクリックします。その後、「ネットワーキング」タブを選択し、「パブリックIP」のIPアドレスをコピーします。ブラウザにて、「コピーしたIPアドレス:8080」にアクセスし、「Hello MISO」と表示されることを確認します。
APIの変更とデプロイ
APIを少し変更して、再度デプロイしてみます。今回は「Hello MISO v2」と返すようにserver.jsを変更し、Gitへpushします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
"use strict"; const express = require("express"); // Constants const PORT = 8080; const HOST = "0.0.0.0"; // App const app = express(); app.use((req, res, next) => { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Credentials", true); res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); next(); }); app.get("/", (req, res) => { res.send("Hello MISO v2"); }); app.listen(PORT, HOST); |
動作確認②
今回はDNSの設定など何もしていないので、タスクごとに新しいパブリックIPアドレスが割り当てられています。そのため、先ほどと同じ手順で「パブリックIP」のIPアドレスをコピーします。ブラウザにて、「コピーしたIPアドレス:8080」にアクセスし、「Hello MISO v2」と表示されることを確認します。
苦労したところ
1つ苦労したのが、自動デプロイ時にDocker Hubのダウンロード数制限に引っかかってエラーで落ちてしまうことがありました。Docker Hubからコンテナイメージをダウンロードするリクエスト数に制限があり、匿名ユーザーだとIPアドレスごとに6時間に100リクエストしかダウンロードできません。 CodeBuildの東京リージョンのIPアドレスは2020/10時点で8つしかないので、東京リージョンを利用している全ユーザで6時間に100リクエストしかダウンロードできないことになります。ただ、Docker Hubのアカウントを作成すれば、アカウントごとに6時間に200リクエストまで可能になるので、小規模なアプリケーションを開発する分には問題ないでしょう。それ以上のリクエストを行いたい場合は、ECRに専用のイメージを登録して、直接ダウンロードする方が良いと思います。
おわりに
ECS初めて触ったのですが、結構楽しいですね!マイクロサービスってフワフワした概念で難しいですが、実際に構築してみると感覚が身に付いてくると思います。皆さまも是非、チャレンジしてみてはいかがでしょうか!
執筆者プロフィール

- tdi デジタルイノベーション技術部
- 社内の開発プロジェクトの技術支援や、Javaにおける社内標準フレームワークの開発を担当しています。Spring BootとTDDに手を出しつつ、LINE Botとかもいじったりしています。最近はマイクロサービスを勉強しつつ、クラウドアプリケーションを開発できるエンジニアの育成にも力を入れてます!