これは、なにをしたくて書いたもの?
GitLab CI/CDのRunnerとしてDocker Executorをよく使うのですが、ジョブのscript
がどのシェルで動作しているのかを知りたいと思いまして。
ドキュメントと実際の動作の2つを確認してみたいと思います。
GitLab CI/CDのDocker Executorで実行されるシェルはなにか?
たとえばGitLab CI/CDのチュートリアルにあるこういう.gitlab-ci.yml
があったとして、ここに書かれているscript
要素がどのシェルで実行されて
いるのかが気になる、という話です。
build-job: stage: build script: - echo "Hello, $GITLAB_USER_LOGIN!" test-job1: stage: test script: - echo "This job tests something" test-job2: stage: test script: - echo "This job tests something, but takes more time than test-job1." - echo "After the echo commands complete, it runs the sleep command for 20 seconds" - echo "which simulates a test that runs 20 seconds longer than test-job1" - sleep 20 deploy-prod: stage: deploy script: - echo "This job deploys something from the $CI_COMMIT_BRANCH branch." environment: production
Tutorial: Create and run your first GitLab CI/CD pipeline / Create a .gitlab-ci.yml file
答えとしてはここに書かれています。
Docker executor / Configure a Docker ENTRYPOINT
ジョブのスクリプトを実行するコンテナを起動して、shまたはbashをCOMMAND
として渡すようです。
It passes sh or bash as COMMAND to start a container that runs the job script.
つまり、以下と同等だと書かれています。
docker run <image> sh -c "echo 'It works!'" # or bash
GitLab Runnerのソースコードだと、こちらが該当しそうです。
execConfig := container.ExecOptions{ Tty: false, AttachStderr: true, AttachStdout: true, Cmd: append([]string{"sh", "-c"}, script...), }
ちなみに、デフォルトでは指定したDockerイメージのENTRYPOINT
をオーバーライドしないようです。
By default, the Docker executor doesn’t override the ENTRYPOINT of a Docker image.
ENTRYPOINT
が指定してあるDockerイメージで、script
を実行させるのが難しい場合は以下のようにENTRYPOINT
をオーバーライドする
必要があります。
image: name: my-image entrypoint: [""]
というわけで、どうやらscript
はshコマンドで実行されるようですが、実際どうなのか確認してみましょう。
環境
今回の環境はこちら。
$ sudo gitlab-rake gitlab:env:info System information System: Ubuntu 24.04 Current User: git Using RVM: no Ruby Version: 3.2.8 Gem Version: 3.6.9 Bundler Version:2.7.1 Rake Version: 13.0.6 Redis Version: 7.2.10 Sidekiq Version:7.3.9 Go Version: unknown GitLab information Version: 18.3.1 Revision: bccd1993b5d Directory: /opt/gitlab/embedded/service/gitlab-rails DB Adapter: PostgreSQL DB Version: 16.8 URL: http://192.168.0.6 HTTP Clone URL: http://192.168.0.6/some-group/some-project.git SSH Clone URL: git@192.168.0.6:some-group/some-project.git Using LDAP: no Using Omniauth: yes Omniauth Providers: GitLab Shell Version: 14.44.0 Repository storages: - default: unix:/var/opt/gitlab/gitaly/gitaly.socket GitLab Shell path: /opt/gitlab/embedded/service/gitlab-shell Gitaly - default Address: unix:/var/opt/gitlab/gitaly/gitaly.socket - default Version: 18.3.1 - default Git Version: 2.50.1.gl1
GitLabは192.168.0.6で動作しているものとします。
環境はTerraformで構築します。
$ terraform version Terraform v1.13.1 on linux_amd64
準備
最初にGitLabプロジェクトとGitLab Runnerのトークンの作成を行います。
terraform.tf
terraform { required_version = "1.13.1" required_providers { gitlab = { source = "gitlabhq/gitlab" version = "18.3.0" } } }
main.tf
variable "root_access_token" { type = string ephemeral = true } provider "gitlab" { token = var.root_access_token base_url = "http://192.168.0.6/" } resource "gitlab_group" "sample_group" { name = "sample group" path = "sample-group" visibility_level = "private" } resource "gitlab_project" "sample_app" { name = "sample-app" namespace_id = gitlab_group.sample_group.id default_branch = "main" visibility_level = "private" auto_devops_enabled = false only_allow_merge_if_pipeline_succeeds = true only_allow_merge_if_all_discussions_are_resolved = true } resource "gitlab_branch_protection" "main_branch" { project = gitlab_project.sample_app.id branch = "main" allow_force_push = false merge_access_level = "maintainer" push_access_level = "no one" unprotect_access_level = "maintainer" } resource "gitlab_group_membership" "sample_user" { group_id = gitlab_group.sample_group.id user_id = gitlab_user.sample_user.id access_level = "owner" } resource "gitlab_user" "sample_user" { name = "sample-user" username = "sample-user" password = "P@ssw0rd" email = "sample-user@example.com" } resource "gitlab_user_runner" "group_runner" { runner_type = "group_type" group_id = gitlab_group.sample_group.id description = "sample group runner" untagged = true } output "runner_authentication_token" { value = gitlab_user_runner.group_runner.token sensitive = true }
GitLabの操作に必要なアクセストークンは、環境変数で定義。
$ export TF_VAR_root_access_token=...
リソースを作成。
$ terraform init $ terraform apply
GitLab Runnerのトークンを確認して
$ terraform output runner_authentication_token "glrt-xxxxxxxxxx"
GitLab RunenrをGitLabに登録します。
$ RUNNER_TOKEN=... $ sudo gitlab-runner register \ --non-interactive \ --url "http://192.168.0.6/" \ --token "$RUNNER_TOKEN" \ --executor "docker" \ --docker-image ubuntu:24.04 \ --docker-privileged \ --docker-volumes "/certs/client" \ --description "sample group runner"
設定ファイルはこちら。
/etc/gitlab-runner/config.toml
concurrent = 1 check_interval = 0 shutdown_timeout = 0 [session_server] session_timeout = 1800 [[runners]] name = "sample group runner" url = "http://192.168.0.6/" id = 10 token = "glrt-xxxxxxxxxx" token_obtained_at = 2025-08-31T09:59:30Z token_expires_at = 0001-01-01T00:00:00Z executor = "docker" [runners.cache] MaxUploadedArchiveSize = 0 [runners.cache.s3] [runners.cache.gcs] [runners.cache.azure] [runners.docker] tls_verify = false image = "ubuntu:24.04" privileged = false disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/cache"] shm_size = 0 network_mtu = 0
これで準備はできました。
scriptがどのシェルで実行されているか確認してみる
それでは、script
に定義したスクリプトがどのシェルで実行されているのか確認してみましょう。
こんな.gitlab-ci.yml
を用意。
.gitlab-ci.yml
stages: - run ubuntu: stage: run image: ubuntu:24.04 script: - | echo -n 'script execution command: ' cat /proc/$$/cmdline | tr '\0' ' ' echo ls -l $(cat /proc/$$/cmdline | tr '\0' ' ') debian: stage: run image: debian:bookworm script: - | echo -n 'script execution command: ' cat /proc/$$/cmdline | tr '\0' ' ' echo ls -l $(cat /proc/$$/cmdline | tr '\0' ' ') rocky: stage: run image: rockylinux:9.3 script: - | echo -n 'script execution command: ' cat /proc/$$/cmdline | tr '\0' ' ' echo ls -l $(cat /proc/$$/cmdline | tr '\0' ' ') alpine: stage: run image: alpine:3.22 script: - | echo -n 'script execution command: ' cat /proc/$$/cmdline | tr '\0' ' ' echo ls -l $(cat /proc/$$/cmdline | tr '\0' ' ')
Ubuntu Linux、Debian、Rocky Linux、Alpine Linuxそれぞれのイメージで、どのシェルで実行されているのか確認します。
結果はこちら。
# Ubuntu Linux script execution command: /usr/bin/bash -rwxr-xr-x 1 root root 1446024 Mar 31 2024 /usr/bin/bash # Debian script execution command: /usr/bin/bash -rwxr-xr-x 1 root root 1265648 Apr 18 22:47 /usr/bin/bash # Rocky Linux script execution command: /usr/bin/bash -rwxr-xr-x 1 root root 1388928 Jan 23 2023 /usr/bin/bash # Alpine Linux script execution command: /bin/sh lrwxrwxrwx 1 root root 12 Jul 15 10:42 /bin/sh -> /bin/busybox
Alpine Linuxは予想していたとおり/bin/sh
なのですが、それ以外は軒並みbashですね。
ちょっと予想外です。
GitLab Runnerのソースコードを見ると、このあたりが気になりますね。
BashDetectShellScript = `if [ -x /usr/local/bin/bash ]; then exec /usr/local/bin/bash $@ elif [ -x /usr/bin/bash ]; then exec /usr/bin/bash $@ elif [ -x /bin/bash ]; then exec /bin/bash $@ elif [ -x /usr/local/bin/sh ]; then exec /usr/local/bin/sh $@ elif [ -x /usr/bin/sh ]; then exec /usr/bin/sh $@ elif [ -x /bin/sh ]; then exec /bin/sh $@ elif [ -x /busybox/sh ]; then exec /busybox/sh $@ else echo shell not found exit 1 fi
https://gitlab.com/gitlab-org/gitlab-runner/-/blob/v18.3.0/shells/bash.go?ref_type=tags#L20-37
どうもヘルパーというものの機能のような気がします。これはExecutorがDocker、Docker+Machine、Kubernetesの時に使われるようです。
Advanced configuration / Helper image
実際、GitLab Runnerにはgitlab-runner-helperというイメージが入ってきますしね。
$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE registry.gitlab.com/gitlab-org/gitlab-runner/gitlab-runner-helper x86_64-v18.3.0 6d44a66eea97 36 minutes ago 92.7MB debian bookworm 3d2058890b96 2 weeks ago 117MB ubuntu 24.04 e0f16e6366fe 4 weeks ago 78.1MB alpine 3.22 9234e8fb04c4 6 weeks ago 8.31MB rockylinux 9.3 9cc24f05f309 21 months ago 176MB
このあたりでしょうか。
Shell: common.ShellScriptInfo{ Shell: "bash", Type: common.NormalShell, RunnerCommand: "/usr/bin/gitlab-runner-helper", },
e.ExecutorOptions.Shell.Shell = "bash" e.ExecutorOptions.Shell.RunnerCommand = "/usr/bin/gitlab-runner-helper"
ちょっとこのスクリプトがどう使われているかどうかは追いきれないので、少し試してみましょう。
こんなDockerfile
を追加。
Dockerfile
FROM ubuntu:24.04 RUN mv /usr/bin/bash /usr/local/bin/sh
/usr/bin/bash
を移動します。ちなみにこれで/bin/bash
もなくなります。
.gitlab-ci.yml
をこう変更。
.gitlab-ci.yml
stages: - build - run my-image-build: stage: build services: - name: docker:28.3.3-dind command: ["--insecure-registry=192.168.0.6:5050"] image: docker:28.3.3 script: - | echo $CI_REGISTRY echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin docker image build -t $CI_REGISTRY_IMAGE/my-ubuntu:24.04 . docker image push $CI_REGISTRY_IMAGE/my-ubuntu:24.04 ubuntu: stage: run image: $CI_REGISTRY_IMAGE/my-ubuntu:24.04 script: - | echo -n 'script execution command: ' cat /proc/$$/cmdline | tr '\0' ' ' echo ls -l $(cat /proc/$$/cmdline | tr '\0' ' ') debian: stage: run image: debian:bookworm script: - | echo -n 'script execution command: ' cat /proc/$$/cmdline | tr '\0' ' ' echo ls -l $(cat /proc/$$/cmdline | tr '\0' ' ') rocky: stage: run image: rockylinux:9.3 script: - | echo -n 'script execution command: ' cat /proc/$$/cmdline | tr '\0' ' ' echo ls -l $(cat /proc/$$/cmdline | tr '\0' ' ') alpine: stage: run image: alpine:3.22 script: - | echo -n 'script execution command: ' cat /proc/$$/cmdline | tr '\0' ' ' echo ls -l $(cat /proc/$$/cmdline | tr '\0' ' ')
自分でビルドしたDockerイメージを使うようにします。
ubuntu: stage: run image: $CI_REGISTRY_IMAGE/my-ubuntu:24.04 script: - | echo -n 'script execution command: ' cat /proc/$$/cmdline | tr '\0' ' ' echo ls -l $(cat /proc/$$/cmdline | tr '\0' ' ')
結果はこうなりました。
script execution command: /usr/local/bin/sh -rwxr-xr-x 1 root root 1446024 Mar 31 2024 /usr/local/bin/sh
ということは、やっぱりこちらのスクリプトが使われていそうですね。
BashDetectShellScript = `if [ -x /usr/local/bin/bash ]; then exec /usr/local/bin/bash $@ elif [ -x /usr/bin/bash ]; then exec /usr/bin/bash $@ elif [ -x /bin/bash ]; then exec /bin/bash $@ elif [ -x /usr/local/bin/sh ]; then exec /usr/local/bin/sh $@ elif [ -x /usr/bin/sh ]; then exec /usr/bin/sh $@ elif [ -x /bin/sh ]; then exec /bin/sh $@ elif [ -x /busybox/sh ]; then exec /busybox/sh $@ else echo shell not found exit 1 fi
少なくとも、bashが利用できる環境ではbashを使おうとすることがわかりました。
おわりに
GitLab CI/CDのDocker Executorで、ジョブを実行するシェルを確認してみました。
ドキュメントを読んでいるとshまたはbashということでしたが、意外とbash優先のような気がしますね。
ジョブを実行するスクリプトがどうやってシェルを選んでいるか、もう少し追ってみたかったりもするのですが今回の簡単な確認までにします。