20190816のAWSに関する記事は21件です。

Alexaランキングの上位1,000,000件を取得する

AlexaランキングはAmazonが運営しているWebサイトのアクセス数のランク付けのサービスです。
ここでは、無料でアクセス数上位1mのリストを取得する方法を紹介します。
参考: https://gist.github.com/chilts/7229605

ダウンロード

対象の1mのリストはAWSのS3にアップロードされています。
http://s3.amazonaws.com/alexa-static/top-1m.csv.zip
タイムスタンプを見る限り、一応毎日更新はされているようですが、中身がちゃんと更新されているかは分からないです。
* 内容が3ヶ月くらい古いと言っている人もいるようです: https://gist.github.com/chilts/7229605#gistcomment-2880207

ブラウザ経由でもダウンロードできますが、ここではCUIでのダウンロードの例を書きます。

$ wget http://s3.amazonaws.com/alexa-static/top-1m.csv.zip
$ unzip top-1m.csv.zip

これでファイルが解凍され、csv形式でアクセス数上位1mのサイトのリスト(top-1m.csv)を取得できます。

$ cat top-1m.csv | head -n 10  # 上位10件を表示
1,google.com
2,youtube.com
3,tmall.com
4,baidu.com
5,qq.com
6,sohu.com
7,facebook.com
8,login.tmall.com
9,taobao.com
10,wikipedia.org
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BigQuery Cloud SQL federated queryでRDSのデータにクエリを実行してみる

BigQueryからCloudSQLに直接クエリが実行できるようになったということですが、サービスの基盤はAWSを利用しているためDBはもちろんRDS。。。
そこでどうにかしてRDSに溜まっているデータに対してBigQueryから直接クエリを実行したいと思いやってみました。
正確には、RDSをCloudSQLでレプリケーションしてCloud SQL federated queryでBigQueryからクエリを実行してみました。

構成

無題のプレゼンテーション (2).png
既にソースDBに対して3台のリードレプリカが作成されている状態だったので、こんな感じの構成にしていきたいと思います。

CloudSQLレプリケーション設定

外部サーバーからCloudSQLレプリカに複製する場合いくつか要件があるのでそれを満たしているか確認します。

  • GTID が有効にされていて、GTID 整合性が強制されること。

問題はここでした。
AWSのドキュメントを見ると

DB インスタンスまたはリードレプリカで RDS MySQL バージョン 5.7.22 以下を使用している場合は、DB インスタンスまたはリードレプリカをアップグレードします。RDS MySQL バージョン 5.7.23 以降の MySQL 5.7 バージョンにアップグレードします。

:thinking:
Screenshot_2019-08-15 RDS · AWS Console(1).png
:sweat_smile:
稼働中DBのバージョンを勝手にあげる訳にはいかないので、一先ずやりたいことが出来るか検証用の環境を作って試してみます。

RDS設定

まずパラメータグループの作成を行います。
Screenshot_2019-08-16 RDS · AWS Console.png

名前
enforce_gtid_consistency ON
gtid-mode ON

GTIDを使用したレプリケーションを構成するために、このように変更します。

ついでに、パブリックアクセシビリティを【はい】に変更しCloudSQLから接続できる状態にします。
Screenshot_2019-08-15 RDS · AWS Console(5).png

ソースDBにmysqldumpを実行しても良いのですが、後に本番環境でやることを考えてリードレプリカの一台にmysqldumpを実行したいと思います。
念の為リードレプリカのレプリケーションを停止しておきます。

mysql> CALL mysql.rds_stop_replication;

GCPのドキュメントにコマンドが記載されているのですが、そのままだとエラーになるので--master-data=1は省きます。
一つのコマンドでGCSにアップロードをしても良いのですが、今回は一度サーバー上に保存します。

$ mysqldump \
    -h [MASTER_IP] -P [MASTER_PORT] -u [USERNAME] -p \
    --databases [DBS]  \
    --hex-blob  --skip-triggers \
    --order-by-primary --no-autocommit \
    --ignore-table [VIEW] \
    --single-transaction --set-gtid-purged=on > ./dump.sql

mysqldumpが終了したらGCSにアップロードします。

$ gsutil cp ./dump.sql gs://[BUCKET]/[PATH_TO_DUMP]

CloudSQL設定

ここからやっとGCPをいじり始めます。
Screenshot_2019-08-15 SQL の概要 - dip-skylab - Google Cloud Platformのコピー.png
CloudSQLからデータ移行を選択します。
移行を開始を選択して入力が必要な部分を埋めていきます。
Screenshot_2019-08-15 SQL の概要 - dip-skylab - Google Cloud Platform(1).png

ソースのパブリックIPアドレスについては、nslookupでホストを指定して調べました。
レプリケーションユーザーですが、今回はCloudSQLのIPからのみ接続を許可したいのでRDS側には後ほどユーザーを作成したいと思います。
証明証ですが、こちらにダウンロードリンクが記載されているので入手します。
ダウンロードした証明書を指定し、【次へ】を選択。
そしてBigQueryのデータロケーションと同じゾーン・任意のマシンタイプを選択、先ほどGCSにアップロードしたダンプファイルを指定しリードレプリカの作成をします。

CloudSQLからRDSへの接続設定

GCPドキュメントにもある通り、この作業はCloudSQLでリードレプリカの作成をしてから30分以内に行わないとリードレプリカの作成が停止します。
ということで、CloudSQLのリードレプリカ作成オペレーションの終了を待たずに並行して作業を進めていきます。
まずCloudSQLリードレプリカのIPアドレスを調べます。

$ gcloud sql instances describe [REPLICA_NAME] --format="default(ipAddresses)"
ipAddresses:
- ipAddress: xxx.xxx.xxx.xxx
  type: PRIMARY
- ipAddress: xxx.xxx.xxx.xxx
  type: OUTGOING

IPアドレスを取得できるようになるまで数分かかります。
CloudSQL(OUTGOINGのIP)からソースデータベースに接続できるようにRDS側でセキュリティグループ等を設定します。
次にレプリケーションユーザーを作成します。
既に作成している場合は飛ばしてください。

mysql> CREATE USER 'cloudsql_rep'@'OUTGOINGのIP' IDENTIFIED BY 'パスワード';
mysql> GRANT REPLICATION CLIENT, REPLICATION SLAVE ON *.* TO 'cloudsql_rep'@'OUTGOINGのIP';

ここまで設定したらCloudSQLにダンプファイルが復元されるまで待ちます。

CloudSQLでリードレプリカの作成が終了したら一応確認します。

mysql> SHOW SLAVE STATUS ¥G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: xxx.xxx.xxx.xxx
                  Master_User: cloudsql_rep
                  Master_Port: 3306
                Connect_Retry: 60
...

無事レプリケーションができているようです。
mysqldumpを実行するためにレプリケーションを停止したリードレプリカも忘れずに再開しておきます。

mysql> CALL mysql.rds_start_replicartion;

BigQuery 接続を作成

いよいよ大詰めです。
BigQueryのコンソールから接続の作成を選択し、
Screenshot_2019-08-16 BigQuery - dip-skylab - Google Cloud Platformのコピー.png
必要な情報を入力していきます。
Screenshot_2019-08-16 BigQuery - dip-skylab - Google Cloud Platform(1).png
接続を作成を選択すると・・・
Screenshot_2019-08-16 BigQuery - dip-skylab - Google Cloud Platform(2).png
外部接続が現れました!
取り敢えずクエリを実行してみます。

SELECT * FROM EXTERNAL_QUERY("プロジェクトID.ゾーン.CloudSQL_federated_query_RDS", "SELECT * FROM INFORMATION_SCHEMA.TABLES;");

Screenshot_2019-08-16 BigQuery - dip-skylab - Google Cloud Platform(3).png
結果が返ってきました!

さいごに

残念なことに本番環境のRDSはMySQLのバージョンが要件を満たせていなかったため検証用に環境を作って試してみましたが、やりたいことはできそうなので気づかれないように本番環境のバージョンを上げておきたいと思います。
Cloud SQL federated queryについてですが、CloudSQLに対してクエリを実行しているのでBigQueryほどのスピードにはなりません。
また、クエリ結果もキャッシュされないようなのであまり調子に乗りすぎると気づかぬうちにスキャン量が積み重なってしまうかもしれません。。。
ただ、いろいろな可能性を感じられるのでいじり倒してみたいと思います。

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

Pythonでaws-sam-cliをローカル実行するまで

AWSコンソールでポチポチしてみたのですが
ローカル環境での実行をしてみたいなとおもい、その備忘録です。

環境:
macOS High Sierra 10.13.6
AWS SAM CLI
pip 19.2.2
Python 3.7.2
Docker for macOS


目次

  1. AWS SAM CLIをインストールする
  2. sam init
  3. テストを実行
  4. ローカルでLambda関数を呼び出す

1. AWS SAM CLIをインストールする

pip を使用して AWS SAM CLI をインストール

$ pip install aws-sam-cli

バージョンの確認

$ sam --version
SAM CLI, version 0.19.0

2. sam init

sam init コマンドで python3.7のサンプルアプリケーションを作成

$ sam init --runtime python3.7
[+] Initializing project structure...


Project generated: ./sam-app


Steps you can take next within the project folder
===================================================
[*] Invoke Function: sam local invoke HelloWorldFunction --event event.json
[*] Start API Gateway locally: sam local start-api


Read sam-app/README.md for further instructions


[*] Project initialization is now complete

Readとのこと => https://github.com/yoshiyoshifujii/sam-app/blob/master/README.md

ツリーで見てみる

$ tree sam-app/
sam-app/
├── README.md
├── event.json
├── hello_world
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-37.pyc
│   │   └── app.cpython-37.pyc
│   ├── app.py
│   └── requirements.txt
├── template.yaml
└── tests
    └── unit
        ├── __init__.py
        ├── __pycache__
        │   ├── __init__.cpython-37.pyc
        │   └── test_handler.cpython-37.pyc
        └── test_handler.py

5 directories, 12 files

3. テストを実行

テストを実行してみる

$ cd sam-app
$ python -m pytest tests/ -v
========================================== test session starts ==========================================
platform darwin -- Python 3.7.2, pytest-5.1.0, py-1.8.0, pluggy-0.12.0 -- /Library/Frameworks/Python.framework/Versions/3.7/bin/python3
cachedir: .pytest_cache
rootdir: /Users/suwa/sam-app
plugins: mock-1.10.4
collected 1 item                                                                                        

tests/unit/test_handler.py::test_lambda_handler PASSED                                            [100%]

=========================================== 1 passed in 0.07s ===========================================

ビルドします

$ sam build

4. ローカルでLambda関数を呼び出す

まずDockerを起動する
ローカルで API Gateway 経由で Lambda 関数を呼び出す

$ sam local start-api

