そこに仁義はあるのか(仮)

略してそこ仁!

herdr がいい感じ

最近は Ghostty + Zellij を使ってたけど、特に不満はありませんでした。

なかったのですが、Kiro や Claude Code などの Agent を複数同時に走らせるようになってから、タブを行ったり来たりして「いま何をしているんだっけ」を確認する時間が地味に増えていました。
そして、見に行ったらだいぶ前に終わっていて、ずっと指示待ちだった、ということがよくありました。
さらに、終わったのに閉じていないペインが溜まっていくのも気になっていました。

そんなところに、同僚が使っていた herdr を入れたらこのあたりが解消されました。

🚀 herdr

herdr.dev

「agent multiplexer」を名乗るターミナルマルチプレクサです。
tmux や Zellij と同じように永続セッションとペイン分割とデタッチができて、そこに加えて、各ペインのエージェントが blocked / working / done / idle のどの状態なのかをサイドバーで一覧できます。

対応エージェントは Claude Code、Codex、Kiro など、主要なものは揃っています。(Kiro も主要です。そうですよね。)

📦 install

Homebrewでインストールできて簡単です。

brew install herdr

作業ディレクトリで herdr と打って起動します。

💬 日本語入力の話

Claude Code や Kiro のような TUI はカーソルを自前で描画していて、ターミナル本来のカーソルは隠しています。
macOS の IME は本物のカーソル位置を頼りに変換候補ウィンドウを出すので、この構成だと候補があらぬ場所に表示されます。
herdr には experimental ですがこれへの対処があって、設定でエージェントごとに IME 用のカーソルを見せられます。

# TUI がカーソルを隠していても、IME が候補ウィンドウの位置を
# 追えるように外側のターミナルへカーソル位置を公開する
reveal_hidden_cursor_for_cjk_ime = true
# 上の設定を適用するエージェント(フォーカス中のペインで判定)
cjk_ime_agents = ["claude", "codex", "kiro"]
# IME 用に表示するカーソルの形状
cjk_ime_cursor_shape = "steady_block"
# prefix モードの間だけ macOS の入力ソースを ASCII 系に切り替え、抜けたら元の入力ソースに戻す
switch_ascii_input_source_in_prefix = true

最後の switch_ascii_input_source_in_prefix は、prefix モードの間だけ入力ソースを ASCII 系に切り替えてくれる設定です。
日本語 IME をオンにしたまま ctrl+b からのキー操作が効くようになります。

この辺りは同僚が社内にシェアしていた設定を完全にいただいてます 🙏

🤲 使ってみて

状態が見える。本当にそれだけなのですが、それが全部でした。

サイドバーを見れば Kiro が working で Claude Code が blocked、というのが視線の移動だけで分かるので、タブを巡回して様子を見る作業がなくなりました。
指示待ちで放置してしまう時間もなくなりましたし、done のまま残っているペインも見えるので閉じるようになりました。

マルチプレクサとしての基本部分もちゃんとしています。
永続セッション、分割、タブ、さらに、マウスでもキーボードでも操作できるので、利用ハードルがとても低い気がします。
不満は正直まだ見つかっていません。新しいツールなので、これから変わっていく部分はあるだろうな、というくらいです。

画面キャプチャを載せたかったけど、ゴリゴリ Agent が動いているので、サイトの画像を見てください...

📝 まとめ

herdr、複数のコーディングエージェントを並行で走らせている人向けです。

自分は困っていなかったのに乗り換えて、戻る気がなくなりました。気になった方はどうぞ。

github.com

JJUG CCC 2026 SpringでEvent Stormingと仕様駆動開発の話をしてきた

JJUG CCC 2026 Springで「イベントストーミングとKiroの仕様駆動開発で実現する要件の認識合わせプロセス」というタイトルでお話してきました。

資料はこちらです。

speakerdeck.com

話したこと

今回話したのは、「実装の前にある認識合わせが大事」という話です。

生成AIを使うと、コードを書くところはかなり速くなります。
ゆえに、業務システムの開発では、その前にある「何を作るのか」を揃えるところがより重要になってきます。
開発が早いことで、恐ろしい速度で物が出来上がってくるので、認識のずれがあるとそれが一気に大きくなってしまいます。
もちろん、作り直すのも早いですが、微調整が多いと開発効率は上がりません。

そのため、認識を合わせていくことが重要ですが、ビジネスサイド同士、開発者同士、ビジネスサイドと開発者などで認識のずれというのは起こりえます。それは、

  • 言葉の意味が違う
  • 見えている業務範囲が違う
  • 欲しいもののイメージが違う

など、多くの理由があります。

なので今回は、Event Stormingで人間同士の認識を合わせて、その内容を仕様に落とし込んでいく方法をテーマにお話をしました。

Event Stormingで人間同士の認識を合わせる

前半では、Event Stormingの話をしました。

Event Stormingは、業務で起きる出来事を時系列に並べながら、ビジネスサイドと開発者で認識を合わせていくプラクティスです。
今ご支援している案件では、Event Stormingをかなり業務理解と課題の洗い出しに寄せた使い方をしています。
使う付箋も以下に絞っています。

  • ドメインイベント
  • アクター
  • ポリシー
  • ホットスポット

