Amazon Web Services ブログ

Amplify CLIを使い、労力ゼロでGraphQL/REST APIやウェブホスティング用にコンテナをデプロイする

この記事は、Zero-effort Container deployment for GraphQL and REST APIs and Web Hosting with Amplify CLIを翻訳したものです。

AWS Amplifyを使うことで、最速かつ簡単に、AWSでクラウド対応のモバイルアプリケーションやウェブアプリケーションを構築することができます。Amplifyは、フロントエンドのウェブ開発者やモバイル開発者が AWSの豊富なサービス群を活用して、革新的で多機能なアプリケーションを構築できるよう、ツール類や各種サービスを一通り備えています。

本日リリースしたAmplify CLIをお使いになることで、フロントエンドのウェブ開発やモバイル開発に携わるお客様は、コンテナを使ってAPI(GraphQL/REST)をデプロイしたり、ウェブアプリケーションをホスティングできるようになります。お客様ご自身のDockerfileまたは Docker Composeを持ち込むことで、Amplify CLIは、AWS Fargateを使用してコンテナを自動的にビルド、パッケージ、デプロイします。

メリット:

  • 移植性の高いバックエンドの構築 – Amplify CLIは、シンプルなコンテナテンプレートを用意しているためすぐに始められます。または、お客様のチームがAPIやホスティング用のコンテナを既に使用している場合は、そのコンテナを利用することができます。
  • コンテナのデプロイパイプラインがすぐに使えるインフラ構成 – Amplify CLIは、VPC、サブネット、NACL、IAMポリシー、およびその他のセキュリティなどのインフラを管理するため、AWSに関する事前知識やインフラの実務経験は全く必要ありません。コンテナ間のネットワーキングは自動的に処理され、ホスティングされたサイトのSSL生成も自動的に処理されます。
  • ビルド&デプロイパイプラインを労力をかけることなく作成 – Amplify CLIはCodePipelineを作成し、イメージをビルド、デプロイします。パイプラインには、ビルドアーティファクトやイメージに対するライフサイクルポリシーなど、コスト最適化のベストプラクティスが備わっています。ビルドしてAWSにデプロイするためにDockerをお使いのシステムにインストールする必要すらありません。

構築するもの :

  • 初めに、乱数を返すExpressJSサーバーを構築
  • 次に、Python/Flask乱数生成サーバーでFizzBuzzアルゴリズムを実行する、ExpressJSを構築。

前提条件:

  • 最新のAmplify CLIをインストール
    • ターミナルを開き、 npm install -g @aws-amplify/cli を実行し、最新のAmplify CLIに更新。
  • Amplify CLIの設定
    • Amplify CLIをまだ設定していない場合は、ドキュメントページのこちらのガイド に従ってください。

新しいAmplifyプロジェクトをセットアップする

以下のコマンドを実行して、“amplify-containerized” という名前の新しいAmplifyプロジェクトを作成してください。または、既存のAmplifyプロジェクトがある場合は、飛ばして次のセクションに進んでください。

mkdir amplify-containerized
cd amplify-containerized

以下を実行してAmplifyプロジェクトを初期化します。

amplify init

本ブログ記事では、amplify init ワークフローのすべてのデフォルト値をそのまま使用します。

コンテナベースのデプロイを有効にする

コンテナベースのデプロイは、明示的に有効化する必要があります。amplify configure project を実行して、プロジェクト設定を確認します。

amplify configure project

コンテナベースのデプロイを有効にするかどうか尋ねられたら、デフォルトの選択肢「yes」を選択します。

...
? Do you want to enable container-based deployments? Yes

新しいコンテナベースの ExpressJS API を追加する

Amplify CLIは、既存のAPIワークフローと開発体験を提供します。コンテナベースのデプロイが有効になると、 amplify add api のワークフロー中に “REST” → “API Gateway + AWS Fargate (Container-based)” を選択できるようになります。

Amplify CLIは、コンテナベースのデプロイに対し、GraphQLおよびREST APIオプションの両方をサポートしています。既存のAppSyncおよびAPI Gateway + Lambdaオプションと共に、コンテナベースのデプロイを使用できます。今回のデモでは、REST APIを作成します。

最初のコンテナベースのREST APIを作成するには、次のコマンドを実行します。

amplify add api

以下のオプションを選択します。

? Please select from one of the below mentioned services:
> REST
? Which service would you like to use
> API Gateway + AWS Fargate (Container-based)
? Provide a friendly name for your resource to be used as a label for this category in the project:
> containerb5734e35
? What image would you like to use
> ExpressJS - REST template
? When do you want to build & deploy the Fargate task
> On every "amplify push" (Fully managed container source)? Do you want to restrict API access
> No

