2021年7月24日土曜日

DockerでFlask+Apache+MySQL環境を作成する

DockerでFlask+Apache環境を作成するではDocker Composeを利用してFlask+Apacheの環境を作成したが、今回はその環境にMySQLコンテナを追加してFalskアプリからデータベースにアクセスしてみる。


環境


Docker Desktop(Windows10 Pro)。


ファイル構成


最終的には以下のファイル構成になる。

任意のフォルダ
│ .dockerignoe
│ .env
│ docker-compose.yml
│ Dockerfile
│ requirements.txt

├─app
│ └─sample
│     │ app.wsgi
│     │ __init__.py
│     │ config.py
│     │ database.py
│     └ models.py

│─conf
│  app.conf
└─init
   world.sql


Dockerfileの作成

Docker Hubにある公式CentOSイメージのcentos:centos8をベースにイメージを作成する。DockerでFlask+Apache環境を作成するで作成したDockerfileにさらにMySQLクライアントとPythonライブラリのインストールを追加する。

FROM centos:centos8

# コンテナ側のルート直下に作業ディレクトリ(work)を作り移動する
WORKDIR /work

# パッケージのインストールなど
# CentOS8 EOLのためdnfコマンドがエラーになるのでリポジトリ変更で対応(2022.2.13変更)
RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-Linux-* \
 && dnf -y update \
 && dnf	group install -y "Development Tools" \
 && dnf install -y openssh-server \
    openssh-clients \
	mysql \
	mysql-devel \
	httpd \
	httpd-tools \
	httpd-devel \
	python38 \
	python38-devel \
	python38-mod_wsgi \
	langpacks-ja \
 && cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
 && dnf clean all

# タイムゾーン、ロケール設定
ENV TZ="Asia/Tokyo" \
    LANG="ja_JP.UTF-8" \
    LANGUAGE="ja_JP:ja" \
	LC_ALL="ja_JP.UTF-8"

# FlaskなどPythonライブラリのインストール
COPY requirements.txt ./
RUN pip3 install --no-cache-dir -r requirements.txt

# SSH設定
# rootでのログインを許可
# ポートを22から20022に変更
# rootのパスワードをpasswordに設定
# ssh-keygenでホスト鍵を作成しておかないとSSHの起動に失敗する
RUN /usr/bin/ssh-keygen -A \
 && sed -ri 's/^#PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config \
 && sed -ri 's/^#Port 22/Port 20022/' /etc/ssh/sshd_config \
 && echo 'root:password' | chpasswd

EXPOSE 80
EXPOSE 20022

# SSHとApacheを起動
CMD ["sh","-c","/usr/sbin/sshd && /usr/sbin/httpd -D FOREGROUND"]

インストールするPythonライブラリなどはrequirements.txtに記述しておく。

flask
mysqlclient
SQLAlchemy
Flask-SQLAlchemy


docker-compse.ymlの作成

DockerでFlask+Apache環境を作成するで作成したdocker-compose.ymlにMySQLコンテナを追加する。コンテナのイメージはDocker Hubにある公式のMySQLのイメージ(mysql:8.0)を使う。このイメージではmysqlに説明があるようにMySQLのrootパスワードなどを環境変数で設定できるようになっている。docker-compose.ymlではこの環境変数の設定も行う。具体的な環境変数の値の設定は後述の.envファイルで行う。MySQLデータベースのデータを格納する場所は、mysql_dataというフォルダをホストに作成してマウントすることでコンテナが停止してもデータが残るようにしておく。

version: "3"
services:
  mysqldb:
    image: mysql:8.0
    container_name: mysqldb
    hostname: mysqldb
    environment:
      MYSQL_ROOT_PASSWORD:
      MYSQL_DATABASE:
      MYSQL_USER:
      MYSQL_PASSWORD:
      MYSQL_ROOT_HOST: localhost # rootでの接続をloalhostからに限定
      TZ: 'Asia/Tokyo' # タイムゾーン設定
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes: # ホスト:コンテナ:ro
      - .\mysql_data:/var/lib/mysql # MySQLデータベース保管場所
      - .\init:/docker-entrypoint-initdb.d:ro # このディレクトリにあるsh/sqlファイルがコンテナup時に自動実行される

  flask:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: flask
    hostname: flask
    environment:
      MYSQL_DATABASE:
      MYSQL_USER:
      MYSQL_PASSWORD:
    volumes:
      - .\app:/var/www/app
      - .\conf/app.conf:/etc/httpd/conf.d/app.conf
    ports:
    - "20022:20022"
    - "80:80"


