読者です 読者をやめる 読者になる 読者になる

s_tajima:TechBlog

渋谷で働くインフラエンジニアのTechブログです。

GitHubのPull Requestへのレビュアーアサインを自動化するツール 「github-dice」の紹介

こんにちは、今回はGitHubのPull Requestへのアサインを自動化するツール
github-dice」を作成したお話です。

背景

GitHubのPull Requestベースの開発をする際、コードレビューはとても大事な工程の一つです。

よくあるコードレビューは、 技術力の高い/システムの理解度が深い/ベテラン のメンバーが、
技術力の低い/システムの理解度の浅い/若手 のメンバーが書いたコードをレビューするようなやり方かなと思います。

しかしコードレビューは、レビューを受ける側(レビュイー)にとってとても良い成長の機会ですが、
同時にレビューをする側(レビュアー)にとっても良い成長の機会となります。 自分より実力の高い人の書いたコードをレビューすることで、
新しい知見を得て、技術力の向上に繋げることができます。
システムの理解の深い人間が増えることは、システムの属人性を減らすことにも繋がるでしょう。

そこで、僕の所属しているチームでは、技術力/システムの理解度/在籍期間等に関係なく、
チームのメンバーが均等にレビュアーの機会を経験できるような運用を行っています。

github-dice

github.com

github-diceは、GitHubのPull Requestへのレビュアーのアサインを自動化するためのツールです。
あらかじめ指定したTeamのメンバーから、サイコロを振るようにランダムにレビュアーを選出しアサインします。

導入方法、使い方はREADMEに記載の通りです。
Golang製のツールなのでバイナリを配置するだけで使用できます。
github-diceを使用すると、以下のようにコメントと共にレビュアーがアサインされます。
(オプションで起票者を同時にアサインすることもできます。)

f:id:s_tajima:20161217181128p:plain

尚、-q, --query オプションで検索クエリを書き換えられるので、
Pull Requestだけでなく、Issueを自動アサインの対象にすることも可能です。

終わりに

技術的知見があまりなく、業務用件の理解も浅いコードをいきなりレビューするのはそれなりに負担が高いです。
「チームのメンバーが均等にレビュアーの機会を経験できるように」という方針だけ立てて
あとは各メンバーの自主性に任せるというやり方では結局うまく回らなそうだと考え、 ランダムにレビュアーを選出し機械的にアサインをするというアプローチをとりました。

自信のないときにはしっかり他のメンバーのサポートも受けられる体制も合わせた運用をすることで、
過度に心理的, 技術的な負荷のかかるレビューをしなければいけない状況を避けられるようにしています。 (僕も大いに助けられています。)

実際に半年ほど運用を続けていますが、 自分が直接は触らないようなコードの理解も深まり、
最初は大きかった負担もだんだん小さくなっていくのを感じています。

以上、簡単ですがgithub-diceの紹介でした。
ぜひ使ってみてください。Pull Requestもお待ちしております。

余談

先日、GitHubはPull Requestに対してAssigneesとは別にReviewersを割り当てられるようになりました。
https://github.com/blog/2291-introducing-review-requests

ちょうどこの記事を書いているタイミングで、このReviewersに対するAPIもEarly Accessが開始されました。
https://developer.github.com/changes/2016-12-16-review-requests-api/

github-diceも近いうちにこの機能に対応しようと考えています。

AWS Reserved Instancesによる減額効果を計算するツールを作った

概要

AWSの料金を節約する手段の1つに Reserved Instances という仕組みがあります。
これは、予め将来的に利用するであろうインスタンスを予約し、一定額の前払金を支払う(支払わないケースも有る)ことで、
通常の利用(Ondemand Price)に比べて一定の割引を受けた料金(Blended Rate)でインスタンスを利用できるようにするというものです。

今回は、そのReserved Instanceによって、
インスタンスの利用料金がどれだけ減額されたのかを確認するツールを作成したので紹介します。

aws-ri-discount-calculator

GitHub - s-tajima/aws-ri-discount-calculator: Reserved Instance discount calcurator

