- 投稿日:2020-12-07T23:55:37+09:00
【Docker】nginx起動/停止/ログ【備忘録】
絶対忘れるので、備忘録
インストールまでの流れは下記参照のこと
macOS High SierraにDockerをインストールする。【旧バージョン】
[zsh]補完機能を有効化する[Docker]Docker Version
yutakaf@mi ~/Desktop/git docker version Client: Docker Engine - Community Azure integration 0.1.15 Version: 19.03.12 API version: 1.40 Go version: go1.13.10 Git commit: 48a66213fe Built: Mon Jun 22 15:41:33 2020 OS/Arch: darwin/amd64 Experimental: false Server: Docker Engine - Community Engine: Version: 19.03.12 API version: 1.40 (minimum version 1.12) Go version: go1.13.10 Git commit: 48a66213fe Built: Mon Jun 22 15:49:27 2020 OS/Arch: linux/amd64 Experimental: false containerd: Version: v1.2.13 GitCommit: 7ad184331fa3e55e52b890ea95e65ba581ae3429 runc: Version: 1.0.0-rc10 GitCommit: dc9208a3303feef5b3839f4323d9beb36df0a9dd docker-init: Version: 0.18.0 GitCommit: fec3683Docker でnginxを起動
--publish , -p コンテナのポートをホストに公開します
docker run✘ yutakaf@mi ~/Desktop/git docker container run --publish 80:80 nginx Unable to find image 'nginx:latest' locally latest: Pulling from library/nginx 852e50cd189d: Pull complete 571d7e852307: Pull complete addb10abd9cb: Pull complete d20aa7ccdb77: Pull complete 8b03f1e11359: Pull complete Digest: sha256:6b1daa9462046581ac15be20277a7c75476283f969cb3a61c8725ec38d3b01c3 Status: Downloaded newer image for nginx:latest /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh 10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf 10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh /docker-entrypoint.sh: Configuration complete; ready for start up初回はダウンロードから始まり、localhostへアクセスすると
下記のようにアクセスログが発生する。172.17.0.1 - - [07/Dec/2020:08:13:01 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36" "-" 2020/12/07 08:13:01 [error] 28#28: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "localhost", referrer: "http://localhost/" 172.17.0.1 - - [07/Dec/2020:08:13:01 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://localhost/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36" "-"バックグラウンド実行 --detach
--detach , -d コンテナをバックグラウンドで実行し、コンテナIDを出力します
docker container runyutakaf@mi ~/Desktop/git docker container run --publish 80:80 --detach nginx 1920f9a96bb38d5cefe54f81dddb40ccc918dff9b7a7dea40c132fa986762ea6コンテナの一覧を表示 ls
yutakaf@mi ~/Desktop/git docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1920f9a96bb3 nginx "/docker-entrypoint.…" 6 minutes ago Up 6 minutes 0.0.0.0:80->80/tcp cranky_mooreコンテナを指定して停止
yutakaf@mi ~/Desktop/git docker container stop 1920f9a96bb3 1920f9a96bb3yutakaf@mi ~/Desktop/git docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1920f9a96bb3 nginx "/docker-entrypoint.…" 8 minutes ago Exited (0) 40 seconds ago cranky_mooreSTATUSがExitedになっていることがわかる。
コンテナに名前をつける
--name コンテナに名前を割り当てます
docker container runyutakaf@mi ~/Desktop/git docker container run --publish 80:80 --detach --name webhost nginx 4ee13d2807b491d3d8306bb0b3e43003b7e6916fc39d87e281c671e688faa595yutakaf@mi ~/Desktop/git docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4ee13d2807b4 nginx "/docker-entrypoint.…" 5 seconds ago Up 5 seconds 0.0.0.0:80->80/tcp webhostNAMESにwebhostと名前がついた。
コンテナのログを取得する。
yutakaf@mi ~/Desktop/git docker container logs webhost /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh 10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf 10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh /docker-entrypoint.sh: Configuration complete; ready for start up 172.17.0.1 - - [07/Dec/2020:12:08:06 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36" "-"IDで指定もできる。--followもしくは-fで後追いもできる。
yutakaf@mi ~/Desktop/git docker container logs 4e -f
コンテナ消去
yutakaf@mi ~/Desktop/git docker container ls -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4ee13d2807b4 nginx "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:80->80/tcp webhost 1920f9a96bb3 nginx "/docker-entrypoint.…" 14 minutes ago Exited (0) 6 minutes ago cranky_moore e24d37aaf015 nginx "/docker-entrypoint.…" 4 hours ago Exited (0) 14 minutes ago pedantic_liskov 65b36297ac90 210daa099678 "docker-entrypoint.s…" 4 hours ago Exited (1) 4 hours ago vigorous_goodall 715fd822bd82 docker101tutorial "/docker-entrypoint.…" 2 days ago Exited (0) 2 days ago docker-tutorial yutakaf@mi ~/Desktop/git docker container rm 192 e24 65b 715 192 e24 65b 715 yutakaf@mi ~/Desktop/git docker container ls -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4ee13d2807b4 nginx "/docker-entrypoint.…" 3 minutes ago Up 3 minutes 0.0.0.0:80->80/tcp webhost yutakaf@mi ~/Desktop/git 一度に指定できる。
起動中は消去できない。-fで強制消込はできる。yutakaf@mi ~/Desktop/git docker container rm ba Error response from daemon: You cannot remove a running container ba0ef64dd1bc94888772847bcd9419e565950c9d9af3f594f18838aa20e7b124. Stop the container before attempting removal or force remove一通り使えるようになったらチートシートを作ろうと思う。
- 投稿日:2020-12-07T23:50:03+09:00
[Docker]最も簡単なWordpressのHTTPS化(Wordpress+https-portal+Docker+Docker-compose)
目標
- ローカル環境のWordpressにHTTPSでアクセスできること
前提
- Dockerインストール済
- Wordpressに触れたことがある
実行環境
- CPU
- Intel(R) Core(TM) i5-6400 CPU
- OS
- Windows 10 pro
- バージョン 1909
- アプリケーション
- Docker
- バージョン 19.03.13
- 使用するDocker Image
- mysql: 5.7
- wordpress: latest
- https-portal: 1
手順
こちらよりdocker-composeの内容を転記
version: '3.3' services: db: image: mysql:5.7 volumes: - db_data:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: somewordpress MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress wordpress: depends_on: - db image: wordpress:latest ports: - "8000:80" restart: always environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress WORDPRESS_DB_NAME: wordpress volumes: db_data: {}
docker-compose up
で起動することを確認http://localhost:8000 へアクセス
言語設定やユーザー設定などの初期登録を済ませる
https-portal
を追記version: '3.3' services: https-portal: image: steveltn/https-portal:1 ports: - '443:443' environment: STAGE: local DOMAINS: 'localhost -> http://host.docker.internal:443' db: image: mysql:5.7 ...
docker-compose up
で起動することを確認https://localhost へアクセス
総評
WIP
- 投稿日:2020-12-07T23:50:03+09:00
[WIP]最も簡単なWordpressのHTTPS化(Wordpress+https-portal+Docker+Docker-compose)
目標
- ローカル環境のWordpressにHTTPSでアクセスできること
前提
- Dockerインストール済
- Wordpressに触れたことがある
実行環境
- CPU
- Intel(R) Core(TM) i5-6400 CPU
- OS
- Windows 10 pro
- バージョン 1909
- アプリケーション
- Docker
- バージョン 19.03.13
- 使用するDocker Image
- mysql: 5.7
- wordpress: latest
- https-portal: 1
手順
こちらよりdocker-composeの内容を転記
version: '3.3' services: db: image: mysql:5.7 volumes: - db_data:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: somewordpress MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress wordpress: depends_on: - db image: wordpress:latest ports: - "8000:80" restart: always environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress WORDPRESS_DB_NAME: wordpress volumes: db_data: {}
docker-compose up
で起動することを確認http://localhost:8000 へアクセス
言語設定やユーザー設定などの初期登録を済ませる
https-portal
を追記version: '3.3' services: https-portal: image: steveltn/https-portal:1 ports: - '443:443' environment: STAGE: local DOMAINS: 'localhost -> http://host.docker.internal:443' db: image: mysql:5.7 ...
docker-compose up
で起動することを確認https://localhost へアクセス
総評
WIP
- 投稿日:2020-12-07T23:49:23+09:00
PHPコンテナのDockerファイルでCMDを書くとnginxコンテナと接続できなくなった
Laravel、cron、nginx、nodeの環境を作ったら詰みかけた。
先に結論
php:7.4-fpm-alpine
をベースイメージにして、CMD
を書くと
php-fpm
が起動されなくなる作ったものはこちら
https://github.com/natsume0718/Docker_Laravelなにがおきたか
cronとphpを同一コンテナにいれたものと、nginxを接続しようとしたらできなかった。
nginx側で
[emerg] host not found in upstream “app:9000”
と出てた。1.alpineでphp-fpmのDockerファイルをこんな感じで作った
Laravelが動くようにしたぐらいで変哲はない
FROM php:7.4-fpm-alpine # 基本的なあれこれとgd,node,npm RUN apk upgrade --update && \ apk --no-cache --update add \ icu-dev autoconf make g++ gcc bash git zip unzip vim\ coreutils \ freetype-dev \ libjpeg-turbo-dev \ libltdl \ libmcrypt-dev \ libpng-dev \ oniguruma-dev \ nodejs npm # php extension RUN docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-install -j$(nproc) gd \ bcmath opcache sockets pdo_mysql # install Composer RUN curl -sS https://getcomposer.org/installer | php && \ mv composer.phar /usr/local/bin/composer && \ chmod +x /usr/local/bin/composer # php ini COPY ./php.ini /usr/local/etc/php/php.ini2.nginxはベースイメージをそのままつかって.
default.conf
だけいじったservices: nginx: image: nginx:stable-alpine container_name: nginx ports: - ${APP_PORT:-80}:80 volumes: - ./src:/var/www/html:cached - ./nginx/default.conf:/etc/nginx/conf.d/default.conf depends_on: - app networks: - laravel app: container_name: app build: context: ./app dockerfile: Dockerfile volumes: - ./src:/var/www/html:cached environment: TZ: "Asia/Tokyo" networks: - laravel下記の様にphpのコンテナのポート指定して接続していた
default.conf# ~省略~ location ~ \.php$ { fastcgi_pass app:9000; # php-fpm側のコンテナ:9000 fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; } # ~省略~この時点まではnginxは問題なくPHPを表示できていました。
cronを入れたとたんうごかなくなるnginx
cronいれるというか最初から入ってるので、cronで動かす設定ファイルコピーして内容書き込み、最後に起動しているだけです。
FROM php:7.4-fpm-alpine # 基本的なあれこれとgd,node,npm,supervisor RUN apk upgrade --update && \ apk --no-cache --update add \ icu-dev autoconf make g++ gcc bash git zip unzip vim\ coreutils \ freetype-dev \ libjpeg-turbo-dev \ libltdl \ libmcrypt-dev \ libpng-dev \ oniguruma-dev \ nodejs npm # php extension RUN docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-install -j$(nproc) gd \ bcmath opcache sockets pdo_mysql # install Composer RUN curl -sS https://getcomposer.org/installer | php && \ mv composer.phar /usr/local/bin/composer && \ chmod +x /usr/local/bin/composer # php ini COPY ./php.ini /usr/local/etc/php/php.ini # cronファイルコピーして、内容を書き込む COPY ./laravel-crontab /var/spool/cron/crontabs/ RUN cat /var/spool/cron/crontabs/laravel-crontab >> /var/spool/cron/crontabs/root CMD ["/usr/sbin/crond"]が、nginxでPHPが動作しているページを開くと
502 Bad Gateway
エラーログを調べると
[emerg] host not found in upstream “app:9000”
原因を探す
ググってると似たような状態にいる人を発見する
cronを追加した途端動かなくなったとのこと。
https://stackoverflow.com/questions/62752220/laravel-docker-cron-nginx-502-bad-gateway-issue-111-connection-refused-while
CMD cron && docker-php-entrypoint php-fpm
で解決しましたとある。
docker-php-entrypoint
コマンドは何者...?という疑問になり調べると下記のようなのが見つかり、もともと知らぬ間に
docker-php-entrypoint php-fpm
というのを実行していたとのこと。
https://qiita.com/shim-hiko/items/653059fab63af962a21fんでこのコマンドはphp-fpmをデーモン化してくれていたらしい...
どう対処したか
supervisor入れて、cronとphpをデーモン化して、CMDではsupervisorの起動をするようにしました
色々対応方法あるっぽいけど、Laravelのキューでsupervisor使うのでまあ良いかって感じですもっと良い対処法あれば知りたいです
[supervisord] nodaemon=true logfile=/var/log/jobschedule/supervisord.log pidfile=/var/log/jobschedule/supervisord.pid [program:php-fmp] command = /usr/local/bin/docker-php-entrypoint php-fpm -D autostart = true [program:laravel-worker] process_name=%(program_name)s_%(process_num)02d command=php /var/www/html/artisan queue:work database --tries=1 --sleep=3 autostart=true autorestart=true user=root numprocs=8 redirect_stderr=true stdout_logfile=/var/log/jobschedule/worker.log stopwaitsecs=3600 [program:crond] command = /usr/sbin/crond user = root autostart = true stdout_logfile=/var/log/jobschedule/cron.log
- 投稿日:2020-12-07T22:11:08+09:00
Go言語をインストールしない Go 言語開発環境
Go言語(go, golang)をインストールすることなく、Go言語の開発環境を用意する方法です。
簡単に言うと、使い捨ての Docker コンテナ上で go コマンドを実行します。
golang インストールは不要ですが、基本的にGOPATH=~/go
以下にプロジェクトのソースディレクトリがある事を前提とします。
※ Docker を利用するので Docker のインストールは必要です。動作確認環境: macOS Catalina 10.15.8
Atom + go-plus で開発していますが、保存時のテスト実行等も問題なく動作しているようです。
サンプルは
go mod init ${PWD:$(echo “$GPATH/src”|wc -c)}
を実行する例です。プライベートリポジトリなしの場合
docker run --rm -v "$PWD":"${PWD:$#HOME}" -w "${PWD:$#HOME}" golang:1.14.12 go mod init ${PWD:$(echo “$GPATH/src”|wc -c)}プライベートリポジトリを含む場合
プライベートリポジトリが
https://privatte-repo
の場合に対応しています。docker run —rm -v "$HOME/.ssh":"/root/.ssh":ro -v “$PWD”:”${PWD:$#HOME}” -w “${PWD:$#HOME}” --env GOPRIVATE="private-repo" golang:1.14.12 /bin/sh -c "git config --global url.ssh://git@private-repo.insteadOf https://private-repo && go mod init ${PWD:$(echo “$GPATH/src”|wc -c)}"
git config
で特定のリポジトリに対してgo get
を ssh 接続に切り替えるため、コンテナでgo
コマンドを含む複数コマンド実行が必要なので、/bin/sh
の-c
オプションを利用しています。エイリアスを登録して利用
1行で書けますが非常に長いので、実際に利用する時は関数を用意してエイリアスを登録して利用しています。
~/.zshrcalias go='golang' alias goinit='golang mod init ${PWD:$(echo “$GPATH/src”|wc -c)}"' golang() { USE_PRIVATE_REPO="git config --global url.ssh://git@private-repo.insteadOf https://private-repo" GOPRIVATE="private-repo" WORKDIR=$(echo "${PWD}" | awk '{print substr($0, index($0, "/go/"))}') docker run --rm \ -v "${HOME}/.ssh":"/root/.ssh":ro \ -v "${PWD}":"${WORKDIR}" \ -w "${WORKDIR}" \ -e GOPRIVATE \ golang:1.14.12 /bin/sh -c "${USE_PRIVATE_REPO} && go $*" }source ~/.zshrc
- 投稿日:2020-12-07T22:11:08+09:00
Go言語をインストールしない Go言語開発環境構築
Go言語(go, golang)をインストールすることなく、Go言語の開発環境を構築する方法です。
簡単に言うと、使い捨ての Docker コンテナ上で go コマンドを実行します。
golang インストールは不要ですが、基本的にGOPATH=~/go
以下にプロジェクトのソースディレクトリがある事を前提とします。
※ Docker を利用するので Docker のインストールは必要です。動作確認環境: macOS Catalina 10.15.8
Atom + go-plus で開発していますが、保存時のテスト実行等も問題なく動作しているようです。
サンプルは
go mod init ${PWD:$(echo “$GPATH/src”|wc -c)}
を実行する例です。プライベートリポジトリなしの場合
docker run --rm -v "$PWD":"${PWD:$#HOME}" -w "${PWD:$#HOME}" golang:1.14.12 go mod init ${PWD:$(echo “$GPATH/src”|wc -c)}プライベートリポジトリを含む場合
プライベートリポジトリが
https://privatte-repo
の場合に対応しています。docker run —rm -v "$HOME/.ssh":"/root/.ssh":ro -v “$PWD”:”${PWD:$#HOME}” -w “${PWD:$#HOME}” --env GOPRIVATE="private-repo" golang:1.14.12 /bin/sh -c "git config --global url.ssh://git@private-repo.insteadOf https://private-repo && go mod init ${PWD:$(echo “$GPATH/src”|wc -c)}"
git config
で特定のリポジトリに対してgo get
を ssh 接続に切り替えるため、コンテナでgo
コマンドを含む複数コマンド実行が必要なので、/bin/sh
の-c
オプションを利用しています。エイリアスを登録して利用
1行で書けますが非常に長いので、実際に利用する時は関数を用意してエイリアスを登録して利用しています。
~/.zshrcalias go='golang' alias goinit='golang mod init ${PWD:$(echo “$GPATH/src”|wc -c)}"' golang() { USE_PRIVATE_REPO="git config --global url.ssh://git@private-repo.insteadOf https://private-repo" GOPRIVATE="private-repo" WORKDIR=$(echo "${PWD}" | awk '{print substr($0, index($0, "/go/"))}') docker run --rm \ -v "${HOME}/.ssh":"/root/.ssh":ro \ -v "${PWD}":"${WORKDIR}" \ -w "${WORKDIR}" \ -e GOPRIVATE \ golang:1.14.12 /bin/sh -c "${USE_PRIVATE_REPO} && go $*" }source ~/.zshrc
- 投稿日:2020-12-07T20:47:14+09:00
Djangoでユニットテストを書く方法
こんにちは Masuyama です。
今回は Django でユニットテストを書く方法をお伝えしたいと思います。
Django の環境構築から丁寧に解説し、Django を使ったことがない人でもユニットテストを書けるようになるはずです。ここでは記事を投稿するようなブログアプリをベースとして、ユニットテストを書く方法をお伝えしていきます。
テスト用 Django アプリの作成
まずは土台となるアプリケーションを作成していきます。
環境構築
作業用のディレクトリを作成します。
ここでの名前はなんでもよいですが、とりあえず blog としておきます。mkdir blog cd blogpipenv のインストール
ベースとなる環境は汚さない方が他のモジュールなどの影響を排除できるため、pipenv を使って仮想環境を構築しましょう。
pip install pipenv # 環境によっては直接 pip を使えない場合もあるので、python3 -m pip install pipenv でインストールしますインストールできたら、作業用フォルダの中で下記のコマンドを実行します。
pipenv shell実行後、仮想環境に入ることが出来るとディレクトリ名に従った文字列がコマンドラインの冒頭に表示されるようになります。
(blog)bash-3.2$Django をインストール
直接 pipenv install django とでも実行して Django をインストールしてもよいですが、
後々 Docker を使うことを考えて、requirements.txt というファイルを作業用ディレクトリ直下に作成して
必要となるモジュールを記述していくことにします。いまは Django だけが必要なので、バージョン情報とともに以下のように記載します。
requirements.txtDjango==3.1.0※今回は Django 3.1.0 で作っていきますが Django 2.2 あたりでも問題なく動くとは思います
モジュールのインストール
下記のコマンドを実行することで、requirements.txt に基づきモジュールをインストールします。
ここでは Django だけがインストールされるはずです。pipenv install -r requirements.txtDjango プロジェクトの作成
blog ディレクトリ直下で実行
django-admin startproject mysite親プロジェクト直下でのファイル構成はこのようになっているかと思います。
├── Pipfile ├── Pipfile.lock ├── mysite │ ├── manage.py │ └── mysite │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── requirements.txtDjango サーバのテスト起動
開発用サーバを起動する機能がついています。
(Ruby on Rails をやったことがある人は rails -s というと分かりやすいかと思います)これを実行するには manage.py ファイルが置いてあるディレクトリに移動する方が便利なので、mysite/mysite ディレクトリに移動してから実行します
cd mysite python3 manage.py runserver正常に実行できると下記のような出力が出ます。
December 19, 2020 - 21:30:23 Django version 3.1, using settings 'mysite.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.なお、上記メッセージの前段でこのようなメッセージが表示されますが
この部分は migrate というデータベースへの統合処理が済んでいないために出力されるメッセージですが、いまのところ気にしないで大丈夫です。You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. Run 'python manage.py migrate' to apply them.さて、これで Django の開発用サーバが起動したのでブラウザから確認します。
Chrome などのブラウザのアドレスバーに127.0.0.1:8000と入力して Enter を押下します。これがすべての始まりです。この画面が出ていれば、最初のステップは完了しています!おめでとうございます。
プロジェクトの設定
さて、先ほどテストサーバへアクセスした時に英語での表記になっていましたね。
こういった設定は mysite/settings.py で設定することができ、デフォルトでは英語圏に合わせた言語表示、およびタイムゾーンになっています。mysite/settings.py(before)LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC'これを次のように変更してあげます。
mysite/settings.py(after)LANGUAGE_CODE = 'ja' TIME_ZONE = 'Asia/Tokyo'もう一度テストサーバへアクセスすると、日本語表記になっていることが分かるかと思います。
(また、この画面からでは確認できませんがタイムゾーンも東京設定になっています)Django アプリの作成
先程 start-project コマンドでプロジェクトを作成しましたが、次はアプリケーションを作っていきます。
混乱しがちですが、ベースとなるプロジェクトと個別のアプリは別物です。公式ページではこのような説明です。
プロジェクトとアプリの違いは何でしょうか? アプリとは、ウェブログシステム、公的記録のデータベース、小規模な投票アプリなど、何かを行う Web アプリケーションです。プロジェクトは、特定のウェブサイトの構成とアプリのコレクションです。プロジェクトには複数のアプリを含めることができます。 アプリは複数のプロジェクトに存在できます。
では、blog アプリを作っていきましょう。mysite プロジェクトリ (manage.py ファイルがある場所) の直下で下記コマンドを実行します。
python3 manage.py startapp blog現在のディレクトリ構成はこのようになっています。
. ├── db.sqlite3 ├── manage.py ├── mysite │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── blog ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py └── views.pyblog 配下に様々なファイルが作成されていることが分かるかと思います。
ここで、このアプリが作成されたことをプロジェクトに教えてあげる必要があります。
mysite/setting.py の中に "INSTALLED_APPS" という欄がありますので、その中で blog アプリの存在を教えてあげましょう。
mysite/settings.pyINSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog.apps.BlogConfig', # ここを追加 ]template, view, url の設定
まずは template を変更していきます。
template は見た目を作るための部分となっています。
mysite プロジェクト直下に templates フォルダ、その下に blogフォルダ、さらにその下に index.html を作成します。
. ├── blog │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── db.sqlite3 ├── manage.py ├── mysite │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── templates └── blog └── index.html ←ここに作成index.html の中身はとりあえず適当で大丈夫です。
index.html<h1>Hello, from Django!</h1>また、templates フォルダをどこに作ったかをプロジェクトに教えて上げる必要があります。
INSTALLED_APPS を設定したときと同様に、settings.py に下記の記述を入れます。settings.py... import os # 追加 ... TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], # 修正 'APP_DIRS': True,次に views.py を修正していきます。
先ほど作った template である index.html を呼び出します。blog/views.pyfrom django.views.generic import TemplateView class IndexView(TemplateView): template_name = 'blog/index.html'URL の設計
次に、blog アプリ専用のルーティング設定を作成します。
ルーティング設定は「urls.py」というファイルで設定していきます。まずはじめに説明をしますと、プロジェクト全体のルーティングを司る urls.py と、アプリ内の urls.py それぞれでルーティングを設定します。
最初に mysite 直下の urls.py から編集します。
mysite/urls.pyfrom django.contrib import admin from django.urls import include, path urlpatterns = [ path('blog/', include('blog.urls')), path('admin/', admin.site.urls), ]後に作成する blog アプリ用の urls を、urlpatterns 内で読み込むことになります。
では mysite 直下にのみ urls.py が作成されていますが、blog 直下にも自分で「urls.py」を作成します。
エディタでもよいですし、アプリの blog ディレクトリ内で下記コマンドを実行してもよいでしょう。/blogtouch urls.pyblog 配下はこのようなファイル構成です。
. ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views.py作成した urls.py の中身はこのように変更することで、先程 views.py で作成した関数(=index.htmlを呼び出す処理)のルーティングを設定します。
blog/urls.pyfrom django.urls import path from . import views app_name = 'blog' urlpatterns = [ path('', views.IndexView.as_view(), name='index'), ]ちなみに name='index' を設定しておくことで「blog:index」という名前を使ってこの url を逆引きで呼び出すことができるようになります。
この時点で index.html を呼び出せるかの確認を行います。
manage.py が置いてあるディレクトリで runserver を実行します。
python3 manage.py runserver無事に通ったら、今度はブラウザで 127.0.0.1:8000/blog へアクセスします。
これは先程 mysite/urls.py で blog 付きのアドレスでアクセスされたときに、blog アプリ内に記述した内容を動作させるようにしているためです。アクセスし、index.html の中身が表示されれば成功です。
model の準備
実際に記事を登録するための準備をします。
ブログで記事を管理するためには model を作成します。
model はデータベースと Django の橋渡しの役割を持っており、これのおかげで我々は SQL といったデータベース構文を意識することなくデータベースにデータを登録することができます。最初に設定する models.py では、どのようなデータを登録していくのかを定義します。
Excelの表でいうと、表の各カラムのカラム名を定義したり、各カラムに入るデータがどのようなもの(文字列や数値など)を定義するところです。今回はブログアプリであり、記事 (Post) を修正していくので Post モデルを作成します。
タイトル、本文、日付が入ればとりあえず十分です。blog/models.pyfrom django.db import models from django.utils import timezone # django で日付を管理するためのモジュール class Post(models.Model): title = models.CharField('タイトル', max_length=200) text = models.TextField('本文') date = models.DateTimeField('日付', default=timezone.now) def __str__(self): # Post モデルが直接呼び出された時に返す値を定義 return self.title # 記事タイトルを返す次に、データベースに models.py で定義した情報を反映させます。
このままデータベースに対する処理を行うわけではなく、models.py の内容を反映させるためのワンクッションとなるファイルを作成します。
ファイルの作成を自動的に Django にやってもらうことができ、次のコマンドを実行することでファイルが作成されます。python3 manage.py makemigrationsすると /blog/migrations 配下に番号付きのファイルが作成されます。
. ├── blog │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py # これが追加される │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── db.sqlite3 ├── manage.py ├── mysite │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── templates └── blog └── index.htmlDjango アプリ作成の中でこのファイルを直接いじることはありませんが、中身はこのようになっており、これのおかげでカラムの作成などを Django が一気にやってくれます。
0001_initial.py# Generated by Django 3.1 on 2020-10-17 01:13 from django.db import migrations, models import django.utils.timezone class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='Post', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('title', models.CharField(max_length=200, verbose_name='タイトル')), ('text', models.TextField(verbose_name='本文')), ('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='日付')), ], ), ]さて、この migration ファイルを使ってデータベースにテーブルを作成することになりますが
反映もコマンド一発で Django が勝手にやってくれます。
以下のコマンドを実行しましょう。(blog) bash-3.2$ python3 manage.py migrateview の準備
管理サイトで作成した記事の一覧を表示できるようにしていきます。
template 準備(ファイル作成)
最初に template を作成しましょう。
templates/blog 配下に post_list.html を作成します。└── templates └── blog ├── index.html └── post_list.htmlview ファイルの修正
Django ではクラスベース汎用ビュー という仕組みを使うと、簡単に model を引っ張ってきて記事を表示させたり、
テンプレートを表示させたりすることが出来、アプリ作成をグンと効率的に行うことができるようになります。
ちなみに前々回、views.py をいじったときに generic という表記がありました。
これもクラスベース汎用ビューを使う準備として必要な宣言です。view.pyfrom django.views.generic import TemplateView汎用クラスビューには様々な種類があり、その中でも単純に template を表示させるためだけに使われるのが
index.html の表示に使っていた TemplateView です。
index.html の表示のために、呼び出す template を views.py の中で指定していたことになります。view.pyclass IndexView(TemplateView): template_name = 'blog/index.html'また、これまではわかりやすいように generic で宣言してから使うクラスを指定して import していましたが
generic.xxxView の形で呼び出すこともできるので views.py を少し書き換えてあげましょう。views.py(書き換え後)from django.views import generic class IndexView(generic.TemplateView): template_name = 'blog/index.html'これからたくさんのクラスベース汎用ビューを呼び出すことになるので、最初の宣言をスッキリとさせました。
さて、今回は template を単純に表示させるだけでなく、データベースから記事の情報モデルも呼び出してあげる必要があります。
そのため、別の ListView というクラスベース汎用ビューを使うのですが、使い方は TemplateView のときと似ています。最初に使うモデルを宣言し、クラスを記述し、呼び出すモデルを指定してあげるだけです。
views.py(ListViewを追加)from django.views import generic from .models import Post # Postモデルをimport class IndexView(genetic.TemplateView): template_name = 'blog/index.html' class PostListView(generic.ListView): # generic の ListViewクラスを継承 model = Post # 一覧表示させたいモデルを呼び出しmodel = Post という記述を入れてあげることで、記事一覧が post_list という変数でリスト型として template に渡すことができます。
ここで TemplateView を使ったときのことを思い出して「templateを指定してあげるのでは?」と考えた方は鋭いです。
もちろん指定してもよいのですが、実は generic.ListView では template のファイル名をルールに沿った形にしてあげることで、
明示しなくても呼び出してくれる便利機能があります。
(ただし、明示した方が第三者にとって分かりやすいので、敢えて記述する場合もあるかと思います。)ルールとしては「post_list.html」のように、model名を小文字にしたものと、ListViewならば"list"という文字列をアンダースコアで区切った文字列をファイル名にすることです。
(使うクラスによって異なってくるため、後ほど説明します)これで template である post_list.html を表示させ、同時に template に記事一覧を渡すための view の準備が整いました。
template 側で記事一覧の受け取り
post_list.html に渡されたモデルを受け取るには、Django ならではの記述方法があります。
Django の template では {% %} で囲むことで Python コードを記述でき、さらに html としてブラウザに値を表示させるには {{ }} と、中括弧を重ねたもので記述します。
今回、記事一覧は post_list というリスト型で変数が template で渡されているので for ループで展開し、それぞれの記事タイトルと日付を取り出していきます。
(各カラムのデータは、変数名にドット付きでカラム名を指定する形で取り出せます)post_list.html<h1>記事一覧</h1> <table class="table"> <thead> <tr> <th>タイトル</th> <th>日付</th> </tr> </thead> <tbody> {% for post in post_list %} <tr> <td>{{ post.title }}</td> <td>{{ post.date }}</td> </tr> {% endfor %} </tbody> </table>ルーティング設定
最後に記事一覧を表示させるための URL へアクセスした時に ListView を呼び出すよう、blog/urls.py を編集します。
blog/urls.pyfrom django.urls import path from . import views app_name = 'blog' urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('post_list', views.PostListView.as_view(), name='post_list'), # ここを追加 ]今回のように、何かを表示させるときには View, Template, URL がそれぞれ絡むことを覚えておいてください。
ユニットテスト作成
いよいよ、本記事の本題です。
Django のテストについて
どんどん機能を追加していくのは楽しいですが、普段はテストを書いているでしょうか?
各種チュートリアルなどでDjangoの簡単なアプリを作れるようになった方でも、
少し自分なりにいじった時にエラーを引き起こしてしまう場合があるかと思います。
また、Djangoをrunserver等で起動した際には特にエラーが出力されなくても
実際に画面をブラウザ経由で動かした時にエラーに気づく場合もあるかと思います。いくつかの操作を手動でテストするという方法はもちろんありますが、毎回そういったことを行うのは手間がかかりますよね。
そこで、Djangoの機能を用いてユニットテストを行うことを推奨します。
DjangoではUnitTestクラスを用いてテストを自動化することができるので、
最初にテスト用のコードだけ書いてしまえば後は何度も同じことをする必要はありません。テストの考えることは開発コードを考えるのと同じぐらい重要であり、
テストを作ってからアプリ動作のためのコードを書くという開発手法もあるぐらいです。これを機にテストを行えるようになり、あなたのテスト時間を節約してアプリ本体をより改善することに労力を費やしましょう。
フォルダ構成について
この時点では下記のようなフォルダ構成になっているはずです。
. ├── blog │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py # 注目 │ ├── urls.py │ └── views.py ├── db.sqlite3 ├── manage.py ├── mysite │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── templates └── blog ├── index.html └── post_list.htmlお気づきになられた方はいるかもしれませんが、blog ディレクトリ配下に tests.py というファイルが自動的に作成されています。
この tests.py の中に直接テストケースを作成していってもよいのですが、
model のテスト、view のテストとテストごとにファイルが分かれていた方が何かと管理しやすいので
test.pyは削除してから下記のように tests ディレクトリを作成し、中にそれぞれ空ファイルを作成しておきましょう。
tests ディレクトリ内のファイルも実行されるように、中身は空の _init_.py ファイルも作成しておくのがポイントです。
※init の前後にアンダーバー2つずつ. ├── blog │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── tests # 追加 │ │ ├── __init__.py # 追加 │ │ ├── test_models.py # 追加 │ │ ├── test_urls.py # 追加 │ │ └── test_views.py # 追加 ......なお、モジュールの名前は「test」で始めないと Django が認識してくれないので注意してください。
テストの書き方
Django では Python標準のTestCaseクラス(unittest.TestCase)を拡張した、
Django独自のTestCaseクラス(django.test.TestCase)を使います。
このクラスではアサーションというメソッドを使うことができ、返り値が期待する値であるかどうかをチェックする機能があります。また、前述の通りテストモジュールは「test」という文字列で始まっている必要があるのと、
テストメソッドも「test」という文字列で始める必要があります(詳細は後述します)。このルールを守ることで Django がテストメソッドをプロジェクト内から探し出し、自動で実行してくれるようになります。
test_models.py
それではまずは model のテストから作成していきましょう。
おさらいですが、blog/models.py に記述されている Post model はこのようになっています。models.py... class Post(models.Model): title = models.CharField('タイトル', max_length=200) text = models.TextField('本文') date = models.DateTimeField('日付', default=timezone.now) def __str__(self): # Post モデルが直接呼び出された時に返す値を定義 return self.title # 記事タイトルを返すこの model に対して、今回は次の3ケースでテストしましょう。
1.初期状態では何も登録されていないこと
2.1つレコードを適当に作成すると、レコードが1つだけカウントされること
3.内容を指定してデータを保存し、すぐに取り出した時に保存した時と同じ値が返されることではまずひとつめからです。
test_models.py を開き、必要なモジュールを宣言します。
test_models.pyfrom django.test import TestCase from blog.models import Postそしてテストクラスを作っていくのですが、必ず TestCase を継承したクラスにします。
test_models.pyfrom django.test import TestCase from blog.models import Post class PostModelTests(TestCase):さて、この PostModelTest クラスの中にテストメソッドを書いていきます。
TestCase を継承したクラスの中で「test」で始めることで、
Django がそれはテストメソッドであることを自動で認識してくれます。
そのため、def の後は必ず test で始まるメソッド名を名付けましょう。test_models.pyfrom django.test import TestCase from blog.models import Post class PostModelTests(TestCase): def test_is_empty(self): """初期状態では何も登録されていないことをチェック""" saved_posts = Post.objects.all() self.assertEqual(saved_posts.count(), 0)saved_posts に現時点の Post model を格納し、
assertEqual でカウント数(記事数)が「0」となっていることを確認しています。さて、これで一つテストを行う準備が整いました。
早速これで一回実行していきましょう。テストの実行は、manage.py が置いてあるディレクトリ (mysite内) で下記のコマンドを実行します。
実行すると、命名規則に従ったテストメソッドを Django が探し出し、実行してくれます。(blog) bash-3.2$ python3 manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). ..... ---------------------------------------------------------------------- Ran 1 tests in 0.009s OK一つのテストを実行し、エラーなく完了したことを意味しています。
ちなみに、先ほどは Post 内にデータが空 (=0) であることを確認しましたが、データが1つ存在していることを期待するようにしてみます。
test_models.py(一時的)from django.test import TestCase from blog.models import Post class PostModelTests(TestCase): def test_is_empty(self): """初期状態だけど1つはデータが存在しているかどうかをチェック (error が期待される)""" saved_posts = Post.objects.all() self.assertEqual(saved_posts.count(), 1)この時の test 実行結果は下記のようになっています。
(blog) bash-3.2$ python3 manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). F ====================================================================== FAIL: test_is_empty (blog.tests.test_models.PostModelTests) 初期状態だけど1つはデータが存在しているかどうかをチェック (error が期待される) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/masuyama/workspace/MyPython/MyDjango/blog/mysite/blog/tests/test_models.py", line 9, in test_is_empty self.assertEqual(saved_posts.count(), 1) AssertionError: 0 != 1 ---------------------------------------------------------------------- Ran 1 test in 0.002s FAILED (failures=1)AssertionError が出ており、期待される結果ではないためにテストは失敗していますね(実験としては成功です)。
Django のテストではデータベースへ一時的なデータの登録も create メソッドから実行できるので、
データを登録しないと確認できないような残りのテストも実行することができます。
下記に model のテストの書き方を載せておくので、参考にしてみてください。test_models.py(全文)from django.test import TestCase from blog.models import Post class PostModelTests(TestCase): def test_is_empty(self): """初期状態では何も登録されていないことをチェック""" saved_posts = Post.objects.all() self.assertEqual(saved_posts.count(), 0) def test_is_count_one(self): """1つレコードを適当に作成すると、レコードが1つだけカウントされることをテスト""" post = Post(title='test_title', text='test_text') post.save() saved_posts = Post.objects.all() self.assertEqual(saved_posts.count(), 1) def test_saving_and_retrieving_post(self): """内容を指定してデータを保存し、すぐに取り出した時に保存した時と同じ値が返されることをテスト""" post = Post() title = 'test_title_to_retrieve' text = 'test_text_to_retrieve' post.title = title post.text = text post.save() saved_posts = Post.objects.all() actual_post = saved_posts[0] self.assertEqual(actual_post.title, title) self.assertEqual(actual_post.text, text)test_urls.py
model 以外にも、urls.py に書いたルーティングがうまくいっているのかどうかを確認することもできます。
おさらいすると blog/urls.py はこのようになっていました。blog/urls.pyfrom django.urls import path from . import views app_name = 'blog' urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('post_list', views.PostListView.as_view(), name='post_list'), ]上記のルーティングでは /blog/ 以下に入力されるアドレスに従ったルーティングを設定しているので、
/blog/ 以下が ''(空欄) と 'post_list' であった時のテストをします。
それぞれのページへ view 経由でリダイレクトされた結果が期待されるものであるかどうかを、assertEqual を用いて比較してチェックします。test_urls.pyfrom django.test import TestCase from django.urls import reverse, resolve from ..views import IndexView, PostListView class TestUrls(TestCase): """index ページへのURLでアクセスする時のリダイレクトをテスト""" def test_post_index_url(self): view = resolve('/blog/') self.assertEqual(view.func.view_class, IndexView) """Post 一覧ページへのリダイレクトをテスト""" def test_post_list_url(self): view = resolve('/blog/post_list') self.assertEqual(view.func.view_class, PostListView)ここまでで一旦テストを実行しておきましょう。
※先ほど、データベースが空である状態のテストをしたときと比べると
データを登録するテストケースが増えているため
テスト用のデータベース作成、消去の処理がメッセージに出力されていることが分かります(blog) bash-3.2$ python3 manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). ..... ---------------------------------------------------------------------- Ran 5 tests in 0.007s OK Destroying test database for alias 'default'...test_views.py
最後に view のテストも行いましょう。
views.py はこのようになっていました。
views.pyfrom django.views import generic from .models import Post # Postモデルをimport class IndexView(generic.TemplateView): template_name = 'blog/index.html' class PostListView(generic.ListView): # generic の ListViewクラスを継承 model = Post # 一覧表示させたいモデルを呼び出しIndexView のテストでは、GET メソッドでアクセスした時にステータスコード 200(=成功) が返されることを確認します。
test_views.pyfrom django.test import TestCase from django.urls import reverse from ..models import Post class IndexTests(TestCase): """IndexViewのテストクラス""" def test_get(self): """GET メソッドでアクセスしてステータスコード200を返されることを確認""" response = self.client.get(reverse('blog:index')) self.assertEqual(response.status_code, 200)何か view でメソッドを追加したときは、
どんなにテストを書く時間がなくてもこれだけは最低限テストケースとして作成する癖をつけましょう。ListView の方もテストをしていきます。
同じく 200 のステータスコードが返ってくることの確認はもちろん、
ここではデータ(記事)を2つ追加した後に記事一覧を表示させ、
登録した記事のタイトルがそれぞれが一覧に含まれていることを確認するテストを作成します。なお、ここで少し特殊なメソッドを使います。
テストメソッドは「test」で始めるように前述しましたがsetUpとtearDownというメソッドが存在します。setUpメソッドではテストケース内で使うデータの登録をし、
tearDownメソッドでは setUp メソッド内で登録したデータの削除を行えます。
(どちらも、どんなデータを登録するかは明示的に記述する必要があることには注意しましょう)同じテストケースの中で何回もデータの登録をするような処理を書くのは手間&テストに時間がかかる要因になるので、
共通する処理は一箇所にまとめてしまおうというものです。これらのメソッドを使い、test_views.py を作成するとこのようになります。
test_views.pyfrom django.test import TestCase from django.urls import reverse from ..models import Post class IndexTests(TestCase): """IndexViewのテストクラス""" def test_get(self): """GET メソッドでアクセスしてステータスコード200を返されることを確認""" response = self.client.get(reverse('blog:index')) self.assertEqual(response.status_code, 200) class PostListTests(TestCase): def setUp(self): """ テスト環境の準備用メソッド。名前は必ず「setUp」とすること。 同じテストクラス内で共通で使いたいデータがある場合にここで作成する。 """ post1 = Post.objects.create(title='title1', text='text1') post2 = Post.objects.create(title='title2', text='text2') def test_get(self): """GET メソッドでアクセスしてステータスコード200を返されることを確認""" response = self.client.get(reverse('blog:post_list')) self.assertEqual(response.status_code, 200) def test_get_2posts_by_list(self): """GET でアクセス時に、setUp メソッドで追加した 2件追加が返されることを確認""" response = self.client.get(reverse('blog:post_list')) self.assertEqual(response.status_code, 200) self.assertQuerysetEqual( # Postモデルでは __str__ の結果としてタイトルを返す設定なので、返されるタイトルが投稿通りになっているかを確認 response.context['post_list'], ['<Post: title1>', '<Post: title2>'], ordered = False # 順序は無視するよう指定 ) self.assertContains(response, 'title1') # html 内に post1 の title が含まれていることを確認 self.assertContains(response, 'title2') # html 内に post2 の title が含まれていることを確認 def tearDown(self): """ setUp で追加したデータを消す、掃除用メソッド。 create とはなっているがメソッド名を「tearDown」とすることで setUp と逆の処理を行ってくれる=消してくれる。 """ post1 = Post.objects.create(title='title1', text='text1') post2 = Post.objects.create(title='title2', text='text2')この状態でテストを実行すると model, url, view で合計 8 つのテストが実行されます。
(blog) bash-3.2$ python3 manage.py test Creating test database for alias 'default'... System check identified no issues (0 silenced). ........ ---------------------------------------------------------------------- Ran 8 tests in 0.183s OK Destroying test database for alias 'default'...これで、これまで書いたコードについてユニットテストを作成することができました。
他にも期待される template が呼び出されているかどうか等、
Django 独自のテスト方法を用いたテストで冗長的にチェックする方法もありますが
いまは使いまわしでもよいので処理を増やす時にテストを作成する癖をつけ、後々のチェックの手間を省くようにしていきましょう。
- 投稿日:2020-12-07T20:16:04+09:00
rails Docker対処法
#間違い docker-compose build # 正解 docker-compose up --build #間違い docker rmi sample-rails-docker_app #正解 docker rmi 91b0abfeb981 Dockerイメージを削除するときはリポジトリではなく、イメージIDで削除する。katoatsushi@MacBook-Pro sample-rails-docker % docker images REPOSITORY TAG IMAGE ID CREATED SIZE sample-rails-docker_app latest 1cb13ce83e34 4 minutes ago 969MB <none> <none> 424f83731ce0 2 hours ago 969MB <none> <none> 91dde8024805 2 hours ago 969MB room_app_app latest 4dcc6beedcfd 5 days ago 989MB <none> <none> 9b3dc2b6e276 5 days ago 989MB <none> <none> e1de5c89d92e 5 days ago 897MB <none> <none> f94f1829487e 5 days ago 897MB myapp_web latest ec1173f0bb40 5 days ago 969MB docker_sample_web latest db6e88beef89 5 days ago 989MB mysql 5.7 ae0658fdbad5 2 weeks ago 449MB mysql 8.0 dd7265748b5d 2 weeks ago 545MB ruby 2.6 39853018958e 2 weeks ago 840MB ruby 2.6.5 ad10dfbc638b 8 months ago 840MB ruby 2.6.1 99ef552a6db8 21 months ago 876MB
- 投稿日:2020-12-07T20:12:33+09:00
dockerコンテナ内の複数python環境に外部から接続する方法(jupyter-notebook利用)
はじめに
以前に
venv
を使った仮想環境の構築について記事を書いたことがあるが、コンテナ内にPython環境を構築して外部からそこにアクセスする使い方は自分で試した事がなかったため、今回はそれを試してみる。実行環境
【Docker導入環境】
・Ubuntu 20.04 LTS(GCP上)
・docker 19.03.13
・docker-compose 1.25.0手順
1.環境準備
2.コンテナ構築用のDockerfile作成(2つ)
3.docker-compose.yml
でコンテナ起動
4.操作確認1.環境準備
GCP上にVMインスタンスを作成
※イメージはUbuntu20.4 LTSパッケージ管理ツールのアップデート
$ sudo apt update $ sudo apt -y upgradedockerのインストール
【Dockerコンテナ内のUbuntuではsystemctlは使えない】の手順1を参考に。docker-composeのインストール
$ sudo apt install -y docker-compose問題なくインストールできているか確認
$ docker-compose --version docker-compose version 1.25.0, build unknown2.コンテナ構築用のDockerfile作成(2つ)
Dockerfile格納用のディレクト作成
$ sudo mkdir ./con1 $ sudo mkdir ./con2Dockerfileの作成
コンテナ1のDockerfile作成
$ sudo nano ./con1/DockerfileDockerfile# ベースイメージの取得 FROM python:3 # 適当にパッケージのインストール RUN pip install jupyter notebook RUN pip install numpy # マウント用のディレクトリを作成 RUN mkdir /root/analysis ########################## # コンフィグファイルの設定# ########################## RUN jupyter notebook --generate-config # ⇒このコマンドにより [/root/.jupyter/jupyter_notebook_config.py] が生成される # jupyter-notebook上のホワイトリストを許可(ホスト側でファイウォールを設定するためOK) RUN echo "c.NotebookApp.ip = '0.0.0.0'" >> /root/.jupyter/jupyter_notebook_config.py # 初期ディレクトリの設定 RUN echo "c.NotebookApp.notebook_dir = '/root/analysis'" >> /root/.jupyter/jupyter_notebook_config.py # パスワード認証 or Token認証を無効化(公式では非推奨) RUN echo "c.NotebookApp.token = ''" >> /root/.jupyter/jupyter_notebook_config.py RUN echo "c.NotebookApp.password = ''" >> /root/.jupyter/jupyter_notebook_config.py # ポート開放 EXPOSE 8888 CMD ["jupyter", "notebook","--allow-root"]
コンテナ2のDockerfile作成(インストールパッケージ以外1と同じ)
$ sudo nano ./con2/DockerfileDockerfile# ベースイメージの取得 FROM python:3 # 適当にパッケージのインストール RUN pip install jupyter notebook RUN pip install pandas # マウント用のディレクトリを作成 RUN mkdir /root/analysis ########################## # コンフィグファイルの設定# ########################## RUN jupyter notebook --generate-config # ⇒このコマンドにより [/root/.jupyter/jupyter_notebook_config.py] が生成される # jupyter-notebook上のホワイトリストを許可(ホスト側でファイウォールを設定するためOK) RUN echo "c.NotebookApp.ip = '0.0.0.0'" >> /root/.jupyter/jupyter_notebook_config.py # 初期ディレクトリの設定 RUN echo "c.NotebookApp.notebook_dir = '/root/analysis'" >> /root/.jupyter/jupyter_notebook_config.py # パスワード認証 or Token認証を無効化(公式では非推奨) RUN echo "c.NotebookApp.token = ''" >> /root/.jupyter/jupyter_notebook_config.py RUN echo "c.NotebookApp.password = ''" >> /root/.jupyter/jupyter_notebook_config.py # ポート開放 EXPOSE 8888 CMD ["jupyter", "notebook","--allow-root"]3.
docker-compose.yml
でコンテナ起動実行ファイルの格納場所を準備
※各コンテナ毎にマウント先として指定したいたいめ。$ sudo mkdir ./con1/analysis_file $ sudo mkdir ./con2/analysis_file
docker-compose.yml
の作成$ sudo nano ./docker-compose.ymldocker-compose.ymlversion: '3.7' services: python1: build: context: ./con1 dockerfile: Dockerfile volumes: - ./con1/analysis_file:/root/analysis ports: - 8080:8888 python2: build: context: ./con2 dockerfile: Dockerfile volumes: - ./con2/analysis_file:/root/analysis ports: - 8081:88884.操作確認
全サービスの起動
$ docker-compose up
※初回起動時は、ビルドやイメージのダウンロードが自動で開始される。
以下のURLにアクセスして、認証なしでnotebookが開くか確認する。
http://[VMの外部IP(グローバルIPアドレス)]:8080/
http://[VMの外部IP(グローバルIPアドレス)]:8081/
試しに
port:8080
で開かれるnotebookでimport
コマンドを実行してみると、./con1/Dockerfile
でパッケージインストールされていないものはできないはず。また、作成したファイルはホスト側のディレクトリをマウントしていて、そこをjupyter noteobookの初期ディレクトリに設定しているため、作成ファイルはホスト側上で永続化されるはず。
- 投稿日:2020-12-07T19:39:51+09:00
【Docker】できること・操作の流れ
本投稿の目的
・Dockerの操作についての議事録です。
学習に使った教材
Udemyの "米国AI開発者がゼロから教えるDocker講座" を教材として使用しました。
○Dockerとは?
・PC上での仮想環境構築を用意にするためのツール
・Host PC上に複数のcontainerを生成しそこにconatainer分の仮想環境を作成する○Docker導入のメリット
・Host PC とは別のフラットな空間で環境構築するためコンフリクトが起きずらい
・Container作成は決められたルールで簡単に作成できる
・他人に全く同じ仮想環境を配布することができる○Dockerの要素
・Dockerは以下4つの要素から構成される
①Dockerfile
②Docker Hub
③Docker image
④Docker container○各要素の説明
①Dockerfile
・テキストファイル
・ここにimage作成時に必要な情報を記述する②DockerHub
・imageを保存するリポジトリ集団
・ContainerをDockerfileから作成しない際にはDocker Hubを使用③Docker image
・containerを作成する際に必要なリソース
・①Dockerfileを使って作成するファイル
・②Docker Hubから直接取得することも可能
・imageから作成したcontainerをここにimageとして保管することも可能④Docker container
・ここに,サーバー用のOS,Softwareなど仮想環境のリソースを設置
・これが一番得たいもので,Host PC上に別の仮想PCを作成できるようなイメージ
・各containerごとに仮想PCを作成できる
・③Docker imageから作成する
・作成後に情報を追加して上書き保存することが可能○container作成までの流れ
・作成方法は以下2つの方法がある
1.imageを作成する場合
①Dockerfile作成
②image作成
③container作成2.作成済みimageを利用する場合
①image取得(Docker Hubから)
②container作成
- 投稿日:2020-12-07T17:11:36+09:00
既に進行中のコンソールセッションがあるため、リモートコンピューター上の他のコンソールセッションに接続できませんでした。
事象 : 作ったコンテナにリモートデスクトップ接続できない
- 環境
- Windows10 Pro 64bit バージョン1909
- Docker Desktop 2.4.0.0
- Docker version 19.03.13
- docker-compose version 1.27.4
原因 : 自分のPCの同じポートに接続しようとしているから
以下の記事を見て「はっ!」とした・・・Windows on Dockerを使って自分のPCにコンテナ作っているんだから同じポート指定したら意味ないじゃん!
これまで、サーバ上にDockerコンテナを作って使っていたので気が付かなかった・・・。繋がらないIPアドレスをレジストリエディタで検索してみたところ
なんだ、自分も接続先と同じ固定IPアドレスを持っていたのか
自分自身に対してリモート接続を行おうとしていたためだった。
リモートデスクトップ接続で - プログラまあのネタ帳version: "3.7" services: hoge: ...省略... ports: - "8080:8080" # アプリケーションサーバ用 - "3389:3389" # リモートデスクトップ用対応 : ポートを変える
自分のPC内同士で接続するのだから別々のポートを指定しなければおかしくなる。
よく忘れるがポートは- "{ホストのポート}:{コンテナのポート}"
。ports: - "18080:8080" - "13389:3389"
- 投稿日:2020-12-07T17:08:55+09:00
Ruby on Rails(v6対応)をDockerで環境構築しHerokuにデプロイする
本記事の対象者
Railsは未経験でしたが、とりあえず学習環境つくろうと思い、備忘録のため記事化しました。
アプリケーションの開発に集中するため、Docker環境をコピペで作りたい人向けの記事です。
Rails6を対象としています。自身のスキルレベル
- Railsは全くの未経験(なんにも知りません)
- Dockerはそこそこやったことあります
- 他フレームワークでWebの基本的な開発はやったことあります
必要なファイル
環境構築に必要なファイルは以下の通りです。
myappディレクトリを作って、以下のファイルを作成してください。myapp - Dockerfile - Gemfile - Gemfile.lock - entrypoint.sh - docker-compose.yml1. 環境構築
1-1. Dockerfile
DockerfileFROM ruby:2.6.5 RUN apt-get update -qq && apt-get install -y nodejs postgresql-client # yarn RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && apt-get install -y yarn # Node.js RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - && \ apt-get install nodejs RUN mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"]1-2. docker-compose.yml
後にHerokuを使うため、dbはPostgreを指定しました。
(Postgreだと無料でHeroku環境が作れるため)docker-compose.ymlversion: '3' services: db: image: postgres environment: POSTGRES_HOST_AUTH_METHOD: 'trust' volumes: - ./tmp/db:/var/lib/postgresql/data web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp ports: - "3000:3000" depends_on: - db1-3. Gemfile
Rails用のパッケージファイルだそうです。
バージョン6を指定しています。Gemfilesource 'https://rubygems.org' gem 'rails', '~> 6'1-4. Gemfile.lock
空の
Gemfile.lock
を作成しておきます。
後にビルド時に自動的に更新されます。1-5. entrypoint.sh
entrypoint.sh#!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /myapp/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@"2. ビルド
Dockerのビルド。
$ docker-compose buildパッケージのインストール。
$ docker-compose run web yarnRails6から必須のwebpackerのインストールを行なっておきます。
$ docker-compose run web bundle exec rails webpacker:installコンテナ起動とDBの作成を行います。
$ docker-compose up $ docker-compose run web rake db:createここまで成功したらRailsが起動していることを確認します。
同時に、ローカル環境にRailsの環境一式がインストールされていることを確認してください。3. Herokuデプロイ
作った環境をインターネット経由でもみたい方は、Herokuにデプロイするところまでやってみましょう。
Herokuのアカウントが無い人は事前に作成しておいてください。
作ったローカル環境をHerokuにデプロイします。# herokuのコンテナレジストリにログイン heroku container:login # 新しいappを作成 heroku create # イメージを作成してコンテナレジストリにpush heroku container:push web # postgresqlアドオンの無料プランを追加 heroku addons:create heroku-postgresql:hobby-dev # DBセットアップ heroku run rails db:migrate # イメージをherokuへデプロイ heroku container:release web # 実際にアクセスして/usersを確認してみる heroku open4. アプリを開発
ここまでできたら、あとはアプリを開発のみです。
お疲れ様でした。
- 投稿日:2020-12-07T16:49:12+09:00
Alpineでバージョンを指定してPythonをいれる
Dockerでflask & vueのイメージをビルドしたかったときに詰まった.
DockerfileRUN apk update && apk add --no-cache python3 && python3 --version # 3.8.62020/12/07時点は自動的に
3.8.6
がインストールされた.しかし,PipでPytorchをいれるときにエラーが出てしまったのでPythonのバージョンを指定することにまた,apkでインストールされるバージョンはこちらから確認できそう
バージョンを指定してPythonをいれる
DockerfileRUN apk update && apk add --no-chache --repository http://dl-cdn.alpinelinux.org/alpine/v3.10/main python3~=3.7リポジトリとバージョンを指定するとできた.
リポジトリのブランチとPythonのバージョンは先ほどのサイトで確認できる.上の例でいうところのv3.10
とpython3~=3.7
参考文献
- 投稿日:2020-12-07T11:18:32+09:00
Puppeteer実行環境をAppEngineからCloudRunに移したときのメモ
FlightBooksというサービスを個人で開発していまして、先日ghostscriptの情報を教えてもらいました。現状PDFの生成部分はGoogle AppEngineのStandard Node環境でPuppeteerが動作していて、この部分をCloudRunに移植しつつ、gsコマンドと両方入ってる環境を作ろうと思いました。この辺毎回ググることになりそうなのでメモとして残そうと思います。
Chromeはaptでインストールする
今回ベースイメージは、node:12-slimを選択してみました。そのためOSイメージはdebian stretchになります。当初npmでPuppeteerをインストールしていたのですが、依存ライブラリの解決が面倒だったので、aptでインストールすることにしました。Chromeにはいくつかバージョンがありますが、こちらのRepoをみて本家のStable版をインストールすることにしました。
https://github.com/buildkite/docker-puppeteer/blob/master/Dockerfile
RUN apt-get update \ && apt-get install -y wget gnupg ca-certificates \ && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ && apt-get update \Puppeteerをpuppeteer-coreにする
Chromeは既にインストール済みのため、バイナリイメージを含まないnpmモジュールをインストールします。
$ npm remove puppeteer $ npm install --save puppeteer-core必要なフォントとfontconfigを入れる
日本語対応したいので、noto fontを全面的に活用することにします。明朝体もインストールするためextraもstretch-backportsを利用していれます。英字は逆に最低限のみで、MS系互換フォントの
fonts-liberation
を利用します。&& echo 'deb http://deb.debian.org/debian stretch-backports main' >> /etc/apt/sources.list \ && apt update \ && apt install -y fonts-noto-cjk-extra -t stretch-backports \&& apt install -y google-chrome-stable fontconfig fonts-liberation fonts-noto-cjk \Puppeteer.launchでパスを指定
puppeteer-coreにしたので、Chromeの実行パスを指定する必要があります。
# which google-chrome-stable /usr/bin/google-chrome-stable
const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome-stable' });以上で、無事CloudRun移植に必要なDockerfileのベースが出来上がりました。これをもとに追加の設定を入れ込んでみたいと思います。
- 投稿日:2020-12-07T08:59:53+09:00
Dockerを使ってPostgreSQL+Goの開発環境を作ってみた
はじめに
GoからPostgreSQLに接続する部分がよくわかってなかったので、自分でDocker環境作って試してみた。
参考
ここを大いに参考にさせてもらった。
Go PostgreSQLにつないでみる - Qiita
Docker で作る postgres 環境 | CrudzooDocker
ポイントはnetworks部分を同じにしてるところ。
ネットワークの準備
# docker network create postgres-test-network # docker network ls NETWORK ID NAME DRIVER SCOPE xxxxxxxxxxxx postgres-test-network bridge localPostgreSQL
こんな感じ。
POSTGRES_HOST_AUTH_METHOD: true
は推奨してないって言われたけど、ローカルのお試し環境だから無視した。DockerfileFROM postgres:11-alpine ENV LANG ja_JP.utf8docker-compose.ymlversion: '3' services: db: build: . tty: true ports: - 5434:5432 environment: POSTGRES_USER: root POSTGRES_HOST_AUTH_METHOD: trust networks: default: external: name: postgres-test-networkこれで立ち上げる
docker-compose up -dGo
DockerfileFROM golang:latest RUN mkdir /go/src/work WORKDIR /go/src/work ADD . /go/src/workdocker-compose.ymlversion: '3' services: app: build: . tty: true volumes: - .:/go/src/work networks: default: external: name: postgres-test-networkこれで立ち上げる
docker-compose up -dアクセス部分
Goの方のコンテナの中では、これを実行する。
ここをほぼほぼコピペさせてもらった。
Go PostgreSQLにつないでみる - Qiitamain.gopackage main import ( "database/sql" "fmt" _ "github.com/lib/pq" ) type EMPLOYEE struct { ID string NUMBER string } func main() { db, err := sql.Open("postgres", "host=db port=5432 user=root sslmode=disable") defer db.Close() if err != nil { fmt.Println(err) } if _, err := db.Exec("CREATE TABLE employee (emp_id serial PRIMARY KEY, emp_number INTEGER);"); err != nil { fmt.Println(err) } // INSERT var empID string id := 4 number := 4445 err = db.QueryRow("INSERT INTO employee (emp_id, emp_number) VALUES($1,$2) RETURNING emp_id", id, number).Scan(&empID) if err != nil { fmt.Println(err) } fmt.Println(empID) // SELECT rows, err := db.Query("SELECT * FROM employee") if err != nil { fmt.Println(err) } var es []EMPLOYEE for rows.Next() { var e EMPLOYEE rows.Scan(&e.ID, &e.NUMBER) es = append(es, e) } fmt.Printf("%v", es) }
lib/pq
が入ってなかったので、go get
した。ドライバーらしい。go get github.com/lib/pqいろいろ試してるとき、ネットワークつながらないって何回か言われたので、pingして確認した。
Dockerがうまいこと、DNSサーバーたててcompose
にかいたservices
の名前で登録してくれるらしい。# ping db PING db (172.20.0.3) 56(84) bytes of data. 64 bytes from work2_postgres_db_1.postgres-test-network (172.20.0.3): icmp_seq=1 ttl=64 time=0.258 msこれで。実行したら、それっぽく動いた。
go run main.goPosticoで確認
一応、DBの中身もGUIツールで確認した。良い感じ。
Postico – a modern PostgreSQL client for the Mac
おわりに
ローカルでに動く環境作れたので、いろいろ試してみたい。