2022年1月28日金曜日

Heroku で Python Flask で Postgres へ接続 (ローカル開発環境は Docker コンテナ)

Heroku の Python Flask アプリの環境で Postgres への接続を試してみました。

一つの Flask アプリでコードを書き換えずにローカル環境で実行した場合はローカルの Postgres、ネット上の Heroku 環境で実行した場合は Heroku の Postgres に接続できることを確認しています。

ローカルの開発環境は Docker コンテナを一つ用意し、この中に Heroku CLI、Postgres を入れています。

Docker イメージ作成

Heroku CLI, Postgres 用の Docker イメージは Docker Hub の Postgres をベースにして dockerfile でごにょごにょして Heroku CLI のインストール等を行います。

dockerfile は以下の通りです。

FROM postgres

RUN apt update && apt -y upgrade
RUN apt update && apt -y upgrade

RUN apt install -y \
  sudo \
  curl \
  wget \
  unzip \
  git \
  byobu \
  python3-pip \
  libpq-dev

RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*

RUN curl https://cli-assets.heroku.com/install.sh | sh

RUN useradd \
  --create-home \
  --groups sudo \
  --shell /bin/bash \
  worker
RUN echo 'worker:worker' | chpasswd
RUN su - worker -c 'mkdir /home/worker/Desktop'

Docker イメージをビルド

$ docker build --tag heroku-postgres:1 ./

Docker コンテナを開始

$ docker run \
  -p 5000:5000 \
  --name test-heroku-postgres \
  -e POSTGRES_PASSWORD=mysecretpassword \
  -d \
  heroku-postgres:1
  • -p 5000:5000」で Heroku ローカル開発環境 (heroku local web) で公開する 5000 番ポートに外部のブラウザからアクセスできるようにしてます。

  • コンテナ開始時に Postgres が初期化されるようなので「-e POSTGRES_PASSWORD=mysecretpassword」環境変数でパスワードを指定しています。ローカルのとりあえずの環境なのでパスワードそのまま書いていますが、気になる場合は https://hub.docker.com/_/postgres の「Docker Secrets」あたりを読むとパスワードをファイルに書いてそのファイル名を指定することもできるようです。(試していないので本当にできるかわかりません)

  • -d」を指定しているのでシェルのコマンドプロンプトにすぐ戻り、「$ docker ps -a」で確認するとバックグラウンドでコンテナが動いていることが確認できます。

  • PC 再起動時等、コンテナが停止している場合は以下でコンテナを起動できます。

    $ docker container start test-heroku-postgres
    

起動しているコンテナで bash を実行して worker ユーザーになります。

$ docker exec -it test-heroku-postgres /bin/bash
# su - worker
$ cd Desktop/

以下、コンテナの中の bash で実行しています。

Flask アプリのスクリプトをダウンロード

