Amazon Web Services ブログ

AWS Copilot CLI を使用した永続性を持つ AWS App Runner サービスの継続的ワークフローの実現

この記事は Enabling continuous workflows for AWS App Runner service with persistency using AWS Copilot CLI を翻訳したものです。

AWS は最近、AWS App Runner と呼ばれる新しいサービスを開始しました。これは、コンテナ化されたステートレスな Web アプリケーションを AWS でビルドして実行する最も簡単な方法です。App Runner は、ビルドパイプライン、ロードバランサー、スケールインとスケールアウト、そしてもちろんその基盤となるインフラストラクチャなど、コンテナを実行するために必要なすべてのリソースをプロビジョニングおよび管理します。

App Runner は Web 層の優れた抽象化レイヤーとして機能し、ステートレスな Web アプリケーションを最も簡単にデプロイして実行することができますが、外部の依存関係が必要になる場合があります。例えば、Web アプリケーションは、DynamoDB テーブルや S3 バケットなど、データ層として他の AWS リソースを必要とするかもしれません。

その必要なリソースをプロビジョニングして管理するには、どのような選択肢があるでしょうか?マネジメントコンソールや AWS CLI を使って作成しますか?それも一部のユーザーには有効ですが、一貫したデプロイメントのためには、より優れた方法が必要かもしれません。CloudFormation や Terraform のテンプレートをスクラッチから書くのはどうでしょうか?これにより、リソースを高度に構成管理することができ、継続的に管理できますが、明らかに簡単な作業ではありません。App Runner のベータ版ユーザーの一部からは、App Runner と同じくらいシンプルに依存するリソースを管理する方法を探しているというフィードバックがありました。

そこで Copilot の出番です。Copilot は AWS が作成したオープンソースのコマンドラインインターフェイスであり、もともとは開発者が Amazon ECS や AWS Fargate でプロダクションレディなコンテナワークロードを簡単に構築、リリース、および運用できるようにするために作成されました。Copilot では、わずかなコマンドで、アプリケーションの永続ストレージなどの外部の依存関係を作成することもできます。Copilot はバックグラウンドで CloudFormation を使用し、それらの依存関係を処理します。Copilot ユーザーが気にする必要があるのは、基盤となるインフラストラクチャについて考えたり管理することではなく、コンテナ化されたアプリケーションの「アーキテクチャ」だけです。

先に述べたように、App Runner はフルマネージドなビルドパイプラインを提供します。現在は、Node.js および Python アプリケーションにシームレスな “code-to-deploy” ワークフローを提供しますが、例えばユーザーによっては、コンテナをデプロイする前にユニットテストや統合テストを実行するといったより詳細な制御を行うために、マネージドなパイプラインではなく、独自のリリースパイプラインを持つことが重要な場合があります。Copilot はこのような場合にも役立ちます。いくつかのコマンドを実行するだけで、独自のカスタマイズ可能なパイプラインを作成できます。

AWS は App Runner の一般提供開始と同時に、App Runner をサポートする Copilot v1.7.0 をリリースしました。これにより、小規模な PoC アプリケーションから本番環境でのマルチリージョン、マルチアカウントのワークロードまで、Copilot を使ってコンテナ化されたアプリケーションを App Runner 上で実行できます。

これらがどのように連携して動作するのか、深く掘り下げてみましょう。このチュートリアルの最後には、以下の図のようなアーキテクチャができあがります。

ステップ 1 と 2 では、サンプルアプリケーションを実行するために、App Runner や DynamoDB など最低限必要なリソースをセットアップしてデプロイします。その後、ステップ 3 で AWS CodePipeline を使用してパイプラインを構成し、ステップ 4 で “push-to-deploy” を試して、すべての要素が期待どおりに連携することを確認します。チュートリアルでは、特に App Runner の使用と DynamoDB テーブルに対して料金が発生する場合があることに注意してください。

Copilot CLI を使用して App Runner で Next.js アプリケーションをビルドしてデプロイする

