りんごとバナナとエンジニア

エンジニア修行の記録

【Docker】Volumeを使わない場合のデータ書き込みの仕組み

Dockerのcontainer上にS3等の外部サービスからファイルをダウンロードできないか考えたが、そもそもVolumeを使わない場合にContainer上で書き込みを行うとどこに保存されるのかよくわかっていなかったので、ドキュメントに当たり直してみた。

まずimageとcontainerの関係を整理すると、ドキュメントに載っている以下の画像のようになる。

docs.docker.com

f:id:Udomomo:20200528091512p:plain

imageはいくつかのlayerからなっており、Dockerfileで定義したそれぞれのコマンドが1つのlayerを作っていく。新しいlayerが古いlayerの上に積み上がっていくことで、layerの再利用が可能な仕組みになっている。
このimageからcontainerが作られるとき、最後に薄いreadable/writable layer (container layer)が追加される。image layerはすべてread onlyであるため、container実行中の変更(ファイルのダウンロード・編集・削除など)は全てcontainer layerに記録される。このため、同じimageをもとに複数のcontainerが作られ、それぞれが別の変更をしても、その変更がcontainerを波及して共有されることはない。
containerが取り除かれると、container layerも消えるため、変更も消える。imageはread onlyなので変更されておらず、最初と同じ状態の新しいcontainerを作ることができる。このように、取り除かれたら変更が消えて特定の状態に戻るようなresourceを ephemeral resource という。

仮にimage内に同梱されたファイルを変更したい場合、containerのstorage driverがimage layerからファイルを探し、readable/writable layerにコピーしてくる。この処理の詳細はstorage driverの実装によって異なるが、このようなオペレーションをCoW(Copy on Write)という。この方法の利点は、container起動時にreadable/writable layerにファイルを含めておく必要がないこと。これにより、readable/writable layerを最小限の大きさに保ち、containerの起動速度への影響を抑えている。
ただし、storage driverはI/Oに最適化されているわけではない。もしcontainer上でデータのwriteを頻繁に行う場合、パフォーマンスに影響が生じてくる恐れがあるため、その場合はContainerから独立したVolumeの使用を検討する方が良い。

【書評】「実践Vim」を読んだ

普段からエディタとしてVimを使っているが、ショートカットがなかなか身につかない。不便だと思ったときはその都度調べているので断片的な知識はあるが、もっとテクニックを体系的に身に着けて編集作業の効率を上げたいと思い、この本を読んでみた。

www.amazon.co.jp

作業の繰り返し

この本は目次だけ見ると単なるtips集に見えるかもしれないが、それだけではなくVimを使うときに持っておくと良い考え方にも触れている。中でも繰り返し触れられている最も重要な考え方は2つある。

  • Vimは同じ操作を繰り返すための機能が充実しているため、適切な粒度で操作を繰り返すことが作業効率につながる
  • 繰り返しとそれをundoできるコマンドを組み合わせることで、誤った変更を防ぎながらスピードを上げることができる

例えば、 . コマンドを使うことで直前の変更を繰り返すことができる。この「直前の変更」とはコマンド操作1つだけではなく、挿入モードに入ってから出るまでの一連の編集作業全てを含めることができる(ただし、途中でカーソルキーで移動してしまうと、それ以降の変更のみとなる)。このため、例えば書いている途中に考え事を始めたときなどに細かく挿入モードから出ておけば、考えが変わったときにそれまでの変更をすぐに取り消して書き直せる。
繰り返し用のコマンドがあるのは変更時だけでなく、検索・置換・移動などにも存在する。特にファイル全体の検索( n コマンドで次の候補へ移動) と、 f コマンドによる行内での文字検索 (; コマンドで次の候補へ移動) を使えば、カーソルキーをほぼ使わなくても移動が可能そうだ。もちろん行き過ぎたときに前の候補へ戻るキーも用意されている。 なお、Vimにはコマンドを実行する際に回数を指定できる機能もあるのだが、本書では回数が明確にわかる時以外は繰り返しコマンドで1回ずつ操作することを勧めている。1回の変更範囲が細かくなることで、変更すべきでない範囲を間違って変更してしまっても気づきやすくするためだ。

オペレータコマンド

また、この本にはVimのコマンドが羅列してあるだけではなく、コマンドの体系の説明が詳しく載っていることで、丸暗記から脱却できるような工夫もなされている。例えば、以前Google検索をしたときに ci" でダブルクオートの中の文字を削除して挿入モードに移るというコマンドを断片的に知っていたが、これはオペレータコマンド c (=変更) + デリミタ区切りのテキストオブジェクト i" の組み合わせであることがわかった。 c コマンドを押すことでオペレータ待機モードに移行し、変更範囲として i" を指定しているわけだ。
また、各tipsにはオペレータコマンドの一覧やテキストオブジェクトの指定方法の一覧など、体系に合わせたコマンドの一覧表が多く載っており、コマンドの応用の幅が広がる。例えば上記の i"a" にすれば、デリミタ自体も削除することができると知った。

