ラベル Docker の投稿を表示しています。 すべての投稿を表示
ラベル Docker の投稿を表示しています。 すべての投稿を表示

2022年12月22日木曜日

作成するDockerイメージを環境により切り替える

 Dockerイメージを作成するときに、環境によってDockerfileで実行する内容を変えたい場合がある。プログラミング言語のif文のように条件によって実行する内容を変えられればいいのだが、Dockerにはif文のようなものはない。そこで、シェルスクリプトのif文を使って、環境によりDockerfileで実行する内容を変えてみる。


環境

WSL2(Ubuntu20.04)。

$ lsb_release -dr
Description:    Ubuntu 20.04.5 LTS
Release:        20.04

Raspberry Pi OS(64bit)。

$ lsb_release -dr
Description:    Debian GNU/Linux 11 (bullseye)
Release:        11

今回はこの2つの環境で、Dockerイメージの内容を切り替えるDockrfileを作成する。


CPUアーキテクチャによりDockerfileの内容を切り替える

Raspberry Piで動作するSelenium+最新PythonのDockerコンテナを作成するでは、Raspberry Pi(ARM64)でDockerイメージを作成するためのDockerfileを作成した。Raspberry PiのCPUはARMで、インストールしているgeckodriverはARM用なので、このDockerfileを使ってWSL2のUbuntu(x86_64)上でDockerイメージを作成しても、インストールされたgeckodriverは動作しない。今回は、このDockerfileをRaspberry PiとWSLのどちらでも動作するようにする。

ただし、Dockerfileではif文のような条件分岐はできないので、CPUアーキテクチャによりインストールするgeckodriverを切り替えられるシェルスクリプトを用意して、それをDockerfileで実行する。


シェルスクリプトの作成

まずはgeckodriverをインストールするシェルスクリプトを作成する。CPUアーキテクチャで条件分岐するために、コマンドでCPUアーキテクチャを取得する。以下のコマンドで、CPUアーキテクチャの文字列を取得できる。


$ lscpu | grep -e Architecture: -e アーキテクチャ: | awk -F : '{print $2}' | xargs
 

上記コマンドの出力結果は、Raspberry Piでは「aarch64」、WSLでは「x86_64」となる。このコマンドを使って、次のシェルスクリプトを作成する。

#!/bin/sh

CPU_ARCH="`lscpu | grep -e Architecture: -e アーキテクチャ: | awk -F : '{print $2}' | xargs`"

if [ "${CPU_ARCH}" = "aarch64" ]; then
    curl -LO https://github.com/mozilla/geckodriver/releases/download/v0.32.0/geckodriver-v0.32.0-linux-aarch64.tar.gz
    tar xzvf geckodriver-v0.32.0-linux-aarch64.tar.gz
    mv geckodriver /usr/bin/
    rm geckodriver-v0.32.0-linux-aarch64.tar.gz
else
    curl -LO https://github.com/mozilla/geckodriver/releases/download/v0.32.0/geckodriver-v0.32.0-linux64.tar.gz
    tar xzvf geckodriver-v0.32.0-linux64.tar.gz
    mv geckodriver /usr/bin/
    rm geckodriver-v0.32.0-linux64.tar.gz
fi
 


Dockerfileの作成

シェルスクリプトが用意できたら、Dockerfileを作成する。Raspberry Piで動作するSelenium+最新PythonのDockerコンテナを作成するで作成したDockerfileを変更する。このDockerfileでgeckodriverのインストールをしている箇所を、作成したシェルスクリプトの実行に置き換える。composeファイルなどは前述の記事と同じものを使う。

FROM python:3.11-bullseye
 
WORKDIR /app
 
# Firefoxと日本語フォントインストール
RUN apt update \
    && apt -y install --no-install-recommends \
    # 日本語フォントインストール
    # 日本語フォントをインストールしておかないとFirefoxの表示が文字化けする
    fonts-noto-cjk \
    firefox-esr \
    && apt clean
 
# SeleniumのPythonバインディングインストール
RUN pip install selenium --no-cache-dir

# geckodriverのインストール
COPY ./install_geckodriver.sh ./
RUN chmod u+x install_geckodriver.sh \
    && ./install_geckodriver.sh
 

ファイル構成は以下のようになる。これで、Raspberry PiとWSLのどちらの環境でも、同じDockerfileでコンテナを起動して使える。

|-- Dockerfile
|-- app
| `-- sample.py
|-- install_geckodriver.sh
`-- docker-compose.yml


2022年11月27日日曜日

Raspberry Piで動作するSelenium+最新PythonのDockerコンテナを作成する

Raspberry Pi上のDockerでSeleniumを使う場合、Doker Hubにはselenium/standalone-chromeをforkしてRaspberry PiのCPUアーキテクチャであるARMに対応したseleniarm/standalone-chromiumがある。ただ、selenium/standalone-chromeのイメージにインストールされているPythonのバージョンは3.8と少し古い。そこで、今回はSeleniumと最新バージョンのPythonがインストールされたDockerコンテナを作成して、Raspberry Piで起動してみる。


環境

Raspberry Pi OS(64bit)。Raspberry Pi OS(64bit)にDockerとComposeをインストールする方法はRaspberry Pi OS 64bitでDockerを使うを参照。

$ lsb_release -dr
Description:    Raspbian GNU/Linux 11 (bullseye)
Release:        11
$ docker --version
Docker version 20.10.21, build baeda1f
$ docker-compose --version
docker-compose version 1.29.2, build unknown


Dockerfileの作成

Seleniumと最新Pythonが使えるDockerイメージを作成するためのDockerfileを用意する。ベースとなるDockerイメージとしてDocker Hubにある公式のPyrthonイメージの3.11-bullseyeを使う。Seleniumを使うにはブラウザとドライバが必要だが、今回はFirefoxとそのドライバであるgeckodriverをインストールする。Firefoxで日本語を表示できるように、日本語フォントの googlefonts / noto-cjkもインストールする。

FROM python:3.11-bullseye

WORKDIR /app

# Firefoxと日本語フォントインストール
RUN apt update \
    && apt -y install --no-install-recommends \
    # 日本語フォントインストール
    # 日本語フォントをインストールしておかないとFirefoxの表示が文字化けする
    fonts-noto-cjk \
    firefox-esr \
    && apt clean

# SeleniumのPythonバインディングインストール
RUN pip install selenium --no-cache-dir

# geckodriverインストール
RUN curl -LO https://github.com/mozilla/geckodriver/releases/download/v0.32.0/geckodriver-v0.32.0-linux-aarch64.tar.gz \
  && tar xzvf geckodriver-v0.32.0-linux-aarch64.tar.gz \
  && mv geckodriver /usr/bin/ \
  && rm geckodriver-v0.32.0-linux-aarch64.tar.gz


docker-composeファイルとSeleniumを使うサンプルスクリプトの用意

