開発にコンテナを使う

事前準備

コンテナとしてイメージを実行 で、イメージ構築をし、それコンテナ化アプリケーションとして実行します。

概要

この章では、これまでのモジュールで構築したアプリケーションの、ローカル開発環境をセットアップする方法を説明します。イメージの構築には Docker を使い、すべてをとても簡単にする Docker Compose も使います。

ローカルデータベースとコンテナ

まず、コンテナでデータベースを実行する方法と、データを保持するためにボリュームとネットワーク機能を使い、アプリケーションがデータベースと通信できるようにする方法を説明します。それから、compose ファイルの中にすべてを入れ込みます。このファイルは1つのコマンドで、ローカル開発環境のセットアップと実行をできるようにします。最後に、コンテナ内で実行しているアプリケーションに、デバッガを接続する方法を説明します。

MySQL をダウンロードし、インストールし、設定し、MySQL データベースをサービスとして実行する代わりに、MySQL 用の Docker 公式イメージを使い、コンテナとして実行できるようになります。

コンテナ内で MySQL を実行する前に、2つのボリュームを作成しておきたいです。これは、Docker が 保持し続けるデータ(persistent data)設定ファイル(configuration) を保存できます。バインド マウントを使う代わりに、 docker が提供するマネージド ボリューム機能を使いましょう。詳しい情報は ボリュームの使用 をご覧ください。

それでは、ボリュームを作成しましょう。作成するボリュームの1つは MySQL のデータ用で、もう1つは設定ファイル用です。

$ docker volume create mysql
$ docker volume create mysql_config

次は、アプリケーションとデータベースが相互に対話できるようにするためのネットワークを作成します。ネットワークは ユーザ定義ブリッジネットワーク(user-defined bridge network) と呼ばれ、優れた DNS 名前解決サービスを提供するため、 接続文字列(connection string) を作成するために使えます。

$ docker network create mysqlnet

次は、コンテナとして MySQL を実行し、先ほど作成したボリュームとネットワークに接続できるようにします。Docker は Hub からイメージを取得し、ローカルでイメージを実行します。以下のコマンドでは、 -v はコンテナにボリュームを付けて起動します。詳しい情報は Docker ボリューム をご覧ください。

次は、コンテナとして MongoDB を実行し、先ほど作成したボリュームとネットワークに接続できるようにします。Docker は Hub からイメージを取得し、ローカルでイメージを実行します。

$ docker run --rm -d -v mysql:/var/lib/mysql \
  -v mysql_config:/etc/mysql -p 3306:3306 \
  --network mysqlnet \
  --name mysqldb \
  -e MYSQL_ROOT_PASSWORD=p@ssw0rd1 \
  mysql

次は、 MySQL データベースが起動して接続できるかどうかを確認します。コンテナ内で実行している MySQL データベースに接続するには、以下のコマンドを実行し、パスワードのプロンプトでは「p@ssw0rd1」を入力します。

$ docker exec -ti mysqldb mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.23 MySQL Community Server - GPL

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

アプリケーションをデータベースに接続

先ほどのコマンドは、 mysqldb コンテナに対して「mysql」コマンドを実行し MySQL データベースログインしました。 MySQL 双方向ターミナルを終了するには CTRL-D を押します。

次は、 イメージ構築 の章で作成したサンプルアプリケーションを更新します。Python アプリのディレクトリ構成をみるには、 ディレクトリ構成 をご覧ください。

それでは、MySQL を実行していますので、 app.py を更新してデータベースとして MySQL を使うようにします。また、サーバに対する手順もいくつか追加しましょう。1つはレコードを取得し、1つはレコードを挿入します。

import mysql.connector
import json
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, Docker!'

@app.route('/widgets')
def get_widgets():
    mydb = mysql.connector.connect(
        host="mysqldb",
        user="root",
        password="p@ssw0rd1",
        database="inventory"
    )
    cursor = mydb.cursor()


    cursor.execute("SELECT * FROM widgets")

    row_headers=[x[0] for x in cursor.description] #this will extract row headers

    results = cursor.fetchall()
    json_data=[]
    for result in results:
        json_data.append(dict(zip(row_headers,result)))

    cursor.close()

    return json.dumps(json_data)