Event StormingをBig PictureとProcess Modeilngレベルに絞り、最初から設計まで細かく詰めるというよりは、まずは業務の目的や流れを理解することを目的にします。

Kiro Specで人間とAIの認識を合わせる

後半では、KiroのSpecモードを使った仕様駆動開発の話をしました。

KiroのSpecでは、以下の3つのドキュメントを作り、それぞれのフェーズで人間がレビューしながら工程を進めていきます。

  • requirements.md
  • design.md
  • tasks.md

requirements.mdでは、自然言語で書いたPBIをそのまま実装に持っていくのではなく、条件と結果がわかる形に整理します。
ここではEARS形式を使って、要件を構造化しています。EARS形式では、以下のように、条件と期待する結果を分けて書きます。

  • WHEN こういうことが起きたとき
  • THEN システムはこう振る舞う

例えば、以下のように書くと、要件が構造化されて明示されることで曖昧性を発見しやすくなり、「予約待ちリストの末尾で本当にいいんだっけ?」「予約上限はどう扱うんだっけ?」という確認がしやすくなります。

  • WHEN 利用者が貸出中の図書を選択して予約を申し込んだ
  • THEN システムは予約待ちリストの末尾に予約を登録する

文章でふわっと書いていると流れてしまうことも、少し構造化されると違和感として見つけやすくなります。

また、EARS形式で書いておくと、そのままテスト観点にもつなげやすいです。
「どういう条件のときに、どういう結果になるべきか」が明確になるので、仕様の確認だけでなく、受け入れ条件やテストケースを考えるときにも使いやすくなります。

文字だけだと難しいところはモックにする

ただ、requirements.mdだけで全部わかるかというと、そうでもないです。

特に、画面の操作感や状態の変化は文章ではイメージしにくいことが多いです。
そういうときは、画面モックや状態遷移図、業務フロー図などを作って確認するようにしています。

画面モックといっても、ちゃんとしたデザインを作るというよりは、認識合わせのための簡単なものです。
ここで作り込みすぎると、今度は細かい見た目の話に寄ってしまうので、HTML 1ファイルでさっと作るくらいに留めています。

生成AIを使うほど、人間が見るところも増える

生成AIを使うと開発が楽になる一方で、人間が見るべきところがなくなるわけではない、ということです。
Kiroは、要件を整理したり、設計ドキュメントを作ったり、タスクに分解したりするところをかなり助けてくれます。

ただ、以下のような人間による意思決定が必要なところは存在します。

  • ドメイン知識として正しいか
  • 業務ルールが合っているか
  • 例外ケースが漏れていないか
  • その設計で本当に良いか

なので、「AIに任せる」というよりは、人間同士で合意した業務理解を、AIにも人にも扱いやすい形にしていく、という使い方をしています。

認識合わせの時間をどこで取るか

Event StormingとSpecを組み合わせたやり方は、正直そこまで軽いプラクティスではないです。
ビジネスサイドの人にも時間を取ってもらう必要がありますし、業務に詳しい人や意思決定できる人がいないと、あまりうまく進みません。

ただ、この時間を取らないと、あとから別の形で時間を使うことになることも多いです。

実装が速くなると、作るところまではすぐに進めます。でも、そこで「そもそも作るものが違った」となると、結局手戻りになります。

なので、先に認識を合わせる時間をちゃんと取ることが重要です。

アンケートを読んで

アンケートの回答も読ませてもらいました。とてもしっかり感想を書いていただいている方が多く、本当に嬉しいです。ありがとうございます。

中でも、「現場感があって参考になった」「明日から使えそう」「仕様駆動開発で困っていたポイントに近かった」といった感想が多く、話してよかったなと思いました。

また、KiroやSpec Kitなどをすでに使っている方からのコメントがあったのが嬉しかったです。
requirements.mdをどこまでメンテナンスするか、レビューの時間をどう考えるか、といったところは自分もまだ考えている途中なので、今後もう少し整理していきたいです。

まとめ

久しぶりのJJUG CCC登壇でした。

簡単まとめ:

  • 生成AIを使うと実装は速くなる
  • ただ、業務システムでは「何を作るか」の認識合わせが難しい
  • Event Stormingで人間同士の業務理解を揃える
  • Kiro Specで人間にもAIにも扱いやすい仕様にする
  • AIに全部任せるというより、AIと一緒に進めるための準備が大事

Javaの話というよりは、業務システム開発やチーム開発の話に近かったですが、Javaの現場でもかなり関係するテーマだと思っています。

アンケートでも、現場での活用や仕様駆動開発の進め方に関心を持ってもらえたようで嬉しかったです!

聞いてくださった皆さま、JJUG CCC運営の皆さま、ありがとうございました。

明日からのプロジェクトで活かせる何かが少しでもあれば嬉しいです!

BLUE BACKS「認知バイアス」を読んだ

以前から「認知バイアス」という言葉自体は知っていて、他の記事でも紹介したことはあったのですが、ちゃんと本として読むのは今回が初めてでした。

わかるわかるという記述が多く、普段の意思決定やチームでの議論にも普通に影響しており、意識した上でも結局バイアスはかかってしまいそう。

💡 特に印象的だったポイント

