nがひとつ多い。

えぬなおの技術的なことを書いていくとこ。

【Rust】連続した文字列を1個にする。

TL;DR

fn main() {
    println!("{}", space_replacer("hello     world  dayo e?".to_string().as_mut_str()));    
}

fn space_replacer(src: &mut str) -> String {
    let dst = &mut src.replace("  ", " ");
    match dst.find("  ") {
        Some(_) => {
            space_replacer(dst)
        },
        None => dst.to_string(),
    }
}

// => hello world dayo e?

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=378ca4a103f099eeea9b7c7d1e48dc69

【Rust】【Docker】Cargoプロジェクトで素直に書いたDockerfileをdocker buildするとソースが書き換わるたびにフルビルドが走って滅茶苦茶遅いのを対策する方法を改良してもっとキャッシュさせる。

目次

ソース

RustのCargoプロジェクトで素直に書いたDockerfileをdocker buildするとソースが書き換わるたびにフルビルドが走って滅茶苦茶遅いことはcargoのファイルだけコピーしてビルドすることで解決します - ncaq

問題点

記事内でもあるが、

この方法を使っても,自分のアプリケーションは差分ビルドすることは出来ません またライブラリのバージョンを1つだけ上げるような行為を行っても当然フルビルドになってしまいます.

これがめっちゃしんどいのに対する対策が今回の議題。

おさらい

元々、任意のRustプロジェクトに対し、

COPY . .
RUN cargo build --release

素直にこれをやるとフルビルドが走るよねってことで、

# プログラムの依存関係だけをコピー
COPY Cargo.toml Cargo.lock /work-dir/
# 何もプログラムが無いとビルドエラーになるのでダミーのものを用意する
RUN mkdir -p /work-dir/src/ && touch /work-dir/src/lib.rs

# キャッシュのために依存ライブラリだけをビルドする
RUN cargo build --release

# リポジトリ全体をコピー
COPY . .

# 本物のビルドを行う
RUN cargo build --release

これをやると依存関係のビルドだけ行われた・・・かに思われるが、 これには罠があり、Cargo.toml Cargo.lockが書き変わると2行目からやり直しになる。

これが例えばCIとかであれば独自のキャッシュ機構とかを使ったりして問題無いかもしれないが、dockerのシンプルなキャッシュを使ってローカルでビルドする事はできない。

これは面倒だ。

対策:ダミーのCargo.toml Cargo.lockを作ってDockerfileに仕込んでおく。

これは泥臭いアプローチである。簡単に言えば、docker(正確にはbuild kitか?)を騙す的なアプローチ。

github.com

  • dummy-cargo-toml-createrを実行してCargo.toml Cargo.lockのパッケージのバージョンだけを0.1.0に固定したDummyVersion.tomlとDummyVersion.lockに作る。
$ cargo install dummy-cargo-toml-creater
$ ~/.cargo/bin/dummy-cargo-toml-creater
$ ls ./DummyVersion.toml
./DummyVersion.toml
$ ls ./DummyVersion.lock
./DummyVersion.lock

この操作は、Makefileとか使ってるなら

dummy-cargo-toml-create:
    ~/.cargo/bin/dummy-cargo-toml-creater

docker-build: dummy-cargo-toml-create
    docker build dummy .

とかしておけば忘れない。

  • 依存パッケージのビルドの時にそのDummyVersion.tomlとDummyVersion.lockをCargo.toml Cargo.lockとしてDockerに入れ込んでビルドする。

  • 最後に ./src、そしてCargo.tomlとCargo.lockを入れ込んでビルドする。

最終的に ekidd/rust-musl-builder を使ったりした時はこうなる。

FROM ekidd/rust-musl-builder:nightly-2019-04-25 as builder

## Build Cache Dependency Library
RUN mkdir /tmp/app
WORKDIR /tmp/app
## Build Dependency Library with DummyVersion.toml/lock
COPY DummyVersion.toml ./Cargo.toml
COPY DummyVersion.lock ./Cargo.lock
RUN mkdir -p src/ && \
    touch src/lib.rs