使い方はREADMEを参照。

  • IAMアカウントの権限は必要ありません。
  • AWSの提供するMonthly Reportからインスタンスの利用時間, Blended Rateを取得。
  • AWS Price List APIからOndemand Priceを取得。
  • Ondemand PriceとBlended Rateの差額にインスタンスの利用時間を掛けて、
    どれくらいの減額を受けているのかを計算します。
    • Reserved Instanceの購入費用(前払金)は計算に含まれません。
    • Reserved Instanceの購入によるコスト削減効果を計算する場合にはそれを含めて考慮する必要があります。
    • そこまで含めた数字を出せるようになるとよさそうですが、うまいやり方を検討中です。
  • Consolidated Billingを組んでいる場合は、子AWSアカウント毎に集計します。
  • Monthly ReportのCSVの詳細な仕様が公開されていないため、現時点では動作確認できているデータが限定的で、
    場合によっては間違った数値を出してしまったり、動作しない可能性があります。
    • Reserved InstanceのオファリングタイプはEC2のAll Upfront, RDSのHeavy Utilizationのみテスト済み。
  • 実は AWS Cost and Usage Reports で近しい情報は閲覧できるのですが、
    以下のような不都合があったので自作することにしました。
    • EC2以外の情報は確認できない。
    • Consolidated Billing環境で複数のAWSアカウントでReserved Instanceを購入している場合に、
      その効果を合算した数値を見ることができない。

以上、aws-ri-discount-calculatorの紹介でした。 PRもお待ちしております。

resticによるカジュアルなバックアップ環境の構築

前置き

僕がインフラを管理している環境では、その規模の大小は様々ですが14個のJenkinsが稼働しています。
それぞれのJenkinsでは、thinBackupによるバックアップが動いているので、障害時にも復旧ができる...はずでした。

現状の設定を確認してみると、

  • thinBackupによるバックアップが正しく動いている: 4環境
  • thinBackupは動いているが、正しく設定されていない(保持世代数が設定されていない等): 2環境
  • thinBackupの設定がされていない: 8環境

という状態でした。

運用の整理されていない状態で利用するチームが増えたため、
適切に設定の自動化がされておらず、手作業によるバックアップの設定をするのが常態化してしまっていたのが原因です。
このような状態は、全く認識してなかったわけではなく、 いわゆる "Elephant in the room." となっていたのですが、
今回はこの問題に着手したお話です。

適切な手段の検討

Jenkinsの環境構築自体(RPMのインストール, データディレクトリの作成等)は、Puppetによるプロビジョニングができるようになっています。
バックアップの自動設定を実現する上でまず考えられるのは、
このPuppetによるプロビジョニングに プラグインのインストール プラグインの設定 を含めてしまうという方法です。
しかし、以下のような観点からこの方法をとるのはやめました。

  • 現在稼働しているJenkinsのバージョンが多岐に渡る
    • 全環境で(それは現時点だけでなく新しいバージョンのJenkinsが導入された時にも)問題なく動くthinBackupのバージョン/設定を選定するのはなかなか大変そうです。
    • JenkinsのバージョンごとにthinBackupの設定を作るのもよいですが、それはそれで管理が大変なのであまり好ましくありません。
  • 実はthinBackupだけではバックアップとして不完全である。
    • thinBackup導入時は、Jenkinsが稼働しているのはAWS環境のみでした。
    • AWS環境は、thinBackupの他に日次でEBSのスナップショットを自動取得する構成になってるため、これらを合わせて十分なバックアップとなっているという想定でした。
    • しかし現状、Jenkinsはオンプレミス環境でも稼働しています。オンプレミス環境では、thinBackupのバックアップを、別ホスト等に移しておく必要があります。

ということで、なにかよさそうな別の方法を検討することにしたのですが、一旦やりたいことを整理してみました。

  • Jenkinsにおいてデータ破損等のトラブルが発生した時、その設定/プラグイン/ビルドの履歴等が復元できる状態にしておく必要がある。
  • バックアップデータは、Jenkins稼働ホストに置いておくだけではなく、ある程度信用のできる外部ストレージに転送する必要がある。
  • それなりにデータサイズの大きいJenkinsにも対応できるように、毎回フルバックアップするのではなく、差分バックアップができるとよい。
  • Jenkinsの設定には機微な情報が含まれるケースもあるので、適切な方法で暗号化できる手段があるとよい。
  • 復元のタイミングは任意である必要はなく、「前日のXX時」程度のレベルでよい。
  • いわゆるバックアップの一貫性もそこまで強く求められるものではない。
  • バックアップの自動設定をしたいので、Puppetによるプロビジョニングがしやすい構成になるとよい。

