woshidan's blog

あいとゆうきとITと、とっておきの話。

terraformのmoduleの基本的な使い方を試しました

あれもこれもやらなくては~と思っていたらどれにも手がついておらず結構ガッカリですが、ひとまず今日はterraformのmoduleの基本についてまとめておきます。

TL;DR

  • terraformがビルドに使うのはカレントディレクトリにある.tfファイル
  • moduleで他のディレクトリや他のサーバにある設定をコピーしてきて再利用する
    • moduleのsourceで指定するコピー元がローカルファイルでも terraform plan の前に terraform init 必要
  • variableを利用してmoduleを書いた側で一部の変数を代入する

terraformがビルドに使うのはカレントディレクトリにある.tfファイル

terraformが terraform planterraform apply した時に利用する設定ファイル(.tf)はterraformのコマンドを実行したディレクトリとなります。

たとえば、

$ tree .

.
├── foo
│   └── test_3.tf
├── test.tf
└── test_2.tf
// test.tf
provider "aws" {
    access_key = "HOGEHOGE"
    secret_key = "FUGAFUGA"
    region = "ap-northeast-1"
}
 
resource "aws_instance" "hoge" {
    ami = "ami-29160d47"
    instance_type = "t2.nano"
}
// test_2.tf
resource "aws_instance" "fuga" {
    ami = "ami-29160d47"
    instance_type = "t2.nano"
}
// test_3.tf
resource "aws_instance" "bar" {
    ami = "ami-29160d47"
    instance_type = "t2.nano"
}

というようなディレクトリ構成のとき、カレントディレクトリで terraform plan すると結果に出てくるリソースは

$ terraform plan 
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + aws_instance.fuga
      id:                           <computed>
      ami:                          "ami-29160d47"
      arn:                          <computed>
      ...

  + aws_instance.hoge
      id:                           <computed>
      ami:                          "ami-29160d47"
      arn:                          <computed>
      ...

のように aws_instance.fuga, aws_instance.hoge の2つだけです。

他のディレクトリのファイルの設定を利用する場合は、次に説明する module を利用します。

moduleで他のディレクトリや他のサーバにある設定をコピーしてきて再利用する

moduleを使うと、

module "test" {
  source  = "./modules/test"
}

のように、参照元の設定ファイルがあるディレクトリを指定して、参照元に書かれた設定を他のディレクトリで利用することができます。

たとえば、先ほどの設定ファイルに対し、

// test.tf
provider "aws" {
    access_key = "HOGEHOGE"
    secret_key = "FUGAFUGA"
    region = "ap-northeast-1"
}
 
resource "aws_instance" "hoge" {
    ami = "ami-29160d47"
    instance_type = "t2.nano"
}

module "foo" {
  source = "./foo"
}

のように module の部分を追加すると、terraform plan の結果に foo/test_3.tf に書かれていた aws_instance.barmodule.foo.aws_instance.bar として登場するようになります。

$ terraform plan 
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + aws_instance.fuga
      id:                           <computed>
      ami:                          "ami-29160d47"
      arn:                          <computed>
      ...

  + aws_instance.hoge
      id:                           <computed>
      ami:                          "ami-29160d47"
      arn:                          <computed>
      ...

  + module.foo.aws_instance.bar
      id:                           <computed>
      ami:                          "ami-29160d47"
      arn:                          <computed>
      ...

ここで、 test_2.tf にも同じ設定を追記すると

$ terraform plan 

Error: Failed to load root config module: Error loading modules: module foo: duplicated. module names must be unique

となります。

設定側で module に対して一意になるように名前をつけるのは、おそらく module が、どこかにある設定を利用する側の都合でコピーしてきたり、コピーしてきた設定に一部変数を代入して使うためのもののようだからと思います。

先ほどまで試していたローカルファイルシステム上の他のディレクトリは、 modulesource として指定できるデータの取得元の一部で、 https://www.terraform.io/docs/modules/sources.html によると、他には

  • Terraform Registry (Docker HubみたいにTerraformの設定をアップロードできるサイト)
  • GitHub
  • BitBucket
  • 他のgitリポジトリ
  • 特定のサイトのURL
  • S3のバケット

があります。

そういえば、この取得元の一覧を見ていると module で外部ソースを指定した時は git fetch ではないんですが、何かコマンドを打たなければいけないような気がします。

じつは、新しく module を設定に追加するとその module のソースがローカルファイルでも一度 terraform init を打つ必要があります。

terraform init を忘れて terraform plan を実行したときのログと、その後 terraform init を打って、./foo 以下から module foo の設定ファイルを取得している様子のログが以下となります。

$ terraform plan 

Error: Failed to load root config module: Error loading modules: module foo: not found, may need to run 'terraform init'

$ terraform init
Initializing modules...
- module.foo
  Getting source "./foo"

Initializing provider plugins...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

variableを利用してmoduleを書いた側で一部の変数を代入する

さて、moduleの使い方について「module がどこかにある設定を利用する側の都合でコピーしてきたり、コピーしてきた設定に一部変数を代入して使うためのもののようだ」と書きましたが、「コピーしてきた設定に一部変数を代入して使うためのもののようだ」の方についてももう少し詳しく見ておくことにします。

