なにもわからない

気分で技術系の雑記を書きます

Clean Craftsmanshipを読んだ

仕事から家に帰ったとき、鏡に映った自分を見て「今日はいい仕事をした」と言うだろうか?それとも、すぐにシャワーを浴びるだろうか?

あまりにも多くの人たちが、一日の終わりに心残りを感じている。あまりにも多くの人たちが、標準以下の仕事をしていると感じている。あまりにも多くの人たちが、品質を犠牲にして速度が求められていると感じている。あまりにも多くの人たちが、生産性と品質は反比例すると考えている。

本書では、そうした考え方を打ち破る。本書はより良く働くための本だ。良い仕事をするための本だ。 仕事を速くして、生産性を高め、毎日自分が書いたものに誇りが持てるように、すべてのプログラマーが知るべき規律とプラクティスについて書いた本だ。 (p. 18「はじめに」)

2度目の『Clean Craftsmanship』を読み終えた。1度目は読書会に参加する形で、今回はチームの読書会を主導する形で読んだ。自身の普段の仕事に対するものの見方を後押ししてくれた。端的に、勇気を与えられた。 本書を読む前は、漠然とした「私は正しいことをやっているのか」という迷いが多くあったが、迷いが減り、目前の仕事に集中できるようになり、自らの仕事に自信が持てるようになった。この効用は大きい。

他のCleanシリーズ、『Clean Architecture』や『Clean Code』に比べて知名度が低いように思うが、本書を読む人が増えてくれると嬉しい。

サブタイトルに「規律、基準、倫理」とある通り、本書のよいところは、社会に生きる職業人のプログラマとして持つべき考えについて述べている点にある。類似書に『達人プログラマー』や『チーム・ギーク』などがあるが、プログラマーの社会的・歴史的位置づけについて書かれた本は他にはないのではないだろうか。 倫理や社会と聞くと警戒する人もいるかもしれないが、押し付けがましい説教を延々聞かされるのではないので安心してほしい。

訳者あとがきにもあるように、「過去の著作から大事なことが抜粋されてまとめられているため、大変オトクなパッケージにもなっている」。他のCleanシリーズや類書で見覚えのある話が多々現れる。

ソフトウェア・テストの技法や考え方に注目して言えば、『単体テストの考え方/使い方』が理解を助けてくれると思う。重複する部分も大いにあるが、この2冊を読むことで習熟度がぐんと上がると思う。プラクティスや、物事をうまくやるための基本的な考えかたに注目して言えば、『エクストリーム・プログラミング』を併読するのがよいだろうと思う。

大変オトクなパッケージであると同時に、人類史の参照もありがたい。

当然、コンピューターやプログラマーに関する歴史にも触れている。

第2章から第5章(p.41〜p.207)はテスト駆動開発リファクタリングの実践が主である。ミーティングツールの音声をオンにして、チームメンバーとワイワイそれぞれ好きな言語で実装するのは楽しいし、テスト駆動開発の威力が体感できるいいワークショップになると思う。ただ、それなりに時間がかかる。ちなみに、私は1度目はDeno、2度目はJavaで実装した。

Cleanシリーズを読んだことのある人にはお分かりかもしれないが、相変わらず語気は若干強めで、読了後の感想戦でもちらほらとそういった感想があった。筆者のスタイルに調子をアジャストして読むのはよくある読書スキルであるし、「はじめに」で著者もこう書いている。

本書を読むと、これこそが「クラフトマンに到達するたったひとつの道」だと感じるかもしれない。だが、私にとってはそうであっても、あなたにとっては必ずしもそうではないかもしれない。本書はあくまでも私の道を示したものだ。あなたには、もちろん、あなた自身の道を選択する必要がある。

とはいえ、TDDに対する姿勢については、人によっては解説が必要かもしれない。例えば、テストのカバレッジについて第6章で以下のように記している。

カバレッジの数値はいくつにすべきだろうか?80%だろうか?それとも90%?多くのチームがそのような数値を満足気に報告している。だが、さきほどの60年前の論文の答えは違う。彼らの答えは「100%」である。

TDDには諸派あり、必ずしもテストファーストのアプローチを取らなかったり、カバレッジをどの程度重視するかなど、グラデーションがある。私は、テストファーストのアプローチを取ることが少ないし、カバレッジをさほど重視しない。コストパフォーマンスのバランス重視であるといえる。そのために、100%を漸近的な目標として掲げることには懐疑的だ。このように書いてあること全てを信じる必要はないのである。

