2018年4月30日月曜日

terraform + ECSを試す


terraform と ECS の学習のため、
下図のAWS環境を terraform で構築してみました。



ECSで試したかったことは以下のとおり。
  • EC2インスタンス1台でマイクロサービスのコンテナを4つ(backend,user,auth,worker)動かす。
  • スケールアウトできるように1サービス:1コンテナ(1タスク)にし、コンテナには、ALB経由でアクセスする。
  • workerコンテナは、awsvpc にして、プライベートサブネットに入れる。
  • SSMでEC2インスタンスの docker コマンドを実行する。
  • コンテナのログをCloudWatchLogsで収集する。
  • Route53にALBをDNS(インターナル)登録する。

実施した構築作業と動作確認の流れを、備忘録として、以下に記述します。

Dockerイメージの準備


まず、テスト用のマイクロサービスとして、下記のDockerイメージを2つ用意しました。
  • blue21/micro.backend
    backendコンテナで使用し、user,auth,workerにアクセスしてレスポンスを表示します。
  • blue21/micro.workers
    user, auth, worker コンテナで使用し、コンテナのIPアドレスを返します。
Dockerイメージは、ローカル環境(VirtualBoxのCentOS7)の docker-compose でビルドと動作確認を行いました。
今回作成した docker-compose のソースは、以下の Bitbucketからダウンロードできます。

上記ソースを使用したDockerイメージ準備作業の流れは、以下のとおり。
最終的に、DockerHUBにイメージを登録します。
イメージのサイズがECRの無料使用枠に収まらないと思ったので、DockerHUBに登録しました。

1. マイクロサービスの Docker イメージをビルドする。


docker-compose でイメージを2つ作成します。
# docker-compose build
# docker images
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
blue21/micro.workers     latest              88fa4a7edec0        2 days ago          261MB
blue21/micro.backend     latest              cc75da278726        2 days ago          297MB

2. ローカル環境でDockerイメージの動作確認


docker-composeで コンテナを4つ起動して、動作確認します。
# docker-compose up -d
# docker-compose ps
 Name        Command       State           Ports
---------------------------------------------------------
auth      python3 app.py   Up      80/tcp
backend   python3 app.py   Up      0.0.0.0:5000->5000/tcp
user      python3 app.py   Up      80/tcp
worker    python3 app.py   Up      80/tcp
# curl http://localhost:5000
<!DOCTYPE html>
<html>
<head>
  <title>[demo] マイクロサービス</title>
</head>
<body>
  <h1>[demo] マイクロサービス</h1>

  <ul>

    <li><h2>worker</h2><strong>Service Response:</strong> こんにちは、worker です. Host は、worker.dev.blue21.local(172.18.0.4) です.
    </li>

    <li><h2>user</h2><strong>Service Response:</strong> こんにちは、user です. Host は、user.dev.blue21.local(172.18.0.2) です.
    </li>

    <li><h2>auth</h2><strong>Service Response:</strong> こんにちは、auth です. Host は、auth.dev.blue21.local(172.18.0.3) です.
    </li>

  </ul>

</body>

3. DockerイメージをDockerHUB に登録


Dockerイメージを DockerHUB の自分のアカウントに登録します。
# docker login
# docker push blue21/micro.backend:latest
# docker push blue21/micro.workers:latest

今回は、無償のパブリックなレポジトリとして登録したので、下記で参照できます。
https://hub.docker.com/u/blue21/

AWS環境構築


terraform でAWS環境を構築します。
terraform コマンドは、dockerコンテナを使用しました。(前回の記事を参照)

今回作成したソースは、以下の Bitbucket で参照できます。
https://bitbucket.org/blue21/aws-sample-ecs/src/master/

上記ソースでは、terraform の workspace を利用して 開発環境と本番環境を構築できるように考慮していますが、本番環境構築の動作確認はしていません。
また、terra.sh というシェルを用意して、dockerコンテナの terraform コマンドを実行しています。

上記ソースを使用した構築の流れは以下のとおり。
今回は、開発環境(workspace=dev)を構築します。ちなみに。本番環境の場合は workspace = prod になります。

1. envfile の準備


envfile にアクセスキー、シークレットアクセスキーを定義します。
今回は、Adminstrator 権限を持ったキーを使用しました。
AWS_ACCESS_KEY_ID=<アクセスキー>
AWS_SECRET_ACCESS_KEY=<シークレットアクセスキー>
envfile は terra.sh で使用し、/root/conf/envfile に配置します。

2. common.tfvars の準備


使用するリージョンを定義します。
今回は us-east-1 を使用するので、ECSで使用する EC2インスタンス用のAMIは、us-east-1 用です。
myip は、セキュリティグループの定義で使用します。myip に定義したIPアドレスのみEC2インスタンスへのSSH接続を許可します。
/*
 * COMMON VARIABLES
 *
 */

common = {
    # default
    default.region     = "us-east-1"         # リージョン
    default.project    = "demo"              # プロジェクト名
    default.myip       = "110.165.221.254"   # 管理者IP
    default.zone_name  = "blue21.local"      # DNS
}