variables については今度 local と並べて使える型と使う場面の違いについてまとめようかな、と思いますが、terraformには

variable "hogehoge" {}

のように変数があることを宣言して、設定ファイル中で

${var.hogehoge}

のように利用できる変数 variables があります。各 variables の変数の値は、

  • 設定ファイル中で記載したり
  • 別の設定ファイルに抜き出してコマンドの実行時に -var-file=foo のように変数が書かれたファイルを指定したり
  • コマンドの実行時に -var 'foo=bar' のように一つずつ設定したり

できます。

設定ファイル中で記載できる箇所には module の中もあり、これを利用して

  • modulesource に指定する設定ファイル: 可変にしたい場所は ${var.env} のように variable を利用して書いておく
  • module を定義する側: module のブロックの中で、 source の設定ファイルに書かれた variable の値を設定する

のようにすると、production, staging環境で一部だけ設定が違う、というような場合の記述が便利にできます。

簡単なサンプルを書いてためしておきましょう。

$ tree .
.
├── modules
│   └── ec2.tf
├── production
│   └── main.tf
└── staging
    └── main.tf
// ./modules/ec2.tf
// stagingとproductionでAWSアカウントを分けてないとする
provider "aws" {
    access_key = "HOGEHOGE"
    secret_key = "FUGAFUGA"
    region = "ap-northeast-1"
}

variable "environment" {}
 
resource "aws_instance" "app" {
    ami = "ami-29160d47"
    instance_type = "t2.nano"

    tags {
       Name = "app-${var.environment}"
       env  = "${var.environment}"
    }
}
// ./production/main.tf
module "app_production" {
    source = "./../modules"
    environment = "production"
}
// ./staing/main.tf
module "app_staging" {
    source = "./../modules"
    environment = "staging"
}
production$ terraform init
production$ terraform plan
...

  + module.app_production.aws_instance.app
      id:                           <computed>
      ami:                          "ami-29160d47"
      arn:                          <computed>
      ...
      instance_type:                "t2.nano"
      ...
      tags.Name:                    "app-production"
      tags.env:                     "production"
staging$ terraform init
staging$ terraform plan
...

  + module.app_staging.aws_instance.app
      id:                           <computed>
      ami:                          "ami-29160d47"
      arn:                          <computed>
      ...
      instance_type:                "t2.nano"
      ...
      tags.Name:                    "app-staging"
      tags.env:                     "staging"

現場からは以上です。

参考

Application Load Balancer(ALB)のコンポーネントについてメモ

先週、CLBではできなくてALBでできるようになったいくつかのことについてメモしました。

今週は、その記事に出てきたリスナーグループ、ターゲットグループといったALBの各コンポーネントに対し、そのコンポーネントを作成することで何を設定しているのかについてメモしておきます。

ロードバランサ

ALBのコンポーネントといったとき、それは実際のロードバランサのインスタンスであったり、管理画面上でぽちぽち追加していく一連の設定であったりすると思っていて、ロードバランサは

  • 一連の設定の中で一番大きい概念、かつ
  • 実際EC2インスタンスの上?*1で動いているだろうロードバランサ

のことを指します。

ALBのインスタンスを作成するときにはEC2の管理画面から作成しますが*2

  • 前提として利用するAZに少なくとも一つのパブリックサブネットを持つVPC

が必要です。ALBの作成時には名称以外に

などを指定します。さらに、作成時のフォームの流れで

も行います。

リスナー

ALBのリスナーは、

  • ALBが受信したリクエストのプロトコル、ポートをチェックして
  • リスナールールを用いてホスト、パスも見つつ、どのターゲットグループに転送したらよいかを決める

役割をするプロセスです。リスナーがチェックするプロトコルHTTPSの場合はリスナーに証明書をデプロイする必要があります。

リスナールール

リスナールールとは、

  • リスナーで指定したプロトコル・IPのリクエストがあった場合
  • ホスト・パスを見たり見なかったりして
  • どのターゲットグループへ転送するか
  • [HTTPSの場合] Amazon Cognito または OIDC を使用したユーザー認証を行うか

を決めるもので、リスナーに追加して利用します。

ALBで利用可能になったコンテントベースルーティング*6はリスナールールでのホスト・パスの条件設定と次の項のターゲットグループを用意して行います。

ターゲットグループ

CLBではロードバランサーに直接EC2インスタンスを追加し、追加したインスタンスの間でリクエストを分散させていましたが、ALBではリスナールールごとにリクエストの分散先を設定するようになりました。

このリスナールールごとに指定するリクエストの分散先の単位がターゲットグループです。ターゲットグループにEC2インスタンスをターゲットとして追加します。

ターゲットグループは配下のインスタンスにリクエストを転送してもよい状態かどうか確認するためのヘルスチェックの設定を持っています。

ターゲット

ロードバランサで外部からのリクエストを受け付け、リスナーがリスナールールに従ってリクエストを割り振るターゲットグループを決め、ターゲットグループの中で最終的にリクエストの転送先として選ばれるEC2インスタンスターゲットです。

instanceID(プライベートIPアドレスと紐付いている)か、プライベートIPアドレスを指定してターゲットグループに追加します*7