(…が、ミューテーション・テストの効果を再発見したり、「1対1の対応関係」を「対応関係の解消」する様子を見ていると、100%を目指すのが正解のようにも思えて来るし、単体テストをよりテストサイズの大きな領域に移行することを考えると、逆に不正解のようにも思えてくる。)

TDDの諸流派の話については、fukabori.fmの114回でt-wadaさんが詳しく述べられている。

fukabori.fm

macOS Mojave で gdb を動かす(2020年10月)

2017年6月発表の macOS High Sierra(10.13)から SIP(System Integrity Protection: システム整合性保護)が導入され、gdb が動かなくなりました。過去には SIP 自体を無効化しなければなりませんでしたが、どうも最近ではそこそこ妥当な対処方法が取れるようになったようです。

困った話なので様々な対処方法がネットにありますが、現状にマッチした情報が見つかりませんでしたので、私の環境で動作した方法を書き記しておきます。 support.apple.com

対処方法

対処方法の手順はざっくり4つです。ポイントは SIP の無効化(csutil disable)や一部無効化(csutil enable --without=debug)しなくても動くという点です。

  1. ~/.gdbinitset startup-with-shell off を書く
  2. Code Signing Certificate を作る(Keychain\ Access.app
  3. --entitlements 付きで gdbcodesign する
  4. taskgated を殺す

動かすもの

今回動かすものはこちらの解説記事のものをそのまま使わせていただきました。 rat.cis.k.hosei.ac.jp

再現手順は以下の通りです。

$ gcc -g -O0 bubblesort.c

$ gdb a.out
GNU gdb (GDB) 10.1
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-apple-darwin19.6.0".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.out...
Reading symbols from /private/tmp/gdb/a.out.dSYM/Contents/Resources/DWARF/a.out...
(gdb) break main
Breakpoint 1 at 0x100003db8: file bubblesort.c, line 9.
(gdb) run
Starting program: /private/tmp/gdb/a.out
Unable to find Mach task port for process-id 8203: (os/kern) failure (0x5).
 (please check gdb is codesigned - see taskgated(8))
(gdb) quit

1. ~/.gdbinitset startup-with-shell off を書く

brew install gdb 時にもメッセージが出る通り、~/.gdbinitset startup-with-shell off の記述が必要です。 これがないとコード署名の問題が解消しても During startup program terminated with signal SIGTRAP, Trace/breakpoint trap. が発生するようです。

2. Code Signing Certificate を作る

Keychain Access を使って Code Signing Certificate を作ります。

  • Keychain Access を開きます
  • メニューから「Keychain Access > Certificate Assistant > Create a certificate」を選びます
  • Create Your Certificate を設定します
    • Namegdb とします。この名前を後のコマンドで指定することになります。
    • Identity TypeSelf Signed Root を選択します
    • Certificate TypeCode Signing を選択します
    • Let me override defaults にチェックを入れます
  • Certificate Information を設定します
    • Validity Period (days) はデフォルト1年ですが10年程度に設定しておくとよいと思います
  • Key Pair Information を設定します
    • デフォルト選択肢のままでも動くようですが、念のため Key Size512 bitsAlgorithmECC にしておくとよいと思います
  • しばらく Continue で飛ばします
  • Specify a Location for The Certificate を設定します
    • KeychainSystem に設定します
  • Conclusion Your certificate has been successfully created. と表示されます
    • 作成された証明書の内容を確認してみてください
  • 一覧から作成された証明書(Name = gdb, Kind = certificate)をダブルクリックして詳細を開きます
    • Trust の三角矢印をクリックして開き、When using this certificateAlways Trust に設定します
    • この手順は不要かもしれません

f:id:tamakiii:20201029214015p:plain
Create Your Certificate
f:id:tamakiii:20201029214055p:plain
Certificate Information
f:id:tamakiii:20201029214127p:plain
Key Pair Information
f:id:tamakiii:20201029214157p:plain
Specify a Location for The Certificate
f:id:tamakiii:20201029223335p:plain
When using this certificate

3. --entitlements 付きで gdbcodesign する

以下の内容の gdb.xml を作ります。何度も使うものではないので適当な場所に作成してよいと思います。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.cs.debugger</key>
    <true/>
</dict>
</plist>

com.apple.security.cs.debugger という設定項目自体は、公式のドキュメントによると Availability は macOS 10.7+ とのことです。 developer.apple.com

codesign の状態を確認します

$ codesign -vv $(which gdb)
/usr/local/bin/gdb: code object is not signed at all
In architecture: x86_64

gdb--entitlements を指定して codesign します。このとき、-fs の引数に Keychain Access に登録したときの名前を指定します。

$ codesign --entitlements gdb.xml -fs gdb $(which gdb)

成功するとこのようになります

$ codesign -vv $(which gdb)
/usr/local/bin/gdb: valid on disk
/usr/local/bin/gdb: satisfies its Designated Requirement

4. taskgated を殺す

OS を再起動するか taskgated を殺して Code Sign を有効化します。

$ sudo pkill taskgated
Password:

しばらく待たないと以下のようにスレッドを立ち上げた所でハングするかもしれません。

Reading symbols from a.out...
Reading symbols from /private/tmp/gdb/a.out.dSYM/Contents/Resources/DWARF/a.out...
(gdb) break main
Breakpoint 1 at 0x100003db8: file bubblesort.c, line 9.
(gdb) run
Starting program: /private/tmp/gdb/a.out
[New Thread 0x2503 of process 8351]
[New Thread 0x2103 of process 8351]

動作確認

冒頭の再現手順を試します。期待通り動作しました。

Reading symbols from a.out...
Reading symbols from /private/tmp/gdb/a.out.dSYM/Contents/Resources/DWARF/a.out...
(gdb) break main
Breakpoint 1 at 0x100003db8: file bubblesort.c, line 9.
(gdb) run
Starting program: /private/tmp/gdb/a.out
[New Thread 0x1d03 of process 8511]
[New Thread 0x1f03 of process 8511]
warning: unhandled dyld version (16)

Thread 2 hit Breakpoint 1, main (argc=1, argv=0x7ffeefbff3f0) at bubblesort.c:9
9         int array[4] = {4, 1, 3, 2};
(gdb) 

動作環境情報

  • macOS Catalina (10.15.7)
$ gdb --version
GNU gdb (GDB) 10.1
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/4.2.1
Apple clang version 12.0.0 (clang-1200.0.32.21)
Target: x86_64-apple-darwin19.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
$ csrutil status
System Integrity Protection status: enabled.

この記事の内容を含むリポジトリです。こちらも参考にどうぞ。 github.com

参考

HomeBrew で Custom Build from Source する

最近ソースコードをビルドする機会が増えてきて、ホストOS上で常用するツールについては少し管理できた方がよさそうということで、HomeBrew で Build from Source を試しています。 例えば vimclientserver オプションを有効化したい、とかが brew install でできるようになります(まだできていない)。

HomeBrew は独特の世界観があってか[?] 情報が少ないからなのか少し混乱しましたが、慣れればまあ使えるんじゃないかと思います。 慣れれば自作のソフトウェアを配布する敷居が下がったり、macOS を使っている人の環境向上に寄与できていいんじゃないでしょうか。知らんけど。

Tap を作る

まずは Tap を作ります。Tap とは A Git repository of Formulae and/or commands らしいです。 Formula は *.rb になっている The package definition、つまり vim git make とかです。

HomeBrew の用語はこちら(Keg、Cellar、Cask など)。 docs.brew.sh

リポジトリの名前を考えます。tamakiii/homebrew-core は本家の homebrew/homebrew-core と競合する(本家に Pull Request を送りたいときに困る)ので tamakiii/homebrew-tap とします。

$ brew tap-new tamakiii/homebrew-tap
Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/tamakiii/homebrew-tap/.git/
[master (root-commit) 390ec63] Create tamakiii/tap tap
 3 files changed, 85 insertions(+)
 create mode 100644 .github/workflows/publish.yml
 create mode 100644 .github/workflows/tests.yml
 create mode 100644 README.md
==> Created tamakiii/tap
/usr/local/Homebrew/Library/Taps/tamakiii/homebrew-tap

When a pull request making changes to a formula (or formulae) becomes green
(all checks passed), then you can publish the built bottles.
To do so, label your PR as `pr-pull` and the workflow will be triggered.

作った Tap がこちら。README と Formula/ .github/workflows があるだけの簡単な構成です。 名前の homebrew- 部分は省略できるようです。

$ ls -lsa /usr/local/Homebrew/Library/Taps/tamakiii/homebrew-tap
total 8
0 drwxrwxr-x   6 tamakiii  admin  192 10 28 19:23 .
0 drwxrwxr-x   4 tamakiii  admin  128 10 28 19:23 ..
0 drwxrwxr-x  12 tamakiii  admin  384 10 28 19:28 .git
0 drwxrwxr-x   3 tamakiii  admin   96 10 28 19:23 .github
0 drwxrwxr-x   3 tamakiii  admin   96 10 28 19:27 Formula
8 -rw-rw-r--   1 tamakiii  admin  254 10 28 19:23 README.md

$ brew --repo tamakiii/tap
/usr/local/Homebrew/Library/Taps/tamakiii/homebrew-tap

$ brew --repo tamakiii/homebrew-tap
/usr/local/Homebrew/Library/Taps/tamakiii/homebrew-tap

ビルドしてみる

試しに本家の vim.rb を入れてみます。

$ cp /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/vim.rb Formula/

コピーした中身はこちら。依存関係やインストールコマンド本体、テストなどがあり、単体の Ruby ファイルで完結して動きます。 github.com

追加した vim Formula は、<tap-name>/<formula-name> で指定できます。

$ brew info tamakiii/tap/vim
tamakiii/tap/vim: stable 8.2.1900 (bottled), HEAD
Vi 'workalike' with many additional features
https://www.vim.org/
Conflicts with:
  ex-vi (because vim and ex-vi both install bin/ex and bin/view)
  macvim (because vim and macvim both install vi* binaries)
/usr/local/Cellar/vim/8.2.1900 (1,953 files, 33.3MB) *
  Built from source on 2020-10-28 at 19:29:45
From: https://github.com/tamakiii/homebrew-tap/blob/HEAD/Formula/vim.rb
License: Vim
==> Dependencies
Required: gettext ✔, lua ✔, perl ✔, python@3.9 ✔, ruby ✔
==> Options
--HEAD
        Install HEAD version
==> Analytics
install: 83,045 (30 days), 238,945 (90 days), 873,774 (365 days)
install-on-request: 80,611 (30 days), 231,601 (90 days), 835,450 (365 days)
build-error: 0 (30 days)

ビルド出来ました。Bottle が落とせなくて怒られていますが今回は気にしなくてよさそうです。

$ brew install tamakiii/tap/vim
Updating Homebrew...
Warning: tamakiii/tap/vim 8.2.1900 is already installed and up-to-date
To reinstall 8.2.1900, run `brew reinstall vim`

$ brew reinstall tamakiii/tap/vim
==> Downloading https://homebrew.bintray.com/bottles-tap/vim-8.2.1900.catalina.bottle.tar.gz
##O=#  #
curl: (22) The requested URL returned error: 404 Not Found
Error: Failed to download resource "vim"
Download failed: https://homebrew.bintray.com/bottles-tap/vim-8.2.1900.catalina.bottle.tar.gz
Warning: Bottle installation failed: building from source.
==> Downloading https://github.com/vim/vim/archive/v8.2.1900.tar.gz
Already downloaded: /Users/tamakiii/Library/Caches/Homebrew/downloads/094d05292a53960f72fae76726aee6cb028b92fbdabc0eb56191a4308b5f8821--vim-8.2.1900.tar.gz
==> Reinstalling tamakiii/tap/vim
Warning: A newer Command Line Tools release is available.
Update them from Software Update in System Preferences or run:
  softwareupdate --all --install --force

If that doesn't show you an update run:
  sudo rm -rf /Library/Developer/CommandLineTools
  sudo xcode-select --install

Alternatively, manually download them from:
  https://developer.apple.com/download/more/.

==> ./configure --prefix=/usr/local --mandir=/usr/local/Cellar/vim/8.2.1900/share/man --enable-multibyte --with-tlib=ncurses --with-compiledby=Homebrew --enable-csco
==> make
==> make install prefix=/usr/local/Cellar/vim/8.2.1900 STRIP=/usr/bin/true
🍺  /usr/local/Cellar/vim/8.2.1900: 1,953 files, 33.3MB, built in 1 minute 27 seconds

vim Formula には Bottle がないのでこれで良いですが、設定されているものには --build-from-source をつける必要がありそうです。せいぜい数台のマシンで動かすものなので Bottle の記述は削るとよさそうです。

また、./configuremake の出力がほしいところなので --verbose をつけるとよさそうです。 --debug はちょっとうるさかったのでやめました。

$ brew reinstall --force --verbose --build-from-source tamakiii/tap/vim
rm /usr/local/bin/ex
rm /usr/local/bin/rview
rm /usr/local/bin/rvim
rm /usr/local/bin/vi
rm /usr/local/bin/view
rm /usr/local/bin/vim

...

/usr/bin/sandbox-exec -f /private/tmp/homebrew20201028-85925-14282ki.sb nice ruby -W0 -I $LOAD_PATH -- /usr/local/Homebrew/Library/Homebrew/build.rb /usr/local/Homebrew/Library/Taps/tamakiii/homebrew-tap/Formula/vim.rb --verbose
tar xof /Users/tamakiii/Library/Caches/Homebrew/downloads/094d05292a53960f72fae76726aee6cb028b92fbdabc0eb56191a4308b5f8821--vim-8.2.1900.tar.gz -C /private/tmp/d20201028-85926-10g8sdh
cp -pR /private/tmp/d20201028-85926-10g8sdh/vim-8.2.1900/. /private/tmp/vim-20201028-85926-1g5a3uf/vim-8.2.1900
chmod -Rf +w /private/tmp/d20201028-85926-10g8sdh
==> ./configure --prefix=/usr/local --mandir=/usr/local/Cellar/vim/8.2.1900/share/man --enable-multibyte --with-tlib=ncurses --with-compiledby=Homebrew --enable-cscope --enable-terminal --enable-perlinterp --enable-rubyinterp --enable-python3interp --enable-gui=no --without-x --enable-luainterp --with-lua-prefix=/usr/local/opt/lua --enable-fail-if-missing
configure: creating cache auto/config.cache
checking whether make sets $(MAKE)... yes
checking for gcc... clang
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out

...

/usr/bin/sandbox-exec -f /private/tmp/homebrew20201028-92467-f57b26.sb nice ruby -W0 -I $LOAD_PATH -- /usr/local/Homebrew/Library/Homebrew/postinstall.rb /usr/local/Homebrew/Library/Taps/tamakiii/homebrew-tap/Formula/vim.rb
==> Summary
🍺  /usr/local/Cellar/vim/8.2.1900: 1,953 files, 33.3MB, built in 1 minute 45 seconds

homebrew/homebrew-core の場合

homebrew/homebrew-core 自体も HomeBrew 的には Tap の扱いになっているようです。

$ tree -L 2 /usr/local/Homebrew/Library/Taps
/usr/local/Homebrew/Library/Taps
├── homebrew
│   ├── homebrew-brewdler
│   ├── homebrew-bundle
│   ├── homebrew-cask
│   ├── homebrew-cask-versions
│   ├── homebrew-core
│   └── homebrew-services
├── osx-cross
│   ├── homebrew-arm
│   └── homebrew-avr
├── qmk
│   └── homebrew-qmk
└── tamakiii
    ├── homebrew-core
    └── homebrew-tap

15 directories, 0 files

homebrew/homebrew-core にコミットしたい場合も、GitHub 上でリポジトリを Fork して tap すれば、自分用の Tap と同様に、変更・ビルド・Pull Request の作成ができそうです。

$ brew tap tamakiii/homebrew-core
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/cask).
==> Updated Casks
logos                            rotki                            springtoolsuite                  switchhosts                      wechatwebdevtools

