エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

GitLab Runner の autoscaling

12月1日は映画の日で1,000円で映画が見れるということだったので話題の「ボヘミアン・ラプソディ」を見てきました。おもしろかったですまる。近頃、映画やドラマを見ては泣いている SRE の寺岡です。

この投稿はエムスリーアドベントカレンダー2日目の記事です。また、 GitLab Advent Calendar 2018 の14日目の記事でもあります。そして14日は私の誕生日ですね、ありがとうございます。

それでは本題へ

現在のエムスリーの GitLab Runner の状況

エムスリーではソースコード管理に GitLab を使用しており GitLab Runner も CI に使っています。この Runner を EC2 の spotfleet で毎朝起動させるという記事を以前 Qiita に投稿しています。平日の朝、固定されて数の Runner が EC2 で起動して、指定した時間だけ稼働するようになっています。(Runner の用途によって状況は異なります)

qiita.com

最近、新たな用途の Runner を追加しようとしたときに register コマンドのオプションに autoscale に関する項目があることに気づきました、そして spotfleet も使えるようなので切り替えることでより効率的な運用ができるのではないかと試してみました。

GitLab Runner のオートスケールとは?

Docker を使う Runner に限られますが docker-machine を使って、必要に応じて docker 用インスタンスの作成、削除をしてくれます。この記事では AWS EC2 を使っていますが docker-machine が対応している環境であれば使うことができます(対応 Driver は https://docs.docker.com/machine/drivers/ で確認することができます)。

f:id:y-teraoka:20181202005707p:plain
Runner構成

Autoscaling 機能は GitLab Runner 1.1.0 で追加されたようです。ずいぶん前ですね 😅

オートスケールのアルゴリズム

ドキュメント にある通りですが、簡単に説明します。

IdleCount, IdleTime, limit 設定によって制御されます。例えば次のような設定であった場合

[[runners]]
  limit = 10
  (...)
  executor = "docker+machine"
  [runners.machine]
    IdleCount = 2
    IdleTime = 1800
    (...)
  • IdleCount = 2 によってまず 2 つの machine が作成されます
  • そこへ 5 つの job がやってきたらそのうち 2 つは既に作成済みの 2 つの machine で実行されます
  • すると idle 状態の machine がなくなるため、追加で 2 つの machine が作成されます
  • これもまた残った 3 つの job のうち 2 つの実行に使われます
  • また idle 状態の machine がなくなったので 2 つの machine を作成します
  • 残っていた 1 つの job が実行されます
  • idle 状態の machine が 1 つになったので 2 つを維持するように machine を 1 つ作成します

永遠に増えるわけではなく machine の上限は limit で制限されていますので、それを超える job は待たされることになります。

job を終えた各 machine は IdleTime の 1800 秒間 idle が継続すると削除されます。IdleCount 分はずっと残るのかと思いきや IdleTime に達するとかならず削除されます。よって、job がしばらく無い場合、2 つの machine を作成しては削除してまた作成するという動作になります。

常に idle 状態の machine をキープするのはなんとなく無駄な感じがしますが、これまで固定台数をずっと起動していたのだとすれば、その数を limit にしておけばそれ以上に増えることは無いので費用面でのデメリットはないはず。常に job が発生するような環境では IdleTime を迎えることもなく動き続けるはずです。

作成される machine の上限にはグローバル設定の concurrent も影響します。

concurrent=20

[[runners]]
  limit = 40
  [runners.machine]
    IdleCount = 10

この設定では limit = 40 で 40 まで増えそうですが、concurrent = 20 で同時実行数が 20 に制限されています。ということは 20 + IdleCount で machine 数の上限は 30 に抑えられます。

OffPeak 設定

Idle な machine を維持するのもさすがに深夜や休日は避けたいところです、それを実現するための設定が OffPeak です (Off Peak time mode configuration)。GitLab Runner v1.7 で追加されたようです。

[runners.machine]
  OffPeakPeriods = [
    "* * 0-9,18-23 * * mon-fri *",
    "* * * * * sat,sun *"
  ]
  OffPeakTimezone = "Asia/Tokyo"
  OffPeakIdleCount = 0
  OffPeakIdleTime = 300

この OffPeak 設定では月曜日から金曜日の 0:00 - 10:00, 18:00 - 24:00 と土曜日、日曜日を OffPeak 期間とし、その間は IdleCount を 0、IdleTime を 300 として動作します。job がなければ machine 起動していないという状態にできます。Idle 状態の machine が無いため、もしも job が発生したらそこから machine の作成の分待たされることにはなりますが、ずーっと実行されないというわけではなくなります。

cache の S3 保存

Autoscaling で頻繁にインスタンスが再作成される状況ではインスタンスのローカルストレージを使った cache ではあまり効果が期待できなくなってしまいます。そこで S3 (もしくは互換ストレージ) を使うことが推奨されています。エムスリーでは先にもあったように現状でも毎日再作成されていますし、実行されるインスタンスによって異なってしまうのを避けようとすでに S3 を使用しています。古い cache は bucket の lifecycle 設定で自動削除できます。

  [runners.cache]
    Type = "s3"
    Path = "Bucket内のPath Prefix"
    Shared = true  # cache の path に runner id を入れないで runner をまたいで cache の共有する
    [runners.cache.s3]
      ServerAddress = "s3.amazonaws.com"
      BucketName = "Bucket Name"
      BucketLocation = "ap-northeast-1"

registry の mirror

cache と似たような問題で docker image も頻繁に再取得が必要になってしまうことから registry mirror の設定についてもドキュメントにあります。

autoscale runner の設定方法

gitlab-runner が docker-machine コマンドを実行するため、ditlab-runner サーバーには docker-machine コマンドのインストールが必要です。また、Spot Request や EC2 まわりの操作を行うために必要な権限をセットした Instance Profile を割り当てます。

次のように gitlab-runner register コマンドを実行することで autoscale runner をセットアップすることができるはずです。このコマンドではグローバルオプションは設定できないので concurrent については直接 /etc/gitlab-runner/config.toml を編集する必要があります。 --machine-machine-options の詳細については docker-machine のドキュメント を参照してください。

/usr/bin/gitlab-runner register \
 --url https://gitlab.example.com/ci \
 --non-interactive \
 --registration-token TOKEN \
 --docker-image docker:latest \
 --tag-list docker,sometag \
 --name "autoscale-runner" \
 --executor docker+machine \
 --limit 10 \
 --docker-volumes "/var/run/docker.sock:/var/run/docker.sock" \
 --machine-machine-name=runner-%s \
 --machine-idle-nodes 2 \
 --machine-idle-time 1800 \
 --machine-max-builds 0 \
 --machine-machine-driver amazonec2 \
 --machine-machine-options "amazonec2-ami=ami-xxxxxxxxxxxxxxxxx" \
 --machine-machine-options "amazonec2-iam-instance-profile=gitlab-runner" \
 --machine-machine-options "amazonec2-instance-type=m4.large" \
 --machine-machine-options "amazonec2-private-address-only" \
 --machine-machine-options "amazonec2-region=ap-northeast-1" \
 --machine-machine-options "amazonec2-request-spot-instance" \
 --machine-machine-options "amazonec2-root-size=50" \
 --machine-machine-options "amazonec2-security-group=gitlab-runner-autoscale" \
 --machine-machine-options "amazonec2-spot-price=0.08" \
 --machine-machine-options "amazonec2-ssh-user=ubuntu" \
 --machine-machine-options "amazonec2-subnet-id=subnet-xxxxxxxx" \
 --machine-machine-options "amazonec2-tags=Name,GitLabRunnerMachine,TagName1,TagValue1" \
 --machine-machine-options "amazonec2-vpc-id=vpc-xxxxxxxx" \
 --machine-machine-options "amazonec2-zone=c" \
 --machine-off-peak-periods "* * * * * sat,sun *" \
 --machine-off-peak-periods "* * 0-9,18-23 * * mon-fri *" \
 --machine-off-peak-timezone Asia/Tokyo \
 --machine-off-peak-idle-count 0 \
 --machine-off-peak-idle-time 60 \
 --cache-type s3 \
 --cache-s3-server-address s3.amazonaws.com \
 --cache-s3-bucket-name "BucketName" \
 --cache-s3-bucket-location ap-northeast-1 \
 --cache-path cache \
 --cache-shared=true

docker-machine で作成されるインスタンスは S3 を使った cache のためにこちらも必要な権限を設定した Instance Profile を割り当てます (--machine-machine-optionsamazonec2-iam-instance-profile で指定)。

--docker-volumes "/var/run/docker.sock:/var/run/docker.sock" については docker in docker で docker build を実行したりするために指定していますが、最近は docker を使わないで image を作成する方法もいくつか登場していますね。--docker-volumes は複数回指定可能ですのでさらに他のディレクトリをマウントすることも可能です。

docker runner で使う docker image (gitlab-ci.yml の image で指定するもの) の pull に認証が必要な場合は gitlab-runner を実行するサーバーで docker login 相当の設定を入れておく必要があります(通常は /root/.docker/config.json でしょうか)。

その他

この検証の過程で現在毎日の作成で使ってるオプションに deprecated で GitLab Runner 12.0 で消えるものが含まれていました、危うくある日突然動作しなくなるところでした。バージョンを固定して古くなってしまうのも問題ですが、自動更新で動かなくなるのも問題で難しいところです。

docker-machine で tls: bad certificate という謎のエラーの調査にだいぶ時間をとられてしまいましたが、docker-machine でトラブったら ~/.docker/machine を消すと回避できたりします。

まとめ

買ってしまったオンプレ資産とは違ってクラウドサービスの場合は効率化で削れば即利益に繋がるので効率的な運用を意識しよう。

We are hiring !

エムスリーではこのような DevOps まわりの改善であったり、オンプレ、クラウドのハイブリッド環境を適切に運用していく仲間を募集しています。

M3 Tech Blog を読まれて興味を持った方はぜひ下記リンクよりご応募ください!
また、Tech Talkの参加、登壇もお待ちしていますのでお気軽にご連絡ください!

jobs.m3.com