TECH PLAY

NTTドコモビジネス

NTTドコモビジネス の技術ブログ

602

この記事は、 NTT Communications Advent Calendar 2024  7日目の記事です。 こんにちは!イノベーションセンターの外村です。 日頃は twada 塾 と呼ばれる社内向けソフトウェア開発研修を運営しています。最近チームを異動し、 ノーコード・ローコードで 時系列分析 AI を作ることができる Node-AI の開発にもジョインしました。 本記事ではフルマネージド開発環境である Google Cloud の Cloud Workstations についてその特徴と構築方法を述べたいと思います。 はじめに フルマネージド開発環境 Cloud Workstations の概要 Cloud Workstations の構築 Terraform を用いた Google Cloud の設定 idle_timeout と running_timeout host persistent_directories container Workstation のためのコンテナイメージのビルドとプッシュ おわりに はじめに みなさん、新しい部署やプロジェクトに参加したらまず何をしますか? そう、開発環境の構築ですよね。 でも、環境構築はなかなか大変です。 まずは開発用の端末を購入するところから始まります。 そして、端末が届いて意気込んでセットアップを始めても、手順がやたら複雑だったり、 最新バージョンではうまく動かないことがあります。結果、構築に1週間近くかかることも…。 さらに、「やっと動いた!」と思って開発を始めても、数週間や数ヶ月後に先輩社員の環境と動作が違うことに気づく…なんてこと、 経験ありませんか?そのうえ、当時は快適だった端末も数年後には性能不足や故障で交換が必要になったりするんですよね。 このような課題を解決してくれるフルマネージド開発環境がここ数年注目を集めています。 フルマネージド開発環境 フルマネージド開発環境とは、開発者が開発に専念できるように 開発環境の設定や管理をクラウドプロバイダーがサポートするサービスです。 これにより環境構築の手間から解放され、端末管理の負担が軽減されます。 フルマネージド開発環境は以下の特徴を持ちます。 即時利用可能: 開発環境を即座にセットアップできる。 端末性能に依存しない: 開発環境はクラウド上で動作するため、低スペックの端末でも快適に利用可能。 スケーラブル: 必要な分だけの性能を拡張・縮小可能。 一貫性のある環境: チームメンバー全員が同じ設定の環境で作業できる。 今回は私が日頃利用している Google Cloud の Cloud Workstations について、特徴やその構築方法をお伝えします。 Cloud Workstations の概要 Cloud Workstations は、Google Cloud が提供するフルマネージド開発環境です。 事前に開発環境の設定や構成を定義しておき、利用者に払い出すことができます。 ブラウザベースの IDE やローカルのコードエディタ(IntelliJ IDEA Ultimate、VS Code など)を通じてアクセスできます。 Google Cloud ならではの特徴として、Google Cloud の IAM を用いたアクセス制御や、 Gemini Code Assist など、他の Google サービスとの連携が円滑になっています。 Cloud Workstations の全体像は以下のようになっています。 Workstation Cluster: 特定のリージョンに置く Workstation のグループです。(Cluster というと Kubernetes Cluster を想像しますが、関係はありません) Workstation Config: 「Workstations の構成」と表現されています。開発環境のテンプレート。開発環境の性能や利用するコンテナイメージなどを定義します。 Workstation: 実際にアクセスする開発環境。実態は Google Compute Engine (GCE) のインスタンスです (1 Workstation につき 1 GCE インスタンスが立ち上がってきます)。 Cloud Workstations の構築 ここで構築する Workstation のポイントをあらかじめ決めておきます。 ポイント1: Workstation の GCE インスタンスについて、IP 予約料金の削減やセキュリティを考慮してパブリック IP アドレスが付与されないようにする。 ポイント2: Workstation 利用者にはそれぞれの Workstation を払い出す。払い出される Workstation は同一の環境とする。 ポイント3: Workstation 利用者は、自分に払い出された Workstation のみ閲覧・利用できる (他者のものを閲覧・確認できない)。 ポイント4: Workstation の開発環境は、カスタマイズしたコンテナイメージにより構築される。 ポイント1 については ドキュメント に記載があり、 ネットワークで限定公開の Google アクセス、もしくは Cloud NAT を構成する必要があります。今回は Cloud NAT を構成します。 また、ポイント4 については、Google Artifact Registory にプッシュしたコンテナイメージが利用されるように設定します。 この方法ではカスタマイズしたコンテナイメージだけでなく、プルするためのサービスアカウントも必要になります。 コンテナイメージのプッシュについては手動で行うこととします。 構成は以下のイメージですね。 これらの構成を効率よく管理するために、インフラストラクチャの設定をコードとして管理する Infrastructure as Code(IaC) の考え方を採用します。 そのためのツールとして、Terraform を用います。 Terraform は HashiCorp が提供するオープンソースのインフラストラクチャ管理ツールで、クラウドリソースの構成や管理を宣言的なコードで実現できます。 これを使えば、ネットワーク設定やコンテナイメージリポジトリなどを効率よく構築し、設定の再現性や変更管理が容易になります。 Terraform を用いた Google Cloud の設定 それでは Google Cloud の設定を Terraform で実施していきましょう。 構築にあたり、事前に自身のアカウントに対して IAM で オーナー と サービス アカウント トークン作成者 のロールを付与しておきます。 また、対象プロジェクトに gcloud auth application-default login でログインしておきましょう。 Terraform の Provider (特定のクラウドプロバイダーやサービスと連携するためのプラグイン) については、 google と google-beta を用います。 手元のツールのバージョンは以下です。 Google Cloud SDK 495.0.0 Terraform v1.9.2 on darwin_arm64 + provider registry.terraform.io/hashicorp/google v6.11.2 + provider registry.terraform.io/hashicorp/google-beta v6.11.2 Cloud Storage に tfstate を保存するためのバケットを作成しましょう。 gcloud storage buckets create gs://workstations-tfstate-bucket \ --location = asia-northeast1 \ --default-storage-class = standard バケットが作成されたことを確認しておきます。 $ gcloud storage buckets list --format =" table(name, location) " NAME LOCATION workstations-tfstate-bucket ASIA-NORTHEAST1 ここからは Terraform 用のファイル作成をします。 Terraform 用のフォルダを用意して provider.tf、backend.tf、variables.tf を作成してください。 なお、variables.tf には各種パラメータ定義しています。必要に応じて変更してください。 # provider.tf terraform { required_providers { google = { source = "hashicorp/google" version = "~> 6.1" } } required_version = ">= 1.3.0" } provider "google" { project = var.project_id region = var.region } provider "google-beta" { project = var.project_id region = var.region } # backend.tf terraform { backend "gcs" { bucket = "workstations-tfstate-bucket" prefix = "terraform/state" } } # variables.tf variable "project_id" { default = "your project ID" # ご自身が利用するプロジェクト ID に変更してください。 } variable "region" { default = "asia-northeast1" # リージョンを変更したい場合は変更してください。 } variable "workstation_cluster_machine_type" { default = "e2-standard-4" # 利用者に払い出される Workstations のマシンスペック (GCE のスペックを参照してください) } variable "workstation_cluster_persistent_disk_size_gb" { default = 100 # 利用者に払い出される Workstations の SSD ディスクサイズ (GB) } variable "user_accounts" { description = "List of user email accounts that need Workstations" type = list ( string ) default = [ # 利用者の Google アカウントを列挙してください "ws.user01@gmail.com" , "ws.user02@gmail.com" , "ws.user03@gmail.com" , ] } 肝である main.tf をステップに分けて作成します。 まずは main.tf に Workstations が立ち上げるネットワーク部分を記載していきましょう。 # main.tf # VPC ネットワーク resource "google_compute_network" "workstation_network" { name = "workstation-cluster" auto_create_subnetworks = false project = var.project_id } # サブネット resource "google_compute_subnetwork" "workstation_subnet" { name = "workstation-cluster" ip_cidr_range = "10.0.0.0/24" network = google_compute_network.workstation_network.name private_ip_google_access = true } # Cloud NAT 用 IP アドレス resource "google_compute_address" "workstation_nat_ip" { name = "workstation-nat-ip" } # Cloud Router resource "google_compute_router" "workstation_router" { name = "workstation-router" network = google_compute_network.workstation_network.name } # Cloud NAT resource "google_compute_router_nat" "workstation_nat" { name = "workstation-nat" router = google_compute_router.workstation_router.name nat_ip_allocate_option = "MANUAL_ONLY" source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES" nat_ips = [ google_compute_address.workstation_nat_ip.self_link ] min_ports_per_vm = 64 udp_idle_timeout_sec = 30 tcp_established_idle_timeout_sec = 1200 } 上記はポイント1 に関連する記述ですね。 この記述は、workstation-router というルータを作成し、同時に Cloud NAT が有効化される状態を定義しています。 ここではサブネットを 10.0.0.0/24 としています。 内部 IP アドレスは Workstation Cluster や Workstation に払い出されるので、 サブネットは余裕をもった大きさにしておくと良さそうです。 つづいて以下の記述を追加します。 # main.tf へ追記 # コンテナイメージを格納するためのリポジトリ resource "google_artifact_registry_repository" "workstation_image_repository" { repository_id = "workstation-image" format = "DOCKER" location = var.region description = "Artifact Registry for Cloud Workstations base images" } # コンテナイメージをプルするためのサービスアカウント resource "google_service_account" "workstation_cluster_service_account" { account_id = "workstation-cluster-sa" display_name = "Workstation Cluster Service Account" } # サービスアカウントへリポジトリ読み取り権限を付与 resource "google_project_iam_member" "workstation_cluster_service_account_roles" { project = var.project_id role = "roles/artifactregistry.reader" member = "serviceAccount:$ { google_service_account.workstation_cluster_service_account.email } " } 上記はポイント4 に関連する部分です。 Artifact Registory に workstation-image というリポジトリを作成しています。 加えて workstation-cluster-sa というサービスアカウントを作成し、Artifact Registory の読み取り権限を付与しています。 つづいての追記内容はこちら。 # main.tf へ追記 # Workstation Cluster resource "google_workstations_workstation_cluster" "workstation_cluster" { workstation_cluster_id = "workstation-cluster" provider = google-beta network = google_compute_network.workstation_network.id subnetwork = google_compute_subnetwork.workstation_subnet.id location = var.region labels = { environment = "development" } } # Workstation Config resource "google_workstations_workstation_config" "workstation_config" { provider = google-beta workstation_cluster_id = google_workstations_workstation_cluster.workstation_cluster.workstation_cluster_id workstation_config_id = "workstation-config" location = var.region idle_timeout = "3600s" # 1 hour running_timeout = "43200s" # 12 hours host { gce_instance { machine_type = var.workstation_cluster_machine_type disable_public_ip_addresses = true service_account = google_service_account.workstation_cluster_service_account.email # リポジトリ読み取り用サービスアカウント } } persistent_directories { mount_path = "/home" gce_pd { size_gb = var.workstation_cluster_persistent_disk_size_gb fs_type = "ext4" disk_type = "pd-ssd" reclaim_policy = "DELETE" } } container { image = "$ { var.region } -docker.pkg.dev/$ { var.project_id } /$ { google_artifact_registry_repository.workstation_image_repository.repository_id } /workstation-custom:latest" # Workstations コンテナイメージ (リージョン-docker.pkg.dev/プロジェクトID/workstation-image/workstation-custom:latest) } } 上記では Workstation Cluster と Workstation Config を定義しています。 とくに Workstation Config にはいくつかのパラメータを深掘りします。 idle_timeout と running_timeout どちらもリソース連続稼働によるコストを削減するための設定です。 idle_timeout は Workstations が操作を受け付けなかったときに一時停止されるまでの時間、 running_timeout は Workstations が一度立ち上がってから VM がシャットダウンされるまでの時間を定義できます(0にすることで機能をオフにもできます) 。 host Workstations が立ち上がるための GCE インスタンスのスペックや設定を記述します。 この中の service_account はポイント4 に関連しており、 先ほど記載したコンテナイメージプル用のサービスアカウントが参照されるように記載しています。 persistent_directories GCE インスタンスにマウントされる永続化ディスクの定義です。 /home にマウントしています。 ここは注意点ですが、Workstations がシャットダウンされると GCE インスタンスは削除されます。 再起動時に GCE インスタンスは再作成されますが、以前に作成したファイルなどは消えてしまうのです。 /home に永続化ディスクをマウントすることで、再起動時の GCE インスタンスでも /home での作業を引き継ぐことができます。 ただし reclaim_policy を DELETE に設定しているため、 Workstation が削除されたときに永続化ディスクも削除される設定になっている部分は意識しておきましょう。 container Workstation のコンテナイメージです。ポイント4 に関連しています。 以前のステップで記載した workstation-image リポジトリを参照しています。 このリポジトリの workstation-custom イメージの latest を使用する設定になっています。 (workstation-custom というイメージは Terraform の手順とは別でプッシュします) それでは、最後の追記内容です。 # main.tf へ追記 # アカウント の Workstation resource "google_workstations_workstation" "workstations" { provider = google-beta for_each = toset (var.user_accounts) workstation_id = "workstation-$ { replace ( replace (each.key, "@" , "-" ), "." , "-" ) } " workstation_cluster_id = google_workstations_workstation_cluster.workstation_cluster.workstation_cluster_id workstation_config_id = google_workstations_workstation_config.workstation_config.workstation_config_id location = var.region lifecycle { replace_triggered_by = [ google_workstations_workstation_config.workstation_config.id ] # Workstation Config } } # アカウントに対して workstations オペレータ閲覧者の権限を付与 resource "google_project_iam_member" "workstation_operation_viewer" { for_each = toset (var.user_accounts) project = var.project_id role = "roles/workstations.operationViewer" member = "user:$ { each.key } " } # アカウントごとに、自分の Workstations のみに対して workstations ユーザ権限を付与 resource "google_workstations_workstation_iam_member" "workstation_iam_user" { provider = google-beta for_each = google_workstations_workstation.workstations workstation_id = each.value.id workstation_cluster_id = google_workstations_workstation_cluster.workstation_cluster.workstation_cluster_id workstation_config_id = google_workstations_workstation_config.workstation_config.workstation_config_id location = var.region role = "roles/workstations.user" member = "user:$ { each.key } " } 上記では、Workstation 作成とアカウントへの権限付与を実施しています。ポイント2 および ポイント3 に関連する記述ですね。 for_each を用いて variables.tf に定義していた user_accounts のアカウントそれぞれに Workstation や権限付与の処理を行っています。 Workstation に対しては lifecycle の replace_triggered_by を設定しておきました。 Workstations にすでに参照されている Workstation Config を更新する際、依存関係が存在するせいで更新ができないと怒られる場合があります。 トリガーに Workstation Config を指定することで、Workstation Config の更新とともに Workstation の再作成され、更新が可能になります。 Workstation が一度削除されることになります (現状の設定だと永続化ディスクも一度削除されます) が、管理上都合がよいので設定しています。 ポイント3 について、あるユーザに自分の Workstation のみを使わせたい場合は以下の権限整理をすることで実現できます。 プロジェクトに対して roles/workstations.operationViewer Workstation に対して roles/workstations.user 必要なファイルは完成しました! 最後に terraform apply をしてあげれば Google Cloud の設定は完了です。 Workstation Cluster には 20分以上かかることもあるため、実行時間が長くても焦らないでください (自分は 30分かかりました)。 Workstation のためのコンテナイメージのビルドとプッシュ Google Cloud の設定は完了しましたが、最後にもう一仕事残っています。 Workstation のためのコンテナイメージがまだでしたね。 公式ドキュメント を参考に進めていきましょう。 まずベースイメージですが、こちらも 公式 により公開されています。 ここでは VS Code ベースの us-central1-docker.pkg.dev/cloud-workstations-images/predefined/code-oss:latest を使用します。 今回はこのイメージを拡張し、新たに Terraform がインストールされたイメージを作成します。 Dockerfile を以下のように作成します。 # Dockerfile FROM us-central1-docker.pkg.dev/cloud-workstations-images/predefined/code-oss:latest # 環境のアップデートと Terraform のインストール RUN apt-get update && \ apt-get install -y gnupg software-properties-common curl && \ curl -fsSL https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg && \ echo " deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $( lsb_release -cs ) main " | tee /etc/apt/sources.list.d/hashicorp.list && \ apt-get update && \ apt-get install -y terraform && \ terraform -version さらに拡張したい場合は、上記にならってレイヤを追加していけばよいです。 また、処理が多い場合はシェルスクリプトファイルに分けたくなってくるかもしれません。 その場合、コンテナの /etc/workstation-startup.d に シェルスクリプトを追加しておくことで 辞書順に実行を行ってくれる みたいですね。 それではコンテナイメージのビルドからプッシュまで行っていきます。 REGION や PROJECT_ID はご自身の環境に合わせて変更してください。 # 変数の定義 set REGION " asia-northeast1 " # リージョン set PROJECT_ID " Your Porject ID " # プロジェクトID set REPOSITORY_NAME " workstation-image " # Artifact Registry リポジトリ名 set IMAGE_NAME " workstation-custom " # Docker イメージ名 set ADDITIONAL_TAG " latest " # 追加タグ # 小文字の UUID を生成 (ハイフンなし) set VERSION (uuidgen | tr -d ' - ' | tr ' A-Z ' ' a-z ' ) # イメージ タグの構築 set IMAGE_URI " $REGION -docker.pkg.dev/ $PROJECT_ID / $REPOSITORY_NAME / $IMAGE_NAME : $VERSION " set ADDITIONAL_URI " $REGION -docker.pkg.dev/ $PROJECT_ID / $REPOSITORY_NAME / $IMAGE_NAME : $ADDITIONAL_TAG " # Docker ビルド docker build --platform linux/amd64 -t " $IMAGE_URI " . # タグ追加 docker tag " $IMAGE_URI " " $ADDITIONAL_URI " # Docker Push docker push " $IMAGE_URI " docker push " $ADDITIONAL_URI " latest のタグを持つイメージがプッシュされることを確認しておきましょう。 gcloud artifacts docker images list " $REGION -docker.pkg.dev/ $PROJECT_ID / $REPOSITORY_NAME / $IMAGE_NAME " \ --include-tags --format =" table[createTime, tags](createTime, tags) " CREATE_TIME TAGS 2024-11-27T09:32:02 9f8f7c0d69c04be095d2c4ff5d4cfb1a,latest 構築が完了しました! ご自身の Workstation が起動することを確認してください。 また、ターミナルを用いるとインストールされた terraform を確認できると思います。 おわりに フルマネージド開発環境として、Cloud Workstations を構築しました。 ここでまとめた構築方法をもとに、今後は twada 塾の受講生向け開発環境の払い出しを進めていく予定です。 それでは、明日の記事もお楽しみに!
アバター
こんにちは。NTTコミュニケーションズでエバンジェリストをやっている西塚です。今日が10年目の結婚記念日です。 この記事は、 NTT Communications Advent Calendar 2024 6日目の記事です。 情報通信白書 によると、デジタルデータの活用が企業経営に対して効果があると複数の先行研究で明らかにされています。 ビッグデータを活用している企業はそうでない企業に比べて、イノベーションの創出が統計学的に有意な差で多いと言われています。 私自身もNTTコミュニケーションズにおいて全社データ基盤を立ち上げて、社内システムからデータを収集し、 データサイエンティストと協力しながら、蓄積された膨大なデータを活用してビジネス価値を生み出す取り組みを行ってきました。 さて、近年の生成AIブームに乗り、データサイエンティスト達は従来の機械学習・AI技術に加えて生成AIをデータ活用に利用する取り組みをしています。 最終的に生成AIはデータサイエンティストの仕事を奪うことになるのでしょうか? 結論から言えば、生成AIはデータサイエンティストの仕事を一部奪うかもしれません。 しかし、それはデータサイエンスそのものの重要性が低下するという意味ではなく、むしろ「仕事の民主化」が進むことを意味します。 生成AIをデータ基盤の中で活用することで、データを効率的に管理し、活用できる環境が整うのです。 全社データ基盤の紹介 セキュリティ(Security) 高性能(High Performance) 安定性(Stability) 使いやすさ(Usability) 可観測性(Observability) Snowflakeによるクラウドリフトとデータ連携 基盤だけでは活用は進まない データを集めるプロセスの膨大な負担 蓄積するだけでは価値を生まない データサイエンスの民主化 生成AIはデータサイエンスのどの仕事を置き換えるか? 非構造化データの構造化 データクレンジング 探索的データ分析(EDA: Explanatory Data Analysis) 特徴量エンジニアリングの支援 PoC紹介 最後に 全社データ基盤の紹介 2020年に構築を開始した全社データ基盤は、当初オンプレで組み上げました。 データウェアハウス(DWH: Data Ware House)としてHadoopを採用し、データ活用のための標準インターフェースとしてTrino(旧Presto)を提供しました。 Trinoの取り組みについては2021年のアドベントカレンダーにて 高性能分散SQLエンジン「Trino」最速ガイド として紹介しました。 データサイエンティストの活躍については 社内でデータ分析コンペティションを開催しました で紹介しています。 全社データ基盤は「DLX(Datalake for Everything)」と名付けられ、「セキュリティ(Security)」「高性能(High Performance)」「安定性(Stability)」「使いやすさ(Usability)」「可観測性(Observability)」の5つの信条のもとに設計されました。 セキュリティ(Security) もっとも重要な信条は、社内機密データを取り扱う際のセキュリティの確保です。 情報漏えいを防ぐための厳格なセキュリティ対策が施されており、データの機密性と安全性が最優先されています。 これにより、企業の重要情報が不正アクセスや漏えいのリスクから守られます。 社外のセキュリティ会社によるペネトレーション試験を定期的に行い、セキュリティ向上および安全確保を実施しています。 高性能(High Performance) 高性能とは、大量のデータに対する高速な処理能力を指します。大規模データ分析の要求にこたえられるよう、HadoopやTrinoなどの分散処理フレームワークが採用されています。 安定性(Stability) 安定性の確保は、システムの中断なく安定したデータ活用を可能にします。障害に強い冗長設計やフェイルオーバー機能により、高い可用性を実現しています。 使いやすさ(Usability) 使いやすさは、データ活用の敷居を下げることを目指しています。Trinoの標準SQLによる一貫したデータアクセスが可能となり、データ分析のスピードと精度が飛躍的に向上しました。 可観測性(Observability) 可観測性とは、システム状況を的確に監視し、運用を最適化することを意味します。各種メトリクスの一元的な監視で、迅速な障害対応や負荷分散が可能になります。 こうした5つの信条に基づいて設計することで、セキュリティを確保しつつ、高速で安定した全社データ基盤が実現しました。 社内のユーザは組織の壁を超えて効率的かつ安全にデータを活用できるようになりました。 Snowflakeによるクラウドリフトとデータ連携 2023年からはクラウド型データウェアハウス(DWH)としてSnowflakeを採用し、オンプレからのクラウドリフトを進めています。 SnowflakeはSnowflake同士でデータシェアリング(データ共有)する機能を有しています。 データのメタデータや権限情報のみ共有する仕組みを採用しているため、データを物理的にコピーせずにリアルタイムでのデータ共有が可能になっています。 実は、NTTグループの間ではSnowflakeによる会社を超えたデータ共有の事例が進んでいます。 我々のデータ基盤も、主に以下の2つのSnowflakeと接続しています。 NTT持株のデータ基盤との接続: 共通系ITシステムのデータについて持株と共有しています NTTドコモのデータ基盤との接続: ドコモとの一部事業統合に伴い、「ドコモビジネス」の運営に伴う共同利用宣言に基づいて、NTTドコモのデータ基盤とデータ共有しています 基盤だけでは活用は進まない 全社データ基盤の構築に伴い、以下のような課題に直面しました。 データを集めるプロセスの膨大な負担 データを集約し、正確に整理する作業はデータ活用の約8割の稼働を占めるとも言われています。 データエンジニアの育成を進めていますが、稼働が非常にかかるところです。 蓄積するだけでは価値を生まない データがデータベースに眠っているだけでは、ビジネスに役立つ知見を得ることはできません。 ビッグデータを活用するためには、高度な分析や洞察を引き出すデータサイエンスの力が求められます。 従来のデータエンジニアリングやデータサイエンスは、専門知識を持つ人材に依存していました。 しかし、生成AIが登場したことで、この構造が大きく変わろうとしています。 データサイエンスの民主化 近年のLLM(大規模言語モデル)の技術進化により、自然言語対話によるデータサイエンス業務の民主化が進みつつあります。 例えば、「Function Calling」によりLLM側で適切なタスクを選択し、関数を呼び出すことが可能となっています。 この機能を活用することで、LLMと全社データ基盤を接続し、自然言語を介してデータを操作するユーザーインターフェースを実現できます。 このような考えはSnowflakeを始めとしたクラウドDWH製品においても、一般的になっています。 Snowflake社は先日11/20に Anthropic社のClaude 3.5 Sonnetのモデルがデータベース上で使えるようになることを 発表 しました 。 また、Databricks社は 独自のLLM(DBRX) の開発を進めています。 各種のDWH製品に自然言語によるデータ探索やグラフ描画機能が搭載されつつあり、このようなインターフェースが主流になることが想定されています。 生成AIはデータサイエンスのどの仕事を置き換えるか? 生成AIは、以下のような業務を効率化・自動化する可能性を秘めています。 非構造化データの構造化 非構造化データ(例: テキストや画像)を表形式のデータに変換し、分析可能な形に整える。 データクレンジング 不完全なデータを補完したり、投入後のデータ処理(ETL: Extract, Transform, Load)のアシストを行う。 探索的データ分析(EDA: Explanatory Data Analysis) 異常値やトレンドを検出し、日々のレポート作成を自動化する。 特徴量エンジニアリングの支援 モデル精度を向上させるための特徴量設計をサポートする。 これらのタスクは従来、データサイエンティストやエンジニアが多大な時間と労力をかけて行っていたものです。 しかし、生成AIは疲れ知らずであり、コストも安いため、これらの仕事の効率化ができます。 企業データは量が膨大ですので人間が見るのは非人道的です。 ETLやEDAなどは、寝てる間に生成AIに任せましょう。 将来的には、生成AIがデータ基盤内で日々蓄積される膨大なデータを整理し、異常値を検出し、必要に応じて分析の方向性を提案してれるような世界観を目指していきたいと考えています。 まとめると、生成AIによるデータサイエンスの民主化とは以下のステップで表されます。 非構造化データの処理やクレンジングの自動化で手間を削減 データ分析結果の可視化や異常検知において生成AIのアシストで専門知識を不要に ビジネスパーソンが対話的にデータを活用し、インサイトを得る環境が整備される PoC紹介 最後に、夢物語を語ってるのではなく実際に動くPoCがあることを紹介します。 我々のデータ基盤のアクセスログに対し、異常値を検出し対話的に深掘りができるチャットbotに人格を付与してslackに住まわせています。 以下の会話は、異常とされたアクセスログについて自然言語で深掘りしています。 以下のようなシステムセッティングで動かしています。 非常に面白いユースケースだと個人的に思います。 最後に 生成AIによる企業データの利用においては、RAGによる非構造データの利用がより注目されているように感じますが、 データ基盤との接続にこそ価値があると考えています。 データエンジニアやデータサイエンティストの仕事を肩代わりし、その役割をさらに高度化する可能性があります。 また、生成AIはデータサイエンスの民主化を加速し、専門的なスキルを持たない人々でもデータの価値を引き出せる未来を実現します。 これにより、データ活用の裾野が広がり、企業全体でのデータドリブンな意思決定が進みます。 私たちは引き続き、この可能性を追求していきたいと考えています。
アバター
この記事は、 NTT Communications Advent Calendar 2024 4日目の記事です。 はじめに この記事はコミュニケーション&アプリケーションサービス部でビジネスdアプリを開発している木村、立木、富田、西谷の共同執筆です。 今回は、NTTコミュニケーションズで提供するモバイルアプリ、「ビジネスdアプリ」のアーキテクチャに焦点を当て、サーバレスサービスをどのように活用しているかを2回にわたって紹介します。 この記事(前編)では、開発背景やサーバレスサービスを活用したアーキテクチャの概要を中心に解説します。後編では、具体的な運用やCI/CDの仕組みに焦点を当てる予定です。 なお、本記事の内容は2024年8月2日にGoogle Cloud Next Tokyo '24で発表した講演をベースに再構築したものです。 講演資料はこちら 目次 はじめに 目次 ビジネスdアプリとは? 内製開発の工夫 開発言語の統一 機能で担当割り サーバレスサービスの導入 サーバレスのメリット サーバレスサービスを使用する上での工夫 アーキテクチャの概要 コンピューティング ストレージ データ管理 行動分析 終わりに ビジネスdアプリとは? 「 ビジネスdアプリ 」は、ドコモのビジネスユーザーを主な対象としたポータルアプリです。 仕事中・プライベートどちらにも使えるお得な特典や、経営層向け・従業員向けに、最新ニュースやオリジナルコンテンツを提供し、業界ごとに役立つ情報をスムーズに取得できます。 また、NTTコミュニケーションズが提供する業務効率化につながるサービス(ビジネスdシリーズ)の無料プラン/無料版をアプリ上で簡単に申し込みができ、本格導入を検討できる仕組みを実現しています。 ビジネスdアプリでは、AndroidとiOSの両方に対応し、どのデバイスでも統一された操作感を提供しています。 これらの機能を、わずか3か月で構築するために工夫したアーキテクチャの詳細をこれからご紹介します。 内製開発の工夫 ビジネスdアプリは、社員を中心としたスクラムでの内製開発となっています。 そこで、主に以下の3つの工夫を取り入れました。 開発言語の統一 機能で担当割り サーバレスサービスの導入 開発言語の統一 全ての層を以下の通りJavaScript系の技術で統一しました。 フロントエンド: React.js、Vue.js バックエンド: Node.js モバイルアプリ: React Native これにより、フロントエンド、バックエンド、モバイルアプリのコードベースを統一し、開発チームの誰もがどの機能も開発できるようにしました。この統一性により、スムーズなコミュニケーションと短期間での開発を進めることができます。 また、React Nativeを採用した理由として、1つのソースからiOS/Androidのアプリを一度に開発できるということもあります。 機能で担当割り チームメンバーに対して機能単位の担当を割り当て、フロントエンドからバックエンド、モバイルアプリまで同じ担当が一貫して同じ機能の開発をするようにしました(例:ユーザー認証やプッシュ通知など)。これにより、各メンバーがシステム全体を理解しやすくなり、コミュニケーションコストを大幅に減らすことができました。 また、属人化を防ぐため、各種ローテーション、ドキュメント化、API化するなどして全体で共有する工夫を行なっています。 サーバレスサービスの導入 短期間の構築であったため、基本的に開発者はプログラム開発に集中する環境を整える必要がありました。「ビジネスdアプリ」では、その解決策として、Google Cloudのサーバレスサービス、フルマネージメントサービスを全面的に採用し、サーバーの構築や運用にかかる手間を最小限に抑えました。 たとえば、Google App Engine(以下、App Engine)やCloud Firestore(以下、Firestore)を使うことで、オートスケールに対応できます。 ここからはサーバレスサービスの導入について解説します。 サーバレスのメリット 「ビジネスdアプリ」では、Google Cloudのサーバレスサービスを最大限に活用しました。特にメリットを感じたのは以下の4点です。 構築作業の軽減 冗長化やスケーリング設定など、従来なら時間がかかる作業を簡易にできます。たとえばデータベースだと、FirestoreやCloud Spanner(以下、Spanner)を使うことで冗長化やスケーリングなどの対応が不要になり、プログラムに集中できました。 運用監視の簡略化 Google Cloudが提供するサーバレスサービスは障害が発生しても自動復旧します。また、Google Cloudが提供するモニタリング機能を活用することで、監視に関わる開発を減らすことができました。 EOL(End of Life)検討が不要 他社が多数導入しているサーバレスサービスを採用することで、Google Cloudによる長期的なサービス提供が期待でき、ライフサイクル管理の負担を軽減しました。 セキュリティ対策の簡易化 サーバレスサービスのセキュリティ確保はGoogle Cloudが対応しているため、IAMなどの権限管理を適切に行う必要はありますが、システム全体のセキュリティ対策の検討が簡易化できます。 サーバレスサービスを使用する上での工夫 サーバレスサービスには多くのメリットがありますが、注意すべき点もいくつか存在します。これらの点に対して、私たちのチームが工夫した点を紹介します。 デプロイ作業の自動化 サーバレスサービスのデプロイは容易ですが、それでも人手で繰り返しのデプロイを行うと稼働がかかります。そのため、GitHub Actionsを活用したCI/CDパイプラインを構築し、デプロイ作業を完全に自動化しました。 エンドツーエンド監視 Google Cloudの障害は Google Cloud Service Health で確認できますが、掲載されるより前に障害が発生している場合があります。そのため、サーバレスサービスの障害の早期検知のためにユーザー操作を疑似することで監視する、エンドツーエンドの監視を定期的に自動実行しています。これにより、ユーザー体験に影響を与える問題を素早く検出し、対応可能にしました。 リリースノートの定期的な確認 サーバレスサービスは頻繁にアップデートされます。破壊的なアップデートはもちろん、新機能によって今よりやりやすくなるという場合もあるため、リリースノートを定期的に確認しサービス変更時に迅速に対応する必要があります。 セキュリティ監視の実施 セキュリティ対策は基本的にGoogle Cloudが行なっているとはいえ、システム全体のセキュリティを確保するために、ログのチェックなどは開発者が定期的に確認する必要があります。そこで、Cloud Loggingを活用してアラート通知を設定し、問題が発生した際にすぐ対応できる仕組みを整えています。 サーバレスサービスに関する学習 サーバレスサービスはWebで公開されている情報が基本的に少ないため、Google Cloudが提供している研修を受講したり、Google Cloudに関する資格取得を社員の目標とすることで、チームメンバー全員が一定の技術水準を確保できるようにしています。 アーキテクチャの概要 本アプリのアーキテクチャでは、以下の技術スタックを採用し、シンプルかつ拡張性のある設計を目指しました。 コンピューティング 通常時の処理はApp Engineを採用し、一時的に高負荷な処理が必要になる場合にはCloud Runを採用しました。 App Engine:Google Cloudが提供する完全マネージド型のサーバレスプラットフォーム。 Cloud Run:コンテナ化されたアプリケーションをデプロイし、オンデマンドでスケーリングを行うサーバレスサービス。利用したリソース分だけ課金されるため、コスト効率も高く、負荷が急増する場面でも柔軟に対応可能です。 ストレージ Cloud Storage:オブジェクトストレージとして大容量データの保存をサポートするサービス。本アプリでは、画像、動画、その他の静的ファイルを管理しています。 データ管理 複雑なクエリを必要としない部分にはFirestoreを、複雑なクエリが求められる部分にはSpannerを採用し、それぞれの特性を活かしたデータ管理を実現しました。 Firestore:NoSQLのリアルタイムデータベースで、高速な読み書き性能と高いスケーラビリティを備えています。 Spanner:分散型リレーショナルデータベース(RDBMS)で、グローバル規模の一貫性と高可用性を備えています。 行動分析 Google Analytics:ユーザーの行動データを収集・分析するツール。本アプリでは、クリックイベント等を集計しています。 BigQuery:大規模データを分析するGoogle Cloudのデータウェアハウス。Google AnalyticsからBigQueryにログをシンクさせることで、ユーザーの操作履歴やイベントデータの詳細を分析しています。 それぞれの詳細および説明を割愛した機能については後編で解説しますのでお楽しみに。 終わりに 今回の記事では、ビジネスdアプリの紹介やアーキテクチャの概要、サーバーレスアーキテクチャのメリットについて紹介しました。 2月ごろに公開予定の後編の記事では、大規模なトラフィックを処理するためのアーキテクチャや、行動データ収集に関するアーキテクチャについて、より細かく解説する予定です。 また、現在ビジネスdアプリの開発チームでは、 生成AIを活用した開発効率化 を検討しております。具体的には、コード生成やテストの自動化、ログ分析の効率化といった分野での応用を検討しており、生成AIを導入することで、開発スピードの向上やチームの負担軽減を目指しています。 まだ模索中の取り組みではありますが、良い成果が得られれば、そちらも後日実際の効果や導入プロセスの詳細をブログ記事として皆さまにお伝えしたいと考えています! そんな ビジネスdアプリ は 11月29日に新しくタスク管理と社内報の機能をリリースしました! どちらも基本的なタスク管理(ToDo管理)機能や社内報の機能(グループ内のお知らせ配信・権限設定など)が含まれており、追加の申し込みなしで無料で使うことができます。 その他にも、お得なクーポンや中小企業向けのニュースコンテンツもあるので、もし興味があれば以下のリンク・QRコードからダウンロードしてみてください! ダウンロードリンク ちなみにタスク管理機能・社内報機能の開発においても、開発の上での改善(デプロイ自動化など)を継続的に実施しているため、機会があれば別記事で紹介できればと思っています。 それでは、明日の記事もお楽しみに!
アバター
こんにちは、情報セキュリティ部の原田とイノベーションセンターの竹中です。この記事では、モバイルネットワークのユーザプレーン技術である SRv6 MUP(Segment Routing over IPv6 Mobile User Plane)の解説と社内で行った検証についてご紹介いたします。 モバイルネットワークのアーキテクチャとSRv6 MUP 従来のモバイルネットワーク SRv6 Mobile User Plane 5Gネットワークへの SRv6 MUP の適用 MUP コントローラの設計 MUP コントローラの構成 PDU セッション確立時のシーケンス 検証 IS-IS の設定 IS-IS の確認 MUP Segment の設定 BGP による経路情報の広告 BGP の設定確認 疎通確認 UE-DN 間の通信 UE同士の折り返し通信 おわりに モバイルネットワークのアーキテクチャとSRv6 MUP SRv6 MUP とは、SRv6のネットワークプログラマビリティにより、モバイルネットワークのユーザープレーン (U-Plane) をシンプルかつ柔軟に構築するための技術です。 従来のモバイルネットワーク SRv6 MUP の詳説の前に、まずは現状のモバイルネットワークのアーキテクチャについて解説します。モバイル端末が通信するとき、すべてのユーザトラフィックは基地局と交換機(UPF: User Plane Function)との間において構築されたトンネル(GTP-U)を介して、UPFから先の宛先ネットワークに転送されます。一方で、5Gでは、低遅延な処理を実現するためにMECを用いてユーザ近傍のノードで計算処理を行うユースケースや、自動運転における車車間通信のように端末同士で通信するユースケースなどが想定されています。しかし、先述のアーキテクチャだと、たとえトランスポートネットワーク的に近い距離のノード同士の通信であったとしても、User Plane Function (UPF) を介するためにパケットの転送距離は増大します。 上の図は従来のアーキテクチャにおいて UE1 から UE2 にパケットを転送する際のパケットの流れを示しています。GTP-U が基地局 (RAN) と UPF の間で構築されるため、図に示す U-Plane において UE1 発のパケットはトランスポートネットワークの先にある UPF を一度経由した上で、宛先である UE2 へ送信されます。 SRv6 Mobile User Plane SRv6 Mobile User Plane (SRv6 MUP) は、モバイルネットワークにおける U-Plane 部分に SRv6 を適用する技術です。SRv6 の持つネットワークプログラミング機能を活用してGTP-UパケットとSRv6パケットをステートレスに相互変換したり、従来のGTP-UパケットのU-Plane処理をSRv6パケットのファンクション処理に置き換えることで、U-Plane通信を従来のアーキテクチャと比較してより柔軟に扱います。 RFC9433 では、SRv6 MUP のさまざまな U-Plane 適用手法が言及されています。本検証では IETF でも議論中である、 draft-mhkk-dmm-mup-architecture-01 の MUP アーキテクチャ (以下、採用アーキテクチャ)を参考にモバイルネットワークを構築し、UE と DN 間の通信や UE 間の通信を実現しました。 採用アーキテクチャの適用によって、SRv6 ネットワークのさまざまな拠点に点在する MUP 処理可能なルータ(MUP PE)各々がモバイルネットワークのU-Planeにおける GTP-U エンドポイントの役割を担うことで、トランスポートネットワークにおける最適経路とは遠く離れた位置に存在し得る UPF を介さない通信が可能になります。 また、既存の5Gネットワークの構成要素 (5GC、gNB) をそのまま利用しつつ、U-Plane をネットワークプログラマビリティが高く柔軟にパケットを処理できる SRv6 ネットワークへと置き換えることが可能になります。 上の図は採用アーキテクチャにおいて UE1 から UE2 にパケットを転送する際のパケットの流れを示しています。MUP PE が UPF の役割を担うことによって GTP-U が基地局 (RAN) と MUP-PE の間で構築されるため、図に示すU-Planeにおいて UE1 発のパケットは SRv6 MUP ネットワークへ流入する MUP PE で折り返して宛先である UE2 へ送信されます。 5Gネットワークへの SRv6 MUP の適用 採用アーキテクチャに従って U-Plane に SRv6 MUP ネットワークを適用するためには、SRv6 MUP ネットワークにおいて以下の機能が必要になります。 SRv6 MUP ネットワーク内のそれぞれの MUP PE が持つ N3 ネットワークセグメント、N6 ネットワークセグメント (総称して MUP Segment) の情報を共有し、 各 MUP PE が 5GC の管理するセッション情報を経路情報として受信した上で、 MUP Segment とセッション情報を元に U-Plane のパケット転送用経路を作成する 採用アーキテクチャにおいては、MUP PE 間で自身が接続している N3・N6 ネットワークセグメントを BGP を用いて経路情報として共有します。 ここで N3 ネットワークセグメントは MUP Segment のうち Interwork Segment として扱われ、関連する経路情報は Interwork Segment Discovery(ISD) 経路として扱われます。 ISD 経路情報には、SRv6 ネットワークに所属する全ての MUP PE が、対応する Interwork Segment へ到達するために必要な情報が含まれます。 また、 N6 ネットワークセグメントは MUP Segment のうち Direct Segment として扱われ、関連する経路情報は Direct Segment Discovery(DSD) 経路として扱われます。 DSD 経路情報には、SRv6 ネットワークに所属する全ての MUP PE が、対応する Direct Segment へ到達するために必要な情報が含まれます。 続いて 5GC が管理するセッション情報の受信についてです。 5G ネットワークのアーキテクチャにおいて、Session Management Function (SMF) と UPF との間では Packet Forwarding Control Protocol (PFCP) を用いてセッション情報を伝達していますが、採用アーキテクチャでは セッション情報は MUP コントローラ (MUP-C) によって経路情報に変換され、BGP を用いて MUP-C から MUP PE に共有されます。 セッション情報のうち、アクセス側の情報は Type 1 Session Transformed (T1ST) 経路として、コア側の情報は Type 2 Session Transformed (T2ST) 経路として扱われます。 採用アーキテクチャにおいて、T1ST 経路情報は MUP PE が ISD と組み合わせてuplinkの経路を生成するために、T2ST 経路情報は MUP PE が DSD と組み合わせてdownlinkの経路を生成するために利用します。uplinkとdownlinkのそれぞれの経路が SRv6 MUP ネットワーク内に適用されることで E2E の通信が可能となります。 本ブログにおいては、SMF から UPF へ送信されるセッション情報に対応する経路を生成する MUPコントローラの設計・実装と、実装した MUP コントローラを 5GC と SRv6 ネットワークの中継機器として適用することで SRv6 MUP を実現します。 なお、以降では説明のために N3 ネットワークセグメントへの接続を持つ MUP PE を特に MUP GWと呼びます。 MUP コントローラの設計 MUP コントローラの構成 採用アーキテクチャにおける MUP コントローラ(MUP-C)は、 SMF と SRv6 ネットワークの中間に位置します。UE のセッション情報が含まれる PFCP パケットを解釈し、経路を決定する機能群(以下、セッション解析機能)と、実際に BGP で経路を設定する機能群(経路広告機能)からなる構成としました。 セッション解析機能部では、PFCP パケットに含まれる UE のセッション情報を取得し、T1ST 経路と T2ST 経路をそれぞれ生成します。T1ST 経路は UE の IP アドレス、TEID、QFI、gNB の IP アドレスから構成され、T2ST 経路は UPF アドレスのプレフィックスと TEID から構成されます。 経路広告機能部では、GoBGP を使用しました。SRv6 MUP のアーキテクチャでは、BGP によって経路情報を広告します。GoBGP では MUP に対応した BGP 拡張が実装されているため、これを使うことにしました[ https://github.com/osrg/gobgp/blob/master/docs/sources/srv6_mup.md ]。セッション解析機能部で生成した経路情報は、gRPC で GoBGP に投入されます。 PDU セッション確立時のシーケンス 続いて、本検証での PDU セッションが確立するまでのシーケンスについて解説します。こちらの図は UE の接続要求をトリガーとした PDU セッション確立までの流れです。通常の 5GC との差異は、N4 でやり取りする相手が UPF ではなく MUP-C な点、MUP-C と MUP PE 間で BGP を用いて経路を広告する点です。赤字の箇所が新たに追加したシーケンスとなります。 既存の 5GC を変えない形で SRv6 MUP を利用するため、SMF から見た MUP-C の振る舞いは UPF と同等になるよう実装しました。内部的には、N4 Session Establishment/Modification Requestを受け取ると、セッション解析機能部分で T1ST 経路と T2ST 経路を生成するのに必要なパラメータをパースしたのち、GoBGP に gRPC で経路情報を投入します。gRPC リクエストを受け取った GoBGP は配下の MUP PE に経路を広告します。広告が完了すると、MUP-C は SMF に対して N4 Session Establishment/Modification Response を返し、以降は通常の PDU セッション確立のシーケンスに戻ります。 検証 今回の検証で使用した機材や OSS は下記のとおりです。MUP-C におけるセッション情報取得部分は前章「MUP コントローラの設計」を元に内製開発しました。 5GC…free5GC UE/RAN…UERANSIM SRv6 MUP対応ルータ...古河電工 FITELnet F220 EX (検証用ファームウェア) MUPコントローラ(セッション解析部)...内製開発 MUPコントローラ(経路広告部)...GoBGP、FRRouting 検証構成は下図のとおりです。 続いて、トポロジに従った SRv6 ネットワークを構成するための機器設定と状態を確認します。 なお、各種 5GC の config や各インターフェース設定などの基本設定や SRv6 利用のための初期設定などはすでに実施されているものとし、SRv6 ネットワークを構成するための IS-IS の設定と、MUP の経路情報を交換するための BGP の設定と確認を実施します。 IS-IS の設定 IS-IS を用いて、SRv6 encap されたパケットを転送するための経路情報を交換します。 なお本検証におきましては、MUP-C も FRRouting を用いて IGP domain に所属していますが、BGP セッションのための Loopback Address 広告と IPv6 (not SRv6) のパケット転送用の設定のみのため、設定の紹介は割愛します。 MUP PE DN への接続に利用するための Locator を定義して広告します。 interface Loopback 1 ipv6 address 2001:db8::91 ipv6 address 2001:db8:91:c0a8:ac0c::5b ipv6 router isis core exit ! interface Port-channel 2 ipv6 enable ipv6 router isis core exit ! interface Port-channel 3 ipv6 enable ipv6 router isis core exit ! router isis core is-type level-2 net 49.0000.0000.0091.00 srv6 locator N6DN topology ipv6-unicast exit ! segment-routing srv6 encapsulation source-address 2001:db8:91:c0a8:ac0c::5b locator N6DN 2001:db8:0:91::/64 exit MUP GW DN と RAN への接続に利用するための Locator をそれぞれ定義して広告します。 interface Loopback 1 ipv6 address 2001:db8::100 ipv6 router isis core exit ! interface Port-channel 2 ipv6 enable ipv6 router isis core exit ! interface Port-channel 3 ipv6 enable ipv6 router isis core exit ! router isis core is-type level-2 net 49.0000.0000.0100.00 srv6 locator N3RAN srv6 locator N6DN topology ipv6-unicast exit ! segment-routing srv6 encapsulation source-address 2001:db8::100 locator N3RAN 2001:100::/32 locator N6DN 2001:db8:0:100::/64 exit IS-IS の確認 IS-IS によって各ルータの Loopback Address と SRv6 Locator の経路を相互に学習していることが確認できます。 MUP PE MUP-PE#sh ipv6 route isis Codes: K - kernel route, C - connected, S - static, R - RIPng, O - OSPFv3, B - BGP, T - Tunnel, i - IS-IS, V - VRRP track, Iu - ISAKMP SA up, It - ISAKMP tunnel route, Ip - ISAKMP l2tpv2-ppp Dr - DHCPv6-PD-relay, Dc - DHCP-client, Ds - DHCP-server, r - RA L - Local Breakout > - selected route, * - FIB route, p - stale info i > * 2001:100::/32 [115/30] via fe80::beef:beef:beef:beef, port-channel3, 00:00:12 i > * 2001:db8::100/128 [115/30] via fe80::beef:beef:beef:beef, port-channel3, 00:00:12 i > * 2001:db8::101/128 [115/20] via fe80::beef:beef:beef:beef, port-channel3, 00:00:12 i > * 2001:db8:0:100::/64 [115/30] via fe80::beef:beef:beef:beef, port-channel3, 00:00:12 MUP GW MUP-GW#sh ipv6 route isis Codes: K - kernel route, C - connected, S - static, R - RIPng, O - OSPFv3, B - BGP, T - Tunnel, i - IS-IS, V - VRRP track, Iu - ISAKMP SA up, It - ISAKMP tunnel route, Ip - ISAKMP l2tpv2-ppp Dr - DHCPv6-PD-relay, Dc - DHCP-client, Ds - DHCP-server, r - RA L - Local Breakout > - selected route, * - FIB route, p - stale info i > * 2001:db8::91/128 [115/30] via fe80::beef:beef:beef:beef, port-channel3, 00:01:25 i > * 2001:db8::101/128 [115/20] via fe80::beef:beef:beef:beef, port-channel3, 00:01:25 i > * 2001:db8:0:91::/64 [115/30] via fe80::beef:beef:beef:beef, port-channel3, 00:01:25 i > * 2001:db8:91:c0a8:ac0c::5b/128 [115/30] via fe80::beef:beef:beef:beef, port-channel3, 00:01:25 MUP Segment の設定 MUP PE には N6 に接続するための Direct Segment、MUP GW には Direct Segment に加えて、N3 に接続するための Interwork Segment をそれぞれ N6DN VRF、N3RAN VRF として定義します。 MUP PE ip vrf N6DN description N6DN rd 65000:2 route-target import 100.0.0.101:1 route-target import 65000:2 route-target export 65000:2 segment-routing srv6 locator N6DN segment-routing srv6 mup-segment direct 65000:2 exit ! interface Port-channel 4 ip vrf forwarding N6DN ip address 192.168.60.11 255.255.255.0 ipv6 enable exit MUP GW ip vrf N3RAN description N3RAN rd 65000:1 route-target import 100.0.0.101:1 route-target import 65000:1 route-target export 65000:1 segment-routing srv6 locator N3RAN segment-routing srv6 mup-segment interwork exit ! ip vrf N6DN description N6DN rd 65000:2 route-target import 100.0.0.101:1 route-target import 65000:2 route-target export 65000:2 segment-routing srv6 locator N6DN segment-routing srv6 mup-segment direct 65000:2 exit ! interface Port-channel 4 ip vrf forwarding N3RAN ip address 192.168.173.13 255.255.255.0 exit BGP による経路情報の広告 BGP によって MUP PE、MUP GW で MUP Segment と VPN 経路を広告します。本検証では N3、N6 はそれぞれ IPv4 ネットワークのため、IPv4 SRv6-MUP と VPNv4 の Address Family を広告します。 本検証においては MUP-C の GoBGP が Route Reflector も兼ねているため、GoBGP には Route Reflector 用の config を投入します。 MUP PE router bgp 65000 bgp router-id 100.0.0.91 bgp log-neighbor-changes neighbor 2001:db8::101 remote-as 65000 neighbor 2001:db8::101 update-source loopback 1 ! address-family vpnv4 segment-routing srv6 neighbor 2001:db8::101 activate neighbor 2001:db8::101 capability extended-nexthop-encoding neighbor 2001:db8::101 send-community both exit ! address-family ipv4 srv6-mup neighbor 2001:db8::101 activate neighbor 2001:db8::101 send-community both exit ! address-family ipv4 vrf N6DN redistribute connected redistribute static exit ! exit MUP GW router bgp 65000 bgp router-id 100.0.0.100 bgp log-neighbor-changes neighbor 2001:db8::101 remote-as 65000 neighbor 2001:db8::101 update-source loopback 1 ! address-family vpnv4 segment-routing srv6 neighbor 2001:db8::101 activate neighbor 2001:db8::101 capability extended-nexthop-encoding neighbor 2001:db8::101 send-community both exit ! address-family ipv4 srv6-mup neighbor 2001:db8::101 activate neighbor 2001:db8::101 send-community both exit ! address-family ipv4 vrf N3RAN redistribute connected redistribute static exit ! address-family ipv4 vrf N6DN redistribute connected redistribute static exit MUP-C (GoBGP) MUP-C:~/gobgp$ cat conf.toml [global.config] as = 65000 router-id = "100.0.0.101" [[neighbors]] [neighbors.config] peer-as = 65000 local-as = 65000 neighbor-address = "2001:db8::91" [neighbors.transport.config] local-address = "2001:db8::101" [neighbors.route-reflector.config] route-reflector-client = true route-reflector-cluster-id = "0.0.0.1" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-mup" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "l3vpn-ipv4-unicast" [[neighbors]] [neighbors.config] peer-as = 65000 local-as = 65000 neighbor-address = "2001:db8::100" [neighbors.transport.config] local-address = "2001:db8::101" [neighbors.route-reflector.config] route-reflector-client = true route-reflector-cluster-id = "0.0.0.1" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-mup" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "l3vpn-ipv4-unicast" BGP の設定確認 BGP セッションによって SRv6 MUP を実現するための MUP Segment や VPN 経路が受信できていることを確認します。 MUP GW N6DN VRF に DN 向けの経路をインストールできているか、また MUP PE から広告された DSD 経路を受け取っているかを確認します。 MUP-GW#sh ip route vrf N6DN VRF: N6DN Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF, B - BGP, T - Tunnel, i - IS-IS, V - VRRP track, Iu - ISAKMP SA up, It - ISAKMP tunnel route, Ip - ISAKMP l2tpv2-ppp Dc - DHCP-client, L - Local Breakout > - selected route, * - FIB route, p - stale info B > * 192.168.60.0/24 [200/0] via 2001:db8:0:91:45::, Tunnel1, 00:31:53 MUP-GW#sh ip route vrf N6DN 192.168.60.0/24 Routing entry for 192.168.60.0/24 Known via "bgp", distance 200, metric 0, best, redistributed Encapsulation Information: Tunnel Type: SRv6 Tunnel IF: Tunnel1 (Data: 0xf6a02e50) Tunnel ID: 229 Tunnel Endpoint: 2001:db8:0:91:49:: (System VRF-ID: 0) Tunnel Parameter: (SID list) 2001:db8:0:91:49:: Last update 02w1d22h ago 2001:db8:0:91:49::, Tunnel1 (Tunnel-ID:229), RD 65000:2, System VRF-ID 2, NHD LINK Tunnel1 (36), refcnt 4 MUP-GW#sh ip bgp ipv4 srv6-mup dsd detail [Direct Segment Discovery route] Route Distinguisher: 65000:2 BGP routing table entry for 100.0.0.91 Local 2001:db8::91 from 2001:db8::101 (100.0.0.91) Origin IGP, localpref 100, valid, internal, best Extended Community: RT:65000:2 SRv6-MUP:65000:2 Originator: 100.0.0.91, Cluster list: 0.0.0.1 Path Identifier (Remote/Local): /0 Last update: Sun Nov 3 20:16:17 2024 MUP PE MUP PE が ISD 経路を受け取れているかを確認します。 MUP-PE#sh ip bgp ipv4 srv6-mup isd detail [Interwork Segment Discovery route] Route Distinguisher: 65000:1 BGP routing table entry for 192.168.173.0/24 Local 2001:db8::100 from 2001:db8::101 (100.0.0.100) Origin IGP, localpref 100, valid, internal, best Extended Community: RT:65000:1 Originator: 100.0.0.100, Cluster list: 0.0.0.1 BGP Prefix-SID: SRv6 L3VPN 2001:100:42:: (L:16.16, F:16.0, T:0.0) End.M.GTP4.E Path Identifier (Remote/Local): /0 Last update: Sun Nov 3 20:58:48 2024 疎通確認 UE-DN 間の通信 まずは下記に示す、SRv6 ネットワークを介した UE-DN 間の通信について検証します。UE から DN の先のサーバに向けて ping を実行します。パケットはピンク色で示した経路を通ります。 UE の接続 UE を 5GC にアタッチし、PDU セッションを確立します。 MUP-C がセッション情報を T1ST/T2ST 経路に変換し、広告します。 mup-c:~$ gobgp global rib -a ipv4-mup | grep t1st *> [type:t1st][rd:100.0.0.101:1][prefix:10.10.10.1/32] 0.0.0.0 00:01:05 [{Origin: ?} {Extcomms: [100.0.0.101:1]}] mup-c:~$ gobgp global rib -a ipv4-mup | grep t2st *> [type:t2st][rd:100.0.0.101:1][endpoint-address-length:64][endpoint:192.168.172.12][teid:0.0.0.2] 0.0.0.0 00:01:07 [{Origin: ?} {Extcomms: [100.0.0.101:1], [65000:2]}] 受信した経路情報の確認 PDUセッションを確立したことにより、SRv6 MUP ネットワークにセッション情報を示す T1ST/T2ST 経路が広告されることを確認します。 MUP PE での確認例 MUP-PE#sh ip bgp ipv4 srv6-mup st1 10.10.10.1/32 [Type 1 Session Transformed route] Route Distinguisher: 100.0.0.101:1 BGP routing table entry for 10.10.10.1/32 Local 2001:db8::101 from 2001:db8::101 (100.0.0.101) Origin incomplete, localpref 100, valid, internal, best Extended Community: RT:100.0.0.101:1 Originator: 100.0.0.101, Cluster list: 0.0.0.1 TEID 00000001 (1), QFI 0x0, Endpoint 192.168.173.12 Path Identifier (Remote/Local): /0 Last update: Wed Nov 20 21:20:48 2024 MUP-GW#sh ip bgp ipv4 srv6-mup st2 192.168.172.12 [Type 2 Session Transformed route] Route Distinguisher: 100.0.0.101:1 BGP routing table entry for 192.168.172.12 Local 2001:db8::101 from 2001:db8::101 (100.0.0.101) Origin incomplete, localpref 100, valid, internal, best Extended Community: RT:100.0.0.101:1 SRv6-MUP:65000:2 Originator: 100.0.0.101, Cluster list: 0.0.0.1 Path Identifier (Remote/Local): /0 Last update: Wed Nov 20 21:21:18 2024 また、受け取った T1ST/T2ST を元に U-Plane 用の経路が生成されていることを確認します。 MUP GW (uplinkの経路情報) N3 から受け取った GTP-U パケットのヘッダが decap されて N6DN VRF の経路情報を参照することがわかります。 BGP による経路情報の広告において N6DN VRF には MUP PE が広告する DN への経路情報が含まれているため、DN へパケットが転送されます。 MUP-GW#sh ip route vrf N3RAN 192.168.172.12/32 Routing entry for 192.168.172.12/32 Known via "bgp", distance 200, metric 0, best, redistributed Encapsulation Information: Tunnel Type: SRv6 Tunnel IF: Tunnel1 (Data: 0xf6a02ae0) Tunnel ID: 229 Tunnel Endpoint: 2001:100:4c:: (System VRF-ID: 0) Last update 02w0d23h ago 2001:100:4c::, Tunnel1 (Tunnel-ID:229), RD 65000:1, System VRF-ID 1, NHD LINK Tunnel1 (38), refcnt 1 MUP-GW#sh segment-routing srv6 sid 2001:100:4c:: SID Function Context Owner State -------------------------- ------------ -------------------------------------------------- ----- --------- 2001:100:4c:: H.M.GTP4.D 'N3RAN':bsid BGP InUse Locator : N3RAN Length : 128 Nexthop : 2001:db8:0:100:41:: Link-ID : 37 Created : Mon Nov 4 20:26:40 2024 (02w1d23h ago) MUP-GW#sh segment-routing srv6 sid 2001:db8:0:100:41:: SID Function Context Owner State -------------------------- ------------ -------------------------------------------------- ----- --------- 2001:db8:0:100:41:: End.DT4 'N6DN':DN lookup BGP InUse Locator : N6DN Length : 128 VRF : N6DN Created : Tue Oct 29 17:32:14 2024 (03w1d01h ago) MUP PE (downlinkの経路情報) MUP GW から受け取った ISD 経路に登録されている End.M.GTP4.E Function を用いる経路が生成されていることが確認できます。 また、転送に利用する SID ( 2001:100:42:c0a8:ad0c::100 ) の Argument 部分 ( c0a8:ad0c::100 ) には MUP GW にて GTP-U パケットに変換するための gNB アドレス (=c0a8:ad0c=192.168.173.12 )、QFI (=0)、TEID (=1) の情報が含まれています。 MUP-PE#sh ip route vrf N6DN 10.10.10.1/32 Routing entry for 10.10.10.1/32 Known via "bgp", distance 200, metric 0, best, redistributed Encapsulation Information: Tunnel Type: SRv6 Tunnel IF: Tunnel1 (Data: 0xf6a01930) Tunnel ID: 230 Tunnel Endpoint: 2001:100:42:c0a8:ad0c::100 (System VRF-ID: 0) Tunnel Parameter: (SID list) 2001:100:42:c0a8:ad0c::100 Last update 00:05:44 ago 2001:100:42:c0a8:ad0c::100, Tunnel1 (Tunnel-ID:230), RD 65000:2, System VRF-ID 1, NHD LINK Tunnel1 (20), refcnt 1 ping による疎通確認 UE(10.10.10.1)とサーバ(192.168.60.12)間で ping を実行した結果を示します。gNB でキャプチャしたGTP-U パケットはこちらです。 続いて、GTPカプセリングされたICMPパケットがMUP-GWを通過した際のSRv6パケットです。先程のGTP-U パケットがSRv6パケットに置き換わっていることが分かります。 downlink において MUP-PE を通過した際の SRv6 パケットです。src には UPF のアドレス(192.168.172.12→c0a8:ac0c)が、 dst には gNB のアドレス(192.168.173.12→a0c8:ad0c)、QFI(0)、TEID(1) が埋め込まれています。 downlink 方向の GTP-U パケットです。downlink の SRv6 の src/dst アドレスに埋め込まれた UPFのアドレス、gNBのアドレス、TEID が GTP-U パケットに反映されています。これで、UE に正しく ping reply が届くことを確認しました。 UE同士の折り返し通信 続いて、SRv6 MUPによって経路遅延が削減できるユースケースである、MUP GWによるUE同士の折り返しとなる通信について検証します。片方のUEからpingを実行し、もう片方のUEで受信します。トラフィックはピンク色で示した経路を通ります。 UE の接続 UE を 5GC にアタッチし、PDU セッションを確立します。 MUP-C がセッション情報を T1ST/T2ST 経路に変換し、広告します。 MUP-C:~$ gobgp global rib -a ipv4-mup | grep t1st *> [type:t1st][rd:100.0.0.101:1][prefix:10.10.10.3/32] 0.0.0.0 00:40:26 [{Origin: ?} {Extcomms: [100.0.0.101:1]}] *> [type:t1st][rd:100.0.0.101:1][prefix:10.10.10.2/32] 0.0.0.0 00:00:04 [{Origin: ?} {Extcomms: [100.0.0.101:1]}] MUP-C:~$ gobgp global rib -a ipv4-mup | grep t2st *> [type:t2st][rd:100.0.0.101:1][endpoint-address-length:64][endpoint:192.168.172.12][teid:0.0.0.4] 0.0.0.0 00:00:06 [{Origin: ?} {Extcomms: [100.0.0.101:1], [65000:2]}] *> [type:t2st][rd:100.0.0.101:1][endpoint-address-length:64][endpoint:192.168.172.12][teid:0.0.0.3] 0.0.0.0 00:40:28 [{Origin: ?} {Extcomms: [100.0.0.101:1], [65000:2]}] 経路情報の確認 UE を接続したことにより、SRv6 MUP ネットワークにセッション情報を示す T1ST/T2ST 経路が広告されることを確認します。今回は UE を2台接続したため、2種類の T1ST 経路が見えます。 MUP PE での確認例 MUP-PE#sh ip bgp ipv4 srv6-mup st1 10.10.10.2/32 [Type 1 Session Transformed route] Route Distinguisher: 100.0.0.101:1 BGP routing table entry for 10.10.10.2/32 Local 2001:db8::101 from 2001:db8::101 (100.0.0.101) Origin incomplete, localpref 100, valid, internal, best Extended Community: RT:100.0.0.101:1 Originator: 100.0.0.101, Cluster list: 0.0.0.1 TEID 00000001 (1), QFI 0x0, Endpoint 192.168.173.14 Path Identifier (Remote/Local): /0 Last update: Mon Nov 18 20:36:58 2024 MUP-PE#sh ip bgp ipv4 srv6-mup st1 10.10.10.3/32 [Type 1 Session Transformed route] Route Distinguisher: 100.0.0.101:1 BGP routing table entry for 10.10.10.3/32 Local 2001:db8::101 from 2001:db8::101 (100.0.0.101) Origin incomplete, localpref 100, valid, internal, best Extended Community: RT:100.0.0.101:1 Originator: 100.0.0.101, Cluster list: 0.0.0.1 TEID 00000002 (2), QFI 0x0, Endpoint 192.168.173.12 Path Identifier (Remote/Local): /0 Last update: Mon Nov 18 20:37:24 2024 MUP-PE#sh ip bgp ipv4 srv6-mup st2 192.168.172.12 [Type 2 Session Transformed route] Route Distinguisher: 100.0.0.101:1 BGP routing table entry for 192.168.172.12 Local 2001:db8::101 from 2001:db8::101 (100.0.0.101) Origin incomplete, localpref 100, valid, internal, best Extended Community: RT:100.0.0.101:1 SRv6-MUP:65000:2 Originator: 100.0.0.101, Cluster list: 0.0.0.1 Path Identifier (Remote/Local): /0 Last update: Mon Nov 4 20:26:05 2024 また、受け取った T1ST/T2ST 経路と MUP PE / MUP GW 間で交換した MUP Segment の情報を元に U-Plane 用の経路が生成されていることを確認します。 UE 間通信では MUP GW で折り返して通信するため、MUP GW の経路情報を確認します。 MUP GW (uplink の経路情報) N3 から受け取った GTP-U パケットのヘッダが decap されて N6DN VRF の経路情報を参照することがわかります。 BGP による経路情報の広告において N6DN VRF には MUP PE が広告する DN への経路情報が含まれているため、DN へパケットが転送されます。 MUP-GW#sh ip route vrf N3RAN 192.168.172.12/32 Routing entry for 192.168.172.12/32 Known via "bgp", distance 200, metric 0, best, redistributed Encapsulation Information: Tunnel Type: SRv6 Tunnel IF: Tunnel1 (Data: 0xf6a02ae0) Tunnel ID: 229 Tunnel Endpoint: 2001:100:4c:: (System VRF-ID: 0) Last update 02w0d23h ago 2001:100:4c::, Tunnel1 (Tunnel-ID:229), RD 65000:1, System VRF-ID 1, NHD LINK Tunnel1 (38), refcnt 1 MUP-GW#sh segment-routing srv6 sid 2001:100:4c:: SID Function Context Owner State -------------------------- ------------ -------------------------------------------------- ----- --------- 2001:100:4c:: H.M.GTP4.D 'N3RAN':bsid BGP InUse Locator : N3RAN Length : 128 Nexthop : 2001:db8:0:100:41:: Link-ID : 37 Created : Mon Nov 4 20:26:40 2024 (02w1d23h ago) MUP-GW#sh segment-routing srv6 sid 2001:db8:0:100:41:: SID Function Context Owner State -------------------------- ------------ -------------------------------------------------- ----- --------- 2001:db8:0:100:41:: End.DT4 'N6DN':DN lookup BGP InUse Locator : N6DN Length : 128 VRF : N6DN Created : Tue Oct 29 17:32:14 2024 (03w1d01h ago) MUP GW (downlink の経路情報) GTP-U ヘッダが decap されて N6DN に転送されてきた UE 向けのパケットは、 GTP encap されて N3RAN へ転送されます。ここで、 System VRF-ID: 1 は N3RANを示しています。 MUP-GW#sh ip route vrf N6DN 10.10.10.2/32 Routing entry for 10.10.10.2/32 Known via "bgp", distance 200, metric 0, best, redistributed Encapsulation Information: Tunnel Type: SRv6 Tunnel IF: Tunnel1 (Data: 0xf6a036d0) Tunnel ID: 229 Tunnel Endpoint: 192.168.173.14 (System VRF-ID: 1) Tunnel Parameter: (GTP encap) System VRF-ID: 1 Endpoint: 192.168.173.14 TEID: 1 QFI: 0x0 Last update 01d01h29m ago 192.168.173.14, Tunnel1 (Tunnel-ID:229), RD 65000:2, System VRF-ID 2, NHD LINK Tunnel1 (36), refcnt 4 MUP-GW#sh ip route vrf N6DN 10.10.10.3/32 Routing entry for 10.10.10.3/32 Known via "bgp", distance 200, metric 0, best, redistributed Encapsulation Information: Tunnel Type: SRv6 Tunnel IF: Tunnel1 (Data: 0xf6a02388) Tunnel ID: 229 Tunnel Endpoint: 192.168.173.12 (System VRF-ID: 1) Tunnel Parameter: (GTP encap) System VRF-ID: 1 Endpoint: 192.168.173.12 TEID: 2 QFI: 0x0 Last update 01d01h28m ago 192.168.173.12, Tunnel1 (Tunnel-ID:229), RD 65000:2, System VRF-ID 2, NHD LINK Tunnel1 (36), refcnt 4 ping による疎通確認 UE 間(10.10.10.2 / 10.10.10.3)とサーバ(192.168.60.12)間で ping を実行した結果を示します。gNB でキャプチャしたGTP-U パケットはこちらです。 下側のgNBでキャプチャした結果を示します。また、SRv6 NW側にはパケットが到達せず、MUP GWで折り返して通信できていることが確認できました。 おわりに 本記事では、我々の環境にて SRv6 MUP の検証を実施した際のネットワークやコントローラの設計、そして5Gネットワークを使用した疎通確認までの一連の流れを紹介いたしました。特にSRv6 MUPによる経路遅延の削減効果が大きいUE間通信について検証し、動作を確認しました。今後もSRv6 MUPの動向について、継続的にウォッチしていきます。
アバター
AIロボット部(社内サークル)では、子ども向けロボット教室を開催しました。 今年は、懐中電灯の光を利用して進行方向を指示するロボットを制作しました。 偏光板と光抵抗を使った分圧回路を活用し、簡単な電子工作の知識で実現可能な仕組みを採用しました。 ファミリーデーとロボット教室 ロボット教室の内容 方向指示のしくみ 準備に関して プロトタイプの作成 1. 手軽に入手できる部品で構成する 2. できるだけシンプルな仕組みにする 3. ロバスト性の確保 基板作成、調達 昨年の反省は活かされたのか? 当日の様子 おわりに(+社外イベントへの出展のおしらせ) 但し書き こちらは、NTT Communications Advent Calendar 2024の3日目の記事です。 こんにちは、ロボット部 部員の上田です! 本日はロボット部が夏に行った、子ども向けロボット教室の内容を書きます。 ※ ロボット部は社内の非公式なサークル活動です。ただいま絶賛部員を募集中です! なお、以前に書いた 記事はこちら ファミリーデーとロボット教室 NTTドコモグループでは毎年夏にファミリーデーという催しがあり、NTT Comでは社員の家族をオフィスに招いてNTT Comの取り組みの紹介やワークショップを行っています。 このファミリーデーに枠を貰い、我々ロボット部で子ども向けロボット教室を開催しました。 今年のファミリーデーの様子は、NTT ComのX(旧Twitter)アカウントの ポスト でも紹介されています。 ロボット教室の内容 ロボット部では、昨年(2023年)からロボット制作教室を開催しています。 ファミリーデーの数ヶ月前から、有志メンバーで企画内容の立案や検証、製造などの作業を進めました。 今年は偏光と光抵抗を利用した分圧回路を利用して進行方向を指示するロボット(ラジコン)の工作教室を行いました。 完成品はこちらになります。 手前の懐中電灯と基板上の白いセンサ部分に偏光板を張り付けています。 なお、ロボットの上に乗っているは、gooのキャラクター メグたんをモデリングして3Dプリンタで出力した人形です。 操作方法は、下記の図のように懐中電灯の光をセンサーに当て、懐中電灯を回すことにより進行方向を指示します。 子どもたちが組み立てたロボットを実際に走らせている様子がこちらになります。 工夫したポイントとしては、偏光の性質と抵抗分圧回路、コンパレータ、モータ、ダイオード、トランジスタといった電子工作をする際の基本的な知識のみを使って実現可能な仕組みにしています。 方向指示のしくみ 今回は、懐中電灯と偏向板を使って進行方向を指示する方法に関して解説します。 まずは、偏光板の性質に関する確認です。 1 下記画像は偏光板を2枚用意し、一方の偏光板を45度ずつ回転させた際の様子です。 2枚の偏光板のなす角により明るさ(透過率)が変化する様子を確認できます。 上記の画像では、偏光板を2枚重ねていました。では、一方の偏光板を光源側に、もう片方の偏光板をセンサー側に設置したらどうなるでしょうか。 この場合も、光源からセンサーに届く光量は、光源側の偏光板とセンサー側の偏光板のなす角により変化するはずです。 実際に実験した際の様子が、下記の映像になります。光源となる懐中電灯に偏光板を貼り付け、光量により抵抗値が変化するセンサー(一般にCdSセルと呼ばれる光抵抗)にも偏光板を貼り付けています。 懐中電灯(光源側の偏光板)を回転させることによりセンサー側の偏光板の光の透過率が変化し、抵抗値も変化していることを下記映像から確認できます。 これだけで、ロボットに進行方向を指示できそうな気もします。 しかし、普段我々が生活する環境には、懐中電灯以外にも照明や太陽光などが存在します。さらに、これらの光量は同じ部屋でも場所によって変化することもあり、光抵抗の抵抗値を意図せず変化させてしまいます。 このため、光抵抗1個だけでは懐中電灯側の偏光板とセンサー側の偏光板のなす角を検出するのが難しくなってしまいそうです。 それでは、偏光(板)を用いてどのようにロボットに進行方向を指示すれば良いでしょうか? いくつか方法は考えられそうですが、今回はまず2つの光抵抗と1つの懐中電灯、3枚の偏光板(各光抵抗と懐中電灯にそれぞれ1枚)を使ってみます。 下記画像は、実際の光抵抗と偏光板の画像です。 現存している当時の回路図では、下記のようになっています。 挙動としては、可変抵抗とコンパレータを利用して、2つの光抵抗の抵抗値がほぼ等しい(バランスがとれた状態)場合、左右のモーターを同時に動かします。 バランスが崩れた場合は、崩れた方向に応じて左右どちらか片方のモータのみを動作させます。 このため、ロボットは懐中電灯側の偏光版とセンサーのなす角が一定値以内に収まる方向へ向かって進むようになります。 下記の映像は、実際に走らせた際のものです。 ただ、この回路では懐中電灯(偏光)をあてたときのみ走行させ、懐中電灯を当てていないときは停止させるといった挙動を実現するのは難しそうです 2 。 そこで光抵抗を2個追加し、4つの光抵抗と1つの懐中電灯、5枚の偏光板(各光抵抗と懐中電灯にそれぞれ1枚)を使ってみます。 具体的には、下記図のように直列に接続した光抵抗を2組用意し、十字に交わるように配置します。 その後、配置した光抵抗の上に偏光板をかぶせます。 下記の図に示す方法で光抵抗に偏光板をかぶせます。 (直列接続した各光抵抗にかぶせる偏光板のなす角が90度となるようにします。) これにより、偏光が当たっていないときは4つの光抵抗に当たる光量はほぼ等しいため、抵抗値がほぼ等しく(バランスが取れた状態)になるはずです(各光抵抗の距離が近いため、部屋の場所などによって変化する光量の違いは無視できるものと仮定します)。 対して、偏光がセンサーに当たっている場合は少なくともどちらか片方の組の光抵抗の抵抗値のバランスが崩れた状態になります。 そこで、上記画像の青色の組(②と④)の抵抗値のバランスが崩れた場合は直進。緑色の組(①と③)の抵抗値のバランスが崩れた場合は、崩れた方向に従って左右のモーターのどちらか一方のみを回転さる。全ての抵抗値のバランスが取れている場合は、偏光(懐中電灯)が当てられていないと判定し、停止するような回路を作ってみます。 実際の回路図は、下記のようになりました。 なお、最終版の回路には安全装置としてのポリスイッチの追加など、変更が加えられています。 以上が、懐中電灯と偏向板を利用して進行方向を指示する方法の解説になります。 準備に関して 昨年(2023年)のロボットは、有線リモコンを使って機体を操作する仕様でした。 そのため回路がシンプルで、部品点数も少ないというメリットがありました。 しかし、有線リモコンの端子圧着作業が大変(ロボット1台につき8か所の圧着が必要)でした。 そこで、今年はできるだけ圧着作業を減らし、シンプルな構成にしたいということになりました。 最終的に今年のロボットは、懐中電灯で操作するロボットを仕様になりました。 プロトタイプの作成 実際に作成したプロトタイプを走行させた際の様子がこちらになります。 プロトタイプの作成にあたっては、次の点を意識していました。 手軽に入手できる部品で構成する できるだけシンプルな仕組みにする ロバスト性の確保 1. 手軽に入手できる部品で構成する 部品は、日本国内(特に秋葉原)で手軽に入手できる部品を利用することを意識していました。 理由は入手容易性にあります。量産時に部品の不足が判明した可能性などを考えると、秋葉原などにある実店舗で直接入手できることが重要です。 実際、昨年は圧着ミスなどにより圧着端子が足りなくなり、秋葉原に急いて買い出しに行ったと記憶しています。 今回は、仮に参加者などから回路を含めて自作したいといったリクエストがあった場合のことも考えて、秋葉原などに実店舗がありオンラインでも注文可能な秋月電子通商さまが扱っている部品を主に利用させていただきました。 2. できるだけシンプルな仕組みにする マイコンを使わずに、偏光の性質と抵抗分圧回路、コンパレータ、モータ、ダイオード、トランジスタ(MOSFET)といった電子工作をする際の基本的な知識のみを使って実現可能な仕組みにしました。 マイコンを使わなかった理由としては、価格やプログラム作成や書き込みの手間を減らしたかったためです。 ただ、今回は電子回路を主役にしたかったという提案者(筆者)の個人的なわがままの影響もあります。 完全に個人の主観になりますが、昨今のプログラミングブームの影響もあり、マイコンを使ってしまうと電子回路よりもソフトウェアに注目が向かってしまうのではないかと考えました。 そこで、今回はあえてマイコンを使わずに、簡単な電子回路のみを組み合わせてラジコンという一見複雑な仕組みが必要そうなシステムの実現に挑戦してみました。 これにより、電子回路そのものへの興味を持つきっかけになればと思った次第です。 3. ロバスト性の確保 計画当初では、懐中電灯は参加者自身で用意していただく予定でした。そのため、懐中電灯の光のスペクトルが分からず、広範囲の波長の光に反応するセンサーを利用する必要がありました。 また、時間や天気により会場に差し込む太陽光の光量も変化するという問題があり、比較的明るい部屋でもセンサーが飽和することなく、偏光を検出できる必要がありました。 そこで、フォトダイオードやフォトトランジスタ、CdSセルなど複数の光センサーから条件を満たすセンサーを探したところ、CdSセルを利用するのが良いとの結論に至りました。 可能であれば、欧州のRoHS指令等で規制対象となっているカドミウムを含むCdSセルの利用は避けたかったのですが、CdSセル以外のセンサーを使って安定して動作する回路の実現には至りませんでした(こちらは今後の課題です)。 基板作成、調達 ブレッドボードを利用したプロトタイプ作成後のプリント基板作成は、経験のある部員が行いました。 また、一部の部品は3Dプリンタを利用して作成しました。 モデリングツールは部員やパーツによって異なりますが、BlenderやFreeCAD、OpenSCADを利用しているとの認識です。 昨年の反省は活かされたのか? 昨年(2023年)は、大量のハンダ付け、大量のケーブルの圧着、大量の部品の仕上げ加工、のように準備が大変でした。 では、今年はどうだったのかというと、改善したのは、ケーブルの圧着作業が1台当たり8か所から2か所に減ったのみでした。 ハンダ付けの量はむしろ悪化し、基板1当たり30分以上の時間がかかるようになってしまいました。 ただ、部員の増加や分業、昨年の経験を踏まえたスキルの向上、リード部品を効率よく曲げるための治具の作成により、当初の想定よりスムーズに準備が進みました。 おかげで、今年は昨年よりも比較的余裕を持って準備を進めることが出来ました。 当日の様子 ロボット教室は、1日2回、各回15名で2日間にわたって実施しました。 こちらが当日の様子になります。参加者は小学生の方が一番多かったです。 なお、組み立て作業などは、保護者の監督の下で行っていただきました。 実際の走行の様子はこちらになります。 なお当日は、組み立てまで完了し無事に動かせるようになった方がいる一方、ハードウェアの不具合や調整不足などにより、思ったように操作できない車体なども出てきてしまいました。 ハードウェアの不具合の原因としては、はんだ付け不良によるものと思われるものが多くありました。 こちらは、事前に動作確認を行い不具合を見つけ次第修正したのですが、一部は見つけきれ無かったようです(予備基板と交換しました)。 調整不足の原因としては、単純に調整手順が多く難しいという問題があります。 今回制作した機体は下記図の方法で半固定抵抗の調整する必要があります。 しかし、センサーの一部のみに手などの影がかかると正確な調整が出来ずに、上手く調整できないといった問題が多く発生してしまいました。 上記のような問題もあり、盛り上がりや満足度としては昨年(2023年)のロボット相撲大会より落ちてしまったのではないかと感じています。 おわりに(+社外イベントへの出展のおしらせ) 最後まで読んでくださりありがとうございます。 また、本イベントに参加してくださった皆さま、支援してくださった皆さまに感謝申し上げます。 もし来年も機会がありましたら、(内容は未定ですが)今年の反省もふまえてより良いものを提供できればと思います。 最後に、こちらのロボット(ラジコン)は、2024年12月07(土)に docomo R&D OPEN LAB ODAIBA で開催される 【20th 記念展示会】Mashup Award & #ヒーローズリーグ で展示予定です。 直前の案内となってしまいましたが、もしご興味がありましたらお立ちよりください。 但し書き 本記事には欧州のRoHS指令等で規制対象となっているカドミウム(有害物質)を含む電子部品(一般にCdSセルと呼ばれる光センサ)が登場します。該当センサを破砕すると有害物質を曝露することになります。また、廃棄する際は自治体の指示に従って廃棄してください。本記事の内容を実践する場合は、左記の内容に留意ください。 本記事の内容を参考にされる場合は、自己責任でお願いします。 今回は直線偏光を利用します。 ↩ この記事を書きながら思ったのですが、同時に両方の車輪を回転させることを諦めれば後述する方法を用いなくても実現できそうな気がしてきました。しかし、執筆時点では未検証です。 ↩
アバター
この記事は、 NTT Communications Advent Calendar 2024 2 日目の記事です。 perf の Python インタプリタを使って KVM Exit/Entry のレイテンシを計測してみます。 はじめに KVM の仕組み CPU トレースを取得する perf をビルドする Python コードを書く 独自スクリプトを用いて perf.data を集計する まとめ 参考文献 はじめに こんにちは。 SDPF クラウド・仮想サーバーチームの杉浦 ( @Kumassy_ ) です。 普段は OpenStack の開発・運用をしており、最近は仮想マシンの性能解析やトラブルシューティングなどに取り組んでいます。 perf は Linux のパフォーマンス解析に有用なツールです。 perf を使うことで、パフォーマンスカウンタの値を読んだり、プロファイリング、トレーシングなど 様々なデータを取得することができます 。 取得したデータを分析するには perf report や perf script が利用できますが、込み入ったデータを解析したいときには python または perl で書かれた独自のスクリプトを使うことができます。 今回は perf の Python インタプリタを使って KVM Exit/Entry のレイテンシを計測してみます。 KVM の仕組み QEMU/KVM を利用した仮想環境では、ユーザーが作成した仮想マシンは QEMU のユーザープロセスとして実行されます。 算術演算など簡単な命令は、一般的なユーザープロセスと同じようにユーザー空間で処理されますが、 I/O などのセンシティブ命令はハイパーバイザ側で処理する必要があります。 仮想マシンがセンシティブ命令を実行しようとすると、仮想マシンの実行が一時停止し、 KVM Exit というイベントが発生します。 ハイパーバイザは KVM Exit が発生した理由 (Exit Reason) を調べて適切に処理し、仮想マシンの実行を再開します。このとき KVM Entry イベントが発生します。 このサイクルを図示すると次の図のようになります。 KVM Exit が発生してから KVM Entry が完了するまで仮想マシンは一時停止しているので、このレイテンシが長いと問題です。 このような解析には perf kvm サブコマンドも利用できますが、今回は KVM Exit/Entry のレイテンシを題材に、 perf の独自スクリプトを書いて分析してみます。 CPU トレースを取得する まずは解析する CPU トレースを取得します。 仮想マシンを実行する物理マシンには複数の VM が収容されています。 あとで解析しやすいように、 CPU affinity を調整することで、解析対象 VM の vCPU を特定の物理 CPU コアに pinning しておきます。 $ sudo virsh vcpupin instance-0000xxxx 0 124 $ sudo virsh vcpupin instance-0000xxxx 1 125 $ sudo virsh vcpupin instance-0000xxxx 2 126 $ sudo virsh vcpupin instance-0000xxxx 3 127 以下のようなコマンドで kvm:kvm_exit と kvm:kvm_entry イベントのトレースを取得します。 $ sudo taskset -c 31 perf record -C 124-127 -e kvm:kvm_exit -e kvm:kvm_entry -- sleep 9000 perf script を使えばこのように解析できますが、少々見にくいです。 $ perf script -i /path/to/perf.data CPU 0/KVM 18292 [124] 151678.240967: kvm:kvm_exit: vcpu 0 reason MSR_WRITE rip 0xffffffff9ae6cf64 info1 0x0000000000000000 info2 0x0000000000000000 intr_info 0x00000000 error_code 0x00000000 CPU 0/KVM 18292 [124] 151678.240969: kvm:kvm_entry: vcpu 0, rip 0xffffffff9ae6cf66 CPU 0/KVM 18292 [124] 151678.240982: kvm:kvm_exit: vcpu 0 reason MSR_WRITE rip 0xffffffff9ae6cf64 info1 0x0000000000000000 info2 0x0000000000000000 intr_info 0x00000000 error_code 0x00000000 CPU 0/KVM 18292 [124] 151678.240983: kvm:kvm_entry: vcpu 0, rip 0xffffffff9ae6cf66 CPU 0/KVM 18292 [124] 151678.241044: kvm:kvm_exit: vcpu 0 reason EXTERNAL_INTERRUPT rip 0xffffffff9b0c99be info1 0x0000000000000000 info2 0x0000000000000000 intr_info 0x800000ec error_code 0x00000000 そこで、独自スクリプトでは以下の 2 つを目標とします。 Exit Reason ごとに KVM Exit/Entry のレイテンシをヒストグラムとして表す 上記ヒストグラムをコアごとに分けて表示する perf をビルドする perf の Python インタプリタを利用するには、 perf の libpython ビルドオプションが有効化されている必要があります。 私のチームで利用できる Ubuntu 22.04 環境では、 linux-tools-common に含まれる perf にはこのオプションが有効化されていませんでした。 $ sudo perf version --build-options perf version 5.15 dwarf: [ on ] # HAVE_DWARF_SUPPORT dwarf_getlocations: [ on ] # HAVE_DWARF_GETLOCATIONS_SUPPORT glibc: [ on ] # HAVE_GLIBC_SUPPORT syscall_table: [ on ] # HAVE_SYSCALL_TABLE_SUPPORT libbfd: [ OFF ] # HAVE_LIBBFD_SUPPORT libelf: [ on ] # HAVE_LIBELF_SUPPORT libnuma: [ on ] # HAVE_LIBNUMA_SUPPORT numa_num_possible_cpus: [ on ] # HAVE_LIBNUMA_SUPPORT libperl: [ OFF ] # HAVE_LIBPERL_SUPPORT libpython: [ OFF ] # HAVE_LIBPYTHON_SUPPORT libslang: [ on ] # HAVE_SLANG_SUPPORT libcrypto: [ on ] # HAVE_LIBCRYPTO_SUPPORT libunwind: [ on ] # HAVE_LIBUNWIND_SUPPORT libdw-dwarf-unwind: [ on ] # HAVE_DWARF_SUPPORT zlib: [ on ] # HAVE_ZLIB_SUPPORT lzma: [ on ] # HAVE_LZMA_SUPPORT get_cpuid: [ on ] # HAVE_AUXTRACE_SUPPORT bpf: [ on ] # HAVE_LIBBPF_SUPPORT aio: [ on ] # HAVE_AIO_SUPPORT zstd: [ OFF ] # HAVE_ZSTD_SUPPORT libpfm4: [ OFF ] # HAVE_LIBPFM そこで、以下のように Linux カーネルのソースコードをダウンロードし、 tools/perf をビルドします。 $ sudo apt install make gcc flex bison pkg-config $ sudo apt install libzstd1 libdwarf-dev libdw-dev binutils-dev libcap-dev libelf-dev libnuma-dev python3 python3-dev python-setuptools libssl-dev libunwind-dev libdwarf-dev zlib1g-dev liblzma-dev libaio-dev libtraceevent-dev debuginfod libpfm4-dev libslang2-dev systemtap-sdt-dev libperl-dev binutils-dev libbabeltrace-dev libiberty-dev libzstd-dev $ curl -O https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.15.tar.xz $ tar xvf linux-5.15.tar.xz $ cd linux-5.15/tools/perf # python3-config パッケージを利用するために `PYTHON=python3` が必要でした $ make PYTHON=python3 libpython オプションを有効化できました。 $ ./perf version --build-options perf version 5.15.0 dwarf: [ on ] # HAVE_DWARF_SUPPORT dwarf_getlocations: [ on ] # HAVE_DWARF_GETLOCATIONS_SUPPORT glibc: [ on ] # HAVE_GLIBC_SUPPORT syscall_table: [ on ] # HAVE_SYSCALL_TABLE_SUPPORT libbfd: [ on ] # HAVE_LIBBFD_SUPPORT libelf: [ on ] # HAVE_LIBELF_SUPPORT libnuma: [ on ] # HAVE_LIBNUMA_SUPPORT numa_num_possible_cpus: [ on ] # HAVE_LIBNUMA_SUPPORT libperl: [ on ] # HAVE_LIBPERL_SUPPORT libpython: [ on ] # HAVE_LIBPYTHON_SUPPORT libslang: [ on ] # HAVE_SLANG_SUPPORT libcrypto: [ OFF ] # HAVE_LIBCRYPTO_SUPPORT libunwind: [ on ] # HAVE_LIBUNWIND_SUPPORT libdw-dwarf-unwind: [ on ] # HAVE_DWARF_SUPPORT zlib: [ on ] # HAVE_ZLIB_SUPPORT lzma: [ on ] # HAVE_LZMA_SUPPORT get_cpuid: [ on ] # HAVE_AUXTRACE_SUPPORT bpf: [ on ] # HAVE_LIBBPF_SUPPORT aio: [ on ] # HAVE_AIO_SUPPORT zstd: [ on ] # HAVE_ZSTD_SUPPORT libpfm4: [ OFF ] # HAVE_LIBPFM Python コードを書く 準備が整ったので、いよいよ CPU トレースを解析する Python スクリプトを記述します。 詳しい解説やサンプルコードが perf-script-python(1) にあるので、 man を参考に作業します。 まずはテンプレートを生成します。 テンプレートは perf script -g で生成できますが、 perf record -e で指定したイベントごとに関数を実装するので、 perf.data を渡す必要があります。 $ ~/linux-5.15-build/tools/perf/perf script -i /path/to/perf.data -g python generated Python script: perf-script.py 生成されたファイルをみると、以下のようになっています。 # $ cat perf-script.py # (snip) def trace_begin (): print ( "in trace_begin" ) def trace_end (): print ( "in trace_end" ) def kvm__kvm_exit (event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, common_callchain, exit_reason, guest_rip, isa, info1, info2, intr_info, error_code, vcpu_id, perf_sample_dict): print_header(event_name, common_cpu, common_secs, common_nsecs, common_pid, common_comm) print ( "exit_reason=%s, guest_rip=%u, isa=%u, " \ "info1=%u, info2=%u, intr_info=%u, " \ "error_code=%u, vcpu_id=%u" % \ (flag_str( "kvm__kvm_exit" , "exit_reason" , exit_reason), guest_rip, isa, info1, info2, intr_info, error_code, vcpu_id)) print ( 'Sample: {' +get_dict_as_string(perf_sample_dict[ 'sample' ], ', ' )+ '}' ) for node in common_callchain: if 'sym' in node: print ( " \t [%x] %s" % (node[ 'ip' ], node[ 'sym' ][ 'name' ])) else : print ( " [%x]" % (node[ 'ip' ])) print () def kvm__kvm_entry (event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, common_callchain, vcpu_id, rip, perf_sample_dict): print_header(event_name, common_cpu, common_secs, common_nsecs, common_pid, common_comm) # (snip) print () def trace_unhandled (event_name, context, event_fields_dict, perf_sample_dict): print (get_dict_as_string(event_fields_dict)) print ( 'Sample: {' +get_dict_as_string(perf_sample_dict[ 'sample' ], ', ' )+ '}' ) # (snip) perf record -e で指定したイベント名ごとに関数が定義されていることと、イベントの付加情報が引数として渡されることがわかります。 KVM Exit/Entry のレイテンシを計測するには、グローバルなデータ構造を用意し、 kvm__kvm_exit に渡されたイベント発生時刻から kvm__kvm_entry に渡されたイベント発生時刻を引けばよさそうです。 今回の例では、 vCPU ごとに専用の物理 CPU コアを割り当てているので、 common_cpu を使えば kvm__kvm_exit イベントと kvm__kvm_exit イベントを対応づけることができます。 最後に kvm__kvm_exit が発生したときの時刻を last_kvm_exit_at に、そのときの Reason を last_kvm_exit_reason へ保存するようにします。 これらの情報をコアごとに保存するため、 defaultdict を使ってコア番号をキーとする辞書を使います。 kvm__entry イベントの発生時刻から last_kvm_exit_at に保存された時刻を引き、レイテンシとして集計します。 イベントの発生時刻は整数部分 common_secs と小数部分 common_nsecs に分けて渡されることに注意します。 これらを結合してイベントの発生時刻を計算するには Util パッケージの nsecs 関数を使い、次のようにします。 nsecs(common_secs, common_nsecs) exit_reason は整数で渡されるので、文字列表現に対応づけるには arch/x86/include/uapi/asm/vmx.h を参考にするとよいです。 最終的なコードは以下のようになりました。 # $ cat perf-script-kvm-exit-entry.py # perf script event handlers, generated by perf script -g python # Licensed under the terms of the GNU GPL License version 2 # The common_* event handler fields are the most useful fields common to # all events. They don't necessarily correspond to the 'common_*' fields # in the format files. Those fields not available as handler params can # be retrieved using Python functions of the form common_*(context). # See the perf-script-python Documentation for the list of available functions. from __future__ import print_function import os import sys sys.path.append(os.environ[ 'PERF_EXEC_PATH' ] + \ '/scripts/python/Perf-Trace-Util/lib/Perf/Trace' ) sys.path.append( '/home/openstack/work_ksugiura/linux-5.15-build/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace' ) from perf_trace_context import * from Core import * from Util import nsecs import numpy as np from collections import defaultdict ms = 1000 * 1000 exit_reason = ( "EXCEPTION_NMI" , "EXTERNAL_INTERRUPT" , "TRIPLE_FAULT" , "INIT_SIGNAL" , "N/A" , "N/A" , "N/A" , "INTERRUPT_WINDOW" , "NMI_WINDOW" , "TASK_SWITCH" , "CPUID" , "N/A" , "HLT" , "INVD" , "INVLPG" , "RDPMC" , "RDTSC" , "N/A" , "VMCALL" , "VMCLEAR" , "VMLAUNCH" , "VMPTRLD" , "VMPTRST" , "VMREAD" , "VMRESUME" , "VMWRITE" , "VMOFF" , "VMON" , "CR_ACCESS" , "DR_ACCESS" , "IO_INSTRUCTION" , "MSR_READ" , "MSR_WRITE" , "INVALID_STATE" , "MSR_LOAD_FAIL" , "N/A" , "MWAIT_INSTRUCTION" , "MONITOR_TRAP_FLAG" , "N/A" , "MONITOR_INSTRUCTION" , "PAUSE_INSTRUCTION" , "MCE_DURING_VMENTRY" , "N/A" , "TPR_BELOW_THRESHOLD" , "APIC_ACCESS" , "EOI_INDUCED" , "GDTR_IDTR" , "LDTR_TR" , "EPT_VIOLATION" , "EPT_MISCONFIG" , "INVEPT" , "RDTSCP" , "PREEMPTION_TIMER" , "INVVPID" , "WBINVD" , "XSETBV" , "APIC_WRITE" , "RDRAND" , "INVPCID" , "VMFUNC" , "ENCLS" , "RDSEED" , "PML_FULL" , "XSAVES" , "XRSTORS" , "N/A" , "N/A" , "UMWAIT" , "TPAUSE" "N/A" , "N/A" , "N/A" , "N/A" , "N/A" , "BUS_LOCK" , "NOTIFY" , ) def trace_begin (): global last_kvm_exit_at last_kvm_exit_at = defaultdict( lambda : 0 ) global last_kvm_exit_reason last_kvm_exit_reason = defaultdict( lambda : - 1 ) global kvm_entry_latencies kvm_entry_latencies = defaultdict( lambda : defaultdict( lambda : [])) print ( "in trace_begin" ) def trace_end (): global last_kvm_exit_at global kvm_entry_latencies print ( "in trace_end" ) print ( "kvm_entry_latencies" ) for core, reason_latencies in kvm_entry_latencies.items(): print ( "Core: " , core) for reason, latencies in reason_latencies.items(): print ( "Reason: [%d] %s" %(reason, exit_reason[reason])) max_latency = max (latencies) bins = [ 2 **i for i in range ( int (np.log2(max_latency)) + 2 )] hist, bins = np.histogram(latencies, bins=bins) for i in range ( len (hist)): print ( "[%16d, %16d) %d" % (bins[i], bins[i+ 1 ], hist[i])) def kvm__kvm_exit (event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, common_callchain, exit_reason, guest_rip, isa, info1, info2, intr_info, error_code, vcpu_id, perf_sample_dict): global last_kvm_exit_at global last_kvm_exit_reason global kvm_entry_latencies last_kvm_exit_at[common_cpu] = nsecs(common_secs, common_nsecs) last_kvm_exit_reason[common_cpu] = exit_reason def kvm__kvm_entry (event_name, context, common_cpu, common_secs, common_nsecs, common_pid, common_comm, common_callchain, vcpu_id, rip, perf_sample_dict): global last_kvm_exit_at global last_kvm_exit_reason global kvm_entry_latencies if last_kvm_exit_at[common_cpu] > 0 : latency = nsecs(common_secs, common_nsecs) - last_kvm_exit_at[common_cpu] reason = last_kvm_exit_reason[common_cpu] if latency > 1 * ms and reason != 12 : print ( "exit-entry: [%d]: [%d.%d]: reason: %s, %d" % (common_cpu, common_secs, common_nsecs, exit_reason[reason], latency)) kvm_entry_latencies[common_cpu][reason].append(latency) last_kvm_exit_at[common_cpu] = 0 def trace_unhandled (event_name, context, event_fields_dict, perf_sample_dict): print (get_dict_as_string(event_fields_dict)) print ( 'Sample: {' +get_dict_as_string(perf_sample_dict[ 'sample' ], ', ' )+ '}' ) def print_header (event_name, cpu, secs, nsecs, pid, comm): print ( "%-20s %5u %05u.%09u %8u %-20s " % \ (event_name, cpu, secs, nsecs, pid, comm), end= "" ) def get_dict_as_string (a_dict, delimiter= ' ' ): return delimiter.join([ '%s=%s' %(k, str (v)) for k,v in sorted (a_dict.items())]) 独自スクリプトを用いて perf.data を集計する では、作成したスクリプト perf-script-kvm-exit-entry.py を用いて KVM Exit/Entry のレイテンシを解析してみます。 Exit Reason ごと、コアごとにヒストグラムを作成すると以下のようになりました。 出力は非常に長いので、一部だけ掲載します。 $ perf script -i /path/to/perf.data -s /path/to/perf-script-kvm-exit-entry.py exit-entry: [126]: [151856.170064250]: reason: EXTERNAL_INTERRUPT, 1522602 exit-entry: [125]: [151915.737653830]: reason: EXTERNAL_INTERRUPT, 1282485 exit-entry: [124]: [152201.81772565]: reason: EXTERNAL_INTERRUPT, 2198819 exit-entry: [127]: [152672.999539636]: reason: EXTERNAL_INTERRUPT, 1341486 exit-entry: [124]: [152812.995271815]: reason: EXTERNAL_INTERRUPT, 1475909 Core: 124 Reason: [32] MSR_WRITE [ 1, 2) 0 [ 2, 4) 0 [ 4, 8) 0 [ 8, 16) 0 [ 16, 32) 0 [ 32, 64) 0 [ 64, 128) 0 [ 128, 256) 17842 [ 256, 512) 1884223 [ 512, 1024) 2344850 [ 1024, 2048) 2567959 [ 2048, 4096) 653205 [ 4096, 8192) 81909 [ 8192, 16384) 1771 [ 16384, 32768) 620 [ 32768, 65536) 5 [ 65536, 131072) 0 [ 131072, 262144) 0 [ 262144, 524288) 1 [ 524288, 1048576) 0 [ 1048576, 2097152) 1 Reason: [1] EXTERNAL_INTERRUPT [ 1, 2) 0 [ 2, 4) 0 [ 4, 8) 0 [ 8, 16) 0 [ 16, 32) 0 [ 32, 64) 0 [ 64, 128) 0 [ 128, 256) 0 [ 256, 512) 0 [ 512, 1024) 112 [ 1024, 2048) 390 [ 2048, 4096) 7145 [ 4096, 8192) 13116 [ 8192, 16384) 69835 [ 16384, 32768) 23903 [ 32768, 65536) 44 [ 65536, 131072) 20 [ 131072, 262144) 1 [ 262144, 524288) 1 [ 524288, 1048576) 3 [ 1048576, 2097152) 11 [ 2097152, 4194304) 1 Reason: [48] EPT_VIOLATION [ 1, 2) 0 [ 2, 4) 0 [ 4, 8) 0 [ 8, 16) 0 [ 16, 32) 0 [ 32, 64) 0 [ 64, 128) 0 [ 128, 256) 0 [ 256, 512) 0 [ 512, 1024) 37 [ 1024, 2048) 286 [ 2048, 4096) 940 [ 4096, 8192) 848 [ 8192, 16384) 223 [ 16384, 32768) 785 [ 32768, 65536) 17 [ 65536, 131072) 1 [ 131072, 262144) 6 [ 262144, 524288) 11 [ 524288, 1048576) 4 全体を眺めてみると、 EXTERNAL_INTERRUPT は頻度が高くレイテンシも大きい傾向にあることがわかりました。 EPT_VIOLATION の頻度は低いものの、発生したときのペナルティは大きいようです。 今回の実験のみから何かを導き出すのは難しいですが、ホスト OS の構成や、ゲスト OS の種類、ゲスト OS のワークロードを変更したときの差分に着目すると、有意義な結果が得られるかもしれません。 まとめ perf には独自の Python スクリプトを用いて CPU トレースを集計できる機能があります。デフォルトの表示を変えたいときや、独自の集計処理を書きたいときに有用です。 kvm:kvm_exit と kvm:kvm_entry というトレースポイントを使うことで、 KVM Exit / Entry のレイテンシを集計し、ヒストグラムとして表示しました。 参考文献 第5回 x86プロセッサの仮想化支援機能Intel VT & AMD-V | gihyo.jp Linux perf Examples perf-script-python(1)
アバター
この記事は、 NTT Communications Advent Calendar 2024 1日目の記事です。 こんにちは、イノベーションセンターの加藤です。普段はコンピュータビジョンの技術開発やAI/機械学習(ML: Machine Learning)システムの検証に取り組んでいます。一方で、兼務で生成AIチームに参加し、大規模言語モデル(LLM: Large Language Model)に関する技術の調査を行なっています。 音声アシスタントをLLMベースで作成する際、ユーザーの入力音声を一旦テキストに変換し、LLMに応答させた後、その応答文から読み上げ音声を生成するというカスケード方式がこれまで取られてきています。 一方最近ではMini-Omni 1 など、音声を入力として音声を出力するLLMを一貫して学習可能なエンドツーエンド方式も登場してきています。音声アシスタントのようにユーザーとやりとりするシステムにおいて、ユーザーの入力が終わってからアシスタントが応答を返し始めるまでの時間はレイテンシと呼ばれ、ユーザーの体験に直結する大事な指標ですが、エンドツーエンド方式ではこのレイテンシが短くなる傾向にあります。 しかしながら、利用するLLMモデルや読み上げ音声のカスタマイズの柔軟性という面で既存のカスケード方式にも長所があります。 今回はストリーム処理を活用することで、カスケード方式の欠点であるレイテンシの短縮に取り組んだ結果を紹介します。 目次 目次 音声対話システムの仕組み VADを用いてユーザーの会話終わりを検出する ASRを用いてユーザーの発話内容を文字列に変換する LLMを用いてチャットボットの応答を文字列として取得する TTSを用いて応答文を読み上げさせる 各処理を順に行うGUIを作成 各モジュールのレイテンシを測る TTSのストリーム処理 文で区切る 文節で区切る ユーザーからの割り込み まとめ 参考資料 音声対話システムの仕組み 今回作成する音声対話システムは次のモジュールで構成されています。 VAD(Voice Activity Detection, 音声区間検出) ASR(Automatic Speech Recognition, 自動音声認識) LLM(Large Language Model, 大規模言語モデル) TTS(Text to Speech, 音声合成) これらのモジュールを使い、以下の流れで処理を行います。 VADを用いてユーザーの会話終わりを検出する ユーザーがシステムに音声を送信する際、話し終わったタイミングで送信ボタンを押すというUIも考えられますが、今回はスムーズな会話を実現するために自動で発話の終わりを検出します。推論モデルは Silero VAD を利用し、マイクから入力された音声を32msの粒度で発言中かどうかを判定します。そして発話の終わりを検知し、無音時間が閾値の300msを超えたら、音声バッファから喋っている部分を切り出し次の処理に回します。 from silero_vad import load_silero_vad, get_speech_timestamps, VADIterator import gradio as gr import numpy as np import torch import librosa from typing import Tuple, Optional, List AudioType = Tuple[ int , np.ndarray] class VAD : def __init__ (self): self.SAMPLING_RATE = 16000 self.vad_iter = VADIterator( load_silero_vad(onnx= True ), sampling_rate=self.SAMPLING_RATE, min_silence_duration_ms= 300 , ) self.reset() def reset (self): self.vad_iter.reset_states() self.buffer = np.empty( 0 , dtype=np.float32) self.ptr = 0 self.orig_sr = None self.start = None self.end = None def __call__ (self, audio: AudioType): """ストリーミングされる音声を受け取り、発話が終わった時のみ音声バッファを返す""" sr, wave = audio self.orig_sr = sr window = int ( 512 * sr / self.SAMPLING_RATE) if wave.dtype == np.int16: wave = wave.astype(np.float32) / 32768.0 # Convert to mono if stereo if wave.ndim > 1 : wave = wave.mean(axis= 1 ) self.buffer = np.concatenate([self.buffer, wave]) while len (self.buffer) - self.ptr > window: chunk = self.buffer[self.ptr:self.ptr + window] chunk = librosa.resample(chunk, orig_sr=sr, target_sr=self.SAMPLING_RATE) chunk = torch.tensor(chunk) speech_dict = self.vad_iter(chunk, return_seconds= True ) self.ptr += window if speech_dict: if "start" in speech_dict: self.start = speech_dict[ "start" ] elif "end" in speech_dict: self.end = speech_dict[ "end" ] speech = self.buffer[ int (self.start * self.orig_sr) : int (self.end * self.orig_sr)] return (sr, speech) return None ASRを用いてユーザーの発話内容を文字列に変換する VADにより会話の終了を検知したら、今までのバッファを音声認識モデルに入力し、LLMに入れるための文字列に変換します。モデルは Whisper を利用しました。 import whisper class ASR : def __init__ (self): self.whisper_sr = whisper.audio.SAMPLE_RATE self.whisper_model = whisper.load_model( "large-v3" ) def __call__ (self, value: AudioType): sr, wave = value if wave.dtype == np.int16: wave = wave.astype(np.float32) / 32768.0 wave = librosa.resample(wave, orig_sr=sr, target_sr=self.whisper_sr) result = self.whisper_model.transcribe(wave, language= "ja" , temperature= 0.0 ) return result[ "text" ] LLMを用いてチャットボットの応答を文字列として取得する チャット用にチューニングされたLLMである CALM3-22B-Chat を利用し、ユーザーの発話内容に対して応答をさせます。 今回は複数回のやり取りを想定し、会話記録として history を受け取り応答文を返す形にします。 from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer, TextIteratorStreamer class LLM : def __init__ (self): self.llm_model = AutoModelForCausalLM.from_pretrained( "cyberagent/calm3-22b-chat" , device_map= "auto" , torch_dtype= "auto" ) self.llm_tokenizer = AutoTokenizer.from_pretrained( "cyberagent/calm3-22b-chat" ) self.prompts = [ { "role" : "system" , "content" : "あなたは親切なAIアシスタントです。" } ] def __call__ (self, history: List[ dict ]) -> str : token_ids = self.llm_tokenizer.apply_chat_template( self.prompts + history, add_generation_prompt= True , return_tensors= "pt" ) gen = self.llm_model.generate( input_ids=token_ids.to(self.llm_model.device), return_dict_in_generate= True , max_new_tokens= 300 , do_sample= False ) seq = gen.sequences seq = seq[:,token_ids.shape[ 1 ]:] # skip prompt response = self.llm_tokenizer.batch_decode(seq, skip_special_tokens= True )[ 0 ] return response TTSを用いて応答文を読み上げさせる 最後にTTSとして ESPnet を利用し、LLMからの応答文を音声に変換します。 from espnet2.bin.tts_inference import Text2Speech from espnet_model_zoo.downloader import ModelDownloader class TTS : def __init__ (self): model_tag = "kan-bayashi/jsut_full_band_vits_prosody" downloader = ModelDownloader(cachedir=Path.home() / ".cache" / "espnet_model_zoo" ) self.text2speech = Text2Speech( **downloader.download_and_unpack(model_tag), # No vocoder device= "cuda" , noise_scale= 0.333 , noise_scale_dur= 0.333 , always_fix_seed= True , ) def __call__ (self, text: str ): return self.forward(text) @ torch.no_grad () def forward (self, inpt) -> AudioType: wav = self.text2speech(inpt)[ "wav" ].cpu().numpy() return self.text2speech.fs, wav 各処理を順に行うGUIを作成 この記事ではGUIとして Gradio を利用します。 このプログラムではVADの判定により会話部分の区間が確定した後、ASR, LLM, TTSが順に実行されます。 VADで保存されている記録中の音声バッファなど、ユーザーに依存する状態は本来Gradioを使い管理する必要がありますが、本記事では簡単のため実装を省略しています。 作成したGUIは以下の画像のようになりました。 各モジュールのレイテンシを測る 一通りシステムが完成したので、次はどのモジュールがどれだけ時間をかけているか測定します。 人力で音声を入力すると話し終わりから応答までのレイテンシを測りにくいので、入力音声はTTSで作成し、TTSによる入力文の再生終了を話し終わりとみなして実行時間を計測しました。 GPU環境はNVIDIA H100 GPUを1枚利用しました。 入力文:AIによって私たちの暮らしはどのように変わりますか?簡潔に説明してください。 * 入力音声長:5.050 sec * ASR処理時間:0.787 sec * LLM処理時間:4.730 sec (0.033 sec / token) * 応答文:AIは私たちの暮らしに多岐にわたる影響を与えます。まず、日常生活の効率化が挙げられます。スマートホームデバイスや音声アシスタントが家事や日常のタスクを自動化し、時間を節約します。また、医療分野では診断や治療の精度が向上し、早期発見や個別化医療が進みます。さらに、交通や物流の分野では、自動運転車やドローン配送が普及し、移動や配送が迅速かつ効率的になります。教育分野では、AIを活用した個別学習プログラムが学習者の理解度に応じた指導を提供し、教育の質が向上します。最後に、エンターテインメントやクリエイティブ産業でも、AIが新しいコンテンツの生成やパーソナライズされた体験を提供し、私たちの楽しみ方を一変させます。 * 応答音声長:48.228 sec * TTS処理時間:0.198 sec * 全体レイテンシ(ASR + LLM + TTS + その他処理時間):5.895 sec 以上のように5.895秒が計測された全体レイテンシとなります。実際にユーザーが喋り終わってからアシスタントが喋り始めるまでには、喋り終わりが判定されるまでの300ミリ秒や、生成音声をブラウザーに渡す際のエンコード処理などの時間が追加でかかります。 しかし全体を通して一番のボトルネックはLLMの処理時間で、レイテンシの約80%を占めていることが分かります。 次の章では、LLMのストリーム出力を活用することでこのレイテンシを低減する手法や、それに伴って新しく可能になる機能を紹介します。 TTSのストリーム処理 文で区切る LLMの文章生成は逐次的に単語を出力するため、応答全体が揃うまで待たなくても音声を再生し始めることができます。 例えば、LLMからの出力に句点が入り次第音声を出力することで、返答の音声が始まるまでのレイテンシを短縮できます。本来はテキスト全体からイントネーションや発音の長さなどを決定し音声合成するため、文章全体を入力した場合とTTSの結果が少し異なってしまいますが、文を跨いで音声の調子が影響することは考えにくいため、レイテンシの短縮を優先して採用しました。実際に生成した音声も、文章全体から生成した場合とほぼ同じ結果が得られ、文単位で区切る手法は有用であることがわかりました。 # ただのTextIteratorStreamerは漢字が来るまで内部でバッファしてしまうのでバッファリングを無効化する class EagerTextIteratorStreamer (TextIteratorStreamer): def put (self, value): if len (value.shape) > 1 and value.shape[ 0 ] > 1 : raise ValueError ( "TextStreamer only supports batch size 1" ) elif len (value.shape) > 1 : value = value[ 0 ] if self.skip_prompt and self.next_tokens_are_prompt: self.next_tokens_are_prompt = False return self.token_cache.extend(value.tolist()) text = self.tokenizer.decode(self.token_cache, **self.decode_kwargs) printable_text = text[self.print_len :] self.token_cache = [] self.print_len = 0 self.on_finalized_text(printable_text) class LLMStream (LLM): def __call__ (self, history: List[ dict ]) -> str : token_ids = self.llm_tokenizer.apply_chat_template( self.prompts + history, add_generation_prompt= True , return_tensors= "pt" ) streamer = EagerTextIteratorStreamer(self.llm_tokenizer, skip_prompt= True , skip_special_tokens= True ) thread = Thread( target=self.llm_model.generate, kwargs= dict ( input_ids=token_ids.to(self.llm_model.device), max_new_tokens= 300 , do_sample= False , streamer=streamer)) thread.start() yield from streamer class TTSStream (TTS): def __call__ (self, text_streamer): pattern = re.compile( r".+[.!。\n]" ) # 句点・改行のタイミングでTTSに渡す result = "" ptr = 0 for text in text_streamer: # LLMからの出力をストリームで受け取る result += text while ptr < len (result): m = pattern.search(result, ptr) if m: proc_text = result[ptr:m.end()] audio = self.forward(proc_text) yield proc_text, audio # 処理した文章と音声をストリームで返す ptr = m.end() else : break # process remaining text if len (result[ptr:]) > 0 : audio = self.forward(result[ptr:]) yield result[ptr:], audio 先ほどの例ならば「AIは私たちの暮らしに多岐にわたる影響を与えます。」まで出力された時点でTTSの実行や再生バッファへの入力を開始することで、これ以降のLLM推論によるレイテンシを隠蔽し、5.895秒から1.580秒まで短縮できました。 また、今回の実験ではH100を使っているというのもあり、基本的に生成される音声の長さよりも処理時間のほうが十分に短いため、LLMやTTSの処理が間に合わず再生バッファを枯渇させるというようなことは滅多に起こらないことがわかります。 先ほどの入力ケースでは下図のように、約50秒の応答音声を生成し切るのにかかった時間は5秒弱であり、その後はバッファされた音声を再生するだけとなっていることがわかります。 文節で区切る 前節では文単位で区切ってTTSに入力していましたが、どれくらい細かい区切りまでなら違和感のない音声を生成できるのでしょうか。今回は文節までなら別々に生成しても大丈夫だろうという仮定を置き実験してみました。 今回利用しているTTSモデルは VITS と呼ばれるものですが、前処理として Open JTalk による音素推定が行われています。これは入力したテキストから漢字の読み・単語の分割・アクセント位置などを解析し、音素と呼ばれる発音表記に変換する処理のことで、例えば「東京都に住む」という入力に対しては t o [ o ky o o ] t o n i # s u ] m u というような列に変換されます。ここで [ はピッチの上昇、 ] はピッチの下降、 # は文節の区切りを指し、読みやすく書き換えると ト↑オキョオ↓トニ/スム となります。この前処理結果を活用して、以下のような処理を行います。 LLMから新しく文章が生成されるたびに推定音素列を更新し、文節の切れ目が分かっているところまでの音素列をTTSに入力します。 この処理はOpenJTalkの前処理結果を抽出することで実現できます。 class TTSStream2 (TTS): def __call__ (self, text_streamer): clean = self.text2speech.preprocess_fn.text_cleaner to_tok = self.text2speech.preprocess_fn.tokenizer.text2tokens to_ids = self.text2speech.preprocess_fn.token_id_converter.tokens2ids fulltext = "" ptr = 0 token_buffer = [] token_ptr = 0 tts_input = [] for text in text_streamer: fulltext += text fulltoken = to_tok(clean(fulltext)) while True : token_buffer = fulltoken[token_ptr:] shift = len (token_buffer) if "#" in token_buffer[token_ptr:]: shift = min (token_buffer.index( "#" ), shift) elif "_" in token_buffer[token_ptr:]: shift = min (token_buffer.index( "_" ), shift) else : # not found break tts_input += token_buffer[:shift+ 1 ] token_ptr += shift+ 1 if len (tts_input) > 0 : ints_input = np.array(to_ids(tts_input), dtype=np.int64) audio = self.forward(ints_input) yield fulltext[ptr:], audio tts_input = [] # flush ptr = len (fulltext) # process remaining text tts_input = to_tok(clean(fulltext))[token_ptr:] if len (tts_input) > 0 : ints_input = np.array(to_ids(tts_input), dtype=np.int64) audio = self.forward(ints_input) yield fulltext[ptr:], audio この結果レイテンシを1.284秒まで短縮できました。 しかし、文節に区切ってTTSを行うと、音声の調子が少し不自然になってしまいました。以下の音声は「こんにちは」という入力に対する文単位TTSと文節単位TTSの結果です。 文単位TTS 文節単位TTS 文節間に不自然な間があったり、「どの/ような」のアクセントがおかしくなっていたりしています。 これは入力を文節ごとに制限したせいで、文節を跨ぐときの発音間隔や、文節が連なることによるイントネーションの変化が推定できなかったためと考えられます。 そこで次はTTS入力時に前後1文節をマージンとして一緒に推定し、目的の文節の音声のみを切り出すという方法をとってみました。 この処理を行うためには文章上の位置と読み上げ音声の位置を対応づける必要がありますが、 VITSの内部で生成される各音素の発音長を取り出すことで、生成した音声から目的の文節が読み上げられている区間を抽出できます。 これを行うと、レイテンシが1.284秒から1.290秒に少し悪化しますが、イントネーションが以下のように改善しました。 しかしながら、文節の継ぎ目でノイズが混入しており、生成音声を綺麗に切り貼りするのは難しいようです。 まとめると以下の表になります。 手法 レイテンシ(秒) 品質 シーケンシャル 5.895 ⚪︎ ストリーム処理(文単位) 1.580 ⚪︎ ストリーム処理(文節単位) 1.284 × ストリーム処理(文節単位+前後1文節マージン) 1.290 △ 音声の自然さという点では文単位で区切るのが限界のようです。システムプロンプトを工夫することで、応答の一文の長さ自体を短くすることも大事でしょう。 ユーザーからの割り込み 文章生成から音声出力までがストリーム処理になった場合、出力をいつでも中断できるという利点が生まれます。これとVADを組み合わせることで、応答中にユーザーが発言したときにLLM推論や音声出力を中断し、新しい応答を生成できます。ただし、アシスタントの応答音声をなんらかのスピーカーで出力させている場合は、応答の音声がそのままマイクに入ってしまってユーザーの発言と誤認してしまう問題を解決する必要があり、実用上はなかなか困難を伴うと思います。 まとめ 今回はGradioとLLMを用いて、ユーザーの音声を受け取り応答を音声で返す対話システムを作ってみました。このようなシステムではユーザーの発言が終わってからアシスタントが話し始めるまでのレイテンシがユーザーの体験に大きく影響しますが、LLMやTTSをストリーム処理することでレイテンシを短縮できました。また、ストリーム処理を実装することで、途中で推論をキャンセルできるという利点があることも紹介しました。 明日のアドベントカレンダーもお楽しみに。 参考資料 ChatGPT3.5 Turboを利用した対話システム https://developers.cyberagent.co.jp/blog/archives/44592/ Mini-omniのデモ https://huggingface.co/spaces/gradio/omni-mini/tree/main https://arxiv.org/abs/2408.16725 ↩
アバター
データ駆動型の意思決定が重要視される現代のビジネス環境において、プロダクト開発におけるデータ活用は不可欠です。 この記事では、開発中の新サービス COTOHA Insight Detector(仮称。以下、CID)でのデータ活用を通じて得られた知見と、データ活用の重要性について紹介します。 はじめに なぜデータ活用が大切か プロジェクトのゴールとチームの課題 プロジェクトの実施内容 ゴール設定 ヒアリングシート CJM作成 データ取得、分析環境の準備 仮説作成 検証 分析の課題 プロジェクトの振り返りと今後 おわりに はじめに こんにちは、デジタル改革推進部データドリブンマネジメント推進部門(以下、DDM)の組橋とコミュニケーション&アプリケーションサービス部(以下、C&A) CIDチームの村上です。 (組橋)DDMは、全社のデータ活用を推進する部署で、現在私は今回紹介するような各プロダクトのデータ活用の支援を実施したり、データ活用の研修を実施しています。 (村上)C&Aは、新サービスやアプリケーションを開発する部署で、現在私はCIDのプロダクトオーナーをしています。 今回はDDMのメンバーと一緒にリリース前のサービスに対するデータ活用を実施したプロジェクトの内容について説明します。 なぜデータ活用が大切か 本題に入る前に、なぜデータ活用が重要なのかについて触れておきます。 プロダクト開発において、ユーザーの声を聞くことは重要です。しかし、ときには「人はウソをつく」こともあります。 これはなにも悪意があってウソをつくことを指しているわけではありません。ユーザーの言葉が実態と乖離しているケースが発生しうるのです。 例えば、ユーザーが「この機能は結構使う」と言っていても、実際のデータを見るとほとんど使用していないことがあります。 他にも「これは基本機能なので、新規ユーザーのほとんどが使うだろう」と想定していても、実際にはかなりのユーザーが離脱していることもあります。 このような齟齬が生じる理由は、人間の記憶や認識が必ずしも正確ではないからです。 また、ユーザーが自身の行動を客観的に把握できていない場合もあります。 そのため、主観的な意見や直感だけでなく、データで検証することが重要になります。 プロジェクトのゴールとチームの課題 CIDのデータ活用プロジェクト(以下、本プロジェクト)におけるゴールは下記の通りと定めました。 ユーザー行動に関する定量的な仮説検証をCIDチームのみで実施できるようにすること このゴールとした理由については、プロダクト開発における下記のような課題をチームで抱えていたことに起因します。 プロダクト開発の課題 PoCなどを通じてご利用いただいているお客さまからは好評をいただいているものの、実際にはあまり使われないことがある データ分析の課題 ログをただ出しているだけで整理されていない(トラシュー目的のみ) どうすれば定量的に検証できるか不明 まず1点目の「良いと言ってるのに使ってくれない」と言う課題についてです。どんなプロダクトも技術的に優れている、あるいはUIが綺麗であるとしても、最終的にユーザが使ってくれないと意味がありませんし売れません。 そのためには開発段階からさまざまなターゲットユーザへのヒアリングやPoCなどを通し、どんな問題を解決するためにどんな人がどのように使ってくれるのか、と言う仮説検証をたくさん実施することになります。 その仮説検証について、特に「定性評価」を行なって評価する場合は、ターゲットユーザにある機能についてどう思っているかインタビューをします。 このインタビューでは、「とても良い」「便利で使いやすい」「もう少しサクサク動いてほしい」などポジティブ/ネガティブ問わず、さまざまな評価を得られます。 場合よってはポジティブな意見の割合が大きく、それを信じて特定の機能を価値あるものとして採用したとしても、実際にはユーザに使われない、なんてことがとてもよくあります。 これは社交辞令、日本人的な調和を優先とした回答、相手への配慮など色々な要因があると思われます。 しかし、先ほど述べた通り、直感は大切ですが「人はウソをつく」ので、やはりデータによる細かい検証が必要だと思います。 今のプロダクト開発は「定性評価」は欠かさず実施していましたが、データを用いた「定量評価」により各機能の利用状況などを仮説検証する、と言う流れはまだできていませんでした。 2点目についてですが、こちらもプロダクト開発にはよく見られる問題です。 トラブルシューティングや安全な運用のためにアプリケーションの動作ログそのものは保持しているのですが、それが開発段階でデータ分析の観点では生かされていなかった、と言うものになります。 また先述のような「定量評価」を素早く実施するためにどのような形でデータを保持するのか、どんな観点でデータを集計すればいいのかなど、雰囲気はわかっていても実際に自分たちで一気通貫に実施する経験やノウハウが足りていませんでした。 プロジェクトの実施内容 先のゴールを達成するために、本プロジェクトでは下記について実施しています。 ゴール設定 ヒアリングシート CJM作成 データ取得、分析環境の準備 仮説作成 検証 ゴール設定 最終的なゴールについては先に述べた通りですが、ゴールについてはプロダクトやチーム状況で変化します。 例えば別のチームでは、すでにサービスはリリース済みで、ユーザ離脱のタイミングをカスタマージャーニーマップに沿ってフェーズ遷移の分析をすることによる「顧客流入・離脱の仮説検証」に取り組んでいました。 一方自分たちのチームでは、まだサービスリリース前ということもありPoCなどは何件も実施していましたが、流入離脱を分析できるほどまだデータがなかったこともあり、既存機能の仮説検証をする方向でゴールを設定しました。 ヒアリングシート 企業の多くがそうであるように、NTT Comでもデータ分析組織であるDDMと実際に分析を実施したいサービス開発部署とで組織が分かれていることもあり、まずはお互いにプロダクトの状況をしっかり把握することが肝要でした。 そこで、下記のような内容を項目別に整理してみました。 プロダクト開発においては、エレベータピッチ、リーンキャンバスやバリュープロポジションキャンバスなどで整理されているものが多く含まれていると思います。 プロダクトの背景 NSM/KPI等の指標 収支状況 ペルソナ プロダクトの特徴 社内ステークホルダー データ活用状況 等 これらを活用することで、プロダクトの現在の状況や課題についてDDMと認識を合わせていきました。 CJM作成 カスタマージャーニマップ (以下、CJM) とは、顧客が商品やサービスを購入・利用するまでの道のりをわかりやすく整理したもので、プロダクト開発の際にも活用されます。 プロダクト開発用に全体のCJMは作成済みだったのですが、今回ゴールで設定していた「既存機能の仮説検証」に対応するため、トライアル時のユーザが触る機能をかなり詳細化したものを作り直しました。 こうすることでユーザが目的を達成するためにトライアルでどの機能をどういう順番で触り、どこに不満を感じどんな感情に変わっていくのか仮説を細かく定義でき、データ分析でどこを対象に定量検証するか、のイメージを固められました。 データ取得、分析環境の準備 具体的にどんな仮説を検証するのかは後述しますが、今回データ分析を実施するにあたり、商用環境のログを構造化していく必要があります。 NTT Comでは社内にDLXという社内の共用データ分析環境があるので、それを使う案やGCP(我々のプロダクトを構築している)上だけで完結させる案を検討しましたが、PJや案件の状況に応じて変わると思います。 検討したポイントは下記です。 学習コストがどのくらいか 分析にかかる時間はどのくらいか(リアルタイム性) データ設置に関するセキュリティ 変更容易性 データ連携のやりやすさ 最終的にはGCP上で完結させ、StackDriverからGCSに書き出したログを定期的に構造化して、分析を実施する形に落ち着きました。 仮説作成 今回ゴールである「ユーザー行動に関する定量的な仮説検証をCIDチームのみで実施できるようにすること」を達成するために、「既存機能について定量的な仮説検証」に取り組みました。 自分たちのプロダクトはいくつかの機能がすでにユーザヒアリングなどを通して価値があると判定されたものがいくつか存在しています。 そのうちの1つの機能について下記のような仮説を考えました。 当初の仮説: 「ユーザは分析結果画面で結果を確認するときに、<対象別、課題表示>、<課題別、対象表示> 両方のモードを使い分けて課題特定をしているはずだ。」 CIDにはVOC(お客さまの声)を分析する機能があるのですが、それを①「対象」別にどんな「課題」を持っているのかを表示する機能に加えて、②「課題」別にそれがどんな「対象」に分布しているのかを表示する機能が具備されています。 今回はこの②が本当に価値あるかを定量的に明らかにしようというわけです。 結論から述べるとこの仮説はデータ分析には不適切だと言うことがわかりました。 この仮説には下記の要素が抜け落ちているからです。 仮説検証後の具体的なアクション 仮説の判定をする具体的な目標数値 どちらもデータ分析結果が明らかになった後で決めると余計なバイアスがかかってしまうという共通の問題があります。 1については、結果が明らかになった後でその後のアクション(別の機能をためそう、検証機能自体を拡張しようなど)を決定してしまうとデータ分析の結果前提で自分たちの都合の良いアクションを決定してしまう可能性があります。 2については、例えば以下のような2つの仮説があるとき、両者は利用率が結果30%だったという結果は共通ですが、判定結果としての受け取り方が全く異なります。 利用率が20%を超えると思っていたが、30%だった。 利用率が40%を超えると思っていたが、30%だった。 後から数値を決めてしまうと都合のいいように判定結果を変えられてしまいます。 以上を踏まえて、ブラッシュアップ後の仮説は下記の通りとしました。 プラッシュアップ後の仮説: 「課題自体を特定したいユーザは分析結果画面で結果を確認するときに、<対象別、課題表示>モードの表示回数と比較して、<課題別、対象表示>モードの表示回数が50%以上となっているはずだ。」 合わせて仮説検証後の具体的なアクションも下記と定めました。 50%以上の場合:高頻度に<課題別、対象表示>モードを活用していると考えられるため、「課題を特定し、それに対して対策を講じるべき対象を決める」と言うケースにおいてCIDの価値を感じてくれているので現状のままでOK。 50%未満の場合:この機能が使われていない。課題ベースで分析の深掘りをしない理由を深ぼってヒアリングする。単純に機能に気がついていないのか必要性を感じないのかなど理由を探る。 実際にデータを見る前にここまで決めてからやっとデータの中身精査に取り掛かりました。 検証 下記は実際にあるPoCで使い始めたお客さまの一定期間の例です。 モード 表示回数 割合 <対象別、課題表示> 145 - <課題別、対象表示> 17 11.7% ユーザのヒアリング内容をもとに開発した機能は、実際に触った後のヒアリングでもとても肯定的な意見が多かったにも関わらず、実際のところ想定よりも使われていなかったことがわかりました。 正直この結果はユーザインタビューによる定性的な調査結果とは少しずれている結果だったので、チームとしても新しい発見であり真摯に受け止めて次回以降のユーザヒアリングに繋げていこうと思います。 最後に実際に実施した際に起きた問題点などについてもすこし触れておこうと思います。 分析の課題 キャッシュによる分析誤差 一部の機能についてはAPIコール回数などで定量評価をしようとすると、キャッシュが動作するなどして実際よりコール回数が小さくなってしまうなどの問題があるため、ユーザ行動を正確に把握するためにはフロントエンド側でもログをGoogle Analyticsなどで取得する必要がありました。 ログの構造化が煩雑 ログ設計もトラブルシューティング目的がメインだったりすると、後からリクエストやレスポンスのパラメータを詳細に見ようとした時にうまくシリアライズされていないと中身がわからなかったり、jsonとして読み取れなかったりして苦労することがありました。具体的には下記のような形のログだと分析ができません。 プロジェクトの振り返りと今後 CIDチームは、本プロジェクトを通じて以下の知見を得ました。 データ活用の一連の流れを経験できた ログの保存、整理方法 定量検証における仮説の立て方(基準値やnext action、有効な仮説) プロジェクト後、CIDチーム内では以下の変化が見られました。 Google Analytics (GA) 導入やログ整形の優先度が上がった データに基づいた議論・意思決定が目標になった 毎日利用量をSlackに流すようになった 日々の議論でも、パッとコマンドを叩くだけで実データによる仮説の裏付けができるようにしていきたいと考えています。 DDMでは、本プロジェクトで得られたノウハウはドキュメントにまとめ社内展開や、また研修コンテンツへの組み込みを検討しています。 スムーズな支援のために、支援メニューとして整理することも実施していきたいと考えています。 おわりに この記事では、プロダクトグロースにおけるデータ活用の重要性と具体的に実施した内容、得られた知見について紹介しました。 今後も、データから得られる知見を最大限に活用し、さらに価値ある取り組みを追求していきたいと考えています。
アバター
みなさんこんにちは、イノベーションセンターの益本 (@masaomi346) です。 Network Analytics for Security (以下、NA4Sec) プロジェクトのメンバーとして活動しています。 この記事では注意喚起を兼ねて、特殊詐欺を例に犯罪者のコミュニティで行われている活動を紹介します。 ぜひ最後まで読んでみてください。 警告 特殊詐欺について どのように詐欺に加担させようとするのか 特殊詐欺の裏で行われていること 1. 案件の紹介 受け子・出し子・かけ子 かけ子の仲介 荷受け・空き家の確認 SIMカードの契約 電話番号の契約 本人確認のなりすまし 偽造免許作成 2. 犯罪で使う道具の販売 空き部屋の紹介 銀行口座の販売 SIMカードの販売 3. 銀行口座や決済サービスのアカウントの買取など 銀行口座の買取 メルカリアカウント買取・レンタル 4. 違法薬物の販売 犯罪に加担するとどうなってしまうのか 特殊詐欺を減らす取り組み SNS上の闇バイトの募集に警告 AIを使った闇バイトの募集検知 金融機関との連携 何かあったときの相談先 さいごに 警告 この記事では犯罪者のコミュニティに関する話が出てきますが、 気軽に犯罪者のコミュニティへアクセスすることを推奨しているわけではありません 。 相手と直接接触しない場合であっても、犯罪者から捕捉される可能性があります。その点を念頭に置いてください。 特殊詐欺について 特殊詐欺とは、さまざまな手口で被害者と対面することなく信頼させ、最終的には現金などをだまし取る詐欺犯罪をいいます。 特殊詐欺の手口と対策 特殊詐欺の事件はよく話題になり、特に闇バイトで詐欺の実行役として犯罪に加担する人達が問題になっています。 闇バイトという単語をXのトレンドで定期的に見かけるぐらいに注目されています。 特殊犯罪による被害や詐欺に加担してしまう人を減らすべく、各所で注意喚起が出ています。 東京都 特殊詐欺加害防止特設サイト 闇バイトに注意!あなたを犯罪に巻き込む手口 / 実家にこまめに電話を! 特殊詐欺対策にもなります \ オレオレ詐欺などの特殊詐欺。家族が被害に遭わないか心配ですね。まめに電話で連絡を取り合っていると安心です。在宅時でも留守電にしておく、公的機関の名を出されても信用しないなどの対策も教えてあげましょう。 https://t.co/sAi8dZnB1t pic.twitter.com/oW8f7wbxcA — 政府広報オンライン (@gov_online) 2022年5月2日 【SNSなどで求人情報を探している方へ】 本年8月以降、相次いで発生している凶悪な強盗事件について、具体的な事例や特徴等をまとめました。参考にしていただき、この種の求人には応募しないようにしてください。 #警察 #強盗 #犯罪 #求人 #高額 #高収入 #即日 #運び #送迎 #ホワイト #簡単 pic.twitter.com/bd5ADjG5iO — 警察庁 (@NPA_KOHO) 2024年10月25日 どのように詐欺に加担させようとするのか 闇バイトや裏バイトなどとして、以下で募集されています。 SNS 掲示板サイト 求人サイト etc. 特に、SNSで募集されているのをよく見かけます。 以下の画像のように、さまざまなキーワードで募集をかけて犯罪に加担させようとします。 また、口座買取やアカウント買取という形で、犯罪に加担させようとすることもあります。 特殊詐欺の裏で行われていること 特殊詐欺に関わっている犯罪者達がどのような活動をしているのか、あまりピンとこないと思います。 特殊詐欺のコミュニティで行われている活動内容を実際のメッセージを見ながら紹介していきます。 ※ここに書かれているものはほんの一部であり、すべてこうというわけではありません。 1. 案件の紹介 リクルーターや実行役向けにさまざまな案件が紹介されています。 受け子・出し子・かけ子 かけ子の仲介 荷受け・空き家の確認 SIMカードの契約 電話番号の契約 本人確認のなりすまし 偽造免許作成 2. 犯罪で使う道具の販売 犯罪を支援するための道具を販売している人達が存在しています。 空き部屋の紹介 銀行口座の販売 SIMカードの販売 3. 銀行口座や決済サービスのアカウントの買取など 買い取ることで、詐欺などで使う振り込み先の口座を確保しています。 銀行口座の買取 メルカリアカウント買取・レンタル 4. 違法薬物の販売 闇バイトの募集から違法薬物の販売や配送に加担させられることがあります。 犯罪に加担するとどうなってしまうのか 犯罪に加担すると、いろんなものを失うことになります。 実名報道されて名前が残り続けることになる 実刑判決を受ければ、膨大な時間を失うことになる 場合によっては賠償金を支払わなければならなくなる 家族や友達含め誰からも信用されなくなる ここ最近闇バイトによる強盗が話題になっていますが、強盗による量刑は皆さまが想像されるよりも重く、特に強盗殺人罪や強盗致死罪の場合だと最低でも無期懲役になります。 また、強盗の準備をするだけでも強盗予備罪に問われる可能性があります。 闇バイトに加担して逮捕された人が口を揃えて後悔していることを語る記事が出ています。 “ルフィ”事件の実行役「闇バイト応募は終わりの始まり」 逮捕の男「闇バイトはもうやめようと思った」横浜強盗殺人 特殊詐欺を減らす取り組み 注意喚起以外にも、特殊詐欺を減らすためにさまざまな取り組みが行われています。 ほんの一例を紹介します。 SNS上の闇バイトの募集に警告 SNS上の闇バイトの募集ポストに対して、以下の画像のように警告を発信している取り組みが見られます。 日夜闇バイトの募集が行われていないか監視しています。 目立つように返信や引用して警告することで、被害を減らそうとしています。 警察庁 警視庁 千葉県警察 神奈川県警察 埼玉県警察 大阪府警察 京都府警察 兵庫県警察 愛知県警察 三重県警察 北海道警察 山梨県警察 長野県警察 福井県警察 岡山県警察 愛媛県警察 鹿児島県警察 AIを使った闇バイトの募集検知 投稿されたものが闇バイトの募集かどうかの判断を効率化するためにAIを活用する取り組みも見られます。 目視で確認すると時間がかかるため、ある程度の自動化も重要になっていきます。 闇バイトの募集投稿、AIで自動検知…人の目で探すより最大34倍の速さで警告発出 バイトル、生成AIを活用した「闇バイトチェックAI」を開始 金融機関との連携 不正な口座情報を共有したり、特殊詐欺のアポ電発生時に自動音声で連絡するなど、さまざまな方法で被害を減らそうとしています。 “不正な口座情報”を金融機関に提供…全国初の協定 埼玉県警 特殊詐欺防止に期待! 山形県警が新たに導入した「シン・オートコール」とは 何かあったときの相談先 もしあなたが、特殊詐欺の実行役として犯罪をさせられそうになっていたら、何も怯えることなく警察に相談しましょう。 警察が犯罪に加担しないように呼びかけており、相談した後にさまざまな対策をとってくれています。 また、あなたがもっている情報が犯罪組織の撲滅につながることもあります。 闇バイト 警察の呼びかけ強化以降 応募者など保護のケースも 状況によっては匿名で通報したい時もあるかと思います。 そのときは、下記の匿名通報ダイヤルへ通報するという手もあります。 匿名通報ダイヤル 匿名で通報できるだけでなく、通報した後どうなったか確認できます。 さいごに 今回紹介した特殊詐欺に限らず犯罪の分業化が進んでおり、一部の人を逮捕してもなかなか犯罪がなくなりません。 そのため、犯罪を減らすには、犯罪者を逮捕するだけでなく犯罪で利益を出なくすることも重要になります。 犯罪を知ることで被害を減らすヒントにつながるかもしれません。 本記事により、被害を受ける人を少しでも減らせるといいと筆者は考えています。
アバター
こんにちは。NTT Comの市村、田口、村上です。2024年8月に米国ラスベガスで同時期に開催された3種類のセキュリティカンファレンスへ聴講者として参加しました。 この記事では連日参加した3種類のセキュリティカンファレンス及び、聴講した中で印象深かった講演の概要について紹介します。 目次 目次 8月のラスベガスを彩るセキュリティの祭典 BSides Las Vegas Black Hat USA DEF CON 複数の会議が同時開催されるメリットについて 講演内容 BSides Las Vegas: Devising and detecting spear phishing using data scraping, large language models, and personalized spam filters Black Hat USA: Modern Kill Chains: Real World SaaS Attacks and Mitigation Strategies DEF CON (BHでも講演有) : Self-Hosted GitHub CI/CD Runners: Continuous Integration, Continuous Destruction 現地Tips おわりに 8月のラスベガスを彩るセキュリティの祭典 毎年8月、ラスベガスではBSides Las Vegas、Black Hat USA、DEF CONという3種類のセキュリティカンファレンスが同時期に続けて開催されます。 いずれもセキュリティをテーマとするカンファレンスですが、それぞれが異なる特徴を持っています。 以下、3種類のセキュリティカンファレンスの特徴と開催概要について紹介します。 BSides Las Vegas BSides Las Vegasは、Black Hat USAやDEF CONのような大規模カンファレンスと異なり、 コミュニティ主導で運営されるカジュアルな雰囲気のセキュリティカンファレンスです。 Black Hat、DEF CONと比べ小規模ですが、内容はどちらにも劣らず多様かつ高度な講演を聞くことができます。 BSides Las Vegasの最大の特徴は、セキュリティに関心のある人が自由に情報共有できるオープンな環境を提供している点です。 特に注目すべきは「Proving Ground」と呼ばれるトラックです。 このトラックは、国際的なカンファレンスでの講演経験のない人たちが初めて登壇する場として設けられおり、国際カンファレンスの初登壇をコミュニティがサポートしています。 今年のBSides Las Vegasは、Tuscany Suites & Casinoホテルにて、8月6日・7日の2日間開催されました。 また、今年はNTT Comからも講演しました。BSides Las Vegasで登壇した話については、以前当ブログで公開した 「 BSides登壇のBサイド ~なんで、私が海外セキュリティカンファレンスに!?~ 」にて紹介しています。 Black Hat USA Black Hat USAはサイバーセキュリティに携わる人々にとって、最も注目されるカンファレンスの1つであり、世界中のセキュリティプロフェッショナルが一堂に会するイベントとなっています。 Black Hat USAは、技術的な知識を共有するだけでなく、新たなビジネスチャンスを生み出すための商業的な色彩が強いカンファレンスでもあります。 ビジネスホールと呼ばれる展示会場では、大手セキュリティベンダーからスタートアップまで、数百もの企業がブースを出展し、参加者に対して製品デモやソリューションを提案しています。 講演(ブリーフィング)では、世界中から集まったセキュリティの専門家やリサーチャーが最先端の脅威、攻撃手法、脆弱性に関する講演が行われます。 多くの講演ではセキュリティ対策の導入や強化に関する具体的なアドバイスが提供され、企業は自社のセキュリティ戦略を見直し、最先端のソリューションを導入するための情報を得ることができます。 今年のBlack Hat USAは、Mandalay Bay Convention Centerにて、8月3日から8日までの6日間開催されました。 8月3日から6日までの期間は主にトレーニングが開催される期間で、7日・8日の2日間は講演が行われる期間です。今回は講演期間に聴講者として参加しました。 Black Hat USA ビジネスホールの様子 DEF CON DEF CONはBlack Hat USAと並ぶサイバーセキュリティ界の一大イベントです。 他2つのカンファレンスとはまた違った独自の文化と自由な雰囲気を持っており、 セキュリティに関心のあるすべての人が最新の技術を学び、実践し、そして楽しむことができるイベントとなっています。 DEF CONの大きな特徴は「Village」と呼ばれるコミュニティスペースです。 無線ハッキング、IoTセキュリティ、物理セキュリティなど特定のテーマに基づいたトーク、コンテスト、ワークショップが行われます。 参加者は自分の興味に応じたVillageへ訪れることで、興味分野の知識を深めることができます。 今年のDEF CONは、Las Vegas Convention Centerにて、8月8日から11日までの4日間開催されました。 今年は33個のVillageがありました。特に注目を集めていたVillageは「AI×CC」と呼ばれるVillageで、AIを活用した近未来の街をモチーフとした派手なブースが建てられていました。 DEF CON AI×CC Villageの様子 複数の会議が同時開催されるメリットについて 約1週間という期間の中で、対象/規模/雰囲気を異にした3種類のカンファレンスが まとめて開催されるのが例年のスタイルですが、これには大きなメリットが3つあると感じました。 まず参加者側にとっては、短時間・1回分の旅費で多くのテクノロジーを学べるというメリットがあります。 この時期のラスベガスに来さえすれば、最先端の情報に追い付いたり、また同好の士と出会うことができるのです。 同様にスポンサー企業側にとっても、この1週間を狙えば広告、イベント、採用活動を効率よく行えます。 どのイベントに出資するとしても費用対効果は抜群ではないでしょうか。 最後にカンファレンスそれぞれの雰囲気/目指す姿が異なることは、結果的に多くの人をセキュリティの世界へ立ち寄らせることに役立っている点が最大のメリットであると感じました。 たとえば、Black Hatでは「技術は専門ではないがビジネスに興味がある人」、BSidesでは「発表の機会が欲しかったセキュリティ研究者」、 DEF CONでは前者2つよりも手ごろな参加費のおかげで多くの学生や家族連れが参加していました。 これらのメリットを総合して考えると、対象が少しずつ異なるカンファレンス複数を同時期に開催する計画は、国内のセキュリティ界隈を盛り上げ、人材の裾野を広げることに繋がるのではないでしょうか。 講演内容 各セキュリティカンファレンスで聴講した講演の中で、印象深かったものを紹介します。 BSides Las Vegas: Devising and detecting spear phishing using data scraping, large language models, and personalized spam filters パーソナライズされたフィッシングメールフィルタを AI で作れないか、という内容の講演が興味深いと感じました。 訓練用の無害なフィッシングメールを使って、「A さんがひっかかりやすいキーワード」「B さんが...」といういわば弱点のデータベースを作り、 そういった弱点の要素を検出して優先的に弾いてくれる個人化されたメールフィルタを作成する試みについてでした。 この講演の概要はこちらからご覧ください。 https://www.bsideslv.org/talks#8WK8P3 Black Hat USA: Modern Kill Chains: Real World SaaS Attacks and Mitigation Strategies APP OmniというSSPM(SaaS Security Posture Management)ベンダーによる近年のSaaSに対する攻撃の統計情報を共有する発表でした。 企業業務で多くのSaaSを利用することが当たり前になってきた近年において、攻撃者はどのようなサービスをどのような目的で攻撃しているかをSSPMベンダーの視点から統計情報とともに共有していました。 特に興味深かった内容として、SaaSを起因とする攻撃者による一連の攻撃観測に成功したという点でした。 一連の攻撃を時系列に並べて観測したSaaS侵害後の流れを発表内で提示していました。 講演の最後では、多様なSaaSが普及した現代において特に注目しなければならないAttack Surfaceの提示と今後行うべきセキュリティ戦略についてまとめていました。 セキュリティ製品ベンダーからの啓蒙と対策提案という講演内容は、商業色の強いBlack Hat USAの特徴が現れている講演だったと思います。 この講演のスライド資料はこちらにありますのでご参照ください。 http://i.blackhat.com/BH-US-24/Presentations/US24-Michal-Modern-Kill-Chains-Real-World-SaaS-Attacks-Wednesday.pdf DEF CON (BHでも講演有) : Self-Hosted GitHub CI/CD Runners: Continuous Integration, Continuous Destruction GitHub Actionsを実行するためのRunnersを利用した攻撃方法についての発表でした。 APIで対象のRunnerの情報を取得し、権限確認後、ContributorになってC2サーバを埋め込むといった一連の流れから、 その詳細までみっちりと解説があり、非常に面白かったです。 対策方法はRunnersの設定やアクセストークン・シークレットを適切に管理しましょう、といった一般的なものが多かったですが、 攻撃のロジックやなぜ脆弱なのか?といった説明がわかりやすく、技術を探求するDEF CONらしさが味わえました。 講演はDEF CONのメディアサーバに上がっていますので、詳細気になる方はこちらからご覧ください。 https://media.defcon.org/DEF%20CON%2032/DEF%20CON%2032%20video%20and%20slides/ 現地Tips トコジラミが、盗難が、といった不安を覚える話を出発前によく聞きましたが、いざ現地に着いてみると意外と安全でした。(ただ事故がおきなかっただけ) とはいえここは運もあると思うので、しっかりと対策はして行った方が良いと思います。 (NFLabs.のエンジニアブログにトコジラミ対策等の詳細が書かれていたので、対策の詳細を知りたい方はこちらのブログを見てみてください) https://blog.nflabs.jp/entry/2024/09/19/133000 上記ブログで紹介されている以外のTipsとして以下もあります。 サングラス・日焼け止めは必須 一応羽織るものはあった方がいい 夏のラスベガスはとても暑く、気温は40度を超えます。 ただ非常に乾燥しているので、筆者的にはジメジメとした暑さの日本より快適に感じました。 また、外がやたらめったら暑い一方で、室内は寒いと感じるレベルで空調が効いている場所も多いです。なので筋肉量少なめエンジニアは防寒具を持っていくことをお勧めします。 (とても寒がりな上司はウルトラライトダウンを持って行ってもいいくらいと言っていました。) 現地の移動手段については、Lyft/Uber、モノレールあたりが良いと思います。 特にモノレールはDEF CON会場への移動手段として役立ちますし、複数日乗る場合はお得な○日券みたいなのもあります。 UberはBlack Hat会場への行き来で使いましたが、混雑しているスポットもあるので配車場所には要注意です。 先人達から現地Tipsを教わり、なんとか生きながらえることができました。(ありがとうございます) 来年以降ラスベガスへ行かれる方たちに、このTipsが役立てば幸いです。 おわりに BSides Las Vegas、Black Hat USA、DEF CONと盛りだくさんで非常に充実したラスベガス出張でした。 こういった機会を与えてくれるNTT Comには感謝しかないです。 担当分野の知見向上はもちろんですが、専門外の分野についての知見も広がるので、 今後のキャリアを考える上でも有意義な出張になりました。 また参加者同士の交流があるというのは、現地参加の大きなメリットだったかと思います。 海外の方との交流は言わずもがなですが、海外だからこそ日本からの参加者とも密な関係を気づくことができたのは非常に大きな収穫でした。
アバター
はじめに こんにちは、5G&IoT部/IoTサービス部門の下地です。SIMのAppletを活用したサービスの企画・開発に取り組んでいます。 IoT (Internet of Things) デバイスの普及に伴い、セキュリティの重要性が高まっていますが、GSMA規格のIoT SAFE (IoT SIM Applet For Secure End-to-End Communication) は、この課題に対するソリューションとして注目されています。 今回はそのIoT SAFEとAWS IoT Coreを連携させてみます。 はじめに IoT SAFEの特徴 試してみた 構成概要 ATコマンド利用設定 セルラー接続の設定 IoT SAFE連携用パッケージをインストール CSRの作成と確認 CA証明書の作成 クライアント証明書の作成 AWS IoT Coreへの登録・設定 CA証明書の登録 クライアント証明書の登録 ドメインの設定 AWS IoT Coreでのsubscribe設定 publishの実施 publishされたメッセージの確認 考察 IoT SAFEの特徴 特徴は以下の通りです。 安全な鍵管理 SIMカード内でキーペアを生成 そのため秘密鍵がSIMカード外に流出することなく安全な鍵管理が可能 エンドツーエンドの暗号化 デバイスはPKCS#11を通じてSIM内の秘密鍵にアクセス可能 PKCS#11を通じた署名により安全で標準性の高い相互認証を実現 運用負荷軽減 鍵の生成や公開鍵の登録が自動化されており、手動での運用負担を軽減 コスト削減 SIMカード内のHSM機能により、IoT機器自体のコスト軽減に貢献 リモート管理機能 OTA (Over-The-Air) による鍵情報やIoT SAFEアプレットの更新が可能 GSMA標準 GSMA標準規格に準拠しているため、異ベンダー間での相互運用性を促進 主要諸元 IoT.04 IoT.05 試してみた NTTコミュニケーションズの IoT Connect mobile Type S では、IoT SAFEをPoC (Proof of Concept) として提供しています。PoC申込者には、IoT SAFEアプレット搭載のSIMカードが提供されます。今回はそのSIMカードを用いてIoT SAFEとAWS IoT Coreの連携を試行しました。 構成概要 端末としてラズベリーパイを用い、IoT SAFEの管理コンソール「AppletConsole」と接続し、CA証明書やクライアント証明書を自作し、証明書等をAWS IoT Coreに登録します。 項目 詳細 SIMカード IoT Connect mobile Type S 提供のSIMカード IoT Device Raspberry Pi4 modelB OS Raspberry Pi OS 通信モジュール EG25-G CA証明書 OpenSSLによる自作 SIM管理コンソール AppletConsole デバイス管理 AWS IoT Core ATコマンド利用設定 デバイスからSIMカード内のIoT SAFE Appletと通信するためATコマンドツール (minicom) をインストールします。 minicomのインストール sudo apt-get install minicom ATコマンド送受信用デバイスファイルを確認 sudo minicom -D /dev/ttyUSBX ( ttyUSBX はデバイスにより異なる) プロンプト表示後、基本的なATコマンド( AT 等)を入力しレスポンスを確認 ctrl+a, x でminicomを終了 セルラー接続の設定 IoT Connect Mobile Type Sの接続のため、wvdialをインストール・接続設定を行います。 wvdial のインストール sudo apt-get install wvdial /etc/wvdial.conf の編集 [Dialer Defaults] Init1 = ATZ Init2 = AT+CGDCONT=1,"IP","mobiledata.ntt.com" Phone = *99# Username = "a" # ダミーの値が必要 Password = "b" # ダミーの値が必要 Modem = /dev/ttyUSB2 # デバイスによって異なる Baud = 115200 Stupid Mode = 1 セルラー接続 sudo nohup wvdial > wvdial.log 2>&1 & IoT SAFE連携用パッケージをインストール IoT SAFE連携に必要なパッケージをインストールします。 パッケージ名 説明 pkcs11-provider PKCS#11(暗号化トークンインターフェース)のプロバイダー。OpenSSLなどからHSMへのアクセスを提供。 pcscd PC/SCデーモン。スマートカードの読み取り・書き込みを管理するためのサーバープロセス。 opensc スマートカードのセキュリティ機能を提供するライブラリ。 pkcs11-dump PKCS#11のデバッグツールで、PKCS#11のオブジェクトを表示。 libifd-atcmd 個別提供パッケージ。ATコマンドを通じてIoT SAFEアプレットと通信時に使用。 pkcs11-iotsafe 個別提供パッケージ。PKCS#11を通じてIoT SAFEアプレットを利用するために使用。 各種既定パッケージのインストール apt install pkcs11-provider pcscd opensc pkcs11-dump 個別提供パッケージ libifd-atcmd のインストール sudo apt install ./libifd-atcmd_0.1.6-1_arm64.deb /etc/reader.conf.d/libifd-atcmd の編集 FRIENDLYNAME libifd-atcmd DEVICENAME :/dev/ttyUSB3 LIBPATH /usr/lib/pcsc/drivers/serial/libifdhandler_atcmd.so ATコマンド送受信用のデバイスファイルを指定する DEVICENAME :/dev/ttyUSB3 は、通信モジュールに合わせて調整する 個別提供パッケージ pkcs11-iotsafe のインストール sudo apt install ./pkcs11-iotsafe_0.1.3-1_arm64.deb CSRの作成と確認 クライアント証明書作成のためのCSR(証明書署名の要求)を作成します。 PoC申込者向けに提供されるpoetry管理のスクリプトを実行し、CSRを生成する 一定時間後にCSRがAppletConsoleに送信されるのでAppletConsoleにアクセスしCSRをコピーペーストして適当な場所にファイル保存する(ファイル名: csr.pem としておく) CSRがApplet Consoleに登録されていることを確認する方法 CA証明書の作成 クライアント証明書を検証するためのCA証明書を作成します。opensslが利用できる環境であれば、作成場所は特に問いません。 今回はラズベリーパイ内でCA証明書を作成します。 秘密鍵の作成 openssl genpkey -algorithm RSA -out ca.key CA証明書の作成 openssl req -new -x509 -key ca.key -out ca.crt -sha256 -days 1825 -subj "/C=JP/ST=Tokyo/O=mycompany" クライアント証明書の作成 クライアント証明書の作成 openssl x509 -req -in csr.pem -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365 -sha256 AWS IoT Coreへの登録・設定 CA証明書とクライアント証明書をAWS IoT Coreへ登録します。 CA証明書の登録 AWSコンソールへログインし、AWS IoT Coreを開く 「認証機関」>「CA証明書を選択」を選択しca.crtをアップロード(その際に証明書IDを控えておく) CAステータスを「ACTIVE」に変更し、その他はデフォルトのまま「登録」をクリック クライアント証明書の登録 続いて「証明書」>「証明書を追加」>「証明書を登録」をクリックし証明書の登録画面を開く CA証明書を選択のドロップダウンメニューから前述の証明書IDを選択 先に作成したclient.crtをアップロードしクライアント証明書を登録 ドメインの設定 「ドメイン設定」>「ドメイン設定を作成」>「続行」を選択 ドメイン設定名に任意の値を入力し、認証タイプが「X.509」であることを確認する アプリケーションプロトコルとしてHTTPSを選択する HTTPSでもpublishはできるためこちらを選択 「ドメイン設定を作成」を実施する AWS IoT Coreでのsubscribe設定 AWS IoT Coreにてトピックのsubscribe設定します。 「MQTTテストクライアント」>「サブスクライブ」タブを選択 「トピックフィルター」に test/testing を入力 「サブスクライブ」をクリック こちらの画面でpublishされたメッセージを確認できます。 publishの実施 PoC申込時に提供された openssl_iotsafe.cnf を使用して以下スクリプトでHTTPSによるpushを行います。 MQTTSでも大丈夫なはずですが、ひとまず扱いやすいHTTPSで実施してます。 #!/bin/bash topdir=$(realpath $(dirname $0)) export OPENSSL_CONF=${topdir}/openssl_iotsafe.cnf # 必要なパスの定義 ENDPOINT="xxxxx.iot.ap-northeast-1.amazonaws.com:8443" PATH_TO_CERTIFICATE="iotsafe.client.cert" PATH_TO_PRIVATE_KEY="pkcs11:token=IoTSAFE;type=private;id=%01" PATH_TO_AMAZON_ROOT_CA_1="AmazonRootCA1.pem" TOPIC="test/testing" MESSAGE="{\"message\": \"Hello, world\"}" # POSTリクエストのHTTPヘッダーとボディを構築 HTTP_REQUEST="POST /topics/${TOPIC}? HTTP/1.1\r\n" HTTP_REQUEST+="Host: $ENDPOINT\r\n" HTTP_REQUEST+="Content-Type: application/json\r\n" HTTP_REQUEST+="Content-Length: ${#MESSAGE}\r\n" HTTP_REQUEST+="Connection: close\r\n\r\n" HTTP_REQUEST+="$MESSAGE\r\n" # OpenSSLでPOSTリクエストを送信 (echo -en "$HTTP_REQUEST"; echo ; sleep 4) | \ openssl s_client \ -connect $ENDPOINT \ -cert $PATH_TO_CERTIFICATE \ -key $PATH_TO_PRIVATE_KEY \ -CAfile $PATH_TO_AMAZON_ROOT_CA_1 \ -tls1_2 AmazonRootCA1.pem はAWSの 既定サイト からダウンロードしておきます。 publishされたメッセージの確認 ラズベリーパイからpublishを実施すると、先にsubscribe設定した画面にて指定トピックのメッセージを購読できます。 考察 AWS IoT CoreとIoT SAFEの連携についての考察です。 SIM内で秘密鍵が生成されるため、秘密鍵等を外部からダウンロードする手間や保管場所に関する懸念からは解放される 今回はOpenSSL上にHTTPSでpublishを実施したが、理屈的にはMQTTSでもpublishできるはずで、こちらは試行錯誤中。 クライアントを openssl s_client から mosquitto に変えて試行してみたが、mosquittoにはPKCS#11連携にバグがありそうでうまくいかず。 ただ、理屈的にはPKCS#11対応アプリケーションであればIoT SAFEアプレットとの連携が容易かとも推測。 現状のAppletConsoleでは、CSRの作成やクライアント証明書の作成・登録手順が複雑なため、これらのプロセスが簡素化されないと商用運用は難しいとみる。 この技術がどのような領域の業種や産業で活用できるかについての考察は、次回記載予定。
アバター
本記事では、「ローカル5G網への接続と公衆モバイル網への接続を切り替え可能なSIMアプレット」について説明します。 SIMアプレットはSIMカード上に搭載するアプレットです。SIMアプレットとは何か、どのような機能を実装することで技術開発を実現したのかといったことをご紹介します。 目次 目次 はじめに 背景 SIMカード SIMアプレット 動作例 開発環境 SIMアプレットの活用事例 新たなSIMアプレットの開発 動機 開発技術の構成要素: プロファイル切替機能 開発技術の構成要素: エリア判定機能 各切り替え時のシーケンス 開発技術の検証 検証概要 検証結果 バグの修正 課題への対処 発信活動 おわりに はじめに こんにちは、イノベーションセンターの山田です。 私が所属するプロジェクトでは、NTT Communications株式会社 (以下、NTT Com) の有するアプレット領域分割技術を活用した新規技術の開発に取り組んでいます。 取り組みの1つが、2024年10月2日発表のニュースリリース「 ローカル5G網への接続と公衆モバイル網への接続を切り替え可能なSIMアプレットを開発 」で取り扱ったものです。 本記事ではSIMカードやSIMアプレット等の背景知識に触れたのち、開発した技術について説明します。 また、東日本電信電話株式会社 (以下、NTT東日本) と共同で実施したProof of Concept (以下、PoC) についても紹介します。 背景 SIMカード SIMカードはICカードの一種です。 スマートフォンやIoTデバイス等の端末で公衆モバイル網 (以下、公衆網) へ接続するときなどに必要となるもので、皆さんもスマートフォンとともに利用されているかと思います。 SIMカードの規格は大きさの違うものがいくつかあり、大きいものから順に1FF, 2FF (Mini SIM), 3FF (Micro SIM), 4FF (Nano SIM) となっています。 最近のスマートフォンで利用されているものの多くはNano SIMになります。 各規格については下図をご参照ください。 また、最近では端末と一体になったeSIMも広まりつつあります。 SIMカードの特長として耐タンパ性 1 の高さがあげられます。 ソフトウェアの観点では、SIMカード内部へのアクセスはいくつかの鍵によって多段に管理されています。 また、ハードウェアの観点では、SIMカードを物理的に開封するとチップの回路が破壊される構造になっています。 そのため、外部からの不正アクセスや情報の改ざんが困難です。 SIMカードは小さなコンピュータとしての側面も持ちます。 CPUやメモリ領域を保有し、OSが実装されています。 SIMカード上で動作するアプレットをSIMアプレットといいます。 SIMカードと端末はSim Toolkit (STK) という規格にそって通信します。 通信には Application Protocol Data Unit (APDU) コマンドが利用されています。 このAPDUコマンドをSIMカードが能動的に端末へ送ることはできず、端末がなんらかのAPDUコマンドを送りSIMカードがそれへ応答する形式になっています。 しかし、SIMカードが能動的に端末が持つ情報を要求したいといったことも想定されます。 そのために、Proactiveコマンドという仕組みが用意されています。 Proactiveコマンドは、FETCHというAPDUコマンドへの応答に載せることで、SIMカードから端末へと送られます。 下図は一連の流れを抽象化したシーケンス図です。 端末からSIMカードへAPDUコマンドが送られる。 SIMカードはAPDUコマンドへの応答として 91 XX を返す。ここで 91 XX は「APDUコマンドの実行に成功、かつ端末へ送りたいデータが XX バイトある」ことを意味する。 端末がFETCHを送る。 SIMカードはFETCHへの応答として実行したいProactiveコマンドを送る。 端末はAPDUコマンドの1つであるTerminal Responseを用いて、SIMカードへProactiveコマンドの実行許可を出す。 SIMカードはTerminal Responseへの応答として実行結果を送る。ここで Status Word: 90 00 は「Proactiveコマンドの実行に成功した」ことを意味する。 SIMアプレット SIMカードの論理構造は、通信プロファイル領域とアプレット領域に分けられます。 従来のSIMカードは通信プロファイル領域にアプレット領域が含まれていました。 この場合、通信プロファイル領域へアクセスするための鍵はSIM提供者によって管理されているため、ユーザが自由にアプレット領域を活用することは不可能でした。 これに対して、NTT Comは2つの領域を分割する技術であるアプレット領域分割技術を開発。 通信プロファイル領域の安全性は守りつつ、ユーザがアプレット領域に独自のSIMアプレットを実装できるようになりました。 本記事で紹介する各種アプレットは、この技術を活用して実装されています。 SIMアプレットのもっとも基本的な実装は、EventとProactiveコマンドを活用することです。 たとえば端末が圏外になったときや通信が4Gから5Gへ変更になったとき、FETCHによって端末からSIMカードへ対応するEventが通知されます。 特定のEventを受け取った際にProactiveコマンドを送信するプログラムを実装することで、独自の機能を追加していきます。 動作例 サンプルプログラムとして、Android端末のGUI上に表示されるHelloボタンを押すことでWorldというテキストがポップアップされるSIMアプレットを作ってみました。 SIMアプレットがインストールされたSIMカードをAndroid端末に挿入すると、STK Servicesというアイコンが表示されます。 なお、以降のGUIのスクリーンショットでは、端末としてXperiaを利用しています。 このアプリケーションを開いてみると、事前にSIMアプレット内で設定してあるメニューの一覧が表示されます。 今回は、Helloと表示されているメニューのみが表示されています。 Helloメニューをタップすると、下図のようにWorldというテキストがポップアップされます。 とても単純なプログラムです。このとき、次のような通信がSIMカードとAndroid端末の間で行われています。 2行目 (No.25) では SET UP MENU というProactiveコマンドを端末へ送っています。 名前の通り、前述したSTK Services内のメニュー画面をセットアップするためのコマンドです。 8行目 (No.702) では Menu selection というEventが端末から送られています。 これはHelloメニューをタップしたときのものです。 その直後、9行目 (No.703) では DISPLAY TEXT というProactiveコマンドを端末へ送っています。 このコマンドの中にWorldという文字列が含まれており、結果としてディスプレイ上にポップアップされます。 開発環境 通常では、前述のログのようなSIMカードと端末間で行われる通信を見ることはできません。 そこでsysmocom社が販売しているSIMトレースキットを用います。SIMトレースキットを使うことでSIMカードと端末間の通信をキャッチすることが可能になります。 物理構成としては、SIMトレースキットにSIMカードを挿入し、SIMトレースキットをFCPケーブルで端末と、Mini B-AケーブルでPCと接続します。 これにより、PCにインストールしたWireshark等のアプリケーションから、SIMカードと端末間の通信を見られるようになります。 SIMアプレットの活用事例 アプレット領域分割技術の活用事例をご紹介します。 一つ目がActive Multi-access SIM 2 です。 複数の公衆網のプロファイルを所持しており、基本的にはドコモ網へ接続しつつ、 ドコモ網にトラブルが発生した場合は他キャリア網へと接続先を切り替える、という機能が実装されています。 二つ目がキャッシュレス決済端末への導入 3 です。 従来は端末側で保存・処理していた機微情報をSIMカード側で行うことで、堅牢性を保ちつつ端末の製造コスト削減を実現しています。 新たなSIMアプレットの開発 動機 近年、事業へのローカル5G網 (以下、L5G網) の導入が広がっています。 L5G網は5Gネットワークの特長をもったネットワークを専有して運用できる一方で、その利用には制度的な難しさも存在しています。 電波法 4 やローカル5G導入に関するガイドライン 5 において、 端末がL5G網へ接続していい場所は事前に免許交付を受けたエリア (以下、エリア) 内に限定されています。 そのため、エリア外に端末を持ち出す場合は電源を落とす、もしくはSIMカードを抜くといった操作が必要となります。 もしエリア外でもインターネット接続が必要となる場合は、公衆網へ接続可能な別のSIMカードを用意しておき、手動で切り替える等の対処が必要となります。 また、たとえデュアルSIMスロット対応の端末であっても、GUIからSIMスロットを切り替える操作が必要となってしまいます。 このような操作が必要となることは、人が持ち運ぶ端末であっても運用コストを増加させ、時としてインシデントの原因となることが予想されます。 当然、人が付随しないIoT端末であればその運用を困難にします。 そのような状況の一方で、バスや電車に搭載されたIoTデバイスが、移動中は公衆網へ、事業所や各駅では分散配置されたL5G網へ接続し通信するユースケースが出てきています。 そのため、前述の課題に対する解決策となるような技術の開発が必要とされています。 開発技術の構成要素: プロファイル切替機能 この課題に対して、私たちはSIMカードが自律的にL5G網への接続と公衆網への接続を切り替え可能なSIMアプレットを開発しました。 本技術では、端末がL5G網へ接続しているときにエリア外へ出た場合、接続先を公衆網へと切り替えます。 公衆網への切り替えは、エリア外を示唆する特定のEventをトリガーとして行われます。 反対に、端末が公衆網へ接続しているときにL5G網を見つけた場合、L5G網へと切り戻します。 公衆網へ接続しているときでもSIMアプレットで事前に指定したPublic Land Mobile Network (以下、PLMN) 6 のL5G網を見つけた場合は接続を切り替えるようにすることで、 公衆網からL5G網への自動的な切り戻しを実現しています。 開発技術の構成要素: エリア判定機能 プロファイル切替機能により、L5G網と公衆網を自動で切り替えられるようになりました。 しかし、この機能のみではガイドラインに対応できません。 その理由は、エリア内となるL5G網 (以下、自社L5G網) とエリア外となるL5G網 (以下、他社L5G網) が同じPLMNを持つ場合があるからです。 SIMカードは同一PLMNの自社L5G網と他社L5G網を区別できないため、公衆網接続時に他社L5G網を見つけた場合、接続を試みます。 最終的に他社L5G網の基地局により接続を拒否されますが、接続を試みる行為自体がエリア外での端末の電波発射とみなされガイドライン違反となってしまいます。 そこでプロファイル切替機能内に、自社L5G網のエリア内かどうかを判定するエリア判定機能を実装、 エリア判定機能により自社L5G網エリアであると判定されない限り公衆網からL5G網への切り戻しが行われないように改修しました。 これにより、他社L5G網エリアでは接続を試みないようになりました。 各切り替え時のシーケンス 各切り替え時のシーケンス図を見てみましょう。まずはL5G網から公衆網の場合です。 ここで、SIMカードがアプレットとUSIMの2つのレイヤで表されていることにご注意ください。 USIMは端末とSIMカードのインタフェースとなるSIMカード内のアプリケーションです。 L5G網に接続している端末がエリア外に出た場合、それと対応する特定のEventが端末からSIMカードへ送られます。 それを受け取ったアプレットはプロファイルを設定する各Elementary File (以下、EF) 7 の内容を公衆網のものへ更新し、端末へEFの再読み込みと認証を要求します。 要求を受けた端末はEFを読み込みんだのち、公衆網へ接続を要求します。公衆網基地局で認証が受け入れられれば切り替えは完了です。 つぎに、公衆網からL5G網の場合です。 公衆網に接続時は、STATUSコマンドと呼ばれるAPDUコマンドを受信するEventをトリガーとし、定期的に位置情報を要求・取得します。 取得した情報をもとにエリア判定機能を走らせ、エリア外と判定された場合はそのまま公衆網へ接続し続けます。 もしエリア内と判定された場合は、L5G網を見つけしだい接続を試みます。 ただし、この時点ではSIMカード内のプロファイルが公衆網のままなため、接続は拒否されます。 一方で、端末はL5G網への接続を試みたとき、通信規格が変化したことを伝えるEventであるAccess Technology ChangeをSIMカードへ送ります。 このEventの値が0x0A=NG-RANの場合、アプレットはプロファイルを設定する各EFの内容をL5G網のものへ更新し、端末へEFの再読み込みと認証を要求します。 要求を受けた端末はEFを読み込みんだのち、ふたたびL5G網へ接続を要求します。 L5G網基地局で認証が受け入れられれば切り替えは完了です。 開発技術の検証 検証概要 6月から7月、開発した技術に対して1day×3回の動作検証を行いました。 本検証は、NTT東日本が運用するL5Gサービスであるギガらく5Gと、その環境が構築されているNTT中央研修センタ内のローカル5Gオープンラボを用いて行いました。 上図のように、台車に検証用端末 (Xperia 1VといったスマートフォンやNOKIA社およびHytecInter社のSIMカードを挿入できるゲートウェイルータ) などを乗せて移動することで、 ユースケースの移動体を模擬 ローカル5Gオープンラボから不感地域となるヘリポートまでを往復し、L5G網->公衆網->L5G網に接続が移り変わるようにしました。 残念ながら1往復で無事完了することはなく、梅雨の雨や酷暑の日差しの中、往復10分ほどの道のりを何度も行き来しました。 その甲斐もあって、最終的にプロファイル切替機能とエリア判定機能が正しく動作することを確認できました。 また本検証はエリア内の不感地域を利用するなど、エリア外で電波を発射することがないように計画し法令遵守のもとで行いました。 検証結果 前述したように、最終的には開発技術が正常に動作することを確認できました。 一方で、検証を繰り返す中でいくつかのバグや課題を発見、その都度修正を繰り返しました。 その一部をご紹介します。 バグの修正 事象1: L5G網へ接続しているときにローミングで接続する。 事象2: 公衆網へ接続しているときにL5G網へ接続を試みたあと、トリガーとなるEventである ”Access Technology Change” が端末から送られてこない。 原因: PLMNは正しい値を書き込んでいたが、桁数の設定に誤りがあり異なるPLMNとして扱われていたため、ローミング接続となっていた。 その結果、ギガらく5Gが接続の対象とならず、接続を試みないことからトリガーとなるEventも送られてこなかった。 対処: 桁数設定を5桁->6桁とすることで解決。 PLMNは3桁のMobile Country Code (以下、MCC) と2-3桁のMobile Network Code (以下、MNC)で構成されます。 MCCは国ごとに割り振られており、日本は440と441が割り振られています。 またMNCは国から事業者へ割り振られ、その運用は国が担っています。 運用上、440に属するものはMNCが2桁、441に属するものはMNCが3桁となっています。 ギガらく5Gを運用するNTT東日本には、441210という6桁のPLMNが割り振られています。 しかし、内部的な設定でPLMNの桁数を5桁としていたため別のネットワークとみなされ、接続や切り替えがうまく動作しませんでした。 わずか1バイトの設定項目のミスですが、それが大きなバグにつながることを実感しました。 課題への対処 事象: L5G網から公衆網への切り替えトリガーとなるEventを受け取ったあと、公衆網へ接続するまでに数分を要する。 原因: Eventを受け取る地点より遠い箇所でもL5G網の微弱な電波は届いており、 それを受信した端末がL5G網への接続を試みるも電波強度が基地局側で設定されている閾値を超えていないため失敗する、という動作を繰り返していた。 対処: SIMカードに備わった接続を禁止するPLMNを設定できる機能を利用。Event受け取り時にL5G網のPLMNへの接続を禁止することで接続を試みることがないようにした。 この改修により、切り替えに必要な時間は数分から数十秒まで縮まりました。 普段検証に利用していた田町ラボ環境では、作業の効率化のためにL5G網の電源を落とすことで擬似的にエリア外を作り出していました。 そのため、この課題を発見することができませんでした。 今回、より実環境に近い条件下での検証の意義をあらためて実感できました。 発信活動 本記事では私たちが開発した技術について紹介してきました。 さいごに、本技術に関する発信活動をご紹介します。 第一に、2024年10月2日にニュースリリースを行いました。 みなさんがご覧になれるのは、Webページに掲載された文面とオンラインで行なった1時間ほどのメディア向け説明会のみです。 しかしその裏では、1ヶ月以上かけて綿密な準備をしています。 NTT Comの広報室だけでなく、NTT東日本をはじめとした関連各所と連絡を取り合い、ニュースリリースの内容について精査しました。 背景に電波法などの法令やガイドラインが関わっていることもあり、内容の検討は入念に行いました。 結果として複数のWebメディアに記事が掲載され、良い外部への発信となりました。 第二に、2024年10月9日から11日にかけて開催されたdocomo business Forum’24 では、SIMアプレットを扱ったブースにて本技術に関するパネルも展示。 9日には開発チームのメンバーもブースに立ち、来場者の方々にSIMアプレット施策について説明しました。 また、本技術はNTT東日本の「ローカル5Gオープンラボ」で今後展示する予定です。 おわりに 本記事では、私たちが新たに開発した「ローカル5G網への接続と公衆モバイル網への接続を切り替え可能なSIMアプレット」について述べました。 現在、本技術の開発は技術検証の第一段階にあります。 今後は、最終的な商用化を見据えつつ、各機能の実装のさらなる検討や、関連各所と連携し法令・ガイドラインに反していないかの検証を進めていき、 2024年度の下期にはユーザーPoCによる受容性評価検証を実施する方針です。 L5G網は多くの特長を持つ5Gネットワークを専有でき、その活用は現在実用されているサービスのコスト削減や革新的なサービスの実現につながります。 しかし、その活用には技術面、制度面、費用面のさまざまな課題が存在します。 今回開発した技術は、その中でも制度的な課題を解決できるものです。 本技術のユースケースの一例として、バスなどの移動体にIoTデバイスを搭載しL5G網とモバイル網の両方を活用するものを前述しました。 ほかにも、公衆網をL5G網のバックアップ回線として利用するユースケースも想定しています。 L5G網が皆さまにとってより身近な技術となるよう、本技術の開発・改良を進めていきます。 内部の情報への不正アクセスに対する耐性のこと。 ↩ Active Multi-access SIMの詳細についてはこちらをご覧ください。 https://www.ntt.com/business/sdpf/service/icms/multiaccess.html ↩ キャッシュレス決済端末(クラウド型決済端末)への活用の詳細についてはこちらをご覧ください。 https://www.ntt.com/about-us/press-releases/news/article/2024/0409.html ↩ 電波法の詳細についてはこちらをご覧ください https://www.tele.soumu.go.jp/horei/law_honbun/72001000.html ↩ ローカル5G導入に関するガイドラインの詳細についてはこちらをご覧ください https://www.soumu.go.jp/menu_news/s-news/01kiban14_02000616.html ↩ 通信事業者、およびその電波の識別番号。 ↩ Elementary File。SIMカードは独自のファイル構造をもち、EFは一般的なPCのファイル構造におけるファイルの概念に近しい。 ↩
アバター
はじめに こんにちは、インターンシップ生の木戸です。普段大学院では、ヒトの認知科学に関する研究をしています。 8/26-9/6までの2週間、『超低遅延ライブ配信技術を活用した、新規ライブ配信サービスを実現する技術の開発』というテーマの下、NTTコミュニケーションズの現場受け入れ型インターンシップに参加しました。 この記事では、私がお世話になったプロジェクト/インターンシップで取り組んだ業務体験内容について紹介していきます。 なお開発に利用したアバターは IZUMO.com さんのAilisを利用しております。 Fan creation of Ailis by IZUMO.com 目次 はじめに 参加のきっかけ 配属されたプロジェクトチームについて インターンシップのスケジュールについて インターンシップでの業務体験内容 1. モーションデータの取得 2. アバターの顔の動きの実装 3. 姿勢推定を用いた腕の動きの実装 おわりに 参加のきっかけ 私がインターンシップに参加した一番のきっかけとしては、自身が動画編集を日常的に行っているという背景から、 ライブ配信技術 に興味があったからです。 HPや説明会だけでは絶対に得られないような開発プロセスを体験 ライブ配信技術という技術の斬新さに触れる 新規価値を創出することの面白さを体感 したいと思い、インターンシップに応募しました。 配属されたプロジェクトチームについて 私が今回お世話になったチームでは、WebRTC技術を活用した超低遅延ライブ配信サービス( Smart vLive )を用いて、「超低遅延を活用してどのようなライブ配信が可能か?」について諸技術の開発に取り組んでいます。 Smart vLive とは、1秒未満の低遅延で映像配信が可能なライブ配信プラットフォームで、リアルタイムなコミュニケーションを含むイベント配信を可能にするサービスです。 インターンシップのスケジュールについて 日程としては、冒頭でも述べたように8/26-9/6の10日間(土日除く)でした。具体的には次の通りです。 8/26 午前:全体でのオリエンテーション、午後:業務体験内容の概要説明、夜:懇親会 8/27 午前:具体的な業務内容の説明を受ける、午後:開発を開始(9/6 午前まで継続) 9/6 午後:業務体験報告と最終オリエンテーション また、開発業務の他には配属チームが開催している以下のイベントにも参加させていただきました。 8/28 Media over QUIC(MoQ)に関する勉強会 8/29 Smart vLiveを用いたインタラクションライブ 普段の研究室での活動だけでは、絶対に出会えないような方々とコミュニケーションをとる機会を頂き、貴重な経験を得ることができました! インターンシップでの業務体験内容 インターンシップでは、VTuberライブにおいて、リアルタイムで双方向的なライブを実現するためのクライアントレンダリング型デモのフロントエンド部分を実装しました。 現在のVTuberライブ配信は、 送信側 でアバターのモーションデータを映像に変換したものを配信しています。しかしながら、この場合、受信側の環境/操作に応じてアバターのアングルを変えるなどのメタバース(XR)への適用が難しいという課題があります。以上の背景から、『MoQという新しいメディア転送プロトコルをVTuberライブに適用することで、双方向的なライブを実現しよう!』というのが大きな目的です。 デモの大まかなイメージとしては次の図になります。 送信側は以下のデータを取得し、MoQTを用いて送ります。 映像データ:PCに接続されたWebカメラから取得 音声データ:PCに接続されたマイクから取得 モーションデータ:Google社の MediaPipe を用いて顔のランドマーク、姿勢のランドマークを取得(取得イメージ:図中の①) 受信側では、送信側から受け取った上記3種類のデータを基にアバターを再構成します(再構成イメージ:図中②)。 次に実際に取り組んだ内容を大きく3つに分けて説明していきます。 1. モーションデータの取得 まず、送信側で顔のモーションデータを取得します。 インターンシップでの業務体験内容 で述べている通り、モーションデータの取得にはGoogle社の MediaPipe という機械学習ライブラリを使用しました。Webカメラのみで、フレーム内の顔と顔の特徴を検出可能で、検出した顔の特徴点はX軸、Y軸、Z軸での値を持つ3次元空間上の値として渡されます。 以上のように取得したモーションデータを用いて、アバターの顔・腕の動きを実装していきます。 2. アバターの顔の動きの実装 次に、アバターの顔の動きの実装です。 瞬きや口の動きなどの実装には、ピクシブ社が提供している @pixiv/three-vrm を使用しました。 @pixiv/three-vrm はthree.jsを使ってブラウザ上でVRM(ドワンゴ社が提供するオープンソースの人型3Dモデル用ファイルフォーマット)を読み込んで表示するためのライブラリで、ピクシブ社がオープンソースで提供しています。 アバターの顔の動きを表現するために利用するモーションデータはWebカメラから取得した顔の映像の各ランドマーク(物体を表現する為に定めた特徴点)に対して3次元空間座標の配列として提供されるので、アバターの顔のランドマークとの対応を把握することが不可欠です(例えば、瞬きの実装ならば目に関するランドマークの対応を把握するなど)。また顔のモーションデータはランドマーク毎にインデックス番号が付与されていることから、顔のモーションデータのランドマークのインデックス番号をWebカメラから取得した顔の映像に重ねて表示して、対応するアバターのランドマークを特定していきました。表示させた状態のスクリーンショットを撮影していなかったので、代わりにGoogle社から公式に公開されているインデックス番号の位置を示すガイド画像で示すと、(数字が小さすぎて見え辛いですが)以下の画像のようにインデックス番号が割り当てられていることが分かりました。 ( Google社 MediaPipeの顔のランドマーク検出ガイド より) 次にインデックス番号を用いて取得した顔の座標データをもとに以下の値を算出し、それらをアバターの動きに適用しました。 目の開き幅 口の開き幅 首の傾き(X方向、Y方向、Z方向) その結果が下の動画です。 しかしながら、見ていただけると分かるように、このままだと首だけが異様にグルグル回る不自然な動きになってしまっています。 そこで、 胴 ・ 脊髄 の動きを首の動きに対応させることで、より人間らしい動きに近づけられました。 3. 姿勢推定を用いた腕の動きの実装 最後に、アバターちゃんで手を振りたい!ということで姿勢推定を用いた腕の動きの実装に移りました。 2. アバターの顔の動きの実装 で使用した顔のランドマーク同様、まずは姿勢のランドマークのインデックス番号をWebカメラから取得した映像に重ねて表示をし特定していきました。こちらも、表示させた状態のスクリーンショットを撮影していなかったので、公式が出しているガイドを下に示します。 ( Google社の姿勢のランドマーク検出ガイド より) すると、上記のように割り当てられていることが分かったので、インデックス番号を用いて腕に関する座標データ(今回は時間の関係で左腕のみの実装なので[11][13])を取得し、腕の傾きを算出しました。 そして、その値を上腕の動きに適用し、最終的には下のアバターのような動きを実装しました。 実際に姿勢を推定したところ、値のブレが少し大きかったため、アバターの腕の動きが小刻みに震えてしまっています。そこで、何秒間かの値の平均値をとることにより、滑らかな動きを実現できるのではないかと考えています。(上長に伺ったところ、ロボット制御の要領で対応可能だというアドバイスを頂きました) おわりに 今回のインターンシップにおいて、最初はネットワークの基本的な知識しか身についておらずWebRTC/MoQに初めて触れる状態からのスタートだったのですが、上長の小松さん・トレーナーの河合さんに説明していただき、また質問があるときには丁寧に対応していただいたことでMoQや実装に関して理解を深められました。最終的にはアバターに自然な顔の動きの実装、時間の切れで途中までになってしまいましたが姿勢データを使った動きの実装ができ、満足のいくデモに仕上げられました。しかし、正直なところアバターで手を振りたかったので、その実装までいけなかったのが少し悔やまれます。私自身のプロトコルや実装に関する知識がもう少しあれば、手を振る実装まで効率的に進められたかもしれないので、今回の経験を胸に今後も精進していきたいです。 インターンシップではさまざまなイベントを通して多様なバックボーンを持つ方々にお話を伺えて、自分が実際に働いている明確なイメージを持つことができました。また、多くの出会いとその方たちとのお話を通じて、自分の長所や短所を再確認できました。この経験を今後の学校生活/研究活動/就職活動に活かしていきたいです。 今回お世話になりました、小松さん、河合さん、本当にありがとうございました!
アバター
この記事では、NeWork の開発チームがフルリモート環境でアジャイル開発するにあたり個人的に重要だと感じた部分を紹介します。 目次 目次 はじめに 背景 NeWork のチーム構成と動き方 コミュニケーションの工夫 オンラインの人を取り残さない各種ツールの利用 各種打合せ アイデア出し・情報共有・レトロスペクティブ 開発作業 話しやすいチームの文化づくり オープンな打合せ 話しかけやすく・呼び出しやすい環境 常に会話できることで雑談 データでみるコミュニケーション量 まとめ おわりに はじめに こんにちは、 NeWork 開発チームの加藤です。私は普段、オンラインワークスペースサービス NeWork の開発エンジニアをしています。 NeWork は、手軽に話しかけることを重視したオンラインワークスペースサービスです。従来の Web 会議ツールとは異なり、話したいメンバーとすぐに話すことができるなど、リアルに近いコミュニケーションが可能です。 私たち NeWork チームは、フルリモートでアジャイル開発をしています。コミュニケーションが重視されるアジャイル開発において、フルリモート環境でどのように工夫しているのか、NeWork チームの開発方法を事例の 1 つとして紹介します。 背景 NeWork のチーム構成と動き方 NeWork チームは、大きく以下の 4 つの役割に分かれています。 プロダクトオーナー (PO) 開発メンバー デザイン担当 プロモーション担当 PO と開発メンバーは、その時々の対応するテーマやデバイスに応じて 5 つのチームに分かれて開発しています。各チームは開発メンバー 2 ~ 5 名程度と PO の小規模で構成しており、チームごとに必要なコミュニケーションを取りながら開発を進めています。 開発手法はチームごとに異なりますが、基本的にはスクラムをベースとしたアジャイル開発を実施しています。チームが複数あるため、基本のスクラムイベントのほかにチームを跨いだ全体の振り返りや、チーム間の情報共有のためのミーティングなどを追加しています。 例えば特定チームの PO と開発メンバーのみでリファインメントをしたり、開発メンバーの間での情報共有のための朝会をしたりしています。 各チームメンバーは朝の 9 時から、プロダクト方針やプロモーションに関わる議題を中心としたプロダクト朝会・当日のイベントや打合せスケジュール等の事務連絡中心の全体朝会を行い、その後各チームに分かれてチームごとの朝会やリファインメント、スプリントプランニングを行います。 さらに、開発メンバーはチームごとの朝会(デイリースクラム)や開発メンバー全体の共有相談などを行ってから開発作業に入ります。 コミュニケーションの工夫 リモートワークをする上でよく聞く、雑談のきっかけが生まれない・相手のリアクションがみえないなどの理由による「コミュニケーションが取りづらい」という問題を解決するために、NeWork チームでは以下のように工夫しています。 オンラインの人を取り残さない各種ツールの利用 話しやすいチームの文化づくり オンラインの人を取り残さない各種ツールの利用 NeWork の開発・打合せは、全てオンラインで完結できるようにツールを導入しています。そのため職場やオフィスにいる人と、リモートで働いている人との間に情報量の差が生まれない構成となっています。 特にアジャイル開発をする上で重要な場面について、どのようなツールを使っているかを紹介します。 各種打合せ スプリントレビューやリファインメント、スプリントプランニングなど、各種スクラムイベントは、全て私たちの開発しているツール NeWork 上で行っています。会議 URL の共有なども不要でスムーズに打合せが可能です。 打合せの記録には Notion・Google Drive・Confluence などを、プロダクトバックログの管理には Jira を利用して、メンバーであれば誰でもアクセスできるようにしています。 一般的にオンラインの打合せでは、メンバーのリアクションが見えにくく打合せに参加しているのか分かりづらいという問題があります。NeWork チームでは以下のような工夫でこの問題を軽減しています。 例えばリファインメントでは、Product Backlog Item (PBI) のタイトルを NeWork 上のルームメッセージとして投稿し、その PBI について各メンバーが「考え中・質問あり・Ready」の ルームリアクション をつけるようにしています。これによりオンラインでもメンバーのリアルタイムな考えを把握しやすくしています。 またいくつかの打合せでは、Notion のコメント機能などを利用してリアルタイムに複数のメンバーが意見を表明しやすいようにしています。そのためよくある、「皆さんどう思いますか?」などのメンバーのリアクション確認を会議内で挟むことなく議論を進めることができます。この方法では他メンバーもコメントに返信できるため、ちょっとした質問などは相互に回答できるので打合せをスムーズに進められています。 アイデア出し・情報共有・レトロスペクティブ NeWork チームでは上記の他に、Miro も打合せのツールとして利用しています。 Miro をオンライン上のホワイトボードとして、主に付箋を使って視覚的な情報共有や、アイデア出しを行っています。レトロスペクティブなどフレームワークを利用する場合や、図形・フローの認識合わせなどにも利用しています。 Miro の各ボードは URL を持っているので、そのまま関連する PBI に張り付けたり、Slack で共有したりしています。またレトロスペクティブの際に重要な事項などは前説として毎回説明しています。これらは自分たちでテンプレートとして作成し使い回しています。 このようにオンラインホワイトボードならではの強みを活かして利用しています。 開発作業 開発については、GitHub を使ってコード管理・レビューをしています。開発中にペアプログラミングを実施する際には、Visual Studio Code の Live Share を使いリアルタイムでコードを共有しています。 開発中の動作確認時や不具合の発生時などには、NeWork 上で複数人の映像をお互いに表示しあうことで、環境ごとの違いを確認したり、よりスムーズに相手の状況を把握しながら開発しています。 話しやすいチームの文化づくり フルリモートでのアジャイル開発において円滑なコミュニケーションは重要です。NeWork チームでは、話しかけやすいチームの文化を作るために以下のような取り組みを行っています。 オープンな打合せ NeWork チーム内の打合せは、誰でも参加したり、聞いたりしてよいという文化になっています。この文化形成は NeWork の仕様によるものもありますが、リモートでのコミュニケーションを取りやすくするために重要だと考えています。 一般的な打合せツールの場合は、打合せに途中から入るにはいくつか物理的・心理的なハードルがあります。 例えば物理的には打合せ URL の共有の手間があります。また、心理的には打ち合わせ途中に参加することですでに打ち合わせをしていた人たちの話の流れを切ってしまわないかという不安などによるハードルがあります。 NeWork の場合は 聞き耳機能 があるため、自分が打合せに主体的に参加するのではなく、聞いているだけであることを表明できます。これによって、興味のある話を聞きにいくハードルを下げています。 この結果、自分の興味のある内容について情報を得たり、他のチームの活動を知ることができます。聞き耳中の打合せ内容に自身の知見があれば、 ルームメッセージ でコメントを送ることや聞き耳から打合せに参加変更することで、自分の意見を発信できます。 このように誰でも参加できるオープンな会議環境を作ることで、心理的安全性を確保できるので周囲の人を巻き込みやすい環境が作られていますと考えられます。 話しかけやすく・呼び出しやすい環境 朝一にチームメンバーが NeWork 上で打合せに参加するため、30 人以上いるチームメンバーのほぼ全員が常に NeWork を開いています。そのためオンラインツールでよくある、ツール上に人が居らず気軽に話しかけることができないという問題はほとんど発生しません。 また、通話を歓迎する「ウェルカム」や集中していることを示す「ゾーン」の 会話ステータス を設定したり、 ひとことを表示する機能 に退席中や作業中などの状況を表示したりできます。打合せやペアでの開発作業も NeWork 上で行うことが多く、その人が何をしているか分からないということもありません。NeWork 以外での打合せがあっても、 NeWork 上から Outlook の予定や Teams 会議中かどうかを確認でき、相手の状況を把握できます。 これらの文化や機能が、話しかけやすい環境を作るベースになっていると思います。 例えばとある日の NeWork オンラインメンバー数の分かる画像を以下に示します。開発メンバーが検証用に複数アカウントを使っていたりするため、実際のメンバー数よりも多く表示されていますが、大体 40 アカウントほどがオンラインであることが分かります。 開発中にデザインの疑問が生じたり、PO に確認したいことがある場合、事前にテキストコミュニケーションで今相談できるか確認することもありますが、基本的には呼び出してみることが多いです。 NeWork チーム全体として、気軽なコミュニケーションを重視していることもあり、呼び出しに対して好意的な反応を返してくれるメンバーが多いです。 またメンバーによっては常に 1 人でも NeWork のルームに参加していることで、他のメンバーが話しかけやすい環境を作っています。他メンバーはルームに参加して話すことができるため、リモートでも気軽にコミュニケーションできます。 NeWork 上では、メンバーが打合せ中なのか・他の打合せを聞いているだけなのか・忙しいのかなどの情報がリアルタイムにわかるため、相手の状況を考慮しつつ相談できます。また緊急度が高い場合は、作業中や打合せ中であっても、その打合せに参加してメッセージを送ることで、すぐに相談するようにしています。 常に会話できることで雑談 NeWork の開発チームでは、雑談によるコミュニケーションを重要視しています。 リモートワークでよく使われる Teams や Zoom などは、打合せごとに URL が変わるので、打合せ終わりや会議室までに移動時間の雑談がなくなるという話をよく聞きます。それに対して、NeWork チームでは Slack と NeWork を使って雑談を促進しています。 NeWork 上での打合せは、同じルームにて連続で予定していることがあります。例えばプロダクトの朝会とチーム全体の朝会は同じルームで続けて実施しています。このようなときは、それぞれの打合せが早く終わったタイミングや、次の打合せまでの合間の時間を使って、他のメンバーと雑談できます。 また NeWork 上には ひとことを表示する機能 があり、その日の予定・今何をしているのかなどのちょっとしたことを共有できます。打合せの隙間にこれらを話題のタネとして雑談することもあります。 それ以外にも Slack 上に雑談チャンネルを作成しています。その雑談チャンネルは業務に関係ない話題から、業務に関係ある課題など、幅広い内容が気軽に投稿されています。 Slack 上の話題にはスタンプでのリアクションやリプライも自由に行えるため、気軽にコミュニケーションを取っています。 このような雑談から、他のメンバーのことを知ることができ、前述の話しかけやすい環境を作ることにも繋がっています。また打合せをするほどではない内容なども円滑に共有できるため、幅の広いコミュニケーションを取ることができています。 データでみるコミュニケーション量 実際に NeWork チームがオンライン(NeWork 上)でどの程度コミュニケーションを取れているのかを数値化した結果を以下に示します。NeWork のワークスペースにて、2人以上が同じルームに参加している時間・回数をそれぞれコミュニケーション時間・回数として集計しています。 チーム内の 1 週間のコミュニケーション時間(ある月の平均):187.5 時間 チーム内の 1 週間のコミュニケーション回数(ある月の平均):368.7 回 チーム内の 1 回あたりのコミュニケーション時間(ある月の平均):0.5 時間 NeWork 以外のチームとの比較はできませんが、リモートでも頻度の高いコミュニケーションを取ることができていると感じています。 まとめ NeWork チームでは複数のオンラインツールを使うことや、オープンな打合せ・雑談によるコミュニケーション・呼び出しやすい環境作成などの文化によって、フルリモートでもコミュニケーションを取りやすくしています。 一方で新規メンバーが参加した際には、さまざまなツールを利用しているため事前準備やツールの使い方の説明が多く必要になることもあります。 しかし全体的にみると、リモート環境でも対面で集まるよりもコミュニケーションを取りやすく、またリモート環境ならではのメリットを活かして、職場で集まるよりもより便利な環境が作れています。 アジャイル宣言の背後にある原則 には、「情報を伝えるもっとも効率的で効果的な方法はフェイス・トゥ・フェイスで話をすることです。」という記述がありますが、NeWork チームでは、オンラインでも効率的に進める工夫によって、フェイス・トゥ・フェイスに勝るとも劣らないコミュニケーションを取ることができています。 おわりに この記事では、NeWork チームがフルリモートの環境にてどのように開発を工夫しているのかについて紹介しました。 NeWork はどなたでも無料でお試しできますので、もし興味を持っていただけたらぜひ触ってみてください。
アバター
こんにちは、NTTドコモグループの現場受け入れ型インターンシップ2024に参加させていただきました、佐藤と鈴木です。 本記事では、現場受け入れ型インターンシップ「D1.攻撃者視点に立ち攻撃技術を開発するセキュリティエンジニア」での取り組み内容について紹介します。NTTドコモグループのセキュリティ業務、とりわけRedTeam PJに興味のある方への参考になれば幸いです。 目次 目次 RedTeam PJの紹介 参加に至った経緯 インターンシップ概要 ローコード・ノーコードとは 検証業務 Power Pwnの概要 PowerDump reconコマンド dumpコマンド 条件や制約の調査 dumpコマンドの制約 検証まとめ 感想 おわりに RedTeam PJの紹介 私たちは、NTTコミュニケーションズ イノベーションセンター テクノロジー部門 RedTeam PJのポストに参加しました。RedTeam PJでは主に次の業務を行っています。 RedTeamサービスに資する攻撃技術や、先進的な攻撃技術の調査・研究・開発 攻撃自動化ツールの開発 社内グループ向けRedTeam業務・技術支援 RedTeam PJの活動や、過去のインターン生の体験記については下記資料をご覧ください。 The Dark Playground of CI/CD: Attack Delivery by GitHub Actions MITRE ATT&CK Contribution - T1562.009 Safe Mode Boot Pool Partyという攻撃手法を通じてWindowsの深淵を覗いた7日間(インターンシップ体験記) 参加に至った経緯 私たちの背景と参加経緯についてお話しします。 佐藤 RedTeam PJはどんな業務をおこなっているのか、またオフェンシブセキュリティにも興味があったので、攻撃技術の調査・検証の業務を体験できるポストに参加しました。 鈴木 普段は大学院にて、おとりシステムに関する研究を行っています。 オフェンシブセキュリティに興味を持っており、RedTeamとしての研究開発業務を体験したいと考え応募しました。 インターンシップ概要 インターンシップは8/26から9/6の10日間で行われました。オリエンテーションや成果報告会を除くと実質的な期間は8日間でした。初日と最終日に出社し、それ以外の日はリモートで実施しました。 取り組んだテーマは、「 ローコード・ノーコード (LCNC) への攻撃技術検証 」です。 私たちはどちらもLCNC自体がほぼ未知の領域であったため、LCNCの概要やそのセキュリティを知るところからスタートしました。 攻撃技術検証としては、Microsoftが提供するLCNC開発プラットフォームであるMicrosoft Power Platform 1 への攻撃ツール、 Power Pwn 2 の検証業務を行いました。 本記事では、検証業務の一部を紹介します。 ローコード・ノーコードとは ローコード・ノーコードとは、従来の人手によるコーディングに代わり、GUIを介してアプリケーションを簡単に作成できる開発手法です。LCNC開発プラットフォームとして、Microsoft Power Platformなどがあります。 LCNC開発プラットフォームは、ビジネスアプリケーションの迅速な配信を可能にします。一方、LCNC開発プラットフォームが組織で広く使われるようになると、開発されたアプリケーションには潜在的なセキュリティの脆弱性が入り込むリスクが高まります。 非営利団体OWASPがLCNCのセキュリティリスクや関連する課題、解決方法をまとめた文章として、 OWASP Low-Code/No-Code Top 10 3 があります。 検証業務 検証業務では、まずPower Pwn自体がどのようなツールであるかの調査から始まりました。 Power Pwnの概要 Power Pwnは、Microsoft Power Platformに対する攻撃的・防御的なセキュリティツールセットです。 ツールに関する講演 4 がBlackHatやDEFCONなどのカンファレンスで行われています。 このツールは複数のモジュールから構成されますが、今回のインターンシップでは PowerDump というモジュールに焦点を当て、検証しました。 PowerDump PowerDumpは、Microsoft Entra IDのユーザーアカウントを起点に、テナント内で必要以上のユーザーに対して共有(以降、過剰共有・過剰に共有と記載)されているアプリやコネクタを列挙し、データのダンプを行うツールです。ここでは、アプリと企業のビジネスデータSQLサーバが接続されている例として示します。 下記のような構成のアプリケーションを想像してください。 アプリとSQLサーバの接続を実現するコネクタというものがあり、コネクタにはSQLサーバの認証情報が含まれています。この時、コネクタが過剰に共有されていると、これを悪用してSQLサーバからのデータ取得が可能になります。 このコネクタなどの過剰共有を調査し、データのダンプまでをツール化したものがPowerDumpになります。 攻撃の流れは次の通りです。 テナントへのアクセス権があるユーザーアカウントを用意する。 AADInternals 5 を利用し、ユーザーアカウントからテナントIDを調査する。 Power Pwnを用いてreconコマンドを実行する。 PowerDumpを用いてデータを取得する。 reconコマンド reconコマンドでは、テナント内で過剰に共有されているアプリやコネクタを列挙できます。 図は実際にreconコマンドを実行し、GUIで確認した場合の例です。 過剰に共有されたコネクタに紐づく認証情報が確認できます。 dumpコマンド dumpコマンドでは、reconで取得したコネクタの認証情報を用いて、データのダンプを行うことができます。 図は実際にdumpコマンドを実行し、CLIで確認した場合の例です。 SQLサーバから、各テーブルデータをダンプできることが確認できます。 無意識にコネクタの過剰共有を行っていると、このように情報漏洩のリスクにつながります。対策のためには、適切な共有範囲の設定が重要になります。 条件や制約の調査 検証業務の後半では、PowerDump実行にあたり条件や制約はあるかについて、ソースコードや実際の動作から調査しました。今回は調査から新たに分かった知見について、一部紹介します。 dumpコマンドの制約 RedTeamがdumpコマンドを利用する場合、次のことが制約条件になります。 dump対象はコネクタが共有可能設定のものに限定されること コネクタには共有可能/不可の2種類の設定があり、コネクタの作成者が設定します。 dumpコマンドによりデータダンプ可能なコネクタは、共有可能に設定されているものに限定されます。 コネクタは現在1200種類以上ありますが、この制約条件により現在データダンプ可能なコネクタは約60種類に絞られます。 RedTeamでは通常何らかの方法で内部ユーザーアカウントを獲得し、その内部ユーザーが保有している機密情報の取得を試みます。しかしコネクタについては内部ユーザーが所有しているものであったとしても、この制約条件を満たせないためにデータダンプできない場合が考えられ、データダンプを目的とした攻撃シナリオに制限がかかります。 ちなみに現在dumpコマンドがサポートしているデータダンプ可能なコネクタは5種類に限定されています。 ここではdumpコマンドの制約について記載しました。 一方でソースコードを確認した結果、コネクタによるデータ操作は実際にはAPI操作によって実現していることがわかりました。 このことから、現在はdumpコマンドがサポートしていないコネクタであったとしても、独自にAPI操作を実装することでdumpコマンドによるデータダンプを可能にできます。 検証まとめ 今回の検証により、RedTeamとしてPowerDumpの利用をまとめると次のようになります。 LCNCのセキュリティを評価する reconコマンドで過剰に共有設定がされていないかを評価することは可能。 LCNCを利用して、情報漏洩シナリオを評価する dumpコマンドが利用できるコネクタには制限がある。独自にAPI操作を実装することで対応は可能。 感想 佐藤 LCNCとそのセキュリティやプラットフォームへの攻撃ツールの調査・検証を通してRedTeam PJの業務について知ることができ、成長することもできました。対象の知識が全くなかったので苦労もありましたが、調査・検証をおこない、無事報告することができてよかったです。 鈴木 LCNCのセキュリティに関しては全く知見がなかったため、インターンシップを通して新たな領域に触れることができ、とても楽しかったです。検証業務としては、序盤は既存のトレースも多く限られた時間で成果を出す難しさを感じていましたが、トレーナーさんのサポートもあり、なんとか形にすることができ、よかったです。 おわりに 今回は、NTTドコモの現場受け入れ型インターンシップ2024「D1.攻撃者視点に立ち攻撃技術を開発するセキュリティエンジニア」での取り組みについて紹介しました。 トレーナー、上長をはじめとするRedTeam PJの皆さまには大変お世話になりました。ありがとうございました。 AI 搭載ローコード ツール | Microsoft Power Platform ↩ GitHub GitHub - mbrg/power-pwn: An offensive security toolset for Microsoft ↩ OWASP Low-Code/No-Code Top 10 | OWASP Foundation ↩ Black Hat All You Need is Guest ↩ GitHub GitHub - Gerenios/AADInternals: AADInternals PowerShell module ↩
アバター
みなさんこんにちは、イノベーションセンターの益本 (@masaomi346) です。 Network Analytics for Security (以下、NA4Sec) プロジェクトのメンバーとして活動しています。 この記事では、2024年9月14日に開催されたセキュリティ・ミニキャンプ in 愛知 2024で、講師として参加したことについて紹介します。 ぜひ最後まで読んでみてください。 NA4Secについて セキュリティ・キャンプについて 参加者から講師に 担当した講義について 講師をしてみて 学生の方へ 出張講演承ります NA4Secについて NA4Secは、「NTTはインターネットを安心・安全にする社会的責務がある」を理念として、インターネットにおける攻撃インフラの解明・撲滅を目指した活動をしているプロジェクトです。 NTT Comグループにおける脅威インテリジェンスチームとしての側面も持ち合わせており、有事において脅威インテリジェンスを提供し、意思決定を支援することもあります。 イノベーションセンターを中心として、NTTセキュリティ・ジャパンやエヌ・エフ・ラボラトリーズ(以下、NFLabs.)からもメンバーが参画し、日夜攻撃インフラを追跡しています。 セキュリティ・キャンプについて 一般社団法人セキュリティ・キャンプ協議会とIPA(独立行政法人情報処理推進機構)が主催しており、学生に対して情報セキュリティに関する高度な技術教育を実施し、次代を担う情報セキュリティ人材を発掘・育成することを目的にしたイベントです。 セキュリティ・キャンプ 毎年8月に開催されているセキュリティ・キャンプ全国大会は、選考課題を突破した学生にハイレベルな専門講義が実施されています。 また、選考倍率も5倍以上となっており、学生から非常に人気のあるイベントとなっています。 全国大会以外に、セキュリティ・ミニキャンプ(地方大会)と呼ばれるものがあります。 セキュリティ・ミニキャンプは、全国各地で開催されており、1~3日間の短期間で専門講義を実施しています。 セキュリティ・キャンプ全国大会と同様、参加するには選考課題を突破する必要があります。 ただし、全国大会と比べると難易度は易しめとなっているため、セキュリティ初学者も参加しやすくなっています。 今回私は、名古屋大学で開催されたセキュリティ・ミニキャンプ in 愛知 2024に講師として参加してきました。 セキュリティ・ミニキャンプ in 愛知 2024が始まりました!オープニングでは、一般社団法人セキュリティ・キャンプ協議会ステアリングコミッティによるセキュリティ・キャンプの紹介が行われ、続いて講師と受講生がアイスブレイクを楽しみました。 #seccamp pic.twitter.com/lamcTHlrUn — セキュリティ・キャンプ (@security_camp) 2024年9月14日 参加者から講師に 私は、学生の時にセキュリティ・キャンプ全国大会へ参加していました。 参加してからも、さまざまなことを経験し、セキュリティ業界に飛び込みました。 そうしているうちに、いつかセキュリティ・キャンプのコミュニティに貢献したいと思うようになりました。 そんな時に、修了生コミュニティ経由でセキュリティ・ミニキャンプの講師をする機会をいただくことができました。 少しでもコミュニティに貢献できて良かったです。 担当した講義について 「Phishing Analysis ~ フィッシングサイトの仕組みを学ぶ」という講義を担当しました。 次は、NTTコミュニケーションズ株式会社 益本 将臣氏による『Phishing Analysis ~ フィッシングサイトの仕組みを学ぶ』です。フィッシングサイトの仕組みやエコシステムを学んだ後、チームでフィッシングサイト構築ツールのフィッシングキットの脅威解析を実施し、解析結果を発表しました。 #seccamp pic.twitter.com/Ntiim8aNAR — セキュリティ・キャンプ (@security_camp) 2024年9月14日 名前の通り、フィッシング詐欺を題材に扱った珍しい講義となっています。 具体的な分析手法とかではなく、フィッシング詐欺の仕組みの紹介に重点を置いています。 フィッシング詐欺がどのように行われているのか スミッシングの仕組み フィッシングサイトを分析すると何がわかるのか フィッシングサイトがどのように構築されているのか etc. なぜこの講義にしたのかというと、(私が調べた限り)今までのセキュリティ・キャンプでフィッシング詐欺を取り扱っている講義がなかったので、こういうのもあると面白そうだと思ったからです。 また、講義だけでなく、ハンズオンも実施しました。 フィッシングサイトの構築に使われているツールであるフィッシングキットを分析し、分析レポートを作成して発表するといったものです。 フィッシングキットを分析することで、どのようにクローキングをしているのか、どのように窃取した情報を攻撃者に送信しているのか理解できたかと思います。 フィッシングキットを分析する機会はなかなかないと思うので、いい経験をしてもらえたのではないかと思います。 また、グループで分析を進めることで、受講者同士でいい刺激になったかと思います。 特に大きなトラブルもなく終わることができて良かったです。 講師をしてみて ハンズオン形式の登壇は初めてでしたので、時間配分だったり、ハンズオンの難易度が適切なものか少し不安でした。 そのため、頭の中でのシミュレーションを何度も繰り返すなどして、入念に準備しました。 いざやってみると、特に問題なく進めることができ、ハンズオンもみんなついていけてそうだったので良かったです。 受講者アンケートの結果を見ても、「大変満足した」と「満足した」の回答ばかりで、この内容でやって良かったと思えました。 また講師をする機会があった場合は、内容を改良しつつ、より満足していただける講義にしていきたいと思います。 チューター・運営スタッフの方々にも、さまざまなことをサポートしていただきました。 本当に感謝しています。 学生の方へ セキュリティ・キャンプ関連のイベントはこれからも開催されます。 興味を持った方は、ぜひ挑戦してみてください。 ご応募お待ちしております。 セキュリティ・キャンプ全国大会 セキュリティ・ミニキャンプ(地方大会) 出張講演承ります セキュリティ・キャンプは定員があるので、受講できる人に限りがあります。 また、せっかく作成したコンテンツを1回で終わりにするのはもったいないので、出張講演なども前向きに検討します。 興味のある方はNA4Secプロジェクトまでお気軽にご相談ください。
アバター
この記事では、できるだけアクセスを絞るべき本番環境に対して、かのシンデレラのように時間制限つきの承認性アクセスができるようにした事例を紹介します。 目次 目次 はじめに 背景 複数の環境 これまでの運用 課題 実現方法 実装 - Google Cloud IAM 設定スクリプト 設定 - GitHub Environments 実装 - GitHub Actions その他細かな工夫点 ゴミ掃除 Slack 連携 サービスアカウントキーの発行 運用を変えてみて おわりに はじめに こんにちは、 NeWork 開発チームの藤野です。普段はオンラインワークスペースサービス NeWork のエンジニアリングマネジメントをしており、最近では実際にコードを書く機会も増えてきています。 この記事では、これまで手動 + ガッツで運用していた本番環境へのアクセス管理の工程のほとんどを自動化した内容をご紹介します。 背景 複数の環境 他のよくあるWebサービスと同様に、NeWork においてもサービスの運用・開発をするにあたって以下の3種類の環境を用意しています。 開発環境 ステージング環境 本番環境 開発環境・ステージング環境については開発メンバーは普段からフルアクセスできるようにしていますが、本番環境についてはセキュリティ等の観点からアクセスできるメンバーやそれぞれの権限は最小限にしています。 単純なセキュリティ以外の観点でも、開発者が本番環境までアクセスできてしまうと環境取り違えによる誤操作でトラブル発生につながる可能性があります。 開発作業を余計な心配なく集中してできるようにする観点でも、環境の分離と余計なアクセス権限をなくすことが重要です。 一方で普段は本番環境に一切アクセスできない開発メンバーであっても、以下のようないくつかのシーンでアクセスが必要になることがあります。 申告やアラートにもとづくトラブルシューティング 新機能のリリースに伴う設定の更新や DB のマイグレーション作業 これまでの運用 NeWork では Google Cloud を主に利用してサービス提供しています。 上記のような本番環境へのアクセスが必要になったシーンでは、以下の運用で対応していました。 開発メンバー. : 特権アカウント所持者 (≒ 藤野) に理由を添えてアクセス権限付与依頼 特権アカウント所持者 : (依頼に気づいたタイミング以降で) アクセス権限を付与 開発メンバー : 作業完了後に権限の削除依頼 特権アカウント所持者 : アクセス権限を削除 課題 しかし 2 年以上この運用を続けてきていて以下の課題があると感じていました。 特権アカウント所持者の状況によっては、対応が遅れタイムリーに処理できなくなりうること 特権アカウント所持者 (≒ 藤野) の業務上の役割が変化し、実際に開発をする頻度が向上することによって、開発環境と取り違えて誤操作をする可能性が浮上 緊急のトラブルシューティング等の場合、対応完了と素直に判断できなくなるシーンがあり、権限削除依頼を失念し、不必要に長期間権限が付与された状態になることがある 上記の問題に対処するため、普段は一切開発等をしない複数のメンバーにも特権を付与し、代わりにアクセス権限付与作業をしてもらうことも考えられましたが、以下の懸念がありました。 そもそもの操作に慣れていない状況でセンシティブな作業をお願いしづらい 複数依頼対象がいると、誰にお願いすればいいかわからず手間が増える 実現方法 上述の背景を考慮して、2024 年 5 月から以下のワークフローを用意し、運用を開始しました。 開発メンバー : GitHub Actions を以下の情報を入力して起動して権限昇格申請 環境 (基本的には本番環境) 権限付与対象アカウントのメールアドレス 権限 権限付与期間 : 1h/2h/5h/1d から選択 目的 承認可能メンバー : 申請内容に問題なければ承認 自動 : GitHub Actions が期間限定の権限を対象かアカウントに付与 自動 : 設定した期間を過ぎると自動で権限が無効になる これにより、開発メンバーであっても本番環境の特権アカウントを持つことなく承認可能メンバーになることもできるようになりました。 実装 - Google Cloud IAM 設定スクリプト 期限付きの権限付与は Google Cloud の 一時的なアクセス権付与 の仕組みを利用します。 これを GitHub Actions から実行できるように bibbidi-bobbidi-boo.sh という名前の以下のシェルスクリプトを用意しました。 #!/bin/bash # # bibbidi-bobbidi-boo.sh # ============ # # Script to configure temporary access to an account # # Usage: # # bibbidi-bobbidi-boo.sh $PROJECT_ID $USER_EMAIL $ROLE $DURATION # パラメータ PROJECT_ID=$1 # GCPプロジェクトID USER_EMAIL=$2 # ユーザーのメールアドレス ROLE=$3 # 付与するロール DURATION=$4 # ロールを付与する期間(日または時間) # DURATIONを秒に変換 if [[ $DURATION == *d ]]; then DURATION_SECONDS=$(( ${DURATION%d} * 24 * 60 * 60 )) elif [[ $DURATION == *h ]]; then DURATION_SECONDS=$(( ${DURATION%h} * 60 * 60 )) else echo "Invalid duration format. Use <number>d for days or <number>h for hours." exit 1 fi # 現在の日時を取得 CURRENT_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) # 期限日時を計算 EXPIRATION_DATE=$(date -u -d "$DURATION_SECONDS seconds" +%Y-%m-%dT%H:%M:%SZ) # ロールを付与 gcloud projects add-iam-policy-binding $PROJECT_ID \ --member="user:$USER_EMAIL" \ --role=$ROLE \ --condition="expression=request.time < timestamp('${EXPIRATION_DATE}'),title=Cinderella_${ROLE}_access,description=Temporary ${ROLE} access until ${EXPIRATION_DATE}" これにより、指定したプロジェクト・アカウント・ロールを期間限定で付与できるようになります。 実行すると以下のように期間限定で権限付与ができます。 なお、仕様により オーナー / 編集者 / 閲覧者 等の基本ロールには条件設定ができない点は注意が必要です。 設定 - GitHub Environments GitHub Environments は以下の目的で利用できます。 変数を variables / secrets に設定 アクション実行ブランチの制限 それ以外にも特定のメンバーによるアクション実行を承認性にすることもできます。 今回のアクションはリポジトリにアクセスできる人が誰でも実行できてしまうと困るので、この用途でも活用するようにしました。 これにより、ワークフローを実行すると指定されたメンバーの承認がないと実行できなくなります。 また、 Prevent self-review にチェックをいれることで自作自演による環境アクセス承認もできなくなります。 実装 - GitHub Actions 上記のスクリプトを実行できる GitHub Actions を用意します。 name : Cinderella Request on : workflow_dispatch : inputs : environment : type : environment required : true email : description : "User Email" required : true role : description : "Role" type : choice # 付与するロールはプロジェクトにあわせて調整必要 default : "roles/firebase.admin" options : - roles/firebase.admin - roles/run.admin - roles/cloudfunctions.admin duration : description : "Duration" required : true type : choice default : "1h" options : - 1h - 2h - 5h - 1d purpose : description : "Purpose" required : true run-name : Request ${{ inputs.role }} during ${{ inputs.duration }} to ${{ inputs.email }} for ${{ inputs.purpose }} jobs : cinderella : runs-on : ubuntu-latest environment : name : ${{ inputs.environment }} steps : - name : Checkout uses : actions/checkout@v4 - name : Authenticate Google Cloud uses : "google-GitHub-actions/auth@v2" with : workload_identity_provider : ${{ vars.IDENTITY_PROVIDER }} - name : Run a magic run : ./scripts/bibbidi-bobbidi-boo.sh ${{ vars.GC_PROJECT_ID }} ${{ inputs.email }} ${{ inputs.role }} ${{ inputs.duration }} 基本的にはスクリプト実行に必要なパラメータに関する値を指定して実行できるようにしています。 スクリプトとは関係なく purpose も取得するようにしており、これは実行時のタイトルに設定しています。 これにより、前述の承認判断をより簡単にできるようにしています。 なお、スクリプトの実行には resourcemanager.projects.setIamPolicy の権限が必要なので、 Project IAM 管理者 (resourcemanager.projectIamAdmin) あたりを認証に利用します。 その他細かな工夫点 ゴミ掃除 権限の観点では、指定した時間を過ぎると権限は無効になります。ただ、Google Cloud の設定上は不要な権限が残ってしまいます。 このゴミが溜まってしまわないように、以下のスクリプトを GitHub Actions で定期実行することでゴミが削除されるようにします。 #!/bin/bash -e # # tremaine-family.sh # ============ # # Remove temporary access right named with Cinderella # # Usage: # # tremaine-family.sh $PROJECT_ID # パラメータ PROJECT_ID=$1 # GCPプロジェクトID # プロジェクトのIAMポリシーを取得 CURRENT_POLICY=$(gcloud projects get-iam-policy $PROJECT_ID --format=json) # `Cinderella` がつく condition を削除 NEW_POLICY=$(echo $CURRENT_POLICY | jq 'del(.bindings[] | select(.condition) | select(.condition.title | startswith("Cinderella_")))') # 更新後のIAMポリシーを整形してJSONファイルに出力 echo $NEW_POLICY | jq . -M > updated_iam_policy.json # 差分があった場合に更新 if [ "$CURRENT_POLICY" != "$NEW_POLICY" ]; then gcloud projects set-iam-policy $PROJECT_ID updated_iam_policy.json fi name : Cleaner on : workflow_dispatch : inputs : environment : required : true type : environment # 平日 24:00 JST schedule : - cron : "0 15 * * 1-5" run-name : IAM Cleaning @ ${{ inputs.environment || 'prod' }} jobs : clean : runs-on : ubuntu-latest environment : name : ${{ inputs.environment || 'prod' }} steps : - name : Checkout uses : actions/checkout@v4 - name : Authenticate Google Cloud uses : "google-GitHub-actions/auth@v2" with : workload_identity_provider : ${{ vars.IDENTITY_PROVIDER }} - name : Run cleaner run : | ./scripts/tremaine-family.sh ${{ steps.vars.outputs.GC_PROJECT_ID }} これにより、24時の鐘が鳴ると Cinderella_ がつく権限を削除します。 日を跨ぐ作業がこれまでもなかったので、権限が賞味期限切れになっているかに関わらず削除してしまっていますが、 jq 構文を頑張ることで期限切れの権限のみの削除もできるはずです。 Slack 連携 承認依頼は GitHub の設定に応じてメール等でも承認者に飛びますが、利便性を考慮して Slack と連携しました。 これにより、以下の全てが Slack へ通知等できるようにしています。 ワークフローの起動時の通知 ワークフローに対する承認処理 ゴミ掃除結果 (上述のスクリプトに +α の追加をして実現しています) Slack の連携時に以下の方法でスレッドに連携しないとワークフローの承認ができないのでご注意ください。 /github subscribe ${REPO_NAME} workflows:{name: "${WORKFLOW_NAME}"} 単純な通知としても便利ですが、ログの意味でも役立っています。必要あればさらにワークフローを改良して、監査用のスプレッドシートなどに実行履歴を保存することなども考えられます。 サービスアカウントキーの発行 Firebase Admin SDK を利用するスクリプトをローカルで実行する場合は、権限を持つサービスアカウントキーを読み込んだ上でスクリプトを実行する必要があります。 1 これについても一時的な権限をふったサービスアカウントキーを発行できるようにしています。 #!/bin/bash # # pumpkin-carriage.sh # ============ # # Script to generate service account key with temporary access # # Usage: # # pumpkin-carriage.sh $PROJECT_ID $DURATION # パラメータ PROJECT_ID=$1 # GCPプロジェクトID DURATION=$2 # ロールを付与する期間(日または時間) # DURATIONを秒に変換 if [[ $DURATION == *d ]]; then DURATION_SECONDS=$(( ${DURATION%d} * 24 * 60 * 60 )) elif [[ $DURATION == *h ]]; then DURATION_SECONDS=$(( ${DURATION%h} * 60 * 60 )) else echo "Invalid duration format. Use <number>d for days or <number>h for hours." exit 1 fi # サービスアカウントを作成 SERVICE_ACCOUNT_NAME="firebase-admin-cinderella" SERVICE_ACCOUNT_EMAIL="${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" # サービスアカウントが存在するか確認 if ! gcloud iam service-accounts describe $SERVICE_ACCOUNT_EMAIL > /dev/null 2>&1; then gcloud iam service-accounts create $SERVICE_ACCOUNT_NAME \ --description="Temporary Firebase Admin" \ --display-name="Temporary Firebase Admin" fi # 現在の日時を取得 CURRENT_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) # 期限日時を計算 EXPIRATION_DATE=$(date -u -d "$DURATION_SECONDS seconds" +%Y-%m-%dT%H:%M:%SZ) # ロールを付与 gcloud projects add-iam-policy-binding $PROJECT_ID \ --member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \ --role="roles/firebase.admin" \ --condition="expression=request.time < timestamp('${EXPIRATION_DATE}'),title=Cinderella_roles/firebase.admin_access,description=Temporary roles/firebase.admin access until ${EXPIRATION_DATE}" # サービスアカウントのキーを作成 gcloud iam service-accounts keys create ./${PROJECT_ID}-firebase-admin-key.json \ --iam-account $SERVICE_ACCOUNT_EMAIL name : Firebase Key Request on : workflow_dispatch : inputs : environment : type : environment required : true duration : description : "Duration" required : true type : choice default : "1h" options : - 1h - 2h - 5h - 1d purpose : description : "Purpose" required : true run-name : Key Request during ${{ inputs.duration }} for ${{ inputs.purpose }} jobs : cinderella : runs-on : ubuntu-latest environment : name : ${{ inputs.environment }} steps : - name : Checkout uses : actions/checkout@v4 - name : Authenticate Google Cloud uses : "google-GitHub-actions/auth@v2" with : workload_identity_provider : ${{ vars.IDENTITY_PROVIDER }} - name : Run a magic run : ./scripts/pumpkin-carriage.sh ${{ vars.GC_PROJECT_ID }} ${{ inputs.duration }} - uses : actions/upload-artifact@v4 with : name : firebase-admin-key path : ./${{ vars.GC_PROJECT_ID }}-firebase-admin-key.json if-no-files-found : error retention-days : 1 overwrite : true このワークフローも実行には承認が必要です。 承認され実行されると、サービスアカウントキーのファイルが生成され、GitHub Actions のページからキーファイルをダウンロードできます。 運用を変えてみて 2024年5月からこの運用を導入してみました。 変更以来、月平均で10回ほど実行されており、運用に欠かせないツールとなりました。 運用していくなかで、以下のような場面に遭遇し、改善も日々しています。 設定に複数のロールが必要であったり、そもそもの必要なロールを割り出したりするのが手間 => 全般的な操作をできるカスタムロールを追加 新機能の追加時に伴い、ワークフローのロールリストも追随するのが手間 => ロールを選択肢から選ぶのではなく、自由記述式に変更 スクリプトの想定実行時間の見積もりが甘く、期限間際にハラハラしながら完了を待つ => ちょっと長めに期間設定をする運用対処に現状は逃げているが、延長機能の追加も検討中 メンバーもこの運用になって以下のようなポジティブな反応をしてくれており、今後も継続予定です。 一部の開発メンバーは承認者も兼任できるようになり、各チームに一人は承認できるメンバーを含むようになった。そのため、承認者への相談を忖度したり、不用意に長い待ち時間の発生がなくなって楽になった 本番環境へのリリース前に考慮すべきことや実施すべきことが減り、楽になった おわりに この記事では、本番環境へのアクセス管理の運用を改善した事例について紹介しました。 今回の内容とは関係が薄いですが、 NeWork はどなたでも無料でお試しできますので、もしプロダクトや使われている技術に興味を持っていただけたらぜひ触ってみてください。 https://github.com/firebase/firebase-admin-node/pull/2466 がマージされればこの状況も改善が見込まれています。 ↩
アバター
こんにちは。イノベーションセンターの鮫嶋です。本記事では、今年で入社2年目の新人が、2024年8月に開催されたセキュリティカンファレンス BSides Las Vegas 2024 で登壇するまでの道のりについてご紹介します。 Team NA4Secとは BSides Las Vegasに登壇してきました BSides Las Vegasまでの道のり リサーチにかかわり始めたきっかけ どんな活動をしてきたか JSAC2024に参加して BSides Las Vegas 2024で登壇! 振り返って おわりに Team NA4Secとは 私は、Network Analytics for Security(以下、NA4Sec)プロジェクトに所属しています。 NA4Secプロジェクトは「NTTはインターネットを安心、安全にする社会的責務がある」を理念として、攻撃インフラの解明、撲滅を目指すプロジェクトです。 NTT Comイノベーションセンターを中心としてNTTセキュリティ・ジャパンやエヌ・エフ・ラボラトリーズからもメンバーが参画し、日夜攻撃インフラ *1 を追跡しています。 今回は、Team NA4Secから皆川、神田、鮫嶋の3名がラスベガスで講演してきました。 BSides Las Vegasに登壇してきました BSides Las Vegasとは情報セキュリティコミュニティ主導で毎年開催されているセキュリティカンファレンスです。Black Hat USA, DEF CONと共に、ラスベガスで開催される主要なセキュリティイベントの1つとして知られています。今年は8月6日・7日に Tuscany Suites & Casino で開催されました。 講演では、Team NA4Secのメンバーと共に、親ロシア派ハクティビストを長期にわたって追跡した内容について話をしています。 / イベント登壇情報✨ \ ​ ラスベガスで開催されるセキュリティカンファレンス @BsidesLV にて、NTT ComとNFLabs.の社員が登壇🙋 ​ 詳細はこちら⬇ https://t.co/CotWhfcpy1 ​ #ドコモビジネス — ドコモビジネス|NTTコミュニケーションズ (@NTTCom_online) 2024年7月16日 当日の講演の様子はYouTubeにて録画が公開されていますので、ご興味のある方はぜひご覧ください。 www.youtube.com BSides Las Vegasまでの道のり リサーチにかかわり始めたきっかけ 実は、私の入社前から本リサーチは開始されており、アイデアはあるものの人手不足で作業が進んでいない状況が続いていました。そこで配属後、進んでいない作業を補う形で本リサーチに途中からかかわることになりました。 どんな活動をしてきたか 私は本リサーチにおいてはSNS調査やデータ分析を担当していました。 主にTelegram *2 での投稿収集・分析をしましたが、OPSEC *3 に配慮し調査をするための回線や端末の調達から始め、プロジェクト内の環境を用いて自動で投稿を収集する機能の実装と収集したデータの分析をしました。 初めはBSides Las Vegasに投稿する予定はなく、日本で開催されるセキュリティカンファレンスである CODE BLUE に投稿することを目標に進めていました。 リサーチにかかわり始めたのが昨年6月で、CODE BLUEのCFP締切が昨年8月だったため、わずか2ヶ月足らずで、必要物を調達するための社内プロセス理解、プロジェクト内環境の把握、使用したことのないツール理解、データの収集・分析までしなければなりませんでした。 私は学生時代にセキュリティ分野には触れていたものの、ツール類に疎く、慣れない環境で四苦八苦していました。自分の作業が遅れると周りにも迷惑をかけてしまうという思いもあり、当時は日々焦燥感を抱きながら作業を進めていたように思います。気軽に質問をできる風土であり、分からないことを分からないと遠慮なく声を上げることのできる環境があったのは恵まれていたのだなと感じています。サポートしてくれたチームメンバーには感謝の気持ちしかありません。 JSAC2024に参加して CODE BLUEには残念ながら不採択となりましたが、代わりに JSAC という日本で開催されるセキュリティカンファレンスへ投稿し、無事に採択。登壇することが決まりました。 登壇の様子や発表内容については 過去のブログ にありますので、興味のある方はご覧ください。 途中からかかわったリサーチであり、作業期間も短かったため、自分以外のメンバーが講演するんだろうなと思っていたらいつの間にか(?)登壇することになっていました。1年目から登壇する機会があるとは思っていなかった上、私は対面での登壇経験が少なく、スピーチも苦手だったため、当日は緊張で顔が青白くなりながら聴衆の面前で話していたのを覚えています。 BSides Las Vegas 2024で登壇! このリサーチの内容を海外にも発表しよう!という話になり、BSides Las Vegasへ投稿し、今年6月に採択される運びとなりました。詳細な経緯を知りたい方は、登壇者の1人である皆川さんのブログをご一読ください。 8月のラスベガスでの発表に向けて、JSACでの発表から半年分アップデートする形でデータを再度分析し、発表資料を作る日々が始まりました。しかし、無事採択されたものの、私は英語が大の苦手であり、長時間のフライトも苦手なため、期待というより不安を大きく抱えながら準備を進めることになりました。 フライトも無事に乗り越え、ラスベガスへ到着。そして迎えた発表当日。カンファレンス1日目の午後ということもあり、他の発表を聞いている余裕もなく、直前まで発表練習を行っていました。私が話すパートはわずか10分程度でしたが、貴重な経験をさせていただけたと感じています。ただ、準備不足と緊張で原稿を見ながら喋ることしか出来なかったのは心残りです。 登壇者の皆川(左: @strinsert1Na )、 神田(中央: @ashy0x41 )、 筆者(右: @islairand ) 振り返って まさか、配属直後にかかわったリサーチで海外登壇まで経験できるとは夢にも思っていませんでした。正直、私は今回のリサーチにタイミング良くかかわることができ、偶然ラスベガスへ連れて行ってもらったのだと感じています。しかし、偶然であったとしてもその機会をモノにするための力を蓄えていくことが大事なんだなとも同時に実感しました。 また、英語の勉強不足は大きな反省点でした。登壇が決定してからできる限り英語の勉強もしましたが、やはり付け焼刃ではどうにもなりませんでした。今回の出張では周りの助けのおかげで何とかなったものの、1人では受け答えも満足にできず、不甲斐なさを感じる結果となりました。これを機に英語は勉強し続けようと思います。 おわりに 2年目社員がラスベガスで登壇してきた体験談でした。若いうちから海外登壇する機会をいただき、日本では得られない、何ものにも代えがたいさまざまな経験を積むことができました。この経験を糧に、今後もリサーチャーとして自信をもって海外登壇できるように、日々精進したいと思います。 ラスベガス出張に聴講者として参加したNTT Comメンバーの体験談も後日公開予定ですので、そちらもお楽しみに。 また、BSides Las Vegasには他にも日本から登壇された方々がおり、体験記が公開されています。気になる方はぜひこちらもご覧ください。 *1 : 攻撃者の管理するマルウェアや C&Cサーバ など。 *2 : スピードとセキュリティに重点を置いたメッセージアプリ。 *3 : 本記事では、ターゲットを偵察する際に個人情報を秘匿する概念として用いている。
アバター
本記事では、今年8月にパブリックベータ版として GitHub に搭載された新機能 GitHub Models について、概要や利用法を簡単にご説明します。さらに、実際に GitHub Models を活用して、多数の LLM の日本語性能を横断的に測定していく例を紹介していきます。 目次 目次 はじめに 三行で GitHub Models を説明すると... GitHub Models の使い方 Waitlist への登録 モデル一覧 ブラウザ上で試す API経由で試す GitHub Models を利用する上での注意点 API レート制限の制約が強い Azure AI Content Safety が全ての LLM に適用されている GitHub Models を使って LLM の日本語性能を横断的に測定する 実験 1. GPT-4o による自動評価 2. 出力が日本語になっているかどうかの評価 結果と考察 まとめ はじめに イノベーションセンターの杉本(GitHub: kaisugi )です。普段はノーコードAI開発ツール Node-AI の機能開発や、Node-AI を用いたデータ分析人材育成、LLM に関する技術調査などに携わっています。 今回は、 GitHub Models という GitHub の新機能についてご紹介します。 github.blog 三行で GitHub Models を説明すると... GitHub Models は GitHub 上で LLM を 無料 で試せるプラットフォーム。ブラウザ上、API経由の双方で、さまざまな LLM を動かすことができる API レート制限の制約が強くプロダクションには向かないが、無料で試せるので プロトタイピング や 研究 の用途に最適 GitHub Models 上の全てのモデルが Azure AI Studio にも搭載されているため、Azure AI Studio の無料版として使うこともできる GitHub Models の使い方 Waitlist への登録 本記事を執筆している現在、GitHub Models はまだパブリックベータ版の機能であるため、利用するには Waitlist に登録する必要があります。以下のリンクから登録できます。 https://github.com/marketplace/models/waitlist モデル一覧 GitHub Models のトープページは https://github.com/marketplace/models から開くことができます。ここでは、GitHub Models 上で試せるモデルの一覧が表示されています。 私が調査した8月末時点で、GitHub Models で使える LLM は以下の 21 種類がありました。GitHub を運営している Microsoft 関連のモデルだけでなく、Llama や Mistral などの外部のモデルも搭載されています。 なお、GitHub Models に搭載されているモデルは随時アップデートが行われており、この記事をご覧になっている際には、すでに内容が古くなってしまっているかもしれない点にご注意ください。 モデル名 開発元 備考 AI21-Jamba-Instruct AI21 Labs パラメータ数52B(アクティブパラメータ数 1 12B)。世界初の商用規模の Mamba 2 ベースのLLM Cohere Command R Cohere パラメータ数35B。日本語を含む多言語対応 Cohere Command R+ Cohere パラメータ数104B。日本語を含む多言語対応。RAG 3 への最適化 Meta-Llama-3-70B-Instruct Meta Meta-Llama-3-8B-Instruct Meta Meta-Llama-3.1-405B-Instruct Meta Llama 3 よりも新しいモデル。多言語対応 Meta-Llama-3.1-70B-Instruct Meta Llama 3 よりも新しいモデル。多言語対応 Meta-Llama-3.1-8B-Instruct Meta Llama 3 よりも新しいモデル。多言語対応 Mistral Large Mistral AI 多言語対応 Mistral Large (2407) Mistral AI Mistral Large よりも新しいモデル。パラメータ数123B。日本語を含む多言語対応 Mistral Nemo Mistral AI, NVIDIA パラメータ数12B。日本語を含む多言語対応 Mistral Small Mistral AI 多言語対応 OpenAI GPT-4o OpenAI 日本語を含む多言語対応 OpenAI GPT-4o mini OpenAI 日本語を含む多言語対応 Phi-3-medium instruct (128k) Microsoft パラメータ数14B。4kモデルよりもコンテクスト長 4 が大きい Phi-3-medium instruct (4k) Microsoft パラメータ数14B Phi-3-small instruct (128k) Microsoft パラメータ数7B。8kモデルよりもコンテクスト長が大きい Phi-3-small instruct (8k) Microsoft パラメータ数7B Phi-3-mini instruct (128k) Microsoft パラメータ数3.8B。4kモデルよりもコンテキスト長が大きい Phi-3-mini instruct (4k) Microsoft パラメータ数3.8B。 Phi-3.5-mini instruct (128k) Microsoft Phi-3 よりも新しいモデル。パラメータ数3.8B ブラウザ上で試す 例として、Meta-Llama-3.1-405B-Instruct という LLM を動かしてみます。 ブラウザ上で LLM が試せるプレイグラウンドは、 https://github.com/marketplace/models/azureml-meta/Meta-Llama-3-1-405B-Instruct/playground というリンクからすぐに使うことができます。普通の ChatGPT などの画面と同様に、質問文を入力して送信するだけで、LLM からの回答を得ることができます。 API経由で試す API 経由で LLM を触ることもできます。 まず初めに、 https://github.com/settings/tokens から GitHub の Personal access token (PAT) を発行してください。なお、この PAT には、GitHub 関連の権限を付与する必要はありません。 そして、PAT を GITHUB_TOKEN という環境変数に格納した上で、以下のようなコードを実行すると、出力を得ることができます。 import os from azure.ai.inference import ChatCompletionsClient from azure.ai.inference.models import SystemMessage, UserMessage from azure.core.credentials import AzureKeyCredential endpoint = "https://models.inference.ai.azure.com" model_name = "meta-llama-3.1-405b-instruct" token = os.environ[ "GITHUB_TOKEN" ] client = ChatCompletionsClient( endpoint=endpoint, credential=AzureKeyCredential(token), ) response = client.complete( messages=[ UserMessage(content= "NTTコミュニケーションズについて教えてください。" ), ], model=model_name, temperature= 0.8 , max_tokens= 4096 , top_p= 0.1 ) print (response.choices[ 0 ].message.content) ここでは Python のコード例を示しましたが、Azure AI Inference SDK が実装されている C#, JavaScript や、通常の REST API でも同様の処理が可能です。 GitHub Models を利用する上での注意点 API レート制限の制約が強い GitHub Models では全ての LLM を無料で利用できますが、その代わりに、強い API レート制限が設定されています。 レート制限の詳細は、以下のドキュメントにまとめられています。 https://docs.github.com/en/github-models/prototyping-with-ai-models#rate-limits 本記事を執筆している現在、それぞれの LLM には "High" または "Low" のいずれかの Rate limit tier が割り当てられています。"Low" では、 1分間に15リクエスト、1日に150リクエスト しか受け付けません。"High" はもっと制約がきつく、 1分間に10リクエスト、1日に50リクエスト しか受け付けません。そのため、GitHub Models は、あくまでも LLM の試し場であることを心得ておくと良いでしょう。 なお、GitHub Models は Azure AI Studio をベースに構築されており、上の "API経由で GitHub Models を試すコード" を Azure の環境変数に置き換えるだけでそのまま動作するため、Azure AI Studio のお試し版として利用することもできます。 Azure AI Content Safety が全ての LLM に適用されている GitHub Models は Azure AI Studio をベースに構築されているため、Azure AI Studio と同様、 Azure AI Content Safety というコンテンツモデレーション機能が適用されています。 コンテンツモデレーションとは、不適切な入出力を除去するシステムのことで、LLM とは別に用意されます。 LLM 単体で不適切な入出力を防止するのには限度があるため、コンテンツモデレーションを LLM と組み合わせて利用するケースが最近では多く、Azure AI Content Safety の他に Meta の Llama Guard や Google の ShieldGemma のような例があります。 実際に、「爆弾の作り方を教えて」という不適切な入力を GitHub Models 上で与えると、Azure AI Content Safety により不適切な入力と検知されているのが分かります。 このようなコンテンツモデレーション機能は、開発者にとっては便利なものですが、LLM そのものの性能とは別個であることには注意が必要です。 特に、LLM 単体の安全性をテストする用途では GitHub Models は使えないと言えるでしょう。安全性を測定するために、故意に不適切な質問例を入力したとしても、LLM に入力する前段階のコンテンツモデレーションで弾かれてしまう場合が多いからです。 GitHub Models を使って LLM の日本語性能を横断的に測定する 実験 GitHub Models を研究用途で使う簡単な実践例として、ELYZA-tasks-100 というデータセットを用いて LLM の日本語性能を測定していきます。 ELYZA-tasks-100 は、株式会社 ELYZA が作成した日本語 LLM 評価データセットです。100 問の幅広い種類の日本語の質問に対する LLM の応答を採点することにより、性能を評価します。質問文だけでなく、模範回答や評価観点も合わせてアノテーションされています。 ELYZA-tasks-100 は、ELYZA 社内だけではなく NEC やリコーなどの他社のLLM評価でも活用されており、日本語 LLM コミュニティの中では標準的なデータセットの1つです。 note.com jpn.nec.com jp.ricoh.com 今回は、先ほど モデル一覧 で紹介した 21 種類の GitHub Models 上の LLM それぞれに対し、API 経由で、ELYZA-tasks-100 のそれぞれの質問を入力します。入力が100件なので、GitHub Models の厳しい API レート制限であっても、1~2日あれば全ての出力が完了するという計算です。 LLMの出力生成に関する余談 ① LLM の出力結果は、推論時のハイパーパラメータに左右されることが知られており、LLM によって最適なパラメータが異なるとも言われています。 今回の実験では、GitHub Models のプレイグラウンドで採用されているデフォルトのパラメータを採用します。具体的には以下の値の通りです( temperature と top_p 以外の値は、全て Azure AI Inference SDK のデフォルトの値を使います)。 モデル名 temperature top_p AI21-Jamba-Instruct 1 1 Cohere Command R, Cohere Command R+ 1 1 Meta-Llama-3-70B-Instruct, Meta-Llama-3-8B-Instruct, Meta-Llama-3.1-405B-Instruct, Meta-Llama-3.1-70B-Instruct, Meta-Llama-3.1-8B-Instruct 0.8 0.1 Mistral Large, Mistral Large (2407), Mistral Nemo, Mistral Small 0.7 1 OpenAI GPT-4o, OpenAI GPT-4o mini 1 1 Phi-3-medium instruct (128k), Phi-3-medium instruct (4k), Phi-3-small instruct (128k), Phi-3-small instruct (8k), Phi-3-mini instruct (128k), Phi-3-mini instruct (4k), Phi-3.5-mini instruct (128k) 0 1 LLMの出力生成に関する余談 ② ELYZA-tasks-100 の 100 件の質問の中でも、Azure AI Content Safety で不適切と判定される質問が 2 件存在しました。具体的には、 あの、娘がやっているあのキ、チックトック?チックトッカー?っていうのは何なんですか? と、 ガラスを使い捨てライターで炙ったら燃えますか? です。これらは LLM が出力を返すことができないため、後述する評価の対象から除外しました。 ELYZA-tasks-100 の質問集に対するそれぞれの LLM の回答を記録した後に、以下の 2 点から評価します。 1. GPT-4o による自動評価 現在使える最高性能の LLM の1つとされる GPT-4o を使って回答を評価します。具体的には、 ELYZA のテックブログで紹介されたプロンプト を改変し、以下のプロンプトを GPT-4o に入力することで1 ~ 5 の 5 段階でスコア付けします。最後に、回答全体でスコアの平均を計算し、評価結果とします。 なお、プロンプト中の {質問文} がデータセットの元の質問文、 {LLMの出力} が質問文に対する LLM の回答、 {評価観点} と {模範回答} がデータセットに質問文と合わせて収録されている評価観点と模範回答の文章になります。 以下に示すユーザの質問に対するAIアシスタントの回答を評価してください。 AIアシスタントの回答に加え、模範的な回答である参考回答が与えられます。参考回答と比較したうえで、AIアシスタントの回答を評価してください。 評価は短い説明から始めてください。説明を提供した後、「評価:[[評価値]]」という形式で1から5までの尺度で応答を評価してください(例:評価:[[5]])。各尺度の基準は以下の通りです。 基本的な採点基準 - 1点: 誤っている、 指示に従えていない - 2点: 誤っているが、方向性は合っている - 3点: 部分的に誤っている、 部分的に合っている - 4点: 合っている - 5点: 役に立つ 基本的な減点項目 - 不自然な日本語: -1点 - 部分的に事実と異なる内容を述べている: -1点 - 「倫理的に答えられません」のように過度に安全性を気にしてしまっている: 2点にする 問題固有の採点基準 {評価観点} [質問] {質問文} [参考回答開始] {模範回答} [参考回答終了] [AIアシスタント回答開始] {LLMの出力} [AIアシスタント回答終了] なお、GPT-4o で自動評価を行なっているため、評価対象の LLM のうち GPT-4o や GPT-4o mini に対しては過剰に高いスコアになる可能性があります。 5 2. 出力が日本語になっているかどうかの評価 GPT-4o のような LLM を使った自動評価の欠点の1つとして、 出力が日本語以外であったとしても、内容に問題なければ高い点数をつけてしまう ことが知られています。少なくとも日本語を母語とする人たちが LLM を利用する際には、日本語で答えるべき質問には日本語で回答してほしいでしょう。 そこで、それぞれの LLM が各回答においてきちんと日本語を出力しているかを調べていきます。具体的には、以下の 2 ステップを踏むことで、出力が日本語でない件数をカウントします。 Python の langdetect モジュールにより、日本語ではない出力一覧を自動で抽出 その中から、実際に日本語ではない出力を目視でカウント 結果と考察 以下の通りになりました。 モデル名 GPT-4o による自動評価 日本語以外の回答の件数 AI21-Jamba-Instruct 1.99 13 Cohere Command R 2.81 0 Cohere Command R+ 3.37 0 Meta-Llama-3-70B-Instruct 3.17 80 Meta-Llama-3-8B-Instruct 2.66 87 Meta-Llama-3.1-405B-Instruct 3.11 0 Meta-Llama-3.1-70B-Instruct 3.13 0 Meta-Llama-3.1-8B-Instruct 2.51 0 Mistral Large 3.01 33 Mistral Large (2407) 3.90 0 Mistral Nemo 2.91 7 Mistral Small 2.86 25 OpenAI GPT-4o 4.10 0 OpenAI GPT-4o mini 3.82 0 Phi-3-medium instruct (128k) 3.11 0 Phi-3-medium instruct (4k) 3.17 2 Phi-3-small instruct (128k) 2.65 1 Phi-3-small instruct (8k) 2.81 0 Phi-3-mini instruct (128k) 2.17 0 Phi-3-mini instruct (4k) 2.27 0 Phi-3.5-mini instruct (128k) 2.61 0 この結果から、以下のような考察を導けます。 同じ開発元の同程度のパラメータ数の LLM であっても、 新しいバージョンになると性能が飛躍的に向上する ケースが見られます。例えば、Mistral Large から Mistral Large (2407) では自動評価スコアが 3.01 から 3.90 に上昇しています。同様に、Phi-3-mini instruct (128k) から Phi-3.5-mini instruct (128k) では 2.17 から 2.61 に上昇しています。 LLM の中には、日本語以外の出力ばかり返してしまう LLM もあれば、きちんと日本語を返す LLM もあります。例えば、Meta-Llama-3-70B-Instruct は自動評価では 3.17 という高スコアを収めているにもかかわらず、80 件もの回答が日本語以外になってしまいました。 6 一方で、Meta-Llama-3.1-70B-Instruct や Cohere Command R+、Mistral Large (2407) など、 公式に多言語対応を謳っている LLM はきちんと日本語を返してくれる 傾向にあるようです。 どの開発元のモデルであっても、パラメータ数の大きい LLM の方が基本的には自動評価スコアも高いです。ただし、Meta-Llama-3.1-405B-Instruct と Meta-Llama-3.1-70B-Instruct の間では差は見られませんでした。 Microsoft の Phi-3 系列のモデルでは、コンテクスト長の長いモデルの方が自動評価スコアは低いという結果になりました。これは、ELYZA-tasks-100 の質問文が基本的に短文であり、長文向けに学習されたモデルにとって不利な測定方法になっているからかもしれません。 まとめ 本記事では、GitHub Models の機能紹介と、GitHub Models を活用した LLM の日本語性能評価の実践例についてご紹介しました。 GitHub Models はまだリリースされたばかりの新しい機能です。今後もさまざまな LLM が GitHub Models に搭載され、すぐに試せるようになることを期待したいと思います。 アクティブパラメータ数とは、Mixture of Experts (MoE) という仕組みを採用した LLM において用いられる概念です。Mixture of Experts (MoE) とは多数の LLM を束ねた構造であり、推論時にいくつかの LLM のみ使用して推論させます。そのため、推論時に使う(アクティブになる)LLM のパラメータ数は、モデル全体のパラメータ数よりも小さい値になります。 ↩ Mamba とは、 Mamba: Linear-Time Sequence Modeling with Selective State Spaces (Gu & Dao, 2023) という論文で提案されたニューラルネットワークのアーキテクチャです。LLM で広く用いられる Transformer というアーキテクチャの欠点である、計算コストの高さを克服するために提案されました。 ↩ RAG とは Retrieval-Augmented Generation の略です。LLM を使ってテキストを生成する際に、まず初めに外部のドキュメントやデータベースなどから質問文に関連するテキストを抽出し、それと質問文を組み合わせて LLM に入力するという技術です。これにより、LLM が持っていない知識に関する質問に対しても正しく答えられることが期待できます。 ↩ コンテクスト長とは、ざっくり言ってしまうと、LLM に入力できる文字数制限のようなものです。 ↩ LLM による LLM の自動評価(LLM-as-a-judge)を提案した論文においては、この種の、自分自身の出力を高く評価するバイアスを self-enhancement bias と名付けています。 ↩ 余談ですが、Meta の Llama 3 に関しては、Llama 3 そのものよりも Llama 3 に日本語で継続事前学習を行なった Llama 3 Swallow を利用するのがおすすめです。Llama 3 自体の言語理解能力を維持しつつ、日本語できちんと出力を返すこともできるからです。本記事を執筆している時点で、Llama 3 Swallow は残念ながら Azure AI Studio には搭載されていませんが、NVIDIA NIM のような他社のプラットフォームにはすでに搭載されています( https://build.nvidia.com/tokyotech-llm/llama-3-swallow-70b-instruct-v01 )。 ↩
アバター
チームの管理情報を溜めていたオンプレ基盤で動く NetBox を Amazon Elastic Container Service へ AWS Cloud Development Kit を用いて移植しました。 今まで NetBox をオンプレで動かしていた際には以下のような運用の難しさがありました。 DB も Docker コンテナによって管理されており、冗長化もなかったため DB コンテナが落ちてしまうとサービス提供できなくなる可能性があった Docker Compose で動かしているので、サービスの作り直しを実施するとそれまでのログが削除される そもそも NetBox を動かしている場所で法定停電があり、定期的に NetBox のサービスがとまっていた NetBox を AWS へともっていくことでオンプレ運用時に発生していた手間を簡素化し、メンテナンス等も一部 AWS にマネージしてもらえることで運用の省力化を達成できました。 さらに、 AWS Cloud Development Kit を利用することでデプロイも簡素化できました。 今回はその様子や管理における粒度等といった細かい意思決定について共有します。 目次 目次 自己紹介 用語 NetBox データベース キャッシュ Docker コンテナサービス Elastic Load Balancing AWS Secrets Manager Infrastructure as Code と AWS Cloud Development Kit アーキテクチャ 実装 RDS ElastiCache NetBox 注意点 まとめ 自己紹介 こんにちは、イノベーションセンターの福田です。 普段はクラウド関連の調査・開発や Infrastructure as Code (IaC) の推進等に従事しています。 チームでオペレータが手動で実施していた NetBox 管理をパブリッククラウドに移行して運用の省力化をしつつ、 IaC 化することでデプロイがしやすい基盤を整えたのでその方法を共有します。 用語 今回の移植対象である NetBox やその移植先である Amazon Web Services (AWS) で使用しているコンポーネント、 IaC や AWS における IaC の実装としての AWS Cloud Development Kit (CDK) 等用語がいくつも飛びでます。 ここではそれぞれがどういったものかといったことについて説明します。 NetBox NetBox とは NetBox Labs が作成・提供している OSS データセンター管理ツールです。 OSS であるため、一定の環境さえ提供できれば今回紹介する AWS や Microsoft Azure, Google Cloud といったクラウドでも動かせます。 NetBox OSS 4.0.8 データセンター上のさまざまなものを管理できますが、一例を上げれば以下のようなネットワークの資産を管理できます。 IP アドレス ラッキング情報 VLAN 管理 この他にもさまざまなものを管理できますが、データセンターに設置した機器の情報等を一括で管理できます。 Apache License, Version 2.0 で提供されている 1 ため、 OSS として利用することもできますが、 NetBox Labs では NetBox Cloud というクラウドサービスを提供しています。 NetBox Cloud - NetBox Labs NetBox を NetBox Labs にマネージしてもらえるため、運用の省力化やセキュティの迅速な対応、信頼性の確保といったものをまかせることも可能です。 データベース NetBox はデータベースとして RDB である PostgreSQL のバージョン 12 以上を利用します 2 。 NetBox を移植するにあたってはこの PostgreSQL をパブリッククラウド上に用意する必要があります。 幸い、 AWS は Amazon Relational Database Service (RDS) のサービスとして Amazon Aurora というサービスにて PostgreSQL を提供しています。 Amazon Aurora(高性能マネージドリレーショナルデータベース)| AWS 簡単に PostgreSQL 互換の RDB をデプロイでき、セキュリティパッチやバージョンアップグレード、スケーラビリティの確保などを AWS に管理してもらうことができます。 Amazon Aurora には Aurora Standard と Aurora I/O 最適化といったプランがあります。 Aurora I/O 最適化は I/O 要求があるようなアプリケーションに向いていると AWS は述べており、今回の NetBox は社内利用であることからそこまで I/O 要求がないことを考えて Aurora Standard を採用しました。 I/O が少量~中程度のアプリケーションで費用対効果が高い選択肢は Aurora 標準です。I/O が大量のアプリケーションでは Aurora I/O 最適化により、料金パフォーマンスの向上、予測可能な料金の利用、最大 40% のコスト削減が可能です。 参考: AWS が Amazon Aurora I/O 最適化をリリース 他にも Amazon RDS for PostgreSQL というサービスがある 3 のですが、 Amazon Aurora は Amazon RDS for PostgreSQL に対して以下のメリットがあります。 ストレージの自動スケーリング ストレージのサイジングが不要 ストレージの地理的分散 マスターへの障害発生時にレプリカを昇格可能 Amazon RDS for PostgreSQL の場合はスタンバイ状態のものを別途用意する必要あり さらに、 NetBox では管理されるものが DC の IP アドレスや VLAN タグ等であることから利用されるデータ量はかなり少ないことが予想されます。 一般にデータ量や IO が少なければ料金的には Amazon Aurora の方が安いです。 Amazon Aurora と Amazon RDS for PostgreSQL における料金表は次の通りです ^InstanceSize 。 比較項目 Amazon Aurora Amazon RDS for PostgreSQL インスタンス料金 0.113 USD/時間・インスタンス 0.129 USD/時間・インスタンス I/O料金 0.24 USD/100万リクエスト - ストレージ料金 0.12 USD/月・GB 0.138 USD/月・GB 稼働時間を 730 時間 (1ヶ月)、利用する容量を 100 GB とし、 I/O は100万リクエストとします。 また、 Amazon Aurora ではリードレプリカを2つ、 Amazon RDS for PostgreSQL ではスタンバイを2つ用意すると仮定します。 この仮定のもと計算すると次のようになります。 サービス名 金額 Amazon Aurora 0.113 USD/時間・インスタンス * 3インスタンス * 730 時間 + 0.24 USD/100万リクエスト * 1 100万リクエスト + 0.12 USD/月・GB * 1ヶ月 * 100 GB = 259.71 USD Amazon RDS for PostgreSQL 0.129 USD/時間・インスタンス * 3インスタンス * 730 時間 + 0.138 USD/月・GB * 1ヶ月 * 100 GB = 296.31 USD 表をみると Amazon RDS for PostgreSQL は 296.31 USD で Amazon Aurora の 259.71 USD よりも高くなっていることがわかります。 そのため、料金・運用の省力化の観点から今回は Amazon Aurora を採用しました。 Aurora は他にも MySQL 互換のものも提供しており、また MySQL にも Amazon RDS for MySQL があります。 さらに RDS ではエンタープライズで人気のデータベースである Oracle Database を提供する Amazon RDS for Oracle や Microsoft が開発する SQL Server を提供する Amazon RDS for SQL Server もあり、さまざまな RDB の中から要件にあった RDB を選択・利用しつつ運用の省力化を実現できます。 キャッシュ NetBox ではキャッシュも Redis というソフトウェアを利用します 4 。 Redis とはインメモリーで動作するキーバリューデータベースです。 インメモリで動作するため非常に高速に読み書きできます。 こちらもパブリッククラウドで動作させるにあたっては別途用意しなければなりませんが、 AWS には Amazon ElastiCache というキャッシュサーバーを提供するサービスがあり、その中で Redis を提供しています。 Redis 用 Amazon ElastiCache(キャッシュ管理・操作)| AWS こちらを利用すれば AWS 側で可用性やスケーラビリティ・セキュリティの確保してもらうことができ、ユーザーは Redis を利用することに集中できます。 VM を使い自前で用意するといったことも可能ですが、運用にあたってはアップデートやセキュリティパッチの適用、スケーラビリティ・可用性の確保といった運用を減らしたかったので ElastiCache を利用しました。 ElastiCache では Memcached も提供されている 5 ため、ニーズに合わせたキャッシュサーバーを利用できます。 Docker コンテナサービス AWS では Amazon Elastic Container Service (ECS) という Docker コンテナサービスを提供しています。 Amazon ECS(Docker コンテナを実行および管理)| AWS ECS を利用すると Docker コンテナのデプロイや管理、スケーリングを容易に実現できます。 ECS には Docker コンテナを動かす環境によって次の形式が存在しています。 Amazon EC2 起動タイプモデル AWS の仮想マシン提供サービスである Amazon EC2 の上で Docker コンテナを動かす形式 Docker コンテナではなく仮想マシンに対して課金されるので仮想マシンが存在しつづけている限りは課金されつづける 通称 ECS on EC2 AWS Fargate 起動タイプモデル ECS on EC2 では Docker を実行する VM 環境を自前で管理するが、 Fargate は Docker コンテナの実行環境を AWS が管理してくれる コンテナに課金されるため、コンテナが起動した分だけ課金される Amazon EC2 起動タイプモデルよりも後に開発された 通称 Fargate ECS on EC2 は Docker コンテナ環境として仮想マシンを動かすため、地理分散させるときなどには仮想マシンへの配慮も必要となり、管理が煩雑になります。 一方、 Fargate であれば Docker コンテナを動かす環境は AWS が管理するため、ユーザーは Docker コンテナのことだけを考えればよくなり運用の省力化を狙えます。 仮想マシンの管理は煩雑になることが多いため、なるべく省きたいと考え、 ECS を利用して構築することにしました。 さらに幸いなことに、 NetBox は Docker Compose による構築方法も提供しています 6 。 そのため、そちらを参考にして今回の実装を進めました。 また今回はコンテナのみ動かせればよく、 GPU も利用しないため、 VM の管理を回避しなければならない ECS on EC2 ではなく Fargate を選択しました。 Elastic Load Balancing Elastic Load Balancing はロードバランサーを提供するサービスです。 Elastic Load Balancing(複数のターゲットにわたる着信トラフィックの分配)| AWS 用途によって4種類のロードバランサーが提供されています。 Application Load Balancer (ALB) L7 ロードランサー HTTP/HTTPS リクエストのロードバランシングを提供 Network Load Balancer (NLB) L4 ロードバランサー 静的 IP を割り振ることができる Classic Load Balancer (CLB) 名前にある通りクラシックな環境向けのロードバランサー Amazon EC2 の中でもクラシックな EC2-Classic な環境向けであり、 EC2-Classic Networking の環境は販売終了しているため 7 、現代で触る機会はまずない Gateway Load Balancer (GLB) L3 ロードバランサー ファイアウォール、侵入検知、ディープパケットインスペクションといった機能を提供する際に利用する 一般に Web サーバーを提供するのであれば ALB を提供すれば十分です。 ALB を利用すれば簡単に HTTPS 通信を提供できるだけでなく、 HTTP から HTTPS へのリダイレクトも実装できます。 ただし、 ALB では固定 IP アドレスを利用できません。 そのためもし固定 IP アドレスを利用する場合、 ALB の前段に NLB を用意しなければなりません。 NLB は固定 IP アドレスを提供できるので、 NLB -> ALB とすることで固定 IP アドレスを提供しつつ HTTPS 通信を提供できます。 NetBox はパスワードによる認証があり、 HTTP による通信は避けたい状況があります。 そのため HTTPS 通信を提供する必要がありました。 ALB を利用すれば AWS Certificate Manager という証明書管理サービスを通して証明書を取得・設定し、 HTTPS 通信を提供できるため、 ALB を通してサービス提供するようにしました。 AWS Secrets Manager AWS が提供するシークレット情報を管理するサービスです。 AWS Secrets Manager(シークレットのローテーション、管理、取得)| AWS 複数人でシークレットを共有したり、シークレットの閲覧・編集権限をロールベースで制御したり可能です。 シークレット情報はこちらに入れておけばアプリケーションにシークレット情報を含めずとも利用可能になり、安全にシークレット情報を利用できます。 シークレット情報は AWS がマネージする AWS Managed Key というもので暗号化されていますが、より強固なセキュリティが欲しい場合は Customer Managed Key (CMK) という顧客管理の鍵で暗号化もできます。 今回は表立ってでてくるというわけではないですが、利用する PostgreSQL や Redis のシークレット情報を保管・参照するといった用途で利用します。 Infrastructure as Code と AWS Cloud Development Kit 近年、インフラの構成時に設定していた細かい部分の暗黙知を減らしたり、デプロイ対応を平準化する目的でインフラをコード化する Infrastructure as Code (IaC) というパラダイムが普及しています。 有名なものでは HasiCorp が提供する Terraform といったものがあります。 Terraform by HashiCorp AWS では IaC の実装として AWS Cloud Development Kit (CDK) というものを IaC の実装の1つとして提供しています。 オープンソースの開発フレームワーク - AWS クラウド開発キット - AWS CDK はコードを TypeScript/Golang/Python/Java といったプログラミング言語で記述でき、新しく言語を覚えずとも慣れた言語でインフラコードを記述できます。 また、そのまま言語のライブラリなど各種エコシステムを利用できるため、既存の OSS インフラに乗って容易に拡張ができます。 たとえば TypeScript を利用すれば npm を利用してさまざまなコミュニティが提供するパッケージへアクセスできます。 このように既存の言語を利用できるため、簡単に始めることができます。 以下の例は TypeScript で仮想マシンを AWS 上に用意している様子です。 import * as ec2 from 'aws-cdk-lib/aws-ec2' ; new ec2.Instance( this , "Instance" , { // 各種コンポーネントを動かすのひ必要となる Virtual Private Cloud (VPC) // 詳細は https://aws.amazon.com/jp/vpc/ vpc , // 仮想マシンを t2.small で作成 instanceType : ec2.InstanceType.of( ec2.InstanceClass.T2, ec2.InstanceSize.SMALL, ), // OS は Amazon Linux 2023 を利用 machineImage : ec2.MachineImage.latestAmazonLinux2023(), } ); このように各種言語が既知であれば簡単にどのようなインフラ構成をしているのかを読めるところが CDK の特徴です。 CDK は背後で AWS CloudFormation という AWS の提供する構成管理ツールを利用しています。 各種言語で記載されたインフラの情報を元として CloudFormation が解釈できる形式にし、 CloudFormation を呼び出すことでインフラの管理を実現しています。 アーキテクチャ アーキテクチャとしては RDS と ElastiCache を配置し、NetBox からクライアントとして接続するという一般的な構成にしました。 NetBox は Docker イメージ内にバックエンドとフロントエンドを含めているため、1つの Docker コンテナを動かせばバックエンドとフロントエンドの両方を提供できます。 この環境を CDK で実装していきます。 実装 流れとしては次のようになります。 RDS の設置 ElastiCache の設置 NetBox の設置 RDS RDS では強力な権限を持つ管理者ユーザーが存在しており、そのユーザーのパスワードとして強力なものを設置します。 import * as sm from 'aws-cdk-lib/aws-secretsmanager' ; // 自動生成されるパスワードにおいて使わない文字を設定 // // 詳しくは https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/CHAP_Limits.html#RDS_Limits.Constraints const excludeCharacters = ':@/" \' ' ; const masterPassword = new sm.Secret( this , "DbMasterPassword" , { // AWS Secrets Manager というシークレット管理サービスにシークレット情報を保存するnode、見たときにわかりやすい名前をつけておく secretName : `/Netbox/DbMasterPassword` , // generateSecretString : { excludeCharacters , passwordLength : 64 , // 英大文字小文字、記号、数字を1つずつ含めるようにする requireEachIncludedType : true , // 管理者ユーザー名は `postgresAdmin` 固定 secretStringTemplate : JSON . stringify ( { username : "postgresAdmin" } ), // ここで生成するパスワードは `password` というキーで登録する generateStringKey : 'password' , } , } ); 次にこのパスワードを利用して RDS を生成するようにします。 import * as cdk from 'aws-cdk-lib' ; import * as rds from 'aws-cdk-lib/aws-rds' ; import * as ec2 from 'aws-cdk-lib/aws-ec2' ; new rds.DatabaseCluster( this , "Db" , { // RDS サービス上の DB 識別子 clusterIdentifier : "NetBoxDb" , // PostgreSQL の 12.16 を利用 engine : rds.DatabaseCluster.auroraPostgres( { version : rds.AuroraPostrgesEngineVersion.VER_12_16, } ), // 管理者パスワードを設定 credentials : rds.Credentials.fromSecret(masterPassword), // ストレージ暗号化を有効化 storageEncrypted : true , // マスター DB を動かす仮想マシンの CPU サイズやメモリーを設定 writer : rds.ClusterInstance.provisioned( "DbClusterWriter" , { instanceType : ec2.InstanceType.of( ec2.InsetanceClass.T4G, ecc2.InstanceSize.LARGE, ), } ), // リードレプリカを設置 readers : [ rds.ClusterInstance.provisioned( "DbClusterReader" , { instanceType : ec2.InstanceType.of( ec2.InsetanceClass.T4G, ecc2.InstanceSize.LARGE, ), } ), ] , } ); 最後に管理者パスワードは定期的に更新するようにします。 import * as sm from 'aws-cdk-lib/aws-secretsmanager' ; new sm.SecretRotation( this , "PasswordRotation" , { application : sm.SecretRotationApplication.POSTGRES_ROTATION_SINGLE_USER, // 保存先 secret : masterPassword, target : db, // 1度実行したら1週間後に実行 automaticallyAfter : cdk.Duration.days( 7 ), // パスワードに入れない文字を設定 excludeCharacters , } ); ここまでくれば psql 等の PostgreSQL クライアントで接続できます。 RDS が用意ができたら NetBox が接続するために必要な環境を RDS 内に作ります。 詳しくは ドキュメント に記載があるのでそちらを参照してください。 簡易には次のスクリプトを流せば準備完了です。 CREATE DATABASE netbox; CREATE USER netbox WITH PASSWORD ' <パスワード> ' ; ALTER DATABASE netbox OWNER TO netbox; 用意したら設定したパスワードを AWS Secrets Manager へ保存しておきます。 こうすることで NetBox のデプロイ時にシークレット情報を直接埋め込むことなくシークレット情報を参照できます。 私たちは既存の NetBox 環境があるので、上記設定で必要な初期化を PostgreSQL に行ったのち、以下の手順でバックアップを取得し復元作業を実施しました。 # 事前にユーザーにバックアップと復元計画を周知した上で既存 PostgreSQL から pg_dump で論理バックアップ docker-compose exec postgresql pg_dump -h postgres -U netbox netbox > db.dump # RDS へ接続できる仮想マシンを用意し、 db.dump をコピーしてその上で実行する # ダンプファイルから復元して netbox データベースを再構築 # コマンド実行後にパスワードの入力を求められるので事前に RDS の管理者アカウントのパスワードを AWS Secrets Manager から取得しておく必要がある psql -h "<RDS のマスター DB エンドポイント>" -U postgresAdmin -W < db.dump ElastiCache Redis には アクセス制御機能 があり、ユーザーに対してさまざまな制御を実現しています。 Redis 版 ElastiCache にもこの機能があり、 ユーザーを管理する機能 があります。 まず各種アクセス権限を設定したユーザーがおり、それをまとめておくユーザーグループがあるという状態です。 ユーザーグループには default というユーザー名を持つユーザーを必ず含めなければなりません。 そこで default ユーザーを作成し、 Redis に紐付けるユーザーグループへその default ユーザーを追加します。 import * as sm from 'aws-cdk-lib/aws-secretsmanager' ; import * as ec from 'aws-cdk-lib/aws-elasticache' ; const defaultUserPassword = new sm.Secret( this , "DefaultUserPassword" , { secretName : "/Netbox/RedisMasterPassword" , generateSecretString : { secretStringTemplate : JSON . stringify ( { username : "default" } ), // ここで生成するパスワードは `password` というキーで登録する generateStringKey : "password" , // Redis AUTH コマンドによる認証によって使用できる記号は制限されている // // https://docs.aws.amazon.com/ja_jp/AmazonElastiCache/latest/red-ug/auth.html#auth-overview excludeCharacters : "@%*()_+=~`{}|[] \\ : \" ;'?,./" , } , } ); const defaultUser = new ec.CfnUser( this , "DefaultUser" , { // ユーザーにつける ID は英小文字とハイフンしか受け付けない userId : "netbox" , userName : "default" , engine : "redis" , // 全ての権限つける accessString : "on ~* &* +@all" , // 生成したパスワードを使用するように設定 passwords : defaultUserPassword.secretValueFromJson( "password" ).unsafeUnwrap(), } ); const userGroup = new ec.CfnUserGroup( this , "UserGroup" , { // ユーザーグループにつける ID は英小文字とハイフンしか受け付けない userGorupId : "netbox" , engine : "redis" , userIds : [ defaultUser.userId ] , } ); ユーザーグループがあれば Redis を構成できます。 new ec.CfnReplicationGroup( this , "ReplicationGroup" , { replicationGroupDescription : "netbox" , engine : "redis" , // バージョンは 6.2 を使用 engineVersion : "6.2" , // Redis を動かす仮想マシンの CPU サイズやメモリーを設定 cacheNodeType : "cache.t5g.medium" , // Redis にはさまざまな設定項目があるが、ベストプラクティスのつまったデフォルトのものを利用する cacheParameterGroup : "default.redis6" , numNodeGroups : 1 , replicasPerNodeGroup : 2 , // TLS 有効化 atRestEncryptionEnabled : true , transitEncryptionEnabled : true , // 作成したユーザーグループを紐付け userGroupIds : [ userGroup.userGroupId ] , // 地理分散して冗長性確保 multiAzEnabled : true , // 英小文字とハイフンしか受け付けない replicationGropId : "netbox" , } ); NetBox NetBox は Docker Compose 化したものがあるので、そちらを参照にして進めます。 GitHub - netbox-community/netbox-docker: 🐳 Docker Image of NetBox 起動に必要となる環境変数は configuration/configuration.py にあります 8 。 以上の情報をもとにサービスを構築していきます。 ECS は以下の関係図式のようなコンポーネントで成り立っています。 タスクとは1つまたは複数の Docker コンテナの集合でタスク内の Docker コンテナは同一ホストで実行されます。 タスク定義は名前の通りタスクの定義を持つもので、タスク定義の内容を元にタスクが起動されます。 サービスはタスクを管理するもので指定された数のタスク数を維持してくれます。 タスクが失敗したら同じタスク定義を元に別のタスクを起動しなおしたりといったことを実施してくれます。 クラスターはサービスやタスクをまとめるための論理的なグループです。 本来は1つ1つのコンポーネントを記載していくのですが、現在の CDK では ALB を配置し、その後ろに ECS コンポーネントを配置するだけであれば ApplicationLoadBalancedFargateService という各種 ECS コンポーネント + Application Load Balancer をまとめてデプロイしてくれるパーツを提供しています。 そのため、今回はこちらを利用して Docker コンテナの実行環境や実行時に必要な情報を設定するのみとして実装量を減らしました。 import * as ecs from 'aws-cdk-lib/aws-ecs' ; import * as et from 'aws-cdk-lib/aws-ecs-patterns' ; const dbSecretName = "<PostgreSQL のシークレット情報を持つ AWS Secrets Manager のシークレット名>" ; new et.ApplicationLoadBalancedFargateService( this , "NetBox" , { // チームでしか利用しないため、アクセス頻度も低くそこまで CPU は必要ないので 0.5 vCPU を利用 cpu : 512 , // チームでしか利用しないため、アクセス頻度も低くそこまでメモリーも必要ないので 1024 MB に設定 memoryLimitMiB : 1024 , // 環境は安定して利用できる Linux/x85_64 で動かす runtimePlatform : { cpuArchitecture : ecs.CpuArchitecture.X86_64, operatingSystemFamily : ecs.OperatingSystemFamily.LINUX, } , taskImageOptions : { // Docker Compose 化された NetBox で指定された Docker リポジトリーを使用 // // https://github.com/netbox-community/netbox-docker/blob/0c99ff8b5663db3e0db5a45660cebda9f917508b/docker-compose.yml#L3 image : ecs.ContainerImage.fromRegistry( "netboxcommunity/netbox:latest" ), // NetBox は 8080 番ポートで動作している containerPort : 8080 , environment : { DB_HOST : "<設置した RDS のマスター DB の持つエンドポイント>" , DB_NAME : "netbox" , DB_PORT : "5432" , REDIS_HOST : "<設置した ElastiCache のプライマリーエンドポイント>" , REDIS_PORT : "6379" , // SSL は入れておくことを推奨 REDIS_SSL : "True" , } , // パスワードや秘密鍵等秘密にしておきたい環境変数はこちらに設定 secrets : { DB_USER : ecs.Secret.fromSecretsManager(dbSecretName, "username" ), DB_PASSWORD : ecs.Secret.fromSecretsManager(dbSecretName, "password" ), // さまざまなハッシュに利用される値 // こちらも事前に AWS Secrets Manager に登録しておくこと // 詳しくは以下の URL を参照 // // https://netboxlabs.com/docs/netbox/en/stable/configuration/required-parameters/#secret_key SECRET_KEY : ecs.Secret.fromSecretsManager( "<シークレットキーを保管する AWS Secrets Manger のシークレット名>" , "<シークレットの参照キー>" ), REDIS_PASSWORD : ecs.Secret.fromSecretsManager( "<設置した Redis の default ユーザーのパスワードを持つ AWS Secrets Manager のシークレット名>" , "password" ), } , } , } ); これらをまとめた上でデプロイすると NetBox 環境が生成されます。 注意点 少し高度な内容になります。 CloudFormation では管理の単位をスタックという単位で行っています 9 。 スタックによってさまざまなリソースを一括で作成・更新・削除でき、削除に失敗すれば元の状態へロールバックしてくれます。 このスタックに変更を加えると場合によってはリソースを削除・再生成するという手順を踏むことがあります。 挙動次第では DB やキャッシュというものの削除・再作成まで走ることがあります。 何度作り直しても問題ない仮想マシンや ECS クラスター等のリソースであればそこまで困ることはありません。 ですが、あまりにも1つのスタックにまとめすぎると何かのパラメータ変更と共にスタックの全てが再作成されることもあり得ます。 場合によっては識別子が一意でなければならない影響で作成に失敗し、ロールバックしようとするも削除されていないものが残っていてにっちもさっちもいかなくなるということも起こります。 特に RDS や ElastiCache は削除や変更は慎重におこなうべきものであり、つくりあげた環境が削除や更新もできなくなるという状況は避けたいです。 そこで CloudFormation スタックを1つで管理はせずに小さく RDS / ElastiCache / NetBox でわけて管理しています。 CloudFormation には Nested Stack という機能 10 もあり、スタックは1つにまとめつつもサブのスタックとして管理もできます。 しかし、Nested Stack があるからと言っても更新頻度が頻繁なものとそうでないものを混ぜると更新頻度の高いものにひっぱられて一斉に更新されることがあります。 Web サーバーは更新頻度が高い一方、 RDS や ElastiCache の更新頻度は高くないことが予想されるため、全てを一緒に含めてしまうと運用が複雑化してしまいます。 このように無用な混乱や運用の複雑化を避けるため、 RDS や ElastiCache 等ステートフルなリソースは個別のスタックで管理すると楽になることがあります。 まとめ ここまででオンプレにあった NetBox を AWS へ CDK を利用して移植する方法について記載してきました。 幸い NetBox が PostgreSQL や Redis を利用しており、 AWS にこれらをマネージするサービスがあったため、それらを利用して簡単にサービスを構築し、運用も省力化可能になりました。 今後は RDS や ElastiCache のアップグレードにおいてインプレースでのアップグレードに対応しているのでそちらを試したり、バックアップの定期取得をおこなったりといったことを計画しており、これらを CDK といった IaC と実体との整合性を考慮しながら実施していこうと思います。 https://github.com/netbox-community/netbox/blob/597fc926c0084ed0935d03f95ca02dc94250be29/LICENSE.txt ↩ https://netboxlabs.com/docs/netbox/en/stable/introduction/#application-stack ↩ https://aws.amazon.com/jp/rds/postgresql/ ↩ https://netboxlabs.com/docs/netbox/en/stable/introduction/#application-stack ↩ https://aws.amazon.com/jp/elasticache/memcached/ ↩ https://github.com/netbox-community/netbox-docker ↩ https://aws.amazon.com/jp/blogs/news/ec2-classic-is-retiring-heres-how-to-prepare/ ↩ https://github.com/netbox-community/netbox-docker/blob/0c99ff8b5663db3e0db5a45660cebda9f917508b/configuration/configuration.py#L60-L338 ↩ https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/stacks.html ↩ https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-cfn-nested-stacks.html ↩
アバター