$ brew --repo tamakiii/homebrew-core
/usr/local/Homebrew/Library/Taps/tamakiii/homebrew-core

$ ls -lsa /usr/local/Homebrew/Library/Taps/tamakiii/homebrew-core
total 48
0 drwxrwxr-x    13 tamakiii  admin     416 10 28 16:03 .
0 drwxrwxr-x     4 tamakiii  admin     128 10 28 19:23 ..
0 drwxrwxr-x    14 tamakiii  admin     448 10 28 16:56 .git
0 drwxrwxr-x     8 tamakiii  admin     256 10 28 16:03 .github
0 drwxrwxr-x   238 tamakiii  admin    7616 10 28 16:03 Aliases
8 -rw-rw-r--     1 tamakiii  admin     884 10 28 16:03 CODEOWNERS
8 -rw-rw-r--     1 tamakiii  admin    2960 10 28 16:03 CONTRIBUTING.md
0 drwxrwxr-x  5313 tamakiii  admin  170016 10 28 18:19 Formula
8 -rw-rw-r--     1 tamakiii  admin    1334 10 28 16:03 LICENSE.txt
8 -rw-rw-r--     1 tamakiii  admin     706 10 28 16:03 README.md
0 drwxrwxr-x     4 tamakiii  admin     128 10 28 16:03 cmd
8 -rw-rw-r--     1 tamakiii  admin    3567 10 28 16:03 formula_renames.json
8 -rw-rw-r--     1 tamakiii  admin    1144 10 28 16:03 tap_migrations.json