参考

現場からは以上です。

*1:マネージドサービスはどうもセキュリティグループなどの設定の構成を見ているとそうなっているきがする...

*2:https://cdn-ak.f.st-hatena.com/images/fotolife/w/woshidan/20180806/20180806093001.png

*3:セキュリティグループはEC2のインスタンスとしての設定なので、あとから詳細を修正・確認するときはEC2の管理画面から可能

*4:インターネット向けはロードバランサがリクエストを受信するまでの経路にインターネットを含む. このためロードバランサにパブリックDNSが作成時に割り振られる https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/classic/elb-internet-facing-load-balancers.html

*5:内部向けはロードバランサがリクエストを受信するまでの経路にインターネットを含まない。VPC内のあるインスタンスから別のインスタンス群にリクエストを分散させたい、といった場合に使うのか。VPC内部同士の通信であればプライベートIPアドレスで通信ができるので、作成時にパブリックDNS名は発行されない。パブリックDNSがなく外部からのアクセスの手段が提供されていないので内部向けロードバランサはVPC外部からのリクエストを受けつけない https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/classic/elb-internal-load-balancers.html

*6:ホスト名やパスを見てリクエストを割り振るインスタンス or インスタンスの集団を変える

*7:https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/load-balancer-target-groups.html#target-type

ALBでできるようになったコンテントベースルーティングとALBと連携したECSの動的ポートマッピングについて

ELBとCLBとALBの違い

AWSのロードバランサで検索すると、ロードバランサを指す言葉に、ELB, CLB, ALBの3種類がでてきます。

この3種類の違いは、

  • ELB ... Elastic Load Balancing. サービス名。ELBの提供しているロードバランサのリソースにCLBとALBがある
  • CLB ... Classic Load Balancing. ELBで利用出来るロードバランサの種類。ALBの提供開始まではELBと呼ばれていて、サービス名とサービスで利用出来るリソースの名前が一致していた
  • ALB ... Application Load Balancing. ELBであとから利用出来るようになったロードバランサ。 コンテントベースルーティング やECSの動的ポートマッピングの利用が可能となった

となっています。以下は コンテントベースルーティング とECSの動的ポートマッピングについて、なぜALBではできるようになったのか、あるいは、どういうことが嬉しいのか、について調べて考えたことについてメモです。

ALBのコンテントベースルーティングについて

CLBを利用する場合、リクエスト元のプロトコルとポートを見て許可されたリクエストをロードバランサに追加されたEC2インスタンスに割り振ること以外できず、リクエストの内容を見て調整することはありませんでした*1

なので、1つのロードバランサに追加するEC2インスタンスは、基本的にすべて同一のインスタンスタイプ(というより同一の働きをする同一スペックのホスト)である必要がありました。

たとえば、Appサーバと画像配信用サーバを利用するサービス(ドメイン: sample.com )があり、

  • 基本的にはAppサーバへリクエストを転送する
  • /img/ のパスだけ画像配信用サーバへリクエストを転送する

のように同じドメインに対してリクエストを処理すべきサーバが複数種類ある場合、

  • まず sample.comドメインへ来たリクエストを1段目のCLBからすべてnginxに転送する
  • nginxがリクエストのパスを見て、パスが /img/ 以下なら2段目のCLB(画像配信サーバ用)、それ以外なら2段目のCLB(Appサーバ用)へリクエストを転送
  • 2段目のCLBが、複数のAppサーバ、画像配信サーバへリクエストを割り振る
  • リクエストが増えてきたら1段目のCLB配下のnginxを増やす

f:id:woshidan:20180803144804p:plain

といった構成になり、CLB以外にリクエストの転送先を切り替えるためにリーバスプロキシを挟む必要がありました*2

一方、ALBではロードバランサ自身がリクエストのパスを読んでリクエスト先を判断することができるので*3、リクエストの分散に対するネットワーク構成をシンプルにすることができます。

f:id:woshidan:20180803144816p:plain

ALBと連携することによるECSの動的ポートマッピングについて

ALBの登場により、ALBとサービスが連携することで一つのECSインスタンス上で同じタスクが複数実行できるようになり、ECSのサービスでタスクを複数実行するのが少し簡単になりました。

これについても少しメモしておきます。

CLBの頃には、

といった形でした。

つまり、ロードバランサから受信した際のプロトコル/ポートとバックエンドのEC2インスタンスへ転送する際のプロトコル/ポートが1対1で、転送先のポートは決めてから割り振るインスタンスを選ぶ感じだったんですね。

さて、タスクというのはだいたいdocker-composeで立ち上げるコンテナ一式のことなので、一つのECSインスタンスで同じタスク定義のタスクを複数立ち上げるということは、一つのDockerホスト上でポートマッピングに関する設定まで同じ定義のコンテナを複数立ち上げる、ということになりますが。。

いくらECSのDocker側でコンテナに対応するポートがたくさん公開されたとしても、ロードバランサーに来たプロトコル/ポートに対応してEC2インスタンスが受信するときのプロトコル/ポートは一つなので、そのうち一つのタスク(コンテナ. コンテナに対応するポート)にしかリクエストは転送されません*6

