おそらくはそれさえも平凡な日々

二十四節気スプリントシステムのススメ

tl;dr

二十四節気スプリントシステムとは

今年から、Nature社でもスクラムっぽくスプリントを回し始めたのですが、前職のチームでも採用していた二十四節気スプリントシステムを導入しました。

二十四節気スプリントシステムというと大げさですが、これは単に、各2週間スプリントの名前に二十四節気を割り当てるものです。二十四節気のWikipediaのリンクを以下に載せておきますが、最近はGoogle検索の結果にも出てくるので驚きです。

二十四節気 - Wikipedia

小寒

ちなみに、これは、Mackerelチームでスクラムを採用し始めた頃に、当時のスクラムマスターであったid:motemenが発案したものです。

参考: はてなMackerelチームの開発フロー(スクラム、リモート)について話しました

これが地味に便利なのでこのエントリをしたためています。

二十四節気スプリントシステムのメリット

スプリントに適切な名前がついていると、会話のときに「〇月〇日から〇月〇日までのスプリント」とか言わずに済みますが、その名前がほぼ自動で決まるので便利です。例えば、「次の大寒スプリントでは云々」みたいな話ができる。

ちなみに今は小寒スプリントです。

季節感があるのも良くて、振り返りのときとかに「次は立春スプリントです!」とか言うと「オオッ」っと何故か妙に盛り上がったりします。ちなみに、立春スプリントは2月の頭から始まります。旧暦ベースなので季節の移り変わりが一足先に感じられるのも、若干前倒しの意識で事を進められる雰囲気が出るので地味に気に入っているポイントです。

あまり聞き馴染みのない節気のほうが逆に記憶に残るというのもあり、例えば芒種は、Mackerelチームが2014年に実際にスプリントを回し始めた最初の節気だったらしく、それが当初メンバーの記憶に残っており、節気が一周したときの振り返りで「そう言えば芒種って聞き覚えがある」みたいな話が出て、スクラム1周年に気づくことができてめでたい気持ちになった、というのがあったりしました。

ズレへの対処

二十四節気は一年を24分割するわけですが、これを2週間で単純に回すと48週間なので、一年間で4週間ずれてしまいます。適宜調整すればよいのですが、その辺りも年末年始とゴールデンウィークとシルバーウィーク辺りで結構吸収できます。

地味なポイントとしては「年始は小寒で始める」ようには調整したほうが良くて、そうすれば「2020小寒スプリント」のように、年をまたいでもスプリント名のユニーク性をを担保できます。

まとめ

ということで、二十四節気スプリントシステムの紹介でした。2週間スプリントを敷いている場合、ぜひ導入してみてください。

弊社は今後外国人が開発チームに加わる可能性もありますが、その場合でも二十四節気の趣を感じて貰えればよいかと思っています。このように開発フローの改善にも取り組んでいる昨今ですが、エンジニアを絶賛募集中ですので、ご興味ありましたら採用にご応募ください!

https://nature.global/jp/careers

ghq v1リリースとghq-handbookのお知らせ

https://github.com/motemen/ghq/releases/tag/v1.0.0

年末にアナウンスしていた通り、先程ghq v1.0.0をリリースしました。変更点は以下のエントリでお知らせしていたとおりです。その他Subversion周りの対応を無駄に頑張って強化したりしました。

https://songmu.jp/riji/entry/2019-12-28-ghq.html

是非ご利用ください。

ghq-handbookのお知らせ

年末年始休暇中にドキュメントを書いていたのですが、思ったよりもしっかりとした分量になったので、思い立って電子書籍にして販売してみることにしました。

https://leanpub.com/ghq-handbook

日本語で20ページほどです。値段は$1.99くらいにしたかったのですが、Leanpubで収益を上げる場合には$4.99が下限のようなので、その額に設定させてもらいました。

この本は、ソースのMarkdownを以下のリポジトリで公開しているため、無料で読むことも可能です。pull requestももちろん歓迎です。

https://github.com/Songmu/ghq-handbook

ghqの使い方を網羅した必読の一冊となっております。「こう使ってほしい」が書かれているので、ghqユーザー全員に読んでほしいと思っています。

現状、Leanpubの無料プランを使っており、デザインなども簡素です。この辺りも収益を元にブラッシュアップしたいと思っています。具体的には以下のようなものに投資したい。是非購入をご検討ください。

