20191205のAWSに関する記事は30件です。

ParquetにしたOSMのデータをEMRでいじってみる

目的

昨日PBFからParquetに変換したOpen Street Mapのデータを、AWS EMRでspark-sqlを使って触ってみる。
全球データ=大きい → 分散処理したい → AWS EMR

今回は、こちらこちらを参考にして、データ処理していきます。

準備

S3にParquetのデータをアップロード

前回作ったParquetのデータ3ファイルをS3のバケットにアップロードします。僕は簡単にAWSコンソールからアップロードしましたが、aws-cliを使うなり方法は他にもありますね。

キーペアの用意

EMRクラスタ用のキーペアを作って、ダウンロードしておきます。使うOSやSSHクライアントに合わせて鍵ファイルを選んでください。

クラスタの立ち上げ

AWSコンソールからEMRクラスタを立ち上げます。
ログ記録はなしで、Sparkクラスタを指定します。クラウド破産も怖いので、インスタンス数も2個くらいにしておきましょう。インスタンスタイプももっと小さいインスタンスでいい気がしますが、デフォルトで指定されているm5.xlargeをそのまま使っておきます。
「クラスターを作成」すると、しばらくしてクラスターが準備完了になります。

セキュリティグループを設定してsshログイン

セキュリティグループがデフォルトで作成されますが、SSHが開いていないので、セキュリティグループを編集してmasterの方だけ外からのSSHを開けます。これで、先ほどのキーペアを使って手元からSSHログインできるようになります。

spark-shellを使って処理してみる

では、立ち上がったEMRクラスタを使って、Parquetに書き出したOSMデータを処理してみます。spark-shellを立ち上げます。

$ spark-shell

データファイルを読み込みます。

scala> import org.apache.spark.sql._

scala> import org.apache.spark.sql.types._

scala> val sqlContext = new SQLContext(sc)

scala> val df = sqlContext.read.parquet("s3://bucket-name/Tokyo.osm.pbf.node.parquet")

スキーマを確認します。

scala> df.printSchema()
root
 |-- id: long (nullable = true)
 |-- version: integer (nullable = true)
 |-- timestamp: long (nullable = true)
 |-- changeset: long (nullable = true)
 |-- uid: integer (nullable = true)
 |-- user_sid: binary (nullable = true)
 |-- tags: array (nullable = true)
 |    |-- element: struct (containsNull = true)
 |    |    |-- key: binary (nullable = true)
 |    |    |-- value: binary (nullable = true)
 |-- latitude: double (nullable = true)
 |-- longitude: double (nullable = true)

緯度と経度だけを抜き出してみます。

scala> df.registerTempTable("Node")

scala> val df3 = sqlContext.sql("SELECT id,latitude,longitude FROM Node")

scala> df3.show
+--------+------------------+------------------+
|      id|          latitude|         longitude|
+--------+------------------+------------------+
|31236558|        35.6351506|139.76784320000002|
|31236561|35.635041900000004|139.76785900000002|
|31236562|        35.6379133|       139.7591729|
|31236563|35.638182900000004|       139.7585998|
|31236564|35.638516100000004|139.75823490000002|
|31236565|35.638767200000004|       139.7580599|
|31236566|        35.6390562|       139.7579335|
|31236567|35.639158200000004|       139.7579045|
|31236568|        35.6393004|       139.7578741|
|31236569|        35.6378794|       139.7589461|
|31236570|35.637997500000004|       139.7586995|
|31236571|        35.6382569|       139.7583386|
|31236572|        35.6384817|139.75812480000002|
|31236573|35.638980700000005|       139.7578382|
|31236574|35.639336400000005|       139.7577578|
|31236575|        35.6395142|        139.757745|
|31236576|        35.6347773|       139.7699528|
|31236579|35.634913100000006|       139.7705506|
|31236580|        35.6350156|       139.7709312|
|31236581|35.635654800000005|       139.7727102|
+--------+------------------+------------------+
only showing top 20 rows

結果をS3に保存します。

scala> df3.rdd.saveAsTextFile("s3://bucket-name/Tokyo.osm.pbf.node.txt")

まとめ

なんかいけそう!

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

terraformでさくっとiamユーザーとグループを作ってみる

こんにちは。株式会社アダコテックのテックリードをしておりますkackyと申します。terraform Advent Calendar 2019の5日目の記事です。12月5日の22時から書き始めているのでかなりタイムアタックになっておりますw

やりたいこと

terraformでiamユーザを作って、それをグループに所属させる。そんなことをさくっとしたいときのやり方tipsです。

@raki さんのご指摘により記述を直しました!有用なご指摘ありがとうございます☆

ユーザー管理テーブルを変数定義する

こんな感じでユーザーとグループを対で登録します。

variable "users" {
  type = map(list(string))
  default = {
     "kacky" = ["developer"],
     "tech" = ["developer"],
     "adaco" = ["intern"]
  ]
}

iam group作成

グループとして、developer、internに分けるとします。

resource "aws_iam_group" "developer" {
  name = "developer"
  path = "/"
}

resource "aws_iam_group" "intern" {
  name = "intern"
  path = "/"
}

iamユーザーとmembershipの作成

ユーザー、グループはterraform 0.12.6で追加されたfor_each文を使うとあっさりできました。
これを使えばLocal Valuesでちまちまフィルタリングする必要がなくなりました。ナンテコッタイ/(^o^)\

resource "aws_iam_user" "users" {
  for_each   = var.users
  user = each.key
  path = "/"
}

resource "aws_iam_user_group_membership" "example" {
  for_each   = var.users
  user       = each.key
  groups     = each.value
  depends_on = [aws_iam_user.users]
}

まとめ

terraformの最新機能を使うとiamユーザーとグループといった一対の関係を直感的に変数定義してresourceに展開できるようになりました。他にもいろんな応用が利くと思いますのでお試しください。

※焦って書いているのでtypoしてたらすみません。今週中に直しますm(_ _)m

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

見せて貰おうか、Amazon新サービス「CodeGuru Reviewer」の性能とやらを

はじめに

これは、NTTコミュニケーションズ Advent Calendar 2019 5日目の記事です.関連記事は目次にまとめられています。前日は@TAR_O_RINさんによるTektonでCI/CDパイプラインを手の内化しよう - Qiitaでしたね!

あなたはだ〜れ?!

昨年書いた深層学習を利用した食事画像変換で飯テロ - Qiitaに既に書いてあるので省略します。好きな言語はObjective-Cです。お世話になった言語もObjective-Cです。嫌いな言語もObjective-Cです。最近はPythonしか書いてないのでマンネリ化が激しいです。最近のマイブームは夜な夜なサイクリングロードです。一緒にサイクリングしましょう。

モチベ

複数人で開発を行う上でコードレビューを行う文化は非常に重要ですよねー。ただ、

  • 開発者はコードレビューをする時間を確保する必要がある
  • 入念にチェックはしても一部の見逃しは発生する(人間だもの)
  • レビュー担当者の技術レベルによってレビュー濃度がまちまち

などなど、他人のコードを読むのは大変勉強になるし、もはやレビューに何も抵抗は無いですが、時間が無い時などは後回しにしがち。ぶっちゃけコードレビューって大変ですよね。自動でコードレビューしてくれたらなあ。。。なんてそんなあなたに、

Amazon CodeGuru - Amazon Web Services

スクリーンショット 2019-12-05 20.42.14.png

今北産業(三行まとめ)

  • 2019年12月3日(米国時間)に公開された機械学習ベースの自動コードレビュー新サービスAmazon CodeGuruCodeGuru Reviewerについての検証
  • Amazon CodeGuruの利用方法について導入方法の解説
  • Amazon CodeGuruにより自動生成されるレビューコメントへの言及

CodeGuruとは

CodeGuru自体の説明は既にいくつかのメディアが取上げているので概要についてはリンク先に委ねようと思います。要は機械学習ベースの自動コードレビュー(CodeGuru Reviewer)およびプロファイラー(CodeGuru Profiler)機能を提供するサービスです。CodeGuru ProfilerについてはAmazon CodeGuruを試してみた - Qiitaにて記載がありますが、CodeGuru Reviewerの方はあまり記載が無かったので前記のブログを補填する内容になっています。

実際のCodeGuruの基調講演はYouTubeに公式がアップロードしていますので以下のYouTube動画を御覧ください。(Qiitaはインライン表示できないので画像リンクとしています。01:58:40からAmazon CodeGuruの発表が始まります。)
見せられないよ!!

簡単な使用手順

ここでは、Amazon CodeGuruの利用方法について導入方法の解説を行います。

CodeGuru Reviewer利用の際の下準備編

自動レビュー機能を試す前にCodeGuruと自動レビュー対象とするレポジトリの紐付けを行います。Preview版ではGitHubおよびAWS CodeCommitをサポートしているようです。今回はGitHubによる検証を行いたいので、GitHubアカウント、GitHubレポジトリとの紐付けを行う想定で記載します。CodeCommitとの紐付けについては公式ガイドGetting Started with Amazon CodeGuru Reviewer - Amazon CodeGuru Reviewer User Guideを参照しましょう。

1. AWSマネジメントコンソールからCodeGuruを選択する

AWSにログイン後、 サービスを検索する の入力欄に CodeGuru と入力して、表示項目をクリック。
0_選択.png

2. リージョンの変更

2019/12/5時点では、サポートリージョンが図中の5リージョンのみなので、もし同じ画面に遷移した場合は、何れかのリージョンを選択。今回は米国東部(オハイオ)を選択します。
1_リージョン変更.png

3. Amazon CodeGuruからCodeGuru Reviewerの選択

Amazon CodeGuruのホーム画面です。今回はCodeGuru Reviewerによるコードの自動レビュー機能を試すので図中のいずれかをクリックしてレポジトリとの紐付けを行います。
スクリーンショット 2019-12-05 17.44.43.png

4. CodeGuru ReviewerとGitHubアカウントの紐付け

CodeGuru Reviewerによる自動レビューの対象とするレポジトリの紐付けを行います。この時、CodeGuru Reviewerによる自動レビューを行う専用のGitHubアカウントを作成することをオススメします。公式ガイドGetting Started with Amazon CodeGuru Reviewer - Amazon CodeGuru Reviewer User Guideにも記載されていますが、専用のGitHubアカウントを介してCodeGuru Reviewerによるレビューが行われるので、人手によるレビューなのか自動か否かの混乱を防げます。また、複数のレポジトリを自動レビュー対象にする場合は、レポジトリに新規に作成したCodeGuru Reviewer専用のGitHubアカウントをレポジトリにCollaboratorsとして招待すればOKです。※ GitHubアカウントとの紐付け後に別のGitHubアカウントに変更する場合はブラウザのCookieを削除する必要があります。
スクリーンショット 2019-12-05 18.05.48.png

5. CodeGuru Reviewerとレビュー対象レポジトリの紐付け

GitHub認証が正常に終了したらConnect to GitHubConnectedへと変わるはずです。これでCodeGuru ReviewerとGitHubとの紐付けが完了します。次に、Repository locationのリストから自動レビューの対象とするレポジトリを選択してAssociateボタンをクリックすれば設定は終了です。(めちゃ楽)※ CodeGuru Reviewer専用のGitHubアカウントを自動レビュー対象レポジトリへ事前に招待して承認しておけば、勿論プライベートレポジトリも全て表示されます。また、Organizationsについても同様です。
スクリーンショット 2019-12-05 18.30.30.png

6. CodeGuru Reviewer紐付け完了ステータスの確認

Associateボタンをクリック後は以下の画面に遷移します。紐付け直後のStatus部分はAssociatingと表示されますが、(レポジトリの規模によりますが)数分程度でAssociatedへと切り替わり下準備は完成です。(公式ガイドGetting Started with Amazon CodeGuru Reviewer - Amazon CodeGuru Reviewer User Guideでは数分程度と記載してありますが、空のレポジトリということもあり、今回は数秒でした。)※ ブラウザの更新か、画面中のAction左にある更新ボタンを押す必要があります。
スクリーンショット 2019-12-05 18.42.55.png

簡単な検証

ここでは、Amazon CodeGuruから実際に自動レビューコメントを受けるまでの検証を行います。※ ここからは実験みたいなものなので文章の雰囲気が変わるやもしれません。

CodeGuru Reviewerによる自動レビューの例その1:まじめなコード編

今回はむかしのむかしに書いたコードをぶっこんで見ることにしましょう。現状はJavaのみのサポートのようなのでそれ以外のコードおよび変更点は多分無視されます。CodeGuru Reviewerによる自動レビューのトリガーはPull Requestにより作動します。おもむろにPRを出してやりましょう。

スクリーンショット 2019-12-05 19.08.48.png

するとCodeGuruDashboardに変化が見られると思います。色々と実験をしていたのでLines of code analyzedが実際と乖離していますが、自分が出したPRと書いたコードの行数が簡易的に表示されます。※ Reviewer recommendationsはいくら待っても変わりませんでした。バグなのかな?

スクリーンショット 2019-12-05 19.15.36.png

(数分後…)適当にPR画面を見ていると何やらコメントが11件来てる。。。

スクリーンショット 2019-12-05 19.23.29.png

恐る恐る確認してみると…なんか色々書かれていて、公式ガイドRecommendations in Amazon CodeGuru Reviewer - Amazon CodeGuru Reviewer User Guideによると以下5項目の観点からレビューをしているっぽい?AWS best practicesConcurrencySensitive information leak preventionについては今回のPRには含まれていないはずだからそれっぽいレビューコメントはついていないみたいだ。

The following kinds of recommendations are provided:
- AWS best practices
- Concurrency
- Resource leak prevention
- Sensitive information leak prevention
- Common coding best practices

スクリーンショット 2019-12-05 19.27.28.png

11件のコメントの中でユニークコメントだったのが以下の3つで残りはほぼResource leak preventionに関する指摘で全文まったく同じコメントレビューが記載してあった。問題点と解決策、参考リンクなど簡潔なレビューだけどどうやって生成してるんだろう…

スクリーンショット 2019-12-05 19.41.09.png
スクリーンショット 2019-12-05 19.39.34.png
スクリーンショット 2019-12-05 19.39.27.png

CodeGuru Reviewerによる自動レビューの例その2:ウンコード編

皆さんはウンコード・マニアという神サイトはご存知だろうか?その名の通りウンコード撲滅を目論む崇高な神サイトである。読めば読むほど一度は…と心に刺さるものがあるので是非訪れてほしい。
スクリーンショット 2019-12-05 19.55.24.png

さて、今回はウンコードをCodeGuru Reviewerにレビューしてもらおう。そう君は今日からUnCodeGuru Reviewerなのだから。

代表的なウンコード

現状のCodeGuru ReviewerはJavaのみのサポートであるので、Java ウンコード-ウンコード・マニアから引用させてもらおう。有名所で申し訳ないが好きなのはやっぱり波動拳ネスト型ウンコードである。(※ 画像はIndent Hadouken : ProgrammerHumorから引用。redditで議論されてて楽しい。)

I1CShqp2wBguKcuv21m6XRnREKuw8kyAoCavCAGJPBI.jpg

もちろん、ウンコード・マニアも扱っているので、[Java] フルHD推奨。-ウンコード・マニアを引用する。

// [[Java] フルHD推奨。-ウンコード・マニア](http://unkode-mania.net/view/5040f47b9b6066a52e000003)を一部改変。

public class ClassSample {
    public static void main(String args[]) {
        int proc = 0;
        try {
            if ( proc == 0 ){
                // 正常処理
                try {
                    if ( proc == 1 ){
                        // 正常処理
                        try {
                            if ( proc == 2 ){
                                // 正常処理
                                try {
                                    if ( proc == 3 ){
                                        // 正常処理
                                        try {
                                            if ( proc == 4 ){
                                                // 正常処理
                                                if ( proc == 5 ){
                                                    // 正常処理
                                                    // まだまだ続くよ!
                                                } else {
                                                    // 異常処理
                                                }
                                            } else {
                                                // 異常処理
                                            }
                                        } catch(Exception e) {
                                            // 例外処理
                                        }
                                    } else {
                                        // 異常処理
                                    }
                                } catch(Exception e) {
                                    // 例外処理
                                }
                            } else {
                                // 異常処理
                            }
                        } catch(Exception e) {
                            // 例外処理
                        }
                    } else {
                        // 異常処理
                    }
                } catch(Exception e) {
                    // 例外処理
                }
            } else {
                // 異常処理
            }

        } catch(Exception e) {
        // 握りつぶす
        }
        System.out.println("Hello! World!");
    }
}

ウンコードのPRを同様に出す

さて先程と同様にPRを出して自動レビューのトリガーを引こう。…が待てよ待てとも一向にレビューが来ない…(ここでボロクソにレビュー来たら面白かったんですがね)前述の5項目の観点外だから音沙汰が無いのか、糞すぎるのか何れにしても何らかの反応はほしいですね。。

スクリーンショット 2019-12-05 20.21.37.png

まとめ

つい先日公開されましたAmazon CodeGuru - Amazon Web Servicesの導入方法と実際に生成される自動レビューについて簡単に検証してみました。【IT】「Amazon CodeGuru」発表。機械学習したコンピュータが自動でコードレビューは5chスレですが、たしかに自動でコードレビューしてくれても実装難易度爆高な方法を提示されたりなんてことがあったらそれはそれで面白そうです。(囲碁・将棋であったように)実装難易度順に候補複数提示してくれるとかだったら神ですね。

また、今回はまだ試していないマルチスレッディング、AWS API等に関するコードレビューは時間があるときにでも試してみたいです。後、コードレビューの正当性についても言及したい。特にマルチスレッディング系はどんなレビューが生成されるのか気になる。

なにわともあれCodeGuru自体はプレビュー版なのとJavaのみのサポートということもありまだまだ開発途上ではありますが、実際に自動で生成される様を見ると、何やら若干の未来感を感じ心躍る気分になりました。早急にPythonとSwiftに対応してくれ〜〜!使うから!笑

明日は@jkr4869さんの記事です!!お楽しみに〜〜〜!!!

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

AWS EC2になぜかS3のマウントができない(/xxx/xxx にアクセスできません)

はじめに

  • これは自分が実務で大いにハマった事象です。。。
  • AWSを商用利用したのは初めてということもあり、初歩的なミスかもしれませんが、もしかしたら他にも苦しんでいる方がいるかもしれないので、ノウハウをシェアします。

事象

  • 東京リージョンに作成したEC2を作成。
  • データ領域として、S3をマウントしようと試みた。
  • s3fsコマンドを導入し、マウントを行ったところ、一見うまく実行できたように見える。
    • echo $?による戻り値は0
  • ところがlsコマンドdfコマンドで状況を確認しようとすると、以下のエラーが出力される。
[ec2-user@ip-xxx-xxx-xxx-xxx ~]$ ls -al /xxx/xxx
ls: reading directory /xxx/xxx: 入力/出力エラーです
[ec2-user@ip-xxx-xxx-xxx-xxx ~]$ df -k
df: /xxx/xxx にアクセスできません: Transport endpoint is not connected
・・・中略・・・
[ec2-user@ip-xxx-xxx-xxx-xxx ~]$ sudo tail -3 /var/log/messages
Jan 15 17:54:02 ip-xxx-xxx-xxx-xxx dhclient[1355]: bound to xxx-xxx-xxx-xxx -- renewal in 1693 seconds.
Jan 15 17:59:31 ip-xxx-xxx-xxx-xxx s3fs: init $Rev: 367 $
Jan 15 17:59:35 ip-xxx-xxx-xxx-xxx s3fs: init $Rev: 367 $
[ec2-user@ip-xxx-xxx-xxx-xxx ~]$
  • df: /xxx/xxx にアクセスできません: Transport endpoint is not connectedというメッセージでWeb検索しても、EC2にS3の許可をしているか?やバケット名は合っているか?という対処法が出てくる。
  • 上記を再作成したり、何度も確認を行ったが、設定には問題がなさそう。

原因

  • EC2の時間が所属するリージョンの時間と異なった、もしくは大幅に時間がずれていたことで本事象が発生。
    • s3fsのデバッグ出力をする中でようやく発見しました

対処方法

  • EC2のタイムゾーンをJapanにすることで解決。
[root@ip-xxx-xxx-xxx-xxx ~]# cat /etc/sysconfig/clock
ZONE="UTC"
UTC=true
[root@ip-xxx-xxx-xxx-xxx ~]# view /etc/sysconfig/clock
[root@ip-xxx-xxx-xxx-xxx ~]# cat /etc/sysconfig/clock
ZONE="Japan"
UTC=true
[root@ip-xxx-xxx-xxx-xxx ~]# ln -sf /usr/share/zoneinfo/Japan /etc/localtime
[root@ip-xxx-xxx-xxx-xxx ~]# reboot
  • reboot終了後、s3fsコマンドでマウントを改めて実行したところ、問題なくS3がマウントされることを確認。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS で ARMのインスタンスを使ってみた。

