Go Test Coverage Not Shown on Gitlab

If the coverage not shown on MR diffs:

  • ensure the giltlab.yaml job artifact field config is correct.
  • on the job logs the artifact is success upload.
  • the coverage report contents pointing the files to correct path (or relative path)

The gocover-cobertura output may contain absolute paths that the gitlab runner cannot match. So ensure the content of coverage reports using relative paths, by replace then using sed command.

For example, your module name in go.mod:


module my-app

go 1.23.2

Then you need to do sed command the test-coverage.xml file with:


$ sed -i "s=<source>.*</source>=<source>./</source>=g" test-coverage.xml
$ sed -i "s;filename=\"my-app/;filename=\";g" test-coverage.xml

The visualizer is shown on MR. Run the pipeline again and ensure it success. Open the Changes tab to show file diffs and see the coverage green marker next to the line number.

If still not shown, then download the test-coverage.xml artifact and check its contents to use correct relative path to root project.

Next, open the developer tools on your browser, open Network / XHR tab, then check the coverage_reports.json file contents. It should not be empty.

Good luck !

Setup Gitlab runner kubernetes executor on Private autopilot cluster GKE

Kenapa pakai autopilot cluster-nya Google Kubernetes Engine (GKE) adalah untuk meminimalisir cost dari penggunaan VM di Google Cloud Platform (GCE). Kenapa pakai private karena alasan keamanan. Cara membuat private autopilot cluster ada di link ini, berikut configurasi Cloud NAT.

Untuk mengakses private cluster selain menggunakan Cloud Shell, bisa dengan menggunakan VM yang VPC network sama dengan cluster-nya. Untuk mengetahui node tag firewall dari autopilot cluster, bisa dengan cara melihat existing firewall rule yang dibuat oleh Google, biasanya dengan nama rule <cluster_name>-<number>-all, atau <cluster_name>-<number>-master.

Cara installasi Gitlab Runner kubernetes bisa di link ini, dengan menggunakan helm chart. Untuk referensi values.yaml yang lengkap bisa lihat di sini.

Minimum values.yaml:

gitlabUrl: https://gitlab.com
runnerRegistrationToken: "${GITLAB_REGISTRATION_TOKEN}"
rbac:
  create: true
runners:
  tags: "${GITLAB_RUNNERS_TAGS}"
  runUntagged: false
  config: |
    concurrent = 10
    [[runners]]
      [runners.kubernetes]
        namespace = "{{ .Release.Namespace }}"
        image = "ubuntu:16.04"
        poll_timeout = 360

Atur nilai poll_timeout (dalam detik) karena ini waktu yang dibutuhkan sebuah job untuk menunggu pod ready bisa digunakan. Untuk autopilot cluster nilainya bisa lebih besar karena autopilot cluster perlu untuk menyiapkan node terlebih dahulu. Untuk konfigurasi RBAC, maka perlu konfigurasi ClusterRoleBinding:

kubectl create clusterrolebinding ${CRB_USER_CLUSTER_NAME} \
       --clusterrole cluster-admin \
       --user ${USER_AT_PROJECT_IAM}

Perintah di atas akan mem-binding user yang mengeksekusi helm dengan ClusterRole cluster-admin.

Setelah itu jalankan helm install:

helm install --namespace $NAMESPACE $RELEASENAME -f values.yaml gitlab/gitlab-runner

JOB POD RESOURCE CUSTOMIZATION

Setiap job yang jalan menggunakan resource CPU, RAM, dan Ephemeral Storage yang sama. Namun jika ada job yang perlu resource lebih maka bisa mengunakan gitlab variables pada job yang diinginkan, bisa di lihat di sini.

Agar bisa di-override maka di config.toml harus di-set nilai maximum dari resource yang di perbolehkan. Sehingga file values.yaml seperti ini:

...
runners:
  ...
  config: |
    ... 
    [[runners]]
      ...
      [runners.kubernetes]
        ...
        # build container
        cpu_limit = "2000m"
        memory_limit = "6Gi"

        # service containers
        service_cpu_limit = "1000m"
        service_memory_limit = "1Gi"

        # helper container
        helper_cpu_limit = "1000m"
        helper_memory_limit = "1Gi"

        # allow override
        cpu_limit_overwrite_max_allowed = "4000m"
        memory_limit_overwrite_max_allowed = "6Gi"
        cpu_request_overwrite_max_allowed = "4000m"
        memory_request_overwrite_max_allowed = "6Gi"
        ephemeral_storage_limit_overwrite_max_allowed = "10Gi"
        ephemeral_storage_request_overwrite_max_allowed = "10Gi"

        helper_cpu_limit_overwrite_max_allowed = "2000m"
        helper_memory_limit_overwrite_max_allowed = "2Gi"
        helper_cpu_request_overwrite_max_allowed = "2000m"
        helper_memory_request_overwrite_max_allowed = "2Gi"
        helper_ephemeral_storage_limit_overwrite_max_allowed = "10Gi"
        helper_ephemeral_storage_request_overwrite_max_allowed = "10Gi"

        service_cpu_limit_overwrite_max_allowed = "2000m"
        service_memory_limit_overwrite_max_allowed = "2Gi"
        service_cpu_request_overwrite_max_allowed = "2000m"
        service_memory_request_overwrite_max_allowed = "2Gi"
        service_ephemeral_storage_limit_overwrite_max_allowed = "10Gi"
        service_ephemeral_storage_request_overwrite_max_allowed = "10Gi"

Setup Cache Using Persistent Volume Claim (PVC)

Kubernetes runner tidak mendukung local cache secara biasa karena pod (beserta storage nya) untuk setiap job akan di hapus setelah job selesai. Maka perlu tambahan konfigurasi untuk cache ini menggunakan distributed cache, misal ke S3 atau GCS.

Namun ada pilihan lain dengan menggunakan storage dari Kubernetes yaitu Persistent Volume Claim. Setiap pod akan me-mount PVC storage ini sebagai cache direktori, seolah-olah sebagai local storage.

Karena tiap job yang jalan di pod tidak menggunakan node yang sama maka menggunakan tipe HostPath tidak memungkinkan apalagi dilarang pada autopilot cluster yang node-nya managed. Cache storage ini harus bisa dibaca-tulis bersamaan oleh lebih dari satu pods, maka perlu akses ReadWriteMany, bukan ReadWriteOnce. Untuk itu yang paling sederhana adalah dengan menggunakan NFS Server (NFS bukan Need For Speed) bisa dilihat di sini.

Maka perlu update values.yaml agar menggunakan cache dari VPC:

...
runners:
  ...
  config: |
    ... 
    [[runners]]
      ...
      cache_dir = "${CACHE_DIR}"
      [runners.kubernetes]
        ...
        [[runners.kubernetes.volumes.pvc]]
          name = "${PVC_NAME}"
          mount_path = "${CACHE_DIR}"

Troubleshooting

Karena pod akan hilang ketika job selesai, maka cara pertama adalah me-monitor pod ketika job di-retry, bisa dari Google Cloud Console atau dari kubectl logs atau kubectl top.

Selain itu bisa menggunakan kubectl get events -n $NAMESPACE, misal untuk mengetahui apakah resource yang digunakan kurang seperti ephemeral storage ketika job men-download packages.

Cache penuh akan membuat job failed. Mengosongkan cache tidak cukup menggunakan UI dari Pipelines Gitlab. Termasuk untuk branch yang dihapus, cache tidak ikut terhapus. Jadi harus hapus manual di NFS servernya, atau cache key nya di-optimalisasi misal menggunakan lock file dari package.