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

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パッケージの取得も同様に簡単になります。

CircleCIでgit-pr-releaseする

git-pr-releaseそのものの説明に関してはninjinkunの以下のエントリを参照ください。

git-pr-releaseのすすめ

さて、現職に入社したら、git-pr-releaseが使われていたのですが、リリース担当者が手元で実行するフローになっていました。しかし、git-pr-releaseの本場であり発祥の地でもある、はてな社では、git-pr-releaseはCIに作らせるものであったので、それに倣って、こちらも現職で利用しているCircleCIに作らせるようにしてみた。

手順

  1. .git-pr-releaseをrepositoryに配置する
  2. GIT_PR_RELEASE_TOKEN をCircleCIに登録する
  3. .circleci/config.yml にjobを設定する

おまけ

1. .git-pr-releaseをrepositoryに配置する

すでにgit-pr-releaseをお使いの場合は、既に配置されているかも知れませんが、repository rootに以下のような.git-pr-releaseファイルを配置してコミットもしておく。

[pr-release "branch"]
    production = master
    staging = develop
[pr-release]
    labels = pr-release

pr-release.labels でラベルの設定もできるのでこれも後でリリース履歴のリストアップするときなどに便利です。設定を~/.gitconfig.git/configに登録しておく手もありますが、ちゃんとrepositoryに置いておくとプロジェクト共通の設定になるので何にせよ良いです。

2. GIT_PR_RELEASE_TOKEN をCircleCIに登録する

GitHub上でpersonal access tokenを発行する。その際、scopeの選択のときにrepoにチェックを入れておく。

そこで作られたtokenをCircleCIのプロジェクトの環境変数設定で GIT_PR_RELEASE_TOKEN という名前で登録しておく。

ここで個人のpersonal access tokenを使うのが嫌な場合は、後述のmachine accountを活用すると良い。

3. .circleci/config.yml にjobを設定する

CircleCIの設定に例えば以下のようにjobを設定する。ここでは"pr-release"という名前で、jobを設定している。たった3ステップの簡単なjobである。

version: 2.1
jobs:
  build:
    ...(略)...
  pr-release:
    docker:
      - image: circleci/ruby
    steps:
      - checkout
      - run: gem install -N git-pr-release
      - run: git-pr-release --no-fetch
workflows:
  version: 2.1
  works:
    jobs:
      - build
      - pr-release:
          filters:
            branches:
              only: develop

workflowのfilters設定で、developでしか動かないように設定しているのもポイントで、これで、developが進んだときだけgit-pr-releaseが再実行されることになる。そして、git-pr-releaseは再実行してもいい感じに既存のpull requestをアップデートしてくれるのです。

ということでこれだけで、developにfeatureブランチをマージしていけばCircleCIが自動的にpr-releaseを生やしてくれるようになりました。開発者の手元にgit-pr-releaseをインストールする必要もなくなりました。めでたしめでたし。

おまけ: machine accountとTriage権限の話

個人のtokenを使うの怖いし、退職リスク等もあるので、アクセスできるrepositoryや権限を絞ったユーザーを自動化のためにオーガニゼーションに一つ作るとよいでしょう。

これは、GitHubの利用規約に、machine accountという名前で定義されています。自動化のためのアカウントを1フリーユーザー辺り1つ作ってもよく、これを複数人でメンテしても良いとされています。

A machine account is an Account set up by an individual human who accepts the Terms on behalf of the Account, provides a valid email address, and is responsible for its actions. A machine account is used exclusively for performing automated tasks. Multiple users may direct the actions of a machine account, but the owner of the Account is ultimately responsible for the machine's actions. You may maintain no more than one free machine account in addition to your free User Account.

Triage(トリアージ)権限

machine accountには必要以上の権限を与えるべきではなく、一般的にはrepositoryのRead権限のみを与えることになるでしょう。

しかし今回の、git-pr-releaseを実行させる場合、pull requestを作成しそれにラベルを付与します。しかし、ラベルの付与はRead権限のみではできません。

ここで、最近ベータで追加されたTriage権限が有用です。これはRead権限に加えて、issueやpull requestの操作が可能になっているものです。

ref. https://help.github.com/en/articles/repository-permission-levels-for-an-organization

とう言うことで、今回の場合、machineアカウントに対象repositoryへのTriage権限を付与すれば万事解決です。

ニッチな権限でありますが、このようにうまく嵌るところが見つかると面白いですね。

ちなみに、machine accountは複数のprivate repositoryにアクセスが必要なCI/CDを構築する際にも効果を発揮するのでその辺りはまた今度。

「コンテナ物語」は破壊的イノベーションと人間たちの物語だった

これは、ITエンジニアが真っ先に想起するアプリケーションコンテナではなくて「物理コンテナ」の物語です。以前、コンテナに関するイベントに登壇させていただいた際に読んでいたのですが、やっとこさエントリを書きます。

この本について