Dockerfileのほかに、docker-compose.ymlとSeleniumを使うPythonサンプルスクリプトを用意する。

version: '3'
services:
  selenium:
    build:
      context: .
      dockerfile: ./Dockerfile
    tty: true
    environment:
      - TZ=Asia/Tokyo
    volumes:
      - ./app:/app


以下のPythonスクリプトでは、Firefoxを起動してGoogleで「Raspberry Pi」を検索し、結果のスクリーンショットを保存する。

import os

from selenium.webdriver import Firefox, FirefoxOptions
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions

url = 'https://www.google.com'

# ブラウザーをヘッドレスで起動
options = FirefoxOptions()
options.add_argument('-headless')
service = Service(
    executable_path='/usr/bin/geckodriver', log_path=os.devnull
)
with Firefox(service=service, options=options) as driver:
    driver.get(url)

    # 画面表示を最大5秒まで待つ
    driver.implicitly_wait(5)
    # 検索ボックスのエレメントを取得(inputタグのname属性はq)
    elem = driver.find_element(By.NAME, 'q')
    # 検索ボックス内のテキストをクリア
    elem.clear()
    # 検索ワードを入力
    elem.send_keys('Raspberry Pi')
    # 検索ボタンがクリックできるようになるまで最大10秒待つ
    wait = WebDriverWait(driver, 10)
    input = wait.until(expected_conditions.element_to_be_clickable((By.XPATH, "//input[@value='Google 検索']")))

    if input.get_attribute('type') == 'submit':
        input.click()
    else:
        print('Failed to find submit button.')

    # 検索結果がなければ「No results found.」と表示する
    assert 'No results found.' not in driver.page_source

    # スクリーンショットを保存
    driver.save_screenshot('firefox_search.png')


ファイル配置は以下のようになる。

|-- Dockerfile
|-- app
| `-- sample.py
`-- docker-compose.yml


コンテナ上でSeleniumを使う

ファイルの準備ができたら、Dockerイメージを作成してSeleniumをPythonで使ってみる。まずはイメージをビルドする。

$ docker-compose build

続いて、コンテナ上で、作成したPythonスクリプトを実行する。

$ docker-compose run selenium python sample.py

スクリプトを実行すると、以下のようなfirefox_search.pngというFirefoxのスクリーンショットファイルが保存される。


2022年8月30日火曜日

Dockerコンテナ上のPythonアプリをホストのcronで実行する

Dockerコンテナ上のPythonアプリをホストのcronで実行することがあったので、その方法をまとめておく。


環境


Raspberry Pi OS 64bit with desktop。Docker環境はRaspberry Pi OS 64bitでDockerを使うで作成したもの。
$ lsb_release -dr
Description:    Debian GNU/Linux 11 (bullseye)
Release:        11
$ docker --version
Docker version 20.10.17, build 100c701
$ docker-compose --version
docker-compose version 1.29.2, build unknown


Dockerコンテナの準備

まずはDockerコンテナを準備する。イメージはDocke公式Pythonイメージを使う。docker-compose.yml、Dockerfileの他にapp.pyを用意する。app.pyは「Hello Test」と表示するだけのPythonアプリで、これをコンテナ上で実行する。

version: "3"
services:
  testpy:
    build:
      context: .
      dockerfile: Dockerfile
FROM python:3.10-bullseye

WORKDIR /usr/src/app

COPY . .

CMD [ "python", "./app.py" ]
print('Hello Test')


コンテナ上のアプリを実行する

ファイルの準備ができたら、とりあえずcronを使わずにコンテナ上のアプリを実行してみる。まずはイメージをビルドする。

$ docker-compose build

そしてコンテナ上のアプリを実行してみる。

$ docker-compose run testpy
Creating testpy_testpy_run ... done
Hello Test


ホストでcron設定をする

Dockerコンテナ上のアプリが実行できることを確認できたので、ホストのcronでコンテナを起動する。

まずはコンテナを起動するスクリプトファイル作成する。cronではこのスクリプトファイルを実行する。docker-compose.ymlなどのファイルは~/docker/testpyにあるとする。cronで実行するときはTオプションをつけておかないとエラーになる。

#!/bin/sh

cd ~/docker/testpy
/usr/local/bin/docker-compose run -T testpy

作成したスクリプトに実行権限を付与しておく。

$ chmod u+x testpy.sh

cronの設定

$ crontab -e

ここでは実行結果を後で確認できるように、以下のように出力結果をログに保存しておく。

* * * * * ~/docker/testpy/testpy.sh > ~/testpy.log 2>&1

cronでスクリプトが実行できたらログファイルを確認する。cronでDockerコンテナ上のPythonアプリが実行できた。

$ cat ~/testpy.log
Creating testpy_testpy_run ...
Creating testpy_testpy_run ... done
Hello Test


2022年8月14日日曜日

Raspberry Pi OSでMariaDB10.8公式Dockerコンテナを使う

Raspberry Pi OSでMariaDB10.8の公式Dockerコンテナを起動してみたので、その手順をまとめておく。


環境


Raspberry Pi OS 64bit with desktop。Docker環境はRaspberry Pi OS 64bitでDockerを使うで作成したもの。
$ lsb_release -dr
Description:    Debian GNU/Linux 11 (bullseye)
Release:        11
$ docker --version
Docker version 20.10.17, build 100c701
$ docker-compose --version
docker-compose version 1.29.2, build unknown


ComposeでMariaDBコンテナを起動

ComposeでMariaDBコンテナを起動するために以下のファイルを用意する。MariaDBのrootパスワードなどは環境変数で設定できる。ここではComposeのサービス名はtestdbとする。MariaDBのデータベースデータは、作成したボリュームをマウントして永続化することで、コンテナを停止しても保持できるようにしておく。

version: "3"
services:
  testdb:
    image: mariadb:10.8
    environment:
      MYSQL_ROOT_PASSWORD: password # データベースrootパスワード
      MYSQL_DATABASE: testdb # 作成するデータベース
      MYSQL_USER: testuser # 作成するデータベースユーザー
      MYSQL_PASSWORD: password # ユーザーパスワード
      MYSQL_ROOT_HOST: localhost # rootでの接続をloalhostからに限定
      TZ: 'Asia/Tokyo' # タイムゾーン設定
    volumes:
      - test_data:/var/lib/mysql # データ保管場所(永続化ボリューム)
      
volumes:
  test_data:


適当なフォルダ(ここではmariadb)を作成し、docker-compose.ymlを配置する。以下のコマンドでコンテナを起動する。

$ docker-compose up -d
Creating network "mariadb_default" with the default driver
Creating mariadb_testdb_1 ... done


コンテナの状態を確認してみる。StateがUpなら起動している。

$ docker-compose ps
      Name                     Command              State    Ports