AWSでARMを使てみる

はじめに

偶々AdventCalenderをを見ていたら12/5の分が余っていたためエントリして急いで作りました。
会議中ですが、爆速で執筆します。

2018年11月にAWSでARMが使えるインスタンスの発表がありました。
https://aws.amazon.com/jp/blogs/news/new-ec2-instances-a1-powered-by-arm-based-aws-graviton-processors/

こういう機能のようです。
https://aws.amazon.com/jp/ec2/instance-types/a1/

ARMって耳にはしますが使ったことがないなー。ということでとりあえず使ってみます。

配備をしてみる

通常のEC2のAMIは使えないようです。

image.png

↓をみると、Arm
image.png

触ってみる

インスタンスを払い出すと通常のEC2インスタンスと同様に操作できるようです。
とりあえずログインしてみます。

Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-1048-aws aarch64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Thu Dec  5 08:18:24 UTC 2019

  System load:  0.09               Processes:           124
  Usage of /:   0.2% of 484.35GB   Users logged in:     0
  Memory usage: 4%                 IP address for ens5: 10.10.242.100
  Swap usage:   0%

0 packages can be updated.
0 updates are security updates.



The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

適当にpythonを動かしてみましたが動きました。

ubuntu@ip-10-10-242-100:~$ python3
Python 3.6.8 (default, Aug 20 2019, 17:12:48) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print("watashi ha maloney")
watashi ha maloney
>>> quit()

docker入れてーと進めたいところですが、dockerのインストール手順などは色々投稿されていたようなので、うーんいったい何をすればいいだろう。とりあえずjupyter とよくあるやつをいきなり入れてみます。

$ sudo apt update
$ sudo apt install python-pip
$ sudo apt install jupyter
$ sudo apt install jupyterlab
$ sudo apt install python3-dev
$ sudo apt install python3-pip

起動します。

$ jupyter lab --ip=0.0.0.0 --port 8081 --allow-root
http://xxxxxxxxxxx:8081/?token=879077bd96XXXXXXXXXXXXXXXXXXXXXXXXXX32

普通にjupyterlab が使えました。特に問題なく動きますね。 すごーい
image.png

そして挫折

あとは、matplotを入れてみようとしてみましたが・・・。

$ sudo pip3 install matplotlib --no-cache-dir 
Collecting matplotlib
  Downloading https://files.pythonhosted.org/packages/75/81/53ccadcb8cad0a9837f1487b57f2b46b21caa2b3f35f72bc1acb06b5825c/matplotlib-3.1.2.tar.gz (40.9MB)
    100% |████████████████████████████████| 40.9MB 39.3MB/s  

なぜかここで固まってしまいました。
ここに限らず jupyter のインストールの際も、パッケージのインストール時に謎の硬直が度々発生しました。
原因は分かりませんが、これはちょっと気になりますね。
かなり放置してみたところ、無事再開・・・と思ったらインストールに失敗してしまいました。

    error: command 'aarch64-linux-gnu-gcc' failed with exit status 1

いろいろ適当に入れてみます。

$ sudo apt-get install libpng-dev
$ sudo apt-get install libfreetype6-dev

やっつけでしたが無事matplotが入りました。
あとはpython2系で動いてしまっているので、3系に切り替えればmatplotがjupyter上で動作しそうです。

時間を見つけて、機械学習をARM上で動かしてみたいですね。
今日はこれまで。

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

【初心者向け】ゼロからできる!PythonでAWS SAMによるAPI作成とOpenAPIドキュメントの出力

はじめに

以前、Swaggerツールを使ったOpenAPIドキュメント作成とAPI自動生成の流れを紹介しました。
【超初心者向け】5分で試せる!OpenAPI(Swagger3.0)ドキュメント作成〜API自動生成

今回は、AWS SAM(Serverless Application Model)を使ってAPI Gateway + AWS LambdaのサーバレスなwebAPIを作成し、OpenAPIドキュメントを出力してみます。
AWS SAMを使うのは初めてです。ゼロからできるように書いてみます。

AWS SAMとは

AWS サーバーレスアプリケーションモデル (SAM、Serverless Application Model) は、サーバーレスアプリケーション構築用のオープンソースフレームワークです。迅速に記述可能な構文で関数、API、データベース、イベントソースマッピングを表現できます。リソースごとにわずか数行で、任意のアプリケーションを定義して YAML を使用してモデリングできます。デプロイ中、SAM が SAM 構文を AWS CloudFormation 構文に変換および拡張することで、サーバーレスアプリケーションの構築を高速化することができます。

SAM ベースのアプリケーションの構築を開始するには、AWS SAM CLI を使用します。SAM CLI により Lambda に似た実行環境が提供され、SAM テンプレートで定義されたアプリケーションの構築、テスト、デバッグをローカルで実行できます。お使いのアプリケーションを SAM CLI を使用して AWS にデプロイすることもできます。

https://aws.amazon.com/jp/serverless/sam/

CloudFormationをサーバーレスアプリケーションで利用しやすくしたフレームワークのようです。
API Gateway , AWS Lambda , DynamoDB をひとまとめに管理できます。

前提

OS:Mac
AWSアカウント作成済み

やること

  1. 環境構築(pyenv, pipenv)
  2. AWS CLIを使えるようにする
  3. AWS SAM CLIを使えるようにする
  4. AWS SAMでHello Worldしてみる
  5. OpenAPIドキュメントを出力する

環境構築(pyenv, pipenv)

1. pyenvのインストール

$ brew install pyenv

brewコマンドが使えない場合は、以下からHomebrewをインストール
https://brew.sh/index_ja.html

2. pyenvのバージョン確認

$ pyenv --version
pyenv 1.2.15

3. pyenvでpython3.7.0をインストール

$ pyenv install 3.7.0

4. pyenvでインストールしたバージョンに切り替え

localの場合は今いるディレクトリに、globalの場合は全体に反映されます。

$ pyenv global 3.7.0
$ pyenv local 3.7.0

5. 反映されているか確認

$ pyenv versions
  system
* 3.7.0 (set by /Users/yusaku/.pyenv/version)

6. pipenvのインストール

$ brew install pipenv

7. pipenvの仮想環境に入る

$ pipenv shell

AWS CLIを使えるようにする

1. aws cliのインストール

$ pipenv install awscli

2. aws cliのバージョン確認

$ aws --version
aws-cli/1.16.294 Python/3.7.5 Darwin/19.0.0 botocore/1.13.30

3. アクセスキーIDとシークレットアクセスキーの確認

https://console.aws.amazon.com/iam/home?#/security_credentials
アクセスキー(アクセスキーIDとシークレットアクセスキー)新しいアクセスキーの作成からアクセスキーを作成し、アクセスキーIDとシークレットアクセスキーを確認します。

4. awscliの設定

3で確認した情報で設定します。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-welcome.html

$ aws configure
AWS Access Key ID [None]: XXXXXXXXXXXXXXXXXXXX
AWS Secret Access Key [None]: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Default region name [None]: ap-northeast-1
Default output format [None]: json

AWS SAM CLIを使えるようにする

1. AWS SAM CLIのインストール

$ pipenv install aws-sam-cli

2. AWS SAM CLIのバージョン確認

sam --version
SAM CLI, version 0.34.0

AWS SAMでHello Worldしてみる

今回はpipenv環境にインストールしたpython3.7を使います。

1. アプリケーションの初期化

$ sam init

    SAM CLI now collects telemetry to better understand customer needs.

    You can OPT OUT and disable telemetry collection by setting the
    environment variable SAM_CLI_TELEMETRY=0 in your shell.
    Thanks for your help!

    Learn More: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-telemetry.html

Which template source would you like to use?
    1 - AWS Quick Start Templates
    2 - Custom Template Location
Choice: 1

Which runtime would you like to use?
    1 - nodejs12.x
    2 - python3.8
    3 - ruby2.5
    4 - go1.x
    5 - java11
    6 - dotnetcore2.1
    7 - nodejs10.x
    8 - nodejs8.10
    9 - nodejs6.10
    10 - python3.7
    11 - python3.6
    12 - python2.7
    13 - java8
    14 - dotnetcore2.0
    15 - dotnetcore1.0
Runtime: 10

Project name [sam-app]: AWSsam_test    

Allow SAM CLI to download AWS-provided quick start templates from Github [Y/n]: y

-----------------------
Generating application:
-----------------------
Name: AWSsam_test
Runtime: python3.7
Dependency Manager: pip
Application Template: hello-world
Output Directory: .

Next steps can be found in the README file at ./AWSsam_test/README.md

2. アプリケーションをビルドする

$ cd AWSsam_test/
$ sam build

3. アプリケーションをデプロイする

$ sam deploy --guided

2回目以降はsam deployでOKです。

4. 動作確認

3で出力されるエンドポイントにアクセスしてみます。

$ curl https://hoge.hoge-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message": "hello world"}

hello worldが返ってきました!
AWSコンソールにログインしてLambdaを見てみると、以下のようにAPI GatewayとLambdaが起動していました。
スクリーンショット 2019-12-05 15.48.19.png

5.Lambda関数を少し変えて実行してみる

hello_world/app.pyを以下のように変更してみます。

hello_world/app.py
import json

# import requests


def lambda_handler(event, context):
    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello Qiita",
            # "location": ip.text.replace("\n", "")
        }),
    }

ビルドしてデプロイします。

$ sam build
$ sam deploy

エンドポイントにアクセスしてみます。

$ curl https://hoge.hoge-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message": "hello Qiita"}

変更が反映されていることが確認できました。

OpenAPIドキュメントを出力する

AWSコンソールからAmazon API Gatewayを選択し、デプロイしたAPIを選びます。
以下の画面が表示されるので、ステージをクリックしてエクスポートを選ぶと、Swagger仕様やOpenAPI仕様のドキュメントをjson形式やyaml形式で出力できます。
スクリーンショット 2019-12-06 9.24.16.png

おわりに

今回はAWS SAMでREST APIを作成し、OpenAPI仕様ドキュメントをエクスポートしました。
逆にOpenAPI仕様をインポートしてAWS SAMを使ったAPI作成も実施してみたいです。
以下の記事で紹介されていますが、そんなに楽ではなさそう。。
https://dev.classmethod.jp/cloud/aws/serverless-swagger-apigateway/

参考

Tutorial: Deploying a Hello World Application
https://dev.classmethod.jp/cloud/aws/aws-sam-simplifies-deployment/

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

Amazon EKSとTerraformを組み合わせたとある事例

はじめに

これは、Amazon EKS Advent Calendar 2019 6日目の記事です。
現在関わっているプロジェクトにおいて進行中のKubernetes移行で取り入れた技術や方法について、少し紹介したいと思います。
現状の構成がベストプラクティスであるということではなく、試行錯誤した結果の途中経過となりますが、何らかのヒントになれば幸いです。

前提

  • Amazon EKS 1.14
  • ワーカーノードはEC2(Fargateではない)
  • IaCはTerraform 0.12系を使う

Terraformでどこまで適用するか

はじめ、EKSクラスタについてはAWS CLIでの構築を考えていました。その理由としては、

  • Terraformの適用環境はCodeBuildである(IAMロールを使う)
  • kubectl実行環境は別にある(EC2+SessionManager+SSHの踏み台的な環境)

という前提で、kubectlの実行環境でクラスタを作成したほうがaws-authのConfigMapの適用で考えることが少なくて済む、という考えがありました。

しかし、現在はTerraformで管理する対象を増やしています。大きな理由としては、

  • アプリケーションをデプロイする手前までの環境は自動化したい

というところです。

Terraformで適用するリソース

AWS Providerを使う

  • EKSクラスタ
  • ワーカーノード
    • Auto Scaling Group
    • Security Group
    • IAM Role

Kubernetes Providerを使う

https://www.terraform.io/docs/providers/kubernetes/index.html

  • ConfigMap: aws-auth
  • Helm
    • Service Account
    • Cluster Role Binding
  • External DNS
    • Service Account
    • Cluster Role
    • Cluster Role Binding
    • Deployment

Helm Providerを使う

https://www.terraform.io/docs/providers/helm/index.html

(できたらContainer Insights on Amazon EKSもHelmで導入したい...!)

これらを一通りTerraformで、 AWS modulesを活用しつつ構築することで、アプリケーション側のデプロイに専念できる形にする、というのが現時点での答えとなっています。

Kustomizeの利用

アプリケーションのマニフェストをdev/staging/productionといった環境ごとに重複しない形で管理できるようにKustomizeを利用しています。

デプロイ

デプロイはArgoCDを選定しています。

Argo CD - Declarative GitOps CD for Kubernetes
https://argoproj.github.io/argo-cd/

これがベストかどうかはまだわかりませんが、利用事例の多さ、開発の頻度、Helmで一発で導入できること、Kustomizeへの対応、見やすいWebUIなどが魅力的だと感じています。

まだ確定はしていませんが、ECRへのイメージプッシュからLambdaを起動して、argocdのCLIからプッシュされたイメージのタグを指定してデプロイする方法(GitOps)が良さそうだと考えています。

Parameter Overrides
https://argoproj.github.io/argo-cd/user-guide/parameters/

おわりに

とても簡単な解説にはなりますが、以上の組み合わせでKubernetesまわりの環境を構築しています(しようとしています)。
ここはどうなっているの?という質問や疑問がありましたら、Twitter @isaoshimizu まで優しくお気軽にメンションしていただけたら、こちらとしても大変ありがたい限りです。

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

Amazon EKSとTerraformの組み合わせたとある事例

はじめに

これは、Amazon EKS Advent Calendar 2019 6日目の記事です。
現在関わっているプロジェクトにおいて進行中のKubernetes移行で取り入れた技術や方法について、少し紹介したいと思います。
現状の構成がベストプラクティスであるということではなく、試行錯誤した結果の途中経過となりますが、何らかのヒントになれば幸いです。

前提

  • Amazon EKS 1.14
  • ワーカーノードはEC2(Fargateではない)
  • IaCはTerraform 0.12系を使う

Terraformでどこまで適用するか

はじめ、EKSクラスタについてはAWS CLIでの構築を考えていました。その理由としては、

  • Terraformの適用環境はCodeBuildである(IAMロールを使う)
  • kubectl実行環境は別にある(EC2+SessionManager+SSHの踏み台的な環境)

という前提で、kubectlの実行環境でクラスタを作成したほうがaws-authのConfigMapの適用で考えることが少なくて済む、という考えがありました。

しかし、現在はTerraformで管理する対象を増やしています。大きな理由としては、

  • アプリケーションをデプロイする手前までの環境は自動化したい

というところです。

Terraformで適用するリソース

AWS Providerを使う

  • EKSクラスタ
  • ワーカーノード
    • Auto Scaling Group
    • Security Group
    • IAM Role

Kubernetes Providerを使う

https://www.terraform.io/docs/providers/kubernetes/index.html

  • ConfigMap: aws-auth
  • Helm
    • Service Account
    • Cluster Role Binding
  • External DNS
    • Service Account
    • Cluster Role
    • Cluster Role Binding
    • Deployment

Helm Providerを使う

https://www.terraform.io/docs/providers/helm/index.html

(できたらContainer Insights on Amazon EKSもHelmで導入したい...!)

これらを一通りTerraformで、 AWS modulesを活用しつつ構築することで、アプリケーション側のデプロイに専念できる形にする、というのが現時点での答えとなっています。

Kustomizeの利用

アプリケーションのマニフェストをdev/staging/productionといった環境ごとに重複しない形で管理できるようにKustomizeを利用しています。

デプロイ

デプロイはArgoCDを選定しています。

Argo CD - Declarative GitOps CD for Kubernetes
https://argoproj.github.io/argo-cd/

これがベストかどうかはまだわかりませんが、利用事例の多さ、開発の頻度、Helmで一発で導入できること、Kustomizeへの対応、見やすいWebUIなどが魅力的だと感じています。

まだ確定はしていませんが、ECRへのイメージプッシュからLambdaを起動して、argocdのCLIからプッシュされたイメージのタグを指定してデプロイする方法(GitOps)が良さそうだと考えています。

Parameter Overrides
https://argoproj.github.io/argo-cd/user-guide/parameters/

おわりに

とても簡単な解説にはなりますが、以上の組み合わせでKubernetesまわりの環境を構築しています(しようとしています)。
ここはどうなっているの?という質問や疑問がありましたら、Twitter @isaoshimizu まで優しくお気軽にメンションしていただけたら、こちらとしても大変ありがたい限りです。

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

IT初心者のSORACOMジャーニー SORACOM Kryptonで認証キーを手放す

本記事はSORACOM Advent Calendar 2019の6日目のものです!

「クラウドって何?サーバーって何?」の状態で、2018年8月からPython・AWSを勉強し始めて、今年はJAWS-FESTA2019にて「IT初心者のクラウドジャーニー」というテーマでプレゼンして、2019年11月12日 SORACOM-UG#1 札幌初開催! 勢いのままに、Qiita初投稿です!

初心者は何から始めるか?

プログラムもデバイスもクラウドサービス利用も全部わからないけど、自己満足できるレベルのアウトプットを求めて、IoT〜通信〜クラウドまで一連で勉強するテーマを決めました。
 「定期的に写真を撮るデバイスを作って、簡単なホームページを開設してみる」

 

初期の構成要素

構成 デバイス 備考
デバイス      Raspberry Pi 3B+   
通信手段 SORACOM SIM  Plan−D
クラウドサービス   AWS      S3、CLI、IAM
プログラミング言語 Python     3.5.3
オープンソースモジュール OpenCV   4.1.1

開発の変遷 

【第一段階】
1. ラズパイからPythonでPiカメラをOpenCVで起動させて、画像を撮影。
2. S3へのアクセス権限をIAMユーザから認証キーを払い出す。
3. AWS CLIで認証して、Boto3でS3へアップロード。
4. S3を公開にして、「index.html」に画像を埋め込んで、HTTPSにて静的サイトのURLを払い出す。
5. AWS Certificate Managerからドメインを購入し、静的サイトの独自ドメイン化を図る。
6. S3 → CloudFront → Route53 にて独自ドメインURLの払い出し。

~簡単な構成図~
最初のアーキテクチャ.png

 この構成で撮影画像をS3の静的サイト上で見れました!

セキュリティを考える

 セキュリティの知識ゼロで、デバイスを作成していました。見直した際の問題点を下記に記載。
  1.ラズパイにIAMユーザ権限を直接払い出し
  2.ラズパイのホームをPi設定、初期パスワードのまま利用
  3.S3のポリシーを全公開

 もうガバガバの状態でした!知らないって怖いです。
 少しずつ整理をしていくことにしました。
 

セキュリティに対して、Kryptonに至る経緯

【まずAWS側でできるセキュリティを確認してみた】
 1.AWS Cognitoを用いて、ユーザープールからIDを払い出し、承認キーの取得。
   AWS CLIから、Pythonのboto3を用いてS3へアップロード完了!
 2.AWS STSで認証をやってみようと思ったけどわからなかったので、やめる。
  
   Cognitoは便利な機能であることがわかった。が、認証キーの管理はいずれにせよ発生する。

【続いて、SORACOMのサービス内容を確認】
今回、作成したカメラデバイスとの相性がいいSORACOMサービスを選ぶ必要がある。
クラウドへのデータ転送を実施する上では、下記のサービスが存在する。

サービス名称 評価  結果
Beam     送信前後のプロトコル変換があり △ ※1
Funnel AWS IoTへの接続可能      ✖︎ ※2
Krypton  AWS Cognitoの認証を肩代わり ○ ※3

 ※1 データの暗号化におけるプロトコル変換が初心者の私にとっては、難しい。
 ※2 AWS IoTからS3へのデータ容量制限が128kByteであるため、画像をUPする上では不向き。
 ※3 認証キーの管理不要。認証の再生成時間も設定可能。
 

Kryptonを実装

 SORACOMさんのホームページ(こちら)を参考に実装しました。
 ホームページの内容のまま、SORACOM Kryptonを実装することができました。 
 ただし、画像をS3へUPする際に、PythonのSDKが無い?あるようだけど作る必要あり?とのことで、
 手っ取り早く実装したいので、既にSDKが存在するNode.jsを勉強し、S3へのアップロード用のプログラムを一部書き換えました。

 コード公開です。毎分撮影画像を指定のS3へアップロード。

