<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:webfeeds="http://webfeeds.org/rss/1.0">
  <title>DevOps カテゴリ | フューチャー技術ブログ</title>
  <subtitle>DevOps カテゴリの記事一覧</subtitle>
  <icon>https://future-architect.github.io/feed_icon.png</icon>
  <logo>https://future-architect.github.io/apple-touch-icon.png</logo>
  <webfeeds:icon>https://future-architect.github.io/apple-touch-icon.png</webfeeds:icon>
  <webfeeds:accentColor>258fb8</webfeeds:accentColor>
  <link href="https://future-architect.github.io/categories/DevOps/atom.xml" rel="self"/>
  <link href="https://future-architect.github.io/categories/DevOps/"/>
  <updated>2026-08-20T15:00:00.000Z</updated>
  <id>https://future-architect.github.io/categories/DevOps/</id>
  <generator uri="https://hexo.io/">Hexo</generator>
  <entry>
    <title>AWS Certified DevOps Engineer - Professional 合格体験記 - 「知らない」を潰す問題演習で一発合格</title>
    <link href="https://future-architect.github.io/articles/20260821a/"/>
    <id>https://future-architect.github.io/articles/20260821a/</id>
    <published>2026-08-20T15:00:00.000Z</published>
    <updated>2026-08-20T15:00:00.000Z</updated>
    <author><name>棚井龍之介</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260821a/aws-certified-devops-engineer-professional.png" alt="" width="600" height="600">

<h2 id="はじめに">はじめに</h2><p>Cyber Security Innovation Group、FutureVulsチームの棚井です。</p>
<p>2026年7月27日に「AWS Certified DevOps Engineer - Professional (DOP-C02)」を受験し、833点&#x2F;1000点（合格ラインは750点）で一発合格しました。</p>
<p>先日、「AWS Certified Solutions Architect - Professional 合格体験記」を公開しました。その記事の締めで、次はDevOps Engineer - Professionalを目指すと書いており、SAP-C02に合格した勢いのまま受験しました。</p>
<h2 id="試験の概要">試験の概要</h2><p>DOP-C02は、DevOpsエンジニアロールを担う人を対象に、AWSでの分散システムのプロビジョニングや運用、管理の技術的な専門知識を検証する試験です。受験対象者には「AWS環境でのプロビジョン、運用、管理に関する2年以上の経験」に加えて、ソフトウェア開発ライフサイクルとプログラミングまたはスクリプティングの経験が求められています。</p>
<h3 id="試験の基本情報">試験の基本情報</h3><div class="scroll"><table>
<thead>
<tr>
<th>項目</th>
<th>内容</th>
</tr>
</thead>
<tbody><tr>
<td>試験コード</td>
<td>DOP-C02</td>
</tr>
<tr>
<td>試験時間</td>
<td>180分</td>
</tr>
<tr>
<td>設問数</td>
<td>75問（採点対象65問＋採点対象外10問）</td>
</tr>
<tr>
<td>出題形式</td>
<td>択一選択問題（正解1つ・不正解3つ）、複数選択問題（5つ以上の選択肢から正解2つ以上）</td>
</tr>
<tr>
<td>受験料</td>
<td>300 USD</td>
</tr>
<tr>
<td>スコア</td>
<td>100〜1,000のスケールスコア</td>
</tr>
<tr>
<td>合格ライン</td>
<td>750点</td>
</tr>
<tr>
<td>対応言語</td>
<td>英語、日本語、韓国語、中国語（簡体字）</td>
</tr>
</tbody></table></div>
<p>出題形式や合格ラインはSAP-C02と同じです。</p>
<h3 id="コンテンツ分野と出題比率">コンテンツ分野と出題比率</h3><div class="scroll"><table>
<thead>
<tr>
<th>分野</th>
<th>出題の比率</th>
</tr>
</thead>
<tbody><tr>
<td>第1分野: SDLCのオートメーション</td>
<td>22%</td>
</tr>
<tr>
<td>第2分野: 設定管理とIaC</td>
<td>17%</td>
</tr>
<tr>
<td>第3分野: 耐障害性の高いクラウドソリューション</td>
<td>15%</td>
</tr>
<tr>
<td>第4分野: モニタリングとロギング</td>
<td>15%</td>
</tr>
<tr>
<td>第5分野: インシデントとイベントへの対応</td>
<td>14%</td>
</tr>
<tr>
<td>第6分野: セキュリティとコンプライアンス</td>
<td>17%</td>
</tr>
</tbody></table></div>
<p>最大配点は第1分野「SDLCのオートメーション」の22%で、第2分野「設定管理とIaC」と合わせると39%になります。CI&#x2F;CDパイプラインとIaCが試験の中心です。</p>
<p>面白いのは、試験ガイドに「受験対象者として範囲外の職務」が明記されていることです。高度なネットワークに関する知識、データベースの設計やクエリ、パフォーマンスの最適化、フルスタックアプリケーションのコード開発は範囲外とされています。全領域を広く問うSAP-C02に対して、DOP-C02は運用の自動化に的を絞った試験です。</p>
<h2 id="学習方法">学習方法</h2><p>今回はSkill BuilderとUdemyの2本立てです。これまでの試験勉強で続けてきた「わからないところをPerplexityに質問する」流れは、今回はほとんど使いませんでした。後述するUdemyの解説が詳しく、疑問がその場で解消されたからです。</p>
<h3 id="1-Skill-Builderで出題の傾向をつかむ">1. Skill Builderで出題の傾向をつかむ</h3><img class="bordered" src="/images/2026/20260821a/skillbuilder.png" alt="" width="640" height="347" loading="lazy">

<p>まずAWS Skill Builderで、分野別のDomain Practiceを1から6まで解きました。問題の傾向を知るためです。そのあとに、無料で受けられるOfficial Practice Question Set（20問）を解きました。</p>
<p>公式模試（Official Pretest）はやっていません。解説が自分にとっては不足しており、理解するまで自力で補う労力が大きすぎたからです。CI&#x2F;CDまわりのサービス群は業務でちょうど使っておらず、解説を読んでも「なぜその選択肢が最適なのか」を判断するだけの事前知識がありませんでした。</p>
<h3 id="2-Udemyの演習問題で「知らない」を潰す">2. Udemyの演習問題で「知らない」を潰す</h3><img src="/images/2026/20260821a/udemy.png" alt="" width="226" height="320" loading="lazy">

<p>『【全出題範囲網羅+詳細解説】AWS DOP-C02日本語実践問題225問(DevOps Engineer Pro)』（syo @Cloud 講師）を使いました。</p>
<p>同じ講師の講座を使うのは、これで5回連続です。演習問題1〜3（75問・75問・76問）と出題順固定版を合わせて計452問が収録されており、ほぼすべての問題に図解が付いています。</p>
<p>正直に書くと、解き始めの正答率は50%程度で、とても焦りました。SAP-C02に合格した直後なので知識の貯金で解けるつもりでいたのですが、CI&#x2F;CDパイプラインの構成やコンテナ運用など、「知らない」問題が次々に出てきたからです。</p>
<p>そこからは、正誤にかかわらず解説を読み込み、知らないサービスや機能を「どんなユースケースで使うのか」とセットで頭に入れていきました。この講座は正解と不正解の両方の選択肢に解説が付き、公式ドキュメントへのリンクも張られているので、「知らない」をその場で潰す使い方に向いています。</p>
<h2 id="試験勉強で得た学び">試験勉強で得た学び</h2><p>ここからは、演習問題で「知らない」となって覚え直した内容です。</p>
<h3 id="PowerUserAccessとよく使うAWS管理ポリシー">PowerUserAccessとよく使うAWS管理ポリシー</h3><p>AWS管理ポリシーのPowerUserAccessは、権限設計を考えるうえで押さえておきたいポリシーです。このポリシーはNotActionを使って「<code>iam:*</code>、<code>organizations:*</code>、<code>account:*</code>以外のすべて」を許可する構造になっています。</p>
<p>開発チームに幅広い権限を渡しつつ、ユーザーやロールの管理（＝権限の自己拡張につながる操作）は渡さない、という用途のポリシーです。ただし例外があり、<code>iam:CreateServiceLinkedRole</code>や<code>organizations:DescribeOrganization</code>など、サービスの利用に必要な読み取り系とサービスリンクロール系のアクションは許可されています。渡さないのはアイデンティティと組織の管理だけだと理解しておくと、選択肢の切り分けがしやすくなります。</p>
<p>PowerUserAccess以外にも、名前から権限の範囲を即答できるようにしておきたいAWS管理ポリシーがあります。よく登場するものを整理します。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>ポリシー</th>
<th>許可する範囲</th>
<th>使いどころ</th>
</tr>
</thead>
<tbody><tr>
<td>AdministratorAccess</td>
<td>すべてのサービスとリソースへのフルアクセス</td>
<td>管理者。付与は最小限の人数に絞る</td>
</tr>
<tr>
<td>PowerUserAccess</td>
<td>IAMとOrganizations、アカウント管理を除くフルアクセス</td>
<td>開発チームにアイデンティティ管理以外を渡す</td>
</tr>
<tr>
<td>ReadOnlyAccess</td>
<td>すべてのサービスの読み取り。S3オブジェクトなどデータの中身の読み取りを含む</td>
<td>調査や監査で、データの中身まで確認するとき</td>
</tr>
<tr>
<td>ViewOnlyAccess</td>
<td>リソースの一覧と基本的なメタデータの参照のみ</td>
<td>リソースの棚卸し、状況把握</td>
</tr>
<tr>
<td>SecurityAudit</td>
<td>セキュリティ設定メタデータの参照（CloudTrailのイベント履歴は参照可。CloudWatch LogsやS3上のログ本文の読み取りは含まない）</td>
<td>セキュリティ監査、インシデントの初動調査</td>
</tr>
<tr>
<td>Billing</td>
<td>請求情報の確認、支払いの設定と承認</td>
<td>経理・コスト管理の担当者</td>
</tr>
</tbody></table></div>
<p>混同しやすいのは、ReadOnlyAccessとViewOnlyAccessの違いです。どちらも「読み取り専用」に見えますが、データの中身まで読めるのはReadOnlyAccessだけです。閲覧させたいのがリソースの一覧なのか、格納されたデータそのものなのかで選択肢が分かれます。なお、SecurityAuditとViewOnlyAccessをインシデント初動調査に使う話は、「AWS Certified Security - Specialty」の試験勉強でも登場しました。</p>
<h3 id="CodeArtifactで依存パッケージを一元管理する">CodeArtifactで依存パッケージを一元管理する</h3><p>第1分野「SDLCのオートメーション」で登場するのが、AWS CodeArtifactです。npmやPyPI、Mavenなどに対応したマネージドのアーティファクトリポジトリで、ドメインの下にリポジトリを作り、チームごとに使い分けます。</p>
<p>特徴は、アップストリームと外部接続の仕組みです。目当てのパッケージがなければ社内の共有リポジトリをたどり、その先の外部接続からnpmjsやPyPIといった公開リポジトリをオンデマンドで参照します。取得したバージョンはCodeArtifact側に保存されるので、2回目以降は社内で完結します。</p>
<pre class="mermaid" data-mermaid="65ed2164e313a1bdbfa3df070132e4798c5ea4df7bcba1b187eafda882d6ddeb">flowchart LR
    BUILD["開発者 / ビルド環境"] -->|"npm install など"| TEAM["CodeArtifact<br/>チーム用リポジトリ"]
    TEAM -->|"なければ<br/>アップストリームをたどる"| SHARED["CodeArtifact<br/>共有リポジトリ"]
    SHARED -->|"外部接続で<br/>オンデマンド取得"| PUB["公開リポジトリ<br/>npmjs / PyPI / Maven Central"]
    PUB -.->|"取得したバージョンを保持"| SHARED</pre>

<p>セキュリティ面の利点は2つです。</p>
<ul>
<li>ビルド環境の通信先をCodeArtifactに一本化でき、IAMの認可トークンで誰がどのリポジトリから取得できるかを制御できる</li>
<li>取得済みのバージョンが保持されるので、公開リポジトリ側で削除や障害が起きても手元のビルドは止まらない</li>
</ul>
<p>依存関係かく乱攻撃（dependency confusion）への対策にもなります。社内パッケージと同じ名前を公開リポジトリに登録して取り込ませる攻撃ですが、パッケージオリジンコントロールで「直接公開のみを許可し、外部接続からの取得はブロックする」と統制すれば、内部パッケージが外の同名パッケージにすり替わる経路を塞げます。</p>
<h3 id="CloudFormationのサービスロールとiam-PassRole">CloudFormationのサービスロールとiam:PassRole</h3><p>第2分野「設定管理とIaC」では、CloudFormationそのものの機能に加えて、権限まわりの設計が問われます。</p>
<p>CloudFormationはデフォルトでは操作した人の権限でリソースを作りますが、サービスロールを指定すると、そのロールの権限でスタックを操作するようになります。開発者にはスタック操作の権限と<code>iam:PassRole</code>だけを与え、リソース作成の強い権限はサービスロールに寄せます。この形にすると、開発者本人に強い権限を直接持たせずに、スタックの作成や更新、削除を回せます。</p>
<pre class="mermaid" data-mermaid="a5a6bf0393dc6b96086c92e533b720a29578eebf2091f8aeb9547a3d0bcdb639">flowchart LR
    DEV["開発者<br/>（スタック操作の権限<br/>＋ iam:PassRole のみ）"] -->|"サービスロールを指定して<br/>スタックを操作"| CFN["CloudFormation<br/>＋ サービスロール"]
    CFN -->|"ロールの権限で<br/>リソースを作成・更新・削除"| RES["AWSリソース"]</pre>

<p>鍵になるのが<code>iam:PassRole</code>です。これは「このロールをサービスに渡してよいか」を制御する権限で、これがないとサービスロールを指定した（関連付け・変更する）スタック操作ができません。注意したいのは、一度サービスロールを関連付けたスタックでは、以後のすべての操作でそのロールが使われる（作成後に取り外せない）点です。スタックへの操作権限を持つユーザーは、PassRoleを持っていなくてもそのロールの権限を利用できるため、サービスロール自体も最小権限にしておく必要があります。逆に、PassRoleを広く許可してしまうと、強力なロールを任意のサービスに渡せてしまいます。CloudFormationに限らず、権限昇格の話で何度も出てくる考え方でした。</p>
<h3 id="Trusted-AdvisorのService-Limitsチェック">Trusted AdvisorのService Limitsチェック</h3><p>クォータ管理の自動化で中心になるのが、Trusted AdvisorのService Limitsチェックです。アラートの条件が具体的に決まっていて、使用率がクォータの80%に達すると黄色、100%に達すると赤になります。</p>
<p>このチェック結果はAWS Support APIから取得・更新できるので、「クォータ超過でデプロイが失敗する前に検知する」といった自動化に組み込めます。ただし、Service LimitsチェックそのものはBasicプランでもコンソールから確認できる一方、APIの利用自体にはBusinessサポート以上のプランが必要です。Trusted Advisorの全チェックを使う場合も、同じくBusinessサポート以上が前提になります。</p>
<p>通知まで自動化するなら、EventBridgeとの組み合わせです。チェックのステータスがWARNやERRORに変わったことをイベントとして拾い、SNSで運用チームに通知したり、Lambdaで後続の対応につなげたりします。</p>
<pre class="mermaid" data-mermaid="8c9de2147c939056c6353b2d61783f132d79e0df9b98ee7d88fa07678397cb7d">flowchart LR
    TA["Trusted Advisor<br/>Service Limits チェック<br/>使用率80%でWARN"] -->|"ステータス変化を<br/>イベントとして発行"| EB["EventBridge<br/>ルールは us-east-1 に作成"]
    EB -->|"WARN / ERROR に<br/>マッチしたら"| SNS["SNS"] --> OPS["運用チームへ通知"]
    EB --> LMD["Lambda"] --> ACT["クォータ緩和申請などの<br/>後続対応"]</pre>

<p>ここで見落としやすいのがリージョンです。Trusted Advisorはグローバルサービスなので、イベントはすべて米国東部（バージニア北部）リージョンのEventBridgeに出力されます。ルールをus-east-1に作らないと、東京リージョンでいくら待ってもイベントは届きません。閾値とサポートプランの前提に加えて、このリージョン制約まで押さえておく必要があります。</p>
<h2 id="試験結果の振り返り">試験結果の振り返り</h2><p>最終スコアは833点（合格ライン750点）で、83点上回っての一発合格でした。これまで受けたAWS認定でいちばん高いスコアです。</p>
<img class="bordered" src="/images/2026/20260821a/score.png" alt="" width="640" height="237" loading="lazy">

<p>分野別の評価は次のとおりです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>コンテンツ分野</th>
<th>出題比率</th>
<th>評価</th>
</tr>
</thead>
<tbody><tr>
<td>第1分野: SDLCのオートメーション</td>
<td>22%</td>
<td>コンピテンシーを満たしている</td>
</tr>
<tr>
<td>第2分野: 設定管理とIaC</td>
<td>17%</td>
<td>コンピテンシーを満たしている</td>
</tr>
<tr>
<td>第3分野: 耐障害性の高いクラウドソリューション</td>
<td>15%</td>
<td>コンピテンシーを満たしている</td>
</tr>
<tr>
<td>第4分野: モニタリングとロギング</td>
<td>15%</td>
<td>コンピテンシーを満たしている</td>
</tr>
<tr>
<td>第5分野: インシデントとイベントへの対応</td>
<td>14%</td>
<td>改善が必要</td>
</tr>
<tr>
<td>第6分野: セキュリティとコンプライアンス</td>
<td>17%</td>
<td>コンピテンシーを満たしている</td>
</tr>
</tbody></table></div>
<p>第5分野「インシデントとイベントへの対応」だけは「改善が必要」となりました。</p>
<p>試験を通しての実感は、「難しい」ではなく「知らない」が多い、に尽きます。SAP-C02は要件を読んで積み上げた知識から最適解を選ぶ試験で、考えれば答えに近づけました。いっぽうDOP-C02は、考える以前に「知らない」とどうにもならない問題が多くありました。SAP-C02と重なったのはIaCやモニタリングの考え方までで、CI&#x2F;CDやコンテナ運用まわりの細部の知識は別物でした。</p>
<p>もうひとつ、この試験は二段構えで理解していないと解けません。まずシステム構成をイメージできる前提知識があり、そのうえで「AWSが提供するDevOps関連のサービス群を活用するなら、どのツールのどの機能を使うべきか」を答えさせられます。構成がイメージできないと問題文が頭に入らず、ツールの理解が浅いと選択肢が絞れません。</p>
<p>時間配分には余裕があり、見直しを入れても30分余りました。180分を使い切ったSAP-C02とは対照的です。矛盾するようですが、これは同じことの裏返しです。知らなければ考えても解けない代わりに、知っていれば即答できる問題が多数を占めます。</p>
<h2 id="おわりに">おわりに</h2><p>ドメインが変わると「知らない」が一気に増える感覚は、「AWS Certified Generative AI Developer - Professional」のときにも味わいました。裏を返せば、資格の勉強は、普段の業務では使わないサービスをまとめて学べる機会でもあります。今回もCodeArtifactのように、業務では触れていなかったサービスを、使いどころごと知ることができました。</p>
<p>設計の選択肢が増えたことも収穫でした。「自前で実装しなくても、ネイティブ機能としてすでに用意されている」と知っていれば、その機能を前提にした構成を最初から検討できます。問題演習の中で「もっと簡単な正解」を何度も突きつけられたので、まずマネージドな機能を探して、なければ作る、という考え方が身につきました。</p>
<p>ProfessionalとSpecialtyの上位資格は、これで揃いました。ただ、AssociateとFoundationalのレベルにはまだ合格していません。次はそちらを進めて、全冠達成まで頑張ります。</p>
]]></content>
    <summary type="html">2026年7月27日に「AWS Certified DevOps Engineer - Professional」を受験し、833点/1000点（合格ラインは750点）で一発合格しました。Skill BuilderとUdemyの演習問題で「知らない」を潰した学習方法と、覚え直した内容をまとめます。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="AWS" scheme="https://future-architect.github.io/tags/AWS/"/>
    <category term="IAM" scheme="https://future-architect.github.io/tags/IAM/"/>
    <category term="合格記" scheme="https://future-architect.github.io/tags/%E5%90%88%E6%A0%BC%E8%A8%98/"/>
  </entry>
  <entry>
    <title>Japan Datadog User Group Meetup#20@札幌に登壇しました</title>
    <link href="https://future-architect.github.io/articles/20260814a/"/>
    <id>https://future-architect.github.io/articles/20260814a/</id>
    <published>2026-08-13T15:00:00.000Z</published>
    <updated>2026-08-13T15:00:00.000Z</updated>
    <author><name>棚井龍之介</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260814a/top.png" alt="" width="660" height="374">

<h2 id="はじめに">はじめに</h2><p>こんにちは。Cyber Security Innovation Group所属、FutureVulsチームの棚井龍之介です。</p>
<p>2026年8月10日(月)開催の Japan Datadog User Group Meetup#20@札幌 に登壇しました。テーマは「Bits AI &amp; Datadog MCP Server」で、さっぽろ大通ビアガーデンの時期に合わせた現地参加限定の回です。会場はクラスメソッド札幌オフィスでした。</p>
<p>前回の登壇で「活用事例はまた今度」という個人的な宿題を残していたので、今回はその回収です。フューチャーからの過去の登壇ブログもあわせてご覧ください。</p>
<ul>
<li>Japan Datadog User Group Meetup#8@札幌に登壇しました(棚井)</li>
<li>Japan Datadog User Group Meetup#14@福岡に登壇しました(FutureVulsチーム 市川さん)</li>
</ul>
<h2 id="発表内容">発表内容</h2><p>私の発表タイトルは「<strong>AI時代の”ひとりSRE”のすすめ～Datadog MCP Serverで運用もチーム連携もコンプラ対応も～</strong>」です。Datadog MCP Serverを軸として、運用・チーム連携・コンプライアンス対応をひとりで進められるようになった経緯を話しました。</p>
<img src="/images/2026/20260814a/スライド1.png" alt="" width="720" height="405" loading="lazy">

<p>発表の冒頭で、ひとつ謎かけをしました。</p>
<blockquote>
<p>うちでいちばん Datadog を使っているメンバーは、Datadog の UI を、ほとんど操作していません。</p>
</blockquote>
<img src="/images/2026/20260814a/スライド2.png" alt="" width="720" height="405" loading="lazy">

<h3 id="「ひとり」の意味の変化">「ひとり」の意味の変化</h3><p>私は脆弱性管理SaaSの FutureVuls でSREとCSIRTを担当しています。これまで、ひとりSREは属人化やバス係数1のようなネガティブな文脈で語られてきました。この前提が、AIエージェントの実用化で変わったと私は考えています。AIはトークンの許す限り24時間365日動かせるため、個人で捌ける量が桁違いになり、守備範囲は自分の手が届く範囲から文脈を渡せる範囲へ広がりました。</p>
<img class="bordered" src="/images/2026/20260814a/スライド4.png" alt="" width="720" height="405" loading="lazy">

<h3 id="転機と決断">転機と決断</h3><p>Datadog導入の当初の目的は、率直に言えば調査工数の削減でした。ところが2026年3月、あるサプライチェーン攻撃について「うちは、影響を受けているか?」という調査を当日中に終わらせる経験をしました。ただ、当日の対応を振り返ると課題がありました。調査に必要な情報がどこにあるかは分かっていても、調査とデータの集約は人手に依存し、実際に調べられるメンバーも限られていました。これはもはやSPOF(単一障害点)なので、改善したいと考え始めました。</p>
<img class="bordered" src="/images/2026/20260814a/スライド11.png" alt="" width="720" height="405" loading="lazy">

<p>たまたま翌月、世界最大級の脆弱性サミットであるVulnCon 2026(米国)に「The CVE Blind Spot」というタイトルで登壇していました(詳細は FutureVulsブログのレポート にあります)。その会場で刺さったのが、次の一言です。</p>
<blockquote>
<p>トリアージは、検知ではなく、文脈集約の問題だ</p>
</blockquote>
<img class="bordered" src="/images/2026/20260814a/スライド13.png" alt="" width="720" height="405" loading="lazy">

<p>この一言をきっかけに、「データの集約はDatadogへ、活用のインターフェースはMCP Serverへ」という方針を決めました。帰国後に進めたことは2つあります。ひとつは環境整備です。分散していたデータをDatadogに集約し、タグや命名をAIエージェントが読める形に整理して、DatadogやGitHub、ZendeskなどのMCP Server接続を整えました。もうひとつはセキュリティ強化で、インシデント対応の型化や外部認証の取得準備を進めました。ひとりで両方を回せたのは、調査や作業をClaude CodeなどのAIエージェントに任せられたからです。</p>
<h3 id="チームに定着した使い方">チームに定着した使い方</h3><p>ほんの二、三ヶ月でDatadog MCP Serverをベースとした運用が定着しました。問い合わせ対応、開発前の壁打ち、PoCとCSの支援、環境キャッチアップ、パフォーマンス改善などです。発表では、このうち3つを紹介しました。</p>
<h4 id="問い合わせ対応が新規参画者の教材になった">問い合わせ対応が新規参画者の教材になった</h4><p>Zendeskの問い合わせチケットを起点に、Claude Codeへ調査を依頼するようになりました。Claude CodeはDatadogやGitHubなどのMCP Serverへ並列に照会し、結果を集めて回答のドラフトと根拠まで作ります。参画直後のメンバーが問い合わせ対応で仕様を学ぶという運用スタイルもできました。MCP連携のフル活用により、問い合わせの総数が増えても少人数で回っています。</p>
<img class="bordered" src="/images/2026/20260814a/スライド17.png" alt="" width="720" height="405" loading="lazy">

<h4 id="作る前に、システム環境に聞く">作る前に、システム環境に聞く</h4><p>開発の着手前に、AIエージェントへ現状を調べさせて設計の壁打ちができるようになりました。To-Be(ありたい姿)はリクエストやBacklogに日々集まってきます。一方のAs-Is(いまの姿)は、メトリクスやログが集約済みのDatadogにAIエージェント経由でいつでも聞けます。このTo-BeとAs-Isの差分が、そのまま設計レビューの材料にもなります。上級エンジニアでなければ意識しないメトリクスまで調べてくれますし、変更前後の状態も全てログに残ります。</p>
<img class="bordered" src="/images/2026/20260814a/スライド18.png" alt="" width="720" height="405" loading="lazy">

<h4 id="エンジニア以外のメンバーもDatadogを使い始めた">エンジニア以外のメンバーもDatadogを使い始めた</h4><p>RUM(Real User Monitoring)と自然言語の組み合わせで、契約単位やユーザ単位の機能利用状況を、非エンジニアのメンバーが自分で調べられるようになりました。PoC支援やCS活動の判断材料になり、セールスやCSのチームにも利用が広がり始めています。ダッシュボードに抵抗があったメンバーも、自然言語で欲しい情報へ届くようになりました。</p>
<img class="bordered" src="/images/2026/20260814a/スライド19.png" alt="" width="720" height="405" loading="lazy">

<h3 id="コンプライアンス対応-ISO-27001-27017">コンプライアンス対応(ISO 27001&#x2F;27017)</h3><p>ここまでは、環境整備の上で生まれた使い方の話でした。並行して進めていたセキュリティ強化でも成果があり、FutureVulsは2026年6月にISO&#x2F;IEC 27001と27017を取得しました(詳細は FutureVulsのセキュリティへの取り組み をご覧ください)。DatadogのCloud SecurityにあるCompliance機能を、現在地を測る計器として、ギャップ分析の起点や改善サイクルの参考指標に使いました。調べる道具が、証明を支える道具にもなりました。</p>
<img class="bordered" src="/images/2026/20260814a/スライド20.png" alt="" width="720" height="405" loading="lazy">

<h3 id="種明かし">種明かし</h3><p>冒頭の謎かけに戻ります。</p>
<blockquote>
<p>うちでいちばん Datadog を使っているメンバーは、Datadog の UI を、ほとんど操作していません。</p>
</blockquote>
<p>答えは、Datadogへの入口がUIからMCPに変わっていたからです。MCP対応が決め手となり、UI経験ゼロのメンバーを含むチーム全員がDatadogのデータを使うようになりました。一方で、新しい課題もあります。MCP経由の利用が中心になると、UIに触れる機会が減り、機能の全体像を知らないまま使う場面も出てきます。ここは今後、私からメンバーへのUIレクチャーで補っていく必要があります。</p>
<img class="bordered" src="/images/2026/20260814a/スライド22.png" alt="" width="720" height="405" loading="lazy">

<p>発表の最後に、ひとりSREを「全員が意識しなくても使える状態を作る人」と定義し直しました。まず、私がタグや命名、MCP接続をAIエージェントが読める形に整えます。すると、聞き方を共有するだけで、エンジニア以外のメンバーにも使い方が広がっていきます。さらに、私が想定していなかった使い方がチームに生まれて、次の整備のヒントとして還ってきます。この「整える、広がる、還ってくる」という循環は、意思決定の速いひとりだからこそ一気に回せます。</p>
<img class="bordered" src="/images/2026/20260814a/スライド23.png" alt="" width="720" height="405" loading="lazy">

<p>発表は、この3行で締めました。</p>
<blockquote>
<p>データをDatadogに集約し、MCPで誰でも使えるようにする。<br>それにより、守備範囲はSREからセキュリティ、外部認証まで広がる。<br>そして、Datadogを活用するメンバーが増えていく。</p>
</blockquote>
<h2 id="当日の様子">当日の様子</h2><p>テーマの通り、Claudeの利用状況の監視、Bits Agent Builderの活用事例、DASH 2026のre:Capなど、Bits AIとMCP Serverの事例が並ぶ濃い回でした。セッション一覧は connpassのイベントページ にあります。当日の雰囲気は、Xで #JDDUG を検索すると参加者の投稿から伝わります。</p>
<p>今回いちばんの収穫は、Datadogアンバサダーのようにフル活用しているユーザの意見を直接聞けたことです。Datadogでは次々と新しいサービスが追加されますが、自分のシステム環境ですぐに試せるとは限りません。だからこそ、すでに使い込んでいるユーザから使用感や勘所を聞き、気になった部分をその場で質問できる機会は貴重です。ユーザ同士だからこそ話せる、ネットには載せられないディープな情報が飛び交うのも、ユーザ会ならではの面白さです。</p>
<h2 id="Datadog認定プログラムのすすめ">Datadog認定プログラムのすすめ</h2><p>種明かしで書いた通り、MCP経由の利用が広がるほど、UIや機能の全体像に触れる機会は減っていきます。ここで役立つのが Datadogの認定プログラム です。現在は次の5つがあります。</p>
<ul>
<li>Datadog Fundamentals:プラットフォーム利用の基礎。Agentの設定やトラブルシューティング、データの可視化など</li>
<li>Log Management Fundamentals:ログの収集からパース、検索、分析まで</li>
<li>APM and Distributed Tracing Fundamentals:アプリケーションの計装と分散トレーシング</li>
<li>Datadog Cloud SIEM for AWS Fundamentals:AWS環境の脅威検知とインシデントレスポンス</li>
<li>Datadog Database Monitoring Fundamentals:DBモニタリングの構成とパフォーマンス分析</li>
</ul>
<p>ユーザ会の参加者には、この5資格を当たり前のようにコンプリートしている方が複数いて、刺激を受けました。試験のシラバスがDatadogの機能一覧を兼ねているので、MCP経由で使い始めたメンバーがUIを学ぶ順序の参考になります。うちのチームでも、それぞれの担当領域に近いFundamentalsから勧めるつもりです。</p>
<h2 id="おわりに">おわりに</h2><p>「活用事例はまた今度」という個人的な宿題を1年半越しに回収できました。運営の皆様、会場を提供いただいたクラスメソッド様、参加者の皆様、ありがとうございました。</p>
<p>次の宿題は、Bits AIも含めたその後の話でしょうか。今後とも、JDDUGコミュニティの皆様方、よろしくお願いします。</p>
]]></content>
    <summary type="html">Japan Datadog User Group Meetup#20@札幌で「AI時代のひとりSREのすすめ」というテーマで登壇しました。Datadog MCP Serverを軸に、運用もチーム連携もコンプラ対応もひとりで進められるようになった経緯を話しました。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="Datadog" scheme="https://future-architect.github.io/tags/Datadog/"/>
    <category term="MCP" scheme="https://future-architect.github.io/tags/MCP/"/>
    <category term="SRE" scheme="https://future-architect.github.io/tags/SRE/"/>
    <category term="オブサーバビリティ" scheme="https://future-architect.github.io/tags/%E3%82%AA%E3%83%96%E3%82%B5%E3%83%BC%E3%83%90%E3%83%93%E3%83%AA%E3%83%86%E3%82%A3/"/>
    <category term="登壇レポート" scheme="https://future-architect.github.io/tags/%E7%99%BB%E5%A3%87%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88/"/>
  </entry>
  <entry>
    <title>Mermaid Live Editor のTips 9選</title>
    <link href="https://future-architect.github.io/articles/20260612a/"/>
    <id>https://future-architect.github.io/articles/20260612a/</id>
    <published>2026-06-11T15:00:00.000Z</published>
    <updated>2026-06-11T15:00:00.000Z</updated>
    <author><name>真野隼記</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2026/20260612a/top.png" alt="" width="512" height="265">

<h2 id="はじめに">はじめに</h2><p>TIG（Technology Innovation Group）の真野です。</p>
<p>Mermaid.js で図を書こうと Mermaid Live Editor を開いたはいいものの、思いの外色々なボタンがあって機能を使いこなせない！と感じている人も多いのではないでしょうか。</p>
<p>そんな人に役立つかもしれない知識を、Tipsという形で9つまとめました。</p>
<h3 id="Mermaid-js-とは？">Mermaid.js とは？</h3><p>Mermaid.js はテキスト DSL から SVG で図を描画する JavaScript ライブラリです。2014年公開時点ではフローチャートとシーケンス図の2種類しかありませんでした。今ではフローチャート、シーケンス図、ER 図など、v11 時点で30種類近いダイアグラムに対応しています（ちなみに名前は『リトル・マーメイド』から取ったそうです）。</p>
<p>2022年2月に GitHub が Markdown の Mermaid ネイティブサポート を発表してから一気に広まった印象があり、現在は GitHub・GitLab・Notion などが標準対応しています。クライアントサイド JS 単独で動く身軽さと相まって、Diagrams as Code の中心的存在です。</p>
<p>同じく「図をテキストで書く」系としては PlantUML が先行しています。PlantUML は Java ベースで開発されていて、配置図・タイミング図・ユースケース図など UML 記号の網羅性が高いのが特徴です。ただしレンダリングにサーバサイド環境を要する分、GitHub や社内 Wiki にそのまま貼って読ませる気軽さは Mermaid の方がある、という棲み分けかなと思います。フューチャーでも過去に PlantUML 用のカラーテーマ（toy &#x2F; vibrant &#x2F; mars）を自作して公式テーマに採用された 経緯があり、社内では Mermaid.js も PlantUML もよく使われています。</p>
<h3 id="Mermaid-Live-Editor-とは？">Mermaid Live Editor とは？</h3><p>Mermaid Live Editor は、Mermaid コードを書くとリアルタイムでプレビューしてくれる公式ブラウザ IDE です。ログイン不要で、開けばすぐ使えます。</p>
<p>画面構成は以下のとおり。</p>
<ul>
<li>左ペイン：エディタ（<code>Code</code> &#x2F; <code>Config</code> &#x2F; <code>History</code> の3タブ）</li>
<li>右ペイン：プレビュー（<code>Diagram</code> &#x2F; <code>Actions</code> タブ）</li>
<li>図のコードは URL に埋め込まれるため、URL を共有すれば同じ図が他人の環境で開ける</li>
</ul>
<p>裏側は VS Code と同じ Monaco Editor ベースで、<code>Config</code> タブは独立した Monaco インスタンスです。これが後述するショートカット系Tipsに効いてきます。</p>
<h2 id="エディタを効率化する">エディタを効率化する</h2><h3 id="1-VS-Code-のショートカットがそのまま使える">1. VS Code のショートカットがそのまま使える</h3><p>Live Editor のエディタは VS Code と同じ Monaco Editor ベースなので、VS Code の感覚で使えるショートカットがそのまま効きます。個人的にヘビロテしているのは次の3つです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>ショートカット</th>
<th>動作</th>
</tr>
</thead>
<tbody><tr>
<td><code>Ctrl + Alt + ↑/↓</code>（macOS は <code>Cmd + Alt + ↑/↓</code>）</td>
<td>マルチカーソル編集（縦方向に行追加）</td>
</tr>
<tr>
<td><code>Shift + Alt + →/←</code></td>
<td>括弧 (<code>[]</code> &#x2F; <code>()</code> &#x2F; <code>&#123;&#125;</code>) 内のラベルを一気に選択</td>
</tr>
<tr>
<td><code>Ctrl + F</code> &#x2F; <code>Cmd + F</code></td>
<td>正規表現対応の検索・置換</td>
</tr>
</tbody></table></div>
<p>ガントチャートで全タスクの期間を後ろ倒しにしたい時や、シーケンス図で参加者名を一括リネームしたい時にマルチカーソルが効きます。クラス図の属性名リファクタでは、正規表現置換（例：<code>private string (\w+)Name</code> → <code>private string $1_name</code>）が刺さります。「マウスに手を伸ばさない編集」が図面の編集にもそのまま効くのは、地味ですが効果が大きいです。</p>
<h3 id="2-Config-タブでスタイルを本体コードから隔離する">2. Config タブでスタイルを本体コードから隔離する</h3><p>Live Editor の左ペイン <code>Config</code> タブに JSON を書くと、<code>theme</code> &#x2F; <code>themeVariables</code> といったスタイル設定を、コード本体の frontmatter とは分離してプレビューに反映できます。</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;theme&quot;</span><span class="punctuation">:</span> <span class="string">&quot;forest&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;themeVariables&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;primaryColor&quot;</span><span class="punctuation">:</span> <span class="string">&quot;#ffefd5&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<p>コード本体はこのままの素の状態で動きます。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line">flowchart LR</span><br><span class="line">  A[開始] --&gt; B[処理] --&gt; C[終了]</span><br></pre></td></tr></table></figure>

<p>frontmatter に <code>config:</code> を書くのと見た目の結果は同じです。</p>
<ul>
<li>コード本体と一緒に持ち回りたい → frontmatter に書く。移植性が高まる</li>
<li>Live Editor 内だけで良い → Config タブに書く。コード本体をスリムに見せることができる</li>
</ul>
<p>ちなみに、<code>config</code> を変更してもURLは反映されるので、単にMermaid Live Editorで閉じてやり取りする場合はどちらを使っても大差ないです。こだわりがなければ、私は移植性が高いfrontmatter記述が良いと思いますが、どうでしょうか？</p>
<h3 id="3-時計アイコンで履歴パネルを開きスナップショット保存">3. 時計アイコンで履歴パネルを開きスナップショット保存</h3><p>画面上部の GitHub アイコンの隣にある白黒の🕓️アイコンをクリックすると、履歴パネルが開きます。<code>Save Current State</code> を押すと現在の編集状態がスナップショットとして残り、あとで一覧から選んで戻せます。</p>
<p>大きな編集をするときは、<code>Save Current State</code> でスナップショットを残すか、Live Editor を別タブで開いてそちらで試すのがお勧めです。元タブの URL（<code>#pako:...</code>）にその時点のコードが保持されているので、別タブの試行が失敗しても元タブに戻ればすぐやり直せます。<code>Save Current State</code> の保存先はブラウザの <code>localStorage</code> なので、別PC・別ブラウザには引き継がれない点に注意してください。</p>
<p>同じ履歴パネルに <code>Save diagram</code> ボタンもありますが、こちらは Mermaid Chart のクラウドに保存するもので、Mermaid Chart アカウントへのログインが必要です。長期保管したいバージョンや環境を跨いで残したいバージョンは、ログインの上 <code>Save diagram</code> を使うか、GitHub 等の外部リポジトリで別途管理する運用になります。</p>
<h2 id="見た目を整える">見た目を整える</h2><h3 id="4-ダークモードでの見え方を気軽に確認する">4. ダークモードでの見え方を気軽に確認する</h3><p>Live Editor のプレビューエリア右下（白塗りの☀マーク &#x2F; 🌜️マーク）には、背景をライト&#x2F;ダークに切り替えるアイコンがあります。</p>
<p>Tip 2 のように <code>themeVariables</code> で独自スタイルを当てた時は、両モードで確認するクセをつけておくのが安全です。ライトモードでは視認性が良くなったが、ダークモードでは文字が読みにくいといったことも多いためです。特に技術ブログなど、外部公開系はどちらでも読めるようにしておくと、読者に優しいです。</p>
<h3 id="5-スクラッチモードを-Live-Editor-の外でも使う">5. スクラッチモードを Live Editor の外でも使う</h3><p>Live Editor のプレビューエリアの下部に、図を手書き風に描画してくれる <code>Sketch</code> モードの切替ボタン（✏️のようなボタン）があります。手書き風の方が頭に入りやすい気がするので、よく使いたくなります。</p>
<p>ただし Live Editor 上で <code>Sketch</code> を有効にしただけでは、コードを他ツールに貼り付けても手書き風は再現されません。<code>Sketch</code> はプレビュー側の描画オプションとして効いているだけで、コード本体には何も書き込まれないためです。</p>
<p>GitHub &#x2F; Qiita &#x2F; 自社サイトなどで同じ見た目を再現したい場合は、frontmatter の <code>config.look</code> に <code>handDrawn</code> を明示します。</p>
<pre class="mermaid" data-mermaid="e60ca848a00c7f567d71e82cb8ef900df74d67d0191be895d76b2e5504d84d95">---
config:
  look: handDrawn
---
flowchart LR
  A[開始] --> B[処理] --> C[終了]</pre>

<figure class="highlight c"><table><tr><td class="code"><pre><span class="line">---</span><br><span class="line">config:</span><br><span class="line">  look: handDrawn</span><br><span class="line">---</span><br><span class="line">flowchart LR</span><br><span class="line">  A[開始] --&gt; B[処理] --&gt; C[終了]</span><br></pre></td></tr></table></figure>

<p><code>look: handDrawn</code> は Mermaid v11 でサポートされた設定で、Rough.js ベースの揺れた線で図が描画されます。Live Editor の <code>Sketch</code> ボタンをオンにした時と同じ見た目が、コードブロックを貼り付けた先でも再現されるようになります。</p>
<h2 id="共有・公開のTips">共有・公開のTips</h2><h3 id="6-実は読み取り専用ビューを作れる">6. 実は読み取り専用ビューを作れる</h3><p>Live Editor の共有URLには、実は2種類あります。</p>
<ul>
<li><code>https://mermaid.live/edit#pako:...</code>：エディタUI付きで開く（デフォルト）</li>
<li><code>https://mermaid.live/view#pako:...</code>：プレビューだけの読み取り専用ビュー</li>
</ul>
<p><code>#pako:...</code> 部分のハッシュは同一なので、URL のパスを <code>edit</code> &#x2F; <code>view</code> で書き換えるだけで切り替わります。<code>edit</code> はエディタUI付きで編集可能、<code>view</code> は読み取り専用でレンダリング済みの図だけが表示されます。</p>
<p><code>Share</code> ボタンから取得できるのは <code>edit</code> 系 URL のみで、<code>view</code> URL は UI 上には出てきません。コピーした URL のパス部分を手動で <code>view</code> に書き換える必要があります。</p>
<h3 id="7-URL-ハッシュの正体は-pako-URL-safe-base64">7. URL ハッシュの正体は pako + URL-safe base64</h3><p>共有URLの <code>#pako:...</code> の正体を追っておきます。Live Editor のserde.ts を読むと、以下の処理が行われています。</p>
<ol>
<li>図のコードと設定を JSON 化</li>
<li>pako で deflate 圧縮（level 9）</li>
<li>URL-safe base64 化（<code>+</code> → <code>-</code>、<code>/</code> → <code>_</code>）</li>
<li>先頭に <code>pako:</code> プレフィックスを付ける</li>
</ol>
<p>これが分かると、Live Editor の UI を使わずに共有URLをプログラム生成できます。Python なら標準ライブラリだけで書けます。</p>
<div class="code-block"><figure class="highlight python"><input type="checkbox" id="code-wrap-1j71nzz-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1j71nzz-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> base64</span><br><span class="line"><span class="keyword">import</span> json</span><br><span class="line"><span class="keyword">import</span> zlib</span><br><span class="line"></span><br><span class="line">state = &#123;</span><br><span class="line">    <span class="string">&quot;code&quot;</span>: <span class="string">&quot;flowchart LR\n  A[開始] --&gt; B[処理] --&gt; C[終了]&quot;</span>,</span><br><span class="line">    <span class="string">&quot;mermaid&quot;</span>: <span class="string">&#x27;&#123;&quot;theme&quot;: &quot;default&quot;&#125;&#x27;</span>,</span><br><span class="line">    <span class="string">&quot;autoSync&quot;</span>: <span class="literal">True</span>,</span><br><span class="line">    <span class="string">&quot;updateDiagram&quot;</span>: <span class="literal">True</span>,</span><br><span class="line">&#125;</span><br><span class="line">compressed = zlib.compress(json.dumps(state).encode(), <span class="number">9</span>)</span><br><span class="line">encoded = base64.urlsafe_b64encode(compressed).decode().rstrip(<span class="string">&quot;=&quot;</span>)</span><br><span class="line"><span class="built_in">print</span>(<span class="string">f&quot;https://mermaid.live/view#pako:<span class="subst">&#123;encoded&#125;</span>&quot;</span>)</span><br></pre></td></tr></table></figure></div>

<p>社内ツールで CSV → 図 → Live Editor URL という一括変換パイプラインを組もうと思えば組めそうです。pakoってなんだろう思ってましたが、ライブラリ名だったとは。</p>
<h2 id="Sample-Diagrams-を覗く">Sample Diagrams を覗く</h2><h3 id="8-Sample-Diagrams-にある-ZenUML-とは？">8. Sample Diagrams にある ZenUML とは？</h3><p>Live Editor 上部の <code>Sample Diagrams</code> ドロップダウンを開くと、フローチャートやシーケンス図などに並んで <code>ZenUML</code> という見慣れない選択肢が入っています。個人的に以前から気になっていたので、この機会に中身を覗いてみました。</p>
<p>ZenUML は Mermaid v10 から統合されたシーケンス図専用の別 DSL で、標準の <code>sequenceDiagram</code> と並ぶ選択肢として Live Editor に同梱されています（公式ドキュメントの ZenUML ページ）。コードの1行目を <code>zenuml</code> にするだけで切り替わります。</p>
<p>標準 <code>sequenceDiagram</code>：</p>
<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">sequenceDiagram</span><br><span class="line">  Client-&gt;&gt;API: POST /order</span><br><span class="line">  API-&gt;&gt;DB: INSERT order</span><br><span class="line">  DB--&gt;&gt;API: ok</span><br><span class="line">  alt stock &gt; 0</span><br><span class="line">    API-&gt;&gt;Queue: publish</span><br><span class="line">    API--&gt;&gt;Client: 201 Created</span><br><span class="line">  else</span><br><span class="line">    API--&gt;&gt;Client: 409 Conflict</span><br><span class="line">  end</span><br></pre></td></tr></table></figure>

<p>ZenUML：</p>
<figure class="highlight text"><table><tr><td class="code"><pre><span class="line">zenuml</span><br><span class="line">  title 注文フロー</span><br><span class="line">  Client-&gt;API.createOrder() &#123;</span><br><span class="line">    API-&gt;DB.insert()</span><br><span class="line">    if (stock &gt; 0) &#123;</span><br><span class="line">      API-&gt;Queue.publish()</span><br><span class="line">      return 201</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      return 409</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>標準のシーケンス図が素直に上から下へ流れる記法なのに対し、ZenUML は Java &#x2F; C# の関数呼び出しに近い中括弧ベースで、条件分岐・並列処理のネストが中に折り畳まれる形になります。<code>alt ... else ... end</code> で縦に伸びていく標準記法より、深いネストの構造が把握しやすい気がします。<code>while</code> &#x2F; <code>par</code> &#x2F; <code>try-catch</code>、<code>@Async</code> &#x2F; <code>@Starter</code> などのアノテーションもあって、非同期フローの表現力でも ZenUML が優位です。</p>
<p>ただし現時点では大きな落とし穴があります。（※2026年5月時点）手元で試した限り、GitHub Markdown も Qiita も ZenUML コードブロックを描画できませんでした（各プラットフォームが内蔵する Mermaid レンダラーが ZenUML 統合のバージョンにまだ追いついていないのが原因のようです）。Mermaid 最大の旨味である「コードブロックをそのまま貼って読ませる」気軽さが失われるので、現時点では ZenUML の採用は見送り、標準の <code>sequenceDiagram</code> を使うのが無難、というのが実際に試してみての結論です。</p>
<h2 id="セキュアに使う">セキュアに使う</h2><h3 id="9-Actions-タブの-Export-系ボタンは外部サービスに図コードを送信している">9. Actions タブの Export 系ボタンは外部サービスに図コードを送信している</h3><p><code>Actions</code> タブには <code>Copy Image</code> &#x2F; <code>PNG</code> &#x2F; <code>SVG</code> &#x2F; <code>PDF</code> &#x2F; <code>Copy Markdown</code> といった便利なボタンが並んでいます。Mermaid にネイティブ対応していない社内 Wiki やメール本文に図を貼りたい時に刺さる機能ですが、実際には Live Editor 本体ではなく外部サービスの mermaid.ink や Kroki にコードを送信してサーバサイドでレンダリングしています。</p>
<pre class="mermaid" data-mermaid="4a7239f39132dc4c7808f373bf0af81fdf9966898530678a38cf580503b2349f">flowchart LR
  User(("👤<br/>ユーザー"))
  subgraph browser["ブラウザ内（編集・プレビューはここで完結）"]
    Editor[Live Editor]
  end
  subgraph external["外部サーバ"]
    Ink[mermaid.ink / Kroki]
  end
  User -->|①編集・プレビュー| Editor
  User -->|②Export ボタン押下| Editor
  Editor -->|③図コードを送信| Ink
  Ink -->|④画像 or 画像URL| Editor
  Editor -->|⑤ダウンロード / クリップボード| User</pre>

<p>プレビューしているだけならブラウザ内で完結していますが、Export ボタンを押した瞬間にコードが外に出る、という構造です。<code>Copy Markdown</code> はクリップボードに以下のような形を返してくれます。画像 URL をクリックすると Live Editor の編集画面に戻れる気の利いた作りで、Mermaid 非対応の Wiki・メールに貼るぶんには便利です。</p>
<div class="code-block"><figure class="highlight markdown"><input type="checkbox" id="code-wrap-1j71nzz-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1j71nzz-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">[<span class="string">![</span>](<span class="link">https://mermaid.ink/img/pako:eNp...</span>)](<span class="link">https://mermaid.live/edit#pako:eNp...</span>)</span><br></pre></td></tr></table></figure></div>

<p>便利な反面、社外秘の情報を扱うなど固くしたい場合は、これらのボタンを押さないといったチーム方針も考えられます。逆に技術ブログなど最初から公開前提の図であれば、mermaid.ink の URL にクエリパラメータを足して画像フォーマットや解像度の調整もできます。パラメータの詳細は mermaid.ink の README を参照してください。</p>
<p>完全に閉じた環境で図を画像化したい場合は、CLI の <code>mmdc</code>（mermaid-cli）をローカルで回すのが現実的です。</p>
<p>おまけで、Mermaid Chart アカウント（無料）でログインすると、シンタックスエラー時にプレビューエリアに <code>Fix with AI</code> ボタンが表示され、LLM に修正案を返してもらえる機能も使えます。GeminiやClaudeなどに直してもらう人が多いと思いますので、利用したいモチベーションは低いかと思いますが、これも Mermaid Chart の LLM 基盤にコードを送信する仕組みです。そのため、Export 系と同じく社外秘の図では使わない方針が固いでしょう。</p>
<h2 id="おわりに">おわりに</h2><p>毎日のように開いている Live Editor ですが、改めて整理してみると意外と触れていない機能がまだまだある、というのが書き終えての率直な感想です。</p>
<p>特に外部データ送信については、よく考えればその通りなのですが、きちんと考えたことはなかったので調べる過程で勉強になりました。</p>
]]></content>
    <summary type="html">Mermaid.js で図を書こうと Mermaid Live Editor を開いたはいいものの、思いの外色々なボタンがあって機能を使いこなせない！と感じている人も多いのではないでしょうか。そんな人に役立つかもしれない知識を、Tipsという形で9つまとめました。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="Mermaid.js" scheme="https://future-architect.github.io/tags/Mermaid-js/"/>
    <category term="Tips" scheme="https://future-architect.github.io/tags/Tips/"/>
  </entry>
  <entry>
    <title>2026年2月版: Dev Containersでプチはまり(Node.js 24とPostgreSQL 18とプロキシ)</title>
    <link href="https://future-architect.github.io/articles/20260213a/"/>
    <id>https://future-architect.github.io/articles/20260213a/</id>
    <published>2026-02-12T15:00:00.000Z</published>
    <updated>2026-02-12T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<p>Dev Containers便利ですよね？使っていますか？便利なのですが、久々に新しい環境を作ろうとしてちょっとはまったので解決のメモを書いておきます。書いておけばそのうち生成AIが拾って簡単に解決できるようになると思うので。</p>
<h2 id="Node-jsのイメージがおかしい">Node.jsのイメージがおかしい</h2><p>Node.js + PostgreSQLのベース設定をもとに環境を作ると、いきなり起動時に失敗します。Node.js 22であれば問題ないのですが、現在アクティブなサポートバージョンの24だとエラーになります。コンテナイメージのタグの命名規則が変わったようです。</p>
<p>ついでにdebianも最新のtrixieに変えましょう。</p>
<div class="code-block"><figure class="highlight diff"><input type="checkbox" id="code-wrap-i9la4n-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-i9la4n-1" title="コードの折り返しを切り替える"></label><figcaption><span>Dockerfile</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="deletion">- FROM mcr.microsoft.com/devcontainers/javascript-node:1-24-bookworm</span></span><br><span class="line"><span class="addition">+ FROM mcr.microsoft.com/devcontainers/javascript-node:dev-24-trixie</span></span><br></pre></td></tr></table></figure></div>

<h2 id="同一のWSLで複数のPostgreSQLを使うDev-Containersを利用するとエラー">同一のWSLで複数のPostgreSQLを使うDev Containersを利用するとエラー</h2><p>これ、わかりにくかったのですが、DBに依存しているアプリケーション開発環境の方が起動できない（参加したいネットワーク service.dbがない）という感じのエラーなのですが、よくよく見てみると、DBがエラーで再起動を続けています。</p>
<p>以前はPostgreSQL 17を使っていたのですが、今回18にしてみたら(docker-compose.ymlのタグを書き換えた)らエラーが発生しました。どうも17と18はデータの配置のルールが変わり、なおかつデータの置き場（ボリュームのマウントポイント）も<code>/var/lib/postgresql/data</code>から、<code>/var/lib/postgresql</code>に変わったようです。</p>
<figure class="highlight diff"><figcaption><span>docker-compose.yml</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="deletion">- version: 3.8</span></span><br><span class="line">  service:</span><br><span class="line">  :</span><br><span class="line">    db:</span><br><span class="line"><span class="deletion">-     image: postgres:17</span></span><br><span class="line"><span class="addition">+     image: postgres:18</span></span><br><span class="line">      restart: unless-stopped</span><br><span class="line">      volumes:</span><br><span class="line"><span class="deletion">-      - postgres-data:/var/lib/postgresql/data</span></span><br><span class="line"><span class="addition">+      - postgres-data:/var/lib/postgresql</span></span><br><span class="line">      environment:</span><br><span class="line">        POSTGRES_PASSWORD: postgres</span><br><span class="line">        POSTGRES_USER: postgres</span><br><span class="line">        POSTGRES_DB: postgres</span><br></pre></td></tr></table></figure>

<h2 id="サービス名も重複しない名前にしておく">サービス名も重複しない名前にしておく</h2><p>アプリ名がぶつかるのもよくないということを聞きましたので、そうなるとサービス名とかも変えておく方が良いですね。</p>
<figure class="highlight diff"><table><tr><td class="code"><pre><span class="line">  services:</span><br><span class="line"><span class="deletion">-   app:</span></span><br><span class="line"><span class="addition">+   myproj-app:</span></span><br><span class="line">      build:</span><br><span class="line">        context: .</span><br><span class="line">        dockerfile: Dockerfile</span><br><span class="line"><span class="deletion">-     network_mode: service:db</span></span><br><span class="line"><span class="addition">+     network_mode: service:myproj-db</span></span><br><span class="line"></span><br><span class="line"><span class="deletion">-   db:</span></span><br><span class="line"><span class="addition">+   myproj-db:</span></span><br><span class="line">      image: postgres:18</span><br><span class="line">      restart: unless-stopped</span><br></pre></td></tr></table></figure>

<h2 id="プロキシ">プロキシ</h2><p>プロキシ突破はコツがわからないと苦労しがちです。一般的なDockerのプロキシ設定であれば、以下の当ブログの記事で書かれたGUIの設定の内容で十分でしょう。</p>
<ul>
<li>ProxyとDockerと新人社員と時々わたし</li>
</ul>
<p>しかし、特にDockerをDev Containersで使う場合は、通常のイメージ作成で使う</p>
<ul>
<li>イメージ取得</li>
<li>ビルド時</li>
</ul>
<p>に加えて、</p>
<ul>
<li>通常ユーザーの実行時(npm installやgo get)</li>
<li>sudoの実行時(apt-get)</li>
</ul>
<p>と4パターンの通信を扱う必要があり、それぞれ別に設定が必要だったりするためにさらにややこしいです。以前、当ブログで紹介した記事ではあっさり紹介しましたが、今回、まっさらな環境からDev Containersを作るスクリプトを書いてみて、いろいろ試行錯誤したのでその知見も紹介します。</p>
<p>Dev Containersは、.devcontainer&#x2F;devcontainer.jsonが設定の大本です。ここで開発環境のDockerfile、もしくは既成のイメージを選択します。PostgreSQLなどのDBも併用する場合はcompose.yamlを間に挟むこともあります。プロキシを通過する場合はさまざまなこれらの設定ファイルに記述していく必要があります。その相関関係を記したのが以下の図です。</p>
<img fetchpriority="high" src="/images/2026/20260213a/image.png" alt="image.png" width="484" height="488">

<p>以下の3つのレイヤーの設定について紹介していきます。</p>
<ul>
<li>ホスト環境(イメージ取得、ビルド時のapt-getなどの通信)</li>
<li>devcontainer.json(実行環境の通信)</li>
<li>Dockerfile(sudoの通信)</li>
</ul>
<h3 id="ホスト環境-イメージ取得、ビルド時のapt-getなどの通信">ホスト環境(イメージ取得、ビルド時のapt-getなどの通信)</h3><p>まず、Dockerが動くホスト環境でDockerのプロキシ設定および、環境変数を指定します。条件によって組み合わせが複雑なので要注意です。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left">環境</th>
<th align="center">イメージ取得</th>
<th align="center">ビルド内通信</th>
<th align="center">環境変数</th>
</tr>
</thead>
<tbody><tr>
<td align="left">Windows版Dockerデスクトップ</td>
<td align="center">(1)</td>
<td align="center">(1)</td>
<td align="center">(5)</td>
</tr>
<tr>
<td align="left">Linux単独</td>
<td align="center">(2)</td>
<td align="center">(4)</td>
<td align="center">(6)</td>
</tr>
<tr>
<td align="left">WSL2+Linux版Docker</td>
<td align="center">(3)</td>
<td align="center">(4)</td>
<td align="center">(5)</td>
</tr>
</tbody></table></div>
<p>個々の設定が終わったあとの確認方法としては、これらを設定すれば、ホスト環境の中でdocker pullやcurlコマンドで外につなぎにいけるはずなので、Dev Containersの起動前に試してみましょう。また、Dockerfileの中の外部通信(apt-getなど)もいけるはず。</p>
<h4 id="1-Dockerデスクトップのプロキシ設定">(1): Dockerデスクトップのプロキシ設定</h4><p>Dockerデスクトップの場合はGUIで設定ができます。これを設定することで、イメージ取得やビルド内の通信の両方でプロキシが取った状態で通信ができます。</p>
<h4 id="2-Linuxのイメージ取得用の設定">(2): Linuxのイメージ取得用の設定</h4><p>Dockerコマンド自体はデーモンへのリクエストだけを行い、実際のビルドやイメージ取得はデーモンが行います。そのデーモンに対しては専用の設定ファイルがあります。</p>
<div class="code-block"><figure class="highlight toml"><input type="checkbox" id="code-wrap-i9la4n-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-i9la4n-2" title="コードの折り返しを切り替える"></label><figcaption><span>/etc/systemd/system/docker.service.d/http-proxy.conf</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="section">[Service]</span></span><br><span class="line"><span class="attr">Environment</span>=<span class="string">&quot;HTTP_PROXY=http://proxy.example.com:8080/&quot;</span></span><br><span class="line"><span class="attr">Environment</span>=<span class="string">&quot;HTTPS_PROXY=http://proxy.example.com:8080/&quot;</span></span><br><span class="line"><span class="attr">Environment</span>=<span class="string">&quot;NO_PROXY=localhost,127.0.0.1,docker-registry.somecorporation.com&quot;</span></span><br></pre></td></tr></table></figure></div>

<p>修正したらデーモンを再起動します。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">sudo</span> system docker restart</span><br></pre></td></tr></table></figure>

<h4 id="3-WSL2-Linuxのイメージ取得の設定">(3): WSL2+Linuxのイメージ取得の設定</h4><p>WSL2の中でLinux版のDockerを使っている場合、現行のWSL2であればは自動でWindowsのプロキシを設定する機能(autoProxy)があります。Windows側設定が適切であればイメージ取得は通るようになります。裏のネットワークインターフェース側で解決してくれるようです。デフォルトでtrueになっていますが、明示的に書きたい場合は次のように設定します。</p>
<figure class="highlight toml"><figcaption><span>$HOME/.wslconfig</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="section">[wsl2]</span></span><br><span class="line"><span class="attr">dnsTunneling</span>=<span class="literal">true</span></span><br><span class="line"><span class="attr">autoProxy</span>=<span class="literal">true</span></span><br></pre></td></tr></table></figure>

<h4 id="4-LinuxのDockerの設定">(4): LinuxのDockerの設定</h4><p>ビルド時にプロキシ情報の環境変数を渡す方法には、–build-argでコマンドで渡す方法と設定ファイルで渡す方法があります。VSCodeのDev Containersのビルドの中の触れない場所で呼ばれてオプションが追加できないため、ファイルで渡す方法しかないでしょう。</p>
<div class="code-block"><figure class="highlight json"><input type="checkbox" id="code-wrap-i9la4n-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-i9la4n-3" title="コードの折り返しを切り替える"></label><figcaption><span>$HOME/.docker/config.json</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line"> <span class="attr">&quot;proxies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">   <span class="attr">&quot;default&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">     <span class="attr">&quot;httpProxy&quot;</span><span class="punctuation">:</span> <span class="string">&quot;http://proxy.example.com:8080&quot;</span><span class="punctuation">,</span></span><br><span class="line">     <span class="attr">&quot;httpsProxy&quot;</span><span class="punctuation">:</span> <span class="string">&quot;http://proxy.example.com:8080&quot;</span><span class="punctuation">,</span></span><br><span class="line">     <span class="attr">&quot;noProxy&quot;</span><span class="punctuation">:</span> <span class="string">&quot;*.test.com,localhost,127.0.0.1&quot;</span></span><br><span class="line">   <span class="punctuation">&#125;</span></span><br><span class="line"> <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></div>

<p>Dockerfile内で直接書いてしまう方法もありますが、ポータビリティも下がりますし、認証情報が入る可能性があるため、お勧めしません。</p>
<h4 id="5-環境変数の設定-WSL2の場合">(5): 環境変数の設定(WSL2の場合)</h4><p>デスクトップ版はWSL2を使います。また、WSL2+Linux版の組み合わせでもWSL2になります。Dev Containerに渡すにはWSL2の中のLinuxの環境変数にプロキシ設定がある状態にしておく必要があります。</p>
<p>設定方法としては、2つあり、まずはWindowsの環境変数にhttp_proxy, https_proxy, no_proxyなどを設定するとともに、次の環境変数も一緒に設定する方法です。</p>
<p><code>WSLENV=&quot;http_proxy/u:https_proxy/u:no_proxy/u&quot;</code></p>
<p>これらで指定した環境変数がWSL2の中にも取り込まれます。それ以外としては次のLinux単独の環境変数設定方法も使えます。</p>
<h4 id="6-環境変数の設定-Linuxの場合">(6): 環境変数の設定(Linuxの場合)</h4><p>WSL2ではないLinuxの場合は、いつも通り、ユーザーの.profileとか.bashrcといったところに環境変数でプロキシ設定を書きます。</p>
<div class="code-block"><figure class="highlight bash"><input type="checkbox" id="code-wrap-i9la4n-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-i9la4n-4" title="コードの折り返しを切り替える"></label><figcaption><span>.bashrc</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="built_in">export</span> http_proxy=<span class="string">&quot;http://proxy.example.com:8080&quot;</span></span><br><span class="line"><span class="built_in">export</span> https_proxy=<span class="string">&quot;http://proxy.example.com:8080&quot;</span></span><br><span class="line"><span class="built_in">export</span> no_proxy=<span class="string">&quot;localhost,127.0.0.1,host.docker.internal&quot;</span></span><br></pre></td></tr></table></figure></div>

<h3 id="devcontainer-json-実行環境の通信">devcontainer.json(実行環境の通信)</h3><p>devcontainer.jsonの中でホスト側の環境変数を実行時に展開してくれる機能があるのでこれを利用します。<code>docker run -e 環境変数=値</code>で渡すのと同じですが、元の変数も参照として書けるため、設定ファイルをプロジェクトで共有する場合にもハードコード不要で安全です。</p>
<figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">	<span class="attr">&quot;remoteEnv&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">		<span class="attr">&quot;HTTPS_PROXY&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;localEnv:https_proxy&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">		<span class="attr">&quot;https_proxy&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;localEnv:https_proxy&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">		<span class="attr">&quot;HTTP_PROXY&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;localEnv:http_proxy&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">		<span class="attr">&quot;http_proxy&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;localEnv:http_proxy&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;NO_PROXY&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;localEnv:no_proxy&#125;&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;no_proxy&quot;</span><span class="punctuation">:</span> <span class="string">&quot;$&#123;localEnv:no_proxy&#125;&quot;</span></span><br><span class="line">	<span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>

<p>これで、<code>https_proxy</code>環境変数などを参照するnpmやgo getなどは正しく動きます。</p>
<p>なお、証明書のエラーが出る場合は証明書の検証をやめるオプションを定義して回避もできますが、可能であれば証明書を入れる方がましです。Dockerfileに以下の行を追加しましょう。</p>
<div class="code-block"><figure class="highlight dockerfile"><input type="checkbox" id="code-wrap-i9la4n-5" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-i9la4n-5" title="コードの折り返しを切り替える"></label><figcaption><span>Dockerfile</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">COPY</span><span class="language-bash"> my-company-ca.crt /usr/local/share/ca-certificates/my-company-ca.crt</span></span><br></pre></td></tr></table></figure></div>

<h3 id="Dockerfile-sudoの通信">Dockerfile(sudoの通信)</h3><p>これまでのところでほぼ問題ありませんが、開発環境の中でapt-getで追加のツールなどを入れようとするとエラーになります。devcontainer.jsonは、作業ユーザーの環境変数には設定してくれますが、sudo apt-getとすると、一時的に環境変数が設定されていない別ユーザーに代わってしまい、設定が見えなくなってしまうからです。</p>
<p>sudoしても必要な環境変数を引き継ぐような設定をDockerfileに足してあげることで、自由にapt-getできるようになります。</p>
<div class="code-block"><figure class="highlight dockerfile"><input type="checkbox" id="code-wrap-i9la4n-6" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-i9la4n-6" title="コードの折り返しを切り替える"></label><figcaption><span>Dockerfile</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">echo</span> <span class="string">&#x27;Defaults env_keep += &quot;http_proxy https_proxy HTTP_PROXY HTTPS_PROXY no_proxy NO_PROXY&quot;&#x27;</span> &gt;&gt; /etc/sudoers</span></span><br></pre></td></tr></table></figure></div>

<h3 id="macOSの場合">macOSの場合</h3><p>macOSの場合はWSL2のレイヤーを意識する必要性がないのでシンプルです。Linux VMは確かにありますが、Windowsと違ってパフォーマンスのためにWSL2の中のファイルシステムに環境を作ってWSL2の中からVSCodeを起動するということがないからです。</p>
<p>Docker Desktop版を使うなら、そちらのプロキシ設定とmacOSの一般の環境変数にhttp_proxyなどを設定すればおしまいです。Linux VMの中にわざわざ作るのであれば同じように設定は必要になると思いますが。</p>
<h2 id="まとめ">まとめ</h2><p>Dev Containersは便利ですが、若さにあふれているというか、安定性よりも新しいものを、という気概を感じます。そこそこ小さい苦労があったので、メモ代わりに書いてみました。もし、ほかにもこんな問題もあったぞとかがあれば、Xなどでお知らせいただければと思います。</p>
]]></content>
    <summary type="html">Dev Containers便利ですよね？使っていますか？便利なのですが、久々に新しい環境を作ろうとしてちょっとはまったので解決のメモを書いておきます。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="Dev Containers" scheme="https://future-architect.github.io/tags/Dev-Containers/"/>
    <category term="Node.js" scheme="https://future-architect.github.io/tags/Node-js/"/>
    <category term="PostgreSQL" scheme="https://future-architect.github.io/tags/PostgreSQL/"/>
    <category term="プロキシ" scheme="https://future-architect.github.io/tags/%E3%83%97%E3%83%AD%E3%82%AD%E3%82%B7/"/>
  </entry>
  <entry>
    <title>ログ設計ガイドラインを公開しました</title>
    <link href="https://future-architect.github.io/articles/20260210a/"/>
    <id>https://future-architect.github.io/articles/20260210a/</id>
    <published>2026-02-09T15:00:00.000Z</published>
    <updated>2026-02-09T15:00:00.000Z</updated>
    <author><name>八木雅斗</name></author>
    <content type="html"><![CDATA[
<img fetchpriority="high" src="/images/2026/20260210a/top.jpg" alt="" width="800" height="458">


<h2 id="はじめに">はじめに</h2><p>Technology Innovation Groupの八木です。</p>
<p>フューチャー社内の有志メンバーでログ設計ガイドラインを作成し公開しました！</p>
<p>ログは、システムの稼働状況を可視化し、トラブルが発生した際に迅速に原因特定するための生命線になります。しかし、その重要性の一方で、プロジェクトごとに設計がバラバラになりがちだったり、とりあえず標準出力しているだけになっていたりと、十分に活用しきれていないケースも多く見受けられます。</p>
<p>本記事では、今回公開したログ設計ガイドラインの背景や、現場で役立つ設計のポイントを抜粋してご紹介します。</p>
<h2 id="ガイドライン作成のモチベーション">ガイドライン作成のモチベーション</h2><p>これまで、ログ設計は個々のエンジニアの経験則や、プロジェクトごとの慣習に委ねられることが多くありました。しかし、システムが複雑化し、マイクロサービスやクラウドネイティブな構成が当たり前になった現代において、ログの役割は「単なるデバッグ用のテキスト」から「オブザーバビリティ（可観測性）の基盤」へと進化しています。</p>
<p>ログ設計が不十分だと、以下のような課題が発生し得ます。</p>
<ol>
<li><strong>原因追求ができない</strong>: 必要な情報（トレースIDやユーザーIDなど）が不足しており、エラーの原因を追えない</li>
<li><strong>横断的な集計ができない</strong>: フォーマットがバラバラで、CloudWatch Logs や Datadog などのツールで検索・集計がしにくい</li>
<li><strong>コストと性能</strong>: 無意味なログが大量に出力され、ストレージ費用を圧迫したり、アプリケーションの性能を劣化させる</li>
</ol>
<h2 id="ガイドラインのポイント紹介">ガイドラインのポイント紹介</h2><p>ログ設計ガイドラインでは、アプリケーションログを対象に命名規則から出力項目、セキュリティ、コストまで幅広くカバーしています。その中から主なトピックをいくつかピックアップします。</p>
<h3 id="1-ログキー命名規則">1. ログキー命名規則</h3><p>特定のプラットフォーム（AWS&#x2F;GCP）やSaaS（Datadog&#x2F;New Relic）に依存しすぎない移植性の高いログにするため、標準仕様といえるOpenTelemetry (OTel) Semantic ConventionsやElastic Common Schema (ECS)をベースにした命名規則を推奨しています。</p>
<ul>
<li><strong>標準化のメリット</strong>: timestamp や @timestamp といった些細な表記揺れを排除し、ログ解析ツールで利用しやすくする</li>
<li><strong>共通スキーマ</strong>: <code>timestamp</code>, <code>severity.text</code>, <code>message</code>, <code>trace_id</code> といった、どのログにも含めるべき必須項目を定義する</li>
</ul>
<h3 id="2-コンテキスト情報の付与">2. コンテキスト情報の付与</h3><p>エラーが発生した際、「何が起きたか（メッセージ）」だけでは原因特定に至らないことが多々あります。「どのユーザーが」「どのリクエストで」起こしたのかという<strong>コンテキスト情報</strong>を付与することで、調査の解像度を高めます。</p>
<ul>
<li><strong>トレースID</strong>：マイクロサービスなどの分散システムにおいて、サービス間をまたぐ一連の処理を紐付けるためのIDです。これを全ログに含めることで、点在するログを追跡可能にします</li>
<li><strong>HTTP&#x2F;DBコンテキスト</strong>：リクエストメソッドやパス、ステータスコード、DBクエリの実行時間といった情報を「拡張スキーマ」として定義します。これにより、「特定のAPIだけレスポンスが遅い」「特定のクエリでエラーが頻発している」といった多角的な分析が可能になります</li>
</ul>
<h3 id="3-出力ルールとメッセージの書き方">3. 出力ルールとメッセージの書き方</h3><p>意外と疎かにされがちなログメッセージ自体の書き方について紹介しています。</p>
<ul>
<li><strong>事実とデータの分離</strong>: メッセージ内に可変値を入れる（例. <code>User login failed: &#123;userId&#125;</code>） ではなく、メッセージ自体は <code>User login failed</code> と固定し、user.id を構造化データの別フィールドとして分離します。これにより、ログの集計が楽になります</li>
<li><strong>メッセージコードの導入</strong>: エラーログを運用者が参照する「運用手順書（障害対応マニュアル）」と紐づけるために、一意のメッセージコード（例：E001）を付与するパターンを紹介しています</li>
<li><strong>「通知フラグ」の考え方</strong>: 全てのエラーを即座に通報するのではなく、ログに「通知の是非」のフラグを持たせる設計についても言及しています</li>
</ul>
<h3 id="4-セキュリティと機密情報">4. セキュリティと機密情報</h3><p>ログに含めてはいけない情報（個人情報、パスワード、認証トークン等）の管理と、誤って出力しないためのマスキングの考え方についてもガイドを設けています。</p>
<h2 id="ガイドライン活動後の感想">ガイドライン活動後の感想</h2><p>私自身、これまでログ設計についてあまり時間をかける機会がありませんでした。しかし、今回のガイドライン活動を通して、今まで慣習として従っていたキー名や構造にもOTel Semantic Conventionsといった標準が存在することや、効率的に開発&#x2F;運用するための考え方を知ることができました。</p>
<p>加えて、活動内でシニアな方々と議論することで、なぜそのような設計なのかというWhyの部分も学ぶことができたことも大きな収穫でした。</p>
<p>今後は自らのプロジェクトでこれを実践し、開発&#x2F;運用者が幸せになれるシステム作りに貢献したいと思います。</p>
<h2 id="おわりに">おわりに</h2><p>ログは「出力して終わり」ではなく、「トラブル時にいかに迅速に原因を特定できるか」という運用のための資産になります。</p>
<p>今回公開したガイドラインが、皆さんのプロジェクトにおける保守・運用しやすいシステム作りの一助となれば幸いです。</p>
<p>また、ガイドラインを読んでのフィードバックやGitHubでのIssue&#x2F;PRもお待ちしております！</p>
<p>ログ設計ガイドライン:</p>
<ul>
<li>https://future-architect.github.io/arch-guidelines/documents/forLog/log_guidelines.html</li>
</ul>
]]></content>
    <summary type="html">フューチャー社内の有志メンバーでログ設計ガイドラインを作成し公開しました！ログは、システムの稼働状況を可視化し、トラブルが発生した際に迅速に原因特定するための生命線になります。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="オブサーバビリティ" scheme="https://future-architect.github.io/tags/%E3%82%AA%E3%83%96%E3%82%B5%E3%83%BC%E3%83%90%E3%83%93%E3%83%AA%E3%83%86%E3%82%A3/"/>
    <category term="ガイドライン" scheme="https://future-architect.github.io/tags/%E3%82%AC%E3%82%A4%E3%83%89%E3%83%A9%E3%82%A4%E3%83%B3/"/>
    <category term="ログ" scheme="https://future-architect.github.io/tags/%E3%83%AD%E3%82%B0/"/>
    <category term="構造化ログ" scheme="https://future-architect.github.io/tags/%E6%A7%8B%E9%80%A0%E5%8C%96%E3%83%AD%E3%82%B0/"/>
  </entry>
  <entry>
    <title>Japan Datadog User Group Meetup#14@福岡に登壇しました</title>
    <link href="https://future-architect.github.io/articles/20251128a/"/>
    <id>https://future-architect.github.io/articles/20251128a/</id>
    <published>2025-11-27T15:00:00.000Z</published>
    <updated>2025-11-27T15:00:00.000Z</updated>
    <author><name>市川裕也</name></author>
    <content type="html"><![CDATA[<p>こんにちは、CSIGの市川です。普段は FutureVuls の開発を主に担当しており、最近はパフォーマンスの問題に重点的に取り組んでいます。</p>
<p>先日開催された Datadog User Group Meetup #14@福岡 にて、「リクエストとDB のパフォーマンスを Datadog で監視する」というテーマで登壇してきました。</p>
<p>実は今回の登壇は、開催の1週間前に急遽決定したものでした。先日 札幌の Datadog ユーザ会 で登壇したチームメンバーの棚井さんから「福岡も盛り上がりそうなので、ぜひ行ってきてください」と勧めを受けたことがきっかけです。 (棚井さんの登壇ブログは こちら)</p>
<p>ちょうど私自身、ここ半年ほどパフォーマンス問題の解決に向けて試行錯誤を続けており、その中で得られた Datadog の知見を一度整理して共有したいと考えていたタイミングでもありました。準備期間は短かったですが、熱量の高いうちにアウトプットしようと思い、参加することにしました。</p>
<p>本記事では、当日の発表内容の抜粋と、実際にユーザ会に参加して感じたことを紹介します。</p>
<h2 id="発表内容">発表内容</h2><p>当日のスライドはこちらです。</p>
<iframe src="https://docs.google.com/presentation/d/e/2PACX-1vTziwVICdYW4WCscN2DG-DfUnUbDS_AP1foZwFC0lWgHVZtR-oZJzo6vkG6bwT5_ezG3VgKDj1A5lMA/pubembed?start=false&loop=false&delayms=3000" frameborder="0" width="95%" height="569" allowfullscreen="true" mozallowfullscreen="true" webkitallowfullscreen="true"></iframe>

<p>私の登壇内容について、一部のみとなりますが、抜粋してご紹介します。</p>
<p>今回は、FutureVuls というサービスにおいて、ユーザ数増加に伴い顕在化してきたパフォーマンス問題に対し、Datadog を用いてどのようにアプローチしたかをお話ししました。</p>
<h3 id="監視導入前の課題">監視導入前の課題</h3><p>当初は、パフォーマンスの問題 (特定の画面が遅い、裏でロック待ちが発生している 等) が発生していても、ユーザからの問い合わせがあるまで気づけない状態でした。</p>
<img fetchpriority="high" src="/images/2025/20251128a/導入前の課題.png" alt="導入前の課題.png" width="960" height="540">

<p>そこで、パフォーマンス監視の第一歩として「リクエストのレイテンシ（ユーザ体験）」と「DBのスロークエリ（システム全体への影響）」の2点を重点的に監視することにしました。</p>
<h3 id="リクエストの監視-アラート疲れと「日次集計」への転換">リクエストの監視 : アラート疲れと「日次集計」への転換</h3><p>リクエストレイテンシの監視では、当初「30秒を超えたら即アラート」という設定にしていました。しかし、これでは通知が鳴り止まず、いわゆる「アラート疲れ」を起こしてしまいました。</p>
<img src="/images/2025/20251128a/アラート疲れ.png" alt="アラート疲れ.png" width="960" height="540" loading="lazy">

<p>そこで、「レイテンシの問題は、傾向を掴んで優先度をつけられるようにすることが重要であり、リアルタイム性は必要ない」と割り切り、カスタムメトリクスを用いた日次集計へと運用を切り替えました。</p>
<p>これにより、各リクエストのパフォーマンス悪化がどの程度ユーザに影響を与えているかを大まかに把握できるようになり、開発チーム内で定量的な議論ができるようになりました。</p>
<img src="/images/2025/20251128a/日次集計.png" alt="日次集計.png" width="960" height="540" loading="lazy">

<img src="/images/2025/20251128a/リクエスト_before_after.png" alt="リクエスト_before_after.png" width="960" height="540" loading="lazy">

<h3 id="DB-のスロークエリの監視">DB のスロークエリの監視</h3><p>一方で、DBのスロークエリ（ロック待ちなど）に関しては、以下の 2 点を達成するため、Database Monitoring (DBM) モニターを用いてリアルタイムにアラートを飛ばす構成にしました。</p>
<ol>
<li><code>pg_stat_activity</code> (リアルタイムで走っているクエリの情報を取得できるビュー) を用いた原因特定およびボトルネック解消のため、問題発生にリアルタイムで気づきたい</li>
<li>「異常に時間がかかっているスロークエリ」が検出された時にアラートが上がってほしい</li>
</ol>
<img src="/images/2025/20251128a/スロークエリ.png" alt="スロークエリ.png" width="960" height="540" loading="lazy">

<p>結果として、問題発生時にリアルタイムで気づけるようになり、どんな問題が発生しているかを調査しやすくなりました。</p>
<img src="/images/2025/20251128a/DB_before_after.png" alt="DB_before_after.png" width="960" height="540" loading="lazy">

<h3 id="まとめ">まとめ</h3><p>最終的に、監視対象の性質に合わせて監視方法を使い分けることが、重要であるという結論に至りました。</p>
<img src="/images/2025/20251128a/Datadog_202511発表_公開用_(5).png" alt="Datadog_202511発表_公開用_(5).png" width="960" height="540" loading="lazy">

<p>詳細な設定方法や、カスタムメトリクスの具体的なクエリについては、ぜひスライド本編をご覧ください。</p>
<h2 id="Datadog-ユーザ会に参加してみてよかった点">Datadog ユーザ会に参加してみてよかった点</h2><p>Datadog ユーザ会に参加してよかったと感じる点を、インプット面とアウトプット面に分けて記載します。</p>
<h3 id="インプットの観点">インプットの観点</h3><p>Datadog は機能数が膨大なため、概要はなんとなく把握しているけれど普段の業務には活かせていない機能や、そもそもあまり知らない機能が眠っていたりします。実際の現場で Datadog を使いこなしているユーザの発表を聞くことで、今まであまり知らなかった機能について知ることができたり、普段使っている機能についても「そんな使い方もあるのか！」といった発見があったりして、とても良かったです。</p>
<p>また、現場で参加すると、発表の中で深掘りしたい点が出てきた際、発表者に気軽に質問できるのも良いなと思いました。<strong>現場で使用している人の意見は腹落ち感が段違い</strong>で、「Datadog をより活用していこう」というモチベーションアップにもつながりました。</p>
<h3 id="アウトプットの観点">アウトプットの観点</h3><p>「他の人に教える」というアウトプットの機会を作ることで、自分が断片的に持っていた知識を体系化できたり、あやふやだった知識をしっかり理解し定着させることに繋がったりと、結果的に自分に返ってくる、ということをあらためて実感しました。</p>
<h2 id="おわりに">おわりに</h2><p>今回は開催1週間前という駆け込みでの参加でしたが、結果として非常に有意義な時間を過ごすことができました。</p>
<p>他の参加者の方々と意見交換することで、自分たちの取り組みを客観的に見直す良い機会になりました。また、熱量の高いコミュニティに触れることで、エンジニアとしてのモチベーションも大きく向上しました。今回得られた知見や刺激を、今後のFutureVulsの開発や運用に活かしていきたいと思います。また、共有できそうな事例が溜まったら、積極的に外部へ発信していこうと思います！</p>
<p>最後に、温かく迎えてくださった運営や参加者の皆様に感謝いたします。</p>
]]></content>
    <summary type="html">Datadog User Group Meetup #14@福岡 にて、「リクエストとDB のパフォーマンスを Datadog で監視する」というテーマで登壇してきました。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="Datadog" scheme="https://future-architect.github.io/tags/Datadog/"/>
    <category term="オブサーバビリティ" scheme="https://future-architect.github.io/tags/%E3%82%AA%E3%83%96%E3%82%B5%E3%83%BC%E3%83%90%E3%83%93%E3%83%AA%E3%83%86%E3%82%A3/"/>
    <category term="登壇レポート" scheme="https://future-architect.github.io/tags/%E7%99%BB%E5%A3%87%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88/"/>
  </entry>
  <entry>
    <title>「Grafana Meetup Japan #6｜どうする？Grafanaする！」に登壇しました</title>
    <link href="https://future-architect.github.io/articles/20250910a/"/>
    <id>https://future-architect.github.io/articles/20250910a/</id>
    <published>2025-09-09T15:00:00.000Z</published>
    <updated>2025-09-09T15:00:00.000Z</updated>
    <author><name>伊藤太斉</name></author>
    <content type="html"><![CDATA[<p>こんにちは。TIGの伊藤です。</p>
<p>9&#x2F;2に開催された「Grafana Meetup Japan #6｜どうする？Grafanaする！」にLTで登壇してきたので、参加レポートも含めて記事にします。当日のYouTubeもアーカイブ公開されているのでそちらと合わせて読んでいただけると幸いです。</p>
<img fetchpriority="high" src="/images/2025/20250910a/gfarana_meetup_6.png" alt="gfarana_meetup_6.png" width="660" height="371">

<h2 id="自身のLT-Grafana-Alloyのconfig運用">自身のLT: Grafana Alloyのconfig運用</h2><p>今回、私が登壇したネタになります。資料は以下です。</p>
<iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/2d59638e59f04898857ce369ed20ba87" title="Grafana Meetup Japan Vol. 6" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>

<h3 id="Grafana-Alloyの利用経緯">Grafana Alloyの利用経緯</h3><p>私が今所属しているチームでは、従来EC2で動かしていたアプリケーションをECS on Fargateで稼働するようにリアーキを実施しました。また、合わせて利用していた監視やジョブについても再選定の対象とし、コンテナとの相性、今後の展開性も含めてGrafanaを利用しました。</p>
<p>その時、コンテナから様々なメトリクスデータを収集する時に利用するツールとしてGrafanaと合わせてAlloyを選定しました。</p>
<h3 id="Alloyの導入の時に考えたこと">Alloyの導入の時に考えたこと</h3><p>AlloyはHCL(HashiCorp Configuration Language)に近い記法で書くことができ、個人的には馴染みが良かったのですが、実際の環境や運用を考えてみると…</p>
<ul>
<li>アプリケーション<ul>
<li>Java</li>
<li>Go</li>
<li>ミドルウェア</li>
</ul>
</li>
<li>稼働環境<ul>
<li>EC2(VM)</li>
<li>ECS on Fargate(コンテナ)</li>
</ul>
</li>
<li>環境面<ul>
<li>本番</li>
<li>ステージング</li>
<li>開発</li>
</ul>
</li>
</ul>
<p>と様々な要因で統合は厳しいと判断しました。</p>
<p>しかし、なるべく省力化を図るために、以下の2つを検討し、実践しました。</p>
<h3 id="環境変数で吸収">環境変数で吸収</h3><p>Alloyには<code>sys.env</code>という形で環境変数の値を取得できるライブラリがあります。</p>
<ul>
<li>https://grafana.com/docs/alloy/latest/reference/stdlib/sys/</li>
</ul>
<p>このライブラリを以下の形で使うと、環境変数の値が取得でき、例示している (<code>LOKI_PUSH_URL</code>に向けて)ような収集したデータのプッシュ先を変えることができます。</p>
<div class="code-block"><figure class="highlight hcl"><input type="checkbox" id="code-wrap-1e04zdd-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1e04zdd-1" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre>loki.write <span class="string">&quot;loki_destination&quot;</span> {
  endpoint {
    url = sys.env(<span class="string">&quot;LOKI_PUSH_URL&quot;</span>)
  }
}</pre></td></tr></tbody></table></figure></div>

<h3 id="起動時にファイルを取得する方法">起動時にファイルを取得する方法</h3><p>環境変数で環境面に対応できましたが、もう1つ、Alloyが収集する対象のアプリ、ミドルに対しての適用です。</p>
<p>様々なログのパス、メトリクスのパスなどはどうしても環境変数で吸収することは難しいので、ファイルごとに分離し、Alloyコンテナが起動する時にファイルを取得する形式としました。</p>
<img src="/images/2025/20250910a/Grafana_Meetup_Vol.6.png" alt="Grafana_Meetup_Vol.6.png" width="960" height="540" loading="lazy">

<p>この2つの方法を織り込むことで、複数の環境面、複数の収集対象に適用できました。</p>
<h3 id="いただいた質問">いただいた質問</h3><p>当日、質問を募集するslidoにていただいた質問について、当日で回答できなかったので、こちらで回答します。</p>
<p><em>Q. Alloyを運用していて、パフォーマンスの問題はありましたか？Otelのcollectorだと、必要なパッケージだけ入れてビルドする、という運用ですが、Alloyはデフォルトで全部盛りと聞いていて、ナレッジあればお願いいたします</em><br>A. 全部盛りであると理解しています。私のチームでも実際にAlloyからMimir、Lokiへ転送しているため、このほかGrafanaのスタックに対しても使えます。</p>
<p><em>Q. Alloyは1つのコンテナでメトリクスとログ全て送信していますか？それとも用途によってコンテナを分けていますか？</em><br>A. 今回は1つのコンテナで全てメトリクスもログも収集、送信しています。</p>
<p><em>Q. Remotecfgを使っていないという認識で合っていますか？もし使わなかった理由などあればご享受頂けば幸いです。</em><br>A. <code>remotecfg</code>は知らなかったです。ドキュメントを見てみたのですが、その過程として<code>remote.s3</code>というコンポーネントがあるらしく、これを使うと以下のように書けそうです（未検証のため確認が必要です）。</p>
<div class="code-block"><figure class="highlight hcl"><input type="checkbox" id="code-wrap-1e04zdd-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1e04zdd-2" title="コードの折り返しを切り替える"></label><table><tbody><tr><td class="code"><pre>remote.s3 <span class="string">&quot;external_config&quot;</span> {
  path = <span class="string">&quot;s3://bucket-name/alloy/config.alloy&quot;</span>
}</pre></td></tr></tbody></table></figure></div>

<p>他にもアクセスキーなどについても設定できそうでした。今回のユースケースであればFargateにタスクロールがついているので、そちらで賄えるのではないかと考えています。</p>
<h2 id="そのほかのセッション、LT">そのほかのセッション、LT</h2><h3 id="Grafanaスタックをフル活用したオブザーバビリティ基盤の紹介">Grafanaスタックをフル活用したオブザーバビリティ基盤の紹介</h3><iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/46fd2892cc3d48d08e8244c86c4799cb" title="Grafanaスタックをフル活用したオブザーバビリティ基盤の紹介" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>

<p>今回のメインセッションであるGO株式会社のQuentinさんによるセッションでした。</p>
<p>GOでは100を超えるマイクロサービスをAWS、およびGoogle Cloud上のKubernetes基盤で稼働させており、これを「Kenos」と呼称しているそうです。</p>
<p>この大きなクラスターを稼働させる上でオブザーバビリティがより重要になってきていましたが、SaaS監視ツールの料金高騰や問い合わせ対応時にツールが分散している煩雑さから、Grafanaのスタックを徐々に自前で導入しています。</p>
<p>規模感は日々ログがTBオーダー、メトリクスやトレースでもGBオーダーと、私も自前で構築をしているものの桁が違いすぎて非常に驚いたこともありますし、さらにその規模を安定して運用されているところが興味深かったです。個人的に改めて納得したポイントではありますが、同じツールでも自前で運用するか、SaaSで展開するかは扱っているデータサイズにも依存していると思いました。ただ、Grafana Cloudもあるので、ある程度運用負荷が高いなどがあれば切り替えることができるのもGrafanaの良さだと感じました。</p>
<p>また、Grafanaの自前運用での振り返りとして話されていたのは、初期の設定の躓きであったり、運用の安定性などについて自身でも気をつけたいところでした。例えば、Grafanaでの障害切り分けをする際に、Grafana自体が過負荷になりサービスダウンしてしまうといったことがあったようで、ここはGrafana自体のチューニングであったり、Loki, Mimirまで含めて設定を洗っておこうと思います。</p>
<p>そのほかの話については以下のブログにも公開されており、見返してみると私もだいぶお世話になったブログでした。</p>
<ul>
<li>LGTM！オブザーバビリティ基盤第1話</li>
<li>Grafana Lokiでログを検索 | オブザーバビリティ基盤第2話</li>
<li>Grafana LokiのLogQLを理解する</li>
</ul>
<h3 id="Grafanaをリバプロ配下で動かすときにやること-Grafana-Liveってなんだ">Grafanaをリバプロ配下で動かすときにやること ~ Grafana Liveってなんだ ~</h3><iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/bc80afab6a944b4c9f52e43b0d0633ff" title="【Grafana Meetup Japan #6】Grafanaをリバプロ配下で動かすときにやること ~ Grafana Liveってなんだ ~" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>

<p>Grafanaがサクサク、ヌルヌル動く秘密の部分を解説してくれるセッションでした。Grafanaにはストリーミングの機能があり、ここではWebSocketを使っていますが、これをNginxなどのリバースプロキシ経由でどう使うか、使えるようにするか解説されました。<br>私自身は過去、複数の環境を束ねているNginx越しでGrafanaを触ろうとして方式を変えた記憶があったので、改めてトライしてみようと思いました。</p>
<h3 id="Grafana-MCPサーバーによるAIエージェント経由でのGrafanaダッシュボード動的生成">Grafana MCPサーバーによるAIエージェント経由でのGrafanaダッシュボード動的生成</h3><iframe class="speakerdeck-iframe" frameborder="0" src="https://speakerdeck.com/player/37c7b077d940478a91b28eb3b7180532" title="Grafana MCPサーバーによるAIエージェント経由でのGrafanaダッシュボード動的生成" allowfullscreen="true" style="border: 0px; background: padding-box padding-box rgba(0, 0, 0, 0.1); margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" data-ratio="1.7777777777777777"></iframe>

<p>Grafana MCPサーバを実際に使って利用できるダッシュボードを作成するセッションでした。今回はClaudeに対して自然言語で入力、対話を続けていき、最後は…</p>
<p>OSS版でも対応している限りMCPサーバは使えるようですので、また試したいものが増えてしまいました。</p>
<h2 id="イベント中の小話">イベント中の小話</h2><p>今回はGrafana Labsの方もいらっしゃったのですが、なんとついに立ち上がった日本法人の入社初日だったらしく、日本のGrafanaのコミュニティが大きく変わりそうな予感がしました。そんなタイミングであることはもちろん知らなかったのですが、LTをここでできたのは個人的には良かったなと思いました。</p>
<h2 id="まとめ">まとめ</h2><p>今回はLTという形で登壇をしつつイベントに参加しましたが、課題を持って、感じてイベントに参加するとイベント後の懇親会も含めてより有意義になることを改めて感じたので、ちゃんと話題としても持っていこうと思いました。</p>
<p>また、今回は実際にto C向けサービスを運用している立場の声を聞くことができたので、よりどう活かそうか、取り込もうかと考えられたことも大きかったので、自身で取り組んでいる構成も見直そうと思います。</p>
<p>今後の日本におけるGrafanaのコミュニティがより盛り上がるように微力ながら頑張っていけたらと思います。</p>
<h2 id="参考">参考</h2><ul>
<li>Grafana Alloyを使って、EKSクラスタ外部のサーバからメトリクス取得を試してみた</li>
</ul>
]]></content>
    <summary type="html">9/2に開催された「Grafana Meetup Japan #6｜どうする？Grafanaする！」にGrafana Alloyのconfig運用というテーマでLT登壇してきたので、参加レポートも含めて記事にします。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="Grafana" scheme="https://future-architect.github.io/tags/Grafana/"/>
    <category term="登壇レポート" scheme="https://future-architect.github.io/tags/%E7%99%BB%E5%A3%87%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88/"/>
  </entry>
  <entry>
    <title>Grafana Alloyを使って、EKSクラスタ外部のサーバからメトリクス取得を試してみた</title>
    <link href="https://future-architect.github.io/articles/20250825b/"/>
    <id>https://future-architect.github.io/articles/20250825b/</id>
    <published>2025-08-24T15:00:01.000Z</published>
    <updated>2025-08-24T15:00:01.000Z</updated>
    <author><name>二宮佑斗</name></author>
    <content type="html"><![CDATA[<p>夏の自由研究2025ブログ連載の1日目です。</p>
<p>初めまして、コアテクノロジーグループに所属している二宮と申します。</p>
<p>私たちのチームでは普段Grafanaを利用していますが、Grafana Alloyはまだ使ったことがありませんでした。当初は、promtailの後継ツールとしての利用のみを考えていただけでしたが、ドキュメントを読み進める内にAlloyを使えばテレメトリデータの活用方法がさらに広がるのでは…？ と感じました。</p>
<p>そこで今回は、Alloyの豊富な機能の一部を利用してEKSクラスタ外部のサーバ監視に挑戦してみました。本記事では、その設定手順や使ってみて分かったポイントを、初めてAlloyに触れた目線でご紹介します。</p>
<h2 id="1-Grafana-Alloyとは">1. Grafana Alloyとは</h2><p>Grafana Alloyは、Grafana Labsが開発するOSSのテレメトリ収集エージェントです。</p>
<p>メトリクス・ログ・トレース・プロファイルをこれ1つで統合的に扱うことができます。Grafana Agentの正式な後継製品であり、Promtailが担っていたログ収集機能もAlloyに統合されています。OpenTelemetryをベースに作られているため、柔軟で強力なデータパイプラインを構築できるのが特徴です。</p>
<h2 id="2-構成説明">2. 構成説明</h2><ul>
<li><p>EKSクラスタ: メトリクスの管理先となるPrometheusと、可視化ツールであるGrafanaをすでに構築済みです</p>
</li>
<li><p>EC2インスタンス: 監視対象であるEKSクラスタ外部のサーバです。このサーバ上にGrafana Alloyをコンテナとして構築します</p>
<ul>
<li>EC2インスタンスの準備: Alloyをコンテナとして動かすため、監視対象のEC2インスタンスにはDockerおよびDocker Composeを事前にインストールしています。</li>
</ul>
</li>
<li><p>通信経路の確保: 本記事では詳細を割愛しますが、AlloyからPrometheusへデータを送信できるよう、事前にセキュリティグループを設定します。</p>
</li>
<li><p>本記事作成時の環境: 今回の検証で使用した、主なソフトウェアの構成は以下の通りです</p>
<div class="scroll"><table>
<thead>
<tr>
<th>ツール</th>
<th>バージョン</th>
</tr>
</thead>
<tbody><tr>
<td>Grafana</td>
<td>12.0.0</td>
</tr>
<tr>
<td>Grafana Alloy</td>
<td>1.8.3</td>
</tr>
<tr>
<td>Prometheus</td>
<td>3.4.1</td>
</tr>
</tbody></table></div>
</li>
</ul>
<img fetchpriority="high" src="/images/2025/20250825b/Alloy記事作成_構成図.drawio.png" alt="Alloy記事作成_構成図.drawio.png" width="882" height="537">

<h2 id="3-今回の検証で利用するAlloyコンポーネントの概要">3. 今回の検証で利用するAlloyコンポーネントの概要</h2><p>本記事の中では以下のコンポーネントを利用しています。</p>
<p>実際の利用方法と記載内容は後述します。</p>
<ul>
<li>prometheus.export.unix<br>  メトリクスを生成します。<br>  このコンポーネントを利用することで、別途node_exporterをインストールしなくても、CPU、メモリ、ディスクスペース、ディスクI&#x2F;O、ネットワークなどUNIXシステムのメトリクスを公開できます。</li>
<li>prometheus.scrape<br>  exporterが公開したメトリクスを定期的に収集(スクレイプ)します。<br>  指定されたtargetsのHTTPエンドポイントにアクセスしてデータを取得します。<br>  収集したメトリクスは、<code>forward_to</code>で指定された次のコンポーネントに渡されます。</li>
<li>prometheus.relabel<br>  Prometheus形式のメトリクスが持つラベルを、動的なルールに基づいて書き換え・追加・削除します。<br>  正規表現を使ったラベルの生成や、不要なメタデータラベルの削除など、メトリクスの整形・加工を担います。</li>
<li>prometheus.remote_write<br>  収集・加工したメトリクスを、最終的な保存先であるPrometheus互換のバックエンドに送信します。<br>  Prometheus Remote Writeプロトコルを使って、設定されたendpointのURLにデータをまとめて送信します。<br>  今回、送信先はPrometheusにしていますが、Grafana MimirやGrafana Cloudなども指定可能です。<br>  送信先のエンドポイントURLはIngressやNodePortまたはTargetGroupBinding等で設定したものを指定します。</li>
</ul>
<h2 id="4-設定方法">4. 設定方法</h2><h3 id="4-1-コンポーネント同士の連携方法">4-1. コンポーネント同士の連携方法</h3><p>Grafana Alloyではconfig.alloyという設定ファイルで、機能単位である「コンポーネント」を繋ぎ合わせ、テレメトリデータの取得ができます。</p>
<p>設定ファイルは、Grafana agentの設定言語であるriver言語をベースに記述され、宣言的に記述できるという特徴があります。</p>
<p>この連携には、データを次のコンポーネントに押し出すPush方式と、他のコンポーネントからデータを引き出すPull方式があります。</p>
<p>Push方式は、Alloy内部にてコンポーネント間でデータを渡す仕組みで、<code>forward_to</code>引数で出力先を指定し、データを受け取る側のコンポーネントは<code>receiver</code>という入力口を持っています。Pull方式も、Alloy内部でコンポーネントが連携するための仕組みで、<code>targets</code>引数で入力元を指定します。データを提供する側のコンポーネントは<code>targets</code>という監視対象リストを公開しています。</p>
<p>他のコンポーネントを参照する際は、<code>&lt;コンポーネント種別&gt;.&lt;コンポーネント名&gt;.&lt;公開名&gt;</code>の形式で記述します。</p>
<ul>
<li>コンポーネント種別 : prometheus.scrapeやprometheus.exporter.unixなど</li>
<li>コンポーネント名   : “internal_node_metrics”や”set_instance_name”など、自分で付けた名前</li>
<li>公開名            : receiverやtargetsなど、各コンポーネントが公開している名前</li>
</ul>
<h3 id="4-2-config-alloy">4-2. config.alloy</h3><p>config.alloyの設定です。</p>
<div class="code-block"><figure class="highlight js"><input type="checkbox" id="code-wrap-1wkxwwf-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1wkxwwf-1" title="コードの折り返しを切り替える"></label><figcaption><span>config.alloy</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment">// Node Exporter相当の機能をAlloy内部で提供</span></span><br><span class="line">prometheus.<span class="property">exporter</span>.<span class="property">unix</span> <span class="string">&quot;default&quot;</span> &#123;</span><br><span class="line">  <span class="comment">// コンテナ内でホストOSの各パスがどこにマウントされているかを指定(compose.ymlのvolumes設定と合わせる)</span></span><br><span class="line">  <span class="comment">// procfs: CPU使用率、メモリ情報、実行中プロセスの一覧などの情報</span></span><br><span class="line">  procfs_path = <span class="string">&quot;/host/proc&quot;</span></span><br><span class="line">  <span class="comment">// sysfs: 接続されているデバイスやドライバに関する情報</span></span><br><span class="line">  sysfs_path  = <span class="string">&quot;/host/sys&quot;</span></span><br><span class="line">  <span class="comment">// rootfs_path: ホストOSのルートファイルシステム(/)のマウントパスを指定,ディスク使用量などのファイルシステム関連のメトリクスを正しく収集できるようにする</span></span><br><span class="line">  rootfs_path = <span class="string">&quot;/rootfs&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// prometheus.exporter.unixが公開するメトリクスをスクレイプする設定を記載</span></span><br><span class="line">prometheus.<span class="property">scrape</span> <span class="string">&quot;internal_node_metrics&quot;</span> &#123;</span><br><span class="line">  targets    = prometheus.<span class="property">exporter</span>.<span class="property">unix</span>.<span class="property">default</span>.<span class="property">targets</span>          <span class="comment">// 内部エクスポーターターゲットを指定</span></span><br><span class="line">  forward_to = [prometheus.<span class="property">relabel</span>.<span class="property">set_instance_name</span>.<span class="property">receiver</span>]   <span class="comment">// スクレイプしたメトリクスの転送先の指定</span></span><br><span class="line">  scrape_interval = <span class="string">&quot;15s&quot;</span>                                        <span class="comment">// メトリクス収集頻度を15sに変更(デフォルトは60s)</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 収集したメトリクスのラベルを整形</span></span><br><span class="line"><span class="comment">// コンポーネントを&quot;set_instance_name&quot;という名前で定義</span></span><br><span class="line">prometheus.<span class="property">relabel</span> <span class="string">&quot;set_instance_name&quot;</span> &#123;</span><br><span class="line">  <span class="comment">// relabelコンポーネントからの転送先を、remote_writeに設定</span></span><br><span class="line">  forward_to = [prometheus.<span class="property">remote_write</span>.<span class="property">stg_prometheus</span>.<span class="property">receiver</span>]</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 書き換えルールを定義</span></span><br><span class="line">  rule &#123;</span><br><span class="line">    <span class="comment">// 書き換えの元となるラベルとして &quot;instance&quot; を指定</span></span><br><span class="line">    source_labels = [<span class="string">&quot;instance&quot;</span>]</span><br><span class="line">    <span class="comment">// 上書きしたいラベルとして &quot;instance&quot; を指定</span></span><br><span class="line">    target_label  = <span class="string">&quot;instance&quot;</span></span><br><span class="line">    <span class="comment">// 上書きする値として、環境変数からホスト名を直接取得</span></span><br><span class="line">    replacement   = <span class="title function_">env</span>(<span class="string">&quot;HOSTOS_HOSTNAME&quot;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Prometheusにメトリクスを送信</span></span><br><span class="line"><span class="comment">// Prometheusへの送信設定を定義</span></span><br><span class="line">prometheus.<span class="property">remote_write</span> <span class="string">&quot;stg_prometheus&quot;</span> &#123;</span><br><span class="line">  <span class="comment">// メトリクスの送信先となるPrometheusサーバのremote_writeエンドポイントURLを指定</span></span><br><span class="line">  endpoint &#123;</span><br><span class="line">    url = <span class="string">&quot;http://&lt;prometheus-endpoint&gt;/api/v1/write&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// WAL(Write-Ahead Log)の設定</span></span><br><span class="line">  <span class="comment">// 送信先のサーバがダウンしている場合などに、メトリクスを一時的にディスクに保存してデータ損失を防ぐための仕組みです。</span></span><br><span class="line">  wal &#123;</span><br><span class="line">    <span class="comment">// 正常に送信が完了した古いデータを、WALから削除する頻度を1時間に設定。</span></span><br><span class="line">    truncate_frequency = <span class="string">&quot;1h&quot;</span></span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// このAlloyインスタンスから送られる全てのメトリクスに共通のラベルを付与</span></span><br><span class="line">   external_labels = &#123;</span><br><span class="line">     cluster = <span class="string">&quot;external&quot;</span>,</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>この設定によって構築される、メトリクスデータのパイプラインを図にすると以下のようになります。</p>
<img src="/images/2025/20250825b/config.alloyのパイプライン.png" alt="config.alloyのパイプライン.png" width="1144" height="691" loading="lazy">

<h3 id="4-3-その他のファイル、ディレクトリ構成">4-3. その他のファイル、ディレクトリ構成</h3><p>作成したconfig.alloyを読み込み、Alloyコンテナを起動するため、compose.ymlは以下のように記載しています。</p>
<div class="code-block"><figure class="highlight yml"><input type="checkbox" id="code-wrap-1wkxwwf-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1wkxwwf-2" title="コードの折り返しを切り替える"></label><figcaption><span>compose.yml</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">services:</span></span><br><span class="line">  <span class="attr">grafana-alloy:</span></span><br><span class="line">    <span class="attr">image:</span> <span class="string">grafana/alloy:v1.8.3</span>     <span class="comment"># 今回の検証で利用したバージョンです</span></span><br><span class="line">    <span class="attr">container_name:</span> <span class="string">grafana-alloy</span></span><br><span class="line">    <span class="attr">hostname:</span> <span class="string">grafana-alloy</span></span><br><span class="line">    <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line">    <span class="attr">user:</span> <span class="string">root</span></span><br><span class="line">    <span class="attr">pid:</span> <span class="string">host</span>  <span class="comment"># Node Exporter機能がホストのプロセス情報を正確に収集するため</span></span><br><span class="line">    <span class="attr">environment:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">TZ=Asia/Tokyo</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">HOSTOS_HOSTNAME=$&#123;HOSTNAME&#125;</span></span><br><span class="line">    <span class="attr">volumes:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/opt/volumes/grafana_alloy/grafana_alloy:/grafana_alloy/:ro</span></span><br><span class="line">      <span class="comment"># Alloy の WAL (Write-Ahead Log) データ用永続ボリューム</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/data/app/grafana_alloy/wal:/var/lib/alloy/data</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/etc/localtime:/etc/localtime:ro</span></span><br><span class="line">      <span class="comment"># prometheus.scrapeのためにホストの重要なパスをマウント</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/proc:/host/proc:ro</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/sys:/host/sys:ro</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/:/rootfs:ro</span></span><br><span class="line">      <span class="comment">#diskstatsコレクターが参照するため</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">/run/udev/data:/run/udev/data:ro</span></span><br><span class="line">    <span class="attr">ports:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;12340:12340&quot;</span></span><br><span class="line">    <span class="attr">command:</span></span><br><span class="line">      <span class="comment"># &#x27;run&#x27; サブコマンドで設定ファイルを指定してAlloyを実行</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;run&quot;</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;/grafana_alloy/etc/config.alloy&quot;</span></span><br><span class="line">      <span class="comment"># --storage.path で WAL などのデータディレクトリを指定 (volumes でマウントしたパスと合わせる)</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&quot;--storage.path=/var/lib/alloy/data&quot;</span></span><br><span class="line">    <span class="attr">logging:</span></span><br><span class="line">      <span class="attr">driver:</span> <span class="string">&quot;json-file&quot;</span></span><br><span class="line">      <span class="attr">options:</span></span><br><span class="line">        <span class="attr">max-size:</span> <span class="string">&quot;100m&quot;</span></span><br><span class="line">        <span class="attr">max-file:</span> <span class="string">&quot;3&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">networks:</span></span><br><span class="line">  <span class="attr">default:</span></span><br><span class="line">    <span class="attr">external:</span></span><br><span class="line">      <span class="attr">name:</span> <span class="string">stg_network</span></span><br></pre></td></tr></table></figure></div>

<ul>
<li>&#x2F;data配下のディレクトリ構成<br>AlloyのWAL(Write-Ahead Log)機能によるデータ永続化のため、compose.ymlでホストOSのディレクトリマウントを行っています。<br>送信先のPrometheusがダウンしていても、Alloyは未送信のメトリクスをこのWALに一時的に保存します。<br>コンテナを再起動してもデータが消えないようにホストOSに保存し、メトリクスの損失を防ぎます。<br>マウント先のディレクトリまで作成後、ディレクトリ配下に作成されるファイルやディレクトリは、Alloyが自動で作成・削除します。</li>
</ul>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-1wkxwwf-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1wkxwwf-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">/data/app/grafana_alloy/</span><br><span class="line">└── wal                                        <span class="comment"># WALデータが格納されます</span></span><br><span class="line">    ├── prometheus.remote_write.stg_prometheus</span><br><span class="line">    │   └── wal</span><br><span class="line">    │       ├── 00000325</span><br><span class="line">    │       └── checkpoint.00000324</span><br></pre></td></tr></table></figure></div>

<ul>
<li>Prometheus側の設定<br>Grafana Alloyからデータを受け取るには、送信先であるPrometheus側でremote-writeリクエストを有効にする必要があります。<br>今回、Prometheusはkube-prometheus-stackのHelm Chartを利用しているので、values.yamlを編集してこの設定を有効にしています。<ul>
<li><p>values.yamlの設定方法<br>values.yamlで<code>--web.enable-remote-write-receiver</code>という起動オプションを追記する必要があります。</p>
  <figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line">    <span class="attr">containers:</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">prometheus</span></span><br><span class="line">  <span class="attr">args:</span></span><br><span class="line">  <span class="comment"># 下の行を追記</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">&quot;--web.enable-remote-write-receiver&quot;</span></span><br></pre></td></tr></table></figure>

<p>  この設定を追記し反映させることで、PrometheusがAlloyからのデータを受け付けられるようになりました。</p>
</li>
</ul>
</li>
</ul>
<h2 id="5-Grafana上からダッシュボードを用いたメトリクス可視化">5. Grafana上からダッシュボードを用いたメトリクス可視化</h2><p>ダッシュボードにはGrafana Labsから提供されているNode Exporter Full を利用します。</p>
<p>上記URLからJSONをダウンロードして、Grafana上の<code>Dashdoards</code>を選択し、<code>Import dashboard</code>でJSONをアップロードしダッシュボードを作成します。</p>
<h3 id="5-1-Variables-変数-の設定">5-1. Variables(変数)の設定</h3><p>GrafanaのVariablesを設定し、監視対象のサーバをドロップダウンで選択できるようにします。</p>
<ol>
<li>ダッシュボード右上の<code>edit</code>をクリックし、<code>Settings</code>を選択します。</li>
<li>Variablesタブに移動してnodeを選択します</li>
<li>Label filtersに設定されているラベルを削除して、Label filtersに<code>cluster = external</code>を設定</li>
<li>画面下のRun Queryを押して対象のサーバが表示されたら、<code>Save dashboard</code>から保存します。</li>
</ol>
<h3 id="5-2-主なダッシュボードパネル">5-2. 主なダッシュボードパネル</h3><p>「Node Exporter Full」ダッシュボードには多くの情報が表示されますが、一部パネルについてご紹介します。</p>
<ul>
<li><strong>CPU Basic</strong>: CPUの基本的な使用率をuser,system,iowaitなどのモード別に表示し、サーバの負荷状況を確認できます</li>
<li><strong>Memory Basic</strong>: システム全体のメモリ使用量、空き容量、バッファやキャッシュとして利用されている量が表示され、メモリ不足の兆候を把握できます</li>
<li><strong>ディスク Space Used Basic</strong>: ディスクごとの使用率(％)を確認できます</li>
<li><strong>Network Traffic Basic</strong></li>
</ul>
<p>ネットワークインターフェースごとの送受信トラフィック(MB&#x2F;s)を表示し、通信量の急増や異常を検知できます。</p>
<p>インポートしたダッシュボードは自由にカスタマイズが可能なので、必要に応じてパネルの追加や削除ができます。</p>
<h3 id="5-3-複数サーバの監視">5-3. 複数サーバの監視</h3><p><code>4. 設定方法</code>に記載した設定のサーバを複数台構築した場合、<code>instance</code>を切り替えることで監視対象サーバを切り替えることができます。</p>
<img src="/images/2025/20250825b/Grafana_dashboards.png" alt="Grafana_dashboards.png" width="1200" height="624" loading="lazy">

<h3 id="5-4-監視設定">5-4. 監視設定</h3><p>今回はメトリクスの可視化までを行いましたが、収集したメトリクスを元に閾値を設定し、アラート設定も可能です。</p>
<p>アラートの通知先として、Slackやメール等、様々なツールを指定できるため、システムの異常を迅速に検知する体制を構築できます。</p>
<h2 id="6-まとめ">6. まとめ</h2><p>Grafana Alloyの機能の一部である、メトリクスの収集・送信設定を行い、複数サーバの監視にも対応できるダッシュボードを作成しました。</p>
<p>コンポーネントの記載方法は少し独特ですが、慣れたらパズルのように組み合わせて直感的に設定できそうと感じました。Alloyはメトリクス以外にもログ、トレース、プロファイルも収集できます。</p>
<p>今後はこれらの機能も試し、Grafanaを利用したデータ利活用をさらに広げていきたいです。</p>
]]></content>
    <summary type="html">Grafana Alloyの豊富な機能の一部を利用してEKSクラスタ外部のサーバ監視に挑戦してみました。本記事では、その設定手順や使ってみて分かったポイントを、初めてAlloyに触れた目線でご紹介します。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="Docker" scheme="https://future-architect.github.io/tags/Docker/"/>
    <category term="EKS" scheme="https://future-architect.github.io/tags/EKS/"/>
    <category term="Grafana" scheme="https://future-architect.github.io/tags/Grafana/"/>
    <category term="オブサーバビリティ" scheme="https://future-architect.github.io/tags/%E3%82%AA%E3%83%96%E3%82%B5%E3%83%BC%E3%83%90%E3%83%93%E3%83%AA%E3%83%86%E3%82%A3/"/>
    <category term="初心者向け" scheme="https://future-architect.github.io/tags/%E5%88%9D%E5%BF%83%E8%80%85%E5%90%91%E3%81%91/"/>
  </entry>
  <entry>
    <title>LLMコンテナイメージでLazy Pullingの効果を検証</title>
    <link href="https://future-architect.github.io/articles/20250626a/"/>
    <id>https://future-architect.github.io/articles/20250626a/</id>
    <published>2025-06-25T15:00:00.000Z</published>
    <updated>2025-06-25T15:00:00.000Z</updated>
    <author><name>鈴木崇史</name></author>
    <content type="html"><![CDATA[<h2 id="はじめに">はじめに</h2><p>最近仕事でLLMサーバーを構築することになりました。</p>
<p>雰囲気をお伝えすると、HuggingFaceの <code>transformers</code> をFastAPIのようなフレームワークでラップしたものです。</p>
<div class="code-block"><figure class="highlight python"><input type="checkbox" id="code-wrap-1mg5vd-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1mg5vd-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> FastAPI, Request</span><br><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> pipeline</span><br><span class="line"></span><br><span class="line">app = FastAPI()</span><br><span class="line"></span><br><span class="line">generator = pipeline(<span class="string">&quot;text-generation&quot;</span>, model=<span class="string">&quot;Qwen/Qwen2-0.5B-Instruct&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.post(<span class="params"><span class="string">&quot;/generate&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">generate</span>(<span class="params">request: Request</span>):</span><br><span class="line">    data = <span class="keyword">await</span> request.json()</span><br><span class="line">    prompt = data.get(<span class="string">&quot;prompt&quot;</span>, <span class="string">&quot;&quot;</span>)</span><br><span class="line">    result = generator(prompt, max_new_tokens=<span class="number">50</span>)</span><br><span class="line">    <span class="keyword">return</span> &#123;<span class="string">&quot;output&quot;</span>: result[<span class="number">0</span>][<span class="string">&quot;generated_text&quot;</span>]&#125;</span><br></pre></td></tr></table></figure></div>

<div class="code-block"><figure class="highlight bash"><input type="checkbox" id="code-wrap-1mg5vd-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1mg5vd-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">curl -X POST http://localhost:8000/generate \</span><br><span class="line">  -H <span class="string">&quot;Content-Type: application/json&quot;</span> \</span><br><span class="line">  -d <span class="string">&#x27;&#123;&quot;prompt&quot;: &quot;生命、宇宙、そして万物についての究極の疑問の答え&quot;&#125;&#x27;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 42という答えを期待していたのですが、軽量モデルだからか無理でした</span></span><br><span class="line">&#123;<span class="string">&quot;output&quot;</span>: <span class="string">&quot;生命、宇宙、そして万物についての究極の疑問の答えを探求します。あなたは「あなたの世界」をどのように描いていますか？何がその世界に存在するのか、そしてそれがどのような形で存在しているのか？そし&quot;</span>&#125;</span><br></pre></td></tr></table></figure></div>

<p>これらをECSでホストするべく Python のベースイメージでコンテナ化したのですが、LLM関連のパッケージをインストールしたコンテナイメージは非常に大きくなります。</p>
<p>例えば、 <code>huggingface/transformers-pytorch-gpu</code> は約10GBです。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-1mg5vd-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1mg5vd-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># nerdctl  images</span></span><br><span class="line">REPOSITORY                              TAG       IMAGE ID        CREATED           PLATFORM       SIZE       BLOB SIZE</span><br><span class="line">huggingface/transformers-pytorch-gpu    latest    f0a0db1c2168    23 minutes ago    linux/amd64    18.66GB    9.746GB</span><br></pre></td></tr></table></figure></div>

<p>中身を調べてみるとcudaやpytorchなどが重量級です。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-1mg5vd-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1mg5vd-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">4.8G    /usr/local/cuda-12.6</span><br><span class="line">2.7G    /usr/local/lib/python3.10/dist-packages/nvidia</span><br><span class="line">1.6G    /usr/local/lib/python3.10/dist-packages/torch</span><br></pre></td></tr></table></figure></div>

<p>さらに私の場合、モデルの重みをイメージに同梱していたためサイズが肥大化していました。コンテナイメージサイズの削減を頑張るべきかもしれません。例えば思い付きですが、モデルの重みはイメージに含めずにボリュームとして切り出してマウントするなどが考えられます。</p>
<p>しかし、今回はECSなどでコンテナの起動を高速化することを目標に、イメージを遅延して読み込むLazy-pullingな技術に目を向け、LLMコンテナに対し効果があるのかどうかを検証します。</p>
<p>CNCF連載 ということで取り上げるのはGraduatedなプロダクトであるcontainerdです。</p>
<h2 id="containerdとSnapshotter">containerdとSnapshotter</h2><p><code>docker run</code>したときの処理の流れをざっくりおさらいすると、</p>
<ol>
<li>イメージを取得して展開</li>
<li>ファイルシステムをホストと分離</li>
<li>いろいろ名前空間を分離しつつプロセスを起動する</li>
</ol>
<p>…です。この上半分くらいを担当するのがcontainerdに代表されるCRIランタイムというものです。DockerやKubernetesの影にいます。</p>
<p>containerdの機能はpluggableです。例えば 下半分くらいを担当するOCIランライムはOCIランタイム仕様に則っていれば切替可能です。</p>
<p>今回着目するイメージの展開やファイルシステムを管理するコンポーネントはSnapshotterと呼ばれ、これも差し替え可能になっています。</p>
<p>デフォルトはoverlayfsですが、lazy pull対応のsnapshotter(Remote Snapshotterといいます)に差し替えることでその機能を利用する形です。</p>
<p>Snapshotterはデーモンとして起動させunix domain socketごしにcontainerdと通信します。</p>
<h2 id="Lazy-Pullingとは">Lazy Pullingとは</h2><p>通常のコンテナ起動では、イメージ全体をダウンロードしてから展開・プロセス起動という流れですが、コンテナ起動時に全てのファイルが必要なわけじゃないよね、という観察があります。</p>
<p>この手のツールのREADMEでよく参照されているHarter et al.によると、コンテナ起動時間の76％がイメージダウンロードに費やされている一方、実際にコンテナが開始するのに必要なデータは平均で6.4％だそうです。</p>
<p>なので、Remote Snapshotterは初回起動時は必要なファイルだけをpullしてプロセスを開始してしまいます。レジストリはFUSEを介してマウントしておき都度取得する、という仕組みです。</p>
<p>ところでコンテナイメージのレイヤーのフォーマットであるtar.gzにおいては、全てのファイルを展開せずに特定のファイルのみにアクセスし取得する、ということができません。イメージのレイヤー内のすべてのファイルがtarにアーカイブgzipで圧縮されており団子状態なためです（Seekableではないという言い方をします）。</p>
<p>ということでRemote Snapshotterを使うにはコンテナイメージをSeekableなイメージに変換する必要があったり、どのファイルがどのレイヤーのどのオフセットに含まれているのかのインデックスをメタデータとして持たせたりする必要があります。</p>
<figure><img fetchpriority="high" src="/images/2025/20250626a/overview01.png" alt="overview01.png" width="1200" height="677"><figcaption>Containerd Stargz Snapshotter Plugin Overview</figcaption></figure>
<p>今回検証するのは以下の2つです。</p>
<p>stargz-snapshotter</p>
<ul>
<li>eStargzというイメージフォーマットでlazily-pullableを実現します</li>
</ul>
<p>soci-snapshotter</p>
<ul>
<li>AWSが提供していてECS・Fargateで使えます</li>
<li>AWSのブログはこちら</li>
</ul>
<h2 id="実験してみた">実験してみた</h2><p>LLMで文を生成するスクリプトをコンテナ化し、レジストリにpushしておき、以下の3パターンのsnapshotterで<code>docker run</code>の時間を計測しました。</p>
<ul>
<li>overlayfs: 通常のsnapshotter</li>
<li>stargz-snapshotter</li>
<li>soci-snapshotter</li>
</ul>
<h3 id="検証環境構築">検証環境構築</h3><p>今回の検証環境はこんな感じです。</p>
<p><code>docker run</code>するホスト</p>
<ul>
<li>EC2</li>
<li>m5.large</li>
<li>Ubuntu</li>
<li>東京リージョン</li>
</ul>
<p>コンテナレジストリ</p>
<ul>
<li>ECR</li>
<li>東京リージョン</li>
</ul>
<p>今回LLMを実行しますが、GPUは使わないことにしました。</p>
<p>containerdとそのクライアントのnerdctl(docker CLIのようなもの)などをインストールします。このあたりはcontainerdのgetting startedの手順通りです。<br>https://github.com/containerd/containerd/blob/main/docs/getting-started.md</p>
<p>snapshotter設定をstargz-snapshotterを例にざっくり記載すると、まずsnapshotterをインストールしてデーモンとして起動します。</p>
<div class="code-block"><figure class="highlight bash"><input type="checkbox" id="code-wrap-1mg5vd-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1mg5vd-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">tar -C /usr/local/bin -xvf stargz-snapshotter-<span class="variable">$&#123;version&#125;</span>-linux-<span class="variable">$&#123;arch&#125;</span>.tar.gz containerd-stargz-grpc ctr-remote</span><br><span class="line">wget -O /etc/systemd/system/stargz-snapshotter.service https://raw.githubusercontent.com/containerd/stargz-snapshotter/main/script/config/etc/systemd/system/stargz-snapshotter.service</span><br><span class="line">systemctl <span class="built_in">enable</span> --now stargz-snapshotter</span><br></pre></td></tr></table></figure></div>

<p>conatinerdのconfigを設定してデーモンを再起動します。</p>
<div class="code-block"><figure class="highlight toml"><input type="checkbox" id="code-wrap-1mg5vd-6" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1mg5vd-6" title="コードの折り返しを切り替える"></label><figcaption><span>/etc/containerd/config.toml</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="attr">version</span> = <span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Enable stargz snapshotter for CRI</span></span><br><span class="line"><span class="section">[plugins.&quot;io.containerd.grpc.v1.cri&quot;.containerd]</span></span><br><span class="line">  <span class="attr">snapshotter</span> = <span class="string">&quot;stargz&quot;</span></span><br><span class="line">  <span class="attr">disable_snapshot_annotations</span> = <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Plug stargz snapshotter into containerd</span></span><br><span class="line"><span class="section">[proxy_plugins]</span></span><br><span class="line">  <span class="section">[proxy_plugins.stargz]</span></span><br><span class="line">    <span class="attr">type</span> = <span class="string">&quot;snapshot&quot;</span></span><br><span class="line">    <span class="attr">address</span> = <span class="string">&quot;/run/containerd-stargz-grpc/containerd-stargz-grpc.sock&quot;</span></span><br></pre></td></tr></table></figure></div>

<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">systemctl restart containerd</span><br></pre></td></tr></table></figure>

<p>soci-snapshotterも大体同様です。</p>
<h3 id="実験用のコード">実験用のコード</h3><p>LLMのモデルをロードし、文を生成させるだけのスクリプトを用意します。</p>
<div class="code-block"><figure class="highlight py"><input type="checkbox" id="code-wrap-1mg5vd-7" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1mg5vd-7" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> transformers <span class="keyword">import</span> pipeline</span><br><span class="line"></span><br><span class="line">pipe = pipeline(task=<span class="string">&quot;text-generation&quot;</span>, model=<span class="string">&quot;Qwen/Qwen2-0.5B-Instruct&quot;</span>)</span><br><span class="line">out = pipe(<span class="string">&quot;生命、宇宙、そして万物についての究極の疑問の答え&quot;</span>, max_new_tokens=<span class="number">20</span>)[<span class="number">0</span>][<span class="string">&#x27;generated_text&#x27;</span>]</span><br><span class="line"><span class="built_in">print</span>(out)</span><br></pre></td></tr></table></figure></div>

<p>コンテナのエントリポイント用のスクリプトを用意します。イメージをpullする速度とPython実行の速度を分けて計測したい意図でshellscriptでラップすることにしました。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="built_in">set</span> -e</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;=== [START] <span class="subst">$(date --iso-8601=seconds)</span> ===&quot;</span></span><br><span class="line">START=$(<span class="built_in">date</span> +%s)</span><br><span class="line"></span><br><span class="line"><span class="keyword">time</span> python run.py</span><br><span class="line"></span><br><span class="line">END=$(<span class="built_in">date</span> +%s)</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;=== [END] <span class="subst">$(date --iso-8601=seconds)</span> ===&quot;</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Duration: <span class="subst">$((END - START)</span>) seconds&quot;</span></span><br></pre></td></tr></table></figure>

<p>以上をコンテナ化します。ここでLLMのモデルをあらかじめpullしてイメージに同梱しておきます。アンチパターンな気もしますが…。</p>
<div class="code-block"><figure class="highlight dockerfile"><input type="checkbox" id="code-wrap-1mg5vd-8" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1mg5vd-8" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">FROM</span> python:<span class="number">3.11</span>-slim</span><br><span class="line"></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> apt update &amp;&amp; apt install -y git &amp;&amp; \</span></span><br><span class="line"><span class="language-bash">    pip install --no-cache-dir torch transformers</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># キャッシュとしてモデルを事前にpullして含める</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> python -c <span class="string">&quot;from transformers import pipeline; \</span></span></span><br><span class="line"><span class="string"><span class="language-bash">    pipeline(task=&#x27;text-generation&#x27;, model=&#x27;Qwen/Qwen2-0.5B-Instruct&#x27;)&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> run.py .</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> entrypoint.sh .</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">chmod</span> +x entrypoint.sh</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;./entrypoint.sh&quot;</span>]</span></span><br></pre></td></tr></table></figure></div>

<p>ちなみにイメージサイズは5GBくらいでした。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-1mg5vd-9" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1mg5vd-9" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">nerdctl  images</span><br><span class="line">REPOSITORY                                           TAG               IMAGE ID        CREATED        PLATFORM       SIZE       BLOB SIZE</span><br><span class="line">foobar.dkr.ecr.ap-northeast-1.amazonaws.com/foobar    overlayfs    728928885b94    9 hours ago    linux/amd64    7.929GB    4.755GB</span><br></pre></td></tr></table></figure></div>

<p>それぞれのsnapshotterに対応させるためイメージフォーマットの変換や、インデックスの作成します。</p>
<div class="code-block"><figure class="highlight bash"><input type="checkbox" id="code-wrap-1mg5vd-10" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1mg5vd-10" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># stargz フォーマットに変換</span></span><br><span class="line">nerdctl image convert --estargz --oci foobar.dkr.ecr.ap-northeast-1.amazonaws.com/foobar  :latest foobar.dkr.ecr.ap-northeast-1.amazonaws.com/foobar:estargz</span><br><span class="line">nerdctl push foobar.dkr.ecr.ap-northeast-1.amazonaws.com/foobar:estargz</span><br></pre></td></tr></table></figure></div>

<h3 id="計測項目">計測項目</h3><p>以下を計測します。</p>
<ol>
<li>イメージpull + containerd初期化時間（<code>time nerdctl run</code>）</li>
<li>コンテナ内でLLMが起動して実行終了するまでの時間（entrypoint.shで計測）</li>
</ol>
<p>各snapshotterでの実行コマンドは以下の通りです。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-1mg5vd-11" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1mg5vd-11" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># overlayfs（通常）</span></span><br><span class="line"><span class="keyword">time</span> nerdctl --snapshotter=overlayfs run --<span class="built_in">rm</span> \</span><br><span class="line">  foobar.dkr.ecr.ap-northeast-1.amazonaws.com/foobar:overlayfs</span><br><span class="line"></span><br><span class="line"><span class="comment"># stargz</span></span><br><span class="line"><span class="keyword">time</span> nerdctl --snapshotter=stargz run --<span class="built_in">rm</span> \</span><br><span class="line">  foobar.dkr.ecr.ap-northeast-1.amazonaws.com/foobar:stargz</span><br><span class="line"></span><br><span class="line"><span class="comment"># soci</span></span><br><span class="line"><span class="keyword">time</span> nerdctl --snapshotter=soci run --<span class="built_in">rm</span> \</span><br><span class="line">  foobar.dkr.ecr.ap-northeast-1.amazonaws.com/foobar:soci</span><br></pre></td></tr></table></figure></div>

<h2 id="結果">結果</h2><p>結果はこんな感じでした。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>snapshotter</th>
<th>pull時間</th>
<th>LLM実行時間</th>
<th>合計時間</th>
</tr>
</thead>
<tbody><tr>
<td>overlayfs</td>
<td>101.8s</td>
<td>9.6s</td>
<td>111.4s</td>
</tr>
<tr>
<td>stargz-snapshotter</td>
<td>1.4s</td>
<td>195.1s</td>
<td>196.5s</td>
</tr>
<tr>
<td>soci-snapshotter</td>
<td>0.5s</td>
<td>73.5s</td>
<td>74.0s</td>
</tr>
</tbody></table></div>
<p>sociは速くなってますが、stargzはむしろ遅くなってますね！</p>
<p>結局イメージサイズに対し支配的なcudaやpytorch系のライブラリへのアクセスが必要なので、総実行時間としてはガンっと速くなったりはせずむしろ遅くなるケースもあるということなんでしょうか。チューニングの余地はありそうです。</p>
<p>検証は若干いい加減なので追試が待たれます。</p>
<p>ちなみに <code>echo hello</code> みたいなコマンドを実行させるケースだと stargz、sociは一瞬で終了しました。試してないですがWebサーバーの起動などだと、遅延読み込みの嬉しさが感じられるのかもしれません。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-1mg5vd-12" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1mg5vd-12" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># stargz</span></span><br><span class="line"><span class="keyword">time</span> nerdctl --snapshotter=stargz run --<span class="built_in">rm</span> \</span><br><span class="line">  foobar.dkr.ecr.ap-northeast-1.amazonaws.com/foobar:stargz <span class="built_in">echo</span> hello</span><br></pre></td></tr></table></figure></div>

<h2 id="おわりに">おわりに</h2><p>LLMサーバーをホストするにあたって、コンテナ起動を高速化するLazy-pulling技術について検証しました。sociはAWSで導入しやすいし効果もありそうなのでいいなと思いました。</p>
<p>本ブログのネタの構成には以下のKubeCon + CloudNativeCon Japan 2025セッションを参考にしました（現地参加しておらず見ていないのですが）。</p>
<ul>
<li>KubeCon + CloudNativeCon Japan 2025: Zero-Extraction Cold Starts: How FUSE-St…</li>
</ul>
<p>KubeConセッションと同じ内容のブログ記事も公開されています。</p>
<ul>
<li>25x Faster Cold Starts for LLMs on Kubernetes</li>
</ul>
]]></content>
    <summary type="html">ECSなどでコンテナの起動を高速化することを目標に、イメージを遅延して読み込むLazy-pullingな技術に目を向け、LLMコンテナに対し効果があるのかどうかを検証していきます。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="CNCF" scheme="https://future-architect.github.io/tags/CNCF/"/>
    <category term="Docker" scheme="https://future-architect.github.io/tags/Docker/"/>
    <category term="ECS" scheme="https://future-architect.github.io/tags/ECS/"/>
    <category term="KubeCon" scheme="https://future-architect.github.io/tags/KubeCon/"/>
    <category term="LLM" scheme="https://future-architect.github.io/tags/LLM/"/>
    <category term="コンテナ" scheme="https://future-architect.github.io/tags/%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A/"/>
  </entry>
  <entry>
    <title>GitHub ActionsのCI/CDパイプラインに静的コード解析Sonar Qubeを組み込んでみた</title>
    <link href="https://future-architect.github.io/articles/20250619a/"/>
    <id>https://future-architect.github.io/articles/20250619a/</id>
    <published>2025-06-18T15:00:00.000Z</published>
    <updated>2025-06-18T15:00:00.000Z</updated>
    <author><name>松本朝香</name></author>
    <content type="html"><![CDATA[<p>CI&#x2F;CD連載 5本目の記事です。</p>
<h2 id="はじめに">はじめに</h2><p>１か月前、情報安全確保支援士を受験してきまして、問題の選択肢にあった “SonarQube” という初耳ワードがどんなOSSなのか気になりました。タイミングよくこの『CI&#x2F;CD pipeline』企画を目にしたので、これは私に書けと言っている！と思い、このブログ記事の題材にすることに決め、ミニマム版を実装構築してみました(&#x2F;・ω・)&#x2F;</p>
<p>ちなみに今回（令和7年春）の問題には、弊社セキュリティプロダクトの Future Vuls の得意分野である脆弱性管理の大問がそっくり出ていて、「これを機に Future Vuls の導入を検討してみては？」と回答に書いても丸もらえるのか試したかったです（さすがに試していない）。</p>
<h2 id="SonarQubeとは？">SonarQubeとは？</h2><p>SonarQubeとは、SonarSourceが開発したオープンソースプラットフォームで、29のプログラミング言語（2025年5月現在）において、コードの静的解析による自動レビューと継続的なコード品質検査をし、バグやコードスメルを観測できます。具体的には、重複コード、コーディング規約、単体テスト、コードカバレッジ、コードの複雑さ、コメント、バグ、セキュリティ推奨事項に関するレポートを提供してくれるソフトウェアです。</p>
<ul>
<li>OSS originalはこちら → https://github.com/SonarSource/sonarqube</li>
</ul>
<p>※無料で使う場合、制限として解析できるのは公開リポジトリのみに限られます。プライベートリポジトリは解析できません。</p>
<h2 id="SonarQube-Cloud">SonarQube Cloud</h2><ul>
<li>SonarQube Cloud Online Code Review as a Service Tool  | Sonar</li>
</ul>
<p>今回は、SonarQubeのクラウド版である <strong>SonarQube Cloud</strong> を使用します。GitHubアカウントがあれば、GitHub ActionsのCI&#x2F;CDパイプラインにSonarQube（またはそのクラウド版であるSonarCloud）を導入する準備は整っています。どちらを利用したいかによって手順が少し異なりますが、一般的にSonarCloudの方がGitHubとの連携が簡単で、サーバー管理の手間もないため、特に初めての方や公開リポジトリでの利用におすすめです。</p>
<p>公式サイトの Docs が充実しているため、こちらを一次情報として読みこんでいきます。</p>
<ul>
<li>Getting started with GitHub | SonarQube Cloud Documentation</li>
</ul>
<h2 id="基本構想">基本構想</h2><p>基本的に、解析対象コードとテストコードは以下の理由から <strong>同一言語</strong> で書いた方がよいです。</p>
<ul>
<li>カバレッジ取得が容易で、静的解析と一元管理できる</li>
<li>エコシステムが統一されていて、ツール連携しやすい</li>
<li>上記に付随して解析精度が向上する</li>
</ul>
<p>今回はPythonで進めていきます。</p>
<h3 id="最低限必要なファイル群">最低限必要なファイル群</h3><p>必須のファイル</p>
<ol>
<li>解析対象Pythonソースコード（<code>.py</code>）</li>
<li>CI&#x2F;CDワークフローファイル</li>
</ol>
<p>上記がないと始まりません。（２）はSonarCloudスキャンを実行するための自動化スクリプトです。使用するCI&#x2F;CDサービスによってファイル名や場所、形式が異なります。</p>
<ul>
<li>GitHub Actionsの場合の例： <code>.github/workflows/XXX.yml</code></li>
</ul>
<p>このファイルには、リポジトリのチェックアウト、Python環境のセットアップ、依存関係のインストール（オプション）、テストとカバレッジレポートの生成（推奨）、そしてSonarCloudスキャナの実行といったステップを記述します。</p>
<h4 id="強く推奨されるファイル（解析の品質と有用性を大幅に向上させるため）">強く推奨されるファイル（解析の品質と有用性を大幅に向上させるため）</h4><ul>
<li>テストコードファイル (<code>test_*.py</code> など)<br>ユニットテストやインテグレーションテストのコードのこと。コードの品質を保証し、カバレッジを測定できる。 （例） <code>tests/test_main.py</code> <code>tests/test_utils.py</code></li>
<li>カバレッジレポートファイル (CI&#x2F;CDパイプラインで生成)<br>テストがコードのどの程度をカバーしているかを示すレポート。SonarCloudはこの情報を表示する。通常、CI&#x2F;CDのテスト実行ステップで生成され、SonarCloudスキャナに渡される。ex. <code>coverage.xml</code> (<code>pytest-cov</code> や <code>coverage.py</code> で生成)<br>※このファイルはリポジトリに直接コミットするものではなく、CI&#x2F;CDプロセス中に生成され、 <code>.gitignore</code> に追加されるのが一般的です。しかし、CI&#x2F;CDワークフローで「生成してSonarCloudに渡す」という設定が不可欠。</li>
<li><code>.gitignore</code> ファイル<br>Gitが追跡すべきでないファイルやディレクトリ（例: <code>__pycache__</code> <code>.venv</code> <code>*.pyc</code> <code>coverage.xml</code> など）を指定する。これにより、不要なファイルがSonarCloudによって解析されるのを防ぎ、解析のノイズを減らす。</li>
</ul>
<h4 id="オプションだが有用なファイル">オプションだが有用なファイル</h4><ul>
<li>依存関係定義ファイル<br>プロジェクトが依存しているライブラリとそのバージョンをリスト化したファイル。(例) <code>requirements.txt</code> <code>pyproject.toml</code> (PoetryやPDMを使用している場合)<br>SonarCloudがプロジェクトの構成をより深く理解するのに役立ち、将来的には依存関係の脆弱性チェック機能（もしあれば）との連携にも繋がる可能性がある。</li>
<li><code>sonar-project.properties</code> ファイル (オプション)<br>リポジトリのルートディレクトリにこのファイルを置くことで、SonarCloudのプロジェクト設定（プロジェクトキー、ソースディレクトリ、エンコーディング、除外設定など）をCI&#x2F;CDワークフローファイルとは別に管理可能。設定項目が多い場合や、複数のCI&#x2F;CD環境で同じ設定を使いたい場合に便利である。これがなくても、CI&#x2F;CDワークフローファイル内のSonarScannerの引数で設定を指定できるが、ファイルとして記載しておくのが無難である。（※今回も配置済み）</li>
</ul>
<h3 id="全体ファイル構成">全体ファイル構成</h3><p>今回は下記のように作成しました。ミニマム構成でシンプルですね！</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-1kofi9h-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1kofi9h-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">PJ_ROUTE/</span><br><span class="line">├── .github/</span><br><span class="line">│   └── workflow/</span><br><span class="line">│       ├── ci_pipeline.yml      <span class="comment"># CI (テスト、カバレッジ、SonarCloud解析) ワークフロー</span></span><br><span class="line">│       └── cd_pipeline.yml      <span class="comment"># CD ワークフロー</span></span><br><span class="line">│</span><br><span class="line">├── pixel_art_app.py             <span class="comment"># あなたの提供したメインのPythonスクリプト</span></span><br><span class="line">|―― screenshot.py                <span class="comment"># 今回テスト実行にあたり作成したダミーファイル</span></span><br><span class="line">│</span><br><span class="line">├── tests/                       <span class="comment"># テストコードを格納するディレクトリ</span></span><br><span class="line">│   ├── __init__.py              <span class="comment"># testsディレクトリをパッケージとして認識させる</span></span><br><span class="line">│   └── test_pixel_art_app.py    <span class="comment"># pixel_art_app.py のテストコード</span></span><br><span class="line">│</span><br><span class="line">├── requirements.txt             <span class="comment"># 必要なPythonパッケージリスト</span></span><br><span class="line">├── sonar-project.properties     <span class="comment"># SonarCloud 解析用の設定ファイル</span></span><br><span class="line">├── .gitignore                   <span class="comment"># Gitで追跡しないファイルやディレクトリを指定</span></span><br><span class="line">├── pytest.ini                   <span class="comment"># (オプション) pytest の設定ファイル</span></span><br><span class="line">└── README.md                    <span class="comment"># プロジェクトの説明ファイル</span></span><br></pre></td></tr></table></figure></div>

<h2 id="使用するソフトウェアアカウントの登録">使用するソフトウェアアカウントの登録</h2><h3 id="Github-account-のサインイン">Github account のサインイン</h3><p>新規開設方法については割愛します。</p>
<h3 id="SonarCloud-account-のサインイン">SonarCloud account のサインイン</h3><h4 id="１．SonarCloudのアカウント作成とプロジェクト設定">１．SonarCloudのアカウント作成とプロジェクト設定</h4><p>SonarCloud のウェブサイトにアクセスし、[Log in] or [Sign up] &gt; [GitHub] を選択してGitHubアカウントで連携・登録する。</p>
<img fetchpriority="high" src="/images/2025/20250619a/sonarqube_03.png" alt="sonarqube_03.png" width="1200" height="366">
<div style="display: flex; align-items: flex-start;">
  <img src="/images/2025/20250619a/sonarqube_04.png" alt="sonarqube_04.png" width="603" height="843" loading="lazy">
  <img src="/images/2025/20250619a/sonarqube_05.png" alt="sonarqube_05.png" width="529" height="939" loading="lazy">
</div>

<h4 id="２．解析対象リポジトリの選択">２．解析対象リポジトリの選択</h4><p>SonarCloudのダッシュボードで [Analyze new project] (または[+]メニュー &gt; [Analyze new project]) をクリックし、GitHub Organizationを選択後、解析したいリポジトリを選択して [Set Up] をクリック。通常は「With GitHub Actions」が推奨されるので、それを選択する。</p>
<img src="/images/2025/20250619a/sonarqube_15.png" alt="sonarqube_15.png" width="1200" height="315" loading="lazy">

<p>この段階で、SonarCloud側にプロジェクトが作成されるため、以下の情報をメモしておきます。（※いつでも確認できるので見逃しても問題ない）</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="center">キー</th>
<th align="center">説明</th>
</tr>
</thead>
<tbody><tr>
<td align="center">Organization Key</td>
<td align="center">SonarCloudの組織名（通常はGitHubの組織名やユーザー名）</td>
</tr>
<tr>
<td align="center">Project Key</td>
<td align="center">SonarCloudがリポジトリに対して自動生成したキー（通常は GitHubユーザー名_リポジトリ名 のような形式）</td>
</tr>
</tbody></table></div>
<h4 id="３．SonarCloudトークンの生成とGitHub-Secretsへの登録">３．SonarCloudトークンの生成とGitHub Secretsへの登録</h4><p>SonarCloudの画面右上の自分のアイコン &gt; [My Account] &gt; [Security] に移動。[Generate Tokens] セクションで、トークン名（例: GITHUB_ACTIONS_TOKEN）を入力し、[Generate] をクリックする。生成されたトークンが表示されるので、必ずコピーして安全な場所に一時保管してください。<br>※この画面を閉じると二度と表示されません!!</p>
<img src="/images/2025/20250619a/sonarqube_10.png" alt="sonarqube_10.png" width="1200" height="562" loading="lazy">

<h4 id="４．TOKENの登録">４．TOKENの登録</h4><p>GitHubリポジトリに移動。[Settings] &gt; [Secrets and variables] &gt; [Actions] を選択する。[New repository secret] ボタンをクリックし、Name に 今回使用した名称 “SONAR_TOKEN” を入力する。Secret または Value に、先ほどSonarCloudで生成・コピーしたトークンを貼り付け、[Add secret] ボタンをクリックする。</p>
<img src="/images/2025/20250619a/sonarqube_11.png" alt="sonarqube_11.png" width="1200" height="614" loading="lazy">

<h2 id="構築手順">構築手順</h2><p>それでは、具体的に設定ファイルの作成および設定に移ります！</p>
<h3 id="GitHub-Actions-CI-pipeline-SonarCloud-による解析">GitHub Actions CI pipeline &amp; SonarCloud による解析</h3><p>先にこちらを構築します。</p>
<h4 id="１．github-workflows-ディレクトリの作成">１．github&#x2F;workflows ディレクトリの作成</h4><p>CI&#x2F;CDを設定したいGitHubリポジトリのルート（一番上の階層）に、 <code>.github</code> という名前のディレクトリを作成します。さらにその中に <code>workflows</code> という名前のディレクトリを作成します。最終的なパスは <code>.github/workflows/</code> となります。GitHub Actionsはこのディレクトリ内のYAMLファイルを自動的に認識します。</p>
<h4 id="２．ワークフローファイルの作成">２．ワークフローファイルの作成</h4><p>yamlの書き方など参考にすべく、初心者は GitHub Actions のテンプレートを使用するのが分かりやすいです。下記、 [Actions] &gt; [Simple workflow] &gt; [Configure] から作成できます。</p>
<img src="/images/2025/20250619a/github-cicd_02.png" alt="github-cicd_02.png" width="1200" height="585" loading="lazy">

<p>静的解析をするためのワークフローファイルを別で分けて、<strong>CI用, Sonarcloud用, CD用</strong> と3つ作成しても問題ありませんが、解析もテストと共に検査した方がよいという考えのもと、CIと同一ファイルにSonarcloudのワークフローファイルを記載することが一般的なようです。</p>
<div class="code-block"><figure class="highlight yaml"><input type="checkbox" id="code-wrap-1kofi9h-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1kofi9h-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">CI</span> <span class="bullet">-</span> <span class="string">Python</span> <span class="string">Build,</span> <span class="string">Test,</span> <span class="string">SonarCloud</span> <span class="string">Analysis</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">branches:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">main</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;feature/**&#x27;</span></span><br><span class="line">      <span class="comment"># Other branch patterns you want to run CI for</span></span><br><span class="line">  <span class="attr">pull_request:</span></span><br><span class="line">    <span class="attr">types:</span> [<span class="string">opened</span>, <span class="string">synchronize</span>, <span class="string">reopened</span>]</span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">build_test_analyze:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">Build,</span> <span class="string">Test,</span> <span class="string">Analyze</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span> <span class="string">code</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">fetch-depth:</span> <span class="number">0</span></span><br><span class="line">          <span class="comment"># SonarCloud needs full history for accurate analysis, however, specified shallow clone this time.</span></span><br><span class="line"></span><br><span class="line">      <span class="comment"># --------------------------------------------------------------------</span></span><br><span class="line">      <span class="comment"># Python Environment Setup</span></span><br><span class="line">      <span class="comment"># --------------------------------------------------------------------</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Setup</span> <span class="string">Python</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/setup-python@v4</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">python-version:</span> <span class="string">&#x27;3.9&#x27;</span></span><br><span class="line">          <span class="comment"># preferable the same version with sonar.python.version parameter in sonar-project.properties file.</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">Python</span> <span class="string">dependencies</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          python -m pip install --upgrade pip</span></span><br><span class="line"><span class="string">          pip install -r requirements.txt</span></span><br><span class="line"><span class="string">          pip install pytest pytest-cov</span></span><br><span class="line"><span class="string"></span></span><br><span class="line">      <span class="comment"># (Install Python dependencies ステップの後、Run tests ステップの前に追加)</span></span><br><span class="line">      <span class="comment">#- name: Show workspace structure and PYTHONPATH</span></span><br><span class="line">      <span class="comment">#  run: |</span></span><br><span class="line">      <span class="comment">#    echo &quot;Current working directory: $(pwd)&quot;</span></span><br><span class="line">      <span class="comment">#    echo &quot;-------------------------------------&quot;</span></span><br><span class="line">      <span class="comment">#    echo &quot;Listing files in workspace ($&#123;&#123; github.workspace &#125;&#125;):&quot;</span></span><br><span class="line">      <span class="comment">#    ls -R $&#123;&#123; github.workspace &#125;&#125;</span></span><br><span class="line">      <span class="comment">#    echo &quot;-------------------------------------&quot;</span></span><br><span class="line">      <span class="comment">#    echo &quot;PYTHONPATH environment variable is: $PYTHONPATH&quot;</span></span><br><span class="line">      <span class="comment">#    echo &quot;-------------------------------------&quot;</span></span><br><span class="line">      <span class="comment">###</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Run</span> <span class="string">tests</span> <span class="string">and</span> <span class="string">generate</span> <span class="string">coverage</span> <span class="string">report</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">pytest</span> <span class="string">--cov=.</span> <span class="string">--cov-report=xml:coverage.xml</span></span><br><span class="line">        <span class="attr">env:</span></span><br><span class="line">          <span class="attr">PYTHONPATH:</span> <span class="string">$&#123;&#123;</span> <span class="string">github.workspace</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="comment"># added PJ_ROUTE path to PYTHONPATH</span></span><br><span class="line"></span><br><span class="line">      <span class="comment"># --------------------------------------------------------------------</span></span><br><span class="line">      <span class="comment"># SonarCloud Analysis</span></span><br><span class="line">      <span class="comment"># --------------------------------------------------------------------</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">SonarCloud</span> <span class="string">Scan</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">SonarSource/sonarcloud-github-action@master</span></span><br><span class="line">        <span class="attr">env:</span></span><br><span class="line">          <span class="attr">GITHUB_TOKEN:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.GITHUB_TOKEN</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="comment"># Required for Pull Request decoration</span></span><br><span class="line">          <span class="attr">SONAR_TOKEN:</span> <span class="string">$&#123;&#123;</span> <span class="string">secrets.SONAR_TOKEN</span> <span class="string">&#125;&#125;</span></span><br><span class="line">          <span class="comment"># SonarCloud token set in GitHub Secrets</span></span><br><span class="line"></span><br><span class="line">      <span class="comment"># --------------------------------------------------------------------</span></span><br><span class="line">      <span class="comment"># Upload Artifacts (Optional, for inspection)</span></span><br><span class="line">      <span class="comment"># --------------------------------------------------------------------</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Upload</span> <span class="string">XML</span> <span class="string">coverage</span> <span class="string">report</span> <span class="string">(for</span> <span class="string">SonarCloud</span> <span class="string">and</span> <span class="string">inspection)</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/upload-artifact@v4</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">coverage-xml</span></span><br><span class="line">          <span class="attr">path:</span> <span class="string">coverage.xml</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Upload</span> <span class="string">HTML</span> <span class="string">coverage</span> <span class="string">report</span> <span class="string">(for</span> <span class="string">local</span> <span class="string">inspection)</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/upload-artifact@v4</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">name:</span> <span class="string">coverage-html</span></span><br><span class="line">          <span class="attr">path:</span> <span class="string">htmlcov/</span></span><br></pre></td></tr></table></figure></div>

<div class="note-container note-info note-has-title"><div class="note-title"><span class="note-icon"></span>Shallow Clone とは？</div><div class="note-body">

<p>“Gitリポジトリをクローン（複製）する際に、リポジトリの完全な履歴を取得する” という意味です。通常、CI&#x2F;CD環境ではビルド時間を短縮するために、Gitリポジトリの最新のコミットから数個分だけを取得する「シャロークローン」が行われることがあります。例えば、「最新の1コミットだけ取得する」といった設定です。これにより、ダウンロードするデータ量が減り、クローンにかかる時間が短縮されます。GitHub Actionsの actions&#x2F;checkout アクションでは、デフォルトでシャロークローン（fetch-depth: 1、つまり最新の1コミットのみ）が行われます。これを無効化（fetch-depth: 0）することで、SonarCloud (や SonarQube) は、ソースコードを静的解析する際に、コードの変更履歴を深く分析でき、より多くの有益な情報を提供します。</p>
</div></div>

<div class="note-container note-info note-has-title"><div class="note-title"><span class="note-icon"></span>ワークフローファイルのカスタマイズ</div><div class="note-body">

<p>トリガー (on:): どのブランチへの push や pull_request で実行したいか、または特定のタグが作成されたときなど、実行条件を細かく設定できます。</p>
</div></div>

<h4 id="３．sonar-project-properties-ファイルの作成">３．sonar-project.properties ファイルの作成</h4><p>SonarCloud (または SonarQube) が対象プロジェクトをどのようにスキャン（解析）すべきかを指示するための設定ファイルです。SonarScanner（コードをスキャンして SonarCloud に結果を送るツール）が実行される際に、このファイルを読み込み、プロジェクトの基本的な情報や解析のパラメータを取得します。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-1kofi9h-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1kofi9h-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># --- SonarCloud 必須設定 ---　※SonarCloud側で確認可能</span></span><br><span class="line">sonar.projectKey=ask-lycoris_.github-workflows-</span><br><span class="line">sonar.organization=ask-lycoris</span><br><span class="line"></span><br><span class="line"><span class="comment"># --- 基本設定 ---</span></span><br><span class="line">sonar.projectName=Pixel Art Project</span><br><span class="line"><span class="comment"># ソースコードのディレクトリ (カレントディレクトリを指す)</span></span><br><span class="line">sonar.sources=.</span><br><span class="line">sonar.host.url=https://sonarcloud.io</span><br><span class="line"></span><br><span class="line"><span class="comment"># --- 言語設定 (自動検出されることが多いが、明示も可能) ---</span></span><br><span class="line"><span class="comment"># sonar.language=js # 例: JavaScript</span></span><br><span class="line"><span class="comment"># sonar.java.source=11 # 例: Javaのソースバージョン</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># --- エンコーディング ---</span></span><br><span class="line">sonar.sourceEncoding=UTF-8</span><br><span class="line"></span><br><span class="line"><span class="comment"># --- Python固有設定 ---</span></span><br><span class="line"><span class="comment"># sonar.language=py # 言語を明示的に指定することも可能 (通常は自動検出)</span></span><br><span class="line"><span class="comment"># CIで使用するPythonバージョンと合わせる。複数指定も可能。</span></span><br><span class="line">sonar.python.version=3.8,3.9,3.10,3.11</span><br><span class="line">sonar.tests=tests</span><br><span class="line"></span><br><span class="line"><span class="comment"># --- テストカバレッジレポートのパス (CIで生成したレポートを指定) ---</span></span><br><span class="line"><span class="comment"># Pythonの場合、一般的に coverage.py (または pytest-cov) で生成されたXMLレポートを指定します。</span></span><br><span class="line">sonar.python.coverage.reportPaths=coverage.xml</span><br><span class="line"></span><br><span class="line"><span class="comment"># --- 除外設定 (必要に応じて) ---</span></span><br><span class="line"> sonar.exclusions=venv/**, .venv/**, __pycache__/**, tests/**</span><br><span class="line"><span class="comment"># テスト自体はカバレッジ対象から除外する</span></span><br><span class="line"> sonar.coverage.exclusions=tests/**</span><br><span class="line"></span><br><span class="line"><span class="comment"># --- Javascriptの場合：テストカバレッジレポートのパス (CIで生成したレポートを指定) ---</span></span><br><span class="line"><span class="comment"># sonar.javascript.lcov.reportPaths=coverage/lcov.info # JavaScript/TypeScriptの場合</span></span><br><span class="line"><span class="comment"># sonar.java.coveragePlugin=jacoco</span></span><br><span class="line"><span class="comment"># sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml # Java/JaCoCoの場合</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># --- その他のプロジェクト固有設定 ---</span></span><br><span class="line"><span class="comment"># sonar.verbose=true # 詳細ログ (デバッグ用)</span></span><br></pre></td></tr></table></figure></div>

<div class="note-container note-warn note-has-title"><div class="note-title"><span class="note-icon"></span>記載する上での注意点</div><div class="note-body">

<p>完成したワークフローがパースの影響で誤認識され回らなかった時がありました。</p>
<img src="/images/2025/20250619a/image.png" alt="image.png" width="1156" height="242" loading="lazy">

<p>特に <code>sonar.tests=tests # ...</code> のようにプロパティ値の直後に # でコメントを続けている場合、# がパスの一部として解釈されたり、あるいはその前のスペースと合わせて問題を引き起こすことがあるようです。最も安全なのは、プロパティ定義の行には値のみを記述し、コメントは独立した行に記述することです。</p>
</div></div>

<h4 id="４．解析対象レポジトリの準備">４．解析対象レポジトリの準備</h4><p>今回は、外部通信のないスタンドアロンのソースコード（Python）を解析対象とします。SonarCloud は、テストカバレッジの結果やビルド情報を利用してより詳細に解析します。</p>
<ul>
<li>ビルドスクリプト： プロジェクトをビルドするためのスクリプト ( <code>package.json</code> の <code>scripts</code> <code>Makefile</code> <code>pom.xml</code> など) がリポジトリに含まれていることを確認します。</li>
<li>テストスクリプト： ユニットテストやインテグレーションテストを実行し、カバレッジレポートを生成するスクリプトを準備します。言語毎にカバレッジレポートを作成するためのライブラリが存在するため、それが出力されるように設定します。</li>
</ul>
<h4 id="５．ブランチ保護ルールの設定-推奨">５．ブランチ保護ルールの設定 (推奨)</h4><p>コードの品質を確保し、main ブランチにマージする前に CI チェックがパスするように設定します。[GitHub リポジトリ] &gt; [Settings] &gt; [Branches] に移動し、[Require status checks to pass before merging]を有効化し、main ブランチに対するブランチ保護ルールを追加します。今回は下記のようにしました。</p>
<img src="/images/2025/20250619a/sonarqube_17.png" alt="sonarqube_17.png" width="736" height="876" loading="lazy">

<p>CI パイプラインから 該当ジョブ を選択し、Require pull request reviews before merging (マージ前にプルリクエストレビューを必須にする) も有効にします。</p>
<h4 id="６．ワークフローファイルのコミット＆プッシュと結果確認">６．ワークフローファイルのコミット＆プッシュと結果確認</h4><p>作成・編集したワークフローファイル (.github&#x2F;workflows&#x2F;ci.yml など) をリポジトリにコミットし、GitHubにプッシュします。</p>
<p>ワークフローファイルがリポジトリにプッシュされると、GitHubはそれを自動的に検出し、on: で指定したトリガー条件（例: main ブランチへのプッシュ）が発生した際にワークフローを実行します。リポジトリのメインページにある [Actions] タブをクリックすると、実行されたワークフローのリストと、それぞれの成功&#x2F;失敗ステータス、実行ログなどを確認できます。通るまで何度もトラブルシューティングを行います。</p>
<img src="/images/2025/20250619a/image_2.png" alt="image.png" width="1200" height="420" loading="lazy">

<img src="/images/2025/20250619a/6d498801-fde6-4da1-a043-e8d10eb1215d.png" alt="" width="931" height="667" loading="lazy">

<p>↓↓↓</p>
<img src="/images/2025/20250619a/github-cicd_03.png" alt="github-cicd_03.png" width="1134" height="258" loading="lazy">

<h4 id="７．SonarQube-Cloud-ルールセットの設定について">７．SonarQube Cloud ルールセットの設定について</h4><p>上記工程までで、Github ActionsとSonarQube Cloudの連携は完了ですが、SonarQube側でコード解析を全く行っていない状態（&#x3D;SASTとしての機能を果たしていない）のため、別途ルールセットの設定が必要です。</p>
<h3 id="QualityProfile-と-QualityGate-の違い">QualityProfile と QualityGate の違い</h3><ul>
<li><strong>Quality Profile：ルールブックそのもの</strong><br>静的コードを解析する際に、「どのような観点でコードを検査し、どんな問題を検出対象とするか」という具体的なルールセットを定義（&#x3D;ルールブック）します。プロジェクトや組織のコーディング標準、品質基準に基づいて、コードの潜在的な問題点を網羅的に洗い出すための「検査項目リスト」を作成する際に使用する機能です。通常、プログラミング言語ごとに設定します。</li>
<li><strong>Quality Gate：ルールブック結果の品質管理</strong><br>クオリティプロファイルに基づいて行われたコード解析の結果が、「プロジェクトとしてリリース（またはマージ）して良い品質基準を満たしているか」を判定するための条件セットです。プロジェクトが定める品質基準をクリアしているかどうかを自動的に判断し、基準を満たさない場合、ビルドを成功&#x2F;失敗させてコードが本番環境にデプロイされたり、メインブランチにマージされたりするのを防ぐ際に使用する機能です。通常、プロジェクトごと、または組織全体で共通のゲートを設定できます。</li>
</ul>
<h3 id="Quality-Profileの設定">Quality Profileの設定</h3><p>ルールセット の設定は、主にWeb UIから行います。SonarQube Cloudのプラットフォーム上で直接 Quality Profile を作成・編集し、プロジェクトに適用します。新しいルールを有効化したり、既存のルールの重要度を変更したりできます。下図のように、右上部から [Account page] &gt; [ADMIN] &gt; [Quality Profiles] へ移動します。</p>
<img src="/images/2025/20250619a/image_3.png" alt="image.png" width="1200" height="206" loading="lazy">

<img src="/images/2025/20250619a/image_4.png" alt="image.png" width="1200" height="524" loading="lazy">

<p>※下図のようにプロジェクト毎 Quality Profiles ページも存在するので、そちらではないため注意してください。無料枠内ではプロジェクト毎の設定はできません。</p>
<img src="/images/2025/20250619a/sonarqube_24_ruleset.png" alt="sonarqube_24_ruleset.png" width="1027" height="423" loading="lazy">

<h4 id="１．Quality-Profile-の選択または作成">１．Quality Profile の選択または作成</h4><p>各プログラミング言語ごとに定義されているQuality Profileの一覧が表示されており、デフォルトで「Sonar way」という推奨ルールセットが用意および設定されています。</p>
<p>「Sonar way」のような組み込みプロファイルは直接編集できません。編集するためには、コピーを作成する必要があります。（無料枠ではこれはできなさそう…）</p>
<h5 id="新しいプロファイルを作成する">新しいプロファイルを作成する</h5><p>Quality Profilesページの右上にある「Create」ボタンをクリックし、下記を入力していきます。Parentに指定すると、指定したQualityGateの内容を引き継いでProfileを作成してくれます。</p>
<img src="/images/2025/20250619a/image_5.png" alt="image.png" width="1200" height="465" loading="lazy">

<h5 id="設定したプロファイルをactivateする">設定したプロファイルをactivateする</h5><p>今回は、セキュリティ面での解析をしてみたいので➀で設定したルールの中で security に関わる項目を有効化してみました！→ 設定自体はできそうですが、無料枠内だとデプロイまで行きつかないので結局ここでの設定は反映できなかったです。</p>
<img src="/images/2025/20250619a/image_6.png" alt="image.png" width="1200" height="507" loading="lazy">

<h5 id="Quality-Profileをプロジェクトに割り当てる">Quality Profileをプロジェクトに割り当てる</h5><p>Quality Profileをカスタマイズしたら、それを解析対象のプロジェクトに割り当てる必要があります。</p>
<ul>
<li><strong>[方法1] Quality Profileのページから</strong><br>カスタマイズしたQuality Profileの詳細ページで、右上にある「Actions」（または歯車アイコンなど）メニューから「Set as Default」（その言語のデフォルトにする場合）または「Change Projects」のようなオプションを選択します。<br>「Change Projects」を選択した場合、このプロファイルを適用したいプロジェクトを検索して選択し、割り当てます。</li>
<li><strong>[方法2] プロジェクトの設定ページから</strong><br>SonarCloudで対象のプロジェクトページに移動します。<br>「Administration」 &gt; 「Quality Profiles」を選択します。<br>プロジェクトで使用されている各言語に対して、どのQuality Profileを使用するかのドロップダウンリストが表示されます。Pythonの項目で、先ほど作成またはカスタマイズしたQuality Profileを選択します。（無料枠では選択できず）</li>
</ul>
<img src="/images/2025/20250619a/image_7.png" alt="image.png" width="1003" height="946" loading="lazy">

<h4 id="２．Quality-Gate-の設定">２．Quality Gate の設定</h4><p>無料枠内では default: Sonarway 以外のプロジェクトに対する <strong>適用はできない</strong> ようですが、<strong>QualityGateの作成自体はできる</strong> ようなので興味のある方は是非手を動かしてみるとよいかもしれません。</p>
<img src="/images/2025/20250619a/image_8.png" alt="image.png" width="1200" height="711" loading="lazy">

<img src="/images/2025/20250619a/image_9.png" alt="image.png" width="791" height="477" loading="lazy">

<h3 id="GitHub-Actions-CD-パイプライン構築手順">GitHub Actions CD パイプライン構築手順</h3><p>CI（ビルドとテスト）が成功したら、自動的にアプリケーションをサーバーやクラウドサービスにデプロイするCDパイプラインも構築できます。CIジョブの後続としてデプロイ用のCDジョブを追加します。</p>
<h4 id="１．デプロイに際し必要な認証情報の設定">１．デプロイに際し必要な認証情報の設定</h4><p>デプロイ先（ex. GitHub Pages, AWS S3&#x2F;EC2&#x2F;ECS, Azure App Service, Google Cloud Run, Heroku）に応じた GitHub Actions (例: <code>actions/deploy-pages</code> <code>aws-actions/configure-aws-credentials</code> <code>azure/webapps-deploy</code>) や、デプロイ用のコマンド (<code>scp</code> <code>rsync</code> <code>docker push</code> など) をワークフローに追加します。</p>
<p>デプロイに必要なAPIキーやパスワードなどの認証情報は、セキュリティ上の理由からワークフローファイルに直接書き込まず、リポジトリの [Settings] &gt; [Secrets and variables] &gt; [Actions] で Secrets として安全に登録し、ワークフロー内から <code>$&#123;&#123; secrets.YOUR_SECRET_NAME &#125;&#125;</code> のように参照します。（一般的なやり方）</p>
<h4 id="２．ワークフローファイルの作成-1">２．ワークフローファイルの作成</h4><div class="code-block"><figure class="highlight yaml"><input type="checkbox" id="code-wrap-1kofi9h-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1kofi9h-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">CD</span> <span class="bullet">-</span> <span class="string">Deploy</span> <span class="string">Application</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">branches:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">main</span></span><br><span class="line">    <span class="comment"># Trigger deployment on push to main branch</span></span><br><span class="line">    <span class="comment"># Alternatively, you might trigger on tag creation:</span></span><br><span class="line">    <span class="comment"># tags:</span></span><br><span class="line">    <span class="comment">#   - &#x27;v*.*.*&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">deploy:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">Deploy</span> <span class="string">to</span> <span class="string">Production</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="comment"># Ensure CI has passed before deploying.</span></span><br><span class="line">    <span class="attr">environment:</span> <span class="string">production</span></span><br><span class="line">    <span class="comment"># Optional: Define a GitHub environment for protection rules and secrets</span></span><br><span class="line"></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span> <span class="string">code</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line"></span><br><span class="line">      <span class="comment"># --------------------------------------------------------------------</span></span><br><span class="line">      <span class="comment"># Setup Your Deployment Environment &amp; Deploy</span></span><br><span class="line">      <span class="comment"># Replace this section with steps specific to your deployment target</span></span><br><span class="line">      <span class="comment"># (e.g., AWS, Azure, Google Cloud, Heroku, Docker Hub, etc.)</span></span><br><span class="line">      <span class="comment"># --------------------------------------------------------------------</span></span><br><span class="line">      <span class="comment"># Example: Deploying a static site to AWS S3</span></span><br><span class="line">      <span class="comment"># - name: Configure AWS credentials</span></span><br><span class="line">      <span class="comment">#   uses: aws-actions/configure-aws-credentials@v4</span></span><br><span class="line">      <span class="comment">#   with:</span></span><br><span class="line">      <span class="comment">#     aws-access-key-id: $&#123;&#123; secrets.AWS_ACCESS_KEY_ID &#125;&#125;</span></span><br><span class="line">      <span class="comment">#     aws-secret-access-key: $&#123;&#123; secrets.AWS_SECRET_ACCESS_KEY &#125;&#125;</span></span><br><span class="line">      <span class="comment">#     aws-region: us-east-1</span></span><br><span class="line">      <span class="comment">#</span></span><br><span class="line">      <span class="comment"># - name: Build Project (if not done in CI or if artifacts are not passed)</span></span><br><span class="line">      <span class="comment">#   run: npm run build # Example build command</span></span><br><span class="line">      <span class="comment">#</span></span><br><span class="line">      <span class="comment"># - name: Deploy to S3</span></span><br><span class="line">      <span class="comment">#   run: aws s3 sync ./dist s3://your-s3-bucket-name --delete # Example S3 sync command</span></span><br><span class="line">      <span class="comment"># --------------------------------------------------------------------</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Placeholder</span> <span class="string">for</span> <span class="string">Actual</span> <span class="string">Deployment</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">echo</span> <span class="string">&quot;Deploying application...&quot;</span> <span class="comment"># Replace with your actual deployment commands</span></span><br></pre></td></tr></table></figure></div>

<h4 id="３．ワークフローのコミット＆プッシュと結果確認">３．ワークフローのコミット＆プッシュと結果確認</h4><p>作成・編集したワークフローファイル (cd_pipeline.yml) をリポジトリにコミット＆プッシュします。GitHubリポジトリの [Actions] タブを開き、ワークフローが実行されていることを確認し、ワークフローがエラーなく完了したことを確認してください。</p>
<img src="/images/2025/20250619a/image_10.png" alt="image.png" width="1200" height="422" loading="lazy">

<p>次に、SonarCloudのプロジェクトページにアクセスしてください。コードの品質、バグ、脆弱性などの解析結果が表示されていれば連携完了です！！Pull Requestを作成した場合、しばらくすると以下のように <strong>SonarCloudの解析結果</strong> がPull Requestのチェック欄やコメントとして表示されるようになります。</p>
<p>↓↓↓</p>
<p>今回は、無料枠内でできることを行ったため、SonarQube Cloudの静的解析を適用するまでには至らず結果としては “0” 表記となりましたが、連携して回してカバレッジ取得までは実装できました👏</p>
<img src="/images/2025/20250619a/image_11.png" alt="image.png" width="1200" height="610" loading="lazy">

<img src="/images/2025/20250619a/image_12.png" alt="image.png" width="1200" height="394" loading="lazy">

<h2 id="Sonar-Qubeの各評価項目">Sonar Qubeの各評価項目</h2><p>SonarCloudでは、主に以下のような項目でコードの品質を評価します。分析結果の解読に役立ててください。</p>
<ul>
<li><strong>Quality Gate (品質ゲート)</strong><br>意味: プロジェクトが定めた品質基準をクリアしているかどうかを示す。「Passed」（合格）なら基準を満たしており、「Failed」（不合格）なら問題あり。</li>
<li><strong>Bugs (バグ)</strong><br>意味: プログラムが正しく動かない可能性のある箇所、つまり「虫（バグ）」が存在する箇所の割合。</li>
<li><strong>Vulnerabilities (脆弱性 - ぜいじゃくせい)</strong><br>意味: セキュリティ上の弱点。悪意のある人（ハッカーなど）に攻撃される可能性がある箇所</li>
<li><strong>Security Hotspots (セキュリティホットスポット)</strong><br>意味: セキュリティ上、特に注意深く確認する必要がある箇所。必ずしも脆弱性とは限らないが、専門家によるチェックが推奨される部分の割合？</li>
<li><strong>Code Smells (コードの臭い &#x2F; 技術的負債)</strong><br>意味: 直接的なバグではないけれど、読みにくかったり、将来的に問題を引き起こしやすかったりする「良くない書き方」のコードの割合？。「技術的負債」とも呼ばれ、放置すると修正にかかる時間（xx日、xx時間などで表示）が増大する。</li>
<li><strong>Coverage (カバレッジ)</strong><br>意味: プログラムのテストが、コード全体のどれくらいの範囲をカバーできているかを示す割合（パーセンテージ）</li>
<li><strong>Duplications (重複)</strong><br>意味: 同じようなコードが複数箇所にコピー＆ペーストされている割合（パーセンテージ）や行数</li>
</ul>
<h2 id="おわりに">おわりに</h2><p>コンサルとして日々業務にあたっていると様々な事情により、テスト自動化やpipelineの構築などまだまだ自動化できていない企業が多いのかなと思います。しかし、ソフトウェアのセキュリティ対策は後付けでは対応しきれない部分も多く、DevSecOpsが昨今開発の主流となりつつあります。是非これを機に “CI&#x2F;CD pipelineの構築” と “SonarQubeの組み込み” をマスターし、社内でも一目置かれる人材になってみてはいかがでしょうか！？</p>
<h2 id="参考">参考</h2><ul>
<li>「入門】GitHub Actionsとは？概要やメリット、使用例まとめ - カゴヤのサーバー研究室</li>
<li>【入門】GitHubの使い方｜設定や基本操作など - カゴヤのサーバー研究室</li>
<li>getting-started | SonarQube Cloud Documentation</li>
</ul>
]]></content>
    <summary type="html">１か月前、情報安全確保支援士を受験してきまして、問題の選択肢にあった SonarQube という初耳ワードがどんなOSSなのか気になりました。タイミングよくこの『CI/CD pipeline』企画を目にしたので、これは私に書けと言っている！と思い、このブログ記事の題材にすることに決め、ミニマム版を実装構築してみました。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="CI/CD" scheme="https://future-architect.github.io/tags/CI-CD/"/>
    <category term="GitHubActions" scheme="https://future-architect.github.io/tags/GitHubActions/"/>
    <category term="静的解析" scheme="https://future-architect.github.io/tags/%E9%9D%99%E7%9A%84%E8%A7%A3%E6%9E%90/"/>
  </entry>
  <entry>
    <title>HyperDXを試す</title>
    <link href="https://future-architect.github.io/articles/20250618a/"/>
    <id>https://future-architect.github.io/articles/20250618a/</id>
    <published>2025-06-17T15:00:00.000Z</published>
    <updated>2025-06-17T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<p>CNCF連載 3日目はHyperDXを試してみたという記事です。とはいっても、HyperDX自体はCNCFに登録されたプロダクトでもなんでもないのですが(OpenTelemetry関連という接点はあり)、興味あったので試してみました。</p>
<p>HyperDXはいわゆるオブザーバービリティに属すプロダクトです。この領域のプロダクトはかなりのデータ量を扱う必要があったり、可用性のために、ストレージとビューアが分かれていたり、大量のツールと連携させる必要があったりします。</p>
<p>ELKスタックだと、ElasticsearchとLokiとKibana。GrafanaはLokiとPrometheusを連携させたり。OpenTelemetryのときによくデモされたJaegerはDBとビューを持ってトレースの機能を備えていてオールインワンです。</p>
<p>本番環境はSaaSにまるっと運用をお任せというのが良いとは思いますが、ローカルの作ったり壊したりする環境用良さそうなツールを探してみて見つけたのがHyperDXでした。</p>
<ul>
<li>ログ、メトリックス、トレースに対応</li>
<li>ストレージもUIも持っている</li>
<li>OpenTelemetry対応</li>
</ul>
<p>HyperDX自身はプロダクション環境でも使えるレベルのプロダクトのようです。管理画面のユーザー管理も厳しいパスワード条件の要求などもしてきます。今回はローカル開発で気軽に使いたい、という目的だったので、管理ユーザーもないローカル版を入れます。ユーザー管理付きのオールインワン版とか、クラウドのClickHouseストレージを使うバージョンとかもあります。</p>
<p>以下のDockerコマンドで起動します。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-fjri2p-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-fjri2p-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">docker run -p 4318:4318 -p 4317:4317 -p 8080:8080</span></span><br><span class="line">    -v ./mongodb:/data/db</span><br><span class="line">    docker.hyperdx.io/hyperdx/hyperdx-local</span><br><span class="line"></span><br><span class="line">(略)</span><br><span class="line"></span><br><span class="line">Send OpenTelemetry data via:</span><br><span class="line">  http/protobuf: OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318</span><br><span class="line">  gRPC: OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317</span><br><span class="line"></span><br><span class="line">Exporting data to ClickHouse:</span><br><span class="line">  Endpoint: tcp://ch-server:9000?dial_timeout=10s</span><br><span class="line">  Database: default</span><br><span class="line"></span><br><span class="line">Waiting for ClickHouse to be ready...</span><br><span class="line">ClickHouse is ready!</span><br><span class="line"></span><br><span class="line">Visit the HyperDX UI at http://localhost:8080</span><br></pre></td></tr></table></figure></div>

<p>ウェブのインターフェースが8080ポート、gRPCの情報受付が4317ポートです。</p>
<p>なお、このイメージは永続化可能なパスが3つあります。ローカルで気軽に使いたいという用途なのでログ自身は消えても良いので管理画面のデータだけ永続化するようにしています。</p>
<ul>
<li><code>/data/db</code> (HyperDXの管理画面で設定する情報を保持するMongoDBのデータ)</li>
<li><code>/var/lib/clickhouse</code> (バックエンドのDBのClickHouseのデータ)</li>
<li><code>/var/log/clickhouse-server</code> (バックエンドのDBのClickHouseのログ)</li>
</ul>
<h2 id="Pythonアプリケーションを作る">Pythonアプリケーションを作る</h2><p>OpenTelemetryで情報を出力するPythonアプリケーションを作ります。FastAPIで作ります。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> pysample</span><br><span class="line">uv init</span><br><span class="line">uv add fastapi --extra standard</span><br></pre></td></tr></table></figure>

<p>OpenTelemetryのトレースのspanを追加するために、traceパッケージを利用します。いまどきのOpenTelemetryはゼロコードコンフィグということで、接続先の情報はプログラムに書いたりしないという方法が追加されたようです。言語によって手法は違うようですが、モンキーパッチやeBPFなんかを利用するようです。なので接続情報はこのコードにはありません。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">uv add opentelemetry-api opentelemetry-sdk</span><br></pre></td></tr></table></figure>

<div class="code-block"><figure class="highlight py"><input type="checkbox" id="code-wrap-fjri2p-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-fjri2p-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> logging</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> FastAPI</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> opentelemetry <span class="keyword">import</span> trace</span><br><span class="line"></span><br><span class="line">app = FastAPI()</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> time <span class="keyword">import</span> perf_counter, sleep</span><br><span class="line"></span><br><span class="line"><span class="comment"># ここがエンドポイント本体！</span></span><br><span class="line"><span class="meta">@app.get(<span class="params"><span class="string">&quot;/&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">read_root</span>():</span><br><span class="line">    sleep(<span class="number">0.1</span>)</span><br><span class="line">    <span class="comment"># トレースを手動で設定</span></span><br><span class="line">    <span class="keyword">with</span> trace.get_tracer_provider().get_tracer(<span class="string">&quot;sleep&quot;</span>).start_as_current_span(__name__) <span class="keyword">as</span> span:</span><br><span class="line">        sleep(<span class="number">0.2</span>)</span><br><span class="line">        logging.info(<span class="string">&quot;read_root&quot;</span>)</span><br><span class="line">        logging.warning(<span class="string">&quot;I am hungry&quot;</span>)</span><br><span class="line">    sleep(<span class="number">0.1</span>)</span><br><span class="line">    <span class="keyword">return</span> &#123;<span class="string">&quot;message&quot;</span>: <span class="string">&quot;Hello, world!&quot;</span>&#125;</span><br></pre></td></tr></table></figure></div>

<p>今回はuvを使っていますが、uvの場合はちょっと問題があって回避の手順が別のページにあります。その手順の通りに行います。<code>uv pip install</code>は<code>uv add</code>とは異なり<code>pyproject.toml</code>にパッケージは追加しません。opentelemetry-bootstrapというコマンドが有名どころのPythonライブラリを検知して、それに対応した計装ライブラリをピックアップします。なかなか力技。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-fjri2p-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-fjri2p-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">uv pip install opentelemetry-distro opentelemetry-exporter-otlp</span><br><span class="line">uv run opentelemetry-bootstrap -a requirements | uv pip install --requirement -</span><br></pre></td></tr></table></figure></div>

<p>これは<code>uv pip install</code>で入れているので<code>uv pip compile pyproject.toml</code>には出てきません。<code>uv pip freeze &gt; requirements.txt</code>で<code>requirements.txt</code>を作る必要があります。</p>
<p>以下のコマンドで起動して、 http://localhost:8000/ で起動してログを送ります。設定は環境変数と<code>opentelemetry-instrument</code>コマンドラッパーの<code>--service_name</code>といった引数で渡す方法が選べますが、真設定は環境変数にしといた方がDockerとかにするときに便利かと思って環境変数にしています。</p>
<p>デフォルトのOTLPのプロトコルはgrpcですが、この場合はhttpsになってしまい証明書エラーが出てしまいます。今回はローカルなので証明書はいらないhttp&#x2F;protobufもしくはhttp&#x2F;jsonにします。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-fjri2p-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-fjri2p-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">export OTEL_SERVICE_NAME=hyperdx-demo</span><br><span class="line">export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf</span><br><span class="line">export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 # 省略可能</span><br><span class="line">export OTEL_PYTHON_LOG_CORRELATION=true</span><br><span class="line">export OTEL_PYTHON_LOG_LEVEL=info</span><br><span class="line">export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">uv run opentelemetry-instrument fastapi run --port 8000</span></span><br></pre></td></tr></table></figure></div>

<p>結構はまったのですが、<code>fastapi dev</code>の開発モードだとログとかは出てこないですね。ちょっと不便。</p>
<h2 id="UIの設定">UIの設定</h2><p>ローカルモードだとユーザー設定などはありませんので、いきなりコネクション選択が表示されます。オールインワンのイメージだとパスワード設定などがあります。このコネクションは緑のCreateを押せばOKです。</p>
<img fetchpriority="high" src="/images/2025/20250618a/スクリーンショット_2025-06-13_19.13.05.png" alt="スクリーンショット_2025-06-13_19.13.05.png" width="1200" height="685">

<p>引き続きデータソース入力が表示されます。最初は二進も三進もわからなかったのですが、こちらの情報を見れば少しは歯が立ちます。</p>
<img src="/images/2025/20250618a/スクリーンショット_2025-06-13_19.16.16.png" alt="スクリーンショット_2025-06-13_19.16.16.png" width="1200" height="685" loading="lazy">

<p>以下の2つのソースを設定しました。名前、ソースタイプ、テーブル以外はデフォルトのままです。</p>
<ul>
<li>Name: Logs<ul>
<li>Data Source Type: Logs</li>
<li>Table: <code>otel_logs</code></li>
</ul>
</li>
<li>Name: Traces<ul>
<li>Data Source Type: Trace</li>
<li>Table: <code>otel_traces</code></li>
</ul>
</li>
</ul>
<h2 id="ログを見てみる">ログを見てみる</h2><p>左上のメニューでSearchを選び、データソースを選択すると出力したログ一覧が見られます。クリックすると時間や出力したサービスなど詳細な情報が見られます。いちいちデータソース選択とか面倒と思われるかもしれませんが、プロダクションで超大規模サービスを想定してでしょうね。同じ種類のデータでも日ごとにパーティションを切って別テーブルに、とかそういうのを想定しているのかと思います。</p>
<img src="/images/2025/20250618a/log.png" alt="log.png" width="1200" height="522" loading="lazy">

<p>トレースは、それぞれのログをクリックしたあとに、トレースのタグを選択します。右側でトレースのデータソースを選択すると、いつものトレースが見られます。ログのデータソース設定のオプショナル中くれている設定に関連するトレースデータソースを設定できるので、そこを設定しておくとクリックは不要になります。動作は軽快です。</p>
<img src="/images/2025/20250618a/trace.png" alt="trace.png" width="1200" height="629" loading="lazy">

<h2 id="まとめ">まとめ</h2><p>ローカルでログやトレースが見られるHyperDXを試してみました。メトリクスも本当は見られたりするのですがダッシュボード設定が難しくまた今度にしようかと。あと、ウェブの操作画面を記録するセッション機能とやらもあるのですが、機能とかが多くてちょっとこちらも時間を設けてゆっくり調べてみようと思います。</p>
<p>OpenTelemetryでデータ投入できるので、本番環境のクラウドサービスのログ基盤とかにもスムーズに遷移できる気がします。</p>
<p>設定がyamlかなにかで軽く共有できれば開発用Dockerとかで気軽に立ち上げてみんなで見るとかやりたいところですが、ちょっとそこはローカル専用にするにはちょっと不便。</p>
]]></content>
    <summary type="html">HyperDXはいわゆるオブザーバービリティに属すプロダクトです。この領域のプロダクトはかなりのデータ量を扱う必要があったり、可用性のために、ストレージとビューアが分かれていたり、大量のツールと連携させる必要があったりします。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="CNCF" scheme="https://future-architect.github.io/tags/CNCF/"/>
    <category term="OpenTelemetry" scheme="https://future-architect.github.io/tags/OpenTelemetry/"/>
    <category term="オブサーバビリティ" scheme="https://future-architect.github.io/tags/%E3%82%AA%E3%83%96%E3%82%B5%E3%83%BC%E3%83%90%E3%83%93%E3%83%AA%E3%83%86%E3%82%A3/"/>
    <category term="ログ" scheme="https://future-architect.github.io/tags/%E3%83%AD%E3%82%B0/"/>
  </entry>
  <entry>
    <title>初めての海外カンファレンスとKubeCon Japan参加レポート</title>
    <link href="https://future-architect.github.io/articles/20250617a/"/>
    <id>https://future-architect.github.io/articles/20250617a/</id>
    <published>2025-06-16T15:00:00.000Z</published>
    <updated>2025-06-16T15:00:00.000Z</updated>
    <author><name>伊藤太斉</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250617a/IMG_2577.jpg" alt="" width="1200" height="747">

<p>こんにちは。TIGの伊藤です。この記事はCNCF連載2025の2日目の記事です。</p>
<p>今回はこのCNCF連載とタイミングを同じくして開催しているKubeCon + CloudNativeCon Japanに参加してきたので、1日目に私が回ったセッションや会場の雰囲気についてお伝えできればと思います。</p>
<h2 id="KubeConとは">KubeConとは</h2><p>Cloud Native Computing Foundation(CNCF)が開催しているカンファレンスであり、これまで、ヨーロッパ、北アメリカをはじめとして世界各国で開催されてきました。私がクラウドネイティブ系のコミュニティに関わり始めたのが2019年ごろで、その時から行ってみたいカンファレンスでした。<br>そして、今年ついに日本で開催されると聞き、今回参加できました。<br>（参考：日本初開催！KubeCon + CloudNativeCon Japan 2025、6月16日-17日に東京で開催）</p>
<h2 id="KeyNote">KeyNote</h2><p>CNCFのChrisから、直近のクラウドネイティブのコミュニティや今回の開催地となった日本のCNCFへのコントリビューションの度合いなどについてのセッションでした。</p>
<p>まず、今回のKubeConにおいては、1500人分のチケットが全て売り切れとなり、今回のカンファレンスへの関心度がわかります。</p>
<img src="/images/2025/20250617a/IMG_2579.jpg" alt="IMG_2579.jpg" width="1200" height="721" loading="lazy">

<p>また、それだけではなく、来年のKubeCon + CloudNativeCon Japan 2026も開催が決定し、会場も非常に盛り上がっていました。</p>
<img src="/images/2025/20250617a/IMG_2580.jpg" alt="IMG_2580.jpg" width="1200" height="900" loading="lazy">

<p>私としても今回連れてきたかった同僚たちを誘って、来年は会社としてもっといろんな情報が集められればいいなと思い、来年の楽しみが1つ増えました。</p>
<p>その他、日本の企業におけるCNCFプロジェクトのケーススタディについても紹介されており、日本のコミュニティ自体にも注目が集まっているようでした。私としては、プロジェクトでもがっつり利用しているKeyCloakが日本のコントリビュートが活発であることから、出せる情報は登壇などできたらいいなと思います。</p>
<h2 id="他のセッション">他のセッション</h2><p>その他のセッションについては海外の参加者がほとんど、というわけでもなく、だいたいどの時間帯も1つは日本の方のセッションをやっていました。</p>
<p>ただ、セッション自体は全て英語で進んでいくので、本当にぎりぎり聞けたものや、一緒に聞いていた知り合いなどと話してちょっとずつ理解していくという感じでした。帰り際に、翻訳ツールも用意されているということを聞き、2日目はしっかり活用しようと思います（完全に準備不足でした）</p>
<p>タイムテーブル自体、個人的な最近の興味関心も含まれていますが、生成AI系やOpen TelemetryをはじめとしたObservabilityが多いように感じました。</p>
<p>また、今回私がみたセッションを1つ紹介します。</p>
<h3 id="Should-Our-Project-Join-the-CNCF-Lenka-Bocincova-Red-Hat">Should Our Project Join the CNCF? - Lenka Bočincová, Red Hat</h3><p>このセッションでは、仮想的にKubeFishというプロジェクトがCNCFにジョインする場合、どのようなことが想定されるか、議論の対象になるかを解説していました。</p>
<p>例えば、企業で開発していて、プロジェクトを公開し、CNCFにジョインすることを決定した場合、権限もCNCFに移譲します。すると、企業としての意思決定よりもCNCF・コミュニティとしての意思決定が重要視されるようになり、改めてこの観点についてはOSSという公平性を重んじていることがわかりました。</p>
<p>一方、CNCFのプロジェクトとしてジョインする場合、成熟度として以下の3種類あります。</p>
<ul>
<li>Graduated</li>
<li>Incubating</li>
<li>Sandbox</li>
</ul>
<p>このレベルはプロジェクトの健全性、および成熟度で判断され、コントリビューションだけではなく、プロジェクトがさまざまな環境で利用されていることも判断の対象になるようです。</p>
<p>また、仮にジョインできた場合、CNCFからも支援を受けることができるが、サンドボックスは他と比べるとはるかに少ないこともあり、ジョインする場合にはどの成熟度でいくかもジョインする側の観点としてはありそうです。各成熟度ごとにチェックリストがあり、事前にすべてクリアしていることが望ましいようです。</p>
<p>https://github.com/cncf/toc/blob/main/process/README.md#how-to-apply-to-move-levels</p>
<p>例えば、OSSのライセンスについても言及されていますが、これは利用しているライブラリについても追求されるため、何かしらのFoundationにジョインを目指すのであれば、開発時にある程度のチェックポイントを設けるのが良いのかなと思っていました。昨今、OSSのライセンスについてはさまざまな問題になったりしていることもあり、より注意が必要だと感じました。</p>
<h2 id="会場の様子とカンファレンスの雰囲気">会場の様子とカンファレンスの雰囲気</h2><p>参加者の比率を見ているところ、かなり日本の方が多かったです。そのため、飛び交う言葉自体も日本語が多く、そこは安心して参加できました。</p>
<p>スポンサーブースについても、本国の社員の方が直接説明してくれたり、英語で聞くことが難しい場合にも日本の方が説明してくれるようになっており、そこまで尻込みせずに話を聞くことができました。私はそれでもビビりながら話しかけていて、1日目で回れなかったところもあるので、2日目も回ろうと思います。</p>
<h2 id="まとめ">まとめ</h2><p>普段仕事としていろんな技術に触れていることも好きですが、改めて勉強会、カンファレンスに足を運ぶとモチベーションになると感じたので、この新しく得たモチベーションを元にプロジェクトの仕組みなどに取り入れていこうと思いました。</p>
<p>また、今回日本開催の海外カンファレンスに参加してきましたが、セッションだったりは基本的にずっと英語なので、改めてリスニング、スピーキングはしっかりできるようにしようと思います。</p>
<p>来年も日本開催が決まったので、そこで英語力、技術力をアップさせていけたらいいなと思います。</p>
]]></content>
    <summary type="html">このCNCF連載とタイミングを同じくして開催しているKubeCon + CloudNativeCon Japanに参加してきたので、1日目に私が回ったセッションや会場の雰囲気についてお伝えできればと思います。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="CNCF" scheme="https://future-architect.github.io/tags/CNCF/"/>
    <category term="KubeCon" scheme="https://future-architect.github.io/tags/KubeCon/"/>
    <category term="参加レポート" scheme="https://future-architect.github.io/tags/%E5%8F%82%E5%8A%A0%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88/"/>
  </entry>
  <entry>
    <title>CNCF連載2025</title>
    <link href="https://future-architect.github.io/articles/20250616a/"/>
    <id>https://future-architect.github.io/articles/20250616a/</id>
    <published>2025-06-15T15:00:00.000Z</published>
    <updated>2025-06-15T15:00:00.000Z</updated>
    <author><name>admin</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250616a/cncf-color.png" alt="" width="1200" height="191">

<h2 id="CNCF-とは">CNCF とは</h2><p>Cloud Native Computing Foundation の略で、コンテナオーケストレーションとして知られている Kubernetes を中心とした OSS を管理している Linux Foundation 傘下の非営利団体です。2015 年に Google などが中心となり、コンテナオーケストレーションツールである Kubernetes のバージョン 1.0 リリースと同時に設立されました。</p>
<p>2025 年で Kubernetes リリースから 10 年経過し、クラウドネイティブ技術の普及も随分進んだ気がします。コンテナ、CI&#x2F;CD、オーケストレーション（ここは ECS や CloudRun なども）、ログやメトリクス収集などのオブザーバビリティなどは導入してもニュースにもならなず、それぞれの組織やプロダクトに自然に導入されているように思えます。</p>
<p>コミュニティ活動の支援も活発で年に数回ほど、KubeCon と呼ばれるカンファレンスを世界各地で実施しています。</p>
<p>直近ではKubeCon + CloudNativeCon Japan 2025が東京で 6&#x2F;16 ～ 6&#x2F;17 に開催されます。実はこれが日本での初開催となります。おめでたいですね！</p>
<h2 id="CNCF-が提唱するクラウドネイティブとは">CNCF が提唱するクラウドネイティブとは</h2><p>CNCF が提唱するクラウドネイティブ技術とは、CNCF のリポジトリで以下のように定義されています。</p>
<blockquote>
<p>クラウドネイティブ技術は、パブリッククラウド、プライベートクラウド、ハイブリッドクラウドなどの近代的で動的な環境において、拡張性あるなアプリケーションを開発・実行するための能力を組織にもたらします。この手法の代表例として、コンテナ・サービスメッシュ・マイクロサービス・イミュータブルインフラストラクチャ・宣言型 API があります。<br>これらの技術により、回復性・運用性・可観測性のある疎結合システムが実現します。これらを堅牢な自動化と組み合わせることで、エンジニアは影響の大きな変更を頻繁かつ予測どおりに行うことができ、最小限の労力で作業できます。</p>
</blockquote>
<p>一言でいうと、クラウドの利点を最大限に引き出した、アプリケーションの設計・開発・運用することかなと思います。</p>
<h2 id="プロジェクトについて">プロジェクトについて</h2><p>CNCF では多くの OSS をホスティングしてします。プロジェクトは成熟度レベル別に 3 つ分類しています。キャズムのイノベーター、アーリーアダプター、アーリーマジョリティに似ているなと思われる方もいらっしゃるかと思いますが、公式サイトにも、それに相当したコンセプトであると記載されています。</p>
<ul>
<li><strong>Graduated（卒業）</strong><ul>
<li>「成熟した」プロジェクトとして認められたものについては Graduated になります</li>
<li>Kubernetes、Prometheus、Envoy、containerd など</li>
<li>2025 年には、CubeFSやin-totoがこのレベルに昇格</li>
</ul>
</li>
<li><strong>Incubating</strong><ul>
<li>Sandbox から利用数などが増加すると Incubating になります</li>
<li>オープンソースの Kubernetes セキュリティプラットフォームである、Kubescape が 2025 年 2 月に昇格</li>
</ul>
</li>
<li><strong>Sandbox</strong><ul>
<li>CNCF のプロジェクトとしては「early stage」として位置付けられています</li>
<li>2025 年には、物理サーバ上に PaaS を構築するCozystack、複数の k8s クラスタ管理する KubeFleet などが追加されました</li>
</ul>
</li>
</ul>
<img src="/images/2025/20250616a/{D9F609E2-F7B7-4FF9-A772-79B5A2EA45B8}.png" alt="Graduated、Incubating、Sandboxの3段階" width="890" height="399" loading="lazy">

<p>他にも、Archive というステータスがあり、2025 年 6 月 13 日時点で 16 プロジェクトが存在していました。</p>
<ul>
<li>https://www.cncf.io/archived-projects/</li>
</ul>
<p>プロジェクト数の推移は公式サイトのメトリクスページから確認できます。</p>
<p>Graduated が 31、Incubating が 36、Sandbox が 143 という内訳のようです。</p>
<img src="/images/2025/20250616a/{48890C4B-2DC3-47EC-9054-041CCE980041}.png" alt="プロジェクト数の推移" width="1152" height="570" loading="lazy">

<p>チャートを見ると分かる通り、右肩上がりでまだまだ勢いがある領域だと感じられます。</p>
<h2 id="CNCF-Annual-Report-2024">CNCF Annual Report 2024</h2><p>2025 年 5 月 19 日にCNCF Annual Report 2024 という、年次報告書が公開されました。せっかくなので NotebookLM に読み込ませ、面白かった内容をサマリで紹介します。</p>
<ul>
<li>CERN が、CNCF プロダクトである、Prometheus、Argo、FluentD、CoreDNS、Harbor などを導入し、数千のノード（！）かつ、500 以上のクラスターを管理している（！）とのこと、トップエンドユーザー賞を受賞</li>
<li>北米の、KubeCon + CloudNativeCon North America 2024 では、プラットフォームエンジニアリング、大規模なクラウドネイティブ AI、セキュリティが主要な焦点だった</li>
<li>中国の、KubeCon + CloudNativeCon + Open Source Summit + AI_dev China 2024 では、クラウドネイティブ AI (CNAI) が主要テーマとなり、大規模言語モデル (LLM) や機械学習ツールがクラウドネイティブインフラで稼働している事例があった</li>
<li>Kubernetes とクラウドネイティブのスキルを証明する新しい認定資格として、Kubernetes and Cloud Native Security Associate (KCSA)、Certified GitOps Associate (CGOA) など、合計 6 つが導入されました</li>
<li>OpenTelemetry は貢献者ベースを拡大し続けており、最近プロファイリングを新しいシグナルタイプとして追加し、golang コンパイル時計測機能の追加に取り組んでいる</li>
</ul>
<h2 id="CNCF-連載とは">CNCF 連載とは</h2><p>2020 年、2023 年にも開催した、技術ブログのリレー企画です。フューチャーでは月に 1 回程度のペースで、何かしらの技術テーマを元にブログリレー（ブログ連載）を行っています。6 月は先に紹介した KubeCon + CloudNativeCon が日本で初開催されるということで、それを記念に開催するとなりました。少しでも盛り上がりに繋がればと思っています。</p>
<p>メンバーは基本的には社内で参加を募り、有志で集まってもらいました。どのプロダクトを選ぶかは完全に各々の自由です。「どれを選ぶべきか分からないんだけど..」という声も最初はよく聞かれました。アイコンを並べるだけでなかなかの情報量です。</p>
<img src="/images/2025/20250616a/名称未設定ファイル.drawio_(3).png" alt="CNCFプロジェクトのアイコン一覧" width="1200" height="1036" loading="lazy">

<p>そのたび、Graduated か Incubating のページか、Sandbox のページ から、プロダクトのアイコンを見て「ジャケ買い」しましょうと伝えています。CNCF のアイコンは、少しでも目立って覚えてもらおうという気持ちからか、デザインが良いです。各自の感性にビビッときた気鋭のプロダクトが選ばれるでしょう。</p>
<h2 id="スケジュール">スケジュール</h2><div class="scroll"><table>
<thead>
<tr>
<th>日付</th>
<th>名前</th>
<th>タイトル</th>
</tr>
</thead>
<tbody><tr>
<td>6&#x2F;17 (火)</td>
<td>真野隼記</td>
<td>Notary v2（Notation）によるコンテナイメージ署名</td>
</tr>
<tr>
<td>6&#x2F;18 (水)</td>
<td>伊藤太斉</td>
<td>KubeCon 参加レポート</td>
</tr>
<tr>
<td>6&#x2F;19 (木)</td>
<td>澁川喜規</td>
<td>HyperDX について</td>
</tr>
<tr>
<td>🐋</td>
<td></td>
<td></td>
</tr>
<tr>
<td>6&#x2F;25 (水)</td>
<td>原木翔</td>
<td>AI Agent 用フレームワーク「Dapr Agents」について調べてみた</td>
</tr>
<tr>
<td>6&#x2F;26 (木)</td>
<td>鈴木崇史</td>
<td>LLMコンテナイメージでLazy Pullingの効果を検証</td>
</tr>
<tr>
<td>🐋</td>
<td></td>
<td></td>
</tr>
<tr>
<td>6&#x2F;30 (月)</td>
<td>片岡久人</td>
<td>ローカルKubernetesでdbtをコンテナ化して実行してみる</td>
</tr>
<tr>
<td>7&#x2F;1 (火)</td>
<td>大前七奈</td>
<td>Kubernates✖️MLflow✖️GCPで簡単にAIモデル公開</td>
</tr>
</tbody></table></div>
<h2 id="さいごに">さいごに</h2><p>私自身は、クラウドマネージドサービスを優先的に技術選定することが多く、CNCF プロジェクトの OSS を直接的に利用することが少ないですが、いざ調べてみると魅力的なプロダクトが数え切れないほど多数あり、調べていくとあっという間に時間が溶けました。</p>
<p>このブログ連載が、CNCF のプロジェクトのエキサイティングな世界に触れるきっかけとなれば幸いです。</p>
]]></content>
    <summary type="html">CNCFとはCloud Native Computing Foundation の略で、コンテナオーケストレーションとして知られているKubernetesを中心としたOSSを管理しているLinux Foundation傘下の非営利団体です。2015年にGoogleなどが中心となり、コンテナオーケストレーションツールであるKubernetesのバージョン1.0リリースと同時に設立されました。&quot;</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="CNCF" scheme="https://future-architect.github.io/tags/CNCF/"/>
    <category term="インデックス" scheme="https://future-architect.github.io/tags/%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9/"/>
  </entry>
  <entry>
    <title>macOS 26でLinuxコンテナのネイティブサポートが来る</title>
    <link href="https://future-architect.github.io/articles/20250610a/"/>
    <id>https://future-architect.github.io/articles/20250610a/</id>
    <published>2025-06-09T15:00:00.000Z</published>
    <updated>2025-06-09T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<p>現在開催中の WWDC で、Linux コンテナネイティブサポートが発表されました。</p>
<ul>
<li>Containerization の紹介 - WWDC25 - ビデオ - Apple Developer</li>
</ul>
<p>毎年 WWDC の時期になると仮想化のアップデートの話が来るのをずっと首を長くしてまっていた日々だったので、待望のアップデートです。macOS は以前から、Linux 仮想化の機能が OS の API レベルでサポートしており、カーネルイメージを指定して起動できたりしました。これで、WSL2 に負けない環境はすぐにでも出るんじゃないかと思いはや数年。それがとうとうきました。なお、今回は macOS 26 beta 1 と、0.1.0 のcontainerをもとにしていますので、時間がたてば状況が変わる可能性がある点にご注意ください。</p>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">WWDCで一番楽しみにしていたVirtualization Frameworkのセッション。EC2のmacインスタンスを実装しているような人には嬉しそうなネットワークブロックデバイス。I/Oパフォーマンスアップはどれぐらいのものだろうか？https://t.co/f3UF03wUxa</p>&mdash; 渋川よしき (@shibu_jp) June 7, 2023</blockquote> 

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">WWDCで一番期待しているのは、AIでも新型Macの発表でもなくて、macOS Subsystem for Linuxです。メモリバルーニングで固定メモリ割り当てなくてもいいLinuxサブシステムが実現できるAPIはすでにある。そうすれば少ないメモリでも効率よくコンテナ起動したりできる。</p>&mdash; 渋川よしき (@shibu_jp) May 10, 2024</blockquote> 

<h2 id="container">container</h2><p>今回、Apple は 2 つの OSS を公開しました。前者がコマンドラインツールで後者はバックエンドのサービス？のようです。前者の Release にあるインストーラでインストールすれば後者からは特にダウンロードする必要はありません。OSS で修正したい人向けにソースが公開されています。</p>
<ul>
<li>github.com&#x2F;apple&#x2F;container</li>
<li>github.com&#x2F;apple&#x2F;containerization</li>
</ul>
<img fetchpriority="high" src="/images/2025/20250610a/スクリーンショット_2025-06-10_8.38.48.png" alt="スクリーンショット_2025-06-10_8.38.48.png" width="732" height="560">

<p>インストールしたら以下のコマンドを実行すると必要なものを追加でダウンロードしてきます。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">container system start</span><br></pre></td></tr></table></figure>

<p>Docker とほぼ互換の container コマンドで起動すれば素早く立ち上がります。起動早いですね。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-5xdelv-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-5xdelv-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">container run --<span class="built_in">rm</span> -it --name debian debian:bookworm</span><br></pre></td></tr></table></figure></div>

<h2 id="コンテナを-Linux-以外で動かす歴史">コンテナを Linux 以外で動かす歴史</h2><p>コンテナは Linux の軽量なリソース分離の仕組みを活用して、VM よりもコンパクトな独立した OS 環境を作って起動する、というのが元の始まりです。ただし、Linux 以外だと Linux カーネルの仕組みは使えないため、仮想 OS で Linux を入れてから動かす必要がありました。</p>
<p>当初は 4GB だと 8GB だの固定のメモリ領域を持った仮想 PC の Linux を起動し、その中で Docker を動かしていました。どんな小さなアプリケーションを起動するのにも固定のリソースのリソースが必要でした。</p>
<p>Windows は WSL2 という、Linux 仮想環境を 5 年前にリリースしました。Windows の持つ Hyper-V という仮想化の仕組みの上で Linux を動かします。Linux と Windows はメモリを共有しており、デフォルト設定では、Linux からは Windows の持つ物理メモリの 50%のメモリもしくは 8GB の少ない容量が見えます。アプリケーションがメモリを必要として Linux カーネルにリクエストしてきたら、Windows からメモリをもらってきてアプリケーションに渡します。</p>
<ul>
<li>WSL2: 開発環境構築＆ツール開発ガイド</li>
</ul>
<p>ただし、Linux は本来は自分だけが管理する世界でパフォーマンスに全力を出すという世界の中で生きています。ディスクアクセスをすると、2回目のロードのためにすべてバッファとしてメモリがある限りはキャッシュします。そんな感じで一度 Linux カーネルが持ったメモリはあまり Windows に帰ってこないという問題がありました。当初は Windows の 80%が見えていましたが減らされて現在に至ります。</p>
<p>その後アップデートで多少の改善はありました。</p>
<ul>
<li>zenn: dozo 氏 WSL のアップデートでメモリ開放？</li>
</ul>
<p>Apple は確か 2022 年に Virtualization Framework をリリースしました。これも WSL2 同様メモリを macOS と融通し合うメモリバルーニングに対応しているし、将来に期待、と思っていましたが、残念ながらこれを活用するコンテナエンジンはこれまで出てきませんでした。ただ、パフォーマンスは良くなったので体験は良くなりました。mac でよく問題として言われていたホストとのストレージの共有はそもそも必要性は減っていますが、速度もアップしました。</p>
<p>今回の仕組みは軽量なカーネルと独自の initd のみの軽量な VM をコンテナごとに動かすという方法を Apple は発表しました。</p>
<img src="/images/2025/20250610a/スクリーンショット_2025-06-10_6.24.42.png" alt="スクリーンショット_2025-06-10_6.24.42.png" width="1200" height="631" loading="lazy">

<p>こちらの説明を見ると、Windows 同様 OS にメモリを返すところはまだ部分的なサポートとありますが、コンテナ&#x3D;VM の場合の macOS では影響範囲としては単独のコンテナになります。WSL とは違ってコンテナさえ再起動しちゃえば OK のように見えます。Go のように言語ランタイムの TCMalloc で OS とのメモリのやり取りを減らしてメモリを使い回すとか、sync.Pool でアプリ内部でメモリ再利用を積極的に行うようにすればさらに問題は軽減されるかと思われます。</p>
<p>Linux カーネルはここの説明によると、virtio だけカーネルに組み込み（モジュールではなく）にしておけば良いうという感じのようですね。Microsoft はいろいろカスタムしていた気がしますが。</p>
<h2 id="WSL2-との違い">WSL2 との違い</h2><p>現状の僕の理解で WSL2 との違いはこうじゃないかというのを書いてみました。</p>
<p>今どきの Windows は Hyper-V の上で動いている OS の 1 つという構成です。Windows 10 のときは WSL2 や Hyper-V の仮想化をした時だけこのような構成になっていましたが、Windows 11 からはセキュリティ対策も兼ねてこの構成が標準になったと理解しています。Docker は WSL2 の中で動きます。Linux カーネルがあってその中にコンテナのランタイムがあり、その中でコンテナが動いています。</p>
<p>macOS はそのようなハイパーバイザの上で動く感じではなく、おそらく通常の darwin カーネルの中で Virtualization Framework がいます。絵には書いてないですが、その下にさらに低レベルな Hypervisor Framework があります。今回の発表では、コンテナごとに VM があがり、その中でコンテナが実行されるということだったので、この図のような感じかと思います。Docker のランタイムにあたるプログラムは Linux カーネルの外側にあるというのが違いかな、と思います。</p>
<img src="/images/2025/20250610a/名称未設定ファイル.drawio.png" alt="名称未設定ファイル.drawio.png" width="541" height="201" loading="lazy">

<p>皆さんのパソコンではプログラムはたくさんの共有ライブラリを使っています。100 個のプログラムが共通の dll なり.so なりを使ていたとして、100 個分のメモリは使わず、ディスクから読み込んだプログラムイメージは物理メモリ上は 1 つだけ配置されて、仮想メモリの形で見た目のアドレスだけ複製されているような形になります。macOS のほうの Linux カーネルも同じような感じでメモリ共有されるのかどうかはわからないのですが、もし共有されるとしたら面白いな、と思っています。</p>
<h2 id="compose-や-Dev-Containers-はまだ動かない">compose や Dev Containers はまだ動かない</h2><p>現状は単独の Docker 相当の機能のみの提供となっています。compose もありません。</p>
<p>コンテナというと開発環境をすべてコンテナのなかで動かす Dev Containers が実用になるかどうかが気になるところかと思います。podman とかも起動コマンドが違うので、Dev Containers 側でコマンドは変更できるようになっています。一応これを設定してみても・・・</p>
<img src="/images/2025/20250610a/スクリーンショット_2025-06-10_12.13.51.png" alt="スクリーンショット_2025-06-10_12.13.51.png" width="1029" height="132" loading="lazy">

<p>実行時にはエラーになります。</p>
<img src="/images/2025/20250610a/スクリーンショット_2025-06-10_9.08.02.png" alt="スクリーンショット_2025-06-10_9.08.02.png" width="563" height="112" loading="lazy">

<p>また、Docker Desktop のようなコンテナのインスペクタとか管理画面はありません。Docker はという Unix ドメインソケットを<code>/var/run/docker.sock</code>というパスにつくり、そこに対してリクエストを送ることで、リモートで制御できます。container は XPC というプロセス間通信を使っており、<code>/var/run/docker.sock</code>はないので、dozzleやPortainerといった既存の管理画面は動きません。</p>
<p>あとは、まだ GPU や NPU を使う方法とかも情報はないですね。</p>
<p>とはいえ、このあたりは些細な差というか、まだ正式リリースまでは先だと思うので更新が楽しみです。</p>
]]></content>
    <summary type="html">現在開催中のWWDCで、Linuxコンテナネイティブサポートが発表されました。毎年WWDCの時期になると仮想化のアップデートの話が来るのをずっと首を長くしてまっていた日々だったので、待望のアップデートです。macOSは以前から、Linux仮想化の機能がOSのAPIレベルでサポートしており..</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="Docker" scheme="https://future-architect.github.io/tags/Docker/"/>
    <category term="Linux" scheme="https://future-architect.github.io/tags/Linux/"/>
    <category term="Mac" scheme="https://future-architect.github.io/tags/Mac/"/>
    <category term="コンテナ" scheme="https://future-architect.github.io/tags/%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A/"/>
  </entry>
  <entry>
    <title>nektos/act はMakefileの代わりになるか？</title>
    <link href="https://future-architect.github.io/articles/20250605a/"/>
    <id>https://future-architect.github.io/articles/20250605a/</id>
    <published>2025-06-04T15:00:00.000Z</published>
    <updated>2025-06-04T15:00:00.000Z</updated>
    <author><name>真野隼記</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250605a/unnamed.jpg" alt="unnamed.jpg" width="1024" height="1024">

<p>※画像はGemini Pro 2.5で作成しました。</p>
<p>CI&#x2F;CD連載 3本目です。</p>
<h2 id="はじめに">はじめに</h2><p>TIG 真野です。</p>
<p>GitHub Actionsをローカル環境で実行できるnektos&#x2F;actをMakefileやTaskfileなどのタスクランナーの代わりとして使えるのか、試してみた記事です。</p>
<h2 id="nektos-act-とは">nektos&#x2F;act とは</h2><p>act は GitHub Actionsのワークフローをローカル上で実行できる、Go言語で実装されたツールです。Dockerを利用してGitHub Actionsの実行環境をエミュレートしてくれ、GitHubのリポジトリにプッシュすることなくワークフローのテストやデバッグを行うことができます。actの名前の由来は、actionsからもらっているんだろうなと思っています。</p>
<p>act のREADMEには以下のようにactを使うべき理由が書かれています。</p>
<blockquote>
<p>Run your GitHub Actions locally! Why would you want to do this? Two reasons:</p>
<ul>
<li>Fast Feedback - Rather than having to commit&#x2F;push every time you want to test out the changes you are making to your .github&#x2F;workflows&#x2F; files (or for any changes to embedded GitHub actions), you can use act to run the actions locally. The environment variables and filesystem are all configured to match what GitHub provides.</li>
<li>Local Task Runner - I love make. However, I also hate repeating myself. With act, you can use the GitHub Actions defined in your .github&#x2F;workflows&#x2F; to replace your Makefile!</li>
</ul>
<p><strong>日本語訳</strong>:<br>GitHub Actions をローカルで実行しましょう！これを行うべき理由は2つあります：</p>
<ul>
<li>フィードバックを早くする - .github&#x2F;workflows&#x2F; ファイルに加えている変更（または埋め込まれた GitHub Actions への変更）をテストしたいと思うたびにコミット&#x2F;プッシュをする代わりに、act を使えばアクションをローカルで実行できます。環境変数やファイルシステムは、GitHub が提供するものと一致するようにすべて設定されます</li>
<li>タスクランナーとして動かす - 私は make が大好きです。しかし、同じことを繰り返すのも嫌いです。act を使えば、.github&#x2F;workflows&#x2F; で定義された GitHub Actions をあなたの Makefile の代わりに利用できるのです！</li>
</ul>
</blockquote>
<p>2つ目の理由として書かれていた、タスクランナーとして使うという点が意外でした。MakefileかTaskfileあたりで十分良い気もしますが、GitHub Actionsとローカル実行でも利用するコマンド定義を共有できれば確かに便利そうです。</p>
<p>この点で評価している記事が、自分の観測範囲で見つけることができなかったため、CI&#x2F;CD連載の1ネタとして試します。</p>
<h2 id="actの使われどころ">actの使われどころ</h2><p>Gitea という、GitHubのセルフホスト可能なGo言語製のプロダクトがあります。類似のツールにGogsも存在しますが、Giteaは元々Gogsからフォークされたものです。理由は公式の発表ブログがあります。</p>
<p>そのGiteaのGitea Actionsは、act （のソフトフォークで、ライブラリ呼び出しなどをできなくしている）で動いているとのことです。Giteaからさらに派生した Forgejoでも Forgejo Runnerで利用されています。</p>
<p>複数のプロダクトからCI&#x2F;CDランナーとして採用されていることから、完成度も高まっているのではないでしょうか。なお、2025年6月時点ではバージョン <code>v0.2.78</code> でした。そもそもがGitHub Actionsの互換を謳っているため、安定性は高いと言えるのではないでしょうか。</p>
<h2 id="アーキテクチャ">アーキテクチャ</h2><p>act のアーキテクチャは少し特殊です。Makefileの代わりにと説明があったので、最初はステップの中で <code>uses: actions/checkout@v4</code> のような別途コンテナ起動が必要な場合のみ、Docker呼び出しし、その他はホスト上で直接コマンドを実行するのかと思っていました。</p>
<p>実際は、GitHub Actionsと同じくRunner(ランナー)のコンテナイメージをローカルに取得し、そのランナー上でジョブを処理します。ステップの中でDocker呼び出しが必要な場合のみ、ランナーとは別にコンテナイメージを起動させます。データのやり取りはボリューム共有機能を経由して行われます。Docker呼び出しが不要な場合は、ランナーで直接実行します（そのために必要な、Node.jsなどの最低限のセットアップはされています）。</p>
<img src="/images/2025/20250605a/act_arch.drawio.png" alt="actコマンドでコンテナが起動する様子" width="854" height="578" loading="lazy">

<p>上記の構造のため、ちょっとしたスクリプト実行も、上図でいうジョブコンテナの呼び出しが必要です。</p>
<h2 id="事前準備">事前準備</h2><p>act を利用する上でDockerのインストールは前提条件です。もし、未構築の場合はMac・Windowsの場合はDocker Desktop（Linuxの場合はDocker Engine）をインストールします。</p>
<ul>
<li>Docker Desktop: The #1 Containerization Tool for Developers | Docker</li>
</ul>
<h2 id="actのインストール">actのインストール</h2><p>公式ドキュメントにインストールについて独立したページがあり、様々なパッケージマネージャーに対応しています。</p>
<ul>
<li>Installation - act - User Guide | Manual | Docs | Documentation</li>
</ul>
<p>私はGo環境が構築済みだったため、 <code>go install</code> で対応します。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">go install github.com/nektos/act@v0.2.78</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">act --version</span></span><br><span class="line">act version 0.2.78</span><br></pre></td></tr></table></figure>

<p>helpを見ると、オプションが豊富なことも分かります。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-baan8z-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-baan8z-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">act --<span class="built_in">help</span></span></span><br><span class="line">Run GitHub actions locally by specifying the event name (e.g. `push`) or an action name directly.</span><br><span class="line"></span><br><span class="line">Usage:</span><br><span class="line">  act [event name to run] [flags]</span><br><span class="line"></span><br><span class="line">If no event name passed, will default to &quot;on: push&quot;</span><br><span class="line">If actions handles only one event it will be used as default instead of &quot;on: push&quot;</span><br><span class="line"></span><br><span class="line">Flags:</span><br><span class="line">      --action-cache-path string                          Defines the path where the actions get cached and host workspaces created. (default &quot;/home/mano/.cache/act&quot;)</span><br><span class="line">      --action-offline-mode                               If action contents exists, it will not be fetch and pull again. If turn on this, will turn off force pull</span><br><span class="line">  -a, --actor string                                      user that triggered the event (default &quot;nektos/act&quot;)</span><br><span class="line">      --artifact-server-addr string                       Defines the address to which the artifact server binds. (default &quot;172.29.0.214&quot;)</span><br><span class="line">      --artifact-server-path string                       Defines the path where the artifact server stores uploads and retrieves downloads from. If not specified the artifact server will not start.</span><br><span class="line">      --artifact-server-port string                       Defines the port where the artifact server listens. (default &quot;34567&quot;)</span><br><span class="line">  -b, --bind                                              bind working directory to container, rather than copy</span><br><span class="line">      --bug-report                                        Display system information for bug report</span><br><span class="line">      --cache-server-addr string                          Defines the address to which the cache server binds. (default &quot;172.29.0.214&quot;)</span><br><span class="line">      --cache-server-external-url string                  Defines the external URL for if the cache server is behind a proxy. e.g.: https://act-cache-server.example.com. Be careful that there is no trailing slash.</span><br><span class="line">      --cache-server-path string                          Defines the path where the cache server stores caches. (default &quot;/home/mano/.cache/actcache&quot;)</span><br><span class="line">      --cache-server-port uint16                          Defines the port where the artifact server listens. 0 means a randomly available port.</span><br><span class="line">      --concurrent-jobs int                               Maximum number of concurrent jobs to run. Default is the number of CPUs available.</span><br><span class="line">      --container-architecture string                     Architecture which should be used to run containers, e.g.: linux/amd64. If not specified, will use host default architecture. Requires Docker server API Version 1.41+. Ignored on earlier Docker server platforms.</span><br><span class="line">      --container-cap-add stringArray                     kernel capabilities to add to the workflow containers (e.g. --container-cap-add SYS_PTRACE)</span><br><span class="line">      --container-cap-drop stringArray                    kernel capabilities to remove from the workflow containers (e.g. --container-cap-drop SYS_PTRACE)</span><br><span class="line">      --container-daemon-socket string                    URI to Docker Engine socket (e.g.: unix://~/.docker/run/docker.sock or - to disable bind mounting the socket)</span><br><span class="line">      --container-options string                          Custom docker container options for the job container without an options property in the job definition</span><br><span class="line">      --defaultbranch string                              the name of the main branch</span><br><span class="line">      --detect-event                                      Use first event type from workflow as event that triggered the workflow</span><br><span class="line">  -C, --directory string                                  working directory (default &quot;.&quot;)</span><br><span class="line">  -n, --dryrun                                            disable container creation, validates only workflow correctness</span><br><span class="line">      --env stringArray                                   env to make available to actions with optional value (e.g. --env myenv=foo or --env myenv)</span><br><span class="line">      --env-file string                                   environment file to read and use as env in the containers (default &quot;.env&quot;)</span><br><span class="line">  -e, --eventpath string                                  path to event JSON file</span><br><span class="line">      --github-instance string                            GitHub instance to use. Only use this when using GitHub Enterprise Server. (default &quot;github.com&quot;)</span><br><span class="line">  -g, --graph                                             draw workflows</span><br><span class="line">  -h, --help                                              help for act</span><br><span class="line">      --input stringArray                                 action input to make available to actions (e.g. --input myinput=foo)</span><br><span class="line">      --input-file string                                 input file to read and use as action input (default &quot;.input&quot;)</span><br><span class="line">      --insecure-secrets                                  NOT RECOMMENDED! Doesn&#x27;t hide secrets while printing logs.</span><br><span class="line">  -j, --job string                                        run a specific job ID</span><br><span class="line">      --json                                              Output logs in json format</span><br><span class="line">  -l, --list                                              list workflows</span><br><span class="line">      --list-options                                      Print a json structure of compatible options</span><br><span class="line">      --local-repository stringArray                      Replaces the specified repository and ref with a local folder (e.g. https://github.com/test/test@v0=/home/act/test or test/test@v0=/home/act/test, the latter matches any hosts or protocols)</span><br><span class="line">      --log-prefix-job-id                                 Output the job id within non-json logs instead of the entire name</span><br><span class="line">      --man-page                                          Print a generated manual page to stdout</span><br><span class="line">      --matrix stringArray                                specify which matrix configuration to include (e.g. --matrix java:13</span><br><span class="line">      --network string                                    Sets a docker network name. Defaults to host. (default &quot;host&quot;)</span><br><span class="line">      --no-cache-server                                   Disable cache server</span><br><span class="line">      --no-recurse                                        Flag to disable running workflows from subdirectories of specified path in &#x27;--workflows&#x27;/&#x27;-W&#x27; flag</span><br><span class="line">      --no-skip-checkout                                  Use actions/checkout instead of copying local files into container</span><br><span class="line">  -P, --platform stringArray                              custom image to use per platform (e.g. -P ubuntu-18.04=nektos/act-environments-ubuntu:18.04)</span><br><span class="line">      --privileged                                        use privileged mode</span><br><span class="line">  -p, --pull                                              pull docker image(s) even if already present (default true)</span><br><span class="line">  -q, --quiet                                             disable logging of output from steps</span><br><span class="line">      --rebuild                                           rebuild local action docker image(s) even if already present (default true)</span><br><span class="line">      --remote-name string                                git remote name that will be used to retrieve url of git repo (default &quot;origin&quot;)</span><br><span class="line">      --replace-ghe-action-token-with-github-com string   If you are using replace-ghe-action-with-github-com  and you want to use private actions on GitHub, you have to set personal access token</span><br><span class="line">      --replace-ghe-action-with-github-com stringArray    If you are using GitHub Enterprise Server and allow specified actions from GitHub (github.com), you can set actions on this. (e.g. --replace-ghe-action-with-github-com =github/super-linter)</span><br><span class="line">  -r, --reuse                                             don&#x27;t remove container(s) on successfully completed workflow(s) to maintain state between runs</span><br><span class="line">      --rm                                                automatically remove container(s)/volume(s) after a workflow(s) failure</span><br><span class="line">  -s, --secret stringArray                                secret to make available to actions with optional value (e.g. -s mysecret=foo or -s mysecret)</span><br><span class="line">      --secret-file string                                file with list of secrets to read from (e.g. --secret-file .secrets) (default &quot;.secrets&quot;)</span><br><span class="line">      --use-gitignore                                     Controls whether paths specified in .gitignore should be copied into container (default true)</span><br><span class="line">      --use-new-action-cache                              Enable using the new Action Cache for storing Actions locally</span><br><span class="line">      --userns string                                     user namespace to use</span><br><span class="line">      --var stringArray                                   variable to make available to actions with optional value (e.g. --var myvar=foo or --var myvar)</span><br><span class="line">      --var-file string                                   file with list of vars to read from (e.g. --var-file .vars) (default &quot;.vars&quot;)</span><br><span class="line">  -v, --verbose                                           verbose output</span><br><span class="line">      --version                                           version for act</span><br><span class="line">  -w, --watch                                             watch the contents of the local repo and run when files change</span><br><span class="line">  -W, --workflows string                                  path to workflow file(s) (default &quot;./.github/workflows/&quot;)</span><br></pre></td></tr></table></figure></div>

<h2 id="サンプルスクリプト">サンプルスクリプト</h2><p>プロジェクトルートに移動し、GitHub Actionsのワークフローファイルを配置する .github&#x2F;workflows ディレクトリを作成します。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> -p .github/workflows</span><br><span class="line"><span class="built_in">cd</span> .github/workflows</span><br></pre></td></tr></table></figure>

<p>今回は、ローカルタスクランナーとしての act を試すため、CI用のワークフローとは別に、ローカル実行専用のワークフローファイル local-tasks.yaml （名前は任意）を作成してみましょう。作成後は <code>git commit</code> をしておくと良いです。act側でgitのリビジョン情報などを取得しようとするため、未コミットの場合はWARNログなどでコンソールが埋まってしまうためです。</p>
<div class="code-block"><figure class="highlight yaml"><input type="checkbox" id="code-wrap-baan8z-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-baan8z-2" title="コードの折り返しを切り替える"></label><figcaption><span>.github/workflows/local-tasks.yaml</span></figcaption><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="attr">name:</span> <span class="string">Local</span> <span class="string">Development</span> <span class="string">Tasks</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span> [<span class="string">workflow_dispatch</span>] <span class="comment"># 手動実行できるようにするため</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">greet:</span> <span class="comment"># hello act するジョブ</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Say</span> <span class="string">Hello</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">echo</span> <span class="string">&quot;🐈️こんにちﾆｬﾝ&quot;</span></span><br></pre></td></tr></table></figure></div>

<p><code>act -j greet</code> で実行します。</p>
<p>初回はMicro&#x2F;Medium&#x2F;Largeのうち、どのランナーで動かすか？ と聞かれますが、<code>Medium</code> で良いでしょう。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-baan8z-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-baan8z-3" title="コードの折り返しを切り替える"></label><figcaption><span>実行結果</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="keyword">time</span> act -j greet</span></span><br><span class="line">INFO[0000] Using docker host &#x27;unix:///var/run/docker.sock&#x27;, and daemon socket &#x27;unix:///var/run/docker.sock&#x27;</span><br><span class="line">[Local Development Tasks/greet] ⭐ Run Set up job</span><br><span class="line">[Local Development Tasks/greet] 🚀  Start image=catthehacker/ubuntu:act-latest</span><br><span class="line">[Local Development Tasks/greet]   🐳  docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true</span><br><span class="line">[Local Development Tasks/greet] using DockerAuthConfig authentication for docker pull</span><br><span class="line">[Local Development Tasks/greet]   🐳  docker create image=catthehacker/ubuntu:act-latest platform= entrypoint=[&quot;tail&quot; &quot;-f&quot; &quot;/dev/null&quot;] cmd=[] network=&quot;host&quot;</span><br><span class="line">[Local Development Tasks/greet]   🐳  docker run image=catthehacker/ubuntu:act-latest platform= entrypoint=[&quot;tail&quot; &quot;-f&quot; &quot;/dev/null&quot;] cmd=[] network=&quot;host&quot;</span><br><span class="line">[Local Development Tasks/greet]   🐳  docker exec cmd=[node --no-warnings -e console.log(process.execPath)] user= workdir=</span><br><span class="line">[Local Development Tasks/greet]   ✅  Success - Set up job</span><br><span class="line">[Local Development Tasks/greet] ⭐ Run Main Say Hello</span><br><span class="line">[Local Development Tasks/greet]   🐳  docker exec cmd=[bash -e /var/run/act/workflow/0] user= workdir=</span><br><span class="line">| 🐈️こんにちﾆｬﾝ</span><br><span class="line">[Local Development Tasks/greet]   ✅  Success - Main Say Hello [115.57099ms]</span><br><span class="line">[Local Development Tasks/greet] ⭐ Run Complete job</span><br><span class="line">[Local Development Tasks/greet] Cleaning up container for job greet</span><br><span class="line">[Local Development Tasks/greet]   ✅  Success - Complete job</span><br><span class="line">[Local Development Tasks/greet] 🏁  Job succeeded</span><br><span class="line"></span><br><span class="line">real    0m3.386s</span><br><span class="line">user    0m0.071s</span><br><span class="line">sys     0m0.046s</span><br></pre></td></tr></table></figure></div>

<p><code>echo</code> だけで3秒…。タスクランナーとしてこの時点で利用する可能性が厳しいのでは？ とすでに感じますが、続けます。</p>
<p>続いて、リポジトリ上で <code>ls -la</code> をします。ローカル実行とは言え、実体はDockerコンテナ上で動作するため <code>actions/checkout@v4</code> をする必要があります。先程の <code>local-tasks.yaml</code> の最後に以下を追加します。</p>
<div class="code-block"><figure class="highlight diff"><input type="checkbox" id="code-wrap-baan8z-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-baan8z-4" title="コードの折り返しを切り替える"></label><figcaption><span>.github/workflows/local-tasks.yaml</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="addition">+  list-files: # ファイル一覧を表示するジョブ</span></span><br><span class="line"><span class="addition">+    runs-on: ubuntu-latest</span></span><br><span class="line"><span class="addition">+    steps:</span></span><br><span class="line"><span class="addition">+      - name: Checkout code # コードをチェックアウトしないとプロジェクトファイルにアクセスできない</span></span><br><span class="line"><span class="addition">+        uses: actions/checkout@v4</span></span><br><span class="line"><span class="addition">+      - name: List current directory</span></span><br><span class="line"><span class="addition">+        run: ls -la</span></span><br></pre></td></tr></table></figure></div>

<p><code>act -j list-files</code> で実行します。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-baan8z-5" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-baan8z-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="keyword">time</span> act -j list-files</span></span><br><span class="line">INFO[0000] Using docker host &#x27;unix:///var/run/docker.sock&#x27;, and daemon socket &#x27;unix:///var/run/docker.sock&#x27;</span><br><span class="line">[Local Development Tasks/list-files] ⭐ Run Set up job</span><br><span class="line">[Local Development Tasks/list-files] 🚀  Start image=catthehacker/ubuntu:act-latest</span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true</span><br><span class="line">[Local Development Tasks/list-files] using DockerAuthConfig authentication for docker pull</span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker create image=catthehacker/ubuntu:act-latest platform= entrypoint=[&quot;tail&quot; &quot;-f&quot; &quot;/dev/null&quot;] cmd=[] network=&quot;host&quot;</span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker run image=catthehacker/ubuntu:act-latest platform= entrypoint=[&quot;tail&quot; &quot;-f&quot; &quot;/dev/null&quot;] cmd=[] network=&quot;host&quot;</span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker exec cmd=[node --no-warnings -e console.log(process.execPath)] user= workdir=</span><br><span class="line">[Local Development Tasks/list-files]   ✅  Success - Set up job</span><br><span class="line">[Local Development Tasks/list-files] ⭐ Run Main Checkout code</span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker cp src=/home/mano/actsample/. dst=/home/mano/actsample</span><br><span class="line">[Local Development Tasks/list-files]   ✅  Success - Main Checkout code [50.644993ms]</span><br><span class="line">[Local Development Tasks/list-files] ⭐ Run Main List current directory</span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker exec cmd=[bash -e /var/run/act/workflow/1] user= workdir=</span><br><span class="line">| total 16</span><br><span class="line">| drwxr-xr-x 4 root root 4096 Jun  7 00:33 .</span><br><span class="line">| drwxr-xr-x 3 root root 4096 Jun  7 00:33 ..</span><br><span class="line">| drwxr-xr-x 7 root root 4096 Jun  7 00:33 .git</span><br><span class="line">| drwxr-xr-x 3 root root 4096 Jun  7 00:33 .github</span><br><span class="line">[Local Development Tasks/list-files]   ✅  Success - Main List current directory [125.864705ms]</span><br><span class="line">[Local Development Tasks/list-files] ⭐ Run Complete job</span><br><span class="line">[Local Development Tasks/list-files] Cleaning up container for job list-files</span><br><span class="line">[Local Development Tasks/list-files]   ✅  Success - Complete job</span><br><span class="line">[Local Development Tasks/list-files] 🏁  Job succeeded</span><br><span class="line"></span><br><span class="line">real    0m3.875s</span><br><span class="line">user    0m0.099s</span><br><span class="line">sys     0m0.055s</span><br></pre></td></tr></table></figure></div>

<p>カレントディレクトリのファイル一覧が表示されました。微妙に実行時間は長くなりました。</p>
<p>ローカルでの実行にあたり、GitHubのシークレット (secrets.GITHUB_TOKEN など) が必要なワークフローの場合、act では –secret MY_SECRET&#x3D;value や .secrets ファイルを使用してこれらを提供できます。タスクランナーとして使う場合、必ずしもシークレットが多用されるわけではありませんが、覚えておくと良いでしょう。</p>
<h2 id="他のランナーで動かすとどうなるのか">他のランナーで動かすとどうなるのか</h2><p>Runners - act - User Guide でmicro, largeで利用しているイメージが記載されています。<code>-P</code> オプションで指定できるようです。</p>
<p>おそらく、最軽量のmicroで動かします。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-baan8z-6" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-baan8z-6" title="コードの折り返しを切り替える"></label><figcaption><span>microでの実行結果</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="keyword">time</span> act -P ubuntu-latest=node:16-buster-slim -j greet</span></span><br><span class="line">INFO[0000] Using docker host &#x27;unix:///var/run/docker.sock&#x27;, and daemon socket &#x27;unix:///var/run/docker.sock&#x27;</span><br><span class="line">[Local Development Tasks/greet] ⭐ Run Set up job</span><br><span class="line">[Local Development Tasks/greet] 🚀  Start image=node:16-buster-slim</span><br><span class="line">[Local Development Tasks/greet]   🐳  docker pull image=node:16-buster-slim platform= username= forcePull=true</span><br><span class="line">[Local Development Tasks/greet] using DockerAuthConfig authentication for docker pull</span><br><span class="line">[Local Development Tasks/greet]   🐳  docker create image=node:16-buster-slim platform= entrypoint=[&quot;tail&quot; &quot;-f&quot; &quot;/dev/null&quot;] cmd=[] network=&quot;host&quot;</span><br><span class="line">[Local Development Tasks/greet]   🐳  docker run image=node:16-buster-slim platform= entrypoint=[&quot;tail&quot; &quot;-f&quot; &quot;/dev/null&quot;] cmd=[] network=&quot;host&quot;</span><br><span class="line">[Local Development Tasks/greet]   🐳  docker exec cmd=[node --no-warnings -e console.log(process.execPath)] user= workdir=</span><br><span class="line">[Local Development Tasks/greet]   ✅  Success - Set up job</span><br><span class="line">[Local Development Tasks/greet] ⭐ Run Main Say Hello</span><br><span class="line">[Local Development Tasks/greet]   🐳  docker exec cmd=[bash -e /var/run/act/workflow/0] user= workdir=</span><br><span class="line">| 🐈️こんにちﾆｬﾝ</span><br><span class="line">[Local Development Tasks/greet]   ✅  Success - Main Say Hello [101.2046ms]</span><br><span class="line">[Local Development Tasks/greet] ⭐ Run Complete job</span><br><span class="line">[Local Development Tasks/greet] Cleaning up container for job greet</span><br><span class="line">[Local Development Tasks/greet]   ✅  Success - Complete job</span><br><span class="line">[Local Development Tasks/greet] 🏁  Job succeeded</span><br><span class="line"></span><br><span class="line">real    0m2.719s</span><br><span class="line">user    0m0.033s</span><br><span class="line">sys     0m0.053s</span><br></pre></td></tr></table></figure></div>

<p>少しだけ早くなりましたが、劇的に高速化とはならないようです。</p>
<p>続いて、large を動かしたかったのですが、イメージが上手く取得できなかったので試していません。ご存じの方がいましたら、Xなどで教えて下さい。</p>
<h2 id="オフライン実行">オフライン実行</h2><p>act にはオフラインモードが存在します。ローカルにジョブコンテナやアクションのイメージがキャッシュされていれば、オフラインでも動作可能。原理的に高速化もされます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-baan8z-7" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-baan8z-7" title="コードの折り返しを切り替える"></label><figcaption><span>オフライン化greetの実行結果</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="keyword">time</span> act --action-offline-mode -j greet</span></span><br><span class="line">INFO[0000] Using docker host &#x27;unix:///var/run/docker.sock&#x27;, and daemon socket &#x27;unix:///var/run/docker.sock&#x27;</span><br><span class="line">[Local Development Tasks/greet] ⭐ Run Set up job</span><br><span class="line">[Local Development Tasks/greet] 🚀  Start image=catthehacker/ubuntu:act-latest</span><br><span class="line">[Local Development Tasks/greet]   🐳  docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=false</span><br><span class="line">[Local Development Tasks/greet]   🐳  docker create image=catthehacker/ubuntu:act-latest platform= entrypoint=[&quot;tail&quot; &quot;-f&quot; &quot;/dev/null&quot;] cmd=[] network=&quot;host&quot;</span><br><span class="line">[Local Development Tasks/greet]   🐳  docker run image=catthehacker/ubuntu:act-latest platform= entrypoint=[&quot;tail&quot; &quot;-f&quot; &quot;/dev/null&quot;] cmd=[] network=&quot;host&quot;</span><br><span class="line">[Local Development Tasks/greet]   🐳  docker exec cmd=[node --no-warnings -e console.log(process.execPath)] user= workdir=</span><br><span class="line">[Local Development Tasks/greet]   ✅  Success - Set up job</span><br><span class="line">[Local Development Tasks/greet] ⭐ Run Main Say Hello</span><br><span class="line">[Local Development Tasks/greet]   🐳  docker exec cmd=[bash -e /var/run/act/workflow/0] user= workdir=</span><br><span class="line">| 🐈️こんにちﾆｬﾝ</span><br><span class="line">[Local Development Tasks/greet]   ✅  Success - Main Say Hello [119.563033ms]</span><br><span class="line">[Local Development Tasks/greet] ⭐ Run Complete job</span><br><span class="line">[Local Development Tasks/greet] Cleaning up container for job greet</span><br><span class="line">[Local Development Tasks/greet]   ✅  Success - Complete job</span><br><span class="line">[Local Development Tasks/greet] 🏁  Job succeeded</span><br><span class="line"></span><br><span class="line">real    0m1.073s</span><br><span class="line">user    0m0.074s</span><br><span class="line">sys     0m0.013s</span><br></pre></td></tr></table></figure></div>

<p>2-3倍、性能が改善しました。体感上もこれなら待てます。続いて、checkout@v4 を含んだlist-filesを動かします。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-baan8z-8" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-baan8z-8" title="コードの折り返しを切り替える"></label><figcaption><span>オフライン化list-filesの実行結果</span></figcaption><table><tr><td class="code"><pre><span class="line">e$ <span class="keyword">time</span> act --action-offline-mode -j list-files</span><br><span class="line">INFO[0000] Using docker host <span class="string">&#x27;unix:///var/run/docker.sock&#x27;</span>, and daemon socket <span class="string">&#x27;unix:///var/run/docker.sock&#x27;</span></span><br><span class="line">[Local Development Tasks/list-files] ⭐ Run Set up job</span><br><span class="line">[Local Development Tasks/list-files] 🚀  Start image=catthehacker/ubuntu:act-latest</span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=<span class="literal">false</span></span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker create image=catthehacker/ubuntu:act-latest platform= entrypoint=[<span class="string">&quot;tail&quot;</span> <span class="string">&quot;-f&quot;</span> <span class="string">&quot;/dev/null&quot;</span>] cmd=[] network=<span class="string">&quot;host&quot;</span></span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker run image=catthehacker/ubuntu:act-latest platform= entrypoint=[<span class="string">&quot;tail&quot;</span> <span class="string">&quot;-f&quot;</span> <span class="string">&quot;/dev/null&quot;</span>] cmd=[] network=<span class="string">&quot;host&quot;</span></span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker <span class="built_in">exec</span> cmd=[node --no-warnings -e console.log(process.execPath)] user= workdir=</span><br><span class="line">[Local Development Tasks/list-files]   ✅  Success - Set up job</span><br><span class="line">[Local Development Tasks/list-files] ⭐ Run Main Checkout code</span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker <span class="built_in">cp</span> src=/home/mano/actsample/. dst=/home/mano/actsample</span><br><span class="line">[Local Development Tasks/list-files]   ✅  Success - Main Checkout code [34.498731ms]</span><br><span class="line">[Local Development Tasks/list-files] ⭐ Run Main List current directory</span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker <span class="built_in">exec</span> cmd=[bash -e /var/run/act/workflow/1] user= workdir=</span><br><span class="line">| total 16</span><br><span class="line">| drwxr-xr-x 4 root root 4096 Jun  7 01:29 .</span><br><span class="line">| drwxr-xr-x 3 root root 4096 Jun  7 01:29 ..</span><br><span class="line">| drwxr-xr-x 7 root root 4096 Jun  7 01:29 .git</span><br><span class="line">| drwxr-xr-x 3 root root 4096 Jun  7 01:29 .github</span><br><span class="line">[Local Development Tasks/list-files]   ✅  Success - Main List current directory [122.866479ms]</span><br><span class="line">[Local Development Tasks/list-files] ⭐ Run Complete job</span><br><span class="line">[Local Development Tasks/list-files] Cleaning up container <span class="keyword">for</span> job list-files</span><br><span class="line">[Local Development Tasks/list-files]   ✅  Success - Complete job</span><br><span class="line">[Local Development Tasks/list-files] 🏁  Job succeeded</span><br><span class="line"></span><br><span class="line">real    0m1.164s</span><br><span class="line">user    0m0.059s</span><br><span class="line">sys     0m0.044s</span><br></pre></td></tr></table></figure></div>

<p>こちらも2-3倍高速化しています。これならまだなんとかなるかもしれません。</p>
<h2 id="それなりに歴史を重ねたリポジトリで動かしてみる">それなりに歴史を重ねたリポジトリで動かしてみる</h2><p>試したリポジトリサイズは以下です。<code>size-pack</code> がリモートサーバーにpushされた時のサイズとのことで、1.3GiB程度です。</p>
<figure class="highlight console"><figcaption><span>リポジトリサイズ計測</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git gc</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">git count-objects -vH</span></span><br><span class="line">count: 0</span><br><span class="line">size: 0 bytes</span><br><span class="line">in-pack: 188260</span><br><span class="line">packs: 1</span><br><span class="line">size-pack: 1.13 GiB</span><br><span class="line">prune-packable: 0</span><br><span class="line">garbage: 0</span><br><span class="line">size-garbage: 0 bytes</span><br></pre></td></tr></table></figure>

<p>これで試してみます。 先程の <code>act --action-offline-mode -j list-files</code> を試してみます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-baan8z-9" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-baan8z-9" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="keyword">time</span> act --action-offline-mode -j list-files</span></span><br><span class="line">INFO[0000] Using docker host &#x27;unix:///var/run/docker.sock&#x27;, and daemon socket &#x27;unix:///var/run/docker.sock&#x27;</span><br><span class="line">[Local Development Tasks/list-files] ⭐ Run Set up job</span><br><span class="line">[Local Development Tasks/list-files] 🚀  Start image=catthehacker/ubuntu:act-latest</span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=false</span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker create image=catthehacker/ubuntu:act-latest platform= entrypoint=[&quot;tail&quot; &quot;-f&quot; &quot;/dev/null&quot;] cmd=[] network=&quot;host&quot;</span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker run image=catthehacker/ubuntu:act-latest platform= entrypoint=[&quot;tail&quot; &quot;-f&quot; &quot;/dev/null&quot;] cmd=[] network=&quot;host&quot;</span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker exec cmd=[node --no-warnings -e console.log(process.execPath)] user= workdir=</span><br><span class="line">[Local Development Tasks/list-files]   ✅  Success - Set up job</span><br><span class="line">[Local Development Tasks/list-files] ⭐ Run Main Checkout code</span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker cp src=/home/mano/myRepo/. dst=/home/mano/myRepo</span><br><span class="line">[Local Development Tasks/list-files]   ✅  Success - Main Checkout code [5.007896049s]</span><br><span class="line">[Local Development Tasks/list-files] ⭐ Run Main List current directory</span><br><span class="line">[Local Development Tasks/list-files]   🐳  docker exec cmd=[bash -e /var/run/act/workflow/1] user= workdir=</span><br><span class="line">| total 236</span><br><span class="line">（中略）</span><br><span class="line">| drwxr-xr-x 18 root root   4096 Jun  9 01:04 tool</span><br><span class="line">[Local Development Tasks/list-files]   ✅  Success - Main List current directory [126.408501ms]</span><br><span class="line">[Local Development Tasks/list-files] ⭐ Run Complete job</span><br><span class="line">[Local Development Tasks/list-files] Cleaning up container for job list-files</span><br><span class="line">[Local Development Tasks/list-files]   ✅  Success - Complete job</span><br><span class="line">[Local Development Tasks/list-files] 🏁  Job succeeded</span><br><span class="line"></span><br><span class="line">real    0m6.452s</span><br><span class="line">user    0m0.530s</span><br><span class="line">sys     0m2.852s</span><br></pre></td></tr></table></figure></div>

<p>チェックアウトだけで5秒程度が追加となり、全体で6.5秒程度。リポジトリサイズが増えると厳しい感じがしますね。</p>
<h2 id="プロキシ、カスタム証明書の読み込みが難しい？">プロキシ、カスタム証明書の読み込みが難しい？</h2><p>例えば、Goの環境を構築したい場合、以下のように <code>actions/setup-go</code> などを呼び出します。しかし、ローカル環境によってはエラーになります。</p>
<figure class="highlight diff"><figcaption><span>local-tasks.yaml</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="addition">+  lint:</span></span><br><span class="line"><span class="addition">+    name: Lint Go Files</span></span><br><span class="line"><span class="addition">+    runs-on: ubuntu-latest</span></span><br><span class="line"><span class="addition">+    steps:</span></span><br><span class="line"><span class="addition">+      - name: Checkout code</span></span><br><span class="line"><span class="addition">+        uses: actions/checkout@v4</span></span><br><span class="line"><span class="addition">+      - name: Set up Go</span></span><br><span class="line"><span class="addition">+        uses: actions/setup-go@v5</span></span><br><span class="line"><span class="addition">+        with:</span></span><br><span class="line"><span class="addition">+          go-version: &#x27;1.22&#x27;</span></span><br><span class="line"><span class="addition">+      - name: Run vet</span></span><br><span class="line"><span class="addition">+        run: go vet ./...</span></span><br></pre></td></tr></table></figure>

<p>エラーの例です。 <code>failed to verify certificate: x509: certificate signed by unknown authority</code> とカスタム証明書を利用している環境において、あるあるなエラーが出ています。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-baan8z-10" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-baan8z-10" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">[Local Development Tasks/Format Go Files] Unable to <span class="built_in">clone</span> https://github.com/actions/setup-go refs/heads/v5: Get <span class="string">&quot;https://github.com/actions/setup-go/info/refs?service=git-upload-pack&quot;</span>: tls: failed to verify certificate: x509: certificate signed by unknown authority</span><br></pre></td></tr></table></figure></div>

<p>カスタムイメージをビルドしたり、ルート証明書を読み込ませたり、SSL VERIFYを無効化などいろいろ試しましたが、残念ながら私の実力では未解決でした。もちろんこの課題が発生すること自体が組織のネットワークポリシー次第であり万人がハマるわけではありません。しかし、ランナーのコンテナが起動する分、環境セットアップが難しくなることは間違いなく、構造上、難易度が高くなるなという印象です。</p>
<p>ちなみに、 <code>setup-go@v5</code> を利用せず、個別に定義を書けば成功できました（もはや、GitHub Actionsのお作法からは外れていますが）。</p>
<div class="code-block"><figure class="highlight diff"><input type="checkbox" id="code-wrap-baan8z-11" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-baan8z-11" title="コードの折り返しを切り替える"></label><figcaption><span>local-tasks.yaml</span></figcaption><table><tr><td class="code"><pre><span class="line">  lint:</span><br><span class="line">    name: Lint Go Files</span><br><span class="line">    runs-on: ubuntu-latest</span><br><span class="line"></span><br><span class="line">    defaults:</span><br><span class="line">      run:</span><br><span class="line">        working-directory: /workdir</span><br><span class="line"></span><br><span class="line">    steps:</span><br><span class="line">      - name: Checkout code</span><br><span class="line">        uses: actions/checkout@v4</span><br><span class="line">      - name: Set up Go</span><br><span class="line"><span class="deletion">-        uses: actions/setup-go@v5</span></span><br><span class="line"><span class="deletion">-        with:</span></span><br><span class="line"><span class="deletion">-          go-version: &#x27;1.22&#x27;</span></span><br><span class="line"><span class="addition">+        run: |</span></span><br><span class="line"><span class="addition">+          curl -sSL -k -o go.tar.gz https://go.dev/dl/go1.22.4.linux-amd64.tar.gz</span></span><br><span class="line"><span class="addition">+          sudo tar -C /usr/local -xzf go.tar.gz</span></span><br><span class="line"><span class="addition">+          echo &quot;/usr/local/go/bin&quot; | sudo tee -a $GITHUB_PATH</span></span><br><span class="line">      - name: Run vet</span><br><span class="line">        run: cd backend &amp;&amp; go vet ./...</span><br></pre></td></tr></table></figure></div>

<p>なお、上記は curl の部分で <code>-k</code> をつけて簡易的に実装しています。プロダクションで用いる場合はカスタム証明書を読み込ませた方がよいでしょう。</p>
<p>actでリンター（<code>go vet</code>） を実行して成功した結果です。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-baan8z-12" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-baan8z-12" title="コードの折り返しを切り替える"></label><figcaption><span>実行結果</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="keyword">time</span> act -j lint</span></span><br><span class="line">INFO[0000] Using docker host &#x27;unix:///var/run/docker.sock&#x27;, and daemon socket &#x27;unix:///var/run/docker.sock&#x27;</span><br><span class="line">[Local Development Tasks/Lint Go Files] ⭐ Run Set up job</span><br><span class="line">[Local Development Tasks/Lint Go Files] 🚀  Start image=catthehacker/ubuntu:act-latest</span><br><span class="line">[Local Development Tasks/Lint Go Files]   🐳  docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true</span><br><span class="line">[Local Development Tasks/Lint Go Files] using DockerAuthConfig authentication for docker pull</span><br><span class="line">[Local Development Tasks/Lint Go Files]   🐳  docker create image=catthehacker/ubuntu:act-latest platform= entrypoint=[&quot;tail&quot; &quot;-f&quot; &quot;/dev/null&quot;] cmd=[] network=&quot;host&quot;</span><br><span class="line">[Local Development Tasks/Lint Go Files]   🐳  docker run image=catthehacker/ubuntu:act-latest platform= entrypoint=[&quot;tail&quot; &quot;-f&quot; &quot;/dev/null&quot;] cmd=[] network=&quot;host&quot;</span><br><span class="line">[Local Development Tasks/Lint Go Files]   🐳  docker exec cmd=[node --no-warnings -e console.log(process.execPath)] user= workdir=</span><br><span class="line">[Local Development Tasks/Lint Go Files]   ✅  Success - Set up job</span><br><span class="line">[Local Development Tasks/Lint Go Files] ⭐ Run Main Checkout code</span><br><span class="line">[Local Development Tasks/Lint Go Files]   🐳  docker cp src=/home/mano/MyRepo/. dst=/home/mano/MyRepo</span><br><span class="line">[Local Development Tasks/Lint Go Files]   ✅  Success - Main Checkout code [14.494783288s]</span><br><span class="line">[Local Development Tasks/Lint Go Files] ⭐ Run Main Install Go</span><br><span class="line">[Local Development Tasks/Lint Go Files]   🐳  docker exec cmd=[bash -e /var/run/act/workflow/1] user= workdir=</span><br><span class="line">| /usr/local/go/bin</span><br><span class="line">[Local Development Tasks/Lint Go Files]   ✅  Success - Main Install Go [16.761998127s]</span><br><span class="line">[Local Development Tasks/Lint Go Files]   ⚙  ::add-path:: /usr/local/go/bin</span><br><span class="line">[Local Development Tasks/Lint Go Files] ⭐ Run Main Run vet</span><br><span class="line">[Local Development Tasks/Lint Go Files]   🐳  docker exec cmd=[bash -e /var/run/act/workflow/2] user= workdir=</span><br><span class="line">| go: downloading github.com/aws/aws-lambda-go v1.41.0</span><br><span class="line">| go: downloading github.com/rs/zerolog v1.29.0</span><br><span class="line">| go: downloading github.com/go-playground/validator/v10 v10.16.0</span><br><span class="line">（中略）</span><br><span class="line">[Local Development Tasks/Lint Go Files]   ✅  Success - Main Run vet [59.368599077s]</span><br><span class="line">[Local Development Tasks/Lint Go Files] ⭐ Run Complete job</span><br><span class="line">[Local Development Tasks/Lint Go Files] Cleaning up container for job Lint Go Files</span><br><span class="line">[Local Development Tasks/Lint Go Files]   ✅  Success - Complete job</span><br><span class="line">[Local Development Tasks/Lint Go Files] 🏁  Job succeeded</span><br><span class="line"></span><br><span class="line">real    1m40.689s</span><br><span class="line">user    0m0.769s</span><br><span class="line">sys     0m3.229s</span><br></pre></td></tr></table></figure></div>

<p>厳しいのは、実行時間が1分40秒かかったというところでしょう。これは <code>go vet</code> を実行するためにコンパイルが必要なので、 <code>go mod download</code> 相当の処理が動くためです。<code>go mod</code> 側のキャッシュをボリュームマウントすれば高速化できると思いますが、逆に言うとそういったチューニングが必要だということです。</p>
<p>ちなみにもし、<code>go vet</code> が失敗（違反コードが存在）した場合は exit 1 でジョブが以下のように失敗します。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-baan8z-13" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-baan8z-13" title="コードの折り返しを切り替える"></label><figcaption><span>失敗例</span></figcaption><table><tr><td class="code"><pre><span class="line">| <span class="comment"># github.com/.../...</span></span><br><span class="line">| app/my_model.go:30:2: struct field myfields has json tag but is not exported</span><br><span class="line">[Local Development Tasks/Lint Go Files]   ❌  Failure - Main Run go vet [1m2.783279728s]</span><br><span class="line">[Local Development Tasks/Lint Go Files] exitcode <span class="string">&#x27;1&#x27;</span>: failure</span><br><span class="line">[Local Development Tasks/Lint Go Files] ⭐ Run Complete job</span><br><span class="line">[Local Development Tasks/Lint Go Files]   ✅  Success - Complete job</span><br><span class="line">[Local Development Tasks/Lint Go Files] 🏁  Job failed</span><br><span class="line">Error: Job <span class="string">&#x27;Lint Go Files&#x27;</span> failed</span><br></pre></td></tr></table></figure></div>

<p>成功&#x2F;失敗の表示は、タスクランナーとして、特段大きな違和感は無いと思います。</p>
<h2 id="フォーマットする場合">フォーマットする場合</h2><p>フォーマットやコード生成などの場合は、ホスト側のコードに反映させる必要があります。この場合、checkout 経由ですと、フォーマット結果を反映できず困ってしまいます。そのため、ボリュームマウントで対応します。ボリュームマウントするので、 checkout のステップは無くすことができます。</p>
<p><code>defaults.run.working-directory</code> に適当なマウント先のフォルダを定義します。</p>
<div class="code-block"><figure class="highlight diff"><input type="checkbox" id="code-wrap-baan8z-14" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-baan8z-14" title="コードの折り返しを切り替える"></label><figcaption><span>local-tasks.yaml</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="addition">+  fmt:</span></span><br><span class="line"><span class="addition">+    name: Format Go Files</span></span><br><span class="line"><span class="addition">+    runs-on: ubuntu-latest</span></span><br><span class="line"><span class="addition">+    defaults:</span></span><br><span class="line"><span class="addition">+      run:</span></span><br><span class="line"><span class="addition">+        working-directory: /workdir # マウント先を適当に定義</span></span><br><span class="line"><span class="addition">+    steps:</span></span><br><span class="line"><span class="addition">+      - name: Install Go</span></span><br><span class="line"><span class="addition">+        run: |</span></span><br><span class="line"><span class="addition">+          curl -sSL -k -o go.tar.gz https://go.dev/dl/go1.22.4.linux-amd64.tar.gz</span></span><br><span class="line"><span class="addition">+          sudo tar -C /usr/local -xzf go.tar.gz</span></span><br><span class="line"><span class="addition">+          echo &quot;/usr/local/go/bin&quot; | sudo tee -a $GITHUB_PATH</span></span><br><span class="line"><span class="addition">+     - name: Run gofmt</span></span><br><span class="line"><span class="addition">+        run: gofmt -l -w .</span></span><br></pre></td></tr></table></figure></div>

<p>実行時は <code>--container-options</code> でボリュームマウント定義を渡します。このオプションはドキュメントで探せなかったのですが、 https://github.com/nektos/act/issues/1548 のIssueから見つけました。</p>
<p>act コマンドを実行します。 <code>--container-options &quot;-v $(pwd):/workdir&quot;</code> の <code>/workdir</code> の値は、さきほどの <code>local-tasks.yaml</code> で指定した値と一致させます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-baan8z-15" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-baan8z-15" title="コードの折り返しを切り替える"></label><figcaption><span>実行結果</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="keyword">time</span> act -j <span class="built_in">fmt</span> --container-options <span class="string">&quot;-v <span class="subst">$(pwd)</span>:/workdir&quot;</span></span></span><br><span class="line">INFO[0000] Using docker host &#x27;unix:///var/run/docker.sock&#x27;, and daemon socket &#x27;unix:///var/run/docker.sock&#x27;</span><br><span class="line">[Local Development Tasks/Format Go Files] ⭐ Run Set up job</span><br><span class="line">[Local Development Tasks/Format Go Files] 🚀  Start image=catthehacker/ubuntu:act-latest</span><br><span class="line">[Local Development Tasks/Format Go Files]   🐳  docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true</span><br><span class="line">[Local Development Tasks/Format Go Files] using DockerAuthConfig authentication for docker pull</span><br><span class="line">[Local Development Tasks/Format Go Files]   🐳  docker create image=catthehacker/ubuntu:act-latest platform= entrypoint=[&quot;tail&quot; &quot;-f&quot; &quot;/dev/null&quot;] cmd=[] network=&quot;host&quot;</span><br><span class="line">[Local Development Tasks/Format Go Files]   🐳  docker run image=catthehacker/ubuntu:act-latest platform= entrypoint=[&quot;tail&quot; &quot;-f&quot; &quot;/dev/null&quot;] cmd=[] network=&quot;host&quot;</span><br><span class="line">[Local Development Tasks/Format Go Files]   🐳  docker exec cmd=[node --no-warnings -e console.log(process.execPath)] user= workdir=</span><br><span class="line">[Local Development Tasks/Format Go Files]   ✅  Success - Set up job</span><br><span class="line">[Local Development Tasks/Format Go Files] ⭐ Run Main Install Go</span><br><span class="line">[Local Development Tasks/Format Go Files]   🐳  docker exec cmd=[bash -e /var/run/act/workflow/0] user= workdir=/workdir</span><br><span class="line">| /usr/local/go/bin</span><br><span class="line">[Local Development Tasks/Format Go Files]   ✅  Success - Main Install Go [17.32964836s]</span><br><span class="line">[Local Development Tasks/Format Go Files]   ⚙  ::add-path:: /usr/local/go/bin</span><br><span class="line">[Local Development Tasks/Format Go Files] ⭐ Run Main Run gofmt</span><br><span class="line">[Local Development Tasks/Format Go Files]   🐳  docker exec cmd=[bash -e /var/run/act/workflow/1] user= workdir=/workdir</span><br><span class="line">| backend/my_model.go</span><br><span class="line">[Local Development Tasks/Format Go Files]   ✅  Success - Main Run gofmt [711.356576ms]</span><br><span class="line">[Local Development Tasks/Format Go Files] ⭐ Run Complete job</span><br><span class="line">[Local Development Tasks/Format Go Files] Cleaning up container for job Format Go Files</span><br><span class="line">[Local Development Tasks/Format Go Files]   ✅  Success - Complete job</span><br><span class="line">[Local Development Tasks/Format Go Files] 🏁  Job succeeded</span><br><span class="line"></span><br><span class="line">real    0m20.747s</span><br><span class="line">user    0m0.069s</span><br><span class="line">sys     0m0.111s</span><br></pre></td></tr></table></figure></div>

<p>checkout が無くなった分、高速化したのと、単純に <code>gofmt</code> を呼ぶだけ（コンパイルなどは不要）であるため、20秒で終わりました。<code>--pull=false</code> や <code>--action-offline-mode</code> をつければ、数秒早くできる可能性があります。ちなみに、ホスト上で直接 <code>gofmt</code> を呼び出す場合は1～2秒で終わります。</p>
<p>ボリュームマウントですが、ローカルでの実行速度を最優先に考えるのであれば、<code>actions/checkout@v4</code> を呼び出さなくて済むため必須かもしれないと思いました。もちろん、代償として GitHub Actions とのコード共有・再利用性は下がります。</p>
<h2 id="Makefileとの棲み分けは？">Makefileとの棲み分けは？</h2><p>リンターやフォーマッタなど、具体的なコマンドはMakefile（Taskfile）に記載し、ローカル開発時にはそれらのタスクコマンドを単純呼び出しできるようにしておく方がデバッグもやりやすいかなと思います。CI&#x2F;CD定義からはそれらのコマンドを呼び出すだけ、という構成にすると、定義が重複せず保守性を保てるでしょう。</p>
<p>このような棲み分けの概念を壊せないかと、CI&#x2F;CD定義を直接ローカルで動かせる act を、タスクランナーとして使ってみようという試みでしたが、現時点ではプロキシ・カスタム証明書の問題が解決したとしても、実行時間のオーバーヘッドが大きく微妙です。そもそも、定義の共有自体が私の技術力では微妙な結果に終わってしまいました。そのため、この記事の結論としては、よくローカル開発で実行するコマンドは、 act 経由ではなく引き続きMakefileやTaskfileを利用する方が無難でしょう。</p>
<p>ボリュームマウントの定義などは煩雑なので、何ならMakefileにactの呼び出しコマンドを書いてしまいたいくらいです。</p>
<h2 id="まとめ">まとめ</h2><p>act をタスクランナーとして試しました。</p>
<p>現時点で得た課題感は以下です。</p>
<ul>
<li>actでは全てのタスクが、コンテナ上で動くため、起動のオーバーヘッドが1～3数秒かかる</li>
<li>1GiB超えのリポジトリの場合は、チェックアウトのみでさらに5秒程度かかる</li>
<li>依存ライブラリの解決など毎回実行するには重い処理は、キャッシュが有効だが、そうするとホストとのボリュームマウントなど面倒なチューニングが必要となり、管理コストが上がる</li>
<li>フォーマットやコード生成など、ホスト側のファイルを書き換えたい場合は <code>actions/checkout@v4</code> を行わず、直接リポジトリごとボリュームマウントする必要があり、管理コストが上がる</li>
<li>プロキシ・カスタム証明書などを前提とする組織ネットワークでは、 <code>actions/setup-go</code> などのコマンドがうまく動作しない可能性。そのため、プロキシ問題をトラブルシュート＆解決できる人材・時間が必要</li>
</ul>
<p>上記、チューニングや環境構築に成功したとしても、GitHub Actions側の <code>workflows</code> 定義の共有は難しく、結局、別のファイルとして管理することになりそうということでした。</p>
<p>act 側のナレッジをチームで積んでいけば、性能その他の課題は潰せそうですが、タスクランナーとして利用するのは、それなりの意思決定が必要になりそうな印象です。GitHub Actionsにある程度習熟した人であればもう少し別の見方になるかもしれません。積極的に導入しているよーという方やチームがいらっしゃいましたら、Xなどで教えてください。</p>
]]></content>
    <summary type="html">GitHub Actionsをローカル環境で実行できる nektos/act をMakefileやTaskfileなどのタスクランナーの代わりとして使えるのか、試してみた記事です</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="GitHubActions" scheme="https://future-architect.github.io/tags/GitHubActions/"/>
    <category term="Makefile" scheme="https://future-architect.github.io/tags/Makefile/"/>
  </entry>
  <entry>
    <title>GitHub 標準の Annotation を活用してレビューを可視化する</title>
    <link href="https://future-architect.github.io/articles/20250604a/"/>
    <id>https://future-architect.github.io/articles/20250604a/</id>
    <published>2025-06-03T15:00:00.000Z</published>
    <updated>2025-06-03T15:00:00.000Z</updated>
    <author><name>武田大輝</name></author>
    <content type="html"><![CDATA[<p>CI&#x2F;CD連載 2本目の記事です。</p>
<h2 id="はじめに">はじめに</h2><p>コードレビューを自動で可視化するためのツールといえば reviewdog が有名です。</p>
<p>最近（2025年03月）reviewdog のソースリポジトリが侵害され、reviewdog を実行しているリポジトリにおいて シークレット情報がワークフロー上に漏洩するセキュリティインシデントが発生 したことは記憶に新しいでしょう。</p>
<p>筆者も reviewdog にはお世話になっている開発者の一人ですが、本件を受けて「そもそも GitHub の標準機能だけで同じようなことができないか」という考えを持ち、あらためて GitHub 標準の Annotation 機能について調べてみた記事になります。</p>
<p>なお、reviewdog 自体の有用性を否定するものでは一切ありません。</p>
<h2 id="レビューの可視化とは">レビューの可視化とは</h2><p>まず誤解のないよう本記事における「レビューの可視化」とは具体的に何を指すのかを説明しておきます。</p>
<p>レビューの可視化とは、Linter や Formatter などソースコードの解析結果をもとに、結果をわかりやすく表示してくれるしくみのことを指しています。実際にソースコードのエラーや警告を検出する部分の話ではなく、その結果を解析してよい感じに開発者へフィードバックしてくれる部分の話となります。</p>
<h2 id="reviewdog-とは">reviewdog とは</h2><p>reviewdog は Linter や Formatter などの出力結果を GitHub などのコードホスティングサービス上にコメントなどで投稿してくれるツールです。</p>
<p>その思想や誕生の背景については、開発者のブログを貼る形で本記事では割愛したいと思います。</p>
<p>http://haya14busa.com/reviewdog/</p>
<h2 id="GitHub-Annotation-とは">GitHub Annotation とは</h2><p>GitHub Annotation（アノテーション）とは、GitHub Actions のワークフロー実行中に出力されるエラーや警告などを、対象のファイルや行に紐付けて GitHub の UI 上に表示する標準のしくみです。公式ドキュメントでは、GitHub Actions のワークフローコマンドの中で説明されています。</p>
<p>https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions</p>
<p>具体的には <code>::error</code>, <code>::warning</code>, <code>::notice</code> といったコマンドをファイルパスや行数とともに出力することで、アノテーションを作成できます。</p>
<div class="code-block"><figure class="highlight text"><input type="checkbox" id="code-wrap-1ep1s7g-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1ep1s7g-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">::error file=&#123;name&#125;,line=&#123;line&#125;,endLine=&#123;endLine&#125;,title=&#123;title&#125;::&#123;message&#125;</span><br></pre></td></tr></table></figure></div>

<p>実際にアノテーションを作成するサンプルを見てみましょう。<br>次のようなワークフローファイルを作成して GitHub Actions を実行してみます。<code>echo &quot;::error ...&quot;</code> や <code>echo &quot;::warning...&quot;</code> と記載している部分がコマンド部分になります。</p>
<div class="code-block"><figure class="highlight yaml"><input type="checkbox" id="code-wrap-1ep1s7g-2" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1ep1s7g-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">Emit</span> <span class="string">Annotation</span> <span class="string">Directly</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">pull_request:</span></span><br><span class="line">  <span class="attr">workflow_dispatch:</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">demo:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/checkout@v4</span></span><br><span class="line"></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Emit</span> <span class="string">Error</span> <span class="string">and</span> <span class="string">Warning</span> <span class="string">Annotations</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          echo &quot;::error file=.github/workflows/annotation.yaml,line=16,col=5::Example Error: This is a sample error message for demonstration purposes.&quot;</span></span><br><span class="line"><span class="string">          echo &quot;::warning file=.github/workflows/annotation.yaml,line=17,col=5::Example Warning: This is a sample warning message for demonstration purposes.&quot;</span></span><br></pre></td></tr></table></figure></div>

<p>このワークフローを実行して GitHub Actions の実行結果サマリをみると 次のように アノテーションが出力されます。</p>
<img fetchpriority="high" src="/images/2025/20250604a/annotations_in_summary.png" alt="annotations_in_summary.png" width="1200" height="653">

<p>また、このワークフローが PR（Pull Request）をトリガとして実行されている場合は、 次のように PR の「Files changed」タブから該当する箇所にインラインでアノテーションが表示されていることが確認できます。</p>
<img src="/images/2025/20250604a/annotations_in_pull_request.png" alt="annotations_in_pull_request.png" width="1200" height="726" loading="lazy">

<p>このように、GitHub Annotation を活用することで該当ファイル・該当行にピンポイントでメッセージを表示できます。<br>サンプルではファイルや行数をベタ書きしましたが、 Linter や Formatter などの解析ツールと組み合わせることで、これらのツールの実行結果をアノテーションとしてユーザにフィードバックできます。</p>
<h2 id="GitHub-Annotation-の実践的な活用方法">GitHub Annotation の実践的な活用方法</h2><p>GitHub Annotation の概要について理解できたところで、実践的な使い方について説明していきます。</p>
<h3 id="Problem-Matcher-の利用">Problem Matcher の利用</h3><p>Problem Matcher とは、GitHub Actions におけるログ出力からエラーや警告の情報を自動的に抽出し、アノテーションとして表示するためのしくみです。</p>
<p>先ほどの例では直接 <code>echo &quot;::error ...&quot;</code> と記述していましたが、これをより汎用的に使いやすくした機能だと理解してもらえれば OK です。</p>
<p>Problem Matcher はパターン（正規表現）を JSON 形式で定義することで、任意のツールの出力形式にマッチさせます。<br>公式ドキュメントにも記載されている ESLint の出力結果に対応させる例をみてみましょう。</p>
<div class="code-block"><figure class="highlight text"><input type="checkbox" id="code-wrap-1ep1s7g-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1ep1s7g-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">test.js</span><br><span class="line">  1:0   error  Missing &quot;use strict&quot; statement                 strict</span><br><span class="line">  5:10  error  &#x27;addOne&#x27; is defined but never used             no-unused-vars</span><br><span class="line">✖ 2 problems (2 errors, 0 warnings)</span><br></pre></td></tr></table></figure></div>

<h4 id="設定ファイル">設定ファイル</h4><p>この ESLint の出力結果に対応する Problem Matcher の設定ファイルは次のとおりです。</p>
<div class="code-block"><figure class="highlight json"><input type="checkbox" id="code-wrap-1ep1s7g-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1ep1s7g-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;problemMatcher&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;owner&quot;</span><span class="punctuation">:</span> <span class="string">&quot;eslint-stylish&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;pattern&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="comment">// Matches the 1st line in the output</span></span><br><span class="line">          <span class="attr">&quot;regexp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^([^\\s].*)$&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;file&quot;</span><span class="punctuation">:</span> <span class="number">1</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="punctuation">&#123;</span></span><br><span class="line">          <span class="comment">// Matches the 2nd and 3rd line in the output</span></span><br><span class="line">          <span class="attr">&quot;regexp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^\\s+(\\d+):(\\d+)\\s+(error|warning|info)\\s+(.*)\\s\\s+(.*)$&quot;</span><span class="punctuation">,</span></span><br><span class="line">          <span class="comment">// File is carried through from above, so we define the rest of the groups</span></span><br><span class="line">          <span class="attr">&quot;line&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;column&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;severity&quot;</span><span class="punctuation">:</span> <span class="number">3</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="number">4</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;code&quot;</span><span class="punctuation">:</span> <span class="number">5</span><span class="punctuation">,</span></span><br><span class="line">          <span class="attr">&quot;loop&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">        <span class="punctuation">&#125;</span></span><br><span class="line">      <span class="punctuation">]</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></div>

<p>ESLint の出力のように、最初にファイル名が 1 行だけ表示され、その後に複数のエラーや警告が続く「マルチライン形式」の出力に対応するため、Problem Matcher では複数の <code>pattern</code> を組み合わせて定義できます。</p>
<p>最初のパターンでは、ファイル名の行を検出します。正規表現（<code>&quot;regexp&quot;: &quot;^([^\\s].*)$&quot;</code>）により、先頭が空白で始まらない行をファイル名として抽出し、ファイル名として指定（<code>&quot;file&quot;: 1</code>）しています。ここでの 1 は、正規表現内の括弧 () によって囲まれた 1 番目のキャプチャグループを指しています。</p>
<p>最初のパターンはファイル名の出力に対応する部分となります。正規表現（<code>&quot;regexp&quot;: &quot;^([^\\s].*)$&quot;</code>）でマッチした部分をファイル名（<code>&quot;file&quot;: 1</code>）として設定しています。<code>&quot;file&quot;: 1</code> の <code>1</code> は正規表現のキャプチャグループの番号を表しています。<br>続く 2 つ目のパターンでは、各エラー・警告行の情報（行番号、列番号、深刻度、メッセージ、ルール名など）をそれぞれのキャプチャグループから取り出し、アノテーションとして表示するための各要素にマッピングしています。各要素は次のとおりです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>項目</th>
<th>説明</th>
<th>実際の値</th>
</tr>
</thead>
<tbody><tr>
<td>line</td>
<td>行番号</td>
<td>1</td>
</tr>
<tr>
<td>column</td>
<td>列番号</td>
<td>0</td>
</tr>
<tr>
<td>severity</td>
<td>エラーの重大度</td>
<td>error</td>
</tr>
<tr>
<td>message</td>
<td>メッセージ</td>
<td>Missing “use strict” statement</td>
</tr>
<tr>
<td>code</td>
<td>メッセージに対応するルール名や識別子</td>
<td>strict</td>
</tr>
</tbody></table></div>
<p>最後の <code>&quot;loop&quot;: true</code> は、同じファイルに対して複数行のエラー・警告が連続して出力される場合に、1 回目に取得したファイル名を保持したまま、後続の行に繰り返しこのパターンを適用するための設定です。</p>
<h4 id="登録と削除">登録と削除</h4><p>Problem Matcher は、GitHub Actions のワークフロー内で <code>::add-matcher::</code> および <code>::remove-matcher::</code> コマンドを使用することで動的に登録・削除できます。</p>
<p>たとえば、先ほどの <code>eslint-matcher.json</code> ファイルを <code>.github/matchers/</code> ディレクトリに保存した場合、次のように登録できます。</p>
<div class="code-block"><figure class="highlight yaml"><input type="checkbox" id="code-wrap-1ep1s7g-5" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1ep1s7g-5" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Add</span> <span class="string">ESLint</span> <span class="string">Problem</span> <span class="string">Matcher</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">echo</span> <span class="string">&quot;::add-matcher::.github/matchers/eslint-matcher.json&quot;</span></span><br></pre></td></tr></table></figure></div>

<p>このようにすると、以降のステップで ESLint の出力に応じたアノテーションが自動的に表示されます。<br>不要になった場合は、次のように削除します。</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Remove</span> <span class="string">ESLint</span> <span class="string">Problem</span> <span class="string">Matcher</span></span><br><span class="line">  <span class="attr">run:</span> <span class="string">echo</span> <span class="string">&quot;::remove-matcher::eslint-stylish&quot;</span></span><br></pre></td></tr></table></figure>

<p>ここで指定する <code>eslint-stylish</code> は、Problem Matcher の設定ファイル内で定義した <code>owner</code> に対応しています。</p>
<p>ただし、実際のところ ESLint を GitHub Actions 上で実行する場合、通常この登録処理を明示的に記述することはありません。<br>なぜなら、ESLint を実行する前には一般的に actions&#x2F;setup-node を使って Node.js のセットアップを行いますが、このステップの中で ESLint 用の Problem Matcher が自動的に登録されるためです。</p>
<p>ほかにも <code>setup-python</code> や <code>setup-go</code> そして <code>setup-dotnet</code> などでも言語に応じて標準的な Problem Matcher が登録されるようになっています。</p>
<h3 id="GitHub-Annotation-にネイティブに対応しているツールの利用">GitHub Annotation にネイティブに対応しているツールの利用</h3><p>Python 用の Linter である Ruff など GitHub Annotation に対応した出力をサポートしているツールもあります。<br>Ruff では <code>--output-format</code> に <code>github</code> を指定することで、次のような出力を得ることができます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-1ep1s7g-6" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-1ep1s7g-6" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">ruff check test.py --output-format github</span><br><span class="line">::error title=Ruff (F401),file=test.py,line=1,col=8,endLine=1,endColumn=10::test.py:1:8: F401 `os` imported but unused</span><br></pre></td></tr></table></figure></div>

<h2 id="GitHub-Annotation-の制約">GitHub Annotation の制約</h2><p>GitHub Actions はワークフロー実行時のアノテーション数を次のように制限しています。<br>https://github.com/orgs/community/discussions/26680</p>
<ul>
<li>1 ステップ（ワークフロー定義の <code>run</code> や <code>uses</code> の単位）あたりエラー 10 件、警告 10 件、通知 10 件</li>
<li>1 ジョブ（ワークフロー定義のステップを 1 つ以上含む <code>jobs</code> の単位）あたり 50 件</li>
<li>1 実行（ワークフローの実行）あたり 50 件</li>
</ul>
<p>大量にエラーや警告が出力される場合は、一度にすべてを表示できないため、注意が必要です。<br>ただし通常 Linter などはローカルでも動作させてエラーを確認できるため、このあたりは割り切れるケースが多いのではないでしょうか。</p>
<h2 id="reviewdog-との比較">reviewdog との比較</h2><p>ここまで GitHub 標準の Annotation 機能を紹介してきましたが、先に紹介した reviewdog と比較したときのメリット・デメリットを整理しておきます。</p>
<p>Annotation 機能を利用するメリットは次のとおりです。</p>
<ul>
<li><strong>サードバーティのアクションに依存しない</strong><br>GitHub が公式に提供しているしくみであり、追加のツールや依存パッケージを導入する必要がありません。</li>
<li><strong>パーミッションの付与が不要でセキュア</strong><br>reviewdog を使用する場合は GitHub API を操作するため、GITHUB_TOKEN を使います。一方、Annotation 機能はログ出力ベースで動作するため、追加の認証情報を必要とせずセキュリティ的に安心です。</li>
<li><strong>GitHub API の Rate Limit を気にしなくてよい</strong><br>reviewdog は PR コメントを投稿する際に GitHub API を呼び出すため、大量のコメントや高頻度の実行で Rate Limit に引っかかる恐れがあります。Annotation はログ出力ベースで動作するため、Rate Limit の考慮は不要です。</li>
<li><strong>標準機能であるため将来的な安定性が高い</strong><br>GitHub が提供するしくのため、GitHub Actions のアップデートや仕様変更にも継続的に対応される可能性が高く、メンテナンスコストや不具合のリスクが比較的小さいと考えられます。</li>
</ul>
<p>Annotation 機能にはない、reviewdog ならではのメリットは次のとおりです。</p>
<ul>
<li><strong>コメントの対象を差分ファイルに限定できる</strong><br>reviewdog では、差分のある行に限定してコメントを残すことができるため、古いコードや無関係な部分に過剰にコメントが付与されることを防げます。これにより、修正しなければならない対象が明確になります。</li>
<li><strong>多くのツールに標準で対応している</strong><br>reviewdog は Checkstyle 形式 や SARIF 形式 など さまざまな形式に標準で対応 しており、自分でフォーマットを定義しなくてもさまざまなツールに対応するできます。</li>
<li><strong>PR コメントとして明示的に残せる</strong><br>reviewdog は PR 上に直接コメントを残すことができます。これにより通知などでレビューイが気付きやすく、PR 上でディスカッションするトリガにもなります。<br>そのほか、GitHub の Annotation や GitHub PR Checks などの出力にも対応しており、さまざまな出力先を選べるのが特徴です。</li>
<li><strong>GitHub 以外のプラットフォームに対応できる</strong><br>GitLab や Bitbucket など、複数の SCM プラットフォームで利用可能な点も reviewdog の強みです。マルチリポジトリ・マルチサービス環境を前提とした CI にも対応できます。</li>
</ul>
<h2 id="どちらを使うべきか">どちらを使うべきか</h2><p>機能的には当然 GitHub Annotation より reviewdog の方が高機能です。</p>
<p>当たり前のことを言ってしまえば、reviewdog の機能が必要であれば reviewdog を使うべきですし、GitHub Annotation で事足りるなら標準機能のみで実現する形が望ましいでしょう。</p>
<p>根本的にはフラットに比較すべきものではないような気がします。</p>
<p>おそらく多くの場合ポイントになるのは、差分出力ではないでしょうか。</p>
<p>ここについては Linter や Formatter が導入されていない既存のコードに対して後から CI を導入する場合は、大量のエラーや警告が発生し、それがノイズになることが多くあります。逆に、新規開発において始めから PR 駆動できっちりと CI を回すケースにおいては、全件出力でも問題にならないでしょう。</p>
<h2 id="おわりに">おわりに</h2><p>案外 GitHub Annotation でもやれるんじゃないか、という記事でした。</p>
]]></content>
    <summary type="html">コードレビューを自動で可視化するためのツールといえばreviewdogが有名です。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="CI/CD" scheme="https://future-architect.github.io/tags/CI-CD/"/>
    <category term="GitHub" scheme="https://future-architect.github.io/tags/GitHub/"/>
    <category term="GitHubActions" scheme="https://future-architect.github.io/tags/GitHubActions/"/>
    <category term="コードレビュー" scheme="https://future-architect.github.io/tags/%E3%82%B3%E3%83%BC%E3%83%89%E3%83%AC%E3%83%93%E3%83%A5%E3%83%BC/"/>
  </entry>
  <entry>
    <title>Dokployで自宅PaaSを構築する</title>
    <link href="https://future-architect.github.io/articles/20250603b/"/>
    <id>https://future-architect.github.io/articles/20250603b/</id>
    <published>2025-06-02T15:00:01.000Z</published>
    <updated>2025-06-02T15:00:01.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250603b/logo.png" alt="" width="818" height="368">

<p>CI&#x2F;CD連載 1本目の記事です。</p>
<p>Dokployというのを知ったので動かしてみました。よくあるクラウドサービスのPaaSマネージドサービスようなインフラをオンプレ環境やVPSなどに簡単に構築できるものです。自宅サーバーは欲しいなと思いつつ（昔たててqmailで自宅メールサーバー運用したりしたことはあったが）、K8sはあんまり興味ないな、と思っていて何かしらいいのがないかなと思っていたところ、結構自分の理想に近かったので試してみました。</p>
<ul>
<li>ソースコードをアップロードしてコンテナでサービス起動</li>
<li>各種OSSのサービスを簡単起動</li>
<li>これらすべてウェブベースの管理画面があり、AWSとかGoogle Cloudの管理画面のようにシステムの設定や閲覧ができる</li>
<li>ログ、メトリックスのビューア完備(Dozzleとかを自前で立てる必要はない)</li>
</ul>
<p>今回試していない機能ですが、以下の機能もあります。</p>
<ul>
<li>通知</li>
<li>Docker Swarmベースの複数のサーバーノード管理</li>
<li>管理画面のユーザー管理</li>
<li>バックアップ機能付きのデータベースサポート</li>
<li>GitHubやGitLabと連携した自動デプロイ</li>
<li>スケジュール機能</li>
<li>SSH鍵管理とか</li>
</ul>
<p>このサービスはDockerとTraefikのリバースプロキシサーバが主要要素で、コンテナでアプリケーションを起動できます。アプリケーションをビルドしてDocker上で起動したり、Docker上で動いているデータベースのデータをバックアップしたりする便利機能群を提供しています。</p>
<p>なお、Dokployそのものはローカルマシンに入れずに、SaaSで動いているDokployに外部からマシン管理機構だけを任せる運用というサービスも提供されています。月当たり$3.5&#x2F;台+$1ぐらい。また、さまざまなホスティング環境もサポートしています。</p>
<h2 id="インストールしてみる">インストールしてみる</h2><p>DokployはLinuxのみをサポートしています。macOSでは直接は動かないのでLinuxの仮想PCを入れてその上で動かします。</p>
<p>macOSは仮想化機能がOS内蔵なのにWSLみたいな直接使えるインターフェースがないので、UTMを入れてその上でDebianを動かします。WWDCのたびにVirtualization Frameworkの新情報を待ち望んでいるのですが・・・今年は新情報来たらいいな。</p>
<p>UTMはApp Storeでも入れられますし、公式サイトからダウンロードしてもいいですし、Homebrew使って <code>brew install --cask utm</code> でもお好きな方法を選びます。</p>
<h3 id="Linuxインストール">Linuxインストール</h3><p>DokployがサポートしているOSはDebian, Ubuntu, Centosです。今回はUbuntuを選びました。手元のmacはM3なので下記のページの「小さなCDまたはUSBメモリ」のARM64のイメージをダウンロードします。Debian Downloadで出てくるサイトだとAMD64版しかないので要注意。</p>
<p>UTMのウインドウで＋ボタンを押して「仮想化」を選びます。OS選択ではLinuxを選び、Apple仮想化と、ISOイメージで先ほどダウンロードした.isoファイルを選択して、あとはすべてデフォルトでインストールしていきました。特にパッケージなども追加はしていません。SSHサーバのみが入った感じですかね。</p>
<p>起動してIDとパスワードを入れて起動できたら、<code>sudo</code>と<code>curl</code>コマンドだけインストールしておきます。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">su -</span></span><br><span class="line">(rootのパスワードを入れる)</span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">apt-get install <span class="built_in">sudo</span> curl</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">adduser debian <span class="built_in">sudo</span></span></span><br></pre></td></tr></table></figure>

<p>内部で起動したサービスにつながるかテスト。ネットワークはデフォルトのホストネットワークを使っています。まずはゲストの中でIPアドレスを確認します。192.168.64.7というIPv4アドレスを持っているようです。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-3b5s58-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-3b5s58-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">ip a</span></span><br><span class="line">1: lo: ...</span><br><span class="line">    :</span><br><span class="line">2: enp0s1: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc fq_code1...</span><br><span class="line">    link/ether 92:60:fc:72:aa:9c brd ff:ff:ff:ff:ff:ff</span><br><span class="line">    inet 192.168.64.7/24 brd 192.168.64.255 scope global dynamic enp0s1</span><br><span class="line">    :</span><br></pre></td></tr></table></figure></div>

<p>引き続き、ゲストの中からPythonのウェブサーバーを起動してみます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-3b5s58-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-3b5s58-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">python3 -m http.server</span></span><br><span class="line">Serving HTTP on :: port 8000 (http://[::]:8000/) ...</span><br></pre></td></tr></table></figure></div>

<p>ホストのマシンからこのゲスト上で動いているサービスにアクセスしてみてHTMLが帰ってきたら無事にサーバーがでっきていることの確認ができます。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">% </span><span class="language-bash">curl http://192.168.64.7:8000</span></span><br><span class="line">&lt;!DOCTYPE HTML&gt;</span><br><span class="line">&lt;html lang=&quot;en&quot;&gt;</span><br><span class="line">:</span><br></pre></td></tr></table></figure>

<p>これでLinuxのインストールは完了。</p>
<h3 id="Dokployのインストール">Dokployのインストール</h3><p>Dokployはシェルでインストールします。さまざまなビルドツールとかをダウンロードするので、そこそこの量のダウンロードが走ります。間違ってもテザリングでやらないように・・・勇気と無謀は違います。この手のシェルでインストールはちょっと怖い気持ちはあるのですが、まあ何もないLinux仮想環境なのでえいやで実行します。最後のシェル実行だけroot権限が必要なので<code>sudo</code>をつけます。</p>
<div class="code-block"><figure class="highlight console"><input type="checkbox" id="code-wrap-3b5s58-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-3b5s58-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">curl -sSL https://dokploy.com/install.sh | <span class="built_in">sudo</span> sh</span></span><br></pre></td></tr></table></figure></div>

<p>最後に管理画面のURLが表示されますが、IP部分は先ほど<code>ip a</code>コマンドで出てきたものを使います。</p>
<img src="/images/2025/20250603b/スクリーンショット_2025-05-12_21.24.11.png" alt="スクリーンショット_2025-05-12_21.24.11.png" width="1200" height="767" loading="lazy">

<h2 id="アプリケーションのデプロイ">アプリケーションのデプロイ</h2><p>アプリケーションをデプロイしていきます。</p>
<h3 id="設定ファイルなしの自動ビルド-Railpack">設定ファイルなしの自動ビルド(Railpack)</h3><p>まずはGoのアプリケーションを作ってみます。ふつうのウェブアプリケーションです。</p>
<figure class="highlight gomod"><figcaption><span>go.mod</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">module</span> goapp</span><br><span class="line"></span><br><span class="line"><span class="keyword">go</span> <span class="number">1.24</span></span><br></pre></td></tr></table></figure>

<div class="code-block"><figure class="highlight go"><input type="checkbox" id="code-wrap-3b5s58-4" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-3b5s58-4" title="コードの折り返しを切り替える"></label><figcaption><span>main.go</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line">	<span class="string">&quot;context&quot;</span></span><br><span class="line">	<span class="string">&quot;fmt&quot;</span></span><br><span class="line">	<span class="string">&quot;log&quot;</span></span><br><span class="line">	<span class="string">&quot;net/http&quot;</span></span><br><span class="line">	<span class="string">&quot;os/signal&quot;</span></span><br><span class="line">	<span class="string">&quot;syscall&quot;</span></span><br><span class="line">	<span class="string">&quot;time&quot;</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">hello</span><span class="params">(w http.ResponseWriter, r *http.Request)</span></span> &#123;</span><br><span class="line">	fmt.Fprintf(w, <span class="string">&quot;hello world&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">	http.HandleFunc(<span class="string">&quot;/hello&quot;</span>, hello) <span class="comment">// /hello以下でリクエストを受ける</span></span><br><span class="line">	srv := &amp;http.Server&#123;</span><br><span class="line">		Addr: <span class="string">&quot;:8080&quot;</span>,</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">go</span> <span class="function"><span class="keyword">func</span><span class="params">()</span></span> &#123;</span><br><span class="line">		log.Printf(<span class="string">&quot;start listening at %s\n&quot;</span>, srv.Addr)</span><br><span class="line">		<span class="keyword">if</span> err := srv.ListenAndServe(); err != http.ErrServerClosed &#123;</span><br><span class="line">			log.Fatalf(<span class="string">&quot;ListenAndServe(): %v&quot;</span>, err)</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;()</span><br><span class="line">	ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)</span><br><span class="line">	<span class="keyword">defer</span> cancel()</span><br><span class="line">	&lt;-ctx.Done()</span><br><span class="line">	ctx2, cancel := context.WithTimeout(context.Background(), <span class="number">5</span>*time.Second)</span><br><span class="line">	<span class="keyword">defer</span> cancel()</span><br><span class="line">	<span class="keyword">if</span> err := srv.Shutdown(ctx2); err != <span class="literal">nil</span> &#123;</span><br><span class="line">		log.Fatalf(<span class="string">&quot;Fail to shutdown: %v&quot;</span>, err)</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<p>すべてのアプリケーション、データベースなどは「サービス」という名前のインスタンスで、各サービスは必ず「プロジェクト」に属すという構成になっています。プロジェクト共有で環境変数を設定したり、サービスごとに環境変数の設定もできます。まずはプロジェクトを作ります。名前を入れるだけですね。go appにしました。</p>
<img src="/images/2025/20250603b/スクリーンショット_2025-05-16_10.36.28.png" alt="スクリーンショット_2025-05-16_10.36.28.png" width="1082" height="620" loading="lazy">

<p>プロジェクトの中にはサービスをおきます。サービスは（自作）アプリケーション、データベース、Compose、テンプレート（後述）、AIで相談して作る、というのがあります。今回はアプリケーションを作ります。こちらもほぼ名前だけです。サーバーはDokployを複数サーバー管理に使っている場合に選びます。今回は1台だけなので選ぶ必要はありません。</p>
<img src="/images/2025/20250603b/スクリーンショット_2025-05-16_10.37.27.png" alt="スクリーンショット_2025-05-16_10.37.27.png" width="1173" height="731" loading="lazy">

<p>アプリケーションのGeneralタブでアプリケーションをデプロイします。ビルドタイプはDockerやBuildpackなどいろいろありますがデフォルトのRailpackにしました。Gitとかと接続もできますが、今回はローカルで作ったzipのソースをドラッグしてデプロイします。Railpackの場合、Goアプリケーションはトップにmain.goがあればこれをアプリとみなしてビルドするようです。Deployボタンをおせば初回は50秒、2回目は9秒ほどでビルドが終わりました。</p>
<img src="/images/2025/20250603b/スクリーンショット_2025-05-16_10.31.46.png" alt="スクリーンショット_2025-05-16_10.31.46.png" width="912" height="724" loading="lazy">

<p>外からアクセスできるようにするためにはドメインを作成します。DMZで動いているマシンならtraefik.meのサブドメインの無料のドメイン発行機能もあり、ボタン一発（サイコロアイコン）で外からもアクセスさせることもできます。</p>
<img src="/images/2025/20250603b/スクリーンショット_2025-05-14_11.57.26.png" alt="スクリーンショット_2025-05-14_11.57.26.png" width="1200" height="766" loading="lazy">

<p>Deokployが動いているサーバーの80番ポートでサービスは公開されるので、そのアドレスで起動すればOKですね。</p>
<img src="/images/2025/20250603b/スクリーンショット_2025-05-16_19.52.19.png" alt="スクリーンショット_2025-05-16_19.52.19.png" width="848" height="336" loading="lazy">

<p>複数アプリケーション起動したくなると思うので、その場合はドメインのパスを <code>/</code> から <code>/hello</code>とすればそれ以外のパスは他のアプリから使えます。ただし、アプリケーションが受け取るパスはこのプレフィックス部分も含むため、プレフィックス部分は環境変数とかで変えられる機能をアプリ側につけると良さそうです。パスのプレフィックスをトリムする機能はなさげですので。</p>
<p>なおブラウザの管理画面からデプロイ（ビルド）時のログが見られたり、アプリケーションのログも見られるし、メトリックスも見られるし、シェルがあるならコンテナの中に入ることもできるし、コマンドを起動もできるし、ローカル開発と遜色なく利用できます。</p>
<h3 id="複数のアプリケーションを起動の準備">複数のアプリケーションを起動の準備</h3><p>PaaSなので複数のアプリケーションを稼働させられます。ただ、各アプリケーションはみんな自分のパスの空間を持っています。例えば、<code>/health</code>でヘルスチェック用のエンドポイントを提供というのはみんなやっているので、80番ポートにフラットにマウントされてしまうと複数のアプリケーションが稼働できません。そこで、バーチャルホストの設定をして複数起動できるようにします。</p>
<p>先ほどのGoアプリと、このPythonアプリ、どちらも <code>/hello</code>というエンドポイントを持っています。192.168のアドレスにマッピングすると使い分けられません。DokployにはTraefikというプロキシが内蔵されています。このプロキシがリクエストを受けるときに、HTTPリクエストに含まれるhostヘッダーフィールドを見て後続のサービスを判断してリクエストを投げ分けます。</p>
<img src="/images/2025/20250603b/virtualhost.png" alt="virtualhost.png" width="561" height="181" loading="lazy">

<p>そのためにはサービスごとにドメインを発行する必要があります。hostsファイルでも良かったのですがワイルドカード対応のためにdnsmasqをローカルに入れて使います。macOSの手順を書いています。他のOSの方は生成AIにでも投げて自分の環境に合わせて変更してください。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">dnsmasqをインストール</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">brew install dnsmasq</span></span><br></pre></td></tr></table></figure>

<p>設定ファイルは以下の通りです。Linuxだと&#x2F;etc&#x2F;dnsmasq.confですかね。ローカル用に<code>private</code>というトップレベルドメインを作ってしまいましょう。次の行を足します。どんなサブドメイン向けのリクエストもこのIPアドレスが返ります。</p>
<figure class="highlight text"><figcaption><span>/opt/homebrew/etc/dnsmasq.conf</span></figcaption><table><tr><td class="code"><pre><span class="line">address=/.private/192.168.64.7</span><br></pre></td></tr></table></figure>

<p>privateドメインだけはこのDNSサーバーを見にいくようにします。<code>/etc/resolver/</code>フォルダを作り<code>private</code>というファイルを作成して以下の内容を書きます。</p>
<figure class="highlight text"><figcaption><span>/etc/resolver/private</span></figcaption><table><tr><td class="code"><pre><span class="line">nameserver 127.0.0.1</span><br></pre></td></tr></table></figure>

<p>最後にDNSサーバーとOSキャッシュをクリアします。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">sudo</span> brew services restart dnsmasq</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">sudo</span> killall -HUP mDNSResponder</span></span><br></pre></td></tr></table></figure>

<p>試しに<code>ping test.private</code>とかいろいろ叩いてみて、192.168.64.7を向いているか確認します。確認できたらアプリをインストールします。</p>
<h3 id="Docker形式のアプリのビルド">Docker形式のアプリのビルド</h3><p>以下の記事で書いたPythonアプリケーションをデプロイしてみます。</p>
<ul>
<li>FastAPI on Dockerがかなりシンプルになった(2025年版)</li>
</ul>
<p>プロジェクトは先ほどの使い回しでも新規で作っても大丈夫です。その後サービスを作ります。今度のアプリケーションはDockerファイル入りなので、ビルドタイプにDockerfileを選び、ファイル名のDockerfileも入れます。あとはまたzipにしてドロップしてデプロイボタンを押せばビルド完了です。</p>
<img src="/images/2025/20250603b/スクリーンショット_2025-05-18_18.57.02.png" alt="スクリーンショット_2025-05-18_18.57.02.png" width="946" height="591" loading="lazy">

<p>ドメイン設定では<code>pyapp.private</code>というドメインを追加します。</p>
<img src="/images/2025/20250603b/スクリーンショット_2025-05-18_18.59.35.png" alt="スクリーンショット_2025-05-18_18.59.35.png" width="991" height="514" loading="lazy">

<p>先ほどのGoのアプリケーションの方のドメイン設定には<code>goapp.private</code>というドメインを追加しておきます。ドメインは複数設定できるので前のものは消さなくても大丈夫です。</p>
<img src="/images/2025/20250603b/スクリーンショット_2025-05-18_19.15.38.png" alt="スクリーンショット_2025-05-18_19.15.38.png" width="1030" height="744" loading="lazy">

<p>新しいドメインでアクセスすると複数のサービスが同時に利用できました。</p>
<img src="/images/2025/20250603b/スクリーンショット_2025-05-18_19.17.42.png" alt="スクリーンショット_2025-05-18_19.17.42.png" width="776" height="555" loading="lazy">

<h3 id="テンプレートカタログからアプリケーションのデプロイ">テンプレートカタログからアプリケーションのデプロイ</h3><p>自作のアプリケーション以外にも人気のアプリケーションを簡単にデプロイできるテンプレートがあります。</p>
<img src="/images/2025/20250603b/スクリーンショット_2025-05-16_20.24.16.png" alt="スクリーンショット_2025-05-16_20.24.16.png" width="1200" height="663" loading="lazy">

<p>試しにminioを選択しました。サーバー選択だけですぐにアプリが登録できました。デプロイ（コンテナのダウンロード）はされていない状態です。ドメインはtraefikの自動生成になっていているので<code>minio.private</code>に書き換えて、デプロイボタンをおしたところ、あっという間に起動しました。パスワードなどは環境変数のところに書いてありました。楽勝ですね。</p>
<img src="/images/2025/20250603b/スクリーンショット_2025-05-18_19.29.01.png" alt="スクリーンショット_2025-05-18_19.29.01.png" width="913" height="608" loading="lazy">

<h2 id="Dokployでできないこと">Dokployでできないこと</h2><p>ドメインの管理はdnsmasqでやりました。AWSとかだとRoute 53みたいなサービスでドメインも一緒にコントロールできますが、ネットワーク周りはオンプレだったり外からアクセスできるサービスだったり、有料のドメインだったりが絡むので扱いきれないのはまあ仕方がないですね。</p>
<p>Dokployそのものの制限というよりも下で動いているDocker&#x2F;Traefikの制約でできないのがオートスケールです。スケールそのものはDocker Composeの機能にあるのですが、明示的にコマンドを打つ必要があります。AWSやGoogle CloudのロードバランサーはサービスのメモリやCPUの計測値や接続数をみてスケーリングを行えます。モニタリングは大変でも、接続数が70超えたらインスタンス増やす、みたいなのが簡単にできる（ついでにしばらくアクセスがなかったらインスタンスがなくなる）とかがあれば、少なめリソースの自宅サーバーでたくさんサービスを起動しておいて・・・・みたいなのができるんですけどね。</p>
<h2 id="まとめ">まとめ</h2><p>「アプリケーション」「データベース」の2つに特化したローカルPaaSのDokployで遊んでみた記録でした。今回はデータベース周りの機能はまだ触らず、またcomposeで複数のアプリの組み合わせを起動みたいなことはしていませんが、基本的なアプリケーションのデプロイを色々試したり、</p>
<p>クラウドのコンソールでちょいちょいアプリケーションを動かして遊んでいた人は、それがそのままオンプレやローカルで動くと考えると興味を持つ人は多いんじゃないでしょうか。「技術は螺旋で行ったり来たりする」と言われますが、コンテナや12 factors appといったクラウドで発展したものがオンプレでも便利に使えるようになるという流れは自然な発展な気がします。</p>
<p>もちろん、フルサービスなGoogle CloudやAWSと比べるとネットワーク周りの設定がなかったり機能が少ない点はありますが、モニタリングやログビューアとかも備えており、ローカルで動かしてテストするのと遜色なく利用できます。今回紹介したもの以外にも色々同様のものもあり、今後さらに発展していていってくれるんじゃないかと思っています。</p>
<ul>
<li>Coolify</li>
<li>CapOver</li>
<li>Kubero</li>
<li>Dokku</li>
</ul>
<p>DNSサーバーを建てるときに、最初.localでやろうとしてハマって「macOSのmDNSが別の用途で使うからダメ」というアドバイスを@takabowと@takuan_oshoにもらってなんとか書き切りました。ありがとうございます。</p>
<ul>
<li>参考<ul>
<li>Buildpacksのビルダーをスクラッチから作ってみる</li>
</ul>
</li>
</ul>
]]></content>
    <summary type="html">Dokployというのを知ったので動かしてみました。よくあるクラウドサービスのPaaSマネージドサービスようなインフラをオンプレ環境やVPSなどに簡単に構築できるものです。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="Docker" scheme="https://future-architect.github.io/tags/Docker/"/>
    <category term="コンテナ" scheme="https://future-architect.github.io/tags/%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A/"/>
  </entry>
  <entry>
    <title>CI/CD連載を始めます</title>
    <link href="https://future-architect.github.io/articles/20250603a/"/>
    <id>https://future-architect.github.io/articles/20250603a/</id>
    <published>2025-06-02T15:00:00.000Z</published>
    <updated>2025-06-02T15:00:00.000Z</updated>
    <author><name>admin</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250603a/top.jpg" alt="" width="600" height="600">

<p>※図は Gemini 2.5 Proで生成しました。</p>
<h2 id="はじめに">はじめに</h2><p>入梅のみぎり、いかがお過ごしでしょうか。</p>
<p>フューチャーは月に1回、何かしらの技術テーマを元にブログリレー（ブログ連載）を行っています。6月はCI&#x2F;CDについて取り上げます。本来は5月予定でしたが、春の入門祭りが総勢26名参加され、スケジュールがスライドしたため1ヶ月ズレての開催です。</p>
<h2 id="CI-CDについて">CI&#x2F;CDについて</h2><p>インテグレーション (Continuous Integration) と継続的デリバリー&#x2F;デプロイメント (Continuous Delivery&#x2F;Deployment) の略で、開発手続きを自動化し、より早く確実にリリースするための開発手法です。</p>
<ul>
<li><strong>CI (継続的インテグレーション)</strong>: コード変更をリモートリポジトリにプッシュ・マージされたことを起点に、ビルドやテストを自動的に実行し、不具合を検知し手戻りを防ぐ</li>
<li><strong>CD (継続的デリバリー&#x2F;デプロイメント)</strong>: CI後、自動的にデプロイメント環境にリリースする方法。手動の作業を減らし、誤操作を防ぐことができる</li>
</ul>
<p>CI&#x2F;CDの略歴を説明すると以下かなと思います。</p>
<ol>
<li>1990年代後半 ～ 2000年代初頭<ul>
<li>XP（エクストリーム・プログラミング）やテスト駆動開発(TDD) で有名なKent Beckさんや、マーチン・ファウラーさんなど、CIを書籍やブログなどで具体的な開発手法として紹介・提唱して広める</li>
</ul>
</li>
<li>2000年代初頭<ul>
<li>Hudson (後のJenkins) などのツールが登場し、オンプレミスでのCIが活発に行われる</li>
</ul>
</li>
<li>2010年代前半<ul>
<li>Travis CIやCircleCIといったクラウドベースのCI&#x2F;CDサービスが登場し、急速に普及（特にスタートアップ系）</li>
</ul>
</li>
<li>2010年代後半<ul>
<li>GitHub Actions（2018年提供開始）やGitLab CI&#x2F;CDが登場。Gitホスティングサービスがネイティブに提供するCI&#x2F;CD機能が主要な選択肢に</li>
</ul>
</li>
<li>CI&#x2F;CDを中心として概念の広がり<ul>
<li>CI&#x2F;CD普及に従い、自動テストとの相性の良さからTDD（テスト駆動開発）を取り入れたり・TerraformなどのIaC（Infrastructure as Code）もCI&#x2F;CDへの組み込み・GitOps（Gitを唯一の情報源として、コミットをトリガーにインフラ・アプリへの自動デプロイを行う手法）・SRE・Four Keysを元にして定量的な開発生産性の計測など様々な広がりがあります。CI&#x2F;CDはその基盤として位置づけられています</li>
</ul>
</li>
</ol>
<h2 id="フューチャーにおけるCI-CD">フューチャーにおけるCI&#x2F;CD</h2><p>私の知る限り、HudsonからCIは私が入社した2009年時点ではすでに当たり前のように行われていました。その後、Jenkinsにツールは変わりつつ、CircleCIやGitHub Actionsなどにツールも加わり、時代に合わせて進化しています。</p>
<p>社内標準ツールとしては、自社チームがホスティングしているGitLabがあるため、GitLab CI&#x2F;CDの利用率も高いのが特徴でしょうか。以下のような記事もあります。</p>
<ul>
<li>GitLabのレビューにPR-Agentを組み込んでみた</li>
<li>GitLab CIを新人研修に導入した話</li>
</ul>
<h2 id="連載スケジュール">連載スケジュール</h2><p>7名、以下のスケジュールです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th align="left">日付</th>
<th align="left">氏名</th>
<th align="left">タイトル</th>
</tr>
</thead>
<tbody><tr>
<td align="left">6&#x2F;3（火）</td>
<td align="left">澁川喜規</td>
<td align="left">Dokployで自宅PaaSを構築する</td>
</tr>
<tr>
<td align="left">6&#x2F;4（水）</td>
<td align="left">武田大輝</td>
<td align="left">GitHub 標準の Annotation を活用してレビューを可視化する</td>
</tr>
<tr>
<td align="left">6&#x2F;5（木）</td>
<td align="left">真野隼記</td>
<td align="left">nektos&#x2F;act をタスクランナーとして使う可能性があるのか</td>
</tr>
<tr>
<td align="left">6&#x2F;6（金）</td>
<td align="left">松本朝香</td>
<td align="left">Github CI&#x2F;CD pipelineに静的コード解析Sonar Qubeを組み込んでみた</td>
</tr>
<tr>
<td align="left">6&#x2F;9（月）</td>
<td align="left">橋本竜我</td>
<td align="left">Xcode Cloudで最初に作りたい基本的なワークフロー</td>
</tr>
<tr>
<td align="left">6&#x2F;10（火）</td>
<td align="left">山本竜玄</td>
<td align="left">Gemma3 + Unsloth + GitLab CI&#x2F;CDで構築する完全オンプレミスAIコードレビュー環境</td>
</tr>
</tbody></table></div>
<p>澁川さんは担当分の記事を執筆するための調査で判明した事項を、別途FastAPI on Dockerがかなりシンプルになった(2025年版)の記事にまとめてもくれました。こちらも面白い記事だと思いますので、Pythonを扱う方はぜひご一読ください。</p>
<h2 id="さいごに">さいごに</h2><p>初めてCI&#x2F;CDをテーマに技術ブログ連載を始めます。</p>
<p>現代のソフトウェア開発の基盤・前提となる内容ですので、比較的広いテーマかなと思います。私個人としても他のメンバーがどのようなCI&#x2F;CDナレッジを公開するか楽しみです。</p>
<p>引き続きよろしくお願いします。</p>
]]></content>
    <summary type="html">フューチャーは月に1回、何かしらの技術テーマを元にブログリレー（ブログ連載）を行っています。6月はCI/CDについて取り上げます</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="CI/CD" scheme="https://future-architect.github.io/tags/CI-CD/"/>
    <category term="インデックス" scheme="https://future-architect.github.io/tags/%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9/"/>
  </entry>
  <entry>
    <title>FastAPI on Dockerがかなりシンプルになった(2025年版)</title>
    <link href="https://future-architect.github.io/articles/20250602a/"/>
    <id>https://future-architect.github.io/articles/20250602a/</id>
    <published>2025-06-01T15:00:00.000Z</published>
    <updated>2025-06-01T15:00:00.000Z</updated>
    <author><name>澁川喜規</name></author>
    <content type="html"><![CDATA[<p>5 年ほど前に Python のコンテナ化について 2 つの記事を書きましたが FastAPI 側も Docker 側もアップデートがあり、当時よりもかなりシンプルになってきたのを感じたので少し調べてまとめてみました。</p>
<p>書き方の部分は別として Python におけるコンテナイメージ選択の考え方とかは 2020 年に書いたときとは変わっていませんので、適宜そちらを参照してください。</p>
<ul>
<li>仕事で Python コンテナをデプロイする人向けの Dockerfile (1): オールマイティ編</li>
<li>仕事で Python コンテナをデプロイする人向けの Dockerfile (2): distroless 編</li>
</ul>
<p>(1)の方からのアップデートとしては Debian のバージョンですね。stretch(9), buster(10)はすでに EOL です。その次に出た bullseye(11)は 2026 年 8 月で EOL です。今からなら bookworm(12)がおすすめです。</p>
<p>(2)の方のアップデートもほぼ同じで、Debian の新バージョンを使ったイメージが追加されています。それにともって Python のバージョンも新しくなっています。</p>
<ul>
<li>gcr.io&#x2F;distroless&#x2F;python3-debian11: Python 3.9.2</li>
<li>gcr.io&#x2F;distroless&#x2F;python3-debian12: Python 3.11.2</li>
</ul>
<p>Dockerfile の書き方自体のアップデートとしては以下に書いた内容がベースとなります。本エントリーでもちょくちょく Docker の説明はありますが、詳細はこちらも併読してもらえると良いかと思います。</p>
<ul>
<li>2024 年版の Dockerfile の考え方＆書き方</li>
</ul>
<p>本エントリーでは Dockerfile を使った debian-slim, Chainguard, Distroless ベースのイメージ作成だけを取り上げます。コンテナ用のイメージについては次のページにまとめがあります。</p>
<ul>
<li>builders.flesh: コンテナサイズ最小化のためのベースイメージ再考</li>
</ul>
<h2 id="FastAPI-のアプリケーションを作る">FastAPI のアプリケーションを作る</h2><p>まずはコンテナイメージに焼き込むアプリケーションを作ります。以前の FastAPI 記事では Poetry を使った環境構築を紹介しましたが、ここ数年で Rye、uv と出てきて、今では uv が覇権を取りそうですので、uv で作ってみます。ただ、ツールは変わっても基本的なメンタルモデルはあんまり変わらないですね。早くなんでもいいから標準に入ってほしい。</p>
<p>環境構築からパッケージのインストールまで一気に終わります。</p>
<figure class="highlight console"><table><tr><td class="code"><pre><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">mkdir</span> pyapp</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash"><span class="built_in">cd</span> pyapp</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">uv init</span></span><br><span class="line"><span class="meta prompt_">$ </span><span class="language-bash">uv add fastapi --extra standard</span></span><br></pre></td></tr></table></figure>

<p>とりあえずファイルを作ります。</p>
<figure class="highlight py"><figcaption><span>main.py</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> fastapi <span class="keyword">import</span> FastAPI</span><br><span class="line"></span><br><span class="line">app = FastAPI()</span><br><span class="line"></span><br><span class="line"><span class="meta">@app.get(<span class="params"><span class="string">&quot;/hello&quot;</span></span>)</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">def</span> <span class="title function_">read_root</span>():</span><br><span class="line">    <span class="keyword">return</span> &#123;<span class="string">&quot;Hello&quot;</span>: <span class="string">&quot;World&quot;</span>&#125;</span><br></pre></td></tr></table></figure>

<p>次のコマンドを実行するとポート番号 8000 で開発モードで起動します。開発モードだとファイル変更を検知して自動再起動します。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">uv run fastapi dev</span><br></pre></td></tr></table></figure>

<p>起動するスクリプトとかも自動探索してくれますし、gunicorn で uvicorn ワーカーを使って起動（5 年前にブログに書いたやつ）とか、uvicorn コマンドで起動（FastAPI の本家の日本語訳はまだこれだった）とかではなく、<code>fastapi</code>コマンド一発で裏で uvicorn を使って非同期 IO を活用したモードで立ち上がります。Next.js とかそういうのと近い感触。</p>
<p>本番モードは dev の代わりに run を使います。自動探索ではなく明示的に初期スクリプトを指定したり、ポート番号を与えるのも良いでしょう。こちらの方が明示的になるし検索でひっかかるようになるので長期運用されるものに対してはこうする方が個人的には好きです。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">uv run fastapi run main.py --port 8000</span><br></pre></td></tr></table></figure>

<p>ブラウザでアクセスしてみて大丈夫だったら次に進みます。</p>
<img fetchpriority="high" src="/images/2025/20250602a/スクリーンショット_2025-05-17_18.41.44.png" alt="スクリーンショット_2025-05-17_18.41.44.png" width="686" height="336">

<h2 id="Docker-化">Docker 化</h2><p>効率の良い Docker イメージ化にはマルチステージビルドが必要で、キャッシュのマウントやら何やら、というのは一度以上見かけたことがある方は多いでしょう。しかし、Docker の機能追加のおかげで、言語によってはマルチステージビルドは不要になりました。</p>
<h3 id="基本な-Python-ベースのイメージの作成">基本な Python ベースのイメージの作成</h3><p><code>docker init</code>コマンドが追加され、よくコンテナと一緒に使われる言語であれば、Dockerfile が自動生成できます。Python もその対象の言語の 1 つですので、特別な要件がなければ書き方に頭を悩ませる必要はありません。以下の<code>Dockerfile</code>はこのコマンでほぼ一発で出力したものです。debian-slim ベースなのでサイズはそこそこ小さく、速度や性能は問題ないです。ウィザードの最後のコマンドとポートだけちょっと直したぐらい。</p>
<div class="code-block"><figure class="highlight dockerfile"><input type="checkbox" id="code-wrap-g6mkq-1" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-g6mkq-1" title="コードの折り返しを切り替える"></label><figcaption><span>Dockerfile</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment"># syntax=docker/dockerfile:1</span></span><br><span class="line"><span class="keyword">ARG</span> PYTHON_VERSION=<span class="number">3.13</span>.<span class="number">3</span></span><br><span class="line"><span class="keyword">FROM</span> python:$&#123;PYTHON_VERSION&#125;-slim AS base</span><br><span class="line"><span class="keyword">ENV</span> PYTHONDONTWRITEBYTECODE=<span class="number">1</span></span><br><span class="line"><span class="keyword">ENV</span> PYTHONUNBUFFERED=<span class="number">1</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"><span class="keyword">ARG</span> UID=<span class="number">10001</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> adduser \</span></span><br><span class="line"><span class="language-bash">    --disabled-password \</span></span><br><span class="line"><span class="language-bash">    --gecos <span class="string">&quot;&quot;</span> \</span></span><br><span class="line"><span class="language-bash">    --home <span class="string">&quot;/nonexistent&quot;</span> \</span></span><br><span class="line"><span class="language-bash">    --shell <span class="string">&quot;/sbin/nologin&quot;</span> \</span></span><br><span class="line"><span class="language-bash">    --no-create-home \</span></span><br><span class="line"><span class="language-bash">    --uid <span class="string">&quot;<span class="variable">$&#123;UID&#125;</span>&quot;</span> \</span></span><br><span class="line"><span class="language-bash">    appuser</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> --mount=<span class="built_in">type</span>=cache,target=/root/.cache/pip \</span></span><br><span class="line"><span class="language-bash">    --mount=<span class="built_in">type</span>=<span class="built_in">bind</span>,<span class="built_in">source</span>=requirements.txt,target=requirements.txt \</span></span><br><span class="line"><span class="language-bash">    python -m pip install -r requirements.txt</span></span><br><span class="line"><span class="keyword">USER</span> appuser</span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> main.py main.py</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">80</span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;fastapi&quot;</span>, <span class="string">&quot;run&quot;</span>, <span class="string">&quot;main.py&quot;</span>, <span class="string">&quot;--port&quot;</span>, <span class="string">&quot;80&quot;</span>]</span></span><br></pre></td></tr></table></figure></div>

<p>これの実行前には requirements.txt の生成が必要です。uv は良いのですがライブラリ更新のたびに自動で出力してくれるオプションとかあったらなぁ、と画竜点睛感はありますが。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">uv pip compile pyproject.toml &gt; requirements.txt</span><br></pre></td></tr></table></figure>

<p>この Dockerfile はマルチステージビルドではありません。新しい bind&#x2F;cache マウントが入る前は次のような手順でやっていました。 requirements.txt やロックファイル、インストール用のツールはデプロイ用イメージにはいらないのでマルチステージビルドビルドで分離していたわけです。</p>
<ol>
<li>requirements.txt やロックファイルを COPY する</li>
<li>インストール用のツールを入れる</li>
<li>インストールする(キャッシュが残る)</li>
<li>別のデプロイ用イメージに必要なファイルをコピーする</li>
</ol>
<p>しかし、この Dockerfile ではこれらが不要となっています。</p>
<ul>
<li><code>python -m pip install</code>: uv を使えば開発用ツールは requirements.txt に入らないので標準ライブラリの範疇で十分</li>
<li><code>--mount=type=cache,target=/root/.cache/pip</code>: キャッシュはそもそもイメージに入らない</li>
<li><code>--mount=type=bind,source=requirements.txt,target=requirements.txt</code>: インストールだけに必要なファイルだがこれもビルド時にだけ存在し、イメージに入らない</li>
</ul>
<p>最初から余計なものが入らない工夫をしているため、マルチステージビルド自体が不要です。Go とか Rust とか TypeScript の静的コンパイル言語だったりだとまだまだ必要ですが、そのまま実行するスクリプト言語だとかなりシンプルです。レイヤーキャッシュ芸とか&amp;&amp;でつながりまくった<code>RUN</code>は過去のものに。</p>
<p>RUN のマウント周りの引数、よくわからん、自分で書ける気がしない、と思われるかもしれませんが、心配する必要はありません。pip なり、apt なり、npm なり、ビルドでたくさん中間ファイルを撒き散らすコマンドごとに正解のオプションは調べれば出てきます。定型句です。</p>
<h3 id="Chainguard">Chainguard</h3><p>以前も紹介した Distroless は Google のプロダクトですが、それをメインのビジネスとしているのがChainguardです。無料だと latest のみが選べ、有償サービスに入るとバージョン固定ができる、という感じのようです。Chainguard ベースのイメージも Distroless 同様にシェルがないのでセキュリティに穴があってもそもそも稼働中のコンテナの中で悪さができない（ログインできない）から強い（アタックサーフェースが狭い）、というのが理屈です。なお、Python の標準ライブラリも、攻撃の足掛かりにされるようなビルドしたりインストールするためのものは省かれています。そのため<code>python -m pip</code>でインストールを行った標準的なやり方はでず、必然的にマルチステージビルドが必要になります。</p>
<p>Distroless は debug にするとシェル入りになる以外の選択肢がなく、イメージ作成には Debian ベースの標準の Python イメージを使いましたが、Chainguard はシェルなしの latest と、開発ツールやシェル、pip モジュールなどの開発用の標準ライブラリも揃っている latest-dev の 2 つがあり、どちらも Chainguard で揃えられます。なお、Chainguard の OS は Debian ではなく、Wolfiというもので、これも Chainguard がメンテナンスしています。apk コマンドがあるので Alipne 系な雰囲気ですが、musl ではなく readelf で見てみると libc を使ってそうなところも Python 的には良いですね。</p>
<p>Chainguard の° ドキュメントを見ると、venv を持ってきてそれを使っています。以前のエントリーでは Distroless はシェルがないので venv を使う方式は避けて無理やり site-packages に入れる方針でやりましたがバージョン番号固定の Dockerfile になってしまうのでこちらの方が良いですね。</p>
<div class="code-block"><figure class="highlight dockerfile"><input type="checkbox" id="code-wrap-g6mkq-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-g6mkq-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># syntax=docker/dockerfile:1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># ビルド用イメージ</span></span><br><span class="line"><span class="keyword">FROM</span> cgr.dev/chainguard/python:latest-dev AS builder</span><br><span class="line"><span class="keyword">ENV</span> PYTHONDONTWRITEBYTECODE=<span class="number">1</span></span><br><span class="line"><span class="keyword">ENV</span> PYTHONUNBUFFERED=<span class="number">1</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /opt/app</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> python -m venv /opt/app/venv</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> --mount=<span class="built_in">type</span>=cache,target=/root/.cache/pip \</span></span><br><span class="line"><span class="language-bash">    --mount=<span class="built_in">type</span>=<span class="built_in">bind</span>,<span class="built_in">source</span>=requirements.txt,target=requirements.txt \</span></span><br><span class="line"><span class="language-bash">    /opt/app/venv/bin/pip install -r requirements.txt</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 実行用イメージ</span></span><br><span class="line"><span class="keyword">FROM</span> cgr.dev/chainguard/python:latest AS runner</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /opt/app</span></span><br><span class="line"><span class="keyword">ENV</span> PYTHONUNBUFFERED=<span class="number">1</span></span><br><span class="line"><span class="keyword">ENV</span> PATH=<span class="string">&quot;/venv/bin:$PATH&quot;</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /opt/app/venv /venv</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> main.py /opt/app/main.py</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">80</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;python&quot;</span>, <span class="string">&quot;/venv/bin/fastapi&quot;</span>, <span class="string">&quot;run&quot;</span>, <span class="string">&quot;main.py&quot;</span>, <span class="string">&quot;--port&quot;</span>, <span class="string">&quot;80&quot;</span>]</span></span><br></pre></td></tr></table></figure></div>

<p>Chainguard のドキュメントだと古い COPY と RUN を組み合わせた書き方になっていますが、マルチステージビルドであったとしても、cache&#x2F;bind マウントを活用する方がキャッシュ効率を上げつつ、キャッシュのために余計なパズルを組み立てる必要はないというメリットは得られます。</p>
<h3 id="Distroless">Distroless</h3><p>前回書いた Distroless のイメージ作成方法は site-packages を丸ごと持ってくるちょっと無理やりな方法で実現していました。Chainguard のやり方がスマートだったので、それと同様の venv を使った方式でやってみます。</p>
<p>なお、前回書き忘れましたが、<code>:debug</code>付きイメージはエントリポイントとしてシェル(busybox)を起動できます。Distroless の Python イメージは、本来シェルが指定される ENTRYPOINT に Python インタプリタが指定されているのでシェルを起動する場合はイメージ名の後ろにコマンドを書いてもダメで、<code>--entrypoint</code>引数でシェルを渡す必要があります。</p>
<div class="code-block"><figure class="highlight sh"><input type="checkbox" id="code-wrap-g6mkq-3" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-g6mkq-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">docker run --<span class="built_in">rm</span> -it --entrypoint=sh gcr.io/distroless/python3-debian12:debug</span><br></pre></td></tr></table></figure></div>

<div class="code-block"><figure class="highlight dockerfile"><input type="checkbox" id="code-wrap-g6mkq-4" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-g6mkq-4" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line"><span class="comment"># syntax=docker/dockerfile:1</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">ARG</span> PYTHON_VERSION=<span class="number">3.11</span>.<span class="number">2</span></span><br><span class="line"><span class="keyword">ARG</span> DISTROLESS=python3-debian12</span><br><span class="line"></span><br><span class="line"><span class="comment"># ビルド用イメージ</span></span><br><span class="line"><span class="keyword">FROM</span> python:$&#123;PYTHON_VERSION&#125;-slim AS builder</span><br><span class="line"><span class="keyword">ENV</span> PYTHONDONTWRITEBYTECODE=<span class="number">1</span></span><br><span class="line"><span class="keyword">ENV</span> PYTHONUNBUFFERED=<span class="number">1</span></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /app</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> --mount=<span class="built_in">type</span>=cache,target=/root/.cache/pip \</span></span><br><span class="line"><span class="language-bash">    --mount=<span class="built_in">type</span>=<span class="built_in">bind</span>,<span class="built_in">source</span>=requirements.txt,target=requirements.txt \</span></span><br><span class="line"><span class="language-bash">    python -m pip install -r requirements.txt</span></span><br><span class="line"><span class="comment"># debianとdistrolessでPythonのフォルダが違うのでvenvの中のシンボリックリンクを修正</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">rm</span> /opt/app/venv/bin/python</span></span><br><span class="line"><span class="keyword">RUN</span><span class="language-bash"> <span class="built_in">ln</span> -s /usr/bin/python /opt/app/venv/bin/python</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 実行用イメージ</span></span><br><span class="line"><span class="keyword">FROM</span> gcr.io/distroless/$&#123;DISTROLESS&#125;:debug AS runner</span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> /opt/app</span></span><br><span class="line"><span class="keyword">ENV</span> PYTHONUNBUFFERED=<span class="number">1</span></span><br><span class="line"><span class="keyword">ENV</span> PATH=<span class="string">&quot;/venv/bin:$PATH&quot;</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> --from=builder /opt/app/venv /venv</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> main.py /opt/app/main.py</span></span><br><span class="line"><span class="keyword">EXPOSE</span> <span class="number">80</span></span><br><span class="line"><span class="keyword">ENTRYPOINT</span><span class="language-bash"> [<span class="string">&quot;python&quot;</span>, <span class="string">&quot;/venv/bin/fastapi&quot;</span>, <span class="string">&quot;run&quot;</span>, <span class="string">&quot;main.py&quot;</span>, <span class="string">&quot;--port&quot;</span>, <span class="string">&quot;80&quot;</span>]</span></span><br></pre></td></tr></table></figure></div>

<p>Chainguard と違ってビルド用イメージと実行用イメージが違う関係で Python のパスが違っており、それにより venv フォルダ(中で Python インタプリタへのシンボリックリンクを持っている)をそのまま持ってきてもうまくいきませんのでちょっとリンクを貼り直す行が必要です。おかげで venv に詳しくなりました。</p>
<ul>
<li>えんでぃの技術ブログ: venv が動作する仕組みを調べてみた</li>
</ul>
<h2 id="まとめ">まとめ</h2><p>Docker の更新そのものは追っかけてましたが、それにより Python のイメージの作成がどう変わったのかというのは把握できていなかったので「なぜそうなっているのか」「今の時代どうすべきか」を調べながら書いてみました。また、FastAPI 自身もアップデートがあり、実行方法が簡単になっていた（簡単になりすぎて不安になった）ので、それも盛り込んでみました。</p>
]]></content>
    <summary type="html">5年ほど前にPythonのコンテナ化について2つの記事を書きましたがFastAPI側もDocker側もアップデートがあり、当時よりもかなりシンプルになってきたのを感じたので少し調べてまとめてみました。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="Docker" scheme="https://future-architect.github.io/tags/Docker/"/>
    <category term="FastAPI" scheme="https://future-architect.github.io/tags/FastAPI/"/>
    <category term="Python" scheme="https://future-architect.github.io/tags/Python/"/>
  </entry>
  <entry>
    <title>初めての保守運用</title>
    <link href="https://future-architect.github.io/articles/20250523a/"/>
    <id>https://future-architect.github.io/articles/20250523a/</id>
    <published>2025-05-22T15:00:00.000Z</published>
    <updated>2025-05-22T15:00:00.000Z</updated>
    <author><name>星貴之</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250523a/undraw_learning_qt7d.png" alt="" width="500" height="784">

<p>春の入門祭り2025の22本目の記事です。</p>
<h2 id="はじめに">はじめに</h2><p>はじめまして、公共サービス事業部の星と申します。2023年10月に新卒入社、翌年1月から約1年間、前所属の事業部でシステムの保守運用業務を担当しました。</p>
<p>この記事は、私の業務経験をもとにしており、まだ保守運用の経験がない方や始めたばかりの方向けです。</p>
<h2 id="システムの保守運用とは">システムの保守運用とは</h2><p>一般的に「保守」と「運用」は別物で、それぞれ具体的に何をするのか簡潔に説明すると以下でしょうか。</p>
<ul>
<li>保守：機能の改修を含む活動</li>
<li>運用：安定稼働させるための活動（定型、非定型）</li>
</ul>
<p>より具体的には、私の場合ですと監視やデータパッチ等の定常作業を実施しつつ、エラーが発生した時の原因調査および復旧や、想定外の挙動を発見した時の改修なども実施していました。</p>
<p>私は特定の顧客向けのシステムを担当していたので、上記に加えて顧客の要望に応じた機能追加やデータ調査等も行っていました。</p>
<h2 id="キャッチアップにおける工夫">キャッチアップにおける工夫</h2><p>私のように保守運用フェーズの途中から配属された場合、当然ですが配属当初からシステムは完成しており既に稼働していることになります。今まで触れたことや聞いたことすらない技術が数多く使われていました。さらに、技術面に加えて顧客の業務背景なども勉強する必要があります。</p>
<p>学ばないといけないことが多いですが、なるべく早くキャッチアップするために意識していたこと、実践していたことを書きたいと思います。</p>
<h3 id="一般の知識か内部の知識か">一般の知識か内部の知識か</h3><p>今学ぼうとしていることは「インターネットで検索したら多数の記事がヒットする」ような広く知れ渡っているものなのか、それとも「プロジェクト内のドキュメントにしか記載がない」ような内部の人しか知らないものなのか。この違いを常に意識していました。</p>
<p>前者であれば、時間をかけてでも納得いくまで調べることが多くありました。様々な記事を読んだり、ひたすらAIに質問したりしていました。特にAIへの質問は、どんなに初歩的な内容でも気軽にできるのでおすすめです。AIからの回答が正しそうか調査し、新たに生まれた疑問をまたAIに聞いてみる、というサイクルが効果的でした。</p>
<p>一方で後者の場合は、自分で調べるだけでは限界がありますし、AIに聞くわけにもいきません。ある程度調べても解決しないものは躊躇せず先輩方に質問していました。そのためにも、やはり素早くプロジェクトに溶け込んで周囲と良い関係を構築するのは重要なことです。以下の記事で紹介されている工夫が興味深かったです。</p>
<ul>
<li>チームを異動で環境が変わった後の立ち上がりについて</li>
</ul>
<p>保守運用業務に限る話ではないですが、両者の違いを意識すると効率的なキャッチアップができるのではないかと思います。</p>
<h3 id="データを見る">データを見る</h3><p>保守運用担当者に対してどこまでデータベースにアクセスする権限が与えられているかはプロジェクトにもよると思いますが、許されている範囲で実際のデータを積極的に見ると良いと思います。</p>
<p>データを見ている中で「このテーブルのこのカラムにはそういう意味があるのか」「このカラムにはこういったイレギュラーなデータが入ることもあるのか」というような発見が多くあり、顧客業務の理解にも繋がっていました。</p>
<h2 id="障害発生時の対応">障害発生時の対応</h2><p>私が配属された時点ではシステム全体が安定稼働していましたが、それでも稀にメインシステムがエラーで落ちてしまうことがありました。</p>
<p>顧客業務に大きな影響があることは理解していたので、発見次第すぐに周囲に伝えることを徹底していました。先輩方の動きを見て対応を少しずつ学んでいきましたが、自分では原因の特定が困難だったと感じる場面もあり、やはり障害を発見した際はまず共有することが大事だと感じました。</p>
<p>システム障害対応の心構えと対応 にもあるように、障害発生時は初動が重要だということを身をもって実感しました。</p>
<h2 id="注意すべき点">注意すべき点</h2><p>特に特定の顧客向けに作られたシステムの場合、顧客業務の事情がソースコードに反映されていることがあるという点は気を付けるべきだと感じました。</p>
<p>例えば、ある機能の改修を担当することになり該当するソースコードを読んでいると、以下のような疑問を抱く場面があるかもしれません。</p>
<ul>
<li>なぜこのような “不自然な” 処理順序になっているのか？</li>
<li>この処理はもっと簡潔に記述できるのでは？</li>
</ul>
<p>そうした場合に改善の余地ありとすぐに断定するのは危険です。不自然あるいは冗長に見える実装がされているのは、顧客の業務上発生しうるイレギュラーなケースに耐えるためかもしれません。もちろん実際に改善できる場合もあると思いますが、まずは周囲に確認することが大切です。</p>
<h2 id="おわりに">おわりに</h2><p>私の業務経験をもとに、保守運用について共有したいと感じたポイントを書きました。</p>
<p>技術的な話はできませんでしたが、少しでも保守運用業務のイメージがわいたり、参考になる部分があれば幸いです。</p>
]]></content>
    <summary type="html">約1年間、前所属の事業部でシステムの保守運用業務を担当しました</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="保守運用" scheme="https://future-architect.github.io/tags/%E4%BF%9D%E5%AE%88%E9%81%8B%E7%94%A8/"/>
    <category term="入門" scheme="https://future-architect.github.io/tags/%E5%85%A5%E9%96%80/"/>
    <category term="初心者向け" scheme="https://future-architect.github.io/tags/%E5%88%9D%E5%BF%83%E8%80%85%E5%90%91%E3%81%91/"/>
  </entry>
  <entry>
    <title>Mac歴10年のWindows入門</title>
    <link href="https://future-architect.github.io/articles/20250521a/"/>
    <id>https://future-architect.github.io/articles/20250521a/</id>
    <published>2025-05-20T15:00:00.000Z</published>
    <updated>2025-05-20T15:00:00.000Z</updated>
    <author><name>長谷川寛人</name></author>
    <content type="html"><![CDATA[<p>春の入門祭り2025の20本目の記事です。</p>
<img fetchpriority="high" src="/images/2025/20250521a/mac_windows.png" alt="" width="1200" height="800">

<h2 id="はじめに">はじめに</h2><p>こんにちは。TIG（Technology Innovation Group）の長谷川です。2025年11月にWeb系企業からの転職でキャリア入社し、現在は主にフロントエンド開発を担当しています。</p>
<p>新しいプロジェクトで支給されたのはPCはWindows。大学入学以降、約10年間MacBookを使い続けてきた私にとっては、高校生以来となるWindowsとの再会です。</p>
<p>私物のマシンはMacBookのUS配列である一方で、支給PCはWindowsのJIS配列。</p>
<p>この差がなかなかにつらく、正直、チャットすら満足にできない状態でした（入社して一番苦戦したポイントかもしれません…w）。</p>
<p>そこで、<strong>Windowsを徹底的にMacBookに近づけ、快適な作業環境を構築する</strong>ことにしました。そのプロセスを詳しく紹介します。</p>
<p>「Mac派だけど諸事情でWindowsに触れることになった」方はもちろん <strong>「元々Windowsユーザだけど便利な設定を知りたい」という方にも役立つ内容</strong>となっていますので、ぜひ最後までご覧ください。</p>
<h2 id="前提">前提</h2><p>MacユーザーがWindows環境で快適に作業するためのアプローチは、以下の3つに大別できます。</p>
<ul>
<li>A. Windowsに慣れる</li>
<li>B. 双方で統一的に使える環境を整える</li>
<li>C. WindowsをMacに寄せる</li>
</ul>
<p>今回は <strong>Cの「WindowsをMacに寄せる」方針</strong> で進めています。</p>
<p>なお、使用するOSはWindows 11です。</p>
<h2 id="目指す使用感">目指す使用感</h2><p>普段のMacBook使用スタイルは非常にシンプルで、<strong>「内蔵のキーボードとトラックパッドのみを使う」派</strong>です。BetterTouchTool（以下BTT）でジェスチャーを割り当て、さまざまな操作を実現しています。</p>
<p>MacBookの使い方から離れないために、Windowsをカスタマイズをしていく上でのポリシーを以下とします。</p>
<ul>
<li>マウスは使用しない</li>
<li>キーボードは持ち運ばない</li>
</ul>
<p>最初は外付けキーボード自体を使わないつもりでしたが、現在はリモートワークの際には分割キーボードを利用しています。</p>
<h2 id="トラックパッド">トラックパッド</h2><p>最初に取り組むのは、トラックパッドのつらさへの対処です。</p>
<p>トラックパッドにおける課題はクリックの重さ。これはWindowsでもデバイスによると思いますが、支給PCのデバイスはクリック時に必要な力が少し大きく若干ストレスでした。</p>
<p>自宅にMagic Trackpadがあったため、Windowsでも利用できるか調査したところ、問題なく使えそう。導入方法については以下の記事を参考にさせていただきました。とくに複雑な設定は不要で簡単に利用できます。</p>
<p>cf. Windows 11 で Apple Magic Trackpad を快適に使う方法</p>
<p>これでまずはトラックパッドがMacに近づきました🎉</p>
<h2 id="Windowsの設定をMac風に">Windowsの設定をMac風に</h2><p>Magic Trackpadの導入により、カーソルの操作性が向上しました。</p>
<p>キーボードを改善する前に、各種設定を調整してMacに近い使用感を目指します。</p>
<p>以下に主な設定を記載しています。</p>
<ul>
<li><strong>Bluetoothとデバイス &gt; タッチパッド</strong><ul>
<li>カーソル速度: <code>10</code><ul>
<li>カーソルは速さが正義💨</li>
</ul>
</li>
<li>タップ<ul>
<li>タッチパッドの感度: <code>低い感度</code></li>
<li>1本の指でタップしてシングルクリックする: <code>ON</code></li>
<li>2本の指でタップして右クリックする: <code>ON</code></li>
<li>2回タップしてドラッグすると複数選択: <code>OFF</code>（誤動作することが多かったので）</li>
<li>右クリックするにはタッチパッドの右下を押します: <code>OFF</code></li>
</ul>
</li>
<li>スクロールとズーム<ul>
<li>2本の指をドラッグしてスクロールする: <code>ON</code></li>
<li>スクロール方向: <code>ダウンモーションで上にスクロール</code></li>
<li>ピンチ操作によるズーム: <code>ON</code></li>
</ul>
</li>
<li>ジェスチャ<ul>
<li>3本指ジェスチャ<ul>
<li>タップ: <code>マウスの中央ボタン</code>（リンクを開くときに新規タブで開いたり便利です）</li>
<li>上方向にスワイブ: <code>デスクトップの表示</code></li>
<li>下方向にスワイブ: <code>カスタムショートカット（Ctrl + W）</code>（タブを閉じる）</li>
<li>左方向にスワイブ: <code>カスタムショートカット（Ctrl + Page Up）</code>（前のタブに移動）</li>
<li>右方向にスワイブ: <code>カスタムショートカット（Ctrl + Page Down）</code>（次のタブに移動）</li>
</ul>
</li>
<li>4本指ジェスチャ<ul>
<li>タップ: <code>通知センター</code></li>
<li>上方向にスワイブ: <code>タスクビュー</code></li>
<li>下方向にスワイブ: <code>カスタムショートカット（Ctrl + Shift + T）</code>（閉じたタブを復元）</li>
<li>左方向にスワイブ: <code>デスクトップを切り替える</code></li>
<li>右方向にスワイブ: <code>デスクトップを切り替える</code></li>
</ul>
</li>
<li>MacではBTTを使って色々とジェスチャーを登録しています</li>
<li>BTTほど柔軟ではありませんが、Windows公式の機能でそれなりに柔軟に対応できるのは嬉しい</li>
</ul>
</li>
</ul>
</li>
<li><strong>システム＞クリップボード</strong><ul>
<li><code>クリップボードの履歴</code> をONに</li>
<li>MacだとBTTでクリップボード管理しています</li>
</ul>
</li>
<li><strong>システム &gt; マルチタスク</strong><ul>
<li>ウィンドウのスナップ: <code>ON</code><ul>
<li>ウィンドウをスナップしたときに、次にスナップする対象を提案する: <code>OFF</code></li>
<li>ウィンドウの最大化ボタンにカーソルを合わせたときにスナップレイアウトを表示する: <code>ON</code></li>
<li>ウィンドウを画面の上部にドラッグしたときにスナップレイアウトを表示する: <code>OFF</code></li>
<li>タスクビューのタスクバーアプリ上にマウスカーソルを移動したとき、そしてAlt+Tabを押したときに、スナップしたウィンドウを表示する: <code>ON</code></li>
<li>ウィンドウをドラッグしたときに、画面の端までドラッグしなくてもウィンドウをスナップできるようにする: <code>ON</code></li>
</ul>
</li>
<li>スナップまたはAlt+Tabを押したときにアプリのタブを表示する: <code>（最新の3つのタブ）</code></li>
<li>デスクトップ<ul>
<li>タスクバーに開いているすべてのウィンドウを表示: <code>すべてのデスクトップで</code></li>
<li>Alt+Tabを押したときに、開いているすべてのウィンドウを表示: <code>使用中のデスクトップでのみ</code></li>
<li>💡Windowsの仮想デスクトップ使いづらいと感じていましたが、この設定をすることでMacと似た感じで使えるようになりました</li>
</ul>
</li>
<li>タイトルバーウィンドウのシェイク: <code>OFF</code></li>
</ul>
</li>
<li><strong>アカウント &gt; サインインオプション &gt; 追加の設定</strong><ul>
<li>再起動可能なアプリを自動的に保存し、再度サインインした時に再起動する: <code>ON</code>（再起動前に開いていたアプリを再起動してくれる）<ul>
<li>Macほどすべてが復元されるわけではなさそう</li>
</ul>
</li>
</ul>
</li>
<li><strong>個人用設定 &gt; タスクバー</strong><ul>
<li>検索: <code>検索ボックス</code></li>
<li>ウィジェット: <code>OFF</code></li>
</ul>
</li>
<li><strong>個人用設定 &gt; テーマ &gt;デスクトップ アイコンの設定</strong><ul>
<li>ゴミ箱をデスクトップから非表示に</li>
<li>何もないデスクトップは至高</li>
</ul>
</li>
<li><strong>Google IME</strong><ul>
<li>ことえりのショートカットを選択<ul>
<li>ことえりのライブ変換のような機能はなさそうでした・・・</li>
</ul>
</li>
<li>後述するAutoHotKeyを使わない場合の英数&#x2F;かなの切り替えはGoogle日本語入力の機能で対応できそう<ul>
<li>cf. Google日本語入力で無変換キーでIMEをオン&#x2F;オフする - 日記とか、工作記録とか</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="キーボードの調整">キーボードの調整</h2><p>US配列（Mac）とJIS配列（Windows）の差によるキーボードの課題は以下の3つ。</p>
<ul>
<li>記号等の配置の違い</li>
<li>修飾キーの意味や使い方、配置の違い</li>
<li>ショートカットキーの組み合わせの違い</li>
</ul>
<p>最初はJIS配列のWindowsキーボードに慣れようとしたものの、Macの操作感からのギャップが大きく断念しました。<br>また、仮に慣れたとしても、今度はMacが使いづらくなってしまう。</p>
<p>そこでMac風の操作に近づける方向でキーボードを調整することとしました。</p>
<p>（Macの場合は大抵 <code>command + ⚪︎</code> ですが、Windowsは操作によって<code>Windows</code> <code>Alt</code> <code>Ctrl</code> を使い分ける印象がありますね。思想の違いがありそう。）</p>
<h3 id="Change-Key-の活用">Change Key の活用</h3><p>キーボード配列はそれぞれ以下のようになっています。</p>
<ul>
<li>MacBookのUS配列<ul>
<li><code>fn</code>・<code>control</code>・<code>option</code>・<code>command</code>・<code>スペースバー</code>・<code>command</code>・<code>option</code></li>
</ul>
</li>
<li>WindowsのJIS配列<ul>
<li><code>ctrl</code>・<code>fn</code>・<code>win</code>・<code>alt</code>・<code>無変換</code>・<code>スペースバー</code>・<code>変換</code>・<code>カタカナ</code>・<code>Copilot</code></li>
</ul>
</li>
</ul>
<p>このように並びが全然違っており、左手側を見ると、似た機能を持つことが多い<code>command(Mac)</code>と<code>ctrl(Windows)</code>の位置が反対にあります。とくに使うことが多いコピペの配置が全く違うのでこれはかなり困りました。</p>
<p>そこで今回はChange Keyというソフトで配列を以下のように変更します。</p>
<ul>
<li>無変換 → <code>Ctrl左</code></li>
<li>変換 → <code>Ctrl右</code></li>
<li>カタカナ・ひらがな → <code>Alt右</code></li>
</ul>
<p>これでMacと似た感覚でコピペできるようになりました🎉</p>
<p>📝参考記事：</p>
<ul>
<li>Mac 慣れした私に Windows が支給されたので、まず設定したこと | フューチャー技術ブログ</li>
<li>【Change Key】キーボードの割り当てを変更するソフトの使い方 | ナポリタン寿司のPC日記</li>
</ul>
<h3 id="ULE4JIS-でUS配列風に">ULE4JIS でUS配列風に</h3><p>JIS配列を完全にUS配列で上書きするとたまに不都合があるため、ULE4JIS を導入し、必要に応じてON&#x2F;OFFを切り替える方式を採用しました。スタートアップに登録して運用しています。</p>
<p>なお、Google Docsのショートカットに一部不具合がある等、少しバグがある(?)点には注意が必要です。<br>（箇条書きのショートカットは本来<code>Ctrl + Shift + 8</code> だが <code>Ctrl + Shift + 9</code> になってしまう）</p>
<p>今だとこちらを利用させてもらってもいいかも。</p>
<ul>
<li>USkey2JP - Musashino Software</li>
</ul>
<p>📝参考記事：</p>
<ul>
<li>ノートパソコンはJIS配列で外付けキーボードをUS配列に（Windows）</li>
<li>Windows11のスタートアップを設定する方法 - mouse LABO</li>
</ul>
<h3 id="AutoHotKey-による魔改造">AutoHotKey による魔改造</h3><p>Change KeyとULE4JISのおかげで普通にタイピングできるようにはなりました。</p>
<p>しかし、Macで染み付いたショートカットはWindowsで利用できません。</p>
<p>そこで、AutoHotKeyを使ってMacの操作感を再現しました。</p>
<p>具体例：</p>
<ul>
<li>ctrl(無変換&#x2F;変換)単押しによるIME切替（左単押しで<code>英数</code>、右単押しで<code>かな</code>）</li>
<li>Mac風のウィンドウ操作やショートカット</li>
<li>各種アプリ（Chrome・Excel・Slack）でのカスタム対応</li>
</ul>
<p>コメント付きのコードを以下に添付するのでご参照ください。</p>
<p><code>main.ahk</code> をスタートアップに登録しておくと快適です。</p>
<h4 id="main-ahk">main.ahk</h4><div class="code-block"><figure class="highlight lisp"><input type="checkbox" id="code-wrap-4nfvyz-1" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-4nfvyz-1" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">#Requires AutoHotkey v2.<span class="number">0</span></span><br><span class="line"></span><br><span class="line">#Include ./lib/IMEv2.ahk</span><br><span class="line">#Include ./utils.ahk</span><br><span class="line">#Include ./applications.ahk</span><br><span class="line">#Include ./configs.ahk</span><br><span class="line"></span><br><span class="line">#SingleInstance Force</span><br><span class="line"></span><br><span class="line"><span class="comment">; main.ahk を再起動</span></span><br><span class="line">^!.:: &#123;</span><br><span class="line">    MsgBox(<span class="string">&quot;Rerun main.ahk&quot;</span>)</span><br><span class="line">    Reload()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">;=============================================</span></span><br><span class="line"><span class="comment">; キーボードの Remap</span></span><br><span class="line"><span class="comment">;---------------------------------------------</span></span><br><span class="line"><span class="comment">; Alt 単押しでカーソルのフォーカスが外れるのを防ぐ</span></span><br><span class="line">~LAlt:: Send <span class="string">&quot;&#123;Blind&#125;&#123;vk07&#125;&quot;</span></span><br><span class="line">~RAlt:: Send <span class="string">&quot;&#123;Blind&#125;&#123;vk07&#125;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">; Copilot ボタンを無効化</span></span><br><span class="line">#+F23:: Send <span class="string">&quot;&#123;Blind&#125;&#123;vk07&#125;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">; Win + Alt + Ctrl + BackSpace で Delete</span></span><br><span class="line">#!^BackSpace::Delete</span><br><span class="line"><span class="comment">;=============================================</span></span><br><span class="line"><span class="comment">; macOS 風キーバインド</span></span><br><span class="line"><span class="comment">; cf. &lt;https://jimon.info/macos-like-windows#単語単位カーソル移動を再現&gt;</span></span><br><span class="line"><span class="comment">; --------------------------------------------</span></span><br><span class="line"><span class="comment">; cmd+矢印 の挙動を再現</span></span><br><span class="line">^Left::Home <span class="comment">; Mac の「cmd + ←」風</span></span><br><span class="line">^Right::End <span class="comment">; Mac の「cmd + →」風</span></span><br><span class="line">^Up::^Home <span class="comment">; Mac の「cmd + ↑」風</span></span><br><span class="line">^Down::^End <span class="comment">; Mac の「cmd + ↓」風</span></span><br><span class="line"></span><br><span class="line"><span class="comment">; Option+左右 の挙動を再現</span></span><br><span class="line">!Left:: SendInput <span class="string">&quot;^&#123;Left&#125;&quot;</span> <span class="comment">; Mac の「option + ←」風</span></span><br><span class="line">!Right:: SendInput <span class="string">&quot;^&#123;Right&#125;&quot;</span> <span class="comment">; Mac の「option + →」風</span></span><br><span class="line">+!Left:: SendInput <span class="string">&quot;^+&#123;Left&#125;&quot;</span> <span class="comment">; Mac の「option + shift + ←」風</span></span><br><span class="line">+!Right:: SendInput <span class="string">&quot;^+&#123;Right&#125;&quot;</span> <span class="comment">; Mac の「option + shift + →」風</span></span><br><span class="line"></span><br><span class="line"><span class="comment">; Mac の control + j,k,l,; が Windows では win + j,k,l,; になるので、macと同じ位置で使えるようにする</span></span><br><span class="line">#j::^j</span><br><span class="line">#k::^k</span><br><span class="line"><span class="comment">; win + l は画面ロック機能で上書きできない</span></span><br><span class="line"><span class="comment">; #l::^l</span></span><br><span class="line">#<span class="comment">;:: Send &quot;&#123;F10&#125;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">; Mac の cmd + tab 的に使えるように変更</span></span><br><span class="line"><span class="comment">; cmd + shift ではうまくいかなかったので caps lock で代用</span></span><br><span class="line">LCtrl &amp; Tab::AltTab</span><br><span class="line">LCtrl &amp; sc03A::ShiftAltTab <span class="comment">; sc03A=caps lock</span></span><br><span class="line"></span><br><span class="line"><span class="comment">; 削除系</span></span><br><span class="line">!Backspace:: Send <span class="string">&quot;&#123;LCtrl Down&#125;&#123;Backspace&#125;&#123;LCtrl Up&#125;&quot;</span> <span class="comment">; 単語削除</span></span><br><span class="line">^Backspace:: Send <span class="string">&quot;&#123;LShift Down&#125;&#123;Home Down&#125;&#123;Backspace&#125;&#123;Home Up&#125;&#123;LShift Up&#125;&quot;</span> <span class="comment">; 行頭まで削除</span></span><br><span class="line"><span class="comment">;=============================================</span></span><br><span class="line"><span class="comment">; IME や Ule4Jis など文字入力関連の設定</span></span><br><span class="line"><span class="comment">;---------------------------------------------</span></span><br><span class="line">~LCtrl:: Send <span class="string">&quot;&#123;Blind&#125;&#123;vk07&#125;&quot;</span></span><br><span class="line">~RCtrl:: Send <span class="string">&quot;&#123;Blind&#125;&#123;vk07&#125;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">; 左 Ctrl 単押しで IME を OFF</span></span><br><span class="line">~LCtrl Up:: &#123;</span><br><span class="line">    if (<span class="name">A_Priorkey</span> != <span class="string">&quot;LControl&quot;</span>) &#123;</span><br><span class="line">        return</span><br><span class="line">    &#125;</span><br><span class="line">    IME_SET(<span class="number">0</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">; 右 Ctrl 単押しで IME を ON</span></span><br><span class="line">~RCtrl Up:: &#123;</span><br><span class="line">    if (<span class="name">A_Priorkey</span> != <span class="string">&quot;RControl&quot;</span>) &#123;</span><br><span class="line">        return</span><br><span class="line">    &#125;</span><br><span class="line">    IME_SET(<span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">; caps lock の２連打で Ule4Jis の有効/無効を切り替える</span></span><br><span class="line">~sc03A:: &#123;</span><br><span class="line">    <span class="comment">; REF: &lt;https://ahkscript.github.io/ja/docs/v2/FAQ.htm#DoublePress&gt;</span></span><br><span class="line">    if (<span class="name">ThisHotkey</span> != A_PriorHotkey || A_TimeSincePriorHotkey &gt; <span class="number">200</span>) &#123;</span><br><span class="line">        return</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if (<span class="name">Utils</span>.isActiveWindow(<span class="name">Configs</span>.windowTitles.ule4Jis)) &#123;</span><br><span class="line">        WinClose(<span class="name">Configs</span>.windowTitles.ule4Jis)</span><br><span class="line">        MsgBox (<span class="string">&quot;ULE4JIS を終了しました&quot;</span>)</span><br><span class="line">        return</span><br><span class="line">    &#125;</span><br><span class="line">    Run(<span class="name">Configs</span>.paths.ule4Jis)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">; ============================================</span></span><br><span class="line"><span class="comment">; window 操作関連</span></span><br><span class="line"><span class="comment">; FancyZones の設定も必要</span></span><br><span class="line"><span class="comment">; Mac では BetterTouchTool で行っている設定</span></span><br><span class="line"><span class="comment">; --------------------------------------------</span></span><br><span class="line"><span class="comment">; ウィンドウを最大化</span></span><br><span class="line">^!,:: WinMaximize(<span class="string">&quot;A&quot;</span>)</span><br><span class="line"><span class="comment">; ウィンドウを最大化してから、次のディスプレイに移動</span></span><br><span class="line">^!m:: &#123;</span><br><span class="line">    WinMaximize(<span class="string">&quot;A&quot;</span>)</span><br><span class="line"></span><br><span class="line">    Send(<span class="string">&quot;&#123;LWin Down&#125;&quot;</span>)</span><br><span class="line">    Send(<span class="string">&quot;+&#123;Right&#125;&quot;</span>)</span><br><span class="line">    Send(<span class="string">&quot;&#123;LWin Up&#125;&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">; ウィンドウを FancyZones 上の左に移動</span></span><br><span class="line">^!j:: &#123;</span><br><span class="line">    Send(<span class="string">&quot;&#123;LWin Down&#125;&quot;</span>)</span><br><span class="line">    Send(<span class="string">&quot;&#123;Left&#125;&quot;</span>)</span><br><span class="line">    Send(<span class="string">&quot;&#123;LWin Up&#125;&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">; ウィンドウを FancyZones 上の右に移動</span></span><br><span class="line">^!k:: &#123;</span><br><span class="line">    Send(<span class="string">&quot;&#123;LWin Down&#125;&quot;</span>)</span><br><span class="line">    Send(<span class="string">&quot;&#123;Right&#125;&quot;</span>)</span><br><span class="line">    Send(<span class="string">&quot;&#123;LWin Up&#125;&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">; ============================================</span></span><br><span class="line"><span class="comment">; アプリの終了や最小化関連の操作</span></span><br><span class="line"><span class="comment">; --------------------------------------------</span></span><br><span class="line"><span class="comment">; Mac では cmd + Q でアプリの終了</span></span><br><span class="line">^q:: WinClose(<span class="string">&quot;A&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">; Mac では cmd + h で非表示、 cmd + m で最小化</span></span><br><span class="line"><span class="comment">; Windows では 非表示出来ないので最小化のみ</span></span><br><span class="line"><span class="comment">; 最小化したウィンドウを復元するために配列に格納しておく</span></span><br><span class="line">minimizedWindows := []</span><br><span class="line">^h::</span><br><span class="line">^m:: &#123;</span><br><span class="line">    activeWindow := WinActive(<span class="string">&quot;A&quot;</span>)</span><br><span class="line">    WinMinimize(<span class="name">activeWindow</span>)</span><br><span class="line">    minimizedWindows.Push(<span class="name">activeWindow</span>)</span><br><span class="line">&#125;</span><br><span class="line">^+h::</span><br><span class="line">^+m:: &#123;</span><br><span class="line">    if minimizedWindows.Length &lt;= <span class="number">0</span> &#123;</span><br><span class="line">        MsgBox <span class="string">&quot;最小化されたウィンドウがありません。&quot;</span></span><br><span class="line">        return</span><br><span class="line">    &#125;</span><br><span class="line">    lastMinimizedWindow := minimizedWindows.Pop()</span><br><span class="line"></span><br><span class="line">    <span class="comment">; もしウィンドウが最小化状態であれば復元</span></span><br><span class="line">    if WinGetMinMax(<span class="name">lastMinimizedWindow</span>) == <span class="number">-1</span> &#123;</span><br><span class="line">        WinRestore(<span class="name">lastMinimizedWindow</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<h4 id="applications-ahk">applications.ahk</h4><div class="code-block"><figure class="highlight lisp"><input type="checkbox" id="code-wrap-4nfvyz-2" class="code-wrap-input code-wrap-narrow" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-4nfvyz-2" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">#Requires AutoHotkey v2.<span class="number">0</span></span><br><span class="line"></span><br><span class="line">#Include utils.ahk</span><br><span class="line"><span class="comment">; ============================================</span></span><br><span class="line"><span class="comment">; Chrome 専用</span></span><br><span class="line"><span class="comment">; --------------------------------------------</span></span><br><span class="line">#HotIf WinActive(<span class="string">&quot;ahk_exe chrome.exe&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">; alt + ctrl + i で開発者ツール</span></span><br><span class="line">^!i::+^i</span><br><span class="line"></span><br><span class="line">#HotIf</span><br><span class="line"><span class="comment">; ============================================</span></span><br><span class="line"><span class="comment">; Excel 専用</span></span><br><span class="line"><span class="comment">; --------------------------------------------</span></span><br><span class="line">#HotIf WinActive(<span class="string">&quot;ahk_exe EXCEL.EXE&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">; Shift + Enter でセルを編集する</span></span><br><span class="line">+Enter::F2</span><br><span class="line"></span><br><span class="line"><span class="comment">; Ctrl + Enter で改行する</span></span><br><span class="line">^Enter::!Enter</span><br><span class="line"></span><br><span class="line"><span class="comment">; Ctrl + Shift + z でやり直す</span></span><br><span class="line">^+z::^y</span><br><span class="line"></span><br><span class="line"><span class="comment">; alt の２連打でコンテキストメニューを表示する</span></span><br><span class="line">~LAlt:: &#123;</span><br><span class="line">    <span class="comment">; REF: https://ahkscript.github.io/ja/docs/v2/FAQ.htm#DoublePress</span></span><br><span class="line">    if (<span class="name">A_ThisHotkey</span> != A_PriorHotkey || A_TimeSincePriorHotkey &gt; <span class="number">200</span>) &#123;</span><br><span class="line">        return</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Send <span class="string">&quot;&#123;Shift Down&#125;&#123;F10&#125;&#123;Shift Up&#125;&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">#HotIf</span><br><span class="line"><span class="comment">; ============================================</span></span><br><span class="line"><span class="comment">; Slack 専用</span></span><br><span class="line"><span class="comment">; --------------------------------------------</span></span><br><span class="line">#HotIf WinActive(<span class="string">&quot;ahk_exe slack.EXE&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">; ctrl + [ で go back</span></span><br><span class="line">^sc01A::!Left</span><br><span class="line"><span class="comment">; ctrl + ] で go forward</span></span><br><span class="line">^sc02b::!Right</span><br><span class="line"></span><br><span class="line">#HotIf</span><br><span class="line"><span class="comment">; ============================================</span></span><br><span class="line"><span class="comment">; VSCode 専用</span></span><br><span class="line"><span class="comment">; --------------------------------------------</span></span><br><span class="line">#HotIf WinActive(<span class="string">&quot;ahk_exe Code.exe&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment">; ctrl + [ で go back</span></span><br><span class="line">^sc01A::!Left</span><br><span class="line"><span class="comment">; ctrl + ] で go forward</span></span><br><span class="line">^sc02b::!Right</span><br><span class="line"></span><br><span class="line">#HotIf</span><br></pre></td></tr></table></figure></div>

<h4 id="utils-ahk">utils.ahk</h4><figure class="highlight lisp"><table><tr><td class="code"><pre><span class="line">#Requires AutoHotkey v2.<span class="number">0</span></span><br><span class="line"></span><br><span class="line">#SingleInstance Force</span><br><span class="line"></span><br><span class="line">class Utils &#123;</span><br><span class="line">    static isActiveWindow(<span class="name">windowTitle</span>) &#123;</span><br><span class="line">        DetectHiddenWindows(<span class="name">true</span>)</span><br><span class="line">        window := WinExist(<span class="name">windowTitle</span>)</span><br><span class="line">        if (<span class="name">window</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            return true</span><br><span class="line">        &#125;</span><br><span class="line">        return false</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="configs-ahk">configs.ahk</h4><div class="code-block"><figure class="highlight lisp"><input type="checkbox" id="code-wrap-4nfvyz-3" class="code-wrap-input" aria-label="コードの折り返しを切り替える"><label class="code-wrap-label" for="code-wrap-4nfvyz-3" title="コードの折り返しを切り替える"></label><table><tr><td class="code"><pre><span class="line">#Requires AutoHotkey v2.<span class="number">0</span></span><br><span class="line"></span><br><span class="line">class Configs &#123;</span><br><span class="line">    static windowTitles := &#123;</span><br><span class="line">        ule4Jis: <span class="string">&quot;ahk_exe Ule4Jis.exe&quot;</span>,</span><br><span class="line">    &#125;</span><br><span class="line">    static paths := &#123;</span><br><span class="line">        ule4Jis: <span class="string">&quot;C:\\Path\\To\\Ule4Jis.exe&quot;</span>, <span class="comment">; ← 実際のパスをここに記入してください</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div>

<h4 id="lib-IMEv2-ahk">lib&#x2F;IMEv2.ahk</h4><p>https://github.com/k-ayaki/IMEv2.ahk/blob/master/IMEv2.ahk を利用させていただきました。</p>
<h2 id="各種ツール">各種ツール</h2><p>その他導入した便利なツールを紹介します。</p>
<ul>
<li><strong>QL-Win&#x2F;QuickLook: Bring macOS “Quick Look” feature to Windows</strong><ul>
<li>MacのQuickLook的なもの</li>
<li>毎回ファイルを開かなくて良くなる（動作は少し遅いかも）</li>
</ul>
</li>
<li><strong>Power Toys</strong><ul>
<li>Microsoftが出しているユーティリティセット</li>
<li>Power Toys Run<ul>
<li>Macでいうspotlight検索<ul>
<li>MacではRaycastを利用しています</li>
<li>Windowsにもいつか来るかも → Raycast for Windows</li>
</ul>
</li>
<li>EveryThingのプラグインも導入<ul>
<li>lin-ycv&#x2F;EverythingPowerToys: Everything search plugin for PowerToys Run</li>
</ul>
</li>
<li>Windowsで他に使えそうなランチャーアプリ<ul>
<li>Ueli - A Cross-Platform Keystroke Launcher<ul>
<li>おしゃれで良さそう</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>FancyZones(Power Toys)の設定<ul>
<li>左1&#x2F;3と右2&#x2F;3でゾーンを作成して使用</li>
<li>MacではRaycastでの「<code>First Third</code> と <code>Last Two Thirds</code>」や「BTTのスナップエリア」を利用</li>
</ul>
</li>
<li>他にもColor Pickerなど色々あるので必要なものを有効化して利用</li>
<li>参考たサイト<ul>
<li>PowerToys のインストール | Microsoft Learn</li>
<li>情シスがPowerToys全機能をさわってみたよ</li>
</ul>
</li>
</ul>
</li>
<li><strong>Clibor | クリップボード履歴ソフト「Clibor」の公式サイト</strong><ul>
<li>クリップボードアプリ<ul>
<li>MacではBTTのクリップボードを利用</li>
</ul>
</li>
<li>CliborだけでなくWindows公式のクリップボードの履歴も併用<ul>
<li>Cliborではリッチテキストや画像の履歴を保持できないため<ul>
<li>「設定 &gt; システム &gt; クリップボード」でクリップボードの履歴をONにすると使えます</li>
</ul>
</li>
<li>Power ToysにAdvanced Pasteというのもあるのでそちらを利用する手も</li>
</ul>
</li>
<li>設定内容<ul>
<li>画面表示制御<ul>
<li>画面表示時に1行目にフォーカスを移すを <code>ON</code> に</li>
</ul>
</li>
<li>ホットキー<ul>
<li>メイン画面の呼び出し: <code>Win + Ctrl + Alt + J</code><ul>
<li>AutoHotKeyが動いているとうまく設定できない場合があるので、その場合一時停止させる（設定後はAutoHotKeyを戻しても動作する）</li>
</ul>
</li>
<li>MacではBetterTouchToolのクリップボードツールを <code>Ctrl + Option + command + J</code> で開く<ul>
<li>最初は<code>command + Shift + V</code> にしていたが、Google Docsのショートカットなどと被るため、絶対に使わないキーに</li>
</ul>
</li>
</ul>
</li>
<li>Cliborもスタートアップへ登録すると快適</li>
</ul>
</li>
</ul>
</li>
<li><strong>CubeICE</strong><ul>
<li>Macであればダブルクリックでよしなにやってくれるが、Windowsは右クリックから展開しないといけない？</li>
<li>設定を以下のようにすることでMacと同じ操作感に<ul>
<li>保存場所: <code>元のファイルと同じフォルダ</code></li>
<li>解凍後に保存先フォルダを開く: <code>OFF</code></li>
</ul>
</li>
<li>zipのデフォルトアプリをCubeICEに設定する</li>
</ul>
</li>
<li><strong>VS Code</strong><ul>
<li>Scroll Sensitivityを<code>3</code>にする（Editor, Workbenchの両方）<ul>
<li>デフォルトだとトラックパッドでのスクロールが遅いため</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="おわりに">おわりに</h2><p>今回紹介した設定やツールを駆使することで、WindowsをMacのように扱えるようになりました。</p>
<p>Windowsを使い始めて半年が経ちますが、業務終了後や休日にMacで違和感なく作業ができています。</p>
<p><strong>最初はWindows自体と戦っていましたが、今ではWindowsで戦えるようになりました🔥</strong><br>快適な環境になりとてもハッピーです。</p>
<p>みなさんも良いWindowsライフを！🍎</p>
<h2 id="おまけ：Macに導入したWindows的ツール">おまけ：Macに導入したWindows的ツール</h2><p>最後に、逆にMacに導入した便利ツールもご紹介。</p>
<ul>
<li>AltTab</li>
</ul>
<p>普段Macでは、エディタやターミナルを各リポジトリごとに仮想デスクトップに割り当てています。これは使いやすく便利なのですが、<code>command + tab</code>でのアプリ切り替え時に「同じアプリだけど別のウィンドウ」に遷移してしまうことが多く、手間に感じていました。</p>
<p>AltTabを導入することで、1つ前のウィンドウを確実に開いたり、同じアプリ内でもウィンドウを選択できるようになり、かなり快適になりました🎉</p>
]]></content>
    <summary type="html">Windowsを徹底的にMacBookに近づけ、快適な作業環境を構築することにしました。そのプロセスを詳しく紹介します。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="Mac" scheme="https://future-architect.github.io/tags/Mac/"/>
    <category term="VSCode" scheme="https://future-architect.github.io/tags/VSCode/"/>
    <category term="Windows" scheme="https://future-architect.github.io/tags/Windows/"/>
    <category term="キーバインド" scheme="https://future-architect.github.io/tags/%E3%82%AD%E3%83%BC%E3%83%90%E3%82%A4%E3%83%B3%E3%83%89/"/>
    <category term="環境構築" scheme="https://future-architect.github.io/tags/%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89/"/>
  </entry>
  <entry>
    <title>初めての運用引き継ぎ</title>
    <link href="https://future-architect.github.io/articles/20250507a/"/>
    <id>https://future-architect.github.io/articles/20250507a/</id>
    <published>2025-05-06T15:00:00.000Z</published>
    <updated>2025-05-06T15:00:00.000Z</updated>
    <author><name>後藤玲雄</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250507a/undraw_agreement_w6ua.png" alt="" width="800" height="773">

<h2 id="はじめに">はじめに</h2><p>製造エネルギー事業部の後藤です。</p>
<p>初めてシステムの運用引き継ぎを経験しました。その中で得た知見や工夫した点を、「春の入門祭り2025」にあわせて共有します。</p>
<p>システムの運用を引き継ぐ立場の方はもちろん、日常的にシステム開発に携わっている方にも、参考になる点があれば幸いです。</p>
<h2 id="1-システム運用引き継ぎとは？">1. システム運用引き継ぎとは？</h2><p>通常システムは何かしらの目的を果たすために作られ稼働しているはずです。大前提としてシステムが何のために作られ、何を実現することが期待されているのかを理解しておく必要があります。</p>
<p>その目的を達成するために、引き継ぎを行った後でも、滞りなくシステムが運用される必要があります。そのためにも最低限「定常作業対応」「非定常作業対応」、「障害対応」を行える必要があります。</p>
<p>できる限り作業を自動化して「定常作業対応」「非定常作業対応」を減らしていくことは重要ですが、費用対効果の観点から難しい部分があるのも現実です。またどんなに良いプログラムを書いても、連携先のシステムのエラーやネットワークのエラーなど、どうしても障害対応に人の手が必要な場合があります。</p>
<p>システムを一度作って、そこで終わりでないケースがほとんどだと思います。利用しているライブラリなどにセキュリティの問題があればアップデートや修正が必要ですし、さらに高い目標の達成のためにシステムを改善したいという気持ちが湧いてくるのも自然でしょう。</p>
<p>詳細な手順書を用意すれば、ある程度の作業を任せられるようにはなりますが、未知の障害にぶつかった時、機能改修や機能追加を行う時には背景や全体像をしっかり理解していないと歯が立ちません。</p>
<p>運用引き継ぎを行う際に準備しておくべきこと、引き継ぎ手順、工夫した点、注意事項などをお伝えできればと思います。</p>
<h2 id="2-引き継ぎ概要">2. 引き継ぎ概要</h2><p>どんなシステムをどのくらいの期間でどんな手順で引き継いだのか概要をまとめています。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>項目</th>
<th>内容</th>
</tr>
</thead>
<tbody><tr>
<td>システム概要</td>
<td>アジャイル的に開発された業務システム</td>
</tr>
<tr>
<td>主な技術</td>
<td>Go, Python, Vue, Flutter, Terraform, AWS</td>
</tr>
<tr>
<td>データ規模</td>
<td>Job数約100</td>
</tr>
<tr>
<td>引き継ぎ期間</td>
<td>6ヶ月</td>
</tr>
<tr>
<td>会議頻度</td>
<td>- 最初の3ヶ月：日次で1〜1.5時間<br>- 後の3ヶ月：週２〜３回で１〜1.5時間<br>- 随時：週に0〜3時間（障害発生時や質問がある場合）</td>
</tr>
</tbody></table></div>
<ul>
<li>引き継ぎ手順<ul>
<li>キックオフ&amp;概要説明</li>
<li>環境構築説明</li>
<li>システム全体像説明</li>
<li>IF連携説明</li>
<li>定常作業説明&amp;非定常作業説明</li>
<li>障害対応手順説明</li>
<li>定例会議資料作成＆ファシリテーション引き継ぎ</li>
<li>コードリーティング&amp;ハンズオン</li>
<li>タスク引き継ぎ</li>
<li>課題&amp;その他共有</li>
</ul>
</li>
</ul>
<h2 id="3-ドキュメント化の重要性">3. ドキュメント化の重要性</h2><p>運用引き継ぎで何が重要かと問われれば「丁寧なドキュメント整備」と即答します。知りたい情報がドキュメントにまとまっていれば、時間がかかっても業務を進めることができます。</p>
<p>運用引き継ぎのためだけにドキュメントを書くのではなく、日頃から丁寧に整備する意識を持ち、チームで徹底することが重要です。急ぎの作業の時にソースコードさえ修正されていればいいと考える人もいると思いますが、実装の背景や経緯を記載していないと後々調査に時間がかかったり、誰も手がつけられないコードが増えてしまい、開発効率やサービスの質を落とすことにも繋がってしまいます。</p>
<p>ドキュメントの徹底的な整備は正直なところ辛い部分もありますが、結果的に自分たちの助けにもなります。私のチームではソースコードを変更した際には必ずレビュアーが設計書などのドキュメントが修正されたかを確認してPRをApproveするようにしていました。またドキュメントに不備があった場合には積極的に修正するなどチーム内での文化の形成が重要です。当たり前のことをしっかりやることがとても大切です。リンターやAIなど便利なツールも沢山あるので、なるべく楽をしつつドキュメントを整備していけるといいと思います。</p>
<h3 id="ドキュメント管理">ドキュメント管理</h3><p>私のチームでは設計書や開発者向けの情報はGitHubのドキュメントフォルダ配下にまとめていました。参考までに下記のようにドキュメントをまとめていました。</p>
<figure class="highlight txt"><table><tr><td class="code"><pre><span class="line">ドキュメント/</span><br><span class="line">├── 01_システム設計/</span><br><span class="line">├── 02_インフラ設計/</span><br><span class="line">├── 03_フロントエンド設計/</span><br><span class="line">├── 04_フロントエンドアプリ利用ガイド/</span><br><span class="line">├── 05_IF仕様書/</span><br><span class="line">├── 06_データモデル/</span><br><span class="line">├── 07_API設計書/</span><br><span class="line">├── 08_ジョブ設計書/</span><br><span class="line">├── 09_帳票一覧/</span><br><span class="line">├── 10_定型運用/</span><br><span class="line">├── 11_非定型運用/</span><br><span class="line">├── 12_障害対応/</span><br><span class="line">├── 13_リリース対応/</span><br><span class="line">├── 14_開発規約/</span><br><span class="line">└── 15_環境構築/</span><br></pre></td></tr></table></figure>

<h2 id="4-引き継ぎ手順">4. 引き継ぎ手順</h2><p>実際にどのような順序で引き継ぎを行っていったかと工夫した点を説明していきます。大まかな流れは引き継ぎ概要に書いた通りです。</p>
<h3 id="4-1-キックオフ-概要説明">4.1. キックオフ&amp;概要説明</h3><p>実施事項</p>
<ul>
<li>メンバーの自己紹介</li>
<li>会議帯の設定</li>
<li>各種ツール準備<ul>
<li>GitHubリポジトリ招待</li>
<li>AWSアカウント発行</li>
<li>チャットツール招待（Slack, Google Chatなど）</li>
<li>チケット管理ツール招待（Backlog、Jiraなど）</li>
<li>ストレージサービス招待（Google Driveなど）</li>
</ul>
</li>
<li>システムの概要の説明</li>
</ul>
<p>ポイント</p>
<ul>
<li>利用するツールやチャンネル一覧情報はGitHubのトップページに集約</li>
<li>ツール準備はできるだけ早い段階で実施</li>
<li>基本的にドキュメントはGitHubに集約するが、特に重要な箇所や、困った時に参照すべき場所を伝えるため、簡単なスライドも用意</li>
</ul>
<h3 id="4-2-環境構築説明">4.2. 環境構築説明</h3><p>実施事項</p>
<ul>
<li>環境構築手順の説明（どのドキュメントを見ればいいか）</li>
<li>環境構築のサポート</li>
</ul>
<p>ポイント</p>
<ul>
<li>環境構築が終了しないと改修タスクの依頼ができないため優先</li>
<li>GitHubに環境構築用のページを用意</li>
<li>コンテナやMakefileなどを利用して同じ環境を短時間で構築</li>
<li>環境構築の概要の説明をしたら基本はドキュメントに従って実行。サポートが必要な場合は会議を実施</li>
<li>プロキシが存在する場合、その関係で詰まるケース多いので早い時期から対応</li>
</ul>
<h3 id="4-3-システム全体像説明">4.3. システム全体像説明</h3><p>実施事項</p>
<ul>
<li>システム概要図説明<ul>
<li>データの発生源や流れを意識して説明</li>
</ul>
</li>
<li>システム概念図説明<ul>
<li>どのような関連システムがありどのような種類のデータを何のためにやり取りしているか大枠を説明</li>
</ul>
</li>
<li>データモデル説明<ul>
<li>DB定義などどのようなデータを扱っていて、どのように関連しているのか説明。ER図なども有効</li>
</ul>
</li>
<li>特に重要な処理の説明<ul>
<li>システムで一番重要なデータや業務影響が大きい処理から説明</li>
</ul>
</li>
</ul>
<p>ポイント</p>
<ul>
<li>システム構成図の準備<ul>
<li>全てのJobやサービスを記載できると良い。大枠の構成図を１つ作成して詳細部分は複数に分けるのもあり</li>
<li>draw.ioでの作成がおすすめ（.drawio.png形式でマークダウンページに埋め込む）</li>
</ul>
</li>
<li>システム概念図<ul>
<li>IF連携しているシステムを全て記載</li>
<li>全てのやり取りを詳細に記載できなくても良いので、どんな情報を何のために連携するのか記載</li>
</ul>
</li>
<li>用語集の準備<ul>
<li>GitHubのトップページにスプレッドシートのリンクを貼り、スプレッドシートに用語集を作成。不明な用語を書いてもらい解説を記入（まずはリアルタイムでやり取りしやすいスプレッドシートで共有し、後にGitHubへ反映する運用）</li>
</ul>
</li>
<li>データの発生源から順にデータの流れを説明することで大枠の流れを伝える</li>
<li>業務影響が大きい処理を説明する際は、この処理が失敗すると、「○円かかる」「○人の顧客に影響がある」「復旧に○人の作業が必要」など具体的な影響を説明すると伝わりやすい</li>
</ul>
<h3 id="4-4-IF連携説明">4.4. IF連携説明</h3><p>実施事項</p>
<ul>
<li>IF仕様書説明<ul>
<li>IF一覧説明</li>
<li>重要なIF連携説明</li>
</ul>
</li>
</ul>
<p>ポイント</p>
<ul>
<li>最悪詳細な仕様書がなくてもIF一覧は用意<ul>
<li>API連携がある場合、どのエンドポイントをどのシステムが利用しているかまとめられるとなお良い</li>
</ul>
</li>
<li>システム改修時の影響範囲調査の際に利用するのでIF仕様の整理はしっかりと行う</li>
</ul>
<p>（IF一覧例）</p>
<ul>
<li><p>受信</p>
<div class="scroll"><table>
<thead>
<tr>
<th>機能ID</th>
<th>機能名</th>
<th>連携システム</th>
<th>形式</th>
<th>頻度</th>
<th>日時</th>
<th>未着チェック</th>
<th>冪等</th>
<th>Path Prefix</th>
<th>説明</th>
</tr>
</thead>
<tbody><tr>
<td>IF_IN_01</td>
<td>A受信</td>
<td>Aシステム</td>
<td>S3</td>
<td>日次</td>
<td>0:00</td>
<td>○</td>
<td>○</td>
<td>prod-xxx-bucket&#x2F;</td>
<td></td>
</tr>
<tr>
<td>IF_IN_02</td>
<td>B受信</td>
<td>Bシステム</td>
<td>Kinesis</td>
<td>随時</td>
<td>-</td>
<td>-</td>
<td>○</td>
<td>prod-xxx-kinesis</td>
<td></td>
</tr>
</tbody></table></div>
</li>
<li><p>配信</p>
<div class="scroll"><table>
<thead>
<tr>
<th>機能ID</th>
<th>機能名</th>
<th>連携システム</th>
<th>形式</th>
<th>頻度</th>
<th>日時</th>
<th>Path Prefix</th>
<th>説明</th>
</tr>
</thead>
<tbody><tr>
<td>IF_OUT_01</td>
<td>X配信</td>
<td>Xシステム</td>
<td>S3</td>
<td>日次</td>
<td>0:00</td>
<td>prod-system-x-bucket&#x2F;</td>
<td></td>
</tr>
</tbody></table></div>
</li>
</ul>
<h3 id="4-5-定常作業説明-非定常作業説明">4.5. 定常作業説明&amp;非定常作業説明</h3><p>実施事項</p>
<ul>
<li>定常作業説明&amp;実行</li>
<li>非定常作業説明&amp;実行</li>
</ul>
<p>ポイント</p>
<ul>
<li>引き継ぎ前に可能な限り作業の自動化や簡略化を行う</li>
<li>作業一覧と作業手順書をGitHubに作成</li>
<li>できるだけはない段階で引き継ぎ先に実行して慣れてもらう</li>
<li>説明会の内容を録画してGitHubにリンクを貼るのがおすすめ</li>
</ul>
<h3 id="4-6-障害対応手順説明">4.6. 障害対応手順説明</h3><p>実施事項</p>
<ul>
<li>監視設計説明</li>
<li>障害発生時対応方法説明</li>
</ul>
<p>ポイント</p>
<ul>
<li>障害が発生した際にどのように通知されどのような対応が必要なのかを説明</li>
<li>最低過去6ヶ月に発生した障害の対応手順書をGitHubに作成する<ul>
<li>誰が見ても作業できるように画面のスクリーンショットを付ける</li>
<li>引き継ぎ先に内容をレビューしてもらい作業が誰でもできる状態を一緒に目指す</li>
</ul>
</li>
<li>可能であればJobエラーアクションリストを作成し全てのJobの全てのエラーを一覧化する<ul>
<li>スプレッドシートを作成してGitHubにリンクを貼る</li>
<li>全ての対応手順を作成するの難しいので全てを埋める必要はないが、過去どのように障害対応をしたのかわかるようにBacklogチケットなどのリンクを貼ると良い（普段から記録をしっかりと残すことが重要）</li>
</ul>
</li>
<li>障害発生を見逃さぬようにメール送信、Slack通知、担当者設定など工夫が必要<ul>
<li>日次で障害の見過ごしがないか時間を取るのもおすすめ</li>
</ul>
</li>
</ul>
<p>（Jobエラーアクションリスト例）</p>
<div class="scroll"><table>
<thead>
<tr>
<th>Job ID</th>
<th>AWS ID</th>
<th>ロググループ名</th>
<th>ソースコードパス</th>
<th>ログレベル</th>
<th>エラーコード</th>
<th>ログ内容</th>
<th>説明</th>
<th>業務影響</th>
<th>エラー発生から対応までの猶予</th>
<th>対応手順</th>
<th>備考</th>
</tr>
</thead>
<tbody><tr>
<td>API001</td>
<td>arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:prod-xxx-api</td>
<td>&#x2F;aws&#x2F;lambda&#x2F;prod-xxx-api</td>
<td>backend&#x2F;app&#x2F;api</td>
<td>Error</td>
<td>01</td>
<td>xxxx</td>
<td>xxの登録に失敗</td>
<td>ユーザーがxxの登録ができていない</td>
<td></td>
<td>障害対応手順書のGitHubリンク</td>
<td>20xx年の改修以降エラーは発生していない</td>
</tr>
<tr>
<td>API001</td>
<td>arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:prod-xxx-api</td>
<td>&#x2F;aws&#x2F;lambda&#x2F;prod-xxx-api</td>
<td>backend&#x2F;app&#x2F;api</td>
<td>Warn</td>
<td>02</td>
<td>xxxx</td>
<td>xxの取得に失敗</td>
<td>xxのデータが取得できない</td>
<td></td>
<td>障害対応手順書のGitHubリンク</td>
<td>過去の障害対応Backlogチケットのリンク</td>
</tr>
</tbody></table></div>
<h3 id="4-7-定例会議資料作成＆ファシリテーション引き継ぎ">4.7. 定例会議資料作成＆ファシリテーション引き継ぎ</h3><p>実施事項</p>
<ul>
<li>定例会議の資料作成法引き継ぎ</li>
<li>定例会議への参加</li>
<li>会議ファシリテーション引き継ぎ</li>
</ul>
<p>ポイント</p>
<ul>
<li>定例会議で顧客への報告などがある場合は、どのような資料を用意しているのかを共有する（過去の会議の資料はGoogle Driveなどに集約する）</li>
<li>いきなり全てを引き継ぐのでなく、まずは会議の参加から部分的なタスクの進捗報告などから徐々に引き継ぐ</li>
<li>いきなり全てを任せようとすると引き継ぎ先としては辛いので、気持ちに寄り添って伴走する姿勢も重要</li>
</ul>
<h3 id="4-8-コードリーティング-ハンズオン">4.8. コードリーティング&amp;ハンズオン</h3><p>実施事項</p>
<ul>
<li>システム構成と具体的なソースコードの処理内容を説明</li>
<li>開発規約の説明<ul>
<li>テストの実施方針、コードレビューの方針なども含めて説明</li>
</ul>
</li>
<li>リリース手順説明&amp;リリース作業</li>
</ul>
<p>ポイント</p>
<ul>
<li>重要なJobや改修タスクに関連するJobから順にソースコードリーディングを実施<ul>
<li>なぜその実装をしているのか背景をソースコードにコメントがあると良い。GitHubのIssueのリンクをコメントに貼るのもおすすめ。引き継がれる側からするとコメントが充実している方が安心</li>
</ul>
</li>
<li>しっかりとした設計思想があると引き継ぎの際にも説明しやすく、すべての指針となるので、設計思想は重要</li>
<li>リリース作業の引き継ぎは時間をかけて行う<ul>
<li>リリース作業は引き継ぎ先と一緒に行い徐々に作業を任せていく</li>
<li>週に１度リリース作業をしていたが、念のため毎回引き継ぎ期間にはリリース作業に立ち会った</li>
</ul>
</li>
</ul>
<h3 id="4-9-タスク引き継ぎ">4.9. タスク引き継ぎ</h3><p>実施事項</p>
<ul>
<li>機能改修説明</li>
<li>レビュー</li>
<li>リリース</li>
</ul>
<p>ポイント</p>
<ul>
<li>できる限り早い段階で機能改修を任せるようにすると、システムへの理解が進む</li>
<li>GitHubのissueに背景、修正対象のソースコード、対応方法、テスト方法、検証方法などを詳細に記載して、対面でタスクの説明をしてタスクの依頼をする</li>
<li>「困ったことがあればいつでも聞いてください」と伝え続けてよい関係性を作る</li>
<li>対応手順がわかっているタスクであれば、相手に考えさせるのではなく自分ならどうするかと、なぜそうするのかを説明してタスクを進めてもらう<ul>
<li>自分の力で考えてもらうことも時には必要ですが、より良い答えを持っているのであれば、出し惜しみをせずに全て伝えましょう</li>
</ul>
</li>
<li>ソースコードなどのレビューは手を抜かない<ul>
<li>いくら不慣れだからといってレビューを甘くしてしまうと、結果的にサービスの質の低下につながってしまうので、しっかりと気になるところは伝えるようにしましょう</li>
<li>大量の指摘事項をもらう側は辛いと思うので、伝え方には注意しましょう</li>
</ul>
</li>
</ul>
<h3 id="4-10-課題-その他共有">4.10. 課題&amp;その他共有</h3><p>実施事項</p>
<ul>
<li>課題共有（GitHub Issueにまとめて説明）<ul>
<li>ユーザーが数倍に増えた時には○○の改修が必要なども説明できると良い</li>
</ul>
</li>
<li>バージョンアプ対応説明<ul>
<li>プログラミング言語やツールのバージョンアップの方針の説明</li>
</ul>
</li>
</ul>
<p>ポイント</p>
<ul>
<li>課題や問題がある場合には包みかくさずに全てを伝える</li>
</ul>
<h2 id="5-コミュニケーション">5. コミュニケーション</h2><p>最終的にシステムを利用するのも運用していくのも人であり、システムの運用を引き継ぐのも人になるので、コミュニケーションは欠かせません。最後に引き継ぎにおいて特に重要だと思うコミュニケーション面のお話を共有します。</p>
<h3 id="引き継がれる側は他の人が育てた未知のシステムの面倒を見ることになると理解する">引き継がれる側は他の人が育てた未知のシステムの面倒を見ることになると理解する</h3><p>システムを引き継ぐ側としては背景や問題点など熟知した身近な存在かもしれませんが、引き継がれる側からするとそうではありません。</p>
<p>引き継がれる側としては、いつ機嫌を悪くして暴れ回るかもわからない、性格も機嫌の取り方もわからない猛獣に見えるかもしれません。暴れ回った責任を育ててもいない自分達が負わなければならないという不安を抱えているかもしれません。</p>
<p>そんな相手の気持ちを理解して、システムの特徴や問題点などを根気よく説明していく必要があります。引き継ぎ元の担当者がしっかりとした人間だと思ってもらえれば、その人が見ているシステムもしっかりしていると思って貰いやすくなるので初めのコミュニケーションは大切です。</p>
<p>相手の中にある不安要素を理解して、それに寄り添いながらコミュニケーションを取っていこうという姿勢がとても大切だと思います。</p>
<h3 id="いつでも情報を共有してもらうように関係性を構築する">いつでも情報を共有してもらうように関係性を構築する</h3><p>円滑に引き継ぎを進めていく上で相手が何を理解できていなかを把握することはとても重要です。物事を理解しないままタスクを進めてしまうと、時間の無駄が発生したり障害を発生させてしまったりマイナスな影響を与える可能性があります。</p>
<p>相手がわからないことがあればすぐに解決できる体制を作ることで、システムへの理解度、タスクの進捗、信頼度を上げることができるので、「わからないことがあればいつでも聞いてください」と引き続き期間中に言い続けていました。困っていそうな時にはこちらから確認して会議を設定したり、チャットで情報共有をすることで、コミュニケーションを円滑に行うことができたと思います。</p>
<p>運用引き継ぎというプロジェクトを成功させるためにお互いに協力してゆくことがとても大切です。</p>
<h3 id="3度説明しただけで理解してもらおうという思いは捨てる">3度説明しただけで理解してもらおうという思いは捨てる</h3><p>引き継ぎ期間中、「前にも同じ説明を何度かしたな」と思うことが複数回ありました。もちろんドキュメントにも同じ説明を記載していますが、会話ベースで説明した方が伝わりやすいと思うことが多々ありました。</p>
<p>３度同じ説明をしたら理解して欲しいという気持ちはよくわかるのですが、引き継ぎ先としては日々膨大なインプットを行い情報の紐付けと整理に苦労していることを理解する必要があります。</p>
<p>３度同じ説明をしたら理解して欲しいではなく、15回同じ説明を別のアプローチで行いなんとか相手の頭の中に情報を整理してもらおうとする姿勢が大切だと思います。タスクを通して理解を進めるのが一番の近道だとは思いますが、根気よく重要な情報は複数回共有するべきたど思います。関係性ができてきたらクイズ大会を実施するのもいいと思います。</p>
<h3 id="引き継ぎ前と引き継ぎ後で同じパフーマンスが出ないことを理解してもらう">引き継ぎ前と引き継ぎ後で同じパフーマンスが出ないことを理解してもらう</h3><p>長く引き継ぎの期間を取ったとしても、引き継ぎ後に引き継ぎ前と同じパフォーマンスを出すことは難しいことが多いです。ステークホルダーにはそのことを根気強く説明して理解してもらうことが大切です。</p>
<h2 id="まとめ">まとめ</h2><p>システムの運用引き継ぎで学んだことを共有しました。</p>
<p>普段から自動化し、ドキュメントを整備し、情報を集約することで、引き継ぎの有無に関係なく業務を効率化できると思います。有識者が不在な場合や移動になった場合にも、滞りなく業務が進められる必要があるので、今回まとめた内容を参考にしていただければと思います。ドキュメントの整理など当たり前のことをしっかり続けていくことが大切です。辛い部分もあるかもしれませんが結果的に自分達のためになると思います。</p>
<p>またコミュニケーションについてもお話ししましたが、一番大切なのは相手の気持ちを理解しつつゴールに向かって互い協力し合うことが大切だと思います。</p>
<p>最後まで読んでいただきありがとうございました。</p>
]]></content>
    <summary type="html">初めてシステムの運用引き継ぎを経験しました。その中で得た知見や工夫した点を、共有します。システムの運用を引き継ぐ立場の方はもちろん、日常的にシステム開発に携わっている方にも、参考になる点があれば幸いです。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="テクニカルライティング" scheme="https://future-architect.github.io/tags/%E3%83%86%E3%82%AF%E3%83%8B%E3%82%AB%E3%83%AB%E3%83%A9%E3%82%A4%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0/"/>
    <category term="ドキュメント" scheme="https://future-architect.github.io/tags/%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88/"/>
    <category term="保守運用" scheme="https://future-architect.github.io/tags/%E4%BF%9D%E5%AE%88%E9%81%8B%E7%94%A8/"/>
  </entry>
  <entry>
    <title>コードレビューガイドラインを公開しました</title>
    <link href="https://future-architect.github.io/articles/20250502a/"/>
    <id>https://future-architect.github.io/articles/20250502a/</id>
    <published>2025-05-01T15:00:00.000Z</published>
    <updated>2025-05-01T15:00:00.000Z</updated>
    <author><name>村田靖拓</name></author>
    <content type="html"><![CDATA[<p><img fetchpriority="high" src="/images/2025/20250502a/image.png" alt="" width="1200" height="796"></p>
<p>こんにちは、村田です。</p>
<p>有志の社員が集まってコードレビューガイドラインを作成、公開したのでご紹介します。</p>
<h2 id="本ガイドラインを読むにあたって">本ガイドラインを読むにあたって</h2><p>コードレビューガイドラインは、既に公開しているGitブランチフロー規約にて推奨されているブランチ戦略や設定が施されていることを想定して作成されています。</p>
<p>前提条件の章を確認の上で本編をご確認ください。</p>
<h2 id="ガイド作成の背景">ガイド作成の背景</h2><p>みなさんは普段どのよう心構えでレビューをしていますか？</p>
<p>フューチャーでも当然毎日のようにたくさんのコードレビューが実施されているのですが、実際問題としてコードレビューにおける立ち振舞いはチームや人によってマチマチであることが多いです。もちろん世間でもそういうケースは多く散見されるだろうなと思います。</p>
<p>「レビューはコミュニケーションである。コメント記載時には相手への敬意を忘れないこと」</p>
<p>これは今回作成したガイドラインに明記したグラウンドルールです。私が一番好きなパートでもあります。改めて考えてみれば当然なのですが、この当たり前を忘れてしまっているレビューがたまに見受けられるのもまた事実。レビューのキャッチボールにおいて双方の発言や行動をあとほんの少しだけアップデートしてあげればよりスムーズなキャッチボールになるのにな…なんて場面、みなさんも多かれ少なかれ思い当たるのではないでしょうか？</p>
<p>ゆえに今回作成したガイドラインでは、「レビュイー」および「レビュアー」それぞれの立場（役割）において推奨される行動をまとめていくスタイルをとりました。</p>
<p>ちなみに、今回のガイドラインの中ではレビュー観点について網羅的に列挙・記載することは行っていません。開発しているプロダクトの性質・開発体制・採用技術などで観点が大きく変動するためです。</p>
<h2 id="レビュイーとレビュアー双方の「推奨行動」">レビュイーとレビュアー双方の「推奨行動」</h2><p>ここからは少しガイドラインの中身をご紹介します。この記事においては焦点を絞って簡単にかいつまむ形で紹介しますが、ぜひ詳細はガイド本体をご覧いただけると幸いです。</p>
<h3 id="レビュイーの推奨行動">レビュイーの推奨行動</h3><p>本章における記載はマインド面よりも実務的な内容が多いです。つまり読んだ直後から実践可能な内容が多く含まれています。</p>
<h4 id="プルリクエストの単位を小さくする">プルリクエストの単位を小さくする</h4><p>レビュアーの負荷を必要以上に高くしてしまう行動のひとつに「複数の改修内容を単一のプルリクエストに含む」ことが挙げられます。</p>
<p>あくまでプルリクエストは小さく保つように心がけましょう。</p>
<h3 id="プルリクエストのタイトルにプレフィックスを入れる">プルリクエストのタイトルにプレフィックスを入れる</h3><p>プルリクエストのタイトルに命名規則をもたせることで、レビュアーの認知負荷を軽減することに繋がります。例えば新機能追加であれば <code>feat</code> など、決まった文言をつけるルールをチーム内で決めておくと良いでしょう。</p>
<h3 id="レビュアーの推奨行動">レビュアーの推奨行動</h3><p>レビュイーの推奨行動がより具体的なアクションについての記載だったのに対し、レビュアーの推奨行動は比較的マインドセットや心構えについての記載が多めです。読んで即実践、というよりも、読んでその意図を理解しつつ日々の行動を少しずつアップデートしていく、といった使い方をしてもらえればと思います。</p>
<h4 id="コメント記載時には相手への敬意を忘れないこと">コメント記載時には相手への敬意を忘れないこと</h4><p>先の章でも触れましたが、このパートが私は一番好きです。レビューは相手を詰問する場ではありません。より良いものをチーム一丸となって作っていく、その意識を忘れないようにしましょう。</p>
<h4 id="指摘内容は具体的であればあるだけ良い">指摘内容は具体的であればあるだけ良い</h4><p>時間に追われるレビュアーが簡素なコメントのみでレビューを終わらせてしまうケースがあると思います。もしレビュイーがその指摘から具体的な修正方針を読み取れなかった場合、結局追加のコミュニケーションラリーが発生してしまいますし、往々にしてそういった場面ではレビュイーがその指摘の意図を再度レビュアーへ伺うこと自体の心理的負荷が高くなってしまいがちです。</p>
<p>指摘内容はできる限り具体的に記載されている方が建設的です。</p>
<h2 id="まとめ">まとめ</h2><p>さて、今回はコードレビューガイドラインを紹介させていただきました。このガイドラインが、皆さんのチームにおける開発体験の向上に繋がることを期待しています。ぜひご活用ください！</p>
<p>▼ フューチャーアーキテクト コードレビューガイドラインはこちら<br>https://future-architect.github.io/arch-guidelines/documents/forCodeReview/code_review.html</p>
<p>ガイドラインに関するご意見やフィードバックも、もちろんウェルカムです。お待ちしております。</p>
]]></content>
    <summary type="html">有志の社員が集まってコードレビューガイドラインを作成、公開したのでご紹介します。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="ガイドライン" scheme="https://future-architect.github.io/tags/%E3%82%AC%E3%82%A4%E3%83%89%E3%83%A9%E3%82%A4%E3%83%B3/"/>
    <category term="コミュニケーション" scheme="https://future-architect.github.io/tags/%E3%82%B3%E3%83%9F%E3%83%A5%E3%83%8B%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3/"/>
    <category term="コードレビュー" scheme="https://future-architect.github.io/tags/%E3%82%B3%E3%83%BC%E3%83%89%E3%83%AC%E3%83%93%E3%83%A5%E3%83%BC/"/>
    <category term="チーム開発" scheme="https://future-architect.github.io/tags/%E3%83%81%E3%83%BC%E3%83%A0%E9%96%8B%E7%99%BA/"/>
  </entry>
  <entry>
    <title>Japan Datadog User Group Meetup#8@札幌に登壇しました</title>
    <link href="https://future-architect.github.io/articles/20250307a/"/>
    <id>https://future-architect.github.io/articles/20250307a/</id>
    <published>2025-03-06T15:00:00.000Z</published>
    <updated>2025-03-06T15:00:00.000Z</updated>
    <author><name>棚井龍之介</name></author>
    <content type="html"><![CDATA[<img fetchpriority="high" src="/images/2025/20250307a/0c1ad1a65e3a410ecd85e9d1291a6a02.png" alt="" width="660" height="377">

<h2 id="はじめに">はじめに</h2><p>こんにちは。tanai（棚井龍之介）です。<br>2025年からは、FutureVuls の SRE 領域で活動しています。</p>
<p>少し前に、Connpass のイベント を眺めていたところ、Datadog のユーザ会が「<strong>札幌現地のみ、オフライン開催！</strong>」との見出しを見つけました。</p>
<p>最近、担当サービスへの <strong>Datadog 導入に成功</strong> しまして、その「導入成功に至るまでのプロセス」をなんらかの方法でナレッジ化しておきたいと考えていました。なので、このイベントを契機に「自分の経験の言語化する」-&gt;「スライド作成を通してナレッジ化する」の経験学習サイクルを回しました。また、「<strong>オフライン限定</strong>」という利点を活かして、オンライン環境では話せないような「<strong>SaaS運用保守担当者としてのリアルな苦労話</strong>」にまで踏み込んだ内容となり、Datadog に限らず「オブザーバビリティツール導入時の、胃がキュッとなるような実体験談」を共有できました。</p>
<br>

<p>会場は、クラスメソッド株式会社さんの「札幌オフィス」でした！</p>
<img src="/images/2025/20250307a/スクリーンショット_2025-03-07_9.26.32.png" alt="スクリーンショット_2025-03-07_9.26.32.png" width="1200" height="722" loading="lazy">

<br>

<p>イベント内容などは「Japan Datadog User Group Meetup#8@札幌 のページ」をご覧ください。</p>
<p>イベント当日のタイムテーブルはこちらでした。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>Time</th>
<th>Contents</th>
<th>Speaker</th>
</tr>
</thead>
<tbody><tr>
<td>18:30-19:00</td>
<td>開場</td>
<td>-</td>
</tr>
<tr>
<td>19:00-19:10</td>
<td>Opening</td>
<td>JDDUG 運営</td>
</tr>
<tr>
<td>19:10-19:25</td>
<td>発表1. Datadogのコスト周りのお話</td>
<td>アノテーション株式会社 あのふじた</td>
</tr>
<tr>
<td>19:25-19:40</td>
<td>発表2. Software Catalog や Scorecard について</td>
<td>Datadog Japan 合同会社 逆井 啓佑</td>
</tr>
<tr>
<td>19:40-19:45</td>
<td>休憩</td>
<td>-</td>
</tr>
<tr>
<td>19:45-20:00</td>
<td>発表3. Datadogクラウドコストマネジメント使ってみた</td>
<td>株式会社Goals 今村 光希</td>
</tr>
<tr>
<td>20:00-20:15</td>
<td>発表4. 現場の課題分析からDatadog導入に繋げた話</td>
<td>tanai（棚井龍之介）</td>
</tr>
<tr>
<td>20:15-20:35</td>
<td>休憩 &amp; ネットワーキングタイム</td>
<td></td>
</tr>
<tr>
<td>20:35-20:40</td>
<td>LT1. Lambdaの監視、できていますか？</br>datadogを用いてLambdaを見守ろう</td>
<td>2357gi &#x2F; Kento Ohgi</td>
</tr>
<tr>
<td>20:40-20:45</td>
<td>LT2. Azure Native ISV Services「Datadog」</td>
<td>いわさ</td>
</tr>
<tr>
<td>20:45-20:50</td>
<td>LT3. Datadog On-Callを試してみた</td>
<td>中川翔太</td>
</tr>
<tr>
<td>20:50-20:55</td>
<td>SRE NEXT宣伝</td>
<td>山口隆史</td>
</tr>
<tr>
<td>20:55-</td>
<td>クロージング &amp; 記念撮影</td>
<td>みんな</td>
</tr>
</tbody></table></div>
<br>

<p>私の登壇内容について、一部のみとなりますが抜粋してご紹介します。</p>
<h2 id="現場の課題分析からDatadog導入に繋げるまでの話">現場の課題分析からDatadog導入に繋げるまでの話</h2><img src="/images/2025/20250307a/スクリーンショット_2025-03-07_7.43.19.png" alt="" width="1200" height="674" loading="lazy">

<br>

<p>今回は、オブザーバビリティツールの利用状況を「利用開始前」と「利用開始後」の2つに分けた上で、今回は「<strong>利用開始前の状況から、Datadog 導入に至るまで</strong>」の部分にフォーカスしました。</p>
<p>オライリー本の「オブザーバビリティ・エンジニアリング」など、可観測性がいかに重要か、なぜ重要かを説く本は多数あります。ただし、それらの内容は、書籍という制約上どうしても「一般的な話」に留まってしまうか、もしくは、ビックテックの事例集になっている感があります。Datadog などの「<strong>実際のところ、業務を回すのに必須とは言い難い</strong>」ツールを導入した担当者であれば、誰もが「（予算獲得に向けた社内説得などでの）個別の苦労話」を抱えているはず（ありますよね？）と思い、その部分にフォーカスしました。</p>
<img src="/images/2025/20250307a/スクリーンショット_2025-03-07_7.43.42.png" alt="" width="1200" height="675" loading="lazy">

<br>

<p>以下のスライドは、プロジェクトの前提条件から、どのような経緯で監視ツールの必要性を強く意識するようになったかを説明しています。</p>
<p>私の場合は、「<strong>サービスがスケールし始めた地点</strong>」から、チーム体制が「役割分担せずに、全員で取り組む」配置から「各領域ごとに、専任の担当者を決める」体制へと移行し始めました。それぞれが専門性を深める中で、「業務効率化の一環」として、監視ツールの必要性を感じ始めました。</p>
<p>※本ブログでは「オブザーバビリティツール」と「監視ツール」の表記揺れがありますが、どちらも同じ意味で利用しています。</p>
<img src="/images/2025/20250307a/スクリーンショット_2025-03-07_7.44.06.png" alt="" width="1200" height="674" loading="lazy">

<br>

<p>その中で私が強く実感したのは「<strong>情報不足</strong>」という点です。</p>
<p>チーム体制が「役割分担せずに、全員で取り組む」のであれば、「<strong>オレが知らないことは、アイツが知っている</strong>」で乗り切れますし、以心伝心とは言わないまでもツーカーでの意思疎通が可能です。この体制がうまく回っている間は、コミュニケーションコストが少ないので圧倒的なアウトプットに繋がるのだとも思いますし、最新の状況を自動トレースできないドキュメントは「むしろ不要」になります。ただし、メンバー増員によりチームがスケールし始めると「<strong>この情報は、誰が知っているんだ？</strong>」のルーティング処理に工数が取られ始めて、ドキュメンテーションへのニーズが高まります。</p>
<img src="/images/2025/20250307a/スクリーンショット_2025-03-07_7.44.24.png" alt="" width="1200" height="671" loading="lazy">

<br>

<p>そこで私は、以下の3ポイントを「解決したい課題リスト」として設定し、オブザーバビリティツールの導入により解決を試みました。</p>
<ul>
<li>課題リスト<ul>
<li><strong>必要な情報が、どこにあるのか分からない</strong></li>
<li><strong>情報はあるのに、必要な情報に辿り着けない</strong></li>
<li><strong>情報が断片的で、全体像を把握できない</strong></li>
</ul>
</li>
</ul>
<br>

<p>導入候補のツールとしては、以下3つのサービスを検討しました。</p>
<ul>
<li>Datadog</li>
<li>New Relic</li>
<li>Dynatrace</li>
</ul>
<img src="/images/2025/20250307a/スクリーンショット_2025-03-07_7.44.56.png" alt="スクリーンショット_2025-03-07_7.44.56.png" width="1200" height="675" loading="lazy">

<br>

<p>各サービスの用意する「計装ツール」を自分の環境に組み込み、コンソール画面での表示状況を確認しながら、課題リストの問題が解決できるかを検証していきます。</p>
<p>計装処理を実装するには、マニュアルに記載された「基本的な設定方法」を正しく理解した上で「自環境に合わせたカスタマイズ」が必要になります。実装上難しいポイントや質問事項などは、各社のシステムエンジニア、カスタマーサポートに伴走いただきました。多数の質問へクイックに回答いただき、大変感謝しております。</p>
<br>

<p>また、トライアル期間中には、Datadog の「Datadog Summit Tokyo」に参加したり、New Relic の「New Relic実践入門 第2版 オブザーバビリティの基礎と実現」を読むことで「基本機能やツール利活用方法」をインプットしました。</p>
<img src="/images/2025/20250307a/スクリーンショット_2025-03-07_7.45.14.png" alt="" width="1200" height="675" loading="lazy">

<br>

<p>オブザーバビリティツールは「<strong>導入後も長い付き合いとなる</strong>」ことが明確なので、ツールの検証は「ノリと勢い」ではなく「事実と論理」を土台に、1つ1つの疑問点を解消しながら進めていきました。各社のサービスには「知名度」や「人気度」がありますが、重要な判断指標は「<strong>そのサービスが自社に適したツールだと、根拠を持って話せるか</strong>」です。このポイントで自信が持てないと、トライアル後の「（予算交渉を含めた）社内説得フェーズ」にて、「なんでそのツールが必要なんだっけ？」や「他はどうなの？」というツッコミに回答できず、それまでの苦労が水疱になります。</p>
<img src="/images/2025/20250307a/スクリーンショット_2025-03-07_7.45.32.png" alt="" width="1200" height="672" loading="lazy">

<br>

<p>最後に、Datadog の導入にあたり、私が重要だと思うポイントを記載しました。</p>
<img src="/images/2025/20250307a/スクリーンショット_2025-03-07_7.46.23.png" alt="" width="1200" height="675" loading="lazy">

<br>

<p>登壇内容では「Datadog 導入に至るまで」のみフォーカスを当たため、「<strong>Datadog 導入後</strong>」の話までは触れられませんでした。このポイントは、今後のユーザ会やブログで発信していく予定です。Datadog の導入により「Ops + SRE 業務の効率化」だけにとどまらず、開発速度の向上や、CS（カスタマーサクセス）活動での利用にもつながっているため、今回の監視ツールは「導入成功」です。</p>
<h2 id="おわりに">おわりに</h2><p>社外イベントは「<strong>同じ悩みを持つエンジニア</strong>」と苦労話を共有する機会になり、自分自身への刺激とのモチベーションアップにも繋がるため、今後も機会を見つけて積極的に登壇していきたいと思いました！</p>
<br>

<p>また、今回のイベント登壇は、以前に 渋川さん の「継続的なアウトプットはなぜよいか？ 著作も数多いエンジニアが語る、社外向け発表がチームまで成長させる話」を読んで刺激を受けていた（<del>それと、イベントに登壇すれば会社経費で東京から北海道に行ける</del>）ので、まずは枠を押さえてから発表ネタを考えた方式です。</p>
<p>ラーニングピラミッドにもあるように、イベント登壇の「他の人に教える」というアウトプットの機会を駆動にすることが、経験の継続的なナレッジ化として有効だと改めて実感しました。</p>
<img src="/images/2025/20250307a/learning_pyramid-1.jpg" alt="learning_pyramid-1.jpg" width="800" height="600" loading="lazy">

<p>出典: ラーニングピラミッド キャリア教育ラボ</p>
<br>

<p>以上、Japan Datadog User Group Meetup#8@札幌 への登壇レポートでした！<br>今後とも、JDDUG コミュニティの皆様方、よろしくお願いします。</p>
<img src="/images/2025/20250307a/IMG_5957.jpg" alt="IMG_5957.jpg" width="1200" height="900" loading="lazy">
]]></content>
    <summary type="html">最近、担当サービスへの Datadog 導入に成功しまして、その「導入成功に至るまでのプロセス」をなんらかの方法でナレッジ化しておきたいと考えていました</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="Datadog" scheme="https://future-architect.github.io/tags/Datadog/"/>
    <category term="オブサーバビリティ" scheme="https://future-architect.github.io/tags/%E3%82%AA%E3%83%96%E3%82%B5%E3%83%BC%E3%83%90%E3%83%93%E3%83%AA%E3%83%86%E3%82%A3/"/>
    <category term="登壇レポート" scheme="https://future-architect.github.io/tags/%E7%99%BB%E5%A3%87%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88/"/>
  </entry>
  <entry>
    <title>【JIS配列Mac】Chrome, Alfred 卒業!? Arc, Zen Browser, Raycast, Karabiner-Elements で開発環境を再構築</title>
    <link href="https://future-architect.github.io/articles/20250225a/"/>
    <id>https://future-architect.github.io/articles/20250225a/</id>
    <published>2025-02-24T15:00:00.000Z</published>
    <updated>2025-02-24T15:00:00.000Z</updated>
    <author><name>棚井龍之介</name></author>
    <content type="html"><![CDATA[<p>JIS 配列 Mac ユーザーの皆さん、今の環境に満足していますか？<br>ブラウザやランチャーアプリには何を使っていますか？</p>
<p>今日は思い切って、長年連れ添った Chrome と Alfred から、Arc, Zen Browser や Raycast に切り替えた環境構築をご紹介します。<br>まるで愛車を最新スポーツカーに乗り換えるような、劇的な変化を体験してください。</p>
<h2 id="はじめに">はじめに</h2><p>こんにちは。棚井（tanai）です。<br>普段の業務ではバックエンド領域を担当しております。</p>
<p>これまで「諸般の事情」により、Windows 環境でコーディングや通常業務を遂行していました。念願叶って、ついに、2年ぶりに、まったくの偶然により、<strong>Mac 環境に戻りました</strong>。</p>
<p>業務外ではバリバリの JIS配列Macユーザとして生態系を築いており、↑のブログにも記載したように「Windows の操作性を JIS配列Mac に近づける」くらいには「JIS配列Mac」に拘りがあります。特に「Emacs キーバインド（Mac のキーボードショートカット）」を無意識レベルで習得している（日本人にとって日本語の文法を説明するのが難しいのと似た感覚で、自分が打ち込んだキーボードショートカットを意識レベルでは認知できていない）ため、他のキー配置では「英数かなの切り替え」でさえストレスを感じてしまうレベルです。<br><br></p>
<p>これまでの Windows 環境（メモリ16GB、ストレージ256GB）から、新たな Mac 環境（メモリ64GB、ストレージ1TB）への環境移行を進める中で、</p>
<p>「なんか、いつもの Mac と操作感と違う」<br>-&gt;「そういえば、こんな設定を入れていた」</p>
<p>「そうそう、このアプリが便利だよね」<br>-&gt;「さらに便利なアプリが見つかった」</p>
<p>という発見がありましたので、「3日後の自分は他人」の箴言に従って「JIS配列Mac の環境構築内容」をブログ化しました。</p>
<h2 id="TL-DR">TL;DR</h2><ul>
<li>マウス・トラックパットを設定する</li>
<li>ターミナルを設定する</li>
<li>ランチャー +α を設定する</li>
<li>Web ブラウザを設定する</li>
<li>キーマッピングを設定する</li>
</ul>
<div class="scroll"><table>
<thead>
<tr>
<th>#</th>
<th>設定対象</th>
<th>Before</th>
<th>After</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>マウス・トラックパット</td>
<td>-</td>
<td>・最速にする</td>
</tr>
<tr>
<td>2</td>
<td>ターミナル</td>
<td>-</td>
<td>・iTerm2<br>・.zshrc</td>
</tr>
<tr>
<td>3</td>
<td>ランチャー+α</td>
<td>・Aflred<br>・Clibor<br>・Shiftlt</td>
<td>・Raycast</td>
</tr>
<tr>
<td>4</td>
<td>ブラウザ</td>
<td>・Google Chrome</td>
<td>・Arc<br>・Zen Browser<br>・Google Chrome</td>
</tr>
<tr>
<td>5</td>
<td>キーマッピング</td>
<td>-</td>
<td>・Karabiner-Elements（Advanced Keymap for JIS Keyboard: Project “UTILITY”）</td>
</tr>
</tbody></table></div>
<h2 id="マウス・トラックパッド">マウス・トラックパッド</h2><p>Macの環境構築に着手して、まず初めに思ったのが「あれ、カーソルを動かすのに、こんなに指をスクロールしていたっけ？」という違和感です。まずはこの感覚から調整していきます。</p>
<p>システム設定 &gt; アクセシビリティ &gt; ポインタコントロール &gt; マウスとトラックパッド に…</p>
<ul>
<li>トラックパッドオプション</li>
<li>マウスオプション</li>
</ul>
<p>のボタン2つがありますので、いずれも「スクロールの速さ」を「最速」に変更しました。</p>
<img fetchpriority="high" src="/images/2025/20250225a/ポインタコントロール設定.png" alt="ポインタコントロール設定" width="1200" height="1073">

<img src="/images/2025/20250225a/スクロール最速.png" alt="スクロール最速" width="1200" height="1073" loading="lazy">

<p>また、システム設定 &gt; トラックパッド &gt; ポイントとクリック には…</p>
<ul>
<li>軌跡の速さ</li>
<li>クリック</li>
</ul>
<p>のバーが2つあります。<br>「軌跡の速さ」は「最速」に、クリックは「弱い」に変更しました。</p>
<img src="/images/2025/20250225a/軌跡の速さを最速、クリックを弱いに変更.png" alt="軌跡の速さを最速、クリックを弱いに変更" width="1200" height="1073" loading="lazy">

<p>クリックを「弱い」に設定したのは「Mac のトリプリクリック」の検知感度を上げるためです。</p>
<p>Mac のテキスト選択には、ダブルクリックで「単語の選択」が、トリプリクリックで「段落の選択」が可能です。ある文章の全体をコピーしたい時に、Chrome 拡張機能の「Copy on Select」などの選択範囲をクリップボードに貼り付ける機能を有効化してから、トラックパッドを3連打してテキスト行全体を選択すれば、クリップボードへの文章の取り込みが一瞬で完了します。トラックバッドでスクロールしながら、気になった場所はトリプルクリックでクリップボードに貼り付け、（後に紹介する）Raycast でクリップボードの履歴を管理するまでがワンセットです。</p>
<h2 id="ターミナル">ターミナル</h2><p>続いて、プログラマーにとって重要な「コマンド環境」を用意していきます。</p>
<p>Macにはデフォルトで「ターミナル」が搭載されています。</p>
<p>「コマンドが実行できる環境」という意味ではターミナルで十分ではあります。ただし、エンジニアの開発支援や「作業モチベーション」を考えると、よりハイスペックなターミナルが欲しくなってきます。</p>
<p>今回は「iTerm2」と「Hyper」の2つを比較の上で、前者の「iTerm2」をメインターミナルとして設定しました。</p>
<h3 id="iTerm2">iTerm2</h3><img src="/images/2025/20250225a/iTerm2ロゴ.jpeg" alt="iTerm2ロゴ" width="1200" height="468" loading="lazy">

<p>サイトからインストール後、Profiles の内容を自分好みに調整していきます。</p>
<p>デフォルトの profile は残しておきたいので、新しい profie を <code>custom</code> という名前で作成して、以下の設定値に更新しました。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>一覧</th>
<th>項目</th>
<th>設定値</th>
</tr>
</thead>
<tbody><tr>
<td>Colors</td>
<td>Color Presets…</td>
<td>Smoooooth</td>
</tr>
<tr>
<td>Text</td>
<td>Font</td>
<td>15</td>
</tr>
<tr>
<td>Window</td>
<td>Settings for New Windows</td>
<td>Columns: 120<br>Rows: 30</td>
</tr>
<tr>
<td>Terminal</td>
<td>Scrollback Buffer</td>
<td>Unlimited scrollback</td>
</tr>
</tbody></table></div>
<h4 id="Colors">Colors</h4><img src="/images/2025/20250225a/1.png" alt="" width="1200" height="817" loading="lazy">

<h4 id="Text">Text</h4><img src="/images/2025/20250225a/2.png" alt="" width="1200" height="675" loading="lazy">

<h4 id="Window">Window</h4><img src="/images/2025/20250225a/3.png" alt="" width="1200" height="811" loading="lazy">

<h4 id="Terminal">Terminal</h4><img src="/images/2025/20250225a/4.png" alt="" width="1200" height="831" loading="lazy">

<p>私が iTerm2 に加えた「見栄えの設定値」は以上です。</p>
<h3 id="zshrc">.zshrc</h3><p>ターミナル操作の簡略化と、表示情報をリッチ化するために <code>.zshrc</code> ファイルへ追記していきます。</p>
<p>設定内容は概ね、macOS の zsh ではこれだけはやっておこう の内容を踏襲しました。今後、作業を進めながら必要に応じて、徐々に追記してく予定です。</p>
<p>コーディング内で頻繁に利用することが分かっているコマンドは、これまでに使い慣れた <code>alias</code> を追加しています。以下に一部だけ添付しました。<code>alias</code> もコマンドショートカット同様に、使いこなすと生産性アップに直結して非常に便利です。唯一の難点としては、<code>$ history</code> でコマンド実行履歴を共有する場合のコミュニケーションが難しくなることくらいです。ちなみに、以下のサンプルで <code>git switch</code> が　<code>gc</code> になっている理由は、もともと <code>git checkout</code> でブランチを切り替えていた頃の名残です。</p>
<figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="comment"># alias for command line</span></span><br><span class="line"><span class="built_in">alias</span> ll=<span class="string">&#x27;ls -la&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># alias for git</span></span><br><span class="line"><span class="built_in">alias</span> ga=<span class="string">&#x27;git add&#x27;</span></span><br><span class="line"><span class="comment">#alias gc=&#x27;git checkout&#x27;</span></span><br><span class="line"><span class="built_in">alias</span> gc=<span class="string">&#x27;git switch&#x27;</span></span><br><span class="line"><span class="built_in">alias</span> gm=<span class="string">&#x27;git commit -m&#x27;</span></span><br><span class="line"><span class="built_in">alias</span> gl=<span class="string">&#x27;git log&#x27;</span></span><br><span class="line"><span class="built_in">alias</span> gf=<span class="string">&#x27;git fetch&#x27;</span></span><br><span class="line"><span class="built_in">alias</span> gp=<span class="string">&#x27;git pull&#x27;</span></span><br><span class="line"><span class="built_in">alias</span> gb=<span class="string">&#x27;git branch&#x27;</span></span><br><span class="line"><span class="built_in">alias</span> gd=<span class="string">&#x27;git diff&#x27;</span></span><br><span class="line"><span class="built_in">alias</span> gs=<span class="string">&#x27;git status&#x27;</span></span><br><span class="line"><span class="built_in">alias</span> gph=<span class="string">&#x27;git push origin HEAD&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># alias for docker</span></span><br><span class="line"><span class="built_in">alias</span> d=<span class="string">&#x27;docker&#x27;</span></span><br><span class="line"><span class="built_in">alias</span> dc=<span class="string">&#x27;docker compose&#x27;</span></span><br></pre></td></tr></table></figure>

<p>私の iTerm2 では、ここまでの設定を加えた時点で、以下のような見栄えになりました。スタート地点の白黒ターミナルと比較すると、リッチ感が増して作業モチベにもいい感じです。</p>
<img src="/images/2025/20250225a/リッチな感じのiTerm2のコンソール.png" alt="リッチな感じのiTerm2のコンソール" width="1200" height="774" loading="lazy">

<h2 id="ランチャー-クリップボード履歴-画面シフト操作">ランチャー, クリップボード履歴, 画面シフト操作</h2><p>ランチャーアプリには「Raycast」を利用します。</p>
<img src="/images/2025/20250225a/raycast.png" alt="raycast" width="512" height="512" loading="lazy">

<p>Raycast には「ランチャー」としての機能だけでなく、クリップボード履歴機能や、各種操作に Hotkey 付与する機能が搭載されています。そして、これらを無料で利用できます。（ただし、「AI機能」は2週間の Free Trial 後に有償利用となります。また、ハイスペ PC なら気にするまでもありませんが、メモリ消費量が比較的多いように感じます。）</p>
<p>今回の環境構築時に Raycast の存在を知ったのが大きな収穫の1つ目です。これまでは、各機能ごとにアプリケーションを使い分けていました。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>機能</th>
<th>アプリ名</th>
</tr>
</thead>
<tbody><tr>
<td>ランチャー</td>
<td>Alfred</td>
</tr>
<tr>
<td>クリップボード履歴</td>
<td>Clibor</td>
</tr>
<tr>
<td>画面シフト操作</td>
<td>Shiftlt</td>
</tr>
</tbody></table></div>
<p>Alfred には「Clipboard History」というクリップボードの履歴管理機能がありますが、「Powerpack」を購入しないと使えない有償機能です。ランチャーとしては文句なしだとしても、この機能だけのために、他機能も含まれたパッケージを購入することには気が乗りませんでした。また、Shiftlt は「新しいメンテナーを募集中」のため、普段使うPCへインストールするには少々不安があります。Shiftlt の移行先として Hammerspoon が候補になりますが、定義ファイルを編集せずとも UI 操作のみでキーマッピングできると嬉しいなと思っていました。</p>
<p>Raycast はこれらのモヤモヤを一挙に解決してくれるため、まさにニーズを満たすツールです。</p>
<h3 id="Raycast">Raycast</h3><p>Raycast Setting には以下の設定を加えました。ランチャーの起動はキーは、長年の習慣で <code>command(⌘) + space</code> に決まりです。</p>
<div class="scroll"><table>
<thead>
<tr>
<th>操作</th>
<th>Hotkey</th>
</tr>
</thead>
<tbody><tr>
<td>Raycast起動</td>
<td><code>command(⌘) + space</code></td>
</tr>
</tbody></table></div>
<img src="/images/2025/20250225a/Raycast起動.png" alt="Raycast起動" width="1200" height="801" loading="lazy">

<br>

<p>「クリップボード履歴」と「画面シフト操作」に加えて、アプリケーション起動の Hotkey も設定しました。</p>
<h4 id="アプリケーションの起動">アプリケーションの起動</h4><div class="scroll"><table>
<thead>
<tr>
<th>Applications</th>
<th>Hotkey</th>
</tr>
</thead>
<tbody><tr>
<td>Arc</td>
<td><code>option(⌥) + a</code></td>
</tr>
<tr>
<td>Finder</td>
<td><code>option(⌥) + f</code></td>
</tr>
<tr>
<td>Google Chrome</td>
<td><code>option(⌥) + c</code></td>
</tr>
<tr>
<td>Slack</td>
<td><code>option(⌥) + s</code></td>
</tr>
<tr>
<td>Visual Studio Code</td>
<td><code>option(⌥) + v</code></td>
</tr>
<tr>
<td>Zen Browser</td>
<td><code>option(⌥) + z</code></td>
</tr>
<tr>
<td>iTerm2</td>
<td><code>option(⌥) + i</code></td>
</tr>
<tr>
<td>アクティビティモニタ</td>
<td><code>option(⌥) + m</code></td>
</tr>
</tbody></table></div>
<h4 id="クリップボード履歴">クリップボード履歴</h4><div class="scroll"><table>
<thead>
<tr>
<th>Clipboard History</th>
<th>Hotkey</th>
</tr>
</thead>
<tbody><tr>
<td>クリップボード履歴の表示</td>
<td><code>command(⌘) + .</code></td>
</tr>
</tbody></table></div>
<h4 id="画面シフト操作">画面シフト操作</h4><div class="scroll"><table>
<thead>
<tr>
<th>Window Management</th>
<th>Hotkey</th>
</tr>
</thead>
<tbody><tr>
<td>Left Half（左に分割）</td>
<td><code>command(⌘) + ←</code></td>
</tr>
<tr>
<td>Maximize（最大化）</td>
<td><code>command(⌘) + ↑</code></td>
</tr>
<tr>
<td>Right Half（右に分割）</td>
<td><code>command(⌘) + →</code></td>
</tr>
</tbody></table></div>
<img src="/images/2025/20250225a/ショートカットキー一覧.png" alt="ショートカットキー一覧" width="1200" height="823" loading="lazy">

<p>1つのアプリで統合管理できると、アプリ間での Hotkey のコンフリクトが避けられるので非常に便利です。</p>
<h2 id="ブラウザ">ブラウザ</h2><p>環境準備にあたり、もちろん真っ先に Safari から Google Chrome をインストールして、Chrome をデフォルトのブラウザに設定しました。</p>
<p>しかしながら、<strong>ブラウザは「Google Chrome 一択」というのは誤解</strong>だと気づいたのが、Mac環境構築で得た収穫の2つ目です。もちろん、Safari や Firefox はこれまでも（たまに）利用していました。ただし、利用用途が非常に限られていたため、Web ブラウザ界隈のナレッジを拡張するきっかけがありませんでした。それほど、Google Chrome の万能性が高いということでもあります。例えば、検証環境と本番環境で AWS アカウントを使い分けており、その2つの環境に「1つの PC 環境にあるブラウザから同時にログインしたい」となった場合、私は Google Chrome の「通常のブラウザ」と「シークレットブラウザ」の両方を起動して、それぞれからログインする方法としていました。業務用途としては、正直これで十分です。</p>
<p>その一方で、PC のハードウェアを交換しても…</p>
<ul>
<li>Google アカウントが同じであれば、基本的に Chrome のブックマークは引き継がれ続けて、とてつもない数になっている</li>
<li>Chrome で無限に増え続ける「タブ」を管理する上手い方法を知りたい</li>
</ul>
<p>というちょっとした「なんか、いい方法ないかな」の思いで「YouTube で web browser を検索」したところ Arc というスタイリッシュな Web ブラウザの存在を知って、試しにインストールしてみました。</p>
<h3 id="Arc">Arc</h3><p>Arc は The Browser Company が開発する Chromium ベースの Web ブラウザーです。</p>
<img src="/images/2025/20250225a/arc.png" alt="arc" width="1920" height="1080" loading="lazy">

<p>Google Chrome であれば「タブ」と「ブックマーク」は水平に広がっていきますが、Arc は垂直にそれらの要素を配置していきます。また、利用頻度の高いアプリをアイコンのように配置したり、Space を切り替えることで「タブやブックマークのグルーピング」と「作業環境の切り替え」をワンセットで行えます。エンジニア目線で「確かに、こんな機能が欲しかった！」がてんこ盛りになっており、私は <strong>Arc をデフォルトのブラウザに設定</strong> しました。Arc ブラウザの詳細機能は本ブログでは扱わないため、以下の記事をご参照ください。具体的なイメージがつかない場合には「とりあえずインストールして、軽く触れてみる」だけでも、Chrome とは違った操作感が得られると思います。</p>
<ul>
<li>世界で話題のブラウザ「Arc」が便利すぎたので魅力を解説する</li>
<li>【ブラウザ】Arcについて力説＆使い方</li>
</ul>
<p>その一方で、Arc ブラウザは、本ブログの執筆時点（2025年2月）にて「機能開発は終了」して、メンテナンスフェーズに入っている状況です。本件について、開発元 CEO の Josh Miller は以下のように投稿しています。</p>
<blockquote>
<p>we’re not abandoning arc!! just don’t think it needs more features. just stability, performance, security.<br>https://x.com/joshm/status/1849889202164334786</p>
</blockquote>
<p>継続的にパフォーマンス問題の改善やパッチ当てを続けてくれるのであれば問題ありませんが、念のための備えとして、別のブラウザも必要だなと思いました。だからと言って、一度でも「Arc の操作感」を知ってしまうと、Google Chrome には戻り難いものがあります。YouTube で検索したところ、Arc ブラウザの開発元は「Dia」という全く別の AI をベースとして新しいブラウザの開発を進めていること、及び、Arc の移行先として「Zen Browser」が候補として上がっていることが分かりました。</p>
<ul>
<li>What have we been up to? (CEO Update)</li>
<li>An early peek at Dia, our second product | A recruiting video<ul>
<li>The Browser Company、新ブラウザー「Dia」のプレビュー動画を公開</li>
</ul>
</li>
<li>My favorite browser is (kind of) dead</li>
<li>I’m Finally Moving On (I have a new browser)</li>
<li>Arc Browser is DEAD… This One is BETTER!</li>
</ul>
<p>「Arc の操作感」が得られるならば、ということで、Zen Browser をインストールしてみました。</p>
<h3 id="Zen-Browser">Zen Browser</h3><p>「Zen Browser」は、オープンソースで開発されている Fixforx ベースの Web ブラウザです。</p>
<img src="/images/2025/20250225a/zen.png" alt="zen" width="2560" height="1440" loading="lazy">

<p>Zen Browser は設定を「日本語」に変更できます。</p>
<p>Zen Browser（左）とArc（右）を並べてみると、見栄えはほとんど同じです。</p>
<img src="/images/2025/20250225a/ZenとArcの画面比較.png" alt="ZenとArcの画面比較" width="1200" height="748" loading="lazy">

<p>ただし、ショートカットの差分として、例えば「サイドバーを閉じる」コマンドには以下のような違いがあります。</p>
<ul>
<li>Zen Browser<ul>
<li><code>option(⌥) + command(⌘) + c</code></li>
</ul>
</li>
<li>Arc<ul>
<li><code>command(⌘) + s</code></li>
</ul>
</li>
</ul>
<img src="/images/2025/20250225a/5.png" alt="" width="1200" height="747" loading="lazy">

<p>「How to make Zen browser feel like Arc」という記事も書かれているように「いかに、Zen Browser を Arc っぽくするか」は、Arc に魅了された世界中のエンジニアにとって共通の課題に見えます。私が感じた差分は「ブックマーク」の扱いです。Arc はサイドバーに「フォルダ」という概念でブックマークを配置しますが、Zen Browser は「ツールバー」にブックマークを表示するスタイルです。ツールバーの配置場所は調整可能という点は Zen Browser の強いポイントなので、願わくば、サイドバーにいい感じに配置できれば嬉しいなという思いです。</p>
<p>Zen Browser の各種機能を動かしていると、Essentials（サイドバー上側のアイコン箇所）が消えてしまう等の不具合がみられるため、将来的には各種機能が改善されることを期待しつつ、ひとまずは「<strong>メインは Arc。サブで Zen Browser</strong>。非常時に Google Chrome」の使い方と決めました。</p>
<h2 id="キーマッピング">キーマッピング</h2><p>JIS配列Macはデフォルトで「Emacs キーバインド（Mac のキーボードショートカット）」が使えるため、あらためてキー配置を変える必要性はありませんでした。しかしながら、<strong>JIS配列のMacを最大限に活用する</strong> ために「Karabiner-Elements」をインストールして「Advanced Keymap for JIS Keyboard: Project “UTILITY”」を設定しました。</p>
<p>本章の設定内容は「Karabiner-Elements Advent Calendar 2023」で紹介された方法を踏襲しました。このアドベントカレンダー内では、以下の引用通りに「JIS配列Macは反則的」という点が紹介されています。</p>
<blockquote>
<p>JIS 配列は､『英数・かな』キーを独自の修飾キーとして設定することにより、他の配列など比ぶべくもないほどに『合理的』なキー配置となるから<br>もはやあなたは､（Mac の内蔵キーボードにおいては）他の追随を許さない最高の配列､「JIS 配列」を手にすることになります。</p>
</blockquote>
<blockquote>
<p>デフォルト状態での「見た目のよさ」や「合理性」など、総合的なメリットで選ぶなら US 配列が「圧倒的に」オススメだが、自分好みにカスタマイズする前提で、機能性を重視するのであれば、それらのメリットが消し飛ぶ勢いで JIS 配列が「反則級に」便利である。<br>｢圧倒的」の二つ名を持つ US 配列と､「反則級」の二つ名を持つ JIS 配列。あなたはどちらを選びますか？</p>
</blockquote>
<p>これらの文章に触発されて、これまで私も「JIS配列Mac」にこだわり続けたからこそ、このキーマップは是非とも使いこなしたいと思いました。</p>
<p>「Karabiner-Elements Advent Calendar 2023」のキーマップ内容は、「JIS配列Mac」にしかない「英数」と「かな」ボタンに、新しい修飾キーとしての役割を与えることで、キーボードにレイヤーの概念を加えるというものです。</p>
<img src="/images/2025/20250225a/キーボードのレイヤー.png" alt="キーボードのレイヤー" width="1200" height="1526" loading="lazy">

<p>上記の「英数（Fn1）レイヤー」の例だと、</p>
<ul>
<li>「英数」ボタンのみ<ul>
<li>-&gt; 英数入力に切り替え</li>
</ul>
</li>
<li>「英数」+ 他のボタン<ul>
<li>-&gt; キーマップに対応した入力</li>
</ul>
</li>
</ul>
<p>となるため、ホームポジションで行える操作が大幅に増加します。<br>デフォルトで使える「Emacs キーバインド」と「キーボードのレイヤー化」を合わせることで、ショートカットコマンドの実行速度をさらに向上させられます。</p>
<p>コマンド数が多いため、まずは「英数」の修飾キー化のみを有効化しました。コマンドを体得次第、徐々に Enabled の幅を広げていく予定です。</p>
<img src="/images/2025/20250225a/コマンド.png" alt="コマンド" width="1200" height="859" loading="lazy">

<p>以上で、今回の「JIS配列Macの環境構築」はクローズです。</p>
<h2 id="おわりに">おわりに</h2><p>本ブログでは、JIS配列Macの環境構築を取り上げました。</p>
<p>環境構築を進める中で、作業前には存在すら知らなかった以下のツール達が、今後の主力武器になりました。せっかく見つけた「Arc」の機能開発が終了してしまったことは残念ですが、CEO のポストを踏まえると「Web ブラウザとしてすでに完成しているので、これ以上の機能追加は必要ない」という意図にも捉えられます。移行先として有力な「Zen Browser」の今後の発展（特に、サイドバーでのフォルダ管理機能）が楽しみです。</p>
<ul>
<li>Raycast</li>
<li>Arc</li>
<li>Zen Browser</li>
<li>Karabiner-Elements の Advanced Keymap for JIS Keyboard: Project “UTILITY”</li>
</ul>
<p>2年前に、Mac -&gt; Windows への環境移行時に「Mac 慣れした私に Windows が支給されたので、まず設定したこと」を執筆して、その「終わりに」には以下の記述がありました。</p>
<blockquote>
<p>「使い慣れた環境から、あえてズレてみる」というのも、技術キャッチアップには刺激になるのかもしれません。</p>
</blockquote>
<p>普段使いの「当たり前のツール」を見直すことで、より便利なツールの発見に繋がりました。</p>
<blockquote>
<p>みなさま、良い Mac ユーザライフを！</p>
</blockquote>
<p>「Mac ユーザライフ」を再開します。</p>
]]></content>
    <summary type="html">JIS 配列 Mac ユーザーの皆さん、今の環境に満足していますか？ ブラウザやランチャーアプリには何を使っていますか？今日は思い切って、長年連れ添った Chrome と Alfred から、Arc, Zen Browser や Raycast に切り替えた環境構築をご紹介します。</summary>
    <category term="DevOps" scheme="https://future-architect.github.io/categories/DevOps/"/>
    <category term="Mac" scheme="https://future-architect.github.io/tags/Mac/"/>
    <category term="キーバインド" scheme="https://future-architect.github.io/tags/%E3%82%AD%E3%83%BC%E3%83%90%E3%82%A4%E3%83%B3%E3%83%89/"/>
    <category term="環境構築" scheme="https://future-architect.github.io/tags/%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89/"/>
  </entry>
</feed>
