Apribase

お茶会 #80

DSC06921

公園で NEX-6 を構えていたら同じく Canon 機を構えていたおねえさんに「ハイ!写真撮ってもらえる?(英語)」と声をかけられる事案。

「ありがとうー!!(英語)」と感情を大きく出す向こうの文化は気持ちいい。でも日本人にされたらひきそうな気がしないでもないので補正かかってる。

今でこそ Canon 一眼レフの撮り方(ファインダーを覗かないと撮れない)も知っているからいいものの、一般的に「写真を撮ってもらえますか?」と頼まれてもカメラの使い方が分からないと思うのだけれど、普通の人はどうしているのだろう。それとも普通の人というのはカメラの使い方くらい常識なのだろうか、なにそれ普通すごい。

DSC06905

SEL24F18Z で風景を撮ると遠くのものが小さくなるのを強く感じる公園だけど、Touit 32mm を投入してみたら以前の不満点が少し改善されたので、やっぱり焦点距離は重要だったと今更。

でもまだ物足りない場面があるので、換算 85mm くらいでもい気がするので、やっぱり Nocticron が気になる。絵はズームでも問題ないと思うので、8割が浪漫成分。

DSC06974

スパ吉のミートソースパスタ(930円)なるものを食べてみました。

揚げナス(+100円)はけっこう大きいのを入れてもらえます。

パスタの味はそのままだとちょっと物足りなかったけれど、チーズを混ぜたらちょうどよく。

絶賛されていたので期待値を高くしすぎた。

ほーむぺーじ #36 ArangoDB でアクセスロガー作った (採用)

ArangoDB Statistics

InfluxDB, OrientDB に続くアクセスロガー第三弾 ArangoDB 版ですが、予想以上に素晴らしい DB (インターフェース)で、文句なしに採用しました、すごくいいこれ。

使いやすい API、リッチな WEB 管理画面、データを直接編集可能、学習コストの低さ、理想的です。

1. Clojure API (clarango) が最高に使いやすい

ArangoDB Clojure clarango

InfluxDB 同様 HTTP で接続するタイプの恩恵もあるとはいえ、ユーザはマップを投げるだけでよくて DB 接続周りは任せてしまっていいです。

set-connection! と set-default-db! は、グローバルを使うべきところでちゃんと使っていて、おかげで db を意識せずにコードを書けます。

Clojure API 自体が REST API を大変綺麗にまとめてる感があります。

OrientDB のときのような冗長さや依存性もなく、思い描いた通りにコードを書けました。

2. 管理 WEB インターフェースが実用レベルで綺麗

見た目がリッチで綺麗というのはそれだけで良いものですが、ごちゃごちゃしていて分からなかったり機能がなかったら意味がないわけですが、これはそれぞれの機能がどれも理解可能なシンプルさで、それでいて十分な機能を持っていました。

DB を使い始めて最初に欲しい機能として、とりあえずデータベースとテーブルを作りたい、シェルでテストデータを作りたい、プログラムから作ったデータを WEB で確認したい、そういった機能のことです。

3. AQL が予想以上にクエリ言語として使える

ArangoDB AQL

RethinkDB の JS 構文は逆に使いにくくなっていたのに対して、ArangoDB の AQL は FOR FILTER で対象を絞る、C言語の for と LISP の filter の感覚で使えます。

気にしていた JOIN のコストについても、DOCUMENT(key) で 直接ドキュメントを取れるし、取った値は LET で束縛できるし、汎用プログラミング言語のごとく foo.bar のようにドット記法で参照できるので、SQL 脳よりも汎用プログラミング脳で書いたほうがよかったです。

DSL がよく出来てる。

4. 難点といえば、JS 関連の縛りがある

ArangoDB Shell

ArangoDB 自体は C++ で書かれているものの、クエリパースにおいて JSON (のようなもの)が絡んでくる関係で、キーにドットなどが含まれていると特殊な記号と判断されてしまうので使えない仕様です。

また、仕様ではキーにハイフンは使えるのですが、JS コンソールや AQL でマイナスの意味で取られてしまうので、実質使えませんでした。

Document Keys | ArangoDB Documentation

Attribute Names | ArangoDB Documentation

Theoretically, attribute names can include punctuation and special characters as desired, provided the name is a valid UTF-8 string. For maximum portability, special characters should be avoided though. For example, attribute names may contain the dot symbol, but the dot has a special meaning in Javascript and also in AQL, so when using such attribute names in one of these languages, the attribute name would need to be quoted by the end user.