もちろん、この辺りの項目について協力してくださる方も大歓迎です。

今後ともghqを便利にご利用ください!

2019 -> 2020

2019年は転職がやはり一番大きな転機。Nature Remo Eをなんとか年内にリリースできたのでかろうじて及第点というところ。

Nature Remo Eはこれから電力事業に舵を切る上で非常に大事で、野心的な商品です。会社として非常に面白いフェーズなので興味があれば採用にご応募ください。

https://nature.global/jp/careers

去年は一瞬で過ぎた。ライフイベント含めて色々あった。近年は、毎年去年より激動だな、と思っているのでこれは今年もきっとそうなるのだろう。

個人事業主やISUCONの話はBlogに書けてない。追って書くかもしれない。

何かを成し遂げたい

これだけ書くと具体性がなくすごくバカっぽい。いや、僕がこういう「ビッグになりたい」みたいなノリを馬鹿にしていたし、今でもどこかで馬鹿にしているからそう感じるのだろう。

去年末に、Findyに寄稿させてもらい、それを書く中で、改めて自分がこれまで何も成し遂げてないことに気がついた。

https://engineer-lab.findy-code.io/neet-to-cto

回り道をした結果、今更そういうことを思うようになった。そういう事が多い人生である。それもまた良しだとも思っているのだが。

これから2年を目処に何かを成し遂げようと思う。今の会社Natureがそのステージになる。Natureをどうにかしないといけない。Natureは順調だし、だからこそしくじることはできない。別に僕の力がなくても成功するだろう。だからこそだ。

Mackerelがそのステージになるのかと思っていた。それなりにやった感じもあるが、道半ば感は否めず「成し遂げた」と感じられるレベルには至らなかった。

中途半端な人生を送ってきたことが自分のしこりになっているのだ、ということにも気がついた。

小学校のときに日能研の全国模試で2位になったことが2回あるが、トップにはなれずじまいだった。その時は、ろくに勉強もしないでその成績が出せる自分に満足していた。ただ、あのとき本気を出していれば、トップを取れたんじゃないかと今からすると思う。結局あの時が一種のピークで、その後何かのトップになれたことも無い。

その後の人生でそれなりに真面目に取り組んできたことが、全てそこそこ止まり。チェスもボウリングも自転車ロードレースもサイキックフォース2012も中国語もプログラミングも。一流になりきれない。

それで良いと思ってそれなりに満足して生きていたんだけど、ここに来て欲が出てきた。半端者でも良いから、今の会社で全方位に何でもやっていこうと思う。開発以外にもやらないといけないこと、やりたいこともたくさんある。楽しみである。

やることが多くて迷っている時間が多いとも自覚しているので、そこは改善していきたいし、もっとやれるはずだとも常々思っている。

経営者としてレベル1のペーペーだというところが大きな課題なので、そこは精進していく。勉強したり、他者の経営者とお話させてもらったりというところを増やしていきたい。

ghqで仕事用と趣味用でディレクトリ分けしてリポジトリ管理しやすくなりました

2020年1月5日追記: v1リリースしました https://songmu.jp/riji/entry/2020-01-05-ghq-v1.html

https://github.com/motemen/ghq

最近のghqの状況と、v1リリースに向けた非互換変更などのアナウンスです。現状の最新のv0.17.4を前提に書きます。

ghq.<url>.root 設定により細やかにclone先を設定可能に

例えば、gitconfig上に以下のように設定すれば、デフォルトでは"~/src/hobby" に、仕事用は"~/src/work"にcloneされます。

[ghq]
root = ~/src/hobby

[ghq "https://github.com/myorg"]
root = ~/src/work

[ghq "https://myvcs.example.com"]
root = ~/src/work
vcs = hg

このghq.<url>.rootの機能と併せて、includeIfでディレクトリ配下固有のgitconfigを読み込ませると有用でしょう。

[includeIf "gitdir:~/src/work"]
path = ~/src/work/.gitconfig

ghqはopinionatedなディレクトリ構成を強制するツールであるため、あまり柔軟なghq.root設定をできるようにしたくありませんでしたが、これは良い落とし所であると思っています。

ghq.rootを複数設定している場合の非互換変更

この変更によりghq.rootを複数設定している場合に意図しない非互換変更が入ったのですが、すみませんが、これを仕様とさせてもらいます。

