What are Gitlab Runners
Gitlab runners execute pipeline jobs. These runners are configured and registered inside a
gitlab runner application.
That application can be installed directly on the OS or a docker container.
The runners configuration is stored into /etc/gitlab-runner/config.toml
where each
runner is defined as a [[runners]]
entry.
General common issues
– The job is failing with a 403 error such as:
Getting source from Git repository 00:01 Fetching changes with git depth set to 20... Reinitialized existing Git repository in /builds/basic-projects-using-shared-ci/maven-docker-basic-example1/.git/ remote: You are not allowed to download code from this project. fatal: unable to access 'http://gitlab.david.com/basic-projects-using-shared-ci/maven-docker-basic-example1.git/': The requested URL returned error: 403 ERROR: Job failed: exit code 1 |
How to fix.
Normally we don’t need to set any credential in the gitlab runner configuration to allow git
cloning.
So trying to do that is not the right way.
A common issue is the user that triggers the job is not member of the project.
Not: even if we are administrator on the gitlab it doesn’t mean we are member of all
projects,it means we need to check in the project or the group precisely.
– The job is stuck with the following error message where foo-tag… is (are) the tags set for a stage defined in the yaml pipeline :
This job is stuck because you don't have any active runners online or available with any of these tags assigned to them: foo-tag..." Go to project CI settings |
How to fix.
case 1 : no runner at all is installed matching to that tag.
Solution : as suggested,
configure/add a runner for that tag.
case 2 : a runner is installed for that tag but it is not accessible for that project.
subcase 1 : the runner was installed specifically in another project/Settings/CICD and not in a
common group which the two project belong.
solution : delete the runner installed at the project level and install it at the
group level.
subcase 2 :the runner was installed in the common group but in Settings/CICD,
the « Enable shared runners for this group » was disabled.
solution : just enable it again.
– The docker runner fails eagerly with a error message concerning caching volume.
The message can look like :
ERROR: Preparation failed: adding cache volume: set volume permissions: running permission container "9802d1f1586765cef195531e7d5571911151d5ba698c2c4d70a251e9ea2e14c5" for volume "runner-84exxmn--project-23046492-concurrent-0-cache-c33bcaa1fd2c77edfc3893b41966cea8": starting permission container: Error response from daemon: error evaluating symlinks from mount source "/var/lib/docker/volumes/runner-84exxmn--project-23046492-concurrent-0-cache-c33bcaa1fd2c77edfc3893b41966cea8/_data": lstat /var/lib/docker/volumes/runner-84exxmn--project-23046492-concurrent-0-cache-c33bcaa1fd2c77edfc3893b41966cea8: no such file or directory (linux_set.go:105:0s) |
That kind of error may happen when a docker volume used for gitlab for caching resources was
dropped.
How to fix : Recreate the docker volume on the hosts with the folder _data as child
folder such as :
"9802d1f1586765cef195531e7d5571911151d5ba698c2c4d70a251e9ea2e14c5"/_data
.
– gitlab runner job fails eagerly with a error message concerning disk space missing in a
subfolder of /var/lib/docker/overlay2/ while the directory contains a lot of space.
The message looks like : :
ERROR: Job failed (system failure): Error response from daemon: error creating overlay mount to /var/lib/docker/overlay2/15454/545454.... no space left on device (docker.go:787:0s)
How to fix :
-check first with df that the /var/lib/docker/overlay2/ folder of the host where the gitlab
runner is run has enough space
Double validate it with a dd to generate for example a file of 2GB.
– if no space issue detected : try by deleting/restarting the gitlab runner. If not working
restart docker too
Install a runner application running as a docker container
Create a docker runner application instance (here with a volume):
docker run -d --name gitlab-runner --restart always \ -v /var/run/docker.sock:/var/run/docker.sock \ -v gitlab-runner-config:/etc/gitlab-runner \ gitlab/gitlab-runner:latest |
The volume config contains the gitlab runner app configuration (mainly the .toml file) and we map
the docker socket of the host to allow the runners that work with docker executor to
start/stop/rm docker containers.
Register a runner instance with interactive form :
docker run --rm -it -v gitlab-runner-config:/etc/gitlab-runner gitlab/gitlab-runner:latest register |
Here we are prompted to enter :
– the gitlab url
– the registration token retrievable since the Gitlab GUI settings/CDI/Runners.
– the executor type (docker, kubernetes, custom, shell, virtualbox, …)
– the base image
Register a runner instance with non-interactive form :
Here it is an example to create a runner allowing to use the docker client via the docker socket
binding of the host :
docker run --rm -it \ -v gitlab-runner-config:/etc/gitlab-runner \ -v /var/run/docker.sock:/var/run/docker.sock \ gitlab/gitlab-runner:latest register \ --non-interactive \ --executor "docker" \ --docker-image "docker:19.03.12" \ --url "https://gitlab.com" \ --registration-token "ijEKCYos8s4hPv7J5DXW" \ --description "docker-runner" \ --tag-list "docker" \ --access-level="not_protected" |
Restart the runner application (not required generally because that performs hot
changes) :
docker restart gitlab-runner
Enable Docker commands in your CI/CD jobs
There are 4 ways to enable the use of docker build and docker run during jobs, each with their
own tradeoffs :
– The shell executor. Here job scripts are executed as the gitlab-runner user of the Gitlab
runner machine/server and it requires to install Docker Engine on that machine/server.
– The Docker executor with the Docker image (Docker-in-Docker)
– Docker socket binding
– kaniko. It is a Google tool that doesn’t depend on a Docker daemon and executes each command
within a Dockerfile completely in userspace
To use the Docker-in-Docker way or the Docker socket binding, we need to specify the
docker executor.
Here is a runner register with the docker socket binding way :
WARN : The
non-interactive form doesn’t work (in my case)! It created a docker+machine executor instead of
a docker executor. That is not visible in the config.toml but at runtime when the gitlab runner
starts : Preparing the « docker+machine » executor
docker run --rm -it \ -v gitlab-runner-config:/etc/gitlab-runner \ gitlab/gitlab-runner:latest register \ --non-interactive \ --executor "docker" \ --docker-image "docker:19.03.12" \ --docker-volumes /var/run/docker.sock:/var/run/docker.sock \ --url "https://gitlab.com" \ --registration-token "ijEKCYos8s4hPv7J5DXW" \ --description "docker-runner" \ --tag-list "docker" \ --access-level="not_protected" |
So favor the interactive form by selecting the docker executor when asked.
Finally we need to have in the config.toml a runner with a volume mount for the docker socket such as :
[[runners]] name = "docker-runner" url = "https://gitlab.com" token = "Kg-y8nNj3zsvx9Xs1DsM" executor = "docker" [runners.custom_build_dir] [runners.cache] [runners.cache.s3] [runners.cache.gcs] [runners.cache.azure] [runners.docker] tls_verify = false image = "docker:19.03.12" privileged = false disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"] pull_policy = "if-not-present" extra_hosts = ["gitlab.david.com:172.17.0.1"] shm_size = 0 |
pull_policy:
To use local images present on the docker host (scenario of docker with socket binding), we use
that option :
[runners.docker] pull_policy = "if-not-present" |
Helpful if the docker image specified with the FROM keyword of Dockerfile or the image property
specified in the gitlab-ci is a custom image that is not in the public registry.
Without that stick, we need to add the image in a private registry and to configure the gitlab
runner to use a certificate to access to that registry (see /etc/gitlab/certs folder in the
gitlab runner)
extra_hosts:
It mimics the add-host argument of the docker run command.
It adds mapping(s) « host:ip address » in the /etc/hosts
of the ephemeral container
started by the docker runner.
And here how to use docker in pipeline :
Docker Build: stage: docker-build tags: - docker # docker is the tag we specified for the runner image: docker:19.03.12 # optional, we can use the image defined in the runner script: - docker build -f docker/Dockerfile -t my-app . when: always only: refs: - master |
The docker in docker way is similar but requires two changes : no socket binding in the runner volume config, privileged mode to true and dind service conf. For now I didn’t manage to do it working.
Use docker-compose in your CI/CD jobs
There are several ways to use that.
A very simple is defining a stage relying on the docker image docker/compose:latest
(or better a specific version such as 1.27.4) and also declaring the docker dind service or a
gitlab runner mounted with the host docker socket.
Example with a gitlab runner mounted with the host docker socket :
Docker Build: stage: docker-build tags: - docker image: name: docker/compose:1.27.4 script: - docker-compose -v - docker-compose -f docker-compose-single-step.yml ps - docker-compose -f docker-compose-single-step.yml build when: always only: refs: - master |
Example with a docker dind service: …
Kubernetes executor
Use the Kubernetes executor to use Kubernetes clusters for your builds. The executor calls the Kubernetes cluster API and creates a pod for each GitLab CI job.
The Kubernetes executor divides the build into multiple steps:
Prepare: Create the Pod against the Kubernetes Cluster. This creates the containers required for the build and services to run.
Pre-build: Clone, restore cache, and download artifacts from previous stages. This step runs on a special container as part of the pod.
Build: User build.
Post-build: Create cache, upload artifacts to GitLab. This step also uses the special container as part of the pod.
Deploy to Kubernetes
Two ways to deploy on kubernetes :
– to use a runner with the Kubernetes executor and correct env variables in the runner
– to use a runner with any executor but using a kubernetes cluster configuration registered in
gitlab
Scenario 1 : gitlab ci native integration with an existing kubernetes cluster that we added in gitlab
Register an existing kubernetes cluster in gitlab
The cluster may be added at 3 distinct levels in gitlab :
– project
– group
– instance/global
In the Gitlab Kubernetes menu page, search the option « Add a Kubernetes cluster
integration ».
Here a preview with gitlab and k8s setup in local :
href= »http://myjavaadventures.com/blog/wp-content/uploads/2021/08/add-existing-k8s-in-gitlab1.png »> src= »http://myjavaadventures.com/blog/wp-content/uploads/2021/08/add-existing-k8s-in-gitlab1-1024×601.png »
alt= »add-existing-k8s-in-gitlab » width= »640″ height= »376″
class= »alignnone size-large wp-image-5309″ />
Fields to set :
– Kubernetes cluster name (required) – The name you wish to give the cluster.
– Environment scope (required) – The associated environment to this cluster.
– API URL (required) – It’s the URL that GitLab uses to access the Kubernetes
API. Kubernetes exposes several APIs, we want the “base” URL that is common to all of them. For
example, https://kubernetes.example.com rather than https://kubernetes.example.com/api/v1.
Get the API URL by running this command:
kubectl cluster-info | grep -E 'Kubernetes master|Kubernetes control plane' | awk '/http/ {print $NF}'
– CA certificate (required) – A valid Kubernetes certificate is needed to
authenticate to the cluster. We use the certificate created by default.
List the secrets with kubectl get secrets, and one should be named similar to
default-token-xxxxx. Copy that token name for use below.
Get the certificate by running this command:
kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode
Note : if the command returns the entire certificate chain, you must copy the Root CA
certificate and any intermediate certificates at the bottom of the chain.
– Token – GitLab authenticates against Kubernetes using service tokens, which
are scoped to a particular namespace.
The token used should belong to a service account with cluster-admin privileges.
To create this service account:
Create a file called gitlab-admin-service-account.yaml and deploy it with kubectl :
kubectl apply -f gitlab-admin-service-account.yaml
apiVersion: v1 kind: ServiceAccount metadata: name: gitlab namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: gitlab-admin roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: gitlab namespace: kube-system |
At last retrieve the token for the gitlab service account:
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep gitlab | awk '{print $1}')
– Project namespace (optional) – You don’t have to fill this in. By leaving it
blank, GitLab creates one for you.
Note:
Each project should have a unique namespace.
The project namespace is not necessarily the namespace of the secret, if you’re using a secret
with broader permissions, like the secret from default.
You should not use default as the project namespace.
If you or someone created a secret specifically for the project, usually with limited
permissions, the secret’s namespace and project namespace may be the same.
Define a pipeline in 3 stage : build a spring boot/redis app, build/tag/push a docker image to the gitlab container registry and finally deploys the image as a pod on kubernetes
Here is the complete pipeline :
stages: - maven-build - docker-build - deploy variables: MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository" CI_DEBUG_TRACE: "true" Maven Package: stage: maven-build tags: - maven3-jdk11 image: maven:3.6.3-jdk-11 cache: paths: - .m2/repository key: ${CI_COMMIT_REF_NAME} script: # we store GIT branch/commit/pipeline info into a file. Helpful for app version actuator or about->version information - echo { \"branch\":\"${CI_COMMIT_REF_NAME}\", \"commit\":\"${CI_COMMIT_SHA}\", \"pipelineId\":\"${CI_PIPELINE_ID}\"} > git-info.json - echo "git-info.json content =" && cat git-info.json - mv git-info.json ${CI_PROJECT_DIR}/src/main/resources - mvn $MAVEN_ARGS clean package # compute the the docker build version : if master, we take the maven artifact version without snapshot. Otherwise we take the take the branch name - > if [[ "${CI_COMMIT_REF_NAME}" = "master" ]]; then dockerImageVersion=$(mvn $MAVEN_ARGS -q -U -Dexpression=project.version -DforceStdout org.apache.maven.plugins:maven-help-plugin:3.2.0:evaluate) dockerImageVersion=${dockerImageVersion/-SNAPSHOT/} else dockerImageVersion=${CI_COMMIT_REF_NAME} fi - echo "dockerImageVersion=${dockerImageVersion}" - echo ${dockerImageVersion} > docker-image-version.json artifacts: name: ${CI_PROJECT_NAME}:${CI_COMMIT_REF_NAME} expire_in: 1 week paths: - ${CI_PROJECT_DIR}/target/dependency/ - ${CI_PROJECT_DIR}/docker-image-version.json when: always only: refs: - master Docker Build: stage: docker-build tags: - docker # image: docker:19.03.12 script: - cat docker-image-version.json - ls -R target/ - imageName="$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$(cat docker-image-version.json)" - docker build -f docker/Dockerfile -t $imageName . - docker login -u $CI_DEPLOY_USER -p $CI_DEPLOY_PASSWORD $CI_REGISTRY - docker tag $imageName $CI_REGISTRY/$imageName - docker push $CI_REGISTRY/$imageName when: always only: refs: - master Deploy in Kubernetes: stage: deploy tags: - docker # image: google/cloud-sdk:350.0.0 # image: registry.gitlab.com/gitlab-examples/kubernetes-deploy image: name: bitnami/kubectl:1.18.19 entrypoint: [""] # 1. by default the image cmd is set in the entrypoint as kubectl and gitlab runner executes and 2.gitlab runner executes the entrypoint of the image at the "step_script" stage. # "so it executes sth like "kubectl sh -c" # Which generates that error : unknown command "sh" for "kubectl" # Fix : removing the entrypoint environment: dev script: - kubectl config view --minify | grep namespace # create a secret in the current namespace (computed and created by gitlab runner) to allow to login to the container registry - kubectl create secret docker-registry gitlab-registry --docker-server="$CI_REGISTRY" --docker-username="$CI_DEPLOY_USER" --docker-password="$CI_DEPLOY_PASSWORD" --docker-email="$GITLAB_USER_EMAIL" -o yaml --dry-run=client | kubectl apply -f - # update the service account to use that secret to pull images - > kubectl -n spring-boot-docker-kubernetes-example-32-dev patch serviceaccount default -p '{"imagePullSecrets": [{"name": "gitlab-registry"}]}' - EPOCH_SEC=\"$(date +%s)\" - image_version=$(cat docker-image-version.json) - image="$CI_REGISTRY/kubernetes-projects/spring-boot-docker-kubernetes-example:${image_version}" - echo $CI_DEPLOY_USER - echo "EPOCH_SEC=$EPOCH_SEC" - echo "image_version=$image_version" - echo "image=$image" - kubectl get pods - cp k8s/deployment.yml k8s/deployment-dynamic.yml - sed -i s@__COMMIT_SHA__@$CI_COMMIT_SHA@ k8s/deployment-dynamic.yml - sed -i s@__DATE_TIME__@$EPOCH_SEC@ k8s/deployment-dynamic.yml - sed -i s@__JAR_VERSION__@$image_version@ k8s/deployment-dynamic.yml - sed -i s@__IMAGE__@$image@ k8s/deployment-dynamic.yml - sed -i s@__NAMESPACE__@$KUBE_NAMESPACE@g k8s/deployment-dynamic.yml - kubectl apply -f k8s/deployment-dynamic.yml when: always only: refs: - master |
And here the kubernetes yml that defines the resource to deploy.
Note that that is a template with some variable part that we set during the deploy stage before
effectively deploying the resources with kubectl.
apiVersion: v1 kind: Namespace metadata: name: __NAMESPACE__ --- # spring boot app apiVersion: apps/v1 kind: Deployment metadata: name: spring-boot-docker-kubernetes-example-sboot namespace: __NAMESPACE__ spec: replicas: 1 selector: #it defines how the Deployment finds which Pods to manage. matchLabels: # It may have complexer rules. Here we simply select a label that is defined just below in the pod template app: spring-boot-docker-kubernetes-example-sboot template: # pod template metadata: labels: #Optional : add a commit sha or a datime label to make the deployment to be updated by kubcetl apply even if the docker image is the same app: spring-boot-docker-kubernetes-example-sboot commitSha: __COMMIT_SHA__ dateTime: __DATE_TIME__ jarVersion: __JAR_VERSION__ spec: containers: - image: __IMAGE__ #Make imag variable name: spring-boot-docker-kubernetes-example-sboot command: ["java"] args: ["-cp", "/app:/app/lib/*", "-Djava.security.egd=file:/dev/./urandom", "-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8888", "davidxxx.SpringBootAndRedisApplication"] imagePullPolicy: Always # useful in dev because the source code may change but that we reuse the same dev docker image # imagePullPolicy: IfNotPresent # prevent pulling from a docker registry readinessProbe: httpGet: scheme: HTTP # We use the default spring boot health check readiness (spring boot 2.2 or +) path: /actuator/health/readiness port: 8090 initialDelaySeconds: 15 timeoutSeconds: 5 livenessProbe: httpGet: scheme: HTTP # We use the default spring boot health check liveness (spring boot 2.2 or +) path: /actuator/health/liveness port: 8090 initialDelaySeconds: 15 timeoutSeconds: 15 # it is informational (only ports declared in service are required). It is alike docker EXPOSE instruction # ports: # - containerPort: 8090 # most of the time : it has to be the same than NodePort.targetPort --- apiVersion: v1 kind: Service metadata: labels: app: spring-boot-docker-kubernetes-example-sboot # service name matters. It provides the way which other pods of the cluster may communicate with that pod name: spring-boot-docker-kubernetes-example-sboot namespace: __NAMESPACE__ spec: type: NodePort ports: - name: "application-port" # port.name is just informational targetPort: 8090 # port of the running app port: 8090 # Cluster IP Port nodePort: 30000 # External port (has to be unique in the cluster). By default Kubernetes allocates a node port from a range (default: 30000-32767) - name: "debug-port"# port.name is just informational targetPort: 8888 # port of the running app port: 8888 # Cluster IP Port nodePort: 30001 # External port (has to be unique in the cluster). By default Kubernetes allocates a node port from a range (default: 30000-32767) selector: app: spring-boot-docker-kubernetes-example-sboot ###------- REDIS---------### # Redis app --- apiVersion: apps/v1 kind: Deployment metadata: name: spring-boot-docker-kubernetes-example-redis namespace: __NAMESPACE__ spec: replicas: 1 selector: #it defines how the Deployment finds which Pods to manage. matchLabels: # It may have complexer rules. Here we simply select a label that is defined just below in the pod template app: spring-boot-docker-kubernetes-example-redis template: # pod template metadata: labels: app: spring-boot-docker-kubernetes-example-redis commitSha: __COMMIT_SHA__ #Optional : add a commit sha label to make the deployment to be updated by kubcetl apply even if the docker image is the same dateTime: __DATE_TIME__ spec: # Optional : hard constraint : force to deploy on a node that has as tag redis-data=true affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: redis-data operator: In values: - "true" # alternative : soft constraint : try to deploy on a node that has as tag redis-data=true # preferredDuringSchedulingIgnoredDuringExecution: # - weight: 1 # preference: # matchExpressions: # - key: redis-data # operator: In # values: # - "true" containers: - image: redis:6.0.9-alpine3.12 name: spring-boot-docker-kubernetes-example-redis # --appendonly flag allows to persist data into a file command: ["redis-server"] args: ["--appendonly", "yes"] # it is informational (only ports declared in service are required). It is alike docker EXPOSE instruction # ports: # - containerPort: 6379 # name: foo-redis volumeMounts: - mountPath: /data name: redis-data volumes: - name: redis-data hostPath: path: /var/kub-volume-mounts/spring-boot-docker-kubernetes-example-redis/_data --- apiVersion: v1 kind: Service metadata: labels: app: spring-boot-docker-kubernetes-example-redis # service name matters. It provides the way which other pods of the cluster may communicate with that pod name: spring-boot-docker-kubernetes-example-redis namespace: __NAMESPACE__ spec: type: ClusterIP ports: - name: "application-port" # port.name is just informational targetPort: 6379 # port of the running app port: 6379 # Cluster IP Port selector: app: spring-boot-docker-kubernetes-example-redis |
Pipeline explanations
Note : no explanation on the two first part
(maven build and docker build/tag/push) that is not the matter here.
Here how Gitlab ci native Kubernetes features works when a pipeline is executed.
1) The Gitlab runner relies on the native Kubernetes features only if the stage definition
define the environment
property. It defines in which kubernetes « conceptual »
environment the stage is currently executing.
2) The Gitlab runner creates a namespace (if not existing) for the project.
The namespace is a concatenation of project name + project id + environment
value we set in the stage.
For example a project named spring-boot-docker-kubernetes-example
with as id 32
and an environment
property set to dev
will make gitlab ci to
create/use the kubernetes namespace
spring-boot-docker-kubernetes-example-32-dev
.
3) The Gitlab runner uses that namespace to interact and deploy any resources on kubectl.
Kubernetes basic behavior :
When a namespace is created, Kubernetes creates automatically a service account for it, named
« default ».
We are so able to apply resources on that namespace (pod, deployment, service, secret,
volume…).
Kubernetes interaction with container registry :
To pull images from a private registry that requires an authentication, Kubernetes needs some
configuration .
Either by setting a imagePullSecrets field in the pod resource or by configuring the service
account of the current namespace with that information.
We will use the second way that is more generic and practical.
So in the first lines of the deploy stage we :
– create in that namespace a secret resource that allows to access to the registry
– update the service account of that namespace to use that secret to pull images.
Common issues with kubernetes
Problem : kubectl apply -f ...
doesn’t fail and Kubernetes creates
the pod but the pod is not READY and keeps as status : ImagePullBackOff along a « denied: access
forbidden » event .
kubectl describe pod foo-pod
returns something like :
Normal Scheduled 56s default-scheduler Successfully assigned spring-boot-docker-kubernetes-example-32-dev/spring-boot-docker-kubernetes-example-sboot-7b964b78b5-5wjps to david-virtual-machine Normal BackOff 24s (x3 over 53s) kubelet Back-off pulling image "registry.david.com:5050/kubernetes-projects/spring-boot-docker-kubernetes-example:1.0.0" Warning Failed 24s (x3 over 53s) kubelet Error: ImagePullBackOff Normal Pulling 10s (x3 over 54s) kubelet Pulling image "registry.david.com:5050/kubernetes-projects/spring-boot-docker-kubernetes-example:1.0.0" Warning Failed 10s (x3 over 53s) kubelet Failed to pull image "registry.david.com:5050/kubernetes-projects/spring-boot-docker-kubernetes-example:1.0.0": rpc error: code = Unknown desc = Error response from daemon: Head https://registry.david.com:5050/v2/kubernetes-projects/spring-boot-docker-kubernetes-example/manifests/1.0.0: denied: access forbidden Warning Failed 10s (x3 over 53s) kubelet Error: ErrImagePull |
Solution :
Create a secret docker-registry with kubectl.
Problem : gitlab runner looks for in the official docker registry
while we don’t want to
If we don’t use a docker registry for kube, the gitlab job with k8s may contact the official
docker registry access
and fail if not reachable.
Symptom :
Look for events in the gitlab runner process/container that shows a failed image pull.
Solution :
Manually pull the image on the docker node that runs the pod and set the
imagePullPolicy
attribute of the deployment/pod to never
to prevent
any error related to pulling.
Problem :
the kubernetes master contacted by the gitlab runner doesn’t manage to make the pod running. It
sticks to the pending state
Symptom :
ci pipeline is blocked with a error that loops forever like (where gitlab is the k8s namespace
used by the k8s serviceaccount to deploy a pod from the gitlab runner):
Waiting for pod gitlab/runner-t3jt2q9b-project-230-concurrent-aser6h to be running, status is Pending Waiting for pod gitlab/runner-t3jt2q9b-project-230-concurrent-aser6h to be running, status is Pending .... |
We can also see in the events of the runner namespace error events referencing a timeout issue :
51s Warning FailedCreatePodSandBox pod/runner-xxxxxxxx-project-115-concurrent-yyyyy Failed to create pod sandbox: rpc error: code = Unknown desc = failed to create a sandbox for pod "runner-t3jt2q6a-project-115-concurrent-0mfkf8": operation timeout: context deadline exceeded 50s Normal SandboxChanged pod/runner-xxxxxxxx-project-115-concurrent-yyyyy Pod sandbox changed, it will be killed and re-created. |
How to fix :
Step one: Diagnostic issues with below checks
Step two :
if something looks to prevent pod running (memory issue, node toleration issue, and so for…)
or appears blocking at the cluster level, fix that.
– Check details and logs of the pod (it has two containers : build and helper):
kubectl -n gitlab describe pod gitlab/runner-t3jt2q9b-project-230-concurrent-aser6h
kubectl -n gitlab logs gitlab/runner-t3jt2q9b-project-230-concurrent-aser6h helper
kubectl -n gitlab logs gitlab/runner-t3jt2q9b-project-230-concurrent-aser6h build
– Check events on the gitlab namespace and on the whole cluster
kubectl -n gitlab get events
kubectl get -A events
– Check the state of the kube-system components
kubectl -n kube-system logs kube-scheduler-hostnameofmaster
kubectl -n kube-system logs kube-apiserver-hostnameofmaster
kubectl -n kube-system logs kube-controller-manager-hostnameofmaster
and so for…
Problem : we encounter some logs such as below means that there
is an issue with creds/certs to access to the cluster:
error: You must be logged in to the server (the server has asked for the client to provide credentials ( pods/log kube-scheduler-hostnameofmaster) |
To fix that kind of issues:
– check the certificates validity
– restart kubelet
– if not enough kill kube-XXX docker containers (scheduler, apiserver, controller-manager).
Killing docker child containers should be enough (that is those not prefixed with « k8s_POD_ »).
Gitlab runner cache
Phsyical Location
Its location depends on the way which the runner application was installed : locally or with
docker.
Local install : stored under the gitlab-runner user’s home directory: /home/gitlab-runner/cache/<user>/<project>/<cache-key>/cache.zip
.
Docker install , stored on the docker host as a volume : /var/lib/docker/volumes/<volume-id>/_data/<user>/<project>/<cache-key>/cache.zip
.
About docker way : A distinct cache is created by user/project/cache-key and so a distinct
volume too. Volume ids have long and generated name such as : runner-84exxmn--project-23046492-concurrent-0-cache-3c3f060a0374fc8bc39395164f415a70
Caches/Volumes are created/read on the fly by the gitlab runner during the CI execution. So
these cache volumes are never mounted on the gitlab runner container.
Enable caching
No caching is enabled by default. We enable it thanks to the cache:
keyword.
Cache
is shared between pipelines of a same project. Caches are indeed not shared across
projects.
Caching availability for jobs
Caching may be declared/usable at pipeline level to make it to be available at every stage or at
some specific stage levels to make it to be available at some specific stages.
It depends on the declaration location of the cache:
keyword : stage(s) or root
level.
Caching scope
A Cache may be created for a specific branch or across different or even all branches.
We define it thanks to the key
attribute.
Caching content
We define directories and/or files to cache with the paths:
that accepts a list of
elements.
Example : caching the .m2/repository
folder with a distinct
cache for each branch/tag name and usable only in that specific stage :
Maven Package: stage: maven-build tags: - maven image: maven:3.6.3-jdk-11 cache: paths: - .m2/repository key: ${CI_COMMIT_REF_NAME} |