Skip to main content

57 posts tagged with "oss"

View All Tags

· 2 min read
Shunsuke Suzuki

https://github.com/suzuki-shunsuke/go-jsoneq

2つの値がJSONとして等しいか比較するGoライブラリを開発したので紹介します。

「2つの値がJSONとして等しい」とは、2つの値をそれぞれJSON文字列に変換したら、2つが表現するデータがおなじになるという意味です。

struct {
Foo string `json:"foo"`
}{
Foo: "bar",
}

map[string]interface{}{"foo": "bar"}

を JSON に変換したらともに

{"foo": "bar"}

になりますね。

json.Marshaler のテストや、 実際の JSON 文字列から構造体を定義したときにちゃんと定義できているかチェックするのに使えると思います。

jsoneq.Equal でやっていることは単純です。

  1. json.Marshal で []byte に変換
  2. json.Unmarshal で []byte を map, array と primitive な型からなるオブジェクト(?)に変換
  3. reflect.DeepEqual で比較

引数が []byte の場合は 1 は飛ばします。

GoDoc やサンプルを見れば使い方は簡単にわかると思います。

以上、簡単ですが、自作ライブラリの紹介でした。

· 3 min read

結構前に開発したツールですが、まだ記事にしてなかったので紹介します。

https://github.com/suzuki-shunsuke/durl

ファイル中の URL が壊れていないかチェックするツールです。 ファイル中の URL を抽出し、HTTPリクエストを投げてステータスコードが 2xx でないものがあった場合、異常終了します。

なお、ページ内リンク(アンカー)が壊れているものについては検知できません。

インストール

Go製で、バイナリを GitHub Releases で公開しています。

https://github.com/suzuki-shunsuke/durl/releases

Docker イメージ

https://quay.io/repository/suzuki_shunsuke/durl

busybox ベースの Docker イメージも提供しています。 CI で使うのに便利です。

使い方

durl init で設定ファイル .durl.yml を生成します。

$ durl init

durl check に対象ファイルパスのリストを標準入力として渡してください。 find コマンドなどと組み合わせると良いです。

https://github.com/suzuki-shunsuke/go-errlog/blob/v0.9.0/scripts/durl.sh#L9

find . \
-type d -name node_modules -prune -o \
-type d -name .git -prune -o \
-type d -name vendor -prune -o \
-type f -print | \
grep -v package-lock.json | \
grep -v Gopkg.toml | \
grep -v Gopkg.lock | \
durl check || exit 1

壊れた URL が見つかったら ファイルのパス、URL、HTTPのステータスコードを標準エラー出力します。

$ echo bar.txt | durl check
[bar.txt] https://github.com/suzuki-shunsuke/dead-repository is dead (404)

無視するドメイン

https://github.com/suzuki-shunsuke/durl/issues/11

これらは無視します。

  • example.com, example.org, example.net
  • localhost

設定

https://github.com/suzuki-shunsuke/durl#configuration

  • ignore_urls: 無視するURL(完全一致)のリスト
  • ignore_hosts: 無視するドメイン(完全一致)のリスト
  • http_method: HTTPリクエストのメソッド
    • head,get: HEADリクエストに失敗したらGETリクエスト
    • head: HEADリクエスト
    • get: GETリクエスト
  • max_request_count: 同時に並列でリクエストする最大数
  • max_failed_request_count: 最大の失敗URL数。これを超えると即時異常終了する
  • http_request_timeout: HTTPリクエストのタイムアウト

CI に組み込む

ドキュメントやブログをGitHubなどでホスティングしている場合、CIに組み込むことも出来ます。

· 5 min read
Shunsuke Suzuki

Graylogのリソースを terraform で管理するために作った terraform provider を紹介します。 Graylogとは何かはこちらを読んでください。

Graylogには様々なリソースがあります。

  • User
  • Role
  • Input
  • Index Set
  • Stream
  • Stream Rule
  • Dashboard
  • Alert
  • etc

これらのリソースはWeb UIから作成したり出来るわけですが、 Web UIでポチポチするのは疲れますし、ソースコードで管理したいものです(Infrastructure as Code)。 また、Web UIからでは細かな権限管理は出来ず(限られた権限管理しか出来ない)、APIを使ってする必要があります。

