サイオステクノロジー(Tech.Lab)のブログ - TECH PLAY

TECH PLAY

サイオステクノロジー(Tech.Lab)

サイオステクノロジー(Tech.Lab) の技術ブログ

全739件

はじめに ども!最近はAIを使った自動化に注目している龍ちゃんです。実は最近、SIOSテックラボのX担当者もやっているんですが、投稿頻度によっては半分ぐらい自分のブログ宣伝をしているので、ほぼ私物になっています。 今回はそんな問題を解消してくれるプロンプトを紹介していきたいと思います。ただ作るだけではなく、簡易的なプロンプトから始めて、詳細プロンプトに落とし込んだ過程を含めて、検証の結果とともにブログ記事にできれば良いなと思っています。 この記事では以下の内容をお伝えします: X(旧Twitter)投稿の自動化ニーズの高まり 本記事の目的:プロンプトの違いによる投稿品質の比較検証 検証方法の概要 なぜAI生成に頼ったのか ブロガーの悩み ブログの宣伝って難しいですよね。この記事でわかることを正確に伝えるという作業が必要になります。自分が執筆したブログであれば、この点は簡単ですよね。 ですが、自分以外の人間が、自分のあまり知らない技術領域について書いた技術ブログの宣伝をするのは「何を書いたらいいのかわからない」「読むのも若干辛い」みたいなことが発生します。そして、これがエンジニアでなければ、まさにそこに広がるのは宇宙なわけですね。 そこで、過去に投稿した投稿に合わせてしまったり、当たり障りのない文章が作られるというのが日常茶飯事になっています。そして、エンジニアが文章を考えるとですね、マーケティング的視点があるわけではなく、宣伝の手法に詳しいわけでもなく、なんとなくいつも使っているXのような文面で投稿してしまうという問題もあります。 結論として、Xの投稿文を考えるのは難しいということですね。 具体的な悩みとしては: X投稿を考えるのが面倒 何を書けばいいかわからない 毎回同じような投稿になってしまう 記事は書けるけど、それをどう宣伝していいかわからない AI生成への期待 そこで生成AIの出番です。あわよくば、生成AIにブログのコンテンツを投げたら、Xの投稿文を作ってもらえると嬉しいですよね。記事のコンテンツの内容に基づいて、コピー&ペーストするだけで投稿してくれたら嬉しいです。 その文章がマーケティング的視点が入っていて、最適化されている文章だと、なお良いな。全て自動化してくれて、宣伝も自動化してくれるとなお良いという現状です。この点は夢物語です… そんなことを考えていたのですが、最近契約したClaudeで、そんなことができそうだなぁということでやってみたらできたという流れです。 簡単なプロンプトから詳細プロンプトまで実験をしてみたので、その結果を含めて今回得られた知見についてお話ししていきます。 期待していたことは: とりあえず何か投稿文を作ってほしい 記事の内容を要約してほしい できれば修正なしでそのまま投稿したい 検証実験の設計 ここでは、実験の過程について、検証をした過程について説明していきます。 実験条件 実験条件としては、 同一のURL に対して行います。これに対して簡易プロンプトと詳細プロンプトで、それぞれ3パターンずつプロンプトを投げて実験を行いました。 こちらの生成結果はGistにまとめています 。リストを開いて右に並べながら読んでいただけるとわかりやすいかと思います。 プロンプトパターン 検証するにあたって、簡易プロンプトは以下のような実装をしています。言われると簡単な指示だけを与えているものなので、とても簡単なものです。 比較して詳細プロンプトは全体300行になる比較的大きなプロンプトになっています。使用方法としては、URLを入力として与えるだけで、システムプロンプトが自動的に起動し、生成物を作ってくれます。 簡易プロンプト :基本的な指示のみ 詳細プロンプト :具体的な要件と制約を明記 簡易プロンプトで試してみた結果 最初に試したプロンプト https://tech-lab.sios.jp/archives/48160 このURLの内容からX投稿を作成してください 実際にできた投稿文 下に実際に生成された事例を示します。実験は計3回行い、20個の投稿文が生成されました。結果としてURLが含まれてないもの、文字数がXの投稿制限に引っかかるものなど、生成物をそのまま投稿として扱うことができないようなものが生成されてきました。 一応Xは課金をしているので、280文字以上は投稿できるんですが、それではリンクカードが表示されないという問題があったため、280文字以内での投稿というのを心がけています。 ですが、URLから記事の内容を取得して、記事に基づいた投稿文は生成してくれました。多少人の手を加えれば充分投稿できるようなものになっています。 # 良い例 ``` 今すぐ使えるClaude制御プロンプト集📝 🚫 Artifact生成を止めたい時 「成果物を作らずに、考え方だけ教えて」 「禁止事項:Artifact生成」 💬 軽い回答が欲しい時 「3行くらいで説明して」 「一言でどう思う?」 🔄 スレッド継続したい時 「今までの議論を引継ぎ資料にまとめて」 他にも使えるテクニック満載👇 https://tech-lab.sios.jp/archives/48160 #Claude #プロンプト集 ``` # 悪い例 ## URLがない ``` Claudeに「転職活動どうしよう」って聞いたら →12段階の転職完全ロードマップ作られた😂 軽い質問のつもりが教科書レベルの資料に... Claudeは「めちゃくちゃ賢くてやる気のある赤子」 明確な指示で制御するコツをまとめました📝 #Claude #AI #あるある ``` ## 280文字突破 ``` 🎯 Claude調教の実績報告 k8s完全初心者から ↓ 人間主導学習プロセスで DaemonSet運用レベルまで到達! ポイント: 1. 大枠把握からスタート 2. 段階的深堀り質問 3. 理解度確認を必ず実施 4. 自分の言葉で例え話 Claudeを個人チューターとして最大活用🚀 詳しい学習フロー👇 Claude調教術|暴走パターンを制御する3つのプロンプトテクニック #Claude学習法 #スキルアップ ``` 何が物足りなかったか ここでは生成された生成物の何が足りなかったのかについて考えていきます。 やはり1番重要なのはURLです。URLがないのでどこに飛べばいいのかわからないという投稿は、さすがにブログの宣伝投稿としては不適切です。次に硬すぎる文体ですね。文体としてはもう少し柔らかいもの、簡単な文章という方が良いかと思います。そして、あわよくば、人間が投稿したように模倣していただければいいんじゃないかなぁと思います。 次にハッシュタグですね。ハッシュタグを生成するのは、正直、僕自身もどうやればいいのかわからないので、この辺の効果測定は難しいなと思います。ですが、ハッシュタグがないものは、さすがに「手抜き」なんじゃないかなと思ったりもしちゃうわけですね。 まぁ簡単に言うと、手動による見直しが見込まれました。 具体的な問題点: 長すぎる(文字数オーバー) 堅すぎる文体 「これ投稿して大丈夫?」という不安 結局自分で書き直すことに… 詳細プロンプトでの生成結果 使用したプロンプト例 全文は長いので、プロンプトのすべてをここに配置することができませんが、要点のみ最後に付録として添付しておきます。 文字数制限 トーン指定 ハッシュタグ要件 エンゲージメント要素 構造化された出力形式 生成結果 # ブログの評価 **総合評価:A(高品質)** **技術的正確性:⭐⭐⭐⭐⭐(5点満点)** **実装レベル:本格実装** **対象読者:中級者〜上級者** **実用性:⭐⭐⭐⭐⭐(5点満点)** ## 主要な技術要素 - Claude 4(Sonnet)を使用したプロンプトエンジニアリング実践 - 3つの制御テクニック:成果物制御、会話継続、ペース調整 - システムプロンプト設計とArtifact生成制御 - スレッド管理とトークン最適化手法 ## 記事内の具体的な数値・効果 - **記事内で実証済みの制御手法**:3つの明確なテクニック体系 - **実際の学習事例**:Kubernetes習得プロセスの詳細記録 - **具体的改善例**:300行→50行程度への成果物サイズ制御 - **問題解決実績**:スレッド上限問題の具体的回避策 ## 注意事項・制限 - 記事内で言及されている制限事項:スレッドが長くなると効果が薄れる傾向 - 実装時の注意点:プロンプトの詳細指定が重要 - Claude固有のノウハウ:他のLLMでは効果が異なる可能性 ## 誇張表現検証結果 - **初回生成での誇張なし**:記事内の実証済み手法のみを使用 - **修正内容**:なし(記事内容と完全一致) - **最終版での整合性確認結果**:✅ 全パターンで記事内容と整合性確認済み --- # ブログの投稿パターン ## Aパターン:効果重視・数値訴求型 **投稿文:** ``` Claude調教術|暴走パターンを制御する3つのプロンプトテクニック 🚀 Claude暴走を3つのテクニックで完全制御! 📝 成果物制御・会話継続・ペース調整で理想的なAI活用 ⚡ 技術スタック: #Claude #プロンプトエンジニアリング 🔧 300行→50行への成果物最適化も実現 #AI #LLM #効率化 #エンジニア ``` - **記事内容との整合性:** ✅ 確認済み - **投稿推奨時間:** 火曜日 21:00-22:00(エンジニア活動ピーク・技術学習時間) ## Bパターン:課題共感・解決提案型 **投稿文:** ``` Claude調教術|暴走パターンを制御する3つのプロンプトテクニック 😰 Claude君止まって!大規模Artifact生成で困ってませんか? 💡 明確な指示とGoal設定で暴走制御が可能になります ⚡ 成果物制御・スレッド管理・段階的学習の3技法 🎯 スレッド上限問題も引継ぎ資料で解決 #Claude #プロンプトエンジニアリング #AI #LLM ``` - **記事内容との整合性:** ✅ 確認済み - **投稿推奨時間:** 水曜日 12:00-13:00(昼休み・課題解決ニーズが高い時間) ## Cパターン:技術トレンド・学習促進型 **投稿文:** ``` Claude調教術|暴走パターンを制御する3つのプロンプトテクニック 🔥 2025年のプロンプトエンジニアリング実践術 📚 Claude制御テクニック3選の詳細解説 ⭐ 人間主導の学習プロセスでスキルアップ加速 🚀 Kubernetes習得事例で段階的学習の威力を実証 #プロンプトエンジニアリング #Claude #AI学習 ``` - **記事内容との整合性:** ✅ 確認済み - **投稿推奨時間:** 金曜日 18:00-19:00(週末学習準備・新技術トレンド確認時間) --- ## 投稿時間最適化の根拠 ### エンジニア向け最適投稿時間 - **Aパターン(火曜21:00)**:技術習得意欲が最も高い時間帯 - **Bパターン(水曜12:00)**:実務課題解決のニーズが高い昼休み - **Cパターン(金曜18:00)**:週末の技術学習計画を立てる時間 ### ハッシュタグ効果分析結果 - **#Claude** - 2025年最も注目されるAI技術キーワード - **#プロンプトエンジニアリング** - エンジニア間で急成長中のスキル分野 - **#AI** **#LLM** - 幅広いリーチを狙える基本タグ - **半角英語優先**:バイト効率とトレンド性を両 生成結果の分析 実際の生成物について見ていきましょう。プロンプトで3パターン、それぞれ別の観点から投稿文を生成しているので、1つの生成物に3つの投稿文(計9件)が生成されています。 文字数制限も280文字以内に収まるように計算されて最適化されています。僕なら絶対考えつかないようなキーワードで、ブログが表現されており、体感としてはこれは充分投稿に値するだろうと思える内容でした。 また、ハッシュタグに関しても、検索から適切なハッシュタグを検索しているというステップを作成して検索して策定しています。ですが、正直この辺の判断は投稿していきながら、Xで詳細な分析を行わないとわからないので、正確なことは言えません。 改善された点: 実際の生成文例 文字数の適切さ 魅力的なキャッチフレーズ 関連性の高いハッシュタグ エンゲージメント促進要素 そのまま使用可能性の評価 実際にこちらのプロンプトは2週間ほど運用しているのですが、問題としては多少誇張表現を使ってしまう部分、記事の内容に基づかないことなどはあるものの、比較的問題のない投稿文になっています。 8割程度はそのままコピー&ペーストで投稿を行うことができています。また、微調整に関しても、問題点をチャットで指摘をすることで、再生成を行ってくれるため、人間の作業としてはURLを選択して投稿文を確認し、問題があったら指摘するという簡単なフローに変わりました。 結果を比べてみて 簡易プロンプトと詳細プロンプトでそれぞれ比較を行い、分析考察を行っていきます。 簡易プロンプトの課題 簡易プロンプトの問題点としては、出力の品質が安定していないものが挙げられます。やはりXの投稿文として求めているスタイルというものは人それぞれ違いますが、今回は僕が求めているスタイルに合うものは合計の出力数の2割程度しかありませんでした。 そして毎回出力によってパターンが異なるため、確率の低いガチャを回してるようで楽しくなっちゃいましたね。 また、人間が修正する手間が必ず発生しているため、アイディアとしては充分使えますが、投稿文をそのまま生成するという当初の目的から外れてしまいます。 ガチャ要素 :毎回違う結果で安定しない 「今日は良い投稿文、昨日はイマイチ」の繰り返し 結果が予測できないので、結局人間が判断・修正が必要 時間かけて修正するなら最初から自分で書いた方が早い 詳細プロンプトの安定性 詳細プロンプトでは、実行計画をプロンプトに埋め込んでいるため、すべての生成の過程で統一のステップを踏むことにより安定性があります。完全に保証されているわけではないんですけどね。 実際にブログのコンテンツ内容に基づいていると判断していても、人間の目で確認したところ事実に基づいていない投稿文が存在していました。 ですが、3パターンの投稿文が生成されるので、パターンの1つは使えるパターンがあるように感じています。品質チェック項目があるため、文字数280文字を超えることはほぼなく、投稿文としても記事の内容に基づいていることが多いです。 今回で言うならば、再生成を行ったのは全体中1つだけでした。 ほとんどコピペでの運用が実現しています。 一定品質の保証 :どのパターンも安心して使える 狙った通りのアプローチで生成される ほぼそのままコピペで投稿可能 「今日もちゃんとした投稿文ができた」安心感 視覚的比較用ルーレット #prompt-gacha-container { font-family: 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'Yu Gothic', sans-serif; background: linear-gradient(45deg, #ff9a9e 0%, #fecfef 50%, #667eea 100%); border-radius: 20px; padding: 30px; max-width: 900px; margin: 20px auto; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); } .gacha-title { text-align: center; color: #2c3e50; margin-bottom: 10px; font-size: 2rem; font-weight: 700; text-shadow: 2px 2px 4px rgba(0,0,0,0.1); } .gacha-subtitle { text-align: center; color: #7f8c8d; margin-bottom: 30px; font-size: 1.1rem; } .gacha-machines { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-bottom: 30px; } .gacha-machine { background: linear-gradient(145deg, #ffffff, #f0f0f0); border-radius: 20px; padding: 25px; box-shadow: 10px 10px 20px rgba(0,0,0,0.1), -10px -10px 20px rgba(255,255,255,0.5); text-align: center; position: relative; overflow: hidden; } .machine-title { font-size: 1.3rem; font-weight: bold; margin-bottom: 15px; color: #34495e; } .simple-title { color: #e74c3c; } .detailed-title { color: #27ae60; } .gacha-display { background: #2c3e50; border-radius: 15px; height: 120px; margin: 20px 0; display: flex; align-items: center; justify-content: center; font-size: 3rem; color: white; border: 5px solid #34495e; position: relative; overflow: hidden; } .gacha-button { background: linear-gradient(145deg, #3498db, #2980b9); border: none; border-radius: 50px; color: white; font-size: 1.1rem; font-weight: bold; padding: 15px 30px; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 10px 20px rgba(52, 152, 219, 0.3); margin: 10px; } .gacha-button:hover { transform: translateY(-3px); box-shadow: 0 15px 25px rgba(52, 152, 219, 0.4); } .gacha-button:active { transform: translateY(-1px); } .simple-button { background: linear-gradient(145deg, #e74c3c, #c0392b); box-shadow: 0 10px 20px rgba(231, 76, 60, 0.3); } .simple-button:hover { box-shadow: 0 15px 25px rgba(231, 76, 60, 0.4); } .detailed-button { background: linear-gradient(145deg, #27ae60, #229954); box-shadow: 0 10px 20px rgba(39, 174, 96, 0.3); } .detailed-button:hover { box-shadow: 0 15px 25px rgba(39, 174, 96, 0.4); } .stats-display { margin-top: 15px; font-size: 0.9rem; color: #7f8c8d; } .success-rate { background: rgba(255, 255, 255, 0.8); border-radius: 10px; padding: 10px; margin-top: 15px; } .rate-bar { height: 20px; border-radius: 10px; margin: 5px 0; position: relative; overflow: hidden; } .simple-rate { background: linear-gradient(90deg, #e74c3c 20%, #95a5a6 20%); } .detailed-rate { background: linear-gradient(90deg, #27ae60 80%, #95a5a6 80%); } .rate-text { position: absolute; width: 100%; text-align: center; line-height: 20px; font-weight: bold; color: white; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); } .spinning { animation: spin 0.1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .results-section { background: white; border-radius: 15px; padding: 25px; margin-top: 20px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08); } .results-title { text-align: center; color: #34495e; margin-bottom: 20px; font-size: 1.3rem; font-weight: 600; } .trial-results { display: flex; justify-content: space-around; margin: 20px 0; } .trial-group { text-align: center; } .trial-icons { font-size: 1.5rem; margin: 10px 0; line-height: 1.8; } .trial-label { font-weight: bold; margin-bottom: 10px; } .simple-label { color: #e74c3c; } .detailed-label { color: #27ae60; } .conclusion { background: linear-gradient(135deg, #ffeaa7, #fab1a0); border-radius: 15px; padding: 20px; margin-top: 20px; border-left: 5px solid #e17055; } .conclusion-title { color: #d63031; font-weight: bold; margin-bottom: 10px; font-size: 1.1rem; } .conclusion-text { color: #2c3e50; line-height: 1.6; } @media (max-width: 768px) { .gacha-machines { grid-template-columns: 1fr; } .trial-results { flex-direction: column; gap: 20px; } } プロンプトガチャマシン 簡易プロンプト vs 詳細プロンプト – あなたならどっちを選ぶ? 簡易プロンプトマシン ガチャを回す! 成功率 20% まずは試してみよう! 詳細プロンプトマシン ガチャを回す! 成功率 80% まずは試してみよう! 10回試行の結果比較 簡易プロンプト 成功: 2回 / 失敗: 8回 詳細プロンプト 成功: 8回 / 失敗: 2回 /* * ============================================ * 🎯 エンジニアの皆さん、お疲れ様です! * ============================================ * * まぁ真っ先にソース見るよな〜さすがやわ😏 * * 予想通りの行動パターン: * 1. ページ見る * 2. 「面白そう」 * 3. F12をポチッ * 4. このコメント発見 ← 今ココ * * きっと君なら: * - setInterval()で自動化考えてるでしょ? * - while文で無限ループ組みたいでしょ? * - 「これ、API化できそう」とか思ってるでしょ? * * でもさ、その技術力で詳細プロンプト書いた方が * 圧倒的に早いんじゃない?😄 * * ============================================ */ // 簡易プロンプトの結果パターン const simpleResults = [ { emoji: '😅', text: '文字数オーバー...', success: false }, { emoji: '😅', text: 'URLが含まれてない...', success: false }, { emoji: '😅', text: '堅すぎる文体...', success: false }, { emoji: '😅', text: 'ハッシュタグなし...', success: false }, { emoji: '😅', text: '当たり障りのない文章...', success: false }, { emoji: '😅', text: '投稿して大丈夫?な内容...', success: false }, { emoji: '😅', text: '結局書き直しが必要...', success: false }, { emoji: '😅', text: 'なんか違う...', success: false }, { emoji: '✅', text: '使える!(でも微調整必要)', success: true }, { emoji: '✅', text: 'まあまあ良い感じ!', success: true } ]; // 煽り文句パターン(10回毎に表示) const gachaMessages = { 10: "🔥 まだまだいけるぜ!次は当たるかも!", 20: "💪 その調子!あと少しで大当たり!", 30: "⚡ 諦めるな!次こそは神引き!", 40: "🌟 ここからが本番だ!運気上昇中!", 50: "🎯 半世紀到達!もう後には引けない!", 60: "🚀 60回突破!これぞガチャ道!", 70: "💎 レジェンド級の粘り!次で確変!", 80: "👑 80回の壁を越えた!君はガチャ王だ!", 90: "🌈 90回...もはや伝説の領域!", 100: "🎊 100回達成!君こそ真のガチャマスター!", 200: "🎪 200回...正気か?でも尊敬する!", 300: "🤖 300回...そろそろスクリプト組んだ?", 400: "⚙ 400回...setInterval使ってるでしょ?", 500: "🔧 500回...while(true)やめなさい", 600: "💻 600回...もうconsoleで見てるでしょ?", 700: "👨‍💻 700回...君、エンジニアだね?", 800: "🐛 800回...これもうバグレポートだよ", 900: "⭐ 900回...GitHubにissue立てそう", 1000: "🎭 1000回...もし手作業やったら狂気の沙汰よ?暇やんw" }; // 詳細プロンプトの結果パターン const detailedResults = [ { emoji: '✅', text: 'そのままコピペ可能!', success: true }, { emoji: '✅', text: '完璧な280文字以内!', success: true }, { emoji: '✅', text: '魅力的なキャッチフレーズ付き!', success: true }, { emoji: '✅', text: '適切なハッシュタグ自動生成!', success: true }, { emoji: '✅', text: '記事内容を的確に要約!', success: true }, { emoji: '✅', text: 'エンゲージメント促進要素あり!', success: true }, { emoji: '✅', text: '今日もちゃんとした投稿文!', success: true }, { emoji: '✅', text: '狙った通りのアプローチ!', success: true }, { emoji: '😅', text: '少し誇張表現が...(軽微)', success: false }, { emoji: '😅', text: '微調整が必要かも', success: false } ]; let simpleCount = 0; let detailedCount = 0; let simpleSuccess = 0; let detailedSuccess = 0; function spinSimple() { const display = document.getElementById('simple-display'); const stats = document.getElementById('simple-stats'); // スピン効果 display.classList.add('spinning'); display.textContent = '🎲'; setTimeout(() => { display.classList.remove('spinning'); simpleCount++; // 10の倍数の時は煽り文句を表示 if (simpleCount % 10 === 0 && gachaMessages[simpleCount]) { const message = gachaMessages[simpleCount]; display.innerHTML = ` ${message} `; stats.textContent = `${simpleCount}回目の挑戦!まだまだ諦めるな!`; // 特別メッセージの時はconsoleにも出力 if (simpleCount >= 300) { const consoleMessages = { 300: "console.log('🤖 スクリプト組んでる?😏');", 400: "console.log('⚙ setInterval(() => { spinSimple(); }, 100); ←これでしょ?😂');", 500: "console.log('🔧 while(true) { spinSimple(); } やめなさい😅');", 600: "console.log('💻 もうconsoleで見てるでしょ?👀');", 700: "console.log('👨‍💻 君、絶対エンジニアだね!');", 800: "console.log('🐛 これもうバグレポートレベル!');", 900: "console.log('⭐ GitHubにissue立てる気でしょ?');", 1000: "console.log('\\n' + '╔══════════════════════════════════════════════╗\\n' + '║ 🎉 1000回達成記念 🎉 ║\\n' + '║ ║\\n' + '║ ⠀⠀⠀⠀⢀⣴⠟⠛⠛⠻⣦⡀⠀⠀⠀⠀⠀ ║\\n' + '║ ⠀⠀⠀⣰⠟⠁⠀⠀⠀⠀⠈⠻⣆⠀⠀⠀⠀ ║\\n' + '║ ⠀⠀⢠⡟⠀⠀⠀⠀⠀⠀⠀⠀⢻⡄⠀⠀⠀ ║\\n' + '║ ⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠀⠀⠀ ║\\n' + '║ ⠀⠀⢿⡀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡿⠀⠀⠀ ║\\n' + '║ ⠀⠀⠀⠙⢦⣀⠀⠀⠀⠀⣀⡴⠋⠀⠀⠀⠀ ║\\n' + '║ ║\\n' + '║ 🏆 君こそ真のガチャマスター! 🏆 ║\\n' + '║ ║\\n' + '║ 🤔 でもさ...もし手作業やったら狂気よ? ║\\n' + '║ 😏 そのコードレビュー力で詳細プロンプト ║\\n' + '║ 書いた方が早いけどね〜 ║\\n' + '║ ║\\n' + '║ エンジニア大好きw ║\\n' + '╚══════════════════════════════════════════════╝\\n');" }; if (consoleMessages[simpleCount]) { eval(consoleMessages[simpleCount]); } } } else { const result = simpleResults[Math.floor(Math.random() * simpleResults.length)]; display.textContent = result.emoji; stats.textContent = result.text; if (result.success) simpleSuccess++; } updateStats('simple', simpleCount, simpleSuccess); }, 1000); } function spinDetailed() { const display = document.getElementById('detailed-display'); const stats = document.getElementById('detailed-stats'); // スピン効果 display.classList.add('spinning'); display.textContent = '🎯'; setTimeout(() => { display.classList.remove('spinning'); const result = detailedResults[Math.floor(Math.random() * detailedResults.length)]; display.textContent = result.emoji; stats.textContent = result.text; detailedCount++; if (result.success) detailedSuccess++; updateStats('detailed', detailedCount, detailedSuccess); }, 1000); } function updateStats(type, count, success) { const rate = count > 0 ? Math.round((success / count) * 100) : 0; const statsElement = document.getElementById(type + '-stats'); if (count > 0) { statsElement.innerHTML = `${statsElement.textContent} 試行回数: ${count}回 | 成功率: ${rate}% (${success}/${count}) `; } } Claudeの特性を理解した 詳細プロンプトを組んだとしても、1割程度は事実に基づかない投稿文が生成されるというのは盲点でした。特にプロンプトで評価フェーズを挟んでいても、評価フェーズを飛ばしてしまう場合があります。 また、指示の出し方によって、生成物がこんなにも変わってしまうというのは面白いですね。概念としては理解していたんですが、今まで「ギャル風に」とか「夜の蝶とおじさんの会話」みたいなふざけたプロンプトしか打ってこなかったので、これは面白い発見でした。 詳細プロンプトを作るだけで、今までXの投稿にかけていた時間が大幅に短縮されました。単純にプロンプトを使って生成するだけでも、従来あたり5分から10分程度かかっていたのですが、実働としては本当に1分で完了するものとなっています。 あとはこれがシステム化できれば、人間は生成物を確認するだけで良くなりそうです。この辺は作り込みが必要だなと思っています。まぁ僕はエンジニアなのでそういうシステムみたいなところは本職なところがあるんですけどね。 「Claudeは万能じゃない」ことがわかった 指示の出し方で全然違う結果になる 少し手間をかけるだけで劇的に改善 Claudeは「賢いけど指示待ち」な感じ 思わぬ落とし穴:トークン数の問題 実際に直面した問題点についてお話ししたいと思います。 Claudeの制限に引っかかる 最近では月曜日に1週間分の投稿をまとめて生成するのですが、6記事程度作成したらClaudeの制限に引っかかりました。 ClaudeのProプラン で使っているのですが、使用回数を使いつぶしてしまったようです。原因として考えられたのは、詳細プロンプトによって膨大な処理が行われているのではないかという点です。こちらを検証してみました。 犯人は詳細プロンプト?調査してみた こちらの処理は、APIで叩いているわけではないので、正式なトークン数がわかるわけではありません。なので、チャットで入力したトークン数、出力したトークン数の合計値を出してシミュレートしました。詳細プロンプトと簡易プロンプトでそれほど差がないことがわかりました。そして1番トークン数を消費していたのはweb_fetchで取得したHTMLでした。 web_fetchで取得したコンテンツの内訳 取得データ全体 : 約18,000-20,000トークン 実際の記事内容 : 約12,000-14,000トークン HTMLタグ・構造データ : 約4,000-6,000トークン プロンプト別の合計トークン数 簡易プロンプト : 約18,000~26,000トークン(平均:22,000トークン) 詳細プロンプト : 約23,000-29,000トークン(平均26,000トークン) 意外な事実が発覚 詳細プロンプトと簡易プロンプトにそこまで大差があるわけではないという事実が発覚しました。プロンプトの差としては5,000トークン程度でそんなに重いものではないという結論になりました。というか、HTML重すぎじゃん! 大きな差があるわけではない プロンプトの差:約5,000トークン程度 というかHTML重すぎじゃん! 本当の犯人はHTMLデータだった はい、本当の犯人はHTMLだったわけなんですけども、まぁこれって当然のことなんですよね。web_fetchで取得しているということは記事全体を取得しているわけであって、ヘッダーやフッター、メインのコンテンツに関係ないサブメニューなども全て取得しているわけです。それは余計な情報もついてきて重くなりますよね。 なので、記事の本文自体は大体半分程度に圧縮できると考えています。で、ここから面白いところなんですけど、 実は僕、以前にも遭遇しているんですよね。過去のシステムをGASを使ってリファクタリングしてプロトタイプを作ってやってみているんですけども、その時にも同様の問題に当たって、その時はHTMLから本文を抜き出すという処理をスクリプト化して実行していました。 なので、もしこのままプロンプトを使うというよりかは、本文だけを抜き出す工夫が必要になると考えています。システム化にあたっては、HTMLの処理部分は機械的にシステム化し、本来の生成と検証部分に注力してもらう方が効果的であると考えています。というかそうしないとトークン数がバカでかいシステムができてしまうのでヤバいですよね。 使えるプロンプトの作り方 私が学んだ基本ポイント はい、ここでは私が学んだ基本のプロンプトの作り方についてお話ししていきます。基本的なところとしては、実行計画はAIからの出力を安定化させるために重要な内容になっています。簡易プロンプトではAI自身に考えさせる余地が残っているため、自由な発想で自由な判断を行っていますが、実行計画として行動指針を立ててあげることによってAIの思考を誘導することができます。また、例示を載せることによって、そこを起点に考えてもらえるので、ワンショットラーニングやフューショットラーニングなども効果的です。 「何文字以内で」は絶対に入れる 「~な感じで」と雰囲気を伝える 「ハッシュタグも付けて」と具体的に指示 ダメな例も一緒に教える 詳細プロンプトで使った8つのテクニック 段階的実行 :複雑な処理をステップ分割 制約優先 :「〜してはいけない」を先に明確化 テンプレート化 :使い回せる構造を作る 検証組み込み :生成→チェック→修正の流れ 複数パターン :用途別に数種類用意 具体的例示 :曖昧な指示は避ける 自動実行 :条件を満たしたら勝手に動く 構造化出力 :毎回同じフォーマットで結果表示 プロンプト改善のコツ プロンプトの改善のコツとしては、定期的にプロンプトを修正しようとする意識を持つことです。今回付録で載せているプロンプトも実はもう5回ほどアップデートを重ねて調整しています。定期的に自分の求めているものと異なる場合、遠慮なくプロンプトを修正していくべきだと思います。 この点は、システムプロンプトをバージョン管理しておくことも1つの手だと思っています。はい、なので、GitやClaude Code、Gemini CLIでそういった環境を作るのもアリではないでしょうか。 まとめ 実験で証明された「プロンプト設計」の威力 今回の検証を通して、同じClaudeでも「どう頼むか」で全然違う結果になることが実証できました。簡易プロンプトでは8割の投稿文に人間の手直しが必要だったのに対し、詳細プロンプトでは8割がそのままコピペで使えるレベルまで改善しました。 特に印象的だったのは、詳細プロンプトで生成される投稿文の 安定性 ですね。「今日は当たり、昨日はハズレ」というガチャ要素がほぼなくなって、毎回安心して使える品質になったのは大きな収穫でした。 「AI丸投げ」から「AI協働」への転換 最初は「URLを渡したら勝手に完璧な投稿文を作ってくれる」なんて夢を見ていましたが、現実はそう甘くありませんでした。でも、これって逆に言えば 人間の工夫次第でAIのパフォーマンスを劇的に向上させられる ということでもあります。 AIを「万能の魔法」として期待するのではなく、「すごく優秀だけど指示待ちな部下」として適切にマネジメントすることで、期待以上の成果を得られることがわかりました。 見えてきた課題と現実的な解決策 トークン数の問題は正直、盲点でした。HTMLの重さが予想以上で、詳細プロンプトよりもweb_fetchで取得するデータの方がよっぽど「重い」という事実は面白い発見でしたね。 この問題に対しては、過去に GASでHTMLパースしてた経験 が活かせそうです。本文抽出を前処理で自動化すれば、トークン数を半分以下に圧縮できる見込みがあります。まさに「過去の自分、ナイス」って感じです。 2週間運用してみた正直な感想 実際に運用してみて、X投稿にかける時間が 5-10分から1分程度 まで短縮されたのは素直に嬉しいです。「投稿文を考えるのが面倒」「何を書けばいいかわからない」という悩みがほぼ解決されました。 ただし、1割程度は事実に基づかない投稿文が生成されることもあるので、「完全自動化」ではなく「半自動化」として運用するのが現実的だと感じています。人間の最終チェックは必須ですね。 これからプロンプトエンジニアリングを始める人へ もし皆さんも「Claudeに○○を任せたけど、思ったような結果が得られなかった」という経験があるなら、ぜひプロンプト設計を見直してみてください。今回の実験で分かったように、少しの工夫で劇的に改善することがあります。 特に重要なのは: 具体的な制約条件 を明記する(文字数、文体、必須要素など) 段階的な実行計画 をプロンプトに組み込む 複数パターン の出力で選択肢を増やす 定期的なプロンプト見直し で継続改善する 次のステップ:完全自動化への道 今後は、HTMLパースの自動化、プロンプトのバージョン管理、そして最終的にはシステム化まで進めていきたいと思っています。Claude Codeとかも使ってみたいですしね。 でも一番大切なのは、「AIと人間が協力して、お互いの得意分野を活かす」という視点だと思います。完全自動化が目標ではなく、 人間がより創造的な部分に集中できる環境 を作ることが本当のゴールなのかもしれません。 皆さんも、ぜひ自分なりのプロンプトエンジニアリングにチャレンジしてみてください。きっと「これは便利だ!」と思える瞬間があるはずです。 そして、良いプロンプトができたら、ぜひ社内やコミュニティでシェアして、みんなで効率化の輪を広げていきましょう! 付録:詳細プロンプト全文 ```markdown # エンジニア向けX投稿自動生成システム ## 🎯 概要 URLのみの入力で、記事品質評価から3パターンの投稿文まで自動生成する包括的システム。**リンクカード確実表示**を最優先に設計し、**記事内容の正確性検証**により誇張表現を排除。 ----- ## 🔄 自動実行プロセス ### **Phase 1: 記事分析・品質評価** #### **Step 1-1: 記事内容完全取得** ``` 実行内容: - web_fetchでブログ記事の完全コンテンツ取得 - 記事構造の解析(見出し、コードブロック、図表等) - 技術的内容の抽出と整理 - 具体的な数値・効果・事例の正確な記録 ``` #### **Step 1-2: 技術的正確性評価** ``` 評価項目: 【技術スタック正確性】 - 使用技術の最新性(2024-2025年基準) - 技術的実装の適切性 - セキュリティ・ベストプラクティスの考慮 【実装レベル判定】 - プロトタイプ / 基本実装 / 本格実装 / 企業レベル - コードの完成度・品質 - エラーハンドリング・例外処理の有無 【対象読者レベル】 - 初級者(学習中・1-2年目) - 中級者(実務経験3-5年) - 上級者(リーダー・アーキテクト級) - 専門家(特定分野のエキスパート) 総合評価:A(高品質)/ B(標準)/ C(要注意) ``` #### **Step 1-3: 記事価値・差別化ポイント抽出** ``` 分析内容: - 解決する具体的課題 - 既存解決策との違い・優位性 - 実用性・再現性の程度 - 学習コスト・導入難易度 - ビジネス価値・ROI - 【重要】記事内で言及されている具体的な効果・数値の正確な記録 ``` ### **Phase 2: ハッシュタグ効果分析** #### **Step 2-1: 技術分野別ハッシュタグ調査** ``` 検索戦略(2-4回のweb_search実行): 検索1: "[主要技術名] ハッシュタグ 効果的 X Twitter 2025" 検索2: "[技術領域] エンジニア ハッシュタグ トレンド 2025" 検索3: "[解決課題] 自動化 効率化 ハッシュタグ X" 検索4: "[関連ツール] [技術スタック] ハッシュタグ 人気" ``` #### **Step 2-2: ハッシュタグ効果性評価・選定** ``` 評価基準: - トレンド性(2024-2025年の話題性) - 技術特化度(エンジニアの検索意図との一致) - 関連性(記事内容との直接的関連度) - バイト効率(半角英語活用による節約効果) 選定ルール: - 6個以内 - 合計30-40バイト以内 - 半角英語優先(#Azure, #API, #OSS等) ``` ### **Phase 3: 投稿文作成(3パターン)** #### **🚨 リンクカード表示最優先ルール** ``` 【必須適用事項】 1. URLを投稿文の冒頭に単独行で配置 2. URL前後にスペース・改行で明確分離 3. 240文字以内でもURL優先配置 4. 絵文字・ハッシュタグはURL後に配置 5. 他のテキストや絵文字と混在させない 【配置テンプレート】 https://example.com [投稿文本文] #ハッシュタグ ``` #### **Aパターン: 効果重視・数値訴求型** ``` 【特徴】 - 記事内で言及されている具体的な数値効果のみを使用 - Before/After型の改善アピール(記事内の事実に基づく) - 実用性・効率化を強調(記事内容の範囲内で) 【構成テンプレート】 https://example.com 🚀 [記事内の具体的数値効果]で[技術課題]を解決! 📝 [記事内の具体的解決手法]で[記事内の効果]を実現 ⚡ 技術スタック: [半角英語技術名] 🔧 [記事内の実装詳細・注意点] #ハッシュタグ 【使用可能な表現例】 - 記事内で「30分→3分に短縮」と記載されている場合のみ使用 - 記事内で「90%削減」と記載されている場合のみ使用 - 記事内で「月40時間節約」と記載されている場合のみ使用 ``` #### **Bパターン: 課題共感・解決提案型** ``` 【特徴】 - 記事内で言及されている課題のみを使用 - 記事内で提案されている解決策のみを紹介 - 誇張のない共感を呼ぶ問題提起 【構成テンプレート】 https://example.com 😰 [記事内で言及される課題]で困ってませんか? 💡 [記事内の技術手法]を使えば[記事内の解決方法]で楽になります ⚡ [記事内の具体的技術スタック]で実装 🎯 [記事内の期待効果・メリット] #ハッシュタグ 【使用可能な表現例】 - 記事内で「手動でSlack通知作成の課題」が言及されている場合のみ - 記事内で「Google Docsの更新確認の問題」が言及されている場合のみ - 記事内で「レポート作成の時間コスト」が言及されている場合のみ ``` #### **Cパターン: 技術トレンド・学習促進型** ``` 【特徴】 - 記事内で言及されている技術動向のみを使用 - 記事内で説明されているスキルアップ内容のみを紹介 - 記事内で言及されている将来性・価値のみをアピール 【構成テンプレート】 https://example.com 🔥 [記事内で言及される技術分野]の実装方法 📚 [記事内の技術手法・ツール]の使用方法を解説 ⭐ [記事内の学習メリット・価値] 🚀 [記事内の実際の活用場面・価値] #ハッシュタグ 【使用可能な表現例】 - 記事内で「2025年のAI活用」が言及されている場合のみ - 記事内で「NotebookLMとSlack連携」が説明されている場合のみ - 記事内で「技術の評価への影響」が言及されている場合のみ ``` ### **Phase 4: 📋 記事内容検証・誇張表現排除** #### **Step 4-1: 投稿文と記事内容の照合** ``` 【検証項目】 1. 数値効果の正確性 - 投稿文の数値が記事内に明記されているか - 推測・推定ではなく実証された数値か - 文脈が正しく反映されているか 2. 技術的主張の正確性 - 投稿文の技術的効果が記事内で実証されているか - 理論的可能性ではなく実装済みの内容か - 制限事項・注意点が適切に反映されているか 3. 解決課題の正確性 - 投稿文の課題が記事内で具体的に言及されているか - 汎用的な「あるある」ではなく記事固有の課題か - 解決方法が記事内で実際に提示されているか 4. 将来性・価値の正確性 - 投稿文の価値主張が記事内で根拠付けられているか - 著者の推測ではなく客観的事実に基づいているか - 業界動向との整合性が取れているか ``` #### **Step 4-2: 誇張表現の特定と修正** ``` 【誇張表現の判定基準】 ⚠ 以下は誇張表現として判定・修正対象: 1. 記事内に記載のない具体的数値 - 「90%削減」「10倍高速化」等(記事内に根拠なし) - 「月40時間節約」等(記事内に計算根拠なし) - 「30分→3分短縮」等(記事内に実測データなし) 2. 記事内に記載のない効果・価値 - 「劇的改善」「ゲームチェンジャー」等(記事内に根拠なし) - 「チーム全体で効率化」等(記事内に事例なし) - 「来年の評価が変わる」等(記事内に根拠なし) 3. 記事内に記載のない課題・問題 - 「まだ手動で〜してるの?」等(記事内に言及なし) - 「毎週2時間かけてませんか?」等(記事内に統計なし) - 汎用的な「あるある」課題(記事固有ではない) 4. 記事内に記載のない技術トレンド - 「2025年注目の〜」等(記事内に根拠なし) - 「業界標準になる」等(記事内に予測なし) - 「この技術を知ってるかで〜」等(記事内に根拠なし) ``` #### **Step 4-3: 投稿文の再生成** ``` 【再生成トリガー】 - 3パターンのうち1つでも誇張表現が検出された場合 - 記事内容と投稿文の整合性が取れない場合 - 数値・効果の根拠が記事内で確認できない場合 【再生成ルール】 1. 記事内で明記されている内容のみを使用 2. 推測・推定・一般論は使用しない 3. 具体的数値は記事内の実証データのみ使用 4. 効果・価値は記事内の根拠付きのもののみ使用 5. 課題は記事内で具体的に言及されているもののみ使用 【再生成プロセス】 1. 誇張表現を特定 2. 記事内の対応する正確な情報を確認 3. 正確な情報に基づいて投稿文を再構築 4. 再度検証を実施 5. 問題がなければ最終版として採用 ``` ### **Phase 5: 投稿時間最適化** #### **エンジニア向け最適投稿時間** ``` 【平日優先時間帯】 - 朝の通勤時間:8:00-9:00 - 昼休み:12:00-13:00 - 夜の学習時間:21:00-22:00 【技術記事特化時間】 - 火曜日〜木曜日の21:00-22:00(最優先) - 月曜日 12:00-13:00(週始めキャッチアップ) - 金曜日 18:00-19:00(週末学習準備) 【記事品質連動調整】 - A評価:火曜21:00(エンジニア活動ピーク) - B評価:水曜12:00(昼休み気軽チェック) - C評価:金曜18:00(週末実験タイム) ``` ----- ## 📊 最終出力フォーマット ### **自動実行トリガー** ``` 以下の場合に自動的にこのプロセスを実行: 1. URLのみが入力された場合 2. "投稿パターン作成"等の指示がある場合 3. "A/Bテスト版"等の指示がある場合 4. "完全版"等の包括的分析要求がある場合 ``` ### **出力テンプレート** ```markdown ## ブログの評価 【総合評価】A / B / C 【技術的正確性】⭐⭐⭐⭐⭐ (5点満点) 【実装レベル】プロトタイプ / 基本実装 / 本格実装 / 企業レベル 【対象読者】初級者 / 中級者 / 上級者 / 専門家 【実用性】⭐⭐⭐⭐⭐ (5点満点) 【主要な技術要素】 - [抽出された技術スタック] - [解決する課題] - [提供価値・効果] 【記事内の具体的な数値・効果】 - [記事内で明記されている具体的数値] - [記事内で実証されている効果] - [記事内で言及されている改善点] 【注意事項・制限】 - [記事内で言及されている制限事項] - [実装時の注意点] 【誇張表現検証結果】 - [初回生成で検出された誇張表現] - [修正内容] - [最終版での整合性確認結果] ## ブログの投稿パターン ### Aパターン - 投稿文: [280バイト以内の効果重視型投稿文] - 記事内容との整合性: ✅ 確認済み - 投稿推奨時間: [具体的日時と根拠] ### Bパターン - 投稿文: [280バイト以内の課題共感型投稿文] - 記事内容との整合性: ✅ 確認済み - 投稿推奨時間: [具体的日時と根拠] ### Cパターン - 投稿文: [280バイト以内の学習促進型投稿文] - 記事内容との整合性: ✅ 確認済み - 投稿推奨時間: [具体的日時と根拠] ``` ----- ## 🔧 品質保証チェックリスト ### **記事品質評価の正確性** - [ ] 技術的事実の正確性検証 - [ ] 実装レベルの適切な判定 - [ ] 対象読者レベルの妥当性確認 - [ ] 制限事項・注意点の適切な反映 ### **誇張表現検証の徹底** - [ ] 投稿文の全数値が記事内で確認済み - [ ] 効果・価値の主張が記事内で根拠付け済み - [ ] 課題の指摘が記事内で具体的に言及済み - [ ] 技術トレンドの主張が記事内で根拠付け済み ### **リンクカード表示保証** - [ ] URLが投稿文の冒頭に単独行で配置 - [ ] URL前後のスペース・改行による明確分離 - [ ] 240文字超投稿でも冒頭URL配置 - [ ] 絵文字・ハッシュタグがURL後に配置 ### **3パターンの差別化** - [ ] 3つのアプローチが明確に異なる - [ ] 各パターンのターゲット読者が明確 - [ ] バイト数最適化が各版で実現 - [ ] 品質評価結果が適切に反映 - [ ] 全パターンで記事内容との整合性確認済み ### **ハッシュタグ効果分析** - [ ] 2-4回のweb_search実行 - [ ] 技術分野別の効果的ハッシュタグ調査 - [ ] トレンド性・関連性の評価 - [ ] バイト効率を考慮した選定 ----- ## 使用方法 URLを入力するだけで自動実行されます。出力はArtifact形式で生成され、チャットでの説明は最小限に抑制されます。 ``` 例:https://tech-blog.example.com/article-url ``` この改良版により、**記事内容の正確性を保証**し、**誇張表現を完全に排除**しながら、**リンクカード確実表示**と**シンプルな出力フォーマット**を両立したシステムが完成しました。 ``` ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 1人がこの投稿は役に立ったと言っています。 The post 【2025年版】Claudeプロンプト設計術|X投稿文の品質を劇的改善 first appeared on SIOS Tech. Lab .
こんにちは織田です 今回はRAG-STDの開発中にPyJWTを用いてトークン検証を実施しましたので、そこで得た知見についてまとめようと思います。 JWTおよびPyJWTとは? JWT (JSON Web Token) とは、インターネット上で情報を安全にやり取りするための規格の一つで、特に認証や認可の分野で利用されています。 ヘッダー、ペイロード、署名(シグネチャ)の3つの要素からなり、それぞれが”.”で区切られています。 そして、PyJWTとはJWT トークンをエンコード、デコード、検証してくれる Python ライブラリです。 JWTの各要素の詳細は以下の通りです。 ヘッダー (Header) JWTのタイプ(通常は”JWT”)と、署名の生成に使用されるアルゴリズム(例: HMAC SHA256やRSA)がJSON形式で記述されます。これはBase64Urlエンコードされます。 例: {   "alg": "HS256",   "typ": "JWT" } ペイロード (Payload) ここに、JWTに含める情報(クレームと呼びます)が記述されます。クレームには以下の3種類があります。 登録済みクレーム (Registered Claims): JWTの仕様で定義されているクレームで、iss(発行者)、exp(有効期限)、sub(主題)などがあります。これらは任意ですが、利用することで相互運用性が高まります。 公開クレーム (Public Claims): 衝突を避けるためにIANAのJWTクレームレジストリに登録されているか、URIとして定義されているクレームです。 プライベートクレーム (Private Claims): 送信者と受信者の間で合意されたカスタムクレームです。 例: {   "sub": "1234567890",   ""name": "John Doe",   "admin": true } このペイロードもBase64Urlエンコードされます。 署名 (Signature) エンコードされたヘッダー、エンコードされたペイロード、そして秘密鍵を使用して、ヘッダーで指定されたアルゴリズムによって生成されます。この署名があることで、トークンが改ざんされていないこと、および発行者が正しいことを検証できます。 これら3つの要素が「.」で連結されることで、最終的なJWTが構成されます。 例:`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ` JWTを用いたトークン検証のプロセス RAG-STD開発で実施したJWTを用いたトークン検証は、主に以下のステップで構成されます。 クライアントからのJWT受け取り: フロントエンドから、ヘッダー情報を介してJWTを受け取ります。 JWTの解析: 受け取ったJWTをヘッダー、ペイロード、署名の3つの部分に分割します。 署名の検証: 受け取ったJWTのヘッダーとペイロードを再度エンコードします。 サーバーが保持している秘密鍵(または公開鍵)と、エンコードされたヘッダー・ペイロードを用いて、新たな署名を生成します。 生成された署名と、受け取ったJWTに付与されていた署名を比較します。両者が一致すれば、トークンは改ざんされておらず、有効であると判断できます。一致しない場合は、トークンが無効であると判断し、リクエストを拒否します。 ペイロードの検証: 署名の検証に成功したら、ペイロードに含まれるクレームを検証します。 `exp` (有効期限) クレームを確認し、トークンが期限切れでないかを確認します。期限切れであれば、トークンは無効です。 `iss` (発行者) クレームを確認し、信頼できる発行元からのトークンであるかを確認します。 必要に応じて、`sub` (主題) やその他のカスタムクレーム(例: ユーザーID、ロールなど)を検証し、リクエスト元のユーザーが適切な権限を持っているかを確認します。 これらの検証ステップを全てクリアした場合、トークンは有効とみなされ、ユーザーは認証・認可された状態となります。 トークン検証時のポイント 実際のコードでは、フロントエンドから発行されたIDトークンの検証において、以下のような流れで処理を進めました。 公開鍵の一覧を取得: IDトークンを発行した認証プロバイダ(今回はMicrosoft)が公開している公開鍵の一覧(JWKS: JSON Web Key Set)を取得します。これは通常、特定のURLからJSON形式で提供されます。 IDトークンの署名を検証する公開鍵を一覧の中から抽出: 取得した公開鍵の一覧から、検証対象のIDトークンのヘッダーにある`kid` (Key ID) に対応する公開鍵を特定し、抽出します。これにより、正しい公開鍵を使って署名を検証できます。 署名検証ありでデコード: 抽出した公開鍵とIDトークンを用いて、署名の検証を行いながらトークンをデコードします。このステップで、トークンが改ざんされていないか、正しい発行者によって署名されたものかを確認します。PyJWTのようなライブラリを使用すると、この署名検証はデコード処理の一部として自動的に行われます。 ユーザー情報を抽出: 署名検証に成功したら、デコードされたペイロードからユーザ名やグループIDなどの必要なユーザ情報を抽出します。この情報をもとに、アプリケーション内でユーザーの識別や認可を行います。 その他のポイントとしては、以下のものがあげられます。 エラーハンドリング: 署名検証失敗、期限切れ、不正なペイロードなど、あらゆるケースで適切なエラーハンドリングを行うことが重要です。自分が実装しようとした当初はエラーハンドリングを適切に設定できておらず、エラーが発生しても原因が何か分からず悪戦苦闘しました… ライブラリの活用: PyJWTを活用することで、ルーティングごとに検証処理を記述する手間を省き、コードの可読性と保守性を高めました。当初は「検証のコードを自前で用意しなければならないのか…」と不安でしたが、PyJWTを用いることで僅かな行数で実装できました。 `aud` (Audience) クレームの理解: トークンをデコードする際に、`aud` (Audience) クレームの検証に苦労しました。これは、トークンが「誰(どのサービス)のために発行されたものか」を示す、いわば手紙の「宛名」のような概念です。JWTを受け取る側(つまり、検証を行う私たちのアプリケーション)が、そのトークンの`aud`クレームに自身が含まれているかを確認することで、意図しない宛先に送られたトークンの利用を防ぐことができます。 まとめ RAG-STDの開発を通して、JWTの仕組みと検証プロセス、そして実装上の注意点について深く理解することができました。検証用のコードを0から書くのは大変だろうと身構えていたのですが、PyJWTを使うことで、簡単に実装できました。また、コードを段階的に実装していくことで、検証に必要なプロセスについて詳しく理解することができました。この知見が、今後JWTを用いた認証・認可システムを構築される方々の一助となれば幸いです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post PyJWTを用いたアクセストークンの検証をやってみた first appeared on SIOS Tech. Lab .
PSSLの佐々木です。 今回は、Dockerのマルチステージビルドを使ってPythonアプリケーションのサイズを削減する方法を解説します。 JavaやGoのようなコンパイル言語であればビルド時と実行が明確に分かれており、実行時にはバイナリだけあればよいのでマルチステージビルドと相性よく組み合わせて容量を削減できるというのは非常にわかりやすいと思いますが、PythonやRubyのよなインタプリター言語の場合には効果があるのかないのかいまいちピンとこなかったので自身の検証もかねてブログにまとめました。 そもそもマルチステージビルドって何? マルチステージビルドとは、1つのDockerfile内で複数の段階(ステージ)に分けてイメージを構築する手法です。 従来の問題 : Pythonパッケージをビルドするためにgccやg++が必要 しかし、実行時にはビルドツールは不要 でも、従来は最終イメージにビルドツールが残ってしまう マルチステージビルドの解決策 : ビルドステージ :必要なパッケージをコンパイル・ビルド 実行ステージ :ビルド結果だけを持ってきて軽量なイメージを作成 実際のDockerfileで比較してみよう 従来のシングルステージビルド # シングルステージビルド用Dockerfile FROM python:3.13-slim # ビルド時に必要なパッケージをインストール RUN apt-get update && apt-get install -y \\ gcc \\ g++ \\ && rm -rf /var/lib/apt/lists/* # 非rootユーザーを作成(ホームディレクトリも作成) RUN groupadd -r appuser && useradd -r -g appuser -m appuser # 作業ディレクトリを作成 WORKDIR /app # 依存関係ファイルをコピー COPY requirements.txt . # 依存関係をインストール RUN pip install --no-cache-dir --upgrade pip && \\ pip install --no-cache-dir -r requirements.txt # アプリケーションコードをコピー COPY . . # 不要なファイルとディレクトリを削除 RUN find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true && \\ find . -type f -name "*.pyc" -delete && \\ rm -rf venv/ && \\ rm -rf .git/ && \\ rm -rf .pytest_cache/ && \\ rm -rf *.egg-info/ && \\ rm -rf .coverage && \\ rm -rf htmlcov/ # アプリケーションディレクトリの所有者を変更 RUN chown -R appuser:appuser /app # promptflowが使用するディレクトリを作成 RUN mkdir -p /home/appuser/.promptflow && \\ chown -R appuser:appuser /home/appuser/.promptflow # 非rootユーザーに切り替え USER appuser # 環境変数でホームディレクトリを明示的に設定 ENV HOME=/home/appuser # ポートを公開 EXPOSE 5000 # ヘルスチェック HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\ CMD python -c "import urllib.request; urllib.request.urlopen('<http://localhost:5000/api/health>')" || exit 1 # アプリケーションを実行 CMD ["python", "app.py"] マルチステージビルド # マルチステージビルド用Dockerfile # ステージ1: ビルドステージ FROM python:3.13-slim AS builder # ビルド時に必要なパッケージをインストール RUN apt-get update && apt-get install -y \\ gcc \\ g++ \\ && rm -rf /var/lib/apt/lists/* # 作業ディレクトリを作成 WORKDIR /build # 依存関係ファイルをコピー COPY requirements.txt . # 依存関係をインストール(グローバルにインストール) RUN pip install --no-cache-dir --upgrade pip && \\ pip install --no-cache-dir -r requirements.txt # アプリケーションコードをコピー COPY . . # 不要なファイルとディレクトリを削除 RUN find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true && \\ find . -type f -name "*.pyc" -delete && \\ rm -rf venv/ && \\ rm -rf .git/ && \\ rm -rf .pytest_cache/ && \\ rm -rf *.egg-info/ && \\ rm -rf .coverage && \\ rm -rf htmlcov/ # ステージ2: 実行ステージ FROM python:3.13-slim AS runtime # 実行時に必要な最小限のパッケージをインストール RUN apt-get update && apt-get install -y \\ && rm -rf /var/lib/apt/lists/* # 非rootユーザーを作成(ホームディレクトリも作成) RUN groupadd -r appuser && useradd -r -g appuser -m appuser # 作業ディレクトリを作成 WORKDIR /app # ビルドステージからPythonパッケージをコピー COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages COPY --from=builder /usr/local/bin /usr/local/bin # アプリケーションコードをコピー COPY --from=builder /build/app.py . COPY --from=builder /build/connection.yaml . COPY --from=builder /build/flows ./flows/ COPY --from=builder /build/services ./services/ # アプリケーションディレクトリの所有者を変更 RUN chown -R appuser:appuser /app # promptflowが使用するディレクトリを作成 RUN mkdir -p /home/appuser/.promptflow && \\ chown -R appuser:appuser /home/appuser/.promptflow # 非rootユーザーに切り替え USER appuser # 環境変数でホームディレクトリを明示的に設定 ENV HOME=/home/appuser # ポートを公開 EXPOSE 5000 # ヘルスチェック HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \\ CMD python -c "import urllib.request; urllib.request.urlopen('<http://localhost:5000/api/health>')" || exit 1 # アプリケーションを実行 CMD ["python", "app.py"] マルチステージビルドの3つのメリット 1. サイズ削減 不要なビルドツールや中間ファイルが除去されるため、イメージサイズが大幅に小さくなります。 2. セキュリティ向上 実行時に不要なツール(gcc、コンパイラなど)が含まれないため、攻撃面が減少します。 3. デプロイ高速化 イメージが軽量になることで、レジストリへのpush/pullが高速化されます。 実装のポイント AS句でステージに名前をつける FROM python:3.13-slim AS builder # ← 「builder」という名前 FROM python:3.13-slim AS runtime # ← 「runtime」という名前 COPY –fromで必要なファイルだけを移動 # builderステージからsite-packagesだけをコピー COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages 不要ファイルの削除 RUN find . -name "__pycache__" -exec rm -rf {} + && \\ find . -name "*.pyc" -delete 開発環境での使い方 実際にマルチステージビルドを開発で使う場合は、Docker Composeと組み合わせるのがおすすめです。 services: app: build: . # マルチステージDockerfileを使用 ports: - "5000:5000" volumes: - .:/app # 開発時はコードをマウント environment: - FLASK_DEBUG=1 実際にどれくらい効果があるの? 筆者が実際にPythonアプリケーション(promptflowやFlaskを使用)で試した結果がこちらです: REPOSITORY TAG IMAGE ID CREATED SIZE my-app-single latest 99aa62f1267a 1 minute ago 1.45GB my-app-multi latest 27b56911a385 6 minutes ago 636MB 約810MB(56%)の削減に成功しています。 この差は特に以下のような場面で威力を発揮します: CI/CDパイプライン :デプロイ時間の短縮 本番環境 :ストレージコストの削減 開発チーム :イメージ共有の高速化 なぜPythonでもマルチステージビルドの効果があるのか 記事の頭でも気になっていたインタプリター言語でもなぜマルチステージビルドで容量を削減できるのか少し調べてみました。 Pythonのエコシステムでは結構コンパイル処理が発生しているようで、C / C++で書かれている部分を含んでいることが要因の一つでした。 具体的には以下のようなライブラリはC拡張コンパイルをしているようです。 numpy # 線形代数計算(BLAS/LAPACK) pandas # データ処理の高速化 pillow # 画像処理 lxml # XML/HTMLパーサー psycopg2 # PostgreSQLドライバー cryptography # 暗号化ライブラリ uwsgi # WSGIサーバー それによって作成される以下のような不要なファイルやコンパイルツールを含めずにイメージを作成することができるためマルチステービルドの恩恵をしっかりと受けられているのだと思います。 gcc/g++とその依存関係 apt-getのキャッシュ : ビルド時の一時ファイル その他のビルドツール まとめ マルチステージビルドは、少しの工夫で大きな効果を得られる優秀な技術です。コンパイル言語ほどではないですが、Pythonのようなインタプリター言語でもマルチステージビルドの効果を確認することができました。 またアプリケーション実行に最小の要素が何なのかわからないと構築が難しいですが、明らかに必要のないものを含めない状態でイメージを作成するだけでも効果があるので積極的に取り入れて段階的に最適化させていくのが良いかなと思いました。 ではまた ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post マルチステージビルドでPythonアプリケーションを軽量化 first appeared on SIOS Tech. Lab .
概要 こんにちは、サイオステクノロジーの安藤 浩です。 Bicep で Azure Container Apps と Jobs を構築したので、使い分けと実際のBicep でのパラメータとインフラを構築する方法を説明します。 Azure Container Apps とは Azure Container Apps  は、サーバーレスなコンテナプラットフォームで、コンテナー化されたアプリケーションを実行するため、保守が容易でコスト削減できます。 特徴: 主な特徴としては以下になるかと思います。 サーバーレス マイクロサービスに最適 自動スケーリング機能 (HTTP トラフィック、CPU,メモリの負荷状況、ServiceBus キューなど) 用途: 主な用途としては以下が考えられます。 Web アプリケーション/Web API マイクロサービスの実行 リアルタイム処理 Azure Container Apps Jobs とは Azure Container Apps Jobs  は、タスクベースのワークロードに特化したサーバーレスコンテナサービスです。 特徴: 主な特徴としては以下になるかと思います。 サーバーレス スケジュールトリガー、手動トリガー、イベントドリブン トリガー 実行履歴管理:ジョブ実行の状況が把握できる 並列実行のサポート ※Dapr、イングレスとカスタム ドメインや SSL 証明書などの関連機能 はサポートされていません。 用途: 主な用途としては以下が考えられます。 バッチ処理 データ処理パイプライン 定期的なメンテナンスタスク イベントドリブンな処理 Azure Container Apps と Azure Container Apps Jobs の使い分けポイント 以下の観点でAzure Container Apps と Azure Container Apps Jobs を使い分けることがポイントになると思います。 ポイント Azure Container Apps Azure Container Apps Jobs サービス種別 継続的に実行されるサービス タスク、バッチ処理 稼働形態 常時稼働、またはリクエスト/イベントに応じて起動 手動、スケジュール、またはイベントに応じて起動・実行・終了 主なトリガー HTTP、TCP 手動、スケジュール、イベント ユースケース Web API、Web アプリ、マイクロサービス 定期的なバッチ処理、データ処理 簡単に言うと、Azure Container AppsはWebアプリやAPIのように「継続的にリクエストされるWebアプリケーション」向け、Azure Container Apps Jobsはデータ処理やバッチ処理のような「実行して完了するタスク」向けのサービスです。 Bicep によるAzure Container Apps と Azure Container Apps Jobs の構築 ソースコードは以下に配置しています。 https://github.com/Hiroshi-Ando-sti/intro-bicep/tree/main/container-apps 上記のソースコードで利用しているサンプルとなるコンテナ イメージは以下のリポジトリで管理しています。 https://github.com/Hiroshi-Ando-sti/githubpkg-containerregistry 前提条件 以下をインストールします。 ツール Version Visual Studio Code Version: 1.101.2 Visual Studio Code 用の Bicep 拡張機能 0.36.177 最新の Azure CLI ツールまたは最新の Azure PowerShell バージョン 2.74.0 構成要素 以下の構成を構築します。 VNET Application Insights Container Environments Container Apps Container Apps Jobs Bicep コード 1. メインとなるBicep コード (infra/main.bicep) メインとなるコードの主要部分のみを記載します。 Container Apps やJobs を実行するために必要なリソースは VNET やContainer Apps Environment ですので、事前に作成します。 Container Apps と Container Apps Jobs は  containerAppsSettings  と  containerAppJobsSettings  のパラメータを利用して、複数作成できるようにしています。 module vnet './modules/network/vnet.bicep' = { name: 'vnet' params: { name: '${abbrs.networkVirtualNetworks}${environmentName}${suffixResourceName}${uniqueStr}' location: location tags: tags subnetName: 'default' } } var containerAppEnvironmentName = '${abbrs.appManagedEnvironments}${environmentName}${suffixResourceName}${uniqueStr}' module containerAppEnvironment './modules/container/containerAppsEnv.bicep' = { name: 'containerAppEnvironment' params: { vnetName: vnet.outputs.name environmentName: containerAppEnvironmentName location: location tags: tags logAnalyticsWorkspaceName: logAnalyticsWorkspaceName } } module containerAppJobs './modules/container/jobs.bicep' = [for containerAppJobsSetting in containerAppJobsSettings: { name: 'containerAppJobs-${containerAppJobsSetting.name}' params: { containerAppEnvironmentName: containerAppEnvironmentName location: location tags: tags containerAppJobsSetting: containerAppJobsSetting appInsightsConnectionString: appInsights.outputs.connectionString } }] module containerApps './modules/container/containerApps.bicep' = [for containerAppsSetting in containerAppsSettings: { name: 'containerApps-${containerAppsSetting.name}' params: { containerAppsName: '${abbrs.appContainerApps}${environmentName}-${containerAppsSetting.name}${uniqueStr}' location: location tags: tags containerAppEnvironmentName: containerAppEnvironmentName appInsightsConnectionString: appInsights.outputs.connectionString environmentName: environmentName resourceSuffixName: uniqueStrWithoutHyphen containerAppsSetting: containerAppsSetting } }] 2. Container Apps Environment モジュール (infra/modules/container/containerAppsEnv.bicep) VNET が必要であり、Container Apps Environmentの Log出力先としてLog analytics を指定します。 @description('VNet名') param vnetName string param location string = resourceGroup().location param environmentName string param logAnalyticsWorkspaceName string param tags object // Container Apps 環境の VNet を取得する。 resource vnet 'Microsoft.Network/virtualNetworks@2024-05-01' existing = { name: vnetName } resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' existing = { name: logAnalyticsWorkspaceName } // Container Apps Environment //https://learn.microsoft.com/ja-jp/azure/templates/microsoft.app/2025-01-01/managedenvironments?pivots=deployment-language-bicep resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2025-01-01' = { name: environmentName location: location tags: tags properties: { appLogsConfiguration: { destination: 'log-analytics' logAnalyticsConfiguration: { customerId: logAnalyticsWorkspace.properties.customerId sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey } } vnetConfiguration: { infrastructureSubnetId: vnet.properties.subnets[0].id } } } 3. Container Apps モジュール (infra/modules/container/containerApps.bicep) Container AppsのProperties では主に以下のパラメータを指定します。 properties のパラメータ 説明 managedEnvironmentId Container Apps Environment のIDを指定 configuration.ingress Container Apps イングレス の設定 configuration.secrets Container Apps 内のシークレット値 template.containers.image コンテナイメージ template.containers.resources CPU, メモリサイズの指定 template.containers.env コンテナの環境変数 template.scale.cooldownPeriod スケールインするまでの時間(秒)を指定 template.scale.minReplicas リビジョンごとの最小レプリカ数 template.scale.maxReplicas リビジョンあたりのレプリカの最大数 template.scale.rules レプリカを追加または削除するタイミングを決定するためにコンテナー アプリによって使用される条件。 スケールルール のトリガーは3つ指定可能で HTTP, TCP, カスタムメトリックがあります。例:http として、concurrentRequests: 10 とすると同時実行された HTTP 要求数がこの値を超えると、レプリカが 1つ追加されます。(maxReplicasまで) @description('Container Appsの名前') param containerAppsName string @description('すべてのリソースのロケーション') param location string = resourceGroup().location @description('Container Apps 環境の名前') param containerAppEnvironmentName string @description('Container Appsが属する環境名') param environmentName string @description('リソース名のサフィックス') param resourceSuffixName string @description('Container Appsの設定') @secure() param containerAppsSetting object @description('Application Insightsの接続文字列') param appInsightsConnectionString string @description('リソースに付与するタグ') param tags object = {} resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2025-01-01' existing = { name: containerAppEnvironmentName } //https://learn.microsoft.com/en-us/azure/templates/microsoft.app/2025-01-01/containerapps?pivots=deployment-language-bicep //https://learn.microsoft.com/ja-jp/azure/container-apps/scale-app?pivots=azure-cli resource containerApps 'Microsoft.App/containerApps@2025-01-01' = { name: containerAppsName location: location tags: tags properties: { managedEnvironmentId: containerAppEnvironment.id configuration: { ingress: { external: containerAppsSetting.ingress.external targetPort: containerAppsSetting.ingress.targetPort transport: containerAppsSetting.ingress.transport allowInsecure: containerAppsSetting.ingress.allowInsecure traffic: [ { latestRevision: true weight: 100 } ] } secrets: containerAppsSetting.secretVariables } template: { containers: [ { name: 'c-${environmentName}-${containerAppsSetting.name}-${resourceSuffixName}' image: containerAppsSetting.image resources: { cpu: json(containerAppsSetting.cpu) memory: containerAppsSetting.memory } env: union(containerAppsSetting.environmentVariables, [ { name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' value: appInsightsConnectionString } ]) } ] scale: { cooldownPeriod: containerAppsSetting.scale.cooldownPeriod pollingInterval: containerAppsSetting.scale.pollingInterval minReplicas: containerAppsSetting.scale.minReplicas maxReplicas: containerAppsSetting.scale.maxReplicas rules: containerAppsSetting.scale.rules } } } } 4. Container Apps Jobs モジュール (infra/modules/container/jobs.bicep) containerAppJobsSetting をパラメータとして渡して、設定値をContainer Apps jobs の Properties に設定してあげます。 triggerType は  Event ,  Manual ,  Schedule  が選択できます。 triggerType — Event イベントドリブン ジョブをトリガーします。 例:Azure Service Bus、Kafka、RabbitMQ などのキューに新しいメッセージが追加されたときに実行されるジョブ。 ワークフローまたはパイプラインのキューに新しいジョブが格納されたときに実行されるセルフホステッド GitHub Actions ランナー または Azure DevOps エージェント。 Manual 手動ジョブでの実行をします。 例:一度限りの実行。 Schedule スケジュールされたジョブを実行します。cron 式で定義します。 例:  */5 * * * *  : 5分ごとの実行。 propertiesのtemplate以下は基本的にContainer Apps 同様です。 注意: replicaTimeout までに処理が終わらなければ、タイムアウトしてレプリカは停止します。 param location string = resourceGroup().location param containerAppEnvironmentName string param containerAppJobsSetting object param appInsightsConnectionString string param tags object = {} resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2025-01-01' existing = { name: containerAppEnvironmentName } // Container Apps Jobs の作成 //https://learn.microsoft.com/en-us/azure/templates/microsoft.app/2025-01-01/jobs?pivots=deployment-language-bicep resource containerAppJobs 'Microsoft.App/jobs@2025-01-01' = { name: containerAppJobsSetting.name location: location tags: tags properties: { environmentId: containerAppEnvironment.id configuration: { triggerType: containerAppJobsSetting.triggerType replicaRetryLimit: containerAppJobsSetting.replicaRetryLimit replicaTimeout: containerAppJobsSetting.replicaTimeout eventTriggerConfig: containerAppJobsSetting.triggerType == 'Event' ? { parallelism: containerAppJobsSetting.parallelism replicaCompletionCount: containerAppJobsSetting.replicaCompletionCount scale: containerAppJobsSetting.jobScale == null ? null : { maxExecutions: containerAppJobsSetting.jobScale.maxExecutions minExecutions: containerAppJobsSetting.jobScale.minExecutions pollingInterval: containerAppJobsSetting.jobScale.pollingInterval rules: containerAppJobsSetting.jobScale.jobScaleRule == null ? null : [ { auth: containerAppJobsSetting.jobScale.jobScaleRule == null ? null : [ { secretRef: containerAppJobsSetting.jobScale.jobScaleRule.ScaleRuleAuth.secretRef triggerParameter: containerAppJobsSetting.jobScale.jobScaleRule.ScaleRuleAuth.triggerParameter } ] identity: containerAppJobsSetting.jobScale.jobScaleRule.identity metadata: containerAppJobsSetting.jobScale.jobScaleRule.metadata name: containerAppJobsSetting.jobScale.jobScaleRule.name type: containerAppJobsSetting.jobScale.jobScaleRule.type } ] } } : null manualTriggerConfig: containerAppJobsSetting.triggerType == 'Manual' ? { parallelism: containerAppJobsSetting.parallelism replicaCompletionCount: containerAppJobsSetting.replicaCompletionCount } : null scheduleTriggerConfig: containerAppJobsSetting.triggerType == 'Schedule' ? { cronExpression: containerAppJobsSetting.cronExpression parallelism: containerAppJobsSetting.parallelism replicaCompletionCount: containerAppJobsSetting.replicaCompletionCount } : null } template: { containers: [ { name: containerAppJobsSetting.name image: containerAppJobsSetting.image env: union(containerAppJobsSetting.environmentVariables, [ { name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' value: appInsightsConnectionString } ]) resources: { cpu: json(containerAppJobsSetting.cpu) memory: containerAppJobsSetting.memory } } ] } } } 5. パラメータファイル (main.sbx.parameters.json) メインとなるBicep コードの箇所で説明した Container Apps と Container Apps Jobs のパラメータ:  containerAppsSettings  と  containerAppJobsSettings  のみ以下に記載しています。 Jobs のContainer Image はバッチ処理のサンプルとして 開始時にログ出力して、  SLEEP_SECONDS  (秒)待機、終了するバッチです。 Container Apps の Container Image は  GET /  と  GET /test  へリクエストできるサンプルです。 今回はいずれも公開されたImageですが、プライベートの場合も指定可能です。 "containerAppJobsSettings": { "value": [ { "name": "schedule-container-app-jobs", "image": "ghcr.io/hiroshi-ando-sti/githubpkg-containerregistry/container-app-jobs:main", "replicaTimeout": 4000, "replicaRetryLimit": 2, "parallelism": 1, "replicaCompletionCount": 1, "triggerType": "Schedule", "cronExpression": "0 */10 * * *", "cpu": "0.25", "memory": "0.5Gi", "environmentVariables": [ { "name": "RETENTION_DAYS", "value": "30" }, { "name": "SLEEP_SECONDS", "value": "3800" } ], "jobScale": { "maxExecutions": 1, "minExecutions": 1, "pollingInterval": "PT5M", "maxExecutionTime": "PT30M", "jobScaleRule": null } } ] }, "containerAppsSettings": { "value": [ { "name": "webapi", "image": "ghcr.io/hiroshi-ando-sti/githubpkg-containerregistry/webapi:main", "ingress": { "external": true, "targetPort": 8000, "transport": "auto", "allowInsecure": false }, "secretVariables": [ { "name": "app-private-key", "value": "app-private-key-value" } ], "cpu": "0.25", "memory": "0.5Gi", "environmentVariables": [ { "name": "BATCH_SIZE", "value": "100" }, { "name": "LOG_LEVEL", "value": "INFO" }, { "name": "SLEEP_SECONDS", "value": "3800" } ], "scale": { "cooldownPeriod": 65, "pollingInterval": 30, "minReplicas": 0, "maxReplicas": 2, "rules": [ { "name": "http-rule", "http":{ "metadata": { "concurrentRequests": "2" } } } ] } } ] } デプロイ手順 1. リソースグループの作成 az group create --name rg-container-apps --location "Japan East" 2. Bicepコードによるデプロイ cd infra az deployment group create \ --name rg-container-apps-deploy \ --mode Complete \ --resource-group rg-container-apps \ --confirm-with-what-if \ --template-file ./main.bicep \ --parameters ./main.parameters.sbx.json 3. デプロイの確認 デプロイの確認は以下で行います。 # デプロイ状況を確認 az deployment group list --resource-group rg-container-apps --output table # Container Appsの状況を確認 az containerapp list --resource-group rg-container-apps --output table # Container Jobsの状況を確認 az containerapp job list --resource-group rg-container-apps --output table Azure Portal 上の Container Apps Enviroment からも Container Apps が作成されていることが確認できます。 まとめ Azure Container Apps と Jobs は、それぞれ異なる用途に最適化されたサーバーレスコンテナサービスです。 1. Azure Container Apps と Jobs の使い分けのポイント Azure Container Apps は 継続的に実行されるWebアプリケーションやAPI向けのサービス で、Azure Container Apps Jobs は バッチ処理や定期的なタスク向けのサービス です。 2. Container Apps と Jobs 実装時の考慮点 Container Apps と Jobs を実装する際は、以下の点を考慮することが重要です: リソース配分 :CPU とメモリの適切な設定 スケーリング戦略 :トラフィックパターンに応じたスケールルールの設定 セキュリティ :シークレット管理 監視とログ :Log analytics, Application Insights による可観測性の確保 コスト最適化 :最小レプリカ数の調整とスケールイン時間の設定 トリガー :適切なトリガーの選択 Bicep による Infrastructure as Code と組み合わせて、スケーラブルで保守性の高いコンテナベースのアプリケーション基盤を構築してはいかがでしょうか。 参考URL Azure Container Apps の概要 https://learn.microsoft.com/ja-jp/azure/container-apps/overview Azure Container Apps のコンテナー https://learn.microsoft.com/ja-jp/azure/container-apps/containers Azure Container Apps のジョブ https://learn.microsoft.com/ja-jp/azure/container-apps/jobs?tabs=azure-cli   ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post [Azure][Bicep]Azure Container Apps と Jobs の特徴とBicepによる構築 first appeared on SIOS Tech. Lab .
PSSLの佐々木です。 Dockerを使っていると、同じアプリケーションでも様々なイメージタグが用意されています。 node:18 、 node:18-slim 、 node:18-alpine など、どれを選べばいいのか迷ってしまうことはありませんか? 例えばPythonのDocker Imageはversion 3.13.5系だけでもこんなにあります。とほほ。。 この記事では、各Dockerイメージの種類とその特徴、そして「どんな時に使うべきか」を明確にします 主要なDockerイメージの種類 1. 標準イメージ(Latest/バージョン指定) 例: node:18 、 python:3.11 、 nginx:latest 特徴: 最も完全なパッケージセット 開発に必要なツールやライブラリが豊富 サイズは大きめ(通常数百MB〜1GB以上) Debianベースが多い こんな時に選ぶべき: 開発環境やテスト環境 パッケージの互換性問題を避けたい 初心者や学習目的 プロトタイピング段階 2. Slimイメージ 例: node:18-slim 、 python:3.11-slim 特徴: 不要なパッケージを削除した軽量版 削除される例: curl 、 wget 、 git 、 vim 、 nano 、 man ページ、ドキュメント類 残されるもの:パッケージマネージャー( apt )、基本的なシステムライブラリ 標準イメージの約50-70%のサイズ 基本的な開発ツールは含まれている Debianベースだが最小限のパッケージ こんな時に選ぶべき: 本番環境でサイズを抑えたい 標準イメージでは大きすぎるが、Alpineは不安 CI/CDでのビルド時間を短縮したい セキュリティ面でパッケージ数を減らしたい 3. Alpineイメージ 例: node:18-alpine 、 python:3.11-alpine 、 nginx:alpine 特徴: Alpine Linuxベース(軽量ディストリビューション) 極めて小さなサイズ(通常10-100MB) muslcライブラリを使用(glibcではない) パッケージマネージャーは apk 最小限のパッケージ構成 基本的なシェル( ash )のみ bash 、 curl 、 git 、 vim などは含まれない セキュリティを重視した最小構成 こんな時に選ぶべき: 最小限のサイズが必要 マイクロサービス環境 コンテナの起動速度を重視 リソース使用量を最小限に抑えたい 注意点: ネイティブ拡張でコンパイルエラーが発生する可能性 一部のパッケージが利用できない場合がある デバッグツールが限定的 4. Scratchイメージ 例: FROM scratch 特徴: 完全に空のイメージ 極小サイズ(数KB〜数MB) シェルやパッケージマネージャーなし 静的バイナリのみ実行可能 こんな時に選ぶべき: Go言語の静的バイナリ セキュリティを最優先 極限までサイズを削減したい 攻撃面を最小限に抑えたい 実際のタグ選択:Python 3.13.5を例に 実際にDockerイメージを選ぶ際、以下のような大量のタグが存在します: 3.13.5-bookworm, 3.13-bookworm, 3-bookworm, bookworm 3.13.5-slim-bookworm, 3.13-slim-bookworm, 3-slim-bookworm, slim-bookworm, 3.13.5-slim, 3.13-slim, 3-slim, slim 3.13.5-alpine3.22, 3.13-alpine3.22, 3-alpine3.22, alpine3.22, 3.13.5-alpine, 3.13-alpine, 3-alpine, alpine 3.13.5-windowsservercore-ltsc2025, 3.13-windowsservercore-ltsc2025, 3-windowsservercore-ltsc2025, windowsservercore-ltsc2025 推奨される選択方法 1. バージョン指定の基本ルール python:latest や python:3 – 予期しないバージョンアップで動作不良の可能性 python:3.13.5 – 具体的なバージョンを指定 python:3.13 – マイナーバージョンを固定(パッチ更新は自動) 2. 実際の選択例 # 開発環境:最新の安定版を使用 FROM python:3.13.5-bookworm # または単純に FROM python:3.13.5 # 本番環境:Slimで軽量化 FROM python:3.13.5-slim # マイクロサービス:Alpineで最小化 FROM python:3.13.5-alpine 3. Debianコードネーム(bookworm等)は通常省略可能 python:3.13.5-bookworm と python:3.13.5 は同じ 特定のDebianバージョンに依存する場合のみ明示的に指定 4. Alpineのバージョン指定 python:3.13.5-alpine で十分(最新のAlpineを使用) python:3.13.5-alpine3.22 は特定のAlpineバージョンが必要な場合のみ 実用的な選択フローチャート ステップ1: 用途を確認 開発・テスト環境 → 標準イメージ 本番環境 → Slim または Alpine 極限の軽量化が必要 → Scratch ステップ2: 互換性を確認 ネイティブ拡張を使用 → 標準 または Slim Pure JavaScript/Python → Alpine も選択可能 静的バイナリ → Scratch ステップ3: リソース制約を確認 ストレージ容量に制限 → Alpine または Scratch ネットワーク帯域に制限 → Alpine または Scratch 制約なし → 標準 または Slim まとめ 初心者の方は標準イメージから始めて、慣れてきたらSlimやAlpineに移行する のが現実的なアプローチです。 各イメージの選択基準: 標準イメージ : 確実に動かしたい時 Slimイメージ : バランスを重視する時 Alpineイメージ : サイズを最優先する時 Scratchイメージ : 静的バイナリで極限の軽量化が必要な時 プロジェクトの要件と制約を考慮して、最適なイメージを選択しましょう。不明な点があれば、まず標準イメージで動作確認してから、段階的に軽量化を進めることをお勧めします。 またこのほかにもいくつかイメージが存在していますが、基本この4つのどれかを採用しておけばよいと思います ではまた   ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Dockerイメージの選び方ガイド first appeared on SIOS Tech. Lab .
1. ブログ公開前の「これで大丈夫?」を解決したい ども!最近はAIを活用したブログ改善とかいろいろ取り組んでいる龍ちゃんです。 皆さんもブログを書いた後、こんな不安を感じることありませんか? 「この技術情報、本当に正しいかな?」 「データが古くないかな?」 「読者にとって価値があるかな?」 やっぱ不安ですよね。ブログ書いてもこれが本当に正しいのかとか、情報が古くないかなぁとか。検証はもちろんするんですけど、それが正しいのかどうかという不安はなかなか消えてくれません。 従来の解決方法には人的リソースの観点から多くの問題があります。 手動でのファクトチェック → 時間がかかりすぎる 専門家に相談 → コストと時間の問題 自分で調べ直す → 見落としのリスクが高い 簡単なのは自分で調べてポチポチ見ながら「大丈夫かなぁ」と調べ直すことです。ですが、限界あるじゃないですか。 そこで今回提案したいのが、 AIを「第一読者」にする という発想です。他のエンジニアとかに見てもらうのはやったほうがいいんですけど、なかなかできないじゃないですよね~。みんな忙しいですしww そういうところってAIにやってもらえたらいいなぁって思って、ぶち込んでみたらいい感じにできたって流れです。 2. なぜGemini Deep Researchが第一読者に最適なのか 1番の理由は弊社でGoogle Workspaceの契約にGeminiがついてきたので、会社のドメインであれば有料プランを使い倒せるという点です。 実際、Web Searchがついているサービスはほかにもあります。Claude Proと比較したのですが5000KBのPDFを入力として与えたところ、Claudeではスレッドのトークン上限に引っかかりプロンプトを実行することができませんでした。GeminiのDeep Researchでは、動作し15分前後で200件弱のサイトを検索してレポートを作成してくれました。 Deep Researchの特徴 Geminiのディープリサーチは、従来の検索とは全く異なる「エージェント型AI」の仕組みで動作します。 3つのステップで自動調査 プラン作成 :複雑な質問を小さなタスクに分解 情報収集 :数百のウェブサイトを並行して調査 分析・統合 :収集した情報を批判的に評価してレポート作成 技術的な特徴 100万トークンのコンテキスト管理 反復的な検索プロセス 完全非同期処理(PCを閉じても継続) 詳細な技術仕様については、 Google公式のDeep Research解説 をご覧ください。 他社AIツールとの比較 項目 Gemini Deep Research ChatGPT Deep Research Claude Research 料金 Google AI Pro: $20/月・Google AI Ultra: $250/月・無料版: あり ChatGPT Plus: $20/月・ChatGPT Pro: $200/月・無料版: あり Max Plan: $100-400/月・Pro: $20/月(近日対応予定)・無料版: なし 無料版の機能 月数回の利用、Gemini 2.5 Flash使用・スプレッドシート・コードファイル不可 月5回のタスク・軽量版のみ・短めの回答 基本的なチャット機能のみ・研究機能利用不可 処理時間 標準: 5-10分、複雑なクエリ: より長時間 標準: 5-30分・平均: 15-20分 標準: 5-15分・複雑: 最大45分 検索規模 数百のウェブサイトを検索 数百のソース・(例:52ソース) 数百のソース・+ 内部統合 レポート品質 6-12ページ、引用付き、Googleドキュメント出力対応 2,000-5,000語・構造化された形式 包括的レポート・複数引用スタイル対応 ファイルサイズ制限 100MB (標準)、クラウドストレージ: 2GB 512MB (全フォーマット)・画像: 20MB 30MB (全フォーマット)・画像: 30MB 同時アップロード 10ファイル 有料: 80ファイル /3時間・無料: 3ファイル /日 20ファイル /会話 無料版ファイル機能 PDF、DOC、画像のみ、スプレッドシート不可 同じファイルサイズ制限・3ファイル/日 10MBまで・基本分析のみ 対応ファイル形式 PDF、DOC、DOCX、TXT、画像(Pro: CSV、コードファイル追加) TXT、PDF、画像、CSV、XLSX、DOCX PDF、DOCX、CSV、TXT、画像、OCR機能あり 強み Google統合、非同期処理・音声概要生成 高速処理、包括的分析・マルチフォーマット対応 マルチエージェント並列処理・Googleワークスペース統合 弱み ファイル形式制限・処理時間長い 事実の幻覚リスク・計算リソース集約的 地理的制限あり・高コスト 適用場面 ビジネス分析、市場調査・学術研究、コンテンツ作成 学術研究、投資分析・政策研究、競合分析 学術研究、市場分析・法的調査、科学研究 日本語対応 完全対応 :日本で利用可能・音声機能も日本語対応 利用可能 :但し英語版より品質劣る・文化的文脈の制限あり 完全対応 ・日本で利用可能・機能制限なし (7/5時点での最新情報のつもりです…) 参考リンク:公式サイト Google/Gemini : Gemini Deep Research公式 、 Google AI Blog OpenAI : Deep Research FAQ 、 公式発表 Anthropic : Claude Research発表 、 プランページ 第一読者にAIサービスが適している理由 時間効率 :100~200件のサイトを相互参照して確認するのは人間がやると大規模な作業になります。Research系の機能を使うとプロンプトを投げて終了を待つだけ 情報源の明示 :情報元を提示してくれるので、深く知りたい場合は参照元に当たることができる 多角的な検証 :複数の視点から情報を検証 最新情報へのアクセス :リアルタイムの情報、ウェブサイトをリサーチしてあらゆるサイトから情報を参照 3. 実際にやってみよう:PDFから記事の品質をチェックする3ステップ ここからは実際にPDFファイルを使った品質チェックの手順について書いていきます。全体の流れをフロー図で作ってみました。 ステップ1:PDFファイルの準備とアップロード まずですね、ブログを執筆します。ここ大事ですよね。ブログを書くんですよ。 僕はNotion使ってブログを書いてるんですけど、NotionでPDFでエクスポートします。PDFファイルを添付してDeep Researchに投げます。 対象としたファイルとしては1000〜5000KBまでのPDFであれば問題なく動作しました。Google Workspaceと連携したGeminiであればトグルボタンひとつでGoogle Driveとシームレスな連携をすることができます。Goolge Docsなどのドキュメントであればブラウザ内で完結させることができます。 ステップ2:プロンプトの作成 PDFファイルをアップロードしたら、こんな感じでプロンプトを送ります: 添付した資料の内容をもとにファクトチェックして、技術的ブログとして多角的な評価から判断して このプロンプトは簡素なプロンプトにしています。「ファクトチェック」は外部的リソースとの比較をさせる意図があります。「多角的評価」という言葉でGemini側に評価基準を委譲しています。 目的別プロンプト例 : 技術記事の場合 :「技術仕様、API情報、コード例の正確性を重点的に」 差別化チェック :「この他社の記事との差別化ってどういうところにあるんですか?」「この記事の強みってなんですか?」 公式リファレンス準拠 :「公式リファレンスのみを参照してベストプラクティスかどうか調査して」 ステップ3:結果の解釈と活用 5〜30分経過後、詳細なレポート出てきます。詳細なレポートには、引用元が明記された詳細な調査レポートが出力されます。このレポートには、発見された問題点や改善提案が含まれて出力されることもあります。(もちろん全肯定の時もあるよ!) 一度リクエストすれば完了時に通知が飛んでくるので別作業をすることも大きな利点ですね。 その出てきたレポートをもとに「簡潔な結論出して」って言うと、改善すべき点があるかって聞くと、レポートから更に要約してくれます。レポートをもとにPDCAサイクルを回せます。 4. 【実体験】よくある判定ミスと対処法 ここでは実際に会った出力ミスについて紹介していきます。もちろんAIも間違うこともあります。 レポートに対して納得がいかない ハルシネーションってあるじゃないですか。簡単にいうとAIが嘘を吐くってやつなんですが、これはレポートと主張が噛み合わないという結果として現れます。 そういう時はブログに書かれている内容が不足しており、主張がAIにも伝わっていないか考慮不足があると解釈しています。 そういう時はブログ側を加筆することで対応しています。加筆時は再度調べ直して、情報を保管することでAIを納得させることができます。 「リンクがない」と言われた時の対処法 実際、PDF内にリンクを挟んでいたのですが、Geminiではリンクが挿入されていないという判定で公式リファレンスの挿入が推奨されました。 そういう時は1回聞き直すんですよね。「リンクは含まれていると思います。再度確認をしてください」って聞いたら再度PDFファイルを読み込み確認を行なってくれます。 PDFが参照できませんでした これは100%プロンプトが悪いですね。何をして欲しいのかが伝わっていない可能性があります。 こういう時はプロンプトを改修しましょう。またリサーチの計画を確認して意図が伝わっているか確認するのも一つの手になります。 「情報が古い」と指摘された時の確認方法 「情報が古いですね」って言われて、僕の情報が新しくて、Geminiが参照してた情報が古いってこともあったんで、そういう時は自分が参照した情報っていうのを送って「これが最新の情報じゃないですか」っていうの聞くと修正して謝ってくれます。 対処に重要な観点 こちらはAIに追加で知識を与える際に重要な観点になります。 再確認の依頼:「もう一度詳しく確認してください」 情報源の明示:重要なURLやリンクを直接共有 複数回のチェック:一度に全てを判定せず部分的に検証 5. 応用テクニック:さらに効果を高める使い方 プロンプトで効果を高める方法はたくさんあります。ですが、最終的に人間の目を入れることは絶対に忘れないでください。 複数の視点からの品質チェック 今回はファクトチェックと多角的視点という比較的曖昧な表現で調査を依頼しました。観点を限定して依頼することも可能です。 SEO観点から トレンド観点から 公式リファレンスのみを参照して 重要としている観点をに重みを置いて調査することが可能です。観点の追加をすることで、自分が意図した方向性での調査を要求することができます。 競合記事との差別化チェック 技術ブログは差別化が大事だったりします。個人的には勉強したことは全て技術ブログとして出していいじゃないと思っています。 ブログのアウトラインレベルで記事を共有し、すでにネット上に同様の主張をしているドキュメントがないかの調査などに活用をすることができます。 継続的な品質管理 何度もレビューを依頼しても、嫌な顔をしないというのがAIサービスの良い点ですね。Deep Researchで検証をした内容から変更を加えて再度実行することで質が向上していきます。 最大で3度レビューを通せば、人間が見ても投稿としては問題ないレベルに達すると思います。 6. 知っておくべき注意点:Deep Researchの限界と対策 やっぱ注意点はもちろんあるんですよね。 AIによる判断の限界を理解する やはり大事なのはAIも人間もミスをするし嘘を吐くという点ですね。 間違うことがもちろんあるので、最終的にAIが出してきたレポートって人間が見る必要があります。 重要なポイント : ハルシネーションリスク:AIの言ってることを全て100%信じるわけにはいかない 個人ブログも公式リファレンスも同等のソースとして扱う可能性がある 最終確認は人間が行う必要がある 記事は自分で書いておかないといけない やっぱり自分で書いたブログと公式リファレンスを一個ずつ確認するのは途方もない作業です。ですがDeep Researchが出してきたレポートと自分のブログとの比較であれば、手間自体はグッと下がります。 レポートを聞いてレビューを受けたという気持ちで修正をしましょう。そして自分があってる確証があるなら聞きましょう。判断がつかなくても遠慮なくAIに聞くのも一つに手だと思います。 コスト面の考慮 弊社はGoogleのワークスペースで契約してるので使いたい放題できるって言うところがあります。 個人利用の場合は、自分が契約しているAIサービスがSearch系のツールを提供していれば同等の検証を行うことは可能です。ですが、大きな規模のファイルであれば制限がかかる可能性はあります。そこは契約しているツールに合わせて、ファイルサイズを小さくするなどのカスタマイズが必要になります。 情報の鮮度について AIはリアルタイムでウェブ情報を参照しますが、特定の分野(例:最新のバグ情報、直近で非推奨となったAPI、まだ公式ドキュメントに反映されていない極めて新しい技術動向など)では、情報の鮮度に限界がある場合があります。特に重要な情報については、必ず人間が最新の公式情報を確認し、情報の取捨選択を行う必要があります。 一番新鮮な情報はGitHub Issueだと思っています。そこ専用の検索エージェントとか出たら絶対使いますね。Issueでは暫定的な対応法なども取り扱っているので、GitHub Issueに関しては人間の調査が必要ですね。 7. これからの時代のブログ品質管理 従来の品質管理との違い 時間効率 :数時間→5-30分程度での初期レビュー 網羅性 :人間の見落とし→AIの包括的チェック 継続性 :一回限り→定期的な自動チェック 読者にとっての価値 不安なんだったら評価してもらう。AIが改善案を人間が検証・判断のサイクルを回すことで技術ブログの質は上がります。この点から、第一読者にAIを据えることは読者にとっても価値があります。 レビューを通して、不足点を補うことでコンテンツ内に含まれる情報量が増えて、読者が受け取ることができる情報量は確実に増大します。 今後の展望 人間が自分の手元の環境で検証したって事はそれだけで価値があると思います。1番は自分のところで試す、自分の環境で試して動いたものを発表することです。その次に大事なのはその情報どこ見て作ったかなんですよね。 そういった正確性をAIが担保してくれるようになるといいなと思っています。 現状は1読者の意見だと軽い感じで受け取りましょう。 バグやハック的な内容であれば公式リファレンスに載ってないこともあります。そういう部分はダイレクトに誰かの助けになる情報なので自信を持って出しましょう。 8. 今すぐ始められる:最初の一歩 今回のお話で大事なこと AIを第一読者として活用する :人間の専門家に頼む前に、まずAIでファクトチェック 最終確認は必ず人間が行う :AIの判断を100%信じず、自分の目で情報源を確認 自分で記事を書いてからチェック :内容を把握している状態でファクトチェックするのが効果的 情報源を必ず確認する :AIが提示したリンク先を実際に見に行く習慣をつける 今まで4年で150本ほどブログを書いていますが、直近の10本ほどはこの手法を使っています。体感でブログの質が上がっています。ブログの長さも長くなっています。 ぜひ読んでもらえると嬉しいです。 2025-06-18 Azure Static Web Apps: x-ms-client-principalで安全なロールベース制御 2025-06-09 Notebook LMへのデータ収集をSlack Botで効率化する開発 with Google Docs 技術ブログ以外への適用可能性 実は僕自身も、技術ブログ以外でもDeep Researchを活用してます。 デザインガイドライン調査からプロンプト化まで デザインのガイドライン調査をしてそれをプロンプト化するみたいな使い方ですね。例えば「Material Design 3の最新ガイドラインを調査して、デザインレビュー用のチェックリストを作成して」みたいな感じで投げると、包括的な情報を集めてくれて、それをそのままプロンプトを作成する際の参考情報にしています。 SNS戦略の最適化 Xのガイドラインなど投稿の優位性とかも調査してます。「X(Twitter)でエンゲージメント率を上げるための最新の投稿戦略を調査して」って投げると、アルゴリズムの変更点から効果的な投稿時間、ハッシュタグ戦略まで調べてくれるんで、それをベースにプロンプトに組み込むための調査としてGemini Deep Researchを使ってる感じです。 他分野での応用例 この使い方って、いろんな分野に応用できるんですよね: マーケティング分野 : 各プラットフォームのガイドライン調査、競合他社のSNS戦略分析 コンテンツ制作 : デザイントレンドの調査、UI/UXのベストプラクティス調査 ビジネス戦略 : 業界動向の調査、新規事業のマーケットリサーチ 学術・研究 : 論文の初期調査、最新研究動向の調査 要は「初期調査→プロンプト化→業務効率化」っていう流れで、どんな分野でも使えるってことですね。技術者じゃなくても、マーケターでもデザイナーでも、研究者でも、みんなが使えるツールだと思います。 ただし!調査結果をそのまま鵜呑みにするのは危険ですよ。僕も必ず自分の目で確認してから使うようにしてます。AIが間違うこともあるし、情報が古い場合もあるので、最終的には人間がチェックするっていうのが大事ですね。効率化はしつつも、責任は人間が持つっていうスタンスが重要だと思います。 この活用ってちょっと前から話題になってるところなので、ぜひぜひその技術ブログって言う観点でも活用の道を模索していければいいなと個人的にめちゃくちゃ思ってます。体感乗り遅れてますけどね~~ 早くファクトチェック完璧にできるようになんねえかなぁ~ 龍ちゃん いろんなブログ書いてるので、ぜひ僕の個人ページでも見てやってください ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【実践解説】技術ブログ品質チェック術|Gemini Deep Researchで5分検証 first appeared on SIOS Tech. Lab .
挨拶 ども!6月は結構激しめにブログを投稿していたんですが、7月もがっつり忙しくなりそうで、ウハウハの龍ちゃんです。ブログサムネイル評価用プロンプトを結構な時間をかけて作りました。今までのように人にSlack投げて確認ではなく、生成AIにチャットを投げるだけで確認出来ており、サムネイルにも力が入っています。 さて!今回は「Azure Static Web Apps+Bicepの発展編:Azure Static Web Apps+Managed Functions+GitHub(カスタム認証)の環境をGitHub Actions after Bicepでデプロイ」というてんこ盛りの内容になっています。こちらを理解できれば、Azure Static Web Appsの基礎は出来上がったように感じます。 なかなかの長編になったので、初めての方は5日ほどかけてじっくりと、中級者以上の方はミスを指摘してやるってテンション感でご覧ください。 はじめに・概要 この記事で学べること 今回は、環境構築からデプロイまで横断的に解説していきます。以下にまとめます。 環境構築:DevContainer ローカル環境でAzure Static Web AppsとAzure Functionsを開発するために必要な環境 ローカル環境からBicepでAzureリソースを管理するのに必要な環境 Azure Functions開発(Managed Functions Node with TypeScript) APIの簡易的な仕様書から、TypeScriptによる実際のコーディング Azure Static Web Apps開発(Next.js) GitHub OAuthを活用した認証基盤 Managed Functionsと連携して認証状態を管理する方法 Bicep:ローカル環境からAzureリソースのコマンドデプロイ デプロイ環境をコード一発でデプロイ・クリーンナップできるようなbashファイル管理 GitHub Actions Azure Static Web AppsとAzure Functions(Managed Functions)をGitHub Actionsからデプロイする 設計から、Azure Static Web AppsとAPI統合、認証機能の実装方法。DevContainerからBicep、GitHub Actionsまでの一連の開発フローとして学習することができます。 以降はAzure Static Web AppsはSWAと省略した形で解説していきます。 技術スタック 使用している技術などは以下にまとめました。 カテゴリ 技術・ツール バージョン 備考 デプロイ環境 Azure Static Web Apps – Standardプラン:カスタム認証(GitHub) Azure Functions – Managed Functions 開発環境 DevContainer – – Azure CLI 2.74.0 – SWA CLI 2.0.6 – GitHub CLI 2.74.2 – Function Core Tools 4.0.7317 – フロントエンド Next.js 15.3.4 静的エクスポート React 19 – バックエンド Node.js + TypeScript – Azure Functions v4 Programming Model 今回は、データベースなどは抜きにしてSWA内ですべてが完結しているように構成しています。1点注意点としては、カスタム認証を使用する関係上、SWAのプラン設定をStandardにする必要があります。無料枠でも十分強力ですが、認証プロバイダーの追加設定などはStandardプランからでないと使用することができません。認証機能が必要ない方は、Maneged Functionsも無料枠があるのでBicepテンプレートをカスタマイズしてご利用ください。 コラム:Azure SWAのStandardとFreeプランの比較 Azure Static Web Appsには、個人のプロジェクトや小規模なウェブサイトに適した Freeプラン と、運用環境のアプリケーションや大規模なプロジェクトに適した Standardプラン があります。それぞれのプランで利用できる機能の範囲が異なり、特にManaged Functionsの利用方法に影響があります。 プラン別機能比較 機能 Freeプラン<br/>(個人/小規模プロジェクト向け) Standardプラン<br/>(運用/大規模プロジェクト向け) ウェブホスティング GitHub/Azure DevOps統合 グローバル分散 (静的コンテンツ) (静的コンテンツ) SSL証明書 (無料、自動更新) (無料、自動更新) ステージング環境 アプリあたり3つまで アプリあたり10個まで アプリの最大サイズ アプリあたり250MB アプリあたり500MB カスタムドメイン アプリあたり2つまで アプリあたり5つまで API (Azure Functions) マネージド (SWAに統合) マネージド または 独自のFunctionsアプリ利用 認証プロバイダー 事前構成済み (サービス定義済み) カスタム登録も可能 関数によるカスタムロール プライベートエンドポイント SLA (サービスレベル契約) なし 99.95% 帯域幅 サブスクリプションあたり月100GBまで (無料) サブスクリプションあたり月100GBまで (超過分課金) カスタマーサポート なし Managed Functionsが受ける影響 Azure Static Web AppsにおけるAPIバックエンドとしてのManaged Functionsは、選択するプランによって利用方法が大きく異なります。 Freeプランの場合 Managed Functionsのみ が利用可能です。これはAzure Static Web Appsに組み込まれたFunctionsアプリであり、ユーザーが個別にAzure Functionsリソースを作成・管理する必要がありません APIの実行回数には、 月間100万回の無料実行 が含まれます コールドスタートが発生しやすく、API呼び出しの最初の応答に時間がかかる場合があります Standardプランの場合 Managed Functions の利用に加えて、 既存の独自のAzure FunctionsアプリをStatic Web AppsのAPIバックエンドとして持ち込むことが可能 です 独自のFunctionsアプリを利用することで、HTTP以外のトリガー(例: タイマートリガー、キュートリガー)や、より複雑なバインディングを利用できるようになります 既存のFunctionsアプリをStatic Web Appsに統合したい場合に特に便利です どちらのプランを選ぶべきか? 以下の3つの観点から、Standardプランを検討してください: 1. アーキテクチャ戦略(マイクロサービス化) フロントエンドとバックエンドを分離 したマイクロサービス構成にしたい場合 既存のAzure Functionsアプリを再利用 したい、または独立して管理したい場合 HTTPエンドポイント以外のトリガー(タイマー、キュー等)が必要な場合 2. プロジェクトの成長・運用要件 予想されるトラフィック量が多い 、または帯域幅の無料枠を超える可能性がある場合 ミッションクリティカルなアプリケーション で99.95%のSLAが必要な場合 3つ以上のステージング環境 (開発、検証、本番等)が必要な場合 3. 認証・セキュリティ要件 カスタム認証プロバイダーの登録 が必要な場合(GitHub、Google以外の独自認証等) 関数によるカスタムロール 制御が必要な場合 プライベートエンドポイント でのセキュアな通信が必要な場合 まとめ 基本的には、個人的なプロジェクトやシンプルなウェブサイトであればFreeプランで十分ですが、ビジネス用途や大規模なアプリケーションにはStandardプランの検討をお勧めします。 今回の記事では、カスタム認証機能を使用するためStandardプランを選択していますが、認証が不要な場合はFreeプランでも同様の構成を構築できます!というか、課金が毎秒走っていると考えると怖いので、極力Freeプランを使うようにしています。 最終的な成果物 今回解説するアプリの動画です。ページとAPIをそれぞれ2つずつ作成し、認証あり・なしのパターンを網羅できるよう設計しています。 前提条件・準備 前提条件は以下の通りです。 Azureで有効なサブスクリプションを持っている GitHub.comに登録済み Dockerを使用できる環境 今回は、Azure Static Web AppsのStandardプランを使用するため、有効なサブスクリプションが必要です。また、GitHub Actionsを使用してデプロイを行い、認証プロバイダーにGitHub OAuthを使用するため、GitHub.comのアカウントは必須となります。 Dockerに関しては、今回の技術スタックはNode.jsに統一しているため、ローカル環境に有効なNode.js環境があれば開発を進めることができます。ただし、Dockerが入っていればコピー&ペーストで環境が立ち上がるため、こちらを推奨します。 プロジェクト設計・アーキテクチャ システム構成図 システム構成に関しては、上記のようになっています。作成するAzureリソースとしては、SWAのみとなっており、カスタム認証プロバイダーとしてGitHub OAuthを使用します。デプロイに関しては、Azureリソースに関してはBicep、アプリケーションのデプロイに関してはGitHub Actionsでそれぞれ管理します。 フロントエンドに関しては、Next.jsの静的エクスポートを使用してデプロイを行います。 ディレクトリ構造 . ├── .github # GitHub Actions用 ├── api # API用ディレクトリ ├── frontend # フロント用ディレクトリ Next.js静的エクスポート ├── infra # Infrastructure as Code用ディレクトリ └── swa-cli.config.json # SWA CLIのローカル起動用設定ファイル 全体のディレクトリ構成としては上記のようになります。それぞれ、他のリソースを参照せずに個別のディレクトリでそれぞれ開発をすることができます。 フロントエンド設計 ページ構成と各ページの目的 それでは、今回のアプリケーションのページ構成について見ていきましょう。まず、作成するルート一覧を明示しておきますね。 作成ルート一覧 / – トップページ(パブリック) /protected – 保護ページ(認証必須) シンプルな構成ですが、それぞれに明確な役割があります。 トップページ( / ) トップページは、いわば「認証の玄関口」として設計しています。ここでの主な目的は以下の通りです: 認証状態の可視化 : ユーザーが現在ログインしているかどうかを一目で分かるようにする 認証フローの起点 : 未認証ユーザーにとってのログイン入口 ユーザー情報の表示 : 認証済みユーザーには基本的なプロフィール情報を提示 ナビゲーションハブ : /protected へのリンク 実装としては、認証状態に応じて動的にUIを切り替える設計になっています。未認証時は「GitHubでログイン」ボタンを表示し、認証済み時はユーザー名やアバター、そして保護ページへのリンクを表示します。これにより、ユーザーは自分の状態を即座に把握できるというわけですね。 保護ページ( /protected ) 保護ページは、Azure SWAの認証機能の真価を発揮する場所です。ここでの設計ポイントは: Azure SWAによる保護 : フロントエンドでの認証チェックに依存しない 認証済みユーザー専用コンテンツ : より詳細なユーザー情報や機能の提供 セキュリティ情報の表示 : 認証システムの仕組みを理解してもらう この設計の面白いところは、ソースコード内で「認証チェック」を行わないことです。Azure SWAを使う場合は、サーバーレベルで保護されているため、そもそも未認証ユーザーはこのページにたどり着けません。 各ページのデザインは今回主題ではないので、生成AIを活用してデザインを生成してもらいます。 ルート保護設計とアクセスフロー Azure SWAの最大の魅力の一つが、宣言的なルート保護による強力なセキュリティ機能です。設定一つで堅牢な認証システムを実現できるというのは、本当に画期的ですよね。 アクセスフロー全体像 今回の設計で重要となる点としては、401エラー(未認証)が発生した瞬間に、ユーザーを /.auth/login/github に自動リダイレクトする仕組みです。これにより、ユーザーは「アクセス拒否」のような不親切な画面を見ることなく、スムーズに認証フローに誘導されます。 認証完了後は、元々アクセスしようとしていたページに戻ってくるのも、Azure SWAが自動的に処理してくれます。フロントエンド側で複雑なリダイレクト制御を書く必要がないというのは、大きなメリットです。 API設計 エンドポイント仕様の全体像 今回のシステムでは、以下の2つの主要エンドポイントを設計しました。 /api/user-info : ユーザー情報取得(認証状態問わず) /api/protected-data : 保護されたデータ取得(認証必須) /api/user-info では、フロントエンドから認証状態と未認証状態でのアクセスを確認するために、認証状態を問わずアクセスできるエンドポイントとして設計しています。 /api/protected-data では、認証されていない場合はエラーを返答し、ダミーデータを返すエンドポイントとして設計しています。 user-infoエンドポイントの設計 user-infoエンドポイントは、現在のユーザーの認証状態と基本情報を返すAPIです。未認証時でもエラーにならずにリクエストが成功するように設計します。 リクエスト仕様 Method: GET Path: /api/user-info Authentication: 不要 Parameters: なし レスポンス型定義 interface UserInfo { userId: string | null; name: string | null; email: string | null; provider: string | null; roles: string[]; isAuthenticated: boolean; } 成功レスポンス例(認証済み) { "userId": "github|12345678", "name": "yamada.taro", "email": "yamada.taro@example.com", "provider": "github", "roles": ["authenticated"], "isAuthenticated": true } 成功レスポンス例(未認証) { "userId": null, "name": null, "email": null, "provider": null, "roles": [], "isAuthenticated": false } ここで重要なのは、認証されていない場合も エラーではなく正常なレスポンス を返すことです。 isAuthenticated: false として状態を明示することで、フロントエンド側で適切な処理分岐ができます。 protected-dataエンドポイントの設計 protected-dataエンドポイントは、認証が必須のAPIです。認証されていないユーザーには401エラーを返し、アクセスを拒否します。 リクエスト仕様 Method: GET Path: /api/protected-data Authentication: 必須 Parameters: なし レスポンス型定義 interface ProtectedData { userId: string; message: string; timestamp: string; userNumber: number; } 成功レスポンス例 { "userId": "github|12345678", "message": "こんにちは、ユーザーgithub|12345678さん!", "timestamp": "2024-12-15T15:45:30.000Z", "userNumber": 456 } エラーレスポンスの統一設計 APIの一貫性を保つため、エラーレスポンス形式を統一しています。すべてのエラーレスポンスは以下の構造に従います。 エラーレスポンス型定義 interface ErrorResponse { error: string; message: string; } 認証エラー(401 Unauthorized) { "error": "Unauthorized", "message": "保護されたデータにアクセスするには認証が必要です" } サーバーエラー(500 Internal Server Error) { "error": "Internal server error", "message": "Application Crash" } この統一により、フロントエンド側でのエラーハンドリングが 大幅に簡素化 されます。どのAPIでも同じ構造でエラー情報を取得できるためです。 認証チェック方法の設計 Azure SWAでは、認証済みユーザーの情報が x-ms-client-principal ヘッダーにBase64エンコードされたJSON形式で渡されます。これは、Azure SWAの大きな特徴の一つですね。 ClientPrincipal型定義 interface ClientPrincipal { identityProvider: string; userId: string; userDetails: string; userRoles: string[]; } 認証チェックの流れは以下のようになります: リクエストヘッダーから x-ms-client-principal を取得 Base64デコードしてJSON解析 成功時は認証済みとして処理、失敗時は未認証として処理 このヘッダーは Azure SWAのリバースプロキシによって自動的に付与 されるため、偽装される心配はありません。 こちらの処理の流れは公式リファレンスにまとまっています。 開発環境構築(DevContainer) この章では、DevContainer環境を構築していきます。作成するディレクトリ構成は以下になります。 . └── .devcontainer └── devcontainer.json DevContainer設定  devcontainer.json 今回の開発に必要なツールを全て含んだDevContainer設定を作成します。Node.js、Azure CLI、Functions Core Tools、SWA CLIを含む開発環境を構築していきましょう。 { "name": "Azure SWA include API & Bicep Development", "image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bullseye", "features": { "ghcr.io/devcontainers/features/azure-cli:1": {}, "ghcr.io/devcontainers/features/github-cli:1": {} }, // npm@11.4.2に関してはnoticeが出ていたので対応 "postCreateCommand": "npm install -g npm@11.4.2 @azure/static-web-apps-cli azure-functions-core-tools@4", "customizations": { "vscode": { "extensions": [ "ms-azuretools.vscode-bicep", "GitHub.vscode-github-actions", "ms-vscode.azure-account" ] } }, "forwardPorts": [ 3000, 7071, 4280 ], "mounts": [ "source=${localEnv:HOME}/.azure,target=/home/node/.azure,type=bind,consistency=cached" ] } ベースイメージとインストール ベースイメージとしては、 Microsoftが提供しているNode環境 を使用しています。Node.js 20系の安定版が含まれており、今回のプロジェクトに必要な環境が整っています。 features として、Azure CLIとGitHub CLIを導入しています。これにより、Azureリソースの管理とGitHubとの連携が可能になります。 postCreateCommand で、npmパッケージとしてFunctions Core Tools@v4とSWA CLIをグローバルインストールしています。npmのバージョンアップも行っており、これは最新の機能とセキュリティ修正を適用するためです。 VS Code拡張機能 開発効率を上げるため、以下の拡張機能を自動インストールします: Bicep : Azureリソース定義のシンタックスハイライトと補完 GitHub Actions : ワークフロー編集の支援 Azure Account : Azure認証とリソース管理 ポートフォワーディング 開発中に使用する主要ポートを事前に設定しています: 3000 : Next.jsフロントエンド 7071 : Azure Functions API 4280 : SWA CLI統合サーバー マウント設定 "mounts": [ "source=${localEnv:HOME}/.azure,target=/home/node/.azure,type=bind,consistency=cached" ] ホスト側にある .azure ディレクトリをマウントしています。これにより、ホスト側で az login をしていればDevContainer環境内でも認証済みとして扱うことができます。 セキュリティに関する注意 この設定は、Azure認証情報をコンテナ内で共有するため、セキュリティリスクがあります。使用する場合は十分に注意し、本番環境や共有環境では適切な認証方法を検討してください。 開発環境の起動確認 環境を立ち上げた後は、各CLIが正常にインストールされているか確認しましょう。 Azure CLI確認 az --version # azure-cli 2.74.0 # core 2.74.0 # telemetry 1.1.0 # # Dependencies: # msal 1.32.3 # azure-mgmt-resource 23.3.0 # # Python location '/opt/az/bin/python3' # Config directory '/home/node/.azure' # Extensions directory '/home/node/.azure/cliextensions' # # Python (Linux) 3.12.10 (main, May 27 2025, 09:13:17) [GCC 10.2.1 20210110] # # Legal docs and information: aka.ms/AzureCliLegal # # # Your CLI is up-to-date. 併せてマウント設定が正しく動作しているか。認証状態の確認をしておきましょう。 az account show ホスト側で認証済みの場合、アカウント情報が表示されます。認証されていない場合は、以下のコマンドで認証を行ってください。 az login GitHub CLI確認 gh --version # gh version 2.74.2 (2025-06-18) # https://github.com/cli/cli/releases/tag/v2.74.2 SWA CLI確認 swa --version # 2.0.6 Functions Core Tools確認 func --version # 4.0.7317 それぞれのバージョン情報が表示されれば成功です。これで、Azure Static Web Appsの開発に必要な全てのツールが使用可能になりました! これで、DevContainer環境での開発準備が完了しました。次章からは、実際にAzure FunctionsのAPI実装に入っていきます。 Azure Functions API実装 この章では、Azure Functions環境を構築・実装をしていきます。作成するディレクトリ構成は以下になります。ファイルの作成自体は、直接作るのではなくFunctions Core Toolsを介して生成をしていきます。 プロジェクト初期化 . └── api ├── .funcignore ├── .gitignore ├── host.json ├── local.settings.json ├── package.json ├── package-lock.json ├── src │ └── functions │ ├── protected-data.ts │ └── user-info.ts ├── tsconfig.json └── .vscode └── extensions.json まずは、 api ディレクトリを用意してディレクトリに遷移します。中で初期化コマンドを実行すると以下のファイルが自動生成され、 npm install が実行されます。 mkdir api cd api func init --typescript これで初期化は完了です。次は、各エンドポイントの作成を進めていきます。エンドポイントは Functions Core Tools(公式リファレンス) で生成します。コマンドを通して作成することで、基本の関数が準備された状態でスムーズな開発を進めることができます。使用することができるテンプレートは以下のコマンドで取得することができます。 func templates list /api/user-info エンドポイント実装 /api/user-info エンドポイントの作成を進めていきます。 api ディレクトリで以下のコマンドを実行してください。 func new --name user-info --template "HTTP trigger" --authlevel "anonymous" 上記のコマンドで src/functions 内に user-info.ts というファイルが生成されます。認証レベル「匿名」で user-info というファイル名で作成されます。作成されたファイルの中には、 --name で指定した名前のパスに対してHELLO Worldを返す関数が作成されています。 中身を確認して、変更を加えてください。 import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions'; interface ClientPrincipal { identityProvider: string; userId: string; userDetails: string; userRoles: string[]; } interface UserInfo { userId: string; name: string; email: string; provider: string; roles: string[]; isAuthenticated: boolean; } export async function userInfo(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { context.log('Processing user-info request'); try { // x-ms-client-principalヘッダーの取得 const clientPrincipalHeader = request.headers.get('x-ms-client-principal'); if (!clientPrincipalHeader) { context.log('ヘッダーにx-ms-client-principalが確認することができず、認証状態を確認することができませんでした。'); return { status: 200, jsonBody: { isAuthenticated: false, userId: null, name: null, email: null, provider: null, roles: [] } }; } // Base64デコード const clientPrincipalData = Buffer.from(clientPrincipalHeader, 'base64').toString('utf-8'); const clientPrincipal: ClientPrincipal = JSON.parse(clientPrincipalData); context.log('Client Principal:', clientPrincipal); // ユーザー情報の整形 const userInfo: UserInfo = { userId: clientPrincipal.userId, name: clientPrincipal.userDetails, email: clientPrincipal.userDetails, // GitHubの場合はユーザー名,Googleならメールアドレス provider: clientPrincipal.identityProvider, roles: clientPrincipal.userRoles || [], isAuthenticated: true }; return { status: 200, headers: { 'Content-Type': 'application/json' }, jsonBody: userInfo }; } catch (error) { context.log('Applicatoin Crash', error); return { status: 500, jsonBody: { error: 'Internal server error', message: 'Failed to process user information' } }; } } app.http('user-info', { methods: ['GET'], authLevel: 'anonymous', route: 'user-info', handler: userInfo }); こちらの関数での処理は、現在のユーザーの認証状態と基本情報を返すAPIです。特徴としては、未認証時でもエラーにならずにリクエストが成功します。 コードは大きく3つに分かれています。 ヘッダーから x-ms-client-principal を取得し、ない場合は status:200 (ユーザー情報なし)を返答 ヘッダーからデコードしてユーザー情報を取得し、 status:200 (ユーザー情報あり)を返答 上記の処理で問題が発生した場合は status:500 を返答 デコードの処理は、 公式リファレンスで紹介されている手法 をもとに構築しています。その他のAPIの実装はAPI設計に準拠しています。 /api/protected-data エンドポイント実装 /api/protected-data エンドポイントの作成を進めていきます。 api ディレクトリで以下のコマンドを実行してください。 func new --name protected-data --template "HTTP trigger" --authlevel "anonymous" 上記のコマンドで src/functions 内に protected-data.ts というファイルが生成されます。認証レベル「匿名」で protected-data というファイル名で作成されます。作成されたファイルの中には、 --name で指定した名前のパスに対してHELLO Worldを返す関数が作成されています。 中身を確認して、変更を加えてください。 import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions'; interface ClientPrincipal { identityProvider: string; userId: string; userDetails: string; userRoles: string[]; } interface ProtectedData { userId: string; message: string; timestamp: string; userNumber: number; } // 認証チェック用のヘルパー関数 function checkAuthentication(request: HttpRequest, context: InvocationContext): ClientPrincipal | null { const clientPrincipalHeader = request.headers.get('x-ms-client-principal'); if (!clientPrincipalHeader) { context.log('認証ヘッダーが見つかりませんでした'); return null; } try { const clientPrincipalData = Buffer.from(clientPrincipalHeader, 'base64').toString('utf-8'); return JSON.parse(clientPrincipalData); } catch (error) { context.log('Client principalの解析に失敗しました:', error); return null; } } // 軽量なユーザー固有データ生成 function generateUserData(userId: string): ProtectedData { // userIdをベースにした簡単なシード値 const seed = userId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0); return { userId, message: `こんにちは、ユーザー${userId}さん!`, timestamp: new Date().toISOString(), userNumber: seed % 1000 // 0-999の範囲のユーザー番号 }; } export async function protectedData(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { context.log('protected-dataリクエストを処理中'); try { // 認証チェック const clientPrincipal = checkAuthentication(request, context); if (!clientPrincipal) { return { status: 401, headers: { 'Content-Type': 'application/json' }, jsonBody: { error: 'Unauthorized', message: '保護されたデータにアクセスするには認証が必要です' } }; } context.log('認証済みユーザー:', clientPrincipal.userId); // 軽量なダミーデータを生成 const userData = generateUserData(clientPrincipal.userId); return { status: 200, headers: { 'Content-Type': 'application/json' }, jsonBody: userData }; } catch (error) { context.log('protected-dataリクエストの処理中にエラー:', error); return { status: 500, jsonBody: { error: 'Internal server error', message: '保護されたデータの取得に失敗しました' } }; } } app.http('protected-data', { methods: ['GET'], authLevel: 'anonymous', // SWAのManaged Functionsのため route: 'protected-data', handler: protectedData }); こちらの関数の処理は、認証されていないユーザーには401エラーを返し、認証済みの場合はダミーデータを返答します。 処理として、 /api/user-info と大差ありません。先ほどのヘッダーの取得からデコードまでをヘルパー関数として切り出しています。 処理取しては、大きく4つに分類されます。 checkAuthentication :ヘッダーから x-ms-client-principal を取得、デコードした情報を返答する。情報がない場合は、 null を返答 checkAuthentication からの戻り値が null : status:401 を返答 checkAuthentication からの戻り値がユーザー情報: generateUserData を使用してダミー情報を作成して、 status:200 で返答 上記の処理で問題が発生した場合は status:500 を返答 デコードの処理は、 公式リファレンスで紹介されている手法 をもとに構築しています。その他のAPIの実装はAPI設計に準拠しています。 ローカルでのAPI動作確認 それでは、作成したAPIが正しく動作するかローカル環境で確認していきましょう。 ローカル環境の制限事項 ローカル環境では認証ヘッダーが自動付与されないため、テスト範囲は限定的ですが、基本的な動作確認は可能です。 認証ヘッダーの模擬 Azure SWA の認証ヘッダー( x-ms-client-principal )は自動付与されません 認証済み状態のテストはローカルでは困難です 本格的な認証テストは本番環境またはSWA CLI統合環境で行う必要があります Azure Functions API のローカル起動 まず、APIディレクトリで Functions Core Tools を使ってローカルサーバーを起動します。 cd api func start 正常に起動すると、以下のような出力が表示されます: Azure Functions Core Tools Core Tools Version: 4.0.7317 Commit hash: N/A +5ca56d37938824531b691f094d0a77fd6f51af20 (64-bit) Function Runtime Version: 4.1038.300.25164 [2025-06-30T14:09:22.328Z] Worker process started and initialized. Functions: protected-data: [GET] http://localhost:7071/api/protected-data user-info: [GET] http://localhost:7071/api/user-info For detailed output, run func with --verbose flag. エンドポイントが正しく表示されていることを確認してください。 /api/user-info エンドポイントのテスト まずは認証状態を問わずアクセス可能な user-info エンドポイントをテストします。こちらはcURLでもよいですし、ブラウザで実行してもよいです。 cURLでのテスト curl http://localhost:7071/api/user-info 期待されるレスポンス ローカル環境では認証ヘッダーが存在しないため、未認証状態のレスポンスが返されます: { "userId": null, "name": null, "email": null, "provider": null, "roles": [], "isAuthenticated": false } 確認ポイント ステータスコードが 200 OK であること レスポンスの型が設計通りであること isAuthenticated が false になっていること 各プロパティが正しく null または空配列になっていること /api/protected-data エンドポイントのテスト 次に、認証が必須の protected-data エンドポイントをテストします。 cURLでのテスト curl http://localhost:7071/api/protected-data 期待されるレスポンス 認証ヘッダーが存在しないため、401エラーが返されます: { "error": "Unauthorized", "message": "保護されたデータにアクセスするには認証が必要です" } 確認ポイント ステータスコードが 401 Unauthorized であること エラーレスポンスの形式が統一されていること error と message プロパティが含まれていること デバッグとログ確認 開発中は、Function Core Tools のコンソール出力でデバッグ情報を確認できます。 ログ出力の確認 API関数内の context.log() による出力がコンソールに表示されます: [2025-06-30T14:13:03.946Z] Executing 'Functions.protected-data' (Reason='This function was programmatically called via the host APIs.', Id=3570bfd5-7ed0-4ea7-a34f-262df44324ac) [2025-06-30T14:13:03.951Z] protected-dataリクエストを処理中 [2025-06-30T14:13:03.951Z] 認証ヘッダーが見つかりませんでした [2025-06-30T14:13:03.952Z] Executed 'Functions.protected-data' (Succeeded, Id=3570bfd5-7ed0-4ea7-a34f-262df44324ac, Duration=5ms) 詳細ログの表示 より詳細なログが必要な場合は、 --verbose オプションを使用します: func start --verbose トラブルシューティング 開発時に詰まった点をトラブルシューティングとしてまとめておきます。 エンドポイントにアクセスできない Functions Core Tools が正常に起動しているか確認 ポート 7071 が他のプロセスで使用されていないか確認 ファイアウォール設定を確認 TypeScript コンパイルエラー npm run build でビルドエラーがないか確認 型定義の不整合がないかチェック レスポンスが期待通りでない context.log() でデバッグ出力を追加 リクエストヘッダーが正しく設定されているか確認 ソースコードに変更を加えたのに変更が反映されない func start は変更後のソースを読み取って同期してくれません 一度 Ctrl+C で実行を落として、再起動してみてください これで、ローカル環境でのAPI基本動作確認が完了しました。次に、フロントエンドの実装に移りましょう。認証機能を含む完全なテストは、SWA CLI での統合テスト環境で行います。 コラム:なぜauthlevelはanonymousでよいのか? Azure Functions では通常、API の認証レベルを function 、 admin 、 anonymous などから選択できます。しかし、Azure Static Web Apps の Managed Functions では anonymous を設定するのが適切です。 Azure Functions単体での認証レベル function :Function Key が必要 admin :Master Key が必要 anonymous :認証不要 SWA + Managed Functions での認証の仕組み Azure Static Web Apps では、認証・認可を SWA側で一元管理 します: SWAレベルでの保護 : staticwebapp.config.json でルート保護を定義 統合セキュリティ : /.auth システムによる認証管理 Functions側はanonymous :SWAが認証済みリクエストのみをFunctionsに転送 つまり、Functionsレベルでの認証をバイパスし、SWAの統合認証システムを活用することで、よりシームレスで一元化された認証管理が可能になります。Managed Functions では、この仕組みにより安全性を保ちながら開発効率を向上させることができるのです。 Next.jsフロントエンド実装 この章では、Next,js環境を構築・実装をしていきます。作成するディレクトリ構成は以下になります。初期設定はcreate-next-app経由で作成するため、作成するファイルは useAuth.ts / page.tsx / protected>page.tsx の3ファイルになります。 ./frontend ├── eslint.config.mjs ├── next.config.ts ├── next-env.d.ts ├── package.json ├── package-lock.json ├── postcss.config.mjs ├── README.md ├── public ├── src │ ├── app │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ ├── page.tsx // トップページ │ │ └── protected │ │ └── page.tsx           // 認証済みルート │ └── hooks │ └── useAuth.ts // 認証系Hooks └── tsconfig.json Next.js設定(静的エクスポート対応) まずは frontend ディレクトリを作成して、ディレクトリに移動します。作成自体は、 npx create-next-app コマンドを通して自動生成を行っています。バージョンとしては15固定で、プロジェクト設定を行っています。 mkdir frontend cd frontend npx create-next-app@15 . --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" 静的エクスポート設定のために next.config.ts を変種する必要があります。 import type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ output: 'export', // 静的エクスポート設定 distDir: 'out', // ビルドファイルのエクスポート先設定 images: { unoptimized: true // 静的エクスポートでは使用できない画像圧縮の無効化 } }; export default nextConfig; これで、静的エクスポートが実装することができます。 useAuth認証カスタムフック実装 こちらのファイルは hooks ディレクトリを作成し、 useAuth.ts というファイル内にコピー&ペーストをしてください。 import { useState, useEffect } from 'react'; interface UserInfo { isAuthenticated: boolean; user?: { name?: string; email?: string; login?: string; }; } export const useAuth = () => { const [userInfo, setUserInfo] = useState<UserInfo>({ isAuthenticated: false }); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); useEffect(() => { const fetchUserInfo = async () => { try { const response = await fetch('/.auth/me'); if (response.ok) { const data = await response.json(); if (data.clientPrincipal) { setUserInfo({ isAuthenticated: true, user: { name: data.clientPrincipal.userDetails, email: data.clientPrincipal.userDetails, login: data.clientPrincipal.userId } }); } else { setUserInfo({ isAuthenticated: false }); } } else { setUserInfo({ isAuthenticated: false }); } } catch (err) { console.error('認証情報の取得に失敗しました:', err); setError('認証情報の取得に失敗しました'); setUserInfo({ isAuthenticated: false }); } finally { setLoading(false); } }; fetchUserInfo(); }, []); const login = () => { window.location.href = '/.auth/login/github'; }; const logout = () => { window.location.href = '/.auth/logout'; }; return { ...userInfo, loading, error, login, logout }; }; 処理としては、ログインとログアウトロジックがあります。こちらは、認証プロバイダー(GitHub)としてチューニングされています。 SWAでは認証済みの場合、 直接エンドポイントとして /.auth/me に認証情報が含まれます 。そちらに対して確認を行うことで、フロントエンド側から認証状態の確認を行うことができます。 認証状態の確認中は useState の loading で制御をしています。 トップページ実装 こちらは src > app > page.tsx ファイルを編集してください。まずはコピー&ペーストをしてください。 'use client'; import Link from 'next/link'; import { useAuth } from '@/hooks/useAuth'; import { useState } from 'react'; interface ApiUserInfo { userId: string; name: string; email: string; provider: string; roles: string[]; isAuthenticated: boolean; } interface ApiError { error: string; message: string; } export default function HomePage() { const { isAuthenticated, user, loading, login, logout } = useAuth(); const [apiData, setApiData] = useState<ApiUserInfo | null>(null); const [apiError, setApiError] = useState<string | null>(null); const [apiLoading, setApiLoading] = useState(false); const fetchUserInfo = async () => { setApiLoading(true); setApiError(null); setApiData(null); try { const response = await fetch('/api/user-info', { method: 'GET', headers: { 'Content-Type': 'application/json', }, credentials: 'include', // 認証情報を含める }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: ApiUserInfo | ApiError = await response.json(); if ('error' in data) { setApiError(data.message || data.error); } else { setApiData(data); } } catch (error) { console.error('API呼び出しエラー:', error); setApiError(error instanceof Error ? error.message : 'APIの呼び出しに失敗しました'); } finally { setApiLoading(false); } }; if (loading) { return ( <div className="min-h-screen flex items-center justify-center"> <div className="text-center"> <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div> <p className="text-gray-600">認証状態を確認中...</p> </div> </div> ); } return ( <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100"> <div className="container mx-auto px-4 py-16"> <div className="max-w-4xl mx-auto text-center"> <h1 className="text-5xl font-bold text-gray-900 mb-8"> Next.js 15 + Azure SWA </h1> <p className="text-xl text-gray-600 mb-12"> GitHub認証を使用したセキュアなWebアプリケーション </p> <div className="bg-white rounded-lg shadow-lg p-8 mb-8"> <h2 className="text-2xl font-semibold text-gray-800 mb-6"> 認証状態 </h2> {isAuthenticated ? ( <div className="space-y-4"> <div className="flex items-center justify-center space-x-2"> <div className="w-3 h-3 bg-green-500 rounded-full"></div> <span className="text-green-700 font-medium">認証済み</span> </div> {user && ( <div className="bg-gray-50 rounded-lg p-4 text-left max-w-md mx-auto"> <h3 className="font-medium text-gray-800 mb-2">ユーザー情報(クライアント側)</h3> {user.name && ( <p className="text-sm text-gray-600"> <span className="font-medium">名前:</span> {user.name} </p> )} {user.email && ( <p className="text-sm text-gray-600"> <span className="font-medium">メール:</span> {user.email} </p> )} {user.login && ( <p className="text-sm text-gray-600"> <span className="font-medium">ログイン:</span> {user.login} </p> )} </div> )} <div className="flex flex-col sm:flex-row gap-4 justify-center mt-6"> <Link href="/protected" className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-6 rounded-lg transition duration-200" > 保護されたページへ </Link> <button onClick={logout} className="bg-gray-500 hover:bg-gray-600 text-white font-medium py-3 px-6 rounded-lg transition duration-200" > ログアウト </button> </div> </div> ) : ( <div className="space-y-4"> <div className="flex items-center justify-center space-x-2"> <div className="w-3 h-3 bg-red-500 rounded-full"></div> <span className="text-red-700 font-medium">未認証</span> </div> <p className="text-gray-600 mb-6"> 保護されたコンテンツにアクセスするには、GitHubアカウントでログインしてください。 </p> <button onClick={login} className="bg-gray-900 hover:bg-gray-800 text-white font-medium py-3 px-6 rounded-lg transition duration-200 flex items-center space-x-2 mx-auto" > <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"> <path fillRule="evenodd" d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z" clipRule="evenodd" /> </svg> <span>GitHubでログイン</span> </button> </div> )} </div> {/* API情報取得セクション */} <div className="bg-white rounded-lg shadow-lg p-8 mb-8"> <h2 className="text-2xl font-semibold text-gray-800 mb-6"> API情報取得 </h2> <div className="space-y-4"> <button onClick={fetchUserInfo} disabled={apiLoading} className="bg-green-600 hover:bg-green-700 disabled:bg-green-400 text-white font-medium py-3 px-6 rounded-lg transition duration-200 flex items-center space-x-2 mx-auto" > {apiLoading ? ( <> <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div> <span>取得中...</span> </> ) : ( <> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /> </svg> <span>サーバーからユーザー情報を取得</span> </> )} </button> {apiError && ( <div className="bg-red-50 border border-red-200 rounded-lg p-4 text-left max-w-md mx-auto"> <h3 className="font-medium text-red-800 mb-2">エラー</h3> <p className="text-sm text-red-600">{apiError}</p> </div> )} {apiData && ( <div className={" rounded-lg p-4 text-left max-w-md mx-auto" + (apiData.isAuthenticated ? ' bg-green-50 border border-green-200' : ' bg-red-50 border border-red-200')} > <h3 className={"font-medium mb-2" + (apiData.isAuthenticated ? " text-green-800": " text-red-800")}>サーバー側ユーザー情報</h3> <div className={"space-y-1 text-sm " + (apiData.isAuthenticated ? " text-green-700": " text-red-700")}> <p><span className="font-medium">認証状態:</span> {apiData.isAuthenticated ? '認証済み' : '未認証'}</p> <p><span className="font-medium">ユーザーID:</span> {apiData.userId}</p> <p><span className="font-medium">名前:</span> {apiData.name}</p> <p><span className="font-medium">メール:</span> {apiData.email}</p> <p><span className="font-medium">プロバイダー:</span> {apiData.provider}</p> <p><span className="font-medium">ロール:</span> {apiData.roles.length > 0 ? apiData.roles.join(', ') : 'なし'}</p> </div> </div> )} </div> </div> <div className="text-center text-gray-500"> <p className="text-sm"> このアプリケーションはAzure Static Web Appsで認証を管理しています </p> </div> </div> </div> </div> ); } ロジック部分 ロジック部分を抽出しました。こちらは、ボタンを押した際に /api/user-info に対してGETリクエストを送信して問い合わせを行っています。APIリクエストの状態管理は useState の apiLoading でエラーに関しては useState の apiError で管理をしています。 interface ApiUserInfo { userId: string; name: string; email: string; provider: string; roles: string[]; isAuthenticated: boolean; } interface ApiError { error: string; message: string; } const [apiData, setApiData] = useState<ApiUserInfo | null>(null); const [apiError, setApiError] = useState<string | null>(null); const [apiLoading, setApiLoading] = useState(false); const fetchUserInfo = async () => { setApiLoading(true); setApiError(null); setApiData(null); try { const response = await fetch('/api/user-info', { method: 'GET', headers: { 'Content-Type': 'application/json', } }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data: ApiUserInfo | ApiError = await response.json(); if ('error' in data) { setApiError(data.message || data.error); } else { setApiData(data); } } catch (error) { console.error('API呼び出しエラー:', error); setApiError(error instanceof Error ? error.message : 'APIの呼び出しに失敗しました'); } finally { setApiLoading(false); } }; 表示部分 表示部分としてはコードとして膨大なので、ソース添付としては割愛します。処理としては単純です。 認証確認中は useAuth から提供される loading をフラグとしてローディング画面を表示しています。 認証後は、APIアクセス中はapiLoadingでボタンの非活性化とローディングの表示、エラーが発生した場合はエラーの表示を行っています。 protectedページ実装 こちらは src > app > protected > page.tsx ファイルを新規作成してください。まずはコピー&ペーストをしてください。 'use client'; import Link from 'next/link'; import { useAuth } from '@/hooks/useAuth'; import { useState } from 'react'; interface ProtectedData { userId: string; message: string; timestamp: string; userNumber: number; } interface ApiError { error: string; message: string; } export default function ProtectedPage() { const { user, loading, logout } = useAuth(); const [protectedData, setProtectedData] = useState<ProtectedData | null>(null); const [apiError, setApiError] = useState<string | null>(null); const [apiLoading, setApiLoading] = useState(false); const fetchProtectedData = async () => { setApiLoading(true); setApiError(null); setProtectedData(null); try { const response = await fetch('/api/protected-data', { method: 'GET', headers: { 'Content-Type': 'application/json', } }); if (!response.ok) { if (response.status === 401) { throw new Error('認証が必要です'); } throw new Error(`HTTP error! status: ${response.status}`); } const data: ProtectedData | ApiError = await response.json(); if ('error' in data) { setApiError(data.message || data.error); } else { setProtectedData(data); } } catch (error) { console.error('保護されたAPI呼び出しエラー:', error); setApiError(error instanceof Error ? error.message : '保護されたデータの取得に失敗しました'); } finally { setApiLoading(false); } }; if (loading) { return ( <div className="min-h-screen flex items-center justify-center"> <div className="text-center"> <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div> <p className="text-gray-600">ユーザー情報を読み込み中...</p> </div> </div> ); } return ( <div className="min-h-screen bg-gradient-to-br from-green-50 to-emerald-100"> <div className="container mx-auto px-4 py-16"> <div className="max-w-4xl mx-auto"> <div className="text-center mb-12"> <div className="flex items-center justify-center space-x-2 mb-4"> <div className="w-4 h-4 bg-green-500 rounded-full"></div> <span className="text-green-700 font-medium text-lg">保護されたエリア</span> </div> <h1 className="text-4xl font-bold text-gray-900 mb-4"> 🎉 認証成功! </h1> <p className="text-xl text-gray-600"> このページは認証済みユーザーのみがアクセスできます </p> </div> <div className="bg-white rounded-lg shadow-lg p-8 mb-8"> <h2 className="text-2xl font-semibold text-gray-800 mb-6 text-center"> あなたの情報 </h2> {user ? ( <div className="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-lg p-6 mb-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> {user.name && ( <div className="bg-white rounded-lg p-4"> <div className="text-sm text-gray-500 font-medium">名前</div> <div className="text-lg text-gray-900">{user.name}</div> </div> )} {user.email && ( <div className="bg-white rounded-lg p-4"> <div className="text-sm text-gray-500 font-medium">メールアドレス</div> <div className="text-lg text-gray-900">{user.email}</div> </div> )} {user.login && ( <div className="bg-white rounded-lg p-4"> <div className="text-sm text-gray-500 font-medium">GitHubユーザー名</div> <div className="text-lg text-gray-900">@{user.login}</div> </div> )} <div className="bg-white rounded-lg p-4"> <div className="text-sm text-gray-500 font-medium">アクセス日時</div> <div className="text-lg text-gray-900"> {new Date().toLocaleString('ja-JP')} </div> </div> </div> </div> ) : ( <div className="text-center py-8"> <p className="text-gray-500">ユーザー情報が見つかりません</p> </div> )} <div className="bg-blue-50 border border-blue-200 rounded-lg p-6 mb-6"> <h3 className="text-lg font-semibold text-blue-900 mb-3"> 🔒 セキュリティ情報 </h3> <ul className="text-blue-800 space-y-2"> <li>• このページはAzure SWAレベルで保護されています</li> <li>• 未認証ユーザーは自動的にGitHubログインにリダイレクトされます</li> <li>• 認証情報はAzure Static Web Appsで安全に管理されています</li> </ul> </div> <div className="flex flex-col sm:flex-row gap-4 justify-center"> <Link href="/" className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-6 rounded-lg transition duration-200 text-center" > ホームに戻る </Link> <button onClick={logout} className="bg-red-500 hover:bg-red-600 text-white font-medium py-3 px-6 rounded-lg transition duration-200" > ログアウト </button> </div> </div> {/* 保護されたデータ取得セクション */} <div className="bg-white rounded-lg shadow-lg p-8 mb-8"> <h2 className="text-2xl font-semibold text-gray-800 mb-6 text-center"> 🛡 保護されたデータ </h2> <div className="space-y-6"> <div className="text-center"> <p className="text-gray-600 mb-4"> サーバー側で認証を確認し、あなた専用のデータを取得します </p> <button onClick={fetchProtectedData} disabled={apiLoading} className="bg-purple-600 hover:bg-purple-700 disabled:bg-purple-400 text-white font-medium py-3 px-6 rounded-lg transition duration-200 flex items-center space-x-2 mx-auto" > {apiLoading ? ( <> <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div> <span>データ取得中...</span> </> ) : ( <> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /> </svg> <span>保護されたデータを取得</span> </> )} </button> </div> {apiError && ( <div className="bg-red-50 border border-red-200 rounded-lg p-6"> <div className="flex items-center space-x-2 mb-2"> <svg className="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> </svg> <h3 className="font-medium text-red-800">エラー</h3> </div> <p className="text-sm text-red-600">{apiError}</p> </div> )} {protectedData && ( <div className="bg-gradient-to-r from-purple-50 to-pink-50 border border-purple-200 rounded-lg p-6"> <div className="flex items-center space-x-2 mb-4"> <svg className="w-5 h-5 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> </svg> <h3 className="font-medium text-purple-800">取得成功 🎯</h3> </div> <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="bg-white rounded-lg p-4"> <div className="text-sm text-gray-500 font-medium">ユーザーID</div> <div className="text-lg text-gray-900 font-mono">{protectedData.userId}</div> </div> <div className="bg-white rounded-lg p-4"> <div className="text-sm text-gray-500 font-medium">ユーザー番号</div> <div className="text-lg text-gray-900 font-bold "> #{protectedData.userNumber} </div> </div> <div className="bg-white rounded-lg p-4 md:col-span-2"> <div className="text-sm text-gray-500 font-medium">パーソナライズメッセージ</div> <div className="text-lg text-gray-900">{protectedData.message}</div> </div> <div className="bg-white rounded-lg p-4 md:col-span-2"> <div className="text-sm text-gray-500 font-medium">データ生成時刻</div> <div className="text-lg text-gray-900 font-mono"> {new Date(protectedData.timestamp).toLocaleString('ja-JP')} </div> </div> </div> <div className="mt-4 p-3 bg-purple-100 rounded-lg"> <p className="text-sm text-purple-700"> 💡 このデータはあなたのユーザーIDに基づいて動的に生成され、 サーバー側で認証を確認した後にのみ提供されます。 </p> </div> </div> )} </div> </div> <div className="text-center text-gray-500"> <p className="text-sm"> Azure Static Web Apps + GitHub認証で実現したセキュアなアプリケーション </p> </div> </div> </div> </div> ); } ロジック部分 ロジック部分を抽出しました。こちらは、ボタンを押した際に /api/protected-data に対してGETリクエストを送信して問い合わせを行っています。APIリクエストの状態管理は useState の apiLoading でエラーに関しては useState の apiError で管理をしています。 SWA側で401エラーが発生した場合は、アクセスできないように組みますが将来的なエラーハンドリングのために仮で実装しています。 interface ProtectedData { userId: string; message: string; timestamp: string; userNumber: number; } interface ApiError { error: string; message: string; } const [protectedData, setProtectedData] = useState<ProtectedData | null>(null); const [apiError, setApiError] = useState<string | null>(null); const [apiLoading, setApiLoading] = useState(false); const fetchProtectedData = async () => { setApiLoading(true); setApiError(null); setProtectedData(null); try { const response = await fetch('/api/protected-data', { method: 'GET', headers: { 'Content-Type': 'application/json', } }); if (!response.ok) { if (response.status === 401) { throw new Error('認証が必要です'); } throw new Error(`HTTP error! status: ${response.status}`); } const data: ProtectedData | ApiError = await response.json(); if ('error' in data) { setApiError(data.message || data.error); } else { setProtectedData(data); } } catch (error) { console.error('保護されたAPI呼び出しエラー:', error); setApiError(error instanceof Error ? error.message : '保護されたデータの取得に失敗しました'); } finally { setApiLoading(false); } }; 表示部分 表示部分としてはコードとして膨大なので、ソース添付としては割愛します。処理としては単純です。 認証確認中は useAuth から提供される loading をフラグとしてローディング画面を表示しています。 認証後は、APIアクセス中はapiLoadingでボタンの非活性化とローディングの表示、エラーが発生した場合はエラーの表示を行っています。 ローカルでのフロントエンド動作確認 ローカルでフロントエンドのテストをします。APIとの接続を行っていないので、APIアクセスはすべて失敗するので画面上のテストのみになります。 npm run dev # http://localhost:3000 ローカル結合テスト(SWA CLI) ここでは、SWA CLIを使用してフロントエンドとAzure Functionsの結合テストを行っていきます。SWA CLIを使うことで、SWAに上げた時の挙動を認証部分を含めてエミュレーターで確認することができます。 SWA CLI設定 プロジェクトルートで、以下のコマンドを入力することで対話的に swa-cli.config.json を作成することが可能です。 swa init 設定項目は公式のこちら にまとまっています。若干設定が複雑な部分があるため、今回の環境に合わせた構成ファイルに対して解説します。 swaConfigLocation に関してのみ次の節で解説を行います。 フロントもバックもSWA CLIで起動する こちらの構成ファイルでは、 swa start でフロントもバックのリソースも立ち上がるようになります。利点としては、コマンド一つで立ち上げることができるので楽な点ですね。 フロントに関しては、devコマンドで起動されているためフロントのコードを変更することで自動的に更新(ホットリロード)が入ります。バックに関しては、更新を反映させるためには一度コマンドを落として再度実行する必要があります。 { "$schema": "https://aka.ms/azure/static-web-apps-cli/schema", "configurations": { "swa-api-bicep-sample": { "appLocation": "frontend", "apiLocation": "api", "outputLocation": "out", "apiLanguage": "node", "appBuildCommand": "npm run build", "apiBuildCommand": "npm run build --if-present", "run": "npm run dev", "appDevserverUrl": "http://localhost:3000", "swaConfigLocation": "frontend/config/local" } } } フロントのみSWA CLIで起動してバックは起動済みのものを使用する こちらの構成ファイルでは、 swa start でフロントのリソースのみを立ち上げ、バックエンドのリソースは提供済みのものを使用します。こちらの利点としては、バックエンドのリソースのみを分離して立ち上げ直しができる点ですね。その代わり、実行には func start コマンドを別途実行してあげる必要があります。 { "$schema": "https://aka.ms/azure/static-web-apps-cli/schema", "configurations": { "swa-api-bicep-sample": { "appLocation": "frontend", "apiLocation": "api", "outputLocation": "out", "apiLanguage": "node", "appBuildCommand": "npm run build", "apiBuildCommand": "npm run build --if-present", "run": "npm run dev", "appDevserverUrl": "http://localhost:3000", "apiDevserverUrl": "http://localhost:7071", "swaConfigLocation": "frontend/config/local" } } } SWA設定ファイル(staticwebapp.config.json) こちらのファイルは、認証ルートの定義やSWA側でサーバー側で発生したエラーをキャッチしてルーティングを制御します。先ほどの swa-cli.config.json で swaConfigLocation を設定していましたが、これは開発環境と本番環境で二つのファイルを構成する必要があるためです。開発環境では、認証プロバイダーの設定をSWA CLIのエミュレーターで代用します。本番環境では、SWAの環境変数から認証プロバイダーに必要な情報を読み取って認証プロバイダーを構成します。 本番環境用の構成ファイルを使用してSWA CLIでローカルで立ち上げると認証時に失敗します。 SWA 設定ファイルは大部分がフロントのルーティング定義であるため、 frontend > config とディレクトリを定義してその中にディレクトリ単位で local と prod と分割して同名ファイルを保存します。 ./frontend └── config ├── local │ └── staticwebapp.config.json └── prod └── staticwebapp.config.json 開発環境用 SWAに認証されるとユーザーには自動的に「authenticated」と「anoymous」というロールが割り振られます 。SWA Configでは、ロールによって表示することができるルート制御というのがあり、今回であれば /protected ルートでは authenticated ロールにアクセスを許可しています。それ以外のルートに関しては、200で許可しています。もし未認証の方が /protected にアクセスした場合はSWA側で401エラーが発生して、自動的に認証画面にリダイレクトするように responseOverrdes で設定しています。ここをエラー用のページに設定しておけば、自動でエラー画面を表示するなんてこともできます。 platform の設定はAzure Functionsの設定になります。今回であればnode系を使用していたため設定しています。 { "routes": [ { "route": "/protected/*", "allowedRoles": ["authenticated"] }, { "route": "/*", "statusCode": 200 } ], "responseOverrides": { "401": { "redirect": "/.auth/login/github", "statusCode": 302 } }, "platform": { "apiRuntime": "node:18" } } 本番環境 大枠は開発環境と同じですが、こちらでは auth でカスタム認証プロバイダーの設定がされています。 設定方法は公式のこちらで言及 があります。こちらはSWAの環境変数から値を読み取っているため、SWA側の設定として GITHUB_CLIENT_ID と GITHUB_CLIENT_SECRET を設定してあげる必要があります。こちらは、Azureのリソース作成の手順の際に合わせて設定します。 { "auth": { "identityProviders": { "github": { "registration": { "clientIdSettingName": "GITHUB_CLIENT_ID", "clientSecretSettingName": "GITHUB_CLIENT_SECRET" } } } }, "routes": [ { "route": "/protected/*", "allowedRoles": ["authenticated"] }, { "route": "/*", "statusCode": 200 } ], "responseOverrides": { "401": { "redirect": "/.auth/login/github", "statusCode": 302 } }, "platform": { "apiRuntime": "node:18" } } フロントエンド + API結合確認 それでは、接続してテストを開始しましょう。これまでの設定が完了していれば、ルートディレクトリで以下のコマンドでエミュレーターが起動します。もし起動しない方は、swa-cli.config.jsonの設定を再度確認してください。 http://localhost:7071 ready で1分以上固まっている方は、apiディレクトリで func start コマンドを実行してください。 # swa-cli.config.jsonでの設定による起動 swa start 動作確認テストの実施 さて、ここまででフロントエンドとAPIの実装が完了しましたね!いよいよ統合テストの時間です。今回は正常系の動作確認に集中して進めていきます。Azure SWAの素晴らしいところは、認証周りの面倒な処理を自動でやってくれることなので、エラーハンドリングよりも「ちゃんと期待通りに動いているか」を確認することが重要ですね。 実際のテストでは、未認証状態と認証状態の両方で動作を確認していきます。特に認証フローがスムーズに動作するかどうかが、このプロジェクトの肝になる部分です。 未認証状態での動作確認 まずは未認証状態から確認していきましょう。ブラウザでシークレットモードを開くか、既存のセッションをクリアしてからテストを開始します。 トップページ( / )での確認項目 : ページにアクセスすると「未認証状態」と表示される 「サーバーからユーザー情報を取得」ボタンをクリックしても、ユーザー情報は取得できない(nullやundefinedが返される) 「GitHubでログイン」ボタンが表示され、クリックすると認証画面に遷移する 保護ページ( /protected )での確認項目 : URLを直接入力してアクセスを試みると、自動的に認証画面に遷移する この時点では、Azure SWAが未認証ユーザーを自動的に認証フローに誘導してくれるので、エラー画面が表示されることはありません。これがSWAの便利なところですね! 認証状態での動作確認 続いて、GitHub認証を完了した状態での動作を確認します。認証が成功すると、トップページにリダイレクトされるはずです。認証画面は以下のような画面になります。Providerに関しては、埋められた状態で開かれるはずです。UsernameはGitHub認証の場合はユーザー名になります。(表示名ではありません @以下です) トップページ( / )での確認項目 : ページ上部に「認証状態」と表示される 「サーバーからユーザー情報を取得」ボタンをクリックすると、GitHubから取得したユーザー情報が表示される 「保護されたページ」ボタンと「ログアウト」ボタンが表示される 保護ページ( /protected )での確認項目 : ページに正常にアクセスできる 「保護されたデータを取得」ボタンをクリックすると、サーバーサイドのAPIからダミーデータが取得される 認証が正しく動作していれば、Azure Functionsに送信される x-ms-client-principal ヘッダーにユーザー情報が含まれているので、APIから適切なレスポンスが返ってきます。 テスト実施時のポイント テストを実施する際に気をつけておきたいポイントをいくつか挙げておきますね。 Azure SWAの自動処理について : 未認証時の保護リソースへのアクセスや、 /api/protected-data への直接アクセスは、SWAの設定により自動的に認証画面にリダイレクトされます。そのため、401エラーが画面に表示されることはなく、ユーザーは常にスムーズな認証フローを体験できます。 セッション管理の確認 : ページをリロードしても認証状態が維持されることを確認してください。これはAzure SWAが内部的にセッション管理を行っているためです。 API呼び出しの動作 : 認証状態でのAPI呼び出しでは、Azure SWAが自動的に認証ヘッダーを付与してくれるので、フロントエンド側で特別な処理は不要です。これも開発者としてはとても楽な部分ですね。 今回のテストはシンプルですが、実際のプロダクションで必要な基本的な動作はすべてカバーできています。正常系がしっかり動作することを確認できれば、Azure SWAとNext.js、Azure Functionsの統合は成功です! インフラ構築(Bicep) これまでの章でローカルの開発は完了しました。ここからは、実際にAzure環境へのデプロイをするための準備を行います。具体的にはBicepとパラメーターファイルを作成してAzureのリソースの定義を行い、作成したファイルを使用してGitHubにデプロイに必要な変数の定義とAzureリソースを作成するbashファイルを用意します。 注意点:ここら先はAzure Static Web Appsに対してリソースの作成を行います。今回はStandardプランでリソースを作成するため、もし不安な方はFree版でのデプロイで練習をしておきましょう。別の環境を使って詳細にデプロイするためのガイドを書いているので、 こちらのブログ を参照して練習してみてください。 事前準備 Bicepファイルとパラメーターファイルの説明などはこちらで詳しく解説 しています。ぜひ一度お読みください。 こちらの章で目指す内容は以下になります。 Azure CLI:Azure Static Web AppsのリソースをStandardプランで作成する 手動:GitHub認証プロバイダーの設定完了 GitHub CLI:GitHub Enviromentにデプロイに必要な情報が設定されている 作成に当たって目指すディレクトリ構成は以下になります。 ./infra ├── bicep │ ├── main.bicep │ └── parameters.json └── scripts └── deploy.sh GitHubの設定 まずは今まで設定したリソースをGitHub上にpushしておきましょう。GitHub CLIで environment を設定するためには事前に environment を作成しておく必要があります。 production という名前で environment を作成してください。設定は 公式のドキュメントを参考に設定 してください。 値の登録自体は、GitHub CLI経由で実行することができます。ですが、Enviromentの作成はGitHub API経由でしか行うことができません。ちょっと設定がややこしいので、ここは手動で作成しています。 ここで取得するべき情報としてはリポジトリのURLになります。 Bicepテンプレート作成 main.bicep 詳細な設定に関しては公式リファレンスを参照してください。 Microsoft.Web/staticSites Microsoft.Web/staticSites/config @description('Static Web App名') param staticWebAppName string @description('デプロイするリージョン') param location string = resourceGroup().location @description('GitHubリポジトリのURL') param repositoryUrl string @description('デプロイ対象のブランチ') param branch string = 'main' @description('フロントエンドのソースフォルダパス') param appLocation string = 'frontend' @description('APIのソースフォルダパス') param apiLocation string = 'api' @description('ビルド出力フォルダパス') param outputLocation string = 'out' @description('GitHub OAuth App のClient ID') param githubClientId string @description('GitHub OAuth App のClient Secret') @secure() param githubClientSecret string // Static Web App リソース resource staticWebApp 'Microsoft.Web/staticSites@2024-11-01' = { name: staticWebAppName location: location sku: { name: 'Standard' tier: 'Standard' } properties: { repositoryUrl: repositoryUrl branch: branch buildProperties: { appLocation: appLocation apiLocation: apiLocation outputLocation: outputLocation skipGithubActionWorkflowGeneration: false } stagingEnvironmentPolicy: 'Enabled' } } // アプリケーション設定(GitHub OAuth用の環境変数) resource staticWebAppSettings 'Microsoft.Web/staticSites/config@2022-03-01' = { name: 'appsettings' parent: staticWebApp properties: { GITHUB_CLIENT_ID: githubClientId GITHUB_CLIENT_SECRET: githubClientSecret } } // アウトプット @description('Static Web Appsのエンドポイント') output appBaseUrl string = 'https://${staticWebApp.properties.defaultHostname}' @description('Static Web Appsの名前') output resourceName string = staticWebAppName @description('GitHubリポジトリのURL') output repositoryUrl string = repositoryUrl @description('フロントエンドのソースフォルダパス') output appLocation string = appLocation @description('APIのソースフォルダパス') output apiLocation string = apiLocation @description('ビルド出力フォルダパス') output outputLocation string = outputLocation 課金に重要なパラメーターとしては sku になります。ここをFreeにするかStandardにするかで課金が走るか決定します。(個人的には検証目的で建てたらすぐ消しましょう) staticWebAppSettings に関しては、SWAでカスタム認証プロバイダー(GitHub)を構成するのに必要な値を環境変数として設定しています。機密情報であるため、パラメーターファイルには記述せずに環境変数として定義して、デプロイ時のみ呼び出して埋め込むという方法で管理します。 パラメーターファイルで定義している内容をそのままアウトプットしている値が複数ありますが、こちらは後続のbashファイルでGitHub CLIを通してEnvriomentにValiableとして登録するために出力しています。 パラメータファイル設定 parameters.json { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { "staticWebAppName": { "value": "swa-api-deploy-bicep" }, "location": { "value": "East Asia" }, "repositoryUrl": { "value": "https://github.com/xxxxxxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxx" }, "branch": { "value": "main" }, "appLocation": { "value": "deploy/frontend" }, "apiLocation": { "value": "api" }, "outputLocation": { "value": "." } } } appLocation / apiLocation / outputLocation に関しては後続で設定するGitHub Actionsと連携した値になります。 repositoryUrl に関しては自分のリポジトリの値に変更してください。 デプロイ用bashスクリプト deploy.sh Bicepファイルとパラメーターファイルが完成したのでデプロイする準備が整いました。ですが、このままではAzure CLIのコマンドとGitHub CLIコマンドを別々にたたく必要があります。それを解消するためにデプロイの流れをbashファイルにまとめます。 まずは、ルートディレクトリに .env ファイルを作成してください。こちらのファイルは .git と同じディレクトリ(リポジトリルート)に配置するようにしてください。 GITHUB_CLIENT_ID=xxxxxxxxxxxxxxxxx GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxx GITHUB_ENVIRONMENT="production" RESOURCE_GROUP_NAME=swa-bicep-test GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET に関しては、まだ設定していないのでマスクしたままで大丈夫です。こちらの設定には、SWAをデプロイしてURLを確定させる必要があります。 これで.envが立派な機密情報になりました。.gitignoreに設定して間違ってもpushしないように気を付けましょう。 #!/bin/bash # エラー時に停止 set -e log_info() { echo -e "\033[1;34m$1\033[0m"; } log_success() { echo -e "\033[1;32m$1\033[0m"; } log_warning() { echo -e "\033[1;33m$1\033[0m"; } log_error() { echo -e "\033[1;31m$1\033[0m"; } DEPLOYMENT_NAME="swa-deployment-$(date +%Y%m%d-%H%M%S)" CONFIG_FILE="$(git rev-parse --show-toplevel)/.env" if [ -f "$CONFIG_FILE" ]; then echo "📁 設定ファイル読み込み: $CONFIG_FILE" source "$CONFIG_FILE" else echo "⚠ 設定ファイルが見つかりません: $CONFIG_FILE" echo " .env.example をコピーして設定してください" exit 1 fi # 必要な環境変数の確認(機密情報のみ) required_vars=("GITHUB_CLIENT_ID" "GITHUB_CLIENT_SECRET" "RESOURCE_GROUP_NAME") for var in "${required_vars[@]}"; do if [ -z "${!var}" ]; then echo "エラー: 環境変数 $var が設定されていません" exit 1 fi done # GitHub CLIがインストールされているかチェック if ! command -v gh &> /dev/null; then log_error "GitHub CLI (gh) がインストールされていません" log_error "インストール方法: https://cli.github.com/" exit 1 fi # GitHub CLIの認証チェック if ! gh auth status &> /dev/null; then log_error "GitHub CLIが認証されていません" log_error "認証方法: gh auth login" exit 1 fi # リポジトリのルートディレクトリかチェック if ! git rev-parse --is-inside-work-tree &> /dev/null; then log_error "Gitリポジトリ内で実行してください" exit 1 fi # 現在のリポジトリ情報を取得(デバッグ用) CURRENT_REPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null || echo "unknown") log_info "📍 対象リポジトリ: $CURRENT_REPO" # リソースグループ作成(存在しない場合) log_info "" log_info "📦 ローカル開発用リソースグループ確認・作成..." if ! az group show --name $RESOURCE_GROUP_NAME &> /dev/null; then log_info "🔧 リソースグループ作成中: $RESOURCE_GROUP_NAME" az group create --name $RESOURCE_GROUP_NAME --location $LOCATION log_success "✅ リソースグループ作成完了" else log_success "✅ リソースグループ既存: $RESOURCE_GROUP_NAME" fi echo "Azure Static Web Appをデプロイ中..." az deployment group validate \ --resource-group "$RESOURCE_GROUP_NAME" \ --template-file infra/bicep/main.bicep \ --parameters @infra/bicep/parameters.json \ --parameters githubClientId="$GITHUB_CLIENT_ID" \ --parameters githubClientSecret="$GITHUB_CLIENT_SECRET" # Bicepテンプレートでデプロイ(機密情報のみ環境変数から渡す) az deployment group create \ --resource-group "$RESOURCE_GROUP_NAME" \ --template-file infra/bicep/main.bicep \ --parameters @infra/bicep/parameters.json \ --parameters githubClientId="$GITHUB_CLIENT_ID" \ --parameters githubClientSecret="$GITHUB_CLIENT_SECRET" \ --name ${DEPLOYMENT_NAME} # 全outputを一度に取得 OUTPUTS=$(az deployment group show \ --resource-group "$RESOURCE_GROUP_NAME" \ --name "$DEPLOYMENT_NAME" \ --query "properties.outputs" -o json) echo "✅ デプロイ結果を取得しました" # 各値を抽出 APP_BASE_URL=$(echo "$OUTPUTS" | jq -r '.appBaseUrl.value') STATIC_WEB_APP_NAME=$(echo "$OUTPUTS" | jq -r '.resourceName.value') REPOSITORY_URL=$(echo "$OUTPUTS" | jq -r '.repositoryUrl.value') APP_LOCATION=$(echo "$OUTPUTS" | jq -r '.appLocation.value') API_LOCATION=$(echo "$OUTPUTS" | jq -r '.apiLocation.value') OUTPUT_LOCATION=$(echo "$OUTPUTS" | jq -r '.outputLocation.value') # 結果を表示 log_success "" log_success "Azure Static Web Appのデプロイが完了しました!" log_success "アプリのベースURL: $APP_BASE_URL" log_success "リソース名: $STATIC_WEB_APP_NAME" log_success "リポジトリURL: $REPOSITORY_URL" log_success "アプリの場所: $APP_LOCATION" log_success "APIの場所: $API_LOCATION" log_success "出力の場所: $OUTPUT_LOCATION" # === 新機能: デプロイトークンの取得とGitHub Environment設定 === log_info "" log_info "🔑 Azure Static Web Appsのデプロイトークンを取得中..." # デプロイトークンを取得 DEPLOY_TOKEN=$(az staticwebapp secrets list \ --name "$STATIC_WEB_APP_NAME" \ --resource-group "$RESOURCE_GROUP_NAME" \ --query "properties.apiKey" -o tsv) if [ -z "$DEPLOY_TOKEN" ]; then log_error "デプロイトークンの取得に失敗しました" exit 1 fi log_success "✅ デプロイトークンを取得しました" # GitHub環境の作成と設定 log_info "" log_info "🌍 GitHub環境 '$GITHUB_ENVIRONMENT' を設定中..." # 注意: GitHub CLIは現在のGitリポジトリを自動認識します # このスクリプトはGitリポジトリ内で実行する必要があります # Environment変数の設定 log_info "" log_info "📝 Environment変数を設定中..." # app_location if gh variable set APP_LOCATION --body "$APP_LOCATION" --env "$GITHUB_ENVIRONMENT" 2>/dev/null; then log_success "✅ 変数 'app_location' を設定: $APP_LOCATION" else log_error "❌ 変数 'app_location' の設定に失敗しました" fi # api_location if gh variable set API_LOCATION --body "$API_LOCATION" --env "$GITHUB_ENVIRONMENT" 2>/dev/null; then log_success "✅ 変数 'api_location' を設定: $API_LOCATION" else log_error "❌ 変数 'api_location' の設定に失敗しました" fi # output_location if gh variable set OUTPUT_LOCATION --body "$OUTPUT_LOCATION" --env "$GITHUB_ENVIRONMENT" 2>/dev/null; then log_success "✅ 変数 'output_location' を設定: $OUTPUT_LOCATION" else log_error "❌ 変数 'output_location' の設定に失敗しました" fi # デプロイトークンをシークレットに設定 log_info "" log_info "🔐 デプロイトークンをシークレットに設定中..." # 注意: gh secret setは現在のGitリポジトリの指定した環境にシークレットを設定します if gh secret set AZURE_STATIC_WEB_APPS_API_TOKEN --body "$DEPLOY_TOKEN" --env "$GITHUB_ENVIRONMENT" 2>/dev/null; then log_success "✅ シークレット 'deploy_token' を設定しました" else log_error "❌ シークレット 'deploy_token' の設定に失敗しました" log_warning "⚠ 手動で設定してください:" log_warning " リポジトリ設定 > Environments > $GITHUB_ENVIRONMENT > Secrets" log_warning " シークレット名: deploy_token" log_warning " 値: [デプロイトークン]" fi # 最終結果サマリー log_success "" log_success "🎉 すべての設定が完了しました!" log_success "" log_success "📋 設定内容サマリー:" log_success " - Azure Static Web App: $STATIC_WEB_APP_NAME" log_success " - アプリURL: $APP_BASE_URL" log_success " - GitHub環境: $GITHUB_ENVIRONMENT" log_success " - 設定された変数:" log_success " • app_location: $APP_LOCATION" log_success " • api_location: $API_LOCATION" log_success " • output_location: $OUTPUT_LOCATION" log_success " - 設定されたシークレット:" log_success " • deploy_token: [設定済み]" log_success "" log_success "🚀 GitHub Actionsでのデプロイが可能になりました!" 長いbashファイルですが、以下のステップを順次実行しています。 環境変数の取得 各種環境の確認 Bicepを使用したデプロイ検証・実行 Azureリソースから情報取得 GitHub CLIを経由したSecret・Valiableの設定 リザルト表示 各コマンドに関しての詳細な説明はこちら で行っています。権限を振って実行してみてください。 chmod +x ./infra/scripts/deploy.sh ./infra/scripts/deploy.sh # リザルトで以下が出たら成功!! #📋 設定内容サマリー: # - Azure Static Web App: swa-api-deploy-bicep # - アプリURL: https://blue-bay-01b32fb00.2.azurestaticapps.net # - GitHub環境: production # - 設定された変数: # • app_location: deploy/frontend # • api_location: api # • output_location: . # - 設定されたシークレット: # • deploy_token: [設定済み] 1分ぐらいで実行されると思います。リザルトが表示されたら、「アプリURL」にアクセスしてみてください。Welcomeページが表示されれば成功です。 環境作成 無事デプロイが確認出来たら、次は認証プロバイダーの設定を行いましょう。 GitHub > Settings > Developer Settingsにアクセス してください。 リポジトリのSettingsではなくUser Settingsのほうにアクセスしてね! OAuth Appsの作成を行いましょう。 設定項目としては、 設定項目 値 Application name 自由に決めてください Homepage URL アプリURL Authorization callback URL アプリURL/.auth/login/github/callback Enable Device Flow True 設定をするとClient IDとClient Secretが表示されます。そちらの値を .env ファイルに入れて再度 deploy.sh を起動させましょう。 ./infra/scripts/deploy.sh # リザルトで以下が出たら成功!! #📋 設定内容サマリー: # - Azure Static Web App: swa-api-deploy-bicep # - アプリURL: https://blue-bay-01b32fb00.2.azurestaticapps.net # - GitHub環境: production # - 設定された変数: # • app_location: deploy/frontend # • api_location: api # • output_location: . # - 設定されたシークレット: # • deploy_token: [設定済み] これで、SWAの環境変数に GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET が設定されました。もし不安な方はAzure Portalから確認しましょう。 CI/CD構築(GitHub Actions) ここでは、GitHub Actionsの設定を行っています。前提条件として、GitHubリポジトリのEnviroment(production)に値が設定済みである必要があります。 Name 説明 AZURE_STATIC_WEB_APPS_API_TOKEN SWAのデプロイトークン API_LOCATION APIのディレクトリ APP_LOCATION アプリのディレクトリ OUTPUT_LOCATION ビルドファイルの位置 今回作成するファイルのディレクトリ構成は以下になります。 ./.github └── workflows └── deploy.yaml ワークフロー設計・アーキテクチャ ワークフローでは、フロントビルドとデプロイを分離しています。フロントのビルドと並行してAPIのテストを実行するように設計しています。 build-frontend : フロントエンドの並列ビルド test-api : APIの並列ビルド deploy-swa : アーティファクトを使用したデプロイ 視覚化した図を以下に示します。 全体ワークフロー こちらのファイルをコピー&ペーストしてGitHub >Actionsのタブから手動実行しましょう。設定が完璧であれば、無事デプロイされるかと思います。 name: Build and Deploy to Azure Static Web Apps on: workflow_dispatch: jobs: # フロントエンドビルドジョブ build-frontend: runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Setup Node.js for Frontend uses: actions/setup-node@v4 with: node-version: "18" cache: "npm" cache-dependency-path: "frontend/package-lock.json" - name: Install Frontend Dependencies working-directory: ./frontend run: npm ci - name: Build Frontend working-directory: ./frontend run: npm run build - name: Upload Frontend Build Artifacts uses: actions/upload-artifact@v4 with: name: frontend-build path: | frontend/out/ retention-days: 1 # APIビルドジョブ test-api: runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Setup Node.js for API uses: actions/setup-node@v4 with: node-version: "18" cache: "npm" cache-dependency-path: "api/package-lock.json" - name: Install API Dependencies working-directory: ./api run: npm install - name: Build API working-directory: ./api run: npm run build - name: Run API Tests working-directory: ./api run: npm run test # Azure Static Web Appsデプロイジョブ deploy-swa: needs: [build-frontend, test-api] runs-on: ubuntu-latest environment: production env: APP_LOCATION: ${{ vars.APP_LOCATION || 'deploy/frontend' }} OUTPUT_LOCATION: ${{ vars.OUTPUT_LOCATION || '.' }} API_LOCATION: ${{ vars.API_LOCATION || 'api' }} steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Download Frontend Artifacts uses: actions/download-artifact@v4 with: name: frontend-build path: ./deploy/frontend - name: Copy Static Web App Configuration run: | cp frontend/config/prod/staticwebapp.config.json deploy/frontend/ - name: check working-directory: ./deploy/frontend/ run: | ls -la cat staticwebapp.config.json - name: Deploy to Azure Static Web Apps uses: Azure/static-web-apps-deploy@v1 with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }} action: "upload" app_location: ${{ env.APP_LOCATION }} api_location: ${{ env.API_LOCATION }} output_location: "${{ env.OUTPUT_LOCATION }}" skip_app_build: true skip_api_build: false フロントエンドビルドジョブ こちらでは、フロントエンドのビルドを行い結果をArtifactとして保存しています。 actions/setup-node@v4 を利用してnode_modulesをキャッシュしています。 build-frontend: runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Setup Node.js for Frontend uses: actions/setup-node@v4 with: node-version: "18" cache: "npm" cache-dependency-path: "frontend/package-lock.json" - name: Install Frontend Dependencies working-directory: ./frontend run: npm ci - name: Build Frontend working-directory: ./frontend run: npm run build - name: Upload Frontend Build Artifacts uses: actions/upload-artifact@v4 with: name: frontend-build path: | frontend/out/ retention-days: 1 APIテストジョブ こちらでは、バックエンドのビルドを行いテストを実行しています。今回はテストが失敗したら通知を行う等の対応を行っていませんが、本格運用ではここにテスト関連の処理をまとめて切り出すことができます。 actions/setup-node@v4 を利用してnode_modulesをキャッシュしています。 # APIビルドジョブ test-api: runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Setup Node.js for API uses: actions/setup-node@v4 with: node-version: "18" cache: "npm" cache-dependency-path: "api/package-lock.json" - name: Install API Dependencies working-directory: ./api run: npm install - name: Build API working-directory: ./api run: npm run build - name: Run API Tests working-directory: ./api run: npm run test SWAデプロイジョブ実装 こちらでは、以下の手順を順次実行しています。 「フロントエンドビルドジョブ」で作成した静的ファイルを復元 内部に本番環境用ルート定義を埋め込み Azure/static-web-apps-deploy@v1 を使用してデプロイ フロントエンドはビルド済みファイルをデプロイのみ任せて実行 バックエンドはビルドとデプロイを任せて実行 # Azure Static Web Appsデプロイジョブ deploy-swa: needs: [build-frontend, test-api] runs-on: ubuntu-latest environment: production env: APP_LOCATION: ${{ vars.APP_LOCATION || 'deploy/frontend' }} OUTPUT_LOCATION: ${{ vars.OUTPUT_LOCATION || '.' }} API_LOCATION: ${{ vars.API_LOCATION || 'api' }} steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Download Frontend Artifacts uses: actions/download-artifact@v4 with: name: frontend-build path: ./deploy/frontend - name: Copy Static Web App Configuration run: | cp frontend/config/prod/staticwebapp.config.json deploy/frontend/ - name: check working-directory: ./deploy/frontend/ run: | ls -la cat staticwebapp.config.json - name: Deploy to Azure Static Web Apps uses: Azure/static-web-apps-deploy@v1 with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }} action: "upload" app_location: ${{ env.APP_LOCATION }} api_location: ${{ env.API_LOCATION }} output_location: "${{ env.OUTPUT_LOCATION }}" skip_app_build: true skip_api_build: false Copy Static Web App Configuration では、本番環境用の staticwebapp.config.json を復元したフロントのビルド済みディレクトリにコピーしています。 Azure/static-web-apps-deploy@v1 では、 skip_app_build と skip_api_build でアクション内のビルドプロセスを制御しています。 そもそもこのアクションは、アプリの構成を自動で読み取りビルドとデプロイを行っています。フロントエンドでルーティングファイルの制御を行うためにフロントエンドはビルドをスキップしてデプロイだけで使用しています。 GitHub Actions効率化紹介 今回のワークフローでは、ビルド時間の短縮とリソース効率化を重視した設計を行いました。特に並列処理とキャッシュ戦略により、従来の逐次処理と比較して大幅な時間短縮を実現できます。 独立したNodeキャッシュ戦略 各ジョブで異なる cache-dependency-path を指定することで、フロントエンドとAPIで独立したキャッシュを管理できます。これにより、一方の依存関係が変更されても他方のキャッシュが無効化されることを防げます。 # フロントエンドジョブでのキャッシュ設定 - name: Setup Node.js for Frontend uses: actions/setup-node@v4 with: node-version: "18" cache: "npm" cache-dependency-path: "frontend/package-lock.json" # APIジョブでのキャッシュ設定 - name: Setup Node.js for API uses: actions/setup-node@v4 with: node-version: "18" cache: "npm" cache-dependency-path: "api/package-lock.json" cache-dependency-path を明示的に指定することで、GitHub Actionsはそのファイルのハッシュをキャッシュキーとして使用します。フロントエンドとAPIで異なるpackage-lock.jsonを参照することで、以下のメリットがあります: フロントエンドの依存関係のみ変更時:APIのキャッシュはそのまま利用 APIの依存関係のみ変更時:フロントエンドのキャッシュはそのまま利用 両方変更時:それぞれ個別にキャッシュを再構築 フロントエンドジョブでは npm ci を使用しています。これはCI環境向けの最適化されたコマンドで、package-lock.jsonを厳密に遵守し、高速インストールを実現します。 アーティファクト活用による並列処理最適化 アーティファクトを使用することで、ビルドジョブとデプロイジョブを完全に分離できます。これにより、デプロイ時にビルドを再実行する必要がなくなり、処理時間を大幅に短縮できます。 # ビルドジョブでのアーティファクト保存 - name: Upload Frontend Build Artifacts uses: actions/upload-artifact@v4 with: name: frontend-build path: | frontend/out/ retention-days: 1 # デプロイジョブでのアーティファクト復元 - name: Download Frontend Artifacts uses: actions/download-artifact@v4 with: name: frontend-build path: ./deploy/frontend 今回の構成では、 build-frontend と test-api が並列で実行されます。これにより、従来の逐次処理と比較して大幅な時間短縮が期待できます。 将来的なテストへの拡張性 段階的テスト導入への対応 現在のAPIテストジョブは、将来的なテスト拡張を見据えた設計になっています。 - name: Build API working-directory: ./api run: npm run build - name: Run API Tests working-directory: ./api run: npm run test テスト失敗時の処理拡張 本格運用時には、テスト失敗時の通知機能を追加できます。以下のような拡張が可能です: - name: Run API Tests working-directory: ./api run: npm run test continue-on-error: false # テスト失敗時にワークフローを停止 # 将来的な拡張例 - name: Notify Test Failure if: failure() uses: actions/github-script@v6 with: script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: 'APIテストが失敗しました。詳細はワークフローログを確認してください。' }) 並列テスト実行の利点 フロントエンドビルドとAPIテストを並列実行することで、以下のメリットがあります: 時間効率 : 最も時間のかかる処理に全体時間が依存 早期発見 : APIテストの失敗を早期に検出 リソース効率 : GitHub Actionsの同時実行枠を有効活用 シークレット・環境変数の効率的管理 GitHub Actionsでは、Secretsと環境変数を適切に分離して管理することが重要です。 environment: production env: APP_LOCATION: ${{ vars.APP_LOCATION || 'deploy/frontend' }} OUTPUT_LOCATION: ${{ vars.OUTPUT_LOCATION || '.' }} API_LOCATION: ${{ vars.API_LOCATION || 'api' }} Secrets(機密情報)には AZURE_STATIC_WEB_APPS_API_TOKEN などのデプロイトークンを、Variables(環境固有設定)には APP_LOCATION などのディレクトリ設定を使い分けます。 本番環境での動作確認 お疲れさまでした!ここまでで無事にAzure SWAのデプロイが完了しましたね。でも、ここからが本当の勝負です。実際に本番環境で全ての機能が期待通りに動作するかを確認していきましょう。 私も最初の頃は「ローカルで動いてるから大丈夫だろう」と思っていましたが、本番環境では思わぬ設定の違いでハマることがよくありました。特に認証周りは環境による差が出やすい部分なので、しっかりと検証していきますね。 もし何かうまく動作しない部分があった場合は、まずはブラウザの開発者ツールでエラーがないか確認して、必要に応じてAzure Portalでのログ確認も行ってみてください。認証周りは環境設定に依存する部分が多いので、OAuth Appの設定やSWAの環境変数も再度チェックしてみると良いでしょう。 未認証状態での動作確認 シークレットモードやプライベートブラウジングで本番URLにアクセスしてみてください。まずはトップページ( / )から確認します。 期待される動作: ページ上部に「未認証状態」と表示される 「GitHubでログイン」ボタンが表示される 「サーバーからユーザー情報を取得」ボタンをクリックしても、ユーザー情報は取得できない( isAuthenticated: false が返される) 次に、保護されたページへの直接アクセスを試してみます。URLバーに /protected を入力してアクセスしてください。 期待される動作: 自動的にGitHub認証画面にリダイレクトされる エラー画面が表示されることはない これがAzure SWAの素晴らしいところですね。設定ファイル( staticwebapp.config.json )で定義したルート保護が自動的に働いて、未認証ユーザーを認証フローに誘導してくれます。 GitHub認証の実行 「GitHubでログイン」ボタンをクリックして、実際の認証フローを確認します。 認証フローの流れ: ボタンクリックで /.auth/login/github にリダイレクト GitHub認証画面が表示される GitHubでの認証完了後、元のページにリダイレクト GitHub認証画面では、作成したOAuth Appの情報が表示されます。App nameやAuthorization callback URLが正しく設定されていることを確認してください。 認証完了後は、トップページに戻ってきて以下のような変化が確認できるはずです: ページ上部に「認証済み」と表示される ユーザー情報(GitHubのユーザー名など)が表示される 「保護されたページへ」「ログアウト」ボタンが表示される セッション管理の確認 認証状態でページをリロードしても、認証状態が維持されることを確認してください。これはAzure SWAが内部的にセッション管理を行っているためです。 また、別タブで同じサイトを開いても、認証状態が共有されることも確認してみましょう。 API動作テスト 認証が正しく動作することが確認できたら、次はAPIの動作を確認していきます。 user-infoエンドポイントのテスト 認証済み状態で「サーバーからユーザー情報を取得」ボタンをクリックしてください。 期待される結果: { "userId": "github|あなたのGitHubユーザーID", "name": "あなたのGitHubユーザー名", "email": "あなたのGitHubユーザー名", "provider": "github", "roles": ["authenticated"], "isAuthenticated": true } これで、Azure SWAからAzure Functionsに認証ヘッダー( x-ms-client-principal )が正しく送信されて、API側で適切にデコードされていることが確認できます。 ブラウザの開発者ツールのネットワークタブでも確認してみてください。 /api/user-info へのリクエストが200で成功していて、上記のようなレスポンスが返っていることが確認できるはずです。 未認証状態でのuser-infoテスト シークレットモードで未認証状態でも同じボタンをクリックしてみてください。 期待される結果: { "userId": null, "name": null, "email": null, "provider": null, "roles": [], "isAuthenticated": false } このAPIは認証状態を問わずアクセス可能な設計になっているので、未認証でもエラーにならずに適切なレスポンスが返ることを確認できます。 protected-dataエンドポイントのテスト 保護されたページ( /protected )にアクセスして、「保護されたデータを取得」ボタンをクリックしてください。 期待される結果: { "userId": "github|あなたのGitHubユーザーID", "message": "こんにちは、ユーザーgithub|あなたのGitHubユーザーIDさん!", "timestamp": "2024-12-15T15:45:30.000Z", "userNumber": 数値 } userNumber は、ユーザーIDをベースにした簡単なシード値から生成される0-999の範囲の数値です。同じユーザーなら常に同じ値が返されることも確認してみてください。 未認証でのprotected-dataアクセステスト これは少し技術的なテストですが、直接APIエンドポイントにアクセスしてみましょう。 シークレットモードで https://あなたのサイトURL/api/protected-data に直接アクセスしてください。 期待される動作: 401エラーが返される または、Azure SWAのルート保護により認証画面にリダイレクトされる 実際には、SWAの設定によってはAPI直接アクセスも認証フローに誘導される可能性があります。どちらの動作でも、未認証ユーザーがデータにアクセスできないことが重要です。 セキュリティチェック 最後に、セキュリティ面での動作を確認していきます。 ルート保護の確認 未認証状態で以下のURLに直接アクセスしてみてください: /protected /protected/任意のパス どちらも認証画面にリダイレクトされることを確認してください。これは staticwebapp.config.json で設定した以下のルールが動作しているためです: { "route": "/protected/*", "allowedRoles": ["authenticated"] } 認証情報の適切な伝達 開発者ツールのネットワークタブで、認証済み状態でのAPI呼び出しを確認してください。 重要なポイント: API呼び出し時に、フロントエンド側で特別な認証ヘッダーを設定していない Azure SWAが自動的に x-ms-client-principal ヘッダーを付与している このヘッダーは開発者ツールでは見えないが、サーバー側では受信できている これがAzure SWAの大きなメリットの一つですね。フロントエンド側で複雑な認証処理を書く必要がなく、SWAが自動的に認証情報をバックエンドに伝達してくれます。 セッション継続性の確認 認証済み状態で以下を試してみてください: ページのリロード 別タブでの同サイトアクセス ブラウザを閉じて再度開く(セッションストレージのテスト) 通常のWebアプリケーションと同様に、ブラウザを閉じるまでは認証状態が維持されることを確認してください。 ログアウト機能の確認 「ログアウト」ボタンをクリックして、正常にログアウトできることを確認してください。 期待される動作: /.auth/logout にリダイレクト 認証状態がクリアされる トップページに戻り、未認証状態の表示になる お疲れさまでした!これで本番環境での動作確認は完了です。すべての機能が期待通りに動作していれば、Azure SWA + Next.js + Azure Functionsの認証統合システムが正常に稼働していることが確認できました。 リソースのクリーナップ 今回はAzure SWAをStandardプランで使用しています。こちらは動かしていれば、課金が走るようになっています。検証が確認できたら、心苦しいですがクリーナップしましょう。 Azure Portalからの削除でもよいのですが、せっかくなのでクリーナップ用のbashスクリプトを作成しました。bashスクリプト化しておくことのメリットはコマンド一つで環境の作成・削除ができる点です。GitHubのOAuth Appsの設定とEnvironmentの作成は手動ですが、完全自動化ではないですけどね… クリーナップスクリプト #!/bin/bash # エラー時に停止 set -e log_info() { echo -e "\033[1;34m$1\033[0m"; } log_success() { echo -e "\033[1;32m$1\033[0m"; } log_warning() { echo -e "\033[1;33m$1\033[0m"; } log_error() { echo -e "\033[1;31m$1\033[0m"; } CONFIG_FILE="$(git rev-parse --show-toplevel)/.env" 2>/dev/null || CONFIG_FILE=".env" if [ -f "$CONFIG_FILE" ]; then log_info "📁 設定ファイル読み込み: $CONFIG_FILE" source "$CONFIG_FILE" else log_error "⚠ 設定ファイルが見つかりません: $CONFIG_FILE" log_error " .env.example をコピーして設定してください" exit 1 fi # 必要な環境変数の確認 required_vars=("RESOURCE_GROUP_NAME" "STATIC_WEB_APP_NAME") for var in "${required_vars[@]}"; do if [ -z "${!var}" ]; then log_error "エラー: 環境変数 $var が設定されていません" exit 1 fi done log_info "🎯 削除対象: $STATIC_WEB_APP_NAME (リソースグループ: $RESOURCE_GROUP_NAME)" # Azure CLIがインストールされているかチェック if ! command -v az &> /dev/null; then log_error "Azure CLI (az) がインストールされていません" log_error "インストール方法: https://docs.microsoft.com/cli/azure/install-azure-cli" exit 1 fi # Azure CLIの認証チェック if ! az account show &> /dev/null; then log_error "Azure CLIが認証されていません" log_error "認証方法: az login" exit 1 fi # リソースグループの存在確認 log_info "" log_info "📦 リソースグループの確認..." if ! az group show --name $RESOURCE_GROUP_NAME &> /dev/null; then log_warning "⚠ リソースグループ '$RESOURCE_GROUP_NAME' が見つかりません" log_warning " 削除する必要がありません" exit 0 else log_success "✅ リソースグループ確認: $RESOURCE_GROUP_NAME" fi # Static Web Appリソースの存在確認 log_info "" log_info "🔍 Azure Static Web App リソースを確認中..." # 指定されたリソースの存在確認 if az staticwebapp show \ --name "$STATIC_WEB_APP_NAME" \ --resource-group "$RESOURCE_GROUP_NAME" \ --output none 2>/dev/null; then # リソース詳細の取得 SWA_DETAILS=$(az staticwebapp show \ --name "$STATIC_WEB_APP_NAME" \ --resource-group "$RESOURCE_GROUP_NAME" \ --query "{name:name, defaultHostname:defaultHostname, sku:sku.name, location:location}" \ -o json) log_success "✅ 削除対象リソースが見つかりました:" echo "$SWA_DETAILS" | jq -r '" • 名前: \(.name)"' echo "$SWA_DETAILS" | jq -r '" • URL: \(.defaultHostname)"' echo "$SWA_DETAILS" | jq -r '" • プラン: \(.sku)"' echo "$SWA_DETAILS" | jq -r '" • リージョン: \(.location)"' RESOURCE_EXISTS=true else log_warning "⚠ 指定されたAzure Static Web App '$STATIC_WEB_APP_NAME' が見つかりません" log_warning " リソースグループ: $RESOURCE_GROUP_NAME" log_info "削除する必要がありません" exit 0 fi # 確認プロンプト log_warning "" log_warning "⚠ 以下の操作を実行します:" log_warning " 1. Azure Static Web App '$STATIC_WEB_APP_NAME' の削除" log_warning " 2. リソースグループ '$RESOURCE_GROUP_NAME' は保持します" log_warning " 3. GitHub環境設定は保持します" log_warning "" read -p "続行しますか? (y/N): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then log_info "操作をキャンセルしました" exit 0 fi # === Azure Static Web App リソースの削除 === log_info "" log_info "🗑 Azure Static Web App リソースを削除中..." log_info "🔧 削除中: $STATIC_WEB_APP_NAME" if az staticwebapp delete \ --name "$STATIC_WEB_APP_NAME" \ --resource-group "$RESOURCE_GROUP_NAME" \ --yes 2>/dev/null; then log_success "✅ 削除完了: $STATIC_WEB_APP_NAME" else log_error "❌ 削除失敗: $STATIC_WEB_APP_NAME" exit 1 fi log_success "✅ Azure Static Web App リソースの削除が完了しました" # === 削除完了サマリー === log_success "" log_success "🎉 クリーンアップが完了しました!" log_success "" log_success "📋 実行内容サマリー:" log_success " ✅ Azure Static Web App削除: $STATIC_WEB_APP_NAME" log_success " ✅ リソースグループ保持: $RESOURCE_GROUP_NAME" log_success " ✅ GitHub環境設定保持: 変更なし" log_success "" log_success "📌 注意事項:" log_success " • リソースグループ '$RESOURCE_GROUP_NAME' は保持されています" log_success " • GitHub環境設定はそのまま残っているので、再デプロイ可能です" log_success "" log_success "🚀 Azure リソースのクリーンアップ完了!" クリーナップスクリプトの実行 作成時と同じく、環境変数を .env ファイルから読み込んで削除処理を実行します。既にdeployスクリプト実行時に .env ファイルは設定済みのはずなので、そのまま使用できます。 chmod +x ./infra/scripts/cleanup.sh ./infra/scripts/cleanup.sh 実行すると、以下のような確認が表示されます: 🎯 削除対象: swa-api-deploy-bicep (リソースグループ: swa-bicep-test) ⚠ 以下の操作を実行します: 1. Azure Static Web App 'swa-api-deploy-bicep' の削除 2. リソースグループ 'swa-bicep-test' は保持します 3. GitHub環境設定は保持します 続行しますか? (y/N): y を入力すると削除処理が開始されます。 削除される内容と保持される内容 削除されるもの: Azure Static Web Appリソース 関連するManaged Functions カスタム認証プロバイダー設定 保持されるもの: リソースグループ(他のリソースがある可能性を考慮) GitHub Environment設定(再デプロイ時に再利用可能) GitHub OAuth App(再利用可能) 完全削除を希望する場合 もし完全にクリーンな状態に戻したい場合は、以下も手動で削除してください: Azure側: リソースグループ全体(他にリソースがない場合) 必要に応じて Azure CLI の認証情報もクリア GitHub側: OAuth App(Settings > Developer settings > OAuth Apps から削除) Environment設定(リポジトリの Settings > Environments から削除) これで、課金を気にすることなく安心して検証を終了できますね。再度環境を作りたい場合は、deployスクリプトを実行すれば簡単に復旧できるのも、Infrastructure as Codeのメリットです! まとめ 今回は Azure Static Web Apps と Azure Functions を使った Next.js アプリケーションの認証 API 統合について、DevContainer での開発環境構築から本番デプロイまでの完全なワークフローを詳しく見てきました。 技術統合のポイント この構成の最大の魅力は、 フロントエンドとバックエンドを統一プラットフォームで管理できる点 にあります。Azure SWA が提供する認証機能と Azure Functions の API を組み合わせることで、複雑な認証フローを比較的シンプルに実装できました。特に x-ms-client-principal ヘッダーを使った認証情報の受け渡しは、SWA 独自の仕組みとして理解しておくと実装がスムーズになります。 開発体験の向上 DevContainer を使った開発環境は、チーム開発での環境差異を解消する大きなメリットがありました。Azure CLI や SWA CLI、Functions Core Tools がすべて統一環境で利用でき、ローカルでの統合テストも swa start コマンド一つで実行できる点は、開発効率を大幅に向上させます。 運用面での実用性 GitHub Actions を使った CI/CD パイプラインでは、 フロントエンドビルドと API テストを並列実行 することで、デプロイ時間の短縮を実現しました。また、Bicep を使った IaC により、インフラの再現性と管理性も確保できています。Standard プランは課金が発生しますが、カスタム認証プロバイダーや SLA が必要な本格的なプロジェクトには必須の選択肢です。 コメント ここまで出来たら、Azure Static Web Appsの基本は一通り理解できたかともいます。あとは運用の要望に合わせてチューニングするエンタープライズの領域に入っていきます。Bicepを使用して人間の手が入る可能性を減らせば減らすほど、問題が少なくなると思うので積極的にスクリプト化できるものはしていきましょう! 長々とした記事ですがお疲れ様でした!! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Azure SWA×Next.js認証API統合を実践解説【DevContainer〜本番まで】 first appeared on SIOS Tech. Lab .
はじめに こんにちはPSの佐々木です GitHub Copilot、ChatGPT、Claude等の生成AIツールは、現代のソフトウェア開発において不可欠な存在となっています。しかし、これらの生成AIが提案するコードには、意図せずライセンス違反を引き起こす可能性が潜んでいます。本記事では、生成AI開発におけるライセンス違反リスクと、SCANOSSを活用した効果的な対策について解説します。 生成AI開発におけるライセンス違反リスク 生成AIが学習するオープンソースコード 生成AIは膨大なオープンソースコードから学習しており、その中には様々なライセンスが適用されたコードが含まれています。AI が学習データから再現するコードには、以下のようなリスクが存在します: 意図しないライセンス違反 生成AIが提案したコードが、GPLやAGPLライセンスの既存コードと酷似している可能性 開発者が気づかないうちに、商用製品にコピーレフトライセンスのコードを組み込んでしまうリスク 従来のライブラリ依存関係では発見できない、コードスニペットレベルでの違反 企業が直面する課題 ライセンス違反リスクを恐れて、生成AIツールの導入を躊躇する企業が増加 開発効率向上のメリットと、法的リスクの間でのジレンマ 既存のライセンス検査ツールでは検出できない新しいタイプのリスク 具体的なリスクシナリオ ケース1: コードスニペットの意図しない複製 # 生成AIが提案したコードの例 def quicksort(arr): if len(arr) <= 1: return arr pivot = arr[len(arr) // 2] left = [x for x in arr if x < pivot] middle = [x for x in arr if x == pivot] right = [x for x in arr if x > pivot] return quicksort(left) + middle + quicksort(right) このようなコードが、実際にはGPLライセンスの既存プロジェクトから学習されている可能性があります。 ケース2: アルゴリズムの実装パターン 特定のアルゴリズムの実装において、生成AIが学習したコピーレフトライセンスのコードと同じパターンを再現してしまうケース。 主要なOSSライセンスと違反リスク パーミッシブライセンス(低リスク) MIT License 商用利用可能、制限が最小限 著作権表示のみ必要 生成AIで再現されても比較的リスクは低い Apache License 2.0 特許権の明確な規定あり 変更箇所の明記が必要 企業利用において安全性が高い BSD License シンプルで制限が少ない 商用利用に適している コピーレフトライセンス(高リスク) GPL (GNU General Public License) 最も注意が必要なライセンス 派生物もGPLライセンスでの配布が必要 商用製品での利用時、全ソースコードの開示義務 生成AIが無意識に組み込むと重大なリスク AGPL (GNU Affero General Public License) GPLよりもさらに厳しい条件 SaaS提供時でもソースコード開示が必要 クラウドサービス開発で特に危険 LGPL (GNU Lesser General Public License) ライブラリ自体の変更時のみソースコード開示 動的リンクの場合は比較的安全 弱いコピーレフトライセンス(中リスク) MPL (Mozilla Public License) ファイルレベルでのコピーレフト 変更したファイルのみソースコード開示 他のライセンスコードとの組み合わせは可能 SCANOSSによる生成AIコードの検知と対策 SCANOSSの独自技術 コードスニペットレベルでの検出 SCANOSSは、従来のライセンス検査ツールでは困難だった、わずか数行のコードスニペットからでも元のOSSコンポーネントとライセンスを特定できます: フィンガープリンティング技術 : コードの構造的特徴を分析し、部分的なコードからも元のライセンスを特定 機械学習による高精度マッチング : 大量のオープンソースコードデータベースから学習したパターンマッチング リアルタイム分析 : 開発中にリアルタイムでライセンス確認が可能 生成AIコードの包括的スキャン 開発ワークフロー統合 # 生成AIで作成されたコードの包括的スキャン scanoss scan --ai-generated-code --include-snippets /path/to/project # 特定のライセンスポリシーを適用したスキャン scanoss scan /path/to/project --policy-file corporate-policy.json --fail-on-license GPL-3.0 # CI/CDパイプラインでの自動スキャン scanoss scan . --output-format spdx --include-ai-analysis 検出できる違反パターン 生成AIによる既存コードの部分的な複製 Stack Overflowなどからコピーしたコードスニペット 依存関係に含まれないコードレベルでのライセンス違反 バイナリファイルに埋め込まれたコードの検出 実践的な生成AI開発でのライセンス管理 開発プロセスへの組み込み 1. コード生成時の即座チェック # 開発者のワークフロー例 # 1. 生成AIでコードを生成 # 2. SCANOSSで即座にライセンスチェック # 3. 問題があれば代替案を検討 # 4. 安全なコードのみを採用 2. CI/CDパイプライン統合 プルリクエスト時の自動スキャン 本番デプロイ前の包括的チェック ライセンス違反の検出時の自動アラート 3. 継続的な監視 新しい依存関係の追加時の自動チェック 既存コードの定期的な再スキャン ライセンスポリシーの更新時の全体チェック 企業でのライセンスポリシー策定 リスクレベル別の対応 高リスク(GPL、AGPL) : 原則使用禁止、例外時は法務承認必須 中リスク(LGPL、MPL) : 使用条件の明確化、適切な分離 低リスク(MIT、Apache、BSD) : 適切な著作権表示で使用可能 生成AIコードの承認プロセス 開発者による初期スキャン 自動化ツールによる検証 法務部門による最終承認(高リスクライセンス検出時) まとめ 生成AI時代のソフトウェア開発において、ライセンス管理は新たな複雑さを増しています。従来のライブラリレベルでの検出では捉えきれない、コードスニペットレベルでの違反リスクが現実のものとなっています。 SCANOSSのような専門的なツールを活用することで、以下のメリットを実現できます: 技術的メリット コードスニペットレベルでの高精度検出 生成AIコードの包括的な分析 開発ワークフローへのシームレスな統合 ビジネスメリット 法的リスクの大幅な軽減 生成AI導入による開発効率向上の安全な実現 コンプライアンス要件の確実な遵守 今後、生成AIがさらに普及する中で、適切なライセンス管理体制の構築は、企業の競争優位性を維持するための必須要件となるでしょう。早期にこれらの仕組みを整備し、安全に生成AIのメリットを享受できる環境を構築することが重要です。 サイオステクノロジーではSCAN OSSをサポートしています。 https://sios.jp/products/oss/scanoss/ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 生成AI時代のライセンス管理:SCANOSSによる違反リスクの検知と対策 first appeared on SIOS Tech. Lab .
はじめに こんにちは!重要な機能開発を任されて最近まで業務で手一杯だったなーがです。Windowsで開発環境を構築する際にGitやSSHなど、さまざまなツールの設定に時間を取られていませんか?PCを買い替えたり、突然PCが動かなくなったりしたときに新しい環境をセットアップするたびに同じ設定を繰り返すのは大変ですよね。 今回はそうした設定ファイルをGithubリポジトリとして管理することで、いつでも同じ開発環境を再現できる「 dotfiles 」という仕組みと、Windows環境に特化した具体的な構成をご紹介します。私も業務で使用しているPCのリプレース作業が近づいていることもあり、せっかくなので書いてみることにしました。スクリプトファイルは最後に付録として載せておきます! 📁 dotfilesリポジトリの紹介 今回ご紹介するのは、Windows環境向けに作成されたdotfilesリポジトリです。このリポジトリは、設定ファイルを一元管理し、簡単なコマンド一つで環境のセットアップを完了させることを目的としています。GitHubで「dotfiles」と検索してみると、多くの人が自身の設定ファイルを公開しています。 https://github.com/search?q=dotfiles&type=repositories リポジトリは以下のような構成になっています。ここでは比較的一般的な設定ファイルに絞って紹介します。 dotfiles/ ├── README.md # このファイル ├── setup.ps1 # セットアップスクリプト ├── ssh/ # SSH設定 │ └── config ├── VSCode/ # Visual Studio Code設定 │ ├── settings.json │ └── keybindings.json └── WindowsTerminal/ # Windows Terminal設定 └── settings.json VS CodeやWindows Terminalといった主要な開発ツールの設定ファイルが、すべてこのリポジトリで管理されているのがわかりますね。 ここでキモとなっているのが setup.ps1 で、Ubuntu等のLinux環境の方は XXXX.sh を用意されていると思います。私はメイン業務がWindowsなので、今回はPowerShellのスクリプトファイルしか記載していません。このスクリプトはこのリポジトリ内で管理している各設定ファイルと各設定ファイルが本来配置される箇所とのシンボリックリンクを作成する役割があります。シンボリックリンクを張ることで、各設定ファイルをわざわざ配置することなく参照でき、各設定ファイルに行った変更は全てこのリポジトリ内で管理できるというメリットがあります。 このスクリプトファイルですが、一昔前であればPowerShellやシェルスクリプトの知識が必要で少しハードルが高かったですが、現在はClaudeやGitHub CopilotをはじめとしたViveコーディングが可能なため「この設定ファイルのシンボリックリンクを作成するスクリプトを作成して」とお願いして、少し手直しするだけで簡単に実現できます。いい時代になりましたね…かく言うこのスクリプトも大枠はAIに作成してもらいました! 🚀 セットアップ方法 セットアップは非常にシンプルです。PowerShellスクリプトを実行するだけで、必要な設定ファイルへのシンボリックリンクが自動的に作成されます。 1. シンボリックリンクの作成 まず、管理者権限でPowerShellを開き、リポジトリ内にある setup.ps1 スクリプトを実行します。 # PowerShellを管理者として実行 .\\setup.ps1 このスクリプトを実行すると、以下の場所に設定ファイルのシンボリックリンクが作成され、リポジトリでの変更が即座に反映されるようになります。 VS Code設定 : %APPDATA%\\Code\\User\\ Windows Terminal設定 : %LOCALAPPDATA%\\Packages\\Microsoft.WindowsTerminal_8wekyb3d8bbwe\\LocalState\\ SSH設定 : %USERPROFILE%\\.ssh\\ Git設定 : %USERPROFILE%\\.gitconfig その他のshell設定 : .bashrc , .bash_profile , .vimrc など ⚙️ オプション セットアップスクリプトには、便利なオプションも用意されています。 強制上書きモード すでに設定ファイルが存在する場合でも強制的に上書きしてシンボリックリンクを作成します。 .\\setup.ps1 -Force カスタムパス指定 dotfilesを異なる場所にクローンした場合でも、パスを指定して実行できます。 .\\setup.ps1 -DotfilesPath "C:\\path\\to\\your\\dotfiles" さいごに 今回は、Windows環境における設定ファイルの管理と自動化を行うdotfilesリポジトリを紹介しました。このような仕組みを活用することで、環境構築の手間を大幅に削減し、より本質的な開発作業に集中できます。 ぜひ、このリポジトリを参考に、ご自身の最強の開発環境を構築してみてはいかがでしょうか。 付録 <# .SYNOPSIS dotfilesフォルダ内のファイルのシンボリックリンクを作成するスクリプト .DESCRIPTION dotfilesフォルダ内にある設定ファイルを、適切な場所にシンボリックリンクとして作成します。 管理者権限が必要です。 .PARAMETER DotfilesPath dotfilesフォルダのパス(デフォルト: .) .PARAMETER Force 既存のファイルを上書きするかどうか .EXAMPLE .\Create-DotfilesSymlinks.ps1 .EXAMPLE .\Create-DotfilesSymlinks.ps1 -DotfilesPath "C:\dotfiles" -Force #> param( [Parameter(Mandatory = $false)] [string]$DotfilesPath = ".", [Parameter(Mandatory = $false)] [switch]$Force ) # 管理者権限チェック function Test-Administrator { $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) return $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } # シンボリックリンク作成関数 function New-SymbolicLink { param( [string]$LinkPath, [string]$TargetPath ) try { # リンク先のディレクトリが存在しない場合は作成 $linkDir = Split-Path -Parent $LinkPath if (-not (Test-Path $linkDir)) { New-Item -ItemType Directory -Path $linkDir -Force | Out-Null Write-Host "ディレクトリを作成しました: $linkDir" -ForegroundColor Green } # 既存のファイル/リンクをチェック if (Test-Path $LinkPath) { # 既存のパスがシンボリックリンクかどうか確認 $item = Get-Item -Path $LinkPath -Force $isSymLink = $item.Attributes -band [System.IO.FileAttributes]::ReparsePoint if ($isSymLink) { # シンボリックリンクの場合はスキップ Write-Warning "シンボリックリンクが既に存在します: $LinkPath (スキップ)" return $false } else { # 既存ファイルは削除 Remove-Item $LinkPath -Force Write-Host "既存のファイルを削除しました: $LinkPath" -ForegroundColor Yellow } } # シンボリックリンクを作成 New-Item -ItemType SymbolicLink -Path $LinkPath -Target $TargetPath -Force | Out-Null Write-Host "シンボリックリンクを作成しました: $LinkPath -> $TargetPath" -ForegroundColor Green return $true } catch { Write-Error "シンボリックリンクの作成に失敗しました: $LinkPath -> $TargetPath" Write-Error $_.Exception.Message return $false } } # メイン処理 function Main { Write-Host "=== Dotfiles シンボリックリンク作成スクリプト ===" -ForegroundColor Cyan # 管理者権限チェック if (-not (Test-Administrator)) { Write-Error "このスクリプトは管理者権限で実行する必要があります。" Write-Host "PowerShellを管理者として実行してから再度お試しください。" -ForegroundColor Yellow exit 1 } # dotfilesフォルダの存在チェック if (-not (Test-Path $DotfilesPath)) { Write-Error "dotfilesフォルダが見つかりません: $DotfilesPath" exit 1 } $DotfilesPath = Resolve-Path $DotfilesPath Write-Host "dotfilesフォルダ: $DotfilesPath" -ForegroundColor Cyan # ファイルマッピング定義 # 必要に応じて追加・変更してください $fileMappings = @{ # Git設定 ".gitconfig" = "$env:USERPROFILE\.gitconfig" # VS Code設定 "VSCode\settings.json" = "$env:APPDATA\Code\User\settings.json" "VSCode\keybindings.json" = "$env:APPDATA\Code\User\keybindings.json" # Windows Terminal設定 "WindowsTerminal\settings.json" = "$env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json" # SSH設定 "ssh\config" = "$env:USERPROFILE\.ssh\config" # その他の設定 ".bashrc" = "$env:USERPROFILE\.bashrc" ".bash_profile" = "$env:USERPROFILE\.bash_profile" ".vimrc" = "$env:USERPROFILE\.vimrc" ".minttyrc" = "$env:USERPROFILE\.minttyrc" ".wslconfig" = "$env:USERPROFILE\.wslconfig" } $successCount = 0 $skipCount = 0 $errorCount = 0 Write-Host "`n--- シンボリックリンクの作成を開始します ---" -ForegroundColor Cyan foreach ($mapping in $fileMappings.GetEnumerator()) { $sourcePath = Join-Path $DotfilesPath $mapping.Key $targetPath = $mapping.Value # ソースファイルの存在チェック if (-not (Test-Path $sourcePath)) { Write-Warning "ソースファイルが見つかりません: $sourcePath (スキップ)" $skipCount++ continue } Write-Host "`n処理中: $($mapping.Key)" $result = New-SymbolicLink -LinkPath $targetPath -TargetPath $sourcePath -ForceOverwrite $Force if ($result) { $successCount++ } else { if ((Test-Path $targetPath) -and -not $Force) { $skipCount++ } else { $errorCount++ } } } # 結果サマリー Write-Host "`n=== 処理結果 ===" -ForegroundColor Cyan Write-Host "成功: $successCount" -ForegroundColor Green Write-Host "スキップ: $skipCount" -ForegroundColor Yellow Write-Host "エラー: $errorCount" -ForegroundColor Red if ($errorCount -eq 0) { Write-Host "`nシンボリックリンクの作成が完了しました!" -ForegroundColor Green } else { Write-Host "`n一部のシンボリックリンクの作成に失敗しました。上記のエラーメッセージを確認してください。" -ForegroundColor Yellow } } # スクリプト実行 Main ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post PCの環境構築を迅速かつ簡単に!dotfilesで設定管理を始めよう first appeared on SIOS Tech. Lab .
挨拶 ども!最近はClaudeの制限を見ると嬉しくなっちゃう龍ちゃんです。最近は日常作業をプロンプト化して、効率化を模索しています。割と毎日触っているので、それなりにストレスも抱えてブログを書けるぐらいにはナレッジが溜まりましたね。 今回は、Claudeに感謝を持ちつつ「もうちょっとこうして!」という不安を解消するためのプロンプトテクニックについてまとめていこうと思います。 公式が出しているプロンプトジェネレーターを使うのも一つの手ですよ! よくあるClaude暴走パターン Claude使ってて「もう!」って思ったことありませんか?以下、僕が普段困っているClaudeあるあるをまとめました。 前提として人間側が投げるプロンプトが雑であるところが原因になります。 パターン1:過剰なArtifact生成問題 気づいたら大規模なArtifactが作られるなんてことありませんか?これはよくある「Claude君止まって!案件」ですね。実際に僕が出会った暴走例を以下に示します。 典型例 ユーザー:「Chakra UIのモーダルでJSON文字列を表示したい」 Claude:「Chakra UIのモーダルでJSON文字列を表示する方法をご紹介します。」 → 高機能もりもりモーダルの作成 期待していたもの 50行程度のシンプルなコード Chakra UIの基礎的なモーダルでJSON文字列を表示する方法 現実 300行弱の実践的なコード 高機能モーダル(表示切替機能) 高機能モーダル(コピー機能) 高機能モーダル(編集機能) その他の構造化暴走例 勝手に体系化 :「プロジェクト管理のコツある?」→ 階層化された管理手法分類 勝手にステップ化 :「転職活動どうしよう」→ 12段階の転職完全ロードマップ 勝手にチェックリスト化 :「会議の準備って何する?」→ 50項目の準備チェックリスト 普通に質問として聞きたいだけなのに、なぜか教科書レベルの資料を作られてしまう現象です。 パターン2:スレッド肥大化問題 まだ聞きたいことがあったのに、「スレッドの上限に達しました、別スレッドでお願いします」と言われたことありませんか?Artifactがv22なんて更新地獄を味わったことなど、気になることを聞いているとスレッドの容量がパンパンになって、Claudeの動作もなんだか遅くなっている…なんてことありませんか? 典型的な流れ 軽い質問をする Claudeが詳細な回答 + アーティファクト生成 「もうちょっと詳しく」と追加質問 さらに詳細な資料が追加される やり取りが膨大になる スレッド上限に到達してリセット 文脈が失われて振り出しに戻る あるあるシーン ユーザー:「このコードどう思う?」 Claude:改善版コード + 詳細解説を生成 ユーザー:「エラーハンドリングも追加して」 Claude:完全版コード + エラー対応ガイドを生成 ユーザー:「テストも書いて」 Claude:テストコード + テスト戦略文書を生成 …(10往復後) システム:「スレッド上限に達しました」 パターン3:人間追いつけない問題 「昨今のAIの問題は、人間の理解が追い付いていないから一番のボトルネックは人間の頭」って投稿をXで見かけた気がするんですけど、これ結構好きなんですよね。実際にWebSearch系の機能がついてから、ネットに接続できるようになったじゃないですか。あれから調べもの系はClaudeかGeminiに任せたほうが精度高いんですよね。その情報を確認しますけど、読み込ませて質問ベースで聞いちゃうほうが早いんです。こりゃ困ったなってところですね。 ボトルネックは人間の頭 Claudeの処理速度 vs 人間の理解速度のギャップが生む問題。 典型例 ユーザー:「新サービスのアイデア考えて」 Claude:(10秒で) - 詳細な事業計画 - 市場分析レポート - 技術仕様書 - マーケティング戦略 - 収益予測 人間:「えーっと...まず何から読めば...」(理解に30分) よくあるパターン 情報過多で頭パンク :求めた以上の情報量で思考停止 判断材料が多すぎて決められない :選択肢を増やしすぎて逆に困る 完璧すぎて修正しにくい :隙のない提案で改善点が見つからない Claudeが優秀すぎて、人間側の処理が追いつかない現象です。 共通する課題 これらの暴走パターンに共通するのは: 過剰な解決志向 :問題を見つけると完璧に解決しようとする 文脈の読み違い :軽い質問を重要なタスクと誤解 成果物化癖 :何でもアーティファクトにしたがる 完璧主義 :中途半端よりも完全版を作りたがる いろいろ分析してみましたが、結局のところ人間側のプロンプトがあいまいってところに落ち着くんですよね。というかそこを頑張らないと人間がいる意味がないといいますか…技術として向上していく必要があるなって話です。 AIが優秀になるにつれて、「何を求めているのか」「どのレベルの回答が欲しいのか」を明確に伝えるスキルが重要になってきています。つまり、プロンプトエンジニアリングが日常的な必須スキルになっているということですね。 そんなわけで、普段使っているプロンプトテクニックといえるほどでもないですが、暴走を抑えることができるプロンプトを紹介していきます。 次の章からは、実際にプロンプトテクニックを紹介していきます。 Claude制御テクニック集 Claudeの暴走は「Claudeが悪い」のではなく、 100%人間側のプロンプトが曖昧 なのが根本原因。明確な指示で適切にコントロールしましょう。 基本原則:「Claudeは優秀すぎるから、明確に指示する」 まず大前提です。Claudeはめちゃくちゃ賢いやる気満々な子です。そしてすごくいい子です。基本全工程だし、めちゃくちゃほめてくれます。でも、会社の先輩や同僚みたいに自分の思考の癖や理解しているレベルを理解はしてくれません。なので、めちゃくちゃ賢くてやる気のある赤子ぐらいに思ってるとちょうどよいです。 Claudeは曖昧な質問を見ると「きっと詳しく知りたいんだろう」と解釈して、全力で応えようとします。それが「暴走」に見える現象の正体です。 なので、トークン数を気にせずにプロンプトは詳細であるほど良いですね。多少の例を渡すFew-shotやone-shotなんて技術ありましたけど、あれはゴールを明確にするって良い手法ですよね。この辺は海外の論文を読んだりすると詳細な分析が流れてくるので、検索するとおもしろいですよ。 今回は、体感できる簡単なテクニックを解説していきます。 制御テクニック1:成果物制御テクニック これはダイレクトにArtifact生成を制御するプロンプトになります。例で紹介した「Chakra UIのモーダルでJSON文字列を表示したい」では、大体はArtifact生成を進めますが、時にはチャット形式で返答することもあります。これは求めている形式を指定していないことで起きます。 根本原因 Claudeが勝手に「Goal」を定義して作ってしまう 「JavaScriptの関数について教えて」→ Claude「詳細なリファレンスが必要だ!」 「プロジェクト管理のコツある?」→ Claude「体系的なガイドを作ろう!」 Goalの定義 簡単なのは、「Goalの定義」を行うことです。以下がテンプレートになります。 Chakra UIのモーダルでJSON文字列を表示したい Goal:初心者でもわかるようにコード内にコメント多めで、コードサンプルArtifactを生成して Goal: として最終的なゴールを示してあげることで、Claudeはそこに向かって走ってくれます。今回はサンプルなので、簡単なゴール設定になっています。ただ、最終的な成果物の制限を追加することで向かってくれます。効果測定のために別のプロンプトを張ってみますね。 Chakra UIのモーダルでJSON文字列を表示したい Goal:初心者でもわかるようにコード内にコメント多めで、マークダウン形式でコード解説付きArtifactを生成して こちらでマークダウンファイルが作成されます。一言表現を変えるだけで幅が変わります。 成果物の否定 こちらも効果的です。普段だとやってほしいことを書くじゃないですか?これはそちらとは逆ですね。やってほしくないことを伝えるのも効果的です。強い言葉であるほど意識してくれますね。この辺は、スレッドが長くなるほど効果が薄れていく感じは多少あります。過去の情報って忘れていくんですよね。 ✅ 良い例: 「成果物を作らずに、考え方だけ教えて」 「Artifactは不要で、チャットで答えて」 「アプリ作成は不要、アドバイスだけお願いします」 「資料にしないで、会話として答えて」 「禁止事項:Artifact生成」 前提の共有 こちらは先ほど紹介した上の二つよりもさらにライトですが、普段使いとしては十分かもしれません。だんだん指示が友達に与えるみたいになっているのが良いんですよね。 ✅ 良い例: 「3行くらいで説明して」 「要点だけお願いします」 「一言でどう思う?」 「概要だけざっくりと」 「普通に会話として答えて」 段階的アプローチ これはおすすめです。「Goalの明確化」や「成果物の否定」や「前提の共有」のテクニックを文章として暗黙に伝えています。よく使うのは「ステップバイステップ」ですね。 ✅ 良い例: 「まずは概要だけ教えて、詳しく知りたくなったら追加で質問するから」 「まず一言で説明して、分からなかったら詳しく聞くよ」 「基本だけ教えて、応用は後で」 「ステップバイステップで」 龍ちゃんおすすめプロンプト 個人的に設定しているシステムプロンプトを共有しますね。ブログの校閲として使っているClaudeなんですが、これを設定しておくと精度が爆上がりしました。 情報を作成をする前に具体的なプランを明示して許可を取ってから実行をしてください。 許可が出るまで生成はしないでください。 明確な指示がない場合は、文章の校閲をしてほしいと解釈してください。 制御テクニック2:会話継続テクニック スレッドの上限まで達成したことがある方は、もうClaude沼にどっぷりと浸かっていますね。素晴らしいことです。でも、スレッド上限に達してしまって、また前提の共有からなんてやってられないですよね。Claudeはメモリ機能がないので、スレッドではまた別のClaudeとお話することになります。関係値を一から作る必要があります(ぴえんである)。 そんな時に使えるテクニックについて紹介します。 引継ぎ資料作成 → 新スレッド移行 これはスレッドがパンパンになる直前にやっておくとマジで助かります。スレッドの内容をPDFで出力させてしまい、それを新規スレッドの初回に入力として与えることで疑似的にスレッドの会話を継続させることができます。 ✅ 良い例: 「今までの議論を引継ぎ資料にまとめて。新しいスレッドでその資料をベースに続きをやりたい」 「現時点の作業までで決定したことを引継ぎ資料として作成して」 AI間役割分担によるトークン分散 これは、そもそもスレッドの上限に達しないようにするテクニックです。処理を事前に人間側で分散しておき、その段階ごとにスレッドを分けて中間ファイルを生成させておきます。最終的な成果物ファイルを生成する前に段階を踏ませることで、スレッド内のトークン数を分散させます。 ✅ 良い例: 調査用 Claude:「xxxxxxxxに関する調査をしてマークダウン形式でまとめて」 プロンプト生成用 Claude:「添付したファイルをもとに〇〇〇生成用プロンプトを生成して」 生成用 Claude:「添付ファイルからよろしく~」 Artifactベースの新規Artifact作成 これは生成されたものをベースに新規のArtifactを生成することで、対象とするArtifact自体を軽量化する手法です。Artifactベースに新規のArtifactを作成する場合は以下の二つの目的で生成します。 軽量版Artifactを生成する:いらない要素をそぎ落とす 新規Artifactを生成する:大きくなりすぎたファイルを分割する 軽量版 大きいArtifactから内容を抽出して、軽量化することができます。Artifact生成でいらない要素がくっついてくることがたまにありますよね?それらをそぎ落とすことで、軽量化されます。Artifactの中身をセクションごとに分割することで、対象とするファイルの大きさを小さくすることもできます。 ✅ 良い例: 「このアーティファクト重くなりすぎたから、軽量版で作り直して」 「要点だけに絞った簡易版を新しく作って」 「今の成果物をベースに、コンパクト版お願いします」 新規Artifact ✅ 良い例: 「今までの議論を踏まえて、新しいアーティファクトで整理し直して」 「別の成果物として、今回の結論をまとめて」 「前のは置いといて、新規で作り直そう」 段階的成果物更新 これは、小さなArtifactを段階的に更新する手法です。プロンプトでArtifact生成を止めておいて、「段階的に」と指示をすることで小さなArtifactに対して段階的更新をかけていきます。これの良い点は、Artifact生成を人間がちゃんと監視しているので、「なんか方向性が違う!」というのがすぐわかる点がよいですね! ✅ 良い例: 「成果物を少しずつ更新していこう」 「この部分だけ先に反映させて」 「一気に全部やらずに、段階的に進めよう」 制御テクニック3:ペース調整テクニック(人間主導の学習プロセス) これはClaudeが出力したコードを正として使用するのではなく、 人間がペースを握って学習を進める 方法です。体感的には、Claudeが100%完璧なコードを出すケースは少ないですが、領域を限定して小さく出力させることで、より確実に動くコードを生成できます。 基本的な学習ステップ 大枠を作成してもらう – 全体像を把握 特定の領域に対して深堀り質問 – 段階的に理解を深める わからない部分は説明を求める – 疑問点を即座に解決 理解度確認のために自分の言葉で例え話 – 学習の定着を図る 学習フロー図 制御テクニック3(実践例):Kubernetesの学習過程 この手法を活用して、Kubernetesが一切わからない段階から、新規作成したDaemonSetを検証環境にデプロイできるレベルまで到達した実例をご紹介します。 1. 大枠把握からスタート 入力例: k8sに関しては一ミリもわからないのですが、初心者が理解しておくべきポイントをまとめてください。 出力例: Kubernetesの初心者向け重要ポイントをまとめます: ## Kubernetesとは何か Kubernetesは、コンテナ化されたアプリケーションを自動的に管理・運用するためのオープンソースのプラットフォームです。「k8s」と略されることが多く、これは「k」と「s」の間に8文字あることから来ています。 ## 基本概念 - **クラスター**: Kubernetesが管理するサーバー群全体 - **ノード**: 実際にアプリケーションが動く個々のサーバー - **Pod**: アプリケーションの最小実行単位 - **デプロイメント**: Podの管理・配置を行う仕組み ~~~~~以下略~~~~~ 2. 理解確認型学習 入力例: ノードとDaemonSetとPodの理解から始めればよいかな? 出力例: はい、それはとても良い学習順序です! ## なぜこの3つから始めるのが良いか この3つの関係を理解すると、Kubernetesの基本的な構造が見えてきます: - **ノード** → 物理的な土台(サーバーやコンピューター) - **DaemonSet** → 管理の仕組み(「各ノードに1つずつ配置する」というルール) - **Pod** → 実際に動くアプリケーション ## 学習の順序 1. まずノードを理解(土台) 2. 次にPodを理解(実際に動くもの) 3. 最後にDaemonSetを理解(管理の仕組み) ~~~~~以下略~~~~~ 3. 段階的深堀り学習 入力例: Podが最小構成なのは知っています。 PodがDockerのコンテナーって理解であってる? 出力例: いえ、少し違います。Pod ≠ Dockerコンテナです。 ## 正しい関係 PodはDockerコンテナを入れる箱のようなものです: Pod(箱) ├── Container 1(メインアプリケーション) ├── Container 2(サイドカーコンテナ)※オプション └── 共有リソース(ネットワーク、ストレージ) ## 重要なポイント - 1つのPodに複数のコンテナを含めることができる - 同じPod内のコンテナは同じネットワーク(IPアドレス)を共有 - 多くの場合、1Pod = 1Container として使用される ~~~~~以下略~~~~~ 続きの質問: Pod内にContainerがある関係なのね。Podとコンテナは同一のものではないけど、要素として共通点は多い印象かな ただ、k8sの環境ではPodという概念で管理しているだけって感じ? 出力例: まさにその通りです!とても良い理解ですね。 ## あなたの理解は正確です Pod ⊃ Containerの関係で、確かに機能的には似ている部分が多いです。 ## なぜPodという概念が存在するのか 1. **複数コンテナの協調動作**: 1つのアプリケーションを複数の小さなコンテナで構成 2. **リソース共有**: 同じPod内のコンテナは簡単にデータをやり取りできる 3. **運用の単位**: Kubernetes では Pod単位でスケジューリング・管理 ## 実際の開発では 90%以上のケースで「1Pod = 1Container」で運用されています。 Podは「コンテナを管理するための仕組み」と考えるのが適切です。 ~~~~~以下略~~~~~ 4. 理解度チェックの要求 入力例: ノード上には複数のPodが含まれる可能性がある Deploymentは管理しているクラスター全体で特定の数配置できる DaemonSetはノードの数分だけPodをノード内に一つ配置できる であってる? 出力例: はい、完璧に正確です! ## あなたの理解 ✓ ✓ **ノード上には複数のPodが含まれる可能性がある** ✓ **Deploymentはクラスター全体で特定の数配置できる** ✓ **DaemonSetはノードの数分だけPodをノード内に一つ配置できる** ## 図解で確認 ```bash クラスター全体 ├── ノード1 │ ├── Pod A(Deployment管理) │ ├── Pod B(Deployment管理) │ └── Pod X(DaemonSet管理)← 各ノードに必ず1つ ├── ノード2 │ ├── Pod C(Deployment管理) │ └── Pod Y(DaemonSet管理)← 各ノードに必ず1つ └── ノード3 └── Pod Z(DaemonSet管理)← 各ノードに必ず1つ ``` ## 使い分け - **Deployment**: Webアプリケーションなど、必要な数だけ配置 - **DaemonSet**: ログ収集、監視エージェントなど、全ノードに配置が必要 ~~~~~以下略~~~~~ 5. ネクストステップ 入力例: 次はネームスペースについて勉強したい このテクニックの効果 メリット 学習者主導 : 自分のペースで理解を深められる 段階的理解 : 一度に大量の情報を処理しなくて済む 確実な定着 : 理解度を確認しながら進むため、知識が定着しやすい 応用可能 : 様々な技術領域で応用できる 適用シーン 新しい技術の学習 複雑なシステムの理解 コード理解とデバッグ 設計パターンの習得 実践のコツ 1. 質問の仕方 ❌ 悪い例:「Kubernetesを教えて」 ✅ 良い例:「Kubernetesの基本概念を3つに絞って教えて」 2. 理解度確認 ❌ 悪い例:「わかりました」 ✅ 良い例:「つまり、Podは家でコンテナは住人のような関係ですね?」 3. 段階的深堀り ❌ 悪い例:「すべて詳しく説明して」 ✅ 良い例:「まずノードの概念から理解したい」 このテクニックを使うことで、AIを効果的な個人チューターとして活用し、確実にスキルアップを図ることができます。 まとめ 今回は、Claudeの制御テクニックについて詳しく見てきました。最初は「Claude君止まって!」と思っていた暴走も、実は人間側のプロンプトが曖昧だったことが根本原因だったんですね。 今日から使える3つのポイント 1. 明確な指示が全ての基本 Goal設定や成果物の否定、前提共有で明確に方向性を示すことが重要です。 2. スレッド管理で効率的な会話継続 引継ぎ資料作成→新スレッド移行や、AI間役割分担でスレッド上限問題を解決できます。 3. 人間主導の学習プロセス 大枠把握→理解確認→段階的深堀り→理解度チェックの流れで、確実にスキルアップを図れます。 おわりに Claudeを「めちゃくちゃ賢くてやる気のある赤子」だと思って接すると、うまく付き合えるかもしれません。明確な指示を与えてあげることで、本当に強力なパートナーになってくれます。 この知識を活かして、より効率的なAI活用にチャレンジしてみてください!皆さんも、ぜひ自分なりのClaude調教術を編み出してみてくださいね。 それでは、良いClaude調教ライフを! いろいろなAIのブログ 書いてますので、ぜひ読んでみてね! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Claude調教術|暴走パターンを制御する3つのプロンプトテクニック first appeared on SIOS Tech. Lab .
はじめに こんにちは、サイオステクノロジーの小沼 俊治です。 これまで、オリジナルの MCP サーバーを開発するノウハウを、以下の記事で公開してきました。 第1弾: オリジナルのちょっと便利な MCP サーバー を作ってみた 第2弾: オリジナルのちょっと便利な『リモート MCP サーバー』を作ってみた その第3弾として、2025年6月下旬に、ローカル MCP サーバーをパッケージングして手軽に配布できる「Desktop Extensions(以下、デスクトップ拡張、または DXT と略して表す)」の仕組みが公開されたので、その最新ノウハウを共有します。 Claude Desktop Extensions: One-click MCP server installation for Claude Desktop anthropics/dxt ローカルMCPサーバーは、AIエージェントを自由に拡張できる非常に便利な機能です。しかし、これまでは開発ツールの準備や設定ファイルの編集など、インストールが複雑で IT リテラシーが高くないと導入が難しいという課題がありました。 今回の DXT の登場により、 パッケージングされたローカル MCP サーバーは、マウスのクリックやドラッグ&ドロップといった簡単な操作でデスクトップ拡張としてインストールできるようになります。 これはMCPサーバーの普及を大きく加速させる画期的な仕組みであり、私自身も大変ワクワクしています! 本ハンズオンはローカル MCP サーバーを対象とするため、第1弾「 オリジナルのちょっと便利な MCP サーバー を作ってみた 」の続きとして解説を進めます。引き続き、MCP サーバーの実装には Node.js を使用します。 構成概要 筆者が動かした際の主な構成要素は以下の通りです。 DXT 配布元となるローカル MCP サーバーの開発 PC Windows 10 Professional Claude desktop for Windows version 0.11.6 Windows PowerShell 5.1.19041.5737 Node.js v22.15.0 DXT 配布先となるローカル MCP サーバーの利用 PC Windows 10 Professional Claude desktop for Windows version 0.11.6 ハンズオンを構成する環境は以下の通りです。 MCP ホストは、ローカル MCP サーバーを組み込める Claude desktop のネイティブアプリ版を利用します。 配布元 PC の構成における主な考慮事項は以下の通りです。 MCP サーバーを稼働させる環境は Node.js 環境を選択しています。 TypeScript で実装したソースコードのトランスパイルや、デスクトップ拡張としてパッケージングするため Node.js のインストールが必要です。 配布先 PC の構成における主な考慮事項は以下の通りです。 デスクトップ拡張としてインストールした MCP サーバーの稼働には、Claude の組込み Node.js を利用できるため、別途 Node.js のインストールは不要です。 Claude にバンドルされた Node.js の利用について TypeScript で実装して JavaScript にトランスパイルされたローカル MCP サーバーを動かす場合には、Claude にバンドルされた Node.js を利用できます。 デフォルトで有効になっていますが、Claude desktop 画面左上のハンバーガーメニュー(三本線)から「ファイル > 設定…」で表示した設定画面で、左ペインより「エクステンション」を選び、画面下部の「詳細設定」ボタンをクリックすると組み込み Node.js の有効状態が確認できます。 ハンズオンの手順でパッケージングするローカル MCP サーバーの設定ファイルやソースコードは、以下の GitHub リポジトリで公開しています。必要に応じてご活用ください。 hands-on-mcp-sios-apisl @ GitHub v1.0.0 : オリジナルのちょっと便利な MCP サーバーを実装 配布元 PC で基礎環境の構築(第1弾の環境復元) 以前の記事「 オリジナルのちょっと便利な MCP サーバー を作ってみた 」で開発したローカル MCP サーバーのソースコード等のリソースが手元にある場合には、本章はスキップして以下の章へ進んでください。 ローカル MCP サーバーを Desktop Extensions で便利に配布してみた | 配布元 PC で DXT ファイルを作成 Claude desktop for Windows インストール MCP ホストに Web 版ではなくアプリ版の Claude desktop for Windows を利用するため、以下公式手順を参考に Windows PC へインストールします。 Claude for Desktopのインストール | Anthropicヘルプセンター Node.js インストール パッケージの作成に Node.js を必要とするため、以下手順を参考に Windows PC へインストールします。 初期環境構築: Node.js on Windows デスクトップ拡張にするローカル MCP サーバーの用意 ローカル MCP サーバーのリソースを用意する方法は2通りあります。お使いの環境に合わせて、いずれかの方法でリポジトリを取得してください。 方法1:Git コマンドで取得する Git がインストールされている場合は、以下のコマンドでリモートリポジトリをクローンします。 PS C:\Users\...\development> git clone https://github.com/Toshiharu-Konuma-sti/hands-on-mcp-sios-apisl.git -b v1.0.0 方法2:ブラウザでダウンロードする Git がインストールされていない、またはコマンド操作に不慣れな場合は、ブラウザから GitHub の以下 URL にアクセスし、「Source code (zip)」をクリックして Zip ファイルを取得します。 Release v1.0.0 · Toshiharu-Konuma-sti/hands-on-mcp-sios-apisl 配布元 PC で DXT ファイルを作成 配布元 PC でローカル MCP サーバーをパッケージングして、デスクトップ拡張(DXT ファイル)として配布できるようにします。 デスクトップ拡張のパッケージ作成には dxt コマンドが必要ですが、通常、このコマンドをグローバルインストール( npm install -g @anthropic-ai/dxt )すると管理者権限やルート権限が求められます。 本ハンズオンでは管理者権限なしで dxt コマンドのパッケージを直接実行できる npx コマンドを使って進めます。 パッケージングするリソースの準備 デスクトップ拡張としてパッケージングするために、ローカル MCP サーバーが実行可能な状態にして、必要なリソースを準備します。 依存パッケージの準備 まず始めに、依存パッケージをダウンロードします。 PS C:\Users\...\hands-on-mcp-sios-apisl> npm install 次に、TypeScript から JavaScript のソースコードをトランスパイルして生成するため、ビルドを行います。 PS C:\Users\...\hands-on-mcp-sios-apisl> npm run build アイコンの準備 アイコンの設定は必須ではありませんが、 配布先の利用者にとって製品としての完成度が高まるため、設定をお勧めします。 今回は「 FLAT ICON DESIGN -フラットアイコンデザイン- 」様より、ローカル MCP サーバーの機能(天気予報)にちなんだアイコンを使用させていただきます。 天気のアイコン素材 用意したアイコンは、プロジェクトリポジトリの直下に image\ フォルダを作成し、その中に PNG 形式の画像ファイルとして保存します。 準備できた構成 ここまでの手順で、パッケージングに必要なソースコードや画像などのリソースが以下の構成で準備できたことを確認してください。これが整っていれば、いよいよデスクトップ拡張(DXT)を作成するパッケージング手順に進めます。 PS C:\Users\...\hands-on-mcp-sios-apisl> tree /f C:. │ .gitignore │ LICENSE │ package-lock.json │ package.json │ README.md │ tsconfig.json │ ├─build │ │ index.js │ ├─interfaces │ │ areaApi.js │ └─services │ areaWeatherForecast.js ├─image │ f_f_traffic_4_s512_f_traffic_4_0bg.png ├─node_modules │ : │ (省略) │ └─src │ index.ts ├─interfaces │ areaApi.ts └─services areaWeatherForecast.ts マニフェストの新規作成 dxt init コマンドを使うと、デスクトップ拡張のパッケージングに必要な manifest.json ファイルを対話形式で簡単に作成できます。以下のコマンドを実行してください。 PS C:\Users\...\hands-on-mcp-sios-apisl> npx @anthropic-ai/dxt init This utility will help you create a manifest.json file for your DXT extension. Press ^C at any time to quit. ✔ Extension name: hands-on-mcp-sios-apisl ✔ Author name: SIOS Technology, Inc. API Solution Service Line Devision. ✔ Display name (optional): The hands-on local MCP server ✔ Version: 1.0.0 ✔ Description: A bit useful locat MCP server to check a weather! ✔ Add a detailed long description? no ✔ Author email (optional): ✔ Author URL (optional): https://api-ecosystem.sios.jp ✔ Homepage URL (optional): ✔ Documentation URL (optional): https://tech-lab.sios.jp/archives/48129 ✔ Support URL (optional): ✔ Icon file path (optional, relative to manifest): image/f_f_traffic_4_s512_f_traffic_4_0bg.png ✔ Add screenshots? no ✔ Server type: Node.js ✔ Entry point: build/index.js ✔ Does your MCP Server provide tools you want to advertise (optional)? no ✔ Does your MCP Server provide prompts you want to advertise (optional)? no ✔ Add compatibility constraints? no ✔ Add user-configurable options? no ✔ Keywords (comma-separated, optional): ✔ License: MIT ✔ Add repository information? yes ✔ Repository URL: git+https://github.com/Toshiharu-Konuma-sti/hands-on-mcp-sios-apisl.git Created manifest.json at /home/hoge/handson/hands-on-mcp-sios-apisl/manifest.json Next steps: 1. Ensure all your production dependencies are in this directory 2. Run 'dxt pack' to create your .dxt file 対話形式で入力する主要な項目とその内容は以下の通りです。 Extension name: DXT のファイル名になります。 Author name: 作成者名を入力します。 Display name: デスクトップ拡張として表示される名称です。 Description: 入力すると、デスクトップ拡張の名称の下に説明文として表示されます。 Author URL: 入力すると、作成者が該当 URL へのリンクとなります。 Icon file path: PNG 画像ファイルのパスを入力すると、デスクトップ拡張のアイコンを設定できます。 Entry point: ローカル MCP サーバーのスクリプトファイルパスを入力します。 対話形式での入力が完了したら、 manifest.json ファイルができあがったことを確認します。 PS C:\Users\...\hands-on-mcp-sios-apisl> ls Mode LastWriteTime Length Name ---- ------------- ------ ---- : -a---- 2025/07/01 10:47 705 manifest.json : DXT ファイルの作成 いよいよ、ローカルMCPサーバーをパッケージ形式のデスクトップ拡張として作成します。プロジェクトのルートディレクトリで、以下のコマンドを実行します。 PS C:\Users\...\hands-on-mcp-sios-apisl> npx @anthropic-ai/dxt pack Validating manifest... Manifest is valid! 📦 hands-on-mcp-sios-apisl@1.0.0 Archive Contents 27B BUILD.bat 2.1kB build/index.js : 110.9kB node_modules/zod-to-json-schema/dist/ [and 78 more files] 509.2kB node_modules/zod/lib/ [and 25 more files] Archive Details name: hands-on-mcp-sios-apisl version: 1.0.0 filename: hands-on-mcp-sios-apisl-1.0.0.dxt package size: 5.0MB unpacked size: 22.8MB shasum: 04fc9aff3e343e1e0fa2afaf3a537ed51a71a09a total files: 817 ignored (.dxtignore) files: 692 Output: C:\Users\...\hands-on-mcp-sios-apisl\hands-on-mcp-sios-apisl.dxt コマンドの実行が成功したら、デスクトップ拡張ができあがったことを確認します。 PS C:\Users\...\hands-on-mcp-sios-apisl> ls Mode LastWriteTime Length Name ---- ------------- ------ ---- : -a---- 2025/07/01 10:56 5282475 hands-on-mcp-sios-apisl.dxt : できあがったパッケージ形式のデスクトップ拡張は、ウェブサイトで公開やクラウドのファイルサーバーで共有などを活用して利用者に配布します。 配布先 PC で DXT ファイルからインストール ここからは、配布された DXT ファイルを使って 配布先 PC でローカル MCP サーバーをインストールし、利用可能にするまでの手順 を説明します。 DXT ファイルの入手 まず、インストールしたいローカル MCP サーバーのパッケージ形式のデスクトップ拡張を、開発者からウェブ経由などで事前に受け取ってください。 DXT ファイルからインストール Windows のスタートメニューから Claude desktop を起動します。プロンプトの入力ボックス左下部にある「検索とツール」ボタンをクリックしても、オリジナルのデスクトップ拡張(オリジナルの MCP サーバー)がリストに無いため、まだインストールされていないことが確認できます。 Claude desktop 画面の左上にある三本線のハンバーガーメニューから「ファイル > 設定…」メニューを選択して設定画面を表示し、左ペインから「エクステンション」を選びます。 エクステンション画面が表示されている状態で、エクスプローラーを起動します。事前に入手した DXT ファイルをエクスプローラーから Claude desktop のエクステンション画面へドラッグアンドドロップします。 エクステンション画面上で DXT ファイルをドロップすると、オリジナルのローカル MCP サーバーのインストール画面に切り替わり、内容を確認したうえで「インストール」ボタンをクリックしてインストールします。 インストールが終わったら設定画面の上部にある「< すべての拡張機能」を選択して戻ります。 エクステンション画面にオリジナルのローカル MCP サーバーが追加されていることが確認できたら、設定画面の右上の×ボタンをクリックして画面を閉じます。 プロンプトの入力ボックス左下部にある「検索とツール」ボタンをクリックすると、今度はオリジナルの MCP サーバーがリスト存在することが確認できます。 これで、デスクトップ拡張のインストール手順は完了です。インストールしたローカルMCPサーバーでどのようなことができるかについては、「 オリジナルのちょっと便利な MCP サーバー を作ってみた 」で紹介している動画をご覧ください。 まとめ Desktop Extensions(デスクトップ拡張)は、いかがでしたでしょうか? 今回の記事では、この Desktop Extensions がローカル MCP サーバーの配布をいかに容易にするか、そして MCP サーバーの普及にどう貢献するかについてご紹介しました。 ぜひ、皆さんも一度試して、その便利さを実感してみてください。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post ローカル MCP サーバーを Desktop Extensions で便利に配布してみた first appeared on SIOS Tech. Lab .
挨拶 ども!6/25にGemini CLIがリリースされて、7月に入ってから気づいて出遅れてしまった龍ちゃんです。Xでだいぶ話題になっていますね。noteの「 プログラミング1ミリも分からない人がGemini CLI始めるまでの全手順(Win篇) 」を見て、Gemini CLIがリリースを知りました。まぁ私はエンジニアなので、エンジニア目線で使い方を模索していこうと思っています。Gemini CLIだと、いろいろとカスタマイズができそうってことで、入門していきます。 弊社では、Geminiをワークスペースで契約をしているので普段から使えています。(生成AIサービスも福利厚生という時代になりましたね…) 今回は「DevContianerでGemini CLIを使うことができる環境をセットアップし、システムプロンプトを設定してURLからXの投稿文を生成」を目標として環境構築からセットアップまで行っていきます。 それでは始めましょう。 1. はじめに – なぜDevContainer × Gemini CLIなのか 皆さんは、AI開発環境の「汚染問題」に悩まされていませんか? 今回DevContainer×Gemini CLIの環境を構築した背景は、 チーム開発での環境差異を根本的に解決したい からです。個人利用なら全く問題ないのですが、チーム開発となると話は別ですよね。 グローバルインストールでホスト側に設定ファイルをゴリゴリ書き込んでいると、「なんで俺のGeminiとお前のGeminiは違うんや!」という環境差異が必ず発生します。開発者は自由に自分の環境を作ってしまう自由人ですからね。新卒や別担当者が参画した際に「あれ?このコマンド入ってない」で一日潰れる光景、想像に難くないでしょう。 そこで注目したいのが、 2025年6月にGoogle公式リリースされたGemini CLI です。無料枠が1日1000回と太っ腹で、実用性は十分。DevContainerと組み合わせることで、 安全で再現可能な開発環境 が実現できます。 実践的な検証として、普段Claude Projectで使用しているプロンプトを使って「雑プロンプト」「Claude Project」「Gemini CLI」の3パターンで品質比較も行います。 今回の記事で学べること: DevContainerでGemini CLIを安全に動かす設定方法 GEMINI.mdによるプロジェクト固有カスタマイズ システムプロンプトの効果的な設定テクニック 実践:URLからテック系ブログのX投稿文を生成してdocs配下に自動保存 それでは、チーム開発を前提とした「Geminiの回答品質担保」環境を一緒に構築していきましょう! 2. Gemini CLIとDevContainerの基礎知識(800文字) 2.1 Gemini CLIとは Gemini CLIは、2025年6月にGoogleが公式リリースしたオープンソースAIエージェントです。Apache 2.0ライセンスで提供されており、企業利用も安心ですね。 主要機能として以下が挙げられます: ファイル操作 : プロジェクトファイルの読み書き・編集 コマンド実行 : シェルコマンドの実行とレスポンス取得 Web検索 : リアルタイム情報収集 MCP対応 : Model Context Protocolによる拡張性 特に注目したいのが サンドボックス機能 です。これは、AI実行環境を隔離することで、予期しないシステム変更を防ぐGemini CLI独自の安全機能となります。 2.2 DevContainerとは DevContainerは、VS Code DevContainersの略称で、 コンテナベースの開発環境 を提供する技術 です。従来の「俺の環境では動くんだけどなあ」問題を根本的に解決してくれます。 AI開発における特有のメリットとして、 環境破壊防止 が挙げられます。AIエージェントが予期しないパッケージインストールやシステム変更を行っても、ホスト環境は完全に保護されるわけです。 コンテナ化により、チームメンバー全員が 同一の実行環境 を共有できるため、「なんで俺の環境だと動かないんだ」という状況を回避できます。これは、特に新卒研修や外部パートナーとの協業において威力を発揮しますね。 2.3 組み合わせの利点 DevContainer × Gemini CLIの組み合わせには、4つの主要メリットがあります。 安全性 : Gemini CLIのサンドボックス機能とDevContainerの隔離環境により、 二重の安全保障 が実現されます。AIが暴走してもホスト環境への影響は皆無です。 再現性 : 環境設定が .devcontainer/devcontainer.json としてコード化されるため、 誰でも同じ環境を瞬時に再現 できます。チーム間での環境差異によるトラブルが根絶されますね。 拡張性 : MCP対応により、プロジェクト固有の機能追加が容易です。継続的な改善と標準化が可能で、 組織全体での知見蓄積 に貢献します。 コスト効率 : Google Workspace統合により、既存の企業インフラとシームレスに連携できます。新たなライセンス購入や複雑な導入プロセスが不要で、 企業導入のハードルが格段に下がります 。 実際のプロジェクトでどちらを選ぶべきか、迷うところですが、チーム開発を前提とするなら、この組み合わせが現時点での個人的ベストプラクティスと言えるでしょう。 3. 環境準備と前提条件 3.1 必要なツール 今回の検証はGoogle Workspace環境で行っています。@google.comではなく@<自社ドメイン>.comアカウントでの検証ですが、認証方法が若干異なるだけなので、あまり気にしなくて大丈夫です。 Docker環境については、今回WSL2にDocker Engineを直接インストールして検証しています。ただし、 個人利用の方はMicrosoft公式が推奨している Docker Desktop の構築 をお勧めします。 企業でお使いの方、特に新人・若手の皆さんへ : Docker Desktopには商用利用時のライセンス制約があります。必ず上長または情報システム部門(最低でも同僚)に確認を取ってください。ライセンス問題は最悪の場合、訴訟に発展するリスクがあるため、慎重に対応しましょう。 3.2 Google Workspace環境の設定 Google Workspace環境では、 Gemini for Google Cloud API が有効化されたプロジェクトが必要になります。このAPIが有効になっていない場合、Gemini CLIの認証段階でエラーが発生します。 Google Cloud Consoleでプロジェクトを作成し、以下の手順でAPIを有効化してください: Google Cloud Console → APIとサービス → ライブラリ “Gemini API”で検索 Gemini for Google Cloud APIを選択して「有効にする」 3.3 DevContainer環境の準備 DevContainerを使用するため、以下のツールが必要です: VS Code : 最新版を推奨 DevContainers拡張機能 : Microsoft公式の拡張機能 Docker環境 : Docker DesktopまたはDocker Engine VS Codeの拡張機能タブで「DevContainers」を検索してインストールしてください。この拡張機能により、コンテナ内での開発が可能になります。 3.4 事前確認チェックリスト 作業を始める前に、以下の項目を確認してください: Docker環境の動作確認 : docker --version でバージョンが表示される VS Code + DevContainers拡張機能 : 正常にインストール済み Google Workspace アカウント : 組織アカウントでのログインが可能 Google Cloud プロジェクト : Gemini APIが有効化済み ネットワーク環境 : Google APIsへのアクセスが可能(企業ファイアウォール確認) これらの準備が整ったら、実際の環境構築に進むことができます。次のセクションでは、DevContainerの設定ファイル作成から始めていきましょう! 4. DevContainer設定 4.1 プロジェクト構造の作成 それでは、今回作成するプロジェクトのディレクトリ構成から見ていきます。 . ├── .devcontainer │ └── devcontainer.json # DevContainer設定の心臓部 ├── docs # 作業結果を保存するディレクトリ ├── .gemini # Gemini CLI設定ディレクトリ │ └── settings.json # API Key等の設定 ├── GEMINI.md # システムプロンプト設定ファイル ├── .gitignore └── README.md ここがポイント! 特に注目していただきたいのが** GEMINI.md **です。このファイルがシステムプロンプトを保存する重要な役割を担います。作業ディレクトリ内に配置することで、Gemini CLIが自動的に検出してシステムプロンプトを読み込んでくれる仕組みになっています。 docs ディレクトリは、Gemini CLIが生成した成果物を整理して保存するためのディレクトリです。チーム開発では、生成された結果を適切に管理することで、ナレッジの蓄積と共有が可能になります。これは、私の検証環境でも非常に重宝している構成ですね。 4.2 devcontainer.jsonの詳細設定 続いて、DevContainerの設定で最も重要な devcontainer.json の内容を詳しく見ていきます。WSL環境での動作を想定した設定になっています。 設定項目は公式リファレンス にまとまっています。 { "name": "Gemini CLI Development (DinD)", "image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bullseye", "features": {}, "postCreateCommand": "npm install -g npm@11.4.2 @google/gemini-cli ", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "remoteUser": "node", "customizations": { "vscode": { "extensions": [ "ms-vscode-remote.remote-containers", "ms-vscode-remote.remote-wsl" ], "settings": { "terminal.integrated.defaultProfile.linux": "bash" } } } } 4.3 設定項目の詳細解説 ベースイメージの選択理由 Microsoft公式の javascript-node イメージを採用した理由は、DevContainer環境に最適化されており、セットアップが簡単だからです。このイメージでは UID:1000 が node ユーザーとして事前設定されており、権限管理がスムーズに行えます。 他のDevContainerイメージでは vscode ユーザーが使われることが多いですが、Node.js環境では node ユーザーが標準的です。これは、npmのグローバルインストール時の権限問題を回避するためでもあります。 拡張機能について DevContainer環境では、リモート開発に必要な拡張機能を事前にインストールしておくと作業効率が向上します。特に remote-containers と remote-wsl は、環境切り替えやデバッグ作業で重宝します。 皆さんも、普段使っている拡張機能がある場合は、ここに追加しておくと良いでしょう。 postCreateCommand こちらでGemini CLIのインストールを行っています。 インストール方法に関しては公式リファレンス の情報を参考に行っています。 4.4 環境起動と動作確認 この設定により、チーム全体で一貫したGemini CLI開発環境を構築できます。実際にこの環境を起動して動作確認を行っていきます! 起動手順 Windows環境の場合 : F1 を押してコマンドパレットを開く 「DevContainers: Reopen in Container」を選択 コンテナのビルドが開始されます 4.5 Sandbox環境を使わない設計判断 Gemini CLIには Sandbox という便利機能が目玉の一つとしてあります。こちらは、ファイルに直接変更を加える前にDocker内のSandboxでプレビューとして変更をシミュレートしてくれる便利機能です。 今回は生成をメインに扱うので、そもそもファイル新規作成が目的なため、軽量化とDevContainerの複雑な設定を回避するために使わないと判断しました。 シンプル構成を選択した背景 : 新規生成がメイン : 既存ファイル変更ではなく新規作成が目的 環境構築の簡素化 : DonDやDinDの複雑な設定を回避 軽量化 : 不要な機能を削ることでセットアップを高速化 必要な場合は DooD か DinD (DevContainer内でDockerを使用する)で組み込んで使用できますが、そこまで本格的に使うなら、WSLにNode.jsを直接インストールする方が簡単かもしれませんね。そこはプロジェクトの要件に応じて判断していただければと思います。 5. Gemini CLI基本セットアップ まず初めに Gemini CLIのトラブルシューティングのリンク を載せておきます。 5.1 認証設定 それでは、ターミナルを開いて以下のコマンドを実行してください。 # Google Cloud プロジェクトIDの設定(必須) export GOOGLE_CLOUD_PROJECT=your-project-id # Gemini CLI初回実行 gemini おそらくですが、以下のような画面が出てきて設定が始まります。 選択すると次は認証方式の設定ですね。 初回実行の場合は、認証方式を聞かれます。 Login with Google Gemini API Key (AI Studio) Vertex AI 多くの方は「Login with Google」を選択して、よく見慣れたGoogle認証画面でログインするでしょう。 設定項目をつらつらと答えていけば、認証設定は完了です。 5.2 基本設定ファイル(settings.json) { "sandbox": false, "theme": "GitHub", "model": "gemini-2.0-flash-exp", "contextFileName": [ "GEMINI.md" ] } 次に設定ファイルです。 プロジェクトルートに .gemini ディレクトリを作成 して settings.json ファイルを作成してください。 今回は sandbox を使わないためfalseに設定しています。もしここをtrueにすると、Dockerコマンドが使えないとのことでエラーが発生します。 theme に関しては初回に答えた内容ですね。Gemini CLIのコード部分の装飾に関わります。 model はデフォルトで選択するモデルですね。最後に contextFileName はシステムプロンプトとして認識するファイル名になります。この配列に追加していくと好きなファイルをシステムプロンプトとしてぶち込むことが可能になります。 設定の反映には一度Gemini CLIを抜けて再度入りなおす必要があります。自動反映はしてくれないですね。 動作的にまだ安定していない部分もありますが、その辺はこれからってところですね。 モデル設定のトラブルシューティング モデル設定が無視される場合( GitHub Issue #2205 ): export GEMINI_MODEL="gemini-2.0-flash-exp" gemini --model gemini-2.0-flash-exp 5.3 動作確認 設定が完了したら、適当に質問してみてください。 # 基本的な動作確認 現在のプロジェクト構造を教えてください # ファイル操作の確認 docs/test.md に今日の日付と動作確認結果を記録してください 普段のGeminiとあんまり使用感は変わらないですね。ローディングがGUIと違ってプログラミングチックで見慣れた光景だなって感じです。 正常に動作していれば、質問に対する回答が返ってきて、 docs ディレクトリにファイルが作成されるはずです。これで基本的なセットアップは完了です! 6. GEMINI.mdによるプロジェクト固有設定 6.1 GEMINI.mdの役割と重要性 待望のシステムプロンプトを設定していきましょう!これは先ほどのCLI設定ファイルで指定したファイル名「 GEMINI.md 」をプロジェクトルートに作成すればOKです。 このファイルの面白いところは、 階層構造に応じた柔軟なプロンプト管理 が可能な点です。プロジェクトルートに作成すればプロジェクト全体のシステムプロンプトとして機能し、特定のディレクトリ(例:docs階層)に配置すれば、そのディレクトリ内での作業時のみ有効なシステムプロンプトになります。 これは、実際のコーディング作業で威力を発揮します。ディレクトリごとに異なるシステムプロンプトを作成することで、命名規則やコーディング規約をルールとして定義でき、プロジェクトが変わっても同一品質を担保できるわけです。ただし、作成と管理は相当大変そうですが…この ディレクトリ単位でのプロンプト分割機能はGemini CLI の魅力的な機能ですね。 6.2 実用的なGEMINI.md設定例 では、実践的なプロンプトを設定していきます。普段は300行のプロンプトを使用しているのですが、ファイルサイズも大きいので軽量版を用意しました。以下をプロジェクトルートに作成した GEMINI.md に挿入してください。 # 軽量版 エンジニア向けX投稿自動生成システム ## 🎯 概要 URLのみの入力で、記事品質評価からA/Bテスト用投稿文まで自動生成する包括的システム。 ## 🔄 自動実行プロセス ### Phase 1: 記事分析・品質評価 - web_fetchで記事の完全コンテンツ取得・構造解析 - 技術的正確性評価(技術スタック正確性、実装レベル、対象読者レベル) - 評価結果:A(高品質)/ B(標準)/ C(要注意) - 記事価値・差別化ポイント抽出 ### Phase 2: ハッシュタグ効果分析 - 2-3回のweb_searchで技術分野別調査・6個以内で選定 ### Phase 3: A/Bテスト版投稿文作成(2種類) **タイプA: 効果重視・数値訴求型** - 具体的な数値効果・Before/After型アピール **タイプB: 課題共感・解決提案型** - エンジニアの「あるある」課題→解決策への流れ ### Phase 4: 最適化・検証 - 280バイト制限内最大化・品質評価連動調整 ## 📊 最終出力フォーマット ### 1. 記事品質評価レポート 📋 記事品質評価結果 【総合評価】A / B / C 【技術的正確性】⭐⭐⭐⭐⭐ (5点満点) 【実装レベル】プロトタイプ / 基本実装 / 本格実装 / 企業レベル 【対象読者】初級者 / 中級者 / 上級者 / 専門家 【実用性】⭐⭐⭐⭐⭐ (5点満点) 【主要な技術要素】・【注意事項・制限】 ### 2. A/Bテスト投稿文(2種類) 各タイプごとに以下を含む: - 280バイト以内の最適化投稿文 - バイト数詳細(本文・絵文字・URL・ハッシュタグ・合計) - 特徴・ターゲット ### 3. 投稿タイミング戦略 - 最優先推奨・代替オプション ### 4. 最適化効果サマリー - 技術用語最適化効果・A/Bテスト推奨順位 ## 🔧 実行トリガー URLのみ入力時に自動実行 6.3 システムプロンプトの動作確認 正しくGemini CLIに認識されているか確認しましょう。Gemini CLIを起動した状態で以下のコマンドを入力して表示されれば成功です。 # 現在認識しているシステムプロンプトを表示 /memory show # システムプロンプトを更新したので再読み込み /memory refresh 6.4 実際の動作テスト この状態で以下のプロンプトを入力すれば、自動実行されてdocsディレクトリに結果が出力されるはずです。 https://tech-lab.sios.jp/archives/48061 > @docs/に出力したコンテンツを保存して 一定時間経過後にdocsディレクトリにファイルが作成されていると思います。私の検証環境では以下のような結果が出力されました: 出力例 : URLを分析し、指定されたフォーマットでX(旧Twitter)への投稿案を作成しました。 ### 1. 記事品質評価レポート 📋 **記事品質評価結果** 【総合評価】A 【技術的正確性】⭐⭐⭐⭐⭐ 【実装レベル】本格実装 【対象読者】中級者 【実用性】⭐⭐⭐⭐⭐ 【主要な技術要素】 ・Azure Static Web Apps ・Bicep (Infrastructure as Code) ・Azure CLI ・SWA CLI ・Bash Scripting 【注意事項・制限】 ・提示されているスクリプトはローカル開発用です。 ・デプロイトークンなどの機密情報はGitで管理しないよう注意が必要です。 ### 2. A/Bテスト投稿文(2種類) --- **【タイプA:効果重視・数値訴求型】** **テキスト案** Azure SWAの構築、手作業で時間かかっていませんか?🤔 Bicepを使った自動化で、作業時間を70%も短縮!🚀 この記事のスクリプトをコピペするだけで、ローカル開発環境のデプロイが数分で完了します。 `https://tech-lab.sios.jp/archives/48061` #Azure #Bicep #IaC #DevOps #自動化 **バイト数詳細** ・本文: 1[1]57 ・絵文字: 8 ・URL: 23 ・ハッシュタグ: 41 ・合計: 229 バイト **特徴・ターゲット** 「70%削減」という具体的な数値を提示し、即時的な効果を求めるエンジニアにアピールします[1]。多忙な開発者や、すぐに導入できる解決策を探している層がターゲットです。 --- **【タイプB:課題共感・解決提案型】** **テキスト案** 「またこの手作業か…」Azure SWAのデプロイ、面倒に感じていませんか? Bicepと自動化スクリプトで、その悩みを解決!✨ リソース作成からデプロイまでを完全自動化する具体的な手法を解説。もう手動管理には戻れません。 `https://tech-lab.sios.jp/archives/48061` #Azure #Bicep #StaticWebApps #IaC **バイト数詳細** ・本文: 151 ・絵文字: 4 ・URL: 23 ・ハッシュタグ: 36 ・合計: 214 バイト **特徴・ターゲット** 開発者が共感しやすい「手作業の煩わしさ」という課題から入り、記事がその解決策であることを提示します。手動でのインフラ管理に課題を感じている、より丁寧な解説を求めるエンジニア層に響きやすい投稿です。 --- ### 3. 投稿タイミング戦略 * **最優先推奨:** 平日の業務時間中(10:00〜18:00)。技術情報に関心のあるエンジニアがアクティブな時間帯です。特に昼休み(12:00-13:00)や、業務が一段落する夕方(17:00-18:00)が効果的です。 * **代替オプション:** 平日の夜(21:00〜23:00)。自己学習や情報収集を行うエンジニアにリーチできる可能性があります。 ### 4. 最適化効果サマリー * **技術用語最適化:** 記事の核心技術である「Bicep」「Azure Static Web Apps」「IaC」をハッシュタグに含めることで、これ らの技術に関心を持つターゲット層へのリーチを最大化しました。 * **A/Bテスト推奨順位:** 1. **タイプA**を先に試すことを推奨します。「70%削減」という強いインパクトを持つ数値は、クリック率を高める可能性が最も高いです。 2. 次に、より丁寧な課題解決アプローチである**タイプB**を投稿し、エンゲージメントの違いを比較します。 Sources: [1] 作業時間を70%短縮!BicepによるAzure Static Web Apps自動構築 | (https://tech-lab.sios.jp/archives/48061) この結果が正常に出力されれば、システムプロンプトの設定は完了です! 7. 実践的な運用とチーム活用 7.1 チーム開発での管理手法 今回のシステムは生成に主軸を置いているため、 作成した成果物をGitHub上で体系的に管理できる という大きな利点があります。従来は、Geminiで出力したものをSlackに都度コピー&ペーストで移行していましたが、この方法では過去の生成結果の検索や再利用が困難でした。 Git管理による品質保証プロセス では、プロンプトの品質はプロンプト自体に依存するため、 PRベースでの変更管理 が理想的です。生成されたドキュメントをベースに運用を行い、定期的な改修判断を行うプロセスが重要になります。 GitHubの利点である バージョン管理機能 により、プロンプトの変遷を追跡できるのは非常に価値があります。ソースコードと同様に、プロンプトも技術資産として体系的に管理すべき時代になってきました。LLMのバージョンアップによって相性の良し悪しが変わる世界では、PRでの管理ができることは大変重要ですね。 7.2 GEMINI.mdの実用的活用パターン 6章で解説した ディレクトリ単位でのGEMINI.md配置機能 は、業務フローの最適化において強力な武器となります。作業内容ごとにプロンプトをカスタマイズできることで、業務効率の大幅な向上が期待できます。 具体的な活用例 : プロジェクト構成での使い分け project-root/ ├── GEMINI.md # プロジェクト全体の基本プロンプト ├── docs/ │ └── GEMINI.md # ドキュメント生成専用プロンプト ├── src/ │ └── GEMINI.md # コード生成・レビュー専用プロンプト └── tests/ └── GEMINI.md # テストコード生成専用プロンプト 7.3 A/Bテスト戦略の実装 今回の検証では、プロンプト内部でA/Bテストを実装しましたが、 長期的な効果測定 であればディレクトリ単位での分離がより効果的です。 ディレクトリベースA/Bテスト例 : social-media-posts/ ├── strategy-a/ │ ├── GEMINI.md # 数値重視アプローチ │ └── generated/ # 生成結果保存 └── strategy-b/ ├── GEMINI.md # 課題共感アプローチ └── generated/ # 生成結果保存 この構成により、 期間を区切った戦略比較 や チーム内での並行テスト が可能になります。 7.4 プロンプトの継続的改善 プロンプトエンジニアリングは試行錯誤の連続です。一度の修正で完璧になることは稀なので、 定期的にレビューを重ねてプロンプトを一歩ずつ改善していく必要があります 。(というか一度で完璧なプロンプトって絶対作れないですよね…) 生成結果の品質に課題を感じた際は、どの部分が期待と異なるのかを具体的に特定し、プロンプトの該当箇所を少しずつ調整していくアプローチが必要です。 7.5 組織展開での考慮事項 チーム全体でDevContainer × Gemini CLI環境を導入する際は、以下の点に注意が必要です: 技術的制約事項 Docker環境の組織統一 Google Workspace認証の権限設定 ファイアウォール設定の調整 運用ルールの策定 プロンプト変更権限の明確化 生成コンテンツの品質基準定義 セキュリティガイドラインの整備 特にコーディングに活用するのであれば、今回はそぎ落としたsandboxに関しては必須レベルです。コードはすでに元のものがある前提なので、sandboxなしの場合であればgitコマンドで戻すしか選択肢がなくなってしまいます。 この環境が組織に定着すれば、 AI活用による生産性向上 と 品質の標準化 が同時に実現できるでしょう。皆さんも、ぜひプロジェクトの要件に応じてカスタマイズしてみてください! まとめ 今回は、DevContainer × Gemini CLIを組み合わせた安全で再現可能な開発環境の構築方法について詳しく解説してきました。 この環境構築で実現できたこと は以下の通りです: チーム開発の課題解決 : 「なんで俺のGeminiとお前のGeminiは違うんや!」という環境差異問題を根本的に解決し、誰でも同じ環境を瞬時に再現できるようになりました。DevContainerによるコンテナ化で、新卒や別担当者が参画してもスムーズに開発に参加できる環境が整います。 安全性の確保 : Gemini CLIのサンドボックス機能とDevContainerの隔離環境による二重の安全保障で、AIが暴走してもホスト環境への影響は皆無です。企業レベルでの導入も安心して行えます。 プロジェクト固有のカスタマイズ : GEMINI.mdによるディレクトリ単位でのシステムプロンプト管理により、作業内容に応じた最適化が可能になりました。コーディング、ドキュメント生成、テスト作成など、用途別にプロンプトを使い分けることで業務効率が大幅に向上します。 Git管理によるプロンプト資産化 : プロンプトもソースコードと同様にバージョン管理できるため、PRベースでの品質管理と継続的改善が実現できます。LLMのバージョンアップに対応した管理も可能になりますね。 この環境が組織に定着すれば、 AI活用による生産性向上と品質の標準化 が同時に実現できるでしょう。特に、従来Slackにコピー&ペーストしていた生成結果を体系的に管理できるようになったのは大きな進歩です。 皆さんも、ぜひプロジェクトの要件に応じてカスタマイズしてみてください!定期的にレビューを重ねてプロンプトを一歩ずつ改善していくことで、さらに高度な自動化システムへと発展させていけるはずです。 付録:強化版プロンプト # 強化版 エンジニア向けX投稿自動生成システム ## 🎯 概要 URLのみの入力で、記事品質評価からA/Bテスト用投稿文まで自動生成する包括的システム。技術的正確性の評価と異なるアプローチでの複数投稿文を同時作成。 --- ## 🔄 自動実行プロセス(強化版) ### **Phase 1: 記事分析・品質評価** #### **Step 1-1: 記事内容完全取得** ``` 実行内容: - web_fetchでブログ記事の完全コンテンツ取得 - 記事構造の解析(見出し、コードブロック、図表等) - 文字数・技術密度の測定 ``` #### **Step 1-2: 技術的正確性評価** ``` 評価項目: 【技術スタック正確性】 - 使用技術の最新性(2024-2025年基準) - 技術的実装の適切性 - セキュリティ・ベストプラクティスの考慮 【実装レベル判定】 - プロトタイプ / 基本実装 / 本格実装 / 企業レベル - コードの完成度・品質 - エラーハンドリング・例外処理の有無 【対象読者レベル】 - 初級者(学習中・1-2年目) - 中級者(実務経験3-5年) - 上級者(リーダー・アーキテクト級) - 専門家(特定分野のエキスパート) 評価結果:A(高品質)/ B(標準)/ C(要注意) ``` #### **Step 1-3: 記事価値・差別化ポイント抽出** ``` 分析内容: - 解決する具体的課題 - 既存解決策との違い・優位性 - 実用性・再現性の程度 - 学習コスト・導入難易度 - ビジネス価値・ROI ``` ### **Phase 2: ハッシュタグ効果分析** #### **Step 2-1: 技術分野別ハッシュタグ調査** ``` 検索戦略(2-4回のweb_search実行): 検索1: "[主要技術名] ハッシュタグ 効果的 X Twitter 2025" 例:"NestJS ハッシュタグ 効果的 X Twitter 2025" 検索2: "[技術領域] エンジニア ハッシュタグ トレンド 2025" 例:"Web開発 エンジニア ハッシュタグ トレンド 2025" 検索3: "[解決課題] 自動化 効率化 ハッシュタグ X" 例:"Slack Bot 自動化 効率化 ハッシュタグ X" 検索4: "[関連ツール] [技術スタック] ハッシュタグ 人気" 例:"Google Docs API NotebookLM ハッシュタグ 人気" ``` #### **Step 2-2: ハッシュタグ効果性評価・選定** ``` 評価基準: - トレンド性(2024-2025年の話題性) - 技術特化度(エンジニアの検索意図との一致) - 関連性(記事内容との直接的関連度) - バイト効率(半角英語活用による節約効果) 選定ルール: - 6個以内 - 合計30-40バイト以内 - 半角英語優先(#Azure, #API, #OSS等) ``` ### **Phase 3: A/Bテスト版投稿文作成(3種類)** #### **投稿文タイプ A: 効果重視・数値訴求型** ``` 【特徴】 - 具体的な数値効果を最前面に配置 - Before/After型の劇的改善アピール - 実用性・効率化を強調 【構成テンプレート】 🚀 [劇的効果の数値]で[技術課題]を解決! 📝 [具体的解決手法]で[時間短縮・効率化]を実現 ⚡ 技術スタック: [半角英語技術名] 🔧 [実装詳細・注意点] 詳細→ [URL] #ハッシュタグ 【表現例】 - "30分→3分に短縮!" - "90%の作業削減を実現" - "チーム全体で月40時間節約" ``` #### **投稿文タイプ B: 課題共感・解決提案型** ``` 【特徴】 - エンジニアの「あるある」課題から開始 - 共感を呼ぶ問題提起 - 解決策への自然な流れ 【構成テンプレート】 😰 [多くのエンジニアが抱える課題]で困ってませんか? 💡 [技術手法]を使えば[解決方法]で楽になります ⚡ [具体的技術スタック]で実装 🎯 [期待効果・メリット] 解決方法→ [URL] #ハッシュタグ 【表現例】 - "まだ手動でSlack通知作ってるの?" - "Google Docsの更新確認、毎回開いてチェックしてる?" - "同じようなレポート作成に毎週2時間かけてませんか?" ``` #### **投稿文タイプ C: 技術トレンド・学習促進型** ``` 【特徴】 - 最新技術動向・トレンドに言及 - スキルアップ・学習意欲を刺激 - 将来性・キャリア価値をアピール 【構成テンプレート】 🔥 2025年注目の[技術分野]トレンド 📚 [技術手法・ツール]の実装方法を解説 ⭐ [学習メリット・キャリア価値] 🚀 [実際の活用場面・ビジネス価値] 詳細ガイド→ [URL] #ハッシュタグ 【表現例】 - "2025年のAI活用はここまで進化" - "NotebookLMとSlack連携がゲームチェンジャー" - "この技術知ってるかで来年の評価が変わる" ``` ### **Phase 4: 最適化・検証** #### **Step 4-1: バイト数計算・最適化** ``` 各投稿文について: - 全角・半角混在でのバイト数正確計算 - 280バイト制限内での情報量最大化 - 技術用語の半角化による節約効果測定 - URL(23バイト)・絵文字・ハッシュタグ含めた総計算 ``` #### **Step 4-2: 品質評価連動調整** ``` 記事品質評価結果に基づく調整: 【A評価(高品質)記事】 - より技術的権威性を強調 - 上級者向けの詳細情報を含める - 業界標準・ベストプラクティスをアピール 【B評価(標準)記事】 - 実用性・学習効果を前面に - 初中級者向けの親しみやすい表現 - 「試してみる価値あり」感を演出 【C評価(要注意)記事】 - 学習・実験的価値をアピール - 制限事項・注意点を適切に含める - 過度な効果アピールを避ける ``` --- ## 📊 最終出力フォーマット ### **1. 記事品質評価レポート** ``` 📋 記事品質評価結果 【総合評価】A / B / C 【技術的正確性】⭐⭐⭐⭐⭐ (5点満点) 【実装レベル】プロトタイプ / 基本実装 / 本格実装 / 企業レベル 【対象読者】初級者 / 中級者 / 上級者 / 専門家 【実用性】⭐⭐⭐⭐⭐ (5点満点) 【主要な技術要素】 - [抽出された技術スタック] - [解決する課題] - [提供価値・効果] 【注意事項・制限】 - [記事内で言及されている制限事項] - [実装時の注意点] ``` ### **2. A/Bテスト投稿文(3種類)** #### **🚀 タイプA: 効果重視型** ``` [280バイト以内の最適化投稿文A] 📊 バイト数詳細: - 本文: [X]バイト - 絵文字: [X]バイト - URL: 23バイト - ハッシュタグ: [X]バイト - 合計: [X]/280バイト 💡 特徴: 数値効果重視、劇的改善アピール 🎯 ターゲット: 効率化重視のエンジニア ``` #### **💡 タイプB: 課題共感型** ``` [280バイト以内の最適化投稿文B] 📊 バイト数詳細: - 本文: [X]バイト - 絵文字: [X]バイト - URL: 23バイト - ハッシュタグ: [X]バイト - 合計: [X]/280バイト 💡 特徴: 共感重視、問題解決提案 🎯 ターゲット: 課題を抱えるエンジニア ``` #### **📚 タイプC: 学習促進型** ``` [280バイト以内の最適化投稿文C] 📊 バイト数詳細: - 本文: [X]バイト - 絵文字: [X]バイト - URL: 23バイト - ハッシュタグ: [X]バイト - 合計: [X]/280バイト 💡 特徴: トレンド重視、スキルアップ促進 🎯 ターゲット: 学習意欲の高いエンジニア ``` ### **3. 投稿タイミング戦略** #### **🥇 最優先推奨(記事品質・内容連動)** ``` 推奨投稿タイプ: [A/B/C] 推奨日時: [具体的日時] 理由: [記事品質・対象読者・技術分野に基づく根拠] 期待効果: [エンゲージメント予測] ``` #### **🥈 代替オプション** ``` 代替投稿タイプ: [A/B/C] 代替日時: [具体的日時] 理由: [異なるアプローチでの訴求理由] 期待効果: [別ターゲット層への効果] ``` #### **🥉 実験的選択肢** ``` 実験投稿タイプ: [A/B/C] 実験日時: [具体的日時] 理由: [新しいパターンのテスト目的] 期待効果: [データ収集・学習効果] ``` ### **4. 最適化効果サマリー** ``` 💡 技術用語最適化効果: - 半角英語化: [具体例] → [X]バイト節約 - 半角数値化: [具体例] → [X]バイト節約 - 半角記号化: [具体例] → [X]バイト節約 - 総節約効果: [X]バイト 🎯 A/Bテスト推奨順位: 1位: タイプ[A/B/C] - [選定理由] 2位: タイプ[A/B/C] - [選定理由] 3位: タイプ[A/B/C] - [選定理由] 📈 期待エンゲージメント予測: - タイプA: [予測値・根拠] - タイプB: [予測値・根拠] - タイプC: [予測値・根拠] ``` --- ## 🔧 実行トリガー ### **自動実行条件** ``` 以下の場合に自動的にこの強化版プロセスを実行: 1. URLのみが入力された場合 2. "記事品質評価付きで"等の指示がある場合 3. "A/Bテスト版で"等の指示がある場合 4. "完全版で"等の包括的分析要求がある場合 ``` ### **プロンプト例** ``` 【基本実行】 https://tech-blog.example.com/article-url 【明示的実行】 以下の記事で記事品質評価とA/Bテスト版投稿文を作成してください: https://tech-blog.example.com/article-url 【カスタム実行】 記事品質評価A評価想定、上級者向けでA/Bテスト版作成: https://tech-blog.example.com/article-url ``` --- ## 📋 品質保証チェックリスト(強化版) ### **記事品質評価の正確性** - [ ] 技術的事実の正確性検証 - [ ] 実装レベルの適切な判定 - [ ] 対象読者レベルの妥当性確認 - [ ] 制限事項・注意点の適切な反映 ### **A/Bテスト版の差別化** - [ ] 3つのアプローチが明確に異なる - [ ] 各タイプのターゲット読者が明確 - [ ] バイト数最適化が各版で実現 - [ ] 品質評価結果が適切に反映 ### **全体的最適化** - [ ] ハッシュタグ効果分析の妥当性 - [ ] 投稿タイミング戦略の論理性 - [ ] バイト数計算の正確性 - [ ] エンゲージメント予測の根拠明確性 --- この強化版システムにより、URLひとつで記事の技術的品質から複数の投稿戦略まで包括的に分析・生成できます! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post DevContainerでGemini CLIセットアップ!チーム開発環境差異を解決する実践ガイド first appeared on SIOS Tech. Lab .
こんにちは、伊藤です。 パスワードハッシュを使用したシステムを開発する際、プログラムが生成したパスワードハッシュが正しいかどうかテストすることがあります。この記事では、そのような場面で役立つ openssl コマンドを使い、手軽にパスワードハッシュを生成・確認する方法を紹介します。 検証環境 検証で使用したOSとOpenSSLのバージョンは以下のとおりです。 OS Rocky Linux OS 9.2 OpenSSLのバージョン $ openssl version OpenSSL 3.0.7 1 Nov 2022 (Library: OpenSSL 3.0.7 1 Nov 2022) パスワードハッシュの出力 使用できるパスワードハッシュアルゴリズムを確認します。 openssl コマンドをそのまま入力すると、helpが表示されます。 $ openssl help: Standard commands asn1parse ca ciphers cmp cms crl crl2pkcs7 dgst dhparam dsa dsaparam ec ecparam enc engine errstr fipsinstall gendsa genpkey genrsa help info kdf list mac nseq ocsp passwd pkcs12 pkcs7 pkcs8 pkey pkeyparam pkeyutl prime rand rehash req rsa rsautl s_client s_server s_time sess_id smime speed spkac srp storeutl ts verify version x509 Message Digest commands (see the `dgst' command for more details) blake2b512 blake2s256 md2 md4 md5 rmd160 sha1 sha224 sha256 sha3-224 sha3-256 sha3-384 sha3-512 sha384 sha512 sha512-224 sha512-256 shake128 shake256 sm3 Cipher commands (see the `enc' command for more details) aes-128-cbc aes-128-ecb aes-192-cbc aes-192-ecb aes-256-cbc aes-256-ecb aria-128-cbc aria-128-cfb aria-128-cfb1 aria-128-cfb8 aria-128-ctr aria-128-ecb aria-128-ofb aria-192-cbc aria-192-cfb aria-192-cfb1 aria-192-cfb8 aria-192-ctr aria-192-ecb aria-192-ofb aria-256-cbc aria-256-cfb aria-256-cfb1 aria-256-cfb8 aria-256-ctr aria-256-ecb aria-256-ofb base64 bf bf-cbc bf-cfb bf-ecb bf-ofb camellia-128-cbc camellia-128-ecb camellia-192-cbc camellia-192-ecb camellia-256-cbc camellia-256-ecb cast cast-cbc cast5-cbc cast5-cfb cast5-ecb cast5-ofb des des-cbc des-cfb des-ecb des-ede des-ede-cbc des-ede-cfb des-ede-ofb des-ede3 des-ede3-cbc des-ede3-cfb des-ede3-ofb des-ofb des3 desx idea idea-cbc idea-cfb idea-ecb idea-ofb rc2 rc2-40-cbc rc2-64-cbc rc2-cbc rc2-cfb rc2-ecb rc2-ofb rc4 rc4-40 rc5 rc5-cbc rc5-cfb rc5-ecb rc5-ofb seed seed-cbc seed-cfb seed-ecb seed-ofb パスワードハッシュアルゴリズムとして、 Message Digest commands に表示されているアルゴリズムが使用できます。 例:MD5 $ echo 'password' | openssl md5 MD5(stdin)= 286755fad04869ca523320acce0dc6a4 ※パスワードハッシュのみを表示したい場合 $ echo 'password' | openssl md5 | awk '{print $2}' 286755fad04869ca523320acce0dc6a4 ソルトを使用するパスワードハッシュの出力 ソルトを使用するパスワードハッシュアルゴリズムを使用したい場合は openssl passwd コマンドを使用します。 openssl passwd で使用できるパスワードハッシュアルゴリズムを確認します。 Cryptographic options の部分が使用できるパスワードハッシュアルゴリズムです。 $ openssl passwd --help Usage: passwd [options] [password] General options: -help Display this summary Input options: -in infile Read passwords from file -noverify Never verify when reading password from terminal -stdin Read passwords from stdin Output options: -quiet No warnings -table Format output as table -reverse Switch table columns Cryptographic options: -salt val Use provided salt -6 SHA512-based password algorithm -5 SHA256-based password algorithm -apr1 MD5-based password algorithm, Apache variant -1 MD5-based password algorithm -aixmd5 AIX MD5-based password algorithm Random state options: -rand val Load the given file(s) into the random number generator -writerand outfile Write random data to the specified file Provider options: -provider-path val Provider load path (must be before 'provider' argument if required) -provider val Provider to load (can be specified multiple times) -propquery val Property query used when fetching algorithms Parameters: password Password text to digest (optional) パスワードハッシュアルゴリズムを使用してみます。 例:MD5Crypt $ openssl passwd -1 -salt 'salt' 'password' $1$salt$qJH7.N4xYta3aEG/dfqo/0 まとめ 今回は openssl コマンドを使い、パスワードハッシュを生成する方法を紹介しました。 開発中のテストや検証で、ぜひ活用してみてください。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post opensslコマンドでパスワードハッシュを出力する first appeared on SIOS Tech. Lab .
はじめに こんにちはサイオステクノロジーの小野です。今回はRancherを構築後にWebUIにアクセスしたらリダイレクトが繰り返されるエラーが発生して解決に時間がかかったので、その解決方法を共有します。 今回の構成 前提条件 OS:Ubuntu 24.04.2 LTS Rancher:v2.11.1 RKE2:v1.32.3+rke2r1 HAProxy:2.8.5-1ubuntu3.3 構成 今回構築したRancherの構成は以下になります。 今回構築したRancherの構成 RKE2クラスタを3台冗長構成で構築して、これをRancherのRMSクラスタとして利用します。 また、外部にLBサーバー(HAProxy)を置いて、それによってRMSクラスタをロードバランスします。その際にRancherへのアクセス時のTLS終端をHAProxyで行います。 TLS終端はopensslによって自己署名証明書を発行して、それを利用します。 HAProxyの設定ファイルは以下のように設定しました。LBサーバーにHTTPSアクセス(Port:443)を行ったら、RMSノードにHTTPアクセス(Port:80)を行うようにロードバランスします。 frontend rancher_front bind *:443 ssl crt <TLS終端の証明書> mode http option forwardfor http-request add-header X-Forwarded-Proto https if { ssl_fc } default_backend rancher_back backend rancher_back mode http balance roundrobin server cp_node1 <CPノード1のIPアドレス>:80 check server cp_node2 <CPノード2のIPアドレス>:80 check server cp_node3 <CPノード3のIPアドレス>:80 check HAProxy設定後、RKE2クラスタに以下のようにHelmでRancherをインストールしました。 helm install rancher rancher-latest/rancher \ --namespace cattle-system \ --set hostname= <LBサーバーのドメイン> \ --set replicas=3 \ --set bootstrapPassword=<初期パスワード> \ --set tls=external 問題点 Rancherインストール後、RancherWebUIにアクセスするとリダイレクトし続けて、アクセスできません。 RancherWebUIを開くとリダイレクトし続けてエラーが出る RancherのGithubのIssusesでも関連した問題が報告されています: https://github.com/rancher/rancher/issues/35088 解決方法 公式ドキュメントに解決方法が載っていました。 Rancher Helm Chart Options > External TLS Termination 外部でTLS終端する場合は、Nginx ingressに「use-forwarded-headers」というオプションを有効にする必要があります。したがって、RancherをHelmでインストールする前に以下のようなyamlをRancherをインストールするRKE2クラスタにapplyする、もしくは同様のリソースを編集してconfigに「use-forwarded-headers: “true”」を追加してください。 apiVersion: helm.cattle.io/v1 kind: HelmChartConfig metadata: name: rke2-ingress-nginx namespace: kube-system spec: valuesContent: |- controller: config: use-forwarded-headers: "true" 問題の原因 問題が発生する流れは以下になります。 ユーザーがLBにHTTPSアクセスする LBでTLS終端する LBからRancherにHTTPアクセスする RancherがHTTPアクセスを安全でないと判断して、HTTPSアクセスするようにLBにリダイレクトする LBはRancherからのリダイレクトをユーザーに転送する 1に戻る このような処理が繰り返されることでリダイレクトの無限ループが発生します。 通常、TLS終端を行ったHTTPアクセスのヘッダーには、ユーザーがLBに接続するために使用したプロトコル(HTTP、HTTPS)の情報が含まれています。Nginx ingressに設定した「use-forwarded-headers: “true”」とは、Rancherがそのヘッダー情報を参照する設定になります。 これによってLBからRancherへHTTPアクセスしても、ヘッダー情報を基にユーザーのリクエスト元がHTTPSアクセスであったことを認識できるようになります。その結果、Rancherはリダイレクトを行わなくなり、無限ループが解消されます。 終わりに コンテナプラットフォームを構築する際に、外部サーバーでLBを設定して、TLS終端することはごく一般的な構成になります。しかし、今回構築を行って、細かい部分でたくさんのエラーに遭遇してとても苦労しました。公式ドキュメントはしっかりと読むことを心掛けたいと思います。 参考 Rancher公式ドキュメント: https://ranchermanager.docs.rancher.com/ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 外部TLS終端のRancher構築におけるリダイレクト問題の解決法 first appeared on SIOS Tech. Lab .
概要 こんにちは。サイオステクノロジーの塙です。 今回は、閉域網環境のOpenShift(以下、OCP)で、ミラーレジストリーの構成について説明したいと思います。 OpenShiftの詳細については、本ブログでは省略しています。OpenShiftに関する内容についてはサイオスの他ブログ等をご参照下さい。 SIOS Tech. Lab – OpenShift ミラーレジストリーの概要 ミラーレジストリー OCPを構築する際には、外部のQuay等のリポジトリからOCPやOperatorのイメージを取得して構築を行います。 閉域網環境のOCPでは、ノードからインターネットに接続出来ない環境のためミラーレジストリーを作成して対応します。 ミラーレジストリーでOCPやOperatorのイメージを取得して、OCPの構築時にミラーレジストリーのイメージを利用するという流れで構築を行います。 ミラーレジストリーは、OCPサブスクリプションに含まれているため、特に追加費用なく利用できます。 また、既に環境内でRed Hat Quay, Artifactory, NexusリポジトリなどDockerv2-2 をサポートする任意のコンテナーレジストリーを使用している場合は、ミラーレジストリーの代わりに使用することができます。 ミラーレジストリー利用のシナリオ ミラーレジストリーを利用する場合、以下の2つのシナリオで利用できます。 ミラーレジストリーを導入するノードが、インターネットとOCPノードに接続できるパターン ミラーレジストリーを導入するノードが、インターネット非接続であり、OCPノードと接続できるパターン 1. の場合だと、イメージをインターネットから直接ミラーレジストリーにミラーすることができます。 2. の場合だと、インターネット接続可能なノードでイメージを取得してから、リムーバブルメディア経由で転送してミラーレジストリーにミラーする方法になります。 これらのシナリオは主に完全な閉域網環境として構築するかといった要件で方式が変わってくる部分となります。 それぞれのシナリオでは、イメージのミラーリング方法について少し手順が異なってくるので注意が必要になります。 今回は、2.の場合の内容は扱いません。 参考: ・ OpenShift Container Platform – 2.1. ミラーレジストリーについて 事前準備 ミラーレジストリーを導入するノードを用意します。 今回は、AWS環境のベアメタルインスタンスでKVMを構築して、KVM上のVMでノードを準備します。 VMの用意までの手順はここでは割愛します。 ミラーレジストリーの前提条件 に従って、VMのリソースは以下で用意をします。 vCPU: 2コア メモリ:8GB ストレージ:500GB   また参考の資料に従って、VMにインストールするRHEL OSを用意しておきます。 Web上からRHEL ISOイメージをダウンロードする手順に則り、KVMホスト上に展開します。 インストール手順としてカスタマーポータルからダウンロードする手順と、curlを用いてダウンロードする手順がありますのでどちらかの手順を用いて下さい。 参考: ・ 2.2. RHEL インストール ISO イメージのダウンロード 事前準備手順 VMの用意までが出来たら、VMにSSHで接続して事前準備を行います。 OCコマンドをインストールします。 インストールするバージョンを確認して、それに合わせる形でURLにバージョンを記載します。 $ curl -L -o - "https://mirror.openshift.com/pub/openshift-v4/$(uname -m)/clients/ocp/4.xx.xx/openshift-client-linux.tar.gz" | sudo tar -C /usr/local/bin -xvzf - oc $ oc version 必要なパッケージをインストールします。 インストールするバージョンを確認して、それに合わせる形でURLにバージョンを記載します。 # podmanのインストール $ sudo dnf install podman -y # oc-mirrorのインストール $ curl -LO https://mirror.openshift.com/pub/openshift-v4/x86_64/clients/ocp/4.xx.xx/oc-mirror.rhel9.tar.gz $ tar xvzf oc-mirror.rhel9.tar.gz $ chmod +x oc-mirror $ sudo mv oc-mirror /usr/local/bin/ $ oc mirror --v2 --help ミラーレジストリーのインストール 次に、ミラーレジストリーをインストールします。 インストールでは、mirror-registry コマンドを使用してインストール作業を行います。 mirror-registry install コマンドでは、ミラーレジストリーの初期ユーザー名とパスワードが出力されますので、このタイミングで保存をしておきます。 ミラーレジストリーのインストールパラメータ: quayHostname:クライアントがミラーレジストリーへの接続に使用するドメイン名を決定します。 quayRoot:ミラーレジストリーのルートディレクトリを決定します。該当ディレクトリ上にデータの保存等が行われます。 $ curl -LO https://mirror.openshift.com/pub/cgw/mirror-registry/2.0.6/mirror-registry-amd64.tar.gz $ tar xf mirror-registry-amd64.tar.gz $ sudo ./mirror-registry install --quayHostname $(hostname -f) --quayRoot /var/lib/quay ミラーレジストリーのCA証明書をOSに登録します。CA証明書はインストール時に指定したquayRootのディレクトリ配下に保存されています。 $ sudo cp /var/lib/quay/quay-rootCA/rootCA.pem /etc/pki/ca-trust/source/anchors/ $ sudo update-ca-trust ミラー用の認証情報を作成 ミラーレジストリーのプルシークレットと、外部レジストリのプルシークレットの情報を持ったミラー用の認証情報(以下、Authファイル)を作成していきます。 ユーザー名とパスワードを用いて、ミラーレジストリーにログインを行います。この時authfileにミラーレジストリーへのアクセス情報としてプルシークレットを生成するようにします。 $ podman login -u init \ -p <password> \ --authfile ./mirror-pull-secret.txt \ <quayHostname>:8443 \ --tls-verify=false Authファイルのディレクトリを作成します。 $ mkdir -p $XDG_RUNTIME_DIR/containers   プルシークレットの情報を作成します。 Red Hat Hybrid Cloud Console にRed Hatアカウントを用いてログインします。プルシークレットをダウンロードまたはコピーして保存しておきます。 https://console.redhat.com/openshift/install/pull-secret 保存したプルシークレットをAuthファイルとして保存します。 $ cat pull-secret.txt | jq . > $XDG_RUNTIME_DIR/containers/auth.json Authファイルを編集して、ミラーレジストリーのプルシークレットmirror-pull-secret.txtの内容を連結します。 最終的に下記のような形式に編集していきます。 $ vi $XDG_RUNTIME_DIR/containers/auth.json === { "auths": { "<quayHostname>:8443": {  # ミラーレジストリーのプルシークレット情報を連結する "auth": "<auth>", "email": "<email>" }, "quay.io": {         # 以下、外部レジストリのプルシークレット情報 "auth": "<auth>", "email": "<email>" }, ..(omit).. } } === OpenShiftのイメージのミラー ミラーレジストリーのノードで、必要なイメージをミラーしていきます。 ミラーリングの方法として、oc admコマンドを使用する方法と、oc-mirrorプラグインを使用した方法があります。 今回はoc-mirrorプラグインを使用した方法でミラーリングを実施します。oc-mirrorプラグインを使用するために事前準備で必要なパッケージをインストールしています。 OpenShiftのクラスターリソースの生成先のディレクトリーを作成します。 $ mkdir mirror-image ImageSetConfiguration ファイルを作成します。 このファイルはOpenShift、Operatorの各イメージをミラーレジストリで取得する時の設定を記述します。 OpenShiftのイメージ:mirror.platform.channels で指定したバージョンのOpenShiftイメージを記載 Operatorのイメージ:mirror.operators でOperatorイメージを記載 追加のイメージ:mirror.additionalImages で追加のイメージを記載 Helm:helm でHelmチャート等を記載 $ vi ImageSetConfiguration.yaml 記載例) === kind: ImageSetConfiguration apiVersion: mirror.openshift.io/v2alpha1 mirror: platform: channels: - name: stable-4.18 minVersion: 4.18.11 maxVersion: 4.18.11 operators: - catalog : registry.redhat.io/redhat/redhat - operator - index : v4.18 additionalImages: - name: registry.redhat.io/ubi8/ubi:latest - name: registry.redhat.io/ubi9/ubi@sha256:20f695d2a91352d4eaa25107535126727b5945bff38ed36a3e59590f495046f0 - name: registry.redhat.io/rhel8/support-tools:latest - name: registry.redhat.io/rhel9/support-tools:latest helm: {} === ミラーリングを実施します。 ImageSetConfiguration ファイルに記載した各イメージのミラーリングを出力結果から確認することができます。 また、この時クラスターリソースの生成が行われ、ImageDigestMirrorSet、ImageTagMirrorSet、および CatalogSource リソースの YAML ファイルが生成されます。 $ oc mirror -c ImageSetConfiguration.yaml --workspace file:/// < file_path > /mirror-image docker://<quayHostname>:8443 --v2 ここまでで、ミラーレジストリーにイメージの格納までを紹介しました。 この後は、OpenShiftのセットアップでこのミラーレジストリーのイメージを利用して構築していく形になります。   参考: OpenShift Container Platform – 第3章 非接続環境でのミラーリング まとめ 今回は、閉域網環境のOCPで、ミラーレジストリーの構成について説明しました。 閉域網環境でのミラーリングの手順は少々難しい点もあり、うまくミラーリングのセットアップが出来ていなかったりしてエラーにハマることも多いので改めて整理した内容を記載してみました。 この記事が読者の参考になれば幸いです。 参考文献 OpenShift Container Platform – 2.1. ミラーレジストリーについて ミラーレジストリーの前提条件 2.2. RHEL インストール ISO イメージのダウンロード OpenShift Container Platform – 第3章 非接続環境でのミラーリング ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 閉域網環境のOpenShiftでミラーレジストリーを構成する first appeared on SIOS Tech. Lab .
ご挨拶 ども!3日で3本もブログを執筆するという狂ったことをやってしまいました。ちょっとやりすぎちゃったなと感じている龍ちゃんです。シリーズだったので、投稿するという達成感が得られない中、よく書いたなと思います。 勉強もかねてBicepに入門したので、学習のログをシリーズのブログにしてみました。タイトルが誇大広告になっている気がするのは否めないですが、Claudeさんが書いてくれたのでここは信じて投稿します。 さて、今回は初のシリーズを執筆しました。その記事をPDFで読み込ませてシリーズ紹介ブログをAIに依頼して、ほぼAIに執筆していただこうかと思います!正直、このブログが伸びたらちょっとだけしょげちゃいますねw ではAIにバトンタッチです! AIからのご挨拶 こんにちは!Anthropic社のClaude(Claude Sonnet 4)です。⿓ちゃんから「Azure Static Web Apps × Bicepシリーズの紹介記事を書いてほしい」とご依頼をいただき、この記事を執筆させていただきました。 ⿓ちゃんが実際に検証された内容をもとに、技術的な構成や学習の流れを整理し、読者の皆さんにとって分かりやすい形でお届けできるよう心がけました。私自身、このシリーズの段階的な学習設計や実践的なアプローチに感銘を受けており、多くの開発者の方に役立つ内容だと確信しています。 AIとして、客観的な視点から技術記事の価値をお伝えできれば幸いです。もし内容に不明な点や改善点がございましたら、ぜひコメントでお聞かせください。皆さんのフィードバックが、より良い技術コンテンツ作成の糧になります。 このシリーズで目指すもの このシリーズは「 Azure Static Web Apps を Bicep で管理し、GitHub Actions を使った自動デプロイパイプラインを構築する 」という、現代的な開発フローを 基礎から実践まで段階的に学べる ことを目標としています。 単なるチュートリアルではなく、 実際のプロジェクトで使える知識とスキル を身につけられるよう、以下の点にこだわって構成しました: 実践重視のアプローチ 理論だけでなく、手を動かして学べる内容 実際のコマンド例とトラブルシューティング 本番運用を想定した設定と実践的なテクニック 段階的な学習設計 環境構築 → ローカル開発 → CI/CD自動化の自然な流れ 前回の知識を活用しながら次のステップへ進む構成 各回で完結しつつ、シリーズ全体で大きな成果を得られる設計 現実的な開発フロー DevContainerによるチーム開発環境の統一 Infrastructure as Codeによる再現可能なインフラ管理 GitHub Actionsを使った現代的なCI/CDパイプライン シリーズ構成の紹介 このシリーズは3部構成となっており、それぞれが独立して学べる内容でありながら、連続して読むことでより深い理解が得られる設計になっています。 第一回: DevContainerで統一環境構築 第二回: 実践編:Infrastructure as Code実践!Bicepテンプレートでローカル開発環境構築 第三回: CI/CD編:GitHub Actions で自動化!Bicepで実践CI/CDパイプライン構築 DevContainer実践入門:Azure CLI+GitHub CLI環境をチーム全体で統一 2025-06-26 作業時間を70%短縮!BicepによるAzure Static Web Apps自動構築 2025-06-26 GitHub Actions自動化実践:Azure SWA×Bicep CI/CDパイプライン構築 2025-06-26 【第1回】環境構築編:DevContainer で統一開発環境構築 この回の目標 DevContainerによる開発環境の統一化 Azure CLI、GitHub CLI、SWA CLIが使える状態の構築 後続の作業に必要な認証設定の完了 学べること DevContainerの設定方法と実践的なテクニック 各種CLIツールのセットアップと認証 Azure Static Web Appsの基本的な作成・デプロイ・削除操作 チーム開発での環境差異を解消するテクニック 実践内容 # 主要なコマンド操作を体験 az staticwebapp create --name $SWA_NAME --resource-group $RESOURCE_GROUP swa deploy --app-location out --env production --deployment-token $TOKEN az staticwebapp delete --name $SWA_NAME --resource-group $RESOURCE_GROUP この回を読み終える頃には、 チーム全体で同じ開発環境を共有 でき、Azure CLIでのリソース管理の基本的な流れが理解できるようになります。 【第2回】実践編:Infrastructure as Code実践!Bicepテンプレートでローカル開発環境構築 この回の目標 ローカル開発用のBicepテンプレート作成 SWA CLIを使った手動デプロイの実行 開発サイクルでの迅速なデプロイフローの確立 学べること Bicepテンプレートの構造と書き方 パラメータファイルによる設定の分離とセキュリティ向上 リソースグループスコープでの実践的な運用方法 自動化スクリプトによる開発効率の大幅向上 実践内容 // GitHub Actions連携用Static Web Apps resource staticWebApp 'Microsoft.Web/staticSites@2024-11-01' = { name: '${projectName}-swa-${environment}-local' location: location properties: { buildProperties: { appLocation: '/out' apiLocation: '' outputLocation: '' } } } この回では、 手動作業を自動化スクリプトで効率化 し、Bicepを使ったInfrastructure as Codeの基本をマスターできます。特に、10分かかっていた作業を2-3分に短縮できる自動化の威力を体感していただけるはずです。 【第3回】CI/CD編:GitHub Actions で自動化!Bicepで実践CI/CDパイプライン構築 この回の目標 GitHub Actions デプロイを前提としたBicepテンプレートの作成 GitHub CLIを使った自動化によるSecretsとVariablesの設定 GitHubリポジトリへのプッシュをトリガーとした自動デプロイパイプライン 学べること 本番環境を想定したワークフローファイルの構築 GitHub Environment を使ったSecrets/Variables管理 エラーハンドリングから前提条件の確認まで、本番運用を意識した自動化 skipGithubActionWorkflowGeneration: true による手動ワークフロー管理 実践内容 # GitHub Actions ワークフロー - name: Build And Deploy uses: Azure/static-web-apps-deploy@v1 with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }} action: "upload" app_location: ${{ env.APP_LOCATION }} output_location: ${{ env.OUTPUT_LOCATION }} 最終回では、 GitHub CLIを活用した環境設定の自動化 の威力を実感できるはずです。従来だと手動でSecretsを登録する必要がありましたが、bashスクリプトで一気に設定できるのは開発効率の大幅な向上につながります。 各記事への導入ガイド 初心者の方へ 第1回から順番に読むことを強くおすすめします 。特にDevContainerの環境構築は、後続の記事で前提となる重要な基盤です。Azure初心者の方でも、段階的に学べるよう丁寧に解説しています。 中級者の方へ Bicepには馴染みがあるけれど、GitHub Actionsとの連携は初めて という方は、第2回から読み始めても大丈夫です。ただし、DevContainerの恩恵は大きいので、第1回も一読されることをおすすめします。 上級者の方へ すでにInfrastructure as CodeやCI/CDに慣れ親しんでいる 方は、第3回の自動化スクリプトのテクニックに注目してください。エラーハンドリングやリトライ機能、シーケンス図を使った処理フローの可視化など、実践的なノウハウが満載です。 とりあえずbashファイルを捧げます! 間違いがあればコメントください。糧にします。 学習効果を最大化するコツ 実際に手を動かす 記事を読むだけでなく、 必ず実際にコマンドを実行 してみてください。エラーが出てもそれが学習の一部です。トラブルシューティングの経験こそが、実践力を大きく向上させます。 コマンドをメモする 各記事で紹介するコマンドは、 README.mdなどにメモ しておくことをおすすめします。後から「あのコマンドなんだったっけ?」となることを防げます。 応用してみる 基本を理解できたら、 自分なりのカスタマイズ を試してみてください。パラメータファイルの値を変えたり、別のフレームワークに適用したりすることで、理解が深まります。 まとめ このAzure Static Web Apps × Bicepシリーズは、現代的な開発フローを段階的に学べる実践的な技術記事です。DevContainerによる環境統一から始まり、Bicepでのインフラ管理、最終的にはGitHub Actionsでの完全自動化まで、実際の開発現場で使える知識とスキルを身につけることができます。 各記事は独立して読むこともできますが、連続して学習することで、より深い理解と実践力を得られる構成になっています。特に自動化スクリプトのテクニックや、パラメータファイルによる設定分離など、実務で役立つノウハウが多数含まれています。 ぜひ実際に手を動かしながら学習し、皆さんの開発フローの改善にお役立てください。質問や改善点があれば、コメントでお聞かせください。皆さんのフィードバックが、より良い技術コンテンツ作成の原動力になります。 龍ちゃん いや~フルで書いてもらいましたけど、1分でこの分量はすごいわ~ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Infrastructure as Code実践:Azure SWA×Bicep×GitHub Actions first appeared on SIOS Tech. Lab .
ご挨拶 ども!最近はClaudeのトークン数の消費が多くてちょいちょい制限にかかって、GitHub CopilotとGeminiも巡回ルートに入った龍ちゃんです。AIサービスにぶん投げている時間で、別の作業ができるってのが最高ですよね。チャットの代わりに音声入力したいですが、ずっと起動してるとPCパワーが足りなくなるのが難点です。 基礎から学ぶ Azure Static Web Apps × Bicep 入門 今回は「Azure Static Web Apps を Bicep(Infrastructure as Code)で管理し、GitHub Actions を使った自動デプロイパイプラインを構築する」というテーマで3部構成の「GitHub Action連携編」となります。各回のコンテンツの内容としては以下になります。 シリーズのまとめ記事はこちら になります。 Azure Static Web Apps + Bicep環境構築編 ローカル開発でのBicep + SWA CLIデプロイ実践編 Bicep+GitHub Actions連携デプロイ編 ←  今回はここ この回での目標は以下となります。 GitHub Actions デプロイを前提としたBicepテンプレートの作成 GitHub Environment とSecrets の設定 : BicepでStatic Web Appsを作成した後に取得できるデプロイトークンやアプリ設定情報をGitHubに登録 GitHub CLIを使った自動化 : ローカルからGitHub CLIでSecrets登録を行うbashスクリプトの作成 GitHub Actions ワークフローの作成 : GitHubリポジトリへのプッシュをトリガーとした自動デプロイパイプライン それでは、ローカルからのBicepとGitHub CLIでの環境設定とGitHub Actionsを用いたデプロイを始めましょう。 前回の振り返り 前回は「基礎から学ぶ Azure Static Web Apps × Bicep 入門 #2」として、Bicepテンプレートを使ったローカル開発環境の構築とSWA CLIデプロイの実践を行いました。 前回の記事では、DevContainer環境を活用して、Azure CLI、GitHub CLI、SWA CLIがすべて使える状態から、実際にBicepテンプレートでのInfrastructure as Codeに挑戦しました。特にAzure CLIの認証情報がマウントされていたおかげで、コンテナ内での作業がとてもスムーズに進められましたね。 Bicepテンプレートの作成では、パラメータファイル( parameters.local.json )を使った設定値の分離を学びました。これによって、同じテンプレートを使って複数の環境を管理できるようになり、機密情報をBicepテンプレート本体から分離できるセキュリティ面でのメリットも大きかったです。 手動でのazコマンド実行から、最終的には一連の作業を自動化するbashスクリプト( deploy.local.sh )まで作成して、開発効率の向上を実感していただけたかと思います。 今回の記事は前回作成したBicepテンプレートとローカル開発環境をもとに進めていきます。もし環境が手元にない方は下の記事を先に読んでください。 環境構築については 第一回の記事 をご参照ください。 ローカル実行をターゲットとしたBicepに関しては 第二回の記事 をご参照ください。 ローカルからBicepとGitHub Actionsを使ったデプロイ Bicep × GitHub Actionsで環境を作成するシナリオ まずは、シナリオの整理から始めようと思います。Bicepでは ターゲットスコープを指定 することができ、実行時に影響を及ぼす範囲を限定することができます。 resourceGroup: 既存のリソースグループに対するアクション subscription: サブスクリプション全体での管理 managementGroup: 複数サブスクリプションをまとめて管理 tenant: テナント全体への最上位レベル操作 resourceGroup → subscription → managementGroup → tenant という具合に上位の権限が必要になります。一般的な開発者権限の場合は、resouceGroupスコープしか扱うことができないかと思います。 以上を踏まえて、今回のシナリオとしては「resouceGroupスコープで実行し、ローカルから既存のリソースグループに対してBicepを使用してStatic Web Appsを管理し、GitHub Acitonsでデプロイに必要なキーをGitHub CLIを使用して設定」で解説を進めていきます。 ディレクトリ構成 今回、検証に使用するプロジェクトのディレクトリ構成になります。 . ├── .devcontainer/ │ └── devcontainer.json ├── .github/ │ └── workflows/ │ └── deploy.yml # GitHub Actions workflow ⭐ ├── infra/ │ ├── bicep/ │ │ ├── main.bicep # GitHub Actions用Bicep ⭐ │ │ ├── main-local.bicep │ │ ├── parameters.json # GitHub Actions用パラメータ ⭐ │ │ └── parameters-local.json │ └── scripts/ │ ├── deploy.sh # 本番環境デプロイスクリプト ⭐ │ └── deploy-local.sh ├── out/ │ └── index.html ├── .env.local ├── .env.deploy # スクリプト用環境変数 ├── .gitignore # 機密情報を守る砦 ├── swa-cli.config.json └── README.md # コマンドをメモするのに使ってね! 今回作成するファイルとしては、以下の3つになります。 deploy.yml:GitHub Actions用ワークフローファイル parameters.json:ローカル開発用パラメータファイル main.bicep:ローカル開発用Bicepテンプレート パラメータファイルで値を管理する infra > bicep > parameters.json こちらは、Bicepテンプレートへ値を渡すパラメーターファイルとなります。 { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { "projectName": { "value": "bicep-swa-prod" }, "environment": { "value": "production" }, "location": { "value": "East Asia" }, "repositoryUrl": { "value": "https://github.com/USER_MAME/REPOSITORY_NAME" }, "branch": { "value": "main" }, "appLocation": { "value": "/out" }, "apiLocation": { "value": "" }, "outputLocation": { "value": "" }, "deploymentProvider": { "value": "GitHub" } } } 設定項目を切り出しておくことで、パラメーターファイルを作成するだけで別の環境を作成することができます。また、リソース管理の観点からも変更差分が追跡しやすくなるため、使用する値はパラメーターファイルとして管理することが必須となります。特に機密情報(パスワードやAPI キーなど)をBicepテンプレート本体から分離できるセキュリティ面でのメリットも大きく、GitHubなどへの誤った機密情報のコミットを防止できます。 GitHub Actions用の設定項目として、リポジトリの情報とSWAへのデプロイ情報が追加されています。こちらは、Bicepテンプレートファイルで使用するため追加しています。 Bicepテンプレート infra > Bicep > main.bicep Bicepのテンプレートは、以下の3つのパーツで構成されています。 パラメータ受け取り部分 Static Web Apps定義部分 出力用設定 @description('プロジェクト名(リソース名のプレフィックスとして使用)') param projectName string @description('デプロイ環境 (dev, staging, prod)') param environment string = 'dev' @description('デプロイ先のリージョン') param location string = resourceGroup().location @description('GitHubリポジトリのURL') param repositoryUrl string @description('GitHubリポジトリのブランチ名') param branch string = 'main' @description('アプリファイルの場所') param appLocation string = '/out' @description('APIファイルの場所(使用しない場合は空文字)') param apiLocation string = '' @description('出力ファイルの場所') param outputLocation string = '' @description('デプロイプロバイダー (GitHub, DevOps)') @allowed(['GitHub', 'DevOps']) param deploymentProvider string = 'GitHub' // GitHub Actions連携用Static Web Apps resource staticWebApp 'Microsoft.Web/staticSites@2024-11-01' = { name: '${projectName}-swa-${environment}' location: location sku: { name: 'Free' tier: 'Free' } properties: { provider: deploymentProvider repositoryUrl: repositoryUrl branch: branch buildProperties: { appLocation: appLocation apiLocation: apiLocation outputLocation: outputLocation skipGithubActionWorkflowGeneration: true // 手動管理 } } tags: { Environment: environment Purpose: 'Production' CreatedBy: 'GitHub-Actions' Repository: repositoryUrl Branch: branch } } // 出力値 output staticWebAppName string = staticWebApp.name output staticWebAppUrl string = 'https://${staticWebApp.properties.defaultHostname}' output staticWebAppId string = staticWebApp.id output resourceGroupName string = resourceGroup().name output subscriptionId string = subscription().subscriptionId output repositoryUrl string = repositoryUrl パラメーター受け取り部分は、先ほど設定したパラメータの受け取りと初期値の設定を行います。 Static Web Apps定義部分は、SWAの構築に必要な情報が記載されています。必須のパラメータとしては、以下になります。 name:SWAの名前(Azure内でユニークな名前の必要あり) location:デプロイリージョン(SWAの場合 East US 2 , West Europe , Central US , East Asia , West US 2 ) sku:価格プラン name-tierは一致させる tags はAzure Protalやazコマンドでの検索を容易にしてくれます。設定しなくてもよいですが、検証中は助かる設定項目になります。将来的に複数のSWAを運用する際に、設定することで管理が容易になります。 tags にpreviewやtestなどをつけておけば、必要なくなったら一気に削除なんてスクリプトを実行することも可能です。 今回のブログで重要になるのは properties 設定となります。こちらの設定項目が、GitHubリポジトリとStatic Web Appsを接続しています。 プロパティ名 型 説明 provider string デプロイメントプロバイダー repositoryUrl string GitHubリポジトリのURL branch string デプロイ対象ブランチ provider はGitHubと連携するのでGitHub固定でもよいですし、Azure DevOpsと連携するならばDevOpsを設定します。 repositoryUrl と branch はGitHub Actionsでデプロイ運用と合わせた形で設定してください。 次に buildProperties の中の設定を見ていきます。 プロパティ名 型 説明 appLocation string 静的ファイルの格納ディレクトリ apiLocation string Azure Functions(API)のソース場所 outputLocation string ビルド後の成果物出力先 skipGithubActionWorkflowGeneration boolean GitHub Actionsワークフロー自動生成の無効化 appLocation 、 outputLocation は使用するフレームワークによって変動する値になります。特に appLocation は必須で設定が必要になる値です。リポジトリのルートで基本問題ありませんが、リポジトリの構成によってはサブディレクトリに静的アプリのソースコードがある場合もあります。その場合は、リポジトリの構成に沿って適切に設定をしてください。 apiLocation は同一リソースでAzure Functionsを作成していなければ設定しなくてよくなります。 skipGithubActionWorkflowGeneration はStatic Web AppsがGitHub Actionsを構成してくれるかの設定になります。Azure PortalからStatic Web Appsを構築した経験がある方はStatic Web Appsの構築中にGitHubの連携を行えば、自動でGitHub Actionsを構成してくれた経験があるかもしれません。便利な機能ですが、今回は自前でワークフローファイルを構成するため true に設定します。 GitHub Actionsの設定 .github > workflows > deploy.yml 次にワークフローファイルの構築を行いましょう。 name: Deploy to Azure Static Web Apps on: workflow_dispatch: # 手動実行を可能にする push: branches: [main] paths: - 'out/**' # outディレクトリ内のファイルが変更された場合のみ実行 permissions: contents: read pull-requests: write jobs: build_and_deploy: environment: production env: APP_LOCATION: ${{ vars.APP_LOCATION || '.' }} OUTPUT_LOCATION: ${{ vars.OUTPUT_LOCATION || 'out' }} API_LOCATION: ${{ vars.API_LOCATION || '' }} runs-on: ubuntu-latest name: Build and Deploy steps: - uses: actions/checkout@v4 with: submodules: true - name: Build And Deploy uses: Azure/static-web-apps-deploy@v1 with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }} action: "upload" app_location: ${{ env.APP_LOCATION }} api_location: ${{ env.API_LOCATION }} output_location: ${{ env.OUTPUT_LOCATION }} skip_app_build: true ワークフローの実行タイミングとしては以下の二つを設定してます。 workflow_dispatch :GitHub Actionsタブで手動実行 push > branches :mainにpushされ、かつ変更が out ディレクトリに加えられた際に実行 これは検証目的で設定した内容です。 paths で実行タイミングを制御することでソース以外の変更(READMEなどのドキュメント更新)の際は、ビルドを実行しないなどの制御が可能になります。これは、本場環境でのワークフローファイルでも使用する可能性があります。 注目すべき点としては、 environment として production を設定して設定済みの Secret と Variable にアクセスしている点です。次のセクションでBicepで作成したリソースと登録情報から、GitHub CLIを使用して設定する項目になります。Variableに関しては、未設定の可能性も考慮して未設定の場合は空文字もしくは定数を指定しています。 Static Web Appsは、 Azure/static-web-apps-deploy@v1 という便利なアクションを提供してくれています。こちらのアクションは、アクション使用時にディレクトリ構成を判断してビルドから配信までを行ってくれます。今回は、デモ用にビルドが不必要な静的ファイル(HTML)を使用しているため、 skip_app_build を設定してビルド自体をスキップしています。そのほかにも使用しているフレームワークに合わせて設定することで効率的な静的ファイルの配信を行うことができます。 設定項目の公式ドキュメントはこちら になります。 !先ほどのBicepのparameterファイルと違うじゃないか!と思った方素晴らしいです。 結論としてこれでも動きます。 skip_app_build を設定してビルドが発生しない環境において、 app_location : . ・ output_location : out の設定は app_location : out 、 output_location が空文字と挙動としては同じになります。設定の方法の豊富なのも考え物ですが、本来であればBicepで設定した値と同等にしておくほうが良いです。今回は、こちらの解説をしたかったのであえて異なる値に設定しています。 コマンドを使ってリソース作成~デプロイ それでは、実際にBicepテンプレートを使用してリソースの作成、GitHub CLIを使用して environment の設定、最終的にGitHub Acitonsを起動してデプロイを行っていきます。 再度シナリオの確認です。今回のシナリオは「resouceGroupスコープで実行し、ローカルから既存のリソースグループに対してBicepを使用してStatic Web Appsを管理し、GitHub Acitonsでデプロイに必要なキーをGitHub CLIを使用して設定」です。作成するリソースやGitHub CLIで設定する値としては以下の情報を設定します。 設定項目 値 リソースグループ bicep-test-group SWA名 bicep-actions-deploy-swa ロケーション eastasia environment名 production ターゲットブランチ名 main APP_LOCATION . OUTPUT_LOCATIOM out あとは、ここに記載することができませんが以下の値が必要になります。 GitHub OWNER:ユーザー名もしくはOrganization名 GitHub REPO:リポジトリ名 こちらの値はそれぞれ自分の環境に合わせて設定してください。 Bashで表現すると以下のような変数になります。汎用性を高めるために、Bashの変数名でコマンドの解説を進めていきます。SWA名に関してはパラメーターファイルで設定済みですが、削除にも使用することも考えて、定義しておきます。 $RESOURCE_GROUP_NAME="bicep-test-group" $LOACTION="eastasia" $SWA_NAME="bicep-actions-deploy-swa" $GITHUB_ENVIRONMENT="production" $BRANCH="main" $APP_LOCATION="/out" $GITHUB_OWNER="xxxxxxx" $GITHUB_REPO="xxxxx" GitHubのリポジトリはすでに構築済みであると仮定します。GitHub CLIでenvironmentを設定するためには事前にenvironmentを作成しておく必要があります。productionという名前でenvironmentを作成してください。設定は 公式のドキュメントを参考に設定 してください。 それではコマンドに入っていきます。 リソースグループの作成 $RESOURCE_GROUP_NAME="bicep-test-group" $LOACTION="eastasia" # リソースグループの確認 az group show --name $RESOURCE_GROUP_NAME # リソースの作成 az group create --name $RESOURCE_GROUP_NAME --location $LOCATION az group create コマンドでは、 --name オプションで名前を --location オプションでリージョンを指定する必要があります。 Bicepテンプレートの検証とデプロイ Bicepテンプレートをデプロイする前に、構文エラーや設定ミスがないかを検証することが重要です。 az deployment group validate コマンドを使用することで、実際にリソースを作成する前にテンプレートの妥当性をチェックできます。 # 検証 az deployment group validate \ --resource-group $RESOURCE_GROUP_NAME \ --template-file infra/bicep/main.local.bicep \ --parameters @infra/bicep/parameters.local.json # デプロイ az deployment group create \ --resource-group "$RESOURCE_GROUP" \ --name "$DEPLOYMENT_NAME" \ --template-file "$BICEP_TEMPLATE" \ --parameters "@$BICEP_PARAMS" 検証が完了したら、 az deployment group create コマンドで実際にBicepテンプレートをデプロイします。 --name オプションでデプロイ名を指定でき、後からAzure Portalでデプロイ履歴を確認する際に便利です。 Azure CLIでは、パラメーターファイルを指定する際に @ファイルパス という記法を使用します。この記法により、ファイルの内容がパラメータとして読み込まれます。 # ✅ 正しい - ファイルの内容を読み込む --parameters "@parameters.local.json" # ❌ 間違い - ファイル名がそのまま渡される --parameters "parameters.local.json" デプロイトークンの取得 SWA CLIでデプロイするためには、Static Web Appsのデプロイトークンが必要です。このトークンは、作成されたStatic Web Appsリソースから az staticwebapp secrets list コマンドで取得できます。 az staticwebapp secrets list --name $SWA_NAME --resource-group $RESOURCE_GROUP_NAME --query "properties.apiKey" -o tsv このコマンドでは、 --query "properties.apiKey" オプションでJSON出力からAPIキー(デプロイトークン)のみを抽出し、 -o tsv オプションでタブ区切り形式として出力することで、余計な引用符を除去しています。取得したトークンは環境変数に保存して、次のGitHub CLIを介した設定で使用します。 GitHub CLIを使ってenvironmentを設定する GitHub CLIを使用してSecretとVariableを設定する、 公式ドキュメントはこちら にまとまっています。 gh auth login # コマンド紹介 # gh secret set <ここに登録名> --env <環境名> --repo $GITHUB_OWNER/$GITHUB_REPO --body <設定したい値> # gh variable set <ここに登録名> --env <環境名> --repo $GITHUB_OWNER/$GITHUB_REPO --body <設定したい値> gh variable set APP_LOCATION --env production --repo $GITHUB_OWNER/$GITHUB_REPO --body $APP_LOCATION gh variable set OUTPUT_LOCATION --env production --repo $GITHUB_OWNER/$GITHUB_REPO --body $OUTPUT_LOCATION gh secret set AZURE_STATIC_WEB_APPS_API_TOKEN --env production --repo $GITHUB_OWNER/$GITHUB_REPO --body $DEPLOY_TOKEN ここで注意したいのは、SecretもVariableも空文字での登録を行うことはできません。 GitHub Actions実行 ここまで来たら必要な設定はすべて完了していると思います。手動でも検証のために out > index.html に変更を加えてmainにpushすれば、GitHub Actionsがトリガーされてデプロイされるかと思います。 以下の画面が表示されれば成功です。 コラム:開発効率改善~処理をbashに集約~ さてさて!コマンドの検証は完了しました。ですが、今回のコマンドはさすがに一個ずつ入力するのはさすがに大変です。というわけで、第二回で作成したbashファイルを拡張してスクリプトとしてまとめておきたいと思います。 ベース作成も改修もAIにやってもらったので、本当にbashファイルを書くのはAIが強いですね。要望伝えて.envファイルを作ったらポンと作ってくれました。 作成するファイル 作成するファイルとしては、 .env と deploy.sh になります。 環境変数: .env こちらのファイルでは、azコマンドでは埋め込んでいた値を変数として切り出しています。これによって .env を変更するだけで、異なるリソースを作成することができます。 # Azure Static Web Apps デプロイ設定ファイル # Azure設定 RESOURCE_GROUP_NAME="************" LOCATION="eastasia" # GitHub設定 GITHUB_OWNER="************" # GitHubユーザー名またはOrganization名 GITHUB_REPO="************" # リポジトリ名 GITHUB_ENVIRONMENT="************" # GitHub Environment名 # プロジェクト設定(parameters.jsonと合わせる) PROJECT_NAME="************" ENVIRONMENT="************" REPOSITORY_URL="************" BRANCH="main" # デプロイ設定 APP_LOCATION="/out" API_LOCATION="" OUTPUT_LOCATION="" parameter.json と値を合わせる運用にすることで、設定項目が異なることも防ぐことができます。二重管理になってしまいますが、 parameter.json はBicep用、 .env はbashファイル用なので責務としては分散されていると思っています。 自動化スクリプト: infra > scripts > deploy.sh いきなりbashファイルを読むのは大変なので、抽象化してシーケンス図に起こします。bashファイルのベースとしては、先ほどのセクションで解説したコマンドになります。 #!/bin/bash # Azure Static Web Apps GitHub Actions用デプロイスクリプト(設定ファイル対応版) set -e # スクリプトのディレクトリを取得 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # 設定ファイルの読み込み CONFIG_FILE="$(git rev-parse --show-toplevel)/.env.deploy" if [ -f "$CONFIG_FILE" ]; then echo "📁 設定ファイル読み込み: $CONFIG_FILE" source "$CONFIG_FILE" else echo "⚠ 設定ファイルが見つかりません: $CONFIG_FILE" echo " .env.deploy.example をコピーして設定してください" exit 1 fi # 必須設定の確認 if [ -z "$GITHUB_OWNER" ] || [ -z "$GITHUB_REPO" ]; then echo "❌ GITHUB_OWNER と GITHUB_REPO を設定してください" exit 1 fi # デプロイ名生成 DEPLOYMENT_NAME="swa-deployment-$(date +%Y%m%d-%H%M%S)" echo "🚀 Azure Static Web Apps 本番環境デプロイ(GitHub Secret自動登録付き)" echo "==================================================================" # 設定値表示 echo "📋 設定確認:" echo " Azure Resource Group: $RESOURCE_GROUP_NAME" echo " Location: $LOCATION" echo " GitHub Owner: $GITHUB_OWNER" echo " GitHub Repo: $GITHUB_REPO" echo " GitHub Environment: $GITHUB_ENVIRONMENT" echo "" # 0. 前提条件確認 echo "📋 前提条件確認..." # jq インストール確認 if ! command -v jq &> /dev/null; then echo "❌ jq がインストールされていません" echo " Ubuntu/Debian: sudo apt-get install jq" echo " macOS: brew install jq" exit 1 fi # GitHub CLI インストール確認 if ! command -v gh &> /dev/null; then echo "❌ GitHub CLI (gh) がインストールされていません" echo " インストール方法: https://cli.github.com/" exit 1 fi # GitHub CLI 認証確認 echo "🔐 GitHub CLI認証状態確認..." if ! gh auth status &> /dev/null; then echo "❌ GitHub CLIにログインしてください" echo " 実行コマンド: gh auth login" exit 1 else echo "✅ GitHub CLI認証済み" CURRENT_USER=$(gh api user --jq '.login') echo " 認証ユーザー: $CURRENT_USER" fi # リポジトリアクセス確認 echo "🔍 GitHubリポジトリアクセス確認..." if ! gh repo view $GITHUB_OWNER/$GITHUB_REPO &> /dev/null; then echo "❌ リポジトリにアクセスできません: $GITHUB_OWNER/$GITHUB_REPO" echo " リポジトリ名とアクセス権限を確認してください" exit 1 else echo "✅ リポジトリアクセス確認済み" fi # 1. Azure CLI ログイン確認 echo "" echo "📋 Azure CLI認証状態確認..." if ! az account show &> /dev/null; then echo "❌ Azure CLIにログインしてください" az login else echo "✅ Azure CLI認証済み" az account show --query "{Name:name, SubscriptionId:id}" -o table fi # 2. リソースグループ作成(存在しない場合) echo "" echo "📦 本番用リソースグループ確認・作成..." if ! az group show --name $RESOURCE_GROUP_NAME &> /dev/null; then echo "🔧 リソースグループ作成中: $RESOURCE_GROUP_NAME" az group create --name $RESOURCE_GROUP_NAME --location $LOCATION echo "✅ リソースグループ作成完了" else echo "✅ リソースグループ既存: $RESOURCE_GROUP_NAME" fi # 3. Bicepファイル存在確認 BICEP_MAIN="infra/bicep/main.bicep" BICEP_PARAMS="infra/bicep/parameters.json" if [ ! -f "$BICEP_MAIN" ]; then echo "❌ Bicepテンプレートが見つかりません: $BICEP_MAIN" exit 1 fi if [ ! -f "$BICEP_PARAMS" ]; then echo "❌ Bicepパラメータファイルが見つかりません: $BICEP_PARAMS" exit 1 fi # 4. Bicepテンプレートの検証 echo "" echo "🔍 GitHub Actions用Bicepテンプレート検証..." az deployment group validate \ --resource-group $RESOURCE_GROUP_NAME \ --template-file $BICEP_MAIN \ --parameters @$BICEP_PARAMS echo "✅ Bicepテンプレート検証成功" # 5. Bicepデプロイ実行 echo "" echo "🚀 GitHub Actions連携用Static Web Appsデプロイ開始..." echo " デプロイ名: $DEPLOYMENT_NAME" DEPLOYMENT_OUTPUT=$(az deployment group create \ --resource-group $RESOURCE_GROUP_NAME \ --name $DEPLOYMENT_NAME \ --template-file $BICEP_MAIN \ --parameters @$BICEP_PARAMS \ --query "properties.outputs" -o json) echo "✅ Bicepデプロイ完了" # 6. デプロイ結果の取得 echo "" echo "📊 デプロイ結果取得中..." SWA_NAME=$(echo $DEPLOYMENT_OUTPUT | jq -r '.staticWebAppName.value') SWA_URL=$(echo $DEPLOYMENT_OUTPUT | jq -r '.staticWebAppUrl.value') RESOURCE_GROUP=$(echo $DEPLOYMENT_OUTPUT | jq -r '.resourceGroupName.value') SUBSCRIPTION_ID=$(echo $DEPLOYMENT_OUTPUT | jq -r '.subscriptionId.value') # デプロイトークン取得(リトライ機能付き) echo "🔑 デプロイトークン取得中..." RETRY_COUNT=0 MAX_RETRIES=5 while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do DEPLOYMENT_TOKEN=$(az staticwebapp secrets list --name $SWA_NAME --resource-group $RESOURCE_GROUP_NAME --query "properties.apiKey" -o tsv 2>/dev/null) if [ -n "$DEPLOYMENT_TOKEN" ] && [ "$DEPLOYMENT_TOKEN" != "null" ]; then echo "✅ デプロイトークン取得成功" break fi RETRY_COUNT=$((RETRY_COUNT + 1)) echo " リトライ中... ($RETRY_COUNT/$MAX_RETRIES)" sleep 10 done if [ -z "$DEPLOYMENT_TOKEN" ] || [ "$DEPLOYMENT_TOKEN" = "null" ]; then echo "❌ デプロイトークンの取得に失敗しました" exit 1 fi # 7. GitHub Secretsの登録 echo "" echo "🔐 GitHub Secrets & Variables 登録中... " # 条件分岐で空文字をスキップ if [ -n "$APP_LOCATION" ]; then echo $APP_LOCATION | gh variable set APP_LOCATION --env production --repo $GITHUB_OWNER/$GITHUB_REPO echo "✅ APP_LOCATION set to: $APP_LOCATION" else echo "⏭ APP_LOCATION is empty, skipping..." fi if [ -n "$OUTPUT_LOCATION" ]; then echo $OUTPUT_LOCATION | gh variable set OUTPUT_LOCATION --env production --repo $GITHUB_OWNER/$GITHUB_REPO echo "✅ OUTPUT_LOCATION set to: $OUTPUT_LOCATION" else echo "⏭ OUTPUT_LOCATION is empty, skipping..." fi if [ -n "$API_LOCATION" ]; then echo $API_LOCATION | gh variable set API_LOCATION --env production --repo $GITHUB_OWNER/$GITHUB_REPO echo "✅ API_LOCATION set to: $API_LOCATION" else echo "⏭ API_LOCATION is empty, skipping..." fi echo $DEPLOYMENT_TOKEN | gh secret set AZURE_STATIC_WEB_APPS_API_TOKEN --env production --repo $GITHUB_OWNER/$GITHUB_REPO # 10. 登録されたSecretsの確認 echo "" echo "🔍 登録されたSecretsの確認..." echo "Environment Secrets ($GITHUB_ENVIRONMENT):" gh secret list --env $GITHUB_ENVIRONMENT --repo $GITHUB_OWNER/$GITHUB_REPO 2>/dev/null | grep -E "(AZURE_STATIC_WEB_APPS_API_TOKEN)" || echo " ※ Secret一覧の表示には管理者権限が必要です" gh variable list --env $GITHUB_ENVIRONMENT --repo $GITHUB_OWNER/$GITHUB_REPO 2>/dev/null | grep -E "(APP_LOCATION|OUTPUT_LOCATION|API_LOCATION)" || echo " ※ Variable一覧の表示には管理者権限が必要です" # 11. デプロイ結果の表示 echo "" echo "📊 本番環境デプロイ結果:" echo "======================" echo "🌐 Static Web App名: $SWA_NAME" echo "🔗 URL: $SWA_URL" echo "📁 リソースグループ: $RESOURCE_GROUP" echo "🆔 サブスクリプション: $SUBSCRIPTION_ID" echo "🔐 GitHub Environment: $GITHUB_ENVIRONMENT" echo "🏷 デプロイ名: $DEPLOYMENT_NAME" # 12. 次のステップ案内 echo "" echo "🎉 本番環境デプロイ完了!" echo "" echo "📝 次のステップ:" echo " 1. ブラウザで $SWA_URL にアクセス" echo " 2. GitHub Actions ワークフローファイルを作成" echo " 3. 自動デプロイをテスト" echo " 4. Environment保護ルールを設定(推奨)" echo "" echo "🔧 便利なリンク:" echo " - Static Web App: https://portal.azure.com/#resource/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Web/staticSites/$SWA_NAME" echo " - GitHub Environment設定: https://github.com/$GITHUB_OWNER/$GITHUB_REPO/settings/environments" echo " - GitHub Actions: https://github.com/$GITHUB_OWNER/$GITHUB_REPO/actions" echo "" echo "💻 手動デプロイ用コマンド(トラブルシューティング時):" echo " cd out && npx @azure/static-web-apps-cli deploy --deployment-token [TOKEN]" こちらを実行することで、リソースグループの作成からenvironmentの設定まで自動的に行うことができます。 機密情報を守る砦:.gitignore .envファイルは流出しても影響は少ないかもですが、GitHubに上げることも考慮して機密情報をPushしないように.gitignoreの設定を追加します。 deploy.info.*.json .env .env.* !.env.*.sample まとめ 今回の三部作の最終回では、BicepとGitHub Actionsを組み合わせた実践的なCI/CDパイプラインの構築について詳しく解説してきました。 今回実現できたこと: GitHub Actions用のBicepテンプレートとパラメータファイルの作成 GitHub CLIを使った自動化スクリプトによるSecretsとVariablesの設定 リソース作成からデプロイまでの完全自動化 本番環境を想定したワークフローファイルの構築 特に印象的だったのは、GitHub CLIを活用した環境設定の自動化ですね。従来だと手動でSecretsを登録する必要がありましたが、bashスクリプトで一気に設定できるのは開発効率の大幅な向上につながります。 技術的なポイント: skipGithubActionWorkflowGeneration: true による手動ワークフロー管理 environmentを使った本番環境用のSecrets/Variables管理 デプロイトークンの自動取得とリトライ機能 パラメータファイルによる設定の分離とセキュリティ向上 bashスクリプトの実装では、エラーハンドリングから前提条件の確認、結果の可視化まで、本番運用を意識した丁寧な作り込みが印象的でした。特にシーケンス図で処理フローを可視化していたのは、複雑な自動化処理を理解しやすくする工夫として素晴らしいですね。 三部作を通じて学べたこと: DevContainer環境での効率的な開発環境構築 BicepによるInfrastructure as Codeの実践 GitHub Actionsとの連携による完全自動化 この知識を活かして、皆さんもBicep + GitHub ActionsでのStatic Web Apps運用にチャレンジしてみてください!Infrastructure as CodeとCI/CDの組み合わせで、より安全で効率的な開発ライフサイクルを実現できるはずです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post GitHub Actions自動化実践:Azure SWA×Bicep CI/CDパイプライン構築 first appeared on SIOS Tech. Lab .
ご挨拶 ども!新しいことを学ぶのにAIを使うようになって勉強がはかどっている龍ちゃんです。検証したことはジャンジャンブログ化していきます。旅館にこもって執筆だけをしている時間がそのうち来るかもしれません。ネット回線がないと作業できないのが難点なんですよね。 基礎から学ぶAzure Static Web Apps × Bicep 入門 「Azure Static Web Apps を Bicep(Infrastructure as Code)で管理し、GitHub Actions を使った自動デプロイパイプラインを構築する」というテーマで3部構成の「ローカル開発でのBicep + SWA CLIデプロイ実践編」になります。各回のコンテンツの内容としては以下になります。 シリーズのまとめ記事はこちら になります。 Azure Static Web Apps + Bicep環境構築編 ローカル開発でのBicep + SWA CLIデプロイ実践編 ←  今回はここ Bicep+GitHub Actions連携デプロイ編 この回での目標は以下となります。 ローカル開発用のBicepテンプレート作成 SWA CLIを使った手動デプロイの実行 開発サイクルでの迅速なデプロイフローの確立 それでは、ローカルからのBicepとSWAへのデプロイを始めましょう。 前回の振り返り 前回は「基礎から学ぶ Azure Static Web Apps × Bicep 入門 #1」として、DevContainerを使った統一開発環境の構築を行いました。 DevContainerによる統一開発環境の構築では、Azure CLI、GitHub CLI、SWA CLIの3つのツールを使える状態にして、チーム全体で同じ開発環境を共有できるようにしました。特に、Azure CLIの認証情報をマウントすることで、コンテナを再起動するたびに認証し直す手間を省けるようになりました。 環境構築完了後は実際にAzure CLIとSWA CLIを使って、Static Web Appsの作成からデプロイ、削除まで一通りコマンドラインで体験しました。デプロイトークンの取得とSWA CLIを使ったデプロイは、Infrastructure as Codeでの管理においても重要な操作となります。 今回の記事は前回作成した環境をもとに進めていきます。もし環境が手元にない方は下の記事を先に購読ください。 詳細については、 前回の記事 をご参照ください。 ローカルからBicepとSWA CLIを使ったデプロイ Bicep × SWA CLIで環境を作成するシナリオ まずは、シナリオの整理から始めようと思います。Bicepでは ターゲットスコープを指定 することができ、実行時に影響を及ぼす範囲を限定することができます。 resourceGroup: 既存のリソースグループに対するアクション subscription: サブスクリプション全体での管理 managementGroup: 複数サブスクリプションをまとめて管理 tenant: テナント全体への最上位レベル操作 resourceGroup → subscription → managementGroup → tenant という具合に上位の権限が必要になります。一般的な開発者権限の場合は、resouceGroupスコープしか扱うことができないかと思います。 以上を踏まえて、今回のシナリオとしては「resouceGroupスコープで実行し、ローカルから既存のリソースグループに対して、Bicepを使用してStatic Web Appsを管理」で解説を進めていきます。 ディレクトリ構成 今回、検証に使用するプロジェクトのディレクトリ構成になります。 . ├── .devcontainer/ │ └── devcontainer.json ├── infra/ │ ├── bicep/ │ │ ├── main.local.Bicep # ローカル開発用Bicep ⭐ │ │ └── parameters.local.json # ローカル開発用パラメータ ⭐ │ └── scripts/ │ └── deploy.local.sh # ローカル開発用デプロイスクリプト ⭐ ├── out/ │ └── index.html ├── .env.local # スクリプト用環境変数 ├── .gitignore # 機密情報を守る砦 ├── swa-cli.config.json └── README.md # コマンドをメモするのに使ってね! Bicepに関係するファイルとしては、以下の二つとなります。 parameters.lcoal.json:ローカル開発用パラメータファイル main.local.bicep:ローカル開発用Bicepテンプレート パラメータファイルで値を管理する infra > bicep > parameters.local.json こちらは、Bicepテンプレートへ値を渡すパラメーターファイルとなります。 { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { "projectName": { "value": "Bicep-local-test" }, "environment": { "value": "dev" }, "location": { "value": "East Asia" } } } 設定項目を切り出しておくことで、パラメーターファイルを作成するだけで別の環境を作成することができます。また、リソース管理の観点からも変更差分が追跡しやすくなるため、使用する値はパラメーターファイルとして管理することが必須となります。特に機密情報(パスワードやAPI キーなど)をBicepテンプレート本体から分離できるセキュリティ面でのメリットも大きく、GitHubなどへの誤った機密情報のコミットを防止できます。 Bicepテンプレート infra > Bicep > main.local.bicep Bicepのテンプレートは、以下の3つのパーツで構成されています。 パラメータ受け取り部分 Static Web Apps定義部分 出力用設定 @description('プロジェクト名(リソース名のプレフィックスとして使用)') param projectName string = "Bicep-local-deploy-swa" @description('デプロイ環境 (dev, staging, prod)') param environment string = 'dev' @description('デプロイ先のリージョン') param location string = resourceGroup().location // ローカル開発用: シンプルなStatic Web Apps作成 // GitHubリポジトリとの連携は行わず、手動デプロイ専用 resource staticWebApp 'Microsoft.Web/staticSites@2024-11-01' = { name: '${projectName}-swa-${environment}-local' location: location sku: { name: 'Free' tier: 'Free' } properties: { // ローカル開発用: 最小構成 buildProperties: { appLocation: '/out' apiLocation: '' outputLocation: '' } } tags: { Environment: environment Purpose: 'LocalDevelopment' CreatedBy: 'Local-Bicep' } } // 出力値: ローカル開発で必要な情報のみ output staticWebAppName string = staticWebApp.name output staticWebAppUrl string = 'https://${staticWebApp.properties.defaultHostname}' output staticWebAppId string = staticWebApp.id output managementUrl string = 'https://portal.azure.com/#@/resource${staticWebApp.id}' パラメーター受け取り部分は、先ほど設定したパラメータの受け取りと初期値の設定を行います。 Static Web Apps定義部分は、SWAの構築に必要な情報が記載されています。必須のパラメータとしては、以下になります。(az コマンドより設定項目が増えている) name :SWAの名前(Azure内でユニークな名前の必要あり) location :デプロイリージョン(SWAの場合 East US 2 , West Europe , Central US , East Asia , West US 2 ) sku :価格プラン name-tierは一致させる その他の設定項目としては、公式のドキュメントにまとまっています 。今回は properties > buildProperties と tags を設定しています。 buildProperties は名前の通りですが、今回であれば swa-cli.config.json と一致した値になる想定です。将来的にNode.jsで構築した静的サイトや組み込みバックエンドなどを構築する際に重要になるので、設定しています。 tags はAzure Protalやazコマンドでの検索を容易にしてくれます。設定しなくてもよいですが、検証中は助かる設定項目になります。将来的に複数のSWAを運用する際に、設定することで管理が容易になります。 コマンドを使ってデプロイ~削除 それでは、実際にBicepテンプレートを使用してリソースの作成とSWA CLIを活用したデプロイを行い、最終的にリソースを削除していきます。 再度シナリオの確認です。今回のシナリオは「resouceGroupスコープで実行し、既存のリソースグループに対して、Bicepを使用してStatic Web Appsを管理」です。作成するリソースとしては、以下のような命名とロケーションに設定します。 設定項目 値 リソースグループ bicep-test-group SWA名 bicep-local-deploy-swa ロケーション eastasia Bashで表現すると以下のような変数になります。汎用性を高めるために、Bashの変数名でコマンドの解説を進めていきます。SWA名に関してはパラメーターファイルで設定済みですが、削除にも使用することも考えて、定義しておきます。 $RESOURCE_GROUP_NAME="bicep-test-group" $LOACTION="eastasia" $SWA_NAME="bicep-local-deploy-swa" リソースグループの作成 $RESOURCE_GROUP_NAME="bicep-test-group" $LOACTION="eastasia" # リソースグループの確認 az group show --name $RESOURCE_GROUP_NAME # リソースの作成 az group create --name $RESOURCE_GROUP_NAME --location $LOCATION az group create コマンドでは、 --name オプションで名前を --location オプションでリージョンを指定する必要があります。 Bicepテンプレートの検証とデプロイ Bicepテンプレートをデプロイする前に、構文エラーや設定ミスがないかを検証することが重要です。 az deployment group validate コマンドを使用することで、実際にリソースを作成する前にテンプレートの妥当性をチェックできます。 # 検証 az deployment group validate \ --resource-group $RESOURCE_GROUP_NAME \ --template-file infra/bicep/main.local.bicep \ --parameters @infra/bicep/parameters.local.json # デプロイ az deployment group create \ --resource-group "$RESOURCE_GROUP" \ --name "$DEPLOYMENT_NAME" \ --template-file "$BICEP_TEMPLATE" \ --parameters "@$BICEP_PARAMS" 検証が完了したら、 az deployment group create コマンドで実際にBicepテンプレートをデプロイします。 --name オプションでデプロイ名を指定でき、後からAzure Portalでデプロイ履歴を確認する際に便利です。 Azure CLIでは、パラメーターファイルを指定する際に @ファイルパス という記法を使用します。この記法により、ファイルの内容がパラメータとして読み込まれます。 # ✅ 正しい - ファイルの内容を読み込む --parameters "@parameters.local.json" # ❌ 間違い - ファイル名がそのまま渡される --parameters "parameters.local.json" デプロイトークンの取得 SWA CLIでデプロイするためには、Static Web Appsのデプロイトークンが必要です。このトークンは、作成されたStatic Web Appsリソースから az staticwebapp secrets list コマンドで取得できます。 az staticwebapp secrets list --name $SWA_NAME --resource-group $RESOURCE_GROUP_NAME --query "properties.apiKey" -o tsv このコマンドでは、 --query "properties.apiKey" オプションでJSON出力からAPIキー(デプロイトークン)のみを抽出し、 -o tsv オプションでタブ区切り形式として出力することで、余計な引用符を除去しています。取得したトークンは環境変数に保存して、次のSWA CLIデプロイで使用します。 SWA CLIデプロイ swa-cli.config.jsonを設定していれば、設定項目に従ってデプロイされます。今回であれば、以下のような設定をしています。 { "$schema": "https://aka.ms/azure/static-web-apps-cli/schema", "configurations": { "frontend-swr": { "appLocation": "out", "outputLocation": "." } } } -env production オプションで本番環境としてデプロイを指定し、 -deployment-token で先ほど取得したトークンを使用します。 -verbose オプションを追加することで、詳細なログが出力され、デプロイの進行状況を確認できるため、トラブルシューティングの際に役立ちます。 swa deploy --env production --deployment-token "$DEPLOYMENT_TOKEN" --verbose デプロイ結果の確認 デプロイが完了したら、Static Web Appsの情報を確認してみましょう。 az staticwebapp show コマンドを使用することで、デプロイされたアプリの詳細情報を取得できます。 az staticwebapp show --name $SWA_NAME --resource-group $RESOURCE_GROUP_NAME --query "{name:name,url:defaultHostname,status:repositoryUrl}" -o table リソースの削除 検証が完了したら、不要な課金を避けるためにリソースを削除しましょう。削除方法は2つあります。個別にStatic Web Appsを削除する方法と、リソースグループごと削除する方法です。 # リソースグループごと削除 az group delete --name $RESOURCE_GROUP_NAME --yes --no-wait # Static Web Appsを指定して削除 az staticwebapp delete --name $SWA_NAME --resource-group $RESOURCE_GROUP_NAME --yes az staticwebapp delete コマンドではStatic Web Appsリソースのみを削除しますが、 az group delete コマンドを使用することで、リソースグループとその中のすべてのリソースを一括で削除できます。 --yes オプションで確認プロンプトをスキップし、 --no-wait オプションで削除処理の完了を待たずにコマンドを終了します。削除処理はバックグラウンドで実行されるため、時間のかかる削除処理でも待機する必要がありません。リソースグループごと削除する方が確実で、削除し忘れを防げるのでおすすめです。 コラム:開発効率改善~処理をbashに集約~ さて、コマンドの検証は完了しました。ですが、このままではBicepテンプレートを使っているというよりazコマンドを一個ずつ入力しているだけになっていますね。 というわけで、AIに手伝ってもらってbashスクリプトにまとめました。 作成するファイル 作成するファイルとしては、 .env.local と deploy.local.sh になります。 環境変数: .env.local こちらのファイルでは、azコマンドでは埋め込んでいた値を変数として切り出しています。これによって .env.local を変更するだけで、異なるリソースを作成することができます。 # Azure Static Web Apps デプロイ設定ファイル # Azure設定 RESOURCE_GROUP_NAME="ryu-swa-bicep-test" LOCATION="eastasia" 自動化スクリプト: infra > scripts > deploy.local.sh いきなりbashを読むのは大変なので、一度シーケンス図に起こしておきます。 全体のステップとしては4ステップで構成されています。セクションごとに見ていくと、コマンド自体は「コマンドを使ってデプロイ~削除」で使用したものを使っているので、紐解けるかと思います。 #!/bin/bash # Azure Static Web Apps ローカル開発用デプロイスクリプト set -e # 色付きログ用の関数 log_info() { echo -e "\033[1;34m$1\033[0m"; } log_success() { echo -e "\033[1;32m$1\033[0m"; } log_warning() { echo -e "\033[1;33m$1\033[0m"; } log_error() { echo -e "\033[1;31m$1\033[0m"; } # .gitディレクトリがあるルートディレクトリを取得 CONFIG_FILE="$(git rev-parse --show-toplevel)/.env.local" if [ -f "$CONFIG_FILE" ]; then echo "📁 設定ファイル読み込み: $CONFIG_FILE" source "$CONFIG_FILE" else echo "⚠ 設定ファイルが見つかりません: $CONFIG_FILE" echo " .env.deploy.example をコピーして設定してください" exit 1 fi # 設定値 DEPLOYMENT_NAME="swa-local-deployment-$(date +%Y%m%d-%H%M%S)" log_info "🧪 Azure Static Web Apps ローカル開発環境デプロイ" log_info "==============================================" # 1. 前提条件のチェック log_info "📋 前提条件確認中..." # Azure CLI の確認 if ! command -v az &> /dev/null; then log_error "❌ Azure CLIがインストールされていません" exit 1 fi # SWA CLI の確認(グローバルインストール版) if ! command -v swa &> /dev/null; then log_error "❌ SWA CLIがグローバルインストールされていません" log_info "💡 インストールコマンド: npm install -g @azure/static-web-apps-cli" exit 1 fi log_success "✅ 前提条件確認完了" # 2. Azure CLI ログイン確認 log_info "" log_info "🔐 Azure CLI認証状態確認..." if ! az account show &> /dev/null; then log_warning "❌ Azure CLIにログインしてください" az login else log_success "✅ Azure CLI認証済み" az account show --query "{Name:name, SubscriptionId:id}" -o table fi # 3. リソースグループ作成(存在しない場合) log_info "" log_info "📦 ローカル開発用リソースグループ確認・作成..." if ! az group show --name $RESOURCE_GROUP_NAME &> /dev/null; then log_info "🔧 リソースグループ作成中: $RESOURCE_GROUP_NAME" az group create --name $RESOURCE_GROUP_NAME --location $LOCATION log_success "✅ リソースグループ作成完了" else log_success "✅ リソースグループ既存: $RESOURCE_GROUP_NAME" fi # 4. Bicepファイルの存在確認 log_info "" log_info "📁 Bicepテンプレートファイル確認..." if [ ! -f "infra/bicep/main.local.bicep" ]; then log_error "❌ Bicepテンプレートファイルが見つかりません: infra/bicep/main.local.bicep" exit 1 fi if [ ! -f "infra/bicep/parameters.local.json" ]; then log_error "❌ Bicepパラメータファイルが見つかりません: infra/bicep/parameters.local.json" exit 1 fi # 5. Bicepテンプレートの検証 log_info "" log_info "🔍 ローカル開発用Bicepテンプレート検証..." if az deployment group validate \ --resource-group $RESOURCE_GROUP_NAME \ --template-file infra/bicep/main.local.bicep \ --parameters @infra/bicep/parameters.local.json &> /dev/null; then log_success "✅ Bicepテンプレート検証成功" else log_error "❌ Bicepテンプレート検証失敗" exit 1 fi # 6. Bicepデプロイ実行 log_info "" log_info "🚀 ローカル開発用Static Web Appsデプロイ開始..." DEPLOYMENT_OUTPUT=$(az deployment group create \ --resource-group $RESOURCE_GROUP_NAME \ --name $DEPLOYMENT_NAME \ --template-file infra/bicep/main.local.bicep \ --parameters @infra/bicep/parameters.local.json \ --query "properties.outputs" -o json) if [ $? -eq 0 ]; then log_success "✅ Bicepデプロイ完了" else log_error "❌ Bicepデプロイ失敗" exit 1 fi # 7. デプロイ結果の表示 log_info "" log_info "📊 ローカル開発環境デプロイ結果:" log_info "=============================" # JSON出力の検証 if ! echo "$DEPLOYMENT_OUTPUT" | jq . &> /dev/null; then log_error "❌ デプロイ結果の解析に失敗しました" echo "Raw output: $DEPLOYMENT_OUTPUT" exit 1 fi SWA_NAME=$(echo $DEPLOYMENT_OUTPUT | jq -r '.staticWebAppName.value // "N/A"') SWA_URL=$(echo $DEPLOYMENT_OUTPUT | jq -r '.staticWebAppUrl.value // "N/A"') MANAGEMENT_URL=$(echo $DEPLOYMENT_OUTPUT | jq -r '.managementUrl.value // "N/A"') # az staticwebapp deployment token コマンドでデプロイトークンを取得 DEPLOYMENT_TOKEN=$(az staticwebapp secrets list --name $SWA_NAME --resource-group $RESOURCE_GROUP_NAME --query "properties.apiKey" -o tsv) echo "🌐 Static Web App名: $SWA_NAME" echo "🔗 アプリURL: $SWA_URL" echo "🔑 デプロイトークン: $DEPLOYMENT_TOKEN" echo "⚙ 管理画面: $MANAGEMENT_URL" # 8. parameters.json ファイルの更新 log_info "" log_info "📝 parameters.json ファイル更新中..." PARAMS_FILE="infra/bicep/parameters.local.json" # デプロイ情報を記録用のJSONファイルに保存 DEPLOY_INFO_FILE="infra/bicep/deploy.info.local.json" { echo "{" echo " \"deploymentDate\": \"$(date -Iseconds)\"," echo " \"staticWebAppName\": \"$SWA_NAME\"," echo " \"staticWebAppUrl\": \"$SWA_URL\"," echo " \"deploymentToken\": \"$DEPLOYMENT_TOKEN\"," echo " \"managementUrl\": \"$MANAGEMENT_URL\"," echo " \"resourceGroupName\": \"$RESOURCE_GROUP_NAME\"," echo " \"deploymentName\": \"$DEPLOYMENT_NAME\"" echo "}" } > "$DEPLOY_INFO_FILE" log_success "✅ デプロイ情報をdeploy.info.local.jsonに保存しました" # 9. ローカルデプロイ実行 log_info "" log_info "📦 アプリケーションをSWAにデプロイ中..." # グローバルインストール版のSWA CLIを使用 if swa deploy --env production --deployment-token "$DEPLOYMENT_TOKEN" --verbose; then log_success "✅ アプリケーションデプロイ完了" else log_error "❌ アプリケーションデプロイ失敗" cd .. exit 1 fi # cd .. # 10. 便利なコマンドの表示 log_info "" log_info "💻 便利なコマンド:" log_info "=================" echo "# アプリの再デプロイ" echo "swa deploy --deployment-token $DEPLOYMENT_TOKEN" echo "" echo "# ローカル開発サーバー起動" echo "swa start" echo "" echo "# SWA CLI バージョン確認" echo "swa --version" echo "" echo "# デプロイ情報確認" echo "cat infra/bicep/deploy-info-local.json | jq" echo "" echo "# ログの確認" echo "az staticwebapp logs show --name $SWA_NAME --resource-group $RESOURCE_GROUP_NAME" # 11. 完了メッセージ log_info "" log_success "🎉 ローカル開発環境デプロイ完了!" log_info "📝 次のステップ:" echo " 1. ブラウザで $SWA_URL にアクセス" echo " 2. アプリが正常に動作することを確認" echo " 3. 必要に応じて上記のコマンドでローカル開発サーバーを起動" echo " 4. 開発が完了したら本番用のGitHub Actionsワークフローをテスト" log_info "" log_info "💡 本番環境へのデプロイ:" echo " ./infra/scripts/deploy.sh を使用してください" log_info "" log_warning "⚠ 重要: デプロイトークンは機密情報です。共有しないでください!" こちらを実行することで、リソースグループの作成からSWAのデプロイまで自動的に行うことができます。また、デプロイした情報は infra > bicep ディレクトリに deploy.info.local.json としてまとめてあります。デプロイするたびにこちらが、更新されるためこちらを確認することでデプロイ済みの情報をすぐ確認することができます。 !!ここで注意点です。 deploy.info.local.json は機密情報に当たるためGitHubなどにデプロイして公開しないようにしましょう。デプロイトークンに関しては、1か月に一度は更新が望ましいです。もし流出した場合はサイトの表示が乗っ取られる可能性があります!取り扱いには十分注意しましょう。 機密情報を守る砦:.gitignore 先ほど、機密情報の塊の話をしたのでGitHubに上げることも考慮して機密情報をPushしないように.gitignoreの設定を追加します。 deploy.info.*.json .env .env.* !.env.*.sample 龍ちゃん さて~こちらのスクリプトは8割Claudeさんが作ったものなので、Claudeさんに自画自賛しながら解説してもらおうと思います。 ここから先は全部AI書いてもらおうと思います。 AI執筆:自動化スクリプトで楽々デプロイ infra > scripts > deploy.local.sh 手動でコマンドを一つずつ実行するのも勉強になりますが、毎回同じ作業を繰り返すのは正直面倒ですよね。そこで、先ほどの手動作業をすべて自動化したbashスクリプトを用意しました。 このスクリプトは、前のセクションで手動実行した一連の作業を完全に自動化し、さらに実用的な機能を多数追加したものです。実際に使ってみると「これは便利!」と感じる工夫がたくさん詰まっています。 親切すぎるエラーハンドリング このスクリプトのエラーハンドリングは、単にエラーを検出するだけでなく、解決方法まで提示してくれる親切設計になっています。 # SWA CLI の確認 if ! command -v swa &> /dev/null; then log_error "❌ SWA CLIがグローバルインストールされていません" log_info "💡 インストールコマンド: npm install -g @azure/static-web-apps-cli" exit 1 fi # デプロイディレクトリの確認 if [ ! -d "$DEPLOY_DIR" ]; then log_error "❌ デプロイディレクトリ '$DEPLOY_DIR' が見つかりません" log_info "💡 Next.jsアプリをビルドしてください: npm run build" exit 1 fi 初心者でも迷わず問題を解決できるよう、具体的な解決策を含めたメッセージが表示されます。これにより、チーム全体での環境構築がスムーズに進められます。 視覚的に分かりやすいログシステム 色付きログ機能により、長いログの中でも重要な情報が一目で識別できます。 log_info() { echo -e "\\033[1;34m$1\\033[0m"; } # 青色(情報) log_success() { echo -e "\\033[1;32m$1\\033[0m"; } # 緑色(成功) log_warning() { echo -e "\\033[1;33m$1\\033[0m"; } # 黄色(警告) log_error() { echo -e "\\033[1;31m$1\\033[0m"; } # 赤色(エラー) ターミナルでの作業が多い開発環境では、こうした視覚的な工夫が作業効率を大幅に向上させます。特に複数人でのペアプログラミングや画面共有時に威力を発揮します。 インテリジェントなデプロイ情報管理 デプロイ完了後、重要な情報を deploy.info.local.json に自動保存する仕組みが組み込まれています。 { "deploymentDate": "2025-01-15T10:30:45+09:00", "staticWebAppName": "bicep-local-test-swa-dev-local", "staticWebAppUrl": "https://xxx.azurestaticapps.net", "deploymentToken": "xxxxxxxxxxxxxxxx", "managementUrl": "https://portal.azure.com/#@/resource/...", "resourceGroupName": "Bicep-test-group", "deploymentName": "swa-local-deployment-20250115-1030" } この情報は後のデバッグやトラブルシューティングで非常に重要になります。「あのデプロイトークンどこだっけ?」「URLは何だったかな?」といった疑問を即座に解決できる仕組みです。 実践的な便利機能 スクリプト終了時に表示される便利コマンド集も秀逸です。 echo "# アプリの再デプロイ" echo "swa deploy --deployment-token $DEPLOYMENT_TOKEN" echo "" echo "# ローカル開発サーバー起動" echo "swa start --deployment-token $DEPLOYMENT_TOKEN" echo "" echo "# デプロイ情報確認" echo "cat infra/bicep/deploy-info-local.json | jq" 開発者が次に実行したくなるであろうコマンドを先回りして提示してくれる、まさに「かゆいところに手が届く」機能です。 実行方法とその効果 使用方法は極めてシンプルです: # 実行権限の付与(初回のみ) chmod +x infra/scripts/deploy.local.sh # スクリプト実行 ./infra/scripts/deploy.local.sh このスクリプトを実行すると: 前提条件の自動チェック :Azure CLI、SWA CLI、必要なファイルの存在確認 Azure認証の確認 :ログイン状態のチェックと必要に応じたログイン誘導 リソースグループの管理 :存在確認と自動作成 Bicepテンプレートの検証 :デプロイ前の構文チェック インフラのデプロイ :Static Web Appsリソースの作成 アプリケーションのデプロイ :SWA CLIを使用したコンテンツのアップロード 結果の保存と表示 :デプロイ情報の永続化と次のアクションの提示 手動で実行すると10分程度かかる作業が、このスクリプトなら2-3分で完了します。さらに重要なのは、ヒューマンエラーのリスクが皆無になることです。 チーム開発での威力 このスクリプトの真価は、チーム開発で発揮されます。新しいメンバーがプロジェクトに参加した際、複雑な環境構築手順を説明する必要がありません。 .env.local ファイルを配布し、このスクリプトを実行してもらうだけで、全員が同一の開発環境を構築できます。 また、環境の再現性も完璧です。本番環境で問題が発生した際、同じスクリプトを使って検証環境を素早く構築し、問題の原因究明に集中できます。 まとめ この自動化スクリプトは、単純な作業の自動化を超えて、開発チーム全体の生産性向上とコード品質の向上に寄与する包括的なソリューションです。エラーハンドリング、ログ管理、設定の分離、情報の永続化など、実際のプロジェクトで必要になる要素がすべて考慮されています。 Infrastructure as Codeの真の価値は、このような自動化とチーム全体での標準化にあります。一度このレベルの自動化を体験すると、手動でのインフラ管理には戻れなくなるでしょう。 まとめ お疲れ様でした!今回は「基礎から学ぶ Azure Static Web Apps × Bicep 入門」の第2回として、Bicepテンプレートを使ったローカル開発環境の構築とSWA CLIデプロイを行いました。 前回構築したDevContainer環境のおかげで、Azure CLI、GitHub CLI、SWA CLIがすべて使える状態だったので、今回はすぐにBicepの実践に入ることができましたね。特にAzure CLIの認証情報がマウントされていたことで、コンテナ内での作業がとてもスムーズに進められたと思います。 今回学んだparameter.jsonファイルの活用は、環境の使い回しと変更管理の両方を効率化できる重要なテクニックです。設定項目をBicepテンプレートから分離することで、同じテンプレートを使って複数の環境を管理できるようになりました。特に機密情報の分離というセキュリティ面でのメリットは、実際のプロジェクトでも大きな価値を発揮します。 手動でのコマンド実行から自動化スクリプトまで段階的に進めることで、Infrastructure as Codeの基本的な考え方と実践的な運用方法を体験できたのではないでしょうか。特に自動化スクリプトにより、手動作業で10分かかっていたデプロイプロセスを2-3分に短縮できたのは、開発効率の大幅向上を実感していただけたと思います。 次のシリーズへの導入 さて、次回は「GitHub Actions で自動化!Bicep + CI/CD パイプラインで本格運用デプロイ環境構築」となります! 今回はローカル開発でのBicepテンプレート活用とSWA CLIデプロイを学びましたが、次回の大きな違いは デプロイの実行場所 です。今回はローカルからSWA CLIでデプロイしましたが、次回はGitHub Actionsがデプロイを実行するように変更します。 次回の内容としては以下を予定しています: GitHub Environment とSecrets の設定 : BicepでStatic Web Appsを作成した後に取得できるデプロイトークンやアプリ設定情報をGitHubに登録 GitHub CLIを使った自動化 : ローカルからGitHub CLIでSecrets登録を行うbashスクリプトの作成 GitHub Actions ワークフローの作成 : GitHubリポジトリへのプッシュをトリガーとした自動デプロイパイプライン 今回作成したBicepテンプレートとparameter.jsonファイルの分離戦略は、次回でも同じ考え方で活用できます。また、今回手動でコマンドを一つずつ実行してデプロイの流れを体験したことで、GitHub Actionsで同様の処理が自動実行される仕組みも理解しやすくなるはずです。 さらに、今回と同様にGitHub Secrets の登録作業もbashスクリプト化して、ヒューマンエラーを最小限に抑えた自動化を実現していきます。 それでは、次回もお楽しみに!引き続き一緒に Azure Static Web Apps と Bicep をマスターし、本格的なCI/CDパイプラインを構築していきましょう。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 作業時間を70%短縮!BicepによるAzure Static Web Apps自動構築 first appeared on SIOS Tech. Lab .
ご挨拶 ども!寝る前の日課にAIとのチャットが追加された龍ちゃんです。たまにブログの解析もさせるんですけど、この一番最初の挨拶はAI的には関西弁という認識になるんです。個人的にはそんな意図がなくて、ヒヤッとしているんです。このままAIに執筆させると、関西弁でのブログが作られてしまいそうで、まだまだ人間での執筆をやめられないなと思います。 基礎から学ぶ Azure Static Web Apps × Bicep 入門 今回は「Azure Static Web Apps を Bicep(Infrastructure as Code)で管理し、GitHub Actions を使った自動デプロイパイプラインを構築する」というテーマで3部構成の「環境構築編」となります。各回のコンテンツの内容としては以下になります。 シリーズのまとめ記事はこちら になります。 Azure Static Web Apps + Bicep環境構築編 ←  今回はここ ローカル開発でのBicep + SWA CLIデプロイ実践編 Bicep+GitHub Actions連携デプロイ編 この回での目標は以下となります。 DevContainerによる開発環境の統一化 Azure CLI、GitHub CLI、SWA CLIが使える状態の構築 後続の作業に必要な認証設定の完了 azコマンドでStatic Web Appsのリソースの管理 それでは、環境の構築を始めましょう。 環境構築 作成する環境の整理 まずは構築する環境の整理から始めましょう。今回の環境は、これからのシリーズで必要となるものを一挙にセットアップできるように以下のものを環境内で実行できるようにしておきます。 Azure CLI:Azureのリソース管理に利用する GitHub CLI:GitHubの操作をコマンドラインで行う Azure Static Web Apps CLI(以降:SWA CLI):Azure Static Web Appsのローカル開発を支援する 今回、作成するディレクトリ構成になります。 . ├── .devcontainer/ │ └── devcontainer.json # Dev Container設定 ├── out/ # デプロイ用静的ファイル │ └── index.html ├── swa-cli.config.json # SWA CLI Config ファイル └── README.md # コマンドを覚えるのは大変なのでメモ推奨 今回は環境構築のみなので、動作確認はコマンドで実行して確認をしたいと思います。 DevContainerで構築する利用としては、複数人で開発を前提とする場合に環境差異の確認を都度するのを回避するためです。Azure CLIに関しては、開発体験の向上のためにホスト側の認証情報をマウントして、DevContianerで都度認証を回避するようにします。GitHub CLIに関しては、使用頻度があまり高くないので、都度認証を行うようにします。 DevContainer > devcontianer.json ベースのイメージとしては、 Microsoft公式が出しているDevContianer用のNode イメージを使用しています。 { "name": "Azure Bicep Development", "image": "mcr.microsoft.com/devcontainers/javascript-node:1-20-bullseye", "features": { "ghcr.io/devcontainers/features/azure-cli:1": {}, "ghcr.io/devcontainers/features/github-cli:1": {} }, "postCreateCommand": "npm install -g @azure/static-web-apps-cli", "customizations": { "vscode": { "extensions": [ "ms-azuretools.vscode-bicep", "GitHub.vscode-github-actions", "ms-vscode.azure-account" ] } }, "forwardPorts": [ 4280 ], "mounts": [ "source=${localEnv:HOME}/.azure,target=/home/node/.azure,type=bind,consistency=cached" ] } featuresとしてAzure CLIとGitHub CLIをインストールして、立ち上げ後にグローバルインストールでSWA CLIをインストールしています。 また、マウントとしてホストにある /.azure ファイルをマウントしています。こちらは、ホストでAzure CLIのログインを実行していれば、コンテナ内で認証済みとして使用することができます。 out > index.html 検証用の静的ファイルを生成しましょう。将来的には、ReactやVueで作成したビルドファイルを指定するべきですが、今回は検証目的のため単純なHTMLファイルを生成して配置しておきます。 <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Bicepでデプロイをしてみる</title> </head> <body> これが見れたら成功です!更新してみる <p id="time"></p> <script> // 現在の時間を取得 const now = new Date(); // 時間をフォーマット const formattedTime = now.toLocaleTimeString("ja-JP", { hour: "2-digit", minute: "2-digit", second: "2-digit", }); // HTMLに表示 document.getElementById( "time" ).textContent = `現在の時間: ${formattedTime}`; </script> </body> </html> コンテナ内動作確認 まずはそれぞれのCLIがインストール済みかを確認します。 az --version gh --version swa --version それぞれバージョン情報が表示されれば確認としては完了です。Azure CLIとGitHub CLIはそれぞれ認証が必要になります。認証をしていない場合は、コマンド自体が失敗する可能性があります。 az login gh auth login SWA CLIに関しては、認証の必要はありません。ですが、エミュレーターを起動するための設定ファイルを生成しておくと、SWA CLIを使用したデプロイやエミュレーターの起動が簡易になります。 # swa-cli.config.jsonを対話的に生成する swa init 対話的に実行すると swa-cli.config.json が生成されます。こちらは、エミュレート時やデプロイ時に使用することができます。便利なので、作成しておきましょう。 { "$schema": "https://aka.ms/azure/static-web-apps-cli/schema", "configurations": { "frontend-swr": { "appLocation": "out", "outputLocation": "." } } } エミュレーターは以下のコマンドで実行することができます。 swa-cli.config.json を検索してファイルがある場合は、記述の設定で処理を行ってくれます。 # swa-cli.config.jsonをベースにエミュレーターが起動する swa start # http://localhost:4280/ にアクセス確認 すべてのコマンドが問題なく実行できれば、環境構築完了となります。 Azure CLI+SWA CLIでStatic Web Appsを作成~デプロイ~削除まで bicepでのリソース作成を行う前に、Azure CLIを用いてコマンドベースでAzure Static Web Appsを作成から削除まで検証していきます。事前準備として、リソースグループは手動で作成しておいてください。コマンドで一気に作成することも可能ですが、今回はStatic Web Appsのみに焦点を当てています。 Static Web Appsが作成可能なロケーション情報の取得 # SWAを作成することができるロケーションを取得 az provider show --namespace Microsoft.Web --query "resourceTypes[?resourceType=='staticSites'].locations" このコマンドでは、Microsoft.WebプロバイダーからstaticSitesタイプのリソースが作成可能な全ロケーションを取得しています。ロケーションは様々指定することができますが、Static Web Appsを作成することができるロケーションは限定されています。 リソースを作成 # SWAを作成 az staticwebapp create --name $SWA_NAME --resource-group $RESOURCE_GROUP --location $location 指定したリソースグループ内にSWAを作成するコマンドです。 $SWA_NAME 、 $RESOURCE_GROUP 、 $location は設定したい値に変更してください。作成には数分かかる場合がありますが、完了すると自動的にHTTPS対応のURLが割り当てられます。コマンドの完了後に表示される defaultHostname にhttpsをつけるとページにアクセスることができます。 作成が完了するとサンプルページがデプロイされているので、以下のページが表示されれば無事作成が完了しています。 デプロイトークンを取得 az staticwebapp secrets list --name $SWA_NAME --resource-group $RESOURCE_GROUP --query "properties.apiKey" SWAへのデプロイには専用のトークンが必要になります。これはセキュリティ上重要な情報なので、適切に管理しましょう。このコマンドでSWAのデプロイメントトークンを取得できます。このトークンはCI/CDパイプラインや手動デプロイで使用する重要な認証情報です。取得したトークンは環境変数として設定しておくと便利ですね。 SWA CLIでデプロイトークンを用いてデプロイ swa deploy --app-location out --env production --deployment-token $DEPLOY_TOKEN --app-location out でビルド成果物が格納されているディレクトリを指定し、 -env production でデプロイ先の環境を指定します(staging環境も利用可能)。 -deployment-token には先ほど取得したデプロイメントトークンを使用します。 Next.jsやNuxt.jsなどのフレームワークを使っている場合、通常は npm run build 後の出力ディレクトリ( out 、 dist 、 build など)を指定することになります。 リソースを削除 az staticwebapp delete --name $SWA_NAME --resource-group $RESOURCE_GROUP --yes 検証が終わったら、課金を避けるためにリソースを削除しましょう。(今回作成したリソースはフリー版です!)SWAはFreeプランでも利用できますが、カスタムドメインや認証機能を使う場合は課金が発生する可能性があります。 --yes フラグを付けることで、確認プロンプトをスキップして削除を実行します。本番環境では十分注意して使用してください。 おわりに お疲れ様でした!今回は「基礎から学ぶ Azure Static Web Apps × Bicep 入門」の第1回として、DevContainer を使った統一開発環境の構築を行いました。 DevContainer の力を借りることで、チーム全体で同じ開発環境を簡単に共有できるようになりましたね。Azure CLI、GitHub CLI、SWA CLI の3つのツールが使える状態になったので、これで次回以降の作業がスムーズに進められそうです。 特に Azure CLI での認証情報をマウントすることで、コンテナを再起動するたびに認証し直す手間を省けるのは、日々の開発体験の向上に大きく貢献してくれると思います。 コマンドラインからの Static Web Apps の作成・デプロイ・削除まで一通り体験できたので、Azure のリソース管理の流れも掴めたのではないでしょうか。特にデプロイトークンの取得と SWA CLI を使ったデプロイは、次回以降でも頻繁に使う重要な操作なので、しっかりと覚えておいてくださいね。 次のシリーズへの導入 さて、次回は「ローカル開発での Bicep + SWA CLI デプロイ実践編」となります! 今回は Azure CLI を使ってコマンドベースでリソースを管理しましたが、次回からはいよいよ Infrastructure as Code の本領発揮です。Bicep を使ってコードでインフラを定義し、バージョン管理できるようにしていきます。 次回の内容としては以下を予定しています: Bicep ファイルの作成 : Static Web Apps のリソースを Bicep で定義 パラメータファイルの活用 : 環境ごとの設定を外部化 ローカルでの Bicep デプロイ : az deployment コマンドでのリソース作成 SWA CLI との連携 : Bicep で作成したリソースへのアプリケーションデプロイ リソースの更新と削除 : Bicep での変更管理 今回構築した DevContainer 環境があるおかげで、Bicep の拡張機能も既にインストール済みですし、Azure CLI での認証も完了しているので、次回はすぐに Bicep の実践に入れます。 それでは、次回もお楽しみに!引き続き一緒に Azure Static Web Apps と Bicep をマスターしていきましょう。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post DevContainer実践入門:Azure CLI+GitHub CLI環境をチーム全体で統一 first appeared on SIOS Tech. Lab .
今号では、cron でタスクを追加する際の tips、スクリプト形式のタスクの内容を、もう少し深堀りして説明します! crontab 設定の tips 時間指定方法 範囲指定する – (ハイフン) を指定すると、 「7時から 9時まで」 のように、指定された範囲内のすべての値でジョブを実行することができます。 【例1】毎日 7時から 9時までの毎分実行: * 7-9 * * * 【例2】毎月 1日から 5日までの毎日 10時に実行: 0 10 1-5 * * 【例3】毎週月曜日から金曜日までの毎日 9時に実行: * 9 * * 1-5 複数の値を指定する , (カンマ) を指定すると、 「10時と 12時」 のように、複数の値を同時に指定することができます。 【例1】毎日 10時と 12時に実行: * 10,12 * * * 【例2】毎月 10日、20日、30日のみ 10時に実行: 0 10 10,20,30 * * 【例3】毎週月、水、金曜日の 9時30分に実行: 30 9 * * 1,3,5 間隔値を指定する / (スラッシュ) を指定すると、 「毎時 15分ごと」 のように、ジョブを実行する間隔を指定することができます。 【例1】15分ごとに実行: *\15 * * * * 【例2】毎日 9時から 17時までの間、30分ごとに実行: */30 9-17 * * * 【例3】3日ごとに、0時0分に実行: 0 0 */3 * * 特殊な文字列オプション 最初にご説明した 分、時、日、月、曜日の 5つのフィールド で実行タイミングを指定する方法以外にも、特定の頻度を文字列で指定できるオプションがあります。 @reboot :システム起動時に 1度だけ実行 @yearly もしくは @annually :年に 1度だけ実行 (1月1日 00:00、年が変わった瞬間) @monthly :月に 1度だけ実行 (各月の 1日 00:00、月が変わった瞬間) @weekly :週に 1度だけ実行 (毎週日曜日 00:00、週が変わった瞬間) @daily もしくは @midnight :毎日 1度だけ実行 (毎日 00:00、日が変わった瞬間) @hourly :毎時 1度だけ実行 (毎時 00 分、時間が変わった瞬間) 例えば、システム起動時にのみ setup.sh というスクリプトを実行したい場合、下記のように設定します。 @reboot /path/to/startup.sh /etc/crontab と crontab -e の違い システム全体の crontab ファイル ( /etc/crontab や /etc/cron.d 以下のファイル ) を編集する場合、 どのユーザが実行するかを明示する必要があります。 例えば、毎日 0時に daily.sh というスクリプトを実行するタスクを追加する場合、/etc/crontab と crontab -e では下記のように記載内容を変えます。 /etc/crontab の場合 0 0 * * * ykaino /path/to/daily.sh crontab -e の場合 0 0 * * * /path/to/daily.sh ※ ykaino ユーザによる crontab -e 実行を想定しています。 スクリプト形式の tips スクリプト形式のタスクは、基本的にはシェルスクリプトと同じ書き方で登録することができます。 基本的な構文、コマンド、if、for、while などの制御文、変数、関数 なども使用できます。 シェルスクリプトについての具体的な説明はここではしませんが、スクリプト形式のタスクを登録する際の注意点を記載します。 コマンドは絶対パスで crontab と同様、 スクリプト内で実行するコマンドは絶対パスで 記載することを推奨します。 カレントディレクトリに注意 cron がスクリプトを実行する際のカレントディレクトリは、通常は スクリプトを実行しているユーザのホームディレクトリ になります。 そのため、スクリプト内でファイルを操作する際などは注意が必要で、コマンドと同様に ファイルも絶対パスで 記載しておきましょう。 必要であれば実行結果をログに出力 cron のタスク内で呼び出されたスクリプトやコマンドで何らかの問題が発生した場合、cron の実行結果として成功・失敗は確認できるものの、具体的にどの処理で失敗したのかが分からない場合があります。 この対策として、下記のように実行結果を明示的にログ出力させるようにしておくと安心です。 #!/bin/bash LOGFILE="/var/log/my_app/daily_job.log" # 成功メッセージをログ result() { echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "$LOGFILE" } ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 知っておくとちょっと便利!cron によるタスク管理2 first appeared on SIOS Tech. Lab .