Node.js
'use strict';
const AWS = require('aws-sdk');
const exec = require('await-exec');
const credentialsRefreshInterval = 15 * 60 * 1000;
const fs = require('fs');
const cron = require('node-cron')

// Executes krypton client to get open ID token
async function getToken(){
  let execResult = await exec("./krypton-cognito");
  return JSON.parse(execResult.stdout);
}

// Initializes AWS credentials and set timer to refresh periodically
async function initializeCredentials(){
  let data = await getToken();
  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityId: data.identityId,
    Logins: {
      'cognito-identity.amazonaws.com': data.token
    }
  }, {region: data.region});
  setInterval(
    async () => {
      data = await getToken();
      AWS.config.credentials.params.Logins['cognito-identity.amazonaws.com'] = data.token;
      AWS.config.credentials.refresh(err => {
        console.log("successfully refreshed AWS credentials");
      });
    },
    credentialsRefreshInterval
  );
}

initializeCredentials().then(() => {
  console.log("Successfully obtained AWS credentials");

  const s3 = new AWS.S3();
  const params = {
    Bucket: 'バケット名称',
    Key: "S3に保存する名前の画像名称"
    };
  let v = fs.readFileSync("./撮影画像名称");
  params.Body=v;
  cron.schedule(' 0 * * * * *',() => { 
  s3.putObject(params, function(err, data){
      if(err) console.log(eer, err.stack);
      else{    console.log(data);
               console.log('update image to S3!!');
           }
        });
    console.log('wait for next action');
    });
})

soracom-krypton-client-for-java中の”kryptonExamples”フォルダの中にある”cognito-auth.js”に対して、「定期実行」と「S3へのアップロード」を追加しています。
参照: Node.js 定期実行
参照: Node.js S3へのアップロード

最終的な構成

最終的なアーキテクチャ.png

Plan-DのSIMで上記の構成にたどり着きました!
無事に、認証キーを手放すことができました!

さっそく大容量アップロード用のPlan-DU SIMで運用開始しようとしたが、Plan-DU用ドングルを購入しておらず、ドコモのWifiルーターで代用中だったことを思い出す。

あれ、そもそもWifiルーターに設置されたSIMに対してもKryptonって使えるんだけっけ?
よくわかっていないことが判明しました。とりあえず、Plan-DU用のドングルを購入しよう。

そして、Wifiルーターに対するKrypton設定についてご存知の方、教えて頂きたいです。
出来ないような気もしてます。。。

IT初心者が記載したブログとなります。
表現に誤りがある場合には、ご指摘いただけますと幸いです。
Advent Calendar 楽しんで参りましょう〜!

参照サイト

【ラズパイの設定に関する資料】
参照: 【python/OpenCV】カメラの歪み補正を行う方法

参照: ラズパイ3にOpenCV3/4を簡単に導入
 OpenCVがPython2に入ってしまう場合に動きません。python3を起動させてcv2のバージョンをチェックしてみるといいです。

参照: boto3を使ってS3にファイルのアップ&ダウンロード
boto3をラズパイに入れる際には、sudo pip3 install boto3
pip3で入れないと、Python3で使えないので注意ください。

【ラズパイとクラウド連携に関する資料】
参照: AWS CLIの設定とS3へのアップロード

【S3の静的サイト作成に関わる資料】
 参照: 【図解】AWS S3を静的なWebサーバーとして利用する

 参照: AWS S3で静的Webページをホスティングする

【クラウド上のセキュリティを高める資料】
参照: AWSにおける静的コンテンツ配信パターンカタログ(アンチパターン含む)

参照: AWS再入門 Amazon CloudFront編

参照: SSL証明書発行時のドメイン認証メールをSESで受け取ってみた

参照: S3と独自ドメインで公開しているサイトをSSL(https)化する

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

API Gateway - API キャッシュを有効にして応答性を強化する

■概要

API キャッシュを有効にして、エンドポイントのレスポンスがキャッシュされるようにできます。

  • エンドポイントへの呼び出しの数を減らすことができる。
  • API へのリクエストの低レイテンシーを実現することができる。

ステージに対してキャッシュを有効にすると、API Gateway は、秒単位で指定した有効期限 (TTL) が切れるまで、エンドポイントからのレスポンスをキャッシュします。
その後、API Gateway は、エンドポイントへのリクエストを行う代わりに、キャッシュからのエンドポイントレスポンスを調べてリクエストに応答します。

API キャッシュを有効にして応答性を強化する

■やってみる

□APIキャッシュを有効にする

デフォルトでは無効になっているので、

  • APIキャッシュを有効化にチェック
  • キャッシュキャパシティ:0.5GB
  • キャッシュ有効期間:180

ぐらいでやってみる。

image.png

※API キャッシュを有効にすると追加料金が発生します。

□詳細 CloudWatch メトリクスを有効化にし、動作確認する

  • 詳細 CloudWatch メトリクスを有効化にチェック

image.png

CacheMissCountが1、その他はCacheHitCountとしっかりキャッシュが効いているようです。
image.png

■リンク

Amazon API Gateway

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

[AWS]Amplify DataStore を Vue で使う

はじめに

先日 Amplify DataStore というものがリリースされました。
詳しくは公式ブログで確認していただければいいのですが、簡単に使ってみたところ、GraphQLの書き方をしなくてもGraphQLを使えるという感想でした。
今までAmplifyでAppSync(GraphQL)を使っていたやり方と比較しながら解説していこうかと思います。(参考記事)

Getting Started

まずVueのプロジェクトを作りましょう。

$ npm install -g @vue/cli
$ vue create amplify-datastore-sample
$ cd amplify-datastore-sample

amplifyの初期化などをします

$ npx amplify-app

処理が完了すると amplify というディレクトリが生成されています。
その中にはAmplifyで利用するAWSサービスの情報が入っていきます。
amplify/backend/api/<datasourcename>/schema.graphql の内容を書き換えていきます。

schema.graphql
enum PostStatus {
  ACTIVE
  INACTIVE
}

type Post @model {
  id: ID!
  title: String!
  rating: Int!
  status: PostStatus!
}

AWSの環境にデプロイしていきます。

$ npm run amplify-modelgen

実行するとAppSyncとDynamoDBがデプロイされます。
これでAWS側の準備は完了です。

Settings

必要なパッケージをインストールしておきます。

$ npm i @aws-amplify/core @aws-amplify/datastore

インストールしたパッケージをimportしておきます。
models というディレクトリは npm run amplify-modelgen を実行したときに src 以下に生成されます。

import { DataStore } from "@aws-amplify/datastore"
import { Post, PostStatus } from "./models"

Save Data

データを書き込むときは DataStore.save() を使います。

await DataStore.save(
  new Post({
    title: "My First Post",
    rating: 10,
    status: PostStatus.ACTIVE
  })
)

このようにDynamoDBにPUTされます。
スクリーンショット 2019-12-05 17.11.26.png

GraphQLで書くと…

const saveBody = `
  mutation {
    putData(
      input: {
        title: "My First Post",
        rating: 10,
        status: "active"
      }
    )
  }
`
await API.graphql(graphqlOperation(saveBody))

Query Data

データを取得するときは DataStore.query() を使います。

const posts = await DataStore.query(Post)

limitや条件を指定することもできます。

await DataStore.query(
  Post,
  c => c.status("eq", PostStatus.ACTIVE),
  {
    limit: 10
  }
)

GraphQLで書くと…

const queryBody = `
  query {
    queryData(limit: 10){
      items {
        id,
        title,
        rating,
        status
      }
    }
  }
`
const posts = await API.graphql(graphqlOperation(queryBody))

Delete Data

データを削除するときは DataStore.delete() を使います。

const todelete = await DataStore.query(Post, "1234567")
await DataStore.delete(todelete)

ちなみに論理削除となるので、DynamoDBからデータが削除されることはありません。
_deleted というフラグが立ちます。
スクリーンショット 2019-12-05 17.12.26.png

GraphQLで書くと…

const deleteBody = `
  mutation {
    deleteData(
      input: {
        id: "1234567"
      }
    )
  }
`
await API.graphql(graphqlOperation(deleteBody))

さいごに

GraphQLのquery文で結構コード量が増えたり、query文の生成がめんどくさかったりとするのですが、AmplifyDataStoreを使うと、そういったところを解消してくれるそうですね。
プロダクションレベルで使うためには認証周りだったりなどを詰めていかないといけませんが、とても使う価値がありそうです。
ではまた!

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

AWS re:Invent 2019のEXPO出展企業を現地限定ノベルティと共に振り返る

アメリカのラスベガスで開催されている、AWSの年次カンファレンス「re:Invent 2019」には、グローバルから数多くの企業が出展しています。
企業の出展ブースはEXPOと呼ばれ、多くの来場客で賑わっています。
そんなre:InventのEXPOを回ってきましたので、私が会話した出展企業を振り返り、傾向などを分析したいと思います。
image.png

image.png

でも、ただ振り返るだけでは面白くないので、これだけ回ってきたんだぞと言わんばかりに、各ブースで配っていたノベルティを紹介していきたいと思います。

そもそもノベルティとは

re:InventやIT系イベントにおけるノベルティとは、平たく言えば、各企業が出しているグッズのことです。
ノベルティには、各企業を象徴するロゴやメッセージが載せられています。
米国のみならず、日本でも多くの企業がイベントで何かしらのノベルティを出しています。

re:Inventでは、多くの企業がステッカー、Tシャツ、ピンバッジのいずれかを出していました。
ただ、少し変わった路線も増えてきており、フィギュア、帽子、靴下、モバイルバッテリー、さらにはバウンドさせると光るボールなどを出している企業もありました。

re:Inventにおけるノベルティ入手方法

基本的には英語で会話でき、ブースの方とユースケースやビジネスの相談等してからでないと、ノベルティは貰えません。
ただ、中にはカンファレンスバッチに個人ごとに付属するバーコードを読み取るだけで、ノベルティがもらえるブースもありました。
基本的に、バーコード読み取りだけで貰えるのは小物が多く、Tシャツがバーコード読み取りだけでもらえるブースはなかったと思います。

出展企業とノベルティ

さて、ここから怒涛のように出展企業を振り返っていきたいと思います。

Nutanix

バッグとモバイルバッテリーをいただきました。
image.png

展示員の方にXi Clustersと、マネジメントコンソールの説明をいただきました。
Nutanix on AWSも覚えておきたいキーワードでした。

ParkMyCloud

帽子をいただきました。
image.png

AWSのコストマネジメントツールについて説明をいただきました。
どこにどのくらいのコストが掛かっているのかを1つのMAPのように表示し、対策できます。
一画面で全範囲のコストを確認できるという説明でした。

Zendesk

マグカップをいただきました。
image.png

最新バージョンのZendeskの管理画面を見せていただきました。

CTO.ai

Tシャツをいただきました。
image.png

CLIやSlackコマンドを通じて、AWSのリソース管理ができるようです。
Slackとのコラボレーションは他のブースでも良く話を聞きました。
開発とコミュニケーションの一体化は、現在のトレンドのように見えました。

paloalto

Tシャツをいただきました。
image.png

ネットワークルータと仮想アプライアンスの説明をいただきました。
アメリカでは、MarketPlaceを中心としたビジネスが確立されている感があります。

IBM

バッグをいただきました。

image.png

なんとAWSのセキュリティを管理するためのツールをリリースしているとのこと。
ただ目指す方向はマルチクラウドだそうです。
自社でもクラウドサービスを抱えるIBMが、先導してマルチクラウドに取り組み始めています。

AWS China

ask me (AWS様スタッフ) のTシャツをいただきました。
image.png

東京リージョンとの接続に関する情報が少ないと言われるお客様が多いそうです、
また、今後はアップデートのスピードを上げていくとのことでした。

ExtraHop

Tシャツをいただきました。
『箱の中身は何だ』ゲームに取り組むと貰えました。
image.png
ネットワークの分析・監視ツールをリリースされていました。

Sophos

靴下をいただきました。
image.png

セキュリティソリューションは、クラウド特化を進めているようです。

databricks

靴下をいただきました。
image.png
データ分析環境をAWS上にデプロイしてNotebookで分析・可視化ができます。

TIBCO

Tシャツをいただきました。
image.png
イベント駆動型アプリを構築するための、超軽量のGoベースのオープンソースエコシステム「flogo.io」です。

splunk

Tシャツをいただきました。
image.png

インシデント管理・監査のためのVictorOpsとSplunkの連携について説明いただきました。

Okta

Tシャツをいただきました。
image.png

シングルサインオン、MFAなどの認証を統合的に実現するサービスです。
日本でも名が知れてきており、コンソールは日本語にも対応しています。
日本語のコンソールで説明いただき、本当に感謝でした。

Rapid7

Tシャツをいただきました。
image.png

脆弱性管理、ペネトレーションテスト等を提供するサービスです。
日本でも取り扱いがあります。

Capital One

Tシャツをいただきました。
展示員の方のトランプマジックに参加した後、バンキングソリューションとバックエンドの話で盛り上がり、最後に、トランプかTシャツかどちらが良いか、という選択で、Tシャツを貰いました。
image.png

SignalFx

靴下をいただきました。
image.png

こちらもSaaS型の監視・分析サービスです。

Instana

かわいいロボット型のマスコットフィギュアをいただきました。
image.png

アプリケーションのパフォーマンスモニタリングに関するサービスを提供されています。

Global Knowledge

靴下をいただきました。
image.png

言わずもがな、技術教育を提供されています。

StackRox

バウンドさせると光るボールをいただきました。
原型をとどめたまま電池を換える方法はなさそうです。
image.png

コンテナやKubernetesのセキュリティに特化したサービスです。

AWS

EXPOのサービス別ブースで英語で質問して会話できないと貰えない引換券があり、引換券をSWAG DESKという受付に持っていくと、Tシャツが貰えました。

こちらはManaged BlockchainのTシャツになります。
image.png

この他にも、サービス毎に靴下等のノベルティがあったようです。

その他、各種ステッカー類

いずれも現地でしか手に入らないものばかりのようです。
image.png

出展内容から感じた傾向

監視・ログ管理系を中心にSaaS型サービスが豊富

様々な企業が、それぞれ独自のUIで個性を主張し、SaaS型でこれらのサービスを展開される傾向があると感じました。
全て似たように思えるかも知れませんが、例えば同じ分析でも、フィルターしながら各個の項目を詳細に眺めるのと、タイル状のマップで全サービスの状態を俯瞰できるのとでは、やはり違うと思います。
特に監視とログ管理系は各企業でかなり力を入れて豊富に展開されており、この分野で戦うにはかなり強烈な個性が必要だと感じました。
また、利用する側に立てば、製品の特徴を押さえ、自分たちが本当に必要で使いやすいサービスを選んでいくべきだと感じました。
ただ、比較しだすときりがなく、ある程度直感で選ぶことも大事でしょう。

ハードウェア中心と思われていた企業はマルチクラウド/ハイブリッドに

例えばIBMやNutanixは、独自のハードだけで攻めるのではなく、クラウドとの連携を打ち出してきていました。
特にIBMは、自社でもIBM Cloudを抱えているのに、AWSに対応したセキュリティサービスを出してきており、その裏側にはやはりマルチクラウドを意識した戦略があると感じました。
Nutanixの場合は、AWSとの接続によるハイブリッドクラウド戦略に重きが置かれていると思いました。
AWS自身もOutpostsをリリースしており、この辺りの市場動向はどうなっていくのかと思うと、目が離せないです。

ブース巡りの所感

ノベルティはTシャツなどの衣類が中心に

ブースでTシャツや靴下を配る企業が爆発的に増えていると感じました。
明らかに集客効果があるのだと思います。
また、Tシャツなら、後から身につけてくれればそれだけで宣伝になるなどの考えもあるのだと思います。
多くの企業が効果があると考えているからこそ、ブームになっているのだと思いました。
日本でも、ノベルティに力を入れる企業が増えていきそうな気がします。

ブース巡りは英語力も大事

1年前、AWS re:Invent 2018に参加した際は、英語が全く話せず、ただただブースを回って、置いてあるノベルティを持って帰るだけになり、あまりにふがいなく終わっていました。
もちろん、ブースでの会話ができないので、ステッカーやピンバッチといった小物以外のものは貰えませんでした。
一番の目的である、情報収集も殆どできていなかったです。

これではまずいですし、面白くないし楽しめていないと思い、今年の1月から英会話の勉強を始め、11月末までにそこそこに聞き取れつつ片言ではあるものの喋れるようになりました。
今年のre:Invent 2019では、その腕試しとの思いでEXPOに挑みましたが、英語が少しでも話せるだけで、ブースでの会話が楽しくなり、情報収集やノベルティ集めも昨年に比べてかなり出来たと感じました。
やはり、re:Inventに行く際は、英語が話せて損はないと感じます。
もちろん英語が話せなくても楽しめますが、話せると楽しめる範囲が格段に広がりますので、良いです。

今年のEXPOはかなり楽しめましたが、これで満足せず、もう1年間英語を勉強して、来年は今年以上に楽しもうと思いました!

参考リンク

Expo - AWS re:Invent 2019

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

【Python用】AWS Lambda Layerへのアップロードファイルをササッと作る

前提

Dockerを利用して、Lambdaで使うPythonライブラリのLayerアップロード用zipを作る。
手順はrequestsをzipにする場合のサンプル。

手順

ホストOSで、ライブラリを使うバージョンのPythonコンテナにアタッチする。

$ docker pull python:3.8
$ docker images
$ docker run -itd ImageID bash
$ docker attach ContainerID

コンテナ側では、pipでライブラリを取得し、zipにまとめる。

$ apt-get update & apt-get install zip
$ mkdir python & pip install requests -t ./python
$ zip -r upload.zip python

「Ctrl + p, Ctrl + q」でコンテナから抜け、コンテナからホストにzipをコピーし、コンテナを終了させる。

$ docker cp ContainerID:/root/upload.zip upload.zip
$ docker stop ContainerID

後はアップロードしておしまい。

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

今年の自分を振り返る

こんにちは
株式会社Diverseで働いていますpython_spameggsです。
6日目の記事になります。
タイトル通り今年一年の自分がやって来たことを振り返りたいと思います。

1月〜3月

社内ツール

AWSへ移転する時に必要ではないものは予め無くしておきたかったので誰も使用していないものを洗い出して停止、削除しました。
弊社の制度で本を購入するのに補助してくるのでその申請が簡単にしやすくするものを作成しました。

DNS移管

AWSへ移管しました。
rspec-dnsを使用して漏れがないことを確認しました。

AWS CodeBuildとTerraformを使用してAWSサービスを作成

PRを作成するとterraform planが実行されてPRがマージされるとterraform applyが実行されます。
こちらを参考にしました。Terraform どこで実行していますか?
すでにAWSコンソールから作成されていたサービスがあったのでterraform importを使用してコード化しました。

mackerelの監視ルールをgithubで管理

監視ルールに追加や変更があったらmkr monitors pullしてgit pushしてgithubで管理しています。
githubにpushされた時にCircleCIが動くようになっておりmkr monitors diffを実行して監視ルールと差分がないかチェックしています。
私以外やっている人がいないので自動化したいなと思っているけど手が回ってないです。

dokkuを使用して開発環境を作成

環境を作成したいブランチを選択してボタン一つで作成、更新、削除ができるようになっています。

4月〜6月

AWS Direct Connectの準備

VPCなどの設定やDirect ConnectのVirtual Interfaceなどを開通するのに必要な情報をエクセルにまとめました。
資料をまとめるのは普段の作業と違ってなかなかハードでした。

MySQLのバックアップ専用サーバのマシン交換

レプリケーション遅延でバックアップがきちんと出来てなかったのでスケールアップしました。
バックアップ方法や監視方法が古いものを使っていたので見直しました。

gRPCのためにプロキシサーバを作成

nginxを使用してプロキシサーバを作成したが最近envoyの方が良かったのではないかと思い始めてます。

RPMをdocker-composeで作成できるように対応

DockerでRPMの作成はしていたけど古くなりドキュメントもなかったのでdocker-composeを使うものを作成しました。

AWS S3にRPM Repositoryとして使用できるように作成

AWSへ移転して使いたいRPMが出てきそうだったので準備しました。
sts:AssumeRoleを使って別のAWSアカウントからも使用できるようにして特定のユーザ or IPアドレスのみアクセスできるようにバケットポリシーを作成しました。
そのままだとs3からyumが出来ないのでAWS EC2インスタンスにyumできるようにプラグインを設定しました。
この時に参考していたものがbotoを使っていて古いのでboto3を使用するに変更しました。