前提条件

  • Copilot v1.7.1 以降 (必要に応じてインストールガイドを参照)
  • Docker Desktop (または Linux 環境では Docker Engine)
  • jq
  • “hello-app-runner-nodejs” GitHub リポジトリのフォーク
    • このチュートリアルのステップ 4 で “push-to-deploy” を試すには、フォークした自分のリポジトリが必要になるため、Push が許可されているフォークしたリポジトリを使っていることを確認してください

0. フォークしたリポジトリをクローンする

$ git clone https://github.com/<Your GitHub ID>/hello-app-runner-nodejs

$ cd hello-app-runner-nodejs

1. Copilot の Application を作成する

Copilot のバイナリがない場合や古い場合は、Copilot のドキュメントに従ってインストールまたはアップデートしてください。

# クローンされたリポジトリのルートディレクトリにいることを確認します
$ ls
CONTRIBUTING.md  Dockerfile  LICENSE  README.md  components  docs  lib    node_modules  package-lock.json  package.json  pages  public  seed  styles

# v1.7.1 以降であることを確認してください
$ copilot --version
copilot version: v1.7.1

# Copilot アプリを作成します
$ copilot init
# 補足:
## Copilot が "Would you like to use one of your existing applications? "と尋ねた場合、"n"(No) を選択します
## その後、次のように質問に答えます
What would you like to name your application?: my-app
Which workload type best represents your architecture?: Request-Driven Web Service  (App Runner)
What do you want to name this Request-Driven Web Service?: my-svc
Which Dockerfile would you like to use for my-svc?: ./Dockerfile

Application name: my-app
Service name: my-svc
Dockerfile: ./Dockerfile
Ok great, we'll set up a Request-Driven Web Service named my-svc in application my-app listening on port 80.

✔ Created the infrastructure to manage services and jobs under application my-app.

✔ Wrote the manifest for service my-svc at copilot/my-svc/manifest.yml
Your manifest contains configurations like your container size and port (:80).

✔ Created ECR repositories for service my-svc.

All right, you're all set for local development.

“test” Environment をプロビジョニングする

この時点で、 “test” Environment の作成に進むかどうかを尋ねられます。 “y” (Yes) を選択して次に進みます。

なお、Copilot は、最初に Copilot の Application の Service で共有するインフラストラクチャを保持するための Environment を作成します。App Runner は VPC や Amazon Elastic Container Service (Amazon ECS) クラスターを必要としませんが、Copilot はデフォルトでこれらの料金が発生しないリソースを作成します。これにより、コンピュートに選択した App Runner または Amazon ECS 間で、同じ Environment 内でシームレスなワークフローを実現することができます。

# 補足:
## この処理は、各 Environment で 1 回だけ行われ、すべての共有リソースを作成します
## 完了までには数分かかります
Would you like to deploy a test environment?: y

Deploy: Yes

✔ Linking account <Your AWS account ID> and region <Your AWS region> to application my-app.
✔ Proposing infrastructure changes for the my-app-test environment.
- Creating the infrastructure for the my-app-test environment.           [create complete]  [77.3s]
  - An IAM Role for AWS CloudFormation to manage resources               [create complete]  [19.1s]

# ~ 省略 ~

✔ Proposing infrastructure changes for stack my-app-test-my-svc
- Creating the infrastructure for stack my-app-test-my-svc                        [create complete]  [343.5s]
  - An IAM Role for App Runner to use on your behalf to pull your image from ECR  [create complete]  [18.6s]
  - An IAM role to control permissions for the containers in your service         [create complete]  [16.3s]
  - An App Runner service to run and manage your containers                       [create complete]  [315.2s]
✔ Deployed my-svc, you can access it at https://<random string>.<your AWS region>.awsapprunner.com.

デプロイされたアプリを Web ブラウザで確認する

