見出し画像

新規プロダクトONE CAREER for EngineerのインフラにGCP & Kubernetesを使った話(技術選定編)

筆者について

こんにちは!ワンキャリアでOCE(ONE CAREER for Engineer)開発チームのテックリードを務めている高根沢です!

【高根沢光輔 プロフィール】

幼少期の頃からプログラミング言語に触れ、中学・高校で個人開発やチーム開発を通じてエンジニアリング活動を盛んに行う。
大学ではアプリ開発サークルの立ち上げや自然言語処理系の研究活動で4年間を過ごし、現在ワンキャリアを含め3社でテックリード、フルスタックエンジニアとして活動中。

2023年9月末に、新しいプロダクト「ONE CAREER for Engineer」が正式にリリースされました!これまでの振り返りも兼ねて、今後複数回に渡ってOCEチームでの取り組みを共有させていただきます。第1弾として、新規プロダクトを開発する際に行ったインフラの技術選定のポイントについてお伝えできればと思います。
※本ブログは下書きを執筆者の方で作成し、その後ChatGPTによる自動校正を行っています。



本記事の概略

新しいサービス「ONE CAREER for Engineer」では、ワンキャリアが初めてGKE + Kubernetesを導入しました。この技術選定の背後にある考え方について詳しくお話しします。

特に、Kubernetesといってもクラウドプロバイダーが違えば(例えばAWSのEKSとGCPのGKE)ではインフラ構築の段階で大きな違いがあります。
また、ECSとKubernetesを比較した場合、Kubernetesには便利なサーキットブレーカーがRollout Deploymentに初めから組み込まれているなどの利点があります。これらのポイントについて触れていきたいと思います。


序論

ONE CAREER for Engineer とは

新プロダクト「ONE CAREER for Engineer」(以下、OCE)は、新卒エンジニア向けのインターンシップ、選考プロセス、コーディングテストの体験談を公開し、エンジニアを志望する学生の就職活動に役立つサービスとして提供しています。
既存の就活サイトONE CAREERとは異なり、エンジニアに特化したサービスであるため、エンジニア志望の就活生にとってより使いやすいサービスとなっています。


OCEのインフラ技術選定で特にこだわったポイント

ワンキャリアでは、既存のアプリケーションの多くがECSに依存していました。その結果、ECSに関する知識は蓄積されましたが、他のプラットフォームに関する知識が不足しており、技術の選択肢が制限されていました。将来的な展望を考えると、ワンキャリアの技術スタックを広げて選択肢を増やす必要があると考えました。
そこでOCEでは、GCPのGKEというフルマネージドなKubernetesクラスターを採用することにしました。次の章ではGCPとKubernetesについて簡単に説明した後、なぜこれらを選択したのか、実際に使用して感じたメリットなどについてまとめています。

参考:既存プロダクトのインフラ構成(ECS)
現在、ONE CAREER、ONE CAREER PLUS、ONE CAREER CLOUDの主要サービスでは、すべてAWS ECSを利用しています。

各サービスごとにECSクラスターを構築し、Fargateを使用してアプリケーションを実行するという構成です。

図1:ワンキャリアの既存プロダクトにおけるECSアプリケーション構成図
図2:OCEで利用しているKubernetes内のインフラアーキテクチャ


選定1:オーケストレーションツールはKubernetesを採用しました。

Kubernetesの基本的な概念

Kubernetesは、コンテナ化されたアプリケーションのデプロイ、スケーリング、および管理を自動化するためのオープンソースのシステムです。アプリケーションを論理的な単位に分割し、コンテナをグルーピングすることで、管理や検出を容易にします。Kubernetesは、Googleの15年以上の経験を基に開発され、コミュニティのアイディアや慣習と組み合わせて最適化されています。
以下は、Kubernetesの主な特徴と基本的な概念に関するいくつかのポイントです。

  1. 惑星規模のスケーリング:Googleが週に何十億ものコンテナを実行するのと同じ原則に基づいて設計されており、大規模な運用もサポートしています。

  2. 柔軟性:どんなに要求が複雑であっても、Kubernetesは矛盾なく簡単にアプリケーションを提供できます。

  3. オープンソース:オンプレミス、パブリッククラウド、ハイブリッドクラウドなど、どこでも実行可能です。

  4. 自動化されたRolloutとRollback:アプリケーションや設定の変更を段階的に行い、問題が発生した場合にはRollbackが可能です。

  5. サービスディスカバリと負荷分散:KubernetesはPodにIPアドレスを割り当て、DNS名を提供し、負荷分散もサポートしています。

  6. ストレージオーケストレーション:さまざまなストレージオプション(ローカル、GCP、AWS、NFSなど)から選択して自動的にマウントします。

  7. Secretと構成管理:イメージの再ビルドや機密情報の露出なしに、Secretやアプリケーションの構成情報をデプロイや更新できます。