7月〜9月

AWS Direct Connectの開通確認

疎通確認して速度計測をa、c、dそれぞれ確認しました。

AMI作成

packerとansibleを使用して作成しました。

MySQLのバージョンアップ

バージョンを5.5から5.6にアップグレードしました。
MHA for MySQLを使用してフェイルオーバーさせて切り替えました。
5.7まであげてもいいじゃないかと話に上がっていたが時間が足りなかったので次の機会に。

pt-duplicate-key-checkerを使用して重複したインデックスをチェック

興味があって時間もかからなかったのでさくっと出しただけで使えていません。

SSL/TLS証明書の更新

オンプレミスで使用しているものを更新しました。

10月〜12月

MySQL(オンプレミス)からAWS RDS(MySQL)にレプリケーション

time_zone設定の違うMySQLのレプリケーションについて
上記に気が付かずTIMESTAMP型のカラムがずれてしまったのこちらを参考にtimezoneを変更してmysqldumpを行いリストアして対応しました。
レプリケーション用のユーザパスワードが保存されていたものと違っていたので探索してmysql_historyから作成したログが残っていたので保護してmysql_historyからは削除しました。

terraformをv0.11からv0.12にバージョンを変更

AWS CodeBuildでWARNがいっぱい出てしまい実行結果のログが読みにくくなったため変更しました。

一部サーバをAWSへ移転

gRPCサーバとgRPCのプロキシサーバを移転しました。
予めRoute53に移転していてルーティングポリシーを重みを少しずつ変更して徐々に行った。
現在はメールサーバとRDSの移転を行っています。

まとめ

やったことをなるべくdocbaseに書いてきましたが詳細が抜けていることが多くて思い出すのが大変でした。
振り返ってみると1年通してこれだけAWSへ移転するための作業をやったことはないのでいい経験なりました。
来年も同じことを続けるけど。

明日の7日目は誰かが埋めてくれるはず!

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

UNHEALTHYのWorkspaces再起動するLambdaをpythonに書き換えました

今年もハンズラボアドベントカレンダーやっていくぞい

概要

弊社ではAWSの仮想デスクトップを提供するサービスWorkspacesを利用しているんですが、
たまにUnhealthyで落ちてたりします

\ 何もしてないのに壊れた /

大抵は一時的に負荷がかかって落ちたとかで再起動すればどうにかなることがほとんどです
というわけでひとまず再起動させるLambdaがあるんですが
Node.jsで書かれているのでたびたび発生するバージョンアップ対応・・・

シンプルなコードだし、どうせならpythonに書き換えてみようかなと思ってやってみました

処理の流れ

  • CloudWatch
    • UnhealthyのWorkspacesがあったらアラートでSNS通知
  • SNS
    • メール送信
    • Lambda発火
  • Lambda
    • Workspaces情報をまるごと取ってきて順にチェック
    • Unhealthyだったら再起動

なんで順にチェックしてるんだろ、不調のあるWorkspaceIdをダイレクトに指定して再起動しないのかなと思ったんですけど
アラート情報から取ってくることになるのでWorkspaceごとにCloudWatchでアラートを設定したくなかったのかなと解釈して変更しませんでした

書き換えた結果

環境・設定

  • ランタイム Python 3.7
  • タイムアウト 15秒

ソースコード

import boto3

sns = boto3.client('sns')
workspaces = boto3.client('workspaces')


def lambda_handler(event, context):

  workspaces_client_list = workspaces.describe_workspaces()

  for workspaces_info in workspaces_client_list['Workspaces']:
    workspace_id = workspaces_info['WorkspaceId']
    workspace_state = workspaces_info['State']

    if workspace_state == 'UNHEALTHY':
      reboot_workspaces(workspace_id)


def reboot_workspaces(workspace_id):

  workspaces.reboot_workspaces(
    RebootWorkspaceRequests = [
      {
        'WorkspaceId': workspace_id
      },
    ]
  )

終わり

どの言語を選択してもバージョンアップ対応からは逃れられませんが、
最近弊社内でPythonの流れが来ていることもあり、書いてみる機会にできてよかったです

明日は @fasahina さんです!

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

DjangoのテンプレートをAWS boto3を組み込んで翻訳

各国の言語に対応したWebアプリが必要だったのでAWSのboto3を使ってタグを生成

メモとして活用します。
完成したら細かいところを記述したいと思います。
環境
djang == 2.1
boto3 == 1.0.0?
python == 3.6 ?

やりたいこと
クライアントが言語選択したらその言語で翻訳して表示

中身
boto3を組み込んだタグを作って
HTML内でタグに囲んだ部分を翻訳する。
⇓イメージ
{% translate %}ここ翻訳{% endtranslate %}
すでにDjangoには{% trans %}という翻訳用のタグがあるようだけど
自分でpoファイルやら何やらよりも
AWSの翻訳API使った方が一程度無料でクオリティ高いかな~と思ったので
まずはやってみる。

事前準備

・AWS登録
・pip install boto3

テンプレートタグについて

djangoはサーバサイドの言語だからHTMLを生成するような便利な機能が必要だったのでDTL(django template language)という便利機能が組み込まれました。
DTLには4つ種類があり、
1つは、変数=>{{ first_name }}
2つ目は、タグ=>{% block %}{% endblock %}
3つ目は、フィルター=>{{ value | filter }}
4つ目は、、、4つ目は、忘れた、とりあえず一時的メモなので後ほど

タグの生成

タグの作り方は、djangoのドキュメントでテンプレートエンジンの仕組みのところを通読すると
その後スムーズにできます。

タグの生成下準備
①アプリは直下に templatetags ファイル生成
②templatetags 内に init.pyを生成 <=これでテンプレートエンジンが探索してくれる
③templatetags 内に translate.py 生成 <=ファイル名は何でもオッケー

translate.py の中身(まだ途中)

from django import template
import boto3

register = template.Library()

REGION = 'ap-northeast-1'
SRC_LANG = 'ja'
TRG_LANG = 'en'

@register.filter(name="translate", is_safe=True, needs_autoescape=True)
def translate(value, autoescape=True):
    translate = boto3.client(service_name='translate', region_name=REGION,use_ssl=True)
    result = translate.translate_text(Text=value,
        SourceLanguageCode=SRC_LANG,
        TargetLanguageCode=TRG_LANG)
    return result.get('TranslatedText')

とりあえずこれはフィルターを生成してみました
HTMLファイルで
{{ '箱' |translate }}
とすると
Box
とブラウザで表示されます。
この'箱'部分に本文をいれれば翻訳されて返ってきますが
今回は{% translate %}ここ翻訳{% endtranslate %}が理想なので
お次はこのタグをつくります。フィルターは簡単そうだったので作ってみました。
これからタグつくるのでメモはここまででまた更新します。

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

AWSでSQSをLambdaのトリガーをマネージメントコンソールで設定するときに気をつけたい運命の分かれ道

はじめに

AWS LambdaのトリガーにSQSを設定されている方も多いと思います。
SQSのキューにメッセージが送られると、トリガーに設定されたLambdaが呼び出されます。
一度の起動で複数のメッセージを処理することも可能で、このメッセージの数はバッチサイズで指定できます。最大は10です。

Lambdaの用途によっては、バッチサイズを1にして運用する場合もあります。
マネージメントコンソールでトリガーを設定する主な導線は、

  1. SQSからLambdaを設定する
  2. LambdaからSQSを設定する

の2通りがありますが、これが運命の分かれ道です。
実際にトリガーを設定してみるので、どのような画面遷移なのかご覧ください。

状況

sqs_sampleというLambdaとlambda_triggerというSQSのキューがあります。
lambda_triggerがメッセージを受信するとlambda_triggerが発火するようにトリガーを設定します。

Lambdaからトリガーを設定する場合

マネージメントコンソールからLambda > 関数 > sqs_sampleを選択します。
この画面で「トリガーを追加」をクリックします。
スクリーンショット 2019-12-03 10.11.10.png

プルダウンからSQSを選択します。
スクリーンショット 2019-12-03 10.17.07.png

スクリーンショット 2019-12-03 10.28.56.png

プルダウンからlambda_triggerを選択します。
ここで注目してもらいたいのは、バッチサイズが画面に表示されている点です。
これにより、トリガーのバッチサイズが10であることをユーザーは認識できます。
今回は10で設定しました。

スクリーンショット 2019-12-03 10.31.47.png

トリガーが設定されるまで1分ほど待ちます。
これで設定完了です。
スクリーンショット 2019-12-03 12.16.32.png

SQSからトリガーを設定する場合

マネージメントコンソールからSQSを選択します。すでにキューが存在する場合は作成済みキューの一覧が表示されます。
lambda_triggerを選択して「キュー操作」をクリックします。
スクリーンショット 2019-12-03 13.36.39.png

表示されたメニューから「Lambda関数のトリガーの設定」を選択します。
スクリーンショット 2019-12-03 13.36.59.png

プルダウンメニューからsqs_sampleを選択して「保存」をクリックします。
スクリーンショット 2019-12-03 13.44.18.png

これでトリガーが設定されました。:rocket:
スクリーンショット 2019-12-03 13.51.20.png

ご覧いただいたように、SQSからトリガーを設定する場合はバッチサイズなんてワードは一切出てきません
ちなみに、バッチサイズは10で設定されます。

まとめ

バッチサイズが1に設定されていることを前提としたLambdaをデプロイするときは気をつけましょう

設定済みのトリガーのバッチサイズを変更する方法はみつからなかったので、一度削除してからLambdaからトリガーを設定しましょう。


ハンズラボ Advent Calendar 2019 17日目は@bakupenさんです?

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

AWSの避けるべき五つの間違い

開発者およびシステム設計者のMichael Wittigは、Cloudonoutブログで、AWS(Amazon Web Services)を使用する際の最も一般的な誤りに関する興味深い記事を投稿しました。 この記事の要点をご紹介します。

WittigはAWSコンサルタントとして働いており、サイズに関係なく多くのシステムを見てきたそうです。ほとんどの場合、これらは標準のウェブアプリケーションです。 彼は、避けるべき5つの最も一般的な間違いのリストを紹介しました。

典型的なWebアプリケーション

標準のWebアプリケーションは次のものが含まれています。

  • ロードバランサー
  • スケーラブルなサーバーサイド(ウェブバックエンド)
  • ストレージ

図では、次のようになります。

05f1b63b8cf645a5a5b0c2c1ccd5f190.png

これは一般的なパターンです。 他の方法を使用してアプリケーションを設計する正当な理由がなければなりません。

間違い1.インフラストラクチャを手動で管理すること

コンソールを使用してAWSのリソースが作られているということは、インフラストラクチャが手動で管理されているということです。この制御オプションの主な問題は、リソースの設定は再現できず、どこにも登録されていないことです。その結果、多くのエラーが発生する可能性があります。幸いなことに、この問題を無料で解決できるAWS CloudFormationがあります。

リソース(EC2インスタンス、セキュリティグループ、サブネット)を手動で作成する代わりに、テンプレートでリソースを記述したり、独自に作成したり、既製のものを使用したりできます。 CloudFormationは、それらを現在のスタックに統合する方法を処理します。図の通り、このサービスが正しい順番にリソースを作成してくれます。

d8b6068c0608412cbe155d2d21b8409a.png

間違い2. Auto Scalingグループを使用しないこと

一部の人は、特別な機能に頼らずに自分でリソースを拡張できると信じています。もちろん、それは大間違いです。各EC2インスタンスはAuto Scaling Groupで実行される必要があります。独自のインスタンスであっても。 Auto Scaling Groupは、必要な数のインスタンスの起動を制御します。実際、仮想マシンの論理グループのように動作します。この機能は無料で使用できます。

標準のWebアプリケーションでは、サーバーはAuto Scaling Group内の仮想マシンの中に起動されます。負荷に基づいてコンピューティングリソースの量を増加または減少させることができます。アドミニストレーターは、自動スケーリングを開始する条件を指定できます。これは例えば、論理グループのCPUパフォーマンスのしきい値または負荷分散のためのリクエストの数です。

間違い3. Amazon Cloud Watchの分析を怠ること

AWSサービスに関する必然的なデータは、Cloud Watchから取得できます。 仮想マシンは、プロセッサの負荷、ネットワーク、ディスク操作について通知します。 ストレージは、メモリ使用量とI/O操作の数に関する情報を提供します。 これらの統計を正しく使用するのはあなたです。 一日のCPU使用率のメトリックスを見てみましょう。

537fbd289f854db798acd9ad70904871.png

ピークをご覧ください。Wittigは、この飛躍が毎日同じ時間に発生していると確信していました。

タスクスケジューラが悪さしているように見えます。 やっぱりそうですね。 このマシンからWebサーバーが起動されたということはスケジューラにより毎日待機時間が増加していたということです。 別の仮想マシンでスケジューラを起動すると、問題は解決するはずです。情報はすべてCloudWatchにありますが、あなたはそれを見る必要があります!

メトリックを分析した後のステップは、メトリックのアラームを定義することです。 その逆ではダメです!

間違い4.Trusted Advisorを無視すること

Trusted Advisorツールは、AWS環境がサービスを操作するベストプラクティスに準拠しているかどうかを確認します。 チェックされるのは:

  • コストの最適化
  • パフォーマンス
  • 安全性
  • 耐障害性。

Trusted Advisor管理コンソールが次のようになっている場合:
c6eb9ac910fa4a66b4db171497b0ad20.png
Wittig氏によると、環境内のプロセスの最適化を開始すべきです。

まず、セキュリティに十分注意をした方がいいです。 毎週のフィードバック機能を使うと、Trusted Advisorは現在および解決済みのすべての問題について報告してくれます。 無料版と有料版があります。 有料版により検証の機能がさらに広がります。

間違い5.仮想マシンの効率を管理しないこと

EC2インスタンスが十分に活用していないことに気付いた場合、インスタンスのサイズ(マシンの数またはc3.xlargeからc3.largeへ)を縮小しないようにする理由はありません。手動で管理されているインフラストラクチャなら話は別ですが。
十分に活用していないというのをどうすればわかると? CloudWatchのメトリックスを確認してください! とても簡単です。
Auto Scalingグループを使用している場合は、スケーリングする前にオートスケーリングルールとCloudWatchメトリックスを確認した方が良いです。

おまけ

今度インフラストラクチャを確認するのはあなたの番です。

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

パイソン(python) 吸引力の変わらない、ただひとつの自動化

はじめに

python_icon.jpg

今日は自分が業務上で定期(単純)作業を自動化するツールを パイソン(python) で作った話をします。
パイソン(python)は掃除機ではありません。掃除機をお探しの人はWEBで「ダイソン(Dyson)」と検索してください。

自動化にする背景

まず、自動化にする理由は

  • 手動で実行すると面倒くさいから
  • 自動のほうが人的ミスも起こりにくいから などなど、

実際に私がやっている業務は、

  1. JIRAで作業依頼を受ける
  2. 作業内容を確認して、作業を実施する
  3. 作業完了の連絡をJIRAコメントで知らせる。
  4. 作業内容によっては、機密情報などをメールで通知する

作業内容には複雑で難しいものもあれば、一瞬で終わる簡単な作業もある。簡単といっても数が多いとけっこう工数がかかってしまうし、メールで通知するパターンだとメール作成が地味に面倒くさい。

なので、これらの作業は 自動化にして結果だけ通知してもらおう!

というのが背景です。

自動化にする内容

超簡単な作業の中に「AWSのIAMユーザー作成」があります。そして「IAMユーザーアクセスキー発行」があります。今日はこれらの作業依頼がJIRAに投稿されて、実行、結果通知までの自動化ツールを紹介します。

使用するのはpython3 (version: 3.7.3)

自動化ツール作成

まずは自動化に必要はパーツ(function)を作成する

  • Pythonで「JIRA操作」
  • Pythonで「LDAP操作」
  • Pythonで「AWS操作」
  • Pythonで「SSH操作」※ 踏み台経由パターン
  • Pythonで「メール送信」
  • Pythonで「HTTP送信」

自動化の処理はPythonのインタフェース実装で作成します。

各パーツ(function)の紹介

各パーツ(function)には必要なライブラリが存在するので、動かす時はインストールしておくこと。以下は jira ライブラリのインストール例

$ pip install jira

Python de JIRA

  • 必要ライブラリ
    • jira
  • 公式ドキュメント

JIRAオブジェクトの作成

各JIRAの操作はJIRAオブジェクトから実行するので、最初にJIRAオブジェクトを作成しておく。

jira.py
from jira import JIRA

jira_url="https://jira.xxxxxxxx.jp"
jira_user="jirauser"
jira_pass="jirapass"

jira = JIRA(jira_url, auth=(jira_user, jira_pass))

JIRAチケットリスト検索

SQLのWHERE句のようなクエリ(文字列)を作成して検索を行う。

jira.py
issues = jira.search_issues(
    'project = {project} AND issuetype in {issuetype} AND status in {status}'.format(
        project = 'PROJECT_NAME',
        issuetype = '(issuetype01, issuetype02)',
        status = '(status_todo, status_doing)'
    )
)
# print(issues) 結果
# [<JIRA Issue: key='PROJECT_NAME-0001', id='000001'>, <JIRA Issue: key='PROJECT_NAME-0002', id='000002'>]

検索結果はkeyとid情報のリスト型で返ってくる。

JIRAチケット情報検索

JIRAチケットリスト検索のkey情報からチケットの詳細情報を取得する。

  • 使用API
  • 実装例
jira.py
# JIRAチケットリスト検索結果をfor文で回しながら、issue(issue_key)でチケットの詳細情報を取得
for issue_key in issues:
    issue = jira.issue(issue_key)

JIRAチケットの項目取得

jira.py
issue = jira.issue(issue_key)

# 依頼者(JIRAチケットを投稿した人)
issue.raw['fields']['reporter']['name']

# チケットの題名
issue.raw['fileds']['summary']]

# チケットタイプ(検索条件にあったissuetype)
issue.raw['fields']['issuetype']['id']

# JIRAチケットの入力項目はカスタム可能で、独自の項目を追加することができる。
# カスタム項目の値を取得する場合は、

Python de LDAP

ユーザー検索

attiribute情報取得

Python de AWS(SDK for Python)

  • 必要ライブラリ
    • boto3
import boto3
from boto3.session import Session

session = Session(
    aws_access_key_id='XXXXXXXXXXXXXXXXXXXXXX',
    aws_secret_access_key='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
    region_name = 'ap-northeast-1'
)

iam = session.client('iam')

def get_user(username):
    try:
        return iam.get_user(UserName=username)
    except iam.exceptions.NoSuchEntityException as e:
        print("no user(%s)" % username)


def create_user(username):
    return iam.create_user(UserName=username)

def create_login_profile(username, password, password_reset_flag):
    return iam.create_login_profile(
        UserName=username,
        Password=password,
        PasswordResetRequired=password_reset_flag
    )

def create_accesskey(username):
    return iam.create_access_key(UserName=username)

def add_user_to_group(username, groupname):
    return iam.add_user_to_group(GroupName=groupname, UserName=username)

def get_random_password(password_length, region):
    secretsmanager = session.client('secretsmanager', region_name=region)
    return secretsmanager.get_random_password(PasswordLength=password_length)['RandomPassword']

認証情報設定

ユーザー作成

パスワード生成

ログイン情報作成

アクセスキー情報発行

Python de SSHTunnel

踏み台経由でコマンド実行

踏み台経由でfunction実行

Python de Sendmail

  • 必要ライブラリ
    • smtplib
    • email

メール送信

import smtplib

from_address = "from_address@test.com" #送信元アドレス
to_address = "to_address@test.com" #送信先アドレス

body="テストメールです"

message = MIMEMultipart()
message["Subject"] = "メールの表題"
message["From"] = from_address
message["To"] = to_address
message["Date"] = formatdate()
message.attach(MIMEText(body))

try:
    smtp = smtplib.SMTP('mail_server', 25, timeout=10)
    smtp.sendmail(from_address, to_address, email_message.as_string())

    smtp.quit()
except Exception as e:
    print(e)

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

import smtplib

from_address = "from_address@test.com" #送信元アドレス
to_address = "to_address@test.com" #送信先アドレス

body="テストメールです"

file_path="/tmp/temp_file.csv"
file_name="temp_file.csv"

message = MIMEMultipart()
message["Subject"] = "メールの表題"
message["From"] = from_address
message["To"] = to_address
message["Date"] = formatdate()
message.attach(MIMEText(body))

