blob: a5c16d25f673ef717c996980f5c26fa818039a40 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
|
<!DOCTYPE html>
<html lang="ja-JP">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="author" content="nsfisis">
<meta name="copyright" content="© 2024 nsfisis">
<meta name="description" content="GitLab CI/CD で bash/sh スクリプトを動かすと、pipefail オプションが有効になった状態で実行される。">
<meta name="keywords" content="CI/CD,GitLab">
<meta property="og:type" content="article">
<meta property="og:title" content="【GitLab】 GitLab CI/CD 上での bash/sh は pipefail が有効になっている|REPL: Rest-Eat-Program Loop">
<meta property="og:description" content="GitLab CI/CD で bash/sh スクリプトを動かすと、pipefail オプションが有効になった状態で実行される。">
<meta property="og:site_name" content="REPL: Rest-Eat-Program Loop">
<meta property="og:locale" content="ja_JP">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<title>【GitLab】 GitLab CI/CD 上での bash/sh は pipefail が有効になっている|REPL: Rest-Eat-Program Loop</title>
<link rel="stylesheet" href="/style.css?h=8415c13be008f55a351b69c568b3e8ed">
</head>
<body class="single">
<header class="header">
<div class="site-logo">
<a href="/">REPL: Rest-Eat-Program Loop</a>
</div>
<nav class="nav">
<ul>
<li>
<a href="/about/">About</a>
</li>
<li>
<a href="/posts/">Posts</a>
</li>
<li>
<a href="/slides/">Slides</a>
</li>
<li>
<a href="/tags/">Tags</a>
</li>
</ul>
</nav>
</header>
<main class="main">
<article class="post-single">
<header class="post-header">
<h1 class="post-title">【GitLab】 GitLab CI/CD 上での bash/sh は pipefail が有効になっている</h1>
<ul class="post-tags">
<li class="tag">
<a href="/tags/ci-cd/">CI/CD</a>
</li>
<li class="tag">
<a href="/tags/gitlab/">GitLab</a>
</li>
</ul>
</header>
<div class="post-content">
<section id="changelog">
<h2><a href="#changelog">更新履歴</a></h2>
<ol>
<li class="revision">
<time datetime="2022-11-17">2022-11-17</time>: デジタルサーカス株式会社の社内記事として公開
</li>
<li class="revision">
<time datetime="2024-04-21">2024-04-21</time>: ブログ記事として一般公開
</li>
</ol>
</section>
<div class="admonition">
<div class="admonition-label">
NOTE
</div>
<div class="admonition-content">
<p>
この記事は、2022-11-17 に <a href="https://www.dgcircus.com/" rel="noreferrer" target="_blank">デジタルサーカス株式会社</a> の社内 Qiita Team に公開された記事をベースに、加筆修正して一般公開したものです。
</p>
</div>
</div>
<p>
ハマったのでメモ。
</p>
<section id="section--background">
<h2><a href="#section--background">前提</a></h2>
<section id="section--background--gitlab-ci-cd">
<h3><a href="#section--background--gitlab-ci-cd">GitLab CI/CD について</a></h3>
<p>
GitLab CI/CD では、Docker executor を用いて任意の Docker image 上でスクリプトを走らせることができる。
</p>
<p>
例:
</p>
<div class="codeblock">
<pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#22863A">hello-world</span><span style="color:#24292E">:</span></span>
<span class="line"><span style="color:#22863A"> stage</span><span style="color:#24292E">: </span><span style="color:#032F62">test</span></span>
<span class="line"><span style="color:#22863A"> image</span><span style="color:#24292E">: </span><span style="color:#032F62">alpine:latest</span></span>
<span class="line"><span style="color:#22863A"> script</span><span style="color:#24292E">:</span></span>
<span class="line"><span style="color:#24292E"> - </span><span style="color:#032F62">'echo "Hello, World!"'</span></span>
<span class="line"><span style="color:#22863A"> rules</span><span style="color:#24292E">:</span></span>
<span class="line"><span style="color:#24292E"> - </span><span style="color:#22863A">if</span><span style="color:#24292E">: </span><span style="color:#032F62">'$CI_MERGE_REQUEST_IID'</span></span>
<span class="line"><span style="color:#22863A"> when</span><span style="color:#24292E">: </span><span style="color:#032F62">always</span></span></code></pre>
</div>
<p>
ここで、<code>script</code> に指定したコマンドが失敗する (exit status が 0 以外になる) と、即座に実行が停止され、ジョブは失敗する。
</p>
<p>
では、次のようなケースだとどうなるか。
</p>
<div class="codeblock">
<pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#22863A">hello-world</span><span style="color:#24292E">:</span></span>
<span class="line"><span style="color:#22863A"> stage</span><span style="color:#24292E">: </span><span style="color:#032F62">test</span></span>
<span class="line"><span style="color:#22863A"> image</span><span style="color:#24292E">: </span><span style="color:#032F62">alpine:latest</span></span>
<span class="line"><span style="color:#22863A"> script</span><span style="color:#24292E">:</span></span>
<span class="line"><span style="color:#24292E"> - </span><span style="color:#032F62">'exit 1 | exit 0'</span></span>
<span class="line"><span style="color:#22863A"> rules</span><span style="color:#24292E">:</span></span>
<span class="line"><span style="color:#24292E"> - </span><span style="color:#22863A">if</span><span style="color:#24292E">: </span><span style="color:#032F62">'$CI_MERGE_REQUEST_IID'</span></span>
<span class="line"><span style="color:#22863A"> when</span><span style="color:#24292E">: </span><span style="color:#032F62">always</span></span></code></pre>
</div>
<p>
失敗するコマンドをパイプに接続した。通常 Bash では、パイプの最後のコマンドの exit code が全体の exit code になる。
</p>
</section>
<section id="section--background--pipefail-option">
<h3><a href="#section--background--pipefail-option"><code>pipefail</code> オプションについて</a></h3>
<p>
前述したようなケースにおいて、途中で失敗したときに全体を失敗させるには、<code>pipefail</code> オプションを有効にする。
</p>
<div class="codeblock">
<pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D"># On にする</span></span>
<span class="line"><span style="color:#005CC5">set</span><span style="color:#005CC5"> -o</span><span style="color:#032F62"> pipefail</span></span>
<span class="line"><span style="color:#6A737D"># Off にする</span></span>
<span class="line"><span style="color:#005CC5">set</span><span style="color:#032F62"> +o</span><span style="color:#032F62"> pipefail</span></span></code></pre>
</div>
<p>
こうすると、パイプ全体が失敗するようになる。 この設定は、デフォルトだと off になっている。
</p>
</section>
</section>
<section id="section--problem">
<h2><a href="#section--problem">発生した問題</a></h2>
<p>
次のような GitLab CI/CD ジョブが失敗してしまった。
</p>
<div class="codeblock">
<pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#22863A">hoge</span><span style="color:#24292E">:</span></span>
<span class="line"><span style="color:#22863A"> stage</span><span style="color:#24292E">: </span><span style="color:#032F62">test</span></span>
<span class="line"><span style="color:#22863A"> image</span><span style="color:#24292E">: </span><span style="color:#032F62">alpine:latest</span></span>
<span class="line"><span style="color:#22863A"> script</span><span style="color:#24292E">:</span></span>
<span class="line"><span style="color:#24292E"> - </span><span style="color:#032F62">'cat hoge.txt | grep piyo | sed -e "s/foo/bar/g"'</span></span>
<span class="line"><span style="color:#22863A"> rules</span><span style="color:#24292E">:</span></span>
<span class="line"><span style="color:#24292E"> - </span><span style="color:#22863A">if</span><span style="color:#24292E">: </span><span style="color:#032F62">'$CI_MERGE_REQUEST_IID'</span></span>
<span class="line"><span style="color:#22863A"> when</span><span style="color:#24292E">: </span><span style="color:#032F62">always</span></span></code></pre>
</div>
<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>
<div class="codeblock">
<pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span>$ docker run --rm alpine:latest sh -c "set +o"</span></span>
<span class="line"><span>set +o errexit</span></span>
<span class="line"><span>set +o noglob</span></span>
<span class="line"><span>set +o ignoreeof</span></span>
<span class="line"><span>set +o monitor</span></span>
<span class="line"><span>set +o noexec</span></span>
<span class="line"><span>set +o xtrace</span></span>
<span class="line"><span>set +o verbose</span></span>
<span class="line"><span>set +o noclobber</span></span>
<span class="line"><span>set +o allexport</span></span>
<span class="line"><span>set +o notify</span></span>
<span class="line"><span>set +o nounset</span></span>
<span class="line"><span>set +o vi</span></span>
<span class="line"><span>set +o pipefail</span></span></code></pre>
</div>
<p>
確かに <code>pipefail</code> は無効になっている。
</p>
<p>
なぜスクリプト内で <code>set -o pipefail</code> しているわけでもないのに <code>pipefail</code> が on になっているのか。
</p>
</section>
<section id="section--where-pipefail-is-enabled">
<h2><a href="#section--where-pipefail-is-enabled">どこで <code>pipefail</code> が on になるか</a></h2>
<p>
<code>.gitlab-ci.yml</code> で明示的には書いていないので、GitLab Runner (GitLab CI/CD のスクリプトを実行するプログラム) が勝手に追加しているに違いない。 そう仮説を立てて <a href="https://gitlab.com/gitlab-org/gitlab-runner" rel="noreferrer" target="_blank">GitLab Runner のリポジトリ</a> を調査したところ、 <a href="https://gitlab.com/gitlab-org/gitlab-runner/-/blob/c75da0796a0e3048991dccfdf2784e3d931beda4/shells/bash.go#L276" rel="noreferrer" target="_blank">ソースコード中の以下の箇所</a> で <code>set -o pipefail</code> していることが判明した (コメントは筆者による)。
</p>
<div class="codeblock">
<pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#6A737D">// pipefail オプションが存在しない環境にも対応するため、</span></span>
<span class="line"><span style="color:#6A737D">// 先に set -o でオプション一覧を表示させたあと、set -o pipefail している</span></span>
<span class="line"><span style="color:#24292E">buf.</span><span style="color:#6F42C1">WriteString</span><span style="color:#24292E">(</span><span style="color:#032F62">"if set -o | grep pipefail > /dev/null; then set -o pipefail; fi; set -o errexit</span><span style="color:#005CC5">\n</span><span style="color:#032F62">"</span><span style="color:#24292E">)</span></span></code></pre>
</div>
</section>
<section id="section--how-to-solve">
<h2><a href="#section--how-to-solve">どのように解決するか</a></h2>
<p>
通常の Bash スクリプトを書く場合と同様に、<code>pipefail</code> が on になっていては困る場所だけ off にしてやればよい。
</p>
<div class="codeblock">
<pre class="shiki github-light" style="background-color:#f5f5f5;color:#24292e" tabindex="0"><code><span class="line"><span style="color:#22863A"> hoge</span><span style="color:#24292E">:</span></span>
<span class="line"><span style="color:#22863A"> stage</span><span style="color:#24292E">: </span><span style="color:#032F62">test</span></span>
<span class="line"><span style="color:#22863A"> image</span><span style="color:#24292E">: </span><span style="color:#032F62">alpine:latest</span></span>
<span class="line"><span style="color:#22863A"> script</span><span style="color:#24292E">:</span></span>
<span class="line"><span style="color:#032F62">+ - 'set +o pipefail'</span></span>
<span class="line"><span style="color:#24292E"> - </span><span style="color:#032F62">'cat hoge.txt | grep piyo | sed -e "s/foo/bar/g"'</span></span>
<span class="line"><span style="color:#032F62">+ - 'set -o pipefail'</span><span style="color:#6A737D"> # この例の場合、ここで終わりなので戻さなくてもよい</span></span>
<span class="line"><span style="color:#22863A"> rules</span><span style="color:#24292E">:</span></span>
<span class="line"><span style="color:#24292E"> - </span><span style="color:#22863A">if</span><span style="color:#24292E">: </span><span style="color:#032F62">'$CI_MERGE_REQUEST_IID'</span></span>
<span class="line"><span style="color:#22863A"> when</span><span style="color:#24292E">: </span><span style="color:#032F62">always</span></span></code></pre>
</div>
</section>
<section id="section--remarks">
<h2><a href="#section--remarks">備考</a></h2>
<p>
なお、上述した実装ファイルは <code>shells/bash.go</code> だが、<code>alpine:latest</code> の例でもそうであったように、シェルが <code>sh</code> である場合にも適用される。
</p>
</section>
</div>
</article>
</main>
<footer class="footer">
© 2021 nsfisis
</footer>
</body>
</html>
|