これらの特徴を持つKubernetesは、多くの企業や開発者にとって、コンテナオーケストレーションのデファクトスタンダードとなっています。
参考:Kubernetes 公式サイト


Kubernetesを選んだ一番の理由は、「サービス環境構築のしやすさ」

私は「開発を迅速に進めるためには、初期構築にかかる時間を短縮する必要がある」と考えました。技術スタックを自由に選択できる状況になったとき、OCEはより拡張性と柔軟性の高いKubernetesを選ぶことでこの目標を達成できると思いました。
もちろん、ECSは社内で広く利用されており、信頼性の高い選択肢です。しかし、既存の社内開発手法にはいくつかの課題がありました。ECSではこれらの課題を解決するのが難しかったのですが、Kubernetesでは以下のメリットが期待できました。

  • インフラリソースの分離:Kubernetesを利用すると、インフラストラクチャのリソースとアプリケーションのリソースを明確に分離することができます。ネットワークレベルの低レイヤーはTerraformで管理し、アプリケーションレベルの高レイヤーはKubernetesで管理することで、アプリケーションエンジニアがネットワークレイヤーの関心を持たなくてもアプリケーション開発を行うことができます。

  • より迅速なリリースの実現:KubernetesのDeployment機能を活用することで、アプリケーションの自動デプロイが可能になります。このデプロイでは、コンテナの公開ポート、環境変数、起動パス、そしてデプロイ戦略(ロールアウトなど)など、アプリケーションに近いレイヤーの設定を行うことができます。一方で、DeploymentリソースはVPCなどのネットワークレイヤーの変更は行えません。そのため、「アプリケーション開発に近い部分のみを管理するリソース」と捉えることができます。この特徴により、リリースのリスクを低く抑えた状態で素早くリリースすることができ、迅速なリリースサイクルを実現できます。

これらの理由から、私はKubernetesを採用し、ECSでは解決できなかった課題を解決することができました。


次に、今回の技術選定で解決したかった課題とその解決策について詳しく説明していきたいと思います。

課題1:Terraformで管理すべきリソース量の多さによる管理コストの高さ

課題の1つ目は、アプリケーションを最初にデプロイしようとした際に、Terraformで記述する必要がある箇所が多いことです。ECSの場合、"クラスター"、"サービス"、"タスク定義"のすべてをTerraformで記述する必要があります。

Terraformで管理するリソース一覧(ECSの場合)

  • network系

    • vpc

    • subnet

  • container repository系

    • ecr

  • orchestration系

    • ecs-cluster

    • ecs-service

    • ecs-task-definition

解決策:Terraformで管理すべきリソース量を最小化する

一方、Kubernetes環境においては、Terraformで記述すべきコードはネットワークからKubernetesクラスターまでの範囲になります。これにより、Terraformで管理すべきリソースの量が最小限に抑えられ、管理が容易になります。
また、タスク定義やServiceなど、Kubernetesクラスターよりも細かい粒度のインフラリソースは、CI/CDの段階で定義することができます。(詳細については、初回デプロイのしやすさをご参照ください。)

Terraformで管理するリソース一覧(GKEの場合)

  • network系

    • vpc

    • subnet

  • container repository系

    • artifact registory

  • orchestration系

    • gke-cluster

解決策がうまくいく理由1:インフラエンジニアとアプリケーションエンジニアの担当領域の分離

上記の内容を見ると、単に「Terraformで管理していたリソースがKubernetes Manifestに移動しただけで総量は変わっていないから開発効率に変わりないんじゃないか」という疑問が浮かびます。
ですが、インフラエンジニアがTerraformで管理する低レイヤーリソースとアプリケーションエンジニアがKubernetesで管理する高レイヤーリソースを分離することで、お互いの得意領域を発揮しながら共同で開発を進めることができるようになり、開発効率の向上に寄与します。

図3:Kubernetesを使った時のリソース分離レベル。アプリケーション層を高レイヤー、物理層ほど低レイヤーとして定義している

OCEの開発時には、「ネットワーク」や「クラスター」といった低レイヤーリソースをインフラエンジニアの領域と位置付けました。これにより、インフラエンジニアは「Service」や「Pod」など高レイヤーのリソースのことを深く知らなくても、Terraformで低レイヤーのインフラリソースを管理し、ネットワークの変更などのメンテナンスを行えるようにしました。
また、「ロードバランサ」や「Service」、そして「Pods」といった高レイヤーリソースをアプリケーションエンジニアの領域としています。これにより、アプリケーションエンジニアはネットワークやクラスタの事を気にすることなく、高レイヤーのインフラリソースをKubernetesで管理できるようになりました。
OCEのアプリケーションエンジニアはサービスのコンテナ定義まで担当することが多いのですが、Kubernetesを導入してリソースの管理レイヤーを分離することで、アプリケーションエンジニアはTerraformのことを知らなくても開発を進められるようになったわけです。