イメージとしてはあるバイナリ1つと設定ファイル1つ程度で、

#バックアップ
$JENKINS_HOMEをtarで固めて -> (必要であれば)適切な方法で圧縮/暗号化して -> S3に転送

#リストア
S3からファイルを取得 -> (必要であれば)圧縮/暗号化を解除 -> $JENKINS_HOMEに展開

くらいのことができると良さそうだなという印象です。

resticによるバックアップ

前述したような要件を持つだけのプログラムであれば、自作しても大した手間ではないのですが、
おそらく世の中には同じような要件はいくらでもあるだろうということで探してみたところこんなものがありました。

https://github.com/restic/restic

  • Golang製のバックアップツール (≒ビルドしたバイナリをpuppetで配るだけでよい。)
  • バックアップをS3に転送することができる
  • 差分バックアップに対応
  • バックアップの暗号化に対応

あたりが選定の要因です。

使い方のイメージはドキュメントを読んでもらうと良いと思います。
https://restic.readthedocs.org/en/v0.1.0-doc/Manual/#buildinginstalling-restic

restic init, restic backupを実行するための、簡単なシェルスクリプトを作成し、
定期的に実行することで、Jenkinsのカジュアルなバックアップを実現しました。

mod_extract_forwarded, mod_rpafの脆弱性らしきものを見つけた話

はじめに

本記事で言及しているmod_rpafというモジュールは、forkによる派生などによって幾つかの実装があるようです。
きちんと区別できるように、今回は以下の実装をそれぞれ分けて表記することにします。
(ここに挙げた以外にもいくつかの実装をみつけることはできました。)

GitHub - gnif/mod_rpaf: reverse proxy add forward module for Apache => mod_rpaf(gnif版)
GitHub - ttkzw/mod_rpaf-0.6 => mod_rpaf(ttkzw版)

※2016/02/19追記
@ttkzwさんご本人から直接連絡をいただきました。
修正していただけたようです。
ご対応ありがとうございます!

Bugfix: wrong remote_addr is set. · ttkzw/mod_rpaf-0.6@345365c · GitHub

概要

mod_extract_forwarded, mod_rpaf(ttkzw版)を導入している環境において、
悪意のあるリクエストによってリモートホスト(接続元のホスト)の 先頭に 任意の文字列が追加 されてしまうという不具合の話です。

尚、httpd2.4に組み込まれている、mod_remoteipについては影響を受けないことを確認しました。

※ mod_extract_forwarded, mod_rpafについては mod_remoteipのリモートアドレスの扱いに関する不具合の修正について - s_tajima:TechBlog で解説しているので割愛します。

脆弱性の解説

mod_extract_forwarded, mod_rpaf(ttkzw版)を導入していて、通常のリクエスト処理フローにX-Forwarded-Forヘッダが使われるような環境を想定します。

わかりやすくするために、今回は以下のようなケースとします。

Client(192.0.2.1) -- global network -->  ReverseProxy(10.0.0.1) -- local network --> WebServer(10.1.0.1)
※Webサーバーには信頼するProxyとして10.0.0.0/8を指定

通常のリクエスト

WebServerには以下のようなX-Forwarded-Forヘッダが届きます。

X-Forwarded-For: 192.0.2.1, 10.0.0.1

その結果、リモートホストには以下の文字列が記録されます。

192.0.2.1

脆弱性をついた攻撃のリクエスト

このような環境にたいして、Clientから、"(ダブルクォート) を含む X-Forwarded-Forヘッダを送信します。
curlコマンドで表すと以下のようなリクエストになります。

curl -H 'X-Forwarded-For: SOMEEVILSTRINGS"' http://example.com

するとWebServerには、以下のようなX-Forwarded-Forヘッダが届きます。

X-Forwarded-For: SOMEEVILSTRINGS", 192.0.2.1, 10.0.0.1

すると不本意なことに、リモートホストとして以下の文字列が記録されます。

SOMEEVILSTRINGS", 192.0.2.1

考えられそうな影響

アクセス制限の回避

正規表現を使ってIPアドレスベースによるアクセス制限をかけているが、
その正規表現が適切でない場合にその制限を回避される懸念があります。