今までVimのテクニックは場当たり的にしか身についてこなかったが、初めて暗記以外の方法で知識を増やすことができた気がする。この本はVimで不便を感じたときのリファレンスとして長くとっておきたい。

「DataDogで始めるモニタリング基盤」を読んだ

業務でDataDogを使っているのだが、今あるログやダッシュボードがパフォーマンス管理のために本当に十分なのか検証する必要性が生じてきた。その前提としてDataDog自体の知識を得たいと思っていたところ、 id:sadayoshi_tada さんのブログでこの本を知った。

sadayoshi-tada.hatenablog.com

DataDogの機能

一口に監視と言っても、DataDogが対応している監視は外形監視、サーバー監視、アプリケーション監視、ログ監視と幅広い。それぞれ、Datadog Synthetics, DataDog Monitors, DataDog APM, DataDog Logsという機能名になっている。
外形監視は、監視対象のサービスのAPIに対してリクエストを投げ、そのレスポンスを確認するというシンプルなもの。サーバー監視は、サービスを動かしているクラウド/オンプレミスのサーバの状況をモニタリングする。しかし今回メインで知りたかったのは、アプリケーション監視とログ監視のメカニズムだった。

DataDog APMを活用する

APMとはApplication Performance Managementの略で、アプリケーションへのリクエストや、アプリケーションからミドルウェアへのリクエストに着目することで、アプリケーション上の処理自体のパフォーマンスを確認できるサービスを指す。
DataDog APMの場合、アプリケーションを動かしているのと同じサーバ・クラスター上でDataDog Agentを動かす必要がある。このDataDog Agentが、アプリケーションが送受信するリクエストを検知してDataDogのサーバに送っている。例えばKubernetesの場合、DaemonSetとして動かすことで、同じCluster上のnodeにあるアプリケーションPodを監視することができる。

docs.datadoghq.com

また、DataDog Logsを使うと、DataDog Agentからログを取得して表示できる。CloudWatchのようなことができるわけだが、DataDog Agentの情報と合わせることで、エラー発生時近くのリクエストが特定しやすくなりそうだ。

DataDogの運用担当ではないので、何かアラートが飛んできた時に試行錯誤してログを探すことが多かったが、この本でDataDog自体の概要を知ることができた。次は公式ドキュメントで個々のダッシュボードの機能を掴んでいきたい。

booth.pm

なるセミ「実践クリーンアーキテクチャ」に参加しました

nrs-seminar.connpass.com

Youtubeライブ配信で行われたイベントに自宅から参加。設計理論にとどまらない話を聞ければと期待していたが、予想以上に濃い2時間だった。

背景

nrsさんの開発チームでは、APIの定義やDBの選定すらそもそもできていない段階で先にステークホルダーに画面を見せなければならなくなってしまったらしい。ステークホルダーにとっては画面こそが最重要である。よくある話すぎてつらい。
ユーザの求める体験に即した画面を作るには、プロトタイプを作って検証し、壊して作り直すサイクルを素早く回したい。しかし、それにはスケジュール上の余裕が必要だし、そもそもAPIやDB次第で画面が実現不可能になってしまう可能性もある。クリティカルパスを避けながら、できるところを先行して開発するために、解決策の一つとして取り入れたのがクリーンアーキテクチャだった。
クリーンアーキテクチャの最大の特徴は、ビジネスロジックをEntityに詰めることで点在を防ぐこと・そして依存性逆転を用いて詳細を抽象に依存させ、詳細の決定を先送りさせることにある。
例えば、Interfaceを作ってStubUserAddInteractorクラスを実装すれば、仮の実装のままユーザ追加のテストを行うことができる。Interfaceを挟むことでフロントエンドがAPIの詳細な実装に依存しなくなり、開発を進めやすくなる。また、MVCフレームワークではDIを使うことができるので、InjectしたInterfaceの実装クラスを設定ファイルで変えることもできる。

クリーンアーキテクチャで開発するうえの問題点と解決策

とはいえ、一つのアーキテクチャのやり方を丸ごと取り入れられるケースは滅多にない。今回の場合、以下の3つの問題が発生したという。

  • PresenterとMVCフレームワークが合わない。本来のクリーンアーキテクチャの場合InteractorがPresenterに出力を返すが、MVCフレームワークではControllerがServiceからレスポンスを受け取らなければならない。
  • ユースケースごとにUseCaseInteractorを作ってControllerにInjectしないといけない。そのため、ユースケースが多いとInject地獄になる。
  • 作らないといけないクラス・Interfaceが多くなってしまい、作業が辛い。

