ソモサン

私rohkiによる活動や読書の記録をつらつらと書くページです

Rust で書いた HTTP サーバーを Github 経由で Zeit へデプロイした

成果物

github.com

でけたー。面白い。

Zeit

zeit.co

クラウドベンダーの模様。
CDN やら DNS やらがある。
今回はインスタンスを作るタイプ。

Docker でデプロイ

zeit.co

Docker image でデプロイできるすごいやつです。 僕にとっては、デプロイ先のサーバーがない状態になります。
CLI 上で操作ができるよう、バイナリも用意されています。

Github との統合

zeit.co

が、今回は Github との統合を行いました。
Pull Request を送ると、すぐにデプロイが始まる感じです。

f:id:rohki:20180916111037p:plain

実際の Pull Request

こんな感じ。

ぶつかったこと

100 MB 制限

Docker image の上限が 100 MB まででした。

f:id:rohki:20180916111402p:plain

実際のログ

適当に書いた 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

はじめに

builderscon.io

いやー、最高でした!

rohki.hatenablog.com

rohki.hatenablog.com

rohki.hatenablog.com

2016年からなんだかんだ 3 年連続参加してました。
以下で特に気になったところの書きます。

IoT の闇 / id:kazuph1986 さん

SNS 禁止なのであまりかけず上記が全てではあるのですが、ひたすら頷いたり質問したりしました。
昨年に続き、こういう話が聞けるのもいいですよねー。
しかし書きたい。共感したことをひたすら書きたい。

Envoy internals deep dive / Matt Klein さん

Envoy の名前は聞いていて、役割も知っていた つもり でしたが、もっと凄まじいものでした。
ネットワークについてのありとあらゆることをやり、アプリケーションエンジニアを業務ロジックに集中できるようにする、という強い意志を感じました。

一方で、当然ながら Envoy がそのあたりのつらさを一手に引き受けることになります。
(こーれめっちゃ複雑じゃん)という感想を発表中に持っていたら、「(Envoy は)複雑なプロセスである」「利用する時はネットワークを知った上で」という発表があり、やっぱりそうだよねーと安心しました。
ネットワーク詳しくならなければ。

Understanding Microservices with Distributed Tracing / lita さん

rohki.hatenablog.com

過去にこんな調べ物をしていたりしてたので、こちらに。*1
アプリケーションを開発する方が追跡性の注入を意識しなくていいようにする、を徹底されててすごいなって感じでした。
Envoy の話を先に聞けていたが故に、内容がするっと入ってきました。良い構成したよ!

こちらでは質問しました。内容は「分散トレーシングで DB アクセスをつなげて出すためには、Envoy 経由になる? それとも DB の手前に簡易サーバーをおく?」 でした。
答えは Envoy 経由!まじか。
そしてそのあとの懇親会で、「でもコードを書き変えないで追跡の親子関係の伝播どうやるんだろう」で盛り上がりました。 で調べてみると、Trace context propagation に行き当たり、「入力コンテキストから取り出して、出力に注入する」とか書いてありそう。
まじか…まじなのか。いったいどうやってるんだろう

lld − 開発ツールの主要コンポーネントの1つをスクラッチから作成した話 / Rui Ueyama さん

内容の多くは PodCast でお聞きしていたのですが、知らなかったところへ質問した結果、自分の考え方との差分に気がついた、というところでした。
質問は、「既存ツールとのバイナリ差分をなくすことを第一のゴールとしなかったのは、増分開発する方がよかったからですか?」でした。
それに対する回答は以下 3 つです。

  • (バイナリ差分をなくすことは難しい) *2
  • 仕様をベースに作り、自分で読み込んで調査することで学習になる
  • カーゴ・カルト絶対に やらない

特に最後にハッとさせられました。
確かに「調査する時間がないから詳細はわからないけどおまじないとして入れる」とかやっちゃってるんですよね。
でもそれってもしかしたら無駄なものかもしれないのに、全然改善できずそのまま次の人に渡すことになるわけで。
調べ切れる時間を作る、腕を持たねば。

終わりに