自作のソフトウェアを配布したい場合には Formula をインタラクティブに作る機能も用意されているようです。 gabecc.me

バージョンロックの仕組みがイマイチ使いにくかったりで少し思う所もある HomeBrew ですが、今日も多くの人に使われているツールではあるはずです。 昔ほど Formula が壊れることもほぼなくなった気がしますし、相当なゲームチェンジャーが表れない限りは当面現役なんじゃないかと思います。

また make の話してる(2020年9月14日)

私の大好きな Makefile の話が盛り上がっていたのでまとめました。 「やめてね。」って方はお声がけください。

Docker nginx 公式イメージで nginx-debug を使う

Nginx でちょっとでも複雑な設定を書こうとすると、通常のログでは状況を把握しにくい。 より詳細なデバッグログを出すには nginx-debug バイナリが必要で、通常 nginx start しているのを nginx-debug start して、debug level specification を指定してやる必要がある。

Pre-built Linux packages provide out-of-the-box support for debugging log with the nginx-debug binary (1.9.8) which can be run using commands

To avoid this, either the line redefining the log should be commented out, or the debug level specification should also be added:

nginx.org

docker.io 上のイメージには 1.9.8 からバイナリがあるのが確認できる。

$ docker run -it --rm nginx:1.9.7 whereis nginx-debug
nginx-debug:

