Dockerの勉強する

Docker絵とき入門を買ったのでそれ読みながらDockerの勉強する
目安は章ごとに一つずつ追記していく

仮想化について
実際のCPUやメモリといったハードウェアを伴うマシンを物理マシンというのに対してそれらのハードウェアをソフトウェアによって実現したものを仮想マシンという
仮想化のメリット
サーバー等の物理マシンを複数台用意せずに済むのでコストカットできる
また仮想化によって複数の環境を一つのマシンで実現できるので開発用のノートPCでも有用
コンテナ型仮想化について
仮想マシンを実現するソフトウェアはいくつかあるがDockerのようなソフトウェアはコンテナ型仮想化を行う
コンテナ型仮想化ではコンテナという単位で仮想化を行うが一つ一つのコンテナにはOSがない
DockerがLinuxOS部分を仮想化の中で実現する
一つ一つのコンテナは一つのアプリケーションを扱うので一つのコンテナで複数のアプリケーションが動くことはない
複数のアプリケーションによってサービスを実現する場合はそのアプリケーション分コンテナを用意する
コンテナ型仮想化のメリット
コンテナを小さくわけていくことで使う要素のアップデートが容易になる
また各コンテナは各コンテナ内部で動いている要素に関して干渉しあわないので一つのマシン上で異なるバージョンの技術を動かすことができる
(コンテナ1ではGo 1.21.2,コンテナ2ではGo 1.22.3を動かすとか)
またコンテナの場合実行環境の設定等をプログラムと一緒に移動させることができるのでデプロイ時やチーム開発での環境構築時等に便利
デメリット
前提としてLinux環境が必要(物理マシンからLinux環境を借りることでコンテナでもOSがあるように動かしているので)
なのでWindowsの場合はWSL等を使うことになる.またコンテナのCPUは物理マシンのCPUと同じアーキテクチャになるのでMac等の場合は注意が必要
普段は問題ないがデプロイ時に影響がでることがある

コンテナについて
コンテナはあるコマンド1つを実行するためのホストマシンに作られる領域
他コンテナのプロセスやファイルと影響しあわない用に1コマンド1コンテナの形でそれぞれ独立して用意され,またOSを含まないため(Linuxカーネルをホストマシンから借りているため)軽量である
またコンテナ起動時につかうコマンドはかならずプロセスIDが1になるという特徴がある
またコンテナには以下の特徴がある
- 必ずイメージからコンテナは作られる
- 個々のコンテナは独立していて中身に影響しあうことはない
- コンテナランタイムさえあればコンテナはどこでも動く
3つ目についてより詳しく説明すると,ここではコンテナランタイムとしてDockerを利用しているがその他にもコンテナランタイムとしてPodmanやクラウド上のサービス(AWSのECS等)があるがそれらのどのコンテナランタイムを使っても同じ用に動くということである.ただしCPUアーキテクチャには注意が必要
イメージについて
イメージはコンテナを起動するためのものであり,複数のレイヤと呼ばれるtarアーカイブファイルで出来上がってる
ここでいうレイヤとはUbuntuのインストール情報だったりGoやPHP, Pythonといったソフトウェアのインストール情報だったりする
Dockerhubというレジストリサービスがあり,そこからベースイメージを選択することが多い
Ubuntuイメージだけでなく,既にGo等の言語がインストールされたイメージもあり,便利なイメージが複数ある.用意されたイメージでは用途に一致していない場合Dockerfileを使って自分の用途に沿ったイメージを用意する必要がある.
イメージの共有には実際にはDockerfileというファイルを記述し,Dockerfile上にイメージの中身を定義することで運用するコンテナのイメージを作成する
Dockerfile内でベースとなるイメージを指定するFROM,ホストマシンのファイルをコンテナに動かすCOPY,aptを使ったパッケージのインストール等コマンドを実行するRUN,環境変数を設定するENV,コンテナの起動時のコマンドを指定するENTRYPOINTやCMD等の命令を組合せてイメージを作る
この時に命令の数分だけレイヤは増えていく,少ないほうが望ましいとされているのでapt等でパッケージをインストールしたりする際は&&を使ってレイヤを減らす等の技術がある
同じDockerfileから作られたイメージならば同じコンテナが起動される保証がある