プロパティアクセスのもう一つの方法を使えばハイフンも有効なので、Clojure マップと透過的に扱うためには AQL 側を合わせたほうがいいかな。

Collection name with dash (-) symbol raises reference error on JavaScript shell arangosh · Issue #497 · triAGENS/ArangoDB

FOR doc IN AccessLog
  SORT doc["last-time"] DESC
  FOR a IN doc.stamp
    RETURN a

5. ドキュメントは頭から読めば全てが書いてある

グーグル検索してもたいした情報が出ない ArangoDB ですが、公式ドキュメントが完璧で、頭から読めば使い方がちゃんと分かるようになっていますし、仕様も書かれています。

OrientDB のように情報が飛んでいたりしないし、元々の API 自体が大変使いやすいですし。

Introduction | ArangoDB Documentation

The documentation introduces ArangoDB for you as an user, developer and administrator and describes all of his functions in detail.

6. なにはともあれアクセスロガーを作る

ArangoDB Collections

ユーザエージェントを個別テーブルに作る方針で考えていたものの、いろいろとコストが高くなりそうなケースが思い浮かんで整理していくうちに、BBClone と同じページデータを取りやすい形に落ち着き、結局 JOIN いらずになりました。

このあたりは数年使って問題が出てから検討すべき話で、今考えることじゃない。

あれ、そういえばネストもサポートしてるんだからテーブル一つでもいけそうな気さえするけど集計のしやすさ次第。

7. データを直接参照、編集まで出来る

ArangoDB Collection JSON

InfluxDB の管理画面とは異なり、わざわざ SELECT を打たなくても一覧で見られるし、中の JSON も見やすい形で参照できます。

それでいて直接 JSON データを編集まで出来てしまいます。完璧すぎる。

Querious を超える使い勝手の GUI が標準で使えるだけですごい。

8. メモリ消費量は動かしてみてから

Clojure API が使いやすければそれだけで採用する前提だったので(実際それで InfluxDB を触っていた)、管理画面とドキュメントの出来は予想以上。

あえてデメリットがあるとしたら、起動しただけで 140M から 180MB が消費されたので、管理用サーバの分にメモリが持って行かれているけれど、これだけ便利な管理画面なら惜しくない。普段は JVM の消費メモリすら惜しいと感じる貧乏サーバなのに。

データが増えた場合にどの程度オンメモリに展開されるかだけど、これだけ便利だとアクセスログだけでなく、いよいよ日記システムも移行出来そうなので、10年分の日記データを入れたらどうなるかしら。

ほーむぺーじ #35 OrientDB でアクセスロガー作った

OrientDB LINK JOIN

BBClone クローンを作る第二弾、OrientDB 版。

以前 OrientDB を使ったときは、あくまで組み込み DB としてしか使わなかったけれど、今回は InfluxDB で欲しくなった機能を使うので、前回よりはレベルアップしてるし、今回かなり理解が深まりました。

1. JOIN とは異なるドキュメントリンクの概念を理解した

Why OrientDB の最初に書かれている No More Joins の図がまさにその通りな絵なんだけれど、OrientDB で JOIN 的なことをする場合には LINK が使われます。

使っていないときは、これは内部的に計算量を少なくしてくれるのかなあと勝手に思っていたのですが、そうではなくて実際にドキュメントを作るときに内部にリンクしたいドキュメントへの参照をプロパティとして持たせるということでした。

Document Database

// CREATE A NEW DOCUMENT AND FILL IT
ODocument doc = new ODocument("Person");
doc.field( "name", "Luke" );
doc.field( "surname", "Skywalker" );
doc.field( "city", new ODocument("City").field("name","Rome").field("country", "Italy") );

この方法、確かにリンクしたいドキュメントって生成時に分かっているケースが多いから計算量の観点では賢いと思うのだけれど、依存関係が生まれてしまうのが気になる。

ちなみに .save() すると中で作ったドキュメントまで一緒に save() されました。

(defn create-session [db request]
  (let [{:keys [headers remote-addr]} request
        ua (headers "user-agent")]
    {:session-id (str (UUID/randomUUID))
     :ip (or (headers "x-forwarded-for") (headers "x-real-ip") remote-addr)
     :ua (or (get-by-index db "UserAgent.ua" ua)
             (orientdb/document "UserAgent" (merge {:ua ua} (os/parse ua) (browser/parse ua))))}))

