TECH PLAY

電通総研

電通総研 の技術ブログ

822

こんにちは。X(クロス) イノベーション 本部 クラウド イノベーション センターの柴田です。 本記事ではTerraformでコードを変更していないリソースが known after apply となってしまう場合の回避策をご紹介します。 前提 問題となるコードの例 原因 回避策 おわりに 参考 前提 この記事は以下のTerraformのバージョンを前提とします。 新しいバージョンのTerraformでは本記事と異なる挙動をする可能性があります。 $ terraform version Terraform v1.5.3 on linux_amd64 + provider registry.terraform.io/hashicorp/aws v5.7.0 問題となるコードの例 以下のTerraformコードを例に考えてみましょう。 resource "aws_s3_bucket" "sample" { bucket_prefix = "sample-" tags = { Project = "My Project A" } } data "aws_iam_policy_document" "sample" { statement { effect = "Allow" actions = [ "s3:*" ] resources = [ aws_s3_bucket.sample.arn ] } } resource "aws_iam_policy" "sample" { name = "SampleBucketFullAccess" path = "/" policy = data.aws_iam_policy_document.sample.json } このTerraformコードを terraform apply します。 $ terraform apply Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create <= read (data resources) Terraform will perform the following actions: # data.aws_iam_policy_document.sample will be read during apply # (config refers to values not yet known) <= data "aws_iam_policy_document" "sample" { + id = (known after apply) + json = (known after apply) + statement { + actions = [ + "s3:*", ] + effect = "Allow" + resources = [ + (known after apply), ] } } # aws_iam_policy.sample will be created + resource "aws_iam_policy" "sample" { + arn = (known after apply) + id = (known after apply) + name = "SampleBucketFullAccess" + name_prefix = (known after apply) + path = "/" + policy = (known after apply) + policy_id = (known after apply) + tags_all = (known after apply) } # aws_s3_bucket.sample will be created + resource "aws_s3_bucket" "sample" { + acceleration_status = (known after apply) + acl = (known after apply) + arn = (known after apply) + bucket = (known after apply) + bucket_domain_name = (known after apply) + bucket_prefix = "sample-" + bucket_regional_domain_name = (known after apply) + force_destroy = false + hosted_zone_id = (known after apply) + id = (known after apply) + object_lock_enabled = (known after apply) + policy = (known after apply) + region = (known after apply) + request_payer = (known after apply) + tags = { + "Project" = "My Project A" } + tags_all = { + "Project" = "My Project A" } + website_domain = (known after apply) + website_endpoint = (known after apply) } Plan: 2 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes aws_s3_bucket.sample: Creating... aws_s3_bucket.sample: Creation complete after 1s [id=sample-20230713121614963200000001] data.aws_iam_policy_document.sample: Reading... data.aws_iam_policy_document.sample: Read complete after 0s [id=55239551] aws_iam_policy.sample: Creating... aws_iam_policy.sample: Creation complete after 1s [id=arn:aws:iam::************:policy/SampleBucketFullAccess] Apply complete! Resources: 2 added, 0 changed, 0 destroyed. 次にS3 バケット aws_s3_bucket.sample の Project タグの値を変更します。 resource "aws_s3_bucket" "sample" { bucket_prefix = "sample-" tags = { - Project = "My Project A" + Project = "My Project B" } } 変更後のTerraformコードに対して terraform plan を実行します。 $ terraform plan aws_s3_bucket.sample: Refreshing state... [id=sample-20230713121614963200000001] aws_iam_policy.sample: Refreshing state... [id=arn:aws:iam::************:policy/SampleBucketFullAccess] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place <= read (data resources) Terraform will perform the following actions: # data.aws_iam_policy_document.sample will be read during apply # (depends on a resource or a module with changes pending) <= data "aws_iam_policy_document" "sample" { + id = (known after apply) + json = (known after apply) + statement { + actions = [ + "s3:*", ] + effect = "Allow" + resources = [ + "arn:aws:s3:::sample-20230713121614963200000001", ] } } # aws_iam_policy.sample will be updated in-place ~ resource "aws_iam_policy" "sample" { id = "arn:aws:iam::************:policy/SampleBucketFullAccess" name = "SampleBucketFullAccess" ~ policy = jsonencode( { - Statement = [ - { - Action = "s3:*" - Effect = "Allow" - Resource = "arn:aws:s3:::sample-20230713121614963200000001" }, ] - Version = "2012-10-17" } ) -> (known after apply) tags = {} # (4 unchanged attributes hidden) } # aws_s3_bucket.sample will be updated in-place ~ resource "aws_s3_bucket" "sample" { id = "sample-20230713121614963200000001" ~ tags = { ~ "Project" = "My Project A" -> "My Project B" } ~ tags_all = { ~ "Project" = "My Project A" -> "My Project B" } # (10 unchanged attributes hidden) # (3 unchanged blocks hidden) } Plan: 0 to add, 2 to change, 0 to destroy. すると、変更がないはずのIAM policy aws_iam_policy.sample の policy が known after apply となってしまいました。 原因 これはData Sourceの仕様によるものです。 Data Resource Dependencies には以下のように記述されています。 Data resources have the same dependency resolution behavior as defined for managed resources . Setting the depends_on meta-argument within data blocks defers reading of the data source until after all changes to the dependencies have been applied. In order to ensure that data sources are accessing the most up to date information possible in a wide variety of use cases, arguments directly referencing managed resources are treated the same as if the resource was listed in depends_on . 要約すると以下のとおりです。 Data Sourceでは depends_on に記載されたリソースのすべての変更が適用されるまで読み取りは延期される。 Data Sourceの引数が他のリソースを直接参照している場合、参照先のリソースがData Sourceの depends_on に含まれている場合と同じように扱われる。 つまり Data Sourceが直接参照しているリソースに変更がある場合、それらの変更が適用されたあと、Data Sourceの読み取りが再実行される ということです。 改めて先ほどの例を考えてみましょう。Terraformが以下のように判断していたことがわかります。 aws_s3_bucket.sample のコードの変更が検出される。 data.aws_iam_policy_document.sample は aws_s3_bucket.sample を直接参照しているため、 depends_on に aws_s3_bucket.sample が含まれている場合と同じように扱われる。 depends_on に含まれる aws_s3_bucket.sample の変更が検出されたため、その変更が適用されたあとで data.aws_iam_policy_document.sample の再読み取りを行う必要があると判断される。 3をうけて aws_iam_policy.sample の policy が更新されると判断される。 回避策 Data Resource Dependencies には以下のように記述されています。 This behavior can be avoided when desired by indirectly referencing the managed resource values through a local value , unless the data resource itself has custom conditions . どうやら Local Value を間に挟むことで先ほどの事象を回避できるようです。 試してみましょう。先ほどのTerraformコードを以下のように変更します。 provider "aws" { region = "ap-northeast-1" } resource "aws_s3_bucket" "sample" { bucket_prefix = "sample-" tags = { Project = "My Project B" } } locals { s3_bucket_arn = aws_s3_bucket.sample.arn } data "aws_iam_policy_document" "sample" { statement { effect = "Allow" actions = [ "s3:*" ] resources = [ local.s3_bucket_arn ] } } resource "aws_iam_policy" "sample" { name = "SampleBucketFullAccess" path = "/" policy = data.aws_iam_policy_document.sample.json } 変更後のTerraformコードに対して terraform plan を実行します。 $ terraform plan aws_s3_bucket.sample: Refreshing state... [id=sample-20230713121614963200000001] data.aws_iam_policy_document.sample: Reading... data.aws_iam_policy_document.sample: Read complete after 0s [id=55239551] aws_iam_policy.sample: Refreshing state... [id=arn:aws:iam::************:policy/SampleBucketFullAccess] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # aws_s3_bucket.sample will be updated in-place ~ resource "aws_s3_bucket" "sample" { id = "sample-20230713121614963200000001" ~ tags = { ~ "Project" = "My Project A" -> "My Project B" } ~ tags_all = { ~ "Project" = "My Project A" -> "My Project B" } # (10 unchanged attributes hidden) # (3 unchanged blocks hidden) } Plan: 0 to add, 1 to change, 0 to destroy. S3 バケット aws_s3_bucket.sample 以外の変更が表示されないことを確認できました。 おわりに 本記事ではTerraformでコードを変更していないリソースが known after apply となってしまう場合の回避策をご紹介しました。 この記事がこの問題に悩んでいる方のお役に立てば幸いです。 最後までお読みいただき、ありがとうございました。 参考 Data Sources - Configuration Language | Terraform | HashiCorp Developer date source referencing managed resource proposes unnecessary changes under 0.14 · Issue #27171 · hashicorp/terraform 私たちは一緒に働いてくれる仲間を募集しています! クラウドアーキテクト 執筆: @shibata.takao 、レビュー: 寺山 輝 (@terayama.akira) ( Shodo で執筆されました )
アバター
こんにちは。X(クロス) イノベーション 本部 クラウド イノベーション センターの柴田です。 本記事ではTerraformでコードを変更していないリソースが known after apply となってしまう場合の回避策をご紹介します。 前提 問題となるコードの例 原因 回避策 おわりに 参考 前提 この記事は以下のTerraformのバージョンを前提とします。 新しいバージョンのTerraformでは本記事と異なる挙動をする可能性があります。 $ terraform version Terraform v1.5.3 on linux_amd64 + provider registry.terraform.io/hashicorp/aws v5.7.0 問題となるコードの例 以下のTerraformコードを例に考えてみましょう。 resource "aws_s3_bucket" "sample" { bucket_prefix = "sample-" tags = { Project = "My Project A" } } data "aws_iam_policy_document" "sample" { statement { effect = "Allow" actions = [ "s3:*" ] resources = [ aws_s3_bucket.sample.arn ] } } resource "aws_iam_policy" "sample" { name = "SampleBucketFullAccess" path = "/" policy = data.aws_iam_policy_document.sample.json } このTerraformコードを terraform apply します。 $ terraform apply Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create <= read (data resources) Terraform will perform the following actions: # data.aws_iam_policy_document.sample will be read during apply # (config refers to values not yet known) <= data "aws_iam_policy_document" "sample" { + id = (known after apply) + json = (known after apply) + statement { + actions = [ + "s3:*", ] + effect = "Allow" + resources = [ + (known after apply), ] } } # aws_iam_policy.sample will be created + resource "aws_iam_policy" "sample" { + arn = (known after apply) + id = (known after apply) + name = "SampleBucketFullAccess" + name_prefix = (known after apply) + path = "/" + policy = (known after apply) + policy_id = (known after apply) + tags_all = (known after apply) } # aws_s3_bucket.sample will be created + resource "aws_s3_bucket" "sample" { + acceleration_status = (known after apply) + acl = (known after apply) + arn = (known after apply) + bucket = (known after apply) + bucket_domain_name = (known after apply) + bucket_prefix = "sample-" + bucket_regional_domain_name = (known after apply) + force_destroy = false + hosted_zone_id = (known after apply) + id = (known after apply) + object_lock_enabled = (known after apply) + policy = (known after apply) + region = (known after apply) + request_payer = (known after apply) + tags = { + "Project" = "My Project A" } + tags_all = { + "Project" = "My Project A" } + website_domain = (known after apply) + website_endpoint = (known after apply) } Plan: 2 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes aws_s3_bucket.sample: Creating... aws_s3_bucket.sample: Creation complete after 1s [id=sample-20230713121614963200000001] data.aws_iam_policy_document.sample: Reading... data.aws_iam_policy_document.sample: Read complete after 0s [id=55239551] aws_iam_policy.sample: Creating... aws_iam_policy.sample: Creation complete after 1s [id=arn:aws:iam::************:policy/SampleBucketFullAccess] Apply complete! Resources: 2 added, 0 changed, 0 destroyed. 次にS3 バケット aws_s3_bucket.sample の Project タグの値を変更します。 resource "aws_s3_bucket" "sample" { bucket_prefix = "sample-" tags = { - Project = "My Project A" + Project = "My Project B" } } 変更後のTerraformコードに対して terraform plan を実行します。 $ terraform plan aws_s3_bucket.sample: Refreshing state... [id=sample-20230713121614963200000001] aws_iam_policy.sample: Refreshing state... [id=arn:aws:iam::************:policy/SampleBucketFullAccess] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place <= read (data resources) Terraform will perform the following actions: # data.aws_iam_policy_document.sample will be read during apply # (depends on a resource or a module with changes pending) <= data "aws_iam_policy_document" "sample" { + id = (known after apply) + json = (known after apply) + statement { + actions = [ + "s3:*", ] + effect = "Allow" + resources = [ + "arn:aws:s3:::sample-20230713121614963200000001", ] } } # aws_iam_policy.sample will be updated in-place ~ resource "aws_iam_policy" "sample" { id = "arn:aws:iam::************:policy/SampleBucketFullAccess" name = "SampleBucketFullAccess" ~ policy = jsonencode( { - Statement = [ - { - Action = "s3:*" - Effect = "Allow" - Resource = "arn:aws:s3:::sample-20230713121614963200000001" }, ] - Version = "2012-10-17" } ) -> (known after apply) tags = {} # (4 unchanged attributes hidden) } # aws_s3_bucket.sample will be updated in-place ~ resource "aws_s3_bucket" "sample" { id = "sample-20230713121614963200000001" ~ tags = { ~ "Project" = "My Project A" -> "My Project B" } ~ tags_all = { ~ "Project" = "My Project A" -> "My Project B" } # (10 unchanged attributes hidden) # (3 unchanged blocks hidden) } Plan: 0 to add, 2 to change, 0 to destroy. すると、変更がないはずのIAM policy aws_iam_policy.sample の policy が known after apply となってしまいました。 原因 これはData Sourceの仕様によるものです。 Data Resource Dependencies には以下のように記述されています。 Data resources have the same dependency resolution behavior as defined for managed resources . Setting the depends_on meta-argument within data blocks defers reading of the data source until after all changes to the dependencies have been applied. In order to ensure that data sources are accessing the most up to date information possible in a wide variety of use cases, arguments directly referencing managed resources are treated the same as if the resource was listed in depends_on . 要約すると以下のとおりです。 Data Sourceでは depends_on に記載されたリソースのすべての変更が適用されるまで読み取りは延期される。 Data Sourceの引数が他のリソースを直接参照している場合、参照先のリソースがData Sourceの depends_on に含まれている場合と同じように扱われる。 つまり Data Sourceが直接参照しているリソースに変更がある場合、それらの変更が適用されたあと、Data Sourceの読み取りが再実行される ということです。 改めて先ほどの例を考えてみましょう。Terraformが以下のように判断していたことがわかります。 aws_s3_bucket.sample のコードの変更が検出される。 data.aws_iam_policy_document.sample は aws_s3_bucket.sample を直接参照しているため、 depends_on に aws_s3_bucket.sample が含まれている場合と同じように扱われる。 depends_on に含まれる aws_s3_bucket.sample の変更が検出されたため、その変更が適用されたあとで data.aws_iam_policy_document.sample の再読み取りを行う必要があると判断される。 3をうけて aws_iam_policy.sample の policy が更新されると判断される。 回避策 Data Resource Dependencies には以下のように記述されています。 This behavior can be avoided when desired by indirectly referencing the managed resource values through a local value , unless the data resource itself has custom conditions . どうやら Local Value を間に挟むことで先ほどの事象を回避できるようです。 試してみましょう。先ほどのTerraformコードを以下のように変更します。 provider "aws" { region = "ap-northeast-1" } resource "aws_s3_bucket" "sample" { bucket_prefix = "sample-" tags = { Project = "My Project B" } } locals { s3_bucket_arn = aws_s3_bucket.sample.arn } data "aws_iam_policy_document" "sample" { statement { effect = "Allow" actions = [ "s3:*" ] resources = [ local.s3_bucket_arn ] } } resource "aws_iam_policy" "sample" { name = "SampleBucketFullAccess" path = "/" policy = data.aws_iam_policy_document.sample.json } 変更後のTerraformコードに対して terraform plan を実行します。 $ terraform plan aws_s3_bucket.sample: Refreshing state... [id=sample-20230713121614963200000001] data.aws_iam_policy_document.sample: Reading... data.aws_iam_policy_document.sample: Read complete after 0s [id=55239551] aws_iam_policy.sample: Refreshing state... [id=arn:aws:iam::************:policy/SampleBucketFullAccess] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # aws_s3_bucket.sample will be updated in-place ~ resource "aws_s3_bucket" "sample" { id = "sample-20230713121614963200000001" ~ tags = { ~ "Project" = "My Project A" -> "My Project B" } ~ tags_all = { ~ "Project" = "My Project A" -> "My Project B" } # (10 unchanged attributes hidden) # (3 unchanged blocks hidden) } Plan: 0 to add, 1 to change, 0 to destroy. S3 バケット aws_s3_bucket.sample 以外の変更が表示されないことを確認できました。 おわりに 本記事ではTerraformでコードを変更していないリソースが known after apply となってしまう場合の回避策をご紹介しました。 この記事がこの問題に悩んでいる方のお役に立てば幸いです。 最後までお読みいただき、ありがとうございました。 参考 Data Sources - Configuration Language | Terraform | HashiCorp Developer date source referencing managed resource proposes unnecessary changes under 0.14 · Issue #27171 · hashicorp/terraform 私たちは一緒に働いてくれる仲間を募集しています! クラウドアーキテクト 執筆: @shibata.takao 、レビュー: 寺山 輝 (@terayama.akira) ( Shodo で執筆されました )
アバター
こんにちは、X(クロス) イノベーション 本部 クラウド イノベーション センターの田村です。 2023 年 5 月の Microsoft Build にて統合分析プラットフォーム Microsoft Fabric が発表されました。 Microsoft Fabric は現在プレビュー中ですが、既存のサービスにはない機能追加や多くのアップデートが予定されており、 Microsoft のデータ領域ビジネスにおいて今後注目すべきサービスです。 本記事では、 Microsoft Fabric の概要とサービス検証として実施した勤怠データ分析の内容をご紹介します。 Microsoft Fabric の概要 One Lake Synapse Data Engineering Data Factory Power BI サービス検証:勤怠データの分析 概要 検証シナリオ:勤怠データ分析 検証アーキテクチャ Microsoft Fabric サブスクリプションの購入 Microsoft Fabric ワークスペースの作成 レイクハウスの作成 勤怠データの格納 ノートブックによるデータの整形 データフローによるデータの変換・加工 SQL エンドポイントによる分析 Power BI レポートの作成 サービス検証結果 一気通貫のデータ分析 データ分析の経験が浅いユーザーへのサポート 各機能の操作感 まとめ Microsoft Fabric の概要 Microsoft Fabric は、データ分析に必要な要素を 1 つの統合環境で提供する SaaS 形式のソリューションです。 従来 Microsoft の クラウド サービスでデータ分析をする場合は、Azure Data Factory や Azure Synapse Analytics といった PaaS を組み合わせた アーキテクチャ を設計する手法が一般的でした。 Microsoft Fabric では、データの蓄積/加工/変換/分析/可視化を単一の基盤上で実現できるため、ユーザーは統一された UI 上でシームレスな分析を実施できると謳われています。 これにより、ユーザーは 1 つのサービス内でデータ分析やデータ管理に関する作業をすべて完結でき、分析リソースのサイジングといった管理負担の軽減が期待できます。 Microsoft Fabric の主要な構成要素について述べます。 記事のボリュームを考慮し、今回は技術検証にて扱った要素について簡潔に説明しますので、他にも興味がある方は こちら のリンクをご参照ください。 (出典) Microsoft Fabric とは One Lake Microsoft Fabric の環境に 1 つ作成される SaaS 形式のデータレイクです。 Microsoft Fabric の各機能で利用されるデータを一元的に管理する基盤であり、ショートカットと呼ばれる機能を利用するとさまざまなサービスから手軽にデータを移動できます。 現時点では Microsoft Fabric 内部/ Amazon S3 /Azure Data Lake Storage との連携が実装されており、今後も拡大されていくと思われます。 OneLake のショートカット Synapse Data Engineering レイクハウスと呼ばれる構造化/非構造化データを管理・分析できるデータストアに格納されたデータに対する処理を行う機能です。 具体的には Apache Spark クラスタ による分析やノートブックによる対話的な分析( Python /R/ Scala )、データパイプラインによるデータの移動/変換/結合等が実施できます。 Microsoft Fabric のデータ エンジニアリングとは Data Factory 既存のデータ統合サービスである Azure Data Factory と、 Excel や Power BI 等に搭載されている Power Query を組み合わせたデータ統合機能です。 さまざまなデータソースからデータを取り込み、柔軟な処理が可能です。 Microsoft Fabric の Data Factory とは Power BI Microsoft Power Platform にて提供されている BI ツールです。 Microsoft Fabric に可視化 コンポーネント として統合されています。 Power BI とは サービス検証:勤怠データの分析 概要 7 月上旬あたりで Microsoft Fabric のキャッチアップと チュートリアル の実施がある程度進んだので、手持ちのデータを使用して Microsoft Fabric での分析に着手しました。 分析作業を通じて、下記の項目について検証することを目的とします。 Microsoft Fabric のみでデータ分析を 一気通貫 で実施できるか データ分析の経験があまりないユーザーをサポートする機能が現時点でどの程度実装されているか Microsoft Fabric の操作感(Power BI や Data Factory など統合されたサービスがこれまで通り利用できるか) 検証シナリオ:勤怠データ分析 ISID では日々の勤怠入力を自社ソリューションである POSITIVE で実施しています。 POSITIVE - 人事給与、勤怠システム ISID 社内で利用している POSITIVE では、入力した勤怠情報を Excel 形式でダウンロードできます。 そこで、自分の勤怠データが記録された Excel ファイルを Microsoft Fabric で分析し、可視化までの工程を 一気通貫 で検証してみることにしました。 検証 アーキテクチャ アーキテクチャ と検証手順は下記の通りです。 Microsoft Fabric サブスクリプション を購入する Microsoft Fabric ワークスペース を作成する Synapse Data Engineering にてデータストアとなるレイクハウスを作成する ローカルから Excel 形式の勤怠データをレイクハウスへ格納する ノートブック を作成し、 Excel 形式の勤怠データを整形して再度レイクハウスへ格納する 整形された勤怠データをデータフローにて変換・加工し、再度レイクハウスへ格納する レイクハウスから SQL エンドポイントを作成し分析を実施する 変換後のデータおよび分析結果を Power BI にて可視化する Microsoft Fabric サブスクリプション の購入 Microsoft 365 もしくは Azure 上から購入できます。 今回は Azure 上から購入しました。 手順は簡単かつドキュメントの内容をなぞるだけですので、本記事では割愛します。 Microsoft Fabric サブスクリプションを購入する Microsoft Fabric ワークスペース の作成 ワークスペース とは、 Microsoft Fabric で作成したさまざまなコレクション(レイクハウス、データフロー、BI レポート etc...)をまとめて管理するものです。 Microsoft Fabric で作業を開始する際には、内容を問わずはじめに実施する工程となります。 手順としては ワークスペース 名を入力して保存するだけですので、こちらについても詳細は割愛します。 厳密には ワークスペース に対する権限設定などもありますが、今回は検証用 ワークスペース のため実施していません。 ワークスペースの作成 レイクハウスの作成 レイクハウスは、大容量のデータを蓄積するデータレイクとデータの取り込みや分析に特化したデータウェアハウスを統合したものです。 一般的に構造化/半構造化/非構造化データを 1 箇所で管理・分析することが可能で、 Microsoft Fabric では多様なデータソースからのデータ格納とデータに対するさまざまな変換・統合処理や SQL や Spark エンジンを利用した分析をサポートしています。 また Microsoft Fabric におけるレイクハウスの特徴として、格納されたデータを自動で検出し、分析に適した形式でテーブルとして登録する機能も用意されています。 作成自体は非常に簡単で、 Microsoft Fabric ワークスペース の[+ 新規]より[レイクハウス]を選択し任意の名前を設定して完了です。 勤怠データの格納 検証に必要なデータを作成したレイクハウスへ格納します。 対象となるのは、私の 2023 年 6 月分の勤怠実績を記録した Excel データ(ファイル名:Attendance_202306.xlsx)です。 作成直後のレイクハウスには 構造化データが管理される Tables とデータレイクのようにさまざまなソースから連携された生データが管理される Files というセクションが用意されています。 データ格納をする際はデータフローやパイプラインを利用する方法や、One Lake のショートカット機能などさまざまな選択肢がありますが、今回はシンプルに手動でアップロードしました。 アップロードが完了すると データ形式 によってはプレビュー表示が可能ですが、2023 年 7 月時点で Excel データはサポート外のようです。 ノートブックによるデータの整形 生データの時点である程度構造化されていればよいのですが、検証で扱う勤怠データは分析用にフォーマットされたものではないため、 変換や統合処理の前にデータを整形する必要があります。 そこで、今回は Spark エンジンが接続されたノートブックによるデータの整形を実施しました。 ほかの選択肢としてデータフロー(Power Query)も検討しましたが、そちらはこの後の作業であるデータの変換・加工処理で利用しています。 ノートブックはレイクハウス UI の[ノートブックを開く]より作成できます。 ノートブックの作成と同時に Spark エンジンも用意されるため、ユーザーは言語を選択して処理を記述していくだけです。 私は Python を選択しましたが、その他に Scala / C# /R/Spark SQL がサポートされています。 データ整形の内容としては、 Excel 形式の勤怠データをより必要なデータを抽出し、 Python のデータフレームを利用してデータを再配置しています。 抽出したデータは下記の通りです。 日付 勤務区分(出勤/法定休日/有給など) 勤務形態(出社/テレワーク/顧客先など) 勤務開始時間 勤務終了時間 勤務時間 休憩時間 また、整形後のデータはレイクハウス内でプレビュー表示させたかったので、 CSV 形式のファイルとして Files セクションに格納しています。 整形後のデータは下記の通りです(ファイル名:Attendance. csv )。 データフローによるデータの変換・加工 整形したデータに対して、変換・加工処理を実施し分析の準備を行います。 前述したノートブックでまとめて実施してもよかったのですが、他の機能も触ってみたいという思いでデータフローによる作業としました。 データフローは Microsoft Fabric の UI より Data Factory を選択し、[Dataflow Gen2(プレビュー)]より作成できます。 作成後はレイクハウスに格納されている整形後の勤怠データ(Attendance. csv )を読み込み、必要な処理を実施します。 今回実施した主な処理内容は下記の通りです。 勤務開始/終了時間のデータ型を文字列型へ変更する 定時を表現する列を追加する 勤務時間と定時を計算し、残業時間を表現する列を追加する 勤務区分に休日出勤が含まれる場合は、勤務時間を残業時間として追加する 実際のデータフロー画面は下画像のようになります。 ノートブックと異なり、コード(Power Query)を記述できなくとも GUI 上の操作で基本的なデータの変換・加工が可能です。 もちろん、必要に応じて直接 Power Query を書くこともできます。 また、処理のサマリがダイアグラムビュー(画像内の青枠部分)に記録され処理結果は画像下部にプレビューされるので、処理に対する結果を即時把握できます。 データ変換が完了したら、画像右下の[データの同期先]よりレイクハウスへの同期設定を行うことで、分析用に最適化された形式で Tables セクションへ格納されます。 また、その際の スキーマ 定義やデータ構造の作成などはすべて自動で実施されます。 データフローからレイクハウスへの同期が完了すると、Tables セクションでテーブルの内容をプレビューできます。 今回は変換後のデータであることを明示するために、attendance_transform というテーブル名としました。 SQL エンドポイントによる分析 データフローから同期されたテーブルに対して分析を実施します。 SQL エンドポイントと呼ばれる機能を利用することで、レイクハウス内のテーブルに対する SQL クエリ発行やリレーションシップの構成が可能です。 レイクハウスの UI より、 SQL エンドポイントへ遷移します。 SQL エンドポイントでは 3 つのペインが用意されており、それぞれ下記の作業が可能です。 データ: スキーマ 定義に含まれるテー ブルデー タの確認や SQL クエリの管理 クエリ: T-SQL クエリの発行 モデル:テーブル間のリレーションシップ構成や Power BI におけるメジャーの設定が可能 今回はクエリとモデルのペインにて、それぞれ分析作業を実施しました。 主な分析内容は下記の通りです。 SQL クエリを利用した分析 勤務時間、残業時間、勤務区分、勤務形態の集計 勤務形態における勤務状況の傾向分析 1 か月間における勤務状況の傾向分析 モデル 勤務日における出社/テレワークの割合を算出 今回は 1 か月分の勤怠データのみであったためそこまで複雑な分析ではありませんが、関連データの追加やデータ期間を拡大することでさらに充実した分析が可能です。 Power BI レポートの作成 最終工程として、分析結果を Power BI で可視化します。 SQL エンドポイント UI の[新しいレポート]より、Power BI レポートの作成画面へ遷移します。 分析結果を反映した Power BI レポートは下画像の通りです。 レポート内の各ビジュアルで分析結果を可視化しています。 勤務時間と残業時間および勤務日に対するテレワークの割合をカード上にテキスト形式で表示 1 か月間における勤務時間の推移と定時に対する残業時間の推移を棒グラフで表示 勤務時間が長い日や期間を表形式で表示 勤務形態(出社 or テレワーク)の違いで平均勤務時間にどのような影響があるか散布図で表示 サービス検証結果 勤怠データの分析シナリオを通じた Microsoft Fabric の検証結果をまとめます。 検証項目は下記の通りです(再掲)。 Microsoft Fabric のみでデータ分析を 一気通貫 で実施できるか データ分析の経験があまりないユーザーをサポートする機能が現時点でどの程度実装されているか Microsoft Fabric の操作感(Power BI や Data Factory など統合されたサービスがこれまで通り利用できるか) その他利用時の注意点 一気通貫 のデータ分析 データの格納から可視化まですべて Microsoft Fabric で完結できました。 従来のように分析ごとに用途に応じて PaaS を組み合わせたり、インフラを確保する必要がなく、統一された UI の元で作業ができることはメリットであると言えます。 今回はローカルデータを中心とした分析シナリオでしたが、 機械学習 やリアルタイム分析など幅広いシナリオに対応できるサービスであるという印象です。 一方で、分析シナリオによっては不要な機能や コンポーネント があることも事実です。 Microsoft Fabric の利用が最適となる分析シナリオは何か、という点については、金銭面や学習コストの観点も含めて今後のアップデートを追っていく必要があります。 データ分析の経験が浅いユーザーへのサポート 今回の検証で触れた機能の中では、データフローの GUI によるデータ変換処理は比較的ユーザーに易しい設計という印象を受けました。 ですが、それ以外の部分については継続的な強化が必要と感じています。 Microsoft Fabric は「誰もがデータを管理および分析してアクションにつながる インサイト を得られる」というコンセプトを掲げていますが、現状の機能構成はデータ変換や加工・分析等の処理設計をユーザーの知識に依存しています。 Microsoft Fabric - データ分析 今回の検証においては、ノートブックを利用したデータの前処理や SQL エンドポイントによる分析で必要となるコードやクエリの作成は、未経験のユーザーには難易度が高い作業です。 誰もがデータを管理および分析するためにはこのような作業に対するサポート機能が必要で、具体的には下記のような内容を期待します。 Copilot 機能による各種コードやクエリの自動生成 One Lake のショートカット機能強化(連携先の拡張など) ドキュメントや チュートリアル の充実 Microsoft によると、Azure OpenAI と連携した Copilot 機能が近日公開予定となっておりますので、そちらを注視したいと思います。 各機能の操作感 主観になりますが、既存の PaaS や SaaS から統合された機能については違和感なく操作できました。 ノートブックによる対話形式でのデータ操作やデータフロー周辺の操作は、UI 自体が統合元の Microsoft 製品と大きく変わらないため、それらの経験がある方であれば抵抗なく利用できるかと思います。 まとめ 本記事では、 Microsoft Fabric の概要とサービス検証として実施した勤怠データ分析の内容についてご紹介しました。 Microsoft Fabric は発表されて間もないソリューションであり、ロードマップでは今後も多くのアップデートが予定されておりますので、引き続き注視したいと思います。 私たちは一緒に働いてくれる仲間を募集しています! クラウドアーキテクト 執筆: @tamura.kohei 、レビュー: @yamada.y ( Shodo で執筆されました )
アバター
こんにちは、X(クロス) イノベーション 本部 クラウド イノベーション センターの田村です。 2023 年 5 月の Microsoft Build にて統合分析プラットフォーム Microsoft Fabric が発表されました。 Microsoft Fabric は現在プレビュー中ですが、既存のサービスにはない機能追加や多くのアップデートが予定されており、 Microsoft のデータ領域ビジネスにおいて今後注目すべきサービスです。 本記事では、 Microsoft Fabric の概要とサービス検証として実施した勤怠データ分析の内容をご紹介します。 Microsoft Fabric の概要 One Lake Synapse Data Engineering Data Factory Power BI サービス検証:勤怠データの分析 概要 検証シナリオ:勤怠データ分析 検証アーキテクチャ Microsoft Fabric サブスクリプションの購入 Microsoft Fabric ワークスペースの作成 レイクハウスの作成 勤怠データの格納 ノートブックによるデータの整形 データフローによるデータの変換・加工 SQL エンドポイントによる分析 Power BI レポートの作成 サービス検証結果 一気通貫のデータ分析 データ分析の経験が浅いユーザーへのサポート 各機能の操作感 まとめ Microsoft Fabric の概要 Microsoft Fabric は、データ分析に必要な要素を 1 つの統合環境で提供する SaaS 形式のソリューションです。 従来 Microsoft の クラウド サービスでデータ分析をする場合は、Azure Data Factory や Azure Synapse Analytics といった PaaS を組み合わせた アーキテクチャ を設計する手法が一般的でした。 Microsoft Fabric では、データの蓄積/加工/変換/分析/可視化を単一の基盤上で実現できるため、ユーザーは統一された UI 上でシームレスな分析を実施できると謳われています。 これにより、ユーザーは 1 つのサービス内でデータ分析やデータ管理に関する作業をすべて完結でき、分析リソースのサイジングといった管理負担の軽減が期待できます。 Microsoft Fabric の主要な構成要素について述べます。 記事のボリュームを考慮し、今回は技術検証にて扱った要素について簡潔に説明しますので、他にも興味がある方は こちら のリンクをご参照ください。 (出典) Microsoft Fabric とは One Lake Microsoft Fabric の環境に 1 つ作成される SaaS 形式のデータレイクです。 Microsoft Fabric の各機能で利用されるデータを一元的に管理する基盤であり、ショートカットと呼ばれる機能を利用するとさまざまなサービスから手軽にデータを移動できます。 現時点では Microsoft Fabric 内部/ Amazon S3 /Azure Data Lake Storage との連携が実装されており、今後も拡大されていくと思われます。 OneLake のショートカット Synapse Data Engineering レイクハウスと呼ばれる構造化/非構造化データを管理・分析できるデータストアに格納されたデータに対する処理を行う機能です。 具体的には Apache Spark クラスタ による分析やノートブックによる対話的な分析( Python /R/ Scala )、データパイプラインによるデータの移動/変換/結合等が実施できます。 Microsoft Fabric のデータ エンジニアリングとは Data Factory 既存のデータ統合サービスである Azure Data Factory と、 Excel や Power BI 等に搭載されている Power Query を組み合わせたデータ統合機能です。 さまざまなデータソースからデータを取り込み、柔軟な処理が可能です。 Microsoft Fabric の Data Factory とは Power BI Microsoft Power Platform にて提供されている BI ツールです。 Microsoft Fabric に可視化 コンポーネント として統合されています。 Power BI とは サービス検証:勤怠データの分析 概要 7 月上旬あたりで Microsoft Fabric のキャッチアップと チュートリアル の実施がある程度進んだので、手持ちのデータを使用して Microsoft Fabric での分析に着手しました。 分析作業を通じて、下記の項目について検証することを目的とします。 Microsoft Fabric のみでデータ分析を 一気通貫 で実施できるか データ分析の経験があまりないユーザーをサポートする機能が現時点でどの程度実装されているか Microsoft Fabric の操作感(Power BI や Data Factory など統合されたサービスがこれまで通り利用できるか) 検証シナリオ:勤怠データ分析 ISID では日々の勤怠入力を自社ソリューションである POSITIVE で実施しています。 POSITIVE - 人事給与、勤怠システム ISID 社内で利用している POSITIVE では、入力した勤怠情報を Excel 形式でダウンロードできます。 そこで、自分の勤怠データが記録された Excel ファイルを Microsoft Fabric で分析し、可視化までの工程を 一気通貫 で検証してみることにしました。 検証 アーキテクチャ アーキテクチャ と検証手順は下記の通りです。 Microsoft Fabric サブスクリプション を購入する Microsoft Fabric ワークスペース を作成する Synapse Data Engineering にてデータストアとなるレイクハウスを作成する ローカルから Excel 形式の勤怠データをレイクハウスへ格納する ノートブック を作成し、 Excel 形式の勤怠データを整形して再度レイクハウスへ格納する 整形された勤怠データをデータフローにて変換・加工し、再度レイクハウスへ格納する レイクハウスから SQL エンドポイントを作成し分析を実施する 変換後のデータおよび分析結果を Power BI にて可視化する Microsoft Fabric サブスクリプション の購入 Microsoft 365 もしくは Azure 上から購入できます。 今回は Azure 上から購入しました。 手順は簡単かつドキュメントの内容をなぞるだけですので、本記事では割愛します。 Microsoft Fabric サブスクリプションを購入する Microsoft Fabric ワークスペース の作成 ワークスペース とは、 Microsoft Fabric で作成したさまざまなコレクション(レイクハウス、データフロー、BI レポート etc...)をまとめて管理するものです。 Microsoft Fabric で作業を開始する際には、内容を問わずはじめに実施する工程となります。 手順としては ワークスペース 名を入力して保存するだけですので、こちらについても詳細は割愛します。 厳密には ワークスペース に対する権限設定などもありますが、今回は検証用 ワークスペース のため実施していません。 ワークスペースの作成 レイクハウスの作成 レイクハウスは、大容量のデータを蓄積するデータレイクとデータの取り込みや分析に特化したデータウェアハウスを統合したものです。 一般的に構造化/半構造化/非構造化データを 1 箇所で管理・分析することが可能で、 Microsoft Fabric では多様なデータソースからのデータ格納とデータに対するさまざまな変換・統合処理や SQL や Spark エンジンを利用した分析をサポートしています。 また Microsoft Fabric におけるレイクハウスの特徴として、格納されたデータを自動で検出し、分析に適した形式でテーブルとして登録する機能も用意されています。 作成自体は非常に簡単で、 Microsoft Fabric ワークスペース の[+ 新規]より[レイクハウス]を選択し任意の名前を設定して完了です。 勤怠データの格納 検証に必要なデータを作成したレイクハウスへ格納します。 対象となるのは、私の 2023 年 6 月分の勤怠実績を記録した Excel データ(ファイル名:Attendance_202306.xlsx)です。 作成直後のレイクハウスには 構造化データが管理される Tables とデータレイクのようにさまざまなソースから連携された生データが管理される Files というセクションが用意されています。 データ格納をする際はデータフローやパイプラインを利用する方法や、One Lake のショートカット機能などさまざまな選択肢がありますが、今回はシンプルに手動でアップロードしました。 アップロードが完了すると データ形式 によってはプレビュー表示が可能ですが、2023 年 7 月時点で Excel データはサポート外のようです。 ノートブックによるデータの整形 生データの時点である程度構造化されていればよいのですが、検証で扱う勤怠データは分析用にフォーマットされたものではないため、 変換や統合処理の前にデータを整形する必要があります。 そこで、今回は Spark エンジンが接続されたノートブックによるデータの整形を実施しました。 ほかの選択肢としてデータフロー(Power Query)も検討しましたが、そちらはこの後の作業であるデータの変換・加工処理で利用しています。 ノートブックはレイクハウス UI の[ノートブックを開く]より作成できます。 ノートブックの作成と同時に Spark エンジンも用意されるため、ユーザーは言語を選択して処理を記述していくだけです。 私は Python を選択しましたが、その他に Scala / C# /R/Spark SQL がサポートされています。 データ整形の内容としては、 Excel 形式の勤怠データをより必要なデータを抽出し、 Python のデータフレームを利用してデータを再配置しています。 抽出したデータは下記の通りです。 日付 勤務区分(出勤/法定休日/有給など) 勤務形態(出社/テレワーク/顧客先など) 勤務開始時間 勤務終了時間 勤務時間 休憩時間 また、整形後のデータはレイクハウス内でプレビュー表示させたかったので、 CSV 形式のファイルとして Files セクションに格納しています。 整形後のデータは下記の通りです(ファイル名:Attendance. csv )。 データフローによるデータの変換・加工 整形したデータに対して、変換・加工処理を実施し分析の準備を行います。 前述したノートブックでまとめて実施してもよかったのですが、他の機能も触ってみたいという思いでデータフローによる作業としました。 データフローは Microsoft Fabric の UI より Data Factory を選択し、[Dataflow Gen2(プレビュー)]より作成できます。 作成後はレイクハウスに格納されている整形後の勤怠データ(Attendance. csv )を読み込み、必要な処理を実施します。 今回実施した主な処理内容は下記の通りです。 勤務開始/終了時間のデータ型を文字列型へ変更する 定時を表現する列を追加する 勤務時間と定時を計算し、残業時間を表現する列を追加する 勤務区分に休日出勤が含まれる場合は、勤務時間を残業時間として追加する 実際のデータフロー画面は下画像のようになります。 ノートブックと異なり、コード(Power Query)を記述できなくとも GUI 上の操作で基本的なデータの変換・加工が可能です。 もちろん、必要に応じて直接 Power Query を書くこともできます。 また、処理のサマリがダイアグラムビュー(画像内の青枠部分)に記録され処理結果は画像下部にプレビューされるので、処理に対する結果を即時把握できます。 データ変換が完了したら、画像右下の[データの同期先]よりレイクハウスへの同期設定を行うことで、分析用に最適化された形式で Tables セクションへ格納されます。 また、その際の スキーマ 定義やデータ構造の作成などはすべて自動で実施されます。 データフローからレイクハウスへの同期が完了すると、Tables セクションでテーブルの内容をプレビューできます。 今回は変換後のデータであることを明示するために、attendance_transform というテーブル名としました。 SQL エンドポイントによる分析 データフローから同期されたテーブルに対して分析を実施します。 SQL エンドポイントと呼ばれる機能を利用することで、レイクハウス内のテーブルに対する SQL クエリ発行やリレーションシップの構成が可能です。 レイクハウスの UI より、 SQL エンドポイントへ遷移します。 SQL エンドポイントでは 3 つのペインが用意されており、それぞれ下記の作業が可能です。 データ: スキーマ 定義に含まれるテー ブルデー タの確認や SQL クエリの管理 クエリ: T-SQL クエリの発行 モデル:テーブル間のリレーションシップ構成や Power BI におけるメジャーの設定が可能 今回はクエリとモデルのペインにて、それぞれ分析作業を実施しました。 主な分析内容は下記の通りです。 SQL クエリを利用した分析 勤務時間、残業時間、勤務区分、勤務形態の集計 勤務形態における勤務状況の傾向分析 1 か月間における勤務状況の傾向分析 モデル 勤務日における出社/テレワークの割合を算出 今回は 1 か月分の勤怠データのみであったためそこまで複雑な分析ではありませんが、関連データの追加やデータ期間を拡大することでさらに充実した分析が可能です。 Power BI レポートの作成 最終工程として、分析結果を Power BI で可視化します。 SQL エンドポイント UI の[新しいレポート]より、Power BI レポートの作成画面へ遷移します。 分析結果を反映した Power BI レポートは下画像の通りです。 レポート内の各ビジュアルで分析結果を可視化しています。 勤務時間と残業時間および勤務日に対するテレワークの割合をカード上にテキスト形式で表示 1 か月間における勤務時間の推移と定時に対する残業時間の推移を棒グラフで表示 勤務時間が長い日や期間を表形式で表示 勤務形態(出社 or テレワーク)の違いで平均勤務時間にどのような影響があるか散布図で表示 サービス検証結果 勤怠データの分析シナリオを通じた Microsoft Fabric の検証結果をまとめます。 検証項目は下記の通りです(再掲)。 Microsoft Fabric のみでデータ分析を 一気通貫 で実施できるか データ分析の経験があまりないユーザーをサポートする機能が現時点でどの程度実装されているか Microsoft Fabric の操作感(Power BI や Data Factory など統合されたサービスがこれまで通り利用できるか) その他利用時の注意点 一気通貫 のデータ分析 データの格納から可視化まですべて Microsoft Fabric で完結できました。 従来のように分析ごとに用途に応じて PaaS を組み合わせたり、インフラを確保する必要がなく、統一された UI の元で作業ができることはメリットであると言えます。 今回はローカルデータを中心とした分析シナリオでしたが、 機械学習 やリアルタイム分析など幅広いシナリオに対応できるサービスであるという印象です。 一方で、分析シナリオによっては不要な機能や コンポーネント があることも事実です。 Microsoft Fabric の利用が最適となる分析シナリオは何か、という点については、金銭面や学習コストの観点も含めて今後のアップデートを追っていく必要があります。 データ分析の経験が浅いユーザーへのサポート 今回の検証で触れた機能の中では、データフローの GUI によるデータ変換処理は比較的ユーザーに易しい設計という印象を受けました。 ですが、それ以外の部分については継続的な強化が必要と感じています。 Microsoft Fabric は「誰もがデータを管理および分析してアクションにつながる インサイト を得られる」というコンセプトを掲げていますが、現状の機能構成はデータ変換や加工・分析等の処理設計をユーザーの知識に依存しています。 Microsoft Fabric - データ分析 今回の検証においては、ノートブックを利用したデータの前処理や SQL エンドポイントによる分析で必要となるコードやクエリの作成は、未経験のユーザーには難易度が高い作業です。 誰もがデータを管理および分析するためにはこのような作業に対するサポート機能が必要で、具体的には下記のような内容を期待します。 Copilot 機能による各種コードやクエリの自動生成 One Lake のショートカット機能強化(連携先の拡張など) ドキュメントや チュートリアル の充実 Microsoft によると、Azure OpenAI と連携した Copilot 機能が近日公開予定となっておりますので、そちらを注視したいと思います。 各機能の操作感 主観になりますが、既存の PaaS や SaaS から統合された機能については違和感なく操作できました。 ノートブックによる対話形式でのデータ操作やデータフロー周辺の操作は、UI 自体が統合元の Microsoft 製品と大きく変わらないため、それらの経験がある方であれば抵抗なく利用できるかと思います。 まとめ 本記事では、 Microsoft Fabric の概要とサービス検証として実施した勤怠データ分析の内容についてご紹介しました。 Microsoft Fabric は発表されて間もないソリューションであり、ロードマップでは今後も多くのアップデートが予定されておりますので、引き続き注視したいと思います。 私たちは一緒に働いてくれる仲間を募集しています! クラウドアーキテクト 執筆: @tamura.kohei 、レビュー: @yamada.y ( Shodo で執筆されました )
アバター
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 AWS のマネジメントコンソールへのユーザー認証ではMFAの利用が一般的になってきましたが、アクセスキー利用時にMFAを必須とするには少しコツがいるので、この記事でまとめてみます。 ちなみに Security Hub のコン トロール IAM.19 「すべての IAM ユーザーに対して MFA を有効にする必要があります」を満たすには、マネジメントコンソールのユーザーだけではなく、アクセスキーで利用するIAMユーザーに対してもMFAの設定をしなければなりません。 前提:IAMユーザーの状態 通常通りポリシーをつけても、MFA不要でアクセスできてしまう MFAを必須にする方法1:ユーザーポリシーに条件を追加する MFAを必須にする方法2:スイッチロールを利用し、コードの取得を簡単にする Tips:OSSツールAWSumeで簡単にスイッチロール まとめ 前提:IAMユーザーの状態 IAMユーザーを作成し、MFAを設定した状態を前提として、この後の話を進めます。(IAMユーザー・アクセスキーを配布するケースは事前にMFAを設定しておくことはできませんが、この記事では割愛します。) このユーザーに対してアクセスキーを発行しておきます。 ~/.aws/credentials に test-user というプロファイル名で、アクセスキーとシークレットアクセスキーを保存しておきます。 [test-user] aws_access_key_id = <アクセスキーID> aws_secret_access_key = <シークレットアクセスキー> region = ap-northeast-1 通常通りポリシーをつけても、MFA不要でアクセスできてしまう このユーザーに対して、以下のようなポリシーを付けてみます。 この記事では一貫して ec2:DescribeRegions を例としていますが、一般的なリソースアクセスの許可に適宜置き換えて考えてください。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": " ec2:DescribeRegions ", " Resource ": " * " } ] } このユーザーを利用し DescribeRegions API をコールすると、MFAを設定しているにも関わらず、MFAコードを入力しなくてもアクセスが成功してしまいました。 Security Hub のIAM.19コン トロール をクリアするだけであれば、このようにMFAを有効にするだけで良いのですが、それだとMFAを利用しなくても権限を使用できてしまいます。せっかくMFAを有効にするのですから、MFAを利用しないと API アクセスできないようにきっちり設定したいですね。 MFAを必須にする方法1:ユーザーポリシーに条件を追加する MFAの利用を必須とする方法の1つ目として、IAMユーザーのポリシーを次のように書き換えます。 Condition 句を追加し、 aws:MultiFactorAuthPresent を利用してMFAを行ったことを条件とします。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": " ec2:DescribeRegions ", " Resource ": " * ", " Condition ": { " Bool ": { " aws:MultiFactorAuthPresent ": " true " } } } ] } 再び DescribeRegions API をコールすると、MFAを利用していないので今度はちゃんとエラーとなります。 MFAを利用するためには STS の GetSessionToken API を呼び出し、一時的な認証情報を取得します。 このとき --serial-number オプションには設定した MFA デ バイス の識別子(ARN)を、 --token-code にはMFA トーク ンを指定します。 発行された一時的な認証情報を使用するために ~/.aws/credentials に新しくプロファイルを作成しても良いですが、ここではシンプルに 環境変数 に設定します。 ( 環境変数 に設定された AWS 認証情報は、 認証情報ファイルよりも優先されます 。) そして 環境変数 に設定した認証情報を利用してもらうために、 AWS CLI の --profile オプションをつけずに DescribeRegions API をコールすると、再びアクセスできるようになりました。 以上の方法でMFAの利用を必須にはできましたが、 GetSessionToken API を呼び出して一時的な認証情報を取得する 取得した認証情報を利用するように設定する といった手間がかかってしまいます。 MFAを必須にする方法2:スイッチロールを利用し、コードの取得を簡単にする 上で説明した手間を省くために、スイッチロールを利用してMFAを強制する方法があります。 IAMロールを1つ作成します。 信頼ポリシー では同じアカウントからの AssumeRole を許可すると同時に、 aws:MultiFactorAuthPresent を利用してMFAを行ったことを条件とします。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Principal ": { " AWS ": " arn:aws:iam::<アカウントID>:root " } , " Action ": " sts:AssumeRole ", " Condition ": { " Bool ": { " aws:MultiFactorAuthPresent ": " true " } } } ] } このロールの 許可ポリシー は、上で作成したユーザーと同じとします。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": " ec2:DescribeRegions ", " Resource ": " * " } ] } そして元のIAMユーザーの許可ポリシーから DescribeRegions の許可を除外し、作成した IAMロールへの AssumeRole のみを許可します。( aws:MultiFactorAuthPresent の条件も不要です。) { " Version ": " 2012-10-17 ", " Statement ": [ { " Action ": " sts:AssumeRole ", " Resource ": " arn:aws:iam::<アカウントID>:role/<ロール名> ", " Effect ": " Allow " } ] } スイッチロールを簡単にするために、 ~/.aws/config に以下のように追記します。 [profile test-user] の部分は ~/.aws/credentials に記載したプロファイル名 test-user と名前を合わせる必要があります。 [profile test-user] region = ap-northeast-1 [profile my-test-role] region = ap-northeast-1 source_profile = test-user role_arn = arn:aws:iam::<アカウントID>:role/<スイッチロール用のロール名> mfa_serial = arn:aws:iam::<アカウントID>:mfa/<デバイス名> DescribeRegions API を呼び出す時に、スイッチロールした先のプロファイル名(ここでは my-test-role )を指定すると、そのままMFAコードを尋ねられます。入力すると API 実行が成功します。 この方法の良いところは、実行したい API をそのままコールすればよく、デ バイス の識別子(ARN)も ~/.aws/config にあらかじめ保存してあるので毎回入力する必要がないことです。 Tips: OSS ツールAWSumeで簡単にスイッチロール MFAを強制するスイッチロールを簡単にするツールとして、 OSS の AWSume もおすすめです。 必要なロール・ポリシー構成は「方法2」と同じで、ローカルで AWS SDK を利用する時にも一時的なクレデンシャル情報を簡単なコマンドでセットアップできたりするので便利です。 セットアップ 後、 awsume <プロファイル名> で一時的な認証情報を取得でき、MFAが必要な場合はここで尋ねられます。そのあとは、一時的な認証情報を利用して API コールできるようになります。 まとめ これまで説明したパターンを図にまとめてみました。 IAMアクセスキーは漏えいしないように細心の注意を払うことが大前提ですが、万が一漏えいしたとしてもすぐに使えないように、MFAで二重のガードをしておきましょう。 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 セキュリティエンジニア 執筆: @kou.kinyo 、レビュー: 寺山 輝 (@terayama.akira) ( Shodo で執筆されました )
アバター
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター セキュリティグループの耿です。 AWS のマネジメントコンソールへのユーザー認証ではMFAの利用が一般的になってきましたが、アクセスキー利用時にMFAを必須とするには少しコツがいるので、この記事でまとめてみます。 ちなみに Security Hub のコン トロール IAM.19 「すべての IAM ユーザーに対して MFA を有効にする必要があります」を満たすには、マネジメントコンソールのユーザーだけではなく、アクセスキーで利用するIAMユーザーに対してもMFAの設定をしなければなりません。 前提:IAMユーザーの状態 通常通りポリシーをつけても、MFA不要でアクセスできてしまう MFAを必須にする方法1:ユーザーポリシーに条件を追加する MFAを必須にする方法2:スイッチロールを利用し、コードの取得を簡単にする Tips:OSSツールAWSumeで簡単にスイッチロール まとめ 前提:IAMユーザーの状態 IAMユーザーを作成し、MFAを設定した状態を前提として、この後の話を進めます。(IAMユーザー・アクセスキーを配布するケースは事前にMFAを設定しておくことはできませんが、この記事では割愛します。) このユーザーに対してアクセスキーを発行しておきます。 ~/.aws/credentials に test-user というプロファイル名で、アクセスキーとシークレットアクセスキーを保存しておきます。 [test-user] aws_access_key_id = <アクセスキーID> aws_secret_access_key = <シークレットアクセスキー> region = ap-northeast-1 通常通りポリシーをつけても、MFA不要でアクセスできてしまう このユーザーに対して、以下のようなポリシーを付けてみます。 この記事では一貫して ec2:DescribeRegions を例としていますが、一般的なリソースアクセスの許可に適宜置き換えて考えてください。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": " ec2:DescribeRegions ", " Resource ": " * " } ] } このユーザーを利用し DescribeRegions API をコールすると、MFAを設定しているにも関わらず、MFAコードを入力しなくてもアクセスが成功してしまいました。 Security Hub のIAM.19コン トロール をクリアするだけであれば、このようにMFAを有効にするだけで良いのですが、それだとMFAを利用しなくても権限を使用できてしまいます。せっかくMFAを有効にするのですから、MFAを利用しないと API アクセスできないようにきっちり設定したいですね。 MFAを必須にする方法1:ユーザーポリシーに条件を追加する MFAの利用を必須とする方法の1つ目として、IAMユーザーのポリシーを次のように書き換えます。 Condition 句を追加し、 aws:MultiFactorAuthPresent を利用してMFAを行ったことを条件とします。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": " ec2:DescribeRegions ", " Resource ": " * ", " Condition ": { " Bool ": { " aws:MultiFactorAuthPresent ": " true " } } } ] } 再び DescribeRegions API をコールすると、MFAを利用していないので今度はちゃんとエラーとなります。 MFAを利用するためには STS の GetSessionToken API を呼び出し、一時的な認証情報を取得します。 このとき --serial-number オプションには設定した MFA デ バイス の識別子(ARN)を、 --token-code にはMFA トーク ンを指定します。 発行された一時的な認証情報を使用するために ~/.aws/credentials に新しくプロファイルを作成しても良いですが、ここではシンプルに 環境変数 に設定します。 ( 環境変数 に設定された AWS 認証情報は、 認証情報ファイルよりも優先されます 。) そして 環境変数 に設定した認証情報を利用してもらうために、 AWS CLI の --profile オプションをつけずに DescribeRegions API をコールすると、再びアクセスできるようになりました。 以上の方法でMFAの利用を必須にはできましたが、 GetSessionToken API を呼び出して一時的な認証情報を取得する 取得した認証情報を利用するように設定する といった手間がかかってしまいます。 MFAを必須にする方法2:スイッチロールを利用し、コードの取得を簡単にする 上で説明した手間を省くために、スイッチロールを利用してMFAを強制する方法があります。 IAMロールを1つ作成します。 信頼ポリシー では同じアカウントからの AssumeRole を許可すると同時に、 aws:MultiFactorAuthPresent を利用してMFAを行ったことを条件とします。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Principal ": { " AWS ": " arn:aws:iam::<アカウントID>:root " } , " Action ": " sts:AssumeRole ", " Condition ": { " Bool ": { " aws:MultiFactorAuthPresent ": " true " } } } ] } このロールの 許可ポリシー は、上で作成したユーザーと同じとします。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": " ec2:DescribeRegions ", " Resource ": " * " } ] } そして元のIAMユーザーの許可ポリシーから DescribeRegions の許可を除外し、作成した IAMロールへの AssumeRole のみを許可します。( aws:MultiFactorAuthPresent の条件も不要です。) { " Version ": " 2012-10-17 ", " Statement ": [ { " Action ": " sts:AssumeRole ", " Resource ": " arn:aws:iam::<アカウントID>:role/<ロール名> ", " Effect ": " Allow " } ] } スイッチロールを簡単にするために、 ~/.aws/config に以下のように追記します。 [profile test-user] の部分は ~/.aws/credentials に記載したプロファイル名 test-user と名前を合わせる必要があります。 [profile test-user] region = ap-northeast-1 [profile my-test-role] region = ap-northeast-1 source_profile = test-user role_arn = arn:aws:iam::<アカウントID>:role/<スイッチロール用のロール名> mfa_serial = arn:aws:iam::<アカウントID>:mfa/<デバイス名> DescribeRegions API を呼び出す時に、スイッチロールした先のプロファイル名(ここでは my-test-role )を指定すると、そのままMFAコードを尋ねられます。入力すると API 実行が成功します。 この方法の良いところは、実行したい API をそのままコールすればよく、デ バイス の識別子(ARN)も ~/.aws/config にあらかじめ保存してあるので毎回入力する必要がないことです。 Tips: OSS ツールAWSumeで簡単にスイッチロール MFAを強制するスイッチロールを簡単にするツールとして、 OSS の AWSume もおすすめです。 必要なロール・ポリシー構成は「方法2」と同じで、ローカルで AWS SDK を利用する時にも一時的なクレデンシャル情報を簡単なコマンドでセットアップできたりするので便利です。 セットアップ 後、 awsume <プロファイル名> で一時的な認証情報を取得でき、MFAが必要な場合はここで尋ねられます。そのあとは、一時的な認証情報を利用して API コールできるようになります。 まとめ これまで説明したパターンを図にまとめてみました。 IAMアクセスキーは漏えいしないように細心の注意を払うことが大前提ですが、万が一漏えいしたとしてもすぐに使えないように、MFAで二重のガードをしておきましょう。 私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。 セキュリティエンジニア 執筆: @kou.kinyo 、レビュー: 寺山 輝 (@terayama.akira) ( Shodo で執筆されました )
アバター
こんにちは、金融ソリューション事業部の孫です。 前回の Part1 記事に続きまして Part2 では、OpenMatchとAgonesを使用して、柔軟性がありスケーラブルなゲームマッチングとゲームサーバー管理システムの構築方法を詳しく説明しました。 この記事(Part3)では、UnrealEngineを利用して、オンラインマルチプレーヤーゲームのデモを完成させます。 さらに、 Part2 で開発したマッチメイキングサービスをUEクライアント(UnrealEngine GameClient)に統合します。 全体 アーキテクチャ は以下の図の通りです。 UEクライアントの実装について、前の 記事 で作成したデモを基にさらに拡張します。 プレーヤーマッチング機能とサーバー管理機能を追加することで、この度のクライアントの実現を目指します。 実施手順 1.前記事のデモにマッチング機能の追加 マッチング機能のボタンの追加 Frontend APIの呼びだす機能実装 2. Agones SDKを呼び出してGameServerの終了機能の実装 ユーザー数の管理機能の追加 Agones SDKを利用してGameServerの終了実装 3.パッケージ化したUEサーバーでAgones Fleetの作成 4.動作確認 終わりに 参考 実施手順 以下の順序でシステムを構築します: 前記事 のデモにマッチング機能の追加 Agones SDK を呼び出してGameServerの終了機能の実装 パッケージ化したUEサーバーでAgones Fleetの作成 動作確認 1. 前記事 のデモにマッチング機能の追加 この前の記事 UE5ネットワーク同期のC++実装例 では、プレーヤーが「Play」ボタンをクリックすると、UEゲームサーバーに接続してゲーム体験を開始する機能をすでに完成させています。 次に、マッチング機能のボタンを追加し、このボタンをクリックするとOpenMatchの Frontend API を呼び出してマッチング要求をリク エス トします。 マッチング機能のボタンの追加 以下の図のように、テキストエリア(Region入力用)とボタン(「Match」)をUnrealEngineのBluePrint Widget UIに配置します。 Frontend API の呼びだす機能実装 以下のモジュールをxxx.Build.csファイルに追加する 今回では、 Agones / HTTP / Json / JsonUtilities の4つのモジュールを使用します。 各モジュールは次のような役割を果たします。 Agones モジュールは Agones の SDK を使うために使用する HTTP モジュールは Frontend API の http リク エス トを送信するために使用する Json 、 JsonUtilities モジュールは http からの返却 Json データを解析するために使用する //xxx.Build.cs using UnrealBuildTool; //:(省略) PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "EnhancedInput", "Agones", "HTTP", "Json", "JsonUtilities" }); //:(省略) LoginHUDWidget.h を編集する まずは、配置したWidgetsをバインドすることから始めます。 ※注意:BluePrint内の Widget 名は LoginHUDWidget.h ファイル内の変数名と同じでなければならず、 meta = (BindWidget) タグを追加して Widget をバインドします それを完了したら、httpモジュールをincludeした上でコールバック関数を作成します。 //LoginHUDWidget.h // "Http.h"ヘッダーファイルの追加 #include "Http.h" UCLASS() class TEST_DESERVER_API ULoginHUDWidget : public UUserWidget { GENERATED_BODY() public: //:(省略) UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UEditableText* regionName; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UTextBlock* matchLabel; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UButton* matchBtn; private: FHttpModule* Http; void OnFetchGameServerResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); } LoginHUDWidget.cpp にマッチングボタンのクリックロジックを実装する //LoginHUDWidget.cpp // "Json.h", "JsonUtilities.h"ヘッダーファイルの追加 #include "Json.h" #include "JsonUtilities.h" // 構造関数内で関連コントロールとHttpモジュールを初期化します // MatchボタンにOnMatchmakingButtonClickedトリガー関数をバインドします void ULoginHUDWidget::NativePreConstruct() { Super::NativePreConstruct(); //:(省略) matchLabel->SetText(FText::FromString("Match")); FScriptDelegate MatchmakingDelegate; MatchmakingDelegate.BindUFunction(this, "OnMatchmakingButtonClicked"); matchBtn->OnClicked.Add(MatchmakingDelegate); //:(省略) Http = &FHttpModule::Get(); } // OnMatchmakingButtonClickedトリガー関数の処理ロジックを追加します void ULoginHUDWidget::OnMatchmakingButtonClicked() { statusLabel->SetText(FText::FromString("Start Matching!! Please wait")); TSharedRef<IHttpRequest, ESPMode::ThreadSafe> FetchGameServerHttpRequest = Http->CreateRequest(); FString frontendUrl = "127.0.0.1:8081/play/" + FString(regionName->GetText().ToString()); FetchGameServerHttpRequest->SetVerb("GET"); FetchGameServerHttpRequest->SetURL(frontendUrl); FetchGameServerHttpRequest->SetHeader("Content-Type", "application/json"); FetchGameServerHttpRequest->OnProcessRequestComplete().BindUObject(this, &ULoginHUDWidget::OnFetchGameServerResponse); FetchGameServerHttpRequest->ProcessRequest(); }; // Httpのコールバック関数の処理ロジックを追加します void ULoginHUDWidget::OnFetchGameServerResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) { if (bWasSuccessful) { TSharedPtr<FJsonObject> JsonObject; TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); if (FJsonSerializer::Deserialize(Reader, JsonObject)) { FString IpAddress = JsonObject->GetStringField("ip"); FString Port = JsonObject->GetStringField("port"); FString LevelName = IpAddress + ":" + Port; statusLabel->SetText(FText::FromString(LevelName)); playBtn->SetVisibility(ESlateVisibility::Visible); } } } // OnPlayGameButtonClickedトリガー関数の処理ロジックを変更します // OpenMatchから返されたGameServerアドレスを接続ターゲットとして設定します void ULoginHUDWidget::OnPlayGameButtonClicked() { //:(省略) FString LevelName = statusLabel->GetText().ToString(); //:(省略) } 2. Agones SDK を呼び出してGameServerの終了機能の実装 すべてのプレイヤーがオフラインになった場合、AgonesSDKを呼び出して利用済のGameServerを削除した後、新しいGameServerを再作成します。 ユーザー数の管理機能の追加 UnrealEngine Gamemodeクラスにおいて PlayerNum 変数を追加して現在のプレーヤー数を保存します。 また、少なくとも1人のプレーヤーがこのGameServerに接続したことがあることを判断するため、 HasPlayerConnected のBool変数を追加します。 前の 記事 で、UnrealEngineによく使われるサーバー側の関数について既にいくつか紹介しました。 今回はその中の PostLogin 、 Logout 関数を用いることで、プレーヤー数の簡易的な統計データが取得します。 //xxxGameMode.h public: //:(省略) virtual void PostLogin(APlayerController* NewPlayer) override virtual void Logout(AController* Exiting) override private: //:(省略) int32 PlayerNum; bool HasPlayerConnected; //xxxGameMode.cpp // 構造関数で変数を初期化します xxxGameMode::xxxGameMode(){ //:(省略) int32 PlayerNum = 0; bool HasPlayerConnected = false; } void xxxGameMode::PostLogin(APlayerController* NewPlayer) { Super::PostLogin(NewPlayer); PlayerNum++; HasPlayerConnected = true; if (!HasPlayerConnected) { HasPlayerConnected = true; GetWorldTimerManager().SetTimer(CountDownPlayerNumHandle, this, &xxxGameMode::TickCount, 1.0f, true); } } void xxxGameMode::Logout(AController* Exiting) { Super::Logout(Exiting); PlayerNum--; } Agones SDK を利用してGameServerの終了実装 UnrealEngine Gamemodeクラスにおいて、Agones SDK のShutdown()関数を呼び出してGameServerがシャットダウンされます。 ※Agonesは一 定量 のGameServerを維持し、GameServerが閉じられると新たなGameServerの生成がトリガーされます。 //xxxGameMode.h public: //:(省略) virtual void Tick(float DeltaTime) override private: //:(省略) //Agones SDKの処理結果に対応するレスポンス関数 //成功処理後のレスポンス関数 void HandleShutdownSuccess(const FEmptyResponse& Response); //成功失敗時のレスポンス関数 void HandleShutdownError(const FAgonesError& Error); void TickCount(); //xxxGameMode.cpp void Atest_DEServerGameMode::HandleShutdownSuccess(const FEmptyResponse& Response) {   //デモのため、実際の処理を行いません UE_LOG(LogTemp, Log, TEXT("Game server successfully shutdown")); } void Atest_DEServerGameMode::HandleShutdownError(const FAgonesError& Error) { //デモのため、実際の処理を行いません UE_LOG(LogTemp, Error, TEXT("shutting down failed: %s"), *Error.ErrorMessage); } void xxxGameMode::TickCount() { Super::Tick(DeltaTime); if (HasPlayerConnected && PlayerNum <= 0) { FShutdownDelegate SuccessDelegate; SuccessDelegate.BindUFunction(this, FName("HandleShutdownSuccess")); FAgonesErrorDelegate ErrorDelegate; ErrorDelegate.BindUFunction(this, FName("HandleShutdownError")); GetWorldTimerManager().ClearTimer(CountDownPlayerNumHandle); AgonesSDK->Shutdown(SuccessDelegate, ErrorDelegate); } } 3.パッケージ化したUEサーバーでAgones Fleetの作成 UnrealEngine Editorを使用して、 Linux プラットフォーム向けのDedicated Serverをパッケージ化します。 以下の図のように、ターゲットプラットフォーム Linux -> Development -> Linux(server) を選択し、パッケージします。 ※ Windows OSではもしかするとターゲットプラットフォームの選択肢に Linux が存在しないかもしれません。 これは、 Linux プラットフォームのサポートが設定されていないためです。 具体的な設定方法については、 こちら を参照してください。 パッケージ化が完了したら、 Part2 で各モジュールをデプロイしたのと同じ手順で、EKSにGameServerをデプロイします。 ローカルにおいてDockerImageを コンパイル する 下記のDockerfileをパッケージ化の際に指定された保存先のルート ディレクト リに配置し、Dockerイメージ作成コマンドを実行します。 ## DockerFile ## 「AgonesOMServer」を実際のプロジェクト名に置き換えます FROM ubuntu:20.04 RUN apt-get update && apt-get install -y \ libxcursor1 \ libxrandr2 \ libxinerama1 \ libxi6 \ libgl1-mesa-glx \ && rm -rf /var/lib/apt/lists/* RUN addgroup --gid 1000 gameserver && \ adduser --gid 1000 --uid 1000 --shell /usr/sbin/nologin --home /home/gameserver --gecos "" --disabled-login --disabled-password gameserver COPY --chown=gameserver:gameserver ./LinuxServer /home/gameserver/LinuxServer RUN chmod -R 770 /home/gameserver/LinuxServer WORKDIR /home/gameserver/LinuxServer USER gameserver EXPOSE 7777/udp ENTRYPOINT ["/home/gameserver/LinuxServer/「AgonesOMServer」.sh"] ## Docker image build command $ docker build -t localimage/ue_server:0.1 . DockerImageを Amazon ECRにアップロードする Part2 と同様に、ue_serverという名前の Amazon ECRプライベー トリポジ トリを新規作成します。 次に、Dockerイメージを Amazon ECRにアップロードします。 $ docker tag localimage/ue_server:0.1 {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/ue_server:0.1 $ docker push {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/ue_server:0.1 Amazon EKSにおいてAgones Fleetを作成する ローカルでAgones Fleetの作成用のfleet. yaml ファイルを作成します。 ## fleet.yaml --- apiVersion: "agones.dev/v1" kind: Fleet metadata: name: fleet-ap-northeast-1 spec: replicas: 2 scheduling: Packed strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 25% template: metadata: namespace: meta-poc labels: region: ap-northeast-1 spec: players: initialCapacity: 4 ports: - name: default containerPort: 7654 health: initialDelaySeconds: 30 periodSeconds: 60 template: metadata: namespace: meta-poc labels: region: ap-northeast-1 spec: containers: - name: ue5-server image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/ue_server:0.1 resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "1Gi" cpu: "1" fleet. yaml ファイルを Amazon EKSに適用します。 $ kubectl apply -f fleet.yaml 4.動作確認 UnrealEngine Editorを使用して4つのクライアントを起動する 具体的な操作手順は、以下の図のとおり New Editor Window(PIE) -> Number Of Players: 4 -> Play Standalone を設定する 以下のコマンドでAgones GameServerのステータス監視を起動する $ watch kubectl get gs OpenMatch Frontend Serviceをローカルに マッピング する # OpenMatch Frontend サービスがローカルの8081ポートにマッピングされます kubectl port-forward services/frontend-ednpoint 8081:80 マッチング機能をテストする 確認ポイント: 4つのクライアントが同じGameServerアドレスにマッチングされていること GameServerのステータスがAllocatedになっていること DedicatedServerへの接続をテストする 確認ポイント: Playボタンをクリックした後、Dedicated Serverに成功したこと Characterの頭上に表示されているNickNameが、クライアントが入力したNickNameと同じであること AgonesによるGameServerのシャットダウンをテストする 確認ポイント: 全てのGameClientを閉じた後、以前Allocated状態だったサーバーが削除されること その代わりに新たにReady状態のGameServerが作成されること 終わりに これで、マッチング機能を備え、Agonesで Unreal Engine DedicatedServerをスケジューリングするオンライン マルチプレイヤー ゲームの作成が完了しました。 この一連の記事では、EKSを使って Kubernetes の特性を活用し、 Unreal Engine のDedicated Serverを管理する方法について深く探求しました。 その中で、マッチングシステムとしてOpenMatchを使用し、その強力な拡張性と設定性を活用してニーズに合ったマッチングルールをカスタマイズしました。同時に、Agonesを用いてゲームサーバーのスケジューリングを行い、効率的で安定したサーバー管理を実現しました。 次は、さらなるゲームに関連するインフラ設計のソリューションを見つけ出し、ゲーム体験をさらに向上させる方法を継続に探求します。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 https://docs.unrealengine.com/5.2/ja/linux-game-development-in-unreal-engine/ https://agones.dev/site/docs/reference/fleet/ 執筆: @chen.sun 、レビュー: @yamashita.yuki ( Shodo で執筆されました )
アバター
こんにちは、金融ソリューション事業部の孫です。 シリーズの最初の記事( Part1 )では、 Kubernetes の強力な機能を活用するためにEKS(Elastic Kubernetes Service)をどのように設定するかについて詳しく説明しました。 EKSの設定が成功した後、ゲームのインフラでよく使われるAgonesとOpen Matchをインストールしました。 また、公式デモでテストを行い、インストールが正しく行われたことを確認しました。 Kubernetes に基づくAgonesとOpen Matchという2つの コンポーネント について、理解していない方がいらっしゃるかもしれません。 そのため、Part2では、まずAgonesとOpen Matchの基本的な概念を簡単に紹介します。 次に、実践でマッチングシステムのデモを作成し、どのようにAgonesとOpen Matchを組み合わせて効率的で柔軟なDedicated Serverの管理とマッチングを実現するかを示します。 Agonesの紹介 OpenMatchの紹介 OpenMatchのマッチメイカーを作成する一般的なフロー OpenMatchとAgonesの統合 実践:ゲームマッチングシステムのデモ作成 マッチングルールの定義 事前準備 マッチング関数の作成 GameFrontend Director Match Profilesの作成 Agones Allocator ServiceによるOpenMatchの統合 AgonesのAllocate機能のパッケージ化 Allocator ServiceパッケージをOpenMatchに統合 MatchFunction デプロイと動作確認 ローカルにおいてDockerImageのコンパイル DockerImageをAmazon ECRにアップロード モジュールをEKSにデプロイ 動作確認 終わりに 参考 Agonesの紹介 Agonesは、 Google Cloudと Ubisoft が共同で開発されて、 Ubisoft 内の大規模な マルチプレイヤー オンラインゲーム(MMO)で利用されているソリューションです。 また、プログラミングが OSS で公開されている為安全に独自ネットワーク内で動作させることが可能です。 Agonesは、 Kubernetes の特性を活用してゲームサーバーを効率的に、そしてスケーラブルに運用および管理する方法を提供します。 Agonesの主要な コンポーネント には、GameServer、Fleetがあります。 Agonesでは、開発者は簡単な Kubernetes のコマンドを用いてGameServerを作成および管理することが可能で、これによりゲームサーバーの管理の複雑さが大幅に低減されます。 Agonesがゲームサーバーのライフサイクルを管理する中で、以下の6つのステージを定義しています。 Agonesはゲームサーバーのステージに応じて、適切な処理を実行します。 Scheduled (予定):GameServerがスケジュールされ、Nodeに割り当てられる Requested (要求): Kubernetes のPodが作成され、GameServerが作成される Starting (起動中):GameServerが起動し、プレイヤーがゲームに接続できる状態になる前の準備状態 Ready (準備完了):GameServerがアクティブ状態で、プレイヤーが接続できる Allocated (割り当て済み):プレイヤーがGameServerに接続し、リソースが確保されている Shutdown (シャットダウン):すべてのプレイヤーが切断され、GameServerがシャットダウンする OpenMatchの紹介 OpenMatchは、Frontend API 、Backend API 、Query API 、Functionなど、複数の コンポーネント から成り立っています。 これらの コンポーネント はそれぞれが独自の役割を果たしながら協調して働き、マッチングシステムを構築します。 OpenMatchのマッチングフローは以下のとおりです。 プレイヤーがFrontend API にマッチングリク エス トを送信する Frontend API はそのリク エス トを内部の状態でストアに保存する マッチング関数がQuery API を使用して状態ストアから条件に合うプレイヤーを問い合わせする マッチング関数がBackend API にマッチング結果を返す Backend API がプレイヤーにマッチング結果を返す OpenMatchのマッチメ イカ ーを作成する一般的なフロー OpenMatchのマッチメ イカ ーを作成するには主に三つのステップがあります。 マッチングルールを定義する マッチング関数を作成する マッチメ イカ ーの設定および運用を行う まず、マッチングルールを定義します。 このルールはマッチングロジックを反映したもので、プレイヤーのレベル、地域、スキルなどを含めます。 次に、マッチング関数を作成します。 この関数はQuery API を使用してマッチングルールに合致するプレイヤーを問い合わせ、そのマッチング結果をBackend API に返します。 最後に、マッチメ イカ ーの設定と運用を行います。OpenMatchは多くの設定オプションを提供しており、それらはニーズに応じて設定できます。 OpenMatchとAgonesの統合 OpenMatchとAgonesの統合は、効率的なゲームマッチングシステムを構築する上での重要な部分であり、主に二つのプロセスが関与しています。 OpenMatchのマッチング関数からAgonesのGameServerを呼びだすところ GameServerのライフサイクルを管理するところ OpenMatchはプレイヤーのマッチングを担当し、一方AgonesはGameServerのライフサイクルの管理を担当します。 これら二つの組み合わせにより、プレイヤーのニーズに応じてGameServerを動的に作成および割り当てることができます。 OpenMatchのマッチング関数内で、Agones SDK を通じて新しいGameServerを作成できます。 しかし、ほとんどの場合新しいGameServerを作成するだけではなく既存のGameServerをスケジュールし、割り当てることがより重要です。 GameServerのパフォーマンス、負荷、地理的な位置などを評価し、最適なGameServerを見つける必要があります。 適切なGameServerを見つけたら、そのアドレスをプレイヤーに返します、プレイヤーはそのアドレスを使用してGame Serverに接続しゲーム体験を始めます。 ここで終わりではありませんが、GameServerの状態を監視し、必要に応じて調整する必要があります。 例えば、GameServerの負荷が高すぎる場合、新しいGameServerを作成して負荷を分散できます。 一方、GameServerのプレイヤー数が減少した場合、それをシャットダウンしてリソースを節約することも可能です。 実践:ゲームマッチングシステムのデモ作成 先に紹介したOpenMatch マッチメーカー の作成プロセスに従って、デモを作成し始めます。 マッチングルールの定義 このデモでは、ユーザーのスキルレベルとレイテンシを基にスコアを算出し、同一リージョン内でスコアが近いユーザーをマッチングするというルールを実装します。 それぞれのマッチングルームは4人のプレイヤーで構成され、スコアが近いユーザー同士は一緒になります。 以下では、このマッチングの詳細や手順、そして適用する アルゴリズム について具体的に説明します。 チケット詳細 Ticket Details チケットは以下図のGameFrontendによって作成され、OpenMatchのFrontendにプッシュされる情報です。 チケットにはプレイヤーに関する情報が含まれており、マッチングの際に使用されます。 以下「GameFrontend」、「Director」、「MatchFunction」章の実装で利用されます。 今回のデモでは、チケット詳細には以下の要素が含まれています。 タグ tag :タグを使ってチケットを分類することが可能で、それによりマッチングシステムはより効率的に対応するキューを見つけることができる 今回はゲームモード Game Mode という設計を前提に実装するため、タグはmode.sessionとする リージョン Region :これはプレイヤーがいる地理的な地域を示しているが、今回はap-northeast-1、ap-northeast-3とする スキルレベル Skill Level :これはプレイヤーのスキルレベルを示しており、0.0から2.0の範囲で設定される レイテンシ Latency :これはプレイヤーのネットワーク遅延を示している ※ほとんどの人は0に近いですが、ネットワークの信頼性をシミュレートするために、一部の人は無限大に設定されている マッチング機能の基準 MatchFunction Criteria 今回のデモでは、マッチングの基準を以下に定義します。 以下「Director」、「MatchFunction」章の実装で利用されます。 まずはプレイヤーの地理的な地域とゲームモードを基準に、チケットプールを作成する 次に、各プレイヤーに対して score = skill - (latency / 1000.0) の アルゴリズム を使用してスコアを算出する そして、スコアに基づいてルームにプレイヤーを配置する 高スキル、低レイテンシのユーザーは同じルームに割り当てられる 1つのマッチに参加できるプレイヤーの上限は、4人と定められている ディレクタープロファイル Director Profiles ディレクタープロファイルはディレクターが生成するオブジェクトで、マッチのリク エス トに使用されます。 以下「Director」章の実装で利用されます。 今回のデモでは、ディレクターは5秒ごとにプロファイルを生成し、マッチをリク エス トする。 また、ディレクターはプレイヤーの地理的な地域に応じて、対応する地理的な地域のGameServerをプレイヤーに割り当てる 事前準備 OpenMatchの リポジトリ をローカル環境にクローンする ベースとなるコードは、tutorials/matchmaker101のパスに存在する git clone https://github.com/googleforgames/open-match.git Golang による実装のため、適切な IDE を設定する この記事では、 Visual Studio Code にGo plugin(v.39.0)をインストールした環境で開発を進めた Docker環境を準備する 対象の環境でDockerをセットアップするには、 Dockerのインストール のドキュメントを参照してください マッチング関数の作成 Openmatch公式ドキュメントの Tutorial をベースに、ステップ バイス テップでGameFrontend、Director、MatchFunctionといった コンポーネント を設計・作成します。 ※TutorialはOpenmatchの フレームワーク プログラムで、マッチングロジックは上記のルールに従って独自に実装する必要があります。 以下の図は、Openmatch の全体的な アーキテクチャ で、赤く囲まれた部分は独自に実装すべき部分です。 GameFrontend チケット生成関数を実装する ※ベースコード: https://github.com/googleforgames/open-match/blob/main/tutorials/matchmaker101/frontend/ticket.go 「マッチングルールの定義」章で定義したチケット詳細 Ticket Details のルールに従って、 mode.session という名前のタグを定義して、次にランダムにスキルとレイテンシの値を設定します。 リージョンの設定については、具体的なユーザーがどこから接続するかは確定していないため、外部から値を取得するように定義します。この情報はクライアントから取得する必要があります。 # ticket.go import( "open-match.dev/open-match/pkg/pb" // 必要なパッケージ追加 "math/rand" "time" ) func makeTicket(region string) *pb.Ticket { modes := []string{"mode.session"} ticket := &pb.Ticket{ SearchFields: &pb.SearchFields{ Tags: modes, DoubleArgs: CreateDoubleArgs(), StringArgs: map[string]string{ "region": region, }, }, } return ticket } func CreateDoubleArgs() map[string]float64 { rand.Seed(time.Now().UTC().UnixNano()) skill := 2 * rand.Float64() latency := 50.0 * rand.ExpFloat64() return map[string]float64{ "skill": skill, "latency": latency, } } echo フレームワーク を使用してFrontendのAPIServerを実装する ※ベースコード: https://github.com/googleforgames/open-match/blob/main/tutorials/matchmaker101/frontend/main.go この API ServerのURLは: GET /play/:region で、regionパラメータを持っています。 # frontend/main.go import( "github.com/labstack/echo" ) type matchResponce struct { IP string `json:"ip"` Port string `json:"port"` Skill string `json:"skill"` Latency string `json:"latency"` Region string `json:"region"` } var fe pb.FrontendServiceClient var matchRes = &matchResponce{} func main() { //:(ベースコートを省略する) // fe = pb.NewFrontendServiceClient(conn)以降のコードをコメントアウト e := echo.New() e.GET("/play/:region", handleGetMatch) e.Start(":80") } func handleGetMatch(c echo.Context) error { // Create Ticket. region := c.Param("region") req := &pb.CreateTicketRequest{ Ticket: makeTicket(region), } matchRes.Skill = fmt.Sprintf("%f", req.Ticket.SearchFields.DoubleArgs["skill"]) matchRes.Latency = fmt.Sprintf("%f", req.Ticket.SearchFields.DoubleArgs["latency"]) matchRes.Region = req.Ticket.SearchFields.StringArgs["region"] resp, err := fe.CreateTicket(context.Background(), req) if err != nil { log.Fatalf("Failed to CreateTicket, got %v", err) return c.JSON(http.StatusInternalServerError, matchRes) } // Polling TicketAssignment. deleteOnAssign(fe, resp) return c.JSON(http.StatusOK, matchRes) } func deleteOnAssign(fe pb.FrontendServiceClient, t *pb.Ticket) { //:(ベースコートを省略する) if got.GetAssignment() != nil { log.Printf("Ticket %v got assignment %v", got.GetId(), got.GetAssignment()) conn := got.GetAssignment().Connection slice := strings.Split(conn, ":") matchRes.IP = slice[0] matchRes.Port = slice[1] break } } Director Match Profilesの作成 ※ベースコード: https://github.com/googleforgames/open-match/blob/main/tutorials/matchmaker101/director/profile.go 「マッチングルールの定義」章で定義した マッチング機能の基準 MatchFunction Criteria のチケットプール作成ルールに従って、 TagPresentFilters および StringEqualsFilter を定義します。 「マッチングルールの定義」章で定義したディレクタープロファイル Director Profiles のゲームサーバー選択ルールに従って、 profile.Extensions を定義します。 # profile.go type AllocatorFilterExtension struct { Labels map[string]string `json:"labels"` Fields map[string]string `json:"fields"` } func generateProfiles() []*pb.MatchProfile { var profiles []*pb.MatchProfile regions := []string{"ap-northeast-1", "ap-northeast-3"} for _, region := range regions { profile := &pb.MatchProfile{ Name: fmt.Sprintf("profile_%s", region), Pools: []*pb.Pool{ { Name: "pool_mode_" + region, TagPresentFilters: []*pb.TagPresentFilter{ {Tag: "mode.session"}, }, StringEqualsFilters: []*pb.StringEqualsFilter{ {StringArg: "region", Value: region}, }, }, }, } // build filter extensions filter := AllocatorFilterExtension{ Labels: map[string]string{ "region": region, }, Fields: map[string]string{ "status.state": "Ready", }, } // to protobuf Struct labelsStruct := &structpb.Struct{Fields: make(map[string]*structpb.Value)} for key, value := range filter.Labels { labelsStruct.Fields[key] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: value}} } fieldsStruct := &structpb.Struct{Fields: make(map[string]*structpb.Value)} for key, value := range filter.Fields { fieldsStruct.Fields[key] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: value}} } // put data to the protobuf Struct filterStruct := &structpb.Struct{Fields: map[string]*structpb.Value{ "labels": {Kind: &structpb.Value_StructValue{StructValue: labelsStruct}}, "fields": {Kind: &structpb.Value_StructValue{StructValue: fieldsStruct}}, }} // to google.protobuf.Any object filterAny, err := ptypes.MarshalAny(filterStruct) if err != nil { panic(err) } profile.Extensions = map[string]*any.Any{ "allocator_filter": filterAny, } profiles = append(profiles, profile) } return profiles } Agones Allocator ServiceによるOpenMatchの統合 Agonesを統合し、マッチング結果に対応するGameServerアドレスを割り当てます。 プレイヤーはこのアドレスを通じて対応するGameServerに接続します。 Agones Allocate機能の実装は独自のものであり、OpenMatchの チュートリアル には基礎となるコードが存在しません。 そのため、directorフォルダの下に新規ファイルとしてallocator_director.goを作成します。 AgonesのAllocate機能のパッケージ化 Agonesが提供する Allocator Service を使って対応するGameServerを取得します。 デフォルトのクライアント証明書を取得する Allocator Service はmTLS認証モードを使用しており、これにより証明書を使用してサービスに接続することは必須になります。 証明書は既にhelmインストール時に作成されています。 独自の証明書を使用することも可能で、具体的な設定は こちら を参照してください。 下記のコマンドを実行してデフォルトのクライアント証明書を取得する ※筆者は Mac の環境でコマンドを実行しています。 Linux の環境であれば、 base64 -D の代わりに base64 -d コマンドを使用してください。 # MACのコマンド kubectl get secret allocator-client.default -n default -ojsonpath="{.data.tls\.crt}" | base64 -D > "client.crt" kubectl get secret allocator-client.default -n default -ojsonpath="{.data.tls\.key}" | base64 -D > "client.key" kubectl get secret allocator-tls-ca -n agones-system -ojsonpath="{.data.tls-ca\.crt}" | base64 -D > "tls-ca.crt" 取得したクライアント証明書を配置します。 上記でダウンロードした証明書を特定のパスに保存し、Pathとして定義します。 ここでは、 allocator/certfile ディレクト リに保存することを想定しています。 # agones_allocator.go const ( KeyFilePath = "allocator/certfile/client.key" CertFilePath = "allocator/certfile/client.crt" CaCertFilePath = "allocator/certfile/tls-ca.crt" ) Allocator Serviceのクライアントを作成する 外部からAgonesのAllocate機能を呼びだす必要がある場合、 NewAgonesAllocatorClient を呼びだすとクライアントが生成されます。 ※ご注意: コードが長くなりすぎないように、エラー処理に関連するコードは削除しています。 # agones_allocator.go type AgonesAllocatorClientConfig struct { KeyFile string CertFile string CaCertFile string AllocatorServiceHost string AllocatorServicePort int Namespace string MultiCluster bool } type AgonesAllocatorClient struct { Config *AgonesAllocatorClientConfig DialOpts grpc.DialOption } func NewAgonesAllocatorClient() (*AgonesAllocatorClient, error) { config := &AgonesAllocatorClientConfig{ KeyFile: KeyFilePath, CertFile: CertFilePath, CaCertFile: CaCertFilePath, AllocatorServiceHost: AllocatorServiceHost, AllocatorServicePort: AllocatorServicePort, Namespace: "default", MultiCluster: false, } cert, err = ioutil.ReadFile(config.CertFile) key, err = ioutil.ReadFile(config.KeyFile) ca, err = ioutil.ReadFile(config.CaCertFile) dialOpts, err := createRemoteClusterDialOption(cert, key, ca) return &AgonesAllocatorClient{ Config: config, DialOpts: dialOpts, }, nil } func createRemoteClusterDialOption(clientCert, clientKey, caCert []byte) (grpc.DialOption, error) { cert, err := tls.X509KeyPair(clientCert, clientKey) tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true} if len(caCert) != 0 { tlsConfig.RootCAs = x509.NewCertPool() if !tlsConfig.RootCAs.AppendCertsFromPEM(caCert) { return nil, errors.New("only PEM format is accepted for server CA") } } return grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), nil } Allocateのメイン関数を実装する この関数では、まずgrpc( grpc.Dial ) プロトコル を使用して Allocator Service に接続します。 次に、GameServerの選択ルール( assignmentGroup.Assignment.Extensions )を取得します。 このルールに基づいて対応するGameServerを取得し、最終的に各Assignmentの address フィールドにアドレスを付与します。 ※ご注意: コードが長くなりすぎないように、エラー処理に関連するコードは削除しています。 # agones_allocator.go func (c *AgonesAllocatorClient) Allocate(req *pb.AssignTicketsRequest) error { conn, err := grpc.Dial(fmt.Sprintf("%s:%d", c.Config.AllocatorServiceHost, c.Config.AllocatorServicePort), c.DialOpts) defer conn.Close() grpcClient := pb_agones.NewAllocationServiceClient(conn) for _, assignmentGroup := range req.Assignments { filterAny := assignmentGroup.Assignment.Extensions["allocator_filter"] filter := &structpb.Struct{} if err := ptypes.UnmarshalAny(filterAny, filter); err != nil { panic(err) } request := &pb_agones.AllocationRequest{ Namespace: c.Config.Namespace, GameServerSelectors: []*pb_agones.GameServerSelector{ { MatchLabels: filter.Fields["labels"], }, }, MultiClusterSetting: &pb_agones.MultiClusterSetting{ Enabled: c.Config.MultiCluster, }, } resp, err := grpcClient.Allocate(context.Background(), request) if len(resp.GetPorts()) > 0 { address := fmt.Sprintf("%s:%d", resp.Address, resp.Ports[0].Port) assignmentGroup.Assignment.Connection = address } } return nil } Allocator ServiceパッケージをOpenMatchに統合 上記のOpenMatchの アーキテクチャ 図に基づき、Agonesサービスを呼び出してGameServerを取得する コンポーネント はDirectorです。 そのため、呼び出しコードをDirectorに統合する必要があります。 ※ベースコード: https://github.com/suecideTech/try-openmatch-agones/blob/master/OpenMatch/mod_matchmaker101/director/main.go GameServerのassgin関数を修正します。 ここでは、上記で作成した Allocator Service パッケージを使用して実際のGameServerアドレスを取得します。 元のコードでは GameServerAllocations を使ってGameServerアドレスを取得していますが、これはAgonesが推奨している方法ではなく、また後期の拡張にも適していません。 そのため、公式に推奨されている Allocator Service をラップしてGameServerを取得するようにしました。 # director/main.go func assign(be pb.BackendServiceClient, matches []*pb.Match) error { for _, match := range matches { ticketIDs := []string{} for _, t := range match.GetTickets() { ticketIDs = append(ticketIDs, t.Id) } aloReq := &pb.AssignTicketsRequest{ Assignments: []*pb.AssignmentGroup{ { TicketIds: ticketIDs, Assignment: &pb.Assignment{ Extensions: match.Extensions, }, }, }, } client, err := allocator.NewAgonesAllocatorClient() client.Allocate(aloReq) if _, err := be.AssignTickets(context.Background(), aloReq); err != nil { return fmt.Errorf("AssignTickets failed for match %v, got %w", match.GetMatchId(), err) } log.Printf("Assigned server %v to match %v", conn, match.GetMatchId()) } return nil } MatchFunction ユーザーマッチングルールを実装します。 ※ベースコード: https://github.com/suecideTech/try-openmatch-agones/blob/master/OpenMatch/mod_matchmaker101/matchfunction/mmf/matchfunction.go 「マッチングルールの定義」章で定義した マッチング機能の基準 MatchFunction Criteria のユーザーマッチングルールに従って、まずユーザーのスコアを計算し、スコアの大きさに基づいて4人部屋を割り当てます。 # matchfunction.go const ( matchName = "basic-matchfunction" ticketsPerPoolPerMatch = 4 ) func (s *MatchFunctionService) Run(req *pb.RunRequest, stream pb.MatchFunction_RunServer) error { //:(ベースコートを省略する) //poolTickets, err := matchfunction.QueryPools(stream.Context(), s.queryServiceClient, req.GetProfile().GetPools()) p := req.GetProfile() tickets, err := matchfunction.QueryPool(stream.Context(), s.queryServiceClient, p.GetPools()[0]) //:(ベースコートを省略する) idPrefix := fmt.Sprintf("profile-%v-time-%v", p.GetName(), time.Now().Format("2006-01-02T15:04:05.00")) proposals, err := makeMatches(req.GetProfile(), idPrefix, tickets) //:(ベースコートを省略する) } func (s *MatchFunctionService) makeMatches(ticketsPerPoolPerMatch int, profile *pb.MatchProfile, idPrefix string, tickets []*pb.Ticket) ([]*pb.Match, error) { if len(tickets) < ticketsPerPoolPerMatch { return nil, nil } ticketScores := make(map[string]float64) for _, ticket := range tickets { ticketScores[ticket.Id] = score(ticket.SearchFields.DoubleArgs["skill"], ticket.SearchFields.DoubleArgs["latency"]) } sort.Slice(tickets, func(i, j int) bool { return ticketScores[tickets[i].Id] > ticketScores[tickets[j].Id] }) var matches []*pb.Match count := 0 for len(tickets) >= ticketsPerPoolPerMatch { matchTickets := tickets[:ticketsPerPoolPerMatch] tickets = tickets[ticketsPerPoolPerMatch:] var matchScore float64 for _, ticket := range matchTickets { matchScore += ticketScores[ticket.Id] } eval, err := anypb.New(&pb.DefaultEvaluationCriteria{Score: matchScore}) if err != nil { log.Printf("Failed to marshal DefaultEvaluationCriteria into anypb: %v", err) return nil, fmt.Errorf("Failed to marshal DefaultEvaluationCriteria into anypb: %w", err) } newExtensions := map[string]*anypb.Any{"evaluation_input": eval} newExtensions for k, v := range origExtensions { newExtensions[k] = v } matches = append(matches, &pb.Match{ MatchId: fmt.Sprintf("%s-%d", idPrefix, count), MatchProfile: profile.GetName(), MatchFunction: matchName, Tickets: matchTickets, Extensions: newExtensions, }) count++ } return matches, nil } func score(skill, latency float64) float64 { return skill - (latency / 1000.0) } ここまでで、マッチング機能とGameServerのケジューリング機能の実装が完了しました。 次に、作成したモジュールをそれぞれEKSにアップロードし、デモとしてテストします。 具体的に完成したデモの アーキテクチャ は、以下の図の通りです。 デプロイと動作確認 ローカルにおいてDockerImageの コンパイル GameFrontend # OpenMatch/mod_matchmaker101/frontend/Dockerfile docker build -t localimage/mod_frontend:0.1 . Director # OpenMatch/mod_matchmaker101/director/Dockerfile docker build -t localimage/mod_director:0.1 . MatchFunction # OpenMatch/mod_matchmaker101/matchfunction/Dockerfile docker build -t localimage/mod_matchfunction:0.1 . DockerImageを Amazon ECRにアップロード EKSでDockerImageを取得する際、ローカルのイメージにアクセスできないため、イメージを Amazon ECRサービスにアップロードする必要があります。 ※ Amazon ECRはイメージを保管するための専用レポジトリで、Docker Hubなどと同様のサービスがあります。 Amazon ECRでプライベートイメージ リポジトリ を作成する具体的な方法については、 ECRでプライベートリポジトリを作成する を参照してください。 以下の名称の Amazon ECRプライベートイメージ リポジトリ をそれぞれ作成する Frontendの リポジトリ 名:mod_frontend Directorの リポジトリ 名:mod_director MatchFunctionの リポジトリ 名:mod_matchfunction ローカルのイメージを Amazon ECRにアップロードする # Frontend docker tag localimage/mod_frontend:0.1 {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_frontend:0.1 docker push {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_frontend:0.1 # Director docker tag localimage/mod_director:0.1 {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_director:0.1 docker push {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_director:0.1 # MatchFunction: docker tag localimage/mod_matchfunction:0.1 {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_matchfunction:0.1 docker push {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_matchfunction:0.1 モジュールをEKSにデプロイ デプロイ yaml ファイル内のimageアドレスを、上記で作成した Amazon ECRのアドレスに変更します。 # Frontend: ## yaml file path ## OpenMatch/mod_matchmaker101/frontend/frontend.yaml image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_frontend:0.1 # Director ## yaml file path ## OpenMatch/mod_matchmaker101/director/director.yaml image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_director:0.1 # MatchFunction: ## yaml file path ## OpenMatch/mod_matchmaker101/matchfunction/matchfunction.yaml image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_matchfunction:0.1 各 Yaml ファイルを以下のように修正します。 # Frontend.yaml ## yaml file path ## OpenMatch/mod_matchmaker101/frontend/frontend.yaml ## KindをDeploymentに変更し、HTTP LBサービスを追加します apiVersion: v1 kind: Service metadata: name: frontend-endpoint annotations: service.alpha.kubernetes.io/app-protocols: '{"http":"HTTP"}' labels: app: frontend spec: type: NodePort selector: app: frontend ports: - port: 80 protocol: TCP name: http targetPort: frontend --- apiVersion: apps/v1 kind: Deployment metadata: name: frontend namespace: default labels: app: frontend spec: replicas: 1 selector: matchLabels: app: frontend template: metadata: labels: app: frontend spec: containers: - name: frontend image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_frontend:0.1 imagePullPolicy: Always ports: - name: frontend containerPort: 80 # Director.yaml ## yaml file path ## OpenMatch/mod_matchmaker101/director/director.yaml apiVersion: v1 kind: Pod metadata: name: director namespace: openmatch-poc spec: containers: - name: director image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_director:0.1 imagePullPolicy: Always hostname: director # MatchFunction.yaml ## yaml file path ## OpenMatch/mod_matchmaker101/matchfunction/matchfunction.yaml apiVersion: v1 kind: Pod metadata: name: matchfunction namespace: openmatch-poc labels: app: openmatch component: matchfunction spec: containers: - name: matchfunction image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_MatchFunction:0.1 imagePullPolicy: Always ports: - name: grpc containerPort: 50502 --- kind: Service apiVersion: v1 metadata: name: matchfunction namespace: openmatch-poc labels: app: openmatch component: matchfunction spec: selector: app: openmatch component: matchfunction clusterIP: None type: ClusterIP ports: - name: grpc protocol: TCP port: 50502 それぞれの yaml ファイルをEKSに適用し、マッチングシステムをデプロイします。 # GameFrontend kubectl apply -f frontend.yaml # Director kubectl apply -f director.yaml # MatchFunction kubectl apply -f matchfunction.yaml 動作確認 以下の図に示すように、 frontend.yaml で作成されたServiceに接続し、マッチングシステムをテストします。 この frontend-endpoint サービスにローカルでもアクセスできるようにするため、 Kubernetes のPortForwadering機能を使用します。 Frontendサービスをローカルに マッピング する 下図の⑤エリアです。 # Frontend サービスをローカルの8081ポートにマッピングします kubectl port-forward services/frontend-ednpoint 8081:80 8つの新しいターミナルを作成して、8名のプレイヤーがFrontendサービスに接続するのをシミュレートする 下図の①エリアで4名(ap-northeast-1)+4名(ap-northeast-3)のプレイヤーが接続するのをシミュレートします。 # Get: /Frontend/play/regionname ## 4名(ap-northeast-1) curl 127.0.0.1:8081/play/ap-northeast-1 ## 4名(ap-northeast-3) curl 127.0.0.1:8081/play/ap-northeast-3 エラーの有無を確認するために、matchfunction/director/frontendモジュールのログ情報を出力する 下図の②〜④エリアです。 # matchfuntion log kubectl logs --tail 4 matchfunction -n openmatch-poc # director log kubectl logs --tail 4 director -n openmatch-poc # frontend log kubectl logs --tail 4 deployments/frontend 上記の手順に従って、8名のプレイヤーがマッチングを開始するシミュレーションを行います。 確認ポイントは次の通りです。 ②〜④エリアのログにはエラー出力がありません。 ①の8名のクライアント全員がIP、Port情報を正常に取得します。 また、前の4名のプレイヤーは同じグループにマッチされるため、その IP:Port アドレスは同じです。 後の4名のプレイヤーも同じグループにマッチされ、その IP:Port アドレスも同じです。 ⑥エリアでは、GameServerのステータスを確認し、2台のサーバーがAllocated状態にあることを確認します。 終わりに これまでに、AgonesとOpen Matchを使用して高可用性と拡張性、スケジューリングが可能なマッチングシステムを構築しました。 次に、 Part3 ではUnrealEngineを使用してGameClientを開発し、このマッチングシステムに接続する方法を説明します。 これにより、マッチング機能を持ち、Agonesを使用してDedicated Serverをスケジューリングする マルチプレイ ゲームの開発を完了します。 引き続き、お楽しみにしてください! 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 https://agones.dev/site/docs/overview/ https://open-match.dev/site/docs/ 執筆: @chen.sun 、レビュー: @yamashita.yuki ( Shodo で執筆されました )
アバター
こんにちは!金融ソリューション事業部の孫です。 以前の 記事 では Unreal Engine Dedicated Serverの構築方法について紹介しました。 今回は続きの記事として、以下の3部で、 AWS が提供するEKSを使用してマッチメイキング機能を持つAgonesでGameServerを運用する環境の構築プロセスを説明します。 なお、EKSは Kubernetes の クラウド サービスの一つであり、同様のものとして「 Google のGKE」や「 Microsoft の AKS 」などが存在します。 以下3記事(Part)に分けて解説します。 Part1 :環境設定(「EKS環境構築」と「Agones/Open Matchのインストール」)、および動作確認 Part2 :マッチメイキングサービスのカスタマイズ処理を実装します Part3 :UnrealEngineを使用したGameServerとGameClientを用意します、Part2で開発したマッチメイキングサービスをGameClientに統合します。 はじめに 実行環境 実施手順 1.EKSの環境構築 2.Agonesのインストール 3.Openmatchのインストール 4.動作確認 Agonesデモのデプロイと動作確認 Open Matchデモのデプロイと動作確認 終わりに 参考 はじめに 現代のゲーム業界では、Dedicated Serverが重要な要素となっています。ある マルチプレイ ゲームの裏側では、大量のDedicated Serverを所有しており、プレイヤーに安定かつ効率的なサービスを提供します。 これらのサーバーの管理とスケジューリングは、プレイヤーのゲーム体験に直接影響する重要な問題です。この問題を解決するために、Agonesは登場し、Dedicated Serverの管理とスケジューリングをよりシンプルにするソリューションを提供します。 しかし、専用サーバーの管理とスケジューリングだけでは不十分で、これらのサーバーを効率的に利用するためにはマッチングシステムが必要です。 例えば、同じ地域のプレイヤーが最も近いゲームサーバーにマッチングされることで、彼らのゲーム体験を向上させることを望んでいます。そのため、Open Matchは、柔軟でスケーラブルなマッチングシステムとして生まれました。 したがって、AgonesとOpenMatchは相互補完的な存在です。一方で、AgonesはDedicated Serverの管理とスケジューリングを行い、サーバーの使用効率を向上させます。 一方で、Open Matchは効率的なマッチング アルゴリズム を通じて、プレイヤーを最も適したサーバーにマッチングします。 これら二つは、ゲームに対して柔軟でスケーラブルで効率的なインフラスト ラク チャを提供します。 これから、このようなインフラスト ラク チャを作成する方法を3記事に分けてステップ バイス テップで説明します。 実行環境 kubectl (v1.27.1) Kubernetes クラスタ ーを操作するための コマンドライン ツール eksctl (0.139.0) EKS クラスタ ーを制御するために使用する コマンドライン ツール AWS CLI (2.11.17) Amazon EKS など AWS のサービスを操作するための コマンドライン ツール 実施手順 以下の手順で環境を構築し、テストを行います。 EKSの環境構築 Agonesのインストール Openmatchのインストール 動作確認 1.EKSの環境構築 EKSの構築方法については、 AWS 公式ドキュメントの 「Amazon EKS の開始方法」 に従って以下の手順を実施します。 EKS展開用のネットワーク環境を作成する Kubernetes クラスタ を作成する クラスタ のノードグループを作成する これから、EKSの構築を実施します。 a.EKS展開用のネットワーク環境を作成する 今回は検証目的のため、 公式サイト で推奨されるネットワーク アーキテクチャ (PrivateとPublicの構造)は使用せず、2つのPublicネットワークのみを設定します。 実施結果を確認します。 VPC CIDR: 192.168.0.0/16 Subnet: $ aws ec2 describe-subnets \ --filters "Name=vpc-id,Values=[VPC ID]" \ --query 'Subnets[*].{SubnetId: SubnetId,AvailabilityZone: AvailabilityZone,CidrBlock: CidrBlock}' \ --output table -------------------------------------------------------------------- | DescribeSubnets | +------------------+------------------+----------------------------+ | AvailabilityZone | CidrBlock | SubnetId | +------------------+------------------+----------------------------+ | ap-northeast-1a | 192.168.0.0/24 | パブリックサブネットID1 | | ap-northeast-1c | 192.168.1.0/24 | パブリックサブネットID2 | +------------------+------------------+----------------------------+ b. Kubernetes クラスタ を作成する ここでは、 クラスタ を作成する方法として「eksctl」と「マネジメントコンソール」の2つの選択肢があります。 初心者がステップ バイス テップで進めたい場合は後者のマネジメントコンソールを使用することをおすすめしますが、今回は手間を省略するためにeksctlコマンドを使用して クラスタ を作成します。 以下のコマンドを使用して クラスタ を作成しますが、「 クラスタ 名」・「パブリックサブネットID1」・「パブリックサブネットID2」はそれぞれ実際の クラスタ 名とパブリックサブネットIDに置き換えてください # コマンドガイドライン ## https://eksctl.io/usage/vpc-configuration/#use-existing-vpc-other-custom-configuration eksctl create cluster --name 「クラスタ名」\ --region ap-northeast-1 \ --without-nodegroup \ --vpc-public-subnets=[パブリックサブネットID1],[パブリックサブネットID2] c. クラスタ のノードグループを作成する NodeGroupを AWS コンソールを介して作成する場合、まず一連のロールを設定する必要があります。手続きを簡略化するために、今回も引き続きeksctlコマンドを使用してNodeGroupを作成します。 以下のコマンドを使用してNodeGroupを作成します。「 クラスタ 名」「ノードグループ名」はそれぞれ希望する名前に置き換えてください。 ※次にOpen Matchをインストールしますが、インストールコマンドを簡略化するために、デフォルトの設定を使用します。このため、最低でも3つ以上のCPUが必要となりますので、今回はt3.xlargeタイプのサーバを選択します。 # コマンドガイドライン ## https://eksctl.io/usage/managing-nodegroups/ eksctl create nodegroup \ --cluster 「クラスタ名」 \ --region ap-northeast-1 \ --name 「ノードグループ名」 \ --node-type t3.xlarge \ --nodes 2 \ --nodes-min 2 \ --nodes-max 2 ここまでは、EKSの環境設定が完了しました。 次に、AgonesとOpen Matchをインストールします。 2.Agonesのインストール Agonesをインストールする方法については、基本的には Agonesの公式ドキュメンテーション に従います。 インストール方法としては、 yaml インストールとHelmインストールの2つの方法がありますが、以下の理由からHelmを使用してAgonesをインストールする方法を選択しました。 ① Helmを使用すると、 Kubernetes アプリケーションのデプロイを再現可能で一貫性のある形で行うことができます。これにより、運用上の一貫性と信頼性を確保する ② Helmはチャートと呼ばれるパッケージ形式を使用するため、チーム間での共有や再利用が容易になる ③ 複雑なアプリケーションを構成するためのパラメータ化された設定を提供します。これにより、カスタマイズや再構成が容易になる ④ Helmはデプロイのバージョン管理、つまりリリースの管理を容易にします。これにより、バージョン間の移行がスムーズに行う Agonesのインストールコマンドは以下のとおりです。 [my-release]はリリースの名前で、適宜置き換えてください。 # 公式のstable Helmリポジトリを追加 helm repo add agones https://agones.dev/chart/stable # Helmを使ってAgonesをインストール helm install [my-release] --namespace agones-system agones/agones 上記のコマンドを使用すると、公式のstable Helm リポジトリ を追加し、Helmを使ってAgonesをインストールできます。[my-release]の部分にはインストールするリリースの名前を指定します。 また、Agonesは agones-system という名前の空間にインストールされます。 3.Openmatchのインストール Openmatchの公式ドキュメンテーション に従ってOpenmatchをインストールします。 OpenMatchのインストールコマンドは以下のとおりです。 [my-release]はリリースの名前で、適宜置き換えてください。 # Open MatchのHelmリポジトリを追加 helm repo add open-match https://open-match.dev/chart # Helmを使ってOpen Matchをインストール helm install [my-release] open-match/open-match --namespace open-match --set open-match-core.enabled=true 上記のコマンドを使用すると、Open MatchのHelm リポジトリ を追加し、Helmを使ってOpen Matchをインストールできます。 また、Open Matchはopen-matchという 名前空間 にインストールされ、 open-match-core が有効になります。 4.動作確認 環境の構築が完了したので、AgonesとOpen Matchが正しくインストールされていることを確認するために、AgonesのデモとOpen Matchのデモを使用してテストを行います。 環境の構築が完了している場合は、以下の手順を実行してテストを行ってください。 Agonesデモのデプロイと動作確認 Agones公式ドキュメントの ゲームサーバ作成手順 に従ってサンプルゲームサーバーをデプロイします。これにより、Agonesが正常に動作するかを確認できます。 サンプルゲームサーバーをデプロイする kubectl create -f https://raw.githubusercontent.com/googleforgames/agones/release-1.32.0/examples/simple-game-server/gameserver.yaml GameServerの状態を確認する kubectl get gs 確認ポイントとして、GameServerのステータスが Ready になっていることを確認してください。 $ kubectl get gs NAME STATE ADDRESS PORT NODE AGE simple-game-server-h4h2w Ready ec2-18-182-6-144.ap-northeast-1.compute.amazonaws.com 7393 ip-192-168-0-130.ap-northeast-1.compute.internal 18s Open Matchデモのデプロイと動作確認 Openmatch 公式デモの作成手順 に従ってOpen Matchのデモをデプロイします。これにより、Open Matchが正常に動作するかを確認できます。 サンプルマッチメイキングフロントエンドとバックエンドをデプロイする kubectl create namespace open-match-demo kubectl apply --namespace open-match-demo \ -f https://open-match.dev/install/v1.7.0/yaml/02-open-match-demo.yaml マッチメイキングフロントエンドとバックエンドの状態を確認する kubectl get pods -n open-match-demo 上記の手順に従い、Open Matchのサンプルマッチメイキングでフロントエンドとバックエンドをデプロイし、 kubectl get pods -n open-match-demo コマンドでポッドの状態を確認します。 確認ポイントとして、 ① すべてのポッドのステータスが Running になっていることを確認する $ kubectl get pods -n open-match-demo NAME READY STATUS RESTARTS AGE om-demo-7bf887b848-75nvn 1/1 Running 0 35s om-function-58b954cf5f-4gqr4 1/1 Running 0 35s om-function-58b954cf5f-ll6pz 1/1 Running 0 35s om-function-58b954cf5f-nqrks 1/1 Running 0 35s ② PortForward を使用してデモページにアクセスできることを確認する $ kubectl port-forward --namespace open-match-demo service/om-demo 51507:51507 Forwarding from 127.0.0.1:51507 -> 51507 Forwarding from [::1]:51507 -> 51507 終わりに 以上で、EKSの環境構築およびAgones/Open Matchのインストールプロセスの説明が完了しました。また、公式デモを使用してテストを行いました。 次に、マッチメイキングサービスの構築とGameClientの実装について詳しく説明します。 Part2 と Part3 では、マッチメイキングサービスのカスタマイズ方法とGameClientの開発について取り上げます。 引き続き、お楽しみにしてください! 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/getting-started-console.html https://open-match.dev/site/docs/ https://agones.dev/site/docs/ 執筆: @chen.sun 、レビュー: @yamashita.yuki ( Shodo で執筆されました )
アバター
こんにちは、金融ソリューション事業部の孫です。 前回の Part1 記事に続きまして Part2 では、OpenMatchとAgonesを使用して、柔軟性がありスケーラブルなゲームマッチングとゲームサーバー管理システムの構築方法を詳しく説明しました。 この記事(Part3)では、UnrealEngineを利用して、オンラインマルチプレーヤーゲームのデモを完成させます。 さらに、 Part2 で開発したマッチメイキングサービスをUEクライアント(UnrealEngine GameClient)に統合します。 全体 アーキテクチャ は以下の図の通りです。 UEクライアントの実装について、前の 記事 で作成したデモを基にさらに拡張します。 プレーヤーマッチング機能とサーバー管理機能を追加することで、この度のクライアントの実現を目指します。 実施手順 1.前記事のデモにマッチング機能の追加 マッチング機能のボタンの追加 Frontend APIの呼びだす機能実装 2. Agones SDKを呼び出してGameServerの終了機能の実装 ユーザー数の管理機能の追加 Agones SDKを利用してGameServerの終了実装 3.パッケージ化したUEサーバーでAgones Fleetの作成 4.動作確認 終わりに 参考 実施手順 以下の順序でシステムを構築します: 前記事 のデモにマッチング機能の追加 Agones SDK を呼び出してGameServerの終了機能の実装 パッケージ化したUEサーバーでAgones Fleetの作成 動作確認 1. 前記事 のデモにマッチング機能の追加 この前の記事 UE5ネットワーク同期のC++実装例 では、プレーヤーが「Play」ボタンをクリックすると、UEゲームサーバーに接続してゲーム体験を開始する機能をすでに完成させています。 次に、マッチング機能のボタンを追加し、このボタンをクリックするとOpenMatchの Frontend API を呼び出してマッチング要求をリク エス トします。 マッチング機能のボタンの追加 以下の図のように、テキストエリア(Region入力用)とボタン(「Match」)をUnrealEngineのBluePrint Widget UIに配置します。 Frontend API の呼びだす機能実装 以下のモジュールをxxx.Build.csファイルに追加する 今回では、 Agones / HTTP / Json / JsonUtilities の4つのモジュールを使用します。 各モジュールは次のような役割を果たします。 Agones モジュールは Agones の SDK を使うために使用する HTTP モジュールは Frontend API の http リク エス トを送信するために使用する Json 、 JsonUtilities モジュールは http からの返却 Json データを解析するために使用する //xxx.Build.cs using UnrealBuildTool; //:(省略) PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "EnhancedInput", "Agones", "HTTP", "Json", "JsonUtilities" }); //:(省略) LoginHUDWidget.h を編集する まずは、配置したWidgetsをバインドすることから始めます。 ※注意:BluePrint内の Widget 名は LoginHUDWidget.h ファイル内の変数名と同じでなければならず、 meta = (BindWidget) タグを追加して Widget をバインドします それを完了したら、httpモジュールをincludeした上でコールバック関数を作成します。 //LoginHUDWidget.h // "Http.h"ヘッダーファイルの追加 #include "Http.h" UCLASS() class TEST_DESERVER_API ULoginHUDWidget : public UUserWidget { GENERATED_BODY() public: //:(省略) UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UEditableText* regionName; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UTextBlock* matchLabel; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UButton* matchBtn; private: FHttpModule* Http; void OnFetchGameServerResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); } LoginHUDWidget.cpp にマッチングボタンのクリックロジックを実装する //LoginHUDWidget.cpp // "Json.h", "JsonUtilities.h"ヘッダーファイルの追加 #include "Json.h" #include "JsonUtilities.h" // 構造関数内で関連コントロールとHttpモジュールを初期化します // MatchボタンにOnMatchmakingButtonClickedトリガー関数をバインドします void ULoginHUDWidget::NativePreConstruct() { Super::NativePreConstruct(); //:(省略) matchLabel->SetText(FText::FromString("Match")); FScriptDelegate MatchmakingDelegate; MatchmakingDelegate.BindUFunction(this, "OnMatchmakingButtonClicked"); matchBtn->OnClicked.Add(MatchmakingDelegate); //:(省略) Http = &FHttpModule::Get(); } // OnMatchmakingButtonClickedトリガー関数の処理ロジックを追加します void ULoginHUDWidget::OnMatchmakingButtonClicked() { statusLabel->SetText(FText::FromString("Start Matching!! Please wait")); TSharedRef<IHttpRequest, ESPMode::ThreadSafe> FetchGameServerHttpRequest = Http->CreateRequest(); FString frontendUrl = "127.0.0.1:8081/play/" + FString(regionName->GetText().ToString()); FetchGameServerHttpRequest->SetVerb("GET"); FetchGameServerHttpRequest->SetURL(frontendUrl); FetchGameServerHttpRequest->SetHeader("Content-Type", "application/json"); FetchGameServerHttpRequest->OnProcessRequestComplete().BindUObject(this, &ULoginHUDWidget::OnFetchGameServerResponse); FetchGameServerHttpRequest->ProcessRequest(); }; // Httpのコールバック関数の処理ロジックを追加します void ULoginHUDWidget::OnFetchGameServerResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) { if (bWasSuccessful) { TSharedPtr<FJsonObject> JsonObject; TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); if (FJsonSerializer::Deserialize(Reader, JsonObject)) { FString IpAddress = JsonObject->GetStringField("ip"); FString Port = JsonObject->GetStringField("port"); FString LevelName = IpAddress + ":" + Port; statusLabel->SetText(FText::FromString(LevelName)); playBtn->SetVisibility(ESlateVisibility::Visible); } } } // OnPlayGameButtonClickedトリガー関数の処理ロジックを変更します // OpenMatchから返されたGameServerアドレスを接続ターゲットとして設定します void ULoginHUDWidget::OnPlayGameButtonClicked() { //:(省略) FString LevelName = statusLabel->GetText().ToString(); //:(省略) } 2. Agones SDK を呼び出してGameServerの終了機能の実装 すべてのプレイヤーがオフラインになった場合、AgonesSDKを呼び出して利用済のGameServerを削除した後、新しいGameServerを再作成します。 ユーザー数の管理機能の追加 UnrealEngine Gamemodeクラスにおいて PlayerNum 変数を追加して現在のプレーヤー数を保存します。 また、少なくとも1人のプレーヤーがこのGameServerに接続したことがあることを判断するため、 HasPlayerConnected のBool変数を追加します。 前の 記事 で、UnrealEngineによく使われるサーバー側の関数について既にいくつか紹介しました。 今回はその中の PostLogin 、 Logout 関数を用いることで、プレーヤー数の簡易的な統計データが取得します。 //xxxGameMode.h public: //:(省略) virtual void PostLogin(APlayerController* NewPlayer) override virtual void Logout(AController* Exiting) override private: //:(省略) int32 PlayerNum; bool HasPlayerConnected; //xxxGameMode.cpp // 構造関数で変数を初期化します xxxGameMode::xxxGameMode(){ //:(省略) int32 PlayerNum = 0; bool HasPlayerConnected = false; } void xxxGameMode::PostLogin(APlayerController* NewPlayer) { Super::PostLogin(NewPlayer); PlayerNum++; HasPlayerConnected = true; if (!HasPlayerConnected) { HasPlayerConnected = true; GetWorldTimerManager().SetTimer(CountDownPlayerNumHandle, this, &xxxGameMode::TickCount, 1.0f, true); } } void xxxGameMode::Logout(AController* Exiting) { Super::Logout(Exiting); PlayerNum--; } Agones SDK を利用してGameServerの終了実装 UnrealEngine Gamemodeクラスにおいて、Agones SDK のShutdown()関数を呼び出してGameServerがシャットダウンされます。 ※Agonesは一 定量 のGameServerを維持し、GameServerが閉じられると新たなGameServerの生成がトリガーされます。 //xxxGameMode.h public: //:(省略) virtual void Tick(float DeltaTime) override private: //:(省略) //Agones SDKの処理結果に対応するレスポンス関数 //成功処理後のレスポンス関数 void HandleShutdownSuccess(const FEmptyResponse& Response); //成功失敗時のレスポンス関数 void HandleShutdownError(const FAgonesError& Error); void TickCount(); //xxxGameMode.cpp void Atest_DEServerGameMode::HandleShutdownSuccess(const FEmptyResponse& Response) {   //デモのため、実際の処理を行いません UE_LOG(LogTemp, Log, TEXT("Game server successfully shutdown")); } void Atest_DEServerGameMode::HandleShutdownError(const FAgonesError& Error) { //デモのため、実際の処理を行いません UE_LOG(LogTemp, Error, TEXT("shutting down failed: %s"), *Error.ErrorMessage); } void xxxGameMode::TickCount() { Super::Tick(DeltaTime); if (HasPlayerConnected && PlayerNum <= 0) { FShutdownDelegate SuccessDelegate; SuccessDelegate.BindUFunction(this, FName("HandleShutdownSuccess")); FAgonesErrorDelegate ErrorDelegate; ErrorDelegate.BindUFunction(this, FName("HandleShutdownError")); GetWorldTimerManager().ClearTimer(CountDownPlayerNumHandle); AgonesSDK->Shutdown(SuccessDelegate, ErrorDelegate); } } 3.パッケージ化したUEサーバーでAgones Fleetの作成 UnrealEngine Editorを使用して、 Linux プラットフォーム向けのDedicated Serverをパッケージ化します。 以下の図のように、ターゲットプラットフォーム Linux -> Development -> Linux(server) を選択し、パッケージします。 ※ Windows OSではもしかするとターゲットプラットフォームの選択肢に Linux が存在しないかもしれません。 これは、 Linux プラットフォームのサポートが設定されていないためです。 具体的な設定方法については、 こちら を参照してください。 パッケージ化が完了したら、 Part2 で各モジュールをデプロイしたのと同じ手順で、EKSにGameServerをデプロイします。 ローカルにおいてDockerImageを コンパイル する 下記のDockerfileをパッケージ化の際に指定された保存先のルート ディレクト リに配置し、Dockerイメージ作成コマンドを実行します。 ## DockerFile ## 「AgonesOMServer」を実際のプロジェクト名に置き換えます FROM ubuntu:20.04 RUN apt-get update && apt-get install -y \ libxcursor1 \ libxrandr2 \ libxinerama1 \ libxi6 \ libgl1-mesa-glx \ && rm -rf /var/lib/apt/lists/* RUN addgroup --gid 1000 gameserver && \ adduser --gid 1000 --uid 1000 --shell /usr/sbin/nologin --home /home/gameserver --gecos "" --disabled-login --disabled-password gameserver COPY --chown=gameserver:gameserver ./LinuxServer /home/gameserver/LinuxServer RUN chmod -R 770 /home/gameserver/LinuxServer WORKDIR /home/gameserver/LinuxServer USER gameserver EXPOSE 7777/udp ENTRYPOINT ["/home/gameserver/LinuxServer/「AgonesOMServer」.sh"] ## Docker image build command $ docker build -t localimage/ue_server:0.1 . DockerImageを Amazon ECRにアップロードする Part2 と同様に、ue_serverという名前の Amazon ECRプライベー トリポジ トリを新規作成します。 次に、Dockerイメージを Amazon ECRにアップロードします。 $ docker tag localimage/ue_server:0.1 {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/ue_server:0.1 $ docker push {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/ue_server:0.1 Amazon EKSにおいてAgones Fleetを作成する ローカルでAgones Fleetの作成用のfleet. yaml ファイルを作成します。 ## fleet.yaml --- apiVersion: "agones.dev/v1" kind: Fleet metadata: name: fleet-ap-northeast-1 spec: replicas: 2 scheduling: Packed strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 25% template: metadata: namespace: meta-poc labels: region: ap-northeast-1 spec: players: initialCapacity: 4 ports: - name: default containerPort: 7654 health: initialDelaySeconds: 30 periodSeconds: 60 template: metadata: namespace: meta-poc labels: region: ap-northeast-1 spec: containers: - name: ue5-server image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/ue_server:0.1 resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "1Gi" cpu: "1" fleet. yaml ファイルを Amazon EKSに適用します。 $ kubectl apply -f fleet.yaml 4.動作確認 UnrealEngine Editorを使用して4つのクライアントを起動する 具体的な操作手順は、以下の図のとおり New Editor Window(PIE) -> Number Of Players: 4 -> Play Standalone を設定する 以下のコマンドでAgones GameServerのステータス監視を起動する $ watch kubectl get gs OpenMatch Frontend Serviceをローカルに マッピング する # OpenMatch Frontend サービスがローカルの8081ポートにマッピングされます kubectl port-forward services/frontend-ednpoint 8081:80 マッチング機能をテストする 確認ポイント: 4つのクライアントが同じGameServerアドレスにマッチングされていること GameServerのステータスがAllocatedになっていること DedicatedServerへの接続をテストする 確認ポイント: Playボタンをクリックした後、Dedicated Serverに成功したこと Characterの頭上に表示されているNickNameが、クライアントが入力したNickNameと同じであること AgonesによるGameServerのシャットダウンをテストする 確認ポイント: 全てのGameClientを閉じた後、以前Allocated状態だったサーバーが削除されること その代わりに新たにReady状態のGameServerが作成されること 終わりに これで、マッチング機能を備え、Agonesで Unreal Engine DedicatedServerをスケジューリングするオンライン マルチプレイヤー ゲームの作成が完了しました。 この一連の記事では、EKSを使って Kubernetes の特性を活用し、 Unreal Engine のDedicated Serverを管理する方法について深く探求しました。 その中で、マッチングシステムとしてOpenMatchを使用し、その強力な拡張性と設定性を活用してニーズに合ったマッチングルールをカスタマイズしました。同時に、Agonesを用いてゲームサーバーのスケジューリングを行い、効率的で安定したサーバー管理を実現しました。 次は、さらなるゲームに関連するインフラ設計のソリューションを見つけ出し、ゲーム体験をさらに向上させる方法を継続に探求します。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 https://docs.unrealengine.com/5.2/ja/linux-game-development-in-unreal-engine/ https://agones.dev/site/docs/reference/fleet/ 執筆: @chen.sun 、レビュー: @yamashita.yuki ( Shodo で執筆されました )
アバター
こんにちは、金融ソリューション事業部の孫です。 シリーズの最初の記事( Part1 )では、 Kubernetes の強力な機能を活用するためにEKS(Elastic Kubernetes Service)をどのように設定するかについて詳しく説明しました。 EKSの設定が成功した後、ゲームのインフラでよく使われるAgonesとOpen Matchをインストールしました。 また、公式デモでテストを行い、インストールが正しく行われたことを確認しました。 Kubernetes に基づくAgonesとOpen Matchという2つの コンポーネント について、理解していない方がいらっしゃるかもしれません。 そのため、Part2では、まずAgonesとOpen Matchの基本的な概念を簡単に紹介します。 次に、実践でマッチングシステムのデモを作成し、どのようにAgonesとOpen Matchを組み合わせて効率的で柔軟なDedicated Serverの管理とマッチングを実現するかを示します。 Agonesの紹介 OpenMatchの紹介 OpenMatchのマッチメイカーを作成する一般的なフロー OpenMatchとAgonesの統合 実践:ゲームマッチングシステムのデモ作成 マッチングルールの定義 事前準備 マッチング関数の作成 GameFrontend Director Match Profilesの作成 Agones Allocator ServiceによるOpenMatchの統合 AgonesのAllocate機能のパッケージ化 Allocator ServiceパッケージをOpenMatchに統合 MatchFunction デプロイと動作確認 ローカルにおいてDockerImageのコンパイル DockerImageをAmazon ECRにアップロード モジュールをEKSにデプロイ 動作確認 終わりに 参考 Agonesの紹介 Agonesは、 Google Cloudと Ubisoft が共同で開発されて、 Ubisoft 内の大規模な マルチプレイヤー オンラインゲーム(MMO)で利用されているソリューションです。 また、プログラミングが OSS で公開されている為安全に独自ネットワーク内で動作させることが可能です。 Agonesは、 Kubernetes の特性を活用してゲームサーバーを効率的に、そしてスケーラブルに運用および管理する方法を提供します。 Agonesの主要な コンポーネント には、GameServer、Fleetがあります。 Agonesでは、開発者は簡単な Kubernetes のコマンドを用いてGameServerを作成および管理することが可能で、これによりゲームサーバーの管理の複雑さが大幅に低減されます。 Agonesがゲームサーバーのライフサイクルを管理する中で、以下の6つのステージを定義しています。 Agonesはゲームサーバーのステージに応じて、適切な処理を実行します。 Scheduled (予定):GameServerがスケジュールされ、Nodeに割り当てられる Requested (要求): Kubernetes のPodが作成され、GameServerが作成される Starting (起動中):GameServerが起動し、プレイヤーがゲームに接続できる状態になる前の準備状態 Ready (準備完了):GameServerがアクティブ状態で、プレイヤーが接続できる Allocated (割り当て済み):プレイヤーがGameServerに接続し、リソースが確保されている Shutdown (シャットダウン):すべてのプレイヤーが切断され、GameServerがシャットダウンする OpenMatchの紹介 OpenMatchは、Frontend API 、Backend API 、Query API 、Functionなど、複数の コンポーネント から成り立っています。 これらの コンポーネント はそれぞれが独自の役割を果たしながら協調して働き、マッチングシステムを構築します。 OpenMatchのマッチングフローは以下のとおりです。 プレイヤーがFrontend API にマッチングリク エス トを送信する Frontend API はそのリク エス トを内部の状態でストアに保存する マッチング関数がQuery API を使用して状態ストアから条件に合うプレイヤーを問い合わせする マッチング関数がBackend API にマッチング結果を返す Backend API がプレイヤーにマッチング結果を返す OpenMatchのマッチメ イカ ーを作成する一般的なフロー OpenMatchのマッチメ イカ ーを作成するには主に三つのステップがあります。 マッチングルールを定義する マッチング関数を作成する マッチメ イカ ーの設定および運用を行う まず、マッチングルールを定義します。 このルールはマッチングロジックを反映したもので、プレイヤーのレベル、地域、スキルなどを含めます。 次に、マッチング関数を作成します。 この関数はQuery API を使用してマッチングルールに合致するプレイヤーを問い合わせ、そのマッチング結果をBackend API に返します。 最後に、マッチメ イカ ーの設定と運用を行います。OpenMatchは多くの設定オプションを提供しており、それらはニーズに応じて設定できます。 OpenMatchとAgonesの統合 OpenMatchとAgonesの統合は、効率的なゲームマッチングシステムを構築する上での重要な部分であり、主に二つのプロセスが関与しています。 OpenMatchのマッチング関数からAgonesのGameServerを呼びだすところ GameServerのライフサイクルを管理するところ OpenMatchはプレイヤーのマッチングを担当し、一方AgonesはGameServerのライフサイクルの管理を担当します。 これら二つの組み合わせにより、プレイヤーのニーズに応じてGameServerを動的に作成および割り当てることができます。 OpenMatchのマッチング関数内で、Agones SDK を通じて新しいGameServerを作成できます。 しかし、ほとんどの場合新しいGameServerを作成するだけではなく既存のGameServerをスケジュールし、割り当てることがより重要です。 GameServerのパフォーマンス、負荷、地理的な位置などを評価し、最適なGameServerを見つける必要があります。 適切なGameServerを見つけたら、そのアドレスをプレイヤーに返します、プレイヤーはそのアドレスを使用してGame Serverに接続しゲーム体験を始めます。 ここで終わりではありませんが、GameServerの状態を監視し、必要に応じて調整する必要があります。 例えば、GameServerの負荷が高すぎる場合、新しいGameServerを作成して負荷を分散できます。 一方、GameServerのプレイヤー数が減少した場合、それをシャットダウンしてリソースを節約することも可能です。 実践:ゲームマッチングシステムのデモ作成 先に紹介したOpenMatch マッチメーカー の作成プロセスに従って、デモを作成し始めます。 マッチングルールの定義 このデモでは、ユーザーのスキルレベルとレイテンシを基にスコアを算出し、同一リージョン内でスコアが近いユーザーをマッチングするというルールを実装します。 それぞれのマッチングルームは4人のプレイヤーで構成され、スコアが近いユーザー同士は一緒になります。 以下では、このマッチングの詳細や手順、そして適用する アルゴリズム について具体的に説明します。 チケット詳細 Ticket Details チケットは以下図のGameFrontendによって作成され、OpenMatchのFrontendにプッシュされる情報です。 チケットにはプレイヤーに関する情報が含まれており、マッチングの際に使用されます。 以下「GameFrontend」、「Director」、「MatchFunction」章の実装で利用されます。 今回のデモでは、チケット詳細には以下の要素が含まれています。 タグ tag :タグを使ってチケットを分類することが可能で、それによりマッチングシステムはより効率的に対応するキューを見つけることができる 今回はゲームモード Game Mode という設計を前提に実装するため、タグはmode.sessionとする リージョン Region :これはプレイヤーがいる地理的な地域を示しているが、今回はap-northeast-1、ap-northeast-3とする スキルレベル Skill Level :これはプレイヤーのスキルレベルを示しており、0.0から2.0の範囲で設定される レイテンシ Latency :これはプレイヤーのネットワーク遅延を示している ※ほとんどの人は0に近いですが、ネットワークの信頼性をシミュレートするために、一部の人は無限大に設定されている マッチング機能の基準 MatchFunction Criteria 今回のデモでは、マッチングの基準を以下に定義します。 以下「Director」、「MatchFunction」章の実装で利用されます。 まずはプレイヤーの地理的な地域とゲームモードを基準に、チケットプールを作成する 次に、各プレイヤーに対して score = skill - (latency / 1000.0) の アルゴリズム を使用してスコアを算出する そして、スコアに基づいてルームにプレイヤーを配置する 高スキル、低レイテンシのユーザーは同じルームに割り当てられる 1つのマッチに参加できるプレイヤーの上限は、4人と定められている ディレクタープロファイル Director Profiles ディレクタープロファイルはディレクターが生成するオブジェクトで、マッチのリク エス トに使用されます。 以下「Director」章の実装で利用されます。 今回のデモでは、ディレクターは5秒ごとにプロファイルを生成し、マッチをリク エス トする。 また、ディレクターはプレイヤーの地理的な地域に応じて、対応する地理的な地域のGameServerをプレイヤーに割り当てる 事前準備 OpenMatchの リポジトリ をローカル環境にクローンする ベースとなるコードは、tutorials/matchmaker101のパスに存在する git clone https://github.com/googleforgames/open-match.git Golang による実装のため、適切な IDE を設定する この記事では、 Visual Studio Code にGo plugin(v.39.0)をインストールした環境で開発を進めた Docker環境を準備する 対象の環境でDockerをセットアップするには、 Dockerのインストール のドキュメントを参照してください マッチング関数の作成 Openmatch公式ドキュメントの Tutorial をベースに、ステップ バイス テップでGameFrontend、Director、MatchFunctionといった コンポーネント を設計・作成します。 ※TutorialはOpenmatchの フレームワーク プログラムで、マッチングロジックは上記のルールに従って独自に実装する必要があります。 以下の図は、Openmatch の全体的な アーキテクチャ で、赤く囲まれた部分は独自に実装すべき部分です。 GameFrontend チケット生成関数を実装する ※ベースコード: https://github.com/googleforgames/open-match/blob/main/tutorials/matchmaker101/frontend/ticket.go 「マッチングルールの定義」章で定義したチケット詳細 Ticket Details のルールに従って、 mode.session という名前のタグを定義して、次にランダムにスキルとレイテンシの値を設定します。 リージョンの設定については、具体的なユーザーがどこから接続するかは確定していないため、外部から値を取得するように定義します。この情報はクライアントから取得する必要があります。 # ticket.go import( "open-match.dev/open-match/pkg/pb" // 必要なパッケージ追加 "math/rand" "time" ) func makeTicket(region string) *pb.Ticket { modes := []string{"mode.session"} ticket := &pb.Ticket{ SearchFields: &pb.SearchFields{ Tags: modes, DoubleArgs: CreateDoubleArgs(), StringArgs: map[string]string{ "region": region, }, }, } return ticket } func CreateDoubleArgs() map[string]float64 { rand.Seed(time.Now().UTC().UnixNano()) skill := 2 * rand.Float64() latency := 50.0 * rand.ExpFloat64() return map[string]float64{ "skill": skill, "latency": latency, } } echo フレームワーク を使用してFrontendのAPIServerを実装する ※ベースコード: https://github.com/googleforgames/open-match/blob/main/tutorials/matchmaker101/frontend/main.go この API ServerのURLは: GET /play/:region で、regionパラメータを持っています。 # frontend/main.go import( "github.com/labstack/echo" ) type matchResponce struct { IP string `json:"ip"` Port string `json:"port"` Skill string `json:"skill"` Latency string `json:"latency"` Region string `json:"region"` } var fe pb.FrontendServiceClient var matchRes = &matchResponce{} func main() { //:(ベースコートを省略する) // fe = pb.NewFrontendServiceClient(conn)以降のコードをコメントアウト e := echo.New() e.GET("/play/:region", handleGetMatch) e.Start(":80") } func handleGetMatch(c echo.Context) error { // Create Ticket. region := c.Param("region") req := &pb.CreateTicketRequest{ Ticket: makeTicket(region), } matchRes.Skill = fmt.Sprintf("%f", req.Ticket.SearchFields.DoubleArgs["skill"]) matchRes.Latency = fmt.Sprintf("%f", req.Ticket.SearchFields.DoubleArgs["latency"]) matchRes.Region = req.Ticket.SearchFields.StringArgs["region"] resp, err := fe.CreateTicket(context.Background(), req) if err != nil { log.Fatalf("Failed to CreateTicket, got %v", err) return c.JSON(http.StatusInternalServerError, matchRes) } // Polling TicketAssignment. deleteOnAssign(fe, resp) return c.JSON(http.StatusOK, matchRes) } func deleteOnAssign(fe pb.FrontendServiceClient, t *pb.Ticket) { //:(ベースコートを省略する) if got.GetAssignment() != nil { log.Printf("Ticket %v got assignment %v", got.GetId(), got.GetAssignment()) conn := got.GetAssignment().Connection slice := strings.Split(conn, ":") matchRes.IP = slice[0] matchRes.Port = slice[1] break } } Director Match Profilesの作成 ※ベースコード: https://github.com/googleforgames/open-match/blob/main/tutorials/matchmaker101/director/profile.go 「マッチングルールの定義」章で定義した マッチング機能の基準 MatchFunction Criteria のチケットプール作成ルールに従って、 TagPresentFilters および StringEqualsFilter を定義します。 「マッチングルールの定義」章で定義したディレクタープロファイル Director Profiles のゲームサーバー選択ルールに従って、 profile.Extensions を定義します。 # profile.go type AllocatorFilterExtension struct { Labels map[string]string `json:"labels"` Fields map[string]string `json:"fields"` } func generateProfiles() []*pb.MatchProfile { var profiles []*pb.MatchProfile regions := []string{"ap-northeast-1", "ap-northeast-3"} for _, region := range regions { profile := &pb.MatchProfile{ Name: fmt.Sprintf("profile_%s", region), Pools: []*pb.Pool{ { Name: "pool_mode_" + region, TagPresentFilters: []*pb.TagPresentFilter{ {Tag: "mode.session"}, }, StringEqualsFilters: []*pb.StringEqualsFilter{ {StringArg: "region", Value: region}, }, }, }, } // build filter extensions filter := AllocatorFilterExtension{ Labels: map[string]string{ "region": region, }, Fields: map[string]string{ "status.state": "Ready", }, } // to protobuf Struct labelsStruct := &structpb.Struct{Fields: make(map[string]*structpb.Value)} for key, value := range filter.Labels { labelsStruct.Fields[key] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: value}} } fieldsStruct := &structpb.Struct{Fields: make(map[string]*structpb.Value)} for key, value := range filter.Fields { fieldsStruct.Fields[key] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: value}} } // put data to the protobuf Struct filterStruct := &structpb.Struct{Fields: map[string]*structpb.Value{ "labels": {Kind: &structpb.Value_StructValue{StructValue: labelsStruct}}, "fields": {Kind: &structpb.Value_StructValue{StructValue: fieldsStruct}}, }} // to google.protobuf.Any object filterAny, err := ptypes.MarshalAny(filterStruct) if err != nil { panic(err) } profile.Extensions = map[string]*any.Any{ "allocator_filter": filterAny, } profiles = append(profiles, profile) } return profiles } Agones Allocator ServiceによるOpenMatchの統合 Agonesを統合し、マッチング結果に対応するGameServerアドレスを割り当てます。 プレイヤーはこのアドレスを通じて対応するGameServerに接続します。 Agones Allocate機能の実装は独自のものであり、OpenMatchの チュートリアル には基礎となるコードが存在しません。 そのため、directorフォルダの下に新規ファイルとしてallocator_director.goを作成します。 AgonesのAllocate機能のパッケージ化 Agonesが提供する Allocator Service を使って対応するGameServerを取得します。 デフォルトのクライアント証明書を取得する Allocator Service はmTLS認証モードを使用しており、これにより証明書を使用してサービスに接続することは必須になります。 証明書は既にhelmインストール時に作成されています。 独自の証明書を使用することも可能で、具体的な設定は こちら を参照してください。 下記のコマンドを実行してデフォルトのクライアント証明書を取得する ※筆者は Mac の環境でコマンドを実行しています。 Linux の環境であれば、 base64 -D の代わりに base64 -d コマンドを使用してください。 # MACのコマンド kubectl get secret allocator-client.default -n default -ojsonpath="{.data.tls\.crt}" | base64 -D > "client.crt" kubectl get secret allocator-client.default -n default -ojsonpath="{.data.tls\.key}" | base64 -D > "client.key" kubectl get secret allocator-tls-ca -n agones-system -ojsonpath="{.data.tls-ca\.crt}" | base64 -D > "tls-ca.crt" 取得したクライアント証明書を配置します。 上記でダウンロードした証明書を特定のパスに保存し、Pathとして定義します。 ここでは、 allocator/certfile ディレクト リに保存することを想定しています。 # agones_allocator.go const ( KeyFilePath = "allocator/certfile/client.key" CertFilePath = "allocator/certfile/client.crt" CaCertFilePath = "allocator/certfile/tls-ca.crt" ) Allocator Serviceのクライアントを作成する 外部からAgonesのAllocate機能を呼びだす必要がある場合、 NewAgonesAllocatorClient を呼びだすとクライアントが生成されます。 ※ご注意: コードが長くなりすぎないように、エラー処理に関連するコードは削除しています。 # agones_allocator.go type AgonesAllocatorClientConfig struct { KeyFile string CertFile string CaCertFile string AllocatorServiceHost string AllocatorServicePort int Namespace string MultiCluster bool } type AgonesAllocatorClient struct { Config *AgonesAllocatorClientConfig DialOpts grpc.DialOption } func NewAgonesAllocatorClient() (*AgonesAllocatorClient, error) { config := &AgonesAllocatorClientConfig{ KeyFile: KeyFilePath, CertFile: CertFilePath, CaCertFile: CaCertFilePath, AllocatorServiceHost: AllocatorServiceHost, AllocatorServicePort: AllocatorServicePort, Namespace: "default", MultiCluster: false, } cert, err = ioutil.ReadFile(config.CertFile) key, err = ioutil.ReadFile(config.KeyFile) ca, err = ioutil.ReadFile(config.CaCertFile) dialOpts, err := createRemoteClusterDialOption(cert, key, ca) return &AgonesAllocatorClient{ Config: config, DialOpts: dialOpts, }, nil } func createRemoteClusterDialOption(clientCert, clientKey, caCert []byte) (grpc.DialOption, error) { cert, err := tls.X509KeyPair(clientCert, clientKey) tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true} if len(caCert) != 0 { tlsConfig.RootCAs = x509.NewCertPool() if !tlsConfig.RootCAs.AppendCertsFromPEM(caCert) { return nil, errors.New("only PEM format is accepted for server CA") } } return grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), nil } Allocateのメイン関数を実装する この関数では、まずgrpc( grpc.Dial ) プロトコル を使用して Allocator Service に接続します。 次に、GameServerの選択ルール( assignmentGroup.Assignment.Extensions )を取得します。 このルールに基づいて対応するGameServerを取得し、最終的に各Assignmentの address フィールドにアドレスを付与します。 ※ご注意: コードが長くなりすぎないように、エラー処理に関連するコードは削除しています。 # agones_allocator.go func (c *AgonesAllocatorClient) Allocate(req *pb.AssignTicketsRequest) error { conn, err := grpc.Dial(fmt.Sprintf("%s:%d", c.Config.AllocatorServiceHost, c.Config.AllocatorServicePort), c.DialOpts) defer conn.Close() grpcClient := pb_agones.NewAllocationServiceClient(conn) for _, assignmentGroup := range req.Assignments { filterAny := assignmentGroup.Assignment.Extensions["allocator_filter"] filter := &structpb.Struct{} if err := ptypes.UnmarshalAny(filterAny, filter); err != nil { panic(err) } request := &pb_agones.AllocationRequest{ Namespace: c.Config.Namespace, GameServerSelectors: []*pb_agones.GameServerSelector{ { MatchLabels: filter.Fields["labels"], }, }, MultiClusterSetting: &pb_agones.MultiClusterSetting{ Enabled: c.Config.MultiCluster, }, } resp, err := grpcClient.Allocate(context.Background(), request) if len(resp.GetPorts()) > 0 { address := fmt.Sprintf("%s:%d", resp.Address, resp.Ports[0].Port) assignmentGroup.Assignment.Connection = address } } return nil } Allocator ServiceパッケージをOpenMatchに統合 上記のOpenMatchの アーキテクチャ 図に基づき、Agonesサービスを呼び出してGameServerを取得する コンポーネント はDirectorです。 そのため、呼び出しコードをDirectorに統合する必要があります。 ※ベースコード: https://github.com/suecideTech/try-openmatch-agones/blob/master/OpenMatch/mod_matchmaker101/director/main.go GameServerのassgin関数を修正します。 ここでは、上記で作成した Allocator Service パッケージを使用して実際のGameServerアドレスを取得します。 元のコードでは GameServerAllocations を使ってGameServerアドレスを取得していますが、これはAgonesが推奨している方法ではなく、また後期の拡張にも適していません。 そのため、公式に推奨されている Allocator Service をラップしてGameServerを取得するようにしました。 # director/main.go func assign(be pb.BackendServiceClient, matches []*pb.Match) error { for _, match := range matches { ticketIDs := []string{} for _, t := range match.GetTickets() { ticketIDs = append(ticketIDs, t.Id) } aloReq := &pb.AssignTicketsRequest{ Assignments: []*pb.AssignmentGroup{ { TicketIds: ticketIDs, Assignment: &pb.Assignment{ Extensions: match.Extensions, }, }, }, } client, err := allocator.NewAgonesAllocatorClient() client.Allocate(aloReq) if _, err := be.AssignTickets(context.Background(), aloReq); err != nil { return fmt.Errorf("AssignTickets failed for match %v, got %w", match.GetMatchId(), err) } log.Printf("Assigned server %v to match %v", conn, match.GetMatchId()) } return nil } MatchFunction ユーザーマッチングルールを実装します。 ※ベースコード: https://github.com/suecideTech/try-openmatch-agones/blob/master/OpenMatch/mod_matchmaker101/matchfunction/mmf/matchfunction.go 「マッチングルールの定義」章で定義した マッチング機能の基準 MatchFunction Criteria のユーザーマッチングルールに従って、まずユーザーのスコアを計算し、スコアの大きさに基づいて4人部屋を割り当てます。 # matchfunction.go const ( matchName = "basic-matchfunction" ticketsPerPoolPerMatch = 4 ) func (s *MatchFunctionService) Run(req *pb.RunRequest, stream pb.MatchFunction_RunServer) error { //:(ベースコートを省略する) //poolTickets, err := matchfunction.QueryPools(stream.Context(), s.queryServiceClient, req.GetProfile().GetPools()) p := req.GetProfile() tickets, err := matchfunction.QueryPool(stream.Context(), s.queryServiceClient, p.GetPools()[0]) //:(ベースコートを省略する) idPrefix := fmt.Sprintf("profile-%v-time-%v", p.GetName(), time.Now().Format("2006-01-02T15:04:05.00")) proposals, err := makeMatches(req.GetProfile(), idPrefix, tickets) //:(ベースコートを省略する) } func (s *MatchFunctionService) makeMatches(ticketsPerPoolPerMatch int, profile *pb.MatchProfile, idPrefix string, tickets []*pb.Ticket) ([]*pb.Match, error) { if len(tickets) < ticketsPerPoolPerMatch { return nil, nil } ticketScores := make(map[string]float64) for _, ticket := range tickets { ticketScores[ticket.Id] = score(ticket.SearchFields.DoubleArgs["skill"], ticket.SearchFields.DoubleArgs["latency"]) } sort.Slice(tickets, func(i, j int) bool { return ticketScores[tickets[i].Id] > ticketScores[tickets[j].Id] }) var matches []*pb.Match count := 0 for len(tickets) >= ticketsPerPoolPerMatch { matchTickets := tickets[:ticketsPerPoolPerMatch] tickets = tickets[ticketsPerPoolPerMatch:] var matchScore float64 for _, ticket := range matchTickets { matchScore += ticketScores[ticket.Id] } eval, err := anypb.New(&pb.DefaultEvaluationCriteria{Score: matchScore}) if err != nil { log.Printf("Failed to marshal DefaultEvaluationCriteria into anypb: %v", err) return nil, fmt.Errorf("Failed to marshal DefaultEvaluationCriteria into anypb: %w", err) } newExtensions := map[string]*anypb.Any{"evaluation_input": eval} newExtensions for k, v := range origExtensions { newExtensions[k] = v } matches = append(matches, &pb.Match{ MatchId: fmt.Sprintf("%s-%d", idPrefix, count), MatchProfile: profile.GetName(), MatchFunction: matchName, Tickets: matchTickets, Extensions: newExtensions, }) count++ } return matches, nil } func score(skill, latency float64) float64 { return skill - (latency / 1000.0) } ここまでで、マッチング機能とGameServerのケジューリング機能の実装が完了しました。 次に、作成したモジュールをそれぞれEKSにアップロードし、デモとしてテストします。 具体的に完成したデモの アーキテクチャ は、以下の図の通りです。 デプロイと動作確認 ローカルにおいてDockerImageの コンパイル GameFrontend # OpenMatch/mod_matchmaker101/frontend/Dockerfile docker build -t localimage/mod_frontend:0.1 . Director # OpenMatch/mod_matchmaker101/director/Dockerfile docker build -t localimage/mod_director:0.1 . MatchFunction # OpenMatch/mod_matchmaker101/matchfunction/Dockerfile docker build -t localimage/mod_matchfunction:0.1 . DockerImageを Amazon ECRにアップロード EKSでDockerImageを取得する際、ローカルのイメージにアクセスできないため、イメージを Amazon ECRサービスにアップロードする必要があります。 ※ Amazon ECRはイメージを保管するための専用レポジトリで、Docker Hubなどと同様のサービスがあります。 Amazon ECRでプライベートイメージ リポジトリ を作成する具体的な方法については、 ECRでプライベートリポジトリを作成する を参照してください。 以下の名称の Amazon ECRプライベートイメージ リポジトリ をそれぞれ作成する Frontendの リポジトリ 名:mod_frontend Directorの リポジトリ 名:mod_director MatchFunctionの リポジトリ 名:mod_matchfunction ローカルのイメージを Amazon ECRにアップロードする # Frontend docker tag localimage/mod_frontend:0.1 {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_frontend:0.1 docker push {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_frontend:0.1 # Director docker tag localimage/mod_director:0.1 {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_director:0.1 docker push {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_director:0.1 # MatchFunction: docker tag localimage/mod_matchfunction:0.1 {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_matchfunction:0.1 docker push {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_matchfunction:0.1 モジュールをEKSにデプロイ デプロイ yaml ファイル内のimageアドレスを、上記で作成した Amazon ECRのアドレスに変更します。 # Frontend: ## yaml file path ## OpenMatch/mod_matchmaker101/frontend/frontend.yaml image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_frontend:0.1 # Director ## yaml file path ## OpenMatch/mod_matchmaker101/director/director.yaml image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_director:0.1 # MatchFunction: ## yaml file path ## OpenMatch/mod_matchmaker101/matchfunction/matchfunction.yaml image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_matchfunction:0.1 各 Yaml ファイルを以下のように修正します。 # Frontend.yaml ## yaml file path ## OpenMatch/mod_matchmaker101/frontend/frontend.yaml ## KindをDeploymentに変更し、HTTP LBサービスを追加します apiVersion: v1 kind: Service metadata: name: frontend-endpoint annotations: service.alpha.kubernetes.io/app-protocols: '{"http":"HTTP"}' labels: app: frontend spec: type: NodePort selector: app: frontend ports: - port: 80 protocol: TCP name: http targetPort: frontend --- apiVersion: apps/v1 kind: Deployment metadata: name: frontend namespace: default labels: app: frontend spec: replicas: 1 selector: matchLabels: app: frontend template: metadata: labels: app: frontend spec: containers: - name: frontend image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_frontend:0.1 imagePullPolicy: Always ports: - name: frontend containerPort: 80 # Director.yaml ## yaml file path ## OpenMatch/mod_matchmaker101/director/director.yaml apiVersion: v1 kind: Pod metadata: name: director namespace: openmatch-poc spec: containers: - name: director image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_director:0.1 imagePullPolicy: Always hostname: director # MatchFunction.yaml ## yaml file path ## OpenMatch/mod_matchmaker101/matchfunction/matchfunction.yaml apiVersion: v1 kind: Pod metadata: name: matchfunction namespace: openmatch-poc labels: app: openmatch component: matchfunction spec: containers: - name: matchfunction image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_MatchFunction:0.1 imagePullPolicy: Always ports: - name: grpc containerPort: 50502 --- kind: Service apiVersion: v1 metadata: name: matchfunction namespace: openmatch-poc labels: app: openmatch component: matchfunction spec: selector: app: openmatch component: matchfunction clusterIP: None type: ClusterIP ports: - name: grpc protocol: TCP port: 50502 それぞれの yaml ファイルをEKSに適用し、マッチングシステムをデプロイします。 # GameFrontend kubectl apply -f frontend.yaml # Director kubectl apply -f director.yaml # MatchFunction kubectl apply -f matchfunction.yaml 動作確認 以下の図に示すように、 frontend.yaml で作成されたServiceに接続し、マッチングシステムをテストします。 この frontend-endpoint サービスにローカルでもアクセスできるようにするため、 Kubernetes のPortForwadering機能を使用します。 Frontendサービスをローカルに マッピング する 下図の⑤エリアです。 # Frontend サービスをローカルの8081ポートにマッピングします kubectl port-forward services/frontend-ednpoint 8081:80 8つの新しいターミナルを作成して、8名のプレイヤーがFrontendサービスに接続するのをシミュレートする 下図の①エリアで4名(ap-northeast-1)+4名(ap-northeast-3)のプレイヤーが接続するのをシミュレートします。 # Get: /Frontend/play/regionname ## 4名(ap-northeast-1) curl 127.0.0.1:8081/play/ap-northeast-1 ## 4名(ap-northeast-3) curl 127.0.0.1:8081/play/ap-northeast-3 エラーの有無を確認するために、matchfunction/director/frontendモジュールのログ情報を出力する 下図の②〜④エリアです。 # matchfuntion log kubectl logs --tail 4 matchfunction -n openmatch-poc # director log kubectl logs --tail 4 director -n openmatch-poc # frontend log kubectl logs --tail 4 deployments/frontend 上記の手順に従って、8名のプレイヤーがマッチングを開始するシミュレーションを行います。 確認ポイントは次の通りです。 ②〜④エリアのログにはエラー出力がありません。 ①の8名のクライアント全員がIP、Port情報を正常に取得します。 また、前の4名のプレイヤーは同じグループにマッチされるため、その IP:Port アドレスは同じです。 後の4名のプレイヤーも同じグループにマッチされ、その IP:Port アドレスも同じです。 ⑥エリアでは、GameServerのステータスを確認し、2台のサーバーがAllocated状態にあることを確認します。 終わりに これまでに、AgonesとOpen Matchを使用して高可用性と拡張性、スケジューリングが可能なマッチングシステムを構築しました。 次に、 Part3 ではUnrealEngineを使用してGameClientを開発し、このマッチングシステムに接続する方法を説明します。 これにより、マッチング機能を持ち、Agonesを使用してDedicated Serverをスケジューリングする マルチプレイ ゲームの開発を完了します。 引き続き、お楽しみにしてください! 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 https://agones.dev/site/docs/overview/ https://open-match.dev/site/docs/ 執筆: @chen.sun 、レビュー: @yamashita.yuki ( Shodo で執筆されました )
アバター
こんにちは!金融ソリューション事業部の孫です。 以前の 記事 では Unreal Engine Dedicated Serverの構築方法について紹介しました。 今回は続きの記事として、以下の3部で、 AWS が提供するEKSを使用してマッチメイキング機能を持つAgonesでGameServerを運用する環境の構築プロセスを説明します。 なお、EKSは Kubernetes の クラウド サービスの一つであり、同様のものとして「 Google のGKE」や「 Microsoft の AKS 」などが存在します。 以下3記事(Part)に分けて解説します。 Part1 :環境設定(「EKS環境構築」と「Agones/Open Matchのインストール」)、および動作確認 Part2 :マッチメイキングサービスのカスタマイズ処理を実装します Part3 :UnrealEngineを使用したGameServerとGameClientを用意します、Part2で開発したマッチメイキングサービスをGameClientに統合します。 はじめに 実行環境 実施手順 1.EKSの環境構築 2.Agonesのインストール 3.Openmatchのインストール 4.動作確認 Agonesデモのデプロイと動作確認 Open Matchデモのデプロイと動作確認 終わりに 参考 はじめに 現代のゲーム業界では、Dedicated Serverが重要な要素となっています。ある マルチプレイ ゲームの裏側では、大量のDedicated Serverを所有しており、プレイヤーに安定かつ効率的なサービスを提供します。 これらのサーバーの管理とスケジューリングは、プレイヤーのゲーム体験に直接影響する重要な問題です。この問題を解決するために、Agonesは登場し、Dedicated Serverの管理とスケジューリングをよりシンプルにするソリューションを提供します。 しかし、専用サーバーの管理とスケジューリングだけでは不十分で、これらのサーバーを効率的に利用するためにはマッチングシステムが必要です。 例えば、同じ地域のプレイヤーが最も近いゲームサーバーにマッチングされることで、彼らのゲーム体験を向上させることを望んでいます。そのため、Open Matchは、柔軟でスケーラブルなマッチングシステムとして生まれました。 したがって、AgonesとOpenMatchは相互補完的な存在です。一方で、AgonesはDedicated Serverの管理とスケジューリングを行い、サーバーの使用効率を向上させます。 一方で、Open Matchは効率的なマッチング アルゴリズム を通じて、プレイヤーを最も適したサーバーにマッチングします。 これら二つは、ゲームに対して柔軟でスケーラブルで効率的なインフラスト ラク チャを提供します。 これから、このようなインフラスト ラク チャを作成する方法を3記事に分けてステップ バイス テップで説明します。 実行環境 kubectl (v1.27.1) Kubernetes クラスタ ーを操作するための コマンドライン ツール eksctl (0.139.0) EKS クラスタ ーを制御するために使用する コマンドライン ツール AWS CLI (2.11.17) Amazon EKS など AWS のサービスを操作するための コマンドライン ツール 実施手順 以下の手順で環境を構築し、テストを行います。 EKSの環境構築 Agonesのインストール Openmatchのインストール 動作確認 1.EKSの環境構築 EKSの構築方法については、 AWS 公式ドキュメントの 「Amazon EKS の開始方法」 に従って以下の手順を実施します。 EKS展開用のネットワーク環境を作成する Kubernetes クラスタ を作成する クラスタ のノードグループを作成する これから、EKSの構築を実施します。 a.EKS展開用のネットワーク環境を作成する 今回は検証目的のため、 公式サイト で推奨されるネットワーク アーキテクチャ (PrivateとPublicの構造)は使用せず、2つのPublicネットワークのみを設定します。 実施結果を確認します。 VPC CIDR: 192.168.0.0/16 Subnet: $ aws ec2 describe-subnets \ --filters "Name=vpc-id,Values=[VPC ID]" \ --query 'Subnets[*].{SubnetId: SubnetId,AvailabilityZone: AvailabilityZone,CidrBlock: CidrBlock}' \ --output table -------------------------------------------------------------------- | DescribeSubnets | +------------------+------------------+----------------------------+ | AvailabilityZone | CidrBlock | SubnetId | +------------------+------------------+----------------------------+ | ap-northeast-1a | 192.168.0.0/24 | パブリックサブネットID1 | | ap-northeast-1c | 192.168.1.0/24 | パブリックサブネットID2 | +------------------+------------------+----------------------------+ b. Kubernetes クラスタ を作成する ここでは、 クラスタ を作成する方法として「eksctl」と「マネジメントコンソール」の2つの選択肢があります。 初心者がステップ バイス テップで進めたい場合は後者のマネジメントコンソールを使用することをおすすめしますが、今回は手間を省略するためにeksctlコマンドを使用して クラスタ を作成します。 以下のコマンドを使用して クラスタ を作成しますが、「 クラスタ 名」・「パブリックサブネットID1」・「パブリックサブネットID2」はそれぞれ実際の クラスタ 名とパブリックサブネットIDに置き換えてください # コマンドガイドライン ## https://eksctl.io/usage/vpc-configuration/#use-existing-vpc-other-custom-configuration eksctl create cluster --name 「クラスタ名」\ --region ap-northeast-1 \ --without-nodegroup \ --vpc-public-subnets=[パブリックサブネットID1],[パブリックサブネットID2] c. クラスタ のノードグループを作成する NodeGroupを AWS コンソールを介して作成する場合、まず一連のロールを設定する必要があります。手続きを簡略化するために、今回も引き続きeksctlコマンドを使用してNodeGroupを作成します。 以下のコマンドを使用してNodeGroupを作成します。「 クラスタ 名」「ノードグループ名」はそれぞれ希望する名前に置き換えてください。 ※次にOpen Matchをインストールしますが、インストールコマンドを簡略化するために、デフォルトの設定を使用します。このため、最低でも3つ以上のCPUが必要となりますので、今回はt3.xlargeタイプのサーバを選択します。 # コマンドガイドライン ## https://eksctl.io/usage/managing-nodegroups/ eksctl create nodegroup \ --cluster 「クラスタ名」 \ --region ap-northeast-1 \ --name 「ノードグループ名」 \ --node-type t3.xlarge \ --nodes 2 \ --nodes-min 2 \ --nodes-max 2 ここまでは、EKSの環境設定が完了しました。 次に、AgonesとOpen Matchをインストールします。 2.Agonesのインストール Agonesをインストールする方法については、基本的には Agonesの公式ドキュメンテーション に従います。 インストール方法としては、 yaml インストールとHelmインストールの2つの方法がありますが、以下の理由からHelmを使用してAgonesをインストールする方法を選択しました。 ① Helmを使用すると、 Kubernetes アプリケーションのデプロイを再現可能で一貫性のある形で行うことができます。これにより、運用上の一貫性と信頼性を確保する ② Helmはチャートと呼ばれるパッケージ形式を使用するため、チーム間での共有や再利用が容易になる ③ 複雑なアプリケーションを構成するためのパラメータ化された設定を提供します。これにより、カスタマイズや再構成が容易になる ④ Helmはデプロイのバージョン管理、つまりリリースの管理を容易にします。これにより、バージョン間の移行がスムーズに行う Agonesのインストールコマンドは以下のとおりです。 [my-release]はリリースの名前で、適宜置き換えてください。 # 公式のstable Helmリポジトリを追加 helm repo add agones https://agones.dev/chart/stable # Helmを使ってAgonesをインストール helm install [my-release] --namespace agones-system agones/agones 上記のコマンドを使用すると、公式のstable Helm リポジトリ を追加し、Helmを使ってAgonesをインストールできます。[my-release]の部分にはインストールするリリースの名前を指定します。 また、Agonesは agones-system という名前の空間にインストールされます。 3.Openmatchのインストール Openmatchの公式ドキュメンテーション に従ってOpenmatchをインストールします。 OpenMatchのインストールコマンドは以下のとおりです。 [my-release]はリリースの名前で、適宜置き換えてください。 # Open MatchのHelmリポジトリを追加 helm repo add open-match https://open-match.dev/chart # Helmを使ってOpen Matchをインストール helm install [my-release] open-match/open-match --namespace open-match --set open-match-core.enabled=true 上記のコマンドを使用すると、Open MatchのHelm リポジトリ を追加し、Helmを使ってOpen Matchをインストールできます。 また、Open Matchはopen-matchという 名前空間 にインストールされ、 open-match-core が有効になります。 4.動作確認 環境の構築が完了したので、AgonesとOpen Matchが正しくインストールされていることを確認するために、AgonesのデモとOpen Matchのデモを使用してテストを行います。 環境の構築が完了している場合は、以下の手順を実行してテストを行ってください。 Agonesデモのデプロイと動作確認 Agones公式ドキュメントの ゲームサーバ作成手順 に従ってサンプルゲームサーバーをデプロイします。これにより、Agonesが正常に動作するかを確認できます。 サンプルゲームサーバーをデプロイする kubectl create -f https://raw.githubusercontent.com/googleforgames/agones/release-1.32.0/examples/simple-game-server/gameserver.yaml GameServerの状態を確認する kubectl get gs 確認ポイントとして、GameServerのステータスが Ready になっていることを確認してください。 $ kubectl get gs NAME STATE ADDRESS PORT NODE AGE simple-game-server-h4h2w Ready ec2-18-182-6-144.ap-northeast-1.compute.amazonaws.com 7393 ip-192-168-0-130.ap-northeast-1.compute.internal 18s Open Matchデモのデプロイと動作確認 Openmatch 公式デモの作成手順 に従ってOpen Matchのデモをデプロイします。これにより、Open Matchが正常に動作するかを確認できます。 サンプルマッチメイキングフロントエンドとバックエンドをデプロイする kubectl create namespace open-match-demo kubectl apply --namespace open-match-demo \ -f https://open-match.dev/install/v1.7.0/yaml/02-open-match-demo.yaml マッチメイキングフロントエンドとバックエンドの状態を確認する kubectl get pods -n open-match-demo 上記の手順に従い、Open Matchのサンプルマッチメイキングでフロントエンドとバックエンドをデプロイし、 kubectl get pods -n open-match-demo コマンドでポッドの状態を確認します。 確認ポイントとして、 ① すべてのポッドのステータスが Running になっていることを確認する $ kubectl get pods -n open-match-demo NAME READY STATUS RESTARTS AGE om-demo-7bf887b848-75nvn 1/1 Running 0 35s om-function-58b954cf5f-4gqr4 1/1 Running 0 35s om-function-58b954cf5f-ll6pz 1/1 Running 0 35s om-function-58b954cf5f-nqrks 1/1 Running 0 35s ② PortForward を使用してデモページにアクセスできることを確認する $ kubectl port-forward --namespace open-match-demo service/om-demo 51507:51507 Forwarding from 127.0.0.1:51507 -> 51507 Forwarding from [::1]:51507 -> 51507 終わりに 以上で、EKSの環境構築およびAgones/Open Matchのインストールプロセスの説明が完了しました。また、公式デモを使用してテストを行いました。 次に、マッチメイキングサービスの構築とGameClientの実装について詳しく説明します。 Part2 と Part3 では、マッチメイキングサービスのカスタマイズ方法とGameClientの開発について取り上げます。 引き続き、お楽しみにしてください! 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/getting-started-console.html https://open-match.dev/site/docs/ https://agones.dev/site/docs/ 執筆: @chen.sun 、レビュー: @yamashita.yuki ( Shodo で執筆されました )
アバター
はじめに 前提 この記事の対象とする読者の方 スポットインスタンスについて 2つのシグナル 2分前中断シグナル 再調整推奨シグナル スポットインスタンスのリソース確保 マネージド型ノードグループの利用 Mixed Instance Policyの利用 Cluster Autoscalerを有効にする。 ノードグループのAZごとの分散 Priority Expander スポットインスタンスの中断に備える ワークロードを停止させないために考慮すること Graceful Shutdownされるようになっている。 Podが必要な数を維持したままドレインされる。 Podが正常であるという状態を適切に定義する。 Pod Readiness Gate Podの可用性を考慮したスケジュール まとめ おわりに はじめに こんにちは!2年目にもかかわらずフレックス制度を存分に活用して、普段8:30 – 16:30で業務をしている クラウド イノベーション センターの石井大樹です。 Amazon EKS(以下EKS)にて、スポット インスタンス は利用されていますか? スポット インスタンス は安いけど、いきなりシャットダウンしちゃうから、使い勝手が悪いんでしょう?など、漠然とした不安から利用をためらっていたりはしないでしょうか。 そのぼんやりとした不安を払しょくし、スポット インスタンス 利用を検討する足がかりになれば、と思い今回は記事にまとめさせていただきました。 前提 この記事の対象とする読者の方 Kubernetes クラスタ をEKSで運用している方 EKS環境でスポット インスタンス を利用しようとしている方 既にEKS環境でスポット インスタンス を利用している方 ワークロードを維持する方法に関心がある方 スポット インスタンス について スポット インスタンス は、通常のオンデマンド インスタンス に比べて最大90%の割引で利用できるオプションです。 しかし、オンデマンド インスタンス と異なり、 AWS 側のリソースキャパシティの都合により、 インスタンス の利用が中断される可能性があります。 2つのシグナル 先ほどスポット インスタンス は、「 AWS 側の都合により中断される可能性がある」とお話しいたしました。しかし、実際には2つのシグナルが発出された後に中断が実行されます。このシグナルを利用することで、スポット インスタンス の中断の影響を最小限に収めることができます。 2分前中断シグナル スポット インスタンス の終了の2分前に通知されるものです。この通知を受け取って2分後に、スポット インスタンス は終了します。 再調整推奨シグナル 対象スポット インスタンス の中断可能性の高まりが検知された場合、2分前シグナルを受け取る前に通知されるシグナルです。このシグナルを受け取ったタイミングで必要な処理が行われるように事前に準備することで、スポット インスタンス を安定して安全に運用できます。 Amazon EC2 AutoScalingグループのCapacity Rebalancingが有効化されている場合、再調整推奨シグナルを受け取ると、 Amazon EC2 AutoScalingは、代替となる新しい インスタンス の起動を試みます。その 新しい インスタンス が起動した後、既存の インスタンス はシャットダウンされます。 なお、EKSノードグループのAutoScalingグループでCapacity Rebalancingを有効化したい場合は、後述するマネージド型ノードグループを利用することで自動的に有効化されます。セルフマネージド型ノードグループで利用をする際は、手動で設定をする必要があります。 また、セルフマネージド型ノードグループにて、再調整推奨シグナルを受け、後述するGraceful Shutdownなどのアクションの実行をするには、 AWS Node Termination Handler などの導入が必要です。セルフマネージド型ノードグループには、 シグナルをキャッチしてノード上のPodの安全な待避を行う仕組みがないためです。 ※注意 この再調整推奨シグナルは2分前中断シグナルよりも前に通知されるということを保証していません。予期せぬリソースの需要が AWS 側で生まれた場合は、2分前中断シグナルと同時に通知を受け取り、必要な対処ができずにワークロードが中断される可能性があります。 再調整推奨シグナルについて スポット インスタンス のリソース確保 せっかくスポット インスタンス の利用を開始したのにも関わらず、スポット インスタンス のリソースが十分に確保できずにワークロードを中断してしまっては元も子もありません。この章では、スポット インスタンス を利用しながら十分なリソースを確保し続ける方法をこちらでご紹介します。 マネージド型ノードグループの利用 EKSでは、マネージド型ノードグループを利用することにより、様々な恩恵を開発者は受けることができます。この恩恵はスポット インスタンス の利用時も例外ではありません。これを利用することにより、EKSでスポット インスタンス を利用する際に AWS がベストプ ラク ティスとして定めた内容を自動的に設定してくれます。 以下が対象の設定内容の一部分です。 配分戦略を capacity-optimized にする。 これにより、中断確率の低いスポット インスタンス が配分されます。 Capacity Rebalancing を有効化。 これにより、 再調整推奨シグナル を受けたときに、EKSは自動的に中断可能性の低いスポットキャパシティプールからスポット インスタンス を起動し、そのノードが準備完了になったら、既存のスポット インスタンス のドレインをしてくれます。 Mixed Instance Policyの利用 複数のEC2 インスタンス タイプをノードグループで利用できるようにすることで、スポット インスタンス を利用できる確率が高まります。利用候補となる インスタンス タイプが増えるためです。しかし、注意することが1点あります。それは、vCPU・メモリが同等の インスタンス タイプであるべきということです。 例えば、 CPU: 2vCPU , メモリ: 4GiB の c5a.large を利用する際は、CPU: 2vCPU , 、メモリ: 4GiB の c5d.large 等と利用されるべきということです。 vCPU・メモリが異なる インスタンス タイプを混在させると、Cluster Autoscalerが正常にリソースの計算ができなくなり、意図しないスケールにつながってしまいます。 Cluster Autoscalerを有効にする。 Cluster Autoscalerは Kubernetes クラスタ を自動的に調整してくれる非常に有効なエコシステムです。このエコシステムは導入のみでも十分な効果を発揮しますが、以下に紹介する機能を利用することでさらなる効果が期待できます。 ※Auto Scalingグループの最大・最少ノード数は尊重されます。そのため、Cluster Autoscalerは最大ノード数以上・最少ノード数以下にはスケールできません。 ノードグループのAZごとの分散 ノードグループを、利用しているリージョンのAZを跨るように作成するようにします。利用候補となる インスタンス が増えるためです。そうすることで、スポット インスタンス を利用できる確率を高めることができます。 Priority Expander しかし、どれだけ対策を練っても、スポット インスタンス が確保できない可能性があります。そんなときは、オンデマンド インスタンス を代わりに確保するようにしましょう。値段は割引がされていない定価で利用することになりますが、ワークロードを中断させないことが最優先です。 Cluster AutoscalerのPriority Expander を利用することで、スポット インスタンス が見つからない際は、オンデマンド インスタンス のノードグループをスケールさせることで不足したリソースを補填できます。 以下が設定例です。 正規表現 で spot に該当するノードグループが優先してスケールされるように設定しています。 apiVersion : v1 kind : ConfigMap metadata : name : cluster-autoscaler-priority-expander namespace : kube-system data : priorities : |- 50 : - .*spot.* 1 : - .*ondemand.* スポット インスタンス の中断に備える ここまでは、できるだけスポット インスタンス を多くの割合で利用する方法をご紹介いたしました。 ご紹介した通り、スポット インスタンス は中断させられてしまうものです。この中断の際にワークロードの実体であるPodに対してなんにも考慮に入れていない場合、ワークロードは中断してしまいます。 ここからはスポット インスタンス が切り替わる際にもワークロードを安全に保つための方法をご紹介します。 ワークロードを停止させないために考慮すること ワークロードを中断させないために考慮する必要があることが4つあります。 PodはGraceful Shutdownされるようになっている。 Podは必要な数を維持したままドレインされる。 Podが正常であるという状態を適切に定義されている。 Podの可用性を考慮してスケジュールされるようになっている。 Graceful Shutdownされるようになっている。 再調整推奨シグナル を受け取り、EKSがノードのドレインを開始して、瞬時にPodが削除されてしまった場合、問題が発生する可能性があります。その問題について理解するには、まずPodの削除が始まると何が起こるかを理解する必要があります。 Podの削除が始まると以下が行われます。 Podの終了処理 ServiceからのPodの切り離し これらが、問題の生じない順序で行われたら良いのですが、 Kubernetes ではこれらは個別のプロセスとして別々に行われるので、それは保証されません。 順序が異なる場合、以下のような問題が生じます。 リク エス トを受け付けたが、処理するPodが削除されてしまい、エラーになる。 また、Podが最後のリク エス トを受け付けたはいいが、処理している間にPodが終了されてしまった場合はどうなるでしょうか。このような問題が生じます。 リク エス トの処理が終わっていないにもかかわらずPodが削除されてしまい、エラーになる。 このような状況が生じ、ワークロードに影響を及ぼさないためにもPodがGraceful Shutdownをするように適切に設定しなければいけません。 この問題を解決するには2つの対策が必要です。 以下では、2つの対策・Podのライフサイクルを語る上では非常に重要な用語を用いて解説します。 SIGTERM :終了処理を開始するように命令するシグナル SIGKILL :コンテナを強制的にシャットダウンするように命令するシグナル terminationGracePeriodSeconds : deletionTimeStampが設定されてから何秒でSIGKILLをおくるかの設定 - preStopと、SIGTERMはこの時間内に終わらせることが必要です。 preStop : Podが終了する前に実行される処理 preStop ライフサイクルフックで、 SIGTERM が、サービスから切り離されたあとに送られるように待機させる。 アプリケーション側でSIGTERMシグナルを適切にハンドリングして、リク エス トの処理が終了した後でアプリが終了するように実装する。 terminationGracePeriodSeconds を十分な時間設定して、 SIGKILL が、リク エス トの処理+ SIGTERM ハンドリング処理が終わったのちにPodに送られるよう待機させる。 図で説明すると、以下のような時系列に処理が行われるようにすることで、Graceful Shutdownは達成できます。 Podが必要な数を維持したままドレインされる。 再調整推奨シグナル が出た際などの、ノードがPodをドレインする必要が生じた際、全てのPodを一度に停止してしまったらどのようなことが起こり得るでしょうか。 リク エス トを受け付けるPodがなくなり、サーバーサイドエラーになってしまい、ワークロードに障害が生じます。 このような状況を防ぐために PodDisruptionBudget、通称PDB という機能が存在しています。これは、ノードがPodをドレインする際に、Podが一度に停止できる数を制限できます。 これは、最大停止数 maxUnavailable , 最少起動数 minAvailable を、レプリカ数または割合を指定することで適用できます。 apiVersion : policy/v1beta1 kind : PodDisruptionBudget metadata : name : my-pdb spec : minAvailable : 2 # ここには最小起動数を指定します。数値または割合を指定できます。 #maxUnavailable: 1 またはこのように、最大停止数を指定できます。 selector : matchLabels : app : my-app # ここには、この PDB を適用する Pod のラベルを指定します。 Podが正常であるという状態を適切に定義する。 無事新しいスポット インスタンス にPodがスケジュールされたとしても考慮しなければいけないことがあります。それは、Podがリク エス トを受け付けられるかのヘルスチェックを正確にすることです。 Podの準備ができていないにも関わらず、 トラフィック をPodにルーティングした場合、Podはリク エス トを処理できずにエラーになってしまいます。これでは、ワークロードが中断してしまうため、非常に問題です。 これを解決するのは、Readiness Probeです。 Readiness Probeは、httpGetを用いて特定のパスの状態をチェックするなどして、アプリケーションがリク エス トを正常に処理できる状態になっているかを確認します。これにより、DB接続や時間のかかる起動プロセスが全て完了している、つまり トラフィック を受け付ける準備が整ったときにだけ、 トラフィック がPodにルーティングされます。 Pod Readiness Gate AWS Load Balancer Controllerを利用している場合は、 AWS LoadBalancer Controller Pod Readiness Gate を有効にすることを強くお勧めします。仮に、ローリングアップデートが非常に高速に行われてしまい、PodのLoad Balancerターゲットグループへの登録が遅れた場合はどうなるでしょうか。Load Balancerはバックエンドに、リク エス トを送信できる正常なPodがないと判断し、リク エス トを送信せずにワークロードが中断してしまいます。 しかし、なぜこのようなことが生じてしまうのでしょうか。それは、Load Balancerが正常であると判断するタイミングと、 Kubernetes 内でPodが正常であると判断されるタイミングに差があるからです。 このような問題が生じることをPod Readiness Gate機能を利用することで防ぐことができます。この機能は、Pod Readiness Probeに、「Load Balancerのターゲットグループへ登録されていること」という条件を追加できます。この新たな条件により、Podが正常な状態として判断される前に、そのPodがLoad Balancerのターゲットグループに正しく登録されていることが確認されます。 この結果、ローリングアップデートが行われて新しいPodがデプロイされる際、すべてのPodがLoad Balancerに適切に登録されてからリク エス トを処理し始めることが保証されます。 Podの可用性を考慮したスケジュール 次は、Podの可用性について考えてみましょう。先ほど、ノードがPodをドレインする際に PDB を設定することにより、ワークロードを保つために必要な最低限のPod数を指定しました。 しかし、仮に災害や電源の問題など、何らかで、一部のノードが全く利用できなくなった場合、Pod Disruption Budgets ( PDB ) 設定だけでは、Podの最少数を維持できなくなってしまう可能性があります。 例えば、1つのノードに全てのPodがスケジュールされていたとします。その1つのノードがダウンした場合、サービスは停止することになります。 こうしたシナリオを考慮すると、Podを複数のノード、さらに アベイラビリティ ゾーン(以下AZ)に分散する方針が重要になってきます。これにより、単一のノード・AZで問題が発生した場合でも、他のAZに展開されたPodがワークロードを維持できます。 そのため、 Kubernetes の podAntiAffinity や、 topologySpreadConstraints の設定を使用して、Podの分散を制御するべきです。 podAntiAffinityを利用した場合: apiVersion : apps/v1 kind : Deployment metadata : name : myapp spec : replicas : 8 selector : matchLabels : app : myapp template : metadata : labels : app : myapp spec : containers : - name : myapp image : myapp:1.0.0 affinity : podAntiAffinity : preferredDuringSchedulingIgnoredDuringExecution : - weight : 100 podAffinityTerm : labelSelector : matchLabels : app : myapp topologyKey : topology.kubernetes.io/zone topologySpreadConstraintsを利用した場合: apiVersion : apps/v1 kind : Deployment metadata : name : myapp spec : replicas : 8 selector : matchLabels : app : myapp template : metadata : labels : app : myapp spec : containers : - name : myapp image : myapp:1.0.0 topologySpreadConstraints : - maxSkew : 1 topologyKey : topology.kubernetes.io/zone whenUnsatisfiable : DoNotSchedule labelSelector : matchLabels : app : myapp 上記の YAML 設定は、podAntiAffinityとtopologySpreadConstraintsをそれぞれ利用した場合どのようにAZ間の分散を実現するかの例です。 podAntiAffinityの設定は、同じAZ内に同じアプリケーションのPodが配置されることを避けることを示しています。これは preferredDuringSchedulingIgnoredDuringExecution オプションを使用して、スケジューリング時には 可能な限り 考慮されます。 ここでは、topologyKeyを topology.kubernetes.io/zone に設定しているため、同じAZ内ではなく異なるAZにPodが配置されます。 ※ requiredDuringSchedulingIgnoredDuringExecution は利用しないでください。同じAZでのスケジュールが不可になるため、Podの最大数がAZ数に制限されてしまいます。 2つ目の例では、topologySpreadConstraintsを用いて、Podが均等に分散されるよう制約を加えています。ここでは maxSkew を 1 に設定しており、これは任意の2つのAZ間でのPodの最大の数量差を示します。 つまり、各AZのPodの数が1つだけ異なる場合にのみPodのスケジューリングを許可します。topologyKeyは topology.kubernetes.io/zone に設定され、Podは異なるAZに均等に分散されます。 そして whenUnsatisfiable: DoNotSchedule により、制約を満たすことができない場合には新たなPodのスケジューリングが行われません。 このようにして、Podの配置を細かく制御し、ノードやAZの障害からアプリケーションを保護できます。これらの設定を適切に使用することで、災害や電源の問題など、予期しないイベントが発生した場合でも、ワークロードの中断を回避する、または最小限に抑えることができます。 まとめ 今回、お話しさせていただいた内容は以下です。 スポット インスタンス のリソース確保 マネージド型ノードグループの利用 Mixed Instance Policyの利用 Cluster Autoscalerの利用 ノードグループのAZごとの分散 Priority Expanderの利用 スポット インスタンス の中断に備える PodはGraceful Shutdownされるようになっている。 Podは必要な数を維持したままドレインされる。 Podが正常であるという状態を適切に定義されているか。 Podの可用性を考慮してスケジュールされるようになっている。 これらを実践することで、よりスポット インスタンス を安全に効率的にご利用いただけます。また、スポット インスタンス を利用しない場合でも、後半の「スポット インスタンス の中断に備える」の部分を実践していただくことで、より堅牢にサービスを維持していただくことができます。 おわりに X(クロス) イノベーション 本部 クラウド イノベーション センターでは、新卒・キャリア採用問わず共に働いてくれる仲間を探しています。 本記事で紹介した私の働き方や、 クラウド を中心とした業務にご興味をお持ちの方は、ぜひ採用ページよりご応募ください。 執筆: @taiki_ishii 、レビュー: 柴田 崇夫 (@shibata.takao) / 寺山 輝 (@terayama.akira) ( Shodo で執筆されました )
アバター
はじめに 前提 この記事の対象とする読者の方 スポットインスタンスについて 2つのシグナル 2分前中断シグナル 再調整推奨シグナル スポットインスタンスのリソース確保 マネージド型ノードグループの利用 Mixed Instance Policyの利用 Cluster Autoscalerを有効にする。 ノードグループのAZごとの分散 Priority Expander スポットインスタンスの中断に備える ワークロードを停止させないために考慮すること Graceful Shutdownされるようになっている。 Podが必要な数を維持したままドレインされる。 Podが正常であるという状態を適切に定義する。 Pod Readiness Gate Podの可用性を考慮したスケジュール まとめ おわりに はじめに こんにちは!2年目にもかかわらずフレックス制度を存分に活用して、普段8:30 – 16:30で業務をしている クラウド イノベーション センターの石井大樹です。 Amazon EKS(以下EKS)にて、スポット インスタンス は利用されていますか? スポット インスタンス は安いけど、いきなりシャットダウンしちゃうから、使い勝手が悪いんでしょう?など、漠然とした不安から利用をためらっていたりはしないでしょうか。 そのぼんやりとした不安を払しょくし、スポット インスタンス 利用を検討する足がかりになれば、と思い今回は記事にまとめさせていただきました。 前提 この記事の対象とする読者の方 Kubernetes クラスタ をEKSで運用している方 EKS環境でスポット インスタンス を利用しようとしている方 既にEKS環境でスポット インスタンス を利用している方 ワークロードを維持する方法に関心がある方 スポット インスタンス について スポット インスタンス は、通常のオンデマンド インスタンス に比べて最大90%の割引で利用できるオプションです。 しかし、オンデマンド インスタンス と異なり、 AWS 側のリソースキャパシティの都合により、 インスタンス の利用が中断される可能性があります。 2つのシグナル 先ほどスポット インスタンス は、「 AWS 側の都合により中断される可能性がある」とお話しいたしました。しかし、実際には2つのシグナルが発出された後に中断が実行されます。このシグナルを利用することで、スポット インスタンス の中断の影響を最小限に収めることができます。 2分前中断シグナル スポット インスタンス の終了の2分前に通知されるものです。この通知を受け取って2分後に、スポット インスタンス は終了します。 再調整推奨シグナル 対象スポット インスタンス の中断可能性の高まりが検知された場合、2分前シグナルを受け取る前に通知されるシグナルです。このシグナルを受け取ったタイミングで必要な処理が行われるように事前に準備することで、スポット インスタンス を安定して安全に運用できます。 Amazon EC2 AutoScalingグループのCapacity Rebalancingが有効化されている場合、再調整推奨シグナルを受け取ると、 Amazon EC2 AutoScalingは、代替となる新しい インスタンス の起動を試みます。その 新しい インスタンス が起動した後、既存の インスタンス はシャットダウンされます。 なお、EKSノードグループのAutoScalingグループでCapacity Rebalancingを有効化したい場合は、後述するマネージド型ノードグループを利用することで自動的に有効化されます。セルフマネージド型ノードグループで利用をする際は、手動で設定をする必要があります。 また、セルフマネージド型ノードグループにて、再調整推奨シグナルを受け、後述するGraceful Shutdownなどのアクションの実行をするには、 AWS Node Termination Handler などの導入が必要です。セルフマネージド型ノードグループには、 シグナルをキャッチしてノード上のPodの安全な待避を行う仕組みがないためです。 ※注意 この再調整推奨シグナルは2分前中断シグナルよりも前に通知されるということを保証していません。予期せぬリソースの需要が AWS 側で生まれた場合は、2分前中断シグナルと同時に通知を受け取り、必要な対処ができずにワークロードが中断される可能性があります。 再調整推奨シグナルについて スポット インスタンス のリソース確保 せっかくスポット インスタンス の利用を開始したのにも関わらず、スポット インスタンス のリソースが十分に確保できずにワークロードを中断してしまっては元も子もありません。この章では、スポット インスタンス を利用しながら十分なリソースを確保し続ける方法をこちらでご紹介します。 マネージド型ノードグループの利用 EKSでは、マネージド型ノードグループを利用することにより、様々な恩恵を開発者は受けることができます。この恩恵はスポット インスタンス の利用時も例外ではありません。これを利用することにより、EKSでスポット インスタンス を利用する際に AWS がベストプ ラク ティスとして定めた内容を自動的に設定してくれます。 以下が対象の設定内容の一部分です。 配分戦略を capacity-optimized にする。 これにより、中断確率の低いスポット インスタンス が配分されます。 Capacity Rebalancing を有効化。 これにより、 再調整推奨シグナル を受けたときに、EKSは自動的に中断可能性の低いスポットキャパシティプールからスポット インスタンス を起動し、そのノードが準備完了になったら、既存のスポット インスタンス のドレインをしてくれます。 Mixed Instance Policyの利用 複数のEC2 インスタンス タイプをノードグループで利用できるようにすることで、スポット インスタンス を利用できる確率が高まります。利用候補となる インスタンス タイプが増えるためです。しかし、注意することが1点あります。それは、vCPU・メモリが同等の インスタンス タイプであるべきということです。 例えば、 CPU: 2vCPU , メモリ: 4GiB の c5a.large を利用する際は、CPU: 2vCPU , 、メモリ: 4GiB の c5d.large 等と利用されるべきということです。 vCPU・メモリが異なる インスタンス タイプを混在させると、Cluster Autoscalerが正常にリソースの計算ができなくなり、意図しないスケールにつながってしまいます。 Cluster Autoscalerを有効にする。 Cluster Autoscalerは Kubernetes クラスタ を自動的に調整してくれる非常に有効なエコシステムです。このエコシステムは導入のみでも十分な効果を発揮しますが、以下に紹介する機能を利用することでさらなる効果が期待できます。 ※Auto Scalingグループの最大・最少ノード数は尊重されます。そのため、Cluster Autoscalerは最大ノード数以上・最少ノード数以下にはスケールできません。 ノードグループのAZごとの分散 ノードグループを、利用しているリージョンのAZを跨るように作成するようにします。利用候補となる インスタンス が増えるためです。そうすることで、スポット インスタンス を利用できる確率を高めることができます。 Priority Expander しかし、どれだけ対策を練っても、スポット インスタンス が確保できない可能性があります。そんなときは、オンデマンド インスタンス を代わりに確保するようにしましょう。値段は割引がされていない定価で利用することになりますが、ワークロードを中断させないことが最優先です。 Cluster AutoscalerのPriority Expander を利用することで、スポット インスタンス が見つからない際は、オンデマンド インスタンス のノードグループをスケールさせることで不足したリソースを補填できます。 以下が設定例です。 正規表現 で spot に該当するノードグループが優先してスケールされるように設定しています。 apiVersion : v1 kind : ConfigMap metadata : name : cluster-autoscaler-priority-expander namespace : kube-system data : priorities : |- 50 : - .*spot.* 1 : - .*ondemand.* スポット インスタンス の中断に備える ここまでは、できるだけスポット インスタンス を多くの割合で利用する方法をご紹介いたしました。 ご紹介した通り、スポット インスタンス は中断させられてしまうものです。この中断の際にワークロードの実体であるPodに対してなんにも考慮に入れていない場合、ワークロードは中断してしまいます。 ここからはスポット インスタンス が切り替わる際にもワークロードを安全に保つための方法をご紹介します。 ワークロードを停止させないために考慮すること ワークロードを中断させないために考慮する必要があることが4つあります。 PodはGraceful Shutdownされるようになっている。 Podは必要な数を維持したままドレインされる。 Podが正常であるという状態を適切に定義されている。 Podの可用性を考慮してスケジュールされるようになっている。 Graceful Shutdownされるようになっている。 再調整推奨シグナル を受け取り、EKSがノードのドレインを開始して、瞬時にPodが削除されてしまった場合、問題が発生する可能性があります。その問題について理解するには、まずPodの削除が始まると何が起こるかを理解する必要があります。 Podの削除が始まると以下が行われます。 Podの終了処理 ServiceからのPodの切り離し これらが、問題の生じない順序で行われたら良いのですが、 Kubernetes ではこれらは個別のプロセスとして別々に行われるので、それは保証されません。 順序が異なる場合、以下のような問題が生じます。 リク エス トを受け付けたが、処理するPodが削除されてしまい、エラーになる。 また、Podが最後のリク エス トを受け付けたはいいが、処理している間にPodが終了されてしまった場合はどうなるでしょうか。このような問題が生じます。 リク エス トの処理が終わっていないにもかかわらずPodが削除されてしまい、エラーになる。 このような状況が生じ、ワークロードに影響を及ぼさないためにもPodがGraceful Shutdownをするように適切に設定しなければいけません。 この問題を解決するには2つの対策が必要です。 以下では、2つの対策・Podのライフサイクルを語る上では非常に重要な用語を用いて解説します。 SIGTERM :終了処理を開始するように命令するシグナル SIGKILL :コンテナを強制的にシャットダウンするように命令するシグナル terminationGracePeriodSeconds : deletionTimeStampが設定されてから何秒でSIGKILLをおくるかの設定 - preStopと、SIGTERMはこの時間内に終わらせることが必要です。 preStop : Podが終了する前に実行される処理 preStop ライフサイクルフックで、 SIGTERM が、サービスから切り離されたあとに送られるように待機させる。 アプリケーション側でSIGTERMシグナルを適切にハンドリングして、リク エス トの処理が終了した後でアプリが終了するように実装する。 terminationGracePeriodSeconds を十分な時間設定して、 SIGKILL が、リク エス トの処理+ SIGTERM ハンドリング処理が終わったのちにPodに送られるよう待機させる。 図で説明すると、以下のような時系列に処理が行われるようにすることで、Graceful Shutdownは達成できます。 Podが必要な数を維持したままドレインされる。 再調整推奨シグナル が出た際などの、ノードがPodをドレインする必要が生じた際、全てのPodを一度に停止してしまったらどのようなことが起こり得るでしょうか。 リク エス トを受け付けるPodがなくなり、サーバーサイドエラーになってしまい、ワークロードに障害が生じます。 このような状況を防ぐために PodDisruptionBudget、通称PDB という機能が存在しています。これは、ノードがPodをドレインする際に、Podが一度に停止できる数を制限できます。 これは、最大停止数 maxUnavailable , 最少起動数 minAvailable を、レプリカ数または割合を指定することで適用できます。 apiVersion : policy/v1beta1 kind : PodDisruptionBudget metadata : name : my-pdb spec : minAvailable : 2 # ここには最小起動数を指定します。数値または割合を指定できます。 #maxUnavailable: 1 またはこのように、最大停止数を指定できます。 selector : matchLabels : app : my-app # ここには、この PDB を適用する Pod のラベルを指定します。 Podが正常であるという状態を適切に定義する。 無事新しいスポット インスタンス にPodがスケジュールされたとしても考慮しなければいけないことがあります。それは、Podがリク エス トを受け付けられるかのヘルスチェックを正確にすることです。 Podの準備ができていないにも関わらず、 トラフィック をPodにルーティングした場合、Podはリク エス トを処理できずにエラーになってしまいます。これでは、ワークロードが中断してしまうため、非常に問題です。 これを解決するのは、Readiness Probeです。 Readiness Probeは、httpGetを用いて特定のパスの状態をチェックするなどして、アプリケーションがリク エス トを正常に処理できる状態になっているかを確認します。これにより、DB接続や時間のかかる起動プロセスが全て完了している、つまり トラフィック を受け付ける準備が整ったときにだけ、 トラフィック がPodにルーティングされます。 Pod Readiness Gate AWS Load Balancer Controllerを利用している場合は、 AWS LoadBalancer Controller Pod Readiness Gate を有効にすることを強くお勧めします。仮に、ローリングアップデートが非常に高速に行われてしまい、PodのLoad Balancerターゲットグループへの登録が遅れた場合はどうなるでしょうか。Load Balancerはバックエンドに、リク エス トを送信できる正常なPodがないと判断し、リク エス トを送信せずにワークロードが中断してしまいます。 しかし、なぜこのようなことが生じてしまうのでしょうか。それは、Load Balancerが正常であると判断するタイミングと、 Kubernetes 内でPodが正常であると判断されるタイミングに差があるからです。 このような問題が生じることをPod Readiness Gate機能を利用することで防ぐことができます。この機能は、Pod Readiness Probeに、「Load Balancerのターゲットグループへ登録されていること」という条件を追加できます。この新たな条件により、Podが正常な状態として判断される前に、そのPodがLoad Balancerのターゲットグループに正しく登録されていることが確認されます。 この結果、ローリングアップデートが行われて新しいPodがデプロイされる際、すべてのPodがLoad Balancerに適切に登録されてからリク エス トを処理し始めることが保証されます。 Podの可用性を考慮したスケジュール 次は、Podの可用性について考えてみましょう。先ほど、ノードがPodをドレインする際に PDB を設定することにより、ワークロードを保つために必要な最低限のPod数を指定しました。 しかし、仮に災害や電源の問題など、何らかで、一部のノードが全く利用できなくなった場合、Pod Disruption Budgets ( PDB ) 設定だけでは、Podの最少数を維持できなくなってしまう可能性があります。 例えば、1つのノードに全てのPodがスケジュールされていたとします。その1つのノードがダウンした場合、サービスは停止することになります。 こうしたシナリオを考慮すると、Podを複数のノード、さらに アベイラビリティ ゾーン(以下AZ)に分散する方針が重要になってきます。これにより、単一のノード・AZで問題が発生した場合でも、他のAZに展開されたPodがワークロードを維持できます。 そのため、 Kubernetes の podAntiAffinity や、 topologySpreadConstraints の設定を使用して、Podの分散を制御するべきです。 podAntiAffinityを利用した場合: apiVersion : apps/v1 kind : Deployment metadata : name : myapp spec : replicas : 8 selector : matchLabels : app : myapp template : metadata : labels : app : myapp spec : containers : - name : myapp image : myapp:1.0.0 affinity : podAntiAffinity : preferredDuringSchedulingIgnoredDuringExecution : - weight : 100 podAffinityTerm : labelSelector : matchLabels : app : myapp topologyKey : topology.kubernetes.io/zone topologySpreadConstraintsを利用した場合: apiVersion : apps/v1 kind : Deployment metadata : name : myapp spec : replicas : 8 selector : matchLabels : app : myapp template : metadata : labels : app : myapp spec : containers : - name : myapp image : myapp:1.0.0 topologySpreadConstraints : - maxSkew : 1 topologyKey : topology.kubernetes.io/zone whenUnsatisfiable : DoNotSchedule labelSelector : matchLabels : app : myapp 上記の YAML 設定は、podAntiAffinityとtopologySpreadConstraintsをそれぞれ利用した場合どのようにAZ間の分散を実現するかの例です。 podAntiAffinityの設定は、同じAZ内に同じアプリケーションのPodが配置されることを避けることを示しています。これは preferredDuringSchedulingIgnoredDuringExecution オプションを使用して、スケジューリング時には 可能な限り 考慮されます。 ここでは、topologyKeyを topology.kubernetes.io/zone に設定しているため、同じAZ内ではなく異なるAZにPodが配置されます。 ※ requiredDuringSchedulingIgnoredDuringExecution は利用しないでください。同じAZでのスケジュールが不可になるため、Podの最大数がAZ数に制限されてしまいます。 2つ目の例では、topologySpreadConstraintsを用いて、Podが均等に分散されるよう制約を加えています。ここでは maxSkew を 1 に設定しており、これは任意の2つのAZ間でのPodの最大の数量差を示します。 つまり、各AZのPodの数が1つだけ異なる場合にのみPodのスケジューリングを許可します。topologyKeyは topology.kubernetes.io/zone に設定され、Podは異なるAZに均等に分散されます。 そして whenUnsatisfiable: DoNotSchedule により、制約を満たすことができない場合には新たなPodのスケジューリングが行われません。 このようにして、Podの配置を細かく制御し、ノードやAZの障害からアプリケーションを保護できます。これらの設定を適切に使用することで、災害や電源の問題など、予期しないイベントが発生した場合でも、ワークロードの中断を回避する、または最小限に抑えることができます。 まとめ 今回、お話しさせていただいた内容は以下です。 スポット インスタンス のリソース確保 マネージド型ノードグループの利用 Mixed Instance Policyの利用 Cluster Autoscalerの利用 ノードグループのAZごとの分散 Priority Expanderの利用 スポット インスタンス の中断に備える PodはGraceful Shutdownされるようになっている。 Podは必要な数を維持したままドレインされる。 Podが正常であるという状態を適切に定義されているか。 Podの可用性を考慮してスケジュールされるようになっている。 これらを実践することで、よりスポット インスタンス を安全に効率的にご利用いただけます。また、スポット インスタンス を利用しない場合でも、後半の「スポット インスタンス の中断に備える」の部分を実践していただくことで、より堅牢にサービスを維持していただくことができます。 おわりに X(クロス) イノベーション 本部 クラウド イノベーション センターでは、新卒・キャリア採用問わず共に働いてくれる仲間を探しています。 本記事で紹介した私の働き方や、 クラウド を中心とした業務にご興味をお持ちの方は、ぜひ採用ページよりご応募ください。 執筆: @taiki_ishii 、レビュー: 柴田 崇夫 (@shibata.takao) / 寺山 輝 (@terayama.akira) ( Shodo で執筆されました )
アバター
金融ソリューション事業部の石沢です。ふだんは所属部門の様々なプロジェクトの支援をしています。今回の記事では、当社でやっている 「伴走型研修」 を受講者の立場で紹介します。資格試験については会社の補助で制限なく受験できるのですが、それでも勉強するのは大変ですよね。 くじけずにやりきる ために今回は伴走型研修というものを利用して、無事に資格合格したというお話です。 受験のきっかけ 今回挑戦したのはタイトルにある通り、 AWS Certified Solutions Architect - Associate(以下SAA)です。さすがに説明は不要だと思いますが、詳細は AWS さんのサイトをご確認ください。 AWS Certified Solutions Architect – Associate 認定 自分は、より初心者向けの AWS Certified Cloud Practitionerという資格を3年前に取得していました。この資格の有効期限が切れるので、再受験して維持するのか、より上位の資格にチャレンジを悩んでいたところでした。悩んだポイントは 実務としての AWS を利用したシステム構築経験がない SAAは中級向けの資格であり、難易度はそこそこ高いらしい 割と忙しいので受験を先延ばしにしたり、挫折することになりそう です。組織的にはそれなりの職級にいるので、別に上司から資格を取れと言われるわけでもありません。どうしよっかなー、失敗したら恥ずかしいしパスしようかなー……。 と悩んでいたところに、伴走型研修の案内が来たのでした。 伴走型研修とは もともと社では AWS 認定資格受験希望者に対して学習コンテンツや学習用環境、資格受験費用の全額補助が提供されているのですが、さらに昨年から任意参加型の「伴走型研修」も利用できるようになっていました。 伴走型研修では、以下のサポートを受けることができます(2023年現在の情報) キックオフウェビナーでの、学習の進め方についての全体ガイダンス 講師による個別面談(スキルや状況に応じた学習計画についてアド バイス してもらえる) 毎週30分のミニ勉強会(試験問題解説や情報共有など) 学習中につまづいたポイントや不明点に関する相談や質疑応答など 私はSAAコースを選択しましたが、 AWS Certified Solutions Architect - Professionalコースも選択可能です。心強い! 実際にやったこと キックオフウェビナー 資格の概要に始まり、学習方法についてひととおり説明していただき助かりました。インターネットにたくさんの情報が公開されているのですが、書いてある内容はバラバラなので、まとめて確認し質疑応答できるのはかなり助かる! 個別面談 キックオフ後、講師による個別の1on1で学習スケジュールと資格受験日程を決めました。受講の目的や AWS 経験などをアド バイス してもらえます。私の場合はだいたい基礎知識学習1カ月+問題集などを利用した試験対策1カ月の2カ月コースが良さそうということでスケジュールを組みました。また挫折防止のために、試験を予約したほうが良いとのこと。えーっと思いながらも従順に2カ月後の試験予約もしてしまいます。デッドラインが決まって身が引き締まります。 なお、この伴走型研修では学習自体は各自が自由に進めることになります(座学の講義があるわけではない)。おすすめコンテンツなどを紹介いただきながら、書籍、 AWS が公開しているコンテンツやUdemy講座を中心のカリキュラムを自分で選択することになります。私自身は学習期間にあまりまとまった時間が取れない見込みだったので、基礎知識の学習についても書籍やドキュメントなどの読み込みではなく、Udemyの講座でキャッチアップすることにしました(スキマ時間を活用するため)。 毎週30分のミニ勉強会 技術資格の勉強は、わりと孤独なものになりがちです。ですが、伴走型研修では毎週30分のオンライン勉強会があるので孤立感が軽減されていて良かったです。業務時間内でも30分という時間なので調整しやすく、欠席をしても録画でキャッチアップすることができるようになっています。勉強会では講師の出す例題を皆で解いたり、理解しにくい点について解説を受けたりしました。 受講者チャネルでのフォローアップ 上記以外には、受講者の参加するコミュニケーションチャンネル( Microsoft Teamsの専用Teamでのチャット)でのフォローアップがあります。学習コンテンツでわからなかった点や、模擬試験集の解説で腑に落ちない点などについて質問をできます。しっかり活用しました。 自習 伴走型研修で伴走してもらいながら、実際に自分でやった事は以下のとおりです。いずれも個人の費用負担はありません。 ハンズオン用の AWS 学習環境の取得(社内で簡単なフォームに入力するだけ。各種ガード設定付きです) いくつかの AWS 提供ドキュメントの通読(読んでおくと良いとアド バイス されたもの) Udemy:【ベストセラー完全日本語化】AWS 認定ソリューションアーキテクト アソシエイト SAA-C03 対応 2022 最新版 書籍での学習を断念し、こちらの講座をUdemy for Business枠で受講。また講座内のハンズオンは極力自分でも手を動かしています。27時間を少しスピードを上げて視聴しています Udemy:【SAA-C03版】AWS 認定ソリューションアーキテクト アソシエイト模擬試験問題集(6回分390問) 試験対策として有名なコースですね。当初想像していた問題よりこちらの模擬試験のほうが難しく感じ、また最初はまったく正解できず焦りましたが、復習をしっかりやって知識を補完していきました 試験受験 最後は試験の受験です。自宅だと集中しにくいのでテストセンターで受験し、無事に1回で合格できました。 よかったこと 伴走型研修に参加して一番良かったのは、途中でくじけずに完走して予定通りに資格取得が完了したことです。伴走型研修を利用せずに個人学習のみでチャレンジしていたら、おそらくもっと時間がかかったと思います(ズルズルと試験日を延ばしていたはず)。もしかしたら、途中であきらめてしまったかもしれません。 なお、毎週の勉強会で講師から 「進捗どうですか、質問はいつでもどうぞ」 と言われていたのが自分にとっては先延ばし防止効果がありました。別に進捗報告の必要はなく単なる声掛けだったのですが、勉強が出来ていない週は「ウッ」となって、あわててやっていました。 加えて、世の中にあふれている資格情報に惑わされなかったという点も自分にとっては大きかったです。試験日が近づくにつれて、つい 銀の弾丸 を求めてネットをさまよってしまい「〇〇をやればよい」とか「〇〇だけでは不十分だ」といったブログ記事を見てしまい不安になってしまうのですが、信頼できる講師と話ができるので不安もだいぶ緩和されたと思います。 まとめ 伴走型研修というものを使って、 AWS 実務経験なしでもくじけずに資格取得が出来て良かった という話でした。個人の学習で詰まっている人は、伴走者を見つけると良いかもしれません。 もちろん当社に入社していただければ、ここで紹介した伴走型研修そのものに参加することもできます。興味があればぜひ以下のページもご覧ください! 私たちは一緒に働いてくれる仲間を募集しています! ISID 募集職種一覧 執筆: Ishizawa Kento (@kent) 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
金融ソリューション事業部の石沢です。ふだんは所属部門の様々なプロジェクトの支援をしています。今回の記事では、当社でやっている 「伴走型研修」 を受講者の立場で紹介します。資格試験については会社の補助で制限なく受験できるのですが、それでも勉強するのは大変ですよね。 くじけずにやりきる ために今回は伴走型研修というものを利用して、無事に資格合格したというお話です。 受験のきっかけ 今回挑戦したのはタイトルにある通り、 AWS Certified Solutions Architect - Associate(以下SAA)です。さすがに説明は不要だと思いますが、詳細は AWS さんのサイトをご確認ください。 AWS Certified Solutions Architect – Associate 認定 自分は、より初心者向けの AWS Certified Cloud Practitionerという資格を3年前に取得していました。この資格の有効期限が切れるので、再受験して維持するのか、より上位の資格にチャレンジを悩んでいたところでした。悩んだポイントは 実務としての AWS を利用したシステム構築経験がない SAAは中級向けの資格であり、難易度はそこそこ高いらしい 割と忙しいので受験を先延ばしにしたり、挫折することになりそう です。組織的にはそれなりの職級にいるので、別に上司から資格を取れと言われるわけでもありません。どうしよっかなー、失敗したら恥ずかしいしパスしようかなー……。 と悩んでいたところに、伴走型研修の案内が来たのでした。 伴走型研修とは もともと社では AWS 認定資格受験希望者に対して学習コンテンツや学習用環境、資格受験費用の全額補助が提供されているのですが、さらに昨年から任意参加型の「伴走型研修」も利用できるようになっていました。 伴走型研修では、以下のサポートを受けることができます(2023年現在の情報) キックオフウェビナーでの、学習の進め方についての全体ガイダンス 講師による個別面談(スキルや状況に応じた学習計画についてアド バイス してもらえる) 毎週30分のミニ勉強会(試験問題解説や情報共有など) 学習中につまづいたポイントや不明点に関する相談や質疑応答など 私はSAAコースを選択しましたが、 AWS Certified Solutions Architect - Professionalコースも選択可能です。心強い! 実際にやったこと キックオフウェビナー 資格の概要に始まり、学習方法についてひととおり説明していただき助かりました。インターネットにたくさんの情報が公開されているのですが、書いてある内容はバラバラなので、まとめて確認し質疑応答できるのはかなり助かる! 個別面談 キックオフ後、講師による個別の1on1で学習スケジュールと資格受験日程を決めました。受講の目的や AWS 経験などをアド バイス してもらえます。私の場合はだいたい基礎知識学習1カ月+問題集などを利用した試験対策1カ月の2カ月コースが良さそうということでスケジュールを組みました。また挫折防止のために、試験を予約したほうが良いとのこと。えーっと思いながらも従順に2カ月後の試験予約もしてしまいます。デッドラインが決まって身が引き締まります。 なお、この伴走型研修では学習自体は各自が自由に進めることになります(座学の講義があるわけではない)。おすすめコンテンツなどを紹介いただきながら、書籍、 AWS が公開しているコンテンツやUdemy講座を中心のカリキュラムを自分で選択することになります。私自身は学習期間にあまりまとまった時間が取れない見込みだったので、基礎知識の学習についても書籍やドキュメントなどの読み込みではなく、Udemyの講座でキャッチアップすることにしました(スキマ時間を活用するため)。 毎週30分のミニ勉強会 技術資格の勉強は、わりと孤独なものになりがちです。ですが、伴走型研修では毎週30分のオンライン勉強会があるので孤立感が軽減されていて良かったです。業務時間内でも30分という時間なので調整しやすく、欠席をしても録画でキャッチアップすることができるようになっています。勉強会では講師の出す例題を皆で解いたり、理解しにくい点について解説を受けたりしました。 受講者チャネルでのフォローアップ 上記以外には、受講者の参加するコミュニケーションチャンネル( Microsoft Teamsの専用Teamでのチャット)でのフォローアップがあります。学習コンテンツでわからなかった点や、模擬試験集の解説で腑に落ちない点などについて質問をできます。しっかり活用しました。 自習 伴走型研修で伴走してもらいながら、実際に自分でやった事は以下のとおりです。いずれも個人の費用負担はありません。 ハンズオン用の AWS 学習環境の取得(社内で簡単なフォームに入力するだけ。各種ガード設定付きです) いくつかの AWS 提供ドキュメントの通読(読んでおくと良いとアド バイス されたもの) Udemy:【ベストセラー完全日本語化】AWS 認定ソリューションアーキテクト アソシエイト SAA-C03 対応 2022 最新版 書籍での学習を断念し、こちらの講座をUdemy for Business枠で受講。また講座内のハンズオンは極力自分でも手を動かしています。27時間を少しスピードを上げて視聴しています Udemy:【SAA-C03版】AWS 認定ソリューションアーキテクト アソシエイト模擬試験問題集(6回分390問) 試験対策として有名なコースですね。当初想像していた問題よりこちらの模擬試験のほうが難しく感じ、また最初はまったく正解できず焦りましたが、復習をしっかりやって知識を補完していきました 試験受験 最後は試験の受験です。自宅だと集中しにくいのでテストセンターで受験し、無事に1回で合格できました。 よかったこと 伴走型研修に参加して一番良かったのは、途中でくじけずに完走して予定通りに資格取得が完了したことです。伴走型研修を利用せずに個人学習のみでチャレンジしていたら、おそらくもっと時間がかかったと思います(ズルズルと試験日を延ばしていたはず)。もしかしたら、途中であきらめてしまったかもしれません。 なお、毎週の勉強会で講師から 「進捗どうですか、質問はいつでもどうぞ」 と言われていたのが自分にとっては先延ばし防止効果がありました。別に進捗報告の必要はなく単なる声掛けだったのですが、勉強が出来ていない週は「ウッ」となって、あわててやっていました。 加えて、世の中にあふれている資格情報に惑わされなかったという点も自分にとっては大きかったです。試験日が近づくにつれて、つい 銀の弾丸 を求めてネットをさまよってしまい「〇〇をやればよい」とか「〇〇だけでは不十分だ」といったブログ記事を見てしまい不安になってしまうのですが、信頼できる講師と話ができるので不安もだいぶ緩和されたと思います。 まとめ 伴走型研修というものを使って、 AWS 実務経験なしでもくじけずに資格取得が出来て良かった という話でした。個人の学習で詰まっている人は、伴走者を見つけると良いかもしれません。 もちろん当社に入社していただければ、ここで紹介した伴走型研修そのものに参加することもできます。興味があればぜひ以下のページもご覧ください! 私たちは一緒に働いてくれる仲間を募集しています! ISID 募集職種一覧 執筆: Ishizawa Kento (@kent) 、レビュー: @sato.taichi ( Shodo で執筆されました )
アバター
ISID X(クロス) イノベーション 本部 の池田です。 2023年2月にGAした Azure Load Testing で利用する機会がありましたので使い方のご紹介です。 目次 目次 Azure Load Testingとは Azure Load Testingの価格 試験環境の概要 Azure Load Testing リソースの作成 テストシナリオの作成 負荷テストの実施 負荷テストの実行結果 まとめ Azure Load Testingとは Azure Load Testingは、Azureの クラウド インフラスト ラク チャを使用して、アプリケーションの負荷テストを実行するためのサービスです。使い慣れた JMeter スクリプト で作成したテストシナリオを クラウド 上で実行できます。 自分で 負荷試験 をしようとすると、試験用の端末を複数用意したり、ネットワーク関連のOSパラメータの変更したり考慮すべき点が多いです。また、社内から実行すると ファイアウォール で制限されたりネットワーク環境も考慮する必要があります。 Azure Load Testingを利用することで、こうした面倒な環境作成はAzureにお任せして、利用者は負荷テストシナリオ作成と試験結果の分析に注力できます。 azure.microsoft.com Azure Load Testingの価格 基本的に、仮想ユーザー時間という概念での課金となっています。 JMeter スクリプト 内の仮想ユーザー(並列スレッド)数と、テストエンジン インスタンス の数によって算出されます。 仮想ユーザーの合計数 = ( JMX ファイル内の仮想ユーザー数) * (テスト エンジン インスタンス の数) インスタンス 数だけでなく JMeter スクリプト 内で指定する並列実行数も課金額に影響しますので注意しましょう。 また、テストを実行していなくても少額の課金があります。 Azure Load Testing の価格 試験環境の概要 今回使用するテスト環境の構成は以下のとおりです。App Service 既定のWebサイトをテスト対象としてAzure Load TestingからHTTPリク エス トを連続送信します。その上でAzureリソースの負荷状況をAzure Load Testingの ダッシュ ボードで観測します。 Azure Load Testing リソースの作成 以降では実際にテストを作成します。 Azure Portal からリソースを作成します。 執筆時点で東西日本リージョンではまだ提供されていませんでしたので東アジアリージョンを利用します。 リソースが作成されました。 テストシナリオの作成 Azure Load Testing では「クイックテストを作成する」 および 「 JMeter スクリプト のアップロード」の2種類の方法でテストが作成できます。 クイックテストでは、特定のURLに対しシンプルなGETリク エス トを繰り返す JMeter スクリプト を作成できます。 しかし、認証を含んだシナリオやPOSTリク エス トを含んだシナリオは Azure Load Testing 上で作成できません。様々なケースのシナリオをテストしたい場合には外部で JMeter スクリプト を作成してアップロードする必要があります。 今回のシナリオではクイックテストでも十分ですが、実務を模して スクリプト を取り込む手順でテスト作成します。 JMeter の入手 スクリプト 作成に使用する JMeter はサイトからダウンロードしましょう。Azure Load Testing以外の クラウド 負荷試験 ツールも JMeter スクリプト を取り込んで実行するツールが多いです。初版が2001年のソフトウェアですが JMeter はまだまだ現役です。 jmeter.apache.org スクリプト の作成 ダウンロードした JMeter を使ってローカルでテストを作成します。どこか懐かしい気持ちに浸りながらテストを作成します。スレッド数の設定はコストに影響する部分ですので、過剰に大きくしないよう注意しましょう。 負荷テストの実施 「 JMeter スクリプト のアップロード」を選択しテストを作成します。 まずは、テスト名、説明を入力します。 先ほど作成した JMeter スクリプト をアップロードします。 スクリプト 内で使用するパラメータをこちらで指定できます。 機密性の高いパラメータはKeyVaultから持ってくることも可能です。 スクリプト を実行するエンジンの インスタンス 数を指定します。 テスト抽出条件の設定です。 テスト抽出条件は、レスポンスタイムやエラー率、 スループット の しきい値 を設定し、 しきい値 を超えるとテスト不合格となりテスト全体が終了します。 自動停止テストは、構成エラーによる異常を検知しエンジンを停止する目的で利用します。エラーの割合はエンジン単位で判定されエンジン単位で個別に終了されます。テスト全体としては継続されますので利用方法に注意しましょう。 テスト結果画面でメトリックを表示する インスタンス を選択します。 インスタンス を選択すると既定で設定されているメトリックが試験結果 ダッシュ ボードに表示されます。 ダッシュ ボードに表示する インスタンス やメトリックはテスト後に追加削除が可能です。 メジャーどころのAzureリソースはサポートされていますが、執筆時点ではApplication Gateway がサポートされていませんでした。こちらは今後の追加を期待したいところです。 参考: サポートされている Azure リソースの種類 設定した内容でテストを作成します。 負荷テストの実行結果 実行画面に遷移すると ダッシュ ボードで各種状態が確認できます。実行中に試験結果が随時プロットされて表示されます。クライアント側の試験状況 と サーバー側の負荷状況 が一画面で確認でき大変便利です。 クライアント側メトリック こちらはクライアント側のメトリック情報になります。 各エンジン インスタンス から収集した内容を集計した結果が表示されます。 サーバー側メトリック Azureリソースの各種メトリックが表示されます。テスト後に表示メトリックを追加することもできます。 各種メトリックを一覧表示してどこが ボトルネック となっているのか把握できます。 ただし、項目による分割 や 条件によるフィルタ などの詳細分析はできません。そうした分析は通常のメトリック画面から別途分析しましょう。 エンジン状態 JMeter スクリプト を実行しているエンジン個別の状態も表示できます。 まとめ Azure Load Testingは、 クラウド 上での負荷テストを簡単かつ効果的に行うための強力なツールです。従来の手法に比べて手軽さや柔軟性があり、リアルタイムでの結果の監視も可能です。アプリケーションのパフォーマンスを向上させたい場合や、スケーラビリティの評価を行いたい場合には、Azure Load Testingを検討してみてはいかがでしょうか。 私たちは同じチームで働いてくれる仲間を探しています。 クラウド アーキテクトの業務に興味がある方のご応募をお待ちしています。 クラウドアーキテクト 執筆: @ikeda.jun 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
アバター
ISID X(クロス) イノベーション 本部 の池田です。 2023年2月にGAした Azure Load Testing で利用する機会がありましたので使い方のご紹介です。 目次 目次 Azure Load Testingとは Azure Load Testingの価格 試験環境の概要 Azure Load Testing リソースの作成 テストシナリオの作成 負荷テストの実施 負荷テストの実行結果 まとめ Azure Load Testingとは Azure Load Testingは、Azureの クラウド インフラスト ラク チャを使用して、アプリケーションの負荷テストを実行するためのサービスです。使い慣れた JMeter スクリプト で作成したテストシナリオを クラウド 上で実行できます。 自分で 負荷試験 をしようとすると、試験用の端末を複数用意したり、ネットワーク関連のOSパラメータの変更したり考慮すべき点が多いです。また、社内から実行すると ファイアウォール で制限されたりネットワーク環境も考慮する必要があります。 Azure Load Testingを利用することで、こうした面倒な環境作成はAzureにお任せして、利用者は負荷テストシナリオ作成と試験結果の分析に注力できます。 azure.microsoft.com Azure Load Testingの価格 基本的に、仮想ユーザー時間という概念での課金となっています。 JMeter スクリプト 内の仮想ユーザー(並列スレッド)数と、テストエンジン インスタンス の数によって算出されます。 仮想ユーザーの合計数 = ( JMX ファイル内の仮想ユーザー数) * (テスト エンジン インスタンス の数) インスタンス 数だけでなく JMeter スクリプト 内で指定する並列実行数も課金額に影響しますので注意しましょう。 また、テストを実行していなくても少額の課金があります。 Azure Load Testing の価格 試験環境の概要 今回使用するテスト環境の構成は以下のとおりです。App Service 既定のWebサイトをテスト対象としてAzure Load TestingからHTTPリク エス トを連続送信します。その上でAzureリソースの負荷状況をAzure Load Testingの ダッシュ ボードで観測します。 Azure Load Testing リソースの作成 以降では実際にテストを作成します。 Azure Portal からリソースを作成します。 執筆時点で東西日本リージョンではまだ提供されていませんでしたので東アジアリージョンを利用します。 リソースが作成されました。 テストシナリオの作成 Azure Load Testing では「クイックテストを作成する」 および 「 JMeter スクリプト のアップロード」の2種類の方法でテストが作成できます。 クイックテストでは、特定のURLに対しシンプルなGETリク エス トを繰り返す JMeter スクリプト を作成できます。 しかし、認証を含んだシナリオやPOSTリク エス トを含んだシナリオは Azure Load Testing 上で作成できません。様々なケースのシナリオをテストしたい場合には外部で JMeter スクリプト を作成してアップロードする必要があります。 今回のシナリオではクイックテストでも十分ですが、実務を模して スクリプト を取り込む手順でテスト作成します。 JMeter の入手 スクリプト 作成に使用する JMeter はサイトからダウンロードしましょう。Azure Load Testing以外の クラウド 負荷試験 ツールも JMeter スクリプト を取り込んで実行するツールが多いです。初版が2001年のソフトウェアですが JMeter はまだまだ現役です。 jmeter.apache.org スクリプト の作成 ダウンロードした JMeter を使ってローカルでテストを作成します。どこか懐かしい気持ちに浸りながらテストを作成します。スレッド数の設定はコストに影響する部分ですので、過剰に大きくしないよう注意しましょう。 負荷テストの実施 「 JMeter スクリプト のアップロード」を選択しテストを作成します。 まずは、テスト名、説明を入力します。 先ほど作成した JMeter スクリプト をアップロードします。 スクリプト 内で使用するパラメータをこちらで指定できます。 機密性の高いパラメータはKeyVaultから持ってくることも可能です。 スクリプト を実行するエンジンの インスタンス 数を指定します。 テスト抽出条件の設定です。 テスト抽出条件は、レスポンスタイムやエラー率、 スループット の しきい値 を設定し、 しきい値 を超えるとテスト不合格となりテスト全体が終了します。 自動停止テストは、構成エラーによる異常を検知しエンジンを停止する目的で利用します。エラーの割合はエンジン単位で判定されエンジン単位で個別に終了されます。テスト全体としては継続されますので利用方法に注意しましょう。 テスト結果画面でメトリックを表示する インスタンス を選択します。 インスタンス を選択すると既定で設定されているメトリックが試験結果 ダッシュ ボードに表示されます。 ダッシュ ボードに表示する インスタンス やメトリックはテスト後に追加削除が可能です。 メジャーどころのAzureリソースはサポートされていますが、執筆時点ではApplication Gateway がサポートされていませんでした。こちらは今後の追加を期待したいところです。 参考: サポートされている Azure リソースの種類 設定した内容でテストを作成します。 負荷テストの実行結果 実行画面に遷移すると ダッシュ ボードで各種状態が確認できます。実行中に試験結果が随時プロットされて表示されます。クライアント側の試験状況 と サーバー側の負荷状況 が一画面で確認でき大変便利です。 クライアント側メトリック こちらはクライアント側のメトリック情報になります。 各エンジン インスタンス から収集した内容を集計した結果が表示されます。 サーバー側メトリック Azureリソースの各種メトリックが表示されます。テスト後に表示メトリックを追加することもできます。 各種メトリックを一覧表示してどこが ボトルネック となっているのか把握できます。 ただし、項目による分割 や 条件によるフィルタ などの詳細分析はできません。そうした分析は通常のメトリック画面から別途分析しましょう。 エンジン状態 JMeter スクリプト を実行しているエンジン個別の状態も表示できます。 まとめ Azure Load Testingは、 クラウド 上での負荷テストを簡単かつ効果的に行うための強力なツールです。従来の手法に比べて手軽さや柔軟性があり、リアルタイムでの結果の監視も可能です。アプリケーションのパフォーマンスを向上させたい場合や、スケーラビリティの評価を行いたい場合には、Azure Load Testingを検討してみてはいかがでしょうか。 私たちは同じチームで働いてくれる仲間を探しています。 クラウド アーキテクトの業務に興味がある方のご応募をお待ちしています。 クラウドアーキテクト 執筆: @ikeda.jun 、レビュー: @nakamura.toshihiro ( Shodo で執筆されました )
アバター
はじめに こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター の山下です。 みなさん GitHub Codespaces( https://github.co.jp/features/codespaces ) をご存知ですか? 昨年一般提供された GitHub で利用できる 統合開発環境 ( IDE )です。 GitHub Codespacesは、ブラウザ上で動作する開発環境となります。 GitHub から起動することができて、ブラウザが動く環境であれば、追加でソフトウェアのインストールを必要とせず、いつでもどこでも開発作業が可能です。 この記事では、その特徴、使い方、そして便利な点について解説します。特にチームの開発で開発環境を統一したい場合や素早く開発環境を用意したい場合には GitHub Codespaces を検討しても良いかもしれません。 GitHub Codespacesの特徴 GitHub Codespacesは、 クラウド 上に開発環境を構築し、 クラウド 上で完結する開発環境です。開発者はデ バイス や場所に依存せず、どこからでもコードの編集や実行が可能になります。従来の開発環境では、各個人のPCに環境を構築する必要がありましたが、Codespacesを使用するとその必要がありません。 Visual Studio Code と完全に統合されているため、その強力な機能を利用できます。コードの補完、 シンタックス ハイライト、 リファクタリング 、 デバッグ といった機能はもちろんのこと、 拡張機能 をインストールしてカスタマイズすることも可能です。 この記事では GitHub Codespacesの概要を紹介しますが、詳細は公式のドキュメントも参照して下さい。 https://docs.github.com/ja/codespaces GitHub Codespacesの利用方法 GitHub Codespacesの利用はとても簡単です。 GitHub の リポジトリ ページにアクセスし、"Code"ボタンの下にある"Open with Codespaces"を選択するだけでCodespaceを作成できます。 Codespaceが作成されると、その リポジトリ 専用に GitHub が提供している クラウド 上に開発環境が構築され、アクセスすると Visual Studio Code がブラウザで立ち上がります。 しかも リポジトリ の ソースコード は既に git clone されていて、すぐにコードの編集や デバッグ を開始できます。また起動した Visual Studio Code はローカルで使用しているのとほぼ同じ機能が実現されています。普段から Visual Studio Code に慣れている開発者であれば、普段通りコードの編集や デバッグ が可能です。もちろん、コード補完、 シンタックス ハイライト、 リファクタリング 、 拡張機能 も利用できます。 GitHub Codespacesの便利な点 ここでは、自分が GitHub Codespacesを使っていて便利だと思った点をいくつか紹介していこうと思います。 Dev Containerを使った環境のカスタマイズ GitHub Codespacesでは、Dev Containerを使った開発環境のカスタマイズを行うことも可能です。 .devcontainer/devcontainer.json を作成して、Dev Container環境を作成することが出来ます。この設定ファイルでは、使用する基本イメージの指定、必要なツールのインストール、VisualStudio Codeの設定、ポートの開放などを定義できます。 先日、このテックブログにもDev Containerを使った Python の開発環境を構築する記事が公開されています。Dev Containerの設定についてはそちらが参考になるかと思います。 Dev Containerを使ってステップバイステップで作るPythonアプリケーション開発環境 Dev Container用の設定ファイルは、 リポジトリ に含めて共有することで、チーム全体で統一した開発環境を簡単に構築できます。 Dev Containerの設定については、 GitHub Codespacesにも説明があるので確認してみてください。 https://docs.github.com/ja/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers GitHub Codespacesでの 拡張機能 の扱い GitHub Codespaces上では起動した環境上でMarketplace から 拡張機能 のインストールが可能です。 もし普段、 Visual Studio Code の設定の同期機能を利用している場合であれば、 GitHub Codespacesとも設定を同期させることも可能です。VisualStudio Codeの設定の保存に GitHub を利用している場合、起動した GitHub CodeSpacesに同期している設定が自動的に読み込まれます。この機能とDev Containerを組み合わせることで柔軟に開発環境を構築することが可能となります。Dev Containerではチームで使う 拡張機能 を共通でインストールして、 Visual Studio Code の同期機能で個人の 拡張機能 をインストールするというような運用が可能となります。 GitHub Codespacesのパーソナライズの項目に説明があるので確認してみてください。 https://docs.github.com/ja/enterprise-cloud@latest/codespaces/customizing-your-codespace/personalizing-github-codespaces-for-your-account GitHub との統合 GitHub との統合も大きな特徴の一つです。起動した GitHub Codespacesの環境では起動した状態で git clone が実行されている状態であることは紹介しました。さらに、 GitHub への認証が完了している状態で起動しているので、 リポジトリ へのコードの git push といった操作を行なう際に追加の設定作業は不要です。起動して、コードを修正して、テストして、すぐにpushするという作業に集中できます。 GItHub 用の 拡張機能 もインストールされているため、Pull Requestの作成、マージ、コードレビュー、イシューの管理も行えます。 クラウド 上に開発環境があることのメリット ここまでで、 GitHub Codespacesの便利な機能を簡単に紹介しました。この他に GitHub Codespacesが クラウド 環境に開発環境が構築されることによるメリットもあります。 開発環境の構築が高速 まず一つ目として、新規プロジェクトを開始する際に、従来のローカル環境と比べて環境設定の時間を大幅に短縮できる点が挙げられます。新たに必要なツールやライブラリをインストールする手間が省け、すぐに開発に取りかかることができます。 開発で利用するマシンが自由 二つ目として、異なるOSやデ バイス での開発が容易になる点です。たとえば、普段は Mac を使って開発している人でもブラウザさえ起動できれば同一の環境がすぐ手に入ります。 開発環境の統一の実現 そして、最も大きな利点として、チームの効率化を挙げることができます。全てのチームメンバーが同じ開発環境を簡単に共有できるため、環境差による問題が大幅に減少します。また、Codespaceはブラウザ上で動作するため、どのデ バイス からでもアクセスできます。これにより、場所にとらわれずに作業が可能となります。 GitHub Codespacesの注意点と制限 使用料金はリソースの使用量によります。無駄な費用を避けるためには、使用しないCodespacesは閉じるなど、適切な管理が求められます。Codespacesは開発環境を起動している時間と利用しているストレージの料金の2種類が請求されます。使っていない環境は自動的に削除されるので、無駄な起動時間の請求はあまり発生しないと思います。しかし、ストレージの方は自動的に削除されないため注意が必要となります。 GitHub Codespacesの利用料金は以下のページを参照してください。 https://docs.github.com/ja/billing/managing-billing-for-github-codespaces/about-billing-for-github-codespaces また、一部のハードウェア依存の開発や、特定の開発を GitHub Codespacesでは行なうことが出来ません。例えば、高度な3Dグラフィックスを必要とする開発作業や、特別なハードウェアに依存した開発作業は、現在のところCodespacesではサポートされていません。 まとめ 今回は GitHub Codespacesについて紹介しました。 GitHub Codespacesは、 クラウド 上に開発環境を提供することで環境設定の手間を省き、どこからでも安全に開発を行うことができる便利なツールです。ただし、料金や対応できない開発がある事など一部の問題を理解した上で使用することが重要となります。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募お待ちしています。 ソリューションアーキテクト 執筆: @yamashita.tsuyoshi 、レビュー: 寺山 輝 (@terayama.akira) ( Shodo で執筆されました )
アバター
はじめに こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター の山下です。 みなさん GitHub Codespaces( https://github.co.jp/features/codespaces ) をご存知ですか? 昨年一般提供された GitHub で利用できる 統合開発環境 ( IDE )です。 GitHub Codespacesは、ブラウザ上で動作する開発環境となります。 GitHub から起動することができて、ブラウザが動く環境であれば、追加でソフトウェアのインストールを必要とせず、いつでもどこでも開発作業が可能です。 この記事では、その特徴、使い方、そして便利な点について解説します。特にチームの開発で開発環境を統一したい場合や素早く開発環境を用意したい場合には GitHub Codespaces を検討しても良いかもしれません。 GitHub Codespacesの特徴 GitHub Codespacesは、 クラウド 上に開発環境を構築し、 クラウド 上で完結する開発環境です。開発者はデ バイス や場所に依存せず、どこからでもコードの編集や実行が可能になります。従来の開発環境では、各個人のPCに環境を構築する必要がありましたが、Codespacesを使用するとその必要がありません。 Visual Studio Code と完全に統合されているため、その強力な機能を利用できます。コードの補完、 シンタックス ハイライト、 リファクタリング 、 デバッグ といった機能はもちろんのこと、 拡張機能 をインストールしてカスタマイズすることも可能です。 この記事では GitHub Codespacesの概要を紹介しますが、詳細は公式のドキュメントも参照して下さい。 https://docs.github.com/ja/codespaces GitHub Codespacesの利用方法 GitHub Codespacesの利用はとても簡単です。 GitHub の リポジトリ ページにアクセスし、"Code"ボタンの下にある"Open with Codespaces"を選択するだけでCodespaceを作成できます。 Codespaceが作成されると、その リポジトリ 専用に GitHub が提供している クラウド 上に開発環境が構築され、アクセスすると Visual Studio Code がブラウザで立ち上がります。 しかも リポジトリ の ソースコード は既に git clone されていて、すぐにコードの編集や デバッグ を開始できます。また起動した Visual Studio Code はローカルで使用しているのとほぼ同じ機能が実現されています。普段から Visual Studio Code に慣れている開発者であれば、普段通りコードの編集や デバッグ が可能です。もちろん、コード補完、 シンタックス ハイライト、 リファクタリング 、 拡張機能 も利用できます。 GitHub Codespacesの便利な点 ここでは、自分が GitHub Codespacesを使っていて便利だと思った点をいくつか紹介していこうと思います。 Dev Containerを使った環境のカスタマイズ GitHub Codespacesでは、Dev Containerを使った開発環境のカスタマイズを行うことも可能です。 .devcontainer/devcontainer.json を作成して、Dev Container環境を作成することが出来ます。この設定ファイルでは、使用する基本イメージの指定、必要なツールのインストール、VisualStudio Codeの設定、ポートの開放などを定義できます。 先日、このテックブログにもDev Containerを使った Python の開発環境を構築する記事が公開されています。Dev Containerの設定についてはそちらが参考になるかと思います。 Dev Containerを使ってステップバイステップで作るPythonアプリケーション開発環境 Dev Container用の設定ファイルは、 リポジトリ に含めて共有することで、チーム全体で統一した開発環境を簡単に構築できます。 Dev Containerの設定については、 GitHub Codespacesにも説明があるので確認してみてください。 https://docs.github.com/ja/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers GitHub Codespacesでの 拡張機能 の扱い GitHub Codespaces上では起動した環境上でMarketplace から 拡張機能 のインストールが可能です。 もし普段、 Visual Studio Code の設定の同期機能を利用している場合であれば、 GitHub Codespacesとも設定を同期させることも可能です。VisualStudio Codeの設定の保存に GitHub を利用している場合、起動した GitHub CodeSpacesに同期している設定が自動的に読み込まれます。この機能とDev Containerを組み合わせることで柔軟に開発環境を構築することが可能となります。Dev Containerではチームで使う 拡張機能 を共通でインストールして、 Visual Studio Code の同期機能で個人の 拡張機能 をインストールするというような運用が可能となります。 GitHub Codespacesのパーソナライズの項目に説明があるので確認してみてください。 https://docs.github.com/ja/enterprise-cloud@latest/codespaces/customizing-your-codespace/personalizing-github-codespaces-for-your-account GitHub との統合 GitHub との統合も大きな特徴の一つです。起動した GitHub Codespacesの環境では起動した状態で git clone が実行されている状態であることは紹介しました。さらに、 GitHub への認証が完了している状態で起動しているので、 リポジトリ へのコードの git push といった操作を行なう際に追加の設定作業は不要です。起動して、コードを修正して、テストして、すぐにpushするという作業に集中できます。 GItHub 用の 拡張機能 もインストールされているため、Pull Requestの作成、マージ、コードレビュー、イシューの管理も行えます。 クラウド 上に開発環境があることのメリット ここまでで、 GitHub Codespacesの便利な機能を簡単に紹介しました。この他に GitHub Codespacesが クラウド 環境に開発環境が構築されることによるメリットもあります。 開発環境の構築が高速 まず一つ目として、新規プロジェクトを開始する際に、従来のローカル環境と比べて環境設定の時間を大幅に短縮できる点が挙げられます。新たに必要なツールやライブラリをインストールする手間が省け、すぐに開発に取りかかることができます。 開発で利用するマシンが自由 二つ目として、異なるOSやデ バイス での開発が容易になる点です。たとえば、普段は Mac を使って開発している人でもブラウザさえ起動できれば同一の環境がすぐ手に入ります。 開発環境の統一の実現 そして、最も大きな利点として、チームの効率化を挙げることができます。全てのチームメンバーが同じ開発環境を簡単に共有できるため、環境差による問題が大幅に減少します。また、Codespaceはブラウザ上で動作するため、どのデ バイス からでもアクセスできます。これにより、場所にとらわれずに作業が可能となります。 GitHub Codespacesの注意点と制限 使用料金はリソースの使用量によります。無駄な費用を避けるためには、使用しないCodespacesは閉じるなど、適切な管理が求められます。Codespacesは開発環境を起動している時間と利用しているストレージの料金の2種類が請求されます。使っていない環境は自動的に削除されるので、無駄な起動時間の請求はあまり発生しないと思います。しかし、ストレージの方は自動的に削除されないため注意が必要となります。 GitHub Codespacesの利用料金は以下のページを参照してください。 https://docs.github.com/ja/billing/managing-billing-for-github-codespaces/about-billing-for-github-codespaces また、一部のハードウェア依存の開発や、特定の開発を GitHub Codespacesでは行なうことが出来ません。例えば、高度な3Dグラフィックスを必要とする開発作業や、特別なハードウェアに依存した開発作業は、現在のところCodespacesではサポートされていません。 まとめ 今回は GitHub Codespacesについて紹介しました。 GitHub Codespacesは、 クラウド 上に開発環境を提供することで環境設定の手間を省き、どこからでも安全に開発を行うことができる便利なツールです。ただし、料金や対応できない開発がある事など一部の問題を理解した上で使用することが重要となります。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募お待ちしています。 ソリューションアーキテクト 執筆: @yamashita.tsuyoshi 、レビュー: 寺山 輝 (@terayama.akira) ( Shodo で執筆されました )
アバター