RUN sudo chown -R rust:rust .
RUN cargo build --release
## Build Base Library with Cargo.toml/lock
COPY ./src/ ./src/
COPY Cargo.toml ./Cargo.toml
COPY Cargo.lock ./Cargo.lock
RUN sudo chown -R rust:rust .
RUN cargo build --release

これでdocker buildをすれば、例え Cargo.tomlのpackage.versionを書き換えても依存パッケはキャッシュされ続けるし、それ以外が書き換わった時はしっかりそのキャッシュは使われなくなる。

dummy-cargo-toml-createrは何をしている?

fn ctoml_creater() {
    let manifest_str = match File::open("./Cargo.toml") {
        Ok(file) => {
            let mut buf_file = BufReader::new(file);
            do_cat(&mut buf_file)
        },
        Err(e) => panic!("{}", e),
    };
    let mut doc = manifest_str.parse::<Document>().expect("invalid doc");
    doc["package"]["version"] = value("0.1.0");
    let mut file = std::fs::File::create("./DummyVersion.toml").unwrap();
    file.write_all(doc.to_string().as_bytes()).expect("Could not write to file!");
}

fn clock_creater() {
    let manifest_str = match File::open("./Cargo.lock") {
        Ok(file) => {
            let mut buf_file = BufReader::new(file);
            do_cat(&mut buf_file)
        },
        Err(e) => panic!("{}", e),
    };
    let mut doc = manifest_str.parse::<Document>().expect("invalid doc");
    let mut counter = 0;
    let mut idx = 0;
    let tables = doc["package"].as_array_of_tables_mut().unwrap();
    for t in tables.iter() {
        for v in t.iter() {
            if v.0 == "name" {
                if let Some(s) = v.1.as_str() {
                    if s ==  env!("CARGO_PKG_NAME") {
                        idx = counter;
                        break
                    }
                }
            }
        }
        counter += 1;
    }
    doc["package"][idx]["version"] = value("0.1.0");
    let mut file = std::fs::File::create("./DummyVersion.lock").unwrap();
    file.write_all(doc.to_string().as_bytes()).expect("Could not write to file!");
}

https://github.com/nnao45/dummy-cargo-toml-creater/blob/master/src/main.rs

やってることは、

  1. Cargo.tomlとCargo.lockを読み込んでStringで持つ。
  2. manifest_str.parse::<toml_read::Document>()を使って toml_read::Document 型にパースする。
  3. 2で作ったオブジェクトの持つtoml::Valueに頑張ってたどり着いて、書き換える
  4. 書き換えたオブジェクトをDummyVersion.tomlとDummyVersion.lockとして保存する。

3の時の Cargo.lock だが、正直めっちゃイケてない・・・が、どうやってスマートにpackageというtomlの配列テーブルから自分のパッケージを探し出して書き込むのか考えるのが面倒だったから途中でやめた・・・・機能は満たしているからいいだろ感。

もっと言えば、別にrustでこの機能を書く必要さえ無いと言えば無い・・・笑 が、今回のrustのビルドが目的なので、まぁrustで書くかとなった。

最後に

正直綺麗な実装とは言えないが、キャッシュも依存の更新も、パッケージ自体のバージョンアップにも対応していて上手くいく。dummy-cargo-toml-createrも大したことやってないので、一つのアプローチとして是非使いたい人はカスタマイズして各々の開発環境に即してキャッシュを効かせてみて欲しい。

流石に、cargo1.40くらいには対応されそうなもんだが・・・。

【Kubernetes】【Network】GKEの1.14で追加されるIPマスカレード出来るアドレス範囲について。

ある日、こんな記事が。

Release notes (Rapid channel)  |  Kubernetes Engine Documentation  |  Google Cloud

GKE Sandbox is supported on v1.14.x clusters running v1.14.2-gke.2 or higher.

The following IP ranges have been added to default non-IP-masq iptables rules:

100.64.0.0/10

192.0.0.0/24

192.0.2.0/24

192.88.99.0/24

198.18.0.0/15