具体的には、設定ファイル上に複数の、ghq.rootが書かれている場合 後に書かれている方が優先 されるようになりました。

具体的には、以下のようなgitconfigがあった場合、v0.17.0以降で、ghq getは"~/proj_b"にcloneをおこないます。

[ghq]
root = ~/proj_a
root = ~/proj_b

後に書かれたものが先に書かれた設定を上書きするのは、gitconfig上では自然な挙動なので、それを正しい仕様とさせてもらいます。

複数設定されている皆様はお手数ですが、設定の変更をお願いします。

ghq listの高速化

多くの貢献により、ghq list がだいぶ高速化されました。ありがとうございます。特に v0.9.0~v0.12.6の間はだいぶ遅いため、アップデートをおすすめします。

また、 ghq list --vcs=git として、探索とリストアップをgitリポジトリのみに絞り込むことで若干速くなるハック(v0.13.1以降)がありますが、現状(v0.14.2以降)ではvcsオプションを付けなくても、速度差はほとんどありません。

ghq createによるリポジトリ新規作成

ghq create myproj とすることで、リポジトリを新規作成するようになりました。この場合、僕の環境だと $(ghq root)/github.com/Songmu/myproj にリポジトリが作られます。内部的にはディレクトリ作成と git init が実行されます。

コマンド実行完了時に標準出力に作成されたディレクトリパスが出力されるため、それを利用してその後の操作もおこなえます。

ghq create <repository URL>, ghq create myorg/newproj の様な指定ももちろん可能です。引数への指定ルールは ghq get <target> のtargetと同様です。

ghq.<url>.rootghq.<url>.vcs の設定も考慮するので、それらが設定されていた場合、適切なディレクトリパス配下で適切なVCSを使って初期化処理がおこなわれます。

現状の機能はそれだけですが、リポジトリ作成後になんらかの初期化コマンド実行(例えばgodzil newなど)をしたいニーズもあると思うので、そのあたりは何らかの連携方法を考え中です。例えば、引数で追加実行コマンドを与えられるなど。

ghq v1に向けて

オリジナル作者のmotemenさんとも話しましたが、issueも大体捌けて、欲しい機能も大体揃い、使い方も固まってきたため、近いうちにv1をリリースしようと計画しています。

それに伴い、幾つかの非互換変更を入れる予定です。すでにpull requestは作成済みです。これらに関して意見があればぜひお知らせください。

ghq.rootのデフォルト値を"~/ghq"に

https://github.com/motemen/ghq/pull/237

gitconfigにghq.rootが設定されていない場合、ghqは現状"~/.ghq"配下にリポジトリをcloneしますが、隠しディレクトリである必要は無いため、標準を "~/ghq" にとする予定です。

"~/src" 派も多いようで、そちらにするか迷ったのですが、"~/src" は一般的な命名であり、ghq以外の何かとコンフリクトする可能性も考えて、デフォルトにはしませんでした。そのあたりはghq.rootを適切に設定してください。

ghq lookの廃止

https://github.com/motemen/ghq/pull/243

ghq lookcpanm --look インスパイアで作られたサブコマンドですが、今は、ghq get --look で代用できますし、そちらのほうがcpanmの挙動とも近いと言えます。

これを廃止する理由は、意図せぬ利用がされていて、それに伴うハマりどころも多いからです。

ghq look はあくまでも「一時的なソースコードのチラ見」のための機能なのですが、これを作業ディレクトリの移動に使い、それゆえに意図せぬ挙動が起こってハマっているケースが散見されます。これはもちろん利用者側の問題ではなく、アナウンス不足であり、そのように誤解されやすい機能であるから故に、削除したいという意図です。

ghq look は子プロセスで別のシェルプロセスを起動して、ディレクトリを移動したように見せかけています。どうしてもそのようにするしかない。

しかしこうすると、シェルの設定がうまく引き継がれなかったり、元のディレクトリに戻るときにexitする必要があるといった直感的でない挙動があります。また、シェルを起動し直すため、シェルの起動時間がかかってしまうという問題もあります。

なので、作業ディレクトリの移動には以下のように、素直にcdをするのがおすすめです。

% cd $(ghq list --full-path --exact motemen/ghq)

また、多くの人がすでにそうしていると思いますが、pecoやfzfのようなフィルタツールと連携して移動する設定をしておくのが王道だとも思います。

