Skip to content

Feedback - Lab 1

Basic markdown formatting

While your report can be brief, please put some effort into formatting your text. Use headings, lists, code blocks, ... to make your report more readable. Check up on the GitLab markdown guide or use the built-in editor tools.

We want to emphasize formatting of code specifically, and the distinction between:

  • Inline code: ./mvnw clean package
  • Code blocks:
    gitlab-job:
      stage: build
      script:
        - echo "Hello, world!"
    

The same goes for all your issues, merge requests, wiki pages, etc. Any form of written media should have proper formatting. It is not only more pleasant to read, it also shows you put some effort into it.

Predefined variables

The ideal setup of your predefined variables would be:

variables:
  QUARKUS_CONTAINER_IMAGE_USERNAME: ${CI_REGISTRY_USER}
  QUARKUS_CONTAINER_IMAGE_PASSWORD: ${CI_REGISTRY_PASSWORD}
  QUARKUS_CONTAINER_IMAGE_REGISTRY: ${CI_REGISTRY}
  QUARKUS_CONTAINER_IMAGE_GROUP: ${CI_PROJECT_PATH}

Assuming our project is hosted at gitlab.stud.atlantis.ugent.be/myusername/devops-project, myusername being the project namespace, devops-project being the project name.

Some used CI_REGISTRY_IMAGE which resolves to gitlab.stud.atlantis.ugent.be:5050/myusername/devops-project as value for the registry. Jib will error here because myusername/devops-project is the group and it expects the top level registry here. The correct variable to use is CI_REGISTRY which resolves to gitlab.stud.atlantis.ugent.be:5050 in this case.

The best option for image group is CI_PROJECT_PATH as it combines both the project namespace and name, in our example myusername/devops-project. A more laborious but equally valid option would be to manually combine CI_PROJECT_NAMESPACE and CI_PROJECT_NAME with a / in between.

Some students used $GITLAB_USER_LOGIN/$CI_PROJECT_NAME instead, which in this case worked out because you are working in your personal namespace and you are the only one triggering pipelines. However, when we move this project to a group namespace, this will no longer work and when a collaborator triggers the pipeline, it will fail as well:

GITLAB_USER_LOGIN: The unique username of the user who started the pipeline, unless the job is a manual job. In manual jobs, the value is the username of the user who started the job.

Using predefined variables where possible

To avoid writing the same variables over and over again, it is best to use the predefined variables where possible. We can use the predefined variables from our container building job to link to our logic container in the run-game job. We could even define our own variables to make it more readable and easy to manage:

variables:
  ...
  # Using this variable we can easily change the registry for all our images if needed
  # This is not necessarily helpful in this case but we include it here as an illustration
  UTIL_REGISTRY: ${CI_REGISTRY}/utils/docker

run-game:
  stage: execute
  image:
    name: ${UTIL_REGISTRY}/devops-runner:latest
    entrypoint:
      - ""

  services:
  # Here we could also just use ${CI_REGISTRY_IMAGE}/logic-service:latest
    - name: ${CI_REGISTRY}/${CI_PROJECT_PATH}/logic-service:latest
      alias: logic-service

Store variables where they are needed

Variables can be defined globally or per job. Defining all the QUARKUS_CONTAINER_IMAGE_* variables globally is not necessary, as they are only needed in the maven-docker-generation job. Only move them to the global scope if they are used in multiple jobs and you are sure their presence won't affect other jobs in a negative way, an example:

If we would define QUARKUS_CONTAINER_IMAGE_BUILD: true and QUARKUS_CONTAINER_IMAGE_PUSH: true globally, it would be defined in all jobs. When we would later add a separate phase to our pipeline where we run mvn package (say we only want to build a JAR and deploy it somewhere or save it as an artifact), this command would build and push a container image as well, which is not what we want.

Always include MVN_CLI_OPTS in maven commands

In the build-game job we show you should use ./mvnw $MVN_CLI_OPTS compile, which contains the --batch instruction to make sure the build does not hang on user input and use defaults instead, it will also omit printing progress bars so you pipeline output isn't polluted.

Most students didn't think to include this option in the maven-docker-generation jobs and just called ./mvnw package or ./mvnw install without it. This is not a problem in this case because the build is not interactive, but it does result in a less readable log output. It is good practice to always include it when using mvn in a CI/CD pipeline.