$ docker run -it --rm nginx:1.9.8 whereis nginx-debug
nginx-debug: /usr/sbin/nginx-debug

どうも https://github.com/nginxinc/docker-nginx のリリースは必ずしも Pull Request を作ってリリースしている訳ではないらしく時系列が負いにくくいつからかはわからないが、このコミット以降 docker-entrypoint.sh の第一引数を参照するようになった模様。

github.com

docker-entrypoint.sh の引数に nginx-debug を指定すればいいだけなので、こう。ENTRYPOINT 自体を書き換える必要はない。

FROM nginx:1.19.1

COPY ./default.conf /etc/nginx/conf.d/default.conf

CMD ["nginx-debug", "-g", "daemon off;"]

デバッグログは常に必要な訳ではないので(むしろ邪魔)、docker build --target を活用してこう書くことが多い。

FROM nginx:1.19.1 as production-pseudo

COPY ./default.conf /etc/nginx/conf.d/default.conf

# --

FROM production-pseudo as development

# --

FROM development as debug

CMD ["nginx-debug", "-g", "daemon off;"]

実際に使っている Nginx のバージョンが古い場合、nginx.orgリポジトリであれば多少バージョンが古くても nginx-debug バイナリも含めてパッケージを配布してくれており、これが使える。バージョンを上げられるならこれを機に上げてしまったほうがよいと思う。

