FlaskのAzure App Service (Linux Container Deploy)へのデプロイでハマった: 503 :( Application Error
問題
- ローカル開発して、Imageに固める
- Azure Container Registry に push する
- その image:tag を指定して Azure App Service を作る
:( Application Error←これが出る

調査
Containerの起動時のログ(Log Streaming)を見ると、以下の文言を発見.
2025-02-14T01:37:30.925773821Z exec /usr/local/bin/gunicorn: exec format error 2025-02-14T01:48:08.906230302Z exec /usr/local/bin/gunicorn: exec format error 2025-02-14T02:05:38.121059397Z exec /usr/local/bin/python: exec format error 2025-02-14T02:14:57.282028923Z exec /usr/local/bin/python: exec format error
- python - /usr/local/bin/gunicorn Exec format error Apple M1 Chip - Stack Overflow
- exec user process caused: exec format errorの原因と対処法 (ECS) #Rails - Qiita
解決
# Dockerfile -- FROM python:3.10-slim as server ++ FROM --platform=linux/amd64 python:3.10-slim as server
雑感
- 土地勘が無いところでの作業はたいへん
- でも勉強になるたのしい
- ところで俺の半日を返してほしい
DRYな備忘録として
Font Awesome のアイコンのアセットサイズが大きかったので利用するアイコンだけのサイズに削減したい
問題
現在開発しているChrome拡張において、Font Awesome のアイコンのためのフォントファイルが占める割合が大きいことがわかった。
% ls -l dist/assets | awk '{print $5"\t"$9}' | sort -n 15463 Mado-c38406e7.js 77160 fontawesome-webfont.woff2 98024 fontawesome-webfont.woff 165548 fontawesome-webfont.ttf 165742 fontawesome-webfont.eot 444379 fontawesome-webfont.svg 680908 index.css
もちろん、CSSもサードパーティのフレームワークを安直にscssで@import 'bulma'とかしているだけなので、改善の余地は大いにあるんだが...
いったん今回は、svgはじめ、他のフォントファイルのサイズも減らしていきたい。
追記: 2024-08-20
- Unicode emoji に global に定義がある(=各プラットフォームでネイティブにサポートされている)ものについて、正しく動いていないような気がする。ただし、aliasを使うと解決するっぽい
- share f064 (alias =
fa-mail-forward) - bell f0f3
- share f064 (alias =
- 要原因調査
tl;dr
できました。

% ls -l dist/assets | awk '{print $5"\t"$9}' | sort -n 3088 fontawesome-webfont.woff2 3804 fontawesome-webfont.woff 5852 fontawesome-webfont.ttf 6048 fontawesome-webfont.eot 13730 fontawesome-webfont.svg 15463 Mado-c38406e7.js 680908 index.css
できてます。444Kが13Kになって、とりあえずいい感じ。
考え方
- 700近くある FontAwesome Icons のうち、使ってるアイコンはせいぜい20とかなので、素朴に考えて「使ってるやつだけ抜粋する」というアプローチでシンプルに減らせるはず
- 同様に、以前
@fortawesome/fortawesome-reactにおけるlibraryは使ったことあって、これは最終的にbundleのサイズが使ってるやつだけにするやつ - だが、またnpmのパッケージ増やすのもあれだし、仕組みがあんまりよくわかってないし、勉強だと思って自分でやってみたい
eotやttfやwoffに比べて、svgはリーダブルだし扱いやすいかなということで、以下の方針で考えた
- プロジェクトのソースコードの中から「使ってるアイコン」を特定する
- 元のsvgファイルの中から、「使ってるアイコン」だけを残して「使ってないアイコン」を消し、新たなsvgファイルとして保存する
- このsvgファイルを下に、ttfやwoff、eotファイルを生成する
1. プロジェクトのソースコードの中から「使ってるアイコン」を特定する
厳密にやってもいいんですが、今回はシンプルに、grepでもいいし、まあjsで line by line でregexpしていこうかと思いました。
const target_folder_path = path.join(PROJECT_ROOT, default_target_folder); const summary: { [name: string]: { glyph: string, unicode: string, appearance: { file: string, line: number }[] } } = {}; // ターゲットとなるsrcフォルダの前ファイルエントリを取得 const entries = await fs.readdir(target_folder_path, { recursive: true }); // 地道に回す for (let i = 0; i < entries.length; i++) { const e = entries[i]; // スキャン対象ではない拡張子のファイルは無視 const ext = e.split(".").pop() || ""; if (!default_scan_extensions.includes(ext)) continue; // 内容取得 const file_path = path.join(target_folder_path, e); const contents = await fs.readFile(file_path, "utf-8"); // 内容を一行ずつ見る contents.split("\n").forEach((line, line_number) => { // 雑に "fa fa-plus" みたいに宣言している行rをregexpで見る // まあさすがにclassNameを複数行で書いたりしてないでしょ // 後述ポイント (1) const match = line.matchAll(/fa[ ]+fa-(?<name>[a-z0-9-]+)/g); for (const m of match) { if (m.groups?.name) { // SVGファイルだとglyphと呼称され、アンダーバーなのでそうしとく summary[m.groups.name] = summary[m.groups.name] || { /** * dictonaryっていきなり出てきたけど、次のセクションで触れる **/ glyph: m.groups.name.replace(/-/g, "_"), unicode: dictionary[m.groups.name], appearance: [], }; // 別に何回参照されてるとかは不要な情報なんだけど、なんとなく summary[m.groups.name].appearance.push({ file: file_path, line: line_number }); } } }); }
後述ポイント (1) className={"fa " + (flag ? "fa-plus" : "fa-minus")} みたいに宣言してるところ。動的にclassName変えたいときとかやってしまうんですが、まあ今回は小さいプロジェクトだし、
className={flag ? "fa fa-plus" : "fa fa-minus"}
とすることでどうにかした。いや〜雑でいいですね。個人開発ならでは、って感じ。
以上で、このプロジェクトが参照しているfont awesome のアイコンは summary の中にまとめることができました。
2-a. 元のsvgファイルの中から、「使ってるアイコン」がどれか知る
まずSVGファイルの中身がどうなっているかを知る必要がある。
どうやら、 svg>defs>font>glyph[] となってて、glyphがタグがたくさんあるのがだいたい700ぐらいありそう。
<?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" > <svg> <metadata>Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 By ,,, Copyright Dave Gandy 2016. All rights reserved. </metadata> <defs> <font id="FontAwesome" horiz-adv-x="1536" > <font-face font-family="FontAwesome" /> <missing-glyph horiz-adv-x="896" d="M224 112h448v1312h-448v-1312zM112 0v1536h672v-1536h-672z" /> <glyph glyph-name=".notdef" horiz-adv-x="896" d="M224 112h448v1312h-448v-1312zM112 0v1536h672v-1536h-672z" /> <glyph glyph-name=".null" horiz-adv-x="0" /> <!-- 以下、 `glyph` が続く --> <!-- たとえばこんなの --> <!-- パターンA --> <glyph glyph-name="infinity" unicode="∞" horiz-adv-x="1792" /> <glyph glyph-name="notequal" unicode="≠" horiz-adv-x="1792" /> <!-- 他にもこんなの --> <!-- パターンB --> <glyph glyph-name="glass" unicode="" horiz-adv-x="1792" d="M1699 1350q0 -35 -43 -78l-632 -632v-768h320q26 0 45 -19t19 -45t-19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45t45 19h320v768l-632 632q-43 43 -43 78q0 23 18 36.5t38 17.5t43 4h1408q23 0 43 -4t38 -17.5t18 -36.5z" /> <glyph glyph-name="music" unicode="" d="M1536 1312v-1120q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v537l-768 -237v-709q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89 t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v967q0 31 19 56.5t49 35.5l832 256q12 4 28 4q40 0 68 -28t28 -68z" /> <!-- さらにこんなのも --> <!-- パターンC --> <glyph glyph-name="f1fc" unicode="" horiz-adv-x="1792" d="M1615 1536q70 0 122.5 -46.5t52.5 -116.5q0 -63 -45 -151q-332 -629 -465 -752q-97 -91 -218 -91q-126 0 -216.5 92.5t-90.5 219.5q0 128 92 212l638 579q59 54 130 54zM706 502q39 -76 106.5 -130t150.5 -76l1 -71q4 -213 -129.5 -347t-348.5 -134q-123 0 -218 46.5 t-152.5 127.5t-86.5 183t-29 220q7 -5 41 -30t62 -44.5t59 -36.5t46 -17q41 0 55 37q25 66 57.5 112.5t69.5 76t88 47.5t103 25.5t125 10.5z" /> <!-- ここが味噌 --> </font> </divs> </svg>
- パターンA:
infinityとかnotequalとかは、Unicode Emoji でたぶん定義されててそれを使ってるにすぎないので、path定義無いのだと推察 - パターンB: FontAwesome独自のアイコンで、適当なhex4桁のunicodeに割り当てて、pathを書いて、フォントを定義しているのだと推察
glyph-nameも、css的なclassNameで指定するものと一致している
- 問題はパターンC:
glyph-nameが human readable な単語ではなく、unicodeに当ててる hex 4桁を流用している。なんでだろ- ちなみにたとえば
f1fcとは、paint-brushというアイコンに対応している
- ちなみにたとえば
パターンCがあるため、単純にソースコードから抽出したアイコン名(e.g., fa-paint-brush)ではなく、ユニコードに割り当てた hex 4桁 をひいてこないと、このSVGファイルから「使ってるやつglyph」「使ってないglyph」を判別できないということである。
おそらくfont-awesome自体は、クラス名から font-awesome.scss の中で、クラス名と hex 4桁 の対応をひいてくる部分があると思われ、npmでダウンロードしたnode_modulesの中のfont-awesomeの中を覗いてみると、_variable.scss というのが臭そう
% tree node_modules/font-awesome/scss/ node_modules/font-awesome/scss/ ├── _animated.scss ├── _bordered-pulled.scss ├── _core.scss ├── _fixed-width.scss ├── _icons.scss ├── _larger.scss ├── _list.scss ├── _mixins.scss ├── _path.scss ├── _rotated-flipped.scss ├── _screen-reader.scss ├── _stacked.scss ├── _variables.scss └── font-awesome.scss 1 directory, 14 files

これを踏まえ、この _variables.scss ファイルをもとに辞書をつくって、fa-paint-brush あるいは f1fc でSVGファイルのglyphを検索できるようにすればよかろう。
ということで、以下のコードでオンメモリの辞書をつくる。
const dictionary: { [name: string]: string } = {}; const refs = await fs.readFile(unicode_reference_file_path, "utf-8"); refs.split("\n").forEach((line) => { const match = line.match(/fa-var-(?<name>[a-z0-9-]+): "\\(?<unicode>[a-f0-9]+)"/); if (match?.groups?.name && match?.groups?.unicode) { dictionary[match.groups.name] = match.groups.unicode; } });
これで、SVGファイルから「使ってるアイコン」と「使ってないアイコン」が峻別できるようになったはずである
2-b. 元のsvgファイルの中から、「使ってるアイコン」だけを残す
// Open SVG file and remove all the unnecessary lines except for the keys in the summary const svg_file_path = path.join(PROJECT_ROOT, "dist", "assets", "fontawesome-webfont.svg"); // XML形式のファイルをjsで扱いたいのでJSDOMでDOM化する const doc: JSDOM = await JSDOM.fromFile(svg_file_path); const glyphs: SVGElement[] = []; Object.values(summary).forEach(({ glyph, unicode }) => { // glyph-name か、あるいは unicode で検索する const node: SVGAElement = doc.window._document.querySelector(`glyph[glyph-name=${glyph}]`) || doc.window._document.querySelector(`glyph[unicode="\\${unicode}"]`); if (node) { // 見つかったら溜めとく glyphs.push(node as SVGElement); } else { warn(" [!] ", "GLYPH NOT FOUND: ", unicode, glyph); } }); info(" > ", "Glyphs to be saved:", glyphs.length);
2-c. 「使ってないアイコン」を消して、あたらたなSVGファイルを保存する
// Remove all glyphs const font: SVGElement = doc.window._document.querySelector("font"); const allGlyphs = font.querySelectorAll("glyph"); allGlyphs.forEach((g) => g.remove()); info(" > ", "Removed glyphs:", allGlyphs.length); // Insert only necessary glyphs font.append(...glyphs); // Backup old file // await fs.cp(svg_file_path, svg_file_path.replace(".svg", ".backup.svg")); // Save the file await fs.rm(svg_file_path, { force: true }); const content = doc.serialize(); await fs.writeFile(svg_file_path, content);
3. SVGファイルを他のフォントファイルへ変換する
ここを自力でやってもよかったが、深淵なるフォントの世界に踏み入れることになるかなーとか思ったので、いったんインターネッツにあるものの力を借りました。
marmooo/fontconvさんを読む限り、SVGが手元のインプットとしてある場合、
WOFF
↑
{ttf2woff}
↑
SVG → {svg2ttf} → TTF → {wawoff2} → WOFF2
↓
{ttf2eot}
↓
EOT
という流れで他のファイルが作れるようである。ttfファイがこの世界の中心なのか。しらんけど。
ちょっと読んでみましたが、いきなりコードから入ってわかるレベルではなかった。勉強したかったら仕様から入らんとダメな雰囲気がした。
ということで、fontconvを使って他のフォントファイルを生成します。
// Convert SVG to TTF, WOFF, WOFF2, EOT for (let i = 0; i < destination_fontfile_extensions.length; i++) { const ext = "." + destination_fontfile_extensions[i]; const dest_content = await fontconv(content, ext, {}); const dest_file = svg_file_path.replace(".svg", ext); await fs.rm(dest_file, { force: true }); await fs.writeFile(dest_file, dest_content); info(" > ", "Created font file:", path.basename(dest_file)); } info("[DONE]", "Minimized fontawesome SVG file with icons actually used in the project\n");
完成したもの

| Before | After |
|---|---|
|
|
雑感
- しょせんプログラマは「自分が理解できる限界のレイヤで踊らされているにすぎない」ということを改めて痛感しました
- でも、できる限り「堀りにいく」という経験や態度は、は非常に重要だなということも、再度実感しました
- また、個人開発で思いっきり雑で勢い重視の実装や調査をするのは、たのしいなあ
- 9月からまた仕事だが、できればこの「技術が好き」という、生む金は大きく無いが、輪郭のはっきりとした情熱を活かしていければと思う
DRYな備忘録として
新調したMacBook Air(M3チップ)にGoの環境を整えたい; go: command not found; ERROR: Failed to download binary go
背景
- 転職するので、個人のパソコンも新調しました。MacBook Air M3 です
- 当方、Goに縁があるので、Goの環境整えたいです
Goのバージョンマネージャ
Goはパッケージマネージャは公式の go mod を駆使したらいいんだと思うんですが、Go自体のバージョンマネージャはどれ使うんでしょうね。
わたしはこれ使いたいです。
モノとしても基本シェルスクリプトで、問題に直面してしまったときのコードリーディングも勉強になりそうですね。
直面した問題
まず、gvmのインストール
bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer)
これが何をやっているかというと、このへんでgvmのrepo自体を$HOME以下に落としてきているのがわかる。
しかしこうやって見ると改めてインターネッツに置かれたシェルスクリプトを直接叩くのって怖いですね
で、
source ${HOME}/.gvm/scripts/gvm # すると、 which gvm # 読み込まれたシェルスクリプトが実体のため、 # gvm() { ... } というような関数の定義が出力される
いよいよGoのダウンロードと配置ですが、いったん、入手可能なGoのバージョンを確認したい
gvm listall
# リモートから入手可能なGoのバージョンがズラッと出力される
いったん、最近のstableでいいかなと思うので、1.22かなーと
% gvm install go1.22 Installing go1.22.0 as go1.22... * Compiling... /Users/otiai10/.gvm/scripts/install: line 93: go: command not found ERROR: Failed to compile. Check the logs at /Users/otiai10/.gvm/logs/go-go1.22-compile.log ERROR: Failed to use installed version %
go: command not found
Goをインストールしようとしているのに go コマンドが無いと言われる。困った。
これはなぜかというと、GoのコンパイルにはGo自身が必要だからで、ここに詳しく説明されているっぽい
以下のような記述がある
The minimum version of Go required depends on the target version of Go:
- Go <= 1.4: a C toolchain.
- 1.5 <= Go <= 1.19: a Go 1.4 compiler.
- 1.20 <= Go <= 1.21: a Go 1.17 compiler.
- 1.22 <= Go <= 1.23: a Go 1.20 compiler.
Going forward, Go version 1.N will require a Go 1.M compiler, where M is N-2 rounded down to an even number. Example: Go 1.24 and 1.25 require Go 1.22.
今回、1.22を入れようとしているので、1.20のバイナリを、gvmを使って、直接ダウンロード+配置をすれば、bootstrap用のGoは手に入れられる気がする
解決
# gvmの --binary オプションを使う % gvm install go1.20 --binary Installing go1.20 from binary source % which go go not found # まだPATHが通ってないからしょうがない % gvm list gvm gos (installed) go1.20 % gvm use go1.20 Now using version go1.20 % which go /Users/otiai10/.gvm/gos/go1.20/bin/go % # PATH通ったので、 `go command not found` とは言われないはず
気を取り直して、1.22を、あらためて、ソースからインストールする
% gvm install go1.22 Installing go1.22.0 as go1.22... * Compiling... go1.22 successfully installed! % gvm list gvm gos (installed) => go1.20 go1.22 % gvm use go1.22 Now using version go1.22 % gvm alias default go1.22 ERROR: Unrecognized command line argument: 'default' # 違った % gvm use go1.22 --default Now using version go1.22 % gvm list gvm gos (installed) go1.20 => go1.22 % which go /Users/otiai10/.gvm/gos/go1.22/bin/go % go version go version go1.22.0 darwin/arm64 %
雑感
- とはいえ、Goを使う環境で、Go自体のバージョンを頻繁に切り替えたりするかなーしないだろうなーという気持ち
- なので、公式で紹介されているように、installerで欲しいバージョンのGoをインストールし、その後のバージョンは、そのGoを使って
go install golang.org/dl/go1.23@latestみたいにしていくのがいいんじゃ無いかなとも思う
- なので、公式で紹介されているように、installerで欲しいバージョンのGoをインストールし、その後のバージョンは、そのGoを使って
- やはり技術が好きなので、これからのキャリアは技術に戻して行こうと思っている
- だけど、なんかやってることが3年前と進歩が無い
- 最初はしょうがないと思って、楽しみながら、リハビリしていきたい

DRYな備忘録として
Docker Desktop がクラッシュしたり、Engine Starting からずっと動かなかったりする【Apple M2】
問題
- Docker Desktop (GUIアプリ)が起動中にクラッシュして落ちる
- 当然、この状態でcliで
docker psとかしても、「Engine not started」みたいなエラーを得る
- 当然、この状態でcliで
- Docker Desktop が立ち上がったとて、Docker Engine のStartが一生終わらない
tl;dr
下記のforumで言及されている解放をいくつか試したが、結果的には
# Docker関係のすべてのプロセスを殺す % ps aux grep docker % pkill docker # それでも殺せてないプロセスも殺しちゃう % ps aux grep docker % sudo kill -9 {pid}
んで、「Docker Desktop」アプリを起動し直すと、Docker Engine も含めて正常に起動した。
参考
上記forumの中の、dockerのプロセス全部殺せ、というのが最も有効であった。
ログ
Docker Desktop GUI アプリのクラッシュログ抜粋
Appleにreportするやつ。
Thread 0 Crashed:: CrBrowserMain Dispatch queue: com.apple.main-thread 0 libsystem_kernel.dylib 0x191c14744 __pthread_kill + 8 1 libsystem_pthread.dylib 0x191c4bc28 pthread_kill + 288 2 libsystem_c.dylib 0x191b59ae8 abort + 180 3 Electron Framework 0x111481768 node::Buffer::New(v8::Isolate*, char*, unsigned long) + 158392 4 Electron Framework 0x11148199c node::OnFatalError(char const*, char const*) + 552 5 Electron Framework 0x10bff34f4 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) + 592
マシンは十分にメモリを積んでいる(24GB)と思いつつ、いったん、OOMというメッセージがあるので、以下をトライ
- メモリ食ってる他のアプリを停止
- Docker Desktop のバージョンを
v4.25.2にアップデート - 念のため、PCをrestart
Engineが永遠に起動しない
Desktopは立ち上がったが、Starting Docker Engine からずっと動かない。Docker Desktopの右下の通知ベルのマークにエラーの表示があったため調べると、
Kernel Triage: VM - pmap_enter failed with resource shortage VM - pmap_enter failed with resource shortage VM - pmap_enter failed with resource shortage VM - pmap_enter failed with resource shortage VM - pmap_enter failed with resource shortage
や
[091:19:56:46.565][E] dockerd died before the API started up: exit status 1
という表記があった。

これを参考にググると、
- Docker Desktop quit unexpectedly version · Issue #6056 · docker/for-mac · GitHub
- Infinite loop when starting docker after update. · Issue #6260 · docker/for-mac · GitHub
などがヒットした。上記のリンク 2-a が直接の参考になったforumのポストであった。
備考
- 明日からまたパワポを書く仕事に戻る
- パソコンの黒い画面を見ているほうが落ち着く自分を強く感じる
- しかし、給料などを考えると、苦しみながらパワポを書いたほうがいいという現状に、ここのところずっと悩んでいる
- 早く抜け出したい
DRYな備忘録として
Twitter API v1.1を利用する箇所が失敗しているので調査・対応ログ
背景
もうかれこれ10年*1開発が続いているChrome拡張*2があり、このCI/CDの結果報告にTwitterのAPIを用いている。また、ユーザにとっても、Chrome拡張上でTwitter認証を行い画像付きツイートを行える機能を提供している。
今回、この機能が以下のエラーを吐いて失敗している。どうやら、Twitter API の「アクセスレベル」関係でコケているようだ。

Web Store TEST · KanCraft/kanColleWidget@0588aa7 · GitHub
Error: [ { message: 'You currently have access to a subset of Twitter API v2 endpoints and limited v1.1 endpoints (e.g. media post, oauth) only. If you need access to this endpoint, you may need a different access level. You can learn more here: https://developer.twitter.com/en/portal/product', code: 453 } ]
調査
- エラーメッセージからは、v1.1でも部分的にまだ利用可能のような雰囲気を感じるが、明らかにv1.1を使っているこのworkflowはコケている
- エラーメッセージにある https://developer.twitter.com/en/portal/product は、単に開発者ポータルへ飛ばされるだけ
- python - 403 Forbidden 453 - You currently have access to a subset of Twitter API v2 endpoints and limited v1.1 endpoints only - Stack Overflow
- 開発者のプラン(課金)による、という指摘がある
- Twitter API Documentation | Docs | Twitter Developer Platform
→ 
- 認証は動いている模様。つまり検索などが走りすぎており、止まっているのだと思われる

ビンゴ
問題の整理
- まず、Twitter API v2 に対応したいのは山々だが、機能を回復させるのが先決
- v2を利用したところで、上記に言われるように、無課金では動かんだろうし
- また、Chrome拡張側でTwitterAPIがコケたときに、他の機能も利用できない状態になっているのはけしからん(下記画像参照)

誰だこんな雑な実装したのは
対応
- TwitterのAPIがコケている状態で、他の機能を心中させないよう修正
- Twitter API へ、最低限の課金をしようと思ったが、$100/monthという値段を見てビビった
- ので、定期的に検索をかけて、どこかにデータを蓄積し、厳密にはリアルタイムではないがこれを配信するように修正したい
修正
(1)
(2) 別のrepositoryを立てて、GitHub Actionsで4時間おきとかにTwitter検索叩いて、結果をrepositoryにcommitするようにすればよかろう。後日やる
DRYな備忘録として
GAEのデプロイが失敗する: ERROR: (gcloud.app.deploy) Error Response: [9] Cloud build xxxxx status: FAILURE
問題
趣味として、所属している社会人アメフト部の出欠確認・備品管理アプリをスクラッチで書いているのだけれど、あるときGitHub Actionsで動いている自動化デプロイが、以下のエラーを出して失敗していた。
# 前略 Beginning deployment of service [dev]... ╔════════════════════════════════════════════════════════════╗ ╠═ Uploading 44 files to Google Cloud Storage ═╣ ╚════════════════════════════════════════════════════════════╝ File upload done. Updating service [dev]...failed. ERROR: (gcloud.app.deploy) Error Response: [9] Cloud build xxxxxxx status: FAILURE An unexpected error occurred. Refer to build logs: https://console.cloud.google.com/cloud-build/builds;region=asia-northeast1/xxxxxxx?project=1111111111 Full build logs: https://console.cloud.google.com/cloud-build/builds;region=asia-northeast1/xxxxxxx?project=1111111111
調査
このログから分かることは
- ソースコードを Cloud Storage に上げるところまでは成功している
- Cloud Build で何らかのエラーが出ているが、詳しくはログを見ろ
ということなので、提示されているURLのログを Cloud Console で見に行くと、
Step #2 - "build": Status: Downloaded newer image for asia.gcr.io/gae-runtimes/buildpacks/google-gae-22/go/builder:go_20230305_RC00 Step #2 - "build": asia.gcr.io/gae-runtimes/buildpacks/google-gae-22/go/builder:go_20230305_RC00 Step #2 - "build": ===> ANALYZING Step #2 - "build": ERROR: failed to initialize analyzer: getting previous image: getting config file for image "asia.gcr.io/triax-football/app-engine-tmp/app/dev/ttl-18h:latest": GET https://storage.googleapis.com/asia.artifacts.triax-football.appspot.com/containers/images/sha256:xxxxxxx?access_token=REDACTED: unexpected status code 404 Not Found: <?xml version='1.0' encoding='UTF-8'?><Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message><Details>No such object: asia.artifacts.triax-football.appspot.com/containers/images/sha256:xxxxxxx</Details></Error> Finished Step #2 - "build" ERROR ERROR: build step 2 "asia.gcr.io/gae-runtimes/buildpacks/google-gae-22/go/builder:go_20230305_RC00" failed: step exited with non-zero status: 1
Cloud Storage に格納されているはずの「previous image」が無くてコケているように見える。 で、該当する Cloud Storage のbucketを見に行くと、確かに何も無いので、そりゃコケるわな、という気持ち。
原因
- 年末大掃除のときに「まあ大丈夫やろ」という気持ちで14日以上のエンティティは全部削除されるようにした気がする。

対処
- Previous Image を削除しちゃっているので、同じCDを回してももちろん同様の理由で失敗する。
- 原理的には、base image(GAEなので "runtime")とソースコードさえあれば、previous image を参照せずともアプリケーションのイメージをbuildできるはずである。
- したがって、
gcloud app deployで、previous image を参照しないオプションが無いか、調査し、--no-cacheというオプションを発見した。
--cacheEnable caching mechanisms involved in the deployment process, particularly in the build step. Enabled by default, use
--no-cacheto disable.
ビンゴ
なので、いったん手元で
gcloud app deploy ./app.yaml --no-cache # 以下もろもろ略
としたらデプロイはできた。
原因に対する解決
- 上記と同様に、GitHub Actions 上の CD においても
--no-cacheを指定すれば、Cloud Storage で何が起きてもデプロイできるのは想像がつく --no-cacheのdownsideとしてはもちろん時間がかかることがあるはずなので、logを確認すると、このプロジェクトは軽いので、--cache= 1分30秒--no-cache= 2分00
- Pricingは、$0.003/分、なおかつ無料枠は120分/日 Cloud Build pricing | Cloud Build Documentation | Google Cloud
ということで、このプロジェクトに関しては --no-cache の対応 + Cloud Storage ではもっと早期に自動削除しちゃってよさそう。(7日削除にした)
以上
DRYな備忘録として
Club TRIAX では、メンバー/スポンサーを募集しております!
ちなみに私の役割は「デジタル担当」で、練習場にはほとんど行きません。そういう多種多様な関わりを通じて、仕事や人生そのものを充実させられる、魅力的なチームです。ご興味ある方は、ぜひお声がけください!



![Google Cloudではじめる実践データエンジニアリング入門[業務で使えるデータ基盤構築] Google Cloudではじめる実践データエンジニアリング入門[業務で使えるデータ基盤構築]](https://m.media-amazon.com/images/I/51s2EB1vvoL._SL500_.jpg)