いくつか面白いポイントがあったのですが、特に印象に残ったのは以下の2つです。

  • 言語が記憶や思考に与える影響
  • ダイバーシティの活き方 / 活きないケース

💬 言語が記憶を邪魔するという話

もともと自分の中では、「言語化することで理解や記憶は強化される」という認識があったのですが、この本では逆の現象が紹介されています。

具体的には、

  • 人の顔を言語で説明させたグループ
  • 説明しなかったグループ

で比較すると、説明したグループの方が、その人を思い出す精度が下がるという結果が出ている、という話です。

言語で説明することで、特徴を「言葉にできる範囲」に抽象化する、そして、その抽象化によって本来の記憶(視覚的な情報)とはズレた形で再構築されるようです。

普段、自分の中でのモヤモヤを整理するために「とりあえず言語化する」ということをよくやるのですが、対象によってはそれが逆効果になる可能性がある、というのは結構面白いポイントでした。

そして、うちの息子(2歳)もめちゃくちゃ言葉の発達が早くていいことだなぁと思っていたんですが、もしかしたら別のところに影響を与えているのかも...?(そういえばお絵描きはあまり好きではない)

🌏 ダイバーシティは万能ではないという話

もう1つ面白かったのが、ダイバーシティの扱いです。 一般的には「多様性は良いもの」として語られることが多いと思いますが、この本ではもう少し分解して説明されています。

ポイントとしては、

  • 目標が曖昧で、試行錯誤が必要なフェーズでは有効
  • 目標が明確で、実行フェーズに入ると逆にノイズになることがある

という話で、これはかなり納得感がありました。

例えば、イノベーションを起こしたり、新しいアイデアを出したりといったフェーズでは、いろんな人がいろんなやり方を試すことで探索の幅が広がるので、ダイバーシティがうまく働く。

一方で、やることが決まっていて、あとは効率よく進めるだけという状態になると、意見のばらつきや認識のズレががコストになりやすく、効率を下げる可能性がある、という話でした。

また、直感的には「多様性があるならしっかり議論した方が良さそう」と思いがちですが、そうとも限らない、むしろ逆効果になるという話も面白かったです。 理由としては、会話をすると他人の意見に引っ張られたり、同調バイアスが働いて、多様性が収束していくという話でした。故に、多様性のあるチームはそれぞれの専門性にある程度任せて、議論は最小限にするなどの工夫が必要とのこと。 単に多様なメンバーを集めるだけではなく、どう意思決定するかまで含めて設計が必要という点が印象的でした。

まとめ

改めて読んでみて思ったのは、認知バイアスは「知っているつもり」でも、ちゃんと整理して理解すると見え方が変わるなという点でした。

Gitで日本語ファイル名が文字化けするのはcore.quotepathの設定で解決した

git statusを実行したとき、日本語ファイル名が"\343\203\206\343\202\271\343\203\210.txt"みたいな謎の数字で表示されることがある。

ずっと、特に気にせずに放置してた。(心の目でみていた)

これはGitがデフォルトで非ASCII文字をエスケープして表示するようになっているのが原因で、core.quotepathという設定で制御されている

core.quotepath falseで解決

git config --global core.quotepath false

これで日本語ファイル名がそのまま表示されるようになった。日本語環境では変更しておくのがおすすめ。

またMacの容量がいっぱいになったけど、セーフモードで起動したら解消した

タイトルで全てを言っているんですが、Macのストレージがいっぱいになってしまってどうしようもなくなった。

調べてみたら /System/Volumes/Data/private/var/folders/zz に大量のデータが溜まってた。 これはmacOSのファイルシステム構造の一部で、システムが一時ファイルやキャッシュデータを保存するために使用するフォルダのよう。

セーフモードで起動して解決

Macをセーフモードで起動すると、このゴミデータが消えてくれるとのこと。 コマンドなどでも消せるけど、システムデータなのでなるべく安全な方法で消すのがおすすめ。

Macの起動時にShiftキー長押しをするとセーフモードで起動でき、自動で問題のフォルダのデータを消してくれた。

gRPCアプリをAWS CDKでECSとFargateにデプロイする

gRPCを利用したGoアプリケーションをAWS上でデプロイするためのCDKを作成したので、その構成や実装のポイントについて紹介します。

今回デプロイするgRPCサーバーの実装は、作ってわかる! はじめてのgRPCを参考にしました。
この記事では、Terraformを使って環境を構築していたので、CDKを使ってAWSのインフラを定義し、Fargate上でgRPCサーバーを動かす構成を作成しました。

ポイントは以下の通りです。

  • Route 53とACMを利用してHTTPS対応のgRPCエンドポイントを構築(これだけ手動で実行)
  • DockerイメージをCDKでビルドし、ECRにアップロード
  • ALBを活用してgRPCリクエストをルーティングし、ターゲットグループで適切なヘルスチェックを設定
  • ECS Fargateのタスク定義とオートスケーリングの設定
  • デプロイ後、ALBのDNSを出力し、サービスが正しく稼働していることを確認

📂 ディレクトリ構成

プロジェクトのディレクトリ構成の重要な部分を抜粋すると以下のようになります。app-grpc フォルダはZennの記事通りの構成で、infraフォルダにCDKのプロジェクトを作りました。

