- 投稿日:2020-02-12T23:50:10+09:00
【NestJS】ヘルスチェック
やりたいこと
NestJS x TypeORM の環境で、DBまで一気通貫したヘルスチェック用URLを作りたい。
terminusというNodeJS用のパッケージで、NestJS用のものがあるので、それを使います。環境
インストール
yarn add @nestjs/terminus @godaddy/terminus --no-optional // or npm install --save @nestjs/terminus @godaddy/terminus --no-optional参照: https://github.com/nestjs/terminus#installation
実装
ほとんど、↓のページに書いている通りです。
- https://github.com/nestjs/terminus#usage
- ↑との違いは、
async (): Promise<HealthIndicatorResult>
として、戻り値の型を定義しているぐらいです。
src/health/health.module.ts
に置いていますが、場所は任意です。src/health/health.module.tsimport { TYPE_ORM_CONFIG } from '../config/app.config' import { Module } from '@nestjs/common' import { TerminusModule, TerminusModuleOptions, TypeOrmHealthIndicator, HealthIndicatorResult, } from '@nestjs/terminus' import { TypeOrmModule } from '@nestjs/typeorm' const getTerminusOptions = ( db: TypeOrmHealthIndicator ): TerminusModuleOptions => ({ endpoints: [ { url: '/health', healthIndicators: [ // Set the timeout for a response to 300ms async (): Promise<HealthIndicatorResult> => db.pingCheck('database', { timeout: 300 }), ], }, ], }) @Module({ imports: [ TypeOrmModule.forRoot(TYPE_ORM_CONFIG), TerminusModule.forRootAsync({ inject: [TypeOrmHealthIndicator], useFactory: (db) => getTerminusOptions(db), }), ], }) export class HealthModule {}
src/app.module.ts
でimports
に追加します。src/app.module.tsimport { HealthModule } from './health/health.module' // この行と、、、 imports: [ TypeOrmModule.forRoot(TYPE_ORM_CONFIG), HealthModule, // この行を追加前提
↑の前提として、
TYPE_ORM_CONFIG
(config/app.config
)に、DBの定義情報が設定されている必要があります。以下、例です。src/config/app.config.tsimport { TypeOrmModuleOptions } from '@nestjs/typeorm' export const TYPE_ORM_CONFIG: TypeOrmModuleOptions = { type: 'mysql', charset: 'utf8mb4_bin', host: process.env.DB_HOST || 'localhost', port: +(process.env.DB_PORT || 3306), username: process.env.DB_USERNAME || 'root', password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE || 'your_database_name_comes_here', entities: [__dirname + '/../**/*.entity.{js,ts}'], }動作確認ログ
$ curl -X GET "http://localhost:3000/health" {"status":"ok","info":{"database":{"status":"up"}},"details":{"database":{"status":"up"}}}%// DBに対して SELECT 1 が実行されていることが確認できます。 $ yarn start:dev : {"level":30,"time":1581485016754,"pid":62278,"hostname":"xl.local","msg":"Server listening at http://0.0.0.0:3000","v":1} query: SELECT 1
- 投稿日:2020-02-12T17:52:04+09:00
Node.js + Express サーバから、Docker(+Docker-Compose) + Redis サーバーにデータを送る・削除する
Node.js サーバーから、Docker サーバにデータを送る
- Ubuntuサーバー環境は2つ使ってます
- マシンA
- Node.js + Express + express-session + connect-redis + Redis
- マシンB
- Docker + Docker-Compose + Redis
マシンA から マシンB を参照してデータを表示したかったので、こんな構成になってます。
色々サイト見てたけど、実際にやってみないとコレわかんねーわって思った。マシンAの下準備
色々インストールする
Ubuntu と Node.js は既に導入済を想定
必要なものをインストールする
$ mkdir exp_redis_test $ cd exp_redis_test $ npm install --save express \ express-session \ connect-redis \ redisマシンBの下準備
前回作ってたので、それを使う
マシンBのRedis起動しておく
docker-compose up -d --buildマシンAで index.js を作る
node で実行するファイルを作成する
var と const と let が混じってるけど気にしない。
index.jsvar express = require('express'); var app = express(); var session = require('express-session'); var RedisStore = require('connect-redis')(session); const REDIS_HOST_NAME = '192.168.123.223'; const REDIS_PORT_NO = 6379; var redis = require('redis'); let redisClient = redis.createClient(REDIS_PORT_NO, REDIS_HOST_NAME); app.use(session({ secret: 'secret', resave: false, saveUninitialized: true, store: new RedisStore({ client: redisClient }), // Redisの設定 cookie: { path: '/', maxAge: 5 * 1000, } })); app.get('/', (req, res) => { // セッションデータが無ければランダム値を取得 var begginer = req.session.value || Math.random(); // valueという名前でセッションデータを生成 req.session.value = begginer; res.send('Begginer value: ' + begginer); }); app.get('/session-delete', function(req, res) { delete req.session.value; res.send('session variable deleted'); }); app.listen(3000, () => { console.log('Example app listening on port 3000!'); });作ったら保存して起動する
$ node index.js Example app listening on port 3000!
ブラウザで http://192.168.123.223:3000/ を起動すると、
数字は同じにはならないが、↓こんな感じになる
この状態のままで、マシンBを確認する
$ docker exec -it mysqltest_redis_1 bash root@09a7f47d4784:/data# redis-cli 127.0.0.1:6379> keys * 1) "sess:VKmVo2FPTXAlspqLyLHgENmEdHhzK_pS"データが入力されてるのが確認できる。中身を見ると、
$ docker exec -it mysqltest_redis_1 bash root@09a7f47d4784:/data# redis-cli 127.0.0.1:6379> keys * 1) "sess:VKmVo2FPTXAlspqLyLHgENmEdHhzK_pS" 127.0.0.1:6379> get sess:VKmVo2FPTXAlspqLyLHgENmEdHhzK_pS "{\"cookie\":{\"originalMaxAge\":null,\"expires\":null,\"httpOnly\":true,\"path\":\"/\"},\"value\":0.016689389693930634}" 127.0.0.1:6379>こんな感じに、Valueが入力されているのが確認できる。
index.js を再確認したら、session-delete が出来るようにしてあるので、
http://192.168.123.223:3000/session-delete/ にアクセスしてみる。
↓こうなるので、またマシンBを確認する。
$ docker exec -it mysqltest_redis_1 bash root@09a7f47d4784:/data# redis-cli 127.0.0.1:6379> keys * 1) "sess:VKmVo2FPTXAlspqLyLHgENmEdHhzK_pS" 127.0.0.1:6379> get sess:VKmVo2FPTXAlspqLyLHgENmEdHhzK_pS "{\"cookie\":{\"originalMaxAge\":null,\"expires\":null,\"httpOnly\":true,\"path\":\"/\"}}" 127.0.0.1:6379>Valueが削除されているのが確認できる。
参考
- ざっくり分かるRedis入門
- Node.js + Express 4.xで、セッション管理にRedisを使う
- ExpressのSessionデータをRedisで管理する
- 【入門】Redis
- redis-cli コマンド操作まとめ
- Redisのインストール・セットアップ
- Redisの説明
- RedisとElastiCacheを分かりやすくまとめてみた
- システム開発で得たRedis利用ノウハウ
- ttps://future-architect.github.io/articles/20190821/
- RedisサーバのCPU負荷対策パターン
- Commands Introduction
- 投稿日:2020-02-12T12:15:01+09:00
expo-cliというかnpmでconfigure errorが出たときの対応
いつも使ってるexpo-cliを利用しようとしたら、下記のようなエラーに遭遇した。
gyp WARN EACCES current user ("nobody") does not have permission to access the dev dir "/Users/xxxxxx/Library/Caches/node-gyp/12.3.1" gyp WARN EACCES attempting to reinstall using temporary dev dir "/Users/xxxxxx/.anyenv/envs/nodenv/versions/12.3.1/lib/node_modules/expo-cli/node_modules/chokidar/node_modules/fsevents/.node-gyp" gyp WARN install got an error, rolling back install gyp WARN install got an error, rolling back install gyp ERR! configure error gyp ERR! stack Error: EACCES: permission denied, mkdir '/Users/xxxxxx/.anyenv/envs/nodenv/versions/12.3.1/lib/node_modules/expo-cli/node_modules/chokidar/node_modules/fsevents/.node-gyp' gyp ERR! System Darwin 19.3.0 gyp ERR! command "/Users/xxxxxx/.anyenv/envs/nodenv/versions/12.3.1/bin/node" "/Users/xxxxxx/.anyenv/envs/nodenv/versions/12.3.1/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild" gyp ERR! cwd /Users/xxxxxx/.anyenv/envs/nodenv/versions/12.3.1/lib/node_modules/expo-cli/node_modules/chokidar/node_modules/fsevents gyp ERR! node -v v12.3.1 gyp ERR! node-gyp -v v5.0.5 gyp ERR! not ok結論から言えば、下記のように--unsafe-permオプションをつけて実行すればインストール自体はできた。
sudo npm install --unsafe-perm -g expo-cli基本的にroot権限でnpm installすることは推奨されていないようで、場合により--unsafe-permオプションが必要なようです。
- 投稿日:2020-02-12T12:06:10+09:00
Node.js + Express + express-session でセッション変数を使ってみる
Express + express-session でセッション変数を試す
事前準備として、フォルダ作ったりパッケージインストールしたりする
mkdir exps_test cd exps_test npm install --save express \ express-session \ body-parser \ ejshttp で確認したいので、 index.ejs を作っておく
index.ejs は view として作るので、views フォルダを作っておく
views フォルダ内に index.ejs を作る
mkdir views cd views nano index.ejsindex.ejs の内容はコレ
index.ejs<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> <body> <p><%=message %></p> <form action="/" method="post"> <input type="text" name="message"> <button type="submit" name="button">send</button> </form> <a href="/">LastUpdateData</a> </body> </html>exps_test フォルダに戻って、nodeで起動するための index.js を作る
cd ../ nano index.js
index.js の内容はコレ
index.jsconst express = require('express'); const ejs = require('ejs'); const bodyParser = require("body-parser"); const session = require("express-session"); const app = express(); app.set('ejs', ejs.renderFile); app.use(bodyParser.urlencoded({ extended: false })); app.use(session({ secret: "secret", resave: false, saveUninitialized: true, })); app.get('/', (req, res) => { let message = 'no message'; if (req.session.message != undefined) { message = "LastUpdateData:" + req.session.message; } res.render('index.ejs', { message: message }); }); app.post("/", (req, res) => { let message = req.body.message; req.session.message = message; res.render('index.ejs', { message: message }); }); app.listen(3000, () => { console.log('Example app listening on port 3000!'); });node.js で index.js を起動する
$ node index.js Example app listening on port 3000!
ブラウザでアクセスする
何かしらメッセージを入力してSendすると、no message の所が変わります。
LastUpdateData をクリックすると、最後に入力した内容が表示されます。
ブラウザを閉じるとデータが消えるので no message に戻るはず。
node を再起動してもデータが消えるので no message に戻ります。session 設定しているとこに cookie の設定を足すと、データの有効期限が付けられたりします。
↓こんな感じにすると5秒後に LastUpdateData をクリックした場合、 no message に戻ってます。app.use(session({ secret: "secret", resave: false, saveUninitialized: true, cookie: { maxAge: 5 * 1000 } }));参考
- express-sessionでセッションを利用する[Express][node.js]
- 投稿日:2020-02-12T12:04:03+09:00
Cloud Firestore まとめ
概要
- サーバーレスな
KeyValue
のデータストアCloud functions
から利用できる- クライアント側のアプリからも直接アクセスできる
- セキュリティ設定に注意が必要
- データの更新について、リアルタイムに通知が受け取れる
簡単なサンプル
CloudFunctionsからデータを取得する
import * as admin from 'firebase-admin'; const userId = 'test_user' const user = (await admin .firestore() .collection('users') .doc(userId) .get()).data(); console.info(JSON.stringify(user));
firebase-admin
を使用すると、Firestore
のアクセス制限の設定を無視してデータを取得できる
CloudFunctions
から利用するときはfirebase-admin
を利用して、別途CloudFunctions
側でアクセス制限するCloudFunctions
からではなくクライアント側から直接Firestore
にアクセスするような時は、Firestore
のアクセス制限機能を利用する気にするべき特徴
- 1秒に1回しか更新できない
- インデックスがないと検索できない
where
に入れるためにはインデックスが必要- なければ自動で作るためのURLをログに出してくれるので、そんなに面倒ではない
!=
で検索できない
- 国籍が日本以外のデータを検索、みたいなことができない
- 他の国籍全てについて
==
で検索して、全ての結果をマージするしかない- 範囲検索が一つしかできない
- 年齢xx歳以上、体重xx以上、のように複数の範囲条件が指定できない
- 別々に検索してマージするしかない
orderBy
が範囲検索と捉えられるため、実質は一個しか指定できない感じ- よくある「商品データベース」みたいなものに利用すると、検索できなくなって困ると思う
- トランザクションがある
- 既に他の誰かが編集中だったりすると失敗してリトライされる
- 検索条件などに癖があるため、利用はきちんと検討してからにするべき
アクセス制限について
- rulesファイルを書く
- アクセス制限のことを考えてデータ構造を設計しなくてはならない
データの項目にユーザーIDを入れておいてアクセス制限する例
service cloud.firestore { match /databases/{database}/documents { match /users/{uid} { allow list: if request.auth != null && (request.auth.uid == resource.data.uid) } } }グループに所属するユーザーのみにアクセスを許可する例
- グループに所属しているかどうかを調べるのに
where
的に検索したいが、そのようなことはできない- 仕方ないので
コレクション
のキー
をuid
にして、exists
で調べるservice cloud.firestore { match /databases/{database}/documents { match /groups/{gid} { allow read: if request.auth != null && exists(/databases/$(database)/documents/groups/$(gid)/members/$(request.auth.uid)); } } }参考サイト
目次
- 投稿日:2020-02-12T12:02:49+09:00
Cloud Functions まとめ
概要
- アプリケーションのサーバーサイドのプログラムを実行する
- 主に
API
を作成するために利用される- 関数一つが一つの
API
になるイメージAuthentication
サービスと連携することによってアクセス制限が可能簡単なサンプル
- 実行すると、
CloudFunctions
のログ(管理コンソールから確認できる)にHello sfjwr !!
と表示されるCloudFunctions側のAPI
import * as functions from 'firebase-functions'; export const hello = functions.https.onCall( (params, context) => { console.info(`Hello ${params.name} !!`); }, );APIを呼び出すクライアント側
import firebase from 'firebase'; firebase.functions().httpsCallable('hello')({name: 'sfjwr'});気にするべき特徴
Cloud Functions
内へのデータの保存はできない
- 何かを保存したければ、
Cloud Firestore
やCloud Storage
やその他サービスへ渡すしかない実行時間に制限がある
- 最大9分
- そのためバッチ処理には不向き
ユーザー側からの呼び出し以外に、
Firebase
の他サービスからの通知によって実行することもできる
- ファイルが
Cloud Storage
にアップロードされた時
- 自動でサムネイルを作るとか
Authentication
でユーザーが作られた時
- 自サービス用のユーザーデータを
Firestore
に放り込むとか- 定期実行も可能
- 1時間毎とか1日毎とか
関数毎にメモリの割り当てを指定することができる
- 環境変数なども指定できる
Firebase
のコンソールからではなく、Google Cloud Platform
のコンソールから指定するFirebase
といいつつGoogle Cloud Platform
のCloud Functions
と同じなので、Google Cloud Platform
側の画面で色々できる
StackDriver
でログを制御する、とか結構遅い
- レスポンスまでに1秒くらいは見ておいた方がいいかも?
- 特に内部的にインスタンスが立ち上がる時が遅い
- しばらくアクセスがなくて久しぶりにアクセスが会った時とか
- 5秒くらいかかるイメージ
Firestore
からデータを取得する時にループを回して、中でawait
とかすると凄まじく遅くなる
- ループではなく並列取得するべき
参考サイト
目次
- 投稿日:2020-02-12T12:02:05+09:00
Google Firebase まとめ
Firebase
について、まとめていきます。はじめに
中級者が気軽にパッと見てある程度の内容が把握できる、くらいのところを目標に記述していきます。初心者向けではないです。
なるべくわかりやすくするため、基本的に短文箇条書き形式にて記します。
サンプルコードが必要なところはJavaScript
にて記載します。間違いの指摘などあれば、コメントお待ちしております。
Firebaseとは?
- アプリケーションのサーバーサイドを楽に作れる仕組み
- 基本的にサーバーレス
- 例えば以下のような物がサーバーレスで利用できる
- データベース(Firestore)
- ファイルサーバー(Storage)
- API(Cloud functions)
- Webサーバー(Hosting)
サーバーレスとは?
その名の通り、サーバーという概念が無い
- 実際には裏には存在しているが、意識しなくてよいようになっている
- イメージ的には、ファイルやアプリケーションのプログラムをアップローダーからアップロードするだけで動くようになるよ、的な感じ
メリット
- メンテナンスの手間が少ない
- セキュリティパッチやディスクの故障など何も心配する必要がない
- アクセスが急激に増えても自動スケールアウトで勝手にうまく処理してくれる
デメリット
- 従量制なのでかかる費用がよくわからない
- 基本的にはオンプレよりは安くなるはず?
- あまり細かい設定はできない
- 特殊な要件だと対応できないかも
- パフォーマンスチューニングとか
- 一台ごとの性能云々はあまり調整できないが、自動スケールアウトでお金で解決
- レイテンシが問題になることはあるかも?
主要サービスの概要
Cloud Functions
- アプリケーションを実行するためのもの
- 主に
API
を作るために利用できる- 言語は
JavaScript
、Python
、Go
- 手元でコードを書いて、アップロードコマンドを実行することで、
CloudFunctions
に配置できる
- 指定のURLを開く等で実行できる
Cloud Firestore
- KeyValueのデータベース
Cloud Functions
から簡単に利用できる- データの更新をリアルタイムに取得できる
Authentication
- 自作サービスのユーザー登録機能を自分で作らなくてよくするためのもの
- SMSやE-mailによる本人確認
- SNSによる本人確認
Firebase
のその他の機能と連携できる
Authentication
のユーザー情報を利用してCloud Functions
の呼び出しを制限するAuthentication
のユーザー情報を利用してFirestore
やCloud storage
のデータへのアクセスを制限するCloud Storage
- いわゆるファイルサーバー
- サーバーレスなので勝手にスケールする
- 同時接続数とか考えなくても良い
CloudFunctions
からアップロードされたファイルを保存したり等に利用できるHosting
- いわゆるWebサーバー
- サーバーレスなので勝手にスケールする
- 同時接続数とか考えなくても良い
- 静的なファイルをホスティングできる
Webでよくある開発パターン
CloudFunctions
でサーバーサイドのAPIを作る
CloudFunctions
からFirestore
へデータベースのデータを格納する- ファイルは
CloudStorage
へ- クライアントサイドを
SPA
で作る
- 作った
SPA
をHosting
でWebに配置SPA
からはCloudFunctions
のAPI
を叩きに行く各サービス毎のまとめ
以下にまとめて行きます。
- Cloud Functions まとめ
- Cloud Firestore まとめ
- Cloud Storage まとめ(準備中)
- Authentication まとめ(準備中)
- Hosting まとめ(準備中)
参考サイト