この本には、コンテナが「20世紀最大の発明」と言われるくらい、如何に革新的であったか、どのように普及していったかが書かれている。これは是非ITエンジニアに読んで欲しい。今のアプリケーションコンテナとの類似点が非常に多いからだ。原題は「The Box」であるが、日本語訳の「コンテナ物語」は僕らにとってはナイス訳だと言わざるを得ない。

コンテナが発明され、世界の船やトラック、鉄道のコンテナ運送が共通化される中で、どのような変化が起こったかについて、具体的には以下のようなことが書かれている。

マルコム・マクリーンというビジョナリー

まず、この本は裸一貫で運送会社を立ち上げたマルコム・マクリーンというビジョナリーがコンテナ輸送を提唱し、その行動力を持って普及させたことについて書かれている。

早く新事業に乗り出したくてうずうずしているマルコム・マクリーンは、なんとかアイデアを実現する方法をみつけるよう部下をせかす。

冲仲士という専門職能団

当時の技術職である沖仲士(港湾労働者)の描写も面白い。当時の沖仲士が会社へのエンゲージメントよりも横のつながりが強かったことは、2000年代のWebエンジニアを彷彿とさせる。

港湾労働の特殊性から、波止場には独特の文化が生まれた。一つの会社のために働く仲仕はまずいないから、彼らの忠誠心は仲間とともにあり、会社に義理立てする者はいなかった。自分の仕事ぶりを知っているのも、気にかけているのも、仲間だけだと男たちは考えていた。労働は苛酷で危険である。そのつらさは他人にはわからない。だから、労働者同士の団結は強かった。

大都市の他の産業と比べ、港湾荷役業では労働者階級特有のコミュニティが発達した

そんな彼らが、コンテナという破壊的イノベーションに対してどのような抵抗を示し、どのように受け入れていったかといったこともこの本には書かれている。

コンテナという「システム」

コンテナは単なる箱ではなくてシステムとなって本領を発揮した。

輸送コストの圧縮に必要なのは単に金属製の箱ではなく、貨物を扱う新しいシステムなのだ

単なる箱だけではなく、港や船、クレーン、制御ソフト、それらがあってこそ、コンテナの本領が発揮される。そこには標準化の物語があり、その中でどのような思惑の交錯があったのか、アメリカとヨーロッパでの思惑の違いについてもこの本には書かれている。

このあたりはKubernetes周りのエコシステムの発展を思い描きながら読んだ。ここでちょっと面白かったのは「コンテナ化して盗難が減った」という話。「隔離性」の重要性が物理コンテナでも在ったのだ。

隆盛と衰退

船会社はコンテナに移行するための巨額の費用を負担して青息吐息になり、ほんの一握りしか生き残れなかった

コンテナリゼーションは世界の港のパワーバランスを一変させた。コンテナ対応が早かった港が一気に成長することとなり、逆にニューヨーク港など隆盛を誇った港が衰退した。

例えば、湾の奥の港はコンテナ以前は工場の近くまで物資を運ぶために大いにメリットがあったが、コンテナ時代になると、無理して湾の奥まで行かなくても、手前の付けやすい港に停泊して、ハイウェイでコンテナを運んでしまえば良くなった。わざわざ湾の奥の港を使う必要がなくなったし、コンテナ化により、船が巨大化し、喫水の関係上、浅い港には停泊できなくなったというのもあった。

パラダイムシフトで情勢が一変したことが書かれていて、インパクトが強い。

アジャイルの萌芽

このような可能性が最初に注目されたのは、一九八〇年代初めのことだった。日本のトヨタ自動車が開発したジャストインタイム方式である

この本はアジャイルについては書かれていないが「コンテナの未来」という章でトヨタについて書かれている。

コンテナにより、輸送コストが下がり、リードタイムも短くなった。これにより、部品などの中間財の在庫を抱えずに、リアルタイムに不足を補うようになっていた。これにより、世界の物流の半分以上を中間財が占めるようになった。

これが「ジャストインタイム」方式である。コンテナによりリードタイムのボトルネックが解消されたからこそ、ジャストインタイムが生まれ、トヨタが躍進したのである。

八七年には、フォーチュン五〇〇にランクされる米企業の五分の二がジャストインタイムを採り入れている。そして採り入れた企業の大多数が、これまでとは全然ちがう輸送方式に切り替えなければだめだと気づいた

「トヨタ生産方式」は僕らアジャイル開発者にとっては必読の一冊である。口酸っぱく「作り過ぎの無駄」について書かれている。そうしないようにジャストインタイム方式が生まれ、それを生み出したものがリアルコンテナなのである。それがまたアジャイルの種になり、クラウドそしてアプリケーションコンテナに至り、インフラ調達のリードタイムを解消する革新的イノベーションにつながっていることは胸熱だと言わざるを得ない。

このエントリでだいぶ書いてしまったが、それでもこの「コンテナ物語」の面白さを損なうものではないので、ぜひ読んで欲しい。

