How to build Nx monorepo apps in Gitlab CI Runner - docker

I am trying to have a gitlab CI that performs the following actions:
Install yarn dependencies and cache them in order to don't have to yarn install in every jobs
Test all of my modified apps with the nx affected command
Build all of my modified apps with the nx affected command
Build my docker images with my modified apps
I tried many ways to do it in my CI and no one of them worked. I'm very stuck actually.
This is my actual CI :
default:
image: registry.gitlab.com/xxxx/xxxx/xxxx
stages:
- setup
- test
- build
- forge
.distributed:
interruptible: true
only:
- main
- develop
cache:
key:
files:
- yarn.lock
paths:
- node_modules
- .yarn
before_script:
- yarn install --cache-folder .yarn-cache --immutable --immutable-cache --check-cache
- NX_HEAD=$CI_COMMIT_SHA
- NX_BASE=${CI_MERGE_REQUEST_DIFF_BASE_SHA:-$CI_COMMIT_BEFORE_SHA}
artifacts:
paths:
- node_modules
test:
stage: test
extends: .distributed
script:
- yarn nx affected --base=$NX_BASE --head=$NX_HEAD --target=test --parallel=3 --ci --code-coverage
build:
stage: build
extends: .distributed
script:
- yarn nx affected --base=$NX_BASE --head=$NX_HEAD --target=build --parallel=3
forge-docker-landing-staging:
stage: forge
services:
- docker:20.10.16-dind
rules:
- if: $CI_COMMIT_BRANCH == "develop"
allow_failure: true
- exists:
- "dist/apps/landing/*"
allow_failure: true
script:
- docker build -f Dockerfile.landing -t landing:staging .
Currently here is what works and what doesn't :
❌ Caching don't work, it's doing yarn install in every jobs that got extends: .distributed
✅ Nx affected commands work as expected (test and build)
❌ Building the apps with docker is not working, i have some trouble with docker in docker.

Problem #1: You don't cache your .yarn-cache directory, while you explicitly set in in your yarn install in before_script section. So solution is simple - add .yarn-cache to your cache.paths section
Regarding
it's doing yarn install in every jobs that got extends: .distributed
It is intended behavior in your pipeline, since "extends" basically merges sections of your gitlab-ci config, so test stage basically uses the following bash script in runner image:
yarn install --cache-folder .yarn-cache --immutable --immutable-cache --check-cache
NX_HEAD=$CI_COMMIT_SHA
NX_BASE=${CI_MERGE_REQUEST_DIFF_BASE_SHA:-$CI_COMMIT_BEFORE_SHA}
yarn nx affected --base=$NX_BASE --head=$NX_HEAD --target=test --parallel=3 --ci --code-coverage
and build stage differs only in one last line
When you'll cache your build folder - install phase will be way faster.
Also in this case
artifacts:
paths:
- node_modules
is not needed, since it will come from cache. Removing it from artifacts will also ease the load on your gitlab instance, node_modules is usually huge and doesn't really make sense as an artifact.
Problem #2: What is your artifact?
You haven't provided your dockerfile or any clue on what is exactly produced by your build steps, so i assume your build stage produces something in dist directory. If you want to use that in your docker build stage - you should specify it in artifacts section of your build job:
build:
stage: build
extends: .distributed
script:
- yarn nx affected --base=$NX_BASE --head=$NX_HEAD --target=build --parallel=3
artifacts:
paths:
- dist
After that, your forge-docker-landing-staging job will have an access to your build artifacts.
Problem #3: Docker is not working!
Without any logs from your CI system, it's impossible to help you, and also violates SO "one question per question" policy. If your other stages are running fine - consider using kaniko instead of docker in docker, since using DinD is actually a security nightmare (you are basically giving root rights on your builder machine to anyone, who can edit .gitlab-ci.yml file). See https://docs.gitlab.com/ee/ci/docker/using_kaniko.html , and in your case something like job below (not tested) should work:
forge-docker-landing-staging:
stage: forge
image:
name: gcr.io/kaniko-project/executor:v1.9.0-debug
entrypoint: [""]
rules:
- if: $CI_COMMIT_BRANCH == "develop"
allow_failure: true
- exists:
- "dist/apps/landing/*"
allow_failure: true
script:
- /kaniko/executor
--context "${CI_PROJECT_DIR}"
--dockerfile "${CI_PROJECT_DIR}/Dockerfile.landing"
--destination "${CI_REGISTRY_IMAGE}:landing:staging"