--------------------------------------------------------------------
mariadb_testdb_1   docker-entrypoint.sh mariadbd   Up      3306/tcp 


コンテナが起動したらデータベースへ接続確認してみる。Composeの環境変数MYSQL_PASSWORDで設定したパスワードで接続できる。

$ docker-compose exec testdb mysql -u root -p -D testdb
Enter password:


接続できたらユーザー情報を確認。rootがlocalhostからの接続に限定されている。

> SELECT host, user FROM mysql.user;
+-----------+-------------+
| Host      | User        |
+-----------+-------------+
| %         | testuser    |
| localhost | mariadb.sys |
| localhost | root        |
+-----------+-------------+


MariaDBの設定

MariaDBコンテナを使うときに、MariaDBの設定を変更したい場合がある。コンテナ上で設定ファイルを変更しても、コンテナを停止したら変更前の状態に戻ってしまうので、MariaDBの設定ファイルをホスト側に用意しておいて、その設定ファイルをマウントしてコンテナ上で有効になるようにする。

MariaDBの設定ファイルはコンテナ上の /etc/mysql/my.cnfで、このファイル内で以下2つのディレクトリ配下の拡張子cnfのファイルを読み込むように設定されている。/etc/mysql/conf.d/配下は空で、デフォルトの状態だと/etc/mysql/mariadb.conf.d/配下のファイルの設定が読み込まれる。

/etc/mysql/mariadb.conf.d/
/etc/mysql/conf.d/

Docker HubのMariaDB公式イメージによると、コンテナのMariaDB設定を変えるには、ホストにあるカスタム設定ファイルを、コンテナ上の/etc/mysql/conf.d/配下のファイルとしてマウントするといいらしい。

ここでは、コンテナ上の/etc/mysql/mariadb.conf.d/配下のデフォルトの設定ファイルをホストにコピーして、設定を変更し、それらのファイルをコンテナの/etc/mysql/mariadb.conf.d/にマウントする。

まずは、コンテナ上の/etc/mysql/mariadb.conf.d/配下の設定ファイルを、ホストに作成したconfigディレクトリ配下にconf.dとしてコピーする。

$ mkdir config
$ docker cp mariadb_testdb_1:/etc/mysql/mariadb.conf.d config/conf.d

コピーしたファイルのうち、設定ファイル以外のファイルを削除しておく。

$ rm -rf config/conf.d/99-enable-encryption.cnf.preset


今回は、文字コードと照合順序の設定を変えてみる。文字コードはutf8mb4、照合順序はutf8mb4_binにする。ホストからコピーしたファイルのうち、50-server.cnfと50-client.cnfをそれぞれ以下のように変更または追記する。

50-server.cnf

[mysqld]
character-set-server  = utf8mb4
collation-server = utf8mb4_bin

50-client.cnf

[client]
default-character-set = utf8mb4


docker-compose.ymlには、MariaDB設定ファイルをマウントする行を追加する。
version: "3"
services:
  testdb:
    image: mariadb:10.8
    environment:
      MYSQL_ROOT_PASSWORD: password # データベースrootパスワード
      MYSQL_DATABASE: testdb # 作成するデータベース
      MYSQL_USER: testuser # 作成するデータベースユーザー
      MYSQL_PASSWORD: password # ユーザーパスワード
      MYSQL_ROOT_HOST: localhost # rootでの接続をloalhostからに限定
      TZ: 'Asia/Tokyo' # タイムゾーン設定
    volumes:
      - test_data:/var/lib/mysql # データ保管場所(永続化ボリューム)
      - ./config/conf.d:/etc/mysql/conf.d:ro # MariaDBの設定ファイル
      
volumes:
  test_data:

設定を反映させるためにコンテナを再起動する。
$ docker-compose restart

変更した設定が反映されているか確認する。まずはDBに接続。
$ docker-compose exec testdb mysql -u root -p -D testdb
Enter password:


文字コードの設定を確認する。

MariaDB [testdb]> SHOW VARIABLES LIKE "%character%";
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | utf8mb4                    |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb4                    |
| character_set_server     | utf8mb4                    |
| character_set_system     | utf8mb3                    |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+ 


照合順序の設定を確認するには以下コマンド。

MariaDB [testdb]> SHOW VARIABLES LIKE "%collation%";
+----------------------+--------------------+
| Variable_name        | Value              |
+----------------------+--------------------+
| collation_connection | utf8mb4_general_ci |
| collation_database   | utf8mb4_bin        |
| collation_server     | utf8mb4_bin        |
+----------------------+--------------------+


2022年7月28日木曜日

Raspberry Pi OS 64bitでDockerを使う

Raspberry Pi OS 64bitをインストールしたので、それを機にRaspberry PiでDockerを使ってみることにした。64bit OSだと、32bitのときに比べて使える公式Dockerイメージが多い。今回はDocker Compose v1をインストールして、DockerでDjango+PostgreSQL環境を作成すると同様に、Django+PostfreSQLの環境をDockerで作成してみる。


環境


OSはRaspberry Pi OS 64bit with desktop。
$ lsb_release -dr
Description:    Debian GNU/Linux 11 (bullseye)
Release:        11


DockerとComposeのインストール

まずはDockerのインストール。インストールスクリプトをダウンロードして実行するだけ。

$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh

インストールされたDockerのバージョン。

$ docker --version
Docker version 20.10.17, build 100c701

Dockerをインストールしたら、root権限なしでDockerコマンドを使用できるようにしておく。

$ sudo usermod -aG docker ${USER}
usermodの変更を反映させるために再起動。
$ sudo reboot
続いて、Docker Composeをインストール。ここではv1をインストールする。
$ sudo apt install libffi-dev
$ sudo pip3 install docker-compose 
インストールしたバージョンの確認。
$ docker-compose --version
docker-compose version 1.29.2, build unknown

OS起動時にDockerが自動起動するようにしておく。

$ sudo systemctl enable docker


Dockerコンテナを使う準備

Dockerが使えるようになったので、起動するDockerイメージのファイルなどを準備する。

今回は、DockerでDjango+PostgreSQL環境を作成するで作成したファイルで起動させてみる。以下のファイルを同じディレクトリに配置するが、docker-compose.ymlについては、マウントするホスト側のパス指定をWindows用からLinux用に変更しておく。

Dockerfile_web
requirements.txt
Dockerfile_db
docker-compose.yml
.env


docker-compose.yml

Composeファイル。PostgreSQLのデータベース領域は、後で作成するボリュームを指定する。

version: '3'

services:
  db:
    build:
     context: .
     dockerfile: Dockerfile_db
    ports: 
        - "5432"
    environment:
      POSTGRES_DB:
      POSTGRES_USER:
      POSTGRES_PASSWORD:
    volumes:
      - postgres_db:/var/lib/postgresql/data
  web:
    build:
     context: .
     dockerfile: Dockerfile_web
    command: python3 /app/manage.py runserver 0.0.0.0:8000
    volumes:
      - ./app:/app
    ports:
      - "8000:8000"
    depends_on:
      - db

