2015年 5月 の投稿一覧

strong-pmを稼働させてloopbackアプリをデプロイする

http://docs.strongloop.com/display/SLC/Setting+up+a+production+host を参照してloopbackで作ったアプリケーションのデプロイを行ってみました。

いままでREMPチームで作っているプロダクトはminaを利用して、PadrinoやRendrアプリをデプロイしていましたが
loopbackにはデプロイ機構やデプロイしたアプリケーションのプロセスの管理まで用意されているので今回はこの手順で進めてみました。

loopbackアプリのデプロイ及び、アプリケーションのプロセス管理をざっくり整理すると以下の様な図になります。

strong-pm - Process Managerというloopbackを開発しているStrongloopが公開しているNode.jsアプリケーション向けの
プロセスマネージャーをプロダクションのホストに導入することでデプロイの制御や複数アプリケーションを立ち上げた際のプロセス管理まで一元的に行うことができます。

ドキュメントを読みながら色々試行錯誤しながら進めたので、その際の手順をメモしておきます。

手順

アプリケーションを運用するProductionなホストにstrong-pmを導入します。

まずはプロダクション用のサーバ上でnpm install

[production]$ sudo npm install -g strong-pm
(snip)

続いてサービスとして起動できる様にするために以下のコマンドを実行します。

[production]$ sudo sl-pm-install
Service strong-pm installed (/etc/init/strong-pm.conf)

で、サービスを起動します。

[production]$ sudo /sbin/initctl start strong-pm
strong-pm start/running, process 5796

これでproductionを運用するホストにloopbackで作成したアプリケーションがデプロイできる様な状態になりました。

実際にこのproductionのホストに向けてデプロイしてみます。

ビルドサーバでまずビルドします。gitにdeployブランチが作成されます。

[build]$ slc build
Running `git log -1 --pretty=format:"%t" HEAD`
=> 670faf6
Running `git commit-tree -p "refs/heads/deploy" -p "refs/heads/master" -m "Commit tree of
'refs/heads/master' onto 'deploy'" 670faf6`
=> f32106f847861e2f4e008064fa25445cc02ad5f8
Running `git update-ref "refs/heads/deploy" f32106f847861e2f4e008064fa25445cc02ad5f8`
Merged source tree of `refs/heads/master` onto `deploy`
Running `npm install --ignore-scripts`
Running `npm prune --production`
Running `git add --force --all .`
Running `git write-tree`
=> f38b2795a54b584d091dff0d8a61fa7b518fc4b4
Running `git commit-tree -p "refs/heads/deploy" -m "Commit build products" f38b2795a54b584
d091dff0d8a61fa7b518fc4b4`
Running `git update-ref "refs/heads/deploy" e03a0ec552504f3db1744e8add99e03f0a296f67`
Committed build products onto `deploy`

引き続きデプロイ。ビルド時に作成したdeployブランチがプロダクションサーバーにデプロイされます。

[build]$ slc deploy http://10.0.3.xxx:8701
Counting objects: 24, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (24/24), done.
Writing objects: 100% (24/24), 2.81 KiB | 0 bytes/s, done.
Total 24 (delta 12), reused 0 (delta 0)
To http://10.0.3.xxx:8701/api/services/3/deploy/default
acc8711..e03a0ec  deploy -> deploy
Deployed `deploy` as `remp-api` to `http://10.0.3.xxx:8701`

このメッセージでデプロイ完了となります。
実際にデプロイされている先は、productionホストの /var/lib/strong-pm/svc にあって構造は以下の様になります。

[production]$ pwd
/var/lib/strong-pm/svc
[production]$ tree -L 3
.
└── 3
├── repo
│   └── 3
└── work
├── current -> e03a0ec552504f3db1744e8add99e03f0a296f67.1432980055653
└── e03a0ec552504f3db1744e8add99e03f0a296f67.1432980055653