分析結果を保存した UserAgent ドキュメントが消失したとしても Session.ua.key を参照すれば ua.key の値自体は生き残って読めるとは思うけれど、それを許容しても、ただの String な ua を where で一致させる JOIN のほうが依存関係がなくなる点ではメリットがあるとも言える気はする。

振る舞いの話というより、コードを書くときに戸惑ったので。

このケースでも :ua が String なら抽象度高いのに、document が要求され、もちろん create-doc みたいな関数で指定したプロパティをドキュメントリンクとして変換する関数を用意すれば、:ua を String のままにできるけれど、あれ、それでいいな。

(create-doc (create-session db request) {:link {:ua ua}})

やっぱり見た目がわるいので却下。

2. スキーマハイブリッドの概念を理解した

スキーマ定義をすると、スキーマを書いたところだけは強制となり、それ以外のプロパティは自由というそのままの話。

Java Schema API

OClass customer= database.getMetadata().getSchema().createClass("Customer");
customer.createProperty("name", OType.STRING);

OClass invoice = database.getMetadata().getSchema().createClass("Invoice");
invoice.createProperty("id", OType.INTEGER);
invoice.createProperty("date", OType.DATE);
invoice.createProperty("customer", OType.LINK, customer);

最初、リンクやインデックスはここでしか定義できないと思っていたので覚えてしまったけれど、後述の通りプロパティ定義時に可能だったので、スキーマレスでよかった。

3. プロパティインデックスの命名規則と操作を理解した

ドキュメントプロパティにインデックスを付ける場合、インデックス名を付けなければならないのがなんでかなあと思っていました。

Java Schema API

profile.createIndex("EmployeeId", OClass.INDEX_TYPE.UNIQUE, "id");

ところがプロパティ側の操作でインデックスを付ける方法があったので、これを使うと profile.name というようにドキュメント.プロパティの名前でインデックスが生成されました。ドキュメントに書いてほしかった。

profile.createProperty("name", OType.STRING).setIndex(true);

インデックスを API で取得する場合は IndexManager で可能。

Document Database

OIndex<?> idx = database.getMetadata().getIndexManager().getIndex("Profile.name");

4. ドキュメントが豊富な割に肝心な情報が抜けている

ドキュメント量は素晴らしいのですが、GitHub Wiki のリンクが html ではなく未レンダリングの md になっていたり、基本的な使い方はわかりやすく書いてあるものの API の詳細を知りたかったらソースを読んでおかないと、便利なメソッドが紹介されていなかったりします。

また先述の Index 操作についても、Document API, Schema, Index の3ページに情報が分散されてしまっています。

5. Java API の設計が Clojure と相性が悪い

Clojure は JavaScript を書くときには大変便利でメイン言語にまで出来たけれど、Java を書ける言語だというのは幻想だと思う。

先述した Java によるスキーマクラス定義がこれ。

OClass invoice = database.getMetadata().getSchema().createClass("Invoice");
invoice.createProperty("id", OType.INTEGER);
invoice.createProperty("date", OType.DATE);
invoice.createProperty("customer", OType.LINK, customer);

空クラスを作ってからプロパティを作っていくのがいかにも Java らしいですが、createClass でマップを喰わせて一気に作りたいものです。

(def-oclass invoice
  {:id OType/INTEGER
   :date OType/DATE
   :customer OType/LINK})

ところが OType/LINK が第三引数にリンク先ドキュメントを指定しなければならないため、マップや JSON のようなキーバリュー形式では、ベクタなしでは書けなくなります。

丸括弧にすればそれっぽく見えるけれど、Clojure 的に書くならばベクタになります。

このあたりは Stch.html のときの話と同じで見た目の話。 ほーむぺーじ #28 Jade を止めて stch.html で WordPress Theme を書いた

(def-oclass invoice
  [:id OType/INTEGER]
  [:date OType/DATE]
  [:customer OType/LINK customer])

結局、生の Java API コールで書いてもたいして変わりません。

(doto (-> database (.getMetadata) (.getSchema) (.createClass "Invoice"))
  (.createProperty "id" OType/INTEGER)
  (.createProperty "date" OType/DATE)
  (.createProperty "customer" OType/LINK customer))

