github/super-linter を使い始めた

github.com/github/super-linter

2020-06-18にgithub tech blogで紹介されていた Introducing GitHub Super Linter: one linter to rule them all - The GitHub Blog を使い始めた。

super-linterはgithub actionを用いて使用することを主眼においたlintコマンドだ。 実体はbash scriptで、それぞれの言語コミュニティがサポートするlinterを実行してくれる(Goに対してgolangci-lintとか)

所見

これのいいところはlintツールを選定しなくてもいい点とdockerimageとして運用を想定されているからCIで使いやすい点、それにより余計な喧嘩をしなくて済む点だ。 開発言語自体のlintはみんな割と真面目に設定するのだけどそれ以外の些末なスクリプトに関しては用意しない。 super-linterはそんな些細な点を救ってくれる。

またk8sの流行によりyamlが登場する機会がさらに増えたと思うが、yamlのフォーマットは各々統一されていないため結構ストレスになる。 super-linterで雑に決め打ってしまえるのは結構利点だと思う。

とりあえず自分はGo言語で実装されたプロダクトデザインのマイクロサービスに対して使ってみた。 自分が管理しているマイクロサービスはCI用のshell scriptやdockerfile, yaml fileあたりが雑に書かれている。 このへんにshellcheck, dockerfilelint, yamllintが勝手にあたってくれるのが便利である。 わざわざそれら用のCIツールを用意するのは時間の無駄で結構メンテナンスがめんどくさい。 このへんのメンテナンスコストをdocker imageにすることで下げてくれる。 super-linterはdefaultですべてのlintツールの実行がonになっている。もし実行が遅くなったら不要なオプションを無効にするといい。

設定はREADMEのgithub actionの設定をそのままもってくればよいんだけど もしかしたらちょっと設定をいじる必要があるかもしれない。workaroundは書いておく 社内で半分放置されてるプロジェクトに適当に使って遊んで見るといい

workaround

--------------------------------------------
Gathering user validation information...
- Only validating [new], or [edited] files in code base...
Failed to switch to master branch to get files changed!
ERROR:[fatal: 'master' could be both a local file and a tracking branch.
Please use -- (and optionally --no-guess) to disambiguate]

僕が導入したときはgitのエラーがでた。これはdefault branchにしていているmasterがorigin/masterとlocalにあるmasterのどちらかはっきりしないと怒られているのでorigin/masterを明示しておけば良いと思う。 大したあれじゃないのでv2.2.1 or laterでより適切に直ると思う。

      - name: Lint Code Base
        uses: docker://github/super-linter:v2.2.0
        env:
          VALIDATE_ALL_CODEBASE: false
          VALIDATE_ANSIBLE: false
          DEFAULT_BRANCH: origin/master

bazelでWORKSPACEにGo言語のライブラリを追加する楽なやり方

Go言語のプロジェクトをビルドシステムbazelを用いて管理しているとする。そのとき、新しくライブラリの依存を追加する場合のやりかた

困ること