.
├── README.md
├── app-grpc
│   ├── Dockerfile  # gRPCアプリのDockerイメージ定義
│   └── src
│       ├── api  # gRPCサービスの定義
│       ├── cmd  # エントリーポイント
│       ├── go.mod  # Goの依存管理
│       ├── go.sum  # 依存関係の固定
│       └── pkg  # サーバーのロジック
└── infra
    ├── cdk.json  # CDKの設定ファイル
    ├── package.json  # CDKの依存関係管理
    ├── tsconfig.json  # TypeScriptの設定
    ├── .env  # 環境変数の設定
    └── src/  # CDKのソースコード
        ├── bin/  # エントリーポイント
        │   └── app.ts
        └── lib/  # スタック定義
            └── grpc-app-stack.ts

🛠 事前準備

本CDKを利用する前に、以下の準備をしました。

1. Route 53でのドメイン取得

gRPCサービスを公開するためのドメインをRoute 53で取得しました。

2. ACM (AWS Certificate Manager)での証明書発行

HTTPS対応のため、ACMでSSL/TLS証明書を発行しました。
証明書のARNをメモしておき、CDKで利用するために ↓ の .env ファイルに記載しました。
.gitignore.env を追加して、Gitリポジトリに含まれないようにしています。

.env ファイルのサンプルは以下の通りです。

# ACM Certificate ARN for HTTPS
ACM_CERTIFICATE_ARN=arn:aws:acm:REGION:ACCOUNT_ID:certificate/CERTIFICATE_ID

3. 環境変数を利用したARNの設定

CDKのapp.tsで.envファイルから環境変数を読み込み、ACM証明書のARNを設定しています。

import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import * as dotenv from 'dotenv';
import { GrpcAppStack } from '../lib/grpc-app-stack';

dotenv.config();

const app = new cdk.App();

const acmCertArn = process.env.ACM_CERTIFICATE_ARN;
if (!acmCertArn) {
  throw new Error('環境変数ACM_CERTIFICATE_ARNが設定されていません。.envファイルを確認してください。');
}

new GrpcAppStack(app, 'GrpcApp', {
  acmCertificateArn: acmCertArn,   // 設定をスタックに渡す
});

🏗 インフラ構成

本CDKでは、以下のAWSサービスを使用しています。

  • ECS (Fargate): gRPCサーバーをコンテナとして動作させる
  • ALB (Application Load Balancer): gRPCリクエストのルーティング
  • ECR: コンテナイメージの管理
  • ACM (AWS Certificate Manager): HTTPS対応の証明書管理
  • Auto Scaling: 負荷に応じたスケーリング

CDKで作成する構成は以下のようになります。

graph TD
    Client[Client] --> gRPC[gRPC API]
    gRPC --> ALB[Application Load Balancer]
    ALB --> ECS[ECS Fargate Cluster]
    ECS --> ECR[ECR Repository]

    subgraph "gRPC Stack"
        gRPC
        ALB
        ECS
        ECR
    end

    classDef aws fill:#F7931E,stroke:#232F3E,color:#000000,font-weight:bold;
    classDef outer fill:#fdf9e6,stroke:#333,stroke-width:2px;
    class gRPC,ALB,ECS,ECR aws;
    class gRPC,ALB,ECS,ECR outer;

📝 CDKの実装

CDKを使ってgRPCアプリケーションをFargateにデプロイするための各コンポーネントを、パーツごとに説明します。記事の最後に全体のCDKを記載します。

1. Dockerイメージの定義とECRアップロード

まず、gRPCアプリケーションのDockerイメージをCDKでビルドし、ECRにアップロードします。

const asset = new assets.DockerImageAsset(this, 'GrpcApiImage', {
  directory: path.join(__dirname, '../../../app-grpc'),
});

これにより、app-grpc/ディレクトリ内のDockerfileとソースコードを元に、ECRにコンテナイメージがアップロードされます。

2. タスク定義とコンテナ設定

Fargate上で動作するgRPCアプリのタスク定義を作成し、コンテナの設定を行います。
ちなみに、この runtimePlatform については こちらの記事に詳しく書きました。

const taskDefinition = new ecs.FargateTaskDefinition(this, 'GrpcTaskDef', {
  runtimePlatform: {
    cpuArchitecture: ecs.CpuArchitecture.ARM64,
  },
});

ARM64アーキテクチャを指定しているのは、M1 Macでビルドしたコンテナを動作させるためです。

次に、コンテナをタスク定義に追加し、ヘルスチェックを設定します。
gRPCアプリのヘルスチェックには、grpc_health_probe を利用しています。
(ヘルスチェックは、タスクに対してと、LBの2箇所。LBは後述。)

const grpcPort = 8080;

const container = taskDefinition.addContainer('grpc-api-container', {
  image: ecs.ContainerImage.fromDockerImageAsset(asset),
  logging: ecs.LogDrivers.awsLogs({ streamPrefix: 'grpc-service' }),
  healthCheck: {
    command: ['CMD-SHELL', '/bin/grpc_health_probe -addr=:8080 || exit 1'],
    startPeriod: cdk.Duration.seconds(5),
  },
});

