この記事は、 ニフティグループ Advent Calendar 2024 20日目の記事です。 はじめに こんにちは。会員システムグループに所属しています、たかたか と申します。 最近、健康への意識が高まり、運動を始めました。ただし、食事制限はまだ行っていません。無理をせず徐々に健康体へ近づいていければと思っています。 さて、本ブログでは、最近起こしてしまった失敗について書いていこうと思います。エンジニアとしての仕事や日常生活の中で、もしかしたら起こるかもしれないミスや失敗を共有することで、皆さんの参考になればと思います。 起こったこと とあるシステムのリプレイスのメンテナンスが終了した後、それは起こってしまいました。 なんと、HTTPSでの接続で証明書の検証がうまくいかず、サーバー間の通信ができなくなってしまったのです。 まあ、そんなこともあるよね、と感じていただけている方もいるかと思いますがその諸々について書いていこうと思います。 まずは証明書の検証について確認 まず、証明書の検証について、簡単に説明させていただきます まず、クライアントがサーバーにHTTPS接続をお願いします サーバーは自分の身分証明書(デジタル証明書)をクライアントに見せます クライアントは以下のようなチェックを行います: 信頼できる機関が発行した証明書かどうか確認 証明書の親子関係(証明書チェーン)が正しいか確認 証明書の期限が切れていないか確認 証明書が無効になっていないか確認 証明書に書かれている名前と接続先が同じか確認 すべての確認がOKだった場合、安全な通信路を作ります 暗号化のための鍵を作って交換 安全な通信をスタート このチェックの中で一つでも問題があると、接続できなくなります。 経緯 AWSのマネジメントコンソールから必要なリソースを作成し、システムの構築を進めていました。その中で、サーバー間の通信にはHTTPSを使用することになっていたのですが、中間証明書とサーバ証明書の設定を間違えてしまいました。 この設定画面です 本番環境へのデプロイ前の検証では、curlコマンドで動作確認を行ってしまい、問題に気付くことができませんでした。 仕組み的にどうすればよかったのか 証明書の検証について、今回原因となったのは設定を間違えてしまったというところが根本的な原因です。しかし、ちゃんと動いているかどうかの確認で、HTTPのリクエストを送るツールが証明書をどのように、どのような環境で検証しているかというところも大事なポイントだと思っています。 サーバー証明書はPEM形式で サーバー自身のサーバー証明書 中間証明書 ルート証明書(通常はサーバーには含めません) の内容を記載することができます。 # サーバー証明書 -----BEGIN CERTIFICATE----- hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge -----END CERTIFICATE----- # 中間証明書 -----BEGIN CERTIFICATE----- hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge -----END CERTIFICATE----- # ルート証明書(通常はサーバーには含めません) -----BEGIN CERTIFICATE----- hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge -----END CERTIFICATE----- webブラウザなどは中間証明書の内容がなくてもサーバー証明書の内容から補完することができたりしますが、スクリプトからhttpリクエストを送信しようとすると失敗することがよくあります。 サーバー証明書の設定は、使用するツールや環境によって検証結果が異なる場合があります。実際の運用環境では、複数のツールで検証を行うことが重要です。それでは、具体的な検証例を見ていきましょう。 検証 試しに、記載されているチェーンが不完全な証明書を用意し、検証してみましょう。 設定を間違った証明書 # サーバー証明書だけを書く -----BEGIN CERTIFICATE----- hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge hogehogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge -----END CERTIFICATE----- 以下のツールを使用して、私の端末で検証したいと思います curl 何かhttpリクエストを送りたいときよく使いますよね。 今回は動作検証をこれだけで実施してしまったので、失敗に気づけなかったです。 基本的に、ローカルに保存されている証明書ストアの情報と照合してちゃんと検証が行われるはずですが、私の使用している端末ではたまたまラッキーで証明書の検証が通過してしまいました。 pythonのrequestsモジュール pythonでhttp通信を行うときの鉄板ですよね。certifiというライブラリに保存されている証明書群に依存しており、ブラウザのように証明書の情報を自動で補完したりすることはないです。誤った証明書が設定されている等の原因で通信ができなくなったりします。 openssl 証明書の検証といえばこれが使用されます。 証明書のチェーンが間違っていれば自動補完とかはせず、検証に失敗しますので、ここがOKなら大丈夫でしょう。 ※入力・出力結果は一部改変しています curlの実行結果 $ curl -D - <https://example.com> HTTP/2 200 server: Apache/2.4.6 (CentOS) cache-control: no-store, no-cache content-type: text/html; charset=UTF-8 date: Mon, 16 Dec 2024 07:40:40 GMT pragma: no-cache set-cookie: dnzHashcmd=fin; content-length: 6879 コンテンツ ... 普通に返ってきましたね。 何か異常は見受けられません requestsの実行結果 Python 3.11.0 (main, Mar 15 2024, 14:30:35) [Clang 15.0.0 (clang-1500.0.40.1)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import requests >>> requests.get("<https://example.com>") requests.exceptions.SSLError: HTTPSConnectionPool(host='example.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:992)'))) 失敗しておりますね。 エラー内容を見ると、SSLCertVerificationErrorの文字が。 opensslの実行結果 $ openssl s_client -connect example.com:443 -showcerts 0 s:/C=JP/ST=Tokyo/L=Minato-ku/O=Example Corporation/CN=www.example.co.jp i:/C=JP/O=Example CA/CN=Example Intermediate CA -----BEGIN CERTIFICATE----- hoge(サーバー証明書の内容)hoge -----END CERTIFICATE----- Server certificate subject=/C=JP/ST=Tokyo/L=Minato-ku/O=Example Corporation/CN=www.example.co.jp issuer=/C=JP/O=Example CA/CN=Example Intermediate CA --- SSL handshake has read 2482 bytes and written 757 bytes Verification error: unable to verify the first certificate --- New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256 Protocol: TLSv1.3 Server public key is 2048 bit This TLS version forbids renegotiation. Compression: NONE Expansion: NONE No ALPN negotiated Early data was not sent Verify return code: 21 (unable to verify the first certificate) ... Verification errorが発生してますね。 このような結果から 次のような教訓を得ることができました。 第一に、サーバー証明書の検証は使用するツールによって結果が異なる可能性があるため、複数のツールで確認することが重要。 第二に、curlコマンドだけでの検証は不十分で、より厳密な検証ツール(opensslなど)を併用する必要があります。 最後に 使用している技術への理解を深め、起こりうるミスのパターンを網羅できれば完璧なのですが、中々そうはいきませんよね。 エンジニアとしての活動がまだまだ短い私ではありますが、これから沢山経験を積んで成長していければと思っています。 明日は、swimland0306 さんの「ニフティにSREとして入社して2年間でやってきたこと」です。 お楽しみに!
前編の記事ではニフティのサーバーストレージチームの業務について色々伺いました。 後編では素顔に迫ります! 自己紹介 T.Yさん ■入社年度 2018年01月 ■主担当サービス 仮想化基盤/仮想デスクトップ環境の設計・構築・運用/クラウドサービスの契約管理・コスト管理 ■趣味 休日は朝Netflixを観て午後に出かけるのが日課です。 D.Nさん ■入社年度 2022年12月 ■主担当サービス 仮想デスクトップ環境の構築・運用/クラウドアカウントや証明書発行の運用/全社クラウドコストの管理 ■趣味 ランニングと休日のはしご酒です。2025年は大阪マラソン走ります。 S.Sさん ■入社年度 2019年4月 ■主担当サービス クラウドサービス関連の運用 ■趣味 辻村深月が好きです。 過去に頑張ったエピソードを教えてください! T.Yさん 仮想化基盤のリプレイスですね。旧基盤がどう動いているかの情報もなく、全く分からない状態でした。そこから要件定義から構築まで一通りやったのでとても大変でしたね。業務を止めてはいけないのに、冗長構成になっていない環境もあったりしましたがトラブルなくやり切りました。 D.Nさん まったく問題が起きなかったですもんね。すごい。 S.Sさん 仮想化基盤は当時の構築者がいなくなってしまっていて、ナレッジも残っておらず本当に大変そうでした。トラブルゼロはすごいですね。 T.Yさん しっかりと仮説を立てて取り組みました。既存を踏襲せず、ゼロベースで要件定義からやったのが成功の鍵になったと思います。 D.Nさん サポートセンターの方が業務で利用する仮想化基盤を使っているのですが、リプレイスを実施したことですね。仮想化基盤上で動くツールが移行後もちゃんと動くようにするために走り回りました。ニフティは歴史が長い会社なので、もう社内に分かる人がいないシステムもあったりして大変でしたがどうにか無事に終えることができました。 T.Yさん 仮想化基盤も従来の構成から新しい構成に変えることのメリットの説明などもやりましたね。 クラウドチームはどうですか? S.Sさん 私はAzureの導入ですね。完全に0から始めて、Azureが何かも分からない状況だったところをニフティで使えるようにしたところです。実際に触ったり、Microsoftの方に聞いてたり、資格(Microsoft Fundamentals)をとってみたりして学習からはじめました。 T.Yさん もともとクラウドのナレッジがあるチームではなかったので、かなりハードルは高かったと思いますがやりきったのは凄いなと思います。誇らしいです! S.Sさん ふりかえってみれば、イチから始める機会をいただけてよかったなと思います。 D.Nさん ベンダーさんとの調整もカウンターでやってくださってたので凄いなと思います。 休日はどう過ごしていますか? D.Nさん ランニングがシュミなので10㎞走っています。フルマラソンにも出ています。お酒が好きなので家や外で飲んだりします。 T.Yさん 会社のブカツのマラソン部(アクティ部)に入っていますよね。 D.Nさん はい、皇居ランを2週に一回くらいやっています。 T.Yさん 平日の夜はD.Nさんや、他のメンバーを誘ってNIF BAR(定時後オープンする社員用の無料バー)にいったりします。そうじゃない日は、僕はお酒が大好きなので場末の飲み屋に一人で突撃したりしますね!休日は映画を観に行ったり美術館にいったりします。 S.Sさん 映画や美術館に出かけられるんですね!T.Yさんはとにかくお酒好きなイメージが強くて意外でした。笑 最近別のチームのマネージャーからT.Yさんは飲むとすごいよ!て聞きました。笑 T.Yさん そんなことないですよ!笑 D.Nさんはそのマネージャーや、中途入社の方と飲みに行ったりされてますもんね。 D.Nさん 新しく入社された方がいたら飲みにいったりしますね。中途入社で社歴が浅い方同士のネットワーキングの場があります。 S.Sさん 皆さん活発ですね!私は休日は家にいることが多いですね。 D.Nさん S.Sさんは本が好きなんですよね。 S.Sさん はい、辻村深月さんの著作が好きです。 T.Yさん ボードゲームも好きなんじゃなかったっけ?西新宿にボドゲカフェあったと思いますよ。あと、会社のブカツにボードゲーム部もあるみたいです。 S.Sさん 今度行ってみます! いま楽しみなことは何ですか? D.Nさん 旅行とフルマラソンを兼ねることが多いですが、来年は大阪マラソンにいって大阪グルメを満喫したいと思います。 T.Yさん 音楽グループをやっている友人のライブの単独公演にいくのが楽しみです。 S.Sさん 半年後に海外旅行に行こうと思っています。20代最後の思い出作りにいってきます! T.Yさん 海外旅行いいなあ。私も行きたいんですけどね… D.Nさん クラウドもそうですけど、円安でなんでも高いですよね…。でも、海外旅行はいけるときに行っておきたいですよね。お土産待ってます! 前編もご覧ください! 今回はニフティのサーバーストレージチームのインタビューの様子をお届けしました。 あわせて前編もご覧ください。 【インタビュー】ニフティのサーバーストレージチームに業務について色々聞いてみた! ニフティでは、さまざまなプロダクトへ挑戦するエンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトよりお気軽にご連絡ください! このインタビューに関する求人情報 /ブログ記事 サーバーストレージチームの求人情報 .is-style-rounded + .has-text-color{ font-size:95%; }
自己紹介 T.Yさん ■入社年度 2018年01月 ■主担当サービス 仮想化基盤/仮想デスクトップ環境の設計・構築・運用/クラウドサービスの契約管理・コスト管理 ■趣味 休日は朝Netflixを観て午後に出かけるのが日課です。 D.Nさん ■入社年度 2022年12月 ■主担当サービス 仮想デスクトップ環境の構築・運用/クラウドアカウントや証明書発行の運用/全社クラウドコストの管理 ■趣味 ランニングと休日のはしご酒です。2025年は大阪マラソン走ります。 S.Sさん ■入社年度 2019年4月 ■主担当サービス クラウドサービス関連の運用 ■趣味 辻村深月が好きです。 担当業務について 担当業務について教えてください。 T.Yさん 社内システムのインフラを仮想化している環境(以下、仮想化基盤)と、クラウドの管理を実施しており、それぞれについてチームを分けています。 仮想化基盤チーム 保守 ベンダー調整 機器の調達 仮想化基盤上で動いているシステム クラウドチーム マルチクラウド化推進 コスト管理 システムの開発 クラウドチームでコスト管理のためのシステム開発を行っているそうですが、使用している技術を教えてください。 S.Sさん AWSでPythonを使っています。コストはSlackやスプレッドシートに連携し、各クラウド担当者に入力してもらってデータを収集しています。 集めたデータはどう活用しているんですか? S.Sさん 先々の予測を行い、改善を図っています。過剰な運用を行っている担当者に直接働きかけも行っています。 T.Yさん 各担当者にコスト意識を根付かせたいという目的があります。予測と実績の乖離をなくしていきたいですね。 コスト削減の効果はどれくらいでしたか? D.Nさん 2年間で25%削減できています。 T.Yさん 着手してすぐに予実の乖離が億単位で起きていることが分かり、会社としての課題として挙がりました。コスト管理を本格的に始めたところ、キャッシュアウトが抑制できたうえ、担当者のコストへの意識も高まりました。 円安の影響はありましたか? T.Yさん 円安の影響は多少ありますが、AWSは長期で利用すると割安で使えるプランもあってそれでもなお削減はできています。 D.Nさん 為替予約なども活用しています。経理と連携しながら円安と戦っています! コストダウンをしてもらうにあたって、各チームに施策をしてもらうために苦労していることや工夫した点はありますか? S.Sさん まずマネージャーにコスト意識を持ってもらい、マネージャーから社員に働きかけてもらうようにしました。 予実5% or 20,000 乖離が出たらSlackでアラートが鳴るようにしています。 ツンデレちゃんが怒ってくれます。 T.Yさん コスト管理を行うまでに、そもそもコスト意識がなかったと思いますが金額の乖離の報告や予測を毎月あらかじめ登録してもらうことで自然と意識が高まるようにしました。情報が可視化されたというところが大きいですね。 業務をやっていてやりがいを感じたり、嬉しかったことがあれば教えてください。 T.Yさん コスト管理の業務でいうと、結果が反映されてきて会社に貢献できているところですね。チームメンバー全員感じていることだと思います! D.Nさん コストのグラフが下がっていると嬉しいですね。 S.Sさん クラウドの管理を通じて、AWS、Azureなどクラウドの基盤側の深い部分を触って詳しくなれるのが嬉しいですね。 MultiAccountの仕組みなどは普段AWSを使っていると見えない部分なので、そのあたりが知られて良かったです。 クラウドの知識が必要そうですが、どう工夫しているんですか? T.Yさん カテゴリ別に体制を分けています。仮想化基盤、クラウドチームで分けていて、チーム間で情報共有や、システムの仕様に関する理解の向上目的で実機を動かしたりと業務共有を進めていて、いずれは人材交流できたらいいなと思っています。 メンバーごとに理解度が異なるので、それを可視化して理解度に応じたカリキュラムを作りたいですね。 D.Nさん 私はどちらのチームにも属しています。 仮想化基盤のことは、入社してからT.Yさんに99%くらいは教えていただきました! T.Yさん 仮想化基盤の経験がない方でもサポートします。 D.Nさん 個人的にいいなと思ったのが、検証環境を設けていただいて色々実験できるのが嬉しいです。 仮想化基盤チームで伸ばせるスキルは? T.Yさん クラウドがどういう仕組みで動いているかが体系的に分かります。クラウドを使っていても、その仕組みまで理解している人はあまりいないのではないでしょうか。 チームについて チームの構成を教えてください。 T.Yさん サーバーストレージチームのサブチームリーダーは私ですが、6年目のS.Sさんが開発チームのリーダーとして立ち回ってくれています。 S.Sさん メンバーは中途1年目の方と派遣社員の方と社歴が浅いので、ドメイン知識の共有や業務のサポートをしています。 インフラエンジニアの役割は会社によって違いがありますよね。中途採用の場合、応募者のスキルセットと求める人材像にギャップを感じることはありますか? T.Yさん 全く違ったというケースはないですね。ただ、サーバーストレージチームという名前と実際の業務との乖離があるかもしれないですね。笑 D.Nさん そうですね。私は「コスト管理までやるんだ!」という驚きがありました。 現在のチームにはどういった課題がありますか?それに対してどうやって取り組んでいますか? T.Yさん プロダクトと業務が多すぎて、課題にスピード感もって取り組めていないことですね。仲間が増えたらあんなことやこんなこともやりたいな…と思っています。 D.Nさん Azureの利用も始まっていて、AzureもAWSと同じようにコスト管理していく必要があったり、課題はまだありますね。 T.Yさん 業務に注力できるように、プロダクトの優先順位付けや、体制を分けて、やる業務を限定することでタスクの消費速度を上げています!一緒にがんばってくれる仲間を募集しています。 後編に続きます! 今回はニフティのサーバーストレージチームのインタビューの様子をお届けしました。続きは後編の記事をご覧ください。 【インタビュー】ニフティのサーバーストレージチームってどんな人がいるの?本人たちに聞いてみた!【後編】 このインタビューに関する求人情報 /ブログ記事 サーバーストレージチームの求人情報 .is-style-rounded + .has-text-color{ font-size:95%; }
この記事は、 ニフティグループ Advent Calendar 2024 18日目の記事です 1. はじめに こんにちは。ニフティ新卒1年目のSREチームに所属している滝川です。 AWS環境でTerraformを使ったCI/CDパイプラインを構築する際、認証情報の管理は避けて通れない課題です。これまで、”AWS Access Key方式”を使用する方法が一般的でしたが、セキュリティや運用の面から、最近ではIAMロールを活用する方法が注目されています。 この記事をご覧の方は、アクセスキー方式ではなくIAMロールを用いて認証を行いたい方、もしくは以下のようなエラーの解消を試みている方々だと思います。 Error: Credentials could not be loaded, please check your action inputs: Could not load credentials from any providers このエラーはGitHub ActionsがAWSリソースにアクセスするための適切な認証情報を見つけられない状況を示しています。 上記の課題はIAMロールを用いた認証方法の実装することで解決すると思います。手順を詳細に説明しているのでぜひ最後までお読みください。 2. 従来の認証方法: AWS Access Keyの使用 1. 仕組み AWS Access Keyは、AWSアカウントのアクセス許可を設定するためのキーです。 Access Key ID と Secret Access Key をGitHub Secretsに登録し、GitHub Actionsで環境変数として使用します。 以下は典型的なGitHub Actionsの設定例です。 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ap-northeast-1 GitHub Secretsには以下を保存します。 AWS_ACCESS_KEY_ID : IAMユーザーで作成したAccess Key ID AWS_SECRET_ACCESS_KEY :IAMユーザーで作成したSecrets Access Key 2. 課題 セキュリティリスク 長期的に有効なクレデンシャルであるため、流出した場合のリスクが大きい。 一度流出すると、取り消すまで悪用される可能性がある。 権限の制御が難しい IAMユーザー単位での権限付与になるため、CI/CD専用の細かな権限設定が難しい。 必要以上の権限が付与されやすく、最小権限の原則に反する可能性がある。 3. 新しい認証方法: IAMロールとGitHub Secrets 1. 仕組み AWSのIAMロールを使えば、長期的なアクセスキーを使わずに安全に認証を行えます。GitHub ActionsがAWSに一時的な権限をリクエストすることで、最小限の権限で操作を行います。 以下のように設定します。 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN }} aws-region: ap-northeast-1 GitHub Secretsには以下を保存します。 AWS_ROLE_ARN : 作成したIAMロールのARN 3.2 メリット セキュリティ向上 GitHub Actionsでのみ有効な一時的な権限を使用するため、クレデンシャル流出のリスクが低減。 一時的な認証情報は短期間で失効するため、万が一流出しても影響を最小限に抑えられる。 最小権限の適用が容易 IAMロールに対して必要最小限の権限を設定可能。 Assume Roleを使用することで、特定のリソースやサービスに対する権限を細かく制御できる。 CI/CD処理に必要な操作のみを許可するIAMポリシーを柔軟に設定可能。 4. 比較: AWS Access Key と IAMロール 項目 AWS Access Key IAMロール 運用負荷 高い 低い 追跡 難しい 容易 最小権限の適用 難しい 容易 5. 実装手順 Step 1: IAM OIDC IDプロバイダを追加 OpenID Connectを選択 プロバイダのURLに https://token.actions.githubusercontent.com 対象者に sts.amazonaws.com プロバイダを追加をクリック Step 2: IAMロールを作成 AWS CLIまたはAWSコンソールでIAMロールを作成します。以下の設定を行います。 IAMロールの信頼関係を設定 ロールを信頼するエンティティとしてGitHub Actionsを指定します。 { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::xxxxx:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" }, "StringLike": { "token.actions.githubusercontent.com:sub": [ "repo:xxxxx/*" ] } } } ] } ポリシーのアタッチ Terraformに必要な権限(例: S3, EC2, IAM)のみを付与します。 Step 3: GitHub Secretsに登録 GitHubのsetting→Actions secrets and variables→actionsからsercretsに以下の値を登録 AWS_ROLE_ARN : Step2で作成したIAMロールのARN Step 4: GitHub Actionsの設定 .github/workflows/terraform.yml (任意のファイル)で環境変数を定義する。 下記はterraformのCIの実装例 name: Terraform CI on: push: branches: - main jobs: terraform: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN }} - name: Setup Terraform uses: hashicorp/setup-terraform@v2 with: terraform_version: 1.9.8 - name: Terraform Init run: terraform init - name: Terraform Plan run: terraform plan 6. まとめ 従来のAWS Access Keyを用いた認証方法には、セキュリティと運用の課題がありましたが、IAMロールを活用することで、認証情報を容易でセキュアに設定することができます! ぜひ、この方法を試してみてください!
この記事は、 ニフティグループ Advent Calendar 2024 19日目の記事です。 はじめに おはようございます。IWSです。 少しいきなりにはなるのですが、みなさんはずんだもんは好きですか?好きですよね? 前々からゲーム実況の動画などで活躍していたり、「Cevio AI Song」 や 「NEUTRINO」 なども出て歌うずんだもんがいたりと見かける機会が多くなったのではないかと思います。 私も 「VOICEPEAK 東北きりたん」 を購入して一緒についてきた ずんだもん を喋らせて遊んだりしました。 ですが、喋らせて終わりでは少し寂しいし、私もずんだもんでなにかしたいなぁ〜と思ったのでちょっとやってみました。 やりたいこと ニフティには「もじこえ」 というテキストを投稿すると音声に変換してコミュニケーションが取れる社内Webアプリがあります。(もじこえについてはぜひ こちらのブログ をご覧ください) 音声は Amazon Polly を使って生成しているのですが、ここにみんなが大好きな ずんだもん を追加しようというのが目標です。 イメージとしてはこんな感じでしょうか もじこえが Node.js で作られているのでこちらもそれに合わせます。 クライアント側からテキストを送信してもらいサーバー側へ渡す。サーバー側はそれをVOICEVOXのコンテナへさらにリクエストを飛ばして音声データを生成してもらうという形です。 VOICEVOX 喋るずんだもんには「Cevio AI」や「VOICEPEAK」などありますが今回はAPIで利用したいというのもあるので「VOICEVOX」を使用します。 https://voicevox.hiroshiba.jp/ core , engine , editor の3種類がありますがAPIとして呼び出して音声合成ができればいいので VOICEVOX_engine を選んでいます。 https://github.com/VOICEVOX/voicevox/blob/main/docs/全体構成.md Docker Hubでイメージが公開されているのがありがたいですね https://hub.docker.com/r/voicevox/voicevox_engine docker-compose.yml にこれを追加するだけでコンテナを立ち上げられます voicevox: image: voicevox/voicevox_engine:0.20.0 container_name: voicevox ports: - '50021:50021' ずんだもんボイスを生成しよう VOICEVOXのAPIにリクエストを投げることで音声を生成することができます。 APIには「テキストを音声クエリに変換するAPI」と「音声クエリを音声データに変換するAPI」の2種類があり、2つのAPIをそれぞれ呼ぶことでテキストから音声データの生成までを行います https://github.com/VOICEVOX/voicevox_engine?tab=readme-ov-file#http-リクエストで音声合成するサンプルコード // テキストから音声クエリを作成 const query = axios.create({ baseURL: `http://voicevox:50021`, responseType: 'json', }); const queryResponse = await query .post(`/audio_query?text=${text}&speaker=3`) .catch((error) => { console.error(error); }); if (!queryResponse) { return ''; } // 音声クエリから音声を合成 const synthesis = axios.create({ baseURL: `http://voicevox:50021`, headers: { 'Content-Type': 'application/json', }, responseType: 'arraybuffer', }); const jsonString = JSON.stringify(queryResponse.data); const synthesisResponse = await synthesis .post(`/synthesis?speaker=3`, jsonString) .catch((error) => { console.error(error); }); if (!synthesisResponse) { console.log('音声の合成に失敗しました'); return ''; } APIを呼ぶ際にクエリパラメーターで speaker={id} とすることで音声ライブラリの好きなキャラクターに喋らせることができます。3 でずんだもん、8 で 春日部つむぎ に喋ってもらうことができ、一部キャラは喋り方を変えたりもできます。(セクシーずんだもんにしたりささやきずんだもんにしたり……) 音声ライブラリの一覧はコンテナを立ち上げて /speakers にアクセスすると見ることができます。 音声合成ができたらファイルに保存しておきます。 // 一意のトークンを生成 const token = uuidv4(); // Pathの指定と音声ファイルの保存 const audioPath = path.join( process.cwd(), 'public', 'audio', `${token}.mp3` ); fs.writeFileSync( audioPath, new Uint8Array(synthesisResponse.data as Buffer) ); const speakUrl = `/audio/${token}.mp3`; return speakUrl; トークンをつかって適当な名前で保存しているだけです。ファイルの保存ができたらそのファイルへのPathを返しています。 Pathを返しているのはクライアント側で /audio/${token}.mp3 を再生するという実装をしているからです。返ってきた speakUrl のURLを使用して const music = new Audio(speakUrl); music.play(); のようにしてあげればブラウザ側で音声が再生ができます。 コード全体 // VOICEVOX export const voicevoxTextToSpeakUrl = async (text: string): Promise<string> => { /** * テキストをVOICEVOXで音声に変換し音声ファイルのURLを返します。 * * @param text 音声に変換するテキスト * @returns 音声ファイルのURL、失敗した場合は空文字列 */ try { // 音声クエリを作成 const query = axios.create({ baseURL: `http://voicevox:50021`, responseType: 'json', }); const queryResponse = await query .post(`/audio_query?text=${text}&speaker=3`) .catch((error) => { console.error(error); }); if (!queryResponse) { return ''; } // クエリから音声合成 const synthesis = axios.create({ baseURL: `http://voicevox:50021`, headers: { 'Content-Type': 'application/json', }, responseType: 'arraybuffer', }); const jsonString = JSON.stringify(queryResponse.data); const synthesisResponse = await synthesis .post(`/synthesis?speaker=3`, jsonString) .catch((error) => { console.error(error); }); if (!synthesisResponse) { console.log('音声の合成に失敗しました'); return ''; } // 一意のトークンを生成 const token = uuidv4(); // Pathの指定と音声ファイルの保存 const audioPath = path.join( process.cwd(), 'public', 'audio', `${token}.mp3` ); fs.writeFileSync( audioPath, new Uint8Array(synthesisResponse.data as Buffer) ); const speakUrl = `/audio/${token}.mp3`; // 一度再生したら音声ファイルはもう使わないため削除 setTimeout(() => { fs.unlink(audioPath, (err) => { if (err) { console.error(`ファイル削除エラー: ${audioPath}`, err); } else { console.log(`ファイルが削除されました: ${audioPath}`); } }); }, 20000); return speakUrl; } catch (e) { console.error('error: ', e); return ''; } }; まとめ VOICEVOXをつかってずんだもんを触ってみました。APIが提供されているおかげでもじこえに簡単にずんだもんを組み込むことができましたね。これでずんだもんライフが充実します! ぜひ皆さんもVOICEVOXを使ってなにかやってみてください! 明日は @takatakanian さんの記事です!お楽しみに! クレジット VOICEVOX:ずんだもん
エンジニアリングマネージャーをしています芦川です。2024年12月16日に GitHub Universe 2024 Recap in ZOZO に参加、登壇させていただきましたので、そのご報告いたします。 イベント内容としては、冒頭、GitHub 服部様による GitHub Universe 2024 のRecap(めちゃくちゃ丁寧な説明、copilot初心者の私でも分かる内容!)、続いて株式会社ZOZO 山田様によるiOS開発におけるCopilot For XcodeとCode Completion(3番勝負が非常に面白かったです!copilotの勝ち!)、同社佐藤様によるGitHub Copilot のテクニック集(すぐにでも使えるショートカットやプロンプトの書き方満載でした!)というところで、短時間ながらも非常に濃密な時間でした。 私の方からは、GitHubの活用事例として、インナーソースの取り組みをメインに話させていただきました。Ask the speakerの時間では、「偶然、今日、社内でインナーソース部を立ち上げてインナーソースを始めたところです!」という方もいまして、神の力か何かで引き合わせてくれたのかな、という、すごい引力を持った場であり、有意義な時間を過ごすことができました。ありがとうございます。 スライド全体はこちらになります。 どんな会社?エンジニア組織の構造?GitHub導入状況? ここからは、スライドをピックアップしていき、話した内容を文章にしていこうと思います。 コラボレーション文化の話をするので、まず会社の組織構造についてお話しました。実際の職制とは異なりますが、チームトポロジーのチームタイプにわけるとざっくりこのようなエンジニア組織の構造になります。エンジニアは全部で約160名いまして、7-10名ほどのサブチームにわかれておりまして、内製開発がメインです。 注目するところとしては、ストリームアラインドがドメインの違いでざっくり2分割されている点だったり、あとは、コンプリケイテッドサブシステムでは、ISP独自のネットワーク部分を扱っているようなところでしょうか? 次にGitHub利用状況です。organizationは1つであり、Repository creationルールは、internalとしていて、組織内の誰でもリポジトリ閲覧可能をデフォルトにしています。一方、Copilot Metrics Viewerからみたcopilotの利用状況です。こちらの導入は全員ではまだありませんが、コード受け入れ率は31%であり、割といい感じなのかな、と思っております。言語はpython、エディタはvscodeが多いですね。 ここからインナーソースの話 ここからインナーソースの話です。 2022年の下期だったとおもいますが、私含めた周辺のマネージャーの中である悩みがありました。事業部からはもっとこうしたいという要望はたくさんでるのですが、開発リソースも限られている。外部発注もありますが、でも、キャッシュアウトせずにもっと内部でうまく回せるやり方や特定のチームへ開発依頼が集中してしまうような状況を打開するものはないかと。で、いろいろググったのですが、ここは衝撃的な出会いでした。この世の中には「インナーソース」というやり方があることを知りました。 最初に私が読んだのは、右のcodezineに乗っているという東芝の小林さんが書いた記事「 オープンソースの開発スタイルを企業内で実践するインナーソースとは? メリットとポイントを理解する 」です。ものすごくわかりやすかったです。で、これ導入したら余力のある人がほかのチームの開発手伝えるようになるのでは?と安易に思いまして、組織に「インナーソース」とやらを導入したいと強く思ったわけです。 でいろいろとやり始めました。そのあたりのくだりは、以下の記事のほうが詳しいですので割愛しますが、なかなかうまくやれてきたのでは?と思っています。 インナーソースを導入してみた その① お試し導入編 インナーソースを導入してみた その② 土台作り編 また、今回の発表では、組織にこういう新しい流れをいれるときにエンジニアリングマネージャーとしてどのように動いたかについて新しく触れました。生々しい話にはなりますが、私としては、以下のようなことをしてきました。 トップダウンで進んでいるようには見られたくなくて、1人現場メンバーの推進者を決め、そのメンバーから全社へ発信するように仕向けたこと 周りのマネージャーを巻き込み、事業計画としてインナーソース推進活動を公式化したこと インナーソースガイドラインでは、コントリビューターの評価や工数の考え方を含めたこと 業務の一環としてインナーソースコミュニティ運営に携われるようにしたこと このあたりもうまくいったのかなあ、と考えています。もう少し、細かい話が書けそうなので、いつか「新しい取り組みをボトムアップで組織に導入するには?」みたいに抽象化したお題で書いてみたいと思います。 インナーソースがうまくいっているところ 個人で作ったツールへのコントリビュートや、業務に少し踏み込んだところだと、API利用者定義ファイルの追加について明らかにリードタイムを短縮できた事例などを紹介しました。このあたりの話は、小松の下記の登壇内容でもありますので、ここでは割愛いたします。 インナーソースがうまくいっていないところ このブログでは、これまであまり語っていなかった、いまのところうまくいってないところを文章化しておきます。やってみていくと、ハマらないパターンもあることが体感としてわかってきました。例えば、ドメイン知識が違う同士のストリームアラインドチームのコラボレーションです。 ドメインが同じチーム同士であれば、ビジネスの方向が同じチームどうしてあれば、どちらのチームが提供しているAPIをお互い利用しながら、そこにコントリビュートが生まれる余地があるかと思うのですが、例えば、弊社の例でいうと、WEBサービスと、ISPサービスはかなり属性が違うというか、土俵が違うというか、ドメインがかなり異なるチームであり、そこにはコントリビュートするためのモチベーションがうまれないので、厳しいかな、と正直に思っています。(もちろん個人のスキルアップやチャレンジ精神というところは大歓迎ですが、ベースとして、という意味です。) 考えてみれば、コントリビュートは己や己のチームのためにまずやってみよう、と思うはずなので、そもそも協働するチームでなければ、やっぱりうまれないよな、と思ったところです。 つづいてはドキュメントへのコントリビュートです。ソースコードにPRを出すよりは圧倒的にドキュメントにPRを出すほうがハードルは低いと思います。が、弊社では、すべてのドキュメントがリポジトリで管理されてはなく、どうここを進めていこうか、と現在悩んでいるところです。どなたか助けてください。。もしかして、生成AIできれいなテキスト化ができるといい感じになるのでしょうか? ここは用語が社内に先に広まった分、理解が実はあまり追いついていなかった?という想像です。新規プロダクト開発や、なにかの事情がありブラックボックス・運用保守コスト増になってしまったシステムの改善に関して、インナーソースは寄与しません!誤解なさらずように。 まとめ で、まとめになります。 組織内でコラボレーション文化がどう作られるか、というところがインナーソースにチャレンジして見えてきました。 まず、土台となるプラットフォームが必要です。つまりGitHubです。 次に、コラボレーションする方法の明確化が必要で、つまりインナーソースです。 プラットフォームやコラボレーション方法だけではコラボレーションは生まれません。最後に、人間が必要ですが、 大事な点として同じ方向を向いていること、つまりお互いがメリットを享受できる間柄 である必要があります。 以上、3点が組織の後ろ盾(評価制度、工数管理など会社の動きの中で公式的に動けるか、という点)を行うことで達成されるのではなかろうか、というのが現時点での結論になります。 まだ道半ばでの途中の状態ですが、また進捗あり次第アップデートしていきたいと思います! 以上です。
この記事は、 ニフティグループ Advent Calendar 2024 16日目の記事です。 はじめに 今月は二本目のブログ投稿をする宮本です。ひと月の間にブログ二本書くのはなかなかしんどいですね。一人アドベントカレンダーをやっている方とか凄いと思います。 今回は、ReactのErrorBoundaryのログ出力についてです。なお、React単体で実装できるクラスコンポーネントを利用するものではなく、関数コンポーネントで利用できる react-error-boundary を対象としています。 ログを簡単に出したい。あとついでに使いまわせるようにしたい。 平たく言えばReactコンポーネントのtry-catchのような役割をするErrorBoundaryですが、実施できることは結構限られます。 エラー発生時の代替コンポーネントの表示 代替コンポーネント内でのリセット処理の追加 エラー発生時にあらかじめ指定したログ出力用の関数を実行 他にも明示的にErrorBoundaryの呼び出し・再レンダリングができるhookが用意されていたりしますが、応用になるため今回は割愛。とりあえずErrorBoundaryを入れよう!となった場合に使うのは、代替コンポーネントの表示とエラー時のログ表示だと思います。 さて、代替コンポーネントの中身自体はアプリケーションによりまちまちだと思いますが、エラーログの表示となると表示内容は割と限られます。エラーの種類ごとにエラー内容を変えるとなると細かく分岐を入れる必要がありますが、「最低限エラーがわかれば……」というレベルならできるだけ楽に書きたいです。 ErrorBoundaryでログ出力をする例として、onErrorで実行する関数内でログ出力をする方法が公式で紹介されています。 // https://github.com/bvaughn/react-error-boundary?tab=readme-ov-file#logging-errors-with-onerror より一部修正 "use client"; import { ErrorInfo } from 'react'; import { ErrorBoundary } from "react-error-boundary"; // 変更点)infoの型はreact-error-boundary 4.0からReactのErrorInfo型を利用 const logError = (error: Error, info: ErrorInfo) => { // Do something with the error, e.g. log to an external API }; const ui = ( <ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}> <ExampleApplication /> </ErrorBoundary> ); しかしこのonErrorで実行する関数、引数があらかじめ決まっているエラーオブジェクトとコンポーネントのスタックしか渡されません。一応スタックは渡されるのでどこで発生したエラーかはわかるようになっています。が、せめてErrorBoundaryを仕掛ける場所が決まっているのであれば、ログ中に一発で「エラー発生箇所はここだ!」とわかるような文言を仕込みたいです。 // こんな感じにErrorBoundaryがたくさんあると、いざエラーが発生した場合もどこで発生したエラーなのか分かりづらい const logError = (error: Error, info: { componentStack: string }) => { console.error("エラー発生!", Error, componentStack) }; const uiFirst = ( <ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}> <ExampleApplicationFirst /> </ErrorBoundary> ); const uiSecond = ( <ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}> <ExampleApplicationSecond /> </ErrorBoundary> ); そこで、次のようにログ出力用の関数を作成するcreateLogError関数を用意して、createLogErrorの引数で与えた値(logId)をログ出力で利用するようにします。 const createLogError = (logId: string) => (error: Error, info: ErrorInfo): void => { console.error(`${logId} エラー発生!`, error, info.componentStack); }; const uiFirst = ( <ErrorBoundary FallbackComponent={ErrorFallback} onError={createLogError("ui-first")}> <ExampleApplicationFirst /> </ErrorBoundary> ); const uiSecond = ( <ErrorBoundary FallbackComponent={ErrorFallback} onError={createLogError("ui-second")}> <ExampleApplicationSecond /> </ErrorBoundary> ); 上記の例を実際に使うと以下のようになります。 export default function Home() { return ( <ErrorBoundary FallbackComponent={FallbackComponent} onError={createLogError('ErrorItem')}> {/* 内部で `throw new Error('なんか凄いエラー');` を実行するコンポーネント */} <ErrorItem /> </ErrorBoundary> ); } 上記の例ではコンポーネント名をそのままIDに入れていますが、機能に即した名前が入っているともっとわかりやすくなると思います。ゆるくログ出力するだけであれば、これで十二分ですね。 おわりに 今回はErrorBoundaryを利用したログ出力について紹介しました。Reactのコンポーネントで発生したエラーは、止める場所がなければどこまでも遡り最終的にページ全体が落ちてしまいます。最低限エラーが発生しそうな箇所ではErrorBoundaryを設定しておき、余裕があればログ出力できるようにしておくと調査がスムーズですね。 また、そもそもエラーが発生したコンポーネントを再レンダリングするトリガーをFallbackComponent内に用意することもできるので、エラー処理といえどもなかなか奥が深いです。 明日は、kiwi26さんの記事です。 お楽しみに! 参考 react-error-boundary – npm
この記事は、 ニフティグループ Advent Calendar 2024 17日目の記事です。 はじめに こんにちは!新卒1年目の佐藤です。 現在ジョブローテの2期目で、基幹システムグループにて代理店様向けの申込ツールの開発・運用を主に行っています。 私が担当している開発の1つに ドキュメントのMarkdown化 があります。 この開発ではStarlightというフレームワークを使用したので、これについて紹介したいと思います! 背景 チームの設計書管理について 私が現在所属しているチームでは前までExcelだけでツールの設計書を管理していました。 その後、Notionに移行しつつあるのですが、一部はExcelに残ったままになっています。 ツールの改善案件が来た際に設計書修正をするのですが、Excel・Notionともにいつ、誰が、どこを修正したのかが非常に分かりにくくなっていました。 文 字 の 色 を人によって変えたりしたのですが、確認洩れを完全に回避することはできません。 そこで、設計書もGitHubで管理しようという話になりました。開発と同じようにブランチを切り、修正を加えて、プルリクエストを投げることで、修正箇所を簡単に把握することができます。また、履歴管理を気にする必要もありません。 サイトのホスティングについて だったらGitHub Pagesがあるじゃん!となりますが、今回選択肢には入りませんでした。 なぜなら、設計書は「社内限定公開にして、開発以外の方も見れるようにする」という要件があったからです。 GitHub pagesは公開範囲を絞れるのですが、その中で GitHubアカウントを持つ人に限り 社内限定公開ができます。しかし開発以外の方は GitHubアカウントを持っていない ので閲覧することができません。 サイト1つを見るためだけに有償のアカウントを発行するのは得策とは言えません。 となると、第2の策としてAmazon S3を使った静的webサイトホスティングが挙がりました。AWSの他のサービスと組み合わせることで適切な公開範囲を設定することができます。 どうやってサイトを作っていくか Webフレームワークで有名どころだとNext.jsやDocusaurus, Nuxt.jsがありますが、私はAstroを選びました。 https://astro.build/ 理由1:高速なWebサイトが作れること ドキュメント量が非常に多いので、できれば軽いサイトにしたいところです。Astro公式からの 2023 Web Framework Performance Report によると、Astroは他のフレームワークに比べてパフォーマンスが良いことが分かります。 理由2:Markdownで記述できること AstroはデフォルトでMarkdownに対応しています。ドキュメントページなので、見た目にあまりこだわる必要がなく、技術的な壁が高くないので誰でもすぐに書けるという点で優れています。私のような新人でも少し調べれば書き方を理解することができます。 以上の理由でAstroを採用しました。Astroでドキュメントページを作るにはStarlightというフレームワークを使用します。 Starlightとは https://starlight.astro.build/ https://starlight.astro.build/ https://starlight.astro.build/ StarlightとはAstro製のドキュメントサイトに特化した静的サイトの生成フレームワークになります。 公式ドキュメント は日本語訳されています。 なんと、Markdownを書くことで簡単に1ページが作れちゃいます。 また、.md形式のファイルだけでなく、.mdx形式のファイルにも対応しています。 となると、StarlightはAstroを基盤としているため、MDXファイルで サポートされているUIフレームワーク (React、Svelte、Vueなど)で構築されたコンポーネントが使えます!!! Starlight自体にもコンポーネントが付属されており、他のUIフレームワークを使わなくとも十分なサイトが作れます。 他にもディレクトリ構造が自動でページ構造になってくれたり、サイト内検索や画面のダークモード・ライトモード切替がデフォルトで付いています。 やってみよう! 新しいプロジェクトを作成するには次のコマンドを叩きます。 事前にNode.jsとnpmのインストールをしておいてください。 npm create astro@latest -- --template starlight プロジェクト名設定と依存関係をインストールするか否かなどが聞かれるので答えます。 すると、数分で新しいプロジェクトが出来上がります。 cdコマンドで作成したディレクトに移動し、次のコマンドを叩きます。 npm run dev するとlocalhostで開発環境が立ち上がります。 Example Guide → のボタンを押すとGuidesとReferenceの2つが出てきます。 Guides直下にページを追加したい場合は、guidesフォルダ内に新しく.mdファイルを作成し、他ファイル同様にtitleを設定します。 あとは astro.config.mjs にページの追加設定をすることで、ページ追加ができます。 Reference直下に新しいページを作りたい場合は、referenceフォルダ内に新しいファイルをつくるところまでは同じです。 astro.config.mjs を見ると、このフォルダは autogenerate を有効としているため、ここで設定をしなくともフォルダ内のファイルを自動で読み取ってページ追加してくれます。 GuidesとExample Guideみたいに親子関係を作りたい場合は、新しくフォルダとその中にファイルを作り、 astro.config.mjs にも追加してあげると出来上がります。 他にも サイドバーのタイトルをページタイトルとは別の名前にする ページごとに数値を付けて、小さい順にページを並べる(デフォルトだとABC順) バッジをつけて強調表示にする ページ内目次を設定して、h2の見出しのみ目次表示する ソーシャルメディアアカウントへのリンクを追加する など色々と設定ができます。 ぜひ公式ドキュメントを読んでカスタマイズしてみてください。 最後にビルドするには、 npm run build でdistディレクトリを生成してくれます。 あとはこのディレクトリを例えば、S3に置き、静的webサイトホスティングの設定をすることで公開できます! おわりに 今回はStarlightを使って簡単なドキュメントページを作ってみました。ポートフォリオや趣味紹介などのページ作りでぜひ検討してみてください! 明日は Kazuhiro-27 さんの記事です。 お楽しみに! ニフティでは、 さまざまなプロダクトへ挑戦する エンジニアを絶賛募集中です! ご興味のある方は以下の採用サイトより お気軽にご連絡ください! ニフティ株式会社採用情報 Tech TalkやMeetUpも開催しております! こちらもお気軽にご応募ください! Event – NIFTY engineering connpassでニフティグループに 参加いただくと イベントの お知らせが届きます! connpassで ニフティグループに参加する
この記事は、 ニフティグループ Advent Calendar 2024 16日目の記事です。 はじめに こんにちは。ニフティ新卒1年目の藤岡です。 上京して少し経ち、満員電車にも慣れてきた今日この頃です。 今回は、コンテナ化されたアプリケーションを開発する際に発生した「VScodeでRancherDesktopを用いて開発コンテナが起動できない」問題について取り上げ、その原因を調査し、解決に至るまでの手順を共有します。 似たような問題に直面した方の参考になれば幸いです。 DockerDesktopからRancherDesktopへ移行 私たちのチームでは、以前からDockerDesktopを使ってコンテナの管理を行ってきました。 しかし、DockerDesktopの利用料が値上がりしたため、先日RancherDesktopに移行しました。 RancherDesktopは、DockerDesktop同様にコンテナの管理を簡単にするためのツールです。 DockerDesktopとの違いについて詳しく知りたい方は、以下の記事が参考になります。 Rancher Desktopの使い方|Docker Desktopとの違いや移行手順を解説 – カゴヤのサーバー研究室 開発コンテナが開けない問題 移行自体はスムーズでしたが、従来通りVScodeでコンテナを起動しようとしたところ次のようなエラーが発生しました。 [2024-12-06T08:03:03.507Z] Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: error mounting "/run/docker/containerd/daemon/io.containerd.runtime.v2.task/moby/32b5120f8d4e4e470a4e14b2d729750846f48652b9c01073e6fed43a8bc573a2/\\wsl.localhost\Ubuntu\mnt\wslg\runtime-dir\wayland-0" to rootfs at "/tmp/vscode-wayland-8287cccf-e6ca-4539-a913-14586d6ff8ee.sock": create mount destination for /tmp/vscode-wayland-8287cccf-e6ca-4539-a913-14586d6ff8ee.sock mount: bind mount source stat: stat /run/docker/containerd/daemon/io.containerd.runtime.v2.task/moby/32b5120f8d4e4e470a4e14b2d729750846f48652b9c01073e6fed43a8bc573a2/\wsl.localhostUbuntumntwslgruntime-dirwayland-0: no such file or directory: unknown エラー内容を読み解こうと試みましたが、見慣れない用語が多くなかなか理解できませんでした。 コンテナ系のエラーは「とりあえず万物再起動」ということで docker prune を実行するも改善せず。 AIに相談してみても、エラー内容は教えてくれましたが解決はできず。 このエラーメッセージは、Dockerコンテナの起動中に発生した問題を示しています。具体的には、コンテナの初期化中に必要なファイルやディレクトリが見つからないため、マウントに失敗しています。 以下の手順で問題を解決できるか試してみてください: 1.WSLの再起動: 2.Dockerデーモンの再起動: 3.必要なディレクトリの存在確認: 4.DockerとWSLの更新: WaylandとVScodeの関係 先輩に相談したところ、「エラー文中の wayland が原因では?」との指摘を受けました。 調べた結果、 wayland はディスプレイサーバプロトコルの一種で、GUIアプリケーションの画面描画を管理する役割を果たしていることが分かりました。 簡単に言うと、「GUIを表示するために必要な準備をする仕組み」です。 wayland についてはもっと分かりやすく説明してくれている記事がいくつもあるので調べてみてください。 ディスプレイサーバとは – わかりやすく解説 Weblio辞書 VScodeでは開発コンテナを起動する際、自動的に wayland のソケットをマウントします。 しかし、RancherDesktop環境ではそのマウントパスがサポートされていないため、エラーが発生していたようです。 解決方法 対処は簡単で、VScodeに「 wayland ソケットをマウントしないように設定」するだけです。 方法はCLIとGUIの二通りあります。 CLI Ctrl + Shift + P で「ユーザー設定(JSON)」を開き、次のコードを追記します。 "dev.containers.mountWaylandSocket": false GUI VScode左下の歯車マーク→設定→「wayland」で検索→「Mount Wayland Socket」のチェックを外します。 これでVScodeは自動的に wayland ソケットをマウントしなくなり、開発コンテナの起動が可能になります。 【参考】 VSCode Dev ContainerとRancher Desktopで作るコンテナ環境【WSL】| SIOS Tech.Lab RancherDesktopとDockerDesktopの違い 今回のエラーはRancherDesktop特有のもので、DockerDesktopでは発生していませんでした。 DockerDesktopは複雑な環境設定をうまく処理してくれる一方、RancherDesktopはその点で少々不便な部分があるようです。 とはいえ、RancherDesktopにはDockerDesktopにはないメリットもあり、今後のアップデートでさらに使いやすくなることを期待しています。 おわりに 今回は、VScodeでRancherDesktopを使って開発コンテナを起動する際に発生するエラーの原因と解決方法を紹介しました。複数のツールを組み合わせる開発環境では、仕様の違いによる問題が起こりがちです。 環境差による問題を防ぐためにコンテナ技術が広まっていますが、その周辺ツールによっては依然として問題が発生することを痛感しました。システムの動作原理を理解しながら開発を進める重要性を再認識する機会になりました。 少しでも同様の問題で悩む方の助けになれば幸いです。 明日は、[佐藤] さんの[Starlightを使った簡単なドキュメントページの作り方]です。 お楽しみに!
💡 はじめに 🪄 Maestroについて 💭 事前準備 💻 ローカルでの実装 🐱 GitHub Actionsでの実装 😎 成果 💁‍ ️ 補足 💡 はじめに こんにちは。ニフティ株式会社のLinです。 台湾出身のモバイルアプリエンジニアとして、社内で「マイ ニフティ( Android ・ iOS )」の開発を担当しております。 今回は、マイニフティ Android 2.8.1 から導入したMaestroとGitHub Actionsによるテスト自動化についてご紹介します。 🪄 Maestroについて Maestro は、Android・iOS・React Native・Flutterなどの各種モバイルプラットフォームに対応した、オープンソースの軽量UIテストフレームワークです。 基本利用は無料ですが、CIを導入すると Maestro Cloud は有料サービスです。ただし、GitHub Actionsを利用すれば無料で同様の機能を実現できます。 (節約できる費用は こちら で計算できます) 今回はこの裏技を紹介します。 💭 事前準備 まずは下記のコマンドを使ってMaestroをインストールしましょう。 curl -fsSL "https://get.maestro.mobile.dev" | bash そして、やりたいことを整理しましょう。 実現できたらいいなと思うことを下記の通り並べてみました: GitHub Actionsのプルリクで変更があれば自動的にテストフローを実行 複数端末のテスト結果を確認できる テスト結果をGitHubのコメントに自動投稿 エビデンスとしてテストの録画を確認できる こういうことは本当にできるのか?! 答えはまさかの YES ! どうやって実現するのか、一緒にやってみましょう! 🙌 💻 ローカルでの実装 CIによるテスト自動化を実現するため、下記の三つのものが必要です。 テストしたいアプリ Maestroテストフロー Github Actionsワークフロー 今回の例は弊社の会員アプリの「 マイ ニフティ 」で実装しました。 最初にアプリのbuild.gradleにアプリバージョンを取得するタスクを追加します。 (これは後のGitHub Actionsコメント機能で使われています) // build.gradle.kts (Module :app) ... tasks.register("printVersionName") { println(project.android.defaultConfig.versionName) } Maestroテストフローは下記の通り追加していきます。 重複操作はoperationsにまとめ、テストフローはtestsに集約します。 . └── maesto/ ├── operations/ │ ├── init.yaml │ ├── login.yaml │ └── skip_tutorial.yaml └── tests/ └── test_flow.yaml 使われたoperationsフローは下記の通り: // init.ymal appId: ... --- - clearState - launchApp: permissions: notifications: allow - runFlow: when: visible: "ログイン" file: "../operations/login.yaml" - runFlow: when: visible: "マイ ニフティへようこそ" file: "../operations/skip_tutorial.yaml" // login.ymal appId: ... --- - tapOn: "@nifty ID または @niftyユーザー名" - inputText: ${USER_ID} - tapOn: "パスワード" - inputText: label: Enter password text: ${PASSWORD} - tapOn: "ログイン" // skip_tutorial.ymal appId: ... --- - tapOn: "チュートリアルをスキップ" テスト用のtestsフロー例は下記の通り: // test_flow.yaml appId: ... env: USER_ID: ${USER_ID} # init.yamlから上書きが必要な場合は設定する PASSWORD: ${PASSWORD} onFlowStart: - runFlow: file: "../operations/init.yaml" --- - startRecording: test_flow_recording # ここにテストを書く - stopRecording ローカルで下記のコメントを実行すると maestro test maestro/tests/test_flow.yaml -e USER_ID="XXX" -e PASSWORD="XXX" テストフローが自動的に動き始めます。 startRecordingとstopRecordingが設定されているので、実行画面の録画も保存されています。 🐱 GitHub Actionsでの実装 動作できるMaestroフローがあれば、次はGitHub Actionsのワークフローを作りましょう。 .githubのworkflowsでandroid_maestro_tests.ymlを追加しよう。 . └── .github/ └── workflows/ └── android_maestro_test.yml // android_maestro_tests.yml name: Maestro Test on: pull_request: branches: - master paths-ignore: - "**.md" jobs: test: name: Run Maestro Tests (API ${{ matrix.api-level }}) runs-on: ubuntu-latest timeout-minutes: 30 strategy: matrix: api-level: [ 28, 33, 34 ] include: - target: default arch: x86_64 profile: pixel_6 ram-size: 8192M disk-size: 4096M heap-size: 4096M steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v4 with: java-version: "17" distribution: "zulu" - name: Enable KVM run: | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - name: AVD cache uses: actions/cache@v4 id: avd-cache with: path: | ~/.android/avd/* ~/.android/adb* key: avd-${{ matrix.api-level }} - name: Create AVD and generate snapshot for caching if: steps.avd-cache.outputs.cache-hit != 'true' uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} target: ${{ matrix.target }} arch: ${{ matrix.arch }} profile: ${{ matrix.profile }} ram-size: ${{ matrix.ram-size }} disk-size: ${{ matrix.disk-size }} heap-size: ${{ matrix.heap-size }} emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none force-avd-creation: false script: echo "Generated AVD snapshot for caching." - name: Build app with Gradle (Production) run: ./gradlew assembleProductionDebug - name: Get apk path id: apk-path run: echo "path=$(find . -regex '^.*/build/outputs/apk/.*\.apk$' -type f | head -1)" >> $GITHUB_OUTPUT - name: Get app version run: | VERSION_NAME=$(${{ github.workspace }}/gradlew -q printVersionName) echo "APP_VERSION=${VERSION_NAME}" >> $GITHUB_ENV - name: Set up Maestro run: | curl -fsSL "https://get.maestro.mobile.dev" | bash ~/.maestro/bin/maestro --version || echo "Failed to execute maestro" - name: Run Maestro tests id: run-maestro-tests uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} target: ${{ matrix.target }} arch: ${{ matrix.arch }} profile: ${{ matrix.profile }} ram-size: ${{ matrix.ram-size }} disk-size: ${{ matrix.disk-size }} heap-size: ${{ matrix.heap-size }} emulator-options: -no-window -no-snapshot-save -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none script: | $ANDROID_HOME/platform-tools/adb install ${{ steps.apk-path.outputs.path }} ~/.maestro/bin/maestro test --format junit maestro/tests/ -e USER_ID="${{ secrets.TEST_USER_ID }}" -e PASSWORD="${{ secrets.TEST_PASSWORD }}" -e APP_VERSION=${{ env.APP_VERSION }} - name: Upload test record id: upload-test-record if: ${{ steps.run-maestro-tests.outcome == 'success' }} uses: actions/upload-artifact@v4 with: name: Android_SDK_API_${{ matrix.api-level }}(${{ env.APP_VERSION }}_Succeeded) path: "**.mp4" - name: Add test record to PR comment if: ${{ steps.upload-test-record.outcome == 'success' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Prepare GitHub CLI if ! command -v gh &> /dev/null; then sudo apt-get install gh -y fi # Prepare PR comment comment="### ${{ env.APP_VERSION }}テスト成功(Android SDK API ${{ matrix.api-level }}) : [レコードをダウンロードする](${{ steps.upload-test-record.outputs.artifact-url }})" # Post PR comment gh pr comment ${{ github.event.pull_request.number }} --body "$comment" - name: Upload test report id: upload-test-report if: ${{ steps.run-maestro-tests.outcome == 'failure' }} uses: actions/upload-artifact@v4 with: name: Android_SDK_API_${{ matrix.api-level }}(${{ env.APP_VERSION }}_Failed) path: | **.mp4 report.xml - name: Add test report to PR comment if: ${{ steps.upload-test-report.outcome == 'success' }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Prepare GitHub CLI if ! command -v gh &> /dev/null; then sudo apt-get install gh -y fi # Prepare PR comment comment="### ${{ env.APP_VERSION }}テスト失敗(Android SDK API ${{ matrix.api-level }}) : [レポートをダウンロードする](${{ steps.upload-test-report.outputs.artifact-url }})" # Post PR comment gh pr comment ${{ github.event.pull_request.number }} --body "$comment" このワークフローで下記の機能が実装されました: エミュレーターはテストしたいAndroid SDKバージョンのPixel 6を指定、実行安定性を保証するためにram-size、disk-sizeとheap-sizeもデフォルト値より高い数値に設定しました エミュレーターを利用するためKVMを有効化し、Productionのapkをビルドしてインストールパスを記録し、エミュレーターにapkをインストールしてMaestroテストフローを実行します 実行成功したら、エミュレーターのキャッシュが保存され、次回実行する時に再利用されます 先ほど追加したprintVersionNameタスクによりテスト対象アプリのバージョンを自動取得し、実行録画もartifactに自動保存、ダウンロードリンクを自動生成して、コメントにエビデンスを残します 成功したら成功コメントを自動投稿、失敗したら失敗コメントを自動投稿 ワークフローで使われたIDとパスワードも GitHub → Settings → Secrets and variables → Actions のsecretsに登録しましょう。 そして、自動テスト君が爆誕。 😎 成果 次回、リリースプルリクが作成された際に、呪文を詠唱すれば自動テスト君を召喚できます。 集いしテストフローが新たな力を呼び起こす!自動化の道となれ! いでよ、自動テスト君! 冗談です (・ω・) 何もしなくても自動テスト君が自動的に召喚され動作します。 テスト成功・失敗した場合、実行エビデンスが自動生成され、ダウンロードして確認できます。 素晴らしいですね! テスト結果は一目で確認でき、レコードをダウンロードすると実行録画も確認できます。 💁♂️ 補足 Maestroは便利ですが、よくある落とし穴もいくつかあります: Maestro 1.33.1以上では、度々「emulator not found」の問題が発生します 詳細 ram-size、disk-sizeとheap-sizeを高めに設定するか、GitHub Actions Runners のスペックを増強することで、ある程度改善できます MaestroはWebViewに弱く、assertVisibleは度々失敗します extendedWaitUntil を追加すると、ある程度改善できます 度々ボタン・文字を認識できない問題があります 対象の contentDescription を追加したら改善できます(null にしないでください)
エンジニアリングマネージャーをしています芦川です。2024年12月7日に Developers CAREER Boost 2024 の懇親会でLTをしました。当日、結構焦って発表してたなあ、あれ言うの忘れたなあ、というのもあったので、内容について、ここで改めて書いてみたいと思います。 また、計画的偶発性について語られている方が当日多く、たしかに、私もこれまでの1つの会社の中であるにせよ、予期しないことに柔軟に前向きに取り組めたおかげで楽しくなっているんだな、ということもわかりました。非常に有意義なイベントでした! プロセスを楽しんでいこう!というメッセージとともに、私のニフティ新卒から20年のエンジニアキャリアを通じて培ってきた自分戦略についてお話しさせていただきます。 自己紹介 ニフティ株式会社でエンジニアリングマネージャーを務めている芦川亮と申します。2004年に新卒入社してから、バックエンドエンジニア、スクラムマスター、そして現在のエンジニアリングマネージャーと、様々な役割を経験してきました。バックエンド後半では主にOAuth2.0/OpenID Connectなどの認証・認可系の開発に携わっていました。 自分戦略 私の自分戦略は、「そのときの状況や環境において自分が積極的に楽しめる方法、学べる行動をする」というものです。この戦略について、私のキャリアの各フェーズに沿って詳しく説明していきたいと思います。 バックエンドエンジニア時代 エンジニアとして最初のキャリアでは、日常的な手作業や運用業務を自動化のチャンスとして捉えることを心がけました。面倒な作業があっても、「これをどうやって自動化できるか」と考えることで、仕事にワクワク感を見出すことができました。もう一回言います、手動作業は自動化してやるチャンスです。 ログ調査を自動化したり、ブラウザでの手動ポチポチをスクレイピングをしたり。バックエンド時代と書きましたが、実は今でもマネージャー特有の承認作業の省力化など密かにやっております。なぜなら、楽しいから。 たとえ1分の省力化であっても、それを500人規模の組織で展開すれば、1日8時間分、つまり、ざっくり人間1人を雇ったことと等価。小さな改善の積み重ねが、大きな価値を生み出すのです。 スクラムマスター時代 さて、つづいては、スクラムマスター時代です。 皆さんご経験があるかもしれません。環境や自分のロールを変えることで、成長というか、経験値が飛躍的にあがった体験があるのではないでしょうか? 環境やロールの変更は、自己成長の大きなジャンプアップのチャンスです。スクラムマスターとして、チームマネジメント、ファシリテーション、心理的安全性など、それまで未知だった分野に挑戦することが新鮮で楽しかったです。未知なものを学ぶこと自体楽しいですが、楽しさが倍増したのは「私は今学んだことをすぐに実践できるポジションにいる」ということだったと思います。学んですぐに試せる場があるということは幸せです。 図は一例です。レトロスペクティブの方法にKPTがあると思いますが、メンバーの気持ちにくすぶっている「PROBLEMまで昇格できていないもやもやや愚痴」のようなものを吐き出せるように、KPTに「BOYAKI」を追加しまして、実践していた例です。 で、改めて振り返ると、「自分対メンバーという構図」ができ始めた時代であり、そういうつながりになって初めて得られる学びってあるんだな、と実感しました。 エンジニアリングマネージャー時代1 最後は、エンジニアリングマネージャー時代です。 エンジニアリングマネージャーというと最初は何をしたらいいのだろう、というところでしたが、右の図( engineering-management-triangle )が教えてくれました。これは、エンジニアリングマネージャートライアングルというもので、EMは、プロダクトとチームとテクノロジーの間を埋める存在であると。例えば採用活動であれば、テクノロジーとチームの間をうめるものになります。 現在のエンジニアリングマネージャーの役割は、私の20年のキャリアの中で最も楽しいと感じています。というのは、EMって何をしたらいいのだろう、ではなくて、「何をやってもこの中にはてはまっていくんだな」、ということがわかり、心地よいある種のコミット感の中で、学習や実践ができることができている、という実感あります。コミット感、というのは自身の成長を感じる部分において重要ですね。皆様は、コミット感、感じてらっしゃいますか? というところで、今が、20年いた中で1番自由であり、仕事が楽しいと感じています。EMおすすめ。 エンジニアリングマネージャー時代2 さらに、人同士をつなげる役割も楽しいプロセスなのかと気づいた時代です。私の場合はたまたまそれがEM時代でしたが、もっと早くからこのようにネットワーキングされている方はいらっしゃると思います。正直、羨ましい! きっかけは会社関係でしたが、大学の教授とかコワーキングスペースにいた全く異なる業種の方と話したり、外部コミュニティで人と人をつなぐ役割に入ったりして、人同士がつながって、そこでうまれるコラボレーションに感動したりします。そういう楽しみ方があったか。 右の図は今、属しているインナーソースコモンズジャパンです。これは、OSSでの開発プラクティスを企業内に持ち込んで、オープンなエンジニア文化を根ざそう、というものであり、ご興味ありましたら、お声がけください。 宣伝1 というところで宣伝させてください。 InnerSource Commons Community です! 宣伝2 人と人がつながると言えば、ニフティが主催するテックイベントがあります。 NIFTY Tech Day 2025 今回で3回目、ハイブリッド開催を考えております!来る2025年2月8日 土曜日開催です!テーマは「学び」。 NIFTY DevelopersのX より更新情報をお待ち下さい! まとめ 1行でまとめてみると、仕事のWhatについては事業起点で会社の中でうまれたり、状況によって作られたり(自分が作ったり)しますが、Howについては、個人の意思で変えていけるところだと思います。 ということで、日々、これからも「どうやれば、この仕事は楽しくなる?学びが増える?」ということを自分に問い続け、モチベーション高くやっていきたいと思います。 どうやれば、この仕事は楽しくなる? エンジニア人生楽しんでいますか? 以上、私の自分戦略でした!
この記事は、 ニフティグループ Advent Calendar 2024 11日目の記事です。 こんにちは! 今回は、JavaScriptとTypeScriptの三大ランタイム環境であるDeno、Bun、Node.jsについて比較していこうと思います。 概要 Node.js: 2009年に登場した最も成熟したJavaScriptランタイム Deno: 2018年にNode.jsの創始者によって開発された、より安全でモダンなランタイム Bun: 2022年に登場した高速で全機能を備えたJavaScript/TypeScriptランタイム 主な特徴 Node.js 広大なエコシステムと豊富なnpmパッケージ 長年の実績と安定性 非同期I/Oに最適化 Deno セキュリティ重視(デフォルトで安全) TypeScriptのネイティブサポート URLベースのモジュールインポート Bun 高速な起動とランタイム実行 Node.jsとの高い互換性 内蔵のパッケージマネージャとバンドラー 比較 パフォーマンス比較簡単なベンチマークとして、各ランタイムで1から1000万までの数を数える処理の実行時間を比較してみましょう。 Node.js Homebrewや公式サイト( https://nodejs.org/en )などでインストールする benchmark.jsというテストファイルを作成する。 console.log('Starting benchmark...'); const start = process.hrtime.bigint(); let count = 0; for (let i = 0; i < 10000000; i++) { count++; } const end = process.hrtime.bigint(); const duration = Number(end - start) / 1_000_000; // ナノ秒からミリ秒に変換 console.log(`Counting completed in ${duration.toFixed(3)} ms`); console.log(`Final count: ${count}`); 実行する node benchmark.js 実行結果 1回目 $ node benchmark.js Starting benchmark... Counting completed in 7.381 ms Final count: 10000000 2回目 $ node benchmark.js Starting benchmark... Counting completed in 8.529 ms Final count: 10000000 3回目 $ node benchmark.js Starting benchmark... Counting completed in 8.092 ms Final count: 10000000 Deno Homebrewなどでインストールする https://docs.deno.com/runtime/getting_started/installation/ benchmark.jsというテストファイルを作成する。 console.log('Starting benchmark...'); const start = performance.now(); let count = 0; for (let i = 0; i < 10000000; i++) { count++; } const end = performance.now(); const duration = end - start; console.log(`Counting completed in ${duration.toFixed(3)} ms`); console.log(`Final count: ${count}`); 実行する deno run benchmark.js 実行結果 1回目 $ deno run benchmark.js Starting benchmark... Counting completed in 10.852 ms Final count: 10000000 2回目 $ deno run benchmark.js Starting benchmark... Counting completed in 10.905 ms Final count: 10000000 3回目 $ deno run benchmark.js Starting benchmark... Counting completed in 9.019 ms Final count: 10000000 Bun Homebrewなどでインストールする https://bun.sh/docs/installation benchmark.jsというテストファイルを作成する。 console.log('Starting benchmark...'); const start = process.hrtime.bigint(); let count = 0; for (let i = 0; i < 10000000; i++) { count++; } const end = process.hrtime.bigint(); const duration = Number(end - start) / 1_000_000; // ナノ秒からミリ秒に変換 console.log(`Counting completed in ${duration.toFixed(3)} ms`); console.log(`Final count: ${count}`); 実行する bun benchmark.js 実行結果 1回目 $ bun benchmark.js Starting benchmark... Counting completed in 7.849 ms Final count: 10000000 2回目 $ bun benchmark.js Starting benchmark... Counting completed in 9.821 ms Final count: 10000000 3回目 $ bun benchmark.js Starting benchmark... Counting completed in 14.227 ms Final count: 10000000 結果 すべてほぼ同等のパフォーマンスを示しています。 HTTPサーバー作成 今度は各ランタイムで簡単なHTTPサーバーを作成してみます。 Node.js server.jsというファイルを作成します。 const http = require('http'); const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello Worldn'); }); server.listen(3000, () => { console.log('Server running at http://localhost:3000/'); }); 実行 $ node server.js Server running at http://localhost:3000/ TypeScriptファイルを試してみる server.tsを作成する import * as http from 'http'; const server: http.Server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello Worldn'); }); server.listen(3000, () => { console.log('Server running at http://localhost:3000/'); }); 実行 実行するとエラー $ node server.ts (node:38672) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension. (Use `node --trace-warnings ...` to show where the warning was created) /Users/server.ts:1 import * as http from 'http'; ^^^^^^ SyntaxError: Cannot use import statement outside a module at wrapSafe (node:internal/modules/cjs/loader:1350:18) at Module._compile (node:internal/modules/cjs/loader:1379:20) at Module._extensions..js (node:internal/modules/cjs/loader:1518:10) at Module.load (node:internal/modules/cjs/loader:1249:32) at Module._load (node:internal/modules/cjs/loader:1065:12) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:158:12) at node:internal/main/run_main_module:30:49 Node.js v22.2.0 TypeScriptをネイティブサポートしていないことが分かります。 Deno server.tsというファイルを作成します。 DenoはTypeScriptをネイティブサポートしています。 import { serve } from "https://deno.land/std@0.181.0/http/server.ts"; const handler = (req: Request): Response => { return new Response("Hello Worldn", { status: 200 }); }; console.log("Server running at http://localhost:3000/"); await serve(handler, { port: 3000 }); 実行 Denoはセキュリティを重視しているため、ファイルアクセスやネットワークアクセスにはパーミッションが必要です。 例えば、ファイル読み書きの場合は以下のようにします。 deno run --allow-read --allow-write file_io.ts $ deno run --allow-net server.ts Server running at http://localhost:3000/ Listening on http://localhost:3000/ ネットワークアクセス許可(--allow-net)を与えないで実行すると、許可してよいかと聞かれます。 $ deno run server.ts Server running at http://localhost:3000/ ┏ Deno requests net access to "0.0.0.0:3000". ┠─ Requested by `Deno.listen()` API. ┠─ To see a stack trace for this prompt, set the DENO_TRACE_PERMISSIONS environmental variable. ┠─ Learn more at: https://docs.deno.com/go/--allow-net ┠─ Run again with --allow-net to bypass this prompt. ┗ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions) > Bun server.tsというファイルを作成します。 BunはTypeScriptをネイティブサポートしています。 const server = Bun.serve({ port: 3000, fetch(req) { return new Response("Hello Worldn"); }, }); console.log(`Listening on http://localhost:${server.port}`); 実行 $ bun run server.ts Listening on http://localhost:3000 DenoとBunの比較 1. モジュールのインポート: Deno: 外部モジュールをURLから直接インポートします。 import { serve } from "https://deno.land/std@0.181.0/http/server.ts"; Bun: 組み込みのグローバル Bun オブジェクトを使用し、追加のインポートは不要です。 2. サーバーの設定: Deno: serve 関数を使用し、ハンドラー関数と設定オブジェクトを渡します。 await serve(handler, { port: 3000 }); Bun: Bun.serve メソッドを使用し、設定オブジェクトを渡します。 const server = Bun.serve({ ... }); 3. リクエストハンドラー: Deno: 独立した関数として定義します。 const handler = (req: Request): Response => { ... }; Bun: 設定オブジェクト内の fetch メソッドとして定義します。 fetch(req) { ... } 比較ポイント 上記の比較結果では、各ランタイムの HTTP サーバー実装の違いがありました。 以下に、主要な比較ポイントをまとめました。 1. TypeScript サポート : Node.js: 追加のツール (ts-node) とタイプ定義 (@types/node) が必要。 Deno: ネイティブサポート。 Bun: ネイティブサポート。 2. セキュリティモデル : Node.js: デフォルトですべてのシステムリソースにアクセス可能。 Deno: 明示的な権限付与が必要( --allow-net フラグ)。 Bun: Node.js 互換のモデルを採用(デフォルトでアクセス可能)。 3. API設計 : Node.js: 標準的な http モジュールを使用。 Deno: モダンな Web 標準に基づいた API。 Bun: シンプルなオブジェクトベースの API。 4. モジュールシステム : Node.js: ES Modules (import 構文) を使用。 Deno: ES Modules と URL ベースのインポートを使用。 Bun: 組み込みの Bun オブジェクトを使用。 5. 実行の簡単さ : Node.js: TypeScript を直接実行するには追加のセットアップが必要。 Deno: 追加のセットアップなしで TypeScript を直接実行可能。 Bun: 追加のセットアップなしで TypeScript を直接実行可能。 結論 結論として、Node.js、Deno、Bunの3つのランタイムは、それぞれ独自の強みを持ち、JavaScriptエコシステムの多様化と進化に大きく貢献しています。 また、これらのランタイムは急速に進化を続けているため、定期的に最新の動向をチェックし、必要に応じて選択を見直すことをお勧めします。 本ブログが、読者の皆様のプロジェクト選択や技術的な意思決定の一助となれば幸いです。 より詳細な情報や具体的な使用例については、各ランタイムの公式ドキュメントを参照することをお勧めします。 明日は、Tsan0409さんの記事です。お楽しみに。
この記事は、 ニフティグループ Advent Calendar 2024 10日目の記事です。 CLIツールを社内にプライベートtapリポジトリを作成し、社内に配布しました。 意外と記事がなかったのでその方法をご紹介します。 アプリケーションのリリースに使っているのはGoReleaserです。 きっかけ ニフティ株式会社では現在Oneloginを使っています。 AWSのIAM RoleにAssumeRoleする際は以下のOneloginが提供しているツールを使っている社員がほとんどです。 https://github.com/onelogin/onelogin-python-aws-assume-role このツールはjava版もあるのですが、どちらにせよランタイムのインストールが必要です。 また比較的頻繁に更新されているわけではないため、バージョンによっては依存ライブラリの問題でセットアップが失敗するなどの問題がありました。 そこで、これをGoで作り直すことでシングルバイナリかつセットアップ不要にしつつ、brewで配布することでMacやWSL環境への導入をより簡単にしようと決めました。 サードパーティ製の他のツールを使うという手段もありましたが、私自身がインフラ管理者ではない(全社向けの選定権がない)ことや、現在使っているツールからの移行モチベーションを上げるため設定ファイルを流用することが重要だったこと、インナーソース化することでより柔軟に開発者自身が必要な機能を追加していけることを重視し、自作することに優位性があると判断しました。 Tapリポジトリの作成 バイナリを配布するためのtapリポジトリを作成します。 このリポジトリの名前は homebrew-<repo> の形式である必要があります。 https://docs.brew.sh/Taps#repository-naming-conventions-and-assumptions プライベートなtapリポジトリを作成する場合、GitHubのトークンを使ってダウンロードURLを作る必要があります。 以下のGistが非常に参考になるため、これを使います。 https://gist.github.com/ZPascal/b21c652b811872b3f56db9d54d61d6c6 GitHubPrivateRepositoryDownloadStrategy というクラスがダウンロード戦略となります。 指示通り、tapリポジトリのlib/配下に配置します。 アプリケーションの作成 今回はHomebrewで配布する部分がメインなので、ここではGoReleaserの設定について紹介します。 GoReleaserは簡単に複数のOSやCPUアーキテクチャ向けにBuildしたり、パブリッシュすることができるツールです。 https://github.com/goreleaser/goreleaser このツールを使ってGitHub Actionsでリリースしています。 アプリケーションリポジトリからtapリポジトリにpushしてPRを作成する必要があるため、PrivateAccessTokenが必要になります。 ダウンロード戦略として先ほど作成した GitHubPrivateRepositoryDownloadStrategy を使うよう指定します。 .goreleaser.yaml version: 2 before: hooks: - go mod tidy - go generate ./... builds: - main: ./cmd/ binary: onelogin env: - CGO_ENABLED=0 goos: - linux - windows - darwin archives: - format: tar.gz name_template: >- {{ .ProjectName }}_ {{- title .Os }}_ {{- if eq .Arch "amd64" }}x86_64 {{- else if eq .Arch "386" }}i386 {{- else }}{{ .Arch }}{{ end }} {{- if .Arm }}v{{ .Arm }}{{ end }} format_overrides: - goos: windows format: zip changelog: sort: asc filters: exclude: - "^docs:" - "^test:" brews: - name: onelogin download_strategy: GitHubPrivateRepositoryReleaseDownloadStrategy custom_require: lib/private_strategy repository: owner: niftycorporation name: homebrew-tap branch: "{{ .ProjectName }}" token: "{{ .Env.PRIVATE_GITHUB_TOKEN }}" pull_request: enabled: true draft: false base: branch: master workflow name: Upload Release Asset on: push: tags: - 'v*' permissions: contents: write jobs: build: name: Upload Release Asset runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: go.mod cache: true - uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser version: "~> v2" args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PRIVATE_GITHUB_TOKEN: ${{ secrets.PRIVATE_GITHUB_TOKEN }} このWorkflowが実行されるとreleasesが作成され、tapリポジトリにPRが作成されます。 一部変更していますが、以下のようなファイルが生成されます。 GoReleaserを使わなくてもこのようなファイルがあれば配布すること自体は可能です。 onelogin.rb # typed: false # frozen_string_literal: true # This file was generated by GoReleaser. DO NOT EDIT. require_relative "lib/private_strategy" class Onelogin < Formula desc "" homepage "" version "1.0.2" on_macos do on_intel do url "https://github.com/niftycorporation/onelogin-go-aws-assume-role/releases/download/v1.0.2/onelogin-go-aws-assume-role_Darwin_x86_64.tar.gz", using: GitHubPrivateRepositoryReleaseDownloadStrategy sha256 "hash" def install bin.install "onelogin" end end on_arm do url "https://github.com/niftycorporation/onelogin-go-aws-assume-role/releases/download/v1.0.2/onelogin-go-aws-assume-role_Darwin_arm64.tar.gz", using: GitHubPrivateRepositoryReleaseDownloadStrategy sha256 "hash" def install bin.install "onelogin" end end end on_linux do on_intel do if Hardware::CPU.is_64_bit? url "https://github.com/niftycorporation/onelogin-go-aws-assume-role/releases/download/v1.0.2/onelogin-go-aws-assume-role_Linux_x86_64.tar.gz", using: GitHubPrivateRepositoryReleaseDownloadStrategy sha256 "hash" def install bin.install "onelogin" end end end on_arm do if Hardware::CPU.is_64_bit? url "https://github.com/niftycorporation/onelogin-go-aws-assume-role/releases/download/v1.0.2/onelogin-go-aws-assume-role_Linux_arm64.tar.gz", using: GitHubPrivateRepositoryReleaseDownloadStrategy sha256 "hash" def install bin.install "onelogin" end end end end end brew installする まずローカルからプライベートリポジトリにアクセスするためにPrivateAccessTokenを作成し、 HOMEBREW_GITHUB_API_TOKEN という環境変数に指定します。 tapリポジトリを指定し、インストールします。 このとき、リポジトリ名の homebrew- 部分は省略することができます。 brew tap niftycorporation/tap brew install niftycorporation/tap/onelogin Brewfileには以下のように記載します。 tap "niftycorporation/tap" brew "niftycorporation/tap/onelogin" これでプライベートのtapリポジトリでバイナリを配布することができました。 まとめ ここまでプライベートのtapリポジトリを作成し、配布する方法を紹介してきました。 企業内で独自のソフトウェアを配布したいことはそれなりに多いんじゃないかと思っています。 特に以下はかなりハマりポイントでした。 tapリポジトリの命名規則 PATを使ったダウンロード戦略の作成 PATを使ったダウンロード戦略の指定 今ハマっている方や今後やろうと思っている方はぜひこれらに気をつけていただければと思います。 ここからはタイトルに関係のない今後の展望についてですが、このリポジトリは現在インナーソースとして社内公開していて利用者も比較的多いプロジェクトになっていると考えています。 しかしコントリビュータがなかなか増えず、敷居の高さや関心を引けていないことが問題かと思っています。 ツール自体メイン機能はほぼ完成していますが、爆速で作ったためリファクタリングや処理の効率化、ドキュメントの整備などやりたい事自体はまだまだある状況です。 また、普段使わない言語や領域に触れるなどエンジニアとしての成長も望めるため、コントリビュータを増やしインナーソースやOSSに興味を持ってもらえるよう活動していく予定です。 tapリポジトリについても現在配布されているのがこのツールのみということもあり寂しいので、こちらも敷居を下げより簡単に追加できるようになればいいなと想像しています。 最後に、この記事がどなたかのお役に立てば幸いです。 明日は、@hayabusa01 さんの記事です。 お楽しみに!
この記事は、 ニフティグループ Advent Calendar 2024 9日目の記事です。 こんにちは。ニフティ株式会社の statiolake です。 私は生粋の Neovimmer なのですが、これはなぜかというと自分の開発環境を自分好みにカスタマイズすることが大好きだからです。 複雑で覚えていられない処理を自動化して隠蔽し、シンプルで明快な操作で実行できるようにする。エンジニアリングの粋と言ってもよいでしょう。この営みを最も高速にイテレートできることこそ、自分の開発環境を整えることなのです。 いきなり脱線して恐縮なのですが、先日開催された VimConf 2024 のキーノートで、Neovim プラグイン開発者でストリーマーでもある TJ DeVries 氏もそのような講演をされていました。 こちら、VimConf ながら Vim 固有の話はほぼありませんので、どなたでも楽しんでいただけると思います。語り口の軽快さはさすがストリーマーといったところ。聞いていて飽きることがありませんでした。 つい最近 YouTube に動画が上がったようなので、よろしくお願いします。 何の話だ。 ええ、そういうこともあって、私はメタプログラミングも大好きです。 複雑で書いていられないボイラープレートを隠蔽し、簡単で直感的なコードで呼び出せるようになると、なんかいいですよね。動的型付けで様々なものがオブジェクトとして扱える Python は、コードを簡単にかけるようにすることにかけては超一流といって良いでしょう。 今回はそういったメタプログラミングで使えそうなテクニックの一つとして、メソッドのフリをするオブジェクトを作る方法を学んだので、共有したいと思います。 これを使ったら何ができるかは、またいずれ別の記事にするかもしれません。 ※ なお、これは言語仕様などに立ち入った厳密な話ではないことを先んじてお断りしておきます。そこまで詳しいわけではないです… 関数のフリをするオブジェクトの作り方 いきなりメソッドに入る前に、普通の関数について考えてみましょう。 def add(a, b): return a + b print(add(1, 2)) # => 3 実は、関数のように使えるオブジェクトを作るのは結構簡単です。 確かに関数には add(1, 2) という特別な文法を与えられてはいます。しかし、それは実は add.__call__(1, 2) と同じ意味になっています。なので単に __call__ というマジックメソッドを定義してあげれば呼び出し構文が使えるようになります。 class AddFunction: def __call__(self, a, b): return a + b add = AddFunction() print(add(1, 2)) # => 3 # ^^^ AddFunction クラスのインスタンスであり、関数ではないが、呼び出せる 完璧です。 メソッドはどうして難しいのか? これができるなら、別にメソッドだって同じような気がしますよね。 class Calculator: def add(self, a, b): return a + b c = Calculator() print(c.add(1, 2)) # => 3 実際、これを先ほどのように __call__() を持ったオブジェクトで置き換えても一見、使えているように見えます。 class AddFunction: def __call__(self, a, b): return a + b class Calculator: # def add(self, a, b): # return a + b add = AddFunction() c = Calculator() print(c.add(1, 2)) # => 3 しかし、これには致命的な欠点があります。それは add() がインスタンスに結びついていないことです。 メソッドなのに self に結びついていない 例えば電卓の M+ ボタンのように、Calculator クラス内に値を保持するメソッドを作りたいとします。しかし、今の形だとそれが実装できません。 AddFunction の __call__ にある self はあくまでも AddFunction であって Calculator ではないからです。 そもそも振る舞いが違う Python のメソッドは少し不思議な性質を持っています。 add() がメソッドの場合、 class Calculator: def add(self, a, b): return a + b c = Calculator() print(Calculator.add(c, 1, 2)) # => 3 print(c.add(1, 2)) # => 3 このように インスタンス.メソッド名(引数...) で呼び出したときは自動で self にオブジェクトが入り、 クラス名.メソッド名(インスタンス, 引数…) で呼び出したときには self にあたるものも自分で指定することができます。 ところが、今作ったオブジェクトを使ってみると… class Calculator: add = AddFunction() c = Calculator() print(Calculator.add(c, 1, 2)) # ^ TypeError: AddFunction.__call__() takes 3 positional arguments but 4 were given print(c.add(1, 2)) # => 3 なんと、3 つの引数で呼び出すところが例外になってしまいました。確かに __call__() には 2 つの引数しか入れられないので、エラーになるのは理解できます。 しかし、ならばなぜメソッドはエラーにならないのでしょうか? 2 つの問題というふうにお伝えしましたが、本質的には 1 つだけです。 結論から言えば、魔法がかかるのはメソッド呼び出し時です。メソッド add の本当の姿は、 クラス名.メソッド名 で呼び出されるのと同じ、 self を含めた 3 引数の関数と考えてください。 つまり、Python が インスタンス.メソッド名(引数...) のときだけ勝手に第一引数にインスタンスを結びつけてくれている、よって 2 引数の関数のように使えている、ということになります。 普通のオブジェクトがメソッドのふりをするためには、この仕組みを自分で実装しなければいけません。そしてそれを可能にする仕組みがデスクリプタです。 デスクリプタ ここから少し込み入った話になりますが、Python にはデスクリプタというものが存在します。標準にある @property デコレータの裏側で使われている機能でもあります。 デスクリプタ は、メソッドやフィールドを取得しようとしたときに割り込んで好き勝手なオブジェクトを返すことができる機能です。まあ他にもできることはあるんですが、ここでは割愛します。 たとえば、以下の RandomValueDescriptor を使うと参照するたびに値が変わっている不思議なフィールドを作ることができます。 class RandomValueDescriptor: def __get__(self, instance, owner): return random.randint(1, 100) class Calculator: value = RandomValueDescriptor() c = Calculator() print(c.value) # => 34 (例) print(c.value) # => 84 (例) 普通のフィールドの場合、 value に代入してもいないのに値が変わっているということはありません。 しかしデスクリプタを使うと、 value にアクセスしたときに毎回 __get__() 関数を通してくれるようになります。そこではあらゆるコードが実行できるため、このように、ランダムな値を返すこともできるわけです。 そして、 __get__ にはいくつかの引数があります。 ここで着目したいのは instance です。ここには インスタンス.フィールド名 で呼ばれた時の インスタンス が与えられています。さらに、クラス名を経由して呼ばれたときは None が入っています。 class InstanceDetectorDescriptor: def __get__(self, instance, owner): if instance is None: return "without instance" return "with instance" class Calculator: instance_detected = InstanceDetectorDescriptor() c = Calculator() print(Calculator.instance_detected) # => without instance print(c.instance_detected) # => with instance なるほど、 instance を見てインスタンスがあるかないかを取ることができる。…もうおわかりですかね? デスクリプタでメソッドのフリをする ここまでの考察を踏まえて、以下のうち memory_add() メソッドをオブジェクトに置き換えてみましょう。 class Calculator: def __init__(self): self.memory = 0 def memory_add(self, delta): self.memory += delta c = Calculator() Calculator.memory_add(c, 1) # memory: 0 + 1 = 1 print(c.memory) # => 1 c.memory_add(1) # memory: 1 + 1 = 2 print(c.memory) # => 2 この中の、引数の数がインスタンスかどうかで変わってくる部分が難しいのでした。しかし今の我々には、デスクリプタを使って以下のことが可能です。 参照された瞬間を __get__() で割り込むことができる __get__() の中でインスタンスの有無がとれ、好きなものを返せる であれば、こうでしょう。 インスタンスが与えられなかったときは、普通の関数を返す。 インスタンスが与えられたときは、それを第一引数に紐づけ、一つ引数が減った関数を返す。 class MemoryAddMethodDescriptor: def __get__(self, instance, owner): def inner(self, delta): self.memory += delta if instance is None: # インスタンスがない場合 - クラス名.メソッド名(インスタンス, 引数...) で呼び出された # 普通に関数をそのまま返す return inner # インスタンスがある場合 - インスタンス.メソッド名(引数...) で呼び出された # 第一引数にインスタンスを与え、引数を一つ減らした関数を作って返す # ここでは手っ取り早く lambda 式を使っている return lambda delta: inner(instance, delta) class Calculator: def __init__(self): self.memory = 0 memory_add = MemoryAddMethodDescriptor() いけるはずです。さて、実行してみると… c = Calculator() Calculator.memory_add(c, 1) # memory: 0 + 1 = 1 print(c.memory) # => 1 c.memory_add(1) # memory: 1 + 1 = 2 print(c.memory) # => 2 成功です!しかも、きちんとインスタンスにもアクセスできていますね。 ここでは __get__() が返すものをただの関数にしているので、あまり便利さが実感できないかもしれません。 しかし、ここで先程の「関数のふりをするオブジェクト」をさし挟むことができてしまうとなれば、どうでしょうか? あれも基本的には普通のクラスの普通のオブジェクトですから、その中にメソッドを追加することもできますし、そうすると… いろいろなことができてしまいます。 長くなってしまうので、今日はここまでですね。 終わりに 今回はメソッドのふりをするオブジェクトを作ってみました。もちろん、これは単体であれば普通にメソッドを定義すれば良く、大した意味を持ちません。しかし、オブジェクトには好き勝手に別のフィールドやメソッドを増やすことができ、自由度が高まります。 たとえば、私はこの機能を単体テストのためのモッククラスに応用してみています。 user = MockUserRepository() with user.get_all.patch([User(id=1, name="Alice")]): assert user.get_all() == [User(id=1, name="Alice")] この例では、 user.get_all の戻り値を一時的に上書きする機能を提供しています。 get_all はメソッドのように呼び出せますが、そこに patch() というメソッドがあるのは、今回の方法を使って「メソッドのふりをしたオブジェクト」に差し替えているからです。具体的には、また別の記事を書くかもしれません。 そんなわけで、ちょっとした小道具でした。長らくお付き合いいただきありがとうございます。みなさんも何かご存知の小道具があれば、ぜひ教えてください!あと、TJ DeVries さんの講演もぜひ見てみてください。 明日は、dev-shimada さんの「社内Tapリポジトリを作り自作ツールをHomebrewで配布する」です。 お楽しみに!
この記事は、 ニフティグループ Advent Calendar 2024 7日目の記事です。 はじめに こんにちは、会員システムグループでエンジニアをしている山田です。 皆様、CloudFront Functionsは使っていますでしょうか。 CloudFrontでエッジ処理を行いたい場合、 Lambda@Edge もしくは CloudFront Functions のいずれかを使用することになります。CloudFront Functionsはランタイムの強い制限を代償に、Lambda@Edge比で料金が約1/6と、安価に使用することができます。ニフティ社内でも静的サイトでのリダイレクト処理などに活用しています。 軽い処理であるとはいえ、継続的な運用を行うには型チェックやテストの恩恵を受けたいものです。 本記事はそのための環境をどう整えるか、という内容になります。 結論 結論から先に述べると TypeScriptだけで良い場合: tscでトランスパイルする テストもしたい場合: バンドラーでバンドル後、 正規表現でexportを除去 する という形になります。 前提 前提として、CloudFront FunctionsはサーバサイドJavaScriptではあるものの、 Node.jsではない ランタイムで動作します。このため、以下のような文法上の制約があります。(JavaScript 2.0ランタイムの場合) ECMAScript 5ベース const/let、async/awaitなど一部のES6文法にも対応 ES Modules非対応 CommonJS向けのexport構文がエラーとなる module.exports はmoduleがundefinedのためエラー 1ファイルのみデプロイ可能 これらの制約を満たすように作る必要があります。 対応方法 型チェックのみで良い場合 型チェックができればよい場合は、TypeScriptの導入のみで良いです。 pnpm add -D typescript tsconfigの設定は以下のようにします。 { "compilerOptions": { "target": "es5", ... "baseUrl": "./src", "outDir": "./dist" }, "include": ["**/*.ts"], "exclude": ["node_modules", "dist"] } targetはES5にしておきます。ES6文法の多くはCloudFront Functionsでも使用可能ですが、一部非対応の文法を使用しても気づかないため、ES5にしておくのが無難でしょう。 次にCloudFront Functions用の型定義を用意します。npmパッケージとして 公開されているもの があるため、これを使います。手元の環境では型情報をうまく参照できなかったため、内容をコピーして利用しています。 declare namespace AWSCloudFrontFunction { interface Event { version: '1.0'; context: Context; viewer: Viewer; request: Request; response: Response; } interface Context { distributionDomainName: string; distributionId: string; eventType: 'viewer-request' | 'viewer-response'; requestId: string; } interface Viewer { ip: string; } interface Request { method: string; uri: string; querystring: ValueObject; headers: ValueObject; cookies: ValueObject; } interface Response { statusCode: number; statusDescription?: string; headers?: ValueObject; cookies?: ResponseCookie; } interface ValueObject { [name: string]: { value: string; multiValue?: Array<{ value: string; }>; }; } interface ResponseCookie { [name: string]: { value: string; attributes: string; multiValue?: Array<{ value: string; attributes: string; }>; }; } } CloudFront Functionsのソースには上記の型を指定します。 function handler( event: CloudFrontFunction.Event ): AWSCloudFrontFunction.Request | AWSCloudFrontFunction.Response { ... } あとはtscでコンパイルすればJavaScriptファイルが得られます。型チェックのみを行う場合は別途コマンドを用意しておくとよいでしょう。 tsc テストもしたい場合 テストも行いたい場合、テスト対象関数をexportする必要が出てきます。またファイルを分割したくなることもあるでしょう。このため、基本的にバンドラを使うことになります。 しかしながら、バンドラの対応するいずれの出力形式でも問題を抱えています。 ESM形式: handlerのexportが必須、 export ~ が文法エラー CJS形式: handlerのexportが必須、 module.exports = ~ がmodule変数の未定義によりエラー IIFE形式: 関数定義が大きく変わる ESM、CJS形式ではexportされていない関数は不要と判断され、バンドル後の結果に残らなくなります。一方でexportすると出力結果にexportの文法が残るため、エラーを引き起こしてしまいます。これらは esbuildのissue でも議論されていますが、いずれもHackyな回避策となっています。 そこで、 ESM形式で出力したものからexportを除去する というアプローチを行います。 バンドラとして今回はtsupを使用します。 pnpm add -D tsup 以下のように設定を行います。 import { defineConfig } from 'tsup'; export default defineConfig({ entry: [ // 出力したいhandlerがあるファイル群 ], format: 'esm', platform: 'node', splitting: false, sourcemap: false, minify: false, clean: true, }); entryは複数指定できるため、複数の関数ファイルを一括して生成することが可能です。 またビルド後のファイルからexportを取り除くスクリプトを用意しておきます。 (コードによっては正規表現を調整する必要があるかもしれません) /* ビルド後のファイルを加工するスクリプト usage: node scripts/postbuild.js {build_dir} CloudFront Functionsはexportを解釈できないため、ビルド後のファイルからexportを削除する */ import * as fs from 'node:fs'; import * as path from 'node:path'; // export文を判別する正規表現 const regex = /export\s*{[^}]*};?/gs; const target = process.argv[2]; const targetPath = path.resolve(target); if (!fs.statSync(targetPath).isDirectory()) { console.error('Target is not a directory'); process.exit(1); } const files = fs.readdirSync(targetPath); for (const file of files) { const filePath = path.join(targetPath, file); const stats = fs.statSync(filePath); if (stats.isDirectory()) { continue; } const ext = path.extname(filePath); if (ext !== '.js') { continue; } console.log('Removing export statement from', filePath); const fileContents = fs.readFileSync(filePath, { encoding: 'utf-8' }); const newContents = fileContents.replace(regex, ''); fs.writeFileSync(filePath, newContents); } あとはビルド後にこのスクリプトを実行するようにしておけばOKです。 tsup && node postbuild.js ./dist まとめ CloudFront FunctionsをTypeScript・テストに対応した環境で書くための方法について解説しました。 どうしてもハック感のある方法を取らざるを得ないのですが、行っていることはシンプルなので今のところこれで運用することができています。テストにも対応できたことで、リダイレクトルール変更時のデグレ防止にも役立っています。 CloudFront Functionsの中身が複雑になってきて、動作の担保に不安がある方は試してみてはいかがでしょうか。 次回はnunaさんの記事です。お楽しみに。
この記事は、 ニフティグループ Advent Calendar 2024 6日目の記事です。 はじめに こんにちは。ニフティ株式会社の並木です。 皆さんは、タスクの割り当てや順番決めに頭を悩ませたことはありませんか? 私はこれまで、オンラインのあみだくじやルーレットツールを利用していましたが、あみだくじの結果を目で追ったり、何度もルーレットを回したりと、意外と手間がかかっていました。 「もっと簡単に、一発で機械的にタスクを割り当ててくれるツールが欲しい!」という思いから、SlackのワークフローとZapierを使用して、タスクを簡単に割り当てるツールを作成しました。 今回は、その方法についてご紹介いたします。 Slackのワークフローを作成する Slackでワークフローを新規作成します。 「Slack内のリンクから」を選択してください。 ステップを追加します。 「フォーム」を選択してください。 フォームの内容を設定します。 質問1でタスク名を入力するよう設定します。 質問2で人の名前を入力するよう設定します。 質問1、質問2で入力された内容を、slackにメッセージとして送信できるよう設定します。 「メッセージ」を選択してください。 「チャンネルへメッセージを送信する」を選択してください。 今回は「ワークフローが使用されたチャンネル」に、以下の内容のメッセージが送信されるよう設定します。 これでワークフローの作成は完了です! Zapierを設定する まずはトリガーの設定をします。 Appは「Slack」、Trigger eventは「New Mention」を選択します。 Slackに「タスク簡単割り当てワークフローを起動しました。」という文章が投稿されたらZapierが起動するよう設定したいので、Highlight Wordには「タスク簡単割り当てワークフローを起動しました。」を設定します。 Trigger for Bot Messages?は、Botによる投稿もトリガーの対象に含めるかを確認していますので、今回は「True」を選択してください。 タスクをランダムに割り当てる処理を書いていきます。 Appは「Code by Zapier」、Action eventは「Run Python」を選択します。 言語はPythonのほかにJavascriptを選択することもできます。 Input Dataの右側の入力ボックスにSlackで投稿された文章を設定し、左側の入力ボックスにキー名を入れます。 以下画像ではキー名を「original_text」としましたが、どんな名前をつけても良いです。 Codeの中で input_data['original_text'] と書くことで、Input Dataで設定したデータを取り出すことができます。 def custom_shuffle(lst): # 簡易的な疑似乱数生成 seed = sum(ord(c) for c in ''.join(lst)) for i in range(len(lst) - 1, 0, -1): seed = (seed * 1103515245 + 12345) & 0x7fffffff j = seed % (i + 1) lst[i], lst[j] = lst[j], lst[i] return lst # ワークフローを使った人の名前とタスクと人名を取り出す text = input_data['original_text'] lines = text.split('n') mention = lines[0] tasks = lines[4].split(',') people = lines[6].split(',') # タスクと人名をシャッフル tasks = custom_shuffle(tasks) people = custom_shuffle(people) # 結果を生成 result = "" for i in range(len(tasks)): person_index = i % len(people) # 人数を超えた場合は最初から割り当て直す result += f"{tasks[i]}:{people[person_index]}n" output = [{'result':result, 'mention':mention}] タスクを割り当てた結果を、Slackに投稿します。 Appは「Slack」、Trigger eventは「Send Channel Message」を選択します。 ChannelにSlack投稿するチャンネル名、Message textにSlack投稿するメッセージを入力します。 Codeの最後に output = [{'result':result, 'mention':mention}] と記載しておくことで、タスクを割り当てた結果とワークフローを使った人の名前を取り出して、メッセージに含めることができます。 これでZapierの設定は完了です! 実際に動かしてみる ワークフローを実行し、タスク名と名前をカンマ区切りで入力します。 「送信する」を押下すると、ワークフローを実行したチャンネルに、以下のメッセージが投稿されます。 さらに数秒待つと、タスクを割り当てた結果が「#bot_タスク割り当て」チャンネルに投稿されます! おわりに 以前の記事「 ZapierでX投稿するAPIを呼び出して結果をSlackに通知してみた 」では、Zapierから Slackへの連携を試しましたが、今回は逆に Slackの投稿をトリガーにして Zapierを起動させる方法を初めて実践しました。 今後も、SlackとZapierの連携を活用して、業務効率化や情報共有の改善に役立つツールを開発していきたいと思います。 明日は、 jimmysharp さんの「CloudFront FunctionsでもTypeScript&テストしたい」です。 お楽しみに!
この記事は、 ニフティグループ Advent Calendar 2024 5日目の記事です。 はじめに ニフティでWEBサービスの開発・運用を担当している渡邊です。 最近、運用の手間をかけずにWEBサービスを立ち上げる方法はないかなと考えていたところ、AWS App Runner(以下App Runner)に出会いました。 この記事では、App Runnerの特徴や他のサービスとの違いについて書いていきます。 App Runnerとは App RunnerはAWSのサービスの1つで、コンテナアプリケーションを簡単にデプロイ・運用できるプラットフォームです。 GitのコードやコンテナをAWSにアップロードするだけで環境が作れるので、インフラ周りを気にする必要がありません。 ECS(Fargate)をベースにしているので、負荷に応じて自動的にスケールするのが特徴です。 また、VPCやロードバランサーの設定が不要なので、ネットワークの知識がなくても使えます。 他のAWSサービスとの違い AWSには似たようなサービスがいくつかありますが、App Runnerは「とにかく簡単に使える」ことを重視して作られています。 主なサービスを比べてみましょう。 サービス メリット デメリット Amazon EC2 • サーバーを自由に設定できる • OSやミドルウェアを自分で選べる • どんな用途でも使える • 管理が大変 • 予想以上に費用がかかることも Amazon ECS •コンテナ運用に便利 • Fargateなら運用が楽 • 自動でスケールする • インフラの知識が必要 • 初期設定が面倒 Elastic Beanstalk • デプロイが簡単 • AWSの他サービスと連携しやすい • サーバー管理不要 • 自動でスケールする • AWSへの依存が強くなる • 初期設定が面倒 AWS App Runner • デプロイが簡単 • シンプルな設定項目 • 運用の手間なし • GitやDockerからすぐ使える • 自動でスケールする • 細かい設定ができない • AWSへの依存が強くなる 設定項目 設定する項目が少なくて、画面の流れに沿って進めるだけで使えます。 WAFも設定できるので、IP制限をかけたい社内向けのアプリも作りやすいです。 コスト フロントとバックエンドを分けたアプリを例に、コストを計算してみました。 トラフィック: 月間50万リクエスト 1リクエストあたり平均200ミリ秒 リソース要件: フロントエンド:1 vCPU、2GBメモリ バックエンド:2 vCPU、4GBメモリ サービス タスク/インスタンス ALB NAT Gateway データ転送 合計 ECS Fargate $138.66 $20.20 $32.40 $57.36 $248.62 EC2 $46.80 $20.20 $32.40 $57.36 $156.76 App Runner $195.75 不要 不要 不要 $195.75 計算式 ECS(fargate) フロントエンド vCPU コスト: $0.05056 × 1 vCPU × 750時間 = $37.92 メモリ コスト: $0.00553 × 2GB × 750時間 = $8.30 合計:$37.92 + $8.30 = $46.22 バックエンド vCPU コスト: $0.05056 × 2 vCPU × 750時間 = $75.84 メモリ コスト: $0.00553 × 4GB × 750時間 = $16.60 合計:$75.84 + $16.60 = $92.44 EC2 フロントエンド: $0.0208 × 750時間 = $15.60 バックエンド: $0.0416 × 750時間 = $31.20 データ転送の仮定条件 ALB経由(クライアント → Fargate): 平均リクエストサイズを1MBと仮定 データ転送量:500,000リクエスト × 1MB = 500GB NAT Gateway経由(Fargate → インターネット): データ転送量:10GB/月 データ転送コスト ALB 経由: (500GB - 1GB無料枠) × $0.114 = $56.91 NAT Gateway 経由: 10GB × $0.045 = $0.45 合計データ転送コスト: $56.91 + $0.45 = $57.36 コスト面ではEC2が最も安価ですが、運用の手間を考えるとApp Runnerも悪くないかなと思います。 App Runnerの裏側はECS(Fargate)のため、同じ環境でありながら安くなることがわかります。 さらに、App Runnerは実行時間ベースの課金体系を採用しているため、コストの予測が容易という利点もあります。 App Runnerの制限と考慮すべきポイント ARM64をサポートしていない 記事執筆時点ではARM64のコンテナイメージには対応していないため、x86_64アーキテクチャのイメージを使用する必要があります。 ARM64は、コストとパフォーマンスの両面でメリットがあるため、今後の対応に期待です。 カスタマイズ性の制限 とにかく簡単に使えることを重視しているので、細かい設定はできません。 特にVPC接続やログ設定などの高度な構成が必要な場合、作業が複雑になることがあります。 オートスケーリングの指標が限定的 同時実行数でしかスケールできず、CPUやメモリの使用率では制御できません。 そのため、特定のアプリケーションではスケーリング要件との不一致が生じ、効率的な運用が難しくなる可能性があります。 サイドカーコンテナ非対応 サイドカーコンテナが使えないので、ログ収集やモニタリングをする時に工夫が必要です。 まとめ コンテナを使うのが当たり前になってきた今、シンプルなWEBサービスを作るならApp Runnerはかなり使えると思います。 実際に試してみたら、約20分ほどでアプリケーションがAWS上で動作できるようになりました。プロトタイプや社内ツールを作るのにぴったりですね。 インフラの心配をせずに開発に集中できるのが、個人的にすごく気に入っています。 ただ、細かい設定やスケーリングが必要な場合は、ECSやEC2も検討した方がいいかもしれません。 参考 AWS App Runner AWS Fargate Amazon EC2 AWS Elastic Beanstalk
はじめに ニフティグループ Advent Calendar 2024 2日目の記事です ! はじめまして! 私はサポシスと呼ばれるチームに所属しており、普段カスタマーサポートセンター(以降CS)向けのアプリケーションの開発と運用を担当しています。 CS向けのアプリケーションはパッケージアプリケーションを利用するだけでなく、完全内製のアプリケーションの作成なども行っています。サポシスでは、これらの内製ツールのデプロイは Terraform と AWS SAM を使用しています。 本記事では、それらのツールを選択した理由と、実際の使ってみた開発メンバーの声を紹介していきたいと思います。 IaCの選択肢 デプロイ先はAWSとします。AWSで利用可能なIaCの一覧は以下の通りです。 Cloudformation AWS CDK AWS SAM AWS Chalice Serverless Frameworks Terraform Pulumi 各ツールの説明については、ここでは割愛します。 現在サポシスでは、ECSなどを含むアプリケーションは Terraform、Lambdaのみで完結するツールはAWS SAMを使用しています。 ツールの選定理由 それぞれのツールを選んだ理由について、各ツールの長所と短所を交えながら説明していきます。 Terraform 冪等性が担保される Terraformは宣言型のIaCとして設計されており、状態をtfstateファイルを用いて管理します。デプロイにはAWSのAPIを利用しており、APIの呼び出し結果を元に、tfstateファイルを更新することで冪等性を担保しています。また、デプロイ時にはtfstateファイルとAWS上のリソースを比較し、差分がある場合のみデプロイを実行するため、無駄なデプロイを防ぐことができます。 リソースのAPIコール順序はTerraform側で自動的に制御されるため、コード内での記述順序を気にする必要はありません。 社内・社外ともにナレッジが豊富 Terraformはニフティ内で5年以上前から利用されており、多くの社員が使い慣れているツールです。そのため、トラブルが発生した際も社内の経験者に相談することで解決できることが多く、社内ドメインに沿ったベストプラクティスなども成熟しつつあります。 また、社外でも広く利用されているため、インターネット上の情報も豊富で、トラブルシューティングが容易です。 モジュール分割機能が便利 モジュールを再利用可能な単位に分割でき、環境依存の設定を外側に分離することで、本番環境と開発環境のデプロイを効率的に管理できます。 サーバーレスアプリの開発には向いていない Terraformはサーバーレスアプリケーションの管理が得意ではありません。特にLambdaに関しては、1つの関数に対して多くのプロパティを設定する必要があり、また、デプロイに必要なzipファイルのビルドプロセスも複雑になりがちです。そのため、私たちのチームではこれらの課題に対処するためにAWS SAMを採用しています。 コラム:動画紹介 Terraformのメリットについて、もっと詳しく知りたい方は、うちのメンバーが分かりやすく解説しているTech talkの動画もチェックしてみてください! AWS SAM 各種パッケージの管理が容易 AWS SAM はデプロイ前にビルドプロセスが実行します。その際に必要なパッケージのインストールとデプロイ用のzipファイルの作成が自動的に行われます。 こちらのbuildはオプションでコンテナ上でも行うことができるため、build環境に依存しない作りにすることができます。 開発機能が豊富 ローカル環境での実行・テストが可能で、コンテナベースの動作により環境依存性を排除できます。また、syncというリモートのAWS上とローカルのコードを動悸する機能も搭載されており、開発効率を向上させることができます。 チーム間での管理が容易 CloudFormationを利用して管理されているため、tfstateファイルといった依存関係管理ファイルをS3にアップロードする必要がありません。また、リソースの削除もCLIだけでなく、AWSコンソールから一括で行うことができます。 大規模アプリケーションは不向き CloudFormationをベースとしているため、モジュール化して分離することがほぼ不可能となっています。また、YAML形式で書かれているため、コードが冗長になってしまい、可読性が低下することもAWS SAMの課題です。 実際に運用してみて チームメンバーからの感想を聞いてみました。 コンソールでの作業手順の作成が不要になったのは嬉しい デプロイ時はIaCを通さなければいけないので、全員が変更履歴を追えるようになった Git(GitHub)で管理できるようになったので、バージョン管理ができるようになった デプロイ時は環境差分のみに集中すればよくなったので、非常にリリース時のミスが減った AWS SAMに関しては、実行まで手元のエディタで全て完結するのは開発体験としていい デプロイが遅いので、待たなければいけないのは不便(lambdaのコピペは直ぐにすんだので) 管理が楽になると回りから聞いていたけど、実際は思っていたより楽ではなかった IaCが読めるようになるまでの学習コストが高い コードで全て管理できるが、グラフィカルに依存関係を見ることができないのは残念 IaCの導入によって管理効率や変更履歴の追跡が向上した一方で、学習コストやデプロイ時間などの運用面での課題も見えてきました。 まとめ 本記事では、ニフティのサポシスチームにおけるIaCツールの選択と運用について紹介しました。Terraformは冪等性の担保や豊富なナレッジ、モジュール分割機能など、大規模なインフラ管理に適している一方、AWS SAMはサーバーレスアプリケーションの開発に特化した機能を提供しています。 実際の運用では、変更履歴の追跡やバージョン管理、環境差分の管理が容易になるなどのメリットが確認できました。一方で、学習コストやデプロイ時間、可視化の課題なども浮き彫りになりました。 これらのツールを目的に応じて使い分けることで、より効率的なインフラ管理が可能になっています。今後も開発者の声に耳を傾けながら、より良い開発環境の整備を進めていきたいと思います。
初日から遅刻してすみません。12/1が日曜始まりであることを完全に忘れていました。 ニフティでスクラムエバンジェリストをしている西野です。業務では2チームのスクラムマスターをしています。 最近、シチュエーショナルリーダーシップの本を読んで実践してみたので、今回はスクラムにおけるリーダーシップについてお話ししたいと思います。 スクラムチームにリーダーは不要? 「スクラムマスターはチームリーダーではない」「スクラムチームには上下関係がないので、いわゆるリーダーはいない」という話を聞いたことはないでしょうか。 (スクラムガイドには「スクラムマスターは真のリーダーである」とも書かれてあり、混乱するかもしれませんが、これについては後述します) リーダーってなんだろう? リーダーと聞いてどんなイメージが浮かびますか? 社会人ならチームリーダーや上司、学生の頃なら部活の部長や生徒会長が身近なリーダー像でしょうか。最近では新しい総理大臣のリーダーシップにも注目が集まっています。 いずれにしろ、リーダーとはチームをゴールに導く力があり、組織の意思決定を行い、人が後ろをついていきたくなるようなスター性、カリスマ性を持っているようなイメージがあります。 そんな理想的なリーダーに自分がなれと言われたら困りますが、身近にいてくれたらどれほど頼もしいでしょうか。スクラムでは、そのようなリーダーを「不要」としているわけではありません。 リーダーと似たような肩書きとして「ボス」もあります。ボスという言葉には「ふんぞりかえって椅子に座り、権威的であり、指示はするが直接手は動かさない人」のようなイメージがあります。 この「ボス」と一緒に働くのは(たとえボスに心酔しているとしても)楽しいよりは苦しそうなイメージがあります。 一方で、もし「頼りになるリーダー」と一緒に働けたら、より安心して、幸せに働けそうなイメージがわくのではないでしょうか。 スクラムチームにボスは不要ですが、リーダーは必要です。 さらに、スクラムチームのリーダーはひとりだけではありません。リーダーシップが発揮できる人がいればいるほど、チームのアジリティが増していくのです。 この「チームにリーダーがたくさんいる」とはどんな状態でしょうか。 柔軟なリーダーシップスタイル 自分がスクラムにおけるリーダーシップについて意識するようになったのは、 Regional Scrum Gathering Tokyo 2022 1日目基調講演にあったAgile Program Managementというセッションがきっかけでした。 このセッションで「(スクラムチームは)全員がリーダーであり、リーダーとフォロワーがシーンによって切り替わる」という話を聞き、参加者同士でリーダーシップの解釈についてかなり盛り上がった記憶があります。 この話を聞いた当初は、全員がリーダーという状況がイメージできませんでした。全員が意思決定者という状況では、進むものも進みません。「船頭多くして船山に上る」という諺もあるくらい普遍的なアンチパターンです。 ここで大事なのは、全員がリーダーという点よりも「 リーダーとフォロワーがシーンによって切り替わる 」という点です。 例えば、あるシーンではiOSが得意なAさんがリーディングするが、別のシーンではAndroidが得意なBさんがリーディングするという状況を指しています。 この場合、AさんもBさんもどちらもリーダーシップを発揮しています。しかし、AさんもBさんも常にリーダーではなく、お互いが得意なシーンごとにリーダーをサポートする側(フォロワー)にまわっています。 技術領域以外でも、説明が上手なCさんにチーム外との交渉をお願いしたり、精度が求められる仕事はゆっくりだが確実に仕事をこなしてくれるDさんを中心にお願いしたりするなど、成熟したチームではその人の特性や得意分野に応じて自然と場をリーディングする人が変わっていきます。 シチュエーショナルリーダーシップという考え方 当時この「リーダーとフォロワーがシーンによって切り替わる」という考え方に感銘をうけたのですが、先日同僚から「新1分間リーダーシップという本が良い」と聞いて読んだところ、この「柔軟なリーダーシップスタイル」についてわかりやすく書かれていたので紹介します。 人がどういう成長過程にあるかにあわせて、発揮するリーダーシップを変える 例えば特定の分野が未経験の新入社員と、経験済み10年目の社員が同時にチームにアサインされたときに、業務に対して同じ教え方をするでしょうか。 1年目のメンバーにはかかりきりで教えることになるでしょうし、10年目のメンバーには逆に「もっと効率的なやり方はないか」と教えを乞うかもしれません。 このような単純な例だとわかりやすいですが、現実はもっと複雑です。 対応者の分野ごとの熟練度とリーダーの接し方がかみあわないと、「わかっているのに細かく指示をしてくる」「ぜんぜんわからないのに何の指示もなく途方にくれる」といったチーム内不和がうまれます。 意識合わせ(アラインメント)対話をする その人の熟練度の認識とリーダーの接し方で不和を生まないために、リーダーは1on1の場でこの認識を合わせます。スクラムの場合は、まずはスクラムマスターとチームメンバーで1on1をするのが良いでしょう。 自分も月1の頻度でチームメンバーと1on1をしているので、実際に以下について聞いてみました。 成長段階の認識合わせ 「新1分間リーダーシップ」の本では、仕事における発達の過程を以下のように定義しています。 D1:意欲満々な初心者 メンバーの状態:技能が低く、意欲が高い 仕事のやりはじめ。なにもわからないが、指示にどんどん従って仕事を覚えるやる気に満ち溢れている。 リーダーの接し方:細かく具体的に指示を出す「指示型」で接する。リーダーが自らやってみせるシーンもある。 D2:期待が外れた学習者 メンバーの状態:技能が低〜中で、意欲は低い 学習が進んで技能は身についてくるが、さらに見えてきた難しさや自分の技能の追いつかなさなどでやる気が低下している リーダーの接し方:「指示型」と「支援型」どちらも行う。指示の背景にある意図を説明し、メンバーの意見を聞いて意思決定に加わるよう促す。 D3:慎重になりがちな貢献者 メンバーの状態:技能が中〜高で、意欲は不安定 できることが増えてきて、仕事に対する意欲が戻ってくる。しかし、ひとりで回せるほどの技能はなく自信をもって取り組めない。 リーダーの接し方:「指示型」は少なく、「支援型」が多い。メンバーとリーダー共同で意思決定を行う。リーダーは聞き役に回ってアイデアを引き出す。 D4:自立した達成者 メンバーの状態:技能が高く、意欲も高い この分野においては何でも任せろ!という状態。自信もある。 リーダーの接し方:意思決定も含めてその人に任せ、指示はしない。その人の貢献を評価し、成長を応援する。 このように、学習段階に応じてリーダーの接し方が変わるということを説明した上で「自分がどの段階にいるか」をメンバーに確認してもらいます。 本来はタスクごとに細かく認識合わせをしたほうがいいのですが、まずは「今自分がいるスクラムチームの仕事においてどのくらいか」という粒度から始めました。 また、スクラムマスターの私から見てどう見えているかということも伝えて相互に認識合わせをしました。 自分がジョインしている2つのチームでやってみたところ、相互の認識はほぼ一致していましたが、ズレる場合は本人が思うより私の評価の方が高く感じていることが多かったです。 基本的にはメンバーの意見を重視しますが、過度に低く評価しているようであれば、事実を交えてもっとできていることを伝えることも行いました。 もし1on1のコーチ側が思うよりメンバー自身の評価を高く置いている場合は、メンバーの認識そのままで目標を立ててよいそうです。高い目標に向けて頑張ろうとするので、次第に本当にその評価に追いつくと本にありました。 全員がリーダーに「なれる」チームをめざす 上記の認識合わせはまだ1回やっただけなので、これによって具体的にチームが変わったということは現段階では起きていません。ただ、1on1の始まりのフェーズで今後も使っていきたいと思っています。 自分の立ち位置を示すものとしてわかりやすいこともありますが、個人的には、まずはこのような考え方を通じて、可変するリーダーシップのあり方をメンバーに伝えていくことが大事だと考えているからです。 冒頭に「スクラムチームにリーダーは不要?」と書きましたが、スクラムチームには、ボスや固定のリーダーは不要です。シーンに応じて誰でもリーダーの帽子を被れるようにします。 これは「スクラムマスターは真のリーダーである」というスクラムガイドの言葉とも矛盾はしません。私はこの言葉を、スクラムマスターは、チームや組織のあり方を良くできそうなトライアンドエラーを 誰よりも率先して行う という意味で捉えています。 例えば、シチュエーショナルリーダーシップをスクラムマスターが率先して実施し、それを見てチームメンバーがリーダーシップを発揮してくれるとしたら、それはスクラムマスターが真にチームをリーディングしている状態といえるのではないでしょうか。 リーダーとは、その名の通り先頭に立ってものを引っ張る人です。ずっと一人でやっていたら疲れてしまうし、フォロワーも一人の背中を見て学べることは限られてしまいます。 シーンに応じて色々な人がリーディングできれば、色々な人の背中を見て学びをもっと増やすことができます。また、自分がリーディングしているときも、後ろにはリーダーになれるくらい強力なフォロワーがついているという安心感もあります。 スクラムチームは決してリーダーが不在なチームではありません。シーンに応じて誰でもリーダーの帽子を被ることができるチームなのです。 シチュエーショナルリーダーシップという考え方は、自律的なチームを育むために良い足掛かりになると思います。 スクラムにおけるリーダーシップのあり方を伝えるために、1on1などのシーンで、シチュエーショナルリーダーシップの説明とお互いの「成長段階の認識合わせ」にぜひトライしてみてください。
この記事は、 ニフティグループ Advent Calendar 2024 3日目の記事です! 2日続けての投稿になります。 はじめに Azure も AWS と同じく仮想ネットワークのサブネットをつくることができます。 今回は、いつもどれくらいのCIDRで作成すればいいのかわからなくなるので、少し考察してみたときの記録をご紹介します。 システム的な最小値 サブネット サポートされる最小の IPv4 サブネットは /29、最大は /2 です。 Azure Virtual Network に関する FAQ サブネット自体の制限として、上記のルールがあります。 AWSのサブネットは /28 までなので、かなり細かく分割できますね。 その他のサービス Azureには サブネットの委任 という仕組みがあります。 Azure 仮想ネットワークでのサブネットの委任とは これは、特定のAzure PaaSサービスでサブネットを占有する仕組みであり、Azure Database for MySQL などをプライベートなネットワーク上に構築したいときなどに使用します。 察しが良い方は気づいたかもしれませんが、実はPaaSサービスごとに委任されるサブネットの大きさに制限がかかっています。 サービス名 最小 最大 備考 参考 Azure App Service /28 /26(1つのマルチテナントの最大スケール数) MPSJ を利用する場合は /26 が最小 https://learn.microsoft.com/ja-jp/azure/app-service/overview-vnet-integration Azure Database for MySQL /29 不明 https://learn.microsoft.com/ja-jp/azure/mysql/flexible-server/concepts-networking-vnet システムに予約されるIP サブネットを作成した時、サブネットに割り当てたIPがすべて使えるわけではありません。先頭と末尾のアドレスはそれぞれネットワークアドレスとブロードキャストアドレスとして予約されます。また、Azureは1つのサブネットにつき3つのアドレスをシステム的に予約します。 そのため、最終的に利用可能なIPは、 サブネットのIPCIDRから5アドレス除いたIPになります。 Azure Virtual Network に関する FAQ 運用に沿った最小値を考察 IP制限するということは、それだけスケーリングの幅を狭めることとなります。また、仮想ネットワークを分割しすぎると管理することも大変です。そのためAzureでは VNetは大きく、Subnetは小さく作ることが推奨されています。 Azure Virtual Network に関する FAQ しかしながら、弊社のネットワーク構成の制約上、VNetは大きく作成することができません。 では、どれくらいのCIDRが現実的な設定値なのでしょうか? 1つのVNetを占有するアプリケーションの場合 おそらく1つのVNetを1つのアプリケーションで占有することになると思います。 利用するサービスにもよりますが、最低でも /26 のVNetを用意して、4つ以上( /28 )に分割して使いたいです。 より大規模な場合は、 /24 で払い出したうえで8つ以上( /27 )に分割して使えると安定して運用ができそうです。 1つのVNetに複数のアプリケーションがあいのりする場合 こちらはあいのりするアプリケーションの数によります。 4つほどなら、最低でも /26 のVNetを用意して、4つ以上( /28 )に分割で足りると思います。 それ以上は、4つほどなら、最低でも /25 のVNetを用意して、8つ以上( /28 )に分割で足りると思います。 小さいアプリケーションだと思うので、1つのサブネットは /28 で十分賄えると思います。 まとめ 本記事では、サブネットの設定をしたときの考察を書きました。 IP制限されている環境と通信したい場合は適切にsubnetを作成して管理してきたいですね。