技術的な話題 に関する投稿を表示しています

WordPress 環境で nginx の proxy_cache を有効にする

おぼえがき。

Module ngx_http_proxy_module

キャッシュの名前、その保存先、サイズを設定する。

// /etc/nginx/nginx.conf
http {
    ...

    # cache
    proxy_cache_path /tmp/nginx/cache keys_zone=cache1:10m;
}

サイズについては公式ドキュメントにこう書いてあります。

One megabyte zone can store about 8 thousand keys.

10m という指定だとざっくり 8 万個のキーが保存できそうです。

どういうときにキャッシュするかを設定する

いくつかポイントがあるが、とりあえず今回の設定内容を出します。

// /etc/nginx/conf.d/server.conf
server {
    ...

    # cache
    set $mobile "";
    if ($http_user_agent ~* '(Mobile|Android)') {
        set $mobile "SP";
    }

    set $do_not_cache "";
    if ($http_cookie ~* "comment_author_|wordpress_(?!test_cookie)|wp-postpass_" ) {
        set $do_not_cache 1;
    }

    proxy_no_cache          $do_not_cache;
    proxy_cache_bypass      $do_not_cache;
    proxy_cache             cache1;
    proxy_cache_key         "$mobile//$scheme://$host$request_uri$is_args$args";
    proxy_cache_valid       200 301 302 30d;
    proxy_cache_valid       any 10m;

    add_header X-Cache-Status $upstream_cache_status;

    ...
}

モバイル判定

WordPress のテーマとして、レスポンシブではなくサーバサイドでモバイルとの何か差分を作っている場合、キャッシュも分けないといけません。

WordPress では is_mobile() という関数が用意されていて、これをつかってモバイル判定ができるので、これに合わせて nginx も設定するとよさそうです。

vars.php in tags/4.9.5/src/wp-includes – WordPress Trac

今回はおおむね iPhone と Android だけという区別でいくので Mobile|Android という指定にしました。

set で変数を設定し proxy_cache_key にその変数を入れることで、キャッシュする内容をモバイルとそれ以外とで分けることができます。

キャッシュをしないとき

管理画面にログインしていたり、コメントフォームに投稿した名前が記録されていたりなど、ユーザのもっているクッキーに応じて、テーマ上で値が出し分けされる場合、その内容をキャッシュしてはいけません。
(ログインしていないのに管理バーが出ちゃうよ!)

comment_author_|wordpress_(?!test_cookie)|wp-postpass_ として示されるクッキーがあるかを判定すると良いみたいです。

ここもモバイル判定と同様に変数に入れます。

この変数は proxy_no_cache と proxy_cache_bypass で使います。
proxy_no_cache はレスポンスをキャッシュに保存しない設定で proxy_cache_bypass はリクエストに対してキャッシュを使ってレスポンスを送らない設定です。
どちらも 0 ではないときに有効になります。

キャッシュの有効期限

proxy_cache_valid を使うことで有効な時間を設定できます。

ステータスコードをスペース区切りで書き、その後ろに有効期限を記載します。使える単位は ms/s/m/h/d/M/y です。

Configuration file measurement units

ここでは、ステータスコードが 200 または 301 または 302 のときに 30 日の間保持するようにしています。

また any という記載はそれ以外のステータスコードすべてに対して適用されます。ここでは 10 分にしています。

キャッシュが使われたか確認する

$upstream_cache_status という変数を見ると、キャッシュを使ったかどうかがわかります。

Module ngx_http_upstream_module

値として、おおむね HIT か MISS 、期限切れの場合は EXPIRED 、キャッシュしない場合は BYPASS がそれぞれ HTTP ヘッダーに出ているはずです。それ以外はちょっとわかんないです。

nginx の再起動

設定した内容に問題ないかを確認し nginx の再起動やりロードをしないと設定が反映されません。

service nginx configtest
service nginx condrestart

このときに proxy_cache_path に設定したパスが存在しないと自動で作られることはないので、自分で作っておく必要があります。

キャッシュの削除

このままでは WordPress で新しい投稿をしてもキャッシュはが有効な間は、新しい投稿についてはキャッシュ上のコンテンツからたどることはできません。
ここでは 30 日が有効期限なので、月 1 回しか更新することができなくなってしまいます。

手動でキャッシュを消す

proxy_cache_path に設定したパスにキャッシュファイルがもりもり作られるので、そのファイル群を消せばキャッシュはなかったことになります。

rm -rf なんかでどばーっと消すと良いでしょう。

毎回手動でキャッシュを消すのも面倒なので、 WordPress から消せるようにします。

WordPress で投稿を保存した時に自動でキャッシュを消す

// functions.php
function clean_nginx_cache($postId) {
    exec('rm -rf /tmp/nginx/cache/*');
    file_get_contents(get_permalink($postId));
}
add_action('save_post', 'clean_nginx_cache');

手動でやるものを WordPress のアクションフックに設定して自動化したものです。
投稿を保存した時に自動でキャッシュを消し、なんならその投稿のキャッシュを作成することができます。

プラグイン API/アクションフック一覧/save post - WordPress Codex 日本語版

 

プラグインもありそうなので、そういうのを使ってもよいと思います。

“nginx cache” の検索結果 — WordPress プラグイン

nginx の設定でなんとかする

今回の環境では利用できなかったのですが(モジュールがないっぽい…?) proxy_cache_purge を使うと nginx 上でキャッシュ削除の設定ができるようになります。

Module ngx_http_proxy_module

おわりに

nginx + WordPress の環境で proxy_cache を有効にした際の覚書です。

有効にした結果ですが、レスポンスに 500ms 掛かっていたページが 50 - 100ms ほどに収まるようになりました。あまりプラグインなども入れていない簡素なテーマを使っているところなのですが WordPress って結構時間かかるんだなあ…。

今回のものはわりとシンプルなテーマ、プラグインの状況だったので設定することや考えることが少なかったのですが、マルチサイトやプラグインによる大幅な機能変更がついてくると、もっといろいろなことを考えないとうまくキャッシュすることが出来なくなりそうな予感がしています。

特に他人の情報が見えちゃうよ!とかはキャッシュあるあるかつ激ヤバ案件なので、そういった時には入念にテストも重ねて回避したいですね~。

Google App Script を使って Bitbucket Pipelines の動いている状況を見える化した

某所で Bitbucket Cloud を使っていて、そのうえで動く CI 、 Bitbucket Pipelines を利用しています。

Bitbucket Pipelines | Atlassian

CI 時間が月 500 分を超えるまでは無料で利用できるのですが(すごい)、あっちこっちのリポジトリで使い始めたり、プッシュ頻度の高いリポジトリで使ったり
あるいは コード量が多くて CI の時間がかかるようなリポジトリ、そんなところで使い始めると途端に課金額がマッハになってしまいます
(ブランチを絞る、手動だけなどしてもいいけどそういうことじゃない)

