pig's diary

何でも忘れるので万年初心者ね

Lerna で webpack を内包したパッケージを開発する際の注意点

Next.jsは、webpackとwebpack.configを内包し、自身のソースをエントリポイントにして起動する風変わりなnpmパッケージである。非常にレアなケースで、多くの人がその気を起こすことなく一生を終えるケースだと思うが、私は同様のパッケージを作ってみたくなった。

この「webpack内包型」のパッケージは、ユーザ側のコードを適切に取り込み、アプリを起動するのが責務となる。開発するためには、実際にユーザが使用する際を想定し、ライブラリ側のコード「libDir」と、それに依存するユーザ側のコード「userDir」の、少なくとも2つのpackage.jsonをもつディレクトリが必要になる。

2つとも限らない。userDirはユーザーに使い方を示すサンプルプロジェクトとしても使える。様々な利用ケースに対応することを示すため、サンプルプロジェクトは今後増やすかも知れない。package.jsonの数が今後増えることを想定する必要が出た。

Lernaを使うことにした。Github上でのBabelやwebpackの開発はmonorepoと呼ばれ、1つのリポジトリで複数のnpmパッケージを開発している。これに利用されるのがLernaである。 lerna init で生成される lerna.json の設定にしたがって複数のパッケージを管理する。 lerna bootstrap が有用で、リポジトリ内の対象package.jsonでお互いの依存があった場合、 node_modules にsymlinkを貼りソースを直に参照できるようにする。それだけなら yarn link と同じだが、おまけに .bin にもsymlinkを貼ってくれる。今回私はユーザ側から bin でwebpackを起動させるため、便利である。

注意点

この「Next.jsみたいなライブラリ」の開発が佳境だ。しかし、主に私のLernaとwebpackの不理解によって大いに時間を削られて来た。3週間前の私に送る警告があるとしたら、それはおよそ下のようなものになる。

  • 君はLernaの動きが分かってない。 lerna bootstrap で依存を貼るのは、package.json に依存が確認された時だけだ。期待した通りにsymlinkが貼られないので、libDirでpackしてuserDirで yarn add したりしてる君、それではLernaを入れた意味がない。userDir/package.jsonのdevDependenciesに "libDir": "*" を入れて lerna bootstrap を叩き直せ。まだpublishしてなくてもだ。
  • webpackの resolve.modules でハマりまくってる君。まずdocsをよく読めresolve.modules絶対パス相対パスを指定できる。絶対パスは期待する通りそこだけ探すが、相対パス遡って全部参照される。ここではそれを「巻き上げ解決」と呼ぶとする。相対パスは通常Node.jsの流儀に従い混乱を避けるため node_modules が指定される。これもdocsに書いてあるが、webpack.configに渡された相対パスは原則 context が起点となる。相対パス node_modules は最初は ${contextで指定されたディレクトリ}/node_modules からsearchし、次に ${context}/../../node_modules, ${context}/../../../node_modules, と巻き上げ解決する。君がころころ context の値を変えたのも災いしたな。
  • 前後するが、 stats.errorDetails: true しておけ。モジュール解決が失敗した時、どこを探して見つからなかったのか教えてくれる。
  • Lernaが貼るのはsymlinkに過ぎない。君が最初に期待するのは、contextをlibDirにし、resolve.modules相対パスnode_modulesにすることで、始めに root/packages/userDir/node_modules/libDir/node_modules、次に root/packages/userDir/node_modules と巻き上げ解決させることだった。そうは動かない。libDirの実ディレクトリはroot/packages/libDir/node_modulesなので、次に巻き上げるのはroot/node_modulesだ。ここも動いたり動かなかったりして混乱したが、最初にroot/package.jsonを整理しなかったのが災いしたな。root/node_modulesにパッケージが残ってて、ビルドが成功したように見えたこともあった。でも実際は失敗してる。意図しない場所からモジュール解決させるな。
  • 最後はwebpack-node-externalsだ。これはwebpackでバンドルしたソースを(ブラウザでなく)Node.jsで実行したい場合、除外して欲しいnpmパッケージを決定する関数を返す便利なツールだ。でもこれはお前向きじゃない。ソースを読んで分かったが、これが除外するパッケージを決定する仕組みは単純で、「${process.cwd()}/node_modules にあるディレクトリ名を除外の対象とする。」、以上。あまりに質素。第一の問題はresolve.modulesの巻き上げ解決を無視していること。除外したつもりで巻き上げ解決されたモジュールはバンドルされ、大体の場合に問題を起こす(十中八九パッケージの中でdynamic resolveしててバンドルが失敗する)。第二の問題は、「相対パスcontextを起点とする」というwebpackのルールを無視していること。おかげで「変な場所だけexternalsされる」が起こり混乱を極めた。このユーティリティは界隈のデファクトだがオフィシャルがホストしてない点に注意を払うべきだったな。1つ1つ順を追って積み上げろ、何一つお前の都合のいいようには作られていないんだから