ブラウザで下記URLにアクセスするとコンテナイメージがダウンロードされ、コードが実行されてブラウザに出力が表示されます。
http://127.0.0.1:3000/hello
{"message": "hello world”}

他のblogではLocationが表示されていたのですが

$ vi ./hello_world/app.py

で、下にある

# "location": ip.text.replace("\n", "")

の、コメントアウトを外せばLocationも表示されます。

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

[JAWS-UG 磐田] コンテナハンズオン

【ハンズオン演習】コンテナ(Docker)に触ってみよう

◆概要

 コンテナについて解説を交えて触ってみましょう。

 参考 【図解】Dockerの全体像を理解する


◆ハンズオンを始める前に

・パソコンの準備をお願いします。

  ※パソコンでのハンズオンを実施いたします。

・会場設置のWi-Fiへ接続願います。

  ※インターネットへの接続を前提としています。

・AWSアカウントの準備はお済みですか?

  ※ハンズオン開始前までに準備願います。

   作成手順:https://aws.amazon.com/jp/register-flow/

・ターミナルソフトウェアの準備

  ※EC2(サーバ)の操作時に使用します。

   Windowsクライアントの方は、Tera TermやPuTTYなどをご用意ください。

・ハンズオン終了後の操作について

  ※使用したEC2・ECS・ECR・ALBは、AWSの課金対象になります。必要ない場合は、削除してください。

   [ECS(クラスター)削除方法]

   [ECS(タスク定義)登録解除方法]

   [ECRイメージ削除方法]

   [Cloudformation削除方法]

    ※下記順番で削除願います。

    1.jawsug-iwata-20190821-alb

    2.jawsug-iwata-20190821-ec2

    3.jawsug-iwata-20190821-vpc


◆今回の演習環境について

構成図.PNG

【演習内容】

①演習環境準備

②EC2を使用してコンテナイメージ作成&リポジトリ登録

③Fargateでアプリケーション実行&プログラム更新


◆ハンズオンを始める前に・・・

・AWSマネジメントコンソールにログインして、東京リージョンが選択されている事を確認してください。

image.png


・画面上にIAM・EC2・S3の文字が表示されている事を確認してください。

 ※表示されていない場合は、画面左上のサービスをクリックして各サービスを選択してください。

image.png


①演習環境の準備

1.ハンズオンのVPC環境構築用のCloudFormationの実行

Launch Stack

上のリンクより、ハンズオン用のVPC環境を構築するためのCloudFormationを実行します。

※環境構築完了後、次の手順に進んでください。

2.EC2用のキーペアー作成

2-1.[作成手順]

 ※作成済みの場合は、次へ進んでください。

3.ハンズオンのEC2環境構築用のCloudFormationの実行

Launch Stack

上のリンクより、ハンズオン用のEC2環境を構築するためのCloudFormationを実行します。

※環境構築完了後、次の手順に進んでください。

3-1.KeyNameの選択

keyname.PNG

  ※2.で作成したキーペアーまたは作成済みキーペアーを選択

3-2.機能の項目で、承認欄をチェック

機能.PNG

  ※チェック後、スタックの作成をクリックしてください。

4.EC2への接続

4-1.Amazon EC2コンソールを開きます。

 ※リンク先をクリックすると、東京リージョンのコンソールに接続します。

4-2.作成したEC2サーバを選択し、[接続]をクリックします。

image.png

image.png

4-3.パブリックDNSの文字列をコピー、ターミナルソフトでssh接続します。

ログインユーザー
ec2-user

5.ハンズオンのALB環境構築用のCloudFormationの実行

Launch Stack

上のリンクより、ハンズオン用のALB環境を構築するためのCloudFormationを実行します。

※環境構築完了後、次の手順に進んでください。


②EC2を使用してコンテナイメージ作成&リポジトリ登録

1.ターミナルソフトからコマンド入力

# ハンズオンフォルダーに移動
cd docker-hands-on

# コンテナイメージの作成
docker build -t jawsug/iwata/sampleap:ver1.0 ./

# コンテナイメージの確認
docker images

# コンテナの実行
docker run -d -p 80:80 --rm --name sampleap jawsug/iwata/sampleap:ver1.0

2.ブラウザーで接続

  パブリックDNSアドレスを入力し、接続します。

docker_image_test.PNG

3.リポジトリ登録

3-1.[リポジトリ作成手順]

リポジトリ名
jawsug/iwata/sampleap

ecr.PNG

3-2.リポジトリへのコンテナイメージプッシュ手順

  リポジトリを選択し、プッシュコマンドの表示をクリック

ecr01.PNG

# ECRリポジトリへの接続
$(aws ecr get-login --no-include-email --region ap-northeast-1)

#  コンテナイメージのタグ名変更
docker tag jawsug/iwata/sampleap:ver1.0 XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/jawsug/iwata/sampleap:latest
※XXXXXXXXXXXXは、プッシュコマンドの表示で確認してください。

#  コンテナイメージのレジストリ登録
docker push XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/jawsug/iwata/sampleap:latest
※XXXXXXXXXXXXは、プッシュコマンドの表示で確認してください。

 登録が成功すると、下記のようになります。

ecr02.PNG


③Fargateでアプリケーション実行&プログラム更新

1.ECSでクラスター作成

1-1.[ECSクラスター作成手順]

クラスター名
ecs-cluster

ecs_cluster01.PNG
ecs_cluster02.PNG
ecs_cluster03.PNG

2.ECSでタスク定義作成

2-1.[ECSタスク定義作成手順]

タスク定義名
ecs-tasks
タスクメモリ タスクCPU
0.5GB 0.25vCPU
コンテナ名 イメージ ポートマッピング
sampleap XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/jawsug/iwata/sampleap:latest 80

ecs_task01.PNG
ecs_task02.PNG
ecs_task03.PNG
ecs_task04.PNG
ecs_task05.PNG
ecs_task06.PNG

3.ECSでサービス作成

3-1.[ECSサービス作成手順]

サービス名
ecs-service
タスクの数
1
クラスターVPC
JAWS-UG IWATA VPC_Tokyo01
サブネット
Trust-A
Trust-C
セキュリティグループ
fargate-sg
パブリック IP の自動割り当て
DISABLED
ロードバランサー名 プロダクションリスナーポート ターゲットグループ名
jawsug-iwata-20190821-ALB 80 : HTTP jawsug-iwata-20190821-TG

ecs_service01.PNG
ecs_service02.PNG
ecs_service03.PNG
ecs_service04.PNG
ecs_service05.PNG
ecs_service06.PNG
ecs_service07.PNG
ecs_service08.PNG
ecs_service09.PNG

4.ALBにブラウザーで接続

4-1.ALBのDNSアドレスを入力し、接続します。

alb_check.PNG

alb_check2.PNG

5.プログラムの更新

5-1.ターミナルソフトからコマンド入力

cd docker-hands-on
cp -p src/index_ver2.0.php src/index.php
docker build -t jawsug/iwata/sampleap:ver2.0 ./
docker tag jawsug/iwata/sampleap:ver2.0 XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/jawsug/iwata/sampleap:latest
docker push XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/jawsug/iwata/sampleap:latest
※XXXXXXXXXXXXは、プッシュコマンドの表示で確認してください。

5-1.[タスク定義の更新]

5-2.[サービスの更新]:タスク定義のリビジョンを最新の番号に更新する。

service_update.PNG

6.ALBにブラウザーで接続

6-1.ALBのDNSアドレスを入力し、接続します。

service_update2.PNG


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

EC2でdigdagを動かすチュートリアル

記事の目的

  • EC2へdigdagを導入して、GUIを表示するまでの方法について説明
  • digdagはインメモリで実行する前提

EC2へのdigdag導入

前提

  • パブリックアクセス可能なEC2(Amazon Linux 2)にssh接続できること

digdagのDL

  • EC2へssh接続後、下記コマンドを実行
  • 適宜sudoで実行する必要
# digdagのDL
$ curl -o /usr/local/bin/digdag -L "https://dl.digdag.io/digdag-latest"

# digdagの権限を変更
$ chmod +x /usr/local/bin/digdag

# digdagのインストール成功を確認
$ digdag —version


(javaをインストールしていない場合のみ、下記を実行)

$ sudo yum install java-1.8.0-openjdk-src.x86_64

#1.8を選択
$ sudo alternatives —config java

#インストール成功を確認
$ java -version

digdag serverのサービス化(Unitファイルの作成)

# サービスを新規作成
$ vi /etc/systemd/system/digdag.service

#以下を記載する

[Unit]
Description=digdag

[Service]
Restart=always
ExecStart=/bin/sh -c "/usr/local/bin/digdag server -b 0.0.0.0 --memory"

[Install]
WantedBy=multi-user.target

digdag起動&自動起動設定

# 設定ファイルの再読み込み
$ systemctl daemon-reload 

# サービス自動起動設定
$ systemctl enable digdag

# サービス起動
$ systemctl start digdag

動作確認

http://EC2のIPアドレス:65432

  • 上記にアクセスし、digdagのGUI画面が表示されていれば成功

※GUI画面が表示されない場合、AWSのセキュリティグループで65432番のポートのインバウンドが開放されていることを確認してください。

参考

Ubuntu で Digdag サーバを作るまでの手順 - Qiita

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

AWSのクレジットカード登録ができない時(ボクの場合)の解決策

AWSを半年ぶりに開いて個人開発しようとしたら、AWSのクレジットカードの登録ができない問題が起こりました

クレジットカードが登録できないとEC2に入れないのです。。。
ボクは過去にホームレスになった時があって、クレジットカードの滞納を3ヶ月以上してしまったので、ブラックリストに名前が乗っています。
(みなさん気をつけましょう)
そのクレジットカード以外のカードに登録を変更しようとしたら今回の問題が起きました。

スクリーンショット 2019-08-16 19.59.05.png

何回も何回も何回も、新しいクレジットカード(デビットカード)をデフォルトに編集・登録しようと試みましたが、一向に反映されませんでした。

下の画像のエラーです。もう見飽きました。。。

スクリーンショット 2019-08-16 19.48.21.png

何回もトライアンドエラーする事にいい加減飽きたので、AWSカスタマーサービスにお問い合わせする事にしました。
(はじめからやれよ)

問い合わせ詳細は下の画像です。

スクリーンショット 2019-08-16 19.53.50.png

スクリーンショット 2019-08-16 19.55.44.png

解決策

すると翌日、AWSカスタマーサービスから返信(下の画像)がきました。

スクリーンショット 2019-08-16 20.01.43.png

新規アカウントを作ればいいだけでした。
初めから問い合わせすればよかった〜〜〜〜〜〜
(永久凍結になると思っていたので本当にホッとしました)

告知

今、進撃のITコミュニティで一緒に活動してくれる方を募集しています。
参加は無料です。
下に参加URLを貼ってます。
管理者はブロックチェーン実務経験あります。
参加資格はプログラマーからマーケター、AIやブロックチェーン、動画編集者やディレクター、youtuber、インスタグラマーも対象です。
進撃のIT.png

進撃のIT Facebook
https://www.facebook.com/groups/612023275874253/
進撃のIT Slack
https://attack-on-it.herokuapp.com/
進撃のIT Twitter
https://twitter.com/IT13389135
進撃のIT Qiita記事
https://qiita.com/f___juntaro_/items/81136c85a8002cc442ac

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

AWSのUbuntu-18.04にsshできない

こんなエラーが出るとき

$ ssh -i key.pem ec2-user@ip-address
Connection closed by ip-address port 22 

原因は単純で,デフォルトのユーザー名がec2-userじゃなくてubuntu.
なので,

$ ssh -i key.pem ubuntu@ip-address

でつながる.

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

AmazonSQSAsyncの呼び出し方

AmazonSQSAsync の呼び出し方です。

    AmazonSQSAsync sqs = SQS.getAsyncClient();
    try {
      CreateQueueResult queue =
        sqs.createQueue(
          new CreateQueueRequest().withQueueName(QUEUE_NAME));
      HashMap<String, MessageAttributeValue> map =
        new HashMap<String, MessageAttributeValue>();
      map.put(
        "foo",
        new MessageAttributeValue().withDataType("String").withStringValue(
          "aaa"));
      Future<SendMessageResult> result =
        sqs.sendMessageAsync(
          new SendMessageRequest(queue.getQueueUrl(), "Message")
            .withMessageAttributes(map));
      while (!result.isDone()) {
        Thread.sleep(10);
      }
    } catch (Exception e) {
      logger.error(e);
    } finally {
      sqs.shutdown();
    }

sqs.sendMessageAsync の返り値として Future が返ってきます。 isDone() メソッドが呼ばれるとSQSの送信が完了した判定になるので、それまでループで待って最後shutdownするのがお作法のようです。

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

AWS S3 のJava SDKで転送完了時にshutdownNowを呼ばないとスレッドが残り続ける

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/HLuploadFileJava.html
にS3へのファイルアップの方法が記載されています。

ただここの処理の最後で shutdownNow を呼ばないと、s3Clientのスレッドが残り続けてしまうことがあるようです。(SDKのバージョンによるかも知れません。)

        AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
                .withRegion(clientRegion)
                .withCredentials(new ProfileCredentialsProvider())
                .build();
        TransferManager tm = TransferManagerBuilder.standard()
                .withS3Client(s3Client)
                .build();

        // TransferManager processes all transfers asynchronously,
        // so this call returns immediately.
        Upload upload = tm.upload(bucketName, keyName, new File(filePath));
        System.out.println("Object upload started");

        // Optionally, wait for the upload to finish before continuing.
        upload.waitForCompletion();
        System.out.println("Object upload complete");

        // 以下のようにして転送マネージャーを終了させる必要があります。
        tm.shutdownNow();

参考

https://www.tcmobile.jp/dev_blog/programming/%E3%80%90java%E3%80%91cse%E3%83%9E%E3%83%AB%E3%83%81%E3%83%91%E3%83%BC%E3%83%88%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%80%81s3%E3%81%B8/

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

【AWS】開発環境では動くが本番だと�動かない事例集

Qiita初投稿になります。
何か不備や誤りがあったらご指摘をいただけるととてもありがたいです!

経緯

スクールでの課題や個人アプリの開発でAWSを使っており、デプロイも双方担当をさせていただいておりました。
その中で、苦戦をした大きな比率を占めるのが、開発環境だと動くのに何故か本番環境だと動かない事例です。
本番で動かないと実際のサービスだと何も意味がないにも関わらず、このパターンが結構ありましたので、これから対処をする方がどういう方法や考え方で解いていけばいいのかをまとめます。
デプロイで苦戦をすると本当にイライラしてしまうので、イライラを少しでも沈めるのに貢献をできたらなと思います。

開発環境(個人アプリもスクールの課題も同一です)
言語:Ruby 2.5.1
フレームワーク:Rails 5.2.3
データベース:MySQL5.6
ライブラリ:JQuery
自動デプロイ:capistrano
Webサーバー(本番):nginx
APサーバー(本番):unicorn
Web・APサーバー(開発):puma
今回は双方ともにEC2、S3を用いております。

前提とエラーの基本の見方

EC2インスタンスの再起動が1番今まで救われたケースが多いです。まずは試してみましょう。(MySQLとnginxの再起動も必要です)

そして、エラーを解決をする基本はエラーログを見て忠実に動くことです。これは本番環境でも開発環境でも一緒です。
エラーが出る場合、開発環境だとターミナルやビューに出てきます。ただ、本番環境だと出てこないため、本番環境だと別でコマンドで見ていきます。
エラーログの本番環境の場所はlinuxコマンドでログインをして確認をします。注意をするのは自動デプロイの前後でエラーが吐き出される場所が異なることです。自動デプロイをしたにも関わらず元のままの位置でファイルを開いても意味がないです。

#手動デプロイ後
/var/www/アプリ名/log/production.log
#capistranoでの自動デプロイ後
/var/www/アプリ名/current/log/production.log

logにまで移動をしたらcat等のコマンドでエラーログを見ることができます。

①pumaとの性能の違いで、拡張子がついていなかった時

ActionView::Template::Error (The asset "bannar_image" is not present in the asset pipeline.):
※本番環境のエラーログです。
これが一番はじめのエラーでした。こちらに関しては上記のエラー文を見ると、bannar_imageが間違っているのが何となく分かると思います。
このbannar_imageという記述があったのが、image_tagになるので、image_tagに関して調べてみました。

index.erb
#エラーが起きたコードのイメージ
<%= image_tag 'bannar_image' %>
#他のサイトで見た見本のコード
<%= image_tag 'flower.png' %>

実際にビューファイルを比べてみると何か違和感が、、、上のコードは実は拡張子がなかったのです。
そのため、本番環境では動かなかったのです。

ちなみにこの時はそれまで自動デプロイが初めてできた瞬間に出たエラーだったため、自動デプロイのcapistranoのエラーだと勘違いをしてしまっておりました。
自動デプロイのエラーばかり見ていたので、何もエラーログがないのに原因不明のエラーが起こっていると勘違いして、だいぶハマりました。
デプロイがどういう流れになっていてどういう仕組みになっているのかを理解をしていないとこういうところで非常に苦戦をします。

というかpumaは何故拡張子がないのに動くのか、、、

②application.jsにJQueryが2つあった時

こちらはそこまで難しくないエラーです。お互いに干渉してしまうため、エラーになるようです。
今出せないのですが、エラーログも出ていて、そんなに複雑なエラーログではありませんでした。

application.js
=require rails-ujs
=require jquery-ujs

下のコードはヴァージョンが古い時に使っていたようでして、Rails5.1以降は上のコードを使うみたいです。
これはスクールの発表で先輩グループの発表の際に触れていたので、すぐに分かりました。
①もだけどそもそもpumaの時点でエラー起きてくれないのかな。。。

③データベース関連で開発環境とmigrationの状況が異なってしまった時

ActionView::Template::Error (Unknown database 'freemarket_sample_54c_production'):

チーム開発でmearge済みのmigrationファイルを変更をしてその後に再度meargeをしたり、
rake db:reset等を繰り返しているといつか起きてしまうかと思います。
私たちのチームではどうしようもなくなってしまい、結局一から本番環境のデータベースを構築をし直したのですが、これは本来あまりやっていいことではないかと思っています。
もちろん開発の初期段階ですので正直困りはしなかったのですが、実際のサービスだと既に重要な顧客データが多数入っているためです。
こちらは他に何かやり方あれば教えていただけると非常に嬉しいです。

ちなみにcapistrano導入後はデータベースをcreateしたり、dropをさせるときもエラーを見るのと同じくcurrent上でコマンドは打ちます。
rake:db:seedも同様で、capistrano導入後はディレクトリの位置が変更をされるためです。

④credentials.yml関連

ActiveSupport::MessageEncryptor::InvalidMessage

このエラーは序盤結構多かったです。こちらに関しては他に詳しい記事も多数あるかと思いますので割愛します。
credentials.ymlに関しては最初は結構苦戦をしましたが、慣れるとAPIkeyの管理がすごく簡単でした。
怖がるものではないかと思います。

⑤データベース内に数値が入っていなかった時

ActionView::Template::Error (undefined method `id' for nil:NilClass):

すごく単純なようで起きがちなエラーです。
特にテーブルを増やしてカラムに新しく外部キーを増やすと起こりがちです。
私はデータベースをSQLで直接いじっていましたが、Sequelpro等で本番でも見れるようにすることができるので、入れたら楽に設定をできると思います。
※ベーシック認証をしてからだと思うのですが、途中からSequelproは使えなくなりました。おそらく弾かれてしまったのだと思います。もしかするとベーシック認証を使っていてもできるのかもしれませんが、私は調べきれませんでした。

⑥JQueryで発火をしなかった※データが入っていない時

エラーログは出ません。
インクリメンタルサーチをしようとしたのですが、本番環境のデータで名前を検索をする際に、そもそも該当の名前のデータが入っていませんでした。。。

これは完全に単なる勘違いです。即解決できましたが、全くエラーログが出てこないので原因が不明になるので少しの時間、不安になりました。
これも上記の⑤と一緒でSequelproとか入れたらわかりやすそうです。

まとめ

基本はエラーログをしっかりと見ていけば何とかなりました。ただ、データベース関連は視覚化ができていなかったので、苦労をしました。
これに関してはエラーログの構造等を理解しだしてから、格段に理解が深まったので、細かいWebアプリの仕組みに関しては理解をしといたほうがいいなと感じました。
今回挙げた事例に関しては正直かなり簡単なほうだと思っています。これから先も出てくる事例は多いと思うので、また何かあれば記事に挙げていきます。

参考記事

https://www.javadrive.jp/rails/template/index11.html
https://www.bokukoko.info/entry/2017/10/27/231129
https://qiita.com/scivola/items/cc06ddbfd94d3118f693

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

【AWS】開発環境では動くが本番だと動かない事例集

Qiita初投稿になります。
何か不備や誤りがあったらご指摘をいただけるととてもありがたいです!

経緯

スクールでの課題や個人アプリの開発でAWSを使っており、デプロイも双方担当をさせていただいておりました。
その中で、苦戦をした大きな比率を占めるのが、開発環境だと動くのに何故か本番環境だと動かない事例です。
本番で動かないと実際のサービスだと何も意味がないにも関わらず、このパターンが結構ありましたので、これから対処をする方がどういう方法や考え方で解いていけばいいのかをまとめます。
デプロイで苦戦をすると本当にイライラしてしまうので、イライラを少しでも沈めるのに貢献をできたらなと思います。

開発環境(個人アプリもスクールの課題も同一です)
言語:Ruby 2.5.1
フレームワーク:Rails 5.2.3
データベース:MySQL5.6
ライブラリ:JQuery
自動デプロイ:capistrano
Webサーバー(本番):nginx
APサーバー(本番):unicorn
Web・APサーバー(開発):puma
今回は双方ともにEC2、S3を用いております。

前提とエラーの基本の見方

EC2インスタンスの再起動が1番今まで救われたケースが多いです。まずは試してみましょう。(MySQLとnginxの再起動も必要です)

そして、エラーを解決をする基本はエラーログを見て忠実に動くことです。これは本番環境でも開発環境でも一緒です。
エラーが出る場合、開発環境だとターミナルやビューに出てきます。ただ、本番環境だと出てこないため、本番環境だと別でコマンドで見ていきます。
エラーログの本番環境の場所はlinuxコマンドでログインをして確認をします。注意をするのは自動デプロイの前後でエラーが吐き出される場所が異なることです。自動デプロイをしたにも関わらず元のままの位置でファイルを開いても意味がないです。

#手動デプロイ後
/var/www/アプリ名/log/production.log
#capistranoでの自動デプロイ後
/var/www/アプリ名/current/log/production.log

logにまで移動をしたらcat等のコマンドでエラーログを見ることができます。

①pumaとの性能の違いで、拡張子がついていなかった時

ActionView::Template::Error (The asset "bannar_image" is not present in the asset pipeline.):
※本番環境のエラーログです。
これが一番はじめのエラーでした。こちらに関しては上記のエラー文を見ると、bannar_imageが間違っているのが何となく分かると思います。
このbannar_imageという記述があったのが、image_tagになるので、image_tagに関して調べてみました。

index.erb
#エラーが起きたコードのイメージ
<%= image_tag 'bannar_image' %>
#他のサイトで見た見本のコード
<%= image_tag 'flower.png' %>

実際にビューファイルを比べてみると何か違和感が、、、上のコードは実は拡張子がなかったのです。
そのため、本番環境では動かなかったのです。

ちなみにこの時はそれまで自動デプロイが初めてできた瞬間に出たエラーだったため、自動デプロイのcapistranoのエラーだと勘違いをしてしまっておりました。
自動デプロイのエラーばかり見ていたので、何もエラーログがないのに原因不明のエラーが起こっていると勘違いして、だいぶハマりました。
デプロイがどういう流れになっていてどういう仕組みになっているのかを理解をしていないとこういうところで非常に苦戦をします。

というかpumaは何故拡張子がないのに動くのか、、、

②application.jsにJQueryが2つあった時

こちらはそこまで難しくないエラーです。お互いに干渉してしまうため、エラーになるようです。
今出せないのですが、エラーログも出ていて、そんなに複雑なエラーログではありませんでした。

application.js
=require rails-ujs
=require jquery-ujs

下のコードはヴァージョンが古い時に使っていたようでして、Rails5.1以降は上のコードを使うみたいです。
これはスクールの発表で先輩グループの発表の際に触れていたので、すぐに分かりました。
①もだけどそもそもpumaの時点でエラー起きてくれないのかな。。。

③データベース関連で開発環境とmigrationの状況が異なってしまった時

ActionView::Template::Error (Unknown database 'freemarket_sample_54c_production'):

チーム開発でmearge済みのmigrationファイルを変更をしてその後に再度meargeをしたり、
rake db:reset等を繰り返しているといつか起きてしまうかと思います。
私たちのチームではどうしようもなくなってしまい、結局一から本番環境のデータベースを構築をし直したのですが、これは本来あまりやっていいことではないかと思っています。
もちろん開発の初期段階ですので正直困りはしなかったのですが、実際のサービスだと既に重要な顧客データが多数入っているためです。
こちらは他に何かやり方あれば教えていただけると非常に嬉しいです。

ちなみにcapistrano導入後はデータベースをcreateしたり、dropをさせるときもエラーを見るのと同じくcurrent上でコマンドは打ちます。
rake:db:seedも同様で、capistrano導入後はディレクトリの位置が変更をされるためです。

④credentials.yml関連

ActiveSupport::MessageEncryptor::InvalidMessage

このエラーは序盤結構多かったです。こちらに関しては他に詳しい記事も多数あるかと思いますので割愛します。
credentials.ymlに関しては最初は結構苦戦をしましたが、慣れるとAPIkeyの管理がすごく簡単でした。
怖がるものではないかと思います。

⑤データベース内に数値が入っていなかった時

ActionView::Template::Error (undefined method `id' for nil:NilClass):

すごく単純なようで起きがちなエラーです。
特にテーブルを増やしてカラムに新しく外部キーを増やすと起こりがちです。
私はデータベースをSQLで直接いじっていましたが、Sequelpro等で本番でも見れるようにすることができるので、入れたら楽に設定をできると思います。
※ベーシック認証をしてからだと思うのですが、途中からSequelproは使えなくなりました。おそらく弾かれてしまったのだと思います。もしかするとベーシック認証を使っていてもできるのかもしれませんが、私は調べきれませんでした。

⑥JQueryで発火をしなかった※データが入っていない時

エラーログは出ません。
インクリメンタルサーチをしようとしたのですが、本番環境のデータで名前を検索をする際に、そもそも該当の名前のデータが入っていませんでした。。。

これは完全に単なる勘違いです。即解決できましたが、全くエラーログが出てこないので原因が不明になるので少しの時間、不安になりました。
これも上記の⑤と一緒でSequelproとか入れたらわかりやすそうです。

まとめ

基本はエラーログをしっかりと見ていけば何とかなりました。ただ、データベース関連は視覚化ができていなかったので、苦労をしました。
これに関してはエラーログの構造等を理解しだしてから、格段に理解が深まったので、細かいWebアプリの仕組みに関しては理解をしといたほうがいいなと感じました。
今回挙げた事例に関しては正直かなり簡単なほうだと思っています。これから先も出てくる事例は多いと思うので、また何かあれば記事に挙げていきます。

参考記事

https://www.javadrive.jp/rails/template/index11.html
https://www.bokukoko.info/entry/2017/10/27/231129
https://qiita.com/scivola/items/cc06ddbfd94d3118f693

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

AWS LambdaのCustom RuntimeでサーバーレスDartしてみた

最近Dartに興味を持ってきたので今回はDartの話をします。

dart-logo.png

今までDartはkotlinやscalaと比べてイケてないという評判を聞いて食わず嫌いしていたのですが、Flutterに採用されてから盛り返してるっぽいのを見てお盆休みで時間もあるしロゴもかっこいいしちょっと触って見ようかなと思ったのでLambdaで動くようにしてみました。

最終的な成果物はこちらにおいてあります。

デプロイしてみる

ではとりあえずこの自作ランタイムを使うときの流れを見ていきます。
デプロイに必要な物は以下の通りです。

  • Dart
  • Serverless Framework
  • docker

まずserverless.ymlです。

serverless.yml
service: serverless-dart-sls

custom:
  defaultStage: dev
  api_version: v0

provider:
  name: aws
  runtime: provided
  region: ap-northeast-1
  stage: ${opt:stage, self:custom.defaultStage}

functions:
  hello:
    handler: hello
    events:
      - http:
          path: hello
          method: get
          integration: lambda
  world:
    handler: world
    events:
      - http:
          path: world
          method: post
          integration: lambda

Serverless Frameworkがわかる方にはほとんど説明不要だとは思います。
使ったことが無い方へ説明をするとfunctionsがLambdaを定義している箇所で、それぞれhelloをハンドラーとしてgetリクエストを<url>/helloで受け付けるLambdaファンクションhelloworldをハンドラーとしてpostリクエストを<url>/worldで受け付けるLambdaファンクションworldが定義されています。

次にここでハンドラーとして指定されているhelloworldを見ていきます。
src/handlerの中にそれぞれhello.dartworld.dartというファイルを用意しています。

hello.dart
import '../../runtime.dart';

dynamic hello(dynamic event) {
  return {'msg': '新たな光に会いに行こう。'};
}

void main() {
  lambdaHandler(hello);
}

getに対して適当なメッセージを返却するコードです。
lambdaHandlerが今回自作したランタイムでlambdが呼び出されたときにhelloを実行するようにしています。

world.dart
import '../../runtime.dart';

dynamic world(dynamic event) {
  final body = event['body'];
  return body;
}

void main() {
  lambdaHandler(world);
}

こちらもほぼ同じです。
postで入ってきたbodyをそのまま返しています。

それではこれらをデプロイするための簡単なスクリプトを用意してあるので実行してみましょう。
問題無くデプロイされれば以下のような画面が出てくるはずです。

$ ./deploy.sh
Password: # 一部コマンドのためにsudoが必要
Resolving dependencies... 
Got dependencies!
(略)
Serverless: Stack update finished...
Service Information
service: serverless-dart-sls
stage: dev
region: ap-northeast-1
stack: serverless-dart-sls-dev
resources: 16
api keys:
  None
endpoints:
  GET - https://hoge.execute-api.ap-northeast-1.amazonaws.com/dev/hello
  POST - https://hoge.execute-api.ap-northeast-1.amazonaws.com/dev/world
functions:
  hello: serverless-dart-sls-dev-hello
  world: serverless-dart-sls-dev-world
layers:
  None
(略)

それぞれ正しくデプロイできていれば以下のようなレスポンスが帰ってくるはずです。

$ curl https://hoge.execute-api.ap-northeast-1.amazonaws.com/dev/hello
{"msg":"新たな光に会いに行こう。"}

$ curl -X POST -H "Content-Type: application/json" -d '[{"name": "島村卯月"}, {"name": "渋谷凛"}, {"name": "本田未央"}]' \
https://hoge.execute-api.ap-northeast-1.amazonaws.com/dev/world
[{"name":"島村卯月"},{"name":"渋谷凛"},{"name":"本田未央"}]

中身を見てみよう

Lambdaで動かすためのランタイムの中身をもう少し詳しく見ていきましょう。
まずcustom runtimeで任意のLambdaを動かすのに必要なことは大きく2つです。

  • 所定のapiから必要な情報を取得し得た情報を利用して何かをした後また別のapiに対して結果を送りつける無限ループの実装
  • custom runtimeに設定したときに起動時に最初に実行されるファイルbootstrapの用意

まず無限ループの方から見ていきます。
今回はルートディレクトリにruntime.dartというファイルを用意しています。

runtime.dart
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:io' show Platform;

void lambdaHandler(Function callback) async {
  final api = Platform.environment['AWS_LAMBDA_RUNTIME_API'];

  while (true) {
    final response =
        await http.get('http://${api}/2018-06-01/runtime/invocation/next');

    final event_data = json.decode(utf8.decode(response.bodyBytes));
    final request_id = response.headers['lambda-runtime-aws-request-id'];

    final result = await callback(event_data);

    http.post(
        'http://${api}/2018-06-01/runtime/invocation/${request_id}/response',
        body: json.encode(result));
  }
}

あまり難しいことはしていませんが一通り見ていきます。
http://${Platform.environment['AWS_LAMBDA_RUNTIME_API']}/2018-06-01/runtime/invocation/nextを叩くことでLambdaへのリクエストの各種情報とリクエストIDが取得できます。
これをこの関数の引数のcallbackに処理をさせ、その結果をそのままhttp://${Platform.environment['AWS_LAMBDA_RUNTIME_API']}/2018-06-01/runtime/invocation/${request_id}/responseに引き渡すことで結果を返却することができます。

次にbootstrapです。もし先程デプロイしたままであればルートディレクトリにこのようなファイルがあるはずです。

bootstrap
#!/bin/sh

bin_dir="$LAMBDA_TASK_ROOT/./.aot"
$bin_dir/dartaotruntime $bin_dir/$_HANDLER.dart.aot

.aotというディレクトリにaotのランタイムとコンパイルした結果を設置してありlambdaが起動したときにそれをそのまま実行するようになっています。
このbootstrap.aotの中身を用意しているので先程使用したdeploy.shなのですが、この中身はこんな感じになってます。

deploy.sh
#!/bin/bash

stg=$1
[ "$stg" = "" ] && stg="dev"

handler_dir="./src/handler"
bin_dir="./.aot"

rm -rf ./bootstrap $bin_dir
mkdir $bin_dir

cat <<EOF > ./bootstrap
#!/bin/sh

bin_dir="\$LAMBDA_TASK_ROOT/$bin_dir"
\$bin_dir/dartaotruntime \$bin_dir/\$_HANDLER.dart.aot
EOF
chmod +x ./bootstrap || exit 1

docker run --rm -v $(pwd):/work -w /work google/dart cp /usr/lib/dart/bin/dartaotruntime ./.aot
sudo chmod +x $bin_dir/dartaotruntime || exit 1

pub get || exit 1

cat ./serverless.yml |
grep 'handler'       |
awk '{print $2}'     |
while read line; do
    dart2aot $handler_dir/$line.dart $bin_dir/$line.dart.aot || exit 1
done &&
sls deploy -s $stg

一つずつ見ていきましょう。

handler_dir="./src/handler"
bin_dir="./.aot"

rm -rf ./bootstrap $bin_dir
mkdir $bin_dir

まずコンパイル結果を置く場所や元のハンドラー場所を設定し、前回のデプロイ時作ったファイル類を掃除してます。

cat <<EOF > ./bootstrap
#!/bin/sh

bin_dir="\$LAMBDA_TASK_ROOT/$bin_dir"
\$bin_dir/dartaotruntime \$bin_dir/\$_HANDLER.dart.aot
EOF
chmod +x ./bootstrap || exit 1

実行に必要なbootstrapを生成しています。
別にbootstrapはわざわざ毎回生成する必要は本来無いのですが、既存のプロジェクトへの組み込みやすさ等を考えて今回はここで生成するようにしてしまいました。

docker run --rm -v $(pwd):/work -w /work google/dart cp /usr/lib/dart/bin/dartaotruntime ./.aot
sudo chmod +x $bin_dir/dartaotruntime || exit 1

pub get || exit 1

ここでdart側の事前準備をしています。
dartのdokcerイメージからlinux向けのaotの実行ランタイムをとって来ていますがこれはmacを考慮してそうしているだけでlinuxな環境ならローカルにある物をそのまま持ってきても問題無く動作します。
正直ここはもっと色々な物を一緒に詰めてやる必要があるかな?っと思ってたのですがlayerとかで頑張って用意する必要もなくあっさり動いてしまいました。

cat ./serverless.yml |
grep 'handler'       |
awk '{print $2}'     |
while read line; do
    dart2aot $handler_dir/$line.dart $bin_dir/$line.dart.aot || exit 1
done &&
sls deploy -s $stg

最後に各ハンドラーをコンパイルしてデプロイしています。
grepやawkでガチャガチャやってますがこれでハンドラーに指定している名前を引っ張りだしてコンパイルしています。

$ cat ./serverless.yml | grep 'handler' | awk '{print $2}'
hello
world

またデプロイ時にステージを選択するオプションに引数を渡していますがこれはこのスクリプトに渡す引数をそのまま横流し、無いならdevになるようにしています。

stg=$1
[ "$stg" = "" ] && stg="dev"

Q.仕組みとかよくわかんねぇけどとりあえず既存のプロジェクトに組み込みてぇんだけどどうすればいいのさ?

そういった声にお答えしてこんな感じにやればいいんじゃねって感じの手順を用意しました。
ただし筆者はFlutter等の実践的なフロントやアプリのDartの経験があるわけでは無く、動作は保証しかねるのであしからず。

1. pubspec.yamlにhttp追記
pubspec.yaml
dependencies:
  http: ^0.12.0+2
2. pubspec.yaml同じ階層にserverless.ymlとdeploy.shを設置

おそらく同じ階層にあればいけるはず

3. お好きなところにruntime.dartとハンドラーを置いておくディレクトリを用意

runtime.dartはハンドラーから読めれば良いのでそれぞれのプロジェクトにとって適切な場所に置いておいてください。
ハンドラーを置くディレクトリもデフォルトではsrc/handlerとしていますが簡単に変更出来るのでこれも適切な場所に用意してください。
ただしこのデプロイスクリプトではハンドラーのディレクトリに更を階層分けるような構造には対応していないので注意してください。

4. deploy.shの修正

もし3でハンドラー置き場等を変更していたりする場合にはここの変数を修正します。

handler_dir="./src/handler"
bin_dir="./.aot"

handler_dirがハンドラー置き場なので好きに変更しましょう。
またコンパイル結果を置く場所もここで変更できます。

5. デプロイ実行

これで実行すればデプロイされるはずです。

$ ./deploy.sh

完走した感想

ということでDart on Lambdaでした。
Dartはシングルバイナリを吐くタイプの言語では無いのでCrystalなどに比べて正直もっと苦戦すると思ってたのですが思いの外あっさり動きましたね。
あと今回始めてDartを触ってみて思ったのですが、vscodeの拡張の出来がいいのか思ってたより開発しやすかったです。
ロゴが好きなので触っただけだったので実績だけ作って終わりでもいいかなって感じで始めたけどこれならしばらくは個人で開発するLambdaはDartにしてもいいかもしれないですね。

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

【API作るよ】Serverless FrameworkとSAMを比較してみる

AWSでAPIを作りましょう(唐突)
面倒なことはフレームワークに丸投げしましょう。

TL;DR

  • AWSでサーバレスアーキテクチャ構築するときに代表的なフレームワークであるServerless FrameworkとSAMを比較してみたよ
  • 筆者の判定だとServerless Frameworkに軍配かな
  • SAMはプラグインがやや少ないのと癖が強いから今後に期待!

導入

サーバサイドエンジニアをやっているとAWSでAPI構築しましょうっていう案件に参画することがあるかもしれません。
そんなときに、ゼロから作ろうって話になることも……あるかもしれません。

で、だいたいそのときにAWSで

  • Lambda
  • DynamoDB
  • API Gateway

とかそれぞれの知識が必要になってくるわけですが、一つ一つ個別に作ってるとかなり大変です。
Serverless FrameworkやSAMのようなフレームワークを使えば、そこらへんを設定ファイル一枚、コマンド一発で構築まで全部やってくれます。

Serverless Frameworkについて

この手のフレームワークのデファクトスタンダード……とまで言うと言いすぎかもしれませんが、この手のフレームワークにしては非常に「枯れて」いて、機能も充実しています。
APIを構築する場合……というかサーバレス環境を構築する場合、
「あるURIにPOST投げるとDynamoDBに要素がPUTされる」
みたいな機能を作ることになると思いますが、いちいち試すためにデプロイするのも面倒ですから、ローカルでDynamoDBを動かして機能を試してみたいところですよね。

そんなときローカルでサクッと動かせるというのは結構重要です。

詳細はこちらを参照してください(丸投げ)
Serverless アプリケーションをローカルで開発する
このとおりに作れば大丈夫です。

……丸投げが酷すぎるので、少し補足します。

Serverless FrameworkはNode.js系のプラグインが非常に充実していて、上記の記事にもあるように

serverless-dynamodb-local

のようなプラグインを導入するとかなり気軽にローカルでDynamoDBの環境を構築することができます。
後述しますが、SAMを使う場合だともう少し複雑になります。

【以下余談】
S3との連携プラグインも存在していますが……
https://www.npmjs.com/package/serverless-s3-local

LambdaをPythonで構築する場合、ちょっと微妙です。
ローカルでの構築例がgithubにありますが……
https://github.com/ar90n/serverless-s3-local/blob/master/example/simple_event_python/handler.py

読んで頂くとわかるように、「ファイル書き込むだけ」的なコードになっており、ローカルでの動作確認という用途としては……少々残念です。
【余談ここまで】

SAMについて

真打ち、AWS公式のフレームワークです。

みんな大好きVisual Studio Code向けに公式提供されているAWS Toolkitと連携してます。
スクリーンショット 2019-08-16 15.04.08.png
Pycharm版もあるぞ。

Serverless Frameworkがコマンド一発でデプロイできるのと比較して、少しステップは多いですがVisual Studio CodeのGUIからポチポチとクリックでデプロイができ、これはこれで便利です。

しかもローカル実行やデバッグもできるスグレモノ。
sam-configure-and-run-still-1.png
(画像は公式のREADMEより)

これはもうSAMを選ぶしか無いのでは?
と言いたいところですが……

実際の構築をざっとお読みください。
こちらの記事が非常にわかりやすいです(再度丸投げ)
Serverless アプリケーションをローカルで開発する

Serverless Frameworkとの違いは色々あるのですが、開発したAPIをローカルで実行する場合の最大の違いは……

  1. リクエストをローカルで受け取る
  2. Dockerプロセスが立ち上がる
  3. Lambdaが実行される

という三段階にあると考えています。
特に2番が曲者で、上記の記事では

  • DynamoDBをDockerでローカルに構築する
  • DynamoDBが起動するネットワークを作成
  • Lambda実行のDockerプロセスが機動するネットワークを指定

という流れで、LambdaがDynamoDBにアクセスできるようにしています。

……これ、デバッグが難しいです……
DynamoDBが動いているネットワークが指定されちゃってるので、逆にローカル実行した際にネックになっちゃってるみたいでアクセスが上手くできなかったです。

一応"Configure"から色々設定してデバッグの引数などは設定できるようですが、一方VSCodeでリモートデバッグ的な設定をして、Dockerプロセスの生成後にステップ実行……みたいな方法は厳しそうでした。
そこまでやるなら普通にServerless Frameworkで構築して、引数:eventのモックみたいなものを作成してそのままデバッグしてしまったほうが楽なのでは?

……という感が否めませんでした。
やってみると実際そっちのほうが楽でした。

それと、AWS Toolkitが出て間もないので情報がやや少ないというのも大きいです。
SAM自体はAWS Toolkit以前からあったのでそれなりに情報があるのですが、AWS Toolkitを使わない場合「Serverless Frameworkよりも多少複雑で、プラグインが少ない」フレームワークという印象で、正直あまり良い印象はありません。

結論

Serverless FrameworkとSAMの最大の差は、要するに
「プラグインの豊富さ」
だと考えています。
例えばSAMで例に出した記事の場合はDynamoDB localを自前で構築していますが、自前で構築する要素が多くなればなるほどそれは負債になる可能性が高いわけで……
これがプラグインひとつ定義すればOKということであれば非常に楽です。

そんなこんなで今回私が関わった案件ではServerless Frameworkを選定しましたが、今後SAM・AWS Toolkitのプラグインや知見が増えた場合、SAMのほうが楽に構築できるようになるかもしれません。

その他フレームワークについて

Serverless Framework、SAMに限らずこの手のフレームワークにはPython系のフレーム枠であるZappaなども存在しています。
……が。情報が明らかに少ないです。
システム立ち上げにおいて「みんな使ってるのを使う」はある程度の正義です。

また、Lambdaを用いたAPI構築の場合、真打ちの真打ちと言ってもいいでしょう、AWS Cloud9が存在しています。
こちらはフレームワークではなくIDEです。
そのままの状態でデバッグしたり、そのままデプロイしたり、かなり機能が豊富で
「アレ? これがベストなのでは?」
と思い、色々検証しましたが……

  1. ステップ実行がうまく動いてくれない
  2. 何故かデバッグ時の「停止」ボタンがない

ことで心が折れました。
1に関しては自分の設定ミスの可能性もありますので、もう少し調査したら変わっていたかもしれません。

が、2に関してはもう完全に心が折れました。
「そこ!?」と言われてしまうかもしれませんが、「当然のように大抵のIDEで備えているものがパッと分かる位置に置いていない」IDEの使用を今後の参画者に強いるのは、自分には無理だなぁと。
これはいくらなんでも学習コストや開発者に対する制約が多すぎるなという感想です。

結論の結論

Serverless Frameworkはとてもいいぞ。
SAMとAWS Toolkitは今後に期待ですね。

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

KubeEdge環境を構築してみた by AWS EC2

TL;DR

KubeEdgeの公式手順に従ってKubeEdge環境をEC2インスタンス上に構築してみた
基本的には参考サイトに載っている手順に準じて構築している
構築までしかしていないので悪しからず!

そもそもKubeEdgeとは?

CNCF(Cloud Native Computing Foundation)の配下で最近GAされたエッジコンピューティングフレイムワークのOSS
詳しくはこちら

Kubernetes(以下、k8s)もCNCFによりGAされており、いわば本家お墨付きのようなエッジ用k8s
似たようなので、Rancherのk3sがある → GitHub

はじめに

KubeEdge環境の構築には主に3通りの方法がある(詳しくはGHのUsage参照)

  • 用意されたバイナリを実行し、k8s環境から全て自動構築する
  • k8sの環境を自前で構築し、 リリースパッケージを使って構築する
  • k8s環境を自前で構築し、ソースからビルドして構築する

今回は2つ目の方法で構築している、なぜか?
答え単純、1つ目の方法で上手くいかなかったからである!
※筆者は3回やって挫折
3度目に失敗してから「もう自分でやったほうが早いな」と思い至り2番目の方法で構築したらすんなりできた

環境

今回は構築方法までなのでエッジ側もAWSのEC2インスタンスでお手軽に構築してみた
本来ならエッジ側はラズパイなどのデバイスになるはずである
以下、環境情報

バージョン

全て最新版を使用した(2019年8月時点)

  • Docker: 19.03.1
  • Kubernetes: 1.15.2
  • kubeedge: 1.0.0

マシンスペック

マスタを大きく設定したけど、こんなにいらなかった
これならローカルのVMでも動かせそうである

  • master
    • OS: Ubuntu 18.04 LTS
    • CPU: 8
    • Memory: 32GB
    • Storage: 30GiB

稼働状況

Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests    Limits
  --------           --------    ------
  cpu                850m (21%)  100m (2%)
  memory             190Mi (1%)  390Mi (2%)
  ephemeral-storage  0 (0%)      0 (0%)
  • worker
    • OS: Ubuntu 18.04 LTS
    • CPU: 2
    • Memory: 4GB
    • Storage: 15GiB

構築手順

本題である構築手順!
EC2インスタンスの立て方については書かないので悪しからず
公式に従い「kubeadm」でk8sクラスタを構築する

共通

まずは必須であるDockerとk8s関連のパッケージをそれぞれにインストールする

sudo apt update
sudo apt upgrade -y  # アップグレードは筆者の好み、正直しない方がいいかもしれない

# docker
sudo su -
apt-get update && apt-get install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository \
  "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) \
  stable"
apt-get update && apt-get install docker-ce

cat > /etc/docker/daemon.json <<EOF
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF
mkdir -p /etc/systemd/system/docker.service.d
systemctl daemon-reload
systemctl restart docker
systemctl status docker

# k8s関連
apt-get update && apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
apt-get update
apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl

echo 'KUBELET_EXTRA_ARGS=--cgroup-driver=systemd' > /etc/default/kubelet

systemctl daemon-reload
systemctl restart kubelet
systemctl status kubelet  # まだkubeletは動かないが問題ない

マスタ

共通の構築ができたらまずはマスタを構築する
今回はCNIにArmでも動く「flannel」を使う

sudo su -

# for flannel
kubeadm init --pod-network-cidr=10.244.0.0/16
sysctl -w net.bridge.bridge-nf-call-iptables=1

# KubeEdge用に編集する
vim /etc/kubernetes/manifests/kube-apiserver.yaml
- - --insecure-port=0
+ - --insecure-port=8080
+ - --insecure-bind-address=0.0.0.0

# flannelのPodをデプロイする
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
kubectl label nodes ip-172-31-38-225 type=master
kubectl get nodes -L type  # StatusがReadyになっているはず

# KubeEdgeで使うCRDをデプロイする
wget -L https://github.com/kubeedge/kubeedge/blob/master/build/crds/devices/devices_v1alpha1_devicemodel.yaml
kubectl create -f devices_v1alpha1_devicemodel.yaml
wget -L https://github.com/kubeedge/kubeedge/blob/master/build/crds/devices/devices_v1alpha1_device.yaml
kubectl create -f devices_v1alpha1_device.yaml

# KubeEdgeのリリースパッケージをダウンロード
curl -LO https://github.com/kubeedge/kubeedge/releases/download/v1.0.0/kubeedge-v1.0.0-linux-amd64.tar.gz
tar -xf kubeedge-v1.0.0-linux-amd64.tar.gz -C /etc

# KubeEdgeで使う証明書を発行
wget -L https://github.com/kubeedge/kubeedge/blob/master/build/tools/certgen.sh
chmod +x certgen.sh
bash -x ./certgen.sh genCertAndKey edge

# マスタ側のKubeEdgeを起動する
cd /etc/kubeedge/cloud
nohup ./edgecontroller > edgecontroller.log 2>&1 &
tail edgecontroller.log

# エッジ側に同じものを送るためにコピーしておく
cp -r /etc/kubeedge /home/ubuntu
chown ubuntu:ubuntu /home/ubuntu/kubeedge -R

ローカル

今回はマスタ側で発行した証明書などをエッジ側にも置くためにローカルからSCPでコピーして送った
※マスタとエッジが直接SSHできるならこれはいらない

scp -r -i ~/.ssh/aws/admin.pem ubuntu@<master-ip>:/home/ubuntu/kubeedge ~/
scp -r -i ~/.ssh/aws/admin.pem /home/ubuntu/kubeedge ubuntu@<edge-ip>:/home/ubuntu/

エッジ

さて、マスタが構築できたらエッジを構築したk8sクラスタに参加させる
※「kubectl」をマスタ側で叩いているが、エッジでも叩けるようにすればどちらでも問題ない
詳しくはこちら

sudo su -

# 送っておいたファイルを適切な場所に置いておく
cp -r /home/ubuntu/kubeedge /etc
chown root:root /etc/kubeedge -R

# 構築したクラスタに参加させる ※適宜置き換える
kubeadm join 172.31.38.225:6443 --token <token> \
    --discovery-token-ca-cert-hash sha256:<ca-cert-hash>

# in master
# nodeの名前は適宜置き換える
kubectl get nodes  # 新しくnodeが追加されているはず
kubectl lobel nodes ip-172-31-39-8 type=worker
wget -L https://github.com/kubeedge/kubeedge/blob/release-1.0/build/node.json
vim node.json
- name: "fb4ebb70-2783-42b8-b3ef-63e2fd6d242e",
+ name: "ip-172-31-39-8",
kubectl apply -f node.json

# エッジ側のKubeEdgeを起動する
cd /etc/kubeedge/edge
vim conf/edge.yaml  # nodeの名前を適切に修正する
- url: wss://172.31.38.225:10000/e632aba927ea4ac2b575ec1603d56f10/fb4ebb70-2783-42b8-b3ef-63e2fd6d242e/events
+ url: wss://172.31.38.225:10000/e632aba927ea4ac2b575ec1603d56f10/ip-172-31-39-8/events
- node-id: fb4ebb70-2783-42b8-b3ef-63e2fd6d242e
+ node-id: ip-172-31-39-8
- hostname-override: fb4ebb70-2783-42b8-b3ef-63e2fd6d242e
+ hostname-override: ip-172-31-39-8
nohup ./edge_core > edge_core.log 2>&1 &
tail edge_core.log

テスト

最後にちゃんと動いてるか、簡単に確認する

kubectl apply -f https://raw.githubusercontent.com/kubeedge/kubeedge/release-1.0/build/deployment.yaml
kubectl get pods
kubectl get deploy

おわりに

とりあえず動くものを構築してみた
最初の方にも書いたが、参考サイトに従って構築したので不明な点は参考サイトを参照してほしい
正直KubeEdge自体が正しく動いているか、確信が持ててない...
故に次は、KubeEdgeのExampleを試してみたいと思う
https://github.com/kubeedge/examples/tree/master/led-raspberrypi

参考

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

時間のかかるPythonスクリプトを定期実行させる

環境

macOS version 10.14.6
Ubuntu 16.04 in AWS
Python 3.5.2

前提

AWSのEC2インスタンスを使って、自動的にスクリプトを起動するcron jobを作りました。
本来は、
・AWS Lambda
・ECS Scheduled Tasks
等を使うのが得策だとは思いますが、

①今回実行するスクリプトが丸一日くらいかかる(Lambdaは15分が限界との噂)
②手っ取り早く、安く済ませたい

との考えがあり、少しレガシーかもしれないですが、シンプルなcrontabを使用することにしました。
本当はDocker環境とかを構築したかったのですが、初心者で且つ時間の都合上、EC2に収まってしまいました。
より良い方法等あれば、ぜひ教えて頂ければと思っております。
cronの選定に関しては、下記などを参考にしました。
https://medium.com/better-programming/cron-job-patterns-in-aws-126fbf54a276

1, EC2インスタンスに接続

インスタンスは作成済みとの前提で進めていきます。今回は上記の環境で動かしていきます。
ターミナルで、

$ ssh -i ~/directory/directory/sample.pem ubuntu@IPv4 パブリックIP

ssh接続でサーバーにログインします。
ubuntuの部分は、AMI(サーバーの種類?)によって変わるので、気をつけてください。
余談ですが、ファイル・フォルダのパスがわからない場合は、そのファイル・フォルダをターミナルにドラッグ&ドロップするとフルパスが表示されるみたいです。当たり前すぎるかもしれないですが、初心者の私は割と重宝しています・・・

2, Pythonをインストール

サーバーにPythonをインストールしていきます。Ubuntu環境なので、aptコマンドを使っていきます。
念の為、

$ sudo apt update
$ sudo apt upgrade

を実行しておきます。


以下でPythonをインストールしていきます。

$ sudo apt install python3 python3-pip python3-dev

最低限ここまではどの人も共通してインストールするのかなと思っております。
あとは、使うモジュール等によって必要なものがあると思うので、その都度インストールすればいいかと思いました。
今回私は後述するモジュールを使うために、以下のものもインストールしました。

$ sudo apt install libmysqlclient-dev libffi-dev libssl-dev build-essential

もしかしたら、いらないものも含まれてるかもしれません。

3, モジュールをインストール

今回のスクリプトで使ったのは以下のモジュールだったので、サーバーで使えるようにインストールします。

$ pip3 install requests sqlalchemy selenium lxml paramiko eventlet mysqlclient

4,crontabを編集する

EC2にはcron jobのための機能がもともと入ってるみたいなので、以下のコマンドを入力すると、cronに関する記述ができる編集画面に移行します。

$ crontab -e

気をつけないといけないのが、$ crontab -rとすると、全てのcrontabのデータが消えるみたいなので、設定してからは、気をつけないと苦労が水の泡になるそうです。私は毎回指差し確認しながら実行しています・・・。

余談ですが、初心者の私がビビったのは、編集画面をVimにしてしまったこと。泣きそうになりました。
nanoがなんだかんだわかりやすかったので、怖い方はnanoを使ったほうがいいとは思います。
editorは以下で後からでも変えれます。

$ select-editor



それではcrontabに記述していきますが、書き方はいろいろあるので、要件にあった内容を調べたほうがいいと思います。今回私は以下のようにしました。
0 20 * * * python3 /insert-fullpath/sample.py >> /insert-fullpath/cron_log.txt 2>&1
上記は「毎日20時にsample.pyを起動する。出力されたもの、さらにエラーが出た場合は予め作っておいたcron_log.txtに書き込んでください」という指示です。
とにかく、起動しているかわからず怖かったので実行結果がわかるように上記のようにしました。
ちなみに、保存した上記crontabのファイルは、ターミナルの指示に従って、何も操作せずに保存すると/var/spool/cron/crontabs/bitnami/に存在していました。

5, タイムゾーンの設定

デフォルトだとサーバーはロンドン時間になってると思うので、タイムゾーンも確認しておきます。

$ timedatectl

上記で現行のタイムゾーンを確認

$ sudo timedatectl set-timezone America/Vancouver

これで、バンクーバー時間になると思います。

おわりに

社内でブラウザテストのスクリプトを起動させるために、今回初心者ながらいろいろ試してみました。本当はLambdaやDockerなどの技術を使いたかったのですが、力及ばず。
よりよい方法やお気づきの点がありましたら、お声がけ頂けると大変嬉しいです。

今働かしていただいている会社のサービスも、ぜひ御覧ください!
サッカー順位表 FootyStats

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

【PHP】Amazon SESを使った添付ファイル付きメールを送信する

はじめに

この記事ではAmazon SESを利用してPHPでメール送信する方法について説明します。
またAWSやSESについての説明や導入方法については様々な記事がありますので、本記事では省きます。

簡易メールの送信(SendEmail)

最も簡単なメール送信で、送信元アドレス・送信先アドレス(複数可)・件名・本文を設定することでメール送信できます。

Request Parameters

Destination
 ・ 送信先のメールアドレス(配列)
Message
 ・ メール本文
 ・ メール件名
Source
 ・ 送信元のメールアドレス

その他にもBCC送信などもあるようなので必要な方は公式サイトの確認をお願いします。

実装:テキストメールの場合

Textメール
use Aws\Ses\SesClient;

class MailUtil
{
    public static function sendMail() {
        // Amazon SES クライアントのインスタンス生成
        $client = new SesClient([
            'region' => env("AWS_SES_REGION"),   // SESのリージョン
            'version' => 'latest',
            'credentials' => [
                'key' => env("AWS_KEY"),
                'secret'=> env("AWS_SECRET")]
        ]);

     $fromAddress = "testFrom@test.com";
     $toAddressList[] = "testTo@test.com";
     $subject = "SESメールの送信";
     $text = "SESメールの実装方法について";

        // 送信元アドレス・送信先アドレス(Array)、件名、本文をリクエストパラメータに代入
        $request = [];
        $request['Source'] = $fromAddress;
        $request['Destination']['ToAddresses'] = $toAddressList;
        $request['Message']['Subject']['Data'] = $subject;
        $request['Message']['Body']['Text']['Data'] = $text;

        // 送信する
        $client->sendEmail($request);
    }
}

実装:HTMLメールの場合

リクエストパラメータを下記のように設定するだけで可能となります。

HTMLメール
        $request = [];
        $request['Source'] = $fromAddress;
        $request['Destination']['ToAddresses'] = $toAddressList;
        $request['Message']['Subject']['Data'] = $subject;
        $request['Message']['Body']['Html']['Data'] = $text;
        $request['Message']['Body']['Html']['Charset'] = 'iso-2022-jp';

AWS SDK for PHP を使用して E メールを送信する

添付ファイル付きメールの送信(SendRawEmail)

SendEmailに対してSendRawEmailは高度なメールカスタマイズをすることができます。

最終的なソースコード

use Aws\Ses\SesClient;
use Carbon;

class MailUtil
{
    public static function sendAttachedMail($toAddrList, $fromAddr, $subject, $body, $data)
    {
        // sendEmailと同様
        $client = new SesClient([
            'region' => env("AWS_SES_REGION"),
            'version' => 'latest',
            'credentials' => [
                'key' => env("AWS_KEY"),
                'secret' => env("AWS_SECRET")
            ]
        ]);

        # 添付ファイル(今回はwindowsをメインで想定していたのでSJIS-winにエンコードしてます)
        $attachFileData['data']     = mb_convert_encoding($data, 'SJIS-win', 'UTF-8');
        $attachFileData['filename'] = Carbon::now()->format('Ymd') . '.csv';
        $attachFileData['mimetype'] = 'text/csv';

        // RawMessageの作成
        $msg = self::createRawMessage($toAddrList, $fromAddr, $subject, $body, $attachFileData);

        $client->sendRawEmail([
            'Source'       => $fromAddress,
            'Destinations' => [$toAddressList],
            'RawMessage'   => [
                'Data' => $msg,
            ],
        ]);
    }

    /**
     * 添付ファイル付きメール用のRawMessageを作成する
     */
    private static function createRawMessage($to, $from, $subject, $body, $filedata)
    {
        if (isset($filedata['filename']) && isset($filedata['data']) && isset($filedata['mimetype'])) {
            $boundaryStr = uniqid(rand());

            $message = "To: " . $to . "\n";
            $message .= "From: " . $from . "\n";
            $message .= "Subject: " . $subject = str_replace("\r", "", $subject) . "\n";
            $message .= "MIME-Version: 1.0\n";
            $message .= 'Content-Type: multipart/mixed; boundary="' . $boundaryStr . '"';
            $message .= "\n\n";
            $message .= "--" . $boundaryStr . "\n";
         $message .= "Content-Type: text/html; charset=iso-2022-jp";
            $message .= "\n";
            $message .= "Content-Transfer-Encoding: 7bit\n";
            $message .= "Content-Disposition: inline\n";
            $message .= "\n";
            $message .= $body;
            $message .= "\n";
            $message .= "\n";
            $message .= "--" . $boundaryStr . "\n";
            $message .= 'Content-Type: ' . $filedata['mimetype'] . '; name="' . $filedata['filename'] . '"';
            $message .= "\n";
            $message .= "Content-Transfer-Encoding: base64\n";
            $message .= 'Content-Disposition: attachment; filename="' . $filedata['filename'] . '"';
            $message .= "\n\n";
            $message .= base64_encode($filedata['data']);
            $message .= "\n";
        } else {
            Log::warning('添付ファイルデータが設定されていません');
        }
    }       
}

https://github.com/aws/aws-sdk-php/issues/1295#issuecomment-307517135

解説

boundary

メッセージヘッダやコンテンツといったメールの各パートは、boundary で分離されます。
boundary は、各パートの開始と終了を示す文字列で、これらを使ってメッセージ本文、または今回のような添付ファイルを分けて、設定を行なっていきます。

ヘッダー

ヘッダーには送信先、送信元、件名、MIME-Ver、コンテンツタイプを設定します。
今回はヘッダー・本文・添付ファイルを設定するため、メッセージのコンテンツタイプにmultipart/mixedを設定します。これを設定することで各パートを別々に扱う必要があることがわかります。

    $message = "To: " . $to . "\n";
    $message .= "From: " . $from . "\n";
    $message .= "Subject: " . $subject = str_replace("\r", "", $subject) . "\n";
    $message .= "MIME-Version: 1.0\n";
    $message .= 'Content-Type: multipart/mixed; boundary="' . $boundaryStr . '"';
    $message .= "\n\n";

メッセージ本文

文字コードに関しては、古いメーラーを使用したユーザーが想定されるためUTF-8ではなくiso-2022-jpを選択しましたが、最近はUTF-8でも問題ないかと思います。

    $message .= "Content-Type: text/html; charset=iso-2022-jp";
    $message .= "\n";
    $message .= "Content-Transfer-Encoding: 7bit\n";
    $message .= "Content-Disposition: inline\n";
    $message .= "\n";
    $message .= $body;

Content-Disposition
・ inline
 メール本文のようなWebページの一部またはWebページとして表示可能である場合はinlineを設定します。

・ attachment
 ダウンロード可能な添付ファイルの場合に、Content-Disposition: attachment; filename="hoge.xxx"といった形式で記述します。

添付ファイル

    // mimetype:'text/csv'
    $message .= "--" . $boundaryStr . "\n";
    $message .= 'Content-Type: ' . $filedata['mimetype'] . '; name="' . $filedata['filename'] . '"';
    $message .= "\n";
    $message .= "Content-Transfer-Encoding: base64\n";
    $message .= 'Content-Disposition: attachment; filename="' . $filedata['filename'] . '"';
    $message .= "\n\n";
    $message .= base64_encode($filedata['data']);
  • Content-Type
    添付ファイルの種類で今回はCSVファイルを指定
  • Content-Disposition
    添付ファイルのためattachmentをセットし、添付ファイル名を指定
  • Content-Transfer-Encoding
    添付ファイルのエンコードに使用されるスキーム。添付ファイルでは、ほとんどの場合この値は base64指定

補足説明

送信結果の判別

AWS SESではメール送信結果を受け取ることができます。
しかし受け取り可能なのは送信失敗(バウンス処理、迷惑メール)となった場合のみで成功通知は受け取ることができません。

種別 結果判別可否
送信成功
バウンス処理
迷惑メール

そのため送信後はしばらく待って失敗通知が来ていなければ、おそらく送信成功したのだと判断する必要があり注意が必要となります。

参考

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

aws Lambdaのカスタムランタイムを利用してaws-cliを実行する

awsのリソースを自動展開するシステムを開発しています。リソース内部の設定が複雑でありPython SDKで実装するとコマンドの検証に不便が出てきそうな雰囲気だったためaws-cliをLambdaで実行しようと考えました。

AWSのチュートリアルを見ればファイルの構3成は把握できると思います。bootstrapがイベントをwhile trueで待ち受け、検知するとそれをパースしてイベントハンドラ(ここではfunction.shの関数)に投げます。その出力を以ってレスポンスとして返します。

コード

チュートリアルのコードに一部インストール用の設定を加えています。インストールを含むためテスト実行時にTimeoutエラーが出るかもしれませんが焦らずにタイムアウト時間を伸ばしましよう。

bootstrap
#!/bin/sh
set -euo pipefail

# tmpフォルダ内にawscliを配置するためHOMEとPATHを設定
export HOME="/tmp"
export PATH="$HOME/.local/bin:$PATH"

# pipをインストール
cd /tmp
curl -sSL https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python get-pip.py --user
# pipを用いてawscliをインストール
pip install awscli --user

# Handler format: <script_name>.<function_name>
#
# The script file <script_name>.sh  must be located at the root of your
# function's deployment package, alongside this bootstrap executable.
source $(dirname "$0")/"$(echo $_HANDLER | cut -d. -f1).sh"

while true
do
    # Request the next event from the Lambda runtime
    HEADERS="$(mktemp)"
    EVENT_DATA=$(curl -v -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
    INVOCATION_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)

    # Execute the handler function from the script
    RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA")

    # Send the response to Lambda runtime
    curl -v -sS -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$INVOCATION_ID/response" -d "$RESPONSE"
done

handlerでaws-cliの動作を確認する。標準エラー出力へaws-cliのバージョンが出力されるか確認します。

function.sh
function handler () {
  EVENT_DATA=$1
# このhandlerの標準出力(1)がそのままbootstrapでRESPONSEになる
# よってデバッグ用echoは標準エラー出力へ逃がす必要がある
  echo "$EVENT_DATA" 1>&2;
  echo $(aws --version 1>&2);
  RESPONSE="Echoing request: '$EVENT_DATA'"

  echo $RESPONSE
}

無事に出力されれば動作しています。今回のコードではtestにデフォルトのものをそのまま利用しているため$EVENT_DATAはデフォルトのものです。

output
{"key1":"value1","key2":"value2","key3":"value3"}
aws-cli/1.16.219 Python/2.7.16 Linux/4.14.123-95.109.amzn2.x86_64 botocore/1.12.209

to do

  • aws-cliを利用した自動展開の検証
  • リソース立ち上げ時間とタイムアウトの格闘
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS CDKの'aws-s3-deployment'を使ってクライアントサイドも一緒にデプロイする

動機

SPAでアプリケーション作ってS3+CloudFrontで公開することってよくありますよね?

どうせならSPA側のリソースとCloudFormationのスタックを別々に管理するのではなく、例えばスタックをdeployしたらSPA側(つまりS3の中身)も同時に更新したいものです。

今までそれをやろうと思うと例えば...

  • SPA側の最新のビルドを(CodePipelineなどを使って)どこかのバケット(ビルドバケットと呼ぶ)に常に置いておく
  • CloudFormation側にCustom Resourceを定義して、ビルドバケットからデプロイ用のバケットにsyncする
    • しかもLambda側からは普通にaws s3 syncが呼べないためawsclidriverのwrapperとか用意しなきゃいけない...

ということで、面倒で仕方無いわけです。

そんなことを思いながら、先日(もう古い?)GAになったAWS-CDKのリファレンスを眺めていると...

aws-s3-deployment

...ん?何これ?

しかもサンプルソースを見ると、

const websiteBucket = new s3.Bucket(this, 'WebsiteBucket', {
  websiteIndexDocument: 'index.html',
  publicReadAccess: true
});

new s3deploy.BucketDeployment(this, 'DeployWebsite', {
  source: s3deploy.Source.asset('./website-dist'),
  destinationBucket: websiteBucket,
  destinationKeyPrefix: 'web/static' // optional prefix in destination bucket
});

./website-distにあるソースをWebsiteBucketにデプロイしてくれるっていうのかい!?

これは試すしか無いってことでやってみました。

環境周り

awscliのセットアップ

もう色々な方が書いているので省略。公式を見て頑張ってください。

当然、ACCESS KEY IDとSECRET KEYを発行するユーザーにはS3やCloudFrontなどを作ることができるポリシーやCloudFormationをいじることができるポリシーが当たっていないとダメです。私はAdminロールが当たってるユーザーでやりました。

node.js,npmのインストール

これもよしなに頑張ってください。グーグル先生に訊いた方が早いです。

aws-cdkのインストール

$ npm install -g aws-cdk

$ cdk --version
1.4.0 (build 175471f)

cdk bootstrap

aws-cdkを動作させるために必要な環境(zipしたソースを置くためのバケットなど)をよしなに作ってくれるみたいです。

$ cdk bootstrap

これがエラーになるようであればawscliのprofileに紐づいているIAMの権限周りを見直してみてください。

やりたいこと

フォルダ構成

スクリーンショット 2019-08-15 11.03.56.png

こんな感じで、web以外はcdk initで自動的に作成される構成。
webディレクトリにデプロイしたいリソースを入れて置いて、cdk buildで一気にデプロイしたい。

ちなみに今回はvue-cliを使ってtypescript + vueをcreateした際の初期のサンプルを入れています。

詳しく知りたい方はこちらなど参考にされるといいんじゃないでしょうか。

やってみた

aws cdkプロジェクトの作成

$ mkdir s3-deployment-inspection
$ cd s3-deployment-inspection
$ cdk init --language typescript

うまく行けば上記フォルダ構成の"web"以外ができるはず。
なお、今回はフォルダ名を"s3-deployment-inspection"としているため、作成されるテンプレートは

lib/s3-deployment-inspection-stack.ts
import cdk = require('@aws-cdk/core');

export class S3DeploymentInspectionStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // The code that defines your stack goes here
  }
}