Removing unnecessary debug output

We advised you to use a printenv and echo command to investigate the predefined variables, but you should remove this command after you are done with it. It is not necessary to keep it in your pipeline and it will only pollute your output. Same goes for other statements that were only necessary when debugging your pipeline.

Try to keep your finished pipeline as clean as possible.

Caching and artifacts

Most of you were able to add caching of dependencies and artifacts, which is great. The reason why we prefer using cache for dependencies and artifacts for build files is because of the way they are stored and how they can be used:

Cache

The cache is backed by storage close the runners, in our case the cache is a MinIO Object Store (S3).

Files in the cache can be used in subsequent jobs, but also across different pipeline runs. Meaning our first pipeline run will download all needed dependencies and store them in the cache. The next pipeline run will use the cache to fetch the dependencies, which is faster than downloading them from the internet.

This is why the cache has a key option. Our project can have different caches with different keys. A good default for a cache key is $CI_COMMIT_REF_SLUG or $CI_COMMIT_REF_NAME since these resolve into the branch or tag name for the current pipeline run. This way we can have a different cache for each branch or tag.

More examples of cache keys and common use cases can be found in the GitLab docs.

If we don't define a key, then our cache is global. This can lead to issues when you start working on multiple branches at the same time and these branches have differing dependencies. If for example someone is updating the dependencies in the main branch, but you are working on a feature branch, then your cache will be updated with the new dependencies as well. Your pipelines will still work, but they will have to download their dependencies again, which is not ideal.

Using a global cache

Many defined their cache globally, for their entire pipeline. However, the run-game job has no use for the cache, so downloading and checking it for updates will only slow down the pipeline.

Either define the cache explicitly for the jobs that make use of them, or define it globally and override the option in the jobs that don't need it like so cache: [].

Artifacts

Artifacts are only available in the current pipeline run. They are stored in the GitLab database and can be downloaded from the pipeline overview page. They are not available in subsequent pipeline runs (there are workarounds for this but they are not ideal).

Artifacts are best used to store build files, like JARs, WARs, etc. You can also use them to store logs, reports, etc. that you want to keep for a while.

Artifacts can be downloaded from GitLab, through Build > Artifacts or through the API. An expiration can be set so they only stay for a set period before they expire and are deleted. When moving onto releasing your software, these artifacts can easily be bundled into a release.

Artifacts and dependencies/needs

Jobs will download all artifacts from previous stages by default. Use of dependencies or needs enforces and controls which artifacts will be downloaded. Since our run-game job has no use for build files, we can prevent it from downloading the artifacts like so:

run-game:
  dependencies: []

Some used dependencies or needs to explicitly download the artifacts from the build-game job. This is not necessary, but it doesn't hurt to be specific. When your pipelines becomes more complex, it can be useful to explicitly define which artifacts are needed.

Not adding an alias for the service

In the run-game job, you need to add an alias for the service. This alias is used to reference the service in the LOGIC_URL variable. If you don't add an alias, the logic service will be only be reachable on its default hostname which is automatically constructed from the image URL (see GitLab docs on Accessing the services for more information)

Adding an alias overrides the default hostname and is as simple as adding the alias key to the service definition:

services:
  - name: ${CI_REGISTRY_IMAGE}/logic-service:latest
    alias: logic-service

Not adding the necessary dependencies

Some students had a pipeline run which was successful but didn't result in an image being pushed to their container registry. Looking in the job logs you can however spot these warnings:

[WARNING] [io.quarkus.config] Unrecognized configuration key "quarkus.jib.use-current-timestamp" was provided; it will be ignored; verify that the dependency extension for this configuration is set or that you did not make a typo
[WARNING] [io.quarkus.config] Unrecognized configuration key "quarkus.jib.base-jvm-image" was provided; it will be ignored; verify that the dependency extension for this configuration is set or that you did not make a typo

These warnings are caused by missing dependencies in the pom.xml file. The quarkus-container-image-jib extension is not included by default, so you have to add it yourself. This should have been set up in Building container images, where we build a container image locally. There we link to Quarkus docs which show the maven command to install the dependency. You can also add the dependency manually to the pom.xml file:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-container-image-jib</artifactId>
</dependency>