yoshikipom Tech Blog

Webエンジニアが「低レイヤこわい」を治した技術書7冊

はじめに

Webエンジニアが低レイヤを学んだ方がいいと思っている理由は大体以下の感じ。

  • システム開発・運用に役立つ(例: スケーラブルなアプリケーション開発、パフォーマンス起因の障害調査)
  • 各技術の得意不得意が理解しやすくなる

学ばなくても機能の開発は可能かもしれないが、質のいいソフトウェア開発を行うためには避けて通れない。

キャリア初期は当然「低レイヤこわい」だったが、ある程度本を読んでいると「全体像は理解している」+「各領域の基本的な部分はちょっと調べれば思い出せる」くらいのレベルにはなった。自分の低レイヤのレベルはここにある本だけでも丁寧に取り組めばかなり自信がつくはず(自分は飛ばし読みしてしまったところもある、、、)。

低レイヤ全般の本

この2冊は低レイヤ周りの技術書記事でもよく見かけるので新鮮さに欠けるかもしれないが、自分も読んで良書だと思った。

プログラムはなぜ動くのか 第3版 知っておきたいプログラミングの基礎知識

プログラムを実行するときにCPUやメモリで何が起きるかわかるようになる本。図を用いてわかりやすく説明されており、まず全体感を知りたいなら最もオススメな本。自分は第2版を読んだので少し古い情報があるところが欠点だったが、2021/05に第3版が出ているのでその問題は解消されていそう。

コンピュータシステムの理論と実装 ―モダンなコンピュータの作り方

論理ゲート、CPU、バーチャルマシン、コンパイラ、OSとボトムアップでコンピュータを作っていく本。各コンポーネントの役割説明と仕様が与えられて下から上に作り上げていく過程でコンピュータの仕組みを実感できる。エミュレータでハードウェア部分の仕組みの理解やデバッグが行え、実装例もインターネット上にたくさんあるのでかなり学びやすい環境ができている。それでもなおここにある中で一番時間をかけた本ではあるが、学びも多かったのでやってよかった。

トピックごとの知識を深める本

ここにあるような入門系の本を各トピック読んでおくと細かいことをググったときの理解度が上がるはず。

入門 モダンLinux ―オンプレミスからクラウドまで、幅広い知識を会得する

カーネル、シェル、アクセス制御、ファイルシステムなどLinuxがOSレベルでサポートする機能について学べる本。"モダンLinux"が対象になっている点も実用的で良い。

ゼロからのOS自作入門

独自のOSを作る本。説明が丁寧でカバレッジが広く、ウィンドウ処理やマルチタスクなど実用的なものを動くサンプル有りで教えてくれるところがスゴい。分量が多く(700ページ超)動かすコストも小さくなかったので途中からは読むだけになってしまったが、他の本ではカバーできていなかったどう動いているか知りたいところを知ることができた。

プログラマーのためのCPU入門 CPUは如何にしてソフトウェアを高速に実行するか

ソフトウェア実行を高速化するためにCPUが持つ様々なハードウェア・ソフトウェア最適化を学べる本。どういう条件だと効率化が難しいといった例をたくさん示してくれる。本来難しいはずの内容だが、わかりやすい構成や各機能の背景の説明によって楽しく理解できた。各種トレードオフにそれぞれのCPU実装がどう対応しているかもわかりやすくまとまっていてよかった。

マスタリングTCP/IP 入門編

ネットワーク関連の基本となるプロトコルを体系的に学べる有名な本。入門編だが、アプリケーション開発で基本となっている部分はわかりやすくカバーしてくれる。

Goならわかるシステムプログラミング

Go言語を使いながらシステムプログラミングを学べる本。 特に実際に使われているプログラミング言語がどうOSと連携しているのか学ぶことができた。言語に依存しない説明も十分にあるがGo言語に結びついた面白い話がたくさんあったため、Go言語を書くならオススメ。

おわりに

こうして振り返ってみるとコンパイラ周りはもう少し知識を深めてみるのも良さそう。

Goアプリケーションでシンプルな設定値管理を実現する

はじめに

あるGoアプリケーションでぼちぼち設定値が増えてきたのでリファクタリングしたのでメモ。以下が達成したかったこと。

  • 設定値の一覧性
  • 読み出し時のシンプルさ
  • 環境変数での設定値インジェクション

動的な値の更新は対応しない。今回の要件では再デプロイでの設定値更新で十分であったため。

全体像