ghq importの廃止とghq getへの機能統合

https://github.com/motemen/ghq/pull/244

表題のとおりです。ghq getghq importは機能が類似しており、ソースコード上も重複が多かったため機能統合することにしました。今までimportサブコマンドを利用していた部分をgetに書き換えるだけで動きます。以下のような具合です。

% cat repolist.txt | ghq get --parallel

ちなみに、v0.11.0で追加した並列取得のための--parallelオプションが便利です。最大6並列でrepositoryを取得します。

ドキュメント強化

古い情報も散見されるため、改めてまとまったドキュメントを整備したいと考えています。チュートリアルやレシピ集的なものがあるとよいかと思っています。以下のような選択肢を考えていますが、何が良いか迷っているので、ご意見いただけると嬉しいです。

ロゴとか欲しい…?

ご意見やフィードバックをお待ちしております

ということで、ご意見やフィードバックをお待ちしております。オンラインサロン「MOTEMEN」 の#goチャンネルでghqの開発について投下していることもあるので興味があればご参加ください。(って僕が宣伝しても良いのかな…?)

また、オリジナル作者のmotemenや僕はGitHub Sponsorも開設しているため、検討してもらえると嬉しいです。

GitHub Actionsを使ってGoプロジェクトのCI/CD及びカバレッジ計測をおこなう

GitHub Actionsを遅まきながら使ってみて、自分のアクティブなGitHub上のGoのOSSプロジェクトで知見がたまったので、共有するものである。

GitHub Actionsについて

非常に良い。VCSとCI/CDの統合は体験が良い。各種イベントをハンドリングできるが、そのイベントが元々Webhookで提供されていたものなので、Webhookを弄っていた身からすると非常に親しみやすかった。コードpush以外のイベントもハンドリングしてプログラマブルに扱えるので夢が広がる。

使い勝手とか具体的に良くなった点

リポジトリ直下の.github/workflows配下に既定のYAMLをpushすると、その設定にしたがって自動でアクションが動いてくれる。ブラウザ操作必要ないのは快適。

Goで扱う場合

オレオレGoオーサリングツールである、godzilに導入している。ちなみに、godzilで作られる雛形も、GitHub Actions前提となりました。詳しくはリポジトリをみて貰えばと思いますが、以下に解説を書きます。

テストの実施

.github/workflows/test.yaml の中身が以下。

name: test
on:
  push:
    branches:
    - "**"
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os:
        - ubuntu-latest
        - macOS-latest
        - windows-latest
    steps:
    - name: setup go
      uses: actions/setup-go@v1
      with:
        go-version: 1.x
    - name: checkout
      uses: actions/checkout@v1
      with:
        fetch-depth: 1
    - name: lint
      run: |
        GO111MODULE=off GOBIN=$(pwd)/bin go get golang.org/x/lint/golint
        bin/golint -set_exit_status ./...
      if: "matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'"
    - name: test
      run: go test -coverprofile coverage.out -covermode atomic ./...
    - name: Convert coverage to lcov
      uses: jandelgado/gcov2lcov-action@v1.0.0
      with:
        infile: coverage.out
        outfile: coverage.lcov
      if: "matrix.os == 'ubuntu-latest'"
    - name: Coveralls
      uses: coverallsapp/github-action@master
      with:
        github-token: ${{ secrets.github_token }}
        path-to-lcov: coverage.lcov
      if: "matrix.os == 'ubuntu-latest'"

ポイントは以下。

リリース

release.yaml が以下。

name: release
on:
  push:
    tags:
    - "v[0-9]+.[0-9]+.[0-9]+"
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
    - name: setup go
      uses: actions/setup-go@v1
      with:
        go-version: 1.x
    - name: checkout
      uses: actions/checkout@v1
      with:
        fetch-depth: 1
    - name: release
      env:
        GITHUB_TOKEN: ${{ secrets.github_token }}
      run: |
        export GOBIN=$(pwd)/bin
        PATH=$GOBIN:$PATH make crossbuild upload

pushイベントをsemverにマッチするtag限定でフックして、リリースイベントをキックしている。実プロセスとしては最後の行のmake crossbuild uploadが全てですが、内部的にはgoxzでcrossbuildして、ghrでアップロードしています。GITHUB_TOKEN環境変数をsecrets.github_tokenで受け渡せるのが嬉しいポイント。