と行った命名になっている。設定より規約。素晴らしい。

vueプロジェクトの作成

$ vue create web
# 色々質問されるので答える。今回はtypescriptのプロジェクトを選びました。

vueプロジェクトのビルド

今回はvue createした際に出来るサンプルをそのままビルドしました。

$ cd web
$ npm run build #./distにindex.htmlを含むリソースがデプロイされる

(/tsconfig.jsonの修正)

さて、Vue側のビルドが終わったのでプロジェクトルートに戻って、

$ cd ../
$ pwd
 # ${your root}/s3-deployment-inspection

まだ何も書いてないけれどcdkの方もビルドしてみようかとしたところ、

$ npm run build #実質$ tsc 叩いてるのと一緒

ビルドエラーが発生。
よくよく考えると、webの中にも*.tsがあって、別のnode_modulesに依存しているにも関わらずそれも一緒にビルドしようとするのでエラーが発生してしまう。

ということで、ルート側のtsconfig.jsonを修正。excludeに"web/*"を追加。

tsconfig.json
{
  "compilerOptions": {
    "target":"ES2018",
    "module": "commonjs",
    "lib": ["es2016", "es2017.object", "es2017.string"],
    "declaration": true,
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": false,
    "inlineSourceMap": true,
    "inlineSources": true,
    "experimentalDecorators": true,
    "strictPropertyInitialization":false
  },
  "exclude": ["cdk.out","web/*"]
}