全体像 https://github.com/yoshikipom/go/tree/main/config

.
├── config
│   └── config.yml // 設定ファイル
├── go.mod
├── go.sum
├── internal
│   └── config
│       └── config.go // 設定ファイルと環境変数からgoの構造体へのマッピング
└── main.go // テスト用

使い方

config.yml

port: 8080
key: value

アプリケーション内で一度だけ Initialize関数を呼ぶ。 エラーが出る可能性があるのはInitializeのみにできるのであとは使いたい場所でGetConfigを呼ぶだけ。 使っているライブラリはconfigパッケージに隠蔽されているのでライブラリの変更は安全に行える。 単体テストでテスト用のファイルを渡したくなることがあったので、 Initialize関数で設定ファイルのパスを渡すことにした。

main.go

package main

import (
    "fmt"

    "github.com/yoshikipom/go/config/internal/config"
)

func main() {
    // call only once
    err := config.Initialize("./config/config.yml")
    if err != nil {
        panic(err)
    }

    // you can get config anywhere
    c := config.GetConfig()
    fmt.Printf("%+v", c)
}

実行例

# config.ymlの値が読めている
yoshiki@yoshiki-mbp:go/config ‹main*›$ go run "/Users/yoshiki/work/study/go/config/main.go"              
&{Port:8080 Key:value}%

# 環境変数で渡した"new_value"が読み込めている。(key= でなく KEY= の理由は後述。)
yoshiki@yoshiki-mbp:go/config ‹main*›$ KEY=new_value go run "/Users/yoshiki/work/study/go/config/main.go"
&{Port:8080 Key:new_value}%  

config.goの実装

package config

import (
    "github.com/spf13/viper"
)

var config *MyConfig

type MyConfig struct {
    Port int    `mapstructure:"port"`
    Key  string `mapstructure:"key"`
}

func Initialize(fileName string) error {
    viper.SetConfigFile(fileName)
    if err := viper.ReadInConfig(); err != nil {
        return err
    }

    viper.AutomaticEnv()

    c := &MyConfig{}
    if err := viper.Unmarshal(c); err != nil {
        return err
    }
    config = c
    return nil
}

func GetConfig() *MyConfig {
    if config == nil {
        panic("config is not initialized.")
    }
    return config
}
  • Viper というライブラリを利用

おわりに

使いやすい設定値管理ができてよかった。Viperはシンプルかつ高機能で良い。

納得できるアクションを見つけるための効果的なインシデント振り返り

はじめに

チームでやっていたインシデントに対する振り返り方法がよかったのでメモ。体系的に振り返る方法を書くだけで具体的なインシデントについては触れない。自分が担当していたのはWebバックエンドシステム。

なぜ振り返りをするのか

インシデントが発生したらまずは二次災害が出ず素早く行える方法で修正してしまうことがよくある。それだと負債は増えるし、似たような問題は防げない。 振り返りの目的は、同様の問題の発生を防ぐこと。振り返りでは、抽象化して問題を切り分け、より幅広い問題に対する実現可能なアクションを見つける必要がある。

何を見つける必要があるか

インシデントに立ち向かうためには、以下の3ステップに対する対策が必要となる。

  1. 予防
  2. 検知
  3. 影響の最小化

これに対して、以下のようなアクションを検討することができる。

  1. システム的解決 (適切なエラーハンドリング、自動テストによる検知など)
  2. 開発フロー (チェックリスト追加、コードレビュールール変更など)
  3. 運用面 (要員の追加、運用ルールの変更など)

以上の3ステップに対して、それぞれのアクションを検討し、実現可能なものを採用する。

振り返り方法の一例

自分達のチームでは以下のようなブレインストーミングを行った。

設計、開発、テスト、運用といったフェーズごとに、今回起きた問題に関連のある良くなかった判断や見落とし、構造的課題をみんなで挙げる。この方法は以下の2点で大変よかった。

  • 設計部分に関して深く議論できる (ただ気づいた問題を出し合うだけだと開発者はコーディングや自動テストあたりに重点を置きすぎる)
  • 予防・検知・影響の最小化の各ステップをバランスよく洗い出せる
  • 開発フェーズ順に考えるので各自がただ意見を出し合うよりは見落としが少なくより多くの意見がでる。

各課題に対しては思いつくアクションをあげる。最後に、実行コストと効果を見て行うアクションと優先度を決める。

