Blogical

AWS/Salesforceを中心に様々な情報を配信していきます(/・ω・)/

AWSの「モダンウェブアプリケーションを構築する」のハンズオンやってみた

こんにちは、ロジカル・アーツの福島です。

最近コンテナサービスに興味が出てきており、学習を進めていく中で、ECSのタスク定義、サービス定義の境界線があいまいで、理解がなかなか進まず途方に暮れていました。そこでAWS公式のハンズオンの中で、ECSを使用していて初心者向けのものをECSの設定が終わる部分(下記リンクの2動的ウェブサイトの構築のモジュール2B)までやってみました。今回はハンズオンを通して、私が理解が深まったと感じた部分をコンテナサービスを中心にお伝えしようと思います。今回行ったハンズオンはこちらです。

ECS(Amazon Elastic Container Service)とは

コンテナオーケストレーションおよびクラスター管理インフラストラクチャを自分でインストール、運用、スケールする必要がないため、コンテナ化されたアプリケーションのリソースニーズと可用性要件に集中することができます。

と下記の公式のページに記載があります。

aws.amazon.com

ECSは、コンテナの管理を自動で行い需要に合わせてコンテナの数を調整したり、中身をアップデートするときの連携を上手にしてくれるものです。

実際に利用する場合は手順は以下の通りです。

  1. クラスタの作成
  2. タスク定義
  3. ロードバランサの作成
  4. サービスの作成

詳しくはアプリケーションの作成の項目の”2.5 ECSの設定”で出てきた際に見ていこうと思います。

ハンズオンを行って感じたメリット

  • ECS, ECR, AWS Fargateなどコンテナ関連の知識が深まった。

  • ELBの設定の理解が深まった。

アプリケーションの作成

前提条件

  • us-west-2のオレゴンリージョンで作業しました。(ハンズオンで使用するサービスを全てサポートしていればどこでも良いようです。今回は公式のページで全てのサービスをサポートしていることが確認できるオレゴンリージョンを選びました。)

  • 今回のハンズオンはAWS Cloud9*のコンソールからAWS CLIで操作します。今回はAWSのコンテナサービスを中心に行おうと思うので、AWS Cloud9の設定手順はこちらをご覧ください。

  • 使用するバケット名はlogicalartsforblogです。

  • 添付してあるコードでアカウントに関するものは適当なパラメータに変更してあります。

*AWS Cloud9はブラウザのみでコーディング、デバック、実行ができるクラウドベースの統合開発環境 (IDE) です。 詳細については公式ページをご覧ください。

aws.amazon.com

最終的に目指す構成

f:id:logicalarts:20200821111323p:plain

本ブログで進めた範囲での最終的な構成は上記のようになります。 このとき、以下のようにしてユーザーにサービスが提供されます。

  1. ユーザーが、静的ウェブサイトのホスティングを行っているAmazon S3のHTMLファイルにアクセスします。
  2. Amazon S3のHTMLファイルにアクセスが行われると、HTMLファイルに記述されたDNS名にアクセスが行われます。
  3. DNS名にアクセスが行われると、ELBがロードバランシングを行い、AWS Fargate上で動いているコンテナにアクセスを振り分けECSで定義したサービスが提供されます。

1. Amazon S3で静的ウェブサイトのホスティング

AWSのストレージサービスであるAmazon S3で、静的ウェブサイトのホスティングを行いました。まず、GitHubから今回のハンズオンの資料を入手してきました。バケットを作成し静的ウェブホスティングの設定とバケットポリシーを設定しました。最後に、今回のハンズオン資料から、配信を行うバケットにオブジェクトをコピーしました。

これらの作業は、下記のコマンドをCloud9上のターミナルで実行することで行うことができます。

