無印吉澤

Site Reliability Engineering(SRE)、ソフトウェア開発、クラウドコンピューティングなどについて、吉澤が調べたり試したことを書いていくブログです。

ansible コマンドでのログ検索結果を TSV や Markdown 形式に変換する方法

f:id:muziyoshiz:20171211235628p:plain

これは Ansible Advent Calendar 2017 の13日目の記事です。

ansible コマンドでログ検索

ansible コマンドって使ってますか?

Ansible と言えば普段は ansible-playbook コマンドを使うと思うので一応解説しておくと、ansible コマンドは Ansible モジュールを1個だけ実行するコマンドです。-m でモジュール、-a でそのモジュールに渡す引数を指定します。Running Ad Hoc Commands - Ansible Tips and Tricks あたりが詳しいです。

この ansible コマンドで shell モジュール を使うと、複数サーバ上のログを簡単に検索できます。

例えば以下のように、すべてのアプリケーションサーバに対して、一括で ERROR ログ数を検索できます。

$ ansible -i hosts app -m shell -a "cat /var/log/foobar | grep "ERROR" | wc -l"
app1 | SUCCESS | rc=0 >>
177

app2 | SUCCESS | rc=0 >>
84

app3 | SUCCESS | rc=0 >>
37

僕の場合は wc の代わりに head を使って ERROR ログを何件か表示し、サーバごとの傾向の違いを調べることもあります。

$ ansible -i hosts app -m shell -a "cat /var/log/foobar | grep "ERROR" | head -5"

もちろん、普段の運用に必要なログは、特定のサーバやデータベースに集めておくべきです。わかってますよ、わかってるんですが、集めてないログを調べたくなるときはどうしてもあって。そういうときに ansible コマンドで手軽に検索できるのは便利です。

ansible コマンドの結果を TSV/JSON/Markdown/Backlog 形式に変換

ログを調べたら、(それが仕事なら)たいてい他の人に共有したくなるわけですが、この出力は若干使い勝手がよくありません。

Excel や Google Spreadsheet で処理するにも手作業でのコピペが必要になりますし、Wiki にそのまま貼るにも不便です。以前からこの出力がちょっと気になっていました。

そこで Go の勉強がてら、ansible コマンドの結果を他の形式に変換する ansible2tab コマンドを作ってみました。

github.com

先ほど例に挙げた出力をこの ansible2tab に通すと、以下のように変換されます。

$ ansible -i hosts app -m shell -a "cat /var/log/foobar | wc -l" | ansible2tab
app1    177
app2    84
app3    37

デフォルトが TSV なので ansible2tab という名前にしましたが、--format を指定すると JSON などにも変換できます。ちなみに、僕が仕事で使うので Backlog 記法にも対応しました。

$ ansible -i hosts app -m shell -a "cat /var/log/foobar | wc -l" | ansible2tab --format json
{"app1":"177","app2":"84","app3":"37"}
$ ansible -i hosts app -m shell -a "cat /var/log/foobar | wc -l" | ansible2tab --format markdown
|Host|Value|
|---|---|
|app1|177|
|app2|84|
|app3|37|
$ ansible -i hosts app -m shell -a "cat /var/log/foobar | wc -l" | ansible2tab --format backlog
|Host|Value|h
|app1|177|
|app2|84|
|app3|37|

出力が複数行の場合はうまく表になりません。その場合は --format markdown-code--format backlog-code と指定すると、以下のようなコードブロックに変換します。

gist.github.com

できることはこれだけの、地味なツールです。でも、個人的には日常業務で結構こういうことしてるんですよね……。

この ansible2tab コマンドですが、Mac なら brew 経由でインストールできます。

$ brew tap muziyoshiz/ansible2tab
$ brew install ansible2tab

それ以外の環境では リリースページ からダウンロードしてパスを通すか、あるいは Go の開発環境があれば以下のコマンドでインストールできます。

$ go get github.com/muziyoshiz/ansible2tab

まとめ

ansible コマンドは、複数サーバ上のログを手軽に検索するには便利です。今回は、このログ検索した結果を TSV/JSON/Markdown/Backlog 出力する ansible2tab を作りました。Go で実装したおかげで、さくっとクロスプラットフォーム対応できました。

世間に似たようなニーズがどれくらいあるかわからないですが、よかったら使ってみてください。別の形式にも対応して欲しい、JSON はもっと違う形式がいい、などの要望は GitHub の Issue@muziyoshiz までお願いします。

あと、Go 初心者なのでコードがあまりイケてないと思います。そのあたりのツッコミも歓迎です。