volumes:
  postgres_db:
    external: true


Dockerコンテナの起動

Dockerファイルなどの準備ができたら、Dockerを起動してみる。

まずは、PostgreSQL用のボリュームを作成しておく。

$ docker volume create --name postgres_db
postgres_db 

続いて、ここではsampleというDjangoプロジェクトを作成する。

$ docker-compose run web django-admin.py startproject sample .

データベース接続などのプロジェクトの設定を変更するため、app/sample/settings.pyを編集する。

...
ALLOWED_HOSTS = ['*']
...
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'postgres',
        'USER': 'postgres',
        'PASSWORD': 'postgres',
        'HOST': 'db',
        'PORT': 5432,
    }
}
...
LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo' 

そして、コンテナを起動。

$ docker-compose up -d

ブラウザでhttp://192.168.1.10:8000にアクセス(192.168.1.10はRaspberry PiのIPアドレス)して、以下の画面が表示されれば問題なし。


PostgreSQlへの接続確認は以下のコマンド。
$ docker-compose exec db psql -U postgres 

Djangoのマイグレーションも実行してみる。

$ docker-compose run web python3 manage.py migrate
Creating django_web_run ... done
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK


2022年6月21日火曜日

DockerでDjango+PostgreSQL環境を作成する

Docker ComposeでDjango+PostgreSQLの環境を作成したので、その手順をまとめておく。Djangoのバージョンは3.2。


環境


Docker Desktop(Windows10 Pro)。
PS > docker --version
Docker version 20.10.16, build aa7e414
PS > docker-compose --version
docker-compose version 1.29.2, build 5becea4c


ファイルの準備

Dockerコンテナ作成に必要なファイルを準備する。準備するのは以下5つのファイル。


Dockerfile_web

Django用のDockerイメージを作成するDockerfile。Python公式イメージを使用。

FROM python:3.10

# 標準出力、標準エラーストリームのバッファリングを行わない
# Djangoのログをリアルタイムで出力するようにする
ENV PYTHONUNBUFFERED 1 

# ロケール設定の準備
RUN apt update \
 && apt -y install --no-install-recommends \
    locales \
 && sed -i -e 's/# ja_JP.UTF-8 UTF-8/ja_JP.UTF-8 UTF-8/' /etc/locale.gen \
 && locale-gen

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

RUN mkdir /app
WORKDIR /app

ADD requirements.txt /app/
RUN pip install -r requirements.txt


requirements.txt

DockerイメージにインストールするPythonライブラリ。DjangoとPostgreSQL接続用のDBアダプタをインストール。

Django>=3.2,<4.0
psycopg2


Dockerfile_db

PostgreSQL用のDockerイメージを作成するDockerfile。PostgreSQL公式イメージを使用。

FROM postgres:14.3

# ロケール設定に必要
RUN localedef -i ja_JP -c -f UTF-8 -A /usr/share/locale/locale.alias ja_JP.UTF-8

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


docker-compose.yml

Composeファイル。PostgreSQLのデータベース領域は、後で作成するボリュームを指定する。

version: '3'

services:
  db:
    build:
     context: .
     dockerfile: Dockerfile_db
    ports: 
        - "5432"
    environment:
      POSTGRES_DB:
      POSTGRES_USER:
      POSTGRES_PASSWORD:
    volumes:
      - postgres_db:/var/lib/postgresql/data
  web:
    build:
     context: .
     dockerfile: Dockerfile_web
    command: python3 /app/manage.py runserver 0.0.0.0:8000
    volumes:
      - .\app:/app
    ports:
      - "8000:8000"
    depends_on:
      - db

volumes:
  postgres_db:
    external: true


.env

環境変数ファイル。

# プロジェクト名
COMPOSE_PROJECT_NAME=django

# PostgreSQLの設定
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres


Djangoプロジェクトの作成

ファイルの準備ができたら、まずはDjangoプロジェクト作成してmanage.pyを作成する。ここではsampleというプロジェクトを作成。

PS> docker-compose run web django-admin.py startproject sample .

このコマンドを実行すると、ホストにappフォルダが作成され、その配下にsampleフォルダとmanage.pyが作成される。

続いて、Djangoのデータベース接続設定を変更しておく。必要なら言語とタイムゾーン設定も変えておく。

...
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'postgres',
        'USER': 'postgres',
        'PASSWORD': 'postgres',
        'HOST': 'db',
        'PORT': 5432,
    }
}
...
LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'


コンテナの起動