これまで個人的にはtoken管理の煩雑さを嫌って、手元でビルドしてGitHub Releasesにあげるパターンが多かったのだけど、これで、GitHub Actions上でリリースするようにできたので嬉しい。今はアップロードにghrを使っているが、公式のアクションactions/upload-artifact の機能が充実してきたらそっちに乗り換えも考えたいと思っている。

ちなみに、on: create でcreateイベントを方法も検討したが、createイベントだとtagsで絞り込むことができないのでやめた。イベントごとに指定できるattributeが異なるのでドキュメントをよく読む必要があります。

バッジ

README.md等に貼り付ける用のバッジも当然あり、URL体系は以下の具合です。

https://github.com/<OWNER>/<REPOSITORY>/workflows/<WORKFLOW_NAME>/badge.svg?branch=master

godzilだと以下の通りです。

https://github.com/Songmu/godzil/workflows/test/badge.svg?branch=master

今後

actions/cacheの活用や、独自acitonの公開などをして知見を貯めて、社内環境への導入も伺いたい。

Go Modules時代に依存をghqで一括cloneする

プロジェクトルートで以下を実行するだけです。ghq import -Pで高速に並列cloneしているのがお洒落ポイントです。

go list -f '{{join .Deps "\n"}}' | xargs go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | ghq import -P

以下のようにシェル関数を定義しても良いでしょう。

gomod-ghq() {
  go list -f '{{join .Deps "\n"}}' | xargs go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | ghq import -P
}

Go 1.13もリリースされ、Go Modulesに対応したプロジェクトも増えてきました。GOPATH時代は $GOPATH/src に依存が自然とcloneされていたのに、そうではなくなったので、このようにghqで一括取得してやると便利です。

ghqは元々Goのパッケージ取得のルールを参考にして作られたツールなのに、逆にこのようにGoのライブラリを取得するためにghqが必要になったというのも面白い話ですね。

そういえば、みんなのGo言語の改訂2版出ていたので皆さん是非ご購入ください。改訂にあたっては、Goの依存管理周りをglideからGo Modulesにゴリっと書き換えました。改訂作業中にもGo Modulesの仕様調整がたびたび入るので結構大変でした。結局Go 1.13でGo Modulesは完全に正式化はされませんでしたしね。

RDBの作成時刻や更新時刻用カラムに関するプラクティス

RDBのレコードに、作成日時や更新日時を自動で入れ込むコードを書いたりすることあると思いますが、それに対する個人的な設計指針です。ここでは、作成日時カラム名をcreated_at、更新日時をupdated_atとして説明します。

tl;dr

MySQLにおける時刻自動挿入

MySQL5.6.5以降であれば、以下のようにトリガーを設定すれば、レコード挿入時に作成日時と更新日時を、更新時に更新日時を、DATETIME型にも自動で埋めてくれます。いい時代になりました。(MySQLが遅すぎたという話もある)