必要なライブラリをnpm install

cdk initしただけでは@aws-cdk/coreしかインストールされていません。
例えば、スタックの中でs3バケットを作りたいのであれば@aws-cdk/aws-s3などそれぞれのリソースに応じたライブラリが必要です。

$ pwd
 # ${your root}/s3-deployment-inspection
 # 間違えてweb下でやらないように!!

$ npm install --save @aws-cdk/aws-s3 @aws-cdk/aws-s3-deployment @aws-cdk/aws-cloudfront @aws-cdk/aws-iam

2019/08/15時点 最新版(1.4.0)の@aws-cdk/aws-s3-deploymentが動かない問題

みなさんが見られる頃には解消しているといいのですが。。。
こちらの通りうまく動かないみたいです。

解消しない場合は、前バージョン(@1.3.0)をnpm installしましょう。

$ npm install --save @aws-cdk/aws-s3-deployment@1.3.0

スタックの定義

ということでここから本番。CDKを使ってスタックを定義していく。

lib/s3-deployment-inspection-stack.ts
import cdk = require('@aws-cdk/core');

import * as s3 from '@aws-cdk/aws-s3';
import * as iam from '@aws-cdk/aws-iam';
import * as s3Deploy from '@aws-cdk/aws-s3-deployment';
import * as cf from '@aws-cdk/aws-cloudfront';