198.51.100.0/24

203.0.113.0/24

240.0.0.0/4

つまりどういう事だってばよ、というお話。

目次

IPマスカレード??

www.kddi.com

IPマスカレードとは、ネットワーク機器に割り当てられたIPアドレスを変換して、1つのIPアドレスで通信ができるようにする手法。ルーターファイアウォールと呼ばれる機器でIPマスカレードは実現され、変換する性能によって、通信速度や接続できる端末数が決まる。

Network Address Port Translation(NAPT)と呼ばれることもある。

NAPTっていうと理解が早いね。 何でkubernetesにこのNAPTの話が出てきたかというと、

cloud.google.com

クラスタで IP マスカレードを使用すると、個々のポッド IP アドレスがリンクローカル範囲と任意の追加 IP 範囲の外部トラフィックに公開されなくなり、クラスタのセキュリティが向上します。さらに、マスカレードを使用しない IP 範囲間の通信を設定することもできます。たとえば、192.168.0.0/16 のアドレス範囲にあるポッドが 10.0.0.0/8 のアドレス範囲のネットワーク リソースと通信できます。

kubernetesが自分のホスト外と通信を行う時にはIPマスカレードを使用している、そのアドレス範囲がプライベートアドレス外の上記の予約アドレスでも可能になるというお話だ。

プライベートアドレスについて

従来、GKEのpodのアドレス割り当て範囲は--cluster-ipv4-cidr とか --services-ipv4-cidrとかで使用するいわゆるプライベートアドレスだ。

ネットワークの概要  |  Kubernetes Engine のドキュメント  |  Google Cloud

現在の情報だと、まだGKEのpodに上記の予約アドレスは振れないので、おそらく今回の対応は将来的な実装のお話、もしくはオンプレとのマスカレードの為に実装されるんだと予想している。

プライベートアドレスはRFC1918で策定されている通り。

www.nic.ad.jp

結論は、

他の組織のホストひいてはインターネットへのアクセスを必要としない ホスト。

に振るアドレス。

Internet Assigned Numbers Authority (IANA)は次の三つのIPアドレス空間

ブロックをプライベートインターネットのために予約している。

10.0.0.0 - 10.255.255.255 (10/8 prefix)

172.16.0.0 - 172.31.255.255 (172.16/12 prefix)

192.168.0.0 - 192.168.255.255 (192.168/16 prefix)

らしい。 当然GKEのpodに振られるアドレスも上のアドレス範囲であった。

しかし、今回の発表では、

100.64.0.0/10

192.0.0.0/24

192.0.2.0/24

192.88.99.0/24

198.18.0.0/15

198.51.100.0/24

203.0.113.0/24

240.0.0.0/4

以上のIPマスカレード出来るアドレス群が追加されている。 正確に言えば、

The following IP ranges have been added to default non-IP-masq iptables rules:

以下のIP範囲がデフォルトの非IP-masq iptablesルールに追加されました。

だそうだ。

さて、僕らが知りたいのは、

  • このアドレス群は外部とIPと干渉しあわないのか?
  • 外部にこのIPアドレス範囲が存在してもGKEのiptablesやcalicoなどCNI、上位ISPが意図しないルーティングを起こさないか?

である。

結論はIANAによって予約されたアドレスだからOK

一覧は↓

予約されたIPアドレス | 技術資料 | ウェブチェッカー | ウェブサイトの更新を定期的にチェックしてメールでお伝えします

IPアドレスのうち、次のIPアドレスは特殊な用途のために予約されたものとなり、グローバルIPネットワークと直接接続している環境上で勝手に使う事はできません。

※もちろんプライベートIPアドレスについては、プライベートネットワーク内においては自由に使う事ができます。

出展 Wikipedia IPv4 2010年9月22日現在

しかし、まぁせっかくなので各アドレスの概要に触れていくことにしよう。

RFC3330(Special-Use IPv4 Addresses)

www5d.biglobe.ne.jp

どうやらこのRFCに正体が書いてあるかもしれないので見ていく。

192.0.0.0/24