nrsさんのチームでは、これらを以下のように解決した。

  • Presenterは思い切って捨てた。Controllerには素直にデータの値を返し、それをフロントに戻す形式を採った。クリーンアーキテクチャからは外れてしまうが、MVCフレームワークであればそこまで不自然な流れではない。
  • Inject地獄の対応としてMessage Busを作った。Message Busの中では、どのInputDataが来たらどのInteractorを使うか登録してあり、呼び出し側ではBusの中でどのInteractorを使うかは知らない。つまり、ControllerにInjectを書かなくてよくなり、ユースケースが追加されたらMessageBusを直せば良い。
  • クラス多い問題は、スカフォールディングツールを作ってコードを自動生成させるようにした。

感想

アーキテクチャの理論を勉強できる機会は多いが、実践に落とし込んだときの問題点まで聞けたのは貴重な機会だった。MVCフレームワークと完全には合わないという話は、自分も古典的MVCMVCフレームワークが完全に合っていないことで悩んでしまった経験があるためとても参考になった。単にそのまま適用できないという所で止まらずに、どう乗り越えるかを考えることこそがアーキテクトの真髄なのだろう。 また、この記事では省いたが、前半部分のクリーンアーキテクチャの解説も非常にわかりやすく、後半の実践例の話がうまく腹落ちするようになっていた。アーカイブはまだ残っているのでぜひ聴いてほしい。

AWS上でRedashを動かす

普段業務でお世話になっているRedash。個人ではローカルで動かしたことはあるが、せっかくなのでちゃんとした環境に立ち上げてみたいと思った。

EC2インスタンスを作成

まずRedash用のVPCを作り、その中にEC2インスタンスを立ち上げる。 Redash公式サイトでAMIが配布されているので、そこから直接EC2インスタンスを立ち上げることができる。デフォルトではインスタンスt2.micro になっているが、これだと動作が重いため、 t2.small に変えるのがおすすめ。

redash.io

このRedash用EC2インスタンスには、開発用にSSH接続できるようにしておくのと、Redashを使うために自分のブラウザからアクセスできるようにする必要がある。このため、EC2作成時に「自動割り当てパブリックIPを割り当て」を有効にしておくのと、IGWを作成してEC2インスタンスがあるサブネットをルートテーブルに追加しておくのを忘れてはいけない。またセキュリティグループは、あくまで開発用であることを考慮し、アクセス元を自分のIPアドレスのみにしておくとよいだろう。

RDSインスタンスを作成

次にRDSインスタンスを作成する。今回は業務用のRedash同様にMySQLを選んだ。
ポイントは、Redash用インスタンスからMySQLを叩けるようにするために、RDSのセキュリティグループにRedash用EC2インスタンスのプライベートIPアドレスを追加すること。EC2とRDSを同じVPC上に立てていれば、プライベートIPアドレスをセキュリティグループに追加することでリクエストを飛ばせるようになる。

RDSにデータ投入

地味に面倒なのが、Redash用のデータを用意すること。今回は、以前使ったことがあるredash-hands-onからデータをいただくことにした。

github.com

docker-compose.yml を覗くと、 kakakakakku/mysql-world-database:5.7 というdockerイメージがある。このイメージをローカルで起動させれば、MySQLからデータをエクスポートできるようになる。

docker run --env MYSQL_ALLOW_EMPTY_PASSWORD=yes --env TZ=Japan -p 3306:3306 kakakakakku/mysql-world-database:5.7

あとはこのデータをRDSに投入する必要がある。横着な方法がないか探したのだが、まとまったデータ移行の際は素直にデータ投入用のEC2インスタンスを立てるやり方が推奨されているようだ。

docs.aws.amazon.com

そこでRedash用VPCにもうひとつEC2インスタンスを立てる。データ投入用の仮のものなので、こちらは t2.micro で良いだろう。このインスタンスのプライベートIPアドレスをRDSのセキュリティグループに追加するのも忘れずに行う。

データ投入用のEC2インスタンスに接続できたら、mysqlクライアントをインストールし、データのインポートを行う。今回はRedashのAMIに合わせてEC2インスタンスUbuntuを使ったため、初期ユーザ名が ubuntu になっている。

$ scp -i <path_to_pem_file> mysql-world-database.sql ubuntu@<EC2 public ipv4 address>:/home/ubuntu/
$ ssh -i <path_to_pem_file> ubuntu@<EC2 public ipv4 address>

# on EC2
$ sudo apt update
$ sudo apt install mysql-client
$ mysql -u root -h <rds_host> -p -e 'CREATE DATABASE redash;'
$ mysql  -u root -h <rds_host> redash -p < mysql-world-database.sql