解決策がうまくいく理由2: トライアンドエラーの高速化

通常、Terraformで全てのインフラリソースを管理していると、Terraformに詳しくない人が予期せぬ破壊的な変更やリモートステートの更新による予期しない影響を及ぼす可能性があります(特に当時のワンキャリアでは手動でインフラリソースの変更を頻繁に行っていたため、予期しないリモートステートの更新が頻発しました)。
このため、レビューに時間がかかったり、慎重なリリースが必要になったりして、開発効率が低下することがあります。
一方、Kubernetes Manifestでは変更範囲がより限定されています。ロードバランサー以上の高レイヤーのインフラリソースにのみ関心を持っていれば良いので、低レイヤーのことを気にせずにどんどんサービスをデプロイしてトライアンドエラーを重ねながら開発を進められます。
たとえば、アプリケーションの起動設定に引数を追加したり、ポートを変更したり、アプリケーションのルーティングを追加したい場合、VPCなどのネットワークの詳細について考える必要はありません。代わりに、Kubernetesリソース内のServiceやPodsの設定が正しいかどうかを確認すれば済みます。
このように、TerraformとKubernetesで管理すべきリソースを分けることで、インフラリソースの変更に伴うリスクを軽減し、学習コストを低く抑えることができます。その結果、開発効率の向上に寄与しています。


課題2:コンテナ定義の管理のしにくさ

ECSで使用されるコンテナの設定は、タスク定義と呼ばれるもので管理されます。しかし、一度Terraformで作成されたタスク定義は、アプリケーションのCI/CDごとにTerraform外で動的に更新されてしまいます。
つまり、Terraformによる管理とCI/CDプロセスでの管理というタスク定義の二重管理が発生してしまうということです。このような二重管理は切り戻しの際などに問題になることがあります。

解決策:タスク定義(コンテナ定義)をGitリポジトリで一元管理する

KubernetesではDeploymentリソースがECSのタスク定義の機能を代替します。レプリカ数、デプロイ戦略、コンテナの定義をyamlファイルに記述し、kubectlを使ってクラスターに適用することで、常に最新の定義内容をデプロイ時に適用することができます。
さらに、Deploymentリソースが記述されているyamlファイルをGit管理することにより、二重管理の問題を解消することができます。

OCEで使っているDeploymentリソースの一部


選定2:IaaSにGCPを採用しました

Kubernetesクラスターを使用して外部サービスを公開するためのプラットフォームとして、AWSのEKSとGCPのGKEを比較しました。
結論として、使いやすさの観点からGKEを選択しました。

kubectl利用時のクラスター認証のしやすさ

Elastic Kubernetes Service(EKS)の場合

EKSの場合、クラスターが作成されると、クラスターを操作するための権限が何も設定されていない状態となります。そのため、クラスターへの認証を行うために、追加でRBACの設定を行う必要があります。
なお、RBAC(役割ベースのアクセス制御)は、ユーザーやシステムが行える操作を役割に基づいて制限するセキュリティモデルです。例えば、ECSでは、特定のタスクやサービスを管理する「管理者」や、情報の閲覧のみを許可される「読み取り専用ユーザー」など、役割に応じてアクセス権限を割り当てることができます。これにより、セキュリティを向上させ、不正なアクセスを防ぐことができます。
クラスター認証のために必要な権限付与手順は以下の通りですが、手順が複数あり、作業量もかかるため、EKS導入の際のデメリットとして私は捉えました。

  1. 準備: EKSクラスターの作成、IAMユーザーの作成、IAMユーザーにEKSクラスターへの参照権限を付与する。

  2. kubeconfigファイルの構成: kubeconfigファイルの作成と内容の確認。

  3. コンフィグマップ「aws-auth」の編集: IAMユーザーとKubernetes上のユーザーのマッピング設定。

  4. RBACの設定: ロールとロールバインディングの定義。

参考:EKSクラスターへ「kubectl」コマンドでアクセスする際の認証・認可の仕組みと設定 | DevelopersIO


Google Kubernetes Engine(GKE)の場合

GKEの場合、クラスター認証はIAMの権限情報をベースに行われますが、GKEではクラスター内でのRBACの設定は不要です。そのため、開発者のGoogleアカウントがKubernetesクラスターに対して適切な権限(たとえば編集者権限など)を持っていれば、クラスターが作成された直後から以下のコマンドを使用してクラスターの操作をはじめることができます。