192.0.0.0/24 - This block, corresponding to the numerically lowest of the former Class C addresses, was initially and is still reserved by the IANA. Given the present classless nature of the IP address space, the basis for the reservation no longer applies and addresses in this block are subject to future allocation to a Regional Internet Registry for assignment in the normal manner.

192.0.0.0/24 - このブロックは前のクラスCアドレスの数値的に最も低いも のであって、まだIANAによって予約されています。IPアドレス空間の 現在のクラスレスの性質という条件のもとで、予約の根拠はもう適用されま せん、そしてこのブロックの中のアドレスが将来の地域インターネット登記 所の標準的な方法での割当ての適用を受けています。

192.0.0.0/24はIANAで予約されてるから大丈夫そう。もともとサブネットが出来る前にクラス別のルーティングしてた時の名残みたいだから、予約している意味も特になさそうだけど、逆に言えば解放する意味合いもなさそう(あったとしたらMoney)

192.0.2.0/24

192.0.2.0/24 - This block is assigned as "TEST-NET" for use in documentation and example code. It is often used in conjunction with domain names example.com or example.net in vendor and protocol documentation. Addresses within this block should not appear on the public Internet.

192.0.2.0/24 - このブロックは文書化と例での使用するコードのために "TEST-NET"として割り当てられます。これはベンダー文書とプロトコル文書 でドメインexample.comあるいはexample.netと関連してしばしば使われま す。このブロック中のアドレスが公共インターネットに現われるべきではあ りません。

192.0.2.0/24はが公共インターネットに現われるべきでは無いらしい。 後述するが、これはドキュメント利用可能アドレスと呼ばれるものであり、要はhogeとかfooとかに意味合いが近い。

192.88.99.0/24

192.88.99.0/24 - This block is allocated for use as 6to4 relay anycast addresses, according to [RFC3068]. 192.88.99.0/24 - [RFC3068]によれば、このブロックは6to4リレーエニ キャストアドレスに使用するために割当てられます。

6to4リレーエニキャストってのだが以下が詳しい。

https://docs.oracle.com/cd/E19253-01/819-0380/ipv6-config-tasks-24/index.html

任意のIPv6アドレス空間AとIPv6アドレス空間Bが遠隔にあり、その間がIPv4アドレス空間が存在した場合、 通常v4とv6アドレスは互いに疎な関係にあり、ルーティングできない。 その場合に使われるトンネリング技術内でISPやIX側で使用されるアドレスであり、通常エンドユーザのIPv4アドレス空間のルーティング対象ではない(それにこの6to4アドレスは脆弱なのでほぼ使われてない https://docs.oracle.com/cd/E19253-01/819-0380/ipv6-ref-58/index.html)。

ちなみにエニーキャストは同じアドレスを複数のホストが所有して「近いホストを優先して」投げる方式のことだ。

ってことで、まあGKE内で振って、内部でこういった擬似6to4リレーをしたい逸般ユーザとかいれば使ってどうぞって事なのかな。

198.18.0.0/15

198.18.0.0/15 - This block has been allocated for use in benchmark tests of network interconnect devices. Its use is documented in [RFC2544].

198.18.0.0/15 - このブロックはネットワーク相互接続装置のベンチマー クテストでの使用に割り当てられます。その使い方は[RFC2544]で文書化 されます。

このRFC2544はネットワーク測定の教科書として非常に有用なので紹介しておきます。

RFC2544 ネットワーク相互接続装置のためのベンチマーク方法論

さて話に戻りますが、このアドレスに関しても予約されている。 ISP側が安心して相互実験出来るようアドレスだろう、技術者としては涙が出る配慮だね。

240.0.0.0/4

240.0.0.0/4 - This block, formerly known as the Class E address space, is reserved. The "limited broadcast" destination address 255.255.255.255 should never be forwarded outside the (sub-)net of the source. The remainder of this space is reserved for future use. [RFC1700, page 4]

240.0.0.0/4 - このブロックは、以前はクラスEアドレス空間として知られ ていて、予約されています。「限定ブロードキャスト」宛先アドレス 255.255.255.255は決してソースの(サブ)ネットの外に転送されるべきで はありません。この空間の残りが未来の使用のために確保されます。 [RFC1700、ページ4]。

クラスEは知っていたが、限定ブロードキャストは知らなかった・・・。

リミテッド・ブロードキャストアドレス (limited broadcast address)とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典 https://wa3.i-3-i.info/word11995.html

そこに送れば、自分が今いるネットワーク内のすべてのコンピュータにお届けしてくれるIPアドレス

・・・?それって255.255.255.255のことじゃなかったか?

ま、

IPアドレス(クラスD・クラスE) TCP/IP入門

クラスEのIPアドレスの範囲は、「240.0.0.0~255.255.255.255」で、このクラスは、実験用としてTCP/IPIPv4)の開発当初から予約されています。実際に使われることはありません。