いきなり XXXXX ドルの請求です!なんて届くと超ビビってしまうので、
毎月どれくらいの請求額になりそうか、過去はどれくらいだったのかを記録して通知するものを スプレッドシート + GAS で作ってみました。

なぜスプレッドシートと GAS なのか

同某所では GSuite を利用しているので、 Google Drive を組織的に利用できます。
GAS を使うことで、定期的にプログラムを実行することができるうえ、他の外部の何か(例えばサーバなど)について管理することが不要になります。
管理コストが少なく、かつ、スプレッドシートを基軸として、今後の分析にも使えるのでは?と考えると、スプレッドシート最高だなぁという結論になりました。

何も管理する必要なくオンラインにエクセルがいて、マクロの自動実行が出来るよって考えたらやばいと思うんだけど。

Google スプレッドシート - オンラインでスプレッドシートを作成、編集できる無料サービス

Apps Script | Google Developers
Spreadsheet Service | Apps Script | Google Developers

どのように Pipelines の利用状況を集めるか

Pipelines 、というか Bitbucket にはプログラム的に操作やデータ取得を行えるよう API が提供されています。
この中に Pipelines に関するものがあるので、これを利用します。

Bitbucket API

リポジトリ一覧を取得 → 各リポジトリに対して Pipelines の利用状況を確認 というながれで情報を集めます。

リポジトリ一覧 API : Bitbucket API
Pipelines状況 API : Bitbucket API

GAS から API を利用してデータを集める

というわけで出来上がったものがこちらです。

var BitbucketApi = {};

BitbucketApi.getAuth = function () {
  var options = {};
  options.headers = {};
  options.headers.Authorization = "Basic " + "ここに base64(ID:PW) したものを入れる";
  
  return options;
};

BitbucketApi.fetch = function (api) {
  var url = "https://api.bitbucket.org" + api;
  try {
    var rawResponse = UrlFetchApp.fetch(url, BitbucketApi.getAuth());
  } catch (ex) {
    Logger.log(ex);
    return null;
  }
  return JSON.parse(rawResponse.getContentText());
};


var Util = {};

Util.each = function(ary, cb) {
  for (var i=0, m=ary.length; i<m; i++) {
    if(cb(ary[i], i) === false) break;
  }
};

Util.getSheetByName = function (name) {
  if (Util.SpreadSheet == null) {
    Util.SpreadSheet = SpreadsheetApp.getActive();
  }
  
  return Util.SpreadSheet.getSheetByName(name);
};

Util.vlookup = function (searchValue, targetValues, column) {
  var result = null;
  Util.each(targetValues, function(row) {
    if (row[0] === searchValue) {
      result = row[column];
      return false;
    }
  });
  
  return result;
};

Util.yesterDay = function(date) {
  date = date || new Date();
  date.setDate(date.getDate() - 1);
  return date;
};

Util.startDay = function(date) {
  date = date || new Date();
  var d = new Date(date);
  d.setHours(0);
  d.setMinutes(0);
  d.setSeconds(0);
  return d;
};

Util.endDay = function(date) {
  date = date || new Date();
  var d = new Date(date);
  d.setHours(23);
  d.setMinutes(59);
  d.setSeconds(59);
  return d;
};


// reposシート、リポジトリチェック済みフラグのカラム番号
var REPOSITORY_CHECK_COLUMN = 4;


// リポジトリのチェックフラグの初期化
function initPipelinesDuration() {
  var repoSheet = Util.getSheetByName("repos");
  var repoRange = repoSheet.getRange(2, REPOSITORY_CHECK_COLUMN, repoSheet.getMaxRows() - 1, 1);
  repoRange.setValue(0);
}


// pipelinesシートの初期化
function resetPipelinesDuration() {
  Logger.log('WARNING!! Reset record durations!!  exit script.');
  return; // warning, danger.
  
  var sheet = Util.getSheetByName("pipelines");
  if (sheet.getMaxRows() > 1) {
    sheet.deleteRows(2, sheet.getMaxRows() - 1);
  }
  sheet.getRange(1, 1, sheet.getMaxRows(), sheet.getMaxColumns()).setValue("");
  sheet.getRange(1, 1, 1, 4).setValues([["repo", "build_number", "build_seconds", "completed"]]);
}


// リポジトリ一覧の同期
function syncRepositories() {
  // reposシートをまっさらにする
  var sheet = Util.getSheetByName("repos");
  if (sheet.getMaxRows() > 1) {
    sheet.deleteRows(2, sheet.getMaxRows() - 1);
  }
  sheet.getRange(1, 1, 1, 4).setValues([["repo", "url", "project", "status"]]);

  // BitbucketAPIを使ってリポジトリ名一覧をシートに移す
  var page = 1;
  while (true) {
    var repositories = BitbucketApi.fetch("/2.0/repositories/チーム名/?sort=-created_on&pagelen=100&page=" + page);
    if (repositories == null || repositories.values == null || repositories.values.length == 0) {
      break;
    }
    Util.each(repositories.values, function(repo) {
      sheet.appendRow([
        repo.slug,
        repo.links.html.href,
        repo.project.name
      ]);
    });
        
    page++;
  }  
}

// pipelinesシートの更新
function syncPipelinesDuration() {
  var sheet =  Util.getSheetByName("pipelines");
  var pipelineSheetRows = sheet.getLastRow();
  var todayStart = Util.startDay(Util.yesterDay());
  var todayEnd = Util.endDay(Util.yesterDay());
  
  var repoSheet = Util.getSheetByName("repos");
  var repos = repoSheet.getSheetValues(2, 1, repoSheet.getMaxRows(), 3);
  
  // 既にシート上にそのビルドの記録があるかチェック
  var isExistsBuild = function(repo, num) {
    var find = false;
    Util.each(sheet.getSheetValues(2, 1, sheet.getLastRow(), 2), function(row) {
      if (row[0] + row[1] === repo + num) {
        find = true;
        return false;
      }
    });
    
    return find;
  };
  
  Util.each(repos, function(repo, rowIndex) {
    if (repo[0] == "" || repo[2] == "1") {
      Logger.log(repo[0] + " : skip");
      return;
    }
    Logger.log(repo[0] + " : sync");
      
    repoSheet.getRange(rowIndex + 1, REPOSITORY_CHECK_COLUMN).setValue(1);
    
    var page = 1;
    while(true) {
      Logger.log(repo[0] + " : sync : page " + page);
      var response = BitbucketApi.fetch("/2.0/repositories/チーム名/" + repo[0] + "/pipelines/?sort=-created_on&pagelen=100&page=" + page);
      if (response == null || response.values.length == 0) {
        break;
      }
      var breakFlag = false;
      
      Util.each(response.values, function(item) {
        // 完了していないビルドはスキップ
        if (item.completed_on == undefined) {
          return;
        }
        var formattedCompletedOn = item.completed_on.replace(/T.+Z/, "");
        var completedTime = new Date(formattedCompletedOn);
        if (completedTime.getTime() > todayEnd.getTime()) {
          return;
        }
        if (completedTime.getTime() < todayStart.getTime()) {
          breakFlag = true;
          return false;
        }
        
        if (isExistsBuild(item.repository.name, item.build_number)) {
          return;
        }
        
        sheet.appendRow([
          item.repository.name,
          item.build_number,
          item.build_seconds_used,
          completedTime.toLocaleDateString(),
          '=MONTH(D' + pipelineSheetRows + ')=month(now())',
        ]);
        pipelineSheetRows++;;
      });
      
      if (breakFlag) {
        break;
      }
      
      page++;
    }
  });
};

