🚀

ようやくDockerに入門した件(半分読み物)

2024/09/06に公開

エンジニアの皆様お疲れ様です。たぬきの教祖でございます。
 今回は、これまでかたくなに避けてきたDockerを使用する機会があり、使ってみると割と当初のイメージと違っていた部分もあって、備忘録も兼ねてそのあたり書いていこうと思います。
私のような頑固なエンジニアの方にはDocker入門、というか心の準備?的な感じで読んでいただければと思います。
なお、初学者ゆえ間違い等あると思いますが何卒何卒

経緯

さて、私は結局博士号まで取得しましたが、私が大学の情報学部に所属して、つまりこのIT界隈にアクセスしてから、9年が経過しました。学部4年次に起業して、仕事として携わるのもそろそろ7年になろうかというところです。

そんな私は、WEB開発はjQueryから入って、Vueに到達するまでもかなりの時間を要しましたし、PHPからNodeに移したのはここ2年、JSを捨ててTSに来たのはここ1年くらいという、違和感を持った仕組みは渋る体質でございまして、Dockerにもついぞしっかり触れることはなくここまでやってまいりました。

しっかり触れることはなかった、というのは、それこそ起業した7年前くらい、日本ではまだDocker黎明期であったころ、Dockerというのが良いらしい、という話だけは聞いてインストールし、PCの中にはずっと眠っていたのです。ただ、当時のDockerはWindowsとの相性があまりよくなく、軽く試すも動作が重く、またPHPを利用していて古典的なWEBアプリを作成していた私の環境は、特にDockerに頼る必要もなく、私はDockerを封印したのです。

そんな流れが変わったのは、私の所属していた研究室が肥大化し、先生は一人なのに学生が40人近くという中々類を見ないマンモス研究室になったころ、学生が学生に指導する勉強会の必須項目にDockerが上がりました。後輩たちが皆Dockerを習得し私が焦り始めていて幾ばくか、映像配信系のアプリケーションの構築を依頼されたことに始まります。
 映像配信系のアプリケーションの構築はそれは大変で、何が大変かっていうと費用で、まあそのあたりは私の初記事「カメラ動画をWEBに表示しようとしたら地獄だった話」とその続編でもお読みいただければと思うのですが、兎に角AWSやGCP、或いは配信用のプラットフォームを使用すると高く、定額のVPSをレンタルして構築する他ありませんでした。
VPSを利用する中で、当然サーバ環境の構築作業が必要になり、いったん契約を解除するとまた1から構築しなおしになったり、サーバを移すと、増やすと、それぞれ再び構築しないといけない状態が堪えがたく、ここぞDockerの出番だろう、と、ようやく本腰を入れて取り組むことになったわけでございます。

Dockerのイメージ(印象)

さてここで、私がDockerを使うまでに抱いていた、想像していたものと、実際に使用してみた感覚では、役割・機能を含めて随分と異なるものであることがわかりました。
その辺をまとめます。

想像していたDocker

  • 動作が重い
  • 学習コストが高い
  • サーバの状態を保存・コピーできる(あるゆるデータを保存しておくような仕組み、バックアップ等)
  • 開発環境と全く同じようにデプロイできる
  • 様々な機能を一つのパッケージにできる
  • 使いこなせるとめちゃ便利、デプロイ作業がほぼなくなる(Vercelくらいのイメージ)
  • 動作(挙動)を統一できる

実際に感じたDocker

  • 動作は重くない
  • 学習コストはさして高くない
  • 基本的にサーバの状態をコピーしたりはできない(パッケージ管理ツール(npm)のような仕組み)
  • 開発環境と同じようには基本的にはデプロイしない
  • 基本的に様々な機能を一つのパッケージにしない
  • デプロイ作業は別にある
  • 動作(挙動)を統一できる

説明・理由

というわけで、はい、私のDockerに対するイメージであっていたのは「動作(挙動)を統一できる」という部分だけだったようです。いや、もちろんDockerが遅い時代はありました、わずかとはいえ経験しました。が、時代は変わったようです。