だから別にGKE上で使ってもいいだろってことだろう。

あまり。

RFC3330には載っていなかった、

  • 100.64.0.0/10
  • 198.51.100.0/24
  • 203.0.113.0/24

を見ていこう。

100.64.0.0/10

www.geekpage.jp

RFC 6598 - IANA-Reserved IPv4 Prefix for Shared Address Space

記事を見る限りは概ね、

ISPがCGNなどで利用するためのISP Shared Address(100.64.0.0/10)がIANAで予約され、ISP Shared AddressのRFC6598が本日発行されました。

感覚としては10.0.0.0/8、172.16.0.0/12、192.168.0.0/16に続いて第4のプライベートIPv4アドレスが誕生したような感覚ではありますが、100.64.0.0/10はサービスプロバイダネットワーク内でのみ利用される点で異なります。

だそうだ。これもISPの実験的な使用に適していそう。

198.51.100.0/24, 203.0.113.0/24

192.0.2.0/24に関してはRFC3330でもあったので紹介したが、これらはその仲間だ。

toe.bbtower.co.jp

この記事が面白い。

技術関連の話題に触れる際には、IPアドレスドメイン名を例示することがよくあります。うっかり実在のIPアドレスドメイン名を使ってしまうと、迷惑をかけてしまうかもしれません。ここでは、ドキュメント用にリザーブされたIPアドレス等、必要になりそうなものをまとめてみました。

[RFC5737] IPv4 Address Blocks Reserved for Documentation

で定義されており、

192.0.2.0/24 (TEST-NET-1) 198.51.100.0/24 (TEST-NET-2) 203.0.113.0/24 (TEST-NET-3) が文書用のIPv4アドレスとしてリザーブされています。

し、知らなかった・・・(_)確かに実在のIPとかでブログ書くとなって時にこの予約IPを使えばわかりやすいかも(よく落ちるグローバルIPとかの模擬を実在のIPで書いちゃうとアレだしな・・・。)

終わりに

概ね全て予約アドレスであるものの、

  • 学術用途
  • テストネット
  • グローバルでもう意味を成してない予約アドレス

が今回の対象でだった。 実験やインターネットを題材とした研究に使いやすいようになのか・・・? 全く何の根拠もない予測と断っておきますが、Googleさんのopenラボとももしかしたら関係があるかも・・・?

Google | CERN openlab

そんな学術的な実験をマルチクラウドでやっていない僕からすると、少なくともgke-1.14ではGKEのpodにこのIPは振れないのであんまり関係ないお話だったかもしれない。

【AWS】【Python】Botoでs3の署名バージョン4でAPI叩くのをソースコード変えるだけでやる

TLDR;

botoを対応バージョンにしてから、

$ pip install -U boto==2.49.0

boto.config で指定してやる。

if not boto.config.get('s3', 'use-sigv4'):
            boto.config.add_section('s3')
            boto.config.set('s3', 'use-sigv4', 'True')

s3 = boto.s3.connect_to_region('ap-northeast-1',is_secure=False)
# Example, Get Bucket
bucket = instance['bucket']
b = s3.get_bucket(bucket, validate=False)

ここが詳しい