振り返ると、もうちょい聞きに行くものの幅を増やした方が良さそうだなーと感じました。
k8s とか microservice あたりに心惹かれたものの、いやそれ聞くなら別の機会があってその時にしよ、と意図的に避けたりしました。が、それでもちょっと実利に寄りすぎな感じでした。

もしくは自分で発表するかか!何をだろ。
Rust と WebAssembly のやつは久しぶりに遊び要素が多めなのであの発展ですかねー

本当にすごいエネルギーであの場を作っていただいていて、感謝しかない感じです。
ありがとう! builderscon!!

*1:OpenCensus でのブログ流入が増えてました。そらそうだ。

*2:3番目に絡むためカッコがき

Rust で WebAssembly と戯れてどうにもならなかった

夢の跡

github.com

頑張ったけど無理でした。
やりたかったことは WebAssembly を使った音声操作です。

動機

Rust 実装の音声ライブラリについて調べてたところ、cpal に行き当たりました。
んで、何とそこには Emscripten という文字が。
お? ブラウザでも動くの?? となぜか WebAssembly で書いてみようと思ったのです。
制限時間は1日。

いや対応してないやん

それはそうなのですが、WebAssembly で動くと

  1. 必要な速度が出れば、ブラウザ上で音声規格の配布ができる
  2. 上記が達成できれば、標準化を狙った規格の試行錯誤ができる
  3. 標準化にこぎつけブラウザに組み込むとなった段階で、組み込める(かもしれない)コードが出来上がってる

こう書くと、風が吹けば桶屋が儲かる感がすさまじい。おかしいな、こんなはずでは…
それはともかく、速度を優先されてかつ私がすぐ試せるものとなると、大学時代にかじった音声処理でした。
ので、それでお試し。

やったこと

まずはネイティブ実装

#[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 の実行環境

nmi.jp

上記ページを参考させてもらいました。

<!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行目でこけてる模様。
rodiocpal をローカルにクローンして、書き換えながら確認しました。

#[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
なるほどな? 関数が見つからん、といってるので、先ほどの関数の対応表に sinfcosf を追加して、突破です。
で、次のエラー。

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 を付けた

github.com

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.tomldev-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-httphttp4selastic4s に対応してくれてます。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 で作りました。
その結果

こんな感じでトレースできるようになりました。やったぜ。

とはいうものの

真価は複数段になってからなので、今回は「こんにちわ世界」と言った程度です。
どう適用させるかとか、お金のかかり具合とか。むずかしい。

OpenAPI Generator で Gatling Client を生成してみた

OpenAPI Generator 3.0.0 リリース!!

やったぜ。2.0 はとか野暮なことはなしです。*1
ということでリリースノートを見ていると、New Generators ところに Gatling の文字があるではないですか。
試すしかない、ってことでやってみました。

やってみた

コマンド

 java -jar openapi-generator-cli.jar generate -i "/path/to/input.yaml" -o output -g scala-gatling

-g scala-gatling で galing 生成を指定しております。

フォルダ構成

├── build.gradle
└── src
    └── gatling
        ├── resources
        │   ├── conf
        │   │   ├── baseline.conf
        │   │   ├── CD.conf
        │   │   ├── CI.conf
        │   │   ├── default.conf
        │   │   ├── logback.xml
        │   │   ├── longevity.conf
        │   │   └── stress.conf
        │   └── data
        │       └── null-pathParams.csv
        └── scala
            └── org
                └── openapitools
                    └── client
                        ├── api
                        │   └── DefaultApiSimulation.scala
                        └── model
                            └── Empty.scala

ということで、Gradle のプロジェクトでした。
Android で軽く触ったくらいであんまりですが、まぁなんとかなります。

動かしてみて

食わせた Swagger は認証なしの適当なやつですが、きちんとアクセスしてくれました。
もうちょい確認が必要ですが、初期としては使えそう?
flood でも試してみましょうかねー
企業情報 or アプリ名のところ、test とかっていれてよいのかしら…?

*1:Swagger CodeGen の Pull Request に Gatling があってマージまだかなーと思ったら OpenAPI Generator に分派しておりました。