このため、CLBを利用して負荷分散をすることを念頭に置いた場合、ECSでは実質1インスタンス1タスクみたいな制限となっていたのでした。

ALBになって

という形になりました。

となりました。そうすると、CLBの頃にできなかった 同じインスタンス内の別のタスク(がリクエストを待ち受けているポート)へのリクエスト振り分け が可能となります。

余談

ここからは、あらためて、 https://aws.amazon.com/jp/premiumsupport/knowledge-center/dynamic-port-mapping-ecs/ のリンクを眺めた末の妄想に近いのですが、

たぶん、

  1. ロードバランサをセットしたサービスを作成する
  2. サービスがタスク起動時にタスクのコンテナが公開したポートを控えておく
  3. 2. で控えたポートとプロトコルを使ってタスク専用のターゲットグループを作成する
  4. 3. のターゲットグループに、タスクが動いてるEC2のインスタンスのIPにより自身が動作しているインスタンスをターゲットとして追加

というようなことをしているのだと思います。

ターゲットグループはロードバランサに対して作成するので、サービスは3, 4のステップのためにロードバランサの情報を知っておく必要があり、そのためにECSの動的ポートマッピングを利用する場合はサービス作成時にロードバランサを登録しているのでしょう。

現場からは以上です。

*1:他のサービス同様、ロードバランサのセキュリティグループやAClで、分散先のホストへ飛ぶ通信においてはこのポートはふさいでおこう、といった個別のリクエストの内容とは関係ないネットワークの経路に関する設定はできます

*2:https://aws.amazon.com/jp/elasticloadbalancing/pricing/

*3:あとで触れますが、ALBのリスナーのリスナールールによってパスごとに転送するターゲットグループを指定することができます https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/load-balancer-listeners.html#listener-rules 。 CLBのリスナーには同等の設定はありません https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/classic/using-elb-listenerconfig-quickref.html

*4:具体的にはinstanceIDの指定により追加 https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/classic/elb-listener-config.html

*5:https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/classic/elb-listener-config.html CLBでリスナーを設定するフォームを確認してみてもそんな感じ https://cdn-ak.f.st-hatena.com/images/fotolife/w/woshidan/20180805/20180805202909.png

*6:リスナーとかいろんな設定を頑張ればいけるかもしれないが、たいがいなハックだと思います

*7:実際の設定画面が https://cdn-ak.f.st-hatena.com/images/fotolife/w/woshidan/20180805/20180805211910.png

AWSのAuto Scalingグループの概要について(概念に関する用語とスケーリングポリシーの種類)

今週はAuto Scalingグループの概要について調べました。

Auto Scalingグループ

EC2 AutoScaling と Auto Scalingグループ はなんのためにあるのか

EC2のインスタンスは起動時間に対して料金が請求される従量制の課金形式となっています。 なので、1日の負荷のピークなど必要なときは多くの台数に、必要ないときは少ない台数にしておけるといいですよね。

CloudWatchからのアラートや事前のスケジュール設定をもとにEC2の台数を増減してくれるのがEC2 AutoScalingです*1

EC2 AutoScaling はプログラムにより自動的にEC2のインスタンスを立ち上げるので、

  • 起動したい数、最小数、最大数
  • どういったインスタンスを起動するのか
  • どういった条件で台数を増減するのか

といった設定が必要になります。Auto Scaling グループはそれらの設定と台数を増減させるEC2インスタンスの集合を管理する単位です。

利用するために必要なものやその呼び方

上の説明の段階でもふれたのですが、Auto Scalingグループは基本的に負荷に応じたEC2の台数増減をプログラムにやってもらおうという趣旨のものなので

  • 起動したい数、最小数、最大数
  • どういったインスタンスを起動するのか
  • どういった条件で台数を増減するのか

を事前に「Auto Scalingグループ」のインスタンスに設定しておく必要があります。

起動したい数、最小数、最大数」はAuto Scalingグループを作成する際、「EC2インスタンスの希望する数(desired count), 最小数, 最大数」としてAuto Scalingグループ自身の属性として設定します。

どういったインスタンスを起動するのか」については、

など自分たちがEC2インスタンスを手動で起動する際にも必要となるパラメータをまとめた「起動テンプレート*2を作成し、どの起動テンプレートを利用するかをAuto Scalingグループの属性として指定します。

どういった条件で台数を増減するのか」については、条件の種別に応じて設定する項目が異なります。

増減を行わず一定の台数を維持したい場合*3は、「EC2インスタンスの希望する数(desired count), 最小数, 最大数」の設定で可能です。

午後7時にテレビで紹介されるから暖機を含めて午後6時半から起動したい、といった事前にスケーリングしたいタイミングがわかっている場合は、Auto Scalingグループに対して Scheduled Action(スケジュールされたアクション) を作成することで対応できます*4

その時々のグループ内のEC2インスタンスの状態によって台数を追加したり減らしたい場合は、 スケーリングポリシーという単位で設定し、これをAuto Scalingグループのインスタンスに追加します。

スケーリングポリシーは、スケールイン用とスケールアウト用に別個に作成する必要があります。

EC2 Auto Scalingのスケーリングポリシーには

  • Target tracking scaling
  • Step scaling
  • Simple scaling

