Gitlab-CI Setup & .gitlab-ci.yml Examples
Adopting CI/CD
I already covered how to install Gitlab before. This time, I adopted a CI/CD tool. In projects I worked on in the past, we mainly used jenkins, but this time I decided to give Gitlab-CI a try. Given that most members of our project are Git beginners, choosing Gitlab-CI—which integrates tightly with Gitlab—seemed advantageous for spreading Git adoption. For a comparison of Gitlab-CI and jenkins, I referred to the links below. Of course, jenkins and Gitlab-CI aren’t really tools you can compare on the same footing in the first place.
Gitlab-CI versus jenkins
Gitlab-CI Setup
This post by All-Round Player explains it very well. You just have to follow along. It’s so good I could kiss the author. The official Gitlab-CI docs are thorough too. But you do have to configure what task the gitlab-runner will perform. This has to be written in .gitlab-ci.yml.
.gitlab-ci.yml Setup
Now let’s write a .gitlab-ci.yml so that the gitlab-runner performs the task I want. This isn’t very hard either. Each line under script: is one command in the executor you entered at step 7 of gitlab-runner register, where it asks Please enter the executor. For example, if you select shell as the executor and configure .gitlab-ci.yml with the script below,
script:
- echo hello
- dir
you get a result like the following.
hello # result of echo hello
c:\blah\blah\blah # result of dir
Example: Simple Deployment
Now let’s build a .gitlab-ci.yml that performs the simple task of copying & pasting the source code. only describes which git branch’s changes it reacts to.
For example,
only:
- master
runs only when there is a commit or change on the master branch.
After registering a gitlab-runner on the deployment target server, let’s run the .gitlab-ci.yml example below. When a push comes into the master branch or the develop branch, it runs the respective script below. On a Windows environment, I used shell as the executor and made use of the xcopy command.
# job that runs when a push comes into the master branch
deploy-to-production-server:
only:
- master
script:
- xcopy . D:\production_server\where_code_goes\ /h /k /y /e /r /d
# job that runs when a push comes into the develop branch
deploy-to-develop-server:
only:
- develop
script:
- xcopy . D:\develop_server\where_code_goes\ /h /k /y /e /r /d
It’s crude, but it works fine for simple deployment. Anyway, It works! But things get a bit more complicated when you’ve set up separate develop and production servers. Also, when there are many services in operation, it’s hard to register a gitlab-runner for each service.
Example: Remote Deployment Using Shared Runners
To solve the problems of the simple deployment example above, you can deploy by mounting the disk of a remote server with Shared Runners. The case where you register one gitlab-runner per project, as in the simple deployment example, is called Specific Runners. The difference between Shared Runners and Specific Runners is explained well in the official docs. Unless it’s a project that runs particularly complex scripts and needs special management, it’s more convenient to use Shared Runners. For how to configure Shared Runners, refer to here. The .gitlab-ci.yml below runs with shell as the executor on a Windows environment.
deploy to production:
stage: deploy # if you don't set this separately, it defaults to test
environment: # setting it separately per server makes rolling back easier later
name: production server
only:
- master
script:
- echo 'deploy to production server'
- 'net use o: \\server_IP\source_code_path server_login_PW /user:server_login_ID' # map the path of the remote server to deploy to as the o drive
- 'xcopy . o:\ /h /k /y /e /r /d' # deploy
after_script: # this after_script block always runs even if an error occurs while running script
- 'net use /delete o:' # disconnect the remote drive
deploy to develop:
stage: deploy
only:
- develop
- branches # sometimes you need to urgently push a branch and deploy it to the develop server
except:
- master # definitely separate it from the master branch. this is a block added for the example, so it's fine to remove it
environment:
name: develop server
script:
- echo 'deploy to develop server'
- 'net use o: \\server_IP\source_code_path server_login_PW /user:server_login_ID'
- 'xcopy . o:\ /h /k /y /e /r /d'
after_script:
- 'net use /delete o:'
Example: Applying stages and cache
In the example above, a single job was run for a single deployment. In this example, I added stage and cache.
- If you register stages, you can run multiple jobs sequentially.
- If you configure cache, you can use the cache created by a previously run job. So if you use it at a point like npm install, where you have to download libraries and such from the internet every time, you can cut down the deployment time. For details, refer to the official docs.
# the jobs run in the order deploy -> foreign_survey_build
# the reason deploy runs first is that npm install and build take a lot of time
stages:
- deploy
- foreign_survey_build
# run after_script after every job finishes
after_script:
- 'net use /delete o:'
# every job's name must be unique
deploy-to-dev:
stage: deploy # this is a job that runs in the deploy stage
environment:
name: development server
only:
- develop
- branches
except:
- master
script:
- echo 'deploy to develop server'
- 'net use o: \\server_IP\source_code_path server_login_PW /user:server_login_ID'
- 'xcopy . o:\ /h /k /y /e /r /d /exclude:.\gitlab-ci-copy-exclude-list.txt'
deploy-to-prod:
stage: deploy # there are 2 jobs with stage: deploy, and in this case the two jobs run in parallel
environment:
name: production server
only:
- master
script:
- echo 'deploy to production server'
- 'net use o: \\server_IP\source_code_path server_login_PW /user:server_login_ID'
- 'xcopy . o:\ /h /k /y /e /r /d /exclude:.\gitlab-ci-copy-exclude-list.txt'
foreign-survey-build-to-dev:
stage: foreign_survey_build
only:
- develop
- branches
except:
- master
environment:
name: development server
script:
- echo 'foreign_survey is being builded to develop server'
- cd foreign_survey\client # only some modules use npm, so I structured it this way
- call npm install # if there is a cache and package.json hasn't changed, it uses the cached files (stored compressed).
- call npm run build
- 'net use o: \\server_IP\source_code_path server_login_PW /user:server_login_ID'
- 'xcopy dist\. o:\foreign_survey\client\dist\ /h /k /y /e /r'
cache:
key: ${CI_COMMIT_REF_SLUG} # uses the cache created on this branch (master)
paths:
- foreign_survey/client/node_modules/ # defines the directory to cache
foreign-survey-build-to-prod:
stage: foreign_survey_build
environment:
name: production server
only:
- master
script:
- echo 'foreign_survey is being builded to production server'
- cd foreign_survey\client
- call npm install
- call npm run build
- 'net use o: \\server_IP\source_code_path server_login_PW /user:server_login_ID'
- 'xcopy dist\. o:\foreign_survey\client\dist\ /h /k /y /e /r'
cache: # cache checks for its existence while running the job, and caches once more after the script finishes running.
key: ${CI_COMMIT_REF_SLUG}
paths:
- foreign_survey/client/node_modules/
In the example above, the cache is stored based on the branch. But when there are multiple sub Node projects inside a single parent project and each builds at its own stage, storing the cache based on branch alone is useless. If you configure it as below, you can store the cache per stage & branch.
# ...
cache:
key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
# ...
For details on how to share cache within the same branch, please refer to here.
I sincerely hope you’ll be freed from deployment hell!
The end!
Leave a comment