今回の参考情報

Go 言語全般

みんなのGo言語【現場で使える実践テクニック】

みんなのGo言語【現場で使える実践テクニック】

Go のコードに詰まってから読み返すと、ヒントになることが書いてある(と気づく)本でした。良い本です。

以下、今回参考にさせて頂いた Web 上の情報です。

バイナリの配布方法

AWS re:Invent 2017 Serverless re:Cap レポート 〜 Lambda, AppSync, Fargate, Cloud9, ML Service

f:id:muziyoshiz:20171120220420p:plain

最近、Serverless 関係の開発力を付けないとなあ……と思っていることもあって、AWS のイベントに参加してきました。

いずれちゃんとしたレポートが出てくると思いますが、個人的に気になった部分のメモを公開しておきます。

Serverless Updates (AWSJ SA 小梁川貴史)

  • AWS Lambda
    • メモリ容量を最大3GBまで設定可能に
    • Go 言語と .NET Core 2.0 のサポートを プリアナウンス
    • CloudTrailに起動イベント(invoke)も記録されるように
    • 関数ごとに同時実行可能数の上限を設定可能に(いままではアカウント全体での上限のみ)
    • コンソールから、その関数に設定したロールでアクセス可能なサービスを確認できるようになった
    • CodeDeploy での段階的デプロイのサポート
    • AWS Serverless Repository
  • Amazon API Gateway
    • いままでは Lambda のVPCアクセスを挟む必要があったが、その必要がなくなった
    • カナリアリリースのサポート(複数バージョンの混在)
    • API Integration(統合API)のタイムアウトのカスタマイズ設定が可能に
    • アクセスログの書式指定が可能に
  • Amazon Cognito
    • 認証機能の強化(ASF)
  • AWS Step Functions
    • re:Invent 前に発表された内容:state machine の update が可能となった

(※もし、スライドが公開されたら「本日ご紹介した update 一覧」のページだけ見れば概要がわかる)

AWS AppSync (AWSJ PS 塚越啓介)

  • DevOps Consultant の方
  • AppSync
    • フルマネージド GraphQL サービス
    • リアルタイム機能とオフライン機能にフィーチャ
    • 現在パブリックプレビュー中
  • AppSync のコンセプト
    • AWS AppSync Client: クライアントライブラリの提供
    • DataSource: Amazon DynamoDB, ElasticSearch, AWS Lambda をサポート
    • Identity: GraphQL Proxy での認証
    • GraphQL Proxy: リクエストのマッピングなど
    • Operation:
    • (※あと2つの特徴はメモできなかった)
  • GraphQL の解説
  • GraphQL Subscription
    • Mutation をトリガーにしたイベントベースモード
  • コンソールのデモ
    • Schema, Data Source の定義など
    • GraphiQL みたいなコンソールを提供してくれる
  • クライアントのデモ
    • ネットワークが繋がらないときは、ローカルストレージにキャッシュされる
    • ネットワークが復帰すると、バックグラウンドで同期される
    • 片方のクライアントからイベントを送ると、subscribeしている他のクライアントにpublishされて、画面が更新される
  • 質問
    • pagination の実装はどれくらい面倒見てくれるのか?
      • 支援の機能はある。詳しくはサンプルコードを参照
      • データベース側への制約はない。DynamoDB 側で next token とか設定すれば、自動的にやってくれる

AWS Fargate (AWSJ SA 大村幸敬)

  • Fargate 概要
    • インスタンス管理不要
    • タスクネイティブAPI
      • タスク → 複数のコンテナを1つにまとめた単位
      • タスクごとに Elastic Network Interface (ENI) が振られる → タスクに直接 IP アドレスを設定できる
      • ECS の場合は、コンテナはホストと通信する構成だった
    • リソースベースの価格
      • CPU と メモリは、50 種類のパターンから選択
      • 秒単位の課金
    • SLA は 99.99%(EC2 と同等レベル)
    • 現在、全世界の Kubernetes 上で動くワークロードの 63% は、AWS 上で動いている
    • Amazon EKS
      • EKS であっても、インフラ部分の管理は必要
      • Fargate の EKS サポートは 2018 年の予定
  • Lambda と Fargate の使い分け
  • デモ
    • マルチAZ、パブリックサブネット/プライベートサブネットの構成
    • タスクごとに CPU、メモリなどを指定
  • Fargate Under the Hood
    • Task Definition を登録。これを使ってタスクを実行
    • Per task ENI で動くので、今まで使っていた VPC でのアクセス制御が可能
    • ALB/NLB との組み合わせが可能
    • visibility がなくなった部分は、CloudWatch でメトリクスを確認するなど
    • Fagate 関連セッション(CON333, CON401 あたりが面白い)