APIを使って管理できるツールを探したものの見つからなかったので、 APIを使ってGraylog用のterraform providerを自作しています。

https://github.com/suzuki-shunsuke/go-graylog

GraylogのAPIの種類は非常に多く、残念ながらカバーできているのは一部だけですが、以下のようなものをサポートしています。

  • Alert Condition
  • Alert Notification (Alarm Callback)
  • Input
  • User
  • Role
  • Index Set
  • Stream
  • Stream Rule
  • Dashboard
  • Ldap Setting

Role はサポートしているので権限管理は問題なく出来ます。 Dashboard Widget もサポートしたいです。

出来れば Alert の設定も出来ると良いのですが、Alertに関するCRUD APIが提供されていない(GETのみ)ので、サポートできません。

terraform を使った管理方法

以下では自分の管理方法を紹介します。

https://github.com/suzuki-shunsuke/example/tree/master/graylog-terraform

にサンプルが置いてあります。

基本はプロジェクトごとに

  1. Index Set, Stream, Role といったリソースを作成
  2. User に Role を付与

という流れになります。

1のプロジェクトごとの設定は terraform の module という形でまとめてしまい、プロジェクトごとにディレクトリを作成しています。

terraform provider の開発について

terraform provider の開発はドキュメントが少なく動かしつつ手探りで書いていたりしています。 terraform provider の開発に興味のある方はこの辺を見てみると良いと思います。

あとは https://github.com/terraform-providers/terraform-provider-google のような公式の provider のソースコードも参考になります。

最後に

API を使って terraform provider を開発することで、Infrastructure as Code をある程度実現できました。

  • Stream Rule と Dashboard をサポートできていない
  • Alertに関するCRUD APIが提供されていない(GETのみ)ので、サポートできない

という問題がクリア出来てないので、そこをクリアしたいです。

また、

  • まだ terraform を CI で出来ていない(ローカルから実行している)
  • 新しいプロジェクトやユーザーの追加の際に雛形を自動生成できるツールを作りたい(特に、新しく参画した人のためにあると良い)

といった点も改善したいです(出来たら記事にしたいと思います)。

2018-12-03 追記

2018-12-31 追記

  • Alert Condition, Notification をサポートしました

· 10 min read
Shunsuke Suzuki

Java 製の OSS ログ管理システム Graylog の紹介です。 Graylog については幾つかに分けて記事を書きたいと思います。 今回はGraylogの入門的な内容になります。

なお、本記事中で「現在」「現時点」といった場合、特に断りがなければ記事執筆時点 2018-11-27 を指します。

Graylog のバージョン

検証に用いるGraylogのバージョンは 2.4.6 になります。

OSSバージョンとEnterpriseバージョンがありますが、本記事ではOSSバージョンを使用します。

Graylog とは

Kibana と Elasticsearch(以下ES) を使ったことがある人は、Kibanaに代わるものだと思っていただくとイメージしやすいかと思います。 ログはGraylogそのものが保持するのではなく、ESにインデキシングされます。 Kibana同様、ESに収集されたログを検索したり、ダッシュボードを作ったり出来ます。 ダッシュボードに関してはKibanaのほうが優れているようにも思えますが、 Graylogは認証・認可によりダッシュボードやログを操作できる人を制限・管理することが出来ます。

Graylogでログを管理する場合、ユーザーは直接ESにはログを送らず、Graylogを経由して送ります。 ESに対するGraylog以外のアクセスを制限し直接ESにアクセスされるのを防ぐことが出来ます。

Graylog は多機能なシステムであり、ログを整形したり、アラートを飛ばしたり、他のシステムにログをフォワードしたりすることも出来ます。 marketplace でサードパーティの plugin が公開されており、機能を拡張することが出来ます。 APIも提供されており、ある程度自動化が可能です。

認証・認可

オンプレミスでログを管理する場合、社外からは勿論社内からのアクセスも制限したいです。 Graylog では LDAP や Active Directory によってアクセスを制限できます。 リソース毎に誰が何を出来るか設定できます。

