{"id":4476,"date":"2022-05-27T13:58:00","date_gmt":"2022-05-27T11:58:00","guid":{"rendered":"https:\/\/blog.besharp.it\/?p=4476"},"modified":"2022-06-30T15:29:48","modified_gmt":"2022-06-30T13:29:48","slug":"a-serverless-approach-for-gitlab-integration-on-aws","status":"publish","type":"post","link":"https:\/\/blog.besharp.it\/a-serverless-approach-for-gitlab-integration-on-aws\/","title":{"rendered":"A serverless approach for GitLab integration on AWS"},"content":{"rendered":"\n
Cost optimization and operational efficiency are key value drivers for a successful Cloud adoption path; using managed serverless services significantly lowers maintenance costs while speeding up operations.<\/p>\n\n\n\n
In this article, you’ll find how to better integrate GitLab pipelines on AWS using ECS Fargate in a multi-environment scenario.<\/p>\n\n\n\n
GitLab offers a lot of flexibility for computational resources: pipelines can run on Kubernetes clusters, Docker, on-premise, or custom platforms using GitLab custom executor drivers.<\/p>\n\n\n\n
The tried and tested solution to run pipelines on the AWS Cloud uses EC2 instances as computational resources. <\/p>\n\n\n\n
This approach leads to some inefficiency: starting instances on-demand will make pipeline executions slower and developers impatient (because of the initialization time). Keeping a spare runner available for builds, on the other hand, will increase costs. <\/p>\n\n\n\n
We want to find a solution that can reduce execution time, ease maintenance and optimize costs.<\/p>\n\n\n\n
Containers have a faster initialization time and help decrease costs: billing will be based only on used build time. Our goal is to use them for our pipeline executions, they will run on ECS clusters. Additionally, we will see how to use ECS Services for autoscaling.<\/p>\n\n\n\n
Before describing our implementation, we need to know a few things: GitLab Runners are software agents that can execute pipeline scripts. We can configure a runner instance to manage the pipeline’s computational resources autoscaling by adding or removing capacity as demand for build capacity changes.<\/p>\n\n\n\n
In our scenario, we\u2019ll also assume that we have three different environments: development, staging, and production: we’ll define different IAM roles for our runners, so they will use the least privilege available to build and deploy our software.<\/p>\n\n\n\n
GitLab Runners have associated tags that help choose the environment that will run the execution step when defined in a pipeline.<\/p>\n\n\n\n
In this example, you can see a pipeline that builds and deploys in different environments:<\/p>\n\n\n\n
Let’s assume that our codebase uses NodeJS: we can build a custom generic Docker image with all the dependencies (including GitLab runner).<\/p>\n\n\n\n
#!\/bin\/sh \n \n# Create a folder to store the user's SSH keys if it does not exist. \nUSER_SSH_KEYS_FOLDER=~\/.ssh \n[ ! -d ${USER_SSH_KEYS_FOLDER} ] && mkdir -p ${USER_SSH_KEYS_FOLDER} \n \n# Copy contents from the `SSH_PUBLIC_KEY` environment variable \n# to the `$USER_SSH_KEYS_FOLDER\/authorized_keys` file. \n# The environment variable must be set when the container starts. \necho \"${SSH_PUBLIC_KEY}\" > ${USER_SSH_KEYS_FOLDER}\/authorized_keys \n \n# Clear the `SSH_PUBLIC_KEY` environment variable. \nunset SSH_PUBLIC_KEY \n \n# Start the SSH daemon \n\/usr\/sbin\/sshd -D<\/code><\/pre>\n\n\n\n
As you can see, there’s no environment-dependent configuration. <\/p>\n\n\n\n
Building a Runner for autoscaling (formerly Runner Manager)<\/strong><\/p>\n\n\n\n
This runner instance needs to be specialized to handle the environment configuration; we’ll use the Fargate Custom Executor provided by GitLab to interact and use different ECS Fargate Clusters for different environments.<\/p>\n\n\n\n
We’ll automatically handle our runner registration with the GitLab server during the Docker build phase by specifying its token using variables.<\/p>\n\n\n\n
Our Fargate custom executor will need a configuration file (“config.toml”) to specify a cluster, subnets, security groups, and task definition for our pipeline execution. We\u2019ll also handle this customization at build time.<\/p>\n\n\n\n
First, we need to get a registration token from our GitLab server: <\/p>\n\n\n\n
Go to your project CI\/CD settings and expand the “Runners\u201d section.<\/p>\n\n\n\n<\/figure>\n\n\n\n
<\/p>\n\n\n\n
Copy the registration token and GitLab server address<\/p>\n\n\n\n
You can embed the GitLab server address in your DockerFile; we’ll treat the registration token as a secret.<\/p>\n\n\n\n
As you\u2019ll see below, these lines will customize our configuration file:<\/p>\n\n\n\n
!\/bin\/bash\n\n# gitlab-runner data directory\nDATA_DIR=\"\/etc\/gitlab-runner\"\nCONFIG_FILE=${CONFIG_FILE:-$DATA_DIR\/config.toml}\n# custom certificate authority path\nCA_CERTIFICATES_PATH=${CA_CERTIFICATES_PATH:-$DATA_DIR\/certs\/ca.crt}\nLOCAL_CA_PATH=\"\/usr\/local\/share\/ca-certificates\/ca.crt\"\n\nupdate_ca() {\n echo \"Updating CA certificates...\"\n cp \"${CA_CERTIFICATES_PATH}\" \"${LOCAL_CA_PATH}\"\n update-ca-certificates --fresh >\/dev\/null\n}\n\nif [ -f \"${CA_CERTIFICATES_PATH}\" ]; then\n # update the ca if the custom ca is different than the current\n cmp --silent \"${CA_CERTIFICATES_PATH}\" \"${LOCAL_CA_PATH}\" || update_ca\nfi\n\n# launch gitlab-runner passing all arguments\nexec gitlab-runner \"$@\"<\/code><\/pre>\n\n\n\n
We can now push our Docker images to ECR repositories (we’ll use gitlab-runner and gitlab-runner-autoscaling as repository names); please refer to ECR documentation for push commands.<\/p>\n\n\n\n<\/figure>\n\n\n\n
<\/p>\n\n\n\n
<\/p>\n\n\n\n
Once we finish pushing, we can proceed to define task definitions. <\/p>\n\n\n\n
We’ll describe our configuration for the development environment only; configuration steps will be the same for every environment.<\/p>\n\n\n\n
You can find a complete guide on creating ECR repositories, task definitions, and services here<\/a>:<\/p>\n\n\n\n