認証周りだけちょっとアレなので、なんとかしたいなあとか思いつつも直打ち。

実装としては次の 2 つの処理に分かれます。

  • リポジトリ一覧の同期

    • 1 日 1 回 午前中 に実行
  • Pipelines 状況の取得

    • 1 日 1 回 お昼ごろ に実行
    • 1 日 1 回 おやつごろ に実行
    • 1 日 1 回 夕方ごろ に実行

リアルタイムに動かすことが難しいので、一日一回動かして、昨日分のデータを取得するようにしています。
Pipelies は 1 度に取りきることが難しいので、何回か動かします。進捗はシートに記録されるので、再度取得することはありません。

あとは、これだけだとシートがなくて動かないので、空っぽのシートを用意したら使えるはず。

  • pipelines
  • repos

収集してからのこと、集計と通知

実行するとこんな感じに Pipelines のデータが収集できるので、ここからスプレッドシートの機能で集計していきます。

ピボットテーブルを使って、当月の、各リポジトリの、実行時間(秒)を合計します。

こんな具合で設定しています。

ここから更に、少々強引な関数を組んで、このデータを参照して日毎の実行時間(分)を算出し、月単位でどれくらいの実行時間になりそうかをざっくり予測できます。

ちなみに使った関数はこんなものです。

データの参照 ( B1 )
=index(aggregate!A1:AA20)


ビルド数の抽出 ( C24 ~ xx26 )
=COUNTIF(pipelines!$D:$D,C1)


総計の抽出行 ( C25 ~ xx26 )
=if(C1="", 0/0, if(C1<>"総計", INDEX(C1:C20, LOOKUP("総計",$B1:$B20,$A$1:$A$20), 1), 0/0))


総計の分 ( C26 ~ xx26 )
=iferror(C25/60, "")


今月予測(ビルド分) ( C27 )
=average(C26:AB26)*22


今月予測(課金額) ( C28 )
=round(((C27-500)/1000)/10*10)*10

で、このときの C28 を UrlFetch で Slack に送るよう、トリガーで日々実行しています。

var SlackApi = {};
SlackApi.config = {};
SlackApi.config.webhook = "";
SlackApi.config.testMode = false;

SlackApi.sendMessage = function (text) {
  var payload = {
    text: text
  };
  
  var options = {
    method: 'post',
    payload: JSON.stringify(payload),
  };
  
  if (SlackApi.config.testMode) {
    Logger.log(SlackApi.config.webhook);
    Logger.log(options);
  } else {
    return UrlFetchApp.fetch(SlackApi.config.webhook, options);
  }
};


function sendSlack() {
  SlackApi.config.webhook = "https://hooks.slack.com/services/....";

  var sheet = Util.getSheetByName("aggregate");
  var usages = sheet.getSheetValues(2, 1, sheet.getLastRow(), sheet.getLastColumn());

  var resultMessage = [];
  resultMessage.push("> *Pipelines利用状況* (" + (new Date()) + ")");
  resultMessage.push("```");
  Util.each(usages, function(usage) {
    if (usage[0] == "") return;
    resultMessage.push(usage[0] + ": " + usage[sheet.getLastColumn() - 1] + "sec");
  });
  resultMessage.push("");
  
  try {
    var paceSheet = Util.getSheetByName("課金ペースをみたい");
    var paces = paceSheet.getSheetValues(27, 2, 2, 2);
    resultMessage.push(paces[0][0] + ": " + paces[0][1]);
    resultMessage.push(paces[1][0] + ": " + paces[1][1]);
    resultMessage.push("");
  } catch (ex) {
    Logger.log(ex);
  }

  resultMessage.push("詳細はここ> https://docs.google.com/spreadsheets/....");
  resultMessage.push("```");
  
  SlackApi.sendMessage(resultMessage.join("\n"));
}

これでいきなり多額の請求が来ることに怯えることが少なくなりました。遅くても 1 日で oops! なことに気づけるはずです。

モダンな xhprof = tideways

PHP で書かれた、パフォーマンスがクソほど悪い web サイトの様子を見ることになったので、どこに時間使っとんねん!を調べようと思い xhprof 入れるぞ!!と準備しようとしたら PHP7 以降はサポートしないらしい。
FB 社が HHVM に移行したから扱わなくなったのかな?わからん。

調べたら PHP7 対応の更新を独自に追加した xhprof と、 tideways という xhprof 互換のプロファイリングツールがあるらしいので tideways を使ってみる話。

XHProf fork with PHP 7.0, 7.1 and 7.2 support | Tideways

tideways エクステンションの準備

ここからエクステンションがダウンロードできる。

tideways/php-xhprof-extension: Modern XHProf compatible PHP Profiler for PHP 7

ちなみにここでタイトル回収なんですけど、こう説明書きされている。

Modern XHProf compatible PHP Profiler for PHP 7 https://tideways.io

 

Windowsの場合はAppVeyorのアーティファクトからになる。Github からもリンクがあるけど、ここ。

php-profiler-extension master.251 - AppVeyor

ここから自分の PHP バージョンを選んで、CI 結果の中から Artifacts があるので、そこに dll が入っている zip がある。

 

あとは環境を問わず、エクステンションを既存の別のエクステンションと同じディレクトリに入れて php.ini で extension=... を記載を追加して読み込めば良い。

こいつは Windows でやってる。

php -m すると今読み込んでいるエクステンションが確認できるので、このときエラーが出ずに tideways の記載が見えれば準備 OK

注意事項として tideways のツール群を使わない(xhprof相当のことだけローカルでやりたい。tidewaysのインフラを使わない)なら
公式サイトでアナウンスされている yum や apt-get を使ったインストールはしないこと。
そっちだとうまくできないっぽい。

Mission control center for PHP application performance | Tideways