動作は重くない

使ってみた感じ、ビルド、起動、稼働にも重いと思わせる部分はありませんでした。「Alpine」のような軽量イメージが活用されてる故の部分もあると思いますが、普通にPCを扱う感覚と何ら変わりません。Dockerを避けていた理由に、重たいソフトを安いサーバでどうやって動かすのか、と考えていた部分もあったので、まず重くないというのは非常に良いと思います。

学習コストは高くない

全く何も見えずに操作できるというほどではないし、私の中では新しい概念だったので少し飲み込むのに時間がかかりましたが、独学でも2日ほどあれば最低限動作させられる程度です。正しく理解している人から教わるなら、1日かからずに最低限使えるレベルになると思います。

状態を保存するというより設計図を作る

さて、ここからが本題で、私が一番伝えたい部分でもあるのですが、Dockerは状態を保存して使いまわすものではなく、設計図を作るようなものだと思いました。私が想像するDockerとは、ある時のサーバの状態、ファイルを徹底的にコピーして、それを複製することで状態を再現するものでした。WEB開発でいうなら、「node_modules」を含めてソースコード全部をまとめて保持するイメージです。ただ実際は、パッケージ管理ツール「npm」の「package.json」に記述して、「npm install」で構築するようなイメージの方が近いと思います。
 元々私が持っていたようなイメージの使い方もできるっちゃできるようですが、安定しないのと、そもそも如何なる用途にしても推奨されていないようです。このイメージ(印象)が今後もかかわってくる重要な部分になると思います。そりゃあ全体を含めれば最低でも何GBとなるし、編集が大変になったり、可視化できなかったりという部分があって実用的ではありませんが、だからこそ、使いにくいと感じる部分もそれなりにあります。

開発環境そのまんまにデプロイ...はできない

私が抱いていたDockerの使いどころのイメージの一つに、開発環境そのままでデプロイできる、というものがありましたが、どうも違うようです。私の抱いていたイメージとしては、アプリを構築して、イメージを取り出して、それをデプロイ先のサーバに投げつけてやればデプロイが完了するような、WEBアプリで例えればVercelのようなイメージを持っていました。Vercelは、GitHubと連携し、GitHubにプッシュすれば自動的にデプロイを行ってくれるという優れものです。
当然Dockerにおいても、GitHub Actionsを活用し、AWSのECSまわりに自動でデプロイするような仕組みを構築すれば同様のことが可能ではありますが、それはあくまでもそうなるような環境を自前で構築したうえでの話で、Dockerがそれを実現してくれるわけではありません。また、そこまでの手順が、まだまだ随分大変な作業であるというのもポイントです。
 加えて、そもそも開発環境とデプロイ環境は同じにするべきではない、というDockerの思想や都合もあります。例えば、Dockerを使用して、DBコンテナ、WEBアプリコンテナ、APIコンテナをそれぞれ立ち上げて開発するとして、これは機能的にはそのままサーバ上のDockerにデプロイすることが可能です。ではそうすべきか、というとそうはいかない思想や都合があります。例えばDB・WEB・APIを含むサービスをAWSにデプロイすることを考えたとき、WEBアプリはECSにデプロイしたとして、APIはAPI GatewayとLambda、DBはRDSにデプロイしたりするのではないでしょうか(筆者はAWSについてさして詳しくない、RDSとEC2辺りしか経験ないので変なこと言ってたら申し訳ない)。AWSにしろGCPにしろ、少なくともDBはDB専用のサービスにデプロイするのが便利であるし、例えばWEBサーバが増えてもDBは一つにしておくのが通常であって、基本的にそのままデプロイできるものでありません。ならデプロイの際に参照先なんかは環境変数にするにしてもやはり書き換えてあげないといけないわけです。またDockerが定義するのは基本的にOSやソフトウェア群、設定くらいのものであって、Dockerを共有したとて周辺のファイルが共有されるものでもありません。これもGitHubを活用するのあれば、周辺のファイルごとプッシュして取り込めばよいのですが、Docker自体はファイルを勝手に云々はしてくれないため、手動でやるかボリュームを駆使して頑張ることになります。ここも作業であって、ま、長々と書きはしましたが、Dockerは開発したものをそのままデプロイできる仕組みではないということです。

