1. はじめに
こんにちは。Sreake事業部のインターン生の大島です。2026年の5月から6月にかけて、技術検証のインターンに参加しました。普段はWebアプリ開発を中心に取り組んでおり、インフラ系のインターンは今回が初めてでした。インターン参加前には2日間のワークショップに参加し、そこでコンテナ技術やSREについて一通りハンズオンで学んだ上でインターンに参加しました。
インターン期間の目標は、コンテナ技術やクラウド技術を学び、運用や保守に関する知識を身につけることでした。ワークショップで学んだことを実践する形で、自作のWebSocketアプリをEKS上にデプロイしました。その実装を進める中で直面する「ステートフルなアプリケーションとKubernetesの相性」という問題を技術検証のテーマとし、試行錯誤しながら得た知見をまとめました。
2. 使用したアプリケーションについて
今回のインターンで使用したアプリは、WebSocketを使ったテトリスゲームアプリです。EKS上に移行する前は以下のような構成でした。
- 認証とDBはSupabase(GraphQL API)に依存
- フロントエンドはVercel、バックエンドはRenderにそれぞれデプロイ
今回のインターンではEKS上でGitOpsを実践することが目的だったため、外部サービスへの依存を減らす形に構成を修正しました。具体的には以下の変更を行っています。
- GitHub OAuthを使って認証し、バックエンドで独自にJWTを発行する形に変更
- シークレット管理にAWS Secret Managerを導入
3. 実装の流れ
アーキテクチャ図