実際に稼働しているかはslcコマンドで確認できます。プロダクションサーバに対して以下のコマンドを実行することで、
稼働させているアプリケーションに設定している環境変数クラスタのサイズ、開けているポート番号等を確認できます。

[build]$ slc ctl -C http://10.0.3.xxx:8701
Service ID: 3
Service Name: remp-api
Environment variables:
Name                Value
NODE_ENV            production
REMP_MONGO_DB_PW    *************
REMP_MONGO_DB_HOST  10.0.3.***
REMP_MONGO_DB_USER  *******
Instances:
Version  Agent version  Cluster size
4.1.1       1.5.1            2
Processes:
ID      PID   WID  Listening Ports  Tracking objects?  CPU profiling?
3.1.22350  22350   0
3.1.22352  22352   1     0.0.0.0:3003
3.1.22354  22354   2     0.0.0.0:3003

これでアプリケーションを稼働させることができる様になりました。今回は3番目に稼働させたアプリケーションなのでService IDとして 3 が振られているので、
該当のアプリケーションは3003番が振られます。*1

この段階でnginx等のリバースプロキシから該当のポートにプロキシしてやることでアプリケーションを公開できる様になります。

メモ

  • strong-pm自体のログは /var/log/upstart/strong-pm.log に保管されます

*1:strong-pmではService ID + 3000がデフォルトのポート番号

MacOSのコンソールから複数画像をまとめてリサイズする方法

ほとんど自分用メモですが便利なのでここに記録として残しておきます。

例えば、特定のディレクトリにある全JPEGファイルの長辺を500pxにする形でリサイズする場合は以下の様に sips コマンドを使うととても楽をできます

☁  ~  sips -Z 500 *.jpg

これだけ。

元画像ファイルが残ったりはしないのでその点のみ注意。

美味しい生ビールに関する一考察

ビールが好きだ。

ただ、このビール。特に生ビールを頼んで「あぁ、このビールは旨い」と心から思えることは意外と少ない気がする。

妙な生ビールを呑むくらいであれば瓶ビールの方が数倍マシである。

過去自分が経験した美味しくない生ビールとして

  1. 泡が荒く出された瞬間から次々と泡が消える生ビール
  2. そもそもガスの調整が抜けておりほとんど泡がない生ビール
  3. ジョッキが冷やされすぎて周りに氷がついており、注がれたビールにもジョリジョリとした氷が浮いている生ビール

などがある。特に2.については最悪で一度出されたことがあって呑んだことがあるが、その場から立ち去ろうかと思うくらいの美味しく無さである。
温泉旅館で宴会が行われ開始から3時間程度経過した後に最初に開栓された瓶ビールを呑んだ様な感じがある。

(1)については要素が2つあって、生ビールサーバーの手入れが不十分で注ぐチューブの洗浄が不十分で注ぐ最中に炭酸がはじけているか、
ジョッキの洗浄が不十分で注いだ際にジョッキの内側の汚れが泡を崩すケースがほとんどな気がする。

(3)についても店側は冷えたビールをという小さな親切大きなお世話が体現した事象で、ジョッキに口をつけた瞬間に「あぁ、冷たいな」という感想しか
生まれないビールではある。そして時間が少し経つとビールが微妙に水っぽくなっていく。ビールの水割りは最悪だ。

では、逆に美味しいビールである条件とは何か。というと、

  • 泡が細かくしばらく置いても消えない
  • グラスにできる結露が細かくまんべんなくつく
  • 飲み進めるとグラスに残る泡がグラスに沿って円形に残っていく

を満たしているビールであることな気がしていて、これを満たしているというのはどういうことかというと「ビールサーバーの手入れがきちんとされている」「グラスが丁寧に洗われている」「グラスの温度が適切である」の3点を抑えていることに等しい。

そんなビールを呑んだときは本当に幸せになる。

なんかビールを呑みたくなった。

loopbackで作成されたAPIのアクセスコントロールを設定する