※PHPでの例です。  
if (preg_match('/^10\./', $_SERVER['REMOTE_ADDR'])){  
    # ローカルネットワークからのみリクエストを受け付ける想定の処理。  
}

このケースでは SOMEEVILSTRINGS10.1.1.1 等とすることで制限を回避できてしまいます。

XSS, SQLインジェクション

リモートホストに入ってくる文字列がIPアドレス以外になることを想定せず、適切なエスケープをしていないと、
XSSSQLインジェクションを実行される可能性があります。

なぜこのようなことが起こるのか

mod_extract_forwarded, mod_rpaf(ttkzw版)では、X-Forwarded-Forヘッダのパースにapacheのコアに含まれるap_get_token関数を使用しています。

ap_get_token関数は、カンマ区切り等で複数の値を取りうるヘッダの値を分割文字列毎に分割するための関数ですが、
ヘッダ文字列内に "(ダブルクォート) が含まれるとそこから文字列の終端までを1つの塊として処理してしまいます。

更に、ap_get_tokeによって返された値を、IPアドレスとして正しい文字列であるかを判定せずに、
そのままリモートホストとして扱ってしまうために、今回のようなことが起こります。

参考:

対応

mod_rpaf(gnif版)の 2014/03/29 のこのコミット Refactor IP subnet comparison using apr funcs · gnif/mod_rpaf@8419240 · GitHub 以降であれば、
X-Forwarded-Forヘッダのパースが自前の実装になっていて、
この問題は発生しないようなので、そちらに乗り換えるというのは1つの手段です。
また、rpaf_looks_like_ip関数によってIPアドレスとして正しいフォーマットであるかのチェックもしてくれています。
https://github.com/gnif/mod_rpaf/blob/stable/mod_rpaf.c#L293-L301

mod_extract_forwarded, mod_rpaf(ttkzw版)は共に長い間メンテナンスがされていないようなので、
何らかの理由で乗り換えが難しいケースでは、適切なパッチを自前であてる必要がありそうです。

以上、mod_extract_forwarded, mod_rpafの脆弱性らしきものを見つけた話でした。

AWS Certificate Managerの検証

AWS

本日、AWSSSL/TLS証明書のマネージドサービスをリリースしたので早速使ってみました。
New – AWS Certificate Manager – Deploy SSL/TLS-Based Apps on AWS | AWS Official Blog

リリース情報、ドキュメントより

ACM takes care of the complexity surrounding the provisioning, deployment, and renewal of digital certificates

証明書のプロビジョニング, デプロイ, 更新を管理してくれるとのこと。

Certificates provided by ACM are verified by Amazon’s certificate authority (CA)

AmazonのCAによってベリファイされた証明書を発行できるとのこと。

SSL/TLS certificates provisioned through AWS Certificate Manager are free.

証明書の発行/管理自体に費用はかからないそうです。

You can use AWS Certificate Manager certificates only with Elastic Load Balancing and Amazon CloudFront.

ELBもしくはCloudFrontでしか使えないとのこと。証明書の秘密鍵をダウンロードすることはできないようですね。