かなり時間のかかる振り返りになるが、絶対に納得できるアクションを見つけたいのでればオススメである。

おわりに

インシデントが起きるのは悲しいが、振り返りはチームの学びになるし、将来のインシデントを潰せるのでがんばろう。

プラグインアーキテクチャ(マイクロカーネルアーキテクチャ)の考慮事項とSpring Bootによる実践

はじめに

"ソフトウェアアーキテクチャの基礎" (https://tech-yoshikipom.hatenablog.com/entry/2023/01/24/220503 で紹介した本) に出てきたプラグインアーキテクチャ(またはマイクロカーネルアーキテクチャ)をWebシステムで使う時に考えたことやSpring Bootを用いたプラグインアーキテクチャの実装を紹介する。アーキテクチャのイメージは以下。

プラグインアーキテクチャ

プラグインアーキテクチャを使う時に考えたこと

Webシステムにおけるプラグインアーキテクチャの使い所

主に以下の二つの要求をシンプルに解決できると思う。

  • 機能面: 入力(ユーザ、APIのクライアントシステム、リクエストデータ等)によって振る舞いを追加したり減らしたりしたい
  • 保守性: たくさんの手続型のロジックをコンポーネント化 -> 責務の明確化、付け替えや追加が容易

機能面のほうは主にA/Bテストがしたかったり、複数のクライアントからの要求に柔軟に答える必要がでたときの話である。単純なif文やStrategyパターンだけで実装すると入力ごとの処理に差異が増えてくると差分が分かりづらくなり、保守性が一気に落ちることがある。保守性の方は実際に”プラグイン”が要件として必要なわけではないが、プラグインとして処理を実装することで開発体験を向上できる。

検討事項

プラグインアーキテクチャを使うときは以下の点は設計しておきたい。

  • 利用するプラグインの決定方法
    • ランタイムで決定: なんらかの方法で実行中でも切り替えられる (例: DBに設定を持って、ツールで変更する)
    • コンパイルで決定: ビルドした時点で決まる (例: 設定ファイルにプラグインのリストを書く)
  • 何をプラグインにするか
    • プラグインの同じインターフェースは同じ
    • inputとoutputの設計が必要
  • 複数プラグインの実行方法
    • 順次実行
    • 並列実行

ここで実装するサンプルでは「コンパイルベースの設定ファイル」、「プラグインの内容はダミー」、「並列実行は未対応」とする。

プラグインアーキテクチャのSpring Bootによる実装例

使用例

作るものを明確にするため使用例から。

設定ファイル(application.yml)

myapp:
  plugin:
    pluginExecutorNamesMap:
      groupA:
        - fooPlugin
      groupB:
        - barPlugin
        - fooPlugin

アプリケーションクラス

@SpringBootApplication
@Slf4j
@RequiredArgsConstructor
@ConfigurationPropertiesScan
public class PluginSampleApplication implements CommandLineRunner {

  private final CoreExecutor coreExecutor;

  public static void main(String[] args) {
    SpringApplication.run(PluginSampleApplication.class, args);
    System.exit(0);
  }

  @Override
  public void run(String... args) throws Exception {
    log.info("===== run app for 'groupA' =====");
    coreExecutor.execute(new CoreExecutorInput("groupA"));
    log.info("===== run app for 'groupB' =====");
    coreExecutor.execute(new CoreExecutorInput("groupB"));
    log.info("===== run app for 'groupC (no config)' =====");
    coreExecutor.execute(new CoreExecutorInput("groupC"));
  }
}

アプリケーションログ

===== run app for 'groupA' =====
process before plugins is running
FooPlugin is running!
process after plugins is running
===== run app for 'groupB' =====
process before plugins is running
BarPlugin is running!
FooPlugin is running!
process after plugins is running
===== run app for 'groupC (no config)' =====
process before plugins is running
process after plugins is running

設計

プラグインアーキテクチャクラス設計

  • core package
  • plugin package
    • プラグインの実装をする。今回はサブパッケージは作らず、実装はサンプルでログを出力するのみ。

実装

https://github.com/yoshikipom/java/tree/main/plugin-sample/src/main/java/com/yoshikipom/dev/pluginsample

他のコードはサンプルであまり内容がないため、プラグインの1実装と PluginExecutorのみ解説。

BarPlugin.java

@Component("barPlugin")
@Slf4j
public class BarPlugin implements Plugin {

  @Override
  public PluginOutput execute(PluginInput pluginInput) {
    log.info("BarPlugin is running!");
    return new PluginOutput();
  }
}
  • coreパッケージのPluginインターフェースを実装
  • PluginExecutorにDIするため @Component が必要
  • @Component("barPlugin")"barPlugin"プラグイン名としてapplication.ymlに記述する

PluginExecutor.java

@RequiredArgsConstructor
@Component
@Slf4j
public class PluginExecutor {

  private final PluginConfig pluginConfig;
  private final Map<String, Plugin> pluginMap;

  public void execute(String pluginGroupName) {
    // load config
    List<String> pluginNames = pluginConfig.pluginMap()
        .getOrDefault(pluginGroupName, null);

    // if no plugin config, return
    if (pluginNames == null) {
      return;
    }

    // execute plugins one by one
    for (var pluginName : pluginNames) {
      Plugin plugin = pluginMap.get(pluginName);
      PluginInput pluginInput = new PluginInput(); // you can add any input to this class
      PluginOutput pluginOutput = plugin.execute(pluginInput);
      // consume pluginOutput if necessary
    }
  }
}

おわりに

Spring BootのDIや設定ファイル管理の機能を使うと簡単にプラグインアーキテクチャが実現できた。実装すると検討ポイントや保守性に関するメリットがよくわかった。

技術書を自費で購入して読む理由

はじめに

技術書を自費で買うのか?というようなコメントがあった。結論としては現状は自費で買って読んでいる。確かにそこそこコストがかかるのになぜなのか考えてみた。以下すべて、一般論ではなく自分の一意見。

なぜ読むようになったか

学生のころは技術書高すぎる、、、と思っていたのでたまに研究室の先輩に借りたり大学図書館で読む程度だった。 習慣的に読むようになったのは、運良く最初に入った会社がそういうカルチャーだったからだと思う。具体的には、入社前にオススメの技術書リストを提供、技術書を買うための補助、業務時間内で輪読会といった部分で自然と技術書に触れるようになっていた。

今でも技術書を購入して読む理由

転職してからは補助がなくなったが、今でも技術書には価値を感じていて購入し続けているので以下に理由を3つ書いてみる。

高クオリティかつ体系的な知識を得られるから

まず、技術書は無料のコンテンツと比べてクオリティが高いことが多い。それは、本になるものは作者が知識を詰め込んでいるのはもちろんのこと、出版前にはレビューされたコンテンツだからである。 また、有名な本はレビューもたくさんあるので、いい本や自分の目的にあった本は見つけやすく、必要なコンテンツを探す時間を短縮できる。

また、技術書はあるテーマのためにそれなりのページが費やされており、小さくなりがちな無料コンテンツと比較すると、トップダウンで体系的な知識を学ぶ時はとても理解しやすい。 体系的な知識があるとなにか調べたい時にすぐ検索ワードが思いついたり、検索結果をすんなり理解できたりするので、技術書で読む優先度が高い。

仕事が楽しくなるから

自分は子どものときからゲームの攻略本を読むのが好きなタイプだった。攻略本を読めば自分がプレイで通らなかったルートや使わなかったキャラクターを知ることができた。 仕事でも技術書を読んでおけば自分達が行なわなかった選択に気づくことができ、それらについて話し合ったりする中で連鎖的に知識を増やしていけるのが楽しい。

また、自分は「活躍した方が仕事は楽しい」と最初の会社で教えられたし、今はある程度そう思っている。なので、知識が増やして重要なタスクを任せてもらえる確率が上げることも仕事を楽しむためには必要だと考えている。

投資した以上に回収できるから

具体的に技術書がどれくらい自分のキャリアに貢献しているのかはもちろんわからないが、事実として身につけた知識はそれなりにアウトプットされているし、給料も増えている。 給料が増えるのは面接で体系的な知識が役に立ったからかもしれないし、普段の仕事がスピードアップするからかもしれない。 逆に技術書を全く読まなかったケースを考えてみると、今のようには働けていなかったと思う。

おわりに

言語化難しい。Web or 本とか、電子 or 紙とかを他のエンジニアはどう使い分けてるのか知りたい。

ソフトウェアアーキテクトに必要なシステム設計知識を学んだ17冊

はじめに

仕事では2022年までは主にバックエンドサービスの開発リードをしていたが、今後はソフトウェアアーキテクトとしてサービス全体の設計や横断的なシステムの設計をメインでやっていく。自分で悩んだ時の辞書としての役割と、おすすめの本を求められたときのリストとしての役割を兼ねて今まで読んだシステム設計系の本をまとめる。

チームの移り変わりはあったものの、基本的に以下のような環境で働いてきたので、そこで役立つ情報に偏ってる可能性有り。

  • マイクロサービス
  • バックエンド
  • 同期通信はREST APIでの連携が多め

アーキテクチャ・デザイン全般

ソフトウェアアーキテクチャの基礎

1部でアーキテクトは何を考える必要があるのか説明され、2部で様々なパターンを学ぶことができる。3部ではアーキテクトが効果的に働くための方法を知ることができる。図や具体例が多くて読みやすくかつ網羅的なのでソフトウェアアーキテクチャとは何か知りたい人にオススメ。

Clean Architecture 達人に学ぶソフトウェアの構造と設計

多数の設計原則を説明した上でメンテナブルでスケーラブルなクリーンアーキテクチャを設計する実践的な方法を学ぶことができる。開発チームで設計原則に基づいて意思決定することも多いのでその原則を学ぶだけでも価値がある。自分の場合、肝心のアーキテクチャ部分は社会人一年目だといまいち価値が理解できなかったので、あとから読み直した。

Design It!

デザイン思考に基づいて良いアーキテクチャをどう作り上げるかステップバイステップで説明される。”第Ⅲ部 アーキテクトの道具箱”ではどうやって問題を理解し、設計を可視化し、評価するかについて、様々なアクションで提案されるので、設計プロセスに困ったら目を通し直す価値あり。

ソフトウェアシステムアーキテクチャ構築の原理

とても分厚い上にあまりわかりやすい具体例はない。しかし、アーキテクチャのプロセス、考えなければならないことが網羅的にモレなく記述してある。自分の場合、深く設計が必要なときは一度は開く良書となっている。

データ指向アプリケーションデザイン

直接的にアーキテクチャ設計を取り扱ってはいないが、設計内に含まれるであろうテクノロジーの特性やトレードオフについて学ぶことができる。特に分散システムについてのトレードオフは深く記述されている。アプリケーション内だけでなく、システム全体を設計するのであれば是非読んでほしい。

マイクロサービス

マイクロサービスアーキテクチャ

マイクロサービスについて、利点欠点、何をするべきか、どう進めるべきかを幅広く説明している良書。 自分が読んだのは1st edition。 2nd editionはKubernetestとかマイクロフロントエンドとか流行りに合わせてかなり情報が追加されている。また、構成も整理されていて読みやすそう。

マイクロサービスパターン 実践的システムデザインのためのコード解説

具体的なエピソードに沿って、具体的な設計とコードを使ってパターンを解説してくれる。理想的なマイクロサービスを作る難しさを痛感した。

ソフトウェアアーキテクチャ・ハードパーツ

"ソフトウェアアーキテクチャの基礎"の作者らによる続編。マイクロサービスで出てくる様々なトレードオフに対してどう考えるかを教えてくれる。設計に関する引き出しを増やし、整理してくれる良書だった。

ドメイン駆動設計

エリック・エヴァンスのドメイン駆動設計

DDDの哲学が学べる。一回で理解しきれなかったが、実践や他の本を経て戻ってくるとよくまとまっている印象を受けた。実装の具体例は下の2冊で学んだ。

ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本

各パターンの意味や実装例がとてもわかりやすいし読みやすい。先にこの本で概要を掴んでから他のDDD本を読みたかった。

現場で役立つシステム設計の原則

特にタイトルにはDDDと入っていないが、DDDを用いた設計がメインの本。Java, Spring Frameworkを用いてオブジェクト指向を突き詰めたような本だった。保守しやすい理想的なアプリケーションの書き方の参考にはなるが、DDD初心者には上2冊のほうがオススメ。

要件定義

はじめよう!プロセス設計 ~要件定義のその前に

はじめよう! 要件定義 ~ビギナーからベテランまで

はじめよう!システム設計 ~要件定義のその後に

要件定義が何かもわかっていない最初の段階で読んだ。要件定義とシステム設計の流れが丁寧に説明されているので新卒にもオススメできる。要件定義から自分でしないとしても、どういう流れで要件が出来上がるか知ることで要件を適切に満たす設計ができるようになるので早めに学ぶと良い。

Web, Web API

Webを支える技術

Webまわりの基本的な技術が丁寧に説明されている。ちょっと古い情報、あまり仕事で出会わない技術も入っているが、REST周りの情報の網羅性が高いので今でもオススメできる。

プロになるためのWeb技術入門

「Webを支える技術」と近い範囲もカバーしているが、この本を読むとWebアプリケーションがどう動くかがより理解できる。

Web API: The Good Parts

Web APIに特化した本。実践的で網羅性が高く、API設計初心者のときは何度も参照した。仕事でAPI設計するならオススメ。

おわりに

新しいバージョンが出てるものや、読んでから時間が経っているものは読み直したい、、、。

GoReplayのDockerfileがAppleシリコン(M1) Macでうまく動かなかったので自分で書いた

経緯

GoReplayのリポジトリにあったDockerfileはIntel Macだとうまく動いたが、Appleシリコン(M1) Macではうまく動かなかった。 Dockerfile: https://github.com/buger/goreplay/blob/1.3.3/Dockerfile 実行時に以下のエラーが発生。

tunl0: SIOCETHTOOL(ETHTOOL_GLINK) ioctl failed: Function not implemented

--platform オプションでamd64用にビルドして実行してみたが、結果は同様だった。 たまにこういうことはありそう?

The emulation is not perfect https://github.com/docker/for-mac/issues/5328

結論

以下のDockerfileを書いた。これならIntel MacでもAppleシリコン(M1) Macでもうまく動く。

FROM golang:1.19 as builder

RUN apt-get update && apt-get install ruby vim-common -y

RUN apt-get install flex bison -y
RUN wget http://www.tcpdump.org/release/libpcap-1.10.0.tar.gz && tar xzf libpcap-1.10.0.tar.gz && cd libpcap-1.10.0 && ./configure && make install

WORKDIR /go/src/github.com/buger/goreplay/
RUN wget https://github.com/buger/goreplay/archive/refs/tags/v2.0.0-rc2.tar.gz -O gor.tar.gz && tar xzf gor.tar.gz -C . --strip=1
RUN go mod vendor
RUN go build -mod=vendor -tags netgo -o gor -ldflags "-extldflags \"-static\""


FROM alpine:3.17

RUN apk add bash
RUN apk add --no-cache ca-certificates openssl

COPY --from=builder /go/src/github.com/buger/goreplay/gor /
ENTRYPOINT ["./gor"]

build & run

# build
$ docker build --tag goreplay:2.0.0 .                 

# run
$ docker run --rm -it goreplay:2.0.0 -h
Gor is a simple http traffic replication tool written in Go. Its main goal is to replay traffic from production servers to staging and dev environments.

なぜ元のDockerfileだとダメか

以下で取得してきているGoReplayのプログラムがファイル名でもわかるようにx64のCPUアーキテクチャ用にビルドされたものなのが原因。

RUN wget https://github.com/buger/goreplay/releases/download/${RELEASE_VERSION}/gor_${RELEASE_VERSION}_x64.tar.gz -O gor.tar.gz

Dockerfile: https://github.com/buger/goreplay/blob/1.3.3/Dockerfile

--platform オプションをつけずにdocker buildコマンドを実行するとホストのCPUアーキテクチャ用のbuildする。 - Intel Mac -> amd64 (= x86 https://ja.wikipedia.org/wiki/X64) - Appleシリコン(M1) Mac -> arm64v8 dockerで使われるCPUアーキテクチャの種類: https://github.com/docker-library/official-images#architectures-other-than-amd64

AppleシリコンでBuildした場合、OSはarm64v8のCPUを想定するが、元のDockerfileだとx86用のgoプログラムを取得しているのでミスマッチが起こっている。

どう直したのか

go build コマンドもdocker buildと同様にdefaultは実行ホストのCPUアーキテクチャ用にbuildする。なので、Dockerfileにgo buildコマンドを入れた。 buildコマンドはMakefileから必要なオプションを持ってきた。 https://github.com/buger/goreplay/blob/1.3.3/Makefile ちなみにGOOS=linux GOARCH=amd64 go buildのようにすれば成果物のCPUアーキテクチャの指定もできるが、今回は指定しないほうが都合が良かったので特に指定していない。buildするCPUアーキテクチャとと実行するCPUアーキテクチャが違う時は指定が必要。

Makefileはbuild用のDockerコンテナ内でビルドする作りになっていたが、docker in dockerをしたくなかったのでマルチステージビルドを使って書いてみた。golangイメージでビルドして、alpineイメージで実行。goのプログラムは実行ファイルだけあれば実行できるのですごい。