Scala 関西 Summit 2018 でいろんな体験をしてきた #scala_ks
よかったー
新しい話をきいたり、初めて会う方とはなしたり、コントリビュートできたり、なじみの人に会えたり、同じ志向の人に会えたりしました。
まずスタッフの方と発表いただいた方、参加者のみなさん本当にありがとうございました。
印象的だったこと
Akkaを分散トレーシングで見てみよう by とーます さん
同志よ!(一方的)
こんな調べものをしていたのでちょーワクワクしながら待ってました。
で、聞いてる間もひたすら頷いてそだよなーという感じ。
発表の後で話した時に
- 僕「ocjdbc 試して詰まったんですよねー…公開されてなさそうだし、Gradle だから sbt の git 指定では難しくて…」
- とーますさん「あれはですね、最新は 0.0.2 なんですが 0.0.1 は公開されてるんですよ!」
- 僕「!?」
とか
- 僕「リクエスト数とサンプル数と容量の調整難しくないですか?」
- とーますさん「そうなんですよねー」
とか。
分散トレーシングで詰まっていたところが解決したり、想定していた懸念点がその通りだったりととてもありがたかったです。
「ブログみました」もうれしかったです!書いてよかった…
OSS コントリビュート
2 日目のアンカンファレンスにて id:takezoe さんとお話しながら、サクッと GitBucket へコントリビュートできました!
本当に貴重な機会でした。
https://t.co/KkfSpgWoTP
— rohki (@r_ohki) 2018年11月11日
おー、@takezoen さんとお話しながらでスッといれていただきました! #scala_ks
いいおっさんなのでビビらずやれって話なんですが、いかんせんビビりなもので。
コントリビュートできたこともそうですし、Java や Scala の OSS 事情的なお話も興味深かったです。
経験できたってことが何より大事かなーとおもっていて、これで一つハードルを越えられた気がします。がんばってこう。
Akka と k8s
アンカンファレンスにて他の方が挙げたところに便乗して質問しました。
というのも、以前 Envoy の話を聞きました。
この時に聞いたコンセプト「アプリケーション開発者がドメインに集中できるようにする」と Akka がどこか同じような感じだなーとぼんやり思っての参加でした。
解決をどうすればよいのか、までは至れてません。そのあたりは各々の製品特性によるでしょうし。
それでも意見を上げて、反応をもらえるってのは貴重な機会でした。
全体を通して
お久しぶりですといったり、スタッフをされてた学生の方と盛り上がったり、同じことを調べてる方と情報交換したり、新しいことに挑戦できたり、2日間でいろいろなことができました。
パワーワードが生まれるところにも立ち会えましたし。
来年も参加しよう
Rust で書いた HTTP サーバーを Github 経由で Zeit へデプロイした
成果物
でけたー。面白い。
Zeit
クラウドベンダーの模様。
CDN やら DNS やらがある。
今回はインスタンスを作るタイプ。
Docker でデプロイ
Docker image でデプロイできるすごいやつです。
僕にとっては、デプロイ先のサーバーがない状態になります。
CLI 上で操作ができるよう、バイナリも用意されています。
Github との統合
が、今回は Github との統合を行いました。
Pull Request を送ると、すぐにデプロイが始まる感じです。
こんな感じ。
ぶつかったこと
100 MB 制限
Docker image の上限が 100 MB まででした。
適当に書いた Dockerfile ではあったものの、400 MB…
ちょっと根本的に考えを変えないといけない感じです。
解決: Multi stage build
FROM ekidd/rust-musl-builder:stable AS rust-builder ADD . . RUN cargo build --release --target x86_64-unknown-linux-musl FROM alpine:3.8 COPY --from=rust-builder /home/rust/src/target/x86_64-unknown-linux-musl/release/zeit-sample /usr/local/bin RUN apk add --no-cache ca-certificates && update-ca-certificates ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt ENV SSL_CERT_DIR=/etc/ssl/certs CMD ["zeit-sample"]
以下 2 つを参考にしました。
こんなことできたんすねー。知らなかった…
Multi Stage Build という手法で、ドキュメント もあります。
ビルド環境と実行環境を明確に分けることで、デプロイに使用する Docker image の内容物を最小限にできます。
上記で最終的には、6.6 MB になりました。
やってみて
✅ ポートを自動検索してくれる
最初 EXPOSE
書いてたんですが、自動で見つけてくれるらしくいらなかったです。
✅ Cold 状態からの起動が早い
5 分(だったかな?) で環境が Cold 状態に移行するのですが、そこからの起動が早いです。
Cold 状態からの起動だった場合は、上記したポートの自動検索の結果がログにでます。
でも、その差がわかりづらいです。
✅ Github との連携が楽
自動でやってくれるのはよいですねー。
Dockerfile 書いて now.json 書いたらデプロイまでやってくれるってのはありがたい。
❌ 100 MB 制限がキツい?
環境や言語によっては、100 MB の制限は厳しい、のでしょうか?
Runtime やバイナリの大きさ次第では、この制限を通過するために、あれこれと努力する必要が出てくるかも。
Golang の 1 バイナリで済むというデプロイしそうがここでも聞いてくるのかなーとか思いました。
先ほどのビルド環境と実行環境を分けることで、純粋な Linux 環境にバイナリをコピーすればおしまい的な。
おわりに
で、この文章を書いてるときに関連文書を見直してるとサンプルを見つけて、かつ Rust もあるという…
now-examples/rust-rocket at master · zeit/now-examples · GitHub
おんなじじゃん!!!
ちゃんとドキュメントというかリリースノートというか、そのあたりは読みましょう…
それはさておき、デプロイの方法とか環境情報の持たせ方とか、パフォーマンスの考え方、そして実装方法。
特に初期起動コストを支払う回数が増えるってのが違うかも。
builderscon.io 2018 に行ってきた #builderscon
はじめに
いやー、最高でした!
2016年からなんだかんだ 3 年連続参加してました。
以下で特に気になったところの書きます。
IoT の闇 / id:kazuph1986 さん
やばいしか言えないくらいやばい #builderscon
— rohki (@r_ohki) 2018年9月6日
SNS 禁止なのであまりかけず上記が全てではあるのですが、ひたすら頷いたり質問したりしました。
昨年に続き、こういう話が聞けるのもいいですよねー。
しかし書きたい。共感したことをひたすら書きたい。
Envoy internals deep dive / Matt Klein さん
Envoy の名前は聞いていて、役割も知っていた つもり でしたが、もっと凄まじいものでした。
ネットワークについてのありとあらゆることをやり、アプリケーションエンジニアを業務ロジックに集中できるようにする、という強い意志を感じました。
一方で、当然ながら Envoy がそのあたりのつらさを一手に引き受けることになります。
(こーれめっちゃ複雑じゃん)という感想を発表中に持っていたら、「(Envoy は)複雑なプロセスである」「利用する時はネットワークを知った上で」という発表があり、やっぱりそうだよねーと安心しました。
ネットワーク詳しくならなければ。
Understanding Microservices with Distributed Tracing / lita さん
過去にこんな調べ物をしていたりしてたので、こちらに。*1
アプリケーションを開発する方が追跡性の注入を意識しなくていいようにする、を徹底されててすごいなって感じでした。
Envoy の話を先に聞けていたが故に、内容がするっと入ってきました。良い構成したよ!
こちらでは質問しました。内容は「分散トレーシングで DB アクセスをつなげて出すためには、Envoy 経由になる? それとも DB の手前に簡易サーバーをおく?」 でした。
答えは Envoy 経由!まじか。
そしてそのあとの懇親会で、「でもコードを書き変えないで追跡の親子関係の伝播どうやるんだろう」で盛り上がりました。
で調べてみると、Trace context propagation に行き当たり、「入力コンテキストから取り出して、出力に注入する」とか書いてありそう。
まじか…まじなのか。いったいどうやってるんだろう
lld − 開発ツールの主要コンポーネントの1つをスクラッチから作成した話 / Rui Ueyama さん
内容の多くは PodCast でお聞きしていたのですが、知らなかったところへ質問した結果、自分の考え方との差分に気がついた、というところでした。
質問は、「既存ツールとのバイナリ差分をなくすことを第一のゴールとしなかったのは、増分開発する方がよかったからですか?」でした。
それに対する回答は以下 3 つです。
特に最後にハッとさせられました。
確かに「調査する時間がないから詳細はわからないけどおまじないとして入れる」とかやっちゃってるんですよね。
でもそれってもしかしたら無駄なものかもしれないのに、全然改善できずそのまま次の人に渡すことになるわけで。
調べ切れる時間を作る、腕を持たねば。
終わりに
振り返ると、もうちょい聞きに行くものの幅を増やした方が良さそうだなーと感じました。
k8s とか microservice あたりに心惹かれたものの、いやそれ聞くなら別の機会があってその時にしよ、と意図的に避けたりしました。が、それでもちょっと実利に寄りすぎな感じでした。
もしくは自分で発表するかか!何をだろ。
Rust と WebAssembly のやつは久しぶりに遊び要素が多めなのであの発展ですかねー
本当にすごいエネルギーであの場を作っていただいていて、感謝しかない感じです。
ありがとう! builderscon!!
Rust で WebAssembly と戯れてどうにもならなかった
夢の跡
頑張ったけど無理でした。
やりたかったことは WebAssembly を使った音声操作です。
動機
Rust 実装の音声ライブラリについて調べてたところ、cpal に行き当たりました。
んで、何とそこには Emscripten という文字が。
お? ブラウザでも動くの?? となぜか WebAssembly で書いてみようと思ったのです。
制限時間は1日。
いや対応してないやん
それはそうなのですが、WebAssembly で動くと
- 必要な速度が出れば、ブラウザ上で音声規格の配布ができる
- 上記が達成できれば、標準化を狙った規格の試行錯誤ができる
- 標準化にこぎつけブラウザに組み込むとなった段階で、組み込める(かもしれない)コードが出来上がってる
こう書くと、風が吹けば桶屋が儲かる感がすさまじい。おかしいな、こんなはずでは…
それはともかく、速度を優先されてかつ私がすぐ試せるものとなると、大学時代にかじった音声処理でした。
ので、それでお試し。
やったこと
まずはネイティブ実装
#[no_mangle] pub extern "C" fn hoge() -> f64{ let device = rodio::default_output_device().unwrap(); let sink = Sink::new(&device); // Add a dummy source of the sake of the example. let source = rodio::source::SineWave::new(440).take_duration(Duration::from_secs(3)); sink.append(source); sink.sleep_until_end(); 1.0 }
サイン波を3秒再生する実装です。
使用したライブラリは rodio で、ドキュメントとかを見ながら実装しました。
ローカルでの WebAssembly の実行環境
上記ページを参考させてもらいました。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <script> fetch('../target/wasm32-unknown-unknown/debug/wasm_sample.wasm') .then(response => response.arrayBuffer()) .then(function(bytes) { return WebAssembly.instantiate(bytes, {}); }) .then(results => { console.log(results.instance.exports.fuga(2)); }); </script> </head> <body> </body> </html>
IntelliJ で HTML を書いて右上にマウスカーソルを動かすと、HTTP サーバーとして動いてくれます。
ので、今回はそれを使いました。お試しなので許して。
WebAssembly 呼び出せてるよなー、の確認で加算関数を作って呼び出し。
無事呼び出せたので次。
ImportObj の定義
いよいよ音声再生だーと呼び出してみたところ、即失敗。
WebAssembly.instantiate
にてエラーが発生していて、エラーメッセージは以下でした。
TypeError: import object field 'env' is not an Object
env とは???
で、エラーメッセージで調べてみると、以下を見つけました。
Rocket - A Rust game running on WASM
なるほどなー、足りない関数の対応表を渡してやる必要があるわけですな。
ということで、そのままパクる。
関数の呼び出しに成功しました。んが!
WebAssembly 環境の追加
動きませんでした。 そらそうだよね!
print デバッグをやって確認したところ、1行目でこけてる模様。
rodio と cpal をローカルにクローンして、書き換えながら確認しました。
#[cfg(not(any(windows, target_os = "linux", target_os = "freebsd", target_os = "macos", target_os = "ios", target_os = "emscripten")))] use null as cpal_impl; use std::error::Error; use std::fmt; use std::iter; use std::ops::{Deref, DerefMut}; mod null; mod samples_formats; #[cfg(any(target_os = "linux", target_os = "freebsd"))] #[path = "alsa/mod.rs"] mod cpal_impl; #[cfg(windows)] #[path = "wasapi/mod.rs"] mod cpal_impl; #[cfg(any(target_os = "macos", target_os = "ios"))] #[path = "coreaudio/mod.rs"] mod cpal_impl; #[cfg(target_os = "emscripten")] #[path = "emscripten/mod.rs"] mod cpal_impl;
引用元 cpal/lib.rs at 77ce5eba06447275ce6a094ac053e6775272ad8c · tomaka/cpal · GitHub
当たり前なのですが WASM の文字がなく、結果として未対応環境の null
に行きついてエラーになってました。そらそうだ。
同じブラウザで動かすし、内部で使われてる stdweb は WebAssembly 対応してるぽいから、emscripten
の記載部分に wasm 追加すればいいかなー、と安直に考えて追加しました。
中を見たところ WebAudio に対応してるかを判定してたので、WebAssembly から API 呼び出しできればいけるかなーってところでした。
追加方法の参考リンクは以下です。
https://stackoverflow.com/questions/48350087/how-do-i-conditionally-compile-for-webassembly-in-rust
https://github.com/koute/stdweb/blob/master/src/webcore/ffi/mod.rs
記載方法はこんな感じ。
#[cfg(any(target_os = "emscripten", all(target_arch = "wasm32", target_os = "unknown")))] #[path = "emscripten/mod.rs"] mod cpal_impl;
Cargo.toml も併せて修正。
[target.'cfg(any(target_os = "emscripten",all(target_arch = "wasm32", target_os = "unknown")))'.dependencies] stdweb = { version = "0.1.3", default-features = false }
んでなんと通ってしまった!
そして次のエラー。
sinf/cosf の補填
突破だーと思ったところ、またもやエラーでした。
LinkError: import object field 'sinf' is not a Function
なるほどな? 関数が見つからん、といってるので、先ほどの関数の対応表に sinf
と cosf
を追加して、突破です。
で、次のエラー。
LinkError: "import object field 'emscripten_asm_const_int' is not a Function" にて挫折・終了
軽く調べて C の header ファイルに行きついた瞬間に、これはちょっとこれまでのやつと違う気がする、となってきました。
C から Javascript の関数を呼び出すための関数、らしいのですが、今そういったことをやってないんですよね。
関数の役割的に sinf
のように補填すればよい、ということでもなさそうです。
んで、stdweb の実装に確かに記載をみつけ、mod.rs
での切り替えや macro.rs
での呼び出しも見つけましたが、よくわからんぞーとなってきて心折れてきたところで夜でした。
難しい
そんな簡単にいくわけがない、とわかっていたものの、中途半端になってしまいました。
いつかリベンジ。いつか…
Kinesis Stream の中身を追いかけて出力する k-iter に verbose mode を付けた
0.3.0 でございます。
呼び出し方と形式
--verbose
を付ける。以上!
k-iter --verbose -n sample-stream -r ap-northeast-1
形式
JSON で出してます。
既定ではデータを UTF8 の文字列としてだします。
{"ApproximateArrivalTimestamp":1533704447.987,"Data":"155.103.165.228 - - [08/Aug/2018:14:00:47 +09:00] \"DELETE /list\" 200 3568 \"-\" \"Mozilla/5.0 (Windows NT 6.0; Win64; x64; rv:5.8) Gecko/20100101 Firefox/5.8.6\"\n","PartitionKey":"9612982382","SequenceNumber":"49587101216856299235294266286047359784703484607321866242"}
背景
Kinesis Stream の中を追跡する際には、そのデータがいつ投入されたのか、ってのも大事かと思います。
データ生成のタイムスタンプとデータ投入のタイムスタンプを比べたい感じです。
データ生成は自分たちで埋め込んで頑張りますが、データ投入は Kinesis Stream のレコード情報にあるので、そちらを利用したいです。
jq などで整形・加工しましょう、ってスタンスです。これ以上は頑張らないです。
現在のオプション
--help
で確認できるオプションは以下のような感じ。
--iterator-type
増やしたり、--data-format
追加したりもしてました。
USAGE: k-iter [FLAGS] [OPTIONS] --region <NAME> --stream-name <NAME> FLAGS: -h, --help Prints help information -V, --version Prints version information --verbose Enable verbose mode. OPTIONS: --data-format <TYPE> Set data output-format. [default: UTF8_STRING] [possible values: RAW_BYTES, RAW_STRING, UTF8_STRING] -t, --iterator-type <TYPE> Sets iterator type. [default: LATEST] [possible values: LATEST, AT_SEQUENCE_NUMBER, AFTER_SEQUENCE_NUMBER, AT_TIMESTAMP, TRIM_HORIZON] -r, --region <NAME> Sets a region name. [possible values: ap-northeast-1, ap-northeast-2, ap-south-1, ap -southeast-1, ap-southeast-2, ca-central-1, eu-central-1, eu-west-1, eu-west-2, eu -west-3, sa-east-1, us-east-1, us-east-2, us-west-1, us-west-2, us-gov-west-1, cn -north-1, cn-northwest-1] --sequence-number <NUM> Set Sequence number when Iterator Type is AT_SEQUENCE_NUMBER or AFTER_SEQUENCE_NUMBER. -s, --shard-id <ID> Set shard id. [default: shardId-000000000000] -n, --stream-name <NAME> Sets a stream name. --timestamp <TIMESTAMP> Set timestamp(UNIX Epoch milliseconds) when Iterator Type is AT_TIMESTAMP.
つづき
終了条件の指定、とかですかねー。ある条件に到達したらプロセスを終了する的な。
- N レコードとったら
- ある日付に到達したら
- あるシーケンスナンバーに追い付いたら
- 先頭に追い付いたら
できそうなのはこんなもんでしょうか?
あとは複数シャードもかな?
Rust のお試しコードを実行する: cargo run --example
タイトル落ち
その通り。自分メモです。
背景
新しい Web フレームワーク warp が出ました。
それで試してみよう、となった時に "この examples
ってどう実行するんだろう" となった感じです。
方法
cargo run --example hello
先に書いた通り、cargo
サブコマンドである run
にオプションと値を付けることで実行できます。
もし、examples
内でのみのライブラリ依存関係がある場合は、Cargo.toml
の dev-dependencies
に記載すればよい模様。 *1
おわりに
いやー、よく考えてはります。
*1:厳密にいうと、test と benchmark でも使います
OpenCensus について調べて試した
OpenCensus
Google 発案の分散トレース/メトリクス収集の仕様および実装、のはず。
opentracing.io とは似て非なるもの。
分散トレーシングについての仕様がいくつかあるようなのだけれども、部分的なものや似たようなものがあるので、それを整理して一括りにしてみた、というイメージです。
組み込む方のインターフェースやスキーマ とか、ライブラリ構成 もあれば、伝播のための仕様もあります。
んで、その Scala Wrapper があるじゃあないですか、ということで試しました。
Github グループ
GitHub グループが 2 つあったんですねー、気が付かなかった。
1 つ目が OpenCensus のメイングループで、先ほど挙げた仕様もこのグループに所属してます。
2 つ目がメインを補完するグループで、今回試した opencensus-scala もこちらの所属です。
opencensus-scala
メイングループに所属してる opencensus-java の軽量 Wrapper とのこと。
と、いいつつ、akka-http
や http4s
、elastic4s
に対応してくれてます。Pray framework は 計画済み とのこと。
elastic4s でためす
HttpClientExampleApp.scala をちょっとばかし改造して、以下のようにしました。
package com.sksamuel.elastic4s.samples import com.sksamuel.elastic4s.{ElasticsearchClientUri, RefreshPolicy} import com.sksamuel.elastic4s.http.HttpClient import com.sksamuel.elastic4s.http.search.SearchResponse import com.sksamuel.elastic4s.http.ElasticDsl._ import com.github.sebruck.opencensus.elastic4s.implicits._ // ここ import scala.concurrent.ExecutionContext.Implicits.global object HttpClientExampleApp extends App { // you must import the DSL to use the syntax helpers val client = HttpClient(ElasticsearchClientUri("localhost", 9200)).traced // ここ client.execute { bulk( indexInto("myindex" / "mytype").fields("country" -> "Mongolia", "capital" -> "Ulaanbaatar"), indexInto("myindex" / "mytype").fields("country" -> "Namibia", "capital" -> "Windhoek") ).refresh(RefreshPolicy.WAIT_UNTIL) }.await def result: SearchResponse = client.execute { search("myindex").matchQuery("capital", "ulaanbaatar") }.await.right.get.result // prints out the original json println(Iterator.continually(result).take(1000).toIterable.last.hits.hits.head.sourceAsString) Thread.sleep(1000) client.close() }
あと設定ファイルも作りました。
opencensus-scala { trace { // The probability of which a trace gets sampled, the default is 1/10000 sampling-probability = 1.0, exporters { zipkin { // Wether the Zipkin exporter should be enabled enabled = true // Example http://127.0.0.1:9411/api/v2/spans v-2-url = "http://127.0.0.1:9411/api/v2/spans" // the local service name of the process service-name = "test" } } } }
今回はローカルに zipkin を作って、そこに投げてます。
これは openzipkin/docker-zipkin: Docker images for OpenZipkin で作りました。
その結果
— rohki (@r_ohki) 2018年7月9日
こんな感じでトレースできるようになりました。やったぜ。
とはいうものの
真価は複数段になってからなので、今回は「こんにちわ世界」と言った程度です。
どう適用させるかとか、お金のかかり具合とか。むずかしい。