以前にもDockerを触ったのだが、その時はさしたる使用目的もなく、Dockerって何かを知るために触っただけだったので、それ以来放置していた。
ここに来て、open-interpreter等導入にあたり、環境構築の効率化を考えDockerに再び取り組んでみようと思う。
作業環境:
Macbook Pro 15inch, 2019(intel)
macOS Ventura 13.5.2
Dockerのインストール
公式サイトからインストールする。
https://www.docker.com/
インストール後、起動されるGUIのソフトは閉じてしまって良いだろう。
Dockerは基本、CUIで利用すると思って良い。
Dockerをターミナルで使用する
Dockerは基本ターミナルで操作すると思ってもらって良い。
まずは通例、Dockerがちゃんとあるかどうかバージョンチェックを使って確認。
docker --version
# 結果
Docker version 24.0.6, build ed223bc
ちゃんとインストールされていることを確認できた。
Dockerで仮想環境を構築する際は、大きく2つの選択肢がある。
一つ目は、Docker Hubから自分の構築したい環境(もしくはそれに近い環境)を公式もしくは親切な誰かが作ってくれてアップロードしてくれているので、それをダウンロードして利用する。
二つ目は、自分で構築したい環境をDockerfileというファイルにコード化(書き込み)し、それを実行することで環境が構築される。
前者は、比較的クリエイター側がエンジニア側から与えられた環境を使ってクリエイティブを行う際にDockerを利用するケースに向いている。
後者は、エンジニア側がちゃんと設計してクリエイター側に渡すことで開発環境を統一するようなケースに向いている。
当然、初心者は前者、上級者は後者という考え方もできる。
さて、まずは前者の方法で行ってみよう。(後者についてはいずれ更新する予定)
Dockerの公式ページ行くと、Docker Hubへのリンクが見つかる。(ググっても良いけどね)
https://hub.docker.com/
例えばUbuntuの環境を仮想環境として構築したければ、以下のUbuntuの環境をpullしてくる。
気持ち的にはどこの誰だかわからない人が作ったイメージより、Docker Official Imageと書かれている方が安心できる。というのは小市民的な発想でお恥ずかしい。
pullの方法は、選択したイメージのページにあるpullコマンドをコピペしてターミナルに貼り付ければ良い。
これでひとまずベースとなるイメージをローカルに持ってきたと思ってもらって良い。
ちなみに、この操作は基本的にどのディレクトリで行っても良い。Gitのように、作業するディレクトリで行わなければならないということはない。
最新のイメージをpullしたい場合は、Tagsタブに進み、latestのバージョン指定をしてpullすると良いだろう。
pullを実行し、ローカルにイメージを持ってこられたかどうかは、docker imeagesコマンドを使って確認できる。
docker images
# 結果
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest c6b84b685f35 5 weeks ago 77.8MB
Ubuntuの最新版(latest)がDockerイメージとしてローカルにダウンロードされていることがわかる。
イメージがあることが確認できたら、早速イメージからコンテナを作り、起動させよう。
docker run -it -d --name my-ubuntu ubuntu:latest
Dockerイメージから実際に稼働させるコンテナを作るにはdocker runコマンドを使う。
上記のコマンドはdocker runコマンドとそのオプションだ。
続く-itというオプションは、起動したコンテナ内でシェルと対話したいことを示す。
その後の、-dはバックグラウンドで実行することを示している。
つまり、仮想環境が立ち上がると、一度ターミナルに戻れる。(その後の操作については後述するのでしばしお待ちください)
--nameは環境に名前をつけるオプションなので、任意の名前をつければ良い。ここではmy-ubuntuという名前をつけた。
そして、最後は仮想環境のベースイメージを指定している。当然、先程ダウンロードしたイメージを指定する。
実行すると以下のように、起動した仮想環境(コンテナ)のIDが表示される。これが表示されることでコンテナが起動したことがわかる。
docker run -it -d --name my-ubuntu ubuntu:latest
# 結果
35d4632f7ed674bd126a2fbaf508eae102fc22f6da360911d8363ac6fa25566e
仮想環境内のディレクトリとローカルのディレクトリを同期させる
コンテナを作成する際、仮想環境内のディレクトリとローカルのディレクトリを同期させておくと何かと便利だ。
例えば、プログラミングをする際、仮想環境内でコードを書くより、ローカルの環境でVSCodeを使った方が断然効率的だ。そんな時、この方法が役にたつ。
docker runコマンドに-vオプションをつける。
-vオプションをつけることで、現在のローカルディレクトリとコンテナ内の指定のディレクトリを同期させられる。
docker run -it -d --name my-ubuntu -v $(pwd):/home ubuntu:latest
-vオプションに続けて、現在のローカルディレクトリ(カレントディレクトリ)を表す$(pwd)、コロンを挟んでコンテナ内の指定のディレクトリ(ここではLinuxの標準的なユーザーディレクトリであるhomeを選択している)を書き込むことで、ローカルのカレントディレクトリと仮想環境内のhomeディレクトリが同期される。
ローカルのファイルを更新したら、仮想環境内のファイルも更新される。逆も然り。こちらの方が便利だと思う。
では、話を戻して、ちゃんと本当にDockerの仮想環境としてコンテナが起動したかどうかを以下のコマンドで確認してみよう。
docker ps
# 結果
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
35d4632f7ed6 ubuntu:latest "/bin/bash" 2 minutes ago Up 2 minutes my-ubuntu
ちゃんと仮想環境が起動していることがわかる。
それでは続けて、この起動している仮想環境へ入ってみよう。
docker execコマンドに、仮想環境へ入ることができる。ここでは-itオプションをつけることでシェルとの対話が可能になり、且つシェルにbashを指定している。
docker exec -it my-ubuntu /bin/bash
# 結果
root@35d4632f7ed6:/#
こんな感じで、仮想環境のUbuntuのbashと対話できるようになる。
あとはお好きなように料理してくれたまえ。
おっと、仮想環境から抜ける場合のことを書き忘れていた。
仮想環境から抜ける場合は、exitコマンドを実行するとターミナルに戻ることができる。
exit
仮想環境から抜けたら、実行中のコンテナを停止する必要がある。
一応、現在実行中のコンテナがあるかどうか念のためdocker psコマンドで確認できたら
docker stop my-ubuntu
docker stopコマンドを実行することで実行中のコンテナが停止する。
この際、docker psコマンドを実行すると、実行中のコンテナがなくなっていることを確認できる。
そして、docker psコマンドに-aオプションをつけると、停止中のコンテナを確認することができる。
この停止中のコンテナは、docker startコマンドで再度実行することができる。
docker runコマンドはイメージからコンテナを構築するコマンドで、docker startは停止中のコンテナを再起動するコマンドであることを覚えておこう。
それでは要らなくなったコンテナの削除についても触れておこう。
コンテナが停止中であることを確認し、docker rmコマンドで停止中のコンテナを削除できる。
docker rm my-ubuntu
このコマンド実行後に、改めてdocker ps -aコマンドを実行してもコンテナは見つからないはずだ。
更に、イメージも削除したい場合はdocker rmiコマンドを使う。
docker rmi ubuntu
ubuntuのところはリポジトリ名を入れれば良い。稀に同じ名前のリポジトリ名のイメージがあり、rmiコマンドでエラーが出ることがあるが、その場合はIMAGE IDを指定すれば削除できる。
されてこれで、一連の仮想環境のライフサイクルは終了だ。
あとはお好きなだけ仮想環境を構築し、削除してくれたまえ。(仮想環境上のことなので、あなたのローカル環境が汚されることはないのでご安心を)
参考
https://youtu.be/B5tSZr_QqXw?si=2gjC4DxyFLovjGK3
https://youtu.be/lZD1MIHwMBY?si=qgFvpkatfsmSe5K3
上記の動画に感謝申し上げます。
例えばPythonの仮想環境をサクッと構築したい時
Docker HubにあるPythonのイメージを見つける。
インストールしたいバージョンのものを探すのも良いし、サクッと試したいなら、とりあえずlatestを選択すれば良いと思う。tagsタブからlatestのpullコマンドをコピペしてターミナルに貼り付けよう。
docker pull python:latest
これまで同様に、Pythonの入ったイメージがダウンロードされる。
念のため確認したければ、docker imagesコマンドを試すと良い。
docker images
# 結果
REPOSITORY TAG IMAGE ID CREATED SIZE
python 3 b94d01b49295 3 weeks ago 1.01GB
python latest b94d01b49295 3 weeks ago 1.01GB
(タグの違いがよくわからないので後で調べます)
とりあえず、イメージがちゃんとダウンロードされていることは確認できるはず。
イメージからコンテナを作るためdocker runコマンドを実行する。
Docker Hubにとりあえず実行するためのdocker runコマンドが書かれているが、これだとコンテナを実行したらいきなりPythonのインタプリタが実行された状態で起動するため、以下のように少しだけ手を加えた。
docker run -it --name python-dev -v $(pwd):/home -w /home python:latest /bin/bash
何度もくどいようだが、一応解説しておく。
-itオプションでシェルと対話できるようにする。
--nameオプションで仮想環境に名前をつける(ここではpython-dev)。
-vオプションでローカルのカレントディレクトリとコンテナ内のユーザーディレクトリとしてhomeディレクトリを同期させる。
-wオプションでワークディレクトリを指定すれば、起動後そのディレクトリから操作を開始できる。
あとはタグ名を付けて、シェルにbashを指定。
コンテナが実行状態であればdocker execコマンドを使ってコンテナの中へいつでも入れるよ。
docker exec -it python-dev /bin/bash
これだけで即座にPythonのプログラムを動かせる環境が手に入るんだから、便利やねー。
いよいよDockerfileで環境構築するよ
Docker Hubから使いたい環境をダウンロードしてきたり、開発チームで環境構築のエンジニアさんからイメージをもらったりして仮想環境を構築することは容易にできるようになったとして、次はDockerfileを使って環境を構築することもできるようになっておこう。
Dockerfileってのは、これから構築したい仮想環境の仕様が書いてあるファイルで、docker image buildコマンドを使うことで、Dockerfileに記述されている内容に従ってイメージが生成されるという仕組み。
一足先にDockerfileのイメージを掴んでもらうために、サンプルをお見せします。(詳しい解説は後ほどするので、まずはDockerfileに何がどのように書かれているのかをサクッと見てもらうことが目的です。
FROM ubuntu:latest
RUN apt-get update && apt-get install -y sudo wget vim curl gawk make gcc
RUN wget https://repo.anaconda.com/archive/Anaconda3-2023.07-2-Linux-x86_64.sh && \
sh Anaconda3-2023.07-2-Linux-x86_64.sh -b && \
rm -f Anaconda3-2023.07-2-Linux-x86_64.sh
ENV PATH $PATH:/root/anaconda3/bin
WORKDIR /home
CMD ["jupyter-notebook", "--ip=0.0.0.0","--port=8888" ,"--no-browser", "--allow-root", "--LabApp.token=''"]
中身はこんな感じです。
もう少し解説すると、Dockerfileには構築したい環境のベースとなるイメージを指定するところがあり(上記ではFROMって書いてある先頭の行のことね)、そのイメージがローカルにない場合、レジストリ(要はDocker Hubのこと)からそのベースとなるイメージをダウンロードしてきて、イメージをローカルに生成するのだ。
その他、同時に実行するコマンドを書いておくことで、一緒に用意する(絶対ではなく、必要な場合という意味)アプリコードと呼ばれるファイルに書かれた指示を実行したり、追加したいソフトやライブラリを自動で取得したり、インストールしたり、そのバージョンを指定したりと細かい指示を自動化することができちゃう優れものなのだ。
Dcokerfileは奥が深すぎて、専門の職人さんがいるくらいマニアックなものなのだが、自分が普段使いやすい環境を構築できるくらいの、最低限の知識があると、環境構築でものすごく楽ができる。
プログラミングをさあやるぞ!とやる気を出した直後に環境構築で躓いて、結局本来の目的に到達する前に挫折することを防止する意味でも、価値ある知識・技術だと俺は思う。
はい、能書はこれくらいにして実際にDockerfileを作ってみましょう。
コツは、最初は最低限の仕様にし、実際その最低限の環境から仮想環境内で必要なソフトやライブラリをインストールしつつ、それをDockerfileに追記していくことで、やがて自分専用の環境を構築するためのDockerfileが出来上がるって寸法さ。
まずは最小限の仕様でDockerfileを作成してみようと思うのだが、せっかくなので、Pythonの開発環境で必須とも言われているAnacondaというディストリビューションの環境をDockerfileを使って構築することにチャレンジしてみよう。その上で、Anacondaの目玉機能の一つ、Jupyter Notebook(後述)を利用できるようにする。
# ベースのイメージとなるUbuntuの最新版を指定
FROM ubuntu:latest
これでさっきも書いた通り、レジストリからUbuntuの最新版がダウンロードされる。
UbuntuはオープンソースのLinuxディストリビューションで、GUIも充実しており、人気が高い。今回イメージとしてダウンロードされるイメージはCUIで使われる軽いものとなる。
次にaptと呼ばれるLinuxでは定番のパッケージ管理システムを入れる必要がある(ベースのイメージにはaptが入っていないため)ので、RUNコマンドを使ってaptをインストールするコマンドを実行させる。
FROM ubuntu:latest
RUN apt-get update && apt-get install -y sudo wget vim curl gawk make gcc
aptのインストールからアップデートも行いつつ、他に必要なツールもaptを使ってインストールしてしまう。
wgetはインターネット上のファイルを取得するためのコマンドラインツール。ソフトのインストールファイルをダウンロードしたりするときに必要なので入れておく。
vimはエディタ。LinuxのCUI環境ではvimが無いとファイルの内容を書き換えたりできないので、これも必須。
curlは指定されたURLへさまざまなプロトコルを用いてアクセルすることができるコマンドラインツール。全体的に考えれば、こいつがあることでインターネット上の様々なリソースにアクセスできるので、処理の自動化に貢献してくれる。
gawkはテキストファイルの処理に便利な処理系のプログラム。csv形式のデータなどを扱うときに必要になる。
makeはソースコードから実行ファイルを作成するビルドツール。
gccはコンパイラのパッケージ。
とにかくこの辺りは最低限入れておいた方が良い基本セットだと思って、インストールしておくと良い。
次に本題のAnacondaをインストールしていく。
FROM ubuntu:latest
RUN apt-get update && apt-get install -y sudo wget vim curl gawk make gcc
RUN wget https://repo.anaconda.com/archive/Anaconda3-2023.07-2-Linux-x86_64.sh && \
sh Anaconda3-2023.07-2-Linux-x86_64.sh -b && \
rm -f Anaconda3-2023.07-2-Linux-x86_64.sh && \
早速wgetを使ってanacondaのインストールファイルをインターネットからダウンロードしています。Dockerfileでは命令を連続して実行することで処理を軽くするため、&& \をサフィックスにすることで関連する処理を1行として扱える。
続けてshコマンドでダウンロードしたインストールファイルを実行。(-bオプションはバイナリ形式であることを指定しています)
そして、rmコマンドでインストール後に不要になったインストールファイルを削除している。
因みに、インストールファイルとなるAnaconda3-2023.07-2-Linux-x86_64.shの部分は
https://repo.anaconda.com/archive/
にアクセスしてもらって、ベースのイメージの環境に合った、自分のインストールしたいAnacondaのバージョンを探し、置き換えてもらって大丈夫です。ここでは、記事執筆時の最新のAnacondaを選択しています。
ENVコマンドでPATH 環境変数を設定します。
Anacondaのパスを通しておくってやつ。
ENV PATH $PATH:/root/anaconda3/bin
ワークディレクトリも設定できます。
WORKDIR /home
最後にCMDコマンドを書きます。
これは書いても書かなくても良いのですが、書くと、docker runコマンドを実行しコンテナが生成された時、ここに書かれたコマンドが実行され、プロセスがあるのでコンテナが実行状態のまま維持されます。
CMD ["jupyter-notebook", "--ip=0.0.0.0","--port=8888" , "--allow-root"]
内容としてはコンテナが生成された時、jupyter notebookが続くオプションの設定で起動されます。
--ip=0.0.0.0オプションは、Jupyter Notebook を外部からアクセスできるようにします。
--port=8888オプションは、Jupyter Notebook のポート番号を 8888 に設定します。
--allow-rootオプションは、 root ユーザーとして Jupyter Notebook を起動します。
さあ、これで準備は完了です。
Dockerfileを使って環境を構築する場合、Dockerfileを置くディレクトリを新たに作ると良いです。
理由は、Dockerfileから環境構築をする時、docker buildコマンドを使用しますが、そのコマンドの実行時にDockerfileのあるディレクトリに存在する全てのファイルを一度読み込んで解析するからです。読み込むといっても、コンテナの生成に関係するかどうかをチェックするだけで、コンテナ内には指定のない限り、コピーや参照はされません。
そして、そのファイルを読み込むのに、多少の時間がかかります。だからDockerfileは空のディレクトリにポツンと人つただけあることが望ましいというわけです。
# Dockerfileを置く空のディレクトリを作成
mkdir MyDockerFile
# 作成したディレクトリにDockerfileをコピー
cp ./Dockerfile ./MyDockerFile
# Dockerfileを設置したディレクトリへ移動
cd MyDockerFile
Dockerfileを実行し、イメージファイルを作成します。
docker build -t jupyter:anaconda3 .
docker buildコマンドを使ってイメージファイルを作成します。
オプションの-tは、作成されるイメージファイルに設定するタグを指定します。ここでは、jupyter:
anaconda3というタグを設定しました。
行末にある「.」はカレントディレクトリを意味し、Dockerfileのあるディレクトリを指します。この場合、カレントディレクトリにDockerfileがあるので、カレントディレクトリを表すドットを指定しています。
少し時間がかかりますが、問題なければイメージファイルが作成されるはずです。
docker images
# 結果
REPOSITORY TAG IMAGE ID CREATED SIZE
jupyter anaconda3 9fbfd7465195 13 hours ago 6.81GB
docker imagesコマンドで確認してみましょう。
Dockerfileに設定した内容のイメージファイルが追加されているはず。
続いて、docker runコマンドを使ってコンテナを生成してみよう。
docker run --name my-jupyter -it -p 8888:8888 -w /home jupyter:anaconda3
--nameオプションは、コンテナ(仮想環境)につける名前。
-itオプションでコンテナ内のシェルと対話できるようになる。
-pオプションでローカルのポートとコンテナ内のポートを接続しておく。こうするとローカルのブラウザからコンテナ内のローカルホストの8888ポートにアクセスできるようになる。
-wオプションでワークディレクトリ(作業ディレクトリ)を設定。
最後にリポジトリ:タグで、どのイメージからコンテナを生成するか指定する。
んで、実行。
# docker runコマンドを実行した結果(例)
[I 04:47:48.015 NotebookApp] Writing notebook server cookie secret to /root/.local/share/jupyter/runtime/notebook_cookie_secret
_ _ _ _
| | | |_ __ __| |__ _| |_ ___
| |_| | '_ \/ _` / _` | _/ -_)
\___/| .__/\__,_\__,_|\__\___|
|_|
Read the migration plan to Notebook 7 to learn about the new features and the actions to take if you are using extensions.
https://jupyter-notebook.readthedocs.io/en/latest/migrate_to_notebook7.html
Please note that updating to Notebook 7 might break some of your extensions.
[W 04:47:48.767 NotebookApp] Loading JupyterLab as a classic notebook (v6) extension.
[W 2023-09-25 04:47:48.769 LabApp] 'ip' has moved from NotebookApp to ServerApp. This config will be passed to ServerApp. Be sure to update your config before our next release.
[W 2023-09-25 04:47:48.769 LabApp] 'port' has moved from NotebookApp to ServerApp. This config will be passed to ServerApp. Be sure to update your config before our next release.
[W 2023-09-25 04:47:48.769 LabApp] 'allow_root' has moved from NotebookApp to ServerApp. This config will be passed to ServerApp. Be sure to update your config before our next release.
[W 2023-09-25 04:47:48.769 LabApp] 'allow_root' has moved from NotebookApp to ServerApp. This config will be passed to ServerApp. Be sure to update your config before our next release.
[I 2023-09-25 04:47:48.772 LabApp] JupyterLab extension loaded from /root/anaconda3/lib/python3.11/site-packages/jupyterlab
[I 2023-09-25 04:47:48.772 LabApp] JupyterLab application directory is /root/anaconda3/share/jupyter/lab
[I 04:47:50.161 NotebookApp] Serving notebooks from local directory: /home
[I 04:47:50.161 NotebookApp] Jupyter Notebook 6.5.4 is running at:
[I 04:47:50.161 NotebookApp] http://3c6c32ca908f:8888/?token=dcf0a1a0230c10c5484ac35c689e4715dacb2c7a8ed391cb
[I 04:47:50.161 NotebookApp] or http://127.0.0.1:8888/?token=dcf0a1a0230c10c5484ac35c689e4715dacb2c7a8ed391cb
[I 04:47:50.161 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[W 04:47:50.164 NotebookApp] No web browser found: could not locate runnable browser.
[C 04:47:50.164 NotebookApp]
To access the notebook, open this file in a browser:
file:///root/.local/share/jupyter/runtime/nbserver-1-open.html
Or copy and paste one of these URLs:
http://3c6c32ca908f:8888/?token=dcf0a1a0230c10c5484ac35c689e4715dacb2c7a8ed391cb
or http://127.0.0.1:8888/?token=dcf0a1a0230c10c5484ac35c689e4715dacb2c7a8ed391cb
Dockerfileの最後に書いたCMDコマンドに、docker run実行時にJupyter Notebookを起動するコマンドを書いておいたので、コンテナ生成と同時にJupyter Notebookも起動。上記のような表示があり、最後の方に「copy and paste one of these URLs」と書かれている欄のURLをブラウザにコピペすると、Jupyter Notebookへアクセスできるはず。
Dockerfileを使った環境構築はこれでおしまい。
後は、使いながら追加で必要になったソフトやライブラリをDockerfileへ追記していくことで、自分好みの環境イメージをdocker buildコマンドで、一発作成できるようになるぜ。
そうそう、終了の仕方。
ブラウザ側のタブをバシバシ閉じていって、閉じ終わったらターミナルに戻り、CTRL+cを押すと
Shutdown this notebook server (y/[n])?
と表示されるのでyを選択すればJupyte Notebookを起動していたサーバがシャットダウンしてターミナルに戻る。
注意して欲しいのは、この時Jupyter Notebookを終了すると同時に、コンテナで稼働するプロセスが全て終了するので、コンテナも一緒に終了します。Docker psコマンドを打つと、稼働中のコンテナがないことがわかります。
Docker ps -aコマンドでコンテナがあることを確認し、再びコンテナを起動したい場合はdocker startコマンドを使ってコンテナを再起動してください。
tokenが分からなくった時の対処
2回目以降のアクセスで、Jupyter Notebookからtokenを求められ、且つそのtokenがわからないときは一度起動中のコンテナにdocker execコマンドで入り、以下のコマンドを試してみてください。
jupyter notebook list
# 結果
Currently running servers:
http://0.0.0.0:8888/?token=29c9c36d99a53db700a5f71461f8a7a5556d302dd747eeaf :: /home
とこんな感じになれば、token=から後ろ::の前のスペースよりも前がtokenです。そちらをコピペして使ってみてください。