Building a CI/CD pipeline in GitLab
You can build a continuous integration / continuous delivery (CI/CD) pipeline using serverless products.
As a project example, we will use a web app based on Django
The project uses two environments:
prod
: Production environment available to users.testing
: Test environment used for testing the app before its release intoprod
.
For each environment, there is an individual folder in Yandex Cloud, as well as a separate set of static resources, such as the DB and service accounts. This isolates the environments from each other at the level of Yandex Identity and Access Management settings.
In addition, there is a common folder named infra
. It contains a Yandex Container Registry registry into which all the app's collected Docker images are published. The images are published under a separate service account named builder
. The service accounts of the prod
and testing
environments have limited permissions in the infra
folder and are only allowed to pull Docker images.
To build a CI/CD pipeline using serverless products:
- Create a GitLab instance.
- Configure GitLab.
- Create a GitLab Runner.
- Upload files to the GitLab repository.
- Create GitLab environment variables.
- Create the CI script configuration file.
- Check the result.
If you no longer need the resources you created, delete them.
Getting started
Download a project
Clone the yc-serverless-gitlab-ci-cd repository
git clone https://github.com/yandex-cloud-examples/yc-serverless-gitlab-ci-cd.git
Install additional dependencies
Install the following items in the local environment:
-
Python libraries listed in the
application/requirements.txt
project file:python -m pip install -r ./application/requirements.txt
Prepare the infrastructure
-
Go to the project directory and run the
bootstrap.sh
script by specifying your cloud ID:YC_CLOUD_ID=<cloud_ID> ./bootstrap.sh
The script will deploy the basic infrastructure and create YAML files with a description of the resources you created in the
config
directory. You can edit the script to create additional folders with required resources. For example, you can do it to add another test environment.Warning
Once completed, the script will output environment variables for GitLab and their values. Save them, as you will need them later.
-
- Service account for the resources with the editor role for the folder where the Yandex Managed Service for Kubernetes cluster is created. This service account will be used to create the resources required for the Managed Service for Kubernetes cluster.
- Service account for nodes with the container-registry.images.puller role for the folder with the Docker image registry. Nodes will pull the required Docker images from the registry on behalf of this account.
You can use the same service account for both operations.
-
Create a Managed Service for Kubernetes cluster and a node group. When creating the cluster, specify the previously created service accounts for the resources and nodes.
-
Configure security groups for the Managed Service for Kubernetes cluster.
-
Configure a security group for the Yandex Managed Service for GitLab instance.
Create a GitLab instance
Create a Managed Service for GitLab instance and a VM with a GitLab image on the same cloud network as the Managed Service for Kubernetes cluster.
Create a Managed Service for GitLab instance by following this guide.
Launch GitLab on a VM with a public IP.
-
On the folder page in the management console
, click Create resource and selectVirtual machine instance
. -
Under Boot disk image, in the Product search field, enter
Gitlab
and select a public GitLab image. -
Under Location, select an availability zone to place your VM in. If you do not know which availability zone you need, leave the default one.
-
Under Computing resources, navigate to the
Custom
tab and specify the required platform, number of vCPUs, and the amount of RAM:- Platform:
Intel Ice Lake
. - vCPU:
4
. - Guaranteed vCPU performance:
100%
. - RAM:
8 GB
.
- Platform:
-
Under Network settings:
- In the Subnet field, select the network and subnet to connect your VM to. If the required network or subnet is not listed, create it.
- Under Public IP, keep
Auto
to assign your VM a random external IP address from the Yandex Cloud pool or select a static address from the list if you reserved one in advance.
-
Under Access, select SSH key and specify the VM access data:
- Under Login, enter the username. Do not use
root
or other names reserved by the OS. To perform operations requiring superuser permissions, use thesudo
command. -
In the SSH key field, select the SSH key saved in your organization user profile.
If there are no saved SSH keys in your profile, or you want to add a new key:
- Click Add key.
- Enter a name for the SSH key.
- Upload or paste the contents of the public key file. You need to create a key pair for the SSH connection to a VM yourself.
- Click Add.
The SSH key will be added to your organization user profile.
If users cannot add SSH keys to their profiles in the organization, the added public SSH key will only be saved to the user profile of the VM being created.
- Under Login, enter the username. Do not use
-
Under General information, specify the VM name:
ci-tutorial-gitlab
. -
Click Create VM.
It may take a few minutes to create the VM. When the VM status changes to RUNNING
and GitLab starts, configure its settings.
Configure GitLab
To configure GitLab and enable Continuous Integration (CI), create a new project and enter the CI authorization parameters:
-
Log in to the Managed Service for GitLab instance web interface.
-
Click Create a project.
-
Click Create blank project.
-
Fill out the fields below:
- Project name:
gitlab-test
. - Project URL: Select the administrator user in the field next to the Managed Service for GitLab instance FQDN.
Leave the other fields unchanged.
- Project name:
-
Click Create project.
-
On the Yandex Compute Cloud page, select the created VM and copy its public IP.
-
Connect to the VM via SSH.
-
Get the GitLab administrator password using the following VM command:
sudo cat /etc/gitlab/initial_root_password
-
Copy the password (without spaces) from the
Password
row to the clipboard or a separate file. -
Open
http://<VM_public_IP_address>
in your browser. This will take you to the GitLab web interface. -
Log in using the administrator account:
- Username or email:
root
- Password: Password you copied earlier
If you are unable to log in, reset the administrator account password
. - Username or email:
-
Log in to the system again using the administrator account and the new password.
-
Select Create a project.
-
Set the project name:
gitlab-test
. -
Click Create project.
Create a GitLab Runner
To run build tasks in the Yandex Managed Service for Kubernetes cluster, create a GitLab Runner
Once it is installed, you can run automated builds inside your Managed Service for Kubernetes cluster.
For more information about installing and running GitLab Runner, see the GitLab documentation
Upload files to the GitLab repository
-
Clone the
gitlab-test
repository using SSH. -
Copy all files from the
yc-serverless-gitlab-ci-cd
repository togitlab-test
. -
Go to the
gitlab-test
directory. -
Index the new files:
git add .
-
Commit the files:
git commit -m "Add project files"
-
Push changes to the
gitlab-test
repository:git push
Create GitLab environment variables
-
In GitLab, go to Settings in the left-hand panel and select CI/CD from the drop-down list.
-
Click Expand next to Variables.
-
Add environment variables with the protection option disabled. All required variables and their values were output following the execution of the
bootstrap.sh
script:cloud_id
: Your cloud ID.CI_REGISTRY
: ID of the Container Registry registry in theinfra
folder prefixed with thecr.yandex/
prefix.CI_REGISTRY_KEY
:builder
service account key.cart_prod
: Name of the production folder in Yandex Cloud.DOCAPI_ENDPOINT_prod
: Document API endpoint of the Yandex Managed Service for YDB database in theprod
folder.PROD_SA_ID
: Thedeployer
service account ID in theprod
folder.SA_PROD_DEPLOYER_PRIVATE_KEY
:deployer
service account key in theprod
folder.prod_container_id
: ID of the Serverless Containers container in theprod
folder.cart_testing
: Name of the test folder in Yandex Cloud.DOCAPI_ENDPOINT_testing
: Document API endpoint of the Managed Service for YDB database in thetesting
folder.TESTING_SA_ID
:deployer
service account ID in thetesting
folder.SA_TESTING_DEPLOYER_PRIVATE_KEY
:deployer
service account key in thetesting
folder.
To add a variable:
- Click Add variable.
- In the window that opens, enter the variable name in the Key field and the value in the Value field.
- Disable the Protect variable option.
- Click Add variable.
Create the CI script configuration file
-
Open the
gitlab-test
project. -
Click
in the repository navigation bar and select New file from the drop-down menu. -
Name the file as
.gitlab-ci.yml
and add the following build stages to it:.gitlab-ci.yml
stages: - build - deploy-test-env - test - delete-test-env - release # Building a container image. build-job: stage: build # Using kaniko to create a container inside the container for enhanced security. image: name: gcr.io/kaniko-project/executor:debug entrypoint: [""] script: - mkdir -p /kaniko/.docker # Upload the container image to the registry. The image is marked with the commit hash. - echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$(echo -n "json_key:${CI_REGISTRY_KEY}" | base64 | tr -d '\n' )\"}}}" > /kaniko/.docker/config.json - >- /kaniko/executor --context "${CI_PROJECT_DIR}/application" --dockerfile "${CI_PROJECT_DIR}/application/Dockerfile" --destination "${CI_REGISTRY}/${CI_PROJECT_PATH}:${CI_COMMIT_SHORT_SHA}" # Deploying a test environment using the embedded container image. deploy-test-env-job: stage: deploy-test-env image: alpine:3.15 script: # Installing tools. - apk add -q --no-cache bash curl jq gettext - apk add yq --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community - curl --fail -silent --location --remote-name https://storage.yandexcloud.net/yandexcloud-yc/install.sh - bash install.sh -i /usr/local/yandex-cloud -n - ln -s /usr/local/yandex-cloud/bin/yc /usr/local/bin/yc # Authentication with a service account key. - echo "$SA_TESTING_DEPLOYER_PRIVATE_KEY" > key.json - yc config profile create sa-profile - yc config set service-account-key key.json # Setting up the variables to deploy API Gateway and creating a container. - export sa_id=$TESTING_SA_ID - export container_id=$(yc serverless container create --name testing --cloud-id ${cloud_id} --folder-name ${cart_testing} | yq .id) # Deploying a revision. - yc serverless container revision deploy --container-name testing --image "${CI_REGISTRY}/${CI_PROJECT_PATH}:${CI_COMMIT_SHORT_SHA}" --cores 1 --memory 512mb --concurrency 1 --execution-timeout 10s --cloud-id ${cloud_id} --folder-name ${cart_testing} --service-account-id ${TESTING_SA_ID} --environment DOCAPI_ENDPOINT=${DOCAPI_ENDPOINT_testing},DB_TABLE_PREFIX='' --secret environment-variable=AWS_ACCESS_KEY_ID,name=cart-app,key=AWS_ACCESS_KEY_ID --secret environment-variable=AWS_SECRET_ACCESS_KEY,name=cart-app,key=AWS_SECRET_ACCESS_KEY --secret environment-variable=SECRET_KEY,name=cart-app,key=SECRET_KEY # Setting up a template and deploying API Gateway. - (cat ${CI_PROJECT_DIR}/apigw.yaml.j2 | envsubst) > apigw.yaml - cat apigw.yaml - yc serverless api-gateway create --name testing --spec=apigw.yaml --description "created from gitlab CI" --cloud-id ${cloud_id} --folder-name ${cart_testing} - mkdir output - export gwdomain=$(yc serverless api-gateway get testing --cloud-id ${cloud_id} --folder-name ${cart_testing} | yq .domain) - echo "https://"$gwdomain>output/gwurl artifacts: paths: [output/] e2e-test-job: stage: test image: alpine:3.15 script: - apk add -q --no-cache bash curl - apk add yq --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community - cat output/gwurl - export gwurlvar=$(cat output/gwurl) - curl $gwurlvar load-test-job: stage: test image: alpine:3.15 script: - echo "Here goes load testing commands" - echo "Might even invoke bash with prepared bash script" - echo "Hello!" delete-test-env-job: stage: delete-test-env image: alpine:3.15 script: - apk add -q --no-cache bash curl jq gettext yq - curl --fail --silent --location --remote-name https://storage.yandexcloud.net/yandexcloud-yc/install.sh - bash install.sh -i /usr/local/yandex-cloud -n - ln -s /usr/local/yandex-cloud/bin/yc /usr/local/bin/yc - echo "$SA_TESTING_DEPLOYER_PRIVATE_KEY" > key.json - yc config profile create sa-profile - yc config set service-account-key key.json - yc serverless api-gateway delete testing --cloud-id ${cloud_id} --folder-name ${cart_testing} - yc serverless container delete testing --cloud-id ${cloud_id} --folder-name ${cart_testing} release-job: stage: release image: alpine:3.15 script: - apk add -q --no-cache bash curl jq gettext - curl --fail --silent --location -remote-name https://storage.yandexcloud.net/yandexcloud-yc/install.sh - bash install.sh -i /usr/local/yandex-cloud -n - ln -s /usr/local/yandex-cloud/bin/yc /usr/local/bin/yc - echo "$SA_PROD_DEPLOYER_PRIVATE_KEY" > key.json - yc config profile create sa-profile - yc config set service-account-key key.json - yc serverless container revision deploy --container-name prod --image "${CI_REGISTRY}/${CI_PROJECT_PATH}:${CI_COMMIT_SHORT_SHA}" --cores 1 --memory 512mb --concurrency 1 --execution-timeout 10s --cloud-id ${cloud_id} --folder-name ${cart_prod} --service-account-id ${PROD_SA_ID} --environment DOCAPI_ENDPOINT=${DOCAPI_ENDPOINT_prod},DB_TABLE_PREFIX='' --secret environment-variable=AWS_ACCESS_KEY_ID,name=cart-app,key=AWS_ACCESS_KEY_ID --secret environment-variable=AWS_SECRET_ACCESS_KEY,name=cart-app,key=AWS_SECRET_ACCESS_KEY --secret environment-variable=SECRET_KEY,name=cart-app,key=SECRET_KEY - container_id=${prod_container_id} # Creating a production environment. environment: name: production/$CI_COMMIT_SHORT_SHA
-
Add a comment to the commit in the Commit message field:
CI scripts
. -
Click Commit changes.
In the .gitlab-ci.yml
file, the following steps of the CI script are described:
- build: Building a Docker image using
Dockerfile
and pushing the image to Container Registry. - deploy-test-env: Deploying the application in test mode. In addition, the artifacts
mechanism for transferring data from one stage to another is described but not used. You can configure it, if required. - test: Testing the application. The tests involve E2E simulation and load testing. Describe and set up custom tests.
- delete-test-env: Deleting the test app.
- release: Deploying the app in production. This stage also uses deployment environments
. They are created and saved each time the pipeline is run successfully. Use them to restore and deploy the previous app version.
After you save the .gitlab-ci.yml
configuration file, the build script will start.
Check the result
To check the build script results, select Build on the left-hand panel in the gitlab-test
project, and then Pipelines from the drop-down menu. Make sure all the five stages complete successfully.
The app will be available at the URL of the API Gateway API gateway's service domain in the prod
folder. You can find the URL in the management consoledomain
field in the bootstrap.sh
script runtime log.
Delete the resources you created
Some resources are not free of charge. To avoid paying for them, delete the resources you no longer need:
- Delete the folders named
prod
,testing
, andinfra
together with their resources. - Delete the Managed Service for GitLab instance or the created VM with the GitLab image.
- Delete the Managed Service for Kubernetes cluster.
- If you reserved a public static IP address for the cluster, delete it.
- Delete the service accounts.