20190625のAWSに関する記事は14件です。

[AWS ECS]Fargateのcontainerにシェルで入りたい(sshd無しで!)

[AWS ECS]Fargateのcontainerにシェルで入りたい(sshd無しで!)

ecsのFargateいいですね。コンテナ単位でcpuとかメモリとか指定する必要はあれど、elastic beanstalkや ec2ベースのecsクラスタのようにec2を意識せずにクラスタ組めるのはともていい感じだと思います。

・・・が、ec2を意識しない故に、困ったときのsshアクセスができません。これはトラブルシュート時や開発初期はとても困ります。
かといって、docker containerにsshの口を開けるのもなんとも負けた感がします。

Fargateいいけど、docker execできたらなーと思っていましたが、そんなことはみんなが思っていることのようで、
下記のようなissueが数年前から立っておりました。

How to run "docker exec... " command in ECS

すると最近(2019/5/8)

「SSMつかったら、Fargateコンテナのシェルにアクセスできたよ」

というコメントが出てきました!! (参考: https://github.com/aws/containers-roadmap/issues/187#issuecomment-490347856)

セッションマネージャーでawsコンソール(やaws-cli)から、シェルアクセスできるのは知っていたのですが、ec2が見えないfargateに関しては無理だろうと思い込んでいたのですが、読んでみるとできるようです。

これは来た!!とおもい試してみたところ、いろいろ使いづらいところはありつつも成功したのでメモ代わりに残しておきます。

セッションマネージャー

AWS System managerの セッションマネージャーに関しては下記の記事を参照ください。

SSH不要時代がくるか!?AWS Systems Manager セッションマネージャーがリリースされました!

簡単にいうと、awsのコンソールや、cliの環境から、特定のaws内のec2にシェルアクセスができる、というものです。

インスタンスIDなどが必要なことから、実際に見えるEC2でないとできないのかな?と思いこんでいたのですが、上記のissueのコメントを見てみるとどうもそうではないらしかったので試してみます。

手順

Fargate自体の仕組みや、設定に関するところ全体を書くと、関係しない部分が多くなるため、必要な部分のみ抜粋します。

しくみの概要

詳細手順の前に、どのように(EC2の見えない)Fargateコンテナに対して設定するのかの概要です。

前提:セッションマネージャーには、aws内のec2以外でも、 AWSのコンソールから System Managerにアクティベーション処理を行い、 amazon-ssm-agent を起動しておくことで、 任意の環境のホストをSystem Managerから セッションマネージャーの機能を使える用に登録できる。

  1. これを踏まえて、事前にアクティベーションを登録しておいて、 Activation CodeActivatoin Id を取得(これらに関しては後述)しておく
  2. Fargateで動くコンテナのビルド時にDockerfileで、 amazon-ssm-agent をインストールしておく。
  3. Fargetでコンテナが起動する際に、 上記「1.」の Activation CodeActivation Id を使って、自分のコンテナを System Managerに登録する、かつ、amazon-ssm-agent を引数なしで起動する。

これで、セッションマネージャーにFargateのコンテナが登録されてシェルを起動できるようになります。

しくみの詳細

Sysetem Managerにアクティベーション登録をする

AWS System Manager -> アクティベーションから「アクティベーションの作成」を実行する

スクリーンショット 2019-06-25 9.49.14.png

「インスタンス制限」 というのがあり、一つのアクティベーションに対して、最大何個のホストを登録できるかの設定項目があります。
dockerで運用する場合、コンテナが起動する度に違うホスト扱いになり、コンテナを起動するたびに「登録できる残りコンテナ数」が減っていきます。

コンテナ破棄時に、ここの登録済み数をクリアする事ができれば気にしなくても良さそうなのですが、ちょっと方法がわからず、知っている方がいたら教えてほしい(切実)

(また、やらしいことに、 managed ec2以外を対象にする場合、ここのアクティベーションと登録済みインスタンス数によって課金とかにも関わってくるらしく気にせず作りっぱなしとかにもできなさそう(あまり詳しく調べていない)なのがつらいところです)

このアクティベーションを行うことで、下記のような Activation CodeActivation ID が生成されるのでメモします(一回しか出てこないので注意)

スクリーンショット 2019-06-25 23.07.16.png

エージェントのインストール

Fargateで動かすコンテナに amazon-ssm-agent をインストールする。

上記のissueコメントにあるように、

https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-install-managed-linux.html

で、OS毎に agent のインストール方法がのっているので、これにしたがい Dockerfileに追記します。
今回自分の場合は ubuntu だったため、下記のようになります。

Dockerfile

RUN curl https://s3.ap-northeast-1.amazonaws.com/amazon-ssm-ap-northeast-1/latest/debian_amd64/amazon-ssm-agent.deb -o /tmp/amazon-ssm-agent.deb \
    && dpkg -i /tmp/amazon-ssm-agent.deb \
    && cp /etc/amazon/ssm/seelog.xml.template /etc/amazon/ssm/seelog.xml

コンテナの登録と、セッションマネージャー用エージェントの起動

コンテナの登録

Fargateのコンテナ起動時に、上記アクティベーション時のコードと、IDでコンテナをSSMに登録します。

Fargateコンテナからアクセスする必要がある秘匿情報なので、最近使えるようになった、暗号化されたSSMのパラメータストアから取得するといいと思います。

詳細は、下記参照

【祝!】FargateでもECSにごっつ簡単に環境変数に機密情報を渡せるようになりました! | DevelopersIO

SSM_AGENT_CODESSM_AGENT_IDが、それぞれ上記で、メモした Activation CodeActivation IDです。

amazon-ssm-agent -register -code "${SSM_AGENT_CODE}" -id "${SSM_AGENT_ID}" -region "ap-northeast-1"

エージェントの起動

セッションマネージャーからアクセスする際にエージェントが起動していないといけないのでバックグラウンドで起動しておきます。
この際、awsのドキュメントだと、サービスとして動かしているサンプルばかりなのですが、dockerコンテナなので、サービスいれるのもちょっとなと思って、単純にバックグラウンド起動にしています。 (supervisordとかで起動するのもいいかも)

  # agent 起動
  amazon-ssm-agent &

これで、fargateのコンテナが正常に起動すれば準備OKです!

セッションマネージャーからのアクセス

System Managerのセッションマネージャーからセッションの開始を選択し、今登録されたインスタンスID(amazon-ssm-agent -register のログなどから確認してください)を指定します。

スクリーンショット 2019-06-25 23.19.49.png

セッション開始!

下記のようにシェルのセッションが開始されます。

スクリーンショット 2019-06-25 23.23.14.png

この際ユーザが ssm-user というユーザになっているため、 docker containerが root で実行されているといろいろ実行できません。
(例えば、 bin/rails console など)

sudo可能なユーザなので、 Dockerfileで sudo 入れておいてsudoするのがいいと思います。
・・・が、その際、sudoで実行中のコマンドの環境変数は、Fargateで設定した環境変数が設定されていないため、

sudo -E bin/rails c

と、 sudo に、環境変数を保存する、 -E をつけて実行すると実行できるようになります。

この辺よくわかってないですが、コンテナ上で実行されているユーザはrootで、そこに対して環境変数が設定されているのですが、 セッションマネージャーで、ssm-userとして実行する際は、ssm-userの環境に環境変数が引き継がれているが、 sudoで実行するrootの環境変数には何も設定されていないようです。

掃除

料金体系とかあまり詳しくまだ調べられていないのですが マネージドなEC2に対してセッションマネージャーからアクセスするのと違い、少し違う設定が有効になるようです。

とりあえず、Fargateでこの設定を行う場合は、スポットでアクセスする場合に限り、一通り使った後は登録済みインスタンスや、アクティベーション等は手動で削除しています。

最後に

Fargateで唯一困ってたシェルアクセスに関して、結構安全そうな手段が見つかってよかったです。
ですが、やはり本来は、Fargateの機能としてシェルにアクセスしたい感じですよね。

そんな中、つい最近出ていた、Fargateの責任者の方へのインタビューだそうですが、

AWSのコンテナ責任者が語るFargate、ECS、App Mesh、EKS、そしてKubernetes (1/2):AWS Summit Tokyo 2019で聞いた - @IT

また、FargateコンテナにSSHして、内部の情報を確認できるようにするなども予定している

まさにこの機能ですね!!!
早く実現してほしいものですが、それまではこういう方法も便利かも!という記事でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Terraform で書く CodeBuild -> ECR -> ECS with Vapor その1

Terraform v0.12.1

Swift製 WebFrameWork である Vapor をECS へのデプロイするところまでをTerraformの練習として書きました。
解説記事というよりは個人的な忘備録、メモになります。

Terraform が初めての場合は Terraform 公式のチュートリアルにまず取り掛かる事をお勧めします。
Terraform はバージョンによる差異も大きく、そしてどんどん良いものになっているため、これから始めるのであれば最新版を使うことをお勧めします。

Introduction to Terraform

アプリケーションの作成

まずECS上で動かすためのアプリケーションが必要です。
Vapor のテンプレートをアプリケーションとして使用します。
今回は以下のような Dockerfile を作成し、この Dockerfile により作られるイメージをECS で動かすことを目的とします。

https://github.com/O-Junpei/vapor-docker-sample

Dockerfile
FROM ubuntu:16.04

RUN apt-get update && apt-get install -y \
  git \
  vim \
  wget \
  curl
RUN /bin/bash -c "$(wget -qO- https://apt.vapor.sh)"
RUN apt-get install -y \
  swift \
  vapor

RUN vapor new Hello --template=web
WORKDIR /Hello
RUN vapor build

EXPOSE 80

CMD ["swift", "run", "Run", "--hostname", "0.0.0.0", "--port", "80"]

Terraform の設定

次に Terraform から AWS にアクセスできることを確認します。

########################
## Credential Infos
########################
variable "access_key" {
  description = "AWS access key"
}

variable "secret_key" {
  description = "AWS secret access key"
}

variable "region" {
  description = "AWS region to host your network"
}

provider "aws" {
  access_key = "${var.access_key}"
  secret_key = "${var.secret_key}"
  region     = "${var.region}"
}

ECRの作成

CodeBuild で作成したイメージを格納する ECR を作成します。

########################
## ECR
########################
# ECS Repository
resource "aws_ecr_repository" "repository" {
  name = "my-repository"
}

# Repositry Policy
# Permit pull image
resource "aws_ecr_repository_policy" "repository_policy" {
  repository = "${aws_ecr_repository.repository.name}"

  policy = <<EOF
  {
    "Version": "2008-10-17",
    "Statement": [
      {
        "Sid": "new statement",
        "Effect": "Allow",
        "Principal": "*",
        "Action": [
          "ecr:BatchCheckLayerAvailability",
          "ecr:BatchGetImage",
          "ecr:GetDownloadUrlForLayer"
        ]
      }
    ]
  }
EOF
}

CodeBuildProjectの作成

CodeBuildProject を作成します。

########################
## CodeBuild
########################
data "aws_iam_policy_document" "codebuild_assume_role_policy_document" {
  statement {
    sid = "CodebuildExecution"
    actions = ["sts:AssumeRole"]

    principals {
      type = "Service"
      identifiers = ["codebuild.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "codebuild_role" {
  name = "my-codebuild"
  assume_role_policy = "${data.aws_iam_policy_document.codebuild_assume_role_policy_document.json}"
}

data "aws_iam_policy_document" "codebuild_policy_document" {
  statement {
    sid = "Logging"
    actions = [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents",
    ]
    resources = ["*"]
  }

  statement {
    sid = "PushECR"
    actions = [
      "ecr:GetAuthorizationToken",
      "ecr:BatchCheckLayerAvailability",
      "ecr:GetDownloadUrlForLayer",
      "ecr:GetRepositoryPolicy",
      "ecr:DescribeRepositories",
      "ecr:ListImages",
      "ecr:DescribeImages",
      "ecr:BatchGetImage",
      "ecr:InitiateLayerUpload",
      "ecr:UploadLayerPart",
      "ecr:CompleteLayerUpload",
      "ecr:PutImage",
    ]
    resources = ["*"]
  }
}

resource "aws_iam_role_policy" "codebuild_role_policy" {
  role = "${aws_iam_role.codebuild_role.name}"
  policy = "${data.aws_iam_policy_document.codebuild_policy_document.json}"
}

# templete などを別ファイルに書き出すことをおすすめです。
data "template_file" "buildspec_template_file" {
  template = <<EOF
version: 0.2

phases:
  pre_build:
    commands:
      - echo pre_build
      - echo Logging in to Amazon ECR...
      - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)

  build:
    commands:
      - echo build
      - echo build docker image
      - docker build -t vapor:latest .

  post_build:
    commands:
      - echo post_build
      - docker tag vapor:latest ${aws_ecr_repository.repository.repository_url}:latest
      - docker push ${aws_ecr_repository.repository.repository_url}:latest
EOF
}

resource "aws_codebuild_project" "codebuild_project" {
  name          = "build-vapor-image"
  description   = "build vapor image"
  build_timeout = "30"
  service_role  = "${aws_iam_role.codebuild_role.arn}"

  artifacts {
    type = "NO_ARTIFACTS"
  }

  cache {
    type  = "LOCAL"
    modes = ["LOCAL_DOCKER_LAYER_CACHE", "LOCAL_SOURCE_CACHE"]
  }

  environment {
    compute_type                = "BUILD_GENERAL1_SMALL"
    image                       = "aws/codebuild/docker:18.09.0"
    type                        = "LINUX_CONTAINER"
    privileged_mode             = true
    image_pull_credentials_type = "CODEBUILD"
  }

  source {
    type = "GITHUB"
    # プライベートリポジトリを使う場合はOAuthToken を設定する
    # パラメーターストアに保存し、取ってくるのが簡単
    # auth {
    #   type     = "OAUTH"
    #   resource = "${aws_ssm_parameter.github_oauth_token.value}"
    # }
    location  = "https://github.com/O-Junpei/vapor-docker-sample.git"
    buildspec = "${data.template_file.buildspec_template_file.rendered}"
  }
}

その2に続きます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

laravelをAWS EC2にデプロイする

はじめに

vagrantなどのローカル開発環境で作成したlaravelプロジェクトをAWSのEC2上にデプロイする時に色々迷ったので、まとめとしてあげます。本番サーバにAWSを使ってみたいという方に見て頂ければと思います。

環境

php7.2.18
apache2.4.39
laravel5.7
EC2(Amazon Linux AMI 2018.03.0 (HVM), SSD Volume Type 無料枠)
RDS(mysqlを使用)

前提

・ローカルで開発したプロジェクトをすでにgithubにプッシュ済み
・EC2にSSH接続できている
・EC2からRDSへ接続できる設定ができている(セキュリティグループなど)

PHP7.2インストール

インストールできるリスト確認
sudo yum list available | grep php72

インストール
sudo yum install -y \
  php72 php72-devel php72-fpm php72-gd php72-mbstring \
  php72-mysqlnd php72-pdo \
  php72-xml php72-json

インストール済みを確認
sudo yum list installed | grep php72

バージョン確認
php -v

Mysqlインストール RDSとの接続

インストール
sudo yum -y install mysql

接続
mysql test_db -h <エンドポイント> -P 3306 -u test_user -p

apacheインストール

sudo yum install -y httpd24

上記の流れでインストールを進めていると、なぜかすでにインストール済みと出た。
どこで入れたかわからなかった。
続きの設定を行っていく

wabサーバの起動
sudo service httpd start

システムがブートするたびにapacheが起動するよう設定
sudo chkconfig httpd on

有効か確認
chkconfig --list httpd

ドキュメントルートの権限変更

ユーザをapacheグループに追加
sudo usermod -a -G apache ec2-user

一旦ログアウト
exit

グループのメンバーシップを検証
groups

グループ所有権をapacheグループに変更
sudo chown -R ec2-user:apache /var/www

グループの書き込み許可追加
sudo chmod 2775 /var/www

サブディレクトにグループ ID を設定するには、/var/www とサブディレクトのディレクトリ許可
find /var/www -type d -exec sudo chmod 2775 {} \;

グループ書き込み許可を追加するには、/var/www とサブディレクトリのファイル許可を再帰的に変更します。
find /var/www -type f -exec sudo chmod 0664 {} \;

Gitインストール

sudo yum install git

composer自身のインストール

インストール
sudo curl -sS https://getcomposer.org/installer | php

パスを通す
sudo mv composer.phar /usr/local/bin/composer

LaravelプロジェクトのClone

cd /var/www/html
git clone <URL>

apacheのドキュメントルート設定

設定ファイルを開く
sudo vi /etc/httpd/conf/httpd.conf

以下に変更
DocumentRoot "/var/www/html/自分のlaravel_project/public

最終行に以下を追記
# .htaccess 有効化
<Directory /var/www/html/自分のlaravel_project/public>
    AllowOverride All
</Directory>

Laravelプロジェクトでcomposer インストール

cd /var/www/html/laravelプロジェクト
composer install

config, routeのキャッシュクリア

php artisan config:cache
php artisan route:cache

プロジェクトのパーミッション変更

chmod 777 storage -R
chmod 777 bootstrap/cache -R

.envの設定

git cloneしたLaravelプロジェクトには.envファイルがないので、
composer insatallで作成された、.env.exsampleを使用する

cp .env.example .env
vi .env

APP_URL=http://IPアドレス ←サーバーのIPにする

DB_CONNECTION=mysql
DB_HOST=←RDSのエンドポイント
DB_DATABASE=データベース名
DB_USERNAME=RDS作成時のユーザ名
DB_PASSWORD=設定したパスワード

キーを作成し、キャッシュをクリアする

php artisan key:generate
php artisan config:clear

テーブル作成

php artisan migrate

シンボリックリンクの作成

これは作成したアプリで画像アップロードしているような場合は必要かと思います。

php artisan storage::link

ブラウザで確認する

あとはブラウザでEC2のIPアドレスを入力するだけです!
ここまでくればブラウザで正しく表示されるかと思います!

参考

https://qiita.com/reflet/items/11ad79e01e808876caa1
https://nori-life.com/install-laravel-aws/
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/install-LAMP.html
https://qiita.com/takg/items/9a044b36cdf216a42ba6
https://qiita.com/nbapps_dev/items/9307e6fc0edb59c09181

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

awsvpcTrunking設定を試す【ECS】

ENI制限の強化

2019年6月6日にAWSからawsvpcTrunkingアカウント設定が発表されました!
Amazon ECS が、awsvpc ネットワークモードのタスクに対する Elastic Network Interface (ENI) 制限の強化をサポート開始
今までインスタンスタイプごとにENIの上限は変更できませんでしたが、この度変更することができるようになりました。
利用可能な条件については下記です。

  • ECS が使用できるすべてのリージョン
  • Amazon ECS最適化AMI

*現時点では、Windows コンテナはサポートされていません。

awsvpcモードを利用したEC2起動タイプのコンテナが更に使い勝手がよくなりましたので、実際にやってみようと思います。

これまでのECS運用

私がECSを利用する際、以下の2つが選択において考慮するポイントでした。

  • タスクの増減が頻繁か
  • awsvpcモードを利用するか

タスクの増減が頻繁に起こるようなコンテナですと、EC2起動タイプよりもFargateの方がスムーズに増減が実現できかつスケーリングの設定などの煩わしさから解放されるためできれば利用したいです。
またawsvpcモードを利用する場合はインスタンス側のENI制限に依存するためFargateを選択して制限から解放されたいので、こちらも可能な限りFargateを利用したいと思っています。

しかし、今年の1月に安くなったとはいえ金額的にFargateの方が高いため躊躇うこともあると思います。
そのためEC2起動タイプを選択する方はまだまだ少なくないと思いますが、その際にぶつかるのが上述したENI問題です。
awsvpcモードの場合、ENIをコンテナにアタッチして利用するため以下の利点が見込めます。

  • ALB / NLBにIPターゲットとして登録ができる
  • タスクごとにSecurityGroupを紐付けることができる
  • 同一ホスト内でのタスクポートマッピングを考慮する必要がない

非常な便利かつ安価に使える反面、何度も繰り返しますがENIの数がインスタンスに依存します。
m5.largeのインスタンスの場合ENIは3つが上限です。
無料利用枠で750h利用できるt2.microに至っては2つが上限となっております。

例えばm5.largeのインスタンスでECS Clusterを利用した場合、
まずはホストがENIを1つ利用するため1インスタンスにつき最大2つまでawsvpcモードを利用したコンテナが利用できます。
負荷が集中しコンテナが2 -> 10までスケールアウトが必要になった際、m5.largeインスタンスは5つ必要になります。
当然コンテナよりもEC2が立ち上がる方が時間がかかるためもしかしたら間に合わずに詰まってしまうかもしれません。

*こんなエラーを見たことがある方もいるかと思います。
trunk_01.png

あらかじめ待機させておいたり、Cloudwatch AlarmをトリガーにLambdaで一気に増やすなど色々な回避策を講じてきました。
便利なマネジメントツールが多くあるので合わせ技で回避することができる反面、管理すべきサービスが増えてしまうなんて状況です。

今回の追加によって、そういった煩わしさを少しだけ解消してくれるのではないかと期待しています。

awsvpcTrunkingアカウント設定をしてみる

では早速やってみましょう。
アカウント設定に従って進めていきます。

下準備

適当なECS周りのものを用意します。
今回は以下のものを用意しました。

  • ECS Cluster
  • Cluster用Launch Config
  • Cluster用Instance Profile
  • Cluster用IAM Role
  • Cluster用IAM Policy
  • Cluster用Security Group
  • ECS Service
  • Service用IAM Role
  • Service用IAM Policy
  • Service用Security Group
  • ECS Task definition
  • Task用IAM Role
  • Task用IAM Policy

ECS最適化AMIについてはこちらから確認してください。

Terraformでぱぱっと作ります。
trunking_ecs.tf
resource "aws_ecs_cluster" "trunking-cluster" {
  name = "trunking-ecs"
}

data "template_file" "trunking-ecs-userdata" {
  template = "${file("userdata-template/trunking-ecs.tpl")
}

resource "aws_launch_configuration" "trunking-ecs" {
  name_prefix          = "trunking-ecs-launch-config-"
  image_id             = "ami-02507631a9f7bc956"
  instance_type        = "m5.large"
  iam_instance_profile = "${aws_iam_instance_profile.trunking-ecs.name}"
  key_name             = "${var.own_key}"
  user_data            = "${data.template_file.trunking-ecs-userdata.rendered}"
  security_groups      = ["${aws_security_group.trunking-ecs.id}"]

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_iam_role" "trunking-ecs-role" {
  name               = "${var.region}-trunking-ecs"
  assume_role_policy = "${file("./policy/iam_assumerole.json")}"
}

resource "aws_iam_instance_profile" "trunking-ecs" {
  depends_on = ["aws_iam_role.trunking-ecs-role"]
  name       = "${var.region}-trunking-ecs"
  role       = "${aws_iam_role.trunking-ecs-role.name}"
}

resource "aws_iam_role_policy_attachment" "trunking-ecs-attach" {
  depends_on = ["aws_iam_role.trunking-ecs-role"]
  role       = "${aws_iam_role.trunking-ecs-role.name}"
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
}

resource "aws_security_group" "trunking-ecs" {
  name        = "trunking-ecs"
  description = "trunking ECS security Group"
  vpc_id      = "${aws_vpc.test-vpc.id}"

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["${aws_vpc.test-vpc.cidr_block}"]
  }

  ingress {
    from_port   = 31000
    to_port     = 61000
    protocol    = "tcp"
    cidr_blocks = ["${aws_vpc.test-vpc.cidr_block}"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags {
    "Name"    = "trunking-ecs-ingress"
    "Service" = "trunking"
  }
}

resource "aws_autoscaling_group" "trunking-ecs" {
  name                      = "trunking-ecs-asg"
  max_size                  = 2
  min_size                  = 1
  health_check_grace_period = 300
  health_check_type         = "ELB"
  desired_capacity          = 1
  force_delete              = true
  launch_configuration      = "${aws_launch_configuration.trunking-ecs.name}"
  vpc_zone_identifier       = ["${aws_subnet.private.*.id}"]

  tag {
    key                 = "Name"
    value               = "${var.region}-trunking-ecs"
    propagate_at_launch = true
  }

  tag {
    key                 = "Service"
    value               = "trunking"
    propagate_at_launch = true
  }

  lifecycle {
    create_before_destroy = true
    ignore_changes        = ["desired_capacity"]
  }
}
trunking_service.tf
resource "aws_ecs_service" "trunking-service" {
  name            = "trunking"
  cluster         = "${aws_ecs_cluster.trunking-cluster.id}"
  task_definition = "${aws_ecs_task_definition.trunking-task.arn}"
  desired_count   = 2

  network_configuration {
    security_groups = ["${aws_security_group.trunking-service.id}"]
    subnets         = ["${aws_subnet.private.*.id}"]
  }

  load_balancer {
    target_group_arn = "${aws_lb_target_group.trunking.arn}"
    container_name   = "trunking-service"
    container_port   = "24224"
  }

  ordered_placement_strategy {
    type  = "spread"
    field = "attribute:ecs.availability-zone"
  }

  ordered_placement_strategy {
    type  = "spread"
    field = "instanceId"
  }

  lifecycle {
    ignore_changes = ["desired_count"]
  }
}

resource "aws_security_group" "trunking-service" {
  name        = "trunking-service"
  description = "trunking service security Group"
  vpc_id      = "${aws_vpc.test-vpc.id}"

  ingress {
    from_port   = 24224
    to_port     = 24224
    protocol    = "tcp"
    self        = true
    cidr_blocks = ["${aws_vpc.test-vpc.cidr_block}"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags {
    "Name"    = "trunking-service-ingress"
    "Service" = "trunking"
  }
}

data "template_file" "trunking-task" {
  template = "${file("task-definitions/trunking-task.tpl")}"
}

resource "aws_ecs_task_definition" "trunking-task" {
  family                = "trunking"
  network_mode          = "awsvpc"
  container_definitions = "${data.template_file.trunking-task.rendered}"
  task_role_arn         = "${aws_iam_role.task-execution.arn}"

  tags {
    Name    = "trunking-task"
    Service = "trunking"
  }
}

resource "aws_iam_role" "trunking-service-role" {
  name               = "${var.region}-trunking-service"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ecs.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy_attachment" "trunking-service-attach" {
  role       = "${aws_iam_role.trunking-service-role.name}"
  policy_arn = "${aws_iam_policy.trunking-cluster.arn}"
}

resource "aws_cloudwatch_log_group" "trunking" {
  name = "/${var.service_name}/trunking"
}

resource "aws_iam_policy" "trunking-cluster" {
  count = "${aws_ecs_cluster.trunking-cluster.count}"
  name  = "${var.region}-TrunkingServicePolicy"

  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ECSTaskTrunking",
            "Effect": "Allow",
            "Action": [
                "ec2:AttachNetworkInterface",
                "ec2:CreateNetworkInterface",
                "ec2:CreateNetworkInterfacePermission",
                "ec2:DeleteNetworkInterface",
                "ec2:DeleteNetworkInterfacePermission",
                "ec2:Describe*",
                "ec2:DetachNetworkInterface",
                "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
                "elasticloadbalancing:DeregisterTargets",
                "elasticloadbalancing:Describe*",
                "elasticloadbalancing:RegisterInstancesWithLoadBalancer",
                "elasticloadbalancing:RegisterTargets",
                "route53:ChangeResourceRecordSets",
                "route53:CreateHealthCheck",
                "route53:DeleteHealthCheck",
                "route53:Get*",
                "route53:List*",
                "route53:UpdateHealthCheck",
                "servicediscovery:DeregisterInstance",
                "servicediscovery:Get*",
                "servicediscovery:List*",
                "servicediscovery:RegisterInstance",
                "servicediscovery:UpdateInstanceCustomHealthStatus",
                "ecr:BatchCheckLayerAvailability",
                "ecr:BatchGetImage",
                "ecr:GetDownloadUrlForLayer",
                "ecr:GetAuthorizationToken",
                "application-autoscaling:*",
                "cloudwatch:DescribeAlarms",
                "cloudwatch:PutMetricAlarm"
            ],
            "Resource": "*"
        },
        {
            "Sid": "TrunkingECSTagging",
            "Effect": "Allow",
            "Action": [
                "ec2:CreateTags"
            ],
            "Resource": "arn:aws:ec2:*:*:network-interface/*"
        }
    ]
}
EOF
}

できました。
trunk_02.png

まずは確認

こちらを参考にまずは設定を確認してみました。(当然disableなのですが…。)

before_enable
$ aws --region us-east-1 ecs list-account-settings --effective-settings
{
    "settings": [
        {
            "name": "awsvpcTrunking",
            "value": "disabled",
            "principalArn": "arn:aws:iam::123456789012:user/hogehoge.fugafuga"
        },
        {
            "name": "containerInstanceLongArnFormat",
            "value": "enabled",
            "principalArn": "arn:aws:iam::123456789012:root"
        },
        {
            "name": "serviceLongArnFormat",
            "value": "enabled",
            "principalArn": "arn:aws:iam::123456789012:root"
        },
        {
            "name": "taskLongArnFormat",
            "value": "enabled",
            "principalArn": "arn:aws:iam::123456789012:root"
        }
    ]
}

コンソールからも
trunk_03.png

無効になっていることを確認しました。

有効化

こちらを参考に有効化していきます。
今回はコンソールではなくCLIで有効化してみます。

enable_trunking
$ aws ecs --region us-east-1 put-account-setting --name awsvpcTrunking --value enabled --principal-arn arn:aws:iam::123456789012:user/hogehoge.fugafuga
{
    "setting": {
        "name": "awsvpcTrunking",
        "value": "enabled",
        "principalArn": "arn:aws:iam::123456789012:user/hogehoge.fugafuga"
    }
}
$
$ aws --region us-east-1 ecs list-account-settings --principal-arn arn:aws:iam::123456789012:user/hogehoge.fugafuga --effective-settings
{
    "settings": [
        {
            "name": "awsvpcTrunking",
            "value": "enabled",
            "principalArn": "arn:aws:iam::123456789012:user/hogehoge.fugafuga"
        },
        {
            "name": "containerInstanceLongArnFormat",
            "value": "enabled",
            "principalArn": "arn:aws:iam::123456789012:root"
        },
        {
            "name": "serviceLongArnFormat",
            "value": "enabled",
            "principalArn": "arn:aws:iam::123456789012:root"
        },
        {
            "name": "taskLongArnFormat",
            "value": "enabled",
            "principalArn": "arn:aws:iam::123456789012:root"
        }
    ]
}

コンソールからも確認
trunk_04.png

問題なさそうですね。

実際に増やしてみる

では本当にENIの上限が変化するかを確認してみましょう。
今回はm5.largeのインスタンスを利用しているので、ホスト用を抜いた限界値9つまで上げてみます。

chage_desired_count
$ aws --region us-east-1 ecs update-service --cluster trunking-ecs --service trunking --desired-count 9 --query 'service[].desiredCount'
{
    "desiredCount": 9,
}
$
$ aws --region us-east-1 ecs describe-services --cluster trunking-ecs --services trunking --query 'services[].deployments[]'
[
    {
        "id": "ecs-svc/9223370475408119189",
        "status": "PRIMARY",
        "taskDefinition": "arn:aws:ecs:us-east-1:123456789012:task-definition/trunking:2",
        "desiredCount": 9,
        "pendingCount": 0,
        "runningCount": 2,
        "createdAt": 1561446656.618,
        "updatedAt": 1561457505.944,
        "launchType": "EC2",
        "networkConfiguration": {
            "awsvpcConfiguration": {
                "subnets": [
                    "subnet-12345678901234567",
                    "subnet-76543210987654321"
                ],
                "securityGroups": [
                    "sg-01928374656473829"
                ],
                "assignPublicIp": "DISABLED"
            }
        }
    }
]

これで完了!と思いきや

error
$ aws --region us-east-1 ecs describe-services --cluster trunking-ecs --services trunking --query 'services[].events[]' | head -n 6
[
    {
        "id": "8e4600d0-206f-4bed-9388-2a86b4bcb54c",
        "createdAt": 1561457461.911,
        "message": "(service trunking) was unable to place a task because no container instance met all of its requirements. The closest matching (container-instance dab5c255b71247dba74f5cb126eb6db7) encountered error \"RESOURCE:ENI\". For more information, see the Troubleshooting section of the Amazon ECS Developer Guide."
    },

どうやら即時反映ではなく設定後に起動されたインスタンスから反映されるようです。
ENI トランキングに関する考慮事項

awsvpcTrunking のオプトイン後に起動された新しい Amazon EC2 インスタンスのみが、
引き上げられた ENI 制限とトランクネットワークインターフェイスを受け取ります。
以前に起動されたインスタンスは、実行されたアクションに関係なく、これらの機能を受け取りません。

というわけでインスタンスを新しく立ち上げて確認します。
新しいエラーがなにやら発生しています。

error_2
$ aws --region us-east-1 ecs describe-services --cluster trunking-ecs --services trunking --query 'services[].events[]' | head -n 8
[
    {
        "id": "5a41e1a5-7c35-4795-a9f5-1636d2dab3c5",
        "createdAt": 1561458498.055,
        "message": "(service trunking) was unable to place a task because no container instance met all of its requirements. The closest matching (container-instance dab5c255b71247dba74f5cb126eb6db7) doesn't have the agent connected. For more information, see the Troubleshooting section of the Amazon ECS Developer Guide."
    },

インスタンスに入って/var/log/ecsのログを確認

$ cd /var/log/ecs
$ cat ecs-init.log
2019-06-25T10:40:45Z [INFO] pre-start
2019-06-25T10:40:46Z [INFO] start
2019-06-25T10:40:46Z [INFO] No existing agent container to remove.
2019-06-25T10:40:46Z [INFO] Starting Amazon Elastic Container Service Agent
$
$ cat ecs-agent.log.2019-06-25-10 | head
2019-06-25T10:40:47Z [INFO] Loading configuration
2019-06-25T10:40:47Z [INFO] Image excluded from cleanup: amazon/amazon-ecs-agent:latest
2019-06-25T10:40:47Z [INFO] Image excluded from cleanup: amazon/amazon-ecs-pause:0.1.0
2019-06-25T10:40:47Z [INFO] Amazon ECS agent Version: 1.29.0, Commit: a190a73f
2019-06-25T10:40:47Z [INFO] Creating root ecs cgroup: /ecs
2019-06-25T10:40:47Z [INFO] Creating cgroup /ecs
2019-06-25T10:40:47Z [INFO] Loading state! module="statemanager"
2019-06-25T10:40:47Z [INFO] Event stream ContainerChange start listening...
2019-06-25T10:40:48Z [INFO] Registering Instance with ECS
2019-06-25T10:40:48Z [INFO] Remaining mem: 7681
$ 
$ docker ps
CONTAINER ID        IMAGE                                                                        COMMAND                  CREATED             STATUS              PORTS               NAMES
2e9ce16d7b40        123456789012.dkr.ecr.us-east-1.amazonaws.com/test:trunking                   "/opt/sh/startup.sh …"   20 minutes ago      Up 20 minutes                           ecs-trunking-2-trunking-service-de8897e0cb8dcded5b00
d84fc735402b        123456789012.dkr.ecr.us-east-1.amazonaws.com/test:trunking                   "/opt/sh/startup.sh …"   20 minutes ago      Up 20 minutes                           ecs-trunking-2-trunking-service-d4918bbcbbf1fbd02d00
40177ea3b5d2        amazon/amazon-ecs-pause:0.1.0                                                "./pause"                20 minutes ago      Up 20 minutes                           ecs-trunking-2-internalecspause-c8f988dcbae8b9bd5500
952b16278748        amazon/amazon-ecs-pause:0.1.0                                                "./pause"                20 minutes ago      Up 20 minutes                           ecs-trunking-2-internalecspause-deabf4c4d4bc81eb8a01
f1413a19e94b        amazon/amazon-ecs-agent:latest                                               "/agent"                 20 minutes ago      Up 20 minutes                           ecs-agent
$ 

起動には成功しているみたいですね。
よくよく読むとここに書いてありました。

アカウント、IAM ユーザー、またはロールは、awsvpcTrunking アカウント設定をオプトインする必要があります。詳細については、「アカウント設定」を参照してください。

どうやら自分のIAMに対してawsvpcTrunkingアカウント設定を有効化するだけではダメみたいですね。
サービスのロールに設定しようと試みましたが、怒られてしまいました。

enable_service_role
$ aws ecs --region us-east-1 put-account-setting --name awsvpcTrunking --value enabled --principal-arn arn:aws:iam::123456789012:role/us-east-1-trunking-service

An error occurred (InvalidParameterException) when calling the PutAccountSetting operation: Only the root user can view or modify the account settings for another user. You do not need to specify a principalArn value to change your own account settings.
$ 

AMIも最新のもので起動されています。

ami_confirm
$ aws --region us-east-1 ec2 describe-instances --instance-ids i-012345abcdef67gh8 --query 'Reservations[].Instances[].ImageId[]'
[
    "ami-02507631a9f7bc956"
]

rootからアカウントに対して許可が必要なんですかね?
近いうちにrootで作業できる環境で再度検証してみようと思います。

次回へつづく

おおーめっちゃ増えてるーってところを見たかったのですが残念
今週中にはrootで試してみようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

awsvpcTrunking設定を試す【ECS】

ENI制限の強化

2019年6月6日にAWSからawsvpcTrunkingアカウント設定が発表されました!
Amazon ECS が、awsvpc ネットワークモードのタスクに対する Elastic Network Interface (ENI) 制限の強化をサポート開始
今までインスタンスタイプごとにENIの上限は変更できませんでしたが、この度変更することができるようになりました。
利用可能な条件については下記です。

  • ECS が使用できるすべてのリージョン
  • Amazon ECS最適化AMI

*現時点では、Windows コンテナはサポートされていません。

awsvpcモードを利用したEC2起動タイプのコンテナが更に使い勝手がよくなりましたので、実際にやってみようと思います。

これまでのECS運用

私がECSを利用する際、以下の2つが選択において考慮するポイントでした。

  • タスクの増減が頻繁か
  • awsvpcモードを利用するか

タスクの増減が頻繁に起こるようなコンテナですと、EC2起動タイプよりもFargateの方がスムーズに増減が実現できかつスケーリングの設定などの煩わしさから解放されるためできれば利用したいです。
またawsvpcモードを利用する場合はインスタンス側のENI制限に依存するためFargateを選択して制限から解放されたいので、こちらも可能な限りFargateを利用したいと思っています。

しかし、今年の1月に安くなったとはいえ金額的にFargateの方が高いため躊躇うこともあると思います。
そのためEC2起動タイプを選択する方はまだまだ少なくないと思いますが、その際にぶつかるのが上述したENI問題です。
awsvpcモードの場合、ENIをコンテナにアタッチして利用するため以下の利点が見込めます。

  • ALB / NLBにIPターゲットとして登録ができる
  • タスクごとにSecurityGroupを紐付けることができる
  • 同一ホスト内でのタスクポートマッピングを考慮する必要がない

非常な便利かつ安価に使える反面、何度も繰り返しますがENIの数がインスタンスに依存します。
m5.largeのインスタンスの場合ENIは3つが上限です。
無料利用枠で750h利用できるt2.microに至っては2つが上限となっております。

例えばm5.largeのインスタンスでECS Clusterを利用した場合、
まずはホストがENIを1つ利用するため1インスタンスにつき最大2つまでawsvpcモードを利用したコンテナが利用できます。
負荷が集中しコンテナが2 -> 10までスケールアウトが必要になった際、m5.largeインスタンスは5つ必要になります。
当然コンテナよりもEC2が立ち上がる方が時間がかかるためもしかしたら間に合わずに詰まってしまうかもしれません。

*こんなエラーを見たことがある方もいるかと思います。
trunk_01.png

あらかじめ待機させておいたり、Cloudwatch AlarmをトリガーにLambdaで一気に増やすなど色々な回避策を講じてきました。
便利なマネジメントツールが多くあるので合わせ技で回避することができる反面、管理すべきサービスが増えてしまうなんて状況です。

今回の追加によって、そういった煩わしさを少しだけ解消してくれるのではないかと期待しています。

awsvpcTrunkingアカウント設定をしてみる

では早速やってみましょう。
アカウント設定に従って進めていきます。

下準備

適当なECS周りのものを用意します。
今回は以下のものを用意しました。

  • ECS Cluster
  • Cluster用Launch Config
  • Cluster用Instance Profile
  • Cluster用IAM Role
  • Cluster用IAM Policy
  • Cluster用Security Group
  • ECS Service
  • Service用IAM Role
  • Service用IAM Policy
  • Service用Security Group
  • ECS Task definition
  • Task用IAM Role
  • Task用IAM Policy

ECS最適化AMIについてはこちらから確認してください。

Terraformでぱぱっと作ります。
trunking_ecs.tf
resource "aws_ecs_cluster" "trunking-cluster" {
  name = "trunking-ecs"
}

data "template_file" "trunking-ecs-userdata" {
  template = "${file("userdata-template/trunking-ecs.tpl")
}

resource "aws_launch_configuration" "trunking-ecs" {
  name_prefix          = "trunking-ecs-launch-config-"
  image_id             = "ami-02507631a9f7bc956"
  instance_type        = "m5.large"
  iam_instance_profile = "${aws_iam_instance_profile.trunking-ecs.name}"
  key_name             = "${var.own_key}"
  user_data            = "${data.template_file.trunking-ecs-userdata.rendered}"
  security_groups      = ["${aws_security_group.trunking-ecs.id}"]

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_iam_role" "trunking-ecs-role" {
  name               = "${var.region}-trunking-ecs"
  assume_role_policy = "${file("./policy/iam_assumerole.json")}"
}

resource "aws_iam_instance_profile" "trunking-ecs" {
  depends_on = ["aws_iam_role.trunking-ecs-role"]
  name       = "${var.region}-trunking-ecs"
  role       = "${aws_iam_role.trunking-ecs-role.name}"
}

resource "aws_iam_role_policy_attachment" "trunking-ecs-attach" {
  depends_on = ["aws_iam_role.trunking-ecs-role"]
  role       = "${aws_iam_role.trunking-ecs-role.name}"
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
}

resource "aws_security_group" "trunking-ecs" {
  name        = "trunking-ecs"
  description = "trunking ECS security Group"
  vpc_id      = "${aws_vpc.test-vpc.id}"

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["${aws_vpc.test-vpc.cidr_block}"]
  }

  ingress {
    from_port   = 31000
    to_port     = 61000
    protocol    = "tcp"
    cidr_blocks = ["${aws_vpc.test-vpc.cidr_block}"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags {
    "Name"    = "trunking-ecs-ingress"
    "Service" = "trunking"
  }
}

resource "aws_autoscaling_group" "trunking-ecs" {
  name                      = "trunking-ecs-asg"
  max_size                  = 2
  min_size                  = 1
  health_check_grace_period = 300
  health_check_type         = "ELB"
  desired_capacity          = 1
  force_delete              = true
  launch_configuration      = "${aws_launch_configuration.trunking-ecs.name}"
  vpc_zone_identifier       = ["${aws_subnet.private.*.id}"]

  tag {
    key                 = "Name"
    value               = "${var.region}-trunking-ecs"
    propagate_at_launch = true
  }

  tag {
    key                 = "Service"
    value               = "trunking"
    propagate_at_launch = true
  }

  lifecycle {
    create_before_destroy = true
    ignore_changes        = ["desired_capacity"]
  }
}
trunking_service.tf
resource "aws_ecs_service" "trunking-service" {
  name            = "trunking"
  cluster         = "${aws_ecs_cluster.trunking-cluster.id}"
  task_definition = "${aws_ecs_task_definition.trunking-task.arn}"
  desired_count   = 2

  network_configuration {
    security_groups = ["${aws_security_group.trunking-service.id}"]
    subnets         = ["${aws_subnet.private.*.id}"]
  }

  load_balancer {
    target_group_arn = "${aws_lb_target_group.trunking.arn}"
    container_name   = "trunking-service"
    container_port   = "24224"
  }

  ordered_placement_strategy {
    type  = "spread"
    field = "attribute:ecs.availability-zone"
  }

  ordered_placement_strategy {
    type  = "spread"
    field = "instanceId"
  }

  lifecycle {
    ignore_changes = ["desired_count"]
  }
}

resource "aws_security_group" "trunking-service" {
  name        = "trunking-service"
  description = "trunking service security Group"
  vpc_id      = "${aws_vpc.test-vpc.id}"

  ingress {
    from_port   = 24224
    to_port     = 24224
    protocol    = "tcp"
    self        = true
    cidr_blocks = ["${aws_vpc.test-vpc.cidr_block}"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags {
    "Name"    = "trunking-service-ingress"
    "Service" = "trunking"
  }
}

data "template_file" "trunking-task" {
  template = "${file("task-definitions/trunking-task.tpl")}"
}

resource "aws_ecs_task_definition" "trunking-task" {
  family                = "trunking"
  network_mode          = "awsvpc"
  container_definitions = "${data.template_file.trunking-task.rendered}"
  task_role_arn         = "${aws_iam_role.task-execution.arn}"

  tags {
    Name    = "trunking-task"
    Service = "trunking"
  }
}

resource "aws_iam_role" "trunking-service-role" {
  name               = "${var.region}-trunking-service"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ecs.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy_attachment" "trunking-service-attach" {
  role       = "${aws_iam_role.trunking-service-role.name}"
  policy_arn = "${aws_iam_policy.trunking-cluster.arn}"
}

resource "aws_cloudwatch_log_group" "trunking" {
  name = "/${var.service_name}/trunking"
}

resource "aws_iam_policy" "trunking-cluster" {
  count = "${aws_ecs_cluster.trunking-cluster.count}"
  name  = "${var.region}-TrunkingServicePolicy"

  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ECSTaskTrunking",
            "Effect": "Allow",
            "Action": [
                "ec2:AttachNetworkInterface",
                "ec2:CreateNetworkInterface",
                "ec2:CreateNetworkInterfacePermission",
                "ec2:DeleteNetworkInterface",
                "ec2:DeleteNetworkInterfacePermission",
                "ec2:Describe*",
                "ec2:DetachNetworkInterface",
                "elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
                "elasticloadbalancing:DeregisterTargets",
                "elasticloadbalancing:Describe*",
                "elasticloadbalancing:RegisterInstancesWithLoadBalancer",
                "elasticloadbalancing:RegisterTargets",
                "route53:ChangeResourceRecordSets",
                "route53:CreateHealthCheck",
                "route53:DeleteHealthCheck",
                "route53:Get*",
                "route53:List*",
                "route53:UpdateHealthCheck",
                "servicediscovery:DeregisterInstance",
                "servicediscovery:Get*",
                "servicediscovery:List*",
                "servicediscovery:RegisterInstance",
                "servicediscovery:UpdateInstanceCustomHealthStatus",
                "ecr:BatchCheckLayerAvailability",
                "ecr:BatchGetImage",
                "ecr:GetDownloadUrlForLayer",
                "ecr:GetAuthorizationToken",
                "application-autoscaling:*",
                "cloudwatch:DescribeAlarms",
                "cloudwatch:PutMetricAlarm"
            ],
            "Resource": "*"
        },
        {
            "Sid": "TrunkingECSTagging",
            "Effect": "Allow",
            "Action": [
                "ec2:CreateTags"
            ],
            "Resource": "arn:aws:ec2:*:*:network-interface/*"
        }
    ]
}
EOF
}

できました。
trunk_02.png

まずは確認

こちらを参考にまずは設定を確認してみました。(当然disableなのですが…。)

before_enable
$ aws --region us-east-1 ecs list-account-settings --effective-settings
{
    "settings": [
        {
            "name": "awsvpcTrunking",
            "value": "disabled",
            "principalArn": "arn:aws:iam::123456789012:user/hogehoge.fugafuga"
        },
        {
            "name": "containerInstanceLongArnFormat",
            "value": "enabled",
            "principalArn": "arn:aws:iam::123456789012:root"
        },
        {
            "name": "serviceLongArnFormat",
            "value": "enabled",
            "principalArn": "arn:aws:iam::123456789012:root"
        },
        {
            "name": "taskLongArnFormat",
            "value": "enabled",
            "principalArn": "arn:aws:iam::123456789012:root"
        }
    ]
}

コンソールからも
trunk_03.png

無効になっていることを確認しました。

有効化

こちらを参考に有効化していきます。
今回はコンソールではなくCLIで有効化してみます。

enable_trunking
$ aws ecs --region us-east-1 put-account-setting --name awsvpcTrunking --value enabled --principal-arn arn:aws:iam::123456789012:user/hogehoge.fugafuga
{
    "setting": {
        "name": "awsvpcTrunking",
        "value": "enabled",
        "principalArn": "arn:aws:iam::123456789012:user/hogehoge.fugafuga"
    }
}
$
$ aws --region us-east-1 ecs list-account-settings --principal-arn arn:aws:iam::123456789012:user/hogehoge.fugafuga --effective-settings
{
    "settings": [
        {
            "name": "awsvpcTrunking",
            "value": "enabled",
            "principalArn": "arn:aws:iam::123456789012:user/hogehoge.fugafuga"
        },
        {
            "name": "containerInstanceLongArnFormat",
            "value": "enabled",
            "principalArn": "arn:aws:iam::123456789012:root"
        },
        {
            "name": "serviceLongArnFormat",
            "value": "enabled",
            "principalArn": "arn:aws:iam::123456789012:root"
        },
        {
            "name": "taskLongArnFormat",
            "value": "enabled",
            "principalArn": "arn:aws:iam::123456789012:root"
        }
    ]
}

コンソールからも確認
trunk_04.png

問題なさそうですね。

実際に増やしてみる

では本当にENIの上限が変化するかを確認してみましょう。
今回はm5.largeのインスタンスを利用しているので、ホスト用を抜いた限界値9つまで上げてみます。

chage_desired_count
$ aws --region us-east-1 ecs update-service --cluster trunking-ecs --service trunking --desired-count 9 --query 'service[].desiredCount'
{
    "desiredCount": 9,
}
$
$ aws --region us-east-1 ecs describe-services --cluster trunking-ecs --services trunking --query 'services[].deployments[]'
[
    {
        "id": "ecs-svc/9223370475408119189",
        "status": "PRIMARY",
        "taskDefinition": "arn:aws:ecs:us-east-1:123456789012:task-definition/trunking:2",
        "desiredCount": 9,
        "pendingCount": 0,
        "runningCount": 2,
        "createdAt": 1561446656.618,
        "updatedAt": 1561457505.944,
        "launchType": "EC2",
        "networkConfiguration": {
            "awsvpcConfiguration": {
                "subnets": [
                    "subnet-12345678901234567",
                    "subnet-76543210987654321"
                ],
                "securityGroups": [
                    "sg-01928374656473829"
                ],
                "assignPublicIp": "DISABLED"
            }
        }
    }
]

これで完了!と思いきや

error
$ aws --region us-east-1 ecs describe-services --cluster trunking-ecs --services trunking --query 'services[].events[]' | head -n 6
[
    {
        "id": "8e4600d0-206f-4bed-9388-2a86b4bcb54c",
        "createdAt": 1561457461.911,
        "message": "(service trunking) was unable to place a task because no container instance met all of its requirements. The closest matching (container-instance dab5c255b71247dba74f5cb126eb6db7) encountered error \"RESOURCE:ENI\". For more information, see the Troubleshooting section of the Amazon ECS Developer Guide."
    },

どうやら即時反映ではなく設定後に起動されたインスタンスから反映されるようです。
ENI トランキングに関する考慮事項

awsvpcTrunking のオプトイン後に起動された新しい Amazon EC2 インスタンスのみが、
引き上げられた ENI 制限とトランクネットワークインターフェイスを受け取ります。
以前に起動されたインスタンスは、実行されたアクションに関係なく、これらの機能を受け取りません。

というわけでインスタンスを新しく立ち上げて確認します。
新しいエラーがなにやら発生しています。

error_2
$ aws --region us-east-1 ecs describe-services --cluster trunking-ecs --services trunking --query 'services[].events[]' | head -n 8
[
    {
        "id": "5a41e1a5-7c35-4795-a9f5-1636d2dab3c5",
        "createdAt": 1561458498.055,
        "message": "(service trunking) was unable to place a task because no container instance met all of its requirements. The closest matching (container-instance dab5c255b71247dba74f5cb126eb6db7) doesn't have the agent connected. For more information, see the Troubleshooting section of the Amazon ECS Developer Guide."
    },

インスタンスに入って/var/log/ecsのログを確認

$ cd /var/log/ecs
$ cat ecs-init.log
2019-06-25T10:40:45Z [INFO] pre-start
2019-06-25T10:40:46Z [INFO] start
2019-06-25T10:40:46Z [INFO] No existing agent container to remove.
2019-06-25T10:40:46Z [INFO] Starting Amazon Elastic Container Service Agent
$
$ cat ecs-agent.log.2019-06-25-10 | head
2019-06-25T10:40:47Z [INFO] Loading configuration
2019-06-25T10:40:47Z [INFO] Image excluded from cleanup: amazon/amazon-ecs-agent:latest
2019-06-25T10:40:47Z [INFO] Image excluded from cleanup: amazon/amazon-ecs-pause:0.1.0
2019-06-25T10:40:47Z [INFO] Amazon ECS agent Version: 1.29.0, Commit: a190a73f
2019-06-25T10:40:47Z [INFO] Creating root ecs cgroup: /ecs
2019-06-25T10:40:47Z [INFO] Creating cgroup /ecs
2019-06-25T10:40:47Z [INFO] Loading state! module="statemanager"
2019-06-25T10:40:47Z [INFO] Event stream ContainerChange start listening...
2019-06-25T10:40:48Z [INFO] Registering Instance with ECS
2019-06-25T10:40:48Z [INFO] Remaining mem: 7681
$ 
$ docker ps
CONTAINER ID        IMAGE                                                                        COMMAND                  CREATED             STATUS              PORTS               NAMES
2e9ce16d7b40        123456789012.dkr.ecr.us-east-1.amazonaws.com/fr-gec-log-aggregator:staging   "/opt/sh/startup.sh …"   20 minutes ago      Up 20 minutes                           ecs-trunking-2-trunking-service-de8897e0cb8dcded5b00
d84fc735402b        123456789012.dkr.ecr.us-east-1.amazonaws.com/fr-gec-log-aggregator:staging   "/opt/sh/startup.sh …"   20 minutes ago      Up 20 minutes                           ecs-trunking-2-trunking-service-d4918bbcbbf1fbd02d00
40177ea3b5d2        amazon/amazon-ecs-pause:0.1.0                                                "./pause"                20 minutes ago      Up 20 minutes                           ecs-trunking-2-internalecspause-c8f988dcbae8b9bd5500
952b16278748        amazon/amazon-ecs-pause:0.1.0                                                "./pause"                20 minutes ago      Up 20 minutes                           ecs-trunking-2-internalecspause-deabf4c4d4bc81eb8a01
f1413a19e94b        amazon/amazon-ecs-agent:latest                                               "/agent"                 20 minutes ago      Up 20 minutes                           ecs-agent
$ 

起動には成功しているみたいですね。
よくよく読むとここに書いてありました。

アカウント、IAM ユーザー、またはロールは、awsvpcTrunking アカウント設定をオプトインする必要があります。詳細については、「アカウント設定」を参照してください。

どうやら自分のIAMに対してawsvpcTrunkingアカウント設定を有効化するだけではダメみたいですね。
サービスのロールに設定しようと試みましたが、怒られてしまいました。

enable_service_role
$ aws ecs --region us-east-1 put-account-setting --name awsvpcTrunking --value enabled --principal-arn arn:aws:iam::123456789012:role/us-east-1-trunking-service

An error occurred (InvalidParameterException) when calling the PutAccountSetting operation: Only the root user can view or modify the account settings for another user. You do not need to specify a principalArn value to change your own account settings.
$ 

AMIも最新のもので起動されています。

ami_confirm
$ aws --region us-east-1 ec2 describe-instances --instance-ids i-012345abcdef67gh8 --query 'Reservations[].Instances[].ImageId[]'
[
    "ami-02507631a9f7bc956"
]

rootからアカウントに対して許可が必要なんですかね?
近いうちにrootで作業できる環境で再度検証してみようと思います。

次回へつづく

おおーめっちゃ増えてるーってところを見たかったのですが残念
今週中にはrootで試してみようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Glueの概要を図と用語で整理する

AWS Glueをざっくりと理解するために基本的な概念とコンポーネントを、図と用語で理解してみます。

AWS Glueとは?

  • フルマネージドETLツール
    • ETL = どっかからデータ引っ張って、いい感じに変換してどっかに突っ込むこと

ざっくりとした概念図

image.png

特徴

  • サーバレス
  • 高セキュリティ
  • etc..

用語

  • データストア
    • S3, DynamoDB, RDBなど
  • データソース
    • Glueへの入力に使われるデータストア
  • データターゲット
    • Glueからの出力に使われるデータストア
  • データカタログ(Data Catalog)
    • Glueを利用するための箱
    • ジョブ、メタデータ(データベース,テーブル)などGlueに関わるコンポーネントはすべてここに含まれる
    • 1AWSアカウントの1リージョンにつき、1データカタログ
  • データベース
    • データカタログに含まれる。テーブル等をまとめておくもの。
  • テーブル
    • データベースに含まれる
    • データソースについてのメタデータを格納したもの
    • あくまでメタデータを格納しており、実際のデータは含まない
  • 分類子
    • データのスキーマを決定する
  • その他の用語

メモ

  • テーブルは手動(またはCloudformation等)でも作成できるが、クローラから作成するのがミスが少なくて良い
  • CloudformationはデータソースにDynamoDBを使用したクローラの作成には未対応(2019/06現在)
    • というかDynamoDBはいろいろ未対応。まだDynamoDBには少々使いづらい印象。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS AuroraをMySQL Workbenchでいじる

はじめに

AWS上のAmazon Aurora(MySQL)を

MySQL Workbenchで外からいじってみました。

Amazon Auroraとは

  • AWSが提供しているフルマネージドなデータベースサービス。
  • 可用性99.99%
    • 最低でも3つのAZにデータを6個複製、継続的にAmazon S3にバックアップ
  • MySQL、PostgreSQL互換
  • いろいろ自動でやってくれる(プロビジョニングとかバックアップとか)
  • ストレージが64TBまで自動で増える。
  • Aurora Serverlessを使えばインスタンスの性能まで自動で管理してくれる。

詳しくはこちら
Amazon Aurora

データベースのセットアップ

AWSコンソールのRDSからいきます。

データベースの作成からAmazonAuroraを選択します。
エディションはMySQLです。

接続のところの追加の接続設定で「パブリックアクセス可能」を「あり」にします
これをしないとVPC外から接続できません。
あと付与するセキュリティグループでインスタンスのポートに接続可能にしておくのも忘れずに(デフォルトだと3306)
他の設定は適当で大丈夫です。

スクリーンショット 2019-06-25 14.46.36.png

作成できたらエンドポイントをメモっておいてください。

スクリーンショット 2019-06-25 14.53.10.png

MySQL Workbenchを起動

起動したら下の矢印の+ボタンを押します。

スクリーンショット 2019-06-25 14.59.11.png

そしたらこの画面が出てくるので先程のエンドポイントとユーザー名、パスワードを入力してOKを押します。

スクリーンショット 2019-06-25 15.56.19.png

スキーマの作成

接続できたらこんな画面
左上のアイコンをクリックするとスキーマが作成できます。

スクリーンショット 2019-06-25 15.09.08.png

テーブルの作成

適当にスキーマを作成したら
今度はテーブルを作成します。
右隣のボタンです。

スクリーンショット 2019-06-25 15.10.33.png

PKとかNNとかなんやっておもったら右下のStorageってところに書いてありました。

PrimaryKeyとかの設定ですね。

データ挿入

テーブルが作れたらデータの挿入です

左側のテーブル名のところにカーソルを当てると表っぽいアイコンが出てきてそれをクリックするとテーブルの中身が見えます。
真ん中らへんのEditのところでいろいろ挿入したり削除したりできます。
変更したら右下のApplyで実際のコマンドとして入力するとテーブルが更新されます。

スクリーンショット 2019-06-25 15.35.37.png

コマンドで表を操作したり、GUI上でポチポチとテーブルを操作したりできます。
すぐに反映された結果のテーブルが見えるので便利です。

まとめ

Amazon AuroraをGUIのMySQLクライアントから触ってみました。
いろいろ機能が豊富そうなので研究しがいがありそうです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】Well-Architected Frameworkをホワイトペーパーから紐解く ~信頼性~

はじめに

クラウドならではの強みである自動化をうまく活用・管理することで、より信頼性の高いアーキテクチャを設計することができます。

設計原則とベストプラクティス

設計原則と、ベストプラクティスに紐づくチェック項目を見てみましょう。
01.jpg

管理やモニタリングなど、セキュリティの柱と共通する文言が並びます。セキュリティが高い状態でないと、信頼性の高いアーキテクチャは設計できないませんね。ベストプラクティスの概要を抜き出してみます。

- 概要
基盤 信頼性に影響を与える基盤を整える
変更管理 変更がシステムに与える影響を把握し、変更があった場合にどうするか、事前に計画を作成できる状態にする
障害管理 論理エラーおよび物理エラーの両方から確実に復旧できる状態にする

候補サービス一覧

この柱のベースとなるサービスは、CloudWatchです。
下記のような構成が基本となります。

  • IAMを中心にセキュリティに守られた基盤を構築
  • CloudWatchなどで変更ログをトラッキング
  • Auto Scalingで必要に応じてリソースをスケール
  • 障害が起こった時のためにCloudFormationでテンプレを作成、ストレージにバックアップ

他のマネージドサービスも入れて、一覧表にしてみます。

02.jpg

まとめ

信頼性の高いアーキテクチャを設計するうえで留意しなくてはいけないポイントと、活用するサービスの概要、つかめましたでしょうか?次回はパフォーマンス効率の柱についてお送りします!

参考リンク

オフィシャル W-Aホワイトペーパー

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CentOS7でT2からT3へ変更時のNIC名変更への対応

はじめに

AWSでCentOS7で動いているEC2インスタンスをNitro世代(C5、M5、T3、…)に変更する際に
ハマったので、誰かの助けになればと思いメモっておきます。

通常の手順

Nitroシステムを使うにはENAを有効にする必要があり、AWSの公式リファレンスやググってみると
以下のような手順が出ます

  • ixgbevfモジュールを入れる
  • enaモジュールを入れる(kernelアップデート)
  • インスタンス停止
  • modify-instance-attributeでena-supportを有効にする
  • インスタンスタイプ変更
  • インスタンス起動

CentOS7の場合、上記だけで対応できない部分があったので、その解決方法を紹介します。

ハマりポイント

CentOS7からNIC名はensXのような名前となり、従来のethXではなくなっています。
ですが、AWS EC2インスタンスのNitro世代前のインスタンスタイプでは、デフォルトでeth0が
使われます。

これをNitro世代に変更するとensXという名称が使われるようになります。
(カーネル起動オプションなどで固定はできますが、クラウド上でそこいじりたくない、という前提)

そのままENA有効化して起動してもネットワークが繋がりません。
以下のその際にシスログに出力される内容です。MetaDataアクセスができていません。

Jun 25 10:47:02 sva0110 journal: [CLOUDINIT] url_helper.py[WARNING]: Calling 'http://169.254.169.254/2009-04-04/meta-data/instance-id' failed [87/120s]: unexpected error ['NoneType' object has no attribute 'status_code']

変更後の手順

  • ixgbevfモジュールを入れる
  • enaモジュールを入れる(kernelアップデート)
  • cloud-initアップデート
  • インスタンス停止
  • modify-instance-attributeでena-supportを有効にする
  • AMI作成
  • AMIからNitro世代インスタンスタイプを指定して起動

解説

最新版のcloud-init(現時点:18.2-1)ではインスタンス初回起動時の処理でethXからensXへの
変更も行ってくれるようです!
なので、AMIを作ってAMIからインスタンス作成すればNitro世代でも起動できるようになります。

まとめ

書いている途中に カーネル起動オプションを修正している公式な手順が見つかりました!!
https://aws.amazon.com/jp/premiumsupport/knowledge-center/install-ena-driver-rhel-ec2/
ということで、意味のない記事になってしまいましたが、もしかしたらこっちの手順の方がよいという
人もいるかもしれないので、そのまま公開します。

最後に、こっちもハマりどころだと思うので紹介しておきます。
NITRO世代(T3など)へのEC2インスタンスタイプ変更ではfstabに注意

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS IAMの管理ポリシーはたまに勝手にアップデートするので要注意

背景

今日たまたまAWSのマネジメントコンソールにログインしたとき、IAMの管理画面に「AWS Organizations」が増えていたことに気づきました。
ログイン中のIAM Userでは、Oranizationsに対する一切の権限は与えられていないはずなので、押しても何も見えないはずと思い、恐る恐る押してみると、なんとOrganizationsの設定が見えてしまいました。

IAMの管理画面がアップデートされた図

下部に「AWS Organizations」が増えています。
image.png

見えないはずのものが見えたの図

IAMの管理画面から、Organizationsの設定が見えています。
image.png

原因

Organizationsの権限が付与されたわけではない(その記憶がない)のに、なぜOrganizationsの設定が見えてしまうのか気になりました。
調査したところ、ログイン中のIAM Userには「IAMFullAccess」という管理ポリシーがアタッチされており、この管理ポリシーが最近アップデートされていました。
IAMの管理画面がアップデートされるのを機に、権限が増えたのでしょう。

IAMの管理ポリシーがアップデートされたの図

2019/6/22に、バージョンが増えています。
image.png

Version 1

権限は「iam:*」のみです。
image.png

Version 2

「organizations」の権限が増えています。
現在の設定は、デフォルトでこのVersion 2になるようです。
image.png

教訓

今回、IAMの管理ポリシーが勝手にアップデートされ、権限が増えるという場面に遭遇しました。
つまり、意図せず権限が勝手に増えることを防ぐためには、管理ポリシーを使わず、独自にポリシーを作成してアタッチしなければならないようです。
管理ポリシーは便利ですが、勝手にアップデートすることがあると理解して使うべし、ということですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS LambdaでAWS CLIから関数にLayerを追加・削除する方法

AWS CLIからLambda関数にLayerを追加・削除する方法がわかりにくかったのでメモ。

前提

  • AWSアカウントがある
  • AWS CLIが利用可能
  • 検証できるLambda関数がある
    • なければ新規作成

Lambda Layerを作成

検証で利用するLayerを作成します。今回は関数内で参照しないので、ファイル内に// だけ含んだファイルでLayerを作成します。空ファイルだとだめでした。
AWS CLIだとaws lambda publish-layer-version コマンドで作成ができます。

> echo '//' layer.js

> zip layer.zip layer.js

> aws lambda publish-layer-version \
  --layer-name test-layer-1 \
  --zip-file fileb://layer.zip \
  --compatible-runtimes nodejs8.10

{
    "Content": {
        "Location": "https://prod-04-2014-layers.s3.amazonaws.com/snapshots/(略)",
        "CodeSha256": "T4pRG6jK1iy8rjqOxxP2t3R1YSb5hl61Q90xHYHkePE=",
        "CodeSize": 168
    },
    "LayerArn": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:test-layer-1",
    "LayerVersionArn": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:test-layer-1:1",
    "Description": "",
    "CreatedDate": "2019-06-20T07:05:45.061+0000",
    "Version": 1,
    "CompatibleRuntimes": [
        "nodejs8.10"
    ]
}


> aws lambda publish-layer-version \
  --layer-name test-layer-2 \
  --zip-file fileb://layer.zip \
  --compatible-runtimes nodejs8.10

{
    "Content": {
        "Location": "https://prod-04-2014-layers.s3.amazonaws.com/snapshots/(略)",
        "CodeSha256": "T4pRG6jK1iy8rjqOxxP2t3R1YSb5hl61Q90xHYHkePE=",
        "CodeSize": 168
    },
    "LayerArn": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:test-layer-2",
    "LayerVersionArn": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:test-layer-2:1",
    "Description": "",
    "CreatedDate": "2019-06-20T07:07:28.453+0000",
    "Version": 1,
    "CompatibleRuntimes": [
        "nodejs8.10"
    ]
}

Lambda Layerを関数に追加

検証用の関数use-lambda-layer がある前提です。

Layerを指定する場合、上記にあるARNのうち、バージョンまで含まれているLayerVersionArn を指定する必要があります。複数Layerを追加するにはスペース区切りで指定します。

# 1つ追加
> aws lambda update-function-configuration \
  --function-name use-lambda-layer \
  --layers \
    "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:test-layer-1:1"

{
    "FunctionName": "use-lambda-layer",
    "FunctionArn": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:function:use-lambda-layer",
    "Runtime": "nodejs8.10",
    "Role": "arn:aws:iam::xxxxxxxxxxxx:role/service-role/xxxxxxxxxxxx",
    "Handler": "index.handler",
    "CodeSize": 262,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2019-06-20T07:11:20.502+0000",
    "CodeSha256": "F8AUyhyiHz5dsBaSl6+u86B5oiwWD3jtdi3IuvB0eKE=",
    "Version": "$LATEST",
    "VpcConfig": {
        "SubnetIds": [],
        "SecurityGroupIds": [],
        "VpcId": ""
    },
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "f8ad4fd8-b8d2-4aba-ba12-9f28759df79d",
    "Layers": [
        {
            "Arn": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:test-layer-1:1",
            "CodeSize": 168
        }
    ]
}


# 2つ追加
> aws lambda update-function-configuration \
  --function-name use-lambda-layer \
  --layers \
    "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:test-layer-1:1" \
    "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:test-layer-2:1"

{
    "FunctionName": "use-lambda-layer",
    "FunctionArn": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:function:use-lambda-layer",
    "Runtime": "nodejs8.10",
    "Role": "arn:aws:iam::xxxxxxxxxxxx:role/service-role/xxxxxxxxxxxx",
    "Handler": "index.handler",
    "CodeSize": 262,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2019-06-20T07:12:47.938+0000",
    "CodeSha256": "F8AUyhyiHz5dsBaSl6+u86B5oiwWD3jtdi3IuvB0eKE=",
    "Version": "$LATEST",
    "VpcConfig": {
        "SubnetIds": [],
        "SecurityGroupIds": [],
        "VpcId": ""
    },
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "8737bdfe-3b0d-404e-b3a0-c9fd57e971a9",
    "Layers": [
        {
            "Arn": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:test-layer-1:1",
            "CodeSize": 168
        },
        {
            "Arn": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:test-layer-2:1",
            "CodeSize": 168
        }
    ]
}

Lambda Layerを関数から削除

Layerを削除というよりは利用するLayerを再指定することになります。
すべて削除するには--layers [] として、空配列を指定する必要があります。わかりにくい

# 1つ削除
> aws lambda update-function-configuration \
  --function-name use-lambda-layer \
  --layers \
    "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:test-layer-2:1"

{
    "FunctionName": "use-lambda-layer",
    "FunctionArn": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:function:use-lambda-layer",
    "Runtime": "nodejs8.10",
    "Role": "arn:aws:iam::xxxxxxxxxxxx:role/service-role/xxxxxxxxxxxx",
    "Handler": "index.handler",
    "CodeSize": 262,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2019-06-20T07:13:27.749+0000",
    "CodeSha256": "F8AUyhyiHz5dsBaSl6+u86B5oiwWD3jtdi3IuvB0eKE=",
    "Version": "$LATEST",
    "VpcConfig": {
        "SubnetIds": [],
        "SecurityGroupIds": [],
        "VpcId": ""
    },
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "fa942867-332a-4d32-ae2d-31209ce9cc30",
    "Layers": [
        {
            "Arn": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:layer:test-layer-2:1",
            "CodeSize": 168
        }
    ]
}


# 全部削除
> aws lambda update-function-configuration \
  --function-name use-lambda-layer \
  --layers []

{
    "FunctionName": "use-lambda-layer",
    "FunctionArn": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:function:use-lambda-layer",
    "Runtime": "nodejs8.10",
    "Role": "arn:aws:iam::xxxxxxxxxxxx:role/service-role/xxxxxxxxxxxx",
    "Handler": "index.handler",
    "CodeSize": 262,
    "Description": "",
    "Timeout": 3,
    "MemorySize": 128,
    "LastModified": "2019-06-20T07:13:53.065+0000",
    "CodeSha256": "F8AUyhyiHz5dsBaSl6+u86B5oiwWD3jtdi3IuvB0eKE=",
    "Version": "$LATEST",
    "VpcConfig": {
        "SubnetIds": [],
        "SecurityGroupIds": [],
        "VpcId": ""
    },
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "c060cba7-2cf4-48af-88d3-4e5f653965a2"
}

参考

AWS Lambda Layers - AWS Lambda
https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html

publish-layer-version — AWS CLI 1.16.182 Command Reference
https://docs.aws.amazon.com/cli/latest/reference/lambda/publish-layer-version.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Fargate の「タスクのスケジューリング」で定時バッチ用コンテナを実行する

概要

  • AWS Fargate には、スケジュールルールに従ってタスクを実行できる機能がある。
  • スケジュールルールは次の2通りある。どちらもスケジュールルールに従って毎回新しいコンテナが起動して終了する。
    • 固定された間隔で実行
    • Cron式
  • この「タスクのスケジューリング」機能を使って、これまで EC2 上の crond で起動して実行していたバッチ処理を、コンテナ化して AWS Fargate に移行してみる。

今回 Fargate に移行するバッチの概要

  • Elasticsearch の インデックスを1日1回削除するバッチ。

    • サーバのアクセスログを Fluentd で Elasticsearch に送ってKibanaで可視化している。1日分のアクセスログを1つのインデックスに格納していて、30日前のインデックスファイルを削除するバッチを1日1回実行している。
  • バッチは bash スクリプトでこんな感じで書いている。

#!/bin/bash
ESDOMAIN="hogehoge.ap-northeast-1.es.amazonaws.com"
INDEXNAME=(
    "fastly-accesslog-"
    "Varnish-accesslog-"
    "nginx-accesslog-"
)
DATA=`date -d '30 days ago' +"%Y-%m-%d"`
for INDEX in ${INDEXNAME[@]}; do
  echo "Delete: ${INDEX}${DATA}"
  curl -s -X DELETE https://${ESDOMAIN}/${INDEX}${DATA}
  echo -e "\n"
done
echo "Execution Result"
curl -s -X GET ${ESDOMAIN}/_aliases?pretty | grep -v '}' | sort

バッチ処理を行うコンテナの Dockerfile 作成

FROM alpine:latest
RUN apk --no-cache add tzdata && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && apk del tzdata
RUN apk --no-cache add coreutils bash bash-completion curl
COPY del-index.sh /del-index.sh
RUN chmod u+x /del-index.sh
CMD /bin/bash /del-index.sh

Dockerfileの簡単な説明

軽量な Alpine Linux を使う

  • いろいろ削がれて軽量になっているので、Dockerfile で必要なものを追加する。

Alpine の timezone を JST に設定

dataコマンド

  • Alpine Linux のdataコマンドは、「date -d '30 days ago'」 を解釈してくれない。 なので、GNU coreutils の date をインストールする。
  • Dockerfile 追加設定
    RUN apk --no-cache add coreutils
    

bash

  • Alpine Linux のデフォルトのシェルは sh なので、配列が使えない。bashで書いた次の部分でエラーになってしまう。
INDEXNAME=(
    "fastly-accesslog-"
    "Varnish-accesslog-"
    "nginx-accesslog-"
)
  • シェルスクリプトを sh 用に書き直すのは面倒なので、bashを入れてしまう。
  • Dockerfile 追加設定
    RUN apk --no-cache add bash bash-completion
    

curl

  • 今回のバッチは、curl で Elasticsearch の API を叩くので、curl をインストールする。
  • Dockerfile 追加設定
    RUN apk --no-cache add curl
    

bashスクリプト本体

  • 今回は del-index.sh という名前で、上記のシェルスクリプトのファイルを作成する。これを Dockerfile と同じディレクトリに置く。
  • docker build 時に del-index.sh ファイルを docker イメージの / にコピーして、実行権限を付与する。
  • コンテナ起動時に、この del-index.sh を実行する。
  • Dockerfile 追加設定
    COPY del-index.sh /del-index.sh
    RUN chmod u+x /del-index.sh
    CMD /bin/bash /del-index.sh
    

DockerイメージをECRへプッシュして、Fargete の設定を行う

Fargate タスクのスケジューリング 設定

  • Amazon ECS クラスター設定画面 ⇒ タスクのスケジューリング ⇒ 作成
    01.png

  • スケジュールルール設定
    今回は、毎日6時に実行するので、Cron 式は cron(0 6 * * ? *)
    02.png

  • ターゲット設定
    起動タイプで「FARGATE」を選択する。
    タスク定義で上記で作成したものを選択する。
    03.png

  • VPC とセキュリティグループ
    AWS Fargate で実行したコンテナに固定のグローバルIPアドレスを割り当てる を参考

  • 作成
    以上で、毎朝 06:00 に Elasticsearch のインデックスを削除するコンテナが起動して、処理を終えたらコンテナが消滅する。
    実行ログは CloudWatch ログに送られる。

おまけ

Slack にも実行結果を送りたい場合は、del-index.sh に Slack へ送る設定を追加する

#!/bin/bash
ESDOMAIN="hogehoge.ap-northeast-1.es.amazonaws.com"
INDEXNAME=(
    "fastly-accesslog-"
    "Varnish-accesslog-"
    "nginx-accesslog-"
)
DATA=`date -d '30 days ago' +"%Y-%m-%d"`

exec 3>&1
exec > /exec.log
echo "Elasticsearch delete index"
for INDEX in ${INDEXNAME[@]}; do
    echo "Delete Index: ${INDEX}${DATA}"
    curl -s -X DELETE https://${ESDOMAIN}/${INDEX}${DATA}
    echo -e "\n"
done
echo "curl -X GET https://${ESDOMAIN}/_aliases?pretty"
curl -s -X GET ${ESDOMAIN}/_aliases?pretty | grep -v '}' | sort
exec 1>&3

sed -i -e 's/\"//g' /exec.log

channel="hoge"
username="Container"
text="Elasticsearch delete index"
color="gray"
message=`cat /exec.log`
attachments="
{
    \"color\":\"${color}\",
    \"text\": \"${message}\"
}"
emoji=':envelope:'
url="https://hooks.slack.com/services/hoge/hogehoge"
payload="payload={\"channel\": \"${channel}\", \"username\": \"${username}\", \"text\": \"${text}\", \"attachments\":[${attachments}] , \"icon_emoji\": \"${emoji}\"}"
curl -s -m 5 -d "${payload}" $url

exit
  • 標準出力をファイルへ出力する設定
    exec 3>&1
    exec > /exec.log
    〜
    exec 1>&3
    
  • 実行結果ファイルからダブルクォーテーションを除外(Slack送信時に invalid_payload エラーとなるため)
    sed -i -e 's/\"//g' /exec.log

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Fargate の「タスクのスケジューリング」でバッチ用コンテナを定時実行する

概要

  • AWS Fargate には、スケジュールルールに従ってタスクを実行できる機能がある。
  • スケジュールルールは次の2通りある。どちらもスケジュールルールに従って毎回新しいコンテナが起動して終了する。
    • 固定された間隔で実行
    • Cron式
  • この「タスクのスケジューリング」機能を使って、これまで EC2 上の crond で起動して実行していたバッチ処理を、コンテナ化して AWS Fargate に移行してみる。

今回 Fargate に移行するバッチの概要

  • Elasticsearch の インデックスを1日1回削除するバッチ。

    • サーバのアクセスログを Fluentd で Elasticsearch に送ってKibanaで可視化している。1日分のアクセスログを1つのインデックスに格納していて、30日前のインデックスファイルを削除するバッチを1日1回実行している。
  • バッチは bash スクリプトでこんな感じで書いている。

#!/bin/bash
ESDOMAIN="hogehoge.ap-northeast-1.es.amazonaws.com"
INDEXNAME=(
    "fastly-accesslog-"
    "varnish-accesslog-"
    "nginx-accesslog-"
)
DATA=`date -d '30 days ago' +"%Y-%m-%d"`
for INDEX in ${INDEXNAME[@]}; do
  echo "Delete: ${INDEX}${DATA}"
  curl -s -X DELETE https://${ESDOMAIN}/${INDEX}${DATA}
  echo -e "\n"
done
echo "Execution Result"
curl -s -X GET ${ESDOMAIN}/_aliases?pretty | grep -v '}' | sort

バッチ処理を行うコンテナの Dockerfile 作成

FROM alpine:latest
RUN apk --no-cache add tzdata && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && apk del tzdata
RUN apk --no-cache add coreutils bash bash-completion curl
COPY del-index.sh /del-index.sh
RUN chmod u+x /del-index.sh
CMD /bin/bash /del-index.sh

Dockerfileの簡単な説明

軽量な Alpine Linux を使う

  • いろいろ削がれて軽量になっている。
  • 今回のシェルスクリプトを実行するために必要なものを追加する。

Alpine Linux の timezone を JST に設定

dataコマンド

  • Alpine Linux のdataコマンドは、「date -d '30 days ago'」 を解釈してくれない。 なので、GNU coreutils の date をインストールする。
  • Dockerfile 追加設定
    RUN apk --no-cache add coreutils
    

bash

  • Alpine Linux のデフォルトのシェルは sh なので、配列が使えない。bashで書いた次の部分でエラーになってしまう。
INDEXNAME=(
    "fastly-accesslog-"
    "Varnish-accesslog-"
    "nginx-accesslog-"
)
  • シェルスクリプトを sh 用に書き直すのは面倒なので、bashを入れてしまう。
  • Dockerfile 追加設定
    RUN apk --no-cache add bash bash-completion
    

curl

  • 今回のバッチは、curl で Elasticsearch の API を叩くので、curl をインストールする。
  • Dockerfile 追加設定
    RUN apk --no-cache add curl
    

bashスクリプト本体

  • 今回は del-index.sh という名前で、上記のシェルスクリプトのファイルを作成する。これを Dockerfile と同じディレクトリに置く。
  • docker build 時に del-index.sh ファイルを docker イメージの / にコピーして、実行権限を付与する。
  • コンテナ起動時に、この del-index.sh を実行する。
  • Dockerfile 追加設定
    COPY del-index.sh /del-index.sh
    RUN chmod u+x /del-index.sh
    CMD /bin/bash /del-index.sh
    

DockerイメージをECRへプッシュして、Fargete の設定を行う

Fargate タスクのスケジューリング 設定

Fargate タスクのスケジューリングの概要

タスクのスケジューリング設定例

  • Amazon ECS クラスター設定画面 ⇒ タスクのスケジューリング ⇒ 作成
    01.png

  • スケジュールルール設定
    今回は、毎日午前 6:00 (JST) に実行したいので、Cron 式は cron(0 21 * * ? *)

04.png

  • ターゲット設定
    起動タイプで「FARGATE」を選択する。
    タスク定義で上記で作成したものを選択する。
    03.png

  • VPC とセキュリティグループ
    AWS Fargate で実行したコンテナに固定のグローバルIPアドレスを割り当てる を参考

  • 作成
    以上で、毎日午前 6:00 (JST) に Elasticsearch のインデックスを削除するコンテナが起動して、処理を終えたらコンテナが消滅する。
    実行ログは CloudWatch ログに送られる。

おまけ

Slack にも実行結果を送りたい場合は、del-index.sh に Slack へ送る設定を追加する

#!/bin/bash
ESDOMAIN="hogehoge.ap-northeast-1.es.amazonaws.com"
INDEXNAME=(
    "fastly-accesslog-"
    "Varnish-accesslog-"
    "nginx-accesslog-"
)
DATA=`date -d '30 days ago' +"%Y-%m-%d"`

exec 3>&1
exec > /exec.log
echo "Elasticsearch delete index"
for INDEX in ${INDEXNAME[@]}; do
    echo "Delete Index: ${INDEX}${DATA}"
    curl -s -X DELETE https://${ESDOMAIN}/${INDEX}${DATA}
    echo -e "\n"
done
echo "curl -X GET https://${ESDOMAIN}/_aliases?pretty"
curl -s -X GET ${ESDOMAIN}/_aliases?pretty | grep -v '}' | sort
exec 1>&3

sed -i -e 's/\"//g' /exec.log

channel="hoge"
username="Container"
text="Elasticsearch delete index"
color="gray"
message=`cat /exec.log`
attachments="
{
    \"color\":\"${color}\",
    \"text\": \"${message}\"
}"
emoji=':envelope:'
url="https://hooks.slack.com/services/hoge/hogehoge"
payload="payload={\"channel\": \"${channel}\", \"username\": \"${username}\", \"text\": \"${text}\", \"attachments\":[${attachments}] , \"icon_emoji\": \"${emoji}\"}"
curl -s -m 5 -d "${payload}" $url

exit
  • 標準出力をファイルへ出力する設定
    exec 3>&1
    exec > /exec.log
    〜
    exec 1>&3
    
  • 実行結果ファイルからダブルクォーテーションを除外(Slack送信時に invalid_payload エラーとなるため)
    sed -i -e 's/\"//g' /exec.log

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker-lambdaで関数連続実行をワンコンテナで

ワンコンテナでlambada関数を連続実行したい

lambci/docker-lambdaでローカルテストする際に、下記のように普通に関数を連続実行すると...

docker run --rm -v "$PWD":/var/task lambci/lambda:python3.6 sample_function.lambda_handler '{"Hello":"World"}'
docker run --rm -v "$PWD":/var/task lambci/lambda:python3.6 sample_function.lambda_handler '{"Hello":"World"}'

下記のようにコンテナが別々に生成される。(※docker container statsの結果を表示↓↓)
コメント 2019-06-25 001242.png

これをワンコンテナでやりたい場合は、下記のようにentrypointを上書きしてやれば可能。

docker run --rm -v "$PWD":/var/task --entrypoint "" lambci/lambda:python3.6 bash -c " \
   python3.6 /var/runtime/awslambda/bootstrap.py sample_function.lambda_handler '{\"Hello\":\"World\"}' & \
   python3.6 /var/runtime/awslambda/bootstrap.py sample_function.lambda_handler '{\"Hello\":\"World\"}' \
"

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む