アイリッジ開発者ブログ

アイリッジに所属するエンジニアが技術情報を発信していきます。

GitLab RunnerをAWS上にワンコマンドデプロイする (オートスケールもあるよ)

TL;DR (忙しい方はここだけ読んでください!)

GitLab RunnerをAWS上にデプロイするためのコードを書いたので、是非お使いください!

ご挨拶

開発推進グループのbbrfkr(ビビリフクロウ)こと、斎藤です。

2019年6月、iRidgeにインフラエンジニアとしてジョインしました。コーディングについては初心者で、至らない点も多々ありますが、本ブログ等を通してご指導ご鞭撻いただけると嬉しいです。

開発者としては日が浅い私ですが、開発を進める中でインフラ観点で効率化に寄与できる点はないかと考えました。その結果として、タイトルの通りGitLab Runnerを簡単にデプロイするためのコードを作成しました。今回はこの場を借りて、本コードについて紹介させていただきます。

モチベーション

弊社では開発にGitLab CommunityEditionを活用しており、もちろんCI/CDにはGitLab CIを使っています。GitLab CIにおいてビルド/デプロイジョブを実行するノードを「GitLab Runner」といいますが、私が担当しているプロジェクトで構築されたGitLab Runnerの調子が悪くなり、作り直すことにしました。
既存Runnerの構築時手順は残されていましたが、どうせなら作成手順をコード化し、他プロジェクトでも簡単に使いまわせるようにしたかったため、本コードを作成することにしました。さらにおまけで、CPU負荷に応じてRunnerをオートスケールする機能も付加することにしました。

使い方

準備

以下のURLでコードを公開しておりますので、まずはコードをgit cloneしてください。

git cloneしたら、pipコマンドで依存パッケージをインストールします。なお、pythonバージョンは3.7でのみ動作確認しております。

$ pip install -r requirements.txt

次にリポジトリ直下で設定ファイルのサンプルをコピーします。

$ cp variables.yaml.sample variables.yaml

コピーした設定ファイルvariables.yaml内の変数に値を入力していきます。各変数の意味は以下の通りです。

変数名 説明
runner_region RunnerをデプロイするAWSリージョン
runner_vpc RunnerをデプロイするVPC
runner_allowed_ip RunnerにSSHする際の接続許可ソースIP(CIDRで記載。なおパブリックIPが付与されるかはVPCの設定に依存しますので、プライベートサブネットでご利用の場合は、生成されたインスタンスにアクセス可能な踏み台サーバのIPを指定して下さい)
runner_ami_id Runnerに利用するAMI ID (Amazon Linux 2で動作確認済み。RedHat系OSのみ利用可能)
runner_instance_type Runnerのインスタンスタイプ
runner_key_pair Runnerのキーペア
runner_desired_count Runnerの初期数
runner_min_count Runnerの最小数
runner_max_count Runnerの最大数
runner_subnets Runnerが所属するサブネットID(カンマ区切りで記載)
runner_job_concurrency Runnerが処理するジョブの並列数(0で無限)
runner_tag_list RunnerのGitLab上のタグリスト(カンマ区切りで記載)
runner_register_token Runnerを登録する際のトーク
runner_gitlab_url Runnerが接続するGitLabのURL
runner_volume_size Runnerに接続するEBSボリュームのサイズ(GB)
runner_scalein_threshold RunnerをスケールインするCPU使用率(%)
runner_scaleout_threshold RunnerをスケールアウトするCPU使用率(%)
runner_scalein_cooldown Runnerをスケールインしたあとのクールダウン期間(秒)
runner_scaleout_cooldown Runnerをスケールアウトしたあとのクールダウン期間(秒)

また、本コードは内部的にboto3を使用しますので、対象AWSアカウントに対するクレデンシャルを環境変数AWS CLIaws configureコマンドで予め与えておいてください。環境変数で与える場合はリポジトリ直下のファイル.env.sampleを以下のコマンドでコピーし、値を入力します。

$ cp .env.sample .env

その後、sourceコマンドで環境変数を読み込ませます。

$ source .env

以上で、GitLab Runnerをデプロイする準備は完了です。

いざ、デプロイ!

依存パッケージと変数の準備が完了していれば、GitLab Runnerのデプロイは以下のコマンドを実行するだけです!
本コードではsceptretroposphereというツールを利用して、CloudFormationスタックを作成することでデプロイを実現しています。

$ sceptre --var-file ./variables.yaml launch runners

sceptreとtroposphereについて簡単に説明させていただきますと、sceptreはCloudFormationスタックをAWS CLIに代わって操作するラッパーで、troposphereはCloudFormationテンプレートをpythonコードから生成可能なライブラリです。この2つを組み合わせて利用すると、troposphereを用いて記述したpythonコードからsceptreにてCloudFormationスタックをシームレスに生成することができます。

単純にCloudFormationテンプレート単体で利用するよりも動的なリソース生成、条件に応じたデプロイリソースのスイッチングといった柔軟なスタック生成が行えるメリットがあります。例えば前者ですと、生成するインスタンス数をパラメータで外出しすることが、for文のループによって簡単に実現できます。後者ですと、if文を用いることで本番環境ではAutoScaling Groupを作成するが、開発環境ではAutoScaling Groupを作らずに、単一インスタンスのみ作成する、といったパラメータだけでは吸収しきれない環境差異も単一コードで表現可能です。

不要になったら…

本コードで作成した環境が不要になった際は、まずAutoScaling Groupの必要数を0にして、インスタンスが削除されたことを確認した後、以下のコマンドでクリーンアップすることができます。

$ sceptre --var-file ./variables.yaml delete runners

構成

本コードを実行すると、お使いのAWSアカウント上に、概ね以下のリソースが作成されます。(詳しい作成内容についてはソースコードをご覧ください)

f:id:bbrfkr:20190919144537p:plain
GitLab Runnerテンプレート

  • オートスケーリンググループ
    Runnerの実体を作成するオートスケーリンググループです。ユーザデータにGitLab RunnerのインストールとGitLabへの登録を行うスクリプトが仕込まれるため、本コードを実行するだけで、すぐに使用可能なGitLab Runnerがプロビジョニングされ、GitLabに自動的に登録されます。

  • スケールアウト/スケールイン用のCloudWatchアラーム
    オートスケーリンググループのCPU使用率を監視し、指定した閾値でアラートをあげるCloudWatchアラームです。このアラームのもと、GitLab Runnerはスケールアウトおよびスケールインします。

  • 登録解除用のLambda関数
    GitLab Runnerがスケールインする際、GitLabから当該Runnerを登録解除する必要があります。こちらはその際に発火するLambda関数です。本関数はAWS Systems ManagerからRunCommand機能を呼び出して、GitLab Runnerに登録解除用のスクリプトを実行させます。

まとめ

GitLab RunnerをAWSにデプロイするときはEC2インスタンスを利用することが一般的かと思います。EC2インスタンス仮想マシンですので、DockerコンテナやLambda関数と異なりステートフルに扱うことが容易で、運用が属人化しやすいリソースです。このため、GitLab Runnerのようにステートレスに扱うことのできるEC2インスタンスは、設定をコード化することも容易ですので、属人化を防ぐために可能な限りコードに落とし込んでおくことをおすすめします。
なお、Docker ExcecuterをRunnerに利用しなくても良い場合、Fargate上にRunnerをデプロイするという荒業も可能なようです。この場合、RunnerはShell Excecuterとして動作するため、Docker Excecuterのようにserviceの概念が使えません。このためDBに接続してのCIといったユースケースには活用できず利用シーンは限られてくるかと思いますが、要件を満たすシーンでは積極的に活用していきたいと思います。