ECSってなんだ?
概要
AWS ECSを使ってみたくなったので勉強する
とりあえず参考になりそうな記事
ECSとはなんぞや
Amazon Elastic Container Service (ECS) はフルマネージドのコンテナオーケストレーションサービスであり、コンテナ化されたアプリケーションをより効率的にデプロイ、管理、スケールするのに役立ちます
公式様より
ふむ。。?
フルマネージド
AWSがサーバーとあかネットワークとか、インフラの管理をやってくれるってこと。
対義語だとオンプレとかセルフマネージドで、自分で全部管理しないといけなくなる。
コンテナオーケストレーションサービス
ざっくりいうとコンテナをいい感じに管理してくれるサービス。
公式様
そもそもコンテナとは?
- アプリケーションを動かすためのコードとか、環境とか、ツールとかを1まとめにしたもの
- アプリケーションの開発、配布、実行をより効率的かつ一貫性のある方法で行うために設計された技術で最近では主流
- コンテナを使わないと色々辛いらしい
- 開発してる時は動いたけど本番に公開したら動かないとか
- 手動で管理する手間が増えたり
- 無駄にリソースを割いてしまったりする
- ちなみにコンテナで開発するときはDockerがよく使われるよ
ECSの構成要素
これわかりやすかった
特徴
Amazon Elastic Container Service (Amazon ECS) は完全に管理されたコンテナオーケストレーションサービスであり、organizations がコンテナ化されたアプリケーションを簡単にデプロイ、管理、およびスケールするのに役立ちます。バージョンレスで独自の Amazon ECS コントロールプレーンは、AWS 環境の他の部分と密接に統合され、クラウドでコンテナワークロードを実行するための安全で使いやすいソリューションを提供します。Amazon ECS は、Amazon Elastic Compute Cloud (Amazon EC2) や AWS Fargate で実行できるほか、Amazon ECS Anywhere のオンプレミスインフラストラクチャでも実行できます。
公式様より
なるほど(?)
コンテナオーケストレーションサービス
さっき説明した通り
バージョンレスで独自の Amazon ECS コントロールプレーン
は?って感じなんだが。。
バージョンレス
- AWSが自動でいい感じに最新の状態を保ってくれるってこと
- 新しいバージョンに切り替わった時の移行措置もAWSがいい感じにやってくれる(=ユーザーに影響を与えずに済む)
コントロールプレーン
- データの転送とかルーティングみたな物理的なデータの処理のプロセスのこと
- リソースの管理とかヘルスチェックとかスケーリングとかしてくれる
「バージョンレスで独自の Amazon ECS コントロールプレーン」って言う特徴があるから、AWSの他のサービスともいい感じに組みわせることができる。
EC2とかFargateで実行できる?
EC2での実行
- EC2インスタンス上でコンテナを実行できる
- ユーザーがEC2インスタンスを管理して、コンテナをホストする仮想マシンのサイズとかタイプを指定する
- 自分で管理できるから柔軟に細かい設定ができる反面、自分で全て管理しないといけなくなる
Fargateての実行
そもそもFargateがよくわかってないのでスレッドにまとめる
Fargateとは
AWS Fargate は、サーバーレスの従量課金制のコンピューティングエンジンで、サーバーを管理することなくアプリケーションの構築に集中できます。サーバー管理、リソース割り当て、スケーリングなどのタスクを AWS に移行すると、運用態勢が改善されるだけでなく、アイデアからクラウドでの本番環境までの移行プロセスが加速され、総保有コストが削減されます。
サーバーレスの従量課金制のコンピューティングエンジン
- ユーザーがサーバーの存在について気にすることなくアプリケーションを実行できるクラウドサービス
- AWSがサーバーとかインフラの管理をいい感じにやってくれる
- 開発者はコードディングやデプロイに集中できるから、サーバーの設定や管理に関する心配をする必要がない
クラスタ
- 複数のマシン、サーバーをグループ化して、1つのシステムのように機能させること
- コンテナオーケストレーションでは、これらのクラスタ内のマシン上でコンテナが実行されます
- クラスタを使用することで、リソース(CPUやメモリなど)を効率的に利用し、アプリケーションの可用性やスケーラビリティを向上させることができます
イマイチECSとFargateの違いがわかってない
簡単に言うと
- ECSはコンテナのデプロイと管理のための全体的なサービス
- Fargateはその中のサーバーレス実行オプションの一つ
実際動かすのはどうしたらええねん
これがわかってないとモチベが湧かないので ChatGPTに聞いてみた。
- アプリケーション実装
- Dockerfileを定義してコンテナで動かせるように
- ビルドしたDockerfileをECR(コンテナレジストリ)にPUSH
- ECSタスク定義の作成
- ECSクラスターの準備
- サービスの定義とデプロイ
- サービスの監視と管理
こんな感じ。
「ECSタスク定義の作成」ぐらいからよくわかってないので調査
そもそもECSには肝となる要素があるらしい。
その中にタスク定義もあったので調査。
ECSの構成要素
- クラスター
- サービス
- タスク定義
- タスク
の4つ。図にすると下記のようになる
https://envader.plus/article/180 様より引用
図を見る感じだと
- クラスタが大枠にある
- その中に複数サービスがある
- そのサービスの中にタスクがある
といった感じだろうか?
クラスタ
- 「サービス」と「タスク」を実行する基盤のこと
- ECSリソースの論理的なグループ
- どんなサービスを使うかもここで定義する
- Fargate?EC2?オンプレ?
- どのVPC,サブネットを使うか、も定義
サービス
- クラスター内で実行するタスク(コンテナ群)を管理する役割
- どのタスク定義を使用するのか?どのクラスターを使用するのか?を決めることができる
- 実行中のタスクを管理する
- どのくらいのタスクが必要か?も定義できる
タスク定義
- コンテナ化されたアプリケーションを実行するために必要な設定を記述したJSONファイル
- どのイメージを使ってコンテナを作るか?とか
- CPUとかメモリどうやって割り当てる?とか
タスク
- タスク定義を元にして起動したコンテナの集まりのこと
- 単一のタスク内で複数のコンテナを実行することもできるよ
参考
ここで再度手順に戻る。
DockerfileをECRにpushしたあとの流れ。
- ECSタスク定義の作成
- ECSクラスターの準備
- サービスの定義とデプロイ
- サービスの監視と管理
この辺くらいからわかってなかったのでおさらいする。
ECSタスク定義の作成
ここで諸々設定する
- コンテナイメージのURI(ECRにプッシュしたイメージ)
- 必要なCPUやメモリの量
- ポートマッピング
- 環境変数
コンテナを実行するために必要な設定
ECS クラスターの作成
さっき説明した一番大枠となる部分。
定義することとしては、
- クラスターのタイプ(EC2かFargateか、それともオンプレか)
- クラスターの名前
- VPC とサブネット
- セキュリティグループの設定
こんな感じ。
ECS サービスの作成
※ちょっと複雑かもなので復習しながらメモ
サービスってなんだっけ?
⇨ クラスター内で実行するタスク(コンテナ群)を管理する役割を持っています
サービス作成でやることをもっと細かくみていく
- サービス定義の開始
- 起動タイプの選択
- タスク定義の選択
- サービスの設定
- ネットワーク設定
- ロードバランサーの設定(オプション)
- オートスケーリングの設定(オプション)
- サービスの作成
- サービスの監視
サービス定義の開始
- クラスターのリストから、タスクを実行したいクラスターを選択し、「サービス」タブに移動して「サービスの作成」をする(=コンソール上ではクラスターの中にサービスを定義していくイメージ)
起動タイプの選択
次にやることは「起動タイプ(Fargaet/EC2)の選択」らしい…?
ん?起動タイプの選択はクラスタの作成時にやったのでは?
どうやら「クラスターの作成」と「サービスの作成」の時に行う起動タイプの選択は違うらしい。
- クラスター作成時にの起動タイプの選択: 例えばFargateを選択した場合、そのクラスターはFargateタスクの実行をサポートします。
- サービス(またはタスク)作成時の起動タイプの選択: 例えばてFargateを選択した場合、クラスター内でFargate上にタスクが実行されるようになる
…ただクラスターの作成時にFargateを選択した場合はサービスの作成時にもFargateを選択しないといけないらしい。。。であればサービス作成時にわざわざ起動タイプを選択する必要はないのでは、、?(そもそもクラスターの起動タイプに依存するので)
タスク定義の選択
事前に作成しておいたタスク定義を選択する
サービスの設定
サービス名を指定し、同時に実行したいタスクの**数(希望するタスクの数)**を設定
ネットワーク設定
- タスクの実行に使用するVPCとサブネットを選択
- セキュリティグループを設定して、タスクが利用するポートへのアクセスを制御
- 必要に応じて、パブリックIPの割り当てを有効にする
「クラスターを作成する際に選択または設定した VPC とサブネットと同じものを使用することが一般的」らしい。だけど「使用するサブネットを変更することで、デプロイメントの配置や可用性の戦略を調整することができる」らしい。
ロードバランサーの設定(オプション)
タスクにトラフィックを分散させるために、ロードバランサー(Application Load BalancerまたはNetwork Load Balancer)を設定できます。
ロードバランサーを使用する場合は、リスナーとターゲットグループを設定します。
オートスケーリングの設定(オプション)
サービスオートスケーリングを設定して、トラフィックの増減に応じてタスクの数を自動的に調整します。
オートスケーリングポリシーには、最小および最大タスク数、スケーリングトリガー(CPU使用率やメモリ使用率など)を指定します。
サービスの作成
すべての設定を確認し、「サービスの作成」をクリックします。数分以内に、指定したタスクがFargate上で実行され始めます。
サービスの監視
サービスが作成された後、ECSコンソールからサービスの状態、実行中のタスク、ログなどを監視できます。
Go言語で作ったアプリケーションをECSにデプロイする
手を動かさないと始まらないのでやってみる。
多分こんな感じ。
- Go言語で開発
- Dockerfile作成
- ビルド
- ビルドしたファイルをECRにPUSH
- ECSセットアップ
|-- app
| |-- Dockerfile
| |-- cmd
| | `-- main.go
| `-- go.mod
`-- infra
main.go
package main
import "net/http"
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
})
http.ListenAndServe(":8080", nil)
}
FROM golang:alpine
WORKDIR /app
COPY . .
RUN go mod download
WORKDIR /app/cmd
RUN go build -o main .
CMD ["/app/cmd/main"]
app直下に移動して、docker build
cd app
docker build -t go-ecs-app .
docker runで起動できることを確認
docker run -p 8080:8080 go-ecs-app
ここまでで下準備完了。
次はDockerfileをECRにプッシュする
とその前にterraform のセットアップ
cd infra
`-- infra
`-- main.tf
main.tf
# ----------------------
# terraform configuration
# ----------------------
terraform {
required_version = ">= 0.13"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~>3.0"
}
}
}
# ----------------------
# provider configuration
# ----------------------
provider "aws" {
profile = "tknr_private"
region = "ap-northeast-1"
}
terraform init
ecr.tf
resource "aws_ecr_repository" "go-ecs-app" {
name = "go-ecs-app"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
}
解説
-
name: リポジトリの名前
-
iamge_tag_mutable: イメージタグが変更可能か、不可能か
- mutable: 同じタグを持つイメージを上書きできます。
- imutable: 一度プッシュされたタグは変更できません。これは、意図せずに使用中のイメージが上書きされるのを防げる
今回はアプリケーションのコードは変更されうるのでmutableにしておく
-
image_scanning_configuration: リポジトリにプッシュされるイメージのスキャンを有効にするかどうかを設定します。スキャンにより、セキュリティ脆弱性が自動的に検出されます
- scan_on_push: この値をtrueに設定すると、イメージがリポジトリにプッシュされるたびにスキャンが実行されます。これにより、新しいイメージにセキュリティ上の問題がないか即座に確認できます
terraform plan
terraform appy
コンソールにECRが作成されてることを確認
Dockerイメージにタグ付け
作成したECRリポジトリにイメージをプッシュする前に、ローカルのDockerイメージにECRリポジトリのURIを使用してタグを付ける必要があります。タグ付けには以下のコマンドを使用します。
docker tag go-ecs-app:latest <your-account-id>.dkr.ecr.<your-region>.amazonaws.com/go-ecs-app:latest
ちなみにアカウントIDは下記で確認できる
aws sts get-caller-identity --query "Account" --output text
- ECRへのプッシュを許可するためにDockerをECRにログイン
- 以下のコマンドを実行
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 879853972315.dkr.ecr.ap-northeast-1.amazonaws.com
pushする
docker push 879853972315.dkr.ecr.ap-northeast-1.amazonaws.com/go-ecs-app:latest
コンソールから確認
クラスターの作成
クラスターって何かおさらい
- 「サービス」と「タスク」を実行する基盤のこと
- ECSリソースの論理的なグループ
- どんなサービス(Fargate/EC2/オンプレ)を使うかもここで定義する
- どのVPC,サブネットを使うか、も定義するよ
resource "aws_ecs_cluster" "golange_ecs_cluster" {
name = "golang-ecs-cluster"
}
クラスターの作成
terraform apply
作成を確認
Q:ここで起動タイプは選択しないの?
A: AWS ECSでは、クラスターを作成する際に「EC2」と「Fargate」の2つの起動タイプから選択することができます。ただし、Terraformでクラスターを定義する際には、直接起動タイプを指定する必要はありません。起動タイプは、タスク定義やサービスを作成する際に指定します
by GPT
タスク定義の作成
- コンテナの実行に必要な詳細を指定する
- 具体的には下記のようなもの
- 使用するDockerイメージ
- GPUやメモリの割り当て
- 環境変数
- ログ設定
- 起動タイプ(EC2とかFargateとか)
タスク定義作っていく前にIAMロールの作成をする必要があるらしい。
- ECSタスクがAWSのECRからイメージをプルしてきたり、Cloud Watch にログを送信したりするために必要らしい
- ECSタスクが必要とする、AWSサービスへのアクセス権限をアタッチする必要がある
- IAMロールを作成するには下記のようにかける
resource "aws_iam_role" "ecs_task_execution_role" {
name = "ecs_task_execution_role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = "sts:AssumeRole",
Principal = {
Service = "ecs-tasks.amazonaws.com",
},
Effect = "Allow",
Sid = "",
},
],
})
}
TODO:この辺の項目指定できるの理解できてないから後でちゃんと調べる
上で作成ロールに対して、下記のポリシーをアタッチする
resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" {
role = aws_iam_role.ecs_task_execution_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
policy_arnで指定したAmazonECSTaskExecutionRolePolicy
とは、ECSタスク実行ロールが、AWSのリソースとやり取りをするために必要な権限があらかじめ定義されているポリシーのこと。
そしてタスク定義の作成
resource "aws_ecs_task_definition" "golang-ecs-app" {
family = "golang-ecs-app"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = "256"
memory = "512"
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
container_definitions = jsonencode([
{
name = "golang-ecs-app"
image = "${aws_ecr_repository.go-ecs-app.repository_url}:latest"
essential = true
portMappings = [
{
containerPort = 8080
hostPort = 8080
protocol = "tcp"
}
]
}
])
}
解説
- family: ECSタスク定義の一意な識別子。タスク定義のfamilyとリビジョン(例: my-app:1)を合わせることで、タスク定義の特定のバージョンを一意に識別することができる
- requires_compatibilities: タスク定義が使用する起動タイプ
- network_mode: タスクやコンテナのネットワークモード。起動タイプで何を選択したかによって異なる。
- Fargateの場合: awsvpcしか選択できない。タスクにElastic Network Interface(ENI)を割り当て、VPC内で直接アクセスできるようにする
- EC2の場合:
- bridge: Dockerのブリッジネットワークを使用します。これがデフォルトです。
- host: コンテナがホストのネットワーク名前空間を使用するようにします。これにより、ポートマッピングなしでコンテナにアクセスできます。
- awsvpc: awsvpcネットワークモードもEC2タスク定義でサポートされており、タスクレベル
- ENIを利用できます。
none: ネットワークを無効にします。
container_definitions
ECSのタスク定義内でコンテナを設定するための項目
JSONで指定する。
↓で定義されてる内容を深ぼってみる
container_definitions = jsonencode([
{
name = "golang-ecs-app"
image = "${aws_ecr_repository.go-ecs-app.repository_url}:latest"
essential = true
portMappings = [
{
containerPort = 8080
hostPort = 8080
protocol = "tcp"
}
]
}
])
- name: コンテナの名前。タスク内で一意である
- image: コンテナのイメージを指定します。このイメージは、Amazon ECRのようなコンテナレジストリに保存されているものである必要がある
- essential: このパラメータがtrueに設定されている場合、このコンテナがタスク内で失敗すると、タスク内の他のすべてのコンテナも停止します。少なくとも1つのコンテナはessentialをtrueに設定する必要があります
- portMappings: ホストとコンテナ間でポートをマッピングする設定です。これにより、コンテナが使用するポートとホスト上のポートを関連付けます。主に以下のパラメータを含みます:
- containerPort: コンテナ内で公開されるポートです。
- hostPort: ホスト上で公開されるポートです。Fargateを使用する場合、hostPortとcontainerPortは同じ値に設定されることが多いです。
- protocol: 使用するプロトコルで、tcpやudpを指定できます。
サービスを作成、、アプリケーションのセキュリティを確保し、適切なネットワーク隔離と通信制御を実現するために下記を作る必要もある
- VPC
- サブネット
- セキュリティグループ
# ----------------------
# VPC
# ----------------------
resource "aws_vpc" "golang-ecs-app-vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "golang-ecs-app-vpc"
}
}
# ----------------------
# サブネット
# ----------------------
resource "aws_subnet" "golang-ecs-app-subnet" {
vpc_id = aws_vpc.golang-ecs-app-vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1a"
map_public_ip_on_launch = true
tags = {
Name = "golang-ecs-app-subnet"
}
}
# ----------------------
# セキュリティグループ
# ----------------------
resource "aws_security_group" "golang-ecs-app-sg" {
name = "golang-ecs-app-sg"
description = "Allow all traffic"
vpc_id = aws_vpc.golang-ecs-app-vpc.id
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "golang-ecs-app-sg"
}
}
terraform apply
失敗してる、、なぜだ、、?
タスクの停止時刻: 2024-04-11T11:11:09.376Z
ResourceInitializationError: unable to pull secrets or registry auth: execution resource retrieval failed: unable to retrieve ecr registry auth: service call has been retried 3 time(s): RequestError: send request failed caused by: Post "https://api.ecr.ap-northeast-1.amazonaws.com/": dial tcp 99.77.62.61:443: i/o timeout. Please check your task network configuration.
ECRからイメージを取得できていない?
ECRからイメージを取得できていない?
考えられる原因
→しかしAmazonECSTaskExecutionRolePolicyはアタッチできてるので権限が足りてないということはないはず、、?
いけてそう
となるとこれか?
chatGPTに要約させると、
- AWSSupport-TroubleshootECSTaskFailedToStart runbookの実行:
- サブネットからインターネットへのルートの確認:
- ネットワークACLとセキュリティグループ設定の検証:
- Amazon VPCエンドポイントのチェック:
- IAMロールと権限の確認:
- Amazon ECSタスク定義内の機密情報の参照確認:
この辺のネットワーク設定から見直してみる
サブネットからインターネットへのルートの確認:
ネットワークACLとセキュリティグループ設定の検証:
Amazon VPCエンドポイントのチェック:
ネットワーク周りの設定を追加
# ----------------------
# インターネットゲートウェイの設定
# ----------------------
resource "aws_internet_gateway" "golang_ecs_app_igw" {
vpc_id = aws_vpc.golang-ecs-app-vpc.id
tags = {
Name = "golang-ecs-app-igw"
}
}
# ----------------------
# ルートテーブルの設定
# ----------------------
resource "aws_route_table" "golang_ecs_app_rt" {
vpc_id = aws_vpc.golang-ecs-app-vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.golang_ecs_app_igw.id
}
tags = {
Name = "golang-ecs-app-rt"
}
}
resource "aws_route_table_association" "golang_ecs_app_rta" {
subnet_id = aws_subnet.golang-ecs-app-subnet.id
route_table_id = aws_route_table.golang_ecs_app_rt.id
}
ログも仕込んでおく
タスク定義に追加
resource "aws_ecs_task_definition" "golang-ecs-app" {
family = "golang-ecs-app"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = "256"
memory = "512"
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
container_definitions = jsonencode([
{
name = "golang-ecs-app"
image = "${aws_ecr_repository.go-ecs-app.repository_url}:latest"
essential = true
portMappings = [
{
containerPort = 8080
hostPort = 8080
protocol = "tcp"
}
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = "/ecs/golang-ecs-app",
"awslogs-region" = "ap-northeast-1",
"awslogs-stream-prefix" = "ecs"
}
}
}
])
}
なんだこれ
あーなるほど
動いてるっぽいしエラーログも出ていないが、
パブリックアドレスからアクセスしても表示されない
:8080がついてないだけだった
docker push で新しいイメージを更新しても反映されない
キャッシュでもなさそう
更新時に「新しいデプロイの強制」オプションにチェックを入れて更新を実施してください。
なるほど
追加してみる
# ECSサービス
resource "aws_ecs_service" "golang_ecs_app_service" {
name = "golang-ecs-app-service"
cluster = aws_ecs_cluster.golang_ecs_cluster.id
task_definition = aws_ecs_task_definition.golang-ecs-app.arn
desired_count = 1
launch_type = "FARGATE"
force_new_deployment = true // これ
やっぱり変わらん
ECRにPUSH→サービスの更新までやらないとダメみたい
プッシュ後にサービス更新のコマンド叩いて反映されることを確認
# ECRにプッシュする
docker push ${ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${name}:latest
# ECSにデプロイする
aws ecs update-service --cluster ${CLUSTER_NAME} --service ${SERVICE_NAME} --force-new-deployment
TODO:サービスを更新するたびにIPアドレス変わるの固定しないと