Related

Gitlab pipeline running very slow

I am using Gitlab as my DevOps platform and running pipeline in docker container. So i am using docker executor and my runner is running as a docker container.
Below is my gitlab-ci.yml file which is doing nothing but npm install cypress
stages:
- release
release:
image: node:12.19.0
stage: release
only:
refs:
- master
- alpha
- /^(([0-9]+)\.)?([0-9]+)\.x/
- /^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?$/
before_script:
- export http_proxy=http://17.14.45.41:8080/
- export https_proxy=http://17.14.45.41:8080/
- echo 'strict-ssl=false'>>.npmrc
script:
# - npm ci
- npm install cypress
When i run this job, it takes almost 12 minutes which is hell lot of time. My Gitlab is self hosted and I am using proxy to talk to outside world but I don't think proxy has any issue because when i do docker pull it works fine and runs instantly.
I don't know if there is anything I could do or I am missing in Gitlab configuration but if anyone has any ideas please let me know. That will be great help.
I don't know your project and if you have too much dependencies do download and install.
To improve the performance, you need to use the cache https://docs.gitlab.com/ee/ci/caching/ feature of gitlab
but, before doing it, you need to configure the cypress cache folder using the environment variable CYPRESS_CACHE_FOLDER https://docs.cypress.io/guides/getting-started/installing-cypress.html#Environment-variables, look at my example below
CYPRESS_CACHE_FOLDER: '$CI_PROJECT_DIR/cache/Cypress'
I'm telling to cypress to download all the dependencies and binaries to this specific folder, and after that, I configured the gitlab to cache this folder
stage: ci
cache:
paths:
- cache/Cypress
In your case your .gitlab-ci.yml file will be
stages:
- release
release:
image: node:12.19.0
variables:
CYPRESS_CACHE_FOLDER: '$CI_PROJECT_DIR/cache/Cypress'
stage: release
cache:
paths:
- cache/Cypress
only:
refs:
- master
- alpha
- /^(([0-9]+)\.)?([0-9]+)\.x/
- /^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?$/
before_script:
- export http_proxy=http://17.14.45.41:8080/
- export https_proxy=http://17.14.45.41:8080/
- echo 'strict-ssl=false'>>.npmrc
script:
# - npm ci
- npm install cypress
But don't forget you need to configure the cache depending of executor you are using. The details about it you can get from the gitlab docs

First job in second stage contains previous job's state in GitLab

I have run across an issue where the first job in my test stage receives the entire state of my previous build job when I don't expect it too. I've simplified the problem down as much as possible in order to still reproduce the issue. Essentially, build runs and checks out my repo. ls -al prints the working directory out which contains my checked out repo. test-one runs, skips the git checkout, and prints the working directory which contains the git checkout from the previous build job. test-two runs, skips the git checkout, and prints the working directory which contains an empty directory as expected.
My questions are, did I configure my .gitlab-ci.yml file incorrectly, do my expectations lack understanding of how GitLab should work, is GitLab or the runner improperly configured, or is this a bug?
Things to note:
I am using GitLab version: 12.6.4-ee
I am NOT the GitLab administrator
The jobs do not depend on each other and the scripts simply print the current working directory.
My docker image is simply a clean base image of a Linux distro with nothing but common Linux commands installed.
linux-runner is a GitLab-runner running as a service on my Linux server.
I disable git from checking out my repo in the test jobs in order to see that the checkout from the build job is present in test-one.
Below is my .gitlab-ci.yml file simplified:
image: $DOCKER_REGISTRY/my-image:latest
build:
stage: build
tags:
- linux-runner
script:
- ls -al
test-one:
stage: test
tags:
- linux-runner
variables:
GIT_STRATEGY: none
script:
- ls -al
test-two:
stage: test
tags:
- linux-runner
variables:
GIT_STRATEGY: none
script:
- ls -al
This appears to be caused by my usage of GIT_STRATEGY: none. By removing the variables I began to receive expected behavior once again.
image: $DOCKER_REGISTRY/my-image:latest
stages: # <-- add them all
- build
- test1
- test2
build:
stage: build
tags:
- linux-runner
script:
- ls -al
test-one:
stage: test1 # <-- change this
tags:
- linux-runner
variables:
GIT_STRATEGY: none
script:
- ls -al
test-two:
stage: test2 # <-- change this
tags:
- linux-runner
variables:
GIT_STRATEGY: none
script:
- ls -al
On your case test-one & test-two share the same "kind of" environment.
By setting up two different stages, they'll have their own environment

