- 投稿日:2019-03-30T22:19:46+09:00
NodeでES modulesからCommon JSを使う、たったひとつ?の全く冴えないやりかた(2019年版)
先日ようやく拙作のnpmパッケージ(1)(2)(3)をnodeの
--experimental-modulesフラグ付きでの直接実行(babelやwebpackによる前処理無し)に対応させたので、その方法をご紹介いたします。まずは復習
ES Modules (以下、ESM)では、Common JS (以下、CJS)と異なり、他のファイル上の関数や値にアクセスするためには、
import/export構文を用います。CJS
foo.js(CJS)// 複数の関数・値を名前付きでエクスポートする場合 exports.bar = function(x) { return x * x; } // 一つの関数・値をエクスポートする場合 // module.exports = function(x) { // return x * x; // }index.js(CJS)// 複数の関数・値が名前付きでエクスポートされている場合 const foo = require('./foo'); console.log(foo.bar(3)); // 9 // 一つの関数・値がエクスポートされている場合 // const bar = require('foo'); // console.log(bar(3)); // 9ESM
foo.mjs(ESM)// 複数の関数・値を名前付きでエクスポートする場合 export function bar(x) { return x * x; } // 一つの関数・値をエクスポートする場合 (デフォルトエクスポート) // export default function bar(x) { // return x * x; // }index.mjs(ESM)// 複数の関数・値を名前付きでエクスポートする場合 import * as foo from './foo'; console.log(foo.bar(3)); // 9 // 一つの関数・値がエクスポートされている場合 (デフォルトインポート=デフォルトエクスポートのインポート) // import bar from 'foo'; // console.log(bar(3)); // 9ESMのインポートにおける種類
// namespace import import * as foo from 'foo'; // named import import { bar, baz } from 'foo'; // default import import bar from 'foo';で、問題は何?
ESMからCJSをインポートする際の動作が歴史的な経緯によって統一されておらず、複数のターゲット(Web app(Webpack)/Node app(Webpack)/APIとしてnpm package(そのまま)/...)を考慮すると破綻をきたします。
Node(
--experimental-modules.mjs)の場合Nodeは、ESMからCJSをインポートする方法をデフォルトインポートのみとしています。
といっても、現時点の挙動(node 11)では、インポート時にエラーになるのではなく、CJSの中身がdefaultに包まれます。
つまり、デフォルトインポートしていないと、いざ使用する際にundefined is not a functionなどというエラーに遭遇します。注: ESMでは、デフォルトエクスポートは
defaultという名前でエクスポートされるお約束です。foo.js(CJS)exports.bar = function(x) { return x * x; }index.mjs(ESM)import * as foo from './foo'; console.dir(foo);出力[Module] { default: { bar: [Function] } }Babelの場合 (babel-core=7.4.0)
Nodeと同様です。CJSの中身が
defaultに包まれます。トランスパイル前import * as foo from './foo'; console.dir(foo);トランスパイル後"use strict"; var foo = _interopRequireDefault(require("./foo")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } console.dir(foo);出力{ default: { foo: [Function] } }Webpack 4.x(
.js.ts)の場合CJSの中身がそのまま渡されます。
デフォルトインポートされたものを使おうとすると、default is not a function等のエラーを見ることになるでしょう。index.ts_トランスパイル前import * as foo from './foo'; console.dir(foo);トランスパイル後... "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var _foo__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./foo.js"); /* harmony import */ var _foo__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_foo__WEBPACK_IMPORTED_MODULE_0__); console.dir(_foo_WEBPACK_IMPORTED_MODULE_0__); ...出力{ foo: [Function] }Webpack 4.x(
.mjs)の場合実は、Webpack 4.xは、Node(
--experimental-modules)と同じモジュール解決をサポートしています。
方法は、webpack.config.jsで.mjsを受け入れるようにし、入力ファイルに.mjsを渡すか、(インポートしようとしている名前).mjsが存在するパスをimportします。webpack.config.js... resolve: { extensions: ['.tsx', '.ts', '.jsx', '.mjs', '.cjs', '.js'] }, ...index.mjs_トランスパイル前import * as foo from './foo'; console.dir(foo);パッケージング中にエラーERROR in ./modules/index.mjs 3:12-19 Can't import the namespace object from non EcmaScript module (only default export is available) @ multi ./modules/index.mjsデフォルトインポート以外(namespace import, named import)が存在するとエラーになります。
TypeScriptの場合
幸いなことに、TypeScriptのトランスパイラーは、インポート先が何であるかに関わらず、固定的な変換をするだけです。
module (コンパイラオプション) namespace import named import default import CommonJS const foo = require('foo')const foo = require('foo')
const bar = foo.barconst foo = require('foo').defaultES2015 そのまま出力 そのまま出力 そのまま出力 どうすればよいのか?
彼方立てれば此方が立たぬ、といった状況ですが
見苦しいコードを受け入れることで回避できます。
defaultに包まれていれば剥がすことで、どちらの場合も動作します。index.ts(TypeScriptの場合)import * as foo_ from './foo'; const foo: typeof foo_ = (foo_ as any).default || foo_; console.dir(foo);index.jsimport * as foo_ from './foo'; const foo = foo_.default || foo_; console.dir(foo);トランスパイル後"use strict"; const foo_ = require("./foo"); const foo = foo_.default || foo_; console.dir(foo);追記 ( __filename / __dirname について)
ESMでは、
__filename__dirnameといったシンボルも定義されません。
代替手段として、import.meta.urlからファイルURLを取得し、パスに変換します。const thisFileName = url.fileURLToPath(import.meta.url); const thisDirName = path.dirname(thisFileName);ただ、こちらは webpack が対応しておらず
Support for the experimental syntax 'importMeta' isn't currently enabledのようにトランスパイル時にエラーとなります。
@open-wc/webpack-import-meta-loaderという webpackプラグインを導入することでトランスパイル時エラーは回避できますが、
実行時に下記のエラーが発生します。url: `${window.location.protocol}//${window.location.host}/C:\path\to\source\file\index.mjs` ReferenceError: window is not definedブラウザでのみ動作するようです。
動作させるには、次のコードに変更します。const isWebpack = typeof __webpack_require__ === 'function'; let thisFileName = ''; let thisDirName = ''; if (isWebpack) { thisFileName = __filename; thisDirName = __dirname; } else { thisFileName = url.fileURLToPath(import.meta.url); thisDirName = path.dirname(thisFileName); }webpack.config.js... module: { rules: [{ test: /\.m?jsx?$/, use: [ 'babel-loader', require.resolve('@open-wc/webpack-import-meta-loader'), ], exclude: /node_modules[\/\\]/ }, { ...追記 (parcelについて)
今回、webpackと併せてparcelでの動作も比較しました。
parcelでは、webpackのtypeof __webpack_require__ === 'function'のように、パックされたコードかどうかを判定する手段が無いこと、import.meta.urlのエラーを回避する手段を見つけられなかったことで、私のnpmパッケージでのparcel対応ができませんでした。注:私のnpmパッケージでは、CSSの
require()について、パックされているかどうかで動作を変えています。
ESMのファイル内のため、Nodeからの実行ではrequire()が存在しないため、呼び出しをCJS経由でバイパスしていますが、
パックされている場合はrequire()が存在するため、直接require()を呼び出します。
※ 文字列リテラルで渡すrequire('path/to/stylesheet.css')が存在しないとバンドルできないため行っています。ソース
上述のnpmパッケージを
node --experimental-modulesと webpack 両方で動作させる全コードです。
https://github.com/shellyln/menneu-api-usage-on-esm参考資料
- 投稿日:2019-03-30T22:18:53+09:00
問.以下のAPIにAjaxでリクエストを送り、レスポンスを表示しなさい(Promiseと再帰)
問題
以下のAPIにAjaxでGETリクエストを送り、レスポンスデータの "message" フィールドの値をコンソールに表示してください。
https://sheltered-island-72837.herokuapp.com/
なお、上記APIはそこそこの確率でエラーレスポンス(ステータスコード500)を返してきます。
もし、1回のリクエストで正常なレスポンス(ステータスコード200)が得られなかった場合は、再度APIへの問い合わせを行ってください。
その際、各リクエストは3秒以上の間を設けるようにし、また最大リトライ回数は2回とします。
リクエストにはaxiosなどのライブラリを使用して構いません。実装例
const axios = require('axios') const url = 'https://sheltered-island-72837.herokuapp.com/' const fetch = (executeCount = 1) => { return axios .get(url) .then(({data}) => data.message) .catch(({response}) => { if (executeCount < 3) { return new Promise(resolve => { setTimeout(() => { resolve(fetch(++executeCount)) }, 3000) }) } return response.data.message }) } fetch().then(res => console.log(res))解説
APIへリクエストを送る
まずは単純に、APIに1回だけ問い合わせてコンソールに表示してみます。
Ajaxをする際、axiosを利用することが多いと思います。
もちろん、Fetch API を使ってもいいでしょう。
今回は axios でいきます。const axios = require('axios') const url = 'https://sheltered-island-72837.herokuapp.com/' axios .get(url) .then(({data}) => console.log(data.message)) .catch(({response}) => console.log(response.data.message)){data} という書き方は、分割代入 (Destructuring assignment) といいます。
オブジェクトや配列の一部だけを取り出して、別の変数に代入してくれます。リトライ機能
メソッド化
まず、先ほどの「APIへリクエストを送る」処理を再利用できるようにメソッドにします。
合わせて、thenやcatch内で直接console.logを呼んでいたのを、値を返すように修正します。const axios = require('axios') const url = 'https://sheltered-island-72837.herokuapp.com/' const fetch = () => { return axios .get(url) .then(({data}) => data.message) .catch(({response}) => response.data.message) } fetch().then(res => console.log(res))thenやcatchのなかで値を返すとその値はPromiseでラップされる、ということはとても大事なので覚えておきましょう。
Promise.prototype.then()
「値を返すように修正って、return書いてなくない...?」と思った方は、アロー関数の構文を復習しましょう。
アロー関数再帰
実現したいことは、「エラーになったら再度リクエストを送る」です。
「エラーになったら」なので、catchの中にその処理を書いていくことになります。
そして、「リクエストを送る」処理は、まさにfetchメソッド自身がそのための処理なのですから、自分を呼び出してしまえばいいわけです。まず、fetchメソッドが実行回数を取るように引数を追加します。
そしてエラーになった時、実行された回数が上限(初回 + リトライ2回 = 3回)に達していなければ実行回数を+1して自分自身を呼び出します。
そうでなければ普通にレスポンスからメッセージを取り出して return します。再帰を使うときはその終了条件をしっかり書いてあげないと、いつまでたっても自分を呼び出し続けて終わらなくなりますので十分意識してください。
なお、処理の流れを確認するため、一時的にconsole.logを追加しています。
const axios = require('axios') const url = 'https://sheltered-island-72837.herokuapp.com/' const fetch = (executeCount = 1) => { console.log(`${executeCount}回目の試行です`) return axios .get(url) .then(({data}) => data.message) .catch(({response}) => { if (executeCount < 3) { console.log('失敗したので再度リクエストします') return fetch(++executeCount) } return response.data.message }) } fetch().then(res => console.log(res))間隔を開けてリクエストする
最後に、リトライ時、リクエストごとに間隔を開ける処理を実装しましょう。
リトライ時の挙動の調整ですから、再帰でfetchを呼び出している部分を修正します。〜秒処理を待つ、といえば setTimeout ですね。
console.log('a') setTimeout(() => { console.log('b') }, 1) console.log('c') //出力順 // a // c // bでは、setTimeout内で実行される関数から、返り値を受け取りたいときはどうすれば良いでしょう。
const res = setTimeout(() => { return 'b' }, 1)これで res = 'b' となるでしょうか? 違いますね。
setTimeoutは、指定した時間(第2引数)待って、第1引数に設定した処理を行う、というタイマーを設定する関数です。
setTimeoutの返り値は、そのとき設定されたタイマーのidです。正解は、setTimeout全体をPromiseでラップして、返したい値でresolveすることです。
const res = new Promise(resolve => { setTimeout(() => { resolve('b') }, 1) }) res.then(data => console.log(data)) // b が出力されるしたがって、修正は以下のようになります。
3秒待つので、setTimeout(() => {}, 3000)、setTimeout自体をPromiseでラップして、返したい値(fetch(++executeCount)) でresolveしています。const axios = require('axios') const url = 'https://sheltered-island-72837.herokuapp.com/' const fetch = (executeCount = 1) => { return axios .get(url) .then(({data}) => data.message) .catch(({response}) => { if (executeCount < 3) { return new Promise(resolve => { setTimeout(() => { resolve(fetch(++executeCount)) }, 3000) }) } return response.data.message }) } fetch().then(res => console.log(res))実際に外部のAPIを叩く時でも、エラーが返ってくるということはAPIに何かしら不具合・過負荷が起きている可能性がありますから、優しいリクエストを送ってあげることは大事です。
よくわからなかったという人へ
解説と言いつつ全く詳細を説明していませんので、初心者の方にはちんぷんかんぷんだと思います。
(当たり前のように require とか使ってるし、Promiseって何なのかも説明していないし...)
でも、Javascriptを書いていく上で理解しておいた方がいいことばかりなので、勉強しましょう!
特にPromiseは大事です。フロントエンドでもサーバーサイドでも、Javascriptにおいて非同期処理の扱いはとっっても大事です。https://sheltered-island-72837.herokuapp.com/
上記APIはしばらく稼働させておきますので、練習にお使いください。
(節度のあるリクエストをお願いします!)
- 投稿日:2019-03-30T18:58:55+09:00
タブレットとWeb両立プロジェクト開発(二)Cordovaをインストールします
Cordovaの特徴
Cordova(コルドバ)とは、オープンソースのハイブリッドアプリ開発フレームワークです。複数のプラットフォームで動作するモバイルアプリをワンソースで開発できます。
Cordovaは、Web標準技術(HTML5+CSS+JavaScript)を使って、iPhone/Androidなどの複数のモバイルOSに対応したクロスプラットフォームなアプリを開発することができるフレームワークです。
複数のOSに対してそれぞれネイティブアプリを開発する作業と比較すると、1つのソースコードで複数OSに対応できるため、大幅に開発コストを削減できます。Webアプリと同様の技術で開発するため学習コストも削減できます。Cordova CLIのインストール
Cordova CLIとは、Cordovaのコマンドラインインターフェイスです。
Cordova CLIは、すぐに使用できる形式でのnpm(Node.jsのパッケージ管理ツール)パッケージとして配布されるので、npmでCordovaのCLIをインストールします。npm install -g cordova #以下の結果が表示 added 438 packages from 349 contributors in 20.705s ╭───────────────────────────────────────────────────────────────╮ │ New minor version of npm available! 6.7.0 → 6.9.0 │ │ Changelog: https://github.com/npm/cli/releases/tag/v6.9.0 │ │ Run npm install -g npm to update! │ ╰───────────────────────────────────────────────────────────────╯インストール完了後、バージョンを確認しておきます。
cordova -v 9.0.0Cordovaプロジェクトの作成
Cordova CLIを使って、プロジェクトの作成を行います。今回は“Hello Cordova!”を出力するCordovaプロジェクトを作ります。
ちなみに、このプロジェクトはiOSまたはAndroidを全部対象とします。#プロジェクト作成コマンドの構造: cordova create ディレクトリ名 識別子 アプリ名 cordova create Hello com.demo.hello HelloCordovaHelloというディレクトリが作成されたので、次の処理が行われる前にHelloに移動必要です。
Cordovaプラットフォームを追加
作成したプロジェクトが対応するプラットフォーム用のファイルをCLIで追加します。
iosを追加すれば、コマンドは、cordova platform add iosとします。
androidを追加すれば、コマンドは、cordova platform add androidとします。
今回は、iosなので、以下を実行します。cd Hello cordova platform add ios #以下が表示 iOS project created with cordova-ios@5.0.0 Discovered plugin "cordova-plugin-whitelist" in config.xml. Adding it to the project Installing "cordova-plugin-whitelist" for ios Adding cordova-plugin-whitelist to package.json追加したらプラットフォームの確認をしておきましょう。
cordova platform ls #以下が表示 Installed platforms: ios 5.0.0 Available platforms: android ^8.0.0 browser ^6.0.0 electron ^1.0.0 osx ^5.0.0 windows ^7.0.0?削除コマンドは次の通りです。
cordova platform rm ios
あとはビルドやデバッグをVSCodeの拡張機能を使って実行できるようにします。
- 投稿日:2019-03-30T18:55:22+09:00
ESLint v5.16.0
前 v5.15.0 | 次 (2019/04/13 JST)
ESLint v5.16.0 has been released: https://t.co/z2dnP92J8r
— ESLint (@geteslint) March 30, 2019
Formatters can now use rule metadata (e.g., documentation URL) in their output. This was originally designed through our RFC process: https://t.co/vWiQLGFOYBESLint
5.16.0がリリースされました。
小さな機能追加とバグ修正が行われました。特に問題がなければ、これが ESLint 5.x 系の最後のリリースになります。次回は ESLint
6.0.0プレリリースの予定です。質問やバグ報告等ありましたら、お気軽にこちらまでお寄せください。
? 本体への機能追加
RFC 10: カスタム フォーマッターからルールの定義データにアクセスできるようになりました。
従来はフォーマッターはリント結果のみにアクセスできました。しかし、各ルールのメタデータ (例えば各ルールのドキュメントへの URL) を使いたいという要望が強くあったため、これにアクセスできるようになりました。
詳細はドキュメントを参照ください。
RFC 10: フォーマッター
json-with-metadataが追加されました。リント結果を、各ルールのメタデータを含む JSON 形式で出力するフォーマッターです。
? 新しいルール
特になし。
? オプションが追加されたルール
特になし。
✒️
eslint --fixをサポートしたルール特になし。
⚠️ 非推奨になったルール
特になし。
- 投稿日:2019-03-30T18:40:50+09:00
Stackdriver Debugger で簡単に本番環境をデバッグする
はじめに
本番環境にデプロイしたらバグが出て、デバッグしたいけど本番環境のソースコードにログを仕込んで再起動を繰り返さなきゃみたいなこと、経験したことないでしょうか。
本番環境にブレークポイントや変数を見るためにログを仕込む、そんなことを簡単に行えるのが Stackdriver Debugger です。
今回は Node.js を使って、Google Compute Engine(GCE)にデプロイして Stackdriver Debugger がどんな風に使えるか試してみます。
本記事の執筆時点(2019/03)では、Node.js の Stackdriver Debugger エージェントがベータ版です。
変更になる可能性があることに注意してください。環境
- macOS High Sierra Version 10.13.6
- Node.js v10.15.3
- npm 6.9.0
- Google Cloud SDK 240.0.0
- express 4.16.4
- @google-cloud/debug-agent 3.1.0
Stackdriver Debugger とは
Stackdriver Debugger は、実行中のアプリケーションに対して現時点のアプリケーションの状態を確認できるサービスです。
アプリケーションの処理速度は低下せず、ユーザーに影響しないと言われています。
用途としては、本番でしか再現しないコードを調査したい場合などに大きな威力を発揮します。Google Cloud SDK をインストールする
以下のドキュメントを参考に Google Cloud SDK をインストールし、プロジェクトの設定をしてください。
Google Cloud SDK documentationサンプルアプリケーションの作成
Node.js が事前にインストールされている前提で話を進めます。
インストールされていない場合は、事前に Node.js のインストールを実施してください。まずは、作成するサンプルアプリケーションを Git で管理します。
今回は、GCP で完結させるために Cloud Source Repositories を利用します。
バージョン管理システムとしては、Cloud Source Repositories の他に GitHub や Bitbucket、GitLab などに対応しています。
それではリポジトリを作成してローカルに clone します。
サンプルとしてstackdriver-debugger-sampleという名前で作成しています。$ gcloud source repos create stackdriver-debugger-sample $ gcloud source repos clone stackdriver-debugger-sampleまずは express を利用した Node.js のアプリケーションを作成します。
プロジェクトを作成して express をインストールします。$ cd stackdriver-debugger-sample $ npm init -y $ npm install --save express以下のような 0 から 10 までの数値をランダム生成して、5 より大きい、または小さいを判断する index.js を作成します。
index.jsconst express = require('express'); const PORT = 80; const HOST = '0.0.0.0'; const app = express(); const getRandomNumber = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; app.get('/', (req, res) => { const num = getRandomNumber(0, 10); if (num > 5) { res.send('5 より大きい'); } else { res.send('5 より小さい'); } }); app.listen(PORT, HOST); console.log(`Running on http://${HOST}:${PORT}`);サンプルアプリケーションの確認をしましょう。
index.js を起動します。$ sudo node index.js別の shell でリクエストすると以下のようなレスポンスであることを確認します。
$ curl http://127.0.0.1 5 より小さいサンプルアプリケーションを Git で管理していきましょう。
不要なファイルをコミットしないように、.gitignoreファイルを作成します。
gitignore.io というサイトから Node.js 用の.gitignoreファイルの設定を利用します。$ curl https://www.gitignore.io/api/node > .gitignore最初のコミットをしましょう。
$ git add . $ git commit -m 'initial commit'次に Node.js 用の Stackdriver Debugger のパッケージをインストールします。
$ npm install --save @google-cloud/debug-agentインストールしたパッケージを利用するために、アプリケーションのエントリポイントである index.js の先頭で Stackdriver Debugger のエージェントを有効にします。
index.js+require('@google-cloud/debug-agent').start({ + allowExpressions: true, + serviceContext: { + service: process.env.SERVICE, + version: process.env.VERSION, + } +}); + const express = require('express'); const PORT = 80; const HOST = '0.0.0.0';
allowExpressionsは、後述するログポイントでメッセージ形式を指定するに当たって必要なため有効にします。
serviceには、アプリケーションの名前、versionには、アプリケーションのバージョンを設定します。
これらの値をデプロイするたびに変えられるように環境変数から設定できるようにしておきます。再度コミットしてプッシュします。
$ git commit -am 'Add Stackdriver Debugger agent' $ git push origin masterGCE へデプロイ
それでは GCE のインスタンスにソースコードを配置しデバッグを行うため、GCE のインスタンスを作成します。
スコープに Cloud Source Repositories の読み取り権限と Stackdriver Debugger の権限を付与しておきます。
サンプルとしてexample-instanceという名前で作成しています。
作成すると以下のような情報が表示されます。$ gcloud compute instances create example-instance \ --zone asia-northeast1-a \ --machine-type g1-small \ --image-family ubuntu-1804-lts \ --image-project ubuntu-os-cloud \ --scopes https://www.googleapis.com/auth/source.read_only,https://www.googleapis.com/auth/cloud_debugger \ --tags http-server Created [https://www.googleapis.com/compute/v1/projects/stackdriver-debugger-235505/zones/asia-northeast1-a/instances/example-instance]. NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS example-instance asia-northeast1-a g1-small 10.146.0.7 34.85.116.83 RUNNING作成した GCE のインスタンスに SSH 接続します。
$ gcloud compute ssh example-instanceNode.js のアプリケーションなので、Node.js と npm をインストールします。
$ sudo apt update && sudo apt install -y nodejs npmCloud Source Repositories のリポジトリから作成したアプリケーションをインスタンス内に clone して、npm module のインストールを行います。
$ gcloud source repos clone stackdriver-debugger-sample $ cd stackdriver-debugger-sample $ npm installソースコードが自動で選択されるようにソースコンテキスト情報を含んだファイル
source-context.jsonを生成します。
アプリケーションと一緒にバージョン管理する必要はありませんが、このファイルを含めた状態でデプロイする必要があります。
今回はサンプルのためインスタンス内で生成します。$ gcloud debug source gen-repo-info-fileサンプルアプリケーションを起動します。
起動時に、環境変数から設定できるようにした Stackdriver Debugger エージェントの変数を指定します。$ sudo SERVICE=sample VERSION=1.0.0 node index.js試しに別の shell でリクエストすると正常にレスポンスされることが分かります。
$ curl 34.85.116.83 5 より小さいデバッガを設定する
Google Cloud Console の左メニューから「デバッグ」メニューを開きます。
すると以下のようにデバッグ画面が開きます。デバッグ画面が表示されたところで、スナップショットの表示から 22 行目をクリックしてブレークポイントを設定します。
この時点で変数numにどんな値が入っているか確認できることを期待します。
再びリクエストを行うとブレークポイントの時点の変数やコールスタックが確認できます。
そして変数numは 4 であることが分かりました。ログポイントを設定する
ログポイントはデプロイしたアプリケーションのままログ出力を設定できる機能です。
これによって、本番で確認したいがために本番へログインしてソースコードを書き換え再起動するという手間が無くなります。先ほどの画面からログポイントの画面に切り替えます。
再び 22 行目をクリックすると、ログポイントの設定が表示されます。
ログポイントにはログが出力される条件とログのメッセージ形式が指定できます。
trueの部分が出力条件、var = {var}の部分がメーセージ形式を指定する項目です。
またログレベルも指定でき、「情報」・「警告」・「エラー」の 3 種類から選択できます。今回は、メッセージ形式に
num = {num}を指定し追加をクリックしましょう。
追加をすると 22 行目にnum = {num}が追加されたことが分かります。追加後、サンプルアプリケーションに何度かリクエストすると、サンプルアプリケーションを起動した標準出力にログポイントのログが出力されていることが分かります。
GCE の場合は、ログの出力先がアプリケーションの標準出力になります。
ログビューア上で見る場合は、別途ロギングエージェントなどを利用した転送が必要です。
GAE や GKE であれば、標準で Stackdriver Logging に対応しているため Stackdriver Logging のリクエストログから確認できます。$ sudo SERVICE=sample VERSION=1.0.0 node index.js Running on http://0.0.0.0:80 LOGPOINT: num = 8 LOGPOINT: num = 7 LOGPOINT: num = 10 LOGPOINT: num = 8 LOGPOINT: num = 4次にログポイントの条件を指定してみましょう。
先ほど作成したログポイントを編集して、条件にnum > 5と入力し適用をクリックすると編集完了です。再びサンプルアプリケーションに何度かリクエストすると、
num > 5の条件の場合にのみログ出力されることが分かります。$ sudo SERVICE=sample VERSION=1.0.0 node index.js Running on http://0.0.0.0:80 LOGPOINT: num = 10 LOGPOINT: num = 9 LOGPOINT: num = 8 LOGPOINT: num = 8 LOGPOINT: num = 6お掃除
以下のコマンドを実行し、
yでリポジトリを削除します。$ gcloud source repos delete stackdriver-debugger-sample以下のコマンドを実行し、
yでインスタンスを削除します。$ gcloud compute instances delete example-instanceさいごに
ここまで Stackdriver Debugger を実際に使ってアプリケーションのデバッグを試しました。
Stackdriver Debugger を使えば簡単かつ便利に本番環境でもデバッグできることがお分かりいただけたのではないでしょうか。
是非有効にして、アプリケーションの調査に役立てましょう。参考
【GCP 入門編・第 24 回】 Stackdriver Debugger で本番環境のデバッグを行おう! | 株式会社トップゲート
Stackdriver Debugger documentation | Stackdriver Debugger
- 投稿日:2019-03-30T06:28:58+09:00
KubernetesとNode.jsでマイクロサービスを作成する 5/8 Dockerを使ったサービス構築
第5章 Dockerを使ったサービス構築
第4章までで、Twitterライクなマイクロサービスを構成する、3つのサービスを作成してきました。
次はいよいよ、これらのサービスをマイクロサービスとして構築するのですが、いきなりKubernetesを利用する前にワンクッションはさみます。本章では、各サービスのDockerイメージを作成し、以下のような構成で、ローカルのDocker環境でTwitterライクなサービスを起動します。
完成版のリポジトリはこちらにあるので、実装がうまくいかない場合は比較してみてください。
reireias/microservice-sample-integrationチュートリアル全体
- 第1章 概要
- 第2章 Tweetサービス
- 第3章 Userサービス
- 第4章 Webサービス
- 第5章 Dockerを使ったサービス構築
- 第6章 Kubernetes with minikube
- 第7章 Kubernetes with GCP
- 第8章 Kubernetes with AWS
構成
dockerとdocker-compose
まずはローカルに
dockerとdocker-composeがインストールされていることを確認しましょう。docker version Client: Version: 18.06.1-ce API version: 1.38 Go version: go1.10.4 Git commit: e68fc7a Built: Fri Jan 25 14:33:54 2019 OS/Arch: linux/amd64 Experimental: false Server: Engine: Version: 18.06.1-ce API version: 1.38 (minimum version 1.12) Go version: go1.10.4 Git commit: e68fc7a Built: Thu Jan 24 10:56:33 2019 OS/Arch: linux/amd64 Experimental: falsedocker-compose version docker-compose version 1.23.2, build 1110ad0 docker-py version: 3.7.0 CPython version: 3.7.2 OpenSSL version: OpenSSL 1.0.2g 1 Mar 2016microservice-sample-integrationリポジトリの作成
ここからは、各サービスのリポジトリ内のコードを利用して、DockerイメージやKubernetesの設定を作成していくことになります。
そのためには、各サービスのコードが参照できる構成が望ましいです。今回は下記のディレクトリ構成をとるような、
microservice-sample-integrationリポジトリを作成します。microservice-sample-integration └── services ├── microservice-sample-tweet ├── microservice-sample-user └── microservice-sample-web実際の開発現場では、このリポジトリの管理をSREチームが主導して行ったり、各サービスの開発者が協力して行ったりします。
microservice-sample-integrationという名前でリポジトリを作成し、cloneしておきましょう。つづいて、このリポジトリ内で行う各種操作を
Makefileに記述し、makeコマンドから実行できるようにします。
作成するコマンドは下記の通りです。
clone: 各サービスをservicesディレクトリ以下にcloneするpull: 各サービスをgit pullするbuild: 各サービスのDockerイメージのビルドを行うup: 全サービスをdocker-composeを使って、ローカルのDocker環境で立ち上げるdown: 全サービスをdocker-composeを使って削除するseed: UserサービスとTweetサービスでscripts/initialize.jsを実行する
Makefileは以下のようになります。MakefileWEB_REPOSITORY := https://github.com/reireias/microservice-sample-web USER_REPOSITORY := https://github.com/reireias/microservice-sample-user TWEET_RESPOSITORY := https://github.com/reireias/microservice-sample-tweet clone: git clone $(WEB_REPOSITORY) ./services/microservice-sample-web git clone $(USER_REPOSITORY) ./services/microservice-sample-user git clone $(TWEET_RESPOSITORY) ./services/microservice-sample-tweet pull: cd ./services/microservice-sample-web && git pull cd ./services/microservice-sample-user && git pull cd ./services/microservice-sample-tweet && git pull build: docker-compose build up: docker-compose up -d down: docker-compose down seed: docker-compose exec user node /app/scripts/initialize.js docker-compose exec tweet node /app/scripts/initialize.js
Makefileが作成できたら、make pullを実行し、各サービスをservicesディレクトリ配下へcloneします。make cloneDockerイメージの作成
つづいて、各サービスのDockerイメージを作成するための、Dockerファイルを実装していきます。
このファイルから生成されるDockerイメージは本番環境でも使用されます。
実際の開発現場では各サービスが責任を持って作成する場合もありますし、SREチームなど、Dockerのノウハウに長けたチームがサービス横断で管理するケースもあるでしょう。services/microservice-sample-tweet/DockerfileFROM node:11.10-alpine ENV NODE_ENV=production WORKDIR /app RUN apk add --no-cache curl && \ curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | sh ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait RUN chmod +x /wait COPY package.json . RUN yarn install && ./bin/node-prune COPY . . EXPOSE 3000 CMD /wait && yarn startservices/microservice-sample-user/DockerfileFROM node:11.10-alpine ENV NODE_ENV=production WORKDIR /app RUN apk add --no-cache curl && \ curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | sh ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait RUN chmod +x /wait COPY package.json . RUN yarn install && ./bin/node-prune COPY . . EXPOSE 3000 CMD /wait && yarn startservices/microservice-sample-web/DockerfileFROM node:11.10-alpine as builder ENV NODE_ENV=production ARG GITHUB_CLIENT_ID ARG GITHUB_CLIENT_SECRET WORKDIR /app RUN apk add --no-cache curl && \ curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | sh COPY package.json . COPY yarn.lock . RUN yarn install && ./bin/node-prune COPY . . RUN yarn build FROM node:11.10-alpine WORKDIR /app ADD package.json ./ ADD nuxt.config.js ./ COPY --from=builder ./app/server ./server COPY --from=builder ./app/node_modules ./node_modules COPY --from=builder ./app/.nuxt ./.nuxt EXPOSE 3000 CMD yarn startWebサービスに関してはマルチステージビルドを利用して最終的なイメージのサイズを軽くするように工夫しています。
実装後、各
Dockerfileはcommitしておきましょう。docker-compose.ymlの作成
上記で用意した
Dockerfileを、dockerコマンドを利用してビルド、コンテナの起動等やってもよいのですが、環境変数等、引数が多くなってしまうため、今回はdocker-composeを利用して一元管理します。
docker-compose.ymlに起動したいコンテナの設定を書いていくことで、一括で立ち上げたり、削除することが可能です。今回は、Webサービス、Userサービス、Tweetサービスと、Userサービス用DB、Tweetサービス用DBの合計5つのコンテナを定義します。
docker-compose.yml--- version: '3' services: web: build: context: ./services/microservice-sample-web dockerfile: Dockerfile image: microservice_web:1.0 ports: - 3000:3000 environment: NUXT_HOST: 0.0.0.0 USER_SERVICE: http://user:3000 TWEET_SERVICE: http://tweet:3000 GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID} GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} user: build: context: ./services/microservice-sample-user dockerfile: Dockerfile image: microservice_user:1.0 environment: MONGODB_URL: mongodb://user-db:27017/user MONGODB_ADMIN_NAME: root MONGODB_ADMIN_PASS: example WAIT_HOSTS: user-db:27017 depends_on: - user-db user-db: image: mongo volumes: - user-db:/data/db environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example tweet: build: context: ./services/microservice-sample-tweet dockerfile: Dockerfile image: microservice_tweet:1.0 environment: MONGODB_URL: mongodb://tweet-db:27017/user MONGODB_ADMIN_NAME: root MONGODB_ADMIN_PASS: example WAIT_HOSTS: tweet-db:27017 USER_SERVICE: http://user:3000 depends_on: - tweet-db tweet-db: image: mongo volumes: - tweet-db:/data/db environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example volumes: user-db: tweet-db:GitHubのOAuthのキー等はGit管理する対象ではないので、
.envファイルに記述します。
docker-composeの機能により、カレントディレクトリの.envファイルが読み込まれ、docker-compose.yml中で利用することができるようになります。
値はDocker環境の場合エンドポイントがlocalhost:3000と、前章までと同じなため、前章で使用していたのと同じGitHubのOAuth設定が利用可能です。.envGITHUB_CLIENT_ID=xxxxxxxxxxxxxxxxxxx GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
services/.gitkeepファイルを作成しておきます。touch services/.gitkeep
.gitignoreファイルを以下のように設定します。.gitignoreservices/* !services/.gitkeep .envサービスを起動してみる
それでは、サービスを起動してみます。
# Dockerイメージをビルド make build # サービスの立ち上げ make up # ダミーデータの投入 make seed
http://localhost:3000へアクセスしてみましょう。
問題なく動作していれば本章の実装は完了になります。第5章まとめ
本章では、各サービスの
Dockerfileを作成し、docker-composeを利用してマイクロサービスをローカルのDocker環境上に構築しました。次の章ではいよいよKubernetes上でマイクロサービスを動作させてみます。
- 投稿日:2019-03-30T06:28:58+09:00
KubernetesとNode.jsでマイクロサービスを作成する 5/6 Dockerを使ったサービス構築
第5章 Dockerを使ったサービス構築
第4章までで、Twitterライクなマイクロサービスを構成する、3つのサービスを作成してきました。
次はいよいよ、これらのサービスをマイクロサービスとして構築するのですが、いきなりKubernetesを利用する前にワンクッションはさみます。本章では、各サービスのDockerイメージを作成し、以下のような構成で、ローカルのDocker環境でTwitterライクなサービスを起動します。
完成版のリポジトリはこちらにあるので、実装がうまくいかない場合は比較してみてください。
reireias/microservice-sample-integrationチュートリアル全体
構成
dockerとdocker-compose
まずはローカルに
dockerとdocker-composeがインストールされていることを確認しましょう。docker version Client: Version: 18.06.1-ce API version: 1.38 Go version: go1.10.4 Git commit: e68fc7a Built: Fri Jan 25 14:33:54 2019 OS/Arch: linux/amd64 Experimental: false Server: Engine: Version: 18.06.1-ce API version: 1.38 (minimum version 1.12) Go version: go1.10.4 Git commit: e68fc7a Built: Thu Jan 24 10:56:33 2019 OS/Arch: linux/amd64 Experimental: falsedocker-compose version docker-compose version 1.23.2, build 1110ad0 docker-py version: 3.7.0 CPython version: 3.7.2 OpenSSL version: OpenSSL 1.0.2g 1 Mar 2016microservice-sample-integrationリポジトリの作成
ここからは、各サービスのリポジトリ内のコードを利用して、DockerイメージやKubernetesの設定を作成していくことになります。
そのためには、各サービスのコードが参照できる構成が望ましいです。今回は下記のディレクトリ構成をとるような、
microservice-sample-integrationリポジトリを作成します。microservice-sample-integration └── services ├── microservice-sample-tweet ├── microservice-sample-user └── microservice-sample-web実際の開発現場では、このリポジトリの管理をSREチームが主導して行ったり、各サービスの開発者が協力して行ったりします。
microservice-sample-integrationという名前でリポジトリを作成し、cloneしておきましょう。つづいて、このリポジトリ内で行う各種操作を
Makefileに記述し、makeコマンドから実行できるようにします。
作成するコマンドは下記の通りです。
clone: 各サービスをservicesディレクトリ以下にcloneするpull: 各サービスをgit pullするbuild: 各サービスのDockerイメージのビルドを行うup: 全サービスをdocker-composeを使って、ローカルのDocker環境で立ち上げるdown: 全サービスをdocker-composeを使って削除するseed: UserサービスとTweetサービスでscripts/initialize.jsを実行する
Makefileは以下のようになります。MakefileWEB_REPOSITORY := https://github.com/reireias/microservice-sample-web USER_REPOSITORY := https://github.com/reireias/microservice-sample-user TWEET_RESPOSITORY := https://github.com/reireias/microservice-sample-tweet clone: git clone $(WEB_REPOSITORY) ./services/microservice-sample-web git clone $(USER_REPOSITORY) ./services/microservice-sample-user git clone $(TWEET_RESPOSITORY) ./services/microservice-sample-tweet pull: cd ./services/microservice-sample-web && git pull cd ./services/microservice-sample-user && git pull cd ./services/microservice-sample-tweet && git pull build: docker-compose build up: docker-compose up -d down: docker-compose down seed: docker-compose exec user node /app/scripts/initialize.js docker-compose exec tweet node /app/scripts/initialize.js
Makefileが作成できたら、make pullを実行し、各サービスをservicesディレクトリ配下へcloneします。make cloneDockerイメージの作成
つづいて、各サービスのDockerイメージを作成するための、Dockerファイルを実装していきます。
このファイルから生成されるDockerイメージは本番環境でも使用されます。
実際の開発現場では各サービスが責任を持って作成する場合もありますし、SREチームなど、Dockerのノウハウに長けたチームがサービス横断で管理するケースもあるでしょう。services/microservice-sample-tweet/DockerfileFROM node:11.10-alpine ENV NODE_ENV=production WORKDIR /app RUN apk add --no-cache curl && \ curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | sh ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait RUN chmod +x /wait COPY package.json . RUN yarn install && ./bin/node-prune COPY . . EXPOSE 3000 CMD /wait && yarn startservices/microservice-sample-user/DockerfileFROM node:11.10-alpine ENV NODE_ENV=production WORKDIR /app RUN apk add --no-cache curl && \ curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | sh ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait RUN chmod +x /wait COPY package.json . RUN yarn install && ./bin/node-prune COPY . . EXPOSE 3000 CMD /wait && yarn startservices/microservice-sample-web/DockerfileFROM node:11.10-alpine as builder ENV NODE_ENV=production ARG GITHUB_CLIENT_ID ARG GITHUB_CLIENT_SECRET WORKDIR /app RUN apk add --no-cache curl && \ curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | sh COPY package.json . COPY yarn.lock . RUN yarn install && ./bin/node-prune COPY . . RUN yarn build FROM node:11.10-alpine WORKDIR /app ADD package.json ./ ADD nuxt.config.js ./ COPY --from=builder ./app/server ./server COPY --from=builder ./app/node_modules ./node_modules COPY --from=builder ./app/.nuxt ./.nuxt EXPOSE 3000 CMD yarn startWebサービスに関してはマルチステージビルドを利用して最終的なイメージのサイズを軽くするように工夫しています。
実装後、各
Dockerfileはcommitしておきましょう。docker-compose.ymlの作成
上記で用意した
Dockerfileを、dockerコマンドを利用してビルド、コンテナの起動等やってもよいのですが、環境変数等、引数が多くなってしまうため、今回はdocker-composeを利用して一元管理します。
docker-compose.ymlに起動したいコンテナの設定を書いていくことで、一括で立ち上げたり、削除することが可能です。今回は、Webサービス、Userサービス、Tweetサービスと、Userサービス用DB、Tweetサービス用DBの合計5つのコンテナを定義します。
docker-compose.yml--- version: '3' services: web: build: context: ./services/microservice-sample-web dockerfile: Dockerfile image: microservice_web:1.0 ports: - 3000:3000 environment: NUXT_HOST: 0.0.0.0 USER_SERVICE: http://user:3000 TWEET_SERVICE: http://tweet:3000 GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID} GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} user: build: context: ./services/microservice-sample-user dockerfile: Dockerfile image: microservice_user:1.0 environment: MONGODB_URL: mongodb://user-db:27017/user MONGODB_ADMIN_NAME: root MONGODB_ADMIN_PASS: example WAIT_HOSTS: user-db:27017 depends_on: - user-db user-db: image: mongo volumes: - user-db:/data/db environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example tweet: build: context: ./services/microservice-sample-tweet dockerfile: Dockerfile image: microservice_tweet:1.0 environment: MONGODB_URL: mongodb://tweet-db:27017/user MONGODB_ADMIN_NAME: root MONGODB_ADMIN_PASS: example WAIT_HOSTS: tweet-db:27017 USER_SERVICE: http://user:3000 depends_on: - tweet-db tweet-db: image: mongo volumes: - tweet-db:/data/db environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example volumes: user-db: tweet-db:GitHubのOAuthのキー等はGit管理する対象ではないので、
.envファイルに記述します。
docker-composeの機能により、カレントディレクトリの.envファイルが読み込まれ、docker-compose.yml中で利用することができるようになります。
値はDocker環境の場合エンドポイントがlocalhost:3000と、前章までと同じなため、前章で使用していたのと同じGitHubのOAuth設定が利用可能です。.envGITHUB_CLIENT_ID=xxxxxxxxxxxxxxxxxxx GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
services/.gitkeepファイルを作成しておきます。touch services/.gitkeep
.gitignoreファイルを以下のように設定します。.gitignoreservices/* !services/.gitkeep .envサービスを起動してみる
それでは、サービスを起動してみます。
# Dockerイメージをビルド make build # サービスの立ち上げ make up # ダミーデータの投入 make seed
http://localhost:3000へアクセスしてみましょう。
問題なく動作していれば本章の実装は完了になります。第5章まとめ
本章では、各サービスの
Dockerfileを作成し、docker-composeを利用してマイクロサービスをローカルのDocker環境上に構築しました。次の章ではいよいよKubernetes上でマイクロサービスを動作させてみます。