そもDockerの強みとは

つまるところそもそもとして、私が想像していた「開発物がそのままデプロイできる」というDockerの強みはただの妄想でありました。
ではDockerの本当の強みは何か、というと、「異なる環境でも同じ開発環境が即座に用意できる」ことと、「スクラップ&ビルドが容易」「大量に自動的に同じ環境を用意できる」この辺りであったのでした。そして、弱小企業でほぼ一人で開発している私にとっては「大量に自動的に同じ環境を用意できる」ことと「異なる環境でも同じ開発環境が即座に用意できる」ことのメリットはほぼなく、「スクラップ&ビルドが容易」この一点だけがメリットでありました。まあ、開発段階に限って言うなら、「開発環境を切り離せる」「Windowsでは動かないソフトを動かせる」も微かにメリットではあるのですが。
というところで、ではDockerは使う価値がないのか、といえば、「スクラップ&ビルドが容易」、ただこの一点でも十分に使う価値はあると思います。が、じゃあ常に使うべきか、というと、正直小さなプロジェクトや、Vercelにデプロイすれば済むような開発では使用しなくてよく、状況に合わせた取捨選択が必要、馬鹿の一つ覚えのごとく常に常にDockerを使う必要はない、と感じたのでありました。

感じた違和感

そんな私の想像妄想は別として、多少なりDockerを知ったうえでDockerに感じる違和感、残念感。詳しい人にはそうではない、と指摘を受けるかもしれませんが、指摘を受けるためにもそれをいくつか書いてみようと思います。

結局Dockerfileを見ても中身がわからない

Dockerfile
FROM ubuntu:22.04

Dockerの各コンテナを示すDockerfileの一文目はだいたいこんな感じで始まります。これは全然問題ありません。OSをUbuntuの22.04に設定する、という書き方です。
違います。違いますけど続けます。では

Dockerfile
FROM node

これは何でしょうか。OSをNodeに、、、するわけではありませんね、勿論。
 まあみなさんご存じとは思いますが、この一行目はDockerイメージを指定するものです。で、これはつまりパッケージ管理みたいなもので、nodeというイメージをインストールしますよ、と。そしてパッケージ管理ツールと同様、この「node」の中にはそれはそれで別の依存関係があったり、実際はコマンドの実行があったりするわけです。
 が、私はこの構成には違和感があります。npmなどのパッケージ管理においては、重要度に差はあれど、それぞれのパッケージは並列に存在しています。しかし、DockerコンテナにおいてOSは明らかにその根底にあるもので、並列の存在ではないし、その影響は他より大きいものです。私はこのイメージ部分はパッケージではなく土台であるべきと思います。ですから、ここにはOSの名前とバージョンが入るべきであって、それ以外のものが入るべきではない、究極、Dockerfileにイメージを記述させるべきではないと思います。
 その上で、私がDockerで理想とする記述・動作は、例えば以下のような形のものです。

MyDockerfile
os:ubunts:22.04
apps:
- node:20
- mysql:8.0
- ...
commands:
- npm install
- ...

