id:nobuoka (@nobuoka)
株式会社はてな
2015-01-11 Jenkins ユーザ・カンファレンス 2015 東京
はてなにおける継続的デプロイメント
の現状と Docker の導入
id:nobuoka
Blog : http://vividcode.hatenablog.com/
Twitter : https://twitter.com/nobuoka
GitHub : https://github.com/nobuoka
● 2012 – 2014 : はてなブックマーク
– ブクマ本体、アプリ、Presso など
● 2014 – 現在 : 少年ジャンプルーキー
– WJ 編集部が運営するマンガ投稿サービス
● 株式会社はてな
● アプリケーションエンジニア
今日の話
1.はてなのサービス開発と Jenkins
● 全社的な話。
2.ジャンプルーキーの開発フローと Jenkins
● ジャンプルーキーのチームを例に開発フローを
紹介。
3.Docker コンテナによる確認環境と Jenkins
● 動作確認用の web アプリケーション。
● Jenkins 上で Docker ビルド。
はてなのサービス開発と
Jenkins
はてな全体におけるサービス開発と Jenkins との関わりについて紹介し
ます。
はてなのサービス
など
ブックマーク・ブログなど
● Perl
● はてなのサービス開発の主流
● コンパイルのプロセスは基本的に不要
– JS や CSS の静的ファイルの変換などはある
● テストの実行や静的ファイル生成、デプロイ
などで Jenkins を活用
● 少年ジャンプルーキーもこの流れ
– 後で詳しく
参考: ブログの開発プロセス
https://speakerdeck.com/shibayu36/hate
naburogutimufalsekai-fa-hurotogithub
https://speakerdeck.com/onishi/hatena
-blog-development-flow
Mackerel
● サーバー管理サービス
● 2014 年リリース
● Scala
● サーバーサイドのアプリケーションを動かす
ためにコンパイルが必要
– これまでのはてなのサービスとは違って
Jenkins でコンパイルをしたりしてる
Mackerel
参考: Mackerel の話 (Jenkins の話も少し)
https://speakerdeck.com/hakobe/scala-in-perl-company
各サービスと Jenkins
● それぞれのサービスで Jenkins を活用
● 昔: master/slave 構成の Jenkins 1 組
● → 最近はサービスごとに Jenkins を用意
● サービスごとに異なる環境
– あるサービス用に変更を加えると別のサービス
のテストが動かなくなる、とか
● 関係者が少ない方がメンテナンスしやすい
なんのための Jenkins か
● ソフトウェア進化を継続するため
– はてなダイアリーは 10 年超
– はてなブックマークは 10 年
● テスト、ビルド、デプロイ
● 意識しなくてもテストが実行される環境
● 面倒な手順の自動化
特にテスト
● 開発時は各自のマシンで
– OS X、Linux、Windows
● 開発者のマシン上ではテストに通っても実際
の環境では動かない可能性
● → 本番に近い環境でのテスト実行
● 面倒なのでいちいち全テスト実行しない
● → 自動でのテスト実行
Jenkins の管理
● Chef で Jenkins の環境を構築
– 本番サーバーも Chef で構築
● Jenkins 自体だけでなく、各プロジェクトのビ
ルドやテストに必要なコマンドやホストの設
定も Chef で行う
● 本番サーバーと同等の環境を実現
Jenkins 使用の方針
● Jenkins の設定を複雑にしない
– 秘伝のタレ問題
– コマンド一つで処理を実行できるように
– 環境準備とかも
● 例 : 処理の内容は Shell スクリプトファイル
に記述してプロジェクトのリポジトリに入れる
スマートフォンアプリと Jenkins
● Android アプリ → Gradle が標準になりビ
ルドやテストの自動化がしやすく
● Jenkins 上でのテスト実行
– Android Emulator Plugin
– Android SDK は用意してくれる
– 必要なコンポーネントは? → Gradle プラグイ
ンでインストール可能!
● iOS アプリも含め、これからという段階
ジャンプルーキーの開発プロセスと
Jenkins
はてなのサービス開発プロセスの中での Jenkins の役割を
具体的に紹介。
少年ジャンプルーキー
● ユーザーによるマンガ投稿・公開サービス
● 2014 年 11 月末にリリース
● 少年ジャンプ編集部
● 漫画雑誌アプリ 「少年ジャンプ+」 内
少年ジャンプルーキー
● ユーザーによるマンガ投稿・公開サービス
● 2014 年 11 月末にリリース
● 少年ジャンプ編集部
● 漫画雑誌アプリ 「少年ジャンプ+」 内
2014 年末
少年ジャンプルーキーの開発・運用
● 開発・運用 : はてな
● ディレクター 1 人、デザイナ 1 人
● エンジニア数名 (開発・運用)
● 最近のはてなでの開発・運用を踏襲・改善
サーバーサイド
● 言語: Perl
– ビルドプロセスは不要
● データストレージ
– MySQL
– Redis
– Amazon S3
フロントエンド
● HTML は生
● TypeScript
– TypeScript → JS → minified JS
– ビルドプロセスが必要 (開発者の手元 + Jenkins)
● LESS
– LESS → CSS
– ビルドプロセスが必要 (開発者の手元)
ビルドツールやデプロイツール
● ビルドツール: gulp (Node.js)
– TS → JS や LESS → CSS
– JS テスト実行
– 静的ファイルにダイジェストハッシュ付与
● デプロイ: Capistrano 3 (Ruby)
● バージョン管理: Git
– 中央リポジトリは GitHub;Enterprise
開発に用いるツール
● はてなグループ : 日記 + Wiki システム
– ドキュメント管理
● Slack : チャットツール
● Trello : タスク管理
● GitHub;Enterprise : コード管理 (レビュー
等)
● Jenkins
開発プロセス概要
● スクラム、2 週間 1 スプリント
● リリースは毎週
– 自社サービスではないのでどんどん出すという
感じではない
– だが、常に最新の機能をリリースできる状態を
保つ
● 今回はタスク管理などにはあまり触れずに
開発者目線で
master
staging
devel
features
ブランチモデル
リリース
ブランチモデル
● master ブランチ
– 本番反映対象
● staging 系列ブランチ (例: staging-20150101-000000)
– 本番反映ごとに別ブランチ名で毎回作成
● devel ブランチ
– リリース可能なものをマージ
● features
– 開発ブランチ
ブランチモデル
● Git-flow のブランチモデル
● devel ブランチは常にリリース可能な状態に
保つ
● リリース済みの機能の緊急修正は master
ブランチからブランチを切って行う
新機能開発、バグ修正
master
staging
devel
features
ブランチを作成して開発開始
pull request を作成
● 開発中に立てるかどうかは任意
– コードレビュー時には必須
● 開発中の様子が見えやすい
● 開発中のコードについて議論しやすい
● Jenkins によるテストの結果の可視化
開発時の Jenkins の役割
● Push されるごとにライブラリ更新、JS minify
– 必要な場合のみ
– 結果をコミット
● Push されるごとにテストを実行
– 失敗時には通知する
– Slack、GH;E
● 動作確認のために開発用ホストにデプロイ
(Docker 使用、後述)
Push されるごとにビルド実行
● Jenkins の GitHub Plugin
● GH;E の Server Hook
● 処理内容は script/jenkins.sh に記述
– Static ファイルの変換処理 → コミット、or
– テスト実行 (Perl、JS)
GH;E のコミットへの通知
● Shell スクリプトの中に記述
– ブクマなどでは通知用ジョブを下流に用意
curl -X POST -H "Authorization: token $token" 
https://github-
enterprise.example.com/api/v3/repos/example/Exa
mple-Project/statuses/$git_head -d "{ 
"state": "success", 
"target_url": "${BUILD_URL}", 
"description": "The build has succeeded." 
}"
Slack への通知
● Slack の Integrations
● Jenkins の Slack Notification Plugin
良いところと課題
● ○ push したら自動でテスト実行
● ○ 失敗時の通知がされる
– × Slack と GH;E だけなので弱い → XFD?
– × GH;E への通知がビルド処理の中にある
● ○ ファイルの変更結果をコミット
● × ファイル変更とテスト実行が同じ Shell ス
クリプトになっていて管理しづらい
● ○ コマンド 1 つで確認用にデプロイ
レビューとマージ
開発が完了したらコードレビュー
● Pull request 上でコメントを残してやりとり
● 問題がある箇所は修正・変更
– ここでの Jenkins の役割は開発時と同じ
● レビュアにとってテスト結果が GH;E 上で確
認できるのが便利
– テストに通っているかどうかすぐにわかる
レビュー後 devel ブランチにマージ
● Pull request のマージボタンでマージ
● 先にコンフリクト解消が強制されるのでコン
フリクト解消ミスをしづらい
● テスト失敗時にわかりやすい
master
staging
devel
features
レビュー後 devel ブランチにマージ
良いところ
● ○ レビュアがテスト結果を確認しやすい
● ○ テストに落ちているのに気付かずマージ
ということがない
– devel ブランチをリリース可能な状態に保てる
● ○ マージボタン強制によりコンフリクト解消
のミスをしづらい
デプロイ
本番リリース用 pull request 作成
● Staging 系列のブランチを作成して master
ブランチに向けた pull request を作成
– ブランチ作成、pull request 作成をコマンド 1
つで実行
● Staging 系列ブランチを staging 環境にデ
プロイして動作確認
– デプロイは Capistrano 3
master
staging
devel
features
本番リリース用 pull request 作成
本番リリース用 pull request 作成
Staging の動作確認後にリリース
● 本番リリース用 pull request のマージボタ
ンで master ブランチにマージ
● Capistrano 3 で本番デプロイ
master
staging
devel
features
Staging の動作確認後にリリース
良いところと課題
● ○ 本番リリース用 pull request をコマンド
1 つで作成できる
● ○ デプロイもコマンド 1 つ
● × staging 環境へのデプロイが自動化され
ていない
まとめと今後
少年ジャンプルーキーの開発プロセス
● Jenkins、GH;E の活用、連携
– テスト (インスペクション含) の自動実行でコー
ド品質を保つ
– ファイル変更処理
● デプロイは少ないコマンドで完了
– Jenkins は使っていない
● 継続的なソフトウェア進化の礎
– 改善し続けることが大事
今後: Jenkins Workflow Plugin 検討
● 2014 年 12 月にバージョン 1.0 リリース
● Scripted control flow
– Shell スクリプトに書いてあることを Workflow
Plugin のスクリプトに置きかえる
● Pause and resume execution
– ユーザーとのインタラクションの機能もあるので
デプロイ処理を Jenkins に乗せやすい
Docker コンテナによる確認環境と
Jenkins
開発中の機能を確認するための環境を準備する方法の紹介。
たとえばこんなこと
D
E
作品をお気に入りする機能を
作るで!
あれが云々これが云々や。
機能開発して
よっしゃ、作るで!
……
できたで!
D
ほんなら次回リリースするで!
D
E
作品のお気に入り機能、ここ
がこうなってこうやと思ってた
んやけどなー。
リリース直前に初めて動作確認
リリース前の動作確認や!!
E なんやて!!
なんてことはさすがにないと思うが
開発した機能の確認が遅れると困る
● 無駄な手戻り
● 開発スピードの低下
● 特に UI/UX 周り
● 開発中の機能を確認できる環境は便利
– チーム内 : 動作を見ながら開発を進める
– ステークホルダーによる確認
目的
開発中の機能の動作や UI/UX を
確認するための Web アプリケー
ションを手軽に動かしたい
社内では devhost と呼んでいる
master
staging
devel
}features
ココが
対象
対象とするブランチ
前提として
● 開発者自身はローカルホスト上でアプリケー
ションを動かして開発
● Devhost は誰のために?
– チーム内のメンバー (非開発者・遠隔地)
– チーム外
● → 社外からも見えるように (状況次第だが)
● ブランチ名に対応するドメイン名
● ストレージは開発用に 1 つ
https://{branch-name}.dev.example.com/
既存の方法 (はてなブックマークなど)
● 開発用サーバで複数アプリケーション起動
● 別ポート番号を使用 (or Unix ドメインソケット)
● ポート番号解決 : Nginx 上の Lua で
https://{branch-name}.dev.example.com/ → {ポート 番号}
開発用 proxy
Nginx
開発用サーバー
Nginx
アプリケーション
アプリケーション
アプリケーション
ポート番号
解決
課題
● 同じ環境を複数アプリケーションが使用する
ので面倒だったり問題が起こったり
– ファイルシステム、ライブラリ等
● ブランチ名とポート番号の対応付けが面倒
– ファイルを使って管理するなど
そこで Docker Engine の登場
Docker Engine とは?
● コンテナ型の仮想化技術
– 環境を分離するという目的に適す
● ゲストの状態を Docker イメージとしてバー
ジョン管理
● Dockerfile にビルド処理を記述して
Docker イメージをビルド
● Docker イメージをもとに Docker コンテナ
内でアプリケーションを動作させる
コンテナ型の仮想化
物理マシン
ハイパーバイザ
仮想マシン
ゲスト OS
● ユーザー空間を分けてリソースを制限する
– ファイルシステム、プロセス、など
物理マシン
Kernel
ユーザー
空間
ユーザー
空間
コンテナ型の仮想化完全仮想化
Docker イメージとコンテナ
Dockerfile
Docker イメージ
docker build
Docker コンテナ
Docker コンテナ
docker run
コンテナが生成され
中でプロセスが動く
Docker コンテナを用いた確認環境
● 開発用サーバで複数 Docker コンテナ起動
● 別ポート番号を使用
● ポート番号解決 : Docker API を使用
https://{branch-name}.dev.example.com/ → {ポート 番号}
開発用 proxy
Nginx
開発用サーバー
Proxy 用 Plack
アプリケーション
Docker コンテナ
Docker コンテナ
Docker コンテナ
Docker API
ポート番号解決ポート番号解決
Docker API を用いたポート番号解決
● ホスト側のポート番号が適当に割り当て
– コンテナ内の web アプリケーションは 80 番
ポートをリッスン
– Docker コンテナは 80 番を EXPOSE
● 各ブランチからイメージをビルドする際、ブラ
ンチ名に対応したタグをイメージに付ける
● ホスト名からブランチ名を抽出 → Docker
API により対応するタグ名のイメージのコン
テナを探す → ポート番号取得
Docker API を叩くコードの例
my $furl = Furl->new();
my $uri = do {
local $_ = $docker_remote_api->clone;
$_->path('/containers/json');
$_;
};
my $body = decode_json $furl->get($uri)->content };
(grep { $_->{Image} eq "$host:latest" } @$body)[0];
Docker コンテナの生成
Dockerfile を準備して docker build
● ライブラリ等インストール → プロジェクトの
ファイルコピー → コマンド登録
FROM debian:stable
# (略)
# config/setup.sh は本番サーバー構築時にも使用される初期化スクリプト。
COPY script/setup.sh /app/shared/script/setup.sh
RUN sh /app/shared/script/setup.sh
# (略)
# Source
RUN mkdir -p /app/src
COPY . /app/src
# (略)
WORKDIR /app/src
EXPOSE 80
CMD ["supervisord", "-c", "/app/src/config/docker/webapp/supervisord.conf"]
イメージビルドに時間がかかる問題
● debian:stable から最後までビルドすると
30 分程度かかる
– apt-get や Perl のインストール
– Perl ライブラリのインストール
イメージビルド時のキャッシュの活用
● Docker イメージのビルドにキャッシュが効く
● デフォルトで有効
● COPY すると (変更されてると) キャッシュ効
かない
● プロジェクト全体を COPY する前に、初期化
処理用のファイルだけを COPY
– それらのファイルが変更されていなければ初期
化処理のキャッシュが有効に!
# config/setup.sh は本番サーバー構築時にも使用される
# 初期化スクリプト。
COPY script/setup.sh /app/shared/script/setup.sh
RUN sh /app/shared/script/setup.sh
# CPAN Modules
RUN mkdir -p /app/shared/carton
WORKDIR /app/shared/carton
COPY cpanfile /app/shared/carton/cpanfile
COPY cpanfile.snapshot /app/shared/carton/cpanfile.snapshot
RUN carton install --deployment
キャッシュ効果でビルド時間短縮!!
Jenkins 上でのビルドとデプロイ
● Jenkins 上で
– docker build
– docker rm -f : 同じブランチのコンテナを止める
– docker run
● 実際のコマンドは rake タスクにしてある
– ブランチ名 → タグ名変換処理
– 同じブランチの古いコンテナが動いてるか調査
– Shell スクリプトより Ruby の方が書きやすい
Jenkins 上でのビルドとデプロイ
1. git clone
イメージ
2. docker build
コンテナ
3. docker rm / docker run
開発用サーバー
開発用サーバー内で完結
しているので単純
ビルド開始方法
● 現在は手動でビルド開始
– Jenkins API 叩く or Web UI から
– 対象ブランチをパラメータで受け取り
● 本来は次のようにしたい
– devel ブランチへの push で自動的にビルド
– 既に devhost が立っているブランチへの push
で自動的にビルド
対象ブランチのパラメータ化
POST /job/{job-name}/buildWithParameters?branch_name={branch-name}
なぜ Jenkins でビルドするのか
● 同じ環境でビルド処理を行わせたい
● ビルドの管理
● 変遷
– Capistrano などで直接開発用サーバー上で
ビルド → 複数人が同時に使うと破滅
– Docker API を使う → キャッシュが効きづらく
て困る
今後
● devel ブランチの自動デプロイ
● Pull request との関連付けを強める
– コメントで新規 devhost 作成 (?)
– close 時に自動で devhost 破棄
– 破棄が結構面倒なので自動化したい
● ビルド後にコンテナ内でテストしてそれから
デプロイ
小ネタ: 確認環境用の favicon
● ローカルホスト上の web アプリケーションや
確認用の web アプリケーション
● 本番と確認用を間違えたりしてしまう
● Favicon を変えることでタブで識別しやすく
小ネタ: 確認環境用の favicon
まとめ
はてなにおける Jenkins
● GH;E への push に応じたテスト自動実行
や確認用ホストへのデプロイなど
● 開発プロセスの中でビルドやテストの実行を
指揮する大切な役割を果たしている
– 少年ジャンプルーキーでの例を紹介
● Jenkins で Docker ビルドしてデプロイする
という仕組みで手軽に確認環境を用意
We are hiring!

はてなにおける継続的デプロイメントの現状と Docker の導入