タグ: トラブルシューティング

  • Dockerの辛いところ|20年現場で見てきた「便利だけど困る」7つの落とし穴

    Dockerの辛いところ|20年現場で見てきた「便利だけど困る」7つの落とし穴

    「Dockerさえ入れれば全部解決する」

    そんな期待を胸にDockerを導入したものの、気づけば新たな悩みと格闘している…。開発の現場でこんな経験をしたことはないでしょうか。

    前回の記事でDockerのメリットをお伝えしましたが、20年以上エンジニアとして様々な現場を見てきた私としては、正直に言わなければならないこともあります。Dockerは確かに革命的な技術ですが、「銀の弾丸」ではありません。

    この記事では、Dockerを実際に使い込んでいく中で遭遇する「辛いところ」を率直にお伝えします。これからDockerを導入しようとしている方も、すでに使っていて困っている方も、事前に知っておくことで対策が立てやすくなるはずです。

    辛いところ1:Mac・Windowsだと動作が遅い

    Dockerの最大の落とし穴のひとつが、Mac・Windows環境での動作速度です。

    「軽量で高速」がDockerの売りのはずなのに、実際に使ってみると「なんか遅い…」と感じる方は多いのではないでしょうか。特に開発中にファイルを編集するたびに反映が遅かったり、ビルドに時間がかかったりすると、せっかくの開発効率が台無しです。

    なぜ遅くなるのか

    根本的な原因は、DockerがLinuxのカーネル機能を前提としているところにあります。

    LinuxマシンでDockerを動かす場合、コンテナはホストOSのカーネルをそのまま使うため、オーバーヘッドはほぼありません。しかしMacやWindowsの場合、Dockerを動かすためにLinux VMを内部で立ち上げており、その上でコンテナが動いています。

    特に問題になるのが、ホストマシンとコンテナ間のファイル同期です。開発時には自分のエディタでコードを編集し、それをコンテナ内のアプリケーションで動かすわけですが、このファイル同期処理が意外と重いのです。MacのファイルシステムとLinuxのファイルシステムを橋渡しするために、変換処理が都度走ることになります。

    対処法

    Windows環境の場合、WSL2(Windows Subsystem for Linux 2)を活用し、ソースコードをWSL2側のファイルシステムに置くことで大幅に改善できます。Linux側にファイルを置けば変換処理が不要になるからです。

    Mac環境では、Docker Desktopの設定でCPUやメモリの割り当てを増やす、あるいはボリュームマウント時に「cached」や「delegated」オプションを使うことで、多少の改善が見込めます。

    ただし、これらは根本解決ではありません。本当にパフォーマンスが必要な場面では、Linux環境での開発を検討するのも選択肢のひとつです。

    辛いところ2:ボリュームマウントの権限問題

    「Permission denied」

    Dockerを使っていると、このエラーに何度も遭遇することになります。特にLinux環境でDockerを使う場合、ボリュームマウント時の権限問題は避けて通れません。

    何が起きているのか

    ホストマシンのディレクトリをコンテナにマウントする際、ファイルの所有者情報(UID/GID)がそのまま引き継がれます。問題は、ホストで使っているユーザーとコンテナ内のユーザーのUID/GIDが一致しないケースです。

    たとえば、ホストでUID=1000のユーザーとして作業していて、コンテナ内ではrootユーザー(UID=0)で動作している場合、コンテナ内で作成されたファイルはホスト側から見るとrootの所有物になり、一般ユーザーでは編集できなくなってしまいます。

    興味深いことに、Docker Desktop for MacやWindowsではこの問題が発生しにくいです。内部でうまくハンドリングしてくれているようですが、Linux環境では自分で対処する必要があります。

    対処法

    よく使われる対処法は、docker run時に「-u」オプションでユーザーを指定するか、/etc/passwdと/etc/groupを読み取り専用でマウントしてホストのユーザー情報を共有する方法です。

    Dockerfileでユーザーを作成し、そのユーザーで実行するように設計しておくことも有効です。ただし、環境ごとにUID/GIDが異なる場合は、ビルド時に引数として渡すなどの工夫が必要になります。

    正直なところ、この問題に対する「これだ」という万能解はありません。プロジェクトの状況に応じて最適な方法を選ぶしかないのが現状です。

    辛いところ3:気づいたらディスクが満杯

    「なんかPCの容量がないな…」と思って調べてみたら、Dockerが数十GBを占有していた。そんな経験をした方も多いのではないでしょうか。

    なぜ容量を食うのか

    Dockerはイメージのレイヤー構造を採用しており、効率的に容量を節約できる仕組みになっています。しかし実際の運用では、様々な「ゴミ」が蓄積していきます。

    まず、使われなくなったイメージ(dangling image)です。同じタグで何度もビルドを繰り返すと、古いイメージはタグを失い「none」という状態で残り続けます。次に、停止したままのコンテナ。明示的に削除しない限り、ディスク上にデータが残ります。そしてビルドキャッシュ。ビルド高速化のためにキャッシュされたレイヤーが積み重なっていきます。

    さらにWindowsの場合、やっかいな問題があります。Dockerの仮想ディスク(ext4.vhdx)は自動的に拡張されますが、中身を削除しても自動的には縮小されません。つまり、一度膨らんだ仮想ディスクは手動で圧縮しない限りそのままなのです。

    対処法

    定期的なお掃除が必要です。まずは「docker system df」で現状を確認しましょう。「docker system prune」で未使用のコンテナ、ネットワーク、イメージを一括削除できます。ボリュームも含めて削除したい場合は「–volumes」オプションを付けます。

    Windowsの場合は、さらに仮想ディスクの圧縮も必要です。Docker Desktopを停止した状態で、PowerShellの「Optimize-VHD」コマンドを使って圧縮できます(Hyper-Vが必要)。

    私の経験では、月に一度くらいはお掃除の時間を取るようにしています。自動化したい場合は、CI/CDパイプラインに組み込むか、cronで定期実行するのも手です。

    辛いところ4:ネットワークとポートの罠

    Dockerのネットワーク周りは、初心者にとってかなりの鬼門です。「繋がらない」「意図せず公開されていた」など、トラブルの温床になりがちです。

    よくあるトラブル

    まず、EXPOSEに関する誤解。Dockerfile内でEXPOSEを書いても、それだけではポートは外部に公開されません。単なるドキュメント的な意味しかなく、実際に公開するには「docker run -p」で明示的に指定する必要があります。

    逆に危険なのが、ポートマッピングのデフォルト挙動です。「-p 8080:80」のように指定すると、デフォルトでは全てのインターフェース(0.0.0.0)に対してポートが公開されます。ファイアウォールで閉じているつもりでも、Dockerは独自のiptablesルールを設定するため、ufwなどの設定を迂回してしまいます。

    公衆WiFiに繋いだ状態で開発用コンテナを立ち上げていたら、LAN内の他の端末からアクセスできてしまった…という怖い話も実際にあります。

    対処法

    ローカル開発では「-p 127.0.0.1:8080:80」のように、必ずlocalhostを指定するようにしましょう。docker-compose.ymlでも「ports: “127.0.0.1:8080:80″」と書けます。

    また、依存サービスの起動順序にも注意が必要です。docker-composeのdepends_onは起動順序を制御しますが、サービスが「使える状態になったか」までは保証しません。アプリ起動時にデータベースがまだ準備中…というケースは珍しくありません。dockerizeなどのツールを使って、依存サービスの起動を待つ仕組みを入れることをおすすめします。

    辛いところ5:学習コストが意外と高い

    「Dockerは簡単」という評判を聞いて始めてみたものの、覚えることが多くて挫折しそうになる。これも多くの人が経験することです。

    覚えなければならないこと

    Dockerを使いこなすには、最低限以下の知識が必要になります。

    Dockerの基本概念(イメージ、コンテナ、レイヤー)、Dockerfileの書き方、docker-compose.ymlの記法、ネットワークの仕組み、ボリュームとデータ永続化、そしてLinuxの基礎知識。特にLinuxに馴染みがない方にとっては、コンテナ内でのデバッグ作業がかなりハードルが高く感じるでしょう。

    さらに本番環境での運用を考えると、セキュリティ設定、リソース制限、ログ管理、オーケストレーション(Kubernetes等)といった知識も必要になってきます。

    対処法

    一度に全部を覚えようとしないことが大切です。まずは「docker run」と「docker-compose up」だけで動かせる環境を用意し、実際に使いながら少しずつ理解を深めていきましょう。

    既存のdocker-compose.ymlを読み解くところから始めるのも良いアプローチです。先人が作った設定を眺めながら「なぜこう書いてあるんだろう」と調べていくと、実践的な知識が身につきます。

    辛いところ6:LinuxベースであることのしがらみがLinux以外では制限になる

    DockerはLinuxのカーネル機能をベースにした技術です。この事実が、いくつかの制限を生み出しています。

    異なるOS間の制約

    Linux上でWindowsコンテナを動かすことはできませんし、その逆もできません(Docker Desktop for Windowsでは切り替えは可能ですが、同時には使えません)。これは、コンテナがホストOSのカーネルを共有する仕組みだからです。

    また、仮想マシンと比較すると分離レベルが低いという特性があります。コンテナ同士、あるいはコンテナとホストの間で完全な分離ができているわけではありません。セキュリティ要件が厳しい環境では、この点が問題になることもあります。

    対処法

    マルチOSでのテストが必要な場合は、DockerではなくVMを併用する必要があります。

    セキュリティ面では、信頼できるベースイメージを使う、不要なパッケージを入れない、rootユーザーで実行しないといった基本的な対策を徹底することが重要です。より高い分離が必要な場合は、gVisorやKata Containersといったサンドボックス技術の導入も検討に値します。

    辛いところ7:「私のPCでは動きます」問題の変形版が起きる

    皮肉なことに、Dockerは「環境差異問題を解決する」ために生まれた技術なのに、Docker環境特有の「動かない」問題が新たに発生することがあります。

    Dockerならではの罠

    ローカルでは動くのにCI/CDで失敗する、開発環境では動くのに本番で動かない。原因を調べると、Dockerイメージのバージョン違い、ベースイメージの更新、ビルドキャッシュの有無、ネットワーク設定の違いなどが見つかります。

    また、Dockerを使いこなす知識がないまま複雑な構成を組むと、問題が発生したときの調査が困難になります。「どのコンテナで何が起きているのか」を追うのは、慣れていないとかなり大変です。

    対処法

    イメージのタグは「latest」を避け、明示的にバージョンを固定しましょう。CI/CDとローカルで同じDockerfileを使い、ビルド手順を統一することも重要です。

    そして何より、Dockerの仕組みをしっかり理解することです。ブラックボックスのまま使っていると、いざというときに手も足も出なくなります。

    まとめ:それでもDockerを使うべき理由

    この記事では、Dockerの「辛いところ」を7つ紹介しました。

    Mac・Windowsでの速度問題、権限周りのトラブル、ディスク容量の肥大化、ネットワークの罠、学習コスト、Linux依存の制約、そして新たな「動かない」問題。なかなか手強い課題が並んでいます。

    それでも、私は断言します。Dockerは使うべきです。

    なぜなら、これらの辛さを差し引いても、Dockerがもたらすメリットのほうが圧倒的に大きいからです。環境構築が数分で終わる、チーム全員が同じ環境で開発できる、本番環境との差異がなくなる。20年前の「手順書地獄」を知る身としては、これらの恩恵は何物にも代えがたいものです。

    そして、辛さの多くは「知っていれば対処できる」ものばかりです。この記事を読んだあなたは、すでに多くの落とし穴を知っています。それだけで、かなり有利なスタートを切れるはずです。

    まだDockerを触ったことがない方は、まずDocker Desktopをインストールして、「docker run hello-world」を実行するところから始めてみてください。すでに使っている方は、この記事で紹介した対処法をぜひ試してみてください。

    トラブルに遭遇するたびに少しずつ理解が深まり、気づけばDockerなしでは開発できない体になっているはずです。私がそうであったように。