TECH PLAY

NTTドコモビジネス

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

602

こんにちは、NTT Com イノベーションセンターのNetwork Analytics for Security(NA4Sec)プロジェクトです。 この記事では、2024年1月25日・26日に開催されたセキュリティカンファレンス JSAC2024 にTeam NA4Secから登壇した2件の講演について紹介します。 Operation So-seki: You Are a Threat Actor. As Yet You Have No Name. Analysis of Activities and Tools of Phishing Actors Targeting Japan JSACとは Team NA4Secとは Team NA4SecによるJSAC2024講演 講演1: Operation So-seki: You Are a Threat Actor. As Yet You Have No Name. 講演2: Analysis of Activities and Tools of Phishing Actors Targeting Japan おわりに 興味を持たれた方へ 出張講演承ります 脅威情報を配信しています 仲間を募集しています JSACとは JSACはJPCERT/CCが主催するセキュリティカンファレンスで、 現場のセキュリティアナリストが集い、高度化するサイバー攻撃に対抗するための情報を共有すること を目的に開催されています。 毎年300-450名の定員が事前に埋まって参加申し込みが締め切られるほど関心を集めているイベントで、今年は7回目の開催でした。 本日、予定通り開催です。みなさまのご来場お待ちしております。 #JSAC2024 pic.twitter.com/0KNBqKv2ed — Analysis Center (@jpcert_ac) 2024年1月24日 もともとはJapan Security Analyst Conferenceの略称としてJSAC(ジェイサック)と呼称していましたが、日本に閉じず国際会議としての位置付けを目指すためにJSAC2023からはJSACを正式名称としています。 実際に毎年、海外からも応募・登壇があり、今年も海外から複数のスピーカーが登壇されていました。 特にAPAC(Asia-Pacific)に関係するセキュリティ脅威について他では得られない深い知見の集まるイベントの1つと言えます。 なお、過去開催の様子は JPCERT/CCのブログ でも紹介されています。 一部非公開のものもありますが、各回のWebサイトでは講演資料が公開されており、 JPCERT/CCのYoutube公式チャンネル には講演動画も公開されています。 Team NA4Secとは NA4Secプロジェクトは「NTTはインターネットを安心、安全にする社会的責務がある」を理念として、攻撃インフラの解明、撲滅を目指すプロジェクトです *1 。 NTT Com イノベーションセンターを中心としてNTTセキュリティ・ジャパンやエヌ・エフ・ラボラトリーズ(以下、NFLabs.)からもメンバーが参画し、日夜攻撃インフラを追跡しています。 今回、JSAC2024においてTeam NA4Secが追跡してきた脅威アクターに関する講演が2件採択され、登壇してきました。 なお、NTT ComのJSAC採択は今回が初で、いきなり合計5名での登壇となりました。 / イベント登壇情報✨ \ ​ 国内有数のセキュリティカンファレンス #JSAC2024 にて、 NTT Com、NFLabs.の社員が2つのセッションに登壇✨ ​ ハクティビスト、フィッシング被害について 調査で得られた知見を来場者限定で公開します! ​ 詳細はこちら⬇ https://t.co/zGuh2fOImO pic.twitter.com/6Iz3ta9BSH — ドコモビジネス|NTTコミュニケーションズ (@NTTCom_online) 2024年1月23日 Team NA4SecによるJSAC2024講演 講演1: Operation So-seki: You Are a Threat Actor. As Yet You Have No Name. 1件目の講演は、親ロシア派ハクティビストを長期追跡した内容でした。 DDoS攻撃を用いるハクティビストという非常にセンシティブな話題を扱うため、多くの情報を参加者限定としましたが、選考委員や運営の方々にもご理解いただき、発表の機会を得ることができました。 昨年来、DDoS攻撃による被害は日本でもたびたびメディアに取り上げられるなど、関心が高まっていたこともあってか、うなずきながら話に耳を傾けてくださる参加者が何人も壇上から見えました。 「複数の観点からの分析や考察がとてもわかりやすく解説されていて非常に良かった」といった声を得ることもでき、このタイミングでこの話を持って来られてよかったです。 講演の中では昨今アクティブサイバーディフェンスの議論などでも注目を集めているフロー情報の活用についても触れ、その有効性や限界についてセキュリティ実務者の視点から共有しました。 前述の理由からほとんどの情報は伏せられていますが、JSAC2024の公式サイトに講演資料( 日本語版 ・ 英語版 )が掲載されていますので、気になる方はダウンロードしてみてください。 講演1登壇者の皆川(左: @strinsert1Na )、 神田(中央: @ashy0x41 )、 鮫嶋(右: @islairand ) 講演2: Analysis of Activities and Tools of Phishing Actors Targeting Japan 2件目の講演は、日本を狙ったフィッシングに関して、フィッシングアクター同士が交流する「フィッシングコミュニティ」やフィッシングに使われるツール(フィッシングキット)を調査した内容でした。 日本を狙ったフィッシングを取り巻くコミュニティがどのように形成され分業化されているのか、その実態を明らかにするとともに、フィッシングキットの挙動について実例を挙げて紹介しました。 参加者からは「フィッシングコミュニティやフィッシングアクターの活動状況についての話は大変興味深い」「フィッシングキットの分析内容の共有やIoCの活用例についても解りやすく説明されていた」「IoCの活用方法としてそういうツールがあるのか」などポジティブな反応を得ることができました。 講演内で取り上げたフィッシングキットに関するIoCについては講演資料( 日本語版 ・ 英語版 )内のAppendixにも記載していますので、もし参加ができなかった方は是非ダウンロードしてみてください。 講演2登壇者の益本(左: @masaomi346 )、 坪井(右: @ytsuboi0322 ) おわりに 多くの優秀なセキュリティアナリストが発表するカンファレンスに登壇し、コミュニティに貢献する機会を得たことは、登壇したメンバー各人だけでなくチームにとっても大きな出来事でした。 実を言えば昨年JSAC2023が開催されていた頃はプロジェクトの本務メンバーが1名しかいない状態でした。 そこから縁にも恵まれ、チームの活動を拡大・加速させることができ、今回の結果につながりました。 実際、今回の登壇者5名のうち3名はこの1年で新たにチームに加わったメンバーです(もっと言えば1名は今年度入社の新卒社員です)。 この短期間で一定の成果を上げられたことは、チームビルディングや環境・風土作りの方向性がチームメンバーや業務の性質にマッチしている表れと考えています。 今後も引き続き、セキュリティアナリストのスキルの底上げに貢献し、セキュリティ業界を盛り上げていければと考えています。 興味を持たれた方へ 出張講演承ります 今回の講演(特に1件目のハクティビスト)は、情報の性質上、攻撃者の詳細に関わる情報は公開していません *2 。 他方、サイバー攻撃に係る情報を(非公開に)共有することは今後の被害の未然防止や被害低減につながる価値があると考えています *3 。 出張講演なども前向きに検討しますので、興味のある方はNA4Secプロジェクトまでお気軽にご相談ください。 脅威情報を配信しています NA4Secプロジェクトでは兄弟プロジェクトであるMetemcyber *4 と連携してマルウェアやフィッシングに関する脅威情報を配信しています( @Metemcyber ) *5 。 情報配信に関する経緯や思いはブログ記事を公開していますので、興味のある方はそちらもぜひご覧ください。 サイバー脅威インテリジェンス(CTI)配信はじめました 日本を狙ったフィッシングサイトの情報配信はじめました 仲間を募集しています NA4Sec/Metemcyberプロジェクトではそれぞれ一緒に働く仲間を募集しています。 「脅威インテリジェンス」をキーワードに活躍の場を探している方、プロジェクトの理念に共感していただける方のご応募をお待ちしています。 Threat Intelligence Analyst / 脅威インテリジェンスアナリスト (NA4Sec) Threat Intelligence Engineer / 脅威インテリジェンスエンジニア (Metemcyber) NFLabs. ではセキュリティエンジニア/セキュリティインストラクター/マネージャーを募集中です。 Xやエンジニアブログでも情報を発信していますのでこちらも合わせてご覧ください。 NFLabs. RECRUIT NFLabs. 公式Xアカウント(@NFLaboratories) NFLabs. エンジニアブログ *1 : 社内では親しみを込めて「NA4Sec(なよせ)」と呼んでいます *2 : DDoS攻撃に関する情報共有の問題については、JPCERT/CCのブログ記事「 注意喚起や情報共有活動における受信者側の「コスト」の問題について ー情報発信がアリバイや成果目的の自己目的化した行為にならないためにー 」がおすすめです。 *3 : このあたりはNISCから公開されている「 サイバー攻撃被害に係る情報の共有・公表ガイダンス 」にて論点が整理されています。 *4 : 最近のMetemcyberの活動については、「 ソフトウェア開発におけるサプライチェーンセキュリティの実践 」をご覧ください。 *5 : 最近では TweetFeed にもデータソースとして取り込まれるようになったので、以前よりも扱いやすくなりました。
アバター
目次 目次 はじめに NeWork とは リリース頻度変更の背景 それまでの運用 課題 実現方法 解説 日次でワークフローが起動するようにする main ブランチの HEAD にタグが付与されていなければ付与する develop に差分があれば main へのマージを自動で行う 細かな工夫点 main の内容を develop に自動で取り込む 祝日はリリースしないようにする 自動リリース・自動 develop → main マージの制御 Slack にリリース結果を通知する stg 環境に変更内容を通知する その他の考慮 上司への事前説明の省略 スプリントレビュー前のリリース リリースノート 品質面 リリース頻度を変えてみて おわりに はじめに こんにちは、NeWork 開発チームの藤野です。普段はオンラインワークスペースサービス NeWork のエンジニアリングマネジメントをしています。 この記事では、それまで毎週新バージョンのリリースをしていた NeWork Web 版のリリース頻度を(最大で)毎日に変更した事例を紹介します。 NeWork とは NeWork はコロナ禍で誕生したオンラインワークスペースサービスです。 従来の Web 会議ツールとは異なり、手軽に話しかけられることを重視したサービスになっており、Web・デスクトップアプリ・モバイルアプリで提供されています。 サービスの基盤は GC(Google Cloud) 上で提供しており、フロントエンドは Next.js 、バックエンドは Node.js+Express を採用しています。 音声基盤は NTT Com の別チームが開発している SkyWay を利用しています。 リリース頻度変更の背景 それまでの運用 NeWork では 2021 年 7 月以降(ほぼ)毎週のリリースを実現し、大きなインパクトのある機能だけでなく、細かな改善やバグ修正を継続して実施してきました。 具体的には git flow ライクなブランチ運用をしながら毎週リリース作業をしていました。 develop から feature ブランチをきって開発し、完了したら develop にマージ。develop にマージされると自動で stg 環境にデプロイ 毎週水曜のリリース日 朝 : develop ブランチから main ブランチにマージ 日中 : NeWork チーム全員で stg 環境を 普段利用 し、致命的なバグがないことを普段利用の範囲で確認 夕方 : リリースタグを付与して prod 環境に自動デプロイし、動作確認 重大なバグがあれば、main ブランチから hotfix ブランチをきって修正 上長に状況を説明し、許可を得た上でリリース 課題 しかし 2 年以上この運用を続けてきていて以下の課題があると感じていました。 お客さまからのフィードバックに即応してもすぐリリースできない バグがあっても重大なものじゃなければ、次のリリースまで待つ必要がある リリース直前にバタつくことがある 来週まで延期したくないので、どうしても今週リリースしておきたい → 水曜の午後まで develop ブランチから main ブランチにマージできないこともあった この駆け込み乗車によってバグがあるままリリースしてしまう可能性があった 複数のスクラムチームのスケジュールに縛りを与える スプリントレビューを通過したものだけリリースになるので、必然とスプリントの終了日が固定され重複する → 複数チームのステークホルダーは全部のイベントに参加できなくなる リリース作業の一部は手動で面倒 & ミスする可能性がある develop ブランチから main ブランチへのマージを忘れたことがある (起きたことはないが) 付与するタグのフォーマットを間違える可能性がある それ以外でも、DevOps Research and Assessment が提唱する Four Keys のうちの「デプロイの頻度」「変更のリードタイム」を改善すべきと考えていました。 実現方法 上述の背景を考慮して、2023 年 9 月から以下を毎日完全自動で実施する運用に変更しました。 平日の夕方に、前日の夕方以降で develop ブランチにマージされたすべての変更を main ブランチに自動でマージする main ブランチへのマージをトリガーに stg 環境へ自動デプロイ NeWork チームは基本的に stg 環境を普段から利用しているので、普段使いの範囲の中で致命的なバグがないか丸一日確認できる 翌日の夕方、main ブランチにリリースタグを自動で付与 リリースタグをトリガーに prod 環境へ自動デプロイ 具体的には日時で起動する以下のようなワークフローを GitHub Actions で用意して実現しました。 name : daily-release on : # 平日 18:00 JST schedule : - cron : "0 9 * * 1-5" run-name : daily-release/${{ github.event.schedule }} # release タグが JST ベースの日付になるようにタイムゾーンを設定 env : TZ : "Asia/Tokyo" jobs : daily-release : runs-on : ubuntu-latest steps : - name : Checkout uses : actions/checkout@v3 with : ref : main # 全部 fetch しないと rev-list が動作しない fetch-depth : 0 # token を指定することで protected branch の設定を bypass できるようにする token : ${{ secrets.GH_TOKEN }} # 最新のタグと最新のコミットを取得 - name : get latest tag and commit id : get_latest_tag_and_commit run : | echo latest_tag_commit=$(git rev-list --tags --max-count=1) >> $GITHUB_OUTPUT echo latest_commit=$(git rev-parse HEAD) >> $GITHUB_OUTPUT - uses : actions/setup-node@v3 with : node-version : "20" - run : npm install @holiday-jp/holiday_jp - uses : actions/github-script@v3 id : is_holiday with : script : | const holiday_jp = require(`${process.env.GITHUB_WORKSPACE}/node_modules/@holiday-jp/holiday_jp`) core.setOutput('holiday', holiday_jp.isHoliday(new Date())); # Release tag の付与 - name : Create release tag id : create_release_tag if : | steps.get_latest_tag_and_commit.outputs.latest_tag_commit != steps.get_latest_tag_and_commit.outputs.latest_commit && vars.DAILY_RELEASE == 'true' && steps.is_holiday.outputs.holiday != 'true' env : # token を指定することで release 後の workflow が起動するようにする # デフォルトのリポジトリが所有するトークンでは他のワークフローがトリガーされないのは # GitHub Actionsの安全性のための仕様 GH_TOKEN : ${{ secrets.GH_TOKEN }} run : | release_tag=`deployment/scripts/get_release_version.sh` latest_tag=$(gh release list -L 1 | awk '{print $3}' ) gh release create $release_tag --title $release_tag --target main --generate-notes --notes-start-tag $latest_tag echo released_version_url=`gh release view --json url -q .url` >> $GITHUB_OUTPUT - name : Create pull request if : vars.DAILY_MERGE == 'true' env : GH_TOKEN : ${{ secrets.GITHUB_TOKEN }} id : create_pull_request run : | release_tag=stg.`deployment/scripts/get_release_version.sh day "+%Y%m%d%H%M" ` echo release_tag=$release_tag >> $GITHUB_OUTPUT latest_tag=$(gh release list -L 1 | awk '{print $3}' ) release_note=`gh api /repos/${{ github.repository }}/releases/generate-notes -f tag_name=dummy_tag -f target_commitish=develop -f previous_tag_name=${latest_tag} --jq .body | grep -Ev "dummy_tag$" | sed -e :a -e '/^\n*$/{$d;N;ba}' ` # || true をいれて失敗しても stderr.txt に書き込みがされるようにしている gh pr create --title "【定期実行】 $release_tag" --body "${release_note}" --base main --head develop -l 'auto merge' > /tmp/stdout.txt 2> /tmp/stderr.txt || true echo pull_request_uri=`cat /tmp/stdout.txt` >> $GITHUB_OUTPUT rm -f /tmp/stdout.txt continue-on-error : true - name : merge remote/main into develop if : ${{ steps.create_pull_request.outputs.pull_request_uri != '' }} run : | git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" git checkout develop git merge main - name : push changes to remote repository if : ${{ steps.create_pull_request.outputs.pull_request_uri != '' }} uses : ad-m/github-push-action@master with : # bypass できるユーザで実行することが重要 github_token : ${{ secrets.GH_TOKEN }} branch : develop - name : Merge pull request if : ${{ steps.create_pull_request.outputs.pull_request_uri != '' }} id : merge_pull_request env : # token を指定することで release 後の workflow が起動するようにする # デフォルトのリポジトリが所有するトークンでは他のワークフローがトリガーされないのは # GitHub Actionsの安全性のための仕様 GH_TOKEN : ${{ secrets.GH_TOKEN }} # push が反映されるように5秒待ってからマージ run : | sleep 5s gh pr merge ${{ steps.create_pull_request.outputs.pull_request_uri }} --merge --subject "Merge to main for ${{ steps.create_pull_request.outputs.release_tag }}" --body "Merge to main for ${{ steps.create_pull_request.outputs.release_tag }}" - name : Get PR error if : ${{ steps.create_pull_request.outputs.pull_request_uri == '' }} id : create_pull_request_error run : | echo error=$(cat /tmp/stderr.txt) >> $GITHUB_OUTPUT rm -f /tmp/stdout.txt /tmp/stderr.txt - name : Send Slack Notification uses : 8398a7/action-slack@v3 if : always() with : status : custom fields : job,took custom_payload : | { attachments : [ { color : '${{ job.status }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning' , text : ` Github Daily Release result : $ {{ job.status }} in $ { process.env.AS_TOOK } `, } , { color : '${{ steps.create_release_tag.conclusion }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning' , text : ` Release : $ {{ steps.create_release_tag.conclusion }} $ {{ steps.create_release_tag.outputs.released_version_url }} `, } , { color : '${{ steps.create_pull_request.outputs.pull_request_uri }}' === '' ? 'danger' : 'good' , text : ` PR Creation : $ {{ steps.create_pull_request.outputs.pull_request_uri }} $ {{ steps.create_pull_request_error.outputs.error }} `, } , { color : '${{ steps.merge_pull_request.conclusion }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning' , text : ` PR Merge : $ {{ steps.merge_pull_request.conclusion }} `, } ] } env : SLACK_WEBHOOK_URL : ${{ vars.SLACK_WEBHOOK_URL }} 解説 日次でワークフローが起動するようにする GitHub Actions のトリガーを schedule を使ってワークフローを自動起動するようにしています。 on : # 平日 18:00 JST schedule : - cron : "0 9 * * 1-5" main ブランチの HEAD にタグが付与されていなければ付与する 以下の手順でリリースタグを付与しています。 main ブランチ HEAD のコミットハッシュと最新のリリースタグがふられているコミットハッシュを取得 上述の値が合致していない = リリースできるものがあると判断してリリースタグを付与 リリースタグの付与をトリガーに別のワークフローで prod にデプロイが走るようにしてあります リリースタグの付与を secrets.GITHUB_TOKEN でやってしまうと別ワークフローの起動をトリガーできないので、個人のトークンを利用しています リリースタグの計算はスクリプトを用意して自動計算するようにしています # 最新のタグと最新のコミットを取得 - name : get latest tag and commit id : get_latest_tag_and_commit run : | echo latest_tag_commit=$(git rev-list --tags --max-count=1) >> $GITHUB_OUTPUT echo latest_commit=$(git rev-parse HEAD) >> $GITHUB_OUTPUT # Release tag の付与 - name : Create release tag id : create_release_tag if : | steps.get_latest_tag_and_commit.outputs.latest_tag_commit != steps.get_latest_tag_and_commit.outputs.latest_commit env : # token を指定することで release 後の workflow が起動するようにする # デフォルトのリポジトリが所有するトークンでは他のワークフローがトリガーされないのは # GitHub Actionsの安全性のための仕様 GH_TOKEN : ${{ secrets.GH_TOKEN }} run : | release_tag=`deployment/scripts/get_release_version.sh` latest_tag=$(gh release list -L 1 | awk '{print $3}' ) gh release create $release_tag --title $release_tag --target main --generate-notes --notes-start-tag $latest_tag 余談ではありますが、 .github/release.yml と PR への自動タグ付与ワークフローを利用して、それっぽいリリースノートを自動生成するようにもしています。 develop に差分があれば main へのマージを自動で行う develop ブランチから main ブランチへの Pull Request 作成 作成時のタイトルはリリースのときにも使ったタグ計算ロジックを応用しています Pull Request の description もリリースノートを自動算出するロジックを応用しています 作成がエラーにならなければ (基本的にはマージできる差分があるとき) マージする main ブランチへのマージをトリガーに別のワークフローで stg にデプロイが走るようにしてあります マージを secrets.GITHUB_TOKEN でやってしまうと別ワークフローの起動をトリガーできないので、個人のトークンを利用しています - name : Create pull request env : GH_TOKEN : ${{ secrets.GITHUB_TOKEN }} id : create_pull_request run : | release_tag=stg.`deployment/scripts/get_release_version.sh day "+%Y%m%d%H%M" ` echo release_tag=$release_tag >> $GITHUB_OUTPUT latest_tag=$(gh release list -L 1 | awk '{print $3}' ) release_note=`gh api /repos/${{ github.repository }}/releases/generate-notes -f tag_name=dummy_tag -f target_commitish=develop -f previous_tag_name=${latest_tag} --jq .body | grep -Ev "dummy_tag$" | sed -e :a -e '/^\n*$/{$d;N;ba}' ` # || true をいれて失敗しても stderr.txt に書き込みがされるようにしている gh pr create --title "【定期実行】 $release_tag" --body "${release_note}" --base main --head develop -l 'auto merge' > /tmp/stdout.txt 2> /tmp/stderr.txt || true echo pull_request_uri=`cat /tmp/stdout.txt` >> $GITHUB_OUTPUT rm -f /tmp/stdout.txt continue-on-error : true ...(中略)... - name : Merge pull request if : ${{ steps.create_pull_request.outputs.pull_request_uri != '' }} id : merge_pull_request env : # token を指定することで release 後の workflow が起動するようにする # デフォルトのリポジトリが所有するトークンでは他のワークフローがトリガーされないのは # GitHub Actionsの安全性のための仕様 GH_TOKEN : ${{ secrets.GH_TOKEN }} run : | gh pr merge ${{ steps.create_pull_request.outputs.pull_request_uri }} --merge --subject "Merge to main for ${{ steps.create_pull_request.outputs.release_tag }}" --body "Merge to main for ${{ steps.create_pull_request.outputs.release_tag }}" これによって、こんな Pull Request の作成・マージまで自動で行っています。 細かな工夫点 main の内容を develop に自動で取り込む main ブランチに直接行った hotfix やその他のコミットを develop に取り込むのも自動化しています。 そのために専用のステップを設けてマージ・プッシュをしています。 - name : merge remote/main into develop if : ${{ steps.create_pull_request.outputs.pull_request_uri != '' }} run : | git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" git checkout develop git merge main - name : push changes to remote repository if : ${{ steps.create_pull_request.outputs.pull_request_uri != '' }} uses : ad-m/github-push-action@master with : # bypass できるユーザで実行することが重要 github_token : ${{ secrets.GH_TOKEN }} branch : develop 余談ではありますが、我々の運用では develop ブランチは基本的に Pull Request なしでコミットできない設定にしてあるので、予めそのルールを迂回できるユーザを設定して、そのユーザのトークンを利用してプッシュするようにしています。 祝日はリリースしないようにする この運用のひとつの肝は、自動テストに頼りきらず(音声系の機能もあるため自動テスト一本に頼れるほど充実していないのが原因ですが)、stg 環境をチーム全体で丸一日にわたって通常利用したうえでリリースするところにあります。 なので、単純に月曜から金曜まで毎日実行してしまうと、祝日はまったく確認しないままリリースすることになってしまいます。 この問題に対応するため、以下のステップで祝日かどうかを判定しています。 - uses : actions/setup-node@v3 with : node-version : "20" - run : npm install @holiday-jp/holiday_jp - uses : actions/github-script@v3 id : is_holiday with : script : | const holiday_jp = require(`${process.env.GITHUB_WORKSPACE}/node_modules/@holiday-jp/holiday_jp`) core.setOutput('holiday', holiday_jp.isHoliday(new Date())); # Release tag の付与 - name : Create release tag id : create_release_tag if : | steps.is_holiday.outputs.holiday != 'true' 自動リリース・自動 develop → main マージの制御 全部自動で動くのは基本とても良いのですが、リリースやマージを制御したいことも稀にあるかもしれません。 stg 環境で事前に確認していたら大きなバグ見つけちゃった stg 環境での確認をもっと長めにしたい 働いている人が少ない状態ではリリースしたくない (年末年始とか) この要望に対して、ワークフロー自体を無効化してもいいのですが、各機能だけ個別に制御できるようにしています。(今のところ一度も活躍してないですが) 具体的には、 GitHub Actions 変数 を利用してこの値を変更するだけで制御可能にしています。 Slack にリリース結果を通知する 全部自動だと予期せぬエラーに気づけず大変です。 なので、以下のアクションがそれぞれうまくいったかどうかを Slack で通知するようにしています。 - name : Send Slack Notification uses : 8398a7/action-slack@v3 if : always() with : status : custom fields : job,took custom_payload : | { attachments : [ { color : '${{ job.status }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning' , text : ` Github Daily Release result : $ {{ job.status }} in $ { process.env.AS_TOOK } `, } , { color : '${{ steps.create_release_tag.conclusion }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning' , text : ` Release : $ {{ steps.create_release_tag.conclusion }} $ {{ steps.create_release_tag.outputs.released_version_url }} `, } , { color : '${{ steps.create_pull_request.outputs.pull_request_uri }}' === '' ? 'danger' : 'good' , text : ` PR Creation : $ {{ steps.create_pull_request.outputs.pull_request_uri }} $ {{ steps.create_pull_request_error.outputs.error }} `, } , { color : '${{ steps.merge_pull_request.conclusion }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning' , text : ` PR Merge : $ {{ steps.merge_pull_request.conclusion }} `, } ] } env : SLACK_WEBHOOK_URL : ${{ vars.SLACK_WEBHOOK_URL }} これによって、こんな風に失敗した理由や作成されたリリースタグ・PR の情報を通知してくれます。 stg 環境に変更内容を通知する 上述のワークフロー外でやっているので詳しく説明できないですが、我々が開発・普段利用している NeWork の機能のひとつであるワークスペース全体へのメッセージ機能を利用して変更内容を通知しています。 ここでも GitHub のリリースノート作成機能を利用して通知内容を作成しています。 これによって NeWork チームのメンバーが今日この後リリースされる内容を把握し、必要に応じて重点的な確認やバグ出しをしてくれることを狙って実施しています。 その他の考慮 上記の運用への変更を検討時、以下のような点についても考慮したうえで実行にうつしました。 上司への事前説明の省略 それまでは、週に 1 回のリリースの前に内容の説明と実際のチケットや PR のリンクを共有して上司の許可を得た上でリリースという手順を踏んでいました。 頻度があがるとこれは難しくなったのですが、上司に相談したところ、運用が改善するならぜひやっていこうとのお言葉をいただき、内容の共有は週に一度かつ事後で良い(=リリースの判断に関して権限委譲して頂いた?)となりました。 スプリントレビュー前のリリース NeWork は開発をスクラムで進めていました。それまでは、リリース日前日にスプリントレビューがあったので、その場でリリース可否を決めていました。 1 リリース頻度があがることでこれまでと同様のメンバーのレビューがはいったうえでリリース可否を決めるという運用はできなくなりました。 ただ、スプリントプランニングにて実装する内容は事前に合意していること、リリース前に一日 stg 環境で確認ができること、リリース後に問題があってもすぐ直す or 切り戻しすればいいという意識あわせをチーム全体で行い、このフロー自体をなくしました。 また、そもそもスプリントレビューしないでリリースしていいのという疑問もチーム内であがりましたが、NTT Com の技術顧問でもある吉羽さんの情報も参考に、そのような縛りがないことを確認したうえですすめました。 www.ryuzee.com リリースノート NeWork Web 版は  リリースノート を公開しています。 それまではリリース毎にプロモーションチームがユーザに伝わりやすい言葉でリリースノートを作り、リリースと同時に公開していました。 これもリリース頻度があがることで同じ運用を維持することが難しいのは明らかでした。 これについては、リリースノートを後追いで出す運用にすることで対処しました。 一方でプロモーションの理由でリリースノートや サービスサイト ・ ご利用ガイド 等とタイミングを揃えたりものについては開発のロードマップレベルであわせてリリースすることにしています。 昨年から feature flag も活用して、重要な機能のロールアウトとデプロイを分離できているのも一役買っています。 品質面 これまでは長いと一週間弱 stg 環境に将来リリースする機能が先行してデプロイされており、その中でバグが見つかりリリースまでの直して対処したこともありました。 リリース頻度があがることでこの確認期間が短くなり、品質が悪化するのではとの声もありましたが、逆に駆け込み乗車を撲滅し、必ず確認期間が一定以上設けられるようになること・確認すべき対象が常にアナウンスされることからほぼ影響がないと判断しました。 リリース頻度を変えてみて 2023 年の 9 月からこの運用をしています。 元々課題に感じていた点・懸念していた点・それ以外の影響について振り返ってみます。 課題 1 : ユーザからのフィードバックや軽微なバグに即応できない → 最短で翌日にはリリースできるようになって対応速度は確実に向上しました。 課題 2 : リリース直前のバタつき → リリース頻度があがることで無理やりリリースにねじ込むことがほぼ 0 になりました。 課題 3 : スクラムチームのスケジュールの縛り → これについては、スケジュール変更がちょっと面倒なこともあり、まだ実行に移せていません。次回チーム構成を変更する際に改めて意識していきたいと考えています。 課題 4 : リリース作業の一部が手動 → 完全に自動化され快適になりました。 考慮 1 : 品質面の劣化 → 今のところ以前の運用であれば防げたはずのバグの流出は一切なく、むしろ駆け込み乗車撲滅の恩恵が大きいと感じています。現在は並行して E2E テストの充実化も進めているので、この懸念はさらに小さくなっていっています。 副産物 : チームの残業時間が他チームと比較して目立って減りました。(この施策だけの影響ではないかもしれませんが、) リリース作業の完全自動化の恩恵と思っています。 基本的に良い影響しか出ていないので、今後も継続していこうと思っています。 おわりに この記事では、リリース頻度を毎週から毎日単位に変更した事例について紹介させていただきました。 ただ、毎日リリースできればゴールではないので、引き続き自動テストの範囲拡充・信頼性向上とそれに伴うリリース頻度の向上をさらに検討できればと思っています。(そもそもリリース頻度がすべてではないですが) NeWork はどなたでも無料でお試しできますので、もしプロダクトや使われている技術に興味を持っていただけたらぜひ触ってみてください。 また、2024 年 1 月現在、NeWork では一緒に開発を進めてくれる仲間を募集しています。詳細は以下のリンクをご覧ください。皆さまのご応募を心からお待ちしています! hrmos.co スクラムガイド にも「スプリントレビューのことを価値をリリースするための関門とみなすべきではない」とある通り、いわゆるアンチパターンです。 ↩
アバター
こんにちは、イノベーションセンターのメディアAI プロジェクト(以下、PJ)の小林です。普段はコンピュータビジョンの技術開発やAI/機械学習(ML)システムの検証に取り組んでいます。 我々メディアAI PJでは技術力の向上および業務で得られた知見の共有のために毎週チーム内で勉強会を行っています。本記事では2023年の上期に開催した勉強会の概要と勉強会で発表された資料をSpeaker Deckで公開したので紹介したいと思います。 目次 目次 メディアAI PJの紹介 メディアAI PJ勉強会の概要 2023年上期で発表された資料公開 おわりに メディアAI PJの紹介 最初に私たちメディアAI PJについて簡単に紹介したいと思います。メディアAI PJは名前の通り、画像・動画・3D・音声・言語 1 などのメディアに関連するAIの技術開発をメインに行っているチームです。事業部から来る技術相談を通してお客さまの課題を解決するための技術開発やメディアに関連するAIの新規技術創出を目指して取り組んでいます。本ブログで過去には技術動向調査のために参加したCVPRの記事 2 なども投稿していますので、興味ある方はそちらも見ていただけると嬉しいです。 メディアAI PJ勉強会の概要 さて、そんな私たちメディアAI PJでは課題解決に資する技術の開発を加速させるために「メディアAI PJ勉強会」を毎週開催しています。この勉強会はメンバーが気になる技術や話題について調査・検証した内容を発表し、チームで知見を共有する場となっています。 2023年上期は毎週30分ほど勉強会の時間を確保し、各回で1名が発表し、質疑応答・議論する形で開催しました。勉強会のテーマ・トピックは特に設定をせず内容は個人が自由に設定して発表しました。テーマ指定は行いませんでしたが発表する際は内容の背景が分かるように「なぜそのテーマを調べたか」という自分の発表内容のモチベーションについて言及することをルールとしました。モチベーションの中には「業務で必要になりそう」といった直近の開発に必要となるような案件ベースの内容から「世間・研究者の中で流行っている、面白そう」といった個人の興味ベースの理由などさまざまでした。 2023年上期で発表された資料公開 今回は2023年上期の勉強会で発表された資料のいくつかをSpeaker Deckで公開したので簡単に紹介したいと思います。公開した資料リストは以下のようになっています。興味のある資料がありましたら、ぜひ見ていただけると嬉しいです。 Embodied AIについて Embodied Cognitionと呼ばれる知能は感覚システムを通じてエージェントと環境の相互作用によって形成されると言う考えがあります。その考えを元に視覚、触覚、聴覚等の複数センサーを備えた自律的に学習するAIを研究するEmbodied AIについて調査してまとめた内容です。Embodied AIのタスクや環境シミュレータ、共通的なアプローチについてまとめられています。 Webスケールデータセットに対する実用的なポイズニング手法 Webスケールデータセットに対して、攻撃者がデータを操作して機械学習モデルを攻撃するポイズニング攻撃の実現可能性について調査した内容です。データセットに登録されているがすでに失効しているドメインを買い取る、Wikipediaのダンプデータに偽情報を差し込むなどの手法が紹介されており、それらを実際に行うための費用や成功率などが考察されています。 論文紹介 DISN: Deep Implicit Surface Network for High quality Single-view 3D Reconstruction コンピュータビジョンの分野で画像から3次元情報を復元することは重要なタスクとなっており、機械学習アプローチをはじめとしてさまざまな手法が研究されています。この資料では、3次元座標点に対して3DモデルSurfaceからの距離(Signed Distance Function)を推論することによって単一視点画像からの3次元形状を復元を試みたDISNという手法を紹介しています。 3D Human Mesh Estimationについていくつかまとめてみた 画像から人の3Dモデルのメッシュを推論することで、モーションキャプチャーのように画像から3Dアバターを動かすことが可能になると考えられます。このような画像から人の3Dメッシュ推論(3D Human Mesh Estimation)は体のパラメータを元にメッシュを変換する方法や、画像から直接メッシュを推論する方法などさまざま取り組まれています。この資料では3D Human Mesh Estimationのサーベイ論文を元にいくつかの手法について調べてまとめています。 CVPR2023 EarthVision Workshopより衛星画像関連論文紹介 近年、センシングデータの1つとして衛星やドローン空撮画像の活用が試みられています。この資料では2023年のCVPR EarthVision Workshopから衛星画像処理に対する論文を調査した内容となっています。具体的には複数時間の衛星画像をもとに雲を除去する手法、ハイパースペクトル画像に対してVision Transformerの学習についての論文をピックアップして紹介しています。 公開した資料リストから分かるようにテーマを指定しなかったためデータポイズニング攻撃から、3Dモデル、衛星画像処理など幅広い分野について各人が調査・検証した内容が発表されました。また、公開した資料以外にもさまざまなテーマについて発表されており、チーム内での知見共有・技術議論の場となっています。私自身もこの勉強会で自分では調べなかった分野についての知見を取り入れることができて非常に勉強になると同時に、普段とは違った分野に触れられるのが面白く感じています。このメディアAI PJ勉強会は23年下期も引き続き開催しており、今後もPJ内での活発な意見交換をしていく予定です。 おわりに 本ブログでは、私たちメディアAI PJで実施している勉強会とSpeaker Deckでの資料公開について紹介させていただきました。メディアAI PJでは画像や映像、さらには音声言語も含めたさまざまなメディアAI技術の論文調査や研究開発に今後も積極的に取り組んでいきます。 2024年1月現在、メディアAI PJでは一緒に技術開発を進めてくれる仲間を募集しています。詳細は以下のリンクをご覧ください。皆さまのご応募を心からお待ちしています! hrmos.co 現在は画像・動画をターゲットにした開発が多くなっています。 ↩ https://engineers.ntt.com/search?q=CVPR ↩
アバター
この記事は、 NTT Communications Advent Calendar 2023 26 日目の記事です。 みなさんこんにちは、イノベーションセンターの @Mahito です。普段は社内のエンジニアが働きやすくなることを目標に、コーポレートエンジニアとしての活動やエンジニア向けイベントの企画・運営をしています。 今回は、 NTT Communications Advent Calendar 2023 を振り返りつつ、今年のブログ運営チームが新たに行った取り組みを紹介します。 今年の Advent Calendar 振り返り Advent Calendar 開始前の検討 ページごとの PV 数共有 OGP 画像 ハッシュタグ まとめ 今年の Advent Calendar 振り返り 今年も無事に 25 日を走りきることができました。 運営スタッフとしては、アドベントカレンダーの記事以外にもやってくる通常記事のレビュー対応で週2本の記事をレビューしたり、 諸事情で記事の順番を急遽入れ替えたりと、大変なこともありましたが無事に終わって一安心しています。 記事としてはセキュリティから始まり、今年も話題になった生成系 AI を含む AI 関連、運用自動化、組織論、etc.. 多岐にわたりました。 そんな中でも、下記の3つは多くの方に読んでいただけた記事でした。 高専かるたを作ってみた TypeScript未経験でもスムーズに業務に取り組める、最強の学習用コンテンツを作った話 サーバレスにおけるRustについて 高専かるたについては、これは個人ブログで書くべきかという相談もありましたが、 面白いことをしている人が NTT Com 社内にいることを知ってもらうことも大事だということで、 本ブログにて執筆・掲載しました。 TypeScript は TypeScript 未経験のインターン生向けのオンボーディング用コンテンツとして作成された物の紹介でしたが、 考え込まれて作られた内容になっており、学習コンテンツを作る参考になる内容でした。 Rust はエネルギー効率という視点を交えつつ、各クラウドベンダーにおけるサーバレスでの Rust の取り組みや比較を紹介しており、 非常に面白い内容でした。 私個人としては以下の2つも趣味や仕事の工夫などが伝わってきておすすめな記事です。 おうち電力の Observability: parser combinator をガリガリ書いてスマートメーターとおしゃべりする 複雑な事業を解釈するためにチームで取り組んだこと この他にも、さまざまな記事がありますので、ぜひお時間がある時に読んでみてください。 Advent Calendar 開始前の検討 今年の Advent Calendar の取り組みは Slack のリマインダーを元に1ヶ月前から始まりました。 昨年の執筆者アンケート結果をまとめた際に今年実施・検討をする事項をまとめていたので、 ブログ運営チームの定例で資料を読みながら、今年はどういう取り組みにするかを話し合いました。 昨年のアンケートからは下記がメインの改善点として挙がっていました。 ページごとの PV 数共有 OGP 画像 ハッシュタグ 今年の Advent Calendar ではこのうち上の2つを改善しつつ、 昨年を踏襲する形で運営することにしました。 以下では、それぞれの取り組みと、ハッシュタグを採用しなかったことについて紹介します。 ページごとの PV 数共有 昨年の執筆者アンケートの中に、 「自分の書いた記事がどれぐらい読まれているのかを知りたい」 という声がありました。 運営側としても、記事を執筆してくれた人に少しでも記事を書いた効果を感じてもらいたいと考えており、 今年は Advent Calendar 期間の PV 数を執筆者へ共有することにしました。 PV 数は Google Analytics から見ることができるように設定しているため、 Google Analytics の閲覧権限を執筆者に付与することを検討しましたが、 権限を付与する会社用 Google Workspace アカウントを執筆者全員が所持をしていないという問題がありました。 また、昨年の執筆者アンケートの中に「少し作業感を感じた」という声もあり、 せっかく参加してくれた執筆者や、興味を持ってくれた人にも楽しんでもらえるようにしたいと考えていました。 このため、Advent Calendar の執筆者や興味を持ってくれている人に対して、 日々の PV 数を共有するため、Google Analytics の 12/1~12/n 日の PV 数を PDF でエクスポートし、 毎日 Slack へ通知することにしました。(手動) Slack で毎日 12/1 から前日までの PV 数やランキングの変化を共有することで、 Advent Calendar の Slack チャンネルに興味を持って参加してくれている人が楽しめるようになったのではないかと思います。 また、この取り組み自体は今回の Advent Calendar で初めて行ったものでしたが、 通常のブログ運営においても取り入れることで記事執筆に興味を持ってもらい、 もっとブログを盛り上げられないかと考えています。 個人的にはそのためにも、冬休みに自動化の仕組みを考えたいと思っています。 OGP 画像 昨年は記事のサムネイルとなる OGP 画像を特に指定していなかったのですが、 他社の Advent Calendar で OGP 画像のフォーマットを揃えて表示しているところなどがあり、 今年は我々も真似してみようかという話になりました。 また、X(旧 Twitter) で記事がシェアされた際にタイトルが欠落してしまう問題(下記 Post 参照)がありました。 私のチームに来てくれた方がblogに寄稿してくれました! #techlunch で話した内容を経験いただくとともに、さらなる改善にチャレンジしてもらいました。大規模なSDNコントローラの内部を見てもらえて、僕としてもすごく楽しかったです。興味ある人は是非読んでみてくだい https://t.co/8e2ts7A7fS — tobioka yoshiaki (@yosiaki6) March 16, 2023 そのため、今回の Advent Calendar では OGP 画像にタイトルを記載することで、タイトルが欠落しても記事の内容が伝わるようにしました。 “サーバレスにおけるRustについて - NTT Communications Engineers' Blog” (7 users) https://t.co/5awYoou8Qn — Mahito / まひと (@Mahito) December 22, 2023 なお、今年はお試しということもありパワポで OGP 画像を作成しましたが、 リリース直前のタイトル変更などもあり、犬の散歩中だった私が慌てるという小ネタもありました。 また、勘のいい方ならお気づきかもしれませんが、 今回の OGP 画像の背景色はコーポレートカラーの1つ #CC0033 にしています。 とても覚えやすいカラーコードなので、 ぜひみなさんも覚えて使ってくださいね! ハッシュタグ 昨年のアンケートでハッシュタグをタイトルに入れるのはどうかというアイデアもいただいていたのですが、 こちらに関しては今年の実施は見送りました。 実は 2021 年の Advent Calendar ではタイトルにハッシュタグをつけていたのですが、 昨年は実施しなかったという背景があります。 その理由として例えば、 #nttcom_ac2023 というハッシュタグをタイトルに付けることで、 記事のタイトルによっては表示が長くなりすぎてしまうためです。 ただ、一律禁止にしたわけではなく、入れる・入れないは任意という形を取りましたが、 今年のタイトルには一度も入ることはなかったという結果に終わりました。 まとめ 今年の Advent Calendar は、昨年のアンケート結果を踏まえ、 読者の方だけでなく執筆者や社内で興味を持ってくれている人にも楽しんでもらえるような取り組みを行いました。 その結果、今後のブログ運営にも取り込めるような発見があり、 運営も楽しく運営ができました。 また来年も、より良いブログ運営を通じて、 みなさんに NTT Com の面白い取り組みを発信していければと思います。 それではみなさん、よいお年を!
アバター
この記事は、 NTTコミュニケーションズ Advent Calendar 2023 25日目の記事です。 はじめに こんにちは、イノベーションセンター テクノロジー部門 メディアAI PJ所属の和田、小林です。 普段は画像/映像/言語/音声 等メディアを入力としたAI技術(メディアAI技術)を用いて、事業部/関連部支援や最新技術の調査/研究開発を行なっています。 今回は技術調査の一環として参加した「ViEW2023」について、ワークショップの概要や発表された論文について紹介したいと思います。 ViEW2023は2023年12月7日~8日にパシフィコ横浜で開催されました。詳細は下記サイトをご覧ください。 ViEW2023 公式Webサイト https://view.tc-iaip.org/view/2023/index.html . 目次 はじめに 目次 ViEWについて 流行りのテーマ 小田原賞 概要 外観検査 手法 In-Context Learning Large Vision-Language Model Otter 実験 課題・所感 未知データへの対応 概要 手法 実験 課題・所感 おわりに ViEWについて ViEW (Vision Engineering Workshop) は、1989年に「外観検査の⾃動化ワークショップ」としてスタートし、2003年より「ビジョン技術の実利⽤ワークショップ」と変わりました。 (外観検査とは、製品や部品の表面を確認する検査業務のことを指します。) 外観検査技術をはじめとした産業応用を根幹に据えながらも、現在では極めて幅広い分野をカバーしています。 今年のワークショップは2日間に渡り開催され、それぞれの日程のプログラムは以下のようなものでした。 (出典: https://view.tc-iaip.org/view/2023/index.html より引用) 各オーラルセッションではテーマが決められており(OS1は「産業応用」、OS2は「3次元・計測」等)、研究発表に加え基調講演も行われました。 インタラクティブセッションは現地でのポスター発表となっており、さまざまな大学や企業から2日間で計79件の発表が行われました。 さらに、特別講演では数理工学の世界的権威とも呼ばれる甘利 俊一 氏(帝京大学先端総合研究機構)の「脳と人工知能」に関する講演や、 満倉 靖恵 氏(慶應義塾大)による「脳研究の観点からみた生成型AI」ついての講演があり、興味深い内容を聴講できました。 流行りのテーマ 今回のViEW2023で発表された全ての研究タイトルに対してWord Cloudを適用し、どんなキーワードが流行しているのかを分析してみました。 Word Cloudによる結果を見ると、「画像」や「検出」(検知)等の単語が大きく見えています。 ViEWは2002年までは外観検査の自動化を目的とするワークショップであったため、 ViEW2023でも画像から製品の状態を検出するような、産業分野での実利用を想定した研究が多く発表されました。 また、「学習」や「精度」等の単語も大きく見えており、これらは検出精度を改善させるためにモデルの学習方法に対してアプローチした発表が多かったことが起因していると考えられます。 これらを踏まえて、今回は最優秀論文に選ばれた研究と未知データに対するアプローチに関する研究の2つの論文を紹介します。 小田原賞 ViEWでは毎年、最優秀論文に対して「小田原賞」が授与されています。 第29回(ViEW2023)の小田原賞に選ばれたのは以下の発表でした。 本節ではこちらの研究について紹介します。 山田 悠正,et al,大規模視覚言語モデルのIn-Context Learningによる少量データからの外観検査 概要 こちらの研究では Large Vision-Language Model(LVLM)とIn-Context Learning(ICL)を組み合わせることで、汎用的な外観検査における新しいアプローチを提案しています。既存の Large Vision-Language Model(LVLM) に外観検査の知識を付与するため、Web 上から収集した多様な良品・不良品画像で追加学習し、In-Context Learning(ICL)を用いて良品・不良品を例示し、これらに基づいて検査画像に対して良否判定をする枠組みとなっています。 外観検査 外観検査手法の例として良品の画像データのみから学習されるPaDiM 1 や画像と言語を組み合わせたSAA 2 が挙げられていますが、 前者には検査対象の製品ごとに学習サンプルの収集とモデルの学習が必要であること、また後者には製品ごとにハイパーパラメータの調整が必要であるという課題があり、どちらも汎用外観検査モデルとはなりえないと考えられています。 手法 これから論文中で用いられている各手法について紹介します。 In-Context Learning In-Context Learning(ICL) 3 とは、与えられた少数の例を用いてモデルのパラメータを更新せずに学習し、未知のデータに対して推論をする手法を指します。 Large Vision-Language Model Large Vision-Language Model(LVLM)はLLMの知識を活用し、視覚的特徴を言語空間にマッピングすることで、CaptioningやVisual Question Answeringなどのさまざまな視覚言語タスクにおいて優れた性能を示しています。 本研究では、この既存のLarge Vision-Language Model(LVLM)に対して外観検査タスクを追加学習させることで外観検査に関する専門的な知識を強化したLarge Vision-Language Model(LVLM)とIn-Context Learning(ICL)を組み合わせることで汎用的な外観検査モデルを提案しています。 (出典:元論文 図1 より引用) Otter OtterはIn-Context Learning(ICL)能力と指示追従能力を合わせ持つマルチモーダルモデルの開発を目的とし、Open Flamingo 4 をIn-Context Learning(ICL)かつInstruction Tuning 5 形式のデータセットで追加学習したモデルです。 Instruction Tuningは、さまざまなタスクにおいて、入力の指示に従った出力をするようにモデルの追加学習する手法です。 これにより、未知のタスクに対しても指示を与えることでタスクを実行することが可能となります。 (出典:元論文 図2 より引用) Otterは、画像特徴を抽出するための画像エンコーダと言語を生成するための言語モデルと、画像エンコーダと言語モデルを接続するためのPerceiver Resamplerから構成されます。 使用する画像エンコーダは CLIP ViT-L/14 6 であり、言語モデルは MosaicML Pretrained Transformer 7B 7 となっています。 実験 実験のデータセットはWeb上から収集した多様な製品の良品、不良品画像(計4,693枚)を学習データと検証データとして8:2に分割して使用しています。 また、学習データ3,738枚のうち1,834枚を例示画像として使用し、1,904枚を検査画像として使用しています。 提案手法の有効性を検証するために、データセットによる追加学習の前後で、Otterの性能比較を行っています。 また、評価には外観検査画像データセットであるMVTec-AD 8 を使用しています。 実験の結果、追加学習なしの場合は"Yes"や"No"で答えることができず、定量的評価が不可能な結果となっています。(例えば、"Carpet"の"Color"に対して、"This is a close-up image of a carpet, but it does not provide enough information to determine if the carpet has any specific defects."と回答) 一方で、追加学習ありの場合は一貫した出力形式が得られ、出力形式を統一したデータセットで学習することで、定量的評価が可能であることを確認しています。 定量的評価が可能であった、追加学習後の性能を製品ごとに評価した結果が以下のように示されています。 (出典:元論文 表1 より引用) 各行はMVTec-ADの各製品を表し、"Acc"の列は、各製品に対する良否判定の平均正解率を指しており、 "Carpet"、"Leather"、"Wood"において提案手法は高精度に良否判定が可能なことを確認しています。 課題・所感 論文中の実験結果では学習データに含まれていない物体を検知できていないため、従来手法のように検査対象ごとに学習サンプルを収集しモデルを学習する必要があります。 汎用外観検査モデルとするためには、学習方法の改善が必要であると感じました。 未知データへの対応 ViEWでは製品検査や物体検出に関する発表が多く見られましたが、なかでも未知データに対する検出や学習のデータ作成などの工夫が見られました。機械学習による実問題の解決を考えた際にデータ不足や未知データへの対応は多くの研究者の中で課題となっていると感じます。 本節では、未知物体の検出を試みた以下の発表を紹介したいと思います。 堀内 裕生, et al, 未知物体の検出とクラスタリング機能を備えた物体認識手法の提案 概要 この研究は既知物体の識別性能を維持したまま、未知物体を検出してクラス識別するモデルを提案しています。工事現場や工場などの自立型作業車では周囲の環境を認識して適切に動作する必要がありますが、そのような現場では一般の画像認識データセットに含まれない特有の物体も存在しており、それらの認識が必要になります。 手法 この手法では2つのステージから構成されていました。 第1ステージでは、教師ありデータを用いた未知物体検出モデルのVirtual Outlier Syhthesis(VOS) 9 によって公開大規模データセットから未知データを収集します。 第2ステージでは、まず第1ステージで取得したデータに対してDeep Clustering 10 を用いてラベル生成をします。 具体的には画像をCNNに入力して特徴量を作成し、その特徴量を主成分分析(PCA)および正規化で次元圧縮します。それに対してk-meansによって疑似ラベルを生成し、エポックごとに疑似ラベルを更新します。 次に既知・未知物体を含む画像のResNet、Region Proposal Networkによって特徴量と提案領域を取得し、物体ごとのクラス尤度を算出して学習しています。ResNetの重みは共有されるため、疑似ラベルはエポックスが進むごとに良いラベルへと更新される様になっています。 (出典:元論文 図2 より引用) 実験 実験では既知・未知物体の識別精度を確認しています。ステージ1に15クラスを既知、5クラスを未知と設定したPASCAL VOCデータセット 11 を使用し、ステージ2の推論にMS COCOデータセット 12 を使用していました。 比較手法はResNet50を用いたFaster R-CNN 13 です。 識別精度の比較は下図に示します。IDが既知クラスを示し、OODが未知のクラスを示しています。これを見ると提案手法は従来手法と同等の既知クラスへの精度を維持するとともに、未知物体への識別も可能としていることがわかります。 (出典:元論文 表1 より引用) 課題・所感 提案手法の課題としては疑似ラベル作成の設定があります。k-meansにおけるクラス数はユーザによる指定が必要です。未知物体のクラス数が把握できるような閉鎖的なシーンでは、ある程度指定は可能ですが他の環境・現場では未知クラス数を柔軟に変化させる必要があります。また、本来は別クラスの物体が同一クラスに分類されてしまう場合や、同一クラスの物体が別クラスに分類されてしまうと誤ったラベルで学習することになるため、疑似ラベル作成する際の分類の精度向上が必要だと感じました。 おわりに 本記事では「ViEW2023」に参加することで経験した、ワークショップ全体の内容やピックアップした研究発表を紹介しました。 このような場でしか得られない知見を技術開発や支援の業務に活かしていきたいと考えています。 また、今回のViEWだけではなく、私たちのチームでは技術調査としてさまざまなイベントに参加しているため、今後も機会があれば投稿したいと考えています。 ありがとうございました。 Thomas Defard, et al, "PaDiM: a Patch Distribution Modeling Framework for Anomaly Detection and Localization", ICPR, 2021. ↩ Yunkang Cao, et al, "Segment Any Anomaly without Training via Hybrid Prompt Regularization", arXiv:2305.10724, 2023. ↩ Tom B. Brown, et al, "Language Models are Few-Shot Learners", Advances in neural information, NeurlPS, 2020. ↩ Anas Awadalla, et al, "OpenFlamingo: An Open-Source Framework for Training Large Autoregressive VisionLanguage Models", arXiv:2308.01390, 2023. ↩ Jason Wei, et al, "Finetuned Language Models Are Zero-Shot Learners", arXiv:2109.01652, 2022. ↩ Alec Radford, et al, "Learning Transferable Visual Models From Natural Language .Supervision", PMLR, 2021. ↩ The MosaicML NLP Team, "Introducing MPT-7B: A New Standard for Open-Source, Commercially Usable LLMs", https://www.mosaicml.com/blog/mpt-7b accessed 2023. 12.20. ↩ Paul Bergmann, et al, "MVTec AD — A Comprehensive RealWorld Dataset for Unsupervised Anomaly Detection", CVPR, 2019. ↩ Xuefeng Du, et al," VOS: LEARNING WHAT YOU DON'T KNOW BY VIRTUAL OUTLIER SYNTHESIS ", ICLR, (2022). ↩ Mathilde Caron, et al,"Deep Clustering for Unsupervised Learning of Visual Features ", ECCV, (2018). ↩ Mark Everingham, et al, "The pascal visual object classes (VOC) challenge", IJCV, (2010). ↩ Tsung-Yi Lin, et al, "Microsoft coco: Common objects in context", ECCV, (2014). ↩ Shaoqing Ren, et al, " Faster R-CNN: Towards RealTime Object Detection with Region Proposal Networks ", IEEE Trans. PAMI, (2017). ↩
アバター
この記事は、 NTTコミュニケーションズ Advent Calendar 2023 23日目の記事です。 はじめに はじめまして。イノベーションセンター テクノロジー部門 OsecT-Ops プロジェクトの鄭(GitHub: nbhgytzheng )です。2021年度入社で、現在はオペレーショナルテクノロジー(OT)セキュリティリスク可視化サービス OsecT(オーセクト )の開発・保守運用業務に取り組んでいます。OsecTについては過去にブログで紹介していますので、ご興味がある方はご覧ください。 制御システムのセキュリティと対策技術OsecTのご紹介(前編) 制御システムのセキュリティと対策技術OsecTのご紹介(後編) OsecT、サービスリリースしました OTセキュリティリスク可視化サービス OsecT、リニューアルしました 今回は技術ブログへの2回目の投稿で、前回はOsecTで利用している Zeek とZeekのプロトコル拡張ツールである Spicy について紹介しました。パケット解析に興味がある方は是非 【日本初紹介】Zeek・Spicyの使い方まとめ をご覧になってください。 本稿では定形作業化された保守運用の作業を自動化したことについて紹介します。GCP(Google Cloud Platform)のみで実現しているため、OsecTだけでなく異なるサービス・プロダクトにも適用できます。 自動化前の保守運用 はじめに自動化前の保守運用についてお話します。なお、ここでは原因及び対処方法がマニュアルレベルで確立されている保守運用のみについて言及しています。 OsecTのシステムでは障害時などの原因分析に必要なSyslogなどログをCloud Loggingで収集しています。また、システムが特定のログを検知した場合、保守運用チームにCloud Monitoringで通知して、保守運用チームが作業をしています。 自動化後の保守運用 サービス契約数が増えるにつれて、保守運用の負担も増えていくため、すでにマニュアルレベルで対応が確立されている部分の自動化をしようと考えました。マニュアルを使って人手で対応すると、オペレーションミスの可能性もあるため、この点でも自動化は有効です。 GCPの機能を調査した結果、Cloud Pub/SubとCloud Functionsを利用すれば、自動化できることがわかりました。各機能を追加した後のイメージは以下のとおりです。 Cloud Pub/SubとCloud Functionsを追加することで、アラートの発生から対応までのプロセスが自動化できました。これにより保守運用チームの負担およびオペレーションミスの軽減につながります。 今回は「Cloud Pub/Subからtesttestというメッセージが転送されたら、GCP上のメッセージが出しているインスタンスにアクセスし、実行中の全てのコンテナを再起動する」という例で自動化のしくみを紹介します。 自動化に利用したGCPの機能紹介 本章では自動化に利用したGCPの機能を紹介します。エラー発生から復旧までの各機能間のつながりは以下のとおりです。 Cloud Logging Cloud Logging はストレージ、検索、分析、モニタリングをサポートするリアルタイムのログ管理システムです。Cloud Loggingを利用すると、対象リソースから自動的にログを収集できます。 Opsエージェント のインストール及びコンフィグファイルの設定により利用できます。これにより、ログが収集されてGCPログエクスプローラーでログ検索などができるようになります。 ログルーター ログルーター はCloud Logging内の特定のログを転送する機能です。以下のように設定すると、inclusion filterで設定した testtest を含むログが指定されたCloud Pub/Subに転送されます。 Cloud Pub/Sub Cloud Pub/Sub は、メッセージを生成するサービスを、それらのメッセージを処理するサービスと切り離す、非同期のスケーラブルなメッセージング サービスです。今回の自動化では特定の文字列に対して、後述のCloud Functionsを動かすために利用しています。 Cloud Functions Cloud Functions はクラウドサービスの構築と接続に使用するサーバーレスのランタイム環境で、Python、Ruby、Javaなどさまざまなプログラミング言語をサポートしています。今回はCloud Pub/Subから転送されたメッセージを確認し、特定の操作をできる環境を作成しました。以下はPythonで実装したコードの一部です。転送されたメッセージはJSON形式でcloud_event.dataに保存されていおり、辞書型として中身の情報をアクセスできます。 なお、Cloud FunctionsからインスタンスへのアクセスはVPCコネクタの設定が必要です。具体的なコーディングは Cloud Functions の公式サイトを参照してください。 以上の設定により、エラーが発生すると自動で対応できるようになります。 自動化による実行結果 インスタンス上でSyslogにtesttestというメッセージを書き込むと、ログエクスプローラに以下のメッセージが表示されます。これは自動対応済みであることを意味しています。 インスタンスにアクセスしてコンテナの状態を確認すると、正しく再起動されていることがわかりました。 USER@instance-demo:~$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES xxx image_name "/usr/bin/testtest" 21 seconds ago Up 19 seconds test_container_name1 xxx image_name "/usr/bin/testtest" 21 seconds ago Up 18 seconds 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp test_container_name2 ... ... まとめ 今回はGCPの機能のみを活用して、マニュアルなど手順が確立された障害対応を自動化しました。自動化のポイントであるCloud Functionsは汎用性が高く応用範囲も幅広いです。引き続き、定形作業を自動化しつつ、より障害が発生しにくい仕組みや構造に向けた取り組みを続けていきます。
アバター
この記事は、  NTT Communications Advent Calendar 2023  22日目の記事です。 はじめに こんにちは、イノベーションセンターの鈴ヶ嶺です。普段は、クラウド・ハイブリッドクラウド・エッジデバイスなどを利用したAI/MLシステムに関する業務に従事しています。 本記事は、各クラウドベンダーのサーバレスにおけるプログラミング言語Rustについて調査・比較した結果を紹介します。 まず初めにサーバレスでRustを利用するメリットをエネルギー効率の観点から説明し、次に各クラウドベンダーの関連記事をピックアップします。 さらに、それぞれのクラウドでRustを使ったサーバレスアプリの代表的な作成方法を紹介して比較します。 Rustのエネルギー効率 Rustは、次の公式ページでも宣伝している通りパフォーマンスを強くアピールしています。 Rustは非常に高速でメモリ効率が高くランタイムやガベージコレクタがないため、パフォーマンス重視のサービスを実装できますし、組込み機器上で実行したり他の言語との調和も簡単にできます。 https://www.rust-lang.org/ja ポルトガルのポルト大学にある研究機関 INESC TEC から発表されたプログラミング言語別にエネルギー効率を比較した論文からも上位に位置する性能を示していることが分かります。 Ranking programming languages by energy efficiency Table 4 1 多くのサーバレスの課金体系はリソースの実行時間に依存するため、エネルギー効率がよく高速に処理可能なRustはコスト的な観点から非常に有用です。 関連記事 このセクションでは各クラウドベンダーがRustに言及している記事をピックアップしました。 Why AWS loves Rust, and how we’d like to help AWSはRustを高く評価しており、Rustコミュニティに積極的に貢献していくことを表明したブログ記事です。 具体的には、オープンソースプロジェクトへのスポンサーやTokioコミッターの雇用などを行なっています。 AWS re:Invent 2023 - “Rustifying” serverless: Boost AWS Lambda performance with Rust (COM306) 2023年のAWSの年次会議AWS re:Invent 2023で発表されたBreakout sessionです。 内容はAWS SAMと cargo-lambda を使用してRust関数をデプロイする方法やPyO3, maturin, AWS SDK for Rustを使って既存のPythonで実装されたAWS Lambda関数にRustを組み込む方法を紹介しています。 発表中にはPythonとRustをコスト面で比較して、Rustがより経済的であることも示しています。 “Rustifying” Serverless: Boost AWS Lambda performance with Rust Rust on Cloud Run Google Cloudの公式Youtubeチャンネル Google Cloud Tech でCloud Run上においてRustを動作させる方法を解説する動画を公開しています。 次のようにRustのメリットとして 高速な起動による、バースト時のスケールアップ シングルバイナリによる扱いやすさ メモリ、CPU利用の高い効率による低コスト クリティカルタスクに対するパフォーマンス などが挙げられています。 https://www.youtube.com/watch?v=rOMroL3mhO4&t=262s Do We Need Rust Language Support for Azure? Microsoft Build 2023で行われたAzureにおけるRustのニーズを議論するセッションです。 残念ながらセッション内容自体は動画が配信されていないため把握できませんがRustコミュニティへのサポートや貢献がここから伺えます。 以上の様に、各クラウドベンダーはRustの高速性に伴うコストメリットなどに着目してコミュニティへの貢献やニーズ調査を積極的に行なっていることが分かりました。 次のセクションでは実際にそれぞれのクラウドでRustを使ったサーバレスを利用する代表的な方法を紹介します。 Amazon Web Services(AWS) AWSでは、 AWS Lambda を利用してサーバレスアプリを作成します。 Lambdaは課金単位が1ミリ秒単位のため高速に実行可能なRustによる低コスト化が期待できます。 AWS Lambdaの開発を円滑に進める周辺ツールにcargo-lambdaがあるため、今回はこちらを利用します。 実際のコマンド例 # install cargo-lambda brew tap cargo-lambda/cargo-lambda brew install cargo-lambda # create project cargo lambda new new-lambda-project --http cd new-lambda-project # debug cargo lambda watch curl http://localhost:9000 # deploy cargo lambda build --release cargo lambda deploy --enable-function-url # function url発行 option cargo lambda invoke --remote new-lambda-project --data-example apigw-request # デプロイされたlambdaをテスト実行 テンプレートとして以下のようなものが作成されます。 main.rs use lambda_http :: {run, service_fn, Body, Error, Request, RequestExt, Response}; /// This is the main body for the function. /// Write your code inside it. /// There are some code example in the following URLs: /// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples async fn function_handler (event: Request) -> Result < Response < Body > , Error > { // Extract some useful information from the request let who = event . query_string_parameters_ref () . and_then ( | params | params. first ( "name" )) . unwrap_or ( "world" ); let message = format! ( "Hello {who}, this is an AWS Lambda HTTP request" ); // Return something that implements IntoResponse. // It will be serialized to the right response event automatically by the runtime let resp = Response :: builder () . status ( 200 ) . header ( "content-type" , "text/html" ) . body (message. into ()) . map_err ( Box :: new) ? ; Ok (resp) } #[tokio::main] async fn main () -> Result < (), Error > { tracing_subscriber :: fmt () . with_max_level ( tracing :: Level :: INFO) // disable printing the name of the module in every log line. . with_target ( false ) // disabling time is handy because CloudWatch will add the ingestion time. . without_time () . init (); run ( service_fn (function_handler)).await } また、他AWSサービスと連携する場合は、 AWS SDK for Rust を用います。 注目するポイントしてAWS SDK for Rustは2023年11月27にGAとなっています。 今まで性能は良い一方で、SDKがプレビューという理由で採用を見送ってきたケースを解決すると思われます。 AWS SDK for Rust の一般提供を開始 https://aws.amazon.com/jp/about-aws/whats-new/2023/11/aws-sdk-rust/ Microsoft Azure Azureでは、 Azure Functions の カスタム ハンドラー を利用してサーバレスアプリを作成します。 Azure Functionsは課金単位が1ミリ秒単位のためAWS Lambdaと同様に低コスト化が期待できます。 ツールとして Azure Functions Core Tools を利用します。 プロジェクト構成は次の様に準備します。 host.json ファイル: アプリのルート local.settings.json ファイル: アプリのルート function.json ファイル: 関数ごとに必要 (関数名と一致する名前のフォルダー内) コマンド、スクリプト、または実行可能ファイル: Web サーバーを実行 | /MyQueueFunction | function.json | | host.json | local.settings.json | handler.exe https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-custom-handlers#application-structure HttpExample/function.json { " bindings ": [ { " authLevel ": " function ", " type ": " httpTrigger ", " direction ": " in ", " name ": " req ", " methods ": [ " get " ] } , { " type ": " http ", " direction ": " out ", " name ": " res " } ] host.json defaultExecutablePath で実行バイナリを指定しています。 { " version ": " 2.0 ", " logging ": { " applicationInsights ": { " samplingSettings ": { " isEnabled ": true , " excludedTypes ": " Request " } } } , " extensionBundle ": { " id ": " Microsoft.Azure.Functions.ExtensionBundle ", " version ": " [3.*, 4.0.0) " } , " customHandler ": { " description ": { " defaultExecutablePath ": " handler ", " workingDirectory ": "", " arguments ": [] } , " enableForwardingHttpRequest ": true } } local.settings.json { " IsEncrypted ": false , " Values ": { " FUNCTIONS_WORKER_RUNTIME ": " Custom " } } 今回は、次の様なRustアプリを用意します。 環境変数 FUNCTIONS_CUSTOMHANDLER_PORT でポートを指定するところがポイントになります。 HTTP イベントを処理するために Web サーバーを実行し、FUNCTIONS_CUSTOMHANDLER_PORT を介して要求をリッスンするように設定されています。 https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-custom-handlers main.rs use actix_web :: {get, web, App, HttpServer, Responder}; use serde :: Deserialize; use std :: env; #[derive(Deserialize)] struct Info { name: String , } #[get( "/api/HttpExample" )] async fn greet (info: web :: Query < Info > ) -> impl Responder { format! ( "Hello {}!" , info.name) } #[actix_web::main] async fn main () -> std :: io :: Result < () > { let port_key = "FUNCTIONS_CUSTOMHANDLER_PORT" ; let port: u16 = match env :: var (port_key) { Ok (val) => val. parse (). expect ( "Custom Handler port is not a number!" ), Err (_) => 8080 , }; HttpServer :: new ( || App :: new (). service (greet)) . bind (( "127.0.0.1" , port)) ? . run () .await } 実際のコマンド例 # install Azure Functions Core Tools brew tap azure/functions brew install azure-functions-core-tools@4 # create project cargo new handler cd handler cargo add actix-web cargo add serde --features derive ## edit src/main.rs # project setup mkdir HttpExample ## edit HttpExample/function.json touch host.json ## edit host.json touch local.settings.json ## edit local.settings.json # debug cargo build cp target/debug/handler . func start --custom --verbose curl "http://localhost:7071/api/HttpExample?name=World" # deploy ## Linux muslでcross build brew tap messense/macos-cross-toolchains brew install x86_64-unknown-linux-musl export CC_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-gcc export CXX_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-g++ export AR_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-ar export CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=x86_64-unknown-linux-musl-gcc cargo build --release --target=x86_64-unknown-linux-musl cp target/x86_64-unknown-linux-musl/release/handler . ## Resource Group, Storage Account, Azure Functionを事前に作成 az group create --name xxxxxxxxxxxxxxxxxx --location japaneast az storage account create --name yyyyyyyyyyyyyyy --location japaneast --resource-group xxxxxxxxxxxxxxxxxx --sku Standard_LRS az functionapp create -g xxxxxxxxxxxxxxxxxx -n zzzzzzzzzzzzzzzzz -s yyyyyyyyyyyyyyy --functions-version 4 --consumption-plan-location japaneast --os-type Linux --runtime custom ## Azure Functions Core Toolsでdeploy func azure functionapp publish zzzzzzzzzzzzzzzzz --custom また、他Azureサービスと連携する場合は、 Azure SDK for Rust を利用するのが一般的かと思います。 こちらはunofficialなSDKのため継続した利用や新しいサービスへの対応については注意が必要です。 Google Cloud Google Cloudでは、 Cloud Run を利用してサーバレスアプリを作成します。 今回は次のようなRustアプリを用意しました。 環境変数 PORT でポートを設定します。デフォルトは8080を利用します。 use actix_web :: {get, web, App, HttpServer, Responder}; use serde :: Deserialize; use std :: env; #[derive(Deserialize)] struct Info { name: String , } #[get( "/" )] async fn greet (info: web :: Query < Info > ) -> impl Responder { format! ( "Hello {}!" , info.name) } #[actix_web::main] async fn main () -> std :: io :: Result < () > { let port_key = "PORT" ; let port: u16 = match env :: var (port_key) { Ok (val) => val. parse (). expect ( "PORT is not a number!" ), Err (_) => 8080 , }; HttpServer :: new ( || App :: new (). service (greet)) . bind (( "0.0.0.0" , port)) ? . run () .await } Cloud Runではコンテナを利用します。 FROM rust:1.74.1 WORKDIR /usr/src/app COPY . . RUN cargo install --path . CMD ["helloworld-rust"]% 実際のコマンド例 # create project cargo new helloworld-rust cd helloworld-rust cargo add actix-web cargo add serde --features derive ## edit src/main.rs tourch Dockerfile ## edit Dockerfile # build docker build --platform=linux/amd64 -t gcr.io/xxxxxxxxxxxxxxxxx/helloworld-rust . # debug docker run -it --rm -p 8080:8080 gcr.io/xxxxxxxxxxxxxxxxx/helloworld-rust . curl "http://localhost:8080/?name=world" # deploy docker push gcr.io/xxxxxxxxxxxxxxxxx/helloworld-rust gcloud run deploy --image=gcr.io/xxxxxxxxxxxxxxxxx/helloworld-rust . また、他Google Cloudサービスと連携する場合は、 Google Cloud SDK for Rust を利用するのが一般的かと思います。 こちらはMicrosoftと同様にunofficialなSDKのため継続した利用や新しいサービスへの対応については注意が必要です。 比較 それぞれのサーバレスアプリの作成方法を比較した表が次の様になります。 Amazon Web Services Microsoft Azure Google Cloud サーバレスサービス AWS Lambda Azure Functions Cloud Run 課金単位 1ミリ秒単位 1ミリ秒単位 100ミリ秒単位 SDK(他クラウドサービスとの連携) AWS SDK for Rust(2023-11-27 GA) Azure SDK for Rust(unofficial) Google Cloud SDK for Rust(unofficial) AWS Lambda, Azure Functionsについては、1ミリ秒単位で課金がされるためエネルギー効率の高いRustによるコストメリットを受けやすいのかと思います。 SDKについては唯一AWSが公式ツールを提供している様な状況です。今まで性能面ではRustのメリットを理解はしていたが、他クラウドサービスとの連携面で採用が難しいと考えていた面を再考しても良いのではと思いました。 まとめ 本記事では、Rustの高いエネルギー効率によるサーバレスの低コスト化のメリットや各クラウドベンダーのRustへの取り組みを紹介しました。 また、それぞれのクラウドでRustを使ったサーバレスアプリの代表的な作成方法を紹介して比較しました。 それでは、明日の記事もお楽しみに! Pereira, Rui, et al. "Ranking programming languages by energy efficiency." Science of Computer Programming 205 (2021): 102609. ↩
アバター
この記事は、 NTT Communications Advent Calendar 2023 21日目の記事です。 IOWN推進室では、NTTグループ一体となり取り組んでいるIOWN®︎ 1 構想 2 (Innovative Optical and Wireless Network)の認知度向上・案件化に向けたプロモーション戦略業務を行っています。 本記事では、IOWN構想の複雑な事業を理解する・伝えるために実践した取り組みをご紹介します。 異動や転職、新規プロジェクトの参画で事業や技術の理解・伝え方に苦戦する方の参考いただければ幸いです。なお、内容は個人の見解に基づくものであり、所属組織の総意ではないことをご了承ください。 はじめに みなさんこんにちは。イノベーションセンター IOWN推進室の塚越と申します。 NTTグループ一体となり取り組んでいるIOWN構想のプロモーション戦略業務をしています。 2022年4月にNTTコミュニケーションズに新卒入社し、デザイン部門「 KOEL 」でデザインリサーチャーをしておりましたが、今年の7月にIOWN推進室に移りました。 今回はイノベーションセンターのValues 3 の1つである 「枠」を越えように焦点を当て、6ヶ月前に異動するまで未知なる領域であった「IOWN構想」を理解し、伝えることにチャレンジした経験をお話しします。 複雑な事業をシンプルに捉えることの難しさ 私たちは「変動性が高く、未来が予測しにくい」現代に、柔軟に対応できるようIOWN構想を推進しています。しかし現代をうまく捉えられないことと同様に、私にとってIOWN構想を解釈することはとっても難しいことでした。 なぜなら、自身の知識不足や情報過多、相互に関連する多くの要因が絡み合うことで複雑化しており、IOWN構想を明確に解釈することができなかったからです。 このように、前例のない複雑な事業を解釈する際に「自分自身が理解が深められない」や「難解な説明になってしまい聞き手を戸惑わせてしまう」といった状態に陥ること、みなさんはありませんか。 私の状況 IOWN構想との出会いは、異動する1年ほど前デザイン部門で携わっていた先端技術リサーチです。そのリサーチでは、「XR が人々の生活や社会に何をもたらすのか」XR を取り巻くさまざまな事例を参考に、XR がもたらす価値について分析をしました。 その際にIOWNに対して、以下のような印象を持ちました。 メタバースのようなデータ量が大きいものをぬるぬる動かすことができてストレスフリーになる 高度情報化が進み、SNSが当たり前に活用されることによって生まれた歪みをなくし、人それぞれの心地よさを追求できる情報社会のための基盤ができる 一方で、光電融合技術や光通信技術などIOWN構想を支える技術の理解は進んでいませんでした。 異動して苦労したIOWN構想全体像の把握 異動を機に最初に取り掛かったことは、IOWN構想の背景や技術に関する知識等全体像の理解です。それらを理解するために、IOWN構想に関するありとあらゆる文献に当たりました。しかし時間をかけてもなかなか理解が進みません。理解が進まない原因は大きく2つあると考えました。1つ目は自身の知識不足、2つ目はアクセス容易な文献の認知負荷 4 が高いということです。 具体例としては、専門的な用語・横文字の多用、文章が冗長、色調やレイアウトに一貫性がないといった事象が重なり、私にとって情報を処理しきれない難解な文献でした。1つ目の知識不足は、新しい領域に挑戦する時は誰しもぶつかる壁だと思いますが、2つ目の認知負荷が高いに関しては個人の問題のみならず、プロモーションを推進する上で障壁になっているのではと推測しました。 資料改善に取り組む これらを踏まえ、お客様説明用スライド資料の改善を進めることにしました。スライド資料改善の営みは、これからIOWNに関する業務を進めていく上で必要な知識を深め、ビジュアルでわかりやすく伝えることがほんの少しだけ得意な私がすぐに貢献できる絶好の機会でした。 情報の整理・分析 既存資料の分析 まずは何がスライドを複雑にさせているのか、現在の構成を整理してみます。 整理してみるとこのような障壁がありました。 シーズに偏った説明になっている 共感できるストーリーになっていない 理解の下地となる知識や情報が不足している 内容が取捨選択できておらず、70ページほどの巨大なスライドになっている etc... これらの問題が聞き手が共感しにくく難しいと思ってしまう原因になっているのではないかと考えました。 お客様、営業さんの声を聞く 次にチームメンバーがスライドを使ってご提案する場に同席し、お客様や営業さんの声を聞きました。特に多くご意見をいただいたのが「利用シーンを想起しにくい」ということでした。技術的な説明の割合が多い一方、ユースケースの割合が少ない構成になっており共感や納得につながらないこと、また聞き手が求めていることを想定できていないという課題を認識しました。 資料化 分析を通して既存のスライドに不足している事柄を把握し、いざ資料化を進めていこうとすると知識不足の壁を感じ、私一人の目線や知識では限界を感じました。 そこで、IOWN推進室のメンバーに協力を仰ぐと皆さん快く引き受けてくださりました。一人ではなくチームで改善を進めていくことにしました。 一方、チームで進めていくと新たな困難に当たります。それは、それぞれ専門分野や経験してきた領域の異なるメンバー間での喧々諤々の議論が起こり、議論が思うように進まないということです。そこで、個々人がIOWN構想に対する認識や価値観・思考方法に違いがあることに気づきました。 ですが、相手の知識や専門用語を知りそれを使って理解し合うことで、新たな気づきを得て手段の選択肢の幅を広げることができるのではないかと可能性を感じました。 ブレイクスルーの方法 立場や専門性の違いを強みにする IOWN全体で見ると情報が複雑で難しいと感じてしまいますが、各々の専門分野に関することであれば、情報が整理されていて要点が明確になっていると考えました。IOWN推進室には「光電融合の技術やデバイスの観点」「ネットワークサービスとしての観点」「コンピューティングの発展経緯としての観点」「戦略マーケティングとしての観点」「研究開発としての観点」を持つメンバーがいてそれぞれの観点からお話ししていただきました。 そして、お話を聞く中で「どういう方向性にするか」「どこを深掘りしていくか」「どこを重要視したらいいか」整理していきました。そして、議論の中で得た新しい視点と自分の専門分野の知識を重ねてIOWN構想に対する知識を深め、気づいたことをアウトプットする。この営みを何週も繰り返しました。すると、次第に情報が整理されたスライドを形作れるようになりました。 なぜその方法にたどり着いたのか? 「 サピア=ウォーフ仮説 」と「 非ゼロ和ゲーム 」というキーワードからこの方法に辿り着きました。 「サピア=ウォーフ仮説」は、人は言語を通してのみ思考するのだから、異なる言語を使えば認識する世界観が変わるという考え方。一方「非ゼロ和」は、複数の人が相互に影響しあう状況の中で、ある1人の利益が必ずしも他の誰かの損失にならないこと・状況のことです。 「サピア=ウォーフ仮説」は、言語に着目していますが、同様に専門性の違いがあれば認識する世界観が変わるのではないかと考えました。 この「サピア=ウォーフ仮説」と「非ゼロ和ゲーム」という2つのキーワードから、複雑な事業を理解・伝えていくためには、専門性の違いで分断するのではなく互いの分野を絶えず行き来して相互に影響し合い、チームとして複数の視点を持つことが重要なのだと感じました。 試した結果、周囲の反応はどうだったのか? 作成したスライド資料を運用し始めると、運用前と比べて以下の変化が起きました。 資料請求が増加した 資料を活用した説明が容易になった 社内外からわかりやすいとお声がけいただくことが増えた 営業さんからの引き合いが増え、共創支援等関係性構築につながった スライド資料が、チームを超え他部門・社外とのハブになってきていると感じています。 また、資料作成のプロセスを通じて「別の案件を一緒にやりたい」や「アドバイスが欲しい」とチームメンバーからお声がけいただくことが増えました。 まとめ 今回取り上げた資料改善の事例を通して、 自分の「枠」にとどまらずチームとして複数の視点を持って新たな価値観を得ることで、理解の深化やわかりやすく伝えることにつながる というお話しをしました。 前例のない複雑な事業に必要なことは、既成概念を取り払いイノベーションを起こしていくということだと思います。自分の意思を曲げて誰かの意思に従うことなく、不協和音に立ち向かう。しかし、争うのではなく互いの視点を絶えず行き来し、妥協するのではなく相互に影響しあって組織力を向上するということは一つの解決手段ではないでしょうか。 自身の展望 私の長期的な目標は、「今は使う人を選ぶ先端技術を、誰もが使える公共性の高いサービスに活用して、世の中の当たり前にしていくこと」です。 IOWNの社会実装で貧富の差を生み出すことなく、誰もが平等に享受できるものにしていけるように、さまざまな「枠」を超え、世の中の潜在的な課題に目を向けていきたいと思います。 IOWN構想について ところで、IOWNってなんだろう?そう思った方いらっしゃいますよね。 IOWN構想について少しだけお話しします。 IOWN構想とは、ひと言で言うと「現在のネットワークインフラでは実現できない、新たな世界・豊かな社会を実現する革新的な取り組み」です。 IOWNはその名の通り 革新的な光と無線のネットワーク で、 光電融合技術 と 光通信技術 を用いて 大容量・低遅延・低消費電力 を実現する、 次世代通信・コンピューティングプラットフォーム です。(かなり砕けた表現をすると、「とっても早くて、一度にたくさん情報が送れるのに、かなり省エネ」なすごい技術です) 現代は市場の競争が激化し技術の進歩が早まり、ビジネスは日々変化を求められます。さらに、経済や政治の不安定さ、自然災害などのリスクも存在しています。こうした現状には、従来の思想や方法・技術だけでは対応ができなくなってしまいました。 想定外なことが起きてもしなやかに対応し豊かな社会を実現するために、NTTを中心として世界の各企業と一緒に「IOWN構想」を推進しています。 5 IOWN推進室について NTTコミュニケーションズのイノベーションセンターに属するIOWN推進室は、2023年4月にイノベーションセンターに設立されました。 IOWNの早期社会実装に向けて、以下の3つに取り組んでいます。 1. IOWNに関する技術開発・検証 2. 認知度向上・案件化に向けたプロモーション戦略の立案/実行 3. 実証実験の推進 私の所属するチームでは、認知度向上・案件化に向けたプロモーション戦略の立案/実行を担っております。 みなさんに「IOWNを使えば今抱えている課題を解決できる!」と思っていただき、IOWNで世の中を豊かにするビジネスを一緒に作り上げていきたい!その一心で活動しています。 6 今年は、docomo business Forum’23 7 やOPEN HUB Park 8 で体験型の展示を設計し、みなさんに「IOWNが拓く未来の世界」をご体感いただきました。 宣伝 IOWN推進室では、IOWN構想の早期社会実装に向けて共に尽力するメンバーを募集しています! IOWNやIOWN推進室の取り組みにご興味持ってくださった方がいたら、ぜひ一度お話ししましょう! IOWN構想展開におけるストラテジー・プロフェッショナル 光伝送ネットワークインフラおよびサービス開発エンジニア IOWN/APN(All Photonic Networks)を支えるネットワークエンジニアIOWN/APN(All Photonic Networks)を支えるネットワークエンジニア 最後まで、ご覧頂きありがとうございました! それでは、明日の記事もお楽しみに! 「IOWN®」は、日本電信電話株式会社の商標又は登録商標です。 ↩ IOWN構想(アイオン:Innovative Optical and Wireless Network)とは、あらゆる情報を基に個と全体との最適化を図り、光を中心とした革新的技術を活用し、高速大容量通信ならびに膨大な計算リソースなどを提供可能な、端末を含むネットワーク・情報処理基盤の構想です。 ↩ イノベーションセンターのValues ↩ 認知負荷とは、読み手が情報を理解する際にかかる精神的なエネルギー量のこと ↩ 「IOWNのこと、もっと知りたいよ〜!」って思ってくださったらぜひ。 OPEN HUB RADIO 「IOWN特集」をお聞きください。IOWN推進室の羽石さんがIOWNについてお話ししています。 ↩ その他IOWN推進室の取り組みは、 こちら で紹介しています。 ↩ docomo business Forum :ドコモグループの法人事業ブランド「ドコモビジネス」のもと事業を展開するNTTコミュニケーションズが、最新テクノロジーとユースケースを紹介するイベント。 ↩ OPEN HUB : 社会課題を解決し、わたしたちが豊かで幸せになる未来を実現するための新たなコンセプトを創り、社会実装を目指す事業共創の場。各領域に精通した専門家であるカタリストやパートナー企業に所属する「人」とともに、多様なアイデアや最先端の「技」を組み合わせて、リアルに、時にバーチャルな「場」で思考を重ね、ビジネス課題の解決に向けた取り組みを行っている。 ↩
アバター
イノベーションセンターの三島と深川です。 普段の業務では、Segment Routing を始めとする経路制御技術や、IPFIX や Streaming Telemetry などの監視技術の検証・運用、高速ソフトウェアルーター「 Kamuee 」の開発をしています。 我々は 2023/11/04-10 に行われた IETF 118 Prague へ参加しました。 この記事では、IETF 118 の参加報告として、主に Hackathon での成果と各 WG の動向などをご紹介します。 (出典: https://www.ietf.org/) IETF の概要や IETF 117 については IETF117 参加報告とおもしろワーキンググループ紹介 をご覧ください。 IETF 118 参加報告 以下では、我々が現地で参加した IETF meeting の内容をご紹介します。 IETF 118 の全スケジュールは IETF 118 Meeting Agenda を参照ください。また、IETF の onsite meeting ならではの用語は Guide to IETF Meetings で紹介されています。 Day1-2: Hackathon IETF meeting では、本会議である Meeting Week に先んじて2日間の Hackathon が開催されます。 IETF Hackathon は、集まった多くの開発者や専門家が、アイデアや便利なツール、標準化にあたっての running code 開発、相互接続性を検証するための場です。 IETF Hackathon では、各自が自由なテーマを設定し、開発に取り組むことができます。 テーマは事前に Hackathon Wiki で募集することもできますし、現地で新たにテーマを定めて開発することもできます。 我々は IETF 116 Hackathon 以降、SRv6 ネットワークにおける情報取得や高度な Traffic Engineering (TE) を目的とし、IPFIX exporter を開発しています。 それぞれの技術は、Operations and Management Area Working Group (opsawg) にて標準化が行われているため、Hackathon では、opsawg の I-D 著者の方々と連携しながら、パラメータの調整や相互接続試験を実施しています。 我々は IETF 116 Hackathon において、汎用の Linux ルーター上でのアイデアの迅速な実装を目的とし、eBPF/XDP ベースの IPFIX exporter として「Fluvia Exporter」を開発しました。 Fluvia Exporter は NTT Com より OSS として公開しています( リポジトリ )。 より詳しい開発背景や設計・実装手法については、 ENOG79 SRv6 対応の IPFIX exporter を開発した話 をご覧ください。 IETF 117 Hackathon では、IPFIX を用いた SRv6 フローの情報取得を目的とし、 Export of Segment Routing over IPv6 Information in IP Flow Information Export (IPFIX) の実装と nfacctd・WireShark との相互接続を実現しました。 この実装により、Segment List や Segment Left などによる SRv6 フローの識別や追跡が可能となりました。 IETF 118 では、「 IPFIX On-Path Telemetry with SRv6 」というテーマを主催しました。 このテーマでは、SRv6 フローの hop-by-hop な遅延メトリックの取得を目的とし、 Export of On-Path Delay in IPFIX の各機能を開発します。 Hackathon 開始時点では、2 つの exporter(FD.io VPP・Huawei VRP)と、collector(nmacctd)の実装が存在していました。 今回我々は、以下の2つのモチベーションで Hackathon に取り組みました。 1つめは、Fluvia Exporter への on-path delay の Information Element (IE) の実装、2つめは、WireShark dissector の実装です。 この2つの実施により、SRv6 ネットワークにおける on-path delay の取得と、取得したパケットのプロトコル解析が可能になります。 Fluvia Exporter への IE 実装 Fluvia Exporter に対し、今回対象となる IE を実装します。 実装したコードは こちら です。 実装対象である IE の Element ID は、IANA での割り当て待ち(TBD)となっているため、今回は相互接続用に仮に定めた番号を用いて実装し、OSS の IPFIX collector である nfacct の既存実装との相互接続を目指します。 今回実装する on-path delay 取得の仕組みを図に示します。 on-path delay では、遅延の取得のため、IPv6 の拡張ヘッダである hop-by-hop options header で Encapsulation された In-situ OAM (IOAM) と呼ばれるプロトコルを利用し、パケットに送出時のタイムスタンプを埋め込みます。 そのタイムスタンプと、各ルーターでのパケット受信時のタイムスタンプの差を取得することで、図の D1/D2/D3 のような送信遅延を取得できます。 (ごく短いタイムスタンプの差を取る関係上、機器間で PTP 等での高精度な時刻同期が行われていることが前提となります。) on-path delay を取得するための IPFIX exporter のパイプラインを上図に示しました。 パイプラインは、capturer・meter・exporter の3つのコンポーネントから構成されます。 capturer: パケットを取得するコンポーネント。IOAM timestamp を取得し、受信時のタイムスタンプと共に meter に送信する meter: パケットを処理し、IPFIX data を生成するコンポーネント。IOAM で付与されたタイムスタンプと機器のシステムクロックのタイムスタンプの差から送信時の遅延を算出し、SRv6 SRH の情報と共に IPFIX data を生成する。 exporter: IPFIX packet を生成し、collector に送信するコンポーネント。 Fluvia Exporter のアーキテクチャを上図に示しました。 Fluvia Exporter は eBPF/XDP ベースに実装されており、eBPF プログラムとして動作する capturer によりパケットを取得し、User land の meter/exporter で IPFIX パケットを生成します。IPFIX のパケットフォーマットは Go のライブラリとして実装しています。 今回は、capturer への IOAM パケットの取得機構の実装・meter へのタイムスタンプ計算処理の実装・IPFIX ライブラリへの IE の実装を行いました。 上図に、実装した IPFIX ライブラリの例を示しています。 相互接続用の仮の Element ID として、521・523・525・527 の4種を実装しています。(注: 522・524・526・528 として実施済みの nanoseconds は draft-ietf-opsawg-ipfix-on-path-telemetry-01 にて廃止されたため、Fluvia Exporter からも今後削除予定です。) それぞれの IE のデータ構造と処理を IPFIX ライブラリに定義します。 その他、今回の PR には eBPF によるパケットの取得や、タイムスタンプの計算処理なども含まれているので、もし興味があれば是非読んでみてください。 上図の通り、nfacctd との相互接続を確認しました。 ログから、すでに実装されていた SRv6 SRH の Element ID (495・498・492・493・496) と、今回実装した Element ID (521・523・525・527) がそれぞれ確認できます。 これにより、Fluvia Exporter に on-path delay の IE が正しく実装できたことが確かめられました。 WireShark dissector の実装 WireShark は、GUI のネットワークプロトコル解析ツール(network protocol analyzer)です。 リアルタイムに取得したパケットや pcap ファイルに記録したパケットの解析ができます。 WireShark は多くのネットワークプロトコルに対応していますが、今回対象とする I-D のように、標準化中などの最新プロトコルには未実装なものも存在します。 そのような場合は、dissector と呼ばれるパケットの解析プラグイン追加することで、新たなプロトコルを扱えるようになります。 Hackathon にて実装したコードは こちら です。 IPFIX 自体の dissector はすでに存在しているため、今回必要となる IE に関するコードのみを、I-D で定義されたデータ型を参考に実装しています。 dissector を追記実装した後、WireShark をビルドし実行しました。図の通り、今回実装した on-path delay に関する IE が表示されています。 これにより、Fluvia Exporter への IE 実装と、WireShark dissector 実装をそれぞれ完了し、動作確認を実施できました。 最終発表 IETF Hackathon 2日目の後半2時間(14:00-16:00)には、それぞれのチームによる最終発表が行われます。 発表の希望者は、 IETF Hackathon の GitHub organization に参加し、締め切りまでに IETF Hackathon 118 のリポジトリ に資料を投稿することで最終発表にエントリーできます。 最終発表では、Hackathon で達成した内容や成果を共有できました。 資料は こちら です。 発表は開発時間が終わった直後に始まるため、開発と並行して資料を用意する必要があり少し大変でしたが、とても良い経験となりました! Hackathon を通じて得られたことと今後について Fluvia Exporter の実装を I-D の Implementation 例として追記していただきました! 今後は IANA による Element ID の払出し後に Fluvia Exporter の実装を修正するとともに WireShark の実装も修正し PR を提出しようと思います。 Hackathon を通じて得られた経験としては、前回に引き続き、I-D 著者の方々と事前のメールや現地会場での交流ができました。 その中で、I-D に関連する取り組みを教えていただけたと共に、I-D に関する勘違いを指摘して頂き、実装をより良いものに修正できました。 また、Hackathon Wiki にて募集をかけたことで、他の参加者から取り組みについてメールで質問をいただき、現地にて交流できました。 この開発により、当初目標としていた、SRv6 網の IPFIX による解析と、hop-by-hop の遅延を得ることがそれぞれ達成できました。 次回以降の Hackathon では、現在 執筆中の SRv6 SFC の I-D に関する実装や、IPv6 に関する別の I-D の実装などを検討しています。 IETF Hackathon は、I-D の著者や機器の開発者など、最先端のスキルを持つエンジニアと交流し、議論ができる貴重な機会です。今後も是非活動を続けていければと考えています。 Day3-7: WG session Day3 以降の Meeting Week では、それぞれの WG session が開催されます。 他の WG session を含め、全てのスライドや I-D などの資料は IETF 118 の Agenda ページ から確認できます。 以降では、我々が参加した WG session のうち、SR に関連して特に面白かったテーマをいくつかご紹介します。 Source Packet Routing in Networking (spring) spring WG は、セグメントルーティング (Segment Routing: SR) と呼ばれる TE 技術の標準化と拡張を目的とする WG です。 SRv6 compression・ICMP による SRH 情報の報告・SRv6 MUP・SR Policy Candidate Path の検証などのトピックが扱われていました。 その中でも、ICMP の SR Policy 拡張は、SR 網のトレーサビリティを高め、トラブルシューティングに役立てることのできる面白いトピックです。提案では、SID の Endpoint behavior や VPN prefix、Flex-Algorithm なども解析できるような提案がされていました。 ICMP は任意の宛先に対してアクティブな検証ができますが、同じくアクティブに検証する TWAMP や、実トラフィックを用いてパッシブに解析する IPFIX との機能の差なども含め、利点を考慮すべきテーマだと考えています。 Internet Area Working Group (intarea) intarea WG は、インターネット全体に影響するトピックを議論するための WG です。 アドレス空間・IP レイヤーの機能などのテーマが扱わます。 この枠では、Trusted Domain SRv6 (draft-raviolli-intarea-trusted-domain-srv6-02) についての発表が行われていました。 この I-D は、外部からの不正な SRv6 パケットによる攻撃(DoS の送信元詐称や悪意のあるループ等)を防止するため、 trusted domain という概念を導入しています。 trusted domain では、現行の MPLS のように、あるプロトコルはデフォルトでは drop され、明示的に有効化されたインターフェースでのみ転送が行われます。 trusted domain の実装として、本 I-D では SRv6 専用の ether type を割り当てることで、一般の IPv6 パケットと識別可能にすることを提案しています。 一方、ether type によるアプローチは SRv6 が持つ IPv6 ネットワーク上に部分的に組み込むことができる利点を妨げるものであり、SRv6 の自由度を下げることが懸念されます。 ether type によるアプローチでは、SR-MPLS 同様に、明示的に有効化するよう設定されたネットワークでしか SRv6 を活用できなくなります。 SR-MPLS と比べた利点として、SID が持つ Function/Argument 領域や Prefix Aggregation、Path Tracing などは残りますが、SRv6 が持つ IPv6 ネットワーク上に部分的に組み込むことができる利点を妨げるものであり、SRv6 の自由度を下げることが懸念されます。 SRv6 の routing security は、今後 SRv6 が広く用いられる技術となる際に重要となる観点です。 一方、security のトレードオフとして技術の持つ利点が大きく損なわれることは望ましくないため、より良いアプローチや SRv6 に求める利点について、我々も引き続き注目し検討していきたいと考えています。 Side Meeting 「 Segment Routing Deployment and Operation discussion 」という Public Side Meeting に参加し、さまざまな SRv6 の運用者による発表を聴講できました。 テーマとしては、uSID の利点や SRv6 の Multi-domain での活用、EANTC での相互接続検証の成果、データ活用基盤( 前回の記事で紹介した取り組み )などが扱われており、各社の利用状況や実装などを知ることができました。 参加してみての感想と今後について IETF 118 では、IETF 116 から行っていた Hackathon の目標を達成すると共に、SRv6 に関する標準化動向を調べることができました。 Hackathon のまとめでも触れましたが、今後は執筆中の I-D を提出し、標準化を目指して活動を進める予定です。 次回、IETF 119 Brisbane の参加登録は 12/04 より開始済みです。 Super Early Birds の締切は 01/29 23:59 (UTC) なので、参加される方は忘れずにご登録ください。 ( IETF 119 Register ) JANOG 53 にて、IETF 118 の参加報告を実施予定です。もし興味があれば、こちらも是非ご参加ください! 若人が1年間に渡って IETF における標準化活動に関わってきた話
アバター
本記事では、学び続けるエンジニアを育成するための取り組みである、twadaラボという取り組みを紹介します。まず既存の研修では対応できない育成上の課題を示し、それを踏まえたtwadaラボのコンセプトや実施内容を説明します。 はじめに 背景 コンセプト 実施内容 学習計画の策定 学習 技術顧問によるメンタリング アウトプットとフィードバック テーマ例 終わりに はじめに NTTコミュニケーションズでソフトウェアエンジニアをしている 川瀬 です。 NTT Comでは2023年の6月から9月にかけて、技術顧問の twada さんとともにtwadaラボというソフトウェアエンジニア育成のための取り組みを実施しました。 本記事では、その背景や取り組み内容を紹介いたします。 背景 NTT Comでは、 MOOC を活用した独学支援から、twada塾やテスト駆動開発(TDD)ワークショップといったWebアプリケーション開発の基礎を身につけることのできる研修、サービス内製開発チームでのOJTなど、すでにさまざまな育成施策があります。これらを活用することで、単なる机上の学習にとどまらず、得た知見を活かす業務経験を積むことができます。 しかしながら、これら既存の研修では対応しきれない課題も存在します。 既存の研修や業務で獲得できる技術の幅や深さは、限定的である 既存の研修で内製開発の基礎を学ぶことができるものの、業務で必要になる技術全てをカバーしているわけではありません。それらがシラバスに含まれた研修がなければ、独学で学んでいかなければなりません。加えて、もし仮に業務上十分な技術を習得できたとしても、同じ業務を続けていくだけでは、そこからさらに専門性を高めることは難しいでしょう。業務において活用している技術によって自身の専門性が頭打ちになり、成長している実感が湧かなくなってしまうのです。 技術研鑽のための時間が不足している 業務に必要な技術研鑽は業務時間内に行うべきであるという指摘はもっともです。しかし、実際に業務時間内に技術研鑽のための時間を割く余裕が、常にあるわけではありません。「今すぐやらなければならないタスク」から「緊急性が低いものの重要なタスク」まで、すべきことはいくらでもあります。それらに日々追われる中で、さらに業務時間内に独学の時間を設けるのは、現実的には難しいのではないでしょうか。 学習を継続しづらく、学習効率も良くない 独学では、わからないことやうまくいかないことに直面したとしても、自らの力で解決しなければなりません。また、利用する学習リソース(本や動画等)の調査も自ら行わなければならず、その内容が古かったり誤っていたりすることがあるでしょう。人に頼らず学習を進めていくことにより、困難に直面した時にモチベーションが低下したり、学習の効率が低下してしまいます。 課題の1で述べたように、個々のエンジニアが求める専門性は多岐に渡りながら、日々変化もしています。その全てについて研修を用意することは現実的には不可能です。よって、従来のように特定の技術習得を目的とした研修を新たに立ち上げても、これらの課題は解決できません。そこでNTT Comでは、以下のコンセプトで新たな研修を立ち上げました。 コンセプト 新たに立ち上げた研修「twadaラボ」では、以下の3点を特徴としています。 学び方を学ぶ エンジニアが習得すべき技術は幅広く、それら技術は日々進化し続けています。よって研修終了後も自ら継続的に学んでいかなければなりません。本研修で学び方を学ぶことで、本研修後も効果的な学びを継続できるエンジニアの育成につなげます。 学習を継続しやすい仕組みを取り入れる 学習のモチベーションを維持向上させるため、研修内では頻繁に自ら学んだ内容に関する発表の場を設けています。これにより、他の参加者の発表から刺激を受けつつ、締め切り効果が発揮される環境の中、高いモチベーションで学習を進めることができます。 業務に繋がる学習をする研修として、学習時間を確保する 研修として学習時間を確保することで、期間中参加者がスキルや知識の獲得に集中できる場をつくります。研修において学習する目的は、参加者個々が業務につながる技術領域を踏まえて自ら決定します。こうすることで、担当業務へ還元できることはもちろん、研修期間中に所属チームからのサポートを得られやすくなり、学習時間を確保しやすくなることも期待できます。 実施内容 前述したコンセプトをもとに、twadaラボ参加者は以下のようなことを行いました。 学習計画の策定 自身の現状を分析し、学習目標を踏まえて学習計画を作ります。これは事前課題として各参加者が作成し、学習を進めながら随時アップデートしていきます。これにより、自己調整学習を実践していくための能力を身につけていきます。 学習 参加者は学習に専念する時間として1日/週程度確保し、学習計画に沿って自身の学習を進めていきます。後述する通り、週に1回アウトプットの機会があるため、それを念頭におきながら学習を進めていくことで、学習効果の向上を目指します。 技術顧問によるメンタリング 技術顧問であるtwadaさんとの1on1を、20分/週程度行います。この場で、効果的に学習目標に至るためのアドバイスを受けたり、自身が学習途中で直面した困難について相談したりできます。 アウトプットとフィードバック 週に1回、学習や技術顧問によるメンタリングとは別に、参加者全体に向けた発表の時間を設けています。これにより、自ら学習を継続するモチベーションを維持しながら、参加者間での知見やアイデアを共有できます。期間中最後の発表の時間は最終発表会として、期間中各々が学んだことの総まとめを披露します。 テーマ例 2023年の6月から9月に開催したtwadaラボの参加者は、それぞれ以下のようなテーマでラボの活動に取り組みました。 複数サービス間でのデータの整合性維持に向けたSagaの実装 ソフトウェア開発におけるサプライチェーンセキュリティの実践 ChatGPT と Whisper で発音練習アプリを作ってみた Azure OpenAI Service と LangChain を用いて、会話をしながら自社サービスに API を実行してくれるチャットボットを作った twadaラボでは、参加者自身の業務とつながりうるテーマを選択することを推奨しています。参加者の好奇心を満たしながら、エンジニアとしてのスキルアップと業務への還元の両立を狙っています。 終わりに 本記事では、学び続けるエンジニアを育てるための新たな取り組みである、twadaラボを紹介しました。twadaラボでは、参加者自身が自ら深掘りしたいテーマを定め探求していくことで、専門性を高めつつ学び方そのものを習得していきます。 NTT Comでは今後も、エンジニアのスキルアップを支援するさまざまな施策を行なっていきます。twadaラボもその1つとして継続的に開催し、学び続けるエンジニアをより多く輩出することを目指します。 NTT Comでは現在、スキルアップに積極的なソフトウェアエンジニアを募集しております! 今回紹介したテーマ例に関連するポストは以下の通りです。 NTTグループ向けマイクロサービス基盤(フレームワーク)の設計・開発、ソフトウエアエンジニア クラウドパートナーとのアライアンスによるモード2向けクラウドサービスの企画/開発 大規模言語モデルおよび生成AIを活用してプロダクトやソリューション開発、社内DXに貢献できるソフトウェアエンジニア Threat Intelligence Analyst / 脅威インテリジェンスアナリスト Threat Intelligence Engineer / 脅威インテリジェンスエンジニア 興味を持たれた方はぜひ応募してみてください。
アバター
この記事は、 NTTコミュニケーションズ Advent Calendar 2023  20日目の記事です。 はじめに こんにちは。 コミュニケーション&アプリケーションサービス部の吉仲です。 新卒2年目で、普段はB向け/C向けメールシステムと文書要約APIサービスの開発・運用に関する業務に取り組んでいます。 今回は、昨年から引き続き話題の生成AIのひとつ、大規模言語モデル (LLM: Large Language Model) を題材に、LLMを使って文章を「やさしい」表現へ言い換える例を紹介します。 この記事の内容 この記事では、以下の内容を扱います。 やさしい日本語 言い換え技術とテキスト平易化 LLMを使ったやさしい日本語への言い換え 前半にやさしい日本語、言い換え技術・テキスト平易化について簡単に解説し、後半はLLMによるやさしい日本語への言い換えの例を紹介します。 なお、この記事ではLLMの技術的詳細やプロンプトエンジニアリングの細かいテクニックには触れません。 はじめに この記事の内容 なぜ「やさしい日本語」を使うのか? 言い換え技術とテキスト平易化 言い換え技術 テキスト平易化 LLMでやさしい日本語へ言い換える Zero-Shotプロンプト Few-Shotプロンプト ガイドラインを活用したプロンプト LangChain Chainsを使った二段階の言い換え おわりに なぜ「やさしい日本語」を使うのか? 厚生労働省によると、令和4年10月時点の外国人労働者の数は180万人を超え、過去最高を更新しています 1 。 今後も労働人口の減少やグローバル化を背景に、日本に住む外国人はますます増え、その国籍もさらに多様化していくと考えられます。 日本に住む (あるいは訪れる) 外国人へ正しく情報を伝えるためには、多言語翻訳・通訳のほか、 やさしい日本語 での情報提供も有効な手段となります 2 。 また、在留外国人に限らず、より多くの人に伝わりやすい日本語表現を使うことが、ユニバーサルコミュニケーション 3 へのアプローチにもなると考えます。 やさしい日本語の書き方や話し方について、出入国在留管理庁と文化庁がガイドラインを作成しています。 www.bunka.go.jp 書き方のガイドラインでは、日本人に分かりやすい文章、そして外国人にも分かりやすい文章の作り方のポイントが紹介されています。 テキストでのコミュニケーションが一層増えた昨今、このガイドラインを読みながら自身の文章の書き方を一度振り返ってみると良いかもしれません。 かく言う私も、普段の仕事の中で難しく回りくどい書き方やカタカナ語を多用してしまい、分かりやすさを意識できていないことが多いです。 やはりすぐにやさしい日本語を書き出すことは難しく、どうしても推敲に時間がかかってしまうと感じます。 そこで今回は、LLMを用いて文章を自動的にやさしい日本語へ言い換えることを試してみます。 なお、この記事ではあくまで文章の分かりやすさにフォーカスすることとします。 文書全体の読みやすい構成や展開には触れません。 言い換え技術とテキスト平易化 ここからは自然言語処理 (NLP: Natural Language Processing) の話になります。 言い換え技術 NLPでの重要な問題のひとつにテキストの持つ曖昧さがあります。 この曖昧さには以下の問題が含まれます。 多義性の問題:同じ言語表現でも意味が異なる 同義性の問題:(近似的に) 同じ意味でも言語表現が異なる 前者は例えば「多義性解消」といったキーワードなどで、NLPの黎明期から研究されています。 後者は主に 言い換え (Paraphrase) 技術として研究されています。 言い換え技術とは、以下の認識技術と生成技術のことをまとめて指します。 言い換え認識:意味が同じ言語表現かどうかを判定する 言い換え生成:ある言語表現から (意味を保ちながら) 別の表現に変換する 後者の言い換え生成では、ニューラル機械翻訳モデル 4 を使い、同じ言語の中で翻訳する手法が近年の主流です 5 。 さらには、報酬を同義性や文法性、目的の言い換えの指標 (例えば、簡単な表現にしたければ難易度) に基づき設計した強化学習の手法も存在します 6 。 テキスト平易化 テキスト平易化 (Text Simplification) は、言い換え生成タスクのひとつです。 テキスト平易化では、入力文の意味を保ちながら、難しい表現をより 易しい 表現へ言い換えます。 例えば、あるテキストの中の表現「捺印する」を「はんこを押す」という表現へ変換していきます。 最近では以下の論文のように、ChatGPTなどのLLMを用いてテキスト平易化タスクを解く手法 7 も研究されています。 arxiv.org この論文では、以下のようなZero-ShotもしくはFew-Shotプロンプト 8 でテキスト平易化を行っています。 (※例は論文内のZero-Shotプロンプトの図から引用) I want you to replace my complex sentence with simple sentence. Keep the meaning same, but make them simpler. Complex: His next work, Saturday, follows an especially eventful day in the life of a successful neurosurgeon. Simple: {Outputs} テキスト平易化に限る話ではないですが、LLMはごく少数の例 (今回は難しい文と易しい文のペア) を用意するだけで、 もっと言えば例を用意しなくとも、さまざまなタスクを解いてしまいます。 以降では、このLLMでのZero-Shot/Few-Shotプロンプトによって、やさしい日本語への言い換えを行います。 さらに、上記の「やさしい日本語」ガイドラインを活用し、 押さえるべきポイントをプロンプトに組み込むことで、より上手くやさしい日本語へ言い換えられないか模索してみます。 LLMでやさしい日本語へ言い換える 前置きが長くなりましたが、ここからが本題の「LLMによるやさしい日本語への言い換え」の話です。 今回は以下の環境・モデルを使用します。 Azure OpenAI GPT-4 (32kトークン) また、やさしい日本語への言い換え例として、前述のガイドラインの例文を引用させていただきます。 以降では、ガイドライン中の以下の例文をやさしい日本語へ言い換えてみます。 父母が共に外国籍の場合、出生届が受理されると、子供が生まれた日から60日の間は、 在留資格を有することなく住民票が作成されます。 子供が生まれた日から60日を超えて日本に滞在しようとする場合は、 子供が生まれた日から30日以内に最寄りの地方出入国在留管理官署において、 在留資格の取得申請をする必要があります。 Zero-Shotプロンプト まずは、Zero-Shotプロンプトでテキスト平易化を行います。 プロンプトは前述の論文を参考に以下のように設計します。 I want you to replace my complex sentence with simple and easy-to-read sentence in Japanese. Keep the meaning same, but make them simpler. Complex: """ {Input} """ Simple: 指示文は英語のまま 9 、読みやすさについての指示 ("easy-to-read sentence") と、日本語での言い換え指示 ("in Japanese") を追加しています。 結果は以下のようになりました。 (※実行環境はAzure AI Studio チャットプレイグラウンド) 「資格を有することなく」を「資格なしで」に言い換えるなど、表現の平易化はできていそうです。 一方で「60日を超え」が「60日以上」になったり、申請場所の情報が抜けたりなどが見られます。 もし情報提供などでこの出力を使うなら、やや情報の不正確さ・欠落が気になるかなと思いました。 Few-Shotプロンプト 次に、Few-Shotプロンプトでテキスト平易化を行います。 プロンプトはZero-Shotのときと同様のものを使います。 プロンプトに組み込む例として、再びガイドライン中の例文をお借りします。 結果は以下のようになりました。 Zero-Shotのときに見られた情報の不正確さ・欠落がやや緩和されつつ、やさしい表現になっていますね。 ただ、プロンプトに入れた例が1つだけなのもあり、箇条書きで読みやすくするような言い換えは再現できていません。 ガイドラインを活用したプロンプト ガイドラインに載っている言い換え例は以下のような文章です。 日本で生まれた子供が、 ・日本国籍を持たない ・出生後60日を超えて引き続き日本に滞在したい ときは、出生した日から30日以内に、地方出入国在留管理局で在留資格取得の申請を行う必要があります。 上で紹介したZero-Shot/Few-Shotプロンプトでは残念ながら再現できていません。 そこで、この例のような言い換えを目標に、ガイドライン中のアドバイスをプロンプトに組み込んでみます。 (ただし「父母が共に外国籍」→「子供が日本国籍を持たない」のような言い換えはさすがに難しいと思うので、可能な範囲での再現を目指します) プロンプトは以下のように設計します。 ガイドライン中のアドバイスを英語に訳して追加しています。 このプロンプトをテンプレートとし、Zero-Shot/Few-Shotでの言い換えを試します。 I want you to replace my complex sentence with simple and easy-to-read sentence. Keep the meaning same, but make them simpler in Japanese. Follow the instructions below; 1. organize what I want to say, 2. select and discard information, 3. keep each sentence short (only one thing to say in one sentence), 4. use bullet points if there are more than three things to say, 5. use no roundabout phrases. Complex: """ {Input} """ Simple: 結果は以下の通りです。 Zero-Shotプロンプトによる言い換え : Few-Shotプロンプトによる言い換え : 後者の結果は、ガイドライン中の言い換え例にいくらか近くなったのではないでしょうか。 もう少しプロンプトを作り込むとしたら、複数の前提条件を箇条書きするような指示を与えると良いかもしれません。 今回はここまでとしますが、元のべた書きの文章よりもかなり分かりやすくなったと思います。 LangChain Chainsを使った二段階の言い換え ここまで触れませんでしたが、ガイドラインには「外国人にも分かりやすい文章」のアドバイスも含まれます。 最後はこちらにも挑戦してみます。 外国人にも分かりやすい日本語へ言い換えるためのプロンプトは以下のように設計します。 日本語の非ネイティブ向けであることを明示し、ガイドライン中のアドバイスを指示に組み込んでいます。 I want you to replace my sentence with easy sentence for non-native of Japanese. Keep the meaning same, but make them easier in Japanese. Follow the instructions below; 1. use simple phrases, 2. note the amount of kanji and supplement Furigana of difficult kanji. Input: """ {Input} """ Output: 今回は最初から外国人にも分かりやすい文章を生成するのではなく、前章で生成した言い換えの結果をさらに言い換える形とします。 LangChain 10 のChainsのうち SimpleSequentialChain は、1つめの出力を2つめのプロンプトに渡すという処理を簡単に実装できます。 今回はそれほど複雑な処理ではありませんが、せっかくなのでこれを使用してみます。 実装には以下のライブラリを使用します。 langchain==0.0.345 openai==0.28 ソースコードは以下の通りです。 Azure OpenAIの各種情報や、Few-Shotプロンプトに使う例文、入力文は適宜埋めてください。 import os from langchain.chains import LLMChain, SimpleSequentialChain from langchain.chat_models import ChatOpenAI from langchain.prompts import PromptTemplate os.environ[ "OPENAI_API_TYPE" ] = "azure" os.environ[ "OPENAI_API_KEY" ] = "xxxxx" os.environ[ "OPENAI_API_BASE" ] = "https://your-resource-name.openai.azure.com/" os.environ[ "OPENAI_API_VERSION" ] = "2023-07-01-preview" os.environ[ "DEPLOYMENT_NAME" ] = "xxxxx" template_for_simple_ja = "xxxxx" # プロンプトのテンプレート、入力文の位置には`{input}`を入れる template_for_easy_ja = "xxxxx" # プロンプトのテンプレート、入力文の位置には`{input}`を入れる def main (): llm = ChatOpenAI( model_name= "gpt-4-32k" , model_kwargs={ "deployment_id" : os.environ[ "DEPLOYMENT_NAME" ]}, temperature= 0 , ) prompt_1 = PromptTemplate(input_variables=[ "input" ], template=template_for_simple_ja) chain_1 = LLMChain(llm=llm, prompt=prompt_1) prompt_2 = PromptTemplate(input_variables=[ "input" ], template=template_for_easy_ja) chain_2 = LLMChain(llm=llm, prompt=prompt_2) overall_chain = SimpleSequentialChain(chains=[chain_1, chain_2], verbose= True ) text = "xxxxx" # 言い換えたい入力文 print (overall_chain(text)) if __name__ == "__main__" : main() このプログラムの実行結果は以下のようになります。 $ python3 text-simplification.py > Entering new SimpleSequentialChain chain... """ 両親が外国籍の場合、子供が生まれたら、 ・60日間は在留資格なしで住民票が作成されます。 ・60日を超えて日本に滞在する場合、生まれてから30日以内に在留資格の申請が必要です。 ・申請は最寄りの地方出入国在留管理官署で行います。 """ """ お父さんとお母さんが外国の人だったら、赤ちゃんが生まれたら、 ・60日間は、ビザ(ざいりゅうしかく)がなくても、住んでいるところを登録(とうろく)できます。 ・60日より長く日本にいるなら、赤ちゃんが生まれてから30日以内にビザの申し込み(もうしこみ)が必要(ひつよう)です。 ・申し込みは、近くの出入国管理事務所(しゅつにゅうこくかんりじむしょ)でやります。 """ > Finished chain. {'input': '父母が共に外国籍の場合、出生届が受理されると、子供が生まれた日から60日の間は、在留資格を有することなく住民票が作成されます。子供が生まれた日から60日を超えて日本に滞在しようとする場合は、子供が生まれた日から30日以内に最寄りの地方出入国在留管理官署において、在留資格の取得申請をする必要があります。', 'output': '"""\nお父さんとお母さんが外国の人だったら、赤ちゃんが生まれたら、\n・60日間は、ビザ(ざいりゅうしかく)がなくても、住んでいるところを登録(とうろく)できます。\n・60日より長く日本にいるなら、赤ちゃんが生まれてから30日以内にビザの申し込み(もうしこみ)が必要(ひつよう)です。\n・申し込みは、近くの出入国管理事務所(しゅつにゅうこくかんりじむしょ)でやります。\n"""'} 私は日本語ネイティブのため、非ネイティブが分かりやすい表現になったかどうかは判断しづらいですが、ガイドライン中の例文には近くなったのではと思います。 ただ、「漢字の量に注意」という指示が「在留資格」→「ビザ」というやや異なる意味への単語の言い換えを誘発してしまっています。 例えば在留外国人向けの資料や案内を作成したいとき、AIによる多言語翻訳で対応すると、誤訳の修正などのコストが大きくなりがちだと思います。 一方、やさしい日本語への言い換えで対応するなら、上記の程度の間違いであれば修正コストも高くないのではと思います。 おわりに この記事では、LLMによってやさしい日本語へ言い換える例を紹介しました。 もしすぐにやさしい日本語で書くことが難しいと感じるなら、ぜひこの記事を参考にLLMを使った言い換えを試してみてください。 また、今回は定量評価まで行いませんでしたが、言い換え技術・テキスト平易化の評価手法も奥が深い研究分野だと思います。 気になった方はぜひ調べてみてください。 最後までご覧いただきありがとうございました!それでは、明日の記事もお楽しみに! 『 「外国人雇用状況」の届出状況まとめ(令和4年10月末現在) 』 (厚生労働省, 令和5年1月27日) ↩ 『 東京都在住外国人向け 情報伝達に関するヒアリング調査報告書 』によると、機械翻訳された母国語よりもやさしい日本語の方が好まれる傾向にある。 ↩ 『 「ユニバーサルコミュニケーションデザイン」(UCD)とは 』 (ユニバーサルコミュニケーションデザイン協会 (UCDA)) ↩ " Sequence to Sequence Learning with Neural Networks " (Sutskever et al., arXiv 2014) ↩ " Exploring Neural Text Simplification Models " (Nisioi et al., ACL 2017) ↩ " Text Simplification with Reinforcement Learning Using Supervised Rewards on Grammaticality, Meaning Preservation, and Simplicity " (Nakamachi et al., AACL 2020) ↩ " Sentence simplification via large language models " (Feng et al., arXiv 2023) ↩ " Language Models are Few-Shot Learners " (Brown et al., arXiv 2020) ↩ 生成の質向上 (経験的には英語のプロンプトの方が良い) とトークン数削減のため。 ↩ https://www.langchain.com/ ↩
アバター
この記事は、 NTT Communications Advent Calendar 2023 19日目の記事です。 この記事では、TypeScript未経験のインターン生にすぐにSkyWayの開発に取り組んでもらうために、TypeScriptの学習用コンテンツを作成した話を紹介します。 学習用コンテンツでどのようなスキルを身に着けてもらったのか、効果的に学ぶためにどのような点を工夫したのかについても説明します。 はじめに 学習用コンテンツの目的 TypeScript学習用コンテンツの紹介 取り組んでもらった結果 より高度な内容について おわりに はじめに 皆さまこんにちは。イノベーションセンター SkyWay DevOps プロジェクト所属の @sublimer です。 SkyWayのチームでは、今年の8〜9月に現場受け入れ型のインターンシップを実施しました。 インターン生を受け入れるにあたって、すぐにSkyWayの開発に取り組んでいただけるようにTypeScriptの学習用コンテンツを作ったので、本記事ではその紹介をします。 学習用コンテンツの目的 今回コンテンツを制作するにあたって、以下のスキルを学ぶことを目的として内容を検討しました。 また、インターンシップの期間は2週間で時間が限られているため、学んでほしい内容には優先順位を付けて、優先度が高いものから順に学んでもらうようにしました。具体的には下記の通りです。 TypeScriptでのサーバーサイドアプリケーション開発 ドキュメント作成 コードレビューのreviewee テストコード CI/CD セキュリティ SkyWayでは、サーバーサイドアプリケーションをTypeScriptで開発しているため、まずはTypeScriptでのサーバーサイドアプリケーション開発を学んでもらうことを目的としました。 また、普段の開発では仕様を考える際に草案をドキュメント化して議論したり、書いたコードをGitHub上でレビューしているため、これらの点についても経験できるようにしました。 テストコードやCI/CDについては、アプリケーションを作る上では必ずしも必要ではないのですが、商用のサービスを開発する上では必要となるスキルであるため、これらについても学べるようにしました。 また、セキュリティについても観点の1つとして意識できるようにしました。 TypeScript学習用コンテンツの紹介 今回は、「指定されたnpmパッケージをインストールしても大丈夫そうか判定するAPI」というお題で、アプリケーションを作ってもらうことにしました。 このアプリケーションを開発するためには、以下のような機能を実装する必要があります。 npmのAPIを利用して、指定されたnpmパッケージの情報を取得する GitHubのAPIを利用して、対応するGitHubリポジトリの情報を取得する GitHubのAPIを利用して、対応するGitHubリポジトリのownerの情報を取得する 以下のような基準を満たしているかどうかを判定して、その結果に応じてレスポンスを返す(基準はあくまでも一例) 直近1年以内に更新がなされているか(アクティブ度合い) インストール数が500以上あるか(利用実績の有無) コントリビューターが5人以上いるか(複数人がコードに目を通しているか) 実装にあたっては、SkyWayのサーバーサイドアプリケーションでも利用しているフレームワークである Fastify を利用してもらいました。 (余談ですが、 Fastifyを利用している組織の一覧 にSkyWayも載せていただきました。) SkyWayのシステムでは、あるアプリケーションから別のアプリケーションのAPIを呼び出す処理が複数あります。 そのため、APIを呼び出し、その結果を元に処理を行うという部分は、SkyWayの開発を体験する際に役立つと考えました。 npmやGitHubのAPIを利用するメリットは、レートリミットなどの制限はあるものの、今回のアプリケーションで利用する範囲においてはAPIキーなどの認証情報を用意する必要がないことです。 今回はインターンシップという限られた時間の中で開発をしてもらう都合上、アカウントや認証情報の準備が不要なAPIのみを利用して、開発ができるようにしました。 また、今回はAPI呼び出しの処理をクライアントライブラリを使わずに実装してもらうことにしました。 APIのレスポンスはJSON形式となっているため、TypeScriptでレスポンスのデータを扱う際は、型定義を用意する必要があります。 一例として、GitHubのリポジトリ情報を取得するAPIは、以下のドキュメントにあるようなデータを返します。 docs.github.com 実際のアプリケーションではこれらのデータのうち一部しか使わないので、必要なデータのみを抽出した、以下のような型定義を用意する必要があります。 type RepositoryInfo = { name: string ; full_name: string ; owner: { login: string ; id: number ; } ; stargazers_count: number ; created_at: string ; updated_at: string ; pushed_at: string ; } ; JSONのデータを元に型定義を作成することで、TypeScriptでは型による表現がどのようになっているのかを学べます。 ドキュメントの作成やコードレビューの観点についても、APIドキュメントを作成してもらったり、GitHub上でコードレビューをしてもらうことで、実際の開発に近い形で経験できるようにしました。 また、テストコードやCI/CDについても、取り組みやすいようにコンテンツを検討しました。 具体的には、「基準を満たしているかどうかを判定する」というロジックは複数の条件の組み合わせで判定する必要があるため、テストコードで挙動を担保する価値が大きい部分です。 特にこの部分について、テストコードを書いてもらうことで、テストコードの価値をより実感してもらえるのではないかと考えました。 加えて、外部のAPIを呼び出す箇所はモックを利用してテストを実行することになるため、どの部分をどのようにモックするかという観点についても学べるようにしました。 テストにおいて何をどこまでモックすべきかについては、 fukabori.fmの第13回が非常に参考になる のでおすすめです。 fukabori.fm さらに、セキュリティについても、直接ではないものの意識できるようにしています。 npm等のパッケージマネージャーを利用する際は、サプライチェーン攻撃などのセキュリティリスクについて意識する必要があります。 例えば、使おうと思ったパッケージが長い間メンテナンスされていなかったりインストール数が非常に少ない場合、脆弱性の対応が行われなかったり、悪意のあるコードが含まれてリリースされても誰も気づかない、といったリスクが想定されます。 そのため、今回のコンテンツでは、npmパッケージの情報を取得し、その情報を元に判定の処理を実装してもらうことで、npmなどのパッケージマネージャーを利用する際のセキュリティについても意識できるようにしました。 ソフトウェア開発のサプライチェーンセキュリティについては、NTT Communications Advent Calendar 2023 14日目の記事で詳しい解説をしていますので、こちらもぜひご覧ください。 engineers.ntt.com このアプリケーションの開発では以下のようなタスクを行うため、身につけてほしいスキルを効率的に学ぶことができます。 TypeScriptでのサーバーサイドアプリケーション開発(必須) TypeScriptの書き方の理解 Fastifyの使い方の理解 非同期処理(Promise、async/await)の扱い方の理解 APIの設計 ドキュメント作成(必須) コードレビュー(必須) テストコードの追加(ここまでできるとGood) GitHub ActionsによるCI(ここまでできるとGood) Cloud Runへのデプロイ(ここまでできるとGood) デプロイの自動化(できなくても大丈夫) 新機能の実装(できなくても大丈夫) タスクについては、多すぎても少なすぎてもつまらなくなってしまうと考え、多めに準備した上で「必須」「ここまでできるとGood」「できなくても大丈夫」にカテゴライズして、必須から先は進捗状況に応じて取り組んでもらうことにしました。 取り組んでもらった結果 今年の8月28日から9月8日にかけてインターンシップに参加してくださった鈴木さんに、実際にこちらのコンテンツに取り組んでいただきました。 鈴木さんはTypeScriptの経験は無いとのことでしたが、「できなくても大丈夫」のタスクまで実施していただき、良い意味で期待を裏切っていただきました。 そして、その後の商用環境で動いているコードの改善にもスムーズに取り組んでいただきました。 鈴木さんのインターンシップの取り組みは、以下のブログ記事に詳しく書かれているので、ぜひご覧ください。 engineers.ntt.com より高度な内容について 今回は、サーバーサイドアプリケーションの実装、テストコードの追加、CI/CDなどに取り組んでいただきましたが、このコンテンツはより発展的なものにできると考えています。 一例として、以下のような応用的な取り組みが考えられます。 OpenAPIなどのAPI定義を元にした、ドキュメントやクライアントライブラリの自動生成 OpenTelemetryやAPMなどを用いたアプリケーションのモニタリング&改善 RESTではなく、GraphQLを利用してみる 生成AIと組み合わせた判定処理の高度化 npm以外のPackage Registryへの対応 また、題材自体は言語やフレームワーク、クラウドサービスに依存する箇所は少ないため、GoやPython、Rustのような他の言語や、Azure、AWSといったGoogle Cloud以外のクラウドサービスを利用してもらうことも可能です。 もし、オンボーディング用のネタに困っている方がいれば、ぜひ参考にしてみてください。 おわりに SkyWayでのインターン生に取り組んでもらった、オンボーディング用のコンテンツについて紹介しました。 インターンは期間が限られているため、参加者のスキルと実際に業務に取り組むためのスキルの間にギャップがある場合は、できるだけ時間をかけずに必要なスキルを身に着けてもらう必要があります。 今回のコンテンツは人にもよるかとは思いますが、TypeScript未経験でも概ね2〜3日程度で一通り取り組めるボリュームになっています。 また、実際のサービス開発に近い形で取り組めるように、ドキュメント作成やコードレビュー、テストコードの追加やCI/CDなども取り入れています。 インターン生の受け入れに限らず、オンボーディングやプログラミング学習用のコンテンツを作成する際の参考になれば幸いです。 以上、 NTT Communications Advent Calendar 2023 19日目の記事でした。明日もお楽しみに!
アバター
この記事は、 NTT Communications Advent Calendar 2023 18日目の記事です。 はじめに この記事はCOTOHA Call Center開発チームの福田、立木、木村の共同執筆です。 この記事では、私たちが普段の開発業務の中で工夫している自動化関連の取り組みについて共有します。 私たちはCOTOHA Call Centerというサービスをスクラム手法で開発し、福田はスクラムマスター、立木と木村は開発者として参画しています。 COTOHA Call Centerの概要 COTOHA Call Centerは簡易なコールセンター機能を搭載したIP電話のサービスです。 IVRのカスタマイズや音声ガイダンス作成、AIオペレーター機能を実装しており、WEBブラウザ版とモバイル版をリリースしています。 基本的に全てJavaScript/TypeScriptで実装しており、WEBブラウザ版ではフロントエンドはVue.js、バックエンドはNode.js+Expressを採用しています。 また、モバイル版はReact Nativeを採用しており、基本的に単一のコードベースでiOS/Androidを同時に実装していますが、通話部分など一部の機能はネイティブ(Swift/Java)で実装しています。 自動化の背景 開発業務に携わっていると同じ内容の作業を何度も行うような機会が発生します。例えば検証環境への反映、システムの基本的な機能に関するテスト、モバイルアプリのテスターへの配布などがあります。スクラムのように短いリリースサイクルで開発する手法だと、そのような定型作業の頻度が高くなります。COTOHA Call CenterではWEBブラウザ版、iOS版、Android版を開発しており定型作業のボリュームが多いため自動化の取り組みを積極的に進めてきました。 自動化している作業は多くありますが、この記事ではモバイルに関する取り組み内容について共有します。 CI/CDの取り組み GitHub Actions まず、GitHub Actionsについて説明します。 GitHub Actionsは、GitHubが提供する自動化プラットフォームサービスです。リポジトリ内で事前にさまざまな「ワークフロー」を定義し、自動的にビルド、テスト、デプロイなどを実行し、開発作業の手間を減らすことができます。 Self-hosted Runner GitHub Actionsの実行環境には2種類あります。 GitHub-hosted Runner :GitHubが提供するクラウドの実行環境で実行する Self-hosted Runner :ユーザーが自分で準備したコンピュータやサーバーで実行する GitHub Actionsで通常使われるのはGitHub-hosted Runnerで、私たちも以前はこちらを使用していました。 一方で、(privateリポジトリが対象の場合)GitHub-hosted Runnerと比較したときにSelf-hosted Runnerには下記のメリットがあります。 内部ネットワーク上で安全に実行できる 従量課金がない 以上の理由から、Self-hosted Runnerを採用して環境を構築しました。 Self-hosted Runnerを使う際は、自身でサーバーを管理するため、ハードウェアのリソースを確保することや、外部アクセスの制限や通信の安全性に注意が必要です。 加えて、環境構築でつまづきやすいポイントと対処法を簡単に紹介しておきます。 Runnerの登録ができない GitHubのトークンやリポジトリのURLが正確であるか確認してみてください。私たちの場合トークン切れが原因でした。 RunnerがOfflineになる ./run.sh を実行しただけではターミナルを閉じたときに利用できなくなってしまいます。actions-runnerをインストールしたフォルダに移動し、 ./svc.sh install および ./svc.sh start コマンドをサービスとして実行することで起動し続けることが可能です。 使用用途 私たちは以下の用途でGitHub Actionsを利用しています。 これらの作業は、GitHub Actionsを活用することで、プッシュしたタイミングやプルリクエストをマージしたタイミングなど任意のタイミングで自動的に実行できます。 統合・単体テスト(jest) 統合テストおよび単体テストを行うテストフレームワークには主にjestを活用しています。 jestとはJavaScriptのテストフレームワークであり、私たちのプロジェクトではwebアプリとモバイルアプリ両方の開発で利用しています。特にReact Nativeではデフォルトで使用できるため導入の手間なく扱えます。 デプロイ 本番環境やステージング環境に自動でデプロイします。私たちの場合、開発者のプルリクエストをマージする際に、クラウド環境上のWEBサーバーとAPIサーバーに自動でデプロイするように設定しています。 テスト配布 テスト用のモバイルアプリを配布します。詳細は後述します。 テスト配布の自動化 COTOHA Call Centerではモバイルアプリに処理を追加、変更する度に動作確認用のテストアプリを配布しています。 モバイルアプリはiOS版とAndroid版を開発しており、開発業務が立て込んでいる時には1日に両OSに対して5回ほどテストアプリの配布をする時もあり、稼動削減のために自動化を行なっています。 単純な稼働削減のみではなく、設定ファイル誤りなどのヒューマンエラー対策にもなっており、動作確認時の手戻りも減らすことができました。 iOS版とAndroid版について、それぞれどのようにテスト配布を実装したかを説明します。 iOS版のテスト配布 iOS版のテスト配布は開発ブランチへのプルリクエストがマージされた時に実行するよう実装しています。 テスト配布処理の実現には以下のものを使用しています。 自動処理の起動:GitHub Actions アプリビルド:Xcode Cloud アプリ配布:TestFlight 実際の処理の流れは以下の通りです。 開発ブランチへのプルリクエストのマージ GitHub Actionsのワークフロー開始(Xcode Cloud上で実行) ビルドしたいブランチのチェックアウト Ruby、Nodeのセットアップ 環境変数の読み込み アプリのバージョンコード編集 アプリのビルド ビルドしたファイルをTestFlightへアップロード TestFlightがテスターへアプリ配布 実装作業としてはワークフローのyml作成とXcode Cloud設定のみで、WEB上にも参考事例が多いため導入しやすかったです。 手動で行うと手間と時間がかかる処理を自動化できるので導入効果はとても高いと感じています。 1点だけ導入時に注意しなければいけないことがあります。Xcode Cloudには処理時間のプランがあります。例えば100時間のプランであれば、Xcode Cloudに処理を実行させられる時間は100時間のみとなります。複数のチームでApple Developer Programを共有している場合はあっという間に上限時間に達してしまうこともありえます。 導入する時にはあらかじめ Xcode Cloudのプラン と、他のチームがどれくらいXcode Cloudを使い込んでいるか確認すると良いでしょう。 Android版のテスト配布 Android版のテスト配布はiOS版と同様に、開発ブランチへのプルリクエストがマージされた時に実行するよう実装しています。 テスト配布処理の実現には以下のものを使用しています。 自動処理の起動:GitHub Actions アプリビルド:Self-hosted Runners アプリ配布:Firebase App Distribution 実際の処理の流れは以下の通りです。 開発ブランチへのプルリクエストのマージ GitHub Actionsのワークフロー開始(Self-hosted Runners上で実行) ビルドしたいブランチのチェックアウト JDK、Android SDK、Nodeのセットアップ アプリのバージョンコード編集 環境変数の読み込み アプリのビルド ビルドしたアプリをFirebase App Distributionへアップロード Firebase App Distributionがテスターへアプリ配布 実装作業としてはワークフローのyml作成のみでiOS版より一手間少なくて楽でした。こちらもWEB上に多くの参考事例があります。 iOS版同様に導入効果はとても高いです。 処理時間の上限については リポジトリの種類とプラン によって変わってくるので、こちらも導入時には上限の確認と他のチームの利用状況を確認した方が良いでしょう。 モバイルアプリのテスト自動化 また、COTOHA Call Centerではモバイルアプリのテスト自動化にも取り組んでいます。 COTOHA Call Centerでは以下の2つのテストを導入しています。 E2Eテスト(MagicPod) Roboテスト E2Eテストは導入に向けた検証が終わり、今後本格導入していく予定です。 Roboテストはすでに実装しており、リグレッションテスト時などに実行しています。 それぞれの導入の背景や役割について説明していきます。 E2Eテスト(MagicPod) COTOHA Call Centerでは、E2Eテストとして MagicPod を導入することになりました。 MagicPodとは、GUIでモバイルアプリのE2Eテストを作成できるサービスで、内部では Appium というオープンソースのテスト自動化フレームワークを使用しています。 Appiumをそのまま用いる場合はテストコードを書く必要がありますが、MagicPodはノーコードでテストを作成できます。 本格的に導入する前に、事前の検証をすることになり、基本的なシナリオを書きました。 テストシナリオは、以下の通りです。 コールセンターアプリとしての基本機能を網羅したものになっています。 初回起動&ログイン キーパッド画面で発信 通話後、メモを保存し、通話終了処理 発着信履歴の内容確認 ログアウト後の表示確認 シナリオを作成し実行した感想としては、GUI上で簡単に操作するだけでテストが実行できるため、使いやすく便利だと感じました。 実は途中までAppiumを用いたテストの自動化を試みたのですが、他の機能開発もある中でなかなかモバイルアプリのテストの実装に時間をかけることができないという課題がありました。 その点、MagicPodはGUI上で操作するだけでテストが簡単に作成できるので、テスト作成にかける工数が大幅に減少しました。 逆に、少し手間がかかる部分としては、iOSとAndroidを同じシナリオでテストするために、アプリの実装の修正が必要な点です。 MagicPodのFAQページ にも記載されているのですが、同じテストシナリオでiOS/Android両方をテストするためには、各UI要素に共通のaccessibility idを割り振っておくなどの修正が必要となります。 地味に手間がかかる部分ではあリますが、これはAppiumで実装した時も手間は同じなのでそこまで問題にはならないと思います。 また、コールセンターアプリ特有の問題として、保留や取次保留など、複数のユーザーを必要とするケースがあります。 こういったケースでは一方が保留した際にもう一方で表示が変更されるかなど、複数の端末を用いて各端末の状態を考慮したテストシナリオを作成する必要があります。 まだ本格的に利用していないのですが、このような場合はMagicPod上で完全に自動化するのは難しそうな印象です。 (もし方法があるのであれば、ぜひ教えていただけると嬉しいです) ですが、メリットが大きく、総じて便利なサービスなので、今後本格的に導入することを決めました。 今後はシナリオ数を増やし、将来的にはモバイルアプリのテストの自動化率を高めていこうと考えています。 Roboテスト COTOHA Call Centerでは、Androidのテストとして Roboテスト も導入しています。 Roboテストとは、Googleのデータセンターでホストされているデバイス上で行われる、UI構造を自動で分析してランダムに動くテストのことです。 COTOHA Call Centerでは想定していない操作方法をした場合の確認やクラッシュの確認など、リグレッションテストの補助に使用しています。 また、Roboテストでは「Roboスクリプト」というものを記述することで、特定の動作をテスト中に実行できます。 COTOHA Call Centerではログインをしないと通話などの機能が使用できないため、ログインを最初に行うといった制御をしています。 おわりに このような形で私たちは自動化に取り組み続けています。今後も新しい自動化の取り組みに挑戦する機会があればまたブログの記事にしたいと考えています。 それでは、明日の記事もお楽しみに!
アバター
この記事は、  NTT Communications Advent Calendar 2023  16日目の記事です。 こんにちは! クラウド & ネットワークサービス部の外村です。 普段は VxF 基盤 という 社内サービス用クラウドの開発・運用をしつつ、ソフトウェアエンジニア育成研修である twada 塾 の研修運営をしています。 今回は自己研鑽と業務効率化を目的として大規模言語モデル (以下、LLM) を用いたチャットボットの開発に挑戦しました。 LLM を用いたアプリケーション開発に興味がある方や、LLM の選択肢として Azure OpenAI Service を検討されている方へ参考になればと思います。 本記事では以下の技術を中心に取り扱います。 Azure OpenAI Service LangChain を用いた Function calling (非同期処理) の実装 開発のきっかけと開発したもの Azure OpenAI Service Azure OpenAI Service API API の利用 Azure OpenAI Service API におけるリージョンごとのレスポンス時間の比較 Function calling LangChain LangChain の Tool と Agent を用いた Function calling の実装 Function calling で実行させる関数の定義 Tool の作成 Fuction calling を実行できる Agent の作成 チャット用 API の作成 アプリケーションの起動 おわりに 開発のきっかけと開発したもの 私が担当する VxF 基盤は Flexible InterConnect (以下、FIC) に直結しているクラウドなのですが、 開発や試験をするなかで FIC 上に仮想ルータを作成したりコネクションを作成したりすることがしばしばあります。 こういった作業を楽にできたらなという思いでチャットボットを作成しました。 API を通じて必要な情報を教えてあげると、FIC に対して API を実行し、リソース作成してくれます。 LLM として Azure OpenAI Service の GPT-4、アプリケーションフレームワークとして FastAPI と LangChain を用いました。 アプリケーションの全体像は以下のようなイメージです。 アプリケーションの動作結果は以下のようになりました。 $ curl -X POST -H " Content-Type: application/json " -d ' {"request_message":"こんにちは"} ' http:// 127 . 0 . 0 . 1 /v1/chat { " response_message " : " こんにちは!何かお手伝いできることがありますか? " } $ curl -X POST -H " Content-Type: application/json " -d ' {"request_message":"FIC でルータを作成するために必要なことを教えてください。"} ' http:// 127 . 0 . 0 . 1 /v1/chat { " response_message " : " FICルータを作成するためには以下の情報が必要です: 1. **名前(name)**:ルータの名前を指定します。これは一意である必要があります。 2. **ユーザーIPアドレス(userIpAddress)**:ルータのユーザーIPアドレスを指定します。例えば、 \" 192.168.0.0/27 \" のような形式で指定します。 3. **エリア(area)**:ルータが配置されるエリアを指定します。 \" JPEAST \" または \" JPWEST \" から選択します。 4. **冗長化(redundant)**:ルータの冗長化を指定します。デフォルトはTrue(冗長化あり)です。 5. **パブリックサービスエンドポイント(isPublicServiceEndpoint)**:パブリックサービスエンドポイントを使用するかどうかを指定します。デフォルトはFalse(使用しない)です。 これらの情報を元に、FICルータの作成リクエストを送信します。ただし、ルータの作成は非同期で行われ、完了までに時間がかかることをご了承ください。 " } $ curl -X POST -H " Content-Type: application/json " -d ' {"request_message":"それでは FIC ルータを作成してください"} ' http:// 127 . 0 . 0 . 1 /v1/chat { " response_message " : " もちろんです。FICルータの作成に必要な情報を教えていただけますか? 1. ルータの名前 2. ユーザーのIPアドレス 3. エリア(JPEAST または JPWEST) 4. 冗長化の有無(デフォルトはTrue) 5. パブリックサービスエンドポイントの有無(デフォルトはFalse) " } $ curl -X POST -H " Content-Type: application/json " -d ' {"request_message":"はい、以下の情報で FIC のルータを作成してください。名前は tono-test-router, エリアはJPEAST、ユーザIPは192.168.0.0/27 でお願いします。"} ' http:// 127 . 0 . 0 . 1 /v1/chat { " response_message " : " FICルータの作成を開始しました。ルータのIDはF022300001179です。作成が完了するまで少々お待ちください。 " } 以降の章では、それぞれの技術要素の詳細や実装の内容について説明していきたいと思います。 Azure OpenAI Service Azure OpenAI Service は、Microsoft Azure 上で OpenAI が提供する LLM を利用できるサービスです。 ChatGPT でも用いられている GPT-4 や GPT-3.5 (以下、まとめて GPT と呼びます) を Azure 上でデプロイし、 Azure OpenAI Studio や OpenAI API を用いて利用できます。 Azure OpenAI Service には次のような特徴があります。 Azure が提供する他のサービスとの連携が容易 Azure が提供するセキュリティ機能を利用可能 複数のリージョンにデプロイすることが可能 ChatGPT で発表された機能は順次 Azure 側でも利用可能になり、Function calling 機能や Fine-Tuning も利用可能です。 機能ごとに利用できるモデルバージョンや API バージョンがあるため、公式ドキュメントをチェックしましょう。 また、2023年11月時点では利用するリージョンによってはデプロイ可能なモデルに差分があったり、 需要増にともない一部機能が制限されていました。 Azure OpenAI Service API Azure OpenAI Service を利用した実装にあたり、 API call を同期処理にするか非同期処理にするか少し検討しておきたいと思います。 API の利用 Azure OpenAI Service の API は、OpenAI 公式のモジュールを使うことで利用できます。下記のコードで実行可能です。 Azure ではない OpenAI API との違いは openai.api_type や openai.api_base 、 engine といった指定が必要なことでしょうか。 api_version も Azure で提供されたバージョンにする必要があります。 import openai openai.api_type = "azure" # プラットフォームの宣言 openai.api_base = "https://test-azure-openai-deployment01.openai.azure.com/" # 作成した Azure OpenAI Service のエンドポイント openai.api_version = "2023-07-01-preview" # API バージョン openai.api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # api-key message_text = [ { "role" : "system" , "content" : "You are an AI assistant that helps people find information." , }, { "role" : "user" , "content" : "こんにちは、API とはなにか教えてください。" }, ] completion = openai.ChatCompletion.create( engine= "test-gpt35-turbo-deploy01" , # デプロイした GPT messages=message_text, temperature= 0 , max_tokens= 800 , top_p= 0.95 , frequency_penalty= 0 , presence_penalty= 0 , stop= None , ) print (completion[ "choices" ][ 0 ][ "message" ][ "content" ]) # 回答結果 Azure OpenAI Service API におけるリージョンごとのレスポンス時間の比較 上記のコードを用いて、各リージョンあるいは ChatGPT で提供されている GPT で API レスポンス時間に差があるのか 計測してみました。 比較対象として、Azure OpenAI Service 上の 3 リージョン(Japan East、US East、UK South) にデプロイした GPT と、 OpenAI で提供されている GPT を用いました。 全てのモデルは gpt-3.5-turbo (0613) を利用し、「こんにちは、API とはなにか教えてください。」というプロンプト (LLM に入力する文章) を5回なげたときのレスポンス時間 (秒) を表にまとめています。 Azure Japan East Azure US East Azure UK South OpenAI 1 回目 5.5 10.2 9.0 19.7 2 回目 4.1 4.3 9.9 18.1 3 回目 10.5 5.5 5.4 16.9 4 回目 5.3 3.7 4.8 13.0 5 回目 8.0 4.0 5.8 18.6 平均 6.7 5.5 7.0 17.3 上記の結果から以下のことが言えそうです。 どの GPT でも毎回の実行結果にばらつきがある Azure OpenAI Service のリージョン間ではおおきな差分はない Azure OpenAI Service では、どのリージョンでも 10秒程度レスポンスにかかることがある Azure OpenAI Service と ChatGPT では ChatGPT のほうがレスポンスに時間がかかっている 上記の結果から、GPT との通信期間中も他の処理が止まらないように、チャットボットは非同期処理を前提に開発する必要がありそうですね。 Function calling Function calling は OpenAI API で提供されている、 GPT を外部システムと連携させることができる機能です。 事前に実行可能な関数を実装しておき、GPT に「きみは必要に応じてこんな関数を実行できるよ」というように関数の情報を渡すことで GPT が必要に応じてその関数の実行判断をしてくれる機能です。 定義した関数をどのような引数を用いて実行すればよいかという判断も行ってくれます。Function calling を用いたアプリケーションのシーケンス図は以下のようになります。 Function calling は強力な機能ですが、GPT からのレスポンスに関数実行の指示がある場合は関数を実行して また GPT に結果を渡す... といった処理は開発者で書く必要があります。 しかし、LangChain を利用することでこの処理を書く必要はなくなります。詳しくは後述します。 LangChain LangChain は LLM を用いたアプリケーション開発を円滑に進めるための OSS ライブラリです。 ChatGPT の普及とともに勢いを伸ばしている OSS であり、GitHub での Issue 数は 2023年11月時点で 1.7K にも上ります。 Python に加え、最近では TypeScript でも実装されたようです。 LLM を用いたアプリケーションでは、多くの場合下記のような実装が必要になります。 LLM 単体では会話履歴を保持できないため、アプリケーション側で管理する必要がある LLM が未知な領域に対して、情報収集機能を持たせる必要がある等 LangChain はこのような実装をライブラリとして提供する OSS という理解をしておけば良さそうです。 LangChain の Tool と Agent を用いた Function calling の実装 Tool とは、Web 検索する関数、データベースを検索する関数、slack にメッセージを送る関数など、外部システムと連携するための関数です。 Agent とは、プロンプトに対して Tool をどの順番でどのように実行すればよいかを判断し実行してくれる仕組みです。 例えば 「ある作家の出版履歴を slack に通知してほしい。」というプロンプトを受け付けた場合、 Web 検索する Tool を実行し出版履歴を取得、その後 slack にメッセージを送る Tool を実行する、といった処理を実行してくれます。 LangChain では自作の関数を Tool として定義し、Agent に読み込ませることで Function calling を実現できます。 詳しく見ていきましょう。 以下は、アプリケーションで用いた Python とモジュールのバージョン情報です。 Python : 3.11.7 openai : 0.28.0 langchain : 0.0.348 fastapi : 0.104.1 httpx : 0.25.2 pydantic : 2.5.2 Azure OpenAI Service の GPT は以下の設定で用いています。 Reagion : Japan East Model : gpt4 API version : 2023-07-01-preview Function calling で実行させる関数の定義 ここでは FIC ルータ作成 API を一例にとり、Tool を作成します。 まずは FIC ルータ作成のための関数 createFicRouter を実装しましょう。 今回は非同期で動作させたいので、非同期実行をサポートする httpx を用いて作成しています。 また、返り値として FIC から返ってくるレスポンス (json) が返却されるようにします。 import httpx from fastapi import HTTPException import os async def createFicRouter ( name, userIpAddress, area, redundant= True , isPublicServiceEndpoint= False ): token = await getToken() # FIC 操作のための token 取得 (詳細は省略) url = "https://api.ntt.com/fic-eri/v1/routers" headers = { "X-Auth-Token" : token, "Content-Type" : "application/json" , } body = { "router" : { "name" : name, "area" : area, "redundant" : redundant, "userIpAddress" : userIpAddress, "isPublicServiceEndpoint" : isPublicServiceEndpoint, "tenantId" : os.environ[ "FIC_TENANT_ID" ], } } # POST API の実行 async with httpx.AsyncClient(timeout= None ) as client: r = await client.post(url, json=body, headers=headers) if r.status_code not in [ 202 ]: raise HTTPException( status_code= 503 , detail= "Could not available: CreateFicRouter" ) return r.json() Tool の作成 Tool のパラメータには以下の3つがあります。 Tool としての名前 関数の説明 関数の引数 関数の引数を定義するためには Pydantic を用います。 関数の引数に関して、LangChain では引数を 1 つまでに限定しているようでした。 そこで、関数実行に必要な 5 引数を args という 1引数のオブジェクトとして内包させる仕組みにしました。 from pydantic import BaseModel # 関数を実行するために必要な引数 class SchemaCreateRouter (BaseModel): name: str userIpAddress: str area: str redundant: bool isPublicServiceEndpoint: bool # schemaCreateRouter で定義された5つの引数を args という一つの引数のオブジェクトにする class SchemaArgsCreateRouter (BaseModel): args: SchemaCreateRouter 最後に Tool を作成します。 langchain.tools にある BaseTool を拡張することで作成可能です。 下記のコードのポイントは 3点です。 description には関数の情報を詳細に記載する。この記述をもとに GPT が適切なタイミング・引数で実行判断を行う 非同期処理させたい場合は async def _arun(): で関数を実行するように記載する args_schema には run または _arun を実行するために必要な引数を定義する from langchain.tools import BaseTool from pydantic import BaseModel from typing import Type # 作成した関数を Tool にする class ToolCreateFicRouter (BaseTool): name = "createFicRouter" description = """ FIC に対して Fic-Router を作成する。 入力: name, userIpAddress (例: 192.168.0.0/27), area (JPEAST または JPWEST), redundant(Default: True), isPublicServiceEndpoint (Default: False) 備考: 非同期作成のため、作成完了までに時間がかかる。 """ args_schema: Type[BaseModel] = SchemaArgsCreateRouter # 同期処理 def _run (self, args): raise NotImplementedError ( "CreateFicRouter does not support sync" ) # 非同期処理 async def _arun (self, args): r = await createFicRouter(**args) return r 以上で Tool が完成しました。 Fuction calling を実行できる Agent の作成 上記の Tool を agent 初期化時に引数として渡すことで、 Agent が Function calling を実行できるようになります。 今回は Agent を非同期で動作させたいので、 initialize_agent の引数 async_mode に True を指定しましょう。 from langchain.agents import initialize_agent from langchain.agents import AgentType from langchain.chat_models import AzureChatOpenAI import os class ChatAgent : def __init__ (self): # LLM の定義 llm = AzureChatOpenAI( deployment_name=os.environ[ "DEPLOYMENT_NAME" ], # デプロイ名 model_name=os.environ[ "MODEL_NAME" ], # gpt4 temperature= 0 , max_tokens= 2000 , ) # Agent の定義 self.agent = initialize_agent( [ToolCreateFicRouter()], # 自作した Tool を配列に含める。Agent はこのなかで定義された Tool を使ってプロンプトに回答する llm, agent=AgentType.OPENAI_FUNCTIONS, verbose= True , return_intermediate_steps= False , async_mode= True , ) # Agent 実行用の関数定義 async def chat (self, message): r = await self.agent.arun({ "input" : message}) return r チャット用 API の作成 ここまで来れば後もう一歩です。 main.py という名前でファイルを作成し、 ChatAgent class を用いて回答を生成する API を定義してコードは完成です。 from fastapi import FastAPI from pydantic import BaseModel # リクエストボディのスキーマを定義 class PostChatRequestBody (BaseModel): request_message: str # レスポンスボディのスキーマを定義 class PostChatResponseBody (BaseModel): response_message: str app = FastAPI() # POST: /v1/chat で API を受け付ける @ app.post ( "/v1/chat" ) async def create_chat (body: PostChatRequestBody): # ChatAgent class のインスタンス化 chat_agent = ChatAgent() # chat() を実行 response_message = await chat_agent.chat(body.request_message) # レスポンスボディの返却 return PostChatResponseBody(response_message=response_message) アプリケーションの起動 環境変数を設定して FastAPI のサーバを起動します。 実行に必要な環境変数は以下のとおりです。 記載したコードのなかで現れたものとそうでないものがありますが、LangChain が自動で読み込むものもあります。 すべて設定してから FastAPI を起動しましょう。 # Azure OpenAI Service で OpenAI API を扱うために必要な環境変数 export OPENAI_API_TYPE = " azure " # プラットフォームの宣言 export OPENAI_API_VERSION = " 2023-07-01-preview " # Function calling は 2023-07-01-preview から利用可能 export OPENAI_API_BASE = " https://test-azure-openai-deployment01.openai.azure.com/ " # 作成した Azure OpenAI Service のエンドポイント export OPENAI_API_KEY = " xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx " # API Key export DEPLOYMENT_NAME = " test-gpt-4-turbo01 " # デプロイ名 export MODEL_NAME = " gpt4 " # モデル名 # 以下、FIC API を扱うために必要な環境変数 export FIC_TENANT_ID = " xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx " ︙ # サーバの起動 uvicorn main:app --reload おわりに 今回は Azure OpenAI Service を用いてチャットボットを作成しました。 チャットボットが必要に応じて外部 API を利用できるように、LangChain による Function calling も実装しました。 今回は Function calling に焦点を当てましたが、会話管理やアプリケーションのテストなどについても、また別の機会に取り組んでいきたいと思います。 それでは、明日の記事もお楽しみに!
アバター
この記事は NTTコミュニケーションズ Advent Calendar 2023 の15日目の記事です。 この記事では、ChatGPT と 音声認識モデルの Whisper を用いた発音練習アプリケーションをご紹介します。 ChatGPT に読み上げる文章を考えてもらい、その文章の読み上げた音声を Whisper で文字起こしします。 正確に発音できていれば、正確に文字起こしできる、という考えから、 原稿と文字起こし結果を比較すれば発音練習に使えるのではないかと考えました。 実際に使ってみた結果、発音のどこが悪かったのかといったフィードバックはもらえませんが、 自分の発話した音声に対して評価がつくだけでも、結構楽しく練習できると感じました。 音声認識を活用したアプリケーションは、一般に音声認識精度がネックになると思いますが、 このアプリケーションは音声認識精度が100%ではないことを逆手にとっている点と ChatGPT に比べると影の薄い Whisper くんの長所である多言語対応を活用できている点がユニークです。 本アプリケーションは Streamlit を用いて比較的簡単に作成できました。 Streamlit で音声データを扱うアプリケーションは文献が少ないように感じるので、 本記事が似たようなことをやりたい方の参考になれば幸いです。 目次 目次 はじめに 発音練習アプリの使い方 読み上げ原稿の用意 読み上げ文ごとに音声を録音する 結果を確認する 実装に使ったライブラリ・フレームワークの紹介 Streamlit streamlit-webrtc Whisper 振り返り Streamlit を使ってみた感想 音声認識を活用したアプリケーションの作成 今後やりたいこと 読み上げ原稿のリアルタイム生成 発音記号の実装 正誤判定処理の見直し アプリケーションの主要な構成要素の解説 Questionクラス 読み上げ原稿の表示 音声の録音 音声認識 結果表示 おわりに はじめに こんにちは、ソリューションサービス部の是松です。 普段はデータ利活用ソリューションに向けた、技術検証やデータ分析のPoCを行っています。 生成AIをはじめとする革新的な技術が次々と世に出てくる昨今、それらの新しい技術を使ってどんなことができるのかを手軽に試したいですよね。 Streamlit は、簡単に UI を含むアプリケーションを作成できるため、技術検証やデモをスピーディに行うのに便利です。 Streamlit は 3rd-party ライブラリがいくつも作られているのも特長で、 streamlit-webrtc というライブラリを使うことで、映像・音声も手軽に処理できます。 今回は (1) ChatGPT を使って読み上げ原稿を作成、 (2) streamlit-webrtc を使って音声を録音、(3) Whipser を使って文字起こし、という手順をとっています。 音声をあつかうアプリケーションは検索してもあまり情報がなく、はまりどころも多いので、本記事が音声を扱ったアプリケーションをつくりたい方の参考になれば幸いです。 発音練習アプリの使い方 アプリケーション全体の処理の流れは以下の通りです。 読み上げ原稿の用意 まず、自分が練習したい文章を ChatGPT に生成してもらいます。 今回は英語で比較的簡単な文章の練習をすることにします。 { " scripts ": [ " The quick brown fox jumps over the lazy dog. ", " A kind smile can make someone's day better. ", " Every morning, I enjoy a cup of green tea. ", " The library is quiet and perfect for studying. ", " On Sundays, we often go to the park. ", " She plays the piano beautifully and gracefully. ", " The cat sat on the warm windowsill. ", " Learning a new language is both challenging and rewarding. ", " He jogs around the lake every evening. ", " Birds sing cheerfully in the early morning. " ] } ...私にとっては結構難しそうな文章が出てきたので、今回はもっと簡単なもので試すことにします。 { " scripts ": [ " The cat sat on the mat. ", " I like to eat apples and bananas. ", " My friend is very kind. " ] } このjsonファイルを 所定の場所 に置いておきます。 読み上げ文ごとに音声を録音する アプリケーションを起動すると、以下のような画面が表示されます。 赤い Start ボタンを押すと、音声録音が始まります。 もう一度ボタンを押すと録音が止まり、録音した音声を確認できます。 音声の確認が済んだら、右上の Next ボタンを押して次の原稿に移ります。 これを繰り返します。 結果を確認する 全ての原稿の録音が終わると、結果画面が表示されます。 読み上げ原稿と、録音の文字起こしが完全に一致した場合を正解として、 正解した問題数が得点になります。 音声認識処理はバックグラウンドで動いているので、まだ処理が完了せず画面に反映されていない場合は、更新ボタンを押して画面を更新します。 このように、実際に自分の発話を聞いて音声認識モデルにどのように認識されるのかを確認することで、 自分一人でのスピーキングの反復練習に活用できます。 実装に使ったライブラリ・フレームワークの紹介 Streamlit Streamlit は、Python を使って簡単にWebアプリケーションを作成できるフレームワークです。 以下のような特長を持ちます。 シンプルで便利なウィジェットを組みあわせることで、少ない行数でアプリケーションを構成できる Python が得意なデータ処理や機械学習モデルとの組み合わせが容易にできる 3rd-party モジュールが多数公開されており、さまざまな用途に対応できる 詳しくは、 公式ドキュメント をご参照ください。 streamlit-webrtc streamlit-webrtc は、Streamlit 上でリアルタイム映像・音声信号処理ができる 3rd-party ライブラリです。 色々なユースケースのサンプルコードも公開されており、映像・音声を使ったアプリケーションの検証をするのに便利です。 WebRTC の特性上、streamlit-webrct ではクライアントとサーバ間で映像・音声ストリームのやり取りをリアルタイムに行います。 そのため、頻繁に画面が更新される Streamlit と相性が良くなかったり 1 、ネットワーク上の問題で正常に動作しなかったりと、使いこなす難易度は高いと感じました。 今回は以下のページを参考に実装しています。 streamlit-stt-app 簡単に原稿を読み上げて録音できるウェブアプリケーションをStreamlitで作った Whisper OpenAI の多言語音声認識モデルです。 ソースコード は公開されており、誰でも自由に試すことができます。 APIサービス も提供されています。(Large モデルのみ) 昨年のアドベントカレンダー企画で書いた記事 にて、Whisper を紹介していますので、よろしければご参照ください。 先月の OpenAI DevDay にて、large-v3 モデルの発表もありましたが、ChatGPT の話題性に比べて、あまり盛り上がっていないように感じられて悲しいです。 振り返り Streamlit を使ってみた感想 今回、フレームワークとして Streamlit を採用しましたが、プロトタイプアプリケーションを作成するのに、非常に有用だと感じました。 特に Whisper で音声認識をする処理を Python でそのまま書けるため、手元で Whisper を試すのと同じ感覚で、アプリケーション開発への組み込みができました。 音声録音処理は、はまりどころが多くて苦労したのですが、UIを全く意識することなく処理を実装できました。 課題としては、UI はあらかじめパーツが決まっており、自分でカスタマイズができないこと。 コードは短くシンプルに書けるが、裏側でどのように処理が行われているのかが見えづらいため、デバッグが難しいこと。 あくまでプロトタイプやデモアプリケーション向けのフレームワークであると感じました。 音声認識を活用したアプリケーションの作成 Whisper を使って誰でも手軽に高精度の音声認識モデルを触れるようになりましたが、 なかなか活用するのは難しいと感じていました。 Whisper はモデルタイプが複数あり、large モデルの性能が一番良いのですが、 その分、マシンスペックや実行時間が重くなってしまいます。 また、音声認識精度が高いとはいえ、ミスはどうしても発生してしまいます。 音声認識結果を活用しようとしても、認識ミスのせいで満足のいくクオリティのものが作れないという悩みを持つ人は多いのではないでしょうか? 今回作った発音練習アプリケーションは、音声認識に誤りがあることを逆に利用して、正しく音声認識させることを目的にしている点が面白いと思っています。 Whisper の軽量モデルは、large モデルと比較すると性能が落ちてしまうのですが、今回のアプリではそれが難易度上昇に繋がっていて、ポジティブにも捉えられると思います。 また、Whisper はさまざまな言語に対応しており言語推定機能を備えているため、読み上げ原稿さえ用意すれば、好きな言語で発音練習ができるはずです。 まだまだ機能としては荒削りですが、個人的に使いたいと思えるアプリケーションを作ることができました。 今後やりたいこと 読み上げ原稿のリアルタイム生成 アプリケーション上で ChatGPT への問い合わせを行なって、毎回ランダムな問題が表示できるようにしてみたいです。 API 料金がかかるので、リアルタイム生成結果はファイル保存した上で、リアルタイム生成かファイル参照かを選べるようにすると良いかなと思います。 発音記号の実装 JSON のフォーマットを変更して、発音記号などで読み方も表示されるようにしたいです。 正誤判定処理の見直し 現状は完全一致を正解としているので、もう少しゆるい正誤判定ルールに変更したいです。 アプリケーションの主要な構成要素の解説 ここからは、実際のアプリケーションのコードをかいつまんで解説します。 Questionクラス まず、読み上げる文章ごとに、録音音声や文字起こしテキストをまとめて管理するためのクラスを用意します。 import hashlib from dataclasses import dataclass from pathlib import Path @ dataclass class Question : script_index: int script: str transcript: str wav_dir_path: Path @ property def file_id (self): return hashlib.md5((self.script + str (self.script_index)).encode()).hexdigest() @ property def output_wav_name (self): return f "{self.file_id}.wav" @ property def wav_file_path (self): return self.wav_dir_path / self.output_wav_name @ property def record_info (self): return { "script" : self.script, "file_name" : self.output_wav_name} script は読み上げ文、 transcript は録音の文字起こし、 wav_dir_path は録音ファイルの保存先の情報を格納します。 ファイル名はハッシュ処理をしており、プロパティとして参照できます。 読み上げ原稿の表示 ChatGPT に作成してもらった読み上げ原稿情報を、 scipts/en.json に格納しておきます。 それを以下のようにして、アプリケーションで表示させています。 import streamlit as st from pathlib import Path from question import Question # 録音ファイルの保存先の設定 RECORD_DIR = Path( "./records" ) RECORD_DIR.mkdir(exist_ok= True ) # セッション状態の管理 if 'current_question_index' not in st.session_state: st.session_state[ 'current_question_index' ] = 0 # 問題文を読み込む script_file_path = Path( 'scripts/en.json' ) scripts = load_scripts(script_file_path) # 問題リストの初期化 if 'questions' not in st.session_state: st.session_state[ 'questions' ] = [ Question(script_index=i, script=script, transcript= "" , wav_dir_path=RECORD_DIR) for i, script in enumerate (scripts) ] # 現在の問題を取得 question = Question( script_index = st.session_state[ "current_question_index" ], script = scripts[st.session_state[ 'current_question_index' ]], transcript = "" , wav_dir_path = RECORD_DIR, ) # 読み上げ文を表示 st.markdown(f "# {question.script}" ) Streamlit では、画面に何かしらの変更があるたびに、コードが全て再実行される仕様となっているため、 通常の変数だと画面の更新のたびに初期化されてしまいます。 streamlit.session_state を使用することで、セッションを通して変数を保持できます。 上のコードでは current_quesiton_index と questions という2つの変数を、セッションを通して保持しています。 current_quesiton_index は、「現在表示している問題が何問目か」を管理する変数で、 questions は、読み上げ分の数だけ Question クラスオブジェクトを保持する変数です。 ファイルから読み上げ原稿を読み込んだ後、 questions を初期化します。 この時点では、文字起こしは存在しないため、 transcript の中身は空にしておきます。 最後に、 st.markdown() を使って、問題文を表示します。 音声の録音 録音用の WebRTCRecord クラスを定義します。 import queue import pydub import streamlit as st from streamlit_webrtc import WebRtcMode, webrtc_streamer class WebRTCRecord : def __init__ (self): self.webrtc_ctx = webrtc_streamer( key= "sendonly-audio" , mode=WebRtcMode.SENDONLY, audio_receiver_size= 256 , rtc_configuration={ "iceServers" : [{ "urls" : [ "stun:stun.l.google.com:19302" ]}]}, media_stream_constraints={ "audio" : True , }, ) if "audio_buffer" not in st.session_state: st.session_state[ "audio_buffer" ] = pydub.AudioSegment.empty() def recording (self, question): status_box = st.empty() while True : if self.webrtc_ctx.audio_receiver: try : audio_frames = self.webrtc_ctx.audio_receiver.get_frames(timeout= 1 ) except queue.Empty: status_box.warning( "No frame arrived." ) continue status_box.info( "Now Recording..." ) sound_chunk = pydub.AudioSegment.empty() for audio_frame in audio_frames: sound = pydub.AudioSegment( data=audio_frame.to_ndarray().tobytes(), sample_width=audio_frame.format.bytes, frame_rate=audio_frame.sample_rate, channels= len (audio_frame.layout.channels), ) sound_chunk += sound if len (sound_chunk) > 0 : st.session_state[ "audio_buffer" ] += sound_chunk else : break audio_buffer = st.session_state[ "audio_buffer" ] if not self.webrtc_ctx.state.playing and len (audio_buffer) > 0 : status_box.success( "Finish Recording" ) try : audio_buffer.export( str (question.wav_file_path), format = "wav" ) except BaseException : st.error( "Error while Writing wav to disk" ) # Reset st.session_state[ "audio_buffer" ] = pydub.AudioSegment.empty() 初期化処理で、 webrtc_streamer の設定します。 クライアントからサーバーへの送信のみを行うモード(サーバからクライアントへの送信は行わない)で、映像なし音声ありの設定です。 key は単なる識別子なのでなんでも良いです。 session_state で audio_buffer 変数を保持して、音声バイナリデータを溜めていきます。 recording 関数の中の、While 句の中身は、ユーザが録音開始ボタンを押してから停止ボタンを押すまでの間に実行されるループ処理です。 録音している音声バイナリを、 audio_buffer 内に蓄積させる処理が行われています。 停止ボタンが押されたら、 audio_buffer の中身を、ファイルに書き出して処理を終了します。 このコードからわかるように、録音中は音声データを蓄積し続けるので 停止をしないと、そのうちバッファが溢れアプリケーションが落ちます。 音声認識 import threading import whisper import streamlit as st def format_string (s): s = s.replace( ',' , '' ) s = s.replace( '.' , '' ) s = s.strip() return s def transcribe (file_path, model): result = model.transcribe( str (file_path)) return format_string(result[ "text" ]) def async_transcribe (question, model): transcript = transcribe(question.wav_file_path, model) question.transcript = transcript def start_transcription_thread (question): model = st.session_state[ "ASR_MODEL" ] x = threading.Thread(target=async_transcribe, args=(question, model)) x.start() # 次の問題へ if st.button( "Next >" ) and question.wav_file_path.exists(): # 音声ファイルがない場次へ行い current_question = st.session_state[ 'questions' ][st.session_s[ 'current_question_index' ]] # トランスクリプションをバックグラウンドで開始 start_transcription_thread(current_question) # 次の問題へ移動 st.session_state[ "current_question_index" ] += 1 音声認識処理は実行に時間がかかるため、別スレッドで実行しています。 音声認識結果は、 question.transcript に格納され、結果表示画面で参照されます。 また、音声認識処理開始と合わせて、 current_question_index 変数をインクリメントすることで、次の問題が表示されます。 結果表示 # 結果表示画面 if st.session_state[ 'current_question_index' ] >= len (scripts): score = 0 st.title( "結果発表" ) for question in st.session_state[ 'questions' ]: if question.script == question.transcript: score += 1 if question.transcript: st.markdown(f "### 問題 {question.script_index}" ) st.write(f "原稿:{question.script}" ) st.write(f "結果:{question.transcript}" ) else : st.markdown(f "### 問題 {question.script_index}" ) st.write(f "原稿:{question.script}" ) st.write(f "結果:処理中..." ) st.markdown(f "# あなたのスコアは... {score} 点!" ) # スレッドが完了した後にボタンを表示 if st.button( "画面を更新" ): st.rerun() 最後の問題が終わったら、結果表示画面に移ります。 questions に保持している情報を展開していくだけですが、音声認識が終わっていない場合は transcript 変数の中身は初期化時の空白のままなので、処理中.... と表示されます。 streamlit.rerun() を実行すると、強制的に画面の更新ができ、もし更新時に音声認識が終わっていれば、結果が正しく表示されます。 おわりに 本記事では、Streamlit を用いた発音練習アプリケーションを紹介しました。 音声を使ったアプリケーション作成の参考になれば幸いです。 それでは、明日の記事もお楽しみに! Streamlit自体は頻繁に画面更新が発生することを想定した書き方をするのに対して、WebRTCの部分は画面更新してもセッションが保持されるため、アプリケーションの挙動が理解しづらいように思います。streamlit-webrtc の実装も大変そうです。( 参考 ) ↩
アバター
この記事は NTTコミュニケーションズ Advent Calendar 2023 の14日目の記事です。 こんにちは、イノベーションセンター所属の志村です。 Metemcyberプロジェクトで脅威インテリジェンスに関する内製開発や、Network Analytics for Security (以下、NA4Sec)プロジェクトで攻撃インフラの解明・撲滅に関する技術開発を担当しています。 ソフトウェア開発プロセスにおけるセキュリティに関心が高まりつつあり、サプライチェーンセキュリティという言葉も広く使われるようになってきました。 またMetemcyberプロジェクトでは SBOMに関する取り組み を行っていますが、SBOMもサプライチェーンセキュリティの分野での活用が期待されている概念となります。 そこで本記事ではサプライチェーンセキュリティとはそもそも何か、具体的にどのような対策が存在するのかについて紹介し、サプライチェーンセキュリティへの取り組み方について考察します。 目次 目次 本記事で扱う「サプライチェーン」について なぜサプライチェーンセキュリティが重要なのか サプライチェーンセキュリティのフレームワーク SLSA S2C2F CIS Software Supply Chain Security Guide サプライチェーンセキュリティの具体的な対策 依存関係に関する対策 ビルドプロセスに関する対策 ビルド成果物に関する対策 サプライチェーンセキュリティの導入の難しさ セキュリティ担当の立場から 開発者の立場から 最後に 宣伝 本記事で扱う「サプライチェーン」について 「サプライチェーンセキュリティ」は、「サプライチェーン」に対する攻撃への対策を意味します。 ではサプライチェーンに対する攻撃とはどのようなものなのかと言いますと、現時点では複数の意味で使われているようです 1 。 標的とする組織の取引先の企業などに攻撃し、そこを踏み台として標的に侵入する攻撃手法 ここでのサプライチェーンは「下請け・取引先を含めた企業などのつながり」を意味する Island Hopping 攻撃とも呼ばれる ソフトウェア開発の一連のプロセス (開発者・ソースコードリポジトリ、ビルド環境、デプロイ環境など) に対する攻撃 ソフトウェアの開発から提供までの一連のプロセス (CICDパイプライン) をサプライチェーンと呼んでいる 本記事では2番の意味のサプライチェーン、すなわちソフトウェア開発のプロセスをどのように保護するか、という観点を取り扱います。 ソフトウェア開発のプロセスに関与する要素としては以下のようなものが挙げられます。 開発者 ソースコード、およびソースコードを管理するSCM (gitなど) ソフトウェアのビルド・テストに用いる環境・ソフトウェア 外部の依存コンポーネント ビルド成果物 上記のようなソフトウェアを開発に関与する要素への攻撃がサプライチェーン攻撃であり、それに対する防御手法がサプライチェーンセキュリティといえます。 なぜサプライチェーンセキュリティが重要なのか 近年、ソフトウェア開発プロセスの高度化に伴い、開発プロセスを構成する要素が増加しています。 つまり攻撃を受けやすい状態に変化してきていることから、その結果としてサプライチェーンセキュリティへの関心が高まっています。 以降でこのことについて詳しく説明します。 近年のソフトウェア開発では、OSSをはじめとする外部ソフトウェアをコンポーネントとして組み合わせる開発方式が一般的となりました。 またソフトウェアのテスト・ビルド・デプロイにおいても、GitHub Actionsなどのサービスインフラを利用して自動化したプロセスを構築し、その上でサードパーティーのツール (テストカバレッジの可視化など)を利用することが増えています。 このような技術によって、ソフトウェア開発の高速化・高機能化が進んできました。 一方で利用・依存するプラットフォームやソフトウェアが増えるということは、攻撃可能な対象が増えることを意味します。 そのうち1つでも侵害できれば、ソースコードやクレデンシャルを取得したり、ソフトウェアに悪意ある挙動を挿入できる可能性があるのです。 代表的な攻撃事例として、2020年に発生したSolarWinds社に対する攻撃があります。 SolarWinds社は自社のビルドプラットフォームを侵害されたことにより、製品にバックドアを仕込まれてしまいました。 これによりSolarWinds社の製品を利用していた組織がバックドア経由で侵入される被害を受けました。 また2021年には、著名なコードカバレッジ測定ツールCodecovのbashスクリプトが改竄された結果、Codecov利用者のソースコードなどの情報が流出するという事件も発覚しています。 このように、開発プロセスに対する攻撃が成功してしまうと、システムへの侵入や情報漏えいといった重大な問題が発生するというリスクがあります。 サプライチェーンセキュリティの実践により、ソフトウェア開発プロセスの各要素に対する攻撃を防いだり、攻撃を受けても被害範囲を限定するといったことが可能になるので、ソフトウェア開発のリスクを低減できます。 上記のような理由のため、サプライチェーンセキュリティが重要であるとして関心度が高まっています。 サプライチェーンセキュリティのフレームワーク サプライチェーンセキュリティのために、どのような対策を採用すれば良いでしょうか。 サプライチェーンセキュリティをどのように実現すべきかという方法論をまとめたフレームワークがいくつか存在しています。 SLSA OSS開発者に向けたフレームワーク S2C2F OSSを安全に利用することを目的としたフレームワーク CIS Software Supply Chain Security Guide 企業内部での開発に焦点を当てたガイド 各フレームワークの概要について簡単に紹介します。 SLSA SLSA(Supply-chain Levels for Software Artifacts) はLinux FoundationのプロジェクトであるOpenSSFが開発・公開しているフレームワークです。 SLSAはオープンソースをどのようにセキュアに開発・提供するかという内容が中心となっています。 SLSAは2023年12月現在バージョン1.0 がリリースされています。 SLSAでは、サプライチェーンの要素を以下のように定義しています。 開発者 コードリポジトリ (GitHub, GitLabなど) ビルドプロセス Dependencies (外部の依存コンポーネント) 成果物のパッケージ ソフトウェアの消費者 サプライチェーンの各要素と、要素間をつなぐ経路の間に攻撃が存在します。 ( 出典: https://slsa.dev/spec/v1.0/threats-overview ) SLSAはサプライチェーンにおいて満たすべき要件をレベルごとに定義しています。 SLSAの バージョン0.1 の段階ではソースコードの安全性などの幅広い分野の要件を定めていましたが、2023年4月のバージョン1.0のリリースにおいてはソフトウェアのビルド環境や来歴 (ビルド成果物がどのように生成されたかの証跡) の作成に焦点を当てたフレームワークとなっています。 そのためサプライチェーン全体の保護の参考にしたい場合、バージョン0.1を参考にした方が良いかもしれません。 S2C2F S2C2F(Secure Supply Chain Consumption Framework) はSLSAと対照的に、開発者がOSSを安全に使う方法を取り扱ったフレームワークです。 SLSAと同様に、セキュリティレベルごとに満たすべき要件を定めており、Level1 ではパッケージマネージャーを利用することや、既知の脆弱性をスキャンして発見することなどが含まれています。 ( 出典: https://www.microsoft.com/en-us/security/blog/2022/11/16/microsoft-contributes-s2c2f-to-openssf-to-improve-supply-chain-security/ ) CIS Software Supply Chain Security Guide CIS Controls などで知られる、インターネットセキュリティ標準化を目的とした団体であるCISが、Trivyなどで知られるAqua Security と共同で作成したガイドラインです。 公式サイト からPDFをダウンロードできます。 CIS Software Supply Chain Guideで想定しているソフトウェア開発サイクルはSLSAとほぼ同一です。 このガイドラインにはサプライチェーンを保護する100以上の推奨事項が含まれていて、以下のカテゴリに分類されています。 Source Code Build Pipelines Dependencies Artifacts Deployments CIS Software Supply Chain Guideでは、プラットフォームなどの詳細には踏み込まずに、一般的なガイドラインとして作成されています。 その手法をプラットフォームの詳細を踏まえて具体的な内容に落とし込んだ個別のガイダンス ( CIS GitHub Benchmark など) が別途存在しています。 サプライチェーンセキュリティの具体的な対策 各フレームワークでは共通している対策が多くあります。いくつか代表的なものをピックアップして紹介いたします。 依存関係に関する対策 OSSなどの外部コンポーネントへの依存を適切に管理し、脆弱性を早期に発見・対処することが重要になります。 主な対策としては以下のようなものがあります。 ソフトウェアに組み込む依存関係は、パッケージマネージャーなどを利用して明示的に依存する ソースコードをコピーするなど、暗黙的な依存を発生させない 利用するバージョンを固定することで、意図しないバージョンへの依存が発生することを防ぐ バージョンを固定しないとビルドした時期によって依存バージョンが変わり、脆弱性の判断などが困難になる SBOMなどを利用して依存関係の把握と脆弱性の追跡する SBOMは含まれるソフトウェア部品の一覧を可視化したリストで、脆弱性対応やライセンス管理などの文脈での活用が進められています。 パッケージマネージャーなどを利用して明示的に依存関係を管理することは、SBOMの作成という観点においても重要なプラクティスとなります。 SBOMに関しては 昨年のアドベントカレンダーの記事 もご覧いただければと思います。 ビルドプロセスに関する対策 ビルドプロセスへの攻撃は、ソフトウェアに意図しない挙動を挿入されたり、認証情報を盗まれるなどの被害を招きます。 具体的な対策は以下のようなものがあります。 ビルドは自動化されたスクリプトで行う ビルド環境が複数回利用されないようにする 使い捨てのコンテナやVMを活用してビルドする テスト・デプロイなどの工程ごとにCICDプロセスを分け、各フェーズでは最小権限の認証情報を付与する 他のフェーズに攻撃の及ぶことを防ぐ 侵害された場合の認証情報の流出を最小限にする 依存するソフトウェアの信頼性を確認する GitHub Actionsの場合、そのソフトウェアの信頼性を確認する (よく似た名前の偽パッケージでないか、利用者数など)。可能であればActionのコミットハッシュを指定し、固定されたバージョンが利用されるようにする ( 参考 ) ビルド成果物に関する対策 生成したソフトウェアやDockerイメージなどのビルド成果物はパッケージレジストリに保存され、そこから環境にデプロイされることが一般的です。 保存されたビルド成果物を改ざんなどの脅威から守ることが重要となります。 パッケージレジストリにアクセスできるユーザを制限し、多要素認証などを導入する ビルド成果物に対して署名し、改ざんを検知できるようにする ソフトウェアに署名・検証を用意にする Sigstore プロジェクトの Cosign などを活用する サプライチェーンセキュリティの導入の難しさ ここまで各フレームワークの概要と、共通する要素について紹介してきました。 具体的に導入すべき対策については、各フレームワークが規定している内容を参考にできます。 一方でサプライチェーンセキュリティの対策を組織に導入する際には、セキュリティとソフトウェア開発それぞれの立場から実施すべき内容があるため、通常のセキュリティ対策とは異なるアプローチが必要になります。 サプライチェーンセキュリティにどのように取り組むべきか、という観点についてセキュリティ担当者と開発者のそれぞれ立場について考察してみます。 セキュリティ担当の立場から 大前提として、サプライチェーンセキュリティの手法はソフトウェア開発の手法の上に成り立っています。 そのためサプライチェーンセキュリティの推進はセキュリティの知識だけでは完結せず、ソフトウェア開発の知識も必要となります。 組織のソフトウェア開発のプロセスを理解したうえで、フレームワークで推奨されているセキュリティ対策をどのように既存のプロセスに組み込むかを決定したり、必要に応じて開発プロセス自体の見直しを進めていくことになるでしょう。 またサプライチェーンセキュリティの対策は、依存ソフトウェアの選定やCICDプロセスの構築など、現場のソフトウェア開発者が意思決定の主体となるものが数多くあります。 そのため、現場のソフトウェア開発者に向けて、ソフトウェア観点でのセキュリティ知識向上のトレーニング実施なども重要です。 開発者の立場から サプライチェーンセキュリティの実現には開発者の貢献が必要です。 ただサプライチェーンセキュリティを構成する要素は広範囲に及ぶため、全ての要素に対する脅威とその対策を把握するのは困難でしょう。 セキュリティ専門家のサポートを受けることが望ましいですが、開発の初期段階から十分な支援を受けることが難しい場合も多いでしょう。 そのため開発の初期段階で意思決定され、後の変更が難しい要素については開発者自身が一定の知識を有することが望ましいと考えられます。 例えば使用する言語・フレームワークなどは開発の初期段階で意思決定が行われ、変更するには多大なコストがかかります。 これらはセキュリティも念頭においた意思決定をしておくと将来的のリスクを軽減できます。 言語の選定 可能であればパッケージマネージャーが利用可能で、依存関係を明示できる言語を選定する 依存ソフトウェアの選定 開発コミュニティは機能しているか、定期的な更新や脆弱性の修正がされているか また依存しているサービスやプラットフォームがセキュリティのガイドラインを発行していることも多いです。 このようなガイドラインを参考にして設定することも重要になります。 GitHub Securing your software supply chain GitLab Secure your application 最後に サプライチェーンセキュリティの概要やフレームワークを紹介し、サプライチェーンセキュリティへの取り組み方について考察しました。 サプライチェーンセキュリティはソフトウェア開発のプラクティスと相互に影響して成立するため、ソフトウェア開発の進化に伴い対策の内容も変化していくことが予想されます。 最新の動向は各種フレームワークなどを参考にしていただければと思います。 宣伝 最後に宣伝ですが、Metemcyberでは今回紹介したSBOMに関する取り組みや、NA4Secと連携した情報配信を行なっております。興味ある方は公式アカウント (@Metemcyber) をフォローしていただけると幸いです。 またMetemcyber/ NA4Sec プロジェクトでは一緒に働く仲間を募集しています。 興味を持たれた方はぜひ応募してみてください。 NA4Sec (Threat Intelligence Analyst / 脅威インテリジェンスアナリスト) Metemcyber (Threat Intelligence Engineer / 脅威インテリジェンスエンジニア) なぜ、SSL-VPN製品の脆弱性は放置されるのか ~“サプライチェーン”攻撃という言葉の陰で見過ごされている攻撃原因について~ ↩
アバター
この記事は NTTコミュニケーションズ Advent Calendar 2023 の13日目の記事です。 こんにちは、イノベーションセンターの坂本です。 ソフトウェアエンジニアとしてノーコードAI開発ツール Node-AI の開発に取り組んでいます。 機械学習やその前処理などの計算にかかる時間はデータサイズや処理内容により大きく異なります。そのため機械学習やデータ分析に関するアプリケーションでは、冪等でない処理をイベント駆動型アーキテクチャ(EDA)で扱う難しさがあります。 今回は上記の課題とその解決策として採用している専用ゲームサーバ(DGS)用OSS Agonesを利用したEDAのWorkerについて紹介します。 Node-AIとは Node-AIはブラウザから以下の図のようにカードを直感的につなげるだけで時系列データの前処理からAIモデルの学習・評価までの一連のパイプラインを作成・実行できるツールです。 各計算処理ステップ(例えば移動平均や正規化など)がカードとして独立し1つずつ実行できるため、視覚的にデータ処理の流れを追いやすくなっています。複数人での同時作業にも対応しているため個人だけでなくチーム単位で効率的なデータ分析やAIモデル開発ができます。もし詳しく知りたい方がいれば過去の関連記事を参照してください。 ノーコードAI開発ツールNode-AIの紹介 ノーコードAIツールNode-AIを使って簡単に需要予測をしてみた 最近噂のノーコードAIモデル開発ツール Node-AIで時系列データの因果分析・重要度可視化・要因分析をしてみた Node-AIにおけるEDA Node-AIではこの各カードに応じた計算処理を非同期処理として専用のマイクロサービス(Worker)で行っています。 ユーザがカードを実行すると裏では大まかに次のような流れで計算処理されます。 なお1つの実行当たりのリソース上限を与えたいので、1つのWorkerが引き受けられるのは1つのmessageまでという作りにしています。 ユーザがNode-AI上のカードを実行する Broker(Redis)にどのような計算処理かといったmessageが格納される WorkerがBrokerからmessageを取得してそれに従った計算処理を実行する WorkerおよびEDAの課題/要件 Node-AIのような機械学習系のアプリケーションにおいて、Workerに関連する部分では以下のようなことが課題/要件として挙げられます。 運用者目線 いつでもアップデートしたい インフラ費用を下げたい ユーザ目線 計算処理が高速に実行されてほしい まずアップデートについて問題になるのは内部処理の安全性担保です。例えばサービス更新によって処理実行中にWorkerが強制終了してしまうとプロダクトの信頼性に関わります。うまくBlue/Green Deploymentのような仕組みを導入できればよさそうですが、そのための開発やメンテナンスのコストを考えると複雑なものや独自のCustom Resource Definitionsは極力採用したくありません。 次にインフラ費用についてです。これはオートスケールやオンデマンドなリソース確保を実現すれば概ね問題にはならないでしょう。 最後にユーザ目線の高速であって欲しいということについてです。そもそもユーザが体感するWorkerに関する待ち時間は ネットワーク遅延 + Workerがmessageを取得するまでの時間 + 計算にかかる時間 + その他 であるとします。 Workerがmessageを取得するまでの時間 について考えてみます。 これはmessageがキューに滞在する時間とも読み替えることができ、実態としてはノードのProvisioningやコンテナ作成、プロセス起動までにかかる時間があります。この滞在時間を小さくするためには、デメリットを無視すれば予めWorkerをたくさん用意したり高速なスケールアウトなどが実現できればよさそうです。 他にも検討した結果、Workerに求められることは以下の4つとなりました。 処理開始までの時間 カードを実行するとすぐに計算処理が開始されて欲しい マイクロサービス更新の安全性 マイクロサービスを更新する際に計算処理中のWorkerに影響を与えたくない スケーリングの安全性 スケールインの際に計算処理中のWorkerに影響を与えたくない インフラ費用面 コンピューティングリソースの維持費用を安く済ませたい 実装方式の比較 まずPodとJobでWorkerを実装した際に要件と合致するかどうかをみていきます。 なおNode-AIはデータ機密性に課題をもつお客さま向けにオンプレミス環境でも提供していたり機械学習を使う都合上柔軟なタイムアウトを設定する必要があるため、各クラウドベンダーのサーバレスサービスなどは選択肢から除外しています。 こちらが評価した結果です。 評価観点 Pod Job 処理開始までの時間 ✅ (条件付き) ❌ マイクロサービス更新の安全性 ❌ ✅ スケーリングの安全性 ❌ - インフラ費用面 ❌ ✅ Node-AIのリリース当初はWorkerをPodで構築していました。 いくつか抜粋すると、まず処理開始までの時間の問題は条件付きでクリアしています。 これはコンテナ作成や内部プロセス起動はPod作成時の一度きりで済むからです。一方でスケーリングを考えた際にはキューにあるmessage数をメトリクスとしてオートスケールさせるとよさそうですが、実際にやってみるとmessageが溜まってからスケーリングが開始するので待ち時間が発生しそうです。 また、マイクロサービス更新やスケールイン時の安全性ついては terminationGracePeriodSeconds や cluster-autoscaler.kubernetes.io/safe-to-evict を駆使するといったことも考えられますが、ライフサイクルの観点から厳しかったりサービス更新には非対応だったりと完璧な解決策にはなり得ませんでした。 次にJobで実装してみました。 JobではPodでの課題をほぼ解決できそうでしたが、今度は処理開始までの時間が大きく低下してしまう結果となりました。これは各message単位で行われるJobの作成とコンテナ中のプロセス起動に大きく時間がかかることに起因します。 Node-AIで使用しているマイクロサービスの場合、image cacheが効いたノードでも上記のオーバーヘッドが約8秒も(つまりPodでの実装パターンよりもカード実行開始までに約8秒多くかかる)あり、UXを大幅に低下させてしまうため採用に至りませんでした。 AgonesをWorkerに利用する これらの課題を鑑みた結果、DGS用OSS Agones を用いてWorkerを構築しました。なお本記事では少し古いですがAgonesのバージョンは 1.28.0 の内容となっています。 オンラインゲームを例とすると、DGSは運営側でホストされプレイヤー間の状態を同期しています。ゲームにもよりますが、おおよそ数分から数時間に及ぶプレイ中の計算結果をメモリに保存するためステートフルな状態となります。そのため実行中のゲームサーバーの保護をする仕組みが必要となります。また利用状況によってはリソースのスケーリングも必要です。AgonesはこうしたDGSのホスト、実行、スケーリングをKubernetes上で容易に実現するものです。 本記事の内容で利用するAgonesのリソースは以下です。 GameServer DGSそのもの、これをWorkerとして使います Fleet 割り当て可能なGameServerのセット FleetAutoscaler Fleetのスケーリングを行うもの DGSと機械学習処理にはステートフルかつ冪等ではない処理を扱うという共通点があります。 そこで具体的にはAgonesの次の機能を利用します。 常にStateが Allocated なGameServerの数を維持するオートスケールアルゴリズムを有する Allocated なStateをGameServerに付与することでアップデートやスケールインの対象外にできる 1つめの特徴を利用することでmessageを引き受けられる状態のWorkerを常に指定数だけPoolしておくことがFleetAutoscalerを用いて実現できます。動画のように例えば4つ用意する設定の場合には、そのうちの1つがmessageを受け取って計算処理状態(Allocated)になると自動で1つWorkerを作成する挙動となります。これによってある程度経済的でユーザに遅延を感じさせないスケーリングを実現できます。 2つめの特徴としてAgonesではStateがAllocatedなものを保護してくれます。つまりWorkerがmessageを取得したタイミングでAllocatedにすれば処理中のWorkerに影響を与えることなく安全なアップデートが可能になります。 また、うまくState遷移を設計すると擬似的にOne-ShotのJobのようなライフサイクルを持つWorkerを作ることができます。Node-AIのWorkerではプロセスの状態に合わせて概ね以下のようなState遷移を行なっています。 これらの特徴から我々の課題を全て一定の水準で解決できることが分かりました。 評価観点 Pod Job Agones 処理開始までの時間 ✅ (条件付き) ❌ ✅ マイクロサービス更新の安全性 ❌ ✅ ✅ スケーリングの安全性 ❌ - ✅ インフラ費用面 ❌ ✅ ✅ Worker内のアーキテクチャ Worker自体は下図のような構成をとっています。なおControllerとの通信部分の記述は省略しています。 AI/ML用コンテナはPythonで書いており、message取得や前処理/機械学習の処理を実行します。 SDK ServerはAgones APIとの通信やメタデータを提供しています。 制御用コンテナはGo言語で書いており、GameServerのlifetime管理や再スケジューリングを行います。( 実装例 ) GameServerを高頻度で作成&削除を繰り返すとFleetで spec.scheduling: Pack としていてもリソースが分散して配置されることがあります。またGameServerのPodには cluster-autoscaler.kubernetes.io/safe-to-evict: false が付与されるのでノードのスケールインが効かなくなります。つまり高頻度でWorkerが使用された後にしばらく使われなくなるとその期間は使用率の低いノードが存在してしまい、ノード単位で課金される料金体系では無駄にコストがかかってしまいます。そこで制御用コンテナを用いて一定時間経つと自己的にGameServerを削除し、最小のノードのセットに詰め込むように再スケジューリングさせる仕組みを導入しています。 また、ゾンビリソースの防止のためにAllocatedなものでも一定時間たったものは強制削除するようにしています。この時間は各カードによって異なる値をAI/MLコンテナからSDK Serverのメタデータとして設定し、例えば正規化の場合は 10分+バッファ時間(5分) 、学習では 24時間+バッファ時間(5分) としています。ただしアプリケーション側にも処理のタイムアウトは入れているのでこれはあくまで保険的な使い方です。 他にもGameServerのhostPort機能などWorkerとして不要なものを無効化しています。 おわりに 本記事ではNode-AIで用いているWorkerについて紹介しました。 Node-AIはどなたでも こちら から無料で試用できるので、もしプロダクトや使われている技術に興味を持っていただけたらぜひ触ってみてください。 それでは、明日の記事もお楽しみに!
アバター
はじめに こんにちは、イノベーションセンターでノーコード分析ツール「Node-AI」開発チームの林です。 業務としては Node-AI のフロントエンドやバックエンド開発、最近では監視/可視化のプラットフォーム開発に携わっています。(先日 こちら の記事を執筆したりしています。) 本記事では、2023 年 11 月 14 日に開催した NTT ドコモ・NTT コミュニケーションズ・NTT コムウェアからなるドコモグループ(以下、DCC グループ)内の Google Cloud のユーザーコミュニティ「GINGER」 の第 5 回目のイベントをご紹介します。 GINGER 紹介 GINGER は Google Cloud Community In NTT Group Enterprise の頭文字をとって命名しました。 2023 年 4 月のドコモ在籍時にグーグル・クラウド・ジャパン合同会社様支援のもと立ち上げました。当初はドコモにとどまらず NTT グループ に広げたいという壮大な思いから上記の名前をつけました。 2023 年 7 月にコミュニケーションズへ出向することとなり、これを契機にコミュニケーションズ内に拡大していき、現状は DCC グループ内の Google Cloud ユーザーコミュニティ として活動しています。 活動としては、ドコモとコミュニケーションズにまたがっている slack チャンネルでの情報共有(コムウェアへの拡大は今後)や 2 ヶ月 1 回の頻度で開催しているオンラインとオフラインでのハイブリッド形式イベントが主なものとなっています。 コミュニティメンバーは Google Cloud やコミュニティ活動に興味がある方メンバーであったり、情報のキャッチアップしたいメンバーなどドコモとコミュニケーションズの社員/BP が入り混じって構成 されています。 活動の中でも 特にイベントでのオフラインのつながりを重要視 しています。Google Cloud に関するノウハウ共有を実施しつつ、 このコミュニティに参加したからこそ得られる業務を超えたつながりを価値 として感じてほしいと思っています! では、本題である GINGER Event#5 の開催報告に入りたいと思います! オープニング Event#5 では下記のアジェンダで開催しました! 恒例の LT 大会となっており、トップバッターは グーグル・クラウド・ジャパン合同会社様からネットワークスペシャリストである有賀さんによる「Google Cloud ネットワークの裏側」 についてお話しいただきました。 次にコミュニティにフル参加いただいている常連の NTT ドコモ データプラットフォーム部(以下、ドコモ DP 部) 兼子さんから「GCS に対する IP 制限をやろうと思ったら意外に結構難しかった件」 、最後に今回初めて参加いただいた NTT コミュニケーションズ コミュニケーション&アプリケーションサービス部 西谷さんから「ビジネス d アプリの GCP サーバーレスをフル活用した内製開発について」 という各所から素晴らしいみなさんにエントリーしてもらいお話しいただきました。 アジェンダを見ただけでもワクワクするような LT 3 本立てとなっていて、振り返ってもとても濃密な時間となっていました! (執筆者 NTTコミュニケーションズ/林 知範) 運営メンバー紹介 今回は運営チームもスケールさせたいという思いからコミュニティメンバーに呼びかけをしました。創設当初から参加いただいている NTT ドコモ DP 部の藤平さんに運営お手伝いを依頼したところ、藤平さんとともに働いているみなさんにも手伝ってもらえる形になったのでそのメンバーの紹介になります!(こちらの記事は運営メンバー 4 名による共同執筆となっています。) —-------------------------------- NTT ドコモ DP 部でお仕事させていただいている NTT データ 辰己と申します。普段は DP 部で藤平さんとお仕事をさせていただいており、その一環として当時 NTT ドコモ SI 部に所属されていた林さんと Google Cloud にかかわる技術交流をさせていただいておりました。 今年は気づいたら、その取り組みが、この GINGER として徐々に大きな活動になってきて、かつ、このコミュニティが自分のチームメンバーにとって刺激になっています。 GINGER メンバー・ドコモ DP 部・Googler の皆さまを含め本活動を支援いただいている全ての方に、この場をお借りしてお礼申し上げます。 (執筆者 NTTドコモ/藤平 亮, NTTデータ/辰己 勝俊) 同じく、NTT ドコモ DP 部でお仕事させていただいている NTT データ 三浦と申します。今年になってから初めて、Google Cloud を本格的に触り始めることになりました。この活動に関わる皆さま方からの刺激を受けながら日々研鑽を積んでいる中、辰己さんと同様に、ドコモ DP 部藤平さん推進の元、今回運営側のお手伝いをさせていただくこととなりました。 このような機会を設けていただいた皆さまに、改めて、この場を借りてお礼申し上げます。 (執筆者 NTTドコモ/藤平 亮, NTTデータ/三浦 佑晟) LT1:Google Cloud ネットワークの裏側 LT パートのトップバッターは グーグル・クラウド・ジャパン合同会社様からカスタマーエンジニアでネットワークスペシャリストの有賀さんから「Google Cloud ネットワークの裏側」 についてお話しいただきました。 有賀さんには Event#2 でもお話しいただいたのですが、時間の都合上途中で終わってしまったことがありました。 コミュニティメンバーからの続編を聴きたい!という強い声を届けたところ、快諾していただき 2 回目の登壇をしていただくこととなりました。 発表の内容としては Google Cloud のネットワークということで、下図にあるような リージョンやエッジローケーションがどのように点在しているのかといった話 から始まりました。リージョン数は 38 で利用可能地域は 200 以上に増えたとのことで Google Cloud の広まりを数字で実感する とともに、どのようにアクセスがルーティングされているかを図を交えながら説明いただき大変勉強になりました。 後半はデータセンターについて説明いただき、 全世界にある Google のデータセンターが同じ仕組みで統一されていること を教えていただきました。その設計のおかげでソフトウェア開発者がどのデータセンターのどのサーバーでも動かせるようになっているというのを知り、設計の凄さ・大切さを学ぶことができました。 濃密な内容を発表いただいて、 30 分という長さがあっという間だと感じるくらいの素晴らしい時間でした! まだまだ聴講したいというメンバーも多く、折を見てまたコミュニティメンバーの声を届けさせていただきたいなと思っています。 素晴らしい LT をありがとうございました! (執筆者 NTTコミュニケーションズ/林 知範) LT2:GCS に対する IP 制限をやろうと思ったら意外に結構難しかった件 次の LT は、 NTT ドコモ DP 部(from NTT データ)の兼子さん です。発表テーマは、特定の GCS バケットに対して、どうやって IP 制限をかけることができるか、という内容で発表いただきました。AWS でいうと S3 に対して IP 制限をかけることはバケットポリシーを利用すれば簡単に実現できますが、 Google Cloud 上でこれを実現しようと思うと、意外にもどう実装するかは、いくつかのパターンがあり、かつバケットポリシー程シンプルには実現ができません 。 最終的に兼子さんが採用された案としては、昨年 Google Cloud でリリースされたばかりの機能である エッジセキュリティポリシーという Google Cloud のエッジで動作する WAF 機能と Cloud Armor のサービスアカウントを元に署名付き URL を発行することでバケットへのアクセスの IP 制限を実現する というものでしたが、その実装に当たって API の仕様レベルでどこに実装上の難しさがあったのかを詳細に紹介いただきました。 ■参考リンク Cloud Armor の新しいエッジ セキュリティ ポリシーとプロキシ ロードバランサのサポートの一般提供開始のお知らせ | Google Cloud 公式ブログ 世の中的には AWS と Google Cloud はハイパースケーラーとして並列に語られることが多いですが、 実際には中の NW の仕組みは大きく異なっていました 。それに伴って同じことを実現しようと思ってもそれぞれで実装できることや逆に実装できることは同じだとしても、実装の仕方は大きく変わることがあるなというとても示唆深いLTでした! (執筆者 NTTドコモ/藤平 亮, NTTデータ/辰己 勝俊) LT3:ビジネス d アプリの GCP サーバーレスをフル活用した内製開発について LTのトリをご担当いただいたのは、 NTT コミュニケーションズ コミュニケーション&アプリケーションサービス部 第3サービス部門 西谷 智広さん です。今回始めて GINGER Event にご参加いただき、突然のご依頼にも関わらず快く LT へのご登壇を引き受けてくださいました。私自身初めてお会いしましたが、経歴紹介で多くの実績を残されていることを知りその後の LT への期待が大きくなっていました。 ご発表いただいた内容は現在開発中のビジネス d アプリというモバイルアプリを Google Cloud サーバレスに内製開発に挑んだお話 で、詳細を公表できないのがもどかしいほど濃密で幾重にも考慮が積み重なったお話を聞くことができました。 お伝えできる範囲でどんな発表だったかをお伝えすると、 コーディング経験が無い方を含めた開発プロジェクトにおいて設計/コード量/検証を極力減らした構成とするために App Engine, Cloud Run, Cloud Firestore, Cloud Spanner 等を組み合わせたGoogle Cloudのサーバレス環境を構築してアプリケーション開発に挑んだ 、という具体的な構成図含めてのご発表でした。ビジネスdアプリのサーバレスアーキテクチャについては、いずれ社外でも講演予定とのことです。 結果として、アプリケーションの開発のみに集中して取り組む体制を構築することにつながり、順調にリリースに向けた準備が整っているとのことで、今後の展開がますます楽しみです! 聴講していた参加者の皆さまも、ご自身の経験にもとづくご質問を数多く寄せていただき、非常に活発なQ&Aになったと思います。 (執筆者 NTTドコモ/藤平 亮, NTTデータ/三浦 佑晟) クロージング これにて LT パート終了となりました。が、最後に私ごとではあるのですが Google Cloud Partner Top Engineer 2024 に選出されたこと を報告させていただきました! NTT コミュニケーションズからは初選出ということと、1 年間選出されることを目標に取り組んでいたこともあり非常に嬉しい報告となりました。 こちらの選出に関しては、 GINGER を盛り上げてくれているコミュ二ティメンバーや全面的に支援いただいているグーグル・クラウド・ジャパン合同会社様のご助力あってこそ だと思っています。この場を借りて皆さまに感謝申し上げます。 来年は GINGER から複数人の Top Engineer が誕生することを願って、本イベント終了となりました! (執筆者 NTTコミュニケーションズ/林 知範)
アバター
この記事は、 NTT Communications Advent Calendar 2023 10日目の記事です。 そして、DevOpsプラットフォームの取り組みを紹介する9回目の記事です。 Qmonus Value Stream については、 当プロダクトの連載記事 をご覧ください。 はじめに こんにちは、イノベーションセンターの Qmonus Value Stream チームに所属している松本です。 私たちQmonus Value Stream チームのミッションはNTTコミュニケーションズおよびNTTグループ向けDevOpsプラットフォームであるQmonus Value Streamを開発してプロダクトチームに提供し、プロダクトチームを課題解決に集中させることでプロダクトの成功に寄与することです。 本記事は、そんなDevOpsプラットフォーム Qmonus Value Streamを使ってユーザに体験してほしい「目的を選ぶだけで作るクラウドアーキテクチャ」を実現するための取り組みを紹介します。 また、今回、Qmonus Value Streamを無料でお使いいただける機会を作りましたので、詳しくは記事の最後をご覧ください。 プロダクトとして開発される内部向けプラットフォーム 私たちのチームが提供するQmonus Value Streamはプロダクトと位置付けられて開発されています。そのためチームにはプロダクトマネージャー(以下、PdM)が配置され、PdMは開発者と共に開発を進めています。私たちのPdMはQmonus Value Streamの開発から提供までの戦略を立て、実行、意思決定する責任者です。 私も以前は内部向けプラットフォームを提供するチームに所属していましたが、内部向けプラットフォームはコスト削減やポリシーの適用を主な目的としていることから利用を強制されることも多く、厳しいルールや機能不足から利用者には窮屈で不便なものだと認識されることもありました。Qmonus Value Streamはこのような過去の内部向けプラットフォームとは目的が違い、Developer Experienceを改善するものです。Qmonus Value Streamは社内規定で強制するものではなく、PdMがプロダクトチームに成功ストーリーを伝え、プロダクトチームが試して自ら採用を決定する方法で利用を促進しています。ここでいうプロダクトチームは社内やグループ内で特定プロダクトを成功させるために組織された、PdM・開発者・デザイナー等のチームです。 ユーザ体験を追求する取り組み 私たちのチームでは開発者もPdMと同様にユーザ中心思考でユーザ体験の追求に取り組んでいます。今回はペルソナの作成とユーザテストについてご紹介します。 ペルソナの作成 プロダクトとして内部プラットフォームを作る際にも他のプロダクト開発と同様にユーザの理解は大事になります。 私たちのチームは、ISPで働いてたメンバー、情シスで働いてたメンバー、新規事業の開発をしていたメンバーなどさまざまなバックグラウンドを持ったメンバーで構成されています。メンバーは「内製開発チームのテックリード」といっても想像できなかったり、若手のリーダーを想像したり、ベテランのマネージャ級の人物を想像することもありました。 私たちのチームは共通のユーザ像を描きたかったのでPdMのメンバーが集まってペルソナを作ることにしました。まずは、社内の16チームのテックリードにインタビューして、テックリードたちがどのような課題に取り組んでいるか、その課題の解決にどのような苦労があるかを聞き出すことができました。そしてインタビュー記録を読み、それぞれのペルソナを書き出し認識を合わせる作業をしてペルソナを書き上げています。 今まで他人が作ったペルソナを見ることはありましたが、本当にこのようなペルソナは存在するのかと懐疑的なところもありました。この取り組みでは、ペルソナを作るプロセスで実在するプロダクトチームにインタビューをしてユーザは何処に関心ごとがあるかを確認しましたので裏付けがあり納得感のあるものができたと思います。 複数のペルソナを作成したのですが、例としてその1つをご覧ください。大手企業のDX推進担当が開発効率・品質向上という課題に取り組んでいるが、本来やるべき仕事に力を入れられず、さらに自己研鑽する時間の捻出に苦労している様子が分かります。開発者にもペルソナを共有することで、今後は問題解決の手法を議論するときにもペルソナだったらどう感じるだろうという視点も加わることを期待しています。 ユーザテスト 今年になってチームはユーザテストのプラクティスを取り入れました。ユーザテストのやり方は goodpatchさんの記事 を参考にさせてもらっています。 私たちのプロダクトではゴールを定めて開発し、ゴールに達成したらユーザテストをしてその効果を確認します。 おおよそ3名から5名に社内から協力いただき、ユーザとして目的を達成するような操作をしてもらいます。ユーザには操作しながら考えていることを発話するようにお願いするので、私たちは操作する画面と表情と考えてることを観察し、受け入れられているのか、つまずいたり迷ったりしていないかを読み取っていきます。観察した結果やインタビュー結果を持ち寄りインパクト分析して次のアクションを導き出しています。次のゴールを設定しゴールを達成したら評価するというフィードバックループを繰り返しているのです。 私たちのチームでは、ユーザテストを開発者が担当しました。これまでの開発チームは直接ユーザの声を聞く機会がなく、ユーザがどのように利用しているか想像できないという課題がありましたが、ユーザテスト後に開発者へヒアリングしてみるとユーザテストは概ね好評で「チームだけでは気付けないことが得られた」「プロダクト開発には必要なことだと考えている」との意見がでています。 ユーザテストを始めた初期のインパクト分析の結果をご紹介します。検出された項目は、影響の大きさで効果問題・効率問題・満足度問題に分類されます。効果問題はユーザが操作を続けられないほどの大きな影響であることを示しています。さらに何人からその問題を検出したかで分類し、3人全員のユーザから検出されたということは、おそらくほとんどのユーザが困難に直面することになる事態を推定できます。このインパクト分析で赤くぬった箇所、黄色く塗った箇所の順に優先順位が高いと分かりますので、最も効果の高い部分から問題を修正できました。 「目的を選ぶだけ」というユーザ体験 私たちのチームはプロダクトチームのテックリードは本来優先して時間を使うべきビジネスロジックの設計や実装に時間を使うべきなのに、直接の顧客価値を生まないクラウドアーキテクチャの構築やCI/CDパイプラインにも多くの時間を使っていることに着目しました。マニュアルを読んで調べたり定義ファイルや設定ファイルを作成したり試行錯誤しなくても良いように、「目的を選ぶだけで」クラウドアーキテクチャが作成でき、その Infrastructure as Code(以下、IaC)を組み込んだCI/CDパイプラインを作成できるという体験をさせたいと考えました。 今回、ペルソナの作成やユーザテストを取り入れフィードバックループを回すことで得られた成果として、「目的を選ぶだけで」というユーザ体験を「主目的」と「オプション」を選択するUIを作成できましたのでご紹介します。 まず、主目的の選択イメージを示します。当初は3層アーキテクチャを選択する画面だったのですが、現在のバージョンでは「スタンダードなWebアプリケーション」を選択するように変わりました。これなら、3層アーキテクチャを知らない人であっても、構築するシステムの目的が分かっている人であれば利用できます。ドキュメントを読む必要も設定ファイルや定義ファイルを書く必要がないだけでなく、他のメンバーやテックリードに力を借りることもなくクラウドアーキテクチャが構築できるようになりました。 次に、オプションの選択イメージを示します。ユーザのアプリケーションには事業特有の要件があったり、組織によって共通で求められるセキュリティポリシー要件が定められていることもあります。この画面ではシステムの冗長化とアプリケーションの脆弱性試験のオプションが選択されています。冗長化の設定はミスに気づきにくい課題がありますし、アプリケーションのパッケージの脆弱性試験もツールの検証から始める必要があり導入は困難ですが、私たちのDevOpsプラットフォームを使えば画面で選択するだけで構築できます。これからも多様なアプリケーションで利用できるようにオプションを拡充していきます。 まだまだ改善は続く 「目的を選ぶだけ」のユーザ体験を作ってからも複数名にユーザテストをしたところ、概ね好意的な反応を得られたのですが、アプリケーションによってはまだ十分な拡張性を得られてなかったりエラー表示時に自力で解決できない場合があったりと、多くの気づきを得ることができました。私たちはまだフィードバックループを繰り返す必要がありそうです。 Qmonus Value Streamの実証実験へのお誘い この「目的を選ぶだけ」でクラウドアーキテクチャを作る体験を、NTTグループだけでなく一般のお客さまにも試してもらいたいと考えています。 そこで、DevOpsプラットフォームの商用化を目標として機能や操作性の課題について利用者からのフィードバックを目的とした実証実験を行います。トライアル利用をご希望される方は以下の応募フォームからお申し込みください。 応募開始日:2023年12月10日  1 トライアル利用可能期間:2024年2月1日 - 2024年9月30日  2 , 3 対象チーム 日本の企業であること 開発チームは内製で開発していること 開発チームはアジャイルに開発していること 開発チームにCI/CDやIaCに理解があること 開発で利用するサービスの導入に関する決定権を持っていること、もしくは決定権者に対して導入の提案ができること 応募フォーム: こちら からお申し込みください。数日中にこちらから折り返しご連絡させていただきます。 利用料:無償 その他:本実証実験でのトライアル提供における導入から運用まで、無償でNTT Communicationsがサポートします。 最後まで、ご覧頂きありがとうございました! それでは、明日の記事もお楽しみに! 応募者多数の場合、本DevOpsプラットフォームの提供をお待ちいただく、もしくはお断りする場合があります。 ↩ トライアル利用は1ヶ月間ですが、トライアル利用期間中であれば1ヶ月単位で延伸できます。 ↩ NTT Com都合でトライアルを中止もしくはトライアル期間を短縮や延伸する場合があります。 ↩
アバター
マイクロサービスアーキテクチャにおいては、個々が独立に選定したデータベースを持つ複数のサービスにまたがって、データの整合性を維持する必要があります。 そのための方法として、Sagaパターンと呼ばれる設計方法がありますが、Sagaでは分離性が欠如しておりLost Update等の異常が発生しかねません。 そこで本記事では、Sagaの分離性を高めるための実装におけるTipsを解説します。 目次 目次 はじめに 複数サービス間での整合性維持における課題 Sagaパターン Sagaを構成するトランザクション Sagaによって実現される安全性 原子性(Atomicity) 整合性(Consistency) 分離性(Isolation) 永続性(Durability) 異常を防止/軽減する実装 分離性の欠如が引き起こす異常 分離性の欠如への対策 Semantic Lock Commutative Updates Pessimistic View / Optimistic View Reread Value Version File By Value 終わりに 参考文献 はじめに この記事は、 NTT Communications Advent Calendar 2023 12日目の記事です。 こんにちは、クラウド&ネットワークサービス部の川瀬(GitHub: hkws )です。業務では主に、NTT Comで提供しているサービスのバックエンド開発を担当しており、特に社内外の複数サービスをオーケストレーションする部分を実装してきました。 本記事では、複数のサービス間でデータの整合性を維持するための方法であるSagaパターンを紹介し、Sagaの分離性の欠如により起きうる異常と、その予防・軽減策を解説します。 複数サービス間での整合性維持における課題 複数のサービス間で整合性を持ってデータを更新しなければならないケースとして、オンラインショッピングサービスでの商品の購入やキャンセルをするという場面を検討してみましょう。ここで、オンラインショッピングサービスは以下のように、注文管理サービス、在庫管理サービス、決済サービスにより構成され、個々のサービスが独自にデータベースを持つDatabase per Serviceの構成とします。 ユーザにより商品が購入されると、以下のような処理が実行されるとします。 A1 在庫管理サービスが、購入された商品の在庫を確保 A2 注文管理サービスが、注文内容を記録する A3 決済サービスが、決済処理を行う A4 配送サービスが、配送依頼を記録する A5 メール送信サービスが、ユーザに購入完了メールを送信 その後、もし商品購入のキャンセルがリクエストされた場合には、以下のような処理を行うとします。 B1 配送サービスが、配送依頼を取り下げる B2 在庫管理サービスが、購入された商品の在庫を解放する B3 注文管理サービスが、注文のキャンセルを記録する B4 決済サービスが、返金処理を行う B5 メール送信サービスが、ユーザに購入キャンセル完了メールを送信 商品の購入(A1 ~ A5)、および商品購入のキャンセル(B1 ~ B5)は、複数のサービスにまたがってデータの変更が生じる処理です。これらの処理はトランザクショナルに、すなわちひとまとまりの処理として行われなければなりません。そうでなければ、決済処理が完了しなかったのに注文は記録された(A3で失敗した場合)、在庫が確保されているのに対応する注文が存在しない(A2で失敗した場合)といったデータの不整合が生じうるからです。 このように、複数のサービス(参加者)にまたがった処理をアトミックに行うため、2相コミット(2PC)のような分散トランザクション管理を利用することも選択肢の1つかもしれません。 しかしながら、2PCには以下のような課題があります。 レプリケーションされていないコーディネータは、システム全体にとっての単一障害点になってしまいます。 各参加者は参加者全員がコミットできるまでロックを保持し続けるため、ロックが長期化する傾向にあります。特に、コーディネータに障害があると、参加者は未確定のトランザクションによるロックを保持したままになってしまいます。 トランザクションのコミットが成功するためには、すべての参加者が反応を返さなければなりません。参加者のいずれかが反応を返せなくなってしまうと、トランザクションは必ず失敗してしまうのです。 一貫性を維持すべきサービスの全てが、2PCをサポートする必要があります。 このうち、2点目と4点目の課題を解決するためのアプローチが、Sagaパターンと呼ばれる方法です 1 。マイクロサービス間の疎結合性を損なわず、データの整合性を維持することを目指します。 Sagaパターン Sagaとは、一連のローカルトランザクションのシーケンスによって、複数サービス間でのデータの整合性を維持する方法です。操作するリソースごとにトランザクションを分解してローカルトランザクションとし、それらは対応するデータベースを更新して、次のローカルトランザクションを実行するためのメッセージやイベントを発行します。これにより、各サービスにおいてロックの保持が長期化することを回避します。 Sagaパターンにおいては、個々のローカルトランザクションは単一データベースにおけるトランザクションと同等です。よって、個々のサービスが持つデータベースは、2PCのような分散トランザクションに対応する必要はありません。 ただし、独立にローカルトランザクションがコミットされてしまうことは、単一データベースのトランザクションや2PCのようなロールバックが、サービスに跨ってできないことを意味します。あるローカルトランザクションがロールバックされたとしても、それ以前にコミットされたローカルトランザクションによる変更は残ったままになってしまうのです。 そこでSagaでは、ローカルトランザクションによるコミットされた変更を打ち消すためのトランザクション(補償トランザクション)によりロールバックを行います。もちろん、補償トランザクションも失敗する可能性がありますし、ビジネス的な要件からそもそも補償トランザクションを実装できないこともありえます。その対策として、ローカルトランザクションが成功するまで自動的にリトライするよう実装しなければなりません。 Sagaを構成するトランザクション Sagaは複数のローカルトランザクションのシーケンスとして構成されますが、各トランザクションはその振る舞いによって、以下の3つに分類できます。これらに対する理解は、後述する分離性の欠如への対策を検討する上で有用です。 補償可能トランザクション(Compensatable Transaction) 補償トランザクションによってロールバックされうるトランザクション ピボットトランザクション(Pivot Transaction) Sagaを最後まで実行するか、ロールバックするかを判断するポイントになるトランザクション ピボットトランザクションがコミットされれば、Sagaは最後まで実行される 補償可能トランザクションや再試行可能トランザクションとは異なるトランザクションになるとは限らず、Sagaにおける最後の補償可能トランザクションや、最初の再試行可能トランザクションになることがある 再試行可能トランザクション(Retriable Transaction) いつかは成功することが保証されているトランザクション ロールバックが不要であるため、対応する補償トランザクションを持たない ピボットトランザクションの前に配置される 前述したオンラインショッピングサービスにおける商品購入キャンセルSagaを、このモデルに照らし合わせると以下のようになります。 Step 種別 サービス トランザクション 補償トランザクション 1 補償可能 配送サービス 配送依頼の取り下げ 配送依頼取り下げのキャンセル 2 補償可能 在庫管理サービス 在庫の解放 在庫の再確保 3 補償可能 注文管理サービス 注文キャンセルの記録 注文キャンセルの取り消し 4 ピボット 決済サービス 返金処理の実施 - 5 再試行可能 メール送信サービス ユーザへの購入キャンセル完了メール送信 - Sagaによって実現される安全性 2PCのような分散トランザクション管理を利用せず、Sagaという代替策を利用するデメリットとして、トランザクションが提供する安全性を享受できないことがあげられます。ここでは、ACID特性と呼ばれる4つの性質(原子性、整合性、分離性、永続性)をSagaがどれほど満たしているか検討します。 原子性(Atomicity) 原子性とは、いくつかの書き込みがオールオアナッシングで行われるという性質です。グループ化された複数の書き込みが障害のため完了しなかった場合、その時点までに行われた書き込みは全て破棄されます。 MySQLやPostgreSQLでは、 START TRANSACTION によってトランザクションを開始し、複数の書き込みを行っている最中に障害が発生した場合、 ROLLBACK によって変更を取り消すことができました。しかしSagaパターンの場合、複数のローカルトランザクションのシーケンスにより書き込みを行うため、今までに行った変更の全てを単一のコマンドで取り消すことはできません。Sagaでは、補償トランザクションを順々に実行していくことで、ロールバックを実現します。 一般には、補償トランザクションによりロールバックできる点を踏まえ、Sagaは原子性を満たすと考えられています。しかしながら、原子性の実現には補償トランザクションが行うような巻き戻し機能だけでなく、分散合意や状態遷移先の出力といった機能も必要であり、Sagaにはそれが不足しているという 指摘 もあります。 整合性(Consistency) トランザクションの文脈において「整合性がある」とは、データについて常に真でなければならない何らかの言明(不変性)を満たしていることを示します。例えば、会計システムにおいて貸方と借方が等しくなければならないことは、会計システムにおける不変性の1つと言えるでしょう。 整合性は、原子性、分離性、永続性とは異なり、データベースのみで保証してくれる特性ではありません。外部キー制約やユニーク制約等があるとはいえ、アプリケーションがデータベースへ不変性に違反する書き込みを行ったとしても、データベースはそれを止めることはできません。 このことは、Sagaパターンの場合も同様です。Sagaパターンによって複数のサービスに書き込みを行う場合、単一のデータベースに対する書き込みと同じく、不変性を満たすようSagaを実装しなければなりません。 Sagaオーケストレーションフレームワーク Eventuate Tram の開発者であるChris Richardsonは、サービス内の参照完全性はローカルデータベースによって実現され、サービスの壁を超えた参照完全性はアプリケーションによって実現されるため、整合性を有すると 主張 しています。しかし、Sagaというアイデア自体が整合性を担保する仕組みを内包しているわけではないことから、筆者は整合性を有するとは言えないと考えています。 分離性(Isolation) 分離性とは、並行に実行されているトランザクション同士が、お互いに干渉できる度合いを示したものです。特に、複数のトランザクションを同時に実行した結果が、それぞれを順番に実行した時と同じ結果になることを保証する場合、直列化可能という最も強い分離性を有することになります。 Sagaは明らかに、分離性を満たしていません。Sagaの個々のローカルトランザクションによって変更がコミットされると、Sagaが全ステップを実行し終えていなくとも、その変更が他のSagaからも見えてしまいます。 永続性(Durability) 永続性とは、トランザクションのコミットが成功したら、仮にハードウェアの障害やデータベースのクラッシュがあったとしても、そのトランザクションで書き込まれた全てのデータは失われないという特性です。 Sagaパターンにおいて、永続性はローカルトランザクションのコミットによって実現されます。 このように、Sagaは分離性のみが欠如しているという意見がある一方、理論的な検討から原子性、整合性、分離性の3つが欠如しているという指摘もあるようです。 本記事では以降、両者が共通して指摘している分離性の欠如について、それが引き起こす異常とその軽減策を解説します。 異常を防止/軽減する実装 分離性の欠如が引き起こす異常 Sagaも単一のデータベースと同様、同時に複数のクライアントから実行される可能性があるものの、分離性を満たしていません。そのため、以下のような問題の発生が考えられます。 Lost Updates: ある Saga が、別の Saga による変更を読み取らずに書き込みを行うこと Dirty Reads: Saga が更新をまだ完了しないうちに、トランザクションまたは Saga がその更新を読み取ること Fuzzy/Nonrepeatable Reads: 読み取りと読み取りの間にデータ更新が発生したため、別の Saga ステップが別のデータを読み取ること MySQLやPostgreSQLでは、このような分離性の問題に対応するため複数のトランザクション分離レベルを備えています。しかしSagaでは、これらの問題を防止・軽減するための実装をアプリケーション開発者が行わなければなりません。 分離性の欠如への対策 Semantic Lock 分離性を高める方法の1つは、アプリケーションレベルでロックすることです。 作成または更新するレコードにフラグを設定することで、このレコードがコミットされておらず、変更される可能性があることを示します。 Semantic Lockを導入する際、当該レコードに対して別のSagaが読み取りや書き込みを試みた場合に、そのSagaがどのように振る舞うかも検討しなければなりません。 最も素朴な方法は、変更される可能性のあるレコードに対する操作を試みたSagaの実行を失敗させ、クライアントに再試行させることでしょう。これは、実装が単純なものの、クライアントに再試行の負担を強いることになります。また、ロックが解放されるまでリクエストをキューイングするという方法もあります。こちらはクライアントの再試行の手間を無くすことができますが、ロックの管理やデッドロックの検出など、サーバ側の実装が複雑になります。 前述したオンラインショッピングサービスの商品購入は、Semantic Lockの導入により以下のようなシーケンスになるでしょう。 注文内容の記録を1ステップ目(A'1)で実施し、その際注文ステータスを「処理中」に設定するよう変更しました。そして、新たに6ステップ目(A'6)を追加し、注文ステータスを「出荷準備中」にしています。このようにすることで、ステータスが「処理中」である注文は変更される可能性があることを示すことができます。 その上で商品購入キャンセル Saga では、以下のようにキャンセルしようとする注文が処理中かどうかを確認し、処理中である場合はキャンセル処理を中止します。これにより、商品購入と商品購入キャンセルの分離性を高めることができます。 Commutative Updates 分離性の欠如によって生じるLost Updatesは、正しい順序で更新操作が実施されなかった場合に発生する問題です。そこで、どのような順序で実行されても正しい更新結果が得られるよう、更新の仕方を工夫しようというアイデアがCommutative Updatesです。 銀行口座への振り込みや引き落としは、(引き落とし額が預金額を上回らない限り)可換な更新の一例です。例えば、残高が300,000円である銀行口座に対して、給与210,000円の振込と家賃70,000円の引き落としが同時にあったとしましょう。このとき、「口座残高を510,000円にする」という操作A、および「口座残高を230,000円にする」操作Bは可換ではありません。A -> Bの順序で実行された場合には給与の振込操作Aが、B -> Aの場合は家賃引き落とし操作Bがロストしてしまいます。しかし、「口座残高を210,000円増やす」という操作A'と「口座残高を70,000円減らす」という操作B'は可換です。どちらが先に実行されても、口座残高は440,000円になります。 よって、補償可能トランザクションを「口座残高を210,000円増やす」、対する補償トランザクションを「口座残高を増やす前(ここでは300,000円)の値に設定し直す」と定義してしまうと、後者は可換な更新ではないためLost Updatesの可能性が生じます。しかし、その補償トランザクションを「口座残高を210,000円減らす」と定義することで、他のSagaによる更新を上書きする危険を低減できます。 Pessimistic View / Optimistic View 開発しているアプリケーションの問題領域によっては、特定の属性について上限や下限を設定しなければならないことがあります。例えばコンサートの残空席は、(ビジネス上オーバーブッキングを許容する場合を除き)0を下回ることは許されません。 しかし、分離性の欠如によりDirty Readsが発生することで、定められた下限や上限を超えてしまう可能性があります。 Sagaでは、原子性を実現するために補償トランザクションが定義されますが、通常直近に実行した補償可能トランザクションに対する補償トランザクションから順に実行されていきます。すべての補償トランザクションが即座に実行されるわけではないため、Sagaの初期のローカルトランザクションで更新される値ほど不整合な値をとる時間が長くなってしまいます。 以下のシーケンス図にて具体例を示します。これは、前述したオンラインショッピングサービスにおいて、ユーザAにより商品Xの購入のキャンセルと、ユーザBによる商品Xの購入が同時に発生したケースです。ユーザA、ユーザBともに注文した商品Xは1つであり、ユーザAによる購入によって商品Xは在庫がなくなっていたものと仮定します。 ユーザAは商品購入のキャンセルを試みましたが、決済処理でエラーが発生したため、補償トランザクションが実行開始されたとします。このときから在庫解放処理に対する補償トランザクションが実行されるまで、在庫は開放された状態になっています。よってその間に同じ商品の購入をユーザBが試みると、在庫があるように見えるため成功してしまいます。しかし実際は、ユーザAによる商品Xの購入キャンセルは失敗したため、依然としてユーザAも商品Xを購入できている状態です。結果、提供できる商品は1つしかないものの、ユーザA、ユーザBともに購入できてしまいます。 Pessimistic Viewは、Sagaのステップの並びを工夫することによって、Dirty Readsが発生する可能性を小さくしようというアイデアです。 先程の商品購入キャンセルを行うSagaの場合、以下のように並び替えることで、商品在庫のDirty Readsを低減できるでしょう。商品在庫の解放を再試行可能トランザクションとすることで、在庫の再確保を不要にできるからです。 Step 種別 サービス トランザクション 補償トランザクション 1 補償可能 配送サービス 配送依頼の取り下げ 配送依頼取り下げのキャンセル 2 補償可能 注文管理サービス 注文キャンセルの記録 注文キャンセルの取り消し 3 ピボット 決済サービス 返金処理の実施 - 4 再試行可能 在庫管理サービス 在庫の解放 - 5 再試行可能 メール送信サービス ユーザへの購入キャンセル完了メール送信 - 一般化すると、Pessimistic View / Optimistic Viewは以下のように実装されます。 下限が存在するリソース(在庫や座席など)について ユーザの選択肢を制限する操作(座席予約など)は、補償可能トランザクションで行う ユーザの選択肢を増やす操作(座席予約のキャンセルなど)は、再試行可能トランザクションで行う 上限が存在するリソース(利用可能な課金額など)について ユーザの選択肢を増やす操作(利用可能な課金額の増加など)は、補償可能トランザクションで行う ユーザの選択肢を制限する操作(利用可能な課金額の減少など)は、再試行可能トランザクションで行う Reread Value データ更新が発生する個々のステップの中でも、分離性を高めるための工夫が可能です。 他のサービスに対するデータ更新を要求する直前に、更新対象データを要求することで、直近の状態に対して更新をかけることができます。 ただし、他のサービスとの通信が増えてしまうため、速度が重要になるサービスにおいては適用すべきではないかもしれません。 また、更新対象データの取得後から更新するまでの間に、別のクライアントによってデータが更新された場合には対応できません。 Version File 同時並行に複数のSagaが実行される場合、連携する各サービスにどのような順序でリクエストが届くかはわかりません。 例えば、商品購入と商品購入キャンセルが同時に実行された場合を考えます。注文管理サービスには「注文を記録する」リクエストと「注文をキャンセルする」リクエストが届きますが、Semantic Lockを使っていなければ、「注文を記録する」リクエストのあとに必ず「注文をキャンセルする」リクエストが届くとは限りません。まだ注文が存在しないのに、注文のキャンセル要求だけが届いてしまうこともありえます。 この対策として、注文管理サービスにおいてどのようなリクエストが届いたかを記録しておき、正しい順序で実行するという手段がありえるでしょう。「注文をキャンセルする」リクエストが「注文を記録する」リクエストより先に到着したとしても、注文管理サービスは「注文を記録」した後に「注文をキャンセル」するのです。 By Value By Valueは、リクエストの内容によって動的にロジックを切り替えるという手法です。例えばリスクの低い要求は saga で実行し、リスクの高い要求は分散トランザクションで実行するのです。 なにがリスクであり、それをどのように分類するかは、ビジネス要件を考慮しながら定義する必要があるでしょう。 オンラインショッピングサービスの場合は、法人からの大口の注文は分散トランザクションによって処理し、個人からの小口の注文についてはSagaで処理するといったロジックの切り替えがありえるかもしれません。 終わりに 本記事では、Sagaの弱点の1つである分離性の欠如に由来する問題を予防・軽減ためのさまざまな方法を解説しました。 これらの方法全てを活用することが、いつも正しいとは限りません。システムに対する要求と照らし合わせながら取捨選択し、安全性の高いサービス間連携を実現していきましょう。 それでは、明日の記事もお楽しみに! 参考文献 マイクロサービスパターン 実践的システムデザインのためのコード解説 データ指向アプリケーションデザイン ― 信頼性、拡張性、保守性の高い分散システム設計の原理 ソフトウェアアーキテクチャ・ハードパーツ ― 分散アーキテクチャのためのトレードオフ分析 Frank, Lars, and Torben U. Zahle. “Semantic Acid Properties in Multidatabases Using Remote Procedure Calls and Update Propagations.” Software, Practice & Experience, vol. 28, no. 1, 1998, pp. 77–98, https://doi.org/10.1002/(SICI)1097-024X(199801)28:1<77::AID-SPE148>3.0.CO;2-R. Frank, L. “Countermeasures against Consistency Anomalies in Distributed Integrated Databases with Relaxed ACID Properties.” 2011 International Conference on Innovations in Information Technology, IEEE, 2011, pp. 266–70, https://doi.org/10.1109/INNOVATIONS.2011.5893830. Garcia-Molina, Hector, and Kenneth Salem. "Sagas." ACM Sigmod Record 16.3 (1987): 249-259, https://doi.org/10.1145/38713.38742. Sagaを導入しても1点目と3点目の課題は残ります。Sagaは一般にオーケストレーションベースで実装されることが多く、オーケストレーターが単一障害点になります。また、Sagaでも参加者のいずれかが反応を返せなくなった場合は実行に失敗し(失敗箇所が補償可能な場合は)ロールバックされます。 ↩
アバター