export class S3DeploymentInspectionStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Vueをデプロイする先のS3バケット
    const websiteBucket = new s3.Bucket(this, `WebsiteBucket-${this.stackName}`);

    // 今回のメイン
    // ./web/distに先ほどビルドしたビルド結果をwebsiteBucketにデプロイする
    // たった4行で済むなんて。。。
    new s3Deploy.BucketDeployment(this, 'DeployWebsite',{
      source: s3Deploy.Source.asset('./web/dist'),
      destinationBucket: websiteBucket
    });

    // CloudFrontからwebsiteBucketにアクセスする際のOriginAccessIdentity
    const OAI = new cf.CfnCloudFrontOriginAccessIdentity(this, `identity-${this.stackName}`,{
      cloudFrontOriginAccessIdentityConfig:{
        comment: `WebsiteBucket-${this.stackName}`
      }
    });

    // webSiteBucketのBucketPolicyのStatement
    // 先ほど作ったOAIにs3:GetObjectを許可する
    // websiteBucketはpublic access出来ない設定(デフォルト)になっているので
    // こうしておかないとCloudFrontからアクセス出来ない
    const webSiteBucketPolicyStatement = new iam.PolicyStatement({effect: iam.Effect.ALLOW});
    webSiteBucketPolicyStatement.addCanonicalUserPrincipal(OAI.attrS3CanonicalUserId);
    webSiteBucketPolicyStatement.addActions("s3:GetObject");
    webSiteBucketPolicyStatement.addResources(`${websiteBucket.bucketArn}/*`);
    websiteBucket.addToResourcePolicy(webSiteBucketPolicyStatement);

    // CloudFrontのdistribution
    // 先ほど作ったOAIを指定しておくのがポイント
    const distribution = new cf.CloudFrontWebDistribution(this, `Distribution-${this.stackName}`, {
      originConfigs:[
        {
          s3OriginSource: {
            s3BucketSource: websiteBucket,
            originAccessIdentityId: OAI.ref
          },
          behaviors: [{ isDefaultBehavior: true}]
        }
      ]
    });

    // CloudFrontのドメインを調べるのにいちいちAWS Consoleに入りたく無いので
    // URLに整形して出力しておく
    new cdk.CfnOutput(this, 'CFTopURL', {value: `https://${distribution.domainName}/`})
  }
}

