20200401のAWSに関する記事は10件です。

Systems Managerエージェントが通信できないEC2一覧の取得

環境

以下の条件に当てはまる環境で、よく発生する問題です。

  • 多数のEC2を構築・運用している
  • Systems Manager(SSM)を使用できるように各種セットアップ済みである
  • Systems ManagerのランコマンドやPatch Manager等を利用している

多数のEC2でSystems Managerを利用すると

正しく構成していれば、各EC2のSystems Managerエージェント(SSMエージェント)はSystems Managerサービスと通信できます。
通常は、何ら問題ないです。

しかしながら何らかの原因で、一部のEC2でSSMの通信ができないケースが出てきます。
(原因は本当に色々あって、IAMの設定不備、VPCルーティング設定不備、セキュリティグループ設定不備、SSMエージェントが落ちている、SSMエージェントのバージョンが古い、169.254.169.xxx の通信など、、多岐にわたります)
多数のEC2を運用していると、SSMの通信できないEC2の原因を調べるよりも、どのEC2が通信できててどのEC2が通信できてないかをそもそも見つけにくい 状況だったりします。

SSMの通信NGをなぜ見つけにくいのか

マネージメントコンソールで、Systems Managerのマネージドインスタンスを見ると、対象EC2のリストが確認できます。
ただし表示されるのはSSMエージェントと通信が確立できたEC2であり、全く通信できていないEC2はリストに表示されません。
また、EC2を停止するとリストからも消えてしまいます。

API/CLIで通信できないEC2一覧の取得

そこで2つのコマンドを使います。

コマンド1つ目
aws ec2 describe-instance-status
APIの場合は、
ec2:DescribeInstanceStatus
を実行すると、稼働している(runningステータス)のEC2一覧が取得できます。

そしてコマンド2つ目
aws ssm describe-instance-information
APIの場合は、
ssm:DescribeInstanceInformation
を実行すると、SSMエージェントの通信OKなEC2一覧が取得できます。

欲しいのはrunningのEC2にもかかわらずSSMの通信NGの一覧です。
それを得るには、コマンド1つ目のEC2一覧 から コマンド2つ目のEC2一覧を引いたもの を抽出すればよいです。
インスタンスIDで突き合せられます。

インスタンスIDじゃ分からないよ!という場合はさらにタグなどを取得します。
https://qiita.com/t-fujiwara/items/835cccbef7ec6d199251

なお、停止しているEC2は調べようがありません。まずは開始させましょう。


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

ローカル開発から本番環境へのエラー対策

ローカル開発環境からのアップロードができない場合

環境変数を設定し、それをCarrierWaveから使えるようにするには、以下の3箇所の設定が正しい必要があります。
1、CarrierWaveの設定ファイル(CarrierWave.rb)
2、Railsアプリケーション全体の秘密情報を管理するファイル(secrets.yml)
3.OSが提供するデータ共有の仕組み、漏洩のリスクが低い(環境変数)
キーを安全に運用するための仕組みは、CarrierWave.rb=>secrets.yml=>環境変数
となります。

環境変数およびsecrets.ymlの設定が正しいか

1、ChatSpaceがあるフォルダで、「rails c」を実行します。
2、コンソール内で、以下のコマンドを実行します。
コンソール

Rails.application.secrets.aws_access_key_id
Rails.application.secrets.aws_secret_access_key

「Rails.application.secrets」+「.キーの名前」を実行する事でsecrets.ymlの設定を呼び出すことができます。
上の2つのコマンドを実行して、正しくAWSのキーとパスワードが表示されればOKです。
うまくいかなかった場合は、bash_profileの内容を確認します。
※OSがCatalina以降で環境構築をしている方は、zshというシェルを使用しているので、bash_profileの部分をzshrcと置き換えてください。
ターミナル

cat ~/.bash_profile

表示されたbash_profileの中に、以下の記述があるか確認してください。
~/.bash_profile

export AWS_SECRET_ACCESS_KEY='AWSのシークレットキー'
export AWS_ACCESS_KEY_ID='AWSのアクセスキー'

もしなければ、AWSの設定ができていないので、確認して追加してください。
次に、bash_profileの内容を反映させます。
ターミナル

source ~/.bash_profile

bash_profileにコマンドを書いただけでは設定は反映しません。
次は、carrierwave.rbの設定を確認します。

config/initializers/carrierwave.rb
require 'carrierwave/storage/abstract'
require 'carrierwave/storage/file'
require 'carrierwave/storage/fog'

CarrierWave.configure do |config|
  config.storage = :fog
  config.fog_provider = 'fog/aws'
  config.fog_credentials = {
    provider: 'AWS',
    aws_access_key_id: Rails.application.secrets.aws_access_key_id,
    aws_secret_access_key: Rails.application.secrets.aws_secret_access_key,
    region: 'ap-northeast-1'
  }

  config.fog_directory  = 'ここにバケット名を入れます'
  config.asset_host = 'https://s3-ap-northeast-1.amazonaws.com/ここにバケット名を入れます'
end

このような記述になっているかを確認してください。

バケットの指定が正しいかの確認