http://docs.graylog.org/en/2.5/pages/users_and_roles/external_auth.html

ログの収集

ログの収集をするには Graylog で幾つかのリソースを作成する必要があります。

  • Input
  • Index Set
  • Stream
  • Stream Rule

Input はログの入力のフォーマットの設定であり、 どのポートでどういったフォーマットのログを受け付けるかという設定になります。 フォーマットは様々なものがサポートされています。

  • AWS Flow Logs
  • AWS Cloud Watch Logs
  • AWS Cloud Trail
  • Beats
  • CEF AMQP
  • CEF Kafka
  • CEF TCP
  • CEF UDP
  • Fake HTTP Message
  • GELF AMQP
  • GELF HTTP
  • GELF Kafka
  • GELF TCP
  • GELF UDP
  • JSON Path
  • NetFlow UDP
  • Raw AMQP
  • Syslog AMQP
  • Syslog Kafka
  • Syslog TCP
  • Syslog UDP

この設定はログを収集するアプリケーションごとに設定するというより、グローバルな設定なので、他のアプリケーションで既に同じ形式でログを収集していたら新たに設定する必要はありません。

Index SetはESのIndexの論理的なセットです。 GraylogはESの複数のIndexを1つのセットとして管理します。 Index Setの設定で古いログを自動でcloseしたり削除したりする事ができるのでログの管理が楽になります。 ESのログは収集しているだけだとどんどん肥大化していってしまうので、Curatorなどを使って古いものを消したりする必要がありますが、そういったことをGraylogが自動でやってくれます。

Stream はGraylogに収集されたログを分類する仕組みです。 Stream毎に検索したりダッシュボードを作ったりアラートを飛ばしたりログの閲覧を制限したりします。 分類の仕方は自由ですが、自分はアプリケーション毎にStreamを作っています。 Stream毎にStream Ruleを定義し、Stream Ruleの条件にマッチしたログはそのStreamに振り分けられます。 自分はfluentdから送ったログの tag によってStreamを分けています。

ログを送る

公式ドキュメントに幾つかログの送り方が紹介されています。

http://docs.graylog.org/en/2.5/pages/sending_data.html

自分が主にやっているのが

ここまでやればログを検索したり出来ます。

Alert

http://docs.graylog.org/en/2.5/pages/streams/alerts.html

アラートの設定は

  • Condition: アラートの条件
  • Notification: アラート先(どこにアラートを飛ばすか)

の2つに分かれます。それぞれStreamに関連づいた設定です。

Condition では

  • Field content condition
  • Field aggregation condition
  • Message count condition

といった条件で設定できます。

Notification は標準では

  • HTTP(Webhook)
  • Email

の2種類しかないのですが、pluginで Slack などにも通知することが出来ます。 自分は https://github.com/graylog-labs/graylog-plugin-slack を使って Slack に通知しています。

Notification のメッセージのテンプレートは自由に変えられますが、どういったテンプレートが良いかは難しいです。 自分は次のような感じにしています。

${alert_condition.title}

${foreach backlog message}
<https://graylog.example.com/streams/${stream.id}/search?rangetype=absolute&from=${message.timestamp}&to=${message.timestamp} | link> ${message.message}
${end}

graylog の URL は適宜置き換えてください。 link の部分を変数(${message.message}とか)にするとリンクが壊れてしまうことがあったので固定文字列にしています。

graylog-plugin-slack の設定で Graylog URL (optional)を設定しないと ${stream_url} などが空になってしまうことに注意してください。 自分は optional なので元々設定していなかったのでハマりました(ググっても分からなかった)。 Graylog URLという設定は notification 毎に変えるようなものでもないのでglobalに設定できるとよいのですが、どうも出来なそうです。

また、テンプレート中で使える変数の中にmessageのURLはないそうです。

https://community.graylog.org/t/message-url-in-alert-notification/1916

そのため、message.timestamp を from と to に指定して検索するという回りくどい(?)ことをしています。

ダッシュボードの作成

