Docker
Overview
Dockerについてまとめているセクション。
コンテナとは
コンテナとはアプリケーションの実行環境をパッケージ化し、それをデプロイ、実行するためのテクノロジーのこと。
- アプリケーションコード
- ライブラリ
- プログラミング言語のランタイム
アプリケーションを実行するための依存関係をコンテナイメージとしてパッケージ化する。
また、パッケージ化されたコンテナイメージは任意の場所で実行されるコンテナランタイムによりコンテナとして実行ができるようになる。
コンテナを利用することで可搬性を得ることができる。
※どこでも同じ環境を再現できる
昨今の開発
コンテナの持つ可搬性によりさまざまなメリットが得られ、ユーザーがよりアプリケーション開発に集中できることから、サーバーレステクノロジーと同様にモダンアプリケーションにおける重要な選択肢の1つとなっている。
Lambdaの方が抽象度高く、多くの作業をAWSにオフロードできる。
Lambdaで要件を満たせない場合にAmazon ECSやAmazon EKSの採用を検討することになる。
AWS Lambda関数の**タイムアウト設定は最大で15分のため、**それ以上の実行時間が想定されるアプリケーションでは採用が難しくなる。
そのような場合、アプリケーションの実行環境としてコンテナが候補に挙がるため、Amazon ECSやAmazon EKSが選択肢に出てくる。
サーバーレスとコンテナ使い分ける指標
アプリケーション開発により多くの時間や人を投資できる選択肢はどれかという観点で整理する考え方。
コンテナをアプリケーション実行環境へする場合
Amazon EC2あるいはAWS Fargateが選択肢となる。
Fargateを利用すると、Dockerなどのコンテナランタイムを含むAmazonEC2インスタンスの管理が不要となるため、コンテナワークロードの運用負荷が軽減されます
Volume Trick
VSCode&Docker Volumeにおけるnode_modules問題を解決する
Volume Trick
は通常、Docker環境でNode.jsアプリケーションを開発する際に node_modules
ディレクトリを効果的に管理する方法として使用される
node_modules
はDockerイメージにホストからコピーしてはいけない。
1. パフォーマンス向上
ホストマシンとDockerコンテナ間で node_modules
ディレクトリを共有すると、パフォーマンスが低下することがあります。Volumeを使用することで、この問題を回遍することが可能です。
2. 環境の違いの管理
異なるOSや環境で動作させる際に、異なる依存関係やバイナリを必要とするパッケージが存在することがあります。Volumeを使用することで、コンテナ内で専用の node_modules
ディレクトリを保持でき、ホストOSとは異なる環境での依存関係の問題を回避できます。
3. ホストマシンのディスクスペースの節約
node_modules
ディレクトリはしばしば大きく、多くのプロジェクトをホストマシンに保持している場合、ディスクスペースを大量に消費します。Volumeを使用すると、これらのディレクトリをコンテナ化された環境内に保持でき、ホストマシンのディスクスペースを節約できます。
4. 簡単な依存関係の管理
開発中に依存関係を迅速にインストール/アンインストールする必要がある場合、Volumeを使用することで、ホストマシンの影響を受けずにこれを行うことができます。
5. コンテナの再利用
コンテナを再起動したり、新しいコンテナを起動したりする際に、すでにインストールされている依存関係を再利用できます。
これにより、新しいコンテナの起動時間が短縮されます。
6. ホストとコンテナ間の依存関係の競合を回避
ホストマシンとDockerコンテナで異なるバージョンの依存関係を使用することが可能。
これにより、ホストとコンテナ間で依存関係の競合が発生するのを避けることができます。
注意: ただし、Volume Trickを使用すると、node_modules
ディレクトリの管理が複雑になる可能性があり、とくに新しい依存関係を追加または削除する際に問題が発生することがあります。また、ボリュームはDockerホストのディスクスペースを使用しますので、大量のデータを保持する長期間稼働するボリュームはディスクスペースを消耗します。このため、定期的なメンテナンスが必要となることもあります。
dockerでvolumeをマウントした時のファイルowner問題
ホスト側のファイルをコンテナ内で使いたい場合や、逆にコンテナで作ったファイルにホストからアクセスしたい場合に有用なのだが、ファイルのアクセス権限について考慮すべき点がある。 ちなみにdocker for macで試したところ、上記の問題は起きない。 コンテナ内からはownerがrootとして表示されるが、mac上からは自ユーザーがownerとして表示されている。docker for macの中でうまく解決してくれているようだ。
docker 仕組み
- Docker Buildにおけるリードタイム短縮のための3つの改善ポイント
Dockerはビルドのステップごとにファイルシステムの変更差分を積み重ねることでイメージを作成する。
先にGemfileをイメージに組み込んでbundle installを実行しておかなければソースコードを変更するたびに毎回bundle installをする必要がある。
# 先にGemfileを転送しbundle installする
COPY --chown=rails Gemfile Gemfile.lock package.json yarn.lock /app/
RUN bundle install
RUN yarn install
上記のような工夫をすることでGemfileの内容に変化がなかった場合は bundle install
の工程までキャッシュを利用することが可能になり2回目以降のビルドが大きく高速化される。
マルチステージビルド
マルチステージビルドは、Docker17.05以上で利用できる新機能。
前のステージでビルドされた成果物をこの新しいステージへコピーする。
最終的なイメージでは取り残され最終的なイメージへは保存されない。.
※つまり、TypeScriptをコンパイルするステージ⇨最終的なJSを実行するステージに分けることができる。
dockerignoreとは
docker buildでimageを作成する際に無視するファイル・ディレクトリを設定できる。
mountの場合は無視ができない
Dockerのマウント種類
mountディレクトリの恐ろしさ たとえばDockerfile内でCopyをした場合、コピーされるがバインドマウントのためではないため編集しても反映されない。
そのためdocker imageを作成するためだけを意識すること また、COPYでせっかく全部コピーしてもバインドマウントで同じディレクトリを指定した場合にバインドマウントの中身ですべて消される。これが注意!!
alpine linux
alpine linuxは基本 GCC などの C コンパイラが含まれていない(つまり開発ツール)
alpine linuxにyarnいれる python:alpineにCコンパイラをインストール
Dockerコンテナ内のアプリケーションport
Dockerコンテナ内のアプリケーションは、デフォルトでネットワークトラフィックを受け入れている。
→http://127.0.0.1:3000
このインターフェースは外部トラフィックを受け入れないため、機能しないのも不思議ではありません。これを機能させるには、nuxtアプリのHOST環境変数を**0.0.0.0(すべてのIPアドレス)**に設定する必要がある。
コンテナのサーバがlocalhostでlistenしていると、ホストマシンからアクセスした際にエラーが起きることを確認した。 これは、ホストマシンのlocalhostとコンテナーのlocalhostが異なるため
ホストマシンとcontainerはnamespaceで区切られていて別のマシンと捉えて構わないため、containerのloopback interfaceにはホストマシンからはアクセスできない。
イメージをビルドするタイミングとは?
- Dockerfileを書き換えた時。
- パッケージ管理を行うファイルを書き換えた時
Dockerを軽くする方法
- RUNはチェーンする(&&)とにかくつなげる。RUN毎にレイヤーが作成されてしまうため(実際これもどうなんだろうと言われている)
- 産業廃棄物(docker build)時に生じた、アプリケーション実行には不要なツールやファイルを削除する
- yumやapt-getのゴミ
- ビルドに使用したソース
- gitですら産業廃棄物
- 成果物を残すことを意識する
- yarn installなどを早めたい
docker-compose.yml => Dockerfileへ環境変数を渡す方法(1)
渡す側(docker-compose.yml)と 受け取る側(Dockerfile)双方の設定が必要。
- docker-compose.ymlから渡す args ... Dockerイメージをビルド(作成)する際に引数を渡すために使用します。
この仕組みを使ってDockerfileへ環境変数を渡します。
services:
api:
build:
context: ./api
args:
# キー: 値
WORKDIR: $WORKDIR
# この書き方でもOK
- WORKDIR=$WORKDIR
- Dockerfileで受け取る
ARG命令を使います。 指定する値はdocker-compose.ymlで渡したキーの名前です。 受け取った後はDockerfile内で変数として扱うことができます
ARG WORKDIR
ENV HOME=/${WORKDIR}
docker-compose.yml => コンテナーへ環境変数を渡す方法
- environment
※これは扱う環境変数が少ない場合に使用する。
services:
api:
environment:
POSTGRES_PASSWORD: $POSTGRES_PASSWORD
# この書き方でもOK
- POSTGRES_PASSWORD=$POSTGRES_PASSWORD
- env_file
env_fileを使用する場合は、環境変数を格納したファイルパスを指定します。 相対パス、絶対パスどちらでも良い。 これは扱う環境変数が多い場合に使用します。
services:
api:
env_file: ./.env
ベースイメージを調べる方法
重いイメージは本当にいいことがない。 参考URL
ベースイメージのRubyバージョンを調べる手順 ちょっと豆知識。これからのプログラミング人生のために。
Rubyのバージョンは開発時点の安定版を使用するようにしましょう。
以下の手順でバージョンを調べます。
- まず、Rails6に必要なRubyのバージョンを知る。
- Railsガイド => Ruby 2.5.0以降が必要
- 次にRuby 2.5.0以上の安定版を知る。
- Ruby => 安定版は2.7.1
- 最後にRuby 2.7.1のベースイメージを調べる。
- Docker Hub => 2.7.1-alpineを採用
最近はオフィシャルでもAlpine版が存在している。
コマンド
近年Dockerコマンドはできるだけ次の書式に統一しようとしている
$docker コマンド 操作 オプション
docker word
デタッチモード: コンテナー内に入らずバックグラウンドで動作する状態のこと。 -dでやる
dockerの基本運用概念
バインドマウントやボリュームマウントは別の場所に置き、コンテナー自体は破棄されても問題ないような運用を心がけるべき方針
開発におけるdocker導入のメリット
メリット
-
同一性 複数人で開発する際に、環境の差が生まれない。
-
カプセル化 アプリケーション込みの環境をコンテナーというカプセルに隠蔽できる。 コンテナーという単位に対するテストが可能に。 コンテナーを捨てる・再生成するのが容易。
-
ポータビリティ(一貫性とも) 開発に使ったコンテナーをCIでテストできる。 CIでテストしたコンテナーをサーバーにデプロイできる。 デプロイしたコンテナーをスケールできる。
-
ひとつのサーバにwebサーバを同居できる(Apache) 1台のDockerホストに2台のWebサーバを同居させることができるなど。
メリットで防げる消耗
-
おれの環境では動いた。 はい。 複数の開発者で同一の環境で開発できるので防げる。
-
ローカルで通ったテストがCIでコケる。 開発と同一の環境でテストできるので防げる。 bundle install, npm installに失敗してテストがコケる。
-
依存ライブラリのフェッチに成功したイメージでテストすることで防げる。 コードを変更していないのにアプリケーションの挙動が変わった。go getしてるライブラリの挙動がいつの間にか変わってたぽい。
-
依存ライブラリをフェッチしたイメージを共有することで、バージョンを固定できる。 nginxの設定を変更したい。サーバにログインするためのSSHキーどれだっけ。 手元で設定して動作を確認しDockerイメージをデプロイできるので、設定変更のためにサーバーにログインする必要がなくなる。 さらに、正しく設定できているかブラックボックステストすることも可能に。
デメリット
-
学習コスト コンテナを立ち上げるあたりまではコスト低めだが、実際に開発環境としてDockerを使うあたりから「これどうやってやるんだろう」「どちらの方法で設定するのが良いのだろう」のノウハウが出てくる感じになる。 開発者全員に深い知識が必要かというとそうではないが、問題解決できる知識を持った人間が開発陣に最低一人は必要。
-
ローカルでの開発とほぼ遜色ない環境でコードを書くことができるということを周知するためのコスト
-
完全な分離ではない。
DockerFile とは(カスタムイメージを作成した時)
docker buildでカスタムimageを作成するファイル 公開されている Docker イメージをそのまま使う場合は必要なく、カスタマイズしたい場合に作成する。
必要な理由 ベースとなるイメージとそのイメージに対して、どのような操作をするのかを記したDockerfileと呼ばれるファイルを用意しそのDockerfile通りにコンテナに対して変更やファイルコピーを加えることによってイメージを作成する。
カスタムイメージの作り方
2つある。
-
コンテナから作る ベースとなるイメージからコンテナを起動し、そのコンテナに対して、
docker exec
でシェルで入って操作したりdokcer cp
でファイルをコピーしたりし調整を加える。 その後docker commit
コマンドを使いイメージ化する。 デメリットが存在する -
Dockerfileから作る ベースとなるイメージとそのイメージに対して、どのような操作をするのかを記したDockerfileと呼ばれるファイルを用意しそのDockerfile通りにコンテナに対して変更やファイルコピーを加えることによってイメージを作成する。 イメージの作成には
docker build
を実行する。
メリット Dockerfileは、ベースとなるイメージに対する変更指示をまとめたファイル。これを見れば悪意ある操作や間違った操作が加えられていないかが一目瞭然 Dockerfileは改良しやすいメリットがある。
Dockerfileでカスタムを利用した理由
ある程度なんでもしたいときはdebianイメージを使用することが多い。
コンテナからイメージを作る(docker cpやexecで中に入ったあと)
以前のhttpdコンテナを作ったあと、docker cp
コマンドをつかってindex.htmlをコンテナ内にコピーし
これと同様の方法で作成したコンテナをイメージ化する。
そしてそのイメージからコンテナを作り直すことでindex.htmlがすでに入ったコンテナが起動することを確認する。
コンテナをイメージ化する
docker commit
コンテナからイメージを作る(Dockerfile)
- イメージに含めるファイル群とDockerfileの用意
イメージに含めたいファイルとDockerfileを1つのディレクトリに置き、それを
docker build
して作るという方法 ※このディレクトリに含めているファイルはイメージを作成したとき、利用していないものであっても含まれてしまうため余計なものは置かない。 ※ただし、同ディレクトリに.dockerignoreを置くと、除外ファイルを指定できる。しかしこの機能はファイル名のマッチングで除外をするためファイル数が多いと、イメージの作成に時間がかかってしまう恐れがある。
レイヤー
Layerは RUN / COPY / ADD
でのみ作成され、それ以外は一時的なLayerとして作成されます。(つまり無闇にこれらのステートメントを小分けにせず可能な限りまとめて記述 && で接続する事も重要)
どのようなレイヤーで構成されているかは docker history
で確認できる。
Docker ImageはUnionFSという複数のFS(Layer)にあるファイルを透過的に1つのものとして扱う技術が使われ、Dockerfileの命令によって作成された全ての中間Layerをマージして最終的に1つのFSとなる。
docker-compose.yml 覚書
コンテナの定義やマッピングするポートなどコンテナに関する設定を記述するファイル
version: "3"
services:
app:
# 起動イメージ
image: node:16
# 環境変数
environment:
- DEBUG=app:*
tty: true
# ホスト側のポート:コンテナのポート
ports:
- "3000:3000"
# ホスト側のsrcをコンテナのappにマウント
volumes:
- ./src:/app
# 起動時のカレントフォルダ
working_dir: /app
# 起動後に実行するコマンド
command: npm start
重要な箇所は # ホスト側の src をコンテナの app にマウントの部分で、通常 Docker コンテナを停止するとコンテナ上で作成した各種ファイルは削除されますが、上記の記述を行うことでコンテナ起動時に再度ファイルがマウント(反映)されます。 また Docker コンテナ上で作成・変更したファイルもこちらに記述した場所に反映される。 基本ホスト上とコンテナ上でファイルの同期をとるための記述と考えれば OK
Docker compose 起動 (docker-compose コマンドについて)
$ docker-compose run --rm app /bin/bash
※rmオプションはDockerコンテナ停止時にコンテナを削除する機能で、停止したコンテナが残り続ける問題を解決するためのオプション
Docker コンテナの軽量化
- コンテナイメージサイズが大きいことによる弊害
イメージのビルド時間が長い
イメージを
Docker registory
にプッシュする時間が長い イメージをPULL
する時間が長い
それらが起因して下記の弊害が起こる
トライアンドエラーに時間がかかり、生産性が低下 ビルド時間,CI時間の増大 オートスケールでコンテナがサービスインされるまでの時間が長くなる。 Kubernetesクラスタを構成するNodeディスクの消費
- 軽量化のアプローチ
- RUN命令をまとめる 基本中の基本
※RUN命令が走る度にイメージレイヤーが生成されてしまうため、ちょっと重くなる。
# npm installの後のパッケージを羅列
RUN npm install -g gulp@3.9.1 \
&& npm install gulp-load-plugins \
gulp-plumber \
gulp-sass \
gulp-pleeease \
gulp-uglify \
gulp-rename \
&& npm init -y
このように、複数の RUN コマンドを「連続で実行する 1 つのコマンド」として扱うことで、イメージレイヤーをまとめて軽量化できます。
-
RUNでaptコマンドを走らせた場合はインストール時に使ったaptキャッシュ(ゴミファイル)が残ってしまうため削除する。
-
使用したいimageにslim versionがあるかDockerHubで探す。 ※デフォルトのNOdeがかなり色々入っている。そのためslimイメージを探す。
docker Script関連
Docker コンテナの動作に必要な設定を起動時に渡す
Dockerコンテナを起動するタイミングで、コンテナの動作に必要な設定を受け渡す方法は2つある。
- 環境変数を通して渡す
- コマンドライン引数を渡して渡す。
どちらの場合も docker run で実行するコマンドの中に設定を含めることになる。
docker-entrypoint.sh
dockerで初回起動時のみ特定の処理を行うヘルパースクリプト
docker run
とか docker-compose up -d
などの初回起動時のみ処理したいことがある場合に使用する。
docker restartとかdocker-compose restartとかsystemctl restart dockerの時には動いてほしくないんだ。 やってみた結果mariadb + zabbixで初回起動時のみ、構成用の .sqlを流し込む。という動作ができるようになりました。
ローカル上にLocalStackをDockerで実行
開発用にAWSのサービスをローカル環境に構築できる、LocalStackというプロジェクトがある。素晴らしい。
ビルドコンテキスト
ビルドコンテキストとは docker build
実行時に指定するディレクトリのこと。
docker build
コマンドを実行した構築時、ビルドコンテキストとして現在のディレクトリ以下にある全てのファイルやディレクトリをDockerデーモンに送信してしまう。
このような事態を防ぐためにもDockerfile用のディレクトリを作成し、そのディレクトリには無駄なファイルは配置しないようにすべき。
Dockerのコンテキストとは
docker build コマンドを実行したときの、カレントなワーキングディレクトリのことを ビルドコンテキスト(build context)と呼びます。 デフォルトで Dockerfile は、カレントなワーキングディレクトリにあるものとみなされます。 ただしファイルフラグ(-f)を使って別のディレクトリとすることもできます。 Dockerfile が実際にどこにあったとしても、カレントディレクトリ配下にあるファイルやディレクトリの内容がすべて、ビルドコンテキストとして Docker デーモンに送られることになります。
要はdocker buildコマンドを実行した場所
-
制約 Dockerはコンテキスト(カレントディレクトリ)の外のファイルにはアクセスできない仕様。
-
ではディレクトリごとにわけた ルートディレクトリをDockerのコンテキストにすることで、Dockerfileはどんなファイルにもアクセスできるようになりました。 一方で、build時はその分Dockerデーモンという奴にそれだけ多くのファイルを送ることになるので遅くなることがあるようです。
構築時、ビルドコンテキストとして現在のディレクトリ以下にある全てのファイルやディレクトリをDocker deamonに送信してしまいます。ビルドコンテキストに余分なディレクトリ・ファイルがあると、build時に時間がかかる、メモリを消費する原因となります。たとえば、ビルドコンテキストに100MBのファイルがあるとimageのサイズが100MBプラスとなってしまいます。このような事態を防ぐためにもDokcerfile用のディレクトリを作成し、そのディレクトリには無駄なファイルは配置しないようにすべきです。
.dockerignore
dockerignoreとは、DockerfileのCOPYコマンドでコピーしたくないファイルやディレクトリを指定するファイルのこと。
dockerignoreを作成することで、ビルド時間の短縮、docker imageサイズの最適化などの効果があります。
仕組み
Dockerfileからイメージをビルドする場合、Dockerfileの存在するディレクトリの中身はtarで固められdaemonへと送られます。DockerfileのCOPYコマンドを使用して、必要なファイルをコピーします。
docker network
コンテナ内の/etc/hostファイルで定義されているそう。以外に簡単
マルチステージビルド
- マルチステージビルド以前 サイズを小さく保ちながらDockerイメージをビルドすることは、最もやりがいのあることのひとつ 。 Dockerfile内の各々の命令ではイメージにレイヤが追加される。 したがって、次のレイヤを作成する(次の命令に移る)前に、不要な生成物のクリーンアップする必要があります。 本当に効率的なDockerfileを作成するには、レイヤをできる限り小さく保ち、各レイヤが前のレイヤの生成物から必要なものを確保するために、シェルのトリックやその他のロジックを採用する必要がありました。 実際に、開発用にはアプリケーションのビルドに必要なすべてが含まれるDockerfileを使用し、プロダクト用にはアプリケーションおよび実行に必要なもののみが含まれるスリム化されたDockerfileを使用することは非常に一般的でした。 これがいわゆる"ビルダーパターン"です。 2つのDockerfilesを保守することは、理想的ではありません。
ここからはさわって学ぶクラウドインフラ本
- コンテナ コンテナはシステムの実行環境を隔離した空間のこと。 アプリケーションの実行に必要なプログラムやライブラリ、各種設定ファイルなどをワンパッケージにし隔離して実行するための仕組み。 コンテナのメリットはそれをコピーして別のコンピューターーで動かすのが容易なこと。
システム開発・運用の現場では、「開発者が作ったプログラム一式を検証機にコピーする」「検証機で動作確認して問題なければ本番機にコピーする」「冗長性や負荷分散のために、同じ構成のものコピーして多数台用意する」というように、そのコピーを作りたいことが、よくある。 コンテナ技術を使えば操作が容易になり、コピーや設定漏れを防げます。またシステムのアップデートも、コンテナを差し替えるだけで済むようになります。
Docker イメージ
2種類 Dockerイメージには基本的なディストリビューションとアプリケーション入りのがある。 カスタマイズするには基本的なディストリビューション入り(Linuxのみ)を使う
Dockerイメージに手を加える時は、アプリケーション入りDockerイメージではなく、LinuxのみDockerイメージをベースとするほうがやりやすい。 →ただ、そうしたカスタムは手間がかかり、1つひとつ手作業していると作業漏れが起こる可能性が少なくない。そこでコンテナに手を加えたあと、そのコンテナをカスタムDockerイメージに変換する。
- ではカスタムのDockerイメージを作るのは? カスタムのDockerイメージを作るときは、手作業でファイルコピーやコマンドを実行するのではなく、Dockerfileにファイルコピーや実行したいコマンドなど一連の設定を記述し、そのファイルを適用して作るのが一般的。 カスタムDockerイメージはDocker HubのようなDockerレジストリに登録できる。
Docker desctop
Docker Desctopの内部にはLinuxカーネルが含まれており、WindowsやmacOSでありながらもLinuxを実行することで、その上でDockerを使えるようにしたもの。
仮想サーバとコンテナの違い
仮想サーバは1台の物理的なサーバの中に復数の仮想的なサーバを作り、物理的なサーバを仮想的なサーバが分割して使う
コンテナはサーバをブナkつする技術ではない。サーバはあくまでも1台で、その中にたくさんのアプリケーションが隔離して実行されているのにすぎない。
コンテナの破棄
docker stopしてもコンテナはずっと残り続けるということ。 これは明らかにディスクを圧迫する。もう使わない場合は停止ではなく明示的に破棄にするべき docker rmで完全削除できる。
イメージの破棄
Dockerイメージが消費する容量も馬鹿にならない。 ダウンロードしたイメージはコンテナを破棄しても残ったまま。 ※これはもう一度、同じDockerイメージからコンテナを作ろうとした時に、再ダウンロードしなくても済むようにするため。
$docker image ls
$docker image rm
→ $docker rmi
とかける。
docker runについて
$docker run
というコマンドは
$docker pull
,$docker create
,$docker start
という3つの一連のコマンドをまとめて実行している。
使い方
$ docker run [オプション] イメージ [コマンド] [引数...]
タグ
イメージ名のタグ(tag)はDockerイメージの製作者が名付けた分類名のこと。 ※タグはリソース版や、開発版、バージョン番号などを示すのに使われる。
タグ名を省略する時は、最新版を意味するlatestというタグが指定されたものとみなされる。
オプション
-pオプション(略称 publish)
ポート番号をマッピングするもの
-p ホストのポート番号:コンテナのポート番号
udpを選択する場合には、ポート番号を ポート番号/udp
とする。
※Dockerでは、pオプションを指定しない限り、DockerホストとDockerコンテナとの通信は繋がらない。
※マッピングの状態は docker port
コマンドで確認ができる。
-vオプション
コンテナの特定のディレクトリにホストのディレクトリをマウントする設定。 -mountオプションを同様
-v ホストのディレクトリ:コンテナのディレクトリ
-ditオプション
-ditは -d, -i, -t
の3つのオプションの組み合わせ。
-dが端末から切り離してバックグラウンドで実行 -iと-tはコンテナを端末(キーボードとディスプレイ)から操作するためのオプション
アタッチの場合(-dを指定しない時)は端末と接続された状態のため、端末からの操作は、そのままコンテナ内で実行中の既定のコマンドに流されます。だからこそ、[Ctrl]+[C]を押すことで、そのコマンドが終了する。 デタッチのときは、端末とは切り離されているので、コンテナ内で実行されているコマンドに対して、何かキー操作することはできません。デタッチの状態とアタッチの状態は、実行中に切り替えることができる。
-iオプション
標準入出力およびエラー出力をコンテナに対して結びつけます。その結果、キー入力した文字はコンテナに渡され、コンテナからの出力が画面に表示されるようになります。iオプションを指定しないと、キー入力はコンテナに伝わりませんからこうしたキーが効きません。そしてコンテナからの出力が届きませんから、httpdコンテナの例で言えば、いま見てきたように、画面に各種ログが表示されることもありません。
-tオプション
-tオプションは、pseudottyと呼ばれる疑似端末を有効にする設定です。疑似端末は、カーソルキーやエスケープキー、[Ctrl]キーなどで操作するためのものです。このオプションを指定せず、iオプションのみだと、これらのキーが使えません。つまり、[Ctrl]+[P]、[Ctrl]+[Q]キーが効きません。
デタッチで起動後、操作の必要がないのであれば-iや-tのオプションは必要ない。 しかし、後でアタッチするなどして端末から操作したいときは-iや-tを指定する必要がある。
Volume関連
ボリュームマウント ボリュームとは、Dockerコンテナーで扱うデータを永続化する仕組み。 Dockerの中にボリュームというデータを保持する領域を確保し永続化する。 HOSTでソースコードなどを変更する必要がないのであれば、ファイル類をすべてコンテナ内管理する。 具体的にはボリュームマウントしたフォルダでファイルを管理するのがベスト。 問題は、ボリュームマウントしたファイルはHOSTから直接操作できないのが難点。 ですので、ソースコードを変更しなければならない開発環境で使うのは難しいです。 また、HOSTで自由に読み書きできないので、バックアップをとるのもちょっと面倒。 docker snapshotみたいな機能があればいいんですが…現状はないっぽいです。
因みに、VSCodeであれば、拡張機能を使うことによってボリューム内のファイルの読み書きが可能です。
これから新規に開発環境を作るのであれば、VSCodeを使うことを前提にボリュームマウントを使うことをお勧めします。
バインドマウント
ホスト側のディレクトリやファイルをコンテナー内にマウントすること。
バインドマウントによってもコンテナーで扱うデータを永続化できる。
※ただしDockerDesktopを使用していると、LinuxのVMを経由して動作させる必要があるためファイルアクセスのパフォーマンスが低下する可能性。そのため :cached
を追加してバインドする。
バインドマウントでマウントキャッシュを利用してホストマシン上のディレクトリをビルド中のコンテナ内にマウントすると、ファイルのuid, git, modeはコンテナ内でファイルを作成するとrootとして操作したことになり、ファイルは uid=0
で作成される。
もしそれがホストマシン上のディレクトリをマウントした場所なら、ホストマシン上のディレクトリにuid=0のファイルが作成されることになります。ファイルのownerやパーミッションの値に注意しましょう。
バインドマウントの注意 マウントするボリュームに何かしらファイルやディレクトリが存在すれば、マウントされるコンテナー側のディレクトリの中身は全部上書きされる。 マウントするボリュームに何も入っていなくて、マウントされるコンテナー側のディレクトリに何か入っていれば、ボリュームにコピーされる。
docker内のファイルと永続化
Dockerコンテナは、それぞれが隔離された実行環境。コンテナを破棄すればその中にあるファイルは自ずと失われる。
$docker rm
このコマンドをして再度 docker run
をするとコンテナIDが変わるとおもう。前のコンテナとは別のものとなっている証拠。
ホストからファイルをコピーする
コンテナーの中のファイルを変更するにはどのようにすればいいか
docker execで、/bin/bashを起動しそこでnanoエディターなどを起動して、編集する方法。 しかしapacheなどではファイルサイズを小さくするためnanoエディターなどが入っていないおそれ
そこで便利なのが docker cp
コマンド
※docker cpはパーミッションをそのままコピーする。ディレクトリも再帰的にコピーする。
※docker cpは/prop, /sys, /dev, tmpfs配下のようなシステムファイルはコピーできない。こうしたファイルをコピーしたい時には標準入出力経由でコピーする。
上記を含めると、コンテナーは一度起動したら破棄してはいけないのか?
違う。そうではなくコンテナーで失ってはいけないデータは外に出すように設計する。
- ではどうすればいいか?
マウントする。 コンテナーは実行するシステムと扱うデータは別に管理することが推奨されている。 またコンテナーの設計として実行するシステムと扱うデータは別に管理することが推奨されている。
docker run -v
でマウントをする。
ボリュームのマウントにはバインドマウントと同様にvオプションを使う。 違うのはディレクトリ名ではなくボリューム名を使うという点だけ
注意 **ただしボリュームのマウントにはvオプションではなく、mountオプションを使うことが推奨されている。**vオプションだとボリュームが作られていない時に新規にボリュームが作成されてしまい、意図しない結果になるため。
つまりデータをコンテナーの外に出す。 Dockerホストのディレクトリをマウントしているのは(外に出しているのと同意)そのためコンテナーを削除しても消えないのは当たり前
mountオプションを使ったマウントの設定
バインドマウントやボリュームマウントするのに-vオプションを使っていたがもう1つ--mountというオプションを使う方法もある。
--mount type=マウントの種類, src=マウント元, dst=マウント先
マウントの種類は、バインドマウントのときはbind, ボリュームマウントのときはvolumeを指定する。 srcはマウント元、dstはマウント先(srcはsource, dstはdestinationやtargetとも書ける。)
--mountはDocker17.06からサポートされたオプション。--mountのほうが推奨されている。
-
バインドマウントかボリュームマウントかわかりにく。 -vオプションではマウント元が[/]から始まる時はバインドマウント、そうではないときはボリュームマウント。
-
ボリュームが存在しない時はエラーを吐く。
コンテナ間のデータ共有にも利用できる。
マウントする手法はデータを失わないようにするだけではなく、別の方法もある。 それはコンテナ間でのデータ共有。1つの場所を2以上のコンテナで同時にマウントすることもできる。
そのため./docker/nginx/nginx.confなど、設定ファイルをプロジェクトに用意しているのは、コンテナ内のnginxの設定ディレクトリにマウントし本来のnginx.confを上書きしている。 この方法であれば、Dockerホストに設定ファイルが残るため設定のバックアップが容易。
※マウントはディレクトリに対して設定するのがほとんどだが、設定ファイルだけをマウントすることも可能。
Dockerホストにあらかじめディレクトリを作っておき、それをマウントする方法をバインドマウントという
- マウント種類
バインドマウント Dockerホストにあらかじめディレクトリを作っておき、それをマウントする方法をバインドマウントという ディレクトリの変更をDockerコンテナに反映したいとき(アプリケーション用のコンテナなど)
ボリュームマウント ボリュームマウントは、ホスト上のディレクトリではなく、DockerEngine上で確保した領域をマウントする方法。 確保した場所のことを、データボリュームもしくは略してボリュームと言う。
データベース用のコンテナなど
ボリュームを使う利点は、ボリュームの保存場所がDockerEngineで管理されるため、その物理的な位置を意識する必要がなくなるという点。ディレクトリ構造はDockerホストの構成によって違うので、ディレクトリ名で指定する場合(dockerrunやdockercreateのvオプションでディレクトリを指定する場合)は、Dockerホストに合わせた場所を指定しなければならず、汎用的ではありません(たとえば、ある管理者は/home以下を使うように構成したかもしれませんし、別の管理者は/var以下を使うように構成したかもしれません)。それに対して、ボリュームを扱う方法は汎用的で、どのDockerホストでも同じです。すぐあとに見るように、dockercreateでボリュームを作るコマンド、そしてそのボリュームをdockerrunやdockercreateで指定するためのオプションは、どのDockerホストでも同じです。
ボリュームマウントはブラックボックス。そのためDockerホストから変更できない。
汎用性という目でいうと、ボリュームマウントが推奨されるが、バインドマウントを完全に置き換えるわけではない。 バインドマウントのほうが優れている場面もある。
バインドマウントのがよい場面
-
設定ファイルの受け渡し Dockerホスト上に設定ファイルを置いたディレクトリを用意して、それをコンテナに渡したい場合です。
-
作業ディレクトリの変更を即座にDockerコンテナから参照したいとき Dockerホスト上のファイルを変更したとき、それをDockerコンテナにすぐに反映させたいときです。たとえば、httpdコンテナを動かして、そのドキュメントルート(/usr/local/apache/htdocs)を、これまでのように、Dockerホストの適当なディレクトリにバインドした場合、Dockerホスト側でそのディレクトリ内のファイルを変更すれば、それはすぐにDockerコンテナに反映されます。
-
各自のPCでDockerを使う場合はバインドマウントが便利
ボリュームマウントのほうがよい場面
ボリュームマウントが向く場面、Dockerコンテナが扱うデータをブラックボックスとして扱い、コンテナを破棄してもデータが残るようにしたいだけの場面。
データベースのデータは、通常ひとまとめのブラックボックスとして扱い、**それぞれのファイルをDockerホストから編集することはないはず。**もしそんなことをしたら、データベースは壊れてしまうことでしょう。このようにDockerホストから不用意にデータを書き換えたくない場面では、ボリュームマウントが向きます ※ボリュームはデフォルトでは、Dockerホスト上のストレージですが、ボリュームプラグインをインストールすることで、AWSのS3ストレージやNFSなどのネットワークストレージを用いることもできます。
ボリューム作成手順(MySQL)
-
ボリュームを作成する(mysqlvolumeという名前)
$docker volume create mysqlvolume
-
作成したボリュームを確認する
$docker volume ls
DRIVER VOLUME NAME local mysqlvolume
※dockervolumelsの結果として表示される「DRIVER」は、ボリュームを構成するドライバです。既定は「local」であり、Dockerホスト上のディスク上に作成される。それ以外にボリュームプラグインをインストールすることでAmazon S3のストレージやNFSなどのネットワークストレージを利用できる。
tmpfs
$docker run --mount type=tmpfs, dst=マウント先
tmpfsはディスクではなく、メモリーを特定のマウント先に指定するものでメモリーディスクを利用することで読み書きを高速化する目的で使う。 tmpfsはメモリーのため揮発性。コンテナを破棄することでなくなる。
データのバックアップ
バインドマウントの場合はDockerホスト上のファイルのためDockerホストで別のディレクトリにコピーするか、tarコマンドでまとめて保存するなどでバックアップができる。
- ボリュームの場合は?
ボリュームの詳細情報は
$docker volume inspect ボリューム名
で見れる。 この場所をtarで固めても意味がない
ボリュームのバックアップの考え方
ボリュームの場合は適当なコンテナに割り当てて、そのコンテナを使ってバックアップを取るようにする。
Docker のネットワーク
Dockerのネットワークは大別するとnone, host, bridghという3つの構成を取ることができる。
本番運用
本番の安全運用は、Dockerホストをマネージドサービスにしてある程度、任せてしまうのが無難。 AWSにはAmazon ECSというコンテナを運用するマネージドサービスがある。
-
マネージドサービス 管理されたManagedサービスという意味で、運用管理をクラウドに任せることができるサービスのことを言う。 仮想サーバのEC2は自分で管理するサービスのためアンマネージドサービスという。
-
さらなるスケーリングや堅牢性がほしいときはKubernetesを使う。
-
どのイメージをダウンロードすればいいのか 納品にあたっては、動作検証するはず、動作検証後にコンテナのバージョンが変わるということはシステムが変わること。 本番環境でDockerを利用するのであれば、タグ名を省略せずに明示的に指定して、特定のイメージの版に利用する(それよりも新しい版ができたとしても使わないようにする。) **もちろん、その特定イメージの版のまま使い続けるという意味ではありません。**ある程度の期間が経ったら、そのときの最新版で再度動作検証し、問題なければ、その版に差し替えるというように、コンテナの定期的なアップデートは必須です(そうしなければ、脆弱性などに対応できない。
-
Dockerイメージを使う場合 そのイメージの製作者がどのような使い方を想定して作っているのか、各種設定はどのようにして行えばいいのかを記載されているドキュメントから汲み取らなければならない。 かいつまんででもいいので下に記す。
1. マウントすべきディレクトリを知る データベースのデータは、/var/lib/mysqlディレクトリに保存されます。ここをボリュームマウント(もしくはバインドマウント)することで、コンテナを破棄しても、データベースの内容が失われないようにする。
- rootユーザのユーザ名、パスワード、規定のデータベース名などの指定方法
データベースにアクセスする際のrootユーザーのユーザー名、パスワード、既定のデータベースなどは、環境変数として引き渡します
docker run -e [環境変数]
※もっと複雑な設定をしたい時は、MySQLの設定ファイルであるmy.cnfをファイルをバインドマウントで引き渡す方法もとれる。
Docker build
Dokerfileからdocker imageを作成するコマンド
$docker build
build後、docker history
で作成されたイメージの詳細情報が確認できる。
ビルド後のイメージを利用する
docker run
をすれば利用できる。
Dockerfile覚書
[COPY] コピー元はDockerfileファイルが置かれている場所からの相対パス コピー先はWORKDIR命令で指定したパスからの相対パス
RUN
RUNコマンドは、docker buildするタイミング(すなわちイメージを生成するとき)に実行している。ここにはイメージの時点で実行しておきたいコマンドを書く。 例としてはソフトウェアパッケージのインストールやファイルのコピー、変更などの処理。
※注意 RUN命令は複数のコマンドを実行するときも、できるだけ1つのRUNコマンドで済ませるようにする。
CMDとENTRYPOINT
docker runやdocker createの引数で指定したコマンドが終了するとコンテナ自身が終了する挙動だったことを思い出す。 service httpd startを実行した場合は、httpdをバックグラウンドで実行してserviceコマンド自体はすぐに終了する。 その結果、コンテナ自体がすぐに終了してしまう。 コンテナを動かしっぱなしにしたいのであれば、CMDやENTRYPOINTはずっと動きっぱなしでいるコマンドを指定する
CMDとENTRYPOINTを指定しない場合は、ベースイメージの設定値が引き継がれる。 ※なおCMDとENTRYPOINTはDockerfileに1つしか記述ができない。複数記述ができれば、もっとも後ろにある設定が採用されそれ以外は無視される。
CMDとENTRYPOINTはコンテナを起動したときのタイミング(docker startやdocker runするときのタイミングで)コンテナの中で実行するコマンドを指定するもの
※docker run
ではコンテナの中で実行するコマンドを指定し、そのコマンドが終了するとコンテナが終了するという仕組みだったことを思い出す。
-
ENTRYPOINT 指定した場合、docker runの最後に指定するコマンドは、このENTRYPOINTで指定したコマンドへの引数となる。
-
CMD
EXPOSE
docker run
する際、-pオプションだけを指定し、ポート番号を省略した時にEXPOSEで指定したポートのマッピングが行なわれるようになる。
ARG [変数名]
Dockerfile内で使用する変数名を指定する。 docker-compose.yml内で指定するのが入ってくる。
ENV[変数名=値]
Dockerイメージで使用する環境変数を指定する。 ENVを使って設定した環境変数はイメージからコンテナへ渡させる。 コンテナへ渡させると、コンテナ内で起動したアプリケーションで参照できる。
ENVを使って設定した環境変数はイメージからコンテナへ渡せる
apk
Alpine Linuxで使用できるパッケージ管理コマンド(Alpine Linux package managementの略) Linuxコマンドのapt-getが使用される場合は、ベースイメージがAlpineではないということ。
Dockerfileとキャッシュ
docker buildはDockerfileに1行1行のビルド行程をキャッシュする。 そしてそこまでに変更がなければキャッシュが使われる。
docker build
する際、キャッシュを使うかどうかは次の基準で決まる。
- FROMで指定しているベースイメージのキャッシュが変わった
- Dockerfile自体の命令が変わった
- ADDやCOPYしているファイルの対象が変わった
キャッシュ判断されないもの たとえば、aptコマンドでApacheやPHPのパッケージをインストールしているが、ApacheやPHPのパッケージがアップデートされたかどうかは、キャッシュの判断基準ではない。 RUNコマンドでどこかからのサイトからのファイルをダウンロードしている場合、その対象ファイルが更新されたかどうかまでを判定するものでありません。
キャッシュを活用してビルドを高速化する
たくさんのパッケージをインストールし、内部でコンパイルするようなコンテナを作ると、docker buildするときとても時間がかかる。 そのような時は、キャッシュを活用すると高速化できる。 RUNコマンドをあえて複数に分ける。そこで別のキャッシュが作られるため、変更されていない部分はそのRUNコマンドの実行を飛ばすことができ高速化できる。
Dockerをさらに早める
Docker Daemonへの転送ファイル削除
Docker image指定
FROM [:TAG|@DIGEST]
DIGEST
ダイジェストを使うとimageを特定できる。
$ docker image ls --digests
で表示が可能。
v2以降の形式を使うイメージには、digestと呼ばれる識別子が振られている。 ダイジェスト値は、イメージ生成後に変更が加えられれば値は変わる。
Docker クリーンアップ
Dockerは使用していないオブジェクト(イメージ・コンテナ・ボリューム・ネットワーク)に対するクリーンアップには慎重なアプローチを取っている。 ※すなわち各オブジェクトをDockerに対して明示的に削除を命令しない限り、削除はしない。 →その結果Dockerは巨大なディスク容量を使うことになった。
dangling image(宙ぶらりイメージ) : ダングリングイメージ
-
latestタグ これは最新のイメージを意味し、現在使用しているイメージとなる
-
noneタグ noneタグとは、同じイメージの中で、最新ではないイメージを指す。
Dockerはイメージを作り替えると、過去のイメージを保管したまま新たなイメージを作成する。 これではどんどんイメージが蓄積され、 PCのメモリを大きく消費してしまう。
dangling状態のイメージは特別な理由がない場合には不要。
<none>
とは何?
Dockerでは同一名のイメージを複数作成できない。
すでに存在するイメージと同一名のDockerイメージを作成しようとした場合は、次のような挙動となる。
最新のビルド結果のイメージが、指定のイメージ名称となる。(上記例の場合はABCDEFG)
すでに存在していた同一名称のイメージは、<none>
という名称未設定の状態に置き換わる
この症状のことを、dangling(宙ぶらりん)という。
削除後、<none>
のimageが消えている。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
neams_api latest f86551e138bc 11 minutes ago 600MB
neams_front latest a665671491c1 12 hours ago 589MB
wordpress latest b6306a6a7068 4 weeks ago 616MB
mysql 5.7 738e7101490b 6 weeks ago 448MB
doc_swagger-merger latest 6421e7fee9e3 6 months ago 118MB
コンテナのprune
コンテナを停止しても --rm
フラグを付けて起動していなければコンテナは自動に削除されない。
停止しているコンテナの書き込み可能なレイヤーは、ディスク容量を消費し続ける。
これらを綺麗に片付けるには $ docker container prune
コマンドを使用する。
Docker コア数確認
# コンテナに入る
$ docker-compose run --rm [service name] /bin/sh
# コア数確認
$ nproc
2
Docker log ディレクトリ確認
Tips
Docker Buildにおけるリードタイム短縮 参考URL
Dockerイメージ分析ツール dive
docker image選び
利用するDocker imageを選ぶ際に、同じバージョンであっても後ろに -stretch
やら -buster
やらがついていて迷うことがある。
これはimageがベースとしているOSの種類によるものです。
stretch/buster/jessie/bullseye
これらの文字列がついたimageは、それぞれ同名のDebian Releaseをベースに構築されたものです。 コードが特定のDebian Releaseやバージョンと互換性を持つ特殊な場合を覗いて、基本的にどれを選んでも大差ありません。
slim
-slim
がついたimageはフルイメージの下位互換バージョンです。インストール済みのパッケージは使用頻度の高いものに限定され、その分軽量化されている。
軽量のimageが望ましい場合に採用する。
ただし前述のようにフルイメージに存在するパッケージが一部存在しないため、正しく動作するか十分なテストが必要。
alpine
-alpine
がついたimageは、Alpine Linuxをベースに構築されたもの。
Alpine Linuxは、コンテナで利用することを想定して設計されたOSで、極めて軽量。
Alpineのベースイメージは5MB未満と非常に小さく収まっている。
コンテナを可能な限り最小で最速で構築したい場合に採用が適してる。
一方で欠点もあります。ベースOSが大きく異なるため、主要なパッケージ、コマンドのいくつかがないか、あるいは類似の別のものを利用する必要があります。
たとえば、aptの代わりにapkでパッケージをインストール(結構古いバージョンがが入ります)、glibcでなくmusl libが入っている、等。 また、一部Debianとの互換性を持つPythonパッケージは、Alpineベースのimageで動作するように再コンパイルしなければ動作しません。
とはいえ、それが大きな問題にならないのであれば、フルイメージを使うよりも、alpine imageにapkで必要なパッケージのみをインストールして利用した方が軽量かつ高速で環境を構築できます。
ただし、正常に挙動するか、入念なテストを行う必要があります。
結局どれ
-
十分なテストをする余裕がなく、環境を素早く立ち上げなければいけない場合、stretch, buster, jessie, bullseyeあたりを適当に選ぶ。
-
コンテナを軽量化する必要があり、最小限のパッケージだけで動作することが分かっていれば、slimを採用する。
-
コンテナを限界まで軽量化する必要があり、十分なテストが可能であればalpineを採用する。ただし、移植等した際に挙動がおかしくなる可能性がある。
-
頻繁にimageを取得するユースケースであれば、slimやalpineを優先的に採用する 私はCIを爆速で回したいのでalpineで頑張ってみるのが好きですが、すんなりうまく行くことは少なく、結局調べたり乗り込んでパッケージを入れたりしてやっとこさ成功する。
scriptsに分ける
キャッシュを
security
安全性を高めるために バイナリダウンロード元にチェックサムや署名などの情報があるのであれば、Dockerfileの中で検証ロジックを入れておくと良い、
M1 macでのdocker
Docker マルチCPUアーキテクチャ
Docker HubにおけるマルチCPUアーキテクチャサポートとは、x86 (AMD64) やARM64など複数のアーキテクチャ向けのイメージを**同一のイメージ名・タグ名で管理できる仕組み Docker Hubで公開されている公式イメージの多くはマルチCPUアーキテクチャに対応している。
公開されているイメージが「マルチCPUアーキテクチャ」に対応している場合、Docker Hubからプルを実行した際に、とくにアーキテクチャを明示しなくても自動的に適切なアーキテクチャのイメージがダウンロードされます。
db:
# これを指定しなくてもM1であれば自動ダウンロード?
platform: linux/amd64
image: mysql:5.7
health check
実行するコマンドはコンテナが正常に稼動していれば終了コード「0」を、そうでなければ「1」を返すようになっている必要がある。これさえ守っていれば、実行するコマンドはバイナリでもスクリプトでも構わない。
Tipsコマンド
prune削除コマンド
docker container prune -f;
docker image prune -f;
docker volume prune -f