Am I right in guessing you found this page because you're looking for guidance on how to start a migration from Jenkins to GitLab CI? If the answer’s yes, you’ve come to the right place! But before diving in, let’s start with a quick introduction to what we’re dealing with when we talk about software pipelines.

According to Wikipedia: 

"A software pipeline is an automated process for executing defined stages in software development—build, test, deploy, and release—in a systematic, repeatable way."

By understanding what a pipeline is, you’ll see how the choice of a CI/CD tool impacts your workflow's efficiency and reliability. Since GitLab CI is your new tool of choice, this marks a new chapter in your software lifecycle and will also change your team’s ways of working.

Before attempting a 1:1 migration (which likely won’t be possible), I recommend taking a moment to pause and discuss with your team whether you’re satisfied with how your pipeline has been functioning so far. You can use various tools to facilitate this conversation, like our pipeline game!

Getting started with your migration

Now that you’ve defined and made your plans, what is the goal of the pipeline, and how does it bring value for you and the development team using the pipeline (the best outcome would be if you belong directly to the development team)? Let’s check what we need

To get started with a Jenkins to GitLab CI migration, I recommend having the following skills:

  • YAML
  • YAML
  • YAML
  • Domain understanding of what your team is doing

So why focus so much on YAML? The answer is simple: Everything in GitLab CI uses YAML syntax. The next part of the course is to understand what is needed from Jenkins in GitLab CI.

The Terminology of CI

Since GitLab CI is a completely different tool from Jenkins (technically, both are glorified cronjob runners), it’s important to agree on terminology.

Jenkins

GitLab

Difference

Agent/Node

GitLab runner

In Jenkins, an "agent" or "node" is a machine where jobs run. In GitLab, the equivalent is a "runner," which executes jobs defined in .gitlab-ci.yml. GitLab offers shared runners (hosted by GitLab) or self-managed runners.

Jenkinsfile

.gitlab-ci.yml

The Jenkinsfile defines the pipeline in Jenkins, typically using Groovy-based syntax. In GitLab CI, the pipeline is defined using the .gitlab-ci.yml file, which uses YAML syntax.

Stages and steps

Stages and jobs

A pipeline is divided into stages in both Jenkins and GitLab CI. In Jenkins, each stage contains multiple steps, while in GitLab CI, a stage contains jobs (which can run sequentially or in parallel).

Trigger / Build trigger

Pipeline triggers

Jenkins triggers jobs via SCM polling, webhook, or manual triggers. GitLab has similar options, such as webhooks, schedules, and manual pipeline runs, but GitLab also integrates with GitLab-specific events (e.g., push events).

Workspace

CI/CD Runner directory

In Jenkins, the workspace is a directory on the agent machine where jobs run. The runner's working directory functions similarly in GitLab, but the GitLab Runner manages it, and artifacts are stored differently (using GitLab's artifact storage).

Jenkins plugin

GitLab CI templates /components

GitLab CI minimizes dependency on external extensions, simplifying management. Components provide modularity, while templates enable reuse. Explore more at GitLab CI Catalog.

Artifacts

Artifacts

Both Jenkins and GitLab CI allow the storage of build artifacts (e.g., logs and reports). GitLab CI has a built-in feature for managing and downloading artifacts stored for a specific duration or pipeline job.

Jobs

Jobs

In Jenkins, a job represents a single unit of work (e.g., a build). In GitLab, a job is a single task within a pipeline stage. The key difference is how jobs are defined and executed in .gitlab-ci.yml vs. in Jenkins' job configuration.

 

The necessary environments

As we can see from the terminology, GitLab CI doesn’t have "built-in" or "nodes" or “agents” like Jenkins does. Instead, it uses runners. This is where your domain expertise comes into play, as you’ll need to consider your team’s specific requirements.

What environments do you need? You must set up your own runners if you're running GitLab on-premises. These can run directly on Linux, Windows, or macOS. You can also run them via Docker on these operating systems or leverage Kubernetes to orchestrate on-demand Docker runners in the background. If you've chosen GitLab.com (the SaaS approach), you can use public runners, depending on your company’s policies.

Now that you've defined your requirements, it’s time to evaluate your existing Jenkins nodes/agents. Check if they can be reused by installing the GitLab Runner or if it’s time to decommission them and send them to the great Server Valhalla, where all retired hardware finds peace.

Tips when converting from Jenkinsfile to GitLab CI yaml file

Before starting to .gitlab-ci.yaml file, take a look at your existing Jenkinsfiles, or freestyle jobs configurations.

Do you really understand what those do? If the answer is no, then I strongly recommend paying the technical debt first and using the tasker tool.

sh '''

if ! command -v node &> /dev/null; then

    curl -fsSL https://deb.nodesource.com/setup_${LTS}.x -o nodesource_setup.sh

            bash nodesource_setup.sh

            rm nodesource_setup.sh

            apt-get install -y nodejs

else

    node -v

fi

npm install -g grunt-cli

rm -rf node_modules && ||

npm cache clean --force && npm install -g npm@latest

npm -v

npm outdated --long

npm install

npm audit fix

npm ls --depth=0

npm cache verify

node -v

npm install --no-save eslint prettier jest

which node

which npm

npm ls -g --depth=0

'''