FROM debian:10.4

RUN apt-get update && \
    apt-get install -y --no-install-recommends \
      curl \
      gnupg2 \
      ca-certificates \
      lsb-release \
      software-properties-common \
      && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

RUN apt-key adv --keyserver ha.pool.sks-keyservers.net --keyserver-options timeout=10 --recv-keys 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62 && \
    add-apt-repository "deb https://nginx.org/packages/debian/ buster nginx"

RUN apt-get update && \
    apt-get install -y --no-install-recommends \
      nginx=1.16.1-1~buster \
      && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

docker-compose の ${VAR:-default} 記法と .env で開発&本番環境を設計する

docker-compose.yml で、シェルでよくある ${VAR:-default} 記法が使えるのがあまり知られていないようだったので書きます。 www.debuntu.org

サンプルプロジェクトはこちらです github.com


PHP が動く Dockerfile と

# Dockerfile
FROM docker.io/php:7.2.29-cli

それを使う docker-compose.yml を用意します。今回はサービスは1つです。

# docker-compose.yml
version: "3.7"
services:
  php:
    build:
      context: .
    working_dir: /app
    volumes:
      - .:/app

動かすプログラム src/main.php環境変数 $MESSAGE を出力するだけです。

<?php

echo $_ENV["MESSAGE"] . PHP_EOL;