コンテナを起動する前に、PostgreSQL用のボリュームを作成する。Windowsホストのフォルダをマウントする方法もあるが、PostgreSQLコンテナでWindowsホストの領域をマウントすると以下エラーになる(Mounting data volume for Postgres in docker for Windows doesn't work)。

data directory "/var/lib/postgresql/data" has invalid permissions

そこで、Dockerでボリュームを作成して、そのボリュームをPostgreSQLのデータベース領域とする。

PS> docker volume create --name postgres_db
postgres_db

次にコンテナを起動。

PS> docker-compose up -d
Creating network "django_default" with the default driver
Creating django_db_1 ... done
Creating django_web_1 ... done
PS> docker-compose ps
    Name                  Command               State            Ports
-------------------------------------------------------------------------------
django_db_1    docker-entrypoint.sh postgres    Up      0.0.0.0:59332->5432/tcp
django_web_1   python3 /app/manage.py run ...   Up      0.0.0.0:8000->8000/tcp


コンテナを確認

各コンテナを確認してみる。まずはDjangoコンテナ。以下のように日付表示がJSTで、日本語表示になっていれば、タイムゾーンとロケールの設定が反映されている。

PS> docker-compose exec web date
2022年  6月 18日 土曜日 20:53:19 JST

次にPostgreSQLコンテナ。webコンテナと同様にタイムゾーンとロケールを確認。

PS> docker-compose exec db date
2022年  6月 18日 土曜日 20:53:19 JST 

続いてDjangoの確認。Djangoが起動していれば、ホストのブラウザでhttp://localhost:8000にアクセスすると以下の画面が表示される。

PostgreSQlへの接続確認もする。以下コマンドで接続できればOK。

PS> docker-compose exec db psql -U postgres 

最後に、Djangoのマイグレーションを実行してみる。

PS>  docker-compose run web python3 manage.py migrate
Creating django_web_run ... done
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK


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テーブルから取得したデータが表示される。



2021年7月11日日曜日

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

Docker Composeを利用してFlask+Apacheの環境を作成したので、その方法をまとめておく。


環境


Docker Desktop(Windows10 Pro)。


ファイル構成


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

任意のフォルダ
│ docker-compose.yml
│ Dockerfile

├─app
│ └─sample
│ │ app.wsgi
│ │ __init__.py

└─conf
   app.conf


Dockerfileの作成

Docker Hubにある公式CentOSイメージのcentos:centos8をベースにイメージを作成する。Dockerfileでは主に以下のことを行う。SSHはFlask+Apache環境を作成するのに必須というわけではないが今回は追加しておく。

  • Python3.8のインストール
  • Apacheのインストール
  • ホストからSSH接続できるようにする
  • ロケールの設定
  • Flaskのインストール
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 \
	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のインストール
RUN pip3 install --no-cache-dir flask

# 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"]


docker-compose.ymlの作成

Dockerfileでイメージ作成の準備が出来たので、次はdocker-compose.ymlを用意する。

version: "3"
services:
  flask:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: flask
    hostname: flask
    ports:
    - "20022:20022"
    - "80:80"


コンテナ動作確認


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

起動したコンテナの状態確認。

ブラウザでlocalhostにアクセスしてApacheが動作していることを確認。問題がなければ以下の画面が表示される。

次にSSHの確認。ポート20022、rootでパスワードpasswordで接続できればOK。


FlaskをApache上で動かす

コンテナ上のApacheの動作が確認できたら、続いてApacheでFlaskを使えるようにmod_wsgiの設定をする。mod_wsgi (Apache)を参考にApacheのVirtualHostの設定を追加するためにapp.confを作成する。今回はFlaskアプリのファイルをコンテナ上の/var/www/app/sampleに配置するようにする。作成したapp.confはconfフォルダを作成してその中に置いておく。

<VirtualHost *:80>
    WSGIDaemonProcess sample user=apache group=apache threads=5
    WSGIScriptAlias /sample /var/www/app/sample/app.wsgi

    <Directory /var/www/app/sample>
		# WSGIDaemonProcessで指定した名前と同じにする
        WSGIProcessGroup sample

        WSGIApplicationGroup %{GLOBAL}
		
		Require all granted
    </Directory>
</VirtualHost>

次にFlaskアプリの処理を記述した__init__.pyを作成する。

from flask import Flask

def create_app():
    app = Flask(__name__)

    @app.route('/')
    def index():
        return 'Hello!'

    return app

続いて作成した__init__.py内のcreate_appを実行するapp.wsgiを作成する。__init__.pyとapp.wsgiはapp\sampleフォルダを作成してその配下に配置する。

import sys
sys.path.insert(0, '/var/www/app')

from sample import create_app
application = create_app()

最後に作成したファイルをコンテナからマウントするようにdocker-compose.ymlを変更する。

version: "3"
services:
  flask:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: flask
    hostname: flask
    volumes:
      - .\app:/var/www/app
      - .\conf/app.conf:/etc/httpd/conf.d/app.conf
    ports:
    - "20022:20022"
    - "80:80"
追加したApacheの設定を反映させるためにコンテナを再起動。

うまくいっていれば、ブラウザでlocalhost/sampleにアクセスするとHello!と表示される。


2021年6月27日日曜日

Vue.jsのプロジェクトでThree.jsを使う

Vue.jsのプロジェクトでThree.jsを使ってみたのでその手順をまとめておく。


環境

DockerでVue.jsとApacheの環境を作成すると同様にDockerを使う。DockerfileにThree.jsのインストールを追加する。

FROM httpd:2.4

WORKDIR /app

# node.jsなどをインストール
RUN apt-get update \
 && apt-get -y install --no-install-recommends \
    nodejs \
    npm \
	curl \
 # キャッシュ削除
 && apt-get clean

# node.jsを最新にする
RUN npm install -g n \
 && n stable \
 && apt purge -y \
    nodejs \
	npm

# Vue.jsとCLIインストール
RUN ["/bin/bash", "-c", " \
    source ~/.bashrc \
     && npm install vue \
     && npm install -g @vue/cli \
     && npm install three \
 "]

EXPOSE 80 8080


Vue.jsプロジェクトの作成


Dockerコンテナを起動してVueプロジェクトを作成する。まずはコンテナを起動して中に入る。


新規でVueプロジェクトsampleを作成する。

今回はマニュアルを選択して次のように選択。
  • ? Please pick a preset: Manually select features
  • ? Check the features needed for your project: Choose Vue version, Babel, Router, Linter
  • ? Choose a version of Vue.js that you want to start the project with 2.x
  • ? Use history mode for router? (Requires proper server setup for index fallback in production) No
  • ? Pick a linter / formatter config: Standard
  • ? Pick additional lint features: Lint on save
  • ? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files


Three.jsでコンテンツ作成

Three.jsのサンプルをVue.jsで動作するようにする。Canvasコンポーネントを作成し、サンプルのHelloworldコンポーネントと置き換える。作成したプロジェクトのsrcディレクトリ配下のファイル構成を以下のようにする。

まずはcomponentsディレクトリ配下にCanvas.vueを追加する。

<template>
    <div ref="container" id="container">
    </div>
</template>

<script>
import * as THREE from 'three';

export default {
  name: 'Canvas',
  data () {
    return {
      camera: null,
      mesh: null,
      scene: null,
      renderer: null
    };
  },
  created () {
    this.init();
    window.addEventListener('resize', this.onWindowResize);
  },
  destroyed () {
    window.removeEventListener('resize', this.onWindowResize);
  },
  mounted () {
    this.animation();
    this.$refs.container.appendChild(this.renderer.domElement);
    this.onWindowResize();
  },
  methods: {
    init: function () {
      this.camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
      this.camera.position.z = 1;

      this.scene = new THREE.Scene();

      const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
      const material = new THREE.MeshNormalMaterial();

      this.mesh = new THREE.Mesh(geometry, material);
      this.scene.add(this.mesh);

      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.renderer.setSize(window.innerWidth, window.innerHeight);
      this.renderer.setAnimationLoop(this.animation);
    },
    animation: function (time) {
      this.mesh.rotation.x = time / 2000;
      this.mesh.rotation.y = time / 1000;

      this.renderer.render(this.scene, this.camera);
    },
    onWindowResize: function () {
      this.camera.aspect = window.innerHeight / window.innerHeight;
      this.camera.updateProjectionMatrix();
      this.renderer.setPixelRatio(window.devicePixelRatio);
      this.renderer.setSize(window.innerHeight, window.innerHeight);
    }
  }
}
</script>

<style>
#container {
  margin: 0 auto;
  width: max-content;
}
</style>

次に追加したCanvas.vueをサンプルのHelloworldコンポーネントと置き換えるようにHome.vueを修正。

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <!--<HelloWorld msg="Welcome to Your Vue.js App"/>-->
    <Canvas>
    </Canvas>
  </div>
</template>


<script>
// @ is an alias to /src
// import HelloWorld from '@/components/HelloWorld.vue'
import Canvas from '@/components/Canvas.vue'

export default {
  name: 'Home',
  components: {
    // HelloWorld,
    Canvas
  }
}
</script>