attachment = MIMEBase('text', 'comma-separated-values')
file = open(file_path)
attachment.set_payload(file.read())
file.close()
encoders.encode_base64(attachment)
message.attach(attachment)
attachment.add_header("Content-Disposition","attachment", filename=file_name)

try:
    smtp = smtplib.SMTP('mail_server', 25, timeout=10)
    smtp.sendmail(from_address, to_address, email_message.as_string())

    smtp.quit()
except Exception as e:
    print(e)

Pyrhon de Slack

  • 必要ライブラリ
    • slackclient

使用例

ログイン可能なユーザーを保持していて、API-Tokenが発行されていることが前提。

import slack

token = "xoxp-xxxxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx"
channel = "#test"
message = ":information_source: これはテストです"
client = slack.WebClient(token=token)

# Slackにメッセージ投稿
response = client.chat_postMessage(
    channel=channel,
    test=message
)

Pythonのインタフェース実装

Baseクラス作成

__init__.pyを実装

インタフェースクラス作成

実行例

さいごに

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

GPUを用いる機械学習推論処理をAWS BatchのSpotインスタンスで実現する

概要

AWSにて機械学習を行うにはまずSageMakerがあります。SageMakerはモデルの学習・管理・デプロイを取りまとめて行えるフルマネージドサービスです。
概ね便利なサービスなのですが、機械学習処理で用いるGPUインスタンスは高額なのでスポットインスタンスでコスト軽減を行いたくなります。
モデルの学習にはスポットインスタンスが使えるのですが、バッチ変換ジョブにはスポットインスタンスが使えません。(2019/12現在)
https://docs.aws.amazon.com/ja_jp/sagemaker/latest/dg/model-managed-spot-training.html
https://docs.aws.amazon.com/ja_jp/sagemaker/latest/dg/batch-transform.html

バッチとして非リアルタイムで推論するGPU処理をAWS Batchを使って実装したのでメモとして残します。

ポイント

AWS Batch

ECSイメージの準備

AWS BatchからはECSが呼ばれるため、EC2インスタンスには制御用のECSエージェントがインストールされている必要があります。
またGPUを利用するためにnvidiaドライバも必要です。
双方を満たすためにec2のGPU用AMIを利用します
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs-optimized_AMI.html
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/retrieve-ecs-optimized_AMI.html

  ECSAMI:
    Description: AMI ID
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ecs/optimized-ami/amazon-linux-2/gpu/recommended/image_id

ランタイムの指定

dockerコンテナ内からnvidiaのデバイスを使いたいのであれば、dockerの起動オプションにruntime=nvidiaを指定する必要があります。
ECSで実現する場合、自作のイメージを作成して設定ファイルを書き換える、ec2のUserDataから設定ファイルを書き換えるなどの方法があります。
AWS Batchで簡易に実現する方法としてJobDefinitionの制約にGPUを指定すると自動的にruntimeがnvidiaになります
しかしながら、この方法ではリソース制約としてGPU1つを要求するため、GPUが1つだけのマシンであれば1インスタンスで1ジョブしか動かせません。スクリプト内でリソースを使い切れるように並列処理を行うことになると思います。

        ResourceRequirements:
          - Type: GPU
            Value: 1

スポットインスタンス停止対策

スポットインスタンスを利用するのでインスタンスが強制停止される可能性があります。
強制停止される通知を受け取った後に途中結果をどこかに保存して、途中から再開できるような推論処理にするのが最も効率が良いです。
ただ今回は推論処理に手を入れたくなかったため、AWSバッチの配列ジョブにて処理を細かいジョブに分けた上で正常終了しなかったジョブはAWS Batchのリトライ機構でリトライさせます。

 配列ジョブ

AWS Batch側でジョブに数字を割り振り、スクリプトからはその数字を環境変数から読み込み、ジョブごとに別の処理をさせることができます。
細かくジョブを分けることでジョブ単位でリトライができるのと、ジョブ単位で並列処理ができます。
ジョブの粒度を小さくしすぎるとコンテナのイメージダウンロードやECS処理などのオーバーヘッドが大きくなるため、いい塩梅で設定してください。
https://docs.aws.amazon.com/ja_jp/batch/latest/userguide/array_jobs.html

pythonスクリプトからos.getenv('AWS_BATCH_JOB_ARRAY_INDEX')のような形で読めます

リトライ

念の為上限の10回リトライさせています

      RetryStrategy:
        Attempts: 10 # 1-10 allowed

aws batchの呼び出し

airflowからこんな感じのスクリプトでboto3を呼び出しています
arrayPropertiesとして配列ジョブのサイズを指定しています。

def _submit_job(*, target_ymd, array_size):
    batch = session(env).client('batch', region_name=region)
    response = batch.submit_job(
        jobName="",
        jobQueue="",
        jobDefinition="",
        arrayProperties={"size": array_size},
        containerOverrides={
            "command": [
                "python",
                "script.py",
                "--target_date",
                target_ymd,
            ],
            "environment": [
                {
                    "name": "STAGE",
                    "value": "test"
                }
            ]
        },
    )
    job_id = response['jobId']
    print(f'job has been sent. job id: {job_id}')
    return job_id


def _wait_job_finish(job_id):
    batch = session(env).client('batch', region_name=region)
    while True:
        response = batch.describe_jobs(jobs=[job_id])
        status = response['jobs'][0]['status']
        if status in ['SUCCEEDED']:
            return True
        elif status in ['FAILED']:
            return False
        else:
            sleep(10)

呼び出しのlambdaをserverlessから構築してる人もいるようです
https://blog.ikedaosushi.com/entry/2019/04/27/222957

region

作業用インスタンスはネットワークの遅延が小さい東京リージョンを使いがちだと思いますが、バッチジョブにおいては多少ネットワークが遅くても問題ないので最安リージョンで実行できます。
最安リージョンとしては北カリフォルニア以外のアメリカ3リージョン(バージニア、オハイオ、オレゴン)を抑えておけば良さそうです。
リージョン差は結構大きくて、例えばp2.xlargeにおいては0.90$/h(us-east-1)と1.542$/h(ap-northeast-1)と1.6倍ほどの開きがあります。

CloudFormation

CloudFormationにて環境構築をコード化します。下記を参考にしました

AWS CloudFormation のベストプラクティス
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/best-practices.html

【AWS】CloudFormationの実践的活用~個人的ベストプラクティス
https://qiita.com/tmiki/items/022df525defa82d1785b

CloudFormation Templateの分け方

今回はVPCから環境を構築しました。VPCは別アプリケーションでも使い回す想定で構築するので、AWS Batchのバッチジョブ層とはライフサイクルが異なります。また、ネットワーク周りの環境依存情報(VPC Peeringなど)はわかりやすいよう別にしておきたかったので中間層として切り出しました。合計3つのテンプレートを作ります。

Cloud Formation Template

VPC

2AZ, 3AZ, 4AZのテンプレートは下記サイトにあります。(一番上3つです)
https://templates.cloudonaut.io/en/stable/vpc/
NCALでは制限をかけず、SGでセキュリティを担保する思想のテンプレートなので、NCALを変えたければ改変する必要があります

VPCに関しては繰り返し処理が多くなりがちですのでいろいろいじるのであればcdkで記述する手もありそうです。
https://github.com/aws/aws-cdk

中間層

別リージョンの踏み台サーバからアクセスできるようにする想定で、環境依存の設定を切り出します。
セキュリティグループの設定とVPCピアリングの設定があります。
(ちなみにsshできるようにするのはデバッグ用で、AWS Batchで使う分にはIAMの設定だけで大丈夫です。)
別途踏み台サーバ側のルートテーブルとSG, NACLの設定も必要です

# this template is supposed to use after make vpc layer
---
AWSTemplateFormatVersion: 2010-09-09
Description: Middle Cloud Formation layer depending on environment specific value

Parameters:
  VpcStackName:
    Type: String
  BastionServerPrivateIP:
    Type: String
  BastionServerVpcId:
    Type: String
  BastionServerRegion:
    Type: String

Resources:
  #####
  # Security Group
  #####
  ComputeSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId:
        Fn::ImportValue: !Sub ${VpcStackName}-VPC
      GroupDescription: Enable SSH access via port 22 from bastion server
      SecurityGroupIngress:
        - CidrIp: !Sub ${BastionServerPrivateIP}/32
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-compute
  #####
  # VPC Peering
  #####
  VPCPeering:
    Type: AWS::EC2::VPCPeeringConnection
    Properties:
      PeerRegion: !Ref BastionServerRegion
      PeerVpcId: !Ref BastionServerVpcId
      VpcId:
        Fn::ImportValue: !Sub ${VpcStackName}-VPC
      Tags:
        -
          Key: Name
          Value: bastion peering
        -
          Key: Desc
          Value: vpc peering for access via bastion server
  RouteTablePublicInternetRouteA:
    Type: 'AWS::EC2::Route'
    Properties:
      RouteTableId:
        Fn::ImportValue: !Sub ${VpcStackName}-RouteTableAPublic
      DestinationCidrBlock: !Sub ${BastionServerPrivateIP}/32
      VpcPeeringConnectionId: !Ref VPCPeering
  RouteTablePublicInternetRouteB:
    Type: 'AWS::EC2::Route'
    Properties:
      RouteTableId:
        Fn::ImportValue: !Sub ${VpcStackName}-RouteTableBPublic
      DestinationCidrBlock: !Sub ${BastionServerPrivateIP}/32
      VpcPeeringConnectionId: !Ref VPCPeering
  RouteTablePublicInternetRouteC:
    Type: 'AWS::EC2::Route'
    Properties:
      RouteTableId:
        Fn::ImportValue: !Sub ${VpcStackName}-RouteTableCPublic
      DestinationCidrBlock: !Sub ${BastionServerPrivateIP}/32
      VpcPeeringConnectionId: !Ref VPCPeering
  RouteTablePublicInternetRouteD:
    Type: 'AWS::EC2::Route'
    Properties:
      RouteTableId:
        Fn::ImportValue: !Sub ${VpcStackName}-RouteTableDPublic
      DestinationCidrBlock: !Sub ${BastionServerPrivateIP}/32
      VpcPeeringConnectionId: !Ref VPCPeering

Outputs:
  ComputeSecurityGroup:
    Description: security group for general compute instance
    Value: !Ref ComputeSecurityGroup
    Export:
      Name: !Sub ${AWS::StackName}-ComputeSecurityGroup

バッチジョブ層

主にIAMの設定とAWS Batchの設定を書いています
IAMには
* AWS Batchの自動制御を許可するrole AWSBatchServiceRole
* ec2インスタンスにecsの自動制御を許可するrole ecsInstanceRole
* spot fleetの自動制御を許可するrole AmazonEC2SpotFleetTaggingRole
* ecs内のコンテナにawsサービスの利用を許可するrole JobRole
などを設定する必要があります。

AWS Batchの環境構築にはComputeEnvironment, JobDefinition, JobQueueの3層が必要なので設定します。
ECSの利用にECRのリポジトリが必要なのでついでに構築しています。開発環境と本番環境でアカウントが異なる場合でも、それぞれのアカウントにリポジトリを作成する想定です。

AWSTemplateFormatVersion: 2010-09-09
Description: Build AWS Batch environment

Parameters:
  VpcStackName:
    Type: String
  MiddleStackName:
    Type: String
  Ec2KeyPair:
    Type: String
  EcrPushArn:
    Type: String
  EcrRepositoryName:
    Type: String
  ECSAMI:
    Description: AMI ID
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ecs/optimized-ami/amazon-linux-2/gpu/recommended/image_id
  EnvType:
    Description: Environment type.
    Default: test
    Type: String
    AllowedValues:
      - prod
      - test
    ConstraintDescription: must specify prod or test.
Conditions:
  IsProd: !Equals [ !Ref EnvType, prod ]

Resources:
  #####
  # IAM
  #####
  AWSBatchServiceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - batch.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSBatchServiceRole
      Path: "/service-role/"
  ecsInstanceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role
  ecsInstanceProfile:
    Type: "AWS::IAM::InstanceProfile"
    Properties:
      Roles:
        - !Ref ecsInstanceRole
  AmazonEC2SpotFleetTaggingRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - spotfleet.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonEC2SpotFleetTaggingRole
      Path: "/service-role/"
  JobRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ecs-tasks.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        # set policy your program need
        - arn:aws:iam::aws:policy/AmazonS3FullAccess
        - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess

  #####
  # ECR
  #####
  ECR:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: !Ref EcrRepositoryName
      RepositoryPolicyText:
        Version: "2012-10-17"
        Statement:
          - Sid: AllowPushPull
            Effect: Allow
            Principal:
              AWS:
                - !Ref EcrPushArn
            Action:
              - "ecr:GetDownloadUrlForLayer"
              - "ecr:BatchGetImage"
              - "ecr:BatchCheckLayerAvailability"
              - "ecr:PutImage"
              - "ecr:InitiateLayerUpload"
              - "ecr:UploadLayerPart"
              - "ecr:CompleteLayerUpload"

  #####
  # AWS Batch
  #####
  ComputeEnv:
    Type: AWS::Batch::ComputeEnvironment
    Properties:
      # do not specify ComputeEnvironmentName to allow resource update
      Type: MANAGED
      ServiceRole: !GetAtt AWSBatchServiceRole.Arn
      ComputeResources:
        Tags:
          Name: Spot Fleet Managed
          auto_stop: "false"
        Type: SPOT
        MaxvCpus: !If [IsProd, 8, 4] # 本番環境でのインスタンス数*4
        MinvCpus: 0
        InstanceTypes:
          - p2.xlarge
        SecurityGroupIds:
          - Fn::ImportValue: !Sub ${MiddleStackName}-ComputeSecurityGroup
        Subnets:
          Fn::Split:
            - ","
            - Fn::ImportValue: !Sub ${VpcStackName}-SubnetsPublic
        ImageId: !Ref ECSAMI
        Ec2KeyPair: !Ref Ec2KeyPair
        InstanceRole: !GetAtt ecsInstanceProfile.Arn
        SpotIamFleetRole: !GetAtt AmazonEC2SpotFleetTaggingRole.Arn
        BidPercentage: 51
      State: ENABLED
  JobDef:
    Type: AWS::Batch::JobDefinition
    Properties:
      JobDefinitionName: !Sub ${AWS::StackName}-jobdef
      Type: container
      ContainerProperties:
        Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EcrRepositoryName}:latest
        Vcpus: 4
        Memory: 50000 # MiB
        JobRoleArn: !GetAtt JobRole.Arn
        Privileged: True
        ResourceRequirements:
          - Type: GPU
            Value: 1
      RetryStrategy:
        Attempts: 10 # 1-10 allowed
      Timeout:
        AttemptDurationSeconds: 86400 # 24h
  JobQueue:
    Type: AWS::Batch::JobQueue
    Properties:
      JobQueueName: !Sub ${AWS::StackName}-jobqueue
      ComputeEnvironmentOrder:
        - ComputeEnvironment: !Ref ComputeEnv
          Order: 0
      Priority: 100
      State: ENABLED

CloudFormation起動スクリプト

こんな感じのスクリプトから呼び出します

#!/bin/bash