Dockerfile で ENV MESSAGE "hello" として

FROM docker.io/php:7.2.29-cli

ENV MESSAGE "hello"

これを実行すると、ENV した hello が出力されます。

$ docker-compose run --rm php php src/main.php
hello

これは環境変数なので当然シェルも同様です

$ docker-compose run --rm php sh -c 'echo $MESSAGE'
hello

docker-compose.yml で environment に表題の MESSAGE: ${MESSAGE:-holla} とすると

--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -3,6 +3,8 @@ services:
   php:
     build:
       context: .
+    environment:
+      MESSAGE: ${MESSAGE:-holla}
     working_dir: /app
     volumes:
       - .:/app

デフォルト値の holla が出力されます

$ docker-compose run --rm php php src/main.php
holla

-e を指定するとこちらが優先されます

$ docker-compose run --help | grep -- "-e KEY=VAL"
    run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] [-l KEY=VALUE...]
    -e KEY=VAL            Set an environment variable (can be used multiple times)
$ docker-compose run --rm -e MESSAGE=ciao php php src/main.php
ciao

.env を docker-compose が勝手に読むので、状態を .env に保持しておけます

$ echo "MESSAGE=haisai" > .env

$ docker-compose run --rm php php src/main.php
haisai

さて、ここで開発用にgitやvimを入れたくなった気持ちになってみます。

--- a/Dockerfile
+++ b/Dockerfile
@@ -1,3 +1,10 @@
 FROM docker.io/php:7.2.29-cli

+RUN apt-get update && \
+    apt-get install -y \
+      vim \
+      git \
+      && \
+    apt-get clean
+
 ENV MESSAGE "hello"

本番稼働時は要らないですよね

$ docker-compose build
Building php
Step 1/4 : FROM docker.io/php:7.2.29-cli
 ---> e7d2518687da
Step 2/4 : ENV MESSAGE "hello"
 ---> Using cache
 ---> 51c63dafa792

Step 3/4 : FROM production-pseudo AS development
 ---> 51c63dafa792
Step 4/4 : RUN apt-get update &&     apt-get install -y       vim       git       &&     apt-get clean
 ---> Using cache
 ---> 964a4048e6ab

そんなときは target build stages の出番です。 production-pseudo はまだ本番稼働していない疑似本番環境を表す語として使っています。

FROM docker.io/php:7.2.29-cli AS production-pseudo

RUN # ここに本番で要るものだけインストールする

# --

FROM production-pseudo AS development

RUN apt-get update && \
    apt-get install -y \
      vim \
      git \
      && \
    apt-get clean

これで TARGET=production-pseudo 時はvimやgitをインストールしなくなりました

$ docker-compose build
Building php
Step 1/2 : FROM docker.io/php:7.2.29-cli AS production-pseudo
 ---> e7d2518687da
Step 2/2 : ENV MESSAGE "hello"
 ---> Using cache
 ---> 51c63dafa792

Successfully built 51c63dafa792
Successfully tagged docker-compose-variable_php:latest

TARGET=development 時はインストールされます

$ echo "TARGET=development" > .env

$ docker-compose build
Building php
Step 1/4 : FROM docker.io/php:7.2.29-cli AS production-pseudo
 ---> e7d2518687da
Step 2/4 : ENV MESSAGE "hello"
 ---> Using cache
 ---> 51c63dafa792

Step 3/4 : FROM production-pseudo AS development
 ---> 51c63dafa792
Step 4/4 : RUN apt-get update &&     apt-get install -y       vim       git       &&     apt-get clean
 ---> Running in 36bcf0f189fe

$ docker-compose run --rm php which git
/usr/bin/git

これに VSCode の Remote Containers 拡張用を合わせて使うと相性が良いです。 FROM development AS debug として XDebug をインストールする、といった使い方もできます(それなりに重いので)。

が、話が長くなるのでこれについてはまた次回書きます。


ちなみに今回、 .env を作ったりする docker.mk はこの様になりました

.PHONY: install install-dev development clean

TARGET := production-pseudo

install: \
   .env \
   build

install-dev: \
   development \
   install

development:
  $(eval TARGET := development)

.env:
  touch $@
  echo "TARGET=$(TARGET)" >> $@

build:
  docker-compose build

clean:
  rm -rf .env