発行してみた

  • 詳細な手順はドキュメントに書いてあるので割愛。
  • ドメイン認証の方法は現状メールのみ。(Whoisの管理者アドレスに認証用メールが送られてくる)
  • 発行した証明書の主な仕様
    • Signature Algorithmはsha256WithRSAEncryption
    • 秘密鍵の鍵長は2048bit
    • FQDN複数指定した場合はSubject Alternative Name (SANs)に含まれる
  • 実際に発行した証明書はこちら。
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            0a:33:56:0e:38:84:ab:1d:23:bb:7f:fa:62:e9:25:99
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, O=Amazon, OU=Server CA 1B, CN=Amazon
        Validity
            Not Before: Jan 22 00:00:00 2016 GMT
            Not After : Feb 22 12:00:00 2017 GMT
        Subject: CN=XXXXX.me
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
            RSA Public Key: (2048 bit)
                Modulus (2048 bit):
                    00:86:6a:b3:f3:ce:38:6d:26:31:d1:94:cc:c3:62:
                    ..snip..
                    d4:e6:00:4f:28:43:1c:5a:4a:40:80:6a:3f:bd:14:
                    38:95
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Authority Key Identifier:
                keyid:59:A4:66:06:52:A0:7B:95:92:3C:A3:94:07:27:96:74:5B:F9:3D:D0

            X509v3 Subject Key Identifier:
                6C:00:94:64:9B:FD:6A:06:1D:B4:55:FC:BD:4A:19:08:D4:C6:38:39
            X509v3 Subject Alternative Name:
                DNS:XXXXX.me, DNS:*.XXXXX.me
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 CRL Distribution Points:
                URI:http://crl.sca1b.amazontrust.com/sca1b.crl

            X509v3 Certificate Policies:
                Policy: 2.23.140.1.2.1

            Authority Information Access:
                OCSP - URI:http://ocsp.sca1b.amazontrust.com
                CA Issuers - URI:http://crt.sca1b.amazontrust.com/sca1b.crt

            X509v3 Basic Constraints: critical
                CA:FALSE
    Signature Algorithm: sha256WithRSAEncryption
        99:ec:47:81:fc:61:55:aa:c7:91:aa:d2:d5:2d:29:68:cf:1c:
        ..snip..
        43:6d:d2:6f
-----BEGIN CERTIFICATE-----
MIIEWTCCA0GgAwIBAgIQCjNWDjiEqx0ju3/6YuklmTANBgkqhkiG9w0BAQsFADBG
..snip..
QifBNlq9ofPYQ23Sbw==
-----END CERTIFICATE-----

有効性の確認

証明書のチェーンは以下のとおり

Certificate chain
 0 s:/CN=XXXXX.me
   i:/C=US/O=Amazon/OU=Server CA 1B/CN=Amazon
 1 s:/C=US/O=Amazon/OU=Server CA 1B/CN=Amazon
   i:/C=US/O=Amazon/CN=Amazon Root CA 1
 2 s:/C=US/O=Amazon/CN=Amazon Root CA 1
   i:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2
 3 s:/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./CN=Starfield Services Root Certificate Authority - G2
   i:/C=US/O=Starfield Technologies, Inc./OU=Starfield Class 2 Certification Authority

手元の環境で確認すると、Firefox, OSXのキーチェーン共に有効なCA認証局からのチェーンとして扱われていた。

  • FireFox (43.0.4)
    f:id:s_tajima:20160122124532p:plain

  • OS X (10.9.5) ※古い環境でごめんなさい...
    f:id:s_tajima:20160122124540p:plain

リリースの記事にもある通り、SSL/TLS証明書の管理はたしかに面倒ですよね。
最近ではLet's Encrypt(https://letsencrypt.org/)等も話題になっていますが、
これらをうまく使って余計な手間をなくしていきたいものです。

※2016/1/22 17:12 追記
Amazonみたいな1企業が(中間)CA認証局を持てるってすごいなとおもってたら2015年6月にこんな記事が出てた。
Amazon wants to be your SSL certificate provider, applies to be a root Certificate Authority - GeekWire

mod_remoteipのリモートアドレスの扱いに関する不具合の修正について

httpd2.4では、X-Forwarded-Forを取り扱うためにmod_remoteipというモジュールがあります。
httpd2.2系でいうところのmod_rpaf, mod_extract_forwardedの代わりとなるモジュールです。
今回は、このmod_remoteipの不具合が修正された話です。

前置き

(mod_remoteip, X-Forwarded-Forについての説明です。知ってる方は読み飛ばしてください。)

これらのモジュールは、主にロードバランサ, リバースプロキシ配下のWebサーバで使われ、
リクエストのリモートアドレスを、X-Forwarded-Forというヘッダの値を元に書き換えてくれます。
(これをしないとWebサーバに残るリモートアドレスは、ロードバランサやリバースプロキシのものになってしまう。)

複数のロードバランサやリバースプロキシを経由してくることを考慮して、
X-Forwarded-Forヘッダには ,(カンマ)区切りで複数IPアドレスを記述できるようになっています。

X-Forwarded-Forヘッダに複数の値が入っている場合に、
Webサーバでリモートアドレスとして使われるのは基本的には一番右側の(最後に付与された)IPアドレスになります。
ただし、それだけでは多くのケースでロードバランサやリバースプロキシのIPアドレスが記録されてしまいます。