初期データベースの準備

公式のMySQLのイメージでは、コンテナの/docker-entrypoint-initdb.dに拡張子shやsqlのファイルを配置しておくと、コンテナの初期起動時に自動実行される。今回はここに初期データベースのsqlを配置しておく。初期データベースとしては、Other MySQL DocumentationにあるMySQLのサンプルデータベースのworld database(zip)を使う。ダウンロードしたファイルを解凍するとworld.sqlがある。initというフォルダをdocker-compose.ymlがあるフォルダ配下に作成し、このファイルを配置しておく。initはdoker-compose.ymlでコンテナの/docker-entrypoint-initdb.dにマウントされている。


環境変数ファイルの準備

MySQLの環境変数を.envで設定する。

## MySQL設定
# rootパスワード
MYSQL_ROOT_PASSWORD=rootpass
# 初期データベース
MYSQL_DATABASE=world
# 作成されるユーザー名
MYSQL_USER=flaskuser
# 作成されるユーザーのパスワード
MYSQL_PASSWORD=test


.dockerignoreの配置

このままだとMySQLのデータなどコンテナ作成時に余計なファイルなどが転送されてしまうので、.dockerignoreを作成して転送されないようにする。

app
conf
mysql_data
init


動作確認


作成したDockerfile、docker-compose.yml、.env、.dockerignore、requirements.txtを同じフォルダに置いてコンテナを起動する。
 

起動したコンテナの状態確認。
 
flaskコンテナからMySQLデータベースに接続する。データベースの接続先はMySQLコンテナの名前を指定すればよい。
 

接続したら初期データベースworldの確認。


FlaskでMySQLデータベースを使う

コンテナの動作確認ができたので、ApacheでFlaskを使えるようにしてFlaskアプリのファイルをコンテナ上の/var/www/app/sampleに配置する。まずはmod_wsgiの設定をする。DockerでFlask+Apache環境を作成すると同様にapp.confとapp.wsgiを配置する。

続いてMySQLデータベースにアクセスするFlaskアプリを作成する。datavase.py、config.py、models.py、__init__.pyを作成してホストのsampleフォルダに配置する。

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def init_db(app):
    db.init_app(app)
import os

class SystemConfig:
    DEBUG = True

    SQLALCHEMY_DATABASE_URI = 'mysql+mysqldb://{user}:{passwd}@{host}:{port}/{dbname}?charset=utf8'.format(**{
        'user': os.getenv('MYSQL_USER'),
        'passwd': os.getenv('MYSQL_PASSWORD'),
        'host': 'mysqldb',
        'port': 3306,
        'dbname': os.getenv('MYSQL_DATABASE')
    })

    SQLALCHEMY_TRACK_MODIFICATIONS = False
    SQLALCHEMY_ECHO = False

Config = SystemConfig

worldデータベースのcityテーブルのSQLAlchemyモデルを作成する。

from sample.database import db

class City(db.Model):
    __tablename__ = 'city'

    ID = db.Column(db.Integer, primary_key=True, autoincrement=True)
    Name = db.Column(db.String(35), nullable=False, default='')
    CountryCode = db.Column(db.String(3), nullable=False, default='', index=True)
    District = db.Column(db.String(20), nullable=False, default='')
    Population = db.Column(db.Integer, nullable=False, default=0)

Flaskアプリの処理を記述した__init__.pyを作成する。cityテーブルから日本の都市名-都道府県名の一覧を取得して表示する。

from flask import Flask

from sample.database import init_db, db
from sample.models import City

def create_app():
    app = Flask(__name__)
    app.config.from_object('sample.config.Config')

    init_db(app)

    @app.route('/')
    def index():
        res = db.session.query(
            City.Name,
            City.District
        ).order_by(
            City.Name
        ).filter(
            City.CountryCode == 'JPN'
        ).all()

        # Name-Districtの文字列のlistを作成
        name_district = ['-'.join(r) for r in res]
        return '< br>'.join(name_district)

    return app


追加したファイルを反映させるためにコンテナを再起動。

ブラウザでlocalhost/sampleにアクセスすると以下のようにcityテーブルから取得したデータが表示される。



0 件のコメント:

コメントを投稿