お好みの Web ブラウザで App Runner サービスのエンドポイントを開きます。エンドポイントの URL は、前のコマンドの出力の最後にあります (https://<ランダムな文字列>.<AWS リージョン>.awsapprunner.com という形式です) 。

開いてみると、アプリがデータの読み込みに失敗しているのがわかります。これは、この時点で DynamoDB テーブルがプロビジョニングされていないためです。

さて、それでは次のステップで DynamoDB テーブルをセットアップしましょう。

2. DynamoDB テーブルをセットアップする

$ copilot storage init

What type of storage would you like to associate with my-svc?: DynamoDB (NoSQL)
What would you like to name this DynamoDB Table?: Items
What would you like to name the partition key of this DynamoDB?: ItemId
What datatype is this key?: String
Would you like to add a sort key to this table?: N

Only found one workload, defaulting to: my-svc
Storage type: DynamoDB
Storage resource name: Items
Partition key: ItemId
Partition key datatype: String
Sort key? No
✔ Wrote CloudFormation template for DynamoDB Table Items at copilot/my-svc/addons/Items.yml

Recommended follow-up actions:
- Update my-svc's code to leverage the injected environment variable ITEMS_NAME.
For example, in JavaScript you can write `const storageName = process.env.ITEMS_NAME`.
- Run `copilot deploy --name my-svc` to deploy your storage resources.

先に進む前に、copilot storage init コマンドは実際には AWS リソースをプロビジョニングせず、ローカルのワークスペースに構成ファイルを作成するだけであることを理解しておくとよいでしょう。具体的には、このケースでは ./copilot/my-svc/addons/Items.yml を作成します。そのため、次のステップでは copilot deploy コマンドを使用してプロビジョニングを行います。

上記のターミナル出力の最後で、Copilot が App Runner サービスに自動的に挿入する ITEMS_NAME という名前の環境変数を介して、アプリケーションが DynamoDB テーブル名を参照できることに気付いたかもしれません。

次に、依存リソース (この場合は DynamoDB テーブルと IAM ロール) をプロビジョニングし、新しい環境変数 “ITEMS_NAME” を使用するようにサービスを更新しましょう。

$ copilot deploy --name my-svc

Only found one environment, defaulting to: test
Environment test is already on the latest version v1.4.0, skip upgrade.
[+] Building 58.6s (12/12) FINISHED

# ~ 省略 ~

✔ Proposing infrastructure changes for stack my-app-test-my-svc
- Updating the infrastructure for stack my-app-test-my-svc                 [update complete]  [351.9s]
  - An Addons CloudFormation Stack for your additional AWS resources       [create complete]  [60.8s]
    - An IAM ManagedPolicy for your service to access the Items db         [create complete]  [15.7s]
    - An Amazon DynamoDB table for Items                                   [create complete]  [31.9s]
  - An IAM role to control permissions for the containers in your service  [update complete]  [15.6s]
  - An App Runner service to run and manage your containers                [update complete]  [261.7s]
✔ Deployed my-svc, you can access it at https://<random string>.<your AWS region>.awsapprunner.com.

初期データを DynamoDB テーブルにシードする

プロビジョニングされた DynamoDB テーブルはもちろんまだ空っぽなので、次のコマンドを実行してシードしましょう。

ワークスペースに npm がない場合は、代わりに ./seed/seed.sh を実行してください。なお、このスクリプトを正常に実行するには、jq コマンドが必要です。

$ npm run seed

Finding the DynamoDB table name ...
Only found one service, defaulting to: my-svc
DynamoDB table name: my-app-test-my-svc-Items
Seeding initial data to the DynamoDB table ...
Done!

デプロイされたアプリを Web ブラウザで確認する (再度)

お好みの Web ブラウザアプリケーションで App Runner サービスのエンドポイントを開くか、前のステップで開いた Web ページをリロードします。前のステップで行ったように、ターミナルの出力から (またはもちろん App Runner マネジメントコンソールから) エンドポイント URL を取得することもできます。

おめでとうございます!これで今度は DynamoDB テーブルから読み込んだデータを使って、アプリが正常に動作するようになりました ?

最初の項目を開きましょう。 “Getting Started with App Runner using Copilot” です。

最初の項目では、このステップバイステップガイドでこれまでに完了したことを説明しています。

Copilot を使用していくつかのリソースを作成したので、次に進む前に、マネジメントコンソールで App Runner サービスという主要コンポーネントを少し詳しく見て、App Runner サービスがどのように見えるかを理解しましょう。

まず、Web ブラウザの新しいタブで AWS マネジメントコンソールの App Runner を開き、サービス名 “my-app-test-my-svc” をクリックして詳細を開きます。ご覧の通り、 “Default domain” として何度もアクセスしているエンドポイント、ECR リポジトリへのダイレクトリンク、そして Logs や Metrics、Custom domains などの複数のタブが表示されています。

“Logs” タブを見ると、複数のセクションがあります。1 つめは “Event log” です。これは、App Runner サービスの最新のライフサイクルイベントを表示します。次は “Deployment logs” で、デプロイメント毎に分離されたログストリームを表示します。最後は “Applocation logs” です。これは、App Runner 上で動作する Web アプリケーションからの実際のログです。この “Application logs” をクリックして開いてみましょう。

ログは CloudWatch Logs に保存され、コンソールにはインスタンスからの最新のログが結合されて 1 つのログストリームに表示されています。次のスクリーンショットでは、最初のインスタンスが “Missing required key ‘TableName’ in params” というエラーログを生成しています。これは、最初に DynamoDB テーブルなしでアプリケーションをデプロイしたため、その時点では TableName 変数がなかったためです。また、 “Using webpack 5. Reason: …” の行では、新しいインスタンスが起動していることがわかります。これらが、App Runner で現在実行しているインスタンスのログです。

この記事の冒頭で述べたように、Copilot はバックエンドとして CloudFormation を使用して AWS リソースをプロビジョニングおよび管理します。そこで、AWS マネジメントコンソールの CloudFormation セクションで、CloudFormation スタックを簡単に見てみましょう。CloudFormation を開いたら、 “Stacks” リストから “my-app-test-my-svc” スタックを選択します。

以下のページは “Template” タブから開くことができます。これはローカルのワークスペースにある ./copilot/my-svc/manifest.yml から Copilot が生成した実際の CloudFormation テンプレートです。AWS::AppRunner::Service CloudFormation リソースの定義があり、リソース定義で “ITEMS_NAME” という名前の環境変数を見つけることができます。

上記の環境変数 “ITEMS_NAME” の値は他のスタックの出力、この場合は次のスクリーンショットに示すように “my-app-test-my-svc-AddonsStack-O2629IALDI8H” として表示されるネストされたスタックを参照しています。

アドオンスタックとその CloudFormation テンプレートも、Copilot が ./copilot/my-svc/addons/Items.yml から作成したものです。YAML ファイルを参照して、前のステップで回答したいくつかの質問と “better by default” の原則で、Copilot が複雑なリソースをどのようにカバーし、抽象化しているかを知るのもよいでしょう。

さて、話を戻しましょう。

Web アプリの最初の項目のページがまだ Web ブラウザに表示されていると思います。それでは、 “Back to home” リンクをクリックしてトップページに戻り、2 番目の項目 “3. Set-up Release Pipeline” を開いて次のステップに進みましょう。

3. リリースパイプラインを設定する

開いた Web ページで説明されているように、2 つの Copilot コマンドを使用して CodePipeline でリリースパイプラインを作成します。

まず、copilot pipeline init コマンドを実行して、構成ファイルを生成します。このファイルは、前のステップで簡単に確認した manifest.yml に似ていますが、今回はパイプライン用です。

$ copilot pipeline init

Which environment would you like to add to your pipeline?: test
Which repository would you like to use for your pipeline?: https://github.com/<Your GitHub ID>/hello-app-runner-nodejs

✔ Wrote the pipeline manifest for hello-app-runner-nodejs at 'copilot/pipeline.yml'
The manifest contains configurations for your CodePipeline resources, such as your pipeline stages and build steps.
Update the file to add additional stages, change the branch to be tracked, or add test commands or manual approval actions.
✔ Wrote the buildspec for the pipeline's build stage at 'copilot/buildspec.yml'
The buildspec contains the commands to build and push your container images to your ECR repositories.
Update the build phase to unit test your services before pushing the images.

Required follow-up actions:
- Commit and push the buildspec.yml, pipeline.yml, and .workspace files of your copilot directory to your repository.
- Run `copilot pipeline update` to create your pipeline.

では、./copilot/pipeline.yml ファイルを見てみましょう。

$ cat ./copilot/pipeline.yml

# This YAML file defines the relationship and deployment ordering of your environments.

# The name of the pipeline
name: pipeline-my-app-hello-app-runner-nodejs

# The version of the schema used in this template
version: 1

# This section defines the source artifacts.
source:
  # The name of the provider that is used to store the source artifacts.
  provider: GitHub
  # Additional properties that further specifies the exact location
  # the artifacts should be sourced from. For example, the GitHub provider
  # has the following properties: repository, branch.
  properties:
    branch: main
    repository: https://github.com/<Your GitHub ID>/hello-app-runner-nodejs
    # Optional: specify the name of an existing CodeStar Connections connection.
    # connection_name: a-connection

# The deployment section defines the order the pipeline will deploy
# to your environments.
stages:
    - # The name of the environment to deploy to.
      name: test
      # Optional: flag for manual approval action before deployment.
      # requires_approval: true
      # Optional: use test commands to validate this stage of your build.
      # test_commands: Amazon Echo

このチュートリアルではこのファイルを編集しませんが、例えば、いくつかのテストを追加したい場合には、ファイルの最後にある test_commands 行のコメントを解除できます。詳細な仕様については、Copilot のドキュメントを参照してください。

続いて、copilot pipeline update を実行して、AWS アカウントに CodePipeline のパイプラインを作成します。

$ copilot pipeline update

✔ Successfully added pipeline resources to your application: my-app
...

ここで、CodePipeline がリポジトリにアクセスできるようにするために、次のターミナル出力で説明されているように手動でアクションを実行する必要があります。

...

ACTION REQUIRED! Go to https://console.aws.amazon.com/codesuite/settings/connections to update the status of connection copilot-toric-hello-app-runner-n from PENDING to AVAILABLE.

...

AWS アカウントにすでにサインインしている Web ブラウザの新しいタブで、コンソールの CodeSuite Connections を開きます。

接続名をクリックして詳細ビューに移動します。

Update pending connection” ボタンをクリックすると、”Connect to GitHub” というタイトルのポップアップウィンドウが表示されます。

すでに GitHub Apps に接続している場合は、拡大鏡アイコンの横にある入力にフォーカスを当てると、既存の GitHub Apps がドロップダウンメニューで表示されます。

< リポジトリにアクセスできる既存の GitHub App がある場合 >

いずれかを選択し、 “Connect” をクリックします。少し下の “Copilot のステータスを確認する” セクションまで進んでください。

< リポジトリにアクセスできる既存の GitHub App がない場合 >

Install a new app” ボタンをクリックすると、GitHub の Web ページが表示され、フォークしたリポジトリが属する GitHub ネームスペースを選択できます。

GitHub ネームスペースを選択したら、Web ページに従って GitHub App を作成または更新して、GitHub リポジトリへのアクセスを許可します。GitHub App ページの “Repository access” の項目で “Only select repositories” を選択した場合は、選択したリポジトリとして “hello-app-runner-nodejs” を選択する必要があります。

GitHub のページの “Save” ボタンをクリックすると、再び AWS マネジメントコンソールにリダイレクトされます。

これで入力に数値が入ったので、 “Connect” ボタンをクリックします。

Copilot のステータスを確認する

copilot pipeline update を実行したターミナルに戻ると、以下のようにパイプラインの作成に成功したことが表示されているはずです。

...
✔ Successfully created a new pipeline: pipeline-my-app-hello-app-runner-nodejs

Recommended follow-up actions:
- Run `copilot pipeline status` to see the state of your pipeline.
- Run `copilot pipeline show` for info about your pipeline.

CodePipeline でリリースパイプラインを作成したので、Web アプリケーション の Web ページの左下にある “Back to home” リンクをクリックします。

次のステップでは、作成したばかりのパイプラインを使用して “push-to-deploy” を試してみましょう。

4. “push-to-deploy” を試す

リポジトリ内の何かを編集し、それを Push してパイプラインを起動しましょう!

$ vim ./components/layout.js

# ファイルの 10 行目にある `topPageMessage` を見てください
# "Let's Get Started!" というテキストを、アプリケーションで表示したいものに変更しましょう
# 私は "Happy building with App Runner and Copilot!" に変更しました :)

テキストを変更したら、それを保存し、ターミナルウィンドウで次のコマンドを実行します。

# 補足:
## "./components/layout.js" ファイルだけでなく、
## "copilot" ディレクトリ以下のすべてのファイルをステージングしていることを確認してください
## これらのファイルがない場合、パイプラインはビルドステージで失敗します
$ git add .

$ git commit -m "My first push-to-deploy with CodePipeline"

[main xxxxxxx] My first push-to-deploy with CodePipeline
 6 files changed, 265 insertions(+), 1 deletion(-)
 create mode 100644 copilot/.workspace
 create mode 100644 copilot/buildspec.yml
 create mode 100644 copilot/my-svc/addons/Items.yml
 create mode 100644 copilot/my-svc/manifest.yml
 create mode 100644 copilot/pipeline.yml

$ git push

Enumerating objects: 15, done.
Counting objects: 100% (15/15), done.
Delta compression using up to 4 threads
Compressing objects: 100% (10/10), done.
Writing objects: 100% (12/12), 4.52 KiB | 1.51 MiB/s, done.
Total 12 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/<Your GitHub ID>/hello-app-runner-nodejs.git
   xxxxxxx..yyyyyyy  main -> main

すべて完了です!Web ブラウザの新しいタブで、https://<AWS リージョン>.console.aws.amazon.com/codesuite/codepipeline/pipelines にあるコンソールの CodePipeline にアクセスし、リストの中のパイプラインを開きます。以下のような表示になります。

パイプラインがすべてのステージを正常に完了するまで数分待ってから、Web アプリケーションの Web ページの左下にある “Back to home” リンクをクリックし、トップページに戻ります。

おめでとうございます ? ご覧のとおり、私たちが行った変更は、パイプラインを介して正常にデプロイされました!

これで、git push で呼び出すことができる構成可能な継続的デリバリーパイプラインができました。追加のコミットを Push して、サンプルアプリがどのように動作するかを確認することもできます。

5. お掃除する

これが最後の (オプションですが重要な) ステップです。予期せぬ課金を避けるために、プロビジョニングされたリソースをすべて削除することをお勧めします。

Copilot を使用すれば、このチュートリアルで AWS アカウントに作成したすべてを削除することも簡単です。これを行うには、次のコマンドを実行します。

$ copilot app delete --name my-app --yes

✔ Deleted service my-svc from environment test.
✔ Deleted resources of service my-svc from application my-app.
✔ Deleted service my-svc from application my-app.
✔ Deleted environment test from application my-app.
✔ Cleaned up deployment resources.
✔ Deleted pipeline pipeline-my-app-hello-app-runner-nodejs from application my-app.
✔ Deleted application resources.
✔ Deleted application configuration.
✔ Deleted local .workspace file.

おつかれさまでした!

さらに学ぶためのリソース

AWS App Runner や AWS Copilot CLI について学ぶためのリソースが他にもありますので、ぜひチェックしてみてください。

App Runner と Copilot で Happy Building! ?

翻訳はプロフェッショナルサービスの杉田が担当しました。