bazelはプロジェクトで使うパッケージはbazel自身が取ってくる。つまり、Goのパッケージ管理システムを使わない。 そbazelはgit_repository もしくは http_archive で取得したディレクトリ( goの場合は go_repositoryも)に ひとつのプロジェクト(e.g. github.com/keizo042/go-something )として扱い、それぞれのディレクトリには使いたいプロジェクトにつけた名前(e.g. com_github_keizo042_go_something)からの絶対パス ( "//utils"_を用いて依存関係を一意にする。

これで何がおこるかというと、bazelが依存したライブラリを見つけられずにビルドが失敗する。 そしてその依存関係を解決を手で記述するのがめんくさいため、なるべくコマンドに自動生成してもらうやり方を書いた。

使うもの

  • bazel
  • gazelle
  • Go言語のパッケージ管理システム(ここではGo Moduleを例に使う)

やりかた

1. Go言語のソースコードに新しく依存したいパッケージを追加する

package sample

import (
 "github.com/keizo042/go-something/utils"
)

2. Go言語のパッケージ管理システムを使い依存関係ファイルを作る

Go111MODULE=on go mod init && go mod tidy

3. gazelle を持ちいて依存関係ファイルからWORKSPACEとBUILD.bazelを生成する

bazel run //:gazelle update-repo -- -from_file=go.mod

4. だいたいこんな感じになる

WORKSPACE

go_repository(
  importpath = "github.com/keizo042/go-something",
  name = "com_github_keizo042_go_something"
  tags = "v1.0.0",
)

BUILD.bazel

go_package(

  deps = [
   "com_github_keizo042_go_something//utils",
  ]
)

なんでこうするのか

bazelのGo言語サポート用コマンドgazelleには、Go言語のパッケージマネージャの依存関係ファイルからWORKSPACEを自動生成する機能がついている。 そのため、Go Module or depに依存関係の解析をさせたのち、gazelle update-repo コマンドでその結果を取り込むことができる。 こうすれば、自分でファイルを書き換えずに依存関係を解決できる。

さらに、gazelle updateでWORKSPACE以下のGo言語のファイルからimportしているライブラリを読み取ってWORKSPACEにあるパッケージ照らし合わせてBUILD.bazelを更新することもできる。 bazel管理のプロジェクトにおいて、 ライブラリを追加した後、

GO111MODULE=on go mod init && go mod tidy && bazel run //:gazelle update-repos -- -from_file=go.mod && bazel run //:gazelle update 

としておくといい感じにライブラリの依存関係を管理できる。

bazel0.24.1, bazel coverageからgo test -coverの結果を取得するワークアラウンド

環境

$ bazel version
Build label: 0.24.1
Build target: bazel-out/darwin-opt/bin/src/main/java/com/google/devtools/build/lib/bazel/BazelServer_deploy.jar
Build time: Tue Apr 2 16:32:47 2019 (1554222767)
Build timestamp: 1554222767
Build timestamp as int: 1554222767

bazel coverage

bazelには bazel coverage コマンドが存在する。 これは対応する言語のテストとカバレッジコマンドの実行を行う。 Go言語の場合、 go test -cover を実行する 。 カバレッジを取得するためには、パッケージに go_test の宣言が存在し無くてはならない。 go_test はgazelleによって自動生成することができる。 テストファイルを書いたら gazelle update をしておくとよい。

bazelのGo言語サポートであるrules_goでは現在(2019-05-10) coverage supportについて議論中であり、bazel-out (成果物ディレクトリ) から出力結果をかき集めるワークアラウンドを行う必要がある。

coverageファイルはどこにある

bazelは成果物をworkspaceのあるディレクトリ配下にあるbazel-outディレクトリに生成する。 coverageの実行結果もそこに出力される。

つまりMac OS上で以下のコマンドを行った場合

bazel coverage //app...

取りたいカバレッジは以下のパスに置かれる。

$PWD/bazel-out/darwin-fastbuild/testlogs/app/go_default_test/coverage.dat

これで package appカバレッジを取得することができるようになる。 Goのカバレッジファイルは雑にcombinedしても問題にならないためcatで一つのファイルにまとめてしまうとカバレッジツールに流し込みやすくて良い。

--combined_report は動かない

--combined_report=lcov というオプションが存在してこれが使えそうではあるが rules_go ではNullPointerExceptionが起こった。諦めるべき。

--test_arg -test.coverprofile=/path/to/coverでcoverage.datの出力先をまとめるやつも動かない

これは optionが適切に渡されずすべてのテストがfailするようになるため諦めるべき。

bazelで別projectにあるprotobufを参照してGo言語をビルドする

bazelはv0.24.1でためした。 あと走り書きなので、わからなかったら @keizo042まで気軽に聞いてほしい。推敲の参考にする。

TL;DR;

  • bazelを使うとprotobufが生成する中間コードを管理する必要がなくなる
  • protobufが示すgoのimport pathと実際のファイル構造に少し差があってもbazelが吸収してくれる
  • そのかわりpb.goはまったく手に入らなくなる。

bazelの軽い紹介

bazelというビルドシステムが存在する。概念としてはmakefileを置き換えるものであり、Googleが主導して開発している。 特徴として

  • 宣言的に実装する
  • targetごとにrule(pluginみたいなの)を実装することができる
  • ビルドに必要なものはランタイムからすべてダウンロードして実行する

あたりかな。

bazel.build

こまったこと

以下の環境が存在するとする

  • git repositoryとして提供されるprotobuf
  • protbufをビルドするbazelが書かれている
  • bazelはgo言語としてビルドすることができる状態にある
  • 開発しているプロダクトのソースコードは別のrepositoryとして管理している

この状況でどうやってprotobuf repoを利用したらいいのかわからなかった

解決

自分のrepositoryのWORKSPACEに対象のprotobuf repoを引っ張ってくるgit_repository を宣言する name は慣例的に github.com/xxxx/common-protoを com_github_xxx_common_proto みたいな感じで変換しているようなので倣うことにする

# WORKSPACE
load("@bazel_tools//tools/build_defs/repo:gitbzl", "git_repository")

git_repository(
  name = "com_github_xxx_common_proto
  tag = "v0.0.1"
)

次にprotobufを使用したいディレクトリのBUILD.bazelのgo_libraryのdepsのところに一行追加する ここのcom_github_xxx_common_protoはgit_repositoryのnameで宣言した文字列である必要がある。 また//path/to/protoはprotoファイルの存在するディレクトリに対する実際のパスである。 go_default_librarはprotobuf repoのBUILD.bazelで宣言したruleのnameである。 goのプロジェクトにbazelを用いるときgo_default_libraryを示すことでGoのパッケージ扱いにする合意があるらしい。

go_library(
  name = com_github_keizo042_goproj_client
 deps = [
 "@com_github_xxx_common_proto//path/to/proto:go_default_library"
)

gazelle updateでgolangのimportpathに書きかられてしまうばあいのワークアラウンド

resolve ディレクティブでパスの解決順を変更できる

# gazelle:resolve go @com_github_common_proto/path/to/proto:go_default_library path/to/proto

AWS S3の特定のディレクトリの最終更新日を確認するmackerel pluginを書いた

github.com

目的

AWS S3はクラウドファイルストレージです。 そのユースケースとして、ログの保存場所としての用途があります。 重要度の低いログは、ほとんど閲覧の可能性がないため更新されていなくても気づかない可能性が存在します。 そのため、なんらかの方法でログの出力、アップロードの更新を監視することが求められます。 その手段の一つとして、AWS S3のlast modifiedを取得するmackerelでcheckするプラグインを実装しました。

出力例

s3://keizo042-s-bucket/path/to/directory" ディレクトリに更新があるかを確認したい

$check-s3-last-modified -id "AWS ACCESS ID" -secret "AWS SECRET TOKEN" -bucket "keizo042-s-bucket"  -key "path/to/directory" 
OK: last modified at 2019-02-20 10:34:24 +0000 UTC in s3://keizo042-s-bucket/path/to/directory

mackerelにはAWS Integrationがあるのでは

AWS S3 Integrationはget/put/list/headごとのメトリクスが取れるなどたいへん高機能です。
しかしmackerelのAWS S3 IntegrationはS3 bucketを1ホストとして扱います。 つまり、1バケットごとに料金がかかってしまいます。 今回の目的は"更新があること"のみを取得したかったため採用しませんでした。

QUIC硬直化の議論を発掘する日記

QUIC硬直化(QUIC Ossification) の議論を発掘する(表題そのまま

エントリ作成現在のおいて、 draft-181 が最新です。

TL;DR;

  • quicwgのML/issueを発掘して現在の仕様が何を考慮しているのかを探るよ
  • 事前準備として自分の言葉で説明を書いたよ
  • それはそれとして、QUIC硬直化の説明の正しさで言えば↓のエントリのがクオリティ高いからこちらを読むことをおすすめするよ。
  • 強い人教えてください

Chromeが6週間毎にTLSバージョン番号を変更していくかもしれない - ASnoKaze blog

QUIC Ossification

Ossification

Ossification(硬直化)とは、インターネット上にプロトコルの更新に耐えられない不適切な実装が数多く存在し、 プロトコルの更新が困難になる問題をいいます。

代表的な例はTLSの更新です。 TLS1.1 -> TLS1.2やTLS1.2 -> TLS1.3において新しいClientHelloのフォーマットの変更を受け付けない、 新しいversionに対して問題のある挙動を示すmiddleboxが無視できない程度に存在し、追加で長い議論が必要になりました。 IETF標準化の過程においてはちゃめちゃ苦労した経験から、 IETFのメンバーは硬直化と題し積極的に議論が前もって行われるようになった(そうです)。 それより過去の議論については若輩ゆえ存じ上げません。(詳しい人教えてください)

QUICにおいて

QUICは2020~においてエンドユーザーとサーバ終端の通信を担うプロトコル2となることが予想されます。 またTCPの反省から、比較して細やかにVersionを刻んで行けることが期待されています。 そのためQUICにおいてもOssificationの議論がなされています。 IETF draftとして関係しそうなのは

です。

Invariant

invariantとは将来に渡ってフォーマットが変化しないフィールドを定義し、 middleboxがそのフィールドにのみ依存することを明示します。 よって、Versionごとにそれ以外のフィールドを変更してもmiddleboxが ペイロードを少なくともQUIC Payloadであることを理解することができます。

GREASE

GREASEはペイロードにランダムな値を入れてフォーマットの変更に対する耐性を保つ試みです。 プロトコルには”予約されているけれど現行の仕様では使われないフィールド"が存在します。 しかし、不完全な実装ではこれらが一定の値であることを期待して振る舞うことがあります。

Googleはこれに対して対策を試みています。 現行の実装から"使用していないけれど実装が受け入れるべき値"をランダムに送信することで、 広域インターネットにおけるプロトコルの透過性を保とうとしています。 これらをGREASEと呼びます。

錆びついたTLSを滑らかに、GoogleによるGREASE試験 - ぼちぼち日記

QUICはTLS1.3の構成要素であるTLS Handshake Protocolを使用するため、TLS Handshakeにおいて 起こる問題は同様に起こります。そのため同様の対策をQUIC向けに策定し直す必要があります。

つづく

ここまでの理解を前提としてquicwg mailing listを漁っていきます。


  1. https://tools.ietf.org/html/draft-ietf-quic-transport-18

  2. 言い換えるとインターネットバックボーン/DCネットワークにおいては使われなさそう