ただ tideways のツール群にのっかると、プロファイリングやアラート通知が出来るよ!と書いてあって非常に便利そう。
ツール群を入れたら自動でプロファイリングして集計して可視化までしてくれる。超便利やん…

tideways の xhprof 互換エクステンションを使ってプロファイリングの実施

Github にも書いてあるが、ざっくりとはこういう形。

tideways_xhprof_enable();

start_my_application();

$data = tideways_xhprof_disable();
file_put_contents(__DIR__ . "/1.xhprof", serialize($data));

これで記載したファイルと同じディレクトリに 1.xhprof というファイルが出来上がる。

Laravel や CakePHP なら一番最初に呼ばれる index.php をまるっと囲ってしまうと何も考えずに「どこのページが重たいかな~~フッフ~ン」と楽できる。
楽できる一方で、全ての処理について出てしまうので「特定のページが重いんだよなあ~~」みたいなときには使いにくい。
そのときは個別にやっていくと良さげ。

プロファイリング結果の確認

出力された結果は xhprof と互換性があるので、xhprof を可視化するツールでそのまま見れる。
例えば xhgui がいろいろできて便利そうに見える。

perftools/xhgui: A graphical interface for XHProf data built on MongoDB

いろいろ必要なのがあるなあとか、変なところで詰まるのもなあ、と思い、今回はとりあえず結果を見たかっただけなので xhprof に付属する xhprof_html を使った。
xhgui の話はまた今度にする。

 

ただ、xhprof_html は xhprof の設定が前提となるので、それそのままだと動かない(主にディレクトリ周り)ので、そのあたりを少しいじった。こちらを使いたい方はどうぞ。

sters/xhprof-html: xhprof (or tideways) visualize html tool from xhprof repo.

これを使うとこんな感じの Web ページが見えるようになるので、これでおしまい。

この画面から、どのメソッドが何回呼ばれてどれくらい時間がかかっているかという、PHP アプリケーションのボトルネックが丸見えになってくるので、パフォーマンスチューニングドンドンやっていける。

余談) PHP7 対応された xhprof

これは Travis でぐるぐる CI も回してる。 PHP 7.0 と 7.1 で CI されてるので、そこは確実に使えるっぽい。

yaoguais/phpng-xhprof: upgrade xhprof extension to PHP7

使い方は README にも書いてあるとおりで xhprof とまったく同じでいい。エクステンション入れて xhprof_enable() したら良い。
結果は xhprof_disable() すると受け取れるので、それを serialize() して保存すると各種ツールで見れる。

textlint のルールを作ってみた

先日、textlint というめちゃ便利な text を lint するツールの追加ルールを作ってみた話。ちょっと前にやったんだけど記事にしかけたまま止まってたのでいい加減に書いた次第。

作ったルール

sters/textlint-rule-a3rt-proofreading: [Unofficial] textlint rule that using A3RT Proofreading API.
textlint-rule-a3rt-proofreading - npm

A3RT という機械学習 API サービスの文章校正を textlint から利用できるようにしたもの。
文章校正 API は万能なものではなく、特定ドメインに特化しているので、こういった文章にも上手く適合してくれないこともあるが、とりあえず textlint から呼べるようにしてみるか、というくらいの気持ちでやった。

textlint とは

textlint/textlint: The pluggable natural language linter for text and markdown.

text を lint するツール。いろんな人の作ったいろんなルールを組み合わせて、最強の文章を作っていけるようになるはず。

prh と web+db ルールを入れるとたのしい。

textlint-rule/textlint-rule-prh: textlint rule for prh.

rules/WEB+DB_PRESS.yml at master · prh/rules

A3RT

A3RT
ABOUT|A3RT

A3RT(アート)は「ANALYTICS & ARTIFICIAL INTELLIGENCE API VIA RECRUIT TECHNOLOGIES」の略称です。
...
当初はA3RTについても、リクルートグループ内に限って導入してきましたが、以下のような理由からこのたびグループ外への提供を決意しました。

ちなみにこれ超重要なんですけど、私自身はリクルート社とは関係ないです。
今回作った textlint ルールは勝手にやったものなので何か問題があれば削除するなり対応するので連絡ください。

textlint ルールの作り方

textlint の中にツールがあるのでそれを使うとスケルトンが出来上がります(すごい)

参考: textlintのルールを簡単に作り始めることができるツールを作りました | Web Scratch

基本セットを作れるやつ
textlint/create-textlint-rule: Create textlint rule project with no configuration.

ビルドしたりテストしたりを出来る便利な子
textlint/textlint-scripts: textlint npm-run-scripts CLI help to create textlint rule.

textlint-scripts が超便利で、基本的にはテストの云々を何も気にしないで、ルールのロジックに集中してテストを書けたり。npm へ上げるときに、何も気にせず babel してくれたりするので、とりあえず ES2015+ なコードを書くと動いてくれるのですごい楽。

あとはドキュメントを見たり、データ構造を見たり、他のルールを見たりして、実装する。実装にあたってはヘルパーライブラリがあるので、これも使っていくと AST をこねこねするような込み入ったルールも簡単に書けそう。

ルールの作り方(データ構造とか)
textlint/rule.md at master · textlint/textlint

ヘルパーライブラリ
textlint/textlint-rule-helper: This is helper library for creating textlint rule.

おわり

独自のルール作るのが思いのほか簡単だったので、何か思うところがあれば作ってみるとよいのではー!
特に正規表現ベースのルールとかは作るのがめちゃんこ楽なのでーーーーー!!!

あいまいな VLOOKUP 関数。レーベンシュタイン距離を添えて。

完全に一致するものを探す VLOOKUP 関数ではなく、ある程度あいまいな VLOOKUP 関数がほしい状況になったので、やってみた件。

レーベンシュタイン距離を使うとお手軽にあいまい検索っぽいことができそうなので、これを利用する。
すげーざっくり言うと 2 つの文字列を比べて、片方から見た時に何文字変更したら一緒になりますか?ってもの。
レーベンシュタイン距離 - Wikipedia

Google Spreadsheet で使いたかったので Google App Script なのだけど VBA に移したら Excel でも同様に利用できるはず。Excel だと配列を返して出力はできないが。

