Vue.js 開発環境を手作りしてみた手順(自分メモ)
Vue.js の開発環境構築は、もっぱら vue-cli で "vue init webpack hogehoge" という方法が一般的ですが、最近 React から Vue に乗り換えた私は、「とりあえず最初は、開発環境を手作りしたい」とうか、そうしないとしっくりこないので、手探りながら一から構築してみた時のメモです 。あくまでも自分用です。
概要
おおよその構成
Vue.js を利用したクライアントアプリのビルドに "webpack"、ES2015 のトランスパイラは "babel"、Vue コンポーネントファイルのビルドに "vue-loader" を導入する。その他 CSS のビルドや、静的なファイルコピー、アプリ実行用サーバも仕込む。あと、Eslint も設定してみた。
大まかな流れ
- npm init で土台作り
- Vue.js インストール
- webpack 入れる
- babel 導入
- css-loader と style-loader 導入
- copy-webpack-plugin 導入
- webpack-dev-server で動かす
- vue-loader 導入
- eslint でコードチェック
Vue.js アプリ開発環境構築 基礎編
基礎編は、vueコンポーネントファイルを用いない Vue.js アプリがビルドできる環境を構築する。
1) npm init で土台作り
最初に node をインストール。nodebrew を使用すると吉。
続いて、npm init で開発環境のベースを作る。
$ npm init
いろいろ対話するが、アプリ名とか適宜入力すればOK。
2) Vue.js インストール
作成したアプリのディレクトリに移動した上で、普通に Vue.js をインストール。
$ npm install --save vue
3) webpack を入れる
$ npm install --save-dev webpack
4) Babel を導入
# webpack から babel を利用する場合の基本モジュール $ npm install --save-dev babel-loader babel-core # ES2015 をトランスパイルするためのもの $ npm install --save-dev babel-preset-es2015
babel の設定ファイル ".babel.rc" を作成
$ echo '{ "presets": ["react", "es2015"] }' > .babelrc $ cat .babelrc { "presets": ["react", "es2015"] }
ここで、babel で js をトランスパイルするための webpack 設定をおこなう。
$ vim webpack.config.js
ここまでの webpack.config.js の中身は次のとおり。
var path = require('path'); module.exports = { entry: './src/app.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'public') }, module: { loaders: [ { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' } ] } };
ここで試しにサンプルアプリをビルドさせてみると、Vue.js のTemplateがコンパイルできないとかの以下のエラーがでた。
Vue warn: You are using the runtime-only build of Vue where the template option is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
調査したところ、vue.js のビルドは、コンパイラ付のものを import する必要があるとのこと。解決するには、webpack.config.js に ビルドの alias を追加すること。*1
ということで、webpack.config.js に下記の設定を追加。これで動作OK。
resolve: { alias: { 'vue$': 'vue/dist/vue.js' } },
5) css-loader と style-loader 導入
$ npm install --save css-loader style-loader
webpack.config.js に下記の loader を追加。
module: { loaders: [ ... // ここから { test: /\.css$/, exclude: /node_modules/, loader: ['style-loader', 'css-loader'], }, // ここまで ], },
6) copy-webpack-plugin 導入
$ npm install -save-dev copy-webpack-plugin
webpack.config.js に copy-webpack-plugin の設定を追加。
plugins: [ new CopyWebpackPlugin([{ from: path.resolve(__dirname, 'src', 'index.html'), }]), ],
7) webpack-dev-server で動かす
$ npm install -save-dev webpack-dev-server
またも webpack.config.js に 設定を追加。
devServer: { contentBase: 'public', port: 3000, host: 'localhost', historyApiFallback: true, },
"npm start" でアプリを動かせるように、package.json に次の設定を追加。
"scripts": { "start": "webpack-dev-server", <--- 追加 "build": "webpack", "test": "echo \"Error: no test specified\" && exit 1" },
ここまでのまとめ
ここまでで webpack.config.js は次のとおりとなりました。
const path = require('path'); const CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'public') }, module: { loaders: [ { test: /\.js$/, include: /src/, loader: 'babel-loader' }, { test: /\.css$/, exclude: /node_modules/, loader: ['style-loader', 'css-loader'], }, ], }, plugins: [ new CopyWebpackPlugin([{ from: path.resolve(__dirname, 'src', 'index.html'), }]), ], resolve: { alias: { 'vue$': 'vue/dist/vue.js' }, }, devServer: { contentBase: 'public', port: 3000, host: 'localhost', historyApiFallback: true, }, };
Vue.js アプリ開発環境構築 vue ファイルビルド編
ここからは、さらに vueコンポーネントファイルを用いた Vue.js アプリもビルドできるように環境設定を追加する。
8) vue-loader 導入
vueコンポーネントファイル をビルドさせるには、 vue-loader が必要ということで、早速導入。
$ npm install -save-dev vue-loader
さらに vue-template-compiler がないとbuildでエラーとなるのでインストールする
$ npm install --save-dev vue-template-compiler
vue-loader の設定を webpack.config.js に追加。
module: { loaders: [ // ここから追加 { test: /\.vue$/, exclude: /node_modules/, loader: 'vue-loader', options: { loaders: { } } }, // ここまで
9) eslint でコードチェック
Vue.js を利用したアプリでも eslint でリントチェックできるようにしてみる。 最初は、eslint の基本設定。今回は対話で設定するケース。
$ eslint --init ? How would you like to configure ESLint? Answer questions about your style ? Are you using ECMAScript 6 features? Yes ? Are you using ES6 modules? Yes ? Where will your code run? Browser ? Do you use CommonJS? No ? Do you use JSX? No ? What style of indentation do you use? Spaces ? What quotes do you use for strings? Single ? What line endings do you use? Unix ? Do you require semicolons? Yes ? What format do you want your config file to be in? JavaScript Successfully created .eslintrc.js file in /Users/foo/bar/vue-sample/
これで ".eslintrc.js" が生成されるはず。ただしこのままでは、vue template がきちんとチェックできないので、eslint-for-vue を導入する。
$ npm install --save-dev eslint-plugin-vue@beta
.eslintrc.js に設定を追加。今回はこのような設定内容となりました。
module.exports = { "env": { "browser": true, "commonjs": true, "es6": true }, "extends": [ "eslint:recommended", "plugin:vue/recommended" <--- eslint-for-vue の設定追加 ], "parserOptions": { "sourceType": "module" }, "rules": { "indent": [ "error", 2 ], "linebreak-style": [ "error", "unix" ], "quotes": [ "error", "single" ], "semi": [ "error", "always" ], "vue/valid-v-if": "error" <--- eslint-for-vue の設定追加 } };
まとめ
今回作成した webpack.config.js はこちら
const path = require('path'); const CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'public') }, module: { loaders: [ { test: /\.vue$/, include: /src/, loader: 'vue-loader', options: { loaders: { } } }, { test: /\.js$/, include: /src/, loader: 'babel-loader' }, { test: /\.css$/, exclude: /node_modules/, loader: ['style-loader', 'css-loader'], }, ], }, plugins: [ new CopyWebpackPlugin([{ from: path.resolve(__dirname, 'src', 'index.html'), }]), ], resolve: { alias: { 'vue$': 'vue/dist/vue.js' }, }, devServer: { contentBase: 'public', port: 3000, host: 'localhost', historyApiFallback: true, }, };
package.json はこうなりました。
{ "name": "vue-tutorial01", "version": "1.0.0", "description": "Vue.js tutorial project", "author": "hoge hoge fuga fuga", "private": true, "main": "index.js", "license": "ISC", "scripts": { "start": "webpack-dev-server", "build": "webpack", "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { "eslint": "^4.7.1", "vue": "^2.4.4" }, "devDependencies": { "babel-cli": "^6.26.0", "babel-core": "^6.26.0", "babel-loader": "^7.1.2", "babel-preset-es2015": "^6.24.1", "copy-webpack-plugin": "^4.0.1", "css-loader": "^0.28.7", "eslint-config-standard": "^10.2.1", "eslint-plugin-import": "^2.7.0", "eslint-plugin-node": "^5.1.1", "eslint-plugin-promise": "^3.5.0", "eslint-plugin-standard": "^3.0.1", "eslint-plugin-vue": "^3.13.0", "file-loader": "^0.11.2", "style-loader": "^0.18.2", "vue-loader": "^13.0.5", "vue-template-compiler": "^2.4.4", "webpack": "^3.6.0", "webpack-dev-server": "^2.8.2" } }
所感
なんというか、"vue init webpack-simple" で開発環境を構築した場合とほぼ同じような感じになってしまった。しかし、今回は自分の理解のために頑張ったということで良かろう。
次回からは、素直に "vue init webpack-simple" で環境構築することにしよう。
以上。
*1:本家の次の情報を参考にしました
Docker で node.js を動かすときは PID 1 にしてはいけない
これは、node.js on Docker の構成で 2〜3日ハマってしまった時の話です。忘れないように記録しておきます。なお、将来は改善・改良されているかもしれませんのでご注意ください。
何が起こったのか
node.js の Docker コンテナを、"docker stop" でコンテナを止めようとしても正常に停止せず、10秒くらい経過した後に強制終了してしまうという症状が発生しました。いつも等しくそうなるので、状態とかタイミングとかそういった要因ではなく、そもそも根本的に何かがおかしいと考えられます。
1. node on Docker の構成
Docker コンテナ上で node.js が動いているだけの極めてシンプルな構成でこの問題が発生しました。
node.js で動くアプリは、"Hello World" を出すだけの超簡単な hello.js です。こんな感じです。
const http = require('http'); http.createServer((req, res) => { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(3000);
hello.js アプリを Docker に仕込むための Docker ファイルは以下の通りです。ベースとなる Docker イメージは、Docker Hub で node.js が提供する node:8.4 (現在のlatest) を利用しました。
FROM node:latest ADD ./hello.js /usr/src/app/ EXPOSE 3000 CMD ["node", "/usr/src/app/hello.js"]
普通にビルドして、生成したイメージからコンテナを作って起動します。たぶん … 無事に動作するはずです。
$ docker build -t nodetest . $ docker run --name hello_node -P 3000:3000 nodetest
2. 問題を発生させる
動いている node.js アプリのコンテナを docker stop で停止しますと、今回の問題が発生します。
$ docker stop hello_node
恐らく、10秒ほど経過した後にコンテナが停止しプロンプトが戻ってくると思いますが、この停止に10秒かかることが問題です。
3. docker stop で何が起こっているか?
“docker stop” コマンドを実行すると、Docker はそのコンテナのルートプロセス(すなわち、PID=1 のプロセス)に SIGTERM シグナルを投げます。
このシグナルを受信したプロセスは、該当するシグナルハンドリング処理を実行します。多くのプログラムでは、SIGTEM を受けた場合、自身を安全に停止するシグナルハンドリング処理が実装されています。
SIGTERM を投げた後、Docker は コンテナ(ルートプロセス)が終了するまで デフォルト10秒間待ちます。そして、10秒経過しても終了しない場合は、コンテナのルートプロセスに対して SIGKILL を投げます。SIGKILL は強制終了を指示するものなので、これを受信したプロセスは、直ちにABORTします。
この時、プロセスの終了に必要な処理は全てスキップされますので ABORT 後は正常な状態であることが保証されません。したがって、次回起動時にうまく立ち上がらなくなる等、思わぬ障害のリスクがあります。
今回の node.js のコンテナも、10秒後に停止しています。これはすなわち SIGKILL で強制終了していることになり、このままではやばい感じです。
原因調査
A. 物理サーバ環境と Docker 環境での違いを検証
ここで、そもそも Docker に起因する問題なのか? それとも node.js の問題なのか?を切り分けたいと思います。
ということで、仮想化しない物理サーバ環境と Docker 環境の両方で node.js の hello アプリを動かし、そのプロセスに対してそれぞれ SIGTERM を送信してみます。
(1)物理サーバ環境で検証
#### Terminal 1 で node アプリを起動 $ node hello.js #### Terminal 2 で node に SIGTERM を送信 $ ps -ef | grep node ubuntu 8526 4061 3 05:21 pts/0 00:00:00 node hello.js $ kill -TERM 8526 #### 成功、node hello.js は、直ちに問題なく終了した
うまくいきました。
物理サーバ環境において、SIGTERM を受信した node.js プロセスは、直ちに自分自身を終了させています。これにより、node.js は SIGTERM を受信した際のシグナルハンドリングがきちんと実装されていることが分かります。
(2) Docker コンテナ環境で検証
#### nodeアプリが入った Docker コンテナを起動 $ docker run --name hello_node -d nodetest $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ef2b7ab09468 nodetest "node /usr/src/app..." 50 seconds ago Up 49 seconds 3000/tcp hello_node #### 起動したコンテナにアタッチ $ docker exec -it hello_node /bin/bash #### ---- hello_node コンテナ内 ---- # ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 1 01:53 pts/0 00:00:00 node /usr/src/app/hello.js root 16 0 0 01:54 pts/1 00:00:00 /bin/bash # kill 1 ### しなない!!
ダメです!!
幾ら SIGTERM を投げても反応しません。Docker コンテナの内の node は、何かの理由で SIGTERM を受信できないか、もしくはシグナルを無視しているように見えます。
B. プロセスID 1 に注目してさらに調査
Linux についてご存知の方は、Dockerコンテナ内の node.js プロセスIDが “1” であることに、限りない怪しさを覚えるかもしれません。
普通の Linux 環境において、プロセスID = 1 は init プロセスです。これは カーネルから起動されますが、(特に Linux においては)プロセスD=1 に対してシグナルを送ることはいろいろ制限されています(see ”man 2 kill" on Linux)。
ということで、今度は、特に、プロセスID 1 に何か特殊な理由が無いか?ということを留意しながら、さらに node.js のソースコードを確認したり、同様の問題がなかったか?という調査を行いました。
・
・・
・・・
見つけました
上記ページの「Handling Kernel Signals」というところに、下記の記載があります。
Node.js was not designed to run as PID 1 which leads to unexpected behaviour when running inside of Docker. For example, a Node.js process running as PID 1 will not respond to SIGTERM (CTRL-C) and similar signals. As of Docker 1.13, you can use the –init flag to wrap your Node.js process with a lightweight init system that properly handles running as PID 1.
docker run -it –init node
You can also include Tini directly in your Dockerfile, ensuring your process is always started with an init wrapper.
まじか。。そもそもnode.js は PID 1 で動くように設計されていないとか、なので Docker コンテナで動かすときは、SIGTERM とか Ctrl+C とか効かないとか、まさに、今回の問題について言及されています。
もう一つ、"lightweight init system" というツールがあって、Docker で動かすときは、それを PID 1 として動作させ、node はその子プロセス にすればいいじゃん的なことも書いてあります。
というか、"Tini" という便利なものがあったのか!! しかも、Docker 1.13 からは “–init” オプションでこれが自動的にラップするように組み込まれているということです。知らなかった。
GitHub - krallin/tini: A tiny but valid `init` for containers
対処内容
問題の原因が分かったので、もう簡単に対策ができます。ということで、早速 Docker 本体にも組み込まれている “Tini” を活用します。
結局、今回は、以下のように Dockerfile に Tiny の設定を追加するだけで、万事 OK となりました。
FROM node:latest # Add Tini ENV TINI_VERSION v0.15.0 ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini RUN chmod +x /tini ENTRYPOINT ["/tini", "--"] # MyApp ADD ./test3.js /usr/src/app/ EXPOSE 3000 CMD ["node", "/usr/src/app/hello.js"]
まとめ
node は PID1 で動作するようにデザインされていないので、 docker にいれるときは、直接 Docker の管理プロセス(つまりPID1)にするのはやめよう!
Docker で PID 1 で Linux の init みたいに制御してくれる Tiny という便利なツールがあるので、それを活用するといいかも
そもそも、node やその他 サーバプログラムをDocker が直接起動するプロセス(つまり PID 1 となるプロセス)とするのは、いろいろ問題があるなぁと思います。
以前、Unicorn が SIGTERM ではなく SIGQUIT を期待しており、Docker stop で異常終了してしまう問題にも遭遇しています。
なので、個別に作成したスーパバイザ Shell でラップするとか、今回ご紹介した Tiny をかませるとか、その方が安全ではないか?と、最近はそう思っています。
なお、PID 1 とする Tiny のような lightweight init プログラムは、例えば、自分自身が SIGTERM を受信したときに、そのシグナルを、子プロセスまできちんと伝播させたり、子プロセスがゾンビにならないよう wait したり、そういった init の作法に則ったものでなければなりません。その辺は十分ご注意ください。
おまけ
Docker で使える lightweight init たち
ここでは、Docker コンテナで PID 1 のプロセスとして使える Tiny をご紹介しました。その後調べたところ、この他にも、幾つか似たようなものがありましたので、まとめとして、以下にリンクしておきます。
Tiny
本記事でご紹介した lightweight init 、前途した通りDocker にも組み込まれており “docker run” の “–init” オプションで自動的にラップできます。
dumb-init
“Yelp” で使用している lightweight init
my_init
“baseimage-docker” という本来の Linux に近いプロセス環境を Docker で提供している。そのイメージで使用している my_init、こちらはマルチプロセスを管理できるようです。
inits_on_docker/init_node.sh
ちなみに、私も Shell で簡易に作成してみました。node.js 専用ですが、せっかくだし、ご紹介させてください。(つっこみ歓迎)
inits_on_docker/init_node.sh at master · ngzm/inits_on_docker · GitHub
#!/bin/bash # # init_node.sh # - init process for aplications using node.js. # # Usage # - add your Dockerfile as follows. # ------------ # ADD ./init_node.sh /usr/local/bin # RUN chmod +x /usr/local/bin/init_node.sh # CMD ["init_node.sh", [path_to_your_app]] # ------------ # echo "start init_node.sh" # set application path path_to_your_app=${1:-''} if [ -z ${path_to_your_app} ]; then echo "require first argment for path_to_your_app" echo "quit this container" exit 1 fi if [ ! -e ${path_to_your_app} ]; then echo "${path_to_your_app} is not exists" echo "quit this container" exit 2 fi # Application PID initialize your_app_pid=0 # SIGINT handler int_handler() { echo "int_handler called" if [ ${your_app_pid} -ne 0 ]; then kill -INT ${your_app_pid} fi } # SIGTERM handler term_handler() { echo "term_handler called" if [ ${your_app_pid} -ne 0 ]; then kill -TERM ${your_app_pid} fi } # trap SIGINT - usually caught by Ctrl+c trap 'int_handler' INT # trap SIGTERM - sent when 'docker stop' trap 'term_handler' TERM # run application echo "run ${path_to_your_app}" node ${path_to_your_app} & your_app_pid="${!}" # wait untill the application (child process) will be killed wait ${your_app_pid} your_app_pid=0 echo "finish init_node.sh"
補足とか
何もこんなに苦労しなくても最初から “npm run” で起動するようにしておけば問題なく起動・終了ができそうです!という情報もあって、一応動作させてみたら、よさげに見えました。確かにこれだと、node の PID は 1 ではなくなるのでいい感じです。
ただし、本当にこれで、子プロセス、孫プロセスまで管理できているのか?すなわち、ゾンビにならずに、また強制終了せずに健全に起動、終了しているのか?といったところまでは裏付けができていません。なので継続して調べていきたいと思います。
誰か知っている人が居たらぜひ教えてください。
最後に苦情です
docker-node/README のページ、本家のトップページで思いっきり PID 1 で起動するインストラクションしてるじゃん
docker-node/README.md at master · nodejs/docker-node · GitHub
あと、こんなシビアな問題があるなら、もっと目立つところで情報公開してください
ubuntu ファイアーウォール :Docker が UFW 管理外のポートを開けてしまう問題
UFW とは ファイアーウォールを設定・管理するツールで、Ubuntuでは標準的に利用します。このツールを使用すれば、iptables を使うよりはるかに簡単にファイアーウォールを構築できます。
UFW - Community Help Wiki
Docker はご存知ですね。
Docker - Build, Ship, and Run Any App, Anywhere
遭遇した問題
今回 UFW と docker の両方を使用するサーバで、UFW で許可していないポート番号がいつのまにか空いてしまった問題に遭遇しました。 次のような感じです。
- ubuntu xenial サーバに対し、UFW で 22番と443番だけアクセス可能、その他は遮断というファイアーウォールをセットアップ
- 同サーバに Docker 入れて、nginx コンテナ起動、この時 “-p 80:80” オプションを付与
- 外部からこのサーバにアクセスしたら、UFW で開けていないはずの 80番ポートのページが見れてしまった
- UFW の設定を再確認してみたが、やっぱし 80番ポートは Allow してない
なんだかやばい感じです。
ちな、UFW の設定内容は以下の通りです。
$ sudo ufw status verbose Status: active Logging: on (low) Default: deny (incoming), allow (outgoing), deny (routed) New profiles: skip To Action From -- ------ ---- 443 ALLOW IN Anywhere 22 LIMIT IN Anywhere 443 (v6) ALLOW IN Anywhere (v6) 22 (v6) LIMIT IN Anywhere (v6)
問題の原因
Docker の問題
調査したところ、ひとつの原因は … Docker でした。docker run の -p や -P オプションでコンテナを起動すると、Docker は、その指定されたポート番号について、ファイアーウォールに穴を開ける処理をします。
UFWの問題
では、どうして UFW で確認できなかったのでしょうか? それは … 直接 iptables のルール一覧を確認すれば分かるかもしれません。
$ sudo iptables -L Chain INPUT (policy DROP) target prot opt source destination ufw-before-logging-input all -- anywhere anywhere ufw-before-input all -- anywhere anywhere ufw-after-input all -- anywhere anywhere ufw-after-logging-input all -- anywhere anywhere ufw-reject-input all -- anywhere anywhere ufw-track-input all -- anywhere anywhere Chain FORWARD (policy DROP) target prot opt source destination DOCKER-USER all -- anywhere anywhere DOCKER-ISOLATION all -- anywhere anywhere ... 省略 ... Chain DOCKER (2 references) target prot opt source destination ACCEPT tcp -- anywhere 172.18.0.4 tcp dpt:https ACCEPT tcp -- anywhere 172.18.0.4 tcp dpt:http Chain DOCKER-ISOLATION (1 references) target prot opt source destination DROP all -- anywhere anywhere DROP all -- anywhere anywhere RETURN all -- anywhere anywhere Chain DOCKER-USER (1 references) target prot opt source destination RETURN all -- anywhere anywhere Chain ufw-after-forward (1 references) target prot opt source destination ... 省略 ... Chain ufw-user-input (1 references) target prot opt source destination ACCEPT tcp -- anywhere anywhere tcp dpt:https ACCEPT udp -- anywhere anywhere udp dpt:https ... 省略 ...
UFW が設定したルールには、"ufw-[hogehoge]“ という名前が付いています。一方、"DOCKER” で始まるチェーンも見ることができますが、こちらは明らかに Docker によって追加されたルールですね。
どうやら UFW は 自分が設定したルール、すなわち “ufw-[hogehoge]” についてのみ管理対象としているようです。従って、Docker など他のプログラムや、手作業で追加したルールについては関与していません。これこそがもう一つの原因であり、運用する上で大変注意が必要である事項であります。
ちなみに、Docker が勝手にポートを開けるのを抑える方法
ubuntu の場合だと、”/etc/default/docker” の ”DOCKER_OPTS" オプションに、"–iptables=false" を追加すればいいようです。
DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4 --iptables=false"
なお、オプションを有効にするため Docker の再起動を忘れないようにしてください。
まとめ
-p や -P オプション(also EXPOSE)をつけて “docker run” すると Docker が 該当するポートを許可する設定を iptables に追加する。
UFW は自分で設定したルール以外は関与しない。このため、UFW で確認できる設定内容がファイアーウォールの全てではないことに注意すること。
ファイアーウォールについて、最終的には iptables コマンドでも確認した方が良いと思われます。
参考
Let's Encrypt で HTTPS な nginx 構築
おおまかな流れ
nginx を HTTPS 対応するおまかな流れは以下の通りです。
ちなみに、Let's Encript のサイトはこちらです。
また、日本語ポータルサイトもあるので、適宜参考にすること。
Certbot のインストール
基本的には、Certbot 本家サイトに従えばインストールできます。
自分のサイトは、OS:Ubuntu 16.04(Xenial) 、Webサーバ:nginx である。この場合、インストール手順は下記のとおり(本家サイトの説明通り)。
$ sudo apt-get update $ sudo apt-get install software-properties-common $ sudo add-apt-repository ppa:certbot/certbot $ sudo apt-get update $ sudo apt-get install python-certbot-nginx
証明書取得
ここからは、Certbot 本家のサイトに書いてあるのとちょっと違う。なぜなら、nginxサーバが Dockerコンテナの入っていたりゴニュゴニュしているためです。*1
今回は、3つのサーバに対応した証明書を発行してもらいます。具体的には、"-d" オプションで複数のサーバ名を指定すればOK。これについては、以下のサイトが参考になります。
また、"--standalone" オプションを指定。これにより、Certbot クライアントに内蔵されている簡易的なウェブサーバが機能が一時的に使われるため、自分サイトの nginx は一旦停止しておく必要があります。
ともあれ、実行したコマンドのログを貼り付けておきます。
### 証明書の取得 ### $ sudo certbot certonly --standalone -d my-domain.jp -d www.my-domain.jp -d app.my-domain.jp ### メールアドレスの指定(初回のみらしい) ### Saving debug log to /var/log/letsencrypt/letsencrypt.log Enter email address (used for urgent renewal and security notices) (Enter 'c' to cancel):mysccount@my-domain.jp ### なんか同意する ### ------------------------------------------------------------------------------- Please read the Terms of Service at https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf. You must agree in order to register with the ACME server at https://acme-v01.api.letsencrypt.org/directory ------------------------------------------------------------------------------- (A)gree/(C)ancel: A ------------------------------------------------------------------------------- Would you be willing to share your email address with the Electronic Frontier Foundation, a founding partner of the Let's Encrypt project and the non-profit organization that develops Certbot? We'd like to send you email about EFF and our work to encrypt the web, protect its users and defend digital rights. ------------------------------------------------------------------------------- (Y)es/(N)o: N ### 証明書が発行される!! ### Obtaining a new certificate Performing the following challenges: tls-sni-01 challenge for my-domain.jp tls-sni-01 challenge for www.my-domain.jp tls-sni-01 challenge for app.my-domain.jp Waiting for verification... Cleaning up challenges IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at /etc/letsencrypt/live/my-domain.jp/fullchain.pem. Your cert will expire on 2017-11-11. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew" - Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal. - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le
ここまで無事に成功したら、"/etc/letsencript/live/[domein name]/" の下に証明書ファイルが生成されているはず。 ちなみに、証明書はいくつかのファイルがあり、それぞれシンボリックリンクされている。
# cd /etc/letsencrypt/live/my-domain.jp # ls -l lrwxrwxrwx 1 root root 44 Aug 13 12:57 cert.pem -> ../../archive/my-domain.jp/cert1.pem lrwxrwxrwx 1 root root 45 Aug 13 12:57 chain.pem -> ../../archive/my-domain.jp/chain1.pem lrwxrwxrwx 1 root root 49 Aug 13 12:57 fullchain.pem -> ../../archive/my-domain.jp/fullchain1.pem lrwxrwxrwx 1 root root 47 Aug 13 12:57 privkey.pem -> ../../archive/my-domain.jp/privkey1.pem -rw-r--r-- 1 root root 543 Aug 13 12:57 README
nginx 設定
証明書を入手できたので、これを nginx に仕込みます。
具体的には、次の2つを設定すれば OK です。
ということで、以下のような感じになりました。
server { listen 443 ssl; server_name my-domain.jp www.my-domain.jp; ssl_certificate /etc/letsencrypt/live/my-domain.jp/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/my-domain.jp/privkey.pem; access_log /var/log/nginx/default.ssl.access.log main; location / { root /usr/share/nginx/html; index index.html index.htm; } error_page 404 /404.html; error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }
以上です。さらに、80番ポートでアクセスされた場合は、443ポートにリダイレクトするような設定を追加するとバッチリかと思います。
今後の運用
Let's Encript 証明書の有効期限は 90日間なので、今後は、90日ごとに更新手続きを行う必要があります。こちらについても、"Certbot" を使って簡単に実行できますので、そのころまたレポートを追加したいと思います。
2017年10月29日 追記: 証明書を更新しました
この記事を書いた時から3ヶ月くらい経ったので、Let's encript 証明書を更新しました。更新手順はざっくり以下の通りです。
- Nginx などwebサーバを止める
- Let's encript 証明書を更新
- Nginx などwebサーバを再起動
ここで、"2. Let's encript 証明書を更新" は次のコマンドを投入するだけでOKです。
$ sudo certbot renew ### コマンドを投入するといろいろ処理メッセージが出ます Saving debug log to /var/log/letsencrypt/letsencrypt.log ------------------------------------------------------------------------------- Processing /etc/letsencrypt/renewal/my-domain.jp.conf ------------------------------------------------------------------------------- Cert is due for renewal, auto-renewing... Renewing an existing certificate Performing the following challenges: tls-sni-01 challenge for my-domain.jp tls-sni-01 challenge for www.my-domain.jp tls-sni-01 challenge for app.my-domain.jp Waiting for verification... Cleaning up challenges ------------------------------------------------------------------------------- new certificate deployed without reload, fullchain is /etc/letsencrypt/live/my-domain.jp/fullchain.pem ------------------------------------------------------------------------------- Congratulations, all renewals succeeded. The following certs have been renewed: /etc/letsencrypt/live/my-domain.jp/fullchain.pem (success)
以上で完了です。とてもシンプルです。
次回からは、自動的に更新処理が実行されるよう cron に登録するといいですね。
*1:ノーマルな nginx サーバであれば、本家 Certbot のサイトに書いてあるように、"sudo certbot --nginx" もしくは "sudo certbot --nginx certonly" とやれば、自サイトの nginx 設定を見ながらいい感じに証明書を発行してくれるようです。
node express + react 開発環境作成手順(自分メモ)
サーバアプリは “node” + “express"、クライアントアプリには "React” を使用した場合の開発環境を構築した時の手順を記載します。あくまでも自分用メモです。
概要
大体の構成
サーバ:
”node" + “express"。クライアント:
“React"。クライアントのビルドに "webpack"。JSトランスパイラは "babel"。その他 CSS のビルドに "style-loader"、また、ビルドしたReact JSファイルを読み込む index.htmlを生成してくれるプラグインには "html-webpack-plugin” を利用する。
大まかな流れ
- node + express-generator をインストール
- express 開発環境を作成
- react インストール
- webpack 導入
- babel 導入
- style-loader 導入
- html-webpack-plugin 導入
サーバアプリ開発環境の構築
1) node + express-generator をインストール
最初に node をインストール。nodebrew を使用すると吉。
続いて、express-generator をグローバルにインストール。
$ npm install express-generator -g
2) express 開発環境を構築
express-generator でサーバアプリ開発環境の雛形を作成。
今回は、HTMLテンプレートエンジンに “pug” を指定。(実際は使用しないかもしれないけど一応あると便利かも)
$ express [app-name] --view=pug
これで、[app-name] ディレクトリ配下に、node + express 開発環境の雛形が生成され、最低限のサーバアプリ開発環境ができた。あとは、自分の必要に応じて、".gitignore" などを作ったり、EsLint のセットアップをすること。
クライアントアプリ開発環境を追加する
3) reacインストール
$ npm install --save react react-dom
4) webpack 導入
$ npm install --save-dev webpack
5) Babel を導入
# webpack から babel を利用する場合の基本モジュール $ npm install --save-dev babel-loader babel-core # React をとトランスパイルするモジュール $ npm install --save-dev babel-cli babel-preset-react # Es2015 をトランスパイルするためのもの $ npm install --save-dev babel-preset-es2015
babel の設定ファイル “.babel.rc” を作成
$ echo '{ "presets": ["react", "es2015"] }' > .babelrc $ cat .babelrc { "presets": ["react", "es2015"] }
ここで、babel で js をトランスパイルするための webpack 設定をおこなう。
$ vim webpack.config.js
ここまでの webpack.config.js の中身は次のとおり。
var path = require('path'); module.exports = { entry: './client/src/app.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'public') }, module: { loaders: [ { test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader' } ] } };
6) style-loader 導入
$ npm install --save css-loader
webpack.config.js に下記の loader を追加。
module: { { test: /\.css$/, exclude: /node_modules/, loader: ['style-loader', 'css-loader'], }, ], },
7) html-webpack-plugin 導入
$ npm install -save-dev html-webpack-plugin
index.html の元となるテンプレ “client/template/index.ejs” を作成。
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8"/> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <div id='app'></div> </body> </html>
あとは、webpack.config.js に html-webpack-plugin の設定を追加する。
plugins: [ new HtmlWebpackPlugin({ title: 'ngzm uploader', template: './client/template/index.ejs', }), ],
まとめ
最終的な webpack.config.js
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './client/src/App.jsx', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'public'), }, devtool: 'inline-source-map', module: { loaders: [ { test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader', }, { test: /\.css$/, exclude: /node_modules/, loader: ['style-loader', 'css-loader'], }, ], }, plugins: [ new HtmlWebpackPlugin({ title: 'ngzm uploader', template: './client/template/index.ejs', }), ], resolve: { extensions: ['.js', '.jsx'], }, };
なお今回は、"webpack-dev-server" は利用していませんが、これも便利そうなので次回は導入したいと思います。
参考
- Installation - React
- Babel · The compiler for writing next generation JavaScript
- React x Webpackプロジェクトの雛形を作る
- 最新版で学ぶwebpack 3入門 - JavaScript開発で人気のバンドルツール - ICS MEDIA
- Using the HTML Webpack Plugin for generating HTML files
以上。