# 認証情報を取得する
gcloud container clusters get-credentials cluster-name --region=asia-northeast1-a --project=project_id
# cluster-nameクラスター中に起動しているpodを表示する
# クラスターを作成したら、gcloudの認証情報を使ってすぐに操作可能
kubectl get pods -A

EKSの手順の多さと比較して、GKEは「クラスターを操作する際にIAM側で権限を持っていれば、クラスター側で自動的に認証される」という手順の少なさから、GKEを採用する理由の一つとなりました。

参考:kubectl をインストールし、クラスタ アクセスを構成する | Google Kubernetes Engine(GKE)


マネージドサービスへの接続のしやすさ

効率よくサービス開発をする際は、Kubernetes Engine(以下、GKE)から、ロードバランサ、SSL証明書、オブジェクトストレージ、コンテナレジストリなどのマネージドサービスを簡単に扱えることが重要です。GKEとEKSの間で、各種マネージドサービスをどれだけ簡単に使えるかを検証しました。
Elastic Kubernetes Service(EKS)の場合

EKSでは、ロードバランサに相当するIngressを作成する際に、カスタムリソースの導入が前提条件となります。そのため、クラスターを作成した直後ではIngressの作成ができない状態になります。

※カスタムリソースとは、Kubernetes APIの拡張機能であり、Kubernetesの標準リソースセットに存在しない独自のリソースタイプを定義することができます。これにより、特定のアプリケーションやサービスの設定や状態を管理するためのリソースをKubernetesのAPIを使用して作成することができます。

詳細は後述しますが、GKEでは不要なこの構築作業を踏まえないといけないという点は、
EKS導入のデメリットとして捉えました。

参考(ALB):EKS Ingress Resourse(ALB)を作成する - Qiita
参考(Custom Resource):Custom Resources | Kubernetes

OCEの開発中の時点においてはカスタムリソースの導入が必要だったが、その後この前提条件は不要となったようです。


Google Kubernetes Engine(GKE)の場合

GKEの場合、クラスターを作成した後でも、カスタムリソースを手動で導入する必要なく、Ingressの作成が可能です。
以下のように、Ingressリソースの定義をManifestで作成し、kubectl apply -f ./ingress.yamlと実行することで、初期作成後のクラスターでもすぐにIngressリソースを作成できます。
特に、SSL証明書の発行なども、networking.gke.io/managed-certificatesアノテーションを付与することで、カスタムリソースなしで簡単に行うことができます。
GKEとEKSを比較した結果、EKSではマネージドサービスを利用する際にカスタムリソースの追加が必要なケースが多く、一方でGKEではカスタムリソースなしで初期クラスターの状態からマネージドリソースを容易に追加できることがわかりました。そのため、クラスターを管理する際の手順や工数が少ないことから、GKEの採用に至りました。

Ingress参考:アプリケーション ロードバランサ向け GKE Ingress | Google Kubernetes Engine(GKE) | Google Cloud

SSL証明書参考:
Google マネージド SSL 証明書の使用 | Google Kubernetes Engine(GKE)


その他:Kubernetes標準搭載の便利な機能

ここまでで説明したこと以外にも、Kubernetesには便利な機能があります。たとえば、デプロイ失敗時のデフォルトの挙動が違います。ECSのデフォルトの挙動は、デプロイに失敗しても無制限にデプロイの試行が繰り返されてしまうという問題があります。コンテナが永遠に起動を繰り返すと、余分なリソースやコストの浪費のリスクが生じます。ECSでもサーキットブレーカーを設定することでデプロイの試行回数を制限することができますが、デフォルトでは有効になっていません。
一方、Kubernetesでは、デフォルトでデプロイ試行を10分間行い、その後にデプロイが失敗した場合は諦める仕様が標準で備わっています(もちろん、この時間はyamlファイルから変更可能です)。そのため、インフラエンジニアは標準機能を利用し、yamlファイルを変更するだけで、この設定に関するカスタマイズが柔軟に行えます。Kubernetesの強みは、このような柔軟性の高いカスタマイズが、最初から標準搭載されている点にあります。
Kubernetesには、このようなプラクティスに沿った良い設定がデフォルトで組み込まれていることも嬉しいポイントです。


まとめ

今回は、ワンキャリアの既存課題である「固定化された技術スタック」の解消と、「より効率的な開発のためのインフラ環境」を実現するため、GCPのKubernetesクラスター(GKE)を採用することになりました。


ちなみにワンキャリアでは、エンジニアを絶賛募集中です!
今回の記事でONE CAREER for Engineerにご興味もたれた方は、ぜひカジュアルにお話しをしましょう!

▼ワンキャリアのエンジニア組織のことを知りたい方はまずこちら

▼カジュアル面談を希望の方はこちら

▼エンジニア求人票


この記事が参加している募集

オープン社内報

この記事が気に入ったらサポートをしてみませんか?