AWS Lambdaでnodeを動かす時にnode_modulesをどうするか
向かうべき道は色々考えられるが,実際に試してみたのは以下.
1. node_modulesをzipに含めてアップロードする
2. browserifyを使って1つのjsファイルにバンドルする
node_modulesをzipに含めてアップロードする
ローカルでnpm install
(or yarn install
) してこさえたnode_modulesディレクトリをzipにアーカイブしてそれをLambdaにアップロードするという方法.
zipファイルを作るのは地味に面倒に思えますが,Apexを使うとかなり楽にzipファイルの作成とアップロードができます.しかし得てしてzipファイルの容量が大きくなりがちなのでS3経由でデプロイするなどの方法を採る必要があるかもしれません.
また,ネイティブ拡張を利用しているライブラリが含まれている場合だとLambdaの実行環境 (アーキテクチャ) と同一の環境上で npm insall
してnode_modulesを作る必要があるでしょう.
可能な限りnode_modulesの容量を小さくするために npm/yarn install --production
でdevDependenciesを外すというのも良い考えです.
pros
- 手っ取り早い・わかりやすい
cons
- アーカイブの容量が大きくなりがち
browserifyを使って1つのjsファイルにバンドルする
ようこそ地獄へ.
node_modulesをアップロードする方法は容量が大きく,スマートではない……おれはもっと格好良い,小洒落た方法でやりたいんだ……ということでbrowserifyを使って1つのjsファイルに依存をバンドルし,それをアップロードするという方法が思いつくでしょう.
とりあえずLambdaで動くbundled jsを構築するには以下のようにコマンドを実行してやる必要があります.
$ browserify --node --standalone 'your-app-name' index.js -o bundle.js
--node
と --standalone 'your-app-name'
を指定して実行すると,運が良ければ動くjsが生成されます.運が悪いと動かない.動かない時はどうするか? おそらくおとなしく諦めたほうが良いでしょう……人生は短い…… (不毛すぎる)
もちろんネイティブ拡張を利用しているライブラリが含まれている場合だと動かないので,そういうときはnode_modulesをzipにバンドルする手法を採用する必要があります.
あるいはbrowserifyの代わりにwebpackを使うという方法も考えられる.僕はやっていません.
pros
- node_modulesをバンドルする方法と比較して容量が小さくなる
- おしゃれ
cons
- ハマりがち
- ネイティブ拡張を利用していると使えない
EOLなUbuntuを使い続けるとどうなるのか
TL;DR
apt-get
関連のコマンドが死ぬ.
例: Ubuntu 16.10の場合
この図からもわかるようにUbuntu 16.10は現時点でEOLです.使ってはいけません.
しかし生きているとうっかりEOLなバージョンが残っていることもあるでしょう.あったのです……
で,EOLを迎えているUbuntuを使い続けるとどうなるか.特筆すべきは apt-get update
が死ぬという点でしょう.
$ sudo apt-get update ... Err:6 http://security.ubuntu.com/ubuntu yakkety-security/universe Sources 404 Not Found [IP: 91.189.88.152 80] ... Err:14 http://archive.ubuntu.com/ubuntu yakkety/universe Sources 404 Not Found [IP: 91.189.88.149 80] ... Err:27 http://archive.ubuntu.com/ubuntu yakkety-updates/universe Sources 404 Not Found [IP: 91.189.88.149 80] ... Err:37 http://archive.ubuntu.com/ubuntu yakkety-backports/multiverse amd64 Packages 404 Not Found [IP: 91.189.88.149 80] ... Reading package lists... W: The repository 'http://security.ubuntu.com/ubuntu yakkety-security Release' does not have a Release file. W: The repository 'http://archive.ubuntu.com/ubuntu yakkety Release' does not have a Release file. W: The repository 'http://archive.ubuntu.com/ubuntu yakkety-updates Release' does not have a Release file. W: The repository 'http://archive.ubuntu.com/ubuntu yakkety-backports Release' does not have a Release file. E: Failed to fetch http://security.ubuntu.com/ubuntu/dists/yakkety-security/universe/source/Sources 404 Not Found [IP: 91.189.88.152 80] E: Failed to fetch http://archive.ubuntu.com/ubuntu/dists/yakkety/universe/source/Sources 404 Not Found [IP: 91.189.88.149 80] E: Failed to fetch http://archive.ubuntu.com/ubuntu/dists/yakkety-updates/universe/source/Sources 404 Not Found [IP: 91.189.88.149 80] E: Failed to fetch http://archive.ubuntu.com/ubuntu/dists/yakkety-backports/multiverse/binary-amd64/Packages 404 Not Found [IP: 91.189.88.149 80] E: Some index files failed to download. They have been ignored, or old ones used instead. $ echo $? 100
ワーオ,repositoryが404になっていますね.それが起因してコマンドが100でexitしています.
curlを打ってみましょう.
$ curl -I http://security.ubuntu.com/ubuntu/yakkety-security/universe HTTP/1.1 404 Not Found
無慈悲にdistのパッケージがremoveされてる!! マジかよ.
解決するには
- Ubuntuのバージョンを上げる
結論
apt-get
系のコマンドがしくじるだけでなく様々な悪いことが起きる.
EOLなUbuntuを使うのをやめよ.
GitHub releasesのフィードを購読する
生きているとOSSのライブラリを使ったり,OSSのソフトウェアを使用することになるでしょう.
そうなってくると内部実装や変更点を逐一知りたくなるというのが人情というものです.
GitHubでコードが公開されているのであれば,「リポジトリをwatchする」というのは有効な方法に思えますが,しかしwatchしているリポジトリが増えてくると現状のGitHubのタイムラインは即座に崩壊し,容易に取りこぼしが生じてしまうでしょう.これはGitHubのタイムラインの問題の一つだと思っていて,なんとかなって欲しい点はあります (例えばタイムラインを分割できるとか……).
というわけでどうするか.もちろんAtomフィードです.
https://github.com/{author}/{repos}/releases.atom
と指定してやると当該repositoryのGitHub releasesをAtomフィードで取得することが可能となります.これで(必ずreleaseを切るプロジェクトであれば)変更をトラックできるようになって便利です.場合によっては https://github.com/{author}/{repos}/tags.atom
を利用するのでも良いかもしれませんね.
というわけでreleasesフィードをSlackのRSSリーダーAppを介してチャンネルに流すようにしたので,関係するメンバーに周知できるようになって便利になりました.良かった良かった.
PerlのYAMLライブラリ性能比較
なんと2018年の記事です.皆様無事明けられておりますでしょうか.
さてYAML::XSには2017年に色々と変更が入り,実用するにあたり非常に便利な機能が色々と導入されました (具体的に言うと,$YAML::XS::LoadBlessedと$YAML::XS::Booleanです).また安定化が図られました *1.
というわけで個人的に,最近PerlでYAMLをserialize/deserializeするにあたってはYAML::XSを使うことが多くなってきたわけですが,そこでふと各YAMLライブラリの性能について気になったのでベンチマークを取ってみたという次第です.以下はその記録です.
ベンチマークコード
#!/usr/bin/env perl use strict; use warnings; use utf8; use Benchmark qw(cmpthese); use YAML (); use YAML::Tiny (); use YAML::Syck (); use YAML::XS (); use YAML::PP::Loader; use YAML::PP::Dumper; my $yaml_text = do { local $/; <DATA> }; my $yaml_pp_loader = YAML::PP::Loader->new; # Deserialize cmpthese(10000, { 'YAML' => sub { YAML::Load($yaml_text); }, 'YAML::Tiny' => sub { YAML::Tiny::Load($yaml_text); }, 'YAML::Syck' => sub { YAML::Syck::Load($yaml_text); }, 'YAML::XS' => sub { YAML::XS::Load($yaml_text); }, 'YAML::PP' => sub { $yaml_pp_loader->load_string($yaml_text); }, }); my $hashref = YAML::XS::Load($yaml_text); my $yaml_pp_dumper = YAML::PP::Dumper->new; # Serialize cmpthese(10000, { 'YAML' => sub { YAML::Dump($hashref); }, 'YAML::Tiny' => sub { YAML::Tiny::Dump($hashref); }, 'YAML::Syck' => sub { YAML::Syck::Dump($hashref); }, 'YAML::XS' => sub { YAML::XS::Dump($hashref); }, 'YAML::PP' => sub { $yaml_pp_dumper->dump_string($hashref); }, }); __DATA__ # From: https://github.com/kubernetes/kubernetes/blob/7bbab6234f99af9adb700ff30968794084b6ee12/examples/mysql-wordpress-pd/wordpress-deployment.yaml apiVersion: extensions/v1beta1 kind: Deployment metadata: name: wordpress labels: app: wordpress spec: strategy: type: Recreate template: metadata: labels: app: wordpress tier: frontend spec: containers: - image: wordpress:4.8.0-apache name: wordpress env: - name: WORDPRESS_DB_HOST value: wordpress-mysql - name: WORDPRESS_DB_PASSWORD valueFrom: secretKeyRef: name: mysql-pass key: password.txt ports: - containerPort: 80 name: wordpress volumeMounts: - name: wordpress-persistent-storage mountPath: /var/www/html volumes: - name: wordpress-persistent-storage persistentVolumeClaim: claimName: wp-pv-claim
Deserialize結果
Rate YAML::PP YAML YAML::Tiny YAML::Syck YAML::XS YAML::PP 251/s -- -29% -86% -97% -98% YAML 351/s 40% -- -80% -96% -97% YAML::Tiny 1767/s 604% 403% -- -81% -87% YAML::Syck 9524/s 3696% 2613% 439% -- -30% YAML::XS 13699/s 5360% 3803% 675% 44% --
Serialize結果
Rate YAML YAML::PP YAML::Tiny YAML::XS YAML::Syck YAML 588/s -- -74% -84% -96% -97% YAML::PP 2299/s 291% -- -38% -84% -87% YAML::Tiny 3731/s 535% 62% -- -74% -79% YAML::XS 14286/s 2330% 521% 283% -- -19% YAML::Syck 17544/s 2884% 663% 370% 23% --
なるほどという結果です.
DeserializeはYAML::XSが最も速く,SerializeはYAML::Syckが高速という結果になりました.
もちろん対象とするYAMLのデータ構造に依存する結果ではありますが,性能を考えるのであればYAML::XSかYAML::Syckを使っておけば良さそうという感じですね.
それぞれ,
YAML
: Pure Perl実装.これでしか解釈できない記法が(なぜか!)ある.YAML::Tiny
: Pure Perl実装.PPなのでシンプル.YAML::Syck
: libsyckのバインディング.XSで比較的高速.*2YAML::XS
: libyamlのバインディング.XSで比較的高速.libyamlという安心感がある.現代では安定している.YAML::PP
: Pure Perl実装.YAML 1.2をサポートするというモチベーションらしい.
という特徴があるので (そして各々のライブラリで解釈できる構文が異なる場合があり,これは業界ではヤムルの地獄と呼ばれています),状況に応じて使い分ける必要はありそうです.こちらからは以上です.
まあなんかなんやかやYAML::Tiny使うことは多いです
— はいじゃないが (@moznion) 2018年1月17日
実装がわかりやすいし……
— はいじゃないが (@moznion) 2018年1月17日
DynamoDB LocalでもDynamoDB Streamsは使える
TL;DR
- DynamoDB LocalはDynamoDB Streamsをサポートしている.
- 本物のDynamoDB Streamsのように,一定期間以上古いレコードについてはトリミングされる (具体的なトリミング期間については未調査).
- ドキュメントが少ない (見つけられなかった……) のに加え,ライセンス的に逆コンパイルは不可能なのでどういう仕組みで実現されているかがわからない……
本文
DynamoDB Localというローカル環境で動くDynamoDBがあり *1,これはDynamoDBの動作検証やテストに利用するためのミドルウェアなんですが (もちろん本番環境で使うものではない),これがDynamoDB Streamsをサポートしていることはあまり知られていません.僕も一昨日知りました.
Yes, the latest version of DynamoDB Local supports DynamoDB Streams on the same port configured for the DynamoDB service (by default 8000).
https://forums.aws.amazon.com/thread.jspa?threadID=231696
つまりこれはDynamoDB Streamsを使っているようなソフトウェアのテストや動作の検証をDynamoDB Localを使って行えるということです.
以下はgoの場合のサンプルコードですが,内容はAWS上で動いているDynamoDBでDynamoDB Streamsを利用する場合と大差はありません.
dynamoSession, err := session.NewSession(&aws.Config{ Region: aws.String("ap-northeast-1"), Endpoint: aws.String("http://localhost:8000"), }) if err != nil { panic (err) } dynamo := dynamodb.New(dynamoSession) dynamoStream := dynamodbstreams.New(session)
DynamoDB Localに向けたDynamoDB及びDynamoDB Streamsのクライアントを作ります.
_, err = dynamo.CreateTable(&dynamodb.CreateTableInput{ AttributeDefinitions: []*dynamodb.AttributeDefinition{ { AttributeName: aws.String("ID"), AttributeType: aws.String("S"), }, { AttributeName: aws.String("Name"), AttributeType: aws.String("S"), }, }, KeySchema: []*dynamodb.KeySchemaElement{ { AttributeName: aws.String("ID"), KeyType: aws.String("HASH"), }, { AttributeName: aws.String("Name"), KeyType: aws.String("RANGE"), }, }, ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ ReadCapacityUnits: aws.Int64(1), WriteCapacityUnits: aws.Int64(1), }, TableName: aws.String("User"), StreamSpecification: &dynamodb.StreamSpecification{ StreamEnabled: aws.Bool(true), StreamViewType: aws.String("NEW_AND_OLD_IMAGES"), }, })
StreamSpecification
を用いてStreamsを有効にしたテーブルを作成します.
_, err = dynamo.PutItem(&dynamodb.PutItemInput{ TableName: aws.String("User"), Item: map[string]*dynamodb.AttributeValue{ "ID": { S: aws.String("ID-1"), }, "Name": { S: aws.String("moznion"), }, }, ReturnConsumedCapacity: aws.String("none"), })
作成したテーブルに対してitemをputしてみます.これでUser
tableにレコードが入っているはず.
table, _ := dynamo.DescribeTable(&dynamodb.DescribeTableInput{
TableName: aws.String("User"),
})
streamArn := table.Table.LatestStreamArn
stream, _ := dynamoStream.DescribeStream(&dynamodbstreams.DescribeStreamInput{
StreamArn: streamArn,
})
shards := stream.StreamDescription.Shards
DescribeTable
でtable情報を引いて,DynamoDB StreamsのstreamArnを取得します.そのstreamArnを利用してStreamのshardsを引っ張ってきます.
Shard: for _, shard := range shards { out, err := dynamoStream.GetShardIterator(&dynamodbstreams.GetShardIteratorInput{ StreamArn: streamArn, ShardId: shard.ShardId, ShardIteratorType: aws.String(dynamodbstreams.ShardIteratorTypeTrimHorizon), }) if err != nil { logrus.Error(err) continue } nextItr := out.ShardIterator for nextItr != nil { record, err := dynamoStream.GetRecords(&dynamodbstreams.GetRecordsInput{ ShardIterator: nextItr, }) if err != nil { continue Shard } records := record.Records // Do something nextItr = record.NextShardIterator } }
shards
を用いて各shardのShardIteratorを取得し,そのshard iteratorを使うことでstreamからレコードを取ってくることが可能となります.後は煮るなり焼くなりできるでしょう.
なおこのコードはあくまでサンプルなので,実際にはshards周りの処理は状況に応じて書き換える必要があるでしょう.
所感
軽く動作検証をした感想ですが,テストやDynamoDB Streamsの動作検証用途であれば十分利用できると思います.「一定期間以上古いレコードはトリミングされる」という挙動もエミュレートしていてすごい (その期間については未検証).
一方で冒頭にも書きましたが,DynamoDB LocalのDynamoSB Streamsはドキュメントが極端に少なく (というかDynamoDB Local自体の情報が少ない気がする……),また具体的にどのような仕組みで動いているのかがわからないという部分がネックと言えばネックとなりえると思います.が,まあDynamoDB Local自体がSQLiteの超テクノロジーで動いているわけだし,まあ……という気持ちではいます.
それにしてもDynamoDB LocalにStreams対応入っているの本当にすごい……
とにかく,DynamoDB LocalはDynamoDB Streamsに対応しているという話でした.
Ref:
手っ取り早くウェブアプリケーションにOAuth2認証を導入する
bitly/oauth2_proxyを用いて,ウェブアプリケーションに手っ取り早くOAuth2認証を導入するという話です.
oauth2_proxyは良い感じでOAuth2による認証を肩代わりしてくれる君で,何らかのリバースプロキシの認証機構と組み合わせて利用すると簡単にOAuth2ログインを実現することができます.
今回は例としてKibanaにGoogleのOAuth2ログインを導入してみたいと思います.
構成
- Kibana
- bitly/oauth2_proxy
- nginx
+------+ +-------+ +--------------+ +--------+ | | | | ----auth----> | | | | | user | --request--> | nginx | | oauth2_proxy | <--auth--> | Google | | | | | <--response-- | | | | +------+ +-------+ +--------------+ +--------+ | access | v +--------+ | | | kibana | | | +--------+
流れ
事前準備
https://console.developers.google.comにアクセスしてcredentialsを作り,Client IDとClient secretを取得します.この時,Authorized redirect URIs
にリダイレクト先のURLを登録しておきます (e.g. https://kibana.example.com/oauth2/callback
).
実際に動くサンプル
docker-composeで実際に動くものを示します.
nginx.conf
auth_request
ディレクティブでOAuth2認証を有効にしています.あとの部分は読むとだいたいわかると思います.このnginx.confの内容については以下の記事が詳しいです (というかパクりました,ありがとうございます).
docker-compose.yml
<>
で囲われた部分については適宜書き換える必要があります.client-id
及びclient-secret
にはあらかじめ作成しておいたcredentialの内容を入れます.
ここで利用されているhttps-portalはLet's Encryptを利用してSSL/TLS証明書の取得を自動化しつつSSL終端してくれるコンポーネントです.これ利用している理由はGoogleのOAuth2認証がリダイレクトしてくる時にhttpsを利用するからです.もしnginxの側でSSL/TLS化してる場合や,ELB等の上流でSSL終端している場合などは不要です.
なお,STAGE: 'local'
と記述しておくとLet's Encryptは利用せずにオレオレ証明書を利用するモードになるのでローカルでの検証等で便利です.つまり本番で使ってはならない.本番運用時にはSTAGE: 'production'
などと記述する必要があるでしょう (ドキュメントを参照のこと: https://github.com/SteveLTN/https-portal).
docker-copose up
してアクセスするとこんな感じで動きます (kibana.example.com
をそのまま利用する場合はhosts等を適宜書き換えておく必要があります).
Googleのログインページが開き,適宜やっていくと
という感じでKibanaが開きます.便利便利.よかったですね.
AWS Elastic Beanstalkで初期環境構築時にhealth checkが延々通らないためにその一生を終えたくない時に読む
AWS Elastic Beanstalkは摩訶不思議な理由でhealth checkが通らなくなることがあります.
たとえばEBの環境を新規に構築する際に,とりあえず動作するかどうか確認するためにAWSが用意してくれているサンプルアプリをデプロイしようとするでしょう.しかしこれは時に謎の詰まり方をして動かないことがある.恐らくAWSのサンプルアプリが悪いわけではなく,サンプルアプリでなくとも動かない時は動かない.ままなりませんね.
この「環境の初期構築時」というのが鬼門で,初期構築時に詰まると手も足も出ない.「オペレーションの中止」で止めることもできないし,かといって「アプリケーションの削除」も「構築中の環境があるから削除できません」の一点張り,ならば設定を変えようにも一度立ち上がりきった状態でなければ設定の変更すらも許されない.そして数十分待たされるのである.そのように俺たちは年老い……死んでゆく.
というわけでこうです;
ELBを使っている時
ELBごと削除する.これしかない.
するとEBのデプロイメントが異常終了するので,環境をこちらのコントロール下におけるようになる.あとは環境を削除して作り直してみましょう.運が良ければ動く.
ELBを使っていない場合
100年待つ (よく知らないが多分シーケンスの最後まで待つ必要があるだろう……).
ガンバだよ!!