まとめ

2週間前の私に向けて書いたたつもりが、昨日の私への苦言になってしまった。でも知ったことか。およその仕組みは分かったし、もう過ぎたことだ。

SSLはどう安全か

何度でも忘れたる。

> SSLの仕組み

https://cspssl.jp/guide/ssl.php

SSLの大きな特徴は「通信対象の真正性の確認」「通信内容の暗号化」「改ざん検知機能」の3点です。

主に暗号化について知っておこうよ。

共通鍵暗号公開鍵暗号

https://cspssl.jp/guide/key.php

共通鍵暗号

  • クライアント側が生成する。
  • 1つの鍵で、暗号化と復号化を両方やる。
  • 速い(=Webサイトの通信全てに、これを使う)。
  • 共通鍵が漏れたら暗号化しても他人が復号化できてしまうので、終わる。
    • 絶対に意図する相手に渡すため、サーバに渡すときに細工する。
    • ユーザーごとに生成。とにかく漏れたら意味がない。

公開鍵暗号

暗号通信を行うときに必要な公開鍵は、SSLサーバ証明書に含まれています。

☟これこれ〜☟

f:id:piglovesyou:20180122092347p:plain

  • 公開鍵と秘密鍵はセット。両方Webサーバに置いてある〜
    • 公開鍵は複製してばらまく錠。秘密鍵はそれを開ける鍵。
  • 安全だけど遅い。

暗号化の手順

  1. クライアントがHTTPSでサーバにアクセス。
  2. サーバがクライアントに公開鍵を送りつける!
  3. クライアントが、共通鍵を生成。それを公開鍵で暗号化し、サーバに送り返す。
  4. サーバが秘密鍵で復号化。これで両方に共通鍵が持てた。
  5. 以後、やりとりする全てのデータを共通鍵で暗号化、受け取ったら復号化。
    • 共通鍵は速いので助かる〜

どう安全か

  • クライアントが自分で作った鍵で、暗号・復号化する。自分で作るから信じられる。通信の途中で内容を覗き見したり、復号化することも原則できない。
  • 共通鍵の受け渡しが安全。公開鍵の正当性はWebサーバ管理者だけでなく第三者機関のお墨付き。仮に公開鍵が不正だった場合(秘密鍵が漏れまくってるとか)、サービス運営者だけでなく第三者機関もダメージを食うことになる(=それは起こりにくい)。

Docker おぼえがき

Be careful it's optimized only for me so the format probably doesn't make sense to you.

Docker

Build docker image without cache with a Dockerfile of the current directory with a name identiproxy with a tag 0.1

docker build --no-cache -t identiproxy:0.1 .

Stop and remove all running containers

docker ps -qa | xargs docker rm -f

Machine

Create a machine using generic (ssh) driver against 10.0.1.211 with a name "n1".

docker-machine create --driver generic --generic-ip-address=10.0.1.211 n1

https://docs.docker.com/machine/drivers/generic/#example Or against DigitalOcean

docker-machine create --driver digitalocean --digitalocean-access-token d40576... identihost-do

Others

Switch machine context

eval `docker-machine env n1`

Show machine ip address