AWS Cloud9 (AWSJ Specialist SA 福井厚)

  • Cloud9
    • クラウドネイティブなIDE
    • EC2 か、SSH 接続可能な Linux サーバにインストールできる
    • Cloud9 からコンソールを実行して、AWS CLI を利用したりできる
    • 複数のダッシュボードを利用して、(アカウントを?)切り替えられる
    • AWS CodeStar と連携
    • 一般的な IDE と同様の操作(例:ショートカットキー、ステップ実行、ブレークポイントなど)
  • Serverless Application Integration
    • リモートの Lambda ファンクションを参照できる
      • Cloud9 をインストールできるリージョンは限られるが、Lambda は任意のリージョンのものを参照できる
    • SAM のテンプレートを自動作成
    • Cloud9 上で Lambda のパラメータのペイロードを変更して、実行できる
  • Collaboration
    • IAM ユーザ間で Cloud9 の環境を共有できる
    • 共有したユーザ間でのチャット、ペアプログラミング(共同編集)ができる
  • AWS CodeStar Integration
    • CodeStar から Cloud9 の環境を構築できる
  • Cloud9 の利用自体は無料
    • EC2 の実行時間と、ストレージ利用量だけが課金対象

ML Services (AWSJ Specialist SA 西谷圭介)

  • 西谷さん、2月末に「サーバーレスアプリケーション開発ガイド」を発売予定
  • AWS の ML サービススタックの Services 層に追加された4つの製品の紹介
  • Amazon Compprehend
    • 自然言語理解サービス
    • 英語とスペイン語に対応
    • キーフレーズの抽出、関連する用語の分類、言語の認識、感情分析(文章のニュアンス)
    • Twitter 等のリアルタイム分析だけでなく、S3 上のファイルに対するバッチ処理も可能
  • Amazon Translate
  • Amazon Rekognition Video
    • 画像認識サービス Amazon Rekognition の動画版
    • 分析結果は1つの JSON で返される
    • JSON にさまざまな分析結果が格納されており、必要に応じて使う
  • Amazon Transcribe
    • 音声をテキストに変換するサービス(文字起こし)
    • プレビューでの対応言語は英語とスペイン語
    • 通常音声と電話音声の両方をサポート
    • タイムスタンプと、その時刻の文字起こしの信頼度
    • ユースケース:コールセンターの音声データの可視化
      • S3 -> Lambda -> Amazon Transcribe -> Amazon Comprehend -> Athena -> QuickSight

感想

Serverless といいつつ、真っ先に連想されそうな Lambda の話はあまりありませんでした。最初のサマリと、あとは Cloud9 の話題が一番 Lumbda に近かったですかね。それでも結構面白かったです。

Fargate は、運用の楽さと AWS サービスとの連携機能が魅力的で、Docker を使うならやっぱり便利そう。Fargate の EKS 対応がリリースされたら、実業務で使ってみたいところです。

GraphQL は以前に少しだけ調べたことがあるんですが、GraphQL Subscription というのが出ているのは知りませんでした。動きが早い分野なので時々見ないと駄目ですね。この辺を読むのがいいでしょうか?

Amazon Linux, RHEL, CentOS での pip のインストール方法の違い

f:id:muziyoshiz:20171120220420p:plain

やらかした話

Amazon Linux は Red Hat Enterprise Linux (RHEL) をベースに開発された Linux ディストリビューションです*1。しかし、古い RHEL 5〜6 をベースにして、その後の開発は分岐しているため、パッケージ管理の方法などには違いがあります。

RHEL や CentOS では、EPEL を使って pip をインストールしても最新版にならず、pip install -U pip (pip install --upgrade pip) を実行する必要があります。

しかし Amazon Linux には独自の yum repository があるため、以下のように yum で最新版にアップデートできます。

$ sudo yum update python27-pip

しかし、それを忘れていて、Amazon Linux 上でつい

$ sudo pip install -U pip

としたところ、/usr/bin/pip が削除されて、新しい pip が /usr/local/bin/pip にインストールされてしまいました。ぐぐってみたら、同様の報告が Stack Overflow にもありました。

stackoverflow.com

「もしかして、これって最新の RHEL や CentOS でも同じことになるんだろうか?」と思って、EC2 上で検証してみました。

検証結果