の3種類があります*5。少しスケーリングポリシーの概要についてもふれておきましょう。

EC2 Auto Scalingのスケーリングポリシー

3種類のスケーリングポリシーの中で一番古くからあるのは、Simple Scalingです。Step Scalingが次に出て来ました。 この二つの違いですが、

  • Simple Scalingはアラートをうけてスケーリング中に追加のアラートを受け取ることができませんが、
  • Step Scalingは追加のアラートを受け取ることができます*6

また、この二つのポリシーではスケーリングの値のために監視しているメトリクスの値によって、一度に増やす台数を調整するためのステップ調整値*7があります。

  • Simple Scalingはステップ調整値として1つの値しかスケーリングポリシーに設定できませんが
  • Step Scalingは複数のステップ調整値をスケーリングポリシーに設定できます

これがどういうことかというと、ステップ調整値は、スケーリングの際にいまの台数から何台調整するかというのを調整値とすると、調整値を適用する範囲と適用する調整値そのものの組からなります。

ステップ調整値が一つしかないSimple Scalingの場合、一つのステップ調整値でアラートが来る全部の範囲を賄う必要があるので

  • アラートが来たら常にN台増やして/減らして
    • CPU利用率が51%のときも99%のときも10台ずつ増やして、的な

となってしまうのですが、Step Scalingの場合、

  • メトリクス値が50以上60より小さいとき 下限: 0, 上限: 10, 調整: 0
  • メトリクス値が60以上70より小さいとき 下限: 10, 上限: 20, 調整: 10
  • メトリクス値が70以上のとき 下限: 20, 上限: null, 調整: 30

のように複数ステップ調整値が設定できて、たとえばこのように設定した場合、

  • アラートが来ているといっても、メトリクスが60より小さいときはまだ見守るだけでdesired countを増やしたりしない
  • メトリクスが60になった場合、現在の台数より10%追加(PercentChangeInCapacity の場合 *8 )
  • さらにメトリクスが70にまでなった場合、11台の30%(PercentChangeInCapacity の場合)、3台を追加

といった具合に、メトリクスの値による増減台数の調整が可能となります。また、 PercentChangeInCapacity という単語が出て来ましたが、増減させる時の台数をどうやって計算するか(今の台数基準/絶対値など)の方法を スケーリング調整タイプ という項目で設定します。

さて、最後に出てきたスケーリングポリシーが、Target tracking scalingです。

Target tracking scalingの場合は、メトリクス値がいくつのときに何台増やすか、ではなく、ターゲット値として指定したメトリクスが指定された値になるように自動的にスケーリング調整値を計算する、といったもので、ステップ調整値の設定が簡素化できるようです*9

現場からは以上です。

いままでのECSの起動モード(EC2起動モード)のネットワーク設定(bridge ネットワークモード)とFargate起動モードを利用した場合のネットワーク設定関連で嬉しいことについて

2017年11月14日にAWSAmazon ECSのTask Networkingを発表*1し、その恩恵を受ける Fargate 起動タイプが2018年7月4日に東京リージョンでサービス開始となりました*2

自分の職場でも一部サービスでFargate(with awsvpcモード)の採用が始まったのをレビューで見たのはいいものの、いままでのECSのネットワークモードとの違いがよくわからず、最初のPRについては「俺を信じるお前を信じろ」になってしまったため、ドキュメント見て少し調べたメモです。

目次

  • いままでのECSの起動モード( EC2 )のネットワーク設定(bridge ネットワークモード)について
    • ECSが利用しているEC2のネットワーク設定
    • Dockerのデフォルトの bridge ネットワークモードをECSで利用する場合にタスクのネットワークに関して起きること
    • ECSでEC2起動モードで利用するDockerのデフォルトの bridge ネットワークモードを利用することで起きる制限
  • Fargate起動モードで利用する awsvpc ネットワークモードについて
    • awsvpc ネットワークモードでは、 ecs-agent が利用するCNIプラグインを利用してタスクにENIをアタッチする
    • awsvpc ネットワークモードの利用で嬉しいこと
    • ECSで awsvpc ネットワークモードを利用する場合の制限の話
  • 参考

いままでのECSの起動モード( EC2 )のネットワーク設定(bridge ネットワークモード)について

実は、 Fargate 起動モードや awsvpc ネットワークモード以前に、既存の EC2 起動モードや EC2 起動モードの際に前提とされてる気がするDockerのデフォルトの bridge ネットワークモードについてよく知らなかったのでそこから確認しました。

ECSが利用しているEC2のネットワーク設定

ECSのコンテナインスタンスはかなり大雑把にいうと ecs-agent が動作し、ECSに登録されたEC2インスタンス*3のことを指しますが、ECSのネットワークに関係する設定はEC2のネットワーク設定の方法に依存しています。

なので、最初にEC2のネットワーク周りのことをおさらいしました。

EC2を起動するには、AWS ドキュメント » Amazon Elastic File System (EFS) » ユーザーガイド » Amazon Elastic File System の開始方法 » ステップ 1: EC2 リソースを作成し EC2 インスタンスを起動するに記載されている通りVPCの利用が前提となっています。