6. Java 可変長引数のような Clojure 側から特定困難なエラーがある

前にも書いたけれど、query, execute の可変長 Object… が Clojure 側で空であっても Java Array を入れないとエラーになるうえに、メソッドが見つからないというエラーメッセージになってしまうので直接ソースを見ないと気づけない問題。

public <RET extends List<?>> RET query(OQuery<? extends Object> iCommand, Object... iArgs)
(defn get-by-index [db index key]
  (-> db
      (.command (OSQLSynchQuery. (str "select from index:" index " where key = ?")))
      (.execute (to-array [key]))
      (first)))

余談だけど index: の値をなぜ execute から渡さずにメソッド引数から渡すことになっているのかというと、execute から index を渡そうとするとクエリパースエラーになりました。クエリを使わずに IndexManager を経由したほうがよい。

7. Clojure で Java を楽に書けるという幻想

Clojure は JavaScript を書くには便利だけど Java を書くものではない(二度目)。

Clojure API を作るということは基本的にマップでやりとり出来るようにすることで、自前でそれを用意するのが面倒。

ラップ API のほうがコード量が多いというか、ライブラリを一つ作っているようなものだから当然だった。

(defn doc->map [doc]
  (apply merge
         (map #(hash-map (keyword %) (.field doc %)))
         (.fieldNames doc)))

(defn map->doc [doc-name m]
  (let [doc (ODocument. doc-name)]
    (doseq [[k v] m]
      (.field doc (name k) v))
    doc))

話が OrientDB から Java に脱線してる。

8. 一応完成までは作れたけれど面倒見たくない

機能は十分だけど API を用意するのが大変という話なので、Clojure API があればこれで決まりだった。

やっぱり API って大事だなとか、地味だけど DB の open close が面倒なので HTTP 経由だとそのあたり省略できていいよねとか。

HTTP でたたけて JOIN ぽいことが出来るとなると次は ArangoDB が選択肢に入るかなあ。 AQL の FOR FILTER でインデックスが効くのかどうかクエリオプティマイザの説明が見当たらないので、そこだけ気になる。

RethinkDB はインデックスを使った JOIN はあるようだけれど、そもそも「開発者指向の使いやすい API を提供」という割に、言うほど使いやすくなかったよ・・・。

機能的には OrientDB は十分だったので、今年の秋に 2.0 が出るらしいのでおもしろい機能が追加されたりするようなら、頑張って Clojure API を作り込む選択肢もあるかな。

ほーむぺーじ #34 InfluxDB でアクセスロガー作った

InfluxDB session grouping

BBClone クローンが昔から欲しくて、以前 OrientDB で途中まで作ったけれど、こうして気が向いたときに少しずつ試していて、今回は InfluxDB, RethinkDB, OrientDB, ArangoDB でどれが一番楽できるか。

第一弾は InfluxDB。

前提が「わたしが楽したい」なので、DB の善し悪し話ではない。

1. Clojure API の使い勝手

Clojure API が大変素晴らしく、post-points でマップを投げるだけでいいので、API の使い勝手は大変良いです。

これだけで、用途に向いてないにもかかわらず使いたくなる魅力。

2. シリーズ分割コスト

InfluxDB はそもそもが time 関数による集計などを得意とする DB と聞きますが、組み込みの time, equence_number 以外にインデックスが使えないので、まずセッショングルーピングはクエリだけでどうにかする方向で検討。

カラムインデックスと似たことをしたい場合は、セッションごとにテーブルを用意する方法が紹介されていました。

3. time カラムは特殊なので時間は別途保存が必要

time の値は time 関数に合わせて変化するので、例えば group by time(30s) すると time の値も 30秒ごとに丸められてしまいました。

集計のときには都合がよいものの、アクセスログの時間を一覧したい場合に不都合が出るので、accesstime を別途保存が必要。

4. time, sequence_number の同時指定でのみ更新可能で大変

基本がログをためるような用途のためか、まず UPDATE は用意されていません。

最新アクセス時間を別シリーズで一覧を持てれば少し楽できたのだけれど、やってみたら面倒だったのでボツ。

試してみた結果、time と sequence_number の二つのキーを指定して post-points することによってのみ上書きが可能ですが、post-points したときの戻り値にそれらの値は含まれていないので、知りたかったら一度 select where で特定する必要があったし、基本的にそういう用途に使わないほうがいいなという結論。

あと time は内部的にマイクロセカンドで保存されているので、クライアントライブラリで time_precision の指定に注意。

5. カラム名にハイフンを使わない

WEB インターフェースでカラムにハイフンが含まれているとクエリが失敗するのに気づくまで動かなかった。

6. 時系列ソート出来ない

現状 time でしかソートできないけれど、time() 以外で group by した場合の time は 0 なので、セッションごとにグルーピングしたうえで時間でソートしようと思っても、time も自前 accesstime でもソート不可。

ほーむぺーじ #33 Shorewall を Docker 対応にした

systemd - shorewall - docker

Docker はコンテナ起動時に iptables を使ってフォワーディングを実現してくるので、iptables 周りをいろいろ頑張らないといけませんでした。

iptables が操作されるということで、他に iptables を操作するソフトウェアを使っている場合はうまく共存するようにしなければなりません。

1. まずコンテナの通信を可能にする zone 定義を行う

まず Shorewall をドキュメントの基本に従って定義していれば、「ホスト-ファイアウォール-インターネット」間の ACCEPT DENY が出来る zone, interface, policy, rules が存在していると思います。

ここにコンテナが追加されることで必要になってくる要素を整理します。

1. コンテナがインターネットに出られる必要がある。wget などのため。
2. ホストがコンテナにアクセス出来る必要がある。nginx などリバースプロキシのため。

docker0 というデバイスを通して通信するという前提を踏まえて、zone に何でもいいので lxc などの名前で zone を定義するところから始めて interface を定義し、policy に 1 と 2 を追加しました。

なお ufw を使っている人は公式が FORWARDING を yes にするだけでいいと言ってます。

2. Docker は必ず Shorewall の後に起動しなければならない

Docker は iptables を操作します。Shorewall も iptables を操作します。

つまり Docker の後に Shorewall を実行してしまうと、Docker が作ったフォワーディング定義が消されてしまいます。従って起動順序は Shorewall の後に Docker になります。

systemd を使っている場合、After, Requires, Wants によって依存関係を定義できるので、これで順序を決めることができます。systemd を使っていない場合は知らない。

余談. ホスト OS はどうする

ウチのホストは今 Debian で、sysv-init は systemd に入れ替えています。

systemd を使う場合、最初から設定ファイルが含まれている CoreOS, CentOS, Arch を使ったほうが楽で、ウチはそれらを参考に自前で書いているので冗長感あります。

Red Hat は firewalld を使うようになったそうなので、まだ調べていないですが Shorewall 以上に楽が出来るのなら使う理由が生まれます。Red Hat は Docker サポートも始めましたし。

puppet の定義で apt リポジトリの GPG やらをたくさん定義しているので、ホストを CentOS にする場合 yum 用にパッケージリポジトリを書き直すのが少しだるいくらい。

ほーむぺーじ #32 Docker スキルを修得した (LightCounter の Docker コンテナを作った)

LightCounter - systemd docker puppet

ようやく Docker に手を出したけれど、正式版がリリースされたばかりなのでタイミング的には情報十分で良かったし、自作して使っている app-install.bash の「使用するアプリケーションは環境込みでインストールを自動化」というのと同じ考えで環境ごと配布できるのは良いです。

ちょっとした感想文と、「起動してみました」系の話では足りていない「公開しました」までの話を。今 LightCounter は Docker で動いています。

1. イメージビルドの開発の流れがコンパイルしてから実行の流れと同じ

予想以上に感心したのが、「イメージビルド、実行確認、リビルド、実行確認」というように、実行終了すれば破棄されるのはアプリケーションのメモリが解放されるのと同じなので、通常のアプリケーション開発と同じ流れで出来ること。通常の開発だったらテスト実行だろうという揚げ足はいらない。

Bitbucket にソースをコミットするとイメージが Docker Hub にビルドされるおかげで、ソースだけでなくビルド結果のイメージまでも外部に任せられるので、実際に使うときには docker run apribase/lightcounter するだけですぐ使えるというのは革命感ある。

余談だけど Dockerfile がプロジェクトルートにない場合、指定したディレクトリ名が長すぎると打ち切られて Dockerfile が見つからない Docker Hub のバグ的なものを見つけたのだけれど、これどこに報告すればよいのかしら・・・。Docker の GitHub issue でいいのか、Docker Hub に専用フォーラムがあるのか・・・。

2. Puppet と違って Docker は「何でも知っている」

Puppet の不満点は「何でもは知らないわよ、書いたことだけ」というあくまでオーバーライド形式であって、書いてないことは未管理なだけで存在はしていました。

したがって Puppet で書いたことは消してもロールバックされるわけではなかったけれど、Docker で書いたことはリビルドでやり直せるのがすごく良い。それ Vagrant でとかいう揚げ足はいらない。

Docker で起動したサービスの状態を保護するために、当然 Puppet は必要です。

3. Docker コンテナを systemd で「起動する」までは学習コストそんなにない

systemd unit ファイルは真面目に Docker との依存関係 After Requires を書いたり、事前準備のために ExecStartPre のマルチラインが必要だけれど CoreOS のドキュメントで足りました。

学習コスト的には1日もかからないのでかなりおいしいほうだと思うけれど、もしも Solaris Zone や BSD jail を触ったことすらない人だとどうなるんだろう。大丈夫だとは思うけれど。

サービスを Docker で「起動する」まではすぐにできます。

4. 実際に公開しようとするとファイアウォールの理解が必須だった

まず Nginx からリバースプロキシで LightCounter へリクエストを渡そうとしたら反応がなく 502 エラーになります。

ufw の場合は FORWARDING を yes にするだけで良いというドキュメントが公式にありますが、ウチでは Shorewall を使っており、現状 Docker にも Shorewall にも方法は書かれていないため、Shorewall をちゃんと理解して仮想ネットワークに合わせてファイアウォールの記述をしっかりしないといけませんでした。頑張ったので Shorewall の理解が大変深まった。

ちなみに ufw に乗り換える選択肢も用意してみたけれど、Shorewall と比べて致命的なデメリットがあったので破棄。

5. 永続化ボリューム権限の整理は保留

immutable なコンテナの公開まではきたものの、mutable なデータを保存する必要がある場合は、ホストボリュームをマウントするか別コンテナのボリュームをマウントするかの二通りが現状与えられている選択肢。

docker コンテナを root で動かす例がほとんどだけれど、LightCounter コンテナでは useradd lightcounter してファイルに書き出しているため、マウント用のホストディレクトリはコンテナユーザの id をオーナーにする必要があると思います。

VOLUME コマンドは強制 root 実行なので、現状 Dockerfile で mkdir chown をすることにしたけれど、これだとホストにその id が既に存在していた場合、その id を使っているプロセスからデータを参照されることになってしまいます。

GitHub でもまだ議論されている段階だったけれど、ホスト側からユーザをコンテナに渡す方法のほうがいいかもしれないし、あくまで共有ディレクトリの扱いの話だから Docker の話に閉じないし、何かしらの綺麗なアイデア次第の話だと思うので識者であればもう答えを見つけているかもしれない。

ほーむぺーじ #31 カウンタサーバ第4弾 ClojureScript 版 LightCounter 作った

LightCounter - ClojureScript Node

Node.js をランタイムとして実行する ClojureScript 版カウンタサーバを書きました。

第3弾の Clojure 版がこれの布石でしたが、期待通り Node であれば Go の 3MB とまではいかないものの、JVM の 60MB よりは遙かにマシで 20MB 程度で動かせます。

ClojureScript で Node を扱う場合は advanced externs の理解は必須で、外部 JS を綺麗に扱うには自分なりのベストプラクティスな構成を確立しておかないとなりませんが、そこは今までの経験値で既に達しています。

ほーむぺーじ #29 Grunt を止めて Leiningen で Bower と SassC と UglifyJS を統合した src dist 構成にレイアウト

Node 公式のチュートリアルは、なぜわざわざ 127.0.0.1 なんてアドレスを指定してるのかなあって思ったら、割と低レイヤーでリクエストを待ち受けるネットワークアドレスの指定だったいう ClojureScript が全く関係しないところ。

実際に使うときは Nginx からリバースプロキシで 127.0.0.1 にまわすけれど、確認用にグローバルに叩こうとしたら受け付けなかったところで気づいたというか、それまで気にしてなかったというか。

単体であればすぐに気づくし何を今更案件なものの、これはその後の docker への布石であり、systemd, shorewall, iptables, docker, lein, node という複数の要素が絡んでいたために疲れたという経緯。

1 / 17912345...102030...最後 »