Dockerfileについて
Dockerfileはdocker image buildコマンドを使って新たに自分の用途用のイメージを作成するためのファイル,このファイルがあることでイメージの共有をどのようなイメージなのかまで含めて容易にする
例えばDockerhubで公式が用意しているUbuntuイメージではまっさらな状態だがDockerfileによってaptを使いvimを入れさせることでvimの使えるUbuntuイメージを共有できるなどである
これらの行動はコンテナを起動する毎にコンテナ内でvimをインストールすることで実現することもできるが,複数人での開発時等ではこれらの時間は無駄なのでイメージ段階で終わらせておくのがよい
イメージを共有するための方法としてtarアーカイブファイルもあるが,こちらでは共有した際にどのようなイメージなのかわからなくなるため,Dockerfileは重要である
Dockerfileの命令
Dockerfileでは上から一つ一つ命令を重ねていくことであらたなイメージを作っていく
主に使うのはベースとなるイメージを選択するFROM,apt等のコマンドを実行するRUN,ホストマシンのファイルをコンテナ内にコピーするCOPY,コンテナ起動時のコマンドを指定するENTRYPOINTやCMDがある
基本1命令毎にレイヤが増える.特にRUNでaptを使う場合等は1命令毎にレイヤが増えていくのでできるだけ&&でつなぎレイヤを減らせると良い
結果的に同じDockerコンテナになるイメージでもレイヤのより少ないイメージのほうがイメージとして軽量になりコンテナの起動がはやくなるメリットがある
FROMを2回使うことで1回目のFROMで実行ファイルを作り2回目のFROMで1回目のFROMで作成した実行ファイルをコピーし,コンテナを作成するマルチステージングビルドと呼ばれるものも存在する

ボリュームについて
Dockerではボリュームと呼ばれるものがあり,主にデータベース等で用いる.本来コンテナを一度削除するとコンテナの中のデータは消えてしまう(ソフトのインストール情報等)が,Docker Engin上のストレージをコンテナ内の特定のディレクトリにマウントすることでコンテナ削除後もデータの永久化ができる
docker container runでコンテナを起動するとき,ボリュームを使うためにはまず
docker volume create --name [volume名]
でボリュームを作成し,docker container run時に
docker container run \
--mount type=volume,source=[ボリューム名],destination=[永久化するディレクトリのパス]
で指定を行う
実際にはdocker compose コマンドを使うことが多いのであまり使うことはないがcompose.yamlを書く上で重要である.

バインドマウント
Dockerではバインドマウントを利用することでホストマシンの指定したファイルやディレクトリをコンテナにマウントできる
マウントしたディレクトリやファイルはホストマシンからだけでなくコンテナ側からでも参照できる
docker run --mount type=bind,source="($pwd)",target=[マウント先ディレクトリ]
でマウントできる."($pwd)"
は自分のいるディレクトリを表すコマンドであり,Linuxからは自分のいるディレクトリと同様に認識される
DockerfileのCOPYと異なる点として,COPYはその時のファイルの複製をコンテナ上に配置するのに対してマウントではホストマシンのファイルをリアルタイムに参照するのでコンテナの起動しながらソースコードの変更を行える等がある
ソースコードがホストマシン上にある開発時はマウントが使いやすいがデプロイ時にはCOPYを使った方が良い
筆者はGoでAPI開発をしていた際にDockerのマウントとAirを使って開発していました

ボリュームとバインドマウントの区別
バインドマウントはホストマシンのファイルをマウントしているのに対し,ボリュームはDocker Engineが管理しているストレージをマウントする
そのためマウントしたファイルが破損した時等の影響はバインドマウントのが大きい
しかし上記のようにソースコードの変更をリアルタイムに影響できるバインドマウントは重要であるので自分が実際に自分が触るファイルはバインドで,自分が触らないがデータとして永続化したいものはボリュームを使うとよい
前者はソースコード,後者にはデータベースのデータが挙げられる

ネットワーク
コンテナ同士が通信する上で使う
コンテナは指定されたポートを開放できるがコンテナ間通信はそのポート情報のみでは行えず,Dockerのネットワークも指定する必要がある
docker network create [ネットワーク名]
でネットワークを作成,
docker network ls
でネットワーク一覧の確認
docker run --network [ネットワーク名]
で作成したネットワークを利用することができる.ポート情報とネットワークの情報2つがそろってはじめてコンテナ間通信ができる

イメージのビルド
Dockerfileを元にイメージを作る場合
docker image build -f [Dockerfile名] -t [タグ名] [今いる場所からDockerfileがある場所へのパス]
でイメージを作成する
-fオプションや-tオプションに伴う値は指定しなくてもよいが,-t
オプションとタグ名は自分で指定しておいたほうがその後イメージを使う上で便利