if [ $# -lt 2 ]; then
  echo 'two argument required. environment, input command'
  exit
fi

env=${1}
if [ ${env} = 'test' ]; then
  profile=ここらへん入れてください
  region=
  vpc_stack_name=
  middle_stack_name=
  application_stack_name=
  bastion_private_ip=
  bastion_vpc_id=
  key_pair_name=
elif [ ${env} = 'prod' ]; then
# 略
else
  echo 'test or prod is required.' >&2
  exit 1
fi


function all () {
  create_key_pair
  create_vpc
  create_middle
  create_batch_env
}

function create_key_pair () {
  # Key pair is created to project root. Move it to suitable place.
  aws ec2 create-key-pair \
    --profile ${profile} \
    --region ${region} \
    --key-name ${key_pair_name} \
    --query 'KeyMaterial' \
    --output text > ${key_pair_name}.pem
}

function create_vpc () {
  aws cloudformation deploy \
    --profile ${profile} \
    --region ${region} \
    --template-file template/networking/vpc-4az.yml \
    --stack-name ${vpc_stack_name} \
    --capabilities CAPABILITY_IAM \
    --parameter-overrides \
      ClassB=0−255までの数字でVPCのIPアドレス指定に使われる \
  ;
}

function create_middle () {
  aws cloudformation deploy \
    --profile ${profile} \
    --region ${region} \
    --template-file template/networking/env_specific.yml \
    --stack-name ${middle_stack_name} \
    --capabilities CAPABILITY_IAM \
    --parameter-overrides \
      VpcStackName=${vpc_stack_name} \
      BastionServerPrivateIP=${bastion_private_ip} \
      BastionServerVpcId=${bastion_vpc_id} \
      BastionServerRegion=ap-northeast-1 \
  ;
}

function create_batch_env () {
  aws cloudformation deploy \
    --profile ${profile} \
    --region ${region} \
    --template-file テンプレート.yml \
    --stack-name ${application_stack_name} \
    --capabilities CAPABILITY_IAM \
    --parameter-overrides \
      VpcStackName=${vpc_stack_name} \
      MiddleStackName=${middle_stack_name} \
      Ec2KeyPair=${key_pair_name} \
      EcrRepositoryName=名前 \
      EcrPushArn=AWSのARN \
      EnvType=${env} \
  ;
}

# execute input command
${2}

まとめ

スポットインスタンスの利用により6-7割引ほどの恩恵を受けられています。コストを削減すればその分を更にたくさんのマシンリソースに充てられます。積極的に使っていきましょう。

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

DB 高負荷時に数秒でサクッと原因を特定する (aws RDS)

概要

なんかよくわからないけど DB の負荷が高くなったどー
という監視アラートが出たとき、あなたはどうしますか?

そういうときに何が原因で負荷が高くなっているかサクッと追いたいですね。
CPU の状態や、スワップの状態、コネクション数から調べることもできますが、これだけでは何が原因で重くなったのかはっきりしないことは多いです。

対象読者

  • aws が何なのか知っている人、運用している人 (aws は食べ物ではない)
  • RDS で DB 管理していること
  • スロークエリログを cloudwatch もしくは DB に保存できる環境であること

準備

DB 側

数秒でサクッと原因を特定できるようにするためには、事前に準備が必要です

まずは RDS のパラメータグループで以下の設定を変更してください (デフォルトのパラメータグループでは変更できないようです)

  • slow_query_log: 1
  • long_query_time: 0.1 ~ (単位 秒)
  • log_output: FILE (TABLE でもよいですが、その場合の方法は今回は割愛)

次に、RDS 自体の設定で [Modify] を設定し、[Slow query log] にチェックをいれます

そうすることで RDS の Configuration に Slow query という cloudwatch へのリンクができます。

スクリーンショット 2019-12-05 9.06.35.png

リンク先を開くと、キャプチャの ① が log-group-name で ② が log-stream-name です。メモしておきましょう。

スクリーンショット 2019-12-05 9.08.28.png

集計側 (local など)

セットアップ

mac の場合 homebrew 導入済みの前提で記載しますと、以下のように percona-toolkit を導入します

$ brew install percona-toolkit

jq コマンドも導入しておきます

$ brew install jq

mac の場合は、以下のように aws-cli を導入します

$ brew install awscli
$ aws configure // aws の設定を行う

スクリプト配置

ということで、 sloq-query-summary.sh というスクリプトで中身を以下のようにします
pt-query-digest のオプションを指定すれば、explain も出したり、余計なクエリをフィルタしたりできますが、ここでは割愛します

LOG_GROUP には先程メモした log-group-name を、 LOG_STREAM には先程メモした log-stream-name をいれます

#!/bin/bash -u

LOG_GROUP=hogehoge
LOG_STREAM=fugafuga

SCRIPT_NAME=`basename $0`
if [ $# -ne 2 ]; then
  echo "指定された引数は$#個です。" 1>&2
  echo "実行するには2個の引数が必要です。" 1>&2
  echo "Usage: $SCRIPT_NAME "\""2019-12-01 12:00:00"\"" "\""2019-12-01 13:00:00"\" 1>&2
  exit 1
fi

start="$1"
end="$2"

echo start datetime iso-8601..
echo $(TZ=JST-9 date --iso-8601="seconds" --date "$start");
echo end datetime iso-8601..
echo $(TZ=JST-9 date --iso-8601="seconds" --date "$end");

starttime=$(expr `TZ=JST-9 date --date "$start" +%s` \* 1000)
endtime=$(expr `TZ=JST-9 date --date "$end" +%s` \* 1000)

aws logs get-log-events \
  --log-group-name ${LOG_GROUP} \
  --log-stream-name ${LOG_STREAM} \
  --start-time ${starttime} \
  --end-time ${endtime} \
  --query 'events[].message' \
  |jq -r '.[]' \
  | pt-query-digest --limit 10000

最後に実行権限をつけます

$ chmod +x ./sloq-query-summary.sh

重くなったなというときに、以下のようにスクリプトを実行すれば、簡単にスロークエリログのサマリが表示されるようになります。

./sloq-query-summary.sh  "2019-12-01 12:00:00" "2019-12-01 13:00:00"

※キャプチャはサマリの部分だけですが、実際のスロークエリログもでてきます

スクリーンショット 2019-12-05 9.20.46.png

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

今日からAmazon Rekognition Custom Labelsで転移学習できるうようになった!

これは、機械学習ツールを掘り下げる by 日経 xTECH ビジネスAI③ Advent Calendar 2019の記事です。12月後半に記事を書こうかなと思ったのですが、12月前半に記事を書いている方が少なそうでしたので、前倒しで書いてみました。本記事では、私のオススメ機械学習ツールとして、先週くらいにアマゾン ウェブ サービス (AWS) から発表があったAmazon Rekognition Custom Labelsを取り上げたいと思います。

参考:日経xTech『画像認識AIの独自開発が容易に、AWSが新サービス「Rekognition Custom Labels」提供へ』

発表があったのは11月25日、使えるようになったのは12月3日(日本時間では12月4日)でしたので、できたてほやほやサービスです。と、いいつつ、本記事は「今日から~」というタイトルにしたのですが、試しているうちに翌日(日本時間12月5日)になってしまいました。まだ日本リージョンでは使えないためバージニア北部リージョンで動かしてみました。そもそもこの機能は日本語設定だと出てこないので、AWSを英語設定にする必要もあります。

サーバレスで画像認識機能を使えるRekogniton!

サーバレスで画像認識を作るのがとても楽なので、従来からRekognitionは気に入っていました。

例えば、LINEで画像メッセージを送ったら、その画像に映っているものが何であるかを判定するようなアプリを作ってみました。次の画像のように、ピザの絵を送ったら、Food:94%やPizza85%といった結果をLINEメッセージで返します。
qiita-square

このようなアプリは、Rekognition含むAWSサーバレスを駆使したら、意外と簡単に作ることができます。無料版のLINE@ Messaging APIを用意してWebhookURLをAWS API Gatewayに設定し、そこで受け取ったRESTリクエストをLambdaで処理するだけです。画像データを一旦S3において、Rekognitionで解析したら、その結果をLambdaからLINE Messaging APIに送るという仕組みです。

system.png

でも、従来のRekognitionに不満もあった

従来のRekognitionは、AWSが自前で用意した画像をもとに作られた機械学習モデルです。ですから、AWSが使った学習データの教師ラベルに含まれていないものは、認識できません。海外のベンダーが作っている画像認識モデルですから、日本人感覚からすると、意外だなと思うものが認識できなかったりします。例えば・・・

キャプチャ.PNG

日本人感覚では、絶対に間違えないであろうクワガタの画像をAWS Rekognitionで解析しても、まずクワガタ(Stag Beetle)とは認識してくれません。たいてい、フンコロガシ(Dung Beetle)と認識されます。USでは、クワガタよりもフンコロガシの方が一般的なのかな・・・(そういえば、ファーブル昆虫記でもフンコロガシが取り上げられていたなぁ)・・・これが、Rekognitionに対する長年の不満でした。こんなに特徴的なカタチをしている分かりやすい昆虫なんですけどね。

Rekognitionでクワガタを認識させたい!

まずは、トレーニング(学習)!

Amazon Rekognition Custom Labelsは、ユーザーが自前の教師データを少量用意すれば、独自の画像認識AIを開発できるというAWSの新しいサービスです。つまり、転移学習ができるようになったわけですね。これを使って、クワガタの画像データを何枚か投入すれば、Amazon Rekognitionでクワガタを判別できるようになるのでは・・・と考えました。
ついに昨日からサービスが使えるようになったので、すぐに試してみました。

まず、学習用の画像データはgoogle検索でクワガタ10枚とカブトムシ10枚くらいを探して準備します。AWS Rekognitionをバージニア北部リージョンで、かつ英語版で開くと、Custome Labelsという今までなかった新機能があるのがわかります。

キャプチャ.PNG

Rekognition Custome Labelsの新しいプロジェクトを作ります。

キャプチャ.PNG

画像をどんどん追加していきます。
キャプチャ.PNG

そして、1匹1匹にラベルと境界線を設定してきます。(意外と面倒・・・もう心が折れてきた・・・)

キャプチャ.PNG

さぁ、学習データが揃ったので、トレーニング(機械学習)してみます!全データ(画像20枚)のうち、20%を検証用データとすることにしました。
キャプチャ.PNG

ここでしばらく待っていると・・・トレーニング完了です。F値は、0.345・・・かなり低いですね。画像データが少なすぎるのか?クワガタとカブトムシが似すぎていて判別がつかないのか?など、色々と思うところはありましたが、今回はお試しなので、そのまま進めます。

性能検証

キャプチャ.PNG

もう少しブレイクダウンして性能を見てみると、カブトムシの精度(Precision)は1.00でクワガタの精度(Presicion)は0.01でした。この数値だけ見ると、カブトムシと認識したものは正確にカブトムシであることを判別できている素晴らしい数値(精度1.0)ともいえなくもないですが、再現率が0.50であるところを見ると、ほとんど全ての検証データをカブトムシと判定してしまっている・・・つまり、あまり意味のない判定機になってしまっているということだと思います。

キャプチャ.PNG

実際にクワガタの画像を解析してみると、次のような感じです。まず、AWS Rekognitionの元からあるモデルで物体認識して領域を抽出してくれます。それぞれの領域に対して、画像分類を実施してくれるみたいです。この画像の中だと、最もクワガタらしいハサミの部分を緑色の領域で「クワガタ」と認識してくれているようです。まぁ、これだけでも今日は満足です。

キャプチャ.PNG

おわりに

性能(精度と再現率)を上げていくためには、当然のことながら、学習データを工夫していく必要がありそうです。今回、Amazon Rekognition Custom Labelsを触ってみて、自分用の画像判定AIを、物凄く楽に作れることが分かりました。Rekognitionですから、もちろんAWS Lambdaから簡単に呼び出せますので、サーバレスでの活用も簡単です。つまり、画像判定AIを作る際に、『学習データの準備だけ』に集中できるようになったということだと思います。凄いな。これから、性能を上げていくための学習データの在り方などを、工夫していきたいなと考えています。

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

AWS re:Invent 2019でワークショップに参加してきた

本記事は、サムザップ Advent Calendar 2019 #1 の12/5の記事です。

はじめに

2019/12/1〜12/6の間、ラスベガスで開催されていたAWS re:Invent2019に参加してきました。
この記事では参加したセッションの中から、クラウド環境でのオペレーションの自動化に関するワークショップ「Intelligently automating cloud operations」の紹介をします。ワークショップというのは一般的な講義形式のセッションとは異なり、自分で手を動かして学習するセッションのことです。

ワークショップについて

  • 各テーブル6〜7名、x 20テーブルくらい
  • ワークショップ全体の時間は2〜3時間

  • 用意された復数の課題を様々なAWSサービスを使って解決していく

    • 各課題に取り組む前に10分程度の講義がある
    • 講義後に共有されるドキュメントに従って作業を進めていく
  • ワークショップで使うAWSアカウントは運営側で用意してくれた

  • 作業用のノートPCは各自持ち込み

  • 作業中の不明点は会場にいる運営メンバーか、同じテーブルの人に相談する

  • お互いの詰まったところを助け合う以外に複数人での作業はなく、ほぼ個人作業

参加するワークショップによっては自分でAWSアカウントを用意する必要があったり、
個人ではなくチームで課題に取り組むなどの違いがあるようです。

ワークショップでやったこと

用意された以下3つの課題を進めました。

AWS HEALTH ABUSE EVENTS

管理しているEC2インスタンスがDoS攻撃を受けているシーンを想定。
AWS Personal Health Dashboardに届いたインシデント報告をCloudWatch Eventsで検出し、Lambdaを使ってSNS経由でSMSを送信しつつ、攻撃を受けているEC2インスタンスの停止アクションの自動実行を実装しました。

Automatic Region Failover

マルチリージョン(us-east-1,2)で動いているシステム。各リージョンにNLBがあり、Webサーバが復数台ぶらさっている構成で片方のリージョン(us-east-1)のトラフィックパターンに異常が発生したシーンを想定。

クロスリージョンフェイルオーバーのオペレーション自動化はこんな感じの流れでした。

  • CloudWatchのCanaryResponseTimeメトリックでAnomaly Detectionを有効にする
    • CloudWatchアラームを作成し、事前に作成済のSNSをターゲットに指定する
  • lambdaにクロスリージョンフェイルオーバーさせる関数を追記する
  • 意図的にトラフィック異常を発生させるためのSSMドキュメントを作成する
    • tcコマンドでレイテンシ遅延を発生させる処理が書かれていた
  • SSMのRunコマンドでus-east-1のインスタンスにレイテンシ遅延を発生させる
    • しばらく経過するとCloudWatchがアラーム状態に変化する
  • Lambdaが作動して、トラフィックがus-east-1からus-east-2に切り替わる

このシナリオではNLB + Webサーバのシンプルな構成だったけど、データストアを含む構成だと実環境に近くてもっと面白そうです。あとは地味にtcコマンドが便利なので社内の障害試験で今後使っていきたいです。

AUTOMATED EBS USAGE FORECAST AND LIMIT INCREASES

AWSアカウント内にあるすべてのEBSボリューム数を自動集計し、AmazonForecastでデータセットを作って、今後の使用量を予測する。更にEBSの制限緩和も自動化する。みたいなシナリオで面白そうだったんですが、ここまでの2つの課題で結構時間を使ってしまって最後まで進められませんでした...

最後に

DoS攻撃を受けたときやリージョン障害が起きたときに限らず、何か問題が発生したときに行う作業が決まっているのであれば、オペミス防止や復旧時間を短縮するためにも自動化すべきだと思いました。サムザップでも今後更に自動化を進めていきます。

明日は @leosuke さんの記事です。

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

Athenaで億単位のレコード数のテーブル同士をJOINしてみる

これは何?

Athenaに思いのほかいろんな関数が実装されていた
のおまけ記事です。

前記事ではいろんな関数を使うことでクエリ1発でいろんな集計ができるかもしれないと書いたのですが、そのためにはデータ量の多い複数のテーブルを繋げるような処理が必要になることが考えられます。
普通に手元のMySQLでそういったクエリを投げると重すぎて最悪結果が返ってこなくなることもあるので、Athenaだとちゃんと現実的な時間で結果が返ってくるのか試して見ました。

問題設定

  • キャラクターをひたすら売却するシミュレーション
  • キャラクターは25種類いて、レア度(rariry)は[1,2,3,4,5]の5通り、売却価格は10^(rarity)
  • プレイヤーが1000人いて、それぞれがキャラクターを1000人持っている
  • 毎日ランダムに100000回プレイヤーを選び、手持ちのレア度[1,2,3,4,5]各一人をランダムに選んで売却し、同じレア度のキャラクターをランダムに選んで補充

これを数千日分シミュレーションし、日毎の「新たに生成されたキャラクター」と「プレイヤーが売却したキャラクター」のログをS3に上げていきます。十分ログが溜まったら合計の売却価格やプレイヤーごとの合計の売却価格を計算します。

データ形式

  • キャラクターマスタ(master_character)

25行です。

{"id":1,"rarity":5,"price":100000}
  • キャラクター生成ログ(character_log)

一日分は500000行です。

{"id":2000001,"master_character_id":1}
  • 売却ログ(sell_log)

一日分は100000行です。

{"player_id":533,"sell_character_ids":[1999466,1669252,1965108,1592234,532614]}

ポイント

  • 売却ログ(sell_log)からキャラクターの売却価格を知るためには、キャラクター生成ログ(character_log)を通してmaster_character_idを得て、そこからキャラクターマスタ(master_character)を引かなければならない

  • レア度[1,2,3,4,5]のキャラクターをそれぞれ売却するので、一人が一回で行う売却価格は必ず111110になる。後の集計で111110の倍数でない数字が出たら何かが間違っている。

  • 生成されてすぐに売却されたキャラクターは日付的に近いところに対応するログがあるが、生成された後でなかなか選ばれなかったデータはその分日付的に離れたところに対応するログがある。

実装

適当に実装します。せっかくなので業務では絶対に書かない闇の1行コードを書いたりしてみましたが地獄でした。
https://github.com/random25umezawa/athena_dataset

実装できたらEC2に乗せ、一日分シミュレーションを進めてログをgzip圧縮してS3に上げるパッチを数千回呼び出しました。

S3に上げる段階で/log_date=YYYY-MM-DD/というパスを含んで保存しているので、log_dateパーテーションを含むAthenaテーブル作成後にMSCK REPAIR TABL table_nameしてパーテーションの設定を済ませます。

出来上がったデータ

数時間シミュレーションを回し続けたところ、以下のようなデータが出来上がりました。

  • 圧縮後約20GBのログ(解凍すると10倍以上になる)

athena_big3.png

  • それぞれ約30億,約6億行のテーブル(sell_logは約6億行だが、長さ5の配列の展開をするので5倍になる)

athena_big4.png

  • 2019-01-01スタートで2035-06-06までのデータ(約6000日分)

athenatest_datemax.png

投げるクエリ

事前に説明したとおり、sell_logにcharacter_logとmaster_characterをJOINします。

WITHの中でパーテーションの指定をすることで検索範囲を絞ります。最初は1日分のみで試し、可能そうなら徐々に範囲を拡げてみます。

WITH sell_log2 AS (
  SELECT
    *
  FROM
    sell_log
  WHERE
    log_date >= '2019-01-01'
  AND
    log_date <= '2019-01-01'
),
character_log2 AS (
  SELECT
    *
  FROM
    character_log
  WHERE
    log_date >= '2018-12-31'
  AND
    log_date <= '2019-01-01'
)

SELECT
  SUM(price) AS price_sum
FROM
  sell_log2
CROSS JOIN UNNEST (
  sell_character_ids
) as _ (sell_character_id)
INNER JOIN 
  character_log2
ON
  sell_character_id = character_log2.id
INNER JOIN 
  master_character
ON
  master_character_id = master_character.id

結果

期間 日数 合計値
~ 2019-01-01 1 11111000000 (Run time: 5.92 seconds, Data scanned: 6.93 MB)
~ 2019-01-10 10 111110000000 (Run time: 7.85 seconds, Data scanned: 39.28 MB)
~ 2019-03-31 90 999990000000 (Run time: 8.2 seconds, Data scanned: 330.22 MB)
~ 2021-06-30 912 10133232000000 (Run time: 20.22 seconds, Data scanned: 3.3 GB)
~ 2035-06-06 6001 66677111000000 (Run time: 1 minute 20 seconds, Data scanned: 21.92 GB)

1分程度で終わってしまった...。

蛇足

プレイヤーごとの集計もうまくできてそうか見てみます。
売却の合計値は111110の倍数のはずなので、結果のテーブルにさらにMOD(price_sum, 111110)を計算させると全て0になるはずです。

WITH sell_log2 AS (
  SELECT
    *
  FROM
    sell_log
  WHERE
    log_date >= '2019-01-01'
  AND
    log_date <= '2035-06-06'
),
character_log2 AS (
  SELECT
    *
  FROM
    character_log
  WHERE
    log_date >= '2018-12-31'
  AND
    log_date <= '2035-06-06'
),
result as (
  SELECT
    player_id,
    SUM(price) AS price_sum
  FROM
    sell_log2
  CROSS JOIN UNNEST (
    sell_character_ids
  ) as _ (sell_character_id)
  INNER JOIN 
    character_log2
  ON
    sell_character_id = character_log2.id
  INNER JOIN 
    master_character
  ON
    master_character_id = master_character.id
  GROUP BY
    player_id
)

SELECT
  player_id,
  price_sum,
  MOD(price_sum, 111110)
FROM
  result

athena_big6.png

集計できていそうです。

まとめ・感想

数分程度で結果が返ってきたうえに、結果も正しそうに見えます。

実際に別のデータでやる場合は、少ないデータ量で結果の整合性・処理時間などをテストした上で投げましょう。

さいごに

初心者なので何か間違いや注意点などあればご指摘ください。

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

AWSコストエクスプローラAPIと気軽につきあう(2019)

この記事ははてなエンジニアAdvent Calendar 2019の5日目のエントリーです。

はじめに

AWSのコストエクスプローラのデータは12ヶ月しか保存されないので、なるべく外部で保存したいですよね。幸い、コストエクスプローラのデータにアクセスするためのAPIがちゃんと提供されているので、気軽に利用することができます。特に難しいこともないのですが、これから「よーしAWS費用の集計をシステム化しちゃうぞー」という人のために、かんたんに足場を作るための手順を整理しておくことにします。

API更新時の説明において以下のように言及されている通り、コンソール経由よりも詳細なデータアクセスが可能なので、ぜひ面倒くさがらずコスト管理のパイプライン構築を進めていきましょう。

グループ化 – コストエクスプローラーウェブアプリケーションで提供されるグループ化は 1 レベルですが、API では 2 レベルが提供されます。たとえば、まずサービス別にコストまたは RI 使用率をグループ化してから、リージョン別にグループ化できます。

この記事で取り上げているのは2019/12現在の情報であり、APi仕様は変更される可能性があります。また気軽ではありますがコストエクスプローラAPIは呼び出し単価高いAPI(リクエストあたり 0.01USD)であったりするのでご注意ください。

さて。

Cloud9 を起動しましょう

もちろん Cloud9 を使います。そして Typescript です。

特に理由がなければ Amazon Linux で立てるといいでしょう。今回は CostExplorerApiDev という名前で立ち上げました。

cloud9.png

起動したら、下部ターミナルから npm のライブラリをセットアップします。

npm init -y
npm install -D typescript ts-node aws-sdk @types/node prettier
npx tsc --init

typescript の設定を開き、Format on Save は有効にしておきます。

スクリーンショット 2019-12-05 2.11.03.png

tsconfig は以下のようにしている。最低限。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2018",
    "module": "commonjs",
    "lib": ["es2018"],
    "strict": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true
  }
}

さて、 vi index.ts して、動作確認をします。

index.ts
console.log("hello typescript");

npx ts-node index.ts してちゃんと実行されることを確認したら、次に進みましょう。

CostExplorer APIを利用する

普段見ているコストエクスプローラのレポート画面に相当するデータ取得は、GetCostAndUsage 呼び出しによって行うことができます。公式の情報は薄めなので、必要に応じてAPI情報をサードパーティから参照します。

ここでは、以下のようなデータがほしいとしましょう。

  • 月次で
  • 2019/06- 2019/11 の範囲で
  • 償却されたコスト(AMORTIZED COST)を
  • クレジット(Credit), 払い戻し(Refund)、税金(Tax)、前払い金(Upfront)に関するレコードを除外1
  • 連結アカウントごとにグループ化する

これは自分が普段利用しているコストエクスプローラのフィルタ条件を整理したものです。

キャッシュフロー上の実際の支払い金額が知りたいのではなく、発生ベースでその月に使用したアカウントごとの費用が知りたいとしましょう。クレジットや払い戻しによる費用の上下、各種の事前チャージのお金の動きは除外したいものとします。簡単のためサンプルでは console に出力しているだけですが、AWSのクレデンシャルがありSDKからAPI叩けているなら、結果を S3 に置くとか SNS に投げるのにとくべつな工夫は必要ないかと思います。

get_cost.ts
import * as AWS from "aws-sdk";

// Cost Explorer は us-east-1 指定が必要
const costexplorer = new AWS.CostExplorer({ region: "us-east-1" });

const params = {
  TimePeriod: {
    Start: "2019-06-01",
    End: "2019-11-01"
  },
  Filter: {
    Not: {
      Dimensions: {
        Key: "RECORD_TYPE",
        Values: ["Credit", "Refund", "Tax", "Upfront"]
      }
    }
  },
  Granularity: "MONTHLY",
  Metrics: ["AMORTIZED_COST"],
  GroupBy: [{ Type: "DIMENSION", Key: "LINKED_ACCOUNT" }]
};

costexplorer.getCostAndUsage(params, (err, data) => {
  if (err) {
    console.error(err);
  } else {
    console.log(data);
  }
});

これを実行します。うまくデータが取得できれば以下のようにコンソールに出力されると思います。なお今回は簡単のためにページングは考慮していません。結果オブジェクトに NextPageToken が含まれていたら後続を読み込む処理が必要になります。