So this could be converted to e.g., sh 'npm run install-global-dependencies'

Now for the actual tips and tricks. There can be valid reasons for having multiline shell/bash scripts that just don’t justify being in a tasker or separate script file.

Multiline example from Jenkins

 sh '''

# Create a Python virtual environment

python3 -m venv venv

# Activate the virtual environment

source venv/bin/activate

# Install dependencies from requirements.txt

pip install -r requirements.txt

# Run the Python script

python my_script.py

# Deactivate the virtual environment

deactivate

'''

How to use artifacts from different stages

In Jenkinsfile, there is a step called stash and unstash to store build binaries and other artifacts for the pipeline run. In GitLab there is no such concept, but there is “artifacts” as a concept.

Jenkinsfile example:

stage('Build Python Project') {

steps {

    script {

        // Build the Python project using pdm

        sh '''

        pdm build

        '''

        // Stash the build artifacts (e.g., .whl or .tar.gz files)

        stash name: 'build-artifacts', includes: 'dist/*'

    }

}

}

 

stage('Deploy Python Project') {

steps {

    script {

        // Unstash the build artifacts from the previous stage

        unstash 'build-artifacts'

        // Install the built package from the dist folder

        sh '''

        pip install dist/*.whl

        python -m my_module

        '''

    }

}

}

GitLab CI yaml example:

build_python_project:

  stage: build

  script:

- pdm build  # Build the Python project

  artifacts:

paths:

  - dist/*  # Store the built artifacts (e.g., .whl, .tar.gz)

expire_in: 1 hour  # Set expiry time for the artifacts

deploy_python_project:

  stage: deploy

  dependencies:

- build_python_project  # This tells GitLab CI to use artifacts from the build stage

  script:

- pip install dist/*.whl  # Install the built wheel package

- python -m my_module  # Run the Python module

Post-build actions

In Jenkins, post-build actions are powerful. You can define them with additional steps, e.g., always, failure, cleanup, etc. In GitLab, you have only after_script, which can have conditionals, of course. One of the biggest things that I noticed in GitLab CI is that you can’t fail the pipeline with the after_script block. So, if you want your pipeline to fail, e.g., if your unit tests didn’t pass a high enough pass rate, then you need to do it in a script block instead of after_script.

Jenkinsfile example:

stage('Run JUnit Tests') {

  steps {

  script {

      sh 'mvn test'

  }

  }

  post {

  always {

      junit '**/target/test-classes/*.xml'

  }

  }

}

GitLab CI yaml example:

test:

  stage: test

  script:

- mvn test

  after_script:

- |

  if [ -f "target/test-classes/*.xml" ]; then

    echo "JUnit test results found. Archiving..."

    # Use GitLab's artifact feature to store the test results

    mv target/test-classes/*.xml /tmp/junit-test-results/

  else

    echo "No JUnit test results found."

            exit 1 # doesn’t affect the pipeline status

  fi

  artifacts:

paths:

  - /tmp/junit-test-results/*.xml

expire_in: 1 hour

There have been a couple of concrete examples of what you might encounter when doing a 1:1 migration from Jenkinsfiles to GitLab CI YAML. I highly recommend running YAMLLint locally against the .gitlab-ci.yml file before pushing it to the remote repository. Additionally, AI tools like GitHub Copilot can be useful for doing the heavy lifting once a basic understanding is established.

Jenkins plugins

“What about Jenkins plugins?” you may ask. Previous examples didn’t mention or use them because GitLab CI doesn’t rely on plugins. No more plugin management or issues with Maven/Gradle versions or Java dependencies!

Instead of plugins and shared libraries, GitLab CI uses templates! GitLab provides many predefined templates, which you can create and share across your organization.

Another alternative to plugins is to look for CLI tools for different use cases. For example, JFrog offers both an Artifactory Jenkins plugin and a CLI tool.

So, instead of creating your own custom tool, I recommend first checking if there is already a suitable tool available out of the box.

Summary

Before starting the actual work, I recommend taking the time to plan. Ask yourself and your team using the pipeline the following questions:

  • Is the current pipeline producing the output we need?
  • Is the current pipeline delivering value to us?
  • What is the most annoying thing about the current pipeline?

A change of tooling is like starting with an empty canvas, and that’s a situation that should be approached carefully.

Once the goals and gatekeepers of the pipeline are defined, compare what is needed from the old system. From there, begin the actual work. Also, take the time to familiarize yourself with GitLab CI/CD—understand what can be found and where. The web UI is quite different from Jenkins, so I recommend getting trained or, at the very least, reviewing some example videos.

And if you need help with the task, don’t hesitate to contact us!

Published: Dec 6, 2024

Software developmentCI/CDGitLab