@app.route('/initdb')
def db_init():
    mydb = mysql.connector.connect(
        host="mysqldb",
        user="root",
        password="p@ssw0rd1"
    )
    cursor = mydb.cursor()

    cursor.execute("DROP DATABASE IF EXISTS inventory")
    cursor.execute("CREATE DATABASE inventory")
    cursor.close()

    mydb = mysql.connector.connect(
        host="mysqldb",
        user="root",
        password="p@ssw0rd1",
        database="inventory"
    )
    cursor = mydb.cursor()

    cursor.execute("DROP TABLE IF EXISTS widgets")
    cursor.execute("CREATE TABLE widgets (name VARCHAR(255), description VARCHAR(255))")
    cursor.close()

    return 'init database'

if __name__ == "__main__":
    app.run(host ='0.0.0.0')

MySQL モジュールを追加し、データベースサーバに接続するようコードを更新し、データベースとテーブルを作成します。また、 widgets の保存と widgets の取得という2つの手順を追加しました。次は、変更を含むイメージを再構築する必要があります。

まず、 pip を使ってアプリケーションに mysql-connector-python モジュールを追加しましょう。

$ pip3 install mysql-connector-python
$ pip3 freeze | grep mysql-connector-python >> requirements.txt

それから、イメージを構築できます。

$ docker build --tag python-docker-dev .

次はコンテナにデータベースネットワークを追加し、コンテナを実行します。これにより、データベースのコンテナ名を使ってアクセスできるようになります。

$ docker run \
  --rm -d \
  --network mysqlnet \
  --name rest-server \
  -p 8000:5000 \
  python-docker-dev

アプリケーションがデータベースに接続し、メモを追加できるかテストしましょう。

$ curl http://localhost:8000/initdb
$ curl http://localhost:8000/widgets

サービスからは以下の JSON を受け取るでしょう。

[]

Compose を使ってローカルで開発

このセクションでは、 python-docker と MySQL データベースを1つのコマンドで起動するための Compose ファイル を作成します。また、 python-docker-dev アプリケーションをデバッグモードで起動するための Compose ファイルも作成しますので、実行中のプロセスにデバッガを接続できるようになります。

IDE のメモ機能やテキストエディタで python-docker を開き、 docker-compose.dev.yml という名前の新しいファイルを作成します。ファイル内に以下の命令をコピー&ペーストします。

version: '3.8'

services:
 web:
  build:
   context: .
  ports:
  - 8000:5000
  volumes:
  - ./:/app

 mysqldb:
  image: mysql
  ports:
  - 3306:3306
  environment:
  - MYSQL_ROOT_PASSWORD=p@ssw0rd1
  volumes:
  - mysql:/var/lib/mysql
  - mysql_config:/etc/mysql

volumes:
  mysql:
  mysql_config:

この Compose ファイルは docker run コマンドに一切パラメータを渡す必要がないため、とても便利です。Compose ファイル内で宣言的にパラメータを指定します。

ポート 8000 を公開していますので、コンテナ内の dev ウェブサーバに到達できます。また、ローカルのソースコードを実行中のコンテナにマッピングしていますので、テキストエディタで変更できるだけなく、それらの変更をコンテナに取り込めます。

Compose ファイルを使う上で、もう1つの素晴らしい機能は、サービス名を使ってサービスの名前解決をできるようになります。そのため、接続文字列として「mysqldb」が使えるようになります。「mysqld」という名前を使えるのは、 MySQL サービスに対して Compose ファイル内でそのように名付けたからです。

次は、アプリケーションを起動し、適切に動作しているか確認しましょう。

$ docker-compose -f docker-compose.dev.yml up --build

--build フラグを渡したため、 Docker はイメージをコンパイルした後、イメージを起動します。

それから、API エンドポイントをテストしましょう。新しいターミナルを開き、 curl コマンドを使いサーバに対する GET リクエストを作成します。

$ curl http://localhost:8000/initdb
$ curl http://localhost:8000/widgets

次のような反応を受け取るでしょう:

[]

次のステップ

この章では、通常のコマンドラインとほとんど同じように使える、一般的な開発用イメージ作成方法を説明しました。また、 Compose ファイルもセットアップし、ソースコードを実行中のコンテナにマップし、デバッグポイントを公開しました。

次の章では、 GitHub Actions を使って CI/CD パイプラインをセットアップする方法を説明します。

CI/CD の設定

フィードバック

フィードバックを通し、このトピックの改善を支援ください。考えがあれば、 Docker Docs GitHub リポジトリに issue を作成して教えてください。あるいは、更新の提案のために RP を作成 してください。

参考

Use containers for development

https://docs.docker.com/language/python/develop/