ビルド & デプロイ

$ npm run build
 #tsに構文エラー等がなければ何も出力されないはず
$ cdk build
 # CloudFrontのdistribution作ってるだけあって、やや待たされるのはやむなしか
 # なんやかんやあって最後は以下が出力されるはず

 ✅  S3DeploymentInspectionStack

Outputs:
S3DeploymentInspectionStack.CFTopURL = https://xxxxxxxxx.cloudfront.net/

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxx:stack/S3DeploymentInspectionStack/15c9fc40-bef3-11e9-a934-06ce06e5203e

確認

ということで、Outputsに出力されているURLをブラウザで叩いてみると...

スクリーンショット 2019-08-16 6.42.28.png

ちゃんとVueのサンプルページが表示される。

追加確認 webだけ編集しても変更検知してくれるだろうか?

スタックの定義を変更したら変更検知して差分をデプロイしてくれそうだが、webのソースだけ変更したら検知して再デプロイしてくれるだろうか?

ということで実験。

Vueのサンプルソースを編集

web/src/App.vue
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <!--CHANGE: msgの末尾に!!を追加-->
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App!!"/>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from './components/HelloWorld.vue';

@Component({
  components: {
    HelloWorld,
  },
})
export default class App extends Vue {}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

webを再ビルドしてcdk deploy

