Docker

オンラインセミナー「なんとなくを卒業するための Docker 入門」を開催しました【ご質問への回答まとめ】

2022 年 2 月 24 日に、「なんとなくを卒業するための Docker 入門」というタイトルでオンラインセミナーを開催しました!

120 名以上の方にご参加いただき大盛況でしたので、講座の概要と、講座中にいただいたご質問への回答を記事にまとめます!

https://studyco.connpass.com/event/238830/

講座概要

Docker は軽量かつ再現性の高い環境構築の手段として優れており、最近は開発環境・本番環境ともに Docker を使う機会が非常に多くなっています。

Docker は仕組みや使い方を知れば非常に便利ですが、使い始めたばかりだと

  • いつどんなコマンドを実行すればいいのか分からない
  • うまく動かないときにエラーを解消できない

といったことも多いです。

この講座では、“なんとなく” Docker を使っているステップを卒業するべく、Docker の仕組みや、開発環境での使い方を解説しました。

資料について

今回はホワイトボードで講座を進めさせていただきましたので、資料はありません。

発表で使用したソースコードは GitHub のこちら で公開しています。

アーカイブ動画について

アーカイブ動画については、2022 年 3 月 31 日まで限定で、参加者・希望者に限定公開しています。

アーカイブ動画の視聴をご希望の方は、お問い合わせフォーム からお問い合わせいただくか、発表者の Twitter に DM でご連絡ください。

講座内でいただいた質問へのご回答

講座内でいただいたご質問にはその場でも回答させていただきましたが、こちらの記事でもここから改めてまとめさせていただきます。

※ ご質問ではないコメントなどについては、こちらの記事での回答は省略させていただきます

Docker の仕組み・動作条件などについて

ローカル環境と Docker でのコンテナの環境は何が違いますか

Docker コンテナはホスト OS の一部が隔離されたような環境になります。

単に隔離されているだけですので、基本的な使用感は Linux マシンと変わらないです。

設定次第ではありますが、ホスト OS と比較して、コンテナでは使用できるリソースやデバイスが制限されたりします。

物理サーバと同じ原理ですか?

物理マシンに Linux をインストールしたうえで、Linux の各種機能を使って環境を隔離したのがコンテナになります。

コンテナには Linux カーネルが含まれないなど、物理マシンで Linux を動かす場合とある程度違いがあります。

どんなホスト OS でも、作った docker コンテナが動くと思っていいんでしょうか。ホストの Linux カーネルによる違いなどで、うまく動かない場合などあるんでしょうか。

Docker コンテナには Linux カーネルは含まれておらず、ホストの Linux カーネルを使用して動作します。

そのため、Linux カーネルに違いがあればコンテナの動作は変わりうる、ということになります。

また、CPU アーキテクチャの違いの影響も受けます。

例えば「x86 向けにビルドしたイメージが ARM で使用できない」といったエラーにはしばしば遭遇します。

イメージについて

docker イメージを独自で実行する方法があると思いますが、その方法はご教授できませんでしょうか?

Docker イメージを独自で作成ということでしたら、

  • 手作業でコンテナ内の環境を構築して docker commit
  • Dockerfile を記述して docker build

という 2 つの方法があります。

次の質問の回答の通り、Dockerfile を記述して docker build する方法が望ましいです。

Docker commit か Docker build、どっちの方がよく使われるのでしょうか。

docker commit は基本的に使わないほうが良いです。

というのも、コンテナ内で apt install などを実行し、できあがった状態を docker commit でイメージ化する方法では、何がインストールされているのか分からないイメージになってしまうためです。

コンテナに何をインストールしたかは、コードとして管理するべきです。 (Infrastructure as Code と言います)

Docker には Dockerfile という Infrastructure as Code の機能があります。

イメージを作りたい場合は、Dockerfile を書いて docker build やそれに準ずる方法 (docker-compose build など) でイメージをビルドすることをおすすめします。

docker を調べると ubuntu が使われていることが多い気がしているのですが、centos ではなく ubuntu を使う理由ってあるんですか?

CentOS については、サポート終了しているため、Docker に限らず利用を避けたほうが良い認識です。

Docker のベースイメージとして ubuntu を使っている例はある程度見かけますが、あくまで分かりやすい例示のためで、実用上ではあまり推奨はされないです。