3. terraform で AWSに network環境を作成


下記のコマンドでネットワーク環境を構築します。
VPC、サブネット、IGW、ルートテーブル、セキュリティグループを作成します。
# ./terra.sh 10.network init
# ./terra.sh 10.network workspace new dev
# ./terra.sh 10.network plan
# ./terra.sh 10.network apply

terraformで作成したリソースを、AWSコンソールで確認すると下図のようになります。

[VPC]

[サブネット]

[ルートテーブル]

[IGW]

[セキュリティグループ]

4. terraform で AWSに IAMロールを作成


下記のコマンドでIAMロールを作成します。
# ./terra.sh 20.iam init
# ./terra.sh 20.iam workspace new dev
# ./terra.sh 20.iam plan
# ./terra.sh 20.iam apply

terraformで作成したリソースを、AWSコンソールで確認すると下図のようになります。

[ロール]

4. terraform で AWSに ALBを作成


下記のコマンドでALBを作成します。
# ./terra.sh 30.ec2 init
# ./terra.sh 30.ec2 workspace new dev
# ./terra.sh 30.ec2 plan
# ./terra.sh 30.ec2 apply

terraformで作成したリソースを、AWSコンソールで確認すると下図のようになります。

[ターゲット]

[ロードバランサ]

7. terraform で AWSに ECSを作成


下記のコマンドでECSを作成します。
ここで作成するEC2インスタンスのキーペアは、"virginia_key" という名前にしています。
また、EC2インスタンスには、userdata を使用して、SSMエージェントをインストールしています。
# ./terra.sh 40.ecs init
# ./terra.sh 40.ecs workspace new dev
# ./terra.sh 40.ecs plan
# ./terra.sh 40.ecs apply

terraformで作成したリソースを、AWSコンソールで確認すると下図のようになります。

[EC2インスタンス]
ENIが2つ作成され、プライベートIPが2つ割当てられます。1つは、ネットワークタイプを "awsvpc" にした worker コンテナ用です。

[ECS: タスク定義]

[ECS: クラスタ]

[ECS: サービス]

[ECS: タスク]

[ECS: ECSインスタンス]

8. terraform で AWSに Route53 を作成


下記のコマンドでRoute53を作成します。
VPC内部で使用可能なインターナルなゾーンを作成します。
# ./terra.sh 50.r53 init
# ./terra.sh 50.r53 workspace new dev
# ./terra.sh 50.r53 plan
# ./terra.sh 50.r53 apply

terraformで作成したリソースを、AWSコンソールで確認すると下図のようになります。

[ゾーン]

[レコードセット]

動作確認


上記でAWS環境ができたので、いろいろ試します。

マイクロサービス


AWSの外側からALBにアクセスしてみます。
成功すると、以下のように各コンテナのIPアドレスが表示されます。
[root@centos701 ~]# curl http://demo-dev-alb-1527439969.us-east-1.elb.amazonaws.com
<!DOCTYPE html>
<html>
<head>
  <title>[demo] マイクロサービス</title>
</head>
<body>
  <h1>[demo] マイクロサービス</h1>

  <ul>

    <li><h2>worker</h2><strong>Service Response:</strong> こんにちは、worker です. Host は、worker.dev.blue21.local(10.0.10.104) です.
    </li>

    <li><h2>user</h2><strong>Service Response:</strong> こんにちは、user です. Host は、user.dev.blue21.local(172.17.0.3) です.
    </li>

    <li><h2>auth</h2><strong>Service Response:</strong> こんにちは、auth です. Host は、auth.dev.blue21.local(172.17.0.2) です.
    </li>

  </ul>

</body>
</html>[root@centos701 ~]#

こんどは、HTTPヘッダでホスト名を worker に変更して、 ALBにアクセスしてみます。
成功すると以下のように workerコンテナのIPアドレスが表示されます。
[root@centos701 ~]# curl -H "Host: worker.dev.blue21.local" http://demo-dev-alb-1527439969.us-east-1.elb.amazonaws.com
こんにちは、worker です. Host は、worker.dev.blue21.local(10.0.10.104) です.[root@centos701 ~]#

CloudWatchLogs


AWSコンソールで CLoudWatchLogs を参照すると、下図のとおり。
コンテナのログを参照できます。

[コンテナのログ一覧]

[backend のログ(アクセスログ)]

SSMでのコマンド実行


AWSコンソールの[EC2]>[SYSTEM MANAGER SERVICES]でコマンドを実行してみます。
ECS用のEC2インスタンス上で"docker ps"コマンドを実行すると下図のように表示されました。


Route53


VPC内部で、blue21.local のDNS名でマイクロサービスにアクセスしてみます。
ECS用のEC2インスタンスにSSHログインして、以下のように curl コマンドで backend に アクセスしてみました。
成功すると以下のように表示されます。
[ec2-user@ip-10-0-0-4 ~]$ curl http://backend.dev.blue21.local
<!DOCTYPE html>
<html>
<head>
  <title>[demo] マイクロサービス</title>
