nykergoto’s blog

機械学習とpythonをメインに

ゴルフという名の、不確実性最適化ゲーム

2026年・関西 Kaggler 会#1 第一部で発表の機会をいただき、表題のタイトルで発表を行いました。小難しいタイトルですが、中身は「ゴルフ楽しいよ!」という至極単純なものです笑 発表中喋られなかった内容を含め文章に起こしてみました。発表で使ったスライドは以下のものです。

(中身について一部ご指摘を頂いたため修正を行っています。貴重な指摘ありがとうございます!! なかなか自分で気がつけず恥ずかしい… )


Intro: ゴルフとは不確実性最適化ゲームである。

みなさんは「ゴルフ」に対してどのような印象を持っていますか?「接待」「お金がかかる」「おじさんのスポーツ」……そんなイメージを持たれる方が多いかもしれません。実際、「Kaggler(データ分析コンペティター)に何の関係があるんだ?」と思う方もいるでしょう。

しかし断言します。ゴルフはKaggler的思考と驚くほど相性が良いスポーツです。

数理最適化やデータサイエンスを専攻してきた私から見れば、ゴルフの本質は「不確実性と向き合いつつ、状況を最適化するゲーム」そのものです。今回は、データサイエンスの視点からゴルフというスポーツを解剖し、そのロジカルな面白さをお伝えします。

理想と現実:アマチュアのゴルフは「確率論的」である

マリオゴルフ64のようにボールは正確に飛んでいかない。

多くの人がイメージするゴルフ、あるいは『みんなのGOLF』のようなゲームの世界観は「決定論的・極小ノイズ」です。狙った方向にゲージを合わせれば、多少のミスはあれど、ほぼ狙い通りにボールが飛びます。

しかし、リアルのアマチュアゴルフは違います。その実態は「複雑怪奇な超高分散・確率論的」な世界です。

  • 入力(スイング)のブレ: 毎回クラブの芯に当たる確率(ミート率)がバラバラなため、「狙った場所を中心に綺麗に分布する」なんてことはまずありません。
  • 外れ値のオンパレード: 「右に40ヤード曲がるスライスが出た」と思えば、次のショットでは「左にドカンと飛ぶチーピン」が出る。さらにOB、空振り、池といった手痛いペナルティ(外れ値)が常に隣り合わせです。

つまり、我々アマチュアのゴルフは、決定論ではなく「確率論的」に捉える必要があるのです。

数式で理解するゴルフ:Stroke Gained(SG)の最適化

Stroke Gained は現代ゴルフを象徴するもっとも重要な式の一つ。

ゴルフにおける意思決定を、数理的に定式化してみましょう。

現代ゴルフのデータ分析において最も重要な指標が Stroke Gained(SG) です。これは、「打つ前の期待値」と「打った後の状況から得られる期待値」の差分(勾配)として1打の価値を定義するものです。

プレイヤーの目標は、毎回この打球価値(SG)を最も高める意思決定 $A$ を行うことに帰結します。

ここで、ゴルフを構成する要素を以下のように定義します。

  • $p(A, x)$:意思決定 $A$ のもとで、ボールが地点 $x$ に飛ぶ確率分布
  • $S(x)$:地点 $x$ からカップインするまでに必要な「期待ストローク数」(過去の大規模な統計データから算出可能)

このとき、最大化すべきSGの数式は以下のようになります。

$$\max_{A} SG = \max_{A} \left[ S(\text{now}) - \int p(x | A) \times S(x) dx - 1 \right]$$

ここで $S(\text{now})$(現在の期待値)や最後の $-1$(1打分の消費)は、現在の意思決定 $A$ には依存しない定数項です。そのため、この最大化問題は次のような「やばい場所($S(x)$ が極端に大きい場所)を避ける最小化問題」へと等価変形できます。

$$\min_{A} \left[ \int p(x | A) \times S(x) dx \right]$$

※イメージ:池やOBなどのペナルティゾーン($S(x)$のスパイク)と、自分の打球の確率分布が重なる面積を最小化する最適化問題