説明はしませんが、説明しなくても何がしたいかわかるのではないでしょうか。
 まあ技術的に難しいことは承知しているのですが、元々Dockerに求められている要件を満たす機能としての正道はこういった形ではないでしょうか。イメージが無数に存在しており、それをブラックボックスとして呼び出す、これがライブラリであれば抽象化されているほうが良いのですが、自身が欲する機能、アプリケーション、またOSまでもがファイル中に現れない構成と、そこに存在するアプリケーションが、列挙ではなく個別のインストールコマンドでもって実際にインストールを行う、というのは、あまりエレガントな設計ではない気がしてしまいます。
 また、イメージを複数選択して組み合わせて使用しているようなケースも見られますが、プログラマ的観点からしたらちょっと危険極まりない。仕組みとしてそういったことが安全にできるようになっているのかもしれませんが、せっかく環境を整理して文書化するDockerに対して、その安定しない使い方はなんだかなあ、、、と思うところです。

実際にサーバを構築してみないと正解がわからない

さて、compose.yamlやDockerfileを記述する際、どのように記述するでしょうか。私は先日、もう随分とDockerを使用しているDocker上級で同じく起業家の後輩に尋ねましたが、その方法は、まず実際にサーバを構築しながらメモを取る。そのメモに従ってDockerfile等を記述する、というものでした。
 、、、うーん。他にも方法があるにはありますが、安定したDockerfileを作成するにはやはりそうするしかないと思います。Docker立ち上げてOS動かしてその上で実際にインストールしてみてそれをメモしてDockerfileにおこして、そして動作するか確認する。二度手間か三度手間か。Docker上でコマンド入力しているのだから、究極はそのまま正しいDockerfileが出力できるはずですが、安定してそれが可能な方法はいまだないように思います。
 基盤技術の時代や操作方法が異なりますし、多数のOSを相手にするので仕方ないですが、例えばスマホなら、機種変更する際に以前のスマホに入っていたアプリを自動的にインストールし、データの移行もしてくれます。Dockerでもそれができれば、と思うのですが、それは叶わないのが実態です。

入門

さて、ここまでは雑談です。ここから一応技術記事っぽいことを多少書こうと思います。

用語とか

イメージ

Dockerコンテナを作成するための設計図。或いはテンプレート。まあpackage.jsonみたいなものに近い。近いけど、パッケージそのものととらえたほうが近いかも。例えばReactとかVueという存在に近い。あれらも実態は内部にpackage.jsonがあって入れ子になっているが、入れ子の内側。

コンテナ

個別の基本的に隔離されたプロセスのこと。Dockerイメージを使用して構築される。概念的にはコンテナ一つが一つのPCだと思ってよいと思う。ただし基本的には1つのアプリだけを動かすのが良いとされている。
技術的にはゲストOSを持たないとか色々言われるが、概念的な理解には不要な知識だと思う。取り合えずOSが丸っと存在するわけではないので、個別のOSに見えてかなり軽い。

ボリューム

コンテナが作成したデータの保管場所。まあハードディスクみたいなもん。

compose.yaml

複数のコンテナ、イメージを統括して管理する為の方法が書かれたファイル。configファイルに近い。 docker-composeコマンドの定義ファイル。例えばDBサーバ、WEBアプリケーションサーバ、APIサーバのそれぞれのコンテナを一括で管理する。configファイル的な感じ。
なお、「version:」を書く方法は古くて、今は別にいらないらしい。
あと、ファイル名はdocker-compose.yamlでもいいけどこれも古くてcompose.yamlにするらしい。

Dockerfile

個別のイメージ・コンテナの設計書。実装するコマンド等の手順が記されている。所謂package.json、特にscriptsって感じ。

操作コマンド

正直DockerデスクトップとWebStormを使用しているのでほとんど知らないし必要ない。

docker image build [Dockerfileのパス]

Dockerfileからイメージを生成

docker run

Dockerコンテナの生成・起動

docker run -d

Dockerコンテナのバックグラウンド起動

docker ps

起動中のコンテナ一覧表示

docker stop

コンテナ停止

docker attach [コンテナ識別子]

コンテナにアクセス
「exit」でコンテナ終了

docker exec -it [コンテナ識別子] /bin/bash

コンテナにアクセスしてシェルを起動
attachとまあ近い、普通こっち使う
「exit」で接続終了

Discussion