Autoscaling GitLab Runner on AWS | GitLab
を試してみた話。
概要
Gitlab Runner は、実行方法としてdockerを選択すると、Gitlab Runner上でdocker runする形で実行されます。
その意味するところは、
- Gitlab Runner本体のリソース上限の範囲でjobを実行しなければならない。
- jobが1個でも2個でも消費するリソースは同一。
- runnerが落ちると全てのjobが巻き込まれて落ちる。
というところです。
特に3つ目の点には注意が必要で、同時実行数の設定を間違うと、OOMキラーなどにより、gitlab runner プロセス自体が殺されたりします。つらい。
Gitlab Runner の docker+machine という実行方法を選択すると、Gitlab Runner自体ではjobが実行されず、docker machineを通して、他のノードで実行されるようになります。
そうすると
- Gitlab Runner がdocker machineを通してアクセスできるノード1つにつき1jobが実行されるので死ぬのはjob単位。
- Gitlab Runner 自体ではjobが実行されないので、Gitlab Runner自体が死ぬ可能性が低くなる
- ノードの数がうまい具合に上下してくれるとjobの数だけしかリソースを消費しない。
というようなメリットが出てきます。 固定ノードによるdocker+machine 運用もありですが、3点目の利点を活かすには、オートスケールの仕組みを取り入れるのが良いです。 またノードにSpot Instanceを利用することで、コスト削減効果も見込めます。
といったところで本題の AWS を利用したAutoscaleです。
AWSを利用したAutoscaleと言っても、Gitlab Runnerが特別なことを行なっているわけではなく、docker machineの awsec2 driverがそのあたりの制御を行なっています。
なお、現在、docker machineはメンテナンスフェーズに入っており、 Direct support for AWS autoscaling groups and spot instances (#3877) · Issues · GitLab.org / gitlab-runner · GitLab のようなissueが立っていたりします。
具体的に何が問題なのかというと、docker machineがAmazonLinuxに対応していないため、ノードとしてubuntuを利用する必要がある、という点です。
ざっくりとした構成
見ての通り、gitlab runnerには EC2インスタンスを作成するという、強めの権限が必要になります。
また、実行ノードには、状況に応じて、各種サービスへのアクセス権が必要となります。
セキュリティ
gitlab runner
構成で軽く説明した通り、gitlab runner には強い権限を与える必要があるため、乗っ取られたりするリスクを最小限にする必要があります。
具体的には、セキュリティグループなどを利用して、
- Gitlabへのアクセス
- ノードへのアクセス
- OSのセキュリティアップデートなどのアクセス
- 管理者がgitlab runnerを制御するためのインバウンドアクセス
しかできないようにすると良いでしょう。
ノードを作成するのに必要な権限としては
"ec2:DescribeSpotInstanceRequests" "ec2:CancelSpotInstanceRequests" "ec2:GetConsoleOutput" "ec2:RequestSpotInstances" "ec2:RunInstances" "ec2:StartInstances" "ec2:StopInstances" "ec2:TerminateInstances" "ec2:CreateTags" "ec2:DeleteTags" "ec2:DescribeInstances" "ec2:ImportKeyPair" "ec2:DeleteKeyPair" "ec2:DescribeKeyPairs" "ec2:DescribeRegions" "ec2:DescribeImages" "ec2:DescribeAvailabilityZones" "ec2:DescribeSecurityGroups" "ec2:DescribeSubnets"
このあたりです。
ノード
ノードには、環境にもよりますが、キャッシュを利用するためS3へアクセスできるようにしたり、ECRを利用していればそちらへのアクセス権、 その他、外部サービスなどへのアクセスが必要であれば、それらの権限が必要になります。 こちらも、それなりに強い権限になることが予想されますので、
- 必要なサービスへのアクセス
- gltlab runner からのインバウンドアクセス
のみに限るようにしておくと良いでしょう。
インストールと設定
まずはGitlab Runnerのインストールです。
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash sudo apt-get install gitlab-runner
RedhatLinux系(Ceotsほか)であれば
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash sudo yum install gitlab-runner sudo /usr/share/gitlab-runner/post-install
あとは docker, docker machine もインストールしておきます。
docker machine のインストールは Releases · docker/machine · GitHub を参考に。
/usr/local/bin/docker-machine
へインストールすることを推奨していますが、一部環境では サービスの実行時に見に行くPATHに/usr/local/bin/
が含まれていないことがあるため、/usr/bin/docker-machine
へインストール(もしくはシンボリックリンクを作成)する必要があるかもしれません。
次にgitlab runnerのGitlabへの登録です。
通常の手順と大きく変わりはしませんが、executor
としてdocker+machine
を選んでください。
最後に設定
設定は/etc/gitlab-runner/config.toml
を直接変更する方がわかりやすい気がしています。
オフィシャルサイトに記載のサンプルを足したものがこちら
concurrent = 10 check_interval = 0 [[runners]] name = "gitlab-aws-autoscaler" url = "<URL of your GitLab instance>" token = "<Runner's token>" executor = "docker+machine" limit = 20 [runners.docker] image = "alpine" privileged = true disable_cache = true [runners.cache] Type = "s3" Shared = true [runners.cache.s3] ServerAddress = "s3.amazonaws.com" AccessKey = "<your AWS Access Key ID>" SecretKey = "<your AWS Secret Access Key>" BucketName = "<the bucket where your cache should be kept>" BucketLocation = "us-east-1" [runners.machine] IdleCount = 1 IdleTime = 1800 MaxBuilds = 10 OffPeakPeriods = [ "* * 0-9,18-23 * * mon-fri *", "* * * * * sat,sun *" ] OffPeakIdleCount = 0 OffPeakIdleTime = 1200 MachineDriver = "amazonec2" MachineName = "gitlab-docker-machine-%s" MachineOptions = [ "amazonec2-access-key=XXXX", "amazonec2-secret-key=XXXX", "amazonec2-region=us-central-1", "amazonec2-vpc-id=vpc-xxxxx", "amazonec2-subnet-id=subnet-xxxxx", "amazonec2-zone=x", "amazonec2-use-private-address=true", "amazonec2-tags=runner-manager-name,gitlab-aws-autoscaler,gitlab,true,gitlab-runner-autoscale,true", "amazonec2-security-group=xxxxx", "amazonec2-instance-type=m4.2xlarge", ]
global
グローバルな設定として重要なものにconcurrent
があります、これは、gitlab runnerで同時に実行できるjobの上限を決めるものです。
状況に合わせてチューニングしましょう。0は無限を意味しないので気をつけてください。
runners
つぎに[[runners]]
設定です。ここには、gitlab runnerを登録した時の情報が含まれています。runnerは一つのrunnerにつき、複数設定できます。
具体的には、gitlab-runner register するたびに増えていきます。例えば、リソースを多く必要とするjobとそうでないjobがある場合に、使う設定を変更したい、というような場合、複数登録するというのはアリかもしれません、gitlab runnerを複数用意する方が素直なケースが多いですが。
runnerの設定では、limit
というのがあります、これは、立ち上げ可能なノードの数を示しています。実行中ノードと待機中ノードの数を足したものの上限が設定されます。
runners.docker
runnerごとの設定に[runners.docker]
があります。ここで重要なのはprivileged
です、jobでdocker in docker が必要な場合(docker buildしたい場合など)はtrue
に設定しておく必要があります。
runners.cache
同じくrunnerごとの設定に[runners.cache]
があります。Type=s3
とすることで、s3をキャッシュとして利用できます、またshared=true
とすることで、ノードでキャッシュが共用できるようになります。基本的にはshared
はtrue
で良いでしょう。
アクセスキーやシークレットについては、適切なinstance profileが設定されていれば不要です。
runners.machine
同じくrunnerごとの設定に[runners.machine]
があります。
ここで、ノードの設定を行います。
アクセスキーやシークレットについては、適切なinstance profileが設定されていれば不要です。
IdleCount
というのが、待機中のノードの数です。待機中のノードの数がこの数より少なくなったら、ノードを立ち上げて待機します。IdleTime
というのは、ジョブが終わったあと、待機する時間です、IdleCountよりたくさんのノードが待機中担っていた場合、この時間がすぎるとノードはシャットダウンされます。MaxBuilds
というのは、ノードがこの回数jobを実行するとシャットダウンするという数値です。ノードを定期的にクリーンアップするのに利用できます。OffPeakPeriods
は、いわゆる、夜間や休日に関する設定です。この期間は、IdleCount
やIdleTime
の代わりにOffPeakIdleCount
やOffPeakIdleTime
が利用されます。MachineDriver
これは今回の説明ではAWSを利用すると言っているのでawsec2
です、利用できるのはdocker machimeがサポートしているものです。MachineOptions
ここが重要なところで、ノードとして作成するインスタンスに関する情報を記載します。設定はだいたい見ての通りなので大丈夫でしょう。amazonec2-ami
で利用するAMI IDが指定できますので、カスタムイメージを利用する場合は設定すると良いでしょう。ただし、AmazonLinuxベースのものは利用できません。
考えるべきこと
globaなconcurrent、ノード単位の limit, IdleCount が重要な数値となります。
runnerが1つの場合、
- concurrent=15, limit=20, IdleCount=5 な状況では、最大20台のノードが立ち上がります。最大同時実行数は15です。(5台はidle状態をキープしている)
- concurrent=15, limit=15, IdleCount=5 な状況では、最大15台のノードが立ち上がります。最大同時実行数は15です。(最大実行時にはidle状態のノードがなくなる)
- concurrent=15, limit=10, IdleCount=5 な状況では、最大10台のノードが立ち上がります。最大同時実行数は10です。(limitのほうがconcurrentより小さいので、設定した最大実行数に満たない数しか同時実行されない)
runnerが複数の場合、
concurrent=15, limit=20, IdleCount=5, limit=20, IdleCount=5 な状況では、最大25台のノードが立ち上がります。最大同時実行数は15です。(各runner5台はidle状態をキープしている)
concurrent=15, limit=10, IdleCount=5, limit=10, IdleCount=5 な状況では、最大20台のノードが立ち上がります。最大同時実行数は15です。(各runnerでidle状態をキープしている台数はジョブの実行数に依存します)
concurrent=15, limit=5, IdleCount=5, limit=5, IdleCount=5 な状況では、最大10台のノードが立ち上がります。最大同時実行数は10です。(runnerの登録の仕方によりますが、片方のrunnerに余裕があるのに、もう片方のrunnerのlimitにひっかかって、同時実行数に余裕があるけれど同時実行されないケースも出てきます。)
というところで、環境に応じた設定が必要になりますし、runnerを複数設定することは、若干の無駄を許容するという感じになりそうです。