git clone -b python https://github.com/aws-samples/aws-modern-application-workshop.git
cd aws-modern-application-workshop
aws s3 mb s3://logicalartsforblog
aws s3 website s3://logicalartsforblog --index-document index.html
aws s3api put-bucket-policy --bucket logicalartsforblog --policy file://~/environment/aws-modern-application-workshop/module-1/aws-cli/website-bucket-policy.json
aws s3 cp ~/environment/aws-modern-application-workshop/module-1/web/index.html s3://logicalartsforblog/index.html 

http://logicarartsforblog.s3-website-us-west-2.amazonaws.comへアクセスすると下記のページが表示でき動作が確認できました。こちらのURLは作成したバケット名とリージョンで変化するのでご注意ください。現在はバケットを削除しているため上記のURLにアクセスしていただいても表示しないようになっています。

f:id:logicalarts:20200818165719p:plain

2. 動的ウェブサイトの構築

2.1 AWS CloudFormationのテンプレートをデプロイ

AWS CloudFormationはインフラストラクチャとアプリケーションリソース全体を、テキストファイルで作成、管理できるサービスです。AWS CloudFormationを利用しテンプレートからスタックを作成しました。AWS CloudFormationから作成されたリソースをスタックと呼びます。

aws cloudformation create-stack --stack-name MythicalMysfitsCoreStack --capabilities CAPABILITY_NAMED_IAM --template-body file://~/environment/aws-modern-application-workshop/module-2/cfn/core.yml

ハンズオン資料からコピーしてきたテンプレートの中身で以下のリソースが作成できました。

VPC

・2つのNATゲートウェイ

Dynamo DB VPCエンドポイント

・セキュリティグループ

・IAMロール

2.2FlaskサービスのDockerイメージを作成

ハンズオン資料からコピーしてきたDockerfileからDockerイメージを作成しました。

ここで手順通り行うとエラーが発生します。最新のUbuntuのイメージ(ubuntu:latest)を使った場合、"apt-get install python-pip"を行うとエラーが出ます。私はUbuntuのverを下げることでこの問題を回避しました。environment/aws-modern-application-workshop/module-2/app以下にあるDockerfileを下記に書き換えました。

FROM ubuntu:18.04
RUN echo Updating existing packages, installing and upgrading python and pip.
RUN apt-get update -y
RUN apt-get install -y python-pip python-dev build-essential
RUN pip install --upgrade pip
RUN echo Copying the Mythical Mysfits Flask service into a service directory.
COPY ./service /MythicalMysfitsService
WORKDIR /MythicalMysfitsService
RUN echo Installing Python packages listed in requirements.txt
RUN pip install -r ./requirements.txt
RUN echo Starting python and starting the Flask service...
ENTRYPOINT ["python"]
CMD ["mythicalMysfitsService.py"]

対処する方法はたくさんあり、参考サイトのGitHubのissueで議論されていますので詳しくはそちらをご覧ください。以下にふたつほど例を挙げておきます。

https://github.com/aws-samples/aws-modern-application-workshop/issues/198

https://github.com/aws-samples/aws-modern-application-workshop/issues/211

cd ~/environment/aws-modern-application-workshop/module-2/app
docker build . -t 123456789.dkr.ecr.us-west-2.amazonaws.com/mythicalmysfits/service:latest

上記のコマンドでローカル環境(AWS Cloud9)にDockerイメージの作成を実行すると、下記の出力が得られました。

Successfully built 8bxxxxxxxxab
Successfully tagged 123456789.dkr.ecr.us-west-2.amazonaws.com/mythicalmysfits/service:latest

最後に実際に作成したイメージを次のコマンドで動かします 。

docker run -p 8080:8080 123456789.dkr.ecr.us-west-2.amazonaws.com/mythicalmysfits/service:latest

AWS Cloud9 メニューバーで [Preview]、[Preview Running Application] の順に選択して、プレビュー用のウェブブラウザを開きます。このときjson形式のテキストが表示されれば問題なく動いています。

2.3 DockerイメージをECRにプッシュ

ECR(Amazon Elastic Container Registry)はDocker imageをprivateに保管する場所を提供するサービスです。

