最近、サーバ開発となると当たり前のようにコンテナ技術(Docker)を使うようになってきたため、Dockerでサーバ開発する方法を書いておこうと思います。
まず、Dockerとは何かというと、仮想環境ではあるのですが、かつてのVMWareのようなOSの上に仮想OSが動くようなもととは違い、 カーネルをnamespacesで分離して、ユーザ、ファイルシステム、プロセス管理等を別にして新たな環境を構築するようなイメージです。 OSが二重に動くわけではないので、無駄なオーバーヘッドが無く仮想環境が実現できるということです。
これによって、ソースコードを実行環境とともにコンテナでお渡しできるのが一番のメリットだと思います。
Dockerインストール方法 (MacOS)
まず、MacにDockerをインストールするには、下記URLからダウンロードしてDockerアプリケーションをインストールします。
https://hub.docker.com/editions/community/docker-ce-desktop-mac
インストールができたら、MacのアプリケーションフォルダにDockerというアプリケーションがあるはずなので、これを起動します。上のメニューバーに くじらのようなDockerアイコンがでてれば起動中です。
次に、dockerhubからイメージを持ってくるのですが、イメージのpullだけならアカウントが無くてもできます。例えば、下記のコマンドで、docerhubからubuntu18.04のイメージを持って来れます。
# docker pull ubuntu:20.04
これでイメージがローカルのDockerにダウンロードされます。docker imagesコマンドで確認できます。そしてこのイメージからdocker runコマンドで コンテナを立ち上げます。
# docker run -it -d --name container_name ubuntu:20.04
container_nameのところは、コンテナの名前なので何でも好きなもので大丈夫です。docker psコマンドで見れると思います。 コンテナを作成したら、そのコンテナに入ります。
# docker exec -it container_name /bin/bash
これでコンテナの中に入れるので、あとはubuntu環境で遊べます。ただ、本当にOS以外何も入ってない状態から始まります。
Dockerコンテナ内の環境設定
コンテナ内がubuntuの例で書きます。
$ apt-get update
まず、これする必要あります。これでいろいろ必要なものをインストールできるようになります。
あと、gccとかmakeとかも入ってないのでこのあたりの開発コマンドを入れます。
$ apt update $ apt install build-essential
pythonも使うなら、
$ apt-get install python3-distutils $ apt-get install python3-dev
pipのインストールは
$ apt-get install wget $ wget https://bootstrap.pypa.io/get-pip.py $ python3 get-pip.py
ここまでやれば、AWSでubuntuのEC2インスタンスを立ち上げたぐらいの状態になってると思います。
ただひとつ困ったことがありました。dockerのシェル上で、Ctrl-pが使えないのです。(dockerのdetachのコマンドのため) Ctrl-pは、ひとつ前のコマンドを実行したいときに多用するので、これが使えないと困ります。一応2回押せばできるんですが、そんなのは無理です。 上矢印キーでもできるんですが、手をホームポジションから動かすのは嫌です。
ですので、Ctrl-pを使えるようにします。
~/.docker/config.json の設定ファイルの中に
"detachKeys": "ctrl-\\"
と書いて、detach key をCtrl-pとは違うキーに割り当てます。これで、Ctrl-pが使えるようになります。
これでubuntuで遊べるようになりました。せっかくなので、この状態をイメージに保存しておきたいと思います。イメージとして保存しておけば、この状態からコンテナを作成できるようになります。
# docker commit [container_name] [image_name]:[tag_name]
これで、このコンテナが、[image_name]で保存されて、[tag_name]というタグが付いています。
docker-compose
では、本題に入りますが、このDockerを使ってサーバ開発する場合に便利なdocker-composeについて書こうと思います。
まず、サーバ開発といえば、外部のネットワークから通信できる必要があるのですが、それには、ホスト側のポートと、コンテナ内のポートを 結びつけるポートフォワーディングの設定が必要になります。
これは、ホスト端末の〇〇番のポートにきた通信は、全てコンテナ内の〇〇番ポートに転送するという設定です。これをやらないと Dockerの中でサーバを立てても外からは使えないです。
あと、ファイルシステムもホスト側と共有できた方が良いです。ソースコードとかコンテナ内で開発するのでも良いのですが、 ホスト側でも編集した場合もありますし、機械学習のモデルファイルとか大きいので、こういうのはコンテナの外で共有した方が コンテナのイメージサイズも大きくならなくて済みます。
Dockerではホスト側のファイルシステムをコンテナ内にマウントすることができるので、これが便利です。
そしてこれらの設定を簡単にできるのが、docker-composeというツールです。dockerコマンドでひとつずつ設定していくのでも良いのですが、 毎回、設定するのは面倒ですので、設定ファイルに書いてdocker-compose一発でOKという状態にしたいと思います。
まず、docker-composeをインストールします。
# sudo curl -L https://github.com/docker/compose/releases/download/1.28.5/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
実行権限をつけます。
# sudo chmod +x /usr/local/bin/docker-compose
どこかのフォルダにdocker-compose.ymlというファイルを作り、以下の設定を書きます。
version: '2.3' services: [service_name]: image: [image_name]:[tag_name] container_name: [container_name] tty: true ports: - 80:80 - 443:443 volumes: - /var/host:/var/docker
[service_name]は好きな名前で。[image_name]と[tag_name]は起動させるコンテナのイメージとタグ名です。 [container_name]は起動後のコンテナの名前です。tty trueは入れないとすぐ終了してしまうので入れましょう。
ports: のところに、ポートフォワーディングの設定を書きます。1行にひとつのポートずつ書きます。 コロンで区切ってますが、前方がホスト側のポート番号、後方がコンテナ側のポート番号になります。
volumes: のところに、ファイルシステムのマウントの設定を書きます。こちらのコロンの前方がホスト側のファイルパス、 後方がコンテナ側のファイルパスになります。上の例ですと、/var/host がコンテナ内の /var/docker にマウントされます。 (MacOSだと設定しないと/Usersか/tmpの下しかマウントできないかもしれません。)
設定ファイルができたら、その設定ファイルがある場所で、以下のコマンドを実行していきます。
# docker compose up -d
これで、先ほどの設定ファイル通りにコンテナが起動されます。-dをつけないとフォアグラウンドで動いてしまいます。
# docker compose ps
これで、docker composeで動いてるコンテナが確認できます。
# docker compose down
これで、docker composeで起動したコンテナを終了できます。
# docker exec [container_name] [command]
これで、docker内のコマンドを実行できます。
ホスト起動時にコンテナを立ち上げたい場合は、docker-compose.ymlの [service_name]の中に restart: always と書けば起動してくれます。コンテナの中のサーバは、上記の docker execでホスト側の起動スクリプトか、cronで 起動すればコンテナ内のサーバまで自動起動できます。
このようにやると、プログラムのソースコードもモデルファイルもホスト側で編集できるので、Dockerコンテナはプログラムの実行環境としてしか使っていないことになります。
自分のローカルPCでサーバの実行環境を再現できるのは本当に便利です。
コンテナ内をGPU対応する
dockerコンテナ内のubuntuでGPUを使うための設定を紹介します。ホスト側はGPUの設定ができてる前提で書きます。
まず、NVIDIA Container Toolkitをインストールします。
これはLinuxのDistributionごとにURLが変わるので、まずコンテナ内のLinuxのDistributionを見なければいけないです。
# cat /etc/os-release
とすれば、OS情報が見れます。この中のIDとVERSION_IDをくっつけたものがDistribution名になります。
bashであれば、下記のコマンドで distributionという変数にDistribution名を格納できます。
# distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
そしてこの distribution変数を使って、curlでapt用のパッケージリストをとってきます。
# curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - # curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
root権限があれば、下記のコマンドでapt installできます。(なければsudoで)
# apt update && sudo apt install -y nvidia-container-toolkit # systemctl restart docker
これで、dockerコンテナ起動時に、–gpusオプションをつければ、コンテナ内でGPUが利用できるようになっています。コンテナ内で nvidia-smiコマンドを実行して、GPU情報ができくれば大丈夫です。
docker-composeでGPUコンテナを起動
docker-composeでコンテナを作成する場合は、–gpusオプションと同等のオプションが無いため、もう少しやることがあります。
まず、nvidia-container-runtimeをインストールします。
# apt install nvidia-container-runtime
設定ファイルを /etc/docker/daemon.jsonに作成します。
{ "runtimes": { "nvidia": { "path": "nvidia-container-runtime", "runtimeArgs": [] } } }
これで、docker-composeの設定ファイルで runtime: nvidia ができるようになります。
docker-compose.ymlの例を記載します。
version: '2.3' services: [service_name]: image: [image_name]:[tag_name] container_name: [container_name] runtime: nvidia environment: - NVIDIA_VISIBLE_DEVICES=all tty: true ports: - 80:80 - 443:443 volumes: - /var/host:/var/docker
これで、docker-composeでコンテナを立ち上げて、コンテナ内で nvidia-smiを実行し、GPU情報が見れればコンテナ内でもGPUが使えます。
M1のMac対応
MacのCPUが進化して、InterのCPUからApple SiliconのCPUに変わりました。これに伴い、docker環境の構築も少し変わります。
まず、Apple SiliconのCPUになったことにより、これまで Intel CPU用のみに開発されたアプリやライブラリを利用するためには、Rosetta 2というソフトウェアをインストールする必要があります。このRosetta 2はバックグランドで動作し、Intel CPU用のバイナリをApple Silicon用に変換してくれます。
これがあれば、Intel CPUでしか動かないアプリも、M1のMacで利用できるようになります。インストールは最初に必要になった時に、ダイアログがでてきてインストールできます。ですが、Rosetta 2を入れていても動かないアプリもあります。
前置きは長くなりましたが、docker環境の構築もM1を利用する場合は、今までと少し変わる部分があります。docker自体はM1対応のものがありますので、それをインストールすれば良いのですが、コンテナやその中でビルドするときに少し気を使います。
まず、コンテナですが、Intel CPU用に作った既存のコンテナイメージですが、一応動きます。ですが、高度な演算などをするととても遅いです。Apple Siliconでかなり速くなるはずですが、旧Intel PCよりも遅くなります。変換のオーバーヘッドですかね。
ですので、高度な演算処理をする場合は、イメージを Apple Silion用のイメージから作り直した方が良いと思います。ubuntu等は、Intel用のイメージだけでなく、M1用(ARM用)の”aarch64″のイメージもあります。ここからイメージを作り直せば、M1の性能を引き出せる素晴らしいコンテナが出来上がります。
また、コンテナ内でビルドなどをする場合に、デフォルトだとIntel CPU用にコンパイルしてしまうものもあります。そんな場合は、rustcというソフトウェアをインストールし、configure時に –build=armを指定すれば大丈夫です。
# apt-get install rustc # ./configure --build=arm
これで、機械学習のような行列演算がたくさんのソフトもM1のdockerコンテナで快適に動作できます。