ようへいの日々精進XP

よかろうもん

docker コマンドで ECS クラスタにデプロイ出来るようになった (beta 版) ので試してみたけど挫折した

tl;dr

AWS より以下のようなブログがポストされました.

aws.amazon.com

合わせて, Docker でも同じようなブログがポストされていました.

www.docker.com

英語は読めませんが, ざっくりと読んだ限りだと docker-compose.yml があれば, docker に ecs コマンドが追加されて, 以下のように, docker-compose up する感覚で, ローカル PC からダイレクトに ECS にアプリケーションが出来るようになったようなので, 試してみました.

$ docker ecs compose up

尚, この機能は現時点では, Public Beta 版として公開されており, 先述のブログ記事でも Edge バージョンの Docker for Mac を利用して確認が可能ということですので, Edge バージョンを利用して動作確認しています. そのため, 開発の状況によって, 記事の内容とは異なる結果が得られることがあるかと思いますのでご注意下さい. また, 記事内に誤りや言葉の誤用等もあるかもしれませんので合わせてご留意下さい.

以下, 動作確認環境の情報です.

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.5
BuildVersion:   19F101

$ docker version
Client: Docker Engine - Community
 Azure integration  0.1.7
 Version:           19.03.12
 API version:       1.40
 Go version:        go1.13.10
 Git commit:        48a66213fe
 Built:             Mon Jun 22 15:41:33 2020
 OS/Arch:           darwin/amd64
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          19.03.12
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.10
  Git commit:       48a66213fe
  Built:            Mon Jun 22 15:49:27 2020
  OS/Arch:          linux/amd64
  Experimental:     true
 containerd:
  Version:          v1.2.13
  GitCommit:        7ad184331fa3e55e52b890ea95e65ba581ae3429
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

以下, Docker for Mac の情報です.

f:id:inokara:20200711063859j:plain

チュートリアル

要件

この機能 (以後, 「ECS 連携機能」と書きます) を利用するには, 現時点では Docker for Mac の Edge バージョンが必要になります. また, IAM ユーザー又は IAM Role に以下の権限を付与する必要があるようです.

  • ec2:DescribeSubnets
  • ec2:DescribeVpcs
  • iam:CreateServiceLinkedRole
  • iam:AttachRolePolicy
  • cloudformation:*
  • ecs:*
  • logs:*
  • servicediscovery:*
  • elasticloadbalancing:*

ecs-plugin

ECS 連携機能は, Docker CLIプラグイン ecs-plugin として提供されているようです.

github.com

このリポジトリの example フォルダにサンプルアプリケーションが収められているので, 本記事の中でも利用させて頂きたいと思います. また, example フォルダ内の README に則ってチュートリルを進めます.

尚, example フォルダ内 docker-compose.yml は以下のように書き換えています.

version: "3.8"
services:
  frontend:
    build: app
    # x-aws-pull_credentials: <<<your arn for your secret you can get with docker ecs secret list>>>
    image: ${DOCER_HUB_ACCOUNT}/timestamper
    ports:
      - "5000:5000"
    depends_on:
      - backend
  backend:
    image: redis:alpine

また, Docker Hub にログインしておく必要があります.

$ docker login

尚, ecs-plugin は以下の通り確認出来ます.

$ docker help | grep ecs
  ecs*        Docker ECS (Docker Inc., v1.0.0-beta.1)

$ docker ecs version
Docker ECS plugin v1.0.0-beta.1 (12a47cb)

docker ecs setup

ECS 連携機能を利用する為には, docker ecs setup コマンドを使ってコンテキストを作成する必要があります.

f:id:inokara:20200711065016j:plain

コンテキストを作成後, docker context ls を実行して確認してみます.

$ docker context ls
NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT               KUBERNETES ENDPOINT              ORCHESTRATOR
aws                 aws
default *           moby                Current DOCKER_HOST based configuration   unix:///var/run/docker.sock   https://xxx.xxx.xxx.xxx (default)   swarm

コンテナイメージを Docker Hub にプッシュ

サンプルアプリケーションを一旦, ローカル PC でビルドした後 (docker-compose build), 以下を実行して Docker Hub にサンプルアプリケーションイメージをプッシュします.

$ docker-compose push

コンテキストを切り替える

先述の docker ecs setup で作成した ECS 連携機能用コンテキストに切り替えます.

$ docker context use aws

以下のように出力されます.

$ docker context use aws
aws

ちょっとさみしい出力ですね.

docker ecs compose up

早速, サンプルアプリケーションを起動してみたいと思います.

$ docker ecs compose up

以下のように出力されました.

$ docker ecs compose up
WARN[0000] services.build: unsupported attribute
account has not default VPC

oh... Default VPC が必要になるようです... 万事休すでした. ecs-plugin の実装を見ると, 現時点では, 以下のようにデフォルト VPC のみをサポートするようになっていました.

...
func (s sdk) GetDefaultVPC(ctx context.Context) (string, error) {
    logrus.Debug("Retrieve default VPC")
    vpcs, err := s.EC2.DescribeVpcsWithContext(ctx, &ec2.DescribeVpcsInput{
        Filters: []*ec2.Filter{
            {
                Name:   aws.String("isDefault"),
                Values: []*string{aws.String("true")},
            },
        },
    })
    if err != nil {
        return "", err
    }
    if len(vpcs.Vpcs) == 0 {
        return "", fmt.Errorf("account has not default VPC")
    }
    return *vpcs.Vpcs[0].VpcId, nil
}
...