ECRでmythicalmysfits/serviceというコンテナイメージリポジトリを作成しました。続いてECRにログインを行い、作成したmythicalmysfits/serviceにDockerイメージをpushして保管しました。最後にECRに保管したDockerイメージを表示し確認を行いました。これらの作業は、下記のコマンドをCloud9上のターミナルで実行することで行いました。

こちらで作成したDockerイメージを管理できるようになりました。

aws ecr create-repository --repository-name mythicalmysfits/service
$(aws ecr get-login --no-include-email)
docker push 123456789.dkr.ecr.us-west-2.amazonaws.com/mythicalmysfits/service:latest
aws ecr describe-images --repository-name mythicalmysfits/service

2.4 AWS CloudWatch Logsグループを作成

AWS CloudWatch Logsで新しいロググループを作成しました。

aws logs create-log-group --log-group-name mythicalmysfits-logs

コンテナを実行しているサーバーインフラストラクチャにはアクセスできないため、AWS Fargate を使用する場合はこのことが特に重要になります。

公式ページのこちら(モジュール2Bステップ2のB)の記述は、私が普段意識していなかった部分でしたので良い学びになりました。

2.5 ECSの設定

ECSでクラスターを作成しました。クラスターとは、コンテナが実際に動作するためのサーバーの集まりのことです。今回はサーバーにAWS Fargateを使用しました。AWS Fargateはコンテナを動かすサーバーのマネージドサービスです。サーバーの管理をAWS側で行います。

aws ecs create-cluster --cluster-name MythicalMysfits-Cluster

次にECSのタスク定義を行いました。

aws ecs register-task-definition --cli-input-json file://~/environment/aws-modern-application-workshop/module-2/aws-cli/task-definition.json

タスク定義のファイルの中身は、リソースの割り当てや使用するコンテナのイメージなどが書いてありました。タスク定義は、コンテナの中身がどのようなものであるかについて定義しているものだと理解しました。

{
  "family": "mythicalmysfitsservice",
  "cpu": "256",
  "memory": "512",
  "networkMode": "awsvpc",
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "executionRoleArn": "arn:aws:iam::123456789:role/MythicalMysfitsCoreStack-EcsServiceRole-97GHNFCHF5FZ",
  "taskRoleArn": "arn:aws:iam::123456789:role/MythicalMysfitsCoreStack-ECSTaskRole-1CHU8NX8UQHLT",
  "containerDefinitions": [
    {
      "name": "MythicalMysfits-Service",
      "image": "123456789.dkr.ecr.us-west-2.amazonaws.com/mythicalmysfits/service:latest",
      "portMappings": [
        {
          "containerPort": 8080,
          "protocol": "http"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "mythicalmysfits-logs",
          "awslogs-region": "us-west-2",
          "awslogs-stream-prefix": "awslogs-mythicalmysfits-service"
        }
      },
      "essential": true
    }
  ]
}

3. ロードバランサーの設定

ロードバランサーとは、アプリケーションへのトラフィックを複数のターゲットに自動的に分散するサービスです。ロードバランサーを設置することで、単一のDNS名にアクセスされたものを状況に応じて、伸縮自在にスケールインとスケールアウトが実行できるというメリットがあります。今回はAWSロードバランサーのサービスELB(Elastic Load Balancer)の中の1種類であるNLB(Network Load Balancer)を使用します。まずNLBをサービスの前面に設置し、続いてターゲットグループの作成を行った後ロードバランサーのリスナーを作成しました。

aws elbv2 create-load-balancer --name mysfits-nlb --scheme internet-facing --type network --subnets PUBLIC_SUBNET_ONE PUBLIC_SUBNET_TWO > ~/environment/nlb-output.json
aws elbv2 create-target-group --name MythicalMysfits-TargetGroup --port 8080 --protocol TCP --target-type ip --vpc-id VPC_ID --health-check-interval-seconds 10 --health-check-path / --health-check-protocol HTTP --healthy-threshold-count 3 --unhealthy-threshold-count 3 > ~/environment/target-group-output.json
aws elbv2 create-listener --default-actions TargetGroupArn=NLB_TARGET_GROUP_ARN,Type=forward --load-balancer-arn NLB_ARN --port 80 --protocol TCP