このままだとサーバー起動時にエラーが発生するので、プロジェクトディレクトリ配下の.eslintrc.jsのrulesに「'semi': 'off'」を追加してESLintで行末の「;」をエラーとしないように設定する。

module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: [
    'plugin:vue/essential',
    '@vue/standard'
  ],
  parserOptions: {
    parser: 'babel-eslint'
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'semi': 'off'
  }
}
ファイルの編集などが終わったら開発サーバを起動。

ブラウザでhttp://localhost:8080にアクセスすると以下のようなThree.jsのサンプルが表示される。


2021年5月16日日曜日

DockerでVue.jsとApacheの環境を作成する

Vue.jsとApacheの環境をDockerで作成する手順をまとめておく。


環境


Docker Desktop(Windows10 Pro)。


Dockerイメージ

Docker HubにあるDocker公式のApacheイメージ(httpd:2.4)を使う。Docerfileを用意してこのイメージにnode.jsやVue.jsなどをインストールする。

FROM httpd:2.4

WORKDIR /app

# node.jsなどをインストール
RUN apt-get update \
 && apt-get -y install --no-install-recommends \
    nodejs \
    npm \
	curl \
 # キャッシュ削除
 && apt-get clean

# node.jsを最新にする
RUN npm install -g n \
 && n stable \
 && apt purge -y \
    nodejs \
	npm

# Vue.jsとCLIインストール
RUN ["/bin/bash", "-c", " \
    source ~/.bashrc \
     && npm install vue \
     && npm install -g @vue/cli \
 "]

EXPOSE 80 8080


docker-compose.ymlの用意

docker-composeでコンテナを起動するためにdocker-compose.ymlを用意する。Dockerfileでイメージをビルドするようにし、ApacheとVue.jsの開発サーバ用のポートをそれぞれホスト側でも使用できるようにしておく。

version: "3"
services:
  vue:
    container_name: vue
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      TZ: 'Asia/Tokyo'
    ports:
    - 80:80
    - 8080:8080


Vue.jsプロジェクトを作成する


コンテナを起動してプロジェクトを作成する。まずはコンテナの起動。


次にコンテナに入る。


Vue.jsプロジェクトを作成する。


開発サーバを起動。

ブラウザでhttp://localhost:8080にアクセスすると次のサンプルページが表示される。


開発サーバで確認ができたらビルドしてApacheで表示してみる。

ブラウザでhttp://localhostにアクセスして開発サーバのときと同じ画面が表示されればOK。


2021年1月24日日曜日

Matplotlibで作成したグラフの一部を切り取る

Matplotlibで作成したグラフの一部だけ画像として切り取る方法を調べたのでまとめておく。


環境


Dockerコンテナ上のJupyter Lab(DockerコンテナでJupyterLab環境を作成する)。


グラフの作成

まずは切り取る対象のグラフとして散布図作成する。

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

np.random.seed(100)

N = 100
x = 0.9 * np.random.rand(N)
y = 0.9 * np.random.rand(N)
c = np.random.randint(1, 10, (N)) 
plt.scatter(x, y, marker='^', c=c)
plt.scatter(x, y, marker='o', c=c)

次のような散布図が作成される。











グラフの一部を切り取る

画像処理ライブラリPillowを使ってグラフを切り取る。Matplotlibで作成したグラフをPillowで画像として読み込んで、cropメソッドで切り取りを行う。cropメソッドで指定するのは切り取る長方形の左上右下の位置。

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import io
from PIL import Image

# グラフ作成
N = 100
x = 0.9 * np.random.rand(N)
y = 0.9 * np.random.rand(N)
c = np.random.randint(1, 10, (N)) 
plt.scatter(x, y, marker='^', c=c)
plt.scatter(x, y, marker='o', c=c)

# グラフをpngとしてPillowで読み込む
buf = io.BytesIO()
plt.savefig(buf, format='png')
plt.close()
buf.seek(0)
im = Image.open(buf)

# 画像サイズ確認
# (432, 288)
print(im.size)

# Pillowで読み込んだ画像を切り取り
# boxで切り取る長方形の左上右下の位置を指定
box = (60, 100, 380, 200)
im_cropped = im.crop(box)

buf.close()
display(im_cropped)

次のようにグラフが切り取られた。





2020年10月16日金曜日

Matplotlibのグラフスタイルを変える

 Pythonのグラフ描画ライブラリMatplotlibにはグラフのスタイルがいくつか用意されていて、グラフの配色を変えてみたいときなどに利用できる。スタイルの変更方法を調べたのでまとめておく。


環境


Dockerコンテナ上のJupyter Lab。


スタイルの一覧

使用できるスタイルの一覧はmatplotlib.pyplot.style.availableで確認できる。

Style sheets referenceには各スタイルのサンプルグラフがある。


スタイルの変更

スタイルを変更してみる。まずはデフォルトのスタイルでグラフを表示。

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

plt.style.use('default')
plt.plot(np.sin(np.linspace(0, 2 * np.pi)), 'o')
plt.show()


つづいて、スタイルを変更してみる。

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

plt.style.use('ggplot')
plt.plot(np.sin(np.linspace(0, 2 * np.pi)), 'o')
plt.show()

いくつかグラフを作成するうちのひとつだけ変えたい場合は、with構文を使うと一時的にスタイルを変えることができる。

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

with plt.style.context('ggplot'):  
    plt.plot(np.sin(np.linspace(0, 2 * np.pi)), 'o')
    plt.show()


すべてのスタイルのグラフを表示


すべてのスタイルのグラフを表示してみる。
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

for style in plt.style.available:
    print('\n[{}]'.format(style))
    with plt.style.context(style):
        plt.plot(np.sin(np.linspace(0, 2 * np.pi)), 'o')
        plt.show()
<以下省略>


2020年9月28日月曜日

PythonでMeCabが使える環境をDockerで作成する

Docker Composeを利用してPythonでMaCabが使えるDockerコンテナを作成したので、その方法をまとめておく。MeCabはオープンソースの形態素解析エンジン。



環境


Docker Desktop(Windows10 Pro)。



MeCabインストールに必要なファイルを準備

MeCabをソースファイルからインストールするので、MeCab: Yet Another Part-of-Speech and Morphological AnalyzerからMeCab本体のソース(mecab-0.996.tar.gz)とIPA辞書(mecab-ipadic-2.7.0-20070801.tar.gz)をダウンロードしておく。


Dockerfileの作成

Docker Hubにある公式Pythonイメージのpython:3.8-busterをベースにイメージを作成する。Dockerfileでは主に以下のことを行う。ダウンロードしたmecab-0.996.tar.gzとmecab-ipadic-2.7.0-20070801.tar.gzはmecabというフォルダに置いておく。

  • MeCabのインストール
  • IPA辞書のインストール
  • 日本語フォント(fonts-takao)インストール
  • ロケールの設定
  • デフォルト設定だとMeCabでは半角記号が名詞・サ変接続に分類されるので記号に分類されるように設定を変更
  • MeCabのPythonラッパーインストール
  • ユーザーの作成