AWSのサービスからS3を選択し、バケットを表示させます。先ほどのcarrierwave-rbでS3のバケット名を指定しています。その内容が正しいか確認してください。
バケットの指定は「バケット名」「リージョン」の両方を正しく行う必要があります。
・バケット名が正しく設定できているか
・S3のリージョンが「アジアパシフィック(東京)」になっているか
を確認してください。もし違ったら直してください。

本番環境からのアップロードができない場合

S3へのアップロードができない場合、まずエラーメッセージを確認します。
ローカルでは、仮想サーバーを起動したターミナルを見ればエラーメッセージが表示されていました。
しかし、本番環境ではローカルのターミナルに当たるものがありません。

Capistranoが動く場合

以下の設定を行うと、本番環境でもローカル開発環境と同じように、ブラウザにエラーメッセージが表示されるようになります。

config/environments/production.rb
〜省略〜
  config.consider_all_requests_local = true
〜省略〜

「config/environments/production.rb」のファイルの中から、上記の記述を見つけ、もともと「false」になっているので、「true」に変更します。
この変更を本番環境に反映させる必要があるので以下の2つの操作を行います。
①GitHubへのプッシュ
②「bundle exec cap production deploy」の実行
なお、エラー対応が終わったら、忘れずにfalseに戻してデプロイをし直してください。

Capistranoがエラーになる場合

Capistranoがエラーになってしまう場合は、上記の方法が取れません。
なので、エラーログを直接確認します。
エラーの内容によって、主に以下の2つのファイルにログが書き込まれます。
どちらに該当するかわかりにくい場合は、両方とも確認してください。

Unicornのエラーログの確認方法

ターミナル

cat /var/www/(アプリ名)/current/log/unicorn.stderr.log 

ターミナル

cat /var/www/(アプリ名)/current/log/production.log

Railsアプリのエラーログの確認方法
エラーログの見方
・ファイルの下に行くほど新しいログが載っています
・いつのログなのかを必ず確認しましょう
まず、ログの中にタイムスタンプがあったら、そこに9時間を足しましょう(標準時での出力になるため、日本は9時間の時差があります)。その時刻と、今の時刻を見比べて今起きたエラーなのかをチェックします。
エラーメッセージが出ない場合や、原因を特定できない場合は以下のチェックを行いましょう。
1. 環境変数が正しく設定できているか確認してください。
リモートでの環境変数が正しく設定されていないとS3へのアクセスができません。下記のコマンドで確認します。
ローカルのターミナル

ssh -i ~/.ssh/<pemファイルの名前> ec2-user@<IP>

sshでリモートにログインします。
リモートのターミナル

> env | grep AWS

出力された結果が、AWSのキー、パスワードと一致するか確認してください。
なお、「|」は複数の処理を行うためのもので、「A | B」と書くことで、Aの処理が終わったらBの処理をする、という指定ができます。
envは、現在設定されている環境変数を確認するためのコマンドです。
grepは文字の検索をして絞込みを行ってくれるコマンドです。そのあとに「AWS」と付ける事で、「AWS」という文字列を含んだ行のみ表示がされます。
つまり、「env | grep AWS」とコマンドを実行する事で、環境変数の中に「AWS」という文字を含んだものだけを出力してほしい、という意味になります。

変更後のコードが本番に反映しているかの確認

ローカル開発環境での変更が、本番環境にうまく反映されていないときは、以下の確認をしてください。
プッシュ先のGitHubを直接確認してください。
最後に変更したファイルをGitHubでチェックして、変更点が反映しているか確認してください。
万が一プッシュができていない場合は、もう一度プッシュを行ってください。
GitHubにプッシュされている場合は、EC2へのプルで失敗している可能性もあるので、以下の確認をしてみてください。
ローカルのターミナル

ssh -i ~/.ssh/<pemファイルの名前> ec2-user@<IP>

sshでリモートにログインをします。
最新のファイルは以下のフォルダ内に格納されているので、最後の変更が反映しているか確認してください。

/var/www/(アプリ名)/current/app

このフォルダ以下にファイルが格納されているので、catコマンドで内容を確認してください。

サーバーを再起動

コードが最新になっているのにアプリに反映しないのは、サーバーの再起動ができていない場合があります。念の為、NginxおよびUnicornの再起動をしてから、再度アプリで確認してみてください。
Nginxの再起動
リモートのターミナル

sudo service nginx restart

Unicornの再起動
リモートのターミナル

> ps aux |grep unicorn
# Unicornのプロセスidを確認する
> kill -9 (unicornのプロセスid)

ローカルのターミナル

> bundle exec cap production deploy

これで、デプロイできました。

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

Chalice を使ってみるなかで調べた小ネタ3点

1. 静的ファイルを Lambda 関数に含める方法

  • シナリオ: 簡単な設定ファイル (YAML, JSONなど) を Lambda 関数内で読み込むファイルとして扱いたい。
  • 問題: app.py と同じディレクトリに env.yml を置いたが、 chalice deploy してもファイルがアップロードされなかった
  • 解決策: chalice プロジェクトの下に chalicelib というディレクトリを作って、そこにファイルを置く。 以下のtreeの結果を参照。