stackoverflow.com

s3の署名バージョン?

この件です。

dev.classmethod.jp

余談

botoの設定ファイルを変えることでもできるみたい。

[s3] use-sigv4 = True

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/UsingAWSSDK.html#UsingAWSSDK-move-to-Sig4

botoの設定ファイルの管理をしたくなかったから俺はやらなかった。

【Docker】指定したDockerのイメージを全部消す

TL;DR

docker-rmi-all(){
  if [ -z ${1} ]; then
    echo "Usage: ${0} <docker image name>"
    return 1
  fi
  docker images | grep ${1} | tr -s ' ' | cut -d ' ' -f 2 | xargs -I {} docker rmi ${1}:{}
}

docker image prune?

docker image pruneだと「システムに利用されてないイメージだけ消す」ので、例えば特定のイメージを今あるタグごと全部消したい時に使えない。

【Rust】Twitterのstream apiの現状とRustでの実装について

とある嫁案件が事の発端

嫁からTwitterにてとあるキーワードを検索したいが、いちいち検索するの面倒だからリアルタイムでslackとかに投げてほしいとの依頼を受けた。はっきり言ってこの開発はなんとなく相当面倒な仕様になるのが予想できたし、仕事なら開発から導入までで数十万くらいならやるかなといったところだが、勉強がてらやってみることにした。 

要件と成果物

要件

- リアルタイムでTwitterの検索して、新しいTweetがきたら通知してほしい。

- 実装はえぬなおの自由

成果物

- 勉強がてらピュアRustでスレッドセーフなコード。

- 通知は家族内slackに投げることにした。

- 主なライブラリは https://github.com/tesaguri/twitter-stream-rs(学生さんが作ったものみたい……衝撃)

- Twitter APIからリクエスト失敗した時のエラーハンドリングはドキュメント通りに自前実装

- 新たに金を払いたくないから自宅Kubernetesにデプロイ

とりあえず出来上がった。動きも悪くはない。

asciinema.org

 

このコンソールに映ってる奴が加工されてslackに飛ぶ。

 

f:id:nnao45:20190513004305p:plain

 

github.com

しかしこの開発、俺の嗅覚は間違っていなくて色々面倒だったから苦労話でも以下に書く。

Twitterまわり

TwitterのStreamって廃止されたんじゃないの?

User Streamはな。

mobilelaby.com

 

まずはじめに俺は「そういえばUser Streamって廃止されたよな………?」と思った。しかしよく調べるとどうやらUser StreamってのはTwitter Stream APIの1つであるらしい。

 

Twitterでは以下のStreaming APIが用意されています。

● Public streams
Twitter上で流れる公開データのストリームのこと。特定のユーザやトピックをフォローしたり、データマイニングに適しています。

● User streams
ユーザ1人のストリームのこと。Twitterのシングルユーザビューで参照できるほぼ全てのデータを網羅する。

● Sites streams
ユーザストリームのマルチユーザ版。サイトストリームは、多くのユーザに代わってTwitterに接続しなければならないサーバを対象としている。

 

Twitter、「User Streams API」を8月17日に廃止。サードアプリはタイムライン取得が制限へ

 

別に今回は俺や嫁のアカウントじゃなくてもいいキーワードであったため、Public Streamを使うのでこの件とは何も関係なかった。知らなかった……。

Twitter APIを叩くのに開発者用のアカウントを作らなきゃいけない。

色々なサイトを見るに、Twitter APIを叩くにはユーザートークンを発行する必要があるらしい。developer.twitter.comで作れる。

 

以下が詳しいが、

qiita.com

 

これが結構大変で、300文字以上の開発者用の機能を使う目的や、アプリを使うためのドメインとか(localhostとかアドレスじゃだめ)、初心者だとこれだけでも心が折れる。幸いにも俺は調子が良かったので頑張って登録したが、Rustで非同期プログラミングの実装とかやっている途中だったら危なかったかもしれない。 

Rustまわり

使ったライブラリについて

今回は以下のライブラリを使ったと前述した、

 

github.com

 