container.addPortMappings({ containerPort: grpcPort, protocol: ecs.Protocol.TCP });

3. ALBとターゲットグループの設定

ALB (Application Load Balancer) を作成し、gRPCアプリをターゲットグループに登録します。
L3 ConstructのApplicationLoadBalancedFargateServiceを使うことで、設定がめちゃくちゃ楽になりました。
ACM証明書をcertificate でALBに設定し、gRPCプロトコルを使用できるようにしています。この設定でprotocol のデフォルトプロトコルが HTTPS に設定されます。
また、Application Load Balancer (ALB) を使用し、protocolVersion: ApplicationProtocolVersion.GRPC を設定することで、gRPC特有のHTTP/2通信を適切に処理しています。

const certificate = acm.Certificate.fromCertificateArn(this, 'Certificate', acmCertArn);

const fargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'GrpcService', {
  taskDefinition,
  desiredCount: 2,
  certificate,
  targetProtocol: elbv2.ApplicationProtocol.HTTP,
  protocolVersion: ApplicationProtocolVersion.GRPC,
});

ターゲットグループのヘルスチェックを設定します。

const targetGroup = fargateService.targetGroup;
targetGroup.configureHealthCheck({
  path: '/grpc.health.v1.Health/Check',
  healthyThresholdCount: 2,
  healthyGrpcCodes: '0',
});

4. オートスケーリングの設定

負荷に応じたオートスケーリングを設定し、サービスの可用性を向上させます。

const scalableTarget = fargateService.service.autoScaleTaskCount({
  minCapacity: 2,
  maxCapacity: 10,
});

scalableTarget.scaleOnCpuUtilization('CpuScaling', { targetUtilizationPercent: 70 });

最小2、最大10のタスク数でスケールするよう設定し、CPU使用率70%を超えるとスケールアウトするようにしました。

5. デプロイ結果の確認

最後に、作成したALBのDNS名をCDKの出力として表示し、デプロイ結果を確認できるようにします。

new cdk.CfnOutput(this, 'LoadBalancerDNS', {
  value: fargateService.loadBalancer.loadBalancerDnsName,
  description: 'Load balancer DNS name',
});

📂 まとめ

本記事では、AWS CDKを活用してgRPCアプリをECS Fargate上にデプロイする方法を解説しました。主要なポイントは以下の通りです。

  • Route 53とACMを利用してHTTPS対応のgRPCエンドポイントを構築
  • DockerイメージをCDKでビルドし、ECRにアップロード
  • ALBを活用してgRPCリクエストをルーティングし、ターゲットグループで適切なヘルスチェックを設定
  • ECS Fargateのタスク定義とオートスケーリングの設定
  • デプロイ後、ALBのDNSを出力し、サービスが正しく稼働していることを確認

👀 参考

CDKスタックの全体はこんな感じです。

import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
import { Construct } from 'constructs';
import * as assets from 'aws-cdk-lib/aws-ecr-assets';
import * as path from 'path';
import { ApplicationProtocolVersion } from "aws-cdk-lib/aws-elasticloadbalancingv2";

// GrpcAppStackのプロパティを拡張
interface GrpcAppStackProps extends cdk.StackProps {
  acmCertificateArn: string;
}

export class GrpcAppStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: GrpcAppStackProps) {
    super(scope, id, props);

    // Docker イメージを CDK でビルド & ECR にアップロード
    const asset = new assets.DockerImageAsset(this, 'GrpcApiImage', {
      directory: path.join(__dirname, '../../../app-grpc'),
    });

    // 証明書ARNの確認
    const certificateArn = props?.acmCertificateArn;
    if (!certificateArn) {
      throw new Error('ACM証明書ARNが提供されていません。');
    }

    // 最初にタスク定義を作成
    const taskDefinition = new ecs.FargateTaskDefinition(this, 'GrpcTaskDef',{
      runtimePlatform: {
        cpuArchitecture: ecs.CpuArchitecture.ARM64,  // M1 Mac でビルドしたので、ランタイムを指定
      },
    });

    // ヘルスチェック付きでコンテナを追加
    const container = taskDefinition.addContainer('grpc-api-container', {
      image: ecs.ContainerImage.fromDockerImageAsset(asset),
      logging: ecs.LogDrivers.awsLogs({ streamPrefix: 'grpc-service' }),
      healthCheck: {
        command: [
          'CMD-SHELL',
          '/bin/grpc_health_probe -addr=:8080 || exit 1'
        ],
        startPeriod: cdk.Duration.seconds(5),
      },
    });

    // gRPC ポート
    const grpcPort = 8080;
    
    // コンテナポートのマッピング
    container.addPortMappings({
      containerPort: grpcPort,
      protocol: ecs.Protocol.TCP,
    });

    // 証明書オブジェクトの作成
    const certificate = acm.Certificate.fromCertificateArn(this, 'Certificate', certificateArn);