</head>
<body>
  <h1>[demo] マイクロサービス</h1>

  <ul>

    <li><h2>worker</h2><strong>Service Response:</strong> こんにちは、worker です. Host は、worker.dev.blue21.local(10.0.10.104) です.
    </li>

    <li><h2>user</h2><strong>Service Response:</strong> こんにちは、user です. Host は、user.dev.blue21.local(172.17.0.3) です.
    </li>

    <li><h2>auth</h2><strong>Service Response:</strong> こんにちは、auth です. Host は、auth.dev.blue21.local(172.17.0.2) です.
    </li>

  </ul>

</body>
</html>[ec2-user@ip-10-0-0-4 ~]$

user,auth,workerコンテナにアクセスすると、成功すれば、以下のよう表示されます。
[ec2-user@ip-10-0-0-4 ~]$ curl http://user.dev.blue21.local
こんにちは、user です. Host は、user.dev.blue21.local(172.17.0.3) です.[ec2-user@ip-10-0-0-4 ~]$
[ec2-user@ip-10-0-0-4 ~]$ curl http://auth.dev.blue21.local
こんにちは、auth です. Host は、auth.dev.blue21.local(172.17.0.2) です.[ec2-user@ip-10-0-0-4 ~]$
[ec2-user@ip-10-0-0-4 ~]$ curl http://worker.dev.blue21.local
こんにちは、worker です. Host は、worker.dev.blue21.local(10.0.10.104) です.[ec2-user@ip-10-0-0-4 ~]$
[ec2-user@ip-10-0-0-4 ~]$

コンテナのスケールアウト


user コンテナを手動でスケールアウトしてみます。
modules/ecs/main.tf の 下記 "desired_count" を 2 に修正して、user コンテナを2つ起動するようにソースを変更します。
resource "aws_ecs_service" "user" {
  name                  = "${local.name_prefix}-user"
  cluster               = "${aws_ecs_cluster.default.id}"
  task_definition       = "${aws_ecs_task_definition.user.arn}"
  desired_count         = 2
  health_check_grace_period_seconds = 30
  iam_role              = "${var.m-role-ecs.["ecs-service-role-arn"]}"

  load_balancer {
    target_group_arn    = "${var.m-alb.["target-user-id"]}"
    container_name      = "user"
    container_port      = 80
  }
}

terraform で上記の変更を反映します。
# ./terra.sh 40.ecs apply

AWSコンソールで変更結果を確認すると下図のとおり。

[userサービスのコンテナが2つある]

[ALBの user用ターゲットに、コンテナが追加されている]

[CloudwatchLogs が追加されている]

マイクロサービスにアクセスすると、以下のように user のIPアドレスが変わります。
# curl http://demo-dev-alb-1527439969.us-east-1.elb.amazonaws.com
<!DOCTYPE html>
<html>
<head>
  <title>[demo] マイクロサービス</title>
</head>
<body>
  <h1>[demo] マイクロサービス</h1>

  <ul>

    <li><h2>worker</h2><strong>Service Response:</strong> こんにちは、worker です. Host は、worker.dev.blue21.local(10.0.10.104) です.
    </li>

    <li><h2>user</h2><strong>Service Response:</strong> こんにちは、user です. Host は、user.dev.blue21.local(172.17.0.3) です.
    </li>

    <li><h2>auth</h2><strong>Service Response:</strong> こんにちは、auth です. Host は、auth.dev.blue21.local(172.17.0.2) です.
    </li>

  </ul>

</body>
</html>
下記は、user コンテナのIPアドレスが上記と異なっているので、ALBで振り分けできてることがわかります。
# curl http://demo-dev-alb-1527439969.us-east-1.elb.amazonaws.com
<!DOCTYPE html>
<html>
<head>
  <title>[demo] マイクロサービス</title>
</head>
<body>
  <h1>[demo] マイクロサービス</h1>

  <ul>

    <li><h2>worker</h2><strong>Service Response:</strong> こんにちは、worker です. Host は、worker.dev.blue21.local(10.0.10.104) です.
    </li>

    <li><h2>user</h2><strong>Service Response:</strong> こんにちは、user です. Host は、user.dev.blue21.local(172.17.0.5) です.
    </li>

    <li><h2>auth</h2><strong>Service Response:</strong> こんにちは、auth です. Host は、auth.dev.blue21.local(172.17.0.2) です.
    </li>

  </ul>

</body>
</html>

あとかたずけ


遊び終わったら、上記で作成したAWS環境を、以下の順番で削除します。お金がもったいないので。
# ./terra.sh 50.r53 destroy
# ./terra.sh 40.ecs destroy
# ./terra.sh 30.ec2 destroy
# ./terra.sh 20.iam destroy
# ./terra.sh 10.network destroy

ちなみに、夜などAWSをさわらないときは、
お金のかからない network, iam だけ残して、ec2, ecs, r53 は削除し、検証時に毎回、ec2, ecs, r53 を作成してました。