そのVPC内のEC2インスタンスは自分たちでsshした後にコマンドを叩かなくても、VPCのルーティングテーブルに設定が登録されており、VPC内の他のインスタンス + 外部エンドポイントにアクセス可能なわけですが、これを実現しているのがENI(Elastic Network Interface)と呼ばれるVPC内の仮想的なネットワーク・インタフェースとなっています。

ENIはすべてのEC2インスタンスに1つ以上アサインされ、EC2インスタンスの起動時に自動的にアサインされる1つのENIを プライマリネットワークインタフェース と呼びます。

EC2インスタンスのネットワーク設定にあたる各種属性( IPアドレスやセキュリティグループなど )は、実際はEC2インスタンスアサインされているENIに設定される属性です。

Dockerのデフォルトの bridge ネットワークモードをECSで利用する場合にタスクのネットワークに関して起きること

ECSではいままでDocker側で用意されている3種類のネットワークモード*4が利用できました。

https://aws.amazon.com/jp/blogs/news/introducing-cloud-native-networking-for-ecs-containers/ では、そのうち、bridgeモードについて説明されていたので、この記事でもそれに沿って考えることにします。

ECSでは、同じタスク定義のコンテナ一式は同じコンテナインスタンスの中で動作し、さらに1つのコンテナインスタンスの中で複数のタスクが動くことがあります*5

このとき、ECSのコンテナとして動くコンテナの間で利用するネットワークインタフェースは共通( docker0 のブリッジが接続されているインタフェース)なので、コンテナたちはすべて同じネットワークインタフェースを経由して通信を行い*6、ENIとそれに伴うネットワーク設定も共有します。

また、コンテナ同士で外部からアクセスされる際に経由するネットワークインタフェースを共有するので、外部から来た通信をそれぞれのコンテナに接続する際に同じ定義のタスクでも違うポート番号を割り振らなければいけないことに関するややこしさがありました。

ECSでEC2起動モードで利用するDockerのデフォルトの bridge ネットワークモードを利用することで起きる制限

前節で述べたことをもとに考えると、これまでのECSのネットワーク設定では

同じコンテナインスタンス上のコンテナはすべて同じENI(の裏のネットワークインタフェース)を経由する前提なので

  • 比較的ネットワークリソースの干渉が起きやすく、複数のコンテナが1つのホストに配置された場合、ネットワークが詰まってパフォーマンスが出ない場合もありそう
  • また、同じ定義のタスクが同じコンテナインスタンスで動く際のネットワーク設定がややこしかった
    • awsvpc ネットワークモードではタスク定義にポート番号とプロトコルを書くだけで完了*7ですが、 bridge 起動モードなどではポートマッピングなどの設定が必要だった*8

同じホスト上のコンテナはすべて同じENI(に紐づいたネットワーク設定)を利用する前提なので

  • セキュリティに気を使うコンテナとそうでないコンテナの設定が一緒くたになってしまう

といった問題が発生していたみたいです。

Fargate起動モードで利用する awsvpc ネットワークモードについて

EC2起動モードの際に利用するネットワークモードについて確認したところで新しい awsvpc ネットワークモードについて確認します。

awsvpc ネットワークモードでは、 ecs-agent が利用するCNIプラグインを利用してタスクにENIをアタッチする

ネットワークの名前空間には名前空間ごとにルーティングテーブルが用意できるだけでなく、ネットワークデバイスを追加することも可能*9ですが、 awsvpc ネットワークモードでは、タスクごとにネットワークの名前空間を作成し、ECSのコンテナインスタンス上にある、ホストマシンのプライマリネットワークインタフェース以外のENIをそのタスク用の名前空間に追加します*10

つまり、 awsvpc ネットワークモードではタスクごとに専用のネットワークインタフェース(ENI)が用意されます。

一方、bridge ネットワークモードの時は、ECSのコンテナインスタンス上にあるホストマシンのプライマリネットワークインタフェースにあたるネットワークデバイスしか利用してなかった*11、つまり、同じホストで動くコンテナ全体で1つのENI*12しか用意されていませんでした。

awsvpc ネットワークモードの利用で嬉しいこと

タスクごとに専用のENIを用意すると何が嬉しいのでしょうか。

ENIにネットワーク設定がアサインされているので、タスクごとにネットワーク設定ができるようになり、1つのコンテナインスタンスで複数同じタスクが動く場合の設定のややこしさも解消されました。

さらに、同じホストで動くタスクの間でネットワークリソースの干渉が減少し、ネットワークで詰まっていたアプリケーションがあったとしたら、ネットワークモードの変更だけでパフォーマンスが改善する可能性があります。

また、ENI(いままでのEC2のネットワーク関係の設定や追跡機能が紐づいているもの)とタスクが1対1で対応しているので、タスクごとの通信内容がVPCフローログなどで既存のEC2のネットワーキング機能を利用して確認できるようになります。

ECSで awsvpc ネットワークモードを利用する場合の制限の話

