pig's diary

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

HASURA、AWS AppSync - GraphQL PaaS比較

あくまで現状の理解です。加筆、修正すると思います。

HASURA

  • GraphQL PaaS
  • DBにテーブルを作るとQuery/Mutationエンドポイントを自動生成するスタイル
  • Actionsは独自エンドポイント。スキーマを拡張し、HTTP経由で処理する。AWS Lamda相当を自前で用意する感じに見える。
  • PostgreSQL, MySQL, SQL Server, Google BigQuery, Oracle, MongoDB
  • カスタマイズされたGraphiQLが使いやすい
  • Subscriptionが動くのかが試せてない。できるらしいけど、どうやってDBの変更を検知するんだ?
  • Free tier 60 requests per minute らしいけど、すぐ止まる。実験が全然進まない。サブスクは$99/月からで敷居が高い。
  • 他GraphQLをガッチャンコ(Schema Stiching)できる。ApolloでいうFederation
  • 全く試してないがAuthenticationはアクセスごと、JWTでなど選べるようだった。Authorizationはテーブルごと、カラムごと、行ごとに、MongoDB風のJSONベースのマッチャーで許可させるよう。画面でコチコチやるしかないのか?
  • 使い始めやすく高機能な印象。DB直挿しエンドポイント+ブラウザクライアントである程度まで作れ、Actions拡張もし易そう。ただユーザ管理、Actionsの構築は別でやる必要がある。サービスを跨いだとき、スピードが出るのだろうか?

AWS AppSync

  • GraphQL PaaS
  • スキーマを画面でぽちぽち作る。
  • ResolverはVTL(Apache Velocity Template Language)。 Lamda、DynamoDBアクセスなどご自由に
  • AppSyncはAmplify推し。AmplifyはAWSの様々なサービスを初期化できる入り口だが、AppSyncに対しては、独自ディレクティブを交えた正規化スキーマを書かせ、それが展開されると(それが実際のスキーマ)、エンドポイントや、DB Table(Dynamo限定)の型設定を自動生成する。これが強力だが、恐らく一度ejectすると威力が失われ管理が一気に大変になる
  • 独自ディレクティブの中でも @auth は鬼そうで、Cognito User Poolと連携した、GraphQL Fieldごと x ロールごとの権限管理が可能そう。
  • "AppSync" というだけありSubscription周りが強力、DynamoDBと連携したSubscriptionエンドポイントを簡単に作れる。
  • アクセス単位課金で実験できたので、安く試せた。
  • AWS AppSyncは「AWS API GatewayのGraphQL版」という感じで、VTLさえわかれば動きもシンプル、使い始めやすい印象。AmplifyこそAppSyncの価値を引き出すフレームワークと思うが、正直初見では仕様が広大すぎて、影響が把握しづらい印象。(AWS初心者の感想です)

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 を知らなかった。速くて嬉しい。

参考にした記事