この章ではコンテナ技術である Docker をデプロイで利用する為に基礎の学習を行っていきます。
Docker とは
Docker は簡単にいうとプログラムを「コンテナ」と呼ばれるものにまとめて、Docker がインストールされている環境であればどこでも実行できるようにする技術です。
例えば、Python のプログラムをコンテナ化したら、Python 自体が入っていない、または対応していない別バージョンの Python がインストールされている OS でも Docker があるだけでコンテナ化したバージョンの Python でプログラムを実行することが可能です。
また Docker はコンテナの実行管理をしてくれる機能もあります。
プログラムが落ちてしまった時にコンテナを自動で再起動したり、実行中のコンテナを停止する機能、コンテナをバックグラウンドで実行する機能があります。 バックグラウンドで起動できるということは、nohup
や tmux
などを利用する必要がありません。
botter 観点からすると Docker を利用すると特に以下の点で嬉しいです。
-
コンテナ化
- 様々なリージョンのインスタンスに素早く bot を展開できる。
- TA-Lib などインストールが複雑なライブラリでも環境構築が容易になる。
-
コンテナの実行管理
- bot の実行停止や一覧表示が容易になる。
- コンテナにログが紐付けされているので、ログ監視が容易になる。
また Docker は API を提供しているので外部からコンテナをデプロイすることができます。 これにより後の章で説明するデプロイの自動化も容易になります。
はじめての Docker
インストール
まずはローカル PC に Docker Desktop をインストールして、Docker を利用できるようにしましょう! インストールするのが面倒で、一時的に試したい場合は GitHub Codespaces を利用しましょう (GitHub アカウントが必要です)。 ブラウザ上の VS Code 上で Docker がインストールされた環境を試すことができます。
Docker Desktop
GitHub Codespaces
Blank のテンプレートを利用することで Codespaces の環境を試すことができます。
Docker のインストールができたら、はじめてのコンテナ化を行って bot コンテナを実行してみましょう!
サンプルコード
任意のディレクトリ (例: try-deploy-docker
) を作成して、そこに以下の main.py
と requirements.txt
のサンプルを張り付けてください。
import time
import requests
def main():
while True:
r = requests.get("https://api.bitflyer.com/v1/getticker?product_code=BTC_JPY")
data = r.json()
print(data)
time.sleep(1.0)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass
requests
このサンプルは requests
ライブラリをインストールして bitFlyer から BTC_JPY
のティッカー情報を毎秒取得して表示するだけの bot です。 注文等は行いませんが、ここでは Python プログラムを実行したいだけなのでこのようなサンプルにしています。
Build & Run!
この bot をコンテナ化するには Dockerfile
を作成します。 拡張子も何もなしで Dockerfile
です。
FROM python:3.12
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY main.py requirements.txt ./
RUN pip install -r requirements.txt
CMD [ "python" , "main.py" ]
これで必要なファイルは揃いました!
docker build
コマンドで bot のコンテナイメージを作成します。
docker build -t bot-demo .
ターミナルにはこのように FINISHED といったログが表示されていれば成功です。
[+] Building 7.9s (9/9) FINISHED
それでは docker run
コマンドでコンテナ化された bot を実行してみましょう!
docker run --rm bot-demo
このコマンドを実行すると毎秒以下のようにティッカー情報が表示されます。
{'product_code': 'BTC_JPY', 'state': 'RUNNING', 'timestamp': '2023-12-15T05:44:18', 'tick_id': 93039265, 'best_bid': 6061996.0, 'best_ask': 6062184.0, 'best_bid_size': 0.96996, 'best_ask_size': 0.02992205, 'total_bid_depth': 329.26893232, 'total_ask_depth': 508.98319124, 'market_bid_size': 0.0, 'market_ask_size': 0.0, 'ltp': 6062184.0, 'volume': 2324.91788788, 'volume_by_product': 1869.75000709}
bot は無限ループで動き続けるので動作確認ができたら Ctrl+C を押して bot を終了しましょう。
これで bot をコンテナとして実行する体験ができました!
Dockerfile
Dockerfile はプログラムの実行環境を 1 つにまとめて定義するファイルです。 Docker のビルドエンジンは Dockerfile を読み込んでビルドを行い、コンテナイメージという形式で内部に保存します。
Dockerfile では FROM
, COPY
, RUN
などの命令を利用して環境を定義します。先ほどの Dockerfile の意味をコメント付きで解説します。
FROM python:3.12
# 利用する「ベースイメージ」を指定する。 ここでは Docker Hub 公式の Python 3.12 イメージを利用する。
# `python` が「イメージ名」で、コロンのあとの `3.12` が「タグ名」と呼ばれる。
# このイメージには Python 3.12 の実行環境が含まれている。
# 初めてのビルド時にこのイメージがダウンロードされる。
# `python` のイメージは以下の Docker Hub のページに存在するもので、
# `3.12` タグ以外にも利用可能なタグを確認することができる。
# https://hub.docker.com/_/python
ENV PYTHONUNBUFFERED=1
# 環境変数 PYTHONUNBUFFERED に 1 を設定する。
# この変数は標準出力のバッファリングに関わる Python の設定で、
# Docker で Python を利用する際のお作法的な変数。
# これを設定しないとコンテナ実行中にログが表示されず、終了と同時に一気に表示される。
WORKDIR /app
# 作業を行う為のディレクトリ。
# WORKDIR 何も設定しないとルート `/` にファイルがコピーされるので移動することを推奨する。
COPY main.py requirements.txt ./
# ローカル側にある main.py と requirements.txt をコンテナ側のディレクトリにコピーする。
# 相対パスを指定しているので WORKDIR 直下にコピーされる。
RUN pip install -r requirements.txt
# コンテナ内にて、`pip install` コマンドを実行して必要ライブラリをインストールしておく。
CMD [ "python" , "main.py" ]
# 実行コマンド。 `docker run` 時に実行される。
docker build / run
docker build
と docker run
コマンドのオプションの意味を解説します。
docker build -t bot-demo .
docker build
は Dockerfile をビルドしてコンテナイメージを作成するコマンドです。
-
-t bot-demo
-
イメージ名:タグ名
を定義するオプションです。 - この例では
bot-demo
というイメージ名を付けました。 - タグ名は省略しています。 省略すると
latest
というタグ名から利用できます。
-
-
.
- 末尾のドット
.
はビルドする場所を表しています。 - 現在のディレクトリでビルドしたいので、現在のディレクトリを表す
.
を指定しています。 - Docker は指定されたディレクトリ直下にある
Dockerfile
を読み込んでビルドを行います。
- 末尾のドット
docker run --rm bot-demo
docker run
コンテナイメージを実行するためのコマンドです。
-
--rm
:- コンテナの実行終了後、そのコンテナを削除します。
- このオプションを使用しないと、コンテナは終了後
Exited
という状態になります。docker run
を何度も行うとExited
状態のコンテナがいくつも作成されてしまいます。 - コンテナを削除するとログなども消えてしまいますが、この演習では一旦実行したかっただけなのでこのオプションを利用しています。
-
bot-demo
- ビルド時に定義したイメージ名を指定しています。
- タグ名を省略しているので暗黙的に
latest
が実行されます。
バックグラウンドで実行してみる
先ほどの run コマンドはフォアグラウンドで実行されていました。 次は bot をバックグラウンドモードで起動してみましょう。
docker run -d --name mybot bot-demo
-
-d
:- バックグラウンドで実行するオプションです。
-
--name mybot
- バックグラウンドで実行したコンテナを識別するためにコンテナに名前を付けます。
-
--name
を指定しないとランダムな名前のコンテナが作成されます。 - 同じ名前のコンテナは重複して作成することができません。 そのため
--name
を指定しておくことで同じ bot を複数個バックグラウンドで実行してしまうことを防ぎます。
実際にコンテナが立ち上がっているか確認しましょう。 実行中のコンテナの確認は docker ps
コマンドで行う事ができます。
docker ps
またバックグラウンド実行なので、ログはそのまま画面に出力されません。 docker logs
をコマンドで確認することができます。
docker logs -f mybot
-
-f
:- ログをリアルタイムで表示するオプションです。
-
mybot
:- コンテナ名を指定します。
ログのリアルタイム表示が確認できたら Ctrl+C で表示を終了します。
確認ができたらコンテナを停止しましょう。 コンテナの停止には docker rm
コマンドを利用します。
docker rm -f mybot
-
-f
:- 実行中のコンテナを強制的に削除する。
-
mybot
:- コンテナ名を指定します。
コンテナイメージをアップデートする
ローカルの main.py
を変更して bot をアップデートしてみましょう。
import time
import requests
def main():
while True:
r = requests.get("https://api.bitflyer.com/v1/getticker?product_code=ETH_JPY")
data = r.json()
print(data)
time.sleep(1.0)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass
requests.get
で取得する銘柄を ETH_JPY
に変更しました。
更新した bot を確認する為に docker run
で実行してみます。
docker run --rm bot-demo
{'product_code': 'BTC_JPY', 'state': 'RUNNING', 'timestamp': '2023-12-15T05:44:18', 'tick_id': 93039265, 'best_bid': 6061996.0, 'best_ask': 6062184.0, 'best_bid_size': 0.96996, 'best_ask_size': 0.02992205, 'total_bid_depth': 329.26893232, 'total_ask_depth': 508.98319124, 'market_bid_size': 0.0, 'market_ask_size': 0.0, 'ltp': 6062184.0, 'volume': 2324.91788788, 'volume_by_product': 1869.75000709}
しかしコンテナが出力するログは変更前の BTC_JPY
のバージョンになっています。 確認したら Ctrl+C でコンテナを止めてください。
これは、コンテナイメージは読み取り専用のレイヤーとなっている為です。 つまり docker build
を実行してコピーされた時点のファイルが実行されています。 その為、ローカルの main.py
を更新してもコンテナイメージには反映されません。
コンテナイメージを更新するには再度 docker build
を実行します。
docker build -t bot-demo .
これで再ビルドができたので docker run
コマンドで再度 bot を実行してみましょう。
docker run --rm bot-demo
{'product_code': 'ETH_JPY', 'state': 'RUNNING', 'timestamp': '2023-12-15T08:04:29.407', 'tick_id': 57115956, 'best_bid': 323590.0, 'best_ask': 323810.0, 'best_bid_size': 0.01, 'best_ask_size': 0.09, 'total_bid_depth': 1836.8604647, 'total_ask_depth': 1711.4365051, 'market_bid_size': 0.0, 'market_ask_size': 0.0, 'ltp': 323760.0, 'volume': 8751.0728978, 'volume_by_product': 8751.0728978}
ETH_JPY
のティッカー情報が表示され、無事 bot が最新の状態になりました!
ここまで docker
コマンドの基礎を学んで bot を実行する方法を学びました 🐋
しかし単なる docker
コマンドは指定するオプションが多く、開発時にこの今度を毎回実行するのは面倒です。 Docker Compose を利用するともっと単純なコマンドでコンテナをビルド・実行することができます。 次は docker compose
コマンドを利用する方法を学んでいきます。
Docker Compose
Docker Compose は簡単にいうと docker
コマンドのオプションをまとめて実行します。
プログラムの環境構築をまとめておくのが Docker で、Docker の実行方法をまとめたのが Docker Compose といったイメージです。
コマンドのオプションをまとめるファイルは compose.yaml
です。
Compose Up!
では Docker Compose を利用して先ほどの bot を実行してみましょう!
まずは compose.yaml
ファイルを作成います。 先ほどの節の Dockerfile
と同じディレクトリに作成してください。
services:
bot-demo:
build:
context: .
restart: unless-stopped
docker compose up
コマンドでコンテナをビルドと実行を同時に行いましょう!
docker compose up --build -d
docker build
コマンドの時のようなビルドのログが表示されます。 同様に FINISHED と表示されていれば成功です。 Docker Compose は自動で現在のディレクトリにある compose.yaml
ファイルを読み込んでいるのです。
このコマンドではコンテナはバックグラウンドで起動します。 現在の Docker Compose プロジェクトで起動しているコンテナの確認は docker compose ps
コマンドで行うことができます。
docker compose ps
ログを確認するには docker compose logs
を利用します。
docker compose logs -f
docker logs
コマンドの時のようにリアルタイムで表示されるので、確認ができたら Ctrl+C で表示を終了してください。
docker
コマンド単体の時と比べて docker compose up
のコマンドのみでコンテナのビルドと実行が同時にできるのでとても簡単になりました 🐳
最後に Docker Compose で作成したコンテナを削除してみます。 docker compose down
コマンドを利用します。
docker compose down
compose.yaml
先ほど利用した compose.yaml
ファイルの意味を説明します。
services:
# トップレベルの宣言。
# Docker Compose では実行する各コンテナは「サービス」と表現されます。
bot-demo:
# コンテナ (サービス) の名前。 コンテナイメージ名にも使用されます。
build:
# コンテナイメージのビルドに関する設定レベル
context: .
# `Dockerfile` が存在するディレクトリの指定する。
# ここでは現在のディレクトリにあるのでドット `.` を指定してる。
restart: unless-stopped
# コンテナの再起動に関する設定を指定する。
# この設定では手動でコンテナを停止しない限り、
# 例えばプログラムのエラー時など自動で再起動してくれます。
# ※ `docker` コマンド自体にもあるオプションです。
docker compose up / down
docker compose up --build -d
docker compose up
は定義した compose.yaml
に従ってサービス (コンテナ) を起動するコマンドです。 コマンド単体でみると docker run
に相当します。
-
--build
-
docker build
に相当するフラグです。 - こちらを指定しないとビルドなしでサービス (コンテナ) を立ち上げるだけなのでプログラムが更新されません。
-
-
-d
- バックグラウンドでコンテナを起動します。
docker
単体コマンドと同じです。
- バックグラウンドでコンテナを起動します。
docker compose down
docker compose up
で立ち上げたサービス群のリソースを削除します。 起動中、停止中の状態に関わらず対象のコンテナは削除されます。
マルチコンテナ
この本では詳しくは説明しませんが、Docker Compose は複数のコンテナを同時に立ち上げることが可能です。
botter 的なユースケースでいうと、環境変数を使って銘柄を切り替えて bot を起動したり、bot のデータや状態を管理するデータベースコンテナを立ち上げて利用するなどです。
こちらの compose.yaml
ファイルはそのユースケースに沿った例です。
services:
bot-demo-btc:
build:
context: .
restart: unless-stopped
environment:
PRODUCT_CODE: BTC_JPY
bot-demo-eth:
build:
context: .
restart: unless-stopped
environment:
PRODUCT_CODE: ETH_JPY
mongo:
image: mongo
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: example
この章のまとめ
ここまで Docker を利用して bot を実行する為の学習を行いました。
- Docker はプログラムの実行環境を「コンテナ」にまとめて実行することができます。
- Docker を利用するには
Dockerfile
を作成します。 -
docker build
コマンドで bot のプログラムをコンテナイメージへビルドができます。 -
docker run
コマンドでコンテナイメージからコンテナを実行できます。-
-d
オプションを利用することでバックグラウンドで実行できます。
-
-
docker ps
やdocker logs
などのコマンドで実行中のコンテナを確認したりログを確認できます。 - Docker Compose を利用するには
compose.yaml
ファイルを作成します。 -
docker compose up
でまとめてビルドと実行ができます。
しかしここで覚えた方法でコンテナが実行される場所はローカル環境になります。 クラウド環境で実行するには何が必要でしょうか?
SSH でログインして、開発した bot のソースコードをクラウド環境にコピーして docker compose up --build -d
コマンドの実行すればよいのでしょうか? 誰もデプロイするために SSH でログインしてコマンドを実行したくはありません。
次の章では実践的なデプロイ方法、及び運用方法を学びます。
参考資料
この章では bot を実行する為だけの必要な Docker の機能のみ紹介しました。
Docker をより学ぶには公式ドキュメントが最も参考になります。 Guides からステップバイステップで学ぶことが可能です。