FROM python:3.8-buster

# 作業ディレクトリを作り移動する
WORKDIR /work

# localesと日本語フォントのインストール
RUN apt update \
 && apt -y install --no-install-recommends \
    locales \
	fonts-takao \
 && sed -i -e 's/# ja_JP.UTF-8 UTF-8/ja_JP.UTF-8 UTF-8/' /etc/locale.gen \
 && locale-gen \
 &&	apt clean \
 &&	fc-cache -fv

COPY ./mecab/* /work/

# MeCabインストール
# ldconfigはライブラリパスのキャッシュ更新用
RUN tar zxvf mecab-0.996.tar.gz \
 && cd mecab-0.996 \
 && ./configure \
 && make \
 && make check \
 && make install \
 && ldconfig \
 && cd ../ \
 && rm -rf mecab-0.996

# IPA辞書インストール
# 半角記号が名詞・サ変接続になるのを記号・一般に変更
RUN tar zxvf mecab-ipadic-2.7.0-20070801.tar.gz \
 && cd mecab-ipadic-2.7.0-20070801 \
 && iconv -f eucjp -t utf8 unk.def > unk_utf8.def \
 && sed -i -e 's/SYMBOL,1283,1283,17585,名詞,サ変接続,\*,\*,\*,\*,\*/SYMBOL,1283,1283,17585,記号,一般,\*,\*,\*,\*,\*/' unk_utf8.def \
 && mv unk.def unk.def.original \
 && iconv -f utf8 -t eucjp unk_utf8.def > unk.def \
 && ./configure --with-charset=utf8 --enable-utf8-only \
 && make \
 && make install \
 && cp unk.def /usr/local/lib/mecab/dic/ipadic \
 && cd ../ \
 && rm -rf mecab-ipadic-2.7.0-20070801

# ロケールの設定
ENV    LANG="ja_JP.UTF-8" \
    LANGUAGE="ja_JP:ja" \
	LC_ALL="ja_JP.UTF-8"