CLI ワークフローが正常に完了すると、以下の新しいファイルがプロジェクトフォルダ構造に追加されます。

amplify/backend/api/
├── amplify.state
├── containerb5734e35-cloudformation-template.json
├── parameters.json
└── src
    ├── Dockerfile
    ├── DynamoDBActions.js
    ├── buildspec.yml
    ├── index.js
    ├── package-lock.json
    └── package.json</p>

src/index.js 内には、DynamoDBを操作するための、基本的なExpressJSソースコードが記載されています。乱数を返すように、コードを編集してみましょう。

index.js ファイルを以下のコードに置き換えます。

const express = require("express");
const bodyParser = require('body-parser');
const port = process.env.PORT || 3001;

const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

// すべてのメソッドに対しCORSを有効にする
app.use(function (req, res, next) {
    res.header("Access-Control-Allow-Origin", "*")
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
    next()
});

app.get("/", async (req, res, next) => {

    try {
        res.contentType("application/json").send({ 
            "randomNumber": Math.floor(Math.random() * 101) 
        })
    } catch (err) {
        next(err);
    }
});

app.listen(port, () => {
    console.log('Example app listening at http://localhost:' + port);
});

次に、API をデプロイしましょう。

amplify push

実際にどのような処理が実行されているか解説します。

Amplifyは、アプリケーションがモニタリングされ、タスクが正常でアクティブな状態を維持するよう、ECS サービスとして API を作成します。そしてインスタンスに障害が発生した場合は自動的に復旧します。ソースコードを変更すると、ビルドおよびデプロイパイプラインはソースコードとDockerfile/docker-compose.ymlを入力として受け取ります。そのソースコードを使用してAWS CodeBuildに1つ以上のコンテナがビルドされ、タグとしてビルドハッシュを使用して ECR にプッシュされるため、アプリケーションコードで予期しない問題が発生した場合、デプロイをロールバックできます。ビルドが完了すると、パイプラインはローリングデプロイを実行し、AWS Fargateタスクを自動的に起動します。新しいバージョンのイメージが全て正常で実行中の状態になってから、古いタスクは停止されます。最後に、(フルマネージドのシナリオにおける)S3のビルドアーティファクト、およびECRイメージは、コスト最適化のために 7 日間の保持期間がライフサイクルポリシーで設定されます。

デプロイしたコンテナ化されたExpressJS APIをテストする

コンテナ化されたAPIのデモを実施する一番良い方法は、cURLコマンド で呼び出すことです。APIエンドポイントは、“amplify push” コマンドの最後または “amplify status” の実行時に出力されます。

curl https://<YOUR_API_ID>.us-east-1.amazonaws.com/

コンテナのデプロイは、ビルドとデプロイに少し時間がかかることがありますが、数分後に “amplify push” コマンドの最初に出力されたCodePipeline URLを確認するか、“amplify console api” を実行してAPIを選択し、“CodePipeline”を選択することで、利用可能な状態にあることが確認できます。

注意 : これは、ワークフローを見せることだけを目的とするシンプルなユースケースです。また、用意してあるExpressJS テンプレートを使うことで、DynamoDB テーブル用のCRUD インターフェイスをすぐに作成することができます。そのようなシナリオに興味がありましたら、AWSドキュメントをご確認ください。

複数コンテナのデプロイ

Amplify CLIは、複数コンテナのデプロイを有効にするかどうかに関し、Docker Composeの設定に従います。Amplifyは、アプリケーションのDockerfileまたはDocker Composeに基づいてFargateおよびECSの設定を自動的に推論します。Amplifyでは、Docker Composeで設定されたポートに基づいてコンテナ間ネットワーキングを行うこともできます。

それではデモを実施してみましょう。amplify add api を実行し、“REST” → “API Gateway + AWS Fargate (Container-based)” → “Docker Compose - ExpressJS + Flask template” と選択することで、新しい複数コンテナのAPI を追加します。これにより、 amplify/backend/api/<your-api-name>/ フォルダに次のフォルダ構造が作成されます。

amplify/backend/api/<your-api-name>
├── amplify.state
├── -cloudformation-template.json
├── parameters.json
└── src
    ├── buildspec.yml
    ├── docker-compose.yml
    ├── express
    │   ├── Dockerfile
    │   ├── DynamoDBActions.js
    │   ├── index.js
    │   └── package.json
    └── python
        ├── Dockerfile
        ├── requirements.txt
        └── src
            └── server.py

トップレベルにある docker-compose.yml はExpressJSサーバーとPythonサーバーを参照しています。Docker Composeは、一度に複数のコンテナをデプロイするメカニズムを提供します。Docker Composeの詳細については、公式のDocker Composeガイドを参照してください。

