はじめに

こんにちは!2026年7月の1か月間、「CA Tech JOB」に参加した池畑です!

株式会社AI Shiftの「AI Worker Platform」開発チームで、バックエンドエンジニアとしてDev・Stage環境のドメイン移行のタスクに取り組みました。

本記事では、ドメイン移行を通して、ユーザーのアクセスがインフラ層からアプリケーション層へ届き、そこで使われる設定値が最終的な挙動につながるまでを追った過程と、そこから得た学びを紹介します!

 

ドメイン移行の背景とタスク全体像

今回担当したタスクについて、背景と全体像を説明します。

現在、AI Worker Platformは、Dev・Stage・Prodの3つの環境を持っており、今回はそのうちのDev・Stage環境で使用しているドメインを新しい構成へ移行するタスクです。

移行前はDev・Stage環境がProd環境と同じルートドメインの配下にあり、本番用と非本番用のDNSレコードが一つのドメインに集まっていました。そのため、DNSレコードを管理しづらいだけでなく、本番環境のURLからDev・Stage環境のURLを推測されやすいという課題もありました。

そこで、Dev・Stage環境を本番環境とは別のルートドメインへ移し、DNSレコードとアクセス先を環境ごとに分けることになりました。また、ドメインを変更する機会に、Stage環境を表すstageとstgの表記を統一し、ホスト名もワイルドカードを利用しやすい順序へ変更しました。

「ドメイン移行」と聞いた時点で、新しいホスト名を名前解決するためのDNSレコードと、HTTPS接続に必要なTLS証明書を用意することは想像できていました。しかし、インフラに関する実装経験はなく、その後リクエストがどの層を通ってアプリへ届き、アプリ内でドメインがどのような挙動につながるのかまでは説明できませんでした。
ドメインを利用している箇所を漏れなく変更するには、最初にタスクの影響範囲を把握する必要があります。
そのため、まずは実装に入る前に既存の構成図とコードを手がかりに、ユーザーのリクエストがブラウザからアプリへ届くまでと、アプリへ届いた後の処理を追っていきました。

 

リクエスト経路を追って見えてきた影響範囲

今回確認したリクエスト経路を簡略化すると、次のようになります。

調査の結果、変更対象は単にDNSレコードと証明書を追加するだけではなく、大きく次の3つに分かれることが分かりました。

 

観点 主な確認・変更対象 必要な状態
アプリへ届ける DNS、TLS証明書、HTTPRoute 新ドメインへのリクエストが既存のアプリへ届く
アプリで扱う Cookie、CORS、外部向けURL 届いたリクエストを新ドメインとして正しく扱える
移行する 新ドメインの追加、切り替え、旧設定の削除 既存環境を利用しながら段階的に切り替えられる

 

インフラ側では、Cloudflare DNS、Google Cloud Load Balancer、GKE上のGatewayという異なる場所で、同じホスト名がそれぞれ名前解決、証明書の選択、転送先の選択に使われていました。
アプリ側でも、HostやOrigin、環境変数として渡されたドメインが、Cookie、CORS、外部向けURLなどに使われていることが分かりました。

ここで見えてきた影響範囲をもとに、各層で必要となる変更を洗い出し、新ドメインの追加から旧設定の削除までを段階的に進める計画を立てました。その上で、Terraform、Helm、アプリケーションコードを変更し、それぞれの層で新ドメインを扱えるように実装と検証を進めていきました。

このタスクを進めていく過程でとくに理解が深まったのが、異なる層をまたいで値の流れを追うことと、完成形だけでなく移行途中の状態まで考慮して計画を立てることでした。
以降では、この2つの学びを、実際の調査や移行計画で直面した出来事とともに掘り下げます!

 

学び①:インフラとアプリをまたいで値を追う

1つ目の学びは、リクエストや設定値がどこから入り、どの処理を通って最終的な挙動につながるのかを一続きで考える視点です。

調査を始めた時点では、アプリ内で使われるドメイン関連の値について、どれがブラウザからのリクエストに含まれ、どれが環境変数として渡されるのかを整理できていませんでした。そこで、ドメイン名が書かれている箇所を探すだけでなく、その値がどこから入り、どの処理で使われ、最終的にどの挙動へ影響するのかを順にたどっていきました。