今までのネットワークモードより便利そうな点が多い awsvpc ネットワークモードですが、タスクごとにENIを割り当てることに関連していると思しき制限もあります。 この記事では、その中の印象的だったもの*13をメモして〆たいと思います。

  • いままで利用していた link 設定が利用できない
    • 代わりに、同じホストで複数台同じ番号で動いてもいいことになったポート番号を使う*14*15
  • タスクごとにネットワークインタフェースをアタッチしているので、クラスタで利用しているEC2インスタンスインスタンスタイプで設定されている ENIの数 - 1 以上にタスクをコンテナインスタンスに配置できない
  • タスクごとにネットワークの名前空間を作成し、ECSのコンテナインスタンス上にある、ホストマシンのプライマリネットワークインタフェース以外のENIをそのタスク用の名前空間に追加する の部分を ecs-agentawsvpc モード用に用意されたCNIプラグイン達を通して行う*16からか、手動で awsvpc ネットワークモードのタスクが動くコンテナインスタンスに対して ENIのアタッチをしていて、他に余っているENIがなければ、 awsvpc ネットワークモードのタスクは PROVISIONING => DEPROVISIONING => STOPPED に移行して動かない

制限やEC2起動モードとの違いの詳細には、 https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task-networking.html を確認してください。

参考

この記事を書き始める前に重点的に読んだもの。他の参考記事は注釈部に。

VPCとALBについてもう少し詳しくなりたいけど、業務的に火急なのはElasticCacheで自分は何をやっているんだ...、現場からは以上です。

*1:https://aws.amazon.com/jp/blogs/news/introducing-cloud-native-networking-for-ecs-containers/

*2:https://aws.amazon.com/jp/blogs/news/aws-fargate-tokyo/

*3:https://aws.amazon.com/jp/blogs/news/under-the-hood-task-networking-for-amazon-ecs/ の図など

*4:https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/create-task-definition.htmlhttps://docs.docker.com/engine/reference/run/#network-settings より

*5:http://www.slideshare.net/AmazonWebServicesJapan/aws-blackbelt-2015-ecs の20ページとかみたいに

*6:https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_definition_parameters.html より今まで使えた設定でもhost ネットワークモードを使えば話は違うみたいですが、たぶん設定がややこしかったのでは

*7:https://aws.amazon.com/jp/blogs/news/introducing-cloud-native-networking-for-ecs-containers/

*8:http://www.slideshare.net/AmazonWebServicesJapan/aws-blackbelt-2015-ecs 69~70ページあたりで2015年時点のその辺の様子を確認できます。ポート番号が被らないように発展編の設定を使ったり、同一Taskは一つのコンテナインスタンスに一つまで、と制限したり

*9:https://docs.openstack.org/liberty/ja/networking-guide/intro-network-namespaces.html

*10:https://aws.amazon.com/jp/blogs/news/under-the-hood-task-networking-for-amazon-ecs/

*11:https://aws.amazon.com/jp/blogs/news/introducing-cloud-native-networking-for-ecs-containers/ の 「コンテナはこのブリッジを利用し、それが実行されているインスタンスのプライマリネットワークインタフェースを使う」 から

*12:ENIは仮想的なインタフェースで論理的に分割されている可能性もあるんでしょうが、インスタンスタイプによってENIの数変わるし、物理的にもある程度そのような気はする...

*13:主にレビューの時に「へぇ」といったり、個人的にトラブルシューティングで使いそうな目処がついたもの

*14:https://qiita.com/taishin/items/84122ad85bedf8b8a682

*15:上の記事とは関係ないんですが、もともとlinkはポート番号が使えないから利用したい機能でポート番号がアプリケーションに対応した番号で固定で使えるならいらないでしょ + アプリケーションが設定しているポートを見て設定するようになるとELBやセキュリティグループの設定で混乱がなくなる、みたいな考えでもあるのか?

*16:https://aws.amazon.com/jp/blogs/news/under-the-hood-task-networking-for-amazon-ecs/

AWS SQSのvisibility timeoutとdelay queueのdelayの違いについて

今週の復習です。

consumerがキューからメッセージを受信するリクエストを送った後、

  • 通信の問題で結局受け取れなかった
  • consumer側で処理中にエラーになって結局最後まで処理できなかった

などの事態に備えるため、SQSのキューにあるメッセージはconsumerが受け取った時点では消えない。

consumerはメッセージを受信して処理した後、キューに対してメッセージ削除のリクエストを送る必要がある。

また、

  • SQSのキューにある1つのメッセージを複数のconsumerが受信してもおかしくない

ので、

  • SQSのキューの仕組みとして: キュー上のあるメッセージに受信リクエストが来た後、指定の秒数の間は他のconsumerにそのキューを見せない visiblity timeout を設定する*1
  • 実装側として: 複数のconsumerが同じメッセージを処理しても問題ない実装にしておく

ということを考える必要がある。 visiblity timeout はデフォルトだと 30秒の設定となっている*2

visiblity timeout を利用している場合、SQSのキューに溜まっているメッセージの数は

  • ApproximateNumberOfMessagesNotVisible (あるconsumerがそのメッセージを処理しているかも)
  • ApproximateNumberOfMessagesVisible (どのconsumerもまだそのメッセージを処理していないかも *3 )

の合計となる。

上記がSQSのふつうのキューを使った場合で、キューを使った非同期で動くシステムの中には、キューにメッセージを送った直後はそのメッセージを消費して欲しくない、という場合がある*4。その場合は、delay queueを利用する。

