diff options
| author | nsfisis <nsfisis@gmail.com> | 2024-04-20 23:44:15 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2024-04-20 23:44:30 +0900 |
| commit | 88f8bcc0862621c96d23a552eaa2a8fed010440f (patch) | |
| tree | e9822c6dcbf4ac58df5638fa6c4d4f3d4fb3c615 /vhosts/blog/content | |
| parent | b372b90ae4f65855d6f7132c7722f0228f43daa8 (diff) | |
| download | nsfisis.dev-88f8bcc0862621c96d23a552eaa2a8fed010440f.tar.gz nsfisis.dev-88f8bcc0862621c96d23a552eaa2a8fed010440f.tar.zst nsfisis.dev-88f8bcc0862621c96d23a552eaa2a8fed010440f.zip | |
feat(blog/content): new post /posts/2024-04-21/pipefail-option-in-gitlab-ci-cd/
Diffstat (limited to 'vhosts/blog/content')
| -rw-r--r-- | vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.ndoc | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.ndoc b/vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.ndoc new file mode 100644 index 00000000..028cbbb0 --- /dev/null +++ b/vhosts/blog/content/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd.ndoc @@ -0,0 +1,180 @@ +--- +[article] +uuid = "a4c326a6-5ffe-450c-abf2-45833c5efb6a" +title = "【GitLab】 GitLab CI/CD 上での bash/sh は pipefail が有効になっている" +description = "GitLab CI/CD で bash/sh スクリプトを動かすと、pipefail オプションが有効になった状態で実行される。" +tags = [ + "ci-cd", + "gitlab", +] + +[[article.revisions]] +date = "2022-11-17" +remark = "デジタルサーカス株式会社の社内記事として公開" +isInternal = true + +[[article.revisions]] +date = "2024-04-21" +remark = "ブログ記事として一般公開" +--- +<article> + <note> + この記事は、2022-11-17 に<a href="https://www.dgcircus.com/">デジタルサーカス株式会社</a> の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。 + </note> + <p> + ハマったのでメモ。 + </p> + <section id="background"> + <h>前提</h> + <section id="background--gitlab-ci-cd"> + <h>GitLab CI/CD について</h> + <p> + GitLab CI/CD では、Docker executor を用いて任意の Docker image 上でスクリプトを走らせることができる。 + </p> + <p> + 例: + </p> + <codeblock language="yaml" filename=".gitlab-ci.yml"> + <![CDATA[ + hello-world: + stage: test + image: alpine:latest + script: + - 'echo "Hello, World!"' + rules: + - if: '$CI_MERGE_REQUEST_IID' + when: always + ]]> + </codeblock> + <p> + ここで、<code>script</code> に指定したコマンドが失敗する (exit status が 0 以外になる) と、即座に実行が停止され、ジョブは失敗する。 + </p> + <p> + では、次のようなケースだとどうなるか。 + </p> + <codeblock language="yaml" filename=".gitlab-ci.yml"> + <![CDATA[ + hello-world: + stage: test + image: alpine:latest + script: + - 'exit 1 | exit 0' + rules: + - if: '$CI_MERGE_REQUEST_IID' + when: always + ]]> + </codeblock> + <p> + 失敗するコマンドをパイプに接続した。通常 Bash では、パイプの最後のコマンドの exit code が全体の exit code になる。 + </p> + </section> + <section id="background--pipefail-option"> + <h><code>pipefail</code> オプションについて</h> + <p> + 前述したようなケースにおいて、途中で失敗したときに全体を失敗させるには、<code>pipefail</code> オプションを有効にする。 + </p> + <codeblock language="bash"> + <![CDATA[ + # On にする + set -o pipefail + # Off にする + set +o pipefail + ]]> + </codeblock> + <p> + こうすると、パイプ全体が失敗するようになる。 + この設定は、デフォルトだと off になっている。 + </p> + </section> + </section> + <section id="problem"> + <h>発生した問題</h> + <p> + 次のような GitLab CI/CD ジョブが失敗してしまった。 + </p> + <codeblock language="yaml" filename=".gitlab-ci.yml"> + <![CDATA[ + hoge: + stage: test + image: alpine:latest + script: + - 'cat hoge.txt | grep piyo | sed -e "s/foo/bar/g"' + rules: + - if: '$CI_MERGE_REQUEST_IID' + when: always + ]]> + </codeblock> + <p> + <code>grep</code> コマンドは、パターンにマッチする行が一行もなかったとき、exit code 1 を返す。よって、<code>pipefail</code> が on になっていると、このジョブは失敗する。 + 現在の <code>pipefail</code> がどうなっているか確かめるため <code>set +o</code> で全オプションを出力させたところ、<code>pipefail</code> が on になっていた。 + </p> + <p> + しかし、先述したように Bash における <code>pipefail</code> のデフォルト値は off のはずだ。 + 実際に、ローカルで <code>alpine:latest</code> を動かしてみたところ、 + </p> + <codeblock> + <![CDATA[ + $ docker run --rm alpine:latest sh -c "set +o" + set +o errexit + set +o noglob + set +o ignoreeof + set +o monitor + set +o noexec + set +o xtrace + set +o verbose + set +o noclobber + set +o allexport + set +o notify + set +o nounset + set +o vi + set +o pipefail + ]]> + </codeblock> + <p> + 確かに <code>pipefail</code> は無効になっている。 + </p> + <p> + なぜスクリプト内で <code>set -o pipefail</code> しているわけでもないのに <code>pipefail</code> が on になっているのか。 + </p> + </section> + <section id="where-pipefail-is-enabled"> + <h>どこで <code>pipefail</code> が on になるか</h> + <p> + <code>.gitlab-ci.yml</code> で明示的には書いていないので、GitLab Runner (GitLab CI/CD のスクリプトを実行するプログラム) が勝手に追加しているに違いない。 + そう仮説を立てて <a href="https://gitlab.com/gitlab-org/gitlab-runner">GitLab Runner のリポジトリ</a> を調査したところ、<a href="https://gitlab.com/gitlab-org/gitlab-runner/-/blob/c75da0796a0e3048991dccfdf2784e3d931beda4/shells/bash.go#L276">ソースコード中の以下の箇所</a> で <code>set -o pipefail</code> していることが判明した (コメントは筆者による)。 + </p> + <codeblock language="go"> + <![CDATA[ + // pipefail オプションが存在しない環境にも対応するため、 + // 先に set -o でオプション一覧を表示させたあと、set -o pipefail している + buf.WriteString("if set -o | grep pipefail > /dev/null; then set -o pipefail; fi; set -o errexit\n") + ]]> + </codeblock> + </section> + <section id="how-to-solve"> + <h>どのように解決するか</h> + <p> + 通常の Bash スクリプトを書く場合と同様に、<code>pipefail</code> が on になっていては困る場所だけ off にしてやればよい。 + </p> + <codeblock language="yaml" diff="true" filename=".gitlab-ci.yml"> + <![CDATA[ + hoge: + stage: test + image: alpine:latest + script: + + - 'set +o pipefail' + - 'cat hoge.txt | grep piyo | sed -e "s/foo/bar/g"' + + - 'set -o pipefail' # この例の場合、ここで終わりなので戻さなくてもよい + rules: + - if: '$CI_MERGE_REQUEST_IID' + when: always + ]]> + </codeblock> + </section> + <section id="remarks"> + <h>備考</h> + <p> + なお、上述した実装ファイルは <code>shells/bash.go</code> だが、<code>alpine:latest</code> の例でもそうであったように、シェルが <code>sh</code> である場合にも適用される。 + </p> + </section> +</article> |