基本的には以下のようなコードでツイートをストリーミングとして受け取ることができる。

 

gist.github.com

https://github.com/tesaguri/twitter-stream-rs/blob/master/examples/echo_bot.rs

変数TRACKに検索したいキーワードを代入して、かかったのを引っ掛けてツイートを受け取れる。

関数としては変数streamが非同期メッセージでjsonシリアライズなバイトデータを取ってくるので、それをmoveクロージャに代入して処理をつくり、それら処理をTokioライブラリによって実装する形となる。

github.com

 

Rust有識者なら分かると思うが、受け取ったデータに対して標準出力するなりどっかに渡すなりする動作は変数bot内のクロージャに閉じ込められているので相当に扱いが面倒なのは覚悟しておいたほうがいい。

 

まぁメッセージングってのは得てしてそんなもんだ。

 

型としてはtweetustという有名なrustでTwitter APIを扱えるようにしたライブラリのtweet型で返ってくる。

github.com

 

詳しく書くと、まるごと必要な情報はストリーミングからfor_eachメソッドで取り出した jsonをサンプルコードの通りだと Ok(StreamMessage::Tweet(tweet)) = json::from_str(&json) のようにtwitter-stream-rsと同リポジトリtwitter-stream-messageライブラリを使用する事で受け取る事ができる。 逆に言えばこのライブラリを使わないとjsonからデシリアライズされた生文字列から値を頑張って取り出す必要があり、使わない手はないと思われる。

420コード

さてとそれでそれならばとサンプル通り普通に使ったところ、少しほっておくといつの間に終了するアプリが出来てしまった。そんな甘くないらしい。

エラーコードを見てみると、420っていうエラーコードが返っているではないか……知らね……。

公式ドキュメントを見て見る限りは、

Response Codes — Twitter Developers

Returned when an app is being rate limited for making too many requests.

は………w

俺はこの時点ではこのapiを2スレッドで叩いていただけなんだが、それでもう制限にぶち当たるらしい。きっつー。

[追記:2019年 5月15日 水曜日 10時44分02秒 JST]

どうやらtwitterのstream apiは2本クライアントたててやると速度制限に引っかかるらしい・・・1本でやれば早々止まらない。

また、検索したい言葉をAND検索やOR検索したときは、ANDは「えぬなお Rust」、ORなら「えぬなお, Rust」のようにtrackに文字列を渡せば上手くいく。これを駆使してtwitter1アカウントにつき1ストリームを意識するといい。

westplain.sakuraweb.com

 

420コードへの対処

ほぼこのエラーコードなので、どうにかする事に。

幸いにも日本語情報を見つけた。

qiita.com

twitterのドキュメントを調べてみると420が返ってきた場合(と他のエラーの場合)の再接続のベストプラクティスが示されているのでそれに従う。またstreamも一度にひとつだけ開くようにする。
Back off exponentially for HTTP 420 errors. Start with a 1 minute wait and double each attempt. Note that every HTTP 420 received increases the time you must wait until rate limiting will no longer will be in effect for your account.
参考: https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/connecting
HTTP 420 errorの場合インターバルを置き再々接続までの時間を最大値まで指数関数的に増やすようにと指示されている。(例:60秒,120秒,240秒,…3600秒)
node-twitterでは実装の例などは無いので自分で実装する必要があるが、issue/159にコードの例があり参考にできる。 https://github.com/desmondmorris/node-twitter/issues/159

 ちゅーことで、fluentdのretry_waitみたいな感じで指数関数的にretryするような仕組みを作る必要があるわけだ。

420コードへの対策

グローバルにstatic変数をおくとライフタイムや所有権管理が面倒すぎるし、まぁ鉄板なとこでクロージャ内にカウンタを埋め込んで管理する事にする。

gist.github.com

ライブラリの方のサンプルコードにはないloop構文だが、実際これを置いておかないと、stream apiからエラーが帰ってきてstreamから離脱させられた時にそのままプロセスごと落ちてしまう事となる。