実装の手順
- Terraformを用いて、EKSクラスタ等のAWSリソースを構築する。
- Helmを用いてアプリケーションをパッケージ化する。
- GitHub ActionsやArgocdを用いて、デプロイを自動化する。
4. EKSクラスタ構築
EKS上にアプリケーションをデプロイする際、以下の手順を踏みました。
- Auroraや認証関連のAWSリソースをTerraformで作成する。
- ローカル環境でKubernetesクラスタを立て、作成したAWSリソースとアプリケーションが正しく疎通できるかを確認する。
- EKSクラスタを構築し、ローカルで動作確認済みのマニフェストをデプロイし、AWS上でも動くことを確認する。
ローカルで環境変数の設定やポートの設定など、基本的な動作を検証しておくことで、EKSに移行した際の問題の切り分けがしやすくなりました。
学び
IaCのメリットと自動化の重要性
はじめは、terraform planとapplyを手元で行っていました。しかし、applyする前に意図しない変更点を見落としたり、後からいつどんな変更を加えたのかも追いづらいという問題がありました。そこでGitHub Actionsでワークフローを組み、PR上でterraform planの結果、どのリソースが削除、更新、作成されるのかをコメントで確認できるようにしました。これにより変更内容を確認しやすくなり、安全にデプロイできる仕組みを整えました。IaCのメリットは、変更を追跡できる点、さらにデプロイを自動化できることで、安全性と再現性を両立できる点であることを実感しました。
つまずいた点
Kubernetesの通信レイヤーの理解
ローカル開発ではlocalhostのポートに直接アクセスするだけで済んでいたため、Kubernetes環境での通信の流れには特に戸惑いました。PodにはCluster内部のIPが割り振られており、外部から直接アクセスできません。Serviceをtype: LoadBalancerに設定することで、AWSがELBを自動作成し、ELB → NodePort → ClusterIP → Podという順でトラフィックが転送されるという複数のレイヤーを経由する必要があります。ローカルとリモートでは通信の抽象度が大きく異なることを、実際にポートの設定を試行錯誤しながら理解しました。 その後ALBのSticky Sessionを導入するため、AWS Load Balancer ControllerとIngressを導入し、ALBを経由する構成に移行しました。(第6章)
ESO(External Secrets Operator)による環境変数の管理
ESOは、外部シークレット管理サービスの値をKubernetes Secretに自動的に注入するOSSのツールです。はじめは環境変数の管理に苦戦しましたが、これを導入することでAWS Secret ManagerのシークレットがKubernetes Secretに自動的に同期され、アプリ側は環境変数を読むだけのシンプルな実装を実現できました。シークレットの更新作業も大幅に楽になりました。
5. ArgoCDでGitOpsを実現する
Helmによるパッケージ化
ArgoCDでデプロイを自動化するにあたり、まずHelmを使ってアプリケーションをChartとしてパッケージ化しました。Helmはアプリケーションのパッケージ化だけでなく、ArgoCD本体やESOといった外部ツールのインストールにも使用しており、Kubernetesのエコシステムの中で中心的な役割を担っていることを実感しました。
学び
手動デプロイからGitOpsへ
TerraformによるAWSリソースの変更管理は前述の通り自動化しましたが、アプリケーションのデプロイについても同様の課題がありました。ArgoCDを導入することで、コードをGitにpushするだけで、EKSへ自動的に反映されるようになりました。最も実感したメリットは、ArgoCDのUI上でデプロイの状態をリアルタイムに可視化できる点です。どのリソースが同期済みで、どのリソースが差分を持っているかが一目でわかるようになり、デプロイの状況を直感的に把握できるようになりました。
デプロイ構成の設計
フロントエンドとバックエンドを別々にデプロイする構成について考えるなど、変更の影響範囲について考えるきっかけになりました。
つまずいた点
ArgoCD Image Updaterの設定
コンテナイメージの自動更新にArgoCD Image Updaterを使いましたが、バージョンの違いによる、ハッシュ値の扱いやannotation方式とCRD方式の違い、update strategyの設定に苦戦しました。
無停止デプロイに向けた設定
無停止でデプロイを行うため、デプロイ中もPodの数を維持できるようにKubernetesのDeplymentにRolling Updateを導入しました。しかし、WebSocketは一度接続を確立すると切断されるまで同じ接続を維持し続ける特性があるため、古いPodが終了する際に接続中のプレイヤーが切断されてしまうという問題が残っています。今後の課題としたいです。
6. ステートフルアプリとKubernetesの相性問題
異なるPodにルーティングされてルームにアクセスできなかった
テトリスアプリにはルーム機能があり、複数のプレイヤーが同じルームに接続してゲームをプレイします。EKS上にデプロイした後、ルームに入室できないという問題が発生しました。
原因は、KubernetesのServiceがリクエストのたびに異なるPodにルーティングすることで、プレイヤーが最初から別々のPodに振り分けられてしまっていたことでした。合言葉を入力して入室する仕様だったため、同じ合言葉を入力しても別々のPodに振られると誰もいない部屋と判定され、新しく部屋を作るような仕組みになっていました。各Podは自分のPodにいるプレイヤーの情報しか持っていないため、相手が入ってくることを期待して、ルーム情報を更新するためのエンドポイントへのポーリングを繰り返していました。これにより、別々のPodを巡り続け、同じタイミングで同じPodに振られない限り、相手を見つけられない状態になっていました。
デバッグの過程
原因の特定には以下の手順を踏みました。
CloudWatchによるログの確認
EKSクラスターに Amazon CloudWatch Observability Addon を追加してCloudWatchへのログ転送を有効化し、リクエストがどのPodに届いているかを確認しました。
Podを1つに絞り動作確認
Podが1つであれば必ず同じPodにルーティングされるため、アプリケーション自体の問題なのかルーティングの問題なのかを切り分けることができました。この方法でルーティングが原因であることを特定しました。
対応方法
ALB Sticky Sessionを使って接続を固定
ALBのSticky Sessionを有効にすることで、同一クライアントからのリクエストを常に同じPodにルーティングするようにしました。ただしcookieが途切れると別のPodにルーティングされてしまうため、Middlewareでcookieが維持されるように管理しました。
アプリ設計の見直し
合言葉を入力して入室する形式では、同じ合言葉を複数のPodが別々に管理してしまう問題がありました。そこでどちらかのプレイヤーが部屋を作成し、もう一方が入室するという設計に変更することで、ルームの管理をシンプルにしました。
ElastiCache for Redisを使って状態の共有
Sticky Sessionだけでは、それぞれ違うPodに入ったプレイヤー同士が情報を共有できません。そこでElastiCache for Redisを導入し、ルームの状態をPodの外部で管理するようにしました。これによりどのPodにリクエストが来ても同じルーム情報を参照できるようになり、各Pod自体はセッションの状態を持たないステートレスな構成にしました。ただしRedisへのアクセスが増えた影響でレイテンシが増加し、ゲームとして快適にプレイできるレベルには達しませんでした。
ALBにACM(AWS Certificate Manager)証明書を付与
フロントエンドとバックエンドが異なるドメインにあるため、クロスサイト通信でcookieを送るにはHTTPSが必要でした。そのため、SSL/TLS証明書の発行や管理をおこなうACMというAWSのサービスを利用しました。証明書を作成しバックエンドALBに付与することにより、HTTPS通信に変更しました。
ステートフルなアプリケーションとKubernetesの相性
今回はRedisに、ユーザ間で共有が必要な情報を外出しすることで、どのPodにリクエストが来ても状態を共有できるようにしました。これはいわばステートフルなアプリをKubernetesとの相性がいいステートレスな構成に寄せることで問題を回避したアプローチです。しかしこの方法ではRedisへの外部アクセスが増える分レイテンシが増加するというトレードオフがありました。
ステートフルなアプリとKubernetesの相性は良くありません。しかし工夫次第で動かすことができそうです。私は運用する上で以下の観点が重要だと考えました。
- 必要なセッション共有をどう実現するか
- 外部アクセスのコストをいかに減らすか
- Podの負荷が増えた時にどうするか
ステートフルなアプリの本質はリアルタイム性にあります。複数のユーザが同じ状態をリアルタイムで共有するためには、サーバが状態を持ち続ける必要があります。これをKubernetes上で実現するには、Podのメモリ上で状態を共有するために、異なるユーザ同士を同じPodに集めることが必要です。現在のSticky Sessionを使用した実装では、同じユーザが常に同じPodにアクセスし続けることは保証しますが、異なるユーザ同士を同じPodに誘導することは保証しません。
今回のケースでのより良いアーキテクチャへの考察として、ルームマッチングサーバとゲームサーバを分離するアーキテクチャが有効ではないかと考えています。現在の実装はルームマッチングとゲームの進行を同じサーバで管理しています。これらを分離し、ルーム情報とPod情報をDBで紐づけて管理します。こうすることにより、プレイヤー同士を確実に同じPodに誘導することができ、試合中の外部アクセスも試合開始前後の最小限に抑えられると考えます。
7. まとめ
やり残した課題
今回の検証を終えて、以下の課題が残りました。
- Redisの導入によって、外部へのアクセスが増える分アプリが重くなってしまいました。本番運用に向けてはAPM(Application Performance Monitoring)を導入して監視しながら、パフォーマンスチューニングをすることが必要だと感じています。
- CloudWatchへのログ転送を部分的に導入しましたが、ログを読み解く力をつけ、より体系的なデバッグフローを確立したいです。
- 本番環境と開発環境を分離し、開発しやすい体制を整えたいです。
- 体系的なSREのプラクティスについて学びたいです。
インターンを通して学んだこと・気づき
このインターンを通じて、EKS・Terraform・Helm・ArgoCDといったSREの主要な技術スタックを、実際に手を動かしながら一通り学ぶことができました。この過程でトイルを無くし、自動化することの効果とメリットを実感することができたことは大きな収穫でした。
また、AIを壁打ち相手として活用しながら学習を進める中で、AIとの向き合い方についても気づきがありました。リソースの概念への理解が浅い段階では、自分で手を動かしながらAIに質問して理解を深めるアプローチは非常に有効でした。一方で、ある程度理解が進んだら、非推奨機能やバージョンの違い、各ツールにデフォルトでどのような機能があるか、ドキュメントを読み、あらかじめ把握しておくと、デバッグの際に見当がつきやすいと感じました。
次に、ステートフルなアプリケーションとKubernetesの相性についても、自作アプリのルーティングの問題に対する試行錯誤を通じ、Kubernetesの特性について理解しながら考察することができました。今回はステートレスな構成に寄せることを試しましたが、これは本質的な解決法ではありませんでした。このインターンを通じて得た知見を元に、ステートフルな部分を分離するアーキテクチャをはじめ、具体的にどのようなリソースを使ってどう設計すべきかについて考察を続けていきたいです。
二ヶ月間ありがとうございました。