Webアプリケーション(Flask)のCI/CD環境構築 -Dockerを用いたデプロイ編-

Docker,Flask,Python

コンテンツ

はじめに

Webアプリケーション開発を始めたいと思い、のちのちデプロイのことを考えなくて済むようにCI/CD環境を構築しました。Webアプリケーションは、Flask + uwsgi + nginxを使って開発予定で、CI/CD環境はGitlabDockerで構築しました。

Webアプリケーション(Flask)のCI/CD環境構築

最低限のFlaskアプリを作成

Flaskのチュートリアルを見て、ルートが /hello(http://domain or address/hello)のとき Hello, World を返すWebアプリを作成しました。appディレクトリの中の__init__.pyファイルがWebアプリの本体です。

    myproject/
    ├── app/
    │   └── __init__.py
    └── tests
        ├── conftest.py
        └── test_factory.py

__init__.py

from flask import Flask

# create Flask application function
def create_app(test_config=None):
    # create and configure the app
    app = Flask(__name__, instance_relative_config=True)
    app.config.from_mapping(
        SECRET_KEY='dev',
    )
    
    if test_config is None:
        # load the instance config (not testing)
        app.config.from_pyfile('config.py', silent=True)
    else:
        # load the instance test config
        app.config.from_mapping(test_config)
        
    # hello page
    @app.route('/hello')
    def hello():
        return 'Hello, World!'
    
    return app
    
# Flask application instance 
application = create_app()
  

同時に最低限のテストも作成しました。Flaskのチュートリアルに従ってpytestを用いました。

conftest.py

import pytest
from app import create_app

@pytest.fixture
def app():
    app = create_app({
        'TESTING': True,
    })

    return app

@pytest.fixture
def client(app):
    return app.test_client()
    

test_factory.py

from app import create_app

def test_config():
    assert not create_app().testing
    assert create_app({'TESTING': True}).testing

def test_hello(client):
    response = client.get('/hello')
    assert response.data == b'Hello, World!'
    

配布パッケージの作成

デプロイ時にリポジトリをgit cloneして必要なファイルをそろえてもよかったのですが、Flaskのチュートリアルでは配布パッケージにしていたのでそれにならって配布パッケージにしてそれをもとにデプロイしようと思いました。

setup.py

from setuptools import find_packages, setup

setup(
    name='app',
    version='0.0.0',
    packages=find_packages(),
    include_package_data=True,
    zip_safe=False,
    install_requires=[
        'flask',
    ],
)

Dockerを使ってとりあえずローカルで動かす

WebアプリのFlaskとWebサーバのNginxの間には、uwsgiをインターフェースとして使用します。Nginxとuwsgiは、unixドメインソケットで接続します。以下がイメージです。

Webアプリコンテナのイメージを作成するDcoekrfileをdeploy配下に作成しました。requirements.txt, uwsgi.iniとapp-0.0.0-py3-none-any.whlをイメージに含め、必要なインストールをした後に、uwsgiコマンドを実行するようになっています。元のイメージは公式のpythonを使用しました。

    myproject/
    ├── app/
    ├── tests/
    ├── deploy/
    │   ├── Dockerfile
    │   ├── nginx.conf
    │   ├── requirements.txt
    │   └── uwsgi.ini
    ├── dist/
    │   └── app-0.0.0-py3-none-any.whl
    ├── docker-compose.yml

Dockerfile

FROM python:3.9.7

WORKDIR /var/www/
ENV FLASK_APP=app

# コンテキスト(buildコマンドを実行したカレントディレクトリ)がプロジェクトディレクトリ(このファイルのディレクトリの親ディレクトリ)
COPY deploy/requirements.txt ./
COPY deploy/uwsgi.ini ./
COPY /dist/app-0.0.0-py3-none-any.whl ./

RUN pip install --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install ./app-0.0.0-py3-none-any.whl

CMD ["uwsgi","--ini","./uwsgi.ini"]

requirements.txt

Flask
uwsgi

uwsgi.ini

[uwsgi]
module = app:application
master = true
socket = /tmp/uwsgi.sock
chmod-socket = 666
logto = /var/log/uwsgi.log

WebアプリのコンテナとWebサーバのコンテナを立ち上げるdocker-compose.ymlを作成しました。

docker-compose.yml

version: "3"
services:
  app:
    build:
      context: .
      dockerfile: deploy/Dockerfile
    volumes:
      - socket:/tmp

  web:
    image: nginx:latest
    ports:
      - "10080:80"
    volumes:
      - socket:/tmp

volumes:
  socket:

docker compose コマンドで立ち上げた Webサーバコンテナにアタッチして /etc/nginx/conf.d/default.conf の location / の設定を以下のように書き換えて nginx のサービスをリロードします(service nginx reload)。これでローカルでの環境でデプロイできるようになりました(Dockerのインストールが必要)。ただ、コーディングのデバッグのときには Flask の組み込みのサーバを用います。

Webサーバコンテナ /etc/nginx/conf.d/default.conf

location / {
    include uwsgi_params;
    uwsgi_pass unix:/tmp/uwsgi.sock;
}

実行例

Windows の docker engine で起動したコンテナに localhost:10080 でアクセスできなかったので、Webサーバコンテナ内で curl コマンドを実行してみました。