速度制限のエラーコード程度でプロセスを再起動してたら夜も眠れないので、前述したベストプラクティスのように連続してエラーで帰ってきたら60s、120sと再度stream apiに聞きに行くまでのインターバルを伸ばしながら立ち上げるようにした。

以下が参考になったというかそのまま使用させて頂いた。

saba1024.hateblo.jp

クロージャをこういう感じでグローバル変数代わりに使うときはBoxとmoveクロージャを使って、クロージャに所有権を渡して、加えてクロージャ経由以外からカウンタに当たる変数が更新できないよう封じ込めると上手くいく。

Dockerfile

今回はコンテナでデプロイした。

gist.github.com

マルチステージビルドにしたが、動かす用のコンテナをalpineにしたら動かなくてよくわからなかったので、rust-slimイメージと同じdebian-slimを採用した。イメージは40MBくらいになった・・・軽すぎて草。

別に大したことしてないので、これについては特に説明することもない。

[追記:2019年 5月15日 水曜日 10時44分02秒 JST]

cargo build長すぎたので少し変えた。

以下が詳しい。

RustのCargoプロジェクトで素直に書いたDockerfileをdocker buildするとソースが書き換わるたびにフルビルドが走って滅茶苦茶遅いことはcargoのファイルだけコピーしてビルドすることで解決します - ncaq

終わりに

Rustの非同期処理、async/awaitはよstableになってくれい。

【zsh】ZshrcやZshのプラグインの開発環境のdockerイメージを作った。

Zshrcの開発環境・・・?

zshrcやzshプラグインの開発環境として、dockerイメージを作成した。

github.com

hub.docker.com

まぁなんで作ったかって言ったらなかったからだけど。

Dockerfile

# 1st stage, for development container
FROM centos:7.6.1810 as builder

# Install package for development
RUN yum -y groupinstall 'Development tools' && \
    yum -y install git latex2html texlive-epsf ncurses-devel

# build with icmake
RUN mkdir /src
COPY icmake /src/icmake
WORKDIR /src/icmake/icmake
RUN ./icm_bootstrap / && \
    ./icm_install strip all /

# build with yodl
COPY yodl-3.03.0 /src/yodl-3.03.0
WORKDIR /src/yodl-3.03.0
RUN ./build programs && \
    ./build man && \
    ./build macros && \
    ./build manual && \
    ./build install programs / && \
    ./build install man / && \
    ./build install macros / && \
    ./build install manual / && \
    ./build install docs /

# build for zsh
RUN git clone git://git.code.sf.net/p/zsh/code /src/zsh
WORKDIR /src/zsh
RUN ./Util/preconfig && \
    ./configure --prefix=/usr/local --enable-locale --enable-multibyte -with-tcsetpgrp && \
    make clean && \
    make -j 4

# make install with porg
COPY porg-0.10 /src/porg-0.10
WORKDIR /src/porg-0.10
RUN ./configure --prefix=/usr/local --disable-grop && \
    make && \
    make install
WORKDIR /src/zsh
RUN porg -lD make install

# 2nd stage, for runnning container
FROM centos:7.6.1810
RUN yum -y install man
ADD .zshrc /root
COPY --from=builder /usr/local/bin/zsh /usr/local/bin/zsh
COPY --from=builder /usr/local/lib/zsh /usr/local/lib/zsh
COPY --from=builder /usr/local/share/zsh /usr/local/share/zsh
COPY --from=builder /usr/local/share/man/man1 /usr/local/share/man/man1
WORKDIR /root/
CMD ["/usr/local/bin/zsh"]

https://github.com/nnao45/docker-zsh-lab/blob/master/Dockerfile

まぁ2ndステージでzshrcを追加してるんで、手元でzshrcある場合はレポジトリごと持ってきてdockerbuildすればいいと思います。 任意のzshのバージョンを使いたいときはgitで引っ張ってくる時にハッシュを指定すればいいんじゃないですかね。 キャッシュされてなかった結構ビルド長いけど。

用途によっては、/root/に.zshrcをdocker volumeとしてマウントするともっと簡単に使えるかもね。