    // ApplicationLoadBalancedFargateServiceを使用して、事前作成したタスク定義を利用
    const fargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'GrpcService', {
      taskDefinition: taskDefinition, // 事前に作成したタスク定義を使用
      desiredCount: 2,
      certificate: certificate,  // 証明書が設定されていればHTTPSに設定される
      targetProtocol: elbv2.ApplicationProtocol.HTTP,  // GRPCプロトコルを指定(HTTPベース)
      protocolVersion: ApplicationProtocolVersion.GRPC,  // GRPCプロトコルを指定(GRPCベース)
    });

    // Configure target group with HTTP health check
    const targetGroup = fargateService.targetGroup;
    targetGroup.configureHealthCheck({
      path: "/grpc.health.v1.Health/Check",
      healthyThresholdCount: 2,
      healthyGrpcCodes: "0",
    });

    // オートスケーリングの設定
    const scalableTarget = fargateService.service.autoScaleTaskCount({
      minCapacity: 2,
      maxCapacity: 10,
    });

    // CPU使用率に基づくスケーリング
    scalableTarget.scaleOnCpuUtilization('CpuScaling', {
      targetUtilizationPercent: 70,
    });

    // Outputs
    new cdk.CfnOutput(this, 'LoadBalancerDNS', {
      value: fargateService.loadBalancer.loadBalancerDnsName,
      description: 'Load balancer DNS name',
    });
  }
}

Macの容量がいっぱいなのなんでと思ったらDocker.rawが原因だった

Macの容量がいっぱいいっぱいで、にっちもさっちも行かなくなった。

サイズの大きいファイルを探す

なんでだと思ってファイルの大きいサイズを見てみた。 MacのFinderで簡単にサイズの大きいファイルを見つける方法は、

  1. 検索ボックスに適当な文字を入れる
  2. 左上に表示されている + をクリックすると、条件入力の欄が出てくる
  3. 一番左のプルダウンで「その他」を選択し、表示されたポップアップから「ファイルサイズ」を選ぶ
  4. 探したいサイズを選択する(「より大きい」という選択も可能)
  5. 検索ボックスに入力していた文字を消す

検索して見てみると、Docker.raw が1TBのサイズと、PCの容量を超えたバグったサイズになってた。

※ 設定して、今はサイズが小さくなったDocker.raw

DockerのResourceの設定をする

DockerのResourceの設定をしてなかったことが原因でした。 Resourceの設定が初期設定でMaxになっていたので、Dockerのリソースが許容量を超えて大きくなってしまっていました。

ResourceのDisk usage limitを小さくしたら、それに合わせてDocker.rawが小さくなりました。

SonarQubeで始めるコード品質チェック

「テストも通ってるし、レビューもOK。でもなんだかバグが出やすい…」「関数が長くなって、後から読むとわかりづらい…」 など、コードを書く上で可読性を含んだ「品質」はとても大切な要素です。
今回は、そんな“コードの内側の問題”を見える化してくれるツール、SonarQubeを紹介します。
最初の会社にいたときは使ってたツールではあったのですが、ここしばらく会わず、最近再開したので導入手順などをまとめておきます。

🛠 SonarQubeってどんなツール?

SonarQubeは、コードの品質や保守性、セキュリティの問題を自動で検出してくれるオープンソースのツールです。
以下のような観点で、コードを分析してくれます。導入してCI/CDのプロセスに組み込むことで、常にコードの品質を確認できます。

  • バグやコーディング規約違反の検出
  • コードの重複や複雑さの分析
  • テストカバレッジの可視化
  • セキュリティの脆弱性チェック(例:SQLインジェクションの可能性など)

最近はAIでのコード品質測定なども話題になりますが、都度ではなく継続して数値として評価してくれるというニーズは今でも十二分にあると思います。

🚀 SonarQubeを導入する

1:SonarQubeをインストールする

Dockerを使ったインストール

docker run -d --name sonarqube -p 9000:9000 sonarqube:lts

macOSでのインストール(Homebrewを使用)

brew install openjdk@17
brew install sonarqube
brew services start sonarqube

いずれかの方法でSonarQubeのインストールと起動が完了したら、http://localhost:9000 にアクセスし、初期ユーザー名/パスワード(admin/admin)でログインします。初回ログイン時にパスワードの変更が求められます。

2:JavaプロジェクトをSonarQubeに登録する

SonarQubeが起動したら、次はプロジェクトを登録して分析の準備をします。

プロジェクト作成