function fuzzyVlookup(targetList, searchItem, showDistance, showNth) {
  showDistance = showDistance || 0;
  showNth = showNth || 1;
  
  searchItem = searchItem.toString().split("");
  
  var results = targetList.map(function(targetItem, idx) {
    targetItem = targetItem.toString().split("");
    
    if(targetItem.join("") == "") {
      return null;
    }

    if(searchItem.join("") == targetItem.join("")) {
      return {distance:0, idx:idx};
    }
    
    var leven = []; 
    for (var i = 0; i <= targetItem.length; i++) { 
      leven[i] = []; 
      leven[i][0] = i; 
    } 
    for (var i = 0; i <= searchItem.length; i++) { 
      leven[0][i] = i; 
    } 

    for (var i = 1; i <= targetItem.length; i++ ) { 
      for (var j = 1; j <= searchItem.length; j++ ) { 
        var cost = targetItem[i - 1] == searchItem[j - 1] ? 0 : 1; 
        
        leven[i][j] = Math.min(
          leven[i - 1][j] + 1,
          leven[i][j - 1] + 1,
          leven[i - 1][j - 1] + cost
        ); 
      }
    }
    
    return {
      distance: leven[targetItem.length][searchItem.length],
      idx: idx,
    };
    
  }).filter(function(x) {
    return x !== null;
  }).sort(function(a, b) {
    return a.distance - b.distance;
  });
  
  if (results.length === 0) {
    throw new Error("要素が見つかりませんでした");
  }
  
  results = results.slice(0, showNth).map(function(x){
    var str = targetList[x.idx][0];
    if (showDistance === 1) {
      str += " dist=" + x.distance;
    }
    return str;
  });
  
  return [results];
}

↓こんな感じで使える。良好。

視認しにくいテキストを探す | その 2 puppeteer を使って任意の web サイト上のテキストに対して色差を計算する

前回は色差を計算する方法の簡単な紹介と chroma.js を使った計算について扱いました。
視認しにくいテキストを探す | その 1 chroma.js を使って色差を計算する | ごみばこいん Blog

今回は実践編として、特定の Web ページに対して色差を計算して「視認しにくいテキストを探す」ことをやります。

Web ページに対して色を確認するには、キャプチャしたものから気合で探すか、 CSS の結果を持って確認するか考えられそう。ここでは後者を puppeteer でやってみます。
puppeteer は以前紹介したとおり、 Headless Chrome を Node.js からお手軽に扱うためのものです。
Node.js から Headless Chrome を操作できる puppeteer を試す | ごみばこいん Blog

ページを読み込んで色差を計算するためにこんなコードを書きました。

// ブラウザの起動
const browser = await puppeteer.launch();
const page = await browser.newPage();
page.setViewport({
    width: 1600,
    height: 900,
})

// デバッグ用に console.log を nodejs 側に渡す
page.on('console', msg => console.log(msg.text()));

// サイトにアクセスする
await page.goto('https://gomiba.co.in/test/color_difference.html');

// 色差を計算するために chroma をページ内で読み込む
await page.addScriptTag({
    url: 'https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.3.6/chroma.min.js',
});

// 色差が一定以上のものを探す
await page.evaluate(() => {
    // 全要素を探索していく
    document.querySelectorAll('*').forEach((element) => {
        // テキストノード、または SVG を子に持っている要素を探す
        let foundChildNode = Array.prototype.filter.call(element.childNodes, (e) => {
            let status = false;
            status = status || (e.nodeType === Node.TEXT_NODE && e.textContent.trim().length > 0);
            status = status || e.nodeName.toLowerCase() === 'svg';
            return status;
        });
        if (foundChildNode.length === 0) {
            return;
        }

        // 計算されたスタイルから色を取得
        let elementStyle = window.getComputedStyle(element);
        let fontColor = elementStyle.color;
        let backgroundColor = elementStyle.backgroundColor;

        // 色差を計算する
        let colorDiff = chroma.deltaE(fontColor, backgroundColor);

        // 計算された色差に透明度の色差を加える(透明度の差分 * 0.5 * 100)
        colorDiff += (Math.abs(chroma(fontColor).alpha() - chroma(backgroundColor).alpha()) * 0.5) * 100;

        // 色差が大きいものは無視
        if (colorDiff > 40) {
            return;
        }

        // 色差が小さいものに色付けをする
        console.log(element.nodeName + " : " + colorDiff)
        element.style.cssText = element.style.cssText + 'border: 5px dashed red !important;';
    });
});


// スクリーンショットを撮ってみる
await page.screenshot({path: 'example.png'});


// ブラウザを終了する
await browser.close();

puppeteer の playground がオンラインで提供されているので、コピペで動作確認できるはず、お試しあれ。
Try Puppeteer

手元に Node.js の環境があるなら、こちらのリポジトリでも。コマンドライン引数で対象 URL とキャプチャの保存するファイル名を受け取って、マーカーが引かれたキャプチャ画像が出力されます。
sters/web-color-difference

たとえば medium でやってみるとこんな感じ。白背景の部分で、薄めグレーの箇所ががマークされている。

たとえば、ランダムに色を設定するページを用意して試してみるとこんな具合。

確かに見にくい!!!なものはマークされているようだけど、ちょっと怪しいライン。もうちょっと基準値を調整したり CIEDE2000 のロジックも試すかなどして詰めたほうがいいかも…?とりあえず、のところはこれでもいいんじゃなかろーか。
元々やりたかった「視認しにくいテキストを探す」は出来たとしておしまい。

…と思ったけど、視認しにくいテキストっていうと話題が広いな、小さすぎる文字とか他の要素と被っているとかそういうのもそうなんだよねきっと、うーん。

視認しにくいテキストを探す | その 1 chroma.js を使って色差を計算する

SEO が云々でも色被ってるの良くないよねっていうけど、いや普通にユーザビリティ悪いやん、適当に色選ぶと見にくいこと往々にしてあるやん、って思ったので、そういう何かがあるのかなあって思って調べたら、 Wikipedia 先生にそのまんまの項目が記載されていた。ので、これをまとめつつ、ライブラリがあったのでそれを使ってみる話。

数式、もといロジックをざっくり読んでみる編

前提として Wikipedia のものをガンガン鵜呑みにしていく。
数式とかなんでそうなの?みたいな理解はせず、そういうものがあるんだなーくらいに留める。

色差を計算するには?

Wikiepedia に色差というページがあって、もうこれ。英語版と微妙に内容が違うけど、それは後で触れる。

色差 - Wikipedia

色差を計算するには単純に距離を計算しては、人間の感覚とはだいぶずれてしまってだめ。どうやら CIEDE2000 というものを使うとよいらしい。 CIE は国際照明委員会のことで DE は Delta E だ。2000 は年代。1976 、 1994 と研究され数式が更新されている。で、この CIEDE2000 を計算するには Lab 色空間を使う必要がある。
ただ、ぼくらが普段生きている世界では RGB とか CMYK とか HSV とか HSL とかなので、そこから変換をしてあげないといけない。いや、デザイナーさんとかその手のグラフィック関連に詳しい人なら Lab の世界でも生きているのかもしれない。

(参考)
カラーネーム/RGB/HSL ウェブでのカラー指定いろいろ | アライドアーキテクツのクリエイターブログ

Lab 色空間に生きたい

