Serverless GitLab CI/CD on AWS Fargate

How to deploy GitLab Runner manager and the Fargate driver with no virtual machine setup

Daniel Coutinho de Miranda
CI&T
8 min readJun 24, 2020

--

Photo by Glenn Carstens-Peters on Unsplash

The GitLab Runner team recently announced the AWS Fargate driver for the Custom executor. This software enables us to have our CI/CD jobs running in separate and isolated containers on Amazon’s Elastic Container Service (ECS).

At the time of writing this article, GitLab documentation provides a pretty nice guide covering how to configure and run GitLab Runner manager and the AWS Fargate driver hosted in an AWS EC2 instance. Assuming everything is properly configured and a pipeline is started in your project, GitLab notifies your Runner manager that a new job is available. The Runner manager, using the Fargate driver, then starts a new task in the ECS cluster and processes the build.

But what if we don’t want to use EC2 for hosting the GitLab Runner manager? We can opt to host it in AWS Fargate and benefit from a fully serverless approach.

This article focus on showing how to migrate the GitLab Runner manager from the EC2 virtual machine (VM) model into a fully serverless approach hosted in AWS Fargate.

Why serverless?

Running GitLab Runner manager serverless may offer some benefits over the traditional scenarios, for example:

  • No server management: Not having to provision a server means not directly dealing with virtual machines (VMs) or bare-metal servers. Therefore, we don’t need to worry about things like a machine or OS-level configuration and tuning, allowing us to focus on what is more important to the business.
  • Pay for what you use: In this model, we will be charged exactly for the resources our Docker containers will use during the time it is up and running. This means we can terminate the Fargate Task when we don’t expect to use it anymore and simply restart it when we need it again.

As there are many variables to compare costs for the solution running in both EC2 and Fargate, such comparison is not the scope of this article. Nevertheless, since for simple workloads we can specify very modest Fargate Task configurations, we believe this approach is not a costly solution. The reader may also refer to this link for a better understanding of both platform pricing and use cases. Please, feel free to leave us a comment in case you have a deeper comparison between both platforms for a similar scenario.

Our premises

For the scope of this article, we consider some resources were already created following GitLab’s Autoscaling GitLab CI on AWS Fargate guide:

  • Necessary infrastructure (VPC network, subnet, security group, and Fargate cluster)
  • Base container image used to run your CI jobs
  • Task Definition to instantiate the build’s task(s)

Solution overview

The image below presents a high-level overview of the solution.

Image showing all components involved in the solution: Container image, Task Definition and Fargate Task.

We will need to execute a few steps to have the Runner manager properly configured and running in AWS Fargate:

  1. Containerize the GitLab Runner manager and Fargate driver
  2. Create a Task Definition to instantiate the Runner manager tasks
  3. Start a new Fargate Task based on the Runner manager Task Definition
  4. Check the Security Group rules for SSH connections
  5. Test the configuration

Below we describe each step in detail.

Step 1: Containerize the GitLab Runner manager and Fargate driver

To have GitLab Runner manager and the Fargate driver running inside a Fargate Task, first, we need to containerize them. To simplify this task we will use an existing image that provides us all dependencies already installed and configured. This image is published in the Container Registry of its project: registry.gitlab.com/danielcmiranda/docker-gitlab-runner-fargate-driver.