ロードバランサーのリスナーはあまりなじみがなかったので、公式ページを見てみました。

リスナーは、構成したプロトコルとポートを使用して接続要求をチェックするプロセスです。リスナーに対して定義したルールは、ロードバランサーが 1 つ以上のターゲットグループ内のターゲットにリクエストをルーティングする方法を決定します。

リスナーはロードバランサーがリクエストをルーティングする方法を定めたものとのことでした。

4. AWS Fargateでサービスを作成

NLBの作成と設定が完了したので、下記のコマンドでAWS Fargate上でECSサービスを作成しました。

aws ecs create-service --cli-input-json file://~/environment/aws-modern-application-workshop/module-2/aws-cli/service-definition.json

サービス定義のファイルの中身は、クラスターの指定やコンテナのdesiredCountやセキュリティーグループなどが書かれていました。サービス定義は、コンテナをどのように動かすのかを定義しているものだと理解しました。

{
  "serviceName": "MythicalMysfits-Service",
  "cluster": "MythicalMysfits-Cluster",
  "launchType": "FARGATE",
  "deploymentConfiguration": {
    "maximumPercent": 200,
    "minimumHealthyPercent": 0
  },
  "desiredCount": 1,
  "networkConfiguration": {
    "awsvpcConfiguration": {
      "assignPublicIp": "DISABLED",
      "securityGroups": [
        "sg-0a98acc08af978429"
      ],
      "subnets": [
        "subnet-02583a5c008f222da",
        "subnet-0024533bd106edb0e"
      ]
    }
  },
  "taskDefinition": "mythicalmysfitsservice",
  "loadBalancers": [
    {
      "containerName": "MythicalMysfits-Service",
      "containerPort": 8080,
      "targetGroupArn": "arn:aws:elasticloadbalancing:us-west-2:123456789:targetgroup/MythicalMysfits-TargetGroup/77f48246384b2ac2"
    }
  ]
}

NLBでサービスをテストしました。NLBのDNS名にアクセスすると上記の2.2で確認したjson形式のテキストが表示され、NLBとコンテナが期待通りに動いていることが確認できました。

最終的にS3にコピーしたファイルに該当する/module-2/web/index.htmlファイルの64行目にあるAPIエンドポイントのURLをNLBのDNS名に変更を行い、変更したファイルを静的ウェブサイトのホスティングを行っているs3://logicalartsforblog/index.htmlに上書きすることで、Amazon S3にアクセスした場合にNLBに繋がるように設定しました。

aws s3 cp ~/environment/aws-modern-application-workshop/module-2/web/index.html s3://logicalartsforblog/index.html

アクセス先をDNS名からS3のバケットのURLに変更しても、先ほどのjson形式のテキストが表示されることを確認できると思います。

まとめ

コンテナのサービスはとっつきにくい印象がありましたが、今回のハンズオンは、今何の作業をしているのかをイメージできる良い教材だと思いました。

ハンズオンを行う前は、タスク定義とサービス定義の境界線の理解があいまいでした。今回ハンズオンを行い、タスク定義はコンテナの中身を定義するもので、サービス定義はコンテナの運用方法を定義するものだという違いが、実際に中身を見て手を動かすことで理解がすすみました。

今後はよりコンテナの理解を深めるために、Blue/Green Deploymentやautoscalingなどを試していきたいと思いました。

公式のハンズオンはまだ続きがあります。興味がありましたらこちらの2.動的ウェブサイトの構築のモジュール2C以降をご覧ください。

参考サイト

今回行ったハンズオン aws.amazon.com github github.com