Is there a way to cache DockerHub image in bitbucket pipeline?

I am using external docker image from dockerhub.
In each step the dockerimage is pulled from dockerhub again and again. Yes it is desired workflow.
My question is can we cache this image, so that it wont pull from dockerhub in each step? This DockerImage is not going to change frequently, as it has only node and meteor as preinstalled.
So is it possible to cache the docker image?
Original bitbucket-pipeline.yml
image: tasktrain/node-meteor-mup
pipelines:
branches:
'{develop}':
- step:
name: "Client: Install Dependencies"
caches:
- node
script:
- npm install
- npm run setup-meteor-client-bundle
artifacts:
- node_modules/**
- step:
name: "Client: Build for Staging"
script:
- npm run build-browser:stag
artifacts:
- dist/**
- step:
name: "Client: Deploy to Staging"
deployment: staging
script:
- pipe: atlassian/aws-s3-deploy:0.2.2
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: $AWS_DEFAULT_REGION
S3_BUCKET: $S3_STAGING_BUCKET_NAME
LOCAL_PATH: 'dist'
ACL: "public-read"
DELETE_FLAG: "true"
EXTRA_ARGS: "--follow-symlinks --quiet"
- step:
name: "Server: Build and Deploy to Staging"
script:
- cd server
- mup setup --config=.deploy/mup-settings.stag.js
- mup deploy --config=.deploy/mup-settings.stag.js --settings=meteor-settings.stag.json
As the OP said in comments to the other answer, defining a Docker cache doesn't work for the build image itself
image: tasktrain/node-meteor-mup
which is always downloaded for each step and then the step scripts are executed in that image. Afaik, the Docker cache
services:
- docker
caches:
- docker
only works for images pulled or built in a step.
However, Bitbucket Pipelines has recently started caching public build images internally, according to this blog post:
Public image caching – Behind the scenes, Pipelines has recently started caching public Docker images, resulting in a noticeable boost to startup time to all builds running on our infrastructure.
There is also an open feature request to also cache private build images.
It is indeed possible to cache dependencies and docker is one of the pre-defined caches of Bitbucket Pipelines
pipelines:
default:
- step:
services:
- docker
caches:
- docker
script:
- docker pull my-own-repository:5000/my-image

GitLab: Mount /builds in build image as tmpfs

In our GitLab CI environment we have a build server with lots of RAM but mechanical disks, running npm install takes a long time (I have added cache but it still needs to chew through existing packages so cache cannot solve all of this alone).
I want to mount /builds in the builder docker image as tmpfs but I'm having a hard time figuring out where to put this configuration. Can I do that in the builder image itself or maybye in .gitlab-ci.yml for each project?
Currently my gitlab-ci.yml looks like this:
image: docker:latest
services:
- docker:dind
variables:
DOCKER_DRIVER: overlay
cache:
key: node_modules-${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
stages:
- test
test:
image: docker-builder-javascript
stage: test
before_script:
- npm install
script:
- npm test
I figured out that this was possible to solve by using the mount command straight in the before_script section, although this requires you to copy the source code over I managed to reduce the test time quite a lot.
image: docker:latest
services:
- docker:dind
variables:
DOCKER_DRIVER: overlay
stages:
- test
test:
image: docker-builder-javascript
stage: test
before_script:
# Mount RAM filesystem to speed up build
- mkdir /rambuild
- mount -t tmpfs -o size=1G tmpfs /rambuild
- rsync -r --filter=":- .gitignore" . /rambuild
- cd /rambuild
# Print Node.js npm versions
- node --version
- npm --version
# Install dependencies
- npm ci
script:
- npm test
Since I'm now using the npm ci command instead of npm install I have no use of the cache anymore since it's clearing the cache at each run anyway.
You probably want something like this to add a data volume on the runner:
volumes = ["/path/to/volume/in/container"]
https://docs.gitlab.com/runner/configuration/advanced-configuration.html#example-1-adding-a-data-volume
I'd probably use the second option from the article though, and add the data volume from a host container, in case your cache gets corrupted for some reason, since it will be easier to clean.
volumes = ["/path/to/bind/from/host:/path/to/bind/in/container:rw"]
I've done this for a composer cache before, and it works very well.
You should be able to set the cache for your npm using the following environment variable in your .gitlab-ci.yaml:
npm_config_cache=/path/to/cache
The other option is to use artifacts between builds, as outlined here: How do I mount a volume in a docker container in .gitlab-ci.yml?

Bitbucket Pipelines - How to use the same Docker container for multiple steps?

I have set up Continuous Deployment for my web application using the configuration below (bitbucket-pipelines.yml).
pipelines:
branches:
master:
- step:
name: Deploy to production
trigger: manual
deployment: production
caches:
- node
script:
# Install dependencies
- yarn install
- yarn global add gulp-cli
# Run tests
- yarn test:unit
- yarn test:integration
# Build app
- yarn run build
# Deploy to production
- yarn run deploy
Although this works, I would like to increase the build speed by running the unit and integration test steps in parallel.
What I've tried
pipelines:
branches:
master:
- step:
name: Install dependencies
script:
- yarn install
- yarn global add gulp-cli
- parallel:
- step:
name: Run unit tests
script:
- yarn test:unit
- step:
name: Run unit tests
script:
- yarn test:integration
- step:
name: Build app
script:
- yarn run build
- step:
name: Deploy to production
trigger: manual
deployment: production
script:
- yarn run deploy
This also has the advantage of seeing the different steps in Bitbucket including the execution time per step.
The problem
This does not work because for each step a clean Docker container is created and the dependencies are no longer installed on the testing steps.
I know that I can share files between steps using artifacts, but that would still require multiple containers to be created which increases the total execution time.
So my question is...
How can I share the same Docker container between multiple steps?
I've had the same issue a while ago and found a way to do it and I'm using it successfully right now.
You can do this using Docker's save and load along with BitBucket's Artifacts. You just need to make sure that your image isn't too large because BitBucket's Artifacts limit is 1GB and you can easily ensure this using multi stage-builds and other tricks.
- step:
name: Build app
script:
- yarn run build
- docker save --output <backup-file-name>.tar <images-you-want-to-export>
artifacts:
- <backup-file-name>.tar
- step:
name: Deploy to production
trigger: manual
deployment: production
script:
- docker load --input <backup-file-name>.tar
- yarn run deploy
You might also like to use BitBucket's caches which can make building Docker images much faster. For example, you can make it so that NPM packages are only installed when package.json and yarn.lock files change.
Further Reading
docker save (Docker 17): https://devdocs.io/docker~17/engine/reference/commandline/save/index
docker load (Docker 17): https://devdocs.io/docker~17/engine/reference/commandline/load/index
BitBucket Artifacts: https://confluence.atlassian.com/bitbucket/using-artifacts-in-steps-935389074.html
BitBucket Pipelines Caches: https://confluence.atlassian.com/bitbucket/caching-dependencies-895552876.html
Each step runs in its own Docker container, and its own volume. So you cannot have two steps running on the same build container.
Diving deeper into your problem
Are you trying to optimize for build minute consumption, or how long it takes for your build to complete?
If you're optimizing for build minutes, stick with what you have now. As the overhead of using multiple steps and artifacts will add some build minutes. But you'll lose out on the flexibility these features provide. Additionally, you can try ensure you're using a small Docker image for your build environment, as that will be pulled faster.
If you're optimizing for pipeline completion time, I'd recommend you go with your idea of using artifacts and parallel steps. While the total execution time is expected to be higher, you will be waiting for less time to see the result of your pipeline.
Possible solution which i recommend:
- step:
name: Install dependencies
script:
- yarn install
- yarn global add gulp-cli
Your first step above should be in a pre-build docker container, which you host on Docker Hub and use for deployment via image: username/deployment-docker:latest.
Then, both steps can use this container for their tests.

Resources