そのため、これらのモジュールの多くは、
リモートアドレスとして採用しないIPアドレス(のレンジ)を指定することができるようになっています。

例えば、mod_remoteipの設定として、以下の記述がされているケースで、

RemoteIPInternalProxy 192.0.2.0/24

以下のようなリクエストを受信したケースでは、

X-Forwarded-For: 198.51.100.1, 198.51.100.2, 192.0.2.1

198.51.100.2 がリモートアドレスとして記録されます。

この方法でリモートアドレスを取得すれば、
最後に経由したRemoteIPInternalProxyに記述されていないアドレス(≒ 自分たちの管理化にないアドレス)
をリモートアドレスとして記録することができるようになります。

不具合の内容

前置きが長くなりましたが、今回修正されたのはこのリモートアドレスの算出方法の不具合です。
この不具合が修正されるまで、mod_remoteipではRemoteIPInternalProxy, RemoteIPTrustedProxyの設定にかかわらず
一番 左側IPアドレスがリモートアドレスとして記録される状態になっていました。

不具合による影響

X-Forwarded-Forは単なるHTTPのヘッダなので、悪意のあるクライアントは簡単に偽装して付与することができます。

以下のようにX-Forwarded-Forを付与した状態でリクエストをかけます。

X-Forwarded-For: 192.0.2.2

すると、Webサーバには以下のようなX-Forwarded-Forヘッダが届きます。

X-Forwarded-For: 192.0.2.2, 198.51.100.1, 198.51.100.2, 192.0.2.1

今回の不具合では、一番 左側IPアドレスがリモートアドレスとなるので、
192.0.2.2 がリモートアドレスとなります。

結果として、ログに残るリモートアドレスを偽装したり、
リモートアドレスを元にアクセス制限をかけている環境等でアクセスの制限を回避したりすることが可能になります。

不具合の修正

この不具合、Red Hat Bugzillaに 2015/01/06 に報告されたあとしばらく修正されずにいたのですが、
2015/11/18 にようやくRedHatで修正がリリースされました。
https://bugzilla.redhat.com/show_bug.cgi?id=1179306

RedHatのAdvisoryはこちら。
https://rhn.redhat.com/errata/RHBA-2015-2194.html

そして、この修正がCentOSにも降りてくるのを待っていたのですが、
先日リリースされた CentOS Linux 7 (1511) でようやくこの修正が取り込まれました。
該当する環境をお使いの方は早いうちにアップデートをすることをおすすめします。

以上、今回はmod_remoteipの不具合についてでした。

fluent-plugin-bigqueryでログの書き込みが痕跡なく欠損するケースがある問題

fluent-plugin-bigqueryを使ってBigQueryにStreaming Insertでログを書き込む時に、
痕跡なくログが欠損するケースがあるのでは? という話です。

fluent-plugin-bigqueryでのログの書き込み処理/エラー処理はこのようになっています。
res.success? がtrueであればエラーはなく書き込みが成功しているという想定。
falseの時にはレスポンスのjsonerrorエラーの中身を見て、ログを吐くなどのエラー処理をするようです。

res = client().execute(
  api_method: @bq.tabledata.insert_all,
  parameters: {
    'projectId' => @project,
    'datasetId' => @dataset,
    'tableId' => table_id,
  },
  body_object: {
    "rows" => rows
  }
)
unless res.success?
  # api_error? -> client cache clear
  @cached_client = nil

  res_obj = extract_response_obj(res.body)
  message = res_obj['error']['message'] || res.body
  if res_obj
    if @auto_create_table and res_obj and res_obj['error']['code'] == 404 and /Not Found: Table/i =~ message.to_s
      # Table Not Found: Auto Create Table
      create_table(table_id)
      raise "table created. send rows next time."
    end
  end
  log.error "tabledata.insertAll API", project_id: @project, dataset: @dataset, table: table_id, code: res.status, message: message
  raise "failed to insert into bigquery" # TODO: error class
end

https://github.com/kaizenplatform/fluent-plugin-bigquery/blob/master/lib/fluent/plugin/out_bigquery.rb#L323-L349

ではres.success?とはどのような処理かというとこちら。
BigQueryのAPIのレスポンスコードをみて400系以上でなければtrueが返る仕様のようです。