$ tree -a .
.
├── .chalice
│   └── config.json
├── .gitignore
├── app.py
├── chalicelib
│   └── env.yml
└── requirements.txt

なお、chaliceでデプロイをする場合、Linux/Mac などの場合はファイルのパーミッションをそのまま受け継ぐ。 そのため、例えば 750 などのファイルをデプロイした場合、そのファイルを読もうとすると実行時に Permission Denied となってしまうため注意。

  • 同様の事例 。アップロード時に作った zip file を展開するときにおそらく保存時のパーミッションをそのまま展開していることが原因。

2. IAM Policy が自動生成されない / 自分でPolicyを設定する方法

  • シナリオ: Chalice の便利な機能の1つにIAM Policyの自動生成がある。 これは、Lambda 内で boto3 ライブラリを使ってAWSリソースにアクセスしている場合、それを自動的に判断してIAM Policyを動的に自動生成してくれる機能である。
  • 問題: なぜか自分のプログラムではこの自動生成・付与を行ってくれなかった。

調べてみると、boto3.client を利用したプログラムのみ自動生成の対象 であるのが原因だった。 boto3 には resource を使う方法もあるのだが、こちらを使った場合ですらIAMの自動生成の対象にならない。 自分のプログラムは全て boto3.resource を利用していた。

そのため、IAM Policy を自動生成するため、 Chaliceでboto3を使うときは常に boto3.client を利用する というのは1つの指針になり得る。
逆に、既存のIAM Roleを利用したい、自分でPolicyの記述内容を管理したい、というケースの場合は .chalice/config.json 内で autogen_policy を無効にする。 例えば以下の通り。

.chalice/config.jsonから抜粋
  "stages": {
    "dev": {
      "api_gateway_stage": "api",
      "autogen_policy": false
    }
  }

これを無効にした場合、デフォルトでは .chalice 内の policy-<stage name>.json (この場合は policy-dev.json) を読み込む。 別名を指定したい場合は iam_policy_file でファイル名を指定する。 より詳細な説明はこちら

.chalice/policy-dev.jsonにはRole内でのアクセス権限を書いていけばよい
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:*"
      ],
      "Resource": [
        "*"
      ],
      "Sid": "xxxx-xxxx-xxxx-xxxx-1234"
    },
    {
        "Effect": "Allow",
        "Action": [
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:PutLogEvents"
        ],
        "Resource": "arn:aws:logs:*:*:*",
        "Sid": "xxxx-xxxx-xxxx-xxxx-1235"
    }
  ]
}

外部のライブラリなどを使いたい場合、boto3.resource を利用する場合はこちらを採用することになるだろう。 IAM権限の関係から、そもそもIAMの操作権限がない場合などはIAM Role ARNを指定して付与することもできる (iam_role_arn) 。

client しか IAM Policy の自動生成にならないという記述は、例えば AWS BlackBelt シリーズでの説明 とか この記事よりもはるかに詳しい説明とかで見つけることはできたが、公式ドキュメント内でこの記述を見つけられなかった。 あっても良いはずなのだが……

3. CORS対応

  • シナリオ: ブラウザから ajax で API Gateway - Lambda をコールし、レスポンスを取得したい
  • 問題: ブラウザから ajax でアクセスしたら CORS が原因ではじかれる。 chalice local で稼働させている場合も同様。

ユースケースに合わせた CORS の設定をすることになるのだが、もうこれはそのまま 公式を読んでもらう方が早い ぐらい説明が充実している。

概略としては chalice.CORSConfig のインスタンスに適当な設定をした後、@app.route(path, cors=config) のようにデコレータ内の cors に対して設定を渡してやればあとはよしなにしてくれる。

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

awscli v2でタブ補完が効かない場合の対処(macOS Catalina)

環境

  • mac
  • macOS Catalina(10.15.4)
  • zsh 5.7.1 -aws-cli/2.0.6 Python/3.7.4 Darwin/19.4.0 botocore/2.0.0dev10

事象

mac OS Catalinaでawscliv2をインストールした際にタブ補完が効かない事象に遭遇した。
結論から言うと、macで使用しているデフォルトのzshでcompleteコマンドが command not found なのが原因

解決策

bashの互換モードを有効にしてcompleteコマンドを読み込ませる。

~/.zshrcに以下3行を追加して様子を見てみる。
追記したら~/.zshrcを再度読み込みます。

autoload bashcompinit
bashcompinit
complete -C '/usr/local/bin/aws_completer' aws

aws_completerの場所は事前にwhich aws_completerで調べ、自身の環境の値に変えてください。

下記コマンドを実行して~/.zshrcに追加します。.zshrcなければ作ってください。

command
cat << _EOF_ >> ~/.zshrc
# awscli
autoload bashcompinit
bashcompinit
complete -C '/usr/local/bin/aws_completer' aws
_EOF_
source ~/.zshrc

とりあえずこの方法で自分の環境では補完が効くようになりました。(2020年4月 現在)
そのうち何かしなくても補完効くようになるかもしれませんが同様の事象の方は試してみてください。