とくに理解につながったのが、Cloud CDNの署名付きCookieです。
既存の処理では、Helmで定義されたドメイン名とCDN URLが環境変数としてアプリへ渡され、起動時の検証を経てSigned Cookie Middlewareから参照される仕組みでした。Middlewareは署名付きCookieを生成し、Domain属性を持つSet-Cookieレスポンスヘッダーとしてブラウザへ返しています。

以下は、実際の処理から今回の説明に必要な入力と出力を抜粋したものです。
cはHonoのContextで、リクエストごとの値の取得やレスポンスヘッダーの設定に使われます。署名生成の詳細は説明には関係ないため省略しています。

 

ts

const env = c.get('env')

const domainName = env.DOMAIN_NAME

const cdnURL = env.CDN_URL

const urlPrefix = `${cdnURL}/${tenantId}/`


// urlPrefixに対する署名を生成する処理は省略

const cookie = `${input}:Signature=${sig}`


c.header(

  'Set-Cookie',

  `Cloud-CDN-Cookie=${cookie}; ...; Domain=${domainName}`

)

 

CDN_URLから署名対象となるパスを組み立て(4行目)、DOMAIN_NAMEはCookieのDomain属性に設定しています(11行目)。この流れを追ったことで、Helmで定義され、環境変数としてアプリへ渡された値が、最終的には「ブラウザがどのドメインへCookieを送信できるか」を決めるということを理解しました。

同じように確認を進めると、HostやCORSで検証するOriginはHTTPリクエストから、Cookieや外部向けURLの生成に使うドメイン名・CDN URL・Base URLは環境変数から入ることが分かりました。

インフラ側でも同じように、既存の構成図とTerraform・Helmの定義を照らし合わせながら、ブラウザで指定されたホスト名が各層で何の判断に使われ、どこへ渡されながらアプリに到達するのかをたどりました。

 

確認対象 入力元・起点 追った流れ 最終的な結果
ユーザーのリクエスト ブラウザで指定したホスト名 DNS → Load Balancer → Gateway → Service リクエストがアプリへ届く
Signed Cookie Helmで定義したドメイン名・CDN URL 環境変数 → Middleware → Set-Cookie ブラウザがCookieを送信できる
CORS HTTPリクエストのOrigin CORS Middleware → レスポンスヘッダー ブラウザがレスポンスを利用できる
外部向けURL 環境変数のBase URL Handler → APIレスポンス 外部から利用するURLが生成される

 

このように、ブラウザから入るホスト名やOriginと、環境変数としてアプリに渡される設定値は、それぞれ異なる経路を通って、アプリやブラウザの挙動につながっていました。

 

この視点は、実装中に問題にぶつかったときにも役立ちました。
新ドメインでログインした後、画面が読み込み中のまま進まなくなることがありました。ログイン自体は成功しているように見えたため、最初は認証設定の不足を疑いましたが、ブラウザのNetworkログを確認すると、認証後のAPIリクエストがErrors,OriginNotAllowedで拒否されていました。ログインが失敗しているのではなく、ログイン後にブラウザから送るリクエストのOriginが、認証基盤であるZitadel側で許可されていない状態でした。

 

今回はホスト名の形式を統一していたため、認証後の戻り先を指定するRedirect URIは、ワイルドカードを使ってまとめて登録していました。それでログインまでは問題なく進んでいたので、Originについても同じように許可されているつもりでいたのですが、実際には拒否されました。設定画面を見ても理由が分からなかったため、Zitadelのソースコードで、許可するOriginがどのように決まるのかを確認しました。

コードを追うと、Zitadelは登録されたRedirect URIからOriginを抽出し、許可するOriginの一覧へ追加していました。Redirect URIとOriginは別々に管理されているのではなく、Redirect URIの登録内容がそのままOriginの許可にも使われていたというわけです。
そのため、Redirect URIをワイルドカードで登録していたことで、*を含んだ文字列がそのままOriginとして登録されており、ブラウザが実際に送るOriginと一致しなくなっていました。