docker-machine ip n1

Compose

https://docs.docker.com/compose/compose-file/#compose-and-docker-compatibility-matrix

Swarm

Initalize manager

docker-machine ssh n1 \
    docker swarm init --advertise-addr 10.0.1.211

# Remember the output command line

Join as a worker

docker-machine ssh n2 \
    docker swarm join --token SWMTKN... 10.0.1.211:2377

List swarm nodes

docker-machine ssh n1 docker node ls

ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS
cfy4gr1s6b2dbz13fuxp7b8pd *   n1                  Ready               Active              Leader
nen91w96s0huyregcvtwgn2dx     n3                  Ready               Active
t5blonb35i8z4g8wf4bywkdm3     n2                  Ready               Active

# Note that the command is runnable only on manager node

Start nginx in the cluster

docker-machine ssh n1 docker service create --replicas 3 --name nginxes -p 80:80 nginx

# Or

eval `docker-machine env n1`
docker service create --replicas 3 --name nginxes -p 80:80 nginx

# *Thankfully*, even though the number of replicas is 2, all nodes can return :80 service, so that we don't have to think which node will run the tasks.

List running services

docker service ls

Update running services

docker service update --publish-rm 80:80 nginxes
# It removes published ports 80

docker service update --publish-add 80:80 nginxes
# It exports 80 again

docker service update --args "ping docker.com" helloworld
# You can even update args you passed on startup

Redisのおぼえがき

Tiny memo for my personal use of Redis. Be careful it's optimized only for me so the format probably doesn't make sense to you.

About persistence

Redis provides 2 types of persistence setting: RDB and AOF.

  • RDB:
    • Redis Database File. Stored and compressed once in a while; it can loose some data after persistence and before unexpected shutdown.
  • AOF:
    • Append Only File. A file includes all info of appending operation. A file size and startup time will be big; it almost never loose data.
    • Set appendonly to yes in redis.conf

Default Setting

https://raw.githubusercontent.com/antirez/redis/4.0/redis.conf

Use in Docker

  • Port
    • 6379
  • Data file will be:
    • /data in a container
  • Config file
    • We have to specify when to boot
docker run -it --name redis \
    -p 6379:6379 \
    -v "$PWD"/data:/data \
    -v "$PWD"/redis.conf:/usr/local/etc/redis/redis.conf \
    redis redis-server /usr/local/etc/redis/redis.conf

From https://hub.docker.com/_/redis/

Commands

Command reference – Redis

String

SET, GET

List

  • LPUSH ... Insert value from left side
  • RPUSH .. Insert from right side
  • LPOP
  • RPOP
  • LLEN ... Get length
  • LRANGE key_of_list from_index to_index # Specify -1 as to_index to get to the end

Set

  • SADD
  • SCARD ... Get length
  • SMEMBERS key ... Get all

Sorted Set

  • ZADD key_of_sorted_set score value ... The score will be a key of being sorted
  • ZRANK key value ... Returns an index (ranking order)

Hash

  • HSET user:pig name "pig"
  • HSET user:pig email "pig@gmail.com" ... Now the "user:pig" contains a hash
  • HGET user:pig name
  • HGETALL user:pig

Travis CI で WebdriverIO + Headless Google Chrome で E2E テストを通す

後述の参考の記事を組み合わせただけの簡単なお仕事。

.travis.yml

---
sudo: required
dist: trusty

addons:
  apt:
    sources:
      - google-chrome
    packages:
      - google-chrome-stable

language: node_js

node_js:
  - "7"

cache:
  directories:
    - node_modules
    - "$HOME/.npm"
    - "$HOME/.cache"

before_script:
  - "export DISPLAY=:99.0"
  - "sh -e /etc/init.d/xvfb start"
  - "npm start &"
  - "sleep 3"

wdio.conf.js 該当部分

  capabilities: [{
    browserName: 'chrome',
    chromeOptions: {
      args: ['--headless', '--disable-gpu', '--window-size=1280,800'],
    }
  }],

実際のプロジェクト

GitHub - piglovesyou/react-server-side-rendering-with-css-modules