Kibelaクライアントのkibelasyncを作った

https://github.com/Songmu/kibelasync

Natureでは、Kibelaをナレッジベースとして利用していますが、文章は書き慣れたエディタで書きたいという気持ちがあり、クライアントを作った。GraphQLの勉強にもなると思ったというのもある。

名前やコンセプトはblogsyncのオマージュです。手元にMarkdownをごそっと落としてきて、編集して更新するものです。

Install

% brew install Songmu/tap/homebrew
# or
% go get github.com/Songmu/kibelasync/cmd/kibelasync

https://github.com/Songmu/kibelasync/releases からバイナリ取得も可能です

Setup

KIBELA_TOKENKIBELA_TEAM環境変数を適宜設定してください。

Usage

記事を取得する

デフォルトでは ./notes 以下にごそっとMarkdownを取得します。これは KIBELA_DIR 環境変数や、-dir オプションで変更可能です。

また、取得時にMarkdownファイルのmtimeがその記事の最終更新日時にセットされます。

# 全件一括取得(ローカルファイルのほうが新しい場合は更新されない)
% kibelasync pull
# 全件一括取得(問答無用で全てのファイルを上書き・高速)
% kibelasync pull -full
# limit指定(更新日時が新しいものから指定件数取得)
% kibelasync pull -limit 10
# folder指定
% kibelasync pull -folder=議事録

記事を更新する

落としてきたMarkdownを編集後、そのファイルを引数に指定して、push サブコマンドを叩きます。

% kibelasync push notes/333.md

記事を作成する

サブコマンドpublishに標準入力、もしくはMarkdownファイルを引数に指定することで新規記事を作成できます。

% kibelasync publish --title=こんにちは /path/to/draft.md
% kibelasync publish --title=kibelasync
さてかきはじめるか…
^D
% echo "# こんにちは\nさようなら" | kibelasync publish

この時、何らかの形でのタイトル指定が必須です。Markdownの冒頭にMarkdownのh1要素がある場合、それがタイトルだとみなされます。

# 共同編集を有効にする
% kibelasync publish -co-edit /path/to/draft.md
# 記事投稿後、当該記事のMarkdownをサーバーから取得して保存する
% kibelasync publish -save /path/to/draft.md

-save オプションを利用した場合、元のMarkdownファイルは削除されます。

ということで

だいぶ便利だと思うのでご利用ください。開発にあたってはAPIの挙動に関してKibelaにいくつかフィードバックを送らせてもらい、爆速で対応してもらいました。ありがとうございました。

開発言語はGoですが、実装的に面白い点として、APIリミットに引っかからないように、http.Client.Transport をカスタムして過剰なリクエストを送らないように工夫しています。このあたりは、先日のOpen Go Friday in Fukuokaでお話させてもらいましたが、別記事にまとめるかも知れません。興味がある方は、client/ratelimit.go辺りの実装を見てみてください。

Go Conference'19 Summer in Fukuokaで登壇してきた

https://junkyard.song.mu/slides/gocon2019-fukuoka/#0

色々あってお声がけ頂き、登壇させてもらえることになったので、福岡に行ってきました。出張扱いで、出張費用も所属のNature Japan株式会社に出してもらいました。

話した内容は、Go Conference 2019 Springで20分で話した内容を、単なる再演にならないように内容を膨らませて40分で話した感じです。割と好評のようで良かったです。資料は口頭で説明した部分も多いので、トピックに分けて改めてブログ記事にしたいと思っています。

イベント全体を通して

スタッフの皆さまが少人数ながら的確に動いていて、何もストレスなく快適にイベントを過ごすことができました。すごく楽しかった。ビールや生ハム、スピーカーディナーも楽しませてもらいました。スポンサーの各社に感謝。

会場のFukuoka Growth Nextも小学校をリノベしたスタートアップ支援施設ということで、趣があって良かった。高島市長がいらっしゃったのはびっくりしました。市長のスピーチすごかった…。

交流とか

@k1LoW さんと初めてお話できたのが良かった。ツールを量産していることになんとなく一方的に共感を持っていて、実際、彼の発表でお話されていた「技芸のコードによるボトムアップ」という言葉には共感を感じることができた。

@pyama86さんには毎回福岡行くたびに勝手にお世話になっています。ありがとうございます。

@budougumi0617さんと話が盛り上がった。普段、東京でも交流がありますが、地方のカンファレンスで話すとなんか盛り上がったりして、そういう合宿的な非日常感は地方開催ならではというところ。

あとは、前夜に、メルカリさんの福岡オフィスにお邪魔してOpen Go Friday #5に参加させてもらったのですが、そこで期せずして@dragon3さんに二年ぶり(YAPC::Fukuoka以来!)にお会いできたのが嬉しかった。

その他福岡の皆様と色々交流させてもらいました。なんだかんだで福岡には年に一回くらい行っていて、今後も行きそうなのでまた機会でもよろしくお願いします。