エラーを解消するだけであれば、Additional Originsへ具体的なOriginを追加する方法もありましたが、その場合、ホスト名が増減するたびにRedirect URIとAdditional Originsの両方を更新することになり、同じ情報を二重に管理することになってしまいます。そのため、Redirect URI側をワイルドカードではなく具体的なURLで登録する方針に変えました。
こうすればZitadelがそこからOriginを抽出してくれるため、追加の登録なしにOriginも許可され、登録先を一か所にまとめられます。実際にこの変更を行うと、認証後のAPIリクエストが通り、画面まで到達できました。

普段の学習から意識していた「値の流れを追う」という考え方が、今回の調査と実装を通して、変更範囲を判断するための実践的な判断軸として自分の中に定着し始めたことは、大きな収穫でした。

 

学び②:完成形だけでなく移行途中の状態を考える

2つ目の学びは、移行を完成形だけでとらえず、新旧の設定が共存する途中の状態まで見越して計画を立てることの難しさと重要性です。

Dev・Stage環境はほかの機能開発や検証にも使われているため、既存の設定を一度に新ドメインへ置き換えると、DNS、TLS証明書、Gateway、アプリの変更がすべて反映されるまで、環境へ正常にアクセスできなくなる可能性があります。また、自分にとって初めての移行作業であり、事前に全ての影響を見通すことは難しかったため、想定外の問題が発生しても速やかに元の状態へ切り戻せるようにしておく必要がありました。

そこで、各環境の移行作業を2段階に分けました。
第一段階では、旧設定を残したまま、新しいDNSレコード、証明書、HTTPRoute、アプリ側の許可設定を追加し、新旧どちらのドメインからも利用できる状態で動作を確認し、
第二段階では、標準で利用するURLを新ドメインへ入れ替えると同時に、その環境の旧ドメイン用設定を削除します。この流れをまずDev環境で実施し、確認後にStage環境へ展開する方針としました。Prod環境のドメインは今回変更しないため、既存設定を維持します。

このように段階を分ける方針は立てられましたが、実装に着手してから計画を変更した箇所もありました。

当初は、CDNに関係する値もほかの設定と同じように新旧二つを並べて持たせて、動作確認できると考えていました。しかし、実際には単一の値しか持てない設定や、環境変数から一つの値を静的に読み取る処理がありました。
新旧ドメインを共存させてアクセス先のドメインに応じてCDNの値を切り替えるには、リクエストごとに使用する値を動的に選ぶロジックが必要になります。その変更は複数の処理に影響する一方、完全移行の直後には不要となり、再び単一ドメイン向けの処理へ戻すことになります。一時的な確認のための変更としては実装・検証範囲が大きいと判断し、CDNは共存期間中の確認を見送り、完全移行後に確認する計画へ変更しました。

この見落としの原因は、すべての設定が新旧の値を同時に持てると思い込んでいたことです。移行計画では各段階の状態を並べるだけでなく、設定値が単一か複数か、どこから取得され、実行時にどう選ばれるのかまで確認する必要があったわけです。

新旧を共存させてから切り替えるという方針を立てるだけでなく、その状態を既存のコードや設定で本当に実現できるかまで確かめて、初めて無理のない計画になると実感しました。
今後は、各段階で必要になる一時的な対応と、移行後にそれを削除する手間まで含めて見積もり、計画に反映していきたいです。

 

もうひとつ、計画どおりに進まなかったのがStage環境への展開です。

Dev環境の移行を終えた時点で、変更が必要な箇所と確認手順は一通り整理できていたため、Stageでは同じ手順をそのまま使えると考えていました。しかし、変更をリリースした後に新ドメインへアクセスしても、画面が表示されませんでした。

開発者ツールで確認すると、リクエストは送信されているものの、レスポンスヘッダーを受け取れずProvisional headers are shownの状態で止まっていました。アプリからのHTTPレスポンスに到達する前、つまりDNSやTLSの段階で失敗している可能性が高い状態です。

Terraformの実行結果を確認すると、Certificate Managerへ渡すリソース名が上限の63文字を1文字超えており、証明書の作成自体に失敗していました。TLS証明書がないためHTTPS接続が成立せず、ブラウザ側では応答が返ってこないように見えていたわけです。

 