参考

macOS での AWS CLI バージョン 2 のインストール
コマンド補完
v2 : AWS ZSH completer does not exist #4950

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

CDKでAPI Gatewayにステージを追加してもLambdaのパーミッションは自動で追加されない

CDKでAPi Gateway+Lambdaの構成を作りました。
Gatewayのステージを2つ持たせて、ステージ毎に変数を持たせてLambdaを叩くという構成にしたかったのですが、追加したステージのパーミッションがLambdaに自動で追加されなかったので、CfnPermissionを使って自分で追加する必要がありました。
このことにちょっとハマって時間を要したので、コードを残しておきます。

環境

CDK CLI: 1.27.0

コード

cdk-lambda-stack.ts
import * as cdk from '@aws-cdk/core'
import * as lambda from '@aws-cdk/aws-lambda'
import * as apigateway from '@aws-cdk/aws-apigateway'
import * as iam from '@aws-cdk/aws-iam'

export class CdkLambdaStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props)
    // Lambdaの定義
    const lambdaFn = new lambda.Function(this, 'function', {
      runtime: lambda.Runtime.PYTHON_3_8,
      handler: 'lambda_function.lambda_handler',
      code: lambda.Code.asset('lambda_asset')
    })

    // API Gatewayの定義
    const api = new apigateway.RestApi(this, 'api', {
      deployOptions: {
        stageName: 'first stage name',
        variables: {foo: 'bar'}
      }
    })

    // ステージの追加
    const stage = new apigateway.Stage(this, 'stage', {
      deployment: new apigateway.Deployment(this, 'stage', {api: api}),
      stageName: 'second stage',
      variables: {foo: 'bar_bar'}
    })
    api.root.addResource(lambdaFn.functionName).addMethod('POST', new apigateway.LambdaIntegration(lambdaFn))

    // 追加したステージからのPermissionをlambdaに追加が必要
    new lambda.CfnPermission(this, 'secondStageInvoke', {
      action: 'lambda:InvokeFunction',
      functionName: lambdaFn.functionName,
      principal: 'apigateway.amazonaws.com',
      sourceArn: 'arn:aws:execute-api:' + this.region + ':' + this.account + ':' + api.restApiId + '/' + stage.stageName + '/POST/' + lambdaFn.functionName
    })
  }
}

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

AWSのRDSが日本語対応にならない場合の対処法

はじめに

 Ruby on Rails初心者です。今回はアプリケーションのデプロイ時に苦戦した箇所があったので
 勉強のために備忘録として残したいと思います。

前提

  Rails 5.2.4

問題

 ・AWSのRDSでMySQLインスタンスを作成
 (RDSのMySQLの文字コードは、初期設定は「latin」)
 ・その後「パラメータグループ」で日本語対応にするも本番環境で
  日本語対応されておらず、、

解決方法

 ・EC2にSSHで接続

ssh -i /Users/ユーザー名/.ssh/キーペア名.pem ec2-user@xx.xx.xx.xx

 ・EC2からRDSにアクセス(パスワード要求されます)

mysql -h エンドポイント -P Port -u ユーザ名 -p データベース名

 ・データベースの状態を確認

mysql> show variables like 'char%';

 ・以下のように表示される

+--------------------------+-------------------------------------------+
| Variable_name            | Value                                     |
+--------------------------+-------------------------------------------+
| character_set_client     | utf8mb4                                   |
| character_set_connection | utf8mb4                                   |
| character_set_database   | latin1                                   |
| character_set_filesystem | utf8mb4                                   |
| character_set_results    | utf8mb4                                   |
| character_set_server     | utf8mb4                                   |
| character_set_system     | utf8                                      |
| character_sets_dir       | /rdsdbbin/mysql-5.7.22.R5/share/charsets/ |
+--------------------------+-------------------------------------------+

 「character_set_database」が「 latin1 」 になっていたので、これを「utf8mb4」に直す
 
 ・データベースの文字コード修正

mysql> ALTER DATABASE データベース名 default character set utf8mb4;

 ・再度データベースの状態を確認

+--------------------------+-------------------------------------------+
| Variable_name            | Value                                     |
+--------------------------+-------------------------------------------+
| character_set_client     | utf8mb4                                   |
| character_set_connection | utf8mb4                                   |
| character_set_database   | utf8mb4                                |
| character_set_filesystem | utf8mb4                                   |
| character_set_results    | utf8mb4                                   |
| character_set_server     | utf8mb4                                   |
| character_set_system     | utf8                                      |
| character_sets_dir       | /rdsdbbin/mysql-5.7.22.R5/share/charsets/ |
+--------------------------+-------------------------------------------+

  これで日本語化に対応できた、、、と思ったができておらず(^^;)

 原因はテーブルをすでに作成していたため、テーブルの文字コードも変更しなければ
 ならなかった。

・テーブルの文字コード変更

mysql> ALTER TABLE テーブル名 CONVERT TO CHARACTER SET utf8md4

 自分で作成した全てのテーブルを変更し、問題解決した

参考にさせていただいた記事