余談

Headless Chrome はバージョン59 から。手元の Mac でも npm test コマンドでテストを実行できてる。--headlessだけで起動して、オーバーヘッドがないぶん実行も速い、手軽すぎ。

あと.travis-ci.yml の cache を知らなかった。速くて嬉しい。

参考にした記事

Arch LinuxをUSBからインストールほか

安い中古PCを何台か買って、LANに繋いで遊んでみた。コモディティマシンで分散で何か。Intel Core2Duo が1800円で買えたので(送料1500円)いつもどおり Arch Linux を入れる。もちろん問題が起きる。メモをする。

USBフラッシュメモリをインストールメディアに使う

いつもはDVDだったのだけど、買ったマシンはDVDドライブが使えない。USBにarchlinux-*.iso を書き込んで、起動時に使う。

BIOSでブートデバイスの順番を変更。中古PCとか始めててドキドキだったけど、BIOSちゃんと動いてよかった。そういえばHDDで起動したとき「This HDD was erased」とかばーんってモニタに出たけど、これどうやって出してるんだろう?消されたこと、何で覚えてるんだろう。

初め Mac OSdd したら失敗、「Isolinux.bin missing or corrupt」とか言われMacをやめて別の動いてる Arch Linuxdd したらすぐに動いた。リンク先のとおり、うまく行くマシンと行かないマシンがある、という同じ結論。

というか手元のLinuxから挿してるUSBが見れない

lsblk は物理ディスクの一覧を出すコマンドだが、USBを挿してもそれらしい物が出てこない。dmesg -w して抜き挿しすると、反応がある。

この [SOLVED] Unable to mount USB, not showing up in lsblk / Newbie Corner / Arch Linux Forums は「もしかして最近 pacman -Syu したままrebootしてないとか?」「あーそれだわ」ってあって、同じ結論だった。再起動したら lsblk にちゃんと出てきた。

isoを書き込む

推奨方法の dd bs=4M if=/path/to/archlinux.iso of=/dev/sdx && sync とほぼ同じに書き込む。やる前に bs オプションについてちょっと調べ、一度に読み書きするバッファの量だそう。試しに bs=256M とかやっても動いた。それで俺が得をしたのかどうかは調べてない。

起動

電源投入、USBが起動デバイスとして認識され、Arch Linux ってちゃんと出てくる。ほっ。

ネットワークインターフェースデバイスが出てこない、LANケーブルでネットできない

俺は Network Interface Card が壊れた中古マシンをつかまされたのか?無保証って書いてあったぞ?これが中古クオリティなのか?部品とかそいうのも買わないとなのか、俺箱の中身まで面倒見るの超面倒だぞ?どうする?

この最後の彼が言う通り rmmod tg3 ; modprobe broadcom ; modprobe tg3 でモジュールをロードし直すと解決した。lspciを見ると tg3 だけっぽいのに・・。

$ lspci
(...)
01:00.0 Ethernet controller: Broadcom Limited NetXtreme BCM57766 Gigabit Ethernet PCIe (rev 01)
        Subsystem: Broadcom Limited NetXtreme BCM57766 Gigabit Ethernet PCIe
        Flags: bus master, fast devsel, latency 0, IRQ 16
        Memory at a0400000 (64-bit, prefetchable) [size=64K]
        Memory at a0410000 (64-bit, prefetchable) [size=64K]
        Capabilities: <access denied>
        Kernel driver in use: tg3
        Kernel modules: tg3
(...)

あとはいつも通り

SWAPパーティションは必要なのかな?といつも思ってたんだが、RAMが1GBより少ない人に推奨、他は自由 とあったので今回は無しにしてみる。bootとrootだけ。

こんどGistに、シェルスクリプトのfdiskとかpacstrapとか全部やってくれる君を書こう。インストールメディアでネットに繋がった時点で落として実行。こうして人はAnsibleを学ぶ機会を逃すんだね。

というわけで

またも全ての問題はarchlinuxドメインに解決策が載っていたのだった。平和は保たれた、めでたし。