スペックを上げて...
TRANSCRIPT
スペックを上げて クラウドで殴るCI
2019.3.5
pixiv.inc sue445
• HN: sue445
• 戸籍ネーム: Go Sueyoshi
• 2018年7月 ピクシブ入社
‣ 今回の登壇者の中では比較的新参者
• インフラ部
• フルスタックキュアエンジニア
‣ プリキュアのカバレッジ100%継続中
go version(自己紹介)
• SUE ≒ SRE
• SREに似てる仕事をやってる
go version(自己紹介)
僕のピクシブでのミッション
「全ての手作業を自動化する」
僕のピクシブでのミッション
• 機械に任せられることは機械に全部任せて、人間しかできないことを人間がやるべき
• 現状はまだほど遠いのでまずはCIで会社をハックしている
• 「働かないために全力で働いている」(?)
「全ての手作業を自動化する」
• ピクシブのCI事情と現状のCIの問題点
• どうやって改善したか
• ピクシブならではの工夫点や苦労点
今日話すこと
• CIはレベル(スペック)を上げて物理(クラウド)で殴るのが大正義
• SaaSもいいけど自前でやるのも運用経験値が貯まるのでおすすめ
‣ ただし万人におすすめはしない
最初にまとめ
• 大前提
‣ 歴史的経緯によりGitHub.comとGitLab(オンプレ)が両方使われている
ピクシブのCI事情
• GitHub.com
‣ Rails系はGitHubを使っている傾向
‣ 例)BOOTH, pixivFACTORY, pixiv PAY, pixivコミック
‣ CI: だいたい全部CircleCIを使ってる
ピクシブのCI事情
• GitLab(オンプレ)
‣ pixiv本体(いわゆるみんながよく知ってるpixiv)、pixiv本体と密結合している周辺サービス、VRoid Studio
‣ CI: GitLab CI, Jenkins
‣ 今日はGitLab CIの話がメインです
ピクシブのCI事情
• その他(リポジトリ非依存)
‣ iOS/AndroidアプリのCIにBitriseを利用
‣ 詳しいこと:モバイルアプリのCIをBitriseにして1年が経ちました - pixiv inside
- https://inside.pixiv.blog/kwzr/6190
ピクシブのCI事情
• VRoid Studio(Unity)でビルドの時間がかかっていた
‣ ビルド1回100分(!)
‣ ハイスペックなGitLab Runnerがほしいという要望
‣ せっかくなのでVRoid Studio専用でなく、社内GitLabの全体で使えるRunnerにした
‣ CIの改善結果、みんなの生産性はバク上がりになって幸せになってます
CIの問題点
• Before
‣ MacMini
‣ 直列で100分
劇的ビフォーアフター
• Before
‣ MacMini
‣ 直列で100分
• After
‣ EC2 c5.2xlargeインスタンス(vCPU 8, メモリ16GiB)
‣ 3並列で25分
劇的ビフォーアフター
• 前半で5分、後半で各20分前後
• 100分 -> 25分なので4倍の生産性
劇的ビフォーアフター
でもお高いんでしょう?
AWS費用は月300ドルくらい
でもお高いんでしょう?
参考:1月分の費用
参考:1月分の費用
• https://docs.gitlab.com/runner/configuration/runner_autoscale_aws/
‣ AWSのスポットインスタンスとdocker machineを利用したオートスケールRunnerを構築するための公式ドキュメント
• https://www.m3tech.blog/entry/advent-calendar-2018-2
‣ M3さんのテックブログ。日本語の説明がむっちゃ詳しい
‣ 「GitLab Runner AWS spot instance」でググればいくらでも資料は出てくる
詳しいこと
• ググれば分かることについて話してもしょうがないので、ピクシブならではの工夫点、苦労点、運用知見などをババーンと紹介します!
ピクシブGitLabとAWS Runner
• これ見ても分からないと思うのでかいつまんで説明AWSのGitLab Runnerの全体像
• スポットインスタンス
• Docker Machine
• Ansible
• Terraform
• Packer
• Serverless Framework
主な登場人物
• 安く使えるEC2の余剰インスタンス
‣ 性能はオンデマンドインスタンス(スポットインスタンスじゃない普通のやつ)と全く同じで、価格は最大9割引
‣ 例)c5.2xlargeだとオンデマンドが0.428USD/時間、スポットが0.0779USD/時間前後
• スポットインスタンスの価格は変動するので、事前に設定した入札価格を超えると作れない
‣ 多少価格が上がっても入札エラーにならないような金額で入札するのがおすすめ
スポットインスタンス
• アカウントごとに作成できるスポットインスタンスの上限が決まってる(デフォルト20)
• スポットインスタンスを削除したらまた作成できるはずなのだが、たまに削除済のインスタンスが作成上限を圧迫することがある
‣ 僕はよく「スポットインスタンスのゴミ」と言ってる
• メインで使うインスタンスタイプは一度に立てるインスタンス数よりも多めに上限緩和申請をした方がいい。(ピクシブだと10並列に対して100個で上限緩和)
スポットインスタンス(注意点)
• リモートサーバ上にdockerコンテナを構築する仕組み
• Dockerコマンドの実行元とコンテナの作成先が別
• http://docs.docker.jp/machine/overview.html
Docker Machine
• 通常Dockerというとこっちを指す場合が多い(Dockerコマンドの実行元とコンテナの作成先が同じ)
‣ http://docs.docker.jp/machine/overview.html
比較用:Docker Engine
GitLab Runner + Docker Machine
GitLabRunner Docker
Machine
オンプレ環境
ec2
ec2
ec2
• この構成のメリット
‣ Docker Engine(サーバのローカルにコンテナを立てる)と違って、重いジョブが1つあった時に他のジョブに影響が無い
‣ ハイスペックなマシンをジョブ1つで専有できる
‣ Runnerがよしなにオートスケールしてくれるので、ジョブが積まれなければEC2は勝手に消えていく
GitLab Runner + Docker Machine
• サーバのプロビジョニングツール
• 既存のRunner(Docker Engine利用)のplaybookがあったので、新しくオートスケールRunner用の設定を作った
Ansible
• AWSなどのクラウドの構成管理ツール
• VPC, S3, IAMなどのプロビジョニングで利用
• GitLabのMergeRequestでterraform plan(dry run)をし、masterにマージされたらapply(本実行)するというインフラCIで利用している
Terraform
• サーバのイメージ(AWSならAMI)を作るためのツール
• 予めピクシブGitLab用の設定を入れてAMIを作成し(後述)、それをRunner
で使っている
Packer
• ローカル開発用にVagrant導入
• VagrantやPacker内でのプロビジョニングにはmitamaeを使い、プロビジョニングした内容をServerspecでテストをしている
• もちろんGitLab CIでCI/CDしている
• 詳しいことは全部 https://sue445.booth.pm/items/
1033989 に書いてる(ステマ)
‣ CircleCIをGitLab CIに変えた以外はサンプルリポジトリをほぼ丸パクリできてむっちゃ便利だった
Packer + Vagrant + mitamae + Serverspec
• (AWSだけに限って言えば)Lambdaとその周辺リソースをいい感じに管理してデプロイするためのツール
• TerraformでLambdaを管理するのは色々と大変なので、そこをラップしてくれているのが嬉しい
• 今回、AWSのモニタリングをLambda + Rubyで作って、Serverless Framework
で管理してる
Serverless Framework
• 起動してるインスタンス数とGitLab
CIのジョブ数をLambdaで集計してCloudWatchでいい感じに可視化している
• そのうちソースを公開したいがRubyKaigiの準備ががががが
Lambda + Ruby + Serverless Frameworkで作った監視ツール
• AWSからGitLabに直接アクセスできるようにした
• CIのジョブ間で大容量ファイルを受け渡せるようにした
• Runnerの起動時間チューニング
• スポットインスタンス枯渇対策
ピクシブならではの工夫点など
• 前提
‣ ピクシブGitLabはssh用のURLは社内DNSでしか解決できないので、AWSからだとアクセスできない
- 仮に名前解決できても社外からのアクセスを受けるGatewayサーバに別の認証が挟まってるのでアクセスできない
‣ httpのURLも社外からだとGoogleのOAuth認証が挟まるのでAWSからGitLabのAPIが叩けない
AWSからGitLabに直接接続できるようにした
• Runnerで使うAMIに予め /etc/hosts を焼いておき、社内DNSの名前解決ができるようにした
• ここでPackerを利用
解決策1: 名前解決済の /etc/hostsを焼く
• GatewayサーバのiptablesでAWSからの通信を許可するようにした
• そのままやるとEC2インスタンスの作り直しの度に送信元のIPアドレスが変わってGatewayサーバで許可しようがないので、NAT Gatewayを使ってAWSのVPCからGatewayサーバへ出る時に送信元のIPアドレスを固定化した
• NAT Gatewayに付与したElastic IPをGatewayサーバで許可している
解決策2: オンプレの味方NAT Gateway
• 特定のIPアドレス(今回の場合Gateway)への通信を全部NAT Gatewayを通すことにより、送信元のIPアドレスを固定化している
Az
NAT Gatewayのイメージ
Subnet
GitLab
オンプレ環境AmazonVPC
ec2
• 特定のIPアドレス(今回の場合Gateway)への通信を全部NAT Gatewayを通すことにより、送信元のIPアドレスを固定化している
Az
NAT Gatewayのイメージ
Subnet Subnet
NAT GatewayGitLab
オンプレ環境
Gateway
ec2
IPアドレスを固定
AmazonVPC
• NAT GatewayのElastic IPをGatewayのnginxで許可してやることにより、GoogleのOAuth認証を回避するようにした
解決策3: http経由のアクセスもNAT Gatewayを通す
CIのジョブ間で大容量ファイルを受け渡せるようにした
• 経緯
‣ VRoid Studioチームの人「ジョブが終わってGitLabにArtifactsをアップロードしようとするとエラーになる」
‣ sue445「GitLabのログ見たら413エラー(Request Entity Too Large)出てますね。nginxのアップロード制限に引っかかってるようなのでGitLab側のアップロードサイズ緩めますがzipに固めた時のファイルサイズどれくらいあります?」
CIのジョブ間で大容量ファイルを受け渡せるようにした
VRoid Studioチームの人「4GBほしいです」(原文ママ)
sue445「😇」
• さっきのGatewayサーバは社内開発全般で使っているので、Gatewayの帯域が詰まると社内の開発が全部死ぬ
• Railsアプリで4GBのファイルのアップロードとかするとunicornが瀕死になる未来しか見えない
‣ 注)GitLabはRailsアプリ
• AWSから外(オンプレのGitLab)に出ていく時の通信費がかかる
要約すると普通のWebアプリに巨大ファイルをupするのは辛い
• アプリから特別な設定をせずにS3にアクセスできるようにした
• やり方
‣ 特定のS3バケットのみにアクセスできるIAMロールを作成
‣ Docker MachineがEC2インスタンスを起動する時のインスタンスプロファイルにこのIAM
ロールを設定
‣ こうすることでRunner側はawscliだけ用意していればバケットに読み書きできる。(アプリ側でアクセスキーなどの設定不要)
• 同一リージョン内だとEC2とS3でファイルやり取りする時の通信費がかからないのも嬉しい
解決策: S3にアップロードする(王道)
• 前提
‣ GitLab RunnerにはOffPeakという設定があって、「業務時間内は常にEC2インスタンスを1
台待機するが、業務時間外や土日は待機させないことでコストを下げる」ということが可能
‣ https://docs.gitlab.com/runner/configuration/autoscale.html
‣ https://docs.gitlab.com/runner/configuration/advanced-configuration.html
Runnerの起動時間チューニング
• 前提
‣ ピクシブGitLabの場合下記のような設定
‣ 平日10:00~18:59はスポットインスタンスを最低1台待機して、ジョブが積まれたら即ビルド開始できるようにしてる
‣ 業務時間外や土日・正月・GWはスポットインスタンスを待機させず、ジョブが積まれた時点でインスタンスを起動
Runnerの起動時間チューニング
• 問題点
‣ ジョブが積まれてから実際にビルドが始まるまで最大4分半くらいかかってたので頑張ってチューニングしたかった
‣ 業務時間内だとEC2が1台以上待機してるので気にならないけど定時過ぎてpushした時にRunnerの実行まで時間がかかる
‣ 起動が遅いより速い方がいい
‣ 速さは正義、時は金なり
Runnerの起動時間チューニング
• Docker Machineはリモートにdockerがインストールされていない時のみdockerインストールしてたので、dockerがインストール済ならこの処理をスキップできると踏んだ
‣ https://github.com/docker/machine/blob/v0.16.0/libmachine/provision/utils.go#L30
‣ Docker Machineで起動するAMIにdockerをインストールしておくだけで4分半から4分に短縮できた
解決策: AMIに予めdockerを入れておく
• 個人的にはもうちょっと短縮したかったが、これ以上やろうとするとDocker Machine本体をいじる必要があったので断念
• Machine作成時のログを全部読んだけどどれか1つの処理が遅いというよりも、細かい処理がたくさんあってチリツモで4分かかってるという印象だった
解決策: AMIに予めdockerを入れておく
• 前提
‣ スポットインスタンスはAWS上の余剰インスタンスを安く使える仕組みなので、AWS全体のスポットインスタンスの在庫がなければ起動できない
‣ しかしスポットインスタンスが枯渇した時に社内の開発が完全に止まるのはきつい
スポットインスタンス枯渇対策
• スポットインスタンスが枯渇した時に別のインスタンスタイプやAZで試すなどのスポットインスタンスガチャをやるよりは、さっさとオンデマンドに全振りした方が早く復旧できるという判断
• 今までの経験上、どっか1ヶ所でスポットインスタンスが枯渇してるとその周辺もだいたい枯渇してる印象
解決策: オンデマンドインスタンスを使うRunnerを作った
• オンデマンドインスタンスを使うRunnerはホットスタンバイ(Runnerとしては動いてるが管理画面上では無効なのでジョブが割り振られない)で用意しておいて、GitLabの管理画面上で有効化した時のみ使われるようにしてる
解決策: オンデマンドインスタンスを使うRunnerを作った
• スポットインスタンスを活用することでハイスペックなCI環境を低価格で運用可能
• CIに限らずこれからも全力で自動化やっていきます
まとめ