ダッシュボードの作成は

  1. Dashboardページ: 空のDashboardを作る
  2. Stream ページ: クエリを実行したりしてグラフを作る
  3. Stream ページ: グラフをDashboardに追加する
  4. Dashboardページ: グラフを並び替えたりする

詳細は割愛するので http://docs.graylog.org/en/2.5/pages/dashboards.html を読んでください。

権限管理

Graylogの権限管理では User と Role という概念があり、 Role の permissions としてどのリソースにどういった操作を許可するか定義し、 User に Role を付与します。

http://docs.graylog.org/en/2.5/pages/users_and_roles/permission_system.html#permissions

permissions として定義できるものは公式なドキュメントが見つからないのですが、APIで取得できます。

https://gist.github.com/suzuki-shunsuke/e371f1bd2716cde31d6fa89bf39f0e77

例えば streams:read は全てのStreamをreadする権限となりますし、 streams:read:<stream id> とすれば特定のStreamのみreadする権限となります。

プロジェクトごとにroleを定義してプロジェクトの関係者に付与したり、アルバイトや正社員といった雇用形態に応じてroleを定義したり、色々な利用方法はあると思います。

こういったRoleのpermissionの定義はWeb UIからも出来ますが、 残念ながらWeb UIからではなくAPIを用いないと定義できないpermissionもあります。 そのため、自分は Graylog APIを用いて terraform provider を開発し、terraform で Roleの作成を行っています。

最後に

以上、自分が主に使っているGraylogの機能を紹介しました。 Graylogは多機能で自分が使っていない、分かっていない機能が色々あります。 興味のある方は調べてみてください。

· 6 min read
Shunsuke Suzuki

自作のOSS akoi の紹介をします。

  • なぜこんなものを作ったのか
  • akoi と ansible を使ってサーバにバイナリをインストールする方法

について主に説明します。

まとめ

  • akoi はバイナリファイルのインストーラ
  • 設定ファイルで管理できる
  • 冪等であり、効率よくインストールできる
    • 並列インストール
    • Accept-Ranges による分散ダウンロード
  • ansibleでサーバにバイナリをインストールするのを補助してくれる
    • ansible で真面目にバージョンコントロールして効率よくインストールするのは難しい(ほとんどの ansible role は出来ていない)

akoi とは

akoi はバイナリファイルのインストーラです。 設定ファイルにインストールするファイルのダウンロードURLとインストール先を記述して管理します。 インストールするバイナリのバージョン管理が可能であり、既にインストールしてあるバージョンへの切り替えはシンボリックを作り直すだけなので一瞬で終わります。無駄にダウンロードをしたりはしません。 複数のバイナリを並列でインストールしたり、Accept-Ranges ヘッダによる分散ダウンロードをサポートしています。

分散ダウンロードについては

https://qiita.com/codehex/items/d0a500ac387d39a34401

が参考になります。

Goで書かれています。

https://github.com/suzuki-shunsuke/akoi/releases からバイナリをダウンロードしてインストールできます。

詳細はREADMEを読んでください。

なぜ作ったのか

サーバにバイナリをインストールする ansible role を書くのが辛かったからです。 最近は色々なソフトウェアがGoで書かれ、バイナリで配布されています。 そういったバイナリをサーバへインストールするのは ansible で行っているという方も少なくないのではないでしょうか? 有名なソフトウェアをインストールする ansible role は大抵Ansible Galaxy で公開されています。

しかし、ほとんどの role は「真面目に」バージョン管理していません。 ここでいう「真面目に」とは

  1. バージョンを指定できる
  2. バージョンを変更できる
  3. 指定したバージョンが既にインストールされている場合は無駄にダウンロードしたりしない

といったことです。

これらを ansible でやろうとすると結構面倒なんです。

既存のroleでよくあるのが

  1. バージョンにかかわらず既にインストールされていたらインストールしない(バージョン変更できない)
  2. 1の問題を解決するため、強制的に再インストールするフラグがある(同じバージョンのものがインストールされていても無駄に再インストールする)

だと思います。

真面目にやろうとするとこんな感じになる気がします。

https://github.com/suzuki-shunsuke/ansible_role_akoi/blob/master/tasks/install_binary.yml