Docker で利用するベースイメージは、

  • 含まれているパッケージが少なくサイズが小さいほど、pull などでの転送が高速になり、かつ、セキュリティ上の考慮事項が減る
  • 色々なパッケージが含まれているほど、コンテナ内で各種コマンドを実行してデバッグしやすい

というトレードオフがあります。

サイズが小さく、デバッグなどの操作も可能という丁度良いイメージとして、alpine や slim といったものがよく使われる印象です。

より軽量なイメージとしては、scratch や distroless といったものがありますが、初心者向けではないです。

RUN apt update と CMD [“apt”, “update”] は何が違うのでしょうか。

Dockerfile に RUN apt update と記述した場合、イメージをビルドするときに apt update が実行されます。

CMD [“apt”, “update”] と記述した場合、イメージからコンテナを起動したときに apt update が実行されます。

apt update や apt install といったコマンドは起動時ではなくビルド時に実行したいケースが多く、その場合は RUN に書きます。

CMD に書くのは rails s や npm start といった、コンテナ起動時に実行したいコマンドになります。

Dockerfile で FROM python:3.9 のように書くことがあると思いますがこの場合の GuestOS はどうなっていますか?

前提として、Docker を利用する際には Guest OS という要素は登場しません。

  • Linux のホスト OS
  • Docker コンテナ

の 2 つがあり、Docker コンテナにはベースとなる「ベースイメージ」があります。

その上で、python:3.9 のベースイメージについては、Dockerfile を見たりして確認することになります。