Lab 色空間も Wikipedia 上にページがある。

Lab色空間 - Wikipedia

単に Lab というと Hunter と CIE の 2 種類があるが CIE の方を最近では指すらしいので、これを使ったらいいみたい。これまた国際照明委員会が決めたもので、明度( L )と、いい感じの色 a と b を使って示し、人間の目で見える全ての色を記述できて機器固有の基準として使えるようにしたものだそうだ。

RGB からどうやって Lab に変換するんだろうねっていうと、 RGB は機器固有の部分があるので、一発変換!とはできず、 RGB を XYZ 色空間 に変換する必要がある。

RGB の世界から XYZ の世界へ移動する

これまた Wikipedia 上にページがある。

CIE 1931 色空間 - Wikipedia

すげーざっくりの理解では「研究成果の結果、 RGB に対する人の感覚を重み付けしたもの」。

ここまでをまとめると?

RGB → XYZ → Lab → 色差が計算できる!

後に回した英語との差分って?

色差の英語版のページを見てみる。
Color difference - Wikipedia

すると「 CMC l:c (1984) 」という日本語にはない数式が出ている。なんか良いやつなんじゃないかな、きっと。色っぽい単語で調べてたらその手の調査したよって論文もでてきた。

DDCPにおける色域外特色近似再現のための色差式評価

優位性とか書いてあるので気になった時に読むとよさそうな気がする。

ライブラリの出番です chroma.js

こう研究された成果があるわけなので、まあきっと便利に使えるようにライブラリ作っている人とかいるんだろうなあ~~とか思っているとやっぱりある。

chroma.js api docs!

色関係の話題をいい感じに取り扱ってくれるライブラリだそうで、各種色空間の変換も任せろ、グラデーションも任せろ、な強いライブラリ。

chroma.js を使って色差を計算する

単純な距離、ユークリッド距離の計算をするには chroma.distance を使う。 mode パラメータで色空間の指定が出来るので、2つの色の間の位相をみたりなんかにもできそうだ。
色差を計算するには chroma.deltaE を使う。ご丁寧に deltaE という命名になっててわかりやすいぞ~。内部的には CMC を使っているらしい。 CIEDE じゃないんだ。

それを実際に試してみようってわけで、こんな HTML + JS を書いて様子を見てみる。

<script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.3.6/chroma.min.js"></script>

<style>
.color-difference td {
    min-width: 15px;
    min-height: 15px;
    padding: 3px;
}
</style>

<table class="color-difference">
    <tr>
        <td></td>
        <td></td>
        <td>distance(RGB)</td>
        <td>deltaE</td>
    </tr>
</table>

<script>
let colors = [
    ['rgb(255,0,0)', 'rgb(0,255,0)'],
    ['rgb(255,0,0)', 'rgb(0,0,255)'],
    ['rgb(255,0,0)', 'rgb(255,255,0)'],
    ['rgb(255,0,0)', 'rgb(255,0,255)'],
    ['rgb(255,0,0)', 'rgb(0,255,255)'],
    ['rgb(255,0,0)', 'rgb(255,128,0)'],
    ['rgb(255,0,0)', 'rgb(255,0,128)'],
    ['rgb(255,0,0)', 'rgb(255,255,255)'],
    ['rgb(255,255,0)', 'rgb(255,0,255)'],
    ['rgb(255,255,0)', 'rgb(0,255,255)'],
    ['rgb(255,255,0)', 'rgb(255,255,255)'],
];

let table = document.querySelector('.color-difference');

colors.forEach((color) => {
    let tr = document.createElement('tr');
    tr.innerHTML = `
        <td style="background-color:${color[0]}"></td>
        <td style="background-color:${color[1]}"></td>
        <td>${chroma.distance(color[0], color[1], 'rgb')}</td>
        <td>${chroma.deltaE(color[0], color[1])}</td>
    `;
    table.appendChild(tr);
});
</script>

こんな出力が得られる。

  • 赤と緑、青よりは、黄ピンクのほうが気持ち近い。水色、距離は遠いんだけど色差はあまり変わらず
  • 赤とオレンジ、マゼンダな色はやっぱり色差が少ない
  • 赤と緑や青を比べるより、赤と白を比べたほうが色差がすくない(!)
  • 黄とマゼンダよりも、黄と水、黄と白のほうが色差がすくない(!)
  • 距離 = 色差 に直結せず、色によって微妙に重み付けがされているのを感じる

わりと見た感覚と近い気がするのと、数字として出るとさらに面白い。

その 2 を書いた。

https://gomiba.co.in/blog/archives/1841

Carbon を使って 開始日と終了日を指定した // 単位の日付データ列を生成する

日単位のデータがわっとあって、週とか月とかの単位で集計してーなー!みたいな、そういう事をやりたいときってあると思うんですよ。
そのための日付の範囲を一覧にしてくれるやつ、というイメージで。作ったらあとは SQL とかそれに準ずる何かにポイポイしたらいいと思う。