SonarQubeのWeb画面から以下のように進めます:

  1. SonarQubeにログイン後、「Create new project」をクリック
  2. 「Manually」を選択
  3. プロジェクトキーとプロジェクト名を入力(例:sample-project
  4. 「Set Up」をクリック
  5. 「Locally」を選択
  6. トークンを生成し、安全な場所に保存します

プロジェクトの設定ファイル

プロジェクトのルートディレクトリにsonar-project.propertiesファイルを作成します。

sonar.projectKey=sample-project
sonar.projectName=Sample Project
sonar.projectVersion=1.0

# ソースコードのパス
sonar.sources=src/main/java
sonar.java.source=17

# コンパイル済みクラスのパス
sonar.java.binaries=build/classes/java/main

# テストコードのパス
sonar.tests=src/test/java
sonar.java.test.binaries=build/classes/java/test

# JaCoCo レポートのパス(カバレッジ情報)
sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml

# エンコーディング
sonar.sourceEncoding=UTF-8

# 除外パターン(自動生成コードなど)
sonar.exclusions=src/gen/**/*

Gradleプロジェクトの設定

JaCoCoの設定もしているのですが、これはSonarQubeにテストカバレッジをレポートするために必要です。SonarQubeは自前でテストを実行しないため、JaCoCoなどのツールでXML形式のカバレッジレポートを生成し、その結果を取り込む必要があります。

build.gradleファイルにSonarQubeとJaCoCoのプラグインを追加します。

plugins {
    id 'org.sonarqube' version '4.4.1.3373'
    id 'jacoco'
}

jacoco {
    toolVersion = "0.8.10"
}

jacocoTestReport {
    reports {
        xml.required = true
    }
}

test {
    finalizedBy jacocoTestReport
}

3. コード分析の実行

Gradleを使った分析

事前にテストとカバレッジレポートを生成した上で以下を実行します:

./gradlew clean test jacocoTestReport sonarqube \
  -Dsonar.host.url=http://localhost:9000 \
  -Dsonar.login=YOUR_SONAR_TOKEN

SonarScannerを使った分析

SonarScannerを使う場合は、事前にビルドとテストを済ませておきます。

brew install sonar-scanner
sonar-scanner \
  -Dsonar.host.url=http://localhost:9000 \
  -Dsonar.login=YOUR_SONAR_TOKEN

4. 品質ゲートの設定

品質ゲートは、コードの品質を一定の基準でチェックするためのルールセットのようなものです。例えば「テストカバレッジ80%以上」とか「新しく追加されたコードにバグがないこと」など、チームとして守りたいラインを明確にできます。

SonarQubeでは、デフォルトで「Sonar way」というルールが用意されています。

5. 関数の複雑度について

「この関数ちょっと長すぎるかも…」「条件分岐が入り組んでて読みにくいな…」と思ったことはありませんか?そんなときに便利なのが、関数の「複雑度」を数値で見える化してくれるSonarQubeの機能です。

SonarQubeでは、主に以下の2つの指標を使ってコードの複雑さを評価します:

  • 循環的複雑度(Cyclomatic Complexity):分岐の数に基づいた複雑度を表す指標
  • コグニティブ複雑度(Cognitive Complexity):コードの読みやすさや理解のしやすさを考慮した指標

デフォルトではコグニティブ複雑度にルールが有効になっていて、例えば15を超えると複雑すぎるとSonarQubeが教えてくれます。 もし循環的複雑度を使いたい場合は、SonarQubeの品質プロファイルで対応するルールを有効にする必要があります。

循環的複雑度に対してもルールを有効にすれば同様に閾値ベースで検出できます。必要に応じて品質プロファイルから有効化・閾値変更を行います。

📝 まとめ

SonarQubeを使用することで、コードの品質を継続的に監視し、問題を早期に発見できます。特にチーム開発では、共通の品質基準を設けることで、ある程度のコードの一貫性と保守性を高めることができます。

とくにチーム開発では以下のようなメリットがあり、レビューの負担軽減や品質の底上げにもつながります。

  • 品質ゲートで「最低限守りたいライン」を共有できる
  • バグやコードスメルを自動で検出してくれる
  • 読みにくいコードを早期にリファクタリングできる

IntelliJ IDEA で NBSP を非表示にする方法

IntelliJ IDEA では、コード内に NBSP が含まれると、特定の記号として表示されることがあります。
この表示をなくし、通常のスペースと同じように見えるようにする設定を紹介します。

🔍 NBSPとは?

NBSP(Non-Breaking Space, ノーブレークスペース)とは、通常のスペースと異なり、改行や折り返しが発生しない特殊な空白文字です。
普通の半角スペースでは、半角スペースは改行できる場所として使われます。
しかし、 THANK YOU のように、2つの単語の間のスペースで改行させたくない場合もあります。 そこで、改行しないことを明示した半角スペースとしてNBSPを使います。

ただ、この NBSP が IntelliJ IDEA 上で特殊な記号として表示されることがあります。
NBSPをそのまま見たい場合もあれば、READMEなどで表示されてしまうと可読性を下げてしまうこともあると思います。
そこで、これを通常のスペースと同じように見えるようにする設定を紹介します。

🛠 解決策

NBSP を含む特殊文字の表示は、エディタの「Advanced Setting(高度な設定)」から変更できます。

  1. 設定画面を開く

    • Ctrl + Alt + S(Windows / Linux)
    • Cmd + ,(macOS)
  2. 設定の場所

    • Advanced Settings を開く
      • 日本語メニューの場合: 詳細設定
    • Render special characters, such as control codes, using their Unicode name abbreviations のチェックを外す
      • 日本語メニューの場合: 特殊文字 (制御コードなど) を対応する Unicode 名の略記を使用して表示する

この設定をオフにすると、NBSP を含む特殊文字が表示されなくなります。

公式ドキュメントはこちら:英語版 / 日本語

M1 MacでビルドしたコンテナをECS上にデプロイしたらヘルスチェック失敗して一生デプロイが終わらなかった

TL;DR

  • M1 MacでDockerイメージをビルドしてECS (Fargate) にデプロイしたら、ヘルスチェックが失敗し続けてCDKのデプロイが完了しなかった。
  • 原因は M1 Mac 上で arm64 でビルドされていたこと。Fargate はデフォルト amd64 で動作するため、platform: assets.Platform.LINUX_AMD64 を指定したら解決。
  • もしくは、Fargate のタスク定義のランタイムに arm64 を指定する

☝️ はじめに

gRPC のサービスをECS(Fargate)上にデプロイしようとしたところ、タスクは起動するのに、ヘルスチェックが通らずに一生 CloudFormation が "CREATE_IN_PROGRESS" のまま デプロイが終わらないという事象に遭遇しました。
その原因と解決策について、記録を残しておきます。

☁️ 環境

  • 開発マシン: MacBook Pro (M1)
  • ECS実行環境: AWS Fargate (x86_64)
  • コンテナ: Go製のgRPCサービス
  • CDKでのデプロイ

ちなみに、デプロイしたのは、作ってわかる! はじめてのgRPC でした!gRPC をわかりやすく学べてオススメ!

💥 発生事象

AWS CDKを使ってECSにデプロイしたところ、デプロイが全く終わらなかった。
環境を見に行くと、ECS のタスクは起動しているものの、 ヘルスチェックがずっと成功しない。結果としてタスクが起動と終了を繰り返し続けていました。

ヘルスチェックが終わらないので、ECSの execute-command を使って、タスク内で直接 /bin/sh を実行してみた。

aws ecs execute-command \
  --cluster GrpcApiCluster \
  --task <task-id> \
  --command "/bin/sh" \
  --interactive

すると、以下のエラーが発生。

SessionId: ecs-execute-command-2v4gc4qaj4grjrlf6dk8krrcz8 : 
----------ERROR-------
Unable to start command: Failed to start pty: fork/exec /bin/sh: exec format error

本来、正常なコンテナであれば sh のプロンプト (sh-4.2# など) が表示され、シェルの入力が始まる 。(実際に、問題解決後は実行できた。)
なので、このエラーから /bin/sh 自体が破損しているか、アーキテクチャの不一致で実行できない ことがわかった。

💡 解決策

CDKで DockerImageAsset を定義する際に、platform: assets.Platform.LINUX_AMD64 を明示的に指定すればOK。

修正前(arm64のままビルドされる)

import * as assets from 'aws-cdk-lib/aws-ecr-assets';
(略)
const asset = new assets.DockerImageAsset(this, 'GrpcApiImage', {
  directory: path.join(__dirname, '../path'),  // Dockerfileのフォルダまでのパス
});

修正後(amd64でビルドするよう指定)

import * as assets from 'aws-cdk-lib/aws-ecr-assets';
(略)
const asset = new assets.DockerImageAsset(this, 'GrpcApiImage', {
  directory: path.join(__dirname, '../path'),  // Dockerfileのフォルダまでのパス
  platform: assets.Platform.LINUX_AMD64  // platform の記述を追加
});

この変更により、M1 Macでも x86_64 向けのコンテナイメージをビルドできるようになり、ECS上で正しく動作するようになった。

また、Dockerfile 内で --platform を指定する方法もある。

Dockerfile の修正例(amd64 でビルドする)

FROM --platform=linux/amd64 golang:1.23 as builder

これを指定することで、M1 Mac上でも amd64 向けのビルドが可能になり、ECS (Fargate) での互換性を確保できる。(こっちの方が良さそう)

↓なお、 platform の説明は以下。

FROM でマルチプラットフォーム対応のイメージを参照する場合には、オプションの --platform フラグを使うと、特定のプラットフォーム向けイメージを指定できます。たとえば、 linux/amd64 や、 linux/arm64 や、 windows/amd64 です。デフォルトでは、今まさに利用しているプラットフォームを対象として構築します。 グローバル構築引数global build arguments が、このフラグの値をとして利用できます。たとえば 自動的なプラットフォーム ARG は、構築段階でネイティブな構築プラットフォームを上書きでき( --platform=$BUILDPLATFORM )、これを、そのステージ内で対象プラットフォーム向けのクロス・コンパイルとして利用できます。
Dockerfile リファレンス — Docker-docs-ja 24.0 ドキュメント

ARM64 のまま実行したいときはランタイムを指定

arm64 のイメージのまま実行したいときは、タスク定義にて cpuArchitecture を指定する

import * as ecs from 'aws-cdk-lib/aws-ecs';
(略)

    // Task definition
    const taskDefinition = new ecs.FargateTaskDefinition(this, 'GrpcApiTaskDef', {
      runtimePlatform: {
        cpuArchitecture: ecs.CpuArchitecture.ARM64,  // この部分を追加
      },
    });

class CpuArchitecture · AWS CDK

📝まとめ

  • M1 MacでDockerをビルドすると、デフォルトで arm64 になるので要注意
  • ECS(Fargate)でデフォルトの設定で動かすなら amd64 でビルドする必要がある
  • AWS CDKを使う場合は2つの対策がある(どちらかを実施)
    • DockerImageAssetplatform: assets.Platform.LINUX_AMD64 を指定して amd64 として動かす
    • FargateTaskDefinitionruntimePlatformcpuArchitecture: ecs.CpuArchitecture.ARM64 を指定し、arm64 として動かす
  • Dockerfile でビルドイメージの指定もできる