ma2saka:~/environment $ npx ts-node get_cost.ts
{ GroupDefinitions: [ { Type: 'DIMENSION', Key: 'LINKED_ACCOUNT' } ],
  ResultsByTime:
   [ { TimePeriod: [Object],
       Total: {},
       Groups: [Array],
       Estimated: false },
     { TimePeriod: [Object],
       Total: {},
       Groups: [Array],
       Estimated: false },
     { TimePeriod: [Object],
       Total: {},
       Groups: [Array],
       Estimated: false },
     { TimePeriod: [Object],
       Total: {},
       Groups: [Array],
       Estimated: false },
     { TimePeriod: [Object],
       Total: {},
       Groups: [Array],
       Estimated: false } ] }

中身を実際に見たければ console.log(JSON.stringify(data)); とかすると良いです。ここでは事情により2割愛します。

ResultsByTime に月ごとのデータが配列として格納されています。ResultsByTime[].TimePeriodStart,End が入ってきます。MONTHLYなので、Startが対象月の初日(もしくは指定した開始日)、Endが対象月の翌月初日(もしくは指定した終了日)になります。

ResultsByTime[].Estimated は推定値が集計範囲に含まれていることを表します。今回のような集計をする場合、まだ締まっていない月のデータが Estimated: true になると思えばよさそうです。

ResultsByTime[].Groups にその集計期間におけるアカウントごとの集計値が並びます。ここでは税金や前払金などを除いたデータを月次で集計した値が得られます。

今回は償却コスト(Amotized Cost)をメトリクスとして集計対象にしました。これによってRIの費用などが月々に償却されて乗ります。指定できるのはこの他に、

BLENDED_COST, UNBLENDED_COST, AMORTIZED_COST, NET_UNBLENDED_COST, NET_AMORTIZED_COST, USAGE_QUANTITY, NORMALIZED_USAGE_AMOUNT

などがありますね。 https://docs.aws.amazon.com/ja_jp/awsaccountbilling/latest/aboutv2/ce-advanced.html などでなんとなく解説されている。3メトリクスは同時に複数指定できます。メトリクス名は Screaming snake case でも Upper Camel Case でも有効みたいだけど、どっちが正なんだろう。

グループ化のタイプとして DIMENSION を、キーとして LINKED_ACCOUNT を指定しています。連結アカウント単位です。タイプを DIMENSION とした場合、値としては以下のようなものが指定できます。

AZ, INSTANCE_TYPE, LINKED_ACCOUNT, OPERATION, PURCHASE_TYPE, REGION, SERVICE, USAGE_TYPE, USAGE_TYPE_GROUP, RECORD_TYPE, OPERATING_SYSTEM, TENANCY, SCOPE, PLATFORM, SUBSCRIPTION_ID, LEGAL_ENTITY_NAME, DEPLOYMENT_OPTION, DATABASE_ENGINE, CACHE_ENGINE, INSTANCE_TYPE_FAMILY, BILLING_ENTITY, RESERVATION_ID

なお、GetCostAndUsage API 経由ではアカウント情報はIDまでしか取れません。アカウントの一覧を取得して付き合わせるには一度 Organizations#listAccounts API を呼び出して辞書を作っておくのが楽だと思います。

グループ化のタイプには TAG を指定することもできます。その場合にはキーとしてリソースタグのキー名を指定します。日本語で書くとちょっとわかりづらいところですが、クエリ書いてみればまあわかる。

以下は、「ある特定のアカウントID」に対し、「OwnerTeam」というリソースタグの値で月額費用を集計します。変更点は Filter 条件と GroupBy の指定です。

awesome.ts
const params = {
    TimePeriod: {
        Start: '2019-06-01',
        End: '2019-11-01',
    },
    Filter: {
        And: [{
            Dimensions: {
                Key: "LINKED_ACCOUNT",
                Values: ["特定のアカウントID"]
            }
        }, {
            Not: {
                Dimensions: {
                    Key: "RECORD_TYPE",
                    Values: ["Credit", "Refund", "Tax", "Upfront"]
                }
            }
        }, ]
    },
    Granularity: "MONTHLY",
    Metrics: ["AMORTIZED_COST"],
    GroupBy: [{ Type: "TAG", Key: "OwnerTeam" }]
}

Filter はちょっと試行錯誤が必要かもしれませんが、そんなに複雑なものでないのですぐ慣れます。Expressionを適宜参照してください。

Lambda などで動かす場合、ce:Get* を許可する

ce:Get* を許してあげましょう。公式のサンプルでは ce:* を許可していますが、権限は狭いに越したことはありません。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "ce:Get*",
            "Resource": "*"
        }
    ]
}

まとめ

Cloud9 は実行環境がついたターミナルとしか使ってなくて、実はぜんぶ vim で書いてました。すみません。Typescript ではあるんですが、Javascript と書いたコードは区別がつきませんでした。これも、そういうもんですね。

6日目は @ne_sachirou さんです。よろしくお願いします。


  1. 裏側のデータ構造をある程度想像した上で集計処理を想像する必要があります。どのアカウントの、どんなタグが付与されたどんなリソースを何分/何GB利用したのか、といった履歴テーブルに集計クエリを打つのだと考えるとわかりやすいです。 

  2. 会社のアカウントのデータ出すわけにもいきませんが、個人アカウントのデータは少なすぎて集計のしがいがなかった。 

  3. 日本語訳で読むと苦しい気持ちになる。純償却コスト、純非ブレンドコストって言われてもエッてなります。設定値との対応関係が読み取りづらいのでドキュメントは大人しく英語で読みましょう。 

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

AWSコストエクスプローラーAPIと気軽につきあう(2019)

この記事ははてなエンジニアAdvent Calendar 2019の5日目のエントリーです。

はじめに

AWSのコストエクスプローラのデータは12ヶ月しか保存されないので、なるべく外部で保存したいですよね。幸い、コストエクスプローラのデータにアクセスするためのAPIがちゃんと提供されているので、気軽に利用することができます。特に難しいこともないのですが、これから「よーしAWS費用の集計をシステム化しちゃうぞー」という人のために、かんたんに足場を作るための手順を整理しておくことにします。

API更新時の説明において以下のように言及されている通り、コンソール経由よりも詳細なデータアクセスが可能なので、ぜひ面倒くさがらずコスト管理のパイプライン構築を進めていきましょう。

グループ化 – コストエクスプローラーウェブアプリケーションで提供されるグループ化は 1 レベルですが、API では 2 レベルが提供されます。たとえば、まずサービス別にコストまたは RI 使用率をグループ化してから、リージョン別にグループ化できます。

この記事で取り上げているのは2019/12現在の情報であり、API仕様は変更される可能性があります。また気軽ではありますがコストエクスプローラAPIは呼び出し単価高いAPI(リクエストあたり 0.01USD)であったりするのでご注意ください。

さて。

Cloud9 を起動しましょう

もちろん Cloud9 を使います。そして Typescript です。

特に理由がなければ Amazon Linux で立てるといいでしょう。気をつけるべきなのは、パブリックサブネットで立てるということです。今回は CostExplorerApiDev という名前で立ち上げました。

cloud9.png

起動したら、下部ターミナルから npm のライブラリをセットアップします。

npm init -y
npm install -D typescript ts-node aws-sdk @types/node prettier
npx tsc --init

typescript の設定を開き、Format on Save は有効にしておきます。

スクリーンショット 2019-12-05 2.11.03.png

tsconfig は以下のようにしている。最低限。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2018",
    "module": "commonjs",
    "lib": ["es2018"],
    "strict": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true
  }
}

さて、 vi index.ts して、動作確認をします。

index.ts
console.log("hello typescript");

npx ts-node index.ts してちゃんと実行されることを確認したら、次に進みましょう。

CostExplorer APIを利用する

普段見ているコストエクスプローラのレポート画面に相当するデータ取得は、GetCostAndUsage 呼び出しによって行うことができます。公式の情報は薄めなので、必要に応じてAPI情報をサードパーティから参照します。

ここでは、以下のようなデータがほしいとしましょう。

  • 月次で
  • 2019/06- 2019/11 の範囲で
  • 償却されたコスト(AMORTIZED COST)を
  • クレジット(Credit), 払い戻し(Refund)、税金(Tax)、前払い金(Upfront)に関するレコードを除外1
  • 連結アカウントごとにグループ化する

これは自分が普段利用しているコストエクスプローラのフィルタ条件を整理したものです。

キャッシュフロー上の実際の支払い金額が知りたいのではなく、発生ベースでその月に使用したアカウントごとの費用が知りたいとしましょう。クレジットや払い戻しによる費用の上下、各種の事前チャージのお金の動きは除外したいものとします。簡単のためサンプルでは console に出力しているだけですが、AWSのクレデンシャルがありSDKからAPI叩けているなら、結果を S3 に置くとか SNS に投げるのにとくべつな工夫は必要ないかと思います。

get_cost.ts
import * as AWS from "aws-sdk";

// Cost Explorer は us-east-1 指定が必要
const costexplorer = new AWS.CostExplorer({ region: "us-east-1" });

const params = {
  TimePeriod: {
    Start: "2019-06-01",
    End: "2019-11-01"
  },
  Filter: {
    Not: {
      Dimensions: {
        Key: "RECORD_TYPE",
        Values: ["Credit", "Refund", "Tax", "Upfront"]
      }
    }
  },
  Granularity: "MONTHLY",
  Metrics: ["AMORTIZED_COST"],
  GroupBy: [{ Type: "DIMENSION", Key: "LINKED_ACCOUNT" }]
};

costexplorer.getCostAndUsage(params, (err, data) => {
  if (err) {
    console.error(err);
  } else {
    console.log(data);
  }
});

これを実行します。うまくデータが取得できれば以下のようにコンソールに出力されると思います。なお今回は簡単のためにページングは考慮していません。結果オブジェクトに NextPageToken が含まれていたら後続を読み込む処理が必要になります。

ma2saka:~/environment $ npx ts-node get_cost.ts
{ GroupDefinitions: [ { Type: 'DIMENSION', Key: 'LINKED_ACCOUNT' } ],
  ResultsByTime:
   [ { TimePeriod: [Object],
       Total: {},
       Groups: [Array],
       Estimated: false },
     { TimePeriod: [Object],
       Total: {},
       Groups: [Array],
       Estimated: false },
     { TimePeriod: [Object],
       Total: {},
       Groups: [Array],
       Estimated: false },
     { TimePeriod: [Object],
       Total: {},
       Groups: [Array],
       Estimated: false },
     { TimePeriod: [Object],
       Total: {},
       Groups: [Array],
       Estimated: false } ] }

中身を実際に見たければ console.log(JSON.stringify(data)); とかすると良いです。ここでは事情により2割愛します。

ResultsByTime に月ごとのデータが配列として格納されています。ResultsByTime[].TimePeriodStart,End が入ってきます。MONTHLYなので、Startが対象月の初日(もしくは指定した開始日)、Endが対象月の翌月初日(もしくは指定した終了日)になります。

ResultsByTime[].Estimated は推定値が集計範囲に含まれていることを表します。今回のような集計をする場合、まだ締まっていない月のデータが Estimated: true になると思えばよさそうです。

ResultsByTime[].Groups にその集計期間におけるアカウントごとの集計値が並びます。ここでは税金や前払金などを除いたデータを月次で集計した値が得られます。

今回は償却コスト(Amotized Cost)をメトリクスとして集計対象にしました。これによってRIの費用などが月々に償却されて乗ります。指定できるのはこの他に、

BLENDED_COST, UNBLENDED_COST, AMORTIZED_COST, NET_UNBLENDED_COST, NET_AMORTIZED_COST, USAGE_QUANTITY, NORMALIZED_USAGE_AMOUNT

などがありますね。 https://docs.aws.amazon.com/ja_jp/awsaccountbilling/latest/aboutv2/ce-advanced.html などでなんとなく解説されている。3メトリクスは同時に複数指定できます。メトリクス名は Screaming snake case でも Upper Camel Case でも有効みたいだけど、どっちが正なんだろう。

グループ化のタイプとして DIMENSION を、キーとして LINKED_ACCOUNT を指定しています。連結アカウント単位です。タイプを DIMENSION とした場合、値としては以下のようなものが指定できます。

AZ, INSTANCE_TYPE, LINKED_ACCOUNT, OPERATION, PURCHASE_TYPE, REGION, SERVICE, USAGE_TYPE, USAGE_TYPE_GROUP, RECORD_TYPE, OPERATING_SYSTEM, TENANCY, SCOPE, PLATFORM, SUBSCRIPTION_ID, LEGAL_ENTITY_NAME, DEPLOYMENT_OPTION, DATABASE_ENGINE, CACHE_ENGINE, INSTANCE_TYPE_FAMILY, BILLING_ENTITY, RESERVATION_ID

なお、GetCostAndUsage API 経由ではアカウント情報はIDまでしか取れません。アカウントの一覧を取得して付き合わせるには一度 Organizations#listAccounts API を呼び出して辞書を作っておくのが楽だと思います。

グループ化のタイプには TAG を指定することもできます。その場合にはキーとしてリソースタグのキー名を指定します。日本語で書くとちょっとわかりづらいところですが、クエリ書いてみればまあわかる。

以下は、「ある特定のアカウントID」に対し、「OwnerTeam」というリソースタグの値で月額費用を集計します。変更点は Filter 条件と GroupBy の指定です。

awesome.ts
const params = {
    TimePeriod: {
        Start: '2019-06-01',
        End: '2019-11-01',
    },
    Filter: {
        And: [{
            Dimensions: {
                Key: "LINKED_ACCOUNT",
                Values: ["特定のアカウントID"]
            }
        }, {
            Not: {
                Dimensions: {
                    Key: "RECORD_TYPE",
                    Values: ["Credit", "Refund", "Tax", "Upfront"]
                }
            }
        }, ]
    },
    Granularity: "MONTHLY",
    Metrics: ["AMORTIZED_COST"],
    GroupBy: [{ Type: "TAG", Key: "OwnerTeam" }]
}

Filter はちょっと試行錯誤が必要かもしれませんが、そんなに複雑なものでないのですぐ慣れます。Expressionを適宜参照してください。

Lambda などで動かす場合、ce:Get* を許可する

ce:Get* を許してあげましょう。公式のサンプルでは ce:* を許可していますが、権限は狭いに越したことはありません。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "ce:Get*",
            "Resource": "*"
        }
    ]
}

まとめ

Cloud9 は実行環境がついたターミナルとしか使ってなくて、実はぜんぶ vim で書いてました。すみません。Typescript ではあるんですが、Javascript と書いたコードは区別がつきませんでした。これも、そういうもんですね。

6日目は @ne_sachirou さんです。よろしくお願いします。


  1. 裏側のデータ構造をある程度想像した上で集計処理を想像する必要があります。どのアカウントの、どんなタグが付与されたどんなリソースを何分/何GB利用したのか、といった履歴テーブルに集計クエリを打つのだと考えるとわかりやすいです。 

  2. 会社のアカウントのデータ出すわけにもいきませんが、個人アカウントのデータは少なすぎて集計のしがいがなかった。 

  3. 日本語訳で読むと苦しい気持ちになる。純償却コスト、純非ブレンドコストって言われてもエッてなります。設定値との対応関係が読み取りづらいのでドキュメントは大人しく英語で読みましょう。 

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

AWS CognitoのGo SDKを使ってみた

AWS cognitoのドキュメントに関する自分用メモです。
サンプルコード

*管理者権限を使用するにはAWS CLIによる認証情報の設定が必要です。詳しくはこちらを参照してください。

サインアップ

AdminCreateUser

管理者権限でユーザーを作成。
ユーザー名と、電話番号またはメールアドレスのどちらか一方を設定。
ユーザーはSMSやemailにより一時パスワードを与えられる。
ユーザーはFORCE_CHANGE_PASSWORD状態になり、初回signin時にパスワードを変更する必要がある。
DesiredDeliveryMediumsで一時パスワードをSMSとemailのどちら(または両方)に送信するか設定可能。
TemporaryPasswordで一時パスワードを指定可能。指定しなかった場合はcognitoが自動生成。

SignUp

通常権限でユーザーを作成。
ユーザー名、パスワード、電話番号またはメールアドレスのどちらか一方を設定。
ユーザーはUNCONFIRMED状態になり、有効化するために認証が必要。
認証リンク or コードがSMS or EMAILでcognitoから送信される。
認証方法はcognitoのユーザープール作成時の設定に依存する。

認証

AdminConfirmSignUp

管理者権限により、リンクやコードによる認証を無視して認証を許可する。

ConfirmSignUp

SignUp後、コードによる認証に使用する。

サインイン

AdminInitiateAuth

一般的なサインイン。
AccessToken、ExpiresIn、IdToken、RefreshTokenが返ってくる。

InitiateAuth

Adminの場合と同じ。

削除

AdminDeleteUser

管理者権限によりユーザーを削除。

DeleteUser

通常権限でユーザーを削除。
サインイン時に取得できるAccess Tokenが必要。

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

Serverless時代のWordPress構築

みなさんこんにちは。
aratana Advent Calendar 2019の5日目です。
https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_150455_e79ba51f-0163-246b-ad7b-998f8f442d72.jpeg

各所でServelessの波が押し寄せていますが、何よりもサーバーリソースや、ソフトウエアのバージョンアップなどのメンテコストを下げられる点はとても嬉しいです。
そんな中、世の中でよく使われているWordPressはServelessとは少し真逆のイメージがあります。
サーバーを用意、LAMP環境を構築し、WordPressをインストール。
WordPressのセキュリティーアップデートはもちろんメジャーアップデートについていく必要がある。。。

WordPressそのものは好きですが、しっかりと運用を行なっていく必要のある立場の人からするとちょっと気軽に導入したいものではないですね。。

■ Shifter とは?

そんなわがままな私たちにオススメのサービスが Shifter です。
https://www.getshifter.io/japanese/

簡潔に言うと、WordPressの記事を静的サイトとして書き出し、Servelessの仕組みで記事が見れるようにする。と言うのを自動でやってくれるサービスです。
管理画面も必要な時だけ起動しアクセスするといった感じです。

■ Shifter で記事を公開してみる

思い立ったらすぐ行動と言うことで、Shifterを利用してサイトを公開してみようと思います。
(ここから下のキャプチャは12月3日時点なのでUI等は今後変更される可能性ありです)

まずは会員登録

以下のリンクから会員登録出来ます。
https://go.getshifter.io/register
スクリーンショット 2019-12-04 23.49.30.png

Continueを押すと認証のメールが入力したメールアドレスに送られるので、メール内の認証URLをクリックすれば認証完了となり、会員登録完了となります。
登録完了画面の後に再度ログインが必要なようなので、登録したメールアドレス、パスワードでログインしてください。

管理画面の中

ログイン後は以下のような画面が表示されました。
シンプルなダッシュボードで、やることはわかりやすいですね。
スクリーンショット 2019-12-04 23.56.35.png

WordPressサイトを立ち上げてみる

早速画面の真ん中に聳え立っている Create Now を押して記事を作成してみましょう。

プランの選択

最初は、プランを選ぶようです。今回はお試しですので、 Free Tier を選びました。
有料プランは、月払いと年払いを選べるようで、ずっと使う場合は年払いが結構安くなってお得なようですね。
とはいえ、VPSを1台借りたと思えばそんなに高い料金ではないと思ます。
スクリーンショット 2019-12-05 0.01.40.png

基本情報の入力

プランを選択後、基本情報を入力するポップアップが表示されるので、入力していきます。
Team の欄がありますが、特に設定してない場合、Defaultで問題ないです。(アカウントの中にTeamの概念があるようです。)
スクリーンショット 2019-12-05 0.08.26.png

サイトが作成された後

とりあえずWordPressのサイトは作成されました。
とはいえ、Shifterの中でWordPressが作成されただけなので、現時点では世界には公開されていません。
スクリーンショット 2019-12-05 0.12.07.png

現時点でのサイトを確認しよう

サイトが作成された後に表示されている、 Visit Site をクリックすると、Shifterの中で一時的に発行されるURLにリダイレクトされ、その中で現段階のサイトをプレビューすることが出来ます。
とりあえず初期のサイトが表示されたようです。

*あくまでも一時的なプレビューなので、リダイレクト先のリンクを保存するとかはあまり意味がなさそうです。ある一定時間たったら変更されます。
スクリーンショット 2019-12-05 0.14.57.png

WordPress管理画面に入ろう