DockerHub (例えば https://hub.docker.com/_/python) を見ると、タグ名の箇所がリンクになっており、そこから Dockerfile を確認できるケースがあります。

その Dockerfile の FROM 部分が、python:3.9 のさらにベースとなっているイメージになります。

ただし、DockerHub からリンクされている Dockerfile については、本当にその Dockerfile からイメージをビルドしたことを保証するものではなく、ドキュメントにリンクをつけただけのものになります。

特に非公式のイメージを使う際などは、DockerHub からリンクされた Dockerfile を読むだけでは安全性を確認することにはならないので、ご注意ください。

お時間あれば alpine や buster、slim についてもう少し教えていただきたいです。例えば ruby:2.6.5-slim-buster というのは何なのでしょうか?

Docker イメージは階層になっており、

  • イメージ A の上に作られたイメージ B
  • イメージ B の上に作られたイメージ C

という構造になっています。

ruby:2.6.5-slim-buster については、Docker Hub から Dockerfile をたどってみると、

  • scrach の上に作られた debian:buster-slim
  • debian:buster-slim の上に作られた ruby:2.6.5-slim-buster

という関係になっています。 (scrach は各種イメージの最もベースになる、空のイメージです)

debian:buster-slim は、buster というコード名のバージョンの debian のことで、そこからパッケージを限定して軽量化したのが buster-slim です。

ということで、「debian のバージョン buster をベースに軽量にした debian:buster-slim に、Ruby 2.6.5 をインストールしたのが、ruby:2.6.5-slim-buster」ということになります。

ubuntu は Linux ディストリビューションの一つだと思うのですが、slim や buster も OS の一種なのでしょうか?それとも slim や alpine と並列で語られる ubuntu は Linux ディストリビューションとは別物ですか?

Ubuntu や Debian、Alpine といったものは Linux ディストリビューションになります。

buster や bullseye といったものは、Debian の特定のバージョンを指すコード名となります。

slim については、例えば buster-slim は buster をベースにパッケージを限定して軽量化したものになります。

レジストリについて

レジストリに変更があった場合、イメージも自動で変更がかかりますか?

レジストリに変更があっても、マシン内に事前に pull したイメージは自動で変更されません。

また、設計面のプラクティスとして、「<イメージ名>:<タグ> はイミュータブルにすべき」というものがあります。 (前提として、コンテナイメージの名前は、<イメージ名>:<タグ> という形式になります。)

同一の <イメージ名>:<タグ> であるにも関わらずイメージの実体が異なることは、利用上誤解を生みやすかったり、動作のトレースが難しいなどの問題点があります。

そのため、myimage:tag1 のようなイメージに対して、同じ myimage:tag1 という名称でイメージを上書きするような運用はせず、myimage:tag2 のように次々タグを変更していくべきです。

また、ご質問内容に関連して、コンテナを実行する環境にはイメージの pull に関する設定がある場合があるので、いくつか参考リンクを掲載させていただきます。

Docker Hub を使わずに Dockerfile, Dockercompose.yml だけを共有するのはよくされるのでしょうか。

Docker Hub 使わずに、GitHub などを経由して Dockerfile や docker-compose.yml だけを共有するといった手法もよく使います。

例えば開発用に Dockerfile を書いた場合、Docker Hub などのレジストリを経由して渡すよりも Dockerfile だけ受け渡すほうが簡単なためです。

ただし、イメージのビルドに長時間かかるようなケースでは、事前にビルドしたイメージをレジストリ経由で受け渡すほうが利便性が高い場合があります。

また、本番稼働用のコンテナについては、Docker Hub や Amazon ECR などのプライベートレジストリで管理するのが基本です。

これは、ビルド済みのイメージを使い回すことで、ステージングと本番などの環境をまたがって動作保証しやすいためです。

イメージの設計について

docker で環境構築する際は、DB サーバやアプリケーションサーバなどサーバ単位でコンテナを分けるべきでしょうか?

イメージの設計としては、「1 コンテナ 1 プロセス」と言われ、プロセス単位でコンテナを分けるべきと言われています。 (ただし、ここで言う「プロセス」は Linux におけるプロセスとは少し感覚が異なり、例えば nginx のマスタープロセスとワーカープロセスは 1 つのコンテナで動かすのが一般的です)

例えば React (フロントエンド) + Express (Web API) + MySQL といった構成であれば、

  • React のコンテナ
  • Express のコンテナ
  • MySQL のコンテナ

の 3 つに分けるべきです。

コンテナを分けて作るのが好ましいとのことですが、docker file をその都度つくつのですか?

イメージを分けるのであれば、Dockerfile は基本的に分けることになります。 (同じ言語で起動するアプリケーションが異なるような場合は、同じ Dockerfile を使い回すケースもあります)

ただし、そもそも Dockerfile が不要な場合も少なくありません。

例えば開発環境で

  • React (フロントエンド)
  • Express (Web API)
  • MySQL

という構成を Docker で構築する場合、

  • DockerHub にある node の公式イメージにコードをマウントして npm start で React (の開発サーバ) を実行
  • DockerHub にある node の公式イメージにコードをマウントして npm start で Express を実行
  • DockerHub にある MySQL のイメージを起動

とするだけでよく、Dockerfile は書かなくても問題ないこともあります。

複数アプリをコンテナで推奨していない理由を教えてください。

複数プロセスをコンテナ内で起動する場合、簡単なのはシェルスクリプトを書いてバックグラウンドで起動する方法だと思います。

この方法では、コンテナ内の PID 1 のプロセスが例えば bash になります。

コンテナを停止する際、SIGTERM などのシグナルは PID 1 のプロセスに送信されるのですが、PID 1 が bash の場合、デフォルトではシグナルが届いたことを子プロセスに伝えてくれず、適切にシグナルハンドリングされません。

これに対して、コンテナ内の PID 1 で init プロセスを起動するといった回避策もありますが、コンテナ内はできるだけシンプルに保ったほうが構築が簡単かつ、セキュリティ上の考慮事項が減ります。

また、本番環境などでは、アプリケーションごとにコンテナを分離することで、一部のコンテナだけをスケールアウトさせることも可能になります。

ローカル環境で xampp を使用しています。xampp 環境から、Docker に移行したいです。コンテナは 2 つになりますか?Apache+PHP のコンテナと MySql のコンテナになるでしょうか?Docker ハブにありますか?

Apache・PHP・MariaDB (または MySQL) の環境を作りたいという前提で回答させていただきます。

まず、MariaDB (または MySQL) については、これだけで 1 つのコンテナにするのが良いです。

Docker Hub にある公式イメージを使うのがおすすめです。

Apache・PHP については、1 つのコンテナにすることも 2 つのコンテナにすることも考えられますが、PHP の公式イメージに Apache 入りのものがあるようなので、そちらを使うのが簡単だと思います。

PHP の公式イメージのリストは、Docker Hub からリンクされている以下の README に記載されています。

Docker Compose について

docker-compose.yaml は、構成を設定するイメージでしょうか?

はい。docker-compose.yaml は、Docker の起動時の設定などを記述するファイルになります。

コンテナを起動する際、docker run -p 3000:3000 -v foo:foo -e BAR=bar ... のように、docker コマンドに大量のオプションをつけて実行するのは大変です。

そこで「これらオプションの内容を docker-compose.yaml に書いておくと、docker-compose up というシンプルなコマンドでコンテナが起動するようになる」というのが Docker Compose です。

さらに、docker-compose.yaml の中には複数のコンテナの設定を書くこともでき、docker-compose up の 1 コマンドで複数のコンテナを起動することもできます。

Dockerfile = docker build で使う、docker-compose.yaml = docker run で使うという認識で合っていますか?

以下の表のように docker コマンドと同じような操作を、対応する docker-compose コマンドで代用可能、という考え方が近いです。

実行内容docker コマンド (docker-compose.yaml は不要)docker-compose コマンド (docker-compose.yaml が必要)
コンテナを起動docker rundocker-compose up
イメージをビルドdocker builddocker-compose build

docker-compose.yaml は、docker-compose コマンドで必要な設定ファイルになります。

Dockerfile は、イメージをビルドするときの、ビルド内容の設定ファイルになります。

Dockerfile をもとにイメージをビルドするコマンドには、上の表のように docker build や docker-compose build があります。

docker-compose up と docker-compose up -d の使い分けについて教えてください。

docker-compose up -d の -d オプションは docker run の -d オプションと同じなので、両方についての説明になります。

まず、docker run や docker-compose up でコンテナを起動すると、コンテナの標準出力がターミナルに表示され、Ctrl + C による停止の命令がコンテナに飛ぶような、コンテナにアタッチされた状態になります。

そうではなくバックグラウウンドでコンテナを起動したい場合に -d オプションを使用します。

Docker の Host OS や導入環境について

Window PC と Mac PC では使い方に差があるのでしょうか?

Windows や Mac に Docker を入れた場合、実際には Docker は Linux の (仮想) マシン上にインストールされています。

そのため、本来的には大きな違いはないです。

ただし、Docker for Windows や Docker for Mac では、まるで Windows や Mac 上で直接 Docker が動いているかのように見せる技術が使われています。

その影響で、少し挙動の差が出るケースがあります。

Windows や Mac などで、ファイルのパーミッションなどの概念が異なるため、ボリューム機能を使うと動作に差が出やすい印象です。

また、Docker for Mac については、非常にパフォーマンスが悪いことで有名です。

会社の都合で Windows で WSL2 は使えないのですが、対策はないでしょうか。

WSL2 が使えない場合、VirtualBox などで Linux 仮想マシンを入れることができれば、Docker を使用可能です。

企業ではあまりローカルでは使わないのでしょうか。

Docker はローカル環境・本番環境ともに多くの企業が利用しています。

ただ、特に大企業などでセキュリティ部門が許可を出さないケースはあると思います。

セキュリティについて

docker hub でも、できるだけ公式イメージを使ったほうがいいのはなぜでしょうか。

GitHub 上の OSS と同様に、Docker Hub も誰でもイメージをアップロード可能なため、公式などの信頼性が高いイメージを使ったほうが良いです。

ただ、「公式だから安心」という訳ではなく、OSS と同様に公式なものにも脆弱性が含まれたり発見されることはあります。

なお、コンテナイメージの脆弱性については Trivy などのスキャンツールがあります。

今日行ったデモはセキュリティや FW がデフォルトの設定のままのように見受けられましたが、この状態で開発をするセキュリティ上の懸念等はありますか?何か行ったほうが良い対策があれば教えてください。

今回は私の自宅内のローカルネットワーク環境で、安全性を確保した上でデモンストレーションさせていただきました。

今回デモンストレーションしたような用途で Docker を利用する際のネットワーク的な注意事項としては、-p オプションでホストのポートをフォワードする際、ファイアウォールの設定次第で、他のマシンからもコンテナに通信可能になることが挙げられます。

加えて、他のマシンからコンテナに接続し、もしも各種コマンドが実行可能な状態になると、コンテナの設定次第でホスト OS や他のコンテナに侵入可能な場合があります。

Docker in Docker や —privileged オプションの危険性について調べると、詳しい情報が得られます。

また、他に Docker 特有のネットワーク的な注意事項としては、Docker デーモンのポートをインターネットなどに公開する危険性がよく挙げられます。

Docker デーモンはデフォルトでは UNIX ドメインソケットで待ち受け、docker コマンドなどから支持を受けます。

Docker デーモンの設定を変更し、TCP ソケットで待ち受けるようにすると、通信可能な他のホストから docker コマンドの実行と同様の操作ができてしまうため、適切に保護しないと非常に危険です。

本番稼働について

複数のコンテナを一度にデプロイしたい時などはどのように行うのがセオリーとかありますでしょうか?

「一度に」がどこまで同時を指しているか次第な部分があるので、いくつかの想定で書かせていただきます。

まず、「複数のコンテナが強調動作するマイクロサービス・アーキテクチャのような構成をとりたい」という場合は、コンテナオーケストレーションを使うことが一般的です。

現状コンテナオーケストレーションとしては、Kubernetes (GKE や EKS などの Kubernetes ディストリビューションを含む) と、Amazon ECS がデファクトスタンダードです。

これらに加えて、Google Cloud Run を利用するケースも多いです。

入門としては、Google Cloud Run か Amazon ECS Fargate といったサーバレスなコンテナプラットフォームを使うことをおすすめします。

また、「複数のコンテナを一度のコマンド実行でデプロイしたい」という場合、ユースケースによりますが、例えば Kubernetes であれば 1 つの Pod に複数のコンテナを入れるなど、複数のコンテナをまとめて配備する方法があります。

ただし、複数のコンテナをまとめて配備すると、スケールアウトも同時に行うことになってしまったりするので、基本的には推奨されません。 (ログ収集やサービスメッシュの利用などで使われる「サイドカーパターン」というケースでは、複数のコンテナをまとめて配備したりします)

コンテナをどう分けるべきかよくわかってないのですが、ログをどこかに飛ばすような機能は別のコンテナを使って送信するなどしたほうがいいんでしょうか。

コンテナでアプリケーションを実装する場合、ログは標準出力に出し、ログの収集は別途行うべきとされています。

具体的には、

  • サイドカーを使い、各コンテナと一緒にログ収集用のコンテナを立ち上げる
  • コンテナが起動するノードに、ログ収集のプロセス (コンテナ) を起動する (Kubernetes の DaemonSet など)
  • Amaozn ECS で、ログドライバの設定により、例えば CloudWatch Logs にログ出力する

といった方法があります。

ログ収集などについては、アプリケーションがそれに対応した実装になっていることも重要です。

クラウドやコンテナの性能を活かしやすいアプリケーションの設計としては、The Twelve-Factor App が参考になります。

独自の docker イメージでリリースタグなどを打つと思うんですが、ベストプラクティスなどありますでしょうか。

タグのベストプラクティスとして最も重要なのは、latest タグを避けることです。

イメージの名前は <イメージ名>:<タグ> という形式ですが、<タグ> は省略すると latest として扱われます。

同じ latest というタグに対応するイメージを上書きしていくと、latest が指しているものがマシンごとに違ってしまうなど、動作保証が非常に困難になります。

そのため、latest タグは避けるべきと言われています。

タグの形式としては v1.0.0 のようにバージョンをつける方法もありますが、CI での自動化が簡単なことから、Git のコミットのハッシュ値を使う例も多いです。

Docker のキャッチアップについて

docker は、アプリ開発よりもインフラ開発の人の方が取っつきやすいと思いますがあっていますか?

はい。アプリケーションよりもインフラ関連の知識がある方のほうがスムーズに使えます。

今回の講座でも感じていただけたと思うのですが、Docker を使うには、前提として Linux やネットワークの基礎知識が必要になります。

Docker でつまづいていると思われている方が、実は Linux やネットワークの知識でつまづいていたというケースは非常に多いです。 (逆に、Linux やネットワークの基礎知識があれば、Docker の入門でつまづく要素はほとんどないです)

Docker での環境構築は、Linux で環境構築できることが前提になるので、Linux サーバで Web サーバ・アプリケーションサーバ・DB の疎通くらいが自信を持ってできるようになってから Docker に進むのがおすすめです。

具体的には、LPIC レベル 1 の取得や、『Amazon Web Services 基礎からのネットワーク&サーバー構築』という書籍をおすすめしています。

おわりに

以上、ご質問への回答を中心に、記事にまとめさせていただきました。

回答内容へのご指摘や追加のご質問については、お問い合わせフォーム からお問い合わせいただくか、発表者の Twitter にご連絡お願いします。

また、StudyCo では、法人様・コミュニティ様向けの出張勉強会や、技術サポートなどもお受けしています。 お問い合わせフォーム から、お気軽にご相談ください。

次回予告

3 月 17 日 (木) 19:30 から、「【自然言語処理】Python で BERT に入門しよう」が開催予定です。

ご興味・お時間ある方は以下の connpass ページからお申し込みください!

https://studyco.connpass.com/event/240829/