Amazon ECSを学びたい
おのやんです。
みなさん、Amazon ECS(以下、ECS)を学びたいと思ったことはありませんか?私はあります。
ECSを学習する際の選択肢の一つに、AWS公式が出しているハンズオンがあります。ネットワークやアプリケーションなどのさまざまな分野のAWSリソースを、実際にマネジメントコンソールで操作して構築することができます。
私は直近でECSを用いたコンテナWebアプリケーション環境を構築することになり、そのキャッチアップとしてAWS公式のWebアプリケーションハンズオンをひととおり触っていました。
ECSのハンズオンを通して、コンソールが大きく変わっていたり、操作に注意が必要な部分が多々ありました(ハンズオン自体も数年前に公開されたものですし)ので、今回はそちらも踏まえながら改めて操作方法を紹介していきたいと思います。
目指す構成
今回は、こちらのコンテナ構成を目指します。最初にAWS Cloud9(以下、Cloud9)にアクセスし、Cloud9環境下でRuby on Rails(以下、Rails)アプリケーションのコンテナをビルドします。その後、Cloud9からAmazon ECR(以下、ECR)へRailsコンテナイメージをプッシュします。最後に、ECR上のコンテナイメージをもとにVPC上でコンテナをデプロイしていきます。
Dockerイメージの作成・起動
ECSはコンテナを管理するAWSリソースですので、まずコンテナそのものを触ってみます。今回は、Dockerが最初からインストールされているCloud9の環境で、コンテナやイメージを操作していきたいと思います。
AWSのアカウントにログインした後に、Cloud9のコンソール画面から「環境を作成」を押下します。
ハンズオンとは違いますが、今回は名前を少し変えてaws-test-cloud9
という名前で環境を作成していきます。なおaws-test
という接頭辞は私が個人的に検証する際のAWSリソース命名規則になります。普段から使用しているaws-test-vpc
の上で起動させるため命名規則をこちらに合わせています。(後述しますが、ECSハンズオン関係のリソースはecs-test
という命名規則で作成します)
環境タイプについては「新しいECSインスタンス」を選択しておきます。
インスタンスタイプはt3.small
、プラットフォームはAmazon Linux 2023を選択しておきます。
今回はCloud9環境に対してSSMで接続することにします。この際、既存のVPC・サブネットを選択しておきます。こちらは「VPC設定」のトグルを開いて設定できます。
正常に作成されれば、十数秒〜数分でCloud9環境が作成されます。こちらのCloud9環境一覧画面の「開く」を押下して、Cloud9環境に入ります。
記事執筆時点で、Cloud9の統合開発環境画面はこんな感じになっています。左側のサイドバーにファイルやディレクトリが表示されていて、中央のセクションでコーディング可能です。また画面下部はコンソール画面となっています。
ハンズオンでは、こちらのCloud9コンソール上でコマンドを実行できます。ハンズオンに記載のあるコマンドですが、python
コマンドに関しては記事執筆時点で有効になっていませんでした。python3
コマンドで正常に実行されるようになるので、参考までにコマンドとその実行結果を以下に掲載しておきます。
$ docker --version
Docker version 25.0.3, build 4debf41
$ systemctl status docker.service
● docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; preset: disabled)
Active: active (running) since Tue 2024-06-11 02:18:42 UTC; 15min ago
TriggeredBy: ● docker.socket
Docs: https://docs.docker.com
Process: 1625 ExecStartPre=/bin/mkdir -p /run/docker (code=exited, status=0/SUCCESS)
Process: 1630 ExecStartPre=/usr/libexec/docker/docker-setup-runtimes.sh (code=exited, status=0/SU>
Main PID: 1636 (dockerd)
Tasks: 11
Memory: 482.9M
CPU: 32.549s
CGroup: /system.slice/docker.service
└─1636 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --default->
Jun 11 02:18:40 ip-10-1-2-158.ap-northeast-1.compute.internal systemd[1]: Starting docker.service - D>
Jun 11 02:18:41 ip-10-1-2-158.ap-northeast-1.compute.internal dockerd[1636]: time="2024-06-11T02:18:4>
Jun 11 02:18:41 ip-10-1-2-158.ap-northeast-1.compute.internal dockerd[1636]: time="2024-06-11T02:18:4>
Jun 11 02:18:42 ip-10-1-2-158.ap-northeast-1.compute.internal dockerd[1636]: time="2024-06-11T02:18:4>
Jun 11 02:18:42 ip-10-1-2-158.ap-northeast-1.compute.internal dockerd[1636]: time="2024-06-11T02:18:4>
Jun 11 02:18:42 ip-10-1-2-158.ap-northeast-1.compute.internal dockerd[1636]: time="2024-06-11T02:18:4>
Jun 11 02:18:42 ip-10-1-2-158.ap-northeast-1.compute.internal systemd[1]: Started docker.service - Do>
Jun 11 02:18:42 ip-10-1-2-158.ap-northeast-1.compute.internal dockerd[1636]: time="2024-06-11T02:18:4>
$ aws --version && git --version && mysql --version && python3 -V && php -v && java -version
aws-cli/2.15.58 Python/3.11.8 Linux/6.1.90-99.173.amzn2023.x86_64 exe/x86_64.amzn.2023
git version 2.40.1
mysql Ver 15.1 Distrib 10.5.23-MariaDB, for Linux (x86_64) using EditLine wrapper
Python 3.9.16
PHP 8.2.15 (cli) (built: Jan 16 2024 12:19:32) (NTS gcc x86_64)
Copyright (c) The PHP Group
Zend Engine v4.2.15, Copyright (c) Zend Technologies
with Zend OPcache v8.2.15, Copyright (c), by Zend Technologies
with Xdebug v3.2.2, Copyright (c) 2002-2023, by Derick Rethans
openjdk version "17.0.11" 2024-04-16 LTS
OpenJDK Runtime Environment Corretto-17.0.11.9.1 (build 17.0.11+9-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.11.9.1 (build 17.0.11+9-LTS, mixed mode, sharing)
ここから、Cloud9環境でコンテナイメージを作成・起動していきます。Cloud9のホーム直下にhandson
というディレクトリを作成して、その下にDockerfile
とGemfile
を作成します。こちらはハンズオン画面のものと同じですが、参考までにこちらにも掲載しておきます。
Dockerfile
# ruby:3.2.1 というベースイメージを取得する
FROM public.ecr.aws/docker/library/ruby:3.2.1
# 必要なパッケージ群を取得する
RUN apt-get update -qq && \
apt-get install -y nodejs postgresql-client npm && \
rm -rf /var/lib/apt/lists/\*
# ローカルにあるファイルをコンテナイメージ内にコピーする
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
# Rails アプリケーションを作成する
RUN bundle install && \
rails new . -O && \
sed -i -e "52a\ config.hosts.clear\n config.web_console.allowed_ips = '0.0.0.0/0'\n config.action_dispatch.default_headers.delete('X-Frame-Options')" config/environments/development.rb
# Rails を 3000 番ポートで起動する
EXPOSE 3000
CMD ["rails", "server", "-b", "0.0.0.0", "-p", "3000"]
Gemfileの内容は以下のようになります。
Gemfile
source 'https://rubygems.org'
gem 'rails', '7.0.4'
これらのファイルが準備できましたら、dockerコマンドを用いてコンテナイメージをビルド・起動していきます。
$ docker build -t rails-app .
[+] Building 148.6s (10/10) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 897B 0.0s
=> [internal] load metadata for public.ecr.aws/docker/library/ruby:3.2.1 2.1s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/5] FROM public.ecr.aws/docker/library/ruby:3.2.1@sha256:b38264d66820f3696906ed5fa51b1317d83215515f4a45500c9bf403 31.2s
=> => resolve public.ecr.aws/docker/library/ruby:3.2.1@sha256:b38264d66820f3696906ed5fa51b1317d83215515f4a45500c9bf403b 0.0s
=> => sha256:b38264d66820f3696906ed5fa51b1317d83215515f4a45500c9bf403bf2c9b47 1.86kB / 1.86kB 0.0s
=> => sha256:3440a912810a14b912ef9738d7b50bb95673c60cd2af71b16b8ace9730723a76 8.42kB / 8.42kB 0.0s
...
=> [internal] load build context 0.0s
=> => transferring context: 144B 0.0s
=> [2/5] RUN apt-get update -qq && apt-get install -y nodejs postgresql-client npm && rm -rf /var/lib/apt/list 47.2s
=> [3/5] WORKDIR /myapp 0.0s
=> [4/5] COPY Gemfile /myapp/Gemfile 0.1s
=> [5/5] RUN bundle install && rails new . -O && sed -i -e "52a\ config.hosts.clear\n config.web_console.all 56.7s
=> exporting to image 11.2s
=> => exporting layers 11.2s
=> => writing image sha256:e246790d98184975ebe2bfb3c4aac84ae1d6175ad26551244a6ba2207ee35888 0.0s
=> => naming to docker.io/library/rails-app
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
rails-app latest e246790d9818 51 seconds ago 1.37GB
$ docker run -d -p 8080:3000 rails-app:latest
こうすることで、Railsアプリケーションを起動することができます。なお、Cloud9上で起動したRailsアプリケーションは、画面上部のPreviewからPreview Running Applicationを選択することで確認できます。
ネットワークリソースの準備
これで、Cloud9でコンテナイメージを作成・起動する手順を把握できましたので、ここからはAWS上にECS環境を準備していきます。
まずは、ECSコンテナを配置するためのVPCやサブネットを、ハンズオンの手順通りに作成していきます。
VPC作成画面で「VPCなど」を選択し、名前タグの自動生成をオンにしecs-test
を入力します。
アベイラビリティゾーンの数は2つに設定し、パブリックサブネットは2つ、プライベートサブネットは0に設定していきます。
VPC・サブネットの設定としては、以上で十分です。これらが設定できたらVPC・サブネットを作成していきましょう。
この作成したパブリックサブネットに対し、追加でパブリックIPに関する設定を追加していきます。サブネット一覧画面で、さきほど作成したパブリックサブネットをひとつ選択します。そして、「アクション」トグルから「サブネットの設定を編集」を押下します。
こちらの「パブリックIPv4アドレスの自動割り当てを有効化」にチェックを入れます。この設定により、パブリックサブネットに配置されたECSコンテナに自動でパブリックIPが割り当てられます。
ECSクラスターの準備
ここからはいよいよECSの設定・作成に移っていきます。ECSのコンソール画面に移動し、「クラスターの作成」ボタンを押下します。
最初にクラスター名を設定します。今回はハンズオンとは別でecs-test-cluster
という名前を設定しておきます。
この後ハンズオンではネットワークの設定に入るのですが、2024年6月の執筆現在ではFargateのネットワーク設定はこの画面では行いません。ECSのネットワーク設定は可能です。ハンズオンと画面が大きく異なるポイントですので、ここは注意してください。
今回はハンズオンの方針通り、一旦AWS Fargate(以下、Fargate)のほかにAmazon EC2(以下、EC2)でもコンテナをホストします。オートスケーリンググループは新規で作成するため、「新しいASGの作成」を選択します。またプロビジョニングモデルに関しては「オンデマンド」を選択します。
そのほか、AMIはAmazon Linux 2、インスタンスタイプはt3.medium、クラスター起動数は最小最大ともに1で設定しておきます。
なお、今回のインスタンスプロファイルとしてはecsInsntanceRole
というロールがアタッチされていますが、「新しいロールを作成」を選択した場合は、こちらのecsInsntanceRole
が作成されてECSインスタンスにアタッチされる形になります。
ちなみにecsInstanceRole
にはAmazonEC2ContainerServiceforEC2Role
というマネージドの許可ポリシーがアタッチされています、こちらのポリシーはJSONで表示すると以下のようになります。それぞれCloudWatch Logsへの書き込み、EC2インスタンス名一覧の取得、ECRの読み取り、ECSへの書き込み・タグ付けが許可されています。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeTags",
"ecs:CreateCluster",
"ecs:DeregisterContainerInstance",
"ecs:DiscoverPollEndpoint",
"ecs:Poll",
"ecs:RegisterContainerInstance",
"ecs:StartTelemetrySession",
"ecs:UpdateContainerInstancesState",
"ecs:Submit*",
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "ecs:TagResource",
"Resource": "*",
"Condition": {
"StringEquals": {
"ecs:CreateAction": [
"CreateCluster",
"RegisterContainerInstance"
]
}
}
}
]
}
上述の通り、ECSクラスター作成画面ではEC2インスタンスのネットワーク設定はできます。先ほど作成したVPC・サブネットを選択して、セキュリティグループとしてもデフォルトのものを選択しておきます。
以上で、ECSクラスターの設定は完了です。クラスターを作成すると、数分後にこちらのようなクラスター詳細を確認することができます。
ECR・コンテナイメージの準備
それでは、次にCloud9で作成したコンテナイメージを格納するECRリポジトリを作成します。ECRコンソール画面に移動し、「プライベートリポジトリ」画面から「リポジトリを作成」を押下します。
今回はハンズオンに合わせて、rails-appという名前でリポジトリを作成します。
リポジトリが正常に作成されると、こちらのようにrails-appリポジトリが表示されますので、ここのリポジトリ名を押下します。
リポジトリ詳細画面では、プッシュしたコンテナイメージが表示されるのですが、肝心のコンテナイメージはまだデプロイされていません。ですので、それ用のコマンドを取得したいとおもいます。ECRでは、リポジトリ詳細画面からプッシュコマンドを表示できるので、ここを押下してプッシュコマンドを表示します。
各コマンドをCloud9条のコンソールで実行することになるので、メモ帳などにコマンドをコピペしておきましょう。
ここから、Cloud9コンソールに戻ります。ここで、先ほどのコマンドを実行します。先ほどのRail動作確認でイメージのビルドは済んでいますので 、コンテナイメージビルドのコマンドは飛ばして実行したいと思います。
$ cd ~/environment/handson/
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 245999598778.dkr.ecr.ap-northeast-1.amazonaws.com
WARNING! Your password will be stored unencrypted in /home/ec2-user/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
$ docker tag rails-app:latest <accound_id>.dkr.ecr.ap-northeast-1.amazonaws.com/rails-app:latest
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
<account_id>.dkr.ecr.ap-northeast-1.amazonaws.com/rails-app latest df6561c12cae 5 minutes ago 1.37GB
rails-app latest df6561c12cae 5 minutes ago 1.37GB
$ docker push <accound_id>.dkr.ecr.ap-northeast-1.amazonaws.com/rails-app:latest
The push refers to repository [<accound_id>.dkr.ecr.ap-northeast-1.amazonaws.com/rails-app]
9c7d49bc6884: Pushed
63f799c0c615: Pushed
1f8b160a522e: Pushed
14b679ac81a0: Pushed
2ee809ddc8d1: Pushed
2da1c0f2e13d: Pushed
6ec70191b5d9: Pushed
d4514f8b2aac: Pushed
5ab567b9150b: Pushed
a90e3914fb92: Pushed
053a1f71007e: Pushed
ec09eb83ea03: Pushed
latest: digest: sha256:6b26fa6708e67e92790e341a7458461c358b05581cf3626cf6dc4e02823593aa size: 2841
コンテナイメージを正常にプッシュできると、rails-appリポジトリにてコンテナイメージを確認できるようになります。
EC2へのデプロイ
次に、ECSタスクの作成を行なっていきます。ECSのタスク定義コンソールを開いて、「新しいタスク定義の作成」を押下します。
タスク定義名はaws-test-taskとします。インフラストラクチャの要件ではFargateとEC2インスタンスの両方を用いるので、起動タイプはともにチェックを入れます。タスクサイズはCPU:.25 vCPU
、メモリ:.5 GB
を選択します。タスクロールはなしで、タスク実行ロールは新しいロールの作成(または ecsTaskExecutionRole
)を選択します。
コンテナの設定項目では、名前はrails-app、イメージURIはECRに作成したプライベートリポジトリのURIを、必須コンテナは「はい」を選択しておきます。また、コンテナポートは3000で設定しておきます。
ログ収集に関しては、権限周りの設定が必要になるためハンズオン用に無効化しておきます。
以上が設定できたら、タスク定義を作成しておきましょう。
またコンテナ用に、いったんセキュリティグループを作成しておきます。名前は今回はecs-test-alb-sg
で設定し、VPCは先ほど作成したECS用のVPCで設定します。また、インターネットからのHTTPインバウンド通信を許可しておきます。
これらが準備できたら、タスク定義からECSサービスを作成していきたいと思います。
ECSのタスク定義コンソールから先ほどのタスク定義を選択し、「デプロイ」トグルから「サービスの作成」を押下します。
ECSクラスターでは、さきほど作成したecs-test-clusterを選択し、コンピューティング設定では「キャパシティープロバイダー設定」を選択しておきます。また、デフォルトのクラスターを使用するよう選択しておきます。
デプロイ設定では、リビジョンは最新のものを(今回はECRにコンテナイメージを1回プッシュしただけなので、1が最新)、サービス名はecs-test-ec2-role
、必要なタスクは2で入力しておきます。
ネットワーク設定では、ECSように作成したVPC、サブネットを選びます。なお、セキュリティグループではdefaultとecs-test-ec2-sgの2つを選んでおきましょう。
ロードバランシング項目では、コンテナにアクセスを分配するALBの設定ができます。基本的にはハンズオンの時と設定項目は変わらないですが、コンソール画面の入力欄の順番や配置が微妙に異なっていますので注意してください。
以上が設定できたら、ECSサービス・タスクをデプロイしましょう。正常にデプロイできると、こちらのようにステータスが「アクティブ」なECSサービスが確認できます。
今回のecs-test-ec2-serviceでは、EC2インスタンス上で2つのRailsコンテナが稼働しています。こちらはタスクとして確認できます。
試しにALBのドメインをブラウザに入力してみると、ECSコンテナとして起動したRailsアプリケーションにアクセスすることができます。
Fargateへのデプロイ
先ほどはEC2上にRailコンテナをデプロイしました。これを、今度はFargate上にデプロイしていきたいと思います。サービス詳細画面の「サービス」タブから「作成」ボタンを押下します。
コンピューティング項目のキャパシティープロバイダ戦略はそのままにして、キャパシティープロバイダー自体をFargateにします。
アプリケーションサービスとしては「サービス」、タスク定義とリビジョンはaws-test-tesk
、最近リビジョンを選択します。サービス名はecs-test-fargate-service
で設定し、タスク数は2で進めます。
ネットワークの部分では、先ほどと同じくECS用のVPC・サブネット、セキュリティグループを設定しておきます。
ロードバランシングは、Fargate用に新しく作成します。設定自体は先ほどと同様で問題ありません。
リスナー・ターゲットグループもEC2番と同様に作成します。なおターゲットグループはecs-test-fargate-tg
という名前で設定しておきます。
以上で、Fargate版のECSサービスは設定完了です。この設定でサービスを作成すると、EC2ホスティングとは別にFargateホスティングを行うサービスがデプロイされます。
Fargate用ALBのドメインをブラウザに入力すると、このようにFargateにデプロイしたRailsアプリケーションへアクセスすることができます。
ECSは手を動かして学ぶと効果的
個人的な感想ですが、ECSは他のAWSサービスと比べても設定項目数が多いため、手を動かして学ぶのがより効果的なサービスであると思っています。
ハンズオン作成当時と比べてECSコンソールも微妙に異なっていますし、ここも含めて試行錯誤することで、ECSの概念や構築方法が理解できると思います。では!