今度は、サイト作成ごの画面の、Dashboard のボタンを押すとプレビューと同じく管理画面が開かれます。この時ログインした状態で表示されます。
見慣れた管理画面です。初期設定は英語のようです。必要だったら設定で日本語にすぐ変えれるので問題ないですね。
スクリーンショット 2019-12-05 0.20.27.png

サイトを公開する

取り急ぎ一つ記事を追加したので、この記事を世界に公開したいと思います。
公開の手順としては、以下の2つです。
1. アーティファクトの作成
2. 作成したアーティファクトのデプロイ(初期設定では、アーティファクト生成後に自動デプロイとなっている)
AWSのCode3兄弟を使っている人とかだとなんとなく中の仕様がわかりそうな気がしますが。。

1. Artifactの名前を入れて、Generateを実行

First Build Artifact を作成します。この時に、静的サイトに書き出しが行われているようです。
スクリーンショット 2019-12-05 0.30.37.png

2. サイトの公開

今回は、自動デプロイがONになっているので、公開自体は勝手にされます。
OFFにする場合は、Settings > Auto Deploy Artifact のToggleをOFFにしてください。
ここまで完了するとサイトは公開されており、 View Site をクリックすると公開されたサイトに飛ぶことが出来ます。
無料プランなので、URLはランダムです。
スクリーンショット 2019-12-05 0.38.22.png

サイトの更新について

基本的にはサイト公開と同じ流れで、
1. 再度アーティファクトの作成
2. 作成したアーティファクトのデプロイ(自動デプロイとなっている場合は不要)
となりますが、今回はあえて自動デプロイをOFFでやってみます。
Second Build Artifact を作成しましたが、この状態ではまだ Fires Build Artifact が公開されています。
ここでいいなと思ったのは、一度作成した後デプロイの前に Previewが出来る点です。安心して公開が出来ますね。
また、新しいBuildを公開しても元のBuildは残っているので何かあってもすぐ切り戻し出来ます。
ここまでくると、公開の時限設定とか欲しいですね。。
スクリーンショット 2019-12-05 0.42.33.png

まとめ

今回は、基本的な使い方の部分しか触れませんでしたが、他にもWebhookや、API、Team管理など色々な機能がありそうです。
小中規模の、コーポレートサイトorブログサイトなどにはとても向いてると思いました。
静的ページで収まる要件ならとても有効です。
裏はCloudFrontをしようしているみたいなので、Aliasさえ張ってしまえば、独自のURLをしようできるなど、そこそこ自由度は高いです。
何かと忌み嫌われることもあるWordPressですが、Shifterのような楽に管理できるツールを使って運用も保守もしやすいシステムづくりを目指したいです。

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

【待望リリース!】もう Lambda×RDS は怖くない!LambdaでRDSプロキシを徹底的に検証してみた 〜全てがサーバレスになる〜

本日の reinvent でのリリースで衝撃のアップデートがたくさん出ましたね。EKS on Fargate や SageMaker の大幅アップデートも魅力的ですが Lambda の常識をくつがえす RDS のプロキシ機能が登場しました ?

Lambda から RDS に対するアクセスはコネクション数の上限に達してしまうという理由からアンチパターンとされてきました。そのため、RDS をデータストアに選択する場合は ECS や EC2 上にアプリケーションをホストする事が一般的でした。Lambda の接続先 DB に RDS を選べるということはほとんどのWebアプリケーションがサーバレスで実行できるようになるので夢が広がります。

本記事では RDS プロキシを使った Lambda の構成を作ってコネクション数の挙動について検証してみました。
https://aws.amazon.com/blogs/compute/using-amazon-rds-proxy-with-aws-lambda/
※本記事は上記のブログを参考にしています。一部文脈で引用している箇所があります。

image

RDS プロキシは、データベースへの接続プールを維持します。これにより Lambda から RDS データベースへの多数の接続を管理できます。
Lambda 関数は、データベースインスタンスの代わりに RDS プロキシと通信します。スケーリング起動した Lambda 関数によって作成された多くの同時接続を保持するために必要な接続プーリングを処理します。これにより、Lambda アプリケーションは関数呼び出しごとに新しい接続を作成するのではなく、既存の接続を再利用できます。

従来はアイドル接続のクリーンアップと接続プールの管理を処理するコードを用意していたのではないでしょうか。これが不要になります。劇的な進化です。関数コードは、より簡潔でシンプルで、保守が容易になります。

現在はまだプレビュー版ですがこの機能を徹底検証していきましょう。

せっかく検証するのですから従来 ECS などで一般的に使ってたフレームワークを例に上げてみましょう。今回は NestJS を Lambda にデプロイして RDS と接続してみます。

lamba-rds-proxy.png

NestJS を Lambda にデプロイする

Serverless Framework を使用して NestJS アプリケーションを AWS Lambda にデプロイします。
こちらのサンプルソースが参考になりました。ほぼそのまま引用させていただきます。

Lambda がデプロイできたことを確認しておきましょう。

$ sls deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
~~~~~~~~~~~~~~~~~~~~~~~~~~ 省略 ~~~~~~~~~~~~~~~~~~~~~~~~~~
endpoints:
  ANY - https://djpjh5aklf.execute-api.us-east-1.amazonaws.com/dev/
  ANY - https://djpjh5aklf.execute-api.us-east-1.amazonaws.com/dev/{proxy+}
functions:
  index: serverless-nestjs-dev-index
layers:
  None
Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.

生成されたエンドポイントにアクセスします。

image.png

これで準備ができました。まずは Hello World! と文字列を返す NestJS アプリケーションを Lambda をデプロイできました。これから MySQL と接続できるアプリケーションを作っていきます。開発過程は省略しますが、以下のリポジトリに完成品をアップロードしておきます。

完成品:https://github.com/daisuke-awaji/serverless-nestjs

参考:Nest(TypeScript)で遊んでみる 〜DB 連携編〜

タスクの CRUD 操作ができるアプリケーションを用意しました。
image.png

Secret Manger に RDS への接続情報を登録

事前に作成しておいたこちらの RDS を使用します。
image.png

まずは Secret Manger コンソールで RDS への接続情報を登録するようです。
image.png
image.png

シークレットができたら ARN をメモしておきましょう。あとで使います。

IAM

次に、RDS プロキシがこのシークレットを読み取ることができる IAM ロールを作成します。RDS プロキシはこのシークレットを使用して、データベースへの接続プールを維持します。IAM コンソールに移動して、新しいロールを作成します。 前の手順で作成したシークレットに secretsmanager アクセス許可を提供するポリシーを追加します 。

IAM ポリシー

image.png

image.png

IAM ロール

image.png
image.png

rds-get-secret-role という名前で IAM ロールを作成しました。

RDS Proxy

さて、ここからが本題です。
RDS のコンソールを開くと Proxies の項目があります。Lambda の接続先をこのプロキシに向けることでコネクションプールをうまく使いまわしてくれるようです。

image.png

作成してみましょう。先ほど作成した IAM ロールや RDS を入力します。
image.png
image.png
image.png

作成まではしばらく時間がかかるようです。
image.png

Lambda の向き先を RDS から RDS Proxy に切り替える

RDS インスタンスに対して直接接続する代わりに、RDS プロキシに接続します。これを行うには、2 つのセキュリティオプションがあります。IAM 認証を使用するか、Secrets Manager に保存されているネイティブのデータベース認証情報を使用できます。IAM 認証は、機能コードに認証情報を埋め込む必要がないため、推奨されているようです。この記事では、Secrets Manager で以前に作成したデータベース資格情報を使用します。

DBに接続するアプリケーションの設定を変更してデプロイしましょう。

db.config.ts
import { TypeOrmModuleOptions } from "@nestjs/typeorm";
import { TaskEntity } from "./tasks/entities/task.entity";

export const dbConfig: TypeOrmModuleOptions = {
  type: "mysql",
  host: "rds-proxy.proxy-ch39q0fyjmuq.us-east-1.rds.amazonaws.com", // <-- DBの向き先をProxyに切り替える
  port: 3306,
  username: "user",
  password: "password",
  database: "test_db",
  entities: [TaskEntity],
  synchronize: false
};
$ npm run build && sls deploy

まだ Serverless では RDS Proxy をサポートしていないようでしたので Lambda のコンソールから設定してみます。セキュリティグループやサブネットなどは適宜各自の環境に合わせて作成してください。

image.png
image.png

RDS Proxy 経由でも無事に接続できました ?
※事前に DB にはテストデータを入れてあります
image.png

準備に使用した SQL

CREATE TABLE `tasks` (
  `id` int(36) unsigned NOT NULL AUTO_INCREMENT,
  `overview` varchar(256) DEFAULT NULL,
  `priority` int(11) DEFAULT NULL,
  `deadline` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=94000001 DEFAULT CHARSET=utf8mb4;

INSERT INTO `tasks` (`id`, `overview`, `priority`, `deadline`)
VALUES
    (1, '掃除', 0, '2020-11-11'),
    (2, '洗濯', 2, '2020-12-03'),
    (3, '買い物', 0, '2020-11-28');

負荷テストを実行してみる

コネクション数が Lambda のスケールに合わせて増え続けるような挙動を取らないか確認してみましょう。

今回は負荷のために Artillary を使用します。
yaml ファイルでシナリオを記述して実行する Nodejs 製の負荷テストツールです。

Artillary のインストール

$ npm install -g artillery

実行

yaml ファイルを記述しなくてもワンラインで実行できる手軽さも魅力的なツールで愛用しています。
以下のようなコマンドで簡単に実行できます。30 ユーザが 300 回リクエストを送るといった内容です。

$ artillery quick --count 300 -n 30 https://djpjh5aklf.execute-api.us-east-1.amazonaws.com/dev/tasks

実行された Lambda を確認します。Invocations が 9000 回を記録しています。

image.png

一方で RDS のコネクション数はなんと 43 になっていました。すごい。
ちなみに MySQL の現在のコネクション数は show status like 'Threads_connected' で確認できます。

負荷テスト開始前 最大リクエスト時
18 43

RDS Proxy を使わない場合はどうなるか

アプリケーションの向き先を RDS 本体に直接接続するように変更してみます。
この状態でもう一度負荷テストを行うとどうなるでしょうか。

import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { TaskEntity } from './tasks/entities/task.entity';

export const dbConfig: TypeOrmModuleOptions = {
  type: 'mysql',
  host: 'aurora.cluster-ch39q0fyjmuq.us-east-1.rds.amazonaws.com', // <-- RDS 本体に向ける
  port: 3306,
  username: 'user',
  password: 'password',
  database: 'test_db',
  entities: [TaskEntity],
  synchronize: false,
};

実行

コネクション数が 124 まで膨れ上がってしまいました。
やはりプロダクションロードで普通に Lambda+RDS の組み合わせはやってはいけないアンチパターンになりそうですね。RDS Proxy の威力を改めて感じることができました。

$ artillery quick --count 300 -n 10 https://djpjh5aklf.execute-api.us-east-1.amazonaws.com/dev/tasks
負荷テスト開始前 最大リクエスト時
18 124

まとめ

RDS プロキシを使用することで、データベースへの接続プールを保持することが確認できました。これで API やユーザリクエストを受けるようなワークロードでも Lambda から RDS への多数の接続を管理できます。とてつもなく強力なアップデートを体感できました。今後追加で RDS Proxy を使用する場合と使用しない場合とで、レスポンスタイムに違いが出てくるのかなど細かなところまで検証したいと思います。

クラウドはいよいよここまで成長してきました。
次は RDS がインスタンスを意識することなく水平にスケールするようになるのでしょうか。
その時は完全にサーバレスなクラウドが完成しますね。待ち遠しいです。

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

CDP:EC2を使わずLambdaで署名つきURLを作成する

EC2は使わない

CDP(Cloud Design Pattern)で紹介されているPCD(Private Cache Distribution)はEC2を使った方法が記載されていますが、EC2はできることが多いので使いたくない、、

そんな訳でLambda上で署名つきURLを作成します。

構成

スクリーンショット 2019-12-04 21.22.47.png

流れとしてはAPIを投げ、API GatewayはLambdaをキックします。

LambdaはSecertManagerから各パラメータを受け取り、署名つきURLを作成し、値を返却します。
クライアント側は受け取った署名つきURLでデータを取得する。という流れになります。

準備

まず、CloudFrontのキーペアを作成します。※ルートのアカウントしか作成できません!
作り方は以下を参照してください。
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html#private-content-creating-cloudfront-key-pairs-procedure

キーペアIDとプライベートキーはSecretManagerに保存します。
キーは「KEY_PAIR_ID」、「PRIVATE_KEY」としておきます。

スクリーンショット 2019-12-04 22.35.32.png

準備は以上です!

CDKでリソースを作り上げる!

CDK(Cloud Development Kit)で簡単に作っちゃいます。

まずは一番重要なLambdaに置くコード

~/src/lambda/index.ts
import { CloudFront, SecretsManager } from 'aws-sdk'
import { Signer } from 'aws-sdk/lib/cloudfront/signer'
import { APIGatewayProxyEvent } from 'aws-lambda'

function getSignedUrlAsync(
  keyPairId: string,
  privateKey: string,
  options: Signer.SignerOptionsWithPolicy | Signer.SignerOptionsWithoutPolicy
) {
  return new Promise<string>((resolve, reject) => {
    const signer = new CloudFront.Signer(keyPairId, privateKey)
    signer.getSignedUrl(options, (err, url) => {
      if (err) {
        reject(err)
      }
      resolve(url)
    })
  })
}

const formatPrivateKey = (privateKey: string) => {
  return privateKey.replace(/(-----) /, '$1\n').replace(/ (-----)/, '\n$1') // --BEGIN と --END の間には改行がないと[no start line]エラーになる為
}

const parseStringifyToJson = (data: any) => {
  return JSON.parse(data)
}

export const handler = async (event: APIGatewayProxyEvent) => {
  const param = parseStringifyToJson(event.body)

  const url = param.bucket.url
  const secretId = param.secretsManager.id

  const client = new SecretsManager({ region: 'ap-northeast-1' })
  const data = await client.getSecretValue({ SecretId: secretId }).promise()
  const secretJson = JSON.parse(data.SecretString!)

  const keyPairId = secretJson.KEY_PAIR_ID
  const privateKey = formatPrivateKey(secretJson.PRIVATE_KEY)

  const expires = new Date().getTime() + 30 * 60 * 1000

  const preURL = await getSignedUrlAsync(keyPairId, privateKey, {
    url,
    expires
  })

  const responseParam = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'success',
      preURL
    })
  }

  return responseParam
}


これができたらLambda、API Gateway、S3、CloudFrontの順で作成します。

~/src/resrouce/Lambda.ts
import * as cdk from '@aws-cdk/core'
import * as lambda from '@aws-cdk/aws-lambda'
import * as iam from '@aws-cdk/aws-iam'

const createLambda = (scope: cdk.Construct) => {
  const handler = new lambda.Function(scope, 'Function', {
    code: new lambda.AssetCode('src/lambda'),
    handler: 'index.handler',
    runtime: lambda.Runtime.NODEJS_10_X
  })

  handler.addToRolePolicy(
    new iam.PolicyStatement({
      resources: ['*'],
      actions: ['secretsmanager:GetSecretValue']
    })
  )

  return handler
}

export default createLambda

~/src/resources/ApiGateway.ts
import * as cdk from '@aws-cdk/core'
import * as apiGateway from '@aws-cdk/aws-apigateway'
import * as lambda from '@aws-cdk/aws-lambda'

const createApiGateway = (scope: cdk.Construct, handler: lambda.IFunction) => {
  const apiKey = new apiGateway.ApiKey(scope, 'ApiKey')

  const privateDistributionApiGateway = new apiGateway.LambdaRestApi(
    scope,
    'RestApi',
    {
      handler,
      deploy: true,
      deployOptions: {
        stageName: 'Stage'
      },
      proxy: false
    }
  )

  new apiGateway.Method(scope, 'Method', {
    httpMethod: 'POST',
    resource: privateDistributionApiGateway.root,
    options: { apiKeyRequired: true }
  })

  new apiGateway.UsagePlan(scope, 'UsePlan', {
    apiKey,
    apiStages: [
      { api: privateDistributionApiGateway, stage: privateDistributionApiGateway.deploymentStage }
    ]
  })

  return privateDistributionApiGateway
}

export default createApiGateway

~/src/resources/S3.ts
import * as cdk from '@aws-cdk/core'
import * as s3 from '@aws-cdk/aws-s3'
import * as cloudFront from '@aws-cdk/aws-cloudfront'
import * as iam from '@aws-cdk/aws-iam'

const createS3 = (scope: cdk.Construct) => {
  const privateDistributionBucket = new s3.Bucket(scope, 'Bucket', {
    blockPublicAccess: new s3.BlockPublicAccess({
      blockPublicAcls: true,
      blockPublicPolicy: true,
      ignorePublicAcls: true,
      restrictPublicBuckets: true
    })
  })
  const privateDistirbutionCloudFrontOriginAccessIdentity = new cloudFront.CfnCloudFrontOriginAccessIdentity(
    scope,
    'CloudFrontOriginAccessIdentity',
    {
      cloudFrontOriginAccessIdentityConfig: {
        comment: `access-identity-${privateDistributionBucket.bucketName}`
      }
    }
  )
  privateDistributionBucket.addToResourcePolicy(
    new iam.PolicyStatement({
      actions: ['s3:GetObject'],
      effect: iam.Effect.ALLOW,
      principals: [
        new iam.ArnPrincipal(
          `arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${privateDistirbutionCloudFrontOriginAccessIdentity.ref}`
        )
      ],
      resources: [`arn:aws:s3:::${privateDistributionBucket.bucketName}/*`]
    })
  )
  return { privateDistributionBucket, privateDistirbutionCloudFrontOriginAccessIdentity }
}

export default createS3

~/src/resources/CloudFront.ts
import * as cdk from '@aws-cdk/core'
import * as CloudFront from '@aws-cdk/aws-cloudfront'
import * as s3 from '@aws-cdk/aws-s3'
import * as route53 from '@aws-cdk/aws-route53'
import * as route53Targets from '@aws-cdk/aws-route53-targets'

const createCloudFront = (
  scope: cdk.Construct,
  bucket: s3.Bucket,
  originAccessIdentityId: string,
) => {
  new CloudFront.CloudFrontWebDistribution(
    scope,
    'Distribution',
    {
      originConfigs: [
        {
          s3OriginSource: {
            s3BucketSource: bucket,
            originAccessIdentityId
          },
          behaviors: [
            { compress: false, trustedSigners: ['{your iam account}'], isDefaultBehavior: true }
          ]
        }
      ]
    }
  )
}

export default createCloudFront

スタック

~/src/stacks/PrivateDistributionStack.ts
import * as cdk from '@aws-cdk/core'
import createApiGateway from '../resources/ApiGateway'
import createLambda from '../resources/Lambda'
import createCloudFront from '../resources/CloudFront'
import createS3 from '../resources/S3'

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

    const handler = createLambda(this)
    createApiGateway(this, handler)
    const {
      privateDistributionBucket,
      privateDistirbutionCloudFrontOriginAccessIdentity
    } = createS3(this)
    createCloudFront(
      this,
      privateDistributionBucket,
      privateDistirbutionCloudFrontOriginAccessIdentity.ref
    )
  }
}

export default PrivateDistributionStack

Synth & Deploy

LambdaはTypeScriptに対応してないので、index.tsはトランスパイルします。
synth時に勝手にトランスパイルしてくれるようにpackage.jsonに追記しておきましょう!

package.json
{
  "scripts": {
    "build": "npx tsc src/lambda/index.ts && npx cdk synth",
    "deploy": "npx cdk deploy --profile {profile_name}",
    "destroy": "npx cdk destroy PrivateDistributionStack --profile {profile_name}"
  }
}

下記コマンドでsynth・deploy

$ npm run build
$ npm run deploy

これでリソースは完成しました!

リクエストを投げてみる

Postmanを起動してメソッドはPOSTにし、Headerにx-api-keyを指定して値には先ほど作ったAPIKeyを入れます。
Bodyには下記のように指定します。

{
  "Bucket": {
    "url": "https://{CloudFrontのドメイン}/{バケットのデータの場所}"
  },
  "secretsManager": {
    "id": "{最初に作ったSecretManagerのID}"
  }
}

リクエストを投げると。。。
スクリーンショット 2019-12-04 23.47.15.png

署名つきURLが返却され、無事にS3のデータを取得することができました!

まとめ

時間がなく、駆け足で書いてしまったので内容薄めになってしまいました...。
最後までありがとうございました!

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