SQS delay queueを利用した場合、

  • standard queueでキューから取り出そうとしたメッセージに対して visiblity timeout の設定を利用した場合: あるconsumerが受信リクエストを送った後、指定の秒数他のconsumerからメッセージが取得できなくなる

のに対し

  • delay queue に追加されたメッセージの場合: producer からメッセージをキューに追加された後、delayで指定した秒数consumerからメッセージが取得できなくなる

の違いがある。また、

  • visiblity timeout の設定はキューに詰めるメッセージに個別に指定
  • delay queueのdelayの設定はキューに対して指定(=>キューに入るメッセージ全体に適用)

される。

delay queueにおいて、全体に適用されるdelayの設定は固定ではなく、メッセージによってはdelayの値を変えたい、といった場合は message timer の delay seconds の指定で上書きが可能*5

delay queueのユースケースがわからないので、もやりとしていますが、現場からは以上です。

*1:というより、デフォルトで30秒に設定されている

*2:https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html

*3:これはconsumerがメッセージを処理するのに必要な時間に応じたvisibility_timeoutを設定している想定で、この辺が適切でなかった場合はその限りではない

*4:このケースについて自分では調べててよくわからなかったんですが、sidekiqなどジョブ管理システムのキューにSQSを利用していて、デフォルトではリトライの時にちょっと時間をおいて欲しいとか、短時間のうちにキャンセルのリクエストがくる可能性があるジョブのリクエストを待つとか...? でも、キャンセルのメッセージがリクエストのメッセージと同じconsumerで処理されなければ意味がなくない...?

*5:https://www.slideshare.net/AmazonWebServicesJapan/aws-31275003 の27ページ目

ECSのタスク、サービス、クラスタ、コンテナインスタンスの意味についてまとめました

ECSのタスク、サービス、クラスタ... といった言葉の意味がいまいちよくわからなかったので、それぞれの言葉が指すインスタンスを作成するときに何を設定するかに基づいてまとめたメモです。

もとの説明をいい感じに引用して... とかの時間がなかったのでひとまずポスト。

クラスタ

  • ネットワークとか、リソース管理の仕方(EC2使う or Fargate*1, EC2の調達の仕方)とか*2
  • リソースの管理の仕方 => クラスタに属する EC2 の設定も管理している*3。特定のインスタンスタイプのEC2を決まった台数、特定のネットワーク内に調達することに関心がある*4

ECSのコンテナインスタンス

タスク定義

  • クラスタが所定の台数Dockerの動くEC2のインスタンスを確保してくれているはずなので、そこにdocker-composeで投げるdocker-compose設定ファイル的なもの*6*7
  • docker-composeの内容を書く = その内容は docker-compose 投げた 先のEC2インスタンスがどういう性能を持っていないといけないか知っている
    • タスク配置の制約事項*8 => データベースのコンテナは複数availavility zoneに配置されるようにしてくれませんか? このタスクはちょっとした雑用だけなので安いEC2インスタンスタイプ使ってくれませんか?
    • タスクサイズ*9 => 画像変換処理のタスクはCPU食うのでたくさんCPU持ってるEC2インスタンスで実行するようにしてくれませんか?
    • サービスを利用してタスクの実行スケジュールを管理することで、並行していくつ稼働するのか、といった管理も可能*10

ECSタスクのスケジューリング *11

  • 長期実行するステートレスサービスおよびアプリケーション用(要するにrailsのフロントとか)を常に指定した台数起動したい => サービススケジューラを使う
    • サービススケジューラがタスクの実行台数を管理する単位がサービス
    • service taskはサービスで管理されてるタスク
    • タスク = docker-composeで動く一連のコンテナの単位 の数をスケーリング != EC2インスタンスの数(こっちに対して課金)をスケーリング
  • その場で実行する => 単発で実行されるバッチジョブとか RunTask

*1:この起動タイプはタスクを作成するときにも設定できる

*2:https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ECS_clusters.html#cluster_concepts

*3:https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/create_cluster.html

*4:https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/scale_cluster.html 2015 年 11月24日以降に作成したクラスターなら、 AWS CloudFormation スタックに関連付けた Auto Scaling グループを作成すると管理画面から何台起動させるか、といったことが変更できる

*5:https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ECS_agent.html AMIで指定する単位だからほぼEC2だろう

*6:クラスタが何台分、と決めている枠の中でタスクを割り振る。クラスタの中にコンテナインスタスタンスが5台起動していて、タスクが3つなら、そのタスクで定義されたプロセスは3つしか動かないし、逆にクラスタの中にコンテナインスタンスが3台でタスクが5つなら複数のタスクが動くコンテナインスタンスが出てくる?

*7:同じタスク定義の中で定義されているコンテナは必ず同じインスタンスで動く http://www.slideshare.net/AmazonWebServicesJapan/aws-blackbelt-2015-ecs の36ページ

*8:https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_definition_parameters.html#constraints (インスタンスタイプの指定はdockerで元になるOSが入ったimageを指定イメージに近い)

*9:https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_definition_parameters.html#task_size

*10:https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs_services.html

*11:https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs_services.html