##
# Check if request failed
#
# @!attribute [r] error?
# @return [TrueClass, FalseClass]
#   true if result of operation is an error
def error?
  return self.response.status >= 400
end

##
# Check if request was successful
#
# @!attribute [r] success?
# @return [TrueClass, FalseClass]
#   true if result of operation was successful
def success?
  return !self.error?
end

https://github.com/google/google-api-ruby-client/blob/v0.8.3/lib/google/api_client/result.rb#L81-L99

BigQueryの挙動を検証していると、レスポンスコードが20Xであるにもかかわらず、
レスポンスのjsonに、特定の行でエラーが含まれることがありました。
そのようなケースでは、fluent-plugin-bigqueryではログが欠損してしまいます。

(この問題だけでなく他のいろいろな経緯から、) 実運用ではfluent-plugin-bigqueryを使うのをやめ、
自前でワーカーを作り以下のようなエラー処理をして運用をはじめました。
エラーになった場合にはエラーの内容を見てリトライをかけるようにしています。

# @gclient = Google::APIClient.new( .. )
# @bq_api = @gclient.discovered_api("bigquery", "v2")
# result = @gclient.execute( :api_method => @bq_api.tabledata.insert_all .. )

def check_result(result, data)
  unless result.success?
    @logger.warn "Insert failed partically. (API call)"
    JSON.parse(result.body)["error"]["errors"].each do |error|
      @logger.warn "Error is #{error["message"]}"
    end
    @retryable = true
    return false
  end

  errors = check_result_errors(result.body)
  unless errors.nil?
    @logger.warn "Insert failed partically. (some rows)"
    @logger.warn "Error is #{errors}"
    @logger.warn "Error data #{data[0]}"
    @retryable = retryable?(errors)
    return false
  end

  @logger.debug "Insert successes. (rows:#{data.size})"
  return true
end

def check_result_errors(body)
  body = JSON.parse(body)
  return nil if body["insertErrors"].nil?

  errors = []
  body["insertErrors"].each do |error|
    errors += error["errors"].inject([]){|x, e| x << e["reason"]}
  end
  return errors.uniq
end

def retryable?(errors)
  return false if errors.include?("invalid")
  true
end

と、ここまでが去年くらいの話。

すると、今日TwitterのタイムラインでBigQueryのログの欠損の話があがっていたのを見かけてこの話を思い出しました。
しばらく間があいたので、状況が変わってるかなと思いしらべてみたけど改善されてはいない模様。

ちなみに、簡単に該当状況が起こる再現コードを書くとこんな感じ。

project_id     = "PROJECT_ID"
dataset_id     = "test"
table_id       = "test"

# schema col1:STRING, col2:INTEGER
rows = [
    {"json"=> {"col1" => "string", "col2" => 1}},
    {"json"=> {"col2" => "string", "col2" =>"string"}},
]

# @gclient = Google::APIClient.new( .. )
# @bq_api = @gclient.discovered_api("bigquery", "v2")
# result = @gclient.execute( :api_method => @bq_api.tabledata.insert_all .. )
result = @gclient.execute(
  :api_method => $bq_api.tabledata.insert_all,
  :parameters => {
    'projectId' => project_id,
    'datasetId' => dataset_id,
    'tableId' => table_id,
  },
  :body_object => {
    "rows" => rows
  }
)

puts result.success?
# => true

puts result.body
# =>
# {
#  "kind": "bigquery#tableDataInsertAllResponse",
#  "insertErrors": [
#   {
#    "index": 1,
#    "errors": [
#     {
#      "reason": "invalid",
#      "location": "Field:col2",
#      "message": "Could not convert value to integer (bad value or out of range)."
#     }
#    ]
#   },
#   {
#    "index": 0,
#    "errors": [
#     {
#      "reason": "stopped"
#     }
#    ]
#   }
#  ]
# }

正直fluent-plugin-bigqueryの問題と言うよりは、
google-api-client もしくは BigQueryのAPIの仕様の問題という気がするので、プラグイン側にIssueを立てたりするわけでもないんですが、
痕跡なくログの欠損がおきてしまうとまずいという環境で使っている方は注意したほうがいいかなと思います。

追記(2015/12/02 18:40): 案の定すでにPRがあった。 https://github.com/kaizenplatform/fluent-plugin-bigquery/pull/38