検証で利用している AWS アカウントには Default VPC が存在しておらず, しかも, 以下のように新たにデフォルト VPC を作ることが出来なかった為, 動作確認はここで断念することにしました. (新しい AWS アカウントを作る気力が無かった...)

$ aws ec2 create-default-vpc

An error occurred (OperationNotPermitted) when calling the CreateDefaultVpc operation: Accounts on the EC2-Classic platform cannot create a default VPC.

雑にコードリーディング

残念ながら

手元の環境では動作を確認することが出来なかったので, ecs-plugin のソースコードをざっくりと読んでみました.

docker ecs compose

docker ecs compose には以下のようなサブコマンドが用意されています.

  • convert
  • up
  • down
  • logs
  • ps

これらは, ここの部分で定義しています. それぞれの機能について, コアな実装は, pkg/amazon/backend 以下にそれぞれ実装されています.

f:id:inokara:20200711092610j:plain

この中から up.go について見てみます.

up.go

up.go はその名の通り, docker ecs compose up を実行した際に呼ばれる関数達が書かれています.

github.com

以下は Up 関数の抜粋です.

func (b *Backend) Up(ctx context.Context, options cli.ProjectOptions) error {
    project, err := cli.ProjectFromOptions(&options)
    if err != nil {
        return err
    }

    if b.Cluster != "" {
        ok, err := b.api.ClusterExists(ctx, b.Cluster)
        if err != nil {
            return err
        }
        if !ok {
            return fmt.Errorf("configured cluster %q does not exist", b.Cluster)
        }
    }

    update, err := b.api.StackExists(ctx, project.Name)
    if err != nil {
        return err
    }
    if update {
        return fmt.Errorf("we do not (yet) support updating an existing CloudFormation stack")
    }

    template, err := b.Convert(project)
    if err != nil {
        return err
    }

    vpc, err := b.GetVPC(ctx, project)
    if err != nil {
        return err
    }

    subNets, err := b.api.GetSubNets(ctx, vpc)
    if err != nil {
        return err
    }

    lb, err := b.GetLoadBalancer(ctx, project)
    if err != nil {
        return err
    }

    parameters := map[string]string{
        ParameterClusterName:     b.Cluster,
        ParameterVPCId:           vpc,
        ParameterSubnet1Id:       subNets[0],
        ParameterSubnet2Id:       subNets[1],
        ParameterLoadBalancerARN: lb,
    }

    err = b.api.CreateStack(ctx, project.Name, template, parameters)
    if err != nil {
        return err
    }

    fmt.Println()
    w := console.NewProgressWriter()
    for k := range template.Resources {
        w.ResourceEvent(k, "PENDING", "")
    }
    return b.WaitStackCompletion(ctx, project.Name, compose.StackCreate, w)
}

以下のような順番で AWS リソースの操作が動いていることが解ります.

  1. ECS Cluster が存在するか確認
  2. CloudFormation スタックの確認
  3. CloudFormation テンプレートの生成 (後述)
  4. default VPC が存在しているか確認して, 存在していたら VPC ID を返す
  5. サブネット, ロードバランサの情報を取得
  6. 最後に CloudFormation のスタックを作成

そして, Create Stack が完了するまで待機するという感じだと思います. 個人的に興味深いのは, CloudFormation のテンプレートを生成している部分ですので, この部分を処理しているコードも見てみます.

convert.go

以下は, up.go 内で呼ばれている Convert 関数が実行されている convert.go です.

github.com

convert.go の冒頭の抜粋です.

func (b Backend) Convert(project *types.Project) (*cloudformation.Template, error) {
    var checker compatibility.Checker = &FargateCompatibilityChecker{
        compatibility.AllowList{
            Supported: compatibleComposeAttributes,
        },
    }
    compatibility.Check(project, checker)
    for _, err := range checker.Errors() {
        if errdefs.IsIncompatibleError(err) {
            logrus.Error(err.Error())
        } else {
            logrus.Warn(err.Error())
        }
    }
...

返り値の型を見ると, *cloudformation.Template とあるので, CloudFormation テンプレートを返すことが解ります.

Convert 関数の引数の型として, *types.Project という型が指定されていますが, これは, compose-spec/compose-go プロジェクトで実装されている関数を呼び出すことが出来る型です.

github.com

詳しくは理解出来ていませんが, どうやら docker-compose.yml をパースして各 YAML キーの値を取り出すことを目的としているようです.

以上

せっかく, 新しいリリースを試そうと早起きしましたが, 思わぬところで躓いてしまいリリース自体を試すことは出来ませんでしたが, ecs-plugin がどのように実装しているのか, ほんの少しだけ覗くことが出来ました. 実際に試すことは出来ませんでした, docker コマンドから直接 ECS にアプリケーションがデプロイ出来ることで, 開発からデプロイまでを一貫して行えるようになるというのは非常に嬉しいことだと思いますし, 世界中のエンジニアがまち待った機能なのではないでしょうか.

そして, 早く default VPC 以外でも使えるようになることを祈っています. 以下の通り, issue 化されているので, default VPC 以外でも使える日は近いのかなと期待しております.

github.com