https://qiita.com/kijitora-neko/items/aab58b4c1f684353e075

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

AWS公式さんがDocker Hubで aws-cli のイメージを公開してくれた!

AWS公式さんがDocker Hubでイメージを公開してくれました。 (2020/03/31)
待ってました :tada: :tada: :tada:

AWS CLI v2 Docker image | AWS Developer Blog
https://aws.amazon.com/jp/blogs/developer/aws-cli-v2-docker-image/

(Google翻訳) AWS CLI v2の2.0.6のリリースにより、AWS CLI v2がDockerイメージとして利用できるようになったことをお知らせします。

これにより、ユーザーはAWS CLI v2のインストールを自分で管理する必要なく、コンテナベースの環境でAWS CLI v2を使用できます。このDockerイメージを利用するにはさまざまな方法がありますが、特にCI / CD設定では、ローカルマシンのDockerコンテナでAWS CLI v2を実行する方法について説明します。

amazon/aws-cli - Docker Hub
https://hub.docker.com/r/amazon/aws-cli

使い方も、かなーり詳しくしてくれているので詳細はDocker Hubをご確認ください。

前にaws-cli を実行するためにDockerイメージを作成して利用していた ( DockerコンテナからAWSコマンドを簡単に実行できるコマンドをつくってみた - Qiita ) のですが、その手間が省けます。はかどります。
バージョンは2.0.6 となっており (2020/04/01 時点)、今後のバージョンアップにも対応してくれることでしょう (期待)

つかってみる

使い方は簡単です。

エイリアス設定して利用する設定があったので、それを利用してみます。

認証や設定情報を含むディレクトリを-v ~/.aws:/root/.aws でマウント、コマンド実行するカレントディレクトリを-v $(pwd):/aws でDockerコンテナ内の/aws にマウントすることでファイルの読み書きもホストでaws コマンドを実行するときと同じ挙動になります。

# 確認用としてエイリアスを aws ではなく、 aws-test としています
> alias aws-test='docker run --rm -ti -v ~/.aws:/root/.aws -v $(pwd):/aws amazon/aws-cli'

# Fishの場合
> alias aws-test='docker run --rm -ti -v ~/.aws:/root/.aws -v $PWD:/aws amazon/aws-cli'

> aws-test --version
aws-cli/2.0.6 Python/3.7.3 Linux/4.19.76-linuxkit botocore/2.0.0dev10

# カレントディレクトリで読み書きできる
> aws-test s3 cp ./hoge.txt s3://<任意のバケット>/

# カレントディレクトリ外はだめ
> aws-test s3 cp ../hoge.txt s3://<任意のバケット>/

これでもうイメージをビルドしなくてもいいんやなって。

参考

amazon/aws-cli - Docker Hub
https://hub.docker.com/r/amazon/aws-cli

DockerコンテナからAWSコマンドを簡単に実行できるコマンドをつくってみた - Qiita
https://qiita.com/kai_kou/items/99d9f9372eed970eab3b

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

AWS で No space left on device と出た時

はじめに

AWSでアプリケーションを動かしている時に、No space left on device というエラーが出てきたので、詳しく調べてまとめてみた。

対処法

dfというコマンドを使用して、ディスクの使用量を確認する。

ターミナル
$ df -h

Filesystem      Size  Used Avail Use% Mounted on
/dev/xvda1       16G   16G    0G 100% /
devtmpfs        993M   56K  993M   1% /dev
tmpfs          1001M     0 1001M   0% /dev/shm

次にduというコマンドを使用して、どのフォルダがディスク容量を多く使っているかを特定する。

ターミナル
du -h | sort -rh | head -5

 10G    .
6.5G    ./hoji
4.5G    ./hoji/log
1.1G    ./hoji
1.2G    ./hoji

ここでは、/hoji/logというフォルダが容量を多く使用していることがわかるので、logの中身を削除してやると、ディスク容量に空きができる。

以上

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

Amazon CloudWatch EventsのイベントルールでターゲットをAmazon SQSにしてAWS Lambdaでイベントソースにして処理するAWS CloudFormationのテンプレートをつくってみた

AWSマネジメントコンソールからだと簡単に設定できましたが、AWS CloudFormationのテンプレート化するのにいろいろとハマったのでメモ。

リソース

必要最低限となる構成はこんな感じになりました。
スクリーンショット 2020-03-26 14.04.41.png

利用するサービスは以下になります。

  • Amazon S3
  • AWS CloudTrail
  • Amazon CloudWatch Events
  • Amazon SQS
  • AWS Lambda
  • AWS CloudFormation(リソース管理用)

ポイント

先にポイントをいくつかあげてみます。
完成形のテンプレートはこのあとにおいてます。

Amazon S3のバケットを複数用意する

Amazon CloudWatch EventsでAmazon S3のイベントを扱うのにAWS CloudTrailも必要になります。

Amazon S3 ソースの CloudWatch イベント ルールを作成する (コンソール) - CodePipeline
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/create-cloudtrail-S3-source-console.html

