Keycloakを使ってみて、是非これは本番環境に入れたいと思った。思ったついでに、作ったコンテナやイメージを自由にあっちこっち持って行けたらいいなと考えて、Dockerについて学習してみることにした。そもそも、コンテナとイメージの違いがよく分からない…
結論はとても簡単。しかし、そこまでに費やした時間が凄かった。
移設の考え方を整理
イメージ・コンテナ・ボリューム
まずは、移設の対象となるモノを整理。
イメージはOSやアプリケーションが入ったROMみたいなもの。
状態(State)を持たず、変更することができない。
Dockerfileからビルドしたり、色々といじり倒したコンテナをコミットしたりして作られる。
コンテナはイメージを取り込み、実行用の様々な情報を加えて作られる。
状態(State)を持っていて、実行されたり停止されたりする。
コンテナに対する変更はコンテナ・レイヤーに保存される(そのため、コンテナを削除するとコンテナで行った変更は消える)。
ボリュームはデーターを永続化させる必要があれば(自分で)作成し、コンテナからマウントして利用する。
複数のコンテナで共有することができる。
色々と自分なりに調べた結果、それぞれの具体的な中身はこんな感じ。
- Image - - Container - ┌───────────┐ ┌───────────┐ │ID:e28abce97d93~ │ │ID:2b3cd8ab8d94~ │ └───────────┘ └───────────┘ ┌───────────┐ ┌───────────┐ │Config: │==│Config: │ │(環境変数群) │ │(実行用環境変数群) │ └───────────┘ └───────────┘ ┌───────────┐ ┌───────────┐ │GraphDriver: │ │GraphDriver: │ │ LowerDir:~ │ │ LowerDir:~ │ │ MergedDir:~ │==│ MergedDir:~ │ │ UpperDir:~ │ │ UpperDir:~ │ │ WorkDir:~ │ │ WorkDir:~ │ │(OS/Application/Data) │ │(OS/Application/Data) │ └────┬┬─────┘ └────┬┬─────┘ - Volume - ┌────┴┴─────┐ ┌────┴┴─────┐ ┌───────────┐ │RootFS: │ │コンテナ・レイヤー │ │Mountpoint: │ │ Layer:第1レイヤー │ └───────────┘ │ ホストのディレクトリ│ │ Layer:第2レイヤー │ ┌───────────┐ └───────────┘ │ … │ │実行用の様々な情報 │ │ Layer:最終レイヤー │ │ 状態(State) │ └───────────┘ │ hostname │ ┌───────────┐ │ hosts │ │OS:Linux │ │ resolv.conf │ │Architecture:amd64 │ │ ~.log │ │(特徴として持つ) │ │ ネットワーク設定 │ └───────────┘ │ etc. │ ┌───────────┐ └───────────┘ │Parent:基にした │ ┌───────────┐ │ イメージかコンテナ│ │Image:e28abce97d93~ │ └───────────┘ └───────────┘
※もちろん保持する情報はこれだけではないが、概要を理解し、大まかに差分を知る為に必要な項目をピックアップした。
※記号==で結んだところは多分Imageが基になって作られるんじゃないかと思った場所(必要な変更はなされるし、環境変数は足すことができる)。
inspectしてみるとコンテナには幾つもConfigっぽいのがあるとわかる。ここで書いたConfigは正しくないかもしれない。
それと、GraphDriverとRootFSの関係も理解がちょっと怪しいが…多分、同じものを表している。
GraphDriverにはImageやコンテナ・レイヤーが全て定義されているが、RootFSは何らかの基準(実際にデーターに変化があったこと?)で間引いたレイヤーを持つように見えた。
移設の対象
このように整理すると、今動いているシステムをそのまま移設するなら、以下を持って行く必要があると思った。
- コンテナが取り込んでいるイメージ
- コンテナの実行用環境変数+コンテナ・レイヤーに含まれる情報
- ボリュームがあるならそれも
Docker+バックアップとかで探すと、saveとexportがヒットする。
日本語マニュアルで調べながら動作を確認してみることにした。
実際の移設
テストのために作った小さな環境を移設してみる。
docker save と load
saveはイメージをバックアップするので、コンテナ・レイヤーを保存しておく必要がある(docker commitする)。
イメージの段階ではボリュームをマウントできないので、ボリュームはsaveの対象外になる。
使い方: docker save [オプション] イメージ [イメージ…]Docs / save
イメージをtarにまとめる。
$ sudo docker save -o apache-test.tar 6b7f288de233
できあがったtarボールをまっさら環境に移してloadしてみる。
$ sudo docker load -i apache-test.tar 7ef368776582: Loading layer [==================================================>] 65.61MB/65.61MB 83f4287e1f04: Loading layer [==================================================>] 991.7kB/991.7kB d3a6da143c91: Loading layer [==================================================>] 15.87kB/15.87kB 8682f9a74649: Loading layer [==================================================>] 3.072kB/3.072kB 12663bddb053: Loading layer [==================================================>] 128.1MB/128.1MB 22f494d3d2a8: Loading layer [==================================================>] 51.02MB/51.02MB Loaded image ID: sha256:6b7f288de233269287e39bf9eace2bd83f887440b58f9a70f795a58331b54492
※下の方で作ったコンテナで、作った後ちょっといじってコミットし、6階層のレイヤーができあがっていたが、それらが全て取り込まれた。
loadしたイメージにリポジトリとタグがついていなかったので付けた。
その上で、移行元と同じパラメーターを付けて実行してみる。
$ sudo docker image tag 6b7f288de233 apache-test:latest $ sudo docker run -p 9080:80 -d -h apache-host --name=apache-cont apache-test 28a4a571f6ca0c0fa562f16a11252c13b0387b5b4c95fe404f5c1e6071555af2
ちょっと改変を加えたApacheなんだけれども、ポート9080番でアクセスしてみたら改変状態が反映されており、上手く動作した。
docker export と import
コンテナにあるファイルをまるごとバックアップする。
レイヤーは意識せずに見えているファイルを全部持ってくるので、インポートするとレイヤーは1つになっている。
実行用の環境設定がらみの情報は取り出せないので、必要な設定は上からかぶせる。
使い方: docker export [オプション] コンテナ
Docs / export
ボリュームはマウントしている部分だけを出力する、とされている。
ボリュームを使うように構成した場合についても、どんな風に移設するのか理解しなきゃならなそうだが、そうした問題に直面するまでは後回しにしよう。
コンテナをtarにまとめる。
$ sudo docker export -o apache-test.tar sad_dhawan
できあがったtarボールをまっさら環境に移してimportしてみる。
importはコンテナではなく、イメージになるので注意。
$ sudo docker import apache-test.tar apache-test sha256:98cbad280c0085956c7081ad6d3e934c811b28f92c80414d964797148113f064
移行元と同じパラメーターを付けて実行してみる。
$ sudo docker run -p 9080:80 -d -h apache-host --name=apache-cont apache-test docker: Error response from daemon: No command specified.
エラーが起きて調べてみたら…あぁ、実行のために必要な環境変数とかCMDが全部なくなってる。
$ sudo docker inspect apache-test
…
"Config": {
…
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"APACHE_RUN_USER=www-data",
"APACHE_RUN_GROUP=www-data",
"APACHE_PID_FILE=/var/run/apache2.pid",
"APACHE_RUN_DIR=/var/run/apache2",
"APACHE_LOG_DIR=/var/log/apache2",
"APACHE_LOCK_DIR=/var/lock/apache2",
"DEBCONF_NOWARNINGS=yes"
],
"Cmd": [
"apachectl",
"-D",
"FOREGROUND"
],
…
※元環境にはあるEnvとかCmdがごっそり抜けていた。
早速Dockerfileを準備して…
Dockerfile
FROM apache-test:latest MAINTAINER rohhie ENV APACHE_RUN_USER www-data ENV APACHE_RUN_GROUP www-data ENV APACHE_PID_FILE /var/run/apache2.pid ENV APACHE_RUN_DIR /var/run/apache2 ENV APACHE_LOG_DIR /var/log/apache2 ENV APACHE_LOCK_DIR /var/lock/apache2 ENV DEBCONF_NOWARNINGS yes EXPOSE 80 CMD ["apachectl", "-D", "FOREGROUND"]
※Qiita / Dockerでapache2起動より抜粋+いくつかの変更。
※インポートしたイメージを親イメージとし、Apacheインストールを削除している。
buildする。
$ sudo docker build -t apache-test . $ sudo docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE apache-test latest 276bc92398b8 About a minute ago 236MB $ sudo docker image history apache-test IMAGE CREATED CREATED BY SIZE COMMENT 276bc92398b8 2 minutes ago /bin/sh -c #(nop) CMD ["apachectl" "-D" "FO… 0B dd8ab46c78be 2 minutes ago /bin/sh -c #(nop) EXPOSE 80 0B d63de5c127c6 2 minutes ago /bin/sh -c #(nop) ENV DEBCONF_NOWARNINGS=yes 0B f13ecacea79a 2 minutes ago /bin/sh -c #(nop) ENV APACHE_LOCK_DIR=/var/… 0B 1947eafe9b92 2 minutes ago /bin/sh -c #(nop) ENV APACHE_LOG_DIR=/var/l… 0B 1983ee0e863e 2 minutes ago /bin/sh -c #(nop) ENV APACHE_RUN_DIR=/var/r… 0B c061f62fe100 2 minutes ago /bin/sh -c #(nop) ENV APACHE_PID_FILE=/var/… 0B cfe2f67e8568 2 minutes ago /bin/sh -c #(nop) ENV APACHE_RUN_GROUP=www-… 0B ce02e891d09d 2 minutes ago /bin/sh -c #(nop) ENV APACHE_RUN_USER=www-d… 0B af9539ff2783 2 minutes ago /bin/sh -c #(nop) MAINTAINER rohhie 0B 98cbad280c00 30 minutes ago 236MB Imported from -
※想像していたのと違う結果になった。別のイメージができるのではなく、同じイメージの履歴となった。
よし、環境設定ができた。改めて起動。
$ sudo docker run -p 9080:80 -d -h apache-host --name=apache-cont apache-test ca25ac7bb5c835e8abb51284c18e27fee1cf1f9c7eeb217758a8fae1b912c8aa
ポート9080番にアクセスしてページが表示されることを確認した。
ここでは、Dockerfileを使って環境変数を設定しているが、後日、docker-composeを利用したコンテナの起動も試している。※2021/05/29追記
save/loadとexport/importのどちらを使うか
最後の状態が復元できれば良い、ということなら、どちらを使っても良さそう。
save/loadをする場合はイメージを移設する形になるから、事前にコミットする必要があってレイヤーが1層増えるけど、お手軽。
レイヤーの数はパフォーマンスにあまり影響しないのかな。
export/importの場合は、環境変数とか実行コマンドを復元する必要があるので、少し手間が掛かるかもしれない。
とはいえ、inspectすれば設定値は見つかるから、どうにでもなるような気もする。
レイヤーが1層にまとまるので何らかの制限に引っかかった場合には回避策になるかもしれない。
最新バージョンのDockerを導入する
少なくとも、移行先のDockerは最新バージョンにしていくのが良いと思う。
移行元についても最新バージョンの方が良いだろう。バックアップを取っておけたらなお安心。
手順はほぼ公式に従う。
docker docks / Install Docker Engine on Ubuntu
事前に古いバージョンを削除しておく
導入済みのパッケージを調べてみる。
$ dpkg -l *docker* containerd runc Desired=Unknown/Install/Remove/Purge/Hold | Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend |/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad) ||/ Name Version Architecture Description +++-========================-=================-=================-===================================================== ii containerd 1.3.3-0ubuntu1~18 amd64 daemon to control runC ii docker 1.5-1build1 amd64 System tray for KDE3/GNOME2 docklet applications ii docker-compose 1.17.1-2 all Punctual, lightweight development environments using un docker-doc <none> <none> (no description available) ii docker.io 19.03.6-0ubuntu1~ amd64 Linux container runtime ii golang-docker-credential 0.5.0-2 amd64 Use native stores to safeguard Docker credentials ii python-docker 2.5.1-1 all Python wrapper to access docker.io's control socket ii python-dockerpty 0.4.1-1 all Pseudo-tty handler for docker Python client (Python 2 ii python-dockerpycreds 0.2.1-1 all Python bindings for the docker credentials store API ii runc 1.0.0~rc10-0ubunt amd64 Open Container Project - runtime
パッケージが入っているので削除する。
$ sudo apt remove docker docker-engine docker.io containerd runc $ dpkg -l *docker* containerd runc Desired=Unknown/Install/Remove/Purge/Hold | Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend |/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad) ||/ Name Version Architecture Description +++-========================-=================-=================-===================================================== rc containerd 1.3.3-0ubuntu1~18 amd64 daemon to control runC rc docker 1.5-1build1 amd64 System tray for KDE3/GNOME2 docklet applications ii docker-compose 1.17.1-2 all Punctual, lightweight development environments using un docker-doc >none< >none< (no description available) rc docker.io 19.03.6-0ubuntu1~ amd64 Linux container runtime ii golang-docker-credential 0.5.0-2 amd64 Use native stores to safeguard Docker credentials ii python-docker 2.5.1-1 all Python wrapper to access docker.io's control socket ii python-dockerpty 0.4.1-1 all Pseudo-tty handler for docker Python client (Python 2 ii python-dockerpycreds 0.2.1-1 all Python bindings for the docker credentials store API un runc >none< >none< (no description available)
※いくつか残っているけれども、これはこれで良いのかもしれない。
何にもインストールされていないまっさら環境だとこんな感じ。
$ dpkg -l *docker* containerd runc dpkg-query: no packages found matching *docker* dpkg-query: no packages found matching containerd dpkg-query: no packages found matching runc
リポジトリの追加
httpsのリポジトリからインストールができるように必要なパッケージをインストールする、とある。
公式のGPGキーを追加し、確認する。
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - OK $ sudo apt-key fingerprint 0EBFCD88 pub rsa4096 2017-02-22 [SCEA] 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88 uid [ unknown] Docker Release (CE deb) <docker@docker.com> sub rsa4096 2017-02-22 [S]
※curlの部分だけ実行してみたところ、PGP公開鍵が表示された。GPG(GnuPG)はPGPと互換性があるらしい。
リポジトリを追加する。
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" Hit:1 http://jp.archive.ubuntu.com/ubuntu bionic InRelease Hit:2 http://jp.archive.ubuntu.com/ubuntu bionic-updates InRelease Hit:3 http://jp.archive.ubuntu.com/ubuntu bionic-backports InRelease Hit:4 http://jp.archive.ubuntu.com/ubuntu bionic-security InRelease Get:5 https://download.docker.com/linux/ubuntu bionic InRelease [64.4 kB] Get:6 https://download.docker.com/linux/ubuntu bionic/stable amd64 Packages [12.5 kB] Fetched 76.9 kB in 1s (90.7 kB/s) Reading package lists... Done
※apt update相当は行われているように見える。
ソースに以下の行が追加されていた。
/etc/apt/sources.list
deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable # deb-src [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable
※add-apt-repositoryで$(lsb_release -cs)とした部分は、bionicに変わっていた。
ここで一度全てを更新して再起動しておくと良いかもしれない。
$ sudo apt update; sudo apt -y dist-upgrade; sudo apt -y autoremove $ sudo reboot
Dockerのインストール
公式によると、docker-ce docker-ce-cli containerd.ioをインストールすることになっている。
$ sudo apt install docker-ce docker-ce-cli containerd.io $ docker -v Docker version 19.03.12, build 48a66213fe
※元々インストールしてあったパッケージdockerも依然としてインストールは可能。だが、これらをインストールして使っていくのだろう。
念のため再起動して動作確認。
移行元の古いバージョンで動かしていたコンテナ達も無事に起動した。
追加ファイルのインストール(docker buildでエラーが出る場合のみ)
docker build したときに発生する free(): invalid pointer エラーを回避する。
まっさら環境にインストールした場合には発生しない問題、アンインストールの仕方が上手くないのかもしれないが、パッケージの関係上仕方がないと割り切り。
Github / docker / for-linux / Docker build says free(): invalid pointer with valid Dockerfile #563
$ wget https://github.com/docker/docker-credential-helpers/releases/download/v0.6.3/docker-credential-secretservice-v0.6.3-amd64.tar.gz $ tar -xf docker-credential-secretservice-v0.6.3-amd64.tar.gz $ sudo cp --preserve=mode,ownership --attributes-only /usr/bin/docker-credential-secretservice ./docker-credential-secretservice $ sudo mv /usr/bin/docker-credential-secretservice /usr/bin/docker-credential-secretservice.org $ sudo mv ./docker-credential-secretservice /usr/bin/
他にもあるかもしれないが、ひとまず気付いたところ。
移設に必要な用語の理解
色々と調べてみたけど言葉の意味が分からない、どうにも腑に落ちない…となって、片っ端から引っ張ってきてメモ。
docker docs / Glossary
docker-docs-ja / 用語集
イメージ
Docker images are the basis of containers. An Image is an ordered collection of root filesystem changes and the corresponding execution parameters for use within a container runtime. An image typically contains a union of layered filesystems stacked on top of each other. An image does not have state and it never changes.
Dockerイメージはコンテナーの基礎です。イメージは、ルートファイルシステムの変更と、コンテナランタイム内で使用するための対応する実行パラメーターの順序付けられたコレクションです。通常、イメージには、互いに積み重ねられた階層化ファイルシステムの結合が含まれています。イメージには状態がなく、変更されることはありません。
docker docs / Glossary
イメージは、コンテナイメージやDockerイメージと表現されることがあるみたい。
Container images become containers at runtime and in the case of Docker containers - images become containers when they run on Docker Engine.
コンテナイメージは実行時にコンテナになります。Dockerコンテナの場合は、Dockerエンジンで実行されたイメージがコンテナになります。
Docker - What is a Container?
Dockerイメージはファイルシステムと実行時設定の集合である。アプリケーションおよび実行に必要なソフトウェアさらに実行時設定が含まれているDockerイメージを基にコンテナを生成すれば、コンテナは起動と共にアプリケーションとして機能する。
ウィキペディア - Docker
おかげで、イメージとコンテナの区別がつかない。
ベースイメージ
A base image has no parent image specified in its Dockerfile. It is created using a Dockerfile with the FROM scratch directive.
ベースイメージには、Dockerfileで指定された親イメージがありません。 FROMスクラッチディレクティブを含むDockerfileを使用して作成されます。
docker docs / Glossary
Dockerfileで最初に FROM scratchを書く、ないしはFROMを省略すると、それはベースイメージになる模様。
ペアレントイメージ
An image’s parent image is the image designated in the FROM directive in the image’s Dockerfile. All subsequent commands are based on this parent image. A Dockerfile with the FROM scratch directive uses no parent image, and creates a base image.
イメージの親イメージは、イメージのDockerfileのFROMディレクティブで指定されたイメージです。後続のすべてのコマンドは、この親イメージに基づいています。 FROMスクラッチディレクティブを含むDockerfileは、親イメージを使用せず、ベースイメージを作成します。
docker docks / Glossary
使いたいアプリを親として、必要なところだけをカスタマイズするのが楽チンだろうと思った。
コンテナ
A container is a runtime instance of a docker image.
A Docker container consists of
A Docker image
An execution environment
A standard set of instructions
The concept is borrowed from Shipping Containers, which define a standard to ship goods globally. Docker defines a standard to ship software.コンテナはDockerイメージの実行時インスタンスです。
Dockerコンテナは、Dockerイメージ、実行環境、一般的な指示一式からなります。
このコンセプトは、運送用コンテナがグローバルに商品を発送する際の一般的な荷札※から取り入れました。Dockerのソフトウェア発送の一般的な荷札です。※define a standardってのが上手く訳せない。荷主、中身、宛先とかがびっしり書かれた紙を想像した。
docker docs / Glossary
イメージのところでもコンテナは実行されたものと書かれている。停止したコンテナはイメージなの?というと、イメージは状態を持たないようなので一致しない。
コンテナはLinuxの通常のプロセスとほぼ同じものだが、利用できる名前空間やリソースが他のプロセスやコンテナからは隔離され、それぞれ固有の設定を持てるようになっている。そのためコンテナ内のアプリケーションから見ると、独立したコンピュータ上で動作しているように振る舞う。コンテナを管理するコストはプロセスを管理するコストとほとんど変わらず、仮想マシンを管理するコストと比較すると非常に軽い。
@IT - 第1回 Dockerとは
結局、具体的な持ち物(データー)が分からないからなのか、イメージとコンテナの違いがどうにもすっきりとはまらない。
違いはこの後で調べている。
ボリューム
A volume is a specially-designated directory within one or more containers that bypasses the Union File System. Volumes are designed to persist data, independent of the container’s life cycle. Docker therefore never automatically deletes volumes when you remove a container, nor will it “garbage collect” volumes that are no longer referenced by a container. Also known as: data volume
There are three types of volumes: host, anonymous, and named:
A host volume lives on the Docker host’s filesystem and can be accessed from within the container.
A named volume is a volume which Docker manages where on disk the volume is created, but it is given a name.
An anonymous volume is similar to a named volume, however, it can be difficult, to refer to the same volume over time when it is an anonymous volumes. Docker handle where the files are stored.ボリュームは1つ以上のコンテナの範囲内で特別に指定されたディレクトリで、ユニオンファイルシステムを迂回します。ボリュームは、コンテナのライフサイクルとは関係なく、データを永続化するように設計されています。したがって、Dockerはコンテナーを削除するときにボリュームを自動的に削除することも、コンテナーによって参照されなくなったボリュームを「ガベージコレクション」することもありません。データーボリュームとしても知られています。
ボリュームには、ホスト、匿名、名前付きの3つのタイプがあります。
docker docs / Glossary
ホストボリュームはDockerホストのファイルシステム上に存在し、コンテナ内からアクセスできます。
名前付きボリュームは、Dockerが管理するボリュームであり、ディスク上でボリュームが作成されますが、名前が付けられています。
匿名ボリュームは名前付きボリュームに似ていますが、匿名ボリュームの場合、時間の経過とともに同じボリュームを参照することが難しくなることがあります。Dockerはファイルが保存される場所を制御します。
コンテナが削除されてもデーターが消えないようにしたいときには、ボリュームを作って、そこにデーターを入れておけば良いらしい。
ボリュームは自動的に作られるものだとばかり思っていたが、そうじゃなかった。
DiscourseやKeycloadを運用しているシステムでdocker volume ls しても何も表示されない。
Discourseは launcher rebuild してもデーターが消えないのだが、それは何故なのかと思ってlauncherを見てみた。
はっきりとは分からなかったが、必要なときにはvolumeを作ってそこにバックアップを持たせているのではないかと思われた。
ユニオンファイルシステム
ボリュームはデーターを永続化させるために使われる。となると、迂回されるユニオンファイルシステムがなんなのかを知らないと…これは永続化されないのだろうから。
Union file systems implement a union mount and operate by creating layers. Docker uses union file systems in conjunction with copy-on-write techniques to provide the building blocks for containers, making them very lightweight and fast.
For more on Docker and union file systems, see Docker and AUFS in practice, Docker and Btrfs in practice, and Docker and OverlayFS in practice.
Example implementations of union file systems are UnionFS, AUFS, and Btrfs.ユニオンファイルシステムは、ユニオンマウントを実装し、レイヤーを作成することによって動作します。 Dockerは、ユニオンファイルシステムをコピーオンライト技術と組み合わせて使用して、コンテナーにビルディングブロックを提供し、非常に軽量で高速にします。
docker docs / Glossary
Dockerとユニオンファイルシステムの詳細については、実際のDockerとAUFS、実際のDockerとBtrfs、実際のDockerとOverlayFSをご覧ください。
ユニオンファイルシステムの実装例は、UnionFS、AUFS、およびBtrfsです。
ユニオンマウント
複数のディレクトリを組み合わせて、組み合わせた全てのディレクトリの中身が1つのディレクトリの中で見えるような仕掛け。
例えば、CD-ROMに書き込みはできないが、書き込み可能なディレクトリと透過的に重ね合わせることでCD-ROMにデーターが書き込まれたように見える。
Wikipedia / Union mount
コピーオンライト
Docker uses a copy-on-write technique and a union file system for both images and containers to optimize resources and speed performance. Multiple copies of an entity share the same instance and each one makes only specific changes to its unique layer.
Multiple containers can share access to the same image, and make container-specific changes on a writable layer which is deleted when the container is removed. This speeds up container start times and performance.
Images are essentially layers of filesystems typically predicated on a base image under a writable layer, and built up with layers of differences from the base image. This minimizes the footprint of the image and enables shared development.
For more about copy-on-write in the context of Docker, see Understand images, containers, and storage drivers.Dockerは、イメージとコンテナの両方にコピーオンライト技術とユニオンファイルシステムを使用して、リソースを最適化し、パフォーマンスを高速化します。本体の複数のコピーが同じインスタンスを共有し、それぞれがその固有のレイヤーに特定の変更のみを加えます。
docker docs / Glossary
複数のコンテナが同じイメージへのアクセスを共有し、書き込み可能なレイヤーにコンテナ固有の変更を加えることができます。書き込み可能なレイヤーは、コンテナが削除されると削除されます。これにより、コンテナの起動時間とパフォーマンスが向上します。
イメージは、基本的には書き込み可能なレイヤーの下のベース画像に基づいたファイルシステムのレイヤーであり、基礎となるイメージとは異なるレイヤーで構築されています。これにより、イメージの足跡が最小化され、共有開発が可能になります。
Dockerのコンテキストでのコピーオンライトの詳細については、Understand images, containers, and storage driversを参照してください。
複数のコンテナが同じイメージへのアクセスを共有!?と思ったけれども…
Ubuntuのイメージがあったとして、これをベースにコンテナ1(Apache)とコンテナ2(DHCPサーバー)を作ったとして、
- ベースとなるUbuntuは1つですよ。
- コンテナ1はUbuntuのイメージを読み取り専用で取り込み、コンテナ・レイヤーにApacheをインストールできるよ。
コンテナの中から見ていると、読み取り専用であることは分からなくて、必要なら書き込みができるように見えるよ。 - コンテナ2はコンテナ1と同じUbuntuを読み取り専用で読み込み、コンテナ・レイヤーにDHCPサーバーをインストールできるよ。
ということを指しているものと思われた。
Dockerfile
A Dockerfile is a text document that contains all the commands you would normally execute manually in order to build a Docker image. Docker can build images automatically by reading the instructions from a Dockerfile.
Dockerfileは、Dockerイメージを構築するために通常手動で実行するすべてのコマンドを含むテキストドキュメントです。 Dockerは、Dockerfileから指示を読み取ることにより、イメージを自動的に構築できます。
docker docs / Glossary
docker build のmanpageを見ると、イメージを作る、とされている。でも、実際にはコンテナも作られるケースがあった。
やったこと
公式サイトやDockerについて取り扱うサイトを調べてみたが、読んでも理解ができなかった。
分かるまでやってみるしかなかった。
学習のための最低限のDockerfile
Dockerfileの作成
Ubuntuをベースとした起動しっぱなしになるイメージを作りたい。
とりあえず、Apacheを動かしておいてログインできるようなヤツ…と思って探してみたら、こちらに。
Qiita / Dockerでapache2起動
探していたのはこれです。ありがとうございます。学習のために転記させていただきます。
Dockerfile
FROM ubuntu:18.04 MAINTAINER rohhie ENV APACHE_RUN_USER www-data ENV APACHE_RUN_GROUP www-data ENV APACHE_PID_FILE /var/run/apache2.pid ENV APACHE_RUN_DIR /var/run/apache2 ENV APACHE_LOG_DIR /var/log/apache2 ENV APACHE_LOCK_DIR /var/lock/apache2 ENV DEBCONF_NOWARNINGS yes RUN apt-get update; apt-get install -y apache2 EXPOSE 80 CMD ["apachectl", "-D", "FOREGROUND"]
※Qiita / Dockerでapache2起動より抜粋+いくつかの変更
FROMでベースイメージを指定。恐らく、Docker公式のリポジトリから持ってくるんだろうと思われる。
MAINTAINERは生成する作者の名前とのことなので、rohhieとしてみた。
ENVは環境変数名と値のセットなんだけれど…きっとこれが自動でセットされず苦労して編みだしたものと思われる。※
RUNでapache2をインストール。
EXPOSEはコンテナ実行時にListenするポートを伝える。実際にアクセスするためには、コンテナ実行時にマップされるポートを-pパラメータで伝える必要がある。
CMDはapachectlで、これはapache2を起動する。-DでFOREGROUNDを指定することで、終わらないコンテナにしていると思われる。
※ENVの設定値について調べてみたら、apache2.confにその記載があると教えてくれた。
Otapps / Ubuntuでapt-getしたApacheの実行ユーザの変更方法。
なお、apt-getをaptにすると、以下の警告が出る。標準出力や戻り値に期待していないので無視しても良い。
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
また、apt-getコマンドを使うと以下の警告が出るが、apt-utilsをインストールする際にも出るみたいなので、警告を出ないように設定を加えた。
Qiita / "debconf: delaying package configuration, since apt-utils is not installed"を表示しないようにする
debconf: delaying package configuration, since apt-utils is not installed
イメージの構築
早速、イメージを構築してみる。
$ sudo docker build -t apache-test . … Successfully built e28abce97d93 Successfully tagged apache-test:latest
さっきまで docker build でコンテナまでできていたのだが、このDockerfileではコンテナができなかった。
どうしてなのか原因を探ろうと思ったけれども、さらっとは見つからない感じ。
イメージ構築の過程で、イメージがどんどん積み上げられていることが分かる。
$ sudo docker image history apache-test IMAGE CREATED CREATED BY SIZE COMMENT e28abce97d93 12 hours ago /bin/sh -c #(nop) CMD ["apachectl" "-D" "FO… 0B 39227f98777a 12 hours ago /bin/sh -c #(nop) EXPOSE 80 0B 28ff7663f8c4 12 hours ago /bin/sh -c apt-get update; apt-get install -… 126MB ← ここでレイヤーが5層になった 52ff4315d5fb 12 hours ago /bin/sh -c #(nop) ENV DEBCONF_NOWARNINGS=yes 0B ← ここまでレイヤーは4層 ffb952972ed3 12 hours ago /bin/sh -c #(nop) ENV APACHE_LOCK_DIR=/var/… 0B d00ee41cee9f 12 hours ago /bin/sh -c #(nop) ENV APACHE_LOG_DIR=/var/l… 0B 36e573a5786f 12 hours ago /bin/sh -c #(nop) ENV APACHE_RUN_DIR=/var/r… 0B bf506c9c26c8 12 hours ago /bin/sh -c #(nop) ENV APACHE_PID_FILE=/var/… 0B eb8ee33510c5 12 hours ago /bin/sh -c #(nop) ENV APACHE_RUN_GROUP=www-… 0B 4f7c1e3d97a2 12 hours ago /bin/sh -c #(nop) ENV APACHE_RUN_USER=www-d… 0B 3a481532297f 12 hours ago /bin/sh -c #(nop) MAINTAINER rohhie 0B 2eb2d388e1a2 2 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B <missing> 2 weeks ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B <missing> 2 weeks ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 745B <missing> 2 weeks ago /bin/sh -c [ -z "$(apt-get indextargets)" ] 987kB <missing> 2 weeks ago /bin/sh -c #(nop) ADD file:7d9bbf45a5b2510d4… 63.2MB
※<missing>と表示されている行は、他のシステムで構築されていることを表す。このサーバーでは扱えない。
普段古いイメージを見ることはないかもしれないが、ID指定でinspectできたりもする。
これらのイメージは、そのままレイヤーになるわけではないようだ。
なんらかのロジック(ディスクに何らかの変更が掛かったとき?)でレイヤーとして整理されてイメージの中でRootFSとして保持される模様。
コンテナの実行
利用中のポートを避けて、ポート80番で公開されているサービスにホストからアクセスできるようにする。
利用中のポートはこれで調べる。
$ sudo ss -pnA tcp,udp,raw state listening state unconnected
今回は、ポート9080番でアクセスすることにした。
$ sudo docker run -p 9080:80 -d -h apache-cont apache-test
※-hでコンテナの名前を指定したつもりだったけれど、実際にはホスト名が設定されただけで、コンテナはsad_dhawanになっていた。この場合は--nameがよかったのかも。
ブラウザで9080ポートにアクセスすると、Apacheのデフォルトページが表示された。
コンテナの中を少しいじってイメージ化してみる
都合でサーバーを再起動した関係で、コンテナが起動していなかった。
起動してログインし、中を少しいじってみる。目的は、移設先で「確かに変更が反映されている」を確認したいから。
$ sudo docker start sad_dhawan $ sudo docker exec -u 0 -it sad_dhawan /bin/bash --login root@apache-cont:/# apt install vim root@apache-cont:~# vi /var/www/html/index.html 編集後 root@apache-cont:~# logout
vimをインストールして、Apacheの「Ubuntu Default Page」のところを「Docker Custom Page」に書き換えただけ。
でも、違いははっきり出るかなと。
早速コミットしてみる。
$ sudo docker commit -a="Rohhie.Net" -m="install vim and change index.html" sad_dhawan sha256:6b7f288de233269287e39bf9eace2bd83f887440b58f9a70f795a58331b54492 $ sudo docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE <none> <none> 6b7f288de233 About a minute ago 239MB apache-test latest e28abce97d93 16 hours ago 190MB …
※リポジトリとタグを付けても良かったのかもしれない。公式にゴミを作ることを懸念したが、多分、そのためにはアカウントが必要なんだろう。
イメージとコンテナの比較
イメージとコンテナをinspectしたものを比較して、違いを認識してみる。
イメージにあるRootFSがコンテナにはない。しかし、コンテナにはRootFSで示されたレイヤーの上にレイヤーがあって、コンテナに加えた変更はこのレイヤーに書き込まれるとのこと。
RootFSで示されたデーターを見に行ってみたが、GraphDriverで示されているIDとつながっているようで、これらが一体となってユニオンファイルシステムを形成していると想定される。
項目 | Image(Dockerfileから構築) → | → Container → | → Image(ContainerをCommit) | 備考 |
---|---|---|---|---|
Id | sha256:e28abce97d93~※1 | 2b3cd8ab8d94~ | sha256:6b7f288de233~ | |
Created | 2020-08-12T10:06:38 | 2020-08-12T10:51:51 | UTCっぽい。 | |
Config | Hostname: 無指定 環境変数ENV 等々 Image: sha256:39227f98777a~ CMD指定がDockerfileそのまま | Hostname: apache-cont 環境変数ENV 等々 Image: apache-test CMD指定がDockerfileそのまま | Hostname: apache-cont 環境変数ENV 等々 Image: apache-test CMD指定がDockerfileそのまま | Imageが親を指している。 |
GraphDriver | LowerDir: /var/lib/docker/overlay2/8137c789cf7f~ MergedDir,UpperDir,WorkDir: /var/lib/docker/overlay2/9c4c7234df8b~ →LowerDirは複数指定されている。 Name: overlay2 | LowerDir: /var/lib/docker/overlay2/a2a2d041f354~ MergedDir,UpperDir,WorkDir: /var/lib/docker/overlay2/a2a2d041f354~等 →LowerDirはImageのLowerDirを含んでいた。 Name: overlay2 | LowerDir: /var/lib/docker/overlay2/9c4c7234df8b~ MergedDir,UpperDir,WorkDir: /var/lib/docker/overlay2/7e77931ed7b5~ →LowerDirはImageのLowerDirを含んでいた。 Name: overlay2 | GraphDriverはスナップショットの前身。 スナップショットをオーバーレイしている。 |
RepoTags | apache-test:latest | なし | [] | |
RepoDigests | [] | なし | [] | |
Parent | sha256:39227f98777a~ | なし | sha256:e28abce97d93~※2 | docker image history e28abce97d93 で見ると親が分かる。 |
Comment | 無指定 | なし | install vim and change index.html | コミットしたときに指定したメッセージ。 |
Container | 1e7f4cf112d5~ | なし | 2b3cd8ab8d94~ | 1e7f4cf112d5~が見つからない。 2b3cd8ab8d94~コミットしたコンテナ |
ContainerConfig | Hostname: 1e7f4cf112d5 環境変数ENV 等々 Image: sha256:39227f98777a~ CMD指定が変形している | なし | Hostname: apache-cont 環境変数ENV 等々 Image: apache-test CMD指定がDockerfileそのまま | |
DockerVersion | 19.03.12 | なし | 19.03.12 | |
Author | rohhie | なし | Rohhie.Net | コミットしたときに指定した作者。 |
Architecture | amd64 | なし | amd64 | |
OS | linux | なし | linux | |
Size | 189913406 | なし | 239015238 | |
VirtualSize | 189913406 | なし | 239015238 | |
RootFS | Type: layers + レイヤー5階層 | なし | Type: layers + レイヤー6階層 | 5階層までは一致。 |
Metadata | LastTagTime: 2020-08-12T19:09:37 | なし | LastTagTime: 0001-01-01T00:00:00Z | JSTっぽい。 |
Path | なし | apachectl | なし | |
Args | なし | -D FOREGROUND | なし | |
State | なし | Status: running 等々 | なし | |
Image | なし | sha256:e28abce97d93~※1※2 | なし | ※1 親イメージを指している。 |
ResolvConfPath | なし | /var/lib/docker/containers/2b3cd8ab8d94~/resolv.conf | なし | |
HostnamePath | なし | /var/lib/docker/containers/2b3cd8ab8d94~/hostname | なし | |
HostsPath | なし | /var/lib/docker/containers/2b3cd8ab8d94~/hosts | なし | |
LogPath | なし | /var/lib/docker/containers/2b3cd8ab8d94~/~.log | なし | |
Name | なし | /sad_dhawan | なし | |
RestartCount | なし | 0 | なし | |
Driver | なし | overlay2 | なし | |
Platform | なし | linux | なし | |
MountLabel | なし | 無指定 | なし | |
ProcessLabel | なし | 無指定 | なし | |
AppArmorProfile | なし | docker-default | なし | |
ExecIDs | なし | null | なし | |
HostConfig | なし | たくさんの設定 | なし | |
Mounts | なし | [] | なし | |
NetworkSettings | なし | ポートの割り当てやIPアドレスなど | なし |
Dockerfileを理解するために(失敗)
ある程度理解が進んだ今となっては突っ込みどころ満載。とはいえ、リンク集的な意味もあるので、セクションを残して青文字で突っ込む。
コピーオンライトを学習していたら、複数のコンテナが同じイメージへのアクセスを共有、といわれた。
イメージへのアクセス?となって理解ページに飛んだら、いきなりDockerfileを検討してください、ときた。
うちでも2つのコンテナを動かしているのだから、どっかにDockerfileってのがあるんじゃないかと思って探したけれど見つからない。
→docker hubでイメージを見ると、Dockerfileへのリンクがある。例えばここ。
分からないんだからしょうがない、学習。
docker docs / Dockerfile reference
$ docker build . unable to prepare context: unable to evaluate symlinks in Dockerfile path: lstat /home/rohhie/work/docker/Dockerfile: no such file or directory
Dockerfileがないよ、と。…最初に書かれたコマンドが動かない。もう、挫折しそう…
ここにある説明をコピってファイルを作ってみる。
/home/rohhie/work/docker/Dockerfile
FROM ubuntu:18.04 COPY . /app RUN make /app CMD python /app/app.py
やってみる。
$ docker build . … どば~っとなにかが表示される。 … Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.40/build?buildargs=%7B%7D&cachefrom=%5B%5D&cgroupparent=&cpuperiod=0&cpuquota=0&cpusetcpus=&cpusetmems=&cpushares=0&dockerfile=Dockerfile&labels=%7B%7D&memory=0&memswap=0&networkmode=default&rm=1&session=kininkoxgbsiv5b6vq56tlc5d&shmsize=0&target=&ulimits=null&version=1: dial unix /var/run/docker.sock: connect: permission denied
うちで作った環境では、Dockerはroot権限で実行しないと上手く動かない。
→色々と深く刺さっている仕組みなので当たり前か。安全設定はRootlessモードというみたい。
$ sudo docker build . free(): invalid pointer … ① SIGABRT: abort PC=0x7f3680fe4f47 m=0 sigcode=18446744073709551610 signal arrived during cgo execution goroutine 1 [syscall, locked to thread]: runtime.cgocall(0x4afd50, 0xc420047cc0, 0xc420047ce8) /usr/lib/go-1.8/src/runtime/cgocall.go:131 +0xe2 fp=0xc420047c90 sp=0xc420047c50 github.com/docker/docker-credential-helpers/secretservice._Cfunc_free(0x274cda0) github.com/docker/docker-credential-helpers/secretservice/_obj/_cgo_gotypes.go:111 +0x41 fp=0xc420047cc0 sp=0xc420047c90 github.com/docker/docker-credential-helpers/secretservice.Secretservice.List.func5(0x274cda0) /build/golang-github-docker-docker-credential-helpers-cMhSy1/golang-github-docker-docker-credential-helpers-0.5.0/obj-x86_64-linux-gnu/src/github.com/docker/docker-credential-helpers/secretservice/secretservice_linux.go:96 +0x60 fp=0xc420047cf8 sp=0xc420047cc0 github.com/docker/docker-credential-helpers/secretservice.Secretservice.List(0x0, 0x756060, 0xc4200642c0) /build/golang-github-docker-docker-credential-helpers-cMhSy1/golang-github-docker-docker-credential-helpers-0.5.0/obj-x86_64-linux-gnu/src/github.com/docker/docker-credential-helpers/secretservice/secretservice_linux.go:97 +0x217 fp=0xc420047da0 sp=0xc420047cf8 github.com/docker/docker-credential-helpers/secretservice.(*Secretservice).List(0x77e548, 0xc420047e88, 0x410022, 0xc420064220) <autogenerated>:4 +0x46 fp=0xc420047de0 sp=0xc420047da0 github.com/docker/docker-credential-helpers/credentials.List(0x756ba0, 0x77e548, 0x7560e0, 0xc420082008, 0x0, 0x10) /build/golang-github-docker-docker-credential-helpers-cMhSy1/golang-github-docker-docker-credential-helpers-0.5.0/obj-x86_64-linux-gnu/src/github.com/docker/docker-credential-helpers/credentials/credentials.go:145 +0x3e fp=0xc420047e68 sp=0xc420047de0 github.com/docker/docker-credential-helpers/credentials.HandleCommand(0x756ba0, 0x77e548, 0x7ffe4bdf18c9, 0x4, 0x7560a0, 0xc420082000, 0x7560e0, 0xc420082008, 0x40e398, 0x4d35c0) /build/golang-github-docker-docker-credential-helpers-cMhSy1/golang-github-docker-docker-credential-helpers-0.5.0/obj-x86_64-linux-gnu/src/github.com/docker/docker-credential-helpers/credentials/credentials.go:60 +0x16d fp=0xc420047ed8 sp=0xc420047e68 github.com/docker/docker-credential-helpers/credentials.Serve(0x756ba0, 0x77e548) /build/golang-github-docker-docker-credential-helpers-cMhSy1/golang-github-docker-docker-credential-helpers-0.5.0/obj-x86_64-linux-gnu/src/github.com/docker/docker-credential-helpers/credentials/credentials.go:41 +0x1cb fp=0xc420047f58 sp=0xc420047ed8 main.main() /build/golang-github-docker-docker-credential-helpers-cMhSy1/golang-github-docker-docker-credential-helpers-0.5.0/secretservice/cmd/main_linux.go:9 +0x4f fp=0xc420047f88 sp=0xc420047f58 runtime.main() /usr/lib/go-1.8/src/runtime/proc.go:185 +0x20a fp=0xc420047fe0 sp=0xc420047f88 runtime.goexit() /usr/lib/go-1.8/src/runtime/asm_amd64.s:2197 +0x1 fp=0xc420047fe8 sp=0xc420047fe0 goroutine 17 [syscall, locked to thread]: runtime.goexit() /usr/lib/go-1.8/src/runtime/asm_amd64.s:2197 +0x1 rax 0x0 rbx 0x7ffe4bdf0a90 rcx 0x7f3680fe4f47 rdx 0x0 rdi 0x2 rsi 0x7ffe4bdf0820 rbp 0x7ffe4bdf0b90 rsp 0x7ffe4bdf0820 r8 0x0 r9 0x7ffe4bdf0820 r10 0x8 r11 0x246 r12 0x7ffe4bdf0a90 r13 0x1000 r14 0x0 r15 0x30 rip 0x7f3680fe4f47 rflags 0x246 cs 0x33 fs 0x0 gs 0x0 … ①' 恐らくここまでエラー表示 Sending build context to Docker daemon 2.048kB … ② Step 1/4 : FROM ubuntu:18.04 18.04: Pulling from library/ubuntu 7595c8c21622: Pull complete d13af8ca898f: Pull complete 70799171ddba: Pull complete b6c12202c5ef: Pull complete Digest: sha256:a61728f6128fb4a7a20efaa7597607ed6e69973ee9b9123e3b4fd28b7bba100b Status: Downloaded newer image for ubuntu:18.04 … ③ ---> 2eb2d388e1a2 Step 2/4 : COPY . /app ---> 0f5e643d00de Step 3/4 : RUN make /app ---> Running in c7fec60d3045 /bin/sh: 1: make: not found The command '/bin/sh -c make /app' returned a non-zero code: 127
①どうやら、Ubuntuのサポートするパッケージが古いために発生している模様。
②ビルドはこのコマンドではなくDockerデーモンで実行されると書かれていた。確かにそうなった。
③Dockerfileの1行目でUbuntu:18.04と指定している。
まず、大量に出ているエラーをどうにかしたいと思い、dockerを最新版にした。
コンテナを削除して作り直してみる。
$ sudo docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c7fec60d3045 0f5e643d00de "/bin/sh -c 'make /a…" 3 hours ago Exited (127) 3 hours ago great_curran $ sudo docker rm great_curran great_curran $ sudo docker build . Sending build context to Docker daemon 1.275MB Step 1/4 : FROM ubuntu:18.04 ---> 2eb2d388e1a2 Step 2/4 : COPY . /app ---> 71b5cec9734f Step 3/4 : RUN make /app ---> Running in 4c9e44126995 /bin/sh: 1: make: not found The command '/bin/sh -c make /app' returned a non-zero code: 127
※とりあえずは大量に出ていたエラーが消えて、通常の動作になったようだ。
結果を見ると、COPY . /app は現在のディレクトリをそのまま/appにコピーすることを表しており、Dockerfileとかがコピーされたとみられる。
RUN make /app でmakeが見つからないか、makeはあるけれどもMakefileが見つからなくて異常終了している。
中に入ってみる。
$ sudo docker exec -u 0 -it pedantic_shtern /bin/bash --login Error response from daemon: Container 4c9e44126995d11dff8f4c04192f933e3d7c59b0f95068ea23cc430b415d7b88 is not running
怒られた。確かに、このコンテナは動作していない。
無駄なコンテナが作られるけど、runしてみるか…。
$ sudo docker container rm pedantic_shtern
pedantic_shtern
$ sudo docker run -it 71b5cec9734f bash ← --rm パラメーターを付けると終了後にコンテナは削除される
root@993c63ff9418:/# make
bash: make: command not found
※何度もdocker buildしていて、このタイミングではイメージが 71b5cec9734f だった。
せめてmakeコマンドくらいあるんだと思ったけど、ないのか…。
これを学習のネタにするのは無理だな…
→チュートリアルを探せオレ…
さいごに
正直なところ、ネットを探しても混乱するだけだった。
原因はイメージとコンテナの定義が分かりづらいこと。
ぱっと見では真逆の表現がなされていたり、考えているものと言葉が一致しなかったりして、何をすると安全にシステムを移設できるのか分からない。
恐らくはこの記事も似たようなものだろうが、自分用メモとして後でぼんやりしたときに見返して思い出せればいい、と割り切ってリリースする。
コメントはこちらから お気軽にどうぞ ~ 投稿に関するご意見・感想・他