Postgres に接続するテスト用の Python Flask スクリプトを Gist (https://gist.github.com/nezuppo/c36bbb4c1f81da348ba74e8ccf3001bf) に貼り付けています。

Gist からこのスクリプトをダウンロード

$ wget https://gist.github.com/nezuppo/c36bbb4c1f81da348ba74e8ccf3001bf/archive/e1df36533ba102fd1494e06d23398f6f3d7b891e.zip
  • 別途新規で git init をする必要があるため、git clone ではなく wget で zip ファイルをダウンロードしています。

ダウンロードした zip を解凍してディレクトリに cd

$ unzip e1df36533ba102fd1494e06d23398f6f3d7b891e.zip
$ cd c36bbb4c1f81da348ba74e8ccf3001bf-e1df36533ba102fd1494e06d23398f6f3d7b891e/

ローカル開発環境で Flask アプリからローカルの Postgres に接続

localdb データベース作成

$ createdb -U postgres localdb

postgres ユーザーで localdb データベースを指定して psql 起動

$ psql -U postgres localdb

psql 内で testtable を作成してデータを挿入

localdb=# create table testtable(aaa text);
localdb=# insert into testtable values('test local');
localdb=# select * from testtable;
    aaa
------------
 test local
(1 row)

localdb=# exit

Python の必要なパッケージをインストール

$ sudo pip install -r requirements.txt

環境変数 DATABASE_URL で Postgres への接続情報を指定して heroku ローカル開発環境を起動

$ DATABASE_URL=postgresql://postgres@localhost/localdb \
  heroku local web

ブラウザで http://localhost:5000/ を開くと Flask アプリが Postgres に接続し、インサートされた「test local」をひっぱってきてブラウザに表示されます。

終了は ctrl-c です。

ネット上の Heroku サイトの Flask アプリから Heroku 上の Postgres に接続

git の初期設定

$ git config --global user.email "worker@localdomain"
$ git config --global user.name "worker"

git を初期化

$ git init

.gitignore 作成

$ echo "__pycache__/" > .gitignore

ファイルを git に追加して commit

$ git add .
$ git commit -m "first commit"

Heroku にログイン

$ heroku login

Heroku アプリを作成

$ heroku create

Heroku Postgres をアドオン

$ heroku addons:create heroku-postgresql:hobby-dev
  • 最初に git init しているので --app で Heroku アプリ名を指定する必要はありません

Heroku のアプリに Postgres がアドオンされたことを確認

$ heroku addons

Heroku Postges への接続 URL の確認

$ heroku config

Heroku Postgres に関する情報の確認

$ heroku pg

Heroku アプリにアドオンした Postgres (ローカルではなくリモート) に psql で接続

$ heroku pg:psql

psql 内で testtable を作成してデータを挿入

DATABASE=> create table testtable(aaa text);
DATABASE=> insert into testtable values('test remote');
DATABASE=> select * from testtable;
     aaa
-------------
 test remote
(1 row)

DATABASE=> exit

リモートが Heroku であることを確認

$ git remote
heroku

リモートに git push して Heroku アプリを初期化

$ git push heroku master

ブラウザでアクセスする URL を確認

$ heroku open
  • 本来はブラウザを立ち上げるためのコマンドだが、Docker コンテナが CUI 環境のためブラウザが起動されず URL が表示されるので、これをブラウザにコピペして開く
  • Postgres にインサートした ‘test remote’ が表示される

2022年1月17日月曜日

Docker の Ubuntu でよく使うコマンドやツールが一通りインストールされたイメージを作成

WSL の Ubuntu には sudo、wget、byobu 等の普段良く使うツールがあらかじめインストールされていますが、Docker のデフォルトの Ubuntu イメージは最小限必要な構成になっているため、これらはインストールされていません。

必要なパッケージをひとつひとつインストールするように dockerfile を書くのも大変なので、まとめてインストールできるよう調べてみました。

環境

  • Docker の Ubuntu イメージ: ubuntu:20.04

結論

いろいろ検討した結果、dockerfile は以下の通りです。

$ cat dockerfile
FROM ubuntu:20.04

RUN apt update && apt -y upgrade
RUN apt update && apt -y upgrade

ARG DEBIAN_FRONTEND=noninteractive
RUN apt install -y ubuntu-minimal
RUN apt install -y ubuntu-standard
RUN apt install -y ubuntu-server

RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*

以下、この dockerfile を作成するために検討した内容です。

WSL のパッケージを確認

上述の通り、WSL の Ubuntu には sudo、wget、byobu 等の普段良く使うツールがインストールされているので、これらを依存関係として持っている大元のパッケージを確認します。以下、WSL にインストールした Ubuntu で実施しています。

まずはインストールされているパッケージ一覧を取得するため、`dpkg -l’ の出力を確認

$ dpkg -l
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name                           Version                               Architecture Description
+++-==============================-=====================================-============-=================================================================>
ii  accountsservice                0.6.55-0ubuntu12~20.04.4              amd64        query and manipulate user account information
ii  adduser                        3.118ubuntu2                          all          add and remove users and groups
ii  alsa-topology-conf             1.2.2-1                               all          ALSA topology configuration files
... (省略) ...

というように、最初の 5行目まではヘッダ情報で、6行目以降の「ii」に続く 2項目がインストールされているパッケージのようなので、これを抜き取りパッケージ一覧を作ります。

$ dpkg -l | tail -n +6 | awk '{print $2}'
accountsservice
adduser
alsa-topology-conf
... (省略) ...

これらすべてのパッケージの依存関係一覧を取得します。

$ dpkg -l | tail -n +6 | awk '{print $2}'| xargs -I{} apt depends {} 2>/dev/null | less

出力の中から、sudo、wget、byobu 等に依存関係を持っているパッケージを見てみると、ubuntu-minimal、ubuntu-standard、ubuntu-server パッケージによく使うコマンドやツールが一通り依存関係に含まれていることがわかりました。

Docker の Ubuntu イメージに ubuntu-minimal、ubuntu-standard、ubuntu-server パッケージをインストールしようとすると予期しないエラーその 1が発生

本題とはそれますが、予期しないエラーが出ました。

まず、ubuntu-minimal、ubuntu-standard、ubuntu-server パッケージをインストールする dockerfile は以下のようににしてみました。

$ cat dockerfile
FROM ubuntu:20.04

RUN apt update && apt -y upgrade

RUN apt install -y ubuntu-minimal
RUN apt install -y ubuntu-standard
RUN apt install -y ubuntu-server

この docker ファイルでイメージを build すると、予期しないエラーが出ました。

$ docker build --tag test-with-tools:1 ./
... (省略) ...
#5 144.7 E: Failed to fetch http://archive.ubuntu.com/ubuntu/pool/main/s/systemd/systemd-timesyncd_245.4-4ubuntu3.14_amd64.deb  404  Not Found [IP: 91.189.88.142 80]
... (省略) ...
executor failed running [/bin/sh -c apt install -y ubuntu-minimal]: exit code: 100

例えば、「http://archive.ubuntu.com/ubuntu/pool/main/s/systemd/systemd-timesyncd_245.4-4ubuntu3.14_amd64.deb」がダウンロード出来ないとのことです。

ブラウザでこの URL を開いてみると「Not Found」と出ました。サーバーは動いているが、指定したファイルが存在しないようです。

そこで、「systemd-timesyncd_245.4-4ubuntu3.14_amd64.deb」が置かれているディレクトリ「http://archive.ubuntu.com/ubuntu/pool/main/s/systemd/」をブラウザで開いてみると

systemd-timesyncd_245.4-4ubuntu3.15_amd64.deb」は置かれているが
systemd-timesyncd_245.4-4ubuntu3.14_amd64.deb」は置かれていないことがわかります。

古いバージョンの「systemd-timesyncd_245.4-4ubuntu3.14_amd64.deb」をダウンロードしようとしてエラーになっているようです。

dockerfile を見てみると「RUN apt update && apt -y upgrade」というように古いリポジトリ情報で apt update し、その後 apt upgrade しているので、このタイミングでリポジトリ情報が新しいものに更新されていると想定されます。なので、もう一度 update すれば良いのではないかと思われます。(ついでに upgrade も実施)

この時点で dockerfile は以下のようになってます。

$ cat dockerfile
FROM ubuntu:20.04

RUN apt update && apt -y upgrade
RUN apt update && apt -y upgrade

RUN apt install -y ubuntu-minimal
RUN apt install -y ubuntu-standard
RUN apt install -y ubuntu-server

無事 Docker イメージのビルドが進みました。(が、以下の予期しないエラーその 2が発生しました)

Docker の Ubuntu イメージに ubuntu-minimal、ubuntu-standard、ubuntu-server パッケージをインストールしようとすると予期しないエラーその 2が発生

以下のエラーです。

#6 242.1 Errors were encountered while processing:
#6 242.1  console-setup
#6 242.1  ubuntu-minimal
#6 242.1 E: Sub-process /usr/bin/dpkg returned an error code (1)
------
executor failed running [/bin/sh -c apt install -y ubuntu-minimal]: exit code: 100

ubuntu-minimal と依存関係がある console-setup をインストールしうようとしていてエラーになっているっぽいです。

更にメッセージを見てみると、

#6 241.9 If you don't use a framebuffer, the choices that start with "." will reduce the
#6 241.9 number of available colors on the console.
#6 241.9
#6 241.9   1. . Arabic
#6 241.9   2. # Armenian
#6 241.9   3. # Cyrillic - KOI8-R and KOI8-U
... (省略) ...
#6 241.9   21. . Combined - Latin; Slavic Cyrillic; Greek
#6 241.9   22. . Combined - Latin; Slavic and non-Slavic Cyrillic
#6 241.9   23. Guess optimal character set
#6 241.9
#6 241.9 Character set to support:

と出ているので、コンソールから入力を求められているが、スクリプトでの処理のため入力できずエラーとなったようです。

ググると環境変数で「DEBIAN_FRONTEND=noninteractive」を指定すると良いようなので dockerfile は以下のようにしました。

コンテナの中に環境変数 DEBIAN_FRONTEND を残したくないので「ENV DEBIAN_FRONTEND=noninteractive」ではなく、「ARG DEBIAN_FRONTEND=noninteractive」としてます。

$ cat dockerfile
FROM ubuntu:20.04

RUN apt update && apt -y upgrade
RUN apt update && apt -y upgrade

ARG DEBIAN_FRONTEND=noninteractive
RUN apt install -y ubuntu-minimal
RUN apt install -y ubuntu-standard
RUN apt install -y ubuntu-server

apt インストールのキャッシュを削除してイメージのサイズを少しでも減らす

ググると apt install した時のキャッシュを削除してイメージのサイズを小さくすると良いとのことなので dockerfile に追加しました。

$ cat dockerfile
FROM ubuntu:20.04

RUN apt update && apt -y upgrade
RUN apt update && apt -y upgrade

ARG DEBIAN_FRONTEND=noninteractive
RUN apt install -y ubuntu-minimal
RUN apt install -y ubuntu-standard
RUN apt install -y ubuntu-server

RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*

参考

2022年1月10日月曜日

WSL Ubuntu で Heroku with Python

Ubuntu への Heroku CLI のインストール、サンプル Python アプリのデプロイ、アプリ開発のためのローカル Heroku 環境の構築等は Heroku サイトのドキュメント「Getting Started on Heroku with Python」を参考にできますが、WSL Ubuntu に適用しようとするとこの通りに進めるだけではうまくいかなかったので、Heroku のドキュメントを実施するにあたり別途必要なことをここにまとめておきます。

環境

  • Windows11 Home
  • WSL2 Ubuntu 20.04.3

pip の代わりに pipenv を使うための準備

Heroku のドキュメントではローカル開発環境でのサンプルアプリ実行に必要な Python パッケージのインストールに pip を使っています。pip でも問題なく動作するかと思われますが、Python パッケージ環境を肥大化させたくないので pip の代わりに今回は pipenv を使います。

Heroku のドキュメントに書かれたことを実施する前にまずは以下の手順で pipenv 環境の準備をします。

pipenv をインストール

$ sudo apt install pipenv

作業用ディレクトリを作成して移動

$ mkdir /some/where/work
$ cd /some/where/work/

pipenv プロジェクトを作成し pipenv shell を起動

$ pipenv --python $(which python3)
$ pipenv shell

以降、全て pipenv shell で実施

Heroku CLI のインストールに snap を使えない

Ubuntu への Heroku CLI のインストールは「$ sudo snap install heroku --classic」を実行するとのことですが、WSL では snap を使うことは難しいようなのでインストール用のシェルスクリプトをダウンロードして実行。

$ wget https://cli-assets.heroku.com/install-ubuntu.sh
$ chmod +x install-ubuntu.sh
$ ./install-ubuntu.sh

アプリ開発のためのローカル Heroku 環境の構築に pipenv を使う

サンプルアプリに必要なパッケージをインストール。これを実施しないと、後述の pipenv install でエラーが出ます。

$ sudo apt install libpq-dev

このサンプルアプリを実行するために必要な python パッケージは requirements.txt で定義されています。

$ cat requirements.txt
django
gunicorn
django-heroku

ローカルにこれらの Python パッケージをインストール。今回は前述の通り pip の代わりに pipenv を使っています。

$ pipenv install -r requirements.txt

ローカル環境にアプリの変更を適用する時も pip ではなく pipenv

ドキュメントではローカル環境に Python の request パッケージをインストールするのに $ pip install requests としていますがこれも pipenv でやります。

$ pipenv install requests