今回は、Python サーバーが乱数を返し、ExpressJSはPythonサーバーの乱数に基づいてFizzBuzzアルゴリズムを実行するようにします。server.py ファイルを以下の内容に置き換えます。

from flask import Flask
from random import randrange

server = Flask(__name__)

@server.route('/random')
def hello():
    return str(randrange(100))

if __name__ == "__main__":
   server.run(host='0.0.0.0')

0 から 100 までの乱数を返す /random ルートを持つ Flask サーバーを作成しました。

それでは、ExpressJSサーバーを編集して、Pythonサーバーとインターフェイスで接続しFizzBuzzアルゴリズムを実行するようにしましょう。まず、 index.js ファイルの内容を置き換えます。

const express = require("express");
const bodyParser = require('body-parser');
const http = require('http');
const port = process.env.PORT || 3000;

const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

// すべてのメソッドに対し CORS を有効にする
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*")
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
  next()
});

app.get("/fizzbuzz", (req, res, next) => {
    // ここでレスポンスの処理を追加します
});

app.listen(port, () => {
  console.log('Example app listening at http://localhost:' + port);
});

次に、Pythonサーバーとのインターフェイスにネットワーキングロジックを追加します。クラウドにデプロイする際、docker-compose.ymlで、ホストを“localhost” として指定し、“port” を指定することで、他のサービスとやり取りできるようになります。Docker CLIを使用してローカルでテストする場合は、Docker Composeコンテナ名でPythonサーバーを参照します。

Fargateでは、複数のコンテナが1つのユニットとしてデプロイされます(同じタスク定義など)。この分かりやすいデプロイ方法により、ローカルループバックインターフェイス上のコンテナ間のネットワーキングが容易になり、追加の設定、コスト、オペレーション、デバッグを回避できます。

// ここで Python サーバーコードにネットワーキングコードを追加します” コメントのすぐ下に、以下のコードを追加します。

const options = {
    port: 5000,
    host: 'localhost', // ローカル開発の場合は 'python' に置き換えます
    method: 'GET',
    path: '/random'
  };

  http.get(options, data => {
    var body = '';
    data.on('data', (chunk) => {
      body += chunk;
    });
    data.on('end', () =>{
      console.log(body);
      const randomNumber = body
      let fizzOrBuzz = ''
      // FizzBuzz ロジックコードをここに追加
      
      try {
        res.contentType("application/json").send({
         "newRandomNumber": body,
         "fizzOrBuzz": fizzOrBuzz
        });
      } catch (err){
        console.log(err);
        next(err);
      }
    }).on('error', (error) => {
      console.log(error);
    });
  })

最後に、FizzBuzzアルゴリズムを追加し、その結果をAPI呼び出し元に返します。以下の FizzBuzzアルゴリズムを “// FizzBuzz ロジックコードをここに追加” のすぐ下に追加します。

      if (randomNumber % 15 === 0) {
        fizzOrBuzz = 'FizzBuzz'
      }
      else if (randomNumber % 3 === 0) {
        fizzOrBuzz = 'Fizz'
      }
      else if (randomNumber % 5 === 0) {
        fizzOrBuzz = 'Buzz'
      }
      else {
        fizzOrBuzz = randomNumber
      }

これでビジネスロジックが完成しました。 以下のコマンドを実行して、複数コンテナのAPI をデプロイしましょう。

amplify push

正常にデプロイされたら、API に“cURL”を試すことができます。これで、乱数とFizzBuzzの結果が返されるはずです。

❯ curl https://<YOUR_API_ID>.execute-api.us-east-1.amazonaws.com/fizzbuzz
{"newRandomNumber":"37","fizzOrBuzz":"37"}
❯ curl https://<YOUR_API_ID>.execute-api.us-east-1.amazonaws.com/fizzbuzz
{"newRandomNumber":"72","fizzOrBuzz":"Fizz"}

成功しました!

本ブログ記事では、Amplify CLIを使用して1つまたは複数のコンテナを素早くデプロイする方法を示しました。サーバーレスコンテナに関しては、本記事ではカバーしきれなかったトピックがたくさんあります。例えば、以下のようなものです。

  • GitHubトリガーベースのデプロイ
  • Amazon CognitoでAPIを自動的に保護
  • ウェブアプリのワークフローのホスティング
  • 複数のenvironmentのサポート

コンテナに関し今後もブログ記事を公開していきますので、乞うご期待ください。また、詳細についてはAWSのドキュメントをご覧ください 。

翻訳はDeveloper Relations Engineer(Mobile/Web)のDaijiro Wachiが担当し、AppDev ConsultantのHiromi MotoderaとISV/SaaS Solutions ArchitectのKoya Kimuraが監訳しました。