リモート開発メインのソフトウェア開発企業のエンジニアブログです

Ansibleのs3_syncモジュールのfile_change_strategy: date_sizeのバグと対処法

対処法については一番最後に書いてありますので、面倒な方は読み飛ばして下さい: 対処法

このバグは当エントリ執筆時点の2019年3月22日現在確認ができています。また、現段階でAnsible本体にIssuePull Requestを開いていますが、この時点では音沙汰が無いので、しばらくは修正されないかもしれません。

また、バグについてのReproducerも用意したので興味がある方はどうぞ: https://github.com/issei-m/ansible-s3-sync-bug-reproducer

Ansibleについて

当エントリの読者には説明は不要かと思いますのでざっくりとしか説明しませんが、サーバー等の構成管理ツールです。Yamlファイルで求めるサーバーの状態を宣言(例:秘密鍵AでSSHログイン可能なユーザー foo が存在する事、など)すると、その状態を保ってくれます。最近はコンテナやサーバーレスのおかげで以前よりは使う頻度は個人的には減りましたが、今でも使う事があります。

公式サイト

s3_syncモジュールについて

その名の通り、AWS S3のバケットにローカルのファイルをアップロードできるモジュールです。

公式ドキュメント

ただファイルをアップロードするだけでなく、差分チェック等をする事によって無駄なアップロードを発生させず、効果的に同期を行う事が可能な賢いモジュールです。

記述例は次のような感じです:

tasks:
  - name: copy file to s3
    s3_sync:
      bucket: "{{ bucket_name }}"
      file_root: "./s3"
      key_prefix: "" # デフォルト
      mode: push
      file_change_strategy: date_size # デフォルト

この設定では、特定のバケット名(変数 bucket_name で指定)のルート直下に対し、ローカルの ./s3 ディレクトリ以下の全ファイルを同期する形となっています。
また、今回指定している file_change_strategy オプションは先述の通り、何を持ってファイルに差分があるか、すなわちファイルをアップロードするべきか判断するストラテジ(ロジック)の指定となっています。

date_size はデフォルトのストラテジとなっており、 “アップロード対象のファイルのファイルサイズがローカルとS3で異なるか、ローカル側の最終更新日時が新しければアップロード、そうでなければアップロードは行わない” と言う内容になっています。確かにこれであれば、ローカル側のファイルに一切変更がなければアップロードは行われないので、パフォーマンスの点で有利です。

しかし、今回タイトルに示す通り、 date_size の判定ロジックにバグがあり、想定した様に動作しません。

バグの内容

実際にどのようなバグがあるか実験してみます。冒頭で紹介したReproducerを使いましょう。

このリポジトリをCloneして以下のコマンドを実行すると、ローカルの s3/target.txt が指定のS3バケットの /target.txt としてアップロードされます:

$ ansible-playbook playbook.yml --extra-vars 'bucket_name=your-bucket-name'

※バケット名の your-bucket-name は適宜読み替えて下さい

すると以下の出力が示す通り、無事にファイルがアップロードされます。

TASK [copy file to s3] *********************************************************************************************************************************************************************************
changed: [localhost]

因みに先程のファイルの中身は Test!! なのですが、S3側の中身を見てみましょう:

$ aws s3 cp s3://your-bucket-name/target.txt -                            
Test!!

成功ですね。

それでは中身を Yeah!! に変更して再度試してみます。

$ echo 'Yeah!!' > s3/target.txt
$ ansible-playbook playbook.yml --extra-vars 'bucket_name=your-bucket-name'

...

TASK [copy file to s3] *********************************************************************************************************************************************************************************
ok: [localhost]

...

すると、今度は変更は無かったとのレポートが出力されました。実際にS3側を確認してみても、アップロードはされていません:

$ aws s3 cp s3://your-bucket-name/target.txt -
Test!!

念の為内容を Yeah!!! とし、1バイト増やしてみるとアップロードは行われます。(結果割愛)

どうやらファイルサイズが同等だと、中身が違ってもアップロードされないみたいです。

しかし、設定ファイル等ですと、微調整した際でもファイルサイズが同じという事はよくある話ですし、そもそもドキュメントには最終更新日時が新しければアップロードすると書いてあるので、実装側に問題がありそうです。

Moba Pro

実装を見てみた

当該のコード(執筆時点でデフォルトブランチ devel の内容)は以下となります。

https://github.com/ansible/ansible/blob/50d56ca89de26317c41c4fb1a777fd7ccdc025a2/lib/ansible/modules/cloud/amazon/s3_sync.py#L439-L440

if local_modified_epoch <= remote_modified_epoch or local_size == remote_size:
    entry['skip_flag'] = True

「S3側の最終更新日時がローカル側より最新以上、または双方のファイルサイズが同等の場合、スキップ」となっており、明らかにバグです。

仮に entry['skip_flag'] がデフォルトで False なのだとしたら、ドキュメントが主張する動作をするには

if local_modified_epoch <= remote_modified_epoch and local_size == remote_size:
    entry['skip_flag'] = True

にするか、

entry['skip_flag'] = not (local_size != remote_size or local_modified_epoch > remote_modified_epoch)

とするのが良いと思います。求める動作の条件として or が使われているのでそれにつられて実装でも使ってしまった為、起きてしまった物と思われます。(正しくは and

※蛇足ですが、論理値の変数命名がネガティブな場合この手のバグが混入しやすいです。

対処法

冒頭でお見せしたIssueが解決すれば良いですが、別の方法もあります。
それは、 file_change_strategy: checksum を使う事です。
このロジックでは、ローカル側とS3側の対象ファイルのチェックサムを md5 で検証する事によって同一性を確認すると言う物で、幸いな事に現在はバグが無いので正しく動作します(動作確認の結果は割愛)

気になるパフォーマンスの点ですが、実際の検証にあたってS3側については、オブジェクトのETagの値が使われます。ご存知の方も多いと思いますが、S3のオブジェクトのETagはコンテンツの md5 が根拠となっており、アップロード時に確定する物となっています。従って、S3側へはHeadリクエストを送るだけで済みます。(ファイルサイズが必要な date_size と同様)
そこまで巨大なファイルをAnsibleのs3_syncで管理する事はあまり無いと思うので、ローカル側のオーバーヘッドも気にするレベルでは無いはずです。

と言う事で、バグが修正されるまでは checksum を使いましょう。

まとめ

  • file_change_strategy: date_size は2019年3月22日時点ではバグっていてファイルサイズが同じだと、内容が異なっていても更新されない
  • 代替策として checksum ストラテジが有効
  • 論理値を表す変数はネガティブな名称は付けないほうが良い

余談

バグ報告は4日以上も前にしているのに音沙汰がないってやばくないですか・・・。著名なOSSとかだと例えマイナーな機能かつ、報告内容がバグじゃなくても割とすぐ何かしらの返事が来るけど・・・と言う愚痴😇

← 前の投稿

Bug in the Ansible&#8217;s s3_sync module `file_change_strategy` not working and workaround for it

次の投稿 →

Build Infrastructures with Terraform

コメントを残す