redash用のmysqlユーザも作っておく。データの閲覧・集計をするだけのツールなので、権限は当該データベースへのSELECT権限のみで良い。また、今後インスタンスを移行する場合のことも考え、権限は 10.0.% に対して与えておいたが、ユーザも 10.0.% にしても良かったかもしれない。

# on EC2
$ mysql -u root -h <rds_host> -p

> CREATE USER 'redash'@'10.0.1.100' IDENTIFIED BY 'xxxxx';
> GRANT SELECT ON redash.* TO 'redash'@'10.0.%';

Redashにアクセスする

準備が整ったら、ブラウザからRedashにアクセスできる。専用のDNSはまだ取得していないが、EC2インスタンスの設定画面にある「Public DNS (IPv4)」に記載されたURLを入れれば接続できる。なお、このPublic DNSには自動で割り当てられたIPアドレスの文字列が含まれているため、インスタンスを再起動すると値も変わってしまう。常に同じにしておきたければ、Redash用EC2インスタンスにElastic IPをアタッチしておく必要がある。

f:id:Udomomo:20200426155550p:plain

Redashにアクセスしてadminアカウントを作り、データベースの接続情報を登録すれば、Redashを使えるようになる。無事にRedash上からクエリを投げることができた。

Google Documentに良い感じのスタイルをつけるtips

開発ドキュメントの執筆はesaやConfluenceを使うことが多いのだが、先日ビジネスサイド向けにどうしてもGoogle Documentを使ってドキュメントを作らなければいけないことになってしまった。Word系のツールは久しく触っていなかったので、Markdown系ツールで自然に生成されるようなシンプルで見やすいスタイルをつけるのに苦戦したが、おかげでいくつかテクニックが分かった。

見出しのスタイルを整える

表示形式 -> 段落スタイル -> 枠線と網掛け から、見出し部分の背景色や枠デザインを作ることができる。文字の背景色ではなく見出しエリア全体に背景色をつけられるので、見出しを目立たせるためにはぴったり。
加えて、枠線の位置を下ではなく左にすれば、即席のアイキャッチにすることもできる。

同じ階層の要素に書式を一括提供

画面上部の「標準テキスト」や「見出し」と表示されているところをクリック する。例えば見出し1であれば 見出し1をカーソル位置のスタイルに更新 を選択することで、ドキュメント全体の見出し1が、今カーソルがある見出し1のスタイルに一括で変わる。これはスタイルを調整する時にとても重宝した。

ページ数をつける

ページ数はフッターの中につけると良い。フッターは 挿入 オプションから入れることができる。その後、 挿入 内の ページ番号 を選択し、 オプション からフッターの中にページ番号を挿入する。ページ番号の大きさや書式などは、デフォルトで一括で変えることができる。
また、タイトルページにはページ番号を入れたくないということもよくある。その場合は、フッターを挿入する際に「最初のページ」のチェックを外しておくとよい。

目次をつける

挿入 -> 目次 で目次を入れられる。目次エリア左上のリロードボタンを押せば、最新の変更に応じてページ数などが自動で反映される。
目次の項目は、ドキュメント内で使用した見出しに応じて自動生成されている。そのため、例えば最初のページのタイトルなどを目次に含めたくない場合は、そのタイトルを標準テキストにすれば目次から消すことができる。目次内の項目を手で消すのは、ページ数が変わってしまう恐れがあるためおすすめできない。

久しぶりにGoogle Documentを触ってみたが、やはり慣れてしまうとMarkdownの方が読みやすく書きやすい。mdファイルを開けるエディタを多くの人が使える世の中になってほしい。

【Scala】A => B => C 型の関数の意味

Scalaのコードを読んでいると、時々 val f: A => B => C のような関数が出てくることがある。見るたびに少し混乱していたので整理。

この A => B => CA => (B => C) と等しい。すなわち、型Aの引数を渡すと、 B => C である関数が返ってくるということ。高階関数の一種といえる。
そもそも X => Y のような表記は無名関数(関数リテラル)の記法であり、実際は Function1[X, Y] 型のオブジェクトである。Function1は引数を1個取る関数と同等の無名クラスを定義できるTraitであり、Xが引数の型、Yが返り値の型となる。例えば a => a + 1 という簡単な関数は、実際には Function1 型のオブジェクトであり、以下と同義である。

val plus1 = new Function1[Int, Int] {
    def apply(a: Int): Int = a + 1
}

これをふまえて val f : Int => Int => Int = a => b => a + b という関数を考えると、これは Int を引数とし、 Int => Int の無名関数(すなわち Function1[Int, Int] 型のオブジェクト)を返している。そのため、例えば f(2) としたとき、 b => 2 + b という関数オブジェクトが返されている。この関数にbの値を渡せば返り値はInt型となるため、 f(2)(3) とすると5 が返される。