npm v6が自動的に脆弱性を見つけるようになった
Node 10.0と合わせて自分のMacのnpmもv6にバージョンアップさせました。 色々いじってた時に脆弱性を教えてくれるようになったので、メモとして残します。
例えばnpm install
でnodemonを入れると、以下のように脆弱性があるよと警告を親切に出してくれます。
画像のインストラクション通り、npm audit
で詳細が見られるようです。
素直にコマンドを叩くとこんな図のようにどのパッケージの何がマズイのか表示されるようになります。
ガイドはNPM公式から出てます。
ガイドを見る限り、npm install
を叩くと同時に npm audit
も叩かれるみたいです。
もしパッケージインストール時に npm audit
を叩かないようにしたい場合は、--no-audit
フラグをつければOK。
今回のnodemonの例でやると、以下のようになる。
npm install nodemon --no-audit
また、npmのインストール全体を通して npm audit
させないようにするためには以下のように設定してあげればいいみたいです。
npm set audit false
Nodeだけじゃなくてnpmも進化してるって発見ですね。 セキュリティ周りをこうやって補強するのを助けてくれるのは嬉しい限りです。最近はGitHubもpackage.jsonから脆弱性アラートを出してくれるようになっているみたいなので、ちゃんとセキュリティ周りは固めておきたいね。
Goで簡易Webアプリ作成&Dockerコンテナにまとめる
ご無沙汰してます。約2ヶ月ぶりの投稿になります。
ここ2ヶ月くらいは社内のイベント向けのアプリを作ったり、Kubernetes勉強会に参加して初めてAWS触ったり、コンテナ周りで新人向けに講師としてスピーカーやったりと技術(特にDockerコンテナ)漬けの日々を過ごしておりました。(Kubernetes@AWSは今度記事にする予定です)
お陰様で今日の健康診断で2ヶ月で10kg体重落ちてました汗
技術だけじゃなく美味しいご飯どころも探さなきゃね。。。
さて、4月末から連休で時間ができたので新しいものに挑戦しようとGoに触り始めました。 折角なら馴染みのあるWebアプリを作成して、Dockerコンテナにまとめるくらいはやろうと全速力でやったので記事投下です。
ソースコード見たいよって方は以下のリンクからどうぞ。
Go出発点
今まで手を出そうと思いつつ何も手をつけていなかったので、Goについては知識0からスタート。 比較的最近出版された本ならばバージョンの違いでつまづくことは少ないはずなので、とりあえず『スターティングGo言語』買って3日ほどで手を動かしつつ読了。あとは作りたいものを完成させます。
さっと書店で手に取った本にしてはアタリだった気がします。 全7章建てで、基本で小分けに5章割いて、残りはツールやパッケージの話で2章。1つ1つのGoの仕組みを分解してコード例も沢山載せてくれているので、写経しているだけでもかなり書けるようになります。
Go製のWebアプリを作る
サーバー側は最低限文字列とJSON返すだけ返せればよかったので、簡易なものとしてピッタリな Echo を選択。 公式のドキュメントは分かりやすいし、サイトも見やすいので取っ付きやすかったです。
"minimalist Go web framework" と謳うくらいだから、Node.jsでいうExpress.jsと同じような位置付けなのかな?
以下、公式の例を使いつつルーティングとミドルウェアを設定した main.go
例です。
ルーティングのミドルウェアはデフォルトの設定を使うとJSON形式の見辛いログが出力されるので、Apacheの Common Log Format に似せて出力されるように設定してます。ログ出力は出力内容は柔軟に変えられるみたいですね。
package main import ( "net/http" "github.com/labstack/echo" "github.com/labstack/echo/middleware" "github.com/sasaken555/ponz_goecho_server/routes" ) func main() { /* Echoインスタンスの作成 */ e := echo.New() /* Root Level Middleware */ // ログ出力は Apache Common Log Format っぽく設定すると読みやすい e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ Format: "${host} [${time_rfc3339_nano}] \"${method} ${uri}\" ${status} ${bytes_in} ${bytes_out}\n", })) e.Use(middleware.Recover()) /* ルーティングの設定 */ // 第2引数の値は別パッケージに外出しすると分かりやすい e.GET("/users/:id", routes.GetUser) e.GET("/users/json", routes.GetJSONUser) e.Logger.Fatal(e.Start(":1323")) // ポート1323で起動。 }
また、ルーティングの設定・関数は main に全部突っ込むと後で見づらくなるので、外出ししてあげます。
今回は routes/user.go
としてルーティングしたときに返す関数をまとめてみました。
package routes import ( "net/http" "strconv" "github.com/labstack/echo" "github.com/sasaken555/ponz_goecho_server/util" ) // GetUser ... Pathパラメータからユーザー(=ID)を取り出して返す func GetUser(c echo.Context) error { // User ID from Path Parameter `users/:id` id := c.Param("id") return c.String(http.StatusOK, id) } // Customer ... 顧客情報の構造体 type Customer struct { ID int64 `json:"id" xml:"id"` Name string `json:"name" xml:"name"` OrderNum int `json:"ordernum" xml:"ordernum"` OrderProd string `json:"orderprod" xml:"orderprod"` } // GetJSONUser ... 顧客情報のJSONを返す func GetJSONUser(c echo.Context) error { userID, err := strconv.ParseInt(c.QueryParam("userId"), 10, 0) // strconvで文字列から整数に型変換 userName := c.QueryParam("userName") orderNum := util.GetRand(100) // 別のパッケージからインポートした指定桁数で乱数を返す関数を使う // userIDが整数でない(=型変換できない)ならば500エラーを返す if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "You Should Provide userId as Integer!") } // 構造体のポインタを作成 u := &Customer{ ID: userID, Name: userName, OrderNum: orderNum, OrderProd: "Blend Coffee", } return c.JSON(http.StatusOK, u) }
Dockerコンテナにまとめる
関心があったのと同時に割と引っかかったのが、ここ。特にビルドイメージ大きすぎなのが難点でした。
GoはOS依存させないために、OSが標準で備えているようなライブラリを使わず、ランタイムとアプリで使うパッケージを全て実行ファイルの中に取り込みます。この性質があるため、少しコード量の多いアプリでも成果物が数百MBになるのもザラ。
実行ファイルを組み込んだDockerコンテナのイメージがデカイとなると、
docker pull
の度にディスクと時間が取られるのでポータビリティの点から使いづらい。
上記の問題があったため、ベースイメージを小さいものにするのに加えて、Dockerのマルチステージビルドで対応することしました。
通常だとソースコードを全てDockerコンテナに含めますが、Goは良くも悪くもソースコードからシングルバイナリ(実行ファイル)を作成するので、実行ファイルだけ入れればOK。下のコード例の(1)が実行ファイル作成のビルド、(2)が(1)で作った実行ファイルをコピーして作った最終系のコンテナイメージになります。
ベースイメージも alpine イメージを使うことでさらに軽量化してます。
# Full SDK version ... (1) FROM golang:1.10-alpine AS build RUN apk update && apk upgrade \ && apk add curl git RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh WORKDIR /go/src/github.com/sasaken555/ponz_goecho_server COPY . . RUN dep ensure RUN go build -o ponz_goecho_server # Final Output ... (2) FROM golang:1.10-alpine COPY --from=build /go/src/github.com/sasaken555/ponz_goecho_server/ponz_goecho_server /bin/ponz_goecho_server CMD /bin/ponz_goecho_server
結果として、通常のベースイメージ+アプリソースを含めてビルドしたイメージ(tag: heavy)とalpineベースイメージ+マルチステージビルドのイメージでサイズは半分以下に抑えられています!やったね!
$ docker image ls ponz_goecho_server REPOSITORY TAG IMAGE ID CREATED SIZE ponz_goecho_server light 24b61e6c4f83 7 seconds ago 386MB ponz_goecho_server heavy 20bac15a0acf About a minute ago 908MB
新しい技術(今回は言語でしたが)として今回はGoを選択しましたが、謳い文句の通りシンプルかつ効率的に開発&ビルドできたのはすごい...!!
バイナリ単位でアプリはまとめられるので、マイクロサービスと相性が良さそうな印象です。しかもブロックチェーンのHyperledger fabricもGoで開発できるようなので、次はGoを使ってブロックチェーンに挑もうかなと夢が膨らんだところで締めます。
自分の勤怠状況を Kubernetes, Slack, Metabaseで可視化
システム構成はタイトル通りで、Slackから飛ばした出勤・退勤をKubernetesにデプロイしたアプリ経由でDBに登録して、 Metabaseから可視化したよって話。
タイトルだけ見ると、いや勤怠管理のアプリ使えよとなるかもしれない
やったことと
狙いとしては、Docker / Kubernetesがどれくらい使えそうか、メリットは何かをざっくり感じれればいいなと。 ついでに、勤怠報告に使えればなおよし。
全体的なフロー
- Kubernetesクラスターを作成して、Dockerコンテナ化したアプリをデプロイ
- SlackのSlashコマンドと(1)のアプリのHTTPエンドポイントを紐付け
- SlackにSlashコマンドで勤怠を入れる
- Kubernetes上のアプリ経由でMongoDBにレコードを登録
- Embulkで MongoDB → MySQL にデータ転送
- Metabaseから勤怠状況をグラフに落とし込む!
完成図
[Slackで勤怠をちょこちょこ入れて]
[Metabaseで結果をダッシュボードに出す]
うーん、激しい勤務実態が見えてしまいましたね。可視化した時点で休み時間入れるの忘れてたことに気がつきました。。。
大変だったとこ
フローごとに難所があったので、覚え書きとして。
Kubernetes
Docker コンテナのイメージさえできていれば、Deploymentは問題なくいける。
ServiceはLoad Balancerを選択しましたが、クラウドプロバイダーによってはデフォルトがHTTPになっています。HTTPSにしないといけないサービスの場合は注意が必要です。例として、SlackのMessage ButtonはHTTPSでないとボタンアクションの質問は出るものの、ボタンを押した後のRequestURLが叩かれません。
ただ、突然Podの再生成がスケジュールできなくなる事象に遭遇。これはKubernetesクラスターを動かしていたGoogle Cloud Platformの問題っぽい。
- 解決策は リソースの自動拡張をONにすること。でないと突発的にリソースを食い始めた時に全て落ちる。
- 調べていたら、もう一つ解決策はあったようですね。Servicesに SessonAffinity プロパティをを加える...ことらしい。
GCE services type LoadBalancer · Issue #18347 · kubernetes/kubernetes · GitHub
Embulk
- 言わずとしれたデータ転送ツール
- MongoDBからデータを抽出した時に、カラムが分割されない問題がありましたがFilterで対応。
- ここは別記事で紹介したい。。。
Metabase
- セットアップから可視化までは相当楽でした。ここは昔の記事を参照いただければと。
MongoDBでのmongoとmongod以外のクライアントプログラム
DBって基本のクライアントプログラム以外にも、クライアントプログラムが入ってるって最近気づきました。 NoSQLのMongoDBも同じようなクライアントプログラムがないか確認しました。MySQLも同様にたくさんあるようですね。
今回はMongoDBのクライアントプログラムの備忘録です。 MongoDBのDBaaSである、mlabの"Tools"タブに用例が書いてありました。
MongoDB Package Components — MongoDB Manual 3.6
使いそうなクライアントプログラムには、以下のようなものがありました。
- はじめによく使うやつら
- mongo, mongos, mongod
- CSV, TSV, JSONを扱うとき
- mongoimport
- mongoexport
- バックアップ・リストア(バイナリ)をするとき
- mongodump
- mongorestore
単純にCSVをインポートするなら、オプションをつけて実行すればOK。
# 基本形 mongoimport -h <hostname> -d <dbname> -c <collection> \ -u <user> -p <password> --file <input .csv file> \ --type csv --headerline # カラムに属性を明示的に付ける場合 mongoimport -h <hostname> -d <dbname> -c <collection> \ -u <user> -p <password> --file <input .csv file> \ --type csv --columnsHaveTypes \ --fields "field1.string(),field2.int32()"
ES6からのトランスパイルするときのBabel 7 対応
タイトル通り、Babelの話。 JavaScript書いていると、避けて通れないのがES6からのトランスパイル。 このとき使うデファクト・スタンダードになったBabelで最近つまづいたのでメモです。
ことの発端
MochaでES6のコードを実行させるために babel-core
, babel-register
, babel-preset-env
入れたけど、deprecated
とか言われたのが事のはじまり。
ターミナルで言われてるバージョンをただ入れるだけでは済まないとは知らず。。。
Babel 7 ??
要はバージョンのメジャーアップデートで単にモジュールが変わっただけじゃない。そもそもの名称から変わっていたのです。 今までの要領でNPM覗いても、バージョン6系しか載ってないので注意しないといけないですね。ちなみに、新しいバージョンは7系(beta版)です。
また、v7系にする場合は、使用するbabelのモジュールのバージョンはすべてv7系にしないと動きません。
[babel-core 今まで] www.npmjs.com
[babel-core v7以降] www.npmjs.com
基本的には、以下のように名称が改定されている模様です。(2018/01/09時点)
- 〜v6 : babel-xxyyzz
- v7〜 : @babel/xxyyzz
名前がわからない場合は以下の、packageから見たいbabelのモジュールを探せばOK。
Githubのissuesを辿る限りまだまだ議論されているようですが、そろそろ新しいものになると考えた方が良さそうです。JS界隈は流れ早いので、ついていくに越したことないですしね。
設定例
名前が変わったとはいえ、基本的には設定方法は以下2つで変わらないですね。
- コマンドラインからオプションでbabelのモジュールを指定する。
.babelrc
にモジュール名を入れる
以下、設定例です。 ほぼ公式の例から分かりやすいところを取り出しました。
{ "presets": [ ["@babel/preset-env", { "targets": { "browsers": ["last 2 versions"] } }] ], "plugins": ["@babel/plugin-transform-runtime"] }
あとは、package.json
で以下のようにbabel-cliを叩くようにすればOK。
babel ./src/ --out-dir dist/ --ignore ./node_modules --copy-files
やることは変わらないとはいえ、やっぱり名前から変わっちゃうのは気づかないとハマって抜けない罠ですわ。。。
Mongooseの処理が非同期であるのを忘れてはいけない(戒め)
Node.jsのMongoDBのORMといえば、Mongoose。APIドキュメントをよく読んで使用されている方ならお分かりかと思いますが、よく使う.save
(ドキュメント保存)や.find
(ドキュメント検索)といった処理は非同期で実行されます。
これ、意識してないと処理はあってるのに値がundifined
になってハマる(実際ハマった)ので、非同期で処理されるという前提を頭に置こう(戒め)というメモです。
一応MongooseのPromiseのページにしれっと以下のように書いてあります。
Built-in Promises
Mongoose async operations, like .save() and queries, return ES6 promises. This means that you can do things like MyModel.findOne({}).then() and await MyModel.findOne({}).exec() (if you're using async/await.
Queries are not promises
Mongoose queries are not promises. However, they do have a .then() function for yield and async/await. If you need a fully-fledged promise, use the .exec() function.
日本語で要約すると、以下のようになるかと。
.save()
や query(find()
/findOne()
/ etc...) といった 非同期の処理 は、ES6のPromiseオブジェクトを返します。- これらのメソッドは
.then()
を続けられる。 - async / await 句をつけて実行できる。
- これらのメソッドは
ただし、queryは厳密にはPromiseではない。
.then()
,async
/await
句を使える。- 厳密にPromiseオブジェクトを使いたい場合は
.exec()
を使おう。
非同期であることはもちろん、返却されるのがPromiseオブジェクト(的なもの)であることもポイントです。 素直に配列が返却されると思いきや、オブジェクトが返ってくるので軽くビビります。
以下のようなコードが非同期で処理されるOK / NGパターンかな。
[NGの例]
/** * findRcd(filterKeys) * @description 引数に渡された値・キーでレコードを検索し、検索結果を返却。 * @param {Object} filterKeys - 更新するレコードの検索キー * @return {Array} resultSet - 検索結果 */ findRcd(filterKeys) { knmKanr.find(filterKeys, (err, awesomeRecords) => { log.debug(`FilterKeys: ${JSON.stringify(filterKeys)}`); // レコード検索失敗 if (err) { throw new Error("Find Docs Error"); // エラーを投げて処理を中断 } // レコード検索成功 log.debug(`AwesomeRecords: ${awesomeRecords}, Size: ${awesomeRecords.length}`); return awesomeRecord; // ここで結果の配列が返却される }); const searchResultSet = findRcd({ flg : '1' }); // 非同期で処理完了前に、変数に値が格納される console.log(JSON.stringify(searchResultSet)); // 処理が未解決のため、undefined になる
[OKの例]
/** * findRcd(filterKeys) * @description 引数に渡された値・キーでレコードを検索し、検索結果を返却。 * @param {Object} filterKeys - 更新するレコードの検索キー * @return {Promise} resultSet - 検索結果 */ async findRcd(filterKeys) { const resultSet = await knmKanr.find(filterKeys, (err, awesomeRecords) => { console.log(`FilterKeys: ${JSON.stringify(filterKeys)}`); // レコード検索失敗 if (err) { throw new Error("Find Docs Error"); // エラーを投げて処理を中断 } // レコード検索成功 console.log(`AwesomeRecords: ${awesomeRecords}, Size: ${awesomeRecords.length}`); }); console.log(`resultSet: ${resultSet}`); return resultSet; // ここでPromiseオブジェクトが返却される } } findRcd(query) .then((searchResultSet) => { console.log(`searchResultSet: ${JSON.stringify(searchResultSet)}`); // {...} 非同期処理が解決されてから値がセットされる }
こういう時こそ、async / await が効果的なんだってわかりますね。 改めて、Mongooseの処理が非同期であるのを忘れてはいけないです(戒め)。
API Blueprintで爽やかなAPI仕様書を作る
ローカルで動かそうがDockerで動かそうが、APIを持つアプリを作るとなると自然と一覧が欲しくなるもの。
できれば型や仕様書のフォーマットを... ブラウザで見れる&修正後はDiffが取れる形で欲しいですね。
(自分の関わってたプロジェクトだとExcelにペタペタ定義を書くor貼る仕様書でした。古いとこだと一太郎で作ってた。一太郎2018ってあるんだぜ)
[一太郎] ... フォントや段組機能は好きです www.justsystems.com
そこでAPI仕様書を簡単に作ろうと思った時に当たった選択肢が、Swagger or API Blueprintでした。 今回はAPI Blueprint触った感じを書きます。
[API Blueprint] API Blueprint | API Blueprint
[Swagger] ... こっちの方が The World's Most Popular らしい swagger.io
完成図
一聞は一見にしかず。下図みたいに エンドポイントごとに作成できます。 GETは青色、POSTは緑色とHTTPメソッドごとに色別になっているのはSwaggerと同じ。また、APIもグルーピングができるようです。
基本
マークダウンで書きます。SwaggerはJSON or YAMLで書くので、API Blueprintはマークダウンに慣れている人にはぴったりです。
ただ、マークダウンといっても作成するファイル形式は .apib
だったり、段落にも意味があったり、特定のブロックは3tab or 12こスペース空けるといったコード規約があります。ここはクセがありますが、1時間もあれば慣れます。
流れとしては、だいたい4つに分けられます。
- マークダウンで 仕様書を書く
- できたファイルは
.apib
で保存 .apib
ファイルを 変換用のツールでHTMLにする- HTMLをお好きなブラウザで見る
HTML変換ツールも言語・フレームワークによって色々用意されているようですね。 例えば、Aglio: Node.js / Laravel Blueprint Docs: PHP(Laravel) / django-apiblueprint-view: Python(Django) / Snowboard: Go といった具合です。 API Blueprint Tools | API Blueprint
また、書いたマークダウンを元にAPIのモックサーバーを動かせるんですが、MacOS10.12以降では動かないみたいですね。。。
→ issuesに上がってました。
書けること
だいたい以下のようなことをかけます。
HTML生成時にリクエストパラメータやレスポンスデータのスキーマを必須・オプション、データ型含めてまとめて表示してくれるのは嬉しい。
面倒なポイント
私は幸いにもマークダウンは普段から書いてる & Node.jsの例は豊富にあったので書くこと自体は苦はないですが、 aglioでの面倒な点として.apib
1ファイルしか変換できないこと がネックでした。1ファイルじゃなくて、分割して書いたものを1つにまとめたいじゃないですか。
少し前の記事ですが、他の方も複数ファイルあるものを1つの.apib
ファイルにまとめて変換をかけているような手段をとっているようです。
というわけで、シェルスクリプト書いて分割した.md
ファイルを1つの.apib
ファイルにまとめて変換しました。
→ Gistで公開してます。(マークダウンはソース貼り付けるとGist上で読みやすく変換してくれるおかげでソースコード見れなくなってますが笑)
作成した仕様書はGithubPagesやローカル、社内のサーバーに置くなりすれば立派な仕様書ができそう。