コンテナの起動
Dockerでは用意されたイメージを使ってコンテナを起動する
コンテナ起動のコマンドは以下の通り
docker container run [オプション] [Image名] [コマンド] [引数]
オプション
オプションでよく使うものとして-it
や--name [コンテナ名]
がある.前者はコンテナ内部での対話操作(シェル操作等)に便利で後者はコンテナに名前を付けておくと後で管理が用意になる特徴がある
またよく使うオプションに-p
や-d
がある
-p
オプションを使うことでホストマシンのポートとコンテナのポートを接続することができる.ホストマシンのポート開放制限を貫通してポート開放するので注意
-d
オプションではコンテナをバックグラウンドで起動することが出きる.コンテナを起動している間もターミナルが使えて便利
他にも環境変数を設定する--env
やコンテナ停止時にコンテナを自動削除する--rm
オプションがある
イメージ
Image名の部分ではコンテナのベースとなるイメージを選択している.自分でビルドしたイメージ以外にもDockerhubに用意されたイメージを直接選択することもできる.
コマンド
Dockerのイメージはコンテナとして作成された時デフォルトでどのコマンドが実行されるかは決まっているが,コマンドの部分で指定してやれば上書きすることもできる
引数
コマンドの引数を指定できる.あまり使わない
使用例
docker container run -it ubuntu bash
以上のコマンドはUbuntuイメージを対話形式で使えるようにし,bashでコンテナ内を操作できるようにするコマンドである.
docker container run -p 8080:80 nginx
以上のコマンドはnginxイメージを起動し,nginxコンテナの80番ポートをホストマシンの8080番ポートとマッピングするコマンドである

コンテナ関係のコマンド
コンテナのログを見たい時は
docker container logs [コンテナ名]
でコンテナを指定してログを見ることができる
実行中のコンテナに追加で命令を実行したい時は
docker container exec [オプション] [コンテナ名] [コマンド] [引数]
でコンテナに命令を出すことができる.よく使うのは
docker container exec -it [コンテナ名] sh
でコンテナに入りコンテナ内部のファイルを確認したりコマンドを実行するなど
他にもデータベースを起動しているコンテナで各データベースのクライアントを起動しデータベースの中身を確認する時等に使う

docker compose
今までのコマンドはどれも一つのコンテナ,一つのイメージに関するものだった
複数コンテナを起動するような場面ではとても長いdocker container run ~~
のようなコマンドをコンテナの数分実行することになるがめんどくさいし,ミスの原因になる
docker compose
はこれらを解決するためdockerの複数コンテナの定義と実行を行うためのツールである
コンテナの定義はyamlファイルで行う.この時ファイルはcompose.yaml
がよい.しかし少し前のプロジェクトではdocker-compose.yaml
なこともある.また拡張子が.yaml
ではなく.yml
なこともあるがあまり気にしなくて良い
compose.yaml
の中ではコンテナ一つ一つをサービスとして定義していく.サービス名がそのままコンテナ名になる.またファイルの中では一つ一つのサービスに関してコマンドでdocker run ~~
とする際のオプション部分に対応するものを書き込んでいく形になる
ポートの公開やボリューム,利用するイメージファイルのように自分たちで定義しないといけないものとネットワークやコンテナ名などのようにdocker run
コマンドの時は指定していたがdocker compose
コマンドでは勝手にやってくれる部分も存在する
またdepend_on
やhealthcheck
のようにコンテナの起動順を指定するための便利なものもある
Dockerfile
とcompose.yaml
が揃ったら
docker compose up [オプション]
でコンテナを一気に起動できる.よく使うオプションとしてバックグラウンドで実行するための-d
や,イメージやコンテナの定義自体に関するソースコードの変更があった場合に反映させるために使う--build
がある.基本的に-d
さえあればどうにかなる
起動したコンテナたちを終了させるには
docker compose down [オプション]
で終了する.docker compose downで終了したコンテナは残らず消去される.またオプションで-v
をつけることでボリュームの中身も同時に消去できる.たまに使う
他にもdocker container ls
等のようにコンテナの情報を得るコマンドとしてdocker compose logs
やdocker compose ps
などのコマンドが存在する
docker
コマンド類ではdockerによって用意されたイメージやコンテナ全てがコマンドの命令対象だったがdocker compose
コマンド類ではcompose.yaml
に定義されたサービスのみが対象となるのが主な違いとなる