`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

便利なので積極的に使っていくと良いでしょう。アプリケーションがバグっていても確実に埋めてくれます。アプリケーションフレームワークでこれらの値を自動で埋めてくれるものもありますが、今の時代はトリガーを使うのが良いのではないでしょうか。

ただし、これらはログ的な調査記録用の扱いで、アプリケーションから参照することはしないようにするのが良いと考えます。

なぜアプリケーションから参照しない方が良いのか

以下のような理由です。

トリガーにアプリケーションロジックが依存しすぎて密結合になってしまう

例えば、テストコードを書く際にRDBも一緒に動かすことが必須になってしまう。現在時刻のモックも難しい。テストの時だけINSERT文に明示的に時刻を指定するのは本末転倒です。

ちなみに矛盾するようなことを言いますが、僕は、アプリケーションのテストコードはなるべく実際のDBを起動してバンバンデータを入れながらテストするのがいいとは考えています。変に頑張りすぎてインターフェースレイヤー切っちゃうとそこがバグったりするので。

アプリケーションから使うのであれば、汎用的な名前ではなく意味のある名前にすべき

created_atupdated_at のような汎用的な名前ではなく、例えば、blog_entries テーブルであれば、published_at, edited_at などの命名にすべきです。

これらのカラムは、created_atupdated_at とは別に設け、値もトリガーではなくアプリケーションから埋めるべきでしょう。

想定問答など

記録用のためであればログをしっかり残して無駄なデータを入れる必要はないんじゃないの?

これはその通りです。ログ保存の確実性と検索性が高いのであれば、別にここにいちいち記録する必要はないでしょう。ただ、多くの場合そこまでログ整備はしっかりしておらず、とりあえずRDBにクエリを打った方が手っ取り早いということも多いでしょう。ログを取り漏れている可能性もあります。多少冗長な情報はいざという時にあなたの身を助けてくれます

イミュータブルデータモデルを採用している場合更新時刻は不要ではないか?

参考: イミュータブルデータモデル(入門編)

これもその通りです。思考停止して created_atupdated_at を追加するのではなく、そのカラムが本当に必要なのかを考えてテーブル設計しましょう。ただ、考えすぎても案外脳のリソースを取られるので、迷ったらとりあえず入れておく、でも良いとも思います。残念ながら設計力が足りず「やっぱり更新が必要になってしまった」となることもあるでしょう(データ量が許すのであればそうなってからカラムを追加してもいいとは思いますが)。

イミュターブルデータモデルの他にも、例えば、ユーザが操作しないマスタ系のテーブルでdeploy時にそのデータを埋めるフローなのであれば、作成時刻や更新時刻は不要でしょう。

ちなみに、レコードをイミュータブルに扱うというのは、近年の安全な設計論からしても理に叶っています。なるべくイベントとして扱うというのはわかりやすい指針です。ただ、現実問題として、難しい局面もあります。データ量も多くなりがちです。データ量はパーティションなどを活用すれば解決できますが、他にも例えば「10人のユーザーの最新のイベントだけを取って一覧する」といったことが案外難しかったりします。

データが重複してデータ量が増えるのが嫌なんですが

もちろん余計なデータが増えることは避けたいですし、そのカラムが本当に必要かどうか都度検討するのが望ましいと前項で述べました。

また、上記の例ですと、created_atpublished_atに同じ値が入ってしまうことが気持ち悪いかもしれません。しかし、この場合は良くないDRYの落とし穴にはまっています。

本当に、created_atpublished_atは本当に同じデータで良いのでしょうか?例えば下書き機能で「下書き追加時点では公開日時は記録しない」などの仕様が発生したら大変です。そしてこの仕様は妥当です。updated_atedited_atも同じで良いのでしょうか。例えばコメントが追加されたり、トラックバックが送信された場合にはどうすれば良いのか困ってしまいます。

(この辺りの設計も、実はイミュータブルデータモデル的に時刻を更新するそれぞれのイベントごとにテーブルを分ければ綺麗に設計できたりするのですが、それは別の話)

つまり、created_atupdated_atはRDBのドメインの上で「レコードが作成された時刻・更新された時刻」以上の意味を持たないことに価値があるのです。RDBドメイン上の情報であるので、RDBの機能(トリガー)でデータを埋めた方が良いとも考えます。

そして、繰り返しとなりますが、アプリケーションから参照するカラム名は、より意味のある命名を心がけましょう。

Goの任意のLoggerをログローテート対応できるreplaceablewriter

https://github.com/Songmu/replaceablewriter

表題の通りですが、io.Writer をラップして io.WriteCloser として振る舞い、その内部に保持した io.Writer を差し替え可能にするライブラリを書いた。

例えば、Goの標準logをログローテートしたい場合には以下のようにします。

f, _ := os.OpenFile("20191001.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
w := replaceablewriter.New(f)
log.SetOutput(w)
// 翌日になったら差し替える
f2, _ := os.OpenFile("20191002.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
w.Replace(f2) // 以降、20191002.logにログが書き込まれる

io.Writer の差し替えはシグナル受けたときにやっても良いし、タイマーを回しても良いでしょう。ファイル名を同じにしたい場合は、事前にファイルを os.Rename してからファイルを開き直して、Replaceを呼べばOKです。例えば以下のような形。

logname := "log.log"
f, _ := os.OpenFile(logname, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
w := replaceablewriter.New(f)
log.SetOutput(w)
// 翌日になったら差し替える
os.Rename(logname, "log-20191001.log")
f2, _ := os.OpenFile(logname, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
w.Replace(f2)

単なる io.WriteCloser なので、io.Writer を指定できるloggerであればなんでも対応可能です。ざっと書いたので、まだ適当クオリティですが、フィードバックあると嬉しいです。

作った動機とか

個人的に多機能なロガーが好きじゃない。ロガーは呼び出しも多く、IOを伴う処理でもあるため、地味にパフォーマンスのボトルネックになりやすい。なので、ロガーはシンプルにストリームに書くことに集中して欲しい。プラガブルな機能とかログローテート機能は別に必要ない。実際、多機能で知られるlogrusとかもパフォーマンスが悪いことで有名です。

ログローテートも、アプリケーション自体はシンプルに標準エラー出力や標準出力に吐いておいて、後続のlograotate, clonolog, multilog等に任せるのが好みです。kazuhoさんもこう言っています。

そもそもファイルに書くという発想が時代遅れになりつつあるわけです。それでもログローテートしたい場合、ファットなロガーがオールインワンでそれを提供するのではなくて、Goにはio.Writer という素晴らしい抽象があるので、このレイヤーで書き出し先を差し替えれば良いんじゃないかと思ったのが、このreplaceablewriterを作った動機です。

ログローテートについて熱く語ってしまいましたが、別にログローテートに限らず、書き出し先を切り替えられるのは便利だと思うので、よろしければご活用ください。

builderscon tokyo 2019

開催から少し経ったが、今年もbuildersconに行ってきた。一般参加。転職のタイミングとCfP締め切りが近く、無理やり現職ネタでCfP出そうとも思ったのだが間に合わなかった。無念。来年はCfP通してトークしたい。

今年はフルで参加できなさそうで実際そうだったんだけど、コンプリートパック買っておいたので、行きたいところにふらっと行くことができたので体験良かったです。

印象に残ったトークをいくつか。

スーパーカミオカンデ!

今年はとにかく、スーパーカミオカンデのトークが圧巻だった。

http://www-sk.icrr.u-tokyo.ac.jp/~hayato_s/20190831_Super-Kamiokande.pdf

あまりにも規模や桁が違いすぎて、わからない事だらけなのにトークはとにかく面白く引き込まれた。規模は違えど、CentOSとかCiscoのスイッチとか、FPGAでTCPスタック実装(!)とか、僕らでも知っている技術も活用している点が興味深かった。

「何言ってるか全然わからないけどとにかく面白い」という、Shibuya.pm等の技術カンファレンスに行き始めた頃の感覚、「ああ、技術カンファレンスっていうのはこういうのが快感なんだよ」ってのを思い出すことができた。初心に戻れるようなすごく良い発表だった。まさしく「知らなかった、を聞く」

アフターパーティーにスピーカーの早戸さんがいらっしゃっていたので、めっちゃ話を聞きに行った。

アプリ国際化の舞台裏

国際化の技術について前提含めて丁寧に話していてそこだけでもわかりやすくてよかったんだけど、最後に意外にもチームや多様性の話がでてきて、その話が思いの外良かった。

多様性のあるチームは働きにくさを感じるがより良い成果が出るという研究結果

というのが印象に残っている。出典としてでている以下のエントリも必読。

https://hbr.org/2016/09/diverse-teams-feel-less-comfortable-and-thats-why-they-perform-better

20年後のソフトウェアテストの話をしよう

ハードウェアを含めたテストが今後どうなっていくかには現在の業務的にも興味があるので見に行った。20年前と現在を比較して、どれくらいの飛躍があったのかから考察が始まったのが面白かった。

ハードウェアや機械学習など色々な技術要素が取り上げられていた。結局リアルワールドをどれくらい再現可能にするのかという話なので、今後技術トピック的にもめちゃくちゃアツい領域なんだなーというのを改めて感じた。

立ち見だったのだけど、僕より後に入ってきた @t_wada さんが、会場に入るやいなやツカツカツカと脇目もふらず会場の前の方に突き進んで行ったのが面白かった。獲物に突き進むライオンのごとしであった。

ということで

なんだかんだで色々交流もできて、非常に楽しませてもらいました。運営の皆様本当にありがとうございました。お疲れさまでした。

来年も期待しています。トークしたい。

GitHubのprivate repositoryを含んだ場合のGo Modules管理

tl;dr

本題

Goに限った話ではありませんが、プロジェクトで使っているprivate repositoryからまた別のprivate repositoryを参照している場合、CI/CDなどの外部環境からどのようにそれらにアクセスさせるか困ることがあります。

例えば、git submoduleを使っている場合や、Goの場合ですとGo Modulesで指定しているパッケージがprivate repositoryである場合などがあるでしょう。

なぜ困るのかと言うと、GitHubの場合、CI/CD環境からprivate repositoryのリソースにアクセスさせる場合、repositoryにRead-only deploy keyを設定して使う方法が個人にも依存せず、アクセス権限を絞る上でも望ましく、一般的に利用されますが、このDeploy Keyを複数のrepositoryで共有することができないという仕様上の制約があるため、複数のprivate repositoryをcloneしようとすると途端に困ってしまうからです。

どうするか

Deploy Keyが使えない以上、個人ユーザーのSSH Keyか、API tokenを利用するしかありません。しかし、個人のものを使ってしまうと、個人に依存してしまいますし、自分の全てのprivate repositoryにアクセス可能になってしまいます。これでは困る。

ここでも、前回の記事でも取り上げた、"machine account"を活用します。

そういった用途に関して、GitHubのDeveloper Guideにも"Machine users"という項でドンピシャで説明されています。ここでは、accountではなくusersになっています。

ref. https://developer.github.com/v3/guides/managing-deploy-keys/#machine-users

Machine userを作成し、最小限必要なrepositoryのみにREAD権限を与えることで権限を絞ることができます。そのアカウントを複数人で管理すれば個人に依存することもありません。

Goの場合

GoのModulesの場合、対象のpackage repositoryをhttpsスキームのURLで取得しようとするため、取得時にそれを insteadOf で書き換えてやるのが基本戦略になります。

SSHを利用する場合

% git config --global url."ssh://git@github.com/<myorg>/".insteadOf "https://github.com/<myorg>/"

personal access tokenを使う場合

% git config --global url."https://${user}:${token}@github.com/<myorg>/".insteadOf "https://github.com/<myorg>/"

SSHかaccess tokenか

CI/CD上で利用する時、tokenの設定をする方法だと、うっかり画面の実行ログ上でtokenが見えてしまうリスクがあるかも知れません。またSSHを使う方法のほうが、GitHub固有の設定にもならないため、SSH KeyをCI/CD環境上に安全に設定する方法があるのであれば、そちらのほうが良さそうです。

Go公式の以下のissueでも特定のソースホスティングサービス用の設定を持たせられるようにできないか議論されていますが、やはり、sshでinsteadOfを使う方法が現状の対応案として紹介されています。Russ Cox氏もコメントしていますね。

https://github.com/golang/go/issues/26134

CircleCIだとSSH方式がより簡単

CiecleCIは標準でprivate repositoryにDeploy Keyを自動登録することで、repositoryのリソースアクセスを実現しています。

これでは冒頭に述べたように、単一private repositoryのリソースしか取得できないため、CircleCIにはそれを回避するために"User Key"を簡単に設定する機能が備えられています。公式ドキュメントの "Controlling Access Via a Machine User" という項にそれが書かれています。

ref. https://circleci.com/docs/2.0/gh-bb-integration/#controlling-access-via-a-machine-user

これはCircleCIがOAuth経由でGitHubユーザーにSSH Keyを自動的に登録する方法で、CircleCIはそのSSH Keyを使って、repositoryのリソースにアクセスするようになります。

これは少し怖いので、やはり、個人ではなくMachine userでやるべきでしょう。 一度、SSH Keyが登録されたあとは、その認可は念の為revokeしておいた方が良いでしょう。GitHubの個人設定のApplications -> Authorized OAuth Apps からrevokeできます。 revokeするとSSHキーも根こそぎ消えるみたいで駄目でした。セキュリティ的には正しい挙動…。

Go Modulesの依存取得

ここまでくればあとは簡単です。checkoutディレクティブを使えば、repositoryを取得できますが、この時、前項のSSH Keyを自動的に使い、insteadOfの設定も以下のように暗黙的にやってくれています。

% git config --global url."ssh://git@github.com".insteadOf "https://github.com"

あとは、private repositoryはmachine userの権限の範囲で取得できますし、Go Modulesの依存取得もinsteadOfの設定がされているため、go mod download とかでうまいことやってくれます。

まとめ

ということで、GitHubのMachine userとCircleCIのSSHのUser Keyを利用すれば、複数のprivate repositoryの取得が容易になり、privateなGoパッケージの取得も同様に簡単になります。