- 投稿日:2020-01-15T22:42:36+09:00
NestJsのDB接続周りでハマった話
何を作成する?
Nest.jsで環境ごとにデータベースの接続先を分けるために,接続情報を実行環境の環境変数から非同期で取得し作成する.
環境
- Node.js v12.14.1
- Nest.js v6.7.2
- TypeORM v0.2.22
- Postgresql v11.6
実装ログ
必要最小限の実装
参考)Nest.js Document > TECHNIQUES >Database
ライブラリインストール
TypeORM, Database Driver (Postgresql)をインストールする.
$ npm install @nestjs/typeorm typeorm pgDB接続情報を定義
app.module.tsにデータベースの接続情報を定義する.
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ItemModule } from './item/item.module'; import { Connection } from 'typeorm'; import { join } from 'path'; @Module({ imports: [ ItemModule, // DBの接続情報を定義 TypeOrmModule.forRoot({ type: 'postgres', host: 'localhost', port: 5432, username: 'postgres', password: 'postgres', database: 'postgres', entities: [join(__dirname + '/**/*.entity{.ts,.js}')], synchronize: false, }), ], }) export class AppModule {}一番シンプルな書き方です.自分一人しか触らず,環境もこれだけ!ということであればこの書き方で良いでしょう.
しかし,実際の開発では個人の開発環境,テスト環境,ステージング環境,本番環境と複数の環境が存在し,上記のような実装では環境ごとに接続情報をハードコードし直し → ビルド → デプロイという手順を踏む必要がありナンセンスです.
そのため,通常は環境変数に定義し,接続情報はその環境変数を参照し作成します.
と,いうことで環境変数を参照するようにapp.module.tsを修正します.
環境変数を参照するように実装を修正
※この方法では実行時に依存関係が解決できずエラーとなります.
参考)Nest.js Document > TECHNIQUES > Configuration
ライブラリインストール
環境変数を参照するために必要なライブラリをインストールします.
$ npm install @nestjs/configダミーの環境変数を用意
本来は,環境変数に定義するのですがサンプル実装なので環境変数ファイル(.env)をプロジェクトルートに作成する.
DATABASE_HOST=localhost DATABASE_PORT=5432 DATABASE_USERNAME=postgres DATABASE_PASSWORD=postgres DATABASE_NAME=postgres環境変数を参照するように接続定義を修正
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { ItemModule } from './item/item.module'; import { Connection } from 'typeorm'; import { join } from 'path'; @Module({ imports: [ ItemModule, ConfigModule.forRoot({ envFilePath: '.env', isGlobal: true, // ignoreEnvFile: true, <- 環境変数から取得する場合はコメントアウトを外す. }), // 非同期で環境変数から値を取得し,接続情報を作成する. TypeOrmModule.forRootAsync({ imports: [ConfigModule], useFactory: async (configServide: ConfigService) => ({ type: 'postgres' as 'postgres', host: configServide.get('DATABASE_HOST'), port: Number(configServide.get('DATABASE_HOST')), username: configServide.get('DATABASE_USERNAME'), password: configServide.get('DATABASE_PASSWORD'), entities: [join(__dirname + '/**/*.entity{.ts,.js}')], synchronize: false, }), inject: [ConfigService], }), ], }) export class AppModule { constructor(private readonly connection: Connection) {} }起動後,以下のエラーが発生.
2:25:34 PM - Found 0 errors. Watching for file changes. [Nest] 19111 - 01/13/2020, 2:25:35 PM [NestFactory] Starting Nest application... [Nest] 19111 - 01/13/2020, 2:25:35 PM [InstanceLoader] TypeOrmModule dependencies initialized +24ms [Nest] 19111 - 01/13/2020, 2:25:35 PM [InstanceLoader] ConfigModule dependencies initialized +1ms [Nest] 19111 - 01/13/2020, 2:25:35 PM [ExceptionHandler] Nest can't resolve dependencies of the TypeOrmModuleOptions (?). Please make sure that the argument ConfigService at index [0] is available in the TypeOrmCoreModule context. Potential solutions: - If ConfigService is a provider, is it part of the current TypeOrmCoreModule? - If ConfigService is exported from a separate @Module, is that module imported within TypeOrmCoreModule? @Module({ imports: [ /* the Module containing ConfigService */ ] }) +1ms Error: Nest can't resolve dependencies of the TypeOrmModuleOptions (?). Please make sure that the argument ConfigService at index [0] is available in the TypeOrmCoreModule context. Potential solutions: - If ConfigService is a provider, is it part of the current TypeOrmCoreModule? - If ConfigService is exported from a separate @Module, is that module imported within TypeOrmCoreModule? @Module({ imports: [ /* the Module containing ConfigService */ ] }) at Injector.lookupComponentInExports (/home/kawamura/docker/docker-services/sample-app/sample-back/node_modules/@nestjs/core/injector/injector.js:185:19) at processTicksAndRejections (internal/process/task_queues.js:94:5) at async Injector.resolveComponentInstance (/home/kawamura/docker/docker-services/sample-app/sample-back/node_modules/@nestjs/core/injector/injector.js:142:33) at async resolveParam (/home/kawamura/docker/docker-services/sample-app/sample-back/node_modules/@nestjs/core/injector/injector.js:96:38) at async Promise.all (index 0) at async Injector.resolveConstructorParams (/home/kawamura/docker/docker-services/sample-app/sample-back/node_modules/@nestjs/core/injector/injector.js:111:27) at async Injector.loadInstance (/home/kawamura/docker/docker-services/sample-app/sample-back/node_modules/@nestjs/core/injector/injector.js:78:9) at async Injector.loadProvider (/home/kawamura/docker/docker-services/sample-app/sample-back/node_modules/@nestjs/core/injector/injector.js:35:9) at async Promise.all (index 3) at async InstanceLoader.createInstancesOfProviders (/home/kawamura/docker/docker-services/sample-app/sample-back/node_modules/@nestjs/core/injector/instance-loader.js:41:9)環境変数を参照するための
ConfigService
がTypeOrmModuleOptions
内で依存関係が解決できないことが原因らしい.同じような事象がGithubのIssueにあがっていたので参考に載せておきます.
Can't init TypeOrmModule using factory and forRootAsync環境変数を参照するように実装を修正
app.module.tsを以下のように修正します.
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConfigModule } from '@nestjs/config'; import { ItemModule } from './item/item.module'; import { TypeOrmConfigService } from './common/database/type-orm-config.service'; @Module({ imports: [ ConfigModule.forRoot({ envFilePath: '.env', isGlobal: true, // ignoreEnvFile: true, <- 環境変数から取得する場合はコメントアウトを外す. }), TypeOrmModule.forRootAsync({ imports: [ConfigModule], // 接続情報を作成するServiceクラスを定義 useClass: TypeOrmConfigService, }), ItemModule, ], }) export class AppModule { }type-orm-config.service.ts
import { TypeOrmOptionsFactory, TypeOrmModuleOptions } from '@nestjs/typeorm'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { join } from 'path'; /** * DBの接続設定. */ @Injectable() export class TypeOrmConfigService implements TypeOrmOptionsFactory { /** * DBの接続設定を環境変数をもとに作成します.</br> * 環境変数に設定されていない場合は,デフォルトの設定値を返却します. * @returns 接続情報 */ createTypeOrmOptions(): TypeOrmModuleOptions { const configService = new ConfigService(); return { type: 'postgres' as 'postgres', host: configService.get('DATABASE_HOST', 'localhost'), port: Number(configService.get('DATABASE_PORT', 5432)), username: configService.get('DATABASE_USERNAME', 'postgres'), password: configService.get('DATABASE_PASSWORD', 'postgres'), database: configService.get('DATABASE_NAME', 'postgres'), entities: [join(__dirname + '../**/*.entity{.ts,.js}')], synchronize: false, }; } }
ConfigService
をDIするのではなく,自分でnewするのがポイントです.最後に
最近使い始めたのですが,素晴らしいフレームワークだとひしひしと感じております.
フレームワーク自体の良さはこちらの記事で紹介されています.参考
- 投稿日:2020-01-15T15:26:15+09:00
Typescriptチュートリアル
Typescriptを始めたいけどどこから初めて良いかわからないという方の、TypescriptのWebサイトに掲載されていた同名のページを少しだけアレンジして紹介します。
環境の準備
まず、node.jsがPCにインストールされている必要がある。インストールされていない場合、下記のページからLTSの最新版をダウンロードしてインストールします。
https://nodejs.org/en/Typescriptのインストール
下記のコマンドを実行して、typescriptのライブラリをダウンロードしてきます。会社の環境などで、プロキシサーバが間にある方は、プロキシありの方のコマンドを実行してください。
プロキシなしnpm install -g typescriptプロキシありnpm install -g typescript最初のTypescriptファイルの作成
まずテキストエディタを開いて、下記のJavascriptコードをgreeter.tsというファイルに保存してください。
greeter.tsfunction greeter(person) { return "Hello, " + person; } let user = "Jane User"; document.body.innerHTML = greeter(user);Typescriptコードのコンパイル
次にコマンドプロンプトを開き、さっき作ったgreeter.tsが保存されているパスに移動してから下記のコマンドを実行します。
コマンドtsc greeter.ts実行すると、同じコードが書かれたgreeter.jsというファイルが作成されます。
次に、Typescriptが提供している機能を使ってコードを改良します。
greeterの引数にstringの型定義を追加しましょう。
function greeter(person: string) { return "Hello, " + person; } let user = "Jane User"; document.body.innerHTML = greeter(user); Type annotations上記のコードによって、javascriptにはない型定義の機能を追加することができました。試しに、変数userに数値を入れて、greeterメソッドを呼んでみます。
function greeter(person: string) { return "Hello, " + person; } let user = 12345; document.body.innerHTML = greeter(user);結果はこの通り。エラーが出力されました。
error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.この場合でもgreeter.jsは作成され、使うこともできますが期待通りに動作しない可能性があることをTypescriptが警告を出します。
typeアノテーション
インタフェース
今度は、firstNameとlastNameを持っているオブジェクトを表現するインターフェースを作成します。Typescriptでは、
interface Person { firstName: string; lastName: string; } function greeter(person: Person) { return "Hello, " + person.firstName + " " + person.lastName; } let user = { firstName: "Jane", lastName: "User" }; document.body.innerHTML = greeter(user); Classesクラス
class Student { fullName: string; constructor(public firstName: string, public middleInitial: string, public lastName: string) { this.fullName = firstName + " " + middleInitial + " " + lastName; } } interface Person { firstName: string; lastName: string; } function greeter(person : Person) { return "Hello, " + person.firstName + " " + person.lastName; } let user = new Student("Jane", "M.", "User"); document.body.innerHTML = greeter(user);TypescriptWebAppの起動
<!DOCTYPE html> <html> <head><title>TypeScript Greeter</title></head> <body> <script src="greeter.js"></script> </body> </html>デバッグ
クロームで実行したJavascriptを実行し、デバッグをしてみましょう。
コードを止めると、javascriptのコードではなく、typescriptが表示されます。まとめ
これにより、typescriptの開発環境ができました・
typescriptはjavascriptと異なりコンパイルというプロセスが入るため、実行しなくても記述みすや型の間違いを見つけてくれます。
記述量は多少増えますが、規模が大きくなればなるほど、開発を助けてくれるはずです。参考
https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html
- 投稿日:2020-01-15T13:52:20+09:00
Apacheサーバーでnode.jsを動作させる(SSL対応)
私の話ですが「あるWebアプリケーションをApacheで稼働していてそのアプリケーションに対応したAPIを同じサーバー上にNode.jsで作りたい」ということがありました。
HTTPであればポートを指定したりリバースプロクシを設定したりすることで問題はないのですが、HTTPSの場合は通常ポート番号が443で固定です。HTTPSに限った話だとあまり記事がないと思い今回備忘録として残しておきます。前提条件
・AWS EC2上でApacheが稼働中
・Apache / node.js / certbot(letsencrypt)がインストール済
・アプリのホスト名はexsample.comでAPIのホスト名はapi.example.comでDNS設定済
・HTTPSはec2のインスタンスで許可済1.SSL対応
まずはexsample.comとapi.example.comのホストのバーチャルドメイン設定をします。$ cd /etc/httpd/conf $ vi httpd.conf == (編集) == $ systemctl restart httpd以下の記述を追加してください。その後にApacheを再起動します。
NameVirtualHost *:80 <VirtualHost *:80> ServerAdmin admin@exsample.com DocumentRoot /var/www/html/ ServerName www.exsample.com ServerAlias exsample.com </VirtualHost> <VirtualHost *:80> ServerAdmin admin@exsample.com DocumentRoot /var/www/html/ ServerName api.exsample.com ServerAlias api.exsample.com </VirtualHost>ブラウザでexsample.comとapi.exsample.comをアドレスバーに入れて確認します。(何もなければおそらくApacheのテストページが表示)アクセスできることが確認できたらSSL設定を行います。
$ certbot --apache -d exsample.com $ certbot --apache -d api.exsample.com $ systemctl restart httpdいろいろな対話が発生しますがここでは省略します。ただし注意としてhttpsへの転送設定はceartbotのコマンドでは行わないでください。その方が確認するときに便利だからです。certbotについては詳しい記事がいろいろあります。時間があればリンクを貼っておきます。設定し終えたらhttps://付きで二つのホストのへアクセスをブラウザで確認しましょう。
2.Node.jsの起動
Node.jsを起動します。
https://qiita.com/someone-said-so/items/ed0aafee065efcbc3b57
にあるindex.jsを/home/ec2-user/api_sampleに作成します。$ cd /home/ec2-user/api_sample $ node index.jsこれで3000番ポートでアクセスがあったときに{msg: 'hello express'}というjsonデータを返すAPIが稼働します。ひとまずhttp://api.exsample.com:3000でアクセスして確認しましょう。(1.でリダイレクトを指定しなかったのはこの確認をするため)
3.リバースプロクシの設定
準備が整ったので最終段階です。443番ポートでアクセスがあった場合のリバースプロクシを設定しましょう。$ cd /etc/httpd/conf $ vi httpd-le-ssl.conf == (編集) == $ systemctl restart httpdcertbotでSSL設定をした場合、httpd-le-ssl.confというファイルが出来上がっているはずです。このファイルを編集します。###で囲まれている箇所を追記してください。Proxy関連の詳細設定が多いこと、パスをhttpで設定することがポイントです。(node.jsはhttpで待ち受けているため)
<IfModule mod_ssl.c> <VirtualHost *:443> ServerAdmin admin@exsample.com DocumentRoot /var/www/html/ ServerName www.exsample.com ServerAlias exsample.com SSLCertificateFile /etc/letsencrypt/live/exsample.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/exsample.com/privkey.pem Include /etc/letsencrypt/options-ssl-apache.conf </VirtualHost> </IfModule> <IfModule mod_ssl.c> <VirtualHost *:443> ServerAdmin admin@exsample.com DocumentRoot /var/www/html/ ServerName api.exsample.com ServerAlias api.exsample.com SSLCertificateFile /etc/letsencrypt/live/api.exsample.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/api.exsample.com/privkey.pem Include /etc/letsencrypt/options-ssl-apache.conf ###ここから追加### SSLEngine on SSLProxyVerify none SSLProxyCheckPeerCN off SSLProxyCheckPeerName off SSLProxyCheckPeerExpire off SSLProxyEngine on ProxyRequests off ProxyPass / http://api.exsample.com:3000/ ProxyPassReverse / http://api.exsample.com:3000/ ###ここまで### </VirtualHost> </IfModule>これで設定は完了です。あとは
・https://exsample.comでアクセスするとApacheのテストページ(そのまま)
・https://api.exsample.comでアクセスするとjsonデータ
であることを確認します。設定してみての感想ですが、リソースがあるなら別のサーバーを立てた方が手っ取り早いです。またEC2なら別のインスタンスでも追加すればよいでしょう。ですがリソースが限られている場合もありますし、DBがlocalhostからアクセスできた方がよいなどいろいろな制約があるかと思います。そのようなケースに役立ててもらえればと思います。
- 投稿日:2020-01-15T12:39:27+09:00
20分でWebアプリを作ろう
この記事はIBM Cloudで初めてアプリケーションを作成する方を対象に記述しています。
<事前準備>
IBM CLOUD CLIのインストール
IBM Cloud ブラウザの操作
1.IBM CloudにLogin
2.Cloud Foundry の立ち上げ
カタログメニューからCLOUD Foundory を選択、パブリック・アプリケーションを”作成”します。
プランを指定し、言語を選択(例:SDK for Node.js)
アプリ名を記入します。(例:GetStartedNode)
ホスト名はアプリ名が自動入力されます。
*地域はダラス推奨、ドメイン名の初期値のままにしてください。”作成”ボタンを押し、アプリケーションの開始を待ちます。
アプリURLにアクセスメニューより、Hello World!が表示されることを確認します。
<ここからはターミナルの操作>
3.Gitをクローンする(ローカルにディレクトリ毎コピーを作成)
ここをコピー:git clone https://github.com/IBM-Cloud/get-started-node
4.コピーしたget-started-nodeディレクトリに移動
ここをコピー: cd get-started-node
5.ローカル環境にアプリを入れて、スタート
ここをコピー: npm install
ここをコピー: npm start6.ローカル環境での動作を確認
ここからはymlファイルの編集
7.ローカルにコピーされた”get-started-node”以下のmainfest.ymlをメモ帳などで編集、保存します。
applications:
- name: GetStartedNode
random-route: true
memory: 128Mname:2で指定したアプリ名に変更
memory: 2で指定した容量に変更
ymlファイルを上書き保存する<ここからはターミナルの操作>
8.IBM Cloudにログインし、Cloud Foundryをターゲット指定しプッシュ
ここをコピー: ibmcloud login
ここをコピー: ibmcloud target --cf
ここをコピー: ibmcloud cf pushIBM Cloud ブラウザの操作
9.結果の確認
アプリURLにアクセスメニューより、「ようこそ、お名前を教えてください」が表示されることを確認します。
- 投稿日:2020-01-15T10:15:31+09:00
無料枠でGAEとAESを使ってみる
Nuxt + Firebase + Elasticsearch 勉強用Qiita まとめで
ローカル環境でFirebaseとElasticsearchをつなぐnodeとElasticsearchを構築できました。これを本番環境に落とし込むためnodeをGAEに、ElasticsearchをAmazon Elasticsearch Serviceに移行してみました。
GCPとAWSに触るのは初めてだったのでかなり大変でした。
GAE (Google App Engine)
Projectのセットアップはこちらのサイトを参考にしました
Google App Engine Node.jsを試してみる。 GAE/Node.js
App Engine スタンダード環境で Node.js を使用するためのクイックスタート
app.yaml
はこちらのサイトを参考にしました。
Google App Engineを無料で運用する方法(2018年版)AES (Amazon Elasticsearch Service)
AESの立ち上げはこちらのサイトを参考にしました。
AWS再入門 Amazon Elasticsearch Service編
- 投稿日:2020-01-15T00:42:29+09:00
ブロガーの買ってよかったものを集計してランキング化したら本当にいいモノが見つかった
動機
ジャバ・ザ・ハットリ(@jabba )さんの技術書ランキングサイトをQiita記事の集計から作ったら、約4000冊の技術本がいい感じに並んだがとても面白かった。
特に面白く感じたのは「意見を集めてランキング形式に整える」と、良いモノはちゃんと上位に入ってくるところ。この「意見を集めてランキング形式に整える」をブロガーの買ってよかったもの紹介記事に応用したら、紹介頻度の高い本当にいいものが見つかるのでは?
最近はAmazonのレビューもサクラコメントで溢れがちで、本当に良いものを見つけるのが難しくなっている世の中に多少のニーズはあるのでは?
と思ったのが今回のそもそもの動機。
作ってみた
実際にブロガーの買ってよかったもの紹介記事から紹介商品を集計してランキング形式で並べたのがこちら。
この記事を書いている時点での上位5つのアイテムを試しに抜粋してみると以下の通りで、名実ともに良いものが並んだ。
1. Anker PowerCore Fusion 5000
2. Apple AirPods Pro
3. Fire TV Stick 4K
4. Fire TV Stick
5. ロジクール ワイヤレスマウス トラックボール 無線 M570t1,4,5は私も実際に持っているので、「私ってモノを見る目あるのかも?」と勝手に錯覚するところだった。
このようにみんなから支持されているいいモノを確認できるだけでなく、後述するように隠れ良品や感性の近いブロガーに出会えるページになった。
気に入っているところ1:隠れ良品に出会える
上位にランクインしている評判になっているモノをチェックするのも面白いのだけど、個人的には30位以降に載っているモノを見るのがとてもおもしろい。
たとえばこのダンボールストッカー。
6000件弱ある買ってよかったもの記事でたった4件でしか同じ商品を紹介していないので、目的なく検索しないと出会えない隠れ良品と言ってもよさそう。
こんな感じで今まで知らなかった隠れ良品に出会える。
気に入っているところ2:感性の近いブロガーに出会える
グッドグッズランクではランクインしている商品の紹介記事も併せて表示している。
商品をキーにして記事を集めているので、記事自体のSEO対策の良し悪しに依らず、平等にみなさんに記事が露出される。
自分の持っている商品を紹介している記事へのアクセスを通して、自分のモノ選びの感性に近いブロガーに出会える機会もあるのも面白いと思っている。
Googleに評価されずネットの海に埋もれている記事でも興味深い記事はたくさんあることがよく分かる。
改善点
Amazon APIを通して各商品のカテゴリデータも集めているので、カテゴリ別に表示しても面白いかな。
買ってよかった記事内でAmazonの商品ページへのリンクなしで紹介している商品はデータベースに登録されていないので、もっと他に隠れ良品がある可能性が残っている。
逆に1つのサイトの複数記事で紹介されている商品がランクインしている問題もある。それだけ推したいってことだからそのままにするか悩んでいる。
こうしたらもっと面白いかも等のご意見等がありましたらぜひコメントにてお知らせください。
グッドグッズランクに使った技術
Python
React (Gatsby)
GraphQL
MongoDB
Netlify
- 投稿日:2020-01-15T00:41:57+09:00
Dockerでnode.jsプロジェクトの開発環境構築
動機
- 新しくMac Book Pro 2019を購入。nodeやpytonの開発環境構築にあたっては, 素の環境を綺麗なままにしたいことや、いろいろな環境での開発を行いためDockerを用いることにした。
- Docker周りの設定や概念の理解で少し躓いたのでここに記す。エキスパートには当たり前すぎる内容だとしてもだ。
やりたいこと
node.js, angular, ionic, webpack を使ったweb appやPWAの開発を、
- ホストのローカル環境を汚すことなく(Dockerを用い)
- ホストで動くVS Codeを用いたコーディング&デバッグしつつ
- ホストのターミナルからgitを用いたバージョニングしつつ
- 仮想マシンでionic server等で起動したサーバとその上で動くweb appを、ホストのブラウザからアクセスし動作確認・デバッグ手順
1. Docker desktop for Macをインストール
https://hub.docker.com からダウンロード。ユーザ登録も済ます。
2. プロジェクトのディレクトリ構造
最終的に下記になります。順番に作っていきましょう。
- Dockerfile - docker-compose.yml - app |- ここに諸々のソースを置く3. Dockerfile
Dockerfie
: Imageを作成する設定ファイル
Image
: 仮想マシンの雛形。OS, インストールされたソフトウェアセット等からなる。node.js入りのalpine linuxのimageをベースに、ionicとcordovaをインストールしたimageを作成する。
alpine linux
: 超軽量linux. Dockerの仮想マシンOSとして頻繁に利用されている.host$ cat Dockerfile FROM node:10.13-alpine WORKDIR /app RUN npm install -g ionic cordova CMD ["sh"]下記コマンドでDocker imageを作成します。最初に実行する場合ローカルにnode:10.13-alpineのイメージがないのでdocker hubからダウンロードされてきます。2回目以降はローカルのイメージが使われます。
host$ docker build . -t watashino-image-tはイメージの名前を与えるオプションです。適当につけてOK。
正常に完了すればdocker imagesでimageが作成されていることが確認できます。nodeのイメージもダウンロードされローカルに保存されています。host$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE watashino-image latest da6f921ba540 2 minutes ago 133MB node 10.13-alpine 93f2dcbcddfe 13 months ago 70.3MB
このimageが仮想マシンの雛形となります。
4. docker-compose.yml
続いて、imageを実際に起動するためのファイルを記述します。
docker-compose.yml
: docker-composeというコマンドへ与える設定ファイル
docker-compose
:imageからcontainerを作成・起動するプログラム。
container
: 厳密な話は置いておいて、ざっくりいうと、雛形であるimageから作成された仮想マシンを表す。型とそのインスタンス、という関係に近い。実際のところ、"docker"コマンドだけでもcontainerの作成・起動はできるが、オプションの指定等が面倒だったり複数のcontainerの起動等も同時に行えるdocker-composeの方が便利そうなので、最初からこちらを採用。
host$ cat docker-compose.yml version: '3' services: app: build: . image: watashino-image volumes: - ./app:/app ports: - "8100:8100" tty: true下記のコマンドでcontainerの作成・起動。まだローカルにappフォルダを作っていない場合には作っておく。
host$ mkdir app $ docker-compose up -d何事もなくコマンドが終了すれば成功。仮想マシンはバックグラウンドで動作している。下記コマンドで実行中のcontainerの一覧が見れる。
host$ docker ps
お気づきの方もいるでしょうが、実はstep 3のdocker buildのコマンドは実行しなくてもよかったのです。"build: ."の行はビルドに関する指示で、docker-composeファイルは指定されたimageが存在しない場合には自動的にDockerfileを参照してimageのbuildと、続いてcontaierの作成と起動を行います。imageとcontainerの関係を明らかにするために冗長な手順とさせてもらいました。
他の設定の意味:
services
docker-composeでは仮想マシンをserviceという呼び方をするようです。ここでは"app"というサービスを一つ定義しています。複数の仮想マシン=サービスの設定を書いてまとめて起動することもできます。というかそれこそがdocker-composeが便利な点で使う理由です。
volumes
ローカルの./appフォルダと仮想マシンにおける/app (root直下)を結びつけます。これによより仮想マシンとホストマシンのファイルが同期されるようになります。
ports
ポートフィワーディングの設定。仮想マシンのport 8100とローカルのport 8100を結びつけます。仮想マシンにおけるlocalhost:8100に、ホストマシンからlocalhost:8100でアクセスできるようになります。(ただし後述の注意点あり)
tty
これがtrueではないと、containerは起動後すぐに終了してしまう。5. 動作中の仮想マシンを操作
バックグランドで実行されているcontainerには下記のコマンドでアクセスできます。
host$ docker exec app sh何をしているかというとappサービス(docker-compose.xmlで指定した名称)でshの実行を指示しています。上記を実行するとシェルで仮想環境の中をいじれるようになります。
仮想環境から退出するには
container# exit
します。
6. gitからソースを持ってくる&VS Codeで読み込む
app以下にソースコードを持ってきましょう。ローカルのappフォルダで通常通りgitでソースファイルを持ってきます。
host$ cd app $ git clone アドレス仮想環境にgitをinstallすれば仮想環境で直接gitを使えるでしょうが(未確認)、わざわざそうする必要もないかと思います。
ファイルは同期されていますから、特に意識することなくローカル環境のVS Codeを開き、ローカルのappフォルダを開けばOK
7. 仮想環境でビルド&実行
host$ docker-compose exec app shで仮想環境に入りappフォルダを覗けばソースコードが見えるはずです。あとは通常通りの開発ルーチンに従います。ここではionicプロジェクトの例では、下記のようなことを行います。ionic(ng)のビルドインサーバをポートフォワーディングで設定したportで起動しています。
container# npm install # ionic serve -p 8100 --address 0.0.0.0これによりホスト環境から localhost:8100 で仮想環境内のWEBサーバにアクセスできます。--address 0.0.0.0 を付けないとホストからは見れません。理由はこちらをご参考ください。
8. Containerの停止, 再開
開発が終わったら起動させておくのは無駄でしょうから、Containerを停止します。
host$ docker-component stop
で停止,
host$ docker-component up -dで再開します。なお、containerを停止すると、Continerの起動中に作成したファイルは、
volumes
でホストのディレクトリに保存しているもの以外は全て消えてしまいます。containerのライフサイクル、データの永続化については以下のような記事を参考ください。
- https://qiita.com/gounx2/items/23b0dc8b8b95cc629f32
- https://qiita.com/onokatio/items/fcc9f8f94f8533bb030aなお今回はホストのファイルシステムとの同期、という方法のみを扱っていますが、そのほかにも永続化手段もある模様(未トライ)
9. container, imageの削除
ニッチもさっちも行かなくなった場合、全ての過去を消し去りたいと思ったら、
docker-component down
コマンドを使います。下記が参考になります。気軽に環境丸ごと削除してやり直せるというのが、Dockerを用いた開発の最大のメリットでしょう。
以上です。
- 投稿日:2020-01-15T00:17:39+09:00
めっちゃ簡単にNodejsでCLIツールを作る
やりたいこと
そこまで凝ったことをしないCLIツールをNodejsで作りたい!
そこでcacを使ってそれを実現したいと思います。こちらの記事で作成したプロジェクトを元に進めたいと思います。
パッケージとindex.jsの追加
$ npm i cac $ touch index.jscacの基本
const cli = require('cac')() cli .command('command', '説明') // コマンド .option('--opt', '説明') // 引数オプション .action((options) => { // 実行したい処理 console.log(options) // 引数の値をオブジェクトで受け取れる }) cli.help() cli.parse()ここまで書いたら、以下を実行することで確認出来る。
$ node index.js --helpさらにコマンドも実行できる。
以下のように変更します。const cli = require('cac')() const exec = require('child_process').exec cli .command('command', '説明') // コマンド .option('--opt', '説明') // 引数オプション .action((options) => { // 実行したいコマンド(例でpwdコマンドを実行する) exec('pwd', (err, stdout, stderr) => { if (err) { console.log(err); } console.log(stdout); }) }) cli.help() cli.parse()上記のようにしたら、
node index.js command
とすることでpwd
コマンドが実行されるかと思います。index.js
それではいよいよ、前の記事で作成した
export.js
とrestore.js
を操作する処理を記述していきます。
追加したindex.js
に追記していきます。const cli = require('cac')() const exec = require('child_process').exec // エクスポート用のコマンド cli .command('export', 'run export from DynamoDB to S3') .action(() => { exec('node export.js', (err, stdout, stderr) => { if (err) { console.log(err); } console.log(stdout); }) }) // リストア用のコマンド cli .command('restore', 'run restore from S3 to DynamoDB') .option('--bucket <name>', 'Bucket name used for import') .option('--table <name>', 'Table name to import') .action((options) => { if(!options.bucket && !options.table){ console.log('「--bucket」オプションと「--table」を指定してください') } else if(!options.bucket){ console.log('「--bucket」オプションを指定してください') } else if(!options.table){ console.log('「--table」オプションを指定してください') } else { exec(`node restore.js ${options.bucket} ${options.table}`, (err, stdout, stderr) => { if (err) { console.log(err); } console.log(stdout); }) } }) cli.help() cli.parse()restore.js
restore.js
が現在はファイルに直接バケット名とテーブル名を記述していますが、これをコマンドライン引数で処理するように変更します。'use strict' function execRestore(bucketName, tableName){ const Restore = require('dynamodb-backup-restore').Restore let config = { S3Bucket: 'backups', // 必須 S3Prefix: bucketName, // 任意 S3Region: 'ap-northeast-1', // 必須 DbTable: tableName, // 必須 DbRegion: 'ap-northeast-1', // 必須 } Restore(config) } // CLIからコマンドライン引数を受け取って実行する execRestore(process.argv[2], process.argv[3])操作方法
# exportの実行 $ node index.js export # restoreの実行 $ node index.js restore --bucket バケット名 --table テーブル名で無理やりCLIを導入することが出来ました。