リソース名を組み立てるロジックはDev・Stage・Prodで共通でしたが、環境名とホスト名の組み合わせによって、Stageだけが上限を超えていました。Dev・Stageでは短い命名規則、Prodでは既存の命名規則を使うように分けることで解消しています。

Devで手順を確立してからStageへ展開するという方針自体は効果的でしたが、手順を再利用できることと、同じ結果になることは別でした。環境ごとに異なる値が外部サービスの制約に触れないかは、展開のたびに改めて確認する必要があったと感じています。

ここまでの二つの経験を通して、開始前には別々に捉えていたインフラの知識、アプリの処理、移行計画が、実際のサービスを変更するための一連の考え方としてつながり始めました。

TerraformやTypeScriptも十分に理解できたわけではありませんが、未知のコードや設定に向き合ったときに、何が分からないのかを整理し、どこを追えば判断に必要な情報を得られるかを考えられるようになったことは、今回のタスクを通して得た大きな変化でした!

 

業務以外の活動

ここからは、タスク以外のイベントやサポート体制についても少し触れたいと思います!

CA Tech JOBや実務インターンに興味はあるものの、実際の働き方や雰囲気がまだよく分からない方に向けて、少しでも参考になれば幸いです!

私が参加した際は、配属先のエンジニア社員の方がメンターとしてつき、週に一度の1on1で、業務の進め方や困っていることについて相談に乗ってくださいました。担当人事の方とも週次で1on1を行い、インターン開始時に立てた目標の振り返りや、企業理解を深めるための相談ができました。

特に印象的だったのは、参加後の過ごし方を本人に任せきりにするのではなく、やりたいことや伸ばしたいことを目標と実際の業務へつなげ、その進み方まで一緒に考えてもらえたことです。私は、実務経験を積むことと、会社の文化や働き方を知ることの二つを目的としていましたが、この1か月を通して、そのどちらも十分に経験できたと感じています!

実務では、本記事で取り上げたドメイン移行に加え、Claude Sonnet 5をアプリケーションで利用できるようにするモデル追加や、既存のDatadog API KeyをTerraform管理へ移行するタスクにも取り組みました。未経験の分野をどこまで任せてもらえるのかという不安もありましたが、希望したことへ積極的に挑戦させてもらい、レビューや相談を通して支えていただけたことがうれしかったです。

企業理解についても、配属先のエンジニアだけでなく、ランチ会や説明会を通して、他チーム・他事業部の方と話す機会を設けていただきました。業務内容や仕事のやりがい、チームの雰囲気などについて率直な話を聞くことができ、外から見ているだけでは分からない会社の姿に触れることができました。

また、業務中はつい目の前のタスクに意識が向きすぎてしまい、全体を見たり、活動を振り返ることがおろそかになりがちですが、JOB中のさまざまなイベントを通して、定期的に振り返る機会があったことも自分の成長につながったように感じます。経験を言葉にすることで、タスクをどこまで理解できているかを確かめ、業務中には意識できていなかった自分の変化にも気づけました。

こうした支えもあり、インターン中は本当に毎日楽しく過ごすことができました。期間中は毎日出社していましたが、満員電車に乗っていても出社を嫌だと感じなかった自分に驚くほどでした笑。
1か月を終えた今は、正直物足りないと感じていて、もう少し長く、3か月くらい参加したかったなぁという気持ちです。

 

さいごに

この1ヶ月は、私にとって初めて現場のエンジニアと一緒に働く就業型インターンでした。
開発チームの一員として働くなかで、これまで漠然としかイメージできていなかったソフトウェアエンジニアの働き方を、技術的な意思決定や開発フロー、チームの雰囲気・文化なども含めて理解できたことは、本当に大きな収穫でした。

また、取り組んだタスクを通して、未知の仕組みでも値の流れやリソース同士の関係を追い、問題が起きている箇所を切り分け、実装しない選択も含めて根拠を持って判断する力が身につきました。
一方で、着手前に既存の手順や管理元、技術・運用上の制約を洗い出し、影響範囲と検証方法を具体的な計画へ落とし込む力などは、まだまだ伸ばす余地があると感じました。

最後になりますが、1ヶ月間支えてくださったトレーナーの方、配属先の皆さま、担当人事の方、そのほかランチ会や説明会でお話ししてくださった皆さま、本当にありがとうございました!!!