Laravel の気持ちだったので Carbon だけど Chronos でも同じようにいけると思う。 startOfMonth とかで、自身の日付変わるところは結構驚いた、さすが Carbon やりますねぇ!(

<?php
require_once('vendor/autoload.php');

use Carbon\Carbon;

// 週の最初を日曜日、最後を土曜日に設定
Carbon::setWeekStartsAt(Carbon::SUNDAY); 
Carbon::setWeekEndsAt(Carbon::SATURDAY);

// $type = 1, 2, 3 // day, week, month
function dateRangeIterator($startDate, $endDate, $type=2) {
	$startDate = Carbon::parse($startDate)->startOfDay();
	$endDate = Carbon::parse($endDate)->endOfDay();

	while ($endDate > $startDate) {
		// "この" 始まりと終わりを取得する
		if ($type === 1) {
			$thisStartDay = $startDate->copy();
			$thisEndDay = $thisStartDay->copy()->endOfDay();
		} elseif ($type === 2) {
			$thisStartDay = $startDate->copy();
			$thisEndDay = $thisStartDay->copy()->endOfWeek();
		} elseif ($type === 3) {
			$thisStartDay = $startDate->copy();
			$thisEndDay = $thisStartDay->copy()->endOfMonth();
		}

		// 終わりが指定値を超えないように調整
		if ($thisEndDay > $endDate) {
			$thisEndDay = $endDate;
		}

		// ジェネレーター生成
		yield [$thisStartDay, $thisEndDay];

		// 次の日/週/月にいく
		if ($type === 1) {
			$startDate->startOfDay();
			$startDate->addDays(1);
		} elseif ($type === 2) {
			$startDate->startOfWeek();
			$startDate->addWeeks(1);
		} elseif ($type === 3) {
			$startDate->startOfMonth();
			$startDate->addMonths(1);
		}
	}
}


// 月単位 2/2 ~ 3/2 まで
foreach(dateRangeIterator('2018-02-02', '2018-03-02', 3) as $x) {
	echo "$x[0] - $x[1]\n";
}

echo "------\n";

// 週単位 2/3 ~ 2/18 まで
foreach(dateRangeIterator('2018-02-03', '2018-02-18') as $x) {
	echo "$x[0] - $x[1]\n";
}

echo "------\n";

// 月単位 2/2 ~ 10/12 まで
foreach(dateRangeIterator('2018-02-02', '2018-10-12', 3) as $x) {
	echo "$x[0] - $x[1]\n";
}

/**
$ php hoge.php
2018-02-02 00:00:00 - 2018-02-28 23:59:59
2018-03-01 00:00:00 - 2018-03-02 23:59:59
------
2018-02-03 00:00:00 - 2018-02-03 23:59:59
2018-02-04 00:00:00 - 2018-02-10 23:59:59
2018-02-11 00:00:00 - 2018-02-17 23:59:59
2018-02-18 00:00:00 - 2018-02-18 23:59:59
------
2018-02-02 00:00:00 - 2018-02-28 23:59:59
2018-03-01 00:00:00 - 2018-03-31 23:59:59
2018-04-01 00:00:00 - 2018-04-30 23:59:59
2018-05-01 00:00:00 - 2018-05-31 23:59:59
2018-06-01 00:00:00 - 2018-06-30 23:59:59
2018-07-01 00:00:00 - 2018-07-31 23:59:59
2018-08-01 00:00:00 - 2018-08-31 23:59:59
2018-09-01 00:00:00 - 2018-09-30 23:59:59
2018-10-01 00:00:00 - 2018-10-12 23:59:59
*/

時間とか分とか、単位をもっと細かくしたいんだよね~~ってなっても、同様にして細かい単位のものを実装したらいいと思う。

いじよ。

(翻訳)Chrome の広告ブロックが何をしているか

元記事はこちら。
Chromium Blog: Under the hood: How Chrome's ad filtering works

自分の理解のため読んだ記録に。
※翻訳者は中の人でもなければ、ガバガバ翻訳なの※


web 上の多くの広告はユーザエクスペリエンスを尊重している。が、ここのところ邪魔なんだけど!と聞いていて、昨年 6 月に発表した( Chromium Blog: Improving advertising on the web )けど Chrome で Better Ads Standards ( The Initial Better Ads Standards - Coalition for Better Ads )に従ってない広告を消すようにしたよ。

Chrome が守っている詳細は前に説明した( Building a better web for everyone )けど、このアプローチがリリースされる 2 月 15 日が近くなってきているので、今日はどうやって動いているかを説明するよ。

Better Ads Standards って何?

オンライン広告のユーザーエクスペリエンスを向上させるための、業界団体が定めるもの。4 万人以上の北米・ヨーロッパのユーザにアンケートを取って、様々な広告について評価をしたよ。
一番迷惑だと評価されたのは全画面広告( Ad Experience: Prestitial Ads [Mobile] - Coalition for Better Ads )とフラッシュアニメーション広告( Ad Experience: Flashing Animated Ads [Mobile] - Coalition for Better Ads )だったよ。

詳しくはこっち( The Research - Coalition for Better Ads )をみてね。

そんな Better Ads Standards に違反する広告のいくつかは、そもそも広告自体に問題があるけれど、ほとんどはサイトの所有者が制御しているよ。思うに、広告が密集していたり、カウントダウンするような広告だったり。

この Better Ads Standards の結果を受けて、多くの迷惑な広告からユーザを守るアプローチを Chrome でやるよ。Better Ads Standards に準拠しているか評価して、発生した問題をサイトに報告、問題に対処する機会を提供し、それでも継続していたら広告の削除を行なうよ。

違反サイトの評価

サイト上のページのサンプルを調査して評価するよ。見つかった Better Ads Standards の問題に応じて、サイトが合格、警告、失敗のステータスに評価するよ。

サイトの評価ステータスは Ad Experience Report API ( Introduction | Ad Experience Report API | Google Developers )を通してアクセスできるよ。

サイト所有者は詳細な結果を見るために、 Google Search Console の Ad Experience Report ( Web Tools - Web Tools )の中で確認できるよ。ここで、再審査をしたりもできるね。

ネットワークレベルでのサイトのフィルタリング

Chrome ユーザがページに移動すると、 Chrome の広告フィルタが、ページが Better Ads Standards に違反してないかを確認するよ。このとき、ネットワークリクエスト(JavaScript や 画像たち)は広告関連 URL と一致しているかチェックするんだ。マッチした場合、 Chrome はそのリクエストをブロックして、広告を表示されないようにするよ。

この広告関連 URL は EasyList フィルタルール( EasyList - Overview )に基づいていて、 Google プラットフォームの AdSense や DoubleClick も含まれるんだ。

Chrome でこれがどのように見えるのか?

ここまで紹介したアプローチで Chrome は広告を自動的にブロックするんだ。少なくとも 1 つの広告がブロックされると、 Chrome はユーザにブロックした旨を伝えるよ。広告を許可するオプションもあるよ。

デスクトップユーザなら Chrome のアドレスバーにでるポップアップブロッカーと似たような通知を出すよ。Android ユーザの場合は画面下の小さなインフォバーで表示されるね。

初期の結果ではユーザのために良い進歩

この操作の結果で Chrome ユーザは Better Ads Standards に準拠していない広告は見えないことになるが、私達のゴールは全ての広告を見せないことではなく web ユーザのエクスペリエンスを向上させることだよ。( The browser for a web worth protecting

2 月 12 日時点では Better Ads Standards に違反していたサイトのうち 42% は既に合格しているよ。これは私達の望んていることで、迷惑な広告体験を修正し、全ての web ユーザに恩恵をもたらすんだ。

違反に関する通知を受けてから 30 日たってもサイトが修正されなければ Chrome は広告をブロックするよ。

私達は迷惑な広告体験から以降する早期な結果を持って、業界と継続的にやり取りし、 Chrome の広告フィルタ機能が不要になる未来を目指しているんだ!

エンジニアリングマネージャーの Chris Bentzel さんが書きました。

docker の出すログファイルでディスクが 100% になった

まとめると?

  • タイトル通り。 docker の出すログファイルでディスクが 100% になった。
  • ログファイルの削除と daemon.json の設定をして完。

起きたこと

環境依存的に難しいので docker を使っているバッチスクリプトがあるのだが、このスクリプトがモリモリと標準出力に出している。や、本来は捨てられる前提のものなので、問題はあるけどない、みたいな状態。
docker は docker 内で動いているメインプロセスの標準出力のログを取っているっぽく、そのログが溜まりに溜まって、ホストマシンのディスクを埋め尽くしてしまった。

それをたまたま、同じホストマシン上の、まったく違う docker コンテナで動いている web アプリケーション上で、セッションがうまく動いていないっぽいという事象が発生したところから、調べてみると、ホストマシンのディスクがいっぱいだった、ということが判明した。

やったこと

何が容量を食っているのか確認した

du コマンドでチマチマ確認したのだが、もうちょっと効率のいい方法がしりたい。一発でバチコーンとわかるような何か。

# du -sh /* | sort -nr
...

# du -sh /var/lib/docker/* | sort -nr
436K    /var/lib/docker/containerd
104K    /var/lib/docker/network
72K     /var/lib/docker/volumes
37G     /var/lib/docker/containers
22M     /var/lib/docker/image
20K     /var/lib/docker/plugins
20K     /var/lib/docker/builder
8.1G    /var/lib/docker/overlay
4.0K    /var/lib/docker/trust
4.0K    /var/lib/docker/tmp
4.0K    /var/lib/docker/swarm
4.0K    /var/lib/docker/runtimes

# du -sh /var/lib/docker/containers/* | sort -nr
372K    /var/lib/docker/containers/df16f7083353204424150c80cb7c87dc9dda19627c7f09632e797a48d3bea2cf
372K    /var/lib/docker/containers/0d9b9a46c096f977921b682b40d7aa9de5dee443ea01a8d0b157f8f366bce764
288K    /var/lib/docker/containers/8d813f2238daa78186881017d1d6b675715f7602549cfb8285cbb08f5832f88d
252K    /var/lib/docker/containers/ce637310ed46de127cfc78813df94b1a269370a07a0f8ab24dc11f2c87bbadc8
228K    /var/lib/docker/containers/fd837df4343e6c54f5c145385bd93e23e8705114243f8a3d856458bf89c454a0
64K     /var/lib/docker/containers/bc4cdc66666f835980147bbc8105741a45db5018777ca883e35fdd3a072a7970
37G     /var/lib/docker/containers/4d81d71c1ea8eaea101cab745f2c16a2bcb096a2bba78f9aecd385f560e62e80
36K     /var/lib/docker/containers/8d9bb861db601facac528d60ee855fd5b38a05dcdc2949fee72e9c3dcb4dd177

# du -sh /var/lib/docker/containers/4d81d71c1ea8eaea101cab745f2c16a2bcb096a2bba78f9aecd385f560e62e80/* | sort -nr
37G     /var/lib/docker/containers/4d81d71c1ea8eaea101cab745f2c16a2bcb096a2bba78f9aecd385f560e62e80/4d81d71c1ea8eaea101cab745f2c16a2bcb096a2bba78f9aecd385f560e62e80-json.log
4.0K    /var/lib/docker/containers/4d81d71c1ea8eaea101cab745f2c16a2bcb096a2bba78f9aecd385f560e62e80/resolv.conf.hash
4.0K    /var/lib/docker/containers/4d81d71c1ea8eaea101cab745f2c16a2bcb096a2bba78f9aecd385f560e62e80/resolv.conf
4.0K    /var/lib/docker/containers/4d81d71c1ea8eaea101cab745f2c16a2bcb096a2bba78f9aecd385f560e62e80/hosts
4.0K    /var/lib/docker/containers/4d81d71c1ea8eaea101cab745f2c16a2bcb096a2bba78f9aecd385f560e62e80/hostname
4.0K    /var/lib/docker/containers/4d81d71c1ea8eaea101cab745f2c16a2bcb096a2bba78f9aecd385f560e62e80/hostconfig.json
4.0K    /var/lib/docker/containers/4d81d71c1ea8eaea101cab745f2c16a2bcb096a2bba78f9aecd385f560e62e80/config.v2.json
4.0K    /var/lib/docker/containers/4d81d71c1ea8eaea101cab745f2c16a2bcb096a2bba78f9aecd385f560e62e80/checkpoints
0       /var/lib/docker/containers/4d81d71c1ea8eaea101cab745f2c16a2bcb096a2bba78f9aecd385f560e62e80/shm

# tail /var/lib/docker/containers/4d81d71c1ea8eaea101cab745f2c16a2bcb096a2bba78f9aecd385f560e62e80/4d81d71c1ea8eaea101cab745f2c16a2bcb096a2bba78f9aecd385f560e62e80-json.log
....
// このログしってる!ぼくが標準出力に出すようにしたやつだ!

ログファイルの削除をした

中身を確認した上で、完全に不要なものだったので削除した。お手軽 echo 。

# echo > /var/lib/docker/containers/4d81d71c1ea8eaea101cab745f2c16a2bcb096a2bba78f9aecd385f560e62e80/4d81d71c1ea8eaea101cab745f2c16a2bcb096a2bba78f9aecd385f560e62e80-json.log

/etc/docker/daemon.json の設定をした

実際には次のような設定を書いている。これで docker 自体が勝手にログファイルの容量制限とローテートをしてくれるっぽい。べんりちゃん。

# cat /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {"max-size": "10m", "max-file": "3"}
}

容量制限とローテートされた様子。上で出したものと別コンテナだけど。

# du -sh /var/lib/docker/containers/61ffb4d88381cd2556a91f6944a2b2d2b57bb7837d8ad9a21ed772748f490b66/* | grep json.log
4.6M    /var/lib/docker/containers/61ffb4d88381cd2556a91f6944a2b2d2b57bb7837d8ad9a21ed772748f490b66/61ffb4d88381cd2556a91f6944a2b2d2b57bb7837d8ad9a21ed772748f490b66-json.log
9.6M    /var/lib/docker/containers/61ffb4d88381cd2556a91f6944a2b2d2b57bb7837d8ad9a21ed772748f490b66/61ffb4d88381cd2556a91f6944a2b2d2b57bb7837d8ad9a21ed772748f490b66-json.log.1
9.6M    /var/lib/docker/containers/61ffb4d88381cd2556a91f6944a2b2d2b57bb7837d8ad9a21ed772748f490b66/61ffb4d88381cd2556a91f6944a2b2d2b57bb7837d8ad9a21ed772748f490b66-json.log.2

公式ドキュメントを見ると他にも設定項目がいろいろあって、デーモン自体の設定なので、どんな権限で動かすか、とか、そういう…。あまり変更するような出番はないかも。
daemon — Docker-docs-ja 17.06.Beta ドキュメント

log-driver として json-file 以外にも fluentd や AWS CloudWatch なども指定できるので、もしかしたら何か活用できる…?具体的にどういう活用方法があるんだろう
Fluentd logging driver | Docker Documentation


docker 入れてる環境、事故防止のためにもログローテート系の設定はとりあえず入れると良いとおもった一件

1 2 3 4 5 6 7 8 9 10