これらのロジックはソフトウェアに関わらずほとんど共通です。 それをソフトウェアごとに実装してテストしてなんてのは面倒です。

そこで akoi を作る前に汎用的な ansible role として開発してみたのが

https://github.com/suzuki-shunsuke/ansible-role-general-installer

です。

しかしこれは ansible のバグでうまく動きませんでした。

https://github.com/ansible/ansible/issues/34736

そこで作られたのが akoi です。 結果的に ansible でやるより高速かつ汎用的なので作って良かったと思います。

akoi と ansible を使ってサーバにバイナリをインストール

akoi を使うにしても ansible が不要になるという話ではなく、ansible でakoiのコマンドを実行する感じになります。 README にも書きましたが、 akoiを ansible の shell モジュールから呼び出して標準出力をパースすることで task の changed を判定することができます。

tasks:
- name: install consul
shell: "/usr/local/bin/akoi install -f ansible 2>&1"
register: result
changed_when: (result.stdout|from_json)["changed"]

akoi を使ってソフトウェアをインストールために、そもそもどうやってサーバに akoi をインストールするかですが(各サーバに akoi をインストールする必要があります)、 akoi をインストールする ansible role を提供しています。

https://github.com/suzuki-shunsuke/ansible_role_akoi

これで akoi を使ってバイナリをインストールできますが、ソフトウェアのセットアップはバイナリをインストールすれば終わりということではなく、ユーザーやグループを作成したり、設定ファイルを作成したり、systemd のサービスを起動したりと色々あります。

自分はそれらの処理は別の ansible role として開発しています。

バイナリのインストールは akoi でやり、それ以外の部分は別途 ansible role を開発するというスタイルです。 それらの role ではいちいちバイナリをインストールする処理を書かなくて良いのでだいぶ開発が楽になりました。

· 9 min read
Shunsuke Suzuki

自作のOSS gomic の紹介をします。

  • なぜわざわざこんなものを作ったのか
  • 生成されたモックの簡単な使い方

を主に説明したいと思います。

まとめ

  • gomic は Goのinterfaceを実装したモックを生成するCLIツール
  • モックを手で書くのが辛すぎた & 既存ツールで満足できなかったため作った
    • 自動生成できるコードは自動生成すべき
  • 設定ファイルで管理するため、interfaceの更新に合わせてmockの更新が容易
  • 生成されるモックはシンプルなAPIのみ提供するので学習コストが低い

gomic とは

gomic は Goのinterfaceを実装したモックを生成するCLIツールです。 これによってモックを使ったテストの作成を効率化します。 単調な作業を自動化し、本来注力すべきことに注力できるようにするためのツールです。

Goで書かれています。

https://github.com/suzuki-shunsuke/gomic/releases からバイナリをダウンロードしてインストールできます。

同様のツールは幾つかあります。

特に gomock は有名ですね。

なぜ作ったのか

上述のように既に同様のツールはありますし、 gomock と minimock は試しました。 しかしあまり満足のいくものではなかったため、自分で作ることにしました。

自分が欲しかったのは学習コストの低いシンプルなAPIです。 interfaceのメソッドを実装した関数をモックに渡すことで 簡単にメソッドの実装を切り替えたいのです。

// Getwd メソッドのモック
mock.SetFuncGetwd(func() (string, error) {
return "/tmp", nil
})

mock.Getwd() // "/tmp", nil

これは非常にシンプルで分かりやすく、柔軟性のあるパターンです(minimockはこのパターンもサポートしています)。

gomock や minimock では

mockSample.EXPECT().Method("hoge").Return(1)

のように 関数のパラメータと戻り値のペアを渡してモックを実装するパターン(何か名前があるのでしょうか?)をサポートしています。 このパターンを gomic はサポートしていません。 このパターンはごく簡単なサンプルでは有効かもしれませんが、実際には使えないことが多いかなと感じています。

また、gomock はそれ以外にも gomock.InOrdergomock#Call.After など、色々便利なAPIを提供していますが、 それらは学習コストを上げてしまう要因になると思います。 gomicはそういったAPIは提供していません。

