DRYな備忘録

Don't Repeat Yourself.

FlaskのAzure App Service (Linux Container Deploy)へのデプロイでハマった: 503 :( Application Error

問題

  • ローカル開発して、Imageに固める
  • Azure Container Registry に push する
  • その image:tag を指定して Azure App Service を作る
  • :( Application Error ←これが出る

:( 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

解決

# 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
  • 要原因調査

tl;dr

できました。

github.com

% 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はリーダブルだし扱いやすいかなということで、以下の方針で考えた

  1. プロジェクトのソースコードの中から「使ってるアイコン」を特定する
  2. 元のsvgファイルの中から、「使ってるアイコン」だけを残して「使ってないアイコン」を消し、新たなsvgファイルとして保存する
  3. この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="&#x221e;" horiz-adv-x="1792" />
      <glyph glyph-name="notequal" unicode="&#x2260;" horiz-adv-x="1792" />
      <!-- 他にもこんなの -->
      <!-- パターンB -->
      <glyph glyph-name="glass" unicode="&#xf000;" 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="&#xf001;" 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="&#xf1fc;" 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

クラス名とunicodeのhexの対応をみつけた

これを踏まえ、この _variables.scss ファイルをもとに辞書をつくって、fa-paint-brush あるいは f1fcSVGファイルの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ファイがこの世界の中心なのか。しらんけど。

ちょっと読んでみましたが、いきなりコードから入ってわかるレベルではなかった。勉強したかったら仕様から入らんとダメな雰囲気がした。

github.com

ということで、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

github.com

雑感

  • しょせんプログラマは「自分が理解できる限界のレイヤで踊らされているにすぎない」ということを改めて痛感しました
  • でも、できる限り「堀りにいく」という経験や態度は、は非常に重要だなということも、再度実感しました
  • また、個人開発で思いっきり雑で勢い重視の実装や調査をするのは、たのしいなあ
  • 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自体のバージョンマネージャはどれ使うんでしょうね。 わたしはこれ使いたいです。

github.com

モノとしても基本シェルスクリプトで、問題に直面してしまったときのコードリーディングも勉強になりそうですね。

直面した問題

まず、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自身が必要だからで、ここに詳しく説明されているっぽい

go.dev

以下のような記述がある

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 みたいにしていくのがいいんじゃ無いかなとも思う
  • やはり技術が好きなので、これからのキャリアは技術に戻して行こうと思っている
  • だけど、なんかやってることが3年前と進歩が無い
  • 最初はしょうがないと思って、楽しみながら、リハビリしていきたい

DRYな備忘録として

Docker Desktop がクラッシュしたり、Engine Starting からずっと動かなかったりする【Apple M2】

問題

  1. Docker Desktop (GUIアプリ)が起動中にクラッシュして落ちる
    • 当然、この状態でclidocker ps とかしても、「Engine not started」みたいなエラーを得る
  2. 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

という表記があった。

右下のベルのマークにエラーの表示があった

これを参考にググると、

  1. Docker Desktop quit unexpectedly version · Issue #6056 · docker/for-mac · GitHub
  2. Infinite loop when starting docker after update. · Issue #6260 · docker/for-mac · GitHub
    1. Docker Desktop 4.6.1 for Mac Won't Start - #10 by khal3d - Docker Desktop for Mac - Docker Community Forums

などがヒットした。上記のリンク 2-a が直接の参考になったforumのポストであった。

備考

  • 明日からまたパワポを書く仕事に戻る
  • パソコンの黒い画面を見ているほうが落ち着く自分を強く感じる
  • しかし、給料などを考えると、苦しみながらパワポを書いたほうがいいという現状に、ここのところずっと悩んでいる
  • 早く抜け出したい

DRYな備忘録として

Twitter API v1.1を利用する箇所が失敗しているので調査・対応ログ

背景

もうかれこれ10年*1開発が続いているChrome拡張*2があり、このCI/CDの結果報告にTwitterAPIを用いている。また、ユーザにとっても、Chrome拡張上でTwitter認証を行い画像付きツイートを行える機能を提供している。

今回、この機能が以下のエラーを吐いて失敗している。どうやら、Twitter API の「アクセスレベル」関係でコケているようだ。

そらそうだ。10年ぐらい前からずっとv1.1だもんなぁお前

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
  }
]

調査

  • 認証は動いている模様。つまり検索などが走りすぎており、止まっているのだと思われる

Chrome拡張内で検索が走る部分のNetworkログ。たしかに失敗している

ビンゴ

問題の整理

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

Twitter検索が走り、失敗するとポップアップ画面のレンダリングごとクラッシュしている。
誰だこんな雑な実装したのは

対応

  1. TwitterAPIがコケている状態で、他の機能を心中させないよう修正
  2. Twitter API へ、最低限の課金をしようと思ったが、$100/monthという値段を見てビビった
    • ので、定期的に検索をかけて、どこかにデータを蓄積し、厳密にはリアルタイムではないがこれを配信するように修正したい

修正

(1)

github.com

(2) 別のrepositoryを立てて、GitHub Actionsで4時間おきとかにTwitter検索叩いて、結果をrepositoryにcommitするようにすればよかろう。後日やる

DRYな備忘録として

お名前ドットコムからGoogle Domainsへのドメイン移管ログ

tl;dr

support.google.com

学び

  • ドメイン移管は、ドメイン管理事業者同士のやりとりで完結する
  • ドメイン移管は、移管事業者が、移管事業者へ要請を送ることでトリガーされる
  • その際に必要なものは、移管事業者が発行する「認証コード」ないし「Authコード」のみである

注意

  • お名前ドットコムにおいて、移管に必要な「認証コード」ないし「Authコード」を得るとき、「移管」などのキーワードでメニューを検索してもヒットしない
  • ドメインドメイン詳細 → Authコード表示、が正解

雑感

  • サービスをつくるのはたのしい

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というオプションを発見した。

--cache

Enable caching mechanisms involved in the deployment process, particularly in the build step. Enabled by default, use --no-cache to disable.

gcloud app deploy  |  Google Cloud CLI Documentation

ビンゴ

なので、いったん手元で

 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 では、メンバー/スポンサーを募集しております!

ちなみに私の役割は「デジタル担当」で、練習場にはほとんど行きません。そういう多種多様な関わりを通じて、仕事や人生そのものを充実させられる、魅力的なチームです。ご興味ある方は、ぜひお声がけください!

www.triax.football