AWS CloudTrail 証跡を作成にはログファイルを出力するAmazon S3のバケットがいりますが、これをイベントを扱いたいバケットにしてしまうと。。。あとはわかりますね。無限ループに陥ります。

また、AWS CloudTrailでログを出力しない設定にすると、イベントが発火しませんでした。

AWS CloudTrailのログファイルを保存するキー名は固定

ログファイルを保存する先はバケット名/AWSLogs/AWSアカウントID/* と指定する必要があります。

テンプレート抜粋
  CloudTrailBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref OutputBucket
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: s3:GetBucketAcl
            Resource: !GetAtt OutputBucket.Arn
          - Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: s3:PutObject
            Resource: !Join
              - ""
              - - !GetAtt OutputBucket.Arn
                - "/AWSLogs/"
                - !Ref "AWS::AccountId"
                - "/*"
            Condition:
              StringEquals:
                s3:x-amz-acl: bucket-owner-full-control

AWSLogs を別名にしてみたらAWS CloudFormationのスタック作成でエラーになりました。

Incorrect S3 bucket policy is detected for bucket: <ProjectName>-output (Service: AWSCloudTrail; Status Code: 400; Error Code: InsufficientS3BucketPolicyException; Request ID: 3a9a7575-5226-4f19-b62a-737e87acc9b8)

AWSアカウントID を指定せずにバケット名/AWSLogs/* とするとイベントが発火しませんでした。

AWS Lambda関数でメッセージの削除はしなくて良い

AWS Lambda関数でAmazon SQSを自前でポーリングして処理する場合、正常に処理が完了したらメッセージを削除する必要があったのですが、イベントソースにするとそれも必要なくなるみたいです。

AWS LambdaがSQSをイベントソースとしてサポートしました! | Developers.IO
https://dev.classmethod.jp/articles/aws-lambda-support-sqs-event-source/

次に関数コードを入力します。サンプルの関数はチュートリアル通り以下の内容として保存します。ここでSQSメッセージの削除処理を入れていないことが分かります。

最初はチュートリアルのサンプルだからかな?と思ってましたが実際に動作させると正常終了時にメッセージが勝手に削除されました。こりゃ便利。

なので実装はキューの情報からS3バケットに保存されたオブジェクトのキーを取得して出力しているだけです。

テンプレート抜粋
  ReceiveQueFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub "${ProjectName}-ReceiveQueFunction"
      Handler: "index.lambda_handler"
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: |
          from __future__ import print_function
          import json
          import os
          import boto3
          def lambda_handler(event, context):
              for record in event["Records"]:
                  requestParameters = json.loads(record["body"])["detail"]["requestParameters"]
                  print(str(requestParameters))
      Runtime: "python3.7"
      Timeout: "60"
      ReservedConcurrentExecutions: 3

AWS Lambda関数の同時実行数を調整する

上記テンプレートでReservedConcurrentExecutions: 3 と同時実行数を指定していますが、こちらはケース・バイ・ケースで指定する必要があります。

AWS::Lambda::Function - AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-reservedconcurrentexecutions

大量のキューをさばく必要がある場合、同時実行数の制限まで全力でポーリングしてくれます。
なので同時実行数を指定していないと、標準設定の1,000 まで同時実行してくれます。アカウントの同時実行数は最大1,000 となりますので、もし他にも関数がある場合、影響する可能性があるのでご注意ください。

イベントルールのターゲット指定でバケット名やキーがプレフィックス指定できる

こちらは下記記事をご参考ください。地味に便利です。

Amazon CloudWatch EventsのルールでAmazon S3のキーをプレフィックス指定できた - Qiita
https://qiita.com/kai_kou/items/7104551a09fd9d195531

今回はキーをprefix: hoge/ とすることでs3://バケット名/hoge/ 配下にオブジェクトがPUTされた場合にイベントが発火する設定にしました。

テンプレート抜粋
  CloudWatchEventRule:
    Type: AWS::Events::Rule
    Properties:
      Name: !Sub "${ProjectName}-EventRule"
      EventPattern:
        source:
          - aws.s3
        detail-type:
          - "AWS API Call via CloudTrail"
        detail:
          eventSource:
            - s3.amazonaws.com
          eventName:
            - CopyObject
            - PutObject
            - CompleteMultipartUpload
          requestParameters:
            bucketName:
              - !Ref InputBucket
            key:
              - prefix: hoge/
      Targets:
        - Arn: !GetAtt S3EventQueue.Arn
          Id: !Sub "${ProjectName}-TarfgetQueue"

SQSのキューポリシーを設定する

今回、一番ドハマリしました。
キューポリシーがなくてもリソースは作成できるのですが、それだとバケットにオブジェクトをPUTしてもイベントが発火しませんでした。AWSのドキュメントを漁ってみてもそれらしき記述が見当たらずでしたが、下記のフォーラムに情報があり知ることができました。

AWS Developer Forums: CloudWatch event rule not sending to ...
https://forums.aws.amazon.com/message.jspa?messageID=742808

AWSマネジメントコンソールでぽちぽちと設定する場合にはポリシーを勝手に作成してくれるので、テンプレート化する際にハマりやすいポイントだったみたいです。

テンプレート抜粋
  SQSQueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Properties:
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: "Allow"
            Principal:
              AWS: "*"
            Action:
              - "sqs:SendMessage"
            Resource:
              - !GetAtt S3EventQueue.Arn
            Condition:
              ArnEquals:
                "aws:SourceArn": !GetAtt CloudWatchEventRule.Arn
      Queues:
        - Ref: S3EventQueue

テンプレート

ちょっと長いですがテンプレートになります。

AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  ProjectName:
    Type: String
    Default: "<お好みで>"

Resources:
  InputBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "${ProjectName}-input"
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True

  OutputBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "${ProjectName}-output"
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True

  S3EventQueue:
    Type: AWS::SQS::Queue
    Properties:
      DelaySeconds: 0
      VisibilityTimeout: 360

  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${ProjectName}-LambdaRolePolicy"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: "sts:AssumeRole"
      Path: "/"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: !Sub "${ProjectName}-LambdaRolePolices"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - s3:*
                Resource: "*"
        - PolicyName: !Sub "${ProjectName}-LambdaRoleSQSPolices"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
            - Effect: Allow
              Action:
              - sqs:ReceiveMessage
              - sqs:DeleteMessage
              - sqs:GetQueueAttributes
              - sqs:ChangeMessageVisibility
              Resource: !GetAtt S3EventQueue.Arn

  ReceiveQueFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub "${ProjectName}-ReceiveQueFunction"
      Handler: "index.lambda_handler"
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: |
          from __future__ import print_function
          import json
          import os
          import boto3
          def lambda_handler(event, context):
              for record in event["Records"]:
                  requestParameters = json.loads(record["body"])["detail"]["requestParameters"]
                  print(str(requestParameters))
      Runtime: "python3.7"
      Timeout: "60"
      ReservedConcurrentExecutions: 3

  CloudTrailBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref OutputBucket
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: s3:GetBucketAcl
            Resource: !GetAtt OutputBucket.Arn
          - Effect: Allow
            Principal:
              Service: cloudtrail.amazonaws.com
            Action: s3:PutObject
            Resource: !Join
              - ""
              - - !GetAtt OutputBucket.Arn
                - "/AWSLogs/"
                - !Ref "AWS::AccountId"
                - "/*"
            Condition:
              StringEquals:
                s3:x-amz-acl: bucket-owner-full-control

  CloudTrail:
    Type: AWS::CloudTrail::Trail
    DependsOn:
      - CloudTrailBucketPolicy
    Properties:
      TrailName: !Sub "${ProjectName}-Trail"
      S3BucketName: !Ref OutputBucket
      EventSelectors:
        - DataResources:
            - Type: AWS::S3::Object
              Values:
                - Fn::Sub:
                  - "${InputBucketArn}/"
                  - InputBucketArn: !GetAtt InputBucket.Arn
          ReadWriteType: WriteOnly
          IncludeManagementEvents: false
      IncludeGlobalServiceEvents: true
      IsLogging: true
      IsMultiRegionTrail: false

  CloudWatchEventRule:
    Type: AWS::Events::Rule
    Properties:
      Name: !Sub "${ProjectName}-EventRule"
      EventPattern:
        source:
          - aws.s3
        detail-type:
          - "AWS API Call via CloudTrail"
        detail:
          eventSource:
            - s3.amazonaws.com
          eventName:
            - CopyObject
            - PutObject
            - CompleteMultipartUpload
          requestParameters:
            bucketName:
              - !Ref InputBucket
            key:
              - prefix: hoge/
      Targets:
        - Arn: !GetAtt S3EventQueue.Arn
          Id: !Sub "${ProjectName}-TarfgetQueue"

  SQSQueuePolicy:
    Type: AWS::SQS::QueuePolicy
    Properties:
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: "Allow"
            Principal:
              AWS: "*"
            Action:
              - "sqs:SendMessage"
            Resource:
              - !GetAtt S3EventQueue.Arn
            Condition:
              ArnEquals:
                "aws:SourceArn": !GetAtt CloudWatchEventRule.Arn
      Queues:
        - Ref: S3EventQueue

  LambdaFunctionEventSourceMapping:
    Type: AWS::Lambda::EventSourceMapping
    DependsOn:
      - S3EventQueue
      - ReceiveQueFunction
    Properties:
      BatchSize: 10
      Enabled: true
      EventSourceArn: !GetAtt S3EventQueue.Arn
      FunctionName: !GetAtt ReceiveQueFunction.Arn

スタック作成して動かしてみる

最後にざくっと検証してみます。

# リソースを作成
> cd テンプレートファイルがある場所

> aws cloudformation create-stack \
  --stack-name <お好みで> \
  --template-body file://<テンプレートファイル名> \
  --capabilities CAPABILITY_NAMED_IAM \
  --region <お好みの> \
  --parameters '[
    {
      "ParameterKey": "ProjectName",
      "ParameterValue": "<お好みで>"
    }
  ]'



{
    "StackId": "arn:aws:cloudformation:<お好みのリージョン>:xxxxxxxxxxxx:stack/<お好みのスタック名>/18686480-6f21-11ea-bcf3-020de04cec9a"
}

# ファイルをアップロード
> touch hoge.txt

# hogeキー配下にアップロードしない
> aws s3 cp hoge.txt s3://<ProjectName>-input/

upload: ./hoge.txt to s3://<ProjectName>-input/hoge/hoge.txt


> aws s3 cp hoge.txt s3://<ProjectName>-input/hoge/

upload: ./hoge.txt to s3://<ProjectName>-input/hoge/hoge.txt


> aws s3 ls --recursive s3://<ProjectName>-input

2020-03-26 06:28:03          0 hoge.txt
2020-03-26 06:26:12          0 hoge/hoge.txt


# Lambda関数のログを確認
> aws logs get-log-events \
  --region <お好みのリージョン> \
  --log-group-name '/aws/lambda/<ProjectName>-ReceiveQueFunction' \
  --log-stream-name '2020/03/26/[$LATEST]ae8735ef9a1c46c38ab241f23a26b384' \
  --query "events[].[message]" \
  --output text


START RequestId: 10f98cf9-c39b-531b-9514-da0f8ea72a42 Version: $LATEST

{'bucketName': '<ProjectName>-input', 'Host': '<ProjectName>-input.s3.<お好みのリージョン>.amazonaws.com', 'key': 'hoge/hoge.txt'}

END RequestId: 10f98cf9-c39b-531b-9514-da0f8ea72a42

REPORT RequestId: 10f98cf9-c39b-531b-9514-da0f8ea72a42  Duration: 1.85 ms       Billed Duration: 100 ms Memory Size: 128 MB
     Max Memory Used: 69 MB  Init Duration: 275.58 ms

やったぜ。

まとめ

AWSマネジメントコンソールで設定すると比較的かんたんに設定できるのですが、CFnのテンプレート化する際にはそこそこ大変でしたが、良い知見を得ることができました。

参考

Amazon S3 ソースの CloudWatch イベント ルールを作成する (コンソール) - CodePipeline
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/create-cloudtrail-S3-source-console.html

AWS LambdaがSQSをイベントソースとしてサポートしました! | Developers.IO
https://dev.classmethod.jp/articles/aws-lambda-support-sqs-event-source/

AWS::Lambda::Function - AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-reservedconcurrentexecutions

Amazon CloudWatch EventsのルールでAmazon S3のキーをプレフィックス指定できた - Qiita
https://qiita.com/kai_kou/items/7104551a09fd9d195531

AWS Developer Forums: CloudWatch event rule not sending to ...
https://forums.aws.amazon.com/message.jspa?messageID=742808

Amazon CloudWatch LogsのログをAWS CLIでいい感じに取得する - Qiita
https://qiita.com/kai_kou/items/60bbe314b74b9eaf7126

AWS CLIを使ってAWS Lambdaのログ取得時に注意したいこと | Developers.IO
https://dev.classmethod.jp/articles/note-log-of-lambda-using-awscli/

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

【Rails】HerokuでAWS s3に画像をアップロードしようとしたら<Message>Access Denied</Message>

事前準備

Railsでcarrierwaveを使ってAWS S3に画像をアップロードする手順を画像付きで説明する

概要についてはこちらの記事がとても分かりやすかったので参考にさせていただきました。

ただしこのまま$ git pushすると、アクセスキーがアップロードされてしまうので注意です。
アクセスキーの隠し方については、heroku 環境変数とかgem 'dotenv-rails'$ heroku config:set ACCESS_KEY=aaaaaaとか調べると出てくると思います。

この記事を読むべき人

  1. https://myapp.herokuapp.com で画像をs3にアップロードしようとしてもうまくいかない
  2. $ heroku logsしたら<Message>Access Denied</Message>って言われる

この状況の人にはお役に立てるかもしれません。

手順

IAMのユーザーのARNを取得する

  1. 「AWSマネジメントコンソール」で「セキュリティ、ID、およびコンプライアンス /IAM」を選択
  2. 「IAMリソース」の「ユーザー: 2」を選択(数字は人それぞれ)
  3. 「ユーザー」を選択
  4. 「ユーザーの ARN」をコピーする

パブリックアクセスのブロックをオフにする

  1. 「AWSマネジメントコンソール」で「ストレージ /s3」を選択
  2. バケットを選択
  3. 「アクセス権限」タブを選択
  4. 「ブロックパブリックアクセス」タブを選択(デフォルトで選択されている)
  5. 全てのブロックをオフにする

バケットポリシーを使用する

  1. 「ブロックパブリックアクセス」から「バケットポリシー」タブに切り替える
  2. 「バケットポリシーエディター」に以下を適用して、保存する
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::111111111111:user/IAMユーザー名"
            },
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:PutObjectAcl"
            ],
            "Resource": "arn:aws:s3:::バケット名/*"
        }
    ]
}
  • "arn:aws:iam::111111111111:user/IAMユーザー名"はコピーしてきたIAMユーザーのARN
  • "Resource": "arn:aws:s3:::バケット名/*"にバケット名を入力する

以上

これでいけるはず。

参考記事

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