の様なモデルのリレーションを定義し、APIも自動的に作成され /rempusers/:id/libraries といった形でアクセスできる様に
なったのですが、ログインして得られるアクセストークンを付与しても現状だと401応答が帰って来ます。

$ http http://localhost:5000/api/RempUsers/55486573fecad3d7215ead80/libraries?access_token=*****
HTTP/1.1 401 Unauthorized
Access-Control-Allow-Credentials: true
Connection: keep-alive
Content-Encoding: gzip
Content-Type: application/json; charset=utf-8
Date: Sun, 10 May 2015 01:52:10 GMT
ETag: W/"DNoQruYc4EoovuVyGTv+hg=="
Transfer-Encoding: chunked
Vary: Origin, Accept-Encoding
X-Powered-By: Express
{
"error": {
"code": "AUTHORIZATION_REQUIRED",
"message": "Authorization Required",
"name": "Error",
"status": 401,
"statusCode": 401
}
}

これはloopbackで既定されているUserモデルを継承したモデルにはユーザ自身の情報のみが制御できる様なアクセスコントロールが設定されているため、
そのモデルとリレーションするモデルをAPI経由で呼びだそうとすると制限がかけられています。*1

この問題を解消するためのアクセスコントロールを設定します。

loopbackでアクセスコントロールを設定するためのscafoldは slc loopback:acl というコマンドで実現できます。

今回対象となるAPIのエントリポイントはモデルのメソッド的にはgetに対応するためmethod nameとしては __get__libraries を指定します。

$ slc loopback:acl
? Select the model to apply the ACL entry to: RempUser
? Select the ACL scope: A single method
? Enter the method name: __get__libraries
? Select the role: The user owning the object
? Select the permission to apply: Explicitly grant access

具体的にモデル間のリレーションを組んだ時にどの様なメソッドが自動生成したモデルに備わっているかはドキュメントを参照するとわかりやすいです。

アクセスコントロールを設定した後に再度アクセスしてみると、

$ http http://localhost:5000/api/RempUsers/55486573fecad3d7215ead80/libraries?access_token=****
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Connection: keep-alive
Content-Length: 329
Content-Type: application/json; charset=utf-8
Date: Sun, 10 May 2015 07:27:01 GMT
ETag: W/"149-11634253"
Vary: Origin, Accept-Encoding
X-Powered-By: Express
[]

今度は問題なく取得できています。

なんとなくloopbackの世界が少しだけ見えてきました。

参照

*1:表現が難しい....

loopbackでモデルのrelationを定義する

loopbackでモデルを作成するときslcコマンドで作成できるのだけど、
モデル間のリレーションを同様にscafoldするやり方のメモです。

ここでは1対多となるモデルのリレーションの定義をslcコマンドで行ってみます。
例としてRempUserというユーザが複数のプレイリストを持つケースを想定します。

ユーザのモデルは既に作成されているものとして、プレイリストを管理するモデルを復習がてら作成します。

$ slc loopback:model
? Enter the model name: Playlist
? Select the data-source to attach Playlist to: db (mongodb)
? Select model's base class: PersistedModel
? Expose Playlist via the REST API? Yes
? Custom plural form (used to build REST URL):
Let's add some Playlist properties now.
Enter an empty property name when done.
? Property name: title
invoke   loopback:property
? Property type: string
? Required? Yes
Let's add another Playlist property.
Enter an empty property name when done.
? Property name: published
invoke   loopback:property
? Property type: boolean
? Required? Yes
Let's add another Playlist property.
Enter an empty property name when done.
? Property name:

次に先ほど作成したプレイリストのモデルとユーザモデルの間のリレーションを定義します。

これもscafoldできて slc loopback:relation というコマンドで実現できます。

$ slc loopback:relation
? Select the model to create the relationship from: RempUser
? Relation type: has many
? Choose a model to create a relationship with: playlist
? Enter the property name for the relation: playlists
? Optionally enter a custom foreign key:
? Require a through model? No
$ slc loopback:relation
? Select the model to create the relationship from: playlist
? Relation type: belongs to
? Choose a model to create a relationship with: RempUser
? Enter the property name for the relation: RempUser
? Optionally enter a custom foreign key:

このコマンドで具体的に行われるのは common/models/*.json で保管されている各モデルの定義が記載されているJSONrelations という
要素に定義が追加されます。

実際に該当のJSONファイルを見てみると、例えばユーザモデルであれば、

{
"name": "RempUser",
"base": "User",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {
"playlists": {
"type": "hasMany",
"model": "Playlist",
"foreignKey": ""
}
},
"acls": [],
"methods": []
}

といった具合にプレイリストモデルとのリレーションの定義が追加されていることがわかります。

また、loopbackの特徴であるモデル定義がそのままAPIとして反映されているので、loopbackのAPI Explorerを介して該当モデルの
エントリポイントを確認してみると、

といった形でCRUDできるAPIが作られています。便利。

ただ、今回の例の場合、新しく作成されている /RempUsers/:id/playlists といったエントリポイントにアクセスをしても HTTP/1.1 401 Unauthorized となってしまいます。

これは、loopbackで設けられているAPIに対するアクセスコントロールによるものなのですが、この辺りを解決するやり方はまた次回。

参考

オムライス

オムライスって日本起源の食べ物だったのね。落ち着いて考えれば米料理だからそうか。

先日、会社の近くで食べたのだが、なかなか驚きの形だった。

loopbackでAPIにアクセスした際のACLの適用状況を確認する

loopbackで作成したAPIには、ユーザのロールに応じてアクセスコントロールを指定することができます。

例えば、

  • adminユーザのみアクセス可
  • このロールのユーザのみアクセス可
  • 認証済のユーザであればアクセス可

といった様なアクセスコントロールをslcコマンドでscaffoldすることができます。(やっていることはmodelの定義をしているJSONにアクセス権限の設定を付与している)

この設定を簡単にチェックするためにアプリケーションログとして標準出力するにはloopbackアプリケーション指定時に以下の環境変数を指定することで実現できます。

$ DEBUG=loopback:security:acl slc run

といった形でloopbackアプリを起動するとloobackで作成したAPIにアクセスした際にそのAPIへ適用されているアクセスコントロールの適用状況を確認することができます。

例えば RempUser というモデルがあり、/api/RempUsers/{id} というエントリポイントに対してユーザ情報を取得するためGETリクエストを発行した場合、発行と同時にアクセスコントロールの適用状況がログとして標準出力に出力されます。

$ http http://localhost:5000/api/RempUsers/1?access_token=pIIxAUP6qn2OtXntEGWO6KxAU71qDSS14A1qZamwTTk2xZEOppdtUVRM3agrdoQ8

といった形でリクエストを発行すると、以下の様なログが出力されます。

loopback:security:acl The following ACLs were searched:
loopback:security:acl ---ACL---
loopback:security:acl model RempUser
loopback:security:acl property findById
loopback:security:acl principalType ROLE
loopback:security:acl principalId $owner
loopback:security:acl accessType *
loopback:security:acl permission ALLOW
loopback:security:acl with score: 8016
loopback:security:acl ---ACL---
loopback:security:acl model RempUser
loopback:security:acl property *
loopback:security:acl principalType ROLE
loopback:security:acl principalId $everyone
loopback:security:acl accessType *
loopback:security:acl permission DENY
loopback:security:acl with score: 7495
loopback:security:acl ---Resolved---

この出力を確認することでloopbackが該当のエントリポイントに対してどういった判断が行われてアクセス可/不可の判定を行ったか確認することができます。

SWITCH VOL.33 NO.5 を読んだ

特集 ジャズタモリ ということで、タモリファンとしては必見ということで読んだ。書店でさっと購入しようとしたが、この号だけよく捌けているらしく数軒回ったが店頭にはどこにもなかった。*1

"around JAZZ", "around TV" という二軸で構成されていて、インタビュー・写真共に多数収められていてとても読み応えがあった。

演じるものが何をしてくれるのではなく、聴くものが演じるものに対して何をするか、タモリの生き方そのものだ。

という冒頭の編集長の一節が頭に残った。


著者 :
スイッチパブリッシング
発売日 : 2015-04-20

*1:店員さんが言われていたので間違いないだろう

loopbackでMongoDBを利用する

loopbackでアプリケーションを作成した直後はデータストレージにmemoryが設定されているため、そのままだとアプリケーションが再起動するたびにmodelの内容が初期化されてしまうため、保管できる様にMongoDB*1を利用できる様にします。

作成したアプリケーションルートでnpmコマンドを叩きます。

$ npm install loopback-connector-mongodb --save

www.npmjs.com

MongoDBに接続するためのコネクタがpackage.jsonに追加されて必要なnpmモジュールがインストールされたのを確認した後、server/datasource.json にある
データベースへの接続設定を更新します。

{
"db": {
"name": "db",
"connector": "mongodb",
"host": "127.0.0.1",
"database": "hogehoge_development",
"username": "",
"password": "",
"port": 27017
}
}

必要な設定はこれだけになります。

先日作成したユーザ登録のAPIを呼び出してみます。

☁  http POST http://0.0.0.0:3000/api/users password=hogehoge email=hideack@remp.jp
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Connection: keep-alive
Content-Length: 59
Content-Type: application/json; charset=utf-8
Date: Mon, 04 May 2015 06:30:46 GMT
ETag: W/"3b-84c0720e"
Vary: Origin, Accept-Encoding
X-Powered-By: Express
{
"email": "hideack@remp.jp",
"id": "554712159967a7b3efce0f0c"
}

MongoDBで永続化されているかを確認してみます。

素朴に mongo コマンドでMongoDBに接続して確認します。

> show collections
User
system.indexes
> db.User.find()
{ "_id" : ObjectId("554712159967a7b3efce0f0c"), "password" : "$2a$10$AzdK8q7nUIHxcoiW5y17Lusb4eag7VOjwQI.e5ukjJRWKMWXSaP6a", "email" : "hideack@remp.jp" }
>

loopbackで作成したアプリケーションで操作されたmodelの内容がMongoDBで永続化される様になりました。

併せて読みたい

過去のloopbackに関する記事

*1:REMPもSTOBOもストレージはMongoDBです

Node Foremanで複数のプロセスを同時に起動する

Rubyで開発している時に開発環境で複数のプロセスをまとめて起動させたいときにforeman
利用することが多いですがNode.jsの実装でNode Foremanがあります。

www.npmjs.com

npmコマンドでNode Foremanをインストールします。

$ npm install -g foreman

コマンド自体は nf となります。

$ nf
_____
|   __|___ ___ ___ _____ ___ ___
|   __| . |  _| -_|     |   |   |
|__|  |___|_| |___|_|_|_|_^_|_|_|
Usage: nf [options] [command]
Commands:
start [options]        Start the jobs in the Procfile
run [options]          Run a one off process using the ENV variables
export [options]       Export to an upstart job independent of foreman
Options:
-h, --help             output usage information
-V, --version          output the version number
-j, --procfile <FILE>  load procfile FILE
-e, --env      <FILE>  use FILE to load environment
-p, --port     <PORT>  start indexing ports at number PORT

では、実際に複数のプロセスをNode foremanで起動してみます。

Procfile自体はRubyのforemanと同じなので準備します。ひとまず開発用ということで Procfile.development というファイル名で以下の様な内容を準備します。

サンプルとして先日作成したloopbackアプリ + MongoDBを動かしてみます。

web: node .
mongo: mongod --dbpath=./db

上記の様なProcfileを用意したらNode Foremanを起動します。

$ nf start -j Procfile.development

上記のコマンドでForemanを起動すると、複数プロセス(loopbackアプリとMongoDB)が起動します。

Node.jsのアプリでforemanを使いたいとき、npmの範囲でカバーできるので便利ですね。