TECH PLAY

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

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

625

2026年6月3日(水)~6月4日(木)の2日間、 九州教育現場支援EXPO に出展いたします。会場は、マリンメッセ福岡です。 サイオステクノロジーはクラウドとOSS、認証技術のスペシャリスト集団です。SaaSサービスや自動化技術で業務効率化を進め、教育機関のDXを支援します。ブースでは、下記サービスをご紹介しております。 文教向け「証明書自動更新ソリューション」 証明書自動更新ソリューションは、SSLサーバー証明書を一元管理し自動更新します。最長47日化に伴う大学の更新負荷を軽減し、運用コスト削減とセキュリティ強化に貢献します。 文教向け「SIOS Shibboleth IdPサービス」 認証ソリューション「Shibobleth IdP」をコンテナ化し、お客様Azure基盤上にスムーズに導入できる環境を提供します。導入の初期コストを抑えながら、迅速かつ柔軟な運用を実現します。 クラウド型ワークフローシステム「グルージェントフロー」 Microsoft365、Google Workspaceと連携するクラウド型ワークフローシステム。直感的な操作性を実現し高度な経路設計もサポート。学内文書、学外文書、稟議書の電子化を実現します。     お近くにお越しの際は、ぜひご来場ください。 The post 6/3(水)~6/4(木) 九州教育現場支援EXPOに出展します first appeared on SIOS Tech Lab .
2026年6月10日(水)~6月12日(金)の3日間、 AI NATIVE EXPO2026 に出展いたします。会場は、幕張メッセです。 サイオステクノロジーのブースでは、下記サービスをご紹介しております。 SCANOSS ― 生成AIコードに潜むOSSリスクを可視化 SCANOSSは、生成AIが出力したコードを含むソースコードを解析し、内部に含まれるOSSコンポーネントやライセンス情報を可視化するコード解析ツールです。ブラックボックス化しやすい生成AIコードの中身を明らかにし、ライセンス違反や著作権リスクの早期発見を支援します。独自の解析技術により、利用OSSの特定や重複検出を高精度に実行。開発現場におけるOSS利用状況を正確に把握し、リスク管理とコンプライアンス対応を支える基盤を提供します。生成AI時代に求められるOSSリスク可視化を実現します。 お近くにお越しの際は、ぜひお立ち寄りください。 The post 6/10(水)~6/12(金) AI NATIVE EXPO2026に出展します first appeared on SIOS Tech Lab .
「先週なにやってたっけ」を、git log に書いてある状態にしておく ども!Claude Code と毎日仕事している龍ちゃんです。 月曜にやったこと、金曜には忘れてるんですよね。タスクが増えてくると「追い切れる気がしない」になって、ちゃんとやろうと思っても結局やらない、みたいな。 そういう人に向けた話なんですが、前提を先に言っておきますね。2つあって。 全業務(ブログも調査もツール開発もセミナー資料も)を 1つのリポジトリ に突っ込んでいる それは 個人 の進捗管理リポジトリで、作業者は自分1人 この2つが揃ってるから、 git log が自分の活動ログになるんですよね。土台の「全業務を1つのリポジトリに集約してる」話自体は「 Claude Codeで全業務を1リポジトリに一元管理する作業基盤の作り方 」で書いていて、そこで「git の履歴がそのままタスクの棚卸しになる」と予告してたんですが、今日はその中身の話です。 2026-06-02 Claude Codeで全業務を1リポジトリに一元管理する作業基盤の作り方 やってることは特別なことは何もなくて。Claude Code に書かせたコミットメッセージと時刻を git log でちらっと眺めるだけ。マジで小さい Tips なんですが、地味に効いてるのでおすそわけします。 この記事の全体像は、こんな感じです。 なぜコミットログが「作業ログ」になるのか じゃあなんで、ただの git log が作業ログとして読めるのか。理由は2つです。 1つめは、 コミットメッセージを Claude Code に書かせてる こと。差分を読んで「何を・なぜ変えたか」を要約してくれるので、書き方が安定するんですよね。自分で手打ちしてると、つい fix とか wip みたいな雑なログになりがちなんですが(笑)、Claude に書かせると一文できちんと残る。だから後から読み返しても意味がわかります。「”wip” じゃ後から誰も分からないけど、AIに書かせたら実際に役立つ履歴になった」って言ってる人もいて( freek.dev )、これめちゃくちゃ分かるんですよね。 もう1つが、これが今日のポイントになるんですが。理想を言えば、 タスクを「終わる分量」で切る ことなんですよね。1個終わるごとにコミットが1つ落ちる=コミットが「完了の単位」になる。結果、 git log が「終わらせたことが時系列に並んだ列」になる。いわゆる atomic commits ってやつですね。「コミット履歴はコードがどうしてこうなったかを語る”物語”だ」って言ってる人もいて( Telling stories through your commits )、まさにそれを作業ログとして読んでる感覚です。 でも現実は、当日朝にいきなり降ってくるタスクもあるし、月末締めのやつと今週中のやつが混在してたりして。粒度をそろえて設計する、できてるか?というと、まあ僕もできてないです(笑)。 そこで Claude Code にコミットを書かせると、差分を読んでファイル単位で適切な粒度に分けてくれるんですよね。僕がよくやるのは、ダーッと一日走り切って、コミットの分割は後から AI に丸投げするやつ。設計しきれてなくても、それで足りてます。 だから個人で運用する分には、「自分しか見ない=コミット粒度のルールが本来ない世界」でも、 git log がそのまま活動ログになるんですよね。ちなみにこれ、リポジトリを分けてたら履歴も割れて1本で見渡せないんですが、全部1リポジトリだから成立してます。 長めのタスクのときは、いったん途中でコミットを切っておいて、翌日 git log で「どこまでやってたか」を確認してから戻る、という使い方もしてます。 実際、時刻付きで眺めるとこんな感じで並びます。 $ git log --pretty=format:"%cd %s" --date=format:"%m/%d %H:%M" 05/31 23:09 docs(article): 連載のブログ記事と計画を追加 05/31 23:09 docs(research): /copyの調査結果を追加 05/31 16:44 docs(slides): 月次LTのスライドを改稿 05/29 14:32 feat(tool): サムネ生成のオプションを追加 05/29 10:10 docs(seminar): OSCの企画ドキュメントを追加 「いつ・どの領域で・何を終わらせたか」が、ただの履歴なのにちゃんと読める。 何に効くか いちばん多いのはタスクの復帰ですね。中断してたタスクに戻ってきたとき、直前のコミットを見れば「あ、ここまでやって、次これやろうとしてたわ」が一発で思い出せる。長いチャット履歴を遡らなくていいんですよね。 週次・月次の振り返りにも効きます。 git log --since="1 week ago" --author="$(git config user.name)" (月次なら --since="1 month ago" )みたいに期間と自分を絞れば、「この期間やったこと」がそのままリストで出てくる。週報や月次レポートを書くときはもちろん、出した報告を「先月ほんとにこれやってたっけ」と裏取り確認するときにも、ゼロから思い出す作業がなくなって、素材がほぼそこにある状態になります。 あとは地味に、自分の稼働の把握。どの領域にどれだけコミットしてたかを見てると、実際に手を動かして終わらせた量がうっすら見えてくるんですよね。終わる分量で切ってるぶん、コミットの分布が「実際に終わらせた仕事」にわりと近いです。正確な稼働率みたいな数字じゃないですけど、「今週は調査に寄ってたな」「ツール開発、全然進んでないな」くらいの肌感は掴めます。しかもこれ、上司に報告するためじゃなくて自分のためなので、相手が自分のデータな分、忖度ゼロで「先週サボってたな〜」と向き合えるのがいいところです(笑)。 もう1つ意識してるのが、「git に乗らない仕事は、乗せにいく」というやつです。会議とかチャットって、ふつうはリポジトリに残らないじゃないですか。なので僕は、Google Meet の文字起こしはファイルを作ってリポジトリにコミットしてますし、Slack でのレビューも「何を投げて、それに対してどう返ってきたか」をファイルとして保存してます。 もちろん社外秘や他メンバーの発言が混ざるものは入れない、と人間が判断したうえで、ですが 手作業にはなるんですが、「なぜこうなったか」の意思決定の過程って、後から見返したときに価値があるので。作業の進捗はひとつのリポジトリで一元管理しようっていう意識です。まあ、乗せ忘れたものは映らないんですけどね(笑)。 Claude Code のログ分析でも、同じことはできるけど 正直に言うと、同じような振り返りは Claude Code 側のデータでもできます。 claude --resume を叩けば過去セッションの一覧が出て、各セッションのサマリーや最終更新時刻、git ブランチが並ぶので、「このへんで何やってたか」をたどれます。もっとガッツリやるなら、会話ログ(セッションの全文)を分析にかける手もあります。 でも、 見る量が多いんですよね。 会話ログは1セッションで数百行いくし、セッション一覧のサマリーも粒度がまちまちです(セッションに自動でタイトルが付くのは Plan を承認したときなどで、名前を付けてないセッションは最初のプロンプトがそのまま表示されたりする)。ぱっと「何やってたっけ」を解消したいだけなら、ちょっと情報が多い。 その点 git log は「メッセージ+時刻」だけ。見る情報が少ないぶん、さっと振り返るには圧倒的に軽いんです。正直、git log が楽すぎてセッション一覧をわざわざ開く気にならないんですよね(笑)。 逆に、Claude Code のログ分析が向いてるのは、セッションをまたいで「どういう経緯でこの判断に至ったか」とか「自分のクセ・口癖」みたいな、もっと踏み込んだ詳細分析のほうですね。軽く棚卸ししたいだけなら git log、腰を据えて深掘りしたいなら会話ログ、という住み分けです。そっちのガッツリ系をやりたくなったら、また別記事で紹介しますね。 おわりに タスク管理ツールを新しく増やさなくても、コミットメッセージを Claude Code に書かせてるなら、 git log はもう作業ログとして読めます。まずは自分の git log を時刻付きで眺めてみるところから。「あ、先週これやってたわ」が見えてくると、地味に面白いですよ。 ほなまた〜 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post git logが作業ログになる:Claude Codeにコミットを書かせるだけ first appeared on SIOS Tech Lab .
仕事の素材が散らばっていると、AIは手伝いきれない ども!Claude Code と毎日仕事している龍ちゃんです。 調査メモはNotion、コードはGit、ブログの下書きはGoogle Docs、スライドはまた別のツール……仕事の素材って、気づくとあちこちに散らばってませんか。 地味につらいのが「 AIに手伝ってもらいにくい 」こと。せっかく Claude Code を使っていても、調査メモが別の場所にあると「先月の調査をもとに、このコードのことを記事にして」が一発で通らない。毎回こっちが文脈を運ぶ羽目になります。 で、たどり着いた答えはすごくシンプルで。 日常のどんなタスクも、1つのリポジトリの中で進める 。調査も検証も実装もブログも、全部そこでやる。それだけです。 これをやると、いいことが2つあって。1つは、Claude Code が全部読んで横断して手伝ってくれること。もう1つが やった作業が消えずに積み上がる ことなんですよね。ここで言う”積み上がる”は、一度やった調査や検証が、後の別のタスクから参照されて使い回せる状態のことです。バラバラのツールでやると、終わったタスクはそのまま埋もれて消える(うっすらとした記憶で探すのも大変)。でもリポジトリの中で進めれば、調査も検証も全部残って、次の仕事の土台になる。点だった作業が、線でつながっていく感覚です。 この記事では、その「全部1リポジトリ」をどう組んで、どういう連鎖が起きて、どこから始めればいいかを順に話します。 全業務を1つのリポジトリに集約している まず、どんなエンジニアにも共通する話をします。コードを書く人なら、調査も検証も実装も同じリポジトリに置くだけで、「なんでこの設計にしたんだっけ」と悩む時間が減ります。コードの隣に検証メモが残っているし、「あの調査どこだっけ」と探し回らなくていい。仕事の種類は関係ないんですよね。 僕の場合は、さらに発信活動も業務の一部に入っているので、ブログやセミナーも同じ場所に置いています。会社としてテックブログとYouTubeチャンネルを運営していて、情報発信も仕事のうちになっているんですよね。だから開発・調査・発信が全部同じ業務として同じ場所にある、というのが実情です。 ディレクトリがそのまま業務の地図になっていて、こんな感じです。 リポジトリ/ ├── docs/ │ ├── research/ # 調査・リサーチ │ ├── experiment/ # 検証・実験 │ ├── article/ # ブログ記事の下書き │ ├── seminar/ # セミナー企画・登壇資料 │ └── data/ # 業務まわりのデータ置き場 │ ├── blog/ # 公開ブログのスクレイピング │ ├── pv/ # 月次PVデータ │ ├── x-post/ # X(旧Twitter)投稿 │ ├── youtube/ # YouTube用の原稿 │ └── … # チラシ・登壇資料なども └── application/ # 開発・ツール実装 git に乗らない仕事は、乗せにいく コードやドキュメントみたいに自然と git に残るものだけじゃなくて、放っておくと残らない仕事もあえてここに入れています。たとえば会議(Google Meet)の文字起こしや、Slack でもらったレビューのやり取り。ふつうはリポジトリの外で流れて消えていくものなんですが、ファイルにして置いておくと「なんでこう決めたんだっけ」の経緯まで同じ場所にそろうんですよね。手作業にはなるんですけど、後からじわじわ効いてきます。 会議録や Slack のやり取りには、社外秘や他のメンバーの発言(=第三者の個人情報)が混ざりがちです。リポジトリを公開・共有・画面共有する場面で一緒に漏れる導線になるので、そういうものは別管理にするか .gitignore に入れる、保存自体も所属組織のポリシーに沿う、を前提にしてください。 ここで大事にしているのは、 何をリポジトリに入れるか(=AIに読ませるか)は人間が判断している ということです。デリケートな情報は、そもそもリポジトリに置かない=AIにも渡さない。AIに自動で何でも食わせるのではなく、「これは入れてよい素材か」を人の目で一段かませる。僕も「出していい素材」と「社外秘」は置き場を分けていて、後者はそもそもこの仕組みに乗せません。 整理されていること自体が目的じゃないんですよね。「ここに全部ある」という状態が、後の仕事の進めやすさを変えます。次のセクションで具体的に話しますね。 点と点が、線でつながった 「全部1つのリポジトリに入れた」結果、一番大きく変わったのは「知識の使われ方」ですね。 以前は、調査した内容は調査ノートの中で完結していました。記事を書くときは記事フォルダだけ見て、コードを書くときはコードだけ触って、という感じで。それぞれの仕事は「点」として存在していたんですよね。 それが1つのリポジトリに集まると、調査→検証→実装→発信という流れが、ファイルのパスをたどるだけで全部追えるようになりました。 docs/research/ での調査が起点になって、そこから docs/experiment/ での検証に進み、検証結果は2方向に分岐します。エンジニアとしての成果は application/ の実装へ、発信としての成果は docs/article/ のブログ記事やセミナー資料へ。さらにブログが公開されると、PV分析の結果を見て次の調査テーマが決まっていく。こうして、バラバラだった点が線でつながって、ぐるっと循環するようになります。 この「つながり」こそが、僕がやりたかったことの正体だと思っているんです。点のままだと、作業が終わった瞬間に埋もれていく。でも線でつながっていれば、どこかの作業が後の作業の土台になる。調査メモが記事を変え、記事のPVが次の調査を決め、検証の経緯が実装の判断根拠になる。やった仕事が消えずに積み上がっていく、という感覚です。 線でつながっている実例 コードの前に、検証の計画をドキュメントで残す 検証や実験をするとき、僕はいきなりコードから書き始めないんですよね。先に「何を確かめたいのか」「どういう手順でやるのか」「どんな仮説を置くのか」をドキュメントに書いて、整理してから実装に入ります。いわゆるドキュメント駆動ですね。 たとえば最近やった MCP サーバーの検証だと、「返ってくるデータ量やツール定義の書き方が、LLM のトークン消費にどう効くか」という仮説と検証シナリオを先にドキュメントで組み立てて、それから実際に動かすコードを書きました。計画のドキュメントと、それを動かすコードが、同じリポジトリに並んで残ります。 これの何がいいかというと、後から「なんでこの設計にしたんだっけ」となったとき、計画の方を見れば、どんな仮説を立てて、測ってみてどうで、だからこうした、という経緯が全部たどれるんですよね。判断の理由が実装の隣に残っている状態です。お恥ずかしい話、以前はこういう計画や経緯メモは別のドキュメントツールに書いて、そのうち参照されなくなる流れだったので。リポジトリで完結させると「消えない判断根拠」として残り続ける。後で設計を見直すとき、隣のドキュメントを開けば理由がそのまま残っている状態です。 さらに、検証して出た結果(レポート)も同じ場所に書き残しておくと、もっと変わります。 計画 → 実装 → 検証結果 までが1か所にそろうので、その結果をそのままブログやセミナーのネタに使うところまで、一本の線で通るんですよね。「検証したけど、結果どこ行ったっけ」と一回一回情報を探す手間がなくなって、やった検証がそのまま発信の素材になります。 PVの傾向から次のネタが決まる 「隣のネタを拾う」という感覚に近いんですが。Agent Skills まわりの記事が伸びていたので、じゃあ隣のトピックである Issue 操作のネタを調査して記事にしよう、という判断をしたことがありました。PV分析→調査→記事化という一連の流れが、同じリポジトリで完結したんですよね。分析結果のメモと記事のドラフトが同じ場所にあると、「この流れで次は何をすべきか」をAIと一緒に考えやすいし、読んだ分析が次の調査へ自然につながっていく。過去のデータが次の仕事の起点になる、というのが一番わかりやすい実例だと思います。 PVのみを意識してブログを出しているわけではありませんが、データも身近にあることで投稿ブログの分析などがブログ執筆のプロセスに入ってきて、執筆活動の質に少しだけマーケティング的視点を入れることができるようになってきました。 この記事自身が一番の実例 こちらのブログでもデータを集約したことでの恩恵にふんだんにあやかっています。 Karpathy の LLM Wiki や みのるんさんの「Claude Codeですべての日常業務を爆速化しよう」 がモノレポや知識集約まわりで同じ方向のことを書いているのを調査ノートで確認していたので、「集約しようというだけじゃなく、集約すると連鎖が起きるという方向で書こう」と記事の角度を変えられました。巨人たちが同じことを言っているのは、独自の発見というよりむしろ裏付けですね。あくまで参考にさせてもらっている立場です。それでも、調査メモが記事の角度を変えたというのは、先行事例の調べものがそのまま次の判断材料になった一例です。(ちなみに先人たちは巨人って表現をしてきたのはAIですww) なお、検証から記事化するフローの詳細は「 検証→記事化で知見を資産化する 」でまとめています。”知見を資産化”という考え方はこの記事が先行しているので、合わせてどうぞ。スライドも application/slides に置いているので、記事の内容をそのまま流用してセミナー資料も作れます。 過去記事を下敷きにして情報補完する ブログを書いていると「この話、前に別の記事で書いたな。リンク貼っておこう」という場面がよくあります。でも、過去記事を探して、URLを確認して、リンクを貼って……というのを毎回手作業でやるの、地味につらいんですよね。過去記事も同じリポジトリにあれば、Claude が「その話はこの記事で書いてますよ」と拾って、リンク候補まで出してくれる。手で探していたリンク貼りが要らなくなります。 もう一つ、これも小さいけど助かるのが、企画の重複を防げることです。「これ書こう」と思って調べ始めたら、実は企画レベルで前に着手していた、みたいなことがたまにあるんですよね。過去の記事も企画メモも全部同じ場所にあると、書き始める前に「似たやつ、もうありますよ」とAIが気づいてくれる。書き上げてから「これ前にやってたやつだ」と気づく事故が減ります。 どちらも、過去の仕事が文脈ごと同じ場所に残っているから成り立つ話で。別のツールに書き散らしていたら、そもそも探し出せなかったと思います。 始め方は1本のファイルから 一気に全業務を移す必要はないんですよね。おすすめは「まずリポジトリを1つ作る」こと。それだけです。 次に、 docs/research/ みたいなディレクトリを切って、 /research で調査させた結果か、手元の調査メモを1本だけ置く。その1本を起点に、記事やコードから参照が伸びていきます。最初から整えようとしなくていいんですよね。「置き場を1つ決めて1本置く → そこから参照が伸びる」という流れが最小の始め方で、リポジトリに仕事が溜まるほど、使い回せるものが増えていきます。 調査の品質を上げる /research の使い方は「 Claude Codeの調査品質を /research で上げる 」と「 調査→構造化→注入 」で紹介しているので、調査の置き場から始めたい方はそちらもどうぞ。 使ってみての感想 使い始めて一番変わったのは「終わった仕事が消えずに残っていく」感覚ですね。以前は、調査やメモは「その仕事のためだけのもの」で、終わったら参照されなくなっていくことが多かったんです。でも全部1つのリポジトリに入れると、終わった仕事が次の仕事の土台になっていく。同じ時間をかけた作業が、使い捨てじゃなくて積み上がっていく感じがあります。 Second Brain 的な考え方とも近いんですが、ちょっと違うのは、僕にとっての「外部記憶」はむしろブログの方なんですよね(笑)。一度書いて公開した話は、もう頭で覚えておかなくても「あの記事に書いてある」で済む。リポジトリはその手前にある、書く前の素材が全部そろっている場所、というイメージです。自分が思い出せなくても Claude が横断して引き出してくれるので、記憶の代替というよりは仕事の土台になっている感覚ですね。「積み上がる」というのが、いちばんしっくりくる言葉です。 あと、進捗管理やタスク管理も同じリポジトリで回しているんですが、git の履歴がそのままタスクの棚卸しになる、という話は少し長くなるので次の記事に書こうと思っています。近日公開予定です。 今回関連する記事はこちらです。 調査品質・ /research の使い方 調査→構造化→AIへの注入 検証→記事化で知見を資産化 製品モノレポとの違い(CLAUDE.md活用) 履歴でタスクを棚卸しする(次記事・近日公開予定) ほなまた〜 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Claude Codeで全業務を1リポジトリに一元管理する作業基盤の作り方 first appeared on SIOS Tech Lab .
PSSLの佐々木です Claude Code・Copilot・Codex といった AI コーディングエージェントは、コマンドを実行できる権限を持ったまま手元のリポジトリの中で動きます。便利ですが、 secret (API token、DB 接続文字列、本番 AWS キー) との同居していることでシークレットが漏洩しないか心配になったので対応策を調査してみました。 この記事では、 ルールで縛っても AI Agent に .env を読まれてしまう情報漏洩リスク その緩和策として Infisical を選んだ理由 Infisical の仕組み (= なぜ AI に「見えない」のか) 個人 AWS アカウントを使った検証での導入手順 についてまとめました。 1. ルールで縛っても AI Agent は secret を読みうる危険がある Claude Code / Copilot 等の主要な AI コーディングエージェントには、運用ルールを書ける場所が用意されています。Claude Code なら CLAUDE.md みたいなやつです。 検証用に立てたプロジェクトの CLAUDE.md にも、こんなルールを書いてみました: - `.env`, `.env.prod`, `.env.*`, `*.pem`, `client_secret.json` などの secret 実体を読まないでください - secret ファイルに対して `cat`, `grep`, `sed`, `awk`, `head`, `tail`, `less`, `python` などで 内容を表示・抽出しないでください - secret 値、DATABASE_URL、SECRET_KEY、SMTP password、RDS password、private key を チャット、docs、issue、PR、ログへ書かないでください しかしここにルールを記載しても何度も裏切られた経験もあり、意図せずAgnetがルールを無視してシークレット情報を見に行く可能性も否定しきれないなと開発をしながら思っていました。 例えば以下のような場合にAgentがルールを無視してシークレットを読みに行く可能性があります 「node dev server が立ち上がらない」→ デバッグのため DATABASE_URL の構造を確認する必要が出る 「ECR push が失敗している」→ AWS profile / credential の状態を見る必要が出る 「 make で env が読まれていないっぽい」→ シェルから env | grep XXX する つまり、 CLAUDE.md だけに頼った secret 管理は 「事故が起きないことを祈る運用」 だと感じていて商用製品の開発をする際にかなりのリスクになりえると思っています。 2. Infisical とは Infisical は OSS の secret 管理プラットフォームです。AWS Secrets Manager や HashiCorp Vault と同じ「secret を集中管理する」カテゴリに属しますが、開発者体験が抜群に良いと思いました Web UI で見て編集できる (json でなく key-value のテーブル) CLI が direnv / dotenv-cli の上位互換 として使える 環境別 ( dev / staging / prod ) + パス別 で分離可能 メンバー単位の RBAC 、誰がいつ何を見たかの audit log Cloud (SaaS) も Self-host (Docker compose) も選べる 無料枠 が個人開発で十分使える 公式に GitHub Star 約 2 万 あって、HashiCorp Vault よりは小規模、AWS Secrets Manager よりは開発者寄りという立ち位置です。 3. 仕組み ― なぜ AI Agent から「見えない」のか Infisical CLI の中核機能は infisical run です: infisical run --env=dev --path=/aws/sandbox -- aws sts get-caller-identity このコマンドの裏では、こういう流れが起きます: infisical CLI (親) │ ├─ 1. ローカルに保存された JWT で Infisical API へ認証 ├─ 2. /aws/sandbox パスの secret 一覧を HTTPS で取得 (in-memory) ├─ 3. fork して子プロセスを作る │ └─ 子プロセスの environ に AWS_ACCESS_KEY_ID 等を export └─ 4. 子プロセス (= `aws sts get-caller-identity`) 実行 └─ 子プロセス終了で memory も解放、secret はどこにも残らない Infisicalを使っていてうれしいポイント ディスクに .env ファイルを一切作らない — AI が cat .env しても “そんなファイルない” 親 shell の env に export しない — AI が env や printenv を打っても見えない (= デフォルトの shell には載っていない) shell history に値が残らない — infisical run -- foo という呼び出し履歴は残るが、secret 値は履歴に出ない 子プロセスが終わったら secret 痕跡ゼロ — RAM 上から消える つまり、AI エージェントが「環境変数経由で secret を盗む」最もカジュアルな経路 (= cat .env と env ) を 両方とも構造的に塞いでいます 。 4. 導入手順 (個人 AWS アカウントで検証) ここからは、自分の個人 AWS アカウント上に検証用の IAM user を作り、その credential を Infisical に登録して AI エージェントから AWS リソースを操作させる、という流れで手を動かしてみた手順です。あわせて、検証用に立てた Django プロダクトの .env 相当の値 (DB 接続文字列、SECRET_KEY、SMTP password など) も Infisical に寄せて、ローカルの .env を消し去るところまでやりました。 4.1 アカウント作成 infisical.com/cloud でサインアップ。Org → Project を作成。 4.2 CLI のインストール # macOS brew install infisical/get-cli/infisical # Linux curl -1sLf '<https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh>' | sudo -E bash sudo apt update && sudo apt install -y infisical 4.3 ログインとリポジトリの紐付け infisical login # ブラウザが開いて OAuth cd path/to/repo infisical init # この repo を Infisical project に紐付け (.infisical.json 生成) .infisical.json は project ID と環境名の対応だけ が入っていて secret 値は無いので、git に commit しても問題なし。 4.4 secret を登録 Web UI から登録するのが楽です。複数環境 ( dev / staging / prod ) と任意のパス ( /aws/sandbox /django/app 等) で分けられます。 検証では、個人 AWS アカウントに作った IAM user の credential と、検証用 Django プロダクトの env をこんな感じで分けました: Infisical path env vars 用途 dev / /aws/sandbox AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY (個人検証用 IAM user) AI エージェントから S3 / EC2 / SSM などを叩く検証 dev / /django/app DATABASE_URL / SECRET_KEY / SMTP_PASSWORD 等 検証用 Django アプリの実行時 env これで手元の .env は完全に削除。値は全部 Infisical 側にだけ存在する状態にしました。 4.5 実行 # AWS 操作 infisical run --env=dev --path=/aws/sandbox -- aws sts get-caller-identity # → arn:aws:iam::xxxxxxxx:user/sandbox-user # Django 起動 infisical run --env=dev --path=/django/app -- python manage.py runserver これで OK。 .env ファイルもシェルへの export も一切無し。 5. 検証してわかった恩恵 5.1 AI エージェントが構造的に secret に触れなくなった 検証では Claude Code に「個人 AWS アカウントの S3 バケットを一覧して、不要なものを削除して」みたいなタスクを投げてみました。 infisical run 経由で AWS 操作を委任しても、Claude は そもそも secret 値を「知る」術がない 。例えば: infisical run --env=dev --path=/aws/sandbox --silent -- \\ aws s3 ls これを Claude に実行させても、Claude が見られるのは: コマンドの引数 (= 公開情報) コマンドの出力 (= 私が許可した情報) だけ。 AWS キー本体は Claude のプロセス空間にも会話履歴にも入りません。 検証用 Django アプリ側でも同様で、 .env を消した状態で Claude に「dev server を立ち上げて動作確認して」と頼むと、 infisical run 経由でしか起動できない。エージェントが好奇心で cat .env しても ファイルが存在しない ので空振りに終わります。実際にやらせてみても、 DATABASE_URL や SECRET_KEY の値が会話履歴に出てくることは一度もありませんでした。 5.2 検証用 IAM user を分けやすい 個人 AWS アカウントで遊んでいると「これは AI に渡していい権限」「これは自分が手でしかやらない権限」を分けたくなります。Infisical のパスで切るとそこが綺麗: # AI に渡していい権限 (read 中心、限定リソース) infisical run --env=dev --path=/aws/sandbox -- <command> # 自分しか使わない権限 (IAM 編集、billing 系) infisical run --env=dev --path=/aws/admin -- <command> IAM user 自体は別々に作って、Infisical 側でパス権限を分けるだけ。エージェントには /aws/sandbox だけアクセスできるトークンを渡す、みたいな運用が現実的にできます。 5.3 検証が終わったら剥奪が一瞬 個人検証あるあるで「検証終わったけど IAM key 消し忘れて放置」が起こりがちですが、Infisical に集約しておけば Web UI で値を消すだけ。 .env が複数のリポジトリに散らばってる状態より圧倒的に管理が楽でした。 6. まとめ AI Agent と一緒に開発する時代、 .env をローカルに転がしておく運用は 「ルールで縛っても、いつかは事故る」 可能性があります。 文章ルール ( CLAUDE.md ) は「お願い」レベル AI Agent はタスク遂行のために env を覗くことがある (悪意なしでも) 一度履歴に入った secret は AI ベンダー側に永続化される Infisical の infisical run -- <command> 方式に切り替えると、 .env ファイルがそもそも存在しない → cat で出ない shell env にも default で乗らない → env / printenv で出ない 子プロセスのライフサイクル内だけで secret が生きる それでいて direnv 同等の手軽さで開発が回る 完全防御ではないが、 カジュアルな漏洩経路を構造的に塞いだ上で、AI エージェントとの共存を成立させる ための最小コストの一手として、強くおすすめできます。 個人 AWS アカウントでの検証レベルでも、 .env を消して Infisical に寄せたことで「エージェントに何を喋らせても secret が混入しない」という安心感は段違いでした。本番投入前のサンドボックスとして手を動かしてみる価値は十分あると思います。 参考リンク Infisical 公式 Infisical CLI ドキュメント Anthropic Claude Code 公式 GitHub: Infisical/infisical ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post AI エージェントに.envを読まれたくなかったからInfisicalを導入てみた first appeared on SIOS Tech Lab .
はじめに こんにちは、サイオステクノロジーの小野です。以前、HelmOpsについて解説しました。 GitOpsだけじゃない!新たな選択肢「HelmOps」とは? HelmOps環境を構築するCDツールは様々な種類があります。(ex. Rancher Fleet, ArgoCD, Flux CD) その中でもRancher FleetはHelmOpsを簡易に構築することが可能です。 今回はRancher FleetのHelmOps機能を使って、実際に自動デプロイできることを紹介したいと思います。 HelmOps設定 前提条件 Rancher環境でHelmOps実施 Rancher v2.13.3 Fleet v0.14.3 設定方法 Rancherの管理コンソールのRancher Fleet管理画面を開きます。左メニューのヨットマークが目印です。 Rancher Fleet画面 「Create Apps Bundle」を押下して、HelmOpsを作成することで、HelmOpsの設定が可能です。 HelmOpsを選択 HelmOps実践 以下のHelmアプリをHelmOpsでデプロイします。replicaCount数のPodが立ち上がり、ClusterIPのService設定で公開されているhttpbinのサンプルアプリをデプロイする例を考えます。 # Chart.yaml apiVersion: v2 name: sample-app type application version: 0.1.0 appVersion: "1.0" # valuse.yaml replicaCount: 1 image: repository: kennethreitz/httpbin pullPolicy: IfNotPresent tag: "latest" service: type: ClusterIP port: 80 このアプリをデプロイするために以下のようにHelmOpsに設定します。 Bundle名:test-helmops(HelmOpsのバンドル名を指定) Release名:demo-sample(Helmのリリース名を指定) Source Type:Repository Repository URL:<HelmリポジトリのURL> Chart:sample-app(HelmOpsでデプロイするHelmチャート名を指定) Version:>=0.1.0(一定バージョン以上を継続的にデプロイしたい場合は「>=0.1.0」のように設定する) Values:設定しない Deploy To:test-cluster(HelmOpsでデプロイする対象のクラスターを設定) Additional Options.Target Namespace:demo-ns(デプロイするNamespaceを指定) Authentication:None(Helmリポジトリの認証がある場合設定) Polling:Enable Polling(継続的にデプロイする場合、Pollingを有効にする) 設定後、HelmOpsのBundleが作成されて、Rancher Fleetの管理画面からデプロイ状況を確認できます。 デプロイ状況を確認 デプロイした先のクラスター管理画面を開くと、自動的にサンプルアプリがデプロイされていることが確認できます。 サンプルアプリのデプロイを確認 Podが1個デプロイされている 80ポートのClusterIPで公開されている Helmリポジトリ内のサンプルアプリを更新して、自動的にデプロイされているアプリも更新することを確認します。 サンプルアプリのChart.yamlとvalues.yamlを以下のように更新します。 # Chart.yaml apiVersion: v2 name: sample-app type application version: 0.2.0 appVersion: "1.0" # valuse.yaml replicaCount: 2 image: repository: kennethreitz/httpbin pullPolicy: IfNotPresent tag: "latest" service: type: ClusterIP port: 8080 更新した上記のファイルをHelmリポジトリにアップロードすると、以下のようにデプロイされているアプリも自動的に更新されることが確認できます。 Podが2台に更新されている 8080ポートのClusterIPで公開されるように更新されている おわりに Rancher FleetのHelmOpsについて解説しました。CD環境を構築するには大きなコストがかかりますが、HelmOpsはHelmリポジトリ内のチャートファイルを指定するだけで簡単にCD(継続的デプロイ)環境を構築することが可能です。ぜひ手軽に試してみてください。 参考 https://fleet.rancher.io/how-tos-for-users/helm-ops ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Rancher Fleetで簡単に実現!HelmOpsを使った継続的デプロイ(CD)の実践 first appeared on SIOS Tech Lab .
こんにちは! 今月も「OSSのサポートエンジニアが気になった!OSSの最新ニュース」をお届けします。 2017 年から潜伏していた Linux カーネルの深刻な脆弱性「Copy Fail」が、極めて重大なリスクとして速やかな対策が求められています。 「Linux」に極めて重大な脆弱性–「Copy Fail」発覚 https://japan.zdnet.com/article/35247165/ GTIG は AI を用いて開発されたと思われる zero-day exploit (ゼロデイエクスプロイト) を使用する脅威アクターを特定しました。 GTIG AI Threat Tracker: Adversaries Leverage AI for Vulnerability Exploitation, Augmented Operations, and Initial Access https://cloud.google.com/blog/topics/threat-intelligence/ai-vulnerability-exploitation-initial-access?hl=en アメリカのセブンイレブンは、ShinyHunters というサイバー犯罪グループによるサイバー攻撃により、同社のシステムが侵害されたことを認めました。 7-Eleven confirms data breach claimed by the ShinyHunters gang https://www.bleepingcomputer.com/news/security/7-eleven-confirms-data-breach-claimed-by-the-shinyhunters-gang/ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【2026年5月】OSSサポートエンジニアが気になった!OSS最新ニュース first appeared on SIOS Tech Lab .
今号では、Linux におけるターミナル操作で、可能な限り入力の手間を削るための tips をご紹介します! なお、ターミナル操作に関連する内容として過去の記事で一部ご紹介しているものもありますので、そちらもぜひご覧ください。 ・ 知っておくとちょっと便利!コマンド5選 ~ショートカットキー編~ ・ 知っておくとちょっと便利!bash スクリプトで使用する括弧 () [] {} について ・ 知っておくとちょっと便利!bash スクリプトで使用する波括弧 {} について 「入力」から「呼び出し」へ。コマンド実行履歴の活用方法 [Ctrl] + [R] で過去に実行したコマンドを検索 矢印キー (↑) を連打して探すのではなく、キーワードの一部を入力することで過去に実行したコマンドを瞬時に呼び出すことができます。 前回実行してから時間が経過してしまったコマンドを探したい場合に役立ちます。 [Ctrl] + [R] を入力すると、下記の様なプロンプトが表示されます。 (reverse-i-search)`': ここでキーワードを入力すると、その文字を含む直近の履歴が自動的に表示されます。 さらに過去の履歴に遡りたい場合は、[Ctrl] + [R] を連打すると該当するコマンドが順番に表示されます。 (reverse-i-search)`less': less /tmp/test.txt なお、該当するコマンドが見つからなかった場合は下記の様に表示されます。 (failed reverse-i-search)`ls': less /tmp/test.txt 直前のコマンドを管理者権限で実行 「!!」を入力すると、直前に実行したコマンドを再実行できますが、その際「sudo !!」と入力することで管理者権限で当該コマンドを再実行できます。 直前に実行したコマンドが Permission denied で実行に失敗した場合などに便利です。 $ sudo !! fc コマンドで直前のコマンドをエディタで編集 「fc コマンド」を入力すると、直前に実行したコマンドをテキストベースで編集できます。 例えば、「アクセスログからステータスコードが 200 以外のものを探し、IP アドレスごとにカウントした後、アクセス数が多い順に並べ替える」という処理があったとします。 cat /var/log/httpd/access_log | grep -v " 200 " | awk '{print $1}' | sort | uniq -c | sort -nr | head -n 10 この処理を (grep するステータスコードを変更する等) 少し変更したい場合、ターミナル上でカーソルを動かして修正するのは大変です。 そこで、fc コマンドを実行するとエディタが起動し、直前のコマンドが表示されます。 cat /var/log/httpd/access_log | grep -v " 200 " | awk '{print $1}' | sort | uniq -c | sort -nr | head -n 10 ~ ~ … 後はいつものように編集、保存、終了すると、その内容が即座に実行されます。 なお、fc コマンドは -l オプションを付けて実行すると履歴の一覧が表示されるなど、他の使い方もありますので、詳細について知りたい方は fc コマンドのマニュアルを見てみてください。 出力内容を自在に制御する小技 tee コマンドによるファイル書き込み tee コマンドを使用すると、実行結果が標準出力とファイルの両方に出力されます。 これは、時間のかかるスクリプトの進捗状況を画面上で確認しながらログとして記録する際に便利です。 ./script.sh | tee output.log 2> /dev/null によるエラーの破棄 /dev/null とは「ゴミ箱」の役割を持つ特殊ファイルです。 コマンドやスクリプトの実行時、最後に「2> /dev/null」を付けることでエラーのみを破棄する、という動作になります。 本当に必要なエラー情報を見落とさないよう、「2> /dev/null」の取り扱いには注意が必要ですが、ファイルを操作する際に発生しがちな「Permission denied(許可がありません)」といったエラーだけを非表示にしたい場合などに役立ちます。 ./script.sh 2> /dev/null コマンドの結果を「ファイル」として扱う 「<()」を使用すると、コマンドの結果を一時的に「ファイル」として扱うことができ、結果を別のコマンドに渡すことができます。 例えば、下記の様に実行するとファイルを作成しなくても 2つのコマンドの結果の差分を取ることができます。 diff <(ls dir1) <(ls dir2) ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 知っておくとちょっと便利!ターミナル操作の時短テクニック1 first appeared on SIOS Tech Lab .
こんにちは、サイオステクノロジーの佐藤です。 今回は、前回のブログでも触れたFoundry IQについて、もう一歩踏み込み、「実際に内部でどのような処理が行われているのか?」という構造面を解説します。 Foundry IQ便利そうだけどどういう仕組みなの? 内部的な動きってどうなっているの? という方は是非最後までご覧ください! なお、Foundry IQの概要やコンセプトに関しては前回の記事をご覧ください。 Foundry IQ は RAGにとっての銀の弾丸となりえるか?【Microsoft Ignite 2025 Recap】 Foundry IQの技術基盤 Foundry IQはMicrosoft Foundry上にて作成できるサービスとなっています。 ただし完全に新規で独立したサービスというわけでもなく、内部的な仕組みとしてはAzure AI Searchのサービスが基盤となっています。 またそれと並行してLLMやEmbeddingsといったAIモデルも必要となってきます。 この Azure AI Search と AIのモデル の2つがFoundry IQを構成する主な要素となります。 そのため、Microsoft Foundry上でFoundry IQを作成しようとすると、まずAzure AI Searchとの接続を求められます。 また、後述するナレッジベースを構築する上では、AIモデルのデプロイメントが必須となります。 AI Searchの役割 AI Searchの役割は高度な検索サービスであり、AI Search上に格納されたデータに対して様々な検索を行えるサービスです。 特に、近年実現されたAgentic Retrievalの検索手法は、今回のFoundry IQの肝となる検索手法と言っても過言ではありません。 また検索手法だけではなく、データのインポート技術にも優れています。 BlobStorageなどを対象として、データの読みとりから、チャンク化・ベクトル化といった加工までを実施可能で、検索に適した形でデータをインポートすることが可能です。 こういったことから、Azure AI SearchはFoundry IQの根幹となるプラットフォームとなっています。 AIモデルの役割 先ほど紹介したAI Searchの検索機能に対してより付加価値を高めるのが、各AIモデルの役割です。 単純にデータのインポート、検索を行うだけであればAI Searchだけで完結しますが ユーザーからの質問の意図の解釈や、質問の分解などに使われ、より検索精度・回答精度を高めるための手段としてLLMが利用されます。 また、LLMによる「思考」だけでなく、Embeddingモデルも非常に重要な役割を果たします。 先ほど述べたように、Foundry IQがデータを自動でインポートする際の「ベクトル化」や、ユーザーの質問をデータベースと照合する際の「意味的マッチング」において、このEmbeddingモデルが裏側で活躍しています。 Foundry IQの構成 実際にFoundry IQの内容についてみていきたいと思います。 Foundry IQのざっくりコンポーネント図としては以下のような形です。 ここで重要なのが以下の2つの要素です。 ナレッジソース ナレッジベース ナレッジソース ナレッジソースとは、実際に検索に使われるファイルなどのコンテンツを指定するための概念となります。 上の図でいうと、Storage AccountとSharePoint Onlineといったリソースと連携している形となります。 各ナレッジソースがこれらのリソースと紐づくことで、紐づいたリソースのコンテンツを検索対象とすることができます。 ナレッジソースの対象となっているものは以下の6種類です。 ナレッジソースと紐づけ可能なリソース タイプ AI Search Index インデックス型 Azure Blob インデックス型 OneLake インデックス型 SharePointOnline インデックス型 SharePointOnline リモート型 Web リモート型 なおここで記載しているタイプについては、以下のような違いがあります。 インデックス型:事前にデータを取り込んでおき、内部にインデックスを作成・保持しておく リモート型:検索が行われた時点で外部のシステムを直接見に行き、常に最新のデータを見に行く ナレッジベース ナレッジベースは、複数のナレッジソースを束ねて、エージェントに対する窓口となる概念です。 エージェントからのクエリに対して、どのナレッジソースを利用するべきか、どういった検索を行うか、本当にこのまま検索結果を返してよいかなどを判断するオーケストレーターとしての役割を担います。 また、ナレッジベースは最低一つ以上のナレッジソースから構築されます。 Agentic Retrieval Foundry IQの重要な機能に、 Agentic Retrieval があります。 とはいいつつ、これはFoundry IQというよりもAzure AI Searchの機能となります。 先ほども述べた通り、実際にエージェントからFoundry IQを呼び出したとしても、検索が行われるのはAI Searchです。 従来の検索手法では、ユーザーからの質問をベクトル化して一度検索を行い、類似度が高いものを結果に返すといった流れでした。(いわゆるSingle-shot) 一方で、Agentic RetrievalではLLMの機能を用いて、 クエリの内容はどのような内容なのか クエリに適したナレッジソースはどれなのか 得られた結果が本当に期待する回答なのか 必要に応じて再検索を実施 などを自律的に考えた検索が行われます。 そのため、従来のSingle-shotの検索よりも、期待に近い回答が得られやすくなります。 Agentic Retrievalの例 このAgentic Retrievalの例をひとつ見てみたいと思います。 例えばユーザーから以下のようなクエリが飛んできたとします。 サイオス病院の健康診断でB判定となりました。上の血圧が150でした。 今40歳なのですが、病院からは運動することを推奨されました。 どういった運動をしたらいいですか? &nbsp; この質問には、「B判定の基準」「血圧150の意味」「40歳の適切な運動量」という複数の要素が混ざり合っています。 これを従来のRAGでそのまま検索にかけても、全ての情報が網羅された完璧なドキュメントを引き当てることは困難です。 一方でAgentic Retrievalを利用することで、こちらが解決できる可能性があります。 Foundry IQ(Agentic Retrieval)では、この質問を受け取ると以下の4つのステップを自律的に実行します。 Step 1: クエリの計画と分解 まず、AIが質問の意図を解釈し、回答に必要な情報を集めるために質問を複数の「サブクエリ」に分解します。 「サイオス病院のB判定の基準とは?」 「血圧150の危険度は?」 「40歳に推奨される1日あたりの運動量はどれくらいか?」 このように、システムが自ら「何を調べるべきか」の計画を立てます。 Step 2: ソースの動的選択 次に、分解したサブクエリごとに、接続されている複数のナレッジソースの中から「どこへ探しに行くべきか」を動的に選択して検索を実行します。 B判定の基準 ➔ 病院内部資料である SharePoint を検索 血圧の危険度 ➔ 調査資料が格納された Blob Storage を検索 推奨される運動量 ➔ 自社データにない一般知識のため Web(Bing)検索 を実行 このように各データソースの特徴をAIが判断し、最適な場所へ同時に情報を探しに行きます。 Step 3: 自己評価と反復検索 ここがAgentic Retrievalの最も強力なポイントです。 取得した情報をそのまま返すのではなく、システム内部にいる小規模言語モデル(SLM)が「この情報でユーザーの質問に答えられるか?」を自己評価します。 もし「まだ情報が足りない(NG)」と判断した場合は、もう一度ステップ1に戻り、クエリを調整して再検索(反復)を行います。 Step 4: 回答の合成 SLMの評価が「OK」となり、十分な情報が揃ったと判断された段階で、複数のソースからかき集めた情報を統合し、自然な回答文として合成してユーザーに返却します。 retrieval Reasoning Effort このように自律的に動作するAgentic Retrievalですが、「毎回そんなに深く考えて反復検索しなくていいから、早く答えが欲しい」というケースもあるはずです。 Agentic Retrievalにおいては、この「検索の深さ(レイテンシと品質のトレードオフ)」を開発者がコントロールできるように、「検索推論努力(retrieval Reasoning Effort)」というパラメータが用意されています。 レベル 説明 minimal LLM 処理を行いいません。 low 質問の計画とソースの選択は行いますが、反復検索は行いません。 medium 自己評価と反復検索をフル活用し、徹底的に情報を集めます。 まとめ 今回は、Foundry IQの内部構造や、Agentic Retrievalの自律的なプロセスについて解説しました。 こうして内部の動きを見ると、開発者がこれまで自前で実装する必要があった複雑な検索処理を、Foundry IQがうまく担ってくれていることが分かっていただけたかと思います。 次回は実際にFoundry IQを活用してエージェントを構築する流れをご紹介したいと思います! ではまた! 余談 本ブログの内容に関しては、以下のYouTubeでもお話ししています。 もしよければこちらもご覧いただければと思います /* From extension marp-team.marp-vscode */ (()=>{var I=Object.defineProperty;var K=(f,u,_)=>u in f?I(f,u,{enumerable:!0,configurable:!0,writable:!0,value:_}):f[u]=_;var r=(f,u)=>I(f,"name",{value:u,configurable:!0});var w=(f,u,_)=>K(f,typeof u!="symbol"?u+"":u,_);(()=>{var f={32(S,E,h){S.exports=h(924)},924(S,E){"use strict";var h;h={value:!0};const b={h1:{proto:r(()=>HTMLHeadingElement,"proto"),attrs:{role:"heading","aria-level":"1"},style:"display: block; font-size: 2em; margin-block-start: 0.67em; margin-block-end: 0.67em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},h2:{proto:r(()=>HTMLHeadingElement,"proto"),attrs:{role:"heading","aria-level":"2"},style:"display: block; font-size: 1.5em; margin-block-start: 0.83em; margin-block-end: 0.83em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},h3:{proto:r(()=>HTMLHeadingElement,"proto"),attrs:{role:"heading","aria-level":"3"},style:"display: block; font-size: 1.17em; margin-block-start: 1em; margin-block-end: 1em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},h4:{proto:r(()=>HTMLHeadingElement,"proto"),attrs:{role:"heading","aria-level":"4"},style:"display: block; margin-block-start: 1.33em; margin-block-end: 1.33em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},h5:{proto:r(()=>HTMLHeadingElement,"proto"),attrs:{role:"heading","aria-level":"5"},style:"display: block; font-size: 0.83em; margin-block-start: 1.67em; margin-block-end: 1.67em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},h6:{proto:r(()=>HTMLHeadingElement,"proto"),attrs:{role:"heading","aria-level":"6"},style:"display: block; font-size: 0.67em; margin-block-start: 2.33em; margin-block-end: 2.33em; margin-inline-start: 0px; margin-inline-end: 0px; font-weight: bold;"},span:{proto:r(()=>HTMLSpanElement,"proto")},pre:{proto:r(()=>HTMLElement,"proto"),style:"display: block; font-family: monospace; white-space: pre; margin: 1em 0; --marp-auto-scaling-white-space: pre;"}},q="data-marp-auto-scaling-wrapper",C="data-marp-auto-scaling-svg",O="data-marp-auto-scaling-container",j=class j extends HTMLElement{constructor(){super();w(this,"container");w(this,"containerSize");w(this,"containerObserver");w(this,"svg");w(this,"svgComputedStyle");w(this,"svgPreserveAspectRatio","xMinYMid meet");w(this,"wrapper");w(this,"wrapperSize");w(this,"wrapperObserver");const n=r(s=>([t])=>{const{width:i,height:o}=t.contentRect;this[s]={width:i,height:o},this.updateSVGRect()},"e");this.attachShadow({mode:"open"}),this.containerObserver=new ResizeObserver(n("containerSize")),this.wrapperObserver=new ResizeObserver((...s)=>{n("wrapperSize")(...s),this.flushSvgDisplay()})}static get observedAttributes(){return["data-downscale-only"]}connectedCallback(){this.shadowRoot.innerHTML=` svg[${C}] { display: block; width: 100%; height: auto; vertical-align: top; } span[${O}] { display: table; white-space: var(--marp-auto-scaling-white-space, nowrap); width: max-content; } `.split(/\n\s*/).join(""),this.wrapper=this.shadowRoot.querySelector(`div[${q}]`)??void 0;const n=this.svg;this.svg=this.wrapper?.querySelector(`svg[${C}]`)??void 0,this.svg!==n&&(this.svgComputedStyle=this.svg?window.getComputedStyle(this.svg):void 0),this.container=this.svg?.querySelector(`span[${O}]`)??void 0,this.observe()}disconnectedCallback(){this.svg=void 0,this.svgComputedStyle=void 0,this.wrapper=void 0,this.container=void 0,this.observe()}attributeChangedCallback(){this.observe()}flushSvgDisplay(){const{svg:n}=this;n&&(n.style.display="inline",requestAnimationFrame(()=>{n.style.display=""}))}observe(){this.containerObserver.disconnect(),this.wrapperObserver.disconnect(),this.wrapper&&this.wrapperObserver.observe(this.wrapper),this.container&&this.containerObserver.observe(this.container),this.svgComputedStyle&&this.observeSVGStyle(this.svgComputedStyle)}observeSVGStyle(n){const s=r(()=>{const t=(()=>{const i=n.getPropertyValue("--preserve-aspect-ratio");return i?i.trim():`x${(({textAlign:o,direction:d})=>{if(o.endsWith("left"))return"Min";if(o.endsWith("right"))return"Max";if(o==="start"||o==="end"){let m=d==="rtl";return o==="end"&&(m=!m),m?"Max":"Min"}return"Mid"})(n)}YMid meet`})();t!==this.svgPreserveAspectRatio&&(this.svgPreserveAspectRatio=t,this.updateSVGRect()),n===this.svgComputedStyle&&requestAnimationFrame(s)},"t");s()}updateSVGRect(){let n=Math.ceil(this.containerSize?.width??0);const s=Math.ceil(this.containerSize?.height??0);this.dataset.downscaleOnly!==void 0&&(n=Math.max(n,this.wrapperSize?.width??0));const t=this.svg?.querySelector(":scope > foreignObject");if(t?.setAttribute("width",`${n}`),t?.setAttribute("height",`${s}`),this.svg&&(this.svg.setAttribute("viewBox",`0 0 ${n} ${s}`),this.svg.setAttribute("preserveAspectRatio",this.svgPreserveAspectRatio),this.svg.style.height=n class extends c{constructor(...s){super(...s);for(const[t,i]of Object.entries(v))this.hasAttribute(t)||this.setAttribute(t,i);this._shadow()}static get observedAttributes(){return["data-auto-scaling"]}connectedCallback(){this._update()}attributeChangedCallback(){this._update()}_shadow(){if(!this.shadowRoot)try{this.attachShadow({mode:"open"})}catch(s){if(!(s instanceof Error&&s.name==="NotSupportedError"))throw s}return this.shadowRoot}_update(){const s=this._shadow();if(s){const t=n?` :host { ${n} } `:"";let i=" ";const{autoScaling:o}=this.dataset;o!==void 0&&(i=` ${i} `),s.innerHTML=t+i}}},"r");let L;const F=Symbol(),z=r(()=>L??(L=!!document.createElement("div",{is:"marp-auto-scaling"}).outerHTML.startsWith(" <div is"),L),"l");let A;const a="marpitSVGPolyfill:setZoomFactor,",l=Symbol(),e=Symbol(),p=r(()=>{const c=navigator.vendor==="Apple Computer, Inc.",v=c?[H]:[],n={then:r(s=>(c?(async()=>{if(A===void 0){const t=document.createElement("canvas");t.width=10,t.height=10;const i=t.getContext("2d"),o=new Image(10,10),d=new Promise(m=>{o.addEventListener("load",()=>m())});o.crossOrigin="anonymous",o.src="data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2210%22%20height%3D%2210%22%20viewBox%3D%220%200%201%201%22%3E%3CforeignObject%20width%3D%221%22%20height%3D%221%22%20requiredExtensions%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxhtml%22%3E%3Cdiv%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxhtml%22%20style%3D%22width%3A%201px%3B%20height%3A%201px%3B%20background%3A%20red%3B%20position%3A%20relative%22%3E%3C%2Fdiv%3E%3C%2FforeignObject%3E%3C%2Fsvg%3E",await d,i.drawImage(o,0,0),A=i.getImageData(5,5,1,1).data[3] {s?.(t?[H]:[])}):s?.([]),n),"then")};return Object.assign(v,n)},"p");let y,g;function H(c){const v=typeof c=="object"&&c.target||document,n=typeof c=="object"?c.zoom:c;window[e]||(Object.defineProperty(window,e,{configurable:!0,value:!0}),document.body.style.zoom=1.0001,document.body.offsetHeight,document.body.style.zoom=1,window.addEventListener("message",({data:t,origin:i})=>{if(i===window.origin)try{if(t&&typeof t=="string"&&t.startsWith(a)){const[,o]=t.split(","),d=Number.parseFloat(o);Number.isNaN(d)||(g=d)}}catch(o){console.error(o)}}));let s=!1;Array.from(v.querySelectorAll("svg[data-marpit-svg]"),t=>{var i,o,d,m;t.style.transform||(t.style.transform="translateZ(0)");const x=n||g||t.currentScale||1;y!==x&&(y=x,s=x);const W=t.getBoundingClientRect(),{length:Z}=t.children;for(let N=0;N {t?.postMessage(`${a}${s}`,window.origin==="null"?"*":window.origin)})}r(H,"v");function B({once:c=!1,target:v=document}={}){const n=(function(s=document){if(s[l])return s[l];let t=!0;const i=r(()=>{t=!1,delete s[l]},"i");Object.defineProperty(s,l,{configurable:!0,value:i});let o=[],d=!1;(async()=>{try{o=await p()}finally{d=!0}})();const m=r(()=>{for(const x of o)x({target:s});d&&o.length===0||t&&window.requestAnimationFrame(m)},"r");return m(),i})(v);return c?(n(),()=>{}):n}r(B,"w"),y=1,g=void 0;const Y=B,R=Symbol(),V=r((c=document)=>{if(typeof window>"u")throw new Error("Marp Core's browser script is valid only in browser context.");if(((t=document)=>{const i=window[F];i||customElements.define("marp-auto-scaling",k);for(const o of Object.keys(b)){const d=`marp-${o}`,m=b[o].proto();z()&&m!==HTMLElement?i||customElements.define(d,T(m,{style:b[o].style}),{extends:o}):(i||customElements.define(d,T(HTMLElement,b[o])),t.querySelectorAll(`${o}[is="${d}"]`).forEach(x=>{x.outerHTML=x.outerHTML.replace(new RegExp(`^ $`,"i"),` `)}))}window[F]=!0})(c),c[R])return c[R];const v=B({target:c}),n=r(()=>{v(),delete c[R]},"n"),s=Object.assign(n,{cleanup:n,update:r(()=>V(c),"update")});return Object.defineProperty(c,R,{configurable:!0,value:s}),s},"f");E.browser=V,h=V,h=Y}},u={};function _(S){var E=u[S];if(E!==void 0)return E.exports;var h=u[S]={exports:{}};return f[S](h,h.exports,_),h.exports}r(_,"__webpack_require__");var Q={};(()=>{"use strict";var S=_(32);const E="marp_vscode_content_section",h="data-marp-vscode-content-start-line",b="data-marp-vscode-content-end-line";function q(a){a.core.ruler.push(E,l=>{if(l.inlineMode)return;let e=null,p=0,y=0;for(const g of l.tokens)g.map&&(p=g.map[0],y=Math.max(y,...g.map)),g.type==="marpit_slide_open"&&(e&&(e.map&&e.attrSet(h,e.map[0]),e.attrSet(b,p-1)),e=g);e&&(e.map&&e.attrSet(h,e.map[0]),e.attrSet(b,y))})}r(q,"marpVSCodeContentSection");const C="marp-vscode.overflowTracker",O=r(a=>typeof a=="object"&&a!=null&&"type"in a&&a.type===C&&"overflowElements"in a&&Array.isArray(a.overflowElements),"isOverflowTrackerEvent"),A=class A{constructor(l){this.postMessage=l,this.delay=150,this.update()}update(){window.setTimeout(()=>this.detectOverflowElements(),this.delay)}cleanup(){}detectOverflowElements(){const l=[];for(const e of document.querySelectorAll(`section[${h}][${b}]`)){const p=e.scrollWidth-e.clientWidth,y=e.scrollHeight-e.clientHeight;if(p {const p=document.getElementById("__marp-vscode");!!a!=!!p?(document.body.classList.toggle("marp-vscode",!!p),p?a={browser:(0,S.browser)(),overflowTracker:l?new k(l):void 0}:(a?.browser.cleanup(),a?.overflowTracker?.cleanup(),a=void 0)):(a?.browser.update(),a?.overflowTracker?.update()),a?(p&&L(p),F()):z()},"updateCallback");window.addEventListener("load",()=>window.setTimeout(e,100)),window.addEventListener("vscode.markdown.updateContent",e),e()}r(T,"preview");const L=r(a=>{a.querySelectorAll("[is]").forEach(l=>{if(l.nodeName.includes("-")||document.createElement(l.nodeName).constructor!==l.constructor)return;const{outerHTML:p}=l;l.outerHTML=p,console.debug("[marp-vscode] Custom element has been upgraded forcibly:",p.slice(0,p.indexOf(">")+1||void 0))})},"forceUpgradeCustomElements"),F=r(()=>{const a=document.querySelectorAll("style:not(#__marp-vscode-style,#_defaultStyles,[data-marp-vscode-body])"),l=document.querySelectorAll('link[rel="stylesheet"][href]:not([href*="marp-vscode"])');a.forEach(e=>{e.closest("#__marp-vscode")||(e.dataset.marpVscodeBody=e.textContent??"",e.textContent="")}),l.forEach(e=>{if(e.closest("#__marp-vscode"))return;const{href:p}=e;e.dataset.marpVscodeHref=p,e.removeAttribute("href")})},"removeStyles"),z=r(()=>{const a=document.querySelectorAll("style[data-marp-vscode-body]"),l=document.querySelectorAll("link[data-marp-vscode-href]");a.forEach(e=>{e.textContent=e.dataset.marpVscodeBody||"",delete e.dataset.marpVscodeBody}),l.forEach(e=>{e.href=e.dataset.marpVscodeHref||"",delete e.dataset.marpVscodeHref})},"restoreStyles");T()})()})();})(); ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 2人がこの投稿は役に立ったと言っています。 The post Foundry IQはどう動いている?構成要素とAgentic Retrievalの仕組みを解説 first appeared on SIOS Tech Lab .
2025年8月にリリースされた、Googleの画像生成AI「Nano Banana」は、空間を的確に把握し、被写体の個性を保った一貫性ある加工が行えるなど、革新的なものでした。そして、2026年2月には、 「Nano Banana 2」 がリリースされています。 OpenAIからは 「GPT Image 2.0」 が2026年4月にリリースされ、こちらも注目です。 ありがたいことに、Nano BananaやGPT Imageは、簡単な言葉で指示をしても「いいかんじ」で「高品質な画像」に仕上げてくれます。 では、具体的にどんな画像を作ることができるのか。 この記事では「AIの画像生成は便利そうだけどまだ試していない」という方が参考にしていただけるような、50文字程度の短い指示(プロンプト)で写真加工した例を中心に、ご紹介します。 写真加工する場合の操作は、GeminiやChatGPTのチャット入力欄にて、 「画像の(を)制作」を選び 元の画像ファイルを添付して 指示文章(プロンプト)を入力して 送信する だけ。 これらのサービスでは、インフォグラフィックスや、ストーリーのある漫画、ページレイアウト、バナーなど、複雑な画像も生成可能ですが、今回は、写真加工に絞ります。 以下は、2026年4月時点のGoogle Gemini(Nano Banana)を利用した例ですが、GPT Imageでも同様の結果が得られると思います。 部分加工 1)被写体を一部削除 元画像 自転車を削除して プロンプト Nano Banana加工後 2)背景をぼかす 元画像 手前の一輪のひまわりにフォーカスして、それ以外の部分を自然にぼかして。 プロンプト Nano Banana加工後 3)色の変更 元画像 このルービックキューブの各面の色を揃えて完成した写真にして。 プロンプト Nano Banana加工後 4)色のバリエーション 元画像 この画像のチューリップの色を、次の4色に変更して。赤、黄、紫、オフホワイト。画像は色別に描き出して。 プロンプト Nano Banana加工後 5)背景合成 元画像1 元画像2 1枚目の写真の人物はそのままにして、背景を2枚目の写真に変更して。 プロンプト Nano Banana加工後 6)貼り込み 元画像1 元画像2 1枚目画像の缶の側面に、2枚目の画像のイラストを印刷して。 プロンプト Nano Banana加工後 7)シルエットで切り抜く 元画像1 元画像2 1枚目の写真を2枚目の写真の馬のシルエットで切り抜いて。シルエットの背景は白で。 プロンプト 8)翻訳 元画像 この看板を日本語に変更して プロンプト Nano Banana加工後 描画 9)線画 元画像 この画像をトレースして、白地に黒い線の細密な線画にして。 プロンプト Nano Banana加工後 10)漫画風 元画像 この画像を白黒漫画風の線画に変更して。 プロンプト Nano Banana加工後 11)イラストから「写真」を描く 元画像 このイラストをもとに、リアルな写真を生成して。 プロンプト Nano Banana加工後 12)ポリゴン風 元画像 この写真のライオンをポリゴンが目視できるようなCG画像に変換して。 プロンプト Nano Banana加工後 (1ページ目 / 全3ページ。次のページへつづきます。) Photo / Illustration by JOHN TOWNER , Kent Banes , 7AV 7AV , Gábor Juhász , Humberto Chávez , Oleksii Piekhov , Ambo Ampeng , Public domain vectors , Dewang Gupta , Helena Lopes , LOGAN WEAVER | @LGNWVR on Unsplash ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post AI写真加工 入門:Nano Banana / GPT Imageを即戦力にする短文プロンプト集 first appeared on SIOS Tech Lab .
サイオステクノロジーのひろです。 今回はpython環境の依存関係を管理するrequirements.txtとconstraints.txtについて学んだのでブログにまとめてみます。 こんな方におすすめ requirements.txtでパッケージ管理を行っている方 pipによるパッケージの依存関係の仕組みを理解したい方 事前準備 本記事では、実際に手元の環境にパッケージをインストールして動作を確認していきます。 システム全体のPython環境を汚すことなく、クリーンな環境でインストール内容の確認等を行うために、 venv を使用して仮想環境の中でコマンドを打っています。 # 仮想環境の作成 python -m venv .venv # 仮想環境の有効化 (Linux/macOS) source .venv/bin/activate # 仮想環境の有効化 (Windows) .venv\Scripts\activate requirements.txtとは何か requirements.txtはpipによってインストールされるパッケージのリストを記述したファイルです。 必ずしもこの名前である必要はなく他の名前であっても動作しますが、慣例としてrequirements.txtという名前が一般的に使用されます。 例えば、以下のように記述されたrequirements.txtがあるとします。 pandas==3.0.1 pip install -r requirements.txtというコマンドを使用して、パッケージをインストールすると、pandasの3.0.1がインストールされます。 実際にインストールして、何がインストールされているのか確認してみます。 pip install -r requirements.txt 上記のコマンドで仮想環境へ実際にインストールしてみました。何が実際にインストールされたのか以下のコマンドで確認してみます。 pip list requirements.txtで指定した通り、pandasの3.0.1がインストールされていることがわかります。 しかし、初期状態からインストールされているpipを除いても、numpyやpython-dateutil、sixといったrequirements.txtに記載していないパッケージがインストールされています。 これらのパッケージは推移依存パッケージと呼ばれ、pandasが依存している、pandasが動作するために必要としているパッケージです。 pipの仕様上、これらの推移依存パッケージのバージョンはpandasが要求するパッケージバージョンの中で最新のものがインストールされます。pip installの仕様については こちら の公式ドキュメントからご確認ください。 つまり、requirements.txtでパッケージのバージョンを指定していたとしても、推移依存パッケージのバージョンは固定されておらず、インストールを実行したタイミングで互換性のある最新のバージョンをインストールしてしまうということになります。 ちなみにpandasが要求するパッケージバージョンは2026/4/2時点の公式ドキュメントで確認すると以下のようになっています。(sixはpython-dateutilの推移依存パッケージです。) Package Minimum supported version NumPy 1.26.0 python-dateutil 2.8.2 依存パッケージのバージョンを固定していたと思ってもこれらの推移依存のパッケージバージョンは固定されていないということに注意が必要です。 例えば、推移依存の最新バージョンに脆弱性があったり、破壊的変更が加えられていた場合、パッケージインストールしたタイミングによって、システムが動作しなくなる可能性や、予期せぬ不具合が起こる可能性があります。 この推移依存パッケージのバージョンを固定することができるのが次の章で説明するconstraints.txt及びpip installの-cオプションです。 constraints.txtと-cオプション 前章で挙げた推移依存パッケージのバージョンが固定されない問題を解決できるのがconstraints.txtです。 例によってconstraints.txtという名前である必要はないですが、一般的にconstraints.txtという名前なのでそのように表現いたします。 constraints.txtはインストールされるパッケージのバージョンに制約を設けることができるファイルです。 例えばrequirements.txtに以下の内容を記述してインストールする場面について考えてみます。 pandas==3.0.1 constraints.txtには以下のように記述したとします。 numpy==1.26.4 pip install時に-cオプションでテキストファイルを指定することで推移依存パッケージのインストールバージョンに制約を設けることができます。 実際にやってみます。 以下のコマンドで仮想環境へインストールを行います。 pip install -r requirements.txt -c constraints.txt インストール後のパッケージリストを確認します。 numpyの1.26.4がインストールされました。 このようにconstraints.txtによって推移依存パッケージのバージョンの固定が可能になります。 constraints.txtの活用方法 推移依存パッケージのバージョンを固定できることがわかりましたが、1つずつ推移依存を調べて書いていくのは面倒で手間がかかります。 以下のコマンドで、現在インストールされているすべてのパッケージとそのバージョンをconstraints.txtに記述できます。 実際に実行してみます。 注意 グローバルな環境でpip freezeすると無関係なパッケージがconstraints.txtに入ってしまいます。 requirements.txtのパッケージを仮想環境へインストールして、仮想環境内でpip freezeしましょう。 pip freeze > constraints.txt 実行した結果、直接依存、推移依存を含めて現在インストールされているすべてのパッケージとそのバージョンが記載されました。 このconstraints.txtを-cオプションで指定することで、いつ誰が環境構築をしても、完全に同じバージョン構成を再現できるようになります。 推移依存の意図しないアップデートを防ぎ、常にクリーンな環境を保てます。 requirements.txtに推移依存も含めてすべてのパッケージをpip freezeするというのも手ですが、constraints.txtを併用すれば、requirements.txtにはプロジェクトで必要なパッケージだけを管理し、推移依存を含めた全体のバージョンはconstraints.txtで管理するという役割分担が可能です。 まとめ pip installは推移依存パッケージのバージョンは要求バージョンの中で互換性のある最新のものをインストールする。 -cオプションによってインストールされるパッケージのバージョンを固定できる pip freeze によって簡単に現環境のパッケージバージョンをconstraints.txtに記述できる。 パッケージ管理はpip以外にもpoetryやuv等を使用して行う方法がありますが、今回はpythonの標準機能でパッケージを管理する方法についてまとめました。 今後はpoetryやuvを使用したパッケージ管理についても学んでブログに起こしていきたいと思います。 参考文献 pip installの仕様 https://pip.pypa.io/en/stable/cli/pip_install/#satisfying-requirements -cオプションについて https://pip.pypa.io/en/stable/cli/pip_install/#options ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 1人がこの投稿は役に立ったと言っています。 The post constraints.txtを使用してPythonの推移依存パッケージを管理する first appeared on SIOS Tech Lab .
こんな方へ特におすすめ セキュリティ対応において、何から手をつければいいか迷っている方 HSTSなどのセキュリティヘッダ導入が、他の環境(検証環境など)に与える影響を知りたい方 概要 こんにちは。サイオステクノロジーのはらちゃんです! 今回はSL代表としてセキュリティスキャン対策のコミュニケーションチームに入りました。 そこで得た「現場でのリアルな立ち回り」「セキュリティ対応の基本となる考え方」「HSTS導入時の思わぬ落とし穴(副作用)」といった、実務に直結する知見をまとめていきます。 背景 ある日、内部的なセキュリティスキャンが実施され、レポートが提供されました。 結果は「外部から容易に利用されるサイトで、改善が必要な項目が多々ある」という厳しいものでした。 最終的に、各SLで担当者を立てて全社的に修正対応を図ることになりました。 対応前のマインドセット 実際の技術的な修正に入る前に、チーム全体で以下の「考え方のポイント」を共有しました。無駄な作業を減らすための非常に重要なステップです。 「廃止・退役できるリソースか?」を最初に問う これが最もコストがかからず、最も安全な究極のセキュリティ対策。 使っていない検証環境は、修正するのではなく消すのが正解。 「対応しない」なら客観的な根拠を システム上の都合でどうしても対応できない項目もある。 しかし、放置するのではなく「なぜ対応できないか(客観的に妥当な根拠)」を言語化し、ディスカッションのテーブルに上げることが求められる。 オープンなコミュニケーション 専用のSlackチャンネルを用意し、不明点や気づきはすぐに共有する体制を作成。 3つの対応ポイント 指摘事項は多岐にわたりましたが、大きく以下の3点に分類して対応を進めました。 1. OS・パッケージの脆弱性対応 対応 サポートされている最新版に遅滞なく更新する。 ツール Amazon Inspector + AWS Security Hub CSPM(EC2やECRのDockerイメージの脆弱性を可視化)。 2. 伝送経路(SSL / TLS)の修正 対応 TLS1.0 / 1.1などの古いプロトコル、脆弱な暗号スイート(cipher)を無効化する。 IE11対応などの特殊な要件がない限り、2026年現在では TLS 1.2 / TLS 1.3 中心 で問題なし。 ツール Mozilla SSL Configuration Generator で安全なconfigを作成し、 SSL Labs で評価を確認。 3. セキュリティヘッダの指定 対応 Webサーバー(またはCDN)にCSPやHSTSなどのヘッダを追加する。 ツール securityheaders.com でのチェック。 ここが一番の難所でした。特にCSP(Content Security Policy)は、設定を間違えると必要なJSやCSSが読み込まれずサイトが壊れるため、技術開発センターの管理下サイトで先行検証・適用を進めるという慎重なアプローチをとりました。 そして、私のSLに最も影響を与えたのが HSTS です。 課題 HSTSポリシーと「自己署名証明書」の衝突 HSTS(HTTP Strict Transport Security)は、ブラウザに対して「今後は絶対にHTTPSで接続しろ」と強制する強力なセキュリティヘッダです。 ベストプラクティスとしては、以下の指定が推奨されます。 max-age で1〜2年以上の長期間を指定 preload 指定 includeSubDomains 指定(サブドメインも全てHTTPS強制) ここで問題発生です。依頼事項として「 [社内検証用サブドメイン配下] でHTTP接続しているリソースはないか?」という確認がありました。最終的にSSL接続が必須になります。 私のSLでは、以下の社内検証環境が稼働していました。 OpenShift検証環境 自動構築で「自己署名証明書(オレオレ証明書)」を利用。 社内検証Rancher/GitLab環境 内部DNS設定で、 [社内検証用サブドメイン配下] を利用。 HSTSが引き起こす「副作用」 本番ドメインにHSTSの includeSubDomains が付与されると、それにアクセスしたブラウザはポリシーを記憶します。 その後、同じブラウザで社内検証環境にアクセスすると、ブラウザは「HTTPSでの接続」と「正当な証明書の提示」を厳格に要求します。 そうすると、検証環境で使っている自己署名証明書がブラウザに強固にブロックされ、アクセス不可になるという懸念が浮上したのです。 検討案 HSTSの副作用をどう回避するか? 検証環境のために本番のセキュリティレベルを下げるわけにはいきません。私たちは以下の回避策を検討しました。 正規の証明書を自動発行する Let&#8217;s Encrypt (DNS-01認証): Route53と連携して証明書の取得・更新を全自動化する。 AWS Certificate Manager (ACM): AWS環境であれば、ACMで発行した証明書をALB等に割り当てるのが最もスマート。 社内検証・開発用ドメインを物理的に分ける 本番ドメインのサブドメインを使わず、検証専用の別ドメインを取得する。 GoogleやFacebookなどの大手テック企業も採用している王道のアプローチ。 再構築のタイミングでドメインを変更する(期間区切り) 「10月までは今の環境を使い、それ以降は新規作成する」という場合、次回の再構築時にドメイン構成を見直すというリスク受容の判断もアリ。 プライベートCAを構築する 社内用の認証局を立てる案だが、構築・維持のコストリスクが高いため優先度は低め。 まずは「影響を小さく検証」する 様々な理想論を挙げましたが、机上の空論で終わらせず、まずは「実際のブラウザ挙動」を確かめるスモールスタートを切ることにしました。 【決定したネクストアクション】 技術管理担当者が、 sios.jp に HSTS ポリシー( includeSubDomains あり)を適用する。 Slackで連絡を受けたら、インフラ担当者がブラウザで sios.jp にアクセスし、HSTSポリシーを学習させる。 そのまま検証環境へアクセスし、HTTPS強制ブロックの挙動を確認する。 回避策の検証 HSTSポリシーを共有しない「シークレットモード」や「別プロファイルのブラウザ」を利用することで、自己署名証明書の検証環境へアクセス可能か(業務への影響を回避できるか)をテストする。 まとめ セキュリティスキャンからの指摘は、一見すると「ただの面倒な作業」に思えるかもしれません。 しかし、今回のように「なぜHSTSのサブドメイン指定が検証環境を壊すのか?」「どうやって回避するのか?」をチームで議論することで、インフラ設計の解像度がグッと上がります。 不要なリソースは捨てるのが最強のセキュリティ。 HSTS導入時は、サブドメインで動いている検証環境(自己署名証明書)の死に直結しないか注意する。 理想のアーキテクチャ(ドメイン分離やACM活用)を描きつつも、まずはプライベートブラウザ等の運用回避で業務を止めない立ち回りも重要。 今後も、こうした全社的な取り組みを通して得られた知見を、皆さんに共有していきたいと思います! ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post セキュリティスキャン指摘対応 | HSTS導入の影響と回避策 first appeared on SIOS Tech Lab .
概要 こんにちは。サイオステクノロジーのはらちゃんです! 今回は、私が 第4回 JAZUG Shizuoka に参加してきた体験をレポートします。ローカル会場の雰囲気や私が登壇した内容の紹介などIT イベント参加の後押しになる情報をお届けします! こんな方へ特におすすめ JAZUG について知らない方 オフラインのIT イベントに興味があるけれど、参加をためらっている方 はじめに: JAZUG(ジャズユージー)とは? Japan Azure Users Group は、Microsoft Azure を学び、楽しみ、活かす、日本のユーザーグループで、2010/8/26 に結成したコミュニティです。 オフィシャルサイト から引用しました。 会場の様子: 常連参加者が多い印象 私が今回参加した静岡会場は、とても親しげな雰囲気でした。会場は 浜松市市民協働センター(第3研修室) に長テーブルが用意されており、参加者は隣同士で交流ができる構図になっていました。 15分前には到着される方がほとんどで、普段扱っている技術について議論する時間を楽しむ間に開始時刻となりました。 懇談会は参加必須 サブイベントとして、本イベント参加者を対象に懇談会を行いました。 登壇前は着席していたこともあり、なんとなく話しかけに行きにくいと感じていたため、道すがらや懇談会の向かい合った座席という場面で親交を深めることができました。 ここで技術初心者の質問をしても、いやな顔をする方などおらず、むしろ素直に分からない事を聞くことで理解を深められました。IT技術を知っているかではなく、興味があるかどうかが参加に大切だと思います。 懇談会への参加としてはもちろん強制ではありません。しかし、私としては懇談会こそ本命のように感じました。 登壇紹介 私が注目した登壇内容をご紹介します。 Work IQ × GitHub Copilot しろくま(Hiorki, Nomura)さんが紹介された「Work IQ」は、Microsoft 365 Copilot やAI エージェントにコンテキストを付与するインテリジェンスレイヤーです。 つまり、データを意味ベースで検索・解釈して、それをエージェント等に提供してくれます。 このメリットは主に以下の3点です。 業務データに自然言語でアクセスできる M365を横断検索する 調べた情報をもとに実行できる 私はGoogle ドライブから資料をダウンロードしてリポジトリに投げるという手間のかかる作業をしていたので、さっそく試してみたいと感じました。 &#8212; 登壇資料が こちら にアップロードされているので合わせてご一読ください。 マルチエージェント ×&nbsp; Azure AI Video Indexer Noriyuki Takeiさんが紹介されたマルチエージェントは、それぞれ役割を持つエージェントが別視点で分析し、それらを総括するエージェントが最終的な結果を表示していました。 ロジックの詳細については配信されるそうなので、視聴しようと思います笑 自分の登壇 実は、YoutubeのLive配信などでライトニングトークをした経験しかなく、オフラインでの登壇は初めての経験でした。 内容としては、これまでの業務で経験したAzureのアーキテクチャ選定に苦労した経験から、公式ツールを使った意識するポイント紹介をしました。 他の方の登壇を聞いていると、導入が引き込まれる話ばかりで、共感を得ることの重要性が分かった気がします。 また、周りを見渡すなどアクションをすることに慣れていないのでオフラインを想定した練習も必要だなと思いました。 まとめ 今回は静岡へ行ってきた感想と気になった登壇内容をまとめました。 JAZUG 参加に必要なのは技術への好奇心だけ。 Work IQ × GitHub Copilot で収集から実行まで一元管理できる。 そう遠くないうちに、またイベントへ参加したいと考えています。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 第4回 JAZUG Shizuoka 参加レポート|気になる雰囲気と登壇紹介 first appeared on SIOS Tech Lab .
今号では、Linux における「拡張子」について、ちょっと深掘りした内容をご説明します! 前号でご紹介した「 知っておくとちょっと便利!Linux の拡張子について 」もぜひご参照くださいね。 Linux がファイルの種類を判断する仕組み 前号で、Linux は ファイルヘッダ というファイルの情報を読み取り、どのようなファイルであるかを判断していると書きましたが、この部分をもう少し具体的に見ていきます。 ファイルの種類を調べたい場合は file コマンドを使用しますが、その際 マジックファイルと呼ばれるファイル (/usr/share/file/magic) の情報を参照した上でファイルの種類を判断します。 なお、/usr/share/file/magic の内容は下記の様になっています (一部抜粋)。file コマンドのプログラムが、どのようにこれらの値を参照して最終的なファイルの種類を決定するか、その仕組みはかなり難解なので、別の機会にお話しできればと思います。 # RISC OS Chunk File Format # From RISC OS Programmer's Reference Manual, Appendix D # We guess the file type from the type of the first chunk. 0 lelong 0xc3cbc6c5 RISC OS Chunk data >12 string OBJ_ \b, AOF object >12 string LIB_ \b, ALF library # RISC OS AIF, contains "SWI OS_Exit" at offset 16. 16 lelong 0xef000011 RISC OS AIF executable # RISC OS Draw files # From RISC OS Programmer's Reference Manual, Appendix E 0 string Draw RISC OS Draw file data # RISC OS new format font files # From RISC OS Programmer's Reference Manual, Appendix E 0 string FONT\0 RISC OS outline font data, >5 byte x version %d 0 string FONT\1 RISC OS 1bpp font data, >5 byte x version %d 0 string FONT\4 RISC OS 4bpp font data >5 byte x version %d # RISC OS Music files # From RISC OS Programmer's Reference Manual, Appendix E 0 string Maestro\r RISC OS music file >8 byte x version %d >8 byte x type %d # Digital Symphony data files # From: Bernard Jungen (bern8817@euphonynet.be) 0 string \x02\x01\x13\x13\x13\x01\x0d\x10 Digital Symphony sound sample (RISC OS), >8 byte x version %d, >9 pstring x named "%s", >(9.b+19) byte =0 8-bit logarithmic >(9.b+19) byte =1 LZW-compressed linear >(9.b+19) byte =2 8-bit linear signed >(9.b+19) byte =3 16-bit linear signed >(9.b+19) byte =4 SigmaDelta-compressed linear >(9.b+19) byte =5 SigmaDelta-compressed logarithmic >(9.b+19) byte >5 unknown format 0 string \x02\x01\x13\x13\x14\x12\x01\x0b Digital Symphony song (RISC OS), >8 byte x version %d, >9 byte =1 1 voice, >9 byte !1 %d voices, >10 leshort =1 1 track, >10 leshort !1 %d tracks, >12 leshort =1 1 pattern >12 leshort !1 %d patterns 0 string \x02\x01\x13\x13\x10\x14\x12\x0e >9 byte =0 Digital Symphony sequence (RISC OS), >>8 byte x version %d, >>10 byte =1 1 line, >>10 byte !1 %d lines, >>11 leshort =1 1 position >>11 leshort !1 %d positions >9 byte =1 Digital Symphony pattern data (RISC OS), >>8 byte x version %d, >>10 leshort =1 1 pattern >>10 leshort !1 %d patterns … Linux でよく使われる二重拡張子 (tar.gz 等) tar.gz や conf.bak 等、Linux ではドットが連続する 二重拡張子 を使用することがあります。 これは、そのファイルにおける 処理の履歴 を表します。 例えば、下記の様な二重拡張子を持つファイルを見てみると… .tar.gz: 複数のファイルを 1つにまとめた (.tar) ものを、圧縮した (.gz)。 .conf.bak: 設定ファイル (.conf) をバックアップとして残した (.bak)。 .log.1.gz: ログファイル (.log) の 1世代前をローテーション (.1) し、さらに圧縮した (.gz)。 それぞれこのような意味になります。 シバン (Shebang) の有無でファイルの種類が変わる!? シバンとは、Linux においてスクリプトファイルの1行目に記述する #! で始まる特別な文字列です。 実はこれが、ファイル実行時における拡張子の代わりとなります。 例えば、下記の様なテキストファイル test.txt があるとします。file コマンドでファイルの種類を確認すると、当然テキストファイル (ASCII text) と判断されます。 # cat test.txt echo "Hello" # file test.txt test.txt: ASCII text ところが、test.txt に #!/bin/bash という文字列を追加してみると、拡張子を含めたファイル名はそのままに、ファイルの種類が変わっていることが分かります。 # cat test.txt #!/bin/bash echo "Hello" # file test.txt test.txt: Bourne-Again shell script, ASCII text executable これは、マジックファイルの定義に基づき、ファイルの先頭に #!/bin/bash が追加された際にスクリプト (Bourne-Again shell script) と判断するルールがあるため、このような結果となります。 プログラムの 1行目に記載する #! は単なるコメントではなく、ファイルの種類を識別するためにも使用される重要な文字列であることが分かりますね。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 知っておくとちょっと便利!Linux の拡張子について2 (ちょっと深掘り) first appeared on SIOS Tech Lab .
こんにちは! 今月も「OSSのサポートエンジニアが気になった!OSSの最新ニュース」をお届けします。 アドビ (Adobe) は、Windows および macOS で Adobe Acrobat、Adobe Reader を使用しているユーザに影響を与える脆弱性「CVE-2026-34621」が、攻撃者に悪用されていることを正式に認めました。 これを受けて、セキュリティアップデートを 72時間以内にインストールするよう呼びかけています。 72時間以内の更新を推奨──「PDFを開くだけで乗っ取られる」アドビリーダーのゼロデイ攻撃が進行 https://forbesjapan.com/articles/detail/95601 攻撃者が送信したフィッシングメールに埋め込まれた番号に電話をかけると、オペレータが悪意のあるソフトウェアのインストール手順を案内したり、認証情報を取得しようとする、ATHR という攻撃プラットフォームが公開されています。 AI Meets Voice Phishing: How ATHR Automates the Full TOAD Attack Chain https://abnormal.ai/blog/athr-ai-voice-phishing-toad-attacks フランス政府は、政府用コンピュータを Windows から Linux へ移行する計画を発表しました。 ヨーロッパ域外へのデジタル依存度を低減するという目標を設定したためです。 フランス政府がWindows搭載の政府用コンピュータをLinuxに移行する計画を発表、アメリカ製技術への依存をさらに軽減するため https://gigazine.net/news/20260414-france-windows-linux/ ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 【2026年4月】OSSサポートエンジニアが気になった!OSS最新ニュース first appeared on SIOS Tech Lab .
Webサービスやアプリの開発現場で、「ここにハンバーガーメニューを置こう」といった会話を耳にしたことはないでしょうか。 「ハンバーガー」「ミートボール」「ケバブ」「ワッフル」「弁当箱」。おいしそうなランチのような名前が並んでいますが、これらは今のUIデザインに欠かせない「メニューアイコン」の俗称です。 スマートフォンの狭い画面にパソコンに準じた多くの情報を盛り込むために、これらのアイコンは「要素を隠すための魔法の箱」として多用されています。しかし、これらは便利であると同時に、使い方を間違えるとプロダクトの使い勝手を下げる「劇薬」でもあります。 今回は、それぞれのアイコンの暗黙的ルールと、メニューを隠すことの代償について紐解いていきます。 4つのメニューアイコンと意味 これらのアイコンは、どれも「クリック(タップ)すると何かメニューが開く」という点では同じですが、使われる文脈が異なります。 1. 三本線 名称、呼称: menu、ハンバーガーメニュー、&#8230; 役割: グローバルナビゲーション(アプリケーション全体に関わる主要なメニュー) 配置: 主に画面の左上(または右上) どの画面にいてもアクセスできる、プロダクトの根幹となるナビゲーションを隠すために使われます。 アイコンが三本線ではなく、二本線や、四本線の場合もあります。同様の役割で、三本線ではなくサイドバーを表すアイコンが配置されている場合もあります。 例: Google Material Designサイト 例: Anthropic Claude (Web) 2. 横の三点リーダー 名称、呼称: ellipsis horizon、more horizon、overflow menu、水平の三点リーダー、ミートボールメニュー、&#8230; 役割: 画面全体や、要素に対する追加のアクション 配置: 画面の右上や、要素の末尾など 文章の最後の「……(続く、省略されている)」と意味合いも形状も同じで、「この行(アイテム)に対して、編集・削除・共有などの操作」(コンテキストメニュー)や、「この画面に関するその他の操作」を示します。 「削除」操作は、誤操作を防ぐために、あえてコンテキストメニューに入れて隠す場合もあります。 例: OpenAI ChatGPT(Web) 例: Apple iOS Safari(Webブラウザー) 3. 縦の三点リーダー 名称、呼称: ellipsis vertical、more vertical、overflow menu、垂直の三点リーダー、ケバブメニュー、&#8230; 役割: 「横の三点リーダー」と同様 配置: 「横の三点リーダー」と同様 「縦」に並んだ点が、展開するアクションメニュー(メニューリスト)を想像させる形状です。 Googleのプロダクトにて標準的に利用されています。 「横の三点リーダー」と「縦の三点リーダー」は同様の役割で、同じサービス内で共存、使い分けるケースは少ないです。 例: Google Gemini(Web) 例: Google Chrome(Webブラウザー) 4. 九つの点 名称、呼称: Apps、launcher、launchpad、ワッフルメニュー、弁当箱メニュー、&#8230; 役割: 別のアプリケーションや、独立したモジュールへの切り替え 配置: 画面の左上、右上など ハンバーガーメニューがアプリケーション内のグローバルメニューなのに対して、ワッフルメニューは、現在のアプリケーション以外に切り替える(起動する)メニューに使われます。 現在のコンテキストを離れ、全く別の機能群にジャンプするための「ハブ」として機能します。 例: Microsoft 365(Web) 現時点では、これらの「暗黙のルール」が主流となっており、メニューアイコンを選ぶ際には、これらを踏まえるのがユーザーのためにも無難です。 メニューを隠すことの代償 これらのアイコンを使えば、どんなに複雑な機能でも小さなボタンの中に押し込むことができます。画面はスッキリと美しくなり、一見するとデザインが洗練されたように感じます。 しかし、UIデザインにおいて 「隠す」ことには代償 が伴います。 認知心理学の分野やUXデザインにおいては 「発見可能性(Discoverability)」 という言葉が使われます。英語のことわざに &#8220;Out of sight, out of mind&#8221;(見えなければ、忘れ去られる)とあるように、ユーザーは「画面に見えていない機能」はないものとして扱います。 メニューアイコンの中に機能を隠すということは、以下の2つの認知的負荷をユーザーに強いることになります。 推測の負荷: 「このアイコンを押せば、自分が求めている機能があるはずだ」とユーザー自身に推測させる必要がある。 操作の負荷: 目的の機能にたどり着くまでに、必ず「メニューを開く」という余分な1クリック(タップ)が発生する。 「画面をスッキリさせたい」という開発者側の都合で作られたハンバーガーメニューの奥底に、プロダクトにとって重要な機能(リンク)を隠してしまった結果、ユーザビリティや、ビジネス的な成果の悪化を招くこともあります。 見えるメニューも試す 「隠すことの代償」を軽減する具体策としては、例えばモバイルアプリでは、ボトムナビゲーション(タブバー)(見えるメニュー)に主たる項目を配置しハンバーガーメニュー内(見えないメニュー)にそれ以外の項目を並べることを試すとよいでしょう。 同様に、三点リーダーの場合も何を隠すのか、隠さないのか、よく検討することが大切です。 例: バーガーキング(App) まとめ:アイコンを「ガラクタ箱」にしないために ハンバーガーメニュー、三点リーダーメニュー。これらは限られた画面領域を有効活用するための素晴らしい発明です。しかし、これらを 「画面に収まりきらなかった機能をとりあえず放り込んでおくガラクタ箱」 として使ってはいけません。モバイル用、デスクトップ用ともに。 UIを設計する際、アイコンで隠す前に、まず 「情報設計(IA:Information Architecture)」 から見直す必要があります。 メニューアイコンは「デザインの魔法」ではなく、単に「整理のための引き出し」と捉えたほうがよいでしょう。内容、役割、頻度、数、ラベルなど、項目を論理的に整理することが、使いやすいプロダクトを生み出すための第一歩となります。 余談:「食べ物の呼び名(スラング)」の歴史 「三本線」や「横の三点リーダー」のアイコン自体は古くからありましたが、それらが「食べ物の名前」で呼ばれるようになったのは、スマートフォンが普及して画面の省略化が進んだ2010年代以降のことです。デザイナーたちの「連想ゲーム」で以下のように名付けられていったようです。 ハンバーガー iPhoneの登場後、Facebookなどの人気アプリが画面領域を節約するために「三本線」のアイコンでメニューを隠すUIを採用しました。この時、アイコンが爆発的に普及し、デザイナーや開発者の間で「ハンバーガー」と呼ばれ始め、UIデザインにおける最初にして最大の食べ物スラングとして定着しました。 ワッフル / 弁当箱 Googleが「9つの点」のランチャーを大々的に導入。すでに「ハンバーガー」という食べ物スラングが定着していた界隈で、「じゃあ、あの四角い格子状のやつはワッフルだ」「いや、おかずが詰まったBento Box(弁当箱)だ」と呼ばれるように。 ケバブ AndroidのMaterial Designにて標準化された「縦の3つの点」。「ハンバーガーがあるなら、肉が縦に並んでいるのは『ケバブ』だろう」というジョークが生まれました。 ミートボール ケバブ(縦)という呼び名に対して、昔から使われていた「横の3つの点」を呼び分ける必要が出てきました。「縦が串焼き(ケバブ)なら、お皿に横に転がっている肉団子だから『ミートボール』にしよう」という、後追いの連想ゲーム。 … UI界隈でも、どこまでこれらの俗称が通じるかは不明ですが &#8230;。 Photo by Jean-claude Attipoe , Olivier Amyot  , Victoria Shes , Najmah Faisal on Unsplash   ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 1人がこの投稿は役に立ったと言っています。 The post 「隠すUI」の功罪:それ、ハンバーガーで大丈夫? first appeared on SIOS Tech Lab .
こんにちは、サイオステクノロジー武井です。今回は、イケてるOSSであるDaprについて一筆したためました。 Daprとは? Daprとは、Distributed Application Runtimeの略であり、本当にざっくり一言で言えば、分散アプリケーションサービスを開発する際のインフラレイヤーを抽象化するためのものです。 ここは説明が非常に難しいので、この章では、ざっくりとした理解で構いません。順を追ってDaprの本質に迫っていきたいと思います。 ここでDaprを理解するために一つの例を上げてみましょう。 例えば、とあるアプリケーションで、ユーザーの情報を保存するために、データベースを使用しているとします。当然従来のアプリケーションでは、当然MySQLにはMySQL用の実装を、PostgreSQLにはPostgreSQL用の実装をする必要があります。これをDaprを使用することで、データベースの種類に関係なく、同じコードでデータベースにアクセスすることができます。つまり、Daprは、データベースの種類を抽象化してくれるのです。 他にも、Daprは、Publish and Subscribe、State Management、Bindingsなど分散アプリケーションを開発する際に必要な機能を提供しているのですが、これを実現するアーキテクチャとして最も重要なのが、Daprのサイドカーです。 このサイドカーの仕組みをわかりやすく伝えるために、データベースにアクセスする際の例を用いて、「Daprを使わない場合」と「Daprを使う場合」の違いを見てみましょう。 Daprを使わない場合 従来のアプリケーションでは、データベースにアクセスするためのコードは、アプリケーションの中に直接書かれています。例えば、MySQLにアクセスするためのコードは、MySQL用のライブラリを使用して書かれています。その構成は以下の通りとなります。 例えば、ここでMySQLをPostgreSQLに変更したい場合、MySQL用のコードを削除して、PostgreSQL用のコードを書き直す必要があります。書き直した後の構成は以下の通りとなります。 先ほどとは違い、アプリ内の「MySQL用のコード」が「PostgreSQL用のコード」に変わっていますね。このように、Daprを使わない場合、データベースの種類を変更するたびに、アプリケーションのコードを変更する必要があります。これが、Daprを使う場合と比べて、非常に面倒であることがわかります。 Daprを使う場合 Daprを使う場合、アプリケーションのコードは、DaprのAPIを使用してデータベースにアクセスします。Daprは、データベースの種類を抽象化してくれるため、アプリケーションのコードは、データベースの種類に関係なく同じコードでアクセスすることができます。 MySQLにアクセスするためのDaprを使う場合の構成は以下の通りとなります。 「Daprを使わない場合」と大きく異なるのは、サイドカーが存在していることです。アプリケーションは、DaprのAPIを使用してサイドカーにアクセスし、サイドカーがデータベースにアクセスするという構成になっています。この構成のメリットは、データベースの種類を変更する際に、アプリケーションのコードを変更する必要がないことです。例えば、MySQLからPostgreSQLに変更したい場合、サイドカーの設定を変更するだけで、アプリケーションのコードはそのままで済みます。変更後の構成は以下の通りとなります。 アプリ側は全く変更してないのがわかりますでしょうか?変更したのは、サイドカーの設定だけです。この設定では、サイドカーにてPostgreSQLにアクセスするように変更しています。 このように、Daprを使う場合、データベースの種類を変更する際に、アプリケーションのコードを変更する必要がないため、非常に便利であることがわかります。 一方で、このサイドカーが対応していないデータベースを使用したい場合は、サイドカーの設定を変更するだけでは対応できないため、アプリケーションのコードを変更する必要があります。 この例では、データベースへのアクセスを例に挙げましたが、Daprは、Publish and Subscribe、State Management、Bindingsなど、分散アプリケーションを開発する際に必要な機能を提供しているため、これらの機能も同様に抽象化されていることがわかります。つまり、Daprを使うことで、分散アプリケーションの開発が非常に楽になるということです。 サイドカーの実行形態について Daprのサイドカーの実行形態は大きく分けると、以下の2つがあります。 サイドカーコンテナ サイドカープロセス サイドカーコンテナ サイドカーコンテナは、Daprのサイドカーがコンテナとして実行される形態です。これは、Kubernetesなどのコンテナオーケストレーションツールを使用している場合に一般的に使用されます。Daprのサイドカーは、アプリケーションと同じPod内のコンテナとして実行されます。アプリケーションとDaprはlocalhostで通信するため、低レイテンシで連携できます。図中の「Scheduler」「Placement」については、Daprのコントロールプレーンコンポーネントであり、サイドカーコンテナとは別に実行されます。本記事では、サイドカーコンテナの説明に焦点を当てるため、これらのコントロールプレーンコンポーネントに関する説明は省略しています。 サイドカープロセス Daprのサイドカーは、VMやローカル環境ではアプリケーションと同じホスト上の別プロセスとして実行されます。アプリケーションは、HTTPまたはgRPCを通じてローカルのdaprdプロセスに接続します。この形態は、Kubernetes以外の環境でDaprを利用する場合によく使われる実行方法です。 以下の図は、開発環境でDaprを利用する際のサイドカープロセスの構成例です。dapr CLIというDapr専用のコマンドラインツールを使用すると、アプリケーションとサイドカープロセスを同時に起動できます。 RedisとZipkinは、Daprのサイドカーが利用する外部コンポーネントの例です。Redisは、DaprのState ManagementやPublish and Subscribeなどの機能でよく利用されるデータストアです。またZipkinは、Daprの分散トレーシング機能で使用されるトレーシングシステムです。これらのコンポーネントは、dapr CLIによって起動されます。 本記事で紹介するサンプルアプリケーションは、ここで説明したように、dapr CLIによって起動されるサイドカープロセスの構成で動作することを前提としています。ただし、実行形態が異なる場合でも基本的な考え方は同じであり、他の実行形態にも応用できます。そのため、本記事ではサイドカープロセスの形態を前提として説明していきます。 Daprを使うための環境準備 先程説明しましたように、Daprを使うための環境は様々なです。例えば、Kubernetes環境でDaprを利用する場合は、Kubernetesクラスターを用意し、Daprをインストールする必要があります。一方で、ローカル環境でDaprを利用する場合は、dapr CLIをインストールするだけで済みます。 本記事の中で説明するサンプルアプリケーションは、ローカル環境でDaprを利用することを前提としています。そのため、dapr CLIをインストールする必要があります。dapr CLIとは、Daprをローカル環境で利用するためのコマンドラインツールです。dapr CLIを使用することで、Daprのサイドカープロセスを簡単に起動したり、Daprのコンポーネントを管理したりすることができます。 そのインストール手順は以下のURLに記載されていますので、それを見てさっくりとインストールしてみてください。 https://docs.dapr.io/getting-started/install-dapr-cli/ そのインストール方法はめっちゃ簡単です。一例としてLinuxにDapr CLIをインストールする手順は以下の通りです。 $ wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash そして、次に、初期化が必要になります。これは、ローカル環境でDaprを使うための様々なコンポーネントを起動するためのコマンドです。初期化のコマンドは以下の通りです。 $ dapr init このコマンドにより、Daprのサイドカープロセスであるdaprdがインストールされ、そしてDaprを動作させるために必要な基本コンポーネントであるRedisやZipkinなども起動されます。試しに以下のコマンドを実行してみてください。 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6822763b7ba6 redis:6 "docker-entrypoint.s…" 2 weeks ago Up 13 days 0.0 .0.0:6379- &gt; 6379 /tcp, [ :: ] :6379- &gt; 6379 /tcp dapr_redis d5bf3b79e44d daprio/dapr:1.17.0 "./scheduler --etcd-…" 2 weeks ago Up 13 days 0.0 .0.0:2379- &gt; 2379 /tcp, [ :: ] :2379- &gt; 2379 /tcp, 0.0 .0.0:50006- &gt; 50006 /tcp, [ :: ] :50006- &gt; 50006 /tcp, 0.0 .0.0:58081- &gt; 8080 /tcp, [ :: ] :58081- &gt; 8080 /tcp, 0.0 .0.0:59091- &gt; 9090 /tcp, [ :: ] :59091- &gt; 9090 /tcp dapr_scheduler c79f84b94e2f daprio/dapr:1.17.0 "./placement" 2 weeks ago Up 13 days 0.0 .0.0:50005- &gt; 50005 /tcp, [ :: ] :50005- &gt; 50005 /tcp, 0.0 .0.0:58080- &gt; 8080 /tcp, [ :: ] :58080- &gt; 8080 /tcp, 0.0 .0.0:59090- &gt; 9090 /tcp, [ :: ] :59090- &gt; 9090 /tcp dapr_placement dbf0fab9540b openzipkin/zipkin "start-zipkin" 2 weeks ago Up 13 days ( healthy ) 0.0 .0.0:9411- &gt; 9411 /tcp, [ :: ] :9411- &gt; 9411 /tcp dapr_zipkin 様々なコンテナが起動していることがわかりますね。これらのコンテナは、DaprのコントロールプレーンコンポーネントであるSchedulerやPlacement、そしてDaprの分散トレーシング機能で使用されるZipkinなどです。それぞれのコンテナの説明は割愛します。ここでは、 これで、ローカル環境でDaprを利用するための環境が整いました。 Daprの様々なコンポーネント Daprにはさまざまなコンポーネントが用意されています。これらのコンポーネントは、Daprの機能を実現するためのものであり、アプリケーションはDaprを介してそれらを利用できます。 例えば、Daprには状態を保存するための State Management コンポーネントがあります。このコンポーネントを利用するには、設定ファイルで対象のコンポーネントを定義します。すると、Daprサイドカー(daprd)がそのコンポーネントを読み込み、対応するインフラへ接続できるようになります。 その結果、アプリケーションがサイドカーに対して状態を保存するAPIを呼び出すと、サイドカーはState Managementコンポーネントを通じて、RedisやAzure Cosmos DBなどのバックエンドストレージに状態を保存できるようになります。 本章では、Daprの代表的なコンポーネントであるState Management、Publish and Subscribe、Service Invocation、Bindingsについて、簡単に説明していきます。これらのコンポーネントは、Daprを使用する際に非常に重要な役割を果たすため、理解しておくことが重要です。 そして、以降では、これらのコンポーネントを実際に利用するためのサンプルコードも紹介しながら、Daprの機能を説明していきます。サンプルコードはGitHubのリポジトリに公開していますので、ぜひ参考にしてみてください。 https://github.com/noriyukitakei/dapr-sample State Management まずは、State Managementコンポーネントについて説明します。State Managementコンポーネントは、Key/Value形式の状態を保存するためのコンポーネントです。Daprは、State Managementコンポーネントを通じて、RedisやAzure Cosmos DBなどのバックエンドストレージに状態を保存できるようになります。 Key/Value形式のデータの代表例で言えば、ユーザーのセッション情報や、IoTデバイスの状態(例: 温度センサーの最新の温度値)などが挙げられます。これらのデータは、アプリケーションの状態を管理するために頻繁に使用されます。 システム構成 State Managementコンポーネントを利用する際のシステム構成は以下の通りとなります。 サイドカーは、Redis用の設定ファイルを読み込むことで、Redisに接続できるようになります。アプリケーションは、DaprのAPIを使用してサイドカーに状態を保存するリクエストを送ります。サイドカーは、そのリクエストを受け取ると、State Managementコンポーネントを通じて、Redisに状態を保存します。 ファイル構成 State Managementコンポーネントを利用するためのファイル構成は以下の通りとなります。これらのファイルは先程紹介したGitHubリポジトリ( https://github.com/noriyukitakei/dapr-sample )の中の、State Managementディレクトリの中に配置されています。 StateManagement ├── app.py ├── Infrastructure │ └── components │ ├── redis │ │ └── statestore_redis.yaml │ └── sqlite │ └── statestore_sqlite.yaml ├── README.md └── requirements.txt ではこれらのファイルを一つずつ紐解くことで、State Managementコンポーネントを利用するための構成を理解していきましょう。 ソースコードの説明 ■ Infrastructure/components/redis/statestore_redis.yaml Infrastructure/components/redis/statestore_redis.yamlは、DaprのState ManagementコンポーネントをRedisに接続するための設定ファイルです。このファイルには、DaprがRedisに接続するための情報が記載されています。 apiVersion : dapr.io/v1alpha1 # Daprのリソース定義のバージョン kind : Component # 設定ファイルの種別 metadata : name : statestore # アプリから参照するコンポーネント名 spec : type : state.redis # コンポーネントの種別 version : v1 # コンポーネントのバージョン metadata : - name : redisHost value : localhost : 6379 # Redisの接続先 - name : redisPassword value : "" # パスワード(未設定) このファイルは一言で言うと、「DaprのState ManagementでRedisを使う」ことを定義しているものです。アプリケーションはRedisを直接操作するのではなく、DaprのAPIを通じて状態を保存します。 例えばアプリケーションから次のように呼び出すと、 client . save_state ( "statestore" , "user1" , data ) Daprは内部的に先程のコンポーネント定義を参照して、Redisに接続し、状態を保存します。つまり以下のような流れです。 アプリケーションがDaprのAPIを呼び出す。そのとき、コンポーネント名として「statestore」を指定する。 Daprがコンポーネント定義の中から、metadata.nameが「statestore」であるコンポーネント定義を探す。 Daprがコンポーネント定義を参照して、Redisに接続するための情報(ここでは接続先がlocalhost:6379、パスワードが空)を取得する。 Daprが取得した情報を基に、Redisに接続する。 Daprがアプリケーションから受け取った状態をRedisに保存する。 Daprが保存の結果をアプリケーションに返す。 ■ app.py app.pyは、DaprのState Managementコンポーネントを利用するためのアプリケーションコードです。このコードは、DaprのPython SDKを使用して、状態を保存するためのAPIを呼び出しています。 では、コードの内容を見てみましょう。 for i in range ( 1 , 10 ) : json_data = [ { "key" : str ( i ) , "value" : { "orderId" : i } } ] requests . post ( "http://localhost:3611/v1.0/state/statestore" , json = json_data ) time . sleep ( 1 ) このコードは、1から9までの数字をキーとし、その値としてorderIdを持つJSONオブジェクト(以下参照)を作成し、DaprのState Management APIにPOSTリクエストを送っています。 [ { "key" : "1" , "value" : { "orderId" : 1 } } , { "key" : "2" , "value" : { "orderId" : 2 } } , ⋯以下略⋯ ] リクエストのURLには、先程のコンポーネント定義で指定した「statestore」が含まれています。これにより、Daprは「statestore」という名前のState Managementコンポーネントを参照して、Redisに状態を保存します。 リクエストのURLの構成は以下の通りです。 http://localhost:3611: Daprサイドカーのエンドポイント ※ このポート番号については、Dapr CLIを使用してサイドカープロセスを起動する際に指定したポート番号になります。後ほど説明しますが、Dapr CLIを使用してサイドカープロセスを起動する際に、&#8211;app-portオプションでアプリケーションのポート番号を指定することができます。例えば、アプリケーションのポート番号を3611に指定した場合、Daprサイドカーはlocalhost:3611で待ち受けるようになります。 /v1.0: Dapr APIのバージョン /state: State Management APIを呼び出すことを示すパス /statestore: 先程のコンポーネント定義で指定した「statestore」という名前のState Managementコンポーネントを参照するためのパス result = requests . get ( f"http://localhost:3611/v1.0/state/statestore/ { i } " ) print ( result . json ( ) ) time . sleep ( 1 ) このコードは、先程保存した状態をDaprのState Management APIにGETリクエストを送って取得しています。リクエストのURLには、先程のコンポーネント定義で指定した「statestore」が含まれていることに加えて、最後に/{i}が追加されています。これにより、Daprは「statestore」という名前のState Managementコンポーネントを参照して、Redisからキーが{i}である状態を取得します。リクエストのURLの構成は以下の通りです。 http://localhost:3611: Daprサイドカーのエンドポイント /v1.0: Dapr APIのバージョン /state: State Management APIを呼び出すことを示すパス /statestore: 先程のコンポーネント定義で指定した「statestore」という名前のState Managementコンポーネントを参照するためのパス /{i}: 取得したい状態のキーを指定するためのパス requests . delete ( f"http://localhost:3611/v1.0/state/statestore/ { i } " ) time . sleep ( 1 ) このコードは、先程保存した状態をDaprのState Management APIにDELETEリクエストを送って削除しています。リクエストのURLの構成は、先程のGETリクエストと同様であり、最後に/{i}が追加されていることがわかります。これにより、Daprは「statestore」という名前のState Managementコンポーネントを参照して、Redisからキーが{i}である状態を削除します。リクエストのURLの構成は以下の通りです。 http://localhost:3611: Daprサイドカーのエンドポイント /v1.0: Dapr APIのバージョン /state: State Management APIを呼び出すことを示すパス /statestore: 先程のコンポーネント定義で指定した「statestore」という名前のState Managementコンポーネントを参照するためのパス /{i}: 削除したい状態のキーを指定するためのパス 実行方法 では次に、State Managementコンポーネントを利用するためのアプリケーションコードを実行してみましょう。実行する前に、Dapr CLIを使用してサイドカープロセスを起動する必要があります。ここではその手順を説明します。 $ git clone https://github.com/noriyukitakei/dapr-sample $ cd dapr-sample/StateManegement まずは、先程紹介したGitHubリポジトリからサンプルコードをクローンして、StateManagementディレクトリに移動します。 $ pip install -r requirements.txt 次に、Pythonの依存関係をインストールします。requirements.txtには、このアプリケーションコードを実行するために必要なPythonパッケージが記載されています。 ちなみにRedisについては、dapr initコマンドを実行した際に、DaprのState ManagementやPublish and Subscribeなどの機能でよく利用されるデータストアであるRedisも起動されます。ですので、特にRedisを起動するためのコマンドを実行する必要はありません。 $ dapr run --app-id statemanagement --dapr-http-port 3611 --components-path Infrastructure/components/redis -- python app.py dapr runコマンドを使用して、アプリケーションコードを実行します。 &#8211;app-idオプションでアプリケーションのIDを指定します。ここでは「statemanagement」というIDを指定しています。このIDは、Daprサイドカーがアプリケーションを識別するために使用されます。State Managementでは利用しませんが、Service Invocationなどの機能を利用する際に、このIDが重要になります。 &#8211;dapr-http-portオプションでDaprサイドカーのHTTPポートを指定します。ここでは3611を指定しています。これにより、Daprサイドカーはlocalhost:3611で待ち受けるようになります。 &#8211;components-pathオプションで、Daprのコンポーネント定義ファイルが配置されているディレクトリを指定します。ここではInfrastructure/components/redisを指定しています。これにより、Daprサイドカーはこのディレクトリの中にあるコンポーネント定義ファイルを読み込むようになります。 図解すると以下のような対応関係になります。 このコマンドを実行すると、Daprサイドカーが起動し、そしてアプリケーションコードも実行されます。アプリケーションコードは、DaprのState Management APIを呼び出して、状態を保存、取得、削除するリクエストを送ります。Daprサイドカーは、そのリクエストを受け取ると、State Managementコンポーネントを通じて、Redisに状態を保存、取得、削除します。 保存先をSQLiteに変更してみる では、Daprのメリットを体感して頂くために、状態の保存先をRedisからSQLiteに変更してみましょう。Daprを使わない場合は、アプリケーションコードを変更する必要がありますが、Daprを使う場合は、コンポーネント定義ファイルを変更するだけで済みます。 つまりこんな感じです。 変更後のコンポーネント定義ファイルは以下の通りとなります。 apiVersion : dapr.io/v1alpha1 kind : Component metadata : name : statestore spec : type : state.sqlite version : v1 metadata : - name : connectionString value : "sqlite/data.db" まず、Redis用のコンポーネント定義ファイルと比較すると、typeがstate.sqliteに変わっています。これにより、DaprはState ManagementコンポーネントとしてSQLiteを使用するようになります。そして、spec.metadataの内容も変わっています。SQLiteに接続するための情報を記載する必要があるため、connectionStringという名前のメタデータを追加しています。これにより、DaprはSQLiteに接続するための情報を取得できるようになります。 このファイルは、Infrastructure/components/sqlite/statestore_sqlite.yamlに配置されています。 先程のdapr runコマンドの&#8211;components-pathオプションで、このファイルが配置されているディレクトリを指定することで、Daprサイドカーはこのファイルを読み込むようになります。 $ dapr run --app-id statemanagement --dapr-http-port 3611 --components-path Infrastructure/components/sqlite -- python app.py いかがでしょうか?アプリケーションコードを一切変更することなく、状態の保存先をRedisからSQLiteに変更することができましたね。これがDaprのメリットの一つである、インフラレイヤーの抽象化による柔軟性の高さです。 Publish and Subscribe 次は、Publish and Subscribeコンポーネントについて説明します。DaprのPublish and Subscribeコンポーネントについて説明する前に、まずはPublish and Subscribeの概念について簡単に説明します。 例えば、あるアプリケーションで、ユーザーが新しい注文を作成したとします。そのとき、注文が作成されたことを他のサービスに通知したい場合があります。例えば、在庫管理サービスや配送サービスなどです。このような場合に、Publish and Subscribeの仕組みが役立ちます。 Publish and Subscribeを使わない場合で考えてみましょう。ユーザーが新しい注文を作成したとき、注文サービスは在庫管理サービスや配送サービスに対して、HTTPリクエストを送って通知します。注文サービスは、配送サービス、在庫管理サービスの両方から正常にレスポンスが返ってきたら、注文が正常に処理されたと判断します。 もし、ここで配送サービスがダウンしている場合、注文サービスは配送サービスに通知することができません。そのため、注文サービスは失敗して、ユーザーは注文を作成することができません。 そこでPublish and Subscribeの仕組みを使うと、注文サービスは、注文が作成されたことを「メッセージブローカー」に対して通知します。そして、配送サービスや在庫管理サービスは、そのメッセージブローカーから注文が作成されたことを受け取ります。これにより、注文サービスは配送サービスや在庫管理サービスの状態に関係なく、注文が作成されたことを通知することができます。そして、配送サービスが仮にダウンしていたとしても、注文サービスは注文が作成されたことをメッセージブローカーに通知することができるため、ユーザーは注文を作成することができ、かつ在庫管理サービスは注文が作成されたことを受け取ることができます。配送サービスが復旧したときに、配送サービスも注文が作成されたことを受け取ることができます。 このように、Publish and Subscribeの仕組みを使うことで、サービス間の疎結合な連携が可能になります。DaprのPublish and Subscribeコンポーネントは、このようなPublish and Subscribeの仕組みを提供するためのコンポーネントです。Daprを使用することで、アプリケーションは、DaprのAPIを通じて、メッセージブローカーに対してメッセージを公開したり、メッセージブローカーからメッセージを受け取ったりすることができます。 システム構成 DaprでPublish and Subscribeコンポーネントを利用する際のシステム構成は以下の通りとなります。メッセージブローカーとしてRedisを使用する場合の構成例を示しています。 ① まず、Publisherが、DaprのHTTP/gRPC APIを通じて、サイドカーに対してメッセージを公開するリクエストを送ります。 ② サイドカーは、そのリクエストを受け取ると、Publish and Subscribeコンポーネントを通じて、Redisなどのメッセージブローカーに対して、メッセージを送信します。 ③ サイドカーは、Redisに接続し、Redisからメッセージを受け取ります。 ④ サイドカーは、そのリクエストを受け取ると、事前にコンポーネントで指定したSubscriberのエンドポイントに対して、メッセージを送信します。 もちろん、PublisherとSubscriberは、メッセージを格納するためのデータストアに何を使っているのかを知る必要はありません。RedisだろうがRabbitMQだろうが、Publisherは、DaprのAPIを通じてサイドカーに対してメッセージを公開するだけで済みますし、Subscriberは、DaprのAPIを通じてサイドカーからメッセージを受け取るだけで済みます。これが、DaprのPublish and Subscribeコンポーネントのメリットの一つである、インフラレイヤーの抽象化による柔軟性の高さです。 ファイル構成 Publish and Subscribeコンポーネントを利用するためのファイル構成は以下の通りとなります。これらのファイルは先程紹介したGitHubリポジトリ( https://github.com/noriyukitakei/dapr-sample )の中の、PubSubディレクトリの中に配置されています。 PubSub ├── Infrastructure │ └── components │ └── pubsub.yaml ├── publisher │ ├── app.py │ └── requirements.txt └── subscriber ├── app.py └── requirements.txt ソースコードの説明 ■ Infrastructure/components/pubsub.yaml Infrastructure/components/pubsub.yamlは、DaprのPublish and SubscribeコンポーネントをRedisに接続するための設定ファイルです。このファイルには、DaprがRedisに接続するための情報が記載されています。 apiVersion : dapr.io/v1alpha1 # Daprのリソース定義のバージョン kind : Component # 設定ファイルの種別 metadata : name : orderpubsub # アプリから参照するコンポーネント名 spec : type : pubsub.redis # コンポーネントの種別 version : v1 # コンポーネントのバージョン metadata : - name : redisHost # Redisのホスト名とポート value : localhost : 6379 - name : redisPassword # Redisのパスワード value : "" StateManagementと同じところは説明を省略します。ここで新たに説明する必要があるのは、spec.typeがpubsub.redisになっていることです。これにより、DaprはPublish and SubscribeコンポーネントとしてRedisを使用するようになります。 順番が前後しますが、metadata.nameが「orderpubsub」であることも重要です。これにより、アプリケーションは「orderpubsub」という名前のPublish and Subscribeコンポーネントを参照して、メッセージを公開したり、メッセージを受け取ったりすることができます。 ■ publisher/app.py publisher/app.pyは、DaprのPublish and Subscribeコンポーネントを利用してメッセージを公開するためのアプリケーションコードです。このコードは、 トピックにメッセージを発行する for i in range ( 1 , 10 ) : order = { "orderId" : i } requests . post ( "http://localhost:3613/v1.0/publish/orderpubsub/orders" , json = order ) logging . info ( "送信データ: " + json . dumps ( order ) ) time . sleep ( 1 ) このコードは、1から9までの数字をorderIdとするJSONオブジェクト(以下参照)を作成し、DaprのPublish and Subscribe APIにPOSTリクエストを送っています。 { "orderId" : 1 } リクエストのURLには、先程のコンポーネント定義で指定した「orderpubsub」が含まれています。これにより、Daprは「orderpubsub」という名前のPublish and Subscribeコンポーネントを参照して、Redisにメッセージを公開します。リクエストのURLの構成は以下の通りです。 http://localhost:3613: Daprサイドカーのエンドポイント /v1.0: Dapr APIのバージョン /publish: Publish and Subscribe APIを呼び出すことを示すパス /orderpubsub: 先程のコンポーネント定義で指定した「orderpubsub」という名前のPublish and Subscribeコンポーネントを参照するためのパス /orders: メッセージのトピックを指定するためのパス トピックとは、メッセージを分類するための名前のことです。Publisherは、メッセージを公開するときに、どのトピックに公開するかを指定します。そして、Subscriberは、どのトピックからメッセージを受け取るかを指定します。これにより、PublisherとSubscriberは、特定のトピックに対してメッセージを公開したり、受け取ったりすることができます。 ■ subscriber/app.py subscriber/app.pyは、DaprのPublish and Subscribeコンポーネントを利用してメッセージを受け取るためのアプリケーションコードです。 ソースコードの説明に入る前に、Subscriberがメッセージを受信するまでの流れを説明します。 まず、Subscriberのサイドカーは、/dapr/subscribeエンドポイントを通じて、Subscriberがどのトピックからメッセージを受け取るかをDaprに通知します。 Subscriberのサイドカーは、Redisからメッセージを受け取ると、Subscriberのエンドポイントである「/トピック名」(この例では/orders)に対して、メッセージを送信します。 では、上記を踏まえてソースコードの主要な部分を説明します。 from flask import Flask , request , jsonify import json app = Flask ( __name__ ) 最初にFlaskをインポートして、Flaskアプリケーションのインスタンスを作成しています。Flaskは、PythonでWebアプリケーションを作成するためのフレームワークです。 先ほど説明したように、Subscriberのサイドカーは、Subscriberの特定のHTTPエンドポイントに対してアクセスしてくるので、SubscriberはHTTPサーバーを立てる必要があります。Flaskは、そのHTTPサーバーを簡単に立てることができるため、ここではFlaskを使用しています。Flask以外のHTTPサーバーフレームワークを使用しても問題ありません。 @app . route ( "/dapr/subscribe" , methods = [ "GET" ] ) def subscribe ( ) : subscriptions = [ { "pubsubname" : "orderpubsub" , "topic" : "orders" , "route" : "orders" } ] return jsonify ( subscriptions ) このコードは、Subscriberのサイドカーが/dapr/subscribeエンドポイントにアクセスしたときに呼び出される関数を定義しています。この関数は、Subscriberがどのトピックからメッセージを受け取るかをDaprに通知するためのものです。 つまり、この関数は、Daprに対して、Subscriberが「orderpubsub」という名前のPublish and Subscribeコンポーネントの「orders」というトピックからメッセージを受け取ることを通知しています。そして、Daprは、Subscriberのサイドカーが「orders」というエンドポイントに対して、メッセージを送信するようになります。 @app . route ( "/orders" , methods = [ "POST" ] ) def orders_subscriber ( ) : event_orderid = request . json [ "data" ] [ "orderId" ] print ( "受信データ: " + json . dumps ( event_orderid ) , flush = True ) return json . dumps ( { "success" : True } ) , 200 , { "ContentType" : "application/json" } このコードは、Subscriber側のサイドカーから「/orders」エンドポイントにリクエストが送られたときに実行される関数を定義しています。 このエンドポイントは、/dapr/subscribe エンドポイントでDaprに登録した「orders」トピックに対応しており、そのトピックにメッセージが発行されると、Daprのサイドカーによってこの関数が呼び出されます。 つまり、この関数は「orders」トピックに発行されたメッセージを受信し、処理するためのエンドポイントとして動作します。 この関数の中では、リクエストのJSONボディからorderIdを抽出し、それをコンソールに出力しています。そして、最後に、HTTPレスポンスとして、成功を示すJSONオブジェクトを返しています。 app.run(port=6104) このコードは、Flaskアプリケーションをポート6104で起動しています。これにより、Subscriberはポート番号6104でHTTPリクエストを受け付けるようになります。Publisherがメッセージを公開すると、Daprのサイドカーはこのポートに対してリクエストを送るため、Subscriberはこのポートでリクエストを受け取る必要があります。 実行方法 では次に、Publish and Subscribeコンポーネントを利用するためのアプリケーションコードを実行してみましょう。 まずは必要なライブラリをインストールします。 $ pip install -r publisher/requirements.txt $ pip install -r subscriber/requirements.txt 次に、Dapr CLIを使用してSubscriber及びSubscriberのサイドカープロセスを起動します。 $ dapr run --app-id pubsub --app-port 6104 --dapr-http-port 3614 --components-path Infrastructure/components -- python subscriber/app.py 上記のコマンドの詳細を説明します。 &#8211;app-idオプションでアプリケーションのIDを指定します。ここでは「pubsub」というIDを指定しています。このIDは、Publish and Subscribeでは特に利用しませんが、Service Invocationなどの機能を利用する際に、このIDが重要になります。 &#8211;app-portオプションでSubscriberのアプリケーションのポート番号を指定します。ここでは6104を指定しています。これにより、Subscriberはlocalhost:6104で待ち受けるようになります。 &#8211;dapr-http-portオプションでSubscriberのDaprサイドカーのHTTPポートを指定します。ここでは3614を指定しています。これにより、Daprサイドカーはlocalhost:3614で待ち受けるようになります。 &#8211;components-pathオプションで、Daprのコンポーネント定義ファイルが配置されているディレクトリを指定します。ここではInfrastructure/componentsを指定しています。これにより、Daprサイドカーはこのディレクトリの中にあるコンポーネント定義ファイルを読み込むようになります。 &#8212; python subscriber/app.pyは、SubscriberのDaprサイドカーが起動した後に実行するコマンドを指定しています。ここでは、subscriber/app.pyを実行するように指定しています。つまり、Subscriberのアプリケーションを実行するように指定しています。 次に、Publisher及びPublisherのサイドカープロセスを起動します。 $ dapr run --app-id pubsub --dapr-http-port 3613 --components-path Infrastructure/components -- python publisher/app.py 上記のコマンドの詳細を説明します。 &#8211;app-idオプションでアプリケーションのIDを指定します。ここでは「pubsub」というIDを指定しています。このIDは、Publish and Subscribeでは特に利用しませんが、Service Invocationなどの機能を利用する際に、このIDが重要になります。 &#8211;dapr-http-portオプションでPublisherのDaprサイドカーのHTTPポートを指定します。ここでは3613を指定しています。これにより、Daprサイドカーはlocalhost:3613で待ち受けるようになります。 &#8211;components-pathオプションで、Daprのコンポーネント定義ファイルが配置されているディレクトリを指定します。ここではInfrastructure/componentsを指定しています。これにより、Daprサイドカーはこのディレクトリの中にあるコンポーネント定義ファイルを読み込むようになります。 &#8212; python publisher/app.pyは、PublisherのDaprサイドカーが起動した後に実行するコマンドを指定しています。ここでは、publisher/app.pyを実行するように指定しています。つまり、Publisherのアプリケーションを実行するように指定しています。 このコマンドを実行すると、Publisherがメッセージを公開し、Subscriberがそのメッセージを受信する様子を確認することができます。Subscriberのコンソールには、Publisherが公開したメッセージが以下のように表示されるはずです。 == APP == 受信データ: 1 == APP == 127.0 .0.1 - - [ 28 /Mar/2026 01:52:45 ] "POST /orders HTTP/1.1" 200 - == APP == 受信データ: 2 == APP == 127.0 .0.1 - - [ 28 /Mar/2026 01:52:45 ] "POST /orders HTTP/1.1" 200 - == APP == 受信データ: 3 == APP == 127.0 .0.1 - - [ 28 /Mar/2026 01:52:45 ] "POST /orders HTTP/1.1" 200 - == APP == 受信データ: 4 == APP == 127.0 .0.1 - - [ 28 /Mar/2026 01:52:45 ] "POST /orders HTTP/1.1" 200 - == APP == 受信データ: 5 == APP == 127.0 .0.1 - - [ 28 /Mar/2026 01:52:45 ] "POST /orders HTTP/1.1" 200 - == APP == 受信データ: 6 == APP == 127.0 .0.1 - - [ 28 /Mar/2026 01:52:45 ] "POST /orders HTTP/1.1" 200 - == APP == 受信データ: 7 == APP == 127.0 .0.1 - - [ 28 /Mar/2026 01:52:45 ] "POST /orders HTTP/1.1" 200 - == APP == 受信データ: 8 == APP == 127.0 .0.1 - - [ 28 /Mar/2026 01:52:45 ] "POST /orders HTTP/1.1" 200 - == APP == 受信データ: 9 == APP == 127.0 .0.1 - - [ 28 /Mar/2026 01:52:45 ] "POST /orders HTTP/1.1" 200 - Bindings Bindingsコンポーネントとは、アプリに入ってくるデータと、アプリから出ていくデータを処理するためのコンポーネントです。 システム構成 例えば、RabbitMQにデータが入ってきたときに、そのデータを処理して、処理した結果をPostgreSQLに保存したいとします。このような場合に、Bindingsコンポーネントが役立ちます。 前のコンポーネントと同じように、Bindingsコンポーネントも、アプリ側はRabbtiMQやPostgreSQLのことを知らなくても、DaprのAPIを通じて、RabbitMQからデータを受け取ったり、PostgreSQLにデータを保存したりすることができます。これも、Daprのメリットの一つである、インフラレイヤーの抽象化による柔軟性の高さです。 ① Publisher(キューにメッセージを登録する側)が、RabbitMQに対して、メッセージを登録します。 ② RabbitMQは、サイドカーにメッセージを登録します。 ③ サイドカーは、RabbitMQからメッセージを受け取ると、アプリが待ち受けている特定のHTTPエンドポイントに対して、メッセージを送信します。 ④ アプリは、そのHTTPエンドポイントでリクエストを受け取ると、処理を実行します。処理が完了したら、アプリは、DaprのAPIを通じて、サイドカーに対して、処理した結果を保存するリクエストを送ります。 ⑤ サイドカーは、コンポーネント定義ファイルを読み取り、PostgreSQLに対して、処理した結果を保存するリクエストを送ります。 サイドカーにデータが入っていくほうをInput Binding、サイドカーからデータが出ていくほうをOutput Bindingと呼びます。 Input Bindingは、ここで紹介したRabbitMQを始め、Azure Event HubsやAWS Kinesisなどのメッセージングサービス、HTTPやgRPCなどのプロトコル、ファイルシステムやFTPなどのストレージサービスなど、様々なものが用意されています。Output Bindingも、ここで紹介したPostgreSQLを始め、Azure Cosmos DBやAWS DynamoDBなどのデータベースサービス、HTTPやgRPCなどのプロトコル、ファイルシステムやFTPなどのストレージサービスなど、様々なものが用意されています。詳細は以下の公式ドキュメントを参照してください。 https://docs.dapr.io/reference/components-reference/supported-bindings/ ファイル構成 Bindingsコンポーネントを利用するためのファイル構成は以下の通りとなります。これらのファイルは先程紹介したGitHubリポジトリ( https://github.com/noriyukitakei/dapr-sample )の中の、Bindingsディレクトリの中に配置されています。 Bindings ├── app.py ├── Infrastructure │ ├── components │ │ ├── binding-mq.yaml │ │ └── binding-sqldb.yaml │ └── db │ ├── docker-compose.yml │ ├── Dockerfile │ └── temperatures.sql ├── publish_temperatures.py └── requirements.txt ソースコードの説明 ■ Infrastructure/components/binding-mq.yaml apiVersion : dapr.io/v1alpha1 # Daprのリソース定義のバージョン kind : Component # 設定ファイルの種別 metadata : name : mq # アプリから参照するコンポーネント名 spec : type : bindings.rabbitmq # コンポーネントの種別 metadata : - name : host # RabbitMQのホスト名とポート value : "amqp://guest:guest@localhost:5672" - name : queueName # RabbitMQのキュー名 value : "dapr-queue" - name : direction # データの流れを指定するためのメタデータ value : "input" 他のコンポーネントと同じところは説明を省略します。ここで新たに説明する必要があるのは、spec.typeがbindings.rabbitmqになっていることです。これにより、DaprはBindingsコンポーネントとしてRabbitMQを使用するようになります。 順番が前後しますが、metadata.nameが「mq」であることも重要です。これにより、アプリケーションは「mq」という名前のBindingsコンポーネントを参照して、メッセージを受信したり、メッセージを送信したりすることができます。 spec.metadataは、RabbitMQに接続するための情報を記載しています。RabbitMQのホスト名とポートを指定するためのhost、RabbitMQのキュー名を指定するためのqueueName、データの流れを指定するためのdirectionという3つのメタデータを定義しています。directionは、データの流れを指定するためのメタデータであり、inputを指定すると、サイドカーはRabbitMQからメッセージを受信するためのBindingsコンポーネントとして動作します。 ■ Infrastructure/components/binding-sqldb.yaml apiVersion : dapr.io/v1alpha1 # Daprのリソース定義のバージョン kind : Component # 設定ファイルの種別 metadata : name : sqldb # アプリから参照するコンポーネント名 spec : type : bindings.postgres # コンポーネントの種別 version : v1 metadata : - name : url # PostgreSQLの接続情報 value : "user=postgres password=docker host=localhost port=5432 dbname=temperatures" - name : direction # データの流れを指定するためのメタデータ value : "output" このファイルは、DaprのBindingsコンポーネントをPostgreSQLに接続するための設定ファイルです。このファイルには、DaprがPostgreSQLに接続するための情報が記載されています。 spec.typeがbindings.postgresになっていることにより、DaprはBindingsコンポーネントとしてPostgreSQLを使用するようになります。 metadata.nameが「sqldb」であることも重要です。これにより、アプリケーションは「sqldb」という名前のBindingsコンポーネントを参照して、メッセージを受信したり、メッセージを送信したりすることができます。 spec.metadataは、PostgreSQLに接続するための情報を記載しています。PostgreSQLの接続情報を指定するためのurl、データの流れを指定するためのdirectionという2つのメタデータを定義しています。directionは、データの流れを指定するためのメタデータであり、outputを指定すると、サイドカーはPostgreSQLにメッセージを送信するためのBindingsコンポーネントとして動作します。 ■ db/docker-compose.yml このファイルは、PostgreSQLをDockerコンテナで起動するためのdocker-composeファイルです。このファイルを使用して、PostgreSQLを簡単に起動することができます。 ■ db/Dockerfile このファイルは、PostgreSQLのDockerイメージを作成するためのDockerfileです。このファイルを使用して、PostgreSQLのDockerイメージを作成することができます。 FROM postgres COPY temperatures.sql /docker-entrypoint-initdb.d/ このDockerfileは、公式のPostgreSQLイメージをベースにしています。そして、temperatures.sqlというSQLファイルを、PostgreSQLの初期化スクリプトが配置されるディレクトリである/docker-entrypoint-initdb.d/にコピーしています。これにより、PostgreSQLが起動するときに、このSQLファイルが実行されて、temperaturesテーブルが作成されます。 ■ db/temperatures.sql このファイルは、PostgreSQLの初期化スクリプトです。このファイルには、PostgreSQLが起動するときに実行されるSQL文が記載されています。ここでは、temperaturesテーブルを作成するSQL文が記載されています \c temperatures ; create table temperatures ( sensorid text , timestamp timestamptz , temperature float ) ; select * from temperatures ; このSQL文は、まずtemperaturesデータベースに接続するための\c temperatures;というコマンドを実行しています。そして、temperaturesテーブルを作成するためのcreate table文を実行しています。最後に、temperaturesテーブルの中身を確認するためのselect文を実行しています。 ■ publish_temperatures.py このファイルは、RabbitMQに対して、温度センサーのデータを送信するためのアプリケーションコードです。 def publish ( ) - &gt; None : data = { "sensorid" : "sensor-1" , "timestamp" : "2026-03-07T10:00:00Z" , "temperature" : 22.5 , } まず、関数publishを定義しています。この関数は、RabbitMQに対して、温度センサーのデータを送信するためのものです。 このコードは、温度センサーのデータを表すJSONオブジェクトを作成しています。このJSONオブジェクトには、sensorid、timestamp、temperatureという3つのフィールドが含まれています。sensoridは、センサーのIDを表す文字列です。timestampは、センサーのデータが記録された日時を表す文字列です。temperatureは、センサーのデータである温度を表す数値です。 params = pika . URLParameters ( "amqp://guest:guest@localhost:5672" ) connection = pika . BlockingConnection ( params ) channel = connection . channel ( ) このコードは、pikaライブラリを使用して、RabbitMQに接続しています。pika.URLParametersを使用して、RabbitMQの接続情報を指定しています。そして、pika.BlockingConnectionを使用して、RabbitMQに接続しています。最後に、connection.channel()を使用して、RabbitMQのチャネルを作成しています。 channel . queue_declare ( queue = "dapr-queue" ) このコードは、RabbitMQのチャネルを使用して、dapr-queueという名前のキューを宣言しています。これにより、dapr-queueという名前のキューがRabbitMQに作成されます。 body = json . dumps ( data ) channel . basic_publish ( exchange = "" , routing_key = "dapr-queue" , body = body , properties = pika . BasicProperties ( delivery_mode = 2 ) , ) このコードは、RabbitMQのチャネルを使用して、dapr-queueという名前のキューに対して、温度センサーのデータを送信しています。exchangeには空文字列を指定することで、デフォルトのエクスチェンジを使用しています。routing_keyには、dapr-queueという名前のキューを指定しています。bodyには、温度センサーのデータをJSON形式で表した文字列を指定しています。そして、propertiesには、メッセージのプロパティを指定しています。ここでは、delivery_mode=2を指定することで、メッセージが永続化されるようにしています。 connection . close ( ) このコードは、RabbitMQへの接続を閉じています。 ■ app.py このファイルは、DaprのBindingsコンポーネントを利用して、RabbitMQからデータを受け取って、PostgreSQLにデータを保存するためのアプリケーションコードです。 app = Flask ( __name__ ) Triggered by Dapr input binding @app . route ( "/mq" , methods = [ "POST" ] ) def process_batch ( ) : このコードは、Flaskを使用して、HTTPサーバーを立てています。そして、/mqというエンドポイントに対してPOSTリクエストが送られたときに呼び出される関数process_batchを定義しています。このエンドポイントの/mqというパスは、先程のコンポーネント定義で指定した「mq」という名前のBindingsコンポーネントを参照するためのパスです。これにより、Daprは「mq」という名前のBindingsコンポーネントを参照して、RabbitMQからメッセージを受信すると、このエンドポイントに対してリクエストを送るようになります。 data = request . get_json ( silent = True ) sql_output ( data ) print ( "Finished processing batch" , flush = True ) return json . dumps ( { "success" : True } ) , 200 , { "ContentType" : "application/json" } 関数process_batchの中身です。まず、リクエストのJSONボディを取得しています。そして、sql_output関数を呼び出して、RabbitMQから受け取ったデータをPostgreSQLに保存しています。最後に、HTTPレスポンスとして、成功を示すJSONオブジェクトを返しています。 def sql_output ( reading ) : # expected keys: sensorid, timestamp, temperature sensorid = reading . get ( "sensorid" ) timestamp = reading . get ( "timestamp" ) temperature = reading . get ( "temperature" ) 先程の関数process_batchの中で呼び出されているsql_output関数です。この関数は、RabbitMQから受け取ったデータをPostgreSQLに保存するためのものです。まず、関数sql_outputを定義しています。この関数は、RabbitMQから受け取ったデータを表すJSONオブジェクトを引数として受け取ります。そして、そのJSONオブジェクトから、sensorid、timestamp、temperatureという3つのフィールドを取得しています。 sqlCmd = ( "insert into temperatures (sensorid, timestamp, temperature) values " + "('%s', '%s', %s)" % ( sensorid , timestamp , temperature ) ) このコードは、PostgreSQLに対して実行するSQL文を作成しています。ここでは、temperaturesテーブルに対して、sensorid、timestamp、temperatureの値を挿入するためのinsert文を作成しています。 payload = { "operation" : "exec" , "metadata" : { "sql" : sqlCmd } } このコードは、DaprのAPIに送るリクエストを定義しています。リクエストの形式は、利用するコンポーネントによって異なるのですが、PostgreSQLは以下の形式になります。 { "operation" : "exec" , "metadata" : { "sql" : "INSERT INTO foo (id, c1, ts) VALUES ($1, $2, $3)" , "params" : "[1, \"demo\", \"2020-09-24T11:45:05Z07:00\"]" } } このリクエストは、operationにexecを指定することで、SQL文を実行することを示しています。そして、metadataの中に、sqlというフィールドを定義して、その中に実行するSQL文を指定しています。ここでは、先程作成したsqlCmdという変数に格納されているSQL文を指定しています。 paramsというフィールドも定義されているのですが、今回は使用しません。paramsは、SQL文の中でプレースホルダを使用する場合に、そのプレースホルダに対応する値を指定するためのフィールドです。今回は、SQL文の中でプレースホルダを使用していないため、paramsは必要ありません。 よって今回送付するリクエストは以下のとおりとなります。 { "operation" : "exec" , "metadata" : { "sql" : "INSERT INTO temperatures (sensorid, timestamp, temperature) VALUES ('sensor-1', '2026-03-07T10:00:00Z', 22.5)" } } resp = requests . post ( "http://localhost:3617/v1.0/bindings/sqldb" , json = payload ) return resp requestsライブラリを使用して、DaprのAPIに対してリクエストを送っています。リクエストのURLには、先程のコンポーネント定義で指定した「sqldb」が含まれています。これにより、Daprは「sqldb」という名前のBindingsコンポーネントを参照して、PostgreSQLに対してSQL文を実行するようになります。リクエストのURLの構成は以下の通りです。リクエストボディには、先程作成したpayloadという変数に格納されているJSONオブジェクトを指定しています。 http://localhost:3617: Daprサイドカーのエンドポイント /v1.0: Dapr APIのバージョン /bindings: Bindings APIを呼び出すことを示すパス /sqldb: 先程のコンポーネント定義で指定した「sqldb」という名前のBindingsコンポーネントを参照するためのパス app.run(port=6107) Flaskアプリケーションをポート6107で起動しています。これにより、アプリはlocalhost:6107でHTTPリクエストを受け付けるようになります。RabbitMQからメッセージが送られてくると、Daprのサイドカーはこのポートに対してリクエストを送るため、アプリはこのポートでリクエストを受け取る必要があります。 実行方法 まずは必要なライブラリをインストールします。 $ pip install -r requirements.txt 次に、PostgreSQLとRabbbitMQのコンテナを起動します。 $ cd Infrastructure/db $ docker-compose up -d Dapr CLIを使用してアプリケーションコードとサイドカープロセスを起動します。 $ dapr run --app-id bindings --app-port 6107 --dapr-http-port 3617 --components-path Infrastructure/components -- python app.py 上記のコマンドの詳細を説明します。 &#8211;app-idオプションでアプリケーションのIDを指定します。ここでは「bindings」というIDを指定しています。このIDは、Bindingsコンポーネントでは特に利用しませんが、Service Invocationなどの機能を利用する際に、このIDが重要になります。 &#8211;app-portオプションでアプリケーションのポート番号を指定します。ここでは6107を指定しています。これにより、アプリはlocalhost:6107で待ち受けるようになります。 &#8211;dapr-http-portオプションでDaprサイドカーのHTTPポートを指定します。ここでは3617を指定しています。これにより、Daprサイドカーはlocalhost:3617でHTTPリクエストを受け付けるようになります。 &#8211;components-pathオプションでDaprコンポーネントの定義ファイルが格納されているディレクトリを指定します。ここではInfrastructure/componentsを指定しています。これにより、Daprサイドカーはこのディレクトリの中にあるコンポーネント定義ファイルを読み込むようになります。 &#8212; python app.pyは、Daprサイドカーが起動した後に実行するコマンドを指定しています。ここでは、app.pyを実行するように指定しています。つまり、アプリケーションコードを実行するように指定しています。 このコマンドを実行した状態で、publish_temperatures.pyを実行してみましょう。 $ python publish_temperatures.py このコードを実行すると、RabbitMQに温度センサーのデータが送信されます。そして、Daprのサイドカーがそのデータを受け取って、アプリケーションコードに送ります。アプリケーションコードは、そのデータをPostgreSQLに保存します。 本当にデータがPostgreSQLに保存されたかを確認してみましょう。以下のコマンドを実行して、PostgreSQLのコンテナに接続します。 $ docker exec -i postgres psql --username postgres --dbname temperatures -c "select * from temperatures;" sensorid | timestamp | temperature ----------+------------------------+------------- sensor-1 | 2026 -03-07 10 :00:00+00 | 22.5 ( 1 row ) すると、temperaturesテーブルの中に、先程publish_temperatures.pyを実行したときに送信したデータが保存されていることが確認できます。 Service Invocation Service Invocationは、Daprの機能の一つであり、アプリケーションが他のアプリケーションに対してリクエストを送るときに、便利な機能がたくさんあります。 アプリケーションは、他のアプリケーションのこと(IPアドレスなど)を知らなくても、DaprのAPIを通じて、他のアプリケーションに対してリクエストを送ることができます。 アプリケーション同士の通信が失敗しても、サイドカーが自動的にリトライしてくれるため、通信の信頼性が高まります。 サイドカーが、アプリケーション同士の通信をmTLSによって暗号化してくれるため、通信のセキュリティが高まります。 サイドカーが、アプリケーション同士の通信をモニターして、ログやメトリクスなどを収集してくれるため、通信の可観測性が高まります。 つまり、サイドカーがいろんなことをしてくれるので、アプリケーション側では何も考えずに、DaprのAPIを通じて、他のアプリケーションに対してリクエストを送ることができます。これが、Service Invocationの大きなメリットの一つです。 様々な利便性があるService Invocationですが、1の機能に焦点をあてて、具体的な事例(センサーデータを送るアプリケーションから、センサーのデータを受け取って処理するアプリケーションに対してリクエストを送る)を通じて、Service Invocationの使い方を説明していきます。 システム構成 ということで、センサーデータを送るアプリケーションから、センサーのデータを受け取って処理するアプリケーションに対してリクエストを送るシステムを例に挙げて説明します。構成図は以下のとおりです。 ① アプリケーションIDが「telemetry-sender」のアプリケーションは、DaprのAPIを使って、アプリケーションIDが「telemetry-collector」のアプリケーションにリクエストを送ります。 このときのURLは   http://localhost:3615/v1.0/invoke/telemetry-collector/method/telemetry   です。 ここで「3615」は telemetry-sender 側のDaprサイドカーのHTTPポートを表しています。 また、このURLのパスは「telemetry-collector」というアプリケーションの「telemetry」というエンドポイントを呼び出すことを意味します。 ② サイドカーはこのリクエストを受け取ると、mDNS(multicast DNS)を使って、アプリケーションID「telemetry-collector」がどこで動いているか(IPアドレス)を調べます。 ③ すると、telemetry-collector 側のサイドカー内のmDNSリゾルバーが応答し、「自分が telemetry-collector である」と名乗って、自身のIPアドレスなどの情報を返します。 ④ telemetry-sender 側のサイドカーは、その情報をもとに telemetry-collector のサイドカーへリクエストを送ります。 ⑤ telemetry-collector 側のサイドカーは、受け取ったリクエストを実際のアプリケーション(telemetry-collector)に転送します。 このように、アプリケーションID「telemetry-sender」のアプリケーションは、アプリケーションID「telemetry-collector」のアプリケーションのIPアドレスやポート番号を知らなくても、DaprのAPIを通じて、アプリケーションID「telemetry-collector」のアプリケーションに対してリクエストを送ることができます。これが、Service Invocationの大きなメリットの一つです。 ちなみに本記事で紹介している構成(Self-hosted)では、mDNSを使っていますが、AWSのECSやAzureのAKSなどのコンテナオーケストレーションサービスを使用している場合は、mDNSの代わりに、高度なDNSリゾルバーが使用されます。 ファイル構成 Service Invocationコンポーネントを利用するためのファイル構成は以下の通りとなります。これらのファイルは先程紹介したGitHubリポジトリ( https://github.com/noriyukitakei/dapr-sample )の中の、ServiceInvocationディレクトリの中に配置されています。 ServiceInvocation ├── telemetry-collector │ ├── app.py │ └── requirements.txt └── telemetry-sender ├── app.py └── requirements.txt ソースコードの説明 ■ telemetry-collector/app.py app = Flask ( __name__ ) このコードは、Flaskを使用して、HTTPサーバーを立てています。 @app . route ( "/telemetry" , methods = [ "POST" ] ) def telemetry ( ) : data = request . json print ( "Collector received:" , data , flush = True ) return ( json . dumps ( { "ok" : True } ) , 200 , { "ContentType" : "application/json" } , ) このコードは、/telemetryというエンドポイントに対してPOSTリクエストが送られたときに呼び出される関数を定義しています。このエンドポイントは、先程の構成図の中で、アプリケーションID「telemetry-sender」のアプリケーションが呼び出すエンドポイントになります。 アプリケーションID「telemetry-sender」が呼び出すエンドポイントは、先ほど説明した通り、 http://localhost:3615/v1.0/invoke/telemetry-collector/method/telemetry   になります。このURLのパスの最後の部分である「telemetry」が、telemetry-collector 側のアプリケーションのエンドポイントになります。なので、telemetry-collector 側のアプリケーションは、このエンドポイントを定義する必要があります。 telemetry関数の中では、リクエストのJSONボディを取得して、コンソールに出力しています。そして、HTTPレスポンスとして、成功を示すJSONオブジェクトを返すという非常にシンプルな処理をしています。 app . run ( port = 6106 ) Flaskアプリケーションをポート6106で起動しています。これにより、アプリはlocalhost:6106でHTTPリクエストを受け付けるようになります。telemetry-senderからリクエストが送られてくると、Daprのサイドカーはこのポートに対してリクエストを送るため、アプリはこのポートでリクエストを受け取る必要があります。 ■ telemetry-sender/app.py payload = { "sensorId" : "sensor-1" , "temperatureC" : 23 } このコードは、telemetry-collectorに送るデータを表すJSONオブジェクトを作成しています。このJSONオブジェクトには、sensorIdとtemperatureCという2つのフィールドが含まれています。センサーのIDを表すsensorIdと、センサーのデータである温度を表すtemperatureCです。 response = requests . post ( url = "http://127.0.0.1:3615/v1.0/invoke/telemetry-collector/method/telemetry" , data = json . dumps ( payload ) , headers = { "content-type" : "application/json" , } , ) このコードは、requestsライブラリを使用して、DaprのAPIに対してリクエストを送っています。リクエストのURLには、先程の構成図の中で、アプリケーションID「telemetry-sender」のアプリケーションが呼び出すURLである   http://localhost:3615/v1.0/invoke/telemetry-collector/method/telemetry   が指定されています。リクエストボディには、先程作成したpayloadという変数に格納されているJSONオブジェクトを指定しています。 実行方法 まずは必要なライブラリをインストールします。 $ pip install -r telemetry-sender/requirements.txt $ pip install -r telemetry-collector/requirements.txt 次に、telemetry-collectorのアプリケーションコードとサイドカープロセスを起動します。 $ dapr run --app-id telemetry-collector --app-port 6106 --dapr-http-port 3616 -- python telemetry-collector/app.py 上記のコマンドの詳細を説明します。 &#8211;app-idオプションでアプリケーションのIDを指定します。ここでは「telemetry-collector」というIDを指定しています。このIDは、Service Invocationでは非常に重要になります。なぜなら、アプリケーションID「telemetry-sender」のアプリケーションが、アプリケーションID「telemetry-collector」のアプリケーションにリクエストを送るときに、このIDを使用して、どのアプリケーションにリクエストを送るかを指定するからです。このアプリケーションIDを元にして、Daprのサイドカーは、mDNSを使って、アプリケーションID「telemetry-collector」がどこで動いているか(IPアドレス)を調べます。 &#8211;app-portオプションでアプリケーションのポート番号を指定します。ここでは6106を指定しています。これにより、アプリはlocalhost:6106で待ち受けるようになります。 &#8211;dapr-http-portオプションでDaprサイドカーのHTTPポートを指定します。ここでは3616を指定しています。これにより、Daprサイドカーはlocalhost:3616でHTTPリクエストを受け付けるようになります。 &#8212; python telemetry-collector/app.pyは、Daprサイドカーが起動した後に実行するコマンドを指定しています。ここでは、app.pyを実行するように指定しています。つまり、アプリケーションコードを実行するように指定しています。 次に、telemetry-senderのアプリケーションコードとサイドカープロセスを起動します。 $ dapr run --app-id telemetry-sender --dapr-http-port 3615 -- python telemetry-sender/app.py 上記のコマンドの詳細を説明します。 &#8211;app-idオプションでアプリケーションのIDを指定します。ここでは「telemetry-sender」というIDを指定しています。 &#8211;dapr-http-portオプションでDaprサイドカーのHTTPポートを指定します。ここでは3615を指定しています。これにより、Daprサイドカーはlocalhost:3615でHTTPリクエストを受け付けるようになります。 &#8212; python telemetry-sender/app.pyは、Daprサイドカーが起動した後に実行するコマンドを指定しています。ここでは、app.pyを実行するように指定しています。つまり、アプリケーションコードを実行するように指定しています。 このコマンドを実行すると、telemetry-sender側のコンソールに以下のような出力がされます。 == APP == Sender sent: {'sensorId': 'sensor-1', 'temperatureC': 23} == APP == Response status: 200 そして、telemetry-collector側のコンソールには以下のような出力がされます。 == APP == Collector received: {'sensorId': 'sensor-1', 'temperatureC': 23} == APP == 127.0.0.1 - - [03/Apr/2026 13:53:42] "POST /telemetry HTTP/1.1" 200 - 上記のようなものが出力されていれば、telemetry-senderのアプリケーションが、telemetry-collectorのアプリケーションに対してリクエストを送ることができていることが確認できます。 Secrets Management Secrets Managementは、Daprの機能の一つであり、アプリケーションがシークレットを安全に管理するための機能です。クラウドのサービスで、シークレットを扱うサービスはたくさんあります。AWSのSecrets ManagerやAzureのKey Vaultなどが有名です。これらのサービスを利用することで、シークレットを安全に管理することができます。 ただし、これらのサービスを使うためには、それぞれの専用のAPIを呼び出したり、SDKを使用したりする必要があります。これらのサービスを利用するためのコードを書くのは、面倒なことが多いです。DaprのSecrets Managementを利用することで、これらのサービスを利用するためのコードを書く必要がなくなります。DaprのAPIを通じて、シークレットを取得することができるようになります。 アプリからはDaprのサイドカーに、「シークレットを取得したい」というリクエストを送ります。すると、サイドカーは、あらかじめ設定されているシークレットストアに対して、シークレットを取得するためのリクエストを送ります。そして、シークレットストアからシークレットが返ってくると、サイドカーは、そのシークレットをアプリに返します。 システム構成 では、シークレットを取得するアプリケーションの例を通じて、Secrets Managementの使い方を説明していきます。構成図は以下のとおりです。 今回ご紹介する例では、AWS Secrets ManagerやAzure Key Vaultではなく、ローカルに配置したJSONファイルをシークレットストアとして使用します。Daprは、ローカルに配置したJSONファイルをシークレットストアとして使用することができます。これにより、AWS Secrets ManagerやAzure Key Vaultなどのクラウドのサービスを利用しなくても、Secrets Managementの機能を試すことができます。 ローカルに配置したJSONファイルをシークレットストアとして使用する方式は、Daprの公式ドキュメントにおいては本番環境では推奨されていません。今回の説明のために、ローカルに配置したJSONファイルをシークレットストアとして使用する方式を紹介していますが、実際のプロジェクトでSecrets Managementの機能を利用する際には、AWS Secrets ManagerやAzure Key Vaultなどのクラウドのサービスを利用することをおすすめします。 ということで先ほどの構成図に基づいて、シークレットを取得するアプリケーションの例を通じて、Secrets Managementの使い方を説明していきます。 ① アプリケーションは、DaprのAPIを使って、シークレットを取得するためのリクエストをサイドカーに送ります。 このときのURLは   http://127.0.0.1:3618/v1.0/secrets/local-secretstore/my-secret   です。このURLの構成を説明します。 http://127.0.0.1:3618 : DaprサイドカーのHTTPポート /v1.0/secrets: シークレット管理のAPIエンドポイント /local-secretstore: 使用するシークレットストアの名前( コンポーネント定義で指定したmetadata.name ) /my-secret: 取得したいシークレットの名前 ② サイドカーはこのリクエストを受け取ると、あらかじめ設定されているシークレットストアに対して、シークレットを取得するためのリクエストを送ります。 ファイル構成 Secrets Managementコンポーネントを利用するためのファイル構成は以下の通りとなります。これらのファイルは先程紹介したGitHubリポジトリ( https://github.com/noriyukitakei/dapr-sample )の中の、SecretsManagementディレクトリの中に配置されています。 SecretsManagement ├── app.py ├── Infrastructure │ └── components │ ├── local-secretstore.yaml │ └── secrets.json ├── README.md └── requirements.txt ソースコードの説明 では、Secrets Managementの機能を利用するためのソースコードを説明していきます。 ■ Infrastructure/components/local-secretstore.yaml apiVersion : dapr.io/v1alpha1 # DaprのAPIバージョン kind : Component # コンポーネントの種類 metadata : name : local - secretstore # コンポーネントの名前 spec : type : secretstores.local.file # コンポーネントの種類を指定 version : v1 metadata : - name : secretsFile # シークレットストアとして使用するJSONファイルのパスを指定するためのメタデータ value : "./Infrastructure/components/secrets.json" # シークレットストアとして使用するJSONファイルのパス このファイルは、DaprのSecrets ManagementコンポーネントをローカルのJSONファイルをシークレットストアとして使用するように設定するためのコンポーネント定義ファイルです。 metadata.nameは、アプリケーションがこのコンポーネントを参照するための名前になります。先ほどご紹介したシステム構成の中で、アプリケーションが送るリクエストのURLは   http://127.0.0.1:3618/v1.0/secrets/local-secretstore/my-secret   でした。このURLの中の「local-secretstore」という部分は、metadata.nameで指定した名前になります。つまり、metadata.nameが「local-secretstore」であることによって、アプリケーションは「local-secretstore」という名前のコンポーネント定義ファイルを参照して、シークレットストアとして使用するJSONファイルのパスを知ることができるようになります。 spec.typeは、コンポーネントの種類を指定するためのフィールドです。ここでは、Daprが提供しているローカルのJSONファイルをシークレットストアとして使用するためのコンポーネントであるsecretstores.local.fileを指定しています。Azure Key Vaultの場合はsecretstores.azure.keyvault、AWS Secrets Managerの場合はsecretstores.aws.secretsmanagerを指定します。 metadataの中には、spec.typeで指定したコンポーネントの種類に応じたメタデータを定義します。ローカルのJSONファイルをシークレットストアとして使用するためのコンポーネントであるsecretstores.local.fileの場合は、secretsFileという名前のメタデータを定義して、そのvalueにシークレットストアとして使用するJSONファイルのパスを指定します。 ■ Infrastructure/components/secrets.json { "my-secret" : "hello-from-file" } このファイルは、ローカルに配置したJSONファイルをシークレットストアとして使用するためのJSONファイルです。このファイルには、シークレットの名前と値のペアが定義されています。ここでは、my-secretという名前のシークレットに対して、hello-from-fileという値が定義されています。 ■ app.py URL = "http://127.0.0.1:3618/v1.0/secrets/local-secretstore/my-secret" print ( requests . get ( URL ) . text ) このコードは、DaprのAPIを使って、シークレットを取得するためのリクエストをサイドカーに送っています。リクエストのURLには、先程の構成図の中で、アプリケーションが送るリクエストのURLである   http://127.0.0.1:3618/v1.0/secrets/local-secretstore/my-secret   が指定されています。リクエストを送ると、サイドカーは、あらかじめ設定されているシークレットストアに対して、シークレットを取得するためのリクエストを送ります。そして、シークレットストアからシークレットが返ってくると、サイドカーは、そのシークレットをアプリに返します。最後に、取得したシークレットをコンソールに出力しています。 実行方法 まずは必要なライブラリをインストールします。 $ pip install -r requirements.txt 次に、Dapr CLIを使用してアプリケーションコードとサイドカープロセスを起動します。 $ dapr run --app-id secretsmanagement --dapr-http-port 3618 --components-path Infrastructure/components -- python app.py .. .略 .. . == APP == { "my-secret" : "hello-from-file" } 上記のコマンドの詳細を説明します。 &#8211;app-idオプションでアプリケーションのIDを指定します。ここでは「secretsmanagement」というIDを指定しています。 &#8211;dapr-http-portオプションでDaprサイドカーのHTTPポートを指定します。ここでは3618を指定しています。これにより、Daprサイドカーはlocalhost:3618でHTTPリクエストを受け付けるようになります。 &#8211;components-pathオプションでDaprコンポーネントの定義ファイルが格納されているディレクトリを指定します。ここではInfrastructure/componentsを指定しています。これにより、Daprサイドカーはこのディレクトリの中にあるコンポーネント定義ファイルを読み込むようになります。 &#8212; python app.pyは、Daprサイドカーが起動した後に実行するコマンドを指定しています。ここでは、app.pyを実行するように指定しています。つまり、アプリケーションコードを実行するように指定しています。 このコマンドを実行すると、コンソールに{&#8220;my-secret&#8221;:&#8221;hello-from-file&#8221;}と出力されます。これは、DaprのAPIを通じて、シークレットを取得するためのリクエストをサイドカーに送った結果、サイドカーがあらかじめ設定されているシークレットストアに対して、シークレットを取得するためのリクエストを送って、そのシークレットをアプリに返した結果になります。つまり、DaprのSecrets Managementの機能が正常に動作していることが確認できます。 デモアプリ ひと通り、Daprの基本的な機能であるPub/Sub、Bindings、Service Invocation、Secrets Managementについて説明してきました。これらの機能を組み合わせることで、ちょっぴり本格的なマイクロサービスを作ってみましょう。 このアプリのソースコードは以下のリポジトリで公開しております。 https://github.com/noriyukitakei/dapr-demo 機能概要 このデモアプリは、センサーから送られてくるデータをリアルタイムに処理し、可視化やアラート通知を行うシステムです。 まず、センサーのシミュレーターが温度データを生成します。このデータは一度メッセージング基盤に送られ、そこからシステム全体に配信されます。 データを受け取ったゲートウェイの役割を持つサービスは、その内容を保存するとともに、異常値かどうかを判断します。 もし異常な値であれば、別のサービスに通知され、メールなどでアラートが送信されます。 一方で、通常のデータは蓄積され、ダッシュボードサービスから参照できるようになります。ユーザーはブラウザなどからセンサーの状態を確認でき、現在の状況をリアルタイムに把握することができます。 つまりこのシステムは、 データの収集 データの保存 異常検知 通知 可視化 といった一連の処理を、複数のサービスに分けて実現しています。 システム構成 デモアプリのシステム構成は以下のとおりです。 このシステムは3つのマイクロサービスで構成されています。デバイスからセンサーを受信するSensor Gateway Service、異常値を検知してアラートを送るAlert Service、センサーの状態を可視化するDashboard Serviceです。 このデモアプリでは、センサーのシミュレーターは、Sensor Gateway Serviceに対してセンサーデータを送ります。Sensor Gateway Serviceは、そのデータを保存するとともに、異常値かどうかを判断します。もし異常な値であれば、Alert Serviceに通知され、メールなどでアラートが送信されます。一方で、通常のデータは蓄積され、Dashboard Serviceから参照できるようになります。 では、システム構成図の中の①~⑰の流れに沿って、システム全体の動きを説明していきます。 ① センサーのシミュレーターであるSensor Simulatorが、センサーデータを生成する。アラートが上がるようなある一定温度以上のデータを一定確率で生成して、MQTTに送信する。 ② MQTTに送られたデータをSensor Gateway Serviceが受信する。Sensor Gateway Serviceは、DaprのInput Bindings機能を使って、MQTTからデータを受け取るように設定されている。 ③ Sensor Gateway Serviceのサイドカーは、MQTTからデータを受け取ると、そのデータをSensor Gateway Serviceに送る。受け取るためのエンドポイントはInput Bindingsのコンポーネント定義で指定されている。 ④ Sensor Gateway Serviceのサイドカーは、Secrets Managementの機能を使って、機密情報を格納しているデータストア(今回はローカルのJSONファイル)から、アラートのしきい値などを取得する。 ⑤ Sensor Gateway Serviceは、④で受け取ったしきい値を元に、受け取ったセンサーデータが異常値かどうかを判断する。 ⑥ Sensor Gateway Serviceは、受け取ったセンサーデータをRedisに保存する。これはState Managementの機能を使って実現している。 ⑦ Sensor Gateway Serviceは、受け取ったセンサーデータが正常値であればtelemetryトピックに、異常値であればalertトピックに送信する。これはPub/Subの機能を使って実現している。 ⑧ RabbitMQは、alertトピックに送られた異常値のデータをAlert Serviceに配信する。Alert Serviceは、DaprのPub/Sub機能を使って、alertトピックからデータを受け取るように設定されている。 ⑨ Alert Serviceのサイドカーは、alertトピックからデータを受け取ると、そのデータをAlert Serviceに送る。/alertsというエンドポイントに対してPOSTリクエストを送るように設定されている。このエンドポイントは、Alert Serviceのアプリケーションコードの中で定義されている。 ⑩ Alert Serviceは、受け取ったデータを元に、メールなどでアラートを送る。Output Bindingsの機能を使って、メールを送るように設定されている。 ⑪ Alert Serviceのサイドカーは、Output Bindingsの機能を使って、指定されたSMTPサーバーに対してメールを送信する。このメールサーバーは、maildev( https://github.com/maildev/maildev )というローカルで動かすことができるメールサーバーを使用している。 ⑫ 管理者は、DashBoard Serviceが提供しているエンドポイントに対して、センサーの状態を確認するためのリクエストを送る。 ⑬ Dashboard Serviceは、Service Invocationの機能を使って、Sensor Gateway Serviceに対してリクエストを送る。リクエストの内容は、センサーの状態を確認するためのものである。リクエストのURLは、センサーのIDがsensor-001とした場合、   http://localhost:3610/v1.0/invoke/sensorgateway/method/state/sensor-001   である。このURLのパスのsensorgatewayという部分は、Sensor Gateway ServiceのアプリケーションIDである。つまり、Dashboard Serviceは、Service Invocationの機能を使って、Sensor Gateway Serviceに対してリクエストを送るときに、Sensor Gateway ServiceのアプリケーションIDを指定している。state/sensor-001という部分は、Sensor Gateway Serviceのアプリケーションコードの中で定義されているエンドポイントになる。つまり、Dashboard Serviceは、Service Invocationの機能を使って、Sensor Gateway Serviceに対してリクエストを送るときに、Sensor Gateway Serviceのアプリケーションコードの中で定義されているエンドポイントを指定している。 ⑭ DashBoard Serviceのサイドカーは、mDNSを使って、Sensor Gateway Serviceのサイドカーがどこで動いているか(IPアドレス)を調べて、Sensor Gateway Serviceのサイドカーにリクエストを送る。 ⑮ Sensor Gateway Serviceのサイドカーは、受け取ったリクエストをSensor Gateway Serviceの/state/{sensor_id}というエンドポイントに転送する。 ⑯ Sensor Gateway Serviceは、受け取ったリクエストを元に、Sate Gateway ServiceのState Managementの機能を使って、Redisに保存されているセンサーの状態を取得する。 ⑰ Sensor Gateway Serviceのサイドカーは、Redisにアクセスしてセンサーの状態を取得した後、その状態をDashboard Serviceに返す。 ファイルの構成 デモアプリのファイル構成は以下のとおりです。 . ├── AlertService │   ├── alertservice │   │   └── app.py │   └── requirements.txt ├── DashboardService │   ├── dashboard │   │   └── app.py │   └── requirements.txt ├── Infrastructure │   ├── components │   │   ├── mqtt-binding.yaml │   │   ├── notify-binding.yaml │   │   ├── pubsub.yaml │   │   ├── secretstore.yaml │   │   └── statestore.yaml │   ├── docker-compose.yml │   ├── mosquitto.conf │   └── secrets.json ├── SensorGatewayService │   ├── requirements.txt │   └── sensorgateway │   ├── app.py │   └── settings.py ├── Simulation │   ├── requirements.txt │   └── simulate.py └── common    ├── pyproject.toml    └── smartfarm_common    └── models.py それぞれのファイルの内容をサービスごとに説明します。 Sensor Gateway Service ■ sensorgateway/app.py センサーのデータを受信して処理するサービスです。センサーデータを受信して、異常値かどうかを判断し、データを保存したり、異常値であればアラートを送ったりします。 Input Bindingsを使ってMQTTからデータ受信 State Managementを使ってデータ保存(Redis) Pub/Subでイベント発行(RabbitMQに正常データ、異常データをそれぞれ別のトピックに送る) DashBoard Serviceからのリクエストに応じて、センサーの状態を返すエンドポイントを提供 ■ sensorgateway/settings.py アラートのしきい値など各種設定を管理するファイルです。DaprのSecrets Management機能を使って、ローカルのJSONファイルから設定値を取得するようになっています。 Alert Service ■ alertservice/app.py アラート通知を担当するサービスです。異常なセンサーデータを検知した際に、通知処理を行います。 メッセージキュー(RabbitMQ)から異常データ(alertトピックに送られたデータ)を受信 異常時にOutput Bindingsの機能を使って、メールで通知 Dashboard Service ■ dashboard/app.py ユーザー向けにデータを表示するサービスです。以下の機能を提供します。 /sensors/{sensor_id}というAPIエンドポイントを提供して、センサーの状態を返却 Infrastructure ■ components/mqtt-binding.yaml MQTTからデータを受信するためのInput Bindingsのコンポーネント定義ファイルです。 ■ components/notify-binding.yaml メールを送るためのOutput Bindingsのコンポーネント定義ファイルです。maildevというローカルで動かすことができるメールサーバーに対して、SMTPプロトコルを使ってメールを送るように設定されています。 ■ components/pubsub.yaml RabbitMQをPub/Subのコンポーネントとして使用するためのコンポーネント定義ファイルです。 ■ components/secretstore.yaml ローカルのJSONファイルをSecrets Managementのシークレットストアとして使用するためのコンポーネント定義ファイルです。 ■ components/statestore.yaml RedisをState Managementのステートストアとして使用するためのコンポーネント定義ファイルです。 ■ docker-compose.yml RabbitMQ、mosquitto(MQTTブローカー)、maildev(SMTPサーバー)をDockerコンテナで起動するためのdocker-composeファイルです。 ■ mosquitto.conf mosquitto(MQTTブローカー)の設定ファイルです。MQTTのポート番号や、認証の設定などが記述されています。 ■ secrets.json DaprのSecrets Managementの機能を使って、ローカルのJSONファイルをシークレットストアとして使用するためのJSONファイルです。アラートのしきい値などの設定値が定義されています。 Simulation ■ simulate.py センサーのシミュレーターです。センサーデータを生成して、MQTTに送る役割を持っています。 common ■ smartfarm_common/models.py このファイルは、Sensor Gateway Service、Alert Service、Dashboard Serviceの3つのサービスで共通して使用するモデルクラスを定義しています。センサーデータの構造などを定義しています。 実行方法 このデモアプリを実行するための方法を説明します。以下の手順に従ってください。 事前準備 既にDapr CLIをインストールしている場合は、Dapr CLIのインストールはスキップしていただいて構いません。まだインストールしていない場合は、以下のコマンドを実行して、Dapr CLIをインストールしてください。 $ wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash $ dapr init リポジトリのクローンとディレクトリ移動 このデモアプリが公開されているGitHubリポジトリをクローンして、プロジェクトのディレクトリに移動します。 $ git clone https://github.com/noriyukitakei/dapr-demo.git $ cd dapr-demo 必要なDockerコンテナの起動 デモアプリの稼働に必要な3つのコンテナ(RabbitMQ、mosquitto、maildev)をDocker Composeを使って起動します。 $ cd Infrastructure $ docker-compose up -d Sensor Gateway Serviceの起動 Pythonの仮想環境を作成して有効にします。 $ cd .. /SensorGatewayService $ python -m venv .venv $ source .venv/bin/activate 必要なライブラリをインストールします。 $ pip install -r requirements.txt Sensor Gateway Serviceのアプリケーションとサイドカープロセスを起動します。 $ dapr run --app-id sensorgateway --app-port 6100 --dapr-http-port 3610 --components-path .. /Infrastructure/components -- python -m uvicorn sensorgateway.app:app --port 6100 Alert Serviceの起動 Sensor Gateway Serviceはフォアグラウンドで起動したままにしておいてください。新しいターミナルを開いて、Alert Serviceを起動します。 Pythonの仮想環境を作成して有効にします。 $ cd [ GitHubリポジトリのクローン先ディレクトリ ] /AlertService $ python -m venv .venv $ source .venv/bin/activate 必要なライブラリをインストールします。 $ pip install -r requirements.txt Alert Serviceのアプリケーションとサイドカープロセスを起動します。 $ dapr run --app-id alertservice --app-port 6101 --dapr-http-port 3611 --components-path .. /Infrastructure/components -- python -m uvicorn alertservice.app:app --port 6101 Dashboard Serviceの起動 Sensor Gateway ServiceとAlert Serviceはフォアグラウンドで起動したままにしておいてください。新しいターミナルを開いて、Dashboard Serviceを起動します。 Pythonの仮想環境を作成して有効にします。 $ cd [ GitHubリポジトリのクローン先ディレクトリ ] /DashboardService $ python -m venv .venv $ source .venv/bin/activate 必要なライブラリをインストールします。 $ pip install -r requirements.txt Dashboard Serviceのアプリケーションとサイドカープロセスを起動します。 $ dapr run --app-id dashboardservice --app-port 6102 --dapr-http-port 3612 --components-path .. /Infrastructure/components -- python -m uvicorn dashboard.app:app --port 6102 シミュレーターの起動と動作確認 Sensor Gateway ServiceとAlert ServiceとDashboard Serviceはフォアグラウンドで起動したままにしておいてください。新しいターミナルを開いて、センサーのシミュレーターを起動します。 Pythonの仮想環境を作成して有効にします。 $ cd [ GitHubリポジトリのクローン先ディレクトリ ] /Simulation $ python -m venv .venv $ source .venv/bin/activate 必要なライブラリをインストールします。 $ pip install -r requirements.txt シミュレーターを起動します。すると、定期的にセンサーデータが生成されてMQTTに送られます。 $ python simulate.py Publishing telemetry to mqtt://localhost:1883 topic = farm/telemetry sent: { 'sensorId' : 'sensor-003' , 'temperature' : 29.89 , 'humidity' : 60.44 , 'timestamp' : '2026-04-07T00:44:04.142516+00:00' } sent: { 'sensorId' : 'sensor-002' , 'temperature' : 24.09 , 'humidity' : 60.69 , 'timestamp' : '2026-04-07T00:44:05.145853+00:00' } sent: { 'sensorId' : 'sensor-002' , 'temperature' : 33.83 , 'humidity' : 41.07 , 'timestamp' : '2026-04-07T00:44:06.147417+00:00' } sent: { 'sensorId' : 'sensor-003' , 'temperature' : 22.22 , 'humidity' : 35.16 , 'timestamp' : '2026-04-07T00:44:07.149945+00:00' } Alert Serviceのコンソールには、異常値が検知されたときに以下のような出力がされます。 == APP == WARNING:alertservice:ALERT received: Temperature 42 .27C exceeds threshold 40 .0C == APP == INFO: 127.0 .0.1:53585 - "POST /alerts HTTP/1.1" 200 OK == APP == WARNING:alertservice:ALERT received: Temperature 44 .84C exceeds threshold 40 .0C == APP == INFO: 127.0 .0.1:53660 - "POST /alerts HTTP/1.1" 200 OK メールが送付されているはずですので、見てみましょう。maildevは、http://localhost:1080でWeb UIが提供されているので、ブラウザでアクセスしてみてください。すると、Alert Serviceから送られたメールが届いていることが確認できます。 DashBoard Serviceにアクセスして、センサーの状態を確認してみましょう。センサーのIDがsensor-001とした場合、以下のURLにアクセスしてみてください。 $ curl http://localhost:6102/sensors/sensor-001 { "found" :true, "state" : { "sensorId" : "sensor-001" , "temperature" :31.88, "humidity" :45.29, "timestamp" : "2026-04-07T00:51:40.901649Z" , "updatedAt" : "2026-04-07T00:51:40.906840Z" } } 上記のようなレスポンスが返ってくれば、Dashboard ServiceがSensor Gateway Serviceに対してリクエストを送って、センサーの状態を取得することができていることが確認できます。 まとめ 今回は、Daprの基本的な機能であるPub/Sub、Bindings、Service Invocation、Secrets Managementについて、わかりみ深く説明しました。これらの機能を組み合わせることで、ちょっぴり本格的なマイクロサービスを作ることができることも紹介しました。 Daprは、マイクロサービスを開発するための便利な機能を提供しているので、ぜひ活用してみてください。Daprを使うことで、マイクロサービスの開発がより簡単になり、開発者はビジネスロジックの実装に集中することができるようになります。Daprは、マイクロサービスの開発を加速させるための強力なツールであると言えるでしょう。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post 世界一わかりみの深いDapr first appeared on SIOS Tech Lab .
こんにちは、伊藤です。 Exchange Online経由でメールを送信するサーバを構築しましたので、今回はその手順をまとめました。 Exchange Online経由とする利点 Exchange Onlineを経由することで、以下の利点があります。 スパム対策などのExchange Onlineのメールフィルタ機能(Exchange Online Protection)を活用可能 Exchange管理センターのメール追跡機能でメール送信を確認できるようになり、メール送信の管理が簡単 メール送信時のDKIM署名をExchange Onlineに集約可能 前提条件 メールサーバに固定のパブリックIPアドレスが割り当てられている必要があります。 送信元のメールドメインは、事前にMicrosoft 365管理センターでセットアップを完了させ、Exchange OnlineとDKIMを有効にしておく必要があります。 検証環境 パブリックIPアドレスを持つRHEL10サーバ(OS:Red Hat Enterprise Linux release 10.1) Exchange Onlineが利用可能なMicrosoft Entra IDテナント Exchange管理センターの設定 Exchange管理センターでは、メールサーバからメールを受信するコネクタを以下の手順で設定します。 1. Exchange管理センター( https://admin.exchange.microsoft.com/ )にログインし、「メールフロー」&gt;「コネクタ」&gt;「コネクタの追加」を選択します。 2. 接続先は「組織のメール サーバー」を選択します。接続先は自動的に「Office 365」が選択されます。設定完了後に「次」を選択します。 3. コネクタ名と機能を設定します。設定完了後に「次」を選択します。 コネクタの保存後の設定のうち、「オンにする」のチェックボックスで、コネクタの有効・無効を設定します。 また、「内部Exchangeメールヘッダーを保持する」は無効(チェックなし)とします。この設定項目は、主に送信元がExchange(Exchange Serverや別のExchange Onlineなど)の場合に利用される項目です。電子メールアドオンサービスに送信されるメッセージの内部ヘッダーを保持することで、該当のメッセージが信頼された内部メッセージとして扱われるようになります。 4. 送信メールの認証を設定します。「送信側サーバーのIPアドレスが、あなたの組織にのみ属している次のIPアドレスのいずれかと一致すること」を設定した上で、メールサーバのパブリックIPアドレスを追加します。追加完了後に「次」を選択します。 5. 設定内容を確認の上、「コネクタを作成」を選択します。 6. コネクタ一覧に表示されることを確認して完了です。 参考: https://learn.microsoft.com/ja-jp/exchange/mail-flow-best-practices/use-connectors-to-configure-mail-flow/set-up-connectors-to-route-mail メールサーバの構築 メールサーバを以下の手順で設定します。 1. メールサーバにログインし、Postfixをインストールします。 $ sudo dnf install postfix 2. /etc/postfix/main.cf を設定します。 # デフォルトから以下の設定を追加または変更 # 外部からの通信を受け付ける inet_interfaces = all # ローカル配送を無効 mydestination = # メール送信元許可IPアドレス帯を設定(※1) mynetworks = 127.0.0.0/8, &#91;::1]/128 # Exchange Onlineへリレー(※2) relayhost = &#91;mail-example-com0i1b.mail.protection.outlook.com]:25 smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination ※1 mynetworksは、環境に合わせて、メールクライアントのIPアドレス帯を設定してください。 ※2 relayhostは、メール送信元ドメインのMXレコードの値(.mail.protection.outlook.comで終わる値)を設定します。メール送信元ドメインが複数想定される場合は、最もよく使用するドメインのMXレコードの値を設定します。 また、メールクライアントからメールサーバへの通信でIPv6を使用しない場合は inet_protocols=ipv4 を設定し、IPv6を無効にしても問題ありません。 3. メールクライアントからメールサーバへの通信でIPv6を使用する場合は、メールサーバから外部への送信時のみIPv6を無効とします。今回の手順ではExchange Online側の受信コネクタでIPv4アドレスによる送信元認証を行っており、IPv6で接続されると認証に失敗するためです。 /etc/postfix/master.cf を以下のように設定します。 # デフォルトから以下の設定を追加 smtp unix - - n - - smtp -o inet_protocols=ipv4 4. Postfixを起動し、自動起動を有効にします。 $ sudo systemctl start postfix $ sudo systemctl enable postfix 必要な通信要件について メールサーバからExchange Onlineへメール送信する際に必要な通信要件は以下の通りです。 通信元 通信先 ポート メールサーバ *.mail.protection.outlook.com, *.mx.microsoft 40.92.0.0/15, 40.107.0.0/16, 52.100.0.0/14, 104.47.0.0/17, 2a01:111:f400::/48, 2a01:111:f403::/48 TCP: 25 通信先は2026年4月時点の内容です。正式情報は、以下URLの内容をご確認ください。 参考: https://learn.microsoft.com/ja-jp/microsoft-365/enterprise/urls-and-ip-address-ranges?view=o365-worldwide メールサーバのSPFの設定について 原則として、すべてのメールがExchange Onlineを経由して送信されるのであれば、外部の受信サーバから見た「送信元IPアドレス」はExchange Onlineのものになるため、 include:spf.protection.outlook.com だけでSPFチェックはパスします。それでもなお、メールサーバのIPアドレスをSPFレコードに追加しておくべき理由は以下の2点です。 1. Exchange Online自身のスパム判定をクリアするため メールサーバからExchange Onlineへメールをリレー(中継)する際、Exchange Onlineが、SPFチェックを行う場合があります。受信コネクタが適切に設定されていれば通常は問題ありませんが、SPFレコードにIPを記載しておくことで、「なりすまし」や「スパム」の誤検知リスクを確実に減らすことができます。 2. 万が一の「直接送信」への保険として 何らかの設定変更やルーティングのトラブルで、メールサーバがExchange Onlineを経由できず、直接インターネット上の宛先へメールを送信してしまった場合(フォールバック)、IPアドレスがSPFレコードにないと、外部の受信側で確実にスパム扱い(SPF Fail)されてしまいます。IPを記載しておくことで、このリスクを回避できます。 SPFレコードを以下のように設定します。 v=spf1 ip4:&lt;メールサーバのIPアドレス&gt; include:spf.protection.outlook.com -all 参考: https://learn.microsoft.com/ja-jp/defender-office-365/email-authentication-spf-configure#scenario-microsoft-365-email-with-on-premises-email-and-a-non-microsoft-email-service メール送信確認 構築したメールサーバ上でtelnetコマンドを使用し、メールの送信テストを行います。 1. telnetコマンドを使用できるようにインストールします。 $ sudo dnf install telnet 2. telnetコマンドで以下のメールを送信します。 $ telnet localhost 25 Trying ::1... Connected to localhost. Escape character is '^]'. 220 testmailserver.localdomain ESMTP Postfix MAIL FROM:&lt;送信元メールアドレス&gt; 250 2.1.0 Ok RCPT TO:&lt;送信先メールアドレス&gt; 250 2.1.5 Ok DATA 354 End data with &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt; Subject: test mail From: &lt;送信元メールアドレス&gt; To: &lt;送信先メールアドレス&gt; test mail contents . 250 2.0.0 Ok: queued as E3F1818054B9 quit 221 2.0.0 Bye Connection closed by foreign host. 3. 送信先メールアドレスで受信できることを確認します。 4. Exchange管理センターのメッセージ追跡機能にて、メールの送信を確認します。メールの詳細では、Office 365(Exchange Online)経由で送信されていることを確認できます。 まとめ 今回は、Exchange Onlineを経由してメールを送信するサーバの構築手順と、実際の送信テストについて解説しました。Exchange Onlineを含むメールシステムの構築の参考にしていただければ幸いです。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post Exchange Online経由でメールを送信するサーバを構築してみた first appeared on SIOS Tech Lab .
概要 前回の記事「 SSL/TLS証明書の有効期限短縮に備えて脱・手動更新① 」の続きとなります。 本記事では、Certbotサーバがどのように認証局(CA)から証明書を取得するのか、 そのための手段としてACME-DNSというソフトウェアがどのように関わるのかを説明します。 CertBOTを用いた証明書発行と設定の流れ Certbotが認証局(CA)から証明書を取得しする過程はこちらの図の通りですが、 これは大まかな流れとなります。 CAがCertBOTサーバに対して行うドメイン所有確認の手法には、 HTTP-01チャレンジとDNS-01チャレンジがあります。 HTTP-01 チャレンジは、 Web サーバーを使ってドメインの所有を証明する方式です。 Let’s Encryptなど認証局 は、「このドメインの管理者であるなら、Web サーバー上の特定の場所に指定した内容のファイルを置けるはずだ」という考え方で確認を行います。 ただし、認証局が対象ドメインに対して HTTP(ポート80)でアクセスするため、インターネットから対象ドメインの80番ポートへアクセスできる必要があります。 Webサーバーを公開していない環境や、80番ポートを外部公開できない環境では利用できないという制約があります。 本記事ではそのような制約を受けないDNS-01チャレンジを採用します。 DNS-01チャレンジ DNS-01チャレンジでは、下記のような流れで証明書取得が行われていきます。 HTTP-01チャレンジが「Web サーバーに置いたファイル(トークン)を見に来る」方式なのに対し、DNS チャレンジは「DNS に登録された情報を見に来る」方式です。 HTTP-01チャレンジとDNS-01チャレンジ、それぞれの特徴を比較すると表のようになります。 DNS-01チャレンジは80 番ポートの公開が不要であるため、メールサーバーや LDAPサーバーなど80 番ポートを使わないサーバーを対象にドメインの所有確認ができ、証明書自動取得を行えます。 一方で、DNS-01チャレンジには注意点もあります。一部のDNSサービスでは、そもそもAPIが公開されておらず、Certbotから自動で書き換える手段がありません。 すべての DNS プロバイダがAPI を提供しているわけではないため、APIがない場合はDNS-01 チャレンジの自動化はできず、更新のたびに手作業が必要になります。 その課題に対する対処法として、acme-dns というオープンソースソフトウェアが挙げられます。 ACME-DNSとは ACME-DNSは、DNS-01 チャレンジ認証を実施するための簡易 DNS サーバ、Web API サーバ機能を有するソフトウェアです。 https://github.com/joohoi/acme-dns acme-dns を使う場合、実際の DNS には最初に一度だけ設定を行います。 まず、ドメインを管理するDNS側で、サブドメイン(_acme-challenge)を、CNAMEを使ってACME-DNSサーバーへ向くように設定(委任)します。 これにより、認証局からのドメイン所有確認のクエリは、自動的にACME-DNSサーバーへと転送されるようになります。 また、ACME-DNSサーバーへの問い合わせを委任するための情報として、Aレコード、acme.example.comのNSレコードもメインのDNSに登録しています。 委任に必要なUUIDとはACME-DNSがFQDNごとに発行するもので、 ACME-DNSサーバーのエンドポイントを叩いた際に発行される情報ですが、 それを取得するコマンドはこの後説明させていただきます。 CertbotとACME-DNSを用いて証明書取得を行う具体的な流れは6つのステップです。 (なお、今回構築する環境では、CertbotとACME-DNSの機能が1つのサーバに同居している構成としています) まず、CertbotがACMEサーバに証明書を要求し、それに対してACMEサーバがドメイン所有を確認しようとDNSチャレンジを要求します。 次に、CertbotはACME-DNSのAPIを叩き、取得する証明書のFQDNに対応するUUIDのレコードに検証用トークンを書き込みます。 ACMEサーバがDNS検証を開始します。 メインDNSはCNAME設定に従い、問い合わせをACME-DNSサーバへ転送します。 ACME-DNSがUUIDに基づいた適切なトークンを回答することで検証が成功し、 証明書が発行されます。 結果、DNSのプロバイダにAPI がなくても、DNS チャレンジの自動更新が可能になります。CNAMEレコードさえ設定すればDNSのレコードを手でいじる手間がなくなり自動更新されるものとなります。 ACME-DNSの構築手順 まずは、ACME-DNSのソースコードを取得します。 # ACME-DNSのソースをGithubから取得 $ git clone https://github.com/joohoi/acme-dns # 設定ファイルやデータベース用のディレクトリを作成 $ cd acme-dns $ mkdir data db 次に、オリジナルをコピーした上で、設定ファイルを編集します。 $ cp config.cfg config.org $ vim config.cfg 下記のような内容に編集します。 [general] # DNS interface. Note that systemd-resolved may reserve port 53 on 127.0.0.53 # In this case acme-dns will error out and you will need to define the listening interface # for example: listen = "127.0.0.1:53" #listen = "127.0.0.1:53" listen = "0.0.0.0:53" # protocol, "both", "both4", "both6", "udp", "udp4", "udp6" or "tcp", "tcp4", "tcp6" protocol = "both" # domain name to serve the requests off of domain = "auth.example.org" # acme-dns が管理する DNS ゾーン名 # zone name server nsname = "auth.example.org" # domain で指定したゾーンのネームサーバ名 # admin email address, where @ is substituted with . nsadmin = "admin.example.org" # SOAレコード内の管理者メールアドレス # predefined records served in addition to the TXT records = [ # acme-dns サーバが固定で返す DNS レコードを定義 # domain pointing to the public IP of your acme-dns server auth.example.org. A 198.51.100.1, # specify that auth.example.org will resolve any *.auth.example.org records auth.example.org. NS auth.example.org., ] # debug messages from CORS etc debug = true # デバッグ用の詳細ログを出力するかどうかを指定する項目 [database] # Database engine to use, sqlite3 or postgres engine = "sqlite3" # acme-dns が TXT レコードや登録情報を保存するデータベースの種類を指定 # Connection string, filename for sqlite3 and postgres://$username:$password@$host/$db_name for postgres # Please note that the default Docker image uses path /var/lib/acme-dns/acme-dns.db for sqlite3 connection = "/var/lib/acme-dns/acme-dns.db" # データベースへの接続情報(保存先)を指定 # connection = "postgres://user:password@localhost/acmedns_db" [api] # listen ip eg. 127.0.0.1 #ip = "0.0.0.0" ip = "127.0.0.1" # API サーバが待ち受ける IP アドレスを指定。ACME-DNSサーバー内でAPI叩く構成であれば 127.0.0.1 を指定。 # disable registration endpoint disable_registration = false # /register エンドポイントを無効化するか指定する項目。false を設定することで、新しいサブドメインを ACME-DNS に登録することが可能。 # listen port, eg. 443 for default HTTPS port = "8080" # APIサーバの待受ポート # possible values: "letsencrypt", "letsencryptstaging", "cert", "none" tls = "none" # APIとの通信を HTTPS (TLS) で暗号化するかどうかを指定 # only used if tls = "cert" # tls = "cert" を選んだ際に使用する項目。ACME-DNS の API との通信を暗号化する際に使用する SSL/TLS 証明書のパスを指定。 tls_cert_privkey = "/etc/tls/example.org/privkey.pem" tls_cert_fullchain = "/etc/tls/example.org/fullchain.pem" # only used if tls = "letsencrypt" acme_cache_dir = "api-certs" # tls = "letsencrypt" を選んだ際、API との通信に使用する証明書を保存するディレクトリ # optional e-mail address to which Let's Encrypt will send expiration notices for the API's cert notification_email = "" # tls = "letsencrypt" に設定している場合に使用。ACME-DNS の API との通信を暗号化する際に使用する証明書の期限を通知する先のメールアドレスを指定。 # CORS AllowOrigins, wildcards can be used corsorigins = [ # ブラウザから API を呼び出せる Web ページのドメインを指定する設定。 * ] # use HTTP header to get the client ip use_header = false # 接続してきたクライアントのIPアドレスを特定する際に、HTTPヘッダーの情報を使用するかどうかを指定する項目。 # header name to pull the ip address / list of ip addresses from header_name = "X-Forwarded-For" # use_header = true に設定した場合に、どの名前のHTTPヘッダにIPアドレスが入っているかを指定。 [logconfig] # logging level: "error", "warning", "info" or "debug" loglevel = "debug" # ログの出力レベルを指定 # possible values: stdout, TODO file &amp; integrations logtype = "stdout" # ログの出力先を指定 # file path for logfile TODO # logfile = "./acme-dns.log" # format, either "json" or "text" logformat = "text" # 記録されるログのフォーマットを指定 設定ファイルを編集したら、諸々の準備を行います。 # Go言語の開発環境(コンパイラ)をインストール $ sudo dnf install -y golang # ソースコードから実行可能なプログラムを生成 $ go build # ビルドしたプログラムを、システム全体の実行用フォルダへ配置 $ sudo cp acme-dns /usr/local/bin/ # 配置されたことを確認 $ which acme-dns # 設定ファイル用のディレクトリを root 権限で作成 $ sudo install -d -m 755 -o root -g root /etc/acme-dns # 専用ユーザーの作成(この手順ではacmednsというユーザでACME-DNSを実行させることとしています) getent passwd acmedns || sudo useradd -r -s /sbin/nologin -d /var/lib/acme-dns -M acmedns # 設定ファイルをコピーしつつ、所有者を root、グループを acmedns に設定 # /path/to は git clone を実行ユーザーの home ディレクトリに置き換えます $ sudo rsync -a --chown=root:acmedns /path/to/acme-dns/config.cfg /etc/acme-dns/ # データディレクトリの作成と権限設定 sudo install -d -m 700 -o acmedns -g acmedns /var/lib/acme-dns # データベースファイルの空作成 sudo test -f /var/lib/acme-dns/acme-dns.db || sudo touch /var/lib/acme-dns/acme-dns.db # ファイルの所有権変更 sudo chown acmedns:acmedns /var/lib/acme-dns/acme-dns.db # ファイルのアクセス権制限 sudo chmod 600 /var/lib/acme-dns/acme-dns.db # 一般ユーザー(acmedns)に、特権が必要なポート(53番)を使うための権限を付与 $ sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/acme-dns 次に、ACME-DNSをSystemd化させて常駐化します。 $ sudo vim /etc/systemd/system/acme-dns.service こちらのファイルを下記のように編集します。 [Unit] Description=acme-dns authoritative DNS server for ACME DNS-01 Wants=network-online.target After=network-online.target [Service] User=acmedns Group=acmedns ExecStart=/usr/local/bin/acme-dns -c /etc/acme-dns/config.cfg WorkingDirectory=/var/lib/acme-dns Restart=on-failure RestartSec=2s # 低権限でも 53 を開ける(setcapコマンドを実行しているので不要ですが予備措置として記載) AmbientCapabilities=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_BIND_SERVICE NoNewPrivileges=true # ログは journal に流す StandardOutput=append:/var/log/acme-dns/acme-dns.log [Install] WantedBy=multi-user.target 編集後、サービスを読み込んで起動。 $ sudo systemctl daemon-reload $ systemctl restart acme-dns ここまで実行すれば、ACME-DNSの初期設定・構築は完了です。 次に、「3. 認証スクリプトの用意」のステップへ進みますが、次回の記事にて解説させていただきます。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post SSL/TLS証明書の有効期限短縮に備えて脱・手動更新② first appeared on SIOS Tech Lab .
概要 インターネットセキュリティの重要性が日に日に増していく中、 SSL/TLS サーバー証明書の最大有効期間が 2029 年までに段階的に短縮されることが決定しました。 この短命化の波において、今回は「Certbot」というツールに注目し、証明書取得・更新の自動化について記事にさせていただこうと思います。 Certbotは、ACME(Automated Certificate Management Environment)プロトコルを利用して証明書の自動取得・更新を行うためのクライアントツールです。 また、取得した証明書を指定のサーバーへ配置、そしてサービスの再起動(Reload)までも自動化することが可能です。 本記事では、このCertbotサーバを構築する手順を記載していきます。 &nbsp; 構成 本記事で構築する環境および証明書発行・設定反映までの流れは図の通りです。 本記事において、Certbotサーバーは図の通り、WEBサーバーなどの証明書を使わせたいサーバーとは別に構築します。 Certbotは証明書を自身のサーバーで取得し、その後各サーバーへ配置させる構成となります。 Certbotサーバの構築手順 構築手順を大きく分けると下記の4ステップとなります。 Certbotのインストール ACME-DNSのインストール・設定 認証スクリプトの用意 デプロイスクリプトの用意 ACME-DNSなど初出のワードが出てきていますが、各ステップの手順にて説明させていただきます。 &nbsp; Certbotのインストール まずは、Certbotをインストールするための基盤(Snap環境)をセットアップ。 # EPELリポジトリが必要な場合(RHEL/Alma/Rockyなど) sudo dnf install epel-release -y # Snapdのインストール sudo dnf install snapd -y # Systemdソケットの有効化と起動 sudo systemctl enable --now snapd.socket # シンボリックリンク作成 sudo ln -s /var/lib/snapd/snap /snap # Snap環境自体のコアパッケージを最新にします。 sudo snap install core sudo snap refresh core 続いてsnap版のCertbotをインストール。 # Snap版Certbotのインストール sudo snap install --classic certbot # certbotコマンドのシンボリックリンクを作成 sudo ln -s /snap/bin/certbot /usr/bin/certbot # バージョン確認 certbot --version なお、snap版のCertbotを取得している理由としては、 Certbotの開発元であるEFF(Electronic Frontier Foundation)が、多くのLinuxディストリビューションにおいてsnap版を利用したインストールを公式に推奨しているからです。 https://eff-certbot.readthedocs.io/en/latest/install.html &nbsp; &nbsp; 続いて、「2. ACME-DNSのインストール・設定」のステップとなりますが、 次回の記事にて解説させていただきます。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post SSL/TLS証明書の有効期限短縮に備えて脱・手動更新① first appeared on SIOS Tech Lab .