結果を先に言うと、RHEL や CentOS ではそうなりませんでした。

  • Amazon Linux AMI 2017.09.1

    • 最新の pip が入っており、これ以上アップデートできない(Python 2.7.12, pip 9.0.1)
  • Amazon Linux AMI 2017.03.0

    • Python 2.7.12, pip 6.1.1 がインストールされている
    • pip は alternatives で管理されており、/usr/bin/pip は /etc/alternatives/pip へのシンボリックリンク
    • pip install -U pip を実行すると、/usr/bin/pip は削除され、/usr/local/bin/pip にインストールされる
    • yum update python27-pip でアップデートすれば /usr/bin/pip のままで pip 9.0.1 になる
  • Red Hat Enterprise Linux 7.4 (HVM), SSD Volume Type

    • Python のバージョンは 2.7.5 で、pip はインストールされていない
    • yum install epel-release で EPEL をインストールできず、rpm ファイルをダウンロードしてインストールする必要がある
    • yum install python-pip で pip 8.1.2 がインストールされる
    • インストール先は Amazon Linux と同じ /usr/bin/pip
    • pip install -U pip を実行すると、パスは /usr/bin/pip のままで pip 9.0.1 にアップデートされる
  • CentOS 7 (x86_64) - with Updates HVM

    • Python のバージョンは 2.7.5 で、pip はインストールされていない
    • yum install epel-release で EPEL をインストールできる
    • これ以降は RHEL と同じ

補足:現時点の最新バージョンは Python 2.7.14, pip 9.0.1

まとめ

結論としては、Amazon Linux では pip install -U pip しちゃ駄目ですが、他の Red Hat 系だと特に問題ない(むしろ yum では最新版が入らない)みたいです。Amazon Linux の pip は alternatives で管理されていることが影響しているんでしょうか?

Qiita で同様の症状がいくつか報告されていて、これらはどうも Amazon Linux ではなさそうですが、これ以上は深入りしないでおきます。

この件を Python に詳しい同僚に話したところ、yum が Python 2 で動作する関係で、RHEL や CentOS ではなかなか Python のバージョンを上げられないという事情もあるそうです。

pip は Python 3.4 以降に標準添付されていますが、RHEL や CentOS でその恩恵が受けられるのは当分先になりそうですね……。

おまけ(1):今回の検証に使った AMI

f:id:muziyoshiz:20171120220437p:plain
f:id:muziyoshiz:20171120220448p:plain
f:id:muziyoshiz:20171120221007p:plain

おまけ(2):そもそものきっかけ

今回そもそも何故 Amazon Linux で pip をアップデートしようとしたかというと、こんな経緯でした。

pyenv や virtualenv を使っていれば、今回みたいな目には遭わないと思います。Ansible で使いたいだけだから、と手抜きしたのがよくなかったですね。

Ansible が Python に依存してるせいで、ときどきどうでもいいところでつまづく気がします(今回のは完全に自業自得ですけど)。運用管理ツールを Go で作りたくなる人の気持ちが最近よくわかります……。

*1:AWS Developer Forums: Amazon Linux AMI - what distro is this based on? での Ben@AWS の回答によると、Amazon Linux は RHEL 5.x と、RHEL 6 の一部を元にしている。

Nginx でリバースプロクシを立てるときに気にすべき proxy_next_upstream 設定

f:id:muziyoshiz:20171026000347p:plain:w400

個人的に、Nginx で「これは危険だ」と思っている設定があって、Nginx でなにかあるたびにその設定をつい疑ってしまいます。その設定について他の人に話すたびに、いちいち資料を集めるのが面倒になってきたので、今回はその設定項目についての情報をまとめておきます。

まだ理解に自信がない部分があるので、新しい情報が入ってきたら、この記事を適宜修正します。

リバースプロクシ設定の基本

Nginx をリバースプロクシとして使う時には、ngx_http_upstream_module でサーバのグループを定義します。そして、サーバ名やロケーション(パス)に対して、送信先のグループを指定します。

以下はマニュアルにある例です。その Nginx サーバへのすべてのアクセスを、backend グループに指定されたいずれかのサーバに送信します。

upstream backend {
    server backend1.example.com       weight=5;
    server backend2.example.com:8080;
    server unix:/tmp/backend3;

    server backup1.example.com:8080   backup;
    server backup2.example.com:8080   backup;
}

server {
    location / {
        proxy_pass http://backend;
    }
}

この送信に関わる設定は、proxy_pass を含む ngx_http_proxy_module の方にあります。このモジュールの設定のなかで、(僕が個人的に)よくつまづくのが proxy_next_upstream から始まる設定です。