素のGoで良いのでは(gomicいらなくない)?

上述のように関数を渡すだけの実装なら gomic なんて使わなくても素のGoで良いのではないかという意見もありそうですね。

http://haya14busa.com/golang-how-to-write-mock-of-interface-for-testing/

でも似たようなモッキングの方法がライブラリに依存しないでmockを書くパターンとして紹介されています (似たようなというか、gomicも v0.4.0 までは構造体のフィールドに代入していました)。

Goではライブラリに依存しないで標準ライブラリだけで書くのが良いという思想・意見がよく見られます。 そのため、gomicのようなツールを好まない方がいるのは承知しています。

ただ、自分はこのパターンの実装を手で愚直に書くのは辛いし、生産的ではないのでツールによって自動生成すべきだと思っています。

以下は2つのメソッドのみ持つシンプルなインタフェースとそのモックです。

とてもシンプルな interface とそのモックですが、それでもモックを実装するのはそこそこ面倒です。 メソッド、interfaceの数に比例してどんどん面倒になります。 golintのようなlinterでエラーにならないようにコードコメントを書くのも地味に大変です。

interfaceを更新すればmockも更新しないといけません。

ツールによって自動化すべきです。

モックの使い方

生成されたモックの使い方について軽く説明します。 v0.5.0 時点のものなので古くなっているかもしれません。 最新の使い方は

をご確認ください。

以下のサンプルは v0.5.0のサンプル を元にしています。

まず mock を生成します(以下このモックを生成する関数を"コンストラクタ"と呼びます)。

mock := examples.NewOSMock(nil, nil)

第一引数は testing.T で、通常のテストならテスト関数の引数をそのまま渡せば良いし、そうでなければ nil を渡せば良いと思います。 第二引数は `func(t testing.T, intfName, methodName string)` 型の関数で、interfaceのメソッドの実装がセットされていない場合に呼び出されます。nil を渡すと代わりにgomic.DefaultCallbackNotImplemented が呼び出されます。

mockは interface を実装しています。

次にinterfaceのメソッドを実装した関数をmockにセットします。

mock.SetFuncGetwd(func() (string, error) {
return "/tmp", fmt.Errorf("")
})

mock.Getwd を呼び出すと SetFuncGetwd に渡した関数が呼び出されます。

上記のサンプルのように決まった値を返すだけの fake はよくあるので、以下のように簡単に書けるようにしています。

mock.SetReturnGetwd("/tmp", fmt.Errorf(""))

モックの SetFuncXXX 及び SetReturnXXX はモック自身を返すのでメソッドチェーンが出来るようになっています。

mock := examples.NewOSMock(nil, nil).
SetReturnMkdir(nil).
SetFuncGetwd(func() (string, error) {
return "/tmp", fmt.Errorf("")
})

実装がセットされていない状態でモックのメソッドを呼び出すと コンストラクタの第二引数で渡した関数が呼び出されます。

コンストラクタの第二引数がnilだと gomic.DefaultCallbackNotImplemented が呼びだされます。 gomic.DefaultCallbackNotImplemented は コンストラクタの第一引数が nil だと log.Fatal を、そうでなければ testing.T#Fatal を呼び出し、そこで処理を停止します。

コンストラクタの第二引数で渡した関数で log.Fatal や testing.Fatal によって処理を止めなければ、interfaceのメソッドを実装していない場合、zero value を返す fake になります。

一番簡単なのは gomic.DoNothing を渡すことです。

s, err := mock.Getwd(nil, gomic.DoNothing)

上で説明したことは

https://github.com/suzuki-shunsuke/gomic/blob/v0.5.0/examples/os_mock.go#L27-L67

を見てもらえばわかると思います。

· One min read
Shunsuke Suzuki

自作のOSS go-gencfg を紹介します。 Golang で viper という汎用的な設定管理ライブラリがありますが、 特定のアプリケーション用に viper のラッパーを生成するCLIツールです。

使い方や開発の背景を書こうかと思いましたが、だいたい README に書いてあるので そちらを御覧ください。

https://github.com/suzuki-shunsuke/go-gencfg/blob/master/README.md