つまりゴルフの意思決定とは 「スコア $S(x)$ が急激に悪化する場所(池やOBなど)にボールが飛ぶ確率 $p(x | A)$ を最小化する選択 (クラブや狙い所 $A$ は何か?」を解く問題なのです。

なぜこの最適化問題は難しいのか?

過去の膨大な集計データがあるため、地形に応じた期待ストローク数 $S(x)$ を把握することは比較的容易です。しかし、問題はもう一方の 確率分布 $p(A, x)$ の把握が極めて難しい 点にあります。

1: ベイジアン的な事後分布の更新が必要

自分のショットがどう散らばるかという分布のパラメータ $\theta$ は一定ではありません。さらに真の $\theta$ を知る余地はなく常に推定をする必要があります。

すなわち、ラウンド前の状態(事前分布 $p(\theta)$)に対し、「今日のこれまでの数ホールの出来(尤度 $p(D|\theta)$)」を掛け合わせ、常に脳内で事後分布を更新し、現在の真の打球分布を推定し続けなければなりません。

例えば「今日はどうも右に出るな(尤度が右に偏っている)」と思えば、それを見越した分布を想定する必要があるのです。

2: 自然環境という環境ノイズ($\epsilon$)

さらに、分布は自分の実力($\theta$)だけでなく、風、雨、地面の傾斜(ライ)といった常に変動する環境ノイズ $\epsilon$ の影響を強く受けます。 これにより、常に観測ノイズを孕んだ高ノイズな数式を解くことを強いられます。

1, 2 を組み合わせると、先程の式は以下のようになるでしょう。$D$ による自分の状態 $\theta$ の更新が追加されているのが違いです。

$$\min_{A} \left[ \int p(\theta)p(D|\theta)p(x | D, \theta, A, \epsilon) \times S(x) dx d\theta \right]$$

3: アマチュアほど「分布の裾(テール)」が広い

理想と現実。アマチュアのほうが球は広く分布し思ったところには飛ばない。

プロゴルファーの打球分布は、幅30〜40ヤードにほぼ収まるシャープな正規分布に近い形状をしています。一方で、アマチュアの分布は幅100ヤード以上に分散し、ギザギザで裾(テール)が広い非正規分布になります。

これはすなわち「1打のミスで大爆死するリスク」を常に抱えているアマチュアこそ、プロ以上に自分の意思決定 $A$ と分布 $P$ の変化に敏感でなければならないのです! へたなほうが考えることが多いだなんて!!

具体的なシチュエーションの想定: 残り 150yd のピンをどうやって狙う?

具体的に、データサイエンス思考をどう実践に落とし込むのか、シミュレーションしてみましょう。

残り距離 150yd のところまできた GOTOさん。ちなみにゴルフは未だに終わっている単位ヤードを利用します。

【シチュエーション】

  • ピンまで残り 150ヤード
  • グリーン 左サイドに池 (入ると+1罰)
  • グリーン 奥にOB (入ると+2罰・問答無用で大爆死)
  • グリーン 右サイドにバンカー (入ると脱出が難しいハザード)
  • ピン位置は グリーン左奥
  • プレイヤーの状態: 150ydのショットはそこそこ打てるが、今日のデータ(ここまでのショット傾向)を見ると、目標より左に外れやすい(尤度が左に偏っている)

直感的なアマチュアは「残り150ydだから、150yd飛ぶクラブでピンを真っ直ぐ狙おう」と考え、左に引っ掛けて池やOBに打ち込み、大爆死(期待ストロークの崩壊)を迎えます。

Kaggler的思考における最適解(Action $A$)はこうです。

  1. 縦距離の最適化(OBの回避): 最もペナルティが重いのは奥のOB(+2)です。そのため、ピン奥へ絶対に外れないよう、あえて150yd届かないクラブ(140yd強のクラブ)を選択します。
  2. 横距離の最適化(池の回避): 次に避けたいのは左の池(+1)です。さらに自分の今日の分布は「左に偏る」というデータが出ています。したがって、ピンを直接狙うのは論外。安全な右サイド(バンカー方向)をエイム(狙い所)に設定します。

【結論】 「140yd強のクラブで、グリーン右手前を狙って打つ」 これが、バンカーや手前のラフを許容しつつ、池・OBという最悪のペナルティを最小化する、数学的な最適解アクションとなります。

※実際の現場では、ボールが置かれた地面の傾斜(ライ)、グリーンの細かな傾斜、自身のバンカーの得意・苦手、そして現在の自分のスコア状況(守るべきか、攻めるべきか)といった無数の特徴量が加わり、問題はさらに複雑化(無限の変数化)していきます。

結論:ゴルフは実質「リアルタイム・データコンペ」である

なぜ、これほどまでに難解で高ノイズなゲームに多くの大人が、そしてデータサイエンティストが狂わされるのでしょうか?

理由は2つあります。

  • 超高速のフィードバックループ: ショットを打つ前に脳内で必死に特徴量を処理し、モデル(意思決定)を構築して実行すると、その結果(予測値と実測値の差分)が一瞬でフィードバックされます。そしてすぐに次の「事後分布の更新」が求められる。この1ラウンドで数十回も超高負荷の分析と検証を繰り返す構造は、まさにデータコンペ(KaggleやatmaCup)そのものです。
  • たまに出る「奇跡の1打」というガチャ(強化的報酬): どれだけ下手であっても、100回に1回はプロ並みの完璧なデータ(ショット)が出力されることがあります。このランダムにもたらされる強化的報酬が脳に強烈に刺さります。これは、atmaCupで提出ボタンを押して、突然リーダーボードが緑色に光る(スコアが上がる)あの感覚と完全に一致しています。

ほら、データコンペとゴルフ、驚くほど親和性が高いと思いませんか?

まとめ

  • ゴルフは確率論的な期待値最適化ゲームである。
  • スコアを最適化するには、己の実力と環境を見定め、打球の確率分布 $P$ を正しく知る(己を知る)必要がある。
  • 無限に頭を使い、仮説検証を繰り返すゴルフは、実質データコンペである。

頭脳戦としてのスポーツに飢えているKagglerのみなさん、ぜひクラブを握って、この極上の「不確実性最適化ゲーム」を一緒にプレイしましょう!

興味が湧いた方は、いつでも @nyk510 までお声がけください。一緒に練習やラウンドに行きましょう!

今更GPTに小説を作らせるのにハマっている

アメリカの大統領選挙というものは、実に不思議なものである。国民は民主主義の誇りだと言うし、世界はそれを自由の祭典と称えて、えらく持ち上げるけれど、実際はどうだろうか。候補者たちは声を枯らし、涙を流し、握手を交わしながら、愛想笑いを振りまいて各地を巡るが、果たしてそこに人間の本性をありのまま見せている者がいるだろうか。

民主党だ共和党だと人は言うけれど、もとは人間同士の競争だ。政策を並べるのはいいが、有権者というものは、案外そういったものを深く吟味して投票しているとは限らない。テレビ討論の出来が勝敗を分ける、などと言われるが、そう言われてみれば、あの討論というものも実に妙なものだ。「あなたの経済政策はどうですか?」と聞かれて、「それはいい質問です」と言いながら、結局は相手の悪口に終始してしまう。討論会というよりもむしろ悪口合戦、プロレスのようなものだ。それを見て人々が喝采を送り、政治への理解を深めたなどと言っているのだから、世の中というのは、まことにわからぬ。

そして大統領選挙には演説というものがあるが、実際のところこれは芝居に近い。大統領候補の演説は、政策の話というより感情の演出が勝負だ。「アメリカは偉大だ」「我々には夢がある」といった決まり文句を語るとき、涙を浮かべ、声を震わせることが大切であるらしい。しかし、よくよく考えてみれば、政治家が涙を流したからといって、その涙が本物である保証はどこにもない。本物かどうかは二の次で、とにかく涙を流すこと、感情を揺さぶること、それが大事なのである。

メディアもまた困ったもので、候補者をやれ「保守派」だ「リベラル派」だ、「急進派」だ「穏健派」だと区別してみるが、そのような言葉の分類が本質をとらえているのか怪しいものだ。実際には、候補者個人の品性や教養が問題であって、「派閥」の分類は空疎な言葉遊びにすぎない。

近頃はSNSというものがあり、候補者は短い言葉で自らを表現するようになったが、これまた困ったことに、短ければ短いほど内容が浅くなりがちだ。「アメリカを再び偉大にする」というが、そもそも偉大とは何か、その「再び」とはいつを指しているのか、具体的に聞かれても答えに困る。つまり、有権者の側にも候補者の側にも、もともとその言葉に深い意味など求めてはいないのである。浅くとも、響きがよく、気持ちよければよい。それが選挙というものの現実である。

トランプ氏が登場したとき、言葉が下品だ、言動が粗暴だ、と批判されながらも、あっという間に支持を広げてしまったが、これは決して不思議なことではない。生活や感情が言葉を選ぶのであって、言葉が先にあるわけではないからだ。人々が政治への不満や怒りを抱いている限り、それに即した言葉や態度を持つ候補者が出てきて支持されることは当然である。礼儀正しく美しい言葉を話す候補者が敗北し、粗野で直接的な候補者が勝つのは、言葉の問題ではなくて、生活そのものがそうさせているからである。

結局、アメリカの大統領選挙もまた、言葉や表現、政策といった表層的なものではなく、それらを動かす生活そのもの、感情そのもの、教養そのものの問題である。大統領選挙を変えたいというならば、表現や演説のテクニックをいくら改めても無駄であって、まず人々の暮らしや意識、教養を高める以外にない。政治を変えるには、有権者が変わるしかない。候補者が国民に媚びるのではなく、国民が政治を真に批判し、自らを高めることが先であろう。

つまり、アメリカの大統領選挙というものは、民主主義の華麗なる祭典などではなく、人間の愚かしさ、欲望、感情が渦巻く、ただそれだけのことであって、その本質は古今東西、変わることはないのである。


これは坂口安吾の短編「敬語論」を元にしてアメリカ大統領選挙の講評を作らせた例。普通に読んでて楽しいのでいろんな話題を小説にしてもらってふんふん読んでいる。本読むのはあれだけどな、みたいなときの暇つぶしにちょうどいい (Webの謎の広告まみれの情報量のない記事を読むよりは楽しい)。

ちなみになんでこんな文章にしたの?を聞くのも結構面白い。この例だと…

この文章がなぜこんなふうになったのか、ということを自己解説してみましょう。

まず、あなたが提示した坂口安吾の短編には、独特の文体と思想があります。坂口安吾の文章の特徴として、言葉や敬語、習慣などの表面的な現象を通じて、その背後にある人間の本質や生活、社会心理を鋭く観察し、皮肉や批評を交えて論じる、というものがあります。

この特徴を具体的に挙げると以下のようになります。

① 言葉や表現そのものに対する批判 坂口安吾は表面的な言葉の乱れや変化を問題視するのではなく、むしろその背後にある人間の感情や生活そのものを見抜きます。この点を私は意識的に取り入れました。

たとえば元の文章では、

「敬語など突ッつき、言葉の合理性などということを言いだすと、言葉全体を新たにメートル法式につくりあげない限り、合理化の極まる果はないのである。」

とあります。つまり、坂口安吾は表層的なルールを強制しても無駄であると断じています。私の書いたアメリカ大統領選挙の講評にも、この考えを踏襲しており、「候補者の表現や演説方法をいくら改善しても、本質である国民の意識や教養が変わらなければ無意味だ」という論調になりました。

② 生活や人間の本質に基づいた批評 坂口安吾の短編では、

「生活や感情が直接こもっているものだ。だから、生活や感情によって動きがあり、時代的に変化がある。」

という趣旨が繰り返されます。私はこの考えを取り入れ、大統領選挙の現象面(演説や討論会の形態、言葉遣い、候補者の演出)を問題にするのではなく、それらを生み出す社会の感情、つまり有権者の生活や心理の方を批評対象としました。

③ 皮肉で諷刺的なトーン 坂口安吾は、自らの主張をする際、あえて皮肉やユーモアを多用します。たとえば、

「ザアマス夫人というのがある。キザの見本だというので漫文漫画に諷刺され世間の笑いものになっているから、自粛するかと思うとそうじゃない」

というように、笑いの対象を通じて真理を指摘する手法です。私もアメリカ大統領選挙について論じる際、候補者が流す涙や演説の芝居がかった性質をわざと強調し、皮肉めいた調子を選択しました。

④ 本質と表面を区別する視点 安吾の文は、表面的な現象(敬語、女性語、言葉遣い)と、それらの背後にある実際の生活や社会構造(階級、教養、差別)を区別し、その二つを鋭く対比しています。

私も、選挙運動における「美辞麗句」と、それを実際に支持する社会の「本当の欲望や感情」を対比し、言葉や政策がいかに空疎になりやすいかを示しました。これは安吾的な手法を大統領選挙という別のテーマに応用したものです。

結論としてのまとめ こうして見ると、私の書いた文章がこのようになったのは、あなたが提示した坂口安吾の短編の精神や文体を意識的に取り入れ、その特徴を忠実に再現しようとした結果であると言えます。

表面的な言葉や現象に惑わされず、その奥にある社会の心理や感情に注目した。

言葉の問題を生活や感情の問題に置き換えた。

皮肉や諷刺的な調子で批評した。

こうした要素を意識して用いたために、結果として坂口安吾の文体や視点に近い、大統領選挙に対する批評が生まれたわけです。


とのこと。結構ちゃんとしとる。

ECSのHealthCheckがDjangoの動的ページのときに失敗する

Django を ECS with Fargate に Deploy しているようなアプリケーションがあり、Django の ECS task には ALB が紐付いていている状況を考える。

TargetGroup はつながっている先のサーバーが動作しているかどうかをテストして Healthy or Not を判断するような仕組みで、はじめはこの確認先を静的なファイル /health_check.html で運用しようと思っていたのだけれど、ふとこれを動的に生成するページに変更するとステータスコード 400 が帰ってくるため health check に失敗するようになった。

ちなみになんで動的なページにしたいかというと静的ファイルだと nginx で止まってしまうので Django の処理まで到達させたい、という気持ちがあったためである。具体的には nginx の config には以下のような設定が入っているので, Django の static file を指定すると $uri で match して Django server までリクエストが到達しないので、なんか嫌だなーという気持ち。nginx だけ動いていて Django が動かないことってまあないとは思うのだけれど、動きとしては確認したいじゃない?

server {
  # 中略
  # django の public (collect static のファイルが保存されるディレクトリ) が root に指定されている
  root   /var/www/django-app/public;

  location / {
    # なので $uri で static file に match するため `@proxy_to_app` にたどり着かない
    try_files $uri $uri/ $uri/index.htm @proxy_to_app;
  }

  location @proxy_to_app {
    # ここまで来れると, django につながる (正確には gunicorn server)
    proxy_pass http://app_server;
  }
}

health check の該当の path にブラウザから直接アクセスすると問題なく画面は表示されていて、ステータスコードは 200。ちなみに health check 先を静的ファイルに変更すると 200 が帰るようになるので、単純に静的・動的によって TargetGroup からのリクエストが成功する失敗するが切り替わることがわかった。

今の ECS Container で動く Django サーバーの構成は nginx + gunicorn で起動していて、上に書いたように静的ファイルは nginx 側で処理されるようになっている。したがって

  • nginx からのレスポンス = 200
  • nginx → gunicorn 経由の django のレスポンス = 400

ということ。

色々調べていると Allow Domains の設定の影響であるという stack overflow にたどり着いた。

stackoverflow.com

要約すると

  • Target Group から送られる health check のリクエストは、ECS のコンテナが存在している IP を直で叩きに行く。
  • Django がそのリクエストを受け取ると IP は Allowed Host にないためエラー (400) を返す。

ということらしい。解決方法はいくつか提示されていて、一つは Django が起動したときに今動いている ECS Container のメタ情報を AWS へリクエストして Allowed Host を増やすというもの。なかなか斬新なアイディアだが、今実行している container の状況については、AWS の公式が提供している機能としてあるらしい。初耳。

python の package もあって, 実装はこういう感じ. app が読み込まれるタイミングで meta data から取得できる local-ipv4 を allowed hosts 追加するという動きになっている.

https://github.com/sjkingo/django-ebhealthcheck/blob/master/ebhealthcheck/apps.py

from django.apps import AppConfig

class EBHealthCheckConfig(AppConfig):
    name = 'ebhealthcheck'

    def ready(self):
        """
        Fix for the EB health check host header:

        https://aalvarez.me/posts/setting-up-elastic-beanstalk-health-checks-with-a-django-application/
        https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
        """

        from django.conf import settings
        import requests

        def get_ec2_instance_ip():
            metadata_url = "http://169.254.169.254/latest"
            n = 0
            while n < 10:
                try:
                    token = requests.put(metadata_url + "/api/token", timeout=0.1, headers={
                        "X-aws-ec2-metadata-token-ttl-seconds": "60",
                    }).text
                    ip = requests.get(metadata_url + "/meta-data/local-ipv4", timeout=0.1, headers={
                        "X-aws-ec2-metadata-token": token,
                    }).text
                except (requests.exceptions.ConnectionError, requests.exceptions.InvalidHeader):
                    n += 1
                    continue
                else:
                    return ip
            return None

        public_ip = get_ec2_instance_ip()
        if public_ip:
            settings.ALLOWED_HOSTS.append(public_ip)

これを入れて問題なく動くかなーと思ったら, health check は一向に直らない。

request に失敗しているのかも? とおもい ECS execute command を使って実行中の container 内部で http://169.254.169.254/latest にアクセスすると 400 エラーになった。ということはこの metadata 取得方法ではうまく行かないということのよう。よくわからないが、多分別のエンドポイントにアクセスしないと行けないんだろうなと思いつつ調べていると Fargate などの仮想マシン上では別の取得方法があるらしいことがわかった。

Use appropriate ECS credentials on CodeBuild maven job: https://stackoverflow.com/questions/42794486/use-appropriate-ecs-credentials-on-codebuild-maven-job/47028691#47028691

これによると

When using AWS Containers (Like CodeBuild does). The instance metadata is at a different location to the usual http://169.254.169.254/latest/meta-data/

Instead. AWS sets an Environment variable $AWS_CONTAINER_CREDENTIALS_RELATIVE_URI which points to the correct URI to obtain metadata. This is required by the AWS SDK's and other tools in order to assume an IAM Role.

とのこと。なるほどこれは CodeBuild だけど仮想マシンという意味では似ているしこの筋でいけるかも? と思ってとりあえず書いてあるエンドポイント http://169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI に container からリクエストするとデータが取れた! 結論から言うと Fargate では https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task-metadata-endpoint-v3.html このドキュメントにあるようなエンドポイント経由でメタデータを取るのが正解らしい。具体的には以下の ECS_CONTAINER_METADATA_URI に入っている URL を利用するのが正解。

${ECS_CONTAINER_METADATA_URI}
このパスはコンテナのメタデータ JSON を返します。

レスポンスは typescript のスキーマとかがあるわけではないのでレスポンスを見て類推。

{
  "DockerId": "43481a6ce4842eec8fe72fc28500c6b52edcc0917f105b83379f88cac1ff3946",
  "Name": "nginx-curl",
  "DockerName": "ecs-nginx-5-nginx-curl-ccccb9f49db0dfe0d901",
  "Image": "nrdlngr/nginx-curl",
  "ImageID": "sha256:2e00ae64383cfc865ba0a2ba37f61b50a120d2d9378559dcd458dc0de47bc165",
  "Labels": {
    "com.amazonaws.ecs.cluster": "default",
    "com.amazonaws.ecs.container-name": "nginx-curl",
    "com.amazonaws.ecs.task-arn": "arn:aws:ecs:us-east-2:012345678910:task/9781c248-0edd-4cdb-9a93-f63cb662a5d3",
    "com.amazonaws.ecs.task-definition-family": "nginx",
    "com.amazonaws.ecs.task-definition-version": "5"
  },
  "DesiredStatus": "RUNNING",
  "KnownStatus": "RUNNING",
  "Limits": {
    "CPU": 512,
    "Memory": 512
  },
  "CreatedAt": "2018-02-01T20:55:10.554941919Z",
  "StartedAt": "2018-02-01T20:55:11.064236631Z",
  "Type": "NORMAL",
  "Networks": [
    {
      "NetworkMode": "awsvpc",
      "IPv4Addresses": ["10.0.2.106"]
    }
  ]
}

見た感じ Networks → IPv4Addresses の配列を全部入れれば OK っぽい。ということで実装。

# `elb_healthcheck/apps.py

import os
from typing import List

import requests
from django.apps import AppConfig


class ESCHealthcheckConfig(AppConfig):
    """Fargate上で動く instance の local ip を allowed hosts に追加する"""

    name = "ecs_healthcheck"

    def ready(self):
        from django.conf import settings

        current_allowed_hosts = [*settings.ALLOWED_HOSTS]
        print("current allowed hosts: {}".format(", ".join(current_allowed_hosts)))

        url = os.getenv("ECS_CONTAINER_METADATA_URI", None)

        if url is None:
            print("uri is not found")
            return

        res = requests.get(url, timeout=1)
        if res.status_code != 200:
            print("fail to fetch")
            return
        data = res.json()
        local_ips: List[str] = data.get("Networks", {}).get("IPv4Addresses", "")

        if len(local_ips) > 0:
            print(f"add local ip: {', '.join(local_ips)}")
            settings.ALLOWED_HOSTS = [*current_allowed_hosts, *local_ips]

この app を INSTALL_APPS に追加すれば OK

# settings.py

INSTALLED_APPS = [
    "ecs_healthcheck",
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    ...
]

余談

これで一見落着、と思ったが Health Check は最低限のリクエストとするべき、という記事を見つけてしまった。

https://cha-shu00.hatenablog.com/entry/2022/08/20/144747
ELB によるヘルスチェックでは、「アプリケーションが新規のリクエストを受付できること」だけを検証するべき。例えば、"ok"などの単純な文字列を返したり、小さな静的コンテンツを返すようなパスを指定する。

確かに DB への接続を watch したければサービスの正常な動作を見るような仕組みを入れたほうがいいよね。例えば betteruptime とか。ただ nginx の動きが OK ならアプリケーションが新規のリクエストを受け取れる状態なのか、は僕にはなんともわからなかった。気持ち的には Django まで動いているぞ! がわかったほうが気分はいいけどそれはやりすぎかもなー。宿題です。

コンテナ物語 世界を変えたのは「箱」の発明だった・を読む。

西梅田にあるジュンク堂でこの本を買った。珍しく友達との飲み会の約束よりも早めについてしまったので本でも仕入れていこうかとより、適当にさまよっていると「コンテナ」という単語が目に入った。ソフトウェアエンジニアにとってコンテナは大変身近な概念だ。といってもこの本で言うところの運送のコンテナではなく、コードの実行環境を仮想化して管理する技術のことであるが、目に入ったのはなにかの縁だろうと思いカゴへ入れた。実店舗ではこのような不思議なマッチングがあるので楽しい。

https://www.amazon.co.jp/%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E7%89%A9%E8%AA%9E-%E4%B8%96%E7%95%8C%E3%82%92%E5%A4%89%E3%81%88%E3%81%9F%E3%81%AE%E3%81%AF%E3%80%8C%E7%AE%B1%E3%80%8D%E3%81%AE%E7%99%BA%E6%98%8E%E3%81%A0%E3%81%A3%E3%81%9F-%E5%A2%97%E8%A3%9C%E6%94%B9%E8%A8%82%E7%89%88-%E3%83%9E%E3%83%AB%E3%82%AF%E3%83%BB%E3%83%AC%E3%83%93%E3%83%B3%E3%82%BD%E3%83%B3/dp/4822289931

この本は運送業で使われるコンテナの歴史を記述したものだ。

ただの箱・コンテナ

巨大な港の写真などで無数のコンテナを積んだ船にクレーンが近づいていって、載せたりおろしたりしている映像は見たことがあるし、JR 四国でも貨物列車でコンテナが運ばれているのを見たことがある。僕たちがいつも買っている商品がコンテナを使って海外から送られていることも事実として知っている。ただその歴史や意味合いについて考えたことは一切なかった。なんなら一番はじめにコンテナという概念を聞いたときには、特にそれはすごいとかは思わなくて、そりゃ運ぶんだから箱ぐらいいるだろうという程度だったように思う。だって固定の箱に詰めるだけなのだから。

しかしこの本を読むとそのようなイメージは一新され、これがとてもインパクトのある概念であることが理解できるだろう。そして、面白いのは、コンテナという概念自体がとんでもない発明かというとそうではないところにある。

もちろん一番はじめにコンテナを使い始めた人は、既存の運送方法とは違う方法を取り入れるわけなので、先見性だったり実行力はあるだろう (マルコフマクリーンというおじさんがそれに該当する)。

ただマクリーンさんがすべてを生み出したわけではなく、コンテナが生まれるにはある種の必然性があり、それに彼は乗ったというかタイミングが良く出会ったようなところがあって、とうのマクリーン氏も「これやれば効率が上がるんじゃないか」程度の意識だったようだ。要するに既存の運送方法の延長線としてコンテナはあり、ちょっと効率よくなって嬉しいね程度のものということだ。

実際に当時の資料等が紹介されているが、一時しのぎのコスト削減方法としてしか見られていなかったし、専門家はコンテナ船は長距離航路では役に立たないと考えていた。専門家や経営者だけではなくて働いている労働者もそれは同じで、当時の海運業は荷物は港で働く労働者が載せたりおろしたりするのが基本だったので、労働者側からみたコンテナは荷物の一つであり、なにか交渉するにしても「そのコンテナの荷降ろしをやらせてくれ」という対象であった。みんなあまり大事だと思っていなかったのだ。

しかし、みなが侮ったコンテナは、界の物流の状況をまるっきり変えた。

世界の運送コストはそれ以前より劇的に減少して届くまでの日数・安全性も格段に向上した。その波及効果も大きくて、工場は最終消費地につくる必要はなくなったて産業構造や雇用は激変したし、東アジアが貿易拠点になったりでそれがない世界と比べて全く違うものになってしまった。読むとほんとにすげーなと思うのでぜひ読んでほしい。

なんでわからなかったのだろう

やはり気になるのは、これをなんでみんなわからなかったのだろうか? というところだ。

一つには今ある制約条件に縛られすぎたことがあると思う。

ちゃんとコンテナに対応した港は必要だし出荷する人がコンテナを意識した荷詰めをする必要もあるし、各国の規制当局との交渉もあるしで、当時はハードルはたくさんあったことが原因だけれど、結局このコンテナをフル活用したときの世界をみなが想像できなかったことがその過小評価につながったのだろうと思う。

もう一つ、こちらはコンテナゆえかもしれない原因なのだが、単なる箱なので侮ってたのでは? というところ。これは反対にすごそうに見える最先端技術が別に対して生活を変えないし利益を生まないパターンと対照的かもしれないのだけれど、やっぱり箱は箱なので今度からコンテナになるよと言われても直感的にすごいと思えないというのがあるようにも思う。

合理的予測の限界

もう一つ印象的なのは、予測が尽く外れていく様である。

p.6: 「コンテナ物語」が教えてくれるいちばん重要なものは「よきせぬ結果」が果たす役割だと思えてならない。

と著者はまえがきに書いている。全部終わった今から見るとなんでわからなかったのかとなるけれどもちろん当時の人達も皆なりに合理的に考えた結果見誤ったわけで、それは未来から見た今と重なるようにも思う。

何らかプロジェクトをしようと思うと、過去のデータをもとに定量的な予測をして計画を建てる。これは大変合理的できれいなんだけれど、その限界がわかりやすく提示されているように思える。計画から降りて今の状態についていくしなやかさでしか対応できないことの大事さを感じた。

他にも具体的にコンテナの規格を広げるときの大変さとか、荷降ろしの過去の常識とかいろいろな知識に触れられるとてもいい本だった。単なる箱だけどすごいんだね。