proxy_next_upstream から始まる設定

これらは、upstream(リクエストの送信先)からエラーが返されたり、リクエストがタイムアウトした場合の動作に関する設定です。

  • proxy_next_upstream
    • 失敗したリクエストを他のサーバに再送する条件(複数指定可)
  • proxy_next_upstream_timeout
    • Nginx 側でリクエストがタイムアウトしたと判断するまでの時間
    • proxy_next_upstream で timeout が指定された場合のみ、この設定が使われる
    • 時間の単位は Configuration file measurement units を参照
  • proxy_next_upstream_tries
    • proxy_next_upstream の条件に合致したリクエストを、最大で何台のサーバに送信するか
    • マニュアルには明示されていないが、この送信回数は最初の1台を含む
      • 1が設定されたら、最初のサーバ1台にしかリクエストを送信しない
      • 3が設定されたら、最初のサーバ1台への送信と、それ以外の2台への再送を行う

これらの設定が明示的に指定されなかった場合のデフォルト値と、その意味は以下の通りです。

  • Default: proxy_next_upstream error timeout;
    • 何らかのエラーが発生した場合、または Nginx 側でリクエストがタイムアウトした場合に、リクエストを再送する
    • ここで言う「エラー」とは、(転送先)サーバへの接続時、リクエストの転送時、またはレスポンスヘッダの読み込み時に発生するエラーのこと
    • 4xx 応答、5xx 応答は、ここで言う「エラー」には含まれない
  • Default: proxy_next_upstream_timeout 0;
    • Nginx 側でのタイムアウトは起こらない(0 は無制限を表す)
  • Default: proxy_next_upstream_tries 0;
    • upstream ディレクトリで定義されたすべてのサーバに対して順番に、エラーが発生したリクエストを再送する(0 は無制限を表す)

不適切な設定が問題になるケース

proxy_next_upstream_tries を指定せずに使っていると、バックエンドのサーバへの接続で何らかのエラーが発生したら、最悪の場合、そのリクエストはすべてのサーバに対して送信 されます。

例えば、以下のような状況になると、無駄なリクエストが Nginx で大量に増幅されて、システム全体の負荷が急増します。

proxy_next_upstream 設定を何も指定していない状態で、
→ アプリケーションサーバが何かのバグで不正なレスポンスを返すようになる
→ そのバグを踏むリクエストが来る
→ そのリクエストがアプリケーションサーバの台数だけ複製される(サーバ10台なら10倍になる)
→ システム全体の負荷が急増

また、proxy_next_upstream_timeout だけ設定していると、こういうこともあり得ます。

proxy_next_upstream_timeout が2秒に設定されていて、proxy_next_upstream_tries は未指定の状態で、
→ 処理時間が2秒を超える重いリクエストが来る
→ その重いリクエストがアプリケーションサーバの台数だけ複製される(サーバ10台なら10倍になる)
→ システム全体の負荷が上がって、普段は2秒未満のリクエストも2秒以上かかるようになる
→ それらのリクエストも10倍に複製される
→ システム全体の負荷が急増

あるべき設定

個人的に考える、あるべき設定は以下の通りです。

proxy_next_upstream_tries は必ず0以外に設定する

この値がデフォルト値の0(無制限)でさえなければ、上記のような問題は起こらないので、まずこれを設定します。

1にすれば再送は起こりませんが、アプリケーションサーバを再起動するような場合にいちいちエラーが出てしまいます。再起動の場合のみを考えるなら、この値が大きすぎても意味はありません。そのため、proxy_next_upstream_tries は2〜3でいいと思います。

proxy_next_upstream_timeout はアプリケーションサーバ側の応答時間より長くする

proxy_next_upstream_timeout がアプリケーションサーバ側の応答時間よりも短いと、せっかくアプリケーションサーバがレスポンスを返しても Nginx で破棄されてしまいます。これではサーバの計算資源の無駄遣いです。

そのため、アプリケーションサーバの応答時間を事前に見積もって、それより長い時間を proxy_next_upstream_timeout に指定しましょう。これは、タイムアウト設計をきちんとしましょう、そして時間がかかる処理(データベース接続)があるならアプリケーション内にきちんとタイムアウト処理を入れましょう、という話ですね。

応答時間の見積もりが難しいなら、proxy_next_upstream_timeout はデフォルト(タイムアウトなし)のままでもいいと思います。

サービスによっては再送処理をオフにする

API 提供時など、クライアント側で再送処理をしてくれるなら、proxy_next_upstream off; を設定し、再送処理をオフにするという手もあります。

関連情報