# MeCabのPythonラッパーインストール
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# インストールに使用したファイルを削除
RUN rm -rf /work/*

#  ユーザーを作成してデフォルトユーザーにする
RUN groupadd -r pyuser \
 && useradd --no-log-init -r -g pyuser pyuser
USER pyuser
インストールするPythonラッパーはrequirments.txtに記載しておく。


Docker-compose.ymlの作成

Dockerfileでイメージ作成の準備が出来たので、次はdocker-compose.ymlを用意する。

version: "3"
services:
  pymecab:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: pymecab
    hostname: pymecab
    # コンテナを起動し続ける
    tty: true
    environment:
	  # タイムゾーン設定
      TZ: 'Asia/Tokyo'

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


イメージのビルド。


コンテナ上のPythonでMeCabを使う


コンテナを起動してコンテナに入る。


コンテナ上のPythonでMeCabを使ってみる。


2020年8月24日月曜日

SSH接続できるDockerコンテナを作成する

SSH接続できるDockerコンテナを作成したので、その手順をまとめておく。


環境


Docker Desktop(Windows10 Pro)。



Dockerイメージ


今回はDocker HubにあるDocker公式のCentOS7イメージ(centos:centos7)を使う。Docerfileを用意してこのイメージにOpenSSHをインストールし、SSHの設定を変更する。変更する設定はrootでのログイン許可、ポートを20022に変更するなど。
FROM centos:centos7

RUN yum -y update \
 && yum install -y openssh-server \
    openssh-clients \
 && yum clean all

# rootでのログインを許可
# ポートを22から20022に変更
# rootのパスワードをpasswordに設定
RUN 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 20022

docker-compose.ymlの用意


docker-composeでコンテナを起動するためにdocker-compose.ymlを用意する。Dockerfileでイメージをビルドするようにし、SSHで使うコンテナのポート20022をホスト側の20022で使えるようにする。
version: "3"
services:
  server:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: server
    privileged: true
    command: /sbin/init
    ports:
      - 20022:20022

ホストからコンテナにSSH接続する


起動したコンテナにホストからSSH接続してみる。まずはコンテナを起動する。


次にホストからSSH接続する。



2020年7月15日水曜日

DTW(動的時間伸縮法)で時系列データ間の距離を求める

以前にPythonで時系列クラスタリングをするで、DTW(動的時間伸縮法)による時系列データのクラスタリングをやってみた。このときはPythonパッケージのtslearnを使ってクラスタリングをしたが、単にDTWによる時系列データ間の類似度(距離)を求めたいだけならfastdtwも使える。ただ、fastdtwではDTWの近似アルゴリズムを使用しているので、tslearnとfastdtwの結果を比べてみることにした。



環境


Debian Buster(Dockerコンテナ)とJupyter Lab。



時系列データ



PythonでPDFの表からデータを抽出する(その2) で使用した帰国者・接触者相談センター(全相談件数)を使う。これは厚生労働省のホームページ地域ごとの感染状況等の公表についてからダウンロードできる「3.帰国者・接触者相談センターへの相談件数の推移(都道府県別・各日)・帰国者・接触者外来の受診者数の推移・うちPCR検査実施件数の推移(都道府県別・各日) 」のPDFに含まれるデータ。最近EXCELファイルが追加されたのでわざわざPDFからデータを抽出する必要はないのだが、ここではPythonでPDFの表からデータを抽出する(その2) の方法で作成したcsvを使う。都道府県ごとのデータがあるので、都道府県データ間の類似度をDTWで比べてみる。

PDFからcsvにしたデータをExcelで開くと次のような感じになる。


ライブラリなどのインストール


DTWを計算するためにtslearnとfastdtwをインストールする。


インストールしたライブラリのバージョン。


さらに、matplotlibのグラフで日本語表示できるように日本語フォントをインストールしておく。



データの準備


PDFから抽出したデータを保存したcsv(000643758.csv)を読み込んで、このcsvに含まれる都道府県ごとの「帰国者・接触者相談センター(全相談件数)」を使う。期間は5月のひと月分。都道府県により絶対数に差があるので、0-1の範囲にデータを変換する。なお、欠損値があるとDTWによる計算でエラーになるので京都、滋賀、香川のデータは除外。

%matplotlib inline

import pandas as pd
from sklearn import preprocessing

import matplotlib.pyplot as plt

# 日本語フォントの指定
plt.rcParams['font.family'] = 'TakaoGothic'

DT_FROM = '2020-05-01'
DT_TO = '2020-05-30'

def normalize(df):
    cols = df.columns
    idx = df.index
    x = df.values
    min_max_scaler = preprocessing.MinMaxScaler()
    x_scaled = min_max_scaler.fit_transform(x)
    return pd.DataFrame(x_scaled, index=idx, columns=cols)

def _load(csvpath):
    df = pd.read_csv(csvpath, index_col=0, header=[0,1], parse_dates=True, encoding='shift-jis')
 
    # 全国を除く「帰国者・接触者相談センター(全相談件数)」だけを選択
    df_con = df.filter(like='帰国者・接触者相談センター(全相談件数)', axis=1).drop(columns=('全国', '帰国者・接触者相談センター(全相談件数)'))
    
    # カラム名を都道府県のみにする
    df_con.columns = df_con.columns.droplevel(level=1)

    # 0-1の範囲に正規化    
    df_con = normalize(df_con[DT_FROM:DT_TO])

    # 欠損値(NaN)があるとDTWの計算ができないので除外
    # 京都、滋賀、香川のデータを除外
    df_con = df_con.dropna(axis='columns')    

    #print(df_con.info())
    #display(df_con.head())
    ax = df_con.plot(legend=None)
    plt.show()
    
    return df_con

df = _load('000643758.csv')

都道府県ごとのデータのプロット。


DTWの計算


tslearnとfastdtwで都道府県データ間の距離をDTWで計算し、似ている/似ていない都道府県の組み合わせを求めてみる。
%matplotlib inline

from itertools import combinations
import pandas as pd
from sklearn import preprocessing

from fastdtw import fastdtw
from tslearn.metrics import dtw

import matplotlib.pyplot as plt

# 日本語フォントの指定
plt.rcParams['font.family'] = 'TakaoGothic'

DT_FROM = '2020-05-01'
DT_TO = '2020-05-30'

def calc_dtw(l_sr):
    dict_dtw = {}
    for sr1, sr2 in combinations(l_sr, 2):
        distance, path = fastdtw(sr1.to_numpy(), sr2.to_numpy())
        dict_dtw[(sr1.name, sr2.name)] = (dtw(sr1.to_numpy(), sr2.to_numpy()), distance)
    
    return dict_dtw

df = _load('000643758.csv')

# Seriesのリスト
l_sr = [item for l, item in df.items()]

# 都道府県間のDTWを計算
dict_dtw = calc_dtw(l_sr)

print('\n似ている都道府県トップ10(DTW)')
display(sorted(dict_dtw.items(), key=lambda x:x[1][0])[:10])
print('\n似ている都道府県トップ10(fastDTW)')
display(sorted(dict_dtw.items(), key=lambda x:x[1][1])[:10])
print('\n似ていない都道府県トップ10(DTW)')
display(sorted(dict_dtw.items(), key=lambda x:x[1][0], reverse=True)[:10])
print('\n似ていない都道府県トップ10(fastDTW)')
display(sorted(dict_dtw.items(), key=lambda x:x[1][1], reverse=True)[:10])

似ている都道府県の組み合わせトップ10は以下の通り。tslearnとfastdtwで違いはあるものの、最も距離が近い組み合わせの神奈川と富山はtslearnとfastdtwで同じ。

似ていない都道府県の組み合わせトップ10は以下の通り。こちらもtslearnとfastdtwで違いはあるものの、最も距離が遠い組み合わせの岐阜と高知はtslearnとfastdtwで同じ。


最も距離が短い神奈川と富山のプロット。ほぼ一致している。


最も距離が遠い岐阜と高知のプロット。確かにけっこう違う。

tslearnとfastdtwそれぞれで距離が短いトップ5を比較。以下のコードを上記コードのあとに追加する。
(2021.12.16 グラフ作成部のコードを追加)
tslearn_top5 = sorted(dict_dtw.items(), key=lambda x:x[1][0])[:5]
fastdtw_top5 = sorted(dict_dtw.items(), key=lambda x:x[1][1])[:5]

for i, ((prefs1, values1), (prefs2, values2)) in enumerate(zip(tslearn_top5, fastdtw_top5)):
    fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 3))
    df.loc[:, prefs1].plot(ax=axes[0])
    df.loc[:, prefs2].plot(ax=axes[1])
    plt.show()   
左がtslearnで右がfastdtw。どちらが良いのかは判断が難しい・・






2020年6月29日月曜日

DockerコンテナでJupyterLab環境を作成する

普段はJupyter Notebookを使っているが、JupyterLabというJupyterプロジェクトの次世代UIがあるので、試しに使ってみることにした。インストールしてもいいのだが、最近はDockerが便利なので、DockerコンテナでJupyterLabを使う環境を作成してみる。


環境


Docker Desktop(Windows10 Pro)。



Dockerイメージ


まずはベースとなるDockerイメージを選ぶ。Jupyterプロジェクトによるイメージ(Selecting an Image)もあるのだが、今回はAnaconaのイメージ(continuumio/anaconda3)を使う。このイメージにはPandasなどデータ分析に必要なライブラリだけでなくJupyterLabもインストールされている。


イメージのカスタマイズ


Docker Hubにあるイメージはそのままでも起動できるが、いくつか手を加えたいのでDockerfileを用意する。日本語フォントのインストールと、タイムゾーン、ロケールの設定を行う。
FROM continuumio/anaconda3:latest

# 作業ディレクトリの作成
WORKDIR /work

# ロケールと日本語フォントのインストール 
RUN apt update \
 && apt -y install --no-install-recommends \
    locales fonts-takao \
 && sed -i -e 's/# ja_JP.UTF-8 UTF-8/ja_JP.UTF-8 UTF-8/' /etc/locale.gen \
 && locale-gen \
 && apt clean \
 && fc-cache -fv

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

EXPOSE 8888

# JupyterLabの起動
# --NotebookApp.tokenでトークン(jupyter)を指定
# 起動時のディレクトリを/workに設定
ENTRYPOINT ["jupyter-lab", "--ip=0.0.0.0", "--port=8888", "--no-browser", "--allow-root", "--NotebookApp.token='jupyter'", "--notebook-dir=/work"]


docker-compose.ymlの用意


docker-composeでコンテナを起動するためにdocker-compose.ymlを用意する。Dockerfileでイメージをビルドするようにし、コンテナのポート8888をホスト側の8888で使えるようにする。また、ホスト(Windows)のd:\workをコンテナからマウントして、ここでファイルなどを共有できるようにしておく。

version: "3"
services:
  jupyter:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: jupyter
    volumes:
    # ホストのd:\workをコンテナの/workにマウント
      - d:\work:/work
    ports:
      - 8888:8888


コンテナの起動


作成したDockerfileとdocker-compose.ymlを同じフォルダに置いて、docker-composeコマンドでコンテナを起動する。


コンテナの確認


ホストでブラウザからlocalhost:8888にアクセスするとJupyterLabのログイン画面が表示される。Dockerfileで指定したトークンでログインできる。

ロケールもちゃんと変更されている。