突撃!隣のリリースエンジニアリング〜Mackerelさんのお宅編〜

# はじめに

[今作っているプロダクト](https://github.com/sacloud/usacloud)に、ちゃちゃっとTravisCI環境を導入したくなりました。

そこで~~パクる~~参考にするため、すでにTravisCIを利用しているプロダクトとして
[mackerel-agent](https://github.com/mackerelio/mackerel-agent)のリリースエンジニアリング周りのソースを読んでみたのでメモを残しておきます。

## 対象ソース/扱う範囲

この記事では執筆時点のmackerel-agent最新バージョンである[v0.41.1](https://github.com/mackerelio/mackerel-agent/tree/v0.41.1)を題材にしています。

取り上げる範囲としては、主にTravisCI周りだけです。
実際にはTravisCIだけでなくAppVeyorを使っていたり、表に出てこないバックエンドのプロセス(例えばChatOps用のボットとか)などがあると思いますので、ここで取り上げるプロセスはリリースエンジニアリングの一部のみとなります。

また、リリース周りはコードが陳腐化するのが早いですので、当記事を参考にする際は最新のソースも合わせて読むことをオススメします。

# Let's read リリースエンジニアリング!

## 全体像

まずはTravisCIの設定ファイルである`.travis.yml`から読んでみます。

https://github.com/mackerelio/mackerel-agent/blob/v0.41.0/.travis.yml

以下のようなことを行うようになっていました。

  - lint/コード整形/テスト/カバレッジ測定/ビルドなど
  - ビルドした成果物をrpm/debにパッケージング(dockerを利用)
  - リリース用プルリクエストの作成
  - GitHub Releaseへのアップロード(ステージング/本番リリース)
  - 結果をSlackで通知

順番に見ていきます。

## lint/コード整形/テスト/カバレッジ測定/ビルドなど

ビルド周り/テストなどは以下の記述がされています。
単純な記述なため、見るだけである程度処理の予測がつくかと思います。

```yaml:.travis.yml(ビルド/テスト周り抜粋)
language: go
go:
- 1.7
script:
- make lint
- make cover
- test `gofmt -l . | wc -l` = 0
- make crossbuild
after_script:
- goveralls -coverprofile=.profile.cov
```

`golang:1.7`でビルドするために、`language: go`と`go:`の記述がされています。
`script:`にlintやカバレッジ測定などの実際のテスト/ビルド処理などが書かれています。

`after_script:`はカバレッジ測定用です。詳細は以下のサイトに載っていました。

[参考：複数パッケージ構成のgolangのプロジェクトのカバレッジを測定しcoverallsに投稿する](
http://www.songmu.jp/riji/entry/2015-01-15-goveralls-multi-package.html)

## ビルドした成果物をrpm/debにパッケージング(dockerを利用)

続いてrpm/debの作成です。`.travis.yml`では以下の部分です。

```yaml:.travis.yml(パッケージング周り抜粋)
before_deploy:
- docker pull astj/mackerel-rpm-builder:c5
- docker pull astj/mackerel-rpm-builder:c7
- make rpm deb rpm-kcps deb-kcps rpm-stage deb-stage tgz
##以下デプロイ周りの処理が続く##
```

パッケージングの処理は`before_deploy:`以下に記述されています。
RPMのビルドで使うdockerイメージをpullした上で`make rpm deb(以下略)`を実行するようになっています。

dockerイメージは`rpmbuild`コマンドをエントリーポイントとしたもので、これでrpmの作成を行なっています。debについてはdockerではなく直接`debuild`コマンドを実行しています。

なお、TravisCI上でdockerを利用するためには以下の記述が必要です。(もちろんmackerel-agentの`.travis.yml`にも記載されています)

```yaml:.travis.yml(dockerを使う設定抜粋)
sudo: required
services:
- docker
```

## リリース用プルリクエストの作成

特定の名前(`bump-version-x.y.z`(x.y.zはバージョン番号))のブランチをGitHubへpushすることでリリース用のプルリクエストを自動生成するようになっています。

TravisCI上でGitHubの履歴を参照し、前回のリリース以降にマージしたプルリクエストの情報などを以下のCHANGELOGに埋め込む処理を行なっています。

  - プロジェクト全体の[CHANGELOG](https://github.com/mackerelio/mackerel-agent/blob/v0.41.1/CHANGELOG.md)
  - rpmの[.specファイル](https://github.com/mackerelio/mackerel-agent/blob/v0.41.1/packaging/rpm/mackerel-agent.spec)のCHANGELOG
  - debの中の[changelog](https://github.com/mackerelio/mackerel-agent/blob/v0.41.1/packaging/deb/debian/changelog)

これらのファイルの変更をpushされた`bump-version-x.y.z`ブランチにコミットした上で
github用のCLIツール`hub`を用いてプルリクエストを作成しています。

`.travis.yml`では以下の部分です。
色々細かなことをやっているため、コメント(風)の説明を入れてみました。

```yaml:.travis.yml(リリース用PR作成部分の抜粋)
before_deploy:
##省略:前述のパッケージング処理##

# GitHubの操作用コマンドのソース取得 & ビルド
- go get github.com/aktau/github-release
- mkdir -p ~/bin
- git clone https://github.com/github/hub.git && cd hub && script/build -o ~/bin/hub && cd ..

# CHANGELOGの作成などで利用するために、ビルド対象以外のブランチの情報も取得
- echo $TRAVIS_BRANCH
- git config --add remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'
- git fetch
- git checkout master || git checkout -b master origin/master
- git checkout $TRAVIS_BRANCH
- git fetch --depth=1000
- git fetch --tags --depth=1000 || true

deploy:

## 省略：GitHub Releaseへのアップロード処理(後述します)

# リリース用プルリクエストの作成処理
- provider: script
  script: _tools/releng --task=create-pullrequest --current-branch=$TRAVIS_BRANCH -v
  skip_cleanup: true
  on:
    all_branches: true
    condition: "$TRAVIS_BRANCH =~ ^bump-version-.*$"
```

`before_deploy:`で`hub`コマンドのインストールなどの準備作業を行った上で、
`deploy:`に書かれた作業を行うようになっています。

`deploy:`では`provider: script`と記載することで、任意のスクリプトファイルを実行するようにしています。ここでは`_tools/releng`というスクリプトを実行しています。
また、`on:`以下に条件を書いておくことで、`bump-version-x.y.z`という名前のブランチの場合のみ処理が行われるように制限を行なっています。

## デプロイ用スクリプト`_tools/releng`

https://github.com/mackerelio/mackerel-agent/blob/v0.41.1/_tools/releng

プルリクエスト作成やGitHub Releaseへのアップロードを行うためのperlで書かれたスクリプトです。
`hub`コマンドや`git`コマンド、`github-release`コマンドを呼び出しています。

**ポイント**

CHANGELOG変更分をcommit/pushしたりプルリクエストを作成するにはGitHubのアクセストークンが必要です。
アクセストークンは`.travis.yml`に暗号化された環境変数として設定しておくことでスクリプトに渡しています。

```yaml:.travis.yml(GitHubアクセストークン(など)の環境変数設定部)
env:
  global:
  - secure: "B34YsRebWxyx/UAHNAL5YWOgx2RE13TVfDC8FCCc7gAlogJcyAcPnC88fe8ECChHQC3GNnsHSi+GqgxTxzfoVkV+LUAFgHWbcF5O4O/4qHztKj7//72RwO+gXVr4U1Laz45bBr2r68XW0llj5tIlaEYxROJNLSAVDE4yDzmAHgc="
```

この値はTravisCIのCLIツールである`travis`コマンドで作成できるものです。
(今回は`travis`コマンドについての説明は割愛)

この設定を行っておくことで、deployスクリプトの中から環境変数を参照可能です。
(以下の例では環境変数`GITHUB_TOKEN`)として渡しています。

```pl:_tools/relengのトークン利用部
git_without_stderr qw/push -u/, "https://$ENV{GITHUB_TOKEN}\@github.com/$REPO_NAME.git";
```

なお、GitHubのトークンは漏れるとやばい情報です。
(今回の場合、このトークンがあればリポジトリにpushできちゃいます、、怖い。。。)

トークン利用時にTravisCIのログに表示されないように、標準出力/エラーのリダイレクトなど適切な処理を行うことを忘れないようにしましょう。

参考：[Travis CIのテストの中でGitHubのレポジトリへpushする](https://rcmdnk.com/blog/2014/10/09/computer-github-travisci/#quietが必須もしレポジトリが存在しない可能性があるならエラーも出力をさせない)

## GitHub Releaseへのアップロード(ステージング/本番リリース)

リリース用のプルリクエストがマージされたらGitHub Releaseへバイナリやrpm/debなどのパッケージファイルがアップロードされます。

これは`.travis.yml`の以下の記述で実現しています。


```yaml:.travis.yml(GitHub_Releaseへのアップロード部分抜粋)
deploy:
- provider: script
  script: _tools/releng --task=upload-to-github-release -v && _tools/releng --task=upload-master-to-github-release -v
  skip_cleanup: true
  on:
    branch: master
```

`on:`に`branch: master`という記述を行なっていますので、masterブランチに変更があった場合にこの処理が行われます。
運用ルールとしてmasterへの直接コミットを禁止しておくことで、プルリクエストのマージ時にのみこの処理が行われることになります。

ここでのデプロイスクリプトの動きとしては、

  - 1) GitHub上に現在のバージョンのリリースがなければ作成 & ファイルアップロード
  - 2) `staging`リリースへファイルアップロード

となっています。
新バージョンのリリース以外でmasterにマージされたコミットは`staging`リリースにアップロードされていくということですね。

## slackへの通知

特に説明することはないでしょう。見たままです。

```yaml:.travis.yml(slack通知設定部分の抜粋)
notifications:
  slack:
    secure: n9Hs5rIlb6MuFVsRp46ykd+CptnE3boI7Q9mNLuHwz77IQ/2mpUmOyBRDeW3BA2EXa4WEYAXu8q7/b7zVV+c+RcPdz8/h9N9EBrHUVeIkCP/Nyp+aUA8GvLNjGuxYC7nov3L+9GHBFXKX8FaV2lZ1/Eeb1Ovbtv6Oi39xNkHqTQ=
```

## おまけ：バイナリへのバージョン情報埋め込み

デプロイで利用しているブランチ名`bump-version-x.y.z`を利用して、goのバイナリのビルド時にバージョン情報を埋め込む処理をしてたりします。

これは、

  - Makefile内でgitのログを検索&加工してバージョン情報を取得
  - 取得したバージョンを`go build`時のオプション`-ldflags`に指定

することで実現しています。

```Makefile:Makefile抜粋
CURRENT_VERSION = $(shell git log --merges --oneline | perl -ne 'if(m/^.+Merge pull request \#[0-9]+ from .+\/bump-version-([0-9\.]+)/){print $$1;exit}')


BUILD_LDFLAGS = "\
	  -X github.com/mackerelio/mackerel-agent/version.GITCOMMIT=`git rev-parse --short HEAD` \
	  -X github.com/mackerelio/mackerel-agent/version.VERSION=$(CURRENT_VERSION) \
	  -X github.com/mackerelio/mackerel-agent/config.agentName=$(MACKEREL_AGENT_NAME) \
	  -X github.com/mackerelio/mackerel-agent/config.apibase=$(MACKEREL_API_BASE)"
```

定義を１箇所にまとめるテクニックの好例ですね。

# 終わりに

ざっと`.travis.yml`を中心に見てきました。

リリースプロセスとしてはおそらくバージョンアップ時のブランチ(`bump-version-x.y.z`)作成などにボットなどを利用してChatOpsしてたりするのだと思います。

リリースエンジニアリングには各開発者(会社/コミュニティ)の個性が出る部分だと思いますので、
読んでいくのは楽しいものです。
機会があれば別のプロダクトについても読んでみたいと思います。

以上です。Enjoy!