$ cd web
$ npm run build
$ cd ../
$ cdk deploy

S3DeploymentInspectionStack: deploying...
S3DeploymentInspectionStack: creating CloudFormation changeset...
 0/2 | 6:51:44 | UPDATE_IN_PROGRESS   | Custom::CDKBucketDeployment                     | DeployWebsite/CustomResource/Default (DeployWebsiteCustomResourceD116527B) 
 1/2 | 6:52:08 | UPDATE_COMPLETE      | Custom::CDKBucketDeployment                     | DeployWebsite/CustomResource/Default (DeployWebsiteCustomResourceD116527B) 

 ✅  S3DeploymentInspectionStack

Outputs:
S3DeploymentInspectionStack.CFTopURL = https://xxxxxxxxxxx.cloudfront.net/

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxx:stack/S3DeploymentInspectionStack/75086bf0-bf17-11e9-a934-06ce06e5203e

お、変更検知されたっぽい。

確認

ブラウザをリロードしてみたが、変更されておらず。。。そうか、よく考えたらCloudFrontはデフォルトでキャッシュが効くな。。。TTLを0にしておくべきだった。

というわけでinvalidate。

$ aws cloudfront list-distributions
  # 長いので出力は貼らないがdistributionの一覧がjsonで出てくるので先ほど作ったdistributionを探してIDを取得する

$ aws cloudfront create-invalidation --distribution-id ${YOUR DISTRIBUTION ID} --paths '/*'

再確認

スクリーンショット 2019-08-16 7.04.55.png

ちゃんとTypeScript App!!になっている。

追加確認 具体的にどうやってwebのソースをデプロイしてるんだろう?

記事の冒頭に書いた通り、自分でやるときはCustom Resourceを作っていたが果たして...

ということで、CloudFormationのコンソールに入って、作成されたスタックをデザイナで見てみる。

スクリーンショット 2019-08-16 7.13.15.png

やはりCustom Resource作ってるか。そうだよな、それしか無いよな。

でも自分で作らなければいけないより一億倍楽!!

後片付け

cdk destroyでスタックごと消してくれる。

$ cdk destroy
Are you sure you want to delete: S3DeploymentInspectionStack (y/n)? y

所感

AWS CDKの魅力はカプセル化だと思う

一見、CloudFormationをTypeScriptやPythonで書けるだけのサービスに見えるけどさもあらず。今回検証したようなCustom Resourceを使ってS3にファイル群を転送するような動きはネイティブなCloudFormationでも出来るが、そこを内部実装を隠匿してs3-deploymentというサービスに仕立て上げられているのが素晴らしい

私は興味があったので中身をみてCustom Resourceがあることを知りましたが、別にCustom Resourceという存在すら知らなくてもこの動きを実現出来るわけなので。

今回検証した以外にもそうやって上手くカプセル化されているAPIが色々ありそうなので、興味ある方はリファレンス読んでみてください。

OAI周りが不満...まだまだ発展途上

せっかくファイルのデプロイまでカプセル化してるのに、CloudFrontのOriginAccessIdentityを作ったりBucketPolicyと紐付けたりといった工程はカプセル化出来んもんかね。。。

と思ったら、やっぱりIssueに上がってた。

aws-cloudfront: easily support Origin Access Identity for S3 buckets

これからどんどん進化していくフレームワークだってことですね。楽しみ。

相当強力だが、学習コストはまだまた高そう

そもそもネイティブなCloudFormationの書き方を知らないと、世界観が把握しづらくてリファレンスとか読み解くのに苦労しそうな気がしてるのですがどうなんでしょう??私は個人的にCloudFormation
地獄に片足を突っ込んでいたのでリファレンスもCDKのメリットも割とすんなり入ってきましたが。。。

もっとカプセル化が進めば、ネイティブなCfnを知らない人でもスッとInfrastructure As Codeが出来るようなフレームワークになるかも知れませんね。

このフレームワークのおかげで、Infrastructure as Codeに対するハードルが少しでも下がることを願っています。

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

AWS ソリューションアーキテクト合格までの対策 (2019年5月時点の情報)

はじめに

AWS 認定ソリューションアーキテクト – アソシエイトに合格したので、やってきたことをまとめました。
なるべく時間を掛けずに合格したかったので、仕事しながら資格取得を目指している方にはオススメかもしれません。

筆者スペック

試験結果

  • [合格] 739点 / 1000点

対策(その1)

試験に向けての対策を書き残しておきます。

模擬試験(その1)

  • ダメ過ぎ・・。
総合スコア:  56%
トピックレベルスコア:
1.0  Design Resilient Architectures: 66%
2.0  Define Performant Architectures: 57%
3.0  Specify Secure Applications and Architectures: 33%
4.0  Design Cost-Optimized Architectures: 50%
5.0  Define Operationally-Excellent Architectures: 100%

対策(その2)

模擬試験(その2)

  • 今度はマシな結果になった!これなら本番はイケる!
総合スコア:  88%
トピックレベルスコア:
1.0  Design Resilient Architectures: 100%
2.0  Define Performant Architectures: 71%
3.0  Specify Secure Applications and Architectures: 83%
4.0  Design Cost-Optimized Architectures: 100%
5.0  Define Operationally-Excellent Architectures: 100%

本試験

  • ピアソンVueにて試験申し込み。
    Vue

  • で!模擬試験は簡単過ぎるから本試験は覚悟しろ!って他サイトにも書かれていましたが、模擬試験と同等の内容でした。

  • アンケートが終わると、すぐその場で合否が出ます。

まとめ

  • まとめると、難易度は 上記の黒色の書籍 < 模擬試験 ≒ 本試験 <<< 上記のオレンジ色の書籍
  • なので、上記、オレンジ色の書籍で対策しておけば、問題なし!!!!
  • と、思いたいのですが、黒色の本は概要を抑える為には悪くない。オレンジ色で詳細まで把握。
  • なので、二冊を我慢してやり切るのが合格への確かな道のりかと思います!
  • 書籍
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS 認定ソリューションアーキテクト – アソシエイト(SAA-C01)に合格しました

SAA-C01に合格出来たので、感想や使用教材をレポートとして纏めようと思います。

私のスペック

関連しそうな資格だと、応用情報技術者情報セキュリティスペシャリスト 1CCNAに合格しています。
英語は苦手です。(英語ドキュメントは時間をかけて翻訳を駆使すれば読める程度)
1年程度AWSを用いたインフラ構築,運用保守業務に携わっています。

試験対策教材

模擬試験について

CertMetricsよりPSIのAWS Certified Solutions Architect - Associate - Practice(SAA-P01)を選択し、受験料を支払うとすぐに実施できました。

制限時間30分の設問数は30問です。
完了するか制限時間に達すると問題が終了し、戻れなくなります。模擬試験終了後に問題内容や自身の回答を見直す事はできません。
問題内容は本試験より簡単でしたが、形式が本試験と同じなので慣れておく為にも実施しておいた方が良いと思いました。
試験官とのチャットはありませんが、フラグ機能や言語切り替えが可能です。

結果

受験完了後すぐに模擬試験の総合スコアとトピックレベルスコアがメールで届きました。私のスコアは以下のような感じです。

セクション スコア
1.0 Design Resilient Architectures 66%
2.0 Define Performant Architectures 28%
3.0 Specify Secure Applications and Architectures 66%
4.0 Design Cost-Optimized Architectures 100%
5.0 Define Operationally-Excellent Architectures 100%
総合スコア 60%

パフォーマンスに優れたアーキテクチャを定義する分野が弱いようでした・・・
業務で取り扱っていないAuto Scalingに関する設問に全く歯が立たず悔しい思いを味わう事が出来ました。2
合格ラインに届いてはいませんでしたが、引きずっているといつまで経っても受験出来ないと思ったので、思い切って約2週間後に本試験を予約し、受験料も支払いました。3

PSI試験について

あまり試験の内容と深くは関係が無いですがAWSの PSI 試験が初めてだったので少し触れようと思います。
試験は PSI と ピアソンVUE から選択出来ますが、どちらも基本は同じなようです。4
CCNAを監督付きの ピアソンVUE 公認テストセンターで受験した事はあります。

試験時間は平日の10:00 AM もしくは 2:00 PMから選択できました。朝弱いので昼から受験可能なのは助かります...
試験は2:00~4:10迄の制限時間130分で設問数は65問でした。
会場へは持ち物として以下を持参しました。

  • 試験の確認番号 5
  • 運転免許証
  • クレジットカード(VISA)6

試験開始の15分前から入室可能で、その後は自身のタイミングで試験を開始できます。
入室時には荷物を入れるロッカーの鍵、上記身分証明書2つ以外の持ち込みは禁止で、紙とボールペンは受付に言えば貰えます。(紙とペンは試験終了後即回収)
初めにメールアドレスと名前での認証とカメラで顔の撮影、スキャナでの運転免許証(表のみ)の撮影、クレカをカメラで裏表見せてPASSしました。
130分の間にトイレに行きたくなったらどうしようと思っていましたが、チャット上でLive monitorさん(試験官)に申告すれば20分以内の退室は認められるそうです。
最初は暑かったので上着を脱いで試験を開始したのですが、長い間冷房の効いた室内でじっとしていたので寒くなってきて上着を着ようとしたらLive monitorさん(試験官)に「両手は机の上に!!」と怒られてしまいました。ちゃんと監視されてるみたいです。
マウスしか使わないのに130分程度両手を机の上に置いておくのは伸びたり体勢変えたりが難しいので地味に辛かったです。
試験終了後、即時合否が出ます。嬉しくなって小さくガッツポーズを取ったら、暫く沈黙していたLive monitorさんが「おめでとうございます!」と祝ってくれました。(ちょっと嬉しかったです。)

試験結果

受験者スコア: 804
※合格に必要な最低スコア: 720
結果詳細のスコアと評価では、全セクションで「十分な知識を有する」判定でした。
先達の受験者達はセクション毎の取得スコアが見れるようなのですが、人によって記載されていた合格ラインのパーセンテージが違っていたりするので方式が変わったのだと考えています。

感想

ひとまず1発合格出来たので安心できました。
AWSに認められた事で私の設計に自信が持てるようになりました。PJ内での発言にも箔が付いたような気がします。

以下のような業務で取り扱っておらず、勉強でも実際には触れていないサービスからの出題が多く、試験中は内心不安でいっぱいでした。

  • Auto Scaling
  • DynamoDB
  • Redshift

また、恐らくハズレ選択肢の一つですが、Snowballのような合格対策で範囲外のサービスを見かけました。
なので、ホワイトペーパーに一通り目を通して各サービスで凡そどのような事が出来るのか等の概要を知っておいて良かったと思いました。

当然ながらコンピューティング, ストレージ, データベース, VPCネットワークに於ける疎通やセキュリティ, IAMについてもしっかりと勉強しておいた方が良いと思います。


  1. 現:情報処理安全確保支援士試験 

  2. 模擬試験で良かった・・・ 

  3. 所謂セルフ追い込み 

  4. ピアソンVUE の方が試験会場不足対策で出た後発の形式? 

  5. 予約時に試験日, 試験時間, 試験会場等と一緒に印刷が可能 

  6. 海外でも有効なカードである必要があるそうです。 

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

EC2インスタンス停止による料金の発生について

EC2インスタンスを停止中にしたら料金が発生していた

勉強不足で私は知りませんでした。
停止中のEC2インスタンスに関連づけられているElasticIPは、課金対象だということを。

標題の件、私の失敗談をご紹介します。

先日、クレジットカードの明細を見たときに
「Amazon web services ¥48」
という記載がありました。

おやおや?と思い、AWSの「請求」を確認すると

請求の内訳.
AWS Service Charges    $0.44

▼ Elastic Compute Cloud
 ▼ Asia Pacific (Tokyo)
  Elastic IP Addresses $0.41
  $0.005 per Elastic IP address not attached to a running instance per hour (prorated)82.600 Hrs $0.41
徴収される消費税(日本)    $0.03

どうやらElasticIPで料金が発生している様子です。
こちらについて公式サイトで調べてみます。
Elastic IP の料金を理解する - Amazon Web Services

次の条件が満たされている限り、Elastic IP アドレスに料金は発生しません。

  • Elastic IP アドレスが EC2 インスタンスに関連付けられている。
  • Elastic IP アドレスに関連付けられているインスタンスが実行中である。
  • インスタンスに 1 つの Elastic IP アドレスしか添付されていない。

これらの条件を満たしていない Elastic IP アドレスについては、1 時間単位で請求されます。

私は、7月の終わりにEC2インスタンスを停止中にしていました。
今回の料金発生は、2つ目の「ElasticIPアドレスに関連付けられているインスタンスが実行中である。」という条件を満たしていないことが原因でした。

これを回避するには、EC2インスタンスを実行中にする、もしくはElasticIPを解放する必要があります。

停止中にした経緯

「AWS Free Tier limit alert」
というタイトルのメールが届きました。

メール本文抜粋.
AWS Free Tier usage limit alerting via AWS Budgets  07/28/2019

Dear AWS Customer,

Your AWS account xxxxxxxxxxxx has exceeded 85% of the usage limit for one or more AWS Free Tier-eligible services for the month of July.

AWS Free Tier Usage as of 07/28/2019    AWS Free Tier Usage Limit
642 Hrs                                 750 hours of Amazon EC2 Linux t2.micro instance usage

「t2.microインスタンスは、月に750時間まで無料で使えるけど、あなたの利用時間は既に85%を超えているから念の為お知らせしとくね」
ということだと思われます。

31日 × 24時間 = 744時間
t2.microインスタンスを一つだけ使う分には、750時間は超えません。
ということは、そのままで大丈夫!

しかし、このメールを初めて見た当時の自分はこう思いました。
「アラートが来てドキッとするくらいなら、停止中にしておいて必要なときにまた実行中にすればいいや」と・・・。
(EC2インスタンスを複数使うときだけElasticIPを解放すれば良いのだと思ってた)

終わりに

今回は、しなくてもいいことをして料金発生という失敗でした・・・。
ただ、今回の失敗を経て、利用するサービスの仕組みはよく調べて理解しておくべきだと実感できました。

もし、料金について気になる方はAWSのマネジメントコンソールから「請求」と検索してご確認ください:point_up:

ご覧いただきましてありがとうございました。
内容に不備がございましたら、ご指摘いただけますと幸いです。

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