The container image expects a few environment variables that will be used for configuring the GitLab Runner and Fargate driver for a specific GitLab project and AWS context. You will only need to assign them in the next step when creating the Task Definition, but we will describe them here for the sake of clarity:

  • GITLAB_REGISTRATION_TOKEN: the registration token for the GitLab project. To obtain it you will need to go to your project CI/CD settings (https://gitlab.com/your/project/-/settings/ci_cd), open the Runners section, and check the registration token under Set up a specific Runner manually.
  • FARGATE_CLUSTER: name of the ECS Fargate Cluster where CI jobs should be processed;
  • FARGATE_REGION: AWS region where CI jobs should be processed;
  • FARGATE_SUBNET: AWS subnet where CI jobs should be processed;
  • FARGATE_SECURITY_GROUP: the AWS security group name;
  • FARGATE_TASK_DEFINITION: Task Definition that will be used to create the task to process the CI job (as mentioned previously, creating it is not the scope of this article);
  • RUNNER_TAG_LIST (optional): you can set up jobs to only use Runners with specific tags. Separate each tag by a comma.

Note: if you want to fine-tune or customize the image, you can fork or clone the project, make your changes, push it into a container registry and then use the published image in the next step.

What does our container image do?

This section describes a little more detail about the container image. The reader not interested in a deeper understanding of it may jump to the next section.

Below, we show the core part of the Docker entry-point bash script code. We will use it for explaining the basics of the configuration and flow within the container image.

create_driver_configregister_runner ${GITLAB_REGISTRATION_TOKEN} ${RUNNER_TAG_LIST}gitlab-runner rununregister_runner ${auth_token}

Basically what it does is:

  • The create_driver_config function creates the Fargate driver TOML configuration file based on a template that is persisted in the repository. It uses the environment variables passed to the container to set the correct values in that file.
  • The register_runner function is responsible for registering the Runner manager in the desired project, identified by the registration token of that project. It also updates the Runner manager TOML configuration file accordingly.
  • The next command starts the Runner manager, so it can process GitLab CI jobs. This is a blocking call, so the rest of the script will only execute after the Runner manager is stopped (i.e: when a Docker stop signal is emitted).
  • For last, the unregister_runner function is only called after the Runner manager is stopped and it is responsible for removing the Runner manager from the list of runners of the GitLab project.

In summary, the container image can configure both the Runner manager and Fargate driver to work together, as well as implement the necessary procedures to register and unregister the Runner manager in the specified project during the startup/shutdown of the container.

Step 2: Create a Task Definition to instantiate the Runner manager tasks

Below we show how to create a Task Definition that will be used for running the Runner manager container.

  1. Go to Task Definitions in your AWS project.
  2. Click Create new Task Definition.
  3. Choose Fargate and click Next step.
  4. Give it a name.
  5. For Task Role, in order to make it simple, we will reuse the already existing ecsTaskExecutionRole role. However, you can create your own role and use it here.
  6. Select values for Task memory (GB) and Task CPU (vCPU). Note that optimized values are highly dependent on your workload. For simple cases, you can select modest configurations like 1 GB and 0.5 vCPU.
  7. Click Add Container, then:
  • Give it a name.
  • For the Image, we will use the one presented in the last section: registry.gitlab.com/danielcmiranda/docker-gitlab-runner-fargate-driver:latest
  • In the Environment variables field, insert one entry for each environment variable expected by the container image (refer to the previous section) and set the correct value for your project. I.e:
Image exemplifying some environment variable fields filled with non-real values
  • Click Add

8. Click Create.

9. Click View task definition.

Note: Please confirm the Task Role (in our case ecsTaskExecutionRole) has the necessary ECS permissions (i.e: the AmazonECS_FullAccess policy attached).

Step 3: Start a new Fargate Task based on the Runner manager Task Definition

Now you need to start a new Fargate Task to run the Runner manager container.

  1. Go to Task Definitions in your AWS project.
  2. Click on the Task Definition you created in the last step.
  3. Click on the revision.
  4. Click on the Actions button and in the menu select Run Task.
  5. Fill the mandatory fields with the correct values.
  6. Click in Run Task.

After you started the Fargate Task, the Runner manager in the task will use the GitLab registration token specified in the Task Definition to register itself as a runner of that GitLab project. If everything works as expected, after a few seconds your Runner manager will be ready for processing pipelines. If you stop the task, the Runner manager will try to deregister itself from your GitLab project before finalizing it.

Note: if you need to autoscale your Runner manager, instead of creating a single task as described in this step, you can create an ECS Service for your task definition. If this is your case, please refer to the AWS guide for creating a service.

Step 4: Check the Security Group rules for SSH connections

In the previous step, you started the Fargate Task for the Runner manager. You will need to ensure the container in this Task will be able to connect to the CI Job Fargate Task container using SSH. For this, check the inbound rules in the related Security Group.

Note: both the Security Group and CI Job Fargate Task were not created as part of the present article (refer to "Our premises" section for more information).

  1. Go to Security Groups in your AWS project.
  2. Click in the Security Group used by the CI Job Fargate Task.
  3. Ensure there is an inbound rule to allow TCP connections to port 22. Also check if the source IP range includes your GitLab Runner container IP or is set to allow connections from any IP (i.e: 0.0.0.0/0).

Step 5: Test the configuration

At this point, you should be able to trigger your pipeline and see your CI jobs processed in AWS Fargate.

  1. In your GitLab project, go to the CI/CD menu and click in Pipelines.
  2. Click in Run Pipeline.
  3. Select the correct branch in the Run for field and add any variable your build requires in the Variables field.
  4. Click Run Pipeline.

Conclusion

This article presented a tutorial on how to deploy GitLab Runner and Fargate driver in AWS Fargate. I focused on the basics to make it as clear and simple as possible, but I encourage the reader to check AWS Fargate documentation for more detailed and advanced configurations.

I hope you found this article helpful